rt-5.0.1/000755 000765 000024 00000000000 14005021226 013000 5ustar00sunnavystaff000000 000000 rt-5.0.1/devel/000755 000765 000024 00000000000 14005011336 014100 5ustar00sunnavystaff000000 000000 rt-5.0.1/install-sh000755 000765 000024 00000032464 14005011336 015016 0ustar00sunnavystaff000000 000000 #!/bin/sh # install - install a program, script, or datafile scriptversion=2006-12-25.00 # This originates from X11R5 (mit/util/scripts/install.sh), which was # later released in X11R6 (xc/config/util/install.sh) with the # following copyright and license. # # Copyright (C) 1994 X Consortium # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- # TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Except as contained in this notice, the name of the X Consortium shall not # be used in advertising or otherwise to promote the sale, use or other deal- # ings in this Software without prior written authorization from the X Consor- # tium. # # # FSF changes to this file are in the public domain. # # Calling this script install-sh is preferred over install.sh, to prevent # `make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. nl=' ' IFS=" "" $nl" # set DOITPROG to echo to test this script # Don't use :- since 4.3BSD and earlier shells don't like it. doit=${DOITPROG-} if test -z "$doit"; then doit_exec=exec else doit_exec=$doit fi # Put in absolute file names if you don't have them in your path; # or use environment vars. chgrpprog=${CHGRPPROG-chgrp} chmodprog=${CHMODPROG-chmod} chownprog=${CHOWNPROG-chown} cmpprog=${CMPPROG-cmp} cpprog=${CPPROG-cp} mkdirprog=${MKDIRPROG-mkdir} mvprog=${MVPROG-mv} rmprog=${RMPROG-rm} stripprog=${STRIPPROG-strip} posix_glob='?' initialize_posix_glob=' test "$posix_glob" != "?" || { if (set -f) 2>/dev/null; then posix_glob= else posix_glob=: fi } ' posix_mkdir= # Desired mode of installed file. mode=0755 chgrpcmd= chmodcmd=$chmodprog chowncmd= mvcmd=$mvprog rmcmd="$rmprog -f" stripcmd= src= dst= dir_arg= dst_arg= copy_on_change=false no_target_directory= usage="\ Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE or: $0 [OPTION]... SRCFILES... DIRECTORY or: $0 [OPTION]... -t DIRECTORY SRCFILES... or: $0 [OPTION]... -d DIRECTORIES... In the 1st form, copy SRCFILE to DSTFILE. In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. In the 4th, create DIRECTORIES. Options: --help display this help and exit. --version display version info and exit. -c (ignored) -C install only if different (preserve the last data modification time) -d create directories instead of installing files. -g GROUP $chgrpprog installed files to GROUP. -m MODE $chmodprog installed files to MODE. -o USER $chownprog installed files to USER. -s $stripprog installed files. -t DIRECTORY install into DIRECTORY. -T report an error if DSTFILE is a directory. Environment variables override the default commands: CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG " while test $# -ne 0; do case $1 in -c) ;; -C) copy_on_change=true;; -d) dir_arg=true;; -g) chgrpcmd="$chgrpprog $2" shift;; --help) echo "$usage"; exit $?;; -m) mode=$2 case $mode in *' '* | *' '* | *' '* | *'*'* | *'?'* | *'['*) echo "$0: invalid mode: $mode" >&2 exit 1;; esac shift;; -o) chowncmd="$chownprog $2" shift;; -s) stripcmd=$stripprog;; -t) dst_arg=$2 shift;; -T) no_target_directory=true;; --version) echo "$0 $scriptversion"; exit $?;; --) shift break;; -*) echo "$0: invalid option: $1" >&2 exit 1;; *) break;; esac shift done if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then # When -d is used, all remaining arguments are directories to create. # When -t is used, the destination is already specified. # Otherwise, the last argument is the destination. Remove it from $@. for arg do if test -n "$dst_arg"; then # $@ is not empty: it contains at least $arg. set fnord "$@" "$dst_arg" shift # fnord fi shift # arg dst_arg=$arg done fi if test $# -eq 0; then if test -z "$dir_arg"; then echo "$0: no input file specified." >&2 exit 1 fi # It's OK to call `install-sh -d' without argument. # This can happen when creating conditional directories. exit 0 fi if test -z "$dir_arg"; then trap '(exit $?); exit' 1 2 13 15 # Set umask so as not to create temps with too-generous modes. # However, 'strip' requires both read and write access to temps. case $mode in # Optimize common cases. *644) cp_umask=133;; *755) cp_umask=22;; *[0-7]) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw='% 200' fi cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; *) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw=,u+rw fi cp_umask=$mode$u_plus_rw;; esac fi for src do # Protect names starting with `-'. case $src in -*) src=./$src;; esac if test -n "$dir_arg"; then dst=$src dstdir=$dst test -d "$dstdir" dstdir_status=$? else # Waiting for this to be detected by the "$cpprog $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if test ! -f "$src" && test ! -d "$src"; then echo "$0: $src does not exist." >&2 exit 1 fi if test -z "$dst_arg"; then echo "$0: no destination specified." >&2 exit 1 fi dst=$dst_arg # Protect names starting with `-'. case $dst in -*) dst=./$dst;; esac # If destination is a directory, append the input filename; won't work # if double slashes aren't ignored. if test -d "$dst"; then if test -n "$no_target_directory"; then echo "$0: $dst_arg: Is a directory" >&2 exit 1 fi dstdir=$dst dst=$dstdir/`basename "$src"` dstdir_status=0 else # Prefer dirname, but fall back on a substitute if dirname fails. dstdir=` (dirname "$dst") 2>/dev/null || expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$dst" : 'X\(//\)[^/]' \| \ X"$dst" : 'X\(//\)$' \| \ X"$dst" : 'X\(/\)' \| . 2>/dev/null || echo X"$dst" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q' ` test -d "$dstdir" dstdir_status=$? fi fi obsolete_mkdir_used=false if test $dstdir_status != 0; then case $posix_mkdir in '') # Create intermediate dirs using mode 755 as modified by the umask. # This is like FreeBSD 'install' as of 1997-10-28. umask=`umask` case $stripcmd.$umask in # Optimize common cases. *[2367][2367]) mkdir_umask=$umask;; .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; *[0-7]) mkdir_umask=`expr $umask + 22 \ - $umask % 100 % 40 + $umask % 20 \ - $umask % 10 % 4 + $umask % 2 `;; *) mkdir_umask=$umask,go-w;; esac # With -d, create the new directory with the user-specified mode. # Otherwise, rely on $mkdir_umask. if test -n "$dir_arg"; then mkdir_mode=-m$mode else mkdir_mode= fi posix_mkdir=false case $umask in *[123567][0-7][0-7]) # POSIX mkdir -p sets u+wx bits regardless of umask, which # is incompatible with FreeBSD 'install' when (umask & 300) != 0. ;; *) tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0 if (umask $mkdir_umask && exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1 then if test -z "$dir_arg" || { # Check for POSIX incompatibilities with -m. # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or # other-writeable bit of parent directory when it shouldn't. # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. ls_ld_tmpdir=`ls -ld "$tmpdir"` case $ls_ld_tmpdir in d????-?r-*) different_mode=700;; d????-?--*) different_mode=755;; *) false;; esac && $mkdirprog -m$different_mode -p -- "$tmpdir" && { ls_ld_tmpdir_1=`ls -ld "$tmpdir"` test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" } } then posix_mkdir=: fi rmdir "$tmpdir/d" "$tmpdir" else # Remove any dirs left behind by ancient mkdir implementations. rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null fi trap '' 0;; esac;; esac if $posix_mkdir && ( umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" ) then : else # The umask is ridiculous, or mkdir does not conform to POSIX, # or it failed possibly due to a race condition. Create the # directory the slow way, step by step, checking for races as we go. case $dstdir in /*) prefix='/';; -*) prefix='./';; *) prefix='';; esac eval "$initialize_posix_glob" oIFS=$IFS IFS=/ $posix_glob set -f set fnord $dstdir shift $posix_glob set +f IFS=$oIFS prefixes= for d do test -z "$d" && continue prefix=$prefix$d if test -d "$prefix"; then prefixes= else if $posix_mkdir; then (umask=$mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break # Don't fail if two instances are running concurrently. test -d "$prefix" || exit 1 else case $prefix in *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; *) qprefix=$prefix;; esac prefixes="$prefixes '$qprefix'" fi fi prefix=$prefix/ done if test -n "$prefixes"; then # Don't fail if two instances are running concurrently. (umask $mkdir_umask && eval "\$doit_exec \$mkdirprog $prefixes") || test -d "$dstdir" || exit 1 obsolete_mkdir_used=true fi fi fi if test -n "$dir_arg"; then { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 else # Make a couple of temp file names in the proper directory. dsttmp=$dstdir/_inst.$$_ rmtmp=$dstdir/_rm.$$_ # Trap to clean up those temp files at exit. trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 # Copy the file name to the temp name. (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && # and set any options; do chmod last to preserve setuid bits. # # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $cpprog $src $dsttmp" command. # { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && # If -C, don't bother to copy if it wouldn't change the file. if $copy_on_change && old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && eval "$initialize_posix_glob" && $posix_glob set -f && set X $old && old=:$2:$4:$5:$6 && set X $new && new=:$2:$4:$5:$6 && $posix_glob set +f && test "$old" = "$new" && $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 then rm -f "$dsttmp" else # Rename the file to the real destination. $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || # The rename failed, perhaps because mv can't rename something else # to itself, or perhaps because mv is so ancient that it does not # support -f. { # Now remove or move aside any old file at destination location. # We try this two ways since rm can't unlink itself on some # systems and the destination file might be busy for other # reasons. In this case, the final cleanup might fail but the new # file should still install successfully. { test ! -f "$dst" || $doit $rmcmd -f "$dst" 2>/dev/null || { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } } || { echo "$0: cannot unlink or rename $dst" >&2 (exit 1); exit 1 } } && # Now rename the file to the real destination. $doit $mvcmd "$dsttmp" "$dst" } fi || exit 1 trap '' 0 fi done # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-end: "$" # End: rt-5.0.1/configure.ac000755 000765 000024 00000043410 14005011336 015274 0ustar00sunnavystaff000000 000000 autoconf; exec ./configure $@ dnl dnl Process this file with autoconf to produce a configure script dnl dnl Embed in generated ./configure script the following CVS info: AC_REVISION($Revision$)dnl dnl Setup autoconf AC_PREREQ([2.53]) AC_INIT(RT, m4_esyscmd([( git describe --tags || cat ./.tag 2> /dev/null || echo "rt-3.9.EXPORTED" )| tr -d "\n"]), [rt-bugs@bestpractical.com]) AC_CONFIG_SRCDIR([lib/RT.pm]) dnl Save our incant early since $@ gets overwritten by some macros. dnl ${ac_configure_args} is available later, but it's quoted differently dnl and undocumented. See http://www.spinics.net/lists/ac/msg10022.html. AC_SUBST(CONFIGURE_INCANT, "$0 $@") dnl Extract RT version number components AC_SUBST([rt_version_major], m4_bregexp(AC_PACKAGE_VERSION,[^rt-\(\w+\)\.\(\w+\)\.\(.+\)$],[\1])) AC_SUBST([rt_version_minor], m4_bregexp(AC_PACKAGE_VERSION,[^rt-\(\w+\)\.\(\w+\)\.\(.+\)$],[\2])) AC_SUBST([rt_version_patch], m4_bregexp(AC_PACKAGE_VERSION,[^rt-\(\w+\)\.\(\w+\)\.\(.+\)$],[\3])) test "x$rt_version_major" = 'x' && rt_version_major=0 test "x$rt_version_minor" = 'x' && rt_version_minor=0 test "x$rt_version_patch" = 'x' && rt_version_patch=0 dnl Check for programs AC_PROG_INSTALL AC_ARG_VAR([PERL],[Perl interpreter command]) AC_PATH_PROG([PERL], [perl], [not found]) if test "$PERL" = 'not found'; then AC_MSG_ERROR([cannot use $PACKAGE_NAME without perl]) fi dnl BSD find uses -perm +xxxx, GNU find has deprecated this syntax in favour of dnl -perm /xxx. AC_MSG_CHECKING([checking version of find]) AS_IF([find --version 2>&1 | grep 'GNU'], [ FINDPERM="/" AC_MSG_RESULT([configuring for GNU find]) ], [ FINDPERM="+" AC_MSG_RESULT([configuring for BSD find]) ]) AC_SUBST([FINDPERM]) dnl WEB_HANDLER AC_ARG_WITH(web-handler, AC_HELP_STRING([--with-web-handler=LIST], [comma separated list of web-handlers RT will be able to use. Default is fastcgi. Valid values are modperl1, modperl2, fastcgi and standalone. To successfully run RT you need only one. ]), WEB_HANDLER=$withval, WEB_HANDLER=fastcgi) my_web_handler_test=$($PERL -e 'print "ok" unless grep $_ !~ /^(modperl1|modperl2|fastcgi|standalone)$/i, grep defined && length, split /\s*,\s*/, $ARGV@<:@0@:>@' $WEB_HANDLER) if test "$my_web_handler_test" != "ok"; then AC_MSG_ERROR([Only modperl1, modperl2, fastcgi and standalone are valid web-handlers]) fi AC_SUBST(WEB_HANDLER) dnl Defaults paths for installation AC_PREFIX_DEFAULT([/opt/rt5]) RT_ENABLE_LAYOUT # ACRT_USER_EXISTS( users, variable, default ) # - users is a list of users [www apache www-docs] # from highest to lowest priority to high priority (i.e. first match) # - variable is what you set with the result # AC_DEFUN([ACRT_USER_GUESS], [ $2=$3 for x in $1; do AC_MSG_CHECKING([if user $x exists]) AS_IF([ $PERL -e"exit( defined getpwnam('$x') ? 0 : 1)" ], [ AC_MSG_RESULT([found]); $2=$x ; break], [ AC_MSG_RESULT([not found]) ]) done ]) AC_DEFUN([ACRT_GROUP_GUESS], [ $2=$3 for x in $1; do AC_MSG_CHECKING([if group $x exists]) AS_IF([ $PERL -e"exit( defined getgrnam('$x') ? 0 : 1)" ], [ AC_MSG_RESULT([found]); $2=$x ; break], [ AC_MSG_RESULT([not found]) ]) done ]) dnl BIN_OWNER AC_ARG_WITH(bin-owner, AC_HELP_STRING([--with-bin-owner=OWNER], [user that will own RT binaries (default root)]), BIN_OWNER=$withval, BIN_OWNER=root) AC_SUBST(BIN_OWNER) dnl LIBS_OWNER AC_ARG_WITH(libs-owner, AC_HELP_STRING([--with-libs-owner=OWNER], [user that will own RT libraries (default root)]), LIBS_OWNER=$withval, LIBS_OWNER=root) AC_SUBST(LIBS_OWNER) dnl LIBS_GROUP AC_ARG_WITH(libs-group, AC_HELP_STRING([--with-libs-group=GROUP], [group that will own RT binaries (default bin)]), LIBS_GROUP=$withval, LIBS_GROUP=bin) AC_SUBST(LIBS_GROUP) dnl DB_TYPE AC_ARG_WITH(db-type, AC_HELP_STRING([--with-db-type=TYPE], [sort of database RT will use (default: mysql) (mysql, Pg, Oracle and SQLite are valid)]), DB_TYPE=$withval, DB_TYPE=mysql) if test "$DB_TYPE" != 'mysql' -a "$DB_TYPE" != 'Pg' -a "$DB_TYPE" != 'SQLite' -a "$DB_TYPE" != 'Oracle' ; then AC_MSG_ERROR([Only Oracle, Pg, mysql and SQLite are valid db types]) fi AC_SUBST(DB_TYPE) dnl DATABASE_ENV_PREF if test "$DB_TYPE" = 'Oracle'; then test "x$ORACLE_HOME" = 'x' && AC_MSG_ERROR([Please declare the ORACLE_HOME environment variable]) DATABASE_ENV_PREF="\$ENV{'ORACLE_HOME'} = '$ORACLE_HOME';" fi AC_SUBST(DATABASE_ENV_PREF) dnl DB_HOST AC_ARG_WITH(db-host, AC_HELP_STRING([--with-db-host=HOSTNAME], [FQDN of database server (default: localhost)]), DB_HOST=$withval, DB_HOST=localhost) AC_SUBST(DB_HOST) dnl DB_PORT AC_ARG_WITH(db-port, AC_HELP_STRING([--with-db-port=PORT], [port on which the database listens on]), DB_PORT=$withval, DB_PORT=) AC_SUBST(DB_PORT) dnl DB_RT_HOST AC_ARG_WITH(db-rt-host, AC_HELP_STRING([--with-db-rt-host=HOSTNAME], [FQDN of RT server which talks to the database server (default: localhost)]), DB_RT_HOST=$withval, DB_RT_HOST=localhost) AC_SUBST(DB_RT_HOST) dnl DB_DATABASE_ADMIN if test "$DB_TYPE" = "Pg" ; then DB_DBA="postgres" else DB_DBA="root" fi AC_ARG_WITH(db-dba, AC_HELP_STRING([--with-db-dba=DBA], [name of database administrator (default: root or postgres)]), DB_DBA=$withval, DB_DBA="$DB_DBA") AC_SUBST(DB_DBA) dnl DB_DATABASE AC_ARG_WITH(db-database, AC_HELP_STRING([--with-db-database=DBNAME], [name of the database to use (default: rt5)]), DB_DATABASE=$withval, DB_DATABASE=rt5) AC_SUBST(DB_DATABASE) dnl DB_RT_USER AC_ARG_WITH(db-rt-user, AC_HELP_STRING([--with-db-rt-user=DBUSER], [name of database user (default: rt_user)]), DB_RT_USER=$withval, DB_RT_USER=rt_user) AC_SUBST(DB_RT_USER) dnl DB_RT_PASS AC_ARG_WITH(db-rt-pass, AC_HELP_STRING([--with-db-rt-pass=PASSWORD], [password for database user (default: rt_pass)]), DB_RT_PASS=$withval, DB_RT_PASS=rt_pass) AC_SUBST(DB_RT_PASS) dnl WEB_USER AC_ARG_WITH(web-user, AC_HELP_STRING([--with-web-user=USER], [user the web server runs as (default: www)]), WEB_USER=$withval, ACRT_USER_GUESS([www www-data apache httpd nobody],[WEB_USER],[www]) ) AC_SUBST(WEB_USER) dnl WEB_GROUP AC_ARG_WITH(web-group, AC_HELP_STRING([--with-web-group=GROUP], [group the web server runs as (default: www)]), WEB_GROUP=$withval, ACRT_GROUP_GUESS([www www-data apache httpd nogroup nobody],[WEB_GROUP], [www])) AC_SUBST(WEB_GROUP) dnl RTGROUP AC_ARG_WITH(rt-group, AC_HELP_STRING([--with-rt-group=GROUP], [group to own all files (default: rt)]), RTGROUP=$withval, ACRT_GROUP_GUESS([rt3 rt $WEB_GROUP],[RTGROUP], [rt])) AC_SUBST(RTGROUP) dnl INSTALL AS ME my_group=$($PERL -MPOSIX=getgid -le 'print scalar getgrgid getgid') my_user=$($PERL -MPOSIX=getuid -le 'print scalar getpwuid getuid') AC_ARG_WITH(my-user-group, AC_HELP_STRING([--with-my-user-group], [set all users and groups to current user/group]), RTGROUP=$my_group BIN_OWNER=$my_user LIBS_OWNER=$my_user LIBS_GROUP=$my_group WEB_USER=$my_user WEB_GROUP=$my_group) # Test for valid database names AC_MSG_CHECKING([if database name is set]) AS_IF([ echo $DB_DATABASE | $PERL -e 'exit(1) unless <> =~ /\S/' ], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_ERROR([no. database name is not set]) ] ) dnl Dependencies for testing and developing RT AC_ARG_WITH(developer,[],RT_DEVELOPER=$withval,RT_DEVELOPER="0") AC_ARG_ENABLE(developer, AC_HELP_STRING([--enable-developer], [Add dependencies needed for testing and developing RT]), RT_DEVELOPER=$enableval, RT_DEVELOPER=$RT_DEVELOPER) if test "$RT_DEVELOPER" = yes; then RT_DEVELOPER="1" else RT_DEVELOPER="0" fi AC_SUBST(RT_DEVELOPER) dnl RT's GraphViz dependency charts AC_CHECK_PROG([RT_GRAPHVIZ], [dot], "yes", "no") AC_ARG_WITH(graphviz,[],RT_GRAPHVIZ=$withval) AC_ARG_ENABLE(graphviz, AC_HELP_STRING([--enable-graphviz], [Turns on support for RT's GraphViz dependency charts]), RT_GRAPHVIZ=$enableval) if test "$RT_GRAPHVIZ" = yes; then RT_GRAPHVIZ="1" else RT_GRAPHVIZ="0" fi AC_SUBST(RT_GRAPHVIZ) dnl RT's GD pie and bar charts AC_CHECK_PROG([RT_GD], [gdlib-config], "yes", "no") AC_ARG_WITH(gd,[],RT_GD=$withval) AC_ARG_ENABLE(gd, AC_HELP_STRING([--enable-gd], [Turns on support for RT's GD pie and bar charts]), RT_GD=$enableval) if test "$RT_GD" = yes; then RT_GD="1" else RT_GD="0" fi AC_SUBST(RT_GD) dnl RT's GPG support AC_CHECK_PROG([RT_GPG_DEPS], [gpg], "yes", "no") if test "$RT_GPG_DEPS" = yes; then RT_GPG_DEPS="1" else RT_GPG_DEPS="0" fi AC_ARG_ENABLE(gpg, AC_HELP_STRING([--enable-gpg], [Turns on GNU Privacy Guard (GPG) support]), RT_GPG=$enableval) if test "$RT_GPG" = yes; then RT_GPG="1" RT_GPG_DEPS="1" else if test "$RT_GPG" = no; then RT_GPG="0" RT_GPG_DEPS="0" else RT_GPG="0" fi fi AC_SUBST(RT_GPG_DEPS) AC_SUBST(RT_GPG) dnl RT's SMIME support AC_CHECK_PROG([RT_SMIME_DEPS], [openssl], "yes", "no") if test "$RT_SMIME_DEPS" = yes; then RT_SMIME_DEPS="1" else RT_SMIME_DEPS="0" fi AC_ARG_ENABLE(smime, AC_HELP_STRING([--enable-smime], [Turns on Secure MIME (SMIME) support]), RT_SMIME=$enableval) if test "$RT_SMIME" = yes; then RT_SMIME="1" RT_SMIME_DEPS="1" else if test "$RT_SMIME" = no; then RT_SMIME="0" RT_SMIME_DEPS="0" else RT_SMIME="0" fi fi AC_SUBST(RT_SMIME_DEPS) AC_SUBST(RT_SMIME) dnl Dependencies for external auth AC_ARG_WITH(externalauth,[],RT_EXTERNALAUTH=$withval,RT_EXTERNALAUTH="0") AC_ARG_ENABLE(externalauth, AC_HELP_STRING([--enable-externalauth], [Add dependencies needed for external auth]), RT_EXTERNALAUTH=$enableval, RT_EXTERNALAUTH=$RT_EXTERNALAUTH) if test "$RT_EXTERNALAUTH" = yes; then RT_EXTERNALAUTH="1" else RT_EXTERNALAUTH="0" fi AC_SUBST(RT_EXTERNALAUTH) dnl ExternalStorage AC_ARG_WITH(attachment-store, AC_HELP_STRING([--with-attachment-store=TYPE], [which attachment storage RT will use for attachments (default: database) (database, disk, S3 and Dropbox are valid)]), ATTACHMENT_STORE=$withval, ATTACHMENT_STORE=database) if test "$ATTACHMENT_STORE" != 'database' -a "$ATTACHMENT_STORE" != 'disk' -a "$ATTACHMENT_STORE" != 'S3' -a "$ATTACHMENT_STORE" != 'Dropbox' ; then AC_MSG_ERROR([Only database, disk, S3 and Dropbox are valid db types]) fi AC_SUBST(ATTACHMENT_STORE) dnl This section maps the variable names this script 'natively' generates dnl to their existing names. They should be removed from here as the .in dnl files are changed to use the new names. dnl version numbers AC_SUBST(RT_VERSION_MAJOR, ${rt_version_major}) AC_SUBST(RT_VERSION_MINOR, ${rt_version_minor}) AC_SUBST(RT_VERSION_PATCH, ${rt_version_patch}) dnl layout paths AC_SUBST([RT_PATH], ${exp_prefix}) AC_SUBST([RT_DOC_PATH], ${exp_manualdir}) AC_SUBST([RT_LOCAL_PATH], ${exp_customdir}) AC_SUBST([RT_LIB_PATH], ${exp_libdir}) AC_SUBST([RT_LEXICON_PATH], ${exp_lexdir}) AC_SUBST([RT_STATIC_PATH], ${exp_staticdir}) AC_SUBST([RT_ETC_PATH], ${exp_sysconfdir}) AC_SUBST([CONFIG_FILE_PATH], ${exp_sysconfdir}) AC_SUBST([RT_BIN_PATH], ${exp_bindir}) AC_SUBST([RT_SBIN_PATH], ${exp_sbindir}) AC_SUBST([RT_VAR_PATH], ${exp_localstatedir}) AC_SUBST([RT_MAN_PATH], ${exp_mandir}) AC_SUBST([RT_FONT_PATH], ${exp_fontdir}) AC_SUBST([RT_PLUGIN_PATH], ${exp_plugindir}) AC_SUBST([MASON_DATA_PATH], ${exp_masonstatedir}) AC_SUBST([MASON_SESSION_PATH], ${exp_sessionstatedir}) AC_SUBST([MASON_HTML_PATH], ${exp_htmldir}) AC_SUBST([LOCAL_ETC_PATH], ${exp_custometcdir}) AC_SUBST([MASON_LOCAL_HTML_PATH], ${exp_customhtmldir}) AC_SUBST([LOCAL_LEXICON_PATH], ${exp_customlexdir}) AC_SUBST([LOCAL_STATIC_PATH], ${exp_customstaticdir}) AC_SUBST([LOCAL_LIB_PATH], ${exp_customlibdir}) AC_SUBST([LOCAL_PLUGIN_PATH], ${exp_customplugindir}) AC_SUBST([RT_LOG_PATH], ${exp_logfiledir}) if test ${exp_sysconfdir} = "etc" -o ${exp_sysconfdir} = "etc/rt"; then AC_SUBST([RT_PATH_R], ${exp_prefix}) AC_SUBST([RT_DOC_PATH_R], ${exp_prefix}/${exp_manualdir}) AC_SUBST([RT_LOCAL_PATH_R], ${exp_prefix}/${exp_customdir}) AC_SUBST([RT_LIB_PATH_R], ${exp_prefix}/${exp_libdir}) AC_SUBST([RT_ETC_PATH_R], ${exp_prefix}/${exp_sysconfdir}) AC_SUBST([CONFIG_FILE_PATH_R], ${exp_prefix}/${exp_sysconfdir}) AC_SUBST([RT_BIN_PATH_R], ${exp_prefix}/${exp_bindir}) AC_SUBST([RT_SBIN_PATH_R], ${exp_prefix}/${exp_sbindir}) AC_SUBST([RT_VAR_PATH_R], ${exp_prefix}/${exp_localstatedir}) AC_SUBST([RT_MAN_PATH_R], ${exp_prefix}/${exp_mandir}) AC_SUBST([RT_FONT_PATH_R], ${exp_prefix}/${exp_fontdir}) AC_SUBST([RT_LEXICON_PATH_R], ${exp_prefix}/${exp_lexdir}) AC_SUBST([RT_STATIC_PATH_R], ${exp_prefix}/${exp_staticdir}) AC_SUBST([RT_PLUGIN_PATH_R], ${exp_prefix}/${exp_plugindir}) AC_SUBST([MASON_DATA_PATH_R], ${exp_prefix}/${exp_masonstatedir}) AC_SUBST([MASON_SESSION_PATH_R], ${exp_prefix}/${exp_sessionstatedir}) AC_SUBST([MASON_HTML_PATH_R], ${exp_prefix}/${exp_htmldir}) AC_SUBST([LOCAL_ETC_PATH_R], ${exp_prefix}/${exp_custometcdir}) AC_SUBST([MASON_LOCAL_HTML_PATH_R], ${exp_prefix}/${exp_customhtmldir}) AC_SUBST([LOCAL_LEXICON_PATH_R], ${exp_prefix}/${exp_customlexdir}) AC_SUBST([LOCAL_STATIC_PATH_R], ${exp_prefix}/${exp_customstaticdir}) AC_SUBST([LOCAL_LIB_PATH_R], ${exp_prefix}/${exp_customlibdir}) AC_SUBST([LOCAL_PLUGIN_PATH_R], ${exp_prefix}/${exp_customplugindir}) AC_SUBST([RT_LOG_PATH_R], ${exp_prefix}/${exp_logfiledir}) else AC_SUBST([RT_PATH_R], ${exp_prefix}) AC_SUBST([RT_DOC_PATH_R], ${exp_manualdir}) AC_SUBST([RT_LOCAL_PATH_R], ${exp_customdir}) AC_SUBST([RT_LIB_PATH_R], ${exp_libdir}) AC_SUBST([RT_LEXICON_PATH_R], ${exp_lexdir}) AC_SUBST([RT_STATIC_PATH_R], ${exp_staticdir}) AC_SUBST([RT_ETC_PATH_R], ${exp_sysconfdir}) AC_SUBST([RT_PLUGIN_PATH_R], ${exp_plugindir}) AC_SUBST([CONFIG_FILE_PATH_R], ${exp_sysconfdir}) AC_SUBST([RT_BIN_PATH_R], ${exp_bindir}) AC_SUBST([RT_SBIN_PATH_R], ${exp_sbindir}) AC_SUBST([RT_VAR_PATH_R], ${exp_localstatedir}) AC_SUBST([RT_MAN_PATH_R], ${exp_mandir}) AC_SUBST([RT_FONT_PATH_R], ${exp_fontdir}) AC_SUBST([MASON_DATA_PATH_R], ${exp_masonstatedir}) AC_SUBST([MASON_SESSION_PATH_R], ${exp_sessionstatedir}) AC_SUBST([MASON_HTML_PATH_R], ${exp_htmldir}) AC_SUBST([LOCAL_ETC_PATH_R], ${exp_custometcdir}) AC_SUBST([MASON_LOCAL_HTML_PATH_R], ${exp_customhtmldir}) AC_SUBST([LOCAL_LEXICON_PATH_R], ${exp_customlexdir}) AC_SUBST([LOCAL_STATIC_PATH_R], ${exp_customstaticdir}) AC_SUBST([LOCAL_PLUGIN_PATH_R], ${exp_customplugindir}) AC_SUBST([LOCAL_LIB_PATH_R], ${exp_customlibdir}) AC_SUBST([RT_LOG_PATH_R], ${exp_logfiledir}) fi dnl Configure the output files, and generate them. dnl Binaries that should be +x AC_CONFIG_FILES([ etc/upgrade/3.8-ical-extension etc/upgrade/4.0-customfield-checkbox-extension etc/upgrade/generate-rtaddressregexp etc/upgrade/reset-sequences etc/upgrade/sanity-check-stylesheets etc/upgrade/shrink-cgm-table etc/upgrade/shrink-transactions-table etc/upgrade/switch-templates-to etc/upgrade/time-worked-history etc/upgrade/upgrade-articles etc/upgrade/upgrade-assets etc/upgrade/upgrade-authtokens etc/upgrade/upgrade-configurations etc/upgrade/vulnerable-passwords etc/upgrade/upgrade-sla sbin/rt-ldapimport sbin/rt-attributes-viewer sbin/rt-preferences-viewer sbin/rt-session-viewer sbin/rt-dump-initialdata sbin/rt-dump-metadata sbin/rt-setup-database sbin/rt-test-dependencies sbin/rt-email-digest sbin/rt-email-dashboards sbin/rt-externalize-attachments sbin/rt-clean-sessions sbin/rt-shredder sbin/rt-validator sbin/rt-validate-aliases sbin/rt-email-group-admin sbin/rt-search-attributes sbin/rt-server sbin/rt-server.fcgi sbin/standalone_httpd sbin/rt-setup-fulltext-index sbin/rt-fulltext-indexer sbin/rt-serializer sbin/rt-importer sbin/rt-passwd sbin/rt-munge-attachments bin/rt-crontool bin/rt-mailgate bin/rt], [chmod ug+x $ac_file] ) dnl All other generated files AC_CONFIG_FILES([ Makefile etc/RT_Config.pm lib/RT/Generated.pm t/data/configs/apache2.2+mod_perl.conf t/data/configs/apache2.2+fastcgi.conf t/data/configs/apache2.4+mod_perl.conf t/data/configs/apache2.4+fastcgi.conf], ) AC_OUTPUT rt-5.0.1/bin/000755 000765 000024 00000000000 14005021226 013550 5ustar00sunnavystaff000000 000000 rt-5.0.1/configure000755 000765 000024 00000370613 14005021222 014715 0ustar00sunnavystaff000000 000000 #! /bin/sh # From configure.ac Revision. # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.69 for RT rt-5.0.1. # # Report bugs to . # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) 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 for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then _as_can_reexec=no; export _as_can_reexec; # 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 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+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. $as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 as_fn_exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi " as_required="as_fn_return () { (exit \$1); } as_fn_success () { as_fn_return 0; } as_fn_failure () { as_fn_return 1; } as_fn_ret_success () { return 0; } as_fn_ret_failure () { return 1; } exitcode=0 as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : else exitcode=1; echo positional parameters were not saved. fi test x\$exitcode = x0 || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1" if (eval "$as_required") 2>/dev/null; then : as_have_required=yes else as_have_required=no fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. as_shell=$as_dir/$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : CONFIG_SHELL=$as_shell as_have_required=yes if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : break 2 fi fi done;; esac as_found=false done $as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : CONFIG_SHELL=$SHELL as_have_required=yes fi; } IFS=$as_save_IFS if test "x$CONFIG_SHELL" != x; then : export CONFIG_SHELL # 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 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+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. $as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi if test x$as_have_required = xno; then : $as_echo "$0: This script requires a shell more modern than all" $as_echo "$0: the shells that I found on your system." if test x${ZSH_VERSION+set} = xset ; then $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" $as_echo "$0: be upgraded to zsh 4.3.4 or later." else $as_echo "$0: Please tell bug-autoconf@gnu.org and $0: rt-bugs@bestpractical.com about your system, including $0: any error possibly output before this message. Then $0: install a modern shell, or manually run the script $0: under such a shell if you do have one." fi exit 1 fi fi fi SHELL=${CONFIG_SHELL-/bin/sh} export SHELL # Unset more variables known to interfere with behavior of common tools. CLICOLOR_FORCE= GREP_OPTIONS= unset CLICOLOR_FORCE GREP_OPTIONS ## --------------------- ## ## M4sh Shell Functions. ## ## --------------------- ## # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits as_lineno_1=$LINENO as_lineno_1a=$LINENO as_lineno_2=$LINENO as_lineno_2a=$LINENO eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall # in an infinite loop. This has already happened in practice. _as_can_reexec=no; export _as_can_reexec # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='RT' PACKAGE_TARNAME='rt' PACKAGE_VERSION='rt-5.0.1' PACKAGE_STRING='RT rt-5.0.1' PACKAGE_BUGREPORT='rt-bugs@bestpractical.com' PACKAGE_URL='' ac_unique_file="lib/RT.pm" ac_default_prefix=/opt/rt5 ac_subst_vars='LTLIBOBJS LIBOBJS RT_LOG_PATH_R LOCAL_PLUGIN_PATH_R LOCAL_LIB_PATH_R LOCAL_STATIC_PATH_R LOCAL_LEXICON_PATH_R MASON_LOCAL_HTML_PATH_R LOCAL_ETC_PATH_R MASON_HTML_PATH_R MASON_SESSION_PATH_R MASON_DATA_PATH_R RT_PLUGIN_PATH_R RT_STATIC_PATH_R RT_LEXICON_PATH_R RT_FONT_PATH_R RT_MAN_PATH_R RT_VAR_PATH_R RT_SBIN_PATH_R RT_BIN_PATH_R CONFIG_FILE_PATH_R RT_ETC_PATH_R RT_LIB_PATH_R RT_LOCAL_PATH_R RT_DOC_PATH_R RT_PATH_R RT_LOG_PATH LOCAL_PLUGIN_PATH LOCAL_LIB_PATH LOCAL_STATIC_PATH LOCAL_LEXICON_PATH MASON_LOCAL_HTML_PATH LOCAL_ETC_PATH MASON_HTML_PATH MASON_SESSION_PATH MASON_DATA_PATH RT_PLUGIN_PATH RT_FONT_PATH RT_MAN_PATH RT_VAR_PATH RT_SBIN_PATH RT_BIN_PATH CONFIG_FILE_PATH RT_ETC_PATH RT_STATIC_PATH RT_LEXICON_PATH RT_LIB_PATH RT_LOCAL_PATH RT_DOC_PATH RT_PATH RT_VERSION_PATCH RT_VERSION_MINOR RT_VERSION_MAJOR ATTACHMENT_STORE RT_EXTERNALAUTH RT_SMIME RT_SMIME_DEPS RT_GPG RT_GPG_DEPS RT_GD RT_GRAPHVIZ RT_DEVELOPER RTGROUP WEB_GROUP WEB_USER DB_RT_PASS DB_RT_USER DB_DATABASE DB_DBA DB_RT_HOST DB_PORT DB_HOST DATABASE_ENV_PREF DB_TYPE LIBS_GROUP LIBS_OWNER BIN_OWNER COMMENT_INPLACE_LAYOUT rt_layout_name exp_customlibdir customlibdir exp_customstaticdir customstaticdir exp_customlexdir customlexdir exp_customhtmldir customhtmldir exp_customplugindir customplugindir exp_custometcdir custometcdir exp_customdir customdir exp_sessionstatedir sessionstatedir exp_masonstatedir masonstatedir exp_logfiledir logfiledir exp_localstatedir exp_plugindir plugindir exp_manualdir manualdir exp_fontdir fontdir exp_htmldir exp_datadir exp_staticdir staticdir exp_lexdir lexdir exp_libdir exp_mandir exp_sysconfdir exp_sbindir exp_bindir exp_exec_prefix exp_prefix WEB_HANDLER FINDPERM PERL INSTALL_DATA INSTALL_SCRIPT INSTALL_PROGRAM rt_version_patch rt_version_minor rt_version_major CONFIGURE_INCANT target_alias host_alias build_alias LIBS ECHO_T ECHO_N ECHO_C DEFS mandir localedir libdir psdir pdfdir dvidir htmldir infodir docdir oldincludedir includedir localstatedir sharedstatedir sysconfdir datadir datarootdir libexecdir sbindir bindir program_transform_name prefix exec_prefix PACKAGE_URL PACKAGE_BUGREPORT PACKAGE_STRING PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR SHELL' ac_subst_files='' ac_user_opts=' enable_option_checking with_web_handler enable_layout with_bin_owner with_libs_owner with_libs_group with_db_type with_db_host with_db_port with_db_rt_host with_db_dba with_db_database with_db_rt_user with_db_rt_pass with_web_user with_web_group with_rt_group with_my_user_group with_developer enable_developer with_graphviz enable_graphviz with_gd enable_gd enable_gpg enable_smime with_externalauth enable_externalauth with_attachment_store ' ac_precious_vars='build_alias host_alias target_alias PERL' # Initialize some variables set by options. ac_init_help= ac_init_version=false ac_unrecognized_opts= ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *=) ac_optarg= ;; *) ac_optarg=yes ;; esac # Accept the important Cygnus configure options, so we can diagnose typos. case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) as_fn_error $? "unrecognized option: \`$ac_option' Try \`$0 --help' for more information" ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. $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}" ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi # Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir do eval ac_val=\$$ac_var # Remove trailing slashes. case $ac_val in */ ) ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` eval $ac_var=\$ac_val;; esac # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: `$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || as_fn_error $? "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || as_fn_error $? "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$as_myself" || $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # 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-5.0.1 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print \`checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for \`--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or \`..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, \`make install' will install all the files in \`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify an installation prefix other than \`$ac_default_prefix' using \`--prefix', for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/rt] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in short | recursive ) echo "Configuration of RT rt-5.0.1:";; esac cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-layout=LAYOUT Use a specific directory layout (Default: relative) --enable-developer Add dependencies needed for testing and developing RT --enable-graphviz Turns on support for RT's GraphViz dependency charts --enable-gd Turns on support for RT's GD pie and bar charts --enable-gpg Turns on GNU Privacy Guard (GPG) support --enable-smime Turns on Secure MIME (SMIME) support --enable-externalauth Add dependencies needed for external auth Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-web-handler=LIST comma separated list of web-handlers RT will be able to use. Default is fastcgi. Valid values are modperl1, modperl2, fastcgi and standalone. To successfully run RT you need only one. --with-bin-owner=OWNER user that will own RT binaries (default root) --with-libs-owner=OWNER user that will own RT libraries (default root) --with-libs-group=GROUP group that will own RT binaries (default bin) --with-db-type=TYPE sort of database RT will use (default: mysql) (mysql, Pg, Oracle and SQLite are valid) --with-db-host=HOSTNAME FQDN of database server (default: localhost) --with-db-port=PORT port on which the database listens on --with-db-rt-host=HOSTNAME FQDN of RT server which talks to the database server (default: localhost) --with-db-dba=DBA name of database administrator (default: root or postgres) --with-db-database=DBNAME name of the database to use (default: rt5) --with-db-rt-user=DBUSER name of database user (default: rt_user) --with-db-rt-pass=PASSWORD password for database user (default: rt_pass) --with-web-user=USER user the web server runs as (default: www) --with-web-group=GROUP group the web server runs as (default: www) --with-rt-group=GROUP group to own all files (default: rt) --with-my-user-group set all users and groups to current user/group --with-attachment-store=TYPE which attachment storage RT will use for attachments (default: database) (database, disk, S3 and Dropbox are valid) Some influential environment variables: PERL Perl interpreter command Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to . _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for guested configure. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF RT configure rt-5.0.1 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## 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-5.0.1, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. $as_echo "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? # Save into config.log some information that might help in debugging. { echo $as_echo "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo # The following way of writing the cache mishandles newlines in values, ( for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( *${as_nl}ac_space=\ *) sed -n \ "s/'\''/'\''\\\\'\'''\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" ;; #( *) sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) echo $as_echo "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac $as_echo "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then $as_echo "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac $as_echo "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then $as_echo "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo cat confdefs.h echo fi test "$ac_signal" != 0 && $as_echo "$as_me: caught signal $ac_signal" $as_echo "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h $as_echo "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. cat >>confdefs.h <<_ACEOF #define PACKAGE_NAME "$PACKAGE_NAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_TARNAME "$PACKAGE_TARNAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_VERSION "$PACKAGE_VERSION" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_STRING "$PACKAGE_STRING" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_URL "$PACKAGE_URL" _ACEOF # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. ac_site_file1=NONE ac_site_file2=NONE if test -n "$CONFIG_SITE"; then # We do not want a PATH search for config.site. case $CONFIG_SITE in #(( -*) ac_site_file1=./$CONFIG_SITE;; */*) ac_site_file1=$CONFIG_SITE;; *) ac_site_file1=./$CONFIG_SITE;; esac elif test "x$prefix" != xNONE; then ac_site_file1=$prefix/share/config.site ac_site_file2=$prefix/etc/config.site else ac_site_file1=$ac_default_prefix/share/config.site ac_site_file2=$ac_default_prefix/etc/config.site fi for ac_site_file in "$ac_site_file1" "$ac_site_file2" do test "x$ac_site_file" = xNONE && continue if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 $as_echo "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ || { { $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; } fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 $as_echo "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 $as_echo "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 $as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 $as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 $as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 $as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 $as_echo "$as_me: former value: \`$ac_old_val'" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 $as_echo "$as_me: current value: \`$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 $as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu CONFIGURE_INCANT="$0 $@" rt_version_major=5 rt_version_minor=0 rt_version_patch=1 test "x$rt_version_major" = 'x' && rt_version_major=0 test "x$rt_version_minor" = 'x' && rt_version_minor=0 test "x$rt_version_patch" = 'x' && rt_version_patch=0 ac_aux_dir= for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do if test -f "$ac_dir/install-sh"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/install-sh -c" break elif test -f "$ac_dir/install.sh"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/install.sh -c" break elif test -f "$ac_dir/shtool"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/shtool install -c" break fi done if test -z "$ac_aux_dir"; then as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5 fi # These three variables are undocumented and unsupported, # and are intended to be withdrawn in a future Autoconf release. # They can cause serious problems if a builder's source tree is in a directory # whose full name contains unusual characters. ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. # Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install # SunOS /usr/etc/install # IRIX /sbin/install # AIX /bin/install # AmigaOS /C/install, which installs bootblocks on floppy discs # AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag # AFS /usr/afsws/bin/install, which mishandles nonexistent args # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. # Reject install programs that cannot install multiple files. { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 $as_echo_n "checking for a BSD-compatible install... " >&6; } if test -z "$INSTALL"; then if ${ac_cv_path_install+:} false; then : $as_echo_n "(cached) " >&6 else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. # Account for people who put trailing slashes in PATH elements. case $as_dir/ in #(( ./ | .// | /[cC]/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ /usr/ucb/* ) ;; *) # OSF1 and SCO ODT 3.0 have their own names for install. # Don't use installbsd from OSF since it installs stuff as root # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then if test $ac_prog = install && grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else rm -rf conftest.one conftest.two conftest.dir echo one > conftest.one echo two > conftest.two mkdir conftest.dir if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && test -s conftest.one && test -s conftest.two && test -s conftest.dir/conftest.one && test -s conftest.dir/conftest.two then ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" break 3 fi fi fi done done ;; esac done IFS=$as_save_IFS rm -rf conftest.one conftest.two conftest.dir fi if test "${ac_cv_path_install+set}" = set; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a # value for INSTALL within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. INSTALL=$ac_install_sh fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 $as_echo "$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' # Extract the first word of "perl", so it can be a program name with args. set dummy perl; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_PERL+:} false; then : $as_echo_n "(cached) " >&6 else case $PERL in [\\/]* | ?:[\\/]*) ac_cv_path_PERL="$PERL" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_PERL="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_PERL" && ac_cv_path_PERL="not found" ;; esac fi PERL=$ac_cv_path_PERL if test -n "$PERL"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PERL" >&5 $as_echo "$PERL" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "$PERL" = 'not found'; then as_fn_error $? "cannot use $PACKAGE_NAME without perl" "$LINENO" 5 fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking checking version of find" >&5 $as_echo_n "checking checking version of find... " >&6; } if find --version 2>&1 | grep 'GNU'; then : FINDPERM="/" { $as_echo "$as_me:${as_lineno-$LINENO}: result: configuring for GNU find" >&5 $as_echo "configuring for GNU find" >&6; } else FINDPERM="+" { $as_echo "$as_me:${as_lineno-$LINENO}: result: configuring for BSD find" >&5 $as_echo "configuring for BSD find" >&6; } fi # Check whether --with-web-handler was given. if test "${with_web_handler+set}" = set; then : withval=$with_web_handler; WEB_HANDLER=$withval else WEB_HANDLER=fastcgi fi my_web_handler_test=$($PERL -e 'print "ok" unless grep $_ !~ /^(modperl1|modperl2|fastcgi|standalone)$/i, grep defined && length, split /\s*,\s*/, $ARGV[0]' $WEB_HANDLER) if test "$my_web_handler_test" != "ok"; then as_fn_error $? "Only modperl1, modperl2, fastcgi and standalone are valid web-handlers" "$LINENO" 5 fi # Check whether --enable-layout was given. if test "${enable_layout+set}" = set; then : enableval=$enable_layout; LAYOUT=$enableval fi if test "x$LAYOUT" = "x"; then LAYOUT="relative" fi if test ! -f $srcdir/config.layout; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Layout file $srcdir/config.layout not found" >&5 $as_echo "$as_me: WARNING: Layout file $srcdir/config.layout not found" >&2;} rt_layout_name=no else pldconf=./config.pld $PERL -0777 -p -e "\$layout = '$LAYOUT';" -e ' s/.*//gims; s/\<\/Layout\>.*//s; s/^#.*$//m; s/^\s+//gim; s/\s+$/\n/gim; s/\+$/\/rt3/gim; # m4 will not let us just use $srcdir/config.layout, we need $1 s/^\s*((?:bin|sbin|libexec|data|sysconf|sharedstate|localstate|lib|include|oldinclude|info|man|html)dir)\s*:\s*(.*)$/$1=$2/gim; s/^\s*(.*?)\s*:\s*(.*)$/\(test "x\$$1" = "xNONE" || test "x\$$1" = "x") && $1=$2/gim; ' < $srcdir/config.layout > $pldconf if test -s $pldconf; then rt_layout_name=$LAYOUT . $pldconf for var in prefix exec_prefix bindir sbindir \ sysconfdir mandir libdir datadir htmldir fontdir\ lexdir staticdir localstatedir logfiledir masonstatedir \ sessionstatedir customdir custometcdir customhtmldir \ customlexdir customstaticdir customplugindir customlibdir manualdir; do eval "val=\"\$$var\"" val=`echo $val | sed -e 's:\(.\)/*$:\1:'` val=`echo $val | sed -e 's:[\$]\([a-z_]*\):$\1:g'` eval "$var='$val'" done else rt_layout_name=no fi #rm $pldconf fi ap_last='' ap_cur='$prefix' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_prefix="${ap_cur}" ap_last='' ap_cur='$exec_prefix' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_exec_prefix="${ap_cur}" ap_last='' ap_cur='$bindir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_bindir="${ap_cur}" ap_last='' ap_cur='$sbindir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_sbindir="${ap_cur}" ap_last='' ap_cur='$sysconfdir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_sysconfdir="${ap_cur}" ap_last='' ap_cur='$mandir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_mandir="${ap_cur}" ap_last='' ap_cur='$libdir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_libdir="${ap_cur}" ap_last='' ap_cur='$lexdir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_lexdir="${ap_cur}" ap_last='' ap_cur='$staticdir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_staticdir="${ap_cur}" ap_last='' ap_cur='$datadir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_datadir="${ap_cur}" ap_last='' ap_cur='$htmldir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_htmldir="${ap_cur}" ap_last='' ap_cur='$fontdir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_fontdir="${ap_cur}" ap_last='' ap_cur='$manualdir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_manualdir="${ap_cur}" ap_last='' ap_cur='$plugindir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_plugindir="${ap_cur}" ap_last='' ap_cur='$localstatedir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_localstatedir="${ap_cur}" ap_last='' ap_cur='$logfiledir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_logfiledir="${ap_cur}" ap_last='' ap_cur='$masonstatedir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_masonstatedir="${ap_cur}" ap_last='' ap_cur='$sessionstatedir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_sessionstatedir="${ap_cur}" ap_last='' ap_cur='$customdir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_customdir="${ap_cur}" ap_last='' ap_cur='$custometcdir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_custometcdir="${ap_cur}" ap_last='' ap_cur='$customplugindir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_customplugindir="${ap_cur}" ap_last='' ap_cur='$customhtmldir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_customhtmldir="${ap_cur}" ap_last='' ap_cur='$customlexdir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_customlexdir="${ap_cur}" ap_last='' ap_cur='$customstaticdir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_customstaticdir="${ap_cur}" ap_last='' ap_cur='$customlibdir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done exp_customlibdir="${ap_cur}" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for chosen layout" >&5 $as_echo_n "checking for chosen layout... " >&6; } if test "x$rt_layout_name" = "xno"; then if test "x$LAYOUT" = "xno"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5 $as_echo "none" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LAYOUT" >&5 $as_echo "$LAYOUT" >&6; } fi as_fn_error $? "a valid layout must be specified (or the default used)" "$LINENO" 5 else { $as_echo "$as_me:${as_lineno-$LINENO}: result: $rt_layout_name" >&5 $as_echo "$rt_layout_name" >&6; } fi if test "x$rt_layout_name" != "xinplace" ; then COMMENT_INPLACE_LAYOUT="" else COMMENT_INPLACE_LAYOUT=# fi # ACRT_USER_EXISTS( users, variable, default ) # - users is a list of users [www apache www-docs] # from highest to lowest priority to high priority (i.e. first match) # - variable is what you set with the result # # Check whether --with-bin-owner was given. if test "${with_bin_owner+set}" = set; then : withval=$with_bin_owner; BIN_OWNER=$withval else BIN_OWNER=root fi # Check whether --with-libs-owner was given. if test "${with_libs_owner+set}" = set; then : withval=$with_libs_owner; LIBS_OWNER=$withval else LIBS_OWNER=root fi # Check whether --with-libs-group was given. if test "${with_libs_group+set}" = set; then : withval=$with_libs_group; LIBS_GROUP=$withval else LIBS_GROUP=bin fi # Check whether --with-db-type was given. if test "${with_db_type+set}" = set; then : withval=$with_db_type; DB_TYPE=$withval else DB_TYPE=mysql fi if test "$DB_TYPE" != 'mysql' -a "$DB_TYPE" != 'Pg' -a "$DB_TYPE" != 'SQLite' -a "$DB_TYPE" != 'Oracle' ; then as_fn_error $? "Only Oracle, Pg, mysql and SQLite are valid db types" "$LINENO" 5 fi if test "$DB_TYPE" = 'Oracle'; then test "x$ORACLE_HOME" = 'x' && as_fn_error $? "Please declare the ORACLE_HOME environment variable" "$LINENO" 5 DATABASE_ENV_PREF="\$ENV{'ORACLE_HOME'} = '$ORACLE_HOME';" fi # Check whether --with-db-host was given. if test "${with_db_host+set}" = set; then : withval=$with_db_host; DB_HOST=$withval else DB_HOST=localhost fi # Check whether --with-db-port was given. if test "${with_db_port+set}" = set; then : withval=$with_db_port; DB_PORT=$withval else DB_PORT= fi # Check whether --with-db-rt-host was given. if test "${with_db_rt_host+set}" = set; then : withval=$with_db_rt_host; DB_RT_HOST=$withval else DB_RT_HOST=localhost fi if test "$DB_TYPE" = "Pg" ; then DB_DBA="postgres" else DB_DBA="root" fi # Check whether --with-db-dba was given. if test "${with_db_dba+set}" = set; then : withval=$with_db_dba; DB_DBA=$withval else DB_DBA="$DB_DBA" fi # Check whether --with-db-database was given. if test "${with_db_database+set}" = set; then : withval=$with_db_database; DB_DATABASE=$withval else DB_DATABASE=rt5 fi # Check whether --with-db-rt-user was given. if test "${with_db_rt_user+set}" = set; then : withval=$with_db_rt_user; DB_RT_USER=$withval else DB_RT_USER=rt_user fi # Check whether --with-db-rt-pass was given. if test "${with_db_rt_pass+set}" = set; then : withval=$with_db_rt_pass; DB_RT_PASS=$withval else DB_RT_PASS=rt_pass fi # Check whether --with-web-user was given. if test "${with_web_user+set}" = set; then : withval=$with_web_user; WEB_USER=$withval else WEB_USER=www for x in www www-data apache httpd nobody; do { $as_echo "$as_me:${as_lineno-$LINENO}: checking if user $x exists" >&5 $as_echo_n "checking if user $x exists... " >&6; } if $PERL -e"exit( defined getpwnam('$x') ? 0 : 1)" ; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: found" >&5 $as_echo "found" >&6; }; WEB_USER=$x ; break else { $as_echo "$as_me:${as_lineno-$LINENO}: result: not found" >&5 $as_echo "not found" >&6; } fi done fi # Check whether --with-web-group was given. if test "${with_web_group+set}" = set; then : withval=$with_web_group; WEB_GROUP=$withval else WEB_GROUP=www for x in www www-data apache httpd nogroup nobody; do { $as_echo "$as_me:${as_lineno-$LINENO}: checking if group $x exists" >&5 $as_echo_n "checking if group $x exists... " >&6; } if $PERL -e"exit( defined getgrnam('$x') ? 0 : 1)" ; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: found" >&5 $as_echo "found" >&6; }; WEB_GROUP=$x ; break else { $as_echo "$as_me:${as_lineno-$LINENO}: result: not found" >&5 $as_echo "not found" >&6; } fi done fi # Check whether --with-rt-group was given. if test "${with_rt_group+set}" = set; then : withval=$with_rt_group; RTGROUP=$withval else RTGROUP=rt for x in rt3 rt $WEB_GROUP; do { $as_echo "$as_me:${as_lineno-$LINENO}: checking if group $x exists" >&5 $as_echo_n "checking if group $x exists... " >&6; } if $PERL -e"exit( defined getgrnam('$x') ? 0 : 1)" ; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: found" >&5 $as_echo "found" >&6; }; RTGROUP=$x ; break else { $as_echo "$as_me:${as_lineno-$LINENO}: result: not found" >&5 $as_echo "not found" >&6; } fi done fi my_group=$($PERL -MPOSIX=getgid -le 'print scalar getgrgid getgid') my_user=$($PERL -MPOSIX=getuid -le 'print scalar getpwuid getuid') # Check whether --with-my-user-group was given. if test "${with_my_user_group+set}" = set; then : withval=$with_my_user_group; RTGROUP=$my_group BIN_OWNER=$my_user LIBS_OWNER=$my_user LIBS_GROUP=$my_group WEB_USER=$my_user WEB_GROUP=$my_group fi # Test for valid database names { $as_echo "$as_me:${as_lineno-$LINENO}: checking if database name is set" >&5 $as_echo_n "checking if database name is set... " >&6; } if echo $DB_DATABASE | $PERL -e 'exit(1) unless <> =~ /\S/' ; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else as_fn_error $? "no. database name is not set" "$LINENO" 5 fi # Check whether --with-developer was given. if test "${with_developer+set}" = set; then : withval=$with_developer; RT_DEVELOPER=$withval else RT_DEVELOPER="0" fi # Check whether --enable-developer was given. if test "${enable_developer+set}" = set; then : enableval=$enable_developer; RT_DEVELOPER=$enableval else RT_DEVELOPER=$RT_DEVELOPER fi if test "$RT_DEVELOPER" = yes; then RT_DEVELOPER="1" else RT_DEVELOPER="0" fi # Extract the first word of "dot", so it can be a program name with args. set dummy dot; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_RT_GRAPHVIZ+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$RT_GRAPHVIZ"; then ac_cv_prog_RT_GRAPHVIZ="$RT_GRAPHVIZ" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_RT_GRAPHVIZ=""yes"" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_RT_GRAPHVIZ" && ac_cv_prog_RT_GRAPHVIZ=""no"" fi fi RT_GRAPHVIZ=$ac_cv_prog_RT_GRAPHVIZ if test -n "$RT_GRAPHVIZ"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RT_GRAPHVIZ" >&5 $as_echo "$RT_GRAPHVIZ" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi # Check whether --with-graphviz was given. if test "${with_graphviz+set}" = set; then : withval=$with_graphviz; RT_GRAPHVIZ=$withval fi # Check whether --enable-graphviz was given. if test "${enable_graphviz+set}" = set; then : enableval=$enable_graphviz; RT_GRAPHVIZ=$enableval fi if test "$RT_GRAPHVIZ" = yes; then RT_GRAPHVIZ="1" else RT_GRAPHVIZ="0" fi # Extract the first word of "gdlib-config", so it can be a program name with args. set dummy gdlib-config; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_RT_GD+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$RT_GD"; then ac_cv_prog_RT_GD="$RT_GD" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_RT_GD=""yes"" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_RT_GD" && ac_cv_prog_RT_GD=""no"" fi fi RT_GD=$ac_cv_prog_RT_GD if test -n "$RT_GD"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RT_GD" >&5 $as_echo "$RT_GD" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi # Check whether --with-gd was given. if test "${with_gd+set}" = set; then : withval=$with_gd; RT_GD=$withval fi # Check whether --enable-gd was given. if test "${enable_gd+set}" = set; then : enableval=$enable_gd; RT_GD=$enableval fi if test "$RT_GD" = yes; then RT_GD="1" else RT_GD="0" fi # Extract the first word of "gpg", so it can be a program name with args. set dummy gpg; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_RT_GPG_DEPS+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$RT_GPG_DEPS"; then ac_cv_prog_RT_GPG_DEPS="$RT_GPG_DEPS" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_RT_GPG_DEPS=""yes"" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_RT_GPG_DEPS" && ac_cv_prog_RT_GPG_DEPS=""no"" fi fi RT_GPG_DEPS=$ac_cv_prog_RT_GPG_DEPS if test -n "$RT_GPG_DEPS"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RT_GPG_DEPS" >&5 $as_echo "$RT_GPG_DEPS" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "$RT_GPG_DEPS" = yes; then RT_GPG_DEPS="1" else RT_GPG_DEPS="0" fi # Check whether --enable-gpg was given. if test "${enable_gpg+set}" = set; then : enableval=$enable_gpg; RT_GPG=$enableval fi if test "$RT_GPG" = yes; then RT_GPG="1" RT_GPG_DEPS="1" else if test "$RT_GPG" = no; then RT_GPG="0" RT_GPG_DEPS="0" else RT_GPG="0" fi fi # Extract the first word of "openssl", so it can be a program name with args. set dummy openssl; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_RT_SMIME_DEPS+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$RT_SMIME_DEPS"; then ac_cv_prog_RT_SMIME_DEPS="$RT_SMIME_DEPS" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_RT_SMIME_DEPS=""yes"" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_RT_SMIME_DEPS" && ac_cv_prog_RT_SMIME_DEPS=""no"" fi fi RT_SMIME_DEPS=$ac_cv_prog_RT_SMIME_DEPS if test -n "$RT_SMIME_DEPS"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RT_SMIME_DEPS" >&5 $as_echo "$RT_SMIME_DEPS" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "$RT_SMIME_DEPS" = yes; then RT_SMIME_DEPS="1" else RT_SMIME_DEPS="0" fi # Check whether --enable-smime was given. if test "${enable_smime+set}" = set; then : enableval=$enable_smime; RT_SMIME=$enableval fi if test "$RT_SMIME" = yes; then RT_SMIME="1" RT_SMIME_DEPS="1" else if test "$RT_SMIME" = no; then RT_SMIME="0" RT_SMIME_DEPS="0" else RT_SMIME="0" fi fi # Check whether --with-externalauth was given. if test "${with_externalauth+set}" = set; then : withval=$with_externalauth; RT_EXTERNALAUTH=$withval else RT_EXTERNALAUTH="0" fi # Check whether --enable-externalauth was given. if test "${enable_externalauth+set}" = set; then : enableval=$enable_externalauth; RT_EXTERNALAUTH=$enableval else RT_EXTERNALAUTH=$RT_EXTERNALAUTH fi if test "$RT_EXTERNALAUTH" = yes; then RT_EXTERNALAUTH="1" else RT_EXTERNALAUTH="0" fi # Check whether --with-attachment-store was given. if test "${with_attachment_store+set}" = set; then : withval=$with_attachment_store; ATTACHMENT_STORE=$withval else ATTACHMENT_STORE=database fi if test "$ATTACHMENT_STORE" != 'database' -a "$ATTACHMENT_STORE" != 'disk' -a "$ATTACHMENT_STORE" != 'S3' -a "$ATTACHMENT_STORE" != 'Dropbox' ; then as_fn_error $? "Only database, disk, S3 and Dropbox are valid db types" "$LINENO" 5 fi RT_VERSION_MAJOR=${rt_version_major} RT_VERSION_MINOR=${rt_version_minor} RT_VERSION_PATCH=${rt_version_patch} RT_PATH=${exp_prefix} RT_DOC_PATH=${exp_manualdir} RT_LOCAL_PATH=${exp_customdir} RT_LIB_PATH=${exp_libdir} RT_LEXICON_PATH=${exp_lexdir} RT_STATIC_PATH=${exp_staticdir} RT_ETC_PATH=${exp_sysconfdir} CONFIG_FILE_PATH=${exp_sysconfdir} RT_BIN_PATH=${exp_bindir} RT_SBIN_PATH=${exp_sbindir} RT_VAR_PATH=${exp_localstatedir} RT_MAN_PATH=${exp_mandir} RT_FONT_PATH=${exp_fontdir} RT_PLUGIN_PATH=${exp_plugindir} MASON_DATA_PATH=${exp_masonstatedir} MASON_SESSION_PATH=${exp_sessionstatedir} MASON_HTML_PATH=${exp_htmldir} LOCAL_ETC_PATH=${exp_custometcdir} MASON_LOCAL_HTML_PATH=${exp_customhtmldir} LOCAL_LEXICON_PATH=${exp_customlexdir} LOCAL_STATIC_PATH=${exp_customstaticdir} LOCAL_LIB_PATH=${exp_customlibdir} LOCAL_PLUGIN_PATH=${exp_customplugindir} RT_LOG_PATH=${exp_logfiledir} if test ${exp_sysconfdir} = "etc" -o ${exp_sysconfdir} = "etc/rt"; then RT_PATH_R=${exp_prefix} RT_DOC_PATH_R=${exp_prefix}/${exp_manualdir} RT_LOCAL_PATH_R=${exp_prefix}/${exp_customdir} RT_LIB_PATH_R=${exp_prefix}/${exp_libdir} RT_ETC_PATH_R=${exp_prefix}/${exp_sysconfdir} CONFIG_FILE_PATH_R=${exp_prefix}/${exp_sysconfdir} RT_BIN_PATH_R=${exp_prefix}/${exp_bindir} RT_SBIN_PATH_R=${exp_prefix}/${exp_sbindir} RT_VAR_PATH_R=${exp_prefix}/${exp_localstatedir} RT_MAN_PATH_R=${exp_prefix}/${exp_mandir} RT_FONT_PATH_R=${exp_prefix}/${exp_fontdir} RT_LEXICON_PATH_R=${exp_prefix}/${exp_lexdir} RT_STATIC_PATH_R=${exp_prefix}/${exp_staticdir} RT_PLUGIN_PATH_R=${exp_prefix}/${exp_plugindir} MASON_DATA_PATH_R=${exp_prefix}/${exp_masonstatedir} MASON_SESSION_PATH_R=${exp_prefix}/${exp_sessionstatedir} MASON_HTML_PATH_R=${exp_prefix}/${exp_htmldir} LOCAL_ETC_PATH_R=${exp_prefix}/${exp_custometcdir} MASON_LOCAL_HTML_PATH_R=${exp_prefix}/${exp_customhtmldir} LOCAL_LEXICON_PATH_R=${exp_prefix}/${exp_customlexdir} LOCAL_STATIC_PATH_R=${exp_prefix}/${exp_customstaticdir} LOCAL_LIB_PATH_R=${exp_prefix}/${exp_customlibdir} LOCAL_PLUGIN_PATH_R=${exp_prefix}/${exp_customplugindir} RT_LOG_PATH_R=${exp_prefix}/${exp_logfiledir} else RT_PATH_R=${exp_prefix} RT_DOC_PATH_R=${exp_manualdir} RT_LOCAL_PATH_R=${exp_customdir} RT_LIB_PATH_R=${exp_libdir} RT_LEXICON_PATH_R=${exp_lexdir} RT_STATIC_PATH_R=${exp_staticdir} RT_ETC_PATH_R=${exp_sysconfdir} RT_PLUGIN_PATH_R=${exp_plugindir} CONFIG_FILE_PATH_R=${exp_sysconfdir} RT_BIN_PATH_R=${exp_bindir} RT_SBIN_PATH_R=${exp_sbindir} RT_VAR_PATH_R=${exp_localstatedir} RT_MAN_PATH_R=${exp_mandir} RT_FONT_PATH_R=${exp_fontdir} MASON_DATA_PATH_R=${exp_masonstatedir} MASON_SESSION_PATH_R=${exp_sessionstatedir} MASON_HTML_PATH_R=${exp_htmldir} LOCAL_ETC_PATH_R=${exp_custometcdir} MASON_LOCAL_HTML_PATH_R=${exp_customhtmldir} LOCAL_LEXICON_PATH_R=${exp_customlexdir} LOCAL_STATIC_PATH_R=${exp_customstaticdir} LOCAL_PLUGIN_PATH_R=${exp_customplugindir} LOCAL_LIB_PATH_R=${exp_customlibdir} RT_LOG_PATH_R=${exp_logfiledir} fi ac_config_files="$ac_config_files etc/upgrade/3.8-ical-extension etc/upgrade/4.0-customfield-checkbox-extension etc/upgrade/generate-rtaddressregexp etc/upgrade/reset-sequences etc/upgrade/sanity-check-stylesheets etc/upgrade/shrink-cgm-table etc/upgrade/shrink-transactions-table etc/upgrade/switch-templates-to etc/upgrade/time-worked-history etc/upgrade/upgrade-articles etc/upgrade/upgrade-assets etc/upgrade/upgrade-authtokens etc/upgrade/upgrade-configurations etc/upgrade/vulnerable-passwords etc/upgrade/upgrade-sla sbin/rt-ldapimport sbin/rt-attributes-viewer sbin/rt-preferences-viewer sbin/rt-session-viewer sbin/rt-dump-initialdata sbin/rt-dump-metadata sbin/rt-setup-database sbin/rt-test-dependencies sbin/rt-email-digest sbin/rt-email-dashboards sbin/rt-externalize-attachments sbin/rt-clean-sessions sbin/rt-shredder sbin/rt-validator sbin/rt-validate-aliases sbin/rt-email-group-admin sbin/rt-search-attributes sbin/rt-server sbin/rt-server.fcgi sbin/standalone_httpd sbin/rt-setup-fulltext-index sbin/rt-fulltext-indexer sbin/rt-serializer sbin/rt-importer sbin/rt-passwd sbin/rt-munge-attachments bin/rt-crontool bin/rt-mailgate bin/rt" ac_config_files="$ac_config_files Makefile etc/RT_Config.pm lib/RT/Generated.pm t/data/configs/apache2.2+mod_perl.conf t/data/configs/apache2.2+fastcgi.conf t/data/configs/apache2.4+mod_perl.conf t/data/configs/apache2.4+fastcgi.conf" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # `ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # `set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 $as_echo "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else case $cache_file in #( */* | ?:*) mv -f confcache "$cache_file"$$ && mv -f "$cache_file"$$ "$cache_file" ;; #( *) mv -f confcache "$cache_file" ;; esac fi fi 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;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' # Transform confdefs.h into DEFS. # Protect against shell expansion while executing Makefile rules. # Protect against Makefile macro expansion. # # If the first sed substitution is executed (which looks for macros that # take arguments), then branch to the quote section. Otherwise, # look for a macro that doesn't take arguments. ac_script=' :mline /\\$/{ N s,\\\n,, b mline } t clear :clear s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g t quote s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g t quote b any :quote s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g s/\[/\\&/g s/\]/\\&/g s/\$/$$/g H :any ${ g s/^\n// s/\n/ /g p } ' DEFS=`sed -n "$ac_script" confdefs.h` ac_libobjs= ac_ltlibobjs= U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`$as_echo "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 $as_echo "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) 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 for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 ## ----------------------------------- ## ## Main body of $CONFIG_STATUS script. ## ## ----------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # 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-5.0.1, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ \`$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE Configuration files: $config_files Report bugs to ." _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-5.0.1 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" Copyright (C) 2012 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' INSTALL='$INSTALL' test -n "\$AWK" || AWK=awk _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) $as_echo "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) $as_echo "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --he | --h | --help | --hel | -h ) $as_echo "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: \`$1' Try \`$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX $as_echo "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "etc/upgrade/3.8-ical-extension") CONFIG_FILES="$CONFIG_FILES etc/upgrade/3.8-ical-extension" ;; "etc/upgrade/4.0-customfield-checkbox-extension") CONFIG_FILES="$CONFIG_FILES etc/upgrade/4.0-customfield-checkbox-extension" ;; "etc/upgrade/generate-rtaddressregexp") CONFIG_FILES="$CONFIG_FILES etc/upgrade/generate-rtaddressregexp" ;; "etc/upgrade/reset-sequences") CONFIG_FILES="$CONFIG_FILES etc/upgrade/reset-sequences" ;; "etc/upgrade/sanity-check-stylesheets") CONFIG_FILES="$CONFIG_FILES etc/upgrade/sanity-check-stylesheets" ;; "etc/upgrade/shrink-cgm-table") CONFIG_FILES="$CONFIG_FILES etc/upgrade/shrink-cgm-table" ;; "etc/upgrade/shrink-transactions-table") CONFIG_FILES="$CONFIG_FILES etc/upgrade/shrink-transactions-table" ;; "etc/upgrade/switch-templates-to") CONFIG_FILES="$CONFIG_FILES etc/upgrade/switch-templates-to" ;; "etc/upgrade/time-worked-history") CONFIG_FILES="$CONFIG_FILES etc/upgrade/time-worked-history" ;; "etc/upgrade/upgrade-articles") CONFIG_FILES="$CONFIG_FILES etc/upgrade/upgrade-articles" ;; "etc/upgrade/upgrade-assets") CONFIG_FILES="$CONFIG_FILES etc/upgrade/upgrade-assets" ;; "etc/upgrade/upgrade-authtokens") CONFIG_FILES="$CONFIG_FILES etc/upgrade/upgrade-authtokens" ;; "etc/upgrade/upgrade-configurations") CONFIG_FILES="$CONFIG_FILES etc/upgrade/upgrade-configurations" ;; "etc/upgrade/vulnerable-passwords") CONFIG_FILES="$CONFIG_FILES etc/upgrade/vulnerable-passwords" ;; "etc/upgrade/upgrade-sla") CONFIG_FILES="$CONFIG_FILES etc/upgrade/upgrade-sla" ;; "sbin/rt-ldapimport") CONFIG_FILES="$CONFIG_FILES sbin/rt-ldapimport" ;; "sbin/rt-attributes-viewer") CONFIG_FILES="$CONFIG_FILES sbin/rt-attributes-viewer" ;; "sbin/rt-preferences-viewer") CONFIG_FILES="$CONFIG_FILES sbin/rt-preferences-viewer" ;; "sbin/rt-session-viewer") CONFIG_FILES="$CONFIG_FILES sbin/rt-session-viewer" ;; "sbin/rt-dump-initialdata") CONFIG_FILES="$CONFIG_FILES sbin/rt-dump-initialdata" ;; "sbin/rt-dump-metadata") CONFIG_FILES="$CONFIG_FILES sbin/rt-dump-metadata" ;; "sbin/rt-setup-database") CONFIG_FILES="$CONFIG_FILES sbin/rt-setup-database" ;; "sbin/rt-test-dependencies") CONFIG_FILES="$CONFIG_FILES sbin/rt-test-dependencies" ;; "sbin/rt-email-digest") CONFIG_FILES="$CONFIG_FILES sbin/rt-email-digest" ;; "sbin/rt-email-dashboards") CONFIG_FILES="$CONFIG_FILES sbin/rt-email-dashboards" ;; "sbin/rt-externalize-attachments") CONFIG_FILES="$CONFIG_FILES sbin/rt-externalize-attachments" ;; "sbin/rt-clean-sessions") CONFIG_FILES="$CONFIG_FILES sbin/rt-clean-sessions" ;; "sbin/rt-shredder") CONFIG_FILES="$CONFIG_FILES sbin/rt-shredder" ;; "sbin/rt-validator") CONFIG_FILES="$CONFIG_FILES sbin/rt-validator" ;; "sbin/rt-validate-aliases") CONFIG_FILES="$CONFIG_FILES sbin/rt-validate-aliases" ;; "sbin/rt-email-group-admin") CONFIG_FILES="$CONFIG_FILES sbin/rt-email-group-admin" ;; "sbin/rt-search-attributes") CONFIG_FILES="$CONFIG_FILES sbin/rt-search-attributes" ;; "sbin/rt-server") CONFIG_FILES="$CONFIG_FILES sbin/rt-server" ;; "sbin/rt-server.fcgi") CONFIG_FILES="$CONFIG_FILES sbin/rt-server.fcgi" ;; "sbin/standalone_httpd") CONFIG_FILES="$CONFIG_FILES sbin/standalone_httpd" ;; "sbin/rt-setup-fulltext-index") CONFIG_FILES="$CONFIG_FILES sbin/rt-setup-fulltext-index" ;; "sbin/rt-fulltext-indexer") CONFIG_FILES="$CONFIG_FILES sbin/rt-fulltext-indexer" ;; "sbin/rt-serializer") CONFIG_FILES="$CONFIG_FILES sbin/rt-serializer" ;; "sbin/rt-importer") CONFIG_FILES="$CONFIG_FILES sbin/rt-importer" ;; "sbin/rt-passwd") CONFIG_FILES="$CONFIG_FILES sbin/rt-passwd" ;; "sbin/rt-munge-attachments") CONFIG_FILES="$CONFIG_FILES sbin/rt-munge-attachments" ;; "bin/rt-crontool") CONFIG_FILES="$CONFIG_FILES bin/rt-crontool" ;; "bin/rt-mailgate") CONFIG_FILES="$CONFIG_FILES bin/rt-mailgate" ;; "bin/rt") CONFIG_FILES="$CONFIG_FILES bin/rt" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "etc/RT_Config.pm") CONFIG_FILES="$CONFIG_FILES etc/RT_Config.pm" ;; "lib/RT/Generated.pm") CONFIG_FILES="$CONFIG_FILES lib/RT/Generated.pm" ;; "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" ;; "t/data/configs/apache2.4+mod_perl.conf") CONFIG_FILES="$CONFIG_FILES t/data/configs/apache2.4+mod_perl.conf" ;; "t/data/configs/apache2.4+fastcgi.conf") CONFIG_FILES="$CONFIG_FILES t/data/configs/apache2.4+fastcgi.conf" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to `$tmp'. $debug || { tmp= ac_tmp= trap '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 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && 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. # This happens for instance with `./config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >$CONFIG_STATUS || ac_write_fail=1 rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" eval set X " :F $CONFIG_FILES " shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) 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 `:'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || 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'" done # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 $as_echo "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`$as_echo "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$ac_tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # case $INSTALL in [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; esac _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= ac_sed_dataroot=' /datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 $as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t s&@INSTALL@&$ac_INSTALL&;t t $ac_datarootdir_hack " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 $as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" case $ac_file in -) 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 ;; esac case $ac_file$ac_mode in "etc/upgrade/3.8-ical-extension":F) chmod ug+x $ac_file ;; "etc/upgrade/4.0-customfield-checkbox-extension":F) chmod ug+x $ac_file ;; "etc/upgrade/generate-rtaddressregexp":F) chmod ug+x $ac_file ;; "etc/upgrade/reset-sequences":F) chmod ug+x $ac_file ;; "etc/upgrade/sanity-check-stylesheets":F) chmod ug+x $ac_file ;; "etc/upgrade/shrink-cgm-table":F) chmod ug+x $ac_file ;; "etc/upgrade/shrink-transactions-table":F) chmod ug+x $ac_file ;; "etc/upgrade/switch-templates-to":F) chmod ug+x $ac_file ;; "etc/upgrade/time-worked-history":F) chmod ug+x $ac_file ;; "etc/upgrade/upgrade-articles":F) chmod ug+x $ac_file ;; "etc/upgrade/upgrade-assets":F) chmod ug+x $ac_file ;; "etc/upgrade/upgrade-authtokens":F) chmod ug+x $ac_file ;; "etc/upgrade/upgrade-configurations":F) chmod ug+x $ac_file ;; "etc/upgrade/vulnerable-passwords":F) chmod ug+x $ac_file ;; "etc/upgrade/upgrade-sla":F) chmod ug+x $ac_file ;; "sbin/rt-ldapimport":F) chmod ug+x $ac_file ;; "sbin/rt-attributes-viewer":F) chmod ug+x $ac_file ;; "sbin/rt-preferences-viewer":F) chmod ug+x $ac_file ;; "sbin/rt-session-viewer":F) chmod ug+x $ac_file ;; "sbin/rt-dump-initialdata":F) chmod ug+x $ac_file ;; "sbin/rt-dump-metadata":F) chmod ug+x $ac_file ;; "sbin/rt-setup-database":F) chmod ug+x $ac_file ;; "sbin/rt-test-dependencies":F) chmod ug+x $ac_file ;; "sbin/rt-email-digest":F) chmod ug+x $ac_file ;; "sbin/rt-email-dashboards":F) chmod ug+x $ac_file ;; "sbin/rt-externalize-attachments":F) chmod ug+x $ac_file ;; "sbin/rt-clean-sessions":F) chmod ug+x $ac_file ;; "sbin/rt-shredder":F) chmod ug+x $ac_file ;; "sbin/rt-validator":F) chmod ug+x $ac_file ;; "sbin/rt-validate-aliases":F) chmod ug+x $ac_file ;; "sbin/rt-email-group-admin":F) chmod ug+x $ac_file ;; "sbin/rt-search-attributes":F) chmod ug+x $ac_file ;; "sbin/rt-server":F) chmod ug+x $ac_file ;; "sbin/rt-server.fcgi":F) chmod ug+x $ac_file ;; "sbin/standalone_httpd":F) chmod ug+x $ac_file ;; "sbin/rt-setup-fulltext-index":F) chmod ug+x $ac_file ;; "sbin/rt-fulltext-indexer":F) chmod ug+x $ac_file ;; "sbin/rt-serializer":F) chmod ug+x $ac_file ;; "sbin/rt-importer":F) chmod ug+x $ac_file ;; "sbin/rt-passwd":F) chmod ug+x $ac_file ;; "sbin/rt-munge-attachments":F) chmod ug+x $ac_file ;; "bin/rt-crontool":F) chmod ug+x $ac_file ;; "bin/rt-mailgate":F) chmod ug+x $ac_file ;; "bin/rt":F) chmod ug+x $ac_file ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_files=$ac_clean_files_save test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi rt-5.0.1/Dockerfile000644 000765 000024 00000000403 14005011336 014770 0ustar00sunnavystaff000000 000000 # This Dockerfile is for testing only. FROM bpssysadmin/rt-base-debian-stretch ENV RT_TEST_PARALLEL 1 ENV RT_TEST_DEVEL 1 ENV RT_DBA_USER root ENV RT_DBA_PASSWORD password ENV RT_TEST_DB_HOST=172.17.0.2 ENV RT_TEST_RT_HOST=172.17.0.3 CMD tail -f /dev/null rt-5.0.1/Makefile000644 000765 000024 00000043605 14005021226 014450 0ustar00sunnavystaff000000 000000 # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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 }}} # # DO NOT HAND-EDIT the file named 'Makefile'. This file is autogenerated. # Have a look at "configure" and "Makefile.in" instead # PERL = /usr/bin/perl INSTALL = ./install-sh CC = @CC@ RT_LAYOUT = relative CONFIG_FILE_PATH = /opt/rt5/etc CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_Config.pm SITE_CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_SiteConfig.pm RT_VERSION_MAJOR = 5 RT_VERSION_MINOR = 0 RT_VERSION_PATCH = 1 RT_VERSION = $(RT_VERSION_MAJOR).$(RT_VERSION_MINOR).$(RT_VERSION_PATCH) TAG = rt-$(RT_VERSION_MAJOR)-$(RT_VERSION_MINOR)-$(RT_VERSION_PATCH) # This is the group that all of the installed files will be chgrp'ed to. RTGROUP = www # User which should own rt binaries. BIN_OWNER = root # User that should own all of RT's libraries, generally root. LIBS_OWNER = root # Group that should own all of RT's libraries, generally root. LIBS_GROUP = bin WEB_USER = www WEB_GROUP = www # DESTDIR allows you to specify that RT be installed somewhere other than # where it will eventually reside. DESTDIR _must_ have a trailing slash # if it's defined. DESTDIR = RT_PATH = /opt/rt5 RT_ETC_PATH = /opt/rt5/etc RT_BIN_PATH = /opt/rt5/bin RT_SBIN_PATH = /opt/rt5/sbin RT_LIB_PATH = /opt/rt5/lib RT_MAN_PATH = /opt/rt5/man RT_VAR_PATH = /opt/rt5/var RT_DOC_PATH = /opt/rt5/docs RT_FONT_PATH = /opt/rt5/share/fonts RT_LEXICON_PATH = /opt/rt5/share/po RT_STATIC_PATH = /opt/rt5/share/static RT_LOCAL_PATH = /opt/rt5/local LOCAL_PLUGIN_PATH = /opt/rt5/local/plugins LOCAL_ETC_PATH = /opt/rt5/local/etc LOCAL_LIB_PATH = /opt/rt5/local/lib LOCAL_LEXICON_PATH = /opt/rt5/local/po LOCAL_STATIC_PATH = /opt/rt5/local/static MASON_HTML_PATH = /opt/rt5/share/html MASON_LOCAL_HTML_PATH = /opt/rt5/local/html MASON_DATA_PATH = /opt/rt5/var/mason_data MASON_SESSION_PATH = /opt/rt5/var/session_data RT_LOG_PATH = /opt/rt5/var/log # RT_READABLE_DIR_MODE is the mode of directories that are generally meant # to be accessable RT_READABLE_DIR_MODE = 0755 # RT's CLI RT_CLI_BIN = rt # RT's mail gateway RT_MAILGATE_BIN = rt-mailgate # RT's cron tool RT_CRON_BIN = rt-crontool BINARIES = $(RT_MAILGATE_BIN) \ $(RT_CLI_BIN) \ $(RT_CRON_BIN) SYSTEM_BINARIES = rt-attributes-viewer \ rt-munge-attachments \ rt-clean-sessions \ rt-dump-initialdata \ rt-dump-metadata \ rt-email-dashboards \ rt-email-digest \ rt-email-group-admin \ rt-externalize-attachments \ rt-fulltext-indexer \ rt-importer \ rt-ldapimport \ rt-passwd \ rt-preferences-viewer \ rt-search-attributes \ rt-serializer \ rt-server \ rt-server.fcgi \ rt-session-viewer \ rt-setup-database \ rt-setup-fulltext-index \ rt-shredder \ rt-test-dependencies \ rt-validator \ rt-validate-aliases \ standalone_httpd ETC_FILES = acl.Pg \ acl.Oracle \ acl.mysql \ schema.Pg \ schema.Oracle \ schema.mysql \ schema.SQLite \ cpanfile \ initialdata WEB_HANDLER = standalone # # DB_TYPE defines what sort of database RT trys to talk to # "mysql", "Oracle", "Pg", and "SQLite" are known to work. DB_TYPE = SQLite # Set DBA to the name of a unix account with the proper permissions and # environment to run your commandline SQL sbin # Set DB_DBA to the name of a DB user with permission to create new databases # For mysql, you probably want 'root' # For Pg, you probably want 'postgres' # For Oracle, you want 'system' DB_DBA = root DB_HOST = localhost # If you're not running your database server on its default port, # specifiy the port the database server is running on below. # It's generally safe to leave this blank DB_PORT = # # Set this to the canonical name of the interface RT will be talking to the # database on. If you said that the RT_DB_HOST above was "localhost," this # should be too. This value will be used to grant rt access to the database. # If you want to access the RT database from multiple hosts, you'll need # to grant those database rights by hand. # DB_RT_HOST = localhost # set this to the name you want to give to the RT database in # your database server. For Oracle, this should be the name of your sid DB_DATABASE = rt5 DB_RT_USER = rt_user DB_RT_PASS = rt_pass TEST_FILES = t/*.t t/*/*.t t/*/*/*.t TEST_VERBOSE = 0 RT_TEST_PARALLEL_NUM ?= 5 #################################################################### all: default default: @echo "Please read RT's README before beginning your installation." instruct: @echo "Congratulations. RT is now installed." @echo "" @echo "" @echo "You must now configure RT by editing $(SITE_CONFIG_FILE)." @echo "" @echo "(You will definitely need to set RT's database password in " @echo "$(SITE_CONFIG_FILE) before continuing. Not doing so could be " @echo "very dangerous. Note that you do not have to manually add a " @echo "database user or set up a database for RT. These actions will be " @echo "taken care of in the next step.)" @echo "" @echo "After that, you need to initialize RT's database by running" @echo " 'make initialize-database'" upgrade-instruct: @echo "Congratulations. RT has been upgraded. You should now check over" @echo "$(CONFIG_FILE) for any necessary site customization. Additionally," @echo "you should update RT's system database objects by running " @echo " make upgrade-database" upgrade: testdeps config-install dirs files-install fixperms upgrade-instruct testdeps: $(PERL) ./sbin/rt-test-dependencies depends: fixdeps fixdeps: $(PERL) ./sbin/rt-test-dependencies --install #}}} fixperms: # Make the libraries readable chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)$(RT_PATH) chown -R $(LIBS_OWNER) $(DESTDIR)$(RT_LIB_PATH) chgrp -R $(LIBS_GROUP) $(DESTDIR)$(RT_LIB_PATH) chmod -R u+rwX,go-w,go+rX $(DESTDIR)$(RT_LIB_PATH) chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)$(RT_BIN_PATH) chmod 0755 $(DESTDIR)$(RT_ETC_PATH) cd $(DESTDIR)$(RT_ETC_PATH) && chmod 0400 $(ETC_FILES) #TODO: the config file should probably be able to have its # owner set separately from the binaries. chown -R $(BIN_OWNER) $(DESTDIR)$(RT_ETC_PATH) chgrp -R $(RTGROUP) $(DESTDIR)$(RT_ETC_PATH) chmod 0440 $(DESTDIR)$(CONFIG_FILE) chmod 0640 $(DESTDIR)$(SITE_CONFIG_FILE) # Make the system binaries cd $(DESTDIR)$(RT_BIN_PATH) && ( chmod 0755 $(BINARIES) ; chown $(BIN_OWNER) $(BINARIES); chgrp $(RTGROUP) $(BINARIES)) # Make the system binaries executable also cd $(DESTDIR)$(RT_SBIN_PATH) && ( chmod 0755 $(SYSTEM_BINARIES) ; chown $(BIN_OWNER) $(SYSTEM_BINARIES); chgrp $(RTGROUP) $(SYSTEM_BINARIES)) # Make upgrade scripts executable if they are in the source. # ( cd etc/upgrade && find . -type f -not -name '*.in' -perm +0111 -print ) | while read file ; do \ chmod a+x "$(DESTDIR)$(RT_ETC_PATH)/upgrade/$$file" ; \ done # Make the web ui readable by all. chmod -R u+rwX,go-w,go+rX $(DESTDIR)$(MASON_HTML_PATH) \ $(DESTDIR)$(MASON_LOCAL_HTML_PATH) \ $(DESTDIR)$(RT_LEXICON_PATH) \ $(DESTDIR)$(LOCAL_LEXICON_PATH) \ $(DESTDIR)$(RT_STATIC_PATH) \ $(DESTDIR)$(LOCAL_STATIC_PATH) chown -R $(LIBS_OWNER) $(DESTDIR)$(MASON_HTML_PATH) \ $(DESTDIR)$(MASON_LOCAL_HTML_PATH) \ $(DESTDIR)$(RT_LEXICON_PATH) \ $(DESTDIR)$(LOCAL_LEXICON_PATH) \ $(DESTDIR)$(RT_STATIC_PATH) \ $(DESTDIR)$(LOCAL_STATIC_PATH) chgrp -R $(LIBS_GROUP) $(DESTDIR)$(MASON_HTML_PATH) \ $(DESTDIR)$(MASON_LOCAL_HTML_PATH) \ $(DESTDIR)$(RT_LEXICON_PATH) \ $(DESTDIR)$(LOCAL_LEXICON_PATH) \ $(DESTDIR)$(RT_STATIC_PATH) \ $(DESTDIR)$(LOCAL_STATIC_PATH) # Make the web ui's data dir writable chmod 0770 $(DESTDIR)$(MASON_DATA_PATH) \ $(DESTDIR)$(MASON_SESSION_PATH) chown -R $(WEB_USER) $(DESTDIR)$(MASON_DATA_PATH) \ $(DESTDIR)$(MASON_SESSION_PATH) chgrp -R $(WEB_GROUP) $(DESTDIR)$(MASON_DATA_PATH) \ $(DESTDIR)$(MASON_SESSION_PATH) dirs: $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_LOG_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_FONT_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_LEXICON_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_STATIC_PATH) $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH) $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH)/cache $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH)/etc $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH)/obj $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_SESSION_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(MASON_HTML_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(MASON_LOCAL_HTML_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_ETC_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_LIB_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_PLUGIN_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_LEXICON_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_STATIC_PATH) clean-mason-cache: rm -rf $(DESTDIR)$(MASON_DATA_PATH)/cache/* rm -rf $(DESTDIR)$(MASON_DATA_PATH)/etc/* rm -rf $(DESTDIR)$(MASON_DATA_PATH)/obj/* install: testdeps config-install dirs files-install fixperms instruct files-install: libs-install etc-install config-install bin-install sbin-install html-install doc-install font-install po-install static-install config-install: $(INSTALL) -m 0755 -o $(BIN_OWNER) -g $(RTGROUP) -d $(DESTDIR)$(CONFIG_FILE_PATH) -$(INSTALL) -m 0440 -o $(BIN_OWNER) -g $(RTGROUP) etc/RT_Config.pm $(DESTDIR)$(CONFIG_FILE) [ -f $(DESTDIR)$(SITE_CONFIG_FILE) ] || $(INSTALL) -m 0640 -o $(BIN_OWNER) -g $(RTGROUP) etc/RT_SiteConfig.pm $(DESTDIR)$(SITE_CONFIG_FILE) @echo "Installed configuration. About to install RT in $(RT_PATH)" test: $(PERL) "-MExtUtils::Command::MM" -e "test_harness($(TEST_VERBOSE), 'lib')" $(TEST_FILES) parallel-test: test-parallel test-parallel: RT_TEST_PARALLEL=1 $(PERL) "-MApp::Prove" -e 'my $$p = App::Prove->new(); $$p->process_args("-wlrj$(RT_TEST_PARALLEL_NUM)","--state=slow,save", "t"); exit( $$p->run() ? 0 : 1 )' regression-reset-db: force-dropdb $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action init --dba-password '' initdb :: initialize-database initialize-database: $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action init --prompt-for-dba-password upgrade-database: $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action upgrade --prompt-for-dba-password dropdb: $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action drop --prompt-for-dba-password force-dropdb: $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action drop --dba-password '' --force critic: perlcritic --quiet sbin bin lib libs-install: [ -d $(DESTDIR)$(RT_LIB_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_LIB_PATH) -( cd lib && find . -type d -print ) | while read dir ; do \ $(INSTALL) -m 0755 -d "$(DESTDIR)$(RT_LIB_PATH)/$$dir" ; \ done -( cd lib && find . -type f -print ) | while read file ; do \ $(INSTALL) -m 0644 "lib/$$file" "$(DESTDIR)$(RT_LIB_PATH)/$$file" ; \ done html-install: [ -d $(DESTDIR)$(MASON_HTML_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(MASON_HTML_PATH) -( cd share/html && find . -type d -print ) | while read dir ; do \ $(INSTALL) -m 0755 -d "$(DESTDIR)$(MASON_HTML_PATH)/$$dir" ; \ done -( cd share/html && find . -type f -print ) | while read file ; do \ $(INSTALL) -m 0644 "share/html/$$file" "$(DESTDIR)$(MASON_HTML_PATH)/$$file" ; \ done $(MAKE) clean-mason-cache font-install: [ -d $(DESTDIR)$(RT_FONT_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_FONT_PATH) -( cd share/fonts && find . -type f -print ) | while read file ; do \ $(INSTALL) -m 0644 "share/fonts/$$file" "$(DESTDIR)$(RT_FONT_PATH)/$$file" ; \ done po-install: [ -d $(DESTDIR)$(RT_LEXICON_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_LEXICON_PATH) -( cd share/po && find . -type f -print ) | while read file ; do \ $(INSTALL) -m 0644 "share/po/$$file" "$(DESTDIR)$(RT_LEXICON_PATH)/$$file" ; \ done static-install: [ -d $(DESTDIR)$(RT_STATIC_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_STATIC_PATH) -( cd share/static && find . -type d -print ) | while read dir ; do \ $(INSTALL) -m 0755 -d "$(DESTDIR)$(RT_STATIC_PATH)/$$dir" ; \ done -( cd share/static && find . -type f -print ) | while read file ; do \ $(INSTALL) -m 0644 "share/static/$$file" "$(DESTDIR)$(RT_STATIC_PATH)/$$file" ; \ done doc-install: # RT 3.0.0 - RT 3.0.2 would accidentally create a file instead of a dir -[ -f $(DESTDIR)$(RT_DOC_PATH) ] && rm $(DESTDIR)$(RT_DOC_PATH) [ -d $(DESTDIR)$(RT_DOC_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_DOC_PATH) -( cd docs && find . -type d -print ) | while read dir ; do \ $(INSTALL) -m 0755 -d "$(DESTDIR)$(RT_DOC_PATH)/$$dir" ; \ done -( cd docs && find . -type f -print ) | while read file ; do \ $(INSTALL) -m 0644 "docs/$$file" "$(DESTDIR)$(RT_DOC_PATH)/$$file" ; \ done -$(INSTALL) -m 0644 ./README $(DESTDIR)$(RT_DOC_PATH)/ etc-install: [ -d $(DESTDIR)$(RT_ETC_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_ETC_PATH) [ -d "$(DESTDIR)$(RT_ETC_PATH)/RT_SiteConfig.d" ] || $(INSTALL) -m 0755 -d "$(DESTDIR)$(RT_ETC_PATH)/RT_SiteConfig.d" for file in $(ETC_FILES) ; do \ $(INSTALL) -m 0644 "etc/$$file" "$(DESTDIR)$(RT_ETC_PATH)/" ; \ done [ -d $(DESTDIR)$(RT_ETC_PATH)/upgrade ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_ETC_PATH)/upgrade -( cd etc/upgrade && find . -type d -print ) | while read dir ; do \ $(INSTALL) -m 0755 -d "$(DESTDIR)$(RT_ETC_PATH)/upgrade/$$dir" ; \ done -( cd etc/upgrade && find . -type f -not -name '*.in' -print ) | while read file ; do \ $(INSTALL) -m 0644 "etc/upgrade/$$file" "$(DESTDIR)$(RT_ETC_PATH)/upgrade/$$file" ; \ done sbin-install: $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_SBIN_PATH) for file in $(SYSTEM_BINARIES) ; do \ $(INSTALL) -o $(BIN_OWNER) -g $(RTGROUP) -m 0755 "sbin/$$file" "$(DESTDIR)$(RT_SBIN_PATH)/" ; \ done bin-install: $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_BIN_PATH) for file in $(BINARIES) ; do \ $(INSTALL) -o $(BIN_OWNER) -g $(RTGROUP) -m 0755 "bin/$$file" "$(DESTDIR)$(RT_BIN_PATH)/" ; \ done regenerate-catalogs: $(PERL) devel/tools/extract-message-catalog license-tag: $(PERL) devel/tools/license_tag start-httpd: $(PERL) sbin/standalone_httpd & start-server: $(PERL) sbin/rt-server & SNAPSHOT=$(shell git describe --tags) THIRD_PARTY=devel/third-party/ snapshot: build-snapshot build-third-party clearsign-snapshot clearsign-third-party snapshot-shasums build-snapshot: git archive --prefix "$(SNAPSHOT)/" HEAD | tar -xf - ( cd $(SNAPSHOT) && \ echo "$(SNAPSHOT)" > .tag && \ autoconf && \ INSTALL=./install-sh PERL=/usr/bin/perl ./configure \ --with-db-type=SQLite \ --enable-layout=relative \ --with-web-handler=standalone && \ rm -rf autom4te.cache \ config.status config.log config.pld \ ) tar -czf "$(SNAPSHOT).tar.gz" "$(SNAPSHOT)/" rm -fr "$(SNAPSHOT)/" clearsign-snapshot: gpg --armor --detach-sign "$(SNAPSHOT).tar.gz" build-third-party: git archive --prefix "$(SNAPSHOT)/$(THIRD_PARTY)" HEAD:$(THIRD_PARTY) \ | gzip > "$(SNAPSHOT)-third-party-source.tar.gz" rm -rf "$(SNAPSHOT)/$(THIRD_PARTY)" clearsign-third-party: gpg --armor --detach-sign "$(SNAPSHOT)-third-party-source.tar.gz" snapshot-shasums: sha1sum $(SNAPSHOT)*.tar.gz* vessel-import: build-snapshot [ -d $(VESSEL) ] || (echo "VESSEL isn't a path to your shipwright vessel" && exit -1) cp $(VESSEL)/scripts/RT/build.pl /tmp/build.pl ./sbin/rt-test-dependencies --with-standalone --with-fastcgi --with-sqlite --list > /tmp/rt.yml shipwright import file:$(SNAPSHOT).tar.gz \ --require-yml /tmp/rt.yml \ --build-script /tmp/build.pl \ --name RT \ --repository fs:$(VESSEL) \ --log-level=info \ --skip cpan-capitalization,cpan-mod_perl,cpan-Encode,cpan-PPI,cpan-Test-Exception-LessClever,cpan-Test-Manifest,cpan-Test-Object,cpan-Test-Pod,cpan-Test-Requires,cpan-Test-SubCalls,cpan-Test-cpan-Tester,cpan-Test-Warn --skip-all-recommends mv $(VESSEL)/scripts/RT/build $(VESSEL)/scripts/RT/build.pl rt-5.0.1/sbin/000755 000765 000024 00000000000 14005021226 013733 5ustar00sunnavystaff000000 000000 rt-5.0.1/etc/000755 000765 000024 00000000000 14005021226 013553 5ustar00sunnavystaff000000 000000 rt-5.0.1/docs/000755 000765 000024 00000000000 14005011336 013731 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/000755 000765 000024 00000000000 14005011336 013244 5ustar00sunnavystaff000000 000000 rt-5.0.1/README000644 000765 000024 00000033770 14005011336 013673 0ustar00sunnavystaff000000 000000 RT is an enterprise-grade issue tracking system. It allows organizations to keep track of what needs to get done, who is working on which tasks, what's already been done, and when tasks were (or weren't) completed. RT doesn't cost anything to use, no matter how much you use it; it is freely available under the terms of Version 2 of the GNU General Public License. RT is commercially-supported software. To purchase support, training, custom development, or professional services, please get in touch with us at . REQUIRED PACKAGES ----------------- o Perl 5.10.1 or later (http://www.perl.org). RT won't start on versions of Perl older than 5.10.1. o A supported SQL database Currently supported: MySQL 5.7 with InnoDB support MariaDB 10.2 or later with InnoDB support Postgres 9.5 or later Oracle 12c or later SQLite 3.0 or later; for testing only, no upgrade path guaranteed o Apache version 2.x (https://httpd.apache.org) with mod_fcgid -- (https://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html) or mod_perl -- (http://perl.apache.org) or nginx -- (https://nginx.org/) or another webserver with FastCGI support RT's FastCGI handler needs to access RT's configuration file. o Various and sundry perl modules A tool included with RT takes care of the installation of most of these automatically during the install process. The tool supplied with RT uses Perl's CPAN (http://www.cpan.org) to install modules. Some operating systems package all or some of the modules required, and you may be better off installing the modules that way. OPTIONAL DEPENDENCIES --------------------- o Full-text indexing support in your database The databases listed above all have options for full-text indexing (excluding SQLite). See docs/full_text_indexing.pod for details. o An external HTML converter Installing an external utility to convert HTML can improve performance. See the $HTMLFormatter configuration option for details. GENERAL INSTALLATION -------------------- 1) Unpack this distribution other than where you want to install RT. To do this cleanly, run the following command: tar xzvf rt.tar.gz -C /tmp 2) Run the "configure" script. To see the list of options, run: ./configure --help Peruse the options, then rerun ./configure with the flags you want. RT defaults to installing in /opt/rt5 with MySQL as its database. It tries to guess which of www-data, www, apache or nobody your webserver will run as, but you can override that behavior. Note that the default install directory in /opt/rt5 does not work under SELinux's default configuration. If you are upgrading from a previous version of RT, please review the upgrade notes for the appropriate versions, which can be found in docs/UPGRADING-* If you are coming from 4.4.x to 5.0.x you should review both the UPGRADING-4.4 and UPGRADING-5.0 files. Similarly, if you were coming from 4.2.x, you would want to review the UPGRADING-4.2, UPGRADING-4.4 and UPGRADING-5.0 documents. Any upgrade steps given in version-specific UPGRADING files should be run after the rest of the steps below; however, please read the relevant documentation before beginning the upgrade, so as to be aware of important changes. RT stores the arguments given to ./configure at the top of the etc/RT_Config.pm file in case you need to recreate your previous use of ./configure. 3) Make sure that RT has the Perl and system libraries it needs to run. Check for missing dependencies by running: make testdeps 4) If the script reports any missing dependencies, install them by hand, or run the following command as a user who has permission to install perl modules on your system: make fixdeps Some modules require user input or environment variables to install correctly, so it may be necessary to install them manually. Some modules also require external source libraries, so you may need to install additional packages. If you are having trouble installing GD, refer to "Installing GD libraries" in docs/charts.pod. Ticket relationship graphing requires the graphviz library which you should install using your distribution's package manager. See docs/rt_perl.pod for additional information about installing perl and RT's dependencies. 5) Check to make sure everything was installed properly. make testdeps It might sometimes be necessary to run "make fixdeps" several times to install all necessary perl modules. 6a) If this is a NEW installation (not an upgrade): As a user with permission to install RT in your chosen directory, type: make install To configure RT with the web installer, run: /opt/rt5/sbin/rt-server and follow the instructions. Once completed, you should now have a working RT instance running with the standalone rt-server. Press Ctrl-C to stop it, and proceed to Step 7 to configure a recommended deployment environment for production. To configure RT manually, you must setup etc/RT_SiteConfig.pm in your RT installation directory. You'll need to add any values you need to change from the defaults in etc/RT_Config.pm As a user with permission to read RT's configuration file, type: make initialize-database If the make fails, type: make dropdb and re-run 'make initialize-database'. 6b) If you are UPGRADING from a previous installation: Before upgrading, always ensure that you have a complete current backup. If you don't have a current backup, upgrading your database could accidentally damage it and lose data, or worse. If you are using MySQL, please read the instructions in docs/UPGRADING.mysql as well to ensure that you do not corrupt existing data. First, stop your webserver. You may also wish to put incoming email into a hold queue, to avoid temporary delivery failure messages if your upgrade is expected to take several hours. Next, install new binaries, config files and libraries by running: make upgrade This will also prompt you to upgrade your database by running: make upgrade-database You should back up your database before running this command. When you run it, you will be prompted for your previous version of RT (such as 4.4.1) so that the appropriate set of database upgrades can be applied. If 'make upgrade-database' completes without error, your upgrade has been successful; you should now run any commands that were supplied in version-specific UPGRADING documentation. You should then restart your webserver. Depending on the size and composition of your database, some upgrade steps may run for a long time. You may also need extra disk space or other resources while running upgrade steps. It's a good idea to run through the upgrade steps on a test server so you know what to expect before running on your production system. 7) Configure the web server, as described in docs/web_deployment.pod, and the email gateway, as described below. NOTE: The default credentials for RT are: User: root Pass: password Not changing the root password from the default is a SECURITY risk! 8) Set up users, groups, queues, scrips and access control. Until you do this, RT will not be able to send or receive email, nor will it be more than marginally functional. This is not an optional step. 9) Set up automated recurring tasks (cronjobs): Depending on your configuration, RT stores sessions in the database or on the file system. In either case, sessions are only needed until a user logs out, so old sessions should be cleaned up with this utility: perldoc /opt/rt5/sbin/rt-clean-sessions To generate email digest messages, you must arrange for the provided utility to be run once daily, and once weekly. You may also want to arrange for the rt-email-dashboards utility to be run hourly. If your task scheduler is cron, you can configure it by adding the following lines as /etc/cron.d/rt: 0 0 * * * root /opt/rt5/sbin/rt-clean-sessions 0 0 * * * root /opt/rt5/sbin/rt-email-digest -m daily 0 0 * * 0 root /opt/rt5/sbin/rt-email-digest -m weekly 0 * * * * root /opt/rt5/sbin/rt-email-dashboards Other optional features like full text search indexes, external attachments, etc., may also have recurring jobs to schedule in cron. Follow the documentation for these features when you enable them. 10) Configure the RT email gateway. To let email flow to your RT server, you need to add a few lines of configuration to your mail server's "aliases" file. These lines "pipe" incoming email messages from your mail server to RT. Add the following lines to /etc/aliases (or your local equivalent) on your mail server: rt: "|/opt/rt5/bin/rt-mailgate --queue general --action correspond --url http://rt.example.com/" rt-comment: "|/opt/rt5/bin/rt-mailgate --queue general --action comment --url http://rt.example.com/" You'll need to add similar lines for each queue you want to be able to send email to. To find out more about how to configure RT's email gateway, type: perldoc /opt/rt5/bin/rt-mailgate 11) Set up full text search Full text search (FTS) without database indexing is a very slow operation, and is thus disabled by default. You'll need to follow the instructions in docs/full_text_indexing.pod to enable FTS. 12) Set up automatic backups for RT and its data as described in the docs/system_administration/database.pod document. GETTING HELP ------------ If RT is mission-critical for you or if you use it heavily, we recommend that you purchase a commercial support contract. Details on support contracts are available at http://www.bestpractical.com or by writing to . We also offer managed hosting plans if you prefer to have someone else manage the RT server. If you're interested in having RT extended or customized or would like more information about commercial support options, please send email to to discuss rates and availability. COMMUNITY FORUM AND WIKI ------------------------ To keep up to date on the latest RT tips, techniques and extensions, you may wish to join the RT Community Forum website. You can find it here: https://forum.bestpractical.com You'll find many different categories of discussion there including the RT Users category for general RT topics. If you're interested in customizing RT code, there is a category for RT Developers with more technical topics. The RT wiki, at https://rt-wiki.bestpractical.com, is also a potential resource. SECURITY -------- If you believe you've discovered a security issue in RT, please send an email to with a detailed description of the issue, and a secure means to respond to you (such as your PGP public key). You can find our PGP key and fingerprint at https://bestpractical.com/security/ BUGS ---- RT's a pretty complex application, and as you get up to speed, you might run into some trouble. Generally, it's best to ask about things you run into on the Community Forum (or pick up a commercial support contract from Best Practical). But, sometimes people do run into bugs. In the exceedingly unlikely event that you hit a bug in RT, please report it! We'd love to hear about problems you have with RT, so we can fix them. To report a bug, send email to . Note that this sends email to our public RT instance. Do not include any information in your email that you don't want shown publicly, including contact information in your email signature. # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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 }}} rt-5.0.1/README.MySQL000644 000765 000024 00000005134 14005011336 014630 0ustar00sunnavystaff000000 000000 Starting with RT 5.0.0, the minimum supported MySQL version is 5.7.7 because this is the first version to provide full support for 4 byte utf8 characters in tables and indexes. Read on for details on this change. Note that MySQL 8 is not yet supported because of changes to the group keyword. RT 5.0.0 now defaults MySQL tables to utf8mb4, which is available in versions before 5.7.7. However, before MySQL version 5.7.7, utf8mb4 tables could not have indexes with type VARCHAR(255): the default size for index entries was 767 bytes, which is enough for 255 chars stored as at most 3 chars (the utf8 format), but not as 4 bytes (utf8mb4). 5.7.7 sets the default index size to 3072 for InnoDB tables, resolving that issue. https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-7.html#mysqld-5-7-7-feature In MySQL, RT uses the utf8mb4 character set to support all unicode characters, including the ones that are encoded with 4 bytes in utf8 (some Kanji characters and a good number of emojis). The DB tables and the RT are both set to this character set. If your MySQL database is used only for RT, you can consider setting the default character set to utf8mb4. This will ensure that backups and other database access outside of RT have the correct character set. This is done by adding the following lines to the MySQL configuration: [mysqld] character-set-server = utf8mb4 [client] default-character-set = utf8mb4 You can check the values your server is using by running this command: mysqladmin variables | grep -i character_set Setting the default is particularly important for mysqldump, to avoid backups to be silently corrupted. If the MySQL DB is shared with other applications and the default character set cannot be set to utf8mb4, the command to backup the database can be set explicitly: ( mysqldump --default-character-set=utf8mb4 rt5 --tables sessions --no-data --single-transaction; \ mysqldump --default-character-set=utf8mb4 rt5 --ignore-table rt5.sessions --single-transaction ) \ | gzip > rt-`date +%Y%m%d`.sql.gz Restoring a backup is done the usual way, since the character set for all tables is set to utf8mb4, there is no further need to tell MySQL about it: gunzip -c rt-20191125.sql.gz | mysql -uroot -p rt5 These character set updates now allow RT on MySQL to accept and store 4-byte characters like emojis. However, searches can still be inconsistent. You may be able to get different or better results by experimenting with different collation settings. For more information: https://stackoverflow.com/a/41148052 https://dev.mysql.com/doc/refman/5.7/en/charset-unicode-sets.html rt-5.0.1/COPYING000644 000765 000024 00000043070 14005011336 014040 0ustar00sunnavystaff000000 000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, Cambridge, MA 02139, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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., 675 Mass Ave, Cambridge, MA 02139, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. rt-5.0.1/lib/000755 000765 000024 00000000000 14005011336 013547 5ustar00sunnavystaff000000 000000 rt-5.0.1/config.layout000644 000765 000024 00000014621 14005011336 015511 0ustar00sunnavystaff000000 000000 ## ## config.layout -- Pre-defined Installation Path Layouts ## ## Hints: ## - layouts can be loaded with configure's --enable-layout=ID option ## - when no --enable-layout option is given, the default layout is `RT' ## - a trailing plus character (`+') on paths is replaced with a ## `/' suffix where is currently hardcoded to 'rt5'. ## (This may become a configurable parameter at some point.) ## ## The following variables must _all_ be set: ## prefix exec_prefix bindir sbindir sysconfdir mandir libdir ## datadir htmldir localstatedir logfiledir masonstatedir fontdir ## sessionstatedir customdir customhtmldir customlexdir customstaticdir ## (This can be seen in m4/rt_layout.m4.) ## # Default RT5 path layout. prefix: /opt/rt5 exec_prefix: ${prefix} bindir: ${exec_prefix}/bin sbindir: ${exec_prefix}/sbin sysconfdir: ${prefix}/etc mandir: ${prefix}/man plugindir: ${prefix}/plugins libdir: ${prefix}/lib datadir: ${prefix}/share htmldir: ${datadir}/html fontdir: ${datadir}/fonts lexdir: ${datadir}/po staticdir: ${datadir}/static manualdir: ${prefix}/docs localstatedir: ${prefix}/var logfiledir: ${localstatedir}/log masonstatedir: ${localstatedir}/mason_data sessionstatedir: ${localstatedir}/session_data customdir: ${prefix}/local custometcdir: ${customdir}/etc customhtmldir: ${customdir}/html customlexdir: ${customdir}/po customstaticdir: ${customdir}/static customlibdir: ${customdir}/lib customplugindir: ${customdir}/plugins prefix: . exec_prefix: ${prefix} bindir: ${exec_prefix}/bin sbindir: ${exec_prefix}/sbin sysconfdir: ${prefix}/etc mandir: ${prefix}/man plugindir: ${prefix}/plugins libdir: ${prefix}/lib datadir: ${prefix}/share htmldir: ${datadir}/html lexdir: ${datadir}/po staticdir: ${datadir}/static fontdir: ${datadir}/fonts manualdir: ${prefix}/docs localstatedir: ${prefix}/var logfiledir: ${localstatedir}/log masonstatedir: ${localstatedir}/mason_data sessionstatedir: ${localstatedir}/session_data customdir: ${prefix}/local custometcdir: ${customdir}/etc customhtmldir: ${customdir}/html customlexdir: ${customdir}/po customstaticdir: ${customdir}/static customlibdir: ${customdir}/lib customplugindir: ${customdir}/plugins prefix: /usr/local exec_prefix: ${prefix} bindir: ${prefix}/bin sbindir: ${prefix}/sbin sysconfdir: /etc+ datadir: ${prefix}/share # FIXME: missing support for lib64 libdir: ${prefix}/lib mandir: ${datadir}/man # FIXME: no such directory in FHS; shouldn't go to somewhere in "${datadir}/rt/"? plugindir: ${datadir}/plugins htmldir: ${datadir}/html lexdir: ${datadir}/po staticdir: ${datadir}/static fontdir: ${datadir}/fonts manualdir: ${datadir}/doc localstatedir: /var logfiledir: ${localstatedir}/log # XXX: "/var/cache/mason/*"? masonstatedir: ${localstatedir}/cache/mason_data sessionstatedir: ${localstatedir}/cache/session_data customdir: ${prefix}/local custometcdir: ${customdir}/etc customhtmldir: ${customdir}/html customlexdir: ${customdir}/po customstaticdir: ${customdir}/static customlibdir: ${customdir}/lib customplugindir: ${customdir}/plugins prefix: /usr/local exec_prefix: ${prefix} bindir: ${exec_prefix}/bin sbindir: ${exec_prefix}/sbin sysconfdir: ${prefix}/etc+ mandir: ${prefix}/man plugindir: ${prefix}/plugins libdir: ${prefix}/lib+ datadir: ${prefix}/share+ htmldir: ${datadir}/html lexdir: ${datadir}/po staticdir: ${datadir}/static fontdir: ${datadir}/fonts manualdir: ${prefix}/share/doc+ logfiledir: /var/log localstatedir: /var/run+ masonstatedir: ${localstatedir}/mason_data sessionstatedir: ${localstatedir}/session_data customdir: ${prefix}/share+ custometcdir: ${customdir}/local/etc customhtmldir: ${customdir}/local/html customlexdir: ${customdir}/local/po customstaticdir: ${customdir}/static customlibdir: ${customdir}/local/lib customplugindir: ${customdir}/local/plugins # RH path layout. prefix: /usr exec_prefix: ${prefix} bindir: ${exec_prefix}/bin sbindir: ${exec_prefix}/sbin sysconfdir: /etc/rt3 mandir: ${prefix}/man libdir: ${prefix}/lib/rt3 datadir: /var/rt3 htmldir: ${datadir}/html fontdir: ${datadir}/fonts lexdir: ${datadir}/po staticdir: ${datadir}/static manualdir: ${datadir}/doc plugindir: ${datadir}/plugins localstatedir: /var logfiledir: ${localstatedir}/log/rt3 masonstatedir: ${localstatedir}/rt3/mason_data sessionstatedir: ${localstatedir}/rt3/session_data customdir: ${prefix}/local/rt3 custometcdir: ${customdir}/etc customhtmldir: ${customdir}/html customlexdir: ${customdir}/po customstaticdir: ${customdir}/static customlibdir: ${customdir}/lib customplugindir: ${customdir}/plugins prefix: /opt/rt5 exec_prefix: ${prefix} bindir: bin sbindir: sbin sysconfdir: etc mandir: man plugindir: plugins libdir: lib datadir: share htmldir: ${datadir}/html fontdir: ${datadir}/fonts lexdir: ${datadir}/po staticdir: ${datadir}/static manualdir: docs localstatedir: var logfiledir: ${localstatedir}/log masonstatedir: ${localstatedir}/mason_data sessionstatedir: ${localstatedir}/session_data customdir: local custometcdir: ${customdir}/etc customhtmldir: ${customdir}/html customlexdir: ${customdir}/po customstaticdir: ${customdir}/static customlibdir: ${customdir}/lib customplugindir: ${customdir}/plugins prefix: /opt/rt5 exec_prefix: ${prefix} bindir: bin sbindir: sbin sysconfdir: etc/rt/ mandir: man libdir: lib/rt datadir: share/rt plugindir: ${datadir}/plugins htmldir: ${datadir}/html fontdir: ${datadir}/fonts lexdir: ${datadir}/po staticdir: ${datadir}/static manualdir: docs/rt localstatedir: var/rt/ logfiledir: ${localstatedir}/log masonstatedir: ${localstatedir}/mason_data sessionstatedir: ${localstatedir}/session_data customdir: local/rt/ custometcdir: ${customdir}/etc customhtmldir: ${customdir}/html customlexdir: ${customdir}/po customstaticdir: ${customdir}/static customlibdir: ${customdir}/lib customplugindir: ${customdir}/plugins rt-5.0.1/README.MariaDB000644 000765 000024 00000006605 14005011336 015126 0ustar00sunnavystaff000000 000000 CHARACTER SETS -------------- Starting with RT 5.0.0, the minimum supported MariaDB version is 10.2.5 because this is the first version to provide full support for 4 byte utf8 characters in tables and indexes. Read on for details on this change. RT 5.0.0 now defaults MariaDB tables to utf8mb4, which is available in versions before 10.2.5. However, before MariaDB version 10.2.5, utf8mb4 tables could not have indexes with type VARCHAR(255): the default size for index entries was 767 bytes, which is enough for 255 chars stored as at most 3 chars (the utf8 format), but not as 4 bytes (utf8mb4). 10.2.5 sets the default index size to 3072 for InnoDB tables, resolving that issue. https://mariadb.com/kb/en/changes-improvements-in-mariadb-102/ https://mariadb.com/kb/en/mariadb-1025-changelog/ (search for utf8) In MariaDB, RT uses the utf8mb4 character set to support all unicode characters, including the ones that are encoded with 4 bytes in utf8 (some Kanji characters and a good number of emojis). The DB tables and RT are both set to this character set. If your MariaDB database is used only for RT, you can consider setting the default character set to utf8mb4. This will ensure that backups and other database access outside of RT have the correct character set. This is done by adding the following lines to the MariaDB configuration: [mysqld] character-set-server = utf8mb4 [client] default-character-set = utf8mb4 You can check the values your server is using by running this command: mysqladmin variables | grep -i character_set Setting the default is particularly important for mysqldump, to avoid backups to be silently corrupted. If the MySQL DB is shared with other applications and the default character set cannot be set to utf8mb4, the command to backup the database must set it explicitly: ( mysqldump --default-character-set=utf8mb4 rt5 --tables sessions --no-data --single-transaction; \ mysqldump --default-character-set=utf8mb4 rt5 --ignore-table rt5.sessions --single-transaction ) \ | gzip > rt-`date +%Y%m%d`.sql.gz Restoring a backup is done the usual way, since the character set for all tables is set to utf8mb4, there is no further need to tell MariaDB about it: gunzip -c rt-20191125.sql.gz | mysql -uroot -p rt5 These character set updates now allow RT on MariaDB to accept and store 4-byte characters like emojis. However, searches can still be inconsistent. You may be able to get different or better results by experimenting with different collation settings. For more information: https://stackoverflow.com/a/41148052 https://mariadb.com/kb/en/character-sets/ TIME ZONE TABLES ---------------- Charts in RT can use time zone conversion for dates and this requires that time zones are loaded into the database. MariaDB on some platforms such as Centos (and possibly others) have time zone tables, but they are not populated and need to be loaded manually. On Unix-like systems, you can use the mysql_tzinfo_to_sql utility, which uses the zoneinfo data provided on the system. Documentation on loading the time zones using the mysql_tzinfo_to_sql tool can be found at: https://mariadb.com/kb/en/mysql_tzinfo_to_sql/ You can confirm that timezone tables are populated by running: select CONVERT_TZ( '2020-07-27 20:00:00', 'UTC', 'Europe/London' ); If the result is "2020-07-27 21:00:00" the timezones are populated. If the result is NULL then time zones are not populated. rt-5.0.1/.tag000644 000765 000024 00000000011 14005021221 013537 0ustar00sunnavystaff000000 000000 rt-5.0.1 rt-5.0.1/.perltidyrc000644 000765 000024 00000000652 14005011336 015166 0ustar00sunnavystaff000000 000000 --maximum-line-length=78 --indent-columns=4 --continuation-indentation=4 --standard-error-output --vertical-tightness=2 --closing-token-indentation=1 --paren-tightness=1 --brace-tightness=1 --square-bracket-tightness=1 --block-brace-tightness=1 --nospace-for-semicolon --no-outdent-long-quotes --cuddled-else --want-break-before="% + - * / x != == >= <= =~ !~ < > | & >= < = **= += *= &= <<= &&= -= /= |= >>= ||= .= %= ^= x=" rt-5.0.1/Makefile.in000644 000765 000024 00000047071 14005011336 015057 0ustar00sunnavystaff000000 000000 # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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 }}} # # DO NOT HAND-EDIT the file named 'Makefile'. This file is autogenerated. # Have a look at "configure" and "Makefile.in" instead # PERL = @PERL@ INSTALL = @INSTALL@ CC = @CC@ RT_LAYOUT = @rt_layout_name@ CONFIG_FILE_PATH = @CONFIG_FILE_PATH_R@ CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_Config.pm SITE_CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_SiteConfig.pm RT_VERSION_MAJOR = @RT_VERSION_MAJOR@ RT_VERSION_MINOR = @RT_VERSION_MINOR@ RT_VERSION_PATCH = @RT_VERSION_PATCH@ RT_VERSION = $(RT_VERSION_MAJOR).$(RT_VERSION_MINOR).$(RT_VERSION_PATCH) TAG = rt-$(RT_VERSION_MAJOR)-$(RT_VERSION_MINOR)-$(RT_VERSION_PATCH) # This is the group that all of the installed files will be chgrp'ed to. RTGROUP = @RTGROUP@ # User which should own rt binaries. BIN_OWNER = @BIN_OWNER@ # User that should own all of RT's libraries, generally root. LIBS_OWNER = @LIBS_OWNER@ # Group that should own all of RT's libraries, generally root. LIBS_GROUP = @LIBS_GROUP@ WEB_USER = @WEB_USER@ WEB_GROUP = @WEB_GROUP@ # DESTDIR allows you to specify that RT be installed somewhere other than # where it will eventually reside. DESTDIR _must_ have a trailing slash # if it's defined. DESTDIR = RT_PATH = @RT_PATH_R@ RT_ETC_PATH = @RT_ETC_PATH_R@ RT_BIN_PATH = @RT_BIN_PATH_R@ RT_SBIN_PATH = @RT_SBIN_PATH_R@ RT_LIB_PATH = @RT_LIB_PATH_R@ RT_MAN_PATH = @RT_MAN_PATH_R@ RT_VAR_PATH = @RT_VAR_PATH_R@ RT_DOC_PATH = @RT_DOC_PATH_R@ RT_FONT_PATH = @RT_FONT_PATH_R@ RT_LEXICON_PATH = @RT_LEXICON_PATH_R@ RT_STATIC_PATH = @RT_STATIC_PATH_R@ RT_LOCAL_PATH = @RT_LOCAL_PATH_R@ LOCAL_PLUGIN_PATH = @RT_LOCAL_PATH_R@/plugins LOCAL_ETC_PATH = @LOCAL_ETC_PATH_R@ LOCAL_LIB_PATH = @LOCAL_LIB_PATH_R@ LOCAL_LEXICON_PATH = @LOCAL_LEXICON_PATH_R@ LOCAL_STATIC_PATH = @LOCAL_STATIC_PATH_R@ MASON_HTML_PATH = @MASON_HTML_PATH_R@ MASON_LOCAL_HTML_PATH = @MASON_LOCAL_HTML_PATH_R@ MASON_DATA_PATH = @MASON_DATA_PATH_R@ MASON_SESSION_PATH = @MASON_SESSION_PATH_R@ RT_LOG_PATH = @RT_LOG_PATH_R@ # RT_READABLE_DIR_MODE is the mode of directories that are generally meant # to be accessable RT_READABLE_DIR_MODE = 0755 # RT's CLI RT_CLI_BIN = rt # RT's mail gateway RT_MAILGATE_BIN = rt-mailgate # RT's cron tool RT_CRON_BIN = rt-crontool BINARIES = $(RT_MAILGATE_BIN) \ $(RT_CLI_BIN) \ $(RT_CRON_BIN) SYSTEM_BINARIES = rt-attributes-viewer \ rt-munge-attachments \ rt-clean-sessions \ rt-dump-initialdata \ rt-dump-metadata \ rt-email-dashboards \ rt-email-digest \ rt-email-group-admin \ rt-externalize-attachments \ rt-fulltext-indexer \ rt-importer \ rt-ldapimport \ rt-passwd \ rt-preferences-viewer \ rt-search-attributes \ rt-serializer \ rt-server \ rt-server.fcgi \ rt-session-viewer \ rt-setup-database \ rt-setup-fulltext-index \ rt-shredder \ rt-test-dependencies \ rt-validator \ rt-validate-aliases \ standalone_httpd ETC_FILES = acl.Pg \ acl.Oracle \ acl.mysql \ schema.Pg \ schema.Oracle \ schema.mysql \ schema.SQLite \ cpanfile \ initialdata WEB_HANDLER = @WEB_HANDLER@ # # DB_TYPE defines what sort of database RT trys to talk to # "mysql", "Oracle", "Pg", and "SQLite" are known to work. DB_TYPE = @DB_TYPE@ # Set DBA to the name of a unix account with the proper permissions and # environment to run your commandline SQL sbin # Set DB_DBA to the name of a DB user with permission to create new databases # For mysql, you probably want 'root' # For Pg, you probably want 'postgres' # For Oracle, you want 'system' DB_DBA = @DB_DBA@ DB_HOST = @DB_HOST@ # If you're not running your database server on its default port, # specifiy the port the database server is running on below. # It's generally safe to leave this blank DB_PORT = @DB_PORT@ # # Set this to the canonical name of the interface RT will be talking to the # database on. If you said that the RT_DB_HOST above was "localhost," this # should be too. This value will be used to grant rt access to the database. # If you want to access the RT database from multiple hosts, you'll need # to grant those database rights by hand. # DB_RT_HOST = @DB_RT_HOST@ # set this to the name you want to give to the RT database in # your database server. For Oracle, this should be the name of your sid DB_DATABASE = @DB_DATABASE@ DB_RT_USER = @DB_RT_USER@ DB_RT_PASS = @DB_RT_PASS@ TEST_FILES = t/*.t t/*/*.t t/*/*/*.t TEST_VERBOSE = 0 RT_TEST_PARALLEL_NUM ?= 5 #################################################################### all: default default: @echo "Please read RT's README before beginning your installation." instruct: @echo "Congratulations. RT is now installed." @echo "" @echo "" @echo "You must now configure RT by editing $(SITE_CONFIG_FILE)." @echo "" @echo "(You will definitely need to set RT's database password in " @echo "$(SITE_CONFIG_FILE) before continuing. Not doing so could be " @echo "very dangerous. Note that you do not have to manually add a " @echo "database user or set up a database for RT. These actions will be " @echo "taken care of in the next step.)" @echo "" @echo "After that, you need to initialize RT's database by running" @echo " 'make initialize-database'" upgrade-instruct: @echo "Congratulations. RT has been upgraded. You should now check over" @echo "$(CONFIG_FILE) for any necessary site customization. Additionally," @echo "you should update RT's system database objects by running " @echo " make upgrade-database" upgrade: testdeps config-install dirs files-install fixperms upgrade-instruct testdeps: $(PERL) ./sbin/rt-test-dependencies depends: fixdeps fixdeps: $(PERL) ./sbin/rt-test-dependencies --install #}}} fixperms: # Make the libraries readable chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)$(RT_PATH) chown -R $(LIBS_OWNER) $(DESTDIR)$(RT_LIB_PATH) chgrp -R $(LIBS_GROUP) $(DESTDIR)$(RT_LIB_PATH) chmod -R u+rwX,go-w,go+rX $(DESTDIR)$(RT_LIB_PATH) chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)$(RT_BIN_PATH) chmod 0755 $(DESTDIR)$(RT_ETC_PATH) cd $(DESTDIR)$(RT_ETC_PATH) && chmod 0400 $(ETC_FILES) #TODO: the config file should probably be able to have its # owner set separately from the binaries. chown -R $(BIN_OWNER) $(DESTDIR)$(RT_ETC_PATH) chgrp -R $(RTGROUP) $(DESTDIR)$(RT_ETC_PATH) chmod 0440 $(DESTDIR)$(CONFIG_FILE) chmod 0640 $(DESTDIR)$(SITE_CONFIG_FILE) # Make the system binaries cd $(DESTDIR)$(RT_BIN_PATH) && ( chmod 0755 $(BINARIES) ; chown $(BIN_OWNER) $(BINARIES); chgrp $(RTGROUP) $(BINARIES)) # Make the system binaries executable also cd $(DESTDIR)$(RT_SBIN_PATH) && ( chmod 0755 $(SYSTEM_BINARIES) ; chown $(BIN_OWNER) $(SYSTEM_BINARIES); chgrp $(RTGROUP) $(SYSTEM_BINARIES)) # Make upgrade scripts executable if they are in the source. # ( cd etc/upgrade && find . -type f -not -name '*.in' -perm @FINDPERM@0111 -print ) | while read file ; do \ chmod a+x "$(DESTDIR)$(RT_ETC_PATH)/upgrade/$$file" ; \ done # Make the web ui readable by all. chmod -R u+rwX,go-w,go+rX $(DESTDIR)$(MASON_HTML_PATH) \ $(DESTDIR)$(MASON_LOCAL_HTML_PATH) \ $(DESTDIR)$(RT_LEXICON_PATH) \ $(DESTDIR)$(LOCAL_LEXICON_PATH) \ $(DESTDIR)$(RT_STATIC_PATH) \ $(DESTDIR)$(LOCAL_STATIC_PATH) chown -R $(LIBS_OWNER) $(DESTDIR)$(MASON_HTML_PATH) \ $(DESTDIR)$(MASON_LOCAL_HTML_PATH) \ $(DESTDIR)$(RT_LEXICON_PATH) \ $(DESTDIR)$(LOCAL_LEXICON_PATH) \ $(DESTDIR)$(RT_STATIC_PATH) \ $(DESTDIR)$(LOCAL_STATIC_PATH) chgrp -R $(LIBS_GROUP) $(DESTDIR)$(MASON_HTML_PATH) \ $(DESTDIR)$(MASON_LOCAL_HTML_PATH) \ $(DESTDIR)$(RT_LEXICON_PATH) \ $(DESTDIR)$(LOCAL_LEXICON_PATH) \ $(DESTDIR)$(RT_STATIC_PATH) \ $(DESTDIR)$(LOCAL_STATIC_PATH) # Make the web ui's data dir writable chmod 0770 $(DESTDIR)$(MASON_DATA_PATH) \ $(DESTDIR)$(MASON_SESSION_PATH) chown -R $(WEB_USER) $(DESTDIR)$(MASON_DATA_PATH) \ $(DESTDIR)$(MASON_SESSION_PATH) chgrp -R $(WEB_GROUP) $(DESTDIR)$(MASON_DATA_PATH) \ $(DESTDIR)$(MASON_SESSION_PATH) dirs: $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_LOG_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_FONT_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_LEXICON_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_STATIC_PATH) $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH) $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH)/cache $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH)/etc $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH)/obj $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_SESSION_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(MASON_HTML_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(MASON_LOCAL_HTML_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_ETC_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_LIB_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_PLUGIN_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_LEXICON_PATH) $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_STATIC_PATH) clean-mason-cache: rm -rf $(DESTDIR)$(MASON_DATA_PATH)/cache/* rm -rf $(DESTDIR)$(MASON_DATA_PATH)/etc/* rm -rf $(DESTDIR)$(MASON_DATA_PATH)/obj/* install: testdeps config-install dirs files-install fixperms instruct files-install: libs-install etc-install config-install bin-install sbin-install html-install doc-install font-install po-install static-install config-install: @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0755 -o $(BIN_OWNER) -g $(RTGROUP) -d $(DESTDIR)$(CONFIG_FILE_PATH) @COMMENT_INPLACE_LAYOUT@ -$(INSTALL) -m 0440 -o $(BIN_OWNER) -g $(RTGROUP) etc/RT_Config.pm $(DESTDIR)$(CONFIG_FILE) @COMMENT_INPLACE_LAYOUT@ [ -f $(DESTDIR)$(SITE_CONFIG_FILE) ] || $(INSTALL) -m 0640 -o $(BIN_OWNER) -g $(RTGROUP) etc/RT_SiteConfig.pm $(DESTDIR)$(SITE_CONFIG_FILE) @COMMENT_INPLACE_LAYOUT@ @echo "Installed configuration. About to install RT in $(RT_PATH)" test: $(PERL) "-MExtUtils::Command::MM" -e "test_harness($(TEST_VERBOSE), 'lib')" $(TEST_FILES) parallel-test: test-parallel test-parallel: RT_TEST_PARALLEL=1 $(PERL) "-MApp::Prove" -e 'my $$p = App::Prove->new(); $$p->process_args("-wlrj$(RT_TEST_PARALLEL_NUM)","--state=slow,save", "t"); exit( $$p->run() ? 0 : 1 )' regression-reset-db: force-dropdb $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action init --dba-password '' initdb :: initialize-database initialize-database: $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action init --prompt-for-dba-password upgrade-database: $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action upgrade --prompt-for-dba-password dropdb: $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action drop --prompt-for-dba-password force-dropdb: $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action drop --dba-password '' --force critic: perlcritic --quiet sbin bin lib libs-install: @COMMENT_INPLACE_LAYOUT@ [ -d $(DESTDIR)$(RT_LIB_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_LIB_PATH) @COMMENT_INPLACE_LAYOUT@ -( cd lib && find . -type d -print ) | while read dir ; do \ @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0755 -d "$(DESTDIR)$(RT_LIB_PATH)/$$dir" ; \ @COMMENT_INPLACE_LAYOUT@ done @COMMENT_INPLACE_LAYOUT@ -( cd lib && find . -type f -print ) | while read file ; do \ @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0644 "lib/$$file" "$(DESTDIR)$(RT_LIB_PATH)/$$file" ; \ @COMMENT_INPLACE_LAYOUT@ done html-install: @COMMENT_INPLACE_LAYOUT@ [ -d $(DESTDIR)$(MASON_HTML_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(MASON_HTML_PATH) @COMMENT_INPLACE_LAYOUT@ -( cd share/html && find . -type d -print ) | while read dir ; do \ @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0755 -d "$(DESTDIR)$(MASON_HTML_PATH)/$$dir" ; \ @COMMENT_INPLACE_LAYOUT@ done @COMMENT_INPLACE_LAYOUT@ -( cd share/html && find . -type f -print ) | while read file ; do \ @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0644 "share/html/$$file" "$(DESTDIR)$(MASON_HTML_PATH)/$$file" ; \ @COMMENT_INPLACE_LAYOUT@ done @COMMENT_INPLACE_LAYOUT@ $(MAKE) clean-mason-cache font-install: @COMMENT_INPLACE_LAYOUT@ [ -d $(DESTDIR)$(RT_FONT_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_FONT_PATH) @COMMENT_INPLACE_LAYOUT@ -( cd share/fonts && find . -type f -print ) | while read file ; do \ @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0644 "share/fonts/$$file" "$(DESTDIR)$(RT_FONT_PATH)/$$file" ; \ @COMMENT_INPLACE_LAYOUT@ done po-install: @COMMENT_INPLACE_LAYOUT@ [ -d $(DESTDIR)$(RT_LEXICON_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_LEXICON_PATH) @COMMENT_INPLACE_LAYOUT@ -( cd share/po && find . -type f -print ) | while read file ; do \ @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0644 "share/po/$$file" "$(DESTDIR)$(RT_LEXICON_PATH)/$$file" ; \ @COMMENT_INPLACE_LAYOUT@ done static-install: @COMMENT_INPLACE_LAYOUT@ [ -d $(DESTDIR)$(RT_STATIC_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_STATIC_PATH) @COMMENT_INPLACE_LAYOUT@ -( cd share/static && find . -type d -print ) | while read dir ; do \ @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0755 -d "$(DESTDIR)$(RT_STATIC_PATH)/$$dir" ; \ @COMMENT_INPLACE_LAYOUT@ done @COMMENT_INPLACE_LAYOUT@ -( cd share/static && find . -type f -print ) | while read file ; do \ @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0644 "share/static/$$file" "$(DESTDIR)$(RT_STATIC_PATH)/$$file" ; \ @COMMENT_INPLACE_LAYOUT@ done doc-install: @COMMENT_INPLACE_LAYOUT@ # RT 3.0.0 - RT 3.0.2 would accidentally create a file instead of a dir @COMMENT_INPLACE_LAYOUT@ -[ -f $(DESTDIR)$(RT_DOC_PATH) ] && rm $(DESTDIR)$(RT_DOC_PATH) @COMMENT_INPLACE_LAYOUT@ [ -d $(DESTDIR)$(RT_DOC_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_DOC_PATH) @COMMENT_INPLACE_LAYOUT@ -( cd docs && find . -type d -print ) | while read dir ; do \ @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0755 -d "$(DESTDIR)$(RT_DOC_PATH)/$$dir" ; \ @COMMENT_INPLACE_LAYOUT@ done @COMMENT_INPLACE_LAYOUT@ -( cd docs && find . -type f -print ) | while read file ; do \ @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0644 "docs/$$file" "$(DESTDIR)$(RT_DOC_PATH)/$$file" ; \ @COMMENT_INPLACE_LAYOUT@ done @COMMENT_INPLACE_LAYOUT@ -$(INSTALL) -m 0644 ./README $(DESTDIR)$(RT_DOC_PATH)/ etc-install: @COMMENT_INPLACE_LAYOUT@ [ -d $(DESTDIR)$(RT_ETC_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_ETC_PATH) @COMMENT_INPLACE_LAYOUT@ [ -d "$(DESTDIR)$(RT_ETC_PATH)/RT_SiteConfig.d" ] || $(INSTALL) -m 0755 -d "$(DESTDIR)$(RT_ETC_PATH)/RT_SiteConfig.d" @COMMENT_INPLACE_LAYOUT@ for file in $(ETC_FILES) ; do \ @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0644 "etc/$$file" "$(DESTDIR)$(RT_ETC_PATH)/" ; \ @COMMENT_INPLACE_LAYOUT@ done @COMMENT_INPLACE_LAYOUT@ [ -d $(DESTDIR)$(RT_ETC_PATH)/upgrade ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_ETC_PATH)/upgrade @COMMENT_INPLACE_LAYOUT@ -( cd etc/upgrade && find . -type d -print ) | while read dir ; do \ @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0755 -d "$(DESTDIR)$(RT_ETC_PATH)/upgrade/$$dir" ; \ @COMMENT_INPLACE_LAYOUT@ done @COMMENT_INPLACE_LAYOUT@ -( cd etc/upgrade && find . -type f -not -name '*.in' -print ) | while read file ; do \ @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0644 "etc/upgrade/$$file" "$(DESTDIR)$(RT_ETC_PATH)/upgrade/$$file" ; \ @COMMENT_INPLACE_LAYOUT@ done sbin-install: @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_SBIN_PATH) @COMMENT_INPLACE_LAYOUT@ for file in $(SYSTEM_BINARIES) ; do \ @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -o $(BIN_OWNER) -g $(RTGROUP) -m 0755 "sbin/$$file" "$(DESTDIR)$(RT_SBIN_PATH)/" ; \ @COMMENT_INPLACE_LAYOUT@ done bin-install: @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_BIN_PATH) @COMMENT_INPLACE_LAYOUT@ for file in $(BINARIES) ; do \ @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -o $(BIN_OWNER) -g $(RTGROUP) -m 0755 "bin/$$file" "$(DESTDIR)$(RT_BIN_PATH)/" ; \ @COMMENT_INPLACE_LAYOUT@ done regenerate-catalogs: $(PERL) devel/tools/extract-message-catalog license-tag: $(PERL) devel/tools/license_tag start-httpd: $(PERL) sbin/standalone_httpd & start-server: $(PERL) sbin/rt-server & SNAPSHOT=$(shell git describe --tags) THIRD_PARTY=devel/third-party/ snapshot: build-snapshot build-third-party clearsign-snapshot clearsign-third-party snapshot-shasums build-snapshot: git archive --prefix "$(SNAPSHOT)/" HEAD | tar -xf - ( cd $(SNAPSHOT) && \ echo "$(SNAPSHOT)" > .tag && \ autoconf && \ INSTALL=./install-sh PERL=/usr/bin/perl ./configure \ --with-db-type=SQLite \ --enable-layout=relative \ --with-web-handler=standalone && \ rm -rf autom4te.cache \ config.status config.log config.pld \ ) tar -czf "$(SNAPSHOT).tar.gz" "$(SNAPSHOT)/" rm -fr "$(SNAPSHOT)/" clearsign-snapshot: gpg --armor --detach-sign "$(SNAPSHOT).tar.gz" build-third-party: git archive --prefix "$(SNAPSHOT)/$(THIRD_PARTY)" HEAD:$(THIRD_PARTY) \ | gzip > "$(SNAPSHOT)-third-party-source.tar.gz" rm -rf "$(SNAPSHOT)/$(THIRD_PARTY)" clearsign-third-party: gpg --armor --detach-sign "$(SNAPSHOT)-third-party-source.tar.gz" snapshot-shasums: sha1sum $(SNAPSHOT)*.tar.gz* vessel-import: build-snapshot [ -d $(VESSEL) ] || (echo "VESSEL isn't a path to your shipwright vessel" && exit -1) cp $(VESSEL)/scripts/RT/build.pl /tmp/build.pl ./sbin/rt-test-dependencies --with-standalone --with-fastcgi --with-sqlite --list > /tmp/rt.yml shipwright import file:$(SNAPSHOT).tar.gz \ --require-yml /tmp/rt.yml \ --build-script /tmp/build.pl \ --name RT \ --repository fs:$(VESSEL) \ --log-level=info \ --skip cpan-capitalization,cpan-mod_perl,cpan-Encode,cpan-PPI,cpan-Test-Exception-LessClever,cpan-Test-Manifest,cpan-Test-Object,cpan-Test-Pod,cpan-Test-Requires,cpan-Test-SubCalls,cpan-Test-cpan-Tester,cpan-Test-Warn --skip-all-recommends mv $(VESSEL)/scripts/RT/build $(VESSEL)/scripts/RT/build.pl rt-5.0.1/.travis.yml000644 000765 000024 00000001634 14005011336 015116 0ustar00sunnavystaff000000 000000 language: bash services: docker notifications: slack: secure: ebb/6lbr1ob7wMh04C5PzM5/NNz6IstEUaUROA7BATuKKgPetl6qwmQNwvlwE5zYvJQBWQwKJ70JaCzJkXK6JVMVRRAWsXINJTzMfSqsoXEcJ59c5isf0bsnspVO7jxHTfXF/NZngR4EuPwH5v5lWp9m++j90t9nBKFFVi34WUE= env: - RT_TEST_PARALLEL=1 RT_DBA_USER=root RT_DBA_PASSWORD=password DB_VERSION_TAG=10.3 # $TRAVIS_BUILD_DIR will have a clone of the current branch before_install: - docker run --name mariadb -e MYSQL_ROOT_PASSWORD=password -d mariadb:$DB_VERSION_TAG - docker build -t rt-base . - docker run -d -v $TRAVIS_BUILD_DIR:/rt --name rt --link mariadb:db rt-base - docker ps -a - docker exec -it rt bash -c "cd /rt && ./configure.ac --with-db-type=mysql --with-my-user-group --enable-layout=inplace --enable-developer --enable-externalauth --enable-gpg --enable-smime && mkdir -p /rt/var && make testdeps" script: - docker exec -it rt bash -c "cd /rt && prove -lj9 t/*" rt-5.0.1/aclocal.m4000644 000765 000024 00000012141 14005011336 014640 0ustar00sunnavystaff000000 000000 dnl aclocal.m4 generated automatically by aclocal 1.4-p6 dnl Copyright (C) 1994, 1995-8, 1999, 2001 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl This program is distributed in the hope that it will be useful, dnl but WITHOUT ANY WARRANTY, to the extent permitted by law; without dnl even the implied warranty of MERCHANTABILITY or FITNESS FOR A dnl PARTICULAR PURPOSE. dnl dnl @synopsis RT_ENABLE_LAYOUT() dnl dnl Enable a specific directory layout for the installation to use. dnl This configures a command-line parameter that can be specified dnl at ./configure invocation. dnl dnl The use of this feature in this way is a little hackish, but dnl better than a heap of options for every directory. dnl dnl This code is heavily borrowed *cough* from the Apache 2 code. dnl AC_DEFUN([RT_ENABLE_LAYOUT],[ AC_ARG_ENABLE(layout, AC_HELP_STRING([--enable-layout=LAYOUT], [Use a specific directory layout (Default: relative)]), LAYOUT=$enableval) if test "x$LAYOUT" = "x"; then LAYOUT="relative" fi RT_LAYOUT($srcdir/config.layout, $LAYOUT) AC_MSG_CHECKING(for chosen layout) if test "x$rt_layout_name" = "xno"; then if test "x$LAYOUT" = "xno"; then AC_MSG_RESULT(none) else AC_MSG_RESULT($LAYOUT) fi AC_MSG_ERROR([a valid layout must be specified (or the default used)]) else AC_SUBST(rt_layout_name) AC_MSG_RESULT($rt_layout_name) fi if test "x$rt_layout_name" != "xinplace" ; then AC_SUBST([COMMENT_INPLACE_LAYOUT], [""]) else AC_SUBST([COMMENT_INPLACE_LAYOUT], [# ]) fi ]) dnl dnl @synopsis RT_LAYOUT(configlayout, layoutname) dnl dnl This macro reads an Apache-style layout file (specified as the dnl configlayout parameter), and searches for a specific layout dnl (named using the layoutname parameter). dnl dnl The entries for a given layout are then inserted into the dnl environment such that they become available as substitution dnl variables. In addition, the rt_layout_name variable is set dnl (but not exported) if the layout is valid. dnl dnl This code is heavily borrowed *cough* from the Apache 2 codebase. dnl AC_DEFUN([RT_LAYOUT],[ if test ! -f $srcdir/config.layout; then AC_MSG_WARN([Layout file $srcdir/config.layout not found]) rt_layout_name=no else pldconf=./config.pld $PERL -0777 -p -e "\$layout = '$2';" -e ' s/.*//gims; s/\<\/Layout\>.*//s; s/^#.*$//m; s/^\s+//gim; s/\s+$/\n/gim; s/\+$/\/rt3/gim; # m4 will not let us just use $1, we need @S|@1 s/^\s*((?:bin|sbin|libexec|data|sysconf|sharedstate|localstate|lib|include|oldinclude|info|man|html)dir)\s*:\s*(.*)$/@S|@1=@S|@2/gim; s/^\s*(.*?)\s*:\s*(.*)$/\(test "x\@S|@@S|@1" = "xNONE" || test "x\@S|@@S|@1" = "x") && @S|@1=@S|@2/gim; ' < $1 > $pldconf if test -s $pldconf; then rt_layout_name=$2 . $pldconf changequote({,}) for var in prefix exec_prefix bindir sbindir \ sysconfdir mandir libdir datadir htmldir fontdir\ lexdir staticdir localstatedir logfiledir masonstatedir \ sessionstatedir customdir custometcdir customhtmldir \ customlexdir customstaticdir customplugindir customlibdir manualdir; do eval "val=\"\$$var\"" val=`echo $val | sed -e 's:\(.\)/*$:\1:'` val=`echo $val | sed -e 's:[\$]\([a-z_]*\):${\1}:g'` eval "$var='$val'" done changequote([,]) else rt_layout_name=no fi #rm $pldconf fi RT_SUBST_EXPANDED_ARG(prefix) RT_SUBST_EXPANDED_ARG(exec_prefix) RT_SUBST_EXPANDED_ARG(bindir) RT_SUBST_EXPANDED_ARG(sbindir) RT_SUBST_EXPANDED_ARG(sysconfdir) RT_SUBST_EXPANDED_ARG(mandir) RT_SUBST_EXPANDED_ARG(libdir) RT_SUBST_EXPANDED_ARG(lexdir) RT_SUBST_EXPANDED_ARG(staticdir) RT_SUBST_EXPANDED_ARG(datadir) RT_SUBST_EXPANDED_ARG(htmldir) RT_SUBST_EXPANDED_ARG(fontdir) RT_SUBST_EXPANDED_ARG(manualdir) RT_SUBST_EXPANDED_ARG(plugindir) RT_SUBST_EXPANDED_ARG(localstatedir) RT_SUBST_EXPANDED_ARG(logfiledir) RT_SUBST_EXPANDED_ARG(masonstatedir) RT_SUBST_EXPANDED_ARG(sessionstatedir) RT_SUBST_EXPANDED_ARG(customdir) RT_SUBST_EXPANDED_ARG(custometcdir) RT_SUBST_EXPANDED_ARG(customplugindir) RT_SUBST_EXPANDED_ARG(customhtmldir) RT_SUBST_EXPANDED_ARG(customlexdir) RT_SUBST_EXPANDED_ARG(customstaticdir) RT_SUBST_EXPANDED_ARG(customlibdir) ])dnl dnl dnl @synopsis RT_SUBST_EXPANDED_ARG(var) dnl dnl Export (via AC_SUBST) a given variable, along with an expanded dnl version of the variable (same name, but with exp_ prefix). dnl dnl This code is heavily borrowed *cough* from the Apache 2 source. dnl AC_DEFUN([RT_SUBST_EXPANDED_ARG],[ RT_EXPAND_VAR(exp_$1, [$]$1) AC_SUBST($1) AC_SUBST(exp_$1) ]) dnl dnl @synopsis RT_EXPAND_VAR(baz, $fraz) dnl dnl Iteratively expands the second parameter, until successive iterations dnl yield no change. The result is then assigned to the first parameter. dnl dnl This code is heavily borrowed from the Apache 2 codebase. dnl AC_DEFUN([RT_EXPAND_VAR],[ ap_last='' ap_cur='$2' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" ap_cur=`eval "echo ${ap_cur}"` done $1="${ap_cur}" ]) rt-5.0.1/README.Oracle000644 000765 000024 00000003043 14005011336 015065 0ustar00sunnavystaff000000 000000 On RT 3.8.2 and later, RT deployment for Oracle databases is very straightforward. You don't need to configure Oracle beforehand. During installation a user is created and all RT's objects are created in his schema. The user is created with the following parameters: CREATE USER IDENTIFIED BY DEFAULT TABLESPACE USERS TEMPORARY TABLESPACE TEMP QUOTA UNLIMITED ON USERS And the user is also granted 'CONNECT' and 'RESOURCE'. It's up to you to do decide how to manage users, change quotas, table spaces, etc. RT has an option $DatabaseName which is used to define the SID of the Oracle database. You don't have to set up the TWO_TASK environment variable or any helper files for establishing connections. Example: ./configure \ --with-db-type=Oracle \ --with-db-database=XE \ --with-db-host=192.168.0.1 \ --with-db-dba=system \ --with-db-rt-user=rtdb1 \ --with-db-rt-pass=rtdb1secret \ ... other configure options ... That should be enough to get you started on Oracle, but to complete installation you must follow the general instructions in the README file. As with all databases it is important to analyze the schema and get current statistics after every significant dataset change. Oracle's cost-based optimizer can provide particularly bad performance when the schema statistics are inaccurate. To analyze the schema of the user called RT, execute the following from within sqlplus: execute dbms_utility.analyze_schema( 'RT', 'estimate'); rt-5.0.1/.perlcriticrc000644 000765 000024 00000001505 14005011336 015470 0ustar00sunnavystaff000000 000000 # This perlcritic policy file isn't to be taken as gospel. It's just a start # As of now, it's mostly about disabling policies we're not able to follow or # strongly disagree with exclude = Subroutines::ProhibitExplicitReturnUndef Modules::RequireFilenameMatchesPackage TestingAndDebugging::ProhibitNoStrict color = 1 verbose = 7 # we don't unpack @_ right away as we mostly use named vars with defaults: # sub foo { # my $self = shift; # my %args = ( default => 'value', ..., @_ ); # ... [-Subroutines::RequireArgUnpacking] # Readonly superiority is not convincing, especially considering # that 'use constant' participates in constants folding during # compilation [-ValuesAndExpressions::ProhibitConstantPragma] # brutal [BuiltinFunctions::RequireBlockGrep] severity = 1 [BuiltinFunctions::RequireBlockMap] severity = 1 rt-5.0.1/share/000755 000765 000024 00000000000 14005011336 014103 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/000755 000765 000024 00000000000 14005011336 015047 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/po/000755 000765 000024 00000000000 14005011336 014521 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/static/000755 000765 000024 00000000000 14005011336 015372 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/fonts/000755 000765 000024 00000000000 14005011336 015234 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/fonts/DroidSans.ttf000644 000765 000024 00000443124 14005011336 017651 0ustar00sunnavystaff000000 000000 LTSH"mý tcOS/2Ÿƒ•[˜`cmapÕeA;L8cvt 9’=ÀIlüfpgmsÓ#°?„gaspFH glyf÷àÜP( @hdmxV‚ý Ø-theadëÑWû6hheaxT$hmtxe…ðûø zkernÙ‰æ]hËîloca>É‚>KhÀmaxpx nameùȤ)X jpostJœOÖ4Ä„prep;ùî©FŒÝÁ=ØÍ_<õÁš3ÃuÚRþ ýÕôÙmþ Zþ þ¢ô^_‘z/ZÝkš3š3Ðfò à¯@ [(1ASC@ ÿýþ„mã ŸJ¶ ÍÁ'“7…+3h{šfžmÏ…hRh=hRhf?“R%“ühbh²h`hRhhƒhqhZhjhj%“%?hfhfhfh%îmÝøÇÓ}yÇ9ÇîÇ…}œÇ¶R+ÿH¢ÇîÇöÇÕÇð}œÇî}¸Ç'h'–¸‹`7PRm¤üm3B)Jÿüž‰?^°®´q°qHq¢%%¶® ÿ¼ø®®+®¶®žq°®°q1®œZ¶!¶¤Õø#é ‡RÕ=héÕ3hf'“h¼hDh{hhéãyž3¨d¦DåRhf“R¨dÿúm{hf¦1¦ž‰Á®=q%“¤#¦?ÍBåTå?å,åhDÝÝÝÝÝÝÑÿþÓ}9Ç9Ç9Ç9Ƕ>¶R¶¶@y/ÕÇð}ð}ð}ð}ð}hð}–¸–¸–¸–¸7œÇÑ®?^?^?^?^?^?^ª^´qHqHqHqHqÿÞ®ÿ½ÿîžo¶®žqžqžqžqžqhfžs¶¤¶¤¶¤¶¤é °®é Ý?^Ý?^Ý?^Ó}´qÓ}´qÓ}´qÓ}´qyǰqy/°q9ÇHq9ÇHq9ÇHq9ÇHq9ÇHq…}%%…}%%…}%%…}%%œÇ¶®œ¶¶ÿõÿ£¶=ÿë¶0ÿÞ¶RD¶R®áR% +ÿHÿ¼¢Çø®ø®îÇ«îÇfîÇ®îÇf®îÿöÕǶ®ÕǶ®ÕǶ®FÿÿÕǶ®ð}žqð}žqð}žq}Hq¸Ç1®¸Ç1`¸Ç1r'hœZ'hœZ'hœZ'hœZ'¶!'¶!'¶!–¸¶¤–¸¶¤–¸¶¤–¸¶¤–¸¶¤–¸¶¤ø7é 7PR‡RPR‡RPR‡RL®hËÝ?^Ñÿþª^ð}žs'hœZžžuž! žm{žžßžøžÝÿé%“Éÿçÿç²ÿçBÿçTÿçJÿçžÿäÝøÇîÇ‹%9ÇPRœÇð}¶R¢Ç‹öÇÕÇ?Rð}‡ÇœÇBN'7#h`#hðN¶@7°q Z¶®ž¤¸¤°qÑ®é žo Z¤q¶®‡qž¤ø®ÿòÁ®#¤qžq ž¤¤q´q˜¸¤–q1ÿìì¤qž¸¤žq¸¤q9Ç îÇÓ}'h¶R¶@+ÿHHuÇ ¢Ç®‡ÇÝœÇøÇîÇ39Ç…\HÕÉÕÉ¢ÇZöÇœÇð}‡ÇœÇÓ}'®#h`šÇH¦úÇúÇ=–ÇœÇÁ;Ǹ?^u‰®3®`)HqîDé®é®î®^¾®é®žqÁ®°®´q‡)é “q#Ñ®¬šô®®m)þ®‰®²7y®?#Hq¶3®´qœZ ÿîÿ¼ƒå®¶î®é Á®îÇ3®øøø7é RRRJÿüff?fçç?ã{ø{–o“ ZfÏ…7…NRNRö“ þ øjh`hD–h?hwçÇþ%ðNôfS3Oq¦b‹)îÇ Jhfd%¨whfhfhfhfªm´´žÏÿ¼‡o}¦%¦ ¦;¦%¦/¦1¦!«Uh%šÍTTÿ¼fÍ R öÇ+®Ý?^BþÓªs3“V_( +    &"  "&   *. . * .  ( '    ' &&&&&& """"" "& . .... &&&     ..          $ ...."""*(****   ...... &&0 &"*$&-  &""" &"& . . -(0&""0$  .* .    &+   *" &+  . &d                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       ,~@>~’ðÿ¼ÇÉÝ󊌡ÎÒÖ O\_‘?…óM   " & 0 3 : < D  ¤ § ¬!!!!"!&!.!^"""""""+"H"`"e%Êûþÿÿýÿÿ  ’ðú¼ÆÉØó„ŒŽ£ÑÖQ^>€òM   & 0 2 9 < D  £ § ¬!!!!"!&!.!["""""""+"H"`"d%ÊûþÿÿüÿÿÿãÿÂÿ°aÿIÿ1ÿ–þ…þ„þvÿhýÐýÏýÎýÍþ‚þý›ýšý™ý˜ýhäXäãzãã âBáïáîáíáêáááàáÛáÚáÓá™ávátápáá á àþàûàôàÈà%à"àààààßçßÐßÍÜiOS45]^   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a†‡‰‹“˜ž£¢¤¦¥§©«ª¬­¯®°±³µ´¶¸·¼»½¾ rdeix¡pk vj0ˆš-s12gw'*).l|!¨ºcn,B/(m}‚…—  ¹3Á:f45y „ŒƒŠ‘Ž•–”œ›óKRqNOPzSQL@EYXUTSRQPONMLKJIHGFEDCBA@?>=<;:9876510/.-,('&%$#"! ,E#F` °&`°&#HH-,E#F#a °&a°&#HH-,E#F`° a °F`°&#HH-,E#F#a° ` °&a° a°&#HH-,E#F`°@a °f`°&#HH-,E#F#a°@` °&a°@a°&#HH-, <<-, E# °ÍD# ¸ZQX# °D#Y °íQX# °MD#Y °&QX# ° D#Y!!-, EhD °` E°FvhŠE`D-,± C#Ce -,± C#C -,°(#p±(>°(#p±(E:± -, E°%Ead°PQXED!!Y-,I°#D-, E°C`D-,°C°Ce -, i°@a°‹ ±,ÀŠŒ¸b`+ d#da\X°aY-,ŠEŠŠ‡°+°)#D°)zä-,Ee°,#DE°+#D-,KRXED!!Y-,KQXED!!Y-,°%# Šõ°`#íì-,°%# Šõ°a#íì-,°%õíì-,F#F`ŠŠF# FŠ`Ša¸ÿ€b# #б ŠpE` °PX°a¸ÿº‹°FŒY°`h:-, E°%FRK°Q[X°%F ha°%°%?#!8!Y-, E°%FPX°%F ha°%°%?#!8!Y-,°C°C -,!! d#d‹¸@b-,!°€QX d#d‹¸ b²@/+Y°`-,!°ÀQX d#d‹¸Ub²€/+Y°`-, d#d‹¸@b`#!-,KSXа%Id#Ei°@‹a°€b° aj°#D#°ö!#Š 9/Y-,KSX °%Idi °&°%Id#a°€b° aj°#D°&°öа#D°ö°#D°íа& 9# 9//Y-,E#E`#E`#E`#vh°€b -,°H+-, E°TX°@D E°@aD!!Y-,E±0/E#Ea`°`iD-,KQX°/#p°#B!!Y-,KQX °%EiSXD!!Y!!Y-,E°C°`c°`iD-,°/ED-,E# EŠ`D-,E#E`D-,K#QX¹3ÿà±4 ³34YDD-,°CX°&EŠXdf°`d° `f X!°@Y°aY#XeY°)#D#°)à!!!!!Y-,°CTXKS#KQZX8!!Y!!!!Y-,°CX°%Ed° `f X!°@Y°a#XeY°)#D°%°% XY°%°% F°%#B<°%°%°%°% F°%°`#B< XY°%°%°)à°) EeD°%°%°)à°%°% XY°%°%CH°%°%°%°%°`CH!Y!!!!!!!-,°% F°%#B°%°%EH!!!!-,°% °%°%CH!!!-,E# E °P X#e#Y#h °@PX!°@Y#XeYŠ`D-,KS#KQZX EŠ`D!!Y-,KTX EŠ`D!!Y-,KS#KQZX8!!Y-,°!KTX8!!Y-,°CTX°F+!!!!Y-,°CTX°G+!!!Y-,°CTX°H+!!!!Y-,°CTX°I+!!!Y-, Š#KSŠKQZX#8!!Y-,°%I°SX °@8!Y-,F#F`#Fa#  FŠa¸ÿ€bб@@ŠpE`h:-, Š#IdŠ#SX<!Y-,KRX}zY-,°KKTB-,±B±#ˆQ±@ˆSZX¹ ˆTX²C`BY±$ˆQX¹ @ˆTX²C`B±$ˆTX² C`BKKRX²C`BY¹@€ˆTX²C`BY¹@€c¸ˆTX²C`BY¹@c¸ˆTX²C`BY¹@c¸ˆTX²@C`BYYYYY-,Eh#KQX# E d°@PX|YhŠ`YD-,°°%°%°#>°#>± ° #eB° #B°#?°#?± °#eB°#B°-,zŠE#õ-@ øÿ÷Ÿ÷ó`ò¸ÿè@+ë Fß3ÝUÞÿÜU0ÝÝUÜú0ÂoÀïÀü¶0·`·€·¸ÿÀ@8·F籯/¯?¯O¯_¯o¯@¯F¬Qœ_œà›+ššš šsšƒš¸ÿê@š F¯—¿—+––Ÿ–¯–|–¸ÿê@…– F/’?’O’@’ F/‘Ÿ‘‡†@|P|t t0ttòt oÿo©o—ouo…oKo nÿn©n—nKnUÿÿÿ?gg/g?gÿg@fPf f°f?ee¯e dàd¸ÿÀ@Kd F`_G_P"÷[ì[T[„[I[;[ùZïZkZKZ;Z3UU3U?¯WW/W¸ÿÀ³VF¸ÿà³V F¸ÿÀ³TF¸ÿÀ@eT F?POP_PúHïH‡HeHVH:HúGïG‡G;Gÿ3UU3UUGUúÏÿo¯ï€¸±TS++K¸ÿRK°P[°ˆ°%S°ˆ°@QZ°ˆ°UZ[X±ŽY…BK°2SX°`YK°dSX°@YK°€SX°±BYss^stu+++++++_sssssssssss++++_sst+++_ssssssssss+++_s^stsst++++_sssstssssststt_s+st+s+_sstt_s+_sstt_ss++sts+st+ss++s+++sss+^ N¶u¶ÍJÿìÿìÿìþþ¬¶¼ÕUƒ—Ÿ}å®®qqºÅº¤ŸÇÇ}}°¹Š›¦w–in´ÁÕfox–ÀÕGþ:Åxþöî–ˆ®–‰ –”Z‚–¨ŒyÙ´ ƒm mˆ“ ‚‰¶ü”ÿïƒm{¼ªT¼¶×•–®¶þ¼þoþƒo­****o¡)âq)M|¬âD[•´?”eÌ?g‚Ú = y » ÷ j 4    t ³ ì 9 — ÍI—·+|Õ"‹üf§å8îMžÑ÷;Yp•i®k΢ã_¯È9wÀ"ƒÉ9ŽÏ'ÔeÎu™õHHô j!!t!¤"]"¡#J#´$$5$=% %#%~%º& &&¤&î'+'e'«'â(*(z(£(È(÷)m)…)œ)´)Ë)ä* *r*…**´*Ì*å*ý++++D+¨+¿+×+î,,,6,›--4-K-c-|-“-Ý.x..¥.».Ñ.é//±/Ä/Û/ñ00070N0e0~11$1;1Q1h11˜2 2‚2™2¯2Å2Þ2ô3Y3q3‰3Ÿ3¶3Ì3ã444/4F4^4u4Ž4¥4½4Õ4è4ð5i5€5–5­5Ã5Ú5ñ66&6=6S6j6€6—6®6Å6Ý6ð777;7Œ7ç7þ88,8C8Z8q8„8˜8¯8È8ñ99,9C9V9i9²9Ê9ç9ú::&:>:Q:t:Á;;%;;;O;b;y;;²<>%>8>L>d>|>“>©>À>Ö>ê>ý??*?=?Q?h?z?Ñ@;@S@i@@—@®@Ä@Ý@õAA&A9ALAcAzA‘A§AÀA×AíBBB2BHB{BßCÁD–D®DÅDÜDòEEENE„EœE×EþFNF{FÂGG2G£G¼GöHHAH‚H³HçII(I0I8IjI¶I¾IÆIÎJ=JEJMJ™J¡J©JöJþK)K1KyKK‰LL LŽMM)MBMXMnM„MœM±N#N¥OOŒPPnP®Q Q^QfQâR0RrSSSnSÈT7TTÚUUšV.VWW2WKWaWwW•W®X(X?XªX²XºXÓXÛYrYÑZ/ZFZ]Z¦Z®[[ [[„[Œ\\œ\û]][]¿]Ç]Ï]×]ß]ç]ï]÷^g^o^w^¬^ó_;_‘_ç`=`‹`îaeaâaêbeb×bÿcecmcâd`d¤d»dõe@e¥eëeóff$f,fkfsfðføg2grg½hhshÕiiˆiüjSjkjÚjðkLkTk\kuk}kõlTl®lÄlÚmmTmm—m®mÅmÜmönn(n@n[nvn‘nºnåooDoroÎp*p”pïquq¸r]s*s2s:sms¢s´sÒttpt÷uvvÃwAw÷x~x†xéyyAypy›zzbz‘zÛ{{-{É||£}}`}¤}ð~~-~p~£~É~îVŸü€[€Š'‘‘‘‘‘‘‘‘‘‘‘‘‘‘‚ÛƒNƒZƒb„ „ˆ…$…;…R…f…z…§…ô†G†t† Á ¶+@  p€¸ÿÀ@  H?2/3/+]33/310!!7!!ÁIü·hyý‡¶úJhæ“ÿã‘¶:¹ÿð@ H€ š¸ÿÀ@  H ›?/õÎ/+3/á2]10+#34>32#".Py3ßð"./""/."žú¹&5!!5&%5""5…¦²¶7@#˜ Ð à / o  ˜àð?33/3/]á]]9/á10#!#J)s)-)r)¶ýðýð3ø¶™@X±!  ± P P  H  ®  ®?Oß   /3?399//]33á2233á]22/]33/3/]ä9293/ä92393/3/10!!#!#!5!!5!3!3!!!×?þÍR“TþÝRNþþAþî+R“R%TTüë#@þÝ}þ¸‰þT¬þT¬‰H‰°þP°þP‰þ¸H{ÿ‰Ù-6?´@34/))/!!p/<753&&'4.'66Ù2]…TŠ2f`T !W`e/YƒV*1[OŠd©CB8ŒJX‡[.°+F3][þ(B1YS¾FrT7 æÝ ¬!²BUnJCoS5 ´°*‘)þZBSkH!7-&þ‹b£$9/&qYfÿì3Ë ';?]²<>¸ÿð@3<><>(´2µ#´(AA´ µ´ 0?>%¶7·!¶-¶·¶?áôá?áôá??/]áôá]Þáôá99//881032#"#".54>3232#"#".54>32#úGPœœPGÇ$JsOIpL&#IqNKqM'¬GPœœPGÆ#JsOJpK&#IqNKqL'ÿüÕž,¥¥JH£¥l¬v??v¬llªu>>uªýJ¥¤IH£¥l«v??v«llªu>>uª’úJ¶mÿì}Í!S€@M'JI,IH G6AGB B6B6B;54.#"2>7%4>7.54>32>73#'#".¦!4$;V8/B*Vd‡:bTH þ}4P7#B`þ}(MoG<-2^ŠXSƒ[02Tm<`+" ¸)5A'á¨1`l|Ni§s="AAC%#>@F)$=,Yû¯(6—!?HU86[A$ðNzdV*$MWc9KwS++SwK@m]O$þŒ73#.R$JqN¬Œ‘%GjEªNqJ$1}óåÓ]Áþ2ôwìâÔ^ZÎáð=þ¼¶@ò ð°øù??Þ]áä210#>54'3$KqNªEjH$¬NqK$1|ðáÎZ^ÔâìwôÎÁ]ÓåóRw$@˜€?2/]/^]å]10%'%7˜+þ†õ²°ž¸òþ‰‡+þwoÁþº`fþš`FÁo‰f¢ )@  ªï `  ­³?3á2/]]2á2]10!5!3!!#éþ}ƒ–ƒþ}–‡–…þ{–þ?þøyî 8@Ï  +  —¸ÿÀ@ H_œ /í/]]+3í2]]]10%#>7j'/3Šî6z|{8=„ƒ}5RÑBy@ @¹/á/]Î105!RðѨ¨“ÿã‘ú5@€  –ÀÐ4Ddt¸ÿÀ¶ H›/í/+]]í]]1074>32#".“"./""/."o&5!!5&%5""5ç¶±¸ÿð@ ?/382/8310#çýà³!¶úJ¶bÿìÍ'&@o))o  #ss?á?á/]á]Þá10#"&&54663232>54.#"3q²v¯s93o±~w°t:ýBkMMlEElMMkBݱþèÂff±±ÁfeÁþè²–à•KJ”á—–à”JJ”à²Ç¶5@!@n¿ÿ~ @ ??Í/^]]]á3/3]10!#4>7'3ǰ”`–‘+baY"y{+`ðË#<@ #o%%"o! " s"t?á2?á39/]3/3í]3/á310!!5>54.#"'>32!ðüp^KvS,"?V5_™Ef(\jvA`›l;5]Kþ籜}Q†€L;Z? M54.##532>54.#"'>32Á.StG±¸A„ÊŠmÁUWË]\†W)5bY……Q~U,$B\8k£J\&]n}Fl£n8`IxX9 µ‘` t@"-ª.2(JlCDa?—(Jf=4R9C6}6)6a…?¾ N@, Vn  w‡_ t??39/3á22/]3]]9/]3333á2/]210##!533!4>7#?Õ°ý]—¼Õþ{  þeHþ¸HŸ×ü0d8{uf"11.ý ƒÿìö¶*N@&o,,'$$(h#Y###ð¸ÿÀ@ Hs't$s ?3á?á9/á/+]3/]]333]Þá3102#".'532>54&#"'!!66!c«HD†Å€3c[R!!Ybc*O|V.°¨??9Z7²ýì' i7l ir¶~C ¬$ %NvQ— 9°¦þ]qÿì Ë+?7@ 1n "AA;o 6u,s's?á?á9/á/]á2]Þ2á104>32&&#"3>32#".2>54.#"q5\ŽÆ…./+#X+Z‰dC* 9L_;_šl;>t¤fd¯€JÛnÕ#Ì#º###  h8˜8Y8(888H88“C&CVCCC-s;s?á?á9/]]Á]]]99/]3/]]]áá]Î2/]]á9á9102#".54>7.54>32>54.''">54&5T•qB(F`8:oW5Cy©fn«u=-Lh:1V?%Cr•Ç DhHFkH$'If?~€j}#>W30U?$~Í,X„XClWEL_vI\•h86e’\Kx`JIZmBWƒX,û¦5Y?##A\84TH@<›Tje9R@34BT6ejjÿìË)=5@9o??/n   4u*s%u?á?á9/á/]3á]Þá210#".'532>7##".54>32"32>54.5\ŽÆ…..,#X+‡®f+ 8L`;_šl;?s¥fe®€Jþ%.;r¥jr·DN óG(TWFoN*/K`0C…kB“ÿã‘f'>@)€)) ) –ÀÐ4Ddt¸ÿÀ@  H#››/í?í/+]]3å2]1074>32#".4>32#".“"./""/.""./""/."o&5!!5&%5""5‘'5!!5'%4""4?þø‘f a@/"€"" "–À Ð d t P D ;  /   +  —¸ÿÀ@H_›œ /í?í/]]+3í2]3/]]]]]]å]10%#>74>32#".j'/3Š"./""/."î6z|{8=„ƒ}5í'5!!5'%4""4fîÝN@0@@o0 Pp€Ðð?/^]]]q33/]]29=/33/]]Î10%5üdœý!ßî¨fá þ”þ¾fºé\@= @ Æ»©†{hB9­/­ðo/]]á3/^]]qá/]]]]]]]]3]Î2105!5!fœüdœT••þf––fîÝN@0@@o0 Pp€Ðð?/^]]]q33/]]39=/33/]]Î105fàý œüdBl þfþX%ÿã%Ë';>@!2š(('F F=/= -›7Q?á3/å2/^]9/]Þá9/á3/á1054>7>54.#"'66324>32#".'B20D+9U8S–F?Q¼a]•h86P64B&»"./""/."ž%9\PM*)CEO50O94"‘*;3`‹WCiZT/-C?B,þÑ&5!!5&%5""5mÿJ¶Who@?X`'''FF'N1 j@j;@NN, [d¿@6S@EI/3Á?Á99//^]^]Á3Á22/]Á]ÞqÁ9///]]ÁÁ10#".'##".54>3232>54.#"32>7#"$&546$3232>77&&#"%9La:-I4!6GY5MwR+;ožb-ZRE"+.F/V˜Ñ{©þþ¯ZO™ã“=wod+VØ‚³þçÃfvÛ7Áœ¿jüeU7N2M*Je?Û>}qaH)2A#%B18eŽVe¨zDþ`5D(=hŒNŽÝ˜OoÇþê R&,fó¼Eîˆe½þñþÕ…w-SsEý :^xݼ„@$FFII€Ð¸ÿÀ@ H/Ÿß¸ÿð@_ H?2?9/9+á/83^]3/8+]q39=/99]]99]]3310!!#3.' ýߢ¼ªþg”‘Åþ;¼úDj¨4 ZåõÖ$*[p€g1111$Zd0 #`y $`"`?á?á9/^]]á9öá2]]ö]á9/]]qá210!2#!32>54&##!2>54.#ǀÃB'JmEEyZ4A{°oþºôTrFš¦ß XwI !K|\¶'Wg>lR7 -OxVdm:J;Y;xhý—ýð(He=8^C%}ÿì˜Ë#L@¯@Hº ` p  ¸ÿÀ@ H %¯%[f$!_ _?á3?á3öá]3/+]]9/+]10"3267#"&&546632&&k®{C;v°vY N'NUa;¤ðLW©ú¢lÄON?”'Q˜Ú‰Û–N#¢lÆ©¦Æn,*œ .Çü¶ &@ [gZd``?á?áöá]öá10#!!24.##3 ü`¶þ÷¨þ’—™ø®_ÅB~¸uÉ¢ é¹þé»^¶\µþô¶’ÕŠCû‰$Ǿ¶ B@&g  Zd _O¯ _ _?á?á9/^]qáöá2æ29/]10!!!!!!!¾ý ÷ýÃýé=¶¤þ<¢ýøÇ¾¶ p@ÿ€Ð¸ÿÀ@8 H  / ¯ Zd _?oÿ@H@H_??á9/++^]qáöá2^]3/+]]q9/10!#!!!!º÷ýÃý鶤ýü¤}ÿìòË+7@++ )Zg--[ f,+_$_$_?á?á9/áöá]ö2á9/10!#"&&546$32.#"32>7!ä7pv‚Kò¦V_¶ «oÌXH$SX].z¼B7x¾†,I>7þÕý3 iî¬Ãi,*¢Q˜Ú‰‚ØœV ´ÇÕ¶ =@# Ze À ¿  Zd _ ?2?39/^]áöá2]]]öá210!#!#3!3ÕºýfººšºªýV¶ý˜hRd¶ W@& + { › « û T + ; K   Zɸÿø@ H  ?Á2?Á2/^]+]Á2ñÁ2_]]]]q10!!57'5!dý¬¬f)˜)ff)ûh)ÿHþ{s¶/@ß`p/Z    _/á?/^]3/á]]]10"&'532>533L"N-%K=&»;i“þ{   2XD¶ú^iše1Ç¢¶ d@- f   °/Zd  H¸ÿð@ H ?3?399++öá2]]]3/8^]33/839]310!##373¢Óþ=‹ººyÄÑýøºrý¸¶ý%¨3ýƒÇ¾¶#@¯Zd_?á?öá]]31033!Ǻ=¶úð¦Ç/¶‹@69 H9Z¸ÿø@ H H eO  ¸ÿø@ H&  Z d H  ¸ÿð¶ H ?22+2?33+3öá22]+^]]]ö993+3+á2]+210]]!##!3!#46767##þE¬œžºþAJI?‹9ü–¶ûX¨úJw4†=GIûǶQ@)(Ze°'   Z dH  ¸ÿð¶H ?22+?3+3öá22]]]]öá2]210!###33&'.53×ý1¬ÕÌ®ºMLAŽ9üç¶ûLLJ CC> }ÿìqÍ'4@ [g)À)¿)p)/)_)[ f(#__?á?áöá]]]]öá10#"&&54663232>54.#"qQ í›£ïLLžð£›ë QûÑ4k¥rr¥k22j¤rr¦l4Ý©þêÆllƪªÄkkÅþ뫉ۙQQ™Û‰ŠÚ—QQ—ÚÇ3¶F@,[(8HgÏ@Zd`0@` ??á9/]áöá2^]]]ö]á10###!232>54&##37~Ϙ–ºj†Â~<ýN]‹[.¤®  [¨MýǶ9m þg GqQމ}þbqÍ18@"([g3À3¿3p3/3_3[f2-_#_ /?3á?áöá]]]]öá10&&'#"&&54663232>54.#"q1_Ž]+‰Zyg­3)£ïLLžð£›ë QûÑ4k¥rr¥k22j¤rr¦l4݃ⵄ&^<ŽIÆlƪªÄkkÅþ뫉ۙQQ™Û‰ŠÚ—QQ—ÚÇ ¶‚@V ù H  [ éù H  ?Ÿ¿ß Zd ``?3?á9/^]á9öá2]]Î82+]q2/]á9^]3+]q10#! #'32>54.##ºd þ1Qh7ŽÛþ¡å¤Z~Q%)SW \ý¤¶ÎÑW‚]>ýq\ž#EgEHd@hÿìÉË3B@'Y##Zg5¿5ÿ5`5?5*Z f4*'_$ ` ?3á?3á99ö2á]]]öá3]10#"&'532654.'.54>32&&#"ÉE€¸soÁA"W`f2 ™Iz]YƒU)@t¡aw¾JCA¥Xz†FsT[‰\/‡a™j7#"²xp6PC?%#Sh„TXŠ_2-#œ+q`9SC;!$L`~¶^@2 Ð O Ï  0 ¯ï„Z@àWgw¸ÿÀ@  H_??á2/+]3/^]]á2/]]]]]q10!#!5!!q»þ^þþ_¤¤¸ÿìݸ/@Ze°o¯Z d_ ?2?áöá]]]öá10#".5332>7ÝB…Ɉ€Ä…D»­¯Y€R(¸üLrÄRMŽÇz®üH¯À6bˆQ¸‹¶ l@ `p°ð¸ÿÀ@ H/¿@ H¸ÿð´ ¸ÿà³ H ¸ÿð@  H ?3?33++?3/83+]3/8+]39=/33103#3667ÅÆþ»þÅ'*.¶úJ¶üa[©JJ©aþ¶*ß¶H¸ÿøµH¸ÿø@HHH¸ÿø@/H%D´%%$%D%T%%% p€À¸ÿÀ@ H,o,, ,0,,¸ÿð@ H H %%¸ÿà³ H%¸ÿð¶ H%?33++3?333++/83^]]]3/8+]q39=///]^]q3+3+3+3+3+3+103>73#&&'&'#3>7)Åå  ÈÇþ‘¼þ ò¼þ~Åß  ¶ü¨8pi^&&Zcg1rúJª3l/7437/p6ü\¶ü‡.cb[&%blo1`¶ @  7  8p€À¸ÿÀ@ H   /  ¸ÿð³¸ÿð@(' ?2?399]]/822/83^]3/8+]q39=/3]33]3/8310!##33`Óþžþ‘¼ÅþZÆLN¾þ[{ý…üºýÑ/ýL7¶s@ï  @ H«˜@¸ÿð@/€OZw‡—O6?3?9]/^]]]á92/8]]]33/8]]]]]3+]103#3TÈþB»þBËÓãüƒýÇ/‡Rþ¶ 8@ g  ? O Ÿ f __?á9?á9æ2/2^]æ22/10!!5!5!!þüTÇýMƒý:Û‘¦‘û¤þ¼9¶&@óñ°Àõøõù?á?á/]áí210!!#39þk•ßßþ¼ú•ú1é¶!·¸ÿð´?///8338310#É ²ýà¶úJ¶3þ¼É¶$@óñ`p õùõø?á?áÖ]áí2103#5!!3ßß–þj®Ï•ù)%Á¶?Í/3/103#)Ëf¿¡þ¯þ£%œüdßý!ÿüþ¼NÿH¶º/á/3/10!5!Nü®Rþ¼Œ‰Ù! @ €_/]Ì/Í10#.'53x#RM?Û+.0ÙSXQ"QQL^ÿìœ^#2T@)G#U44o40H ¸ÿÐ@ H H V3P*R$P??3á?9/áá2æ2/++á^]öá2210!'##".5467754.#"'6632%2>55%!BN`?EtU0çì¸7Q4SB@J¶df•a0þ/=hL+ZzI a˜-A*'Q{T¤°ECZ70"‰(8)YŠbý&MuOc 9Q3\V®ÿì?/8µ-HW11¸ÿ¸@ I%GT0*P  P?2á?3á??öá22+öá102#".'##33>"32654&ž^šm<32.#"3267Re°‚JL…²fN•268<:‘”Qƒ66{?‰Õ–Û‰>"š  ÉÔÓÃ%¢qÿì04@&GU22.H V1+P P?3á?3á??öá]öá2210%##".54>323&'&&53#%2>754.#"T;M`<]šn<32!32>7"!4.`n¶ƒHBx§ecžn;ýL™—3WQL'(MQW`r… ì9XJŽÒ‡ˆÖ•NGµnqÁ¶ ¢Ûœ•DqP,ðp@NÏß`€ ?O¿G/¯ € OP ??á?^]3á2/^]3/]3á22/]9/]]]10###5754>32&&#"3‹õ·ÂÂ-U|N;c'/I((:&õÁü?ÁKD`kT# 0SAh%þü^?R^§@ 2SG7/`7p7€77/7/'HYG¸ÿÀ@M H ý°ˆ 0@``¿`ß` `@'@ H'2 7&&5467.54>3232654.##"32654&#"üÅ&/_Œ],!)8°]€Q$A†Í‹k j5'BW/*6@E+G12b’a%Oþ@;aHº¹7ZA°#L?)\lcdgidcjJq#mEL^5  (!/Pm=XŒa4*PqG<[B* R5=Y*?Q`3YŒb4 û%@.sl.:! ,M`spow{tx®2@GU`€ G TP ?2??á3öá22]öá10!4&#"#33>32\ipQnC¶¶ ER\0·¹‚4f”`ýÇþ2+?*¿Òý3 uå%@  GTS??3/åö2á2]10!#34632#"&d¶¶Ä=-'?,-=J)<6 +:98ÿ¼þuå!.@# #G  T"S P?á?3/åæ2/2á2]10"&'532>534632#"&B0?6#.#¶"Hm=-'?,-=þ ” 'A3ôûM{W/_<6 +:98®ð^@ D¸ÿÀ@ H/ G T ¸ÿø@ H H ??399++?öá2^]3/8+]333931073##3V‡%Óþo¬Ñþ°m´´7ªiþ%ý‘øRþZý6þí®d@ GT??öá]10!#3d¶¶®‡^,e@?# G¹ – ¦ ‰ g w  G,U.ð.Ï. .P..GT-#P( ?22??3á223öá2^]]]]öá9/]]]]á210!4&#"#4&#"#33>323>32ÑdiIfA·ciMh?¶” BOY.xŸ&IW`2¯±‚/[‡Xý¢‚4f”`ýÇJ”+?*X^/D-¿Òý3®^0@GU`€ G TP  ?2??á3öá2]öá10!4&#"#33>32\ipQnC¶” ER\0·¹‚4f”`ýÇJ”+?*¿Òý3qÿì-^0@HW!@!Ð!à!!H V PP?á?áöá^]]öá10#".54>3232654&#"-C}²og®GC|³og®Gý‰šš‡‰šš‡'‰Õ‘LL‘Õ‰ˆÓ‘KK‘ÓˆÑÓÓÑÑÏÏ®þ?^06@.HW22& G T1 P +P?2á???3áöá222]öá10".'##33>32"32654&ž;`M; ¶”:M`<^šm<754.#"".54>32373#46767#5LiAAlQ‡f]šn<‰H;?hK)¶”9GX^¨3_…Qý°JÉ+P=%Zÿì?^5H@-%G W7?7_7Ÿ77,G Ÿ¯V6&)P," P?á2?99á2ö]2á]]ö]á310#"&'532>54.'.54>32&&#"?:mš`mœ;LTY,A[95\HHsP+7dŒVa¡H?A‰Gfb8^FHqP*-PxQ(#"¦);$212#4./7#".5#5773!!ú-*# (04>jM,››Niþì?Š  N…e}QNæü‰ýƒab¤ÿìJ0@GU`€G T P??3á?3öá]öá210!'##".5332>53u ER\0[Š\/¶joQnC¶“+?).b˜iÍý=‚‚4e”`:û¶ÕJm¹ÿø@ H H ¸ÿÀ³H¸ÿÀ@ H¿ÏïP/O¸ÿð@ G  ??39]/8Á^]]]3/8++Á9=/3+3+10!33>73wþ‰¼Ç  Ǽþ‰Jý!hl``lh!cû¶ãJ/ù/ÿø@ H/ H' ¸ÿø@ H  H  H¸ÿø@ HT''¸ÿà@ H[  H'  '-.¸ÿÀ³H.¸ÿÀ@ H...1 1011¸ÿð@-  'fv?33]3?3]33/83^]]3/8++39=///+]+]3+3+3+3+3+3+10!.'&'##33>733>73ð¨   ¬Óþ翃  ³Ä¬   ‰ºþäh-24:>?:2j%ýœJý¸-ig[Wa_!ký•"\_XWhm/Hû¶#ÛJ å@¡‰ †÷å6èç øê9k{W:JdtX5E   ÷å6@H@Hk{W:J  0  °  Ù È º   ; K (    ??/]]]Á]]]]^]]qÞ]]]++Á]]]q9=///]]]]]]Á]]]q3]33]Á]]]q10]]33##˜þŸÏúúÏþuÏþôþòÏ3þfšýéýÍ´þL þßJ"d¶"¸ÿÀ³H¸ÿÀ@ H$¿$Ï$ï$P$$/$O$¸ÿð@ "#P?2?á333/8Á3^]]]3/8++Á9=/331033>73#"&'532>77 ½× ǼþNAVtP4L@#0F4%9Jý›(XXR#Va^!cû'QZ1 ‘,@) R5J l@ — H¸ÿÀ@ H ? _  ˜¸ÿøµ H¸ÿÀ·H?¸ÿð@ HO HO?á2+?á2+/]+33+]]3/+3+]310!!5!5!!5ýþ °ýô}D‰’üÑ=þ¼¢¶'@@%÷ 'ñ#ö  #õÙ_)õøõù?á?á9/]]á9/]æ33ñ2â/210.54ᒑ4>7ô-A(Mƒ_6ƒ}}ƒ6_ƒM(A-wssw0=# –!GnNNgV›VgMNnG!• #=0þ´i{ zjéþ-@0@p€ª@€À??/^]á]q103#é––ø3þ¼˜¶)@@% $$÷ñöõïÿÙ$õ#ù õø?á?á9/]]á9/]33æñ2â/2104675&&54.'53"5>5áwssw-A(Mƒ_6!A`>}ƒ6_ƒM(A-;jz {iL0=# •!GnNþ³4H-›Vgþ²NnG!– #=0fJZ#<@ %%  ­ ¸ÿÀ@H­  ? O o  /]3ñÈ/+2á/]]Î10.#"563232>7#".%7-)<;8d”27C/%7/(<;8c•27C‹ !,¢l  !,¢l “þ‹‘^A¹ÿð@ H0 °Àš¸ÿÀ@ H ›/^]/õÎ/+3/á2]10+3##".54>32Õy3ßï#..##..#¤ûçH&5!!5&%4""4¼ÿìºË%Z@%F %'@'H 0 @ Ð  s!s¸ÿÀ@ H??99//+3á3á/^]á]Æ29/3á210$#5.54>753.#"3267vnL‰WŠb45a‹V‰Hˆ.58<;‘”Qƒ6ÔÈÎ K…ljˈK ¬¤!š  ÊÔÒÃ%¡D#É(u@ o#¸ÿÀ³ H¸ÿÈ@0 H**!@ H)!u /"""¯"¿"ß"ÿ"""ts?á?á9/]3á23/+3]3/++399//^]3á2102&&#"!!!!5>55#534>šj®BB8K0RY@+¦š )DaCÕ‰DW‰_2{ì‹#7†@#.« !p99 $ª€¸ÿÀ@1 H8€  )® 3®Ï ï    °  ? o  /]]]áÆ2/]á93Æ]23/+]áÆ2]3/]9ñÀ/]210467'766327'#"&''7&&732>54.#"º#b/l<7&&54>32&&#"#"&'532>54.'.7>54.'‰-:KU7dŒVaH8AŒGcf9_FHqN*)4EL;l›`lœ;LTY+E]73^LIsP)š?eH#)!AlR/&)3S@-&rT=bD%( ‹';9.,/ANa>4UD1&mNGoM(! ž'3--1>NdY%?:7 $.8"&@;9-:3 jÙ 5@! †¯À†@P‘Ÿ Ï 0  /]]3å2/^]áÜ]á104632#"&%4632#"&38('::'(8w8(#:&(8s6015522560 &522dÿìDË%AUj@CÅ"""&LÃ4À44WBÃ& ÉÉ/Ÿ`p€-GÈ;QÈ-?á/á99//]^]áá/áÞ]qá99//3/á10"32>7#".54>32&&4>32#".732>54.#"{=^@!=_C69815<#f˜e36i™d?„;>4aü¾6aЧÀhhÀ§Ša66aЧÀhhÀ§Ša6me¯ê……ê¯ee¯ê……ê¯e,SxKNxR+  ƒ Bzªge§xC!þ¾hÀ§Ša66aЧÀhhÀ§‰b55b‰§Àh…ê¯ee¯ê……ê¯ee¯êDBÇ-N@/-à///O//¯/$à `  .-ä'äÀäÞ?áÜÄá99/áÆ]2/á]Ö]á2210'#".5467754&#"'663232>55ç'/8#+H4c=80Z*03u<}wÉ3D)2*":+R# 3M3flH9d$jzþ:9+3-,A,1Rs“Ç `@ ë P`   ¸ÿÀ@! H Ÿ¯ëŸ   /3/39=/93333/]á]Æ+299//]á310%R5uîîuþË—6tíítþÊ)žNþ¤þ¤N›žNþ¤þ¤N›f9@$ª–‹yVK8 ­³?á/]]]]]]]]]Þá10#!5•üùýé–ÿÿRÑBydÿìDË:NÂ@}¤´Ä´ÄRÅ Å ÅÀÐEÃ-À--P;ÃÉÉ/Ÿ`p€&@È4JÈ&?á?á99//]^]qá339á2/áÞ]qá99//]^]q9áá29‡+Á‡+Ä]10]32654&#####324>32#".732>54.#"çH[OSYF’-9C5*! ³Î_騞ûë6aЧÀhhÀ§Ša66aЧÀhhÀ§Ša6me¯ê……ê¯ee¯ê……ê¯eHEJ;0K9( nW%G8`þ }‚þÃhÀ§Ša66aЧÀhhÀ§‰b55b‰§Àh…ê¯ee¯ê……ê¯ee¯êÿú ¶º/á/3/10!5!ûô Œ{VòË'C@,« )Ÿ)ª0@® àðo #®?á3/^]]]qá/]á]Öá104>32#".732>54.#"{2UsAAsV22VsAAsU2{4F((F55F((F4AsV22VsAArU11UrA'E44E'(G55Gf¢ :@! ª ï `  ­ ­³?3á2/á/]]333á223]10!5!3!!#5!éþ}ƒ–ƒþ}–þ}œ‡–…þ{–þþú––1JmÉ@@á O   @ Há¸ÿÀ@H åÞåÝ?á?á9/+3/á+]Þ2á10!57>54&#"'6632!mýÄÑ9H(B63]-N6…RUC;"A@2&^0A!?[92VU[79hÉ0a@<áá2_222@ H''@ Hä/_ß&#å,Þå ß?3á?á39/^]á9/+3/+]Þá3/á9/910#"&'532654&##532654.#"'>32NQEXX(S~VF{9?„5bXk`bb\T#/;a3E=DL,EiF#çNjjN73#‰//*Û?MQ#yôLQQ"QXS®þJ7@" G U `p€GTP  ?3??á?öá2]öá21032>53#'##"&'#3djoRnC¶“ 0gHj#¶¶‡‚‚4e”`:û¶“ST.*&(#U*þÀ6qþüf7@!™0@P ™   /2?Á/]]Öá9/^]á10####".54>3!fxÏy=U_›m32#".“"./""/."Ó&5!!5&%4""4#þ˜9@ „Œ@ H /á/9/+áÆÖá9/]33310#"&'532654.'73˜–-1GP.?%Zy9":+þáall+1# °s):?Jº¶4@!O@ Há 0Ý åÜ?áÍ?/]]3á3/+]103#4>7'3‡‘&^J¶ü”<<8(I`B‹Ç.²à¸ÿÀ@ H!!à äÀäÞ?áÜáÖá]Ö+á10#".54>3232654&#"‹)MmD?jN+)LmD>kN,þ:KVUKKUVKmS‚Y//Y‚SSX..XSwyywxssTs–Ç V@/Ÿ¯ë   ëŸ @     /3/399=//3333/]]áÆ299//3á]10'7'7–þÊtíít6þhþËuîîu5þeN\\NþbþeN\\Nþbÿÿ?‹¶&{'J<üý·0@¿?@@]5]]5]]]55?55ÿÿ, ¶&{í'5t3ý·(@°@p@]]5]]5]5?5ÿÿÎÉ&u'¨<?ý·<@'8p8P88´3¤3„3d3P303 33L]]]]]]]]5]]55?55DþwD^';D@2š(('F == F¸ÿÀ@H ''-›7Q/á3?å2/9/+á^]Î9/á3/á103267#".54>7>55#".54>32P'A20D+9U7T–E@R¼a]•g85Q64B&º#..##..#¤%:[QL*)CEO50O93#’*:3`ŠXDhZT/-C>C+/&5!!5&%4""4ÿÿÝs&$Cÿ½R´&¸ÿœ´%+5+5ÿÿÝs&$vR@ !&l%+5+5ÿÿÝs&$KR´&¸ÿÿ´%+5+5ÿÿÝ5&$RR@ &,%+5+5ÿÿÝ+&$j!R@ &)%+55+55ÿÿÝ&$P}1@ ïßP@ %+55]]]]]]]55ÿþV¶„@* Z©$4T  g¸ÿð@  __ _ O  ¯   _?3á/?99//^]qááá2/83æ29///]]]]}‡ÄÄ3á2310!!!#!!!!!!#Výþ%˺ÉýÃýê=ûu“lÅþ;¶¤þ<¢ýøÆ¨ÿÿ}þ˜Ë&&zü ¶O*$ %+5ÿÿǾs&(Cÿ·R´ &¸ÿ´ %+5+5ÿÿǾs&(v?R@ &J %+5+5ÿÿǾs&(KÿñR´ &¸ÿý´ %+5+5ÿÿǾ+&(jÿõR@ & %+55+55ÿÿ>ds&,CþµR´ &¸ÿ¨´ %+5+5ÿÿRŠs&,vÿxR@ &j %+5+5ÿÿ©s&,KÿR@  & %+5+5ÿÿ@w+&,jÿ R@ & %+55+55/ü¶]@:[g! !Zd _?o¯ßÿ@H``?á?á9/+^]q3á2æ22/á2]öá9/103!2#!#%4.##!!3 /˜—™ø®_`¶þ÷¨þ’˜B~¸uÉPþ°¢ %‘\µþô°¹þé»^ƒ`’ÕŠCþ¢þ$ÿÿÇ5&1R‹R@  & !/ %+5+5ÿÿ}ÿìqs&2CTR´(&¸ÿ«´.( %+5+5ÿÿ}ÿìqs&2vR@ 4&X(. %+5+5ÿÿ}ÿìqs&2K®R@ (&0( %+5+5ÿÿ}ÿìq5&2R}R´0&¸ÿð´1? %+5+5ÿÿ}ÿìq+&2jªR@ 1&(< %+55+55-Ý{ ‡¹ÿð³H¸ÿà@HH H H H¸ÿð³H¸ÿà@0H@  P   Pp€ ÀÐð ³?^]q2323/]3333]10++++++++7'ËþÂi=Bhþ¿?fþ¾þÃgÓ?iþÂ>gþ¿þÀf=þÅg}ÿ´qü&1\@:)*'[ g3À3¿3p3/3_3[f2)*-"_  -_ ?3á?39á9öá]]]]ö9á910#"''7&5466327&&#"'32>\[^Q í›½…N‰Za[Lžð£^¡BPü·.0C0rGr¦l4jXý¾/rEr¥k2®•cþÞ·©þêÆlGN‘d*¾ªÄk*&üáƒÑN± Q—ÚŠ—üTQ™Ûÿÿ¸ÿìÝs&8C=R´&¸ÿÀ´ %+5+5ÿÿ¸ÿìÝs&8vÅR@ $&H %+5+5ÿÿ¸ÿìÝs&8KyR´&¸ÿý´  %+5+5ÿÿ¸ÿìÝ+&8j}R@ !&, %+55+55ÿÿ7s&<v1R@ &c %+5+5Ç3¶<@![gŸ Zd``    ??99//ááöá22]]öá10###33232>54&##37~Ϙ–ºº°†Â~<ýN]‹[.¤® [¨Mþöü9m þg GqQˆ®ÿìuKm@HF.5G/@ H_.o./..A$GWMM MÀM@GATL$5:PGAP?3á??á9öá]öá9///^]]+]áá10#"&'532>54.'.54>54.#"#4>32ò+?K?+'F98X=!8eUa‹5AHL%8Q4+H8?U5)>H>)!W~Q'#"¦-@($;8:#(DCF*6O?6:C,*>)0SAûN°hU%&Ltÿÿ^ÿìœ!&DC”´3&¸ÿå´93 "%+5+5ÿÿ^ÿìœ!&Dv5@ ?&…39 "%+5+5ÿÿ^ÿìœ!&DKâ@ 3&3;3 "%+5+5ÿÿ^ÿìœã&DR½@ ;&)7#"&'#".732>55"!4.^çì¸7Q4SB@J¶dƒ¦+3¦gašl9ý`““1UNJ%'KOU1ŠÊ>"L_tJG{Z4½aO=hL+ZzI …n ×7T3¤°ECZ70"‰(8U]U]GµnqÁ¶ ¢rs6U;'Q{R\V&MuOc 9Qcœ•DqP,ÿÿqþo^&FzB ¶/&  %+5ÿÿqÿìá!&HC”´(&¸ÿ¹´.(%+5+5ÿÿqÿìá!&HvR@ 4&v(.%+5+5ÿÿqÿìá!&HKÞ@ (&0(%+5+5ÿÿqÿìáÙ&HjÚ@ 1&(<%+55+55ÿÿÿÞg!&óCþU´&¸ÿš´ %+5+5ÿÿ®B!&óvÿ0@ &t %+5+5ÿÿÿ½U!&óKþ»@ & %+5+5ÿÿÿî%Ù&ójþ»@  &%+55+55oÿì-#'9t@F(H# """ W;@;Ð;à;;2H V: #!!-P¯¿07P?á?99//]]3á39öá^]]æ9///9á210#".54>327&&''7&&'774.#"326-C}²oh¯G?v¨ifš+xZÿJÙ(U/FAz;ãJÃCoO,¼"FnKMmF!!GmLš‡=ŽÜ˜OB¹ww¸~A;<vÀQ™rƒ7{ H,ŠquAœ»Ý°8kR2.XƒUL}Z1Çÿÿ®ã&QRù@ !&"0 %+5+5ÿÿqÿì-!&RCØ´ &¸ÿ×´& %+5+5ÿÿqÿì-!&RvP@ ,&N & %+5+5ÿÿqÿì-!&RKû´ &¸ÿú´( %+5+5ÿÿqÿì-ã&RRâ´(&¸ÿý´)7 %+5+5ÿÿqÿì-Ù&Rjù¶)&¸ÿù´ 4 %+55+55fø¬+`@0-"ªªVf(8¸ÿð@( H'­ ­ `°ÀЭ³?á3/^]á3/]qá/3+3]]á3/á]105!4>32#".4>32#".fœý¿)*  *))*  *)‡––þî#/ /#!//Û#/ /#!//sÿ´/‘$-\@;'(%H  W/@/Ð/à//H V.('+"P +P?Æá?Æ9á9öá^]]ö9á910#"''7&&54>327&&#"4'326/C}²o}bDƒP?FC|³o?q1DƒP>EýK-š‡D'þrH-š‡'‰Õ‘L5mJƒHÕ‰ˆÓ‘KlIIцTƒ3‡ÏÑŸcý{Óÿÿ¤ÿì!&XC£´&¸ÿ›´! %+5+5ÿÿ¤ÿì!&Xv`@ '&W! %+5+5ÿÿ¤ÿì!&XK@ &# %+5+5ÿÿ¤ÿìÙ&Xj¶$&¸ÿû´/ %+55+55ÿÿ þß!&\v@ /&g#)%+5+5®þ? 18@/H W33' GT2,P!P?3á?3á??öá2222]öá10>32#".'##3%"32654&d:M`<^šm<>%+5ÿÿ/ü¶’qÿìž'8U@1-G& "&U::6H V9'%O" 3P (P?3á?]3á?9/3á2?öá]æ29/á22210%##".54>323&'&&55!5!533##%2>754.#"T;M`<]šn<32\ipQnC¶œœ¶{þ… ER\0·¹š‚‚4f”`ýðÕ‰¶¶‰¸+?*¿Òý\ÿÿÿõÄ5&,RþóR@ &#%+5+5ÿÿÿ£rã&óRþ¡@  & %+5+5ÿÿ=|Á&,Mÿ"R@ & %+5+5ÿÿÿë*o&óMþÐ@ &%+5+5ÿÿ0Š@&,NÿR@ & %+5+5ÿÿÿÞ8î&óNþ½@  &%+5+5ÿÿRþBd¶&,Qœ ¶%+5ÿÿDþBƒå&LQ%@ o%%%%+]5ÿÿRd7&,OPR@ & %+5+5®dJ@ GT??öá]10!#3d¶¶JÿÿRþ{)¶&,-¶8@O@H¸ÿÀ@H¿! !!P!!!@H+]]]]]++]]5ÿÿ þ‡å&LM.@ Ïß5Ÿ5€5_5@5 555@H+]]]]]]]]55ÿÿÿHþ{ds&-KþÊR@ & %+5+5ÿÿÿ¼þW!&7Kþ½@ & %+5+5ÿÿÇþ;¢¶&.9s¹ÿ±´ %+5ÿÿ®þ;ð&N9!¹ÿÄ´ %+5®ðJI@ D¸ÿÀ@ H/GT ?2?399öá2^]3/8+]339310!#373#þ¬m´´ƒ3Íþo¬éQþhJþç5##33&'&&53‹3M"N-%K=&ý¬ÕÌ®7dþ{   2XDºMLAŽ9üç¶ûuAA8}4 ú^iše1®þ^(:@" G$U**`*€*GT)P P?á???á3öá2]öá9/10"&'532>54&#"#33>32î0?6#.#ipQnC¶” ER\0·¹"Hmþ ” 'A3m‚‚4f”`ýÇJ”+?*¿Òü•M{W/ÿÿ}ÿìqÁ&2M¼R@ *&() %+5+5ÿÿqÿì-o&RM@ "& ! %+5+5ÿÿ}ÿìq@&2NªR@ -&2( %+5+5ÿÿqÿì-î&RN´%&¸ÿÿ´* %+5+5ÿÿ}ÿìqs&2SìR@ 4&C(< %+55+55ÿÿqÿì-!&RSF@ ,&E 4 %+55+55}ÿì¤Í*[@6Z'' g,,[ f+_O¯__$__?á?á?á?á9/^]qáöá]æ29/á29/]10!!#"&&546632!!!!!"3267&&¤ý +[0£ïLLžð£bTôýÂýé>üVr¦l44k¥r4Z&&Y lƪªÄk¤þ<¢ýø…Q—ÚŠ‰Û™QXqÿìá^*6?d@;?HÐ111 7H%WAAÿA@A+H V@P?+????.<4P .P?33á2?33á29/^]]áöá]]qö2á9/]á29910"&'#".54>326632!32>732654&#"4.#"`‚ÌA?Æ€g®GC|³oyÃ?<¹ucžn;ýL™—3WQL'(MQWûš‰š–‹Œ—š‡ð9X>r… pmmpL‘Õ‰ˆÓ‘KojipGµnqÁ¶ ¢;ÑÓÉÑÜÎÏbDqP,œ•ÿÿÇ s&5vTR´)&¸ÿî´# %+5+5ÿÿ®!&UvÎ@ #&@%+5+5ÿÿÇþ; ¶&59w¹ÿ¶´# %+5ÿÿ`þ;^&U9þñ¹ÿ´%+5ÿÿÇ s&5LR´"&¸ÿ©´( %+5+5ÿÿr !&ULÿp´&¸ÿã´"%+5+5ÿÿhÿìÉs&6v;R@ @&p4:%+5+5ÿÿZÿì?!&VvÜ@ B&]6<%+5+5ÿÿhÿìÉs&6KÿíR@ 4&"<4%+5+5ÿÿZÿì?!&VK›@ 6&>6%+5+5ÿÿhþÉË&6z5¹ÿú´:4%+5ÿÿZþ?^&Vzð ¶<6%+5ÿÿhÿìÉs&6LÿæR@ 9&?4%+5+5ÿÿZÿì?!&VL—@ ;&A6%+5+5ÿÿþ;¶&79ù¹ÿØ´%+5ÿÿ!þ;F&W9ÿ]¹ÿ÷´$%+5ÿÿs&7LÿÈR@  &%+5+5ÿÿ!ÿìè&W8o ¶a$$%+5¶s@Là_ß 0@¿ÿ ZPð w‡—1  ` _?á2?9/3á2/^]]]33/]]9á22/]9]]]q10!5!!!!#!5¶þ^þþ_'þÙ»þ×3ߤ¤þ!•ýbž•!ÿìF%j¶ ¸ÿÀ@7 H'/'?'#G °À"O OP ?á?33á29/3á2/^]33Ä]2á22]3/+33]10%2>7#".55#535#5773!!!!ú-*# (04>jM,‹‹››Niþìþþ?Š  N…eûŠøQNæü‰øŠûabÿÿ¸ÿìÝ5&8R`R´ &¸ÿÿ´!/ %+5+5ÿÿ¤ÿìã&XRó@ #&$2 %+5+5ÿÿ¸ÿìÝÁ&8MR´&¸ÿÿ´ %+5+5ÿÿ¤ÿìo&XM!@ & %+5+5ÿÿ¸ÿìÝ@&8N}R@ &" %+5+5ÿÿ¤ÿìî&XN @  &% %+5+5ÿÿ¸ÿìÝÙ&8P{R@ &" %+55+55ÿÿ¤ÿì‡&XP @  &% %+55+55ÿÿ¸ÿìÝs&8S²R@ $&6, %+55+55ÿÿ¤ÿì!&XSP@ '&H/ %+55+55ÿÿ¸þBݸ&8Q ¶ % %+5ÿÿ¤þBJ&XQ¸ ¶!!%+5ÿÿþs&:K;R@ +&3+%+5+5ÿÿã!&ZK®@ 0&80.%+5+5ÿÿ7s&<KÿÐR@  & %+5+5ÿÿ þß!&\K©@ #&+#%+5+5ÿÿ7+&<jÿÎR@ & %+55+55ÿÿRþs&=v9R@ &^ %+5+5ÿÿR5!&]vÖ@ &` %+5+5ÿÿRþ7&=O)R@ & %+5+5ÿÿR5å&]OÍ@ & %+5+5ÿÿRþs&=LÿàR@ & %+5+5ÿÿR5!&]L†@ & %+5+5®¾%@  GT P??áöá]3/1034>32&&#"®-U|N;c&/H((:'°kT# 0SAûNËþéË+J@*0-)G 0@`)) #P P?á?á9/3á2/^]3/3/3á2/2]10#"&'532>5#5754>32&&#"3-U{N =9(;&ÂÂ-U|N;c&/H((:'öDü?kT# ™ 0SAÃKD‰kT# 0SA‘‰Ýª#1>@^‰))¾**0118ƒ    V#f##Yi2ƒoÖ 7 G W Ç  Ù8HXÈ €Ð¸ÿÀ@ H@@@/@@Ÿ@ß@ ¸ÿð@1 _# H <55Œ<ï`1 1#<11<#) ?3/9////]]]]á]333+á/83^]3/8+]q39=/3]]3]]3/]á99]99]3/]qá3/33/í]10#!#&&54>32.'>73#4&#"326P<3ü¾šýÓœ¼ø3: ;R21T># ”‘…0/*Û?MQ#y¼?21?31 2?œF`û#‡þyÛ`F3O87Oü_}.6;<6.þƒ‹=A@AGBþö4<<43;<^ÿ윪 1@T`œ@#UƒAA/AAA[ƒK K K 7G1Ub?b>H(¸ÿÐ@: H( H((VaXŒPP ` p  PP^Œ@FPFFF'$P+8R+2P/]??3á?9/áá23/]á9/3/]áæ2/++á]öá2299//Íá3/]á10>73#'##".5467754.#"'6632%2>55#".54>324&#"326Ç0/*Û?MR#xR%!BN`?EtU0çì¸7Q4SB@J¶df•a0þ/=hL+ZzI a†#=T12R;!!;R20T>#u?12?981?¸=A@AGBù\˜-A*'Q{T¤°ECZ70"‰(8)YŠbý&MuOc 9Q3\V3Q88O33O87O45<<55<<ÿÿÿþVs&ˆv%R´ &¸H´%+5+5ÿÿ^ÿìD!&¨vu@ ]&qQW%+5+5ÿÿ}ÿ´qs&švR@ >&X28%+5+5ÿÿsÿ´/!&ºvP@ :&L.4 %+5+5ÿÿhþ;ÉË&69¹ÿÚ´4:%+5ÿÿZþ;?^&V9»¹ÿá´6<%+5Ùš!%@À€_/]3Ì2/Ì9=/3310#&&'#5>73šy3l46j3yDC;À;CEÙ"a77a"LQQ""QQLÙš!%@À  €_/]3Í2/Ì9=/3310#.'536673šEC;À;CDy3j64l3yLQP##PQL"a88a"ÙZo@ _/]á/Í10!!?ýÁo–!Ù{î/@ƒÿÀ ƒ  Ÿ  €_/]áÍ]2/áÜ]á10#".'332>7{,MmGImI'l0C,$A3"î=eJ)'If?+2 1( uå @‡‘Ÿ Ï ï 0  /]]å/á104632#"& =-'?,-=s<6 +:98mÙ1‡@@-ƒ?O_ƒ0  Œ?O_¯ÿŒ_/]áÔ^]á/]áÔ]á10#".54>324&#"3261#=T12R; ;R20T>#u?12?981?²3Q88O33O87O45<<55<<þB^@ €„ Ž /á//áÌ103267#"&54>73´4"-@dd/8‹î-+qhZ*K@4…ÙÑã8@#/  @H@ H /]2á3/++á3/^]Ì]10".#"#>3232673þ(OLF -0h!5J.*QLE-.i!5JÛ#+#5>73#%>73#ß//*Ç?MQ#ek0/*Æ?MQ#dôLQQ"QXSLQQ"QXSøÙ! '@ H@ ’€ _  /]í/]3Í2+10>73#ø Ç!-4lôMQP!NWV  ‰´ )f@„$$$$Ï$$@ H$$ „¸ÿÀ@' H H ’ ‘'ÿ`°Ð/^]]]3å29/í/3Í2+3/+á3/+]qá10>73#'4632#"&%4632#"&þ Ï08= Rê8)#:&)8µ8(#:&(8‡KOQ$ MPQ%60 &522560 &522ÿÿÿéݼ&$Týñÿ—¶0"¸ÿé´""%+]5?5“H‘^3@€  –ÀÐ4Ddt¸ÿÀ¶ H›/å/+]]í]104>32#".“"./""/."Ó&5!!5&%4""4ÿÿÿçM¸'(Týïÿ—.´¸ÿÀ²H¸ÿ@%g¿]]þ4++5?5ÿÿÿçP¸&+{Týïÿ—1´¸ÿÀ²H¸ÿ±@%eo/]]]þ4++5?5ÿÿÿç`¸',üTýïÿ—f@À 0 ¸ÿ¥@/%ðÐÀ¯p`P@? @H@ H++]]]]]]]]]]5+]]]]]]5?5ÿÿÿçÿìÃÍ&2RTýïÿ—G@0- .€.p.P. ...$.. %PPg¿777]]þ]]44+]]]]]]]5?5ÿÿÿçT¸'<Týïÿ—K´¸ÿÀ¶H ¸ÿÀ² H¸ÿÖ@%À° ?/]]]]]]]5++]+5?5ÿÿÿçÍ&vZTýïÿ—5@#5 66666%PPg¿???]]þ]]4+]]]5?5ÿÿÿäÿìw´&†UþÐ@À$@$ $:%+]]555ÿÿݼ$ÿÿLJ¶%Ǿ¶C@ Äô°¸ÿÀ@H/Oo@HZd_??áöá+]3/+_]]10!#¾ýú¶¦úð¶%h¶X@ [`p°ð¸ÿÀ@' H/_oŸ¿@ H [ H _?á?3+/á+]3/+]á9=/3310%!53!&&hû½»^*þ®ü.}}…1ÍJ¨[ý ða¨ÿÿǾ¶(ÿÿRþ¶=ÿÿÇÕ¶+}ÿìqÍ+S@4"[g-Ð-Ï-€-?-o-[f,`¯ßÿ'__ ?á?á9/^]áöá]]]]öá99//10!!%#"&&54663232>54.#"ìýê…Q í›£ïLLžð£›ë QûÑ4k¥rr¥k22j¤rr¦l49¡E©þêÆllƪªÄkkÅþ뫉ۙQQ™Û‰ŠÚ—QQ—ÚÿÿRd¶,ÿÿÇ¢¶.‹¶ _@/ïÿ@H@ H@ H  ¸ÿð@" /O_Ÿ¯Ïï H  ?2?3+/8]]2/839=/33+++]10!#&&'#3‹ÆþÛ.*þÙÅ绚a¨KK¨[ü`¶ÿÿÇ/¶0ÿÿǶ1Rî¶ c@> À? O o 0 p € O  _¯ _ _?á?á9/^]á/]]3/]]Î]]q2/99//]]10!!!!!5ͦýZRJü¶südN¢ ¤û’¤¤ÿÿ}ÿìqÍ2ÇÁ¶1@Ze ° o Ÿ ¯  Zd_?2?áöá]]]öá10!#!#!Á»ý{ºúúî¶ÿÿÇ3¶3N¶ Z@ [ H ¸ÿÀ@&H  / o  [/? _ _?á?á9=/3/]3á2^]3/+99//+á10355!!!Nžþn}ýX…þg÷˜f%“¤ýîý¤¤ÿÿ¶7ÿÿ7¶<hÿìºË!.;@P"gZ;!š‡z([g=/==Ð=¯=¿==_=0===5[f<";`./`!!!!??99//3á23á2öá^]]]]]]qqöá9/]]]33á]2210332###5#".54>3332>54.+"33´»I†Â~<4TxŸe/»/eŸxT5=~Â…J»]‹[.)SW9»9WT(.[‹]Ë´^™Äf={nR0áá0Rn{=fÄ™^üS;i”XN‹h<32!!5>54.úr¤j2#Qƒ_ý°b@oP.Q ìš›ë Q.PnAbý°_ƒQ#2j¤)Dºud»«—A“¤0‡¨Ço–ô¬^^¬ô–oǨ‡0¤“A—«»duºDÿÿ@w+&,jÿ R@ & %+55+55ÿÿ7+&<jÿÎR@ & %+55+55ÿÿqÿì‘!&~T@ J&>D/%+5+5ÿÿZÿì\!&‚TÊ@ F&m:@-%+5+5ÿÿ®þ!&„TD@ %&b %+5+5ÿÿ¤ÿìw!&†Tþδ"&¸ÿý´"%+5+5ÿÿ¤ÿì=´&’U²¸ÿî´,B%+555qÿì‘^=8@8G/(??HV>" P8/,P3?3á222?á3?öá]Ô2á2210%2>754.#"".54>32366733267#".'#5LiAAlQ‡f]šn<=q¢dp1 !  2"%A"&?2$ ;M`ƒ/b˜hek7ÚÌÑÍ—HÔŒÕISU#R!hy~7þ]<3… '@0"=.®þu=Y@49H3/333,G W? ?0?"GT>2P333"'PP?á?á29/^]á9?öá2]öá99//]9á102#"&'#4>"32>54.##532>54.w`£vC˜°¹?x­m`¤<¶Ey¨c8dK, NRR$PoF5`„OfMRwN%&D_1b”b•­ʺl¢m7 ýé4{²s7–L`ü’ (MoGPuM%˜(If=?^> þßJc@@HP/OH¸ÿð@   @ ¸ÿð@   ??33?3/83/8^]39=/333/8á]^]]+10%#4>733>73`" ¾%þ`½Ù Ǽ>ˆ†~4*y‰?>ýº(]ZPR]Z!Loÿì-2DQ@.F8.3H$WF@FÐFàFF=H.VE$B 88 BP)P ?3á?á9/993öá^]]ö2á9/99á10.54>32.#"#".54>4.'3268]C%3\}JDq`Q#J IQ[3*<'=A)1l~“XÁƒC=uªnj£zSþQEoYI:b‘gBoQ-´Zÿì\^9Y@7#-W; ;°;À;?;_;;4F##GV:P¿9y9‰999 1P( P?á?á9/]]á9öá3/á]]]æ29/910#"32>7#".54>75.54>32&&#"3œŠ…)F`73\QG;žmq¦l5&?R,+F3:iV5ZRM(?KGls'Hd=‡™[^3E* ")1VuC>X>) +>P2FmJ& “"&MM-@'qþoj39@.F 55À5 H))V4.P?á22/æ2/á]Ö]2á3/3105!#>54.'.54667#®¶€¸~L( -TxKG_:"+ª+"0[MY‡\.\šÉl+/(`/{™vͳš…o0Yl@#.;H*-XRK HHC%"Bl˜f”èÙl®þ^1@GU`€ G TP  ????á3öá2]öá104&#"#33>32\ipQnC¶” ER\0·¹þ¯‚‚4f”`ýÇJ”+?*¿ÒûGqÿì+'[@:%GW))Ð))Ÿ)@))$G V(PË$º$‰$™$$$$PP?á?á9/^]]]]áöá2^]]]]qöá210#"&&5466322>7!"!4p³~v¯t83o±~v°u:þ+IhE#ýË!DhJGfD#3 „ ¼þ×ÎmmÎ)¼¼)ÎlkÍþ×ü¹J”ß•“ß•KE‰Í‰¤ÿìwH5@#0   `pÀÐGTP??áöá^]3/]1032>7#".5Z?H-*$ )04>jM,HüüabŠ  N…eÿÿ®ðJúÿòÿì!.h@*") ) )P4D 0`000.¸ÿð@.)PP?á?á9/3?/83^]]3/]]]99=//339310#'.#"566323267#".'.'#¸5 +=+"2B#JiO?H.&&7%';/%‡  5å3 )C1‘ *Y‡]ü669… (@.¢!^aWMªOýÁ®þJ?@& G U `p€GT P  ?3??á33?öá22]öá221032>53#'##"&'#3djoRnC¶“ 0gHj#¶¶‡‚‚4e”`:û¶“ST.*&(#U*þÀ6ÏJ;@ GW°¸ÿð¶ ?2?3/82^]]qöá9=/331033>53#¼É ZrB¶%\šuÀJý°!cg\`Íç—£þàþõýqþojF~@N2;;*AF8--$ F H?H_HHïHHH$VG<28P9*IYi8$O9ú??9/á9]]9á22öá]]Ö]ñÀ/^]9/3á93/310#"#>54.'&&54>75&&54>7##5!#"3#¢QW--TxKG_:"+ª+"0[M²¸/Nf7er-Ok>"'!U.>7I‘sG FrQw‰1UqAN`;".;H*-XRK HHC%""ǵMcC †uHfI1™&NuN5Q7ÿÿqÿì-^RÿìÉJX@8 G/Ÿï G/ïÿ@HT PP?á??á22ö+]á3]3/]3/9/]á10%267#"&5!##57!#P/J0qqþm¶Ý‰'Ó/  „‚¾üP°JPšýNF7¤þ-^'9@!%HW)Ï)@)) GT(P"P?3á??áöá22^]]]öá10#"&'##4>32"32654&-?u§hK6¶Ax©ia£wCþ;‰‚6KŒ{}'‰Õ‘L--+.'a.þ݈ӑKK‘ÓÄÈþ¬31ÓÑÑÏqþoo^1Q@6''F   3?3_33ï33HV2(-Q7 (8H $ú??9]]á2öá]]Ö]2á3/10#>54.'.54>32.#"-I~aG_:"+ª+"0[MM„b7Iƒ³jN•268<:PrI"`yN/.;H*-XRK HHC%"Av´‚šâ•I"š  9p¤qÿì…J)6@!$!HW+++ +°+à+H V*%PP?á?á2öá]ö22á210#".54>3!!32>54&'#"->z´uk¯}DM΀éþö%A0ý!GnMLmG!OK;_‹Z+øoÀPFŠË…œÚŠ>š)]l|6Ui;7b†N×Y,c¡ÿåfJ:@$   W+;GàPP?á?á2/]áÆ]]æ^]210!32>7#".5!57fþŒ->$*($ '.5@uY4þ׆Jšý¢;R3‡ M†hsJP¤ÿì=J)@GW`€GT P?á?3öá]öá10".5332654.'3\ªd)¶ BfF’ ¶ôPŒ½mXý²VŒc6è÷G}ut==rwKþÁþÓqþ%^%5^@<1%G–¦&HW7 77à7o77Ÿ7@777GV6+P 1P$??3á2?3áöá^]]]]qqöá9/]3á210.54>746324.#">fm·†K 8N-%?-3Xt@¥‘Z’f7V¾hO6I+1%H{Y3þÚC‰Õ—S˜Š}7`3hr}Go™a0^¼ÁPÉz•ÙJþ&%b”e35WAý ;kŸÿìþ%N( @hxg w Ç  v7¸ÿÀ@H4À¸ÿÀ@ H**?*O*Ï*°%À%%%¸ÿð@)'( P#P?á??á9]]?3/833/]^]3/+]3/8]+39=/]]33]3]10233267#".'#&&#"566¼-I:1{²þs²&4%.9(C_G3ƒþ¶ÂÆ G5$>N>\=þ¨Jüøþ &B3 &FfAjýƒ>¾OY  ¤þq'L@/G&GW)0)@))°)À)))!GT( &P???3á2?3öá^]]öá9/3á210>54.'3#.53VNƒ_4¶W“Äm²o¼‰L¶6ZxBús 7332>5332>54.'3#"&'#éZŒ`2/ º /6K-.D,²cQ-K6/ » /2`Zk‹ ‹R’ËyQ‡‡HI†‡ŽRb–e4)Hb92þ΋4e–bRއ†IH‡‡QyË’RW[[WÿÿÿìwÙ&†jþâ@ &2*%+55+55ÿÿ¤ÿì=Ù&’j¶'&¸ÿÞ´2%+55+55ÿÿqÿì-!&RT@ ,&= & %+5+5ÿÿ¤ÿì=!&’T@ *&$%+5+5ÿÿqÿì–!&–TÃ!@ H&`<<¸ÿÀ@ H=554&#!#!5!!!2ž/FD' @3!i{þ»»þ¬±þ^Z]’e55_„   3WE…stý#¤¤þo1^‹Y‰iše1ÿÿǾs&av3R@ &> %+5+5}ÿì˜Ë&_@@HÊ#p#€##¸ÿÀ@$ H##(¿([f'_¯ $_  _?3á?á39/^]áöá2]3/+]]99//+10"!!3267#"&&546632&&_ xM tý†@v«qY N'NUa;¤ðLW©ú¢lÄON?”'Az¯o¢‚ÉŠH#¢lÆ©¦Æn,*œ .ÿÿhÿìÉË6ÿÿRd¶,ÿÿ@w+&,jÿ R@ & %+55+55ÿÿÿHþ{s¶-ÿéß¶*5µ%+Z ¸ÿø@SHZ#™#à#´#Ä#Ô##P## ###/[g7Ï77@ H65_%¯%%%+_#`+`?á?á?á9/^]á3/+]öá99//^]]]^]38]á+2á210#!!#"&'532>7667!3232654.##ß<~†þ¿þÂ!"5OnN#J: 0>* '+“m˜Ï~7ýww®¤.[‹]X¬ažp=qôìÔQg¢o; š ?bu69È:ªý˜Bq˜þ„ˆFa<Ç ¶!S@2Z #####ÿ# Z d"!_ ¯   ` ?3á?39/^]3á2öá2]qöá9/3á210#!!#3!33232654.## <~†þ¿ý¸ººHºm˜Ï~7ýww®¤.[‹]X¬ažp=ªýV¶ý˜hý˜Bq˜þ„ˆFa<¶s@MZUoô °tZ0@PÀ°gw^?O_ _?3?9/áá2/]]]]]3/]á22/]]]^]öá10!2#4&#!#!5!!#Z]’e5ºi{þ»»þ¬±þ^1^‹Yýòöstý#¤¤ÿÿÇ¢s&´v‰R@ &" %+5+5ÿÿÿì®i&½6#R@ '& ,"%+5+5ÇþÁ¶ c@AÆÖZÅÕ¨–HG: Ze ° o Ÿ ¯  Zd _/33/á?3öá]]]öá9/]]+]]]á]10!!#!3!3ÁþZ±þ]º…»þ¶úðÿÿݼ$Ç3¶K@._[g_Ï Zd_ ¯   _`?á?á9/^]áöá2^]öá9/]10#!!!3232654.##3<~†þ–ý¦–˜Ï~7ýN ®¤.[‹]¬ažp=¶¤þ7!3!! °üd°q/VMA0eÂþƒþú.=FM'þþ'UÈÙæèãiúðjL¹ÊÑÈ·KÿÿǾ¶(¶Ë@Nf v † 7 G W  G ÷  Ziy‰8HX¨¸™†Wgw  p € À  ¸ÿÀ@& H  ð¿Ïß @¸ÿð@ ?33?33933/8333^]]]]]]3/8+]q3339/]]]]33]]á]23]]10333###%ýíÍ ³ Íýí!Óýî³ýîÓòÄý<Äý<Äý<ýåýåýHÿììË9h@?'[!0!!0[ g;Ÿ;p€?O_: `ª!x!!!!/,`5`?3á?á39/^]]]á93/]]]öá9///9á10#"&'532654&##532>54.#"'>32Ñ3[}KWŠ^2CˆÍ‰nÀU+`cc.²°Ïº¿°\Ža2%D_:n©K\&btƒGm¦q9`IxX9  9YwH` t@"-ª$ ”‡‡—'He=6S:C6}6)6a…ɶk@ Z eЯ 0 Zd ¸ÿè³H ¸ÿà@# H  &  H H ) ?22^]++?3]++3öá22]]]öá2210333#46767##É®ÌÕ¬ý1×¶üà>CC JL´úJ9ŽALMûFÿÿÉi&²6‰R´&¸ÿë´" %+5+5Ç¢¶ ^@!   ° /  — V  ¸ÿø@ H Zd ?2?399öá23+]]]]]3/8^]338310!##33¢Ûýººº5ÏýËåý¶ý<ÄýBÿé“¶b±¸ÿø@;HZ‰à´ÄÔP€Ze!!!! _` ??3á?á3/^]öá9/^]]]38]á+210!#!#"&'532>7667!“ºþ…!"5OnN#J: 0>* '+ÐqôìÔQg¢o; š ?bu69È:ªÿÿÇ/¶0ÿÿÇÕ¶+ÿÿ}ÿìqÍ2ÿÿÇÁ¶nÿÿÇ3¶3ÿÿ}ÿì˜Ë&ÿÿ¶7ÿì®¶!o@!`p°ð¸ÿÀ@ H### ¸ÿð@ "ç÷Ö¸ÿà@ H _ ?2?3á9+]]3/833]3/8+]393310#"&'532>733>7®þT&Spœo3Z%%Y45RB8ýîÌ  7¶ûú]¤{H¹5V??ü×  !ÿÿhÿìºËsÿÿ`¶;Çþq¶ ;@!Z Z ° ¯  Zd _?3/á2?3öá]]]Ôá3/á10%3#!3!3Á°°üº…»¦ýÙ¶úð¦¶H@Zep / Z` p   °  ¸ÿÀ@ H _ ??39/á3/+]á]]]öá210!##".5332673ºsÃb]’e5ºi{Z¹pºV,.1_ŠYGýÑst((ÆÇ3¶ `@BVZ†–¦gw Ze   P p € À Ð à  Zd _?á2?33öá^]]qöá9/]]qá]10!!3!3!33ù”ººº¶úðúðÇþã¶q@KV  Z † – ¦ g w   ZZe Pp€ÀÐàZd  _?3/á22?33öá^]]qä2/áá9/]]qá]10%3#!3!3!33°°ù”ººº¦ýÙ¶úðúðÕ¶R@3 Z[g/?_¯_ ¯  _ `?á?á9/^]á3/^]qöá9/]á210#!!5!3232654.##Õ<~†þ•þ¬•˜Ï7ýN ®£-\Š]¬ažp=¤ý˜Bq˜þ„ˆFa<Ç϶I@+[ZeŸ Zd_ ¯  `?3á?39/^]áöá2^]öá9/]á10#!33232654.##33<~†þ–º–˜Ï~7ýN ®¤.[‹]“»¬ažp=¶ý˜Bq˜þ„ˆFa<ýV¶úJÇ3¶H@.[g_@H@ H Zd_ ¯  `?á?9/^]áöá2++^]ö]á10#!33232654.##3<~†þ–º–˜Ï~7ýN ®¤.[‹]¬ažp=¶ý˜Bq˜þ„ˆFa<;ÿìDË$Q@/ [  g&¿&Ï&ß&%_ªx__?á3?3á9/^]]]á3/]æ99//á210"'6632#".'532!5!.º^”?NOÄl¢ô¢RR¥÷¤:aVN'N Yí÷ý‹q Gs'. œ*,mÁþö³þàÊm¢#¢o°z@ÇÿìžÍ.^@<[  %[g00?0o00ï0ÿ00@H Z d/*_ _   _?á??9/^]á?áöá2+]qöá9/]3á10#"&&'!#3!>3232>54.#"žO›å–—á˜Pþ®ººV U˜Û‘•åšOûú1glmf00elmžg1Ý©þêÆld· ýV¶ý˜’ì§ZkÅþ뫉ۙQQ™Û‰ŠÚ—QQ—Úò¶›@ æö¸ÿø@$ HZ e?Ÿ¿ß æö¸ÿø@ HK@ H¸ÿð@&[ßïÿ@Hf`` ?3?á9/3áö^]+]á3/8+]3+]q]]ö93á2+]q10#.5463!##"33Bþ²Û}7cK,ýûu»¼JsO)'NwQ´\ý¤>aˆ]ÆÉúJ\»>aDBiJ(ÿÿ^ÿìœ^Duÿì!#';I@,-H W=Ð==@==7HV<2P(P#  H Q?á+?á9/3áöá2^]]]]ö2á1046676673>32#".2>54.#"u1h¢q}òf!2{}u,>dH) >Tk@c–e2E|®hk­{Bá4&#!32>ö{k8aG)/eŸqþ9ÅSŽg;‚€ÿ8]C&mlþõì>\?5hu$=[?CtV0J@kýÇ\Hþ¨*E M@þ×$<® J/@ PGTP??áöá]]]3/]10!#! þZ¶\°üPJ)þƒ7Jy@F /? ¸ÿð@9¢ ²     GF U¯Ï0/FO Pû?2?á22?á3/á]]]ä]2/áá99//]]8]]á310#!#3>7!3!#7®ýN®VAcC"—þ²À$:Q4þƒ}þƒ_ßóüP$g×ÑÂSÿÿqÿìá^H¾Jœ@ FÖ ¹ É ¦ —  ¸ÿÀ³H¸ÿÀ@$ Hß0p ¸ÿð@  ?33?33933/8333^]]]q3/8^]++3339/]]]]33á231033###3¤ ÅþXÎÎþC¤þDÏÏþXÅ5ýëýëýË-ýÓ-ýÓ5DÿìR^9_@:F99 4G!W;°;À;Ð;o; ;) V:8PÏ9‰9™9999/P&P?á?á9/^]]]á9æ2]]]öá99//9á102>54&#"'>32#"&'532>54&##5q=dH'slGK?(MRZ5Vi:3F+,R?&5l¦qm¯:LWa37`F)…Ї'@-MM&"“ &JmF2M;) ,BZ>CuV1#"¦*E3^[™®;J Q@ HFU    À Ð ð  ¸ÿÀ³H ¸ÿð@ H F T   ?33?33öá2++]öá2+103##Z é¬ ýþêJýdü˜û¶‡üjJÿÿ®;&Ò6%´&¸ÿÿ´ %+5+5®åJ A@#   GT  ?3?3933öá22]3/8]333103##3úÄþaÆÎþK´´JýñýÅ-ýÓJýëÿò°JL@1Fô€ÀÐT GU/ÿ PO??á?á3/^]öá9/]]]qá10!#!#"&'532667!°¶þþ@_‚V17YE3T°þýþ”æiƒuñoú®J ^@?FU"Ï"@"P""FT!;K[ )4DT& ?333]]?33]]3öá2^]]]öá210!##.'#3>73¡þø‡þ÷¢Ý  Ù9;6ýP° 1;>üJýG"C<21;A!½®;J [@GU 0 @ P Ð ð  ¸ÿÀ@#H G T P멹/ ?2?39/^]]]áöá2+]qöá210!3#!#d!¶¶ýß¶Jþ9Çû¶éþJÿÿqÿì-^R®J-@GU  ` p € GTP?2?áöá]öá10!#!#!d¶d¶þJû¶°ÿÿ®þ?^Sÿÿqÿìo^F)^JX@< ß p € _ Ì¿G`pаÀP??á2/]3/]]á2/^]]q]]]q10!#!5!^þÁ·þÁ5°üP°šÿÿ þßJ\qþ#")t@K' F I¹ É — §  #HW+ ++Ÿ+ï+€+o+@+++HV*&P'P  ??3á2?3á2?öá^]]]]]qqöá9/]]+33á2210#.54>734&'66!q½ˆLG†¾wªr¿‰LG…Áyªþ œ®X}P%9›ª¬™Z UÆ}}È‘V þ$Ü VÉ~}ÇU ºü»Ò< =g‘^ºÌüÆÑÿÿ#ÛJ[®þƒ²J D@+GG   ` p € À GT  Pû??á2?3öá]Ô]á3/^]á10#!3!33²¶ü²¶ø¶ þƒ}JüP°üPšþJ1@ G U`€GTP  ?2?9/á3öá]öá21032>73##".5P¸-PLM*¶¶,PU^:NzR+Jþf®,Õû¶é0 /TsD¦®FJ f@CG¶ Æ © t „ ” g  GU  € ð o @  / GT P ?33?á2öá^]]]]qqöá9/_]]_]]á10%!3!3!3Õº·úh¶»¶š°û¶JüP°®þƒåJw@N G¶Æ©t„”g GG¿ @€àð/GT  Pû??á22?33öá^]]qqÄ]2/]áá9/_]]_]]á10#!3!3!33å¶ú¶»¶º·Ÿþƒ}JüP°üP°üP)üJ\@>GWP`à?G  ° ð   P `    P¿ P P ?á?á9/]á/^]3/]qá2]]öá10!2#!!5!4.#!!2>HÔÌ/eŸqþ9þ˜!A`@ÿ7]D&‡œ›M|X/°šüø1B(þ¨)B®PJ a@@G pÐ G U¿Ïßÿ G TP¿ P ?3á?39/]áöá2^]]]qöá9/^]á1032#!3#332>54.#dìÔÌ/eŸqþb¶ì¶¶üÛ8]C&!A`@‡œ›M|X/Jû¶Jý¤þ¨)B01B(®J @@)GW p°ÐG T P¿ P ?á?9/]áöá2^]qöá10!2#!3!2>54.#dÔÌ/eŸqþ9¶8]C&!A`@‡œ›M|X/Jý¤þ¨)B01B(7ÿìB^&^@> H"W((¯(@(/@ HX' P啕/QQ?á?á9/^]]]áæ+q2]]öá29/10"&'532>7!5!&&#"'>32BY|66ƒRItR0þ#Û ’Œ;<85@HN&f³„MQŒº¢%(T„\𦗠 š  >‰Û’ÔŠC®ÿì^&r@KH O !HW((/(ï(ÿ(G((/( G T'$P P啕,   P?á??9/^]]]]á?áöá2]]]qöá9/]q3á10#".'!#3!>3232654&#"@x«jb¢uFþï¶¶ Hv cb§yDý)€Ž~€Ž~'‰Õ‘LB¾|þJþ9t±x>K‘ÓˆÑÓÓÑÑÏÏ#‘JS@GUo¸ÿð@F@HVPP ?2?á9/á2ö+á3/83^]öá293103#.54>3!##33#"éÆ#-TA(8dŠS¼¶ôÝ%AZ6ÛþkhÍ 0LlGNyQ*û¶°T0F.f_ÿÿqÿìáÙ&HjÚ@ 1&(<%+55+55þ1S@2 G-U33`3€3#"GT2!O#P)) ))P?á???]á39/3á2ö2á222]öá910"&'532>54&#"##5353!!3>32ð0?6#.#ipQnC¶œœ¶{þ… ER\0·¹"Hmþ ” 'A3D‚‚4f”`ýðÕ‰¶¶‰¸+?*¿Òü¾M{W/ÿÿ® !&ÍvÔ@ &E %+5+5qÿìo^"O@1 $$¯$@$HV#P啕/Q Q?á?á9/^]]]áöá2]]Î29/10".54>32.#"!!3267Re°‚JL…²fN•268<:‹’Ûþ# ’ŠQƒ66{?‰Õ–Û‰>"š  —¦š¸¤%¢ÿÿZÿì?^Vÿÿ uåLÿÿÿî%Ù&ójþ»@  &%+55+55ÿÿÿ¼þuåMÿòJ*k@E F%G Ô $  GW,,/,?,_,,¿,ï,+$P¿% PO%P ?á?á?á9/]á3/]öá99//]]]]á2á1032#!##"&'532667!4.##32>‡ìÔË.eŸqþbÙ@_‚V17YE3+Ù!A`@×Û8]C&‡œ›M|X/°þýþ”æiƒuñoúüø1B(þ¨)B®uJ!S@0GG W#ï#€#?##GT"P¿P?2?3á9/]3á2öá2]]]]öá9/3á210!332#!!#4.##32>d϶ìÔÌ/eŸqþbþ1¶!Aa?ØÜ7]D&Jþ9Çþ=œ›M|X/éþJüø1B(þ¨)B!S@2h G!U##`#€# G T" OP  ?2??]á39/3á2ö2á222]öá9]10!4&#"##5353!!3>32\ipQnC¶œœ¶{þ… ER\0·¹š‚‚4f”`ýðÕ‰¶¶‰¸+?*¿Òý\ÿÿ®å!&Ôv@ & %+5+5ÿÿ þß&\6±@ (& -#%+5+5®þƒJ Y@=Ö·ÇGµÅÕv†JZ GU  ` p € G T P û ?3??á3öá]öá9/]]]á]]10%!3!#!3dø¶þ³·þ ¶š°û¶þƒ}JǾãD@,Zßïÿ ° Ð  ¯ Zd_ ??3/]áöá]]3/]]á103!#°ýú¶-þ-úð¶® ‰0@G GT O??á3/]öá3/]á10!#!3 þZ¶¦¶Áü?J?ÿÿþs&:CçR´+&¸ÿ¬´1+%+5+5ÿÿã!&ZCL´0&¸ÿž´60.%+5+5ÿÿþs&:v R@ 7&d+1%+5+5ÿÿã!&Zv@ <&i06.%+5+5ÿÿþ+&:j9R¶4&¸ÿÿ´+?%+55+55ÿÿãÙ&Zj¬¶9&¸ÿÿ´0D.%+55+55ÿÿ7s&<CÿrR´ &¸ÿ¤´ %+5+5ÿÿ þß!&\CÿK´#&¸ÿ¤´)#%+5+5RÑ®y¹ÿÀ@  H¹½?á/3/+105!R\Ѩ¨RÑ®y¹ÿÀ@  H¹½?á/3/+105!R\Ѩ¨RÑ®y¹ÿÀ@  H¹½?á/3/+105!R\Ѩ¨ÿüþ1NÿÓ*@ ºïÿº /]á/]á/33/310!5!5!5!Nü®Rü®Rþ1‹Œ‹ÁP¶ %@__o¿Ï ˜ œ?å/á/]3]10'>73%'.4‰Á6z|{8=„ƒ|5ÁP¶ %@_ ˜_o¿Ïœ?å/]á/3]10#>7B'/3‰¶7y}z8<„„|5?þøyî 5¹ÿÀ@ H ˜_oϸÿÀ·Hœ¨?å/+33/]á+10%#>7j'/3Šî6z|{8=„ƒ}5ÁR¶ +@__o¿Ïߘ œ?å/á3/]3]10#.'7î‰4.'¶5|„„<8z}y7ÁѶ b@H¿_o_o¿Ïߘ ˜P`p°ÀÐ_o¿Ï œ?3å2/]33/]á/á3/]3]]]10'>73!'>73¦'.4‰ý¸'.4‰Á6z|{8=„ƒ|56z|{8=„ƒ|5ÁѶ b@H¿_oP`p°ÀИ_o¿Ï ˜_o¿Ïßœ ?2å2/33/]á/]á3/]3]]]10#>7!#>7B'/3‰H'/3‰¶7y}z8<„„|57y}z8<„„|5?þøúî ~@QÐàð¤´Ä 0@`p€P`pÀИàð_ ˜_oÏ߸ÿÀ@ Hœ ¨?2å2/+33/]á/]]á3/]3]_]]]10%#>7!#>7j'/3ŠH'/3‰î6z|{8=„ƒ}56z|{8=„ƒ}5{h |@R   à ð o  0 @ Àä ô Ö w j T E &  À  6¾ ¿ Â/?ö222á222/]á]233æ]]]]]]]3/3æ3]]]10%#53%hþµ7Ù7þÉ77Ù7KÝüü´¡þ_{}°@q °ð @P À ôæ‡zdU6FÀ¾  P ° ¿  ¿Â/ö222á222?ö222á222/^]q333/á222233æ]]]]]]]q23/33æ2]]]q10%%#553%%1Lþ´7Ù8þ´L//þ´L8Ù7Lþ´/ð´þ‡y´"´xþˆ´þí–åmòF@$/_oÏïÿ_ o Ÿ ¯ ß ï  Ð¸ÿÀ@  H/]Å]/+]Å]]]104>32#".–$?V21V@%%@V12V?$ìGd??dGFd??d“ÿãÛú'9¨@v;$;û;ä;»;Ë;¤;‹;d;t;K;4; ;–fv(–$2û2à2Ô2»2¤2‹2r2f2K202 222 –àðTd -›7#/33í22/^]]]í/^]]_]]]]]]]]]]qí9/]í]]]]]]]]]q1074>32#".%4>32#".%4>32#"&“"./""/."%"./""/."%#./""/6Io&5!!5&%5""5%&5!!5&%5""5%&5!!5&%5"BfÿìôË ';?I]‰@\@´TµJ´2µ#´( >0<@<°<0(@(><((<>E´J_?_O____¯_´ µ´ 0G¶Y·C¶O?>%¶7·!¶-¶·¶?áôá?áôá???áôá/]áôá]Þá9///]]]áôáôá1032#"#".54>3232#"#".54>32#32#"#".54>32úGPœœPGÇ$JsOIpL&#IqNKqM'¬GPœœPGÆ#JsOJpK&#IqNKqL'ÿüÕž,—GQ››QGÇ#JsOJpK&#IqNJrL'¥¥JH£¥l¬v??v¬llªu>>uªýJ¥¤IH£¥l«v??v«llªu>>uª’úJ¶ü¥¤IH£¥l«v??v«llªu>>uªÿÿ…¦J¶ ÿÿ…¦²¶RsüÇ<±¸ÿÀ@ H?Ÿ¯ßïÿëŸ//9=/33/]á]Æ+210R5uîîuþË)žNþ¤þ¤N›RsüÇ?@(ëßïÿ ?Ÿ¯ßïÿ?//9=/33/]3]/]]á10'7üþËuííu5þeN\\Nþbÿÿ“ÿãb¶'Ñ µ/11]]þ h¶±¸ÿð@ ??/82/8310#hüÕ+¶úJ¶j“ÇA@à`Ààð à ¸ÿÀ@ H ÀäÞ Ü??á3Ì2/+á2]]Ö]á104&#"#33632??-A*i @‚å¦QD4WAþ¦XeúþP`¶v@H   0Z ßÀ`_?o@H _ ??á99//+^]á3á2/^]3/]3á22]3/]99//10!!##53!!!!Ã$þܳ°°ðýÃýê‰þú‰'¤ýü¤D#É0‡@ o+'###¸ÿÀ@> H22)%@ H%u&&)u* *o&Ð***?*O*¯*¿*&*&*ts?á?á299//]]]3á23á2/+33]3/+399//333á22102&&#"!!!!!!5>55#535#5354>šj®BB8K0RY@+¦š )DaC‰ž‰ÝW‰_2–ÿì¶¶*5y@1on$ÿ@H¸ÿÀ@, H777@ H+#n$|6u+s"""$5s%$u ?á??á99//á3á2öá2+]3/++]99//3á22á10%2>7#".5#57733####3232>54&##;$#P?5X@$œœAkÑÑ4þŒ4|̘/²û†¿{:ý½]‹[.¤®9Š "FjH¿RM½Ó‰þVLN‰[¨MýǶ9m þg GqQމ?ÿìJË9†´(6¸ÿÀ@K H;;,## o."c(((:#`$$,`--_$$$ï$ÿ$--/-_-Ÿ-Ï-$-$-7t3_?3á?á399//]]3á23á23/]33á2233]3/+39910"!!!!3267#".'#53&45465#53>32&&Ev^C°þAþ’"¹•K‡;;…[s¶‡X¤”” X‡¸ra OP3w'4c[‰  )‰¯¸ ¢I‡Áy‰.‰}ÊN+1’+ÿøÛÁ+H†@ 9F´¸ÿð@JpFF FÐFßFF1"´ïÿJ@´111 1à1ð11Bü,ý=ü6'üýü ???áôá?áôá/^]qáÞ]qá9////8]]83á3310##".54>3232>54.#"".54>32&&#"3267 üÕ+n-PpD?nQ/,PqD>nR/þ3&<+*<%%<*+<&ýËEyZ45\}H3d !U"g_Â3Z##c¶úJ¶û˜SW--WSSW--WS3V>##>V34U=!!=U’&R€Z_„R&k tvåkwÿì{Ë-:U@2.p#@H##<5(n 0€6v((0vu  ?3/á?á9/33á22/]Å3á2ÖÁ9/+á10%2>73#".5556674>324#">o 9.d&FiF:jQ0.a14_-@hK6W=!5\|G$7cf ) 6N3w2R;SƒZ0%Tˆcç yî;lS1*OoEc¦†h&þÓ0Q;!!¼2E*þj!Nbylj¶+7;º@rZ99,á"°Ÿ0@"" 2á::/:::ïg=o=¿=Ï=@=  Z d3232654&#"5!¨Íý‡¦Ïv¤á)MmD?kM,)MmD>kN,þBHQQGGQQHcðºMLAŽ9üç¶ûLLJ CC> ü¹S‚Y//Y‚SSX..XSqssqrmmý““%åP¶ £@]Ä ÄÀÐào0@P ÐÄŸ¯""/"?""@H"@HÏï H ȸÿà@ H?333/333+3á23+3/]++]Ö]á2229///]]]]]]á22á10##5!###33#4>7#hÄ Ç@º{º´¿²Ãåellý›%"þIÑýÙ'ý/¬ ## ýÛÿÿN¦ÍvfÿÝ‹H"/;@!#J1/JN+/;///)N N?á?á9/]á9//]á2Þ2á10".54>32!32>7.#"y‚ƆE,Lfv€?qÂŽQüÅ@MX.Jt^M"H$SnÌ;M]53WI<#^Ìnc ~\<O“уþœ," 326454&#"5>322>7.#"; AeбmjŒS"2Qs™b[“-‹‰DCACKO%}¦c*ýž5^QC5% );I*>fO:%-F¦jáÔ¼RBnL<ƒe=OE* ¾É ®  Y–Äüs,Mhz…C(E30Qhon/2V@$)b¶X@ [`p°ð¸ÿÀ@' H/_oŸ¿@ H [ _ H ?3+?á/á+]3/+]á9=/331073!&&'!)¾»ÀûÇw -*ú¦qEú¹oša¨KK¨[ýÇþ'¶7@Ze 0 @ p €  ¸ÿÀ@HZd_?2?áöá+]öá10!#!mýº`þüù¢ø^Jþã¶ \@ [p ¸ÿÀ· H  ¸ÿà@ H  ¸ÿÀ@H _ _?á?á9=/3+3/33+23/+99//]á1055!!!Jpý Hü¼:ý°›þs’+r¤ý ü¤f‡2@–‹yVK8 ­³?á/]]]]]]]]Î105!fœ‡––%ÿòÅ ,@®//9/á//8399//33310##5!3s…þë´)å’ ýi¬w‘1#3Cv@OIH ðE/E_E:'J'j'*77'?w/—//ª 0x??«4*®:$®7'   Ÿ /]]33/^]39á2á2/á]/]á]9]]]]10]]#"&'#".54>32>32267&&#""32>54.1+MmB]›AFNS+AnO-+NoCUž>DOW0BmM+ü{?l41kE(@,,A|?k73lD'@-.@Í?rW4is0O8,RuHAsV1kp0N8-RuùWa^Z3D&$B2jWa]\3D'&C1þ#7@#%Ð% ¬  ®®?á?á/]3]]á2]]]102&&#"#"&'532>54>ƒ"K=$3B'2Y|J$K>#3E*/Wy “ 'AT-ú×^†V) “%@T0'^†V(f{%#GK@.AII.  @)­1¯C­ ¯­ß ï ÿ  @H ;­.C³?3á3/+]á3õ2áõá3/]3]Î210.#"563232>7#"..#"563232>7#".%7-)<;8d”27C/%7/(<;8c•27C/%7-)<;8d”27C/%7/(<;8a—27C¼ !,¢l  !,¢l ®  ,¢m  -¢l f¤¦@   ¸ÿð@O    @Æ»©†{hB9  ­­/ ðo/]]33/^]]q3á223á233/]]]]]]]]3]Î299//883}‡ÄÄÄÄ3‡ÄÄÄÄ10#5!!5!3!!!'^ø>yþIü…ŠiúþÁ{ºþ‰º–•;à•þü–þê9fÝ R@2 @ @o0 Pp€Ðð?/^]]]q33/]]29=/33/]3]Î210%5 5!üdœý!ßüdœî¨fá þ”þ¾þq––fÝ R@2  @ @o0 Pp€Ðð?/^]]]q33/]]39=/33/]3]Î21055!fâýœüdœBj¢þfþXî––m?à ]@6  ¬ ÿ @ P € ° À  / ª­ ­/á?á99=//33/]á]]]Þá99333310#3 ?þÂL þÏþÏ1áýßäýþýþÿÿ&IL¢$@¯P¯/@//]]]]]]]55ÿÿ&IO¢"@¯P¯!@!!]]]]]]]5ÏÙÍ?@) ¯Ï0p 0  @ HŽ/á3/+]3/^]3/]]]q10#".'332>7Í1]dgŒX)ª2L5,I6"NvQ)'PwP9I)+H5ÿ¼þdJ#@ G  T P?á?æ2/á]10"&'532>53B0?6#.#¶"Hmþ ” 'A3ôûM{W/‡Íy @ …? ’?å/3Ý]á10>73#‡ ´).`çMQP!NVV oþ;uÿƒ @ … ’/å/á2/310>73#o ²,6bþVLTS!MWV!}Ùƒ! @ …’ ?å/á2/310#5>73ƒ ²-7bLTS!NVV!%9Ç *@á O@ Há åÞåß?á?á/á+]Þá1032654&#"#".54>32¸GQNNNNQGÇ#JsOJpK&#IqN•š¥¡Ÿ§¥ŸŸ¥lªu>>uªll©t<ì J¼ F@* á _@ Háå   /  åÜÝ??á9/^]3á2/á+]9/33á210##5!533!5467}þ‰y}þô › ÀÀoCýÍÃ*c1 %*(ð;7f¶$J@!"" á&O&&&@ H ¸ÿÀ@H å!åÜåß?á?á9/á/++]Þá39/333102#"&'532654&#"'!!66B=kO-¦¡?y,<=;U__Y %'%C!ºþ¾9m#DeAŒ LXMU+¨{×%9Õ-8@  !á/O///@ H)á $äÞåß?á?39/3á/á2+]Þá910#".54>7366322654&#"*NoD@nR/E~²n-O`@ cJ6Z@$þÛDTMG'?--=fAoP--X€Sn³m'm@O`;+1%Hi÷\VRZ)7,I5/Jd¶8¹ÿð@áO@ HÐàðåÜÝ??á/]+]Þá9/810!5!@þb5þ¿Jñ{düø19qÇ%4Dw@"8á!Bá =2!!,áFOFFF@ H&á¸ÿÀ@'H =2K2[2k2›2«225¶=Æ=Ö==)åß5åÞ?á?á9]9]99/+á+]Þá99//9áá102#"&54>7.54>32654.''">54&R4^F*'4">0*Mi?‹–*8.#+H_bJHJK'7!AB”8? -, AÇ7T8%>2(-7E*9]B$ƒs*E8++5>%8S7ýh;FF;0' "Mè76*$ #,67!9{É%5;@"1à 7O777@ H)á,ä&å!Þ åß?á?á9/3á/á+]Þ9á210#"&'532>7##".54>32%"32>54.{!Z¢@6Sl@ $0=&<`D$)KlCArT0þËDVJL&?-*>B`»“[} 5Wr=$%GfABoQ-/a“¬^XLX)3$H;$TþÁî #'+/37;?CGS_oxä@Y4, 8WQzpotgdd‚~kpQÀQÐQQokkoQ]‰OŒ_ŒŒŒD($ ]PK`KKKA=1 *BF>&2†oygppz`zoTHHpo€oaoo¸ÿÀ@% Hoo`ZNNŠx`/`?`o```, 95!/^]3333/33339/]333/33/+]]3/339/333/3/3/3/3/33333/]3/33333/]39///]333/33333/3/3/310!#%5!#533!5353!5!!5!5!#3#35!#35!35!#35#3#3"&54672'2654&#"32##32654&##32654&#532653#"T/ÀÎ0mùoÀÃmýIûáþò·mmmmûÂü0ooÀwú¨ooooþmmú™‡‡‡‡H??HEBBŸ¬mp--83m^Ï{B.$)0;J1%&4 %1}h_=¾0oÁÁoþÐÁù/ÂmmÂþÑmmmmþooú¨ú;mm¦Jooooü/yýhþŠŸŽ‘›œ‘ŽŸhg^^ff^^gêCS1D D:QYb" "ãš+% *üf$2’þr^dTþÁª#/P@(##$ *0*@***O$$$ ##0---''/3/33/3/]3//3/3/33/]3/]3/310 54676654.#"663232654&#"þ¬üTüVë!LcM1[ƒQ+ZWR"RD~8?>'REJGFDGGDFGüVüW©û/,>:LƒYEkJ'#²".:/1DA5yP;þí>II>@IIÿÿÿ¼þU!&7Lþ»ÿÿÁP¶ ÿì²+ Ou@GM C>;GCC;GOQ Q?Q3G "P)>OONN))0)@)P))N)N8PH8P?á?á99//^]3á2á/]á]]3/]3á9/á999910.#"#".54>54&#"'6632324&'.54>323j 9M[0LV.j«<@ăo’V" 6%%d62E, 0N:¤¤®ò–C'QYh¡uKÑlªt=XH8iR3Š:ŸþîÉs7_}E(]YK-! 2D(#V]a-*J620>KxšQCpR.Tœß‹‰HÃ"@¯$ï$$@ H"«!˜!@!!!!¸ÿð@D!!   HŸ ¯ } k Z O + ;  Zw‡—O! ?Á??99/^]]]á2/]]]]]]+qq93/8]]]]]3+]10>7>32&&#"#3=?</5@)#+  # (2662»þBËÛK¨ ‰,(=' ‘*#Un€……<ýã/‡ÿì`J<@Gg//GÕ,·,©,š,,,7H«»>>T>d>„>¤>´>@>0>>"H0@P¸ÿÀ@H--'P 2'P?2á2?á229/9/+^]á]]_]]q3/]á9/_]]]]á]910"&54>7!57!##"&'#32>55332>54.')¶Ã +þë†Èó&ĵj‹ ‹º(6K-.D,³bR-K6$êó9}~{8JPš8{~}9óêW[[WÄ6vyz:bƒN!)Hb9°°‚Š!Nƒb:zyv6ÿÿÇ/u&0vyT@ '&K %+5+5ÿÿ®‡!&PvÇ@ :&z-3+%+5+5ÿÿýÕݼ&$[ ·%+55ÿÿ^ýÕœ^&D[¼ ·=3 "%+55ÿÿþÓÿìÃÍ&2R\þ@?¶-5¸ÿÀ² H¸ÿ!@55 %°€P@]]]]]]55++55?55sýÕ7ÿƒ:@(ƒ?O_ƒ0  Œ0@P ðŒ/áÔ^]á/]áÔ]á10#".54>324&#"3267#=T12R; ;R20T>#t@12?981@þ®3Q88O33O87O45<<55<<“hÙÇ B@ 0@¸ÿÀ@ H+      ?O_/Ì]99///Í]]2/+3Í]910>73#%467#"&° Ï08>Rþãxz<9%2/:E‰KOQ$ MPQ%xNsL.%'Fÿÿ¹&I'I¢LD8@&9p9@9/999à°¯KpK@KK]]]]]]5]]]]]55ÿÿ¨&I'I¢OD6@%9p9@9/999à°¯=p=@==]]]]]]5]]]]]5Ëê!úÀ Ü$ÿ®,)7R9R:f;)<R=)FÿÃGÿÃHÿÃJÿ×RÿÃTÿÃW)Y)Z\)‚ÿ®ƒÿ®„ÿ®…ÿ®†ÿ®‡ÿ®ˆÿ\Ž)))‘)ŸR¨ÿéÿêÿëÿìÿíÿôÿõÿöÿ÷ÿøÿúÿÿ)Á)Âÿ®Äÿ®Æÿ®ÉÿÃËÿÃÍÿÃÏÿÃÕÿÃ×ÿÃÙÿÃÛÿÃÝÿÃì)ð)ò)ÿÃÿÃÿÃÿÃ$R&R6f78R9):R;)=)?)Cÿ®_ÿ®iÿ®qRyÿÃ~ÿÀ)‚ÿÊ)ŒÿÃŽÿÃÿÑ)“ÿÔ)–ÿÙÿÛÿÃR¤ÿš¦R¨=ªÿ®®ÿ…°=±µÿ…¼R½=¿)ÄRÏÿÃØÿÃÛÿÃÜ)Ý)ÞÿÃêÿÃíÿÃúfûüfýþfÿR)(ÿ® $ÿ® ,) 7R 9R :f ;) <R =) Fÿà Gÿà Hÿà Jÿ× Rÿà Tÿà W) Y) Z \) ‚ÿ® ƒÿ® „ÿ® …ÿ® †ÿ® ‡ÿ® ˆÿ\ Ž) ) ) ‘) ŸR ¨ÿà ©ÿà ªÿà «ÿà ¬ÿà ­ÿà ´ÿà µÿà ¶ÿà ·ÿà ¸ÿà ºÿà ¿) Á) Âÿ® Äÿ® Æÿ® Éÿà Ëÿà Íÿà Ïÿà Õÿà ×ÿà Ùÿà Ûÿà Ýÿà ì) ð) ò) ÿà ÿà ÿà ÿà $R &R 6f 7 8R 9) :R ;) =) ?) Cÿ® _ÿ® iÿ® qR yÿà ~ÿà €) ‚ÿà Š) Œÿà Žÿà ÿà ‘) “ÿà ”) –ÿà ™ÿà ›ÿà R ¤ÿš ¦R ¨= ªÿ® ®ÿ… °= ± µÿ… ¼R ½= ¿) ÄR Ïÿà Øÿà Ûÿà Ü) Ý) Þÿà êÿà íÿà úf û üf ý þf ÿ R ) (ÿ® -{ ö{ £{&ÿÃ*ÿÃ2ÿÃ4ÿÃ7ÿš8ÿ×9ÿš:ÿ®<ÿš‰ÿÔÿÕÿÖÿ×ÿØÿÚÿÛÿלÿ×ÿמÿןÿšÈÿÃÎÿÃÞÿÃàÿÃâÿÃäÿÃÿÃÿÃ$ÿš&ÿš,ÿ×0ÿ×2ÿ×4ÿ×6ÿ®8ÿš:ÿšfÿÃmÿÃqÿš¸ÿûÿüÿšúÿ®üÿ®þÿ®ÿš7ÿšqÿšrÿðÿ×µÿ×¼ÿšÄÿ®&ÿÃ*ÿÃ2ÿÃ4ÿÃ7ÿš8ÿ×9ÿš:ÿ®<ÿš‰ÿÔÿÕÿÖÿ×ÿØÿÚÿÛÿלÿ×ÿמÿןÿšÈÿÃÎÿÃÞÿÃàÿÃâÿÃäÿÃÿÃÿÃ$ÿš&ÿš,ÿ×0ÿ×2ÿ×4ÿ×6ÿ®8ÿš:ÿšfÿÃmÿÃqÿš¸ÿûÿüÿšúÿ®üÿ®þÿ®ÿš$ÿ®$ ÿ®$&ÿì$*ÿì$2ÿì$4ÿì$7ÿ…$8ÿì$9ÿÃ$:ÿ×$<ÿš$‰ÿì$”ÿì$•ÿì$–ÿì$—ÿì$˜ÿì$šÿì$›ÿì$œÿì$ÿì$žÿì$Ÿÿš$Èÿì$Îÿì$Þÿì$àÿì$âÿì$äÿì$ÿì$ÿì$ÿ×$$ÿ…$&ÿ…$,ÿì$0ÿì$2ÿì$4ÿì$6ÿ×$8ÿš$:ÿš$fÿì$mÿì$qÿ…$¸ÿì$»ÿì$¼ÿ…$úÿ×$üÿ×$þÿ×$ÿš$ÿ®$ ÿ®%,ÿì%7ÿì%9ÿì%;ÿì%<ÿì%Ÿÿì%$ÿì%&ÿì%8ÿì%:ÿì%qÿì%¼ÿì%ÿì&)& )& )&&ÿ×&*ÿ×&2ÿ×&4ÿ×&@)&`)&‰ÿ×&”ÿ×&•ÿ×&–ÿ×&—ÿ×&˜ÿ×&šÿ×&Èÿ×&Îÿ×&Þÿ×&àÿ×&âÿ×&äÿ×&ÿ×&ÿ×&fÿ×&mÿ×&¸ÿ×&»ÿ×&)& )'ÿÃ'ÿÃ'$ÿì',ÿì'7ÿÃ'9ÿì':ÿì';ÿì'<ÿ×'=ÿì'‚ÿì'ƒÿì'„ÿì'…ÿì'†ÿì'‡ÿì'ˆÿÃ'Žÿì'ÿì'ÿì'‘ÿì'Ÿÿ×'Âÿì'Äÿì'Æÿì'ìÿì'ðÿì'òÿì'$ÿÃ'&ÿÃ'6ÿì'8ÿ×':ÿ×';ÿì'=ÿì'?ÿì'Cÿì'_ÿì'iÿì'qÿÃ'ªÿì'¼ÿÃ'úÿì'üÿì'þÿì'ÿ×'(ÿì)=) =) ))ÿš)ÿš)"))$ÿ×)9):)<)@))`))‚ÿ×)ƒÿ×)„ÿ×)…ÿ×)†ÿ×)‡ÿ×)ˆÿÃ)Ÿ)Âÿ×)Äÿ×)Æÿ×)6)8):)Cÿ×)_ÿ×)iÿ×)ªÿ×)ú)ü)þ))=) =)(ÿ×,), ),&ÿì,*ÿì,2ÿì,4ÿì,‰ÿì,”ÿì,•ÿì,–ÿì,—ÿì,˜ÿì,šÿì,Èÿì,Îÿì,Þÿì,àÿì,âÿì,äÿì,ÿì,ÿì,ÿì,fÿì,mÿì,¸ÿì,»ÿì,), ).). ).&ÿ×.*ÿ×.2ÿ×.4ÿ×.‰ÿ×.”ÿ×.•ÿ×.–ÿ×.—ÿ×.˜ÿ×.šÿ×.Èÿ×.Îÿ×.Þÿ×.àÿ×.âÿ×.äÿ×.ÿ×.ÿ×.ÿ×.fÿ×.mÿ×.¸ÿ×.»ÿ×.). )/ÿš/ ÿš/&ÿì/*ÿì/2ÿì/4ÿì/7ÿ…/8ÿì/9ÿ®/:ÿÃ/<ÿš/‰ÿì/”ÿì/•ÿì/–ÿì/—ÿì/˜ÿì/šÿì/›ÿì/œÿì/ÿì/žÿì/Ÿÿš/Èÿì/Îÿì/Þÿì/àÿì/âÿì/äÿì/ÿì/ÿì/ÿì/$ÿ…/&ÿ…/,ÿì/0ÿì/2ÿì/4ÿì/6ÿÃ/8ÿš/:ÿš/fÿì/mÿì/qÿ…/¸ÿì/»ÿì/¼ÿ…/úÿÃ/üÿÃ/þÿÃ/ÿš/ÿš/ ÿš2ÿÃ2ÿÃ2$ÿì2,ÿì27ÿÃ29ÿ×2:ÿì2;ÿ×2<ÿ×2=ÿì2‚ÿì2ƒÿì2„ÿì2…ÿì2†ÿì2‡ÿì2ˆÿ×2Žÿì2ÿì2ÿì2‘ÿì2Ÿÿ×2Âÿì2Äÿì2Æÿì2ìÿì2ðÿì2òÿì2$ÿÃ2&ÿÃ26ÿì28ÿ×2:ÿ×2;ÿì2=ÿì2?ÿì2Cÿì2_ÿì2iÿì2qÿÃ2ªÿì2¼ÿÃ2úÿì2üÿì2þÿì2ÿ×2(ÿì3ÿ33ÿ33$ÿ®3&ÿì3;ÿì3<ÿì3=ÿ×3‚ÿ®3ƒÿ®3„ÿ®3…ÿ®3†ÿ®3‡ÿ®3ˆÿq3‰ÿì3Ÿÿì3Âÿ®3Äÿ®3Æÿ®3Èÿì3Îÿì38ÿì3:ÿì3;ÿ×3=ÿ×3?ÿ×3Cÿ®3_ÿ®3iÿ®3ªÿ®3»ÿì3ÿì3(ÿ®4ÿÃ4ÿÃ4$ÿì4,ÿì47ÿÃ49ÿ×4:ÿì4;ÿ×4<ÿ×4=ÿì4‚ÿì4ƒÿì4„ÿì4…ÿì4†ÿì4‡ÿì4ˆÿÃ4Žÿì4ÿì4ÿì4‘ÿì4Ÿÿ×4Âÿì4Äÿì4Æÿì4ìÿì4ðÿì4òÿì4$ÿÃ4&ÿÃ46ÿì48ÿ×4:ÿ×4;ÿì4=ÿì4?ÿì4Cÿì4_ÿì4iÿì4qÿÃ4ªÿì4¼ÿÃ4úÿì4üÿì4þÿì4ÿ×4(ÿì57ÿì5$ÿì5&ÿì5qÿì5¼ÿì7R7 R7ÿš7ÿš7ÿš7")7$ÿ…7&ÿÃ7*ÿÃ72ÿÃ74ÿÃ76ÿì777Dÿ…7Fÿ…7Gÿ…7Hÿ…7Jÿš7Pÿ®7Qÿ®7Rÿ…7Sÿ®7Tÿ…7Uÿ®7Vÿ…7Xÿ®7YÿÃ7ZÿÃ7[ÿÃ7\ÿÃ7]ÿÃ7‚ÿ…7ƒÿ…7„ÿ…7…ÿ…7†ÿ…7‡ÿ…7ˆÿq7‰ÿÃ7”ÿÃ7•ÿÃ7–ÿÃ7—ÿÃ7˜ÿÃ7šÿÃ7¢ÿ…7£ÿ…7¤ÿ…7¥ÿ…7¦ÿ…7§ÿ…7¨ÿ…7©ÿ…7ªÿ…7«ÿ…7¬ÿ…7­ÿ…7³ÿ®7´ÿ…7µÿ…7¶ÿ…7·ÿ…7¸ÿ…7ºÿ…7»ÿ®7¼ÿ®7½ÿ®7¾ÿ®7¿ÿÃ7ÁÿÃ7Âÿ…7Ãÿ…7Äÿ…7Åÿ…7Æÿ…7Çÿ…7ÈÿÃ7Éÿ…7Ëÿ…7Íÿ…7ÎÿÃ7Ïÿ…7Õÿ…7×ÿ…7Ùÿ…7Ûÿ…7Ýÿ…7ÞÿÃ7àÿÃ7âÿÃ7äÿÃ7ÿ®7ÿ®7 ÿ®7ÿÃ7ÿ…7ÿ…7ÿÃ7ÿ…7ÿÃ7ÿ…7ÿì7ÿ…7ÿ…7 ÿì7!ÿ…7"ÿì7#ÿ…7$7&77ÿÃ79ÿÃ7@ÿÃ7Cÿ…7Dÿ…7Jÿ…7_ÿ…7fÿÃ7iÿ…7mÿÃ7q7yÿ…7{ÿ®7~ÿ…7€ÿÃ7‚ÿ…7„ÿ®7ŠÿÃ7Œÿ…7Žÿ…7ÿ…7‘ÿÃ7“ÿ…7”ÿÃ7–ÿ…7™ÿ…7›ÿ…7 ÿì7ªÿ…7¸ÿÃ7»ÿÃ7¼7Êÿ…7Ïÿ…7Øÿ…7Ûÿ…7ÜÿÃ7ÝÿÃ7Þÿ…7êÿ…7íÿ…7îÿ…7ûÿÃ7ýÿÃ7ÿÿÃ7ÿÃ7ÿ®7ÿš7R7 R7(ÿ…8ÿ×8ÿ×8$ÿì8‚ÿì8ƒÿì8„ÿì8…ÿì8†ÿì8‡ÿì8ˆÿ×8Âÿì8Äÿì8Æÿì8Cÿì8_ÿì8iÿì8ªÿì8(ÿì9R9 R9ÿš9ÿš9")9$ÿÃ9&ÿ×9*ÿ×92ÿ×94ÿ×9DÿÃ9FÿÃ9GÿÃ9HÿÃ9JÿÃ9Pÿ×9Qÿ×9RÿÃ9Sÿ×9TÿÃ9Uÿ×9Vÿ×9Xÿ×9‚ÿÃ9ƒÿÃ9„ÿÃ9…ÿÃ9†ÿÃ9‡ÿÃ9ˆÿ…9‰ÿ×9”ÿ×9•ÿ×9–ÿ×9—ÿ×9˜ÿ×9šÿ×9¢ÿÃ9£ÿÃ9¤ÿÃ9¥ÿÃ9¦ÿÃ9§ÿÃ9¨ÿÃ9©ÿÃ9ªÿÃ9«ÿÃ9¬ÿÃ9­ÿÃ9³ÿ×9´ÿÃ9µÿÃ9¶ÿÃ9·ÿÃ9¸ÿÃ9ºÿÃ9»ÿ×9¼ÿ×9½ÿ×9¾ÿ×9ÂÿÃ9ÃÿÃ9ÄÿÃ9ÅÿÃ9ÆÿÃ9ÇÿÃ9Èÿ×9ÉÿÃ9ËÿÃ9ÍÿÃ9Îÿ×9ÏÿÃ9ÕÿÃ9×ÿÃ9ÙÿÃ9ÛÿÃ9ÝÿÃ9Þÿ×9àÿ×9âÿ×9äÿ×9ÿ×9ÿ×9 ÿ×9ÿ×9ÿÃ9ÿÃ9ÿ×9ÿÃ9ÿ×9ÿÃ9ÿ×9ÿ×9!ÿ×9#ÿ×9CÿÃ9DÿÃ9Jÿ×9_ÿÃ9fÿ×9iÿÃ9mÿ×9yÿÃ9{ÿ×9~ÿÃ9‚ÿÃ9„ÿ×9ŒÿÃ9ŽÿÃ9ÿÃ9“ÿÃ9–ÿÃ9™ÿÃ9›ÿÃ9ªÿÃ9¸ÿ×9»ÿ×9ÊÿÃ9ÏÿÃ9ØÿÃ9ÛÿÃ9ÞÿÃ9êÿÃ9íÿÃ9îÿ×9R9 R9(ÿÃ:f: f:ÿ®:ÿ®:$ÿ×:&ÿì:*ÿì:2ÿì:4ÿì:Dÿ×:Fÿ×:Gÿ×:Hÿ×:Jÿì:Pÿì:Qÿì:Rÿ×:Sÿì:Tÿ×:Uÿì:Vÿ×:Xÿì:]ÿì:‚ÿ×:ƒÿ×:„ÿ×:…ÿ×:†ÿ×:‡ÿ×:ˆÿ®:‰ÿì:”ÿì:•ÿì:–ÿì:—ÿì:˜ÿì:šÿì:¢ÿ×:£ÿ×:¤ÿ×:¥ÿ×:¦ÿ×:§ÿ×:¨ÿ×:©ÿ×:ªÿ×:«ÿ×:¬ÿ×:­ÿ×:³ÿì:´ÿ×:µÿ×:¶ÿ×:·ÿ×:¸ÿ×:ºÿ×:»ÿì:¼ÿì:½ÿì:¾ÿì:Âÿ×:Ãÿ×:Äÿ×:Åÿ×:Æÿ×:Çÿ×:Èÿì:Éÿ×:Ëÿ×:Íÿ×:Îÿì:Ïÿ×:Õÿ×:×ÿ×:Ùÿ×:Ûÿ×:Ýÿ×:Þÿì:àÿì:âÿì:äÿì:ÿì:ÿì: ÿì:ÿì:ÿ×:ÿ×:ÿì:ÿ×:ÿì:ÿ×:ÿ×:ÿ×:!ÿ×:#ÿ×:@ÿì:Cÿ×:Dÿ×:Jÿ×:_ÿ×:fÿì:iÿ×:mÿì:yÿ×:{ÿì:~ÿ×:‚ÿ×:„ÿì:Œÿ×:Žÿ×:ÿ×:“ÿ×:–ÿ×:™ÿ×:›ÿ×:ªÿ×:¸ÿì:»ÿì:Êÿ×:Ïÿ×:Øÿ×:Ûÿ×:Þÿ×:êÿ×:íÿ×:îÿ×:f: f:(ÿ×;); );&ÿ×;*ÿ×;2ÿ×;4ÿ×;Fÿì;Gÿì;Hÿì;Rÿì;Tÿì;‰ÿ×;”ÿ×;•ÿ×;–ÿ×;—ÿ×;˜ÿ×;šÿ×;¨ÿì;©ÿì;ªÿì;«ÿì;¬ÿì;­ÿì;´ÿì;µÿì;¶ÿì;·ÿì;¸ÿì;ºÿì;Èÿ×;Éÿì;Ëÿì;Íÿì;Îÿ×;Ïÿì;Õÿì;×ÿì;Ùÿì;Ûÿì;Ýÿì;Þÿ×;àÿ×;âÿ×;äÿ×;ÿ×;ÿì;ÿì;ÿ×;ÿì;ÿ×;ÿì;fÿ×;mÿ×;yÿì;~ÿì;‚ÿì;Œÿì;Žÿì;ÿì;“ÿì;–ÿì;™ÿì;›ÿì;¸ÿ×;»ÿ×;Ïÿì;Øÿì;Ûÿì;Þÿì;êÿì;íÿì;); )<R< R<ÿš<ÿš<")<$ÿš<&ÿ×<*ÿ×<2ÿ×<4ÿ×<6ÿì<Dÿš<Fÿš<Gÿš<Hÿš<Jÿš<PÿÃ<QÿÃ<Rÿš<SÿÃ<Tÿš<UÿÃ<Vÿ®<XÿÃ<[ÿ×<\ÿì<]ÿÃ<‚ÿš<ƒÿš<„ÿš<…ÿš<†ÿš<‡ÿš<ˆÿq<‰ÿ×<”ÿ×<•ÿ×<–ÿ×<—ÿ×<˜ÿ×<šÿ×<¢ÿš<£ÿš<¤ÿš<¥ÿš<¦ÿš<§ÿš<¨ÿš<©ÿš<ªÿš<«ÿš<¬ÿš<­ÿš<³ÿÃ<´ÿš<µÿš<¶ÿš<·ÿš<¸ÿš<ºÿš<»ÿÃ<¼ÿÃ<½ÿÃ<¾ÿÃ<¿ÿì<Áÿì<Âÿš<Ãÿš<Äÿš<Åÿš<Æÿš<Çÿš<Èÿ×<Éÿš<Ëÿš<Íÿš<Îÿ×<Ïÿš<Õÿš<×ÿš<Ùÿš<Ûÿš<Ýÿš<Þÿ×<àÿ×<âÿ×<äÿ×<ÿÃ<ÿÃ< ÿÃ<ÿ×<ÿš<ÿš<ÿ×<ÿš<ÿ×<ÿš<ÿì<ÿ®<ÿ®< ÿì<!ÿ®<"ÿì<#ÿ®<9ÿì<@ÿÃ<Cÿš<Dÿš<Jÿ®<_ÿš<fÿ×<iÿš<mÿ×<yÿš<{ÿÃ<~ÿš<€ÿì<‚ÿš<„ÿÃ<Šÿì<Œÿš<Žÿš<ÿš<“ÿš<–ÿš<™ÿš<›ÿš< ÿì<ªÿš<¸ÿ×<»ÿ×<Êÿš<Ïÿš<Øÿš<Ûÿš<Ýÿì<Þÿš<êÿš<íÿš<îÿ®<ÿì<R< R<(ÿš=)= )=&ÿì=*ÿì=2ÿì=4ÿì=‰ÿì=”ÿì=•ÿì=–ÿì=—ÿì=˜ÿì=šÿì=Èÿì=Îÿì=Þÿì=àÿì=âÿì=äÿì=ÿì=ÿì=ÿ×=fÿì=mÿì=¸ÿì=»ÿì=)= )>-{>ö{>£{D ÿ×E ÿ×F=F =F=F =H ÿ×IfI fIYIZI\I¿IÁI7I9I€IŠI‘I”IÜIÝIûIýIÿIIfI fJ)J )JJJ)J )K ÿÃN)N )N)N )P ÿ×Q ÿ×R[ÿ×R]ÿìR@ÿìUfU fUDÿìUJÿìU¢ÿìU£ÿìU¤ÿìU¥ÿìU¦ÿìU§ÿìUÃÿìUÅÿìUÇÿìUDÿìUÊÿìUfU fV=V =V=V =WRW RWWWRW RYRY RYIYRY RZRZ RZIZRZ R[)[ )[Rÿ×[¨ÿ×[´ÿ×[µÿ×[¶ÿ×[·ÿ×[¸ÿ×[ºÿ×[ÿ×[ÿ×[ÿ×[ÿ×[Œÿ×[Žÿ×[ÿ×[“ÿ×[–ÿ×[™ÿ×[›ÿ×[Øÿ×[Þÿ×[)[ )\=\ =\I\=\ =]Rÿì]¨ÿì]´ÿì]µÿì]¶ÿì]·ÿì]¸ÿì]ºÿì]ÿì]ÿì]ÿì]ÿì]Œÿì]Žÿì]ÿì]“ÿì]–ÿì]™ÿì]›ÿì]Øÿì]Þÿì^-{^ö{^£{mqÿ×mxÿ×m‘)}qÿš}rÿÃ}xÿÂÿ®‚ ÿ®‚ ÿ…‚D‚D‚"ÿÂ&ÿì‚*ÿì‚-^‚2ÿì‚4ÿì‚7ÿ…‚8ÿì‚9ÿÂ:ÿׂ<ÿš‚=;‚Iÿì‚Wÿì‚YÿׂZÿì‚\ÿׂ‚ÿׂ‰ÿì‚”ÿì‚•ÿì‚–ÿì‚—ÿ삘ÿ삚ÿì‚›ÿ삜ÿì‚ÿ삞ÿ삟ÿš‚¿ÿׂÁÿׂÈÿì‚Îÿì‚Þÿì‚àÿì‚âÿì‚äÿì‚ö^‚ÿì‚ÿì‚ÿׂ$ÿ…‚&ÿ…‚,ÿì‚0ÿì‚2ÿì‚4ÿì‚6ÿׂ7ÿì‚8ÿš‚9ÿׂ:ÿš‚;;‚=;‚?;‚fÿì‚mÿì‚qÿ…‚€ÿׂŠÿׂ‘ÿׂ”ÿׂ£^‚¸ÿì‚»ÿ삼ÿ…‚ÜÿׂÝÿׂúÿׂûÿì‚üÿׂýÿì‚þÿׂÿÿì‚ÿš‚ÿׂÿ®‚ ÿ®ƒÿ®ƒ ÿ®ƒ ÿ…ƒDƒDƒ"ÿÃ&ÿìƒ*ÿìƒ-^ƒ2ÿìƒ4ÿìƒ7ÿ…ƒ8ÿìƒ9ÿÃ:ÿ׃<ÿšƒ=;ƒIÿìƒWÿìƒYÿ׃Zÿìƒ\ÿ׃„ÿ׃‰ÿ샊ÿ׃ÿ׃ÿ僔ÿ샕ÿ샖ÿ샗ÿ샘ÿ샚ÿ샛ÿ샜ÿìƒÿ샞ÿ샟ÿšƒ¿ÿ׃Áÿ׃ÈÿìƒÎÿìƒÞÿìƒàÿìƒâÿìƒäÿìƒö^ƒÿìƒÿìƒÿ׃$ÿ…ƒ&ÿ…ƒ,ÿìƒ0ÿìƒ2ÿìƒ4ÿìƒ6ÿ׃7ÿìƒ8ÿšƒ9ÿ׃:ÿšƒ;;ƒ=;ƒ?;ƒfÿìƒmÿìƒqÿ…ƒ€ÿ׃Šÿ׃‘ÿ׃”ÿ׃£^ƒ¸ÿ샻ÿ샼ÿ…ƒÜÿ׃Ýÿ׃úÿ׃ûÿìƒüÿ׃ýÿìƒþÿ׃ÿÿìƒÿšƒÿ׃ÿ®ƒ ÿ®„ÿ®„ ÿ®„ ÿ…„D„D„"ÿÄ&ÿì„*ÿì„-^„2ÿì„4ÿì„7ÿ…„8ÿì„9ÿÄ:ÿׄ<ÿš„=;„Iÿì„Wÿì„YÿׄZÿì„\ÿׄ„ÿׄ‰ÿ섊ÿׄÿׄÿå„”ÿì„•ÿì„–ÿì„—ÿ섘ÿ섚ÿì„›ÿ서ÿì„ÿ섞ÿ섟ÿš„¿ÿׄÁÿׄÈÿì„Îÿì„Þÿì„àÿì„âÿì„äÿì„ö^„ÿì„ÿì„ÿׄ$ÿ…„&ÿ…„,ÿì„0ÿì„2ÿì„4ÿì„6ÿׄ7ÿì„8ÿš„9ÿׄ:ÿš„;;„=;„?;„fÿì„mÿì„qÿ…„€ÿׄŠÿׄ‘ÿׄ”ÿׄ£^„¸ÿì„»ÿ센ÿ…„ÜÿׄÝÿׄúÿׄûÿì„üÿׄýÿì„þÿׄÿÿì„ÿš„ÿׄÿ®„ ÿ®…ÿ®… ÿ®… ÿ……D…D…"ÿÃ…&ÿì…*ÿì…-^…2ÿì…4ÿì…7ÿ……8ÿì…9ÿÃ…:ÿ×…<ÿš…=;…Iÿì…Wÿì…Yÿ×…Zÿì…\ÿ×…‚ÿ×…‰ÿì…”ÿì…•ÿì…–ÿì…—ÿì…˜ÿì…šÿì…›ÿì…œÿì…ÿì…žÿì…Ÿÿš…¿ÿ×…Áÿ×…Èÿì…Îÿì…Þÿì…àÿì…âÿì…äÿì…ö^…ÿì…ÿì…ÿ×…$ÿ……&ÿ……,ÿì…0ÿì…2ÿì…4ÿì…6ÿ×…7ÿì…8ÿš…9ÿ×…:ÿš…;;…=;…?;…fÿì…mÿì…qÿ……€ÿ×…Šÿ×…‘ÿ×…”ÿ×…£^…¸ÿì…»ÿì…¼ÿ……Üÿ×…Ýÿ×…úÿ×…ûÿì…üÿ×…ýÿì…þÿ×…ÿÿì…ÿš…ÿ×…ÿ®… ÿ®†ÿ®† ÿ®† ÿ†D†D†"ÿ׆&ÿì†*ÿì†-^†2ÿì†4ÿì†7ÿ…†8ÿì†9ÿÆ:ÿ׆<ÿš†=;†Wÿå†YÿÕ†Zÿå†\ÿÛ†‰ÿ솔ÿ솕ÿ솖ÿ솗ÿ솘ÿ솚ÿ솛ÿ솜ÿì†ÿ솞ÿ솟ÿš†¿ÿÛ†ÁÿÛ†Èÿì†Îÿì†Þÿì†àÿì†âÿì†äÿì†ö^†ÿì†ÿì†ÿ׆$ÿ…†&ÿ…†,ÿì†0ÿì†2ÿì†4ÿì†6ÿ׆7ÿå†8ÿš†9ÿÛ†:ÿš†;;†=;†?;†fÿì†mÿì†qÿ…†€ÿÛ†ŠÿÕ†‘ÿÕ†”ÿÕ†£^†¸ÿ솻ÿ솼ÿ…†ÜÿÕ†ÝÿÛ†úÿ׆ûÿå†üÿ׆ýÿå†þÿ׆ÿÿå†ÿš†ÿÛ†ÿ®† ÿ®‡ÿf‡ ÿf‡ ÿ‡D‡D‡"ÿׇ&ÿì‡*ÿì‡-^‡2ÿì‡4ÿì‡7ÿ…‡8ÿì‡9ÿÇ:ÿׇ<ÿš‡=;‡Wÿå‡YÿÕ‡Zÿå‡\ÿÛ‡‰ÿ쇔ÿ쇕ÿ쇖ÿ쇗ÿ쇘ÿ쇚ÿ쇛ÿ쇜ÿì‡ÿ쇞ÿ쇟ÿš‡¿ÿÛ‡ÁÿÛ‡Èÿì‡Îÿì‡Þÿì‡àÿì‡âÿì‡äÿì‡ö^‡ÿì‡ÿì‡ÿׇ$ÿ…‡&ÿ…‡,ÿì‡0ÿì‡2ÿì‡4ÿì‡6ÿׇ7ÿå‡8ÿš‡9ÿÛ‡:ÿš‡;;‡=;‡?;‡fÿì‡mÿì‡qÿ…‡€ÿÛ‡ŠÿÕ‡‘ÿÕ‡”ÿÕ‡£^‡¸ÿ쇻ÿ쇼ÿ…‡ÜÿÕ‡ÝÿÛ‡úÿׇûÿå‡üÿׇýÿå‡þÿׇÿÿå‡ÿš‡ÿÛ‡ÿf‡ÿ®‡ ÿf‡ ÿ®‡ ÿš‰)‰ )‰ )‰&ÿ׉*ÿ׉2ÿ׉4ÿ׉@)‰`)‰‰ÿ׉”ÿ׉•ÿ׉–ÿ׉—ÿ׉˜ÿ׉šÿ׉Èÿ׉Îÿ׉Þÿ׉àÿ׉âÿ׉äÿ׉ÿ׉ÿ׉fÿ׉mÿ׉¸ÿ׉»ÿ׉)‰ )Š)Š )Šÿ׊&ÿìŠ2ÿìŠ4ÿ슄ÿ슉ÿ슊ÿìŠÿ슔ÿ슕ÿ슖ÿ슗ÿ슘ÿ슚ÿìŠÈÿìŠÎÿìŠö=ŠÿìŠÿìŠfÿìŠmÿ슣=Џÿ슻ÿìŠ)Š )‹)‹ )‹ÿ׋&ÿì‹2ÿì‹4ÿ싉ÿì‹‹ÿì‹”ÿì‹•ÿì‹–ÿì‹—ÿ싘ÿ싚ÿì‹Èÿì‹Îÿì‹ö=‹ÿì‹ÿì‹fÿì‹mÿì‹£=‹¸ÿì‹»ÿì‹)‹ )Œ)Œ )Œÿ׌&ÿìŒ2ÿìŒ4ÿ쌄ÿ쌉ÿ쌊ÿìŒÿ쌔ÿ쌕ÿ쌖ÿ쌗ÿ쌘ÿ쌚ÿìŒÈÿìŒÎÿìŒö=ŒÿìŒÿìŒfÿìŒmÿ쌣=Œ¸ÿ쌻ÿìŒ)Œ )) )ÿ×&ÿì2ÿì4ÿì„ÿì‰ÿìŠÿìÿì”ÿì•ÿì–ÿì—ÿì˜ÿìšÿìÈÿìÎÿìö=ÿìÿìfÿìmÿì£=¸ÿì»ÿì) )Ž)Ž )Ž&ÿìŽ*ÿìŽ2ÿìŽ4ÿ쎉ÿ쎔ÿ쎕ÿ쎖ÿ쎗ÿ쎘ÿ쎚ÿìŽÈÿìŽÎÿìŽÞÿìŽàÿìŽâÿìŽäÿìŽÿìŽÿìŽÿìŽfÿìŽmÿ쎸ÿ쎻ÿìŽ)Ž )) )&ÿì*ÿì2ÿì4ÿì‰ÿì”ÿì•ÿì–ÿì—ÿì˜ÿìšÿìÈÿìÎÿìÞÿìàÿìâÿìäÿìÿìÿìÿìfÿìmÿì¸ÿì»ÿì) )) )&ÿì*ÿì2ÿì4ÿì‰ÿì”ÿì•ÿì–ÿì—ÿì˜ÿìšÿìÈÿìÎÿìÞÿìàÿìâÿìäÿìÿìÿìÿìfÿìmÿì¸ÿì»ÿì) )‘)‘ )‘&ÿì‘*ÿì‘2ÿì‘4ÿ쑉ÿì‘”ÿì‘•ÿì‘–ÿì‘—ÿ쑘ÿ쑚ÿì‘Èÿì‘Îÿì‘Þÿì‘àÿì‘âÿì‘äÿì‘ÿì‘ÿì‘ÿì‘fÿì‘mÿ쑸ÿì‘»ÿì‘)‘ )’$ÿì’‚ÿì’ƒÿì’„ÿì’…ÿì’†ÿì’‡ÿì’Âÿì’Äÿì’Æÿì’Cÿì’_ÿì’iÿì’ªÿì’(ÿì” ÿ×”ÿÔÿÔ$ÿì”,ÿì”-ÿö”6ÿì”7ÿÔ9ÿ×”:ÿì”;ÿ×”<ÿ×”=ÿì”@ÿ×”`ÿ×”‚ÿ씃ÿ씄ÿì”…ÿ씆ÿ씇ÿ씈ÿ×”Žÿì”ÿì”ÿ씑ÿ씟ÿ×”Âÿì”Äÿì”Æÿì”ìÿì”ðÿì”òÿì”öÿö”ÿì” ÿì”"ÿì”$ÿÔ&ÿÔ6ÿì”8ÿ×”:ÿ×”;ÿì”=ÿì”?ÿì”Cÿì”_ÿì”iÿì”qÿÔ ÿ씣ÿö”ªÿ씼ÿÔúÿì”üÿì”þÿì”ÿ×”(ÿì• ÿוÿÕÿÕ$ÿì•,ÿì•-ÿö•6ÿì•7ÿÕ9ÿו:ÿì•;ÿו<ÿו=ÿì•@ÿו`ÿו‚ÿ앃ÿì•„ÿì•…ÿ앆ÿ앇ÿ안ÿוŽÿì•ÿì•ÿì•‘ÿ앟ÿוÂÿì•Äÿì•Æÿì•ìÿì•ðÿì•òÿì•öÿö•ÿì• ÿì•"ÿì•$ÿÕ&ÿÕ6ÿì•8ÿו:ÿו;ÿì•=ÿì•?ÿì•Cÿì•_ÿì•iÿì•qÿÕ ÿì•£ÿö•ªÿ야ÿÕúÿì•üÿì•þÿì•ÿו(ÿì– ÿ×–ÿÖÿÖ$ÿì–,ÿì–-ÿö–6ÿì–7ÿÖ9ÿ×–:ÿì–;ÿ×–<ÿ×–=ÿì–@ÿ×–`ÿ×–‚ÿì–ƒÿì–„ÿì–…ÿì–†ÿì–‡ÿì–ˆÿ×–Žÿì–ÿì–ÿì–‘ÿì–Ÿÿ×–Âÿì–Äÿì–Æÿì–ìÿì–ðÿì–òÿì–öÿö–ÿì– ÿì–"ÿì–$ÿÖ&ÿÖ6ÿì–8ÿ×–:ÿ×–;ÿì–=ÿì–?ÿì–Cÿì–_ÿì–iÿì–qÿÖ ÿì–£ÿö–ªÿì–¼ÿÖúÿì–üÿì–þÿì–ÿ×–(ÿì— ÿ×—ÿ×ÿ×$ÿì—,ÿì—-ÿö—6ÿì—7ÿ×9ÿ×—:ÿì—;ÿ×—<ÿ×—=ÿì—@ÿ×—`ÿ×—‚ÿì—ƒÿì—„ÿì—…ÿì—†ÿì—‡ÿì—ˆÿ×—Žÿì—ÿì—ÿì—‘ÿì—Ÿÿ×—Âÿì—Äÿì—Æÿì—ìÿì—ðÿì—òÿì—öÿö—ÿì— ÿì—"ÿì—$ÿ×&ÿ×6ÿì—8ÿ×—:ÿ×—;ÿì—=ÿì—?ÿì—Cÿì—_ÿì—iÿì—qÿ× ÿì—£ÿö—ªÿì—¼ÿ×úÿì—üÿì—þÿì—ÿ×—(ÿì˜ ÿטÿØÿØ$ÿì˜,ÿì˜-ÿö˜6ÿì˜7ÿØ9ÿט:ÿì˜;ÿט<ÿט=ÿì˜@ÿט`ÿט‚ÿ옃ÿ옄ÿ옅ÿ옆ÿ옇ÿ예ÿטŽÿì˜ÿì˜ÿ옑ÿ옟ÿטÂÿì˜Äÿì˜Æÿì˜ìÿì˜ðÿì˜òÿì˜öÿö˜ÿì˜ ÿì˜"ÿì˜$ÿØ&ÿØ6ÿì˜8ÿט:ÿט;ÿì˜=ÿì˜?ÿì˜Cÿì˜_ÿì˜iÿì˜qÿØ ÿ옣ÿö˜ªÿ옼ÿØúÿì˜üÿì˜þÿì˜ÿט(ÿìšÿÚÿÚ$ÿìš,ÿìš7ÿÚ9ÿך:ÿìš;ÿך<ÿך=ÿìš‚ÿ욃ÿìš„ÿìš…ÿ욆ÿ욇ÿ욈ÿךŽÿìšÿìšÿìš‘ÿ욟ÿךÂÿìšÄÿìšÆÿìšìÿìšðÿìšòÿìš$ÿÚ&ÿÚ6ÿìš8ÿך:ÿך;ÿìš=ÿìš?ÿìšCÿìš_ÿìšiÿìšqÿÚªÿìš¼ÿÚúÿìšüÿìšþÿìšÿך(ÿì›ÿ×›ÿ×›$ÿì›0ÿì›=ÿì›Dÿ웂ÿ웃ÿ웄ÿì›…ÿ웆ÿ웇ÿ웈ÿ×›¢ÿ웣ÿ웤ÿ웥ÿ웦ÿì›§ÿì›Âÿì›Ãÿì›Äÿì›Åÿì›Æÿì›Çÿì›;ÿì›=ÿì›?ÿì›Cÿì›Dÿì›_ÿì›iÿ웪ÿì›Êÿì›(ÿìœÿלÿל$ÿìœ0ÿìœ=ÿìœDÿ윂ÿ윃ÿ위ÿ윅ÿ윆ÿ윇ÿ윈ÿל¢ÿ윣ÿ윤ÿ윥ÿ윦ÿ윧ÿìœÂÿìœÃÿìœÄÿìœÅÿìœÆÿìœÇÿìœ;ÿìœ=ÿìœ?ÿìœCÿìœDÿìœ_ÿìœiÿ윪ÿìœÊÿìœ(ÿìÿ×ÿ×$ÿì0ÿì=ÿìDÿì‚ÿìƒÿì„ÿì…ÿì†ÿì‡ÿìˆÿ×¢ÿì£ÿì¤ÿì¥ÿì¦ÿì§ÿìÂÿìÃÿìÄÿìÅÿìÆÿìÇÿì;ÿì=ÿì?ÿìCÿìDÿì_ÿìiÿìªÿìÊÿì(ÿìžÿמÿמ$ÿìž0ÿìž=ÿìžDÿìž‚ÿ잃ÿìž„ÿìž…ÿ잆ÿ잇ÿ있ÿמ¢ÿ잣ÿ잤ÿ장ÿ잦ÿìž§ÿìžÂÿìžÃÿìžÄÿìžÅÿìžÆÿìžÇÿìž;ÿìž=ÿìž?ÿìžCÿìžDÿìž_ÿìžiÿ잪ÿìžÊÿìž(ÿìŸRŸ ÿß RŸ =Ÿ )ŸÿšŸÿšŸÿšŸ")Ÿ$ÿšŸ&ÿן*ÿן-ÿ¾Ÿ0ÿß2ÿן4ÿן6ÿìŸ7'Ÿ9)Ÿ:Ÿ@=ŸDÿšŸFÿšŸGÿšŸHÿšŸIÿåŸJÿšŸPÿßQÿßRÿšŸSÿßTÿšŸUÿßVÿ®ŸXÿßYÿןZÿìŸ[ÿן\ÿìŸ]ÿß`=Ÿ‚ÿšŸƒÿšŸ„ÿšŸ…ÿšŸ†ÿšŸ‡ÿšŸˆÿqŸ‰ÿן”ÿן•ÿן–ÿן—ÿן˜ÿןšÿן¢ÿšŸ£ÿšŸ¤ÿšŸ¥ÿšŸ¦ÿšŸ§ÿšŸ¨ÿšŸ©ÿšŸªÿšŸ«ÿšŸ¬ÿšŸ­ÿšŸ³ÿß´ÿšŸµÿšŸ¶ÿšŸ·ÿšŸ¸ÿšŸºÿšŸ»ÿß¼ÿß½ÿß¾ÿß¿ÿìŸÁÿìŸÂÿšŸÃÿšŸÄÿšŸÅÿšŸÆÿšŸÇÿšŸÈÿןÉÿšŸËÿšŸÍÿšŸÎÿןÏÿšŸÕÿšŸ×ÿšŸÙÿšŸÛÿšŸÝÿšŸÞÿןàÿןâÿןäÿןöÿ¾Ÿÿßÿß ÿßÿןÿšŸÿšŸÿןÿšŸÿןÿšŸÿìŸÿ®Ÿÿ®Ÿ ÿìŸ!ÿ®Ÿ"ÿìŸ#ÿ®Ÿ$'Ÿ&'Ÿ6Ÿ7ÿìŸ9ÿìŸ@ÿßCÿšŸDÿšŸJÿ®Ÿ_ÿšŸfÿןiÿšŸmÿןq'ŸyÿšŸ{ÿß~ÿšŸ€ÿ쟂ÿšŸ„ÿߊÿןŒÿšŸŽÿšŸÿšŸ‘ÿן“ÿšŸ”ÿן–ÿšŸ™ÿšŸ›ÿšŸ ÿ쟣ÿ¾ŸªÿšŸ¸ÿן»ÿן¼'ŸÊÿšŸÏÿšŸØÿšŸÛÿšŸÜÿןÝÿìŸÞÿšŸêÿšŸíÿšŸîÿ®ŸúŸûÿìŸüŸýÿìŸþŸÿÿìŸÿìŸRŸ RŸ(ÿš¢ ÿ×£ ÿפ ÿ×¥ ÿצ ÿ×§ ÿר[ÿר]ÿì¨@ÿì©=© =©=© =ªÿ˜ª ÿ׫ÿ˜« ÿ׬ÿ˜¬ ÿ×­ÿ˜­ ÿ׳ÿ˜³ ÿ׳ ÿ×´ÿo´ ÿo´IÿÛ´[ÿ×´]ÿì´@ÿìµÿoµ ÿoµIÿÛµ[ÿ×µ]ÿìµ@ÿì¶ÿo¶ ÿo¶IÿÛ¶[ÿ×¶]ÿì¶@ÿì·ÿo· ÿo·IÿÛ·[ÿ×·]ÿì·@ÿì¸ÿo¸ ÿo¸IÿÛ¸[ÿ׸]ÿì¸@ÿìº[ÿ׺]ÿìº@ÿì»ÿ¾» ÿ¾¼ÿ¾¼ ÿ¾½ÿ¾½ ÿ¾¾ÿ¾¾ ÿ¾¿=¿ =¿ÿ¾¿ÿ¾¿"ÿ´¿Fÿö¿Gÿö¿Hÿö¿I¿Jÿö¿Rÿö¿Tÿö¿W¿¨ÿö¿©ÿö¿ªÿö¿«ÿö¿¬ÿö¿­ÿö¿´ÿö¿µÿö¿¶ÿö¿·ÿö¿¸ÿö¿ºÿö¿Éÿö¿Ëÿö¿Íÿö¿Ïÿö¿Õÿö¿×ÿö¿Ùÿö¿Ûÿö¿Ýÿö¿ÿö¿ÿö¿ÿö¿ÿö¿yÿö¿~ÿö¿‚ÿö¿Œÿö¿Žÿö¿ÿö¿“ÿö¿–ÿö¿™ÿö¿›ÿö¿Ïÿö¿Øÿö¿Ûÿö¿Þÿö¿êÿö¿íÿö¿=¿ÿ¿ =¿ ÿ¿ÿ¿ Á=Á =Áÿ¾Áÿ¾ÁIÁ=Á =Âÿ®Â ÿ®Â&ÿìÂ*ÿìÂ2ÿìÂ4ÿìÂ7ÿ…Â8ÿìÂ9ÿÃÂ:ÿ×Â<ÿšÂ‰ÿì”ÿì•ÿì–ÿì—ÿì˜ÿìšÿì›ÿìœÿìÂÿìžÿìŸÿšÂÈÿìÂÎÿìÂÞÿìÂàÿìÂâÿìÂäÿìÂÿìÂÿìÂÿ×Â$ÿ…Â&ÿ…Â,ÿìÂ0ÿìÂ2ÿìÂ4ÿìÂ6ÿ×Â8ÿšÂ:ÿšÂfÿìÂmÿìÂqÿ…¸ÿì»ÿì¼ÿ…Âúÿ×Âüÿ×Âþÿ×ÂÿšÂÿ®Â ÿ®Ã ÿ×Äÿ®Ä ÿ®Ä&ÿìÄ*ÿìÄ2ÿìÄ4ÿìÄ7ÿ…Ä8ÿìÄ9ÿÃÄ:ÿ×Ä<ÿšÄ‰ÿìÄ”ÿìÄ•ÿìÄ–ÿìÄ—ÿìĘÿìÄšÿìÄ›ÿìÄœÿìÄÿìÄžÿìÄŸÿšÄÈÿìÄÎÿìÄÞÿìÄàÿìÄâÿìÄäÿìÄÿìÄÿìÄÿ×Ä$ÿ…Ä&ÿ…Ä,ÿìÄ0ÿìÄ2ÿìÄ4ÿìÄ6ÿ×Ä8ÿšÄ:ÿšÄfÿìÄmÿìÄqÿ…ĸÿìÄ»ÿìļÿ…Äúÿ×Äüÿ×Äþÿ×ÄÿšÄÿ®Ä ÿ®Å ÿׯÿ®Æ ÿ®Æ&ÿìÆ*ÿìÆ-áÆ2ÿìÆ4ÿìÆ7ÿ…Æ8ÿìÆ9ÿÃÆ:ÿׯ<ÿšÆ‰ÿìÆ”ÿìÆ•ÿìÆ–ÿìÆ—ÿìÆ˜ÿìÆšÿìÆ›ÿìÆœÿìÆÿìÆžÿìÆŸÿšÆÈÿìÆÎÿìÆÞÿìÆàÿìÆâÿìÆäÿìÆÿìÆÿìÆÿׯ$ÿ…Æ&ÿ…Æ,ÿìÆ0ÿìÆ2ÿìÆ4ÿìÆ6ÿׯ8ÿšÆ:ÿšÆfÿìÆmÿìÆqÿ…ƸÿìÆ»ÿìÆ¼ÿ…ÆúÿׯüÿׯþÿׯÿšÆÿ®Æ ÿ®Ç ÿ×È)È )È )È&ÿ×È*ÿ×È2ÿ×È4ÿ×È@)È`)ȉÿ×È”ÿ×È•ÿ×È–ÿ×È—ÿ×Șÿ×Èšÿ×ÈÈÿ×ÈÎÿ×ÈÞÿ×Èàÿ×Èâÿ×Èäÿ×Èÿ×Èÿ×Èfÿ×Èmÿ×ȸÿ×È»ÿ×È)È )É=É =É=É =Ë=Ë =Ë=Ë =Í=Í =Í=Í =Î)Î )Î )Î&ÿ×Î*ÿ×Î2ÿ×Î4ÿ×Î@)Î`)Ήÿ×Δÿ×Εÿ×Ζÿ×Ηÿ×Θÿ×Κÿ×ÎÈÿ×ÎÎÿ×ÎÞÿ×Îàÿ×Îâÿ×Îäÿ×Îÿ×Îÿ×Îfÿ×Îmÿ×θÿ×λÿ×Î)Î )Ï=Ï =Ï=Ï =ÑfÑ fÑ Ñ"¤Ñ@¤ÑERÑKRÑL=ÑM=ÑNRÑORÑ`¸Ñ®öѰÍѱÍÑçRÑé¤Ñë ÑíÍÑïöÑñ)Ñõ=Ñ÷áÑùRÑüRÑþRÑRÑRÑRÑÑ Õ ÿ×× ÿ×Ù ÿ×Ú-fÛ ÿ×Ý ÿ×ì)ì )ì&ÿìì*ÿìì2ÿìì4ÿìì‰ÿìì”ÿìì•ÿìì–ÿìì—ÿìì˜ÿììšÿììÈÿììÎÿììÞÿììàÿììâÿììäÿììÿììÿììÿììfÿììmÿìì¸ÿìì»ÿìì)ì )ð)ð )ð&ÿìð*ÿìð-Rð2ÿìð4ÿìð‰ÿìð”ÿìð•ÿìð–ÿìð—ÿìð˜ÿìðšÿìðÈÿìðÎÿìðÞÿìðàÿìðâÿìðäÿìðÿìðÿìðÿìðfÿìðmÿìð¸ÿìð»ÿìð)ð )ò)ò )ò&ÿìò*ÿìò2ÿìò4ÿìò‰ÿìò”ÿìò•ÿìò–ÿìò—ÿìò˜ÿìòšÿìòÈÿìòÎÿìòÞÿìòàÿìòâÿìòäÿìòÿìòÿìòÿìòfÿìòmÿìò¸ÿìò»ÿìò)ò )ø)ø )ø&ÿ×ø*ÿ×ø2ÿ×ø4ÿ×ø‰ÿ×ø”ÿ×ø•ÿ×ø–ÿ×ø—ÿ×ø˜ÿ×øšÿ×øÈÿ×øÎÿ×øÞÿ×øàÿ×øâÿ×øäÿ×øÿ×øÿ×øÿ×øfÿ×ømÿ×ø¸ÿ×ø»ÿ×ø)ø )ú)ú )ú)ú )ûÿšû ÿšû&ÿìû*ÿìû2ÿìû4ÿìû7ÿ…û8ÿìû9ÿ®û:ÿÃû<ÿšû‰ÿìû”ÿìû•ÿìû–ÿìû—ÿìû˜ÿìûšÿìû›ÿìûœÿìûÿìûžÿìûŸÿšûÈÿìûÎÿìûÞÿìûàÿìûâÿìûäÿìûÿìûÿìûÿìû$ÿ…û&ÿ…û,ÿìû0ÿìû2ÿìû4ÿìû6ÿÃû8ÿšû:ÿšûfÿìûmÿìûqÿ…û¸ÿìû»ÿìû¼ÿ…ûúÿÃûüÿÃûþÿÃûÿšûÿšû ÿšýÿšý ÿšý&ÿìý*ÿìý2ÿìý4ÿìý7ÿ…ý8ÿìý9ÿ®ý:ÿÃý<ÿšý‰ÿìý”ÿìý•ÿìý–ÿìý—ÿìý˜ÿìýšÿìý›ÿìýœÿìýÿìýžÿìýŸÿšýÈÿìýÎÿìýÞÿìýàÿìýâÿìýäÿìýÿìýÿìýÿìý$ÿ…ý&ÿ…ý,ÿìý0ÿìý2ÿìý4ÿìý6ÿÃý8ÿšý:ÿšýfÿìýmÿìýqÿ…ý¸ÿìý»ÿìý¼ÿ…ýúÿÃýüÿÃýþÿÃýÿšýÿšý ÿšÿÿšÿ ÿšÿ&ÿìÿ*ÿìÿ2ÿìÿ4ÿìÿ7ÿ…ÿ8ÿìÿ9ÿ®ÿ:ÿÃÿ<ÿšÿ‰ÿìÿ”ÿìÿ•ÿìÿ–ÿìÿ—ÿìÿ˜ÿìÿšÿìÿ›ÿìÿœÿìÿÿìÿžÿìÿŸÿšÿÈÿìÿÎÿìÿÞÿìÿàÿìÿâÿìÿäÿìÿÿìÿÿìÿÿìÿ$ÿ…ÿ&ÿ…ÿ,ÿìÿ0ÿìÿ2ÿìÿ4ÿìÿ6ÿÃÿ8ÿšÿ:ÿšÿfÿìÿmÿìÿqÿ…ÿ¸ÿìÿ»ÿìÿ¼ÿ…ÿúÿÃÿüÿÃÿþÿÃÿÿšÿÿšÿ ÿšf f "¤@¤ERKRL=M=NROR`¸®ö°Í±ÍçRé¤ë íÍïöñ)õ=÷áùRüRþRRRR ÿš ÿš&ÿì*ÿì2ÿì4ÿì7ÿ…8ÿì9ÿ®:ÿÃ<ÿš‰ÿì”ÿì•ÿì–ÿì—ÿì˜ÿìšÿì›ÿìœÿìÿìžÿìŸÿšÈÿìÎÿìÞÿìàÿìâÿìäÿìÿìÿìÿì$ÿ…&ÿ…,ÿì0ÿì2ÿì4ÿì6ÿÃ8ÿš:ÿšfÿìmÿìqÿ…¸ÿì»ÿì¼ÿ…úÿÃüÿÃþÿÃÿšÿš ÿšÿš ÿš&ÿì*ÿì2ÿì4ÿì7ÿ…8ÿì9ÿ®:ÿÃ<ÿš‰ÿì”ÿì•ÿì–ÿì—ÿì˜ÿìšÿì›ÿìœÿìÿìžÿìŸÿšÈÿìÎÿìÞÿìàÿìâÿìäÿìÿìÿìÿì$ÿ…&ÿ…,ÿì0ÿì2ÿì4ÿì6ÿÃ8ÿš:ÿšfÿìmÿìqÿ…¸ÿì»ÿì¼ÿ…úÿÃüÿÃþÿÃÿšÿš ÿš ÿ× ÿ× ÿ×ÿÃÿÃ$ÿì,ÿì7ÿÃ9ÿ×:ÿì;ÿ×<ÿ×=ÿì‚ÿìƒÿì„ÿì…ÿì†ÿì‡ÿìˆÿ׎ÿìÿìÿì‘ÿìŸÿ×ÂÿìÄÿìÆÿììÿìðÿìòÿì$ÿÃ&ÿÃ6ÿì8ÿ×:ÿ×;ÿì=ÿì?ÿìCÿì_ÿìiÿìqÿêÿì¼ÿÃúÿìüÿìþÿìÿ×(ÿì[ÿ×]ÿì@ÿì[ÿ×]ÿì@ÿìÿÃÿÃ$ÿì,ÿì7ÿÃ9ÿ×:ÿì;ÿ×<ÿ×=ÿì‚ÿìƒÿì„ÿì…ÿì†ÿì‡ÿìˆÿ׎ÿìÿìÿì‘ÿìŸÿ×ÂÿìÄÿìÆÿììÿìðÿìòÿì$ÿÃ&ÿÃ6ÿì8ÿ×:ÿ×;ÿì=ÿì?ÿìCÿì_ÿìiÿìqÿêÿì¼ÿÃúÿìüÿìþÿìÿ×(ÿì[ÿ×]ÿì@ÿì[ÿ×]ÿì@ÿì= == == == =!=! =!=! =")" )"0"6ÿì"7ÿ×"9ÿì":ÿì";ÿ×"<ÿ×"Yÿì"\ÿì"Ÿÿ×"¿ÿì"Áÿì"ÿì" ÿì""ÿì"#ÿì"$ÿ×"&ÿ×"6ÿì"8ÿ×"9ÿì":ÿ×"qÿ×"€ÿì"Šÿì"‘ÿì"”ÿì" ÿì"¼ÿ×"Üÿì"Ýÿì"úÿì"üÿì"þÿì"ÿ×"ÿì")" )#=# =#=# =$R$ R$ÿš$ÿš$")$$ÿ…$&ÿÃ$*ÿÃ$2ÿÃ$4ÿÃ$6ÿì$7$Dÿ…$Fÿ…$Gÿ…$Hÿ…$Jÿš$Pÿ®$Qÿ®$Rÿ…$Sÿ®$Tÿ…$Uÿ®$Vÿ…$Xÿ®$YÿÃ$ZÿÃ$[ÿÃ$\ÿÃ$]ÿÃ$‚ÿ…$ƒÿ…$„ÿ…$…ÿ…$†ÿ…$‡ÿ…$ˆÿq$‰ÿÃ$”ÿÃ$•ÿÃ$–ÿÃ$—ÿÃ$˜ÿÃ$šÿÃ$¢ÿ…$£ÿ…$¤ÿ…$¥ÿ…$¦ÿ…$§ÿ…$¨ÿ…$©ÿ…$ªÿ…$«ÿ…$¬ÿ…$­ÿ…$³ÿ®$´ÿ…$µÿ…$¶ÿ…$·ÿ…$¸ÿ…$ºÿ…$»ÿ®$¼ÿ®$½ÿ®$¾ÿ®$¿ÿÃ$ÁÿÃ$Âÿ…$Ãÿ…$Äÿ…$Åÿ…$Æÿ…$Çÿ…$ÈÿÃ$Éÿ…$Ëÿ…$Íÿ…$ÎÿÃ$Ïÿ…$Õÿ…$×ÿ…$Ùÿ…$Ûÿ…$Ýÿ…$ÞÿÃ$àÿÃ$âÿÃ$äÿÃ$ÿ®$ÿ®$ ÿ®$ÿÃ$ÿ…$ÿ…$ÿÃ$ÿ…$ÿÃ$ÿ…$ÿì$ÿ…$ÿ…$ ÿì$!ÿ…$"ÿì$#ÿ…$$$&$7ÿÃ$9ÿÃ$@ÿÃ$Cÿ…$Dÿ…$Jÿ…$_ÿ…$fÿÃ$iÿ…$mÿÃ$q$yÿ…${ÿ®$~ÿ…$€ÿÃ$‚ÿ…$„ÿ®$ŠÿÃ$Œÿ…$Žÿ…$ÿ…$‘ÿÃ$“ÿ…$”ÿÃ$–ÿ…$™ÿ…$›ÿ…$ ÿì$ªÿ…$¸ÿÃ$»ÿÃ$¼$Êÿ…$Ïÿ…$Øÿ…$Ûÿ…$ÜÿÃ$ÝÿÃ$Þÿ…$êÿ…$íÿ…$îÿ…$ûÿÃ$ýÿÃ$ÿÿÃ$ÿÃ$R$ R$(ÿ…&R& R&ÿš&ÿš&")&$ÿ…&&ÿÃ&*ÿÃ&2ÿÃ&4ÿÃ&6ÿì&7&Dÿ…&Fÿ…&Gÿ…&Hÿ…&Jÿš&Pÿ®&Qÿ®&Rÿ…&Sÿ®&Tÿ…&Uÿ®&Vÿ…&Xÿ®&YÿÃ&ZÿÃ&[ÿÃ&\ÿÃ&]ÿÃ&‚ÿ…&ƒÿ…&„ÿ…&…ÿ…&†ÿ…&‡ÿ…&ˆÿq&‰ÿÃ&”ÿÃ&•ÿÃ&–ÿÃ&—ÿÃ&˜ÿÃ&šÿÃ&¢ÿ…&£ÿ…&¤ÿ…&¥ÿ…&¦ÿ…&§ÿ…&¨ÿ…&©ÿ…&ªÿ…&«ÿ…&¬ÿ…&­ÿ…&³ÿ®&´ÿ…&µÿ…&¶ÿ…&·ÿ…&¸ÿ…&ºÿ…&»ÿ®&¼ÿ®&½ÿ®&¾ÿ®&¿ÿÃ&ÁÿÃ&Âÿ…&Ãÿ…&Äÿ…&Åÿ…&Æÿ…&Çÿ…&ÈÿÃ&Éÿ…&Ëÿ…&Íÿ…&ÎÿÃ&Ïÿ…&Õÿ…&×ÿ…&Ùÿ…&Ûÿ…&Ýÿ…&ÞÿÃ&àÿÃ&âÿÃ&äÿÃ&ÿ®&ÿ®& ÿ®&ÿÃ&ÿ…&ÿ…&ÿÃ&ÿ…&ÿÃ&ÿ…&ÿì&ÿ…&ÿ…& ÿì&!ÿ…&"ÿì&#ÿ…&$&&&7ÿÃ&9ÿÃ&@ÿÃ&Cÿ…&Dÿ…&Jÿ…&_ÿ…&fÿÃ&iÿ…&mÿÃ&q&yÿ…&{ÿ®&~ÿ…&€ÿÃ&‚ÿ…&„ÿ®&ŠÿÃ&Œÿ…&Žÿ…&ÿ…&‘ÿÃ&“ÿ…&”ÿÃ&–ÿ…&™ÿ…&›ÿ…& ÿì&ªÿ…&¸ÿÃ&»ÿÃ&¼&Êÿ…&Ïÿ…&Øÿ…&Ûÿ…&ÜÿÃ&ÝÿÃ&Þÿ…&êÿ…&íÿ…&îÿ…&ûÿÃ&ýÿÃ&ÿÿÃ&ÿÃ&R& R&(ÿ…'f' f' '"¤'@¤'ER'KR'L='M='NR'OR'`¸'®ö'°Í'±Í'çR'é¤'ë 'íÍ'ïö'ñ)'õ='÷á'ùR'üR'þR'R'R'R'' ,ÿ×,ÿ×,$ÿì,‚ÿì,ƒÿì,„ÿì,…ÿì,†ÿì,‡ÿì,ˆÿ×,Âÿì,Äÿì,Æÿì,Cÿì,_ÿì,iÿì,ªÿì,(ÿì0ÿ×0ÿ×0$ÿì0‚ÿì0ƒÿì0„ÿì0…ÿì0†ÿì0‡ÿì0ˆÿ×0Âÿì0Äÿì0Æÿì0Cÿì0_ÿì0iÿì0ªÿì0(ÿì2ÿ×2ÿ×2$ÿì2‚ÿì2ƒÿì2„ÿì2…ÿì2†ÿì2‡ÿì2ˆÿ×2Âÿì2Äÿì2Æÿì2Cÿì2_ÿì2iÿì2ªÿì2(ÿì4ÿ×4ÿ×4$ÿì4‚ÿì4ƒÿì4„ÿì4…ÿì4†ÿì4‡ÿì4ˆÿ×4Âÿì4Äÿì4Æÿì4Cÿì4_ÿì4iÿì4ªÿì4(ÿì6f6 f6ÿ®6ÿ®6$ÿ×6&ÿì6*ÿì62ÿì64ÿì6Dÿ×6Fÿ×6Gÿ×6Hÿ×6Jÿì6Pÿì6Qÿì6Rÿ×6Sÿì6Tÿ×6Uÿì6Vÿ×6Xÿì6]ÿì6‚ÿ×6ƒÿ×6„ÿ×6…ÿ×6†ÿ×6‡ÿ×6ˆÿ®6‰ÿì6”ÿì6•ÿì6–ÿì6—ÿì6˜ÿì6šÿì6¢ÿ×6£ÿ×6¤ÿ×6¥ÿ×6¦ÿ×6§ÿ×6¨ÿ×6©ÿ×6ªÿ×6«ÿ×6¬ÿ×6­ÿ×6³ÿì6´ÿ×6µÿ×6¶ÿ×6·ÿ×6¸ÿ×6ºÿ×6»ÿì6¼ÿì6½ÿì6¾ÿì6Âÿ×6Ãÿ×6Äÿ×6Åÿ×6Æÿ×6Çÿ×6Èÿì6Éÿ×6Ëÿ×6Íÿ×6Îÿì6Ïÿ×6Õÿ×6×ÿ×6Ùÿ×6Ûÿ×6Ýÿ×6Þÿì6àÿì6âÿì6äÿì6ÿì6ÿì6 ÿì6ÿì6ÿ×6ÿ×6ÿì6ÿ×6ÿì6ÿ×6ÿ×6ÿ×6!ÿ×6#ÿ×6@ÿì6Cÿ×6Dÿ×6Jÿ×6_ÿ×6fÿì6iÿ×6mÿì6yÿ×6{ÿì6~ÿ×6‚ÿ×6„ÿì6Œÿ×6Žÿ×6ÿ×6“ÿ×6–ÿ×6™ÿ×6›ÿ×6ªÿ×6¸ÿì6»ÿì6Êÿ×6Ïÿ×6Øÿ×6Ûÿ×6Þÿ×6êÿ×6íÿ×6îÿ×6f6 f6(ÿ×7R7 R7I7R7 R8R8 R8ÿš8ÿš8")8$ÿš8&ÿ×8*ÿ×82ÿ×84ÿ×86ÿì8Dÿš8Fÿš8Gÿš8Hÿš8Jÿš8PÿÃ8QÿÃ8Rÿš8SÿÃ8Tÿš8UÿÃ8Vÿ®8XÿÃ8[ÿ×8\ÿì8]ÿÃ8‚ÿš8ƒÿš8„ÿš8…ÿš8†ÿš8‡ÿš8ˆÿq8‰ÿ×8”ÿ×8•ÿ×8–ÿ×8—ÿ×8˜ÿ×8šÿ×8¢ÿš8£ÿš8¤ÿš8¥ÿš8¦ÿš8§ÿš8¨ÿš8©ÿš8ªÿš8«ÿš8¬ÿš8­ÿš8³ÿÃ8´ÿš8µÿš8¶ÿš8·ÿš8¸ÿš8ºÿš8»ÿÃ8¼ÿÃ8½ÿÃ8¾ÿÃ8¿ÿì8Áÿì8Âÿš8Ãÿš8Äÿš8Åÿš8Æÿš8Çÿš8Èÿ×8Éÿš8Ëÿš8Íÿš8Îÿ×8Ïÿš8Õÿš8×ÿš8Ùÿš8Ûÿš8Ýÿš8Þÿ×8àÿ×8âÿ×8äÿ×8ÿÃ8ÿÃ8 ÿÃ8ÿ×8ÿš8ÿš8ÿ×8ÿš8ÿ×8ÿš8ÿì8ÿ®8ÿ®8 ÿì8!ÿ®8"ÿì8#ÿ®89ÿì8@ÿÃ8Cÿš8Dÿš8Jÿ®8_ÿš8fÿ×8iÿš8mÿ×8yÿš8{ÿÃ8~ÿš8€ÿì8‚ÿš8„ÿÃ8Šÿì8Œÿš8Žÿš8ÿš8“ÿš8–ÿš8™ÿš8›ÿš8 ÿì8ªÿš8¸ÿ×8»ÿ×8Êÿš8Ïÿš8Øÿš8Ûÿš8Ýÿì8Þÿš8êÿš8íÿš8îÿ®8ÿì8R8 R8(ÿš9=9 =9I9=9 =:R: ÿÃ: R: =: ):ÿš:ÿ\:ÿš:"):$ÿš:&ÿ×:*ÿ×:-ÿ¾:0ÿì:2ÿ×:4ÿ×:6ÿì:7':@=:Dÿš:Fÿš:Gÿš:Hÿš:Iÿå:Jÿš:PÿÃ:QÿÃ:Rÿš:SÿÃ:Tÿš:UÿÃ:Vÿ®:XÿÃ:[ÿ×:\ÿì:]ÿÃ:`=:‚ÿš:ƒÿš:„ÿš:…ÿš:†ÿš:‡ÿš:ˆÿq:‰ÿ×:”ÿ×:•ÿ×:–ÿ×:—ÿ×:˜ÿ×:šÿ×:¢ÿš:£ÿš:¤ÿš:¥ÿš:¦ÿš:§ÿš:¨ÿš:©ÿš:ªÿš:«ÿš:¬ÿš:­ÿš:³ÿÃ:´ÿš:µÿš:¶ÿš:·ÿš:¸ÿš:ºÿš:»ÿÃ:¼ÿÃ:½ÿÃ:¾ÿÃ:¿ÿì:Áÿì:Âÿš:Ãÿš:Äÿš:Åÿš:Æÿš:Çÿš:Èÿ×:Éÿš:Ëÿš:Íÿš:Îÿ×:Ïÿš:Õÿš:×ÿš:Ùÿš:Ûÿš:Ýÿš:Þÿ×:àÿ×:âÿ×:äÿ×:öÿ¾:ÿÃ:ÿÃ: ÿÃ:ÿ×:ÿš:ÿš:ÿ×:ÿš:ÿ×:ÿš:ÿì:ÿ®:ÿ®: ÿì:!ÿ®:"ÿì:#ÿ®:$':&':9ÿì:@ÿÃ:Cÿš:Dÿš:Jÿ®:_ÿš:fÿ×:iÿš:mÿ×:q':yÿš:{ÿÃ:~ÿš:€ÿì:‚ÿš:„ÿÃ:Šÿì:Œÿš:Žÿš:ÿš:“ÿš:–ÿš:™ÿš:›ÿš: ÿì:£ÿ¾:ªÿš:¸ÿ×:»ÿ×:¼':Êÿš:Ïÿš:Øÿš:Ûÿš:Ýÿì:Þÿš:êÿš:íÿš:îÿ®:ÿì:R: R:ÿ×:(ÿš;); );&ÿì;*ÿì;2ÿì;4ÿì;‰ÿì;”ÿì;•ÿì;–ÿì;—ÿì;˜ÿì;šÿì;Èÿì;Îÿì;Þÿì;àÿì;âÿì;äÿì;ÿì;ÿì;ÿ×;fÿì;mÿì;¸ÿì;»ÿì;); )=)= )=&ÿì=*ÿì=2ÿì=4ÿì=‰ÿì=”ÿì=•ÿì=–ÿì=—ÿì=˜ÿì=šÿì=Èÿì=Îÿì=Þÿì=àÿì=âÿì=äÿì=ÿì=ÿì=ÿ×=fÿì=mÿì=¸ÿì=»ÿì=)= )?)? )?ÿÃ?&ÿì?*ÿì?2ÿì?4ÿì?6ÿ×?8ÿì?Fÿì?Gÿì?Hÿì?Rÿì?Tÿì?Yÿ×?Zÿ×?\ÿ×?‰ÿì?”ÿì?•ÿì?–ÿì?—ÿì?˜ÿì?šÿì?›ÿì?œÿì?ÿì?žÿì?¨ÿì?©ÿì?ªÿì?«ÿì?¬ÿì?­ÿì?´ÿì?µÿì?¶ÿì?·ÿì?¸ÿì?ºÿì?¿ÿ×?Áÿ×?Èÿì?Éÿì?Ëÿì?Íÿì?Îÿì?Ïÿì?Õÿì?×ÿì?Ùÿì?Ûÿì?Ýÿì?Þÿì?àÿì?âÿì?äÿì?ÿì?ÿì?ÿì?ÿì?ÿì?ÿ×?ÿì?ÿ×? ÿ×?"ÿ×?,ÿì?0ÿì?2ÿì?4ÿì?7ÿ×?9ÿ×?fÿì?mÿì?yÿì?~ÿì?€ÿ×?‚ÿì?Šÿ×?Œÿì?Žÿì?ÿì?‘ÿ×?“ÿì?”ÿ×?–ÿì?™ÿì?›ÿì? ÿ×?¸ÿì?»ÿì?Ïÿì?Øÿì?Ûÿì?Üÿ×?Ýÿ×?Þÿì?êÿì?íÿì?ûÿ×?ýÿ×?ÿÿ×?ÿ×?)? )@Rÿì@¨ÿì@´ÿì@µÿì@¶ÿì@·ÿì@¸ÿì@ºÿì@ÿì@ÿì@ÿì@ÿì@Œÿì@Žÿì@ÿì@“ÿì@–ÿì@™ÿì@›ÿì@Øÿì@Þÿì@ Cÿ®C ÿ®C&ÿìC*ÿìC2ÿìC4ÿìC7ÿ…C8ÿìC9ÿÃC:ÿ×C<ÿšC‰ÿìC”ÿìC•ÿìC–ÿìC—ÿìC˜ÿìCšÿìC›ÿìCœÿìCÿìCžÿìCŸÿšCÈÿìCÎÿìCÞÿìCàÿìCâÿìCäÿìCÿìCÿìCÿ×C$ÿ…C&ÿ…C,ÿìC0ÿìC2ÿìC4ÿìC6ÿ×C8ÿšC:ÿšCfÿìCmÿìCqÿ…C¸ÿìC»ÿìC¼ÿ…Cúÿ×Cüÿ×Cþÿ×CÿšCÿ®C ÿ®D ÿ×J=J =J=J =Vb)VfÿìVi)VmÿìVqÿ…VrÿšVsÿ×Vuÿ×VxÿšVˆ)V‘ÿÃZfÿìZmÿìZsÿì[_ÿì[dÿì[gÿì[lÿì[pÿì[qÿÃ[rÿ×[tÿ×[wÿì[xÿ×[ˆÿì\ÿÃ\mÿÃ\_ÿš\bÿÃ\fÿ×\iÿÃ\sÿ×\vÿ×\yÿš\zÿš\{ÿÃ\}ÿÃ\~ÿš\ÿ®\‚ÿš\ƒÿ×\„ÿÃ\…ÿ×\†ÿÃ\‡ÿÃ\‰ÿÃ\Œÿš\Žÿš\ÿš\ÿš\’ÿÃ\“ÿ®\”ÿ×\•ÿÃ\–ÿ®\˜ÿ×\™ÿš\šÿÃ\›ÿ®\ÿÃ\ÿÃ]qÿÃ]rÿÃ]xÿÃ^yÿ×^}ÿ×^~ÿ×^€ÿ×^ÿ×^ƒÿì^…ÿ×^†ÿ×^ˆ)^Šÿ×^‹ÿì^Œÿ×^ÿì^ÿì^ÿ×^‘ÿì^’ÿ×^“ÿ×^”ÿì^•ÿ×^–ÿì^˜ÿ×^™ÿ×^šÿ×^›ÿ×_ÿ®_ ÿ®_&ÿì_*ÿì_2ÿì_4ÿì_7ÿ…_8ÿì_9ÿÃ_:ÿ×_<ÿš_‰ÿì_”ÿì_•ÿì_–ÿì_—ÿì_˜ÿì_šÿì_›ÿì_œÿì_ÿì_žÿì_Ÿÿš_Èÿì_Îÿì_Þÿì_àÿì_âÿì_äÿì_ÿì_ÿì_ÿ×_$ÿ…_&ÿ…_,ÿì_0ÿì_2ÿì_4ÿì_6ÿ×_8ÿš_:ÿš_fÿì_mÿì_qÿ…_rÿš_sÿ×_uÿÃ_xÿš_‘ÿÃ_úÿ×_üÿ×_þÿ×_ÿš_ÿ®_ ÿ®`7ÿì`9ÿì`;ÿì`<ÿì`Ÿÿì`$ÿì`&ÿì`8ÿì`:ÿì`gÿì`lÿ×`pÿì`qÿì`rÿì`tÿì`wÿì`xÿì`‘ÿì`”ÿì`ÿìaÿša ÿšaÿšaÿšaÿša&ÿìa*ÿìa2ÿìa4ÿìa7ÿ…a8ÿìa9ÿ®a:ÿÃa<ÿšamÿša}ÿ®a‰ÿìa”ÿìa•ÿìa–ÿìa—ÿìa˜ÿìašÿìa›ÿìaœÿìaÿìažÿìaŸÿšaÈÿìaÎÿìaÞÿìaàÿìaâÿìaäÿìaÿìaÿìaÿìa$ÿ…a&ÿ…a,ÿìa0ÿìa2ÿìa4ÿìa6ÿÃa8ÿša:ÿša_ÿ…abÿšafÿìaiÿšamÿìasÿ®avÿ×ayÿ…azÿ…a{ÿ®a~ÿ…a€ÿÃaÿša‚ÿ…a„ÿÃa†ÿÃa‡ÿÃa‰ÿÃaŠÿÃaŒÿ…aÿÃaŽÿšaÿ…aÿ…a‘ÿÃa’ÿÃa“ÿ…a”ÿÃa•ÿÃa–ÿša—)a˜ÿÃa™ÿ…ašÿÃa›ÿšaúÿÃaüÿÃaþÿÃaÿšaÿšaÿšaÿša ÿšbRb Rbÿšbÿšb")b$ÿÃb&ÿ×b*ÿ×b2ÿ×b4ÿ×bDÿÃbFÿÃbGÿÃbHÿÃbJÿÃbPÿ×bQÿ×bRÿÃbSÿ×bTÿÃbUÿ×bVÿ×bXÿ×b‚ÿÃbƒÿÃb„ÿÃb…ÿÃb†ÿÃb‡ÿÃbˆÿ…b‰ÿ×b”ÿ×b•ÿ×b–ÿ×b—ÿ×b˜ÿ×bšÿ×b¢ÿÃb£ÿÃb¤ÿÃb¥ÿÃb¦ÿÃb§ÿÃb¨ÿÃb©ÿÃbªÿÃb«ÿÃb¬ÿÃb­ÿÃb³ÿ×b´ÿÃbµÿÃb¶ÿÃb·ÿÃb¸ÿÃbºÿÃb»ÿ×b¼ÿ×b½ÿ×b¾ÿ×bÂÿÃbÃÿÃbÄÿÃbÅÿÃbÆÿÃbÇÿÃbÈÿ×bÉÿÃbËÿÃbÍÿÃbÎÿ×bÏÿÃbÕÿÃb×ÿÃbÙÿÃbÛÿÃbÝÿÃbÞÿ×bàÿ×bâÿ×bäÿ×bÿ×bÿ×b ÿ×bÿ×bÿÃbÿÃbÿ×bÿÃbÿ×bÿÃbÿ×bÿ×b!ÿ×b#ÿ×bCÿÃbDÿÃbJÿ×bbbfÿ×bmÿ×bqÿšbrÿÃbsÿ×buÿ×bxÿÃbyÿÃbˆ)bÿ×bRb Rb(ÿÃdfÿìdmÿìdsÿìd’ÿ×d•ÿ×d—)d˜ÿ×dšÿ×fÿÃfÿÃf$ÿìf,ÿìf7ÿÃf9ÿ×f:ÿìf;ÿ×f<ÿ×f=ÿìf‚ÿìfƒÿìf„ÿìf…ÿìf†ÿìf‡ÿìfˆÿ×fŽÿìfÿìfÿìf‘ÿìfŸÿ×fÂÿìfÄÿìfÆÿìfìÿìfðÿìfòÿìf$ÿÃf&ÿÃf6ÿìf8ÿ×f:ÿ×f;ÿìf=ÿìf?ÿìfCÿìf_ÿìfbÿ×fgÿìfiÿìflÿìfpÿìfqÿÃfrÿ×ftÿ×fxÿ×fúÿìfüÿìfþÿìfÿ×f(ÿìgfÿìgmÿìgsÿìgÿìgˆ)h)h )h&ÿ×h*ÿ×h2ÿ×h4ÿ×h‰ÿ×h”ÿ×h•ÿ×h–ÿ×h—ÿ×h˜ÿ×hšÿ×hÈÿ×hÎÿ×hÞÿ×hàÿ×hâÿ×häÿ×hÿ×hÿ×hÿ×hfÿ×hmÿ×hsÿ®h‘ÿ×h—)h)h )iÿ®i ÿ®i")i$ÿÃi&ÿìi*ÿìi2ÿìi4ÿìi7ÿ…i8ÿìi9ÿÃi:ÿ×i<ÿšiDÿÃiFÿÃiGÿÃiHÿÃiJÿÃiPÿ×iQÿ×iRÿÃiSÿ×iTÿÃiUÿ×iVÿ×iXÿ×i‚ÿÃiƒÿÃi„ÿÃi…ÿÃi†ÿÃi‡ÿÃiˆÿ…i‰ÿìi”ÿìi•ÿìi–ÿìi—ÿìi˜ÿìišÿìi›ÿìiœÿìiÿìižÿìiŸÿši¢ÿÃi£ÿÃi¤ÿÃi¥ÿÃi¦ÿÃi§ÿÃi¨ÿÃi©ÿÃiªÿÃi«ÿÃi¬ÿÃi­ÿÃi³ÿ×i´ÿÃiµÿÃi¶ÿÃi·ÿÃi¸ÿÃiºÿÃi»ÿ×i¼ÿ×i½ÿ×i¾ÿ×iÂÿÃiÃÿÃiÄÿÃiÅÿÃiÆÿÃiÇÿÃiÈÿìiÉÿÃiËÿÃiÍÿÃiÎÿìiÏÿÃiÕÿÃi×ÿÃiÙÿÃiÛÿÃiÝÿÃiÞÿìiàÿìiâÿìiäÿìiÿ×iÿ×i ÿ×iÿìiÿÃiÿÃiÿìiÿÃiÿ×iÿÃiÿ×iÿ×i!ÿ×i#ÿ×i$ÿ…i&ÿ…i,ÿìi0ÿìi2ÿìi4ÿìi6ÿ×i8ÿši:ÿšiCÿÃiDÿÃiJÿ×ifÿìimÿìiqÿ…irÿšisÿ×iuÿÃixÿši‘ÿ×iúÿ×iüÿ×iþÿ×iÿšiÿ®i ÿ®i(ÿÃlfÿìlmÿìlˆ)mÿÃmÿÃm$ÿìm,ÿìm7ÿÃm9ÿ×m:ÿìm;ÿ×m<ÿ×m=ÿìm‚ÿìmƒÿìm„ÿìm…ÿìm†ÿìm‡ÿìmˆÿ×mŽÿìmÿìmÿìm‘ÿìmŸÿ×mÂÿìmÄÿìmÆÿìmìÿìmðÿìmòÿìm$ÿÃm&ÿÃm6ÿìm8ÿ×m:ÿ×m;ÿìm=ÿìm?ÿìmCÿìm_ÿìmgÿìmiÿìmlÿìmpÿìmqÿÃmrÿ×mtÿ×mwÿìmxÿ×múÿìmüÿìmþÿìmÿ×m(ÿìoÿ3oÿ3o$ÿ®o&ÿìo;ÿìo<ÿìo=ÿ×o‚ÿ®oƒÿ®o„ÿ®o…ÿ®o†ÿ®o‡ÿ®oˆÿqo‰ÿìoŸÿìoÂÿ®oÄÿ®oÆÿ®oÈÿìoÎÿìo8ÿìo:ÿìo;ÿ×o=ÿ×o?ÿ×oCÿ®o_ÿ®obÿ®odÿìoiÿ®otÿìoxÿìoÿ×oˆÿ×oŽÿ×oÿìo(ÿ®p)p )p&ÿ×p*ÿ×p2ÿ×p4ÿ×p‰ÿ×p”ÿ×p•ÿ×p–ÿ×p—ÿ×p˜ÿ×pšÿ×pÈÿ×pÎÿ×pÞÿ×pàÿ×pâÿ×päÿ×pÿ×pÿ×pÿ×pfÿ×pmÿ×psÿ×pˆ)p)p )qRq Rqÿšqÿšqÿšq")q$ÿ…q&ÿÃq*ÿÃq2ÿÃq4ÿÃq6ÿìq7qDÿ…qFÿ…qGÿ…qHÿ…qJÿšqPÿ®qQÿ®qRÿ…qSÿ®qTÿ…qUÿ®qVÿ…qXÿ®qYÿÃqZÿÃq[ÿÃq\ÿÃq]ÿÃqmÿšq}ÿ×q‚ÿ…qƒÿ…q„ÿ…q…ÿ…q†ÿ…q‡ÿ…qˆÿqq‰ÿÃq”ÿÃq•ÿÃq–ÿÃq—ÿÃq˜ÿÃqšÿÃq¢ÿ…q£ÿ…q¤ÿ…q¥ÿ…q¦ÿ…q§ÿ…q¨ÿ…q©ÿ…qªÿ…q«ÿ…q¬ÿ…q­ÿ…q³ÿ®q´ÿ…qµÿ…q¶ÿ…q·ÿ…q¸ÿ…qºÿ…q»ÿ®q¼ÿ®q½ÿ®q¾ÿ®q¿ÿÃqÁÿÃqÂÿ…qÃÿ…qÄÿ…qÅÿ…qÆÿ…qÇÿ…qÈÿÃqÉÿ…qËÿ…qÍÿ…qÎÿÃqÏÿ…qÕÿ…q×ÿ…qÙÿ…qÛÿ…qÝÿ…qÞÿÃqàÿÃqâÿÃqäÿÃqÿ®qÿ®q ÿ®qÿÃqÿ…qÿ…qÿÃqÿ…qÿÃqÿ…qÿìqÿ…qÿ…q ÿìq!ÿ…q"ÿìq#ÿ…q$q&q7ÿÃq9ÿÃq@ÿÃqCÿ…qDÿ…qJÿ…q_ÿ…qbÿšqfÿÃqiÿ…qmÿÃqqqsÿÃqvÿÃqyÿ…qzÿ…q{ÿ®q}ÿ®q~ÿ…q€ÿÃqÿ®q‚ÿ…q„ÿ®q†ÿ®q‡ÿ®qˆq‰ÿ®qŠÿÃqŒÿ…qŽÿ…qÿ…qÿ…q‘ÿÃq’ÿ®q“ÿ…q”ÿÃq•ÿ®q–ÿ…q—=q˜ÿ®q™ÿ…qšÿ®q›ÿ…qûÿÃqýÿÃqÿÿÃqÿÃqÿšqÿšqRq Rq(ÿ…rÿšrÿÃrÿšrmÿÃr_ÿšrbÿÃrfÿ×riÿšrmÿ×rsÿÃrvÿ×ryÿšrzÿ®r{ÿÃr|ÿìr}ÿ×r~ÿšrÿ×rÿšr‚ÿ®rƒÿ×r„ÿÃr…ÿ×r†ÿÃr‡ÿÃrˆr‰ÿÃr‹ÿìrŒÿšrŽÿšrÿšrÿšr‘ÿìr’ÿÃr“ÿšr”ÿ×r•ÿÃr–ÿ®r—)r˜ÿÃr™ÿšršÿÃr›ÿ®rÿÃrÿÃs_ÿ×sbÿ×sgÿìsiÿ×sqÿÃsrÿÃstÿÃswÿ×sxÿÃsˆÿ×tfÿ×tmÿ×tsÿÃtyÿìt|ÿìt}ÿìt~ÿìtÿìt…ÿìt†ÿìtˆtŒÿìtÿìtÿìt‘ÿ×t’ÿ×t“ÿìt•ÿ×t–ÿìt˜ÿ×t™ÿìtšÿ×t›ÿìuÿ®uÿ®u_ÿÃubÿ×uiÿÃuÿ×uŽÿ×uÿìu“ÿìu–ÿìu™ÿìu›ÿìvqÿÃvrÿ×vxÿ×w)w )w&ÿìw*ÿìw2ÿìw4ÿìw‰ÿìw”ÿìw•ÿìw–ÿìw—ÿìw˜ÿìwšÿìwÈÿìwÎÿìwÞÿìwàÿìwâÿìwäÿìwÿìwÿìwÿìwfÿìwmÿìwsÿ×w€ÿìwÿìwˆw‘ÿìw)w )xRx ÿÃx Rx =x )xÿšxÿ\xÿšx")x$ÿšx&ÿ×x*ÿ×x-ÿ¾x0ÿìx2ÿ×x4ÿ×x6ÿìx7'x@=xDÿšxFÿšxGÿšxHÿšxIÿåxJÿšxPÿÃxQÿÃxRÿšxSÿÃxTÿšxUÿÃxVÿ®xXÿÃx[ÿ×x\ÿìx]ÿÃx`=xmÿÃx}ÿ×x‚ÿšxƒÿšx„ÿšx…ÿšx†ÿšx‡ÿšxˆÿqx‰ÿ×x”ÿ×x•ÿ×x–ÿ×x—ÿ×x˜ÿ×xšÿ×x¢ÿšx£ÿšx¤ÿšx¥ÿšx¦ÿšx§ÿšx¨ÿšx©ÿšxªÿšx«ÿšx¬ÿšx­ÿšx³ÿÃx´ÿšxµÿšx¶ÿšx·ÿšx¸ÿšxºÿšx»ÿÃx¼ÿÃx½ÿÃx¾ÿÃx¿ÿìxÁÿìxÂÿšxÃÿšxÄÿšxÅÿšxÆÿšxÇÿšxÈÿ×xÉÿšxËÿšxÍÿšxÎÿ×xÏÿšxÕÿšx×ÿšxÙÿšxÛÿšxÝÿšxÞÿ×xàÿ×xâÿ×xäÿ×xöÿ¾xÿÃxÿÃx ÿÃxÿ×xÿšxÿšxÿ×xÿšxÿ×xÿšxÿìxÿ®xÿ®x ÿìx!ÿ®x"ÿìx#ÿ®x$'x&'x9ÿìx@ÿÃxCÿšxDÿšxJÿ®x_ÿšxbÿÃxfÿ×xiÿšxmÿ×xsÿÃxvÿ×xyÿšxzÿ®x{ÿÃx|ÿìx}ÿÃx~ÿšxÿ×x€ÿìxÿšx‚ÿšxƒÿ×x„ÿÃx†ÿÃx‡ÿÃx‰ÿÃxŠÿìx‹ÿìxŒÿšxÿ×xŽÿšxÿšxÿšx‘ÿ×x’ÿÃx“ÿšx”ÿ×x•ÿÃx–ÿšx—)x˜ÿÃx™ÿšxšÿÃx›ÿšxÿìxRx Rxÿ×x(ÿšyˆ)zyÿ×z~ÿìzÿìzŒÿìzÿìz“ÿì{ ÿ×|yÿì||ÿì|}ÿì|€ÿì|ÿì|…ÿì|†ÿì|ˆ)|Šÿ×|Œÿì|ÿì|ÿì|ÿì|‘ÿ×|’ÿì|•ÿì|—ÿì|˜ÿì|™ÿì|šÿì~ˆ)”ÿì€=€ =€I€ÿì€)€Žÿ쀑€”€–€=€ =‘ÿì”ÿׂ=‚ =‚yÿׂÿ삌ÿׂÿì‚ÿׂ“ÿׂ™ÿׂ=‚ =ƒyÿ®ƒzÿ׃}ÿ׃~ÿÀÿ׃ÿÂÿ׃ƒÿ׃„ÿ׃…ÿ׃†ÿ׃‡ÿ새)ƒŠÿ׃‹ÿ׃ŒÿÃÿÃÿÃÿÑÿÃ’ÿÓÿÕÿÖÿØÿÙÿÚÿÛÿÄ ÿ׆R† R†yÿ׆}ÿì†~ÿ솀ÿ׆…ÿ׆†ÿ솈)†Šÿ׆ÿ׆‘ÿ׆“ÿ׆—ÿì†R† R‡)‡ )‡yÿׇ~ÿׇÿׇ‚ÿ쇌ÿì‡)‡ )ˆ=ˆ =ˆIˆ}ÿ숀ÿ숅ÿ׈ˆ=ˆŠÿ׈ÿ׈‘ÿÈ”ÿ׈=ˆ =Š=Š =ŠIŠŠŽÿìŠ=Š =‹=‹~ÿ싈)Œ[ÿ׌]ÿìŒ@ÿ쌑ÿ쌔ÿ×~ÿ쀊)‘Ž[ÿ׎]ÿìŽ@ÿ쎑ÿ쎔ÿ×[ÿ×]ÿì@ÿ쀊‘)‘R‘ R‘I‘})‘€‘ÿ쑊)‘Œÿב‘ÿבÿì‘‘‘R‘ R“[ÿד]ÿì“@ÿì“”ÿì”R” R”I”yÿ×”zÿ×”~ÿ×”€”ÿ×”‚ÿ×”…ÿ씌ÿ×”ÿ×”ÿ×”’ÿ씓ÿì””)”•ÿ×”–ÿ×”™ÿì”R” R•[ÿו]ÿì•@ÿì–[ÿ×–]ÿì–@ÿì–€—yÿì—}ÿì—~ÿ×—€ÿ×—ÿì—…ÿ×—ˆ)—Œÿì—ÿ×—ÿì—ÿì—‘ÿ×—’ÿì—”ÿì—•ÿ×—˜ÿì—™ÿì—šÿì—›ÿì™[ÿ×™]ÿì™@ÿì›[ÿ×›]ÿì›@ÿìœ)œ )œÿל&ÿìœ-=œ2ÿìœ4ÿ위ÿ윉ÿ윊ÿìœÿ윔ÿ윕ÿ윖ÿ윗ÿ윘ÿ윚ÿìœÈÿìœÎÿìœö=œÿìœÿ윸ÿ윻ÿ윾ÿìœ)œ )ÿà ÿÃÿ…¦ÿ…°ÿ×¼ÿ…½ÿ׿ÿìÁÿÃÄÿ…ÜÿÃÝÿìßÿìáÿÃäÿ®ÿà ÿÞRž Ržÿ\ž&ÿìž*ÿìž2ÿìž4ÿìž7ÿ…ž8ÿìž9ÿ®ž:ÿÞ<ÿšž‰ÿìž”ÿìž•ÿìž–ÿìž—ÿ잘ÿìžšÿìž›ÿìžœÿìžÿìžžÿ잟ÿšžÈÿìžÎÿìžÞÿìžàÿìžâÿìžäÿìžÿìžÿìžÿìž$ÿ…ž&ÿ…ž,ÿìž0ÿìž2ÿìž4ÿìž6ÿÞ8ÿšž:ÿšžŸÿ잤ÿšžªÿ…ž®ÿ…žµÿ…ž¸ÿמ»ÿìž¾ÿÞÊÿ…žËÿמÌÿÞÍÿÞÎÿ\žÏÿ…žÐÿÞÑÿÞÒÿÞÓÿÞÔÿÞÕÿ\žÖÿÞ×ÿÞØÿ…žÙÿÞÚÿÞÛÿ…žÜÿÞÝÿÞÞÿ…žßÿÞàÿÞáÿÞâÿÞãÿÞäÿÞåÿÞæÿÞçÿÞèÿÞéÿšžêÿ…žìÿÞíÿ…žîÿ®žð=žòÿ\žóÿÞõÿÞ÷ÿÞùÿÞúÿÞüÿÞþÿÞÿšžRž RŸ)Ÿ )ŸŸÿן¸ÿ쟻ÿן¾ÿìŸÞÿןáÿן)Ÿ )  ÿì ÜÿסŸÿì¡ÜÿסÝÿì¡áÿסäÿì¢)¢ )¢&ÿì¢*ÿì¢2ÿì¢4ÿ좉ÿ좔ÿ좕ÿ좖ÿ좗ÿ좘ÿ좚ÿì¢Èÿì¢Îÿì¢Þÿì¢àÿì¢âÿì¢äÿì¢ÿì¢ÿì¢ÿ좟ÿ좸ÿ좻ÿì¢ )¤ÿš¤ ÿš¤¦ÿ…¤¨ÿפ°ÿפµÿ줼ÿ…¤½ÿ줿ÿì¤Áÿ®¤Äÿ…¤Üÿפáÿפäÿäÿš¤ ÿš¥ÿš¥ ÿš¥ÿ…¥¦ÿ…¥¨ÿ×¥°ÿ쥼ÿ…¥½ÿ×¥Áÿ®¥Äÿ…¥Üÿ×¥ßÿì¥áÿì¥äÿ×¥ÿš¥ ÿš¦ÿš¦¦ÿš¦Äÿ…¦ÜÿצÝÿì¦áÿצäÿæöÿì§)§ )§&ÿ×§*ÿ×§2ÿ×§4ÿ×§‰ÿ×§”ÿ×§•ÿ×§–ÿ×§—ÿ×§˜ÿ×§šÿ×§Èÿ×§Îÿ×§Þÿ×§àÿ×§âÿ×§äÿ×§ÿ×§ÿ×§ÿ×§Ÿÿ×§¤)§µ)§¸ÿ×§»ÿ×§¾ÿ®§Ëÿì§Î§Ïÿ×§Øÿ×§Ûÿ×§Üÿ×§Ýÿ×§Þÿ×§áÿçäÿ×§êÿ×§íÿ×§)§ )¨R¨ R¨¤ÿš¨ªÿ…¨®ÿq¨µÿš¨»ÿר¼)¨¾ÿרĨÉÿì¨Êÿ®¨ÌÿרÍÿרÎÿ\¨Ïÿ®¨ÑÿרÒÿרÓÿרÔÿרÕÿ\¨Öÿר×ÿרØÿ®¨ÙÿרÚÿרÛÿ®¨Þÿ®¨àÿרáÿרâÿרãÿרåÿרæÿרèÿרéÿרêÿרìÿרíÿ®¨îÿרðR¨òÿq¨óÿרõÿר÷ÿרùÿרR¨ Rªÿ®ª ÿ®ª&ÿìª*ÿìª2ÿìª4ÿìª7ÿ…ª8ÿìª9ÿê:ÿת<ÿšª‰ÿ쪔ÿ쪕ÿ쪖ÿ쪗ÿ쪘ÿ쪚ÿ쪛ÿ쪜ÿìªÿ쪞ÿ쪟ÿšªÈÿìªÎÿìªÞÿìªàÿìªâÿìªäÿìªÿìªÿìªÿת$ÿ…ª&ÿ…ª,ÿìª0ÿìª2ÿìª4ÿìª6ÿת8ÿšª:ÿšªÿšªŸÿ쪤=ª¦ÿ…ª®)ªµ)ª¸ÿ쪻ÿ쪼ÿ…ª¾ÿתÁÿ®ªÄÿšªÕ)ªÜÿתáÿêäÿêç)ªò)ªúÿתüÿתþÿתÿšªÿ®ª ÿ®«ÿ׫¼ÿ׫½ÿ׫¿ÿì«ÁÿëÄÿ׫Ðÿì«Üÿ׫áÿ׫äÿ׬7ÿì¬9ÿì¬;ÿì¬<ÿ쬟ÿì¬$ÿì¬&ÿì¬8ÿì¬:ÿ쬰ÿ쬼ÿ쬽ÿ쬿ÿì¬ÿì­R­ R­ÿ\­Ÿÿì­¤ÿš­ªÿ…­®ÿ…­µÿ…­¸ÿ×­¾ÿíÊÿ…­ÌÿíÍÿíÎÿ\­Ïÿ…­ÐÿíÑÿíÒÿíÓÿíÔÿíÕÿ\­Öÿí×ÿíØÿ…­ÙÿíÚÿíÛÿ…­ÜÿíÝÿíÞÿ…­ßÿíàÿíáÿíâÿíãÿíäÿíåÿíæÿíçÿíèÿíéÿš­êÿ…­ìÿííÿ…­îÿ®­ð=­òÿ\­óÿíõÿí÷ÿíùÿíR­ R®ÿ×®£ö®¤)®¦ÿ×®ª®®)®µ)®¸ÿì®»ÿ쮼ÿ×®¾ÿì®Áÿ×®Äÿ×®Î)®Õ)®áÿ×®ç)®ñf®ò)°=° =°ÿ×°Ÿÿ×°¤)°µ)°¸ÿ×°»ÿ×°¾ÿðËÿ×°Õ)°Üÿ×°áÿ®°ò)°=° =±)± )±±ÿì±µÿ×±¼ÿ×±½ÿì±¾ÿ챿ÿ×±Áÿì±Äÿì±Çÿì±)± )´)´ )´ÿ×´&ÿ×´*ÿ×´2ÿ×´4ÿ×´‰ÿ×´”ÿ×´•ÿ×´–ÿ×´—ÿ×´˜ÿ×´šÿ×´Èÿ×´Îÿ×´Þÿ×´àÿ×´âÿ×´äÿ×´ÿ×´ÿ×´ÿ×´Ÿÿ×´¤=´µ)´¸ÿ×´»ÿ×´¾ÿ®´Ëÿ×´Õ)´áÿ®´äÿ×´ò)´)´ )¸ÿøÿø$ÿì¸,ÿì¸7ÿø9ÿ׸:ÿì¸;ÿ׸<ÿ׸=ÿ츂ÿ츃ÿ츄ÿ츅ÿ츆ÿ츇ÿ츈ÿ׸Žÿì¸ÿì¸ÿ츑ÿ츟ÿ׸Âÿì¸Äÿì¸Æÿì¸ìÿì¸ðÿì¸òÿì¸$ÿø&ÿø6ÿì¸8ÿ׸:ÿ׸;ÿì¸=ÿì¸?ÿì¸Cÿì¸ÿ׸¤ÿ׸¦ÿ׸ªÿ츮ÿ츰ÿ׸¼ÿø¿ÿì¸Äÿ׸úÿì¸üÿì¸þÿì¸ÿ׸(ÿìº=º =ºÿ3ºÿ3º$ÿ®º&ÿìº;ÿìº<ÿìº=ÿ׺‚ÿ®ºƒÿ®º„ÿ®º…ÿ®º†ÿ®º‡ÿ®ºˆÿqº‰ÿ캟ÿìºÂÿ®ºÄÿ®ºÆÿ®ºÈÿìºÎÿìº8ÿìº:ÿìº;ÿ׺=ÿ׺?ÿ׺Cÿ®º¤ÿ®ºªÿ®º®ÿšºµÿšº»ÿìºÎÿšºÕÿ®ºòÿ®ºÿìº=º =º(ÿ®»)» )» )»&ÿ×»*ÿ×»2ÿ×»4ÿ×»@)»`)»‰ÿ×»”ÿ×»•ÿ×»–ÿ×»—ÿ×»˜ÿ×»šÿ×»Èÿ×»Îÿ×»Þÿ×»àÿ×»âÿ×»äÿ×»ÿ×»ÿ×»Ÿÿ×»¸ÿ×»»ÿ×»¾ÿûáÿû)» )¼R¼ R¼ÿš¼ÿš¼ÿš¼")¼$ÿ…¼&ÿü*ÿü2ÿü4ÿü6ÿì¼7¼Dÿ…¼Fÿ…¼Gÿ…¼Hÿ…¼Jÿš¼Pÿ®¼Qÿ®¼Rÿ…¼Sÿ®¼Tÿ…¼Uÿ®¼Vÿ…¼Xÿ®¼YÿüZÿü[ÿü\ÿü]ÿü‚ÿ…¼ƒÿ…¼„ÿ…¼…ÿ…¼†ÿ…¼‡ÿ…¼ˆÿq¼‰ÿü”ÿü•ÿü–ÿü—ÿü˜ÿüšÿü¢ÿ…¼£ÿ…¼¤ÿ…¼¥ÿ…¼¦ÿ…¼§ÿ…¼¨ÿ…¼©ÿ…¼ªÿ…¼«ÿ…¼¬ÿ…¼­ÿ…¼³ÿ®¼´ÿ…¼µÿ…¼¶ÿ…¼·ÿ…¼¸ÿ…¼ºÿ…¼»ÿ®¼¼ÿ®¼½ÿ®¼¾ÿ®¼¿ÿüÁÿüÂÿ…¼Ãÿ…¼Äÿ…¼Åÿ…¼Æÿ…¼Çÿ…¼ÈÿüÉÿ…¼Ëÿ…¼Íÿ…¼ÎÿüÏÿ…¼Õÿ…¼×ÿ…¼Ùÿ…¼Ûÿ…¼Ýÿ…¼Þÿüàÿüâÿüäÿüÿ®¼ÿ®¼ ÿ®¼ÿüÿ…¼ÿ…¼ÿüÿ…¼ÿüÿ…¼ÿì¼ÿ…¼ÿ…¼ ÿì¼!ÿ…¼"ÿì¼#ÿ…¼$¼&¼7ÿü9ÿü@ÿüCÿ…¼Dÿ…¼Jÿ…¼Ÿÿü ÿ켤ÿ®¼ªÿ…¼®ÿš¼µÿš¼¸ÿü»ÿü¼¼¾ÿüļÊÿ…¼ÌÿüÍÿüÎÿq¼Ïÿ…¼ÐÿüÑÿüÒÿüÔÿüÕÿq¼Öÿü×ÿüØÿ…¼ÙÿüÚÿüÛÿ…¼ÜÿüÝÿüÞÿ…¼ßÿüàÿüáÿüâÿüãÿüåÿüæÿüèÿüéÿüêÿ…¼ë)¼ìÿüíÿ…¼îÿ…¼ðR¼òÿ…¼óÿüõÿü÷ÿüùÿüûÿüýÿüÿÿüÿüR¼ R¼(ÿ…½=½ =½ÿ×½Ÿÿ×½¤ÿ®½ªÿš½®ÿ…½µÿš½»ÿ×½¾ÿì½Ä)½Êÿ®½ÌÿýÍÿýÎÿq½ÏÿýÒÿýÓÿýÔÿýÕÿq½Öÿý×ÿýØÿš½ÙÿýÚÿýÛÿýÞÿ®½àÿýáÿýâÿýãÿýåÿýæÿýèÿýéÿýêÿýìÿýíÿýîÿýð=½òÿ…½óÿýõÿý÷ÿýùÿý=½ =¾ÿ×¾¤ÿ×¾¦ÿþ¨ÿ쾪ÿ×¾®ÿ×¾°ÿþµÿ×¾¼ÿþ¿ÿ×¾ÄÿþÇÿ×¾ÎÿþÕÿþòÿÿ)¿ )¿Ÿÿ׿¤=¿®)¿µ)¿¸ÿì¿»ÿ׿¾ÿ׿Áÿì¿áÿÿ)¿ )À£öÀ¤)ÀªÀ®)Àµ)À¼ÿìÀ¾ÿìÀ¿ÀÁÿìÀÎ)ÀÕÀáÿ×Àç)ÀñfÀò)Ãÿìãáä=ê)î)õ)üÿìý)þÿìÿÃÁÿìÃÄÿìÃÇÃÎ=ÃÑÃÕ)ÃÜÿìÃáÿ×ÃäÿìÃç)ÃñfÃò)ÄÿÃÄ ÿÃÄÿ…ĦÿqĨÿ×ļÿ…ÄÁÿÃÄÄÿ…ÄÜÿ×Ääÿ×ÄÿÃÄ ÿÃÆÿšÆ ÿšÆÿ…Ʀÿ…ƨÿׯ¼ÿ…ÆÁÿÃÆÄÿ…ÆÜÿׯäÿׯÿšÆ ÿšÇÿ×Ǥÿ×Ǧÿ×Ǩÿ×Ç®ÿ×ǰÿÃDZÿìǵÿÃǼÿ×ǽÿìÇ¿ÿ×ÇÕÿ×Çòÿ×Èÿ×Ȥÿ×Ȧÿ×ȨÿìÈ®ÿ×Ȱÿ×ȱÿìȵÿ×ȽÿìÈ¿ÿ×ÈÎÿ×ÈÕÿ×Èòÿ×Ê ÿ×ÊÜÿ×ÊÝÿìÊáÿìÊäÿ×Êöÿ×ËÎÿ×ËÐÿ×ËÜÿ×ËÝÿ×Ëßÿ×ËáÿìËäÿ×Ëöÿ×Ì=Ì =ÌÐÿìÌÜÿ×ÌÝÿ×Ìßÿ×ÌáÿìÌäÿ×Ìöÿ×Ì=Ì =ÍÎÿšÍÕÿšÍíÿìÍòÿšÎÊÎÎ)ÎÜÿ×Îáÿ×ÎäÿÃÎñ{Ï ÿ×ÏÐÿ×ÏÜÿìÏßÿìÐ)Ð )ÐÏÿ×ÐØÿ×ÐÛÿìÐÞÿ×Ðáÿ×Ðêÿ×Ðíÿ×Ð)Ð )Ñ=Ñ =ÑÑÿìÑÜÿìÑÝÿìÑßÿìÑáÿ×ÑöÿìÑ=Ñ =Ô)Ô )ÔËÿ×ÔÏÿ×ÔØÿ×ÔÛÿ×ÔÞÿ×Ôáÿ×Ôêÿ×Ôíÿ×Ô)Ô )Ø[ÿר]ÿìØ@ÿìØÐÿרÑÿìØÕÿìØÜÿרÝÿìØßÿרòÿìØöÿìÚÐÿ×ÚÑÿìÚÕÿìÚÜÿìÚßÿìÚäÿìÚòÿìÛ=Û =ÛÏÿìÛØÿìÛíÿìÛ=Û =ÜRÜ RÜIÜÊÿ×ÜÎÿšÜÏÿìÜÕÿÃÜØÿ×ÜÛÿ×ÜÝÜÞÿ×Üíÿ×ÜòÿšÜöÜRÜ RÝ=Ý =ÝIÝÊÿìÝÎÿÃÝÕÿÃÝØÿìÝÜÝÞÿìÝòÿÃÝ=Ý =Þ[ÿ×Þ]ÿìÞ@ÿìÞÐÿ×ÞÑÿìÞÕÿìÞÜÿ×ÞßÿìÞáÿìÞäÿ×Þòÿ×ßËÿìßÏÿìߨÿìßÛÿìßÞÿìßáÿìßêÿìßíÿìàÎ)àÕ)àÜÿìàáÿìàäÿ×àç)àéàñfàò)àöãÎ=ãÕ)ãÜÿìãáÿìãäÿìãçãêÿìãíÿìãñfãò)ãöäÜÿšäÝÿ×äßÿìäáÿ×ääÿqäöÿÃå=å =å=å =æ=æ =æÜÿšæÝÿ׿ßÿìæáÿ׿äÿqæ=æ =çÎÿ×çÐÿ×çÑÿìçÕÿìçßÿìçäÿ×çòÿìçöÿ×èÎÿ×èÐÿ×èÑÿìèÕÿìèßÿìèäÿ×èöÿìé=é =é=é =êÿ˜ê ÿ×êßÿììÎÿšìÏÿ×ìÕÿšìØÿ×ìÛÿììÞÿ×ìêÿ×ìíÿììòÿší=í =í=í =î=î =î=î =ðë=ðô=òÐÿ×òÜÿšòÝÿÃòÞÿìòßÿìòáÿ×òäÿšòöÿ×óÐÿ×óÜÿšóÝÿÃóßÿìóáÿ×óäÿšôöÿ×õ)õ )õËÿìõÏÿ×õØÿ×õÛÿìõÞÿ×õêÿ×õíÿ×õöÿìõ)õ )öÊÿ×öÕÿÃöØÿìöÜöòÿ×øRø Røÿ\ø&ÿìø*ÿìø2ÿìø4ÿìø7ÿ…ø8ÿìø9ÿ®ø:ÿÃø<ÿšø‰ÿìø”ÿìø•ÿìø–ÿìø—ÿìø˜ÿìøšÿìø›ÿìøœÿìøÿìøžÿìøŸÿšøÈÿìøÎÿìøÞÿìøàÿìøâÿìøäÿìøÿìøÿìøÿìø$ÿ…ø&ÿ…ø,ÿìø0ÿìø2ÿìø4ÿìø6ÿÃø8ÿšø:ÿšøŸÿìø¤ÿšøªÿ…ø®ÿ…øµÿ…ø¸ÿ×ø»ÿìø¼ÿ…ø¾ÿÃøÊÿ…øÌÿÃøÍÿÃøÎÿ\øÏÿ…øÐÿÃøÑÿÃøÒÿÃøÓÿÃøÔÿÃøÕÿ\øÖÿÃø×ÿÃøØÿ…øÙÿÃøÚÿÃøÛÿ…øÜÿÃøÝÿÃøÞÿ…øßÿÃøàÿÃøáÿÃøâÿÃøãÿÃøäÿÃøåÿÃøæÿÃøçÿÃøèÿÃøéÿšøêÿ…øìÿÃøíÿ…øîÿ®øð=øòÿ\øóÿÃøõÿÃø÷ÿÃøùÿÃøúÿÃøüÿÃøþÿÃøÿšøRø RùÎÿšùÕÿšùíÿìùòÿšúfú fúÿ®úÿ®ú$ÿ×ú&ÿìú*ÿìú2ÿìú4ÿìúDÿ×úFÿ×úGÿ×úHÿ×úJÿìúPÿìúQÿìúRÿ×úSÿìúTÿ×úUÿìúVÿ×úXÿìú]ÿìú‚ÿ×úƒÿ×ú„ÿ×ú…ÿ×ú†ÿ×ú‡ÿ×úˆÿ®ú‰ÿìú”ÿìú•ÿìú–ÿìú—ÿìú˜ÿìúšÿìú¢ÿ×ú£ÿ×ú¤ÿ×ú¥ÿ×ú¦ÿ×ú§ÿ×ú¨ÿ×ú©ÿ×úªÿ×ú«ÿ×ú¬ÿ×ú­ÿ×ú³ÿìú´ÿ×úµÿ×ú¶ÿ×ú·ÿ×ú¸ÿ×úºÿ×ú»ÿìú¼ÿìú½ÿìú¾ÿìúÂÿ×úÃÿ×úÄÿ×úÅÿ×úÆÿ×úÇÿ×úÈÿìúÉÿ×úËÿ×úÍÿ×úÎÿìúÏÿ×úÕÿ×ú×ÿ×úÙÿ×úÛÿ×úÝÿ×úÞÿìúàÿìúâÿìúäÿìúÿìúÿìú ÿìúÿìúÿ×úÿ×úÿìúÿ×úÿìúÿ×úÿ×úÿ×ú!ÿ×ú#ÿ×ú@ÿìúCÿ×úDÿ×úJÿ×ú_ÿ×úfÿìúiÿ×úmÿìúyÿ×ú{ÿìú~ÿ×ú‚ÿ×ú„ÿìúŒÿ×úŽÿ×úÿ×ú“ÿ×ú–ÿ×ú™ÿ×ú›ÿ×úªÿ×ú¸ÿìú»ÿìúÊÿ×úÏÿ×úØÿ×úÛÿ×úÞÿ×úêÿ×úíÿ×úîÿ×úfú fú(ÿ×ûRû RûIûRû Rüfü füÿ®üÿ®ü$ÿ×ü&ÿìü*ÿìü2ÿìü4ÿìüDÿ×üFÿ×üGÿ×üHÿ×üJÿìüPÿìüQÿìüRÿ×üSÿìüTÿ×üUÿìüVÿ×üXÿìü]ÿìü‚ÿ×üƒÿ×ü„ÿ×ü…ÿ×ü†ÿ×ü‡ÿ×üˆÿ®ü‰ÿìü”ÿìü•ÿìü–ÿìü—ÿìü˜ÿìüšÿìü¢ÿ×ü£ÿ×ü¤ÿ×ü¥ÿ×ü¦ÿ×ü§ÿ×ü¨ÿ×ü©ÿ×üªÿ×ü«ÿ×ü¬ÿ×ü­ÿ×ü³ÿìü´ÿ×üµÿ×ü¶ÿ×ü·ÿ×ü¸ÿ×üºÿ×ü»ÿìü¼ÿìü½ÿìü¾ÿìüÂÿ×üÃÿ×üÄÿ×üÅÿ×üÆÿ×üÇÿ×üÈÿìüÉÿ×üËÿ×üÍÿ×üÎÿìüÏÿ×üÕÿ×ü×ÿ×üÙÿ×üÛÿ×üÝÿ×üÞÿìüàÿìüâÿìüäÿìüÿìüÿìü ÿìüÿìüÿ×üÿ×üÿìüÿ×üÿìüÿ×üÿ×üÿ×ü!ÿ×ü#ÿ×ü@ÿìüCÿ×üDÿ×üJÿ×ü_ÿ×üfÿìüiÿ×ümÿìüyÿ×ü{ÿìü~ÿ×ü‚ÿ×ü„ÿìüŒÿ×üŽÿ×üÿ×ü“ÿ×ü–ÿ×ü™ÿ×ü›ÿ×üªÿ×ü¸ÿìü»ÿìüÊÿ×üÏÿ×üØÿ×üÛÿ×üÞÿ×üêÿ×üíÿ×üîÿ×üfü fü(ÿ×ýRý RýIýRý Rþfþ fþÿ®þÿ®þ$ÿ×þ&ÿìþ*ÿìþ2ÿìþ4ÿìþDÿ×þFÿ×þGÿ×þHÿ×þJÿìþPÿìþQÿìþRÿ×þSÿìþTÿ×þUÿìþVÿ×þXÿìþ]ÿìþ‚ÿ×þƒÿ×þ„ÿ×þ…ÿ×þ†ÿ×þ‡ÿ×þˆÿ®þ‰ÿìþ”ÿìþ•ÿìþ–ÿìþ—ÿìþ˜ÿìþšÿìþ¢ÿ×þ£ÿ×þ¤ÿ×þ¥ÿ×þ¦ÿ×þ§ÿ×þ¨ÿ×þ©ÿ×þªÿ×þ«ÿ×þ¬ÿ×þ­ÿ×þ³ÿìþ´ÿ×þµÿ×þ¶ÿ×þ·ÿ×þ¸ÿ×þºÿ×þ»ÿìþ¼ÿìþ½ÿìþ¾ÿìþÂÿ×þÃÿ×þÄÿ×þÅÿ×þÆÿ×þÇÿ×þÈÿìþÉÿ×þËÿ×þÍÿ×þÎÿìþÏÿ×þÕÿ×þ×ÿ×þÙÿ×þÛÿ×þÝÿ×þÞÿìþàÿìþâÿìþäÿìþÿìþÿìþ ÿìþÿìþÿ×þÿ×þÿìþÿ×þÿìþÿ×þÿ×þÿ×þ!ÿ×þ#ÿ×þ@ÿìþCÿ×þDÿ×þJÿ×þ_ÿ×þfÿìþiÿ×þmÿìþyÿ×þ{ÿìþ~ÿ×þ‚ÿ×þ„ÿìþŒÿ×þŽÿ×þÿ×þ“ÿ×þ–ÿ×þ™ÿ×þ›ÿ×þªÿ×þ¸ÿìþ»ÿìþÊÿ×þÏÿ×þØÿ×þÛÿ×þÞÿ×þêÿ×þíÿ×þîÿ×þfþ fþ(ÿ×ÿRÿ RÿIÿRÿ RR Rÿšÿš")$ÿš&ÿ×*ÿ×2ÿ×4ÿ×6ÿìDÿšFÿšGÿšHÿšJÿšPÿÃQÿÃRÿšSÿÃTÿšUÿÃVÿ®XÿÃ[ÿ×\ÿì]ÿÂÿšƒÿš„ÿš…ÿš†ÿš‡ÿšˆÿq‰ÿ×”ÿוÿ×–ÿ×—ÿטÿךÿ×¢ÿš£ÿš¤ÿš¥ÿš¦ÿš§ÿš¨ÿš©ÿšªÿš«ÿš¬ÿš­ÿš³ÿôÿšµÿš¶ÿš·ÿš¸ÿšºÿš»ÿüÿýÿþÿÿÿìÁÿìÂÿšÃÿšÄÿšÅÿšÆÿšÇÿšÈÿ×ÉÿšËÿšÍÿšÎÿ×ÏÿšÕÿš×ÿšÙÿšÛÿšÝÿšÞÿ×àÿ×âÿ×äÿ×ÿÃÿà ÿÃÿ×ÿšÿšÿ×ÿšÿ×ÿšÿìÿ®ÿ® ÿì!ÿ®"ÿì#ÿ®9ÿì@ÿÃCÿšDÿšJÿ®_ÿšfÿ×iÿšmÿ×yÿš{ÿÃ~ÿš€ÿì‚ÿš„ÿÊÿìŒÿšŽÿšÿš“ÿš–ÿš™ÿš›ÿš ÿìªÿš¸ÿ×»ÿ×ÊÿšÏÿšØÿšÛÿšÝÿìÞÿšêÿšíÿšîÿ®ÿìR R(ÿš= =I= =7ÿšqÿšrÿÃ7ÿšqÿšrÿÃ$ÿ®,)7R9R:f;)<R=)FÿÃGÿÃHÿÃJÿ×RÿÃTÿÃW)Y)Z‚ÿ®ƒÿ®„ÿ®…ÿ®†ÿ®‡ÿ®ˆÿ\Ž)))‘)ŸR¨ÿéÿêÿëÿìÿíÿôÿõÿöÿ÷ÿøÿúÿÃÂÿ®Äÿ®Æÿ®ÉÿÃËÿÃÍÿÃÏÿÃÕÿÃ×ÿÃÙÿÃÛÿÃÝÿÃì)ð)ò)ÿÃÿÃÿÃÿÃ$R&R6f78R:R;)=)?)Cÿ®_ÿ®iÿ®qRyÿÃ~ÿÂÿÊ)ŒÿÃŽÿÃÿÑ)“ÿÔ)–ÿÙÿÛÿÃR¤ÿš¦R¨=ªÿ®®ÿ…°=±µÿ…¼R½=¿)ÄRÏÿÃØÿÃÛÿÃÜ)ÞÿÃêÿÃíÿÃúfûüfýþfÿR(ÿ® $ÿ® ,) 7R 9R :f ;) <R =) Fÿà Gÿà Hÿà Jÿ× Rÿà Tÿà W) Y) Z ‚ÿ® ƒÿ® „ÿ® …ÿ® †ÿ® ‡ÿ® ˆÿ\ Ž) ) ) ‘) ŸR ¨ÿà ©ÿà ªÿà «ÿà ¬ÿà ­ÿà ´ÿà µÿà ¶ÿà ·ÿà ¸ÿà ºÿà Âÿ® Äÿ® Æÿ® Éÿà Ëÿà Íÿà Ïÿà Õÿà ×ÿà Ùÿà Ûÿà Ýÿà ì) ð) ò) ÿà ÿà ÿà ÿà $R &R 6f 7 8R :R ;) =) ?) Cÿ® _ÿ® iÿ® qR yÿà ~ÿà ‚ÿà Š) Œÿà Žÿà ÿà ‘) “ÿà ”) –ÿà ™ÿà ›ÿà R ¤ÿš ¦R ¨= ªÿ® ®ÿ… °= ± µÿ… ¼R ½= ¿) ÄR Ïÿà Øÿà Ûÿà Ü) Þÿà êÿà íÿà úf û üf ý þf ÿ R (ÿ®(ÿ®( ÿ®(&ÿì(*ÿì(2ÿì(4ÿì(7ÿ…(8ÿì(9ÿÃ(:ÿ×(<ÿš(‰ÿì(”ÿì(•ÿì(–ÿì(—ÿì(˜ÿì(šÿì(›ÿì(œÿì(ÿì(žÿì(Ÿÿš(Èÿì(Îÿì(Þÿì(àÿì(âÿì(äÿì(ÿì(ÿì(ÿ×($ÿ…(&ÿ…(,ÿì(0ÿì(2ÿì(4ÿì(6ÿ×(8ÿš(:ÿš(fÿì(mÿì(qÿ…(¸ÿì(»ÿì(¼ÿ…(úÿ×(üÿ×(þÿ×(ÿš(ÿ®( ÿ®24 4>E 4 Z fNo½ gÑ 4 h8   ´ *   ,ì  œ* (Æ Îî 8¼ \ô ¢P F òDigitized data copyright © 2006, Google Corporation.Droid SansRegularAscender - Droid SansVersion 1.00DroidSansDroid is a trademark of Google and may be registered in certain jurisdictions.Ascender CorporationDroid Sans is a humanist sans serif typeface designed for user interfaces and electronic communication.Digitized data copyright © 2006, Google Corporation.Droid SansRegularAscender - Droid SansVersion 1.00 build 107DroidSansDroid is a trademark of Google and may be registered in certain jurisdictions.Ascender CorporationDroid Sans is a humanist sans serif typeface designed for user interfaces and electronic communication.http://www.ascendercorp.com/http://www.ascendercorp.com/typedesigners.htmlThis font software is the valuable property of Ascender Corporation and/or its suppliers and its use by you is covered under the terms of a license agreement. This font software is licensed to you by Ascender Corporation for your personal or business use on up to five personal computers. You may not use this font software on more than five personal computers unless you have obtained a license from Ascender to do so. Except as specifically permitted by the license, you may not copy this font software. If you have any questions, please review the license agreement you received with this font software, and/or contact Ascender Corporation. Contact Information: Ascender Corporation Web http://www.ascendercorp.com/http://ascendercorp.com/eula10.htmlÿff_  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a¬£„…½–膎‹©¤Šƒ“òó—ˆÞñžªõôö¢­ÉÇ®bcdËeÈÊÏÌÍÎéfÓÐѯgð‘ÖÔÕhëí‰jikmln oqprsutvwêxzy{}|¸¡~€ìîº   ýþ  ÿøù !"#$%&'()*+,-./0×123456789:;<=>?âã@ABCDEFGHIJKLMN°±OPQRSTUVWXûüäåYZ[\]^_`abcdefghijklmn»opqræçs¦tuvwxyz{Øá|ÛÜÝàÙß}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ›¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ      !"#$%&'()²³*+¶·Ä,´µÅ‚‡«Æ-.¾¿/¼0÷123456ŒŸ789:;˜<š™ï¥’œ§”•¹=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg.nulluni00AD overscoreperiodcenteredAmacronamacronAbreveabreveAogonekaogonek Ccircumflex ccircumflexCdotcdotDcarondcaronDcroatdcroatEmacronemacronEbreveebreve Edotaccent edotaccentEogonekeogonekEcaronecaron Gcircumflex gcircumflexGdotgdot Gcommaaccent gcommaaccent Hcircumflex hcircumflexHbarhbarItildeitildeImacronimacronIbreveibreveIogonekiogonek IdotaccentIJij Jcircumflex jcircumflex Kcommaaccent kcommaaccent kgreenlandicLacutelacute Lcommaaccent lcommaaccentLcaronlcaronLdotldotNacutenacute Ncommaaccent ncommaaccentNcaronncaron napostropheEngengOmacronomacronObreveobreve Ohungarumlaut ohungarumlautRacuteracute Rcommaaccent rcommaaccentRcaronrcaronSacutesacute Scircumflex scircumflex Tcommaaccent tcommaaccentTcarontcaronTbartbarUtildeutildeUmacronumacronUbreveubreveUringuring Uhungarumlaut uhungarumlautUogonekuogonek Wcircumflex wcircumflex Ycircumflex ycircumflexZacutezacute Zdotaccent zdotaccentlongs Aringacute aringacuteAEacuteaeacute Oslashacute oslashacute Scommaaccent scommaaccentmacrontonos dieresistonos Alphatonos anoteleia EpsilontonosEtatonos Iotatonos Omicrontonos Upsilontonos OmegatonosiotadieresistonosAlphaBetaGammauni0394EpsilonZetaEtaThetaIotaKappaLambdaMuNuXiOmicronPiRhoSigmaTauUpsilonPhiChiPsiuni03A9 IotadieresisUpsilondieresis alphatonos epsilontonosetatonos iotatonosupsilondieresistonosalphabetagammadeltaepsilonzetaetathetaiotakappalambdauni03BCnuxiomicronrhosigma1sigmatauupsilonphichipsiomega iotadieresisupsilondieresis omicrontonos upsilontonos omegatonos afii10023 afii10051 afii10052 afii10053 afii10054 afii10055 afii10056 afii10057 afii10058 afii10059 afii10060 afii10061 afii10062 afii10145 afii10017 afii10018 afii10019 afii10020 afii10021 afii10022 afii10024 afii10025 afii10026 afii10027 afii10028 afii10029 afii10030 afii10031 afii10032 afii10033 afii10034 afii10035 afii10036 afii10037 afii10038 afii10039 afii10040 afii10041 afii10042 afii10043 afii10044 afii10045 afii10046 afii10047 afii10048 afii10049 afii10065 afii10066 afii10067 afii10068 afii10069 afii10070 afii10072 afii10073 afii10074 afii10075 afii10076 afii10077 afii10078 afii10079 afii10080 afii10081 afii10082 afii10083 afii10084 afii10085 afii10086 afii10087 afii10088 afii10089 afii10090 afii10091 afii10092 afii10093 afii10094 afii10095 afii10096 afii10097 afii10071 afii10099 afii10100 afii10101 afii10102 afii10103 afii10104 afii10105 afii10106 afii10107 afii10108 afii10109 afii10110 afii10193 afii10050 afii10098WgravewgraveWacutewacute Wdieresis wdieresisYgraveygrave afii00208 underscoredbl quotereversedminutesecond exclamdbl nsuperior afii08941pesetaEuro afii61248 afii61289 afii61352 estimated oneeighth threeeighths fiveeighths seveneighthsDeltauniFB01uniFB02 cyrillicbrevedotlessjcaroncommaaccent commaaccentcommaaccentrotate zerosuperior foursuperior fivesuperior sixsuperior sevensuperior eightsuperior ninesuperioruni2000uni2001uni2002uni2003uni2004uni2005uni2006uni2007uni2008uni2009uni200Auni200BuniFEFFuniFFFCuniFFFDuni01F0uni02BCuni03D1uni03D2uni03D6uni1E3Euni1E3Funi1E00uni1E01uni1F4Duni02F3 dasiaoxiauniFB03uniFB04ÿÿrt-5.0.1/share/fonts/Droid.README000644 000765 000024 00000000711 14005011336 017153 0ustar00sunnavystaff000000 000000 Droid is a font family created by Ascender Corporation for use by the Open Handset Alliance platform Android and licensed under the Apache license. The fonts are intended for use on the small screens of mobile handsets and were designed by Steve Matteson of Ascender Corporation. The name was derived from the Open Handset Alliance platform name Android. Droid Sans Fallback is a font with CJK support. See Also: http://en.wikipedia.org/wiki/Droid_(font) rt-5.0.1/share/fonts/DroidSansFallback.ttf000644 000765 000024 00013417450 14005011336 021300 0ustar00sunnavystaff000000 000000 €pOS/2ÚÃî4x`cmapÈç`|àÈcvt A`fpgm°!Y°ADgasp. glyfö³äèL) ¦headæ»YÀü6hheae4$hmtxé’ÙGØ^¤kern>‰Bœ.ôloca ¡¤¬Ad¦èmaxpªõX name_ ‡ .ôpostÿï .ü prep€ˆ)AL#ë-_<õ ÃGxgÃK¢“ÿéÿ¼  ÿ¼+ÿéÿ䙩¹¬'/þ³¦$³¦z > €¯+ß|û1ASC@ÿæÄÿà D‰· K’CEg¥Ó ´:M M @R E`   EE mÞœŸš¯‡~±´W Eÿé”~ß»¾”¾—… …³‘âŒ‡Š N`Nˆi”1ˆ –w–‰T…—BBÿøBå—”––ft W—{¿€}q [=[ E|”&U R Õ n UU”1¨E5UZ½½½m Ú¯¾”šˆ ˆ Õ ‰‰‰BÿýB””” ”———–ˆ –‰‰´—BÿþBœ …~M~Bÿÿ©»—”äé…W—” #”$B”./”œŸ~‘‡Š ´¾W ”‘ß»ˆ ¾±”ˆ …‡Ä ŒÄ ¾ –š}”t u—‘Tƒÿþ˜„r”¡”—s—³†ÿþ¾Á‡œ”Ÿ~¦‡ÑŒ »»”«ß´¾±”š…–Ä Œ³©ÿÿ¨Ó”˜—ˆ ’‘fŒ‰¸v ~Œ¸”˜–wq}²€š–ßá®À‘vψ‰€  --]]|Î+ : g +P-¾ ½½½½B:664€F FFp]4J7tbD:FlapBFSGF<IFEGFphFFFG-D9=FF4:E@DP*4,I,B@36-02;gBRAbEFJ;AB;CIBMJ"C?F;SMFC8#:8JMwEF821'41221 ƒ!R ‰EšÿýšÿýšBš7šÿýšÿýšBš7šÿýšÿýšBš7šBšBš7š7šÿýšÿýšÿýšÿýšBšBš7š7šÿýšÿýšÿýšÿýšBšBš7š7š7š7š7š7šÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýš,šBš,š,šÿýšÿýšÿýšBš,š,šÿýšÿýšÿýšBš,š,šÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšBšÿýšÿýšBššššÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšÿýšššÿýš†šššššššššššš#š#šššššššššššššššššð ðJJ00,,aE%WJ€2€6€1€6€#€6€.€.€)€)€+€+€€€€€€€€€%€ €€:Uˆ Bÿø”—————C #”1”1#³ ^/€==p=FUUUU2½½M, 8 : I 8:PBFw2#*€ *€ € ::::**-F<CB11.-€ €7AA4444FFFF""FF1111-iM"€<€€<€+€0(3€B€+¥;/1?++<<&&??-**&"""      %01+2$&(³ÁDD0'7%,+($!,VU <888'''#&);)!9:+8%$&3*h#FF #&"(+7D)   &+!!2"-%2"""$#""13&&"7##-1()j[iZG7F2v%!##BC' *B,gI  ..            "  "    #+*%pw-€ €€1€ €€€€€€€€€ €€€ € €€€€€€ €€ €€€€€€€€€€€€&€€ € €€€€€ €€€€€€€ €€€€ €€€ €€ €€€ --- ---  ---  ---  &&& &&& &&& &&&11&&11&%11&&11&&11"#11##11"#11"#********11%"11%"11%"11%"22"#22"#22"#22"#11"#11"#11"#11"#-------- "  "" ""  #"      ------- $$ $$ $$ $$ # ##  ## ##  ## ##  ##  %%  ... .. ... .. .11#"11%"11%"11%" .. .. .. ..********¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼Œ”šŒ”šŒ”šŒ•šŒ”š¼¼¼¼¼¼¼¼¼¼¼¼¼¼¼Œ”šŒ”šŒ”šŒ”šŒ”š…‹w…†‹…“‘w…‡‹…”‘w~„”‡Œ€•‘w‡„“‡Œ…”‘n~„’‘‰Œg{^kpp~lju{^llo€toly]mmo€uolzzbmnp€soh~z\moq€tq‰v‡Š”‡Ž‹•††€‹•vŒ”‰’€Œ—v‹‘”‰”€Œ—v‹ˆ”ˆ”ftw\kpz€kwfu}^krv€kvfty]mrt€kvbwycmrt€nvbvybnoo€lv¼¼½¼¼½¼¼¼¼¼¼¼¼½.....................&&&&&&&&&&&&&&&&&&&&&&&&$&&&#&&&&&&"" $&&!533252325235225332522)%%.)%%.)%%)%%)%%.)%%%%%.))).))))%%)%%.)%%521155215525525221552551155114414445511551)%$,)%),)%%)%%)%#,)%%)%%+)%%,)&&)%%)##+)%%)%%+)%%+&&&&%%)%%+)%%)&&&)%%&)%%)%%)%%&)%%)%%+)%%+)%%)%%)$$+)%%)$$*($$*(%%($$)$$*)%%%%%%%%%%%%%%%%%%%%%%%522255525255555252555555155515555555551555)%%-).%-)%%)%%)%%-)%%..+#...#........)#---#"""!!"!!!!!!111.111.111111111.111)%%(*%%)*&&*&&)&&$*&&)%%%*&&#*&&*&&*&&$)%%---------------------555155515555555511555...+...+.........+...0+0+0+0*0+00+00+0*0*0 F b? gS]D KK JADF m zc Ko[` K_]ou y„ŒŸ ))))k m D v m ¢ ~ i r z [ ny€‹ U Xi VW ; @ L>VG‚++  “ q N`[a\KeV‹lZs'  O Ib m [KUg]JE  :W |U ŒZ €UY >— ZUJ yœSKb   ! †L# P YM?SlR D@€ )   G^  # M ¶ FF6Œ‰ N‡Q Y `I aJ­@ q VN ' ±6 A+ J’ ar c LM€WL G>BP s V ]N a ¡— b , 3:l6C> %^Z  ƒ L#k RFAHN rgDf#+  d\ M[ h} †S x q y n‚ eQ   NV N U«s ` \Q[  Y &AHuQXN ^ngXj a b>WEI >H T_8F 1  L`NNKG?Evˆ… +  } H2uhP zxDg TlNoH >œc lY› z !!] ej t d – ˆ  ] ˜| U a €hY"bS Q CJ 0} M sŠ\ f ‘`k ® †mq|kqpr u bnFD ' Y g ?@F+s vHCk M p ` dA ª  NlQMWRISF@K CKBIMMUOJR LJ [LGCGI y : I › . arQUaX^fUœyTVOE@`OWZPAP>•PCIQaO! MÄKSPS8 o‘# wdd`bHU RphbuTa FldVjclVZgP MnZVR bYR\USPE `PH\NTRUF ‘bRVy”VWdYMMW@{d  `rUIFG\\R B5H@@ Y O€ «E €TG: owbee cgUz^" ia  Œi k a b i Wb }  €guif su _" Zbw ‡ t LZg LIT „gmX[TV ”f vum n  | ‚ ‹R dJZNz ¡6 > ‚ $ y W dvr^„ L” 4 QÔ }ou{wJG TN + wrZ`” m ƒ \rw~mpnvšI [NJHRqQ  O bX ypD@  •B › wq_ Ÿ qK2Wg Š `=K] ^o 9_ uekdCes n d` im`is V 6 U_ *blH X kB67†5I?@ Bš +  @ †T}uc nH """7  € Š€ |„ ƒO oNŒm˜ Tpl M& &¥O ‘ L[y ‚ KG 6i 1g_L`H U I^y S ]EZ? \   `yy?W\/? [ƒ n wv…iZIF @† s  3:7^LE? G8.dNUNX XAGCV  hbf l} v’2V ]a Yj  ›GER Z_dknU=nMfaIR ] L' Y ‡cZ K5 cj ©H  N †u)* mq & 440,Tz g Y dtXKi =39 3;A<-==:<97K2-<.2_ G56= +)-; Y`d W a f  Pd mrQ [[\ƒ J pUO7V = llmtTxm 1€r so .……|Œ  o xl  GV_[nU  cWJ„  [&\‰@¾T™V }s 9Ze™u K ™Vi—SWjm[G [p1f p }vd •   gs -•=`G<: XKHU ; Q Ef\‹CR ”J OeE'] †XPwS |{K‚W 6] 5455 y~ ;662855=  $ gj ,, jeA‰)E[gWR q X  k`V@/ªK Ž` o u–  ‰glJMc:„’SEB|  YjPZ ™  SEN@CEmas d qh Mˆ [k  † ~¬©y [ @XWWf€r hfp "r_`K  yS… TJ[u mqFJ†H\ )GE‹]  ¤t   s  R ¶Qn y 7+yW  '! " 5V  =lwljpo~qvwJjW+\] a [€GUO8’E2kfelTN a Q?eo’U _†¦J ‚g mu .D Ih3H\934/7b.< 98;  X s[jr Y r"r^TCfX š{X>[ tq uizvxw =V` Fm„ @ sx[d+J{|_a R \ v7 RlIg]gcUb ] 8<j4:M QAlK cL] n«¦n¡TB€| _K W_ Rfi X[ Uz—mWFYdKK¢E@fUm‹ i~bTo}y3@zCUNQRD@ aV jI ipc”$ $KRgr kŒQOdj6f e  dPhbJB^gcakaXk`Vb QeeSngZZG"*#"_&V"p[]  ‹Ui‰F[r‚|„\jsmp    Kg3W  z  WIRx p ! # ! !UCq‹8\FMLH] \ … ‹zwzhotw  a \ W} : YHa P ZM O ]VOZLhu  cNkuN[hN ` bCkƒ uq ;;gg &C  A; -K 6jž9 o 0Rl€w vmVg„v9 : }7V KoˆqQTOW+ _ flm WC5`.m ;\gm[’s JYFgJ OYj ˆ *k} ƒ[š YY¦ C~D[c Zio €r X ‚W‡;K j9A¡½W žbARž žERž žaP s[L R_? }| e $*+rC FUb\mZB bi ¦ ‚«r m/  Fd ; j[TbPZoFW€ k`r Pu l KJe:\ EKJF B\iet|XEyurrwzyghmg`bZoHR†M Š][ •PR SSPvDHj^R MDWƒ}m} ndnudYP®glc\kfb fS__ \ej i 0 . 9”/D "  @•h  Of\hd UQKPr\nY  ~cW]§? #Tme“ qr Kdr[HŽ^ vi P]\KKE[ +F" HTbm<=fYgis ˜ƒbepM] B]E_iji Qg pgm q b hB DRVVžR e )wXeK Gs GT 6W8g_f {r] tw  GO>JX k€ †9 cN gfea€T–L Œ i &w N Q ?o K ObYh gCVaIA*&~ _.edu tŽc8\kH mgu[b? 2PXc[m [`Xa\BgG>MT>@SbJhdven[z€ )  1/? wvK _ hr'~ƒXD_ l qo GQWQAe8TKn e f Fc=C P ^wn'(ŸMZw+“+~a }„ (U\EW d I I]en  spui … ^  WKaLepH`kb^k uEE0X ><50 42/8 km=JN ?n J\U Qg š i ƒƒ w|…^QPRR 'k B†LoyS^fGr } €c u^efKlJ Bz ` bMI=Y> V}f ar (dbPi$ |9B { E˜B… !" _lvf dkeIU m€ pkgpQ d: \X[SYc ’ o  V ok q ‡uŠ RG^ w RLn it Pu?FP[^  fmNX^Im weH•>7| ahi G]`k kMf vrƒsi!ilavkgjw_hgdmgmxHtp‹,+gXG§Ž  O g^eQmi?Bˆ?‰Š_W‰apn‚O^ gOKg ^ [xvy  r_Suuutiomnloljpf^kg ^ niZ\iq c?K I]U j Gp CP  jtLI_›„sg}|  n ] @ cXxy GPM adm*xsu~ olk pq a AyytŸd <10\MauI_f`iqqpe_q[Z[Mfb\  YdG JM u_^XdMhh|Ur gq~‚phˆz waZX 8 HIW`E` c ;U\ p RmT prjKahWfpRaKLFNV`kŠ €G ^^• ‹MaT‰ YrYV  Eƒ Qnf pNzK }5U-^ …QE>;‰ C l ~VJ qtyS,^h cGhMppf_NS TƒhuK?Vi_OUF r t ioZFA•;BEV +7 Zdof Pm|r P  DR} qvrlqV^ w `IXJfodly?GZ‡€ dQ D\  LN#^l@l_  ZSlG~ iRH<…{ Dz i2d ) aH`c› b u X M<M Lb?QQtLO XDr _ TX9 elz\WmQ Dg y l=MdnV V bF Ee^X =; `L  WeP_ozR T]{i=k^H|Xp TL`[ogOUzƒŠ  mfqc] Qo tR? v]…i;~NƒBBA–– [ K/K?·u ‚N J•mf Wij  ‹ Œ TL aXDC a  g @ ˆ O F Š: LU_8W C yJp949 U ^ hhd lW^F‚ |_d_ ps}N sld{~}€ƒ|†{OQn„€k p£sokwatbžg{ ™¢_pqZc‚~u  TibD KI_ L ciXu`c] fm ?36D BL[L\fSinncpvftmra R`Lne Ov†Z  P‹bPƒa   GgK mkVE UaROVI zƒ uyo‰qpa ~~yqJU`k ›HP ~V] EktFPk [RZc rk G\bWOLW! qaTFY€ qy{m EHEJJ \N’_ƒH^p*Sq+ ‰aQkmn Hd 9A l[IPT lU[fGh s~ {ƒh YF i e  [aN^SZJfxfoNJrfGC RDZ W45gxS;`D‡RM FPA? spxoq €Z_ImfNWVC j vG] BA\ L Y`OelT Cm Y> UX ][ Q €Fg6 i u|A€M‰Ž LZU€I‰ __k\`gEL 6| r~vx~ qwp kHLE ; q @O\ aidK —T] egZJl‡ƒxxwwumguogvrkd^JWs\Glsrk]T SIF[ {M_e ak}‚ ‚ƒ‚ [ej™œa–^] CLPTc`SKŸAF T : F ‰ ƒ’G\k a DQIhikO` ]rV \ dKT 3J_u 5„AGS_…wniY_ PfWr}c&h ‹ZE :s` 7›o  G :;FSF>XIR-?aqmi LIV -\OAisq†` c \O6Z cq 9 E[ i c mmW4Q_] A Xh Gk?5 KUeKraUa @p`i?y €YJDPL\ Itk5T[GgxE@ [l SeVMC@O Z<l gU^[QMIS\k P7[eT_<m U; _G dqYq]ldQG QN]T§_Œ\ ]QkDNf   ~gMt  _e K TAQhGWaTp[ aDoF _smV7T>B<§ Bk xJ bK: _JJhG] ^dsT5 F u`RBQQe\cp0?˜< ^_`^e\WX VJb T DTmˆXbReMU m0TMl€ €z~§ER^d\4E`_K Wa X  R@^fnUE\ `b BC  i/ Y\LN7UM E JMSGLMk+ O iYTrmFb_a uZ]^h] ˆ\\WQRG [D@EMZdŽ@ gff_eT`{U]dbZ`LNS`“^dY@RYaXZMRTVH>\UaQVX`UYSJVFc ^cJSOCLMUcFMX@G NRI KQmm2 xfl`_ c`RKKBDFCJDDHBJPcZ[MLKMITF E† xk e    !     ­PKOA`U?  l;;[K‹Va7 OULIGlXPKE\KIAQRM[nl KCMUEe[gUKIL_W HDIY@CGIMFK@DS@IN~L\u‰2D%C% {Q[`gLE>@F@FMES@VJK>@y|m€„€xrvykpk‚{xptq ABB=E:8  Oja     ^imi` rtl !udtp_tb   w   I T_YAq\QJYY€ QgdT´) [„[KgYU[XWUK YUJDR£ PQQJ?WYRZZQV^LIF>OLRRYRRROVK …€…‰~€\Q : W`WSUI^]aTi\W`]`N`EIX?RIWPXXU@YPUWS Œ`k`[`deZZYkbX]XnY]ke][__X^`X_[ r cte` d`^`c`aRa`rnijeb  [ `_Mƒ!!&%€hl_]Ag `USV^ UA Yu LjVUf¬TYNcbg\SS]]UB\YNG]YUCQbl[NHZVYOS…\   qnqnnpkot d `ofkedZttvlqq{ -AM[UYPcDUPR gc`^M t   GLFRNQKBg`^DJbJTIJGlJYS[G lS^  Q?]DFSuzrllunnwillssvgnjpvxBCAW]Z 89IHTH;;LMOMH@EŠFEBIBJG @;PBDQII8BCC>>BA?>ExLKO?HGLBB8A@8L>C:>@ƒEI:<MA?Œ4W@B>CAA@:N[  WQibg"\\K| | [Š Y,\f+‡ Q^]W_Z+[ l Z kigkk_ei l U\VV „€ jO“on    # m Bh || ¥m `lotemjrfdpogea €}xu fW   !!!!!x s{tsvtn “ldJ^^ET c^ ltai[gm hfh^fp gk v ‚ } £[n`†`ESYOZOZ_cOUD@XTRXI ‹\UWJ^]EKLMKL[JFJ;7N    NHQOO?@LeQ‡5VG[lep`k^Y \[ ^U?R]eluŽ 3 i_p{RCL| ws‡stnl w{kszproyt|dnbdfZ ckgiankchgid`e Ifu [_KE Igts[kx p] lqIBIK  +( KKŸ$Jv M\Jn _`MV U_cao^H\QXGPX'UqSZ JTZKcZeJVHRbZ`PraQXom`hmfi`c] bj_ W]X\z]_T‰ lj_YZ P  u  zt  zy       t „ˆ |qz zx R» #008 |3…_Y Q}ŠC I —xŒ;  H j]l ‰ G  a u u z  z @  ^` Yf    ¤ Xk”QSGX]Qkfa_]ixql]`‘ |p{~šrzv r    s  OE;   Y[[Ÿ"b[bnR Mb R{‚ Oj$R`Rga` _V R hg _’WL QTVHIEUP«XœII € w  RSRUFSXS^fo¤]t[žcJ?^VN`ŠQQ[›  I^?>”N{=BGs;€—ŠB5Œ?DGH6C6>GGM €Tb)N}4Z ˆ”I^ k q,+--)`=JW& dZDYU\VOR[o\ znf \‚\\OsU b] r x!hea]jYhnmeg•f`kWƒb haXƒ \ lZ[hkbRe^l …‚„+ ) urtmukkrc ga),igf e_mw¤¢e q  „ [8R_K[:Yb CX_U_faz:LS^fZPFA;MELGUT b^OQNPfMR{ b˜`— m+ cfLJL@OLL IJCY[cgf ^_ TY a š ±YOYRZgNX PPTG O;^\K QisGXKNSKU ZXC8E<EKUchR\U‡]YSV PDGDFS‰J•DBGV”ABD8DFAXF`tUdbiblgddjtti]ecfliscegeaUD=L`_bb XUYR[ `f ~kc Yqnb_qmikb d /    Xf<8HU`  g¬ a AQUFG7K |U59 ?BZN TGF¨ŠN M\\SJ’mq§‚g\X"bhdk`d Z±£¬°¼KWHS MDTV]VJCQCfaT%aaGN JU^adnn>M UObkpmZA9Q SLY`M\ IR?[jp ROpe?R€?Q 7?phE9 ‚Dj`iq_\Z^d]d[¥\TbF [dh bgiVEm]O U[Metm?YPU [g^ls O`   G{kŸxw’c`i [` ^Mbk³ªdG V_XTQœOxFdkUfU_R bh lb^Q ~ r¹RPµ mke]gg^Ri oeqhpNI\uz‘   Wj‚ e_QDY O\L?vor W 5C & \Za dZU^S9TPbdbYD J E j# a  RIL\e‰Ki a [\fPJ PciWdTQQ`QdkVCsJ^amv XRd   eSObmyUEEc_RZ ‚G lgiS hbbX^dm€w‚od ] Y`oj`nue`XeJpr;pj<CGN ` vO4     v  \P0    P     B8 JOGSFF@>>?IAUA@EDBC>{>U>B:<CBGG9    # @ wK ?BT 04  m! !/!;5915318B2<@    A\=;37893629,2-26<:  ) U! Q aBQ‹):V>LXPK JMP ZA YOSRQ†FEbN XQKCHDPGI NHKHKV>ONnGB LSLD=O7/::8?3403512244.046,16212041- L  ^ SLT !PP]IYKRS(3 J Uz;  lLy ?O ?      ) -  #'%%-w )cFj`[OKY4 x{Q=[YUX3_TUYQVRGY /  0' *(6+ [      V      $&ƒ0  s ] 3_8,+B?<BD>C>?78:&jCjsi8.-!%9c$   V S  ~T †d S #%"$7" Res+q87;?B0@?4GK3136911784).?96<=42456<F5+A5&?A*74:39.538,69:2833,81264.3513k ' e` W"w X?JGL=DK> : PZ3  FJMa I 3 X A[@ R-_ G P +  H| . ‚NzM ‡ryG HK0 JLK /'( 8 BLC;6Kgm`MHX ` ILU_SX_EW ySH TWiTGRT4CKUKwKINKHOR~OKIKE@lKUTRN‚ OW=MK@TLGIGHKb     tmomkdhnckhc\^i,"1)b  P 2 FS2I "   … \> V yURSgaOZ/MUE]R T M U K BdIPF}F S‡FQ mLKHMS„T_HWN E ‚S WSIW@•P\MuS _PRY[EZ „ @NWI8]]Z^^ HQTGQ J@ NORNALOAMM T:  30   y!gf aP_a]]$$ L ŠLY D2CK@@FFPI_OF0D98J‹^LCI1LBVb<MDB>;N<?ME:7F>:EJCADyEaGAKA?<FBu2@5>>CE=7F@C<K9?C9cjF@A8C.L4@A6@J<Eiq7@EF.>8}|8CP:@>>:?E=e 9DMB;DB69C6@5KA694 ;MC56A??_@9^?IB=E?5i@6@<9<= %'c  ?  \ dR V†  |wr% G yO3Q G   Vf5~ 11c Q¡¦`TT NL_LKTPKE GN§¡ÃCJV[S[‡JKt_GOD ƒLFD ‡ z"      KYPKU]^IDOHIN<R;ROJEHNX9LNKPXLOIY=E^COC>DI>GGGQHIKLLHOIXMMHBHD@QMDICID?=:RJ9DC?A8E@DGCCL;BFLGF>JENDAODNH9=>=IHR<;;>:@AD?9?4:œ5DHAC=œD " J       2  OQYƒ"VZ k Sfe LN4 V_ˆ-WO ][g XSS 8  J  kd RX0 - - #   , i  … && !      †    ) O+ %R m a &   K  N Y%J G W  |  K8    V       GN Š3v a \iTa0T ` 3•fYZ{ VOb‹Q UWB^ZRW‚O i      T4  uj t  €  4icx    #'  ka`(g[9\dSfd2T _Q\ R w   '  d *#     "      #* $$   &     S  H  Z  fK  ^  NJ TRZE    f*)  Ie d a"krA  K  J   P  I  N        gZy >UD k Q  )  J" )  b KGF\  J    k &  1 @ †    n w T        I    A  ‰         F         „   {    .4323& ^ ] &c_ h˜ ‡u]  3 if_d^. _   \ c1 _   QAFJIACB< ' h_ _' ‰W  \T‡  z\QVYOGY  O !u v‚x fk 8_]\ _U‡TUWVvX…#Y[` eY^MTEFRUKzr 5 +      4\c1 M 06 x“K, : p m•ƒ„wurnk{{~mwoskhdwm{jepcfqkemefgjOY ŒYk‰UO V]ORX t‚RO^T\ZZ_    ^ ^  ^ GFZ) eU\HNJ[LNUMYRMZRKS[OQQMbJKD]GL]bPMDNQJX[KSHQPGFK^[NINNIDTEPZNIVLKFOQKKPEONBPLGMEVEMPUBKHJEOBPEGMLLMJMJICJCPAMFMACKLPLEC m ]  no˜lqorf Gv[ OoiH XŠ/ \bZ^[‘iV†\ `^fY^ W‚‹lU__XkZF9:9@751-:17<K</>=6;81610=GB„/83+@3859;15181>6.308/.596/E;O?<8<R=;:H@>7<I45L?H;1669;77gZXYY}MN}uV \2  "   U _ Y    T    — B 52Bm aS –œƒ‹—’ƒ ƒ……‹ v \‹ Skkon ]m_gfj  a‘rf   U   .+ ;‡‚ƒƒw†€~1w{' Kh nbkg`d@œŒhkp7/ = 5  w  tn  7 ((d 7 ;= 2 )/+(+,0;*55+&555506  ‚p>   ! " G  • f N   \  AHG ]    51 \    X   9=c $#_ G5UC  c X 2/  A  -   VF \I]f   @  u   Z XNM V FB ii`                                                                                                                                                                               &                                   ""                                                                                                    "     *                                                                                                                                                                                                 &                                                                                         !                           ' "                                                                                                                                                                           " "                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    ༹ ~  ¡¡b¤¤c§¨dªªf­®g¯¯M°´i¶ºn¼¿sÆÆwÐÐxרyÞá{ææèê€ìíƒðð…òó†÷úˆüüŒþþŽ‘&'’++”13•88˜?B™IKMM RS¡fg£kk¥ÎÎâÐÐãÒÒäÔÔåÖÖæØØçÚÚèÜÜéÇǦÉËëÍÍîÐÐêØÛ¨Ýݬ‘¡­£©¾±ÁÅÃÉÖÝOÞQQ        "  $ !& % % & &( ' ' 0 0) 2 3* 5 5à ; ;ï t tá  ð „ù ¬ ¬,!!ý!!-! ! þ!!.!!/!!!!ÿ!"!"0!&!&1!+!+!S!T![!^2!`!b!c!cø!d!k!p!r!s!s!t!t!u!x!y!y!!™!Ò!Ò(!Ô!Ô)""*""6""+"",""/" " 0""7""8""-""9""."":"" 1"#"#4"%"%3"'"*5"+"+;",",ñ".".ò"4"7ó"<"<÷"="=9"H"H<"L"L:"R"R;"`"`="a"a<"d"e>"f"g="j"k?"n"oA"‚"ƒC"†"‡E"•"•I"™"™J"¥"¥G"¿"¿H##K$`$µž$Ð$éô%%K%P%t^%%ƒ%’%•’% %¡–%£%©˜%²%³Ÿ%¶%·¡%¼%½£%À%Á¥%Æ%ȧ%Ë%˪%Î%Ñ«%â%å¯%ï%ï³&&´&&¶&&¸&&¹&@&@º&B&B»&`&a¼&c&e¾&g&jÁ&l&mÅ&o&oÇ00N00100R00100S00c0!0$1 0%0%f0&0)1$0A0”g0™0ž»0¡0öÁ0û0þ11)111Ž@22ž2 2)1(2122122929142`2{»22×2£2¨1533Ø3 3 Ù33Ú33Û3"3#Ü3&3'Þ3+3+à3636á3;3;â3I3Jã3M3Må3Q3Qæ3W3Wç3{3~1;3€3„è3ˆ3Êí3Í3Ó03Õ3Ö73Ø3Ø93Û3Ý:MM@“MµMµ@”NN1?NNeNNNNN N *•N N oN N N N .NNNN*UNNÛNN1@NNNN*VNN1BNN*WNN1ENNSNN*XN!N!#N"N"*YN$N$1FN%N%/N&N&—N'N(1GN*N+1IN,N, N-N.1KN0N11MN2N2N3N31ON4N4@•N6N61QN8N8¦N9N9jN:N:N;N=1RN>N>!“N?N?1UNBNB1VNCNCÚNENEÀNGNI1WNKNKXNLNO1ZNPNP%ÎNRNS³NTNT1^NUNU2NVNV1_NWNW*ZNXNX–NYNY(N\N\1`N]N]ßN^N^1aN_N_SN`Na1bNbNb@–NfNf1dNiNi@—NkNk@˜NmNm@™NpNpüNqNq@šNsNs@›NvNw@œN~N@žN€N€1gN‚N„@ N…N…1hN†N†”NˆNˆ0N‰N‰=NŠNŒ1iNN<NŽNŽ*^NN*—NN/iN‘N‘*_N’N”1lN•N•®N˜N˜ÕN™N™•NšNšÝN›N›@£NœNœ1oNžNž#iNŸNŸ˜N N¡cN¢N¢@¤N¤N¤fN¥N¥°N¦N¦eN§N§ÐN¨N¨ñN©N©@¥N«N¬@¦N­N­*`N®N®@¨N°N°@©N²N²!¾N³N³@ªNµN¶@«N¹N¹@­NºNº‚N»N»1vN¼N¼@®N¿NÉ@¯NÊNÊ1yNËNËžNÍNÏ@ºNÑNÑ@½NÓNÚ@¾NÜNß@ÆNáNá@ÊNãNã@ËNäNä*bNåNå²NèNî@ÌNðN÷@ÓNûNý@ÛNÿO@ÞOOîOO@àOO @äO O@èOO@ñOO©OO @ôO"O"@úO$O'@ûO*O-@ÿO/O0AO2O4AO6O6AO8O?A OAOAAOCOCAOFOIAOLOWAOXOXâOYOY& OZOdA#OeOe,OgOgA.OiOlA/OnOpA3OsO…A6O†O†&O‡O‹AIOOANOO’AOO”O•ASO–O– O—O˜AUOšOžAWO O¡A\O£O£A^O¥O¯A_O²O³AjOµO·AlO¹O»AoO¿OÅArOÇOÇAyOÉOËAzOÍOÑA}OÓOÔA‚OÖOÝA„OÞOÞ#ƒOßOáAŒOãOæAOèOêA“OìOïA–OñOñAšOóOøA›OúOúA¡OþPA¢PPA¥PPA¦P P $$P PA©PPA®PP1AºP3P3AÎP5P7AÏP9P<AÒP>P>AÖP@PCA×PEPQAÛPSPSAèPUPWAéPZPeAìPhPpAøPrPxBPzP{BP}P€B P‚PƒBP…P…BP‡PˆBP‹PŽBP‘P’BP”P–BP˜P˜hP™PžBP¢P£B"P¥P¥B$P§P©B%P¬P¸B(PºP»B5P½P¿B7PÁPÂB:PÄPÈB<PÉPÉ&ÀPÊPËBAPÍPÏBCPÑPÑBFPÓPØBGPÚPÛBMPÝPÞBOPàPàBQPãPêBRPìPñBZPóPöB`PøPùBdPûPûBfPýQ BgQQBwQQB€QQBQQ"B‚Q$Q&B†Q)Q*B‰Q-Q.B‹Q0Q5BQ7Q=B“Q?Q?&ØQ@Q@1žQAQAÇQCQC èQDQDðQEQEBšQFQF¼QGQG QHQHßQIQIþQJQJB›QKQK&òQLQL^QMQM'QNQN1 QPQP1¡QQQQ_QRQRÀQTQU1¢QVQWBœQYQYBžQZQZ$=Q[Q[BŸQ\Q\1¥Q]Q_B QaQdB£QeQeçQgQgæQhQh^QiQi1¦QjQj#†QkQkñQlQn1§QpQp1ªQqQq/QsQs1«QtQtQuQu7QvQv1¬QwQwQxQx§QyQy1­Q{Q{1®Q|Q|êQ}Q}[Q€Q€1¯QQB§Q‚Q‚/Q…Q‡1°QˆQˆB¨Q‰Q‰1´QŠQЦQŒQŒ¥QQ‚QQ%:QQQ‘Q‘ Q’Q’ „Q“Q“ÃQ”Q”B©Q•Q–1µQ—Q—BªQ˜Q˜1¸Q™Q™B«Q›Q›B¬QœQœÔQQžB­Q Q B¯Q¢Q¢B°Q¤Q¦B±Q¨Q©B´QªQ¬1ÀQ¯Q·B¶Q¹Q¹B¿Q»Q¾BÀQÀQÀBÄQÄQÑBÅQÔQÔBÓQÖQØBÔQÛQÞB×QàQà¯QáQáEQäQäBÛQæQçBÜQéQíBÞQïQñBãQóQó1ÆQõQõ>QöQö(QøQøÕQùQù«QúQû1ÇQüQÿBæRRÐRR'‰RR×RRÀRR BêR R /%RRBðRRBñRRRRBöR R!BÿR$R%CR'R+CR-R.CR0R3C R5R;CR=R=CR?RDCRFRGCRIRLCRMRM#¤RNRRC"RTRVC'RZR_C*RaReC0RgRgC5RiRuC6RwRxCCRzR}CERR„CIR‡RCORR”CVR–R™C[R›R›éRœRC_RžRž0€RŸR¤CaR¦R¦‹R¨R¯CgR±RµCoR¹R¹CtR»R¼CuR¾RÃCwRÅRÅC}RÇRÇC~RÉRÉCRËRËC€RÍRÍCRÐRÐC‚RÒRÓCƒRÕRÙC…RÛRÛCŠRÝRàC‹RâRäCRæRçC’RéRéC”RëRëC•RïRõC–R÷RøCRùRùYRúRú]RûRüCŸRþSC¡SS>SSËSSC¦SSuS S C§S SCªSS]SSC­SS-SSC¯SSìSSC°SSâSSC±SS$C³S&S&C¹S*S*CºS-S1C»S3S4CÀS7S7CÂS8S8ïS9S9CÃS:S:ÖS;S@CÄSASAôSCSC¶SDSD1èSESEGSFSFCÊSGSG¥SHSH«SISJ1éSLSM1ëSNSNWSOSOCËSQSQ1íSRSRmSSSS USTSTCÌSUSUÐSVSV1îSWSW"´SXSXiSZSZCÍS\S\ùS^S^1ïS_S_CÎS`S`øSaSb1ðScSdSfSgCÏShSh1òSiSiæSkSk1óSlSlCÑSnSn1õSoSo0SpSpCÒSqSqSrStCÓSuSuSwS}CÖSSCÝS‚S‚sS„S„0'S…S†CÞS‰SCàS’SšCçSœS CðS¢S©CõS¬S®CýS°S°DS²S²DS³S³ŠS´S´DS¶S¶úS¹S¹DS»S»2 S¿S¿ûSÁSÂ2 SÃSÃÏSÈSÉHSÊSʆSËSË\SÌSÌDSÍSͽSÎSÎDSÑSÑ/tSÔSÔDSÖSÖDS×S×2SØSÙDSÛSÛD SÝSÝD SßSß2SàSáD SâSã2SäSäSåSæ2SèSéDSêSê&²SëSëDSìSìySíSîDSïSï*øSðSðjSñSñDSòSò"SóSó2SõSöDS÷S÷+äSøSø2SùSýDTTDTTDTTDTT'VT T dT T ‰T T D T T xT T wTTvTTuTTD!TTtTTD"TTrTTD$TT2TT!D(T#T2D.T3T32T4T4©T5T9D>T;T>DCT@TADGTBTB"TCTCDITETEDJTFTF'DTGTGDKTHTH'CTITI2TJTJ&9TKTKDLTNTPDMTQTQ¤TRTWDPTXTX&!TYTY2T[T\DVT_TgDXThTh']TjTlDaToTxDdTzT|DnT}T}&T~T‚DqT„T„DvT†TˆDwTŠT’DzT”T–DƒT˜TD†T T "pT¡T¡DŒT¢T¢"ÑT£T´DT¶T·DŸT¸T¸!TºT»D¡T¼T¼'LT½T¿D£TÀTÀ\TÁTÁ'MTÂTÈD¦TÉTÉ!üTÌTÚD­TÜTàD¼TáTá$TâTâDÁTäTäDÂTåTå#ÿTæTëDÃTíTîDÉTñTóDËT÷TøDÎTúTýDÐTÿTÿDÔUUDÕUU DÖUUDàUUDåUUDæUUDèU U DëU"U$DìU&U'DïU*U*DñU,U9DòU;U<EU>UAEUCUEEUFUF’UHUSE UUUWEU\U_EUaUgEUjUjE#UlUnE$UuUxE'U{UƒE+U„U„22U†U†23U‡UE4U‘U•E=U˜UšEBUœUœ,!UUŸEEU¡U¥EHU¦U¦27U§U©EMUªUª28U«U«EPU¬U¬'lU­U­EQU®U®'kU°U³ERUµU·EVU¹U¹EYU»U»EZU½UÀE[UÂUÆE_UÇUÇîUÈUÖEdUÙUÝEsUßUßExUáUìEyUïUïE…UòUóE†UõU÷EˆUùUúE‹UüVEVVE”VVE•VV E–V VE˜VVEVVE¤V#V$E©V'V'E«V)V*E¬V,V6E®V8V;E¹V=VBE½VEVFEÃVHVJEÅVLVNEÈVPVPEËVSVTEÌVWV\EÎV^V^EÔV`V`EÕVbVfEÖVhVi2RVjVtEÛVvV|EæV~V‡EíVŠVŠE÷VŒVEøV“V•EýV—VšFVœVFV V FV¢V¢2^V£V£FV¥V¨FVªV¯F V²V·FV¼V¾FVÀVÃFVÅVÆFVÈVÎF!VÑVÑF(VÓVÔF)V×V×VØVØ”VÚVÛF+VÝVÝF-VÞVÞ¨VßVßÜVàVåF.VçVçF4VêVêkVëVëF5VíVîF6VðVðF8VñVñ2sVòVõF9V÷V÷F=VùVúF>VýWF@WW FHW W jW W FMWWFOWWFPWWFSWWFTWWFUWWFXWW¼W W FYW"W#FZW&W'F\W(W(TW)W*F^W,W,F`W-W-îW.W0FaW3W4FdW7W;FfW>W>FkW@WBFlWEWEFoWGWGFpWIWOFqWPWP'ŸWQWQFxWRWRWWWWFyWYWbFzWdWfF„WhWkF‡WmWsF‹WtWt2WuWwF’W{W}F•WW€F˜W‚W‚'¤WƒW†FšWˆW‰FžW‹WŒF WWF¢W’W•F£W—W›F§WW§F¬W©W®F·W°W°F½W²W¶F¾W¸WºFÃW¼W½FÆW¿W¿FÈWÀWÀ2•WÁWÃFÉWÆWÈFÌWËWÌFÏWÎWÐFÑWÒWÖFÔWØWÚFÙWÜWÝFÜWßWåFÞWçWçFåWéWéFæWìWùFçWúWú2˜WûWýFõXXFøXX¥XXFúXX$ýXXFýXXGXXGXXGXXG X X!G X#X%GX'X*GX,X.GX/X/2™X0X;GX=X=G%X?XAG&XDXDG)XHXOG*XQXUG2XWX[G7X]X^G<XaXeG>XhXiGCXkXmGEXoXrGHXtXvGLXyXƒGOX…X‹GZXŽX”GaX—XšGhXœX¡GlX£X£GrX¥X¦GsX¨X©GuX«X¬GwX®X¯GyX±X³G{X¸X¿G~XÁXÂG†XÅXÊGˆXÌXÌGŽXÎXÏGXÑXßG‘XâXåG XçXéG¤XëXì2¼XîXòG§XóXó$XôXô ùXöXöG¬X÷X÷YXùXù1XúXÿG­YYOYY2ÅYY2ÇYYG³Y Y G´Y Y 2ÈY Y !ƒYYGµYY2ËYY2ÍYY2ÎYY^YYG¶YYšYYYYG¹YY G»Y"Y"+ Y$Y$2ÑY%Y%G½Y'Y'ªY)Y)¦Y*Y*•Y+Y,2ÒY-Y-(#Y.Y.2ÔY/Y/G¾Y1Y12ÖY2Y2G¿Y4Y42ØY7Y76Y8Y8ÎY9Y9üY:Y:GÀY<Y<GÁY>Y> ×Y@YAGÂYBYB2ÜYDYEGÄYGYG*YHYH ?YIYJ2ßYKYKGÆYNYNGÇYOYO2âYPYP"®YQYQGÈYSYSGÉYTYT DYUYXGÊYZY]GÎY`Y``YaYa_YbYcGÒYeYeGÔYgYlGÕYmYm2ïYnYnGÛYpYrGÜYsYs$YtYtGßYvYyGàY{YˆGäYŠYŠGòYYGóY’Y“G÷Y–Y™GùY›Y›GýYYžGþY Y¬HY®Y¯H Y±Y¶HY¹YºHY»Y»ÜY¼Y¾HYÀYÁHYÃYÃHYÅYÔHYÖYÞH-YàYáH6YãYæH8YèYîH<YñZHCZZHTZZ#µZZ HUZ Z H[ZZH\ZZH]ZZH^ZZH_ZZ HgZ#Z#HjZ%Z%HkZ)Z)HlZ-Z/HmZ1Z9HpZ<Z<HyZ>Z>HzZ@Z@H{ZAZA‘ZBZDH|ZFZJHZLZMH„ZPZSH†ZUZXHŠZZZ`HŽZbZbH•ZdZgH–ZiZjHšZlZmHœZpZpHžZtZxHŸZzZ}H¤ZZH¨ZƒZ„H©ZŠZŒH«ZŽZH®Z’Z•H±Z—Z—HµZšZŸH¶Z¢Z¢H¼Z¤Z§H½Z©ZªHÁZ¬Z¬HÃZ®ZÂHÄZÄZÄHÙZÆZÍHÚZÐZÐHâZÒZÒHãZÔZãHäZåZæHôZèZîHöZñZñHýZóZûHþZýZýIZÿZÿI[[I [[I [[ I [ [ I[[I[[I[[I[[I[[I[ [(I[*[*I&[,[0I'[2[2I,[4[4I-[6[8I.[<[@I1[C[CI6[E[EI7[G[HI8[K[NI:[P[P/[Q[Q[S[SŽ[T[TI>[U[U3[V[WI?[X[XO[Y[\IA[][]3[_[_IE[b[fIF[i[lIK[n[nIO[p[sIP[u[uIT[w[xIU[z[}IW[[I[[€[€,[[I\[ƒ[„I][…[…Ø[‡[‰I_[‹[‹Ib[Œ[Œ[[Ic[’[“If[•[™Ih[š[šÅ[›[¨Im[ª[®I{[°[°I€[³[¶I[¸[¹I…[½[ÇI‡[É[ÎI’[Ð[ÔI˜[Ö[ÙI[Û[ÛI¡[Ý[ìI¢[î[óI²[õ[öI¸[ø[ø,[ù[ùIº[ú[úG[û[üI»[þ[þI½[ÿ[ÿ(\\I¾\\  \\$s\\I¿\\'ê\ \ IÃ\ \ ,\ \IÅ\\?\\IÈ\\g\\IÉ\\3m\\IÊ\\IÌ\\Ž\\IÍ\\3o\\ IÎ\"\",\$\$;\%\%IÐ\'\'\(\(3q\*\*IÑ\,\,IÒ\-\-3s\0\1IÓ\3\4IÕ\7\7I×\8\83u\9\9J\:\:3v\;\<IØ\=\=\>\BIÚ\D\QIß\S\TIí\U\UË\V\VIï\X\YIð\[\^Iò\`\jIö\l\mJ\n\o3’\q\q,\s\tJ\v\vJ\y\|J\~\J \\‚J \†\†J\ˆ\J\\šJ\›\›3—\œ\J!\Ÿ\±J#\³\³J6\µ\¸J7\º\ÁJ;\Ä\ÌJC\Î\ÐJL\Ò\ÔJO\Ö\ÛJR\Þ\áJX\ä\æJ\\è\êJ_\ì\ñJb\ô\õJh\ö\ö#À\÷\ûJj\ý\ýJo\ÿ]Jp]]Ju] ]Jw]]J|]]J~]] J†]"])JŠ]-].J’]0]:J”]<]CJŸ]E]EJ§]G]GJ¨]I]LJ©]N]NJ­]P]SJ®]U]UJ²]X]YJ³][]^Jµ]b]cJ¹]e]eJ»]g]iJ¼]k]mJ¿]o]oJÂ]q]tJÃ]v]wJÇ]y]zJÉ]|]‚JË]„]„JÒ]†]JÓ]]JÛ]’]•JÜ]—]—Jà]™]šJá]œ]¢Jã]§]ªJê]¬]²Jî]´]´3·]µ]µJõ]·]ºJö]¼]½Jú]À]ÀJü]Â]ÃJý]Å]ÇJÿ]É]ÉK]Ë]ÍK]Ï]ÖK]Ø]ØK]Û]Û3Ã]Ý]Þ3Ä]ß]ßK]à]à¦]á]âK]ã]ã0]å]å3È]æ]æ]ç]çK]è]è3É]é]éK]ë]ë3Ê]î]î#]ï]ðK]ñ]ô3Ë]õ]õí]÷]÷!y]ù]ùK]û]ûK]ý]ýK]þ]ÿ^^^^^^K^^K^ ^ K^^K ^^K$^^D^^K*^^^^%K,^&^& ÷^'^)K3^+^+K6^-^-Y^.^.K7^/^/3Ó^0^1K8^3^4K:^6^7K<^8^8×^;^EK>^G^GKI^J^OKJ^S^UKP^W^YKS^[^dKV^f^pK`^r^s3Ù^t^tº^u^uKk^v^vâ^w^w3Ü^x^xÂ^y^yKl^z^z3Þ^{^|Km^}^~3à^^ü^€^„Ko^†^Kt^^‘K|^“^—K^™^œK„^ž^ Kˆ^¢^¨K‹^ª^®K’^°^·K—^¸^¸–^¹^¹KŸ^¾^¾K ^Á^ÌK¡^Î^ãK­^å^êKÃ^ì^ìKÉ^î^óKÊ^ô^ô%@^ö^øKÐ^ú^üKÓ^þ_4*__KÖ__!ì__ KÛ_ _ 42_ _ KÞ__E__’__Kà__45__¿__Kâ__46__t__Kã__47__Kä__Kå__48_ _*Kæ_-_0Kñ_1_14:_3_:Kõ_<_<Ký_>_>Kþ_@_AKÿ_C_FL_H_LL_N_OL _P_Q4>_R_RL _S_Sè_T_UL _V_V"í_W_W/T_X_YL_[_]L_a_a»_b_bL_d_dL_e_e!ç_f_f"_g_g4E_i_mL_o_qL_s_s+¿_t_tL_v_yL_{_ŒL#__“L5_•_™L9_›_œL>_ž_¡L@_¥_¦LD_¨_¯LF_²_·LN_¹_¹LT_»_ÁLU_Ã_É_Ä_Ä_Å_Å4O_Æ_ÆL\_É_ÉL]_Ì_ÍL^_Ï_ÒL`_Ô_ÙLd_Ü_ÜLj_Ý_Ý _Þ_ÞLk_à_áLl_ã_åLn_ç_èLq_ê_ëLs_í_ñLu_ó_õLz_÷_øL}_ú_ûL_ý`L` `L‹``Lš` `"L `$`/L£`1`5L¯`7`7L´`9`<Lµ`?`GL¹`I`MLÂ`P`PLÇ`R`ULÈ`X`[LÌ`]``LÐ`b`lLÔ`m`m4a`n`pLß`r`sLâ`u`}Lä``Lí`ƒ`ŽLð``Lü`’`’Lý`”`—Lþ`š`M`Ÿ` M`¢`¤M`¦`©M `ª`ªZ`«`­M`¯`ÁM`Ã`ÍM%`Î`Î4q`Ï`ÏM0`Ñ`ÑM1`Ó`ÕM2`Ø`äM5`æ`éMB`ë`üMF`þaMXaaM\aa M`a aMdaaMnaaMoaa!Msa#a$Mva&a)Mxa+a,M|a.a0M~a2a2Ma4a4M‚a6a7Mƒa;aBM…aDaOMaQaVM™aXa[MŸa]a_M£aaahM¦ajalM®anauM±avav4“awawM¹ayazMºa|a~M¼a€aM¿a‚a‚4•aƒaƒMÁa‡a‡MÂa‰aŽMÃaa”MÉa–a–MÎa˜a›MÏaaMÓaŸaŸMÔa¡a¢MÕa¤a¤M×a§a¸MØaºaºMêa¼a¼Mëa¾a¿MìaÁaÃMîaÅaÍMñaÐaÒMúaÔaÔMýaÖaÖMþaØaØMÿaÞaàNaãaëNaíaîN aðaòNaôbNbbNbbN!bbSb b –b b ”b b N#bb—bbN%bbŠbbªbbN&bb7bbN(bb>bbN)bb4³bbN-bb4´bbN.b b 4µb!b!N/b"b"`b#b'N0b)b4N5b6b6úb7b7(¼b8b8ûb:b;NAb=bCNCbFbJNJbKbKObLbL4¾bMbM{bNbNNObPbUNPbXb\NVb^b^N[b`btN\bvbwNqbyb~Nsbbib€b„Nyb†bŠN~bŒbŒNƒbŽbN„b‘b˜N†bšbœNŽbžb¢N‘b¤b¦N–b¨b±N™b³b¶N£b¸b¹N§b»b¿N©bÂbÂN®bÄbÔN¯bÖbÝNÀbßbãNÈbåbéNÍbëcNÒcc Nëc cNîccNõccNùcc%Nüc'c-Oc/c/O c2c4O c6c6Oc8cQOcTcZO+c\c\O2c^c_O3caccO5ceceO8cgciO9ckckO<cmcrO=cucxOCczc}OGcc…OKc‡cŠORcŒcŒ½cc’OVc”c”O\c–c™O]c›c¥Oac§cµOlc·c¸O{cºc¾O}cÀcÀO‚cÂcÐOƒcÒcÓO’cÕcáO”cãcåO¡cçcëO¤cícúO©cýcýO·cÿdO¸ddO¼d dO¾ddOÆddOÍdd(OÐd*d0OÛd3d7Oâd9d:Oçd=dHOédJdKOõdMdNO÷dPdTOùdXdYOþd[daPdediPdkdpP drd{Pd}d}PddPd‚d…Pd‡dŒP"dd“P(d•dšP-dœd P3d¢d¦P8d©d©P=d«d®P>d°d³PBdµdµPFd·dÅPGdÇdÇPVdÉdËPWdÍdÐPZdÒdÒP^dÔdÔP_dÖdÛP`dÞdÞPfdàdéPgdëdíPqdïdôPtdödøPzdúdÿP}ee5eePƒeeP„e e P‰e ePŠeePeeP—e e&Pše)e.P¡e/e/5e2e3P§e4e55e6e9P©e;e;P­e=e?P®eAeAP±eCeCP²eEeFP³eHeJPµeLeOP¸eQeQP¼eSeYP½e[e^PÄebehPÈejemPÏeoepPÓerezPÕe{e{5e|e|PÞe~e…Pße‡e‡5eˆe‰Pçe‹eŒPéeŽeŽPëee–Pìe—e—e™e™Póe›e¢Pôe¤e¥5"e§e¨Püe©e© 7eªe°Pþe²e³Qe¶e¸Qe¹e¹@e»e½Q e¿e¿Q eÁeÁ$¥eÂeÆQeËeÐQeÒeÓQeÖe×QeÙeÛQeÝeßQ eàeá57eâeãQ#eåeå5;eæeæåeçeçQ%eèeèeéeéˆeìe÷Q&eúeýQ2eÿfQ6ffQ8ff%•ffQIffQKffQLff6f f"QOf$f$QRf%f%Yf&f(QSf+f+QVf-f.QWf/f/"f0f6QYf9f<Q`f>f?QdfAfEQffGfGQkfIfLQlfOfOQpfQfWQqfYf\Qxf]f]Uf^f_Q|fafbQ~fdflQ€fnfnafoftQ‰fvf|Qf~f~Q–f€f„Q—f†fŽQœffQ¥f‘f‘,f”f™Q¦ffQ¬fŸf¢Q­f¦f«Q±f®f³Q·f´f´%bfµfµQ½f·fÁQ¾fÄfÄQÉfÆfÌQÊfÏfÏQÑfÒfÒQÒfÖfÖQÓfØfÞQÔfàfàQÛfãfäQÜfæfæQÞfèfèQßféfé5ZfëfîQàfðfð5[fòfò¬fófó±fôfôfõfõ&föf÷QäføføafùgQæggµggQïggÅg g 5eg g Qñg g Ëg gQòggQögg$îggQøggQüggQýgg5ggg#Qþg&g'Rg(g(Úg*g,5ig-g-Rg.g/5lg1g1•g3g4Rg5g5:g6g>Rg?g?ˆg@gCRgEgIRgKgQRgSgSR!gUgWR"gYgZR%g\g^R'g_g_g`gdR*gegegfgfR/ghgjR0glgmR3gogpR5gqgqÑgrgR7ggREgƒg‡RFg‰g‰RKg‹g•RLg—gšRWgœgœ2gg£R[g¥g«Rbg­g°Rig²g»Rmg¾g¾RwgÀgÆRxgÈgÔRgØgàRŒgâgçR•gégëR›gìgìÖgígøRžgúhRªhh R¶hhR¼hhR¾hhRÂhh$hhRÃhhRÄhh"RÅh%h&RÌh(h+RÎh-h/RÒh1h5RÕh7h<RÚh=h=5~h>h>Ràh@hFRáhHhURèhYhYRöh\h]R÷h_hiRùhkhkShmhoShqhrShthyS h{hƒSh…h‡Sh‰hShh”S!h–h—S'h›hS)hŸh¤S,h¦h¶S2h¹hºSCh¼h¼SEhÀhÀSFhÂhÂSGhÄhÄ5hÅhÉSHhÊhÊ5hËhÖSMh×h×5hØhØSYhÚhÚSZhÜháS[hãhäSahæhìSchîhýSjiiSziiS|i iSiiSiiSŽii(S’i*i*Sœi-i-Si/i0Sži2i9S i;i=S¨i?iBS«iDiES¯iHiLS±iNiOS¶iQicS¸ieifSËihilSÍimimÏiniqSÒisi~SÖi€i„Sâi†iŠSçiiŽSìii‘Sîi“iœSðižižSúi i¡Sûi£i·Sýi¹i¹Ti»i¿TiÁiÄTiÆiÇTiÉiÐTiÓiÔT&iØiÙT(iÛiÛT*iÝiàT+iâiâT/iäièT0iêiîT5iñiôT:iöiùT>iûjTBjj TJjjTTjjTUjjT^jj#T_j%j+Tfj.j2Tmj4j6Trj8jATujDjDTjFjF5ÅjGjIT€jKjKTƒjMjVT„jXj[TŽj]jbT’jdjkT˜jmjmT jojoT¡jqjsT¢jvjvT¥jxjyT¦j|j|T¨j~jT©jƒj…T­j‡j‡T°j‰j‰T±jŒjŽT²jj—TµjšjœT½jžj¦TÀj¨j¯TÉj³j´TÑj¶j»TÓj½j½TÙjÁjÃTÚjÅjÇTÝjËjÍTàjÏjÑTãjÓjÓTæjÙjâTçjäjåTñjçjèTójêjìTõjîjñTøjójóTüjöjöTýjøjüTþkkUkkUkk UkkU kkUkk5õkkUk k ék!k%Uk'k(Uk,k-U k/k/U"k1k4U#k6k?U'kAkCU1kEkNU4kPkQU>kSkVU@kYkYUDk[k\UEk^kaUGkbkd5ûkeke<kfkf5þkgkgUKkikjULkmkmUNkokoUOkrktUPkvkxUSkyky6k{k{6k|k|UVk~k„UWk†kU^k‘k›Uhkžk§Uskªk«U}k­k²Uk³k³6k´k·U…kºk½U‰k¿kÉUkÊkÊ6kËkËWkÌkÌ6kÍkÍXkÎkÏ…kÐkÐU˜kÒkÓU™kÔkÔkÕkÙU›kÚkÚ6kÛkÛOkÞkäU kækæ6kçkèU§kêkìU©kïkðU¬kòkóU®kõkõU°k÷kùU±kûl U´l l UÃllBll6 llUÆll×llUÇllUÎll,UÏl.l0UÝl2l3Uàl4l760l8l8­l;l;Uâl=lAUãlBlB64lClCUèlFlGUélIlPUëlRlRUólTlUUôlWlWUölYl]U÷l^l^"l_lbUüldlkVlmlmVloltV lvlvVlxl{Vl}l~Vl€l‰VlŠlŠ68l‹lV l’l–V&l˜lV+lŸlŸV1l¡l§V2l©l®V9l°l´V?l¶lÇVDlÉlÉ#SlÊlÊVVlÌlÍVWlÏl×VYlÙlÞVblàlãVhlålåVllçlïVmlðlð$mlñlóVvlõlþVymmVƒmmV…mmV‡mmV“mm V™m"m"Vm%m%Vžm'mHVŸmJmKVÁmMmOVÃmQmTVÆmXmZVÊm\m\VÍm^mjVÎmlmpVÛmtm€Vàm‚mŽVímm™Vúm›m¡Wm£m¤W m¦m¬W m®m¯Wm²mµWm·m¸WmºmÀWmÂmÍW#mÏmæW/mèmýWGnnW]nnW^nnW_nnW`nnWhnnWjnnWonn)Wtn+n6Wn8nAW‹nCnGW•nInKWšnMnNWnQnVWŸnXnXW¥nZniW¦nknkW¶nnnoW·nqntW¹nvnzW½n~n€WÂn‚nƒWÅn…n†WÇnˆn‰WÉnŒnWËn’n”WÐn–n™WÓn›n§W×nªn«Wän®n´Wæn¶n·Wín¹nØWïnÚnÚXnÜnâXnänæXnènéXnënïXnñnòX!nônùX#nûoX)oo X2o oX8ooX;ooX@ooXCoo'XDo)o3XNo5o<XYo>oAXaoCoCXeoEoGXfoKoKXioMoUXjoWoXXsoZodXuofogX€oiopX‚orotXŠovoxXozo‚Xo„o‰X™o‹oŽXŸoo—X£oœoœX«ožožX¬o o¶X­o¸oºXÄo¼oÄXÇoÆoÏXÐoÑoÒXÚoÔoÕXÜoØoØXÞoÚoäXßoæoéXêoëo÷XîoúoüXûoþpXþppYp pYppY pp$Yp&p,Yp/p5Y&p7p<Y-p>pFY3pHpJY<pLpLY?pOpRY@pUpXYDpZp[YHp]pfYJphpjYTpkpk%Åplpl6âpmpmYWpopoYXpppp>pqpqYYptpvYZpxpxY]pzpzY^p|p€Y_p‚p†Ydp‰pŠYipŽpŽYkp‘p–Ylp˜pšYrpœpYupŸpŸYwp¡p¡Yxp¤p¤Yyp©p©Yzp«p¬Y{p­p­bp®p±Y}p³pµYp·p¹Y„pºpº¬p»p¾Y‡pÀpÃY‹pÅpÈYpÊpËY“pÍpÎY•pÏpÏ#pÑpÔY—p×pâY›päpäY§pæpéY¨pëpíY¬pïpñY¯pópôY²pöpýY´pÿqY¼qqY¾qqY¿qqYÀq qYÁqqkqqYÇqqYÈqq YÒq!q!Kq"q#YÕq%q%Y×q&q&jq(q(YØq.q2YÙq6q6YÞq:q:Yßq<q<YàqAqGYáqIqNYèqPqPYîqRqVYïqXqZYôq\qjY÷qlqlZqnqnZqpqpZqrqsZ qxqxZ qzq{Z q}q}Zq€q‚Zq„qŠZqqZq’q’Zq”q•Zq—q¢Zq¤q¥Z*q§qªZ,q¬q¬Z0q¯q³Z1qµqµZ6q¸qºZ7q¼qËZ:qÎqÐZJqÒqÒZMqÔqÜZNqßqâZWqäqèZ[qìqîZ`qðqòZcqôqõZfqøqùZhqûrZjrrZsr r Zvr r ZwrrZyrrZzrrZ|rrZr"r#Z‚r&r'Z„r(r(7r)r)Z†r*r*7r,r,Z‡r-r-£r0r0"§r1r27r5r87r9r:Zˆr;r;Ïr<r<ZŠr=r=žr>r?7r@rBZ‹rDrDZŽrFrFZrGrG7rHrMZrOrOZ–rRrSZ—rVrVZ™rXrXZšrYrY)BrZrZZ›r[r[%˜r]rcZœrfrgZ£rirjZ¥rlrlZ§rnrpZ¨rrr‚Z«r„r“Z¼r•r˜ZÌršr›ZÐrrªZÒr¬r¬Zàr­r­|r®r²Zár´rºZær½rÆZírÈrÎZ÷rÐrÒZþrÔrÔ[rÖrÜ[rÞrä[ ræræ[rèrô[rös[ss[*ss[+s s [-ss[0ss[2ss[5ss[:s!s'[?s)s<[Fs>s@[ZsBsE[]sIsJ[asLsR[csWs[[js]sc[osesp[vsrss[‚susx[„szsƒ[ˆs„s„ís…sŠ[’s‹s‹)GsŽsŽ[˜s‘s˜[™s›s›[¡ss[¢sŸs¢[£s¤s©[§s«s°[­s²sÀ[³sÂsÃ[ÂsÅsÔ[ÄsÖsÞ[Ôsàsà[Ýsãsë[Þsísî[çsñsò[ésôsú[ësüt[òtt [øtt\tt\tt\tt\tt\ t t&\ t(t6\t8t8\ t:t:\!t<t<\"t?tD\#tFtF\)tJtK\*tMtR\,tTtU\2tWtW\4tYt\\5t^t`\9tbte\<tgtj\@tmts\Dtutw\Ktyty\Nt|tƒ\Ot…t‹\WtŽtŽ\^tt\_t’t’\`t”t•\at—t˜\ctštš\etœtœ\ftžt£\gt¥t«\mt­t­\tt¯t²\utµt¹\ytºtº7St»t»\~t½tÃ\tÅtÆ\†tÊtË\ˆtÏtÏ\ŠtÒtÒ\‹tÔtÛ\ŒtÜtÜ7ZtÝtå\”tætæ§tçté\tìtì\ tîtò\¡tôtø\¦tûtû\«týu\¬uu\µu u\·uuuu\Äuu#Puu\Åuuu!u!\Éu"u"Xu#u#›u%u&\Êu(u*†u+u+Šu,u,‰u-u/\Ìu0u1„u2u2‹u3u37au5u57bu7u7\Ïu8u97cu:u:\Ðu;u;u<u@\ÑuDuK\ÖuLuL ouMuN\ÞuOuO#=uQuQ\àuSuT\áuYu]\ãu_u`\èubub7iucug\êuiuj\ïukuk¶ulum\ñuouo\óupup7kurut\ôuuuu7muvuvzuwuy\÷uzuz7nu}u}\úu~u~%luu€7ou‚u‚7quƒu„\ûu†u‡\ýu‰u‰\ÿuŠuŠu‹uŒ7suŽu]uu7uu‘u‘]u’u’}u”uš]uu] uŸu¥] u§u§]uªu¬]u®u¶]u¸uÅ]uÇuÒ]-uÔuÛ]9uÝuä]Auæuë]Iuíuí]Ouïv]Pvv]cvv]dvv ]evv]kvv+]nv-v-]‡v/v5]ˆv8v8]v:v@]vBvC]—vFvI]™vLvN]vPvP] vRvT]¡vVvZ]¤v\v\]©v^ve]ªvgvr]²vuvu]¾vvvv7ûvxvx~vyvy]¿vzvzþv{v|]Àv}v}7þv~v~vv„]Âv†v‹]ÈvŽv“]Îv•v–]Ôv™vž]Öv¤v¤]Üv¦v¦]Ývªv«]Þv­v­]àv®v®8v¯v²]áv´vµ]åv·v»]çv½v¾]ìv¿v¿vÂvÆ]îvÈvÊ]óvÍvÔ]övÖvØ]þvÚvß^vává^vãvæ^vçvçÉvévê^ vìví^vîvîÕvïvñ^vòvò8vóvó^vôvô8võvõ^v÷vü^vþvþ!tww!uww^ww^w w !w w ^ w w !kw w ^!ww^"ww^&ww^'ww^*ww8w w ^+w"w)^,w-w-^4w/w/^5w1w>^6w@w@8wAwA^DwCwG^EwJwR^JwTwV^SwYw\^Vw^wc^Zwewo^`wywy^kw|w}^lw~w8w€w…^nw‡w‰^tw‹w^ww‘w’^|w•w•^~w—w—^w™w£^€w¥w¥^‹w§w­^Œw°w·^“w¹w½^›w¿w¿^ wÂwÂ^¡wÄwÄ^¢wÇwÇ^£wÉwÊ^¤wÌwÌ^¦wÍwÍÄwÎwÐ^§wÓwÕ^ªw×wÚ^­wÛwÛ8'wÜwÜ^±wÞwÞ8(wàwà^²wâwâ8)wãwã^³wåwå^´wçwé^µwëwò^¸wówóŽwöx^Àxx^Îx x ^Ðx x^Ñxx^Þxx^ßxx#^áx%x5^æx7x<^÷x>x@^ýxCxC_xExE_xGxJ_xLxP_xRxR_ xUxW_ x\x^_x`x`_xbxb_xdxe_xhxr_xtxt_!xwxw_"xyx|_#x~x€_'xx8/xƒx‡_*x‰x‰_/xŒx_0x‘x‘_4x“xœ_5xžx¥_?x§x­_Gx¯xµ_Nx¹x¼_Ux¾x¾_YxÁxÁ_ZxÃxÆ_[xÈxÉ__xÊxÊ%`xËxÑ_axÔxÕ_hxÙxÛ_jxÝxã_mxåxå_txçxê_uxìxí_yxïxï_{xòxõ_|x÷x÷_€xùxÿ_yy_ˆyy_Šyy_Œy y _y y _Žyy_yy_yy_•yy_—yy_˜y!y!_œy#y+_y,y,8Ay-y-_¦y/y1_§y4y5_ªy8y9_¬y:y;8Ey<yB_®yDyL_µyOyW_¾yZye_Çygyk_Óymym8Gyoyp_Øyryt_Úywyz_Ýy|y}_áyy‚_ãy„y…_çyˆyˆ_éyŠy‹_êyy˜_ìyšy_øy y¢_üy¤y¤_ÿy¦y¨`yªy®`y°y´`y¶y·` y¸y¹8Kyºyº yy»y»8My½y¾8Ny¿yÁ`yÃyÃ`yÅyÆ`yÈyÈ`yÉyÉ8QyÊyË`yÍyÏ`yÑyÒ`yÕyÖ`yØyØ`yÜyá` yãyä`&yæyæ$dyçyç`(yéyð`)yöyø`1yúyû`4yýyý`6zz`7zz`8zz`=z z`>zz`Czz`Izz `Oz"z#`Rz&z&`Tz(z(`Uz+z+`Vz.z3`Wz6z7`]z9z9`_z;z@``zBzD`fzFzQ`izTzT`uzVzX`vzZz\`yz_zc`|zgzi`zkzn`„zpzq`ˆztz{`Šz}z`’zƒz`—zz™`¢zœz `­z¢z£`²z¥z¦`´z¨z¸`¶zºzº`Çz¾zÁ`ÈzÃzÅ`ÌzÇzÈ`ÏzÊzÊ`ÑzËzËhzÍzÍ`ÒzÏzÏ`ÓzÑzÑ`ÔzÒzÒ!½zÓzÓ`ÕzÕzÚ`ÖzÜzÜ$ózÝzÞ`Üzßzß/ªzàzàŽzázä`Þzåzå/©zæzç`âzêzë`äzízð`æzözø`êzùzù8zúzû`ízýzý`ïzÿ{`ð{{ `ø{{`ü{{a{{a{ { a {"{&a {({(a{*{6a{8{<a{>{>a#{@{@a${D{Ra%{T{Ta4{V{Va5{X{Xa6{Z{[a7{]{]a9{`{ga:{i{iaB{l{naC{p{{aF{}{~aR{€{€aT{‚{‚aU{„{ˆaV{Š{’a[{”{¢ad{¤{¤as{¦{­at{¯{¯a|{±{±a}{´{µa~{·{¹a€{¾{¾aƒ{À{Áa„{Ä{Äa†{Æ{Ça‡{É{Ìa‰{Î{Ïa{Ñ{Õa{Ø{ëa”{í{îa¨{ð{ôaª{ö{ùa¯{û|a³||a¼| |a¿||aË||aÍ||#aÎ|%|-aÖ|0|0aß|3|3aà|7|9aá|;|Aaä|C|Caë|E|Eaì|G|Jaí|L|Mañ|O|Paó|S|Taõ|V|\a÷|_|`aþ|c|gb|i|lb|n|ob |r|rb |s|s^|t|ub |x|…b|ˆ|b||’b"|”|˜b%|›|›/¤|œ|žb*|Ÿ|ŸÂ|¡|£b-|¤|¤9D|¥|¥b0|§|¨b1|ª|«b3|­|¯b5|±|µb8|¹|Âb=|Å|ÅbG|Ç|ÈbH|Ê|ÎbJ|Ð|ÒbO|Ô|ÙbR|Ü|àbX|â|âb]|ç|èb^|ê|êb`|ì|ìba|î|òbb|ô|ôbg|ö|÷bh|ø|ø™|ú|úbj|û|ûÞ|ý|þbk}}"bm}'})b}+},b“}.}3b•}5}6b›}8}Hb}J}Lb®}N}Vb±}X}Xbº}[}\b»}^}_b½}a}cb¿}f}kbÂ}m}sbÈ}u}wbÏ}y}}bÒ}}b×}ƒ}†bÚ}ˆ}‰bÞ}Œ}bà}‘}”bä}–}–bè}™}£bé}¦}¦9O}§}§bô}©}²bõ}´}µbÿ}·}Âc}Ä}Çc }É}Ìc}Î}Ïc}Ñ}Òc}Õ}ác}ã}äc&}æ}êc(}ì}ìc-}î}ôc.}ö}÷c5}ù}ûc7~~c:~~c;~~c>~~&cN~)~+c[~-~Mc^~P~Zc~\~ccŠ~f~kc’~m~mc˜~o~pc™~r~ƒc›~†~œc­~Ÿ~Ÿ9n~ ~­cÄ~¯~³cÒ~µ~ºc×~½~ÕcÝ~×~ãcö~å~ëd~í~ød ~ú d d&d+5d866c8:dO=?dRBEdUGHdYJPd[QQzTT{UUdbWXdcZcdeendopvdyww/žxd€‚dˆƒƒ9•…‰dŠŠŠ¼‹‹9˜ŒŒÚŽd‘’d‘””d“••$Û––d”šd•žžOŸ¨d™©©¾¬±d£²²9³³d©µ¼dª½½9¡¾Ãd²ÅÇd¸ÉÒd»ÔÕdÅרdÇÛÜdÉÞãdËåædÑèõdÓ÷ùdáû€d䀀’€€€€dꀀN€€d뀀9«€ € 9¬€ € dì€ € E€ €d퀀9®€€dò€€dø€€"dú€$€*dÿ€,€-e€0€1e€3€39°€4€9e €;€;e€=€?e€B€B/<€C€Ce€F€He€J€Me€O€Re€T€Te €V€Ve!€X€Xe"€Z€Ze#€\€_e$€a€be(€d€de*€g€je+€l€le/€o€ye0€}€~e;€€€€€€9½€‚€‚e=€ƒ€ƒ9¾€„€„e>€…€…9¿€†€‡e?€‰€‰€Š€ŒeA€€eD€’€’eF€“€“9Á€•€•eG€–€–9€˜€˜eH€™€™9Àš€eI€Ÿ€¥eM€©€©9Ä€ª€¯eT€±€²eZ€´€´9Æ€µ€µe\€·€¸e]€º€ºe_€¼€Âe`€Ã€Ã"5€Ä€Ä ‚€Å€Êeg€Ì€Ñem€Ô€Þes€à€áe~€ã€ãe€€ä€ä#»€å€íe€ï€öeŠ€ø€þe’e™ eœ /F e¡e«e¯!%e²''e·))e¸+-e¹/3e¼66eÁ8:eÂ=>eÅCDeÇFHeÉJUeÌY\eØ^beÜdgeáiieåkkeæmteçv€eeú†eüf‘‘f““f••f—™fšš­› f ¢¤f¦¬f®®f°µf·Êf"ÌÍf6ÏÓf8ÕÕf=×Ûf>ÝâfCãã9àååfIæç9áèèfJéé9äêêÍìîfKòòfNóó,ôôfO÷ùfPúú9çûûfSüüÑþþ9èÿÿ!ù‚‚fT‚‚®‚‚9ë‚‚Ê‚ ‚ fV‚ ‚ 9ñ‚ ‚ m‚ ‚'‚‚fX‚‚f[‚‚Ú‚‚9ò‚‚)‚ ‚#f`‚%‚%fd‚(‚@fe‚B‚Bf~‚D‚Ef‚G‚Gf‚I‚If‚‚K‚Kfƒ‚N‚Sf„‚U‚_fŠ‚a‚df•‚f‚ff™‚h‚mfš‚n‚nK‚o‚o}‚p‚qf ‚r‚rt‚s‚wf¢‚x‚x9ö‚y‚y0‚z‚zf§‚|‚€f¨‚‚‚…f­‚ˆ‚ˆ9ù‚Š‚‹f±‚‚“f³‚”‚”9ÿ‚—‚±fº‚³‚ºfÕ‚»‚»:‚¼‚Åf݂ǂÈfç‚Ê‚Ïfé‚Ñ‚Ùfï‚Û‚Üfø‚Þ‚áfú‚ã‚èfþ‚ê‚íg‚ï‚÷g‚ù‚ûg‚ý‚þgƒƒ gƒ ƒ g ƒ ƒ :Iƒƒg!ƒƒg#ƒƒg$ƒƒg-ƒ ƒ g.ƒ"ƒ-g/ƒ/ƒ/g;ƒ1ƒ5g<ƒ6ƒ6Sƒ7ƒ7gAƒ8ƒ8Uƒ9ƒ:gBƒ;ƒ;:]ƒ<ƒ<gDƒ?ƒHgEƒIƒIXƒJƒTgOƒVƒVgZƒXƒXg[ƒZƒ\g\ƒ^ƒog_ƒsƒxgqƒzƒgwƒƒg}ƒƒƒƒg~ƒ…ƒgƒ’ƒ g‹ƒ¢ƒ«gšƒ®ƒºg¤ƒ¼ƒ¼g±ƒ½ƒ½/Ûƒ¿ƒÌg²ƒÎƒÏgÀƒÑƒÑgƒӃÙgÃۃågʃçƒìgÕƒîƒÿgÛ„„gí„„gî„„gð„ „gò„„gý„„gþ„ „ h„"„-h„/„=h„?„@h„B„Hh„I„I/Ü„K„Nh&„P„Rh*„T„Th-„V„Wh.„Y„ch0„e„ih;„k„qh@„s„zhG„}„~hO„‚„‚hQ„„„„hR„†„‰hS„‹„‘hW„”„”h^„—„˜h_„™„™P„š„¢ha„¤„¤hj„§„²hk„´„´hw„¶„¶hx„¸„½hy„¿„Âh„Ä„Çhƒ„É„Ôh‡„Ö„×h“„Ù„Ýh•„ß„àhš„ã„ãhœ„å„ìh„î„ôh¥„ö„÷h¬„ù…h®……h¶……h·……hÁ……!hË…#…1hÑ…5…5hà…7…Ahá…C…Khì…M…Nhõ…Q…Qh÷…S…[hø…]…^i…`…ni…q…ri…t…|i…~…~i…€…‘i…”…¤i0…¦…¬iA…®…±iH…³…ºiL…½…ÉiT…Ë…Ëia…Í…Óib…Õ…Õii…×…Úij…Ü…ßin…á…æir…è…íix…ï…òi~…ö…ûi‚…ý†iˆ††À††iކ † i‘††i•††i–††i—††'iž†)†*i¨†,†6iª†8†<iµ†>†@iº†C†Ci½†F†Hi¾†K†LiÁ†M†MІN†N¶†O†ViÆY†\iˆ^†_iφa†eiцg†jiÖ†k†k †l†piÚ†q†q!†s†ti߆w†wiá†y†|iâ†}†} l†~†‚i憅†‡i놊†Ži‘i󆓆šiõ†œ†žiý†¡†¥j†§†¬j†¯†±j †³†Ìj†Í†Í\†Î†Ñj(†Ó†Ôj,†Ö†ßj.†â†äj8†æ†æj;†è†ûj<†þ†þjP‡‡jQ‡‡j`‡‡jc‡‡jd‡‡jj‡ ‡*jk‡,‡.jv‡0‡5jy‡7‡8j‡:‡<j‡>‡Cj„‡F‡IjЇK‡gjއh‡h<2‡i‡pj«‡s‡j³‡‡…jÀ‡‡‡‰jŇ‹‡‹jȇ‡jɇ‡”jʇ–‡˜jЇš‡ŸjÓ‡¢‡¤jÙ‡¨‡¨j܇ª‡°j݇²‡Àjä‡Â‡Ìjó‡Ð‡Ôjþ‡×‡Ùk‡Û‡èk‡ê‡ïk‡ò‡ôk‡ö‡÷k‡ù‡ük‡þˆk#ˆˆk)ˆˆk6ˆˆk;ˆˆk<ˆˆ,k?ˆ.ˆ3kMˆ5ˆ9kSˆ;ˆ?kXˆ@ˆ@<^ˆAˆFk]ˆHˆHkcˆJˆJkdˆKˆK̈LˆNkeˆRˆWkhˆYˆ[knˆ]ˆ^kqˆaˆbksˆcˆc¯ˆdˆd÷ˆeˆekuˆgˆgkvˆhˆhl ‰A‰Dl$‰F‰Fl(‰I‰Il)‰K‰Ml*‰O‰Sl-‰V‰dl2‰f‰flA‰i‰olB‰q‰tlI‰v‰wlM‰y‰|lO‰~‰~ƒ‰‰Œ‰‰ƒlS‰…‰ˆlV‰Š‰ŠlZ‰‹‰‹<›‰‰l[‰“‰“l\‰•‰˜l]‰š‰Ÿla‰¡‰¤lg‰¦‰§lk‰©‰ªlm‰¬‰¯lo‰²‰³ls‰¶‰·lu‰¹‰ºlw‰½‰Àly‰Á‰ÁR‰Â‰Âl}‰Ä‰Ìl~‰Î‰Ñl‡‰Ò‰Ò@‰Ó‰Öl‹‰Ù‰él‰ë‰íl ‰ï‰ôl£‰ö‰øl©‰ú‰ül¬‰þ‰ÿl¯ŠŠ<¯ŠŠl±ŠŠl´Š Š l¶Š Š l·ŠŠl¸ŠŠl¾ŠŠlŠŠlÊ"Š#lÆŠ%Š%lÈŠ'Š'lÉŠ*Š*lÊŠ,Š-lËŠ0Š1lÍŠ3Š4lÏŠ6Š7lÑŠ9Š<lÓŠ>ŠAl׊DŠFlÛŠHŠHlÞŠJŠJlߊLŠRlàŠTŠYlçŠ[Š[líŠ^Š^lîŠ`ŠclïŠfŠflóŠhŠilôŠkŠnlöŠpŠwlúŠyŠy<°ŠzŠ|mŠŠmŠŠ‡mЉЉ<±ŠŠŠm ŠŠmБГmЕЖmŠ˜ŠšmŠžŠžmŠ Š¡mУЍmŠªŠ­m#ааm'ввm(жжm)ЏŠÀm*ŠÂŠÄm3ŠÅŠÅ<³ŠÆŠÉm6ŠËŠÍm:ŠÏŠÏm=ŠÑŠâm>ŠäŠämPŠæŠèmQŠêŠëmTŠíŠømVŠúŠümbŠþ‹me‹‹mj‹ ‹mo‹ ‹(m„‹*‹,m‹.‹1m‹3‹3m”‹5‹7m•‹9‹>m˜‹@‹Bmž‹E‹Lm¡‹N‹]m©‹_‹`m¹‹c‹cm»‹e‹hm¼‹j‹mmÀ‹o‹pmÄ‹q‹q<Å‹r‹rmÆ‹t‹tmÇ‹w‹{mÈ‹}‹€mÍ‹‚‹†mÑ‹ˆ‹ˆmÖ‹Š‹Œm׋ދŽmÚ‹‹mÛ‹’‹–mÜ‹˜‹šmዜ‹œm䋞‹Ÿmå‹ ‹ <Ñ‹¡‹«mç‹­‹°mò‹²‹ºmö‹¼‹Æmÿ‹È‹Ïn ‹Ñ‹én‹ëŒn+Œ ŒnIŒŒ6n]Œ7Œ7{Œ9Œ?nuŒAŒCn|ŒEŒEnŒFŒF¨ŒGŒJn€ŒKŒK<ÙŒLŒPn„ŒTŒTn‰ŒUŒVƒŒWŒWnŠŒZŒZn‹Œ\Œ]nŒŒ_Œ_nŽŒaŒakŒbŒbnŒdŒfnŒhŒmn“ŒoŒsn™ŒuŒwnžŒxŒxeŒyŒ}n¡Œ€Œ‚n¦Œ„Œ†n©Œ‰ŒŠn¬ŒŒŒ•n®Œ—Œšn¸ŒœŒœn¼ŒŒ쌞Œž iŒ Œ "tŒ¡Œ¡n½Œ¢Œ¢<àŒ£Œ¥n¾Œ§Œ¬nÁŒ­Œ®<猯Œ°nÇŒ²Œ²nÉŒ³Œ³ÇŒ´ŒÅnʌnjÈn܌ʌÊnތ̌ÏnߌьÓnãŒÕŒÕnæŒ×Œ×nçŒÙŒÝnèŒÞŒÞ%NŒßŒâníŒãŒã ŒäŒènñŒêŒê%ZŒìŒînöŒðŒñnùŒóŒõnûŒøŒþnþoo o oooooÎÑÐ!.o///K00!ó14o(55"68o,99N:Lo/MM=NNoBOOlPPoCSVoDX^oH`coOdd=fioSkooWpp'qq:ryo\{{od}}oe€of„…oh‰–oj™™ox›œoyŸ¡o{££o~¥¥o§¨o€ª¯o‚±²oˆ³³'%´ºoм¼o‘¾¿o’ÁÈo”ËÑoœÓÓo£Õäo¤æìo´îõo»÷ŽoÃŽŽ oÍŽ Ž oÖŽŽ'oØŽ)Ž,oñŽ.Ž1oõŽ3Ž6oùŽ8Ž:oýŽ<ŽBpŽDŽEpŽGŽNp ŽPŽWpŽYŽgpŽiŽjp(ŽlŽmp*ŽoŽpp,ŽrŽtp.ŽvŽvp1ŽxŽxp2ŽzŽ|p3ŽŽp6ŽŽ‚p7Ž„Žˆp9މމ=FŽŠŽšp>ŽœŽ¡pOޣަpUŽ¨ŽªpYޫޫެެp\ޝ޲p]ŽºŽºpa޽޾pbŽÀŽÀpdŽÂŽÂpeŽÅŽÆpfŽÈŽÉphŽÊŽÊ=XŽËŽÌpjŽÍŽÍ!)ŽÎŽÏplŽÑŽÔpnŽ×ŽØprŽÛŽãptŽåŽép}ŽëŽìp‚ŽîŽïp„ŽñŽñp†Žôp‡p— p¡#'p²)*p·,,p¹./pº29p¼;;pÄ>>pÅ??=_@@pÆBIpÇKdpÏff=igipékpì‹q‘q “šq››=pœœÁžŸq££q¦©q««q ­±q!²²=w´´q&¶¶u··q'¹¿q(ÁÂq/ÄÉq1ËËq7ÍÎq8Ðæq:èëqQíðqUòüqYýý0lþqdqm$q}&'q‰-/q‹12qŽ46q89q“;?q•ABqšDEqœGGqžIKqŸMYq¢[^q¯`cq³eeq·giq¸kkq»mpq¼r‹qÀqÚqÛ‘‘&O“•qÝ—™qà››qã£q䥨q몪qשּׁqð®¶qñ¸»qú½¿qþÁÁrÃÅrÇÈrÊËrÎÑr Óír ïõr(÷‘ r/‘ ‘ rB‘ ‘rC‘‘$rI‘&‘6rZ‘8‘;rk‘>‘Aro‘C‘Hrs‘I‘I&q‘J‘Jry‘K‘K!¢‘L‘Prz‘R‘Xr‘Z‘Zr†‘]‘er‡‘h‘jr‘l‘lr“‘n‘zr”‘}‘‡r¡‘‰‘r¬‘‘“r±‘—‘—r¶‘™‘¥r·‘§‘¨rÄ‘ª‘µrÆ‘·‘ºrÒ‘¼‘¾rÖ‘À‘ÀrÙ‘Á‘Á>‘‘ÃrÚ‘Å‘ÅrܑƑÆ7‘Ç‘Ërݑ̑Ì/¬‘Í‘Í!`‘ΑÎrâ‘Ï‘Ï-‘БÐrã‘ёёӑÛrä‘Ü‘Ü$Ö‘Ý‘ßrí‘á‘îrð‘ñ‘ñrþ‘ó‘ùrÿ‘ü‘ýs‘ÿ’s’ ’ s’ ’s’’s’’s’’s ’’s!’#’'s"’)’)s'’,’.s(’0’4s+’6’:s0’<’@s5’D’Fs:’H’Ts=’V’WsJ’Y’[sL’^’^sO’`’gsP’l’msX’o’rsZ’t’ts^’v’€s_’‚’ƒsj’…’ˆsl’Š’Žsp’‘’‘su’“’sv’ ’®s’²’·s’¹’¹s–’»’¼s—’À’Ós™’Õ’Õs­’×’Ùs®’Ý’ás±’ä’äs¶’æ’ês·’í’ós¼’÷’üsÃ’þ“sÉ““sΓ“sÏ““ sГ “sÒ““sØ““sÝ““/sá“2“6sô“8“<sù“>“>sþ“D“Dsÿ“F“Rt“T“\t “^“^t“`“at“c“et“g“gt“j“jt“l“nt“p“qt!“u“wt#“y“|t&“~“~t*“€“€t+“‚“ƒt,“ˆ“Št.“Œ“t1“‘“’t5“”“›t7““Ÿt?“¡“ªtB“¬“µtL“·“·tV“¹“¹tW“À“ÀtX““ÄtY“Æ“Èt\“Ê“Êt_“Ì“Òt`“Ô“Útg“Ü“ßtn“á“ètr“ì“ìtz“î“ît{“õ”t|””tˆ””tŠ” ”tŒ””tš” ”!t”%”%tŸ”(”,t ”.”.t¥”0”3t¦”5”Atª”D”Lt·”O”StÀ”U”UtÅ”W”WtÆ”Z”[tÇ”]”^tÉ”`”`tË”b”dtÌ”h”ktÏ”m”xtÓ”|”ƒtß”…”…&”†”tç”’”•tò”—”—tö”™”Æt÷”È”Îu%”ДÒu,”Õ”Ùu/”Û”åu4”ç”úu?”ü•uS••us•!•&uv•(•2u|•4•<u‡•>•Bu•D•Gu••I•Ju™•L•Tu›•V•Yu¤•[•_u¨•a•mu­•o•suº•v•vu¿•w•w>2•z•}uÀ••>3•€•€f•‚•ƒuÄ•†•”uÆ•–•–uÕ•˜•™uÖ•›•œuØ•ž•¥uÚ•§•©uâ•«•®uå•°•²u镵•·u앹•Àuï•ÕÃu÷•Å•Íuø•ЕÖv•Ø•Øv•Ú•Üv •Þ•åv •è•è’•é•ëv•í•þv––v)––v0––v;––v?––¶––µ––$vA–(–(vH–*–*vI–,–6vJ–9–=vU–?–@vZ–B–Qv\–S–Uvl–X–Xvo–[–_vp–a–mvu–o–xv‚–z–zvŒ–|–~v–€–€v–ƒ–‹v‘––•vš–—–™v£–›–žv¦– –¤vª–§–ªv¯–¬–¬v³–®–´v´–¶–¶M–·–¸v»–¹–¹>––»–¼v½–½–½$Ж¾–¿v¿–À–À>—–Á–ÏvÁ–Ñ–ÞvЖߖß>Ÿ–à–ãvÞ–å–åvâ–è–è–é–ëvã–ï–ñvæ–ò–ò6–ó–óvé–õ–úvê–û–ûÌ–ý—vð—— vö— —vþ——w——w—— w—"—0w —2—3w—5—5w—8—;w—=—?w#—B—Dw&—F—Iw)—K—Kw-—M—Mw.—O—Ow/—Q—R>Õ—S—Sw0—U—Vw1—X—\w3—^—^>×—`—aw8—b—b"E—d—fw:—h—hw=—i—i!B—j—nw>—p—twC—v—†wH—ˆ—ˆwY—Š—‹wZ——’w\—”—”wb———šwc—œ—žwg— —¦wj—¨—¨wq—ª—¯wr—²—´wx—¶—·w{—¹—¹w}—»—»w~—¿—¿w—Á—Áw€—×Éw—Ë—Ë"þ—Ì—Ðwˆ—Ó—Ùw—Ü—ßw”—á—áw˜—ã—ãw™—å—åwš—æ—æp—ç—çw›—é—ìwœ—í—í![—î—îw —ð—ðw¡—ñ—ñ>á—ò—òw¢—ó—ó!É—õ—öw£—ø—ûw¥—ý˜w©˜˜ Ę˜w­˜ ˜ w´˜ ˜wµ˜˜w½˜˜wÀ˜ ˜!wŘ$˜$wǘ&˜)wȘ+˜-w̘/˜0wϘ2˜2wј4˜5wÒ˜7˜9wÔ˜;˜=wטA˜AwÚ˜C˜FwÛ˜H˜UwߘW˜`wí˜b˜ew÷˜g˜gwû˜i˜kwü˜o˜twÿ˜u˜u•˜v˜Šx˜Œ˜x˜˜‘x˜“˜”x˜–˜˜x!˜š˜¢x$˜¤˜§x-˜¨˜¨0˜©˜ªx1˜¬˜¯x3˜±˜³x7˜¶˜¶x:˜¸˜¸x;˜º˜Äx<˜Æ˜ÇxG˜É˜ÉxI˜Ë˜ÌxJ˜Î˜Î#ç˜Ñ˜ÓxL˜Õ˜ÕxO˜Ø˜ÚxP˜Û˜Û˜Ü˜ÜxS˜Þ˜Þy˜ß˜ß)¯˜á˜ãxT˜å˜åxW˜ç˜ëxX˜í˜ïx]˜ò˜òx`˜ô˜ôxa˜ö˜öxb˜ù˜úxc˜ü˜þxe™™xh™™xi™™xk™™ xl™ ™ >ù™ ™ xo™™xq™™xz™™!x|™$™%x™'™3xƒ™5™5x™:™:x‘™<™?x’™A™Cx–™E™Ex™™G™Ixš™K™Nx™P™Yx¡™[™[x«™\™\>ÿ™^™_x¬™a™ax®™c™c©™e™ex¯™g™rx°™t™wx¼™z™zxÀ™|™}xÁ™™xÙ„™ˆxÆ™Š™‹xË™™xÍ™™•xΙ–™–!™™—™˜xÕ™™™™"”™œ™žx×™¡™¡xÚ™£™£xÛ™¥™¨xÜ™«™«xà™¬™¬ö™­™µxᙹ™½xê™Á™Éxï™Ë™Ùxø™Û™Ýy™ß™ßy ™â™åy ™ç™çy™é™êy™ì™îy™ð™òy™ô™ôy™ö™ÿyšš y#š šy.ššy1ššy7š š y=š"š%y>š'š.yBš0š2yJš4š:yMš=šFyTšHšJy^šLšPyašRšWyfšYš[ylš^š`yošbšbyršdškysšlšl>šmšqy{šsš‚y€š„šˆyšŠšŒy•šš“y˜š–š˜yššš¥y š§š§y¬š¨š¨#+š«š«y­š­š­y®š¯š±y¯š³š´y²š¶š¼y´š¾šÂy»šÄšÇyÀšÊšÍyĚϚÖyȚؚØ%šÙšÙ?šÜšÜyКޚÞyÑšßšß$½šášãyÒšåšçyÕšêšïyØšñšôyÞšöš÷yâšùšþyä››yê››yë››yï› ›yð››yú›› z›"›$z›%›%$›'›+z›.›/z ›1›1?;›2›2$›3›3z ›5›5z›7›7z›:›;z›<›<#û›>›?z›A›Oz›Q›Rz#›T›Vz%›X›Yz(›Z›ZÄ›[›[z*›_›az+›d›dz.›f›hz/›l›lz2›o›rz3›t›wz7›z›~z;›€›€z@›‚›ƒzA›…›ˆzC›Ž›“zG›•›—zM›š››zP›ž›¢zR›¤›¦zW›¨›¨zZ›ª›«z[›­›¯z]›±›±z`›´›¶za›¸›¹zd›»›»zf›½›½zg›¿›Ázh›Ã›Äzk›Æ›Êzm›Ï›Ïzr›Ñ›Özs›×›×?P›Ù›Üzy›Þ›Þz}›à›èz~›ê›ìz‡›ð›òzŠ›õ›õz›÷›øzŽ›ý›ýzœœz‘œœz’œœzœœzžœœz¢œœz£œ!œ!z¦œ#œ%z§œ(œ)zªœ+œ4z¬œ6œ7z¶œ9œAz¸œDœDzÁœFœNzœPœPzËœRœRzÌœTœZzÍœ^œ`zÔœbœczלfœhzÙœmœnzÜœqœqzÞœsœzzßœ|œ|Aœœz眜‚z蜅œˆz꜋œ‹zŽz’zñœ”œ•zôœšœœzöœžœ©zùœ«œ«{œ­œ®{œ°œ¸{œºœ½{œÃœÇ{œÊœÐ{œÓœÙ{!œÜœß{(œâœâ{,œåœå¿œæœç{-œéœí{/œðœ÷{4œùœý{<œÿ{A {C{J{K{L{M{O{R {S"#{W%&{Y(1{[33{e68{f;;{i=F{jHH{tJL{uOT{xVa{~dd{Šgl{‹ou{‘w{{˜}}{‚{ž„Œ{¢{«’’{­””{®–¤{¯¦­{¾¯¯{Ʊ±?s²¼{Ǿ¿{ÒÁÈ{ÔÊÓ{ÜÕß{æáæ{ñèé{÷ëð{ùòû{ÿýž| ž ž |ž ž |ž ž |žž|žž|žž|žž|"žžž ž#|$ž%ž&|(ž(ž-|*ž/ž/?Šž1ž3|0ž5ž:|3ž=ž?|9žAžL|<žNžO|HžQžQ|JžUžU|KžWžX|LžZž\|Nž^ž^|Qžcžd|Ržfžm|Tžpžq|\žsžs|^žužusžxžz|_ž|ž~|bžž?’ž€žƒ|ež†žŽ|iž‘ž•|rž—ž—|wž™ž|xžŸž¡|}ž¤ž¤|€ž¥ž¥"Rž¦ž§|ž©žª|ƒž­ž®|…ž°ž°|‡ž´žÀ|ˆžÂžÂ|•žÃžÄ?©žÈžÉ|–žÌžÌ|˜žÍžÍ?­žÎžÎ%]žÏžÐ|™žÑžÒ%/žÓžÖ|›žØžà|Ÿžâžâ|¨žäžë|©žížð|±žòžõ|µžöžö?²ž÷ž÷|¹žùžù2žúžü|ºžýžþ?³žÿŸ|½ŸŸ |ÀŸ Ÿ |ÆŸŸÈŸŸ|ÇŸŸ|ÉŸŸVŸŸ|ÊŸŸ|ÒŸ Ÿ ™Ÿ!Ÿ!–Ÿ"Ÿ%|ÓŸ(Ÿ9|ן;Ÿ;5Ÿ=Ÿ>|éŸ@ŸD|ëŸFŸI|ðŸJŸJ%}ŸKŸQ|ôŸRŸR'”ŸTŸY|ûŸ[Ÿg}ŸjŸl}ŸnŸr}ŸtŸ{}Ÿ~Ÿ€}ŸƒŸŒ}!ŸŸqŸŸ’}+Ÿ”Ÿ•}.Ÿ˜Ÿ˜}0Ÿ™Ÿ™+ÞŸšŸ›}1ŸœŸœ?ŸŸ}3ŸŸŸŸ🠟 ?ÄŸ¢Ÿ¢}4Ÿ¤Ÿ¥}5¬×£}7ùù¨Ûùùùù?Åùù¨Üùù?Èù ù ¨Þù ù ?Íù ù ¨ßùù?Ðùù¨âùù?Ôùù¨åùù?Úùù'¨íù(ù(?àù)ù,¨øù-ù-?âù.ù2¨üù3ù3?çù4ù4’ù5ù5©ù6ù6?éù7ù;©ù<ù=?îù>ù>©ù?ù@?ñùAùE©ùFùF?øùGùN© ùOùO?þùPùY©ùZùZ@ù[ù]©ù^ù^jù_ùa©"ùbùb@ùcùcìùdùf©%ùgùg.ùhùj©(ùkùk@ ùlùl©+ùmùm@ ùnùn©,ùoùq@ùrùv©-ùwùx@ùyù{©2ù|ù|@ù}ù}©5ù~ù~-ùù©6ù€ù€"ùù$ù‚ùƒ@ù„ù‡©7ùˆùˆ@ù‰ù‰©;ùŠùŠéù‹ù©<ùŽùŽºùù•©?ù–ù˜@(ù™ù™©Fùšùš@,ù›ùœ©Gùù@/ùžùž©IùŸù¢@0ù£ù©©Jùªùª@:ù«ù­©Qù®ù®@=ù¯ù¹©Tùºùº”ù»ù»©_ù¼ù¼@Hù½ùÀ©`ùÁùÁ@LùÂùédùÄùÄ@OùÅùЩfùÑùÑ@WùÒùÝ©rùÞùÞuùßùä©~ùåùå@aùæùæ©„ùçùç@cùèùè©…ùéùê@eùëù쩆ùíùí@hùîùö©ˆù÷ù÷*Ýùøùû©‘ùüùü@nùýùÿ©•úú@qúú©˜úú@súú ©™ú ú @wúú©Ÿúú@}úú©¢úú@„úú!©®ú"ú#@‡ú$ú)©±ú*ú*@ú+ú+©·ú,ú,@‘ú-ú-©¸þ0þ1=þ3þD?þIþOQþPþPÈþQþQXþRþRÉþTþWÊþYþfÎþhþkÜÿÿ^@ÿaÿŸYÿàÿâÿãÿãÿäÿåÿæÿæL°,°!-K°ÈQK°SZX¹ÿ…Y""""Z€ÞPæj„ªÐ 4Vnš´þ"lÎ Z¼ÚR²ü>`‚¤ ¦à < € º à  N t ž Ì ö  L ~ È  ^ ¢  " Z † Ú,PpŠªÌänÈ f²ôžÜ`’¨@Šæ@tÐLzÎüDh¼Ô(\”¨òP`æ0bª 0bŽÈò<š ¨L–È6xî   ® Ä Ú!N!d!z!ð"""r"à"ö# #"#‚#˜$$$.$p$¾$Ô$ê%%%F%\%r%¢%Î%ä&.&‚&˜&î'l'œ'ì((6(N(x(¤(î) )Z)j)z)–)È)Ø)è)ø*N*^*n*š*ª*º*è*ø++&+P+`+p+Ú+ê,6,„,ö-d-œ..l.¾.ú/H/x/ˆ/æ0(0X0Î0Þ1 1p1¾1ø262’2î303’3¨3¸3ú4 44T4d4ž5545ˆ5°5ä5ô666$646D6T6–6¦6¶6Þ77:7j7¬7ô828z8Ð99$9Š9æ::::J:„:è;;^;†;¸;ê<<"<@:>x>¾??V?l?„?œ?´?Ö?ø@0@h@˜@âALB B:BbB¾CLC´D0DrD‚E"EúF¾GTGºGÚHH*HœHâI>I|I¨IÔJ J2JKK˜LL2L^LŠLÆLîMM(MTMnM¸MÜN&NˆNÄOOvO”P PlP¶PøQQ<Q^QÆR`RšRöS:StSšS¼TT.TXT†T´TÎUU:U„UÂVV^VÀVÞWW@W”WÂWæX X*XDXdX†X XÄY*Y„YÈZZjZ¬[Z[˜[Þ\0\^\„\à]]f]À^^L^¬^ì_(_T_¤_Ò``@`”`¬aa4ažb&bÆc@cÐdpdÎe‚f$fÌgLgôh²iJiúj¸k4kþl¾m„mänbnøohoîp„pØq‚rr¸s.sÌt€uu´vhvÚwšxPy y"y8yNydyzyy¦y¼yÒzZzvzþ{ž||¤}F}¢~X~öž€8€Ä<È‚F‚¼ƒ˜„„v„ð…V…°†@†°‡.‡¼ˆHˆ°‰@‰´Š"Š„‹‹d‹ÞŒ8ŒÚrôŽŠ’tð‘h‘ì’\’À“X“Ò”Z”𕈕ø–’——ˆ—ô˜v˜â™h™Ì™òš šTš€š˜š°šÈšà››<›h›”›Ìœœ:œpœŒœ¨œÄœàœü4Plˆ¤ÀÜøžž0žPžpž”ž¸žØžüŸ Ÿ@Ÿ`Ÿ€Ÿ¤ŸÈŸè   0 P p ” ¸ Ø ø¡¡@¡`¡€¡¤¡È¡è¢¢,¢P¢p¢˜¢À¢è££8£`£ˆ£´£à¤ ¤8¤`¤ˆ¤°¤Ø¥¥"¥H¥l¥¥º¥Þ¦¦,¦P¦t¦ž¦Â¦æ§§8§d§š§Â§î¨&¨P¨x¨¬¨Ö¨þ©4©l©¤©ìªª0ªRªtªŽª¨ªÚªò« «"«:«R«j«‚«œ«¶«Ð«ê¬¬¬8¬R¬l® ®ò¯ ¯$¯<¯^¯Œ¯Ð°°Ð±*±„²Ê²à³³³8³P³r³Š³¬³Æ³î´$´n´öµ"µ`µžµ´µÊµàµö¶@¶j¶´·¤¸Ø¹àºä»D»¤»ð¼Z¼²½>½z¾ ¿¿6¿z¿ÆÀ ÀhÀŠÀªÀàÁÁnÁšÁÀÁæÂ:޲ÂÖÃ4ôÃðÄÄ0ÄRÄtĖİÅÅ®ÆDÆ^ƘƮÆÄÆÚÆðÇ Ç(ÇDÇ`ǂǒǢDzÇÄÈrȬÉ,ɲÊʆÊÐËXËh˲ËÂËÒËâÌÌ2ÌH̨̒Í4ÍÞÎÎ>Î~ÎŽÎÚÏ$ÏrϾÏÎÐÐlвÑ$ÑÂÑîÒÒ„ÒìÓ~Ô8Ô ÔìÕ<ÕXÕzÕºÕÒÖÖ^Ö¤Öê×`×ÖØØZØœØÜÙ.Ù¸ÙòÚÚNÚhÚÔÛÛHÛbÛ‚Û¨ÛÀÛâÜÜ<ÜtÜâÝfÝÆÝôÞ,ÞbÞ˜ÞÎßßZß’ßÊà àNàlààúádá”â>âPâPâpâºââã6ãVãvãªãÞãúää@äjäŽä²äÜäþå"åHå„åÀåöæ,ædæÎç‚è2è€èÖé&évêê”ë2ëÒìRìúíˆî>î‚îîïVïæð6ð®ñ$ñÆòòbòäóŽô ô°õ*õÌöX÷ ÷„ø&ø\ø˜øüù>ù¨ùôúhûûzüDüêý\ýöþÆÿ¬$Äz ÂÌ0ªLðx¸ N Î L È ^ ø b Ò < ž2¼,š ®ˆ„ü0z®ø,ˆÞ2^ŠÂü$LœðPØ"”æ`¬ DÖF” ^ÚBÒ pÔ`² * | Ð!J!‚!â" "Z"˜"º# #‚#¨#Ü$8$ª$ð%\%Þ&&~&ö'&'~'ì(R(à)„)Î**Z*¤*ð+>+–+¼+ä,,<,„,Â--Z-„-¶-æ.(.p.¬.æ/H/¢/è00,0L0”0Ø161l1¢1ò2H2Š2ì383†3°3ä4404|4¼4þ5`5˜5Þ66N6š6ì727”7º88l8¸8î9<9d9¦9¾: :B:^:ˆ:È:ä;6;´;Ô<<6>ž>È>ú?R?ª?â@8@Š@ÂA2AxAžAÆAüB^B~B¦BÎCC CJCrC¤CÄCøD4D`DŒD¬DâE$EPExEE²EÊEÊEüF2FzF°G GbGèH,H¬HüINIŒIüJ>J„JöKlKÌLL’M0MpM°MôNLN¬NÌObO´P*PQ QÌR(RlR¼RðS8SŠS¾SêT TpTÀUUpUÐV8V¤W*W–XXlXÈY4YÊZ6Z [[†[ð\b\â]|]þ^Š__z_ü`¦a&a€aÚb8bžccvcìd|dôeteØf>f²gTgÈh<h´i6iªj&j¬kNkÚlplòmrmþn°o*oÖpxpàqÊr–sDsÞt4u@vRw&w’x˜yTz6zÂ{@{¦|,|®}}°~z~ê>¶€¸²‚~‚¾ƒ"ƒ„„b…"…º†t‡‡ˆˆÌ‰xŠ ŠŠ‹jŒ0ŒÀtŽrXØ‘”’‚““Ô”˜•B–—N˜°™Z™èš˜›D›Àœ œ¨$rОlžâŸŒ . ¦¡&¡–¢££²¤,¤¼¥Ì¦l§§ ¨8¨Ä© ©„©èª.« «X«æ¬Š­p­ò®v¯¯X¯p¯ˆ¯ð°°@°”°ê±±2±V±z±®±â²²"²>²Z²„²®²Ü³³Š³þ´,´b´Ê´êµ4µPµlµŒµ¸µê¶@¶l¶¢¶Ê··h··¼¸¸&¸~¸ª¸ä¹ ¹\¹®¹øºRº˜º¼ºþ»L»˜»ü¼6¼’¼Þ½0½j½’½Ò½ô¾Z¾º¾Ú¿¿N¿’¿ÌÀ2À~ÀÆÁ ÁRÁ ÁîÂÂBˆ¼ÃÃJÃtèÃâÄÄ`Ä„ĨÄÌÄîÅÅ2ÅTÅvŘźÅÜÆÆ$ÆHÆjƌƮÆÐÆòÇÇ6ÇXÇ|Ç ÇÄÇæÈÈ*ÈLÈnÈȲÈÔÈøÉÉ@ÉbÉ„ɦÉÈÉêÊ Ê.ÊPÊŠÊÄÊþË4ËjË ËÖÌ ÌBÌxÌ®ÌèÍ"Í\Í’ÍÈÍþÎ4ÎjΠÎÖÏ ÏFÏ€ϺÏðÐ&Ð\Ð’ÐÈÐþÑ4ÑjѤÑÞÒÒNÒ„ÒºÒðÓ&Ó\Ó’ÓÈÓìÔÔ4ÔPÔlÔˆÔ¤ÔÀÔÜÕÕ$ÕHÕdՀ՜ոÕÔÕðÖÖ8Ö\ÖxÖ”Ö°ÖÌÖè××(×L×p׌ר×Ä×à×üØØBØlØ–ضØÖØöÙÙ6ÙVـ٪ÙÐÙðÚÚ0ÚPÚpÚÚ¶ÚÜÛÛ"ÛBÛbÛ‚Û¢ÛÂÛìÜÜ8ÜXÜxܘܸÜØÜøÝ<Ý|ݼÝöÞ2ÞlÞ¦Þàßß^ßžßâààVààÊáá@á€áÀââ:âtâ®âèã"ã\ã ãàä äZä”äÎååBå|å¬åÜæ æ4æ\æ„æ¬æÔæüç,ç\ç„ç¬çÔçüè$èLè|è¬èÜéé,éTé|é¤éÌéüê,ê\ê„ê¬êÔêüë$ëLëvë ëÊëììì0ìRìtì–ì¸ìÚìüíí@íbí„í¦íÈíêî î.îPîrî”î¶îØîúïï>ï`ï‚ï¤ïÆïèð ð<ðnð ðÊðôññHñrñœñÆñðòòDònò˜òÂòìóó@ójó”ó¾óèôô<ôfôôºôäõõ8õbõŒõ¶õàö8ööè÷0÷x÷ÀøøPø˜øàù(ùpù¸úúHúúØû ûhû°ûøü@üˆüÐýý`ý¨ýðþ8þ€þÈÿÿXÿ ÿè0dœÎ6hšÌþ2d˜Ì2d–Èü4f˜Êü.`’Æø*\Âô&X®~Ô * € Ö , ‚ Ø . „ Ú 0 † Ü 2 ˆ Þ4Šà6Œâ8Žä:æ<’è>”Þ(r¼Pšä.x V ê4~È\¦ð:„Îb¬ö@ŠÔh¦àTŽÄþ8pªè " \ – Î!!>!v!®!æ" "X""È##8#p#¨#à$$V$$È%%8%p%Ø&@&¨''r'Ú(B(ª))z)â*J*²++|+Ü,D,¨--x-Ü.>. //d/È0*0’0ú1d1Æ2(2Œ2ì3P3²3ø4>4„4Æ55J5Œ5Î66V6ž6ä7&7h7ª7ì8.8p8¶8ø9:9|9¾::B:Ž:Ð;;T;–;Ø<<\<ž<à="=T=†=¸=è>>F>v>¤>Ò??2?d?–?È?ö@$@T@„@²@àAABAtA¦AÔBB2BbB’BÀBîCCPC‚C´CâDD@DpD DÎDüE,E^EEÂEêFF:FbFŠF²FàGGDGlG”G¼GäH H4HbHH¾HæII6I^I†I®IÜJ J8J`JˆJ°JØKK(KbKœKÖL LBL|L¶LðM*MdMžMØNNDN~N¸NòO,OfO OÚPPFP€PºPôQ.QhQ¢QÜRRHR‚R¼RöS0SSðTPT°UUpUÐV0VVðWPW°XXpXÐY0YYðZPZ°[[p[Ð\0\\ð]P]°^^p^Ð_0__ð`P`p``°`Ð`ðaa0aPapaa°aÐaðbb0bXb€b¨bÐbøc cHcpc˜cÀcèdd8d`dˆd°dØee(ePexe eÈeðff@fhff¸fàggDgvg¨gÚh h>hph¢hÔii8ijiœiÎiîjj.jNjnjŽj®jÎjîkk.kNknkŽk®kÎkîll.lNlnlŽl®lÎlîmm.mNmnmŽm®mÎmînn.nNnnnŽn®nÎnîoo.oNonoŽo®oÎoîppBplp–pÀpêqq>qhq’q¼qærr:rdrŽr¸râs s6s`sŠs´sÞtt2t\t†t°tÚuu.uXu‚u¬uÖvv*vTv~v¨vÒvüw&wPwzw¤wÎwöxxFxnx–x¾xæyy6y^y†y®yÖyþz&zNzvzžzÆzî{{>{f{Ž{¶{Þ||.|V|~|¦|Î|ö}}F}n}–}¾}æ~~6~^~†~®~Ö~þ&XŠ¼î€ €R€„€¶€èL~°â‚‚F‚x‚ª‚܃ƒ@ƒrƒ¤ƒÖ„„:„l„ž„Ð……4…f…˜…Ê…ü†.†`†’†Ä†ö‡(‡Z‡Œ‡¾‡ðˆ"ˆTˆ†ˆ¸ˆê‰ ‰*‰J‰j‰Š‰ª‰Ê‰êŠ Š*ŠJŠjŠŠŠªŠÊŠê‹ ‹*‹J‹j‹Š‹ª‹Ê‹êŒ Œ*ŒJŒjŒŠŒªŒÊŒê *JjŠªÊêŽ Ž*ŽJŽjŽŠŽªŽàL‚¸î$ZÆü‘2‘h‘ž‘Ô’ ’@’v’¬’â““N“„“º“ð”&”\”’”È”þ•4•j• •Ö– –B–x–®–ä——P—†—¼—ò˜(˜^˜”˜Ê™™6™t™²™ðš.šlšªšè›&›d›¢›àœœ\œšœØT’ОžLžŠžÈŸŸDŸ‚ŸÀŸþ < z ¸ ö¡4¡r¡°¡î¢,¢j¢¨¢æ£$£b£ £Þ¤¤Z¤˜¤Ö¥¥R¥€¥®¥Ü¦ ¦8¦f¦”¦Â¦ð§§L§z§¨§Ö¨¨2¨`¨Ž¨¼¨ê©©F©t©¢©Ð©þª,ªZªˆª¶ªä««@«n«œ«Ê«ø¬&¬T¬‚¬°¬Þ­ ­:­h­–­Ä­ò® ®H®p®˜®À®è¯¯8¯`¯ˆ¯°¯Ø°°(°P°x° °È°ð±±@±h±±¸±à²²0²X²€²¨²Ð²ø³ ³H³p³˜³À³è´´8´`´ˆ´°´Øµµ(µPµxµ µÈµð¶¶0¶P¶p¶¶°¶Ð¶ð··0·P·p··°·Ð·ð¸¸0¸P¸p¸¸°¸Ð¸ð¹¹0¹P¹p¹¹°¹Ð¹ðºº0ºPºpºº°ºÐ»»<»r»¨»Þ¼¼J¼€¼¶¼ì½"½X½Ž½Ä½ú¾0¾f¾œ¾Ò¿¿>¿t¿ª¿àÀÀLÀ‚À¸ÀîÁ$ÁZÁÁÆÁüÂ2ÂhžÂÔà Ã@ÃvìÃîÄ0ÄrÄ´ÄöÅ8ÅzżÅþÆ@Æ‚ÆÄÇÇHÇŠÇÌÈÈPÈ’ÈÔÉÉXÉšÉÜÊÊ`Ê¢ÊäË&Ëh˪ËìÌ.Ìp̲ÌôÍ6ÍxͺÍüÎ>΀ήÎÜÏ Ï8ÏfÏ”ÏÂÏðÐÐLÐzШÐÖÑÑ2Ñ`ÑŽѼÑêÒÒFÒtÒ¢ÒÐÒþÓ,ÓZÓˆÓ¶ÓäÔÔ@ÔnÔœÔÊÔøÕ&ÕTÕ‚Õ°ÕÞÖ Ö4Ö\Ö„Ö¬ÖÔÖü×$×L×tל×Ä×ìØØ<Ød،شØÜÙÙ,ÙTÙ|Ù¤ÙÌÙôÚÚDÚlÚ”Ú¼ÚäÛ Û4Û\Û„Û¬ÛÔÛüÜ$ÜLÜtܜܴÜÌÜäÜüÝÝ,ÝDÝ\ÝtÝŒݤݼÝÔÝìÞÞÞ4ÞLÞdÞ|Þ”Þ¬ÞÄÞÜÞôß ß$ß<ßTßl߄ߜߴßÌßäßüàà,àDà\àtà˜à¼ààáá(áLápá”á¸áÜââ$âHâlââ´âØâüã ãDãhãŒã°ãÔãøää@ädäˆä¬äÐäôåå<å`å„å¨åÌåðææ8æPæhæ€æ˜æ°æÈæàæøçç(ç@çXçpçˆç ç¼çØçôèè,èHèdè€èœè¸èÔèðé é(éDé`é|é˜é´éÐéìêê@êjê”ê¾êèëë<ëfëëºëäìì8ìbìŒì¶ìàí í4í^ížíÞîî^îžîÞïï^ïžïÞðð^ðžðÞññ^ñžñÞòò^òžòºòÖòòóó*óFóbó~óšó¶óÒóîô ô&ôBô^ôzô–ô²ôÎôêõ<õŽõàö2ö„öÖ÷$÷r÷Àøø\ø¬øúùHùšùìú>úúâû4û†ûüürüèý^ýÔþJþÀÿ6ÿ¬"˜„úpæ\ÒH¾4Tt”´Ôô4Tt”´Ôô4Tt”´Ôü$LtœÄì  < d Œ ´ Ü  , T | ¤ Ì ô  \ œ Ü  \ œ Ú  Z š ÚX˜ÖR’ÒR î<ŠØ&tÂ^¬úH–ä2€Îj¸dºf¼h¾jÀlÂnÄlÀlÂnÄ  p Ä!!n!Â""j"À##l#Â$$n$Ä%%\%¨%ô&>&ˆ&Ò''h'²'ü(F((Ú)&)r)¾**T* *ì+F+ +ú,T,®--b-¼..p.Ê/$/~/Ø020Œ0æ1@1š1ô2N2Ò3V3Ú4^4à5b5ä6h6ì7p7ô8x8ü9~::†; ;Ž<<–==D=n=˜=Â=ê>>:>d>Ž>¸>â? ?6?`?Š?´?Þ@@2@\@†@¸@êAANA€A²AäBBHBzB¬BÞCCBCtC¦CØD D<DnD DøEPE¨FFXF°GG`G¸HHhHÀIIpIÈJ JxJÐK(K€KØLLHL€L¸LðM(M`M˜MÐNN@NxN°NèO OXOOÈPP8PpPÆQQrQÈRRtRÊS$SzSÐT&T|TÒU(U~UÔV*V€VÖW,W‚WÌXX`XªXôY>YˆYÒZZfZ°Zú[D[Ž[Ø\"\l\¶]]J]”]Ì^^<^t^¬^ä__T_Œ_Ä_ü`4`l`¤`ÜaaLa„a¼aôb,bnb°bòc4cvc¸cúd<d~dÀeeDe†eÈf fLfŽfÐggTg–g¼gâhh.hThzh hÆhìii8i^i„iªiÐiöjjBjhjŽj´jÜkk,kTk|k¤kÌkôllDlll”l¼läm m4m\m„m¬mÔmün2nhnžnÔo o@ovo¬oâppNp„pºpðq&q\q’qÈqþr4rjrÊs*sŠsêtJtªu ujuÊv*vŠvêwJwªx xjxÊy*yŠyêzJz¬{{À||d|ê}F}²~~`:²€<€ hÌ‚.‚^‚ª‚êƒjƒô„‚… …š††ô‡8‡ªˆˆŽ‰4‰ÖŠxŠì‹´Œ&<ŽVhz‘‚’Š“˜”š•°–j–¨–æ—h—ê˜P˜Ž˜ò™V™ÊšB𬛛@›l›–›Âœ:œ†œÔn¾žžNžŽžÎŸŸNŸŒŸÐ  X œ à¡$¡h¡¦¡ê¢H¢À£8£¶¤.¤¦¥¥–¦¦Ž¦Â§x§¬¨¨`¨ö©`©¾ª(ª\ªÒ«8«À¬H¬²­H­ê®r®â¯.¯à°0°Ú±‚±è²–³³´´xµ`µÔ¶„¶ø·J·œ·ö¸H¸Þ¹L¹ˆ¹àººÈ»<»t¼^¼š½¦½â¾¾~¾ø¿lÀBÀ¤Á Á6ÁbÁŽÁÖ˜ÂÄÂÄ:ÄøÅ(Å~ŲÅøÆ,ÆŒÇÇžÈ0È¢É&ɪÊ”ËLË‚ËÞÌ:ÌšÌüÍ`ÍÀÎ>δÎðÏ(Ï`ϘÏÐÐÐ@Ñ ÑŽÒ ÒFÒ¢ÒòÓxÓ°Ô Ô<ÔšÕÕ€ÕÐÖ"ÖtÖÆ××x×ÊØBضÙZÙ¸ÚÚdÛ(Û|Ü>Ü–ÝVÝæÞ&ÞšÞàß&ß|àà>à¨àþáÊâRããÐä<ätäòåNå¾åöæ¤çLçôè|èôé4é´êŽêÈëžì$ì€ííŽî.îšï.ïžððPðÆññXñ òópôŽõ°öZö¬öÞ÷:÷„øFø°ùnùœúlú¶ûjûÐü„ýnþ*þêÿzÿ²*ŒnÌdªð:ˆJ T°6r® pÚ  Š H ò D Æ  | Ú N ¸Hê*jæbà~Ú:ºþ¦2|â`˜Âdö"¾"Z æ2œÜ\¤Xü,ˆÚ.ÆH î > ®!!€!ì""L"´#J#°$x%%t%ü&L&ª&à''L'˜'ä(4(º(î)ˆ)¼***ž+ +J+Ä,",V,Â- -ê.H/$/T/Â0X1˜22¬33z3Þ4X4²5&5˜606ª6Ò7Ú8ž9J9Ô:V:Â:ú;j;Ø<$<¸=f=à>@>Š>¾??j@$@„AvAÔBB¤BÌC(C´DD E&EnEòFxGGÎHPHªIRI–IÜJ$JjJòK&KŽKêLHLÂM(M|M´NfOO”OäP.PvP¶QHR$R‚RÈSS`S¸TUU¦UÞV.V|W¼X&XnYYŠZZrZâ[:[¸\>]]@]þ^¶_v_®`0`Üa€aæbDbÀbúc8cºdd¨dìe>eÒf.f¨g`gÆh<h´i(i¾j:jtj¬k0k¢l‚m.mônvnÀo@oÚpZpÀqqˆqÂr rpr¼rÞs&snsät:t”tÖu@uªv:väww†wôx<x¾y0y”yðzì{˜{ô|,|’|Ö} }h}°~~¾¨€€Š€Ì¼‚v‚¼ƒœƒè„„Î…ˆ†>†f‡‡|‡ÖˆZˆ”ˆæ‰@‰ˆ‰ÆŠ&Š\Š”ŠÚ‹ÂŒ>Œ~ŒÐ:bÌŽfŽÂX´š‘|’,’¼“\”$”l”â•*•r•ì–€–Þ—’—þ˜l˜´™ ™¼šš¨››z›æœ.œ”œÚÖžnžºŸ Ÿ„  ˆ ê¡Š¢8¢’££n£¶£ü¤P¤´¥P¦*¦¢¦ð§€§ö¨Þ©~©èªhª”««f«¬¬4¬”­0­f­æ®:®”®ú¯P¯¦°°Ô±f²,³(³°³ô´Z´‚´äµFµ²¶*¶Ö·X¸D¹4ººÆ»»b»Ä¼>¼ ½†¾<¾Ä¿<¿¦ÀÀ¸ÁÁ†ÂhÃÃFÄÄzÄØÅPÅœÆ"ÆpƼÇbǺÈ ÈèɆÊ6ÊðËZÌÌLÌöÍÍnÍ–;Î"ÎrάÎæÏ(ÏŒÏþÐXÐîÑ:ѲÒ’ÓÓ^ÓþÔ`ÔÊÕHÕºÖPÖ¤×"×vØغÙzÚÚ0ÚdÚÂÛ4ÛÌÜŠÝZÞÞ–Þüßrà8àXáázá®â`â¢ã|ä6äjäæåšæ´æêç çjç°è4è¤ééVé˜éØêhëëzëìì~í,í¬îî|ïïÆðXðäñDñnñÔò0ó óPóøô¨ôØõõXõÔöÐ÷d÷¶ø>ø„øœù~ùÞúÎüüœýýœýâþbþÆÿÿÿÌ&~:’b¨ô€ZÎ8Îzø~ T ¤  Æ Ž  j Ô  € À*¼þfº¢*ŒÀfÐfØNª–ÎÔDÀVþ ’6TŒ.‚¶: $ z Ô!!†""¨#z$*% &&Ò'R'Ò(&(Ð)r)ö*¦++b+¸,,\,Â--@-°..2/"/â0¾1H22d2´3*3 44Œ4¸5.66^6Ø7H7¶8*8f9 9R9ü:V:æ;2;Œ<*<„<þ=x=º>D>Ê?2?æ@N@¶APAÊBæC.CÌD$D¼EEpEàF€GBG²H6II²IêJÆKfLL¢MdN¨O:O–P P~P®QŠQèRŒRúSRSºTTbT¶VV²WNWöXˆXÞY0YtYÀYèZH[6[ \$\î]\]’]î^J_ _¸`0`øaVa bb€bîc<c¬dBdØe&eØf&ggèh\h i<i¾iòj¾kNk”kôl$l´mfm¾nhooŒpFp¨q†r@s2sÖtzu:uÌvˆw>wŠxdyzz–{f|H|š}~}Ô~.~öx€šRº‚B‚ȃƒnƒÎ„2„€„Þ…l††œ†ä‡\‡°‡Ò‡úˆ"ˆJˆÀ‰ ‰zŠŠpŠØ‹F‹øŒvŒàZ ŽŽlŽîžt‘X‘~‘Ð’J’è“j“°“ø”ª”•:•¢–D–Æ—(—|—À˜(˜À™Vššš›ŒœBœÒÄøžtžìŸpŸú T € Ô¡(¡„¢¢Œ££Ž£ò¤x¥¥œ¦*¦–§2§º¨p¨Ò©2ªªtªð«v«È¬­"­v­Ø®@®h®œ¯&¯´° °d°Ð±’±Ê²l³.³ì´¬´ðµ®¶v·@¸¸˜¹N¹àºB» »¸¼f¼ô½F¾,¿¿öÀfÁÁ¶Â&ˆÃ6ÃtÄ.ÄšÄàÅfÅìƸÇBÇÎÇøÈjÉÉÆÊtË"ËþÌÖÍ\;Î ÎÊÏpÐÐNЂкÑdÒÒìÓ~ÓüÔ|ÔüÕˆÕÞÖ*Ö‚Öà×LרØnØ¢Ù ÙrÙ¶Ú ÚtÚÒÛ2Û¸ÜDÜäÝžÝôÞPÞ²ß4ß’ßäàbáálââjãääîåH岿žçŽè>èºéLé´ê&êfê¤ëëšì0ìÄíípí¼îîpîØï(ïˆï¨ðð˜ðîñ`ñªñöò.òŽòÐó2ó–óÞô:ô†ôæõõvõÞöövöü÷X÷Èøøhø–ù ùvùÒúúVú˜úØûûPû†û¼üüÖýýàþ°ÿ*ÿ¤"òxêXövðV  Àj¦ât– ( º P ´  x H   6 ÌdÈ0xì$ˆÆ‚NÐ@® äjþzþ‚<zþ0n8È‚:Â$ŒhÈ $ „!:!ô"z##†#ö$f%%ž&8&È'V'æ(’)D)è*Ž+*+ž+Æ,h--‚..¦/8/Þ0„1l2*2è3b3Ü4V4Ì5L606¬77´8L8®9*99î:V;(;ö<Æ=>X> ?H?Ø@|@ðBC CHCpDìEdF FÆGGHH¦HüIzJJÜKNKÆL®LâM\MÚNpOOtPhP¾QQÖR(RhSTTT²UNVFWWŠXX\XâYDYxZZ¢[&[[º\ \h]]Š^<__T`,aaNa¢aþbRb¦cBcÞd"dÆe exeäfPfØg^gêhŒi.iÎj2j–júkJkškâl*l”m.m¾nnPnÞoro¼pZpøqZqŠqúrhrÊs,s¨t"t´uZuÊv:v¨wwnw´x y$ydzVzÞ{Î|n}}H}¦~ž~ÖÆ€ff΂<‚d‚ÀƒƒŽ„8„Ü…N…n…Ð…ð†d†°‡‡d‡°ˆˆ^ˆÆ‰Š2Š„‹tŒ Œ@Œ~¢ŽŽjŽÞp²‘\’’Ì“X“ä”`••‚––ž—6—¾˜L˜ú™¢šP𸛤œPœ”$€ØžjžŸ8Ÿ”Ÿþ V¡¡L¡¸¢D¢¬¢þ£V£¨¤"¤Ê¥’¦F¦r§(§r§¸§þ¨.¨|¨Ä©©X©°ª ªfªˆªÊªþ«H«¶¬¬@¬v¬Ò­B­x­¸®J®š®ö¯<¯ˆ¯Ô° °B°x°¶°ì±$±„±ô²*²¢³P³¼´(´üµ°¶8·@·Ì¸œ¹@¹ÜºTºÌ»B»¾¼$¼ ½½ˆ¾¾x¾ô¿P¿¬ÀÀTÀ”ÀÔÁÁ¤ÁÚ ÂP†Ã&ÚÃöÄ*ÄnÄ ÅÅ>ÅšÅöÆ"ÆžÆÔÇZǘÇÐÇúÈBÈ”É É4ÉhÉ´ÉüÊ$ʆÊÔË Ë\ËŽËÐÌ¢ÌîÍ:Íf͸Î ÎLΰÎúÏ:ÏpϺÐЄÐôÑ8ÑnÑ®ÒÒ`Ò°ÒúÓHÓ˜ÓâÔNÔ¢ÔþÕ|ÕâÖRÖÂ×2×~×êØ8؆ÙÙ°ÙÜÚÚ4Ú`ڌڸÚäÛzÜ2ÜZÜ¢ÜÊÝÝLÝ’ÞÞ„Þ¸ßßhßÐàRàzà¢á á¦ââZâ âìãã<ãjãÔääVä¨äâå(å°æ2æ´ç6ç~è<èèêéDéØê2êÂë ëŽìì”ííšíìîNîÎïJïÎðXð¬ðøñxñÆòFòÆòæó(ójóªóÜô(ôtôÀôðõ:õpõ²õâö*ö„öÀöè÷T÷„÷îø\ø¤øÌùDù¸ú<újúÆû"û~ûÆüüæýýþþBþzþðÿHÄH"lÂ@x¾@†ì@ˆêDŒÈ ^¦"b  è Ž ` ü d È  l Î0’ôläZ¼PÜpÂ`‚ôHÔ|Jèˆ,Ðbºö2@¸6ÚN‚®â8€Ìü 6 ´!v"R#D#ü$–%%ˆ& &†''ì(H(°) )r)¬)þ*†+D,,V--è...ò/Â0Š1*1ì2ž3^4f4²5ª6 6p6Â7(7j7¼7ô8`8Ò9V9ú:2: :ê;Z;Ä<*<Ö=’=ð>b>Ú?p?º@\@þAœB:BÚCVCÀDD`D¢DèEDEŠEôF4FFæGZGüHVHÀIBJJ¾KK@K¬LLLLªMMRMæNPN¼O6OÆOþPÂQ†RR\RòSŒT,TÈUURU¢VNVúW¦XXbXÊY6Y‚YþZBZrZ¢[2[¼\F\\Ú]D]Æ^2^’^Ø__F_ˆ_ú``æahaîb^bÎcBc²d&d–e&e¶fFfÚgnhDii>iŽj jzjòkhkÚlLlÆmJm¼mènlnæoXoØp\pÞqRqÂrr€rêsJs¦tt2t–túuPu|vvˆwwŠwÊxNxÔyyŽyäzBzÌ{€||ì} ~"~¾’€€ìd‚ ‚êƒXƒÊ„8„¦„î…^…̆<†¬‡‡t‡¼‡ôˆ ˆ†ˆÖ‰6‰üŠ4ŠvЏ‹(‹‹ÀŒ*Œ¨ŒòD¾ŽZŽàt \œ‘‘†‘ì’N’¤““Š“ü”T”¨••\•ª––f–ª——B—¬—ܘ,˜†˜ð™t™Ìššnšššð›F›ˆ›ÎœœVœšœÖB°žž^ž¸Ÿ¦Ÿì L Ž î¡2¡p¡ì¢`¢Ò£@£´¤"¤”¥¥Z¥¸¦¦X¦´§0§”§Ü¨:¨Œ©©@©²ªªpªÎ««\«¶¬¬†¬Þ­&­˜­î®F®œ¯"¯z¯Ò°@°¼±¨±ú²&²€³³F³ª´´„µ<µ`µö¶¶®·(·P·p·”·ú¸¸t¸¶¹*¹d¹¤ººfºÒ»$»v»Ö¼¼B¼Ž¼æ½4½z½ô¾–¾â¿~¿ÌÀÀXÀ®Á Á„ÁöÂP²ÃÃ^úÄDÄÎÅTÅœÆ.ÆÎÇ*ÇtǰÈÈ`ÈÀÉÉtÉÚÊRËrÌ̲Í~Î4ÎþÏ–ÐLаÑRÑ‚Ò(ÒÈÓlÔÔ²ÕÕXÕ¦ÕøÖJÖ¤××\×®ØØNتØöÙnÙ®Ú ÚhÚÐÛ ÛjÛÖÜBܦÝ(ÞVÞÈßÚàîá\á¾â"âˆã(ã`ãˆã°ä"äJäzäªäòå8åhå˜ææ0æâçç>çxç¾è.è–èæééxé¶éîêJêÌë$ë’ëèì>ìŽìÞíTí íìî<îžîÐï0ï’ïîðpðÞñTñÄò8ò˜ó~óÞô6ô–ôìõ@õ”õòöFöšöî÷HøøjøÚù2ù†ùàú6úÌûbû¨ü ü˜ý ý þ<þ®ÿTÿ„ÿÊÿþ(nÆ Tœîbªð8tÎF‚Ð:f  D¶(^†èBj’Èþ & | Ê H ª à 0 – æ 6 n š  X Ô$–ê:ŠØ,†À>|h²\¬ú2f Ø<~Â\ÄøJ¸œ–l¾,’ô^ŠÆö2|æ"^šÖB’  \ ˜ Ô!!p!¸"""š"ô##`#Ò$>$ $Ð%˜%À&$&†&è'F'¤((°)R)Æ*2*Æ+8+ ,,ª--Œ-ú.d.Ð/>/Ú060Œ0â1"1R1’1è202†2à363”3ê4F4œ4ò565p5ª6>6Œ6æ7@7¤8<8l8Œ9N9è:¾;H;ö<ž=B=æ>º?^@@¦A A¢B8B~CpD"DîEÖFvFìG<GŒGÌHHHHœIBI‚IÂJJBJªKXKòL„LÞMžN6NÊOO–PP RRžSÊTTfT¾UU–UøV`VÂW0WªXXtYJYºZ®[[@[Â\"\P\œ\ü]f]Î^^\^À__”_ö`F`˜`üa`a´bbTb¦böcÚd.d¬e0eæf’g>gêh–i:iâjÔkblHlúmÞnTo<oÒp~qŒr s´tÆuTuÒvZvÞwdwêxnxòyzzzŠ{{„||„}}d~~X~®~ì²ð€.€|€Æè‚:‚r‚ª‚ìƒ2ƒjƒ¢ƒÚ„„´„ì…$…d…ʆ$†„†Þ‡<‡š‡êˆ$ˆ^ˆ¢ˆæ‰&‰l‰²ŠŠlŠ®‹‹z‹ÀŒŒlŒÌ*ŽŽ(ŽŠŽöJªrœ‘‘@‘¤’’ˆ’Â’ü“H“”””T”𔿕0•Ž•ì–J–„—ˆ˜x™™Äš`šô›„œ œ–* žž\žÒŸHŸ†ŸÐ 8 ²¡,¡Š¡þ¢6¢®£&£ž£þ¤„¥¥’¦¦ª§§R§Ü¨€¨ô©l©àªTªÈªü«p«ä¬t¬ö­z­þ®‚®ô¯\¯Î°h°ø±’²²’³&´´¬µ4µÌ¶¶Œ¶À··\·ª·ô¸B¸¸¹Z¹¨ºFº”ºâ»®¼8¼Æ½½N½n½ü¾n¾Ê¿P¿ÊÀÀˆÁÁ~ÁÄÂ<¸ÃÈÃÖÄNĤÅÅÖÆJƪÇ"ÇšÈÈ~ÈèÉPÉ”ÊÊžË.ËÄÌhÌþͨÎ>ÎàÏvÐ ÐXЦÐòÑ.ÑfÑ´ÒÒ:ÒˆÒØÓ&Ó|ÓÒÔÔnÔªÕÕTÕ´ÕðÖ¢Öö×2ׄ×ÖØJظÙ*ÙžÙöÚTÚ¬ÛÛœÛìÜJÜšÜêÝ:ÝŠÝêÞ2ÞxÞØß&ßžßþàæââ\âœâäã2ãzãÂä äˆå å’ææ ç(ç®è2èèîéJé¦êêXê¾ëëzëØììnìÖí2íˆíäî@î¢îøïXï¸ð<ðÀñ ñ¤òò”òÚó>ó¦ôô|õ8öŒ÷¢ø„øêùPùÜújûûœü8üœý(ý´þDþÐÿ\ÿè z ì F ž î H ¢ ú R ¤  v ä X Î : – ö †  ª : Ê \ Ü l ü Ž ò \ Æ D ” æ X À J ò t \ F Œ  – Ú & j ® Ú  L  | Ê  € Æ J Ô  Š  < r Ø 4 ˆ Ü 0 \ ì z  ¦ ð !^ !Ì "< "¬ # #ˆ #ö $f $’ $â %< %x %Æ &, &š &Þ 'R 'ª ( (n (Þ )" )d )Ò *< *ª +4 , ,’ -R . /$ /” 02 0Ú 1º 1þ 2® 3 3¸ 4  5~ 5ð 6` 6Î 78 8 8Ú 96 : :t ;@ ;¸ < <„ = =¦ > >: >~ >â ? ?j ?Ô @ @† @ð AZ AÆ B2 Bx C C` CÄ D( DÌ Ep Eö Fš GL G¢ Gø HN H¾ I. Iž J JZ J¦ Jò Kt K¼ L L` L² M MØ NŠ Nö OH O  OÞ P P† Pê QB Q  Qú RT R² S ST S² T Tj TÚ U. UŽ V V~ Vð Wb WÔ X XZ Xœ XÞ Y Yª Z$ Zh Z¬ [ [` [º \ \’ \ì ]F ]º ^n ^Ì _( _z `F a ax aº aú bL b‚ c& c„ cÞ dT d¦ dø eJ eò f” g: gØ h€ i i€ iÆ jL jÎ kŠ lF m mÎ nŒ oF oæ p¢ q^ qÒ rF r† s" sÒ tH tÒ uF uº v2 v¦ w wL x( y yì zª { {~ {ì |Z |Ä }2 }þ ~& ~” ^ €( €† D Ä ‚& ‚‚ ƒV ƒÞ „( „ž … …r …Ü †F †¨ ‡ ‡Z ‡¾ ˆB ˆ¬ ‰$ ‰d Š Šú ‹ˆ ‹ò Œ\ Œè r Ž Žš 6 Ì D È ‘6 ‘¤ ’ ’d ’Ò “ “F “´ ”$ ”‚ ”î •„ •ð –Î —ª ˜z ™ ™è š: › ›Œ œ` œê t ú ž‚ žþ ŸR Ÿ¦    |  Ô ¡$ ¡t ¢ ¢V ¢’ ¢è £< £Ê ¤x ¤Ì ¥ ¥h ¥È ¥ü ¦@ ¦„ ¦Ü §( §x §Ä ¨n © ©š ©î ªt ªú «~ ¬ ¬B ¬Ä ­B ­ü ®¶ ¯Œ °f °Î ±d ² ²” ³6 ³Ì ´\ ´ü µŠ µô ¶^ ¶È ·0 ·š ¸ ¸p ¸Ú ¹D ¹´ º ºŒ ºú »h »¬ »Ø ¼ ¼j ¼¶ ¼þ ½d ½Ö ¾ ¾r ¾ä ¿V ¿à ÀP À¾ Á8 Áü ˆ à À Ãä ÄH Ĭ Å Åp ÅÐ Æ4 Ƙ Æà ÇN Ǿ È Ȳ ÉJ ɤ Éü ʦ Ë\ Ëö ÌŒ Í" ͼ ÎV Îê Ï„ Ð Ð~ Ðæ Ñ, Ñ’ Ñì ÒJ Ò¼ Ó2 Ó  Ô Ôˆ Õ Õt Õæ ÖX ÖÌ ×F ×À Ø0 ؤ Ù Ù„ Ùô Úl ÚÈ Û$ Û€ Ü ܈ ÝB ݶ Þ. ÞÜ ßd ßì àp àô áÜ âf â ãj ä ä^ ä¾ åP åâ æ’ æ¸ æÞ çV çÆ çú è^ èæ éX éæ ê@ êÐ ë` ëì ìx í íˆ íþ ît îæ ïX ïÜ ð^ ðâ ñh ñì ò¬ óf ó² óþ ôD ô¸ õP õè ö~ ÷ ÷¦ ø øt øê ùZ ùÆ ú< ú¬ û û‚ ûê üd üâ ýN ý¾ þ. þ¤ þü ÿp ¢ 8 Ò j  À f  ¶ f Ü R ¸  „ ð Š ð Z ¾ & Ž ø ` J 0 ¤  |  P Þ ^ Ü \ Ü \ Ö V ¶  ˆ æ D ¤  T ®  ^ ´ ’ ê h ¾     b º ¤  š 4 Ð !l " "  #< #Ø $t % %† &t ' 'Œ ( (Œ ) )Ž *\ *à +R +ê ,n , -F -v -ú .p /0 /b /ò 0v 0Ø 1: 1Ä 2X 2Þ 3f 3ì 4r 4þ 5„ 6 6 7 7œ 8 8¬ 9: 9È :X :æ ;t ;ü <š =( =¦ >$ >„ >þ ?t @ @J @Ü AX A¬ B4 B¾ C8 C¶ DD D˜ E E€ Eê F@ F² G Gr Gª H Hv H® I In IÖ J JZ Jè K| L Lr M M¤ N< NÎ OP OÔ PR PÔ QX QÚ R^ SH S® TH TÈ U6 U¶ V6 V° W2 W® X0 X¬ Y, Y° Z< Z¸ [@ [Ä \0 \¦ ]† ^ ^¬ _. _® `, `¬ a, aª b& bn bè cj c¾ d> d¸ e2 e eÚ f$ fn f¸ g gL g– gþ h\ h¶ i i† iú jp jØ k kh k° kø l@ lˆ lÐ m mŒ mÔ n nÈ o ož oæ pd qD q’ qà r rr s s˜ t u uÔ vØ wÜ xÐ yH yÀ zp zè {` {à |` |à }V }Ú ~Z  † €* €Ô ¾ ‚ ‚N ‚Ü ƒ~ ƒÂ „t „è …\ † †¨ ‡< ‡r ‡Ä ˆb ˆ¨ ˆî ‰Ž ‰ü Šš ‹ ‹„ ‹ø Œ~ ŒÖ J ê Ž\ ŽÐ D ¸ * j â ‘V ‘Ê ’" ’– “ “€ ”, ”ž ”þ •` •À – –z –Ü —L —¬ ˜ ˜T ˜¶ ™ ™® šD šÚ ›p ›Ð œ0 œˆ œ´  t Ì žŽ žÖ Ÿˆ  B  ü ¡º ¢` £ £Ì ¤€ ¥> ¥ø ¦¦ §b ¨ ¨Ú ©– ªR « «Ê ¬„ ­( ­† ®$ ®Â ¯: ¯ê °N °È ±. ±â ²6 ² ²ä ³8 ³Œ ³ä ´ ´b ´¸ µ µd µ¼ ¶ ¶d ¶¨ · ·À ·ø ¸ ¸€ ¸ò ¹d ¹¬ ¹ô ºD ºÖ »" »’ ¼ ¼F ¼¸ ½N ½ú ¾¬ ¿Z ¿˜ ÀR Àš ÀÒ Á Á^ Á¢ Áê Â0 Âx ÂÀ à ÃN Ô ÃÞ Ä& Än Ķ Äü ÅD Ũ Åü Æ~ Ç ÇŽ È È’ É É‚ Éî ÊV ʼ Ë" ˈ Ëæ ÌD ̪ Í ÍŒ Î* Έ Îæ Ïè ÐF Ъ Ñ4 Ñž Ñü Òh ÒÖ ÓN Ó¼ Ô Ôt ÔÐ Õ, Õè Ö¨ ×, Ø ج Ù Ù¨ Ú” Úî ÛV ÛÜ Üš Üö ÝR ÝÌ ÞB ÞÊ ßB ߬ à à† á á” â ⪠ãB ãö ä  å å˜ æ æ” ç çŒ è. èz é éð êÜ ëd ëò ì€ ìò íb íÒ îB î² ïX ð ð¬ ñV ñü ò¨ óR ó¼ ô$ ôŒ õ õ˜ ö ö– öî ÷p ÷ò ø4 ø| øþ ù‚ ú( úÐ ûx ü üN üª ýP ýØ þJ þ¬ ÿ ÿp ÿö ž Ú 6 ’ ð ¢ ú P Ò P Ö Z Ú R Ð B º 6 ´ 8  @  6 ² 2 ¶ h ê œ  r â ž  ˜  D ¦  l î T ¶   > ž  ˆ ð Ú V Þ f  ` ä p  X †  ~ ö v ! !Š " "– # #ž $" $  % %° &* &¨ 'T 'ô („ (° )P * *Ð +˜ ,0 ,¸ -¦ .D .â /h /ú 0˜ 1* 1¼ 2N 2à 3< 3Î 4@ 4Ð 56 5ž 6 6t 6È 7 7l 7Ê 8. 8‚ 8æ 9F 9À : :| :Ü ; ;> ;„ ;à > >ž ? ?f ?Æ @& @Æ Af AÐ BB B² Bø CŒ D D¶ EJ EÞ Fr G G´ Hp I" IÖ JŽ KD Kþ Lh LÒ M& MŒ Mä N< N” Nê OB Oº P0 Pœ Q Qr QÞ RJ R¶ S SŠ Sð T^ Tè UT Uº V0 Vœ W W Xb Y Y€ Yð Z` ZÐ [@ [È \8 \ª ]* ]ª ]ð ^p ^ð _p _ð `p `ð ar b bt bÖ c cˆ cä d\ dä eh eì fp g g¼ hZ hÞ i^ iÚ jV jÔ k k¤ l. l¨ lÜ mX mú nx n¸ o6 o¨ pH pÄ qF qÀ r: r¸ s, sÌ tD tâ uV uþ v¢ wL wø xP xô yš z z~ zè {X {È |2 |ê }  ~X  Ð €\ €è t ‚ ‚Œ ƒ ƒp ƒÐ „2 „” „ö …z † †Š ‡* ‡Ê ˆÀ ‰D ‰È ŠN ŠÐ ‹  ŒT j È Ž Ž| ŽÜ < œ 2 Ä ‘X ‘ê ’| “ “® ”L ”¾ •0 •  – –l –Æ —B —Ž ˜ ˜² ˜Ú ™V ™Š š š. š® šØ ›¬ ›ö œp œè B  ž< ž¸ Ÿ6 Ÿ²  .  ª ¡& ¡¢ ¢ ¢Ð £€ £þ ¤z ¥ ¥ž ¥ê ¦p ¦¸ ¦è §j §¼ ¨ ¨Ä ©v ª& ªÎ «€ ¬ ¬° ­H ­à ®x ¯ ¯¨ ° °˜ °ì ±@ ±” ±è ²4 ²€ ²Ô ³& ³R ³¤ ´ ´n ´Ô µ8 µª ¶ ¶† ¶ø ·j ·Ü ¸H ¸º ¹( ¹^ ¹Ð ºB ºº »6 »² ¼. ¼¬ ½( ½¦ ¾$ ¾¢ ¿ ¿ž À À– Á Á’ Áô ÂV ¸ Ãj Ä ÄÆ Äæ Å ÅÊ Æp ÆÜ Ç” Ç´ È ȶ É, É´ ÉÔ Ê† Êî Ë  ÌP Í ÍL Íþ ζ ÏZ Ïz ÏÜ Ð~ О о Ñb Ñ‚ ÑÜ ÒV Ò~ Ó( ÓÔ Ô~ Õ* ÕÖ Ö| ×8 ×° Ø\ Ù Ù– ÚH Úö Û¤ ÜX Ý ݲ Þ Þ~ ß ß´ à( à á^ â( âî ã¸ ä‚ åH æ æª ç@ çØ èn é é0 éŒ ê0 êÄ ëZ ëð ìP ìî í† î îÖ ï” ð ðÌ ñˆ òT òÈ ó˜ ôP ôì õ† ö* öÐ ÷< ÷Þ øz øþ ù‚ ú úŠ û0 ûÔ üz ý ý þX þò ÿ– 8 Š Ü . ˜ ê < Ž à 2 Ò $ ˆ Ú . ‚ Ö * ~ Ò & z Î "    š  ‚ ö n Ü L º ( –  z ò l ò x  ¬ F ä €  ¼ R ô  (  \ ö , ^ æ n ¸  € æ L ²  ~  ’ ê v – ! !¢ ", "º #. #° $2 $® %" %  & &| &ø 'l 'Ü (x (è ) )` )Ð *@ *è + +„ +ú ,f ,Þ -f -Æ .0 .œ / /t /Þ 0D 0° 1 1T 1¾ 2* 2Œ 2ü 3h 3´ 4h 4Ü 5L 5À 6 6Ž 6¾ 72 7¨ 8 8X 8® 9 : :þ ;n <Ž = =† >$ >Ò ?T ?î @ @® AJ Aä B€ C C¶ Cæ D‚ E EÀ F^ Fð G‚ H Hœ I IÆ J4 J” Jú KZ K¾ L" Lt L M$ MÌ N0 N NÞ O< OÜ P6 PÀ QT Qè RZ RÔ S S€ Sö Td TÒ U@ U® V VŒ Vú Wh XP X¤ Y" Y Z ZŠ [ [z \, \Þ ]„ ^. ^Ô _€ `* `Ô a€ b. bl c c¼ d^ dâ ef eê fn fò gv h0 hž i" iÀ j& jŒ k k” kè lZ lŠ m m’ n n¬ nú o~ p pŒ pì qj r rx sb s¬ t$ tœ u už vZ w w| wô xT xª xÖ yJ y  yÞ z@ z˜ zÚ { {‚ {ð |h |¬ } }^ ~2 ~Ê € D ¤ ‚ ‚ä ƒ\ ƒ´ „ „d „ò …| † † ‡" ‡¬ ˆ< ˆÊ ‰Z ‰â Š: ŠÊ ‹X ‹ä ŒZ Œ’ Œæ L Š ð Ž2 Ž  0 ¶  h À ‘p ’ ’Î “8 “¤ ” ”r ”Ð •. •Œ –, –Ð —v ˜ ˜¸ ™Z ™Ð š^ šâ ›P ›ð œR œÈ ‚ ž žö Ÿ’  .  ¼ ¡N ¡Ü ¢n ¢è £R £Ê ¤4 ¤Î ¥F ¥Ú ¦– §* §Î ¨Š ©H ª ª¾ «~ «Â ¬" ¬f ¬Â ­ ­H ­Â ®B ®¬ ¯2 ¯‚ ¯ü °v °ð ±L ±  ²& ²  ³ ³¦ ´6 ´¾ µh ¶ ¶¤ ·6 ·² ¸* ¸¤ ¹ ¹– º ºŠ » »‚ »ô ¼T ¼Î ½J ½² ¾& ¾° ¿, ¿¦ Àž Á  Âè Ãt ÄN ÄÊ Åp Åú Ç Ç” È Ȧ É, ÉÀ ÊR ʸ ËT Ëð ̬ Íb Íþ ÎŒ Ï ϰ Ð< ÐÌ Ñ\ Ñè Òt Ó Ó² Ôf ÔÚ ÕN ÕÀ Ö0 Ö¤ × ׌ Ø> Øô Ù  Ú& Ú¬ Û2 Û¸ Ü> ÜÄ ÝH Ý´ Þ ÞŒ Þø ßd ßÐ à< àž á ဠáö ân â ã ã ä äx äî å^ åÔ æd æ¾ çT çà èp èü éŒ éÔ ê` êð ë| ë¼ ìH í íª íè îh îª ïR ïö ðž ðà ñˆ ò, òˆ ó óˆ ô ô® õR ö öX öØ ÷R ÷Ô øR øÎ ùN ùâ út û ûœ ü6 üœ ý ýh þ þÌ ÿ2 ÿ˜ ÿö \ ì Þ  š ” d  â F ª è Š 4 à ~ 6 Ð n  ¦ 4 ° , ¨ & –  v æ V F Ú n à P  0 ¤ 8 Æ V â n ô ´ t 4 ü À þ À ˆ F ² !p "0 "ð #¦ $` % %´ &F &Ü 'p (~ )„ *’ +, +l +ì ,h ,Ø -n -è .ž /` /ö 0€ 1 1l 1Ò 28 2ž 3 3j 4 4¼ 5Š 6T 7 7ô 8¼ 9Š :X ; ;è <œ =T > >h >Ð ?2 ?Ä @V @è Az B Bž C4 CÆ D\ Dò EŒ F( F GX HH I2 Iª J" Jœ K K’ L L° M` N NÀ O OÎ PL PÆ Q8 Q° R& Râ Sž TX TŒ UH V V¾ Wz Wæ Xj XÌ YJ Y‚ Z< Z€ [ [¢ \. \¾ ]N ]¦ ^r _@ ` `Ô a¢ bl bÔ cœ dh e eà f° g: gÈ hX hä il iø j„ k k– l0 l¶ m@ m² n4 nz o o’ pX pü qˆ r rº sP t tö uˆ v v¤ w0 wö xl y y’ zp { {È |t }" }Î ~| V ¢ ò €r €¾ ^ à ‚^ ‚Þ ƒ^ ƒÞ „^ „Þ … …P …Ø †l †ò ‡² ˆ. ˆÞ ‰Ž Šf ‹* ‹ö Œ¨ h Ž< ŽÆ N Ö ` ê ‘t ‘þ ’ˆ “ “¦ ”0 ”º •n –" –Ô —Ž ˜> ˜ð ™z š šŽ › ›¢ œ, œÊ d þ ž Ÿ( ŸÀ  X  ò ¡l ¡Ò ¢* ¢  £0 £’ £ú ¤` ¤Ø ¥* ¥’ ¥ö ¦h ¦æ §8 §Š §Ü ¨. ¨€ ¨Ò ©$ ©ˆ ©Ú ª ªb ª² ªô «Ê ¬L ¬¨ ­¨ ® ¯P °( °þ ±` ² ²Ú ³D ³¦ ´N µ µ¾ ¶| ·6 ·î ¸¤ ¹N º º@ ºì »œ ¼F ¼¬ ½\ ¾" ¾¼ ¿" ¿ˆ À2 ÀÄ Á6 Á‚ Â< Âú ú Ät Å2 Åò Æœ Ç Ǹ È` É É® ÊP Êð Ë’ Ì0 ÌÔ Íz Î Î^ ÎÄ Ï Ï® ÐT Ñ Ñè Ò° Ó‚ Ô@ Õ ÕÒ Ö  ×n ØT Ù4 Ùˆ ÙÆ Ú„ Û Û” Ü ܶ ÝP Ýî ÞŒ ß* ßÈ àh á â âè ãÆ ä¤ å‚ æb ç ç¤ èF èæ éÊ êh ê¦ ë. ëÆ ì` ìö íŠ î îž ïL ïú ðB ðò ñš òB ó ó ô ô  õ õä öä ÷ä ø¾ ù¾ ú˜ ût ür ýn þ. þî ÿ¸‚FØžn*òjâZÒZâ j ô €  – $ ¬ 4 ÂJØf¤Œ’zîbÜt°Jæ„"¼PèzBÔpn¶ > È!R!Ü"¢#h$.$ò%¸&~'D( (â)¼*˜+<+Ü,|--à.”/J/þ0~0ø1t1ô2Œ3$3¼4z55¶6V6ö7–868ö9¶:l;,;ì<¬=`>>¢?F?ì@ŒA>AðBBÊC|D@DúEÆFŠGLHH I2IÀJ KbL"LæMªNhO$OäP¤Q`RRÞS SÔTT–U$U¾VhWW¼XdYY¶Zf[[®\\] ]¶^b__®`T`ôab$b°cFcôd¢eNe°ffpfÎg¶h(hÈi0iüjvjÒk0kÐkðlœmLmônšo*oºpZpúq˜r8rÚspsötÂu€vzvøwdxx˜y.yÄz˜{8{Ü|‚}~Z~ð†€€¤:Ò‚nƒƒ°„`……´†\†Ø‡ŽˆP‰ ‰ÆŠ~‹8‹úŒ¼ŒôŽ@Žð‘^’L“2””p•:–&——ò˜¦™–š šÐ›œœBœú°ždŸŸÒ ²¡‚¢4¢â£Š¤>¤Ì¥V¥ä¦Œ§:§ì¨|© ©œª4ªÎ«^«î¬ˆ­­Ž® ®Š¯¯‚°°¨± ±œ²²”³³„´´~´úµtµð¶l·*·ä¸œ¹4¹Ìºb»T»°¼¼l½½´¾6¾æ¿ÈÀŒÁÁªÂ6ÂÂÃHÄ@ÅŶÆ"ÆŒÆöÇ`ǺÇþÈfÈ®ÉÉÊÊxË˰ÌH̪Í ÍNÍêÎ>Î’ÎèÏ:Ï’ÏäÐ6ЊÐÜÑ0ÑâÒ”Ó ÔˆÕpÖX×.ØØÚÙvÙÔÚÎÛ”ÜTÝÞJß~à´ájâ ã ãôääåÔæ¼ç¦èdéé¼êhë&ëäìíNî î~ï<ïúðDñ.òòþóˆôxõdöD÷2øøæù²ú|ûHüüâý¬þ„ÿ.øœ†<ŒübàF¢ü,`è,xÄ^Ö < ˆ Ì h ´ . z Î  d ¼ D  ðÈl¸6ÂDð~8PœÔ6˜ Œ N–ðNª fvÚ0Ö"”ÞXÚ J À!!l!Ö"H"¼#P#®$¢%p&&Î'j((®)N**´+X+â,d-"..Š/R04122Ô3z4F5 67.8T99â:²;î<à=ò>N>®??p?Ê@*@˜@úAlAìBrB²C4CxCèD2D|DðEˆFF¬G0G¤H@HÜIVI¢IòJRJ²KK¤LLhL¾M8MºNNŠNÞOFOºP@P”QQ‚QòRhRæSRTT|UUxUðVjVøW\WúX°Y.Y†YâZtZÖ[8[š[ü\\\¾] ]‚^^J^Â__l_À``l` `Úaa`a²aþbhbÐcXcÂd dˆdÈe(eÊftfügvhhŠiiŽj j˜k2k²l.lÆm mfmònZnÎoopoîpÆqbrps^sätptêu<u¦vv|væwPw´wðxbxòy’zDzÐ{2{®|,|¦}}œ~4~ÎTþ€®Lƒ ƒ¶„@……ª†R‡D‡ðˆ ‰2‰Š‰ìŠt‹ªŒœ<ŽޤR,𑬒x“"”••Ì–š— —ä˜ˆ˜ô™¤šNšî›8›‚›Ìœœ`œª –àž*žlž¸ŸŸZ  Ô¡V¢¢Î£†£ê¤n¤ö¦¦ä§Œ¨H© ©Êªî«¢­®,®Ø°.± ²R³€³Ö´ ´€´¼µ&µà¶D¶¶è·¤¸L¹¹x¹ðºFºœ»»†¼2¼ª¼ú½j½æ¾t¿¿šÀÀ^À¾Á$ÁºÂDÂôÃ’ÃþÄdÄÀÅ"ÅvÅêÆŽÇ,ǪÈBÈÀÉ0ɘÉúÊPʰËBËÊÌDÌÎÍ ͰÎZÎäψÏäÐZÐàÑÎÒzÓ ÓœÔ:Ô˜ÔöÕDÕÜÖTÖÐ×tØ4ØÒÙ˜ÙúÚxÛÛØÜzܼÝ*ݨÞVßfà>á áèâ ã„äå`æŠæâç*ç‚çÈèfèôé¤êXêàë¦ìœííPí¨îî–îòïfððRðšñ ñhòòxó&óüô„ôÔõhõÌö*ö˜öÜ÷F÷ ÷Öøø|ùùxù¶úVúäûNûÊü‚ýýªþþ¸ÿ:ÿÀ|",쮼ú8¨V,¾† 0 ,  |  Œ R.ÌLîЦâÀdèàŽn`D2Þ"`ÂLü¦Ð  ‚ î!N!Œ!ì"v"Ü#v#ä$€%%â&Z&Ø'|'ø(x(ö)–**˜++\,,Ô./0 0¤1.1Ö2~323¼4`5,5Þ6t77R7®88>8°8ü9„9Ú:0:†:â;V;¸<.= =h=æ>R??Ø@p@êAlAâB„CdDHDÒEzF\GG–HBHòJ4J–KhKœL L®MJMäN˜OTPhPôQÀR†S†T<UUÌVèWøX0YYê[[[Þ\P\Ð]b^^ì_‚`0`Êa*aœaôbTb´ccfc¶d dVd¦dòe^eØfTfÒgRgÊhJhÌi*ižiôjdjÄk"k€kÔl*l†lâmBmšnn„nòonoÈp(p¤pæqq°rJr¬sHsètHtºuZuÜvZväwxwäxxyydyÄzž{z|(|Ú}â~@~Ö,Üæ‚žƒ^„††ê‡V‡ÞˆDˆˆˆè‰x‰îŠn‹‹ÐŒn2îŽt$ü‘æ’v“&“~”Z”²•H•Ò–H–ž—Ì™™þš–›&›¼œ˜(ØžZŸŸ¶ Z¡¡¤¡ê¢–£’¤¤ò¥Š¦r§¨$¨Ô©’ª^ªú«’¬"¬ä­ ­–­ö® ¯P°:°±±t±Ü²€²ö³r´^´öµÖ¶¦·z·®¸¸h¸ ¸à¹6¹j¹²º0ºšºâ»»|»ê¼V¼Ü½h½À¾¾F¾†¾ø¿v¿ÈÀÀnÀ²ÀúÁ>Á~ÂÂF†ÂðÃnÄBÄžÅÅNÅÊÆ ÆNÆŽÆàǨÈrÉ’Ê.ʶË`ÌÌøͲÎhÏÏØФÑ*ÒÒÒÓ¸ÔhÕÕÆÖ4Ö¸×פ×ðØ8ØÖÙDÙäÚPÚøÛ–Ü,Ü¢Ý2ݾÞhßß|ßæà~á"á¢ââjã,ã äläøåâæ€ç@èèæé¬êpë ë´ì†íí¼î˜ï|ïðð¢ñÈò°óvôôºõ\ö"öÂ÷døøÞù‚ú ú¦ûVüüîý þˆÿ ÿòF:²rRÀVh   þ ö î Øb|–Æ îÒ rŒP^œþ^¾€¸ðVž^è:Þ |!B!°"¦##È$6$¤%%´&€''î(°))¬)ø*”*Ò+L+ä,",´-8-º.L.è/¸0Ž1^1œ2®33f3æ4X4¢4ò5J5 5ø6j6î7´8@8Š99X9Ä:R:ä<<,<”<ô=\=¾=ø>D??P?”?Þ@Z@ØAAbAÜB„CC¢CúDFDÀE:EÊFLG GòHtII¸J†K(KÂLšMhMÎNNNöO°P P¸Q>QÂR`S S¬TTbTÀUU|V&VzVÌWjXXfYˆZ8Zî[Š\~]@]ú^:^–__¤`.`¬a|bZcHcÐd’dÔeexf:ffúg@g‚gæh(h®iiTi€i¾jjZj¤jîk8k‚kàl mRmÐnznøorp6pÖqq\q¾r"rŒs0t tèvwx@y&yªz˜{|| |š}}\}¶~@~ðxè€8 ¨‚2ƒƒÐ„Œ„ò…^†<†t†ü‡„ˆˆ|ˆ¬‰”‰ÒŠ0Šþ‹NŒ„LŽŽ`.VÜ,˜Âì‘‘<‘f‘î’†’°’Ú“–“À“æ” ”t”š”Ä••ª–D—4—À˜š™|šš¼›bœœÚœäžžjž¼Ÿ<Ÿ¤Ÿò $ €¡¡²¢¢œ¢þ£^£æ¤`¤â¥d¦D§ §x§¶¨@¨ª©©¾ªÚ«Æ¬V¬ž¬ò­l­–®®x®ê¯L¯°° °Â±|±ò³ ´P´Øµ&¶ ··´¸&¸l¸¶¸à¹„º(ºØ»ˆ»È¼¼²½½‚¾¾Î¿ ¿ÐÀÈÁ®”ÃzÄ`ĺÅÅjÆÆrÆÞÇfÇÔÈ ÉÈÊŽË&Ë˾ËìÌ8ÌÒÍlÍÖÎ,ÎÊÏlÐоÑNÑØÒ²Ó<ÓêÔÚÕ ÕŒÕâÖNÖÀ× ×fתØBØÄÙÙ|ÙÚÚ¾Û€ÜLÜÄÝœÞxßßÄà àòá~â"ããâälåæç0çöèÞébê>êüëvëºì.ì¢í,í¾îðïÌð²ñ”òDòÀó<ó¸ô&ô–õõèö0ö~öÌ÷÷˜÷âø,ø¨ù$ù¶úšûJûòüšýhþFþÌÿ”ÿüNÊ\¾x¢Ì`¶0¬XRØb’âÐ ˜ x ¨ Ø  8 î h Š þ „ öäRŽFÂ~äDÊ" ‚Þ´Š~JÜ hXRäN|œün°  ¶!P!Ì"D"Þ#È$4$t$Ì%\%¬%ü&r&ô'T'¬(.(¼)J)ê*°+>+â,D,°--p.H.Â/V/¶0‚1B2d3V3à4d4ø66Š787Ú868’8ê9|:;;è==n=ô>f??œ@@ÐA"A–AöB|C0CöDE"EŒF@F¾G‚H:HÂIžJ(JÌKjL6MMÒN„O(OàP¶Q’RXS SÀTU(VVôWÒXÊY‚Zh[˜[Ú\\œ]]Ð^Ž_^``’`Þa‚b^c8cŠdHdÒeŠfFfæghgðhVhäiÈj¬kŽkælÌm2mÐmønLn¶nüoBoˆoÎpppp®pîq.qnqšqÆr4ržrÞs(sXsŒsÀttht®uuJu¨vvDv¶wwzwÖxFxÌz{2{œ{î|"|T|||À}}V}¨~~¸N€*€Î–¾‚‚¤ƒ˜ƒâ„„V„ „ê…V…œ†N‡8‡n‡žˆˆ˜ˆú‰X‰¬ŠŠˆ‹‹V‹˜ŒŒ¼f¾Ž ŽZŽ„4f8¸‘j‘Ö’€““~“æ”V”À•b•Ú––®——t—Ƙ˜l™™X™ˆ™ðš„šò›*›j›öœl–ž žjžÆŸ¤ @¡¡Ö¢>¢ü£‚£Ô¤,¥¦¦þ§°¨’©"©X©¼ªHªö«š¬¬¢­€­ø®`®Ü¯X¯ª¯ü°R°¢±T±ò²”³8³È´˜µ.¶¶ž·8¸(¸Ö¹‚ºXºà»¶»ê½½š¾¬ÀÀ¢Á4ÂÂxÃZÃÖÄzÄèŰÆFÆÌÇ ÇvǼÈ0È„ÉÉÒÊ@Ê„ÊÈË,ËØÌHÌÚÍ ÎŒÎêÐfÑÑšÒÒÌÓØÔ6ÔÆÕVÖÖ®×4×þؾÙ„ÚZÛÜ ÝݦßߎßêànàÄádáÌáøâXâæãbãîäpå*æ.ç¤è ètéé”êê¢ë"ë ìTì„í&í¬î`ïZð8ðæñlñàòžóDóÚôRôÄôüõ|õÐöDö´÷F÷’÷ÂøDøzø®ù¤úŒû\ûÀüVüÈý:þþ6þšÿÿrÿìf–’ ¼x È|2ðŽ"  Ä T  v ^ þ . ^ ®¸bÎ:Üzvø¼\¬hþŒV,œÜRÂ0tútö¸Î¼ ¨!Œ"b#j$ˆ%¨&6&n' '²(¬)º**D*Ä+ˆ,¨-R-Ô.6.È/*/Œ/Ò0\0¤1D2L2”2Ü3X3Ü4 4ž4Ú55b5ž5Ö66V6è7.8L9l:ˆ:Ê;D;¨<$<¨==–>>`??Ð@b@øAäBÄC¢D²E E†F.F€FÔGGVG¦HHVIfI¾JbKKÚLpM\MºN N¦O$O¼PdPþQ¨QþR.RbR®RäS\SœTU.UvVV¶VþW¢X.X¬Y@YØZ.[\$\°]–^^¬_@_ð`Øa~b^c*cêd†ereÚf0fÆg`gühLi iŠjj¢kTkâlŒmmvn"nnðoZožp^qqÖr rhr¼rìsVs¸t0thuDv2w@x>x”xèyDz6{.{‚{ö|&|p|º} }˜~(~¶zà€F€â|‚‚Š‚æƒÈ„„”… …„…ú†~†ò‡Œˆ,ˆ~‰nŠb‹‹°„öŽl0ô¶‘~’R’ΓL“È”2” ••À––´—X—ü˜L˜œ™|šX›2›ðœ€ŽîžLž®ŸR   Ø¡N¡ê¢þ£¶¤>¤ü¥À¦¼§Ì©D©pªªŒ««Ž¬D¬È­J­š­Ê­ú®L®œ®È¯¯ °`°Ü±²²H²Ì³¤´z´üµº¶¼·R·¼¸~¹H¹´ºº²»`¼¼r¼æ½¦¾n¿¿°ÀTÀøÁœÂ@ÂÞÃÌĜłÆXÇLǨÈdÉÉìÊDʨËË|ËôÌN̬Í^ÍØÎxÏϼÐZÑÑpÑúÒàÓšÔ¼ÕVÖX×`Ø^ÙŒÚÀÛ4Û´ÜpÜœÝÞ,Þ„ß`à>àðápáðâLâÔã¢ä:äò妿Zç çºènéé€êJëëÞì¦í"î2ïRðXð®ñÜòPòØó\óîônôÊõ\õ®ööº÷6÷¾øøØùjú`ûXüý&þbþ þÞÿ¢ œ°4Ò Tîœ.Ö œ  ¢ P ä x  ‚š&̤’–䞀ö´ŽÈ öÆÞ²Z$|ô Ž!(!ž!â"˜#€$%.%Ž&&j&à'D'Ì(p(Þ)º*ª+,,N,Ä-8-´.0.¤/T/Ø0P0â1@1Ô2,2®3Â4"4z55†5î6ž7N7þ8L8š9F::”;F;Î<†==ú>F>Ä?.?”@hA@B8ClC²D2D²E4E²F0FFòGtGôH”I6I~J€KrL2LôMPN,O OÐPPnPúQžR€SZT$T´U¤V0VºW:WŽX$X¤YYÒYþZ4ZŽ[[Î\L\Ê]Ž^|__ü`‚`äaaNaŒaÌb bÌc~deœfäg:gjgâhFhäi~jjxjükÆmm–múnÖo<o¢p:pÒq¢rzssêtÊu¦v"v ww”xxˆyyÒz–{d|.}}Ö~tœ€$€®8Ú‚|ƒƒÀ„b……¤†J†ì‡¢ˆ`‰‰ä‹ŒZŒè6øŽºÌÒ‘Ú’¢“R”4”ˆ”Ø•*•z•È–:–Ž–è—>—Ú˜z™™´šFšÐ›\›æœJœÚrôž˜Ÿ$Ÿ´ ª¡:¡´¢Š£J¤¤–¥P¥ò¦ä§P§Š¨R© ©ÀªŠªö«|¬¬Š¬î­ˆ­ú®L¯¯’°&°º±P²r³’³îµ¶.·L¸¸ê¹Äºv»»º¼b½l½Î¾$¾®¿ŒÀ`ÁFŠÃÞÄÄŒÅÅ|ÅüƆÆôÇHÇèÈ¢ÉXÊDÊæË®ÌpÍ~Î6ÏÏþÑ2ÒRÒöÓxÓêÔjÔ¾Õ&Õ„ÖÖ˜××>Ø&ØðÙ®ÚPÛÛÆÜvÝ&ÝÖÞêß²ààVá8ââêãtä.åæ:æÂçÊèÊéÌê¾ë&ììÖíJíºîRîèï|ððxðÜñŒòxóhóÐô6ôœõfö0ö÷÷šøø°ù2ùÚúÀû üxýTþ.ÿÿà>ÂFÌL’®¾pÐ0Ì Ü † ø ª ˆ ö d Ä6¢¼ ,Ö‚4òbÒ@”àBŽ&€Ü2¦PªøF”JÚ^Ĩ¤.´  æ!þ"ä#Ü$(%V%¨&$&Ò'„((ä)j)ô*Â+’,`-2. .Ü/6//ü0Œ11ª242Ä2ü3R3¢44¢5(5¬686È7P88ä9®:x;D;â<|==Œ>\>ü?¤@L@ÌAHA¼AüB¦C6CâD^DØEŠF4FâGbGÌHHhH IIVIœJKKtL LÀM`NNfO@O‚OÜP4PPÊQQzR R¤SnT8T UUfU–UüVbV¤W WŽX$XÌYY\YòZ’[4[Ö\x]]ª^^¤_0_¼`L`Þajb:c cÚdrdèe eÌf\fìg|h h˜i&i´j>jvk kZk¨kölDl’mˆnlooÐp‚q4qÆr^rðsPttÒu”vÖwºxbyyÈz({{°|@|œ}}Z}¶~~n~Î.ò€”6®‚‚d‚胎ƒò„8„°…(… ††Š‡‡|‡ôˆ¤‰‰üŠpŠÈ‹ ‹t‹ÈŒŒxŒØ8Ž"Žú¨^‘„’Ê“ ”L••X•¢•º•Ü– –T–€–¶–ø—^—®˜˜p™™‚šš‚šð›ˆ›èœÀpž$žšŸŸ‚Ÿð „¡¢££p¤|¤”¤æ¥4¥¦¥ö¦`§§¢¨:¨R¨’¨Ê©©l©´ªª°««8«€«ð¬~¬ª­­n­Æ®$®’®Ü¯X¯Ì°J°æ±V±Â² ²Œ³³‚´‚µPµ€¶*¶Ò¶ô·:·|·¾¸*¸¸ø¹p¹æº˜ºü»(»@»|»à¼¼n¼¢½½R½Ì¾¾Š¿¿ŒÀ$À€ÀÈÁvœÃ(ÄÄÆÅBÅÀƸÇ<ÈÈHÉPÉ–Ê&ÊüËXÌÍ2ÍŽκÏòÐþÑ^ÑâÒ†ÓÓ¸Ô*Ô^ÕÕ¾Ö(Ö|Öâ×4רØ&ÙÙÀÚ¾Û"ÛlÛÒÜÜ”ÝVÝvÝÆÞ@ÞŽÞìß¾ßÞàNà°áxâ`â”ã&ã¤ãää(å4å|åÈæNæâçvçÂè&è„é†êtëÚì4ìäí6íÖî îVî¢ï2ïÚðdð¨ñRñ¤òZòàó¦ôpôÂõ&õŠööböšöö÷V÷®÷æøjùùXùœùàú\ú úÔûXû”ûìüŠýýbý¶ýúþLþ¼ÿ8ÿš ŠÜ~*ðÆ”’ö–R  ” ¬ Ö :   ú  r ø " ’ øpøbò”ØVÆ\Ä(Ø@bþX¸$~Ø*š:âpä\Ò@Ú<ŠörÞx l þ!r!ä"P"~# #$T%%Ž&R'd'â(š))’)º*€++è,p-N-ž.J. /2/¾0˜1ž2Æ323Ö4@4 5z6 7º9:F:¤<<><ˆ<Ø=B=”=ö>X> ??¬@$@”AAzBB¬CC¦DvDèEtF>FÄG8GÔH8IIšJ4JÞLŽLàMXMÀN"NœOJOªPrPðQªQîRŠS>SrSòTŽUTUÐVVdVÆW~X*XÈY¤ZFZ”[V[Ê[ú\ž]B]È^l^°^ì_š_Ê`L`ÞaŠbŠc\dFeeˆfTfêg$h&i<ili°iîj>j kkZkÀllm m˜n:nºo€p0pÔqÊrþtFt–u4v v^vÀw*w~wìxxŒxàyyvzzvzì{‚||j}$}ž~~f š€€”*È‚*‚惆„4„ì…L…¸…ð†f‡0‡¢ˆˆXˆü‰ŒŠŠ–‹|ŒŒpŒòÎŽ:Ž Tªòž‘,‘Þ’°“b”f•º–Η4—˜˜Œ™jšn››êžž¬žüŸXŸ , |¡R¢0¢š£p¤¤>¤’¤Ø¥,¥€¥ú¦f¦¢¦ô§§Ü¨V¨¼©T©¤©äªdªÒ«4«`«Ú¬x¬ò­‚­è®.®Â¯\°°Ä±L±Æ²X³³’´,´²µRµø¶´·°¸>¹¹¬º*º¼»¬¼†½@½ò¾z¿ ¿æÀ”ÁdÂ<ÃÄÄÔÅ‚Æ`ÇÇÀÈlɂʂËnÌJÌr̾ÍÍ®ÎTÎzÎòÏzÐÐôѰÑèÒ„Ó4ÓlÓ¶ÔÔ\ÔÔÕBÕŠÕàÖ:Ö|ÖÂ× ×XתØØfØòÙxÙúÚšÛjÛÜÜxÝÝØÞ†ߌà˜ášáÊâ0â„âÎãDãØä(ätäÆäöåÌæ0æšçç¨è"èœéRéúê˜ë&ìì†ìÒí^îî°îÚï’ð&ññÔòfó@óêôÆõxör÷ø¢ùÆú¦ûüPý"ý”þªÿº¾*bÖ0œÄöpÂf†ÆXîŠðT F & V ® î j À & Z 2”ÚHªèXÀ pþ8Šzî„êjìŽHºj„<Ú\ƈ„ö€F  ¢!`!ø"Š#L$@%%Ô&š'n(L)$)î*¾+^, ,Ì-‚.l//ì0Æ1î2’3V44¸5Ì6h789.::;p<–=Ê> >L>Ž>â?$?l?Æ?þ@`@¾@ÖAADA”BB¬CCäD|DþEÎEúF8F°G0GøHH¨IšIÒJ0J‚JàKvLLÖMLNLNæOVOÌPJP°QQnQÔR(R€RøSšSàTzTÞU`UÞV<VþWXW¾XX†XÖYTYöZÂ[[¼\2\°]`]’^&__X_À``ŒabbbZbÄcc dd¬eTe¦fVg gÒh0hòjj„k8llþm¾nbnÔo,p(pÀq˜r@stXuv.vtvôw`xHy$yþzÆ{ {b{Ä|H|Ú}Æ~V \€ö‚¾ƒ¤„d…p†b‡¦‡¾‡Þˆˆn‰&‰öŠØ‹v‹ÈŒŒtŒ®ŒúpŽŽjŽž~æŽà‘D‘à’V’î“z“ü”t”ä•~––R——€—ê˜b˜Ü™Šš’›`›¼œfœÖ`žž²Ÿp @ ’¡v¡è¢ £`£Ò¤v¥T¥Œ¦ ¦Š¦ü§’¨"¨p©"© ª,ªÚ«€¬¬Š­R­ö®T®ò¯n°v±\²V²¤³Ò´pµø··Þ¹º0»*¼X½Œ¾Š¿tÀHÀîÂZÃÆÄ0ÄÖÅ8ŤÆjÆÚÇhÇôÈÂɆÊRÊöËäÌšÍÍB͸ÎÎÞÏ Ï„ÏÂÐ6ѦÑÚÒ*ÒhÒÖÓLÓ¾Ô*ÔŒÕÕŽÖ Öº×nØ*Ù@ÙüÚfÚÚÛVÛÌÜÜÜHܦÜèÝ@ݲÞ:ÞjÞ´ßßßêà`àäá.áŠâ2âÜãXä äÊ儿æ²çBè èÎéHê6êÆëÈëúìbìÞí^í¸íîîPîÆïjð&ð ñ.ñðòDòšòðóHóÀôô„ô¸ôþõTõšöö`öž÷"÷p÷àø&øŠøÜù*ù¶úˆúÄûŽüü¬ý.ý„ýÀþ6þœÿ$ÿrÿÎ.¦~Lþ´`¦p€ròV² 0 ”  z  Ž  F œ > ÆTÀ4ô”NÀd:´ 8ŒD îæz$®|&¼  ´!"`"Ð#Ê$% &H'2(4(Ü)8)´*F*Î+h+æ,h,Ò-¶.d/>/Ê0r1n2|3"44È5T5Œ6n78Z9>::;$;€<‚=,=ì>È?Œ@jA(BPC<D:EZFlG¶I4JÖKæL¸M0N&OšOÊPPzQQnQòR¾S˜T>UUtUÒVfVèWXWúXŠYY®ZPZÆ\B\¢]8^|^®_x_ø``üaÌbtcDcÖdteeøf~fòggFgrgÞh0hÂiiviæjnjÈk8kÚlXlÊmm˜nn¼nÜo.o`p,pdp¸qqxqØr rRrÂs0sds¨t(tXtÆu2u¬vvjvÄw wrx:x”yy¢z"zÌ{H{z||‚|¼}^}º~~¦FÊ€b€Ò"¸‚vƒ*ƒŠƒØ„…h†T†˜†þ‡ˆ†‰‰N‰Øж‹ŽŒHþŽš`¤\‘‘Ä’¤“B“ž”4”¦•\––²—Š˜r™<™ š’››|›´œXœâ>˜žNŸLŸð ì¡J¡â¢X¢Ö£`£¢£è¤0¤\¥6¥Þ¦X¦È§Ž¨"¨Ê©ÚªVªÄ«¤«â¬­2­Ž®2¯~°±²(²Ú³&´´|´Àµ<¶ ·J¸.¹>¹Üº„»ž¼Ø½V¾4¾ð¿øÀÒÁÂÂJÃ6ÄÅÅRÆhÇ(ÇÆÈÌÉöʌ˜ÌlÍÍÖδÏ|ЬÑæÒ¸Ó¨Ô.ÕJÖžØLÙðÛÜbÞ4ÞÞ¨ÞÐß0ßbß´ààhàÈáFáÞâ@âÎã.ãÐä€å.å€åôæ¤çèè&èÄéé¬ê‚ëRìHìöíJí¬î.îžîÞïÐñòò˜ó€óðõõÈöÜørúBú”ûhüpý~ýðþÿN²î,ðL¾``¶Hð¬öž¼ œ ú J j Š  . jÌDÄtÂ4þ°¶ú0è(¬Ì.ˆ ¸ÚNê\®8¨2¾¨ x!P"’#,$¦%¨&Â'¶(.(š)* *~+8+„+Þ,Z,æ-d-¬.$. /2/Ð0r11b22¬45N6$6–77¢8h8Ä8ð9 9r9Þ:8:°;.;˜<<œ<è=j=ø>D>º??|?ì@*@Œ@òA,A B$BfBžBôCJCÄDJDèEvFF~FøGÀHNHÔIjIìJJJÚKlLLˆMM²N2N²OJPP®QQ–R,R¨S.SæT”UVUôVfVÒW@WâXžYˆZZâ[t\f]*]â^r_ _¸`\aaÊb¢c†d†e\f6fºg¢hŒiNj(j¸k¦lŒm0nopp¶qÒrXrÆsŠttÊuèvÔw¢xVxøyÂz®{v|v}L~"8€2ì‚Ѓ¬„|…d†B† ‡¸ˆÄ‰ðŠº‹î|pðpÊ‘‘t‘ì’h’ö“‚”8”~”À•"•†•æ––æ—H—ä˜\™™Èšä›F›ªœVœâ  žFžâŸLŸö ¶¡6¡¢R¢È¢ô£T¤V¥4¥þ¦Ð§N¨8¨˜¨þ©F©„ªªT««š¬ ¬®­"­Ü®:®ê¯’¯¾° ±&±´±þ²H²€³¦´Œ¶·X¸€¹P¹¸º »»T¼¼Î½D½ì¾Ì¿ÚÀ2À~ÁÁbÁðÂÊÃ$üÄzÅ(ÅôÆ.ÆŽÆêÇ4ÇxÇÈÈ$ÈŠÈèÉbɤÉâÊLÊÐË„ËöÌb̪Í ÍrÍÌÎÎrÎòψÏêÐbÐØÑ@ÑäÒRÒàÓ¦ÔTÔüÕXÖ@×פØ`Ù,ÙèÚVÛ8Ü(ÜÐÝÝêÞÌßxà8á`áÆââ|âæãNãˆãÜä.ä˜äðåtåÌæ>æ”æÊçHç¸çüè¬é é„éöêDê¦êôëœì*ì|ìÂíí²îîžï$ï˜ð,ðÌñbòò^òÒóóšô.ô¤ôìõfõðö@öœ÷.÷–øøŠù&ùªúú¼ûûÊüÐý(ý¦þ@þ‚ÿÿ€ÿêªl êp,¬@ |žD¾ > ¾ <   ² z â È’š†,ÂPÒ,ЬJæœ<F ÎÒdÚ4ОV " ˆ!6!ì"¢#R$$¬$Ô%´&:&f&È'^((Ø)|*X+6,,¼-€.@.ø/ 0\1V1ú2d2Ä3–4|5Š6b6Þ7~8X99ò:Ä;Š<€=¾>°?Ü@ÈB8C\DèEÎF¶GžHŠI®JšK®L¸M†O"PPQœRÐSøU<UúVšVôW|XRXˆXÈYYŒZ8Zê\\H\ð]\]ö^0_\_Ð`Pa:bbbðcbdŠeeÜf¼g.g®hi<i¾jÞknl mœnn4oHp4q4qœr@rzssPs¼tHtÒu0u²v‚vÚw0wrwàxRxšxØy\yÀzz®{.{è|:|°}Ê~.~ˆ~ì €D€ð\‚2‚ЃȄ`…T†R‡dˆì‰T‰¤ŠŠº‹ŒhŽD\üpì‘x’’¢“4“ú”Œ•<•ð–¢——v—²˜˜d˜Æ™4™üš\šÔ›@œ:œ€œâ°ž0ž„Ÿ"ŸÔ v è¡b¡Ä¢F¢¢£Z£ª¤¤Œ¤ø¥„¥è¦†§2§Æ¨¸©ªjªâ«’¬V¬æ­º®h®ø¯p°X±&²L³^´r´ºµ<¶&¶„·d·À¸H¹¹n¹¶¹öº¼»r¼<¼Ü½½È¾Œ¿hÀŒÁ†ÁòÂNžÂüÃTØÃÈÄÄBÄ’ÅÅvÅØÆ@ÆšÇÇ~ÇîÈ2ÈzÉÉRÉÄÊ:ʪËËŒËöÌjÌÔÍ0Í`͸ÍöÎÖÏ:ÏšÏâÐDЮÑ"ÑŽÑîÒnÒ¨Ó(Ó¾Ô6ÔˆÔÔÕ,ÕzÕ¸Ö:Öœ××2ׂ×ÜØØNØÀÙ0ÙjÚÚhÚäÛLÛÜ(ÜÝÝ^ݶÞVÞÈß6ß~àà’á$á áöâ&â®ãã˜ääfå åråêæ0æŠæîç@ç¨è8è’èÆé0éîêrëëÆìLìÂííZíèîpî´ï<ïÀð,ñ ñŽòò|ó óˆô2ôšôôõrõìöˆ÷&÷èøbùùnùþú€úÜû<û~ûÒü&ü†üÄý*ýŽþþlþðÿ†²:ÄBà”ÚTÎhFª2xª & š . ˆ ä x 6 ¬ h æ*\þšò†ö€ ´tÆœ0n ¬D®ÆtæR¾"š\  œ!^!ä"€#,#j#î$n%%œ&*&ª''º(V(Ø)0)¦*D*Ä+„+î,\,Î-n-ò.F//¨0H0ð1N1¼262¨3.3‚44l56@6¬7N88 9H9Ò:j;H;Ô<ª=D>>”>Ú?¾@œ@îA„B<BÀCŽD"EE|EôFÄGGàHTII®JJJüKšKØLšM„NLOHP&P´Q¶RxS0SòTNUXUØV–W–WüXÖYlZ@Zü[¢\6\¦\ü]Î^|_8_Ž``øa¸bRc cÂdäeºf¤gŒhBhìi^iÎj0jük–llÒm¾n–o4oºpfqDqðr¬sTtt¦u’vVw"wðx¶y*yÊz’{F{ú|Ð}¤~Dœ€Œ^‚0‚²ƒj„b……¨†^‡tˆ^‰@ŠЦ‹¶Œ’H¾ŽDŽÎHü¸‘l‘æ’Ž“X””œ•,––Η昊™ ™þš¨›v›¾œÆž|ŸBŸ¼ r¡d¢*¢´£ª¤~¥B¦¦²§r§þ©ª<«.«ò¬Ø­œ®”¯Ð°ª±¨²@²ì³à´’µ6µö¶Ê··Ä¸Œ¹xº8º¸»Ö¼´½R¾L¾ð¿ÊÀ¤ÁfÂ0ÂÖÃÄĪÄúÅèƸÇfÈÈÊÊ6Ë^ÌfÌÌÍæΘψÐ(ÐîÑÎÒHÓ(ÔÔþÕÞֺ׮ظÙÞÛÛÊÜüÝÐÞÄàáââÞãäÄåŠæ~çTèé ê<ëëèìöíÖîªïlð€ñvòVòæô\õpöb÷nøJùZù¾úÜûÀüÊý þ–2ZŒldœ† D œ , Z Ê¾Ü¦ÔøÆú$œÞ\îÖ  > þ"\#$$‚%æ&Œ&ü'L'Œ((†)(*x*Þ++Š+ì,N,ž-B-ú.V.æ/D/Ø0t0Ò1B2:2¨323€4n5:66ì77ü8Ä9P:(:ò;l>€?(@@@¤ARAîCCêDàEÐFÚG¨H IÎJ KKöLÖNROŽPšQ–R~ST:U¼V V<V˜VøW8W¢XXŽY YÈZ˜[*[à\n\¬]~^^ä_X_Î`¶aa–aÖb>bðc„cød°eepeøf$f„g gÆh>h¾i†j2kBl:l¢mBm´n$ooŽpdp¸qjrs:t&uvvúxlx¾y(ylzJz¾{{Œ||Š|ª}˜~<t²€€¬.Æ‚¦ƒN„f…Z†\†¸‡€ˆHˆ˜‰hŠ ж‹†Œ(Œ¬6ªŽŽzŽÐzêì‘x‘Ð’N’°“ª””n• ––î—ܘ8™jšv›xœ$œdÒž Ÿ8Ÿ¤  t¡\¡ô¢š£:£þ¤Ú¦¦‚¦â§R§Î¨,¨ˆ¨È©`ªªlªà«$«Ä¬"¬¤­@­¼® ®r®Ô¯&¯Ä°X°ò±È²t³X³ø´Îµš¶Œ·v¸T¹8ºB»¼¼¸½Ü¾B¾¬¿¿R¿ ¿èÀ ÀLÁ ÁМÃxÄJÅ@ÅúƈÆàÇÇ6ÇfÇŽÇþÈRȶÉ2ÉpÉÈÊÊšÊêË8Ë Ì@ÌÐÍfÍæÎ6δÏ0ϸÐ^ÐÔÑzÒdÓÓ¾ÔzÕBÕÊÖžײØØÙjÙÔÚ\Û2ÛÔÜFÜÈÝ2Ý®Þ\ÞàßöàúáÞâ¦ã”ååöænç<èvéêë2ìŠíŠî<ïfðbðÔñ0ñâòTòÆótôôüõ®ö ÷øø˜ù@ù²úˆüüjüúý\ýþþPÿ\ÿôb´®hÞ°ÈðˆÖX¬ú.fÈ"zâF ì R ˜ ô V ² þ 0 œ ê | È 6 À F¸ünÌ0†¾ŽþŽþDÊ:Ð,¦´^þ’(º‚Ôf¾J˜ð²NÀN  –!!†""®#\#Ð$N$Æ%P&&~''˜(@(ô)þ**ü+¢,t--Ü.|/.00æ1’2r3244â5À6V7d8>99à:ø<<¬=*=î?0?Ò@¨BC.D`EEúFRFþGžHhII‚J2JêKÜL:LºLøMŽMþNŠO OàPžQ¦S S>S”T(TÒUjUÀVVbVÞWjX<XêYÖZtZæ[f[Ä\\è]h]ô^Ú_H`vabb¶c$d2e>ffg`h`itjBjúkJk˜kÄl>l‚lêm’nnˆnÊo>ošo²oèppFpnpºqq€qìr$rœräs>sjsÌt8tŒtêuBuŽvvŒvÔwRwöxZxÒy y@yøzzzê{œ| |À}p}ø~F~´<ЀH€ÞXÊ‚fƒ"ƒÜ„”……ކƇL‡èˆŽ‰F‰^‰Œ‰®‰æŠHŠ’Šþ‹*‹‹òŒdŒª\ÞŽ>Žv’ ¼‘P‘¾’p““”(”¼•†–R–ž——¾˜r˜Ì™D™Üš~›D›ÖœÚ|þž¬Ÿ(Ÿ¤ŸÖ  ð¡”¢^£v¤Z¤†¤ø¥`¥Ò¦¦¢§§È¨$¨º©8©Ž©ôªp« «Ö¬¬¬æ­`­Æ®~®ò¯´°j±(²@³³Þ´¬µTµò¶À·J¸ ¸ê¹¶º¦»„¼z½$¾$¿&ÀÀæÁÈÂzÃ\ÄÄôÆÇTÇÒÈPÈ¢ÈúÉÄÊÖË´ÌdÍ\ÍêÏÏXÐ~Ñ8ÒÒ ÓÓlÓæÔ¨ÕnÕÎÖÖ„×פØTÙ(ÙÖÚÊ۾ܠ݆Þßßþàøá²â,â¦ãzä|åÎæNæÀæêçœè éPéèëëÖìÖífîJïðñ†òòêóêôbõNö"ö®÷÷€÷êøXøºùùžúú û(û~ûÄüfüîýtýüþšÿ$ÿÀhì®DÊ\¸Jú¤F b V &  ºˆ Ô>|Òbè`ÞlàŽÒ2ð^@d¾ô–DxÆ  Ì!˜"r#4#ê$Ž$ò%à&d&˜'H((°)2)d)Ø*>*ˆ+2+æ,Ž-0-Ú.//Æ0p0ô1â3@3ˆ4\546*6ð7~88Ô9d:4:ö<8<à=6=z>?h@JA2B BÔCØDÄEhF.FvF¬G2H4IJJJXJ–JðK8KÞLTLÒMPMúNlNÜOˆPP¢Q`QøRÎS˜T~U0VBWVWøXžYBYæZÐ[D\\f]P^Ð_r``6`‚`úa\aübüddðfg2g`gØiifkkölhlºm:m¤n o,pLpâqdqÌr`s˜tšuœvfw2x°yzzž{¶|P}¨~\~Î*x€D€œ:¤‚˜ƒ¢„’…„†t‡”ˆX‰‰ØŠ€‹fŒ–@Žj6Œrª‘’’t’Ü“\“Ò”®•8•ú–Ä—T—Θ^™ršRš¨››ºœ|¶žÈŸj  œ¡n¢Š££`£î¤š¥¥t¥è¦>§ ¨¨îª ªJ«\¬z­h®8®þ°±d²l²Î³\´&µ<¶r·|¸H¹¹ ¹üº¸»”¼‚½T¾P¾¨¿¿XÀ>À‚ÀðÁØÂHˆÀÃúÄJÄØÅPÆÆzÆÖÇ ǬÇþÈþÉnÉÚʺËæ̸ÍÎÎvÏ|ÏÈЄÑÑ–ÒTÒœÓpÓúÔ„ÔÖÕœÕøÖš××`Ø,ØÔÙ¢ÚÚbÛšÜ\ÜÖݤÞlÞÊß6ßšàºá2á¤â.âÒãÂãêä¶åRåÊæ®ç çþè¾é°êRêîë¶ëêì†ìöíPíöîÒð@ññÈò:òÞónôô†õõàöZ÷÷ª÷À÷Ö÷ìøøø.øDøZøpø†øœø²øÈøÞøôù ù ù6ùLùbùxùŽù¤ùºùÖùìúúú.úDúZúpú†úœú²úÈúÞúôû û û6ûLûbûxûŽû¤ûºûÐûæûüüü(ü>üTüjü€ü–ü¬üÂüØüîýýý0ýLýbýxýŽý¤ýºýÐýæýüþþ(þ>þTþjþ€þ–þ¬þÂþØþîÿÿÿ0ÿFÿ\ÿrÿˆÿžÿ´ÿÊÿàÿö "8Ndz¦¼Òèþ*@Vl‚˜®ÄÚð2Hdz¦¼Òèþ*@Vl‚˜®ÄÚð2H^tŠ ¶Òèþ*@Vl‚˜®ÄÚð2H^tŠ ¼Òè0F\rˆž´Êæü(>Tj€–¬ÂØî   0 F \ r ˆ ¤ º Ð æ   . D Z p † œ ² È Þ ô   6 L b x Ž ¤ À Ö ì   . D ` v Œ ¢ ¸ Î ä ú  & < R h ~ ” ª À Ö ì.J`vŒ¢¸Îäú&<Rh~”ªÀÖì.J`|’¨¾Ôê,BXn„š°Æâø$:Pf|’¨¾Ôð2H^tŠ ¶Ìèþ*@Vl‚˜®ÄÚð2H^tŠ ¶Òèþ*@Vl‚˜®ÄÚð"8Ndz¦¼Øî0LbxޤºÐæü(>Tj€–¬ÂØî 6LbxޤºÐì4J`vŒ¨¾Ôê,BXnŠ ¼Òèþ0F\rޤºÐæü(>ZpŒ¢¸Îäú,BXtŠ ¶Ìâø  $ @ V l ‚ ˜ ® Ä Ú ð!!"!8!N!d!z!–!¬!Â!Ø!î""&"<"R"h"~"”"ª"À"Ü"ò###4#J#`#v#Œ#¢#¸#Ô#ê$$$2$H$d$z$$¦$¼$Ò$è%%%0%L%b%x%”%ª%À%Ö%ì&&&.&D&Z&p&†&œ&¸&Î&ä&ú''&'B'X'n'„' '¶'Ì'â'ø(($(:(P(f(|(’(¨(¾(Ô(ð)))2)H)^)t)Š) )¶)Ì)â)ø**$*:*P*f*‚*ž*º*Ð*æ+++.+D+Z+p+†+œ+²+Î+ê,,,2,H,^,t,Š, ,¶,Ì,â,ø--$-:-P-f-|-’-¨-¾-Ô-ê...,.B.X.n.„.š.°.Æ.Ü.ò///4/J/`/v/Œ/¢/¸/Î/ä/ú00&0<0R0h0~0”0ª0À0Ö0ì111.1D1Z1p1†1œ1²1È1Þ1ô2 2 262L2b2x2Ž2¤2º2Ð2æ2ü33(3>3T3j3€3–3¬3Â3Ø3î44404F4\4r4ˆ4ž4´4Ê4à4ö5 5(5>5T5j5€5–5¬5Â5Ø5î66606F6\6r6ˆ6ž6´6Ê6à6ö7 7"787N7d7z77¦7¼7Ò7è7þ8808F8\8r8ˆ8ž8´8Ð8æ8ü99(9>9T9j9€9–9¬9Â9Ø9î:: :6:L:b:x:”:ª:À:Ü:ò;;;4;J;`;v;Œ;¢;¾;Ô;ê<<<,>&><>R>h>~>”>°>Æ>Ü>ò???4?J?`?v?Œ?¢?¸?Î?ä?ú@@&@<@R@h@~@”@ª@À@Ö@ìAAA.ADAZApA†AœA²AÈAÞAôB B B6BLBbBxBŽB¤BºBÐBæBüCC(C>CTCjC€C–C¬CÈCÞCôD D D6DLDbD~D”DªDÀDÖDìEEE.EDEZEpE†EœE²EÈEÞEôF F F6FLFbFxFŽF¤FºFÐFæFüGG(G>GTGjG€G–G¬GÂGØGîHHH0HFH\HrHˆHžH´HÊHàHöI I"I>ITIjI€I–I¬IÂIØIôJ J J6JLJbJxJ”JªJÀJÖJìKKK.KDKZKpK†KœK²KÈKÞKôL L L6LLLbLxLŽL¤LºLÐLìMMM.MDMZMpM†MœM²MÈMÞMúNN&N<NRNhN~N”NªNÀNÖNìOOO.ODOZOpO†OœO²OÈOäOúPP&PBPXPnP„PšP°PÆPÜPòQQQ4QPQlQ‚Q˜Q®QÄQÚQðRRR2RHR^RtRŠR R¶RÌRâRøSS*S@SVSlS‚S˜S®SÄSÚSðTTT2TNTdTzTT¦T¼TØTîUUU0UFUbUxUŽU¤UºUÐUæUüVV.VDVZVpV†VœV²VÈVÞVôW W W6WLWbWxWŽW¤WºWÐWæWüXX(XDXZXpX†XœX²XÈXÞXôY Y Y6YLYhY~YšY°YÆYÜYòZZZ:ZPZfZ|Z’Z¨Z¾ZÔZð[[[2[H[d[z[[¦[¼[Ò[è[þ\\0\F\\\r\ˆ\ž\´\Ê\à\ö] ]"]8]T]j]€]–]¬]È]Þ]ô^^&^B^X^t^Š^¦^¼^Ò^è^þ__*_F_\_r_ˆ_ž_´_Ð_æ```.`D```v`Œ`¢`¸`Î`ä`úaa&a<aRana„aša°aÆaÜaòbbb4bJb`bvbŒb¨b¾bÔbêccc,cBcXcnc„cšc°cÆcÜcòddd4dJd`dvdŒd¢d¸dÎdädúee&e<eRehe~e”eªeÀeÖeìfff.fDfZfvfŒf¢f¸fÎfäfúgg,gBg^gzgg¦gÂgägúhh&hBhXhnh„hšh°hÆhÜhòiii4iJi`ivi’i¨i¾iÔiðj j(j>jTjjj€j–j¬jÈjÞjôk k k<kRkhk~kšk°kÆkÜkòlll4lJl`lvlŒl¢l¾lÚlðmmm2mNmdmzmm¦m¼mÒmèmþnn*n@nVnln‚n˜n®nÄnÚnöo o"o8oNodozoo¦oÂoÞoôpp&p<pXpnp„pšp°pÌpâpøqq*q@qVqlq‚q˜q®qÄqÚqðrrr2rNrdrzr–r²rÈrÞrôs s&sBs^stsŠs s¶sÌsâsøtt$t:tPtft‚t˜t´tÐtætüuu(uDuZupu†u¢u¸uÎuäuúvv&v<vRvhv~v”vªvÀvÖvìwww.wDwZwpw†wœw²wÈwÞwôxx&x<xRxhx~x”xªxÀxÖxìyyy.yDyZypy†yœy²yÈyÞyôz z z6zLzbzxzŽz¤zºzÐzæzü{{({>{T{j{€{–{¬{Â{Ø{î|||0|F|\|r|ˆ|ž|´|Ê|à|ö} }"}8}N}j}€}–}¬}Â}Ø}î~~~0~F~\~r~ˆ~ž~´~Ð~æ~ü(>Tj€–¬ÂØô€ €&€<€R€h€~€”€ª€À€Ü€ò4J`v’¨¾Ôê‚‚‚,‚B‚X‚n‚„‚š‚°‚Æ‚â‚øƒƒ$ƒ:ƒVƒlƒ‚ƒžƒ´ƒÊƒàƒö„ „"„8„N„d„z„„¦„¼„Ò„è„þ……0…F…\…r…ˆ…ž…´…Ê…à…ö† †"†8†N†d†z††¦†¼†Ò†è†þ‡‡*‡@‡V‡l‡‚‡˜‡®‡Ä‡Ú‡ðˆˆˆ2ˆHˆ^ˆtˆˆ¦ˆ¼ˆÒˆèˆþ‰‰0‰F‰\‰r‰ˆ‰ž‰º‰Ö‰ìŠŠŠ4ŠJŠfŠ|Š’ЍоŠÔŠê‹‹‹,‹B‹X‹n‹„‹š‹°‹Ì‹è‹þŒŒ0ŒLŒbŒxŒŽŒªŒÀŒÖŒì:Pf|˜®ÄÚðŽŽŽ2ŽHŽdŽzŽަ޼ŽÒŽèŽþ0LbxޤºÐæü(>Tj€–²ÈÞú‘‘&‘<‘X‘n‘„‘š‘°‘Æ‘Ü‘ò’’$’@’\’r’ˆ’ž’´’Ê’à’ö“ “"“>“T“p“Œ“¨“¾“Ô“ê”””2”H”d”€”œ”²”Ȕޔú••&•<•R•h•~•”•ª•À•Ö•ì–––.–D–Z–p–†–¢–¸–Ô–ê———2—H—^—t——¦—¼—Ò—è—þ˜˜*˜@˜V˜l˜‚˜˜˜®˜Ä˜Ú˜ð™™™2™H™^™t™Š™ ™¶™Ì™â™øšš$š:šPšfš|š˜š®šÄšÚšð›››2›H›^›t›Š› ›¶›Ì›â›øœœ$œ:œVœlœ‚œ˜œ®œÄœÚœð2H^tŠ ¶Ìâøžž$ž:žPžfž|ž’ž¨ž¾žÔžêŸŸŸ,ŸBŸXŸnŸ„ŸšŸ°ŸÆŸÜŸò   4 J ` v Œ ¢ ¸ Î ä ú¡¡&¡<¡R¡h¡~¡”¡ª¡À¡Ö¡ì¢¢¢.¢D¢Z¢p¢†¢œ¢²¢È¢Þ¢ô£ £ £6£L£b£x£”£ª£À£Ö£ì¤¤¤.¤D¤Z¤p¤†¤œ¤²¤È¤Þ¤ô¥ ¥ ¥6¥L¥b¥~¥”¥ª¥À¥Ö¥ì¦¦¦.¦D¦Z¦p¦†¦œ¦²¦È¦Þ¦ô§ § §6§L§b§x§Ž§¤§º§Ð§æ§ü¨¨(¨D¨Z¨p¨†¨œ¨²¨È¨Þ¨ô© © ©6©R©n©„©š©°©Æ©â©øªª*ª@ªVªlªˆªžª´ªÊªàªö« «"«8«N«d«z««¦«¼«Ò«è«þ¬¬*¬@¬V¬l¬‚¬˜¬®¬Ä¬Ú¬ö­ ­"­8­N­d­z­­¦­Â­Ø­î®®®0®F®\®r®ˆ®ž®´®Ð®æ®ü¯¯(¯>¯T¯j¯€¯–¯¬¯Â¯Ø¯î°°°0°L°b°x°Ž°¤°º°Ð°æ°ü±±(±>±T±j±€±œ±¸±Î±ä±ú²²&²<²R²h²~²”²°²Æ²Ü²ò³³³:³P³f³|³’³¨³¾³Ô³ê´´´2´H´^´z´´¦´¼´Ò´è´þµµ0µFµ\µrµˆµžµ´µÊµàµö¶¶(¶>¶Z¶v¶Œ¶¢¶¸¶Î¶ä¶ú··&·<·R·h·~·”·°·Ì·â·ø¸¸$¸:¸P¸f¸|¸’¸¨¸¾¸Ô¸ê¹¹¹,¹B¹X¹n¹„¹š¹°¹Ì¹â¹øºº$º@ºVºrºˆºžº´ºÊºàºö» »"»8»N»d»z»»¦»¼»Ò»è»þ¼¼*¼@¼V¼l¼‚¼˜¼®¼Ê¼à¼ö½ ½"½8½N½d½z½½¦½¼½Ø½î¾¾¾0¾F¾\¾r¾ˆ¾ž¾´¾Ê¾à¾ö¿ ¿"¿8¿N¿d¿z¿¿¦¿¼¿Ò¿è¿þÀÀ*À@ÀVÀlÀ‚À˜À®ÀÄÀÚÀðÁÁÁ2ÁNÁdÁzÁÁ¦Á¼ÁÒÁîÂÂÂ0ÂFÂ\ÂrÂŽ¤ºÂÐÂìÃÃÃ4ÃJÃ`ÃvÃŒâøÃÎÃäÃúÄÄ,ÄBÄXÄnĄĚİÄÆÄÜÄòÅÅÅ4ÅJÅ`ÅvŌŢŸÅÎÅäÅúÆÆ&Æ<ÆRÆhÆ~Æ”ƪÆÀÆÖÆìÇÇÇ.ÇDÇZÇpdžÇœDzÇÈÇäÇúÈÈ&È<ÈRÈhÈ~È”ȪÈÀÈÖÈìÉÉÉ.ÉDÉZÉpɆÉœɲÉÈÉÞÉôÊ Ê Ê6ÊLÊbÊ~Ê”ʪÊÀÊÖÊìËËË.ËDËZËpˆËœ˸ËÎËêÌÌ"Ì>ÌZÌp̆Ìœ̲ÌÈÌÞÌôÍ Í Í6ÍLÍbÍxÍŽͤͺÍÐÍæÍüÎÎ(Î>ÎTÎj΀ΖάÎÂÎØÎîÏÏÏ0ÏFÏ\ÏrψϞϴÏÊÏæÏüÐÐ(Ð>ÐTÐjЀЖЬÐÂÐØÐôÑÑ&Ñ<ÑRÑhÑ~Ñ”ѪÑÀÑÖÑìÒÒÒ.ÒDÒZÒp҆ҜҲÒÈÒÞÒôÓ Ó Ó6ÓLÓbÓxÓŽÓ¤ÓºÓÐÓìÔÔÔ4ÔJÔ`ÔvԌԢԸÔÎÔäÔúÕÕ&Õ<ÕRÕhÕ~Õ”ÕªÕÀÕÖÕìÖÖÖ.ÖDÖZÖpֆֲ֜ÖÈÖÞÖô× × ×6×L×b×x׎פ׺×Ð׿×üØØ(Ø>ØTØj؀ؖجØÂØØØîÙÙÙ0ÙFÙ\ÙrÙˆÙžÙ´ÙÊÙàÙöÚ Ú"Ú8ÚTÚjÚ€Ú–Ú¬ÚÂÚØÚîÛ Û Û6ÛLÛhÛ~Û”ÛªÛÀÛÖÛìÜÜÜ4ÜJÜ`Ü|Ü’ܨܾÜÔÜêÝÝÝ,ÝBÝXÝn݄ݚݰÝÆÝÜÝòÞÞÞ4ÞJÞfÞ|Þ’Þ¨Þ¾ÞÔÞêßßß8ßNßdßzß–߬ßÂßÞßôà à à6àRàhà„àšà°àÆàÜàòááá4áJá`áváŒá¢á¸áÎáäáúââ&âBâXânâ„âšâ°âÌâââøãã$ã:ãPãfã‚ã˜ã®ãÊãæãüää.äDäZäpä†äœä²äÈäÞäôå å å6åLåbåxåŽå¤åºåÐåæåüææ(æ>æTæjæ€æ–æ¬æÂæØæîççç0çFç\çrçˆçžç´çÊçàçöè è"è8èNèdèzèè¬èÂèØèôé é é6éLébé~é”éªéÀéÖéìêêê4êJê`êvêŒê¢ê¸êÎêäêúëë&ë<ëRëhë~ë”ëªëÀëÖëìììì.ìDìZìpì†ìœì²ìÈìÞìôí í í6íLíbíxíŽí¤íºíÐíæíüîî(î>îTîjî€î–î¬îÂîØîîïïï0ïFï\ïrïˆïžï´ïÊïàïöð ð"ð8ðNðdðzðð¦ð¼ðÒðèðþññ*ñ@ñVñlñ‚ñ˜ñ®ñÄñÚñöò ò"ò8òNòdòzòò¦ò¼òÒòèòþóó0óFó\óróˆóžó´óÊóàóöô ô"ô8ôNôdôzôô¦ô¼ôÒôèôþõõ*õ@õVõlõ‚õ˜õ®õÄõÚõðööö2öHö^ötöŠö ö¶öÌöâöø÷÷$÷:÷P÷f÷|÷’÷¨÷¾÷Ô÷êøøø,øBøXønø„øšø°øÆøâøøùù$ù:ùPùfù|ù’ù¨ù¾ùÔùêúúú,úBúXúnúŠú¬úÎúðûûû2ûHû^ûtûŠû û¶ûÌûâûøüü$ü:üVülü‚ü˜ü®üÄüÚüðýýý2ýHý^ýtýŠý ý¶ýÌýâýøþþ$þ:þPþfþ|þ’þ¨þ¾þÔþêÿÿÿ2ÿHÿ^ÿtÿŠÿ ÿ¶ÿÌÿèÿþ  * F b x Ž ª À Ü ø  * @ V l ‚ ˜ ® Ä Ú ð   2 H ^ t Š   ¶ Ì â ø  $ : P f | ’ ¨ ¾ Ú ð   2 H ^ t Š   ¶ Ì â ø  $ : P f | ’ ¨ ¾ Ô ê   , B ^ t Š   ¶ Ì â ø  $ : P f | ˜ ® Ä Ú ð   2 H ^ z  ¦ ¼ Ò è þ  * @ V l ‚ ˜ ® Ä Ú ð   2 H ^ t Š   ¶ Ì â ø  $ : P f ‚ ˜ ® Ä Ú ö " 8 N d z ¦ ¼ Ò è þ  * @ V l ˆ ž ´ Ð æ ü  ( > T j € – ¬  Ø î   0 F \ r ˆ ž ´ Ê à ö  " 8 T j € – ¬ È Þ ô   6 R h ~ ” ª À Ö ì   . D Z p † œ ² È Þ ô   6 L b ~ ” ª Æ Ü ø  $ : P f | ˜ ® Ä Ú ö  " 8 N d € – ¬  Ø ô  & < R h ~ ” ª À Ö ì   4 J f | ’ ¨ Ä Ú ð   2 H ^ t Š   ¶ Ì â ø  $ : P f | ’ ® Ä Ú ð   8 N d z  ¦ ¼ Ø î   0 F \ r ˆ ž ´ Ê à ö  ( > T j € – ¬  Ø î   0 F \ r ˆ ž ´ Ê à ü  ( > Z p † œ ² Î ä ú  & < R h ~ ” ª À Ö ì   . D Z p † œ ² È ä ! ! !2 !N !d !z ! !¬ ! !Ø !ô " " "6 "L "b "x "Ž "¤ "º "Ð "æ "ü # #4 #P #f #| #’ #´ #Ê #æ #ü $ $( $> $T $j $€ $– $¬ $ $Þ $ô % %& %< %R %n %„ %š %° %Æ %Ü %ò & &$ &@ &V &r &Ž &¤ &º &Ð &ì ' ' '. 'D 'Z 'p '† 'œ '² 'È 'ä 'ú ( (& (< (R (h (~ (” (ª (À (Ö (ì ) ) )4 )J )` )v )Œ )¢ )¸ )Î )ä )ú * *& *< *R *h *~ *” *ª *À *Ö *ì + + +. +D +Z +p +† +œ +² +È +Þ +ô , , ,6 ,L ,b ,x ,Ž ,¤ ,º ,Ð ,æ ,ü - -( -> -T -j -€ -– -¬ - -Ø -î . . .0 .F .\ .r .ˆ .ž .´ .Ê .à .ö / /" /8 /N /d /z / /¦ /¼ /Ò /è /þ 0 0* 0@ 0V 0l 0‚ 0˜ 0® 0Ä 0Ú 0ð 1 1 12 1H 1^ 1t 1Š 1  1¶ 1Ì 1â 1ø 2 2$ 2@ 2V 2l 2‚ 2˜ 2® 2Ä 2Ú 2ð 3 3" 38 3N 3d 3z 3 3¦ 3¼ 3Ò 3è 3þ 4 4* 4@ 4V 4l 4‚ 4˜ 4® 4Ä 4Ú 4ð 5 5 52 5H 5^ 5t 5Š 5  5¶ 5Ì 5â 5ø 6 6$ 6: 6P 6l 6‚ 6˜ 6® 6Ä 6Ú 6ð 7 7 72 7H 7d 7z 7 7¦ 7¼ 7Ò 7è 7þ 8 8* 8@ 8V 8l 8‚ 8˜ 8® 8Ä 8Ú 8ð 9 9 92 9N 9d 9z 9 9¦ 9¼ 9Ò 9è 9þ : :* :@ :V :l :‚ :˜ :® :Ä :Ú :ð ; ;" ;8 ;N ;d ;z ; ;¦ ;¼ ;Ò ;è ;þ < <* <@ <\  > >0 >F >\ >r >ˆ >ž >´ >Ê >à >ö ? ?" ?8 ?N ?d ?z ? ?¦ ?¼ ?Ò ?è ?þ @ @0 @F @\ @r @ˆ @ž @´ @Ê @à @ö A A" A8 AN Ad Az A A¦ A¼ AÒ Aè Aþ B B* B@ B\ Br Bˆ Bž B´ BÊ Bà Bö C C" C8 CN Cd C€ C– C¬ C CØ Cî D D D0 DF D\ Dr Dˆ Dž D´ DÊ Dà Dö E E" E8 ET Ej E€ E– E¬ E EØ Eî F F F6 FL Fb Fx FŽ F¤ Fº FÐ Fæ Fü G G4 GJ G` Gv GŒ G¨ G¾ GÔ Gê H H H, HB HX Hn H„ Hš H° HÆ HÜ Hò I I I4 IJ I` Iv IŒ I¢ I¾ IÔ Iê J J J, JB JX Jn J„ Jš J° JÆ JÜ Jò K K K: KP Kf K‚ K˜ K® KÄ Kà Kö L L( L> LT Lj L† Lœ L² LÈ LÞ Lô M M& MB MX Mn M„ Mš M° MÆ MÜ Mò N N N4 NJ N` Nv NŒ N¢ N¸ NÎ Nä Nú O O& OB OX Ot OŠ O  O¶ OÒ Oè Oþ P P* P@ Pb Px PŽ P¤ Pº PÐ Pæ Pü Q Q( Q> QT Qj Q€ Qœ Q² QÈ QÞ Qô R R& R< RR Rh R„ R  R¶ RÌ Râ Rø S S$ S: SP Sf S| S’ S¨ S¾ SÔ Sê T T T2 TH T^ Tt T T¦ T¼ TØ Tî U U U6 UL Ub Ux UŽ U¤ Uº UÐ Uæ Uü V V( VD VZ Vp VŒ V¢ V¸ VÔ Vð W W W2 WH Wd Wz W– W² WÈ WÞ Wô X X X6 XL Xb X~ X” Xª XÆ XÜ Xò Y Y Y4 YJ Y` Yv Y’ Y¨ YÄ YÚ Yö Z Z" Z8 ZT Zj Z† Zœ Z² ZÈ ZÞ Zô [ [ [6 [L [b [~ [” [ª [À [Ü [ò \ \ \4 \J \` \v \Œ \¢ \¾ \Ô \ê ] ] ], ]B ]X ]n ]Š ]  ]¶ ]Ò ]è ]þ ^ ^0 ^F ^\ ^r ^ˆ ^¤ ^º ^Ð ^æ ^ü _ _( _D _Z _p _† _¢ _¾ _Ô _ê ` ` `, `B `X `n `„ `š `° `Æ `Ü `ò a a a4 aJ a` a| a’ a¨ aÄ aÚ að b b b2 bH b^ bt bŠ b  b¶ bÌ bâ bø c c$ c: cP cf c| c’ c¨ c¾ cÔ cê d d d, dB dX dn d„ dš d° dÆ dÜ dò e e e4 eJ e` ev eŒ e¢ e¸ eÎ eä eú f f& f< fR fh f~ f” fª fÀ fÜ fò g g g4 gJ g` gv gŒ g¢ g¸ gÎ gä h h h, hB hX hn h„ h  h¶ hÌ hè hþ i i* i@ iV ir iˆ iž i´ iÊ ià iö j j" j8 jN jd jz j j¦ j¼ jÒ jè jþ k k0 kL kb k~ kš k¶ kÒ kè kþ l l0 lL lh l„ l  l¼ lØ lô m m& mB m^ mz m m¦ m¼ mÒ mè mþ n n* n@ nV nl n‚ n˜ n® nÄ nÚ nð o o o2 oH o^ ot oŠ o¦ o¼ oÒ oè oþ p p* p@ pV pl p‚ p˜ p® pÄ pÚ pð q q q2 qH q^ qt qŠ q  q¶ qÌ qè qþ r r* r@ r\ rr rˆ rž r´ rÊ rà rö s s" s> sT sj s€ s– s¬ s sØ sî t t t0 tF t\ tr tˆ tž t´ tÊ tà tö u u" u8 uN ud uz u u¦ u¼ uÒ uè uþ v v* v@ vV vl v‚ v˜ v´ vÊ và vö w w" w8 wN wd wz w w¦ w¼ wØ wî x x x0 xL xb xx xŽ x¤ xº xÐ xæ xü y y( y> yT yj y€ y– y¬ y yØ yî z z z0 zF z\ zr zˆ zž z´ zÊ zà zö { {" {8 {N {d {z { {¦ {¼ {Ø {î | | |0 |F |\ |r |ˆ |ž |´ |Ê |à |ü } }( }> }T }j }€ }– }¬ } }Ø }î ~ ~ ~0 ~F ~\ ~r ~ˆ ~ž ~´ ~Ê ~à ~ü  ( > T j € – ¬  Ø î € € €0 €F €\ €r €ˆ €ž €´ €Ê €à €ö  ( > T j € – ¬ Â Ø î ‚ ‚ ‚0 ‚F ‚\ ‚r ‚ˆ ‚ž ‚´ ‚Ê ‚à ‚ö ƒ ƒ" ƒ8 ƒN ƒd ƒz ƒ ƒ¦ ƒ¼ ƒÒ ƒè ƒþ „ „* „@ „V „l „‚ „˜ „® „Ä „Ú „ð … … …2 …H …^ …t …Š …  …¶ …Ì …â …ø † †$ †: †V †l †‚ †˜ †® †Ä †Ú †ð ‡ ‡ ‡2 ‡H ‡^ ‡t ‡Š ‡  ‡¶ ‡Ì ‡â ‡ø ˆ ˆ$ ˆ: ˆP ˆf ˆ| ˆ’ ˆ¨ ˆ¾ ˆÔ ˆê ‰ ‰ ‰, ‰B ‰X ‰n ‰„ ‰š ‰° ‰Æ ‰Ü ‰ò Š Š Š4 ŠJ Š` Šv ŠŒ Š¢ Џ ŠÎ Šä Šú ‹ ‹& ‹< ‹R ‹h ‹~ ‹” ‹ª ‹À ‹Ö ‹ì Œ Œ Œ. ŒD ŒZ Œv ŒŒ Œ¢ Œ¸ ŒÔ Œð   2 H ^ t Š   ¶ Ì â ø Ž Ž$ Ž: ŽP Žl Žˆ Žž Ž´ ŽÊ Žà Žö " 8 N d z ¦ ¼ Ò è þ  * @ \ r ˆ ž ´ Ê à ö ‘ ‘" ‘8 ‘N ‘d ‘€ ‘– ‘¬ ‘È ‘Þ ‘ô ’ ’ ’6 ’L ’b ’~ ’” ’ª ’À ’Ö ’ì “ “ “. “D “Z “p “† “œ “² “È “ä “ú ” ”& ”< ”R ”h ”~ ”” ”° ”Æ ”Ü ”ò • • •4 •J •` •v •Œ •¢ •¸ •Ô •ê – – –, –B –X –n –„ –š –° –Æ –Ü –ò — — —4 —J —` —v —Œ —¨ —¾ —Ô —ê ˜ ˜ ˜, ˜B ˜X ˜n ˜„ ˜š ˜° ˜Æ ˜Ü ˜ò ™ ™ ™: ™P ™f ™| ™˜ ™® ™Ê ™à ™ö š š( š> šT šp š† šœ š² šÈ šÞ šú › ›, ›B ›X ›n ›„ ›š ›° ›Ì ›â ›þ œ œ* œ@ œV œl œ‚ œ˜ œ® œÄ œÚ œð  " 8 N j € – ¬ Â Ø î ž ž ž0 žF ž\ žr žˆ žž ž´ žÊ žà žö Ÿ Ÿ" Ÿ8 ŸN Ÿd Ÿz Ÿ Ÿ¦ Ÿ¼ ŸÒ Ÿè Ÿþ    *  F  \  r  ˆ  ž  º  Ð  æ  ü ¡ ¡( ¡> ¡T ¡j ¡€ ¡– ¡¬ ¡Â ¡Þ ¡ú ¢ ¢& ¢< ¢R ¢h ¢~ ¢” ¢ª ¢À ¢Ö ¢ì £ £ £. £D £Z £p £† £œ £¸ £Î £ä £ú ¤ ¤& ¤< ¤R ¤n ¤„ ¤š ¤° ¤Ì ¤â ¤ø ¥ ¥$ ¥@ ¥V ¥r ¥ˆ ¥ž ¥º ¥Ð ¥æ ¥ü ¦ ¦. ¦D ¦` ¦v ¦Œ ¦¢ ¦¸ ¦Î ¦ä ¦ú § §& §< §X §n §„ §š §° §Æ §Ü §ò ¨ ¨ ¨4 ¨P ¨l ¨ˆ ¨ž ¨º ¨Ð ¨æ ¨ü © ©( ©> ©T ©j ©€ ©– ©² ©È ©Þ ©ô ª ª ª6 ªL ªb ªx ªŽ ª¤ ªº ªÖ ªì « « «. «D «Z «p «† «œ «² «È «Þ «ô ¬ ¬& ¬B ¬X ¬n ¬„ ¬š ¬° ¬Æ ¬Ü ¬ò ­ ­ ­4 ­J ­` ­v ­Œ ­¢ ­¸ ­Î ­ä ­ú ® ®& ®< ®R ®h ®~ ®” ®° ®Ì ®è ®þ ¯ ¯* ¯@ ¯V ¯l ¯‚ ¯˜ ¯® ¯Ä ¯Ú ¯ð ° ° °2 °H °d °z ° °¦ °¼ °Ø °î ± ± ±0 ±F ±\ ±r ±ˆ ±ž ±´ ±Ê ±à ±ö ² ²( ²> ²Z ²p ²† ²œ ²¸ ²Î ²ä ³ ³ ³, ³H ³d ³z ³ ³¦ ³¼ ³Ò ³è ´ ´ ´0 ´F ´\ ´r ´ˆ ´ž ´º ´Ð ´æ ´ü µ µ( µ> µZ µp µ† µœ µ¸ µÎ µä µú ¶ ¶, ¶B ¶X ¶n ¶„ ¶š ¶° ¶Æ ¶Ü ¶ò · · ·4 ·J ·f ·| ·’ ·¨ ·¾ ·Ú ·ð ¸ ¸ ¸2 ¸H ¸^ ¸t ¸Š ¸¦ ¸¼ ¸Ò ¸è ¸þ ¹ ¹0 ¹F ¹\ ¹x ¹Ž ¹¤ ¹º ¹Ð ¹ì º º º4 ºJ º` ºv º’ º¨ º¾ ºÔ ºê » » », »B »X »n »„ »  »¶ »Ì »â »ø ¼ ¼$ ¼: ¼P ¼f ¼| ¼’ ¼¨ ¼Ä ¼à ¼ö ½ ½( ½> ½T ½j ½€ ½œ ½¸ ½Î ½ä ½ú ¾ ¾& ¾< ¾R ¾h ¾~ ¾š ¾° ¾Ì ¾è ¾þ ¿ ¿0 ¿F ¿\ ¿r ¿ˆ ¿ž ¿´ ¿Ê ¿æ ¿ü À À. ÀD À` Àv ÀŒ À¨ À¾ ÀÔ Àê Á Á Á, ÁB ÁX Án Á„ Áš Á° ÁÆ ÁÜ Áò  Â* Â@ ÂV Âl ˆ ž ´ ÂÊ Âà Âü à Ã. ÃD Ã` Ãv ÃŒ â þ ÃÔ Ãê Ä Ä Ä2 ÄH Äd Äz Ä– Ĭ ÄÂ ÄØ Äî Å Å Å0 ÅF Å\ År ň Åž Å´ ÅÊ Åà Åö Æ Æ" Æ8 ÆN Æd Æz Æ Æ¦ Ƽ ÆÒ Æè Æþ Ç Ç* ÇF Ç\ Çr Lj Çž Ç´ ÇÊ Çà Çö È È" È8 ÈN Èd Èz È È¦ ȼ ÈÒ Èè Èþ É É* É@ ÉV Él É‚ ɘ É® ÉÄ ÉÚ Éð Ê Ê Ê2 ÊH Ê^ Êt ÊŠ Ê  ʶ ÊÌ Êâ Êø Ë Ë$ Ë: ËP Ëf Ë| Ë’ ˨ ˾ ËÔ Ëê Ì Ì Ì, ÌB ÌX Ìn Ì„ Ìš ̰ ÌÆ ÌÜ Ìò Í Í Í4 ÍJ Íf Í| Í’ ͨ ; ÍÔ Íê Î Î Î, ÎB ÎX În ΄ Κ ΰ ÎÆ ÎÜ Îò Ï Ï Ï4 ÏJ Ï` Ïv ÏŒ Ï¢ ϸ ÏÎ Ïê Ð Ð Ð, ÐB ÐX Ðn Є К а ÐÆ ÐÜ Ðò Ñ Ñ Ñ4 ÑJ Ñ` Ñv ÑŒ Ñ¢ Ѹ ÑÎ Ñä Ñú Ò Ò2 ÒH Ò^ Òt ÒŠ Ò  Ò¶ ÒÌ Òâ Òø Ó Ó$ Ó: ÓP Óf Ó| Ó’ Ó¨ Ó¾ ÓÔ Óê Ô Ô Ô, ÔB ÔX Ôn Ô„ Ôš Ô° ÔÆ ÔÜ Ôò Õ Õ Õ4 ÕJ Õf Õ| Õ’ Õ¨ Õ¾ ÕÔ Õê Ö Ö Ö, ÖB ÖX Ön Ö„ Öš Ö° ÖÆ ÖÜ Öò × × ×4 ×J ×` ×v ׌ ×¢ ׸ ×Î ×ä ×ú Ø Ø& Ø< ØR Øh Ø~ Ø” ت ØÀ ØÖ Øì Ù Ù Ù. ÙD ÙZ Ùp Ù† Ù¢ Ù¸ ÙÎ Ùä Ùú Ú Ú& Ú< ÚR Úh Ú~ Ú” Úª ÚÀ ÚÖ Úì Û Û Û. ÛD ÛZ Ûp Û† Ûœ Û² ÛÈ ÛÞ Ûô Ü Ü Ü6 ÜL Üb Üx ÜŽ ܤ ܺ ÜÐ Üæ Üü Ý Ý( Ý> ÝT Ýj Ý€ Ý– ݬ ÝÂ ÝØ Ýî Þ Þ Þ0 ÞF Þb Þ~ Þ” Þª ÞÀ ÞÖ Þì ß ß ß. ßD ßZ ßp ߆ ßœ ß² ßÈ ßÞ ßô à à à6 àL àb àx àŽ à¤ àº àÐ àæ àü á á( á> áT áj ဠᖠᬠáÂ áØ áî â â â0 âF â\ âr ∠➠⺠âÐ âæ âü ã ã. ãJ ã` ã| 㒠㨠㾠ãÔ ãê ä ä ä, äB äX än ä„ äš ä° äÆ äÜ äò å å å4 åP ål å‚ å˜ å® åÄ åÚ åð æ æ æ2 æH æ^ æt æŠ æ  æ¶ æÌ æâ æø ç ç$ ç: çP çf ç| ç˜ ç® çÄ çÚ çö è è( è> èT èj è€ è– è¬ èÂ èØ èî é é é6 éL éb éx éŽ é¤ éº éÐ éæ ê ê ê. êD êZ êp ê† êœ ê² êÈ êÞ êô ë ë ë6 ëL ëb ëx ëŽ ë¤ ëº ëÐ ëæ ì ì ì. ìD ì` ìv ìŒ ì¨ ì¾ ìÚ ìð í í í8 íN íd íz í– í¬ íÂ íØ íî î î î0 îF îb îx îŽ î¤ îº îÐ îæ ï ï ï4 ïJ ï` ïv ïŒ ï¨ ï¾ ïÔ ïê ð ð ð, ðB ðX ðn ð„ ð  ð¶ ðÌ ðâ ðþ ñ ñ0 ñF ñ\ ñr ñˆ ñž ñ´ ñÊ ñà ñü ò ò( òD òZ òp ò† òœ ò¸ òÎ òä òú ó ó2 óN ój ó† óœ ó² óÈ óä óú ô ô& ô< ôR ôh ô~ ô” ôª ôÀ ôÖ ôì õ õ õ. õD õZ õv õŒ õ¢ õ¸ õÎ õä õú ö ö& ö< öR öh ö~ ö” öª öÀ öÖ öì ÷ ÷ ÷. ÷J ÷` ÷| ÷’ ÷¨ ÷¾ ÷Ô ÷ê ø ø ø2 øH ø^ øt øŠ ø  ø¶ øÒ øè øþ ù ù* ù@ ùV ùr ùˆ ùž ù´ ùÊ ùà ùö ú ú" ú8 úN úd úz ú ú¦ ú¼ úÒ úè úþ û û* û@ ûV ûl û‚ ûž û´ ûÊ ûà ûö ü ü" ü8 üN üj ü€ ü– ü¬ üÈ üÞ üô ý ý ý6 ýL ýh ý~ ý” ýª ýÀ ýÜ ýò þ þ$ þ: þP þr þˆ þ¤ þº þÐ þæ þü ÿ ÿ( ÿD ÿ` ÿv ÿŒ ÿ¢ ÿ¸ ÿÔ ÿê!!!,!B!X!n!„!š!¶!Ì!â!ø!!$!:!P!f!|!’!¨!Ä!Ú!ð!!!2!N!d!z!!¦!¼!Ò!è!þ!!*!@!V!l!‚!ž!´!Ð!ì!!!.!D!`!v!Œ!¢!¸!Î!ê!!!,!B!X!n!„!š!°!Ì!â!þ!!*!F!\!r!ˆ!¤!º!Ð!æ!ü!!(!>!T!j!€!–!¬!Â!Ø!ô! !&!<!R!h!~!”!ª!À!Ö!ì! ! $! :! V! l! ‚! ˜! ®! Ä! Ú! ð! ! "! 8! N! d! z! ! ¬! Â! Ø! î! ! ! <! R! n! ! ¦! ¼! Ø! î! ! ! 0! F! b! x! ”! ª! À! Ö! ì! ! ! .! D! Z! p! †! œ! ¸! Î! ä! ú!!,!B!X!t!Š! !¶!Ì!â!ø!!$!:!P!f!|!˜!®!Ä!à!ü!!.!D!Z!p!†!œ!²!Î!ê!!!2!N!j!€!–!¬!Â!Ø!î!!!0!F!\!r!Ž!ª!Æ!Ü!ò!!!4!V!l!‚!˜!®!Ä!Ú!ð! !"!8!T!j!†!¢!¸!Î!ä!ú!!,!B!^!t!Š!¦!¼!Ø!î! ! !6!L!b!x!Ž!¤!º!Ð!ì!!!.!D!`!|!’!®!Ê!æ!ü!!.!J!f!|!˜!´!Ê!à!ö! !"!8!N!d!€!–!²!È!Þ!ô! ! !6!L!b!~!”!ª!À!Ü!ò!!!4!J!`!|!’!´!Ê!à!ö!!(!>!T!p!†!œ!²!Î!ä!ú!!2!H!^!t!Š! !¶!Ì!â!ø!!$!:!V!l!‚!ž!´!Ê!à!ö! !(!>!Z!p!Œ!¢!¸!Î!ê! ! ! 8! N! d! z! ! ¦! Â! Ø! î!!!!!!0!!F!!\!!r!!ˆ!!¤!!º!!Ö!!ò!"!"!"4!"P!"f!"|!"˜!"®!"Ê!"à!"ü!#!#(!#>!#T!#p!#†!#¢!#¾!#Ô!#ð!$ !$"!$>!$T!$p!$†!$œ!$¸!$Î!$ä!$ú!%!%&!%<!%X!%t!%Š!%¦!%¼!%Ò!%è!%þ!&!&*!&F!&\!&r!&Ž!&¤!&º!&Ð!&ì!'!'!':!'P!'l!'ˆ!'ž!'´!'Ê!'à!'ö!( !("!(>!(Z!(p!(Œ!(¢!(¸!(Î!(ä!(ú!)!)&!)<!)R!)h!)~!)”!)ª!)À!)Ö!)ì!*!*!*.!*D!*Z!*p!*†!*œ!*²!*È!*Þ!*ô!+ !+ !+6!+L!+b!+x!+Ž!+¤!+º!+Ð!+æ!+ü!,!,(!,>!,T!,j!,€!,–!,¬!,Â!,Ø!,î!-!-!-0!-F!-\!-r!-ˆ!-¤!-º!-Ö!-ì!.!.!..!.D!.Z!.p!.†!.œ!.²!.È!.Þ!.ô!/ !/ !/<!/R!/h!/~!/”!/ª!/À!/Ö!/ì!0!0!04!0P!0f!0|!0’!0¨!0¾!0Ô!0ê!1!1!1,!1B!1X!1n!1Š!1 !1¼!1Ò!1è!1þ!2!2*!2@!2V!2l!2‚!2˜!2®!2Ä!2à!2ö!3 !3"!3>!3T!3j!3€!3–!3¬!3Â!3Ø!3î!4!4 !46!4L!4b!4x!4Ž!4ª!4À!4Ö!4ì!5!5!54!5J!5`!5v!5Œ!5¢!5¸!5Ô!5ê!6!6!6,!6B!6^!6t!6Š!6 !6¶!6Ì!6â!6ø!7!7$!7:!7V!7l!7‚!7˜!7´!7Ð!7æ!7ü!8!8(!8>!8T!8j!8†!8œ!8¸!8Î!8ä!8ú!9!9&!9<!9R!9h!9~!9 !9¶!9Ì!9â!9ø!:!:*!:@!:V!:l!:ˆ!:ž!:´!:Ê!:à!:ö!; !;"!;8!;N!;j!;†!;œ!;¸!;Î!;ê!<!<!<,! !>"!>8!>N!>d!>z!>!>¦!>¼!>Ò!>î!? !?&!?<!?R!?n!?Š!? !?¶!?Ì!?â!?ø!@!@*!@@!@\!@r!@ˆ!@ž!@´!@Ð!@æ!@ü!A!A(!A>!AT!Aj!A€!A–!A¬!AÂ!AØ!Aî!B!B!B6!BL!Bb!Bx!BŽ!Bª!BÀ!BÖ!Bì!C!C!C.!CD!C`!Cv!CŒ!C¢!C¸!CÎ!Cä!D!D!D,!DB!DX!Dn!D„!Dš!D°!DÆ!DÜ!Dò!E!E!E4!EJ!E`!Ev!EŒ!E¢!E¸!EÎ!Eä!Eú!F!F&!F<!FR!Fh!F~!F”!F°!FÆ!FÜ!Fò!G!G!G4!GJ!G`!Gv!GŒ!G¢!G¸!GÎ!Gä!Gú!H!H&!HB!H^!Ht!HŠ!H !H¶!HÌ!Hâ!Hø!I!I$!I:!IP!If!I‚!I˜!I®!IÄ!IÚ!Iö!J !J"!J>!JZ!Jp!J†!Jœ!J²!JÈ!JÞ!Jô!K !K !K6!KL!Kb!Kx!KŽ!K¤!Kº!KÐ!Kæ!Kü!L!L(!L>!LT!Lj!L€!L–!L¬!LÂ!LØ!Lî!M!M!M0!MF!M\!Mr!Mˆ!Mž!M´!MÊ!Mà!Mö!N !N"!N8!NN!Nd!Nz!N–!N¬!NÂ!NØ!Nî!O!O!O0!OF!O\!Or!Oˆ!Ož!O´!OÊ!Oà!Oö!P !P"!P8!PN!Pj!P€!P–!P²!PÎ!Pä!Pú!Q!Q&!Q<!QR!Qh!Q~!Q”!Qª!QÀ!QÖ!Qò!R!R$!R:!RP!Rf!R|!R’!R¨!R¾!RÔ!Rê!S!S!S,!SB!S^!St!SŠ!S !S¶!SÌ!Sâ!Sø!T!T*!T@!TV!Tl!T‚!T˜!T®!TÄ!TÚ!Tð!U!U!U2!UH!U^!Ut!U!U¦!U¼!UÒ!Uè!Uþ!V!V0!VF!V\!Vr!Vˆ!Vž!V´!VÐ!Vì!W!W!W.!WD!WZ!Wp!W†!W¢!W¸!WÎ!Wä!X!X!X,!XB!XX!Xn!X„!Xš!X°!XÆ!Xâ!Xø!Y!Y$!Y:!YP!Yf!Y|!Y˜!Y®!YÄ!YÚ!Yð!Z!Z!Z2!ZH!Z^!Zz!Z!Z¬!ZÂ!ZØ!Zî![![![0![F![\![r![Ž![¤![º![Ð![æ![ü!\!\(!\>!\T!\j!\€!\–!\¬!\Â!\Ø!\î!]!]!]0!]F!]\!]r!]ˆ!]ž!]´!]Ê!]à!]ö!^ !^"!^8!^T!^j!^€!^–!^¬!^Â!^Ø!^î!_!_!_0!_F!_\!_r!_ˆ!_ž!_´!_Ê!_à!_ü!`!`(!`>!`T!`j!`€!`œ!`²!`È!`Þ!`ô!a !a !a6!aL!ab!ax!aŽ!a¤!aº!aÐ!aæ!aü!b!b(!b>!bT!bj!b†!bœ!b²!bÎ!bä!bú!c!c&!c<!cR!ch!c~!c”!cª!cÀ!cÖ!cì!d!d!d.!dD!dZ!dp!d†!dœ!d²!dÈ!dä!dú!e!e,!eH!e^!et!eŠ!e !e¶!eÌ!eâ!eø!f!f$!f:!fP!ff!f|!f’!f¨!f¾!fÔ!fê!g!g!g,!gB!gX!gn!g„!gš!g°!gÆ!gâ!gø!h!h$!h:!hP!hf!h|!h’!h¨!h¾!hÔ!hê!i!i!i,!iB!iX!in!i„!iš!i¶!iÌ!iâ!iø!j!j$!j:!jP!jf!j|!j’!j¨!j¾!jÔ!jê!k!k!k,!kB!kX!kn!k„!kš!k°!kÆ!kÜ!kò!l!l$!l:!lP!lf!l|!l’!l¨!l¾!lÚ!lö!m !m"!m8!mT!mj!m€!m–!m²!mÈ!mÞ!mô!n !n !n6!nR!nh!n~!nš!n°!nÆ!nÜ!nò!o!o!o:!oP!of!o|!o˜!o®!oÄ!oà!oö!p !p"!p8!pN!pd!pz!p!p¦!p¼!pÒ!pè!pþ!q!q0!qF!q\!qr!qˆ!qž!q´!qÊ!qà!qü!r!r(!r>!rT!rj!r€!rœ!r²!rÈ!rÞ!rô!s !s !s6!sL!sb!sx!sŽ!s¤!sº!sÐ!sæ!sü!t!t(!t>!tT!tj!t€!t–!t¬!tÂ!tØ!tô!u !u !u6!uR!uh!u~!u”!uª!uÀ!uÖ!uì!v!v!v.!vD!vZ!vp!v†!vœ!v²!vÈ!vÞ!vô!w !w&!w<!wR!wh!w~!w”!wª!wÀ!wÖ!wì!x!x!x.!xD!xZ!xp!x†!xœ!x²!xÈ!xÞ!xô!y !y !y6!yL!yb!yx!yŽ!y¤!yº!yÐ!yæ!yü!z!z(!z>!zT!zj!z€!z–!z¬!zÂ!zØ!zî!{!{!{0!{F!{b!{x!{Ž!{¤!{º!{Ð!{æ!{ü!|!|(!|>!|T!|j!|€!|–!|¬!|Â!|Ø!|î!}!}!}0!}F!}\!}r!}ˆ!}ž!}´!}Ê!}à!}ö!~ !~"!~8!~N!~d!~z!~!~¦!~¼!~Ò!~è!~þ!!*!@!V!l!‚!˜!®!Ä!Ú!ð!€!€!€2!€H!€^!€t!€Š!€ !€¶!€Ì!€â!€ø!!$!:!P!f!|!’!¨!¾!Ô!ê!‚!‚!‚,!‚B!‚X!‚n!‚„!‚š!‚°!‚Æ!‚Ü!‚ò!ƒ!ƒ!ƒ4!ƒJ!ƒ`!ƒv!ƒŒ!ƒ¢!ƒ¸!ƒÎ!ƒä!ƒú!„!„&!„<!„R!„h!„„!„š!„°!„Æ!„Ü!„ò!…!…!…4!…J!…`!…v!…Œ!…¢!…¸!…Î!…ê!†!†!†,!†B!†X!†n!†„!†š!†°!†Æ!†Ü!†ò!‡!‡!‡4!‡J!‡`!‡v!‡Œ!‡¢!‡¸!‡Î!‡ä!‡ú!ˆ!ˆ&!ˆ<!ˆR!ˆh!ˆ~!ˆ”!ˆª!ˆÀ!ˆÖ!ˆì!‰!‰!‰.!‰D!‰Z!‰p!‰†!‰œ!‰²!‰È!‰Þ!‰ô!Š !Š !Š6!ŠL!Šb!Šx!ŠŽ!Ф!Šº!ŠÐ!Šæ!Šü!‹!‹.!‹D!‹Z!‹p!‹†!‹œ!‹²!‹Î!‹ä!‹ú!Œ!Œ,!ŒB!ŒX!Œn!Œ„!Œš!Œ°!ŒÆ!ŒÜ!Œò!!!4!J!`!v!Œ!¢!¸!Î!ä!ú!Ž!Ž&!Ž<!ŽR!Žh!Ž~!Žš!ް!ŽÆ!ŽÜ!Žò!!!4!J!`!v!Œ!¢!¸!Î!ä!ú!!&!<!R!h!~!”!ª!À!Ö!ì!‘!‘!‘4!‘P!‘f!‘|!‘˜!‘®!‘Ä!‘Ú!‘ö!’ !’"!’8!’N!’d!’€!’–!’¬!’È!’ä!’ú!“!“&!“<!“R!“h!“~!“”!“ª!“À!“Ö!“ì!”!”!”.!”D!”Z!”p!”†!”œ!”²!”Î!”ä!”ú!•!•&!•<!•R!•h!•~!•”!•ª!•À!•Ö!•ì!–!–!–.!–D!–Z!–p!–†!–œ!–²!–È!–Þ!–ô!— !— !—6!—L!—b!—x!—Ž!—¤!—º!—Ð!—æ!—ü!˜!˜(!˜>!˜T!˜j!˜€!˜–!˜¬!˜Â!˜Ø!˜î!™!™!™0!™F!™\!™r!™ˆ!™ž!™´!™Ê!™à!™ö!š !š"!š8!šN!šd!šz!š!š¦!š¼!šÒ!šî!›!›!›0!›L!›b!›x!›Ž!›¤!›À!›Ü!›ò!œ!œ!œ4!œJ!œ`!œv!œŒ!œ¢!œ¸!œÎ!œä!œú!!&!<!R!h!~!š!¼!Ò!è!þ!ž!ž*!ž@!žV!žl!ž‚!žž!ž´!žÊ!žà!žö!Ÿ !Ÿ"!Ÿ>!ŸT!Ÿj!Ÿ€!Ÿ–!Ÿ¬!ŸÂ!ŸØ!Ÿî! ! ! 0! F! \! r! ˆ! ž! ´! Ê! à! ö!¡!¡(!¡>!¡T!¡j!¡€!¡œ!¡²!¡È!¡Þ!¡ô!¢ !¢ !¢6!¢L!¢b!¢x!¢Ž!¢¤!¢º!¢Ð!¢æ!¢ü!£!£(!£>!£T!£j!£€!£–!£¬!£Â!£Ø!£î!¤ !¤ !¤6!¤L!¤b!¤x!¤Ž!¤¤!¤º!¤Ð!¤æ!¤ü!¥!¥(!¥>!¥T!¥j!¥€!¥–!¥¬!¥Â!¥Ø!¥î!¦!¦!¦0!¦F!¦\!¦r!¦ˆ!¦ž!¦´!¦Ê!¦à!¦ö!§ !§"!§8!§N!§d!§z!§!§¦!§¼!§Ò!§è!§þ!¨!¨*!¨@!¨V!¨l!¨‚!¨˜!¨®!¨Ê!¨à!¨ö!© !©"!©8!©N!©d!©z!©!©¦!©¼!©Ò!©è!©þ!ª!ª*!ª@!ªV!ªl!ª‚!ª˜!ª®!ªÄ!ªÚ!ªð!«!«"!«8!«N!«d!«z!«!«¦!«¼!«Ò!«è!¬!¬!¬0!¬F!¬\!¬r!¬ˆ!¬ž!¬´!¬Ê!¬à!¬ö!­ !­"!­8!­N!­d!­z!­!­¦!­¼!­Ò!­è!­þ!®!®*!®@!®V!®l!®‚!®ž!®´!®Ê!®à!®ö!¯ !¯"!¯8!¯N!¯d!¯z!¯!¯¦!¯¼!¯Ò!¯è!¯þ!°!°*!°@!°V!°l!°‚!°˜!°®!°Ä!°Ú!°ö!± !±"!±8!±N!±d!±z!±–!±¬!±Â!±Ø!±ô!² !² !²6!²L!²b!²x!²Ž!²ª!²À!²Ö!²ì!³!³!³4!³J!³`!³v!³Œ!³¢!³¸!³Î!³ä!³ú!´!´&!´B!´X!´t!´Š!´ !´¶!´Ì!´â!´ø!µ!µ$!µ:!µP!µf!µ|!µ’!µ¨!µÄ!µÚ!µð!¶!¶!¶2!¶H!¶^!¶t!¶Š!¶ !¶¶!¶Ì!¶â!¶ø!·!·$!·:!·P!·f!·|!·’!·¨!·¾!·Ô!·ê!¸!¸!¸,!¸B!¸X!¸n!¸„!¸š!¸°!¸Æ!¸Ü!¸ò!¹!¹!¹4!¹J!¹`!¹v!¹Œ!¹¢!¹¸!¹Î!¹ä!¹ú!º!º&!º<!ºR!ºh!º~!º”!ºª!ºÀ!ºÖ!ºì!»!»!».!»D!»Z!»p!»†!»œ!»²!»È!»Þ!»ô!¼!¼&!¼<!¼R!¼h!¼~!¼”!¼ª!¼À!¼Ö!¼ì!½!½!½.!½D!½Z!½p!½†!½œ!½²!½È!½ä!½ú!¾!¾&!¾B!¾^!¾t!¾Š!¾ !¾¶!¾Ì!¾â!¾þ!¿!¿*!¿@!¿V!¿l!¿‚!¿˜!¿´!¿Ê!¿à!¿ö!À !À"!À8!ÀN!Àd!Àz!À!À¦!À¼!ÀÒ!Àè!Àþ!Á!Á*!Á@!ÁV!Ál!Á‚!Á˜!Á®!ÁÄ!ÁÚ!Áð!Â!Â!Â2!ÂH!Â^!Ât!Š! !¶!ÂÌ!Ââ!Âø!Ã!Ã*!Ã@!ÃV!Ãl!Â!Ø!î!ÃÄ!ÃÚ!Ãð!Ä!Ä!Ä2!ÄH!Ä^!Ät!ÄŠ!Ä !Ķ!ÄÌ!Äâ!Äø!Å!Å$!Å:!ÅP!Åf!Å|!Å’!Ũ!ž!ÅÔ!Åê!Æ!Æ!Æ,!ÆB!ÆX!Æn!Æ„!Æš!ư!ÆÆ!ÆÜ!Æò!Ç!Ç!Ç4!ÇJ!Ç`!Çv!ÇŒ!Ç¢!Ǹ!ÇÎ!Çä!Çú!È!È&!È<!ÈR!Èh!È~!È”!Ȫ!ÈÀ!ÈÖ!Èì!É!É!É4!ÉJ!É`!Év!ÉŒ!É¢!ɸ!ÉÎ!Éä!Éú!Ê!Ê&!Ê<!ÊR!Êh!Ê~!Ê”!ʪ!ÊÀ!ÊÖ!Êì!Ë!Ë!Ë.!ËD!ËZ!Ëp!ˆ!Ëœ!˲!ËÈ!ËÞ!Ëô!Ì !Ì !Ì6!ÌL!Ìh!Ì„!Ìš!̰!ÌÆ!Ìâ!Ìø!Í!Í$!Í:!ÍP!Íf!Í|!Í’!ͨ!;!ÍÔ!Íê!Î!Î!Î2!ÎH!Î^!Ît!Ί!Π!ζ!ÎÌ!Îâ!Îþ!Ï!Ï0!ÏF!Ï\!Ïr!ψ!Ïž!Ϻ!ÏÖ!Ïì!Ð!Ð!Ð.!ÐJ!Ð`!Ðv!ÐŒ!Т!и!ÐÎ!Ðä!Ðú!Ñ!Ñ&!Ñ<!ÑR!Ñh!Ñ~!Ñ”!Ѫ!ÑÀ!ÑÖ!Ñì!Ò!Ò!Ò.!ÒD!ÒZ!Òp!Ò†!Òœ!Ò²!ÒÈ!ÒÞ!Ó!Ó!Ó,!ÓB!ÓX!Ón!Ó„!Óš!Ó°!ÓÆ!ÓÜ!Óò!Ô!Ô!Ô4!ÔJ!Ô`!Ôv!ÔŒ!Ô¢!Ô¸!ÔÎ!Ôä!Ôú!Õ!Õ&!Õ<!ÕR!Õh!Õ~!Õ”!Õª!ÕÀ!ÕÖ!Õì!Ö!Ö!Ö.!ÖD!ÖZ!Öp!Ö†!Öœ!Ö²!ÖÈ!ÖÞ!Öô!× !× !×<!×R!×h!×~!×”!×°!ׯ!×â!×ø!Ø!Ø$!Ø:!ØV!Øl!Ø‚!ؘ!Ø®!ØÄ!ØÚ!Øð!Ù !Ù"!Ù8!ÙN!Ùd!Ùz!Ù!Ù¦!ÙÂ!ÙØ!Ùî!Ú!Ú!Ú6!ÚL!Úb!Úx!ÚŽ!Ú¤!Úº!ÚÐ!Úæ!Û!Û!Û4!ÛJ!Û`!Ûv!ÛŒ!Û¢!Û¸!ÛÎ!Ûä!Ûú!Ü!Ü&!Ü<!ÜR!Üh!Ü~!Ü”!ܪ!ÜÆ!ÜÜ!Üò!Ý!Ý*!Ý@!ÝV!Ýl!Ý‚!ݘ!Ý´!ÝÊ!Ýà!Ýö!Þ !Þ"!Þ>!ÞT!Þj!Þ€!Þ–!Þ¬!ÞÂ!ÞØ!Þî!ß!ß!ß0!ßF!ß\!ßr!߈!ßž!ß´!ßÐ!ßæ!ßü!à!à(!à>!àT!àp!à†!àœ!à¸!àÎ!àä!á!á!á2!áN!ád!á€!á–!á¬!áÂ!áØ!áô!â !â !â6!âL!âh!â~!âš!â¶!âÌ!ââ!âø!ã!ã$!ã:!ãP!ãf!ã‚!ã˜!ã®!ãÊ!ãà!ãü!ä!ä(!ä>!äT!äp!ä†!ä¢!ä¸!äÎ!ää!äú!å!å,!åB!åX!ån!å„!å !å¶!åÌ!åâ!åþ!æ!æ0!æL!æb!æx!æŽ!æª!æÀ!æÖ!æì!ç!ç!ç4!çP!çl!ç‚!ç˜!ç®!çÊ!çæ!è!è!è.!èD!èZ!èp!èŒ!è¨!è¾!èÔ!èê!é!é!é,!éB!éX!ét!é!é¦!é¼!éØ!éî!ê!ê !ê6!êL!êh!ê~!ê”!êª!êÀ!êÖ!êì!ë!ë!ë.!ëD!ëZ!ëp!ë†!ëœ!ë²!ëÈ!ëÞ!ëô!ì !ì !ì6!ìR!ìn!ì„!ìš!ì°!ìÆ!ìÜ!ìò!í!í!í4!íJ!í`!ív!íŒ!í¢!í¸!íÎ!íä!íú!î!î&!î<!îR!îh!î~!î”!îª!îÆ!îÜ!îò!ï!ï!ï4!ïJ!ï`!ïv!ïŒ!ï¢!ï¸!ïÎ!ïä!ïú!ð!ð,!ðB!ðX!ðn!ð„!ðš!ð°!ðÆ!ðâ!ðø!ñ!ñ$!ñ:!ñP!ñf!ñ|!ñ’!ñ¨!ñ¾!ñÔ!ñê!ò!ò!ò,!òH!ò^!òz!ò!ò¦!ò¼!òÒ!òè!òþ!ó!ó*!ó@!óV!ól!ó‚!óž!óº!óÐ!óæ!óü!ô!ô(!ô>!ôT!ôj!ô€!ô–!ô¬!ôÂ!ôØ!ôî!õ !õ&!õ<!õR!õh!õ~!õ”!õª!õÀ!õÖ!õì!ö!ö!ö.!öD!öZ!öp!ö†!öœ!ö²!öÈ!öÞ!öô!÷ !÷ !÷6!÷L!÷b!÷~!÷”!÷ª!÷À!÷Ö!÷ì!ø!ø!ø.!øD!øZ!øp!ø†!ø¢!ø¾!øÔ!øê!ù!ù!ù,!ùB!ùX!ùn!ù„!ùš!ù°!ùÆ!ùÜ!ùò!ú!ú!ú4!úJ!ú`!úv!úŒ!ú¢!ú¸!úÎ!úä!úú!û!û&!û<!ûR!ûh!û„!ûš!û°!ûÆ!ûÜ!ûò!ü!ü!ü4!üJ!üf!ü‚!ü˜!ü®!üÄ!üÚ!üð!ý!ý!ý2!ýH!ý^!ýt!ýŠ!ý !ý¶!ýÌ!ýâ!ýø!þ!þ$!þ:!þV!þl!þ‚!þ˜!þ®!þÄ!þÚ!þð!ÿ !ÿ"!ÿ8!ÿN!ÿd!ÿz!ÿ!ÿ¦!ÿ¼!ÿÒ!ÿè!ÿþ""*"@"V"l"‚"˜"®"Ä"Ú"ð"""2"H"^"t"Š" "¶"Ì"è"þ""*"@"V"l"‚"˜"®"Ä"Ú"ð"""2"H"^"t"Š" "¶"Ì"â"ø""$":"V"l"‚"˜"®"Ä"Ú"ð"""2"H"d"z""¦"¼"Ò"è"þ""*"@"V"l"‚"˜"´"Ð"æ"ü""."D"Z"v"Œ"¢"¸"Î"ê""","H"^"t""¦"¼"Ò"è"þ" " *" @" V" l" ‚" ˜" ®" Ä" Ú" ð" " " 2" H" d" z" " ¦" ¼" Ò" è" " " 6" L" b" x" Ž" ¤" º" Ð" ì" " " ." D" Z" p" †" œ" ²" È" Þ" ô" " &" <" R" n" „" š" °" Æ" Ü" ò""":"P"f"|"’"¨"Ä"Ú"ð"""2"H"^"t"Š" "¶"Ò"è"þ""*"F"\"r"ˆ"ž"º"Ð"ì"""."J"`"|"’"¨"¾"Ô"ð" "(">"T"j"€"–"¬"È"Þ"ô" " "6"L"b"x"Ž"¤"º"Ð"æ"ü""(">"T"j"€"–"¬"Â"Ø"î"""0"F"\"r"ˆ"ž"´"Ê"à"ö" """8"N"d"z""¦"¼"Ò"è"þ""*"@"V"l"‚"˜"®"Ê"à"ö" """>"T"j"€"–"¬"Â"Ø"î"""0"F"\"r"ˆ"ž"´"Ê"à"ö" """8"N"d"z""¦"¼"Ò"è"þ""*"@"V"l"‚"˜"®"Ê"à"ö" """>"T"j"€"–"²"Î"ê""","B"X"n"„"š"°"Æ"Ü"ò"""4"J"`"v"Œ"¢"¸"Î"ä"ú"","B"X"n"„" "¶"Ì"è"þ" " *" @" V" l" ‚" ˜" ´" Ð" æ" ü"!"!."!D"!Z"!p"!†"!œ"!²"!È"!Þ"!ô"" "" ""6""L""b""x""Ž""¤""º""Ð""æ""ü"#"#("#>"#T"#j"#€"#–"#¬"#È"#Þ"#ô"$"$,"$B"$X"$n"$„"$ "$¶"$Ì"$â"$ø"%"%*"%@"%V"%l"%ˆ"%¤"%º"%Ð"%æ"%ü"&"&("&>"&T"&j"&€"&–"&¬"&Â"&Ø"&î"'"'"'0"'F"'\"'r"'ˆ"'ž"'´"'Ê"'à"'ö"( "(""(8"(N"(d"(z"("(¦"(¼"(Ò"(è"(þ")")*")@")V")l")‚")˜")®")Ä")Ú")ð"*"*"*2"*H"*^"*t"*Š"* "*¶"*Ò"*è"*þ"+"+*"+@"+V"+l"+‚"+˜"+®"+Ä"+Ú"+ð",",",2",H",^",t",Š", ",¶",Ì",â",ø"-"-$"-:"-P"-f"-|"-’"-¨"-¾"-Ô"-ê".".".,".B".X".n".„".š".°".Æ".Ü".ò"/"/"/4"/J"/`"/v"/Œ"/¢"/¾"/Ô"/ê"0"0"0,"0B"0X"0n"0„"0š"0°"0Æ"0Ü"0ò"1"1"14"1J"1`"1v"1’"1¨"1¾"1Ô"1ê"2"2"2,"2B"2X"2n"2„"2š"2°"2Æ"2Ü"2ò"3"3$"3:"3P"3f"3|"3˜"3®"3Ä"3Ú"3ö"4"4("4>"4T"4p"4†"4œ"4²"4È"4Þ"4ô"5 "5 "56"5L"5b"5x"5Ž"5¤"5º"5Ð"5æ"5ü"6"6("6>"6T"6j"6€"6–"6¬"6Â"6Ø"6î"7"7"70"7F"7\"7r"7ˆ"7ž"7´"7Ê"7à"7ö"8 "8""88"8N"8d"8z"8"8¦"8¼"8Ò"8è"8þ"9"9*"9@"9V"9l"9‚"9˜"9´"9Ê"9à"9ö": ":"":8":N":d":z":":¦":¼":Ò":è":þ";";*";@";V";l";‚";˜";®";Ä";Ú";ð"<"<"<2"">">.">D">Z">p">†">œ">¸">Î">ä">ú"?"?&"?<"?R"?h"?~"?”"?ª"?À"?Ö"?ì"@"@"@."@D"@Z"@p"@†"@œ"@²"@È"@Þ"@ô"A "A "A6"AL"Ab"Ax"AŽ"A¤"Aº"AÐ"Aæ"Aü"B"B("B>"BT"Bj"B€"B–"B¬"BÂ"BØ"Bî"C"C"C0"CF"C\"Cr"Cˆ"Cž"C´"CÊ"Cà"Cö"D "D""D>"DT"Dp"D†"Dœ"D²"DÈ"DÞ"Dô"E "E "E<"ER"En"E„"Eš"E¶"EÌ"Eâ"Eø"F"F*"F@"FV"Fl"F‚"F˜"F®"FÄ"FÚ"Fð"G"G"G2"GH"G^"Gt"G"G¦"G¼"GÒ"Gî"H"H"H0"HF"H\"Hr"Hˆ"Hž"H´"HÊ"Hà"Hü"I"I."ID"IZ"Ip"I†"Iœ"I²"IÎ"Iä"Iú"J"J&"J<"JR"Jh"J~"J”"Jª"JÆ"JÜ"Jò"K"K"K4"KJ"K`"Kv"KŒ"K¢"K¸"KÎ"Kä"Kú"L"L&"L<"LR"Lh"L„"Lš"L°"LÆ"LÜ"Lò"M"M"M4"MJ"M`"Mv"MŒ"M¢"M¸"MÎ"Mä"Mú"N"N&"N<"NR"Nh"N~"N”"Nª"NÀ"NÖ"Nì"O"O"O."OD"OZ"Op"O†"Oœ"O²"OÈ"OÞ"Oô"P "P "P6"PL"Pb"Px"PŽ"P¤"Pº"PÐ"Pæ"Pü"Q"Q("Q>"QT"Qj"Q€"Q–"Q¬"QÂ"QØ"Qî"R"R"R0"RF"R\"Rr"Rˆ"Rž"R´"RÐ"Ræ"Rü"S"S("S>"ST"Sj"S€"S–"S¬"SÂ"SØ"Sî"T"T"T0"TF"T\"Tr"Tˆ"Tž"T´"TÊ"Tà"Tö"U "U""U8"UN"Ud"Uz"U"U¦"U¼"UÒ"Uè"Uþ"V"V*"V@"VV"Vl"V‚"V˜"V®"VÄ"VÚ"Vð"W"W"W2"WH"W^"Wt"WŠ"W "W¶"WÌ"Wâ"Wø"X"X$"X:"XP"Xf"X|"X’"X¨"X¾"XÔ"Xê"Y"Y"Y,"YB"YX"Yt"YŠ"Y "Y¶"YÌ"Yâ"Yþ"Z"Z*"Z@"ZV"Zl"Z‚"Z˜"Z®"ZÄ"ZÚ"Zð"["["[2"[H"[^"[t"[Š"[ "[¶"[Ì"[â"[ø"\"\$"\:"\P"\l"\‚"\˜"\®"\Ä"\Ú"\ð"]"]"]2"]H"]^"]t"]Š"] "]¶"]Ì"]â"]ø"^"^$"^:"^P"^l"^‚"^˜"^®"^Ä"^Ú"^ð"_ "_""_8"_N"_j"_†"_œ"_²"_È"_Þ"_ô"` "` "`6"`R"`n"`„"`š"`°"`Æ"`Ü"`ò"a"a"a:"aP"af"a‚"a˜"a®"aÊ"aà"aö"b"b("b>"bT"bj"b€"b–"b¬"bÂ"bØ"bî"c"c "c6"cR"ch"c~"c”"cª"cÆ"cÜ"cò"d"d"d4"dJ"df"d|"d’"d¨"d¾"dÔ"dê"e"e"e8"eN"ej"e†"e¢"e¸"eÎ"eê"f"f"f,"fB"f^"ft"f"f¬"fÂ"fÞ"fô"g "g&"g<"gR"gh"g~"g”"gª"gÆ"gÜ"gò"h"h*"h@"h\"hr"hˆ"h¤"hÀ"hÜ"hò"i"i"i4"iJ"i`"iv"iŒ"i¢"i¸"iÎ"iä"iú"j"j&"j<"jX"jn"jŠ"j "j¶"jÌ"jâ"jø"k"k$"k@"kV"kl"k‚"k˜"k®"kÄ"kÚ"kö"l"l("l>"lT"lp"l†"lœ"l¸"lÔ"lê"m"m"m,"mH"m^"mt"mŠ"m "m¶"mÌ"mâ"mø"n"n$"n:"nV"nr"nˆ"nž"n´"nÊ"næ"nü"o"o."oD"oZ"op"o†"oœ"o²"oÈ"oÞ"oô"p"p&"p<"pR"ph"p~"p”"pª"pÀ"pÖ"pì"q"q"q4"qJ"q`"qv"qŒ"q¨"q¾"qÔ"qê"r"r"r,"rB"rX"rn"rŠ"r "r¶"rÌ"râ"rø"s"s$"s:"sP"sf"s|"s˜"s®"sÊ"sà"sö"t "t("t>"tZ"tp"t†"tœ"t²"tÈ"tÞ"tô"u "u&"u<"uR"un"u„"uš"u°"uÆ"uÜ"uø"v"v0"vL"vh"v„"vš"v°"vÆ"vâ"vø"w"w$"w:"wV"wl"w‚"w˜"w®"wÄ"wÚ"wð"x"x"x2"xH"x^"xz"x"x¦"x¼"xÒ"xè"xþ"y"y*"y@"y\"yr"yŽ"y¤"yº"yÖ"yì"z"z"z."zD"z`"zv"zŒ"z¨"zÄ"zÚ"zð"{ "{""{8"{N"{d"{z"{"{¬"{Â"{Ø"{î"| "|&"|<"|R"|n"|„"|š"|¶"|Ì"|â"|ø"}"}0"}F"}\"}x"}Ž"}¤"}º"}Ð"}æ"}ü"~"~("~D"~`"~v"~’"~¨"~¾"~Ô"~ê"""2"H"^"t""¦"¼"Ò"è"€"€"€6"€L"€b"€x"€Ž"€¤"€À"€Ü"€ò""$"@"\"r"Ž"ª"À"Ö"ì"‚"‚"‚:"‚P"‚f"‚|"‚’"‚¨"‚Ä"‚à"‚ü"ƒ"ƒ("ƒ>"ƒT"ƒp"ƒ†"ƒœ"ƒ²"ƒÈ"ƒÞ"ƒô"„ "„ "„6"„R"„h"„~"„”"„ª"„À"„Ö"„ì"…"…"…."…J"…`"…v"…Œ"…¢"…¸"…Î"…ä"…ú"†"†&"†<"†R"†n"†„"†š"†°"†Ì"†â"†ø"‡"‡$"‡:"‡P"‡l"‡‚"‡˜"‡´"‡Ê"‡à"‡ü"ˆ"ˆ("ˆ>"ˆZ"ˆp"ˆ†"ˆœ"ˆ¸"ˆÎ"ˆä"ˆú"‰"‰,"‰H"‰d"‰z"‰"‰¬"‰Â"‰Ø"‰ô"Š "Š "Š6"ŠR"Šn"Š„"Šš"а"ŠÌ"Šâ"Šø"‹"‹*"‹@"‹V"‹l"‹‚"‹ž"‹´"‹Ð"‹ì"Œ"Œ"Œ:"ŒP"Œl"Œ‚"Œ˜"Œ´"ŒÊ"Œæ"Œü""."J"`"v"Œ"¢"¸"Ô"ê"Ž"Ž""Ž>"ŽT"Žj"Ž€"Ž–"ެ"ŽÈ"ŽÞ"Žô" "2"H"^"t"Š" "¶"Ì"â"ø""*"@"V"l"‚"ž"´"Ê"à"ö"‘ "‘("‘J"‘f"‘|"‘’"‘¨"‘Ä"‘Ú"‘ð"’"’""’8"’N"’j"’€"’œ"’²"’È"’ä"’ú"“"“2"“N"“d"“€"“–"“¬"“Â"“Ø"“ô"” "” "”<"”R"”h"”„"”š"”°"”Æ"”Ü"”ò"•"•"•4"•J"•`"•v"•Œ"•¢"•¸"•Î"•ä"•ú"–"–,"–H"–^"–t"–"–¦"–Â"–Ø"–î"—"—"—0"—F"—\"—r"—ˆ"—ž"—´"—Ê"—à"—ö"˜ "˜""˜8"˜N"˜d"˜z"˜–"˜¬"˜Â"˜Ø"˜î"™ "™ "™6"™L"™b"™~"™”"™ª"™À"™Ü"™ò"š"š$"š:"šP"šf"š|"š’"š®"šÄ"šÚ"šö"› "›""›8"›T"›j"›€"›–"›¬"›Â"›Ø"›î"œ"œ"œ0"œL"œb"œ~"œ”"œ°"œÆ"œâ"œø""$":"P"f"|"’"¨"¾"Ô"ê"ž"ž"ž,"žB"žX"žn"ž„"žš"ž¶"žÌ"žâ"žø"Ÿ"Ÿ$"Ÿ:"ŸP"Ÿf"Ÿ|"Ÿ’"Ÿ¨"Ÿ¾"ŸÔ"Ÿê" " " ," B" X" n" „" š" °" Æ" Ü" ò"¡"¡"¡4"¡P"¡f"¡|"¡’"¡¨"¡¾"¡Ô"¡ê"¢"¢"¢,"¢B"¢X"¢n"¢„"¢š"¢°"¢Æ"¢Ü"¢ò"£"£"£4"£J"£`"£v"£’"£¨"£¾"£Ô"£ê"¤"¤"¤2"¤H"¤^"¤t"¤Š"¤ "¤¶"¤Ì"¤â"¤ø"¥"¥$"¥:"¥P"¥f"¥|"¥’"¥¨"¥¾"¥Ô"¥ê"¦"¦"¦,"¦B"¦X"¦n"¦„"¦ "¦¶"¦Ò"¦è"¦þ"§"§*"§@"§V"§l"§‚"§˜"§´"§Ê"§à"§ö"¨ "¨""¨>"¨T"¨j"¨†"¨œ"¨²"¨È"¨Þ"¨ô"© "© "©6"©L"©b"©x"©Ž"©¤"©º"©Ð"©æ"©ü"ª"ª("ªD"ªZ"ªp"ª†"ªœ"ª²"ªÈ"ªÞ"ªô"« "«&"«B"«X"«n"«Š"« "«¶"«Ì"«â"«ø"¬"¬*"¬@"¬V"¬l"¬‚"¬˜"¬®"¬Ä"¬Ú"¬ð"­"­"­2"­H"­^"­t"­Š"­ "­¶"­Ì"­â"­ø"®"®$"®@"®V"®l"®‚"®˜"®®"®Ä"®à"®ö"¯ "¯""¯8"¯N"¯d"¯z"¯"¯¦"¯¼"¯Ò"¯è"¯þ"°"°0"°F"°\"°r"°ˆ"°ž"°´"°Ê"°æ"°ü"±"±."±D"±Z"±p"±†"±œ"±²"±È"±Þ"±ô"² "²&"²B"²X"²t"²Š"² "²¶"²Ì"²â"²ø"³"³$"³@"³V"³l"³ˆ"³ž"³´"³Ê"³à"³ö"´ "´""´8"´N"´d"´z"´"´¦"´¼"´Ò"´è"´þ"µ"µ0"µF"µ\"µx"µŽ"µ¤"µº"µÐ"µæ"µü"¶"¶("¶>"¶T"¶p"¶†"¶¢"¶¸"¶Î"¶ä"¶ú"·"·,"·B"·X"·n"·„"·š"·°"·Æ"·Ü"·ò"¸"¸"¸4"¸J"¸`"¸v"¸Œ"¸¢"¸¸"¸Î"¸ä"¸ú"¹"¹,"¹B"¹X"¹t"¹Š"¹ "¹¶"¹Ì"¹â"¹ø"º"º$"º:"ºP"ºf"º|"º’"º¨"º¾"ºÔ"ºê"»"»"»,"»B"»X"»n"»„"»š"»°"»Æ"»Ü"»ò"¼"¼"¼4"¼J"¼`"¼v"¼Œ"¼¢"¼¸"¼Î"¼ä"¼ú"½"½,"½B"½X"½n"½„"½š"½°"½Æ"½Ü"½ò"¾"¾"¾4"¾J"¾`"¾v"¾Œ"¾¢"¾¸"¾Î"¾ä"¾ú"¿"¿&"¿B"¿X"¿n"¿„"¿š"¿°"¿Æ"¿Ü"¿ò"À"À"À4"ÀJ"À`"Àv"ÀŒ"À¢"À¸"ÀÎ"Àä"Àú"Á"Á&"Á<"ÁR"Áh"Á~"Á”"Áª"ÁÆ"Áâ"Áþ"Â"Â6"ÂR"Ân"Š"¦"ÂÂ"ÂÞ"Âú"Ã"Ã2"ÃN"Ãd"Ãz"Ã"æ"ü"ÃÒ"Ãè"Ãþ"Ä"Ä*"Ä@"ÄV"Äl"Ä‚"Ę"Ä´"ÄÊ"Äà"Äö"Å "Å""Å8"ÅN"Åj"Å€"Å–"Ŭ"ÅÂ"ÅØ"Åî"Æ"Æ "Æ6"ÆL"Æb"Æx"ÆŽ"Ƥ"ƺ"ÆÐ"Ææ"Æü"Ç"Ç("Ç>"ÇT"Çj"Ç€"Ç–"Ǭ"ÇÂ"ÇØ"Çî"È"È"È0"ÈL"Èb"Èx"È”"Ȫ"ÈÀ"ÈÖ"Èì"É"É"É4"ÉJ"É`"Év"ÉŒ"ɨ"ɾ"ÉÚ"Éð"Ê"Ê"Ê2"ÊH"Ê^"Êt"ÊŠ"Ê "ʶ"ÊÌ"Êâ"Êø"Ë"Ë$"Ë:"ËP"Ëf"Ë|"Ë’"˨"˾"ËÚ"Ëð"Ì "Ì""Ì8"ÌT"Ìj"Ì€"Ì–"̬"ÌÂ"ÌØ"Ìî"Í"Í"Í0"ÍF"Íb"Íx"ÍŽ"ͤ"ͺ"ÍÐ"Íæ"Íü"Î"Î("ÎD"ÎZ"Îp"Ά"Μ"β"ÎÈ"ÎÞ"Îô"Ï"Ï&"Ï<"ÏX"Ïn"Ï„"Ïš"ϰ"ÏÆ"ÏÜ"Ïò"Ð"Ð"Ð:"ÐP"Ðf"Ђ"И"д"ÐÊ"Ðà"Ðö"Ñ "Ñ""Ñ>"ÑT"Ñj"ц"Ñ¢"Ѹ"ÑÎ"Ñð"Ò"Ò""Ò8"ÒN"Òd"Ò€"Ò–"Ò²"ÒÈ"Òä"Òú"Ó"Ó&"ÓB"ÓX"Ón"ÓŠ"Ó¦"Ó¼"ÓÒ"Óè"Óþ"Ô"Ô*"Ô@"ÔV"Ôl"Ô‚"Ô˜"Ô´"ÔÊ"Ôà"Ôö"Õ "Õ""Õ8"ÕN"Õd"Õz"Õ"Õ¦"Õ¼"ÕÒ"Õè"Õþ"Ö"Ö0"ÖL"Öb"Öx"ÖŽ"Ö¤"Öº"ÖÐ"Öæ"Öü"×"×("×>"×T"×j"×€"×–"ײ"×È"×ä"×ú"Ø"Ø&"Ø<"ØR"Øh"Ø~"Ø”"ت"ØÀ"ØÖ"Øì"Ù"Ù"Ù."ÙD"ÙZ"Ùp"Ù†"Ùœ"Ù²"ÙÈ"Ùä"Ùú"Ú"Ú&"Ú<"ÚR"Úh"Ú~"Ú”"Úª"ÚÀ"ÚÖ"Úì"Û"Û"Û."ÛD"Û`"Ûv"ÛŒ"Û¢"Û¸"ÛÎ"Ûä"Ûú"Ü"Ü&"Ü<"ÜR"Üh"Ü~"Ü”"ܪ"ÜÀ"ÜÖ"Üì"Ý"Ý"Ý."ÝD"ÝZ"Ýp"݆"Ýœ"ݲ"ÝÈ"ÝÞ"Ýô"Þ "Þ "Þ6"ÞL"Þb"Þx"ÞŽ"Þ¤"Þº"ÞÐ"Þæ"Þü"ß"ß("ß>"ßT"ßj"߀"ß–"߬"ßÂ"ߨ"ßî"à"à"à0"àF"à\"àr"àˆ"àž"à´"àÊ"àæ"àü"á"á("á>"áT"áj"á€"á–"á¬"áÂ"áØ"áî"â"â"â0"âF"â\"âr"âˆ"âž"â´"âÊ"âà"âö"ã "ã""ã8"ãN"ãd"ãz"ã"ã¦"ãÂ"ãØ"ãî"ä"ä"ä0"äF"ä\"är"äˆ"äž"ä´"äÊ"äà"äö"å "å""å8"åN"åd"åz"å"å¦"å¼"åÒ"åè"åþ"æ"æ*"æ@"æV"æl"æ‚"æ˜"æ®"æÄ"æÚ"æð"ç"ç"ç2"çH"ç^"çz"ç"ç¦"ç¼"çØ"çî"è"è"è0"èF"è\"èr"èˆ"èž"è´"èÊ"èà"èö"é "é""é8"éN"éd"éz"é"é¦"é¼"éÒ"éî"ê"ê"ê0"êF"ê\"êr"êˆ"êž"ê´"êÊ"êà"êö"ë "ë""ë8"ëN"ëd"ëz"ë"ë¦"ë¼"ëÒ"ëè"ëþ"ì"ì*"ì@"ìV"ìl"ì‚"ì˜"ì®"ìÄ"ìÚ"ìð"í"í"í2"íH"í^"ít"íŠ"í "í¶"íÌ"íâ"íø"î"î$"î:"îP"îf"î|"î’"î¨"î¾"îÔ"îê"ï"ï"ï,"ïB"ïX"ïn"ï„"ïš"ï°"ïÆ"ïÜ"ïò"ð"ð$"ð@"ðV"ðl"ð‚"ð˜"ð®"ðÊ"ðà"ðö"ñ "ñ""ñ8"ñN"ñd"ñz"ñ–"ñ¬"ñÂ"ñØ"ñî"ò"ò"ò0"òL"òb"òx"òŽ"ò¤"òº"òÐ"òæ"ó"ó"ó."óD"óZ"óv"óŒ"ó¢"ó¾"óÔ"óê"ô"ô"ô2"ôH"ô^"ôt"ôŠ"ô¦"ô¼"ôÒ"ôî"õ"õ"õ0"õF"õ\"õr"õˆ"õž"õ´"õÊ"õà"õö"ö "ö""ö8"öN"öd"öz"ö"ö¦"öÂ"öØ"öî"÷"÷"÷0"÷F"÷b"÷x"÷Ž"÷¤"÷À"÷Ö"÷ì"ø"ø"ø."øD"ø`"øv"øŒ"ø¢"ø¸"øÎ"øä"øú"ù"ù&"ù<"ùR"ùn"ù„"ùš"ù°"ùÆ"ùÜ"ùò"ú"ú"ú4"úJ"úf"ú‚"ú˜"ú®"úÄ"úÚ"úð"û"û"û2"ûH"û^"ût"ûŠ"û "û¶"ûÌ"ûâ"ûø"ü"ü$"ü:"üP"üf"ü‚"ü˜"ü®"üÄ"üÚ"üð"ý"ý"ý2"ýH"ý^"ýt"ýŠ"ý "ý¶"ýÌ"ýâ"ýø"þ"þ$"þ:"þP"þf"þ|"þ’"þ¨"þ¾"þÔ"þê"ÿ"ÿ"ÿ,"ÿB"ÿX"ÿn"ÿ„"ÿš"ÿ°"ÿÆ"ÿÜ"ÿò###4#J#`#v#Œ#¢#¸#Î#ä#ú##,#B#X#n#„#š#°#Æ#Ü#ò###4#J#`#v#Œ#¢#¸#Î#ä#ú##&#<#R#h#~#”#ª#À#Ö#ì###.#D#Z#p#†#¢#¸#Î#ä#ú##,#B#X#n#„#š#°#Æ#Ü#ò###4#J#`#v#’#®#Ä#Ú#ð###2#H#^#t#Š# #¶#Ì#â#ø##$#:#P#f#|#’#¨#¾#Ô#ê# # # ,# B# X# n# „# š# °# Æ# Ü# ò# # # 4# J# `# v# Œ# ¢# ¸# Î# ä# ú# # &# <# R# h# ~# ”# ª# À# Ö# ì# # # .# D# Z# p# †# œ# ²# È# Þ# ô# # # 6# L# b# x# Ž# ¤# º# Ð# æ# ü##(#>#T#j#€#–#¬#Â#Ø#î###0#F#\#r#ˆ#ž#´#Ê#à#ö# #"#8#N#d#z##¦#¼#Ò#è#þ##*#@#V#l#‚#˜#®#Ä#Ú#ð###2#H#^#t#Š# #¶#Ì#â#ø##$#:#P#f#|#’#¨#¾#Ô#ê###,#B#X#n#„#š#°#Æ#Ü#ø##$#:#P#f#|#’#®#Ä#Ú#ð###2#H#^#t#Š# #¶#Ì#è###0#L#b#x#Ž#ª#À#Ö#ì###.#D#Z#p#†#œ#²#È#Þ#ô# # #<#R#h#~#”#ª#À#Ö#ì###.#D#Z#p#†#œ#²#È#Þ#ô# # #6#L#b#x#Ž#¤#º#Ð#æ#ü##(#>#T#j#€#–#¬#Â#Ø#î###0#F#\#r#ˆ#ž#´#Ê#à#ö# #"#8#N#d#z##¦#¼#Ò#è#þ##*#@#V#l#‚#˜#®#Ä#Ú#ð# # # 2# H# ^# t# Š#  # ¶# Ì# â# ø#!#!$#!:#!P#!f#!|#!’#!¨#!¾#!Ô#!ê#"#"#",#"B#"X#"n#"„#"š#"°#"Æ#"Ü#"ò######4##J##`##v##Œ##¢##¸##Î##ä#$#$#$,#$B#$^#$t#$Š#$ #$¶#$Ì#$â#$ø#%#%$#%:#%P#%f#%|#%’#%¨#%¾#%Ô#%ê#&#&#&,#&B#&X#&n#&„#&š#&°#&Æ#&Ü#&ò#'#'$#':#'P#'f#'|#'’#'¨#'¾#'Ô#'ê#(#(#(,#(H#(^#(t#(Š#( #(¶#(Ì#(â#(ø#)#)$#):#)P#)f#)|#)’#)¨#)¾#)Ô#)ê#*#*#*,#*B#*X#*n#*„#*š#*°#*Æ#*Ü#*ò#+#+#+4#+J#+`#+v#+Œ#+¨#+Ä#+Ú#+ð#,#,#,2#,N#,j#,€#,–#,¬#,Â#,Ø#,î#-#-#-0#-F#-b#-x#-Ž#-¤#-º#-Ð#-ì#.#.#.4#.J#.`#.v#.’#.¨#.¾#.Ô#.ê#/#/#/,#/H#/^#/t#/Š#/ #/¶#/Ì#/â#/þ#0#00#0F#0\#0r#0Ž#0¤#0º#0Ð#0æ#0ü#1#1(#1>#1T#1j#1€#1œ#1²#1È#1Þ#1ô#2 #2 #26#2L#2b#2x#2Ž#2¤#2º#2Ð#2æ#2ü#3#3(#3>#3T#3j#3€#3–#3¬#3Â#3Ø#3î#4#4#40#4F#4\#4r#4ˆ#4ž#4´#4Ê#4à#4ö#5 #5"#58#5N#5d#5z#5#5¦#5¼#5Ò#5è#5þ#6#6*#6@#6V#6l#6‚#6˜#6®#6Ä#6Ú#6ð#7#7#72#7H#7^#7t#7Š#7 #7¶#7Ì#7â#7ø#8#8$#8:#8P#8f#8|#8’#8¨#8¾#8Ô#8ê#9#9#9,#9B#9X#9n#9„#9š#9°#9Æ#9Ü#9ò#:#:#:4#:J#:`#:v#:Œ#:¢#:¸#:Î#:ä#:ú#;#;&#;B#;X#;n#;„#;š#;°#;Æ#;Ü#;ò#<#<#<4##>$#>:#>P#>f#>|#>’#>¨#>¾#>Ô#>ê#?#?#?,#?B#?X#?n#?„#?š#?°#?Æ#?Ü#?ò#@#@#@4#@J#@`#@v#@Œ#@¢#@¸#@Î#@ä#@ú#A#A&#A<#AR#Ah#A„#Aš#A°#AÆ#AÜ#Aø#B#B0#BL#Bh#B~#Bš#B¶#BÌ#Bâ#Bø#C#C$#C:#CP#Cf#C|#C’#C¨#C¾#CÔ#Cê#D#D#D,#DB#DX#Dn#D„#Dš#D°#DÆ#DÜ#Dò#E#E#E4#EJ#E`#Ev#EŒ#E¢#E¸#EÎ#Eä#Eú#F#F&#F<#FR#Fh#F~#F”#Fª#FÀ#FÖ#Fì#G#G#G.#GD#GZ#Gp#G†#Gœ#G²#GÈ#GÞ#Gô#H #H #H6#HL#Hb#Hx#HŽ#H¤#Hº#HÐ#Hæ#Hü#I#I(#ID#IZ#Ip#I†#Iœ#I²#IÈ#IÞ#Iô#J #J #J6#JL#Jb#Jx#JŽ#Jª#JÀ#JÖ#Jì#K#K#K.#KD#KZ#Kv#KŒ#K¢#K¸#KÎ#Kä#Kú#L#L&#L<#LR#Lh#L~#L”#Lª#LÀ#LÖ#Lì#M#M#M.#MD#MZ#Mp#M†#Mœ#M²#MÈ#MÞ#Mô#N #N #N6#NL#Nb#Nx#NŽ#N¤#Nº#NÐ#Næ#Nü#O#O(#O>#OT#Oj#O€#O–#O¬#OÂ#OØ#Oî#P#P#P0#PF#P\#Pr#Pˆ#Pž#P´#PÊ#Pà#Pö#Q#Q(#Q>#QT#Qj#Q€#Q–#Q¬#QÂ#QØ#Qî#R#R#R6#RL#Rb#Rx#RŽ#R¤#Rº#RÐ#Ræ#Rü#S#S(#S>#ST#Sj#S€#S–#S¬#SÂ#SØ#Sî#T#T#T0#TF#T\#Tr#Tˆ#Tž#T´#TÊ#Tà#Tö#U #U"#U8#UN#Ud#Uz#U#U¦#U¼#UÒ#Uè#Uþ#V#V*#V@#VV#Vl#V‚#V˜#V®#VÄ#VÚ#Vð#W#W#W2#WH#W^#Wt#WŠ#W #W¶#WÌ#Wâ#Wø#X#X$#X:#XP#Xf#X|#X’#X¨#X¾#XÔ#Xê#Y#Y#Y,#YB#YX#Yn#Y„#Yš#Y°#YÆ#Yâ#Yø#Z#Z$#Z:#ZP#Zf#Z|#Z’#Z¨#Z¾#ZÔ#Zê#[#[#[,#[H#[^#[t#[Š#[ #[¶#[Ì#[â#[ø#\#\$#\@#\V#\l#\‚#\˜#\®#\Ä#\Ú#\ð#]#]#]2#]H#]^#]t#]Š#] #]¶#]Ì#]â#]ø#^#^$#^:#^P#^f#^|#^’#^¨#^¾#^Ô#^ê#_#_#_,#_B#_X#_n#_„#_š#_°#_Æ#_Ü#_ò#`#`#`4#`J#``#`v#`Œ#`¢#`¸#`Î#`ä#`ú#a#a&#a<#aR#ah#a~#a”#aª#aÀ#aÖ#aì#b#b#b.#bD#bZ#bp#b†#bœ#b²#bÈ#bÞ#bô#c#c&#c<#cR#ch#c~#c”#cª#cÀ#cÖ#cì#d#d#d.#dD#dZ#dp#d†#dœ#d²#dÈ#dÞ#dô#e #e #e<#eR#eh#e~#e”#eª#eÆ#eÜ#eò#f#f#f:#fP#ff#f|#f’#f¨#f¾#fÔ#fê#g#g#g,#gB#gX#gn#g„#gš#g°#gÆ#gÜ#gò#h#h#h4#hJ#h`#hv#hŒ#h¢#h¸#hÎ#hä#hú#i#i&#i<#iR#ih#i~#i”#iª#iÀ#iÖ#iì#j#j#j.#jD#jZ#jp#j†#jœ#j²#jÈ#jÞ#jô#k #k #k6#kL#kb#kx#kŽ#k¤#kº#kÐ#kæ#kü#l#l(#l>#lT#lj#l€#l–#l¬#lÂ#lØ#lî#m#m#m0#mF#m\#mr#mˆ#mž#m´#mÊ#mæ#mü#n#n(#n>#nT#nj#n€#n–#n¬#nÂ#nØ#nî#o#o#o0#oF#o\#or#oˆ#ož#o´#oÊ#oà#oö#p #p"#p8#pN#pd#pz#p#p¦#p¼#pÒ#pè#pþ#q#q*#q@#qV#ql#q‚#q˜#q®#qÄ#qÚ#qð#r#r#r2#rH#r^#rt#rŠ#r #r¶#rÌ#râ#rø#s#s$#s:#sP#sf#s|#s’#s¨#s¾#sÔ#sê#t#t#t2#tH#t^#tt#tŠ#t #t¶#tÌ#tâ#tø#u#u$#u:#uP#uf#u|#u’#u¨#uÄ#uÚ#uð#v#v#v2#vH#v^#vt#vŠ#v #v¶#vÌ#vâ#vþ#w#w*#w@#wV#wl#w‚#w˜#w®#wÄ#wÚ#wð#x#x#x2#xH#x^#xt#xŠ#x #x¶#xÌ#xâ#xø#y#y$#y:#yP#yf#y|#y’#y¨#y¾#yÔ#yð#z#z#z2#zH#z^#zt#zŠ#z #z¶#zÌ#zè#zþ#{#{*#{@#{V#{l#{‚#{˜#{®#{Ä#{Ú#{ð#|#|#|2#|H#|^#|t#|Š#| #|¶#|Ì#|â#|ø#}#}$#}@#}V#}l#}‚#}˜#}®#}Ä#}Ú#}ð#~#~#~2#~H#~^#~t#~Š#~ #~¼#~Ò#~è#~þ##0#F#\#r#ˆ#ž#´#Ê#à#ö#€ #€"#€8#€N#€d#€z#€#€¦#€¼#€Ò#€è#€þ##*#@#V#l#‚#˜#®#Ä#Ú#ð#‚#‚#‚2#‚H#‚^#‚t#‚Š#‚ #‚¼#‚Ò#‚è#‚þ#ƒ#ƒ*#ƒF#ƒ\#ƒr#ƒˆ#ƒž#ƒº#ƒÖ#ƒì#„#„#„4#„J#„`#„v#„Œ#„¢#„¸#„Ô#„ê#…#…#…2#…H#…^#…t#…Š#… #…¶#…Ì#…â#…ø#†#†$#†@#†V#†l#†‚#†˜#†®#†Ä#†à#†ö#‡ #‡"#‡8#‡N#‡d#‡z#‡#‡¬#‡Â#‡Ø#‡î#ˆ#ˆ#ˆ0#ˆL#ˆb#ˆx#ˆŽ#ˆ¤#ˆº#ˆÖ#ˆì#‰#‰#‰.#‰D#‰Z#‰p#‰†#‰œ#‰²#‰È#‰Þ#‰ú#Š#Š&#Š<#ŠR#Šh#Š~#Šš#ж#ŠÌ#Šâ#Šø#‹#‹$#‹:#‹P#‹f#‹|#‹’#‹¨#‹¾#‹Ô#‹ê#Œ#Œ#Œ,#ŒB#ŒX#Œn#Œ„#Œš#Œ°#ŒÆ#Œâ#Œø##$#:#P#f#|#˜#®#Ä#Ú#ð#Ž #Ž"#Ž8#ŽN#Žd#Ž€#Ž–#ެ#ŽÈ#Žä#Žú##&#<#R#n#„# #¶#Ì#â#ø##*#@#V#l#‚#ž#´#Ê#à#ö#‘ #‘"#‘8#‘N#‘d#‘z#‘#‘¦#‘Â#‘Ø#‘î#’#’#’0#’F#’\#’r#’ˆ#’ž#’º#’Ð#’æ#’ü#“#“(#“>#“T#“j#“€#“–#“²#“È#“Þ#“ú#”#”&#”<#”R#”h#”~#””#”ª#”À#”Ö#”ì#•#•#•.#•D#•Z#•p#•†#•œ#•²#•È#•Þ#•ô#– #– #–6#–R#–n#–„#–š#–°#–Æ#–Ü#–ò#—#—#—4#—J#—`#—v#—Œ#—¢#—¸#—Î#—ä#—ú#˜#˜&#˜<#˜R#˜h#˜~#˜”#˜ª#˜À#˜Ö#˜ì#™#™#™.#™D#™Z#™p#™†#™œ#™²#™È#™Þ#™ô#š #š #š6#šL#šb#šx#šŽ#š¤#šº#šÐ#šæ#šü#›#›(#›>#›T#›j#›€#›–#›¬#›Â#›Ø#›î#œ#œ#œ0#œF#œ\#œr#œˆ#œž#œ´#œÊ#œà#œö# #"#>#T#j#€#–#¬#Â#Ø#ô#ž #ž #ž6#žL#žb#žx#žŽ#ž¤#žº#žÐ#žæ#žü#Ÿ#Ÿ(#Ÿ>#ŸT#Ÿj#Ÿ†#Ÿœ#Ÿ²#ŸÈ#ŸÞ#Ÿô#  #  # 6# R# h# ~# ”# ª# À# Ö# ì#¡#¡#¡.#¡D#¡Z#¡p#¡†#¡œ#¡¸#¡Ô#¡ê#¢#¢#¢,#¢B#¢^#¢t#¢Š#¢ #¢¶#¢Ì#¢â#¢ø#£#£$#£:#£P#£f#£|#£’#£¨#£Ä#£Ú#£ö#¤ #¤"#¤8#¤N#¤d#¤z#¤#¤¦#¤¼#¤Ø#¤î#¥#¥#¥0#¥F#¥b#¥x#¥Ž#¥¤#¥º#¥Ð#¥æ#¥ü#¦#¦(#¦>#¦T#¦j#¦€#¦–#¦¬#¦Â#¦Ø#¦î#§#§#§0#§F#§\#§r#§ˆ#§ž#§´#§Ê#§à#§ö#¨ #¨"#¨8#¨N#¨d#¨€#¨–#¨¬#¨È#¨ä#¨ú#©#©&#©<#©R#©h#©~#©”#©ª#©À#©Ö#©ì#ª#ª#ª.#ªD#ªZ#ªp#ª†#ªœ#ª²#ªÈ#ªÞ#ªô#« #« #«6#«L#«b#«x#«Ž#«¤#«º#«Ð#«æ#«ü#¬#¬(#¬>#¬T#¬j#¬€#¬–#¬¬#¬Â#¬Ø#¬î#­#­#­0#­F#­\#­r#­ˆ#­ž#­º#­Ð#­æ#­ü#®#®(#®>#®T#®j#®€#®–#®¬#®Â#®Ø#®î#¯#¯#¯0#¯F#¯\#¯r#¯ˆ#¯ž#¯´#¯Ê#¯à#¯ü#°#°(#°>#°T#°j#°€#°–#°¬#°Â#°Ø#°î#±#±#±0#±F#±\#±r#±ˆ#±ž#±´#±Ê#±à#±ö#² #²"#²8#²N#²d#²z#²#²¦#²¼#²Ò#²è#²þ#³#³*#³@#³V#³l#³‚#³˜#³®#³Ä#³Ú#³ð#´#´#´2#´H#´^#´t#´Š#´ #´¶#´Ì#´â#´ø#µ#µ$#µ:#µP#µf#µ|#µ’#µ¨#µ¾#µÔ#µê#¶#¶#¶,#¶B#¶X#¶n#¶„#¶š#¶°#¶Æ#¶Ü#¶ò#·#·#·4#·J#·f#·|#·’#·¨#·¾#·Ô#·ê#¸#¸#¸,#¸B#¸X#¸n#¸„#¸š#¸°#¸Æ#¸Ü#¸ò#¹#¹#¹:#¹P#¹f#¹|#¹’#¹¨#¹¾#¹Ô#¹ê#º#º#º,#ºB#ºX#ºn#º„#ºš#º°#ºÆ#ºÜ#ºò#»#»#»4#»J#»`#»v#»Œ#»¢#»¸#»Î#»ä#»ú#¼#¼&#¼<#¼R#¼h#¼~#¼”#¼ª#¼Æ#¼Ü#¼ò#½#½#½4#½J#½`#½v#½Œ#½¢#½¸#½Î#½ê#¾#¾#¾,#¾B#¾X#¾t#¾#¾¦#¾¼#¾Ò#¾è#¾þ#¿#¿*#¿@#¿V#¿l#¿‚#¿˜#¿®#¿Ä#¿Ú#¿ð#À #À"#À8#ÀN#Àd#Àz#À#À¦#À¼#ÀÒ#Àè#Àþ#Á#Á*#Á@#Á\#Ár#Áˆ#Áž#Á´#ÁÊ#Áæ#Áü#Â#Â.#ÂD#ÂZ#Âp#†#¢#¸#ÂÎ#Âä#Âú#Ã#Ã&#Ã<#ÃR#Ãh#Ã~#Ú#ð#ÃÆ#ÃÜ#Ãò#Ä#Ä#Ä4#ÄJ#Ä`#Äv#ÄŒ#Ĩ#ľ#ÄÔ#Äê#Å#Å#Å2#ÅH#Å^#Åt#ÅŠ#Å #Ŷ#ÅÒ#Åè#Åþ#Æ#Æ0#ÆF#Æ\#Ær#ƈ#Ƥ#ƺ#ÆÖ#Æì#Ç#Ç#Ç.#ÇD#ÇZ#Çp#dž#Çœ#Dz#ÇÈ#Çä#Çú#È#È&#È<#ÈR#Èn#È„#Èš#Ȱ#ÈÆ#ÈÜ#Èò#É#É#É4#ÉP#Éf#É|#É’#É®#ÉÄ#ÉÚ#Éð#Ê#Ê#Ê2#ÊH#Ê^#Êt#ÊŠ#Ê #ʶ#ÊÌ#Êè#Êþ#Ë#Ë*#Ë@#ËV#Ël#Ë‚#˘#Ë®#ËÄ#ËÚ#Ëö#Ì #Ì(#Ì>#ÌT#Ìj#Ì€#Ì–#̬#ÌÂ#ÌÞ#Ìô#Í #Í #Í<#ÍR#Íh#Í~#Í”#ͪ#ÍÀ#ÍÖ#Íì#Î#Î#Î4#ÎJ#Î`#Îv#ÎŒ#΢#θ#ÎÎ#Îä#Îú#Ï#Ï,#ÏB#Ï^#Ït#ÏŠ#Ï #϶#ÏÒ#Ïè#Ïþ#Ð#Ð0#ÐF#Ð\#Ðr#Ј#О#д#ÐÐ#Ðæ#Ðü#Ñ#Ñ(#Ñ>#ÑT#Ñj#Ñ€#Ñ–#Ѭ#ÑÂ#ÑØ#Ñî#Ò#Ò#Ò0#ÒF#Ò\#Òx#ÒŽ#Ò¤#Òº#ÒÐ#Òæ#Òü#Ó#Ó(#Ó>#ÓT#Ój#Ó€#Ó–#Ó¬#ÓÂ#ÓØ#Óô#Ô #Ô #Ô6#ÔL#Ôb#Ôx#ÔŽ#Ô¤#Ôº#ÔÐ#Ôæ#Ôü#Õ#Õ(#Õ>#ÕT#Õj#Õ€#Õ–#Õ¬#ÕÂ#ÕØ#Õî#Ö#Ö#Ö0#ÖF#Ö\#Ör#Öˆ#Öž#Ö´#ÖÊ#Öà#Öö#× #×"#×8#×N#×d#×z#×#צ#×¼#×Ò#×è#×þ#Ø#Ø0#ØF#Øb#Øx#ØŽ#ؤ#غ#ØÐ#Øæ#Øü#Ù#Ù(#Ù>#ÙZ#Ùp#Ù†#Ùœ#Ù²#ÙÈ#ÙÞ#Ùô#Ú #Ú #Ú6#ÚL#Úb#Úx#ÚŽ#Ú¤#Úº#ÚÐ#Úæ#Úü#Û#Û(#Û>#ÛT#Ûj#Û€#Û–#Û¬#ÛÂ#ÛØ#Ûî#Ü#Ü#Ü0#ÜF#Üh#Ü~#Ü”#ܪ#ÜÀ#ÜÖ#Üì#Ý#Ý#Ý.#ÝD#ÝZ#Ýp#݆#Ýœ#ݲ#ÝÈ#ÝÞ#Ýô#Þ #Þ #Þ6#ÞL#Þb#Þx#Þ”#Þª#ÞÀ#ÞÖ#Þì#ß#ß#ß.#ßD#ßZ#ßp#߆#ßœ#ß²#ßÈ#ßÞ#ßô#à #à #à6#àL#àb#àx#àŽ#à¤#àº#àÐ#àæ#àü#á#á(#á>#áT#áj#á€#á–#á¬#áÂ#áØ#áî#â#â#â0#âF#â\#âr#âˆ#âž#â´#âÊ#âà#âö#ã #ã"#ã8#ãN#ãd#ãz#ã#ã¦#ã¼#ãÒ#ãè#ãþ#ä#ä*#äF#ä\#är#äˆ#äž#ä´#äÊ#äà#äö#å #å"#å8#åN#åd#åz#å#å¦#å¼#åÒ#åî#æ#æ#æ0#æF#æ\#ær#æˆ#æž#æ´#æÊ#æà#æö#ç #ç"#ç8#çN#çd#çz#ç#ç¦#çÂ#çØ#çî#è #è #è6#èL#èh#è„#èš#è°#èÆ#èÜ#èò#é#é#é4#éJ#é`#év#éŒ#é¢#é¸#éÎ#éä#éú#ê#ê&#ê<#êR#êh#ê~#êš#ê°#êÆ#êÜ#êò#ë#ë#ë4#ëJ#ë`#ëv#ëŒ#ë¢#ë¸#ëÎ#ëä#ëú#ì#ì&#ì<#ìR#ìh#ì~#ì”#ìª#ìÀ#ìÖ#ìì#í#í#í.#íD#íZ#íp#í†#íœ#í²#íÈ#íÞ#íô#î #î #î6#îL#îb#î~#î”#îª#îÀ#îÖ#îì#ï#ï#ï.#ïD#ïZ#ïp#ï†#ïœ#ï¸#ïÎ#ïä#ïú#ð#ð&#ð<#ðR#ðh#ð~#ð”#ðª#ðÀ#ðÖ#ðì#ñ#ñ#ñ.#ñD#ñZ#ñp#ñ†#ñœ#ñ²#ñÈ#ñÞ#ñô#ò #ò #ò6#òL#òb#òx#òŽ#ò¤#òº#òÐ#òæ#ó#ó#ó.#óD#óZ#óp#ó†#óœ#ó²#óÈ#óÞ#óô#ô #ô #ô6#ôL#ôb#ôx#ôŽ#ô¤#ôº#ôÐ#ôæ#ôü#õ#õ(#õ>#õT#õj#õ€#õ–#õ¬#õÂ#õØ#õî#ö#ö#ö0#öF#ö\#ör#öˆ#öž#ö´#öÊ#öà#öö#÷ #÷(#÷>#÷T#÷j#÷€#÷–#÷¬#÷Â#÷Ø#÷î#ø#ø #ø6#øL#øb#øx#øŽ#ø¤#øº#øÐ#øæ#øü#ù#ù(#ù>#ùT#ùj#ù€#ù–#ù¬#ùÂ#ùØ#ùî#ú#ú#ú0#úF#ú\#úr#úˆ#úž#ú´#úÊ#úà#úö#û #û"#û>#ûT#ûj#û€#û–#û¬#ûÂ#ûØ#ûî#ü#ü#ü0#üF#ü\#ür#üˆ#üž#üº#üÐ#üæ#üü#ý#ý(#ý>#ýT#ýj#ý€#ý–#ý¬#ýÂ#ýØ#ýî#þ #þ #þ6#þL#þb#þx#þŽ#þ¤#þº#þÐ#þæ#þü#ÿ#ÿ(#ÿ>#ÿT#ÿj#ÿ€#ÿ–#ÿ¬#ÿÂ#ÿØ#ÿî$$$0$F$\$x$”$ª$À$Ö$ì$$$.$D$Z$p$†$œ$²$È$Þ$ô$ $ $6$L$b$x$Ž$¤$º$Ð$æ$ü$$.$D$Z$p$†$œ$²$È$Þ$ô$ $ $6$L$b$x$”$ª$À$Ö$ì$$$.$D$Z$p$†$œ$²$È$Þ$ô$ $ $6$R$h$~$”$ª$À$Ö$ì$$$.$D$Z$p$†$œ$²$È$ä$ú$$&$<$R$h$~$”$ª$À$Ö$ì$ $ $ .$ D$ Z$ p$ †$ œ$ ²$ È$ Þ$ ô$ $ $ 6$ L$ b$ x$ Ž$ ¤$ º$ Ð$ æ$ $ $ .$ D$ Z$ p$ †$ œ$ ²$ È$ Þ$ ô$ $ $ 6$ L$ b$ x$ Ž$ ¤$ À$ Ö$ ì$ $ $ .$ D$ Z$ p$ †$ œ$ ²$ È$ Þ$ ô$ $ $6$L$b$x$Ž$¤$º$Ð$æ$ü$$($D$Z$p$†$œ$²$È$Þ$ô$ $ $6$R$h$~$”$ª$À$Ö$ì$$$4$J$`$v$Œ$¢$¸$Î$ä$ú$$&$<$R$h$~$”$°$Æ$Ü$ò$$$4$J$`$v$Œ$¢$¸$Î$ä$ú$$&$<$R$h$~$”$ª$À$Ö$ì$$$4$J$`$v$Œ$¢$¸$Î$ä$ú$$&$B$X$n$„$š$°$Æ$â$ø$$$$:$V$r$ˆ$ž$´$Ê$à$ö$ $"$>$T$j$€$œ$²$Î$ä$ú$$&$<$R$h$~$š$°$Æ$Ü$ò$$$4$J$`$v$Œ$¢$¸$Î$ä$ú$$&$<$R$h$~$”$ª$À$Ö$ì$$$.$D$Z$v$Œ$¢$¸$Î$ä$ú$$&$<$R$h$~$š$°$Æ$Ü$ò$$$4$J$`$v$Œ$¢$¸$Î$ê$$$2$H$^$t$Š$ $¶$Ì$â$ø$ $ $$ :$ P$ f$ |$ ’$ ¨$ ¾$ Ô$ ê$!$!$!2$!H$!^$!t$!Š$! $!¼$!Ò$!è$!þ$"$"*$"@$"V$"l$"‚$"˜$"®$"Ä$"Ú$"ð$#$#$#2$#H$#^$#t$#Š$# $#¶$#Ì$#â$#ø$$$$$$$:$$P$$f$$|$$’$$¨$$¾$$Ô$$ê$%$%$%,$%B$%X$%t$%Š$% $%¶$%Ì$%â$%ø$&$&$$&:$&P$&f$&|$&’$&¨$&¾$&Ô$&ê$'$'$',$'B$'X$'n$'„$'š$'°$'Æ$'Ü$'ò$($($(4$(P$(f$(|$(’$(¨$(¾$(Ú$(ð$)$)$)8$)N$)d$)z$)$)¦$)¼$)Ò$)è$)þ$*$**$*@$*V$*l$*‚$*˜$*®$*Ä$*Ú$*ð$+$+$+2$+H$+^$+t$+Š$+ $+¶$+Ì$+â$+ø$,$,$$,:$,P$,f$,|$,’$,¨$,¾$,Ô$,ð$-$-$-2$-H$-^$-t$-Š$- $-¶$-Ì$-â$-ø$.$.$$.@$.V$.l$.‚$.˜$.®$.Ä$.Ú$.ð$/$/$/2$/H$/^$/t$/Š$/ $/¶$/Ì$/â$/þ$0$0*$0@$0V$0r$0ˆ$0¤$0º$0Ð$0æ$0ü$1$1($1>$1T$1j$1€$1–$1¬$1Â$1Ø$1î$2$2$20$2F$2\$2r$2ˆ$2ž$2´$2Ê$2à$2ö$3 $3"$38$3N$3d$3z$3$3¦$3¼$3Ò$3è$3þ$4$4*$4F$4\$4r$4ˆ$4ž$4´$4Ê$4à$4ö$5 $5"$58$5N$5j$5†$5¢$5¸$5Î$5ä$5ú$6$6&$6B$6X$6n$6„$6š$6°$6Ì$6â$6ø$7$7$$7:$7P$7f$7|$7’$7¨$7¾$7Ô$7ê$8$8$8,$8B$8X$8n$8„$8š$8°$8Æ$8Ü$8ò$9$9$94$9J$9`$9v$9Œ$9¢$9¸$9Î$9ä$9ú$:$:&$:<$:R$:n$:„$:š$:¶$:Ì$:â$:ø$;$;$$;:$;P$;f$;|$;’$;¨$;¾$;Ô$;ê$<$<$<,$$>$>2$>H$>^$>t$>Š$> $>¶$>Ì$>â$>ø$?$?$$?:$?P$?f$?|$?’$?¨$?¾$?Ô$?ê$@$@$@,$@B$@X$@n$@„$@š$@¶$@Ì$@è$@þ$A$A*$A@$AV$Al$A‚$A˜$A®$AÄ$AÚ$Að$B $B"$B8$BN$Bd$Bz$B$B¦$B¼$BÒ$Bè$Bþ$C$C*$C@$CV$Cl$C‚$C˜$C®$CÄ$Cà$Cö$D $D"$D8$DN$Dd$Dz$D$D¦$D¼$DÒ$Dè$Dþ$E$E*$E@$EV$El$Eˆ$Ež$E´$EÊ$Eà$Eö$F $F($F>$FT$Fj$F€$Fœ$F²$FÈ$FÞ$Fô$G $G $G6$GL$Gb$Gx$GŽ$G¤$Gº$GÐ$Gæ$Gü$H$H($H>$HT$Hj$H€$H–$H¬$HÂ$HØ$Hî$I$I$I0$IF$I\$Ir$Iˆ$Iž$I´$IÊ$Ià$Iö$J $J"$J8$JN$Jd$Jz$J$J¦$J¼$JÒ$Jè$K$K$K0$KF$K\$Kr$Kˆ$Kž$K´$KÊ$Kà$Kö$L $L"$L8$LN$Ld$Lz$L$L¦$L¼$LÒ$Lè$Lþ$M$M*$M@$MV$Ml$M‚$M˜$M®$MÄ$MÚ$Mð$N$N$N2$NH$N^$Nt$NŠ$N $N¶$NÌ$Nâ$Nø$O$O$$O:$OP$Of$O|$O’$O¨$O¾$OÔ$Oê$P$P$P,$PB$PX$Pn$P„$Pš$P¶$PÒ$Pî$Q $Q&$QB$Q^$Qz$Q–$Q²$QÎ$Qê$R$R"$R>$RZ$Rv$R’$R®$RÊ$Ræ$S$S$S:$SV$Sr$SŽ$S¤$SÀ$SÜ$Sø$T$T0$TL$Th$T„$T $T¼$TØ$Tô$U$U,$UH$Ud$U€$Uœ$U¸$UÔ$Uð$V $V($VD$V`$V|$V˜$V®$VÊ$Væ$W$W$W:$WV$Wr$WŽ$Wª$WÆ$Wâ$Wþ$X$X6$XR$Xn$XŠ$X¦$XÂ$XÞ$Xú$Y$Y2$YN$Yj$Y†$Y¢$Y¸$YÔ$Yð$Z $Z($ZD$Z`$Z|$Z˜$Z´$ZÐ$Zì$[$[$$[@$[\$[x$[”$[°$[Ì$[è$\$\ $\<$\X$\t$\$\¬$\Â$\Þ$\ú$]$]2$]N$]j$]†$]¢$]¾$]Ú$]ö$^$^.$^J$^f$^‚$^ž$^º$^Ö$^ò$_$_*$_F$_b$_~$_š$_¶$_Ì$_è$`$` $`<$`X$`t$`$`¬$`È$`ä$a$a$a8$aT$ap$aŒ$a¨$aÄ$aà$aü$b$b4$bP$bl$bˆ$b¤$bÀ$bÖ$bò$c$c*$cF$cb$c~$cš$c¶$cÒ$cî$d $d&$dB$d^$dz$d–$d²$dÎ$dê$e$e"$e>$eZ$ev$e’$e®$eÊ$eà$eü$f$f4$fP$fl$fˆ$f¤$fÀ$fÜ$fø$g$g0$gL$gh$g„$g $g¼$gØ$gô$h$h,$hH$hd$h€$hœ$h¸$hÔ$hê$i$i"$i>$iZ$iv$i’$i®$iÊ$iæ$j$j$j:$jV$jr$jŽ$jª$jÆ$jâ$jþ$k$k6$kR$kn$kŠ$k¦$kÂ$kÞ$kô$l$l,$lH$ld$l€$lœ$l¸$lÔ$lð$m $m($mD$m`$m|$m˜$m´$mÐ$mì$n$n$$n@$n\$nx$n”$n°$nÌ$nè$nþ$o$o6$oR$on$oŠ$o¦$oÂ$oÞ$oú$p$p2$pN$pj$p†$p¢$p¾$pÚ$pö$q$q.$qJ$qf$q‚$qž$qº$qÖ$qò$r$r$$r@$r\$rx$r”$r°$rÌ$rè$s$s $s<$sX$st$s$s¬$sÈ$sä$t$t$t8$tT$tp$tŒ$t¨$tÄ$tà$tü$u$u.$uJ$uf$u‚$už$uº$uÖ$uò$v$v*$vF$vb$v~$vš$v¶$vÒ$vî$w $w&$wB$w^$wz$w–$w²$wÎ$wê$x$x$x8$xT$xp$xŒ$x¨$xÄ$xà$xü$y$y4$yP$yl$yˆ$y¤$yÀ$yÜ$yø$z$z0$zL$zh$z„$z $z¼$zØ$zô${${&${B${^${z${–${²${Î${ê$|$|"$|>$|Z$|v$|’$|®$|Ê$|æ$}$}$}:$}V$}r$}Ž$}ª$}Æ$}â$}þ$~$~0$~L$~h$~„$~ $~¼$~Ø$~ô$$,$H$d$€$œ$¸$Ô$ð$€ $€($€D$€`$€|$€˜$€´$€Ð$€ì$$$$:$V$r$Ž$ª$Æ$â$þ$‚$‚6$‚R$‚n$‚Š$‚¦$‚Â$‚Þ$‚ú$ƒ$ƒ2$ƒN$ƒj$ƒ†$ƒ¢$ƒ¾$ƒÚ$ƒö$„$„.$„D$„`$„|$„˜$„´$„Ð$„ì$…$…$$…@$…\$…x$…”$…°$…Ì$…è$†$† $†<$†X$†t$†$†¬$†È$†ä$‡$‡$‡8$‡N$‡j$‡†$‡¢$‡¾$‡Ú$‡ö$ˆ$ˆ.$ˆJ$ˆf$ˆ‚$ˆž$ˆº$ˆÖ$ˆò$‰$‰*$‰F$‰b$‰~$‰š$‰¶$‰Ò$‰î$Š $Š&$ŠB$ŠX$Št$Š$Ь$ŠÈ$Šä$‹$‹$‹8$‹T$‹p$‹Œ$‹¨$‹Ä$‹à$‹ü$Œ$Œ4$ŒP$Œl$Œˆ$Œ¤$ŒÀ$ŒÜ$Œø$$0$L$b$~$š$¶$Ò$î$Ž $Ž&$ŽB$Ž^$Žz$Ž–$޲$ŽÎ$Žê$$"$>$Z$v$’$®$Ê$æ$$$:$V$l$ˆ$¤$À$Ü$ø$‘$‘0$‘L$‘h$‘„$‘ $‘¼$‘Ø$‘ô$’$’,$’H$’d$’€$’œ$’¸$’Ô$’ð$“ $“($“D$“`$“v$“’$“®$“Ê$“æ$”$”$”:$”V$”r$”Ž$”ª$”Æ$”â$”þ$•$•6$•R$•n$•Š$•¦$•Â$•Þ$•ú$–$–2$–N$–j$–€$–œ$–¸$–Ô$–ð$— $—($—D$—`$—|$—˜$—´$—Ð$—ì$˜$˜$$˜@$˜\$˜x$˜”$˜°$˜Ì$˜è$™$™ $™<$™X$™t$™Š$™¦$™Â$™Þ$™ú$š$š2$šN$šj$š†$š¢$š¾$šÚ$šö$›$›.$›J$›f$›‚$›ž$›º$›Ö$›ò$œ$œ*$œF$œb$œ~$œ”$œ°$œÌ$œè$$ $<$X$t$$¬$È$ä$ž$ž$ž8$žT$žp$žŒ$ž¨$žÄ$žà$žü$Ÿ$Ÿ4$ŸP$Ÿl$Ÿˆ$Ÿž$Ÿº$ŸÖ$Ÿò$ $ *$ F$ b$ ~$ š$ ¶$ Ò$ î$¡ $¡&$¡B$¡^$¡z$¡–$¡²$¡Î$¡ê$¢$¢"$¢>$¢Z$¢v$¢’$¢¨$¢Ä$¢à$¢ü$£$£4$£P$£l$£ˆ$£¤$£À$£Ü$£ø$¤$¤0$¤L$¤h$¤„$¤ $¤¼$¤Ø$¤ô$¥$¥,$¥H$¥d$¥€$¥œ$¥²$¥Î$¥ê$¦$¦"$¦>$¦Z$¦v$¦’$¦®$¦Ê$¦æ$§$§$§:$§V$§r$§Ž$§ª$§Æ$§â$§þ$¨$¨6$¨R$¨n$¨Š$¨¦$¨¼$¨Ø$¨ô$©$©,$©H$©d$©€$©œ$©¸$©Ô$©ð$ª $ª($ªD$ª`$ª|$ª˜$ª´$ªÐ$ªì$«$«$$«@$«\$«x$«”$«°$«Æ$«â$«þ$¬$¬6$¬R$¬n$¬Š$¬¦$¬Â$¬Þ$¬ú$­$­2$­N$­j$­†$­¢$­¾$­Ú$­ö$®$®.$®J$®f$®‚$®ž$®º$®Ð$®ì$¯$¯$$¯@$¯\$¯x$¯”$¯°$¯Ì$¯è$°$° $°<$°X$°t$°$°¬$°È$°ä$±$±$±8$±T$±p$±Œ$±¨$±Ä$±Ú$±ö$²$².$²J$²f$²‚$²ž$²º$²Ö$²ò$³$³*$³F$³b$³~$³š$³¶$³Ò$³î$´ $´&$´B$´^$´z$´–$´²$´Î$´ä$µ$µ$µ8$µT$µp$µŒ$µ¨$µÄ$µà$µü$¶$¶4$¶P$¶l$¶ˆ$¶¤$¶À$¶Ü$¶ø$·$·0$·L$·h$·„$· $·¼$·Ø$·î$¸ $¸&$¸B$¸^$¸z$¸–$¸²$¸Î$¸ê$¹$¹"$¹>$¹Z$¹v$¹’$¹®$¹Ê$¹æ$º$º$º:$ºV$ºr$ºŽ$ºª$ºÆ$ºâ$ºø$»$»0$»L$»h$»„$» $»¼$»Ø$»ô$¼$¼,$¼H$¼d$¼€$¼œ$¼¸$¼Ô$¼ð$½ $½($½D$½`$½|$½˜$½´$½Ð$½ì$¾$¾$¾:$¾V$¾r$¾Ž$¾ª$¾Æ$¾â$¾þ$¿$¿6$¿R$¿n$¿Š$¿¦$¿Â$¿Þ$¿ú$À$À2$ÀN$Àj$À†$À¢$À¾$ÀÚ$Àö$Á $Á($ÁD$Á`$Á|$Á˜$Á´$ÁÐ$Áì$Â$Â$$Â@$Â\$Âx$”$°$ÂÌ$Âè$Ã$à $Ã<$ÃX$Ãt$Ã$ì$ÃÈ$Ãä$Ä$Ä$Ä2$ÄN$Äj$Ć$Ä¢$ľ$ÄÚ$Äö$Å$Å.$ÅJ$Åf$Å‚$Åž$ź$ÅÖ$Åò$Æ$Æ*$ÆF$Æb$Æ~$Æš$ƶ$ÆÒ$Æî$Ç $Ç $Ç<$ÇX$Çt$Ç$Ǭ$ÇÈ$Çä$È$È$È8$ÈT$Èp$ÈŒ$Ȩ$ÈÄ$Èà$Èü$É$É4$ÉP$Él$Ɉ$ɤ$ÉÀ$ÉÜ$Éø$Ê$Ê*$ÊF$Êb$Ê~$Êš$ʶ$ÊÒ$Êî$Ë $Ë&$ËB$Ë^$Ëz$Ë–$˲$ËÎ$Ëê$Ì$Ì"$Ì>$ÌZ$Ìv$Ì’$Ì®$ÌÊ$Ìæ$Í$Í$Í4$ÍP$Íl$͈$ͤ$ÍÀ$ÍÜ$Íø$Î$Î0$ÎL$Îh$΄$Π$μ$ÎØ$Îô$Ï$Ï,$ÏH$Ïd$Ï€$Ïœ$ϸ$ÏÔ$Ïð$Ð $Ð($Ð>$ÐZ$Ðv$Ð’$Ю$ÐÊ$Ðæ$Ñ$Ñ$Ñ:$ÑV$Ñr$ÑŽ$Ѫ$ÑÆ$Ñâ$Ñþ$Ò$Ò6$ÒR$Òn$ÒŠ$Ò¦$ÒÂ$ÒÞ$Òú$Ó$Ó2$ÓH$Ód$Ó€$Óœ$Ó¸$ÓÔ$Óð$Ô $Ô($ÔD$Ô`$Ô|$Ô˜$Ô´$ÔÐ$Ôì$Õ$Õ$$Õ@$Õ\$Õx$Õ”$Õ°$ÕÌ$Õè$Ö$Ö $Ö<$ÖR$Ön$ÖŠ$Ö¦$ÖÂ$ÖÞ$Öú$×$×2$×N$×j$׆$×¢$×¾$×Ú$×ö$Ø$Ø.$ØJ$Øf$Ø‚$Øž$غ$ØÖ$Øò$Ù$Ù*$ÙF$Ù\$Ùx$Ù”$Ù°$ÙÌ$Ùè$Ú$Ú $Ú<$ÚX$Út$Ú$Ú¬$ÚÈ$Úä$Û$Û$Û8$ÛT$Ûp$ÛŒ$Û¨$ÛÄ$Ûà$Ûü$Ü$Ü4$ÜP$Üf$Ü‚$Üž$ܺ$ÜÖ$Üò$Ý$Ý*$ÝF$Ýb$Ý~$Ýš$ݶ$ÝÒ$Ýî$Þ $Þ&$ÞB$Þ^$Þz$Þ–$Þ²$ÞÎ$Þê$ß$ß"$ß>$ßZ$ßp$ߌ$ߨ$ßÄ$ßà$ßü$à$à4$àP$àl$àˆ$à¤$àÀ$àÜ$àø$á$á0$áL$áh$á„$á $á¼$áØ$áô$â$â,$âH$âd$âz$â–$â²$âÎ$âê$ã$ã"$ã>$ãZ$ãv$ã’$ã®$ãÊ$ãæ$ä$ä$ä:$äV$är$äŽ$äª$äÆ$äâ$äþ$å$å6$åR$ån$å„$å $å¼$åØ$åô$æ$æ,$æH$æd$æ€$æœ$æ¸$æÔ$æð$ç $ç($çD$ç`$ç|$ç˜$ç´$çÐ$çì$è$è$$è@$è\$èx$èŽ$èª$èÆ$èâ$èþ$é$é6$éR$én$éŠ$é¦$éÂ$éÞ$éú$ê$ê2$êN$êj$ê†$ê¢$ê¾$êÚ$êö$ë$ë.$ëJ$ëf$ë‚$ë˜$ë´$ëÐ$ëì$ì$ì$$ì@$ì\$ìx$ì”$ì°$ìÌ$ìè$í$í $í<$íX$ít$í$í¬$íÈ$íä$î$î$î8$îT$îp$îŒ$î¢$î¾$îÚ$îö$ï$ï.$ïJ$ïf$ï‚$ïž$ïº$ïÖ$ïò$ð$ð*$ðF$ðb$ð~$ðš$ð¶$ðÒ$ðî$ñ $ñ&$ñB$ñ^$ñz$ñ–$ñ¬$ñÈ$ñä$ò$ò$ò8$òT$òp$òŒ$ò¨$òÄ$òà$òü$ó$ó4$óP$ól$óˆ$ó¤$óÀ$óÜ$óø$ô$ô0$ôL$ôh$ô„$ô $ô¶$ôÒ$ôî$õ $õ&$õB$õ^$õz$õ–$õ²$õÎ$õê$ö$ö"$ö>$öZ$öv$ö’$ö®$öÊ$öæ$÷$÷$÷:$÷V$÷r$÷Ž$÷ª$÷À$÷Ü$÷ø$ø$ø0$øL$øh$ø„$ø $ø¼$øØ$øô$ù$ù,$ùH$ùd$ù€$ùœ$ù¸$ùÔ$ùð$ú $ú($úD$ú`$ú|$ú˜$ú´$úÊ$úæ$û$û$û:$ûV$ûr$ûŽ$ûª$ûÆ$ûâ$ûþ$ü$ü6$üR$ün$üŠ$ü¦$üÂ$üÞ$üú$ý$ý2$ýN$ýj$ý†$ý¢$ý¾$ýÔ$ýð$þ $þ($þD$þ`$þ|$þ˜$þ´$þÐ$þì$ÿ$ÿ$$ÿ@$ÿ\$ÿx$ÿ”$ÿ°$ÿÌ$ÿè%% %<%X%t%%¬%È%Þ%ú%%2%N%j%†%¢%¾%Ú%ö%%.%J%f%‚%ž%º%Ö%ò%%*%F%b%~%š%¶%Ò%è%% %<%X%t%%¬%È%ä%%%8%T%p%Œ%¨%Ä%à%ü%%4%P%l%ˆ%¤%À%Ü%ò%%*%F%b%~%š%¶%Ò%î% %&%B%^%z%–%²%Î%ê% % "% >% Z% v% ’% ®% Ê% æ% ü% % 4% P% l% ˆ% ¤% À% Ü% ø% % 0% L% h% „%  % ¼% Ø% ô% % ,% H% d% €% œ% ¸% Ô% ð% % "% >% Z% v% ’% ®% Ê% æ%%%:%V%r%Ž%ª%Æ%â%þ%%6%R%n%Š%¦%Â%Þ%ú%%,%H%d%€%œ%¸%Ô%ð% %(%D%`%|%˜%´%Ð%ì%%$%@%\%x%”%°%Ì%è%%%6%R%n%Š%¦%Â%Þ%ú%%2%N%j%†%¢%¾%Ú%ö%%.%J%f%‚%ž%º%Ö%ò%%$%@%\%x%”%°%Ì%è%% %<%X%t%%¬%È%ä%%%8%T%p%Œ%¨%Ä%à%ü%%.%J%f%‚%ž%º%Ö%ò%%*%F%b%~%š%¶%Ò%î% %&%B%^%z%–%²%Î%ê%%"%8%T%p%Œ%¨%Ä%à%ü%%4%P%l%ˆ%¤%À%Ü%ø%%0%L%h%„% %¼%Ø%ô%%,%B%^%z%–%²%Î%ê% % "% >% Z% v% ’% ®% Ê% æ%!%!%!:%!V%!r%!Ž%!ª%!Æ%!â%!þ%"%"6%"L%"h%"„%" %"¼%"Ø%"ô%#%#,%#H%#d%#€%#œ%#¸%#Ô%#ð%$ %$(%$D%$`%$|%$˜%$´%$Ð%$ì%%%%$%%@%%V%%r%%Ž%%ª%%Æ%%â%%þ%&%&6%&R%&n%&Š%&¦%&Â%&Þ%&ú%'%'2%'N%'j%'†%'¢%'¾%'Ú%'ö%(%(.%(J%(`%(|%(˜%(´%(Ð%(ì%)%)$%)@%)\%)x%)”%)°%)Ì%)è%*%* %*<%*X%*t%*%*¬%*È%*ä%+%+%+8%+T%+j%+†%+¢%+¾%+Ú%+ö%,%,.%,J%,f%,‚%,ž%,º%,Ö%,ò%-%-*%-F%-b%-~%-š%-¶%-Ò%-î%. %.&%.B%.^%.t%.%.¬%.È%.ä%/%/%/8%/T%/p%/Œ%/¨%/Ä%/à%/ü%0%04%0P%0l%0ˆ%0¤%0À%0Ü%0ø%1%10%1L%1h%1~%1š%1¶%1Ò%1î%2 %2&%2B%2^%2z%2–%2²%2Î%2ê%3%3"%3>%3Z%3v%3’%3®%3Ê%3æ%4%4%4:%4V%4r%4ˆ%4¤%4À%4Ü%4ø%5%50%5L%5h%5„%5 %5¼%5Ø%5ô%6%6,%6H%6d%6€%6œ%6¸%6Ô%6ð%7 %7(%7D%7`%7|%7’%7®%7Ê%7æ%8%8%8:%8V%8r%8Ž%8ª%8Æ%8â%8þ%9%96%9R%9n%9Š%9¦%9Â%9Þ%9ú%:%:2%:N%:j%:†%:œ%:¸%:Ô%:ð%; %;(%;D%;`%;|%;˜%;´%;Ð%;ì%<%<$%<@%<\%%>2%>N%>j%>†%>¢%>¾%>Ú%>ö%?%?.%?J%?f%?‚%?ž%?º%?Ö%?ò%@%@*%@F%@b%@~%@š%@°%@Ì%@è%A%A %A<%AX%At%A%A¬%AÈ%Aä%B%B%B8%BT%Bp%BŒ%B¨%BÄ%Bà%Bü%C%C4%CP%Cl%Cˆ%C¤%Cº%CÖ%Cò%D%D*%DF%Db%D~%Dš%D¶%DÒ%Dî%E %E&%EB%E^%Ez%E–%E²%EÎ%Eê%F%F"%F>%FZ%Fv%F’%F®%FÄ%Fà%Fü%G%G4%GP%Gl%Gˆ%G¤%GÀ%GÜ%Gø%H%H0%HL%Hh%H„%H %H¼%HØ%Hô%I%I,%IH%Id%I€%Iœ%I¸%IÎ%Iê%J%J"%J>%JZ%Jv%J’%J®%JÊ%Jæ%K%K%K:%KV%Kr%KŽ%Kª%KÆ%Kâ%Kþ%L%L6%LR%Ln%LŠ%L¦%LÂ%LØ%Lô%M%M,%MH%Md%M€%Mœ%M¸%MÔ%Mð%N %N(%ND%N`%N|%N˜%N´%NÐ%Nì%O%O$%O@%O\%Ox%O”%O°%OÌ%Oâ%Oþ%P%P6%PR%Pn%PŠ%P¦%PÂ%PÞ%Pú%Q%Q2%QN%Qj%Q†%Q¢%Q¾%QÚ%Qö%R%R.%RJ%Rf%R‚%Rž%Rº%RÖ%Rì%S%S$%S@%S\%Sx%S”%S°%SÌ%Sè%T%T %T<%TX%Tt%T%T¬%TÈ%Tä%U%U%U8%UT%Up%UŒ%U¨%UÄ%Uà%Uö%V%V.%VJ%Vf%V‚%Vž%Vº%VÖ%Vò%W%W*%WF%Wb%W~%Wš%W¶%WÒ%Wî%X %X&%XB%X^%Xz%X–%X²%XÎ%Xê%Y%Y%Y8%YT%Yp%YŒ%Y¨%YÄ%Yà%Yü%Z%Z4%ZP%Zl%Zˆ%Z¤%ZÀ%ZÜ%Zø%[%[0%[L%[h%[„%[ %[¼%[Ø%[ô%\ %\&%\B%\^%\z%\–%\²%\Î%\ê%]%]"%]>%]Z%]v%]’%]®%]Ê%]æ%^%^%^:%^V%^r%^Ž%^ª%^Æ%^â%^þ%_%_0%_L%_h%_„%_ %_¼%_Ø%_ô%`%`,%`H%`d%`€%`œ%`¸%`Ô%`ð%a %a(%aD%a`%a|%a˜%a´%aÐ%aì%b%b%b:%bV%br%bŽ%bª%bÆ%bâ%bþ%c%c6%cR%cn%cŠ%c¦%cÂ%cÞ%cú%d%d2%dN%dj%d†%d¢%d¾%dÚ%dö%e%e(%eD%e`%e|%e˜%e´%eÐ%eì%f%f$%f@%f\%fx%f”%f°%fÌ%fè%g%g %g<%gX%gt%g%g¬%gÈ%gä%h%h%h2%hN%hj%h†%h¢%h¾%hÚ%hö%i%i.%iJ%if%i‚%iž%iº%iÖ%iò%j%j*%jF%jb%j~%jš%j¶%jÒ%jî%k %k&%k<%kX%kt%k%k¬%kÈ%kä%l%l%l8%lT%lp%lŒ%l¨%lÄ%là%lü%m%m4%mP%ml%mˆ%m¤%mÀ%mÜ%mø%n%n0%nF%nb%n~%nš%n¶%nÒ%nî%o %o&%oB%o^%oz%o–%o²%oÎ%oê%p%p"%p>%pZ%pv%p’%p®%pÊ%pæ%q%q%q:%qP%ql%qˆ%q¤%qÀ%qÜ%qø%r%r0%rL%rh%r„%r %r¼%rØ%rô%s%s,%sH%sd%s€%sœ%s¸%sÔ%sð%t %t(%tD%tZ%tv%t’%t®%tÊ%tæ%u%u%u:%uV%ur%uŽ%uª%uÆ%uâ%uþ%v%v6%vR%vn%vŠ%v¦%vÂ%vÞ%vú%w%w2%wN%wd%w€%wœ%w¸%wÔ%wð%x %x(%xD%x`%x|%x˜%x´%xÐ%xì%y%y$%y@%y\%yx%y”%y°%yÌ%yè%z%z %z<%zX%zn%zŠ%z¦%zÂ%zÞ%zú%{%{2%{N%{j%{†%{¢%{¾%{Ú%{ö%|%|.%|J%|f%|‚%|ž%|º%|Ö%|ò%}%}*%}F%}b%}x%}”%}°%}Ì%}è%~%~ %~<%~X%~t%~%~¬%~È%~ä%%%8%T%p%Œ%¨%Ä%à%ü%€%€4%€P%€l%€‚%€ž%€º%€Ö%€ò%%*%F%b%~%š%¶%Ò%î%‚ %‚&%‚B%‚^%‚z%‚–%‚²%‚Î%‚ê%ƒ%ƒ"%ƒ>%ƒZ%ƒv%ƒŒ%ƒ¨%ƒÄ%ƒà%ƒü%„%„4%„P%„l%„ˆ%„¤%„À%„Ü%„ø%…%…0%…L%…h%…„%… %…¼%…Ø%…ô%†%†,%†H%†d%†€%†–%†²%†Î%†ê%‡%‡"%‡>%‡Z%‡v%‡’%‡®%‡Ê%‡æ%ˆ%ˆ%ˆ:%ˆV%ˆr%ˆŽ%ˆª%ˆÆ%ˆâ%ˆþ%‰%‰6%‰R%‰n%‰Š%‰ %‰¼%‰Ø%‰ô%Š%Š,%ŠH%Šd%Š€%Šœ%Џ%ŠÔ%Šð%‹ %‹(%‹D%‹`%‹|%‹˜%‹´%‹Ð%‹ì%Œ%Œ$%Œ@%Œ\%Œx%Œ”%Œª%ŒÆ%Œâ%Œþ%%6%R%n%Š%¦%Â%Þ%ú%Ž%Ž2%ŽN%Žj%ކ%Ž¢%޾%ŽÚ%Žö%%.%J%f%‚%ž%´%Ð%ì%%$%@%\%x%”%°%Ì%è%‘%‘ %‘<%‘X%‘t%‘%‘¬%‘È%‘ä%’%’%’8%’T%’p%’Œ%’¨%’¾%’Ú%’ö%“%“.%“J%“f%“‚%“ž%“º%“Ö%“ò%”%”*%”F%”b%”~%”š%”¶%”Ò%”î%• %•&%•B%•^%•z%•–%•²%•È%•ä%–%–%–8%–T%–p%–Œ%–¨%–Ä%–à%–ü%—%—4%—P%—l%—ˆ%—¤%—À%—Ü%—ø%˜%˜0%˜L%˜h%˜„%˜ %˜¼%˜Ò%˜î%™ %™&%™B%™^%™z%™–%™²%™Î%™ê%š%š"%š>%šZ%šv%š’%š®%šÊ%šæ%›%›%›:%›V%›r%›Ž%›ª%›Æ%›Ü%›ø%œ%œ0%œL%œh%œ„%œ %œ¼%œØ%œô%%,%H%d%€%œ%¸%Ô%ð%ž %ž(%žD%ž`%ž|%ž˜%ž´%žÐ%žæ%Ÿ%Ÿ%Ÿ:%ŸV%Ÿr%ŸŽ%Ÿª%ŸÆ%Ÿâ%Ÿþ% % 6% R% n% Š% ¦% Â% Þ% ú%¡%¡2%¡N%¡j%¡†%¡¢%¡¾%¡Ú%¡ð%¢ %¢(%¢D%¢`%¢|%¢˜%¢´%¢Ð%¢ì%£%£$%£@%£\%£x%£”%£°%£Ì%£è%¤%¤ %¤<%¤X%¤t%¤%¤¬%¤È%¤ä%¤ú%¥%¥2%¥N%¥j%¥†%¥¢%¥¾%¥Ú%¥ö%¦%¦.%¦J%¦f%¦‚%¦ž%¦º%¦Ö%¦ò%§%§*%§F%§b%§~%§š%§¶%§Ò%§î%¨%¨ %¨<%¨X%¨t%¨%¨¬%¨È%¨ä%©%©%©8%©T%©p%©Œ%©¨%©Ä%©à%©ü%ª%ª4%ªP%ªl%ªˆ%ª¤%ªÀ%ªÜ%ªø%«%«*%«F%«b%«~%«š%«¶%«Ò%«î%¬ %¬&%¬B%¬^%¬z%¬–%¬²%¬Î%¬ê%­%­"%­>%­Z%­v%­’%­®%­Ê%­æ%®%®%®4%®P%®l%®ˆ%®¤%®À%®Ü%®ø%¯%¯0%¯L%¯h%¯„%¯ %¯¼%¯Ø%¯ô%°%°,%°H%°d%°€%°œ%°¸%°Ô%°ð%± %±"%±>%±Z%±v%±’%±®%±Ê%±æ%²%²%²:%²V%²r%²Ž%²ª%²Æ%²â%²þ%³%³6%³R%³n%³Š%³¦%³Â%³Þ%³ú%´%´,%´H%´d%´€%´œ%´¸%´Ô%´ð%µ %µ(%µD%µ`%µ|%µ˜%µ´%µÐ%µì%¶%¶$%¶@%¶\%¶x%¶”%¶°%¶Ì%¶è%·%· %·6%·R%·n%·Š%·¦%·Â%·Þ%·ú%¸%¸2%¸N%¸j%¸†%¸¢%¸¾%¸Ú%¸ö%¹%¹.%¹J%¹f%¹‚%¹ž%¹º%¹Ö%¹ò%º%º*%º@%º\%ºx%º”%º°%ºÌ%ºè%»%» %»<%»X%»t%»%»¬%»È%»ä%¼%¼%¼8%¼T%¼p%¼Œ%¼¨%¼Ä%¼à%¼ü%½%½4%½J%½f%½‚%½ž%½º%½Ö%½ò%¾%¾*%¾F%¾b%¾~%¾š%¾¶%¾Ò%¾î%¿ %¿&%¿B%¿^%¿z%¿–%¿²%¿Î%¿ê%À%À"%À>%ÀT%Àp%ÀŒ%À¨%ÀÄ%Àà%Àü%Á%Á4%ÁP%Ál%Áˆ%Á¤%ÁÀ%ÁÜ%Áø%Â%Â0%ÂL%Âh%„% %¼%ÂØ%Âô%Ã%Ã,%ÃH%Ã^%Ãz%Ö%ò%ÃÎ%Ãê%Ä%Ä"%Ä>%ÄZ%Äv%Ä’%Ä®%ÄÊ%Äæ%Å%Å%Å:%ÅV%År%ÅŽ%Ū%ÅÆ%Åâ%Åþ%Æ%Æ6%ÆR%Æh%Æ„%Æ %Ƽ%ÆØ%Æô%Ç%Ç,%ÇH%Çd%Ç€%Çœ%Ǹ%ÇÔ%Çð%È %È(%ÈD%È`%È|%Ș%È´%ÈÐ%Èì%É%É$%É@%É\%Ér%ÉŽ%ɪ%ÉÆ%Éâ%Éþ%Ê%Ê6%ÊR%Ên%ÊŠ%ʦ%ÊÂ%ÊÞ%Êú%Ë%Ë2%ËN%Ëj%ˆ%Ë¢%˾%ËÚ%Ëö%Ì%Ì.%ÌJ%Ìf%Ì|%̘%Ì´%ÌÐ%Ìì%Í%Í$%Í@%Í\%Íx%Í”%Ͱ%ÍÌ%Íè%Î%Î %Î<%ÎX%Ît%Î%ά%ÎÈ%Îä%Ï%Ï%Ï8%ÏT%Ïp%φ%Ï¢%Ͼ%ÏÚ%Ïö%Ð%Ð.%ÐJ%Ðf%Ђ%О%к%ÐÖ%Ðò%Ñ%Ñ*%ÑF%Ñb%Ñ~%Ñš%Ѷ%ÑÒ%Ñî%Ò %Ò&%ÒB%Ò^%Òz%Ò%Ò¬%ÒÈ%Òä%Ó%Ó%Ó8%ÓT%Óp%ÓŒ%Ó¨%ÓÄ%Óà%Óü%Ô%Ô4%ÔP%Ôl%Ôˆ%Ô¤%ÔÀ%ÔÜ%Ôø%Õ%Õ0%ÕL%Õh%Õ„%Õš%Õ¶%ÕÒ%Õî%Ö %Ö&%ÖB%Ö^%Öz%Ö–%Ö²%ÖÎ%Öê%×%×"%×>%×Z%×v%×’%×®%×Ê%׿%Ø%Ø%Ø:%ØV%Ør%ØŽ%ؤ%ØÀ%ØÜ%Øø%Ù%Ù0%ÙL%Ùh%Ù„%Ù %Ù¼%ÙØ%Ùô%Ú%Ú,%ÚH%Úd%Ú€%Úœ%Ú¸%ÚÔ%Úð%Û %Û(%ÛD%Û`%Û|%Û˜%Û®%ÛÊ%Ûæ%Ü%Ü%Ü:%ÜV%Ür%ÜŽ%ܪ%ÜÆ%Üâ%Üþ%Ý%Ý6%ÝR%Ýn%ÝŠ%ݦ%ÝÂ%ÝÞ%Ýú%Þ%Þ2%ÞN%Þj%Þ†%Þ¢%Þ¸%ÞÔ%Þð%ß %ß(%ßD%ß`%ß|%ߘ%ß´%ßÐ%ßì%à%à$%à@%à\%àx%à”%à°%àÌ%àè%á%á %á<%áX%át%á%á¬%áÂ%áÞ%áú%â%â2%âN%âj%â†%â¢%â¾%âÚ%âö%ã%ã.%ãJ%ãf%ã‚%ãž%ãº%ãÖ%ãò%ä%ä*%äF%äb%ä~%äš%ä¶%äÌ%äè%å%å %å<%åX%åt%å%å¬%åÈ%åä%æ%æ%æ8%æT%æp%æŒ%æ¨%æÄ%æà%æü%ç%ç4%çP%çl%çˆ%ç¤%çÀ%çÖ%çò%è%è*%èF%èb%è~%èš%è¶%èÒ%èî%é %é&%éB%é^%éz%é–%é²%éÎ%éê%ê%ê"%ê>%êZ%êv%ê’%ê®%êÊ%êà%êü%ë%ë4%ëP%ël%ëˆ%ë¤%ëÀ%ëÜ%ëø%ì%ì0%ìL%ìh%ì„%ì %ì¼%ìØ%ìô%í%í,%íH%íd%í€%íœ%í¸%íÔ%íê%î%î"%î>%îZ%îv%î’%î®%îÊ%îæ%ï%ï%ï:%ïV%ïr%ïŽ%ïª%ïÆ%ïâ%ïþ%ð%ð6%ðR%ðn%ðŠ%ð¦%ðÂ%ðÞ%ðô%ñ%ñ,%ñH%ñd%ñ€%ñœ%ñ¸%ñÔ%ñð%ò %ò(%òD%ò`%ò|%ò˜%ò´%òÐ%òì%ó%ó$%ó@%ó\%óx%ó”%ó°%óÌ%óè%óþ%ô%ô6%ôR%ôn%ôŠ%ô¦%ôÂ%ôÞ%ôú%õ%õ2%õN%õj%õ†%õ¢%õ¾%õÚ%õö%ö%ö.%öJ%öf%ö‚%öž%öº%öÖ%öò%÷%÷$%÷@%÷\%÷x%÷”%÷°%÷Ì%÷è%ø%ø %ø<%øX%øt%ø%ø¬%øÈ%øä%ù%ù%ù8%ùT%ùp%ùŒ%ù¨%ùÄ%ùà%ùü%ú%ú.%úJ%úf%ú‚%úž%úº%úÖ%úò%û%û*%ûF%ûb%û~%ûš%û¶%ûÒ%ûî%ü %ü&%üB%ü^%üz%ü–%ü²%üÎ%üê%ý%ý%ý8%ýT%ýp%ýŒ%ý¨%ýÄ%ýà%ýü%þ%þ4%þP%þl%þˆ%þ¤%þÀ%þÜ%þø%ÿ%ÿ0%ÿL%ÿh%ÿ„%ÿ %ÿ¼%ÿØ%ÿô&&&&B&^&z&–&²&Î&ê&&"&>&Z&v&’&®&Ê&æ&&&:&V&r&Ž&ª&Æ&â&þ&&0&L&h&„& &¼&Ø&ô&&,&H&d&€&œ&¸&Ô&ð& &(&D&`&|&˜&´&Ð&ì&&$&:&V&r&Ž&ª&Æ&â&þ&&6&R&n&Š&¦&Â&Þ&ú&&2&N&j&†&¢&¾&Ú&ö& & .& D& `& |& ˜& ´& Ð& ì& & $& @& \& x& ”& °& Ì& è& & & <& X& t& & ¬& È& ä& & & 8& N& j& †& ¢& ¾& Ú& ö& & .& J& f& ‚& ž& º& Ö& ò&&*&F&b&~&š&¶&Ò&î& &&&B&X&t&&¬&È&ä&&&8&T&p&Œ&¨&Ä&à&ü&&4&P&l&ˆ&¤&À&Ü&ø&&0&L&b&~&š&¶&Ò&î& &&&B&^&z&–&²&Î&ê&&"&>&Z&v&’&®&Ê&æ&&&:&V&l&ˆ&¤&À&Ü&ø&&0&L&h&„& &¼&Ø&ô&&,&H&d&€&œ&¸&Ô&ð& &(&D&`&v&’&®&Ê&æ&&&:&V&r&Ž&ª&Æ&â&þ&&6&R&n&Š&¦&Â&Þ&ú&&2&N&j&€&œ&¸&Ô&ð& &(&D&`&|&˜&´&Ð&ì&&$&@&\&x&”&°&Ì&è&& &<&X&t&Š&¦&Â&Þ&ú&&2&N&j&†&¢&¾&Ú&ö& & .& J& f& ‚& ž& º& Ö& ò&!&!*&!F&!b&!~&!”&!°&!Ì&!è&"&" &"<&"X&"t&"&"¬&"È&"ä&#&#&#T&#p&#Œ&#¨&#Ä&#à&#ü&$&$4&$P&$l&$ˆ&$ž&$º&$Ö&$ò&%&%*&%F&%b&%~&%š&%¶&%Ò&%î&& &&&&&B&&^&&z&&–&&²&&Î&&ê&'&'"&'>&'Z&'v&'’&'¨&'Ä&'à&'ü&(&(4&(P&(l&(ˆ&(¤&(À&(Ü&(ø&)&)0&)L&)h&)„&) &)¼&)Ø&)ô&*&*,&*H&*d&*€&*œ&*²&*Î&*ê&+&+"&+>&+Z&+v&+’&+®&+Ê&+æ&,&,&,:&,V&,r&,Ž&,ª&,Æ&,â&,þ&-&-6&-R&-n&-Š&-¦&-¼&-Ø&-ô&.&.,&.H&.d&.€&.œ&.¸&.Ô&.ð&/ &/(&/D&/`&/|&/˜&/´&/Ð&/ì&0&0$&0@&0\&0x&0”&0°&0Æ&0â&0þ&1&16&1R&1n&1Š&1¦&1Â&1Þ&1ú&2&22&2N&2j&2†&2¢&2¾&2Ú&2ö&3&3.&3J&3f&3‚&3ž&3º&3Ð&3ì&4&4$&4@&4\&4x&4”&4°&4Ì&4è&5&5 &5<&5X&5t&5&5¬&5È&5ä&6&6&68&6T&6p&6Œ&6¨&6Ä&6Ú&6ö&7&7.&7J&7f&7‚&7ž&7º&7Ö&7ò&8&8*&8F&8b&8~&8š&8¶&8Ò&8î&9 &9&&9B&9^&9z&9–&9²&9Î&9ä&:&:&:8&:T&:p&:Œ&:¨&:Ä&:à&:ü&;&;4&;P&;l&;ˆ&;¤&;À&;Ü&;ø&<&<0&&>"&>>&>Z&>v&>’&>®&>Ê&>æ&?&?&?:&?V&?r&?Ž&?ª&?Æ&?â&?ø&@&@0&@L&@h&@„&@ &@¼&@Ø&@ô&A&A,&AH&Ad&A€&Aœ&A¸&AÔ&Að&B &B(&BD&B`&B|&B˜&B´&BÐ&Bì&C&C&C:&CV&Cr&CŽ&Cª&CÆ&Câ&Cþ&D&D6&DR&Dn&DŠ&D¦&DÂ&DÞ&Dú&E&E2&EN&Ej&E†&E¢&E¾&EÚ&Eö&F &F(&FD&F`&F|&F˜&F´&FÐ&Fì&G&G$&G@&G\&Gx&G”&G°&GÌ&Gè&H&H &H<&HX&Ht&H&H¬&HÈ&Hä&I&I&I2&IN&Ij&I†&I¢&I¾&IÚ&Iö&J&J.&JJ&Jf&J‚&Jž&Jº&JÖ&Jò&K&K*&KF&Kb&K~&Kš&K¶&KÒ&Kî&L &L &L<&LX&Lt&L&L¬&LÈ&Lä&M&M&M8&MT&Mp&MŒ&M¨&MÄ&Mà&Mü&N&N4&NP&Nl&Nˆ&N¤&NÀ&NÜ&Nø&O&O*&OF&Ob&O~&Oš&O¶&OÒ&Oî&P &P&&PB&P^&Pz&P–&P²&PÎ&Pê&Q&Q"&Q>&QZ&Qv&Q’&Q®&QÊ&Qæ&R&R&R4&RP&Rl&Rˆ&R¤&RÀ&RÜ&Rø&S&S0&SL&Sh&S„&S &S¼&SØ&Sô&T&T,&TH&Td&T€&Tœ&T¸&TÔ&Tð&U &U(&U>&UZ&Uv&U’&U®&UÊ&Uæ&V&V&V:&VV&Vr&VŽ&Vª&VÆ&Vâ&Vþ&W&W6&WR&Wn&WŠ&W¦&WÂ&WÞ&Wú&X&X2&XH&Xd&X€&Xœ&X¸&XÔ&Xð&Y &Y(&YD&Y`&Y|&Y˜&Y´&YÐ&Yì&Z&Z$&Z@&Z\&Zx&Z”&Z°&ZÌ&Zè&[&[ &[<&[R&[n&[Š&[¦&[Â&[Þ&[ú&\&\2&\N&\j&\†&\¢&\¾&\Ú&\ö&]&].&]J&]f&]‚&]ž&]º&]Ö&]ò&^&^*&^F&^\&^x&^”&^°&^Ì&^è&_&_ &_<&_X&_t&_&_¬&_È&_ä&`&`&`8&`T&`p&`Œ&`¨&`Ä&`à&`ü&a&a4&aP&af&a‚&až&aº&aÖ&aò&b&b*&bF&bb&b~&bš&b¶&bÒ&bî&c &c&&cB&c^&cz&c–&c²&cÎ&cê&d&d"&d>&dZ&dp&dŒ&d¨&dÄ&dà&dü&e&e4&eP&el&eˆ&e¤&eÀ&eÜ&eø&f&f0&fL&fh&f„&f &f¼&fØ&fô&g&g,&gH&gd&gz&g–&g²&gÎ&gê&h&h"&h>&hZ&hv&h’&h®&hÊ&hæ&i&i&i:&iV&ir&iŽ&iª&iÆ&iâ&iþ&j&j6&jR&jn&j„&j &j¼&jØ&jô&k&k,&kH&kd&k€&kœ&k¸&kÔ&kð&l &l(&lD&l`&l|&l˜&l´&lÐ&lì&m&m$&m@&m\&mx&mŽ&mª&mÆ&mâ&mþ&n&n6&nR&nn&nŠ&n¦&nÂ&nÞ&nú&o&o2&oN&oj&o†&o¢&o¾&oÚ&oö&p&p.&pJ&pf&p‚&p˜&p´&pÐ&pì&q&q$&q@&q\&qx&q”&q°&qÌ&qè&r&r &r<&rX&rt&r&r¬&rÈ&rä&s&s&s8&sT&sp&sŒ&s¢&s¾&sÚ&sö&t&t.&tJ&tf&t‚&tž&tº&tÖ&tò&u&u*&uF&ub&u~&uš&u¶&uÒ&uî&v &v&&vB&v^&vz&v–&v¬&vÈ&vä&w&w&w8&wT&wp&wŒ&w¨&wÄ&wà&wü&x&x4&xP&xl&xˆ&x¤&xÀ&xÜ&xø&y&y0&yL&yh&y„&y &y¶&yÒ&yî&z &z&&zB&z^&zz&z–&z²&zÎ&zê&{&{"&{>&{Z&{v&{’&{®&{Ê&{æ&|&|&|:&|V&|r&|Ž&|ª&|À&|Ü&|ø&}&}0&}L&}h&}„&} &}¼&}Ø&}ô&~&~,&~H&~d&~€&~œ&~¸&~Ô&~ð& &(&D&`&|&˜&´&Ê&æ&€&€&€:&€V&€r&€Ž&€ª&€Æ&€â&€þ&&6&R&n&Š&¦&Â&Þ&ú&‚&‚2&‚N&‚j&‚†&‚¢&‚¾&‚Ô&‚ð&ƒ &ƒ(&ƒD&ƒ`&ƒ|&ƒ˜&ƒ´&ƒÐ&ƒì&„&„$&„@&„\&„x&„”&„°&„Ì&„è&…&… &…<&…X&…t&…&…¬&…È&…Þ&…ú&†&†2&†N&†j&††&†¢&†¾&†Ú&†ö&‡&‡.&‡J&‡f&‡‚&‡ž&‡º&‡Ö&‡ò&ˆ&ˆ*&ˆF&ˆb&ˆ~&ˆš&ˆ¶&ˆÒ&ˆè&‰&‰ &‰<&‰X&‰t&‰&‰¬&‰È&‰ä&Š&Š&Š8&ŠT&Šp&ŠŒ&Ѝ&ŠÄ&Šà&Šü&‹&‹4&‹P&‹l&‹ˆ&‹¤&‹À&‹Ü&‹ò&Œ&Œ*&ŒF&Œb&Œ~&Œš&Œ¶&ŒÒ&Œî& &&&B&^&z&–&²&Î&ê&Ž&Ž"&Ž>&ŽZ&Žv&Ž’&Ž®&ŽÊ&Žæ&Žü&&4&P&l&ˆ&¤&À&Ü&ø&&0&L&h&„& &¼&Ø&ô&‘&‘,&‘H&‘d&‘€&‘œ&‘¸&‘Ô&‘ð&’&’"&’>&’Z&’v&’’&’®&’Ê&’æ&“&“&“:&“V&“r&“Ž&“ª&“Æ&“â&“þ&”&”6&”R&”n&”Š&”¦&”Â&”Þ&”ú&•&•,&•H&•d&•€&•œ&•¸&•Ô&•ð&– &–(&–D&–`&–|&–˜&–´&–Ð&–ì&—&—$&—@&—\&—x&—”&—°&—Ì&—è&˜&˜&˜6&˜R&˜n&˜Š&˜¦&˜Â&˜Þ&˜ú&™&™2&™N&™j&™†&™¢&™¾&™Ú&™ö&š&š.&šJ&šf&š‚&šž&šº&šÖ&šò&›&›$&›@&›\&›x&›”&›°&›Ì&›è&œ&œ &œ<&œX&œt&œ&œ¬&œÈ&œä&&&8&T&p&Œ&¨&Ä&à&ü&ž&ž.&žJ&žf&ž‚&žž&žº&žÖ&žò&Ÿ&Ÿ*&ŸF&Ÿb&Ÿ~&Ÿš&Ÿ¶&ŸÒ&Ÿî&  & && B& ^& z& –& ²& Î& ê&¡&¡"&¡8&¡T&¡p&¡Œ&¡¨&¡Ä&¡à&¡ü&¢&¢4&¢P&¢l&¢ˆ&¢¤&¢À&¢Ü&¢ø&£&£0&£L&£h&£„&£ &£¼&£Ø&£ô&¤&¤,&¤B&¤^&¤z&¤–&¤²&¤Î&¤ê&¥&¥"&¥>&¥Z&¥v&¥’&¥®&¥Ê&¥æ&¦&¦&¦:&¦V&¦r&¦Ž&¦ª&¦Æ&¦â&¦þ&§&§6&§L&§h&§„&§ &§¼&§Ø&§ô&¨&¨,&¨H&¨d&¨€&¨œ&¨¸&¨Ô&¨ð&© &©(&©D&©`&©|&©˜&©´&©Ð&©ì&ª&ª$&ª@&ªV&ªr&ªŽ&ªª&ªÆ&ªâ&ªþ&«&«6&«R&«n&«Š&«¦&«Â&«Þ&«ú&¬&¬2&¬N&¬j&¬†&¬¢&¬¾&¬Ú&¬ö&­&­.&­J&­`&­|&­˜&­´&­Ð&­ì&®&®$&®@&®\&®x&®”&®°&®Ì&®è&¯&¯ &¯<&¯X&¯t&¯&¯¬&¯È&¯ä&°&°&°8&°T&°j&°†&°¢&°¾&°Ú&°ö&±&±.&±J&±f&±‚&±ž&±º&±Ö&±ò&²&²*&²F&²b&²~&²š&²¶&²Ò&²î&³ &³&&³B&³^&³t&³&³¬&³È&³ä&´&´&´8&´T&´p&´Œ&´¨&´Ä&´à&´ü&µ&µ4&µP&µl&µˆ&µ¤&µÀ&µÜ&µø&¶&¶0&¶L&¶h&¶~&¶š&¶¶&¶Ò&¶î&· &·&&·B&·^&·z&·–&·²&·Î&·ê&¸&¸"&¸>&¸Z&¸v&¸’&¸®&¸Ê&¸æ&¹&¹&¹:&¹V&¹r&¹ˆ&¹¤&¹À&¹Ü&¹ø&º&º0&ºL&ºh&º„&º &º¼&ºØ&ºô&»&»,&»H&»d&»€&»œ&»¸&»Ô&»ð&¼ &¼(&¼D&¼`&¼|&¼’&¼®&¼Ê&¼æ&½&½&½:&½V&½r&½Ž&½ª&½Æ&½â&½þ&¾&¾6&¾R&¾n&¾Š&¾¦&¾Â&¾Þ&¾ú&¿&¿2&¿N&¿j&¿†&¿œ&¿¸&¿Ô&¿ð&À &À(&ÀD&À`&À|&À˜&À´&ÀÐ&Àì&Á&Á$&Á@&Á\&Áx&Á”&Á°&ÁÌ&Áè&Â& &Â<&ÂX&Ât&Â&¦&ÂÂ&ÂÞ&Âú&Ã&Ã2&ÃN&Ãj&Æ&â&þ&ÃÚ&Ãö&Ä&Ä.&ÄJ&Äf&Ä‚&Äž&ĺ&ÄÖ&Äò&Å&Å*&ÅF&Åb&Å~&Åš&Ű&ÅÌ&Åè&Æ&Æ &Æ<&ÆX&Æt&Æ&Ƭ&ÆÈ&Æä&Ç&Ç&Ç8&ÇT&Çp&ÇŒ&Ǩ&ÇÄ&Çà&Çü&È&È4&ÈP&Èl&Ȉ&Ȥ&Ⱥ&ÈÖ&Èò&É&É*&ÉF&Éb&É~&Éš&ɶ&ÉÒ&Éî&Ê &Ê&&ÊB&Ê^&Êz&Ê–&ʲ&ÊÎ&Êê&Ë&Ë"&Ë>&ËZ&Ëv&Ë’&Ë®&ËÄ&Ëà&Ëü&Ì&Ì4&ÌP&Ìl&̈&̤&ÌÀ&ÌÜ&Ìø&Í&Í0&ÍL&Íh&Í„&Í &ͼ&ÍØ&Íô&Î&Î,&ÎH&Îd&΀&Μ&θ&ÎÎ&Îê&Ï&Ï"&Ï>&ÏZ&Ïv&Ï’&Ï®&ÏÊ&Ïæ&Ð&Ð&Ð:&ÐV&Ðr&ÐŽ&Ъ&ÐÆ&Ðâ&Ðþ&Ñ&Ñ6&ÑR&Ñn&ÑŠ&Ѧ&ÑÂ&ÑØ&Ñô&Ò&Ò,&ÒH&Òd&Ò€&Òœ&Ò¸&ÒÔ&Òð&Ó &Ó(&ÓD&Ó`&Ó|&Ó˜&Ó´&ÓÐ&Óì&Ô&Ô$&Ô@&Ô\&Ôx&Ô”&Ô°&ÔÌ&Ôâ&Ôþ&Õ&Õ6&ÕR&Õn&ÕŠ&Õ¦&ÕÂ&ÕÞ&Õú&Ö&Ö2&ÖN&Öj&Ö†&Ö¢&Ö¾&ÖÚ&Öö&×&×.&×J&×f&ׂ&מ&׺&×Ö&×ì&Ø&Ø$&Ø@&Ø\&Øx&Ø”&ذ&ØÌ&Øè&Ù&Ù &Ù<&ÙX&Ùt&Ù&Ù¬&ÙÈ&Ùä&Ú&Ú&Ú8&ÚT&Úp&ÚŒ&Ú¨&ÚÄ&Úà&Úö&Û&Û.&ÛJ&Ûf&Û‚&Ûž&Ûº&ÛÖ&Ûò&Ü&Ü*&ÜF&Üb&Ü~&Üš&ܶ&ÜÒ&Üî&Ý &Ý&&ÝB&Ý^&Ýz&Ý–&ݲ&ÝÎ&Ýê&Þ&Þ&Þ8&ÞT&Þp&ÞŒ&Þ¨&ÞÄ&Þà&Þü&ß&ß4&ßP&ßl&߈&ߤ&ßÀ&ßÜ&ßø&à&à0&àL&àh&à„&à &à¼&àØ&àô&á &á&&áB&á^&áz&á–&á²&áÎ&áê&â&â"&â>&âZ&âv&â’&â®&âÊ&âæ&ã&ã&ã:&ãV&ãr&ãŽ&ãª&ãÆ&ãâ&ãþ&ä&ä0&äL&äh&ä„&ä &ä¼&äØ&äô&å&å,&åH&åd&å€&åœ&å¸&åÔ&åð&æ &æ(&æD&æ`&æ|&æ˜&æ´&æÐ&æì&ç&ç&ç:&çV&çr&çŽ&çª&çÆ&çâ&çþ&è&è6&èR&èn&èŠ&è¦&èÂ&èÞ&èú&é&é2&éN&éj&é†&é¢&é¾&éÚ&éö&ê&ê(&êD&ê`&ê|&ê˜&ê´&êÐ&êì&ë&ë$&ë@&ë\&ëx&ë”&ë°&ëÌ&ëè&ì&ì &ì<&ìX&ìt&ì&ì¬&ìÈ&ìä&í&í&í2&íN&íj&í†&í¢&í¾&íÚ&íö&î&î.&îJ&îf&î‚&îž&îº&îÖ&îò&ï&ï*&ïF&ïb&ï~&ïš&ï¶&ïÒ&ïî&ð &ð&&ð<&ðX&ðt&ð&ð¬&ðÈ&ðä&ñ&ñ&ñ8&ñT&ñp&ñŒ&ñ¨&ñÄ&ñà&ñü&ò&ò4&òP&òl&òˆ&ò¤&òÀ&òÜ&òø&ó&ó0&óF&ób&ó~&óš&ó¶&óÒ&óî&ô &ô&&ôB&ô^&ôz&ô–&ô²&ôÎ&ôê&õ&õ"&õ>&õZ&õv&õ’&õ®&õÊ&õæ&ö&ö&ö:&öP&öl&öˆ&ö¤&öÀ&öÜ&öø&÷&÷0&÷L&÷h&÷„&÷ &÷¼&÷Ø&÷ô&ø&ø,&øH&ød&ø€&øœ&ø¸&øÔ&øð&ù &ù(&ùD&ùZ&ùv&ù’&ù®&ùÊ&ùæ&ú&ú&ú:&úV&úr&úŽ&úª&úÆ&úâ&úþ&û&û6&ûR&ûn&ûŠ&û¦&ûÂ&ûÞ&ûú&ü&ü2&üN&üd&ü€&üœ&ü¸&üÔ&üð&ý &ý(&ýD&ý`&ý|&ý˜&ý´&ýÐ&ýì&þ&þ$&þ@&þ\&þx&þ”&þ°&þÌ&þè&ÿ&ÿ &ÿ<&ÿX&ÿn&ÿŠ&ÿ¦&ÿÂ&ÿÞ&ÿú''2'N'j'†'¢'¾'Ú'ö''.'J'f'‚'ž'º'Ö'ò''*'F'b'x'”'°'Ì'è'' '<'X't''¬'È'ä'''8'T'p'Œ'¨'Ä'à'ü''4'P'l'‚'ž'º'Ö'ò''*'F'b'~'š'¶'Ò'î' '&'B'^'z'–'²'Î'ê''"'>'Z'v'Œ'¨'Ä'à'ü' ' 4' P' l' ˆ' ¤' À' Ü' ø' ' 0' L' h' „'  ' ¼' Ø' ô' ' ,' H' d' €' –' ²' Î' ê' ' "' >' Z' v' ’' ®' Ê' æ' ' ' :' V' r' Ž' ª' Æ' â' þ''6'R'n'Š' '¼'Ø'ô'','H'd'€'œ'¸'Ô'ð' '('D'`'|'˜'´'Ð'ì''$'@'\'x'”'ª'Æ'â'þ''6'R'n'Š'¦'Â'Þ'ú''2'N'j'†'¢'¾'Ú'ö''.'J'f'‚'ž'´'Ð'ì''$'@'\'x'”'°'Ì'è'' '<'X't''¬'È'ä'''8'T'p'Œ'¨'¾'Ú'ö''.'J'f'‚'ž'º'Ö'ò''*'F'b'~'š'¶'Ò'î' '&'B'^'z'–'²'È'ä'''8'T'p'Œ'¨'Ä'à'ü''4'P'l'ˆ'¤'À'Ü'ø''0'L'h'„' '¼'Ò'î' '&'B'^'z'–'²'Î'ê''"'>'Z'v'’'®'Ê'æ' ' ' :' V' r' Ž' ª' Æ' Ü' ø'!'!0'!L'!h'!„'! '!¼'!Ø'!ô'"'",'"H'"d'"€'"œ'"¸'"Ô'"ð'# '#('#D'#`'#|'#˜'#´'#Ð'#æ'$'$'$:'$V'$r'$Ž'$ª'$Æ'$â'$þ'%'%6'%R'%n'%Š'%¦'%Â'%Þ'%ú'&'&2'&N'&j'&†'&¢'&¾'&Ú'&ð'' ''(''D''`''|''˜''´''Ð''ì'('($'(@'(\'(x'(”'(°'(Ì'(è')') ')<')X')t')')¬')È')ä')ú'*'*2'*N'*j'*†'*¢'*¾'*Ú'*ö'+'+.'+J'+f'+‚'+ž'+º'+Ö'+ò',',*',F',b',~',š',¶',Ò',î'-'- '-<'-X'-t'-'-¬'-È'-ä'.'.'.8'.T'.p'.Œ'.¨'.Ä'.à'.ü'/'/4'/P'/l'/ˆ'/¤'/À'/Ü'/ø'0'0*'0F'0b'0~'0š'0¶'0Ò'0î'1 '1&'1B'1^'1z'1–'1²'1Î'1ê'2'2"'2>'2Z'2v'2’'2®'2Ê'2æ'3'3'34'3P'3l'3ˆ'3¤'3À'3Ü'3ø'4'40'4L'4h'4„'4 '4¼'4Ø'4ô'5'5,'5H'5d'5€'5œ'5¸'5Ô'5ð'6 '6"'6>'6Z'6v'6’'6®'6Ê'6æ'7'7'7:'7V'7r'7Ž'7ª'7Æ'7â'7þ'8'86'8R'8n'8Š'8¦'8Â'8Þ'8ú'9'9,'9H'9d'9€'9œ'9¸'9Ô'9ð': ':(':D':`':|':˜':´':Ð':ì';';$';@';\';x';”';°';Ì';è'<'< '<6''>.'>J'>f'>‚'>ž'>º'>Ö'>ò'?'?*'?@'?\'?x'?”'?°'?Ì'?è'@'@ '@<'@X'@t'@'@¬'@È'@ä'A'A'A8'AT'Ap'AŒ'A¨'AÄ'Aà'Aü'B'B4'BJ'Bf'B‚'Bž'Bº'BÖ'Bò'C'C*'CF'Cb'C~'Cš'C¶'CÒ'Cî'D 'D&'DB'D^'Dz'D–'D²'DÎ'Dê'E'E"'E>'ET'Ep'EŒ'E¨'EÄ'Eà'Eü'F'F4'FP'Fl'Fˆ'F¤'FÀ'FÜ'Fø'G'G0'GL'Gh'G„'G 'G¼'GØ'Gô'H'H,'HH'H^'Hz'H–'H²'HÎ'Hê'I'I"'I>'IZ'Iv'I’'I®'IÊ'Iæ'J'J'J:'JV'Jr'JŽ'Jª'JÆ'Jâ'Jþ'K'K6'KR'Kh'K„'K 'K¼'KØ'Kô'L'L,'LH'Ld'L€'Lœ'L¸'LÔ'Lð'M 'M('MD'M`'M|'M˜'M´'MÐ'Mì'N'N$'N@'N\'Nr'NŽ'Nª'NÆ'Nâ'Nþ'O'O6'OR'On'OŠ'O¦'OÂ'OÞ'Oú'P'P2'PN'Pj'P†'P¢'P¾'PÚ'Pö'Q'Q.'QJ'Qf'Q|'Q˜'Q´'QÐ'Qì'R'R$'R@'R\'Rx'R”'R°'RÌ'Rè'S'S 'S<'SX'St'S'S¬'SÈ'Sä'T'T'T8'TT'Tp'T†'T¢'T¾'TÚ'Tö'U'U.'UJ'Uf'U‚'Už'Uº'UÖ'Uò'V'V*'VF'Vb'V~'Vš'V¶'VÒ'Vî'W 'W&'WB'W^'Wz'W'W¬'WÈ'Wä'X'X'X8'XT'Xp'XŒ'X¨'XÄ'Xà'Xü'Y'Y4'YP'Yl'Yˆ'Y¤'YÀ'YÜ'Yø'Z'Z0'ZL'Zh'Z„'Zš'Z¶'ZÒ'Zî'[ '[&'[B'[^'[z'[–'[²'[Î'[ê'\'\"'\>'\Z'\v'\’'\®'\Ê'\æ']']']:']V']r']Ž']¤']À']Ü']ø'^'^0'^L'^h'^„'^ '^¼'^Ø'^ô'_'_,'_H'_d'_€'_œ'_¸'_Ô'_ð'` '`('`D'``'`|'`˜'`®'`Ê'`æ'a'a'a:'aV'ar'aŽ'aª'aÆ'aâ'aþ'b'b6'bR'bn'bŠ'b¦'bÂ'bÞ'bú'c'c2'cN'cj'c†'c¢'c¸'cÔ'cð'd 'd('dD'd`'d|'d˜'d´'dÐ'dì'e'e$'e@'e\'ex'e”'e°'eÌ'eè'f'f 'f<'fX'ft'f'f¬'fÂ'fÞ'fú'g'g2'gN'gj'g†'g¢'g¾'gÚ'gö'h'h.'hJ'hf'h‚'hž'hº'hÖ'hò'i'i*'iF'ib'i~'iš'i¶'iÌ'iè'j'j 'j<'jX'jt'j'j¬'jÈ'jä'k'k'k8'kT'kp'kŒ'k¨'kÄ'kà'kü'l'l4'lP'll'lˆ'l¤'lÀ'lÖ'lò'm'm*'mF'mb'm~'mš'm¶'mÒ'mî'n 'n&'nB'n^'nz'n–'n²'nÎ'nê'o'o"'o>'oZ'ov'o’'o®'oÊ'oà'oü'p'p4'pP'pl'pˆ'p¤'pÀ'pÜ'pø'q'q0'qL'qh'q„'q 'q¼'qØ'qô'r'r,'rH'rd'r€'rœ'r¸'rÔ'rê's's"'s>'sZ'sv's’'s®'sÊ'sæ't't't:'tV'tr'tŽ'tª'tÆ'tâ'tþ'u'u6'uR'un'uŠ'u¦'uÂ'uÞ'uô'v'v,'vH'vd'v€'vœ'v¸'vÔ'vð'w 'w('wD'w`'w|'w˜'w´'wÐ'wì'x'x$'x@'x\'xx'x”'x°'xÌ'xè'xþ'y'y6'yR'yn'yŠ'y¦'yÂ'yÞ'yú'z'z2'zN'zj'z†'z¢'z¾'zÚ'zö'{'{.'{J'{f'{‚'{ž'{º'{Ö'{ò'|'|$'|@'|\'|x'|”'|°'|Ì'|è'}'} '}<'}X'}t'}'}¬'}È'}ä'~'~'~8'~T'~p'~Œ'~¨'~Ä'~à'~ü''.'J'f'‚'ž'º'Ö'ò'€'€*'€F'€b'€~'€š'€¶'€Ò'€î' '&'B'^'z'–'²'Î'ê'‚'‚'‚8'‚T'‚p'‚Œ'‚¨'‚Ä'‚à'‚ü'ƒ'ƒ4'ƒP'ƒl'ƒˆ'ƒ¤'ƒÀ'ƒÜ'ƒø'„'„0'„L'„h'„„'„ '„¼'„Ø'„ô'…'…&'…B'…^'…z'…–'…²'…Î'…ê'†'†"'†>'†Z'†v'†’'†®'†Ê'†æ'‡'‡'‡:'‡V'‡r'‡Ž'‡ª'‡Æ'‡â'‡þ'ˆ'ˆ0'ˆL'ˆh'ˆ„'ˆ 'ˆ¼'ˆØ'ˆô'‰'‰,'‰H'‰d'‰€'‰œ'‰¸'‰Ô'‰ð'Š 'Š('ŠD'Š`'Š|'Š˜'Š´'ŠÐ'Šì'‹'‹$'‹:'‹V'‹r'‹Ž'‹ª'‹Æ'‹â'‹þ'Œ'Œ6'ŒR'Œn'ŒŠ'Œ¦'ŒÂ'ŒÞ'Œú''2'N'j'†'¢'¾'Ú'ö'Ž'Ž.'ŽD'Ž`'Ž|'Ž˜'Ž´'ŽÐ'Žì''$'@'\'x'”'°'Ì'è'' '<'X't''¬'È'ä'‘'‘'‘8'‘N'‘j'‘†'‘¢'‘¾'‘Ú'‘ö'’'’.'’J'’f'’‚'’ž'’º'’Ö'’ò'“'“*'“F'“b'“~'“š'“¶'“Ò'“î'” '”&'”B'”X'”t'”'”¬'”È'”ä'•'•'•8'•T'•p'•Œ'•¨'•Ä'•à'•ü'–'–4'–P'–l'–ˆ'–¤'–À'–Ü'–ø'—'—0'—L'—b'—~'—š'—¶'—Ò'—î'˜ '˜&'˜B'˜^'˜z'˜–'˜²'˜Î'˜ê'™'™"'™>'™Z'™v'™’'™®'™Ê'™æ'š'š'š:'šV'šl'šˆ'š¤'šÀ'šÜ'šø'›'›0'›L'›h'›„'› '›¼'›Ø'›ô'œ'œ,'œH'œd'œ€'œœ'œ¸'œÔ'œð' '('D'`'v'’'®'Ê'æ'ž'ž'ž:'žV'žr'žŽ'žª'žÆ'žâ'žþ'Ÿ'Ÿ6'ŸR'Ÿn'ŸŠ'Ÿ¦'ŸÂ'ŸÞ'Ÿú' ' 2' N' j' €' œ' ¸' Ô' ð'¡ '¡('¡D'¡`'¡|'¡˜'¡´'¡Ð'¡ì'¢'¢$'¢@'¢\'¢x'¢”'¢°'¢Ì'¢è'£'£ '£<'£X'£t'£Š'£¦'£Â'£Þ'£ú'¤'¤2'¤N'¤j'¤†'¤¢'¤¾'¤Ú'¤ö'¥'¥.'¥J'¥f'¥‚'¥ž'¥º'¥Ö'¥ò'¦'¦*'¦F'¦b'¦~'¦”'¦°'¦Ì'¦è'§'§ '§<'§X'§t'§'§¬'§È'§ä'¨'¨'¨8'¨T'¨p'¨Œ'¨¨'¨Ä'¨à'¨ü'©'©4'©P'©l'©ˆ'©ž'©º'©Ö'©ò'ª'ª*'ªF'ªb'ª~'ªš'ª¶'ªÒ'ªî'« '«&'«B'«^'«z'«–'«²'«Î'«ê'¬'¬"'¬>'¬Z'¬v'¬’'¬¨'¬Ä'¬à'¬ü'­'­4'­P'­l'­ˆ'­¤'­À'­Ü'­ø'®'®0'®L'®h'®„'® '®¼'®Ø'®ô'¯'¯,'¯H'¯d'¯€'¯œ'¯²'¯Î'¯ê'°'°"'°>'°Z'°v'°’'°®'°Ê'°æ'±'±'±:'±V'±r'±Ž'±ª'±Æ'±â'±þ'²'²6'²R'²n'²Š'²¦'²¼'²Ø'²ô'³'³,'³H'³d'³€'³œ'³¸'³Ô'³ð'´ '´('´D'´`'´|'´˜'´´'´Ð'´ì'µ'µ$'µ@'µ\'µx'µ”'µ°'µÆ'µâ'µþ'¶'¶6'¶R'¶n'¶Š'¶¦'¶Â'¶Þ'¶ú'·'·2'·N'·j'·†'·¢'·¾'·Ú'·ö'¸'¸.'¸J'¸f'¸‚'¸ž'¸º'¸Ð'¸ì'¹'¹$'¹@'¹\'¹x'¹”'¹°'¹Ì'¹è'º'º 'º<'ºX'ºt'º'º¬'ºÈ'ºä'»'»'»8'»T'»p'»Œ'»¨'»Ä'»Ú'»ö'¼'¼.'¼J'¼f'¼‚'¼ž'¼º'¼Ö'¼ò'½'½*'½F'½b'½~'½š'½¶'½Ò'½î'¾ '¾&'¾B'¾^'¾z'¾–'¾²'¾Î'¾ä'¿'¿'¿8'¿T'¿p'¿Œ'¿¨'¿Ä'¿à'¿ü'À'À4'ÀP'Àl'Àˆ'À¤'ÀÀ'ÀÜ'Àø'Á'Á0'ÁL'Áh'Á„'Á 'Á¼'ÁØ'Áî' 'Â&'ÂB'Â^'Âz'–'²'ÂÎ'Âê'Ã'Ã"'Ã>'ÃZ'Ãv'Ã’'î'ÃÊ'Ãæ'Ä'Ä'Ä:'ÄV'Är'ÄŽ'Ī'ÄÆ'Äâ'Äø'Å'Å0'ÅL'Åh'Å„'Å 'ż'ÅØ'Åô'Æ'Æ,'ÆH'Æd'Æ€'Æœ'Ƹ'ÆÔ'Æð'Ç 'Ç('ÇD'Ç`'Ç|'ǘ'Ç´'ÇÐ'Çì'È'È'È:'ÈV'Èr'ÈŽ'Ȫ'ÈÆ'Èâ'Èþ'É'É6'ÉR'Én'ÉŠ'ɦ'ÉÂ'ÉÞ'Éú'Ê'Ê2'ÊN'Êj'ʆ'Ê¢'ʾ'ÊÚ'Êö'Ë 'Ë('ËD'Ë`'Ë|'˘'Ë´'ËÐ'Ëì'Ì'Ì$'Ì@'Ì\'Ìx'Ì”'̰'ÌÌ'Ìè'Í'Í 'Í<'ÍX'Ít'Í'ͬ'ÍÈ'Íä'Î'Î'Î2'ÎN'Îj'Ά'΢'ξ'ÎÚ'Îö'Ï'Ï.'ÏJ'Ïf'Ï‚'Ïž'Ϻ'ÏÖ'Ïò'Ð'Ð*'ÐF'Ðb'Ð~'К'ж'ÐÒ'Ðî'Ñ 'Ñ 'Ñ<'ÑX'Ñt'Ñ'Ѭ'ÑÈ'Ñä'Ò'Ò'Ò8'ÒT'Òp'ÒŒ'Ò¨'ÒÄ'Òà'Òü'Ó'Ó4'ÓP'Ól'Óˆ'Ó¤'ÓÀ'ÓÜ'Óø'Ô'Ô*'ÔF'Ôb'Ô~'Ôš'Ô¶'ÔÒ'Ôî'Õ 'Õ&'ÕB'Õ^'Õz'Õ–'Õ²'ÕÎ'Õê'Ö'Ö"'Ö>'ÖZ'Öv'Ö’'Ö®'ÖÊ'Öæ'×'×'×4'×P'×l'׈'פ'×À'×Ü'×ø'Ø'Ø0'ØL'Øh'Ø„'Ø 'ؼ'ØØ'Øô'Ù'Ù,'ÙH'Ùd'Ù€'Ùœ'Ù¸'ÙÔ'Ùð'Ú 'Ú('Ú>'ÚZ'Úv'Ú’'Ú®'ÚÊ'Úæ'Û'Û'Û:'ÛV'Ûr'ÛŽ'Ûª'ÛÆ'Ûâ'Ûþ'Ü'Ü6'ÜR'Ün'ÜŠ'ܦ'ÜÂ'ÜÞ'Üú'Ý'Ý2'ÝH'Ýd'Ý€'Ýœ'ݸ'ÝÔ'Ýð'Þ 'Þ('ÞD'Þ`'Þ|'Þ˜'Þ´'ÞÐ'Þì'ß'ß$'ß@'ß\'ßx'ß”'ß°'ßÌ'ßè'à'à 'à<'àR'àn'àŠ'à¦'àÂ'àÞ'àú'á'á2'áN'áj'á†'á¢'á¾'áÚ'áö'â'â.'âJ'âf'â‚'âž'âº'âÖ'âò'ã'ã*'ãF'ã\'ãx'ã”'ã°'ãÌ'ãè'ä'ä 'ä<'äX'ät'ä'ä¬'äÈ'ää'å'å'å8'åT'åp'åŒ'å¨'åÄ'åà'åü'æ'æ4'æP'æf'æ‚'æž'æº'æÖ'æò'ç'ç*'çF'çb'ç~'çš'ç¶'çÒ'çî'è 'è&'èB'è^'èz'è–'è²'èÎ'èê'é'é"'é>'éZ'ép'éŒ'é¨'éÄ'éà'éü'ê'ê4'êP'êl'êˆ'ê¤'êÀ'êÜ'êø'ë'ë0'ëL'ëh'ë„'ë 'ë¼'ëØ'ëô'ì'ì,'ìH'ìd'ìz'ì–'ì²'ìÎ'ìê'í'í"'í>'íZ'ív'í’'í®'íÊ'íæ'î'î'î:'îV'îr'îŽ'îª'îÆ'îâ'îþ'ï'ï6'ïR'ïn'ï„'ï 'ï¼'ïØ'ïô'ð'ð,'ðH'ðd'ð€'ðœ'ð¸'ðÔ'ðð'ñ 'ñ('ñD'ñ`'ñ|'ñ˜'ñ´'ñÐ'ñì'ò'ò$'ò@'ò\'òx'òŽ'òª'òÆ'òâ'òþ'ó'ó6'óR'ón'óŠ'ó¦'óÂ'óÞ'óú'ô'ô2'ôN'ôj'ô†'ô¢'ô¾'ôÚ'ôö'õ'õ.'õJ'õf'õ‚'õ˜'õ´'õÐ'õì'ö'ö$'ö@'ö\'öx'ö”'ö°'öÌ'öè'÷'÷ '÷<'÷X'÷t'÷'÷¬'÷È'÷ä'ø'ø'ø8'øT'øp'øŒ'ø¢'ø¾'øÚ'øö'ù'ù.'ùJ'ùf'ù‚'ùž'ùº'ùÖ'ùò'ú'ú*'úF'úb'ú~'úš'ú¶'úÒ'úî'û 'û&'ûB'û^'ûz'û–'û¬'ûÈ'ûä'ü'ü'ü8'üT'üp'üŒ'ü¨'üÄ'üà'üü'ý'ý4'ýP'ýl'ýˆ'ý¤'ýÀ'ýÜ'ýø'þ'þ0'þL'þh'þ„'þ 'þ¶'þÒ'þî'ÿ 'ÿ&'ÿB'ÿ^'ÿz'ÿ–'ÿ²'ÿÎ'ÿê(("(>(Z(v(’(®(Ê(æ(((:(V(r(Ž(ª(À(Ü(ø((0(L(h(„( (¼(Ø(ô((,(H(d(€(œ(¸(Ô(ð( (((D(`(|(˜(´(Ê(æ(((:(V(r(Ž(ª(Æ(â(þ((6(R(n(Š(¦(Â(Þ(ú((2(N(j(†(¢(¾(Ô(ð( (((D(`(|(˜(´(Ð(ì( ( $( @( \( x( ”( °( Ì( è( ( ( <( X( t( ( ¬( È( Þ( ú( ( 2( N( j( †( ¢( ¾( Ú( ö( ( .( J( f( ‚( ž( º( Ö( ò( ( *( F( b( ~( š( ¶( Ò( è(( (<(X(t((¬(È(ä(((8(T(p(Œ(¨(Ä(à(ü((4(P(l(ˆ(¤(À(Ü(ò((*(F(b(~(š(¶(Ò(î( (&(B(^(z(–(²(Î(ê(("(>(Z(v(’(®(Ê(æ(ü((4(P(l(ˆ(¤(À(Ü(ø((0(L(h(„( (¼(Ø(ô((,(H(d(€(œ(¸(Ô(ð(("(>(Z(v(’(®(Ê(æ(((:(V(r(Ž(ª(Æ(â(þ((6(R(n(Š(¦(Â(Þ(ú((,(H(d(€(œ(¸(Ô(ð( (((D(`(|(˜(´(Ð(ì(($(@(\(x(”(°(Ì(è(((6(R(n(Š(¦(Â(Þ(ú((2(N(j(†(¢(¾(Ú(ö((.(J(f(‚(ž(º(Ö(ò( ( $( @( \( x( ”( °( Ì( è(!(! (!<(!X(!t(!(!¬(!È(!ä("("("8("T("p("Œ("¨("Ä("à("ü(#(#.(#J(#f(#‚(#ž(#º(#Ö(#ò($($*($F($b($~($š($¶($Ò($î(% (%&(%B(%^(%z(%–(%²(%Î(%ê(&(&"(&8(&T(&p(&Œ(&¨(&Ä(&à(&ü('('4('P('l('ˆ('¤('À('Ü('ø((((0((L((h((„(( ((¼((Ø((ô()(),()B()^()z()–()²()Î()ê(*(*"(*>(*Z(*v(*’(*®(*Ê(*æ(+(+(+:(+V(+r(+Ž(+ª(+Æ(+â(+þ(,(,6(,L(,h(,„(, (,¼(,Ø(,ô(-(-,(-H(-d(-€(-œ(-¸(-Ô(-ð(. (.((.D(.`(.|(.˜(.´(.Ð(.ì(/(/$(/@(/V(/r(/Ž(/ª(/Æ(/â(/þ(0(06(0R(0n(0Š(0¦(0Â(0Þ(0ú(1(12(1N(1j(1†(1¢(1¾(1Ú(1ö(2(2.(2J(2`(2|(2˜(2´(2Ð(2ì(3(3$(3@(3\(3x(3”(3°(3Ì(3è(4(4 (4<(4X(4t(4(4¬(4È(4ä(5(5(58(5T(5j(5†(5¢(5¾(5Ú(5ö(6(6.(6J(6f(6‚(6ž(6º(6Ö(6ò(7(7*(7F(7b(7~(7š(7¶(7Ò(7î(8 (8&(8B(8^(8t(8(8¬(8È(8ä(9(9(98(9T(9p(9Œ(9¨(9Ä(9à(9ü(:(:4(:P(:l(:ˆ(:¤(:À(:Ü(:ø(;(;0(;L(;h(;~(;š(;¶(;Ò(;î(< (<&((=Z(=v(=’(=®(=Ê(=æ(>(>(>:(>V(>r(>ˆ(>¤(>À(>Ü(>ø(?(?0(?L(?h(?„(? (?¼(?Ø(?ô(@(@,(@H(@d(@€(@œ(@¸(@Ô(@ð(A (A((AD(A`(A|(A’(A®(AÊ(Aæ(B(B(B:(BV(Br(BŽ(Bª(BÆ(Bâ(Bþ(C(C6(CR(Cn(CŠ(C¦(CÂ(CÞ(Cú(D(D2(DN(Dj(D†(Dœ(D¸(DÔ(Dð(E (E((ED(E`(E|(E˜(E´(EÐ(Eì(F(F$(F@(F\(Fx(F”(F°(FÌ(Fè(G(G (G<(GX(Gt(G(G¦(GÂ(GÞ(Gú(H(H2(HN(Hj(H†(H¢(H¾(HÚ(Hö(I(I.(IJ(If(I‚(Iž(Iº(IÖ(Iò(J(J*(JF(Jb(J~(Jš(J°(JÌ(Jè(K(K (K<(KX(Kt(K(K¬(KÈ(Kä(L(L(L8(LT(Lp(LŒ(L¨(LÄ(Là(Lü(M(M4(MP(Ml(Mˆ(M¤(Mº(MÖ(Mò(N(N*(NF(Nb(N~(Nš(N¶(NÒ(Nî(O (O&(OB(O^(Oz(O–(O²(OÎ(Oê(P(P"(P>(PZ(Pv(P’(P®(PÄ(Pà(Pü(Q(Q4(QP(Ql(Qˆ(Q¤(QÀ(QÜ(Qø(R(R0(RL(Rh(R„(R (R¼(RØ(Rô(S(S,(SH(Sd(S€(Sœ(S¸(SÎ(Sê(T(T"(T>(TZ(Tv(T’(T®(TÊ(Tæ(U(U(U:(UV(Ur(UŽ(Uª(UÆ(Uâ(Uþ(V(V6(VR(Vn(VŠ(V¦(VÂ(VØ(Vô(W(W,(WH(Wd(W€(Wœ(W¸(WÔ(Wð(X (X((XD(X`(X|(X˜(X´(XÐ(Xì(Y(Y$(Y@(Y\(Yx(Y”(Y°(YÌ(Yâ(Yþ(Z(Z6(ZR(Zn(ZŠ(Z¦(ZÂ(ZÞ(Zú([([2([N([j([†([¢([¾([Ú([ö(\(\.(\J(\f(\‚(\ž(\º(\Ö(\ì(](]$(]@(]\(]x(]”(]°(]Ì(]è(^(^ (^<(^X(^t(^(^¬(^È(^ä(_(_(_8(_T(_p(_Œ(_¨(_Ä(_à(_ö(`(`.(`J(`f(`‚(`ž(`º(`Ö(`ò(a(a*(aF(ab(a~(aš(a¶(aÒ(aî(b (b&(bB(b^(bz(b–(b²(bÎ(bê(c(c(c8(cT(cp(cŒ(c¨(cÄ(cà(cü(d(d4(dP(dl(dˆ(d¤(dÀ(dÜ(dø(e(e0(eL(eh(e„(e (e¼(eØ(eô(f (f&(fB(f^(fz(f–(f²(fÎ(fê(g(g"(g>(gZ(gv(g’(g®(gÊ(gæ(h(h(h:(hV(hr(hŽ(hª(hÆ(hâ(hþ(i(i0(iL(ih(i„(i (i¼(iØ(iô(j(j,(jH(jd(j€(jœ(j¸(jÔ(jð(k (k((kD(k`(k|(k˜(k´(kÐ(kì(l(l(l:(lV(lr(lŽ(lª(lÆ(lâ(lþ(m(m6(mR(mn(mŠ(m¦(mÂ(mÞ(mú(n(n2(nN(nj(n†(n¢(n¾(nÚ(nö(o(o((oD(o`(o|(o˜(o´(oÐ(oì(p(p$(p@(p\(px(p”(p°(pÌ(pè(q(q (q<(qX(qt(q(q¬(qÈ(qä(r(r(r2(rN(rj(r†(r¢(r¾(rÚ(rö(s(s.(sJ(sf(s‚(sž(sº(sÖ(sò(t(t*(tF(tb(t~(tš(t¶(tÒ(tî(u (u&(u<(uX(ut(u(u¬(uÈ(uä(v(v(v8(vT(vp(vŒ(v¨(vÄ(và(vü(w(w4(wP(wl(wˆ(w¤(wÀ(wÜ(wø(x(x0(xF(xb(x~(xš(x¶(xÒ(xî(y (y&(yB(y^(yz(y–(y²(yÎ(yê(z(z"(z>(zZ(zv(z’(z®(zÊ(zæ({({({:({P({l({ˆ({¤({À({Ü({ø(|(|0(|L(|h(|„(| (|¼(|Ø(|ô(}(},(}H(}d(}€(}œ(}¸(}Ô(}ð(~ (~((~D(~Z(~v(~’(~®(~Ê(~æ(((:(V(r(Ž(ª(Æ(â(þ(€(€6(€R(€n(€Š(€¦(€Â(€Þ(€ú((2(N(d(€(œ(¸(Ô(ð(‚ (‚((‚D(‚`(‚|(‚˜(‚´(‚Ð(‚ì(ƒ(ƒ$(ƒ@(ƒ\(ƒx(ƒ”(ƒ°(ƒÌ(ƒè(„(„ („<(„X(„n(„Š(„¦(„Â(„Þ(„ú(…(…2(…N(…j(…†(…¢(…¾(…Ú(…ö(†(†.(†J(†f(†‚(†ž(†º(†Ö(†ò(‡(‡*(‡F(‡b(‡x(‡”(‡°(‡Ì(‡è(ˆ(ˆ (ˆ<(ˆX(ˆt(ˆ(ˆ¬(ˆÈ(ˆä(‰(‰(‰8(‰T(‰p(‰Œ(‰¨(‰Ä(‰à(‰ü(Š(Š4(ŠP(Šl(Š‚(Šž(Šº(ŠÖ(Šò(‹(‹*(‹F(‹b(‹~(‹š(‹¶(‹Ò(‹î(Œ (Œ&(ŒB(Œ^(Œz(Œ–(Œ²(ŒÎ(Œê(("(>(Z(v(Œ(¨(Ä(à(ü(Ž(Ž4(ŽP(Žl(Žˆ(ޤ(ŽÀ(ŽÜ(Žø((0(L(h(„( (¼(Ø(ô((,(H(d(€(–(²(Î(ê(‘(‘"(‘>(‘Z(‘v(‘’(‘®(‘Ê(‘æ(’(’(’:(’V(’r(’Ž(’ª(’Æ(’â(’þ(“(“6(“R(“n(“Š(“ (“¼(“Ø(“ô(”(”,(”H(”d(”€(”œ(”¸(”Ô(”ð(• (•((•D(•`(•|(•˜(•´(•Ð(•ì(–(–$(–@(–\(–x(–”(–ª(–Æ(–â(–þ(—(—6(—R(—n(—Š(—¦(—Â(—Þ(—ú(˜(˜2(˜N(˜j(˜†(˜¢(˜¾(˜Ú(˜ö(™(™.(™J(™f(™‚(™ž(™´(™Ð(™ì(š(š$(š@(š\(šx(š”(š°(šÌ(šè(›(› (›<(›X(›t(›(›¬(›È(›ä(œ(œ(œ8(œT(œp(œŒ(œ¨(œ¾(œÚ(œö((.(J(f(‚(ž(º(Ö(ò(ž(ž*(žF(žb(ž~(žš(ž¶(žÒ(žî(Ÿ (Ÿ&(ŸB(Ÿ^(Ÿz(Ÿ–(Ÿ²(ŸÈ(Ÿä( ( ( 8( T( p( Œ( ¨( Ä( à( ü(¡(¡4(¡P(¡l(¡ˆ(¡¤(¡À(¡Ü(¡ø(¢(¢0(¢L(¢h(¢„(¢ (¢¼(¢Ò(¢î(£ (£&(£B(£^(£z(£–(£²(£Î(£ê(¤(¤"(¤>(¤Z(¤v(¤’(¤®(¤Ê(¤æ(¥(¥(¥:(¥V(¥r(¥Ž(¥ª(¥Æ(¥Ü(¥ø(¦(¦0(¦L(¦h(¦„(¦ (¦¼(¦Ø(¦ô(§(§,(§H(§d(§€(§œ(§¸(§Ô(§ð(¨ (¨((¨D(¨`(¨|(¨˜(¨´(¨Ð(¨æ(©(©(©:(©V(©r(©Ž(©ª(©Æ(©â(©þ(ª(ª6(ªR(ªn(ªŠ(ª¦(ªÂ(ªÞ(ªú(«(«2(«N(«j(«†(«¢(«¾(«Ú(«ð(¬ (¬((¬D(¬`(¬|(¬˜(¬´(¬Ð(¬ì(­(­$(­@(­\(­x(­”(­°(­Ì(­è(®(® (®<(®X(®t(®(®¬(®È(®ä(®ú(¯(¯2(¯N(¯j(¯†(¯¢(¯¾(¯Ú(¯ö(°(°.(°J(°f(°‚(°ž(°º(°Ö(°ò(±(±*(±F(±b(±~(±š(±¶(±Ò(±î(²(² (²<(²X(²t(²(²¬(²È(²ä(³(³(³8(³T(³p(³Œ(³¨(³Ä(³à(³ü(´(´4(´P(´l(´ˆ(´¤(´À(´Ü(´ø(µ(µ*(µF(µb(µ~(µš(µ¶(µÒ(µî(¶ (¶&(¶B(¶^(¶z(¶–(¶²(¶Î(¶ê(·(·"(·>(·Z(·v(·’(·®(·Ê(·æ(¸(¸(¸4(¸P(¸l(¸ˆ(¸¤(¸À(¸Ü(¸ø(¹(¹0(¹L(¹h(¹„(¹ (¹¼(¹Ø(¹ô(º(º,(ºH(ºd(º€(ºœ(º¸(ºÔ(ºð(» (»"(»>(»Z(»v(»’(»®(»Ê(»æ(¼(¼(¼:(¼V(¼r(¼Ž(¼ª(¼Æ(¼â(¼þ(½(½6(½R(½n(½Š(½¦(½Â(½Þ(½ú(¾(¾,(¾H(¾d(¾€(¾œ(¾¸(¾Ô(¾ð(¿ (¿((¿D(¿`(¿|(¿˜(¿´(¿Ð(¿ì(À(À$(À@(À\(Àx(À”(À°(ÀÌ(Àè(Á(Á (Á6(ÁR(Án(ÁŠ(Á¦(ÁÂ(ÁÞ(Áú(Â(Â2(ÂN(Âj(†(¢(¾(ÂÚ(Âö(Ã(Ã.(ÃJ(Ãf(Â(Þ(ú(ÃÖ(Ãò(Ä(Ä*(Ä@(Ä\(Äx(Ä”(İ(ÄÌ(Äè(Å(Å (Å<(ÅX(Åt(Å(Ŭ(ÅÈ(Åä(Æ(Æ(Æ8(ÆT(Æp(ÆŒ(ƨ(ÆÄ(Æà(Æü(Ç(Ç4(ÇJ(Çf(Ç‚(Çž(Ǻ(ÇÖ(Çò(È(È*(ÈF(Èb(È~(Èš(ȶ(ÈÒ(Èî(É (É&(ÉB(É^(Éz(É–(ɲ(ÉÎ(Éê(Ê(Ê"(Ê>(ÊT(Êp(ÊŒ(ʨ(ÊÄ(Êà(Êü(Ë(Ë4(ËP(Ël(ˈ(ˤ(ËÀ(ËÜ(Ëø(Ì(Ì0(ÌL(Ìh(Ì„(Ì (̼(ÌØ(Ìô(Í(Í,(ÍH(Í^(Íz(Í–(Ͳ(ÍÎ(Íê(Î(Î"(Î>(ÎZ(Îv(Î’(ή(ÎÊ(Îæ(Ï(Ï(Ï:(ÏV(Ïr(ÏŽ(Ϫ(ÏÆ(Ïâ(Ïþ(Ð(Ð6(ÐR(Ðh(Є(Р(м(ÐØ(Ðô(Ñ(Ñ,(ÑH(Ñd(Ñ€(Ñœ(Ѹ(ÑÔ(Ñð(Ò (Ò((ÒD(Ò`(Ò|(Ò˜(Ò´(ÒÐ(Òì(Ó(Ó$(Ó@(Ó\(Ór(ÓŽ(Óª(ÓÆ(Óâ(Óþ(Ô(Ô6(ÔR(Ôn(ÔŠ(Ô¦(ÔÂ(ÔÞ(Ôú(Õ(Õ2(ÕN(Õj(Õ†(Õ¢(Õ¾(ÕÚ(Õö(Ö(Ö.(ÖJ(Öf(Ö|(Ö˜(Ö´(ÖÐ(Öì(×(×$(×@(×\(×x(×”(×°(×Ì(×è(Ø(Ø (Ø<(ØX(Øt(Ø(ج(ØÈ(Øä(Ù(Ù(Ù8(ÙT(Ùp(Ù†(Ù¢(Ù¾(ÙÚ(Ùö(Ú(Ú.(ÚJ(Úf(Ú‚(Úž(Úº(ÚÖ(Úò(Û(Û*(ÛF(Ûb(Û~(Ûš(Û¶(ÛÒ(Ûî(Ü (Ü&(ÜB(Ü^(Üz(Ü(ܬ(ÜÈ(Üä(Ý(Ý(Ý8(ÝT(Ýp(ÝŒ(ݨ(ÝÄ(Ýà(Ýü(Þ(Þ4(ÞP(Þl(Þˆ(Þ¤(ÞÀ(ÞÜ(Þø(ß(ß0(ßL(ßh(ß„(ßš(ß¶(ßÒ(ßî(à (à&(àB(à^(àz(à–(à²(àÎ(àê(á(á"(á>(áZ(áv(á’(á®(áÊ(áæ(â(â(â:(âV(âr(âŽ(â¤(âÀ(âÜ(âø(ã(ã0(ãL(ãh(ã„(ã (ã¼(ãØ(ãô(ä(ä,(äH(äd(ä€(äœ(ä¸(äÔ(äð(å (å((åD(å`(å|(å˜(å®(åÊ(åæ(æ(æ(æ:(æV(ær(æŽ(æª(æÆ(æâ(æþ(ç(ç6(çR(çn(çŠ(ç¦(çÂ(çÞ(çú(è(è2(èN(èj(è†(è¢(è¸(èÔ(èð(é (é((éD(é`(é|(é˜(é´(éÐ(éì(ê(ê$(ê@(ê\(êx(ê”(ê°(êÌ(êè(ë(ë (ë<(ëX(ët(ë(ë¬(ëÂ(ëÞ(ëú(ì(ì2(ìN(ìj(ì†(ì¢(ì¾(ìÚ(ìö(í(í.(íJ(íf(í‚(íž(íº(íÖ(íò(î(î*(îF(îb(î~(îš(î¶(îÌ(îè(ï(ï (ï<(ïX(ït(ï(ï¬(ïÈ(ïä(ð(ð(ð8(ðT(ðp(ðŒ(ð¨(ðÄ(ðà(ðü(ñ(ñ4(ñP(ñl(ñˆ(ñ¤(ñÀ(ñÖ(ñò(ò(ò*(òF(òb(ò~(òš(ò¶(òÒ(òî(ó (ó&(óB(ó^(óz(ó–(ó²(óÎ(óê(ô(ô"(ô>(ôZ(ôv(ô’(ô®(ôÊ(ôà(ôü(õ(õ4(õP(õl(õˆ(õ¤(õÀ(õÜ(õø(ö(ö0(öL(öh(ö„(ö (ö¼(öØ(öô(÷(÷,(÷H(÷d(÷€(÷œ(÷¸(÷Ô(÷ê(ø(ø"(ø>(øZ(øv(ø’(ø®(øÊ(øæ(ù(ù(ù:(ùV(ùr(ùŽ(ùª(ùÆ(ùâ(ùþ(ú(ú6(úR(ún(úŠ(ú¦(úÂ(úÞ(úô(û(û,(ûH(ûd(û€(ûœ(û¸(ûÔ(ûð(ü (ü((üD(ü`(ü|(ü˜(ü´(üÐ(üì(ý(ý$(ý@(ý\(ýx(ý”(ý°(ýÌ(ýè(ýþ(þ(þ6(þR(þn(þŠ(þ¦(þÂ(þÞ(þú(ÿ(ÿ2(ÿN(ÿj(ÿ†(ÿ¢(ÿ¾(ÿÚ(ÿö)).)J)f)‚)ž)º)Ö)ò))$)@)\)x)”)°)Ì)è)) )<)X)t))¬)È)ä)))8)T)p)Œ)¨)Ä)à)ü)).)J)f)‚)ž)º)Ö)ò))*)F)b)~)š)¶)Ò)î) )&)B)^)z)–)²)Î)ê)))8)T)p)Œ)¨)Ä)à)ü))4)P)l)ˆ)¤)À)Ü)ø) ) 0) L) h) „)  ) ¼) Ø) ô) ) &) B) ^) z) –) ²) Î) ê) ) ") >) Z) v) ’) ®) Ê) æ) ) ) :) V) r) Ž) ª) Æ) â) þ) ) 0) F) \) r) Ž) ¤) À) Ö) ò)))4)J)`)|)’)¨)¾)Ô)ê))),)B)^)t)Š) )¶)Ì)â)ø))$):)P)f)|)’)¨)¾)Ô)ê))),)B)X)n)„)š)°)Æ)â)ø))$):)P)f)|)’)¨)¾)Ô)ê))),)B)X)t)Š) )¶)Ì)â)ø))$):)P)f)|)’)¨)¾)Ô)ð)))2)H)^)t)Š) )¶)Ì)â)ø))$)@)V)l)‚)˜)®)Ä)Ú)ð) )")8)N)d)z)–)¬)Â)Ø)î)))0)F)b)x)Ž)¤)º)Ð)æ)ü))()>)T)p)†)œ)²)Î)ä)ú))&)<)R)h)~)”)ª)À)Ö)ì))).)D)`)v)’)¨)¾)Ú)ð)))2)H)^)t)Š) )¶)Ì)â)ø))*)@)V)l)‚)˜)®)Ä)Ú)ð)))2)H)^)t)Š) )¶)Ì)â)))0)F)\)r)ˆ)ž)´)Ê)à)ö) ) ") 8) N) d) z) ) ¦Kµ·73#735#Kjj OO·· ÿü2·7#'3#"&54632*4ƒ© uV·7#'3#')E·BBBBŸ·73##7##7#537#5373373337#{#&  %  !$"%  $  !c%%p)6666)6666))ÿñ{Â!&7#5"'535&&546753&''5654N>'  64 #V.#. ÿþƹ '374632#"&74&#"3267#74632#"&74&#"326 9 `ef9 €K··€ÿþ°º$,7&54632673#'#"&5467654&#"'323&, # ( <1e. % ]3u)·7#')·BB ÿØE· 73#&540##&·2?>0+CDÿØC· #654'3##&(0>@1.CB OƒÃ77''7'7'S10/1Ã2) -- )2 !€” 7#53533##=0000Q000ÿß/7#67-"# :HO753 >:ÿü2 7#"&546322 ]·7#7]DD··· ÿþº 74632#"&74&#"326 ^\////////* +, Y·3#547'73Y" 0r& ~¹3#576654&#"'6323~r,  #V/ % ÿþ~¹"7#"'532654&##532654&#"'632Q-"!  `&   $ˆ¸ 7##5#5733#547#ˆTS14))){z, Lÿþ·7532654&#"'73#632#"  VB  "  V4 ÿþ¹!7&#"3632#"&54632"32654&t 8 !(( !·J )'65X  ·37#53#G_vF¢¥ ÿþ€º)7#"&547&54632654&#"32654&'Y'$3a       ÿþ¹!35327##"&54632#"72654&#" 7 "((!J )'65Xÿü2 7#"&54632#"&546322{  v ÿß2 7#"&54632#672{  f"# €œ7'57€ss\\5 <.( 7€}75353 sssk4 €œ77'5 \\ss2(.< 5ÿüe¹#754>54&#"'632#"&54632# 4    & ÿéз.97#"&5463232654&#"327#"&54632#"'&#"327‹  ' (/($,2;.(1  24 "(2+&)  2-1>2*"V'œ¸3'##73''&'„DCD399¸¸M5 5‘· 732##732654&##32654&#2$%*=!·"$iMBÿþ“¹7#"&54632&#"32Œ%*-& !//,1  '#$% ·3532#'32654&#3'-/*"!·/+-0£$$$#x· 3#53#3#3x__HCCH·8Ax· 3#53#3#0_HCC·@ÿþž¹73#"&54632&#"3275#b<!%,/* " %aZ 0.,1  &$$%6›· 3#5##53353›SSUU·MM M· 3#575'53MCC “ “ÿéÿÏ.· 5326553#"  .·´”· 3#'#5373”8HAWI·\\Px·3533H·¢Æ·3'##53373#547#d7"43#7 #m·••·o  ¢·3#'##533&553¢ZY—$c·—!dÿþ®º 74632#"&74&#"326*%$++$%*†\.01-.0//$%%$%%%†· 7#532#'32654&##0- "!GG·ÿÌ®º7&'#"&54632'4&#"326  %**%$+//.01-EE$%%$%%%”· 7#532#''32654&##0,! $2,LL·$ RL ÿþy¹!7532654'&'&&54632&#"#"   5        ‚·3#5#53#N44¢ÿþœ·7#"&55332655œ$#·v ## vww‘· 73#'367y==%···tà·736773#'&'#'367e .0·k#n·u u·oŒ· 3#'#7'373Œ--95)*5OO`WFFW‡·773#5'3C+88Z]pGFq €· 3#57#533€vYVpY\ÿØG·#53#3G22(ߺ]·7#'DD···ÿØ9·35#53#33ºßEƒ¸773#'5 <.(Ess\\ÿØjÿé#53jjj(1›bÄ7#&'53b › ÿþtŒ"3'##"&547754&#"'632'3265c:  *  ^C ÿþˆÃ7##533632#"72654&#"- Ã/ $##$ÿþnŒ7&#"327#"&54632n  !„##$$ÿþ€Ã7##"&546323'53#'54&#"326k##$$7Ã?ÿþ|Œ7#327#"&54632'34&#"|V" V>@  $""&!^Ä7##5#5754632&#"3Q   xxx    ÿÀŒ%1>7#"'332#"&547&547&546324&#"326#"32654&€ ""     ‰   +M  ‚Ã354&#"#533632l XGÃ:Z/½ 7#"&54632#53/®¶‰ÿøÿÃ/½ 7#"&546325326553#"/7 ®ð ž~à 773#'#53+525*GB;N? 5ÃZ"-Ã3#53-ÃÑŒ!354&#"#54&#"#5336323632º     XLXG‰Z‚Œ354&#"#533632l XG‰Zÿþ†Œ 74632#"&74&#"326!"!!`E"%&!#$%"ÿÈŒ7##533632#"'32654&#"-  :Æ$##$LÿÀŒ7373#57##"&5463254&#"326jwÆ:##$$MaŒ7&#"#533632a ‹J‰ ÿþhŒ7532654.54632&#"#"  -  .        ÿþR©7#"&55#57733#32R  ##  O  O ÿþ‰3'##"&553326553o  YXG‰{‰ 3'336773// /‰LL‰¼‰3'&'##'33677336773~##M$t‰IMMI‰{‰ 7'373#'#3,,.""FC33CF77ÿÃ|‰7336773#"'53277  6  ‰LL› g‰ 3#57#533g]C?VADhfÿØT·3"&554�"?   *  ) )=ÿÃPÃ73#=ÃÿÿØS·7475&5543"#5265  ') )  *  I€k75632327#"&#"  #   $ I  ÿÑ2Œ 74632#"&3# z  „$~‘'7'76327'#"''7&544&#"326           N  u           ÿÿlÄ*57&54632&#"#"'532654&'&&54654&'%   9 …        4   &¢m» 7#"&54632#"&54632?.® bH¹7'#"&547354&#"'632'32655=  #    d  8' ÿÿ :HO ÿþɹ &/7#"&546324&#"326'#'##532'32654&##É8&'77'&8/!"//"!/@  2 [&77&'77'"//"!///,,o k^¹ 7#"&546324&#"326^  ’   €” 7#53533##53=00000sQ000!IN¹7#576654&#"'6323NH  2I   GM¹"7#"'532654&##532654&#"'6327   ƒ    1›bÄ7673#1  ŸÿàÃ#5##5#"&54633 G ÏÏf !I2l 7#"&546322Z  ÿÃ3532654'73#"  <   I7· 73#547'& ·nA bQ¹ 74632#"&74&#"3269 ޱ·7#7#3#547'##5#5733#547–ef] ©//!···nA ‡HF 0´·%7#7#3#547'#576654&#"'6323”fe\ ¯G  1···nA Ÿ   º¹"&187#"'532654&##532654&#"'6327#7##5#5733#5477   Xee,./"ƒ    -··ŸHF  ÿÏiŒ #74632#"&327#"&54>550 z       Ë·3#5##73#3#3'35#Ë_<RyHCCH’399·8A8U · 73532##5#73#32654&#3'-/*.***"!eR/+-0PS><$$$#&| 7'77''9' ') (' )' Z( '' )( '' ÿ÷®À#7#"''7&5463277&#"732654£ +$  *% ]H cI ¶..0 1.0 ™w%$"Uv%% †· 7#5332#'32654&##0 "!((· ÿþÄ+7532654.54654&#"#54632#";  ! #$    ––  ÿÿ ÿþtÄ&DCóÿÿ ÿþtÄ&Dm ÿþÉŒ#)4754&#"'632632#327#"'#"&547734&#"3265] ! T" :/; S  !  *6 ÿÿÿþ|Ä&HCóÿÿÿþ|Ä&Hm ÿþ¥Ä &7#&'#57673#327#"&54632'34&#"¥  )V" V>› _  $""&!ÿÿÿý.Ä&•CÌÿÿIÄ&•mçÿþ†Ä&73&''7&'77#"&5463232654&#"l    &!!;k  $:$&! >ÿÿÿþ†Ä&RCüÿÿÿþ†Ä&Rm  €– 7#"&5463253#"&54632UHs+†="ÿ÷†’#7'7&546327#"'7&#"732654.  !  !2 D2  !"%  #$&Q6PÿÿÿþÄ&XCõÿÿÿþÄ&Xm ÿÿÿþ»&XeÿÈà 7632#"'##332654&#"-  w$##$::-ÿÿ ÿþt®&D§þÿþ”Ã&7##"&546323'5#53533##'54&#"326k00"""#›=ÿÿÿþ|®&H§ÿÿÿþ|Ä&H¦´·7533533##5##5#55#SSƒS˜†UU†.‚Ã354&#"#5#53533#3632l// SB›UÿÿÿþF®&•§Û-‰3#53-‰ÿÿ ÿÏ…·&,-WÿÿÿÃq½&LMB~‰ 3'#5373d*725= 3‰#F=Lÿÿx·&/©<®ÿÿUÃ&O©&¨x· 75373#5' &H_ UbT9?ÿÿDà 7537#5'  _dUXIÿÿ”·&Q#ýÿÏ¢·53265'##533&553#"^ _Y .—$c·’d´ÿÂŒ5326574&#"#533632#"M  : mG‰nÿÿÿþ†®&R§ÿþÕº3#"&546323#3#3'5&#"32Õ_%**% _HCCH_  //.08A‹%$%%ÿþÜŒ&,7#"&54632632#327#"'4&#"32674&#"z !!V!V%""%!  F)&‚·75#53#3##5#5744%%%f<<TTÿþR©7#"&55#535#57733#3#32R  ##!!     ÿÿÿþ®&X§ ›sÄ 7#&''53673s  Á  #›k®73##HH®$›o¾ 7#"'3327o## ¾##  /½ 7#"&54632/®.›fÑ 74632#"&732654&#".   ¶   ÿÈ,#"&547332,  (  ›xÄ7673#7673#  -   Ÿÿÿœ¸$ÿÿ‘·%x·7##5xH·¢·· 7#5733'&ˆ8  V ¦_^ÿÿx·(ÿÿ €·=ÿÿ›·+ÿþ®º 74632#"&74&#"326'3#*%$++$%*†XBB\.01-.0//$%%$%%%0ÿÿ M·,ÿÿ”·.‘· 3#'&'#73‘%%=st·ÿÿÆ·0ÿÿ¢·1 ~· 73#3##5jj TTdt·8Aÿÿÿþ®º2˜·3#5##53˜Q¢¢·ÿÿ†·3 ‚· 357'53#3 43pU14_MDBKÿÿ‚·7ÿÿ‡·< ÿþ·¹'7332###5#"&5463332654&+"33W #$%$ ¹&&&&vÿÿŒ·; ··7###5#"&5533353326553·$ ${$88$=<kk= µº7#53&546323#5654&#"TJ,&*%%*%,J+5',,'588ÿþ’Œ(73673327#"'##"&5463254&#"326k w "4##$$IÿÃÄ'7#"'#5463232654&##532654&#"b-Y h-BÆ%T ÿÃ|‰7#47'336773L  4  '‡HIÿþ†Ä$7&54632&#"#"&54732654C   ! D,u   !. ( ÿþlŒ#7#"327#"&5475&54632&#"3TQ     ÿÎmÃ753#654.5467#WG ,  '*¯A6   5)ÿÂŒ54&#"#533632l =•G‰—ÿþƒÅ 74632#"&73&#"#326G!!EGb1221221?DW%&&ÿþO‰ 7327#"&55+  ‰` `ÿÿ~‰˜ÿþÿþƒÄ#7'&&#"5632327#"''&'7  )  † y5 Hÿ‰7326553#'##"'#53-   1G‰ (Æz‰ 536653# ‰J5-.?ÿÎmÃ*7#"#654.54675&547##53#"3d -%$ Po     ÿÿÿþ†ŒRÿþ™‰7#"&55##5#573#32• 2…Xvv VÿÆŒ7#54632#"'32654&#"+  "$‚"%%"#$ ÿþ‘‰ 7##"&5463#"32654‘"! ##‰##!#$!ÿým‰7#327#"&55#57m/ %‰L N ÿþˆ‰7332654'3#"&5‰I  '& ÿÃ¥Œ5&547546325654&#"M?($*  =;D%! /L$!!';M2ÿþÿÃ…Š773327#"&''#7'&#"5632F$2 )8   @Ia< -Og8ÿîÂ7654'3#5&5535k,C@*±1##E;;EDE0²ÿþ³‰#7332655332654'3#"'##"&54  ‰"$&&$""$"##"$ÿÿxå&(e*ÿÿœ¸$†· 353#32#'32654&##bK"! ·8ÿÿ‘·%ÿÿx·¯ÿС· #5##536733#5#¡s(M/!#000EMU¢GFÿÿx·(з7'35373#'#5#ECAABDBB^YYYYYY^]]]] ÿþ~¹"7#"'532654&##532654&#"'632O/#" !`%   $¢·73373#547##YZ·d *—·c—¢í 7#"&'33273373#547##‹. \YZí( 6d *—·c—”· 3#'#5373”IGG]]·YYXÿý’·3#5##"'532673’/  Z¢y,)~ÿÿÆ·0ÿÿ›·+ÿÿÿþ®º2ÿÿ˜·¼ÿÿ†·3ÿÿÿþ“¹&ÿÿ‚·7ÿþ–·7#"'53267'3677–6 B4'·" ˆq cÿÿ ÿþ·¹ÁÿÿŒ·;ÿЮ· 73#5#53353˜QE0·¢¢·3#5#"&55332753K IF Yæ· 3#53353353æÍDD·¢¢¢¢ÿÐü·73#5#53353353æÍDDE0·¢¢¢¢›· 35#5332#'32654&##-*A"" !¢Mº· 35332#'32654&##53"! s·MU··†· 35332#'32654&##"! ·Mÿþ‰¹7'632#"'5327#53&&#" %-,'<ON› /+01Dÿþôº7##533632#"&74&#"326Z*+E#**#")UU·MP1-.0+3$%%$%%%~· 7#7&54633#55#"33H)0" .LLR '·LWÿÿ ÿþtŒDÿþ„Ä!73632#"&54767632654&#"9   Ä  !#**0f!!ƒ‰ 7##532'32654&#32654&#b!98R ! I‰(% 8+ a‰7##53a4Kvv‰ÿЇ‰ #5##536533#5#‡V !C*000C1Eve=(ÿÿÿþ|ŒH¸‰75373#'#5#7'3R45:8895GBBBBGFFFFGB ÿþjŒ#72654&#"'632#"'532654&##5.Q    ‡‰ 773#57#5+@@‰S s‰Q"s‰‡Ã 7#"&5332773#57#5~. >@@Ã( :S s‰Q"s‰}‰ 73#'#53_497‰BGFF‰Bÿþv‰3#5##"'53273v  JvH0z¢‰3#5#'#5373¢!*++p Vpp‰ss‡‰ 7353#5##5-DD‰99‰==‰ÿÿÿþ†ŒR‚‰3#53#5#-l?‰‰vÿÿÿÈŒSÿÿÿþnŒFl‰7##5#53l((gvvvÿÿÿÃ|‰\ÿäÃ7#5&&54675357654d""#")>)‹&%;;&%8²g00cg0/ÿÿ{‰[ÿЖ‰ #5#533533–j?00‰vvv€‰732753#5#"&55* ‰3 :‰=4ɉ 7353#53353{7³7v‰‰vvÿÐ݉#5#533533533ݰ7700‰vvvvv ‰ 732##5#5332654&#I#9-D!QvK+ ª‰ 732##5332654&##53-4cQ‰K+ >‰ƒ‰ 732##5332654&#-"9 Q‰K+ ÿþhŒ75327#53&#"'632#"'<;"  #,($$"$ÿþÁŒ7##533632#"&74&#"326O""3 Z==‰9<&!#$ 'r‰ 3#7&54633#5#75#"3%7 :‰6- ÿÿÿþ|»&Heü :vO753 l::O55!: :öO753 ì:x*·7'673 x"%x*·7#67( ·"&xZ· 7'673#'6735 I x"%"%xZ· 7#673#67( I ·"&"&mà 7'#75'37m)'')|€€55pÃ77'#75'75'37'F********>//$#//#ÿü» #7#"&54632#"&54632#"&546322ED    ÿþ¹ '3?K74632#"&74&#"3267#74632#"&74&#"32674632#"&74&#"326 9 `ef9 9 €K··€ t4¹7'74 ±=@ ta¹7'7'74 D ±=@=@ÿþ‰¹!73#3#327#"'#535#536632&#"3681. %8 $ $u-A""  +ÿÿÕ¸%17&#"327#"&54632#74632#"&74&#"326m   Yfe9 ´··Pÿþ°¹#73#"&555675463232'654&#"¤ "  + &*,>-%Q$ ì·'+3#'##533&5534632#"&74&#"32653ON7 2>—$c·—"di@-]Ó·7#5#53#'#5373#55VBH]LLDDZEEZ5Dÿÿ µºÄÿþ±·",77#7#3#547'#"&547&54632654&#"32654'eeZ ™   ···nA e        ÿþ¹¹"&:DO7#"'532654&##532654&#"'6327#7#"&547&54632654&#"32654'8   Rfe   ƒ    -··}        ÿþ··1;F7#7532654&#'73#632#"#"&547&54632654&#"32654'›fe 7(   ···i 5        ÿþ±· (37#77#53#"&547&54632654&#"32654'‹efg(4G(x   ···n^ a        Bÿþ¾¹#7554&#"5632#"&54632&#"326§ &  l ""7$#,)$#:ÿÃÆ·5##53¯^Œ=ßßôô6ÿÃÊ· 57'53#36NL‰hGJt=rf_l6ÿþÊÔ#'#5373€"%@bRµ42Ì‚'7#"&54632632#"'&#"32732654&#"€    N(  ÿÃoÃ7&#"#"'5326554632o    À¥¥F/º…75632327#"&#"5632327#"&#"F  #   #  $   # c  C   €¡7#537#5373#3#',(7? (7@7!$!"Fºœ 7'5753ºtt\\tt< </.+Fºœ 77'553F\\ttt+./< <pÿü·7#'3#"&54632ˆ4ƒ© ]u£·7#'3#'vF·BBBB4Ì·73##7##7#537#5373373337#¨#&  %  # #&  %  b$$p)6666)6666))Jÿñ¶Â!&7#5"'535&&546753&''5654‰>'  64 #V.#.ÿþâ¹ '374632#"&74&#"3267#74632#"&74&#"3269 ffe8 €K··€7ÿþÙº#+7&54632673#'#"&547654&#"'32\&, #8 <1e. %  ]3tuŒ·7#'Œ·BBbÿؼ· 73#&&546¤BB%%·(IG' =$%=DÿØž· #654'3\BB%%('GI(=%$=:JÆÃ77''7'7'<9&&9;Ã2. 22 .2F!º” 7#53533##w1111Q000lÿß”7#67’ "#a:ŸO753a>:pÿü 7#"&54632 B¾·7#7¾cc···Fÿþºº 74632#"&74&#"326F]\////////* ++ S–·3#547'73–" 0r&G¹¹3#576654&#"'6323¹r, #V/ %Fÿþº¹"7#"'532654&##532654&#"'632-"!  `&   $<Á¸ 7##5#5733#547#ÁUS04))){z, LIÿþ··7532654&#"'73#632#"I  VC  "  V4 Fÿþº¹!7&#"3632#"&54632"32654&¬ 7 "(( ·J )'65X E»·37#53]F^vF¢¥Gÿþ¹º)7#"&547&54632654&#"32654&'“&#4  a      Fÿþº¹!35327##"&54632#"72654&#"T 7 "(( !J )'65Xpÿü 7#"&54632#"&54632{  v hÿß’ 7#"&54632#67’ {  f"'Fºœ7'57ºtt\\< </.F7º}75353Ftttk4Fºœ77'5F\\tt+./< <Gÿü·¹#754>54&#"'632#"&54632n   $4    & ÿéá·.97#"&5463232654&#"327#"&54632#"'&#"327œ  ' (/($-2;.(1  24 "(2+&)  2-1>2*"V'-Ó¸3'##73''&'ºIHH699¸¸M9 9DÄ· 732##732654&##32654&#D:#%* D&$)·"$iMB9ÿþǹ7#"&54632&#"32À)-1) $ !0.+2  '#$%=η3532#'32654&#=='-/*!"!·/+-0£$$$#Fº· 3#53#3#3ºtt\WW\·8AFº· 3#53#3#^t\WW·@4ÿþ̹73#"&54632&#"3275#‹A#).2.  %#"*aZ 1-+2  &$$%6:Æ· 3#5##53353Æ^^UU·MME»· 3#575'53»v//v//““@ÿþ©· 75326553#"@" DÏ· 3#'#53773ÏEFNVH·]JSP¿·3533PW·¢*×·3'##5373#547#u7"44#8 #m·••·o  4Ì·3#'##533&553Ìlk—$c·—!d,ÿþÔº 74632#"&74&#"326,,('--'(,\.01--10.$%%$%%%IÆ· 7#532#'32654&##a= "!"#GG·,ÿÌÔº7&'#"&54632'4&#"326  #" (,,('-"0..01-FF$%%$%%%BÍ· 7#532#''32654&##ZD  ÿøåÁ "*57#"&546324&#"326'##5#5733#547##547'73å<))<;**; 6%%66%%6 32   \);;)*;;*%66%%55 JI-%D  ÿøåÁ ">7#"&546324&#"326#547'73532654&#"'73#632#"å<))<;**; 6%%66%%6x     4( \);;)*;;*%66%%55D  i  3 ÿøåÁ -9D7#"&546324&#"326'&#"3632#"&54632"32654&#547'73å<))<;**; 6%%66%%6*! ?   \);;)*;;*%66%%55[ -  5   :D  ÿøåÁ ")7#"&546324&#"326#547'737#53å<))<;**; 6%%66%%6x   +9F)\);;)*;;*%66%%55D  ma bÿøåÁ +5@H7#"&546324&#"326'#"&547&54632654&#"32654'#55'73å<))<;**; 6%%66%%69   5  \);;)*;;*%66%%55(         3D ÿøåÁ .:E7#"&546324&#"32653265##"&54632#"72654&#"#547'73å<))<;**; 6%%66%%6_  (   \);;)*;;*%66%%55   5   3D  ÿøåÁ #/G7#"&546324&#"326'4632#"&74&#"326#576654&#"'6323å<))<;**; 6%%66%%6[8 >?   .\);;)*;;*%66%%55%   ÿøîÀ 73#&54#654'3'#547'738 $$ &· #$ %e  À,97,'<= ,79,(=<D  ÿøîÀ +73#&54#654'3'#576654&#"'63238 $$ &· #$ %LD  3À,97,'<= ,79,(=<   ÿøîÀ 673#&54#654'3'#"'532654&##532654&#"'6328 $$ &· #$ %g    À,97,'<= ,79,(=D  ÿøîÀ &173#&54#654'3'##5#5733#547##547'738 $$ &· #$ %1 32   À,97,'<= ,79,(=< JI-%D  ÿøîÀ :73#&54#654'3'#547'73532654&#"'73#632#"8 $$ &· #$ %‹     4( À,97,'<= ,79,(=<D  i  3 ÿøîÀ )5@73#&54#654'3'&#"3632#"&54632"32654&#547'738 $$ &· #$ %=! ?   À,97,'<= ,79,(=?   .À,97,'<= ,79,(=<=   ÿÿ8ÿüÁ·&PåM1ÿÿ2ÿü×¹&QëMGÿÿ1ÿü×¹&RëMGÿÿ'ÿü׸&SëMGÿÿ4ÿü×·&TëMGÿÿ1ÿü×¹&UëMGÿÿ2ÿü÷&VíM3ÿÿ2ÿü׺&WëMGÿÿ1ÿü×¹&XëMGÿüïº ".74632#"&74&#"326#547'73#"&54632^Sg '¥\////////++,0r© ÿÿ ÿüå·&P¸&MUPÿüï¹".3#576654&#"'6323#547'73#"&54632Ãi(    My '¥/ %r© ÿüí¹"-97#"'532654&##532654&#"'632#547'73#"&54632œ*   v '¢`&   &fr© ÿüï¸ )7##5#5733#547##547'73#"&54632ÍJI,*$ (¦)))|z,  G>r© ÿüï· %13#547'73532654&#"'73#632#"7#"&54632K '  Q=  ~r° V4 ÿüï¹"-97&#"3632#"&54632"32654&#547'73#"&54632º   %%  Z (¦·%% ((65Xar© ÿüì· 3#547'737#537#"&54632R  0+ATlAWr &·¢¥ ÿüïº)4@7#"&547&54632654&#"32654&'#547'73#"&54632¡&#1   H (¦a      Ur© ÿüï¹!,835327##"&54632#"72654&#"#547'73#"&54632j 3 &%  : (¦J ((65XVr© ÿüôº /;74632#"&74&#"326#576654&#"'6323#"&54632pN  Qf&   K‡\////////++,0/ % ÿøîÀ +673#&54#654'3''##"&547354&#"'632'#32658 $$ &· #$ %W )   À,97,'<= ,79,(=<   8( ÿøîÀ %173#&54#654'3'##533632#"72654&#"8 $$ &· #$ %€   À,97,'<= ,79,(=< t  ÿøîÀ )73#&54#654'3'&#"327#"&546328 $$ &· #$ %L    À,97,'<= ,79,(=:ÿüw 7#"&54632#"&546322E  I2l 7#"&546322Z  ÿýWœm7#53œŸŸWÿýNœy7#53œŸŸN+BÿÈXÿ#3X877ÿÈcÿ#3c,,87ÿýYœn 7#53#53#53œ'';((<((YÿýNœy 7#53#53#53œ'';((<((N+++++BÿÈXÿ 7#53#53#53X¥ZÈYÈZ7ÿÈcÿ 7#53#53#53c,,,,,,¥ZÈYÈZÿýYœn 7#53#53#53#53œ,--YÿýNœy 7#53#53#53#53œ,--N+++++++BÿÈXÿ 7#53#53#53#53XÁ>‘>‘>‘>7ÿÈcÿ 7#53#53#53#53c,,,,,,,,Á>‘>‘>‘>BÿÈœn7##53œDZY‘¦BÿÈœy7##53œDZN†±7ÿÈœn7##53œ9,eY‘¦7ÿÈœy7##53œ9,eN†±ÿýÿÈXn#5#53XE[8‘ÿýÿÈXy#5#53XE[8†+ÿýÿÈcn#5#53c,:f8‘ÿýÿÈcy#5#53c,:f8†+BYœÿ7#533œZDY¦‘BNœÿ7#533œZDN±†7Yœÿ7#533œe,9Y¦‘7Nœÿ7#533œe,9N±†ÿýYXÿ7#5353X[EY‘ÿýNXÿ7#5353X[EN+†ÿýYcÿ7#5353cf:,Y‘ÿýNcÿ7#5353cf:,N+†BÿÈœÿ7##33œDDY‘7‘BÿÈœÿ7##33œDDN†7†7ÿÈœÿ 7##5#533œD ,9Y‘‘¦‘7ÿÈœÿ 7##53533œ9, DY‘¦‘‘7ÿÈœÿ7##33œ9,,9Y‘7‘7ÿÈœÿ 7##5#533œD ,9N††±†7ÿÈœÿ 7##53533œ9, DN†±††7ÿÈœÿ7##33œ9,,9N†7†ÿýÿÈXÿ#5#5353XEE8‘‘ÿýÿÈXÿ#5#5353XEE8†+†ÿýÿÈcÿ 7##5#5353c E:,Y‘‘‘ÿýÿÈcÿ #5#53533c,:E 8‘‘‘ÿýÿÈcÿ#5#5353c,::,8‘‘ÿýÿÈcÿ 7##5#5353c E:,N††+†ÿýÿÈcÿ #5#53533c,:E 8†+††ÿýÿÈcÿ#5#5353c,::,8†+†ÿýÿÈœn7##5#53œDEŸY‘‘ÿýÿÈœy 7##5#533œDE[DY‘†+ ÿýÿÈœy 7##5#5353œDEEZN†‘ ÿýÿÈœy7##5#53œDEŸN††+ÿýÿÈœn7##5#53œ9,:ŸY‘‘ÿýÿÈœy 7##5#533œ9,:f9Y‘†+ ÿýÿÈœy 7##5#5353œ9,::eN†‘ ÿýÿÈœy7##5#53œ9,:ŸN††+ÿýYœÿ7#53533œŸEDY‘‘ÿýNœÿ 7##53533œD[EDY +†‘ÿýNœÿ 7#5#53533œZEEDN ‘†ÿýNœÿ7#53533œŸEDN+††ÿýYœÿ7#53533œŸ:,9Y‘‘ÿýNœÿ 7##53533œ9f:,9Y +†‘ÿýNœÿ 7#5#53533œe::,9N ‘†ÿýNœÿ7#53533œŸ:,9N+††ÿýÿÈœÿ 7##5#53533œDEEDY‘‘‘‘ÿýÿÈœÿ 7##5#53533œDEEDY‘†+†‘ÿýÿÈœÿ 7##5#53533œDEEDN†‘‘†ÿýÿÈœÿ 7##5#53533œDEEDN††+††ÿýÿÈœÿ 7##5#53533œDE:,9Y‘‘‘‘ÿýÿÈœÿ 7##5#53533œ9,:EDY‘‘‘‘ÿýÿÈœÿ 7##5#53533œ9,::,9Y‘‘‘‘ÿýÿÈœÿ 7###5#53533œ9 E:,9Y ††+†‘ÿýÿÈœÿ 7##5#5#53533œD ::,9N†† ‘†ÿýÿÈœÿ 7##5#535333œ9,:E 9Y‘†+†† ÿýÿÈœÿ 7##5#5353533œ9,:: DN†‘ ††ÿýÿÈœÿ 7##5#53533œDE:,9N††+††ÿýÿÈœÿ 7##5#53533œ9,:EDN††+††ÿýÿÈœÿ 7##5#53533œ9,::,9Y‘†+†‘ÿýÿÈœÿ 7##5#53533œ9,::,9N†‘‘†ÿýÿÈœÿ 7##5#53533œ9,::,9N††+††ÿýCœ„7#53#53œŸŸŸŸnA,ÿÈnÿ#3#3n,87þÉ7BÿÈœ„ 7##53#3œDZDDC{¼,ÿÈœn 7##5##53œ.pY‘‘‘¦,ÿÈœ„ 7##53##53œZp.Dn¦¼A{‘ÿýÿÈX„ #5#535#53XEEE[8{ÿýÿÈnn #5##5#53n/q8‘‘‘ÿýÿÈn„ #5#53#5#53n[q,/E8¦¼{BCœÿ 7#533#3œZDDDC¼{,Yœÿ 7#533533œp.Y¦‘‘‘,Cœÿ 7#533#533œD.pZn‘{A¼¦ÿýCXÿ 7#535#5353X[EEEC{ÿýYnÿ 7#5353353nq/Y‘‘‘ÿýCnÿ 7#5353#5353BE/,q[n{¼¦BÿÈœÿ 7##33#3œDDDDC{7{,ÿÈœÿ 7##33#3œ..ZY‘7‘¦7,ÿÈœÿ 7#533#3##53œD.ZZ.Dn‘{¼7¼{‘ÿýÿÈXÿ #5#535#5353XEEEE8{{ÿýÿÈnÿ #3#5#5353n,//87þÉ‘‘ÿýÿÈnÿ 7#5353#3#5#53BE/,,/En{þÉ7þÉ{ÿýÿÈœ„ 7#53##5#53œŸŸDEŸnA{{ÿýÿÈœn 7##5##5#53œ./ŸY‘‘‘‘ÿýÿÈœ„ 7#53##53#5#53œŸŸ.DZ/EnA{‘‘{ÿýCœÿ 7#53533#53œŸEDŸŸn{{AÿýYœÿ 7#53533533œŸ/.Y‘‘‘‘ÿýCœÿ 7#533#5353#53œD.ZE/ZŸŸn‘{{¼ÿýÿÈœÿ7##5#535#53533#3œDEEEEDDDC{{{{ÿýÿÈœÿ7##5##5#53533533œ.//.Y‘‘‘‘‘‘‘‘ÿýÿÈœÿ 7#533#5353##53#5#53œD.ZE/Z.DZ/En‘{{¼{‘‘{Bn7"#463(5&Y)%5ÿýXn7#44X(&5)5ÿýYX³7#5265X6%(³%5)BY³7"&533&5(Y5%)š°7'7šŠŠ¡ŠŠš°7'7šŠ&Šš° 7''7'77š==>>==>&===>==>ÿýYBn7#53BEEYÿýÿÈœÿï53Ÿ8''ÿýÿÈœ53Ÿ8NNÿýÿÈœ=53Ÿ8uuÿýÿÈœc53Ÿ8››ÿýÿÈœŠ53Ÿ8ÂÂÿýÿÈœ±53Ÿ8ééÿýÿÈœØ3Ÿ8þðÿýÿÈœÿ3Ÿ87þÉÿýÿȆÿ3‰87þÉÿýÿÈsÿ3v87þÉÿýÿÈ`ÿ3c87þÉÿýÿÈMÿ3P87þÉÿýÿÈ:ÿ3=87þÉÿýÿÈ&ÿ3)87þÉÿýÿÈÿ387þÉ'ÿ¼š  #'+/37;?CGKOSW[_cgkosw{ƒ‡‹“—›3#73#73##3#73#73#'3#73#73##3#73#73##3#73#73#'3#73#73#'3#73#73##3#73#73##3#73#73#'3#73#73##3#73#73##3#73#73#'3#73#73#33€34M33€34M33€34M33€34M33€34M33€34M33  ÿ½š #'+/37;?CGKO#5##535#535#535#535#535#535#53355#5#5#5#5#5#5#5#5#5#5#šM443LLLLL þ²4ÿýØœÿ'53ŸØ''†ÿÈœÿ3†87þÉ)„—7#5„n—nn)„—7#5#3„nd[[—nn \)„— 7#5#3'#5„nd[[.—nn \E--)„— 7#535#5#5#5#„nn [[[[[[[)n)„— 7#5#37#37#37#3„n—nn \\\\\\\)„— #'+/37;?C7#535##5##5##5#5##5##5##5#5##5##5##5#5##5##5##5#„nn    [   [   [   )n)„— 7#535#5'#5'#'#'#'„nn 0>NU?1 )n1U?\U?1)„— 7#53#7#7#75375335„nnW1U?[T>0 )n 1?8U1)„— !%*.258<AFKPSVY\_7#53#'#'#'#'#5#'5''''''''75#''75''''3''375#''#'3'„nn    PT[   A  ?T9 - < A  >TU@*)n           6ƒœ7#7ƒl66f6ƒœ7#7'ƒl6&&'6f\HH#*‰–75‰f`6l#*‰–75'‰fRI`6l6&L$ƒŠ7'ƒ66Šff$ƒŠ7'#ƒ66\M'Šff I*w–7'7wgg*66*w–7'75wgg H*66\L&%ˆ›7'7ˆ;;;`;;;%ˆ›7'7'ˆ;;;--..`;;;;...%ˆ› 7'7'7'7ˆ;;;--..`;;;;....)„— 72#"&5462654&#"M  —   e)„— #/7"&54632'"32654&"&54632'"32654&M       )   eI  -   )„— 74632#"&   `  )„— 74632#"&2654&#   7`  )„— 7"&54632'5"M  )   \)„—7#„n—n)„—7#nn—n)„—75„n—nn)„—7'„n—nn•¨ 7"&54632'"32654&M****$$%%****†$$$$ÿ÷êÀ 77'373€D@PP?&/N/LL/Nÿ÷êÀ 77'373'7'7#'€D@PP?,00-7&/N/LL/Ns!7""7!77ð·#'+/37;?CGKOS7"546332##'&&##3#5735#"73537#3#3#'#3'#37#3#33'#37#3#33'#37#3#3 ,> ?+ ' $ž$3->F2!O9%Z€  yy   ,ð·37=AEIMQUY]aeim7'&&##3#5735#"#"546332#'763323254&##"373535'3#3#'3733#'3#3#'33#'3#3#'33#'3#3#'3¾ $ž$' ,> ?+ ªT '< <'U2 Œ EC'L-V3b€  {{     s s #á”48>HR]7/###"''57#"&5433632?3'732654&#"7'337'#"'32'#"'32'##"'32É  Q  ~ q ³  #     #  6   q?!HI )    #ã”48>HR[75363232###"'#"'767254''&&#"33654'75'265#3277#"3277#"3277  Q  $  # Ž J (    #q   6  ;"H      J¶º!7#535&&546323##74&#"326w!!, + . ƒJÿþ¶·73'#"&5475'4&#"326|+ - Q·* */* Y0н7#"'#575#"&5466€ :  D  9½7  $$  7ÿøâ»#.54632632>54&#"&#"€A?<>A""A =! ?0й7#"'#575#"&547&54632§) D)€$ $$ $  ,ÕÀ17#"'#575#"&54665'532654&&'327€ 9 N  :+ 8 8 À0 2¥, 4 4 ,ÿøâ».54632632€A?A""A,Õ¾=7#"'#575#"&547&546325'532654'654&#"327¬) N (*+ƒ$$ƒ ,"  ", ÿþêº:W763654&547654&547#"&547654&547#67&##67"#6732654`     80::0@ W    2+22+­ -   ,  ,  -  ,    - €      aÿþŸ· 753#"&54“  <{“ "Eÿþ··753'654&'#"&54x 0 <{ ) u "%ÿþ¹·7#"&5475##"&5475335#¹ 3K 2bVKK$ "@X "{)Wÿü°Ã 7632536654&#"c 0)  V,Ǽ" Jÿÿ¶·7575575375377#5#775e $  $ $$#  * #$ ) +&(A*2ÿìI7#67H 6ÿþJ7#"5432J  1ÿìJU7#"5432#67J  J C6ÿþJU7#"5432#"5432J J M #ÿþ\o754>54&#"'632#"54325       6ÿþJn 7#'3#"5432D  Of .ÿèRn 73#&54E  n&%().ÿèRn #654'3;  %')()ÿèWn3"&554�"J    )ÿèWn7475&55443"#52656      +ÿèUn'57U**WJ+ÿèUn75'5+** JWnn73##7##7#537#5373373337#X    ;C  !!!! ÿÿvo#+7&54632673#'#"&547654&#"'32+    !$ =       8  /du77''7'7'G u  cY 7#53533##:  1  1c<753F1 c^7'57cFF88 % c^77'577FF % !cK75353FFF@  %[n7#'2) )nnn ÿ÷`u#7#5"'535&547532&''5654E      %  !  3  ÿÿxo '374632#"&74&#"3267#74632#"&74&#"326 ":= = "M -nnM ÿðzk,77#"&5463232654&#"327#"&54632#"'&#"327Q  #  $ %4 t/¹7' ¹@=IR¸ 7##5#5733#547R./"aIG  ÿÿ ÿþtÄ&D¦üÿÿÿøKÄ&•¦Øÿÿÿþ†Ä&R¦ÿÿÿþÄ&X¦ÿÿÿþÝ&X&e§/ÿÿÿþî&X&em*ÿÿÿþî&X&e¦*ÿÿÿþî&X&eCò* 7‰7'#77--‰##‰#ÿÿ#›k®§ÿÿ1›bÄmÿÿ1›bÄCÿÿ#ÿÙkÿì§ÿ> ¦° #/;7''7'77'"&54632"&54632#"&54632"&54632¦FGFFGFF&]&MMNMNNM+  ,dQ¹754&#"#53632A    d4 +S 6/ÿÃÑÃ/7&#"#"'5326554632&#"#"'5326554632Ž    I    À¥¥¥¥ÿÃpÃ%*/754632&#"#"'532655&&54665455    &(s#!(&6.6=ÿüà #7#"&54632#"&54632#"&546324gq  l  =ÿüà #7#"&54632#"&54632#"&54632\g3q   l pÿüƒ 7#"&54632#"&54632q  l =ÿüà #/7#"&54632#"&54632#"&54632#"&54632\gggq   l  ÿÿFI¹ka9ó· 3#575'5373#'367XBB‚==% “ “ª··tÿÿÿÊ78qÿÿÿÊN:kÿÿÿÈM:lÿÉR8 ##5#5733#547R./"IG  ÿÿÿþï¹&i&\ÿÿâ¹&i)j]ë· 7#3#3#53#5#53#3#53¢'!!(87_L€(8© ZZL? Zÿÿ2Îá&$2ª6ÿþ²·17#7#3#547'#"'532654&##532654&#"'632‘efX ”   ···nA e    ÿþ¸¹=7#576654&#"'63237#7#"'532654&##532654&#"'632LG  1Qfe   I   ^··}    M²· 3#575'53²e((e&& “ “,Õ· 3#575'53'5#Õ©##©##:5 “ “““ ÷· 3#575'53'5##5#÷î!!î!!;13 “ “““““ÿÿ8É·98 ë· 775'53#'6773#'3©BB^%=> “ “ Dt··ô·3'373#575'75#70%}-w ·j› “ “ª““ü·3'373#575'75#735.+ ¢ )c/·›› “ “ª“““““ñ· 3#575'53#'#7'373XBB™,.95*)5 “ “ OO`WFFWÿÿ:Æ·;:ë· 775'53#''373#'#¨CC`5*)59., “ “ `WFFW`OO ó·7#'#7'3775'5#ó! -)#u · “ JJ`WAAª“JN““I·Â 7#"&54632'53#57!8,n+´Dw é '7#"&54632#"&54632'53#575'533¿[B"9,É+!8D´Dwewð #97#"&54632#"&54632#"&54632575'5335'5335'53A‚¼!.*.*."´»ewewew8lj 3'36773q9#  #9‰V V‰:Ɖ 7'373#'#s6)*69,,FC44CF88ï !7#"&54632'53#57'336773K)Og9#  #9´Dw‰V V‰ñ !7#"&54632'53#57'334773Ö)Ot9#  #9´Dw‰V V‰ú #37#"&54632#"&54632'3367733575'5335'53«:³- --&#'´»‰V V‰ewewû #C7#"&54632#"&54632#"&54632'33677335'5335'53#575'ò12b(0 ! ! ‘ $´»‰Q Qweweweyè !7#"&54632'53#577'373#'#O)Pi, ,/"!´Dw4C33CF77Pÿþ°¹7##5&54753&#"32® // 2?@B¾¹7&#"3#3#5655#5354632¸//]|¯ $ (F!ºd7#5#5ºadC0wÿÉÃ73#3#wÃb=a2η7733#3##5#535#53'3€39&//////%8Z]`!!`#ÃÝÔ7#53ݺºÃ* Öx73##"''&5477632R„„++H "'' ÿÃwÃ7#5#"5477632#"'F "''š××++* Öx7#53'&5432#"547®„„++; "'' ÿÃwÃ537632#"''&5432: #''ØØ++ áx)7##"''&54776323'&5432#"547¹r++r++;"''""'' ÿÃwÃ)5#"5477632#"''7632#"''&5432:"''##''¯++¯++:ÿûƈ7'#"'&54632^hi # lih$ &:ÿûƈ7'7'&547632#"'«ih$ #chi $ :ÿûƈ7'77632#"'&547¢hi $ ih$ &:ÿûƈ77#"&57632Uih% $ hi 1 * Öx7'#53'&5432#"5477#53µ„} ++ }„B '' * Öx)/7#"''&54776323'&5432#"5477#77'#c++ O ++ OV\'''' -ÿÿÓ·7#'337677ÓHHIW·¸¸99M9  9Fº· 735#535#53#F]XX]ttA8·<Ä· 73#'#67<ˆ88nT·©©_C¼·7#7¼ef···B2¿‚$7#"'#"&54632632&#"33'&#"32¾ 4  4 1Ϧ7#"3#33#"&5463ÏD?ŠŠ =DB-/0,¦/0)""*1Ϧ75327#53&##532#1D= ŠŠ>EC+0/-0/*"").Ò·3533.’·¤-Ò·3'733.kg¨¤ ÿÃaÃ73#3#N.Ãÿÿ7ÿÃIÃ73#7ÃÿA#¿—773#'A5 <-)#tt\\A#¿—773#'U)-< 5—\\tt4 Ì«7#54&#"#54632Ì+!"* D$$$$DE*0/,4 Ì«73326553#"&54*"!+«E$##$EE+//+42Ì‚%7"32776632#'2654&#"#"&546Z     ‚     4̸%)-7"32776632#'2654&#"#"&5465353Z     ttt¸     {)Fºµ 7#"&546325353#"&54632f ttt£  A4&  Fº— 7535353Fttttt„33FºÉ 7'575353ºtt\\ttttD< </./)FºÉ 77'55353F\\tttttX./< <)"Þœ 7'57'57•ss\\Iss\\< </.< </."ßœ 77'5'7'5k\\ttI\\ss+./< <./< <Fºª77'#7'5777¨ (:@&@% ª,!07 !)[Fºª7#7577'577¨(C %8> "ª6 #( /!/F1Ϧ7#"33#"&5463ÏD$$$$DD+//+¦+!")1Ϧ7532654&##532#1D$$$$DD+//*)"!+1϶7#"33#"&546353ÏD$$$$DD,.--Zž¶' &¶1϶7532654&##532#531D$$$$DD--.+Ež+& '+ã·353533ZZ¤¤-Ò·7#'735Ò¤“{z··¦¤ŠÿøåÁ 7#"&54632&&''3667##5å<))<;**;()H(HZ(\);;)*;;")II*Y''FÿøåÁ #7#"&546324&#"326'#"&54632å<))<;**;1!!11!!1B\);;)*;;*"00"!00!  'åŒ 74&#"#4632Ò1!!1;**;'"00"*;;*ø·#'+/49753'#53'33733733#3##'##'73&37#37#3'#7##7##+,"&,Q##_#2#A gE<<<<<<EEEEW!7J%(, ÿÿÃjÔBëÿâI&&'7I $  ÿãK' 74632#"&732654&#"     M(µ‘7'7'7µ#"Œdedd"Þ±7&''6766327&#"Þ?  /- ?O   6  (,19E ,€ÿíÄÌ'7Ä552 opi<ÿí€Ì7'7'7€522\ohi€ÿíäÌ '7'7ä662552 opigopiÿí€Ì 7'7'7'7'7€522633\ogipogi€\ÄÌ7##53Ä-D¹]p<ÿí€\#5353€D-]€\ÕÌ 7##535#35Õ#2U5™=p#P=+ÿí€\ #53535##€U"3"3<_O<€ÿíÐÌ#53ÐPP22ß +<:-0ÿí€Ì#5654'53€P22P-:<+ (Ø· 7#53##5#53ذ°MM°¢Jmm3̨7#53#53Ì™™™™u3—3€ÿì¾Ì'57¾ 55 '¨–Bÿì€Ì7'75'7€5 '' 5–€ÿíÕÌ#53'&&5467#ÕUU!!  /ß +;:+98Â+ÿí€Ì#5654'535#€U!!U/   +:;+ ÑÂ89¥šýÙ 7#&'73#&'73ý'š#%#šZÙ 7#673#673Z / Ö#%#%ÿßZ 7#'67##'67Z "#'#;ÿýĹ"7#"&547&5476732654&Ä &%%"/ ' 2,²("#  %U/ÿíÑš-3=&'654'#"&54747#5334767#63267'6&''32ƒ@ *0( 7; /") ! )''   (X  ÿðåÇ,2<&'654'#"&54747#532747763267'"6&5532†O+6?6  ^a =*.#*   -" 744 3#p!& ! 1ÿõΊ7#"&54732754'7u  l'2',)"#/ =5:Bÿøæ»7#"&547326754&'7k  ‘  >F555$(+()= )Z_)?ÿíÁ™7&'7'6654&#"'632°#10". 0-!(Š © !&ÿåáÉ7&'7'6654&#"'632±&FK7 L14EF8!#=· Ý %!&2+ÿîÕ.7&'7#"&574#"'67''277632327ž6$d #*1$ ? %  ‘© ,4/!ÿñçÇ/7&'7#"&&554&#"'67''277632327¥B8l '3A ([4  ¹Î   1>D4  +ÿïÕ™087&'632'6654&#"#"&5475##53275367#7325Õ  !<  5 "h! b'  *"! <  ÿóçÉ087&'632'654&#"#"&5475##5327'367'7325ç"! '+''? #B$ )~,‚""#9*#(' E  ÿî÷É"(4'327654&#'67'676732#"7&'7Y  ) ˆ    7 ]5 6P-I0T @/ 0ÿîÿÖ(.47&'74'327654&#'67'676732#"7&'7&'7ÿ  œ  ) {    ±  Ñ  7 ]5 6P-I0²  s @/ 0ÿñáË07&#"327#"&546327&'#527'#527'777á= "'5&**%'M-4:3# *F=Hp   ÿñøÛ <7&'7&'7&#"327#"&546327&'#527'#527'777ø     = "'5&**%'M-4:3# *F=H·    Q   <ÿè¿Ï&'&546767¿N "7I GH  0>A<ÿèÿÖ7&'7&'&5467677&'7ÿ  5N "7I GC  ±  ÎH  0>A‹  ÿëëÆ 7&547"'65'#'5327'767>Ä"71'Ã2D<>L4E8< 1621ÿëÿÖ ,7&'7&'7'&547"'65'#'5327'767ÿ    ¥Ä"71'´  "   2D<>L4E8< 1621&ÿüÚ­7"'5327#"&547327ÌW$!:+!2>* 25–« &ÿüøÜ $7&'7&'7"'5327#"&547327ø    T$!:(!2>* 25¹  #  (« ÿ÷ãÌ'7&#"327#"&546327&'##'327'77ãD.#.+ **(%$1@ M‘2  % ,ÿ÷ñÛ 47&'7&'7&#"327#"&546327&'##'327'77ñ    D.#.+ **(%  $1@ M¹    62 % ,?ÿöèÇ7#"&54733267è /("#' U5*z“%,?ÿöèÌ7&'7#"&54733267'&'7â   /("#'  ª  i5*z“%,@  ÿëðÌ ,7'67'#"&546325574'3732654&#"ðR ", ;wvSš  –& &-( T  ÿëôÛ ,87&'7&'7'67'#"&546325574'3732654&#"ô    $R ", ;wvSš  »    5& &-( T  ÿöñÆ)7#"'72675327#"&57577375537ñ2Q *)523Q2z( "@ %@43 ÿöÿÛ 67&'7&'7#"'732675327#"&57577375537ÿ    2Q *)523Q2»  "  B( "@ %@43 ÿõîÃ'7#"37#"&5467''677''77677î. 7* 1#+O^rO("8b >9ÿõýÜ 37&'7&'7#"37#"&5467''677''77677ý    . 7* 1#+O^rO("8»  $  ^ >9ÿòéÉ,7'674'276767&#"'632#"&54732((S;1. $% a87Y$=o  ÿòñÏ(87&'7'674'276767&'7&#"'632#"&54732ñ  T((G  ;1. $% ®  2a87Y$  Mo  ÿõãÍ'7632#"''32654&#"'67#527767ãY %!$-,"" !)$ 9 J&•  #5*ÿõþÜ57&'7632#"''32654&#"'67#527767&'727þ  Y %!$-,"" !)$ 9 6*  »  :  #5* -ÿùÓ…'654&#"'632XjIH!6Jÿÿæ®'6654&#"'632OQ4"+CM$2)@%$$&.3ÿÿþÚ 7&'7&'7'6654&#"'632þ    ‡Q4"+CM$2)@¾  "  Á%$$&.3ÿóéµ7"3&5467''é6 )*7ssµ4K2 ÿóîµ7"3&5467''&'7&'7é6 )*7ss×    µ4K2 4  #  *ÿöàÈ7#"&547&576732à::+9Kh5 /)6!&)#*ÿöîÓ#7&'7#"&547&5767327&'7î  ::+9Kh5 /3  °  ³)6!&)#–  ÿïðÉ,77'67#527767&'7&'#"&54632&'7&#"326Ž# _ * !) 3  ‘M. )F' ’ *N  &ÿðéÀ 7&547"563#"&54732D±H53J !2& &¾8B&,'(M40Ž  ÿóøÆ,2;E7'#"&54632654&###"&547&'767672'6&'327&#"32ø  0"( s  }      ##KG/ '*p :'7 ÿòøÄ/97'#"&54632654&#"#7'677'3737632&#"32ø 1)*  /$)   #;cF%((* /&2  ÿõìµ&&'654&##"&54632'3267654#"ŽP,"$@0-;Á % F(?.+>4%TD :'"ÿóöÇ(37&'#"&54632''27'367&#"32'&547ö 2U!)1 !m N,+ L   ¹0[.1M<"ÿóþÛ .9D7&'7&'7&'#"&546324'#"'27'367&#"326'&547þ     ,3  l½  #  « 9,+ V  °0[.1M<"ÿóþÚ :EP74632#"&732654&#"&'#"&546324'#"'27'367&#"326'&547É   ,3  lÀ   ´ 9,+ V  °0[.1M<ÿïõ¾'7'&'#"&5467''7732654'7õ/%!'?$ I,3(#9 C!0023ÿïþÛ 37&'7&'7'&'#"&5467''7732654'7þ    /%!'?$ ¶  %  s,3(#9 C!0023ÿïüÖ ?74632#"&732654&#"'&'#"&5467''7732654'7Ç   /%!'?$ ¼   x,3(#9 C!0023 ÿóôÁ &-7&'7732654.5467#"7&'7''67¸9<J@     ˆ¢ ¯¿  "   G! F (: ÿóûÚ ,297&'7&'7&'7732654.5467#"7&'7''67û    #9<J@     ˆ¢ ·  &   ¿  "   G! F (: ÿóô× 8>E74632#"&732654&#"&'7732654.5467#"7&'7''67½  9<J@     ˆ¢ ½   ¿  "   G! F (:ò¢7&'&#"'>32ò(C 9 8  d P E Clò¾ 7&'7&'7&'&#"'>32Û    <(C 9 8  d  "  Š P E ClòÆ (74632#"&732654&#"&'&#"'>32—  M(C 9 8  d¬   ™ P E Cl ÿðïÄ.87#'47&'#"&54632'#'275##53277#'&#"32? Æ  +)*(,# +C&0  Â-K*0UH7½ 8$$C   ÿðþÛ0:C7&'7&'#"&54632'#'275##5327'7'7#'&#"32'#'47þ    +)*(,# 7  '0  e ¸  Å 8$   #C  º-K*0UG8 ÿðüÙ1=GP77#&'#"&54632'#'275##5327&54632#"'32654&#"&#"32'#'47Ò'  +)*(,# 2  +  e ©#C  8$  ª º-K*0UG8ÿòãÅ%07&'#"&546325#53275#535377#&#"326ã) 6 H1T5NNNN0G  +# *  ÿéø¶"+7&''667&'#"&54637''773'326ø %*$ %[„ C # ! W&2(! 9A  ÿôíÈ-9?7&&54677#5357327654'7#"&54'32654&#"7&'79 /-.+ /1  ½'     %+ ,  +#!ÿòçÊ%/'654&'#"&547&'76767'76'&'32 X 3&%*k   ;"CE#1 +"K†%*J -ÿòèÇ+7"5747#?676732654'7#"&54R&=>?6=%5B )((O *'!#    %ÿîÛœ(7732654&#"'''67'767'7632#"ƒ   $     ( jj &&ÿìðÆ(77232654&#"'''7'767'7632#"„$  -0    $!3"ˆˆ  23&*!0ÿèÐ'0'67&'67654'&547367'7#76654&ˆ  '(#  %2 %,"$- !!c: ÿêäÇ)27'67&'7654'&54733367'7#7654&” (  5#(+#3 6%*3-<? ( (|8 -1ÿòÏ—"7&'#"&54632&537&#"326Ï  <;&;    >+#7 ÿôãÂ$7&'#"&54632&577&#"326ã $( M31H  q G  +ÿõÕÃ!7&'7'32654&#"'67632#"«2.8=''1 %$!&04«Å E6  2ÿïÎÂ'67654&#"'632'&547k 5U ? %&@$¿+&$&/'$ÿôܾ)373654&#"&'7''767632#"&54632&#"327 #3 q1(gP)1-#  `@ "&  ÿñùË.7#"&574&#"#47'677"'757632327ù   !/  ,/!  .X 6:02 (%.( ,Z'&ÿöÚ¼$'32654&#"'67''77632#">$$#1-578"m J&/- !(2= #(ÿðØš#'6654&#"#7'677'7737632…&' !" &#29 $"  DÿóìÆ$'6654&#"#67'677'7733632ˆ 1&#/ )* .%% # 1b0 '-* (&"Tÿòå¼&.97654&###"&5467677''732#"&54632'326&#"327Æ "" ,&;Y ,.(![8 [   H/!19(! ( H2 ,)ÿíï¿DN&#"#&#"'67&546326654&#"'675577632##63236632'4&#"6ï "$*! 69Xo 9 *)  !_   -#'   "(  ÿóåÉ27#53327#"&547&&#"'67#57767366327åB9 -33#M "D >"C?W,& $ !"+    ÿóæÆ!7#"&574&#"'67632327æ!  (# #Z<*) C€K  2* WÿåöÓ #7&'7&'7&'7'6654&#"'632ö  9&FKW  ƒ L14EF8!#=µ     Ï %!&2³õÚ 7&'7&'7õ    ³  !  Á¦öÛ 74632#"&732654&#"Á  Á   DÚ 7&'7&'7D  ³  !  ¦9Û 74632#"&732654&#"  Á   D ¼¨ 7#"'677&'7¼] &,1@ QWD б7&'7#"'677&'77&'7Ð  ] &,1>  ’  e QW/  0ÿîד7'67'577'6655Ñ"! FA“P& z#(#$ &ÿðë¼ 7'67''77'6655å, ­ºd!. '¡5%*0+- &$.'ÿñÁž 7#5'67Á U3p_*7ÿòÖÈ 7#5'67Ö((. m=¸Ž}6A7ÿíÉœ7'667##53533É,: 4$c<>q?6 (32I%ÿîÛÉ7'6675##53533Û5I >0„NP”PE 57@X%%,ÿýÔ‚ #535#53#3Ô¨H?”>IXXìª 7#535#53#3ìØ`O¶O`ww+ÿïÕš7#"''3255'67#57533Õ. #28#U^.dY B(+ ÿòéÇ7##"''325''67'7'37é9 )HL,pz9‡u]1,+8))ÿôÚÉ7#"''32677''667#5653Ú  F&&"#AC›Y, !B5B9/ÿôï× )7&'7&'7#"''32677''667#5653ï      F&&"#AC·     Y, !B5B9/ÿïçÈ7''7''7'777çW  ^]RPRPYISS-)(-ÿïþÚ 7&'7&'7''7''7'777þ     S  ^]RPMLU¶    ySS-)(-ÿíÖË7'667''6777Ò8608Z*T’EGA7# (G ÿíþ×"7&'7'667''67777&'7þ  "9608Z*T  ¶  7DH@8# (G   ÿñëË7#'6677'677ëA-4 .%A* ‚.9.&+C ÿñôÕ 7&'7&'7#'6677'677ô    A-4 .%A* ±    9.9.&+C (ÿöØ© #5#535#53ؘ˜•­ s$ÿöÿÜ 7&'7&'7#5#535#53ÿ    ——”­º  %  ÆsÿòíÇ7#'6655##5#53533'33í-d4(O..O-zO +'EE2255ÿòÛ $%&'7&'7#'6655##5#53533'33    -d4(O..O-¹  %  BO +'EE2255ÿðåÁ 7&'7'667&'7€) +ƒRD BKf.& /,'!" XT:7ÿñãÀ 7'667&'7ãFH e¸¦!UT` 8! (!ÿñþÜ 7&'7&'7&'77&'667þ    y‡ JG H= ¼    ` 9 +PS ERÿîâÌ7&'67&'767'#'6737ÞB: 8! + R1;N˜CN ' --76 ÿîÿÜ )7&'7&'7&'67&'767'#'6737ÿ     A; 9  , S2<Nº    %CN ' $-76 ÿñéÂ7#&'6655#535'673é[#1 ( __!)j4 *[[*- %#( +ÿñÿÜ&7&'7&'3#&'6655#535'677ÿ    )\\"2 )__!)g4 ¸  $  +*- &"( ,ÿïÔ”7&'77'>7&'7 M.0 ',[ Y  #?/" &>H  ÿîçº7&'77&'6767&'7’g  -ªµš(=D*.(#ÿðÜÁ 7&'7&'7&'7ÕQR^>;ULX?rm¨ X l  ÿïìÅ'&'76767&'7ì 9y8;:ÿîîÂ7#"'77367î/3O7_) ¹~5K+ÿöÕ± #5##535#Õzªz »–8ÿîÈ 7'>7##5È'. '#`@3 %-8O%ÿñÚ¹ 7'665##5Ú18 E-¹LE( >OKbÿðìÇ7##5#535#53533#3#5#ì5‹5*€--5M>&66L&&LLLê´ 7'67'#537'53533Þ"š Ô\_–0 $”hi$ÿðÜ·7&'667#537'#5Ü:G 40 “6g·bS +$&ÿóå¼ 7&'7'667t"( +’QSKKŒJD =E&ÿîÿÛ 7&'7&'7'6677##53533ÿ    6H ?/‚KP½    1PE 47@X%%3ÿïÍ›7#"''32675#'667#57653Í  1 45wH$3(3)#%*ÿïÖž7#'6655#'673Ö4#+ %0#qa"- ##4hE˜t 74632#"&h  \  #NÜk7#53ܹ¹NF&¶›7&'7¶(5066.&F&Ñ· 7&'7&'7&'7Ñ    (50˜  %  b6.& ÿõàÁ7#"'53267#'67à   ¢ ‹a L #ÿõÝÇ7''67&'767#'673Ý6,>8%(% -(Š  † !." % +'&Ú´7#54##"#546332Ú s Œ  ž "Þ®7#"&554633#"33Þ© ¨˜ š   m ÿõâÂ7#"'53265#'67#'67â F@ 7HŒg0 SY'%I  ÿûå¾7&''67#53673#7&'7å” )LV  Zd*y   ,9 /4 %(ÿùØÁ7#"''3267#'7#53Ø  A fu Z.  B1-ÿ÷áÃ7#"'53267#'67#'67367á  9F =O  A Šg, K]M+ÿ÷ÕÁ 7&'677&'67ur 0//< 1.1Š*1&- ./24ÿûã·7#3#"'532677#'67#53ãpr j=Ç¡ 9(ÿõå¹ 7#'6653å§¢EG!JN7ÿöÉÁ#5#"&55332753Éc  V Lk hDÿó¼Ä 7&'67¼%@6%"-4,4;6/2+ÿ÷äº7##5#53äYXÈ£¬¬ä¾3#535#"&553335332553##3äÉZ; .*  7X4 OD __ DO 4ÿõâÃ7'67#5'67©:5 93H"*%/eP²/*1"_O%Rÿøâ¿7##'6732554##532â u%"~ ‹˜ t D$#I "  )ÿþ×»!7##"&554633254##"332'&'7× ‰ Š  o o ! # ˜ Š  8  ÿöà¼7#"''3265##5#5à   [7¼c9±±ÿôãÆ7#3'67#'7#53673ãuj: .w8C l†(D 5/#ä¼7''6767'7äLN 7"3c  RT CR )ÿöâ¾ 7#5&'767â*1,&&*',¬()ee4 "-#*ÿûå¸7#'"327#"&54635#53åT5A(+''_Ê¢9> H!!0ÿõæÆ7#"3267#"&5475#53533æT @'*K``T‰' A!4!((ÿùäÀ7##5#33#"&55#53533533ä.A r ))A.p::X  f;;;;ÿ÷ä¿7#3'67#'67#'67#53#36ä@ 00B ;= "ÈV9 ;ª 87+4O?%$ÿüåÁ7&''7å:Yp HaqÿüâÃ7&''67'6767677'7âTC 11: ( $,4Gf   5)-" G? #ÿþâº7&''67&'767#53â',=9%#/ *,‰’&-  #1<ÿóä¼7'67#'67#533'#36ä97Œ 0œ $%GE ZI3$ 0??ÿôá¾ 7'67#'673á œ ‘eG* $> %,)ÿ÷äÀ7#33#"&55'67#53673äN šll!ìž 7'6753&'3#5#· % .* !žXDc,AAdl2Ñ™733#2‹Ÿ™i"åš7#&''675#5327Û&0 $ $ ,(X#-š :*&8 hë¢ *7#53277#553#"&546"32654&ʸ...šC'MdQ"Ûš 73#67535#53#5##535##O< $,/<;¤///š@*P'~~'?--$ìž 7'6753&'3#67535#· % .* !šS@ (+@?c,AAd?+Q#Þš 73#67535##3#53#$R?"&.1?>µ>CWR>šB(N%}"àš7#5#3#67535##53'3373Û\\O<!)-<;½ešA)OVPPPP"ê¢#/7#53#27535##553#"&546"32654&È7nM: *,98Çw2"  ¢A)O  1Ì› 7#5##535#Ìsss›}}dQ3Ì™ 73353#5##735#3qqqq™&&{)&Ü™ 73353#5##735#73353#5##735#&1111J0000™&&},9&&},&îž 7'6753&'3353#5##735#¸ " *+ !š1111W%:!!;h&&},"Ùž7'66753&†E )/%$ 8h2 6"!4 íž 7'6753&''6753&° ) 13 'j # +! ^(; </)=07Ê«753#"&546"32654&w!&#$&!)›"##"#Þ›7#&''67#5ÙNO >A PO›D2/Cë›$7#&''675#5##&''6675#5ä) $ # +" '  " $›* *(; $ #( * #Þª7#5#&''667#5«SMN ?B ',Oª&2&$&-Í› 73#5'75#- ŠŒŒ›}=1К 7#3#3#5Ή……‹Ÿš&}(×›7#5#53'3373Р§¯0  ) ›kOOOO) Ù© 7#5#553#"&54"32654& D}°N5! ?©#& && jÿñºÈ733##j<<ÈYk[ÿñ¬È 73353#5##[))Ä\`×dWiÿñ¹È 733#3##i<<<<È=)JZÿñ¬È 73353#5##735#Z****Ä59×K>Q-Gÿñ§È73#5#53“LLÈ×q7ÿñ¹È 73#'53#5#5¥3;È×{XÇ\Fÿñ§È 73#5#535#53“MMMMÈ×E+2ÿñ¹È73#'3#5#535#53¥3@@@@È×ÓÇ:*&ë—7#5353ëÕ`9^^ÿñêÈ733##5'7537£33Ž;BÈac> dbÿñ×È73753353#5##5'7B;|0“Ny]b×bU9 ÿñËÈ 73#5'7537·œ@KÈ×D^[(íš 7#53'3373íÚ>4  ;WW__ÿñë’7##5#5ëbb’ÿñÎÈ73#5#53''7#º55f:Ž=È×^nÿñ×È 73#'3#5#53''7#Ã-**M/p*È×ÓÆS eÿñÌÈ 73#5#5'7¸BL¥È×ba ÿïë‘ 7##5##5#5ë;9:‘ŒŒ<íO7#5íÙOÿñÇÈ73#5'7³ È×TvÿñŠÈ73#vÈ×%æ™ 733#'327#†L`a""(3™i|i !àš7#3#5327#ÜDH[d!'0šW}i ìž 7'6753&'327#¸ ' 1) !¤"!(2b,AAei æ¥ 7#73'327#­9t;;® &0¥…[Jhi çž$7'6753&&5'3#67535#73#5#¤  =)%'))A8$6&'5 #  u?+Qlçš7#3#5#3#67535#ãCGZpS>"&.1>?šW}A)Oïž*.7'6753&&5'3#27535#53#5##535#­  ˜9%$%n6&'6 "  v@*P'~~'?--î¥7#73'3#67535#µ9t;:´T? #,/?@¥…[JiC'Míš  ,73#67535#7#553#"&546"32654&T?"%-1?@Ðs1  šC'M$   #Ûš 753#5##535#'#5##535#Ç///...s'~~'?--f||bOêž 7'6753&&'#5##535#® " (3 >000U#9#$8 T||bO#ê¥ 7#73'#5##535#³7o87U...¥…[Ji||bOBÿöÀ¿ &7#5##535#53#"&546"32654&ÀVVV   !¿UU=*V  Þš73#5#53#5##535#‰UA$777šl&~~&>,,çš7#3#553#5##535#âEJ]$777šW}'&~~&>,,êœ &7'6753&&5'53#5##535#73#5#l  ,{C/9 9 &  M'~~'?--flíœ (7'6753&&5'53#5##535#7#3#5l  ,º")=9 9 &  M'~~'?--fW}îš 7#&''675#553#5##535#ã'2 % & ,'"777š7'"6 '&~~&>,,æš 7#3#53#'53#5##535#á?DWR?:777h%} &~~&>,,CÿöÀà *753#5##535#53#"&546"32654&¬UUU  "­[[-*  'ÿöÛà .:753#5##535#'53#5##535#53#"&546"32654&Ç(((<(((;  "­ZZ,,ZZ,+  àž 7'6753&73#5#[ ' /4 (_K_(==dlæš 7'6753&733#J !*/ #3L`V#77hiæš 7'6753&7#3#5J !*/ #ŽFK_V#77iW}Ûš 7'6753&753#5##535#J !*- "t///V#78A'~~'?-- õš 7'6753&7#&''675#5D ! '. $Ÿ'1 & % .'U"7 7h 9(%7 *Ѥ7#73P§W6f¤‡dQêª)5753#"&546"32654&753#"&546"32654&C  X  ›# $$ #$ $$ $B¾¢753#"&546"32654&v"!"Ž æ¢'753#"&546"32654&'6753&?  ] ' /* •  !!!*AAç¥ 7#73'53#"&546"32654&®9s:;Œ  ¥…[Jd  !!!,ÿôÒ¿*7#5#53'337353#"&546"32654&Ê–ž¦*,  3  "¿B''''(  ò£ *9E7#53#5#53#553#"&546"32654&753#"&546"32654&_5ª6!lámH  _  £!    í¢7#553#"&546"32654&íÙc¢6ÿñíÈ733#3##5'7537537¬,,--–($&ÈG(BA _^ZYÿóÍÉ73753753353#5##5'7735#5{#p–SQOz7=ÖOD8 #ÿñÊÈ73#5'7537537¶š'%(È×A _^[YÿñÌÈ 73#5#535#53''7#5#¸""""w)”$È×C qplÿñÔÈ73#'3#5#535#53''7#5#À(a!wÈ×ÓÆ5gfdÿñÌÈ73#5#5#5'7¸+$*¤È׃cb]\ gC™v 7"&54632€  C  IÿñÊÈ73#'"&54632¶S  È×T  ÿíþÌ #654'3'#&5473#5#áÐ |g0>@1-DC´1@>0+CD-6{hÿíþÌ #654'3'#&54733#áÐ)n‚0>@1-DC´1@>0+CD-3eÿíþÌ #654'3'#&547#3#5áСbh|0>@1-DC´1@>0+CD-1TzÿíþÌ #654'3'#&5473#3#535#áÐ'r\dx\^0>@1-DC´1@>0+CD-2C(NÿíþÌ #654'3'#&547#5##535#áЗBBB0>@1-DC´1@>0+CD-1~~fSÿíþÌ ##654'3'#&54753#5##535#áЈKKK0>@1-DC´1@>0+CD-W${{$>++ÿíþÌ "#654'3'#&547'66753&áÐd/ !  00>@1-DC´1@>0+CD-c1 2!2 ÿíþÌ ".#654'3'#&54753#"&546"32654&áÐX!" 0>@1-DC´1@>0+CD-/$$$#ÿíþÌ "#654'3'#&547#&''67#5áТ8> 0 . 3 70>@1-DC´1@>0+CD-2A1-AÿíþÌ '#654'3'#&547#5#&&''67#5áЄIi:= " - 1 60>@1-DC´1@>0+CD-'6 &(6ÿíþÌ #654'3'#&5473#5'75#áÐ&}nqi0>@1-DC´1@>0+CD-6o5ÿíþÌ #654'3'#&547#3#3#5áПd^^n‚0>@1-DC´1@>0+CD-2&~ÿíþÌ ##654'3'#&547#5#53'3373áРˆ  &0>@1-DC´1@>0+CD-2kOOOOÿíþÌ (4#654'3'#&547#5#553#"&54"32654&áЀCp•@1;0>@1-DC´1@>0+CD-& $ $$ ÿíþÌ ##654'3'#&54733##''67#53áЇh =?T0>@1-DC´1@>0+CD-SRD[ÿíþÌ ##654'3'#&54733##'327#áЇu,-2>0>@1-DC´1@>0+CD-SR›]  ÿíþÌ %#654'3'#&54733##'3#27#áЇtZF3!390>@1-DC´1@>0+CD-SR¢PÿíþÌ )#654'3'#&54733##'3#27535#áЇrR@&+26@>0>@1-DC´1@>0+CD-SR¥D,RÿíþÌ ##654'3'#&54733##'3#75#áЇtUUB.0>@1-DC´1@>0+CD-SR¡tNNÿíþÌ #'#654'3'#&54733##'353#535#áЇ`/W//0>@1-DC´1@>0+CD-SR|%vvc++ÿíþÌ *#654'3'#&54733##''66753&áЇC #  0>@1-DC´1@>0+CD-SRb* 0"!!. ÿíþÌ *6#654'3'#&54733##'53#"&546"32654&áЇQ  0>@1-DC´1@>0+CD-SR¬ %"'&#&ÿíþÌ ,#654'3'#&54733##'#&''675#5áЇ$/ $ % /'0>@1-DC´1@>0+CD-SR›6$"7ÿíþÌ /#654'3'#&54733##'#5#&''67#5áЇEV'(  $ +*0>@1-DC´1@>0+CD-SR­'.-ÿíþÌ (#654'3'#&54733##'67#53'67'áЇ0BXH &?0>@1-DC´1@>0+CD-SRu a,( ÿíþÌ )#654'3'#&54733##'3#3#27#áЇsYE@@-*70>@1-DC´1@>0+CD-SR&ÿíþÌ +#654'3'#&54733##'#537737'7áЇdl0>@1-DC´1@>0+CD-SR™"?=<ÿíþÌ #0<#654'3'#&54733##'#5#553#"&54"32654&áЇ&;Tl-$. 0>@1-DC´1@>0+CD-SR­#! #$ ÿíþÌ "*#654'3'#&547#&''67#5##5#5áÐ¥:I :7 > 8™MM0>@1-DC´1@>0+CD-  ^RRÿÝÿÛ 74632#"&732654&#"73#5#J54KK45J D//CC/0C2€l\5JJ55JK4/DD/0CCkXÿÝÿÛ 74632#"&732654&#"733#J54KK45J D//CC/0C7n‚\5JJ55JK4/DD/0CCYÿÝÿÛ 74632#"&732654&#"7#3#5J54KK45J D//CC/0C±em\5JJ55JK4/DD/0CC HnÿÝÿÛ #74632#"&732654&#"73#3#535#J54KK45J D//CC/0C8{gn‚gg\5JJ55JK4/DD/0CC ?!GÿÝÿÛ #74632#"&732654&#"7#5##535#J54KK45J D//CC/0C±TTT\5JJ55JK4/DD/0CC rrZGÿÝÿÛ #'74632#"&732654&#"53#5##535#J54KK45J D//CC/0CSSS\5JJ55JK4/DD/0CCooA..ÿÝÿÛ &74632#"&732654&#"'66753&J54KK45J D//CC/0Cv/ !  0\5JJ55JK4/DD/0CC0$ )) ÿÝÿÛ &274632#"&732654&#"753#"&546"32654&J54KK45J D//CC/0Cl"!!\5JJ55JK4/DD/0CC !"! ÿÝÿÛ (74632#"&732654&#"7#&''675#5J54KK45J D//CC/0C¶8@ 1 - 87\5JJ55JK4/DD/0CC 4#!2ÿÝÿÛ *74632#"&732654&#"7#5#&''67#5J54KK45J D//CC/0C”Bd8? 1 - 3 8\5JJ55JK4/DD/0CC+ +ÿÝÿÛ !74632#"&732654&#"73#5'75#J54KK45J D//CC/0C9|nqh\5JJ55JK4/DD/0CC o5ÿÝÿÛ #74632#"&732654&#"7#3#3#5J54KK45J D//CC/0C²f``j~\5JJ55JK4/DD/0CC nÿÝÿÛ '74632#"&732654&#"7#5#53'3373J54KK45J D//CC/0C¶ƒ‹“#  "  \5JJ55JK4/DD/0CC Z;;;;ÿÝÿÛ -974632#"&732654&#"7#5#553#"&54"32654&J54KK45J D//CC/0C“@gŽ?6\5JJ55JK4/DD/0CC# " ÿÝÿÛ '74632#"&732654&#"733##''67#53J54KK45J D//CC/0C”\ 55K\5JJ55JK4/DD/0CCCB>VÿÝÿÛ '74632#"&732654&#"733##'327#J54KK45J D//CC/0C”c$#*4\5JJ55JK4/DD/0CC@EˆNÿÝÿÛ )74632#"&732654&#"733##'3#27#J54KK45J D//CC/0C”eP<).0\5JJ55JK4/DD/0CC@E‹E  ÿÝÿÛ -74632#"&732654&#"733##'3#67535#J54KK45J D//CC/0C”eP="&/1=<\5JJ55JK4/DD/0CC@EŒ< FÿÝÿÛ #'74632#"&732654&#"733##'3#75#J54KK45J D//CC/0C”eMM9%\5JJ55JK4/DD/0CC>GŠc==ÿÝÿÛ '+74632#"&732654&#"733##'353#535#J54KK45J D//CC/0C”Q"J""\5JJ55JK4/DD/0CC>GniiV%%ÿÝÿÛ -74632#"&732654&#"733##''6753&J54KK45J D//CC/0C”: " + \5JJ55JK4/DD/0CC@EP"6$ÿÝÿÛ &2:74632#"&732654&#"753#"&546"32654&733##J54KK45J D//CC/0CM 5\5JJ55JK4/DD/0CC @EÿÝÿÛ 074632#"&732654&#"733##'#&''675#5J54KK45J D//CC/0C”'  % ,!\5JJ55JK4/DD/0CC@EŽ3 3ÿÝÿÛ #474632#"&732654&#"733##'#5#&''675#5J54KK45J D//CC/0C”4D!&  ! '!\5JJ55JK4/DD/0CC@E‘# $ÿÝÿÛ ,74632#"&732654&#"733##'65#53'67'J54KK45J D//CC/0C”*7M@ $ 7\5JJ55JK4/DD/0CC@EkV'&ÿÝÿÛ -74632#"&732654&#"733##'3#3#27#J54KK45J D//CC/0C”cN:33%1+\5JJ55JK4/DD/0CC@EŽÿÝÿÛ #/74632#"&732654&#"733##'#537737'7J54KK45J D//CC/0C” Y d\5JJ55JK4/DD/0CC@EŒ:97ÿÝÿÛ #'4@74632#"&732654&#"733##'#5#553#"&54"32654&J54KK45J D//CC/0C” 6I^%' \5JJ55JK4/DD/0CC@E“  ÿòëÈ)7#4632#&&#"3773#'#53#"'33267#I5;/J.'1#' $! JE<+M)!02U6=G .%#!2&T#4>F-%ÿáôÑ,77'67'577'6655#53#"'75367''6554'3t  %3a 4Àeeu L Á    "Š! b=    ÿâôÕ $/377#"''32657#'67#553#5##535#&'66765#53#53t "' #"#ª<X<=6,Êee½0)#2 +SaL>>*>*   @IF> ÿëíÖ7''7''7'777#5##535#w-10+**).w<X<‘++µaL>>ÿãôÚ!%6H7&'7'67'#'67377&'7'5&'667'#57''276767&'7Š  0  ) rTT "%#PU z> !  É #& 4 &   .  »O U ÿäøÔ &.C7'67'327#"&&55'7577&'7'667#'65#535'673t  #; E M+,''r/&22&8/¬ -    ($%£   ÿãøÔ &.87'67'327#"&&55'7577&'7'667&'#53t  #; E M+,''q#¬ -    ($%¡ =r&.ÿáýÕ %07&'#537&'7&'7#"'75367''6554'3y#› L  =r&  ¬! b=  .ÿçøÕ 7&'#53&'7'667y#] M+,'' =r&p  (#  % ÿã÷Û #-LRZ74632#"&73254#"&'7''65#53&'#53''67'327#"&55'75377&'7'667d . Òee ‘    # / 5  Îe 'E=* 4IF ÿâøÕ %39?HNR7'6655&'7#53&'7&'7&'7#"'75367'&'7&'77'6=3'65#53å h).2bA.-+"<9™   “ } › ŒFFÕ>4 CG2 7 9 :#^U  ` (C>, "D' #?8ÿáýÕ!1<7&''67&'767#53&'#53#"'75367''6554'3x ) &ee{# L ‡      † =r&! b=  ÿáýÖ #-=H7&'66765#53&'77&'667&'7&'#53#"'75367''6554'3f6,ž  +" .  )# L Ö?*   AJG1(!  $! ’ =r&! b=  ÿãëÏ  *7'665##5&'77&'667&'7&'#53p+#A¸  +" .  )#Ï5,  )'5-(!  $! ’ =r& ÿÃõ¸ +3#'##73'##533632#"'32654&#"õ13 ‚   99¸kO3;:Æ$#$#L ô¸ !3#'##73'&'#54&#"#5336632ô234  99¸k8OMYG‰ ÿÃþ¸ -3#'##73'#"'##"&'#73326773327þ23 ,        99¸kO36FÆX Tb ü¸ .3#'##73'#54&#"#54&#"#5336323632ü13 3  99¸kO 3MY YY Y‰ öà 3#'#5373#'##73'&'n( 10»14 ? 5ÃZ!A;N99¸k3 8 ÿþôÃ&<3#53#'##"&5467754&#"'6325326'&#"327#"&54632ô&  S   Ãà   '  a$""&ÿþýÃ)3I3#53#'#533773#'#"&547754&#"'6325326'&#"327#"&54632ý¤! (² )  7   ÃÃ>6ÃW *9P(   '   `###%ÿÃë· (7#3##53##533632#"'32654&#"ëHCC_¿  ¢@M·¥:Æ#$$#Lë· 7#3##53#54&#"#533632ëGBB_t  ¢@M··YG‰ ÿÃô· *7#3##53#"'##"&'#73326773327ôGBB_j       ¢@M· FÆX Tb  ÿÃöŒ%1>`7#"'332#"&547&547&54632332654&#"#"32654&'#"'##"&'#73326773327ö !   +T #  L       {   ,j  /ÆX  Tb  ÿÃõŒ%1>`7#"'332#"&547&547&54632332654&#"#"32654&#54&#"#54&#"#5336323632õ  !  +U #  V  {   ,j  Y YY Y‰ÿÃïà 2>K3#'#53737#"'332#"&547&547&54632332654&#"#"32654&u' 2/¬   !  ,U #  ? 5ÃZ!A;-   ,j  ñ· 3#5##53353#57#533‚EEo\B>VBDVV·MM·hfûà #3#'#53373#5##53353#57#533[!'(ƒ&&H@(&<(*>7ÃW;:OVV·MM·hfü· '3#5##53353#77##'##53373#57#533¸&&Y@)&<()VV·MM·8-ee-8··hfÿþüº%/7&#"3275#53#"&54632#5##53353#57#533Z &h&&G@)&<'(¯ "'%%6Y .0.0ºVV·MM·hfü· 3#5##53353##5#53#57#533´&&Z  V¢@(%;')VV·MM¢¢·hfÿÃøº"C7327#"'567547632'6654&#"#"'##"&'#73326773327±   %$ *         #9.!%=0 +JFÆX Tb ÿýúº!57327#"'56754632'6654&#"#547##'##53373³  ( &# 1!!  # ,Y%= 1 *bc,’’*c·ŠŠ ÿýðÃ8B3#'##"&546323&55354&#"326327#"'567547632'6654&#"w  J   &# $#"%7ƒ  #:. %= 1 *ÿýëà %/3#'#5373327#"'567547632'6654&#"y' 20^    &# ? 5ÃZ!A;/  #:-!%= 1 *ñÄ77&#"3##5#5754632#54&#"#54&#"#5336323632j  •  À xx  Ä[ ZZV‰ñŒ!53#54&#"#54&#"#5336323632#54&#"#533632ñ  €  Y YY Y‰ZYG‰ÿÃúŒ!B3#54&#"#54&#"#5336323632#"'##"&'#73326773327ú  v       Y YY Y‰CFÆX Tb  ÷Œ!C3#54&#"#54&#"#5336323632#54&#"#54&#"#5336323632÷  |  Y YY Y‰ZY YY Y‰ ÿþôŒ!73#54&#"#54&#"#5336323632'&#"327#"&54632ô     [ ZZV‰)$""&ðà /3#'#53373#54&#"#54&#"#5336323632t( 10¯  ? 5ÃZ!A;NY YY Y‰ úç5W7#57654#"'6323#54&#"#54&#"#5336323632#54&#"#54&#"#5336323632ú3   "  |  ˜   ¤Y YY Y‰ZY YY Y‰ ÿþúç5K7#57654#"'6323#54&#"#54&#"#5336323632'&#"327#"&54632ú3   "     ˜   ¤[ ZZV‰)$""&"úç57#57654#"'6323#54&#"#54&#"#5336323632ú3   "     ˜   ¤YLYG‰úç!C7#57654#"'6323#'#53373#54&#"#54&#"#5336323632ú3   "†( 10¯  ˜   ¤? 5ÃZ!A;NY YY Y‰ úç!Ce7532654&##532654#"'632#"#54&#"#54&#"#5336323632#54&#"#54&#"#5336323632Æ       '  |  œ      —Y YY Y‰ZY YY Y‰ ÿþúç!CY7532654&##532654#"'632#"#54&#"#54&#"#5336323632'&#"327#"&54632Æ       $     œ      —[ ZZV‰)$""&"úç!C7532654&##532654#"'632#"#54&#"#54&#"#5336323632Æ            œ      —YLYG‰úç!/Q7532654&##532654#"'632#"#'#53373#54&#"#54&#"#5336323632Æ       \( 10¯  œ      —? 5ÃZ!A;NY YY Y‰ÿýéÄ%G7#7#54&#"#54&#"#5336323632532654.54632&#"#"În mQ  .    ÄÆÆzG GG Gn     ÿþþÔ8Y7#57654#"'6323'#7532654.54632&#"#"'#54&#"#54&#"#533632632ö,  97 7 #  A  Ž    +ÆÆ¿      Y YY Y‰ÿþå· ,67#532#'32654&##'##"&5467754&#"'63253262+! ¢  GG·\H£   '  ÿþüà !9B3#'#53373#532#'32654&##'#"&5467754&#"'6325326[!'(D& |    >7ÃW;:G·\H£  4 ÿþý· %>G7#532#'32654&##57#'##53373#'##"&5467754&#"'6325326z" %Ÿ    GG·\H£8-ee# 8··  4 ÿþüº!(3<7&#"3275#53#"&54632#'##73'&'#532#'32654&#Y  '± ! &K" ¯ "'%%6Y .0.0º99¸k,  ,G·\HÿþùÃ8AQ3#'##"&546323'5354&#"326#'##"&5467754&#"'6325326'&#"#533632ù   H   $ 9  #$"%4‡&   '  gJ‰ÿþýÄ"@Ybq7#7#'##"&546323'5354&#"326532654.54632&#"#"'#'##"&5467754&#"'6325326'&#"#53632Õ+ +,   :    %o     !  ÄÆÆÄ#$%"1‡!     '  gO‰ÿþýÔ)6Tmv…7#57654#"'6323'#7#'##"&546323'5354&#"326532654.54632&#"#"'#'##"&5467754&#"'6325326'&#"#53632ý*  (+ +,   :    %o     !  Ž   +ÆÆÄ#$%"1‡!     '  gO‰ÿÃåŒ2?7532654.54632&#"#"'##533632#"'32654&#"‘ $  ' m          :Æ#$$#LÿþåŒ47532654.54632&#"#"'#54&#"#5336632’ $  ' "         YG‰ ÿÃîŒ@7532654.54632&#"#"'#"'##"&'#73326773327› &  '             FÆX Tb ÿþíŒA7532654.54632&#"#"'#54&#"#54&#"#5336323632š &  ' #        Y YY Y‰ÿÃñ· *7#'3677##533632#"'32654&#"ñ33  ²  ··· ¥:Æ#$#$Lñ· 7#'3677#54&#"#5336632ñ33  d  ··· ·YG‰ ÿÃø· -7#'3677#"'##"&'#73326773327ø33! Z       ···   /ÆX Tb  ö· +7#'3677#54&#"#54&#"#536323632ö33  a  ··· ·Y YY Y‰îà 3#'#53737#'3677v' 2/«33  ? 5ÃZ!A;i··  þ· 7#'3677#547##'##53373þ33!!h!"··· ·c,’’,c·ŠŠÿÃò·(57#'#'36773677##533632#"'32654&#"ò    ¶    ··‚(Z·NJJN¥:Æ$##$Lò·'7#'&'#'367737#54&#"#5336632ò  l  ··Z ‚·}JJ}·YG‰ ÿÃû·97#'&'#'36773677#"'##"&'#73326773327û    b       ··Z Z·NJJN FÆX Tb ù·97#'&'#'36773677#54&#"#54&#"#5336323632ù    k  ··Z (Z·NJJ N·Y YY Y‰ñà &3#'#533737#'&'#'36773677s( 10±    ? 5ÃZ!A;i·Z Z·NJJNÿ·*7#'&'#'36773677#547##'##53373ÿ   k!!··Z Z·}JJN·c,’’*c·ŠŠ ôà (3#'#5373#5654&#"#53&546323p' 2/¶76$#? 5ÃZ!A;N9 94)--)4þº/3#5654&#"#53&546323#547##'##53373þ66#$€!"9 94)--)4c,’’*c·ŠŠÿüþŒ!:CO[3#54&#"#54&#"#5336323632#'##"&5467754&#"'63253264632#"&'4632#"&à  Œ  $ ¥ŒY [[ [‰Z   '  ÿÃï· 1>3532#'32654&#32654&##57##"&5463237354&#"326- %­   ·#$£:MB“:#$#$IÿþéŒ+7&#"327#"&54632&#"327#"&54632é   c  !…##$$##$$ÿþìÃ43#'##"&546323'5354&#"326'&#"327#"&54632ì  a  !$##$7ƒ]##$$ÿÃûÄ&LXe7#7#'#5373'&#"327#"&54632&547&546323#"'332#"&546732654&#"#"32654&ƒ* *GS  …         ÄÆÆÄ91ÃW3:_ #&%%//0.±    Yk ÿüúº!-97&#"327#"&546324632#"&732654&#"4632#"&|!'*!N¯ &#%%.0-1u#$$#"%%"S ÿþôÃ,5>3#'##"&546323'5354&#"326532#'32654&#32654&#x  &.!&$##$7ƒ(·"$£:MB ÿÃõº/7#"&54632&#"3275#537#"'53277'33677|$'   4y1   .  //.0  $%%%6(›†D DÿþéÃ-73#54&#"#533632#'##"&5467754&#"'6325326z  o   YGÃ:Z   '  ÿüý· $03#53773#'#5373#'4632#"&'4632#"&™ !.0&+./%È·P8JmXQ·OOJmXEû·3#547##'##53373#5373#'û!!ßC>@5c,’’*c·ŠŠ··ZZPgW#ÿþÝà #3#'#53373#"&55#57733#327‡( 10‰ "" ? 5ÃZ!A;LO  O ìÃ$3#53#54&#"#54&#"#533632632+Á    ÃÃYLYG‰+ÕÃ3#53#54&#"#533632B“  ÃÃYG‰ÿÃþÃ)5BNZ3#53#"'332#"&547&547&54632332654&#"#"32654&'4632#"&732654&#"à  !  +T "  µÃH   ,j  C#$$#"%%"*ÖÃ3#53#'#7'373A•!"/, -ÃÃ77GB33BåÃ043#534632#"&#54&#"#54&#"#533632632#53åE  -çY YY Y‰Z‰ÿþúÃ=3#534632#"&732654&#"#54&#"#54&#"#5336323632ú}   Ã~$##$#$%"_Y YY Y‰ÿÃþŒ!3@LX3#54&#"#54&#"#5336323632##533632#"'32654&#"4632#"&'4632#"&à  Æ  ÉŒY [[ [‰H:Æ"%$#L V%ÿþÛŒ.7&#"#53632532654.54632&#"#"Û  ¯ &  ' ‹J‰†      ÿþìº!-7532654'&'&&54632&#"#"7#'33477 ,É**      ‹‰‰L)LÿþøÃ37##533632#"'32654&#"'#'#'36773677¨       Ã/#$$#GT·‚(Z·NJJNpÿü 74632#"&4632#"&p|  e wÿÜŠÜ#3Š$ÿÜÜ#3$-ÿÜSÜ%#4&54654&54654&54653S  $      !ð\ 7&#"5632ð1??1-CD,"#$%%\ð˜ 7#"'5327ð,DC-1??1‚&&$#ð\7#4&##"'###"#66332653332ð )) *  *  \ð©7##"#4&##"&'33323633265ð*  * )) ©  ð\7'#'73ð–¨' (( 5\ðš7#'737𨖑55 '' ð\7#&#"#53ð ,;;, à 11O\ð¬7#533273ðà ,;;, \P22ÿøð\ 7''7''7ðhhpphhp'225U225\ðÁ 7'77'77ðpphhpphh²6622/6633ð\7''7ðhhp'225\ð 7'77ðpphh‘5522ð\7#5#53ðÍà.\ð¡7#533ðàÍ\E.€ð\ 7#5#535#3ð43232>323                ¢   ¡Ð)%".#"#".#"#52>3232>323              ¡ ÿæíÿ÷ #53#53#53í((Y((Z''ÿåíÿù 432#"7#53#53v wCC—CC )%%"#"&#"#"&#"#"432632326323263         ÿäA&'7A  ÿãL' 74632#"&732654&#"     1?vÌ7##53v.E¹z ÿíOz353# .E{ÿâK&&'7K"  E?t 74632#"&  \  ÿïq¶ 7'67#5365#5q"# - EHM¶`RMÿâr7'67'#577'6655o  $P2 y+  ('# "&ÿãs— 7#5'67s  <‹ze50ÿájš7'655##53533j /+ !kA8P6I ÿñpz #535#53#3pa'R&ddÿãp—7##"''325''6753533p    !14be @#"5#" ÿâs˜7'67'''7'777o  % - e* % vx() ÿöp~ '57#53p`;2E duÿæiˆ #5#535#535#53i??<<>Q 4+ ÿâs7&'77&'667&'7M  -!! !0  R " IB @BC  ! Psi7#53sffPÿòzº7'67''77&'655w /)^8&"£ 9 (/D$ <-ÿô{Ç 7#5'67{ Gºu=9ÿðqÈ7'6675##53533q ' 3$'”R@ 1:AV&&y© 7#535#53#3ys.$`$-vvÿóyÆ7##"''325''6757533y (9<ˆv K)!)=(( ÿ÷wÇ7#"''32657''67#553w (& šY+#Ca-*U--ÿñxÇ7''7''7'777x,--&&(',JTR.*(/ ÿïwÉ'67''6777&7#  ! )e&2>  $7ÿò|È7#'677#'677|;2  GƒP'G*Aÿ÷r¨ #5#535#53rMMKb uÿñ{Á7#'655##5#53533533{:3$${LIBB,,00ÿñx½ 7&'7'667&'7E D/# !+,  œ-=@:>,  ÿí}¶&''67537}!;;E  **4f &!ÿõzÇ7'67'327#"&55'77377t*2€.% 8YT;8ÿð{¾ 7'667&'7{*&&$- ·TZLV^ 7! *ÿï|Ê7'67&'767'#'6737w@!   -  +˜w2,  *!,B ÿïyÁ7#&'655#55'673y, )00!: ,\*0 A( .ÿð{·7&'77&'667&'7P  3"'" 7 r ( $]F?YO % *ÿðx·7#53#&'665#53iUU*-p¢R5. !2&ÿò~Æ 7&'#53~ #_ uÔH ÿózÅ7#&'6654'#53'33z.//.{3* #0 55 w§7#53#53m[[ nn’§ ÿðwº7&'&'67&'767'577w   %I & +(5%ÿò~Ç7#5'67'#535377&'7i7%!!  …cJ (---Xÿñp¿7'67p B? º‰@7…ÿð|¼ 7&'7''65|,A‡wvJBwÿøpÁ7#"&5536732p& -  —H7  ÿñv²7&'6767'577q)& $-N™NI "E~¦7&'&#"'7632~'    ['  Iÿò|Å 7##"''3255#5533&'7''67y-  //- J ‡wr((‹ UVOI$ !Hÿïy¶7&'767'#537t!  5 Z›.7B% 4ÿñsÀ 7&'7&'7&'7n'0.%/,-#:5¬Ym  ÿð{Ä'&'36767&'7{!: !   š ¥4 ÿð{Á7''67&'767{&&  ) )4!8  /9" ÿøu¶7#327#"'&55#737#53#3u6   ^/6Z;  =22ÿñ|Ç7'67'''7'777w  ,5Œ2%( ‹Œ.0 y© 7'537#53yrD9Pzÿòp² #5#535#535#53pIIFFH_>2 ÿïuº7'5'67''57iUU#'> LQ¢L15!EÿñkÁ 7'66553#53k?T)+ ),[†ÿï~¿7#"'773367''6754'3~   H L0¸q.+ 73G; ÿïu 7#"'753367u? #X09¹{59ÿ÷q° #5##535#q5b5 ¹– ÿòs¸ 7'6657#5s #5¸T9, 88J`ÿò{º 7&'7'667<P/* ') ŒGC-ν765#53­ ¡ 2ar4(˜½765#53x dy (-Uf/5‘À7'67#53 Y[s5 Gc 5‰À7'67#53 W\s5 Gc 5uÀ7'67#53 FH_ 5 Gc-hÎÉ765#53® ¡ m1C-jÎÉ765#53® ¡ o0Ae–Å765#53zeyj/Bp–É765#53zeyu,=fzÅ765#53^ Ob j/Au{Å765#53_ Nau(7-]ÎÊ765#53¯ ¡ ];K" a–Å765#53x bv e2EJ’Å7'67#53 Y\tJ<X K‹Å7'67#53 W^vK<W KuÅ7'67#53 FI` K<W-xÎÉ765#53° ¡ |'8-rÎÉ765#53° ¡ w*<w–É765#53{ey {'9{–É765#53|ey$6s~É765#53a Pc x);{Ê765#53b Pd {(7-pÎÉ765#53¯ ¡ p/= k–É765#53v bv k2AQ’Å7'67#53 Y[tQ7S RŠÅ7'67#53 W\tR7R RuÅ7'67#53 FH_ R7R-{ÎÉ765#53° ¡ €$5-sÎÉ765#53­ ¡x*;{–É765#53|ey€$6}–Ê765#53zey }&5v~Æ765#53a Pc z%7{~Æ765#53b Pc{$4-qÎÊ765#53¯ ¡ q/= k–É765#53w bv k2A¡¹7'67#53'67#53 3,A 5(<' OfB&_u ™¹7'67#53'67#53 2-B 4';' OfB&_v €¹7'67#53'67#53 &3 +2& PfB'^t&>ÑÀ765#53765#53?2F^CVF#DW'$FY)&aÒÉ765#53365#53P;PM 8M a9G!9G!I¡À765#53765#536 (< 9 +? N!>Q&!>Q&`¡Å765#53765#53< +@ 1 )= `6E 2AH‡À765#53765#53& 1 0 %9 M!?R&!>Q&k‡É765#53765#53- #7 )2k2A1@&ѽ765#53'65#53¬7Kr;P2bs42ar4%¡½765#53#65#53z%9_.B%-Xi/-Xi/2¢Â7'67#53'67#53^ / %= ~ 0 +C 2"Kg@[ :›Â7'67#53'67#53V / $= | - +C : Fa;V9ƒÁ7'67#53'67#53 %!8 $3G<W1Fa&kÒÉ765#53765#53G2FUCWp/A/A&fÒÉ765#53765#53I7KS=Qk2D1Bg¤Å765#53765#536 (< ; .B l.@.@m£É765#53765#535(< ; -A m1?/<j‹Å765#53765#53& 16 (< o,>,>uˆÉ765#53765#53- #7 + 4u,:*8&^ÒÉ765#53765#53V>RI 8L c7I"7I"Y¡Å765#53765#53< .B4 %9 ^7J"6I"L¡Æ7'67#53'67#53 - +B + &< Y2N,;V L™Æ7'67#53'67#53 - *A , %;Y2N,;V IÆ7'67#53'67#53 "3 #2Q8T)>X&}ÑÉ765#53765#53I2FV BV ‚$4#3&vÑÉ765#53765#53N8KP =P {(9(9v£É765#53765#538 )< : .A z(9%6x£É765#53765#53< .A6 )< }&7 $5v‡É765#53765#53& / 3': {(9'8x‡É765#53765#530 #6 * 3}&7 $5&kÒÉ765#53765#53V>RI 8L p/A/Ae¡É765#53765#53:.B2 %9 i3E 0BS¡Æ7'67#53'67#53 . ,B + &< `-I*6Q S™Æ7'67#53'67#53 . ,B , %;`-I*6Q QÆ7'67#53'67#53 #4 "1Z2M(8S&|ÒÉ765#53765#53J2FV BU $5$5&vÑÉ765#53765#53O8KQ >Q z(9(9u£É765#53765#539 )< : .A z(9 %6z£É765#53765#53; ,@8 )< %6 "2v‡Æ765#53765#53& / 3': {%7$6{‡Æ765#53765#530 #6 ) !3 €"4!3&pÒÉ765#53765#53U>RL 8L t,=+<f¡É765#53765#53;.B4 "6 k2D 0A&¯½7367>=@Q½… &˜½7367226D½…  &€½7367**/;½…  1\ÒÅ733#1¡ÅV1€ÒÏ733#1¡Ï=&l¢Å733#&h|ÅF&užÉ733#&dxÉAj…Á733#SgÁDvƒÉ733#QeÉ@J¯É7367>=@QÉmJ—É7367216CÉm  J€Å7327**/;Åi  1ÒÏ733#1¡Ï=1ƒÒÏ733#1¡Ï9&z¢É733#&h|É<%|¢Ì733#%i}Ì=s‚É733#PdÉC‚É733#PdÉ5_¯Ì7327>=@QÌ[Z˜Ì7327226DÌ`  Z€É7327**/;É\ 1ˆÒÏ733#1¡Ï41ŒÒÏ733#1¡Ï0&ƒ Ì733#&fzÌ6&Š›Ì733#&auÌ/€Ì733#NbÌ8‰€Á733#NbÁ%\¯Ì7327>=@QÌ] \˜Ì7327226DÌ]  \€É7327**/;ÉZ 1ˆÒÏ733#1¡Ï41ˆÒÏ733#1¡Ï4&Š¢Í733#&h|Í1&ŠžÍ733#&dxÍ1‚‚É733#PdÉ5‰‚É733#PdÉ-&¯¸ 73#267#s_JDM¸l & ¸ 73#267#t`EDF¸l %†¸ 73#267#[G8 4<¸m 1Yѽ7#3#5ΉŒ ½>d1rÒÉ7#3#5Ή¡É1W"\¡½7#3#5žhk½;a#lžÅ7#3#5›dg{Å3Y^½7#3#5~LOc½9_sÅ7#3#5~LOcÅ,RN°Á 73#267#s_NFLÁM F‘Á 73#267#t`A 9BÁU HÁ 73#27#[G/%29ÁS  1wÑÉ7#3#5ΉŒ É-R1|ÑÉ7#3#5ΉŒ É(M#r Æ7#3#5fi}Æ,T#|žÊ7#3#5›dg{Ê(NrÆ7#3#5}KOcÆ.TzÆ7#3#5~LOcÆ&L]°Å 73#27#s_D7IIÅC  U•Å 73#27#t`=+@?ÅK  YÅ 73#27#[G-&38ÅG1€ÑÉ7#3#5ΉŒ É$I1ƒÑÉ7#3#5ΉŒ É!F"| É7#3#5gj~É(M#…žÉ7#3#5›dg{ÉDzÅ7#3#5~LOcÅ%K„É7#3#5~LOcÉ!Ec°Å 73#267#s_KGKÅ=\•Å 73#27#t`;,:EÅD  ZÅ 73#27#[G-'0;ÅF1ƒÑÉ7#3#5ΉŒ É"F1ƒÑÉ7#3#5ΉŒ É"F" É7#3#5žhj~É$H#†žÉ7#3#5›dg{ÉC|Å7#3#5~LOcÅ$I„Å7#3#5~LOcÅA$·¸ 73#27#73#267#9&#CC/''/¸p”n $¨¸ 73#27#73#27#:&$DB. $*¸p”n  $“¸ 73#27#73#27#087# %¸p”n  *WÙ½ 73#27#7#3#5*G3!**«<@T½@  f?e*qÙÊ 73#267#7#3#5*G3&(,«<@TÊ3Y2XZ¬½ 73#27#7#3#59&$ˆ-3G½=c<br¬Å 73#27#7#3#59&#ˆ-3GÅ-S-S\…½ 73#27#7#3#52 o"#7½<a;`rˆÅ 73#27#7#3#52 o"&:Å-S+QN·Á 73#27#73#267#:& DB.' '.ÁNsL I©Á 73#27#73#27#:%#EB.(&ÁSxQ N“Á 73#27#73#267#/:4  #ÁNsL *uÙÊ 73#27#7#3#5*G3#'-«<@TÊ0U.T*zÙÊ 73#27#7#3#5*G3!&.«<@TÊ*P)Oq«Å 73#27#7#3#59&"ˆ-2FÅ.T,Sy«Å 73#27#7#3#59&#ˆ-2FÅ&L%Kr…Å 73#267#7#3#5/n"$7Å.S,RxˆÅ 73#27#7#3#50 n"':Å(M&L[·Å 73#27#73#27#:&#DB.$*+ÅFjD  W©Å 73#27#73#27#:%! EB.#+ÅJnGX“Å 73#27#73#27#/95!!#ÅImF*~ÙÊ 73#27#7#3#5*G3#)+«<@TÊ&L%J*ÙÊ 73#27#7#3#5*G3#(,«<@TÊ$I"Hz«Ê 73#27#7#3#59&$ˆ-2FÊ*P(N«É 73#27#7#3#59&! ˆ-2FÉ#H!Ex‹Å 73#27#7#3#50m!$7Å'M%J~ˆÉ 73#27#7#3#5/ m!&9É%K#H`·Å 73#27#73#267#:& DB.' *+Å@e> ]©Å 73#27#73#27#:%"EB.&(ÅChA]Å 73#27#73#27#/ 94 !ÅChA*ÙÉ 73#27#7#3#5*G3#**«<@TÉ#H"F*ÙÉ 73#27#7#3#5*G3#**«<@TÉ#H"F«É 73#27#7#3#59& !ˆ-2FÉ&H$Gƒ«É 73#27#7#3#59&$ˆ-2FÉ#F!D{‹Å 73#27#7#3#51n #7Å&J$H‚‰Å 73#27#7#3#5. m!';ÅCA°½ 73#67535#r_:@FL`_½S;  b- ½ 73#67535#jW7<BHWV½S;  b-½ 73#67535#ZG(,47GF½S;  b-1OÑ¿ 73#3#535#2œ‰Œ ‰ˆ¿@C1jÑÉ 73#3#535#2œ‰Œ ‰ˆÉ6<%O¦¿ 73#3#535#&{hmhg¿?D"f¡Å 73#3#535##zgkgfÅ99R†¿ 73#3#535#cQUhPO¿>Bd†Å 73#3#535#cPThPOÅ::F®À 73#67535#r_9@EK_^ÀB%  KEÀ 73#67535#ZF'+47GFÀC$K1jÑÉ 73#3#535#2œ‰Œ ‰ˆÉ6<1jÑÌ 73#3#535#2œ‰Œ ‰ˆÌ8=%e¦Å 73#3#535#&|imihÅ9:"k¡É 73#3#535##zgkgfÉ7:c†Å 73#3#535#cQUhPOÅ9<o†Å 73#3#535#cQUhPOÅ36 S°Å 73#67535#r_:@FL_^Å>!  GT¡Å 73#27535#kX7<CHXWÅ> FS€Å 73#67535#ZG',37HGÅ? F1tÑÉ 73#3#535#2œ‰Œ ‰ˆÉ26 1tÑÌ 73#3#535#2œ‰Œ ‰ˆÌ47%p¦É 73#3#535#&|imihÉ66"w¡É 73#3#535##zgkgfÉ22 p†Å 73#3#535#cQUhPOÅ44|†É 73#3#535#bPUhONÉ/ 1 W¯Å 73#67535#r_:@FK_^Å;   FWÅ 73#67535#jV.3;?WVÅ; FW€Å 73#67535#ZF'+37GFÅ; F1xÑÌ 73#3#535#2œ‰Œ ‰ˆÌ25 1xÑÌ 73#3#535#2œ‰Œ ‰ˆÌ25 %u¦É 73#3#535#&|imihÉ23"x¡É 73#3#535##zgkgfÉ20u†Å 73#3#535#cPThPOÅ/2 |†Å 73#3#535#cPThPOÅ- -  ‰¸ 7#5##535#‰CCC¸” ˜zg ‚¸ 7#5##535#‚DDD¸” ˜zg q¸ 7#5##535#q444¸” ˜zg2Yν73#75#2œœˆt½d>>2rÎÉ73#75#2œœˆtÉW11"\ž½73#75#"||hT½a;;#lœÅ73#75##yyeQÅY33]‚½73#75#ddP<½`::p‚Å73#75#ddP<ÅU//OŠÁ73#75#llXDÁrLLLqÁ73#75#[[H4ÁuOO2wÎÉ73#75#2œœˆtÉR--2|ÎÉ73#75#2œœˆtÉM(("ržÆ73#75#"||hTÆT--#zžÆ73#75##{{gSÆL%%p‚Æ73#75#ddP<ÆV//z‚Æ73#75#ddP<ÆL%%]ŠÆ73#75#llWCÆiBB]Æ73#75#kkWCÆiBB]qÆ73#75#[[H3ÆiBB2€ÎÉ73#75#2œœˆtÉI$$2ƒÎÉ73#75#2œœˆtÉF!!"|žÉ73#75#"||hTÉM((#…œÉ73#75##yyfRÉD|‚Æ73#75#ddP<ÆJ##„‚É73#75#ddP<ÉE ]ŠÆ73#75#llWCÆiBB]Æ73#75#llXCÆiBBZqÆ73#75#[[H3ÆlEE2ƒÎÉ73#75#2œœˆtÉF!!2ƒÎÉ73#75#2œœˆtÉF!!"žÉ73#75#"||hTÉH###†œÊ73#75##yyfRÊD|‚Å73#75#ddP<ÅI##„‚Å73#75#ddP<ÅA!Џ 73353#5##735#DDDD¸--“ :!‚¸ 73353#5##735#DDDD¸--“ :!r¸ 73353#5##735#4444¸--“ :1Xν 7353#535#EuuuŸeeR!!1mÎÉ 7353#535#Euuu®\\I"Zž½ 7353#535#6T|TT¡ccP!!#lÅ 7353#535#7RzRR¬YYG\‚½ 7353#535#2<d<<¢aaMl‚Å 7353#535#2<d<<¬YYFO‰À 7353#535#2CkCCš&qq^%%LrÀ 7353#535#*4\44š&tta((1tÎÉ 7353#535#Euuu²UUB1zÎÉ 7353#535#Euuu¶OO<"lžÅ 7353#535#6T|TT¬YYF#zÉ 7353#535#7RzRR¶OO<l‚Å 7353#535#2<d<<¬YYFv‚Å 7353#535#2<d<<°OO<Z‰Å 7353#535#2CkCC¢#kkX""Z‚Å 7353#535#*DlDD¢#kkX""ZrÅ 7353#535#*4\44£"kkX##1|ÎÉ 7353#535#EuuuµMM91|ÎÉ 7353#535#Euuu¶MM9"vžÉ 7353#535#6T|TT±SS@#€œÉ 7353#535#7RyRR¶II6v‚Å 7353#535#2<d<<°OO<€‚È 7353#535#2<d<<´HH6\‰Ä 7353#535#2CkCC¤ hhT!!\‚Ä 7353#535#*DlDD¤ hhT!!ZrÄ 7353#535#*4\44¦jjW%%1ÎÉ 7353#535#Euuu¸HH51ƒÎÉ 7353#535#EuuuºFF3"žÈ 7353#535#6T|TT¶II6#€œÈ 7353#535#7RyRR¶HH5{‚Ä 7353#535#2<d<<°II8€‚Ä 7353#535#2<d<<³DD2!£¸ 73353#5##735#73353#5##735#5¸11• 6E22• 6!œ¸ 73353#5##735#73353#5##735#6¸11• 6E22• 6!¸ 73353#5##735#73353#5##735#,¸11• 6E22• 6-XÓ¼ 7353#535#'353#535#ž!I!!]!I!!¡ddQ##6ddQ##-mÓÈ 7353#535#7353#535#A!I!!]!I!!®[[H.[[HY¤¼ 7353#535#7353#535#0>J> ccP!!4ccP!!i£Ä 7353#535#7353#535#0>J=¬[[H0[[H\‹¼ 7353#535#7353#535#(6@7£``L 3``L mŽÄ 7353#535#7353#535#+7@7«WWD+WWDO¤¿ 7353#535#7353#535#0>J>"pp]));"pp]))Ož¿ 7353#535#7353#535#*>J>"pp]));"pp]))IŽ¿ 7353#535#7353#535#*7A7›$vvc,,?$vvc,,-sÓÈ 7353#535#7353#535#A!I!!]!I!!³UUB-UUB-zÓÈ 7353#535#7353#535#A!I!!]!I!!´NN;'NN;m£Ä 7353#535#7353#535#0>J=¬WWD,WWDt£È 7353#535#7353#535#0>J=°TTA)TTAo‹Ä 7353#535#7353#535#&7A7¬UUB*UUBsÄ 7353#535#7353#535#+6@6­QQ>'QQ>Y¤Ä 7353#535#7353#535#0>J>£!kkX$$7!kkX$$ZžÈ 7353#535#7353#535#+>I>¥#nn[%%8#nn[%%VŒÄ 7353#535#7353#535#*6@6¢"nn[&&9"nn[&&-}ÓÈ 7353#535#7353#535#A!I!!]!I!!·KK7&KK7-|ÓÈ 7353#535#7353#535#A!I!!]!I!!¸LL9)LL9y£È 7353#535#7353#535#0>J=²OO<&OO<~£È 7353#535#7353#535#0>J=µJJ7$JJ7vŒÄ 7353#535#7353#535#*5@6®NN<&NN<}È 7353#535#7353#535#*5A5´KK7#KK7\£Ä 7353#535#7353#535#0>J=£!hhU!!4!hhU!!\Ä 7353#535#7353#535#+>I=£!hhU!!4!hhU!!XÄ 7353#535#7353#535#*8@7¤ llY&&9 llY&&-ÓÈ 7353#535#7353#535#A!I!!]!I!!·GG4#GG4-ƒÓÈ 7353#535#7353#535#A!I!!]!I!!ºEE2$EE2~£È 7353#535#7353#535#0>J=µJJ7$JJ7€£È 7353#535#7353#535#0>J=µHH6#HH6y‹Ä 7353#535#7353#535#*5@5¯KK8#KK8€ŒÄ 7353#535#7353#535#*5@6³DD2!DD2«½ 7'6753&&]3 >A $q4""M#$J -˜½7'66753&&R0#:  q5":&#$J - ƒ½ 7'6753&G % .0 &p4 !M#$LSâà 73&''6z> <> Pà 3-*!#"lÝÎ 73&''6z: 8; LÎ )$#Q¬Ä 7'6753&c4 ?< 0‘';7h¬È 7'6753&d4 @= / "30 SŠÄ 7'6753&O * 3/"’%94gÄ 7'6753&S ) 3. $ž"0+ C­Ä 7'66753&^4 %B 5Œ,1=C–Ä 7'6753&S1>6 +’0DBC„Ä 7'6753&&I )4/ ‰*=; %"pÝÎ 73&''6z9 9: LÎ &!""yÝÎ 73&''6z: 8< LÎ !h¬È 7'6753&d4 @= / "30p«È 7'6753&c3 @< 0Ÿ* (k‹Ä 7'6753&S ) 3, $›($n‹Ä 7'6753&S ) 3, $œ'"   P­È 7'66753&^3 %B 6•* .:P–È7'66753&&R 2 #7 –* /> &R…È 7'6753&J )40 %–)<:"~ÝÎ 7'673&9 K = ;®("~ÝÎ 7'673&9 K = ;®(s­Ë 7'6753&c4 >? 0¦+ &}­Ë 7'6753&c4 >? 0©$ !  pÈ 7'6753&S ) 3. % (#|Ë 7'6753&S * 3. $¤"   S­È 7'66753&^4!$B 6% )3S™È 7'6753&S1<: .“&73 V‚È 7'6753&J ( 2- #–':7#}ÝÎ 7'673&9 J = ;®)"ÝÎ 73&''6z9 8: LÎ  {­Ë 7'6753&c4 ?? 0©% !  }­Ë 7'6753&c4 =@ 0¦"   €È 7'6753&Q ( 3. %¨!  {È 7'6753&S ) 3. $¤!   ´¹ 7'6753&7'6753&F !* < (# f(;01+ &=787¥º 107'6753&'6753&&5| "( n )  Z'A568>,-, º!107'6753&'''6753&&5 & "  (723*   F,-? % Tå¾ 7'6753&7'6753&W % ,# P !- #‹"4) .-iäË 7'6753&7'6753&Z ) 0 M (+ !›1 $ 0+ V³¾ 7'6753&''6753&† ! I " * ….,1)  ^²Å 7'6753&7'6753&F # , = ' –,!  0) ]’¿ 7'6753&7'6753&; # 1  ‰*"  &$  d•Ä 7'6753&7'6753&> # - " —-  (#  9±Ä 7'6753&7'6753&F # + < ( }!7 !* '9((49§Ä 7'6753&7'6753&= ( 9 '" y2&&' "9()3:’Ä 7'6753&7'6753&4  1 # { 4#$% ":'(2måË 7'6753&7'6753&Z ( 0 N ", "ž0 ) . 1såË 7'6753&7'6753&Z ' 0 N "- "¡* % .-l²Ä 7'6753&7'6753&G " + > ! ›+ % ($ k²È 7'6753&7'6753&G " ( < &! ž, "  ,%  m•Ä 7'6753&7'6753&; ! 0  š+   %$   r”Ä 7'6753&7'6753&@  ' /  # ž&    (  Q±Ä 7'6753&7'6753&G " ) = ! ' +"  2+ P¥Ä 7'6753&7'6753&? " ) 9 ! ' Š,  2* J’Ä 7'6753&''6753&h # :  |5- - {äÎ 7'6753&7'6753&X % . O %, !£'  ( % {äË 7'6753&7'6753&X % . O %, !£'  ( % w²Ë 7'6753&7'6753&G # + >   &   &! x²Ë 7'6753&7'6753&I % , ; % £$    &   x“È 7'6753&7'6753&? $ /  ¡ '    $" u”É 7'6753&7'6753&?  # /  " £ (   '!  R±Ä 7'6753&''6753&ˆ ! ' I # , ˆ 3/#0' Q¥Ä 7'6753&''6753&z ' @ ! ) … 1-& 4) L‘Ä 7'6753&''6753&h # 9  {/&'( #0" äË 7'6753&7'6753&Y & 0 O %, ¦$   "  |äË 7'6753&7'6753&Y & 0 O %, !¦&  # "  y±É 7'6753&7'6753&I % + ;  £'    % $  y±É 7'6753&7'6753&I % + ;  £'    % $  x•Ä 7'6753&7'6753&@ $ .  ¡ '  "   s•Ä 7'6753&7'6753&>  ' 0 " £ '  * ¿ 7"&54632'"32654&S#"##+%&++&%+Ž   ‰¿ 7"&54632'"32654&L#"##+%&++&%+Ž  !y¿ 7"&54632'"32654&D *&'*+&&*Ž  -SÒÁ 7"&54632'"32654&'+*((++(  S  !\-mÒÏ 7"&54632'"32654&(**()*+(  mO  S¡Á 7"&54632'"32654&`####S \g¡É 7"&54632'"32654&` "" "#gP  U†Á 7"&54632'"32654&OUYl†É 7"&54632'"32654&OlJ  J‘Ä 7"&54632'"32654&S!!""J""""hHyÄ 7"&54632'"32654&CH"!"#j-rÒÏ 7"&54632'"32654&(**()**)  rJ  m¡É 7"&54632'"32654&` "" "#mJ  v¡É 7"&54632'"32654&` "" ""vA  l†É 7"&54632'"32654&OlJ  w†É 7"&54632'"32654&Ow?  Y‘É 7"&54632'"32654&T!!""Y ^Y‰É 7"&54632'"32654&L"!!!Y ^XyÉ 7"&54632'"32654&CX _-{ÑÏ 7"&54632'"32654&))))))))  {B  -ÑÏ 7"&54632'"32654&))))))))! >v¡É 7"&54632'"32654&` "" ""vA  €¡Ì 7"&54632'"32654&`!!!!""€:   v†É 7"&54632'"32654&Ov@  €†Ì 7"&54632'"32654&O€9   Y‘É 7"&54632'"32654&T!!""Y ^Y‰É 7"&54632'"32654&L"!!!Y ^XyÉ 7"&54632'"32654&CX _-|ÑÏ 7"&54632'"32654&))))))))  |A  -|ÑÏ 7"&54632'"32654&))))))))  |A  z¡Ì 7"&54632'"32654&` "" ""z@  ¡È 7"&54632'"32654&`!!!! !"7   {†Å 7"&54632'"32654&O{7   ~†È 7"&54632'"32654&O~8   §¹7#&&''675#5 7> #/ :5¹G +2  K  ¹7#&''675#5˜7?1 /;5¹I31 H…¹7#&''675#5}(0 ' ( 2,¹D-0 G$UÛ½7#&''67#5ÑG; :8 EJ½'$$+$jÚÉ7#&''67#5ÑG: 88 BHÉ# &Q­½7#&''67#5Ÿ3A 4 1 : 5½/#&3b­Å7#&''675#5Ÿ2@ 2 1<6Å'+Xн7#&''675#5€&0 % ) 2+½(+l‡Å7#&''67#5€&+ " '+ )Å$  'C«Á7#&''675#5 7B 5 2 ;5Á=))> >£Á7#&''6675#5˜8C 4 0 !4Á<** / C…Á7#&''675#5|(1 ' & 0,Á7%';$tÚÉ7#&''67#5ÑH; 97 BHÉ $wÚÉ7#&''67#5ÑH; 98 BHÉd­Å7#&''67#5Ÿ5B 3 / 6 4Å$",o­É7#&''67#5Ÿ4@ 4 . 6 5É" (d‡Å7#&''67#5€'- " ( .*Å( ,n‡Å7#&''67#5€', " * , *Å!  %Q­Å7#&''67#5 5@ 4 1 9 8Å4#'9 Q¥Å7#&''67#5—5@ 3 1 8 7Å3%&8 T„Å7#&''675#5|(0 & ' 1,Å1!"2$|ÚÉ7#&''67#5ÑI; 98 AHÉ$ÚÉ7#&''67#5ÑI; 98 @HÉx¯É7#&''67#5¡6B 3 5 < 9É  !¯É7#&''67#5¡6@ 2 5 : 8É   r‡É7#&''67#5€(, " ' - +É# &}‡É7#&''67#5€(. " ' ,+É   Q­Å7#&''67#5 6B 2 2 ; 8Å3#'8 Q£Å7#&''67#5—6A 2 08 7Å3#&8 R„Å7#&&''675#5}*1  ' 1-Å3 #6$ÚÉ7#&''67#5ÑH: 89 @HÉ$|ÚÉ7#&''67#5ÑH; 98 AHÉz¯É7#&''67#5¡6A 3 4 : 8É   ¯É7#&''67#5¡6? 2 5 88É   }ŠÅ7#&''67#5‚'+ # ' * *Å   }†Å7#&''67#5'* ! ) * +Å    ±¸!7#&''675#53#&''675#5b  # ,Š!  (¸5+F<$+D ¥¸!7#&''675#53#&''675#5Z  '†   '¸5+E>&*C¸!7#&''675#53#&''675#5J t   $¸2"= 8!'@Qå½!7#&''675#53#&''675#5{  # +'µ%/ "  $½% "2-+#hÜÉ7#&''67#53#&''67#5z  !'%§#* % ,É  '(, S²½!7#&''675#53#&''675#5b  " ,‹"  #½!  .** ]²Æ!7#&''675#53#&''675#5b  " ,‹"  (Æ!  ,( - X޽!7#&''675#53#&''675#5P  !v   ½  ( ( & aÅ!7#&''675#53#&''675#5P  !v  ( Å $% . :±Á!7#&''675#53#&''675#5b  " ,‹!  )Á1%; 8#'> :¥Á!7#&''675#53#&''675#5Z  !,…  )Á1%< 8#'> :Á!7#&''675#53#&''675#5J  !s  #Á/ "94#:#iÝÉ!7#&''675#53#&''675#5z  ! *&¨#, É  (& %#sÝÉ7#&''67#53#&''67#5z ! '&¨$+  É  ## $ k²Æ!7#&''675#53#&''675#5b  " ,‹"  !Æ &$  #k±Æ7#&''67#53#&''67#5b  ! )‹!  ! )Æ ##  ' nÆ!7#&''675#53#&''675#5P  !v   Æ    $ $ oÆ7#&''67#53#&''675#5P  !v   ( Æ    !R²Æ!7#&''675#53#&''675#5b  *‹"   (Æ' /1 7R§Æ!7#&''675#53#&''675#5Y  *ˆ"  )Æ% .4 7LÁ7#&''67#53#&''675#5J  s   $Á& 30  7#yÝÉ7#&''67#53#&''67#5z  !% %§")  É      #}ÝÉ7#&''67#53#&''67#5z  $ %§")  É       s±É7#&''675#53#&''67#5b  " +‹  É #% % y±É7#&''67#53#&''67#5b  " )‹  $É    ! vÉ7#&''67#53#&''675#5P  v   É $       xÉ7#&''67#53#&''67#5O  q   É    !R±Å!7#&''675#53#&''675#5b  ! * ‹!  )Å% .17R¦Å!7#&''675#53#&''675#5Y  + ‰!  )Å% .1 7LÆ"7#&''6675#53#&''675#5J  s   $Æ'  ' 0  7#yÝÉ7#&''67#53#&''67#5z  !% %§")  É      #yÝÉ7#&''67#53#&''67#5z  !% %§")  É       s±É7#&''675#53#&''67#5b  " +‹  É ##   ! y±É7#&''67#53#&''67#5b  " )‹  $É    ! vÆ7#&''67#53#&''675#5O  u   Æ      xÆ7#&''67#53#&''67#5O  p   Æ       ¦Å7#5#&''675#5~>]6? 0 1<8Å);'(= šÅ7#5#&''675#5x=V/8 + 0<7Å)=++? Á7#5#&''675#5k:J'- # ' 1+Á):&'<#GÜÉ7#5#&''67#5£GuGO ?> NHÉ')  (#dÜÏ7#5#&''67#5£GuG I ?@ H GÏ    G¬Å7#5#&''67#5‚=\7A 1 09 4Å!&)S¬É7#5#&''67#5<[5? 2 2 7 4É!!  #HˆÅ7#5#&''67#5g1H&. " ( -(Å " )U‰Å7#5#&''67#5k9N*2 % ' .*Å   8£É7#5#&''675#5|=]8? 1 /:6É$-!"1 2šÉ7#5#&''6675#5q6V1: - . 7É$1%& ' 2Å7#5#&''675#5i8I%, " ' 2-Å -!#1 #hÜÏ7#5#&''67#5£GuF F ?@ E GÏ   #hÜÏ7#5#&''67#5£GuF F ?@ E GÏ   `¬É7#5#&''67#5=\4A 2 2 9 8É   f­É7#5#&''67#5=]4< 2 2 8 7É    bŽÉ7#5#&''67#5l8N*5 ( * 1+É   jŒÉ7#5#&''67#5k9N)1 ( * 1*É J¦Ì7#5#&''675#5|=]8B 2 1 <6Ì!&) EšÌ7#5#&''67#5x=V2; - 0 :6Ì!- !/ B€É7#5#&''67#5i8I', ! ( /,É."3#vÜÏ7#5#&''67#5£GuF H ?@ H GÏ  #vÜÏ7#5#&''67#5£GuGN A? MHÏ  fªÌ7#5#&''67#5>^5> 2 1 9 9Ì   s­Ì7#5#&''67#5=]2 4 05 8 7Ì  kÉ7#5#&''6675#5k8O* ( , +É    tŒÌ7#5#&''67#5k8M(- ( + .)Ì J¥Ì7#5#&''67#5|=]9? 0 1 7 6Ì!(, OšÌ7#5#&''67#5x=V1: , / 8 6Ì ') J€Ì7#5#&''67#5i7H'+ ! ( / ,Ì* .%wÛÏ7#5#&''67#5£GuF F >> D GÏ  %wÛÏ7#5#&''67#5£GuF F >> D GÏ  nªÌ7#5#&''67#5>]5< 2 4 ?8Ì   u¬Ì7#5#&''67#5<[37 05 : 6Ì  oŽÉ7#5#&''67#5l9O)0 ' , 3,É  uŒÉ7#5#&''67#5k8M', ( - - *É  ¹ 7'67'767#53 AT^^s3  s й 7'67'767#53 @R\^s3  s t¹ 7'67'767#53 4AIH]3s.8ν 767'765#53­ˆ‹ˆœ=$ [*.SÍÉ 767'75#53­ˆ‹‡› X  Q%E˜½ 767'765#53ycgey I"  S%W˜Å 767'765#53|dgey \  L"Fz½ 767'75#53\KOL` K  R%YÅ 767'75#53bLQNb ]  K!.ν 767'765#53ª ˆ‹ˆœ!6 u62˜½ 767'765#53t cgdx7' `+1‘À 7'67'767#53 ;Q\[s1)  f 1ŠÀ 7'67'767#53 :P\[s1)  f 5wÀ 7'67'767#53 ->JH` 5%  c.bÍÉ 767'75#53°ˆ‹‡› g G .bÍÉ 767'75#53°ˆ‹‡› g G `˜Å 767'765#53|cgey e  E `˜Å 767'765#53|cgey e  E `|Å 767'75#53`MQNbe  E mÅ 767'75#53fMQNbq   =U˜Å 767'765#53|addx Z  M#A‘Å 767#53'67'pZrd 9Jš ^&$  AŠÅ 767#53'67'h[sd 9Jš ^&$  @wÆ 7'67'767#53 .>HH` @%  _.pÍÉ 767'75#53±ˆ‹‡›u   =.hÍÉ 767'75#53¯ˆ‹‡› m  Bo˜É 767'765#53|addx t   =r˜É 767'765#53{addx v   <q~É 767'765#53aPSPd u   =tÉ 767'75#53gNRNby  :.^ÍÉ 767'75#53±ˆ‹‡›c I"d˜É 767'765#53{addx h  E >’Å 767#53'67'p\se :L˜ `'%  >‹Å 767#53'67'i\te :M˜ `'%  AvÅ 767#53'67'WI_ Q -=˜ ^&$ .rÍÉ 767'75#53²ˆ‹‡›w   <.gÍÉ 767'75#53±ˆ‹‡›l  C r˜É 767'765#53{`c_s v    <q˜É 767'765#53|addx u   =r~Å 767'765#53aPSPd w   9uÅ 767'75#53gORNby  7.eÍÉ 767'75#53°ˆ‹‡› i  Eh˜É 767'765#53|addx l C°½73#3#267#s_ZZIEM½,< ¡½73#3#267#kWRRFCH½,< †½73#3#267#[G==6 5;½,< 1NÒ¿ 7#3#3#5Ή‰‰¡¿q1jÒÉ 7#3#3#5Ή‰‰¡É_#N¦¿ 7#3#3#5¡jjjoƒ¿q"f¡Æ 7#3#3#5œfffkÆ`R¿ 7#3#3#5|JJJOc¿md†Æ 7#3#3#5OOOThÆbF°Á 73#3#27#s_ZZC8EMÁ$F Á73#3#267#kWRRFDFÁ$ F‡Á73#3#267#[G==; 6;Á$ 1jÒÉ 7#3#3#5Ή‰‰¡É_1jÒÌ 7#3#3#5Ή‰‰¡Ìb%e¦Æ 7#3#3#5¡hhhmÆa"k¡É 7#3#3#5œfffkÉ^c†Æ 7#3#3#5OOOThÆcn†Æ 7#3#3#5OOOThÆ XS°Æ 73#3#27#s_ZZG4GKÆ  S Æ 73#3#27#kWRRC0GCÆ  S‡Æ 73#3#27#[G==5%6;Æ 1sÒÉ 7#3#3#5Ή‰‰¡ÉV1tÒÌ 7#3#3#5Ή‰‰¡ÌX%o¦É 7#3#3#5 gggmÉZ"v¡Ê 7#3#3#5œfffkÊ Tp†Æ 7#3#3#5OOOThÆVz†Ê 7#3#3#5OOOThÊ  PW°Æ 73#3#27#s_ZZC8DNÆ  W”Æ 73#3#27#kWRR8.@>Æ  Z€Æ 73#3#27#[G==-%/;Æ1xÒÌ 7#3#3#5Ή‰‰¡Ì T1xÒÌ 7#3#3#5Ή‰‰¡Ì T%s¦É 7#3#3#5 gggmÉV"w¤É 7#3#3#5žhhhn‚É Rs†Å 7#3#3#5OOOThÅ Rz†Å 7#3#3#5OOOThÅ  K¯¹7#537737'7£„&  )œ)¹#b_^  ž¹7#537737'7˜  &’(¹#c`_  ‡¹7#537737'7|e   #{¹!eca .VѾ7#5#53'3373Ñ£ œ#/¾T8888.qÑË7#5#53'3373Ñ£ œ"1ËF++++Q¨½7#537737'7¥…!%Œ"½=;;h¨Æ7#537737'7¥…!$Œ"Æ-,+Q‡½7#537737'7„d l½<;:d‡Æ7#537737'7„e lÆ432F®Á7#537737'7¥†%(š)ÁHFD  CžÁ7#537737'7—€ %’(ÁJHG  H‡Á7#537737'7|e   #{ ÁECB .uÑË7#5#53'3373Ñ£ œ"0ËB''''.{ÑË7#5#53'3373Ñ£ œ"1Ë<!!!!k¨Æ7#537737'7¥…!$Œ"Æ,++s¨Ê7#537737'7¥…!#Œ#Ê)''h‡Æ7#537737'7„e lÆ0/.q‡Æ7#537737'7„e lÆ'&%Y®Æ7#537737'7£„('™&Æ:87  SÆ7#537737'7˜‚ $’(Æ@><  S‡Æ7#537737'7|f "|Æ?=< .~ÑË7#5#53'3373Ñ£ œ"1Ë9.‚ÑË7#5#53'3373Ñ£ œ"1Ë5s¡Ê7#537737'7œ|"…"Ê)'&€¨Ê7#537737'7¥…##Œ"Ês‡Æ7#537737'7„e lÆ%$#€‡Ê7#537737'7„e lÊX®Æ7#537737'7£„&(š(Æ;98  SœÆ7#537737'7—€!#(Æ@><  S‡Æ7#537737'7|e "{Æ?>< .‚ÑË7#5#53'3373Ñ£ œ"1Ë5.‚ÑË7#5#53'3373Ñ£ œ"1Ë5|¡Ê7#537737'7œz"‚Ê" ¨Ê7#537737'7¥‚"$‰Êx‡Æ7#537737'7„e lÆ! |‡Æ7#537737'7„e lƦÁ7#5#5"&54632'"32654&€Dj‘IÁ$‚P  ¡Á7#5#5"&54632'"32654&zDk‘HÁ$‚P  ƒÁ7#5#5"&54632'"32654&f9Vs9  Á$‚P*:ÖÅ7#5#5"&54632'"32654&¥K|¬V "!! "#Ål>   *YÖÏ7#5#5"&54632'"32654&¥K|¬V!!!! "" Ï\4   :¦Å7#5#5"&54632'"32654&€@fŒFÅm@ P¦Í7#5#5"&54632'"32654&€@fŒFÍb9   >‹Å7#5#5"&54632'"32654&k7Ww;  Åi> Q‹Ê7#5#5"&54632'"32654&k7Ww;  Ê_6   ?¦Ê7#5#5"&54632'"32654&€Dj‘IÊl@ ?‰Ê7#5#5"&54632'"32654&i9Yy<  Ê!j> *[ÖÏ7#5#5"&54632'"32654&¥K|¬V!! "!!" ÏZ  2   *_ÖÏ7#5#5"&54632'"32654&¥K|¬V" " "!!ÏV  ,T¦Ê7#5#5"&54632'"32654&€@fŒFÊX  0   ^¦Ì7#5#5"&54632'"32654&€@fŒFÌT  -Z‹É7#5#5"&54632'"32654&k7Ww;  ÉV  . e‹É7#5#5"&54632'"32654&k7Ww;  ÉL  'M¦Í7#5#5"&54632'"32654&€Dj‘IÍc9   M¡Í7#5#5"&54632'"32654&zCj‘HÍc9   N‰Ê7#5#5"&54632'"32654&i9Yy<  Ê_6   *eÖÏ7#5#5"&54632'"32654&¥K|¬V" " "!!ÏQ  +*nÖÏ7#5#5"&54632'"32654&¥K|¬V" #" !!ÏH  $b¦Ì7#5#5"&54632'"32654&€@fŒFÌP  )l¦Ì7#5#5"&54632'"32654&€@fŒFÌH  $a‹É7#5#5"&54632'"32654&k7Ww;  ÉQ  +l‹Ì7#5#5"&54632'"32654&k7Ww;  ÌH  $M¦Ì7#5#5"&54632'"32654&€Dj‘IÌb9   M¡Ì7#5#5"&54632'"32654&zDk‘HÌb9   K‰É7#5#5"&54632'"32654&i9Yy<  Éa9   *gÖÏ7#5#5"&54632'"32654&¥K|¬V!! "!!" ÏO  )*gÖÏ7#5#5"&54632'"32654&¥K|¬V!! "!!" ÏO  )d¦Ë7#5#5"&54632'"32654&€@fŒFËN  )k¦Ë7#5#5"&54632'"32654&€@fŒFËI  %e‹È7#5#5"&54632'"32654&k7Wx<  ÈK  'i‹É7#5#5"&54632'"32654&k7Wx<  ÉH  $¼ÿëöÎ733##¼&&Î_q¼ÿëöÎ733##¼&&Î_q¼ÿëöÎ733##¼&&Î_q¼&öÎ733##¼&&ÎRC¼&öÎ733##¼&&ÎRC¼+öÎ733##¼&&ÎR>¼OõÎ733##¼%%ÎD(¼OõÎ733##¼%%ÎD(¼OöÎ733##¼&&ÎD(¼@öÎ733##¼&&ÎD7¼@õÎ733##¼%%ÎK0¼@õÎ733##¼%%ÎK0¼TõÎ733##¼%%Î?(¼TõÎ733##¼%%Î?(¼TõÎ733##¼%%Î?(ŒÿëÐÎ 73353#5##ŒÉafãj]”ÿëÑÎ 73353#5##”É`eãl_šÿëÑÎ 73353#5##šÉ`eãl_Œ&ÐÎ 73353#5##ŒËLO¨F=”&ÐÎ 73353#5##”ËLO¨F=š!ÑÎ 73353#5##šËOR­H@ŒKÐÎ 73353#5##ŒËADƒ,)”KÐÎ 73353#5##”ËADƒ,)šKÑÎ 73353#5##šËAŽ:/ŒSÐÎ 73353#5##ŒË8;{--”SÑÎ 73353#5##”Ë>Î~9„LÐÎ753#5#5¼8›3‚<”LÐÎ73#5#53¼((΂(LÐÎ753#5#5¼,”:‚5‡PÐÎ73#5#53¼55Î~1ŒMÐÎ73#5#53¼00Î-€TÐÎ73#5#53¼<<Îz,•RÐÎ73#5#53¼''Î|0‘TÐÎ73#5#53¼++Îz8wTÐÎ73#5#53¼EEÎz@‡TÐÎ73#5#53¼55Îz0„TÐÎ73#5#53¼88Îz4“TÐÎ73#5#53¼))Îz TÐÎ73#5#53¼,,Îz,‡TÐÎ73#5#53¼55Îz.ŒTÐÎ73#5#53¼00Îz$…BÐÎ753#5#5¼7‘=Œ<”BÐÎ753#5#5¼(•9Œ@‘BÐÎ73#5#53¼++ÎŒJnBÐÎ73#5#53¼NNÎŒR~BÐÎ73#5#53¼>>ÎŒH„BÐÎ73#5#53¼88ÎŒF’BÐÎ73#5#53¼**ÎŒ2‘BÑÎ73#5#53½,,ÎŒ?‰BÐÎ73#5#53¼33ÎŒ?ŒBÐÎ73#5#53¼00ÎŒ8gÿëÐÎ 73#'3#5#53¼+**ÎãÞÑj{ÿëÐÎ 73#'3#5#53¼%ÎãÞÑn^ÿëÐÎ 73#'3#5#53¼-11ÎãÞÑukÿëÐÎ 73#'3#5#53¼+&&ÎãÞÑmpÿëÐÎ 73#'3#5#53¼+!!ÎãÞÑvpÿëÐÎ 73#'3#5#53¼($$ÎãÞÑU~ÿëÐÎ 73#'3#5#53½"ÎãÞÑnlÿëÐÎ 73#'3#5#53¼+%%ÎãÞÑaj&ÐÎ 73#'3#5#53¼+''Ψ£›Hu&ÐÎ 73#'3#5#53¼%""Ψ£›K{$ÐÎ 73#'3#5#53¼%Ϊ¥˜O^&ÐÎ 73#'3#5#53¼.00Ψ£›Xl&ÐÎ 73#'3#5#53¼+%%Ψ£›Ql"ÐÎ 73#'3#5#53¼+%%ά§Wo&ÐÎ 73#'3#5#53¼(%%Ψ£›/€&ÑÎ 73#'3#5#53½"Ψ£›Et"ÐÎ 73#'3#5#53¼+ά§Fo)ÐÎ 73#'3#5#53¼(%%Î¥ =lLÐÎ 73#'3#5#53¼+%%΂}z3LÐÎ 73#'3#5#53¼%΂}z4yLÐÎ 73#'3#5#53¼%΂}z8]LÐÎ 73#'3#5#53¼*55΂}zBmLÐÎ 73#'3#5#53¼*%%΂}z;mLÐÎ 73#'53#5#5¼*%΂Q,z;oIÐÎ 73#'3#5#53¼(%%Î…€|€MÑÎ 73#'53#5#5½"ÎL0y6uLÐÎ 73#'3#5#53¼*΂}z-oIÐÎ 73#'3#5#53¼(%%Î…€|)lUÐÎ 73#'3#5#53¼+%%Îytt'zUÐÎ 73#'3#5#53¼%Îytt)zUÐÎ 73#'3#5#53¼%Îytt2bUÐÎ 73#'3#5#53¼*00Îytt<mUÐÎ 73#'3#5#53¼*%%Îytt5nUÐÎ 73#'3#5#53¼*$$Îytt4pQÐÎ 73#'3#5#53¼($$Î}xx€UÑÎ 73#'53#5#5½"ÎyD0t1sUÐÎ 73#'3#5#53¼+Îytt'oSÐÎ 73#'3#5#53¼(%%Î{vv#hAÐÎ 73#'3#5#53¼+))Έu-~AÐÎ 73#'3#5#53¼%Έu-zBÐÎ 73#'3#5#53¼%ÎŒ‡|:\AÐÎ 73#'3#5#53¼+55Έu<mAÐÎ 73#'3#5#53¼+$$Έu5oAÐÎ 73#'3#5#53¼+""Έ|>qAÐÎ 73#'3#5#53¼)""Έ|€BÑÎ 73#'3#5#53½"ÎŒ‡|5tAÐÎ 73#'3#5#53¼+Έt'qAÐÎ 73#'3#5#53¼)""Έ|)ÿëÐÎ 753#5#535#5¼==2ŠDãO*‰ÿëÐÎ 753#5#535#5¼33-“;ãU-ÿëÐÎ 753#5#535#5¼,,,—7ãf vÿëÐÎ 753#5#535#5¼FFF1ão‡ÿëÐÎ 753#5#535#5¼555’<ã^$ŠÿëÐÎ 753#5#535#5¼222š4ãe$”ÿëÐÎ 753#5#535#5¼(((yUãG ÿëÐÎ 753#5#535#5¼---’<ãW+‡ÿëÐÎ 753#5#535#5¼555’<ã^$ŽÿëÐÎ 73#5#535#53¼....Îã[&ÐÎ 73#5#535#53¼==22Ψ-&‹&ÐÎ 73#5#535#53¼11,,Ψ3%•"ÐÎ 73#5#535#53¼''''άH†&ÐÎ 73#5#535#53¼6666ΨD"ÐÎ 73#5#535#53¼----ά: †%ÐÎ 73#5#535#53¼6666ΩE&ÐÎ 73#5#535#53¼,,,,Ψ1€PÐÎ 73#5#535#53¼<<22Î~#‹PÐÎ 73#5#535#53¼1111Î~ •OÐÎ 73#5#535#53¼''''Î)vOÐÎ 73#5#535#53¼FFFFÎ1ŒOÐÎ 73#5#535#53¼0000Î!OÐÎ 73#5#535#53¼----Î&”IÐÎ 73#5#535#53¼((((Î…OÐÎ 73#5#535#53¼,,,,ΉOÐÎ 73#5#535#53¼3333Î%’IÐÎ 73#5#535#53¼****Î…€TÐÎ 73#5#535#53¼<<22ÎzŒTÐÎ 73#5#535#53¼0000Îz—UÐÎ 73#5#535#53¼%%%%Îy&vTÐÎ 73#5#535#53¼FFFFÎz.‹SÐÎ 73#5#535#53¼1111Î{‘SÐÎ 73#5#535#53¼++++Î{&”IÐÎ 73#5#535#53¼((((Î…TÐÎ 73#5#535#53¼,,,,Îz‰PÐÎ 73#5#535#53¼3333Î~%”QÐÎ 73#5#535#53¼((((Î} €BÐÎ 73#5#535#53¼<<22ÎŒ"ŒBÐÎ 73#5#535#53¼0000ÎŒ%—BÐÎ 73#5#535#53¼%%%%ÎŒ7vBÐÎ 73#5#535#53¼FFFFÎŒ@‹BÐÎ 73#5#535#53¼1111ÎŒ.ˆBÐÎ 73#5#535#53¼4444ÎŒ5”<ÐÎ 73#5#535#53¼((((Î’BÐÎ 73#5#535#53¼----ÎŒ-ˆBÐÎ 73#5#535#53¼4444ÎŒ5”FÐÎ 73#5#535#53¼((((ΈfÿëÐÎ73#'3#5#535#53¼+++%%ÎãÞÑJ'tÿëÐÎ73#'53#5#535#5¼%## Îã¤:ÑJ'wÿëÐÎ73#'53#5#535#5¼% Îã¤:ÑQ \ÿëÐÎ73#'3#5#535#53¼.2222ÎãÞÑckÿëÐÎ73#'53#5#535#5¼+&&&Îã«3ÑQ'pÿëÐÎ73#'53#5#535#5¼+!!!Îã­1Ñ]zÿëÐÎ73#'53#5#535#5¼(Îã‹SÑ;€ÿëÐÎ73#'53#5#535#5¼!Îã¤:ÑA0kÿëÐÎ73#'3#5#535#53¼+&&&&ÎãÞÑQ'wÿëÐÎ73#'53#5#535#5¼(Îã‘MÑKf&ÐÎ73#'3#5#535#53¼+++!!Ψ£›+!u$ÐÎ73#'3#5#535#53¼%"" Ϊ¥˜+!}$ÐÎ73#'3#5#535#53¼%Ϊ¥˜?^&ÐÎ73#'3#5#535#53¼.0000Ψ£›Fk&ÐÎ73#'3#5#535#53¼+&&&&Ψ£š5r&ÐÎ73#'3#5#535#53¼+Ψ£—;v&ÐÎ73#'53#5#535#5¼(Ψ`C›€&ÑÎ73#'3#5#535#53½"Ψ£›. k"ÐÎ73#'3#5#535#53¼+&&&&ά§;v)ÐÎ73#'3#5#535#53¼(Î¥ + fLÐÎ73#'3#5#535#53¼+++ ΂}ztLÐÎ73#'3#5#535#53¼%## ΂}zyLÐÎ73#'3#5#535#53¼%΂}z, ]LÐÎ73#'3#5#535#53¼.1111΂}z/mLÐÎ73#'3#5#535#53¼*%%%%΂}z!rIÐÎ73#'53#5#535#5¼+Î…c|,tIÐÎ73#'3#5#535#53¼( Î…€| €MÐÎ73#'53#5#535#5¾#ÎY#ykLÐÎ73#'3#5#535#53¼*''''΂}z#vIÐÎ73#'3#5#535#53¼(Î…€| bUÐÎ73#'3#5#535#53¼+//ÎyttwUÐÎ73#'3#5#535#53¼% ÎyttyUÐÎ73#'3#5#535#53¼%Îytt& cUÐÎ73#'3#5#535#53¼.++++Îytt)mUÐÎ73#'3#5#535#53¼+$$$$ÎyttrUÐÎ73#'3#5#535#53¼+Îytt$tMÐÎ73#'3#5#535#53¼( Î|| €UÑÎ73#'53#5#535#5½"ÎyQ#tnUÐÎ73#'3#5#535#53¼+####Îytt!vNÐÎ73#'3#5#535#53¼(΀{{ bBÐÎ73#'3#5#535#53¼+//!!ÎŒ‡yvBÐÎ73#'3#5#535#53¼%!!!!ÎŒ‡yyBÐÎ73#'3#5#535#53¼%ÎŒ‡|-bBÐÎ73#'3#5#535#53¼.,,,,ÎŒ‡t)nBÐÎ73#'3#5#535#53¼,""""ÎŒ‡t!oBÐÎ73#'3#5#535#53¼+""""ÎŒ‡}+oBÐÎ73#'3#5#535#53¼(%%%%ÎŒ‡| €BÑÎ73#'53#5#535#5½"ÎŒ_(|lBÐÎ73#'3#5#535#53¼+%%%%ÎŒ‡tvBÐÎ73#'3#5#535#53¼(ÎŒ‡|  ên7#5353êÕVQQ êp7#5353êÕfSS êW7#5353êÕa:: êe7#5353êÕaHH ê]7#5353êÕa@@ ê>7#5353êÕa!! êN7#5353êÕa11 êQ7#5353êÕa44Gê7#5353êÕVZ55Lê7#5353êÕl_..Dêp7#5353êÕaWDê{7#5353êÕaW$$Bê}7#5353êÕaU((9êc7#5353êÕaLAêo7#5353êÕaTGê7#5353êÕVZ%%@êo7#5353êÕaS\ê˜7#5353êÕVo))\ê˜733#53UÕl˜)Uê|7#5353êÕahXê7#5353êÕak$$Uê‡7#5353êÕahXê…7#5353êÕakFêo7#5353êÕaYPêy7#5353êÕacZêˆ7#5353êÕWmQêy7#5353êÕadcê7#5353êÕVv''cê7#5353êÕlv''[êƒ7#5353êÕan[êŽ7#5353êÕan [ê‰7#5353êÕanYê…7#5353êÕalIêo7#5353êÕa[Zê~7#5353êÕal[êŠ7#5353êÕWnYê€7#5353êÕakWê˜733#53UÕl˜.Qê733#53~XÕiSêŽ7#5353êÕaf((Sê‡7#5353êÕaf!!Sê‡7#5353êÕaf!!Eêo7#5353êÕaXNêz7#5353êÕaaSê…7#5353êÕWfLê{7#5353êÕa_ÿëöÎ733##'37'7¼&&tP™8ÎhhIÿëöÎ733##'37'7¼&&^;™NÎhhGÿëöÎ733##'37'7¼&&fB™FÎhhl+ÿëöÎ733##'37'7¼&&gB™EÎhh„CÿëöÎ733##'37'7¼&&gB™EÎhhx7ÿëöÎ733##'37'7¼&&fB™FÎhhu4ÿëöÎ733##'37'7¼&&fB™FÎhh[ ÿëöÎ733##'37'7¼&&fB™FÎhhl,ÿëöÎ733##'37'7¼&&tP™8Îaoy1ÿëöÎ733##'37'7¼&&fB™FÎhhl,$öÎ733##'37'7¼&&tP™8ÎSDj7 $öÎ733##'37'7¼&&_;šMÎSDj6 $öÎ733##'37'7¼&&fBšFÎSDI $öÎ733##'37'7¼&&fBšFÎSD_+ $öÎ733##'37'7¼&&fBšFÎSDT$ $öÎ733##'37'7¼&&fBšFÎSDY+ $öÎ733##'37'7¼&&fBšFÎY>> $öÎ733##'37'7¼&&fBšFÎRDF $öÎ733##'37'7¼&&uQš7ÎQFV' $öÎ733##'37'7¼&&fBšFÎSDH LöÎ733##'37'7¼&&tPš8ÎE*M0 LöÎ733##'37'7¼&&^;šNÎE*N/ LöÎ733##'37'7¼&&fBšFÎE*+ LöÎ733##'37'7¼&&fBšFÎE*?& KöÎ733##'37'7¼&&fBšFÎE+8 KöÎ733##'37'7¼&&fBšFÎE+<%>öÎ733##'37'7¼&&fBšFÎN** DõÎ733##'37'7¼%%fFFÎN)3 MöÎ733##'37'7¼&&uQš7ÎE)8  CöÎ733##'37'7¼&&fBšFÎE32 SöÎ733##'37'7¼&&tPš8ÎA'F+ SöÎ733##'37'7¼&&^:šNÎA'C& QöÎ733##'37'7¼&&fBšFÎA)* QöÎ733##'37'7¼&&fBšFÎA)A$ QöÎ733##'37'7¼&&fBšFÎA)5RöÎ733##'37'7¼&&fBšFÎA(6DöÎ733##'37'7¼&&fBšFÎN', PöÎ733##'37'7¾%%iBšFÎA*+PöÎ733##'37'7¼&&tPš9ÎA*6IöÎ733##'37'7¼&&fBšGÎA11<öÎ733##'37'7¼&&uQš7ÎE:]5 <öÎ733##'37'7¼&&^;šNÎE:Z1 <öÎ733##'37'7¼&&fBšFÎE:> <öÎ733##'37'7¼&&fBšFÎE:O& <öÎ733##'37'7¼&&fBšFÎE:G#<öÎ733##'37'7¼&&fBšFÎE:L'6öÎ733##'37'7¼&&fBšFÎN77 7öÎ7##5337'7ö%{BšF€6—N  <öÎ733##'37'7¼&&uQš7ÎE:I# 6öÎ733##'37'7¼&&fBšFÎDA= ÿëÐÎ 73353#5##'37'7’`?t$É`eãl_zI ÿëÐÎ 73353#5##'37'7–M0|;É`eãl_}J ÿëÐÎ 73353#5##'37'7–L/|<É`eãl_b/ ÿëÐÎ 73353#5##'37'7‘K+t8É`eãl_zG ÿëÐÎ 73353#5##'37'7‘K+t8É`eãl_l9 ÿëÐÎ 73353#5##'37'7’L+t8É`eãl_j7 ÿëÐÎ 73353#5##'37'7‘K+t8É`eãl_R ÿëÐÎ 73353#5##'37'7›U68É`eãl_\) ÿëÐÎ 73353#5##'37'7’X7t,É`eãl_l2 ÿëÐÎ 73353#5##'37'7“M+t8É`eãl_b' &ÐÎ 73353#5##'37'7’`@u$ËEH¨MDb9 &ÐÎ 73#5##533'37'7¼s2}:ΨMDœE1&ÐÎ 73#5##53337'7¼v5}7ΨMDœE&ÐÎ 73353#5##'37'7’L,u8ËEH¨MDM#&ÐÎ 73353#5##'37'7’L1y7ËKN¨G>J$&ÐÎ 73353#5##'37'7’L1y7ËKN¨G>J$&ÐÎ 73353#5##'37'7’L1y7ËX[¨;2:+ÐÎ 73353#5##'37'7—Q5}7ËEH£G;3 +ÐÎ 73353#5##'37'7’X7s+ËEH£G;F( $ÐÎ 73353#5##'37'7’L1y7ËWZª=6BHÐÎ 73353#5##'37'7‘_?s#Ì:<†74K.HÐÎ 73353#5##'37'7–M.y:Ì:<†74I/HÐÎ 73353#5##'37'7–P3{7Ì:<22+IÐÎ 73353#5##'37'7‘K1y7Ì@B…0,9IÐÎ 73353#5##'37'7‘K1y7ÌEG…+'6!HÐÎ 73353#5##'37'7‘K1y7Ì:<†749#?ÐÎ 73353#5##'37'7‘K1y7ÌLNŽ--+HÐÎ 73353#5##'37'7šT8€7ÌEG…+'& IÐÎ 73353#5##'37'7‘W9v,Ë9<…628  BÐÎ 73353#5##'37'7‘K1z8ËKNŒ+(1 SÐÎ 73353#5##'37'7‘_Au#Ì:<{,,C*SÐÎ 73353#5##'37'7–M0{:Ì:<{,,A&RÐÎ 73353#5##'37'7–P4|7Ì:<|---RÐÎ 73353#5##'37'7‘K1y7Ì?A|((@%RÐÎ 73353#5##'37'7‘K1y7Ì?A|((2RÐÎ 73353#5##'37'7‘K1y7Ì?A|((5CÐÎ 73353#5##'37'7‘K1y7ÌLNˆ'', OÐÎ 73353#5##'37'7šT77ÌEG%%2RÐÎ 73353#5##'37'7‘W9v,Ë9<|--1 IÐÎ 73353#5##'37'7‘K0y8ËBE…../ <ÐÎ 73353#5##'37'7‘_Bv#Ì:<’C9L-<ÐÎ 73353#5##'37'7–M1|:Ì@B’=3K/<ÐÎ 73353#5##'37'7–P4|7Ì@B’=21 =ÐÎ 73353#5##'37'7‘K1y7Ì?A‘=3@&=ÐÎ 73353#5##'37'7‘K1y7ÌKM‘1'9$<ÐÎ 73353#5##'37'7‘K1y7Ì@B’=3:$ 6ÐÎ 73353#5##'37'7‘K1y7ÌLN˜704 =ÐÎ 73353#5##'37'7šT8€7ÌFH‘5++ =ÐÎ 73353#5##'37'7‘W9v,Ë9<‘B8>  6ÐÎ 73353#5##'37'7‘K1z8ËLO˜6,1 ÿëÐÎ 73#'37'7¼tP™8ÎãIÿëÐÎ 73#''753¼™NÎãLIGÿëÐÎ 73#'37'7¼^:™NÎãl*ÿëÐÎ 73#''753¼™EÎãHGEÿëÐÎ 73#''753¼™EÎãH86ÿëÐÎ 73#''753¼™EÎãC!ÿëÑÎ 73#''753½šEÎãF-+ÿëÐÎ 73#''753¼™8ÎãP42ÿëÐÎ 73#''753¼™EÎãH,*$ÐÎ 73#'37'7¼tPš8Ϊj7 $ÐÎ 73#'37'7¼_;šMΪj6 $ÐÎ 73#'37'7¼fC›FΪI $ÐÎ 73#'37'7¼fBšFΪ_+ $ÐÎ 73#'37'7¼fBšFΪT$ $ÐÎ 73#'37'7¼fC›FΪY+ $ÐÎ 73#'37'7¼fBšFΪ> $ÑÎ 73#'37'7½gEFΪF $ÐÎ 73#'37'7¼uQš7ΪV' $ÐÎ 73#'37'7¼fC›FΪH LÐÎ 73#'37'7¼tPš8΂M0 LÐÎ 73#'37'7¼^<›N΂N/ LÐÎ 73#'37'7¼fC›F΂+ LÐÎ 73#'37'7¼fC›F΂?& KÐÎ 73#'37'7¼fC›F΃8 KÐÎ 73#'37'7¼fC›F΃<%>ÐÎ 73#'37'7¼fC›F΋* KÐÎ 73#'37'7¼fEF΃+ MÐÎ 73#'37'7¼uQš7Î8  CÐÎ 73#'37'7¼fC›F΋2 SÐÎ 73#'37'7¼tQ›8Î{F+ SÐÎ 73#'37'7¼^;›NÎ{C% QÐÎ 73#'37'7¼fC›FÎ}* QÐÎ 73#'37'7¼fC›FÎ}A$ QÐÎ 73#'37'7¼fC›FÎ}5RÐÎ 73#'37'7¼fC›FÎ|6 DÐÎ 73#'37'7¼fC›FΈ, PÑÎ 73#'37'7¾iEFÎ~+ PÐÎ 73#'37'7¼tRœ9Î~6IÐÎ 73#'37'7¼fDœGÎ…1<ÐÎ 73#'37'7¼uQš7Î’]5 <ÐÎ 73#'37'7¼^<›NÎ’Z1 <ÐÎ 73#'37'7¼fC›FÎ’> <ÐÎ 73#'37'7¼fBšFÎ’O& <ÐÎ 73#'37'7¼fBšFÎ’G#<ÐÎ 73#'37'7¼fC›FÎ’L'6ÐÎ 73#'37'7¼fC›FΘ7 :ÑÎ 73#'37'7¾hEFΔ= <ÐÎ 73#'37'7¼uR›7Î’I# 6ÐÎ 73#'37'7¼fC›FΘ=  ëu 733533#53J YÖ5uXXX ëm 733533#53jEÖUmPPP ëU 733533#53O::Ö:U888 ëU 733533#53O::Ö:U888 ëR 733533#53O::Ö:R555 ëV 733533#53O::Ö:V999 ë= 733533#53N<9Ö9=  ëM 733533#53O::Ö:M000 ëg 733533#53J YÖ5gJJJ ëK 7#5353353ëÖ::....Gë 733533#53N!TÖ9555Gë 733533#53nBÖY333Aës 733533#53M=9Ö8sHëƒ 733533#53M=9Ö8ƒ(((Dë{ 733533#53M=9Ö8{$$$Dëy 733533#53M=9Ö8y""":ëa 733533#53M=9Ö8aAë| 733533#53M=9Ö8|(((Gë} 733533#53N!TÖ9}###Aën 733533#53M=9Ö8n\ë˜ 733533#53N!TÖ9˜)))\ë˜ 7#5353353ëÖYo))))Uë… 733533#53M=9Ö8…ZëŒ 733533#53M=9Ö8ŒUë… 733533#53M=9Ö8…Uë… 733533#53M=9Ö8…Gëm 733533#53M=9Ö8mSë… 733533#53M=9Ö8…Zë† 733533#53N!TÖ9†Sëx 733533#53M=9Ö8xcëœ 733533#53N!TÖ9œ&&&cëœ 733533#53nBÖYœ&&&Zë… 733533#53M=9Ö8…ZëŽ 733533#53M=9Ö8Ž!!!Zë‰ 733533#53M=9Ö8‰Xë… 733533#53M=9Ö8…Iëm 733533#53M=9Ö8mZë„ 733533#53M=9Ö8„Zë‡ 733533#53N!TÖ9‡Xë} 733533#53M=9Ö8}Wë˜ 733533#53N!TÖ9˜...Wë— 733533#53nBÖY—---Që… 733533#53M=9Ö8…!!!XëŒ 733533#53M=9Ö8Œ!!!Rë„ 733533#53M=9Ö8„Rë„ 733533#53M=9Ö8„Gëm 733533#53M=9Ö8mSë… 733533#53M=9Ö8…Rë… 733533#53N!TÖ9… Lëx 733533#53M=9Ö8xÿëëf7##5#5ëbafhhÿëëY7##5#5ëbaY[[ÿëë\7##5#5ëba\^^ÿëëY7##5#5ëbaY[[ÿëëY7##5#5ëbaY[[ÿëëO7##5#5ëbaOQQ ëv7##5#5ë_dvCC ëf7##5#5ë_df33 ëk7##5#5ë_dk88 ë_7##5#5ë_d_,,ëV7#53##ydÖ_C(Kë}7##5#5ëba}Fër7#53##vaÖb^?ëu7##5#5ëbau##>ëm7##5#5ëbam4ëb7##5#5ëbab<ëm7##5#5ëbam:ëi7##5#5ëbaiIë}7##5#5ëba}!!Eëp7#53##vaÖb\Eëu7##5#5ëbauEëo7##5#5ëbao:ëe7##5#5ëbaeDëm7##5#5ëaamBëh7##5#5ë``hIë}7##5#5ëba}!!Iëp7#53##vaÖb\Iëp7##5#5ëbapIëp7##5#5ëbap<ë`7##5#5ëba`Gëm7##5#5ëbam3ëf7##5#5ëbaf Ië7##5#5ëba##Bër7#53##vaÖb_Bëv7#53##vaÖbc!Bëo7#53##vaÖb]7ë_7#53##vaÖbMBëo7#53##vaÖb]Fëo7#53##vaÖb]ÿëÐÎ753#5#5''7#¼72AšBBŒãD \ÿëÐÎ753#5#5''7#¼70C¡F7—ã9 KÿëÐÎ753#5#5''7#¼70Cš?;“ã= QÿëÐÎ753#5#5''7#¼70Cš?4šã6 KÿëÐÎ753#5#5''7#¼61Cš?-¡ã/ :ÿëÐÎ753#5#5''7#¼70C¢H7—ã9 KÿëÐÎ73#5#53''7#¼66fDš?Îã0  A#ÐÎ753#5#5''7#¼72AšBWw«! ?#ÐÎ753#5#5''7#¼51Dš?O«  /#ÐÎ753#5#5''7#¼51Dš?O«  /#ÐÎ753#5#5''7#¼51Dš?O«  /ÐÎ753#5#5''7#¼52Cš@BŒ³)#ÐÎ753#5#5''7#¼52CœBO«  /ÐÎ753#5#5''7#¼52C›AG‡³ 2KÐÎ753#5#5''7#¼55@šCkcƒ &BÐÎ753#5#5''7#¼51D E_oŒ  !DÐÎ753#5#5''7#¼51Dš?djŠ "DÐÎ753#5#5''7#¼51Dš?`nŠ   =ÐÎ753#5#5''7#¼51Dš?Uy‘ AÐÎ753#5#5''7#¼52C¢H]q  =ÐÎ753#5#5''7#¼51Dš?Ww‘  DÐÎ753#5#5''7#¼54AšBlbŠ0AÐÎ753#5#5''7#¼51D¡F`n   @ÐÎ753#5#5''7#¼51D›@eiŽ %@ÐÎ753#5#5''7#¼51D›@eiŽ #=ÐÎ753#5#5'7#¼51Dš?Xv‘  AÑÎ753#5#5''7#½62C£I_o BÐÎ753#5#5''7#¼51D›?\rŒ  =ÐÎ753#5#5''7#¼54A›Chf‘ 3<ÐÎ753#5#5''7#¼51D Eam‘,<ÐÎ753#5#5''7#¼51Dš?ck‘-<ÐÎ753#5#5''7#¼51Dš?\r‘  *7ÐÎ753#5#5''7#¼51Dš?Q}— !=ÐÎ753#5#5''7#¼52C¢H^p‘(7ÐÎ753#5#5''7#¼51Dš?Yu—%LÐÎ753#5#5''7#¼54AšBlb  *EÐÎ753#5#5''7#¼51D E`n‰  !CÐÎ753#5#5''7#¼51D›@ei‹ #EÐÎ753#5#5''7#¼51D E`n‰  !<ÐÎ753#5#5#'7#¼51D›@Xv‘ BÐÎ753#5#5''7#¼52C¢H_oŒ  AÐÎ753#5#5''7#¼51D›@[s ÿëÐÎ 73#'3#5#53''7#¼'&&P3|2ÎãÞÑBXÿëÐÎ 73#'3#5#53''7#¼'&&P3|2ÎãÞÑ/ AÿëÐÎ 73#'3#5#53''7#¼'&&P3|2ÎãÞÑ6  GÿëÐÎ 73#'3#5#53''7#¼'&&P3|2ÎãÞÑ6  GÿëÐÎ 73#'3#5#53''7#¼'&&P3|2ÎãÞÖ+:ÿëÐÎ 73#'3#5#53''7#¼ ''V46ÎãÞÖ4 @ÿëÐÎ 73#'3#5#53''7#¼&''Q3|2ÎãÞÖ/ C#ÐÎ 73#'3#5#53''7#¼'&&P3{1Ϋ¦ž!  4#ÐÎ 73#'3#5#53''7#¼'&&P3{1Ϋ¦ž ##ÐÎ 73#'3#5#53''7#¼'&&P3{1Ϋ¦ž  -ÐÎ 73#'3#5#53''7#¼'&&P3{1β­¤(ÐÎ 73#'3#5#53''7#¼'&&M6{.β­¤&#ÐÎ 73#'3#5#53''7#¼!&&T55Ϋ¦ž%ÐÎ 73#'3#5#53''7#¼'&&O4{0β­¤'IÐÎ 73#'3#5#53''7#¼'&&O4{0Î…€|)BÐÎ 73#'3#5#53''7#¼'&&O4{0ÎŒ‡ƒ!BÐÎ 73#'3#5#53''7#¼'&&O4{0ÎŒ‡ƒ (BÐÎ 73#'3#5#53''7#¼'&&O4{0ÎŒ‡ƒ !=ÐÎ 73#'3#5#53''7#¼'&&O4{0ΑŒŒBÐÎ 73#'3#5#53''7#½"&&U4€5ÎŒ‡„!=ÐÎ 73#'3#5#53''7#¼&''P4{0ΑŒ‰ #DÐÎ 73#'3#5#53''7#¼&''P4{0Ί…‚(BÐÎ 73#'3#5#53''7#¼&''P4z/ÎŒ‡„"HÐÎ 73#'3#5#53''7#¼&&&P4{0Ά $HÐÎ 73#'3#5#53''7#¼&&&P4{0Ά <ÐÎ 73#'3#5#53''7#¼&''Q4{1Î’ŠBÐÎ 73#'3#5#53''7#¼!&&U46ÎŒ‡„ !BÐÎ 73#'3#5#53''7#¼&''P4{0ÎŒ‡„  <ÐÎ 73#'3#5#53''7#¼'&&P3{1ΑŒ‚ 4<ÐÎ 73#'3#5#53''7#¼'&&P3{1ΑŒ‚ +<ÐÎ 73#'3#5#53''7#¼'&&P3{1ΑŒ‚  2<ÐÎ 73#'3#5#53''7#¼'&&P3{1ΑŒ‚ *6ÐÎ 73#'3#5#53''7#¼'&&P3{1Θ“Œ <ÐÎ 73#'3#5#53''7#¼!&&T56ΑŒ„ *6ÐÎ 73#'3#5#53''7#¼'&&P3{1Θ“Œ *LÐÎ 73#'3#5#53''7#¼&&&P4{0΂}} (EÐÎ 73#'3#5#53''7#¼'&&P3{1Ή„„ !EÐÎ 73#'3#5#53''7#¼'&&P3{1Ή„‚  'EÐÎ 73#'3#5#53''7#¼'&&P3{1Ή„„ $=ÐÎ 73#'3#5#53''7#¼'&&P4{1ΑŒŒ  DÐÎ 73#'3#5#53''7#¼!&&T66Ί…  BÐÎ 73#'3#5#53''7#¼'&&P3{1ÎŒ‡„ ÿëÐÎ 73#''7#¼fD EÎãk [ÿëÐÎ 73#''7#¼fD EÎãZ KÿëÐÎ 73#''7#¼fD EÎã_ OÿëÐÎ 73#''7#¼fD EÎãY IÿëÐÎ 73#''7#¼fDš?ÎãI @ÿëÑÎ 73#''7#½gD£HÎãZ JÿëÑÎ 73#''7#½gDš?ÎãO ?#ÐÎ 73#''7#¼fDŸDΫ? 7#ÐÎ 73#'#5'¼ EDΫF('#ÐÎ 73#'#5'¼ EDΫH*(#ÐÎ 73#'#5'¼ EDΫH*(ÐÎ 73#'#5'¼ EDα> #ÑÎ 73#'#5'½EDΫF('ÐÎ 73#'#5'¼ EDαA$#NÐÎ 73#''7#¼gC E΀!"LÐÎ 73#''7#¼gC E΂ GÐÎ 73#''7#¼gC E· GÐÎ 73#''7#¼gC E· ?ÐÎ 73#''7#¼gC EÎ GÐÎ 73#''7#¼gC E· ?ÐÎ 73#''7#¼fD EÎGÐÎ 73#''7#¼gC E·*+GÐÎ 73#''7#¼gC E· GÐÎ 73#''7#¼gC E· GÐÎ 73#''7#¼gC E·?ÐÎ 73#''7#¼fD EÎGÐÎ 73#''7#¼gC E·?ÐÎ 73#''7#¼fD EÎ=ÐÎ 73#''7#¼gC EΑ42=ÐÎ 73#''7#¼gC EΑ$ "=ÐÎ 73#''7#¼gC EΑ('=ÐÎ 73#''7#¼gC EΑ% #8ÐÎ 73#''7#¼fD EΖ>ÐÎ 73#''7#¼gC EÎ$%9ÐÎ 73#''7#¼fD EΕ!PÐÎ 73#''7#¼gC EÎ~  LÐÎ 73#''7#¼gC E΂KÐÎ 73#''7#¼gC E΃KÐÎ 73#''7#¼gC E΃AÐÎ 73#''7#¼fD EÎ IÐÎ 73#''7#¼fD EÎ…CÐÎ 73#''7#¼fD E΋ÿíëf 7##5##5#5ë2K1fff__ÿíëY 7#53##5##F1Ö2KFYYRÿíë\ 7##5##5#5ë2K1\\\UUÿíëY 7#53##5##F1Ö2KFYYRÿíëO 7#53##5##F1Ö2K<OOHÿíëY 7#53##5##F1Ö2KFYYRÿíëO 7#53##5##F1Ö2K<OOH!ëv 7#53##5##U@Ö2;cBB>!ëf 7#53##5##U@Ö2;S22.!ëk 7#53##5##U@Ö2;X773!ë_ 7#53##5##U@Ö2;L++'ëS 7#53##5##U@Ö2;@$$ë_ 7#53##5##U@Ö2;L00+ëV 7#53##5##U@Ö:3C++&Jë} 7#53##5##F1Ö2Kj Gër 7#53##5##F1Ö2K^Jëx 7#53##5##F1Ö2Ke@ëm 7#53##5##F1Ö2KZ8ëb 7#53##5##F1Ö2KO=ëp 7#53##5##F1Ö2K\=ëi 7#53##5##F1Ö2KVLë} 7#53##5##F1Ö2KjBëp 7#53##5##F1Ö2K\Hëu 7#53##5##F1Ö2KbCëo 7#53##5##F1Ö2K]:ëc 7#53##5##F1Ö2KR>ëo 7#53##5##F1Ö2K]Bëi 7#53##5##F1Ö2KW=ë} 7#53##5##F1Ö2Kj---=ëp 7#53##5##F1Ö2K\=ëp 7#53##5##F1Ö2K\=ëp 7#53##5##F1Ö2K\/ë` 7#53##5##F1Ö2KM=ëp 7#53##5##F1Ö2K\4ëf 7#53##5##F1Ö2KSHë 7#53##5##F1Ö2Kl$$$Bër 7#53##5##F1Ö2K`Bër 7#53##5##F1Ö2K`Bëo 7#53##5##F1Ö2K]8ë_ 7#53##5##F1Ö2KLBëo 7#53##5##F1Ö2K]Fëo 7#53##5##F1Ö2K] ë7#5ëÖ ë7#5ëÖ ë7#5ëÖ ë7#5ëÖ ë7#5ëÖ ë7#5ëÖ ë7#5ëÖUëh7#5ëÖhIë\7#5ëÖ\Gë[7#5ëÖ[<ëO7#5ëÖODëW7#5ëÖW@ëS7#5ëÖSfëy7#5ëÖyXëk7#5ëÖkUëh7#5ëÖhUëh7#5ëÖhLë_7#5ëÖ_Uëh7#5ëÖhSëf7#5ëÖfgëz7#5ëÖzUëh7#5ëÖhZëm7#5ëÖmYël7#5ëÖlOëa7#5ëÖaVëh7#5ëÖhXëj7#5ëÖjcëv7#5ëÖvVëh7#5ëÖhYël7#5ëÖlVëh7#5ëÖhJë\7#5ëÖ\Uëh7#5ëÖhQëd7#5ëÖdfëy7#5ëÖy^ër7#5ëÖraës7#5ëÖs_ëq7#5ëÖqLë^7#5ëÖ^]ëo7#5ëÖoZël7#5ëÖlÿëÐÎ73#''¼ ÎãF ÿëÐÎ73#''¼ ÎãF ÿëÐÎ73#''¼ ÎãF ÿëÐÎ73#''¼ ÎãF ÿëÐÎ73#''¼ ÎãN ÿëÐÎ73#''¼ ÎãG #ÐÎ73#''¼œΫJ #ÐÎ73#''¼œΫ: #ÐÎ73#''¼œΫ> #ÐÎ73#''¼œΫ7 #ÐÎ73#''¼œΫ) #ÐÎ73#''¼œΫ@ #ÐÎ73#''¼œΫ7 NÐÎ73#''¼ ΀+NÐÎ73#''¼ ΀NÐÎ73#''¼ ΀ NÐÎ73#''¼ ΀  AÐÎ73#''¼œΊ MÐÎ73#''¼œÎ  DÐÎ73#''¼œΊMÐÎ73#''¼œÎ. MÐÎ73#''¼œÎ& MÐÎ73#''¼œÎ& MÐÎ73#''¼œÎ$ =ÐÎ73#''¼œΑ% MÐÎ73#''¼ žÎ( EÐÎ73#''¼œΉ"=ÐÎ73#''¼œΑ7 =ÐÎ73#''¼œΑ0 =ÐÎ73#''¼œΑ2 =ÐÎ73#''¼œΑ/ 6ÐÎ73#''¼œΘ& =ÐÎ73#''¼œΑ. 6ÐÎ73#''¼œΘ- RÐÎ73#''¼œÎ|( RÐÎ73#''¼œÎ|  SÐÎ73#''¼œÎ{' SÐÎ73#''¼œÎ{ EÐÎ73#''¼œΉRÐÎ73#''¼œÎ| MÐÎ73#''¼œÎ¼ÿëÐÎ73#¼Îã¼ÿëÐÎ73#¼Îã½ÿëÑÎ73#½Îã¼%ÐÎ73#¼Ω¼%ÐÎ73#¼Ω½%ÑÎ73#½Ω¼PÐÎ73#¼Î~¼PÐÎ73#¼Î~¼PÐÎ73#¼Î~¼BÐÎ73#¼ÎŒ¼BÐÎ73#¼ÎŒ¼BÐÎ73#¼ÎŒ¼UÐÎ73#¼Îy¼UÐÎ73#¼Îy½UÑÎ73#½Îy.ÿëÐ@73#5#.¢Ž@UB.ÿëÐ@73#5#.¢Ž@UB.ÿëÐ@73#5#.¢Ž@UB.ÿëÎ=73#5#. Œ=R?.ÿëÐ<73#5#.¢Ž '7#5327#7#5"&54632'"32654&Ä=b#,ºr9  >9<C  )ÿçßQ '7#5327#7#5"&54632'"32654&Ä;`",¶p8  QMIQ  *%ÿçßQ '7#5327#7#5"&54632'"32654&Ä>a#,ºs9  QMIQ  *%ÿçßQ '7#5327#7#5"&54632'"32654&Ä>a#,ºs9  QMIQ  *.ÿçßQ '7#5327#7#5"&54632'"32654&Å:]!)±n7 QN JQ  *)ÿçßQ '7#5327#7#5"&54632'"32654&Ä=^")¶r9  Q FIQ  *%ÿçßQ '7#5327#7#5"&54632'"32654&Ä?`"*ºu:  Q FIQ  *%ÿçßQ '7#5327#7#5"&54632'"32654&Ä?`"*ºu:Q FIQ  *5ÿïÕF7#3#5І‹ F1W2ÿïÕF7#3#5Њ£F1W1ÿïÕF7#3#5Ћ¤F1W1ÿïÑ?7#3#5͈Œ ?*P5ÿïÕ?7#3#5І‹ ?*P5ÿïÕ?7#3#5ЇŒ ?*P2ÿïÕ<7#3#5Њ£<'M1ÿïÑ67#3#5͈Œ 6!G5ÿïÕ67#3#5І‹ 6!G5ÿïÕ67#3#5І‹ 6!G2ÿïÕ67#3#5Њ£6!G5ÿíÕ67#3#5І‹ 6#I5ÿìÕ67#3#5ЇŒ 6$J2ÿìÕ67#3#5Њ£6$J5ÿìÕF7#3#5І‹ F4Z2ÿìÕF7#3#5Њ£F4Z2ÿìÕF7#3#5Њ£F4Z1ÿíÑB7#3#5͈Œ B/U5ÿìÕ@7#3#5І‹ @.T5ÿìÕ@7#3#5І‹ @.T2ÿìÕ@7#3#5Њ£@.T5ÿíÕL 73#3#535#6š‡Œ ‡†L7;5ÿíÕL 73#3#535#6š‡Œ ‡†L7;1ÿíÕL 73#3#535#2ž‹¤‹ŠL7;1ÿíÑL 73#3#535#2œ‰Œ ‰ˆL7;5ÿíÕG 73#3#535#6š‡Œ ‡†G595ÿíÕD 73#3#535#6š‡Œ ‡†D461ÿíÕB 73#3#535#2ž‹¤‹ŠB35 1ÿíÑ< 73#3#535#2œ‰Œ ‰ˆLaN5=)ÿëÐH73#5#'3#67535#†J6]S@%(03??H]J49%ÿëÐE73#5#'3#67535#…K7_TA%*15A@EZH48)ÿëÐ@73#5#'3#27535#†J6]S@$)03??@UC/7 ,ÿëÔ;73#5#'3#67535#ŠJ6]R?$'03?>;P>-3 )ÿëÐ;73#5#'3#67535#†J6]S@%(03??;P>-3 %ÿëÐ;73#5#'3#67535#…K7_TA%(14A@;P>-3 %ÿëÐ;73#5#'3#67535#…K7_TA%(14A@;P=.2 )ÿëÐB73#5#'3#27535#†J6]S@$)03??BWD17 %ÿëÐ?73#5#'3#67535#†J6`VC%(14CB?TB15 %ÿëÐ;73#5#'3#67535#†J6`TA%)14A@;P>.2 )ÿëÐN73#5#'3#27535#†J6]S@%(03??NcP8<%ÿëÐN73#5#'3#27535#†J6`UB%)14BANcP8<#ÿëÐN73#5#'3#27535#…K7`UC&*25CANcP8<,ÿìÔO73#5#'3#67535#ŠJ6]R?$)04?>OcP8=)ÿëÐJ73#5#'3#67535#†J6]S@%(03??J_M5:%ÿëÐI73#5#'3#67535#…K7_TA%*15A@I^L79%ÿëÐH73#5#'3#67535#…K7_TA%(14A@H]J49)ÿíÐL 73#67535#7#5##535#)M:"*-99§%%%L7;]]F3%ÿíÐL 73#67535#7#5##535#&M:!+-:9ª&&&L7;]]F3%ÿíÐL 73#67535#7#5##535#&M:!+-:9ª&&&L7;]]F3+ÿíÔL 73#67535#7#5##535#,L9!+-98¨&&&L7;]]F3)ÿíÐH 7#5##535#'3#67535#Ð%%%nM:#+-99HYYC039%ÿíÐG 7#5##535#'3#67535#Ð'''oN;#,.;:GXXB048%ÿíÐ? 7#5##535#'3#27535#Ð'''oN;#,.;:?PP:).5 ,ÿîÔ; 73#27535#7#5##535#,N:"*-::¨&&&;.0 LL6$)ÿíÐ; 7#5##535#'3#27535#Ð%%%nM:#+-99;LL6$.1 &ÿíÐ; 7#5##535#'3#27535#Ð'''nM:"+.:9;LL6$.1 &ÿíÐ; 7#5##535#'3#27535#Ð'''nM:"+.:9;LL6$.1 )ÿíÐA 7#5##535#'3#27535#Ð'''lM:!*,99ARR<*/6 %ÿíÐA 7#5##535#'3#27535#Ð'''oN;"+.;:ARR<*/6 %ÿíÐ> 7#5##535#'3#27535#Ð'''oN;"+.;:>OO9(,6 )ÿíÐN 73#67535#7#5##535#)M:"*-99§%%%N8<__H5#ÿíÐN 73#27535#7#5##535#%O=!$,0=;«'''N8<__H5#ÿíÐN 73#27535#7#5##535#%O= $,/=;«'''N8<__H5+ÿíÔO 73#67535#7#5##535#+N:"+-::©&&&O8=``I6)ÿîÐI 73#27535#7#5##535#)M:!+,99§%%%I39ZZD3%ÿîÐK 73#67535#7#5##535#%O;#,.;;«&&&K4:\\F4%ÿîÐH 73#27535#7#5##535#%O;$,.;;«&&&H29YYC2)ÿíÐL 73#67535#53#5##535#)M:"*-99“&&&L7; \\(%ÿíÐL 753#5##535#'3#67535#¼'''oM:!+-:9.]](F7;%ÿíÐL 73#67535#53#5##535#&M:!+-:9–'''L7; ]](+ÿðÔL 73#67535#53#5##535#,L9!+-98”&&&L5:ZZ*)ÿíÐH 753#5##535#'3#67535#¼&&&mM:"*-99,YY'C58%ÿíÐG 753#5##535#'3#67535#¼'''oN;#,.;:,XX'B48%ÿíÐ? 753#5##535#'3#27535#¼'''oN;#,.;:+PP&:.5 +ÿîÔ< 73#27535#53#5##535#,L9"+-98”&&&<.1 KK"&ÿíÐ: 753#5##535#'3#67535#¼(((mM:"*.:9&KK!5,3&ÿíÐ9 753#5##535#'3#67535#¼(((mM:"+-:9%JJ 4,1 &ÿíÐ9 753#5##535#'3#67535#¼(((mM:"+-:9%JJ 4,1 &ÿìÐA 753#5##535#'3#67535#¼(((mM:"+-:9*RR%<06 %ÿìÐ@ 753#5##535#'3#67535#¼(((mM;",-;9)QQ$;05 %ÿìÐ= 753#5##535#'3#67535#¼(((mM;",-;9)NN$8/4 )ÿñÐN 73#67535#53#5##535#)M:"*,99“&&&N5;\\+%ÿìÐN 73#27535#53#5##535#&N;#+.;:–&&&N5@^^+%ÿìÐN 73#27535#53#5##535#&N;#+.;:–&&&N5@^^++ÿíÔO 73#67535#53#5##535#,N;"*-;:”&&&O7> ``,)ÿîÐJ 753#5##5335#'3#27535#¼&&&mM:!+,990[[++2:%ÿîÐJ 753#5##5335#'3#27535#¼'''oN;#+.;:0[[++2:%ÿîÐH 753#5##5335#'3#27535#¼'''oN;#+.;:.YY)+29)ÿììM 7'6753&'3#67535#· " )* !–R?!*,>>*)O6<&ÿìéM 7'6753&'3#67535#³ ! )* !”Q>!*,>=))O6<&ÿìéM 7'6753&'3#67535#³ " (, !”Q>!),>=*(O6<&ÿìãM 7'6753&'3#67535#® ! (+ Q>'*>=))O6<)ÿìéI 7'6753&'3#67535#³ '+ !’R? *+>>))K5:%ÿëèG 73#67535#'6753&%S?!*,??Œ  (, !G49*(%ÿëéE 7'6753&'3#67535#± ! )- !–T@!),@@*( J47&ÿìé@ 7'6753&'3#67535#± ), "“R? )+?>#" B0 2 )ÿìé@ 7'6753&'3#27535#´ !** !‘Q? )+>=%$ A/0 %ÿëé> 7'6753&'3#67535#´ # ,* !•S@!),@?%$ A/0 %ÿìé= 7'6753&'3#27535#´ # -* !•R? )+?>$$ ?. 0 )ÿìéB 7'6753&'3#27535#´ # ,* !‘P>'*=<%$ F/5 %ÿìéB 7'6753&'3#27535#´ % -* !•R? (+?>%$ E/4 %ÿìé@ 7'6753&'3#27535#´ # ,* !•R? ),?>%$ C/3 )ÿììP 7'6753&'3#67535#· " )* !–Q> ),==*)S9=%ÿìéP 7'6753&'3#67535#³ # ,* !–S? )+??))S9=%ÿìéP 7'6753&'3#67535#³ $ +* !–S? )+??))S9=&ÿìãN 73#67535#'6753&'Q> (+>=‡  '* N7=!)))ÿììL 73#67535#'6753&)Q>(+==Ž $ ,* !L5=*)%ÿìéL 73#67535#'6753&%S? ),??Ž " +* !L5=))%ÿìéI 73#67535#'6753&%S? ),??Ž # ,* !I5:)))ÿíÔL 73#67535##3#53#)M:"*-99§6:NJ6L5=_%ÿíÔL 73#67535##3#53#%P<"*-;;«8RM9L5=_)ÿîÔG 73#27535##3#53#)M:"*,99§9=PL9G19Y%ÿîÔG 73#27535##3#53#%P<"*-;;«8RN:@03 R+ÿîÙ; 73#67535##3#53#,J7"+-76¨9>RM9;.1 M )ÿîÔ; 73#27535##3#53#)M:"*,99§9=PL9;,2 M %ÿîÔ> 73#27535##3#53#%P<"*-;;«:>RN:>.3 P%ÿîÔ: 73#27535##3#53#&N;"+-;:ª:>QM::,2  L )ÿîÔA 73#27535##3#53#)M:"*-99§9=PL9A05 S%ÿîÔA 73#27535##3#53#&N; "+/;:ª:>RN:A04 S%ÿîÔ= 73#27535##3#53#&N; "+/;:ª:>RN:=-3  O)ÿíÔN 73#67535##3#53#)M:"*-99§6:NJ6N5?a$ÿíÔN 73#67535##3#53#&N< #,/<:ª6:NJ6N5?a$ÿíÔN 73#67535##3#53#&N< #,/<:ª6:NJ6N5?a+ÿìÙO 73#27535##3#53#+K7!+-77©9>RM9O6@b)ÿíÔL 73#67535##3#53#)L9#+-88§6:NJ6L5=_%ÿíÔL 73#67535##3#53#&N; #,/;:ª8 #53#=#J›r OO%%5ÿïÐ> #53#=#J›r OO%%5ÿïÐ> #53#=#J›r OO%%2ÿïÎ; 7#5##535#Îttt;LL4!5ÿïÐ6 7#5##535#Ðrrr6GG/2ÿïÎ7 7#5##535#Îttt7HH2 5ÿïÐ7 7#5##535#Ðsss7HH2 5ÿïÐ7 7#5##535#Ðsss7HH2 5ÿïÐ7 7#5##535#Ðsss7HH2 5ÿïÐ7 7#5##535#Ðsss7HH2 5ÿïÐF 7#5##535#ÐsssFWW?+2ÿïÐF 7#5##535#ÐvvvFWW?+5ÿïÐF 7#5##535#ÐsssFWW?+2ÿïÎF 7#5##535#ÎtttFWW?,5ÿïÐ? 7#5##535#Ðsss?PP8%5ÿïÐ? 7#5##535#Ðsss?PP8%5ÿïÐ? 7#5##535#Ðsss?PP8%5ÿîÐH 73353#5##735#5rrrrHZ5ÿîÐH 73353#5##735#5rrrrHZ5ÿîÐG 73353#5##735#5rrrrGY1ÿîÎH 73353#5##735#1uuuuHZ5ÿîÐC 73353#5##735#5rrrrCU5ÿîÐB 73353#5##735#5rrrrBT5ÿîÐ@ 73353#5##735#5rrrr@R1ÿîÎB 73353#5##735#1uuuuBT5ÿîÐ9 73353#5##735#5rrrr9K5ÿîÐ9 73353#5##735#5rrrr9K5ÿîÐ8 73353#5##735#5rrrr8J5ÿîÐ9 73353#5##735#5rrrr9K5ÿîÐ9 73353#5##735#5rrrr9K5ÿîÐ8 73353#5##735#5rrrr8J5ÿîÐG 73353#5##735#5rrrrGY5ÿîÐG 73353#5##735#5rrrrGY5ÿîÐG 73353#5##735#5rrrrGY1ÿîÎH 73353#5##735#1uuuuHZ5ÿîÐD 73353#5##735#5rrrrDV5ÿîÐD 73353#5##735#5rrrrDV5ÿîÐD 73353#5##735#5rrrrDV)ÿèìH 73353#5##735#'6753&),,,,z " )* !HZ++%ÿèìH 73353#5##735#'6753&%////~ $ +* !HZ++%ÿèìH 73353#5##735#'6753&%////~ $ +* !HZ++-ÿèìL 73353#5##735#'6753&-,,,,v  '* !L^++)ÿèéH 73353#5##735#'6753&),,,,v  &+ !HZ++.ÿêéH 73353#5##735#'6753&.****q $+ !HZ*+%ÿêéB 73353#5##735#7'6753&%////z (+ !BT* +-ÿêé> 7'6753&'3353#5##735#³ $+ "++++' 'BN)ÿêé> 7'6753&'3353#5##735#³  &+ "‘----' 'BN%ÿêé> 7'6753&'3353#5##735#²  &, "•////' ' CN%ÿêé< 73353#5##735#'6753&%////x &, "<N%$ )ÿêé< 73353#5##735#'6753&)----t %, "<N%%%ÿêé< 73353#5##735#'6753&%////x  &, "<N%%%ÿêé< 73353#5##735#'6753&%////x &, "<N%$ )ÿêìN 73353#5##735#'6753&)----x" )+ !N`*+%ÿêìN 73353#5##735#'6753&%////| " ++ !N`*+%ÿêìN 73353#5##735#'6753&%////| " ++ !N`*+-ÿêìN 73353#5##735#'6753&-,,,,t &+ !N`*+)ÿêìH 73353#5##735#'6753&)----x" )+ !HZ*+%ÿêìH 73353#5##735#'6753&%////| " *, !HZ*+%ÿêìH 73353#5##735#'6753&%////| " *, !HZ*+.ÿèãL7'66753&’D *,$% 6$'( & .ÿèãL7'66753&’D *,$% 6$'( & +ÿèàL7'66753&ŽE )-#& 7$'( % #ÿèÜG7'66753&€? '*', ?!$'% .ÿèãF7'66753&E *,$% 8 #&$ .ÿèãF7'66753&E *,$% 8 #&$ .ÿèãF7'66753&E *,$% 8 #&$ #ÿèÜ:7'66753&€@ (*&- @ .ÿèã>7'66753&D *,$% 8"#!.ÿèã>7'66753&D *,$% 8"#!.ÿèã87'66753&D ++#& 9.ÿêã>7'66753&C ++"' 8  .ÿêã>7'66753&C ++"' 8  .ÿêä<7'66753&D ++"( 9  .ÿèäL7'66753&‘D *,$% 7() (' .ÿèäL7'66753&‘D *,$% 7() (' )ÿèåL7'66753&D *-&( ;'()& #ÿèÜG7'66753&€@ &+&, <"%% " -ÿçåE7'66753&D *-#' 8!$& " -ÿçåE7'66753&D *-#' 8!$& " -ÿçåE7'66753&D *-#' 8!$& " #ÿééE 7'6753&''6753&³ # ), !X ( 0 (&( "ÿééE 7'6753&''6753&³ & -, !Z ' 0 (&(  "ÿééE 7'6753&''6753&³ & -, !Z ' 0 (&(  ÿéãE 7'6753&''6753&® & -+ !X ' 0 (&)  "ÿééE 7'6753&''6753&³ # ++ "V ) 3 +)' !ÿçé@ 7'6753&''6753&³ # +, "X * 1 *&*   !ÿçé@ 7'6753&''6753&³ # +, "X * 1 *&*   ÿéã< 7'6753&''66753&® & -+ "Y &  %#  "ÿéé< 7'6753&''6753&³ $ ++ "T * 3 %# '  !ÿéé< 7'6753&''6753&³ $ ++ "W ) 1 %# (  !ÿéé< 7'6753&''6753&³ $ ++ "W ) 1 %# (  !ÿìé< 7'6753&''6753&³ # ++ "V + 3 & $ ' !ÿìé< 7'6753&''6753&³ # ++ "V + 3 & $ ' !ÿìé< 7'6753&''6753&³ # ++ "V + 3 & $ ' ÿééN 7'6753&''6753&³ # ), "X - 5 0. ,  ÿééN 7'6753&''6753&³ # ), "X - 5 0. ,  ÿééN 7'6753&''6753&³ # ), "X - 5 0. ,  ÿéãJ 7'6753&''6753&® % ,+ !Z ( 1 /-!.!  !ÿééI 7'6753&''6753&´ $ ++ W + 4 0 . +  ÿééI 7'6753&''6753&´ $ ++ [ + 4 0 . +  ÿééI 7'6753&''6753&´ $ ++ [ + 4 0 . +  1ÿçÔD "&54632'"32654&ƒ*((*)(()J  1ÿçÔD "&54632'"32654&ƒ*((*)(()J  1ÿçÔD "&54632'"32654&ƒ*((*)(()J  .ÿçÑA "&54632'"32654&€+''+*''*H    1ÿçÔ? "&54632'"32654&‚*''**((*F 1ÿçÔ? "&54632'"32654&‚*''**((*F 1ÿçÔ? "&54632'"32654&ƒ+''+*''*E .ÿçÑ9 "&54632'"32654&€+''+*''*? 1ÿçÔ7 "&54632'"32654&ƒ+''+*''*= 1ÿçÔ7 "&54632'"32654&ƒ+''+*''*> 1ÿçÔ7 "&54632'"32654&ƒ+''+*''*> 1ÿêÔ7 "&54632'"32654&ƒ,&&,+&&+; 1ÿêÔ7 "&54632'"32654&ƒ,&'+*'&+: 1ÿêÔ7 "&54632'"32654&ƒ,&'+*'&+: 1ÿçÔD "&54632'"32654&ƒ*((*)(()J  1ÿçÔC "&54632'"32654&‚*''*+'(*J1ÿçÔD "&54632'"32654&ƒ*((*)(()J  .ÿçÑE "&54632'"32654&€*('+)(()L1ÿçÔ? "&54632'"32654&ƒ+''+*''*E 1ÿçÔ@ "&54632'"32654&ƒ+''+)('*F 1ÿçÔ@ "&54632'"32654&ƒ+''+*''*F )ÿçáB7#&''67#5ÖGK ?@ I GB(!(%ÿçÞB7#&''67#5ÓGJ ?@ I GB) !(%ÿçÞB7#&''67#5ÓGJ ?@ I GB) !((ÿçØ?7#&''67#5ÎCE << C C?& !  &*ÿçâ<7#&''67#5×F F @? D G<$   $%ÿçÞ<7#&''67#5ÔG F ?A E F<$   $%ÿçÞ<7#&''67#5ÔG F ?A E F<$   $)ÿç×67#&''67#5ÎD A ;: ? D6  *ÿçâ37#&''67#5ÖG H A@ D F3  &ÿçÞ67#&''67#5ÓF E ?? CF6  &ÿçÞ27#&''67#5ÓG G ?@ E F2   *ÿëâ37#&''67#5ÖGK A? H H3    &ÿêÞ67#&''67#5ÓHN ?@ KG6    &ÿêÞ97#&''67#5ÓHL ?? JG9   )ÿçáB7#&''67#5×HK ?@ I GB(!(&ÿçÞB7#&''67#5ÔHK ?@ IFB)  (&ÿçÞB7#&''67#5ÔHK ?@ IFB)  ($ÿçÜB7#&''67#5ÒHK @@ H GB)   )*ÿçâ?7#&''67#5ÖF I ?? D F?&   &&ÿçÞB7#&''67#5ÓGK ?@ IGB)  (&ÿçÞB7#&''67#5ÓGK ?@ IGB)  ()ÿçáR7#5#&''67#5©GtF F ?? E GR   %ÿçÞR7#5#&''67#5¥FuH H ?A G FR   %ÿçÞR7#5#&''67#5¥FuH H ?A G FR   %ÿçÚK7#5#&''67#5£GuF F => D FK   *ÿçâM7#5#&''67#5©GtF F @@ D GM  &ÿçÞM7#5#&''67#5¥FtG G ?? CFM  &ÿíÞI7#5#&''67#5¥FtH J ?> G FI  #ÿçÛE7#5#&''67#5£GuG D ?? CEE  *ÿçáD7#5#&''67#5©GtGJ @@ G GD   &ÿçÞD7#5#&''67#5¥FtHK @? H FD   &ÿçÞA7#5#&''67#5¥FtF D @? AEA  *ÿëáD7#5#&''67#5©GtF F @? C FD  &ÿëÞE7#5#&''67#5¥FtG F ?? C EE  &ÿëÞB7#5#&''67#5¥FtG G @@ E FB  *ÿçâY7#5#&''67#5©GtG I @> E FY#  &ÿçÞY7#5#&''67#5¥FtG I ?? F FY#  &ÿçÞZ7#5#&''67#5«S{G I ?? F FZ$  $ÿçÛW7#5#&''67#5£GuG G ?? E FW!  )ÿçâR7#5#&''67#5©GtGL ?@ I GR!   %ÿçßR7#5#&''67#5¥FuHL @A J GR!   %ÿçßR7#5#&''67#5¥FuHL @A J GR!   -ÿëÐF 73#5'75#.¢ŒŽF[$ -ÿëÐF 73#5'75#.¢ŒŽF[$ -ÿëÐF 73#5'75#.¢ŒŽF[$ -ÿëÎF 73#5'75#. ŠŒF[$ -ÿëÐC 73#5'75#.¢ŒŽCX! -ÿëÐA 73#5'75#.¢ŒŽAV! -ÿëÐ? 73#5'75#.¢ŒŽ?T! -ÿëÎ< 73#5'75#. ŠŒ 7#3#3#5Ї‡‡Œ > Q5ÿíÕ> 7#3#3#5Ї‡‡Œ > Q5ÿíÕ> 7#3#3#5Ї‡‡Œ > Q5ÿìÕM 7#3#3#5Ї‡‡Œ Ma5ÿìÕM 7#3#3#5Ї‡‡Œ Ma1ÿìÕM 7#3#3#5Ћ‹‹¤Ma1ÿìÑO 7#3#3#5͈ˆˆŒ Oc5ÿìÕK 7#3#3#5Ї‡‡Œ K_5ÿìÕK 7#3#3#5Ї‡‡Œ K_5ÿìÕD 7#3#3#5Ї‡‡Œ DX.ÿíØG7#5#53'3373ÒŸ¥ª)/GG----.ÿíØG7#5#53'3373ÒŸ¥ª)/GG----.ÿíØG7#5#53'3373ÒŸ¥ª)/GG----+ÿíÕ@7#5#53'3373Ïž¤ª)1@@((((.ÿíØ<7#5#53'3373ÒŸ¥ª)1<<####.ÿíØ<7#5#53'3373ÒŸ¥ª)1<=$$$$.ÿíØ<7#5#53'3373ÒŸ¥ª)1<=$$$$+ÿíÕ87#5#53'3373Ïž¤ª)189!!!!.ÿíØ67#5#53'3373ÒŸ¥ª)167 .ÿíØ67#5#53'3373ÒŸ¥ª)167 .ÿíØ67#5#53'3373ÒŸ¥ª)167 .ÿðØ:7#5#53'3373ÒŸ¥ª)1:8!!!!.ÿðØ:7#5#53'3373ÒŸ¥ª)1:8!!!!.ÿðØ67#5#53'3373ÒŸ¥ª)164.ÿíØG7#5#53'3373ÒŸ¥ª)/GG----.ÿíØG7#5#53'3373ÒŸ¥ª)/GG----.ÿíØG7#5#53'3373ÒŸ¥ª)/GG----+ÿíÕF7#5#53'3373Ïž¤ª)/FF----.ÿíØ@7#5#53'3373ÒŸ¥ª)0@@&&&&.ÿíØ@7#5#53'3373ÒŸ¥ª)0@@&&&&.ÿíØ@7#5#53'3373ÒŸ¥ª)0@@&&&&0ÿçÜT7#5#5"&54632'"32654&«K|¬V##""TS  *+ÿçÖT7#5#5"&54632'"32654&¦K{«U"## "TS  *0ÿçÜT7#5#5"&54632'"32654&«K|¬V##""TS  *+ÿçÖP7#5#5"&54632'"32654&¦K{«U" !!!#PO  '0ÿçÜM7#5#5"&54632'"32654&«K|¬V#!!#"MM  &+ÿçÖM7#5#5"&54632'"32654&¦K{«U" !!!#MM  &0ÿçÜK7#5#5"&54632'"32654&«K|¬V##""KK  $*ÿçÖD7#5#5"&54632'"32654&¥K|¬V#!!! "DF  0ÿçÜ@7#5#5"&54632'"32654&«K|¬V#!!! "@C +ÿçÖ@7#5#5"&54632'"32654&¦K{«V$!! !"@C 0ÿçÜ?7#5#5"&54632'"32654&«K|¬V#!!! "?C 0ÿçÜD7#5#5"&54632'"32654&«K|¬V#!!! "DF  +ÿç×G7#5#5"&54632'"32654&¦K|¬V!!$"#GH  "0ÿçÜD7#5#5"&54632'"32654&«K|¬V#!!#"DE   0ÿçÜY7#5#5"&54632'"32654&«K|¬V##" !YY  .+ÿçÖY7#5#5"&54632'"32654&¦K{«U""# "YX  .0ÿçÜY7#5#5"&54632'"32654&«K|¬V##" !YX  .*ÿçÖW7#5#5"&54632'"32654&¥K|¬V" !##WT  +0ÿçÜP7#5#5"&54632'"32654&«K|¬V##""PO  '*ÿç×P7#5#5"&54632'"32654&¦K|­V"###PO  '0ÿçÜP7#5#5"&54632'"32654&«K|¬V##""PO  ' ÿéõÏ 733327267##"&55#'667#530)+  3&#Ï-–  ‰o6A?ÿékÐ735'673#&'#5'67# '-% "$$  $Œ! 'rd$/FÿéõÐ %+16<73#'633#3##"''327#67#536365#7&'7#7&'nnx  x  mSQ! 8T) Ð :#  S)%   ÿéLÍ 7&'&''6$ % Í  0  1 /,*bÿéëÉ 7#5##535#3533#''67#ëccc ""& ! Éàྫ*"" :2#!3?ÿéõÐ!+7&'367&'#"''3255#3'67#q+' ))W  "  B C!# +/Ð 1 =R ˆC!2 ÿêmÏ7367&''65''64   *  Ï3 ) .B;,! gÿéîÑ"73'67#'63#7#5##535#35#”B3€11111Ñ 1ŽŽŽŽ6$[%SÿéõÏ73#3#5##535#535'635#Ý 8GG8\8GG1C.\\Ï*&dd&&¶1]ÿéôÑ 7&''63##5##535#¡& $$ / NNaJJJÑ ''*(,bb>,DÿéøÏEK73&'33#67327#"''67&'#7#"''3255'675#535'67&'’ '75     )    ))%b Æ% )* '# ((& 5  (/   ÿéò|$7#533&''67&''667#'7#@œ 2 * -"!2 -%.a$ Tj    6  $KÿéîÐ*.273733#537#35#3#3#3##5#'7#535#735#35[(B£,"0;47‰277??E MB__ 4¾")3C\ïË#7#53#3#5##5##533#73#3#73#vZÈZeQQeG==e==f==g==º1 <<0 aïË#73#3#5##5##535#3#73#3#73#ÈZeQQeZ==e==f==e==Ë1 990, fïË#73#3#5##5##535#3#73#3#73#ÈZeQQeZ==d??e==e>>Ë2!55 1) €ïÌ#73#3#5##5##535#3#'3#3#'3#È[fRRfZx<>f==h??Ì+##+  kïÌ#73#3#5##5##535#3#73#3#'3#ÈZeQQeZ==d??>>e==Ì .//- ' ÿéîO 73#&'#5#Þg.) ''cO  4TKÿóóÏ7&'3#3#3#535#535#—  4E<60/365: ((<*Ñ$# ?!$$?BDÿìõÐ=CI73673#3#&'#3#"''3267#3267#"&55'67#537#7&'7'6V7DJ[ 3 U   6!# 0/ "/ 1  d¦  *8D 8   FÿéóÏ73#3#3##5#'6ttXHHKK Ï''=°& ÿéŽÏ#'+73533#3#3##5#535#535#35#33535#335577007777//5.J.¼^((^:< ÿé{Ï#'+73533#3#3##5#535#535#35#33535#335,--(())..)),(=(¼_((_:; ÿépÏ#'+73533#3#3##5#535#535#35#33535#335'(($$&&(($$'$5$»]((]:<mÿèóÐ(,0573#3#3&''67&''67#5'635#35#67S[\>G      <<<<  Ð L   F1 *?  ÿéjÏ#)/7676767&'7&''67'67&'''674'$  !$4  /…9"  /+p   ÿéoÏ#)/7676767&'7&''67'67&'''674'$  %)9  /…9"  /+r  zÿéôÏ$73#327#"&'#67'56&'Ú&%  # 9Ï%/5>[  ÃO"7cÿèõÌ5:@7'23673#3#3&''67&''67#537#53&'67'&'å 2I<# )  FOSH     # ' !  Ì     %* m z  ÿéfÏ#'+73533#3#3##5#535#535#35#33535#335"$$!!""## " / »]((]:<KÿèõÆ+BX73&''67&'767#73&''67&'767#3&''67&'7567#73&''67&'767#XG     2QB     -K=    'FH      2Æ             X!       ,!    oÿéöÇ,CY73&''67&'767#73&''67&'767#3&''67&'7665#73&''67&'767#u;    'A9     $@7    "==    'Ç"     #       X%      /   [ÿéöÇ+@V73''67&'767#73&''67'767#3&''67&'767#73&''67&'767#hA    ,H=     (C8   "?B      ,Ç"    $     X"       0   `ÿéöÇ*@V73&''67&'767#73&''67'767#3&''67&'767#73&''67&'767#l?     *F<   'A7    !>@     *Ç      #     X       /   ÿè©Æ(>S73''67&'767#73&''67&'767#67#53&''67&''3&''67&'767#C    .K@    + .D    B:   $Æ     "     p %    "        ÿèŽÆ'>T73''67'767#73''67&'767#4367#53''67&''3&''67&'767#;    &C5      #9    84    Æ!     !    p  %   "#        ÿèÆ)@V73''67'767#73&''677&'767#4367#53''67&''3&''67&'767#;    &C5     #9    84    Æ!     $    p  %   "#        ÿéƒÇ)<Q73&''67&'767#73''67&'767#67#53''67''3''67&'767#5     !=/     2    3/     Ç#     #   q $   "&    KÿèõÆ+BX73&''67&'767#73&''67&'767#3&''67&'7567#73&''67&'767#XG     2QB     -K=    'FH      2Æ           X!       ,!    _ÿéòÏAE733533##3#"''32655''67'75##5''67&'75##535#5#53#3~0"=     +   +>"B00Ï"s   :s)  :yŠ"ÿóZ¾ 7#5##535#35#35#Z!!!!!!!¾ÅË7%\%\%ÿóW¾ 7#5##535#35#35#W¾À Ë7%\%\%]ÿèóÇ #)7#5##53#735#35#3#53&''67&'íb ppJJJJ);> ' DÇ  'S11) )   oÿèóÇ #)7#5##53#735#35#3#53&''67&'ïY ggAAAA&6‚7  >Ç  'S11) )   ÿókÏ!74''675#53533#67&'75##5#^ % !! / 5`--`1 )<<<<uÿîóÌ73#3#535635#Ý0&W~1++Ì .k¼ÀkÿóuÏ 7''675#53533#67&'75##5#e $*$$## / 5`--`1 )<<<<ÿópÏ 73533#67&'7''675#75##5#$## "'$H¢--`/  4<<<< lòÑ#73#&'#'673#&'#'67?&  tH+    Ñ  '0  ( xòÑ$73#&'#'673#&'#'67>%   sG+    Ñ   #)    # “òÑ 73#&'#'673#&'#'67:   tE(  Ñ      “òÑ73#&'#'673#&'#'67:   tE(  Ñ      ÿé[Ï 7#5'6G  +ϧ‡2 ÿéPÏ 7#5'6<  "Ï©ƒ 2 ÿéGÏ 7#5'64  Ï©€2 ÿéSÏ 7#5'6?  %ϲŒ2 ÿéˆÏ#'+/73533533##3#3##5#535#535#5#5#35#335 +..3344..S+/»$<((<$OyÿéîÏ73533#"''3267#'67#‹6  "0*£,,…wy.*n„ÿéïÏ73533#"''3267#'67#’0  +&£,,†!px/+mŒÿéïÏ73533#"''3267#'67#™,   '"£,,* qy.*nŸÿéïÏ73533#"''3267#'655#¤'  ¢--…u&O2 /E&ÿéæ773#"''3267#'67#536qc WN@MW7 / ' ÿéæ?73#"''3267#'67#53qd  WMBMV?2 *   ÿîóH 73#53535#35#35#Ûæ##6""5##HGGG55555 ÿîóD 73#53535#35#35#Ûæ##6""5##DCCC11111 ÿîó9 73#53535#35#35#Ûæ##6""5##9888&&&&& ÿîó- 73#53535#35#35#Ûæ##6""5##-,,, ÿîó' 73#53535#35#35#Ûæ##6""5##'&&& ÿîóM 73533#75##5##5# ¶æº#"#LL999999ÿêYÆ75#53#3#"''3267#7F3F.1   3&K(L3Lÿê‰Æ75#53#3#"''3267#7pUiNZ ] Ž%K(L1NÿêmÆ75#53#3#"''3267#7YDX<? B Ž%K(L2NÿêaÆ75#53#3#"''3267#7M:N47   9 Ž%K(L3NÿêMÅ75#53#3#"''3267#76%8"(   )&J(I 4L)ÿèád73#3#"''3267#735#,¨’  ¤”d2%2)ÿèág73#3#"''3267#735#,¨’  ¤”g3&3)ÿéáV73#3##'3267#735#,¨’ ,¤”V.  - )ÿèáP73#3#"''3267#735#+©’  ¤•P+  , kÿéî¾73#"''32767#'665#'6ƒk   ##$¾¥/,reEA`!$  ÿéuÏ#'+/73533533##3#3##5#535#535#5#5#35#335 !''****&&D!(»$;((;$O ÿé}Ï#'+/73533533##3#3##5#535#535#5#5#35#335 %**....))J%*»$<((<$OmÿéöÄ '73&'#"''3267&''67&'765#65#…`   )& * Ä^6 /  1)$: !T6<3 ÿélÏ#'+/73533533##3#3##5#535#535#5#5#35#335 $$&&&&##= %»$;((;$O ÿé^Ï#'+/73533533##3#3##5#535#535#5#5#35#335    4  »$;));$N ÿèóa#'+/73533533##3#3##5#535#535#5#5#35#335,\,,8LLjjhhLL6,›\::N9[ !  ! *  ÿèó\#'+/73533533##3#3##5#535#535#5#5#35#335,\,,8LLjjhhLL6,›\::N9U   ' [óÉ (-73#3#735##"''3255#3##5#53&'##57# ææªª„„©  " 99!z <É  '   )5  ÿèók#'+/73533533##3#3##5#535#535#5#5#35#335,],,8LLjjhhLL6,›]::N9c #  #  - ÿî>È 7&''6$   È f 1-+DÿéõÏ48<B73533#33##3#&''67&'7#537#535#535#535#353567O?IIAMg  $< ++4(6EE66?R...J. ¿     B   ÿî7È 7&''6   È f 1-+ ÿébÏ73533#67#"''3255'75#!    &!¥**2F  8  ; ÿé\Ï73533#7#"''32655'75#    !¥**1 F 9  9 ÿé¢Ð  $,07&''6'66553&'73'#335#5##535#X"  & 5  0(JJJJ888Ðu." !@ J:-D D'vÿèôÏ73#&''67&''667#™N   1ÏC*# 4+#6 ÿézÆ 73#735#35#35#'67&'ZZ222222B Æ£sMM6 mÿèõÏ73#&''67&''667#‘U!" 5ÏC)" 1)$6ÿîgÁ73#67'535#U< ) <@ÁbY  z< ÿéÏ,73533#3#3#3#"''3267#'67#535#535#,++''+<9   ( !.'',» B )1 $C¢ÿèõÐ73#&''67&''667#»/     Ð&;($ 2C& ÿémÑ!'-73#"''255#'655#5353635#&'4'># . ..  Ñ ° N79 L\:H ÿétÑ!'-73#"''255#'655#5353635#&'4'C' 444 Ñ ° N79 L\:H~ÿì÷Í "733#67'73673267#"&5~  8    ÍJk  ×Q_ #  ÿémÑ!%+17#"''255'655'7536735#7&'4'k  &   &lY S5!9 O G8<5 HiÿéôÏ973&''67&''667#3533#3#3##5#535#535#”C  "  * 8 (0..**77::**0Ï      > ÿéaÑ!'-73#"''255#'655#5353635#&'4'6  &   &&  Ñ ± O5!9 L[;HrÿéõÇ #73#3#537#35#35#35#&'''6yy2/h$2BBBBBB9 ! Ljˆ<=>,    ÿðsÍ7335#53533#353#7'5#**))$ $ŠCYYCU. 9 ÿê‚Î8767#"''32654''67&''67&''67&'767&''6d    * /"% &    % *Î  =#      zÿêñÏ73533##"''32655'67#†=   ' /6ž11† e6'07 ÿêzÎ677#"''32654''674''67&''67&'77&''6^    ' + " $     'Î >#      ÿêiÎ5767#"''32655'67''67&''7&'767&''6O     %    !Î  !> !     [ÿé÷Ï 7#5'673533#&'#5'67#‡ '&"    "Ï¯Š ,11D1zu,-< ÿêoÎ7767#"''32654''67&''67''67&'767&''6T    ! (     #Î  !=!     nÿéóÎ $).73#3533##5#5367#536365#335367#335¦99 $$M %$1M/!Î c 33 c 5 A  yÿëõÄ &73#33267##"&547#67'7#&'ypG=   +8Ä#  t} ¹O€ÿëõÄ &73#3327##"&547#67'47#&'€jC9   &6Ä#  t} ±OÿêõE$73#33267#"&55#67'7#&'ãœo a %3p E %  ?   IëÑ &*.2N73&'73#3&'73#3#3#3#5'65#5#5#'6767767'67'67'67ZdÖm '/****1s .!!!!!P  E%  Å   5    7%   ÿêõQ%73#33267#"&55#67'7#&'ãœo ` %3p Q-   "% K%  SêÉ %+1773#735#35#35#73#735#35#35#'67&'''6'&'[[777777Z__;;;;;;e Ÿ  = ÉU= ! ! BFLR733#5367#'635#35#35#'3533#3#&'#5'67#535#35#335'67&'·$I  %%%%%%j    , 3  Ð  ||  I88~EML"$EK'''q   ÿéNÏ 7#53&''69 ,Ž¥æ)  '!"@ÿéöÐ%)-26Jd733#"''255##5#'655367#'635#33535#73573#"''3267#'67#3533#3##5#535#'6h %  , N      Д %!!#?A UJ o7 !- "F  22  ÿé`Ï 7#53&''6A 7•¬æ#' " ÿéhÏ 73&'#''64   Ï1´' !LÿéöÏ 73655#535333#&''67#75#W:((7D1015 6r#])))I2 54!$.6)>ÿîõÆ#'73#735#'67&3533#75##5##5#Z€€XX)    g•·“Æb@%  †OO======VÿìöÎ&733#7'753773673267#"&5‚%- 0    ÎId ¢ŸÅO  e GÿêöÐ *D7&''67&''6''6#5'6733#33#"&''673¢   6  Y    W&&   Ð   %2   %0 ' x["/ $EÿéõK73&''67&''667#I€&- :/-; 2#  LaK    eìÐ7'63673#5##53'7&'ÛWli@ Y $¯( O  Ð   (+ ‚Ý» 73#735#35#‚[[3333»X2w2+kÕÇ 73#735#35#+ªª‚‚‚‚Ç\77+_ÕÆ 73#735#35#+ªª‚‚‚‚Æg<A ÿéòU736733#&''67#al_G KN DXC $36 &ÿéðY73#3##5#535# ÃXeeeeVY00 ÿé÷Ñ$/7&'3673&'&'#"''3255#3'67#C E @>=$ 20   Ñ  ( 3,-E &#7“æ» 73#735#35#“SS++++»X2w2 ÿé|Ð #)/5;733#5'667#35#33535#335&'''67&''4'/(T   0  GDÐ cX *C/# qÿé÷Ò%+17767'7&''6373#&'#'67#'67'67'6¥   (,)\ÿçõÉ 273#735#35#3#3#"''32765#'67#'67#'67#qooIIII$•[\  / 4* # %ÉP0..F (/ +" DîÏ (.7#5##53&'73673#&''67&'67#367æ¥_|Kx0 #& -&)< 1! 9S- ¼   (      -ñÒ!%73#3#53&'#53&'367#3#735#35#ƒS! >á; W ;R3©©Ò   6 *K,*KÿèóÐ#(-73#3##5#5367#53635#335367#3355#‘E%77^)+''9&_%9+$ Ð l44 l 9I  L eÿèôÐ#(-73#3##5#53665#5335#335367#3355#ž<,,O$&2 R1&!  Ð l44 l.J  L VÿéöÐ>BFL7&'3533#676767327#"''67&''67''67'#3#735#'6Õ  gI:9       J33&&)Ð  ''  +  3T2K  ÿçð¥6<73#&'#5'67#535'23673#&''67&'67#367Õ &.dK. 6% < 4IdLXjI|) " 1)C 3" :R 8 ¥ a    ‹ÿéêÀ7##53#"''325Ö7_  ­Ä×  lÿéìÉ 7#5##535#3533#''67#ìYYY!  Éàྫ*"" :1"!3ÿéÈ 7#5##535#3533#''67#HHH  Èßß½¬+"" 8+ $3ZÿéëÉ 7#5##535#3533#''67#ëiii"$$ ! $"Éàྫ*"" 3#"2sÿéìÉ 7#5##535#3533#&''65#ìSSS Éàྫ*"" "2ÿéâ 7#5##535#3533#&''67#â››› 968!  . 6¦ ¦‰w 'FØÆ73#735#36533#&''67#'±±‹‹ 0/2  $ *,Æ€^     ÿìô' %7&''&'''6733276767#"&5Ù  CF& :)'      @èy"7&''332667#"&57&'''6{ %4~  … y        ÿéiÏ 73&'#''64   Ï& ©´' !¦ñÏ73533533##5##5#;A<<A;à  ŸñÏ73533533##5##5#;A<<A;Á ¡ñÏ73533533##5##5#;A<<A;Á •ñÏ73533533##5##5#;A<<A;º¦ñÏ73533533##5##5#;A<<A;à  ™ñÏ73533533##5##5#;A<<A;¿ ÿèô£5DL7&'3&'733#537#5'6'#"''32654'7##53#&''67#&'7#6¢% ""' }K N 0' $J$ /5&e, £   1 1¡³u   OÿéìÇ"&73#"''3255##53535#335#3#735#Ö  uIN55!!JJ%%ÇUo  Yx‰UUB,B> BáÈ/733#'3267#7#3&''67&'767#'67#!¦ #  ( [J   =$ , !È.0 %.     4H ÿèîÇ 7#'655î½ÇSC6 5?` ÿèõ 7'67&'7&''&'*~  K  _       ÿëôÉ7&'333#"&''675#2 %>F P,(É  D`   PI íÏ*.73533#3#535#3#67&'#5'67#735#[9::EŸF9‚9  #' '1\\Á/2  &% bÿóôÂ73#3#3#535#535#i†:66?’@559ÂKKKK ÿéuÏ73533#67#5'675#'6!Ä 11: RI  C +mÿóõÏ73533#3#535#y/009ˆ;/†IImm ÿéaÏ73533#67#5'675#'6    Ä118 TI  C ([ÿìðÏ!73#"''32767#'67#'67#'6‡`   M F< 5 Ï‘0.Xm83bO)%D!KÿèõÏ$73&''67&'#'6655667Ý 1Aq# #   L  Ï $=*"2@66D:N  %UÿéòÁ73##"''3255###535#U   uX9&&Á«  ¦'XfF5gÿïóÏ73533#3#535#3533#3#535#t...8ƒ7..//<Œ<.­""""a##$$]ÿïóÏ73533#3#535#3533#3#535#k222=<2333A–B3­""""a##$$JÿïóÏ73533#3#535#3533#3#535#Y:::FžD::::J©K:­""""a##$$EÿïõÏ73533#3#535#3533#3#535#U===I¥H==<íÏ73533#3#535#3533#3#535#$QQQbØaQUVVbÚdU¼< ÿëõ6 #7&'7&''3326767#"&5''6zay "4%6    * . ":ÿéóÆ"733&''67&''667#67#'7#co, " "." $3Æ2?'#B-Ag43!)3WÿéóÏ37;?C73533533##5##5#3#3#3#3##5#535#535#535#5##5#35#335[$($$($‹=DDCC;e,½ ) ÿénÇ 73#3##"''3255'67#'735#[   & "4Ç=d  K,",5.=|ÿéíÅ73#"''3255#3###535#|q  ]MMH/ÅÁ ©M Z<+UÿéêÅ73#"''3255#3###535#\Ž  zgg]@--ÅÁ ©M Z<+ ÿéõÏ"',0A73#32767#"&55#'67#5337#335367#33567&'7''6<0 "@ N0(%$#)A'   ÏdN  SB* %:d* E 7  ŒóÏ 7#5'753'&'&'ñ:==  f;7  pm`  #  ZÿéôÑ$(-1A73#3265#"&55#'67#536365#35#365#35#67'7''6•@!    : / *//,!#  Ñ gO   UE$;g 7 E 3    f½73#3#67&'7&''67#FFQ !½(9  & 8 ÿéôÏ$(,0@73#32767#"&55#'67#536365#33537#33567'7''68.$@ O2$!" &<%  ÏdN  SB* $;d 5 E2  €ñÏ&,73673#3&''67&'7767#'67#7&'ƒ?B8     * O  ¥     "!<   ]Î 73##5365#35#0"8%%%Î ™­Q11D2UÿèõÏ"7'6655673&''67&'367†C* *9d tB55D:  $>* ),*%\Î 73#5##53635#5#3 & &&&&Î ¤ ­Q1u22bÎ 7#53673#5'35#5#%&*****­ ¤ V1u22ÿðƒÎ73'67#&''6A8/ K* "Î R:q  2YÿéóÐ(76767'67'67#53&'73#&''6} #L /*@ ÿé”§  $73#735#35##"''3255##55#35@QQ0000D   2D22§B( $ (X  %n —ÿéõ§73533#&''67#7&'›#   D  w0D(2-#&F>    TóÐ!7@DH73533#3#535#733#"&55#'6653&''67'767#'6553'35335122*e)1Å $  V     A` dRÇ     ,    ) ÿé÷j!&*.73#'#5##5'67#535'235#3&'#35#35#ÌI`:* ~ +<`EO1,/, K~~~~j <9  5  + $ ZÿéõÇC7#67&'7''5353573#3#33267#"&55'67#'67367#˜*   J    (!Çy7   Ã!!3""E* C  '$'>*UÿéõÇ@7#67'7''535#373#3#33267#"&55'67#'67367#•,   !L    * "Çy8   Ã!!3"g*C )%'>*JÿêõÏ 7#'6553&'7''6767&'óy>,- & °:?: 0D;' )9 07Ï UEEH    4   %  KÿéõË 3773#735#3353353#3#67&'#7'5'67#735#c††&w‘‘ €3  6 #  ZZË:6 3    .     bÿì÷Å )/573#735#33535#335&'33267#"&57&'''6pvv1N1!    T  [  ÅrBK6 : 9 ! ÿéõÏ-7353333267##"&547#&''67&'767#EJ   5 9 6 Dª%%[?   6S*0#",  ÿéóÄ!'73#333277##"&547#67'7#&'Ñp   ]''6)-^  Ä$a&  $Q{ ¸N ÿéåÁ 7#5##535#3#735#å¡¡¡!^^66ÁØس b< ÿéôÑ &7&''63##53#67&'7&''6€26 44,= PiiGÒr25  EMÑ-4.*#/' #  ÿéõÐEK73&533#673267#"''67&'#67#"''3255'675#535'67&'v ?GF   $ A 66%4}  É$&'  %! $'#/(3  ).!  ÿðåÅ 7353#5##535##5#l*O¡¡'R(ÅWWÕÕ²ŸWW ÿèïÐ#(,28>D7367333#"''32765#'67#53&'37#7#&'&'''674';  M     5!J"=C;Pjs   K: Ï//K * )D E=   ÿéóÑ!*7&'367&'#"''3255#3'67#G;5 88i #* 3  UX: 6CÑ &  #1!?W  @$"1ÿçïÏ!73533533#3##5#'67#535#5#3E2288F6 -893ŒE¡....?SS4!'???? ÿéõÏ(7'67#53&'73#67&'2767'< ;Ne ^q#$ Q+ '$ J %1 ,!*1i Z  ÿæïÐ*73#6767'67'67#53&'&''66ƒ ^{) A8 A"&KhY$$0J EMÐ'/! d 0ÿéõÑ"&*/47#67327#"''67&'##53'33'#3353&'#335ÛO    &<> L- DLM;9NB’@=Q>¸r  ,%  €0L  !ÿéóÏ73&''667'367'7&'´7 .! “! #, >ÏP("%$-6C>žœ!$ $! ÿéÇ"&,27#3##"''3255#537#'65535#35#'67&'‚"   &-))))  F  Ç\?  ;\RF3 5@_R:. †ÿéòÇ #73#3#537#35#35#35#&'''6Œd('Z'5555552  Ljˆ==>,   LÿèõÏ"73533#3&''67&'#535#67#UBCC4( (!* ) :BLH ©&&'%    %'l#…ÝÑ7&''67'76Æ 'F'!<,&"Ñ     ÿéð*73673#3533#"''3255##5##5'67#G Œ 9L 87 "8x  6  KK>?ÿéoÏ73533#&'#5'67#%##  ! #¡.. €q")8PÿêõÂ73267#"&55#'655Ï .)µ  §G-9$RQ ÿéeÏ73533#&'#5'67#    ¡.. ~m!+6YÿèöÎ%+73533533#3#535#35#35#35#'67&'h9—&999999 (!H °nnCD/  MÿéõÑ8So736533&533&'73#67327#"''67&'#&''67#767677'7''67'6767677'7&''67'67S!=     '  t  h  H (ag"       *g $ "?ÿèôÐbh73673#3#3#535#535#53&'3&'33#67327#"''67&'#67#"''3255'75#535'67&'u ' $D<         SÿéîÏ$*0673#"''3267#'63533##5'67#7'6'&'&' d  Z !--- %W  ;  L  Ïš/#€ "O::UCB   8 ÿèõÑ73&''67#'6`S $5 7#*N xH 3Ñ389;"v),lÿñóÆ 73#53#3'35€s‡ƒoffR2.Õ.SA..ÿébÏ7#"''3255'675#53533#6_   !! tN  ?3**+Rÿéñ¿73#3#5##5'67#35#f‹L [@  ,++@@¿#!d2C¤M ÿåôÒ $*06<733#5'6367#35#33535#335&'''67&''&'WW/¯ 4X P ;;N:ˆ;;N:  —~$  Ò qe ! 9K7  DÿèïÍ#73533533#3#535#35#&'''6O"+''+¥'"6++75Ÿ....AAAA\! )@ÿéóÏ#'9=A73#&'#5'67#5367'23&'#35##"''32655##53#735#Ó !V* b  "2)<=" >>g   i"JJ))Ï   >(+Q ;Uf.€ÿéÞÏ 736753#5'€$ Ä—“æ@   TòÑ0G73673#"''3267#'67#3733#"''3267#'67#73733#"''3267#'67#!NV  JC4>..  !$  #s..  !" #à  *      )ÿè×R7#"''3255##55#35×  ‰›‰‰RT  i ÿéóË%)-15;A73#3#35335#535#535#533#535635#3#3#735#'67&'@$\#-æ2::..(( " / (i& !&Ë   ““  “†‹  J)`  ÿèYÍ76767677'67'67'67   7 &   6!T,!)1.ÿçõp#'+87&'#7'5'63&'73&'#353567&'‚0< ˆ0> G38&! vvv1  02p  2  O    oôÐ3IMQ73#&''677&''667#'3533533##5##5#3#"''3265#'63#735#¢@    &”S I  77Ð    ,    ! ÿéíi!%+17=7#3#3#3#"''3277#55#5#5#'67&'7&''&'âSKKKK^   ´VCCCCC  8a    i (S        ÿéóÏ39?E73533533#3#3#3#&'#5'67#535#535#53&'#33677'6'&' O O4 <`VVgR", 6"#5,$QgWWa: 4L 0 +  ˜Ÿ0000  12   B  GÿïóÏ/37;73533533##3#3#3#3#535#535#535#535#5#5#35#335P"1""%@@EE>>M¬K<›ñÏ73533533##5##5#<A<<A<½ÿéâ” !'7#5##535#3##5'67#535'6&'✜œ0:: /;,;$  ”« «}7, 9 ^ÿèöÑ 7'67&'&'3'67#¥-!& !  -y %d­)%1 &.:* òÑ#73#&'#'673#&'#'67=#  uG*    Ñ  %    òÑ!73#&'#'673#&'#'67=#  uG*  Ñ  %   ÿòîÇ7#3#5&''67&'76æ¸ÀÔ›/ "! ǯÕ! #% [ðÍ 73533##5#3533533##5##5#%RRRR,_,,_,¸# ÿèôH73533#&'#5'67#edT#7 ;$$9 5"R8 ":;! ÿèôh73533#&'#5'67#edX%9 9&"; 8"UP&.MN*% ÿéóÌ73533#&'#5'67#bc\&= 9%"8?!Z™33J#&I‡‡E*.D#iÞÑ7&''67&'76Ç" ',4-%!%-&"Ñ   ÿéÅ73#3##5#'655#535#5#y"$ M"ÅLjj?(#6LLLLMÿèòÐ!%)-E73&'73#3#3#3##5'67565#5#5#33#"''3267#7#'67#w"25////6s 0)))))y%  )+5."Ð     <  )   *6* ÿééÍ06<767&'7&'#"''3255'67'67'67676'67&'©7   !Ï@(# . )!/ ÿèóO73533#&'#5'67#fdT"6 :$#95"S;'=?' ÿèóÐ7'673'673&''>T% QOH\ *.—+%F.  H"8> %+ ÿéóÑ48<@DH73673#33##&'#5##5'67#535#535#535#53'5#3533535335R ' BL;0* *#& '/9KK77FAK##)_#)Ñ  %%!$73#3#3&'73#3#3#3##5'67#537#'65#5#35#yc?T[#-2,,,,5q #+  :)))))Ð     W  ) s""‡dóÈ73&''67&'#367‹\   È    ÿéíÏ'73&'73#676767&'7&''67'67#a  `t#*;BF8 K[,)$(%O¬ ,M) "' (,QÿéëÅ 7#5##535#3533#3#535#35#ëttt ((( R (..ÅÜ Ü½«&EEP$ÿçëa 73#735#35#35#'67&'*¬¬„„„„„„ 8 2X))('aX> " "    ÿêcÃ7#"''3255'67567#53[  >Sr S H*Yÿè÷Ï#7&&'67&'7''5'6556Ô    8ÏIR%$ZP¨ $²+_; 7\8 ÿèfÎ 73&'7367&'#5'67#   #1¯   ao 6`ÿéõÐ/73673#&'#'67#3#3#3##'32667#7#d1D)$&'<<&Ž\S S µ  #$$$ &IÿèôÐ 37&''6'&''633#33#"&''673Á  (D  "+66*/Ð(0  *7 , #D  ÿéKÏ7'6#5'67   Ï (# {e  ÿéMÏ7'6#5'67    "Ï ( }b $ÿé‹Ï $(733#"''3255##537'6'&'3#735#D2  M/6 M  #33ÏI„  n‹<  [E'aÿéìÐ $(733#"''3255##537'6'&'3#735#œ:  a;>Y  "??ÐJ…  n‹<  XG%JÿéöÏ#)73533#3#&'#5'67#535#'6'&'`8;;D:% (' ):C8p  R  ²>..VU-+>   ­ÿèðÀ7#"''3255##5ð   À  ‹ÆØ@ÿõ¤Î$73#3#3#67'675375#535#'6d3 +6 ##  Î%#* QOa%  ÿélÏ 73&'73#3#3##5##535# %%` MMMMN&&&² V V6$qÿéóÆ!73#33#537#537#35##5##535#vt;7‚%)/(BDDDÆ5#O#HWW7% ÿéñÏ#)733#3267#"&55#'67#537'6'&'uaI  "! F@ B`U„Ï]X _U!HK  VÿòõÅ 73#3#735#5#3#`ŽŽ uuMMMM*ŸŸÅuC Q.NÿèöÏ#+:>73533533##5##5#3#3#5#53&'3'66733267#"&5'3#V#.%%.#S;obuA'R )º ,Y(# "=  BW ÿçñÇ#'+/73#3#&''67&'767#535#35#33535#335ÖbQR -JM3:3 OP`$<7#"''3255##535#35#Ö  ……………>C V "  HòÑ !73#53&'3#735##5##53#735#gØ]A©©²¼#  vvÑ " " ÿéœÇ&,7#"''3255#353#5335##536735#&'œ  ) J)4 b  ÇÆ  pKr  Ÿ ++ 8   ÿñïÏ73673#3#3#535#'67#?„ ‘J[Ï`=1:ª!GG5;AÿèõÐ#'-373533#3#3#535#535#3#735#35#35#'67&'MCFF>>M¯M;;Ceeeeee . &K"! Ä     DbE ' '     +~ÕÈ 73#735#35#+ªªƒƒƒƒÈJ- )  ÿéóp373#7#5'75#35#35#75#767#53&''67&' æy (/(222222a BX   pJT14    ÿèóf767&''66''6y"2 M 5* 73; f    2( 4 JÿéöÏ(-1BHN73'67#&'&''673&'67#3#3##"''3255#'67&'n# *   S2   8#>DD#‹<   ; b Ï !+      K) 86  3 ÿé[Æ7#"''32654'7##5[  "Æ: <ËÝ’ÿéðÃ7#"''32654'7##5ï   $ 2Ã;"" ! >ÇÚ ÿézÏ7#53533#&'#5'69(+'' %Ž..xt% .aÿéòÏ73533##"''3255'67#> $- 3*=¢--Š  b<#'LrÿéóÏ#'73533533#3#535#35##5##535#35#y * E@@@@@±  Crr'= ÿõóº 73#53535#35#35#Þæ$$8$$8$$º²²²ŸŸŸŸŸ ÿïôÐ +7&''63#3#3673#53&'735#535#/8 44-< Ox4UU  6Ö4  WW/Ð%+*#""< <" ÿébÏ73533#&'#5'67#   ¡.. |l!*7cÿéôÒ87&'7767327#"''67&''7&''7''753  1;55;:   6 2;:+,*,Ò      &7#  &$ ÿçô‡!+/37'#67&'7#7'5'63'73&'3535ƒ 39 <  12 E0! F5=+$(www‡  =    [    LÿôôÏ73533#3#535#LJJJCšCJ…JJkkMÿèóÏ73533#&''67&'7655#UV220 -( ) VŸ00&!$%€ÿêóÏ73533##"''3255#&'€F F  ›44‚  } WÿéòÐ73533##"''32655#&'Wb$$  b ›55… €# "LÿéëÁ73#"''32767#'667#'6[   , / (: Á˜"XM D^/! ' bõÐ 7&''6{0< 75,; NÐ/52+GÿîòÍ-7#"''3267#5327667##"&55'753753ã  )$'  0"$žW/ ^Y a " d:5 D?>ÿìçt7#3267#"&553#"''326§UG P-}  aZ q9BÿìØp736732767#"&5B;$ -?2 ;*p#  2  PÿéôÆ733##"''3255#53567#b€GG  KKhÆ\ Y! ÿéôÏ#733#&''67&'767#5335#35#vUV0OQ76.VVBBBW@@Ï['  "[H555 ÿèoÏ73&''67&'67#5367#7)     Ï)P* !%i 6# ÿèóÏ!73673#&''67&'67#367 L ƒ. )$2*= <"!'D\ žB!$  ##$  8 ÿèwÏ73&''67&'67#53667#;+   $ ÏN+  "$€!6# sÿéóÄ73#3##5#535#wv177662ÄIllI ÿéÄ73#3##5#535#r08866.ÄIllI ÿèhÏ73&''67&'67#53667#/&    ÏG* !! !%y4#Vÿè÷Ï,0673533#3#3##'32767#&''67#735#535#35655#b7DD99E 35 4*3 3///7K'< ½6 '.(( )8$9  ÿè^Ï73&''67&'67#53667#,#   Ï)N-  "$~!4# ]ÿèïÇ7=C73#3#"''3267'677#735#73#3#"''3267'677#735#&'7&'aA%+    / #.KA%+    / #.B V Ç:h!  .<:h!  .<_   NÿêïÆ#)/573#"''3255#'3#"''3255#&''&''6''6«D  0TE  1d F n E ÆÅ ¯Æ °   , )% $' )& $ ÿéiÏ73533#7#"''3255'675# %# !%¥**4 E  8 ;aÿéôÏ#)767327#"&''677&''7&537&'ìB &  /1š (!5  ($?8  ÿéñÆ733##"''3255#53567#(³$,ff gg)!˜ÆX T ÿê…Æ7367#"''3255'67567#k   0TÆU M '¡ÿíóÏ733267#"&5¡  ÏÈ  ÿíòÏ733267#"&5  . ÏÆ   —ÿíòÏ733267#"&5—   ) ÏÆ    ÿéó[7357#533##"''3255# m-s–!ee  m-    ÿéód7357#533##"''3255# l.s–!ee  l,  PïÇ73567#533##"''3255#i{›"bb   iŽ  bÿéôÐ(76767'67'67#53&'73#&''6… H *&: 8K A - Vm J "% ÿèëÉ 7'66553'#36 É¡¡+F6 1%`:),ÿèò|-373#6732767#"&55#3'67&''67#67#,Ä;!  0+   1@  |&  + k $  $J  ÿèëÉ 7'66553'#36 É¡¡ˆ$F6 1%`A03ÿéöƒ!7'63533#&'#5'67#7&'Ï‚HOC4., /<  ƒ $55!FE%"F :ÿéñ‰#(73673#3##5#'67#53655#53&'5#l ,&(//1 0 $ (,(% S.‰ !<ÿêé… 73#735#35#35#&'''6KjjjjjjQ.# …oN..$    ÿèëÉ 7'66553'#36 É¡¡’.F6 1%`7&%ÿéóÊ"73#327#"&'#67'56&5Ð$(QN $ 0 M&#/ dGÊ$+0/;8V  ½M 5ÿñ^¿73#3#5##5'67#35# Q# +  $¿#r b$;’D^ÿèóÐ+E73673#53&'4''67'677767&'7''67'677767'ˆ # Ž  +   Z   Ъ %.+)F$ %/+)B(ÿéòÅ"73#3#"''3255##5##5##5367#äjh   (('Gcʼn  q‚‚‚‚£ZÿéöÏ7=AEK73&'73#3&'33#673267#"''67#5367&'#53&'#367#35#35#7&'`)(    =F a  j  ²  #.Q/" &$\T ,! Y=‹íÑ 73#5##53&'‚`²dÑ .. ÿïò%73#67&'7&'3#3#535#535'67#3š\.13TThägTT"$%    ÿè÷™ )/73'67'677367&'3267#"&5'&'YQ C%*"2#'   +b™`1 #  N0   1    ÿíõ  "(.>DTZ`f7&'7&''332767#"&5''6&'7&'33265#"&57&'733265#"&57&'''67'6z e  v ,"'  …  „  D  @  E  À  x      ( - ' 9   ?9  ?  ÿìñ¤0733#3#5353333#3#53533'33#7'7537xXXeÞ',=$$*zp##"/9¤((*DDT B? ÿé‡Ð %+17&''63#3##"''3255#535#'67&'G * F++  -- V  Ð&J  G:"  ƒÿéøÏ 7#5'753'&'&'ö<?>  OLH Іs. ÿèóÐ$(,73533#3673#3#5##5'67#535#35#35##G44*! =Z0mq 7-c]G.qqqqµs arC ÿêóÐ$73#3##"''32655#535#535'2Ø &/UUff ll[[*0`Ð )$5 0$&Lÿí°Ï(73533#3#535#3#735#7677'7&'U  H GG##  +6 ·::0 ÿèêV!73#3#"''32655##5##5##537#Ôhe   &&%IXV7 "5555:J#ÿéÝe $(733673#"''3255##53&'7335#35#u   ’ >’’’’d  M b  ' kÿèëÏ&753753753#5&'#5&''655'6}        ’=8h4qçc#™\$4E* (:!!& ÿê‹Ð,27&'3533#67&'#"''3255'675#&'o  O433     #4  Ð   ))&  -  0<   EéÐ"&*73&'73#3#3#3##5'65#5#5#A4RREEEEU§ $O>>>>>Ð  N RÿçóÏ 73533##5#RGFFGNN‡‡óÑ 48<\73#53&'733327#"&547#&''67&'767#533#735#367#"''3255'67567#J-n,m%     ‡^^::f   GÑ ! 1 " &   * (    FÿèôÑ3:7''6767&'&'''63&''67&''667#À6=$' 9 !H $ *!"/ + ' >Æ " 3      ÿéóÑ.48<73673#3#3#&'#5##5'67#5367#5367#3&'35#35#RimkrŒ2! g  1?:BN@k^gggg¾   ] Z   E  9=AÿèóÍ$(,07373#3#5##5367#'6655635#35#35#ç 8K=496G#8  W#GGGGGGÍ I;8F<u98HÿêóÑ -73&'73#3#735#33##"''3255#53567#NCH¢……__Ž!JJ   MMl½ 43   NîÅ 7&'67&'67&'6rABÅ.!&* 0)(/-!&) /((/-"&* 0)( ÿéjÏ 73'6573#'3# A ÏpG/ )@pæÙ¿rÿèõÈ #73#3#537#35#35#35#'67&'u~2.l)7FFFFFF  Aȇ‡?>>.     ÿéõÏ 7333#"''67&''667#119;$l     "ÏO7*%1++4 gôÃ73267#"&55#'655Í   à  7#,??ÿéòÐ#'+37;735333##3#3#535#535#535#535#33535#5##535#35#$OQQXXkäeUUPPeeOc>>>Ä        ]S S+DÿéóÑ;73#5'67335#35#3#3#"''3265#'67#'67#'67#ow~  tyZZZZ*«qp  ) "- # &² C: ( & ': $* #  fÿéêÐ"(733#"''3255##5335#35#7'6'&' 7  U3 UUUUW Z  Ð=’  ;ª0Ly  wîÐ733673#5##53&'733#735#u  *²-  A™™qqÐ +, ' #?éo73673267#"'&5#IH?Y :GIF l   +ÿéÕ3 7#5##535#35#Õ‚‚‚‚‚3JJ '  ÿéíÎ 73#7&'''6'66tLršB{ GSΙ€% ':( ) P-ÿéí‚ 73#"''325''67&'x    8•‚  Z2$ #+%, .% ÿêöÇ+8C733#3#3##"''3255#535#535#53567#&'76'3'6767#)®#+"",, '' %޼/ Ã?  +Ç ( %">/k >B.dÿéêÄ73#"''3267#'67##5##535#h‚  // )*}QQQÄL1>/_iiG4ÿéôÔ@FKP733#67&'#"''32655'67&''67&''67#5'66367#367#335OV ?`  - .  /> C1): :*1 *3  K I 3AWDÔ 7  ',    (! / ÿèëe 73#735#35#35#'67&')¬¬„„„„„„( 'c%% %&eZ? $ $     ÿé_Æ7#"''32654'7##5_ %Æ< ?ËÝ\ÿéôÑ)-173#3#53&'#53&'367#3#3##5#535#735#35#©9&—"8*9$v1BBAA1OOOOÑ   7 +P/2ÿôîË 733#3#53kbbnÚWËFi ÿèõ 7'67&''&''&'*¸  )  %       ó™ )-7367&'#"''325567#3'67#3#4‘  6  sR8 /:““™   # '  7MÿòòÎ#73533#33#53535#35#35#35#35#Z>??3¥/>#NNNNNNNN·‹‹;665[ÿéõÏ.73&533#3265#"'&'#7&'3#67'675#cF65   Gs  cA$,£H,#/]>   YB G ÿîóÐ$(,733#32767#"&55'6367#35#35#T U <Ÿ5J  WC 2Q J< MT iÊ7\ (QJ/>YyÿéõÏ73'67#'6'665&D  4 ' 'Ï! % [.B9:  ÿé[Ï7#"''3255'675#53533#W    eB  4>**7nÿòôÇ7#3#5&''67&'76ðos†] DZÕ&  ‚<÷Ï(73533327#"&55#&''67&'767#‹&    ´@ #.      ÿèiÏ73&''67&'67#5367#3'     Ï)L.  "$i!5# eÿðóÏ73533#3#3#535#535#'6ƒ22//7„;11$ Á888888 &QÿòôÂ73#3#3#535335#b:22?£$?ÂLK}}ª ÿéóÏ<7&'#673267#"''67&'#3#"''3267#'66553'3  5D      ZD   / lÏ  8&*5$!*$ .G*G% < 9- )P& >óÑ;7#673267#"''67&'#3#"''3267#'6553&'33&'7ìN     VE   2g »0  %0 # %/  ÿéñÇ&.7#5##535#53#'#335#"'&55#'655#33275ãž>LâK##až#- %*uŠ¡¡****{'*+%-ÿéöÂ73&''67&'#367šU     Â`4!-T@''@ ÿéíÄ73#3#5##5'67#35#ÚuŠv1&O;aNÿèôË'+?CIO73267#"&553'#33267#"&553'#33533533#3#535#35#'67&'º  <g  <- -!!)¦* 3-- M   Q<,  Q<,O+     VÿòöÑ 7'67&3#3#3#535#535#¥& 2 ' #JR!33C˜B22±)&- '+**+ ÿèô\73533#&'#5'67#deW%8 <$": 7#UI"*HH'!OWÐ 7&'&''6.  6Ð   NTñÐ7333267#"&55#'67#53‚0   7 -$&ÐC :8+yëÑ 7#5##53&'7&'''6ë­c & & (»,*       ÿéër"73#3##"''3255#'67#'735#Æ.66  %O FGf„r!.  *0  !ÿê÷Ð#,5;AGM73#35#535#53#5633267#"'&'367'7367''&'7&'&'7&'l!(@@“==@T».p  " P" * ] E \ Ð[Vd9*3Cc  pc  o        UÿêìÑ#+/373673#53&'35#&'735'6#5##535#35# !  "—'//  (*Ï#""""#]####3"""pâÎ 7'2'6'&''&'Ø Ps_QA  8  Î "    ÿçó{!73673#&''67&'67#367 K…2 %! /)> <! BY  d  '    !BéÏ767&'7''63#735#c48 #NX+  xxÏ  -? ÿîõ< %7&'''673327667##"&57&'yC+ 4  Š <  $)  1 _òÑ(7&'3267#"&''7&'7'#5'6° AJ  , 2.K  'Ñ   % F3 YÿéøÐ@73673#67&'#"''32655'67''67&''67#53&'…   $?      + /"" # 7! Ð   :#;""$)  ÿéöÎ73533#&''67#]b^N G!W a\”::_"!WI/7M ÿéòÓ16<@D7#3#3#3#3#5'67#67#5'6553&'7'#5#3535ò?4500008ˆ  EZ!),,,Å   #  1"E4 4?^  &ÿèòo#)/5;733#3#3#"''3267#5363535'67&'7&''&'‚PŠ˜   —:*vvv2  Y   o1  T;      AÿéðÈ048<@D73#3#"''3267#3#7&'7&''75#535#'67#735#33533535#35#U”hyD.. 1;=//   +m/È>l&S/ / Hÿõ÷Ä 73#735#35#3#`€€XXXX,¯¯ÄœW2v1duÿéïÇ#173#3#5##5##535#3#'3#3#73#3#5#535#535#ux35$#44M%%E$$%%F%%BhUUWWUÇEE4 a Qÿê÷Ð6:>B73533533##3#3#3#&'#'67#5367#535#535#5#5#35#335W:(;;<=K9, 37 +6?7988%i:$$7'Å  2  #&  2 ?XÿëòÎ4973533673#673267#"&55'675367#535#67#i ! 3B.& ,2   )# ;/ < ®      .     \óÇ!7#53'67#3#33#"&''67w^Ð F??<512 µ   * ÿçêb73533#7'7&''75#75##5#&PPP; Rd_P <‚XÿéëÏ7#5##5##535335#335ë,+??++?,¡tVVt..O<<<jÿéëÏ 7#5##535335#33535#335ë[68$$8#[$$8#¢¹¹--J777888 ÿèôÏ)/7367&'3267#"&5'3'655'67'&'&*  (7P G$)"8ÏB E  Ë|A)$4L ÿécÏ 73&'73#3#3##5##535# "VEEEEG  ² V V6$aÿéëÇ$(,7#"''3255#'6553533#3#535#3#735#ë  WG>>ÇÄ  ¬SE4 4A_16= ÿéóÒ#)/5;73#3#3#"''3267#53635#35#&''&'''67&'[e—¼¼µ ³/!ƒƒƒƒ{    M >  ÒP= & *.e  bÿéóÎ!733#3##5#53533'66''6ª++5<?6>3 /9N Î <<;;$*4 -& >ÿéôÏ(7#5#'67667##5365333267#"&5ê1- -B   ¥7$F!&5(&9HU  WÿèöÐ  7&''6'6'6'66¥' &( 2#0 .0 = ;681 .5Ð!"$ $ , "&$EÿéöÐ'+/735333##3#3##5#535#535#535#535#33535X8>>GGSSKK;;99GG8K+++¼'(((% IÿçôÏ#'-373533#3#535#735#3353#735#35#35#'67&'\999K«L9'';'v‰‰cccccc$ !C"" !à +   E_C ' '     ÿêôÑ *7#5##53&'73#3#33#"&''675#ë­c`¢EDD 1*C/ *H»,+  3-0.;!l ÿéíÐ73#'66553&'ŠY¶ [Ð N@2 /!Y ÿéíÐ7#'6553&'7í¼`»N@2 2;Y  ÿçæÉ "073#735#35#35#'33#5#'65#535#73#3#5##53DvvPPPPPP=D# RE·CE3CÉiK++7bk8 srG67l>ÿçñÇ %7'66553'#33673267#"&5o ˆ``I'" '/   ( €E5 3"`G4! *  * $  HÿéóÊ #BFJN73#5'675#73#5'675#&'7&'3&'73#3#3#3##5'65#5#35#XC1ND18^8$982222<} =.....ÊW  W      4R   ÿèïÈ&*48<73#67&'#67'5#'6553#3#7#335#5355#35#¹5U   D½&''•••/''B///H    98 B, ,7m<V1 + ÿëõË#:>BFJ73#3#5##5##535#3#73#3#73#3#327267##"&55#735#33535#335ÈZdQReZ;;d==e;;e==rµP  B Q>>R=>>R=Ë /99 0 (  M   -/TÿéóÏ!%)-73533673#3#5##5'67#535#67#35#35#i+-/BVI  36+? IIIIµ  ue oA_ÿéò¿73#3#5##5'67#35#p‚FR:  &()::¿#"~ c1D¤M8ÿè÷Ñ!%)-73533#3#&'#5'67#535#35#33535#335MGGG=/( )#, . 0>G**=*g**=*¾]"+KJ,']99FÿéôÑ'/37;73#33##3#5##535#535#'6553&'5#35#5353535#ª==//0F(''2A 22':DFFÑ #$J J B1 2>Q>>>Q>½`!,IF)!`:<1ÿó‰q 73#3#735#76767'7&'5NNFF"" &.q ,"     sóÏ73533#3#3#535#535#W^^VVjæhOOWÁÿéóe$(-733#3##"''327#67#5363&'#33657##&'#0§  ›=.A 2>0e!  ; ÿéåÆ ##53533#'##35#Ñ¡1g11@0¡1‹RR‹ÉQSS ÿêNÍ 7&'&''6$1Í  0  / 0-+LÿìòÏ&73#5#3267#"&55#5335#53533#3Å&   * 6"??KK&„V 0  6K7O O`ÿòóÃ7333267##"&5477#d~[ & )NcÃ| &$iNÿþó²73#3#]‰‰¥¥²ŽNÿèëÏ73533#"''3267#'65#Y1M  9?<1¢--‡/sfA ?[KÿéõÃ73##5'67#&'^“@' 9>ià ±‡*"5F5(&%GÿíòÌ'73673267#"&5'37533#7'67¨  T'0ÌTY   —”¼>g ?ÿòóÏ73673#3#3#535#'67#M5Y] j9D¤J' 00«!II-5EÿéóÍ73673#3#5##5'67#35#M9X] dN  ,46NN¡tY)0‚>vÿèôÏ767&'7''6¶"")  -- )ω:/1 SˆÿéõÏ73'67#'6'65&¨:  +  )  Ï! g-2[+ …ÿéõÏ73'67#'6'65&¤>  - ! )  Ï!  "_-2[+  ÿïhÏ !'-7&''6'675#535#53#3#7'6'&'4  <$+##:!!/Ï – C?6ÿæò‡#'5;73#3#5##5##535#3#73#3#73#3#3#''67#&'#6ÇYdQReZ::e;;g::h<"  !&:    e Z Ð N= 49  .4,.(*4B  ™  ÿçìX6:>1073533533#3#3#67&'#7'5'67#535#535#5#35#2677//Eb O"  / H:((2y666I    &      cìÑ#7#5##535#535#53533#3#535#35#ì³aLL^^^^LL;;O<<‡#$ " "  2RÌr 73#735#73#735#2DD%%EFF''r uÿéîÏ!7#"''3255#&''67##5373î    !4¨§  "B­¿''‚ŠÐ7&''6767&'i +1¿ &#  ÿézz7#"''3255##535#35#z  44444zx  3‘(<añÉ73673267#"&5! !(  $É"    ÿëõW73673267#"&5# #* %W#    hÿìõÄ73#3267#"&57#;5#hk!#  -06""""ÄpI !+  ­JJÿéîÈ!%+17=753#3#3#3#"''3267'#33535&'''67&''&'ÇZRRRRc aDDDDDD5  z f  @ˆE%-v'/ " Pÿé÷È#'+/T73#3#3#&'#'67#535#535#735#35#5#35#367&'#"''3255'67&'767iu$$,% * +.$$PPPP:%%%  !   ÈC ) ' **      ÿê_Ï7367&''65''6,  ! Ï9    2 *@=) zÿçøÏ'767&''67&'&''67&'76  V  È    O ( # "xÿèøÈ7'6553#&'7#3 ^ " & 88d2/ &/dL \Q> DÿéøÑ048<@DLPTX733#3'67#7326567#"&55'75#'65533#735#33535#3353#53535#35#35#AAS@-/ ! 1kk-J- ”"!Ñ    J=0 07UH<$ ! !$$$gÿêòÏ(048<@73673#&'#5'67#53&'735'2#5##535#33535#35#â  2 . ;BR!!2 R!!2 Ï     $   sg g%:ÿébÏ7#"''3255##5##5353b !©t  \­­yŒ&& ÿè÷ÎJN733&'76767&'#3#3267#"&55#'655#535#'67&'767#5#"Y RAA   ) "J AAA   Dl"È"#        *+ !  1."*  n** ÿêsÐ73653367'5#'667##   Ÿ1†  ƒn5 B= ÿéjÄ73#3##5#535#Q%%$$ÄIllITÿéóÐ#)/5;73#3#3#"''3267#53635#35&'''67&''4'›;\jjh j III  P  K Ð JA-… (_  lÿéòÏ $*06<73#3#3#"''32767#5363535&'''67&''4'•=Xhhk k EEED @ÏM> &‰ `   NÿéîÏ#)/5;73#3#3#"''3267#5363535&'''67&''4'ŒCbuut tPPP P  K  ÏM> -‰ ` ÿé`Ð 73&'# Ð?oÿéòÏ#)/5;73#3#3#"''3267#5363535&'''67&''4'œ8Q__` `>>>? =ÏM? ,‰ _ HÿèóÏ73533#3##5#535#7'6'&'SABBKKLLA}  ]  ‚MM*JJ*U   ÿéKÐ73533#7#"''3255'675#     ¥++' V H2>ÿè™Ï%+73'62655#'67#535335#4'4'i" ### Ï ¯ IG( AOO> ViòÉ733#"&55#'655à   É@  5  "œÿçõe73&''67&'#367žJ    e/     "cÿïùÃ732767#"&553#"''3267x 3 :)~  ¯  ,  »_#D ÿéZÎ 7&'&''6*-Î .  3 /,+ ÿékÏ733533#3#5#'65  90Ê=BB)iW6 $4lÿèóÏ73533#3##5#535#7'6'&'u12299::1c  Q ‚MM*JJ*U  Yÿè–Ï 73'65''6ƒ-*  ÏK`< :U 4*)›ÿóôÂ73#3#3#535#535#ŸR #Y$ ÂKKKK _Â73#3#67'675#535# M#+Â:; B: VóÐ !(:?CHM73#'673#&''67&''667#'33#3##5#7#5;&'#3377##&'#+S] ‚B   )}W K   Ð          . z<õÃ$)733#"&55#'655#53&''67&67#Ù $ e     0 à   N      ÿîó7 73#53535#35#35#Ûæ##6""5##7666$$$$$ZïÐ7353#5#535#535#733#3#3##EII>>E}FF==IIÀv " !ÿéóÌ "733#67'73673267#"&5!>>! * g# $*  )ÌGn  ÙKb ! ÿéðT 733##5#53vffffT==!TïÐ "733#67'73673267#"&5!>>"!* h# $* (Ð$0  s.   ]ÿë÷Ï#)/7&'36732767#"&55'67''67&'œ )   t  Ï .T6Mk5$  X6% + ")*! ÿéqÍ7#5'67#535'673#&I  '(+% (( Qh`#0& *eÿòõÆ 73#53535#35#35#âAAAAAAÆÁÁA/o/o.jÿéóÅ73#3##5#535#'6'&'sz6<<::1f N  ÅcVVc * ! "#%! ÿélÍ73#&'#5'67#535'6^ %%   #%*Í* o_#0& ÿéóÊR7367&'3267#"'&55#353367&'3267#"&55##5#'655#535'6 ‘   7 %2    %)('' )È   &  %  (CC$   *0jj :% "0 ! tÿéøÉQ7367&'327#"&55#&'327#"&55##5#'655#535'6735336{Y   Gb      É    g  %&jj?' %6,6JJ&  ÿétÎ.26:>73673#&'3#5##535'67#53&'735'235#33535#35#k ( " $7$  !,#7#Î!    'g g*  Ÿ=dÿéõÏ#)73&''67&''667#&'&'–<  !$ " !  /$ ""?+ *=Ï "      ]   ÿèðÑ &,273#3#'66553&'#53&'367#'6'6'6„^# 1µ 5 &X7 V ^ 2L P98U \A5 ˆÿêõÍ 7'6'6'6Ô0 01 ,"!? =Í *# ' 2#$ ÿè÷Ê 7'6'6'6ß- +*'/,Ê ) $( /  ÿè–Ç$(,7#"''3255#'6553533#3#535#3#735#–  VG>>ÇÄ  ¬SG3 4A_16=]ÿéõÐ!273&533#3265#"'&'#7&''3#33#7'7537iM)(  Mv  _::&0‘"#??8$WD 7$, _[ÿèër 73#735#35#35#'67&')¬¬„„„„„„) &d%% %&rbE (( "     ÿêÐ %*04:73#'636733#3##"''327#67#765#7&'7#7&'.Ua `   Q^8)9$Ð ^$(#  X))#  ˜ÿêôÎ73#&''67&''667#´4       Î#(" 2&-|ÿèõÏ 73#&''67&''67#675«C   )  ¥F$ 5@$$( ÿéõÃ73##5'67#&'ÙU &0C2m*(#/à ª0".N+"/++UÿéöÏ73533#3#&''67#53655#l022<;0*3 8 8:0¤++#:43 %3# ÿêùÉ59=7327#"'&5#'66553#7&'7&''75#535'235#335Ò  ˆ † // 2;9//6.Édd$#-ˆSF4 1$_J  !J^&&& ÿémÉ7#"''3255#'665535#35#m  ' &&&&Éà 94( 2#`:(a(aÿêõÑ3733#'65537#'6#3267#"&553#"''326•A#nF8 M,  (P  Ñ=3' (/G [U  l8  ÿé]É7#"''3255#'65535#35#]    Éà 95' 5@`:(a(€ÿéôÉ&7#"''32767#3&''67&'767##5æ  >Z    DÉ4J5$     qßhÿéíÇ #73#3#735##5##535#33535#35#h…… ssMMi]&&8%]&&8%%Ç9:o o*?YÿéôÏ7''6767&'À ;; 3,6[06  P{A8*  " ÿéòÉ73#3#535635#'67&'ÆO=•0Aã'R     #d0 0  4 Ï         0(C /HHIYbÿçíÐ*.267'6553&'73'35#'27655##5##5##535#35#35#€  31hV      w79 :o  >-Af #$$&&;z.Sâ½73#"''32765#'667#]…<#½' SA2h  Š''N-*C>.-PM  ÿë|Ï"(73533#3#&''67#535#'6'&'%)).0  #*-%M  9  ²;  ' ,D  ÿéöÄ73267#"&55#'655&'¹   U*1Ä·   ªI.:%RS@$ # 0ÿéŠ73533#&'#5'67#<    }   Y_ !,}ÿèõ73533#&'#5'67#‘)  } 51ll.2ÿèïÂ7#53&''67&67#2± 5"8 6()=C"5D+ t ¯S;&&!?0/\ 4XS^ #^  ÿêVœ 7#5'6C  &œ{X / zõÏ!767&'7&'&''67&''6\  !$  *- =+67 7# &Ï        ÿéŠt73#67#5'675#35#35#75#s%* %++++++tJU03‘ÿéët7#"''32654'7##5ë  0t"&y‹`ÿèîÏ#)73'67#'63#"''3257&'''6ˆ^ O  '  8  IÏ   *ƒ  _)*-'/( )kÿéìÎ73#"''32765#'6&'—N   C   ΄k!3  ÿéÏ!'-776767&'7''67'67&'''674'( ! #)0  C  3 …<   /)t  ®ÿéïÀ7##53#"''325ÜA   ®Å×™  ÿèïÐ"'7373#3&''67&'#'67#67H…n, +#%2 0  - :D`  ­#1 '8);K5!"†ÿèôÐ73533#&''67#7&'Œ$*& # &#O  ŠFFP.%9?'ZP ÿéØÏ7&''673'67i  3 e$-; ˆ œ   @$4%"z }È767&'7&''67'6767b ' $+–E*  +"95mÿéõË73#&''67#53655'6ä 84, ( (-12,AË7J=9!I(qÿéìÈ7#"''3255##53#3#735#ì  UAA77ÈÅ  ­Ìß,W5|ÿèíÎ73#"''3255##5363#735#ª9   M" 33Ω  ‘±Ä:lNkÿé÷ÏBH73&'33#673267#"''67&'#7#"''3255'675#535'67&'§ )(        Q  Í. )I) &  #.#  6  *+(qÿêõÏ $*07&''63#3##"''3255#535#'67&'¬ $9//   22PÏ $%#C  ?7 pÿèõÑ5;7''6767&'&'''63&''67&''667#Õ *. 1 6     *Ä " 1  %      rÿçöÒ'-O7&'67327#"''67&''7&'3&'67327#"''67&''7&'7À >I  !  CA !  ($Ò     L        ÿê`Ò"&*7&'3'73#67'7''5'635352 .     Ò X-   ‹ 1# ÿéSÏ73533#'#5'67#   //|v'. ÿôsÏ 73&'73#67'676'&''(eH *4% ¨ 92 77.03,uÿéóÏ 73533##5#u4664NN…… ÿônÏ 73&'73#7'676'&'' $`D (1#¨ =- >0.03, ÿôiÏ 73&'73#7'676'&'$ "[? %, ¨ =- :4.03,bÿèôÏ73673#'67#''6767&'t!EH&& ` " ¨&hD HWX#)  -RI*nÿíõÍ&7773267#"&55'75'75'6Ú 9 9#@ ' +ÿèòÐ!%73#67&'#67'53&'3535~ DM "% Y .%"- J){{{Ð f   KM Á ))sÿêõÉ 7#67&&'#67'53535á1  " " FFFÉr  /)V  Ö0ÿêõÇ7#67&'#67'53535é+  .  444Çp  !JV Ô/ ÿé„Ï/37;A73533533##3#3#3#'67#5365#535#535#5#5#35#335&' ---.58 , " +0+,++K + » 0 '  0 9_   ÿïkÃ73&''67&'767#S   =ÃC1,2" .vÿíôÐ"&*733#3267#"&55'6367#3535#™4 O  % '$ ÐT1 }$3333H õÒ17;?EK73673#67&'##"''3255#5'67&'767#33&'35#35#'67&'W/H&  +  1  %= > 1JJJJ f½  0'  $.    : ' #   CÿéøÏ )-157&''63##"''32655##5##5##535#335335˜&+ *( . ;KKr *Ï#(# i 1111=‚3!!!!!kÿéõÏ73533#&'#5'67#%+' -$#¡..69$(st2(D ÿétÐ $*06<733#5'667#35#33535#335&'''67&''4'+%O   +   B@Ð cW *C/" MÿçõÏ !7&''6&'67#53&'›#) &%'2 %`x&-Ï,/,%   X PíÑ73'73##5##5#&'''6c bJ$D°  | ¸ UUUU ! ÿðöÌ#7&'332667#"&57&'''6f$!  '"…  š Ì!+†# ‚-6 8,C. 7 ÿísÃ73#3#7'75375#735#R ,6%++ÃP1 XTg,pÿéõÐ%)73&''67&''667##5##535#–A $    2H@@@Ð        a\\<+ ÿíiÃ73#3#7'675375#735#K(0 %%ÃP1 XTg, ÿí^Ã73#3#7'75375#735#H"*  ""ÃP/ XSf,`ÿéóÏ16<736533#3#3#3##"''3255#'67#5367#537#5#&'l*DEBET   9  %!)Y+  ¸ 4  1 "* ^  dëÍ7&'73#5##536'&'x  _ +­‘ƒ  Í ,* ÿéÓq73#"''3267#'67#536e\   KD = 58q L5I4ÿèñÐ26:>BFJP735#5#53535333##3#3#&''67&'67#5367#735#33535#3#335#33567#]QQQQWm†+ #'/(+G4$% -?C ==Q=Ž==Ž==›JJ^DD Sr 0     f # >R  ÿìòÏ873533#3673#6732667#"&55'67567#535# ?@@' >W)?7 % ##* +>IFQ3 `  % 6 0 (\3Îy    !  ÿòîÇ 73#33#537#'67#&'&'Ï…o:Ý\  6Rljv4.AQE  ÿéóÏ,B73#&'#5'675#535'2353#5'675#73673267#"&5Õ %.dd+= 9%*6 @+ff$(Xe8!8‡    ÏG.)IG)-GJ G      ªÿëõÍ733267#"&5ª   ÍÇ   ÿîóÃ4873#"''325567###535#73&''67&'7667#3#$¯"*  ";4!!sI    3‘ææÃi o#Pf>,)       y£òÐ 73#53&'ƒcähÐ  ÿêñ|73267#"&55#'667´    D|q  c;2-C •òÐ 73#53&' cålЬòÐ 73#53&'„bäjÐ ÿíîJ7732767#"&55'75'6¾$(_a) 8+ORPJ  <òÐ 73#53&'3#735##5##5„bäjC¡¡{{­·Ð +/,%'NÿéóÊ73##5#535'6Ü  HHII6 BÊ?rr;lÿéóÊ73##5#535'6Þ 99::( 4Ê?rr;QÿéòÏ73533#3#5##535#35#QFGG8X4F&XX222pp2~9MÿêëÎ73#"''3267#'6##535#‚b   X#J9&&Ί(q$'XgG5WÿîðÏ %7#5##53&'736732667#"&5ínD @- &5  /&¬.. /0 0   RÿêòÐ 7#5##53&'73##"''3255#ïsA Y A   K®11 ?Y  VIÿé÷Ì"(.73&'3267#"&5'3'67'677'6'&'§!   (.* aw  ÌcL  È}>( "3R & SÿéóÂ73#3#5##5367#35#35#S JE`.A````¦¦`-l,FÿéòÏ!%)73673#3#"''3255##5'67#35#35#Q0Zb `  O  '/OOOO° € 5|  #CB @ÿîôÏ#)/5;CGKO735333##3#535#535#535#33535'67&''&'7&'3#53535#35#35#W:>>O«I::HH:M***t  I  >  A°()      =       333#####KÿéòÇ!%73#3267#"&55#'67#735#35#35#a{"    1 *SSSSSSÇ‘/   57+fAA ÿíqÌ 7&''6&''67 %  'Ì ",*$-CÿéóÐ%+05733#3267#"&55#'67#5'667#367#335{G 0/   ?91 %3 :  #(;,Ð  K;  ?6")B 8%KÿñóÄ#73#3#3#535#535#735#33535#335Z‰:::J¨J<<;'';&a'';&ÄsCMBÿéñÆ!%7#'66553#3#3#5##537#33535# 5 3==2I+3hhh5IIY?/ /$^>UUu} IÿéöÏ-73#35#535#53#3267#"&55#'667#56‡**d**+?% #Ï sI  L., $%kMÿðóÐ %73#5##53&'&'''63#3#535#› DuB0  $ '‡:I¥F7Ð .1 : -22MÿéïÏ #/73&'73#&''67&''63533##5#[9 >Ž  Z  hGEEG´    -   ;>>Uÿééà 73#735#35##5##535#35#_€€XXXXvlllllÃ[668m m'<OÿéóÏ(,073533#3#3#535#535##"''3255##535#35#X=CC;;J¤F33=ˆ WWWWWÀR\  $t 0JÿéòÏ733#3#3##5#535#5335#35#ŒKK?6IIJJ80[[[[Ï^++^&:RÿéöÄ /7#3#3##535#73#535#3&''67&'767#š53333""@FF11K     5ÄO0Û=+M)L/$      ÿéöÓ!%+37;7&'#5'63&'3#735#&'735'6#5##535#35#}36 e OCK··@@  @@".|||||Ó     &B!  !!  2T T*LÿêöÏ!%)-BH73533&'73#3#5##5##535#35#33535#3353533##"''3255#&'QC G<))F ‡‡cc4%«'ycccà   */$  (= =! ÿéIÏ 7#5'65  ϳ‰2[ÿèáG 7#5##535#35#á^^^^^G_ _!1LNžÈ73#3#'67#'737#37'SE +  È &  7  MôÈ&7'67#'737#53#3#33267#"&5Ç I  t     GÿîõÉ #'+/373#3#735#33535#3353#3#735#33535#3353#U••‹‹**=*g**=*†¥¥ ŽŽ++>+i++>+Ž®®É @& $ $ @& $ %Cÿé÷Ñ  -:7&''63#3#735#73#735#&''67&''6–%/ /%+ :JJ @@=@@I `  Ñ!!;;/   "   GÿéóÏ$=EIO73533#3#535#3'67#3#3#535#3#3##"''3255#535##5##535#&'QCCC<Š;C¤ Žr+K¢D4¬ 44‚FI Å   $    #   D D*  IÿéòÆ #'+37;?C73#735#33535#335#5##535#33535#35#'#5##535#33535#35#]……'':'a'':'"--;--ÆT262xx-L  **>M.‘íÆ7#5##5í²Æ5$$5 ÿèõ°$*/4:733#3267#"&55#'67#5'6367#367#35#&'YS 4<  * KE8 0G G -4B>74  ° G& -0#7   <##( :ÿéöÏ$7367&'#"''325'3'67#  *+   D9 * %Ï- 4!'@h  Ž/%2@ ÿîCÈ 7&''6#  ! È f 1-+IÿéôÑ 48<7'6'6'&''&'35#533###"''3255#535#75#35#Ô8V W2 /%  E;Ž@   ;;E„,,,Ñ     E**"  )ÿéå;7#53#"''3267#'6\EÎ _F >)9 3   ÿé‘Ð 8<@DH7&'67&'6'&'63#3#&'#5'67#535#735#33535#335'   Z      .r199  ,6//N/Ð     .K  -0,- ÿêæÎ4;73#"''32765#&''6677''67&'767#'667#Aš    + . (   " %> "Î Š!v1#  ,  %# %- *›ÿéëÀ7##53#"''25×(P ­Äן  ÿé”Ë  $7'67&'&''6#5##535#9A+L999Ë $" 1^^<*.ÿêð­.4:73533&'73#673267#"&''675#'67#'6'6<"0  2W/-," +  ."€ $& ’  m %  u@B!c     ÿçõÓ#5BHNT767&'7&''667&'7&''6'67&'7&''6&''6'67'67'6e$& ;CT   $]  &+P0< 7;+7 O' 2 69 .S XN :n qÓ               %  + aÿêóÄ73##"''3255#a’1   MÄ­  ªrÿêóÄ73##"''3255#r*   CÄ­  ªQÿéèÃ73#"''32665#'655#qw  5<9ÃmV PR?T42H?UÿèöÄ7'6553#5#&'7#3…x 2 8 0OOj C5 4?]f F*0RG4aÿéóÏ '73&'73#67&'7&''67'67676b>=‘f/*%* 87, ²  X "( "%-(XÿðóÄ%73#67&'7&''67#3533#3#535#d†@!# /50577D›C5Ä+# )u ''^ÿéóÇ$*73#3#5##535#35#35#"&55#'655#5#3^•.(a)/A'a aÇ"©©"""§* 7* "772fÿéóÁ73#3##"''3255#pyy %  SÁ.j  fUÿé›Î7'6'67#~  #Î-_ !( xœÿéóÁ73#3##"''3255#¥EE W   'Á.k  gyÿêòÐ%)-157&'3533#3#"''3255##5##535#35#33535#335Ö  N422- -4.H.Ð !!ˆ  225Ÿ@GTÿèôÏ (.473&''667'6''63&''657'6''6¡% +(8  \ ,) *3 G: ] Ï$' A(&#!* V=õÑ 7&''6&'3'67#£' '% 4 *w ^Ñ    Oÿìõ@!733267#"&57&'''67&'€) 5 €  @;  8  $ EÿçõÏ/73#'66553&'#3&'36533#&''67#§ 2m#9@[[A!6+ ,)/ *Ï ?@1 / X #  ),,*@ÿéöÐ-1597&'#673267#"''67&'#'66553'33#3#735#Û  %*      H YQ7755Ð 4!3"*3*,BH<- +S##%E#`ÿèìÉ $(,73#735#33535#335#"''3255##535#35#`ŒŒ++=*g++=*  YYYYYÉT231g  )#4OÿëžÎ73#&'#5'67#535'6“    #Î( b^ .#WÿèóÏ -73353353#3#3#"''3255##5##5##5367#`&'‰ œHG 0@À&55&7S ?JJKKVg ZÿéîÑ 473&'73##5##53&'7367533##'2655##5##5_8 =‹n% "  ,5  ""¹  #((   2; 'XX?PPÿéõÑ #37;7&''63#&'67&'6'#"''3255##55#35  ( %$* 8HH=    +    3 1Ñ$$! !$ (# #("p  .‡%AÿíóÐ573673#3#3#3#3#535#'67#5367#5367#53&'   "D@CV\Z.:‰;$ ,!($(4'Ð   ""+   PÿçóÐ#)/7373#33#53537#5#35#3#3&'''6c=8<6—/9mRRRRRR *# ¾ww/  >ÿé’Ï73#3#"''3267#'667#53&'r!#     Ïd*S27>L}óÏ 73#'6¬=D Ï "•ÿéõ!73#3#&'''67#53655#'6´+     #& ' # Pÿé¤Ð#73533#3#3##5#535#535#35#35#P! !&&&&¹[%%[<: CÿéöÐ!%)-17=CI73#3#3#535#535'65##5##5#35#35#35#&'''67&''&'lr  i""!  o d    Ð ... 9......@.....I  Iÿé÷K73533#&'#5'67#WBC6% &* *7>   86QÿèôÏ!%6:>73533533##5##5#3#53&'3#735##"''3255##53#735#Q$5$$5$UA›D+ssOOt r$RR00Á '* 'K  7Ra+ ÿéñÏ'/37;73533#3#535#&''67&''6#5##535#3#735#[``jâf[*   ~   5’’’llIIÀ::      ?l lQA ) aÿéóà 73#&'#5#a’H*6Ã*„ÇqÌ73533#67'675#" ,3"”88JQOÿéñÍ73##5#'66556Þ *9j&.(>Í +……D:1W!;ÞÉ"&*733#5##5##53&'767#35#35#35#35#!½") ???W  ??S??S??S??É  __ =- ÿè‘Ï(,26<BHNT73#3#3#67'75#535#535#535'235#&'735'6&''&'''67&'ƒ 661111B283322::7! +       8  -Ï =   =  d   Y  ŽÐ&*.2673#3#3#7'675#535#535#535'635#33535#335 05500//,2@000055,5 /M/Ð  ?    ?  K ! îÏ73533#"''32767#'67#™,    ©&&h  ?.6"HMÿé÷Ê -73#735#3#735#73#735#3533#&'#5'67#lkkCC'AA>AAfCF;& %( '8Ê4177:  !98ÿégÏ7#"''3255##5##5353g "©t  \­­yŒ&&ÄÿéØÏ73#ÄÏæMôÐ &-BFJ7'6'&'73#&''67&''667#'33#"''3255##533#735#z K  >     #b/ L255Ð         0)C  /IY% mîÐ73##5##5#53&''67&'€`G#C`2 Ð 7788 *   \ôÑ #)BHN7&'7'673#&''67&''667#'33#"''3255##5##53'67&''  a 7=     $e0 0  4 Ñ        '#:  &@@@P Kÿé÷É+;J[k73#3&'7'#"''3255##5##5'6735#''67&'7'''67'76''67&'76'''67'76Y’@,    ))  *@m    4   =    7    É    „™™ž£   6        7        SÿéõÑ "(.7#5##53&'73#3##"''3255#'67&'ïf<6TT“A   >c´10 +J  G PÿéóÆ $*73#735#35#3#3##"''3255#535#&'gyySSSS"–!!  nnf  ÆQ003,  ), SÿéôÏ&*.736533#&''67#3##"''3255#3#735#a8>A$ "& & 3¡  vMM))¹     9X  U>ÿéRÆ7#"''32654'7##5R Æ;  <ËÝ8ÿè¨Ï!%)-73533#3#&'#5'67#535#35#33535#3#3W    ) ) ¹V @@"$V95 ÿèô]73#&''67&'#53&'67†_. "&:B.24 7$'k' ]   ! oÿïô•73533#3#3#535#535#'6‹,,++2…?..! $$$$$$ ‘ÿìõÏ&7773267#"&55'75'75'6Ý ()/2    %Ï(  '  4  3&'#UÛÐ 73533##5#735#335#RRRR>>R>¸K''' HóÐ73#&'#5'67#535'2Î #+gR!3 ;$"9 2 Qg!'SÐ)43% `óÏ73#&'##5#'67#535'6Î MgM / :$"8 ,!LgFZÏ "&&  6ôË4735#533#67&'#"''32655'675#&'#535#2ƒ‰œ#`   $?   *38-4 ¡ƒ­ ;       .êÐ'-P7&'67327#"''67''7&'7&'673267#"''67''7&'7  kf   %4- 2$NB   kf  %&; 2$NB Ð       2       w|íÏ 73353353#wvÃ5AA5Gdÿêèv7&''673'67  %B # LP  +@dÿçõÑ7>7&''6767&'&'''63&''67&''6667#Ð .4!4 !:  # !   1Ä "  3  %      `ÿéóà 73##5'67#&'g‹8(?`ô†, .P5)'%bÿéóÎ73533533##5##5#35#35#b<<+<<<<¤))**§¨88ƒ8HÿèôÏ*.7533'67#3&''67&'767#'665535#< &4!  Q"!!¯  10$    ;/ ,TD1UÿéôÏ1767&'7&''63#3#&''67#5367#'6• " 17U/B;- . : 6 6< Ï" ! )! '   MË73533#67'675#  ! ”77N VRÿðôÉ +73#3#535#5#35#3353353533#3#535#\“-(‰(-T&&t8::F¢H8ÉOO=+++++X!!pÿìõÍ "733#67'73673267#"&5p""  ?   ÍJl  ×Q` # hÿèôÂ$)/736733#3##"''327#67#77#7&'7#7&'hn   blG 6J" q'**'=&&*>>5  z=!5 bÿéôÅ73#3##5#'67#535#5#qz!!"'$ !O!ÅLjj@* &7LLLLuÿééÉ 73#735##"''3255##535#35#yjjBB\ LLLLLÉ>=x  1‘(;TÿîõÆ $(73#735#'67&3533#75##5##5#kttKK"   Z† ¡€Æb@&   …OO====== aÿéòÈ #'+/73#735#73#735#3#3##5#535#735#33535#335f??6??S|4==AA5""5!V""5!È11/W((44FíÑ%8K73#3#35#535#535#533#5##53567&''67&'76&''67&'76S&&&&•%%$$&8µT         Ñ  ]''T     (      ÿéóN733##"''3255#53567#4• dd   nnqN   Fÿìòz$736732767#"&5'67'533› & #@$ 2z-  2 D@  ‚!lÿéôÑ 48<7'6'6'&''&'35#533###"''3255#535#75#35#Ù 'I F%  &  5.t 3   ..5gÑ    D)*"  )dÿîöË$(,0@76767&'#3'67&'767#3#3#735#3673#53&'¼    2 B<  )(11bb<<   "Š Ë      H 6  39-   VÿéõÈ 37'66553'#33#67'7'3#3#535#535'67# }XXPc7   ))1y5** ‘.F4 0$`7&!   jçÏ 73533#735#33535#335j55}##6#Y##6#¦))‰N)))e***c òÆ  &73#3#535#35#35#"&55#'655#5#3c-&‚&,>&\ \Æwwƒ ,% ,,'l!ëÆ 73#735#3#735#lWW88Æ¥S2V òÏ'+/735333##3#3##5#535#535#535#535#33535l,7799GGBB66--??,?$$$½""#ZÿéêÁ 7#5##535#3#735#êhhhDDÁØس¡ a=gÿêöÉ 7#67&&'#67'53535ß6 !&#% PPPÉr   0(V  Ö0PÿîòÄ !-73#&'67&'67&'63#3#535#[““   :   =   o”AG¢G?Ä   S** ÿêUÐ7'6'67#>$ " ' Ð )[#( ‚MÿìõÑ %73#53635#35#3#3#3#535#535#–A„.\\\\™B::H¨L99CÑ \\ 09:nÿêòÏ73533##"''32655#&'nR R  ›44ƒ ~$ &ZÿîóÄ 733#537#537#37#37#g ™"%''32/52ÄÃNOOO°NFàÐ-3767#'673#"''3267#&''67''67'367#>  %Ž     T !— #U!=  Vÿé÷Ï &,733#3267#"&55#'667#53'&'7'6Ÿ?.  %@+  t Ï]X  [02,)M ! RDóÏ73'67#&''667#'6vc  &0 - 5 & Ï  *#! #  DSÉ 7&''6%6# &É>bÿéîÈ7#"''3255##53#3#735#î  dPPDDÈÅ  ­Ìß,W3YÿéôÈ 73#735#35#3#3##5#535#mvvPPPP†;DDDD8ÈZ59733RÿéùÏ!%73533#3#&'#5'67#535#35#335`>AA5)("' +(5>""5!·G"MM(#GN###\ÿéóÏ #/73&'73#&''67&''63533##5#i49„  T   `B@@B´    -   :>>UÿèõÊ'7#3#3#3#67&'#67'5#535èbYYXXm?   :! Ê   EM  ZlSÿêôÊ '73#735#73#735#3#3#3#"''3265#7#d;;6<<W}}¡bZ   b)ÊB B ?3 %Pÿéö" 7&'''67&''&'ß f ^  "  EÿéõÏOSY73533#3&533#67327#"''67&'#3#3#3#67'75#535#535#535#535#75#7&'Q$%%/))     " .8!,$& e  º!<,0)'= ˜pC  ÿéKÏ7#53''68  /–æ75'!"ÿêóÐ)/R7&'673267#"''67&''7&'7&'673267#"''67&''7&'7 _b    !.? A& SJ  kj   #2? I [Q Ð         J     (   `ÿéñÆ%733'67##"''32655#53&'767#lv0  %   AE ^Æ  _ Z PÿéôÑ *7#5##53&'73#3#33#"&''675#ìe;Aj+11+,  +»**  4+0.;gHÿéõÏ"(,273533#3#&'#5#'67#535#35#&'735'6YBBB9+%"+ $$9B%%3%  ¹T$KT/%TW.  .. \ÿèòÏ<7'67#&'&''6733#3#353#5##5335#535#'67|]3   #K:T4@@'^$AA  c0     .  11  NÿèôÎ0K73533#3#535#3533#3#535#'3533#7'75#3#3267#"&55#'67#c666A‘=6EQ P&1   3 +*¾5 2! $2  #Tÿèñg 73#735#35#35#'67&'b€€ZZZZZZ  P gY? # #     Rÿèòx#'+73533#3#3##5#535#535#35#33535#335]=AA99FFHH99=((:(b((:(n  =  = ! # UkóÐ<73533#3#&''67#5365#'67#5365#53533#3#&^   n    Á   '  FÿèóÐ#'C_73#3#&'#5'67#535#53635#35#''67'6776767&'7''67'6776767&'›G:$ &) +;H     |    Ð h #=A**R  ;;;;;Æ?""??.0e  '}#4YÿéöÏ '/37#5##53&'7'67&'&''66#5##535#ðb:( A ' )0 "IGGG¹&&     "# 6P P1 MÿéóÏ#CGKO733533##5##5#533#735#73#735#3&'73#3#3#3##5'65#5#35#€&''&'' @@<@@H$794444;w <*****Ï  #.." O  MÿíôÐ "(048<73#'63#53#3#'#335357&'3#53535#35#35#À',$RP&&H '§&'Ð G3I#  5<<<***** WÿéôÒ $*08<@DU7#5##53'7&''3326567#"&57&'''63#53535#35#35#3##"''3255#ïm@    T ]  ##`Œ:   ?½       (((4 @ÿè÷È &BR7#'6553#&'#5'67#535'6#535'673#&'#5'63#53533533ð„L   7     9/ÈKE> 6A] +# .   -&K22> ÿé’Ï#BFJN733533##5##5#533#735#73#735#3&'73#3#3#3##5'6353535177/66<..****/` Ï  #.." M {ÿèõÇ#(733#"&55#'6653&''67&'#367Ù  c     Ç<  0%  *d/    $dÿèõÇ#(733#"&55#'6673&''67&'#367Õ % l# ' %  Ç<  0$  *d/    #ÿçyÃ73#3'67#&''67#g/0G?   $Ã[I @D   *; ÿçõÆ!',17736733#3##"''327#'67#67#767##67#7##67# #¨'  E=!º69|8 8z$(($1$(  9L9%&ÿêóÆ!'-3736733#3##"''327#67#767#7&'67#7&' ¨#  ˜·‚+_‚4s(++(!$',!"9~!#8 ÿíôÐ*7'67773276767##"&55'75'7- %% $&2J+!¬ *  (  1  3  &  `ëÔ '-373&''667'6''63&''667'6''6¡! &#4  P+ +(*  JÔ %  8  &  ÿéõÒ 73#'63#3327#"&'#:£¬   ± žÒ   c""1+\ÿèõÑ 73#'63#3327#"&'#:£­ ± žÑ   h&!0/brÿèïÏ 73533#3#"''3255##5##535#r5552  25¯ !\  E‚‚ew!UÿçòÏ 733#3#"''3255##5##535#53šDD=  ()>>>JJo‚888FÿçöÏ"73#&''67#5353533655#335áI. /49E88"$8'¨N142! $/N''N ** ;GÿéóÌ73##5#'66556ß 0A{)>KÌ ,ƒƒ:37D9\ÿéóÏ733#3#5##5335#ŽQQA_2__Ï,/xxTA\ÿéõÆ$7#3#327#"&'#67'535&'#ç3<: 5 ) c00ÆK!&)-.C Ò%%\$RÿéóÆ73#3##5#535#'6'&'_Œ?GGGG:x ZÆaVVa( ""$  ÿéDÏ 7#5'60  ϰ‚2BÿèôÐ J7'2'6'&''&'&'3#&'#'67#5367'67'6776767&'Ø 9RDC 1 ( p)P=$ .: .9D!%&**"Ð    3   !%      "5ÿéôÆ$(=C7#'66553#3#67&'#67''3#3533##"''3255#&'v ŸC   9  oo V%%  V zC6 2"_+   +%_d$ !  HÿéðÑ K73&'73#&''67&'763353#3#"''3255#67'7&''67##5367#IHG§n     [j?P  C !$ $?;½      11BI  3  Qb @ÿéóÉ +/373#3#535#5#35#3353353#3##5#535#735#35#F¨2.Ÿ.3d,,‚>PPPP<hhhhÉ 33 $1D) ( @ÿèöÑ *0D73#5##536'6'&'3533##5'67#&'3673#&''67#‡Sl):  ;  '((   I bBRC1 9? 29Ñ~no "   $$-    2  %' 6öÅ73267#"&553'#;5#Ÿ ($ ^8q"  yTC22 ÿéõÑ7&&'#5'6556y 1L N86ÑmC  MrÉÆP>5 24aYÿêòÏ%736733#'67#7&'33267#"&5a2FH/ * /`    –h1-[F  Kj   ÿéhÐ$7#"''32654''67&''67&'76W      Ð "7.-## '    ÿé^Ð$7#"''32654''67&''67&'76M      Ð $6.-## '    ÿéSÐ$7#"''32654''67&''67&'76B       Ð %6.- &   Oÿé†Ï 7#5'6s ϰ€ 2€ÿé÷Ç (733#537#3#3#&''67#535#'6‘Np?9 @%.* %# # *. Ç2     ;ñÒ0FL73'67#&'&''6'3353#5#'655#535#3533##"''32655#&'§1U L,  /y ?2LW""   W%   Ò )!)Ž/       ÿéôG7#'67#53673&'73#&€P DU^KZCL ,       ÿêUÐ '73#53&'&'73#3#'67#537#5363H) &Ð /, %«ÿéóÐ %73#53&'&'#53673#3##5#53ÑH$Ð /=IIEÿéÁÐ"7&&'567&'7''5'6556Ÿ     %Ð %n4 LD!²  ·-]; 6[8 ÿéóÂ%73#33267#"&55#67'7#&' sE@ 2  5,."8  Â$|  lz ·N€òË73##5#535'6ß //// .Ë 7OO0 ÿéëÒ '+/377'6'&'733#5##533#735##5##535#33535#35#à Hb­`7‚‚\\‰Œ==P<Œ==P<<Ò    #7&&7 20d d&9`¼ 7##55#'#33535#`<<  ¼¢¸I88887777TÿéôÏ37=736533#3#3#3##"''3255#'67#5367#5367#5#&'l.@A>AR   ;#!'!$-[+  º  4 1! (2  b  ÿèóÒ73#'67'6753&'&'’X±Xc  Ò OA2 .'  [&Gÿæ÷¦#)73&''67&''667#&'&'~J "# ,"/ ' $. =# !!B- ,A¦      4 a¼ 7#5##535#35#35#35#a+  ¼­ ¹H666777ÿéòÒ753&'73#'67'65&'+U^´  a\ O@3 *+  R:ÿèú¦ %)873533##5#73533##5#3'6673'6'3#733267#"&5ER$$O R S  Œ““““2$ "+J$ !BONK   ÿèðÑ753&'73#'67'6'&'7VM¥!  aX  M?3 -,  P pöÏ#76767&'3'67&'767#• PjbH   IÏ       ?4  oÿçè…'733#"&55#'66767#53&''67'Å   D[  $… ! ]    ÿém‚73#3#"''327#735#*@2;  = /,‚83)6wÿéõÏ#',73'67#3&''67&'#'65535335#67Æ, %  ++ ¯ /0%  ( >1QQ¿"" I K K`eÿèõW7373#&''67&'67#67#g&Q1 !, " G+E    eÿèõÑ4;73#&'#5'67#535'2373#&''67&'67#67#ß 6(   (77B&Q1 !, " G+Ñ !+*€     ÿëiÏ 73#3#&''67#537#'6'. !$' Ï %$:1 ÿërÏ 73#3#&''67#5367#'6.5"$ ',. Ï'#;cÿé÷Ä/5733#67&'#"''32655'675#535#535#&'wb=  " #@cMMO  ÄM   "+ .W gÿêòÆ $*73#735#35#3#3##"''3255#535#&'uqqLLLL ]]WÆR103+  () UÿèöËFe73267#"&5536733'67#3#33#"&''6735#53&'767#3#3#&''67#537#'6Ž     L     !%   69#   Ç    5 *5 #Df  7     zÿìõº753#3267#"&735#zkW #  .$CC¹oC   iIÿéôy%+173'67'676573&'3267#"&57'6'&'])/ < !*+#!  #!K ¡ y)/0 '  (7&  p   ‹êÑ 7#5##53&'7'67&'ê¯a) ' (L$ $Â#!  ^ÿéðÈ%)-373#3#"''32765#&''67#'67#735#35#33#umF^  )   HHHHJ]ÈU X! 7  31a"ÿîñ€7#32767#"&55#;5#ݧ;J TFT@@@@€O( |** ÿîô‹ 7'67&3#3#3#535#535#|"E O 29 4gj,CCdßeCC(s$ ÿènÏ"73533#&'#5'67#7&'7'6$##   "M ~QQY[!-QiÿéöÏ9733533##5##5#53#5'673673267#"&55'67&&  7     Ï. uZ&. .    aÿéõÑ A7#5##53&'73#67&'#"''32655'674''67''67#îY4Bp9    & .   º!!  *   ''(,        ÿégÏ"73533#&'#5'67#7&'7'6!     G  ~QQWX",Qÿéïo */73#3#735###53#"''3255#3##5#53&37#ÝÝ¡¡||*Ñ   +63+o " .5D0  wëÏ$9?EK73533##5'67#'3#3##'327#735#5#53#3#"''327#7'&'7'6&'Z  D<*/ 0'+Á+=*. 0K=   ± , )$  # $ $   )   ÿéƒÏ#)/7676767&'7&''67'67&'''674''  & &-/  A4„;!  /*s  ŒÿìðÏ733267#"&5Œ  ÏÇ   iÿé÷Í #06<C73#5'675#73#5'675#&'7&'&''6'6'67'66i= +F= +1W ' ' ( & '+ !/ 35 'K ,0ÍU  U      5       +  ÿémÏ"(.7676767'7&''67'67&'''674'$  "%  5  /…9" /+q  kÿêøÇ $(,0487#3#25267##"&55#535#55#7355##373535335í*** (  &&'F H1\1Ç8h g8Ë»° º1* ÿémÏ"(.7676767'7&''67'67&'''674'$  %  5  /…:!  /+p  aÿçóÑ "&*.4:@7#5##53&'73#673#5'675#5#5#35#5'6'67&'ïf74V"1Fo %!SJJJJJ    B¾##  \RG   i |   ÿéô^0EIMQU[agm7676767&'7&'#"''3255'67'67'6'3##"''3255#5355#355#5#&'''67&'7'6ä    * 6@ - 1 I99999¦ ¨ K 9  ^       C   C       bÿèõÏ-1573533533##5##5#3#3#&'#'67#5367#735#35#i %!!% x4@1 )- / ' -40QQQQ½P "*   00 ÿð„Ð 73#3#753#55375#535#'6'I*22O11 Ð.U@_ ZFV.  ÿèlÏ &7&'7'63#3#3#'67#5365#535#'  A @\%'*  ##Ï .$ iÿçõÏ !7&''6&'67#53&'© !)    Of %Ï -.)& Y —ðÓ73#3#5#53'€g°’¦eÓ    ÿåöÓ,@DJP73#3#5#53'3#735##'6553265#"&5'#"''3255#'65535#35#74'€g°’¦e@¤¤~~Ž<   žÓ   B* 6$) ))Y _Y  )( ( Wÿé™["73673#3#3##5#535#535#53&'j  [     bëÃ+1775#53#"''3255'675#53#"''3255'6'&'7&'&9   \&9   N  W  v:–*:–*<    ÿéòÏ$73533#33"&''677&'767#535# !""AVUG     ?!!­""#6&  $#}ÿéö¾73#3#67&'7&''67#Š`` r6%  ,,'¾3@ (0 (7 ÿèwÏ&735#53533#3#3#&'#5'67#535#%''((&&**  '*%Ž  HM (aðË7#5'75#53#'35#35#5#î'ž' Í zffffff~ E@4 & &  ÿé}Z7#5'75#53#'35#35#5#{K g >,,,,,, F;/ % % €ÿéðZ7#5'75#53#'35#35#5#îK g >,,,,,, F;/ % %  gÿç÷Ò !%6?HU[agm7&'6'&'6'&'63#735#&''7&'7367'7367'733265#"&'&'7&'&'7&'Ø   "   !   ~~ZZF   C 3 1 B ? ( ? Ò         .F ,    2MZMY$$ %/*        uÿéóÐ 37'2'6'4''&'33##"''3255#53567#è 0F92    h22  :: OÐ  ++ ' iÿéóÐ 37'2'6'4''&'3567#533##"''3255#à /D83  &  ?Uo99  ?Ð  c + 'fÿíóÏ-159=73&'73#3#53&'#67#3#3#3#535#535#735#33535#335o46 ‰ Q3+w255;?6622P2¼    M.-ÿíï• !-73#&'67&'67&'63#3#535#ÍÍ(  P  O —¹SfÞdR•   > ÿéiÅ73#735#3#3#"''3267#7#KK''\34  9 ÅC!E@ )%ÿêëÏ 7#5##535335#33535#35#ëH,..H.¡··..K999„999sÿæïÐ73#3#3##5#'6•P;..11 Ð$$A­ +uÿèòÏ73533533##5##5#35#35#u//'////¤++++©©99ƒ8±òÒ 73#53&'„fägÒ  ÿéñ<767&'7'5'6}    *Q+(  K<  4    ÿéòÒ #'+/5Q73#53&'3#3##5#'67#535#735#33535#335&''67&'7'5'6„fägF­Mbb&3 #&EcM99L9…99L9#" ##    *Q+(  KÒ &@    & # <   4    ÿèmÎ 73&'7367&'#5'67#!      (7¯     em5_ÿéíÇ$(,73#3#"''3265#'67#'67#'67#735#35#wkBW / ), %  CCCCÇ^ N18"+* 7:XîÑ%6G73#35#535#535#533#5##53567377&''67&'&''7'765##•$$$$&8´ #     -    ¡    K&&C  *       ÿéóf!%73#3267#"&55#'67#735#35#35#3š, &#> 2"ttttttfW   != " # "ŽÜÉ 73#735#335335"ºº&&8%&É; ÿéó— 73&'73#3#3##5##535# i gæ ¦¦¥¥¥€€€€     >>% ÿé^Ï 73&'73#3#3##5##535# QBBBBD² V V6$ZÿéóÒ*/37?C733#&'#'67#3&'73#'655'667#3#3##5##535#ŽA"  /-r $>8 ]]]]]999Ò      !  ,* !(O M  7 7 bÿçôÏ#'+/5;73533#3#535#3#735#3353353#735#35#35#'67&'b???76?‡‡'nyyTTTTTT  EÅ   "/ ,Q: !     wÿé÷Ë #7'67&'&''6#5##535#£8  )D222Ë $  2Y Z:( ÿé÷Ð"&*.26<B73'33&'73#3267#"'&'#3#3#3#735#35#35#&'''6– 5   –hhŽŽ }}YYYYYYB!»  X/ !#/l   Z@ $ #     ÿéfÆ 73#735#35#35#'67&'LL$$$$$$ 8  Æ£sMM6 ‡ïÀ75#53#3267#"'"&55ÍFYE  '}2U=  T ÿçôÏ(73533#3#3#33#"&''675#535#!!!("V60L7  /(!° #- $A V  ÿçôÏ(73533#3#3#33#"&''675#535#$##*& Q60L7  1,$°  1 $AY t ôÑ 7&''6'6'6'6¯ &! # $' 1 1. %D GÑ " !-  ÿõ[Ã7'753675#53#3#'35#Q' F " QJ]PP%h,LÿêìÏ"&73533#"''3267#'6655#7#5##535#e "     ‡¤++Œ)0_JIA?)Éʦ”ZÿèõË15973533533#'3255#'655#'3255#'655#75#35#[:: 6_o\\\\Y  T?#!@ Y  T?##> KKKKgÿèôÐ!'-73673#3&''67&''67#67#7&'p"JLF    # 53  ¢8#  )<@!p ÿífÄ73#3#7'675375#735#N&/"''ÄQ0 WSf+LÿèöÐ $*06<73#3533533##5#'6553&'#3&'''67&''4'¯4n D!8(   M I Ð &;;-8#PP Z* # ÿõSÃ7'75375#53#3#6'35#J$  > PJ^PP(h-IÿèõÊ '-;J7#'6553#353#'67#53365#536'&'73'7#'63&''667òƒG   ! N!     ÊTF6 6@a7 /@) @/3    %#+  %"TÿçøÒ (:IN73&'73#67#5&'67&'7''3##'3267#'67#3353#5#'6735#_;@’:    4   a0   PT QP¾   ,2  ! &# =." 9g&1  ÿéxÐ#'73#"''3255'67#5353635#35#35#;1  0 2G  777777Ð º  *'p 189„ÿêêÆ73#3#"''3267#735#ˆ]CM  P AIÆK(F#,N%gÿéôÈ 5;73#735#3353353533#3#3#3##5#535#53&'#535#367#o~~$h233;2====/;2+!8È8;   8 ÿéhÎ"(73#&'#5'67#535'6'6'&'[ !! #$ 5 6  Î<_T.7  mÿèöÊ 7&'''6Ì ! ' #+ &Ê’<Bœ“JEXÿéóÐ,AG73'67#&'&''6'3#5#'67#535#5333533##"''3255#&'¿)$-+   .  #!4  4  Ð F3   !æc?" 9I9:=  9 ÿí_Ð !'-7&''63#3#7'75#535#&'7'64   0 '" AÐ    E I3ÿèjÇ7#"''32654'7##5j  ,Ç8 ;Ëß[ÿéöÃ73#3#3267#"&55#'667#upp ‰(   ! !Ã2e i;3,3TÿðóÊ733533#3#53u448Ÿ!™–ÇMgVÿéõÏ"-73533#&''655#&'7''67'7''6h899/ ,6 J8   `   °" E =7$4M"$ % #*   ÿéóÉ59=73267#"&55#'65535'673#7&'7&''675#735#335+ 7T# &  És4&  />`m;$ ?xGV444”"õÐ767&'7''63#735#· "% LL))Ð0   7D#fÿéøÉ37;7325#"&547#'6553#7'7''75#535'635#335ä  LM     ÉH8&&#=0>n>"!?yT "Tk666 ÿéqÉ 06<73#3#535#5#35#3353353##53##"''325''67&' c_6    EQQ *d'  EÉ;;,<")    vÿéíÎ)73'67#'63#35#535#53#5##56 9  +  I 3IÎ& !* !!} xÿéwÇ!%+17=7#3#3#3#"''3267#55#5#35&'&'''67&'s"&  Q+ &"ÇN:‚&&/ÿòóÇ 7#3#53#735#3#735#73#735#òabt$CC!!&& !%% ǯÕ!98K,K,uÿéôÇ-159=CI73#676767'7&'#"''3255'67'67#735#33535#335&'''6}n5  !   #.J.  9 ÇP  " + %  00x   ÿéoÇ!%+17=7#3#3#3#"''3267#55#5#35&'&'''67&'j" J(  $ÇO8‚&%/mÿéôÑ PTX73#53'3#735#73#735#3533533#3#3#67&'67'5'67#535#535#5#35#±6}2'55.55N 5  +   (IÑ#+ ++    ,      NðÏ")/57'6'#3#3#3#&'#'67#535&'#67'6'6Þ 5.6ULLLLZ -/ "I %Ž 1 2 !6 9Ï        G` J     ÿèöZ"373533#&'#5'67#7&'''667'7&''6)%%  '¹ ) $  %(B  //   "  ÿé„Ð $*06<733#5'667#35#33535#335&'''67&''4'5+[  ! $7$N H  Ð cZ *C/  " ÿéìÈ$(,04767#533#"''3255##5##53&'35#33535#335š  G]   , .I.«   //8¥ =J nÿéõÒ!%+37;7'67&'#7&'3#735#&'735'6#5##535#35#— + 5.  "nn )   =====¢    B"  ""  2S S+ "ñÒ"(.4:73#3#3#"''3267#5363535'67&''&'7&'aa³³« «, |||– ‰  B  ? Ò< . k  K         ÿçô-73267#"&55#'665®  E! -)  ÿé}ÌEK73'33#673265#"''67&'#67#"''32655'675#535'6&'>            AÊ )BB!) + ,(%? 3,"|ÿèòÐ#)/5;73#3#3#"''3267#5363535&'''67&''4'¤4GRRU  U4449  5Ð L? 0ˆ b  ÿè‡Ï$1IP73533#&'#5'675#&''6'&''63&''67&''667#2..  !2Z   A  .  "    $¼,   ,    3     ‚ÿìóÑ'+73#"''3267#'63267#"&553'#3¢?   7 &$ 9Ñ o,` w9  …H7& ÿêÈ#)/5;A73#3#67'75#535#735#&'735'6&''&''&'''6l.**3>3++, )  $    ÈX 7  77 v ÿé‰Ì"7N73#35#535#53#563327#"&'37&'7'7'737&'7&'67': G-i8  Q   )      ÌXVb8"/M  % s  % ‹ÿéíÏ #53533#=#'#3355#ž)('¸..¸[<<<<=====ÿéŽÏ&04L733#3#53533'7''677&'3353#57#7''7''6767&J,,2     STffTT    Ï))6      -,Ž Q>      ÿëñÏ73673267#"&55'7§ ÏW\  U ÿéUÐ735'673#'#5'67# !    Œ!' mj"#1RÿêøÅA73#67&'##'32654''67&'&''67'767&''67#^3   ", 3!     )FÅ A"#B  13       ÿéhÈ &+/73#3#5##535#5#35#"&55#'7335655#35# [111  *111ȵµp 7#K29=†dÿèõÌ&Ne76767&'33#"&55#'6653&'#33#3#"''3267#735#5'67&'767#67#53&''67&'À      ' @<!   %    )E ,@    Ì    # +"94 8         JÿéóÏ/G733#3'67#73267#"&55'75#'65533353353#3#535#533”CCH9+-  6+‰-Ï     F:, /4PYML*%65$ÿêîp!73533##"''32655#'67&'ebb   e6 ~!]E @ %ZÿéöÏ '+7'67&'3#3267#"&55#'667#735#‘ ; 9f    >>Ï 3HA  G&) !&NÿéôÐ &*7&'''63#3267#"&55#'667#735#½ %& $ k   CCÐ # !'HA  G%* !&zÿëõÇ73267#"&553'#37#3Ž$  %+m@,NG  ÄyfSSSÿëõÇ73267#"&553'#37#3’# +) i>*NG $ ÄygTTT ÿôbÏ7&'7''675#53533#75##5#L"     2_--_,<>>>> ÿó_Ï 7''675#53533#67&'75##5#V!  / 6_--_1 (>>>>¡ÿéóÁ73#3#"''3255##5##535#¡R   !Á"p [““v‡"6ÿéšn 73#735#35#35#'67&'@UU222222 ;  n[? % %      ÿêô¥/7#673267#"''67&'#'66553&533&'7ì6     n € ,%(2"   ,;<2& $F  ÿçï &,;73#53635#35#'67'5673#535#3'6733267#"&5u%S////"   11SF @7& . YY 07?7 P];U. !"   ÿèò¦"&*.735#53533#373#53&'#53#=#73#735#J SSVV# 2ä6±‰YY88„   ‹fg ==2' >ÿø™l 7#3#3#535#35#5#—![66l/t#32ÿææ‘'73'67'56767567#"''3255##5] " 0  )& ##‰  /l=   _  C>a  I‹ž ÿèYÉ7#"''3255#'65535#35#Y     ÉÄ  :6& 6A`9'a' ÿõó^73#3#+¯¯ææ^C ÿñój 73#3#535#ÃWhçjWjSSÿïñc 73#3#535#-¦HfãgHcNN‚ÿñòÁ 73#3#535#†e(/p-)ÁªªÿéâŒ#73#"''32765#'6&''6<˜  Ž % #c6F JŒ a C    ÿçö‚7&'#5'6556Ö + 1 4#c‚K%(\~2,%)2$ÿèîŽ$7#3#327#"&'#67'535&'#ÚITM ( P*!&3 ŽEDŽ< + #(  ›B  ÿîò˜%)-1573#3#3#3#535#535#535#535'25##5#35#335× '/g0%%0g\Ë[j3%%3jNZ2$$$$8$˜E$yÿéïÇ #73#3#735##5##535#33535#35#{rrff??ZN0N0Ç::o o*? ÿé`Ð#73673#3533#7#5'675#'67# $  '' ¯! 73!  ÿé[Ð%73673#3533#7#5'675#'67# "  $ ¯ ! 83! WÿéöÏ 159=7&'#5'63&'3#"''325'#"''3255##535#35#73#ž!(  Q 2D  =  -  >Ï "/s  mt  1‹%8-\ ÿèmÐ #)73#53&'3#3#'67#5365#536'&'=!X"%% "#'4Ð 2 )    dÿèöÏ!%73533#3#&'#5'67#535#35#335q666/(%! '$/6.·F"HK&$FM"""v[ñÈ)73&'767#533'67##"''3255'67#z/ Ie1   )›     rÿéöT73533#&'#5'67#y34' %K #II($EéÒ"&*73#3#3#3##5'673&'5#5#35#‡ PQHHHHUª !5 AAAAAÒ K  # 0 ÿèð<7#53&''67&767#0¼&!.<+*6 -"; t+    ^ÿêïÐ.7'6553533#3'67#73265#"&55'75277; -!   •E;+ -5P)      „ÿðòI7#3#3#5#535ìFLLG[I#$ ÿéoÈ%*.73#3#5##535#5#35"&55#'735655#35# b646  .6 66ȵµp 4#K07?†L éÏ"73533##5'67#7'6'&'&'PBBB!"3^  hPP_Q-*U  [”ÿéöÏ73'67#'6'665&­7&  Ï!!  "a,B4&CÿéŸÈHLPTX7#3533533##5##5#'6553#3#"''3255#27&'7&''675##535#735#33535#335Ÿv_)1      -'%<%È7C7 5@`<@A -DS& &  ÿèí] 7#'655í½])! 04ÿèÜC73673#"''3265#'67#:8W H7- 32 5 .   ÿéóc!'73##"''3255#535'2'6'&'Û )2jj hh).aF  v  c(  '   ÿéôa%+173'67'676573&'3267#"&57'6'&']'1 9 *+""  #!M ¡ a#%)  ".   [     ÿèïh#(73'73#&'#5&''67'67&'#367_`3 "6  8 ,!3")D Y  *./   Qÿéô\73533#&'#5'67#[=@7+#$(2JBBÿçV^ 7&'&''6.  9  ^     ÿæð~37;A73533#33##3#&''663'7#537#535#535#535#353567!VZZMg…5 " 1,@".07C3OffMMVi;;;h6 v       +ÔÿéèÐ73#ÔÐç q€Í(733'67##"''3255#'67#53'767#b !    * KÍ       }qòÐ73#&''67&''667˜J    Ð     gðÐ"7=73533#3#3#67'675#535#535#73533##"''3255#&'***01++4@++12*q>  >  Æ       )  $  oÿíõÎ'+73#"''3265#'6#3267#"&5535#•J  @  ;.!-( Î p(U$J18%uÿèðÑ#73#3#"''3255##5##535#'6”Q1-  - Ñ (^  Fex( {ÿéóÏ73#3#5##535#535'635#ã22)?*22'6??Ï+&dd&&¶1wÿê¿Æ75#53#3#"''3267#7«3G/1   3&K(L5LÿéïA 73#3##"''3255#'67&'*¬¬Þe   e> -&x A       JÿéòÏ'+/3873533533#3#"''3267##5#'67#735#3353355##55#R*--7  $ . & . &*=+µF!2 RR1!%G T!!!ÿè·ƒ'+/3773533533#3#"''3267##5#'67#735#3353355##5#/''2    &%*(/?(i-  11 11GÿêõÉ9=A73265#"&547#'65535#'273#67&'7&''275#735#335Þ Y "!  &  ÉH8$$ +$;/?V>9 /%  /  TÿëöÏ?HL^73#3#3325267##"&55##535#535#5335##535#535#5335#536535#3735''67&'76N055  ' +##+++.&-a+& Ï 0a    <  #0 Éž–jCC     NÿéìÏ%-159=77'7&''5633#"''3267#'67##5##535#33535#35#ˆ   "+Q  Lg))>)g))>))Ï / OG/6 -Wq q*Cÿé…Ñ%)-7&'3&'73#67&'7&''5'63535E# C #(///Ñ Y.    ’4# ÿëöÏ=GK^73#3#;267##"&55##535#535#5335##535#535#5335#536535#3735&''67&'76i nDJJ  :=+88*<=+88*<A7'B“? 8      Ï1a    <  #0 Éž•jCC      +ÿéÕ9 7#5##535#35#Õ‚‚‚‚‚9P P &  ÿñòÒ #'7'6733#736735#33535#3353#) 4W/¯' Y d::N:ˆ::N:¶ää… "% s‹ IMK DóÑ"&73#5'673'3735#33535#3353#«-¯  +\d R c;;M;ˆ;;M;·ææ¥E2  ( ' ( ÿñxÏ #)733#5'667#35#33535#335'62*V    !2!+58Ï j`   -I7   ÿñqÏ!%+733#5'667#35#33535#335'6.'S  /(24Ï j_  -I7  wÿêïÅ!73#3#"''3255##5##5##5367#wx49 #/Å wwwww¡rÿé¯Ï 7#5'6œ  Ï©‚ 2ZÿêõÄ7&'333#"&'&'75#x  (% . ÄGf   W`ÿêûÄ7&'333#"&'&'75#~  !(% . ÄGf   W”óÐ!73#3533#3##5#535#'67#53¶05 ##**& Ð ##'' ÿñkÏ #)733#5'667#35#33535#335'6+% N  + &.0Ïi^  ,I7  mÿèõÑ!)73&'73#&''67&'#67#3#'3'65u1 7  @ : %=!¹    Egg- % OõÐ%+73673673#3#&'#'67#5367#7&'R " ,pƒ:$ /@* 0DG+¬  4  ÿíóx #'733#5'6367#35#5#3535#3#k @*­ 4@:&:9†9N:99~ææx  F+ (ƒÿéòÏ#73#3#3##5#535#535#536'&'Î ,''..--&&+7 (  Ï##::##  ÿñ€Ï $*733#5'667#35#33535#335'66,\  " $7$.:<Ï kb -G7  \ÿèôÐ *73#53&''67&'67&&5'67&'ª9†9  S +    #+ + Ð 6 " '   rÿèõÐ +73#53&''67&'&''667&'76µ0s/ J    #   Ð 6 " &    wÿèõÅ#'+/73#3#&''67&'767#535#35#35#35#35#wz3+, )+   +,5+++Å`   `=<~ÿêòÆ &735#53#535#33##"''3255#53&'‰NTggTN@   KK1  ža06  3 mÿéòÄ/573##"''3255&'#575#7#"''3255&'#575#m……<   j     Ä  &!{²+R  !s²*#$^pÿðõÏ-73#'3#7#53&''67&67#3533#3#535#”?A    M,,,7‚9,Ï}uo]/    j  nÿèõÏ3:73533#3#535#&'''63&''67&''667#€***5{3*M2 1  $ !  '¹-       vÿñóÄ#73#3#3#535#535#735#33535#335k,))5}5++,,D,ÄsBMÿèôÏ7333"&&#"'663367#53&'w U=I,./FA# Q,¤X ÏA3    9.šÿêóÏ#'+1FL73533#3#&''275#535#35#33535#335&'#63533##"''3255#&'›#%%  ' #. ;8   8  à  I I *,) #    IÿéõÏ(733#3#5353333#33#"&''673›66?–77+/  Ï  ==) !@  ÿîõD %7&'7&''3327267##"&5''6ƒ  e  { 4  D !0   4 &[ÿéõÐ%)73&''67&''667##5##535#J! %+$   =YNNNÐ       \\ \>+NÿéóÐ7353#5#535#535#33#3##533U'..&&'n%%00*¨(ç1*++*1ç(JÿéóÐ7353#5#535#535#33#3##533Q(//''(q&&11+¨(ç1*++*1ç(HÿèôÒ)/S7&'67327#"'#'67&''7&'7&'67327#"''67&''7&'7¤ KK     &$ /( YR   !( 5.Ò         G       RÿçõÄ73#3#67&'7''67#d‚‚ M-6 >= %<Ä:G  (/ *:qÿéöÏ 73&'73#3267#"&55#'655x1 2x_ '§s  f-554QÿéòÏ "73#53&'3267#"&55#'6655 >‘> @   1ÏOr  e-% #4 Xóœ 73#53&'~ eæj œÿññÏ73&'73#3#5#o  ^«¡µ#¡ ŠÿéòÐ!'-73##"''3255#'>5#53&'&'''6} dF   (!  Ni c€Ð  ‰@, !*AQ%, +',# ! ÿçõÐ )73&'73#'67&'&''67&'76b bÛ?) #y! /2 @&0= >&®  #   OÿóóÐ 73&'73#3#536'&'W@  ?•r8¤VA¤ G4>?5692 ÿòóÐ 73&'73#3#536'&'_ \Ñ Læƒd  ¢F8@?1;<1™òÑ 73#&'#'673#&'#'67  ÿèéŸ 37;A7#5##535#3&533&'73#67327#"''67&'#3#735#'6骪ªS7      S ==.%'Ÿ··Œ  # - 'ÿèé– 26:@7#5##535#3&533'73#67327#"''67&'#3#735#'6骪ªS7     T ==.%'–®®•„    + %bÿéîÇ 26:@7#5##535#&'3'33#67325#"''67&'#3#735#'6îhhhTG5     5++ ÇÞ ÞÀ¯ !'  &(2 -  ÿéYÏ733#"'#5##533255#,    Ï&t 7¯{Œx ^XÿéîÇ 159?7#5##535#&'3533#67325#"''67&'#3#735#'6îpppZ L8    9--ÇÞ ÞÀ®  & !,2,  ÿéä£-39?73#5##5353335#&''67&''67&'&'7&'&'€bžMd³žžD  2  x & † ‘‘) „g       ÿéâÏ.4:@733#3#5##5335#&'7&''675&'76&''&'&'lbb`›L:››? E*   a D ϵ µšˆ       #  ÿéwÏ +17=733#3##5335#&'7&''67&'76&'7&'&'5//-T%CC    3 Ϥµ“ƒ      % yÿéõÐ!&,17&'#3#&''67#535#5'63&'3655#335³ *' " " $*', *Ð! A"$"A c pÿê÷Ð159=7&'3265#"''67'&'#'66553'33#6'3#3#735#ä      0 AE''%% Ð  E9 .& (.H;. ) S#K!C%DÿéòÊ /37'6553'#33673#3##5#'67#535#53&'5#rjjG !"  ?!Ž+F4 6@a<) >>& E9äÏ#73533#3##5#535#735#33535#335$QPPYYZZQ==S<˜FF\E 7 8 8 X @ÿìõÏ#)/?E73533#3##5#535#735#33535#335&'7&''33267#"&5''6W;::@@BB;'':&h//B-:  L Z   Á577X8    *  +  ÿçƒÈ 06<73#3#535#5#35#3353353#3##"''3255#'67&'s#!o!#AVaa v1 2 O È == -; 2 /   ÿéí– 06<73#3#535#5#35#3353353#3##"''3255#'67&'ÖG7¸8F}%7%%7%% ««Úa   e:# y" – .. )       ÿéœÇ "(.7'6553'#33#3##"''3255#&'''6-tNNBSS^&  %R ;  ‰'E4 5?_>+$A  >  •ÿéóÏ73533##"''3255#&'•9   9 ›44„  # #BÿéõÒDJP7&'#67327#"''67&'#3533#3##"''3255#'655353'67&'Û   $*      L#   _U =Ò 6#3.' % :9=5I  D>- /:Uk 4ñÐDJP7#673267#"''67&'#3533#3##"''3255#'6553'33&'7'67&'ï9     t(&&31  + „  C ¸ .  &+&     #4  Q     ÿêœÑEKQ7&'#67327#"''67&'#3533#3##"''3255#'655353&'''6…   $    @   O$  Ñ 1##-5&& 9:>8O  K7 6kl›ÿéöÇ #73#3#537#35#35#35#'67&'žR K))))))  ) Ljˆ=@?.     ÿé‹Î 0C73533#3#535#3533#7'75#73533#3#535##53#67'5#'6'((.n-' A<$y#  ' ¾5 C#  11  ÿéÉ &*0673#3#53&'#735#35#3##"''3255#735#'67&'c,/u1"====b&   *>> N  ÉE+ % E2) &*  wÿéóÇ #73#3#537#35#35#35#&'''6~s0-e#.>>>>>>7  Ljˆ<=>,   qÿéóÐ73&'73#3#"''3267#'665#y6 .L@   -©&c#N,7=L _ÿéóÉ 06<73#3#535#5#35#3353353#3##"''3255#'67&'gŒ+%€%+N##dqq;   = g  É<<+;( %  ÿóU¾ 7#5##535#35#35#U¾À Ë7%\%\%ÿéÇ!%+17=7#3#3#3#"''3267#55#5#35&'&'''67&'z$ )  W/  ("ÇN8‚&&/ ÿë–Æ#'73#3#3#5##535#535#735#35#3#735#d(<<2S1;;(?? SS 99Æ8u u®I /ŸÿèñÃ7#"''32654'7##5ñ  +ÃB FÈÛ ÿé˜Ð(,073#3#53&'#53&'367#3#3##5#535#735#35#R5 $Š#2-"l-<<<<,HHHHÐ  6 ,P/1 ÿévÑ(,073#3#53'#53&'367#3#3##5#535#735#35#@& j& W"++,,"2222Ñ  8 -O/1 qÿçõÑ&*.26<B73&''67''6673#3#535#3#735#35#35#'67&'œD "    GS#7}5llJJJJJJ  1Ñ     . T< ! !   ÿéñÐ)-173#3#53&'#53&'367#3#3##5#535#735#35#Y&=Þ<$Z 9O1¦HggggJ~~~~Ð  6 ,P/0K ðÐ)-173&'73#3#53&'#67#3#3##5#535#735#35#W<</¥-W1,8EEEE5[[[[¼    D  ) ( ÿéèÇ 7#5##535#說ªÇÞ Þñ2 ή ,73353353#3#3#"''3255##5##5##537#?&&‚ œKD   /?§ ) 5 "11337GÿéëÐ &,26:73&'73#3#"''3255##53&'#367#'67&'3#735#`a. 3   ’1 .U* L  F 7TT..º †  oŒž (   ?WÿéóÏ &,26:73#3#"''3255##53&'#53&'367#'67&'3#735#£E!(  j&!A/  / &BBÏ †  pžB(     !<gÿéòÏ &,26:73#3#"''3255##53&'#53&'367#'67&'3#735#­=$  ]!9'  , #==Ï †  qžB(    !<ÿéë¦ &,26:73&'73#3#"''3255##53&'#367#'67&'3#735#_a/ 2  ’0 .U,J  E 7TT..•  k  Tqƒ     6 ÿéðÒ5=AEIMQU7'66553&'73#33##3#"''3255##5##535#535'35#35#3353535#35#35#35#7 ]VREEH  52EBBBBPPBV111w22F55F22F55yC3 0#\   R """h  * (  E* ÿê‰ÐCI73#3#67&'#"''32654''67&5'67&''67#53&'#53&'367#F,  (      &  . *  *Ð             MŠÿèöÇ%*733#"&55#'6653&''67&'#367à    X    Ç<  0$  (d2   "ÿôŒÄ 7#3#53#735#3#735#73#735#‡ccv%HH%%++ %** ĬÐ!87E%E%ÿôÄ 7#3#53#735#3#735#73#735#€]]p"BB (( ((ĬÐ!87E%E% ÿçñÒ>73&'73#3#'66553&'#367#3533#3#3#535#535#'6Za( =¼ 2'M2 T *GG@@O»Y==3¹  :/& "C)   ÿèñ¡=73&'73#3#'66553&'#367#3533#3#3#535#535#'6V d(8½ 0'L7 S )FF??N»Y==4  -% 5     `ÿéôÒ=73#3#'6553&'#53&'367#3533#3#3#535#535#'6± 2!m  7/&&!!+t5   Ò :0$ '+C D-ÿùcË736753#5'5372˲ˆ¥ ¤˜ÿù^Ë73753#5'537/   ˲ˆ¥  ¤—ÿùË73753#5'5367@"+  ˲‡¥ ¤—=ÿéõÒ >73#3#'66553&'#53&'367#'673533#3#3#535#535 D *‰ ) C# B 22--9E++Ò :.& #C EQ  KÿéõÒ >73#3#'66553&'#53&'367#'673533#3#3#535#535¨? &~ & =; ..))4…>&&Ò :/% "C EQ  ]ÿçôÒ5=AEIMQU7#33##3#"''3255##5##535#535#'66553&'75#35#5353535#35#35#35#ó1) )+  )%%0 9 00%7?)))À  S  """h  B5 .$]  (   E* ÿïhÏ "(.7&''6'675#535#53#3#67'6'&'4  8")##:!! /Ï – C>2ÿðîÏ+/73533673#5##53&'3#3#3#535#535#735#B   5±1 Ž=TTeÜcUU=ffÌ(( 3!!3 B@^ÿðóÏ+/7#5##53&'7353367#53#3#3#535#53'35#ði    6#Y#22B•@3311§3""3 (( g@@'oÿðóÏ+/7#5##53&'7353367#53#3#3#535#53'35#ñ[    2M,,9„8,, &&§3""3 (( g@@' ÿëmÎ &,7&''6'75#535#53#3#7'6'&'7 ;&.'%%=##3 Î   FC;9ÿë¾Î73'67#'6367'dO > " Î #&t  _ÿêõÂ73265#"&55#'655Ô )$µ §G.9%QQ ÿëmÐ73'67#'6367'.2  #    Ð #*t  uÿìõÐ#733#353#3267#"&55#5335#53§33.  .22ÐYCU-  3UCYeÿç÷Ð "73#'63#3327#"&547#‡T[ UUh TÐ  %'!0 kÿññÉ73#3#535#535'2â 995~4889ÉIOOGdÿçóÐ %7'67333#"''32765#'67#536 [X@  +< 0"#š $- _ 9M!@CÿéõÑ73'67#'6&''66|_  N % $!2;+#Ñ (%. 54!<ÿðSà 7&''6$+ ÃU 30-eÿéõÑ73'67#'6&''66Q  B !#& - Ñ '" F52";sÿéöÏ73'67#'6&''66œH ; # (Ï  !" H!22!; ÿézÍ73##5#'66556n )I!  1Í .ƒƒ<3 8E: ÿîtÌ73#"''3265#733'66D  G:&$ '—/W#;w"X  nÿéõÑ7'673'67&''66› I 0 $ )—(8 ! I53!;zÂ7#3#567&''67&'yTQd'    œÁ$  ÿê€Î73#"''3267#'6##535#.I  =;)Ε-€ %(WfF6 ÿéŠÐ(76767'67'67#53&'73#&''6*> ( !31C 4  ) JnJ    ! ÿéˆÏ$*073533#3#535#3#3##"''3255#&'''6233+i*2 bb x0   4d  @  ½6;  7  dÿèõÏ+17767327#"''67&''7&''7&537&'á:CB  " & 34&(  £  " $    4   `ÿèõÎ">7673265#"''67&''7'73#3265#"&55#'667#ß<    #"?Ž+    (      f6 <"&   ÿë^Ð7'673'67367'0   )   œ#(;  v  iÿèíÒ7#"''3255##53673#735#í  `" ;;¬¨  ±Ä )mMmÿèîÑ#73#3#"''3255##5##535#'6T22  1 Ñ (^  Fex( `ÿíôÐ'+73#"''32765#'6#3267#"&5535#€X K F+"-*Ð n( S J1 8%iÿéóÇ73#67#5'75#35#35#675#j‰*1)88888ÇvB< Ž R WsÿéôÐ7353#5#535#535#33#3##533x""Y##§)ç2,--,2ç) ÿéqÉ#'+73#&'#5'67#535'2#5##535#35#e &$  "*!)111111É %&oh h%9 -õÑ(@7373#3#3#'#5'67#5367#5367#35#535#53&'#673[\dalŠ2 )¤ (<3@Q%88;1S  !8Ä  78  k     MëÐ73#3#3#535#535#53&'736­ 5ZRRaÖ`RR[7  . Ï  @ëÐ"(.4733##"''3255#'67#536553635#7&'&'mV   w -!sq.   Ð 8   $ B&  / VNóÐ73'67#&''667#'6x` $/ ,2 $ Ð  '"   ?åÏ '3=73#567373367&'#"''325735#53#535#'3'67#0#5/    8#65"j,  }%y  "R4  *  5'…&-& ÿêöÏ/37;?CKOSW733#3#67#732767#"&55'75#'65533#735#33535#3353#53535#35#35#jbbreXZ+$ 15+-2K ŽŽ--=-j--=-Ì 1 1 Ï        I95 +7XF>& ! #&&& 6ÿéøÑ.26:>BJNRV733#3'7#73267#"&55'75#'665533#735#33535#3353#53535#35#35#…GGZ G35  %$ 5rr 0 P 0  %$Ñ   J?. ,UH<# ! !$$$ ÿêYÏ7367&''65''6,  ! Ï7 !  2 *@=) ’òÑ7'673#&'73#&'#'68   ?!KN( °     UÿéóÑ.26:>BJNRV733#3'67#73267#"&55'75#'65533#735#33535#3353#53535#35#35#”88I7') +aa(?( ƒ Ñ    J>/ 07UH<$ ! !$$$ _ÿéóÑ048<@DLPTX733#3'67#7326567#"&55'75#'65533#735#33535#3353#53535#35#35#™44D2$&  ([[%:% {    Ñ     J?. /8UH<$ ! !$$$ IìÉ %+1773#735#35#35#'3#735#35#35#&'7&'''6''6‰YY888888|ZZ8888882    ) e ÉY? # # ?Y? # #      HòÐ "(73#'6'#3#3#535#35#5#7&'¬'"ÿéíd 73#"''325''67&'y    9•da  B' "# $XÿéìÂ'667#533#"''32767#7#jg $  ' 'DZJ[ EJ_J ÿéõÏ"7365333267#"&55#'6767#<R   ?( #;‘ " „B%#7kÿéøÎ735333265#"&55#'67#€/ -(11—  ‰t-)jBÿéøÏ 7333267#"&55#'665#53z<  '  ,-Ï2—  ‰=IC;ÿòîÇ7#3#5ä¶ÀÔǯÕ6Ù®!73#"''32765#'67#'67#'6c l   P K. ( ®[?R!D3( 7 â½'-733#3#535#5335#35##5##5'66&'ƒ77L«L77%%%8%%6Z9$)'!½'  ' @0!!0  † ô®735333267#"&55#'67#•#  ! Œ""f  ZQ H5 ­#'+73533#3#3##5#535#535#35#33535#3356"$$""##" .    E  E ( & IТ 73##5#735#33535#335I‡9:'';'b'';'¢g++<C?æ73#3#3#535#535#&'F™C;;J§H::Ar !''!7  @ä73#3#3#535#535#G—B99H¤G99@%%%% DõÏ7333267#"&55#'667#536YQ  ?&% # 69Ï M  @!- $ ÿéâi767&'7''6j 7< !JS.iE  ") "ÿéõ“ 7333267#"&55#'667#536\O  =&$ "!79“f " Y)7-#ÿìí“7732765#"&55'75'6Å &+np * 3$X[:U“'  0% 5  'ÿéí„73#3##"''32655#535#2›Ccc  ddD„$6 1$BÿéõÐ737333267#"&55#'67#T"' , ¢.‘  €EG 1hšêÐ73533#"''3265#'67#¢$«%%lZ]& "T WõÏ7333267#"&55#'667#536ZQ  ?%' #!7:Ï ? 1&   +ÿéÕT 7#5##535#35#Õ‚‚‚‚‚Tk k'; ÿèõb7365333267#"&55#'667#=Q ?%' #!:P H  : (  ÿêî^!73533##"''32655#'67&'gbb   g7 ~!N6 1 !ƒðÏ73533533##5##5#<A<<A<° ÿèõ{7365333267#"&55#'667#=Q  ?%' #!;b Y$ L'0(@ÿéóÏ/73533#3#3267#"&55#'667#535#'6i;;G0  -H& È***O # R)1)#* # ÿéVÐ 73&'73#3#3##5##535# I<<<<>² V V6$† íÎ"73533#&'#5'67#7'6'&'Ž#%%  J  <  MM=C&L  TÿëòÄ7&'333#"&'&'75#p " )'1 ÄGf   W}ÿéë‰73733#"''32765#'67#ƒ5  "#e$$[  2G#=upðÍ 7'673›  M™ /cÿäöÇ!&73#735#3#7#5'75#35#35#675#zdd>>'ˆ /5&>>>>!>Ç:7W#!_7: ÿékÏ$73673#3533#7#5'675#'67# (.  .. ¯ ! 73" NîÏ!733#3##5#535#'67#53673#3}GG]]llK /= x„ 2Ÿ   nÿéóÏ73533#3#5##535#35#n899.E+8!EE222pp2~9Hÿÿ›Í733#3#5##535#535#h " "Í-.W a.{))ÿéîÏ73533#3#5##535#35#dddJ~Gd1~~222pp2~9 ÿó]Ï7#535#53533#3#=#% o----j33ÿéîz73533#3#5##535#35#dddJ~Gd1~~eS SL#"„ÝÈ 73#735#335335"»»%%7%%ÈD""""""~ÝÈ 73#735#335335"»»$$7%$ÈJ'''''"—ÜÊ 73#735#335335"ºº&&8%&Ê37É©73533#&''67#9<AC%" "$ 2 9„%%"*?íÏ !'=C73#5'677'6#&''6'&''&'3533##"''32655#&'E™ 2NPŽ  À  '   ^  ^  ÏŒ-  J          ÿéòI73673#&''67#ejZA JU F[6   *,   ÿéò $:@73'67#&''6'3#5'67'&'3533##"''32655#&'†Q-V J(B ;  Lc""   c)   1  "  ±9  OA) $   iïÒ!'<B7'2'3#5'67'&''6'&''&'3533##"''3255#&'Þ3I=[  ¹$`  `+  Ò e  ,          ÿéóY"(,7#53#3#5##5;5#35#"&55#'67#5#335#^QæUBš><š. ('š.qššJ XX 5  *  ÿéóc"(,7#53#3#5##5;5#35#"&55#'67#5#335#^QæUBš><š. )(š.qššT _ _ <   . €\îÌ(733#"&55#'66767#53&''67&'× $  AW    Ì    C   ^óÕ ,AG73'67''667#7&''3353#5#'67#535#3533##"''3255#&'A[  / -  u  =3Ob  b" Õ"    #o&      ÿé‚È (-173#3#5##535#5#35#"&55#'73335655#35# u&B$? B  6B BBȵ µp 1"J-5=„ŠÿéðÏ"&736732667#"&5#5##535#35#Š" #)  . _66666Ï    $zz-H€ÿêîÏ#733#5##5'67#&''635#­4(?0?&    00Ï :c UC  š2ÿéõÐ0673533#3&533#673267#"''67&'#535#7&'677H64       œ?6Á  ¿*$&  #+; ÿè•}%)-73#3#5##535#5#35#"&55#'733567#35#‡,#T%,I $T  C TTT}uvA  L  ÿéór"(,7#53#3#5##5;5#35#"&55#'67#5#335#]PæTA™><™- )&™-r™™aj jC   4 |hôÐ7&''67'6767767&'Ú )1 '  "„ÿèçc7#"''3255##55#35#ç   <O<<…ƒÿéõÌ ,27'2'6'&'&'3533##"''3255#&'æ *=2'      F   F  Ì  =H  E OÿéóÏ/?733#3'67#'665533267#"&55'753#3#3#5#535’AAH d .<*" ?LZZObÏ    F:, *P#  6$# ÿöZÏ7&'37'5#2  4 !Ï ?n  p ÿöeÏ7&'37'5#3  6 #Ï ?t  poÿéñÑ 473&'73##5##53&'7367533##'6655##5##5t/4x}])-  ¹  #((   2; 'XX?PÿéìÑ673#3#5##53&'#53&'#36#"''3255##5##5353~b' <°; &\BW 4 4  87KÑ (( #D<  $UUCVNÿéðÑ 473&'73##5##53&'7367533##'3255##5##5R> D™ž{(  &  /; ''º  #((   2; 'XX?Pÿê€Ð 673#53&'3673#5#3#"''3255##5##535##53&'H-m,  )  ("Ð  ))8  "YY?P) ÿéì¦673#3#5##53&'#53&'367##"''3255##5##5353ƒV8°8 Y=Xz  66I¦  %% 7 8.  DD5Fÿé‹É$,047#3#327#"&'#67'535&5##5##535#35#…'+' '  H%!\GGGGGÉ0  n0=c d$7ŒÿéóÐ733#3&''67&'#5367²))'   "Ð$ 7(  . mÿèôÏ733#3&''67&'#5367Ÿ==8"% " & Ï"9( 1'"˜ÿèõÐ733#3&''67&'#53767º$$$       Ð% 9(  3' MšÏ%9I73#35#535#535#533#5##5356737&''67&'76''7&'76)V" j  #    œ  U&&N  /    #     ÿé™S737#"''3255'757#*V5 B=S    TÿéóÉ#+/373#3#327#"&'#67'735#&'##5##535#35#TŽ5;1 # <% ff51zfffffÉ0  M.=d e%6pÿéóÉ#+/373#3#327#"&'#67'735#'##5##535#35#pw,/( , PP'#dMMMMMÉ0 N.=d e%6lÿéòÉ$,0473#3#327#"&'#67'735#&'##5##535#35#l{/2+ . SS(%gOOOOOÉ0 N.=d e%6 ÿþgÏ767'67'6767'6U "* $,/§5$  0*`   ÿþVÏ767'67'67676'6D"   !#§8" 0.n  Mÿê¡Ï73533#&'#5'67#Z    ¡..ho (8&ÿéê %7#3#327#"&'#67'535&'#ÛENE ) T,#'6 AG8 #  9  ÿðóm73#3#3#535335#Ï]LLhæ**\m"";;W&ÿçÜ‹!73#735##"''32655##535#35#.¦¦›  ŽŽŽŽŽ‹/+Q  i ( ¥ÿéíÄ 7#5##535#35#35#í"""""""ÄÛÛ=+f*e*OÿîóÈ&+37;?733#'3267#''67&'767#'67#37#3#53535#35#35#_u      %%38`¤%&È&/" (     ( *> j@@@..... ÿñ•Ç',6:>B733'3267#&''67&'767#'67#337537'27#775#'#7l      %.Tr;I'<Ç"(       " $4 ¯C. 3/ !$$*‘ÿéõÐ!'767325#"''67&''7&537&'ò4       ™ " / "7&@: ÿïîÉ!%)73#3#3#3#535#535#'6735#735#35#)¯KXXPPaÜfNNB  8O‡‡‡‡É\ 68LÿïóÉ!%)735#53#3#3#3#535#535#'6735#35#h(7€4::44A›E440 YYYYg \\ Y8[ÿïóÉ!%)735#53#3#3#3#535#535#'6735#35#t!0r-44//:>..( MMMMi[[ X9yÿïóÉ#'+73#3#3#3#535#535#'6735#735#35#Œ_$''##,o/##'8888É[  59 ÿð“É#'+7'75#535#'6735#53#3#3#6'35#35#9E:''! +e&++''G????   XX–6 ÿé¢Í #06<B73#5'675#73#5'675#&'7&''67&''6'67'6= ,C= +.T  (" ) # ' '+ 1 35 'K LÍS  S      P     +‚ÿêóÐ"(73267#"''67&''7'3767&'Ü   /1   j%' *0D$  "vÿéôÐ,273533#3'33#67327#"''67&'#535#7&'444@@?      ’=4º  ¿445(: 'D  ÿ혇#'+73&'73#3#3#3##5'65#5#35#1(-((((,b 5$$$$$…  ]"!KÿèóÎ/3733533533##5#3#3#&'#5'67#535#5#535#gAl;A5$ $'%1?2ZÅ++: !99L+GÿèõÎ/3733533533##5#3#3#&'#5'67#535#5#535#eCo=C7% &)(2A3^Å++: !:: L+ ÿèeÏ%+73#"''255#'67#535365#7&'4'9  $$  Ï ° N>- /2L[::3H ÿèëÉ 7'66553'#36 É¡¡–2F6 1%`3"6ÿév“7'6#5'6`   “  [H iÿéó‘/373533533533##5#3#3#&'#5'67#535#5#5#p >]39- +5* [€' ,(  7 ÿèëÊ 7'6553'#3.Щ©8G6 6@a- 1ÿêo—7'6#5'6Y   —"XD gÿçñ˜.26:>BFK735333##3#3#''27'67#537#535#5#5;5#33535#33535#33567#u00 04CM '+,%(#40 /M/R##4"$ , !     !  6 < _ÿñóÐ "27#5##53&'73#67&'7''67#3533#3#535#í`9 Hy;  +1'533@”@5µ##  )   ILÿéêÉ!%)B733#5##5##53&'767#5#'#35#'3533#"''3267#'67#53`…))(<   hw)((=)<(E 3 < 25:Ébb  ;2+ -`ÿéíÉ!%)A733#5##5##53&'767#5#'#35#'3533#"''3267#'67#53rw$""5  [h"""6"6">* 6 , -2É bb  ;3+ -HÿéóÏ4873533533#3#3#"''3255#353#5335##535#535#35#P"0"")LE  1W0DL*"600»j  T<->>-!99M m  (+3>R!&:!#D, I$ dk a .(,+¼2!5./321)   1H   ÿïeÏ 73#'6#53#3#67'5#53-.6 <   Ï 5%7 E ÿï_Ï 73#'6#53#3#67'5#53**2 8  Ï 1&7 F ÿïgÏ 73#'6#53#3#67'5#53/.7 =   ""Ï  4&7  FUÿéóÏ4:@73533533##5##5#&''63533##"''3255#'67&']..C& #!% 4!211  2_» ! /:  5 $  ÿéòÏ5;A73533533##5##5#&''63533##"''32655#'67&'3L33L3n12 5108 P6ONN   O!‡» ##09 4 #IÿéóÏ$(,173533533##5##5#3#7#5'75#35#35#675#N$3$$3$Ÿ8@)MMMM*#MºdpBDÿéñÐ$(,173533533##5##5#3#7#5'75#35#35#675#:F::F:Ö!$KW% 5llll;1lºa nBC^ÿéöÐ$(,173533533##5##5#3#7#5'75#35#35#675#` -- 29$CCCC%CºcoBCyÿéóÏ#'+73533533##5##5#3#3##5#535#735#35#z d'3333)>>>>½[,,69 ÿéóÐ#'+73533533##5##5#3#3##5#535#735#35#8H88H8¬LiihhK„„„„¾[,,67 ÿéðÐ8<7#'6553&'733533##3#"''3255##5##535#5#53#3ïºc8<'''E  11E(''P<<»MA2 1W#%' ! Ï  /$”âÏ 73353#533uDÄCÏ(00ÿéòŽ$*<73533#3#535#73533##"''3255#&'3533#67'75#)))0u1)t= = y.**6C5.|W  S   FòÐ%+>73533#3#535#73533##"''32655#&'3533#67'675#)))0u1)t= = y.**5@.ÂC >    ÿéãm"&*73#3#"''3255##5##535#53635#35#tQIX   DDXJC3‚‚‚‚m:   &&!/ :   ÿéóÑ#'+17&'3673#3##5#535#735#33535#335'&'x  ?{  MhhiiO;;P9‰;;P9‰  Ñ ,]..7;g`ÿéóÑ#'+17&'73#3##5#535#53635#33535#335'&'¢  C 6AA??6PO##6#Y##6#^  Ñ^//^?<g yÿéóÐ#'+17&'73#3##5#535#53635#33535#335'&'±  7 -4433,B>,F,L  Ð]//]A<gyÿéóÏ .47#5##53&'7&'''636733#&''67#7&'ëG+   *2) " ! &%&S·(*   8-5,'0  ?ÿéòÏ 47#5##53&'7&'''6&'36533#&''67#ërB2 O  `AJB4 3C B >·),    ,/,*ÿéˆÍJR73#35#535#53#563325#"'&'7&'67'5&'67'53753745&'8 A,c8          2 Í [Wa-(#B   + * - u    !WÿéóÐ(-28>7367#'6733#3267#"&55#'67#7367#335&'&'d@+ 1+*   1 &*#6(J$$##9265™  8  %   R    \ÿéóÐ(-28>7367#'6733#3267#"&55#'67#7367#335&'&'i=) /*)    0 %(!4&F##"!6144™   8  %  R   /ÿéê "&*73533#&'#5'67#7#5##535#35#35#<    ®2222222~""[Z#+-°°0LM?ÿéëÏ"&*73533#&'#5'67#7#5##535#35#35#D    §.......›44nl (6;ÛÛ=+h,h+ ÿéæ‘"&*73533#&'#5'67#7#5##535#35#35#'   #ÒHHHHHHHr RT",* ¡,DE[ÿéìÎ!%)733#'#5'67#537#5##535#35#35#x  t"""""""Î3lt$(6)ÛÛ=+h,h+ƒ4õÊ#(733#"&55#'6673&''67&'#367Ú   Y   Ê!  % C    1õÏ!);@P73533#3#535#733#"&55#'667#5##53&''67&'#3677'5#'667333&a(3Ë  PzY   `    Ä   !  % =!"    &   #  ÿé†Ï+73533#3#535##5##53#7'5#'665,,,$[#,lH>>?   À05  6!  ' ÿéò]!73533673#&'#5'67#53&'I  /Q$3 9%!; ;N6 [    #;;$  RóÏ4733#3'67#&''67373&''67&'767#F,,(UK=     F\  ! DÏ 1 %         ÿézÏ373673267#"&53#&'#5'67#535'6! )&S ((  $/0Ï    :@ nÿéóÇ7#5##537#53#3'>&'ç<"/u3   ¡‚pqƒ'A$!  ! ÿéÊ $*73#3##5'67#535#735#33535#335&'a',, (0(&:&  ÊeX> ;CZ wÿéóÇ 7#5##537#53#3'>&'è7 +m.   ¡‚pqƒ '@&  " vòÊ7#5##537#53#3'>&'ç7!+m.? ¦tbcu!)@ …ÿéóÇ!7#5##5367#53#3'>&'é4)h+  ¡‚pqƒ  '@'  !ÿé„Ï8<BIN73533533##5##5#3#3#"''3255&''7&''#535#3355#765#655#n    . " »l    /ƒ; -  6 iÿéòÏ>BHM73533533##5##5#3#3#"''3255&''67#''#535#3355#655#i!!!!!!%#      $%5 I»l    *  0ƒ; 0 1"ZÿéôÏ?CIN73533533##5##5#3#3#"''3255&''67#&''#535#3355#655#Z&&''&&‘+(        )+; Q»l    *  0ƒ; 2 2#IÿéóÏCGL73533533##5##5#3#3#"''3255&''67#&''655##535#3355#I++,,++¢0,      -0A »k    *   %rƒ: 2F5ìÏGK73533533##5##5#3#3#"''3255#&''67#&''67##535#335K)())()¦2,     -7G¾ 6  "    9H 3IÏ 7&'&''6$  +Ï  $    @ÿí”Î:736733267##"&53#3#&''67#53655#'6K    - " Î       †ÿéóÅ1733'67#3#33#"&''6735#53&'767#–Q     $/  :Å +4 !C! f  ÿéuÐ873673267#"&53#3#&''67#53655#'6 %  :$(  (-Ð         sÿéóÏ'+/735333##3#3##5#535#535#535#535#33535{)--118811++((//)<»''''& ÿèöÒ6:>CZ`7#367#533#7#5'675#'6553&'73&'736735#35#675#73#&''67&''667#ôÆ72G "'   .   €(((((\9     ±-JV >. /9T   a33 {9  ( ) ÿèôš4;VZ^b73&'73673#'6553&'3#&''67&''667#'367#533#7#5'635#35#35#75#9 ,  "À„9      ;>R $% ,,,,,,™    6-" "*= #      4 ; ! #âÏ 73353#533vDÄEÏ **¤âÏ 73353353#DDÄÇ# ÿéõ $(,27Sls„73#33267#"'"&55'67##5365#'#33655#35#'3#&'#5'67#535'23673&''67'67#367767'74''6¨+,    %!)&K *&  #*)/ /   + m    T+  !, ^+" D     _       ÿébÌ #7&'''6&''6#5##535#J      /Ì   -__@03ÿýÈ­)37;?C73673#'#326547#"&55##5'67#3533&'#35#33535#335@->' )   "$   )A)—   A   M "*,:ÿùʰ'+/57&'3&533#67327#"''67&'#3#735#'6³gJ*)     J::-#'°   )$+30ÿèæÒ7&'3#"''3255#3#@ <{   gQÒ Âª¹7ÿîȪ#'+73673#3##5#535#53&'35#33535#335e  3????4!!!4!U!!4!ª   W''W 96^ÿèôÏ &,77'5673#3267#"'&''3&'&'r Q$53   32  ih É  4!)"FE2•Lÿêðž$*767'5673#3267#"&''3&'&'`  U':7  ":7  C?  ’ $1,-7%bEÿèõÏ73533#&'3##5#535'67#QAE8) *$$##()4£,,K#)Q]00]L.-E?ÿéôÐ#'+#5'673&'73#3#3#3'353535s # -  870000955555 -3!"!™!!2""4!! ÿé¢Ï48<733#3#&'#5'67#535#535335#535'273#335#335y- 18876 ;;A*‡B># / ÿé§Ï%*.26<B733#3#5##533##5##5'67'3&35#35#35#''67&'H66=a7RR`D3$$K @1DDDDDD  Y Ï "";] \ )  ' ' & n    Gÿé”Ï#'+73533#3#3##5#535#535#35#33535#335G  (  ½b..b;B8 Ç®'+/73533#3#3#535#535###53#"''32=#35?67700>=//6fJn  JJ¦  pXD ' .ÿêó«-159=AIMQU733#3'7#732567#"&55'75#'65533#735#33535#3353#53535#35#35#wWWeQFG$ '$ '7uu!!1!R!!1!¨ ()«      ?4( )/F:6!  dÿðóÏ73&'73#3#5#dD  5hau¡ŒžN%íÐ"&*7'673&'73#3#3#3##75#5#35#m $./))))1m>+++++ (5   e%%UÿéôÐ#'+#5'673&'73#3#3#3'353535  +  .3----5s+++++‘ (-!"!™!!2""4!!NÿèõÐ  $,07&''63#'66553&'#335#5##535#¢"# "#&2 ,d 59QQQQ@@@Ð K." #A #  .D D*XvóÐ<73533#3#&''67#5365#'67#5365#53533#3#&^  n    Ä     #      ÿéaÍ73&'73#''67&'767#  "   6£,$#"(  XÿéíÊ  $CGMQW7##535#35#7#"''3255#535#5#3533#3#&'#5'67#535#35#&'735'6›1ƒ   1 K+++%%   %+  #  ÊEœá ' )Ì „E '  6  #' 6 4   AÿéõÏ73533#3#&'#5'67#535#^6<BJNRV733#3'7#73267#"&55'75#'665533#735#33535#3353#53535#35#35#pNNh[GH %  01')2 K(ŽŽ--=-j--=-Å00Ÿ         9/$ !@;0 ÿçës*067676767&'7'#"''3255#'67'67&'''6N!39M)  !  ++,  G! !R -'X #  $     G   ÿèï[+177676767&'7&'#"''3255#'67#'6&'''6Y . 9CS( ""   .-. ^#!!H & #Z      :   \ïÐ&*/3873533#3#3#535#535#3733##7#7#73'3373'#337YYYOOeßfKKY ™"%†360~3+=0È    <   ÿèðÑ/37;?CTX\733#3'67#673267#"&55'75#'65533#735#33535#335#"''3255##535#35#ibbs _'G & 0",/5G)££77H6~77H6 uuuuuÑ       L?1 0:W@5 >  O ÿéõÐ-F7373#3#3#&'#5##5'67#5367#5367#673#35#535#53&'Wahgo7  v 3@7?PC  //v//6:¿ ^ Y A     hÿèõÎ/3733533533##5#3#3#&'#5'67#535#5#535#}:Z16.  )3)IÅ++: 65L+bÿèõÎ/3733533533##5#3#3#&'#5'67#535#5#535#x;^490" # !+6+LÅ++:76L+fÿí›Ï73#3#5##53635#5#{$$Ï I^È E'Ž++ ÿé}È (-173#3#5##535#5#35#"&55#'73335655#35# p$@#= @  5@ @@È´´l / ,M*2C lÿê÷Ï7'2333#"''67#53&'à 1F: (/ 9EX1Ï;  /@}ÿðóÏ73&'73#3#5#}9  )NEY¡Šžÿé|Ï#+/373673#53&'35#4'735'6#5##535#35#*   l  &  ;;;;;Ï RR Z4 44;ff#7 ÿìô^%*/7&'#3#3#53&'#535#5'6''35#367#‰ -9 /Y 4Þ5Y0 GT $3%^      ; vÿéöÏ/3733533533##5#3#3#&'#5'67#535#5#535#‡4R,1*  %.'DÆ ++9 43K+ÿçðF#73533533#3#535#35#'67&',G,,8Ü7,?GG$*+e(' '(< "  „òÎ 73#'6«JVÿéôÏ!+37;7333#"''67&''667##53&#5##535#35#|% 28   P SSSSSÏ $    ,\G` `$2 ÿécÏ"73533#&'#5'67#7&'7'6     G  ~QQ TZ",Q]ÿéóÏ!+37;7333#"''67&''667##53&#5##535#35#€# 03    K MMMMMÏ $     +\H_ _#2aÿéñÏ *.B7&'6'&'67&'63#5##53635#67&''67&'«   $   h   8J\%\\    Ï   9} } lQ      cÂ73#3#67'75#535# Q%-Â:;  B:YÿéïÏ +/C7&'6'&'67&'63#5##53635#67&''67&'§  &   n  ;Nc(cc    Ï   :} } lQ     jÿéñÏ +/C7&'6'&'67&'63#5##53635#67&''67&'®   "  b   4FW$WW    Ï   : } } kQ    ÿñl¿73#3#5##5'67#35#\) 2   (¿#r g %:’DÿèNÉ7#"''3255#'65535#35#N     ÉÊ ?1( +5v;)d) ÿèÓÉ7#"''3255#'665535#35#Ó | xxyxÉÄ-,$ 4"`?,k, ›ÿèíÉ7#"''3255#'65535#35#í ÉÉ ?1( *6v;(c)GÿèšÉ7#"''3255#'65535#35#š ÉÉ ?2' +5v;(c)EÿèîÉ/377#"''3255#'65535#35#7#"''3255#'65535#35#˜   ˆ  ÉÉ ?/* ,4v;(c)MÉ ?/* +5v;(c)RÿéíÉ/377#"''3255#'65535#35#'#"''3255#'65535#35#í   !   ÉÉ ?6"!>y;(c)MÉ ?6""=y;(c)ÿêUÆ75#53#3#"''3267#7A0D-/   1&K(M5L ÿçêÉ15:7#"''3255#'65535#35#'#"''3255#'665535#35#ê  10000,  / ....ÉÁ22' 6A`;'b(gÁ21( 1&`;'b(ÿøY¿73#3##5'67#35# L *$  #¿#hY $;‘CZÿæóÆ)73#3#3#535#535#3533533##5#'67#h„822>Ž<228- -* !ÆtDD+_ÿæðÆ)73#3#3#535#535#3533533##5#'67#l~5//:†8//5**( ÆtDD,ÿéð‘%)73#3#3#3##5#'67#535#535#535#5#!ÀVLL]088E: , :<4]LLUƒC‘//& UdÿéóÅ.73#7#5'75#35#35#75#733267#"&5e‡?  =  ň0,’##W#\$Zž! kÿéóÅ-73#7#5'75#35#35#75#733267#"&5l€;  9  ň0,’##W$]$ZŸ ! nÿéôÅ073#27#5'635#35#35#75#733267#"&5n€8"4  Ň0,“##W$]$ZŸ ! UÿèóÆ 67'66553'#3773267#"&55'75'75'6€ {TT/179  " /ˆ'D5 2#_>,   =ÿèóÆ 67'66553'#3773267#"&55'75'75'6l ‹dd79@B  $ &) #7‡&E4 1#_?,    nÿèóÆ 57'6553'#3773267#"&55'75'75'6‘kFF&(-/  'ˆ'D5 7=_>,   MÿéóÇ 73#735##53#3#&''67#536dzzRR6ƒ8IA, /5 .5;ÇF"Y $*-$ fÿéôÇ 73#735#365#53#3#&''67#yff?? 4/t2=6% & 0/1ÇE#v  #&  aÿéóÇ 73#735#367#53#3#&''67#skkEE!73z5A9& ' 204ÇE#v  $' !IÿíõÆ 73#735#35#3533#3#535#^……]]]]<==J¬L<Æk?EN&&Rÿîõ‹ 73#735#35#3533#3#535#a„„]]]];<>   AA gÐ%'&"A  =8 % 'ÿèÔE 7#5##535#33535#35#Ô…99L9…99L99E]]$5 @÷Ñ%+17&'#5'6&'3##"''3255#&'''6‚26 "Z$ OI6±P M~("!'J+.Ñ          fíÐ3733#3'67#&''673767#53&''67&'H)) D ;4     J@U    Ð  + !      YÿñóÄ#73#3#3#535#535#735#33535#335f666DšB555""6"X""6"ÄsBM ÿîô873533#3#535# VVViçiV%4ˆÈ"&73#3#67'675#535#735#33535#335f*,,3>++**B*ÈM   ./‡3ðÉ%733'67##"''3255#53&'767#Œ^(  '+  BÉ   3  0  cÿñóÄ#73#3#3#535#535#735#33535#335nx111>=1123Q3ÄsBMZÿñóÄ#73#3#3#535#535#735#33535#335h~555B™C555""6"X""6"ÄsBM ÿíôÊ"&*73#'65563267#"&553'#3#5#Û Keµµj0 .9 C<–--@.Ê C959W—! jF4"""KÿìóË!%)73#'6556#3267#"&5535335Þ 1BxxG;Q " ,Ë '@5 26^PE&  n####5ÿþï#'+/3733#3267#"&55#5367#'6#;5##;5#c6#.  -3.  "¯  T  T  0! ÿé÷Ñ$*.26:7##5'66733#5#3267#"&5567##;5##;5#u9 M H; '  3& > C99;;99;;$o   x  Ž  ,ÿèîÐ %73#53&'3#735#3#"''325567#‚aÞfB¥¥||$¾!(   šÐ  3:8 $ cÿéóÏ %73#53&'3#735#3#"''325567#ª?;$ooGG}  _Ï  2;7 & îÑ $73#53&'3#735#3#"''32557#ƒbÝdC¦¦||%Ã"*2•Ñ *0-   jÿéóÏ %73#53&'3#735#3#"''325567#¯;‰8"jjDDy  [Ï  3:8 &  ÿébÉ7#"''3255#'65535#35#b   ! Éà 91+ 6?`:(a( ÿçóF736533533##5#'67# C<??>8-@3 772 ©ÿéóÏ73#&''67&''667#¾'      Ï=,  .!,HÿéóÏ<BIOU1073#&''67&''667##"''3255&'#5'#535375#7675#7'6'&'¾'      !   % & E ?  Ï=,  .!,‹ $nf"0 DD'=F L    ÿèFÏ7'6#5'61   Ï)c #<ôÏ $*CIO7&'7'673#&''67&''667#'33#"''3255##5##53'67&''  a 7>     $f0 0  4 Ï     !      0/N :SSTdÿçñT.73#673265#"&55#3'67##&''67#âP% , '8 7SH1   -T    E/ "   oôÑ #)BHN7&'7'673#&''67&''667#'33#"''3255##5##53'67&'&  a 7<     $e0 0  4 Ñ       "0 444B   ÿéøi"&*.26:7#3#;267##"&55#535#55#7355#355#355#ÚEOO  < LLFi 2g3h=r>.=k>>.=T0  1JQ. §æË 73#735#73#735#[[55\\\88Ë$ $ ÿæñQ7&'3673#&''67#´ —_h]A I!3) AVQ   %  ÿèô— #*EKQ7&'7'673#&''67&''667#'#"''3255##5'#5353675#&')  \  5<    $"  00 @— ,!   & "g  RfL &‚++X °òÑ73533533##5##5# =C==C=Ç \ñ² #)BHN7&'7'6&''673#&''67#6'33#"''3255##5##53&'''6* Q A   ;  0& ‡/ /  %  ²   4      #) **+:    ÿéöZ"&*.26:325267##"&55#535#53#3#'#'5#335#5#335#35#5#;!MMF¿JPP4c88/=l>>>>/=   4 1OQN 2  NÿéôÏ;AHNT1073#&''67&''667##"''3255&'#5'#535375#7675#7'6'&'¿%         % % ? ;  ÏB#! - "(‰  "ha"1¡DD)IQ  I    ÿèNÐ 73&'7367&'#5'67#    &® "   ce 7ÿèòK 73673#&''67'67#37667K‚2 # /%G 1!5 ?U 5  =       † òÎ 73533##5#†-++-ŠDDmm`zÏ73#&'#5'67#535'6r&&  (.1Ï  !%u^ðÑ7367&''66''6®  (!  Ñ   ( ÿêõ[%73#33267#"&55#67'7#&'ãœp  ` %3p [3  (+  T)  ^ðÑ067367&''66'3#&'#5'67#535'6'6®  (!<&&  (.1F  Ñ   (*  !% |êÏ%+17367&''657367&''65''67'6D    )l    )‰  o  Ï             )ÿçñY$73#33267#"&55#67'7#&')­}g  X )N Y4 (- S%  Qðx7#5##5ð»x''rðÏ#73533533##5##5#3#735#335335>>>>>>ÀÀ&&9&&Ä  1>îg7#5##5î¸g()*ÿçñK%73#33267#"&55#67'67#&'*«|g  X (M K +  #E!   ÿéóÏ73533533##5##5#35#35# *h,,h*>hhhh¥****©©88ƒ8ÿòóÏ73533#3#3#535#535#'6? 9WWMMcájMMC  Ç667777 'eíÐ733673#5##53&'733#735#u  .²/ B™™qqÏ  -- -mÿéöË73533533##5##5#35#35#m55+5555¤''''¨¨99…:qÿòóÏ73533#3#3#535#535#'6‹ --''3~7''Ä&777777 ' ÿù}Î73533#3#7'75#535#'6$""(-7.&&Á ,,-4   8- (ÿéòÆ #73#3#735#33535#33573#5##533ãã2||""5!V""5!'¤¤Æ|G$$$Z%%%;©ª‡&cÝÆ 73#735#33535#335&··==Q>==Q>Æc9> ÿê—Ï37;?CGMSbh733#3'67#'65533267#"'"&55'675363#735#33535#335&'7&''33267#"&5''6C44@ [ .0  :cc&=&   .  6    Ï  I=/ 07T  0A' #        ÿêâ[733#"''3267#'67#536oa  ROIOU[ ?&=-ÿëîÍ #'+/37;CKOSW73#735#33535#3353#735#33535#33573#735#33535#335#5##53#53535#35#35#-¤¤88I888I8©aa(A(aa(A(¹·$×ppppppÍ5 4  4  ##AA   [æÍ #'+/37;73#735#33535#3353#735#33535#33573#735#33535#335-¤¤88I888I8©aa(A(aa(A(Í5 5  5  /îS7#5##5î¸S$$ÿîòO 733#537#'77#O tAã piO+  ÿèîÏ %973#"''3265'3#73#3#3#535#535#73#3#3#535#535#Ü ÊO "S![O "S!ÏÏ Êçã ÿèîÏ%973#73#"''3265'3#3#3#535#535#73#3#3#535#535#É   ¯PQ ZPQ! ÏççÍ Å4ÿíÊt"73#3#"''3255##5##535#53&'?@6   $%6CAt * AA/@4ÿõËq#73533533#3#535#35#'67&'>$ )—&.$$  L^-     0ÿñÊp*73#35#535#53#3267#"&55#'67#56n ((\&&+>'   0 +#p   H  $ DÿèîÏ%973#73#"''3265'3#3#3#535#535#73#3#3#535#535#É ±Q !R\Q  R ÏççÍ Å,ÿîÏr &873533##"''3255#'3#3#735#&'63677'7&'…(   (VQQEE$$P  A%/]G  C" ,   TÿéïÁ4873#3#"''3255#''655#&''655##535#5#T›*&     ,/` Á%Š  t (;   & !2 ¡%%%%ñÄ,07<73#3#"''3255&''655#&''#535#5#5#655#r"     $B 8 Äs    !1  !‰87,! z ôÏ &*.473'33#673267#"''67&'#7&'3#735#'6z?('    @c  O--«$$?, !53  :D&:  ÿéóÐ!%).4E73#32767#"&55#'67#53635#335365#33567&'7''69*(8  J6)%!#6#    Ð iI  NF!ãEzz&4J   +,/ 2  ? YÿîïÏ)-373533#3#535##5##53#3#3#53&'#735#67#[?@@75?“qaa x*–*TT6* +&& 2 ? ÿëuÏ*.473533#3#535##5##53#3#7'7&'#735#767)))$Z")gD;; W *5 33 Á,#' 1   #  dÿîïÏ(,173533#3#535##5##53#3#3#53&'#735#7#f:992v0:‰hZZ p %Š'LL2% +&& 2?tÿïðÏ)-373533#3#535##5##53#3#3#53&'#735#67#v222+g)2zYNN d !{"AA*  +&& 2 ?ÿèñÊ#'=73#3#5##5##535#3#'3#3#'3#3#3#67&'7&''67#ÂWdPPdVu;;f>>g;;g>> ´´â‡49IRBÊ.>>..     XÿèñÊ#';73#3#5##5##535#3#'3#3#'3#3#3#67'7''67#h~7B//A4N!!A @""A uu˜W  -3 +Ê.??..      KÿèñÊ#'=73#3#5##5##535#3#'3#3#'3#3#3#67&'7&''67#XŽ?J77J=X%%I$$I%%K&&ƒƒ¥\ ! 280Ê.??..     iÿèòÊ#';73#3#5##5##535#3#'3#3#'3#3#3#67'7''67#vq0;((;/F99:hh‰Q  *. "Ê.??..     ÿæò¡#'5<73#3#5##5##535#3#'3#3#'3#3#3#''67#&'#6ÂVdPPdVv;;e;;f;;g<< ¹¹ä/  IQ7© G5¡ '11' $     ÿè~É#'<73#3#5##5##535#3#'3#3#'3#3#3#67&'7''67#Y&/.$8...RRk; " É-??--     ÿèîÂ73##53##"''3265'´´QgÝb  ÂAl  ÿéõÑ-157'67333##5##"''3255#535#535#53673535N 0] 2=   NNhh?Y ===¬!  .9 #   +.=ÿëò­"73#3#353#5##5335#535#'6gvCNN.p/TT) ­ *=.M M.=* 3ÿêí 73#3##"''3255'67#5335#E¡*11   (B =&GFe 0H  >3*6$09ÿæó® $*07373#33#53537#5#3535#5#'67&'FIDG?º:Gƒccccccc ! Y   dd '  # #       ÿèîÊ 7#'655î¿ÊTG5 6Aa3ÿæõ $(47&'#67'5'63&'73&'35#35#67&'‹-/ u 8#'aaaa5 -/ 3T !    ;gìÁ736533&'73#'67#&'?C!W >2 ;i$ "!¦      Aÿéæe!%7#53#3#5##53635#335335#3#3ƒB¥PW~;+'/(////V \\O8 88 <jí¯7373&'73#&'#'67#CB%C,8<.7¢   -ÿê÷|048=A7#3267#"&55'67#53673#7&'7&''6'35#335365#335° " ,>B 37H   N++?1o')=4"!   C  C  5 # =ÿéê‘73##"''3255#=­G Q‘y  t=ÿèë­ %73&'73#3673#3##5#535#53&'ECG.&  /NEEEEL/ —   ..:ÿñò­&*.2673#3#3#3#535#535#535#535#'25##5#35#335Ø $JKA™DMI!H0  4 ­J+<ÿèó¬#'733#3#535#53533#3#53#=#5#¨((7·0(((((A}YYY¬wff5$9ÿêó«7353#5#535#535#733#3#3##?288--2b;;77>>‘Á.  , /7`ï¯7373&'73#&'#'67#>E(F0;?1<ž  $# KÿèÛ] 7#53#5##55#335#355#_,++?,>+?, ac H2ÿéæ«"&*73533#&'#5'67#7#5##535#35#35#>    ¨2222222†%% [c!-/¹¹2 R R -ÿé÷¯973#&'#5'67#535'6'3#&'#5'67#535'6Ü )$   *9    % %¯6)WX)!4  _`$+<ÿçõ£,>BF7#'7367#53#3#33325267##"&55'6'3#67&'7&''735#35#µ(Z&(   "+qK8 &&&&J( 7  '$!`4  z>.ÿêñ­4:@73533533#3#535#35#35#35#73##5#'66556'67&':! c!!!!!!‹ !=  )z 4  —TT33e ss4. ,9/   2ÿék³ 7#5'6Z  ³”u(_ÿêó³#)/5;73#3#3#"''3267#53635#35&'''67&''4'›:asson$OOO  I  A  ³?  9 %s "  S   ÿé÷Æ73265#"'&5#'6655Ô  ‹ Æcb#,ˆRE4 1$^Gÿê·©7#"''3255##5##5353·  /ˆ\  FŒŒas!!5ÿñÁ¦73533#&'#5'67#C//*!  %*}))6*Z[-"36ÿý¡733#3#53533~%%0Œ¡2Ltt=¼’767#53&''67&'YMc' &p0# ÿéçÐ73#"''32765#'6D• ‰$Ð ™7"u)% ÿéíÍ7'673#"''32765D "    ª#.—" ÿéçÏ73#"''32765#'69¢  — Ï —7 tÿể73#3##5#535#'6'&'4‰=LLOO9w  d  ‰B99B  ÿëåÏ73#"''32765#'6&'O% (!!Ï ‘4n$& # Æ7333267#"&55#'67#536P8   ' +Q  D$+ 3 +'Ãv73#3#=uu˜˜v()³’7''6767&'‰17!$' i$*  "CA-²… 7&''6[_6F J….; ¹}73267#"&5536ª /8 "  .&1}  X! ÿøÂ“!73533#&'#5'67#7'6'&'MED% /4AŽ]  \77@D&$F    Æ›73#3#353#5335#535#'6K_:JJ&Š)JJ  ›!!*)!! ÿëïÄ73##"''32655#ß_ lĬ §ÿêóÄ73##"''3255#V   (Ä­  ªYÿê©Ï73533#7#"''3255'675#[   ¥**2  I  <:`ÿèõŽ #73#3#537#35#35#35#'67&'j…?&'g/!   Áš‰Š›'O0&  #+1  ÿêzÏ7'655367&''6E 0 ;2+ .BhC …,!  oÂ73#3#67'675#535# \$""*4""%Â:< A:w¼ 7#5##535#35#35#35#w@)))¼­ ¹H666777q¼ 7#5##535#35#35#35#q<'''¼­ ¹H666777ÿèóÂ73##"''3255#r,  2¯  ¬ÿéíQ73##"''32655#Ú]   iQ; 6Wÿêïƒ73##"''3255#W˜;   Iƒm  jÿói¾ 7#5##535#35#35#i0000000¾ÅË7%\%\%ÿêð{73##"''3255#ße   f{d  aŠñÐ73533533##5##5#=@==@=· ÿïuÏ 73#'6#53#3#67'5#5376@ G&&))Ï  2&8  FaÿèõÈ #73#3#537#35#35#35#'67&'jƒ44x/:RRRRRR Gȇ‡?>>.      ÿêcÄ73##"''3255# V  "Ä®  « ÿê{Ç '73#735#73#735#3#3#3#'7667#7#11*00F__n>5 9ÇD"D"@  !QñÑ*06<733#3#67'675#5353373#3#5##537#3'6''6&'D), !D1:r40?!-.9 1‚  ° Ñ  ( ' " 2"#3 '6)   fÿéöÇ"7#5##5367#53#3'>&'éD%38 !!  ¡‚pqƒ  ': &   ÿélÅ$733'67##"''3255#53&'767#U     '  ?Å  ^  Z   ÿé„Ï#CGKO733533##5##5#533#735#73#735#3&'73#3#3#3##5'6353535,22(118($!!!!%S Ï  #.." I  ÿè|È#=E73#3#5##5##535#3#73#3#73#3#3#"''#5##5##537##3255Y$/0$ 045Cf**   #*IÈ&88,)  ; 011BQ 0 ( ÿìƒÑ !%+73#53&'3#735#3#735#3#735#35#'6I2v1$hhEE 22'``<<<T   %7   E&8    A5// *W ¸   6       - A4   ÿç{Ï#)/7373#33#53537#5#35#3#3&'''6,&)) n"(L444444   ¾ww/   ÿç‡Ç0D73#7#5'75#35#35#75#3&''67&'767#'3''67&'767#m P!......3    >6   #ÇDJ,, 2            ÿêˆÏ $)157#5##53&'73&''67&''667##5##535#J-"3     )?111¸#%        GM M0  ÿèˆÏ%+173#3#'6553&'#53&'367#'6'6'6L* Y- +) '" .'% !1 3Ï 8.# #,A G'   ! ÿè£Ï .73353353#3#3#"''3255##5##5##5367#$$„ –DE ->À&55&7 S ?JJKKVg PÿèóÏ .73353353#3#3#"''3255##5##5##5367#Z))Ž £KK 2DÀ&55&7 S ?JJKKVg dÿèóÏ -73353353#3#3#"''3255##5##5##537#k!"€AA +:À&55&7 S ?JJKKVg ÿé‹Ï '.73353353#3#3#"''#5##5##5367#3255r~87   &2XÀ&55&7 R  JKKVg %C> ÿôcÏ 73&'73#67'676'&'  U< #+§ 83 96.03,mÿèóÏ '.73353353#3#3#"''#5##5##5367#3255tx†<<  (6^À&55&7 S  JKKVg %D?rÿéóÏ %+73353353#3#3#"''#5##5##537#3255xt99 '4ZÀ&55&7Q  JKKVg%C=Q òÏ -73353353#3#3#"''3255##5##5##5367#`%$¡MG  /AÄ**0E 1====EV ÿè…Ï '-73353353#3#3#"''#5##5##5367#3255nx55    $/TÀ&55&7 S  JKKVg %E? ÿèzÏ '-73353353#3#3#"''#5##5##5367#3255em//    *JÀ&55&7 S  JKKVg %E? ÿö‚Ò "&7&''6&'3'67##53#=#@!  %N  8W2Ò    l ON !! ÿôxÐ 7&''63##53#=#@ %OOO)Ð -eb`** ÿøzÅ7'67#53&3#7'75#G 6=R CX"&c*"}1 04[ÿéóÇ!7#5##537#53#3'6656&'æL(7…: #"' ¡‚pqƒ': &  )"2ÿèãÂ73#"''325567#È&/  +!«Âx ~[ÿì݆73#"''325567#[‚  i†O V\à¿73#"''325567#\„  l¿U [ƒÿêïÂ73#"''325567#ƒl  V€ † ÿîˆÑ !'-7&''63#3#7'675#535#&'7'6G ) N ))+2=--  UÑ  !C F6JbõÐ 7&''6š#+ ''( 7Ð*..,DvÊ7#"''32654'7##5v  -Ê %t† ÿðòC733#3#535#53vVVgåiVVCpGóÏ 73&''>£.% * Ï4.#% UÿéöÏ %-157&''63&'73#3#53&'#367##5##535#35#Ÿ ) (!' 4++ -š, &0SPPPPPÏ&     9VV!.ÿéñŒ$7367&'#"''325'3'67#w   6%  \O9 5;Œ   7H  U9!-OÿèöÌ 73&''65'3&''65´   -<  $ )ÌY T$79"7RHE @+9R7ÿëó– 73&''65…A<> N–(H$77$.CVÿéñÍ 73#535333&'#˜Y›.K_/" #—#Y#=A ÿèõF 7&''6x"M P! D OF #'' ÿþbÏ767'67'67676'6P &   &)¦8!  /,p  =¾“ 7&''6x"3“"#2 ÿéuÏ#73533#3#3##5#535#535#35#35# +++''++++##+8888¹[&&[=9 ÿélÏ#73533#3#3##5#535#535#35#35# &&&!!&&''!!&0000º\%%\;9l~òÏ 73#'6”V^  Ï  lÿéò˜#'+73'73#3#3#3##5'65#5#35#—  )$$$$*[ -˜   c('m€õÑ 7&''6®! ! $Ñ tÿçò˜(76767'67'67#53&'73#&''6—= $ #5 /A 9 ) EL :   TÿçõÏ7'6653&¦ :'+P;.ShB''xÿèöÌ73&''>® , ÌW S%5;-8m“èÏ 73#'6ŒPZ Ï  ÿôxÅ 7#3#3#535#35#5#x !`==Å.Q.Ñ@.m-m.. ÿêóÂ73267#"&55#'6655³    M¹ $ «H,:7-Q1!ßÒ!'73#"''3267#3#"''3267#536&'iU   pš  š/Ò 1 >3 b '  †{Ï 73353#533<jÏ6*=<)ÿïsr73#67'535#`E%". EKrC*  J€ÿéúÂ73265#"&55#'655Þ  » #­HS+$RRrÿêõÃ73265#"&55#'655Ò  ù ¬a60 '3s“€Ï 73353353#kÄ**1 ÿ 73#3#735#7677'67&' zziiBB 2< „7,     ÿéób73267#"&55#'667±  HbX  K/(%6sáÆ 73#735#73#735#VV..[VV--ÆS-S-sÿé÷|7#'665533267#"&5Ä# J h%*%0v  olôÐ 7&''6ª! &Ð $$'# ÿòl¿73#3##5'67#35#\* 2* (¿ hf+6‘C ÿçïÒ#'>K7#3#'6553&'75##5#35#3353353267#"&5536'33#67'ïC:·X,---?,( $  # ˆ88 ' Ã*C< 3@] %   . .ÿèó,73267#"&55#'665à   Q  ,'  …ÿîõ{7333267##"&5477#•O=  24{P A……óÐ 73#'6 HP Ð |ÿêõÏ73533#3#&''67#535#$''0, % & ) ')$£,,# 6 11 %3,ŒÿêñÁ73#3#"''3267#5335#365#Œe,*   N'9Á+w$&bQoo \òÏ73533#3#3#535#535#\[[RRhåiTT\»ÿéóg733&''67&''667Y r(. :*-< 6& ,g       ÿêqÃ73#"''32765#'655#[  # ÃwN ƒCQ20ECoÿêñÀ7#53#3#"''32765#Œ‚N RW­7c) E t¹73#67'675#Y#-6"¹~ ƒxÿéìÃ73#"''32765#'667#ƒi  (è0,w[L GYlÿçëÃ73#"''32765#'665#|o  .æ*x`KAf ÿê]Ð #)73&'73#3#3#'67#5365#536'&'N4!-²  0$  ÿéðÐ %73&'73##53673#3##5#53'&'¡N, ² BIIVGÿë™Ã73#"''3265#'655#_:   "Ã¥2•@S3 2H@Vÿé˜Ï 73'65''6…2/ ÏkL/ ,B9-%#_ÿö¡Ï7&'367'5#z + Ï ?n  p[ŸÐ767'67'6776'6   "&¡.! 0+ m ÿèá}73#"''32765#'667#ÆN)/.#O}P1 J1;1,ÿéåÃ73#"''32765#'667#'¾   `&'% 7ÃxO „OX MLÿêçÃ73#"''32765#'6Î  »‚1@ DÃwL"  > =*%)bL-1)'WÿéôÏ !7&'''63#"''3267#'667#¿$&!d   (Ï=!= ?($)a H,2)'JÿéôÏ !7&'''63#"''3267#'667#¸!&+%l   ,Ï=#<>)$)`!E,2*&„îÆ73#3#5##5367#35#35#„j.,:'::::Æ ‚ ‚ GP AóÏ 7'67&'3#"''3267#'67#\!/ +j$- +(X‡  < : 1 "Ï %#"&-54  $AëÇ 73#735#3#735#73#735#0žžvv.__99b``88Ç;9;;ÿôë„ 73#735#3#735#73#735#0žžvv.__99b``88„>@>> GóÓ %+17&''63#3##"''3255#535#&'''6‚0: ;7(6 Qe)]]  UU)]*#"(O& )Ó       "      2óÒ $*07&''63#3##"''3255#535#&'''6€37 89)4 I e)]] UU)]*#"(O%(Ò    (  [ÿêêÏ 73#5#533533Ö{)*š°¢ÄÄ&ÿéÛ{ 73#5#533533Ç¡<RÏ 7&'&''6- 0Ï    VAóÆ73#3##"''3255#535#a‡4?? JJ?Æ""  "AñÏ$*7373#'67#3&''6657'6''65‡Œ*&/u: 9@ -!F ^ ¸ 6*'+     ÿèvÎ7&'#5'67#53&'736e  -=%   x  ho 5  }ÿéí 7#5##535#35#íHHHHHÂÙÙ[H¢GsÿêõÍ 7'6'6'6Ñ ::! ; 9()L JÍ *" ' 3"# ÿè€Î 7&'#5'67#53&'736o  1 D%  x  ip 5 9ÿê¹Ï 7367&'#5'67#53&'p %    3$I% Ï   ir 6 ZÿíôÐ'+73#"''3265#'6#3267#"&5535#Z  N G0% 0,Ð m'R J1 8%eÿêÐÏ 73&'#e3$#&ÏL! #‚™ÿêëÏ 73&'#™$ÏO  }uÿíóÐ&*73#"''3265#'63267#"&553'#3‘M  @ &' <Ð m'T s3  I8' ÿê|Ï,73673#&''67#3#3#3#"''3265#7#*,. ! #::j;8  ; ¹   +2 %Kÿô³Ï 73533#3#535#3533#7'75#X  &a'  %-7,±W# & ÿéqÏ73533#67#"''3255'75#)""#   /)¥**3E  9: ÿéƒÏ7#5'67#53533#&V&-1** g~{)*:.. ™ÿéõÍ 7&'67&'6À2Í>.17 =45<>,/3 915ÿíUË73#'3#B,ËÞÉ¥ÿéZÏ 73'65'3#F,(+ÏrG- *=]ÿéKÏ 73'65'3#7'#%ÏrG- *=]VÿéñÈ *73#535#535##5#3##'3255##5##535##5f||iaai‹1:  '&91ÈQO/  FF7HÿêZ 73'665'3#G +A((!5]iÿéï73#3#"''3255##5##535#i†93  29?  (^^GY—ÿéóÏ73533##"''3255#&'—2  2 ž11ˆ  ƒ SñÊ73##5#535'6Ú DDFF2 ?Ê5XX2WðÄ73#3##"''3255#535#a…8BB DD:Ä3@  =3jÿéõÏ,73'33#3265#"'&'#7&'3#67'675#mB0/  Cm  Z; !( £,,F0#+d>   YB GmÿêòÏ#)733##"''3255#'67673'67&'©// * 7&%- Y  ž3Y  T ,)"! #$! [ÿéõÏ#)733##"''3255#'67673'67&'£44  , ;()0! b ž3Z  U ,)"! %" !%"!GÿèöÏ#)733##"''3255#'67673'67&'›;;  7 F/1:,)mž3Z  V ,)"! %"!&"! ÿêdÏ73367'5#'67#536$ Ï0€ {o4 /g[ÿèôÏ.E733#3'67#73267#"&55'75#'65533265#"&55#'665 ::>/!"  " /5    Ï     C8+ ,3N`> 6   &p!òÐ 73&'73#3#3#3#735#p58‚ggggggAA¹   ;1ÿò°ž 73&'73#3#3##53#=#165vvvvyQŠ    C ?? fÿéõÏ7373267#"&55'65557. -)Ï<• ƒZ6 5N ÿëtÎ "(.7&''63#3#67'75#535#'6'&'< A&&+5+((= 8  Î $C F2pÿêöÏ73533#&'#5'67#7&'~,0. $&V  š55A.(7{t/&1>?    ÿñ|Ï !'-7&''63#3#7'675#535#'6'&'A $F''&/**>7  Ï !; ?1 ÿèyÎ73767'5'67'7)%  Î3 ‡}e< Â>3 .3# zóÒ7&'327#"&''7&'3˜ [V )  5 e] Ò    ÿç⎠&*73#"''3267#3#"''3267#536&'3#`S  gœ  ›( J˜˜Ž)/9 #R $  DÿéâÑ%)73##'3267#3#"''3267#536&'3#jP  nœ  ›.   O¢¢Ñ A&WJ1}& j•ÿéðÏ"(,7#3#"''32765#5373#"''3276'&'3#Ó(E   G* ==¤XH%| F   qÿéÄ#)/73##"''3255##53#"''3255##5&'#&'~~<  |  ! 5 ÄŸ ‹¥´ Ф³*"$&!""%  ÿéçy733#"''3267#7#'667#« 0  3 M$ !0y/C)/38.- zðÌ7#533#"''3267#7#'6J8È   !h 51¼%'ÿèæ  !/73#735#35#35#'33#5#'65#535#73#3#5##53NeeCCCCCC>? %P?«?E3> S; .X+# ><;,*Y„êÐ73533##"''3255#'67#ˆ;;  -W L'kÅ   ÿèëˆ !/73#735#35#35#'33#5#'67#535#73#3#5##53E}}[[[[[[9E"+ !!TE¼DD1DˆK5 )IL#  WW 0!#Lÿèæ© !/73#735#35#35#'33#5#'65#535#73#3#5##53JmmLLLLLL9? %P?«?E3>©Y? $ % 'LZ,$ [[<-+Z˜íÈ7#5##5í²È00 ÿí{¿73#3#"''32765#7# n; >B¿7b&@IgÿêîÀ73#3#"''32765#7#g‡PV [!À8d'AKsÿêêu7#53#3#"''32665#‹wI L Sd@ ÿùvm 73#67'75#\!-8)&mE I-ÿòäÌ736732667#"&5-K? ER&0 ;4ÌW Q#) •óÏ736732765#"&5• % #ÏK?  =ÿñç†736732667#"&5=J&2L(- 44†8 -  íÑ 73#5##53&' [°h Ñ// `ÿîðŽ73673267#"&5`7- 3;* ,*Ž9  6  Gÿêìz73##"''3255#G¥F  Kzc  _<ÿîò‰7''6767&'À HG /#4?Q)1 2NX  :ÿñí”73533#3#535#I?DDQ³N?b22KKz|Ì73673267#"&5" ) #Ì    ÿêwt7367&''66''6;  "  t  !/+ Xÿèå7#53#"''32765##'   ?nY;†Kÿöðƒ 73#3#535#X?H¥I<ƒggHÿïï£ #73#'6333267##"&5477#xjw  sL  !';P£  $8   ,[Ç 767'533.+{`  º:UÿéóÇ"7#5##5367#53#3'>&'æR,9‹> &* !  ¡‚pqƒ  '-$/   ÿê†p#735367&'#"''32655'67#-      W   / / ;ÿëì+73673267#"&5;L/F8,9B9)    ÿøå¹ 73#5##533Ñ££¹À Á¤ ÿêö$73733#3&''67&''67#67:‘ƒ"( 0((<1!,6I! "   &6)ÿéôY#73733#3&''67&''67#67#:’‚ !! .'+:.'3…^O     1 QÿêòÏ$)734733#3&'''67&''667#67gZ[S      #2  ­"#2     :+ AE5" ÿégÏ 736753#5' Ä––æ=  ÿéwÏ!73533533#3##5#'67#535#5#? ////BSS2! )BBBBEÿéòÏ!73533533#3##5#'67#535#5#\,!!%%,5 ,"#],¡....?SS3 (????fÿéõÏ!73533533#3##5#'67#535#5#v !. #M ¢----?QQ1!&????j¼ 7##55#'#33535#jDD¼£¸H66666666n¼ 7#5##535#35#35#35#n4"""¼­ ¹H666777\ÿéóÐ$:@F73&533#3265#"'&'#7&'&'33325265##"'"&57&'''6mD''   EkH    &  /  ¢.F0.c:  ? J I‹ÿóóÂ73#3#3#535#535#’Z# *h* #ÂKKKKCÿéŽÐ#7#"''32655'67&''67&'76~      Ð :;!'   RÿêœÏ#7#"''32655'67&''67&'76Œ       Ï %6,-"# (    ÿöYÏ7&'367'5#1 / Ï ?r  p”ìÂ73#3#3#535#535#–T "X#!Â:99:J‘Ï$7#"''32654''67&''67&'76       Ï  2     xóÑ$87#"''32654''67&''67&'7633#3#3#535#535#Z  $  €722=Ž>116Ñ        ÿèñ~#)/5;73#3#3#"''3267#5363535'67&''&'7&'Ze³³« «+|||– „  B ?~5 &]?      OÿéðÐ  &733#5##53'&'7'63'665&'›3Q2% q 5R )!#ÐFm[\n:  F)> #(eÿçôÏ "(733#5##537'6'&'3'>&'¥/H-3 L  2  ÏEm[\n8  O5   EÿéóÏ 7M73#53635#35#73#3#3267#"&55'67#'7367#3673267#"&5_!G""""=X   #. +M   Ï ZZ 3:A%W  ,#!&>8/&_    'ÿìèK73673267#"'&5'LL B_:<FE K ÿílÁ7#67&'7&''53535i>  ***ÁsF    Ê0]ÿéóÇ +7367#533#3#3#&''67#535#'6eSK` ˆW2=9) )6 88< › #"!  ÿé_Ñ 7#5'673#:  Ñ­„ 4ž ZÂ73#3#67'675#535#K  (Â:= C:†ÿèóÇ ,7367#533#3#3#&''67#535#'6Š<5Jh>",% $ '),œ   $ ÿèSÆ7#"''32654'7##5SÆ: =ÍÞXÿéôÈ73#735##5##53'>&'tkkFF`T2L  %ÈBA\JK] "5PbíÏ 73#7&'''6'6’;  R c (O QÏ>3   #  ^RÏ 7&'&''6,  5 Ï      ÿéðf#73#3##"''3255#535#535'2× '.SSdd  iiUU(/]f     wÿêöÏ 73#7&'''6'66´- 4 J0( &-ωj"*+"0 &. (-*SÿíŠÂ 7&'&''6n       -  ) .*) _yÏ767'5'753w5 #»(  3|`ëÄ73#"''3267#'67#l   #% !!ÄA#0:-{ òÏ 73#7&'''6'6°-  6MN GÏ~j , "DK„Í 7&'&''6e  ! Í  (  + %" !‚ÿéòÍ (7'6'&'3#3#33#"&''6535#× .  `'##    &Í  907 $I nWÿéÎ7'6#5'6w    Î & ‚h ÿìdÏ"(.7''67'67676767&'&'4'''6M      k !/(8! 3   6ÿèôÈ15;AGM73'657#3#3#535#35##37#3267#"&5535'67&'7&''&'K^Bc(  Ž h C  L ÈK2 *J<š/M!/‰M/ ‚--“   ]ÿêòÓ/5767'67&''667&'3533##"''3255#&'· -D   1  >b b(  Ó   +   HE  A  EðÐ.CI73'67#&'&''6'3353#5#'67#535#3533##"''3255#&'¤>]R3 !m :/K^  ^ Ð$    &‡2&     ÿéâÐ 1#53673#=#767&''67''67&'763, €›R    #   È Èˆ    5¿® 73#"''3267#'67#'67#'6^X   0*$  ® Z@@* %8, " 4Ï¢ 73#3#3267#"&55#'667#Gmm ˆ%   "¢0 3  5Ç©73533#&'#5'67#<9::$!-‰  @T/+5Ë«!73533533#3##5#'67#535#5#:!$!!$$&$&!Y$#66" ####ÿéêÏ%73533#3#3#"''3267##5#535#535#YXXPP`  K[[KKY²: #WW ÿézÎ&73533#3#3#"''3255##5#535#535#%(($$3    ((!!%±: %WWyÿèíÏ%735#53533#3#3#"''3267##5#535#€%%%..**4 !,,%€<)VV~ îÏ%735#53533#3#3#"''3267##5#535#‚$((,,))5   !(($.FF ÿçöÊ (73#735#35#3#3#33#"&''675#TT.... k"U60M6  5Ê\6:7 ) < ÿçöÊ (73#735#35#3#3#33#"&''675#TT.... p'U60M6  5Ê\6:7 ) < ÿçöÊ (73#735#35#3#3#33#"&''6735#^^8888"x+""N60N7  :Ê\6:7"(?;À¬%73533#3#3#"''3267##5#535#535#=3;;33<   (55--3œ&336Ȭ73#&'#5'67#535'6² 44" # &.:.;¬ 4C 6Ê«73533#3#535#35#6@@@/p-@&IIŽFFP!2̯"(73&''67&''667#&'&'a@  !( $   3  / -¯     <    8wÆ­ 7&'''6ž +­  5 ËŠ73&'73#3#"''3267#'67#8C 9VO  ? " '*q  + & 55ʬ 73&'73#3#3#3#735#5A?• ||||}}WWš    3=HÉ®373#67&'#"''32655'67''67''67#?„;       # %  2®           4àÈ73#735# ÀÀ››È”vÿêò/ !7&'7&''33265#"&5''6‚  a  z ( 2&/   &  +  ÿéõÀ73#32767#"&55#'655# \/@K<À¨  ­/[: 8Q/$;×Ð $*73673#735#'673&''67'67$?^³‹‹&  =    ½ ‚^3      ÿêïÃ673#67&'#"''32654''667&''67&''67#Úe  % 0/   ,> >'4 4)!$ 6%[à  :!%="! 1 , %' ! ÿêôÃC73#67&'#"''32654''67&'''67&'767&''67#×` % ))  0F J4  . 7([à ;%@"" 14        g òÃ373#67&'#"''32654''67''67&''67#€j0     # )"  !$à  !!  jÿéöÃ473#67&'#"''32654''674''67&''67#wt5     %  *à  $   !'  ÿêrÃ573#67&'#"''32655'67&''67&''67#^"      !  &à    '!& ÿêcÂ573#67&'#"''32655'67&''67&''67#M            '%   ÿéÝ '.73#5##53635#3&''67&''667#k m•8 /••4A  "  /6 Œ Œ |^      xóÑ 73#5##53&''67&'„a³c2 /i*$#)Ñ ('!   ,UÑÐ 73#53635#35#_ j¥$ ~~~~Ð bb 5>,IÑÐ 73#53635#35#_ j¥$ ~~~~Ð kk:E ÿêñT7'66733327667##"&5X",*8  $  T'/'#I   '  ÿëx£7767#"''3265'67&''67&''67&'767&''6`    % *" "   )£  (         jÿç÷¢ $(73#3267#"&55#'67#53635#35#¡6   ) #BBBB¢W2 59.W -5eÿè÷Ð"&*73#3267#"&55#'667#53635#35#ž;   HHHHÐ jE  I-' !$j :HA “Ð37'667#"''3265'67&5'67''67&'77&'| !     Ð    -       ‰÷Ï#'73#3267#"&55#'67#5335#35#²)   ))))Ï ]8 ;=1]';)ÿéØE 7#5##535#35#ØŠŠŠŠŠE\ \!2ÿíŠÏ&,27#"''3255&'75##5#67'#53537'6'&'‰     0   M  ‹†  ) >ŠŠF--žDD<  Eÿì§Î&,27#"''3255&'75##5#67'#53537'6'&'§    * 8  Ї  *!=‹‹U! / ,žDD:   e¼ 7#5##535#35#e,,,,,¼±¹H67[`êË73673267#"&55'7€/$(.  &#%Ë%  R_Î 7&'&''65  L$'Î      gÿéóÏ73533533##5##5#35#35#gAA%AAAA¥****©©99ƒ8W‡ðÐ 73#5##53&'Ÿ Bq>Ð 22 Rÿéó… 7'667&'‚D ) 0 …?@99a(+j ÿíòn $(73#5'673'36735#33535#3353#ª,¬  +[c Qd<DHL767#533'67##"''3255'67#53'#"''3255##5'67&'3#735#š  Mk.   )/^  S*  & 99³     Ee Piy   -vôÔ $*CIO7&'7'673#&''67&''667#'33#"''3255##5##53&'''6(  ^ 8<     #e0 0  %  Ô        , ../>     ÿèïv#)/5;73#3#3#"''3267#5363535'67&''&'7&'[c±±©  ª,{{{– „  A> v4 $ \=        ÿé”Ï*0767&''67&'3533#&'#5'67#7&'%  722  -\ È   [!!EN)-3   ÿûœÐ  &7&''63#'767'&'&'Y0 OO`{O $    Ð %Y ,*,!O!! ÿê¡Ï#'73533533#3#535#35##5##535#35#&&–$1&&LNNNNN±!!!!Drr)A ÿé™Ç$(,73#3#"''3265#'67#'67#'67#735#35##kAW / ), &  CCCCÇ^ N18"+* 7:ÿí”Ç&*7#"''3255#&''67##5365#53#'35#”    $:0l)0FFlg  P  #m GG %%ÿéqÐ*0767&''67&'#5'67#53533#&7&'   ;  '##  È     ‘?E$!! c  XÿèôÍ8<@E73#&''67&''667#'367#533#7#5'35#35#35#675#¿(      m((>   Í#>&! 4 &   w}IP ÿññÆ 73#3#735#3#536'&'Íͨ¨€€l GâƒP  ÆT.G  ÿë|Á 73#3#735#7'676'&'nn__992 .8! '  ÁJ&@ ÿîïƒ 73#3#735#3673#53&'ÐЪª‚‚ < 9Þ:ƒ>0    ÿëdÁ 73#3#735#7'676'&' WWLL((##- ÁI'A  kSâÏ 73#53635#35#– ?w"QQQQÏcc 5?`ÿëñM73673267#"&55'7Œ+"%+ #!(,M  VÿéõÐ"',7333#&''67#535367#'65##67#Š5 . ;, 1 3- 6 = ) V"#Ð C%4)"C j11@ÿèòÏ.E733#3'7#73267#"&55'75#'665533265#"&55#'665Ž>>K  ;)+ ) 6?   '  Ï     C8* (M`>  6!  &/ÿçðÏ.I733#3'67#'665533267#"&55'75332767#"'&55#'655n))6 H #(    #.Ï  D9, (O!    9-  !ªñË 7'6'6'6Ü   & %Ë ! % 'KÿèòÏ0G733#3'67#732765#"&55'75#'665533265#"&55#'667”::G 6&(& 3;   $  Ï     C7+ )M`> 6   ' ÿêóÎ1J733#3'67#'66553326565#"&55'7533267#"&55#'667B--0 B #)    *2  5'   Î  C7+ )M%  :< .!  &ŽíÉ73533533##5#5#5#Ž # G A###¢''''tt(((9((ÿéðœ.G733#3'67#'65533267#"&55'7533267#"&55#'6655jffq  IQD 1 9''*? 8 œ    3)! !':    3!   ÿéõÏ2L733#3'67#7327267##"&55'75#'665533267#"&55#'6675q\\h R@B  2 47= OA  0 Ï   C7+ 'Ni2 *`ÿèõÏ/I733#3'67#73267#"&55'75#'66553#'665533267#"&5 === +!    ) <  Ï   B6+ 'Mu;   ÿétÎ ,27'2'6'&'&'3533##"''3255#&'f %6+*   9   9 Î    8E  B  oÿéóÏ/F733#3'67#'665533267#"&55'753#'66533265#"&5¢..8 G !+     7 Ï D8, (O"    K%';  ÿê~Î.>733#3'67#'65533267#"&55'753#'66537'9..1 ? 2       6  Î  D8, ,4O"   I')< uÿèöÏ.E733#3'67#'65533267#"&55'753#'66533265#"&5§,,5 C 2    6 Ï E9+ ,5O"    H'*> ÿéöÑ+1S7&'67327#"''67&''7&'3&'67327#"''67&''7&'7Ë3<      99   !Ñ      J         –ÿêðÆ73##53#3#"''32765#ŸKK Z1 6   9Æ=*I)ÿé–ÏQUY]733#3'67#732767#"'"&55'75#673'73#3#3#3##5''65535#5#35#C77@ 1#% ! ##"N +Ï       H    E -7QqÿéîÅ!7#53#3#"''3267##5#7;5#35#uVÁVd  M`QAAZE?²I 5 aaE#V  ÿé‚Ð !73#53&'#5##535#33535#35#L.u3A;&;&ÐCžž>+++h+++ÿêõÐ7'67#'673&Á& : /  8,"8d' #@0: ÿéÐ !73#53&'#5##535#33535#35#P6ƒ8GD+D+ÐCžž>+++h+++‰ÿéóÍ7''6767&'× ") c4=  H€{A%gÿéöÎ73533#3#&''67#535#}'0095 /(, 0 ,/'£++#6!10 $3+lÿèíÐ 73#'3'6573#ÙT*Ðçç„>& #4wÇjÞÐ 7&'67&'6'&'6C –/Ð   JÿèôÈ'7#3#3#3#67&'#67'5#535èpffff{K   > ( È    GI  VlMÿóõÍ  &7&''63#3673#7&'&'›$* ''' 6ffX -œE    Í"'%#+c,+/!g !cÿéôÐ$*073&''67&''667#"'6'6'6ŠK  $' - =" 1 41 *> D7 .U YÐ   & .  ( :ÿé€Ð 7#5'673#e  Ð ¢{ 5–„aöÐ73&''67&''667#£;       ,Ð      ’ÿéç\7#"''3255##535#35#ç   00000\[  's -SÿéóÏ(73533#3#3#33#"&''675#535#m222=@77#,!  ;>2³$!0*5a$E[óÐ73&''67&''667#{Q+ ."+ ) !CÐ    Bÿêì_ 73533##"''3255#&'''6T=??  =oHM7  2  W„Î(,<733#3'67#73267#"&55'65535#'665367'5?((0 !   $ 3  Î     " ,   |WóÎ*.D733#3'67#73267#"&55'65535##'66533265#"&5¬**1 #     !1 3 Î     ! # .     ÿçîV 73#735#35#35#'67&',««„„„„„„"!''^''&&VP9   ÿéë›!73673#'67#67&'7&''6Gy ,*=n*2 A@,x@  1 6  % $ÿìò˜&7732667#"&55'75'75'67×bsu &8(PSGJ>[M ).h        ÿëð–!'-7&'3673267#"&5'67''67&'w  +$+7 %  1$ $$­  – L @ N"   M'! "#$ ÿéî73#3#5##5'67#35#Û† Œ{ 2?8{{cN #f1YÿéîË %17&'7'6'&'&''67&'763#5##533ž  O g  a    $ddË #  ;   x {]jÿéíË %17&'7'6'&'&''67&'763#5##533¨  C Y  T     UUË# <   x {^PÿçòÏ873&'73673#3#3#3267#"&55#'67#5367#537#`!  B8:J5  2,+/)+6¯    A  E9(#. ÿçòÐ973673#3#3#3267#"&55#'67#5367#5347#53&'P *  >`WXiN $ V FW[LNY7 Ð   ?  C@!4   ZÿèóÐ973&'73673#3#3#3267#"&55#'67#5367#5365#l  1.08"   4/ *.-/6®    A E8($,   \ïÎ*73533#&'#5'67#73##5#'66556)++  "Ì #/X, 7¸  15&22" ÿçîY"73533#&''275#735#335&'7#6'PPPj30P<>R>>{(( &   &  +Ñe7'67#53#3#735#G ""¢kq‡cc?   $  ÿéõ273533#&'#5'67#b_J7 ;&%: .'L   $# jîÐ#73533#3#3#5##535#535#35#335ceeQQe·dRRc$??S>È  ""   ÿéó&73533#&'#5'67#cfL2 :$$9 ('I    ÿçî8"73533#&''275#735#335&'7#6*NN Pj2/N>>P=82*   # <âs7335#53353353#353#FC0/CHÆT    HßÇ73#"''3267#'667#'6 ¿  D00 ++P$ ÇZ!D02  +& SIæÆ73#"''3267#'67#'6_‡  % C <9ÆY AL? EÿíòR"7&'332765#"&57&'''6¡  !a  nR ? A !& NÿéõÎ=73533#3673#33##"''3255#53567#'67#5367#535#[*++*<;??   MM4  + N7*¶     @ÿéô–7&'36533#&''67#º `BJC5 2G E?–  !=<7<CÿèôÏ!%73533#3#&'#5'67#535#35#335TACC81-$*/,9A%%9$·H !KP)#HM"""EÿéôÄ %*.733##3#5##5'67#5367#537#3353535#a}RdL ,.%8248DLLÄ,+ Y ?(  - ~)mÿéôÏ/5;7'6&'#"''3255'67'67676767''67&'Ë #* &8      (@ [Ï O A  ;! %3! aÿíóÅ/5;73#"''3267#'67#'6&'33267#"&57&'''6nt   9 31;    V  [ ÅZ CM??@ D  & sÿëè¼ #53#=#'#35#'35‡u0/ÑÑgCCCCUDDDDD 8`Î 7#5'673#:  !!ÎhH (_d>òÏ73#&''67&''667#‰U% %# 4Ï #      ÿéóP767&''676''6}  LN 96 P  #%    MiÎ 7#5'673#<  !)Î\? $RqRõÏ73#&''67'767#'6•I %  / Ï       hÿèô‡ 73#&''67&''667#ŽO  !  3‡    # ' ÿédŠ 7#5'673#@  *Š rV pÿêõÏ,273533#3'33#67327#"''67&'#535#7&'433E:9      —=4¼  ¹==#$"-!!  %9$Mÿê¦Ï7&'3#&''67&'767#‚S   %  4Ï%3 )  ˆÿîõÐ #73#'6333265##"&5477#©?E E6  -+Ð$P  Aÿðô|#'733#5'6367#35#33535#3353#XP5° 'IE<EÿéëÏ(73#5##535335#767&''67&'y`–Jr&–" ¤¢¢D˜llg   XæÏ 733#3#5335#3#535#ˆJJC‡0__JJ66Ï ……r_6ÿñòR73533#3#535#\\\häh\5WÿïóR73533#3#535#f555DœD53WÿïóÏ #3767'53373673267#"&53533#3#535#y #  C555DœD5“,  t(<,     5ÿñòÏ!173267#"&5536'33#7'3533#3#535#Þ %+ ($ŸEE;(# []]iäg[à f/'. &fÿïóÏ !1767'53373673267#"&53533#3#535#…     ;.//=<.“+  t(<,    5€ÿî÷Æ73#5#3267#"&5535#€g= " -QSÆp W $-  q=rÿî÷Æ73#5#3267#"&5535#rsD *  1"X_Æp W -  q= ÿèõÏ!%73533#3#&'#5'67#535#35#335dddQD%9 ;%#::"BQd'==Q=¸M'-OP,%MR'''hÿîöÏ-7#"''3265#532667#"'&&55'753753ê   -# ¤T%/ _Xe " bC;E=fÿêòÎ #73#'6333267##"&5477#‹Zc ^B $6?Î &Q  CpÿêóÏ7327#"&''7&'37&'îG   ( ##  “ ;8#!=K )F@  PõÏ73533#&'#5'67#dcO"4 :%#; 7O´(5ML2'ÿñîG 73#3#535#$¹TeÜcQG00"IêÅ73#3267#"&5535#"¬’ 6E L@’˜ÅB 5rKíÄ73#3267#"&5535#rlU ' $4UXÄA 5_ÿìõ@!733267#"&57&'7&'''6‹  (  ;  ]  @;  <  $ `ÿìóÏ!173533#3#535#&'&'''6733267#"&5k5551w25= >  R     ©&&''@ ! ) !6  2Ü‘73#"''3267#'67#'6¿  =^ V Z‘C)> 0 ÿêóÏ/73533#3#535##5##53267#"&55#'665^^^R¶P^Ð¥  % /((¼94!#6)=  /  &+ïÏ173533#3#535##5##5#'665533267#"&5]__Q´O]ËŸ€;) &b   Á  ,)*-  "   KÿèõÑ #'7&''63#3#3#5##535#535#35#˜#, (' & 5K<<1Q3<<QQÑ"&%!WWu$ ÿèõÑ #'7&''63#3#3#5##535#535#35#|4: :6+8 Ng'UUCtEUU,ttÑ !&$!WWu$ ÿèõÑ #'7&''63533#3#535##5##535#|4: :6+8 N***X¾S*ƒtttÑ !&$!?VV5# ÿè‚Ð #'7&''63#3#3#5##535#535#35#G  * A))$6%))66Ð  !WWu$ÿéïÆ'733'67##"''3255#53&'767#„` % .< HÆ]  X  ÿèoÐ #'7&''63#3#3#5##535#535#35#;  !7!!+!!++Ð  !WWu%yÿêóÐ%)-157&'3533#3#"''3255##5##535#35#33535#335Ö  O333. .3/J/Ð !!ˆ  225Ÿ@GSîÑ%)-157'3533#3#"''3255##5##535#35#33535#335à fCDD;  ()(Ð  !!ˆ  336Ÿ@G ÿé‚Ï+17=CI733533#3#"''3255##5#67'#535#537'6'&'&'7'6&'0.-  .2#C  S    Q Ï222%g  SggA$&%*   3   3 ÿéòÏ/5;AG733533#3#"''3255#&'#5'67##535#537'6'&'&'7'6^QhV B! 'AViQh ‡    ‡  Ï111&f  PAG$n&+    2  ÿéßB 73353353#5#BB¬6+77,Nÿçèe7#5##53'665&'Ñ{B+; 8&+'&!-eVEHY   TÿêóÏ'736733#'667#7&'33267#"&5g&IJ$X     –:E@5G  Kh !) ÿè÷Ì$736533#'67#7&'33265#"&5£**( 9   “ m,(`<  Dk & Jÿíð–&736533#'67#7&'33267#"&5T2UW< 40l   nK! =; @E [ÿêõ‹%7&'36733#'67#33267#"&5Ñ[-GJ / ) *E  ‹  K"? F  ÿép#7#"''32655'67&''67&'76`        ' !      uÿéõÏ $73533#'67#7&'33267#"&5":;2 *!R    •::k.(_C   Of  ÿéòÏ'736533#'67#7&'33267#"&5Vnp00 TU•   ' ”!!9G(]K  Rd %   ÿê€Ï $*73#53&'3##"''3255#735#'67&'H-o,^&  %88  P  Ï  =H?  :$B   mLóÐ$7&'36533#'67#332767#"&5Ò  W.BD0 $-E   Ð #4) * qÿèìI 73353#5#533¥ h!IE2N C2Fÿé¤Ï $*73#53&'3##"''3255#735#&'''6}HA ) /  Ï  ;GG  C#;   JÿèôÏ&73733#'667#7&'33267#"&5S6LN4i  –9;E@6F  Oc  †lñÏ%736533#3267#"&55#'67#7&'†#5! "  J °    !" . iŠÓ#)73#53'3##"''3255#735#&'''6O4u+`) '??; ;  Ó"    aŠÓ $*73#53&'3##"''3255#735#&'''6O3u-`) '??; ;  Ó #"    ÿéâo!'+73#"''3267#3#"''3267#536&'3#`R  gœ   œ) Oo" %.E"  5HÿìõÏ #7'67&3673267#"&5ž+ 9%( %S%%- !  ,#­,+0 -0$  \ÿìôÏ "7'67&3673267#"&5¦& . $ !P% %-   *!­)&2 +,$   ÿèóÌ733533533##5##5#'655#53021##12)%##ÊRNNTT||tt E,(; )ÿéÑ›7'67'67'676767676¼ GM _4>%! "+0,%`/8  %  3 ÿéöŒ"7333"'&''#&#"'663267#53&'€R0JJ.=$ F+¢]Œ +  " ÿéòÅ!735#533##'67#53655#75#35#W@­`9 /8>W°EGE‹'::4!' '':' ÿëó¿735#533##'67#5367#75#35#€"V0 "M…(::3)((:(GÿéôÁ!735#533##'67#53655#75#35#G=0P. & $*=†575†(;;/! ((<( Eÿê„Ï 7#5'6r  Ï©„ 0ÿèò‹!733##'67#5367#5365#33535)© aA 01;X[CW?AF‹**"  * ÿèò‚!733##'67#5367#5365#33535)© a? -0;X[CW?AF‚''   ( ‹ÿêô¿733##'67#535#535#33535™P *$ &¿::0&))));))]ÿê–Ï 7#5'6„  Ϫƒ 0ÿéšÌ"(767327#"''67&''7&537&'‘H     #%%!  “ %).& !&%=  ÿéõÏ#)767327#"&''67&''7&537&'ãj" +,: >'MM'  !! ();'$  #"<   ÿèúÐ"(73&533#67327#"&''67&'#7&' RQ    !€¿ ª7&+ 7 %&80?8 ÿéöÐ"(73&'33#67327#"''67&'#7&'…IG     †½  ¨(=* 8! " ) N:  ÿꔆ73533533##5#'67#) !Y----BBA6¤ÿé÷Ð"(767327#"''67&''7&537&'ò&       ›!. ""&2$" 5 tÿèóÏ"(73767327#"&''67&''7&7&'™?@    # &'-Ï# !  *$!5(*  ÿýxÏ733#3#5##5335#311*8"88Ï&'ssR? ÿô„Ï )7&''6#3267#"&553#"''326M  ,1+ $ R   Ï   !$9\ q5ÿéŽÈ7#"''3255##53#3#735#Ž  UAA77ÈÅ  ­Ìß,W5ÿètÆ 73#735#35#35#'67&'UU------ = Æ£sMM6   ÿõ’Ï!%+173533533#3#3#5#535#35#35#35#&'''65eOc $555555<   ¼V;MV44)   ÿéÍ;76767&''33#3#&''67#535#'67&'767#^    :5=01# +,-  $Æ  3+ + "!!"    ÿé•Ð #+/7&''6'66553&'73'#335#5##535#P   0 ,%BBBB111Ð u." !@ J:-D D' ÿéòÏ/37;?CI73#67327#"''67&'#7#5'75#53&'35#35#75#'3#735#7&'«B@      #,+a555555^^99œ  Ï, "+  ;V _"*q6;ž9  ÿé“É%)15973#67'7&''6767&'7''63##5##535#35#~~  N  S‡‡{KKKKKÉ!   ) aa$5 ÿê‹Ç #'+73#3#535#35#&'7'6#5##535#35#t"'~% 0  h =====ÇEEEE   Fff$6ÿë‘Ï !%)/573#5##53&'3#3#535#35#33535#335'67&'R6Z6L.m.,H,;  8 Ï +,1\\6:+   ÿé˜Ï.=CI733#3'67#73265#"&55'75#'6553375367'677'6'&'B@@?,!   +  0:< IÏ     D8- ,5NUDA= A  ÿé–È #'+/73#735#'3#735#3#3##5#535#735#33535#335Z;;Z;; u088<<22O2È11/V((34ÿé˜Ï/3GK733#3'7#7326567#"&55'75#'65533#3#7'7&'7767#735#B@@?  +   +hh_ +5 B;;Ï   I:0 /7SL +    ÿémÏ!%)7373#3#"''3255##5'67#35#35# .39   '  $''''°   5q %CBsÿèöÏ &*.473'33#673267#"''67&'#7&'3#735#'6tB,+   Ci  V77+%(©&&A#5!):+477 EC#;   ÿé‘Ê #BFJN73#5'675#'3#5'675#&''&'3'73#3#3#3##5'65#5#35#U7&?8'Q   2   $$($$##&Z 9"""""ÊR  R      6 ^ÿèòÂ$73##"''3255#'67#53535#&'Ë''  n!!%'mm8  ÂgF B8( -(ggT ÿéÄ!'73##"''3255#'6655#53535#&'ˆ @ @@ Ä`Q M <" 1 ``M  RÿèðÅ$73##"''3255#'67#5355#7&'Ý  EXDÅdJ  E;+ /,ddQQB ÿéÄ%73533##"''3255#'65#75#7&' Z  3]3 accN J;# =PPB¶ÿé÷Ê 7'6'6'6à  ! Ê * (% / Qÿç»Å &73##"''3255#'655#53535#&'° (((  ÅaS O =#!>aaN nÿéóÅ$73##"''3255#'67#53535#&'à 666 ÅbN J:. 0-bbO   ÿéià &73##"''3255#'655#53535#&']     Ã_S O <"!<__N I|Ð#'73&'73#3#3#3#5'65#5#35- \ )Ð H yIêÐ#'73&'73#3#3#3#5'65#5#35› \ *Ð H ÿóyÆ73#53#3265#"&55#7#65&K]e   %!Ór  x<9#˜†+ ÿésÁ73##5'67#&'b%)I Á ´|"-I4('%7ÿéðG 7'667&; '$> ;% #! !+qÕÇ 73#735#35#+ªª€€€€ÇV43ÿèxv7373&''67&'67#367& /   3Z )     yÿéón733##"''3255#53567#ƒa44  22Gn . + gïÏ7##5#53&'73##''67&'ŽIb_JV›¥77  7/ WÿêñÏ17=7'6&'#"''3255'67'67676767&'&'''6Ñ 1; 5;       )0)!V Ï P ?  : ( 3    ÿïó_73#3#3#535335#Ï]LLhæ**\_33JcòÄ 73##5'67#&'×a"5 A^. -ÄN8 !tò 73##5'67#&'Ö]!?>W–' &Â<2 ÿèÚo$(7#'6553#"''3255##5#;5##;5#t:±   >99>>99>> %7k+o"  våÑ 73#"''3267#'67#'67#'6I‘   3,">5")Ñ6 "  “ìÑ 73#5##53&'a³aÑ --'ÿêÕe $7#"''3255##5##535#35#35#35#Õ  :;;;N::N;;N::ec  """{$8mé¦ %7&'&''332767#"'"&5''6~  W p ' ¦        !ÿïÞ¼ 7#5##535#33535#35#Þ•@@U@•@@U@@¼ÍÍTAAA”@@@ ÿèàÏ 7#5##535335#33535#35#à–UV@@V@–@@V@@¢ºº--J777€777 ÿéãà &*7#"''3255##5#'665535#33535#735ã  AC @@TA–A@TAü+CC*% 1"]<)))e) )) ÿêòÇ'+/487'66553#"''3255#327267##"&=#335#355#9 µ  =  * <;O;Î ¬  )BB&# 0?KD###Y### ÿéáÊ$(,04733#"''3255##5##53&'767#35#35#35#35#*¤A  CDV ƒDDYCCYDDYCCÊŠ  88;¥  UCÿéïÒ&*.267&'3533#3#"''3255##5##535#35#35#35#35#¯ ”eddT  @@Te%@@U@@U@@U@@Ò  !!†  ,,6Ÿ@E"ÿçÜÆ 7#5##5##535#33535#35#Ü????S?’??S??Æ£LL£@---m--- ÿëó" 73#53535#35#35#Úæ##5$$6##"%%%(è©!%)-7&'3533#3#5##5##535#5#'#35#'35Ÿ |]^^N::N]«:::N:N:©   M M $  ÿè•É 73#735##5##53'6656&'%__99SF,  !ÉA@^LM_ 2 !5 É©73#735##5##53'665&'Pbb>>WL-H &!©3,>-->#   VÿæöÈ73#735##5##53'66&'zff@@[M/)0, "ÈAB[JL] 82'  ÿåkÃ73#3'67#&''67#Y*/"7  Ã1M( @F -: ÿè€È73#735##5##53'66&'[[77PB*  ÈA!B^LM_=0&  ÿêŒÑ $*73#53&'3##"''3255#735#'67&'R0x1c)   '>>S  Ñ  >I?  :#B   ÿéæÁ#'7#"''3255##53267#"&553'#3æ  ¤C#  .*kCCÁ½  ¥ÅØu% qI6# ÿéôÐ73533#&''67#&'bab,? @(R Y_dš6(a'+WO3:Rc  =ÿéõÏ73533#&''67#&'NDKG4.D K CQ ˜77[-/LJ18Rd lÿéôÏ736533#&''67#&'t-72 /%.0,;  • U2*FA/-\i ÿçõÏ736533#&''67#&'‡&-* ) % )%:‘""S1&<8,/XfwÿéòÃ7#53#3#3#"''3267#533ª3{3,,.   ^±#$N 7N<lÿéëÃ$7#"''3255##5&''67&'76ë WR   ÿ  ¨ÈÚ&  jÿéôÃ73##5'67#&'v}. 0:VÃ¥|$4E5)'%pÿéîÏ7#5##5##535335#335î!!55!!5!¡tVVt..P===oÿìõÏ&73#5#3267#"&55#5335#53533#3Î  *22;;ˆ_3 9P?S S~ÿòôÆ 73#53#3'35’bvq]NN:2-Ô.SA..qÿèö½73&''67&'#367&'xp"  !     ½U:  "!1LC&18 vÿóòÏ73533#3#535#}-,,4|4-†IImmwÿèðÒ7&'3#"''3255#3#Š 0G  42Ò Å  ­¹JÿéõÏ736533#&''67#&'a5@93 /C D4D  $$\! QJ&(Wb jÿèíÏ#7#"''3255#&''67##53653í  $ $9©¨  ‘  "C¯ÁÿéùÊ!'+7367'273#&'#5##5'67#3&'#35#W#'YY '/vB1 {7I5„'{{] Y .Z2ÿéðÏ73533##5#'655#535'6ˆ J22J? 7;;>Ë<``ssC*&67 WÿéñÌ73533##5#'67#535'6§ * *+' %Ë=]]ooF-):5 ÿêïp73533##5#'67#535'6‚*J11L .& 9;+8p..AA*+tÕÇ 73#735#35#+ªª‚‚‚‚ÇS21\ÿéñÌ73533##5#'67#535'6© (()% %Ë=]]ooG,(;6 ]>ïÍ73533##5#'67#5365'6­"*+ !" )Í "<<<<+  ÿéóÏ73#3##5#535#'6B‘Qddnn5!ÏBbbB# (aÿéòÏ7'673#3##5#535‹  Y088BBŸ"2 AbbA ÿékÏ73#3##5#535#'6(4(( ÏCccC $[ÿéóÎ73#3##5#535#'6€a399FF" Î @bb@ #€ÿéóÏ73#3##5#535#'6žD&,,11 Ï CbbC GÿöÊ7&'37'5#c  * Ê :n  qUÿèòÆ &735#53#535#33##"''3255#53&'eej~~jeP((   ``5  ža07  3OÿéóÊ)7#5##53#3533#3##5#535#'67#536ït: U_ .;;EELLB!,Ê-,..8ÿë§Ð7&'3#&''67&'767#y^     ' =Ð&*  +/’ÿéòÐ73#3##5#535#'6«7""$$  Ð BaaB "Eÿé“Ð 73#53&'3#3##5##535#lN @@@@@Ð >U V8&2ÿçðÑ/PV733#3'67#73267#"&55'75#'6655335'673673##"''3255#7&'‹LLO ;)+ % ") <(=,?5 <   =%Ñ      F;, +Q„  )  kÿéòÏ7'673#3##5#535’  S-44<<  !7 BbbBfÿçóÎ73#&'#5&'75#'6556Þ &2]$  &7Î *7 :I ))4,'+xeÿèöÈ7'6553#&'7#3j $ ) BBdP,%UUdJ [Q>lÿëòË$733#353#3267#"&55#5335#53¢==0   -66Ë[AR/  5RA[T ðÏ73#3##5#535#'6{b4>>JJ" Ï 6JJ6 NðÍ73##5#'6556Ú 0?y*:EÍ %cc3*-1< ÿéïÏ %73&'''67&'#'655667Ð DZ—.-  ()"`2%&Ï):+    3<2.2fO, 'aÿêòÎ"7'655673&''67&'3672*(0W   uQ7 5X6  %9+() ! ÿétÏ%73533#3#'67#53655#7&'7'6%##&(  '*%  R  NN, #P QÿêóÎ 7'655673&''67&'367ƒ8/,6` uR6 5X6  %7,(*#$?ÿêóÎ"7'655673&''67'&&'367r  @64>n ' $  uP8 7V6  %:("#eÿêòÎ 7'655673&''67&'3673*)0W  uN5 4U5  %;)(*!"oÿéóÐ!73&''67&&'#'655667Ý)1W   3 Ð '=* 2V6 9Y7O##’ÿç÷Î$)'655673&''675'&''&5767¡" 8      7\8 )<(  s$"UÿéœÐ733533#3#5#'65c ( È;CC)M<5$ #4 ÿéfÅ 73#735#35#35#'67&'QQ------ 3  Å¡qNN3    ÿègÁ7#5##53'>&'g.!  ÁЉœ'O0&  "+0   ÿìxÀ7#5##53'>&'u5%  À™†‡š&N-'  ").\hÏ73533#67#"''3255'75#&     *&·  __õÏ(733327#"&'5#&''67&'767#53‘+      Ï1       ÿèòo#735#535'673#3##"''3255#hSS(#k6.PPhh  h$   ]ƒÈ 7#3#3#535#35#5#‚&$"%jCC È-k-.ÿéôZ"73#3##"''3255#535#535'2Ö &.WWii ggXXX^Z      †]ñÆ7#53&''67&67#” _  !1µ (    \˜Ñ.73533#3#3#"''3255##5##535#535#'6+00::2  0==  Ñ     ,   ¦`äÏ 73#"''3265'3#Ñ   +ÏX FGJÿî¶Î#73&'7'#"''32765#'667#'6l *    Ê&!7 A! `! K670/+ bóÏ#73#3##"''3255#535#535#'2Ò $+TTgg  llYY&,XÏ      ÿé}a"73#3##"''3255#535#535'2t((..  00**'2a      ‚ÿéóa"73#3##"''3255#535#535'2ê((..  00**'2a      ÿè¡b 73#'3'6673#Ž]-bzz8/)-kÿæ¦U733#"''3267#7#'667#v !  # '$U#3 #$*!ÿéöÑ 73#'63#3327#"&'#>ž¦  ŒÑ $98+0CÿçöÐ 73#'63#3327#"&'#;¡« œœ°œÐ;;"33GGð "7&''6733267#"&57&'v :%   *$   #+  0gÿìñ~73267#"&553#"''3267{ - 7&u  le  }A (ÿèV‰ 7&'&''6.5‰     #  ÿ鈓#'+73533#3#3##5#535#535#35#33535#335211++4444++2*A*‡  C  C ( ' ÿìóŠ7#3267#"&553#"''326Ò/  " U   xq   ‰Hmÿïõ‰7#"''3267#3267#"&55ß   I ,  6.‰B#,k   ‚uÿïñÃ7#"''3267#3267#"&55ß   B $ )#Ã_#I¥ %! ¾ MðË'+7367&'##'325567#3'67#3#- *8 {T: 1>››Ë  +& , (=.ÿëíA7#"''3267#3267#"&55Ì   u2D N>A! * ?Dÿëä87#3267#"&553#"''325ŸG +4  ?8o   ($7 ÿçë  #733#3#5335#35#35#'67&'oOOV±H4‰‰‰‰‰‰% 5+b#( )%   jj+,"    ÿêê¢#'+73&'73#3#3#3##5'65#5#35#<6RPFFFFTªVAAAAA¢   h #** ÿê¡Î"8P73533##5'67#7'6'&'&'3'67#&''635#53533#3##5#'::: *q L  U H% 0  B"" %£++.&  <  ;   ,16  ((Iÿéð‰7#533#"''3267#7#'66vw +  . .w3I!03A72ÿéR‘ 7#5'6?  !‘z\ &hÿèóÃ733#"''32767#7#'667#z` %  & #ÃDdJD`KG[3ë«,7#"''3267#5336767#"&55'753753×   +' $" F0'™?! B= ; <(# '"H Ü«7#"''3255##5##5353Ü   ,,@ŒO  8mmVh\ÿíóÊ 73673265267##"&55'67€&$/  *   ÊU% / N 0 I ÿìôÍ %73#5'675#53736732667#"&5T"& CC;" #* Íá7D:Ka  9 ç¯ !73&'73#3267#"&55#'667;DB›x /•  D  7$  *3 ë±  ,87&''63#3#735#73#735#&''67&''6Œ%. 0'"* > PPBB=BBM  ^  ±   + +     ÿöñ¸ 7#53#3#.⯤¸¥œ 4äÂ#'-373533#3#535#735#3353#735#35#35#'67&'C>>>O°M>,,@,}iiiiii P±# 2O7    ÿðîÊ7#3#5ãµÀÕʸÚ/ ÙÂ+/377&'73#'65535#53#"''3255##5##575##5##5#† ?ˆu\  J§  3- `f(^H !!!!7ê«(,73533533##5##5#3673#3#5'67#35#:(5((5(<bl^u 04QQš  ;% :ÿéñÏ 73533##5#fhhfNN……bÿòîÇ7#3#5çqxŒÇ¯Õÿêí 73#5#533ÙÆ²Ž¤—„.ÐÄ7367&'##'325'3'67#{  G: '&ÄD #(9  j25;ÿéát 73#5#533ͯ›sŠ o<ÿé£Ð73#&'#5'67#535'6˜ !!!  &&*Ð * lf!$0%ÿêèÏ7''6767&'®# QQ 6 ),@Mh6? Q{Š9'!ÿçòÈ!%73#67&'7&''67#53535#35#35#É)†A3  FUE*iiiiiiÈ„    „-FE ÿèíÐ7#'66553&'7í¶ _ ²J?. -V 9ÿêïž73533#&''67#@GPH: 7A DEl22CF9$'78ÿèñ¢ 7&''6&'3'67#‘', *+"- > 0Œr¢ %"  # ;ÿîê›$733#353#3267#"&55#5335#53ƒLL,@  (@,HH›>*<  $=+>&òÈ#7#53533#67'7''6735#35#35#RD)“(†=1  FTkkkkkkX^^    c.. ÿñóÉ732767#"'&55'753ñ… ()93ILŽ]&_LIÿéïÁ7#53#3#"''3267#'66QAߊq  ^"&®i(S.;B ÿêóÉ73533#&''67&'7655#xHI0= ?2$= ; !wž++ 2#"!)$0ÿéïÁ 7#53#&'#n\Ým1"!$®8vÿêîÁ7#'>5#53##ž8#  AÝ<®L#2'1NÄÿëêÏ73#"''32767#5333#H—! —¢Öšš—/Y$ ;z%cÿêìÇ73#3#"''3267#5335#365#Øf[  ¢5^rFHÇ&y*aNeeÿëìÇ73#"''3267#5335#53#3†b" ®8^ØfRy(I1VCc(ÿîïÄ 733#537#537#37#37#,ª(Þ5 ') 0:UQYUÄÃNOOO°NXÿêôÏ/733#3#5##535#533567#533##"''3255#š99@k?99BH HaBB   HÏ$$„ & " ÿéô¤'-26733#32767#"&55#'67#5'6367#367#3356/ $ D O) / ( $ $ "+¤ =*  /7 (5  6 sã˜73533#"''32765#'67#ˆ*  8 2W8K @[ÿêøÑ$*/4733#3267#"&55#'67#5'6367#367#335? .+  / )' #- 02'Ñ K; # A:,E  @'jÿéðÄ73#3#"''3255##5##535#j†94     49Ä%i  Qo‚%rÿééÐ733#"''3255##5##53£2   1Ð%|  d¯¯”ÿéâÑ7#"''3255##5##5353â  CCW«‹  s¯¯£&& ÿéòÎ 733#3#"''3255##5##535#53yeeW  CCWllÎd  L‚‚i|YÿèöÁ73267#"&55#'655&'Ñ   0 "-  Áº ! ¬H-:$SR=#$&"RìÐ &767'5673#23267#"&''3&'0) ]T ''UN "-RK#Œ%   f   &%  ÿéðÑ)73673#3533#"''3255##5##5'67#I  ~‡ 4H  43 (?®&&R  :rr]W *4ÿèçÈ7#"''3255##5##535'27Û  <<P)/bc *2‡j  RŒŒk~#$ÿéñÑ#7#53&'73#3#"''32655##5##53vgf fgN  ::N›"\ D}}cvrÿéêÎ&735#53533#3#3#"''32767##5#535#y'..44--6",,'< VV"ÿéßZ7#"''3255##5##5353ß  @@TH8  LL>QrÿèõÍ733#&'#67'5#537'6…V4* 2  FÍZ? M]  jU) ^ÿèóÏ)-7533'67#3&''67&'767#'665535#¦4 -   D ¯ 10$    ;/ ,TD1TÿèóÏ)-7533'67#3&''67&'767#'665535#¡8 #1   J ¯  11#    <. -TD1Cÿé¡Ð(,7'6553533'67#3''67&'767'5#c    \B1 6D@"" 3 %    33ÿéOÏ73533#&'#5'67#   ¢-- }s ".fÿéïÑ 473&'73##5##53&'7367533##'2655##5##5k27„c" *0  ¹  #((   2: &XX?PXÿîñÂ1573##'325567###535#73''67&'767#3#a}  b0F6     "`˜˜Âr y "d nR@,! y ÿèóo73#3#&'##5#'67#535#735#,¥HeJ!. ;#$7 . HcIo5  33   ÿé…7&''67&'76n ()…1 šÿê⇠73#"''3265'3#Î 4‡‚ i[{íÁ7&''6767&'76Ô Á2%!"!)&! XÿìõÍ!7353333267##"&547#'665#_%<     &2 %00U:  1Mo)?;>ÿëôÎ!73333267#"&547##'665#53z>    (B $%Î1W<  4Oq)A:[ÿéôÍ7333265#"&55#'665#53ˆ1   Í0 ! ‚G"&A< `vÏ7365333265#"&55#'67## ²  1  %," ÿéwe 73533##"''3255#&'''6)))  )S  3  SC @  tÿéóÐ"&*73&'73#3#3#3##5'65#5#35#”  $!!!!&X 1 Ð'"""‡ /*""3""3"qÿêôÐ#bfjnr73533#3#3#5##535#535#35#3353#35#53#3#3#3#67&'67'5'67#535#535#535#735#35#5#35#s666--9c7--6/Q*  '!!$-      ,!  /È !&&! 0          1 ÿépÐ  $(773673#735#35#35#3#735#33535#335#53##5#'6)S//////ZZ$6$8a  Q9   ;$  1,, ÿé{Ð !%)873673#735#35#35#3#735#33535#335#53##5#'6.[666666bb(>(>m ÂQ9   ;$  1,, uÿçôÏ #<I73533533#735#33533535#335335#3#3#7'5#'655&'76„jJ^UU_C  _  # ÁI++*  - 8" %6K  ( iÿèôÐ.6:>B733#3'67#'65533267#"&55'7533#53535#35#35#§((9U/,    2 l,,,,,,Ð   G9/ .6Q!   4QQ))zÿíôÐ !%8<@7&'67&'67&'63#735#3267#"&553'#37#3•   .  0   LWW55" -% a8'Ð        2-e U:+vÿèðÆ%)-373#3#"''3265#&''67#'67#735#35#33#…`8 M     ::::>PÆSVB      21X(xÿé÷Ï37;A735333##67&'#"''3255'675#535#535#33535&'‚%,,     &&--%7J »%$   $(   1%2  wÿéìÐ73#"''3267#'6##535#˜K  @ 8&Ѓ #)SeC3 ÿé‘Ð !%)873673#735#35#35#3#735#33535#335#53##5#'6!6lFFFFFFvv1O1L„$!ÂQ9   <%  1,, ÿêòÃ73#3##5#535#”X!''((#ÃIllI ÿéóÐ !%)973673#735#35#35#3#735#33535#3353##5#'667#.9W¤ ¿¿BBUB—BBUBÁè9FCÃS;  ?& ! !&& =ÿéòÐ !%)773673#735#35#35#3#735#33535#3353##5#'67#X1<[[[[[[šš00C1t00C1—µ0+2(4ÂQ9 ?& ! !''# VÿéôÐ !%)873673#735#35#35#3#735#33535#3353##5#'67#i+8vPPPPPP††&&9'`&&9'~ž(+#%ÂP9  =% "(( `ÿéòÐ !%)873673#735#35#35#3#735#33535#3353##5#'67#q(5pJJJJJJ€€$$7#Z$$7#v’"( "ÂP9  =% ! !(( ÿçžÐ !%)77#53675#5#35#5#335#3535#3##5#'67#‹m 'GGGG~7$$4$4$$$O‘ ''"ÃRR == !((#FÿèéÇ733#'3267#737#3#[{" $ j  7 ewwÇe229XCR„mÿèïÆ733##'276767#737#3#tj %d /U\\ÆiS" +XCV„ÿéé733##'2667#737#3#&—4(¤ Z€››?<2.R„ÿèîŠ733#"''3267#737#3#ˆW L #BEEŠF@(9&4YÿëêÉ733#"''3267#737#3#+ž ,  “T‡ššÉhS#5ZEU‰ ÿêvÈ733##'26767#737#'6ZJ #F8!ÈiQ$ (ZDV|  ÿênÇ733##'276767#737#'6PA =1ÇiQ# *ZEX}  ÿêiÇ733##'276767#737#'6L=9-ÇiR" )ZEX}  ÿééc733#"''3267#737#3#&–4  ¤ [€››c/3+>ÿéél733#"''3267#737#3#&–4  ¤ \€››l28. Bp…Ñ736533#"''3267#'67#&4  #,%#¾ 3 &ÿêdÇ733##'276767#737#'6H:4+ÇiR" (ZEX}  @ÿøºž7337#533##'32665#3#Y%EY VVV|'7IB" ÿ耒#'+/73533533##3#3##5#535#535#5#5#35#335"))++55))J"*‡  .  . ;sÿèöÏ73533#&''67#z,63 /# ) 2,’==T1(?>)5PxÿóòÆ7#3#5&''67&'76îcgz\   Æ®Ó%%  % [ÿéò^7&'36533#&''67#ÌV9;>% "-7 5^     !dYõÏ73&''67&''667#E  %'"   ;Ï      ÿèÖU7373#"''3267#'67##C[ ME :<@ >#5 &+ÿèÓN 7#5##535#33535#35#Ó‚77J8‚77J88Nff';RïÑ7'673&''67&'67A p9 @%-= 5&” %     !ÿûëÇ73&''67&''667#Vf $75')8 6% & "WÇ=!  1%/ÿèñf73&''67&''667#] q !5A*,9 4% 1;]f    - VíÑ )7&''67&''63533#3#535#> ~   ŒQSSc×`QÑ      'AA ïÒ;@PTX`fl73#3#53&'#53'367#35#5'67&''673&'3#3#73673#3##5#535#735#35##5##53'67&'G% ^# 92    7  @!5w# «O!))((////¶=&F ? Ò   ,  /    J /:  $ '), ÿêì !7&''&'''6733265#"&5Í  6M + " ,#        JÿæóÑ3=GMSY_733673#5##53&'733#735#3#3#3#535#535#73&'735#336735'67&''&''&'•  ‚% 1ssQQ ’@@@K¦I??@  0Bv ˆ    Ð   # !4    V        +òÏ $(Y]aek73&'73#3#3#3#5'6353535'3533533##3#3#3#'67#53655#535#535#5#5#35#335&'Ÿ ##c  ¬(!))--7; &  (1,,))N(*  Ï  V  $%o$    $+ E \óÑ73&''67&''675667Xk(/ ;*,< 6&  Ñ       {~óÔ73&''67&''667£=   Ô       |ÿéóJ73&''67&''667# ;     % . J       _gñÍ7'673&''67&767{ I ($  —     aÿêãb)/573#"''3255'675#'3#"''3255'675#&''&'¨;  *C;   *P 7 bd  #d  #    ÿê[Ð 7#5'673#:  Ð#¢| /ŒRÿõòË 733#3#53‹NNR 9ËFj ZóÏ 733#3#53k``sæ^Ï#- ÿïòW !7&'7&'''6733267#"&5  ] • /  -#W  ( 7 \ÿõòË 733#3#53ŽKKO–2ËFj\ëÅ 73#&'#5#Ój/! !"UÅ-VÿéîW 73#&'#5#Þh/) ('dW  :\ `{Ï73533#&'#5'67#,&&  #³  +/v`õÏ73533#&'#5'67#*.%  ³ 00ÿéî} 73#&'#5#Þg.( '&c}R7ÿôÁ] 73#&'#5#7ŠB! 4]  :XÿéìÊ "7##535#35#7'3255#535#5#uN::::Å'"O<<<<Ê\…á&:7Ä b\&:€îË 73#&'#5#Þh.)('dË  #: ÿçúŠ-15<7327#"&'#'6553#&''675#535'235#35##67&'Ö Ž 1  2<111:#0$$ ŠHH-.d;0& &.D/ / < RÿèöÊ 7&'''6à ) / +2-Ê<Aœ“JClÿéòÏ 73533##5#l;77;NN……Iÿè÷Ï7&''67&'š)%/ 0( Ï&j7 dT11["gÿéðÀ73#3##"''3255#535#qt.99 <<2ÀAV  QA]ÿîõÐ $73#'6333267##"&5477#Xa "]K   &=@Ð "!P  AgÿéîÊ7&''67'67676767&'Ë-5%"!" +3& 9 '  %8%C:$[1cÿéôÆ733##"''3255#53567#ss>>  AA[Æ\ Y!UÿôöÏ73533#3#535#e8<3 AC   14¤  <P  T ÿîßÅ73#735##53#=#/  xx¿—ÅR,­mn11]ÿêñÍ 7#56'&'ß "(VÍ/‰’#+ ÿéäÐ73#"''3255##5363#735#f w  ž9 cc>>Ð ©  ‘°ÃCV2ÿëòÎ#',73533#3#&''67&'767#535#35#335cccTV1LP74, QTc#@@T@¹K   KN% % ÿéíÍ73#'66556#5##535#Õ E^±± kJdddÍ B85D9lmnI6 ÿéãÏ#733#5##5'67#&''635#er%U|y‰2h :yyÏ:h]A  !Ÿ3ÿççÇ7#"''3255##53#3#735#ç  ¦&ƒƒ iiAAÇÅ  ­Íà/N(ÿìõ¿7367&'327#"'&'#   ‡¿ W$5!-r8í73533##"''32655'67#?O!!   0 0?Œ!!M E4 !%ÿéíÏ73533##"''32655'67#’33 :G N<Ž¢--Œ lC%(K<ÿì¢Ñ$7#"''32654''67&''67&'76Ž (3"  Ñ'>'( #  jÿéòÏ73533##"''3255'67#yF #% /&F¢--Š  ^?!)O4ÿñÈg73533##"''3255'67#=P(( 2 .>U9  /$:ÿøÅ¦73533##"''32655'67#@I(( "+#H€&&\ 9(0MÿèôÁ73265#"&55#'655&'Ë 72  Á¸  ªG/9$SR="$&! \¹ 73#7'675#I")¹~  „QÿèöÁ73267#"&55#'655&'Ï  5 $.  Áº  ¬H.:$SS=#$&"AÿèôÁ73265#"&55#'655&'Ë >$6  Á¸  ªG.9%RR="$&!lÿéöÁ73265#"&55#'655&'Ô  + + Áº ! ¬GS+$RR=#$&"KÿèóÆ'733&''67&''667'367#67#'7#co,  !# $3Æ2D##A,;3 63 *3 ÿèôÆ!733&''67&'767#'7#'67#˜?'*' !2 1 4A> 7,Æ2@%" #'2’82‡cÿéóÆ#733&''67&''667#767#'7#va &   +Æ3E$ @*G^53#-4ÿèõ"7#533&''67&&'767#'7#'6E/ª 0+.7#!1 - !" P 2.n.    O2,LÿéóÆ"733&''67&''667#67#'7#cn-  !#%5Æ2E"!B,F]74 (3]ÿéóÅ!737#53#33#537#35##5##535#j /J=–!(5.NSSS˜4"""FW W8& ÿèïÏ7Q736533#"''3267#'667#36533#"''32765#'67#73733#"''32767#'67#Hg  T, * D (.   '" %u 5   # '" ¾ 4    W L .=0Q ,>0 ÿíÄ73#"''32765#'67###535#p  #.)$hL88ÄJ $;!,_VeC/ÿéïÆ7#"''3255'7567#536íd dh)!˜³$,5oR L  "ÿéîÆ7567#53&'#"''3255&'" 4)!˜³$,!B 3& =&€/+  < >ÿéïÏ 737#5'7'&'&' 9;…ˆY"  $#χLJw*ÿéðf 7#5'753'&'&'î;‡Š]  $$"  IGE   nÿéõÏ 7#5'753'&'&'ó#JMH  OLIІs,«ÿèôÎ7#5'67536'&'&'ñ0    EE?”Žw;¦ñÍ 7#5'753'&'&'ï'*.  g40  miW   %   nÿéôÏ 7#5'753'&'&'òLOJ OMI‰†s,  ¡ÿéôÏ 7#5'753'&'&'ò*.5  CC>  ”w  2  Tÿé«Ð73#&'#5'67#535'6¡     #Ð, gc$.&BÿéóÎ $(.4F73#32767#"&55#'67#53635#3353655#3354''6767&'u &' * &  )  7  Î fJ NC :f9F  M  €ÿéñ{7&'#5'753'&'˜ cADL {  >2/  PM'  ÿéy"73533#&'#5'67#7'6'&'(''  %P  >  L3395 ;    zîÑ 7#5##53&'7'67&'î¶c+#1 -].& (,½%&      |ÿéó“ 7#5'753'&'&'ñDGB  ;;8  `\[     ÿéu–73#&'#5'67#535'6m$$  &."0– MO & ÿíóË733533533##5##5#5335#35#+6666K66½;II==‚ ‚ubbb_ÿèôË15973533533#'3255#'655#'3255#'655#75#35#b 8 9       0Yk````V  Q?##> V  Q>$"? OOOOKÿèòË15973533533#'3255#'655#'3255#'655#75#35#O >?  8hm^^^^W R?#$= W R>$!@ MMMM NÂ73#3#67'675#535#? " Â:B H:WÿéõË!%,73##"''#5##5##5#53535#35#335#325ê   && Ë]Z  eeess]]KKKKK]\ _ÿéöË!%,73##"''#5##5##5#53535#35#335#325ê  ##Ë[] fffuu[[IIIII[a ÿé¯Ì"&*73###'2655##5##5##5#53535#35#35#   &&Ì`W R____pp``NNNNNÿè¬Ë15973533533#'3255#'655#'3255#'655#75#35#9 9       8ZsXXXX]  X@""?]  X>$!@GGGGÿèóÌ37;73533##"''3255#'655#'3255#'655#53535#35#tQ   (&&&e((Ì___UO=$#> S M>#">__KKK ÿéöÌ"&*73###'2655##5##5##5#53535#35#35#ß &%%%%8%%8&&Ì`V Qaa``pp``MMMMM RÿéóÐ#+37;?73533533##5##5#3#735#335335#5##5#5##535#35#35#Y%%%%%% ~~##}…FFFFFFFÁ / -$%bb $ % fÿéóÄ73#3##5#535#n~5<<==5ÄImmIiÿêîl73#3##"''3255#535#pt(22  ??8l(  % uÖÉ 7#5##53#3#ÖŒ#qqqqÉTBBT ÿøbg 7#5##535#b$$$gloL;XÿéðÀ73#3##"''3255#535#d€4@@ DD8ÀAV  QAiîÒ 73#5##53&'&'''6„_³d1&! %. ", 2Ò ++ , ÿêîl73#3##"''3255#535#-¦Faa  ffLl(  #[ÿéòq73#3##"''32655#535#f‚3== FF;q * ' ÿéWx 7&'&''67   0x     ÿénÑ73&'73&'#5'67#!   )=° !hc @UÿéòÎ &7#5'673533##"''32655#&'‡ "4   4   Î!£ ,77 { ÿèìw73#3##"''32655#535#)²J[[ jjTw!. *!zÿéðÀ73#3##"''3255#535#ƒc%// 33*ÀAV  QAÿêŒÃ73#3##"''32655#535#v.22 664Ã>[ V>ÿèðÊ73##5#535'6Ï?ffee!, xÊ>rr; SñÐ73#&'#5'67#535'2Î $+`N"4 8%!8 3#M]"&SÐ #1/ÿéð[73##5#535'6À!#ffff@ f[ ++ÿéïƒ7'673##5#53v$% y0$+eeeef )CC—òË73##5#535'6à &&"" $Ë6[[1ÿñàÌ 7'6'66'66­ .U Y2 0^ 8<3Ax HOÌ( "0  )9 ÿöòÎ73533#3#535#[]]iäg[ˆFFllzÌ73533#7'675#('')-7(”88N SmÿéêÏ 73#5#533533Öi !š±¢ÄÄWÿêóÐ73#'66553&'° 2i!5ÐI<0 , U FÿèñÅ #7#'66553533#"''32767#'67#ñr&!3  1+ÅQC4 0#^G$$o# K]&!SYÿõöÄ 73#735#35#3#nuuMMMM)ÄœW2v1ddÿîóÏ %7#5##53&'736732667#"&5îb> 9' !/   *"¬22 ./ . ' KÿêðÇ %7'66553'#33673267#"&5~"zUUC! !'  $ D5 3!_F5$'  -   K¥¹73#67'675#QN&0¹z €¢ÿñôÁ 73#3#535#¤K RÁªªEÿù§Ñ 74''675#53533#7&'75##5#– #)  4 4^,,^/ )::::@‰¹ 73#7'675#EB& ¹y  €fÿñóÁ 73#3#535#p~6;>4ÁªªUÿñôÁ 73#3#535#[”@EŸF@ÁªªmÿñóÁ 73#3#535#vw28†90Áªªÿõ|u73#67'675#c(/9'uJ O‹ÿçêz7##53#"''325Ö7_ g€“f  …óÈ 73#3#535#'²NhæjPÈÿæôÅ7#5##53'6656&'ÒuB187,&,+&2Å™††™-5*92': ÿæéU7#5##5'66&'ÏxI16 3*/) ,,UD35F"&  ÿçìÏ"(7#5##5367#'6733'6656&'ÕyQ D!1O 'i 8*'''"-Šr__r$$'> $*ÿççÏ 73#5##535333'6656&'‚S‚Cctd6(+&%!+§kXXkBA'> $€¹73#67'675#d*-: &¹~ ‚iÿèíÍ7&'73#"''3255#3#~ .Q   ?3Í Á  «·~Ô¢7&'#53#&''67&'76ª-R     ¢  . bÿéíÐ7&'3#"''3255#3#  5J  7AÐ Á  «·TÿèìÎ7&'33#"''3255#3#k  3^  L:Î Á  «·oÏ’7#53#3#3#535#53–Q &`'#""}ÃŽ 73#735#35#}FF$$$$ŽtB"T"y ÙŸ!7&'33267#"&57&''6§   *  2 Ÿ#S U"ÿææŸ7&'73'3255#3#:  9‚ #oHŸ   ™ ‚ –3ÿôÀ#'+73&'73#3#3#3##5'65#5#35#]+-$$$$,a1#####   T @ÿëô¸7&'333#"&''75#_ ( -1 :!  ¸ 7c  UzîË#'+73533#3#3##5#535#535#35#33535#335z100))2211))1(?(»YY57CÿíõÃ7&'333#"&'&'75#b  $,. 7 ÃA`  Qÿèó‹7&'333#"&'&'75#5 7 %9@ J)&#$#‹ -C   3ÿèó‹7&'333#"&'&'75#5  0 %;D N*(‹ /B   3ÿçö‘7&'333#"''75#(  1 #%¢--€rx.*l ÿæ|Ï73533#"''32765#'67#$5  "/)$¢--eA m{.+ojÿéíÏ73533#"''32765#'67#;  (93£,,Ž('Wy.+mIÿèöÊ 7&'''6¿ , 2 ,1ÊŽ>@Uc%DzîÑ 7'67&'T/ -q #Ñ - "%1 0 ÿèõÄ 7&&'#'6©= .A%#AÄŠ:rIOb$@iÿéò… 7'67&'@ " ( …@?,``(,jp‡ëÐ 73#5##53&'«4U2Ð 22 cÿèöÊ 7&'''6Ê$ ) (,'Ê?@’KC”ÿêòÇ7''6767&'× "H'/ G~uC$êÐ767&'7&''6k49 KV*Ð    ÿéÐ73#&'#5'67#535'6u *++  $02.:Ð *oi#$0&$™ÝÒ767&'7&''6j-0HP)Ò    Kÿèói7373#&''67'67#367K5^  % 3 -4 +A Z #   RqñÑ7#53&'73#3#`GBw‰¥  !ÿêæZ73#"''3267#'67#536qdVNEPVZ B (; ' gÿèôÏ 73673#&''67&'67#67#w!G+ * =% ¡D( "  $(_"=& rÿìóÌ7'6773267#"&55'7Ÿ6. 9;  *-£  <P  S ÿèöÏ+733327267##"&55#&''67&'767#532+* 7&   Ï.˜  Œ$ ,& $0  kÿéøÏ)733327#"&547#&''67&'77#53,   "#  Ï-5&$#&"$7 )  28  'ÿéäÏ!'736533#"''32665#'67#7&'&'"Hd  RU J G  e  ‹''Y5 96b-&VN` ÿéäÏ#'73533#3#5##5##535#735#33535#335'OOOYEEYO;;P;•EEZEºEV//Z |!!!ÿèíÉ  $7##535#35#7#"''3255#535#5#uN::::Æ P====ÉL”à,-É  xL,ŒÿñÓs73533##"''3255#&'Œ)   )aG  B Qÿåë†#7#'67#533#"''32767#'67#'6}4#Ty$(^  > 9  '"<L )9*  O@ëÈ!7#'67#533#"''3265#'67#'62"T{#'Z    ( # )%…? - %ÿåõ?7'66767&''6~N 4-  # 6{ % !  @ dÿéî‰"7#'67#533#"''32765#'67#'6‹,6W"R    * & $CR*;,'jòÏ 73#'6Š]e Ï  6ÿè›Î 73#5'67'&'‡ ' /":Îæ_UfÿôôÏ73533#3#535#f===7‚7=…JJkk ÿèZÎ 73#5'67'&'F "#  ÎæYN  ÿéóT73673#&''67#]l]H M 4( @T;   -" !eÿéóÐ73#&''67&'67#5367#6£P( $ #)) #¢D*  '+=*!) ÿèL¢ 73#5'67'&'9   ¢ºE = ÿéNÏ 73#5'67'&':   Ïæ[ T ScÏ 73#5'67'&'P"$  Ï|% = ÿêX 73#5'67'&'D!  ¥>  :dÿóî73533#3#535#d;;;5}4;\44CCPÿéŠÏ 73#5'67'&'v ÏæO  PhWáÍ7'673'67#&ƒ DJ @7  ‘# =.  ÿéñX#7367&'#"''3265'3'67#x$!"( 9#   cT3 -?T   $' =*bÿéöÏ736533#&''67#7&'j3@7 0* 7 42j  Š('L/,IN'&VKJÿé•Ï 73&'#''6j   Ï. ¡¹, Bÿé¹Ï 73&'#''6uÏ1œ·* ^ÿé÷Î736533#&''667#k485 5* : 3""R0(B=-<0gÿêòÏ73533##"''32655#&'gY Y  ›44ƒ ~$ &cÿéìÐ 7&'3##5#&'š  '‰E0]Ð (¢¢ aÿèõÏ"73533#3&''67&'#535#67#h9::/" #% # 29D > ©&&'&    %'kkÿèôÊ %7673#3267#"&'#67'7&'kD0.-  0! A+» &1"5@Z  {#;aIöÏ7365333267#"&55#'67#f$4  !- ##²  I  ;<0Xÿéñ_ 73533##5#XADDA;$$??kÿéëÏ7#5##5##535335#335ë"!65!!5"¡tUUt..O<<<`ÿèôÏ&7#5#'667##5365333267#"&5î(#+   ¥7$CIE=$7HV " VÿëôÏ$7#5#'667##537333267#"&5ë&!$ !"8   ¥7$CHC>&9*HU bÿìøÏ$7335#53533#353#3267#"&55#k;;AA!4  $ 2ŠAR RAS/ 5 ÿìó)73533#3#535# WWWhçkWQÿîöË$(,0@76767&'#3'67&'767#3#3#735#3673#53&'²     9JC  0-77kkEE " (›&Ë    I    39-  eòÐ 73&'73#e<<° eÿéìË767&''677&'3#5#533     ]s`Ë"#&5'$&$ .(¿ ¶£ÿéåÈ767&''67&'3#5#533H#$(!%˜·£¾"#"-5''$(# À ·¤SÿéêË767&''67&'3#5#533    k„qË#$ '4'%&& %$.(¿ ·¤nÿêé”767&''67&'3#5#533”     SgTŽ.) Š nÿéìË767''67&'3#5#533      JXEË!#&:*!") #3,¿ µ£gÿìõÍ "733#67'73673267#"&5g&& D  ÍJl  ×R` " ZÿéôÌ "048733#67'73673267#"&53#5##53635#35#Z)) G   ! G`*````Ì#  \    q q 3@ÿéŽÌ "048733#67'736732765#"&53#5##53635#35#  8    9EEEEEÌ"  ]    qq 3AZÿîõÎ"7&'332765#"&57&''6   K YÎ.Š }'00'8.+Gÿîð–#736732765#"&5'33#67'› &  T11$ –4  F  “1K  ÿé÷a &,7&'367326767#"&''67''67&'{  <0,P1E ! °a-( +#  <! "\óÎ &733#67'73673325267##"&5"DD# , d" #)  . Î *  i'   $ *ÿëï\ !733#7'73673267#"&5*;;9&# _" #) '\.  e)   #tîÐ &733#67'7367327667##"&5#AA" + b% %,  , Ð#  R    ÿçñn27373#673267#"&55#3'67#&''67#;¦P% ,  &89RJ3  /m(    \ <0  ÿê÷i%7773267#"&55'75'75'6Ä ).bdvx'( 33SVHK? Si      "hòÏ#7367326767#"&5'33#7'„$ ,  * b??=($ Ï    J'  _ÿìõÍ $733#67'732767#"&5536_'' ˆ#  ÍJo  ¦[ ËV&ÿìòR "77'53373673267#"&5:9'" >$ $+  &+*  ['  ! ! Vÿñòš#736732767#"&5'33#67'¢ !   L-- š3  I –2N  "ÿéò #733#67'736732767#"&5"==' `$ $+ )!*E  ‰/6! pÿéòÐ !.26767'53373673265#"&53#5##53635#35#„ "     ;IIIII¤&  ^,!    qq 3A[ÿèóÏ!733#3##5#'67#535#5353335¾!!'%! &&&Ï.@SS1 '@..@@]ÿéóÏ 73#"''3257&'''6Ÿ  3  E ÏÍ  ™9D F8K4 6ÿéóÏ 73#"''325''67&'y :“ÏÍ  ™K5 6D9C F8^ìÏ 73#''67&'u5‘Ïqb 5")"' &# ÿñòf733#3#535#53wVVgåjWWf(((ŠáÏ 73#7&'''6wA\ÏD8   ÿèÚO 73#"''3257&'''6v   F  o ON  /    )ö†73673#&'#'67#Po;) 1*!; ,?s   &$‹ÿëôÏ 73#"''325''67&'·   K  ÏÍ ™L5 8E9DF8Uÿë ¿7#"''3255'67567#536‹    .Dg N B6&iÿèð‚ 73#"''325''67&'¤   &  f  ‚  \3$ $,%- .%ÿêr7#"''3255'67567#53h  D[M3 * ‰ òÏ 73#"''325''67&'µ   M  Ϫ  ~@, /:19;/F“Æ7367#"''3255'67567#MF     /Æ A 4"[ à733#"''32765#'67#53ˆE   4#)+ 6" '%-rÿèôÐ 73#"''3257&'''6«  /  < ÐÎ  –9C F7K4 7‚ÿéêÐ 73#5##53635#35#§4AAAAAÐ ÄÅ^:‡:|ÿéòÏ733#3#5##5335#¡==4E%EEÏ,0wwRA„ÿéóÏ733#3#5##5335#¨771C$CCÏ-0wwR@\ÿçòÐ &,73673#3&''677&''67#677&'rNPG   # #  £"6#    /O4 o jÿ÷óÊ73533533533##5#3#5#5#j=bvbŒ44>>>>JJo‚888sÿéïÐ7#5##5##535335#33535#35#ï  44 4 T 4 ©‰ BB ‹''6###X"""mÿêòÏ 7#5##53&'73##"''3255#îU3M…4   =­00 <[  XpÿéòÏ!73533#3#67&'7&''67#535#y-,,8B # ,0+6-¤+++6 %* 4+ÿîñÏ#'+/37;735333##3#3#535#535#535#535#335353#735#35#3#&LOO]]jÞaUUNNccL_;;;–««………….ááÅ        U@' # $îÏ73533#3#535#dddRºTdÀQïƒ7#5##5ﺃ0 2 ÿêóÏ &D73#53&'67&''67'#'66553533#3#3#535#535#'6Š[Î\0$$ ! @(´¿ 6*CC==P·S;;5Ï    &1) :  ÿïïÏ'+/37;73533#3#535###53#5#3#3#535#537#335#35#35#35#dddRºTdL9Û800MÝL111O111ÀC!31;;)A ÿíòÈ#'->DJ73#3#3#535#535#5#35#33533535#&''332767#"&57&'''6ÏD55JÛI66Cx"6##6""W""" +)Š  — È;;)?,  ) /   ÿéòÏ#+/73673#53'35#33535#3353##53#=#Z % ,°. <¡ß " %à  €> " 3ÿê͉#'-373533#3#535#735#3353#735#35#35#'67&'@777CšD7%%8%m}}XXXXXX  9 ƒ   -A/    Wÿçò¢#'-373533#3#535#735#3353#735#35#35#'67&'e777D›E7&&8&n~~ZZZZZZ  C ›% 5Q:     ÿé]Ÿ7#"''32654'7##5] "Ÿ04¥¶ ÿéòÏ $0>K7&''63533##5#7&''67&''6&''6'&''613 43-< OOihhi:  x     c   Ï#!ww((             ÿì‡Ð048<@7673&'73#3#3#3##5'66553&'73'#35#5#35#* A  /'HH…'    X'! +"X 6&J"" ÿêòÏ#'+17=C73#3#3#3##5'673&'5#5#5#&''&'''67&'ƒ LPHHHHT¬ # 9 BDDDDš  &  `G  ÏX (, ;&%    ÿêòÇ*.28>DJ73#"''325567#67#53&''67&''3#735#&''&'''67&'/“!  wr - ÿétÁ7#5##53'>&'m4$   Áš‰Š›'R,(  "+0 ÿénÁ7#5##53'>&'i/!   Áš‰Š›'O0&  "+0  cóÑ735#53533#3#3#535#*KUUVVNNhæhKšÿæé\7#5##5'66&'Ñ{K25 3,-) ,*\L;76767&&'3'67&'767#3#3#&''67#5355#—  (6hZC E;QRT60 .4> J RV Ð   F* @%  6"$ÿèèÏ#17#"''3255#&''67##5367&''66è  O, (*1 C\ % #+ "¯¬  ”$´Ç H !!ÿèÞ—#07#"''3255#&''67##5367&''6Þ  D' % # + <V!  " 3}}  f  ‚•  9    ~ôÑ7&&'''67&'e!F( %+F=' Ñ 0  #ÿèÝy#/7'67##536533#"''3255#&&''6|& 9UR  B% %  '=  hy  `  J     nôÏ)733#67'7537736732767#"&5K""5AB#+  &Ï <:G    !ÿèÛZ .73#"''3255#&''67##536&''6€O  E# #!2T )Z H  3   Qa,   QðÓ %?CGKOU[agms73&'73#''67'6776767'7''67'677767''3#3#3#735#'&'7&'''67&'7'67&'V#R    ¢     |CCCC??!£Ã$$»            '   !ÿèÛR.73#"''&''673255#&''67##536‚M  ' E# $7YRD       /    M]UÿçõÐ$PTX\`fl7'673533#3#7'5#'67#53535#'673533#3#3267#"&55#'67#3#735#35#35#'67&'i      ",      A{{WWWWWW J °      *ZA $ %      ÿéñÇ73#3##5#535#735#35#(¯NhhffM‡‡‡‡Çn@@@HÿéåÆ 73#3#"''3255##5##535#735#0 EZ  FFZGxxÆPE  -__K^*Cÿèë 73#3#"''3255##5##535#735#M;J  78KAjj9/  <<4FqÿéîÅ 73#3#"''3255##5##535#735#|f(4    !5*>>ÅOE  .__K]+ ‹ÿïõÒ !%)73&'73#3#735#3#735#3#735#35#3#‹++j^^::** &VV2222ggÁ  S7 # 8@' # #8ÿûÏ73533#67&'7''75#735#335?  $"  ¥**^2  5>>>\ÿéöÏ"7#67&'7''275##535335#335è6 6E"!"65""5"¦c:$  <u))P===FÿèïŸ733#67'7''275#5335#35#==  +++Mÿìï“&73#3#"''3255#&''67##5365#M¢HC  0   (>H“ c  M $j{ Lÿéç“ &*7#"''3255##5#'665535#33535#735ç   &*  '':&a(':&“ 22" $G.JHÿèôÏ"7#67&'7''675##535335#335à8 CF'%%89%%9$¥a: #  <z**O===ÿéZÍ73#''665&'767#53&'1   0 Í*#)#. ]ÿçíÑ2=HL7&'3#"''3255#3533#3#&'#5'67#535#35&'75#367'35'3#p /^   L'((%     $' % eÑ Ä  ¬J 35JJ   ,6» ÿèyÏ"73533#&'#5'67#7&'7'6)((  ' W  ~QQ[^",Q \ÿéíÊ  $CGMQW7##535#35#7#"''3255#535#5#3533#3#&'#5'67#535#35#&'735'6ž0   0I)**%%    $)  ! ÊEœá ' )Ì „E '  6  $& 6 3    „õÑ73#&''67&'#53&'67ƒi. #1?-'E 3#)h!Ñ     …ÿéßÏ 736753#5'…" Ä–“æ@   ÿé¡Ñ.733#3'67#73267#"&55'75#'6553F==H7$&   -Ñ      H<- .7S ÿñÏ7''675#53533#7&'75##5#x/8++++5 7_--_4#====‹ÿéáÏ 736753#5'‹ Ä–•æ> zÿóôÏ7&'3#3#3#535#535#¯  #v0))3z3**2Ï &::::wÿéîÄ73#"''3267#'67##5##535#{s   ') #$pEEEÄL3>0_iiG4zÿéïÏ#7#"''3255#&''67##53653ï    2¨§  #A­¿hÿìõÏ!73533#67&'7&''675#75##5#o2334=2e¢--_4"" 7====oÿçöÈ7'6553#&'7#3“h ! '@@dR+&UUdJ [Q>`îÇ7'67#53#3#"''3265]1 : E܃t   ‘- (ÿåë€ 73533#7&'7&''275#75##5#+JII1Lc0.J”56a=   tÿéóÏ735#535333#&''67#75#u-$$-6#! " *Z]6))I311"06)wÿìôÍ&7773267#"&55'75'75'6Ú  1368  &)"%!<Í '  )  2 4( ' ÿñ‚Ï73533#7&'7''675#75##5#&'')1&M¢--_4  7====ÿèóÃ73#3#&''667#5365#!¼UdbNIU 1.^`SÃ4G#;?2$ - ^õÇ73#3#&'#'67#5367#%¶S x1 %&>/ .F IÇ    aÿçòÇ 73#735##53#3#&''67#536tllFF/w5>9 1+ 0/25ÇF"Z  "!"  ÿèôÉ 73#735##53#3#&''67#536.¦¦~~5O±OeWELVNW^ÉG#Z (  ;,  ÿçô_73#3#&''67#5367#$½ZjZD K/ M S[N_  *    BõÃ7367#53#3#&'#'67#M PºV p;-3+8.;‘&"\ÿè÷»73#3#&''67#5365#oz5=:0 , 7 =671»1D58D - WöÇ73#3#&'#'67#5367# ½V t5# +)2&* %5I NÇ  !" Wó¿73#3#&''67#5365#bˆ;DF&$$ 4 7=@8¿ & $/DöÐ?PTX73&'73#3#53'#367#733#3#3#3#3#3267#"&5535##"''3255##535#35#)+r($QTTMOKKKKGG% .% MM ;;;;;Á        J< N  }ÿéêÇ7#"''3255#'65535#35#ê  .,,,,ÇÁ0.% *3t=*f* ÿèŠÏ%+73533533#3#535#35#35#35#'67&'*|(******>  ±mmCC0  :ÿìòÏ2733#3#5353333#3#53533'33#67'75367’//B,!cV .$ Ï99 /LL_"KG ÿì¤Ï#'+/37;?E735333##3#3#535#535#535#535#335353#735#33535#335'6411::C—A6655AA4Gdww2Q2!FJLÆ        S@' "  YÿðòÏ#'+/37;?C735333##3#3#535#535#535#535#335353#735#33535#3353#k12288C“>6611==1Cauu 1 Q 1 v˜˜Æ        SB( % # HÿïòÏ#'+/37;?C735333##3#3#535#535#535#535#335353#735#33535#3353#X:::EEL§H==::II:M'''s††''9'`''9'„©©Æ        SB( $ $ ÿðïÐ#'+/37;?C735333##3#3#535#535#535#535#335353#735#33535#3353#'OPPUUcÙcUUOOeeOb<<<œ¯¯;;N;‰;;N;´ÞÞÇ        TB( " # aÿðòÐ#'+/37;?C735333##3#3#535#535#535#535#335353#735#33535#3353#r/..66><22//::/B]rr0M0pÆ        SA( # # ÿêhÏ#)/7&''67'67676767&'&'#&'''6X!     r  /)8! 7# ÿîôœ-159=73&'73#3#53&'#367#3#3#3#535#535#735#33535#335W U'?Ü<%F6J5©KVVkçjVVL::L9…::L9ˆ 8  "  PÿîóÐ"&*/5GMS73533#3#''275#535#35#33535#335&'#&''3326767#"&57&'''6Z>??6 9B"55>""5#X""5#   #^  j  ÀI I- ) '  (   ,   ÿï]Ï &,7&''63#3#7'75#535#'6'&'2    7'1 )Ï   "C G9ZÿêóÐ.48<N73533#3&533#673265#"''67&'#535#7&'3#735#7677'67&'a#*)     X#w  gFF$$  "* ¸">#"$=    B J:2   ÿèðÐC73533673#5##53&'3#735#3#3##"''3255#535#535#'2J  2³2››ss‹ $,VVff  ggVV#'UÍ !! () 0+ &     MÿéòÏ'+/37GKOSW73533533##5##5#73##5#'65563#735#'3#735#3#3##5#535#735#33535#335Nš/  !S&& <(( P $$'' .   *ŒŒ?5 o9 *  * 'I  +, ÿêMÏ$7#"''32654''67&''67&'76=      Ï *?'( (   ÿéòÏ(,048HLPTX73533533##5##5#3##5#'66556'3#735#73#735#3#3##5#535#735#33535#335:F::F:Ó &H/±66155Lm-3399--G-   nn0( (7.+ +'I  ,, ÿéóÏ/3973533#3#3#3#3##5#535#53&'#53&'#535#35#67#gggOSX(Ci[[[[i@)_LRg(eKÁ2 !! 2/T ÿéñÈ =73#3#535#5#35#3353353533673#&'#5'67#53&'×D9Á:E'9&&9'%Œ   2R"1 7$!6-"Q1 È@@.6""  #;:! OÿéðÎ <@DHZ_73#735#35#35#73#735#35#35#3#3#3#5'673&'7335#5#5##53&''67&67#^AA!!!!!!:@@ 4444;‡ :s******ƒ& + '= ÎH3 3H3 2 )    !    ÿæóÎ >CGK]b73#735#35#35#73#735#35#35#3#3#3#3##5'673&'5#5#5##53&''67&67#XX444444Y[[777777ZXLLLL\¬ 0;>>>>¬(6+1C 4&8YÎG23G2 ,   %  ]ÿéóÍ =AEI\a73#735#35#35#73#735#35#35#3#3#3#5'673&'7335#5#5##53&'''67&67#j<<3<<////5z5i&&&&&&y&% 8 ÍF1   2F1   1 -    !    VÿéöÎ =AEI[`73#735#35#35#73#735#35#35#3#3#3#5'673&'7335#5#5##53&''67&67#d@@7??22229ƒ  9p((((((€. )&; ÎH3 3H3 2 .    !   ÿéóÏ,767&'7'32767#"&55'667'6f:?# & +'+Ï7  (c& i6-0ÿçó¢'7'6767&'7'3267#"&55'6U%.3    $! A:L/*$ K NJÿçî‹+767&'7'32765#"&55'6767'6q1)   % . & !#‹   >! C$ OÿèôÏ(7''6767&'3'66733267#"&5É 66"%* >1  °& !24  G7/)2\ ! ZÿèõÏ)7&''6767&'3'66733267#"&5Ì32 "' 8-   °& !25  G7/)2\ ! LÿèõÏ)7&''6767&'3'66733267#"&5Ç88$%, >4  °& !23  G8.)2\ ! hÿèõÏ(7''6767&'3'66733267#"&5Ð -.#4)  ° %  37  G8.(3\ ! uÿêõÏ)7'6767&'7'3267#"&55'66“"   j!23 & o   n>/. ÿéòÏ'767&''67&'&''67&'76/1*"% #+%A 1'#(†+5 :.+= 9'ÉK#$ÿüsÌ'767&''67&'67&''67&'#              V      ÿéò˜'767&''67&'&''67&'76/.'! $ $.#> *#!†*3 :/+= 7%‘   5     cÿéìÏ&73#"''3267#'6&''6767&'Y  M 3 $ Ï1x &C 63 NÿéëÐ%73#"''3267#'6''6767&'zk  `  ;&) ÐŒ2!v )F ! 75 ÿéæ(73#"''32665#'6&''6767&'G   †$Y27!!# K9 -5"  /- kÿéíÐ%73#"''3267#'6''6767&'V  I 3"ÐŽ0z (E 76 uÿéïÐ%73#"''3267#'6''6767&'šN   A - ÐŽ0 w )F 76 NÿéðÏ 73#3#"''3267#'667#53&'¡ :bS   ?$I Ï^%E+4;I[ÿèô &,2736733#3##"''327#67#765#7&'67#7&'[v  jwP  ;R# q'**'=&&* !5  z!5 hÿèïÏ 73533#3#"''3255##5##535#h9::6   ""69¯ !\  E‚‚ew!ÿéð– 7#"''3255##5##535#53533#Ù   <<PeeggT@  )YYFXÿðóÆ 73#53535#35#35#Ì'å)mmmmmmÆÃÃA-n.o. ÿïó¯)-159AEIM73#3#35335#535#535#533#5##535635#3#3#735#3#53535#35#35#E #X#1 ¶ 144((&& S'å(rrrrrr¯  MM  M""GL?  3EE NÿïòÆ 73#53535#35#35#ܤLLLLLLÆÃÃB/o.o/ ÿðõ’ 73#53535#35#35#Ê+è*kkkkkk’0MM ÿó›Å 767'7535#35#675#ƒ ?M<<< 9WWÃgU  [R!DC;ÿé÷Ã73#3267#"&55#'667#735#[„!  * &ZZÃgU! [3.'*Aÿçõg73#3267#"&55#'67#735#. + ' E < +zzgB %2 #gÿéõÃ73#3267#"&55#'67#735#yh  * %BBÃgU [R!DCgÿéõÃ73#3267#"&55#'67#735#d  -)>>ÃgU [R!DC ÿèpÁ7#5##53'>&'p4%   ÁЉœ'P.(  "+, ÿé‹Ð733#3#5##5335#=::-?'??Ð3/ssR?&ÿéåÏ733#3#5##5335#kffYŠE1ŠŠÏ,/xxTACÿææŸ73#5#'6553&'#3¢:e>CddŸ Z ,% '1; &) ÿèåÎ7'655673'35:dI G^ªª–D1+16c  N;"(ÿççÄ73#'66553'#3ÊÊ "µÄb6* (LL9& ÿéâÐ7'66553&'73'#3?TMp>2 /!Y  I6# ÿéáÐ7'66553&'73'#3?  SLz ?2 ."X  >-Aÿçòr 7#5'753'&'&'ð-mpM  .0/  IGD   ;ÿçót73673#&''>77#7&'F?RD7 =(  :{  K,9*  <    ÿèâÏ73#'6553&'#3ˆM«[Z——Ï >Q("NO #-ÿéóu 73#3#"''3265#'6767#53&'“Tqc S ,  A\u  9 #Kÿêò‚"73#735##32767#"&5535335P……]]|‚+:F9--‚.*4  I ÿéáÐ7'66553&'73'#3?  TNŒŒ2?3 ."X  + 6ÿéêÏ7'6553&'73'#3fD =ppo=/ 08T A/jÿèõÏ"73533#3&''67&'#535#67#q555, " ! /5? 9 ©&&'&    &'jÿìwb73533#7&'7''275#735#335)(( #.)*P4 žÿèôÏ73533#7'7''675#735#335¢ #  ¡..d69BBB9ÿé÷È %AQ7#'6553#&'#5'67#535'6#535'673#&'#5'63#53533533ï‹P    9    =™2ÈKD> 5A] ,$ .   .( L22>]ïŒ7#5##5ï·Œ//ÿçès733#7&'7''275#5335#35#tGG2Md1/GG333F33s> >-ÿåôÏ#733#67&'7&''275##5335#35#vRR! Rk30>R>>>R>>Ï)a= & ?wP??? ÿéõÀ73#32767#"&55#'655#e-<H:À¨  ­/\9 9P/oïÍ73533#67'7''275#75##5#z,++  ,:,W©$$Q( )////ÿåîq 73533#7&'7&''275#75##5#&PPP8Md/-P <<^>   MöÐ73#&''67&'#53&'67€`%&9B/-< 4'a#"Ð     &0ÿéÐJ 7#5##535#ÐxxxJaa@-RÿéõÍ73#&''67&'#53&'667#  @ ) )+, ? GÍ?+%EŽ''lÿéóÏ733#3#5##5335#™EE;U-UUÏ/,xxS@€ÿéöÏ733#3&''67&'#5367¤995   "Ï#8)  2&$wÿéöÏ733#3&''67&'#5367Ÿ==9  # %Ï#8) 2&$ ÿíôÐ)7773276767##"&55'75'75'6b %!(*/H(%(#& ,Ð''4 7(%vÿéóÏ733#3#5##5335# >>6K*KKÏ/,xxS@mÿéõÏ73#5##5353335#­9Q,HtQQ0wwo,ƒ@Vÿé£Â73#3#5##5367#35#35#VM"&&&&&¦¦a-l-gÿéóÂ73#3#5##5367#35#35#gŒ@>Q&7QQQQ¦¦`-l,ÿéõÂ%73#33267#"&55#67'7#&'f;> 6  @,-"<  Â#|  ly ¶MÿéëÂ73#3#5##5367#35#35#ÖbUƒ?])ƒƒƒƒÂ¥¥b.n-ÿé|Á73#3#5##537#35#35#l)(6.6666Á!¥¥!`-m.„ÿéïÁ73#3#5##5367#35#35#„k)(6-6666Á¥¥`-m.vÿéñÂ73#3#5##5367#35#35#v{75F$/FFFF ¦¦`-l,9ÿéî¯"73#3533#3##5#535#'67#536€U_,< KÿçôÐ$(,047##53&533#673267#"''67&75#'#33'35#š-4@3    *0 <-,!##)$(B‚o   )!  [oÿçõÐ#(,073'33#67327#"''67&'#73'#3353'#335w-3*  + )7.L 0±l )% ? Jˆÿç÷Ð#'+/7#53533#67327#"''67&75#'#33'35#¹.#,"     &Cnn  " ' Qÿéò‹ #/767'53373673267#"&53533##5#5 * =% $, $ !ucggcb  O(     &**ÿéò” $0767'533736732767#"&53533##5#5!) =% $, + "ucggci!  T*    '--qÿéòÏ(47367325667##"'"&5'67'53333##5#53±   )  6644Ï+  !  .1 x&E>>QÿíòÌ'73673267#"&5'37533#67'7¬   O %- ÌSY ! —”¼>g T_õÏ'737533#7'773673267#"&5b %-O   ¹BV# \'  Oÿéõ`73533#&'#5'67#XCF8# %* )3J%+FF+%€ôÌ'73673267#"&5'33#7'27537’) - ( G**'8: Ì   2 ('WÿíöÌ'73673267#"&5'37533#67'7±    N $, ÌQ[   —”¼>f +ÿéÒe 7#5##535#35#35#Ò‚‚‚‚‚‚‚e| |!.. ÿèî]73#3#5##5'67#35#Úƒ ‡z +<8zz] M ; Fÿèïi 73#3##"''3255#'67&'*¬¬Þe   e<+ %y!i5 2 _ÿíòÌ(73673267#"&5'367533#67'7±   H !) ÌRY ! —”¼>f  hôÏ(733#67'7536773673267#"&5J''9< C"* )Ï<:M    ÿì„Ð 733#67''37533#67'7W  D 'Ð4ƒ ¦“º>c  ÿèôI&733#7'753773673267#"&5K""$5AB$ #+  &I <:G   €åÉ 73#735#'3#735#ŒYY22†ZZ33ÉI#I#ÿéîÈ)7#'6655333#"&53533#&'#5'67#ž@g&+ …^_M!0 5$"6 3Jµ6  4+4UW3)fñÇ)733#"&55#'6653533#&'#5'67#× $  277  ! %'Ç1  '   '_8?#!lÿéõÈ*733#"&55#'66553533#&'#5'67#Ò   14'  "È8  ,    o(+OM*(mÿêóÇ 73#&'#5'67#3533##5#s€2  ,:2442Ç 69$y?? ÿçõÏ "(7373#'67#3&''>7'6''63’ 80 0y&+5? ! D] ²pC;e;>-1 %0* ' WÿéóÏ"(73733#'67#3&''657'6''6\]_K( %; P.  ?­"E' &:97**#3   CÿèöÏ#)73673#'67#3&''657'6''6Z"]_ 3 - R  "2 A.  E  ³Z81P7 .,'8   5ÿèòÏ$*734733#'667#3&''667'6''6Pop['" -$.  H  ±HHBD'6'%:1%# `ÿèôÎ$*73733#'667#3&''667'6''6s\\F  ) # 9  °?> :: *6&#<0!! .ÿêì§7&'36533#&''67#º  h?OJ %!4 I O?§ ) &"&- :=HmÿèñÂ73#3#5##5367#35#35#m„;8F!4 FFFF§¦_,k,ÿéòÏ-73&533#327#"'&'#7&'3#67'675#}KJ   ~± œj-5B)¤B//'#5P>  Z@ C ÿèqÎ(.47&''67&'763533##"''3255#&'''6[   >&##  &L  3  Î    \P  L    ÿèóÏ$*73533#3#535#3533##"''3255#&'XXXiæhX”33 ”5  ±P8  4 ;ÿèí¢%+73533#3#535#3533##"''32655#&'JABBM±PAz## z)  =* &\ÿéóÐ$*73533#3#535#3533##"''3255#&'i666?–C6 e  e&  ²N<  8  gÿéóÐ$*73533#3#535#3533##"''3255#&'s122:‹=1 ]  ]%  ²N<  8  mÿéóÐ$*73533#3#535#3533##"''3255#&'x///7…:/ Y  Y  ²N<  8  [¼ 7#5##55#35 ¼±¸I8888’ÿéóÐ$*73533#3#535#3533##"''3255#&'š %`( >   >  ²O<  8 sÿéóÐ$*73533#3#535#3533##"''3255#&'~,--47, U   U!  ²N<  8   ÿéóÐ373673##5'675367#33##"''3255#53567#GŠ  =ZmAA  KKU¶ #i   6 3JÿéôÏ.73##5'67#533567#533##"''3255#Œ[a  $"), =U ((  ,Ïj1.n  7 3YÿéôÏ/73##5'67#5363567#533##"''3255#“SX  %' 8O ##   'Ïg .1|  7 3FÿéôÏ/73##5'67#533567#533##"''3255#‰^d "%,. @X))  .Ï  #Ši,3n  7 3gÿéôÏ/73##5'67#5363567#533##"''3255#—NS   & 5L !!  &Ï  Œd.)|  7 3ÿêðÐ(73673##5'675367#3533#3#535#G‚‹  >G<>>G£H<° &v] =11FFJÿéòÏ$73673##5'67#3533#3#535#](Y^  %"6!""*i+!° †i '->..HH ÿêï™(73673##5'675367#3533#3#535#Ou  BI:==E F:ƒ  WE  * 11OÿëòÎ773533#3673#673267#"&55'675367#535#a#"" 6F1) .5   -# ?2#®  "     /    YÿëòÎ373533#3673#673267#"&55'67#535#v-<)! (,     "1)®  "     5 " jÿëòÏ0573533673#673267#"&55'67#535#67# (7 &$)   # 0*1 ®!!       5    YñÏ/73533#3673#673267#"&55'67#535#(:;;!) 5W$<2 7A4.83 4,^T:Á        ÿêóY&773267#"&55'75'75'67Ô]qr "2<0MOCE !^O*0<        HñÏ.373533673#673267#"&55'67#535#67#,66 9W# :2 6@.:2 4*]S6e 5À     ÿë–Ï=LR733#3#53533#673267#"''67&'#'6553&533&'73#67'675#'6K$$0}A  8H Z1$    Ï###"# &  ./676  ! < ; ˆÿêìÆ775&'75#53#"''3255'6'5&'75#53#"''3255'6Ú  ,   %  .    N 5Å )    5Å -  `ñÏ/73533#3673#673267#"&55'67#535#(:;;"( 2W :1 7A4/92 3*[T:à           ÿìò673533#3673#6732767#"&55'6757#535#%>==& 7V0@7 ;G,5 >9-  %bU>‡  $ *ÿé× 7#5##535#35#׌ŒŒŒŒ55kKôÏ173533#3673#673267#"&55'67#535#|!!!  1 # )   & 62!½     }ÿèëC 7#5##535#35#ëJJJJJC[ Z!1 ÿéóÌ73533#3#535##5##535# iiiXÆZiÉ………¬   Mcc@- ÿé}Ï73533#3#535##5##535# ...)h+.h888­""""Ncc?,ƒÿéóÏ73533#3#535##5##535#ƒ...)h+.h888­""""Ncc?,ÿñg¿73#3#5##5'67#35#W$ 4  (¿#s g "<Ž@[ÿú¡Ï733#3#535#53#5##535#uA'Ï!!!\W X;)šÿç÷Å %73#3#5367#35#35#35#'67&'žT!F$$$$$$  2  Å >BB,     ÿé^Ï"(.767&'7&''67'6776&''67&'H   .'®9!   0+f# YÿéóÏ73533#3#535##5##535#YCCC8…9C‹QQQ­""""Ncc?,Yÿð©Ð!'-767'7''67'67676&'''67&'    -'¥8/, b$ ¦ÿéóÏ73533#3#535##5##535#¦FFª%%%%Nb bD4 ÿéó]73533#3#535##5##535# hhhYÌ]hÎR   &5 5 ÿê…È"(.4:@73#3#7'75#535#735#&'735'6&'#&''&'''6h+'')0<1))+)     ÈX 7 77 w 5ñœ.73#673265#"&55#3'67#&''67#âP% ,'75RH2  ,œ     ;-    ÿçñB/73#673267#"&55#3'67#&''67#0 /" )(42RG-  B       8'  2î¯ 73&'73#3#735##5##5ecÛ©©®µŸ& !'&uࢠ73#735#335335ÁÁ)):((¢-Aïj7#5##5ï·j))ÿçñP073#673267#"&55#3'67##&''67#-¦2% , '43SH0 P   A- !   CÿèèÏ #753#5&''3'65753#5&'#'6Ê M ( /  •mçc(";uC, (:?k×](". %~ÿéíÉ 7#5##535#3533#''65#íIII  Éàྫ*""+ "2DÿêóÐ#).73533#3#&''67&'767#535#3655#335QFHH?A 9 6%* $ =@F+,@+¹J    JN$[=ñÑ"&*73#3#"''3255##5'67#53635#35#”PYV  E  'EEEEÑN I  E )<^Ç7#"''32654'7##5^  !Ç#'z‹cÿéòÏ!%)73673#3#"''3255##5'67#35#35#m%JQ P   = (====° € 5x $CB ÿéìš"&*73673#3#"''3255##5'67#35#35#:ˆ’Œ  s"/@ssss„   _  &d30ÿéòÍ"&*73673#3#"''3255##5'67#5#5#›(/7  " >"""¯  €  7‚ F)Zÿé—¿7#"''32654'7##5— ¿B FÆÖÿé‘Ï!%)73673#3#"''3255##5'67#5#5##FM I 7_777° †  5z &A,iÿéòÏ"&*73673#3#"''3255##5'67#5#5#v!ENP  =_===°  †  5ƒ  A,oÿèö¿7&'333#"&'&'75#  #!)¿ Cd   U ÿéôÄ $(,073#33##"''3255##5#53535#35#33535#35#ÖaP P`$<>^IKÿïðÅ73#3#5##5##535#5#5#àG=)Q)=H…)))Å(›ˆMMˆ›((((M::ÿé‰}#'+/73533533##3#3##5#535#535#5#5#35#335(**++44++P( +v )  )  3 €ÿéæy7#"''3255#'65535#35#æ /----yz 55';ÿéz~#'+/73533533##3#3##5#535#535#5#5#35#335&&((--''B't  '  '  0 rÿéð{ $*06<7#3#3#3#"''267#55#5#5#&''&'''67&'î+$$$$-  [/8  * %{ 2 W       ÿéôÏ+7#"''3255#&'#5'675##535#53533#Þ ?+> :&'7 A(>RddbbŽ8  </Z]5 <=P öÏ"73533#3#5#&'#5'67##535#`bbN5+> =%7 =(6O`º:'2/--- 3':XÿéõÏ,73533#3#"''3255#&'#5'675##535#f;775  !% "$ -!5;·6  --!GQ,4 >P ÿéó,73533#3#"''3255#&'#5'675##535#dffM 9+> ;$(6 A(9MdŠ,  *$;;%*1C ÿ釛,73533#3#"''3255#&'#5'675##535#/22,   ,/‹( ) 33 #->‚ÿéôœ73#&''67&''667#¢@   'œ ,$"  WÿóòÆ 73#33#537#'67#&'&'`SK'›`9  (=  Ɖx5.CQH  ‡ÿóóÆ73#33#537#'67#&'&'c:7l@ % +  Æ(‰x5.CRJ   ;ÿêç°73#3#3##5#'6evZNNUU° 2–! &Kÿèò©%+73#3267#"&'#67'56&'&'Ï ;8 #<$ G6 ©$(/1;  Ž8 T   ÿëôÏ"(73'33#327#"&'#7&'3#3#'6 …KI! .‡¸  •^^eep6BH‹D(:<*9GFR V   ÿèõÒ(.7#67327#"&''67&'#'66553'37&'ìK    ( +T f# /#.+'6$:C6+ (M20   ÿèôÏ*07#67327#"&''67&'#'6553&'37&'íT   !, 2K]ž&  %/#3D1- %,X1  ÿèíÑ27&''37767327#"&''67&''7&''7&¦  6``kh   "-; :, YXABÑ    0  ÿçòÑ07&'#67327#"&''67&'#67'53&53¯ CW    & )H # \Ñ  3#$ . 1,<} žÿçöÑ$*873&533#67327#"&''67&'#7&'3533#'67#wNM   # & w® œ.))9 0.¦<%1- %!F>   r005")ÿèðS17'6553'33&'73#67327#"''67&'#&7dK    " " T  $       jÿéõÒ/57#673265#"''67&'#&''6553&537&'ï(      ' : Ÿ,"5 ! %C'8* ,2M30  ÿé÷Æ27327#"&'5#'6553#3'67#&''67#Õ‹y?@3!S5   &Æ_34);,RLJT,$TT+  $ H  39ÿéøÅ27327#"&55#'6553#3'67#&''67#Ú `! R&*#9    Å^56-&.PLIU,%TS+ :! H  5Aÿè®Å73#3##5#'655#535#5#M^=ÅKjjA( %7KKKKÿè•Å73#3##5#'655#535#5#{$# O$ÅLjj@) %7LLLLB”Æ73#3##5#'67#535#5#v #  K Æ$99$$$$$¡=âË 73#"''3265'3#Ï   .Ës `\½ÿèïÏ 73#"''325'3#Ý    ÏÏ ³’Wÿè¶Ä73#3##5#'655#535#5#cR  3ÄMkk@) %8MMMM ÿ锌73#3##5#'67#535#5#x"" "M!Œ,SS9.,,, žÿéã” 73#"''325'3#Ï 1”‘  |sbÿè´Ä73#3##5#'655#535#5#hL  .ÄDtt @) &7 DDDDAÿè©Â73#3'67#&''67#OZ//D 7   Âz1$b  .:Rÿè¯Â73#3'67#&''67#]R)*= 1    Â{0$b  .:ÿé“Ã73#3'67#&''67#€@; Z L-  "-Ãt."\   ,:K‹È73#3'67#&''67#t9;L@1  'È 0% žRâÍ 73#"''3265'3#Ï   1Íb OI ÿä‹€73#3'67#&''67#t9;+B/  !(€ .$ 6  'žÿë≠73#"''3265'3#Ï  1‰† reEÿç§Ã73#3'67#&''67#NY)-D;   ÃZJ @D  +:Rÿè¯Â73#3'67#&''67#]R)+= 1    Âv."]  .:.‹Æ73#3'67#&''67#t9< B0  !(Æ $1  #ž4âÍ 73#"''3265'3#Ï   1Í€ jX ÿéóÈ59=73267#"&55#'65535'673#67'7&''675#735#3353 $?J      Èr3(  0=`l<$!>xG U  555[‹Ç7#53#3'67#&''6:#t=;ME4  ·2 %žhâÍ 73#"''3265'3#Ï 1ÍM 9:ÿèóÌ73#3#535635#'6ÄGE–0Bå)R>RR -*Ì8v|8S ÿéóÌ73#3#535635#&'ÄGE–0Bå)R>RRQ$"Ì8v|8TaÿêêÏ!%733#5##5'67#&''6635#šE2OC R 8  CCÏ :c WC   ¬2PÿêèÏ#733#5##5'67#&''635# M5TN W#B  * NNÏ:c UC  š2 ÿéÝš $733#5##5'67#&''6635#e_ 8eu |,S   uuš+R B2  …&sÿêíÏ!%733#5##5'67#&''6635#¨9,F8 G,  88Ï :c VC   ®2[ÿêóÓ73#3#3##5#53535#'65#€^/,,77N8$ 7%Ó*0==B*!k00ÿêðÓ!73#3#3##5#53535#'65#@™LGGXXrUB  UAÓ )/==B) "l//ÿèãR73#"''3255##5363#735#w_   R*llHHR F  1K[#+ ÿèãq73#"''3255##5363#735#U€   /hh@@qb  Mgw *9ÿèî{ (/73#5##53&'3673#&''67&'67#67#…Y¤^aG~/ #& +%)C 9 ! :y B{#%4        LÿïôÏ 733#5353635#35#35#˜ L ¨) "''Ϧ¦»“““““ ÿõ•Ð737'6353675#75#75#B< =K ÐŽ Ÿ²ˆ†„‚RÿïôÏ 733#5353635#35#35#š H ¢& &%Ï¥¥»“““““_ÿïôÏ 733#5353635#35#35#  C •# #"Ï¥¥¸“““““?ÿïôÏ 733#5353635#35#35#‘ Oµ- %)*Ï ¦¦»“““““ ÿðóe 733#5353635#35#35#jfæ8 -""5##6""e HHV55555 ÿõ†Ð737'6353675#75#675#=5 6B    Ð Ž Ÿ²ˆ†„‚ÿéõÁ73##5'67#&'…l,-NÁ ´$/G5('%}ÿîóà 733#537#537#37#37#^ v##" ##ÃÃPOOO±P|ÿèðÏ73533#3##5#535#7&'7'6‚***00,,*  `  ‚MM*JJ*N   rîÑ 733#535365##5##5#ggÜ5b"##Ñ 66 B%%%%%%ÿéðn"7'6767&'#''6'6y*-ePK # < / 3# $3 9O     'A]    ÿõoÐ7327'753675#635#75#3++6Ð Œ  µЇ†‚€eÿéõÏ#T73533533##5##5#3#735#335335#67327#"''67&'#&''6553&533'7n""{{"5      &$ 4À 9>    "#$  ÿêóÐ'+/=AESW[imqu735333##3#3##5#535#535#535#535#335353#3#5367#35#35#73#3#5367#35#35#33#5353635#35#35##STT^^iigg^^SSiiSfAAA¶\#%Y"'8888k\#%Y"'7777+VåK>%%7$$6%%É   ;;    A --. --%% .$ÿéÛÐ 73#5##53635#35#35#lc=1Ð ÉÉD#Y#Y#*E×Ò 73#5335#35#35#j `­6"………………Òxx$21+`ÖÒ 7#53675#3535#Ö«6K……………À``   $ ÿéÐ 73#5##53635#35#35#@3???????Ð ÈÈD$Z$Z$ÿéáÈ73#35#535#53#5##56m#*GGœEEBVœ/È:CAAÍÇrÿéìÈ73#35#535#53#5##56Ÿ$$P##!6PÈ=CC?ÍÆ C½r73#35#535#53#5##56r%%T%%#6Tr f ` ÿçöÐ(7#5#67&'67'5'67##537æH ,J$   F A] °7$& "4W S  O K&9 FÿçöÏ)7#5#67&'67'5'67##537ï0   3   6/I¯6$ " !/Q P  J  K&8 UÿçôÏ+7#5#67&'67'5'67##5367ï+   .   0(A¯6$ " "0O P  H  K&8 bÿçôÏ+7#5#67&'67'5'67##5367ð'  )   +#;¯6$# !/O P  G  K&8 ÿìíÑ -73#5##53&'732765#"&55'75'6‚_²cX &+np * 3$X[:UÑ -- 7(  1% 6  '\ÿëòÐ -73#5##53&'73267#"&55'75'6«6^:3CE  9=.Ð5! 4 <&1! 3%mÿëòÐ -73#5##53&'73267#"&55'75'6­2U43 68  /2 *Ð5! 4 <'0 2  &ZÿëòÐ ,73#5##53&'73267#"&55'75'6¥<k@9@B  $47#4Ð5! 4 <& / ! 2  &BÿéôÑ 7&''63##5##535#–#. ,#!) :\\p[[[Ñ&(,(+bb>, ÿê£ 7&''63##5##535#V& "$1 TT^CCCK K.bÿé÷Ñ 7&''63##5##535#¥& #" .HHZEEEÑ(),(,bb>,ÿèõ‘ 7&''63##5##535#ƒ18 85.;G kk‹€€€‘  L M1 iÿéôÑ 7&''63##5##535#¦# ! + GGYBBBÑ((*(,bb>, ÿéXÎ735'673#3#'67#535# $  Š$ +)4 ))¦ÿêòÎ$7'673#3##"''32655#535#53 %    ® ))< 8) löÑ7&'#5'63&'3#735#~/; b QP:››vvÑ  !)  ÿêŒÑ 7'67&3##5##535#M +=DDR666«$'*aa>,‚ÿéõÑ 7&''63##5##535#³  "99G111Ñ''-),bb?.«ÿêæ 73#"''325'3#Ô  )Œ  |nrÿé÷Ñ 7&''63##5##535#ª"  'CCS<<<Ñ)),(-bb>, ÿèuÏ&735#53533#3#3#&'#5'67#535#"$$%%$$'' &("Ž  JJ (mÿéöÑ 7&''63##5##535#«# ) FF\DDDÑ ##&$)cc?,/ÿõÐu 7&''63#3#735#~"% '$# 7RR ffCCu  4 ÿô}Ð 7&''63##53#=#C 'QQP+Ð ,eb`** ~÷Ô7&'#5'63'3#735#~48 'T! P; @££Ô   % \óÑ7&'#5'63&'3#735#€,< ` OK0™™uuÑ   . ÿò÷Ñ 7&''633#3#53533~28 74.= Q MM`Þ'/Ñ -10+:<ddYîÏ7##5#53&'73##''67&'ŽHb`KU•£DD  D7 " \ïÏ7##5#53&'73##7&'''6ŽIb_J5{£==  =1 JîÏ$*7#53&'73##"''3255#'667&'''6ZEb`F  & h{ £  4  /'$   RíÏ$*73##"''3255#'667#53&''67&'ƒ^E  % Da9 œÏ 0  +%"   +  F]óÐ73&'73##5##5#'67&'U= B1&   w  ³  CCCC  dÿéõÑ$*73##"''3255#'665#53&''67&'® 5%   )9  s  Ñš  •RF@O E7& -*.03-oïÏ7##5#53&'73##''67&'ŽIa_JV›©11 1(   ÿîôy!&+07'67&'#3#3#53&'#535#73&67##35#C H/ 37 3J=Þ2L27O!0(A #   $ Q  ;ÿçñY73533#&'#5'67#NCE<' '1 37J $DBÿêòe7'673&''67&67#B +m #4?+(>3"3`2     ÿèzÏ&735#53533#3#3#&'#5'67#535#'))))''++  ),'Ž  IN ) ÿégÐ&73533#3#3#&'#5'675#535#535#!!!!  !!¸S= ( jXõÏ!73533#&'#5'67#7&'7'6n78, +  `  22$">  [ÿéôÆ#'<B7#'6553#3#67&'#67''3#3533##"''3255#&' €nn3  /  YY D   D zE4 6>_+  +%_d$ !  TÿèôÐ9Up734733&'33&'73#67327#"''67&'#&''67#767767&'7''67'6767677'7''67'67_ :      %        L   L (\=.   "/    "+e%% bÿçõÌ#'-TY_73#33#&'75##53537#35#35#35#&''33265#"'3&''67&''67&567''6j„>7 l '2NNNNNN&     D  )"  O ÌC   C!          (  4  PÿèôÏ%;NR73#'6553'3533#&'#5'67#73533#&'#5'67#3#3#5##5'67#35#°6w?"   9    9tE>+  )++Ï M>1 0;X8 $"   $ A < $ 0ZÿéôÏ%73533#3#3#&'#5'67#535#535#e;<<22>9% !# (7=22;¸(%QP&+oÿèôÏ%73533#3#3#&'#5'67#535#535#y222--50  *5..2¸/(OY**FÿèìÏ*73'67#&''63'67#&''6œCo eA  3" ?"|s>   0Ï = / E G9   WÿèíÏ+73'67#&''63'67#&''6¢=dY9  ,9n g6   *Ï = /  E G9  ÿèßÑ073'67#&''677363'67#&''6 S'Œ#U   .M,›’&N   ;Ñ = 0   Z H:   €ÿèïÏ.73'67#&''63'67#&''6²-*@%   (-I"  Ï(0   C / :   ÿèzÏ073'67#&''63'67#&''6=.)@%   ).I!   Ï (0   C / :  kÿêìÎ73#"''3267#'6##535#”P  E  @.Î #&WfF6`ÿèîÏ*73'67#&''63'67#&''6¥:^T4   (6h `2  &Ï = /  D G:  rÿèíž*73'67#&''63'67#&''6­/PF*   $ )[Q'   %ž/ !  3 7 *   ÿètÏ,73'67#&''63'67#''69+&<#   &+D   Ï(0  E . :   ÿæé¤*7'673'67#&'673'67#&> 4*M.z g/K! K:<7Ž €4? o  1 $ @! >2 PíÑ*73'67#&''6&''673'67„E \ P<%2   :2#c X Ñ 9 ) T "G5uòÏ*73'67#&''63'67#&''6±2RI,   &!*[R( 'Ï3 &   8 =0ÿæä†,7'673'67#&'673'67#&< 4)B,p a*C   M= 47‰ w6<X  )  6 2 &  wõÏ 7'67&'&''67&'76\)&k $ %"+. >,,? 5" Ï         lÿèóÌ73#'66556#5##535#ä '3__  >/***Ì D8 7F:ki jI6 ÿéÊ73#'66556#5##535#‚ '5\\  >,***Ê"B9 6F:jooI7KòË73#'665563#735#ç 7J}}  T6hh@@Ë3* +6/WS-JÿéóÊ73#'66556#5##535#é 3Exx O5AAAÊ!C75E9ippI6eÿéóÊ73#'66556#5##535#é ,:ff  D/555Ê"C7 7E:jooI7:ÿéóÊ73#'66556#5##535#é 8M……W7IIIÊ"B7 7D:jooI7\ÿéóÊ73#'66556#5##535#é .=kk  G0888Ê"C7 7E:jooI7 ?íÏ73#'665563#735#Ü Jdµµ  qQ——rrÏ&! ")#C>  Ï73#&''673#&'#'6*$ M' Ï  &   EƒôÐ73#&'#'6#'673#&h. f  1 Ð  K“òÐ!73#&'#'6#'673#&j,a /Ð     Jÿéó›'+/735333##3#3##5#535#535#535#535#33535Y<;;@@IIKKAA<˜~‰ #  BÿéóÑ-157'67333##5##"''3255#535#535#53673535u !I&)   88II-@ )))¬ !  .9 #   +.\ÿéóÑ-157'67333##5##"''3255#535#535#53673535ˆ =#"  ..>>'5 """¬   .9 #   +.iÿéóÑ.267'67333##5##"''3255#535#535#53673535’ 8    ))77#/¬   .9 #   */ÿéñÑ-157'67333##5##"''3255#535#535#53673535¯  (    $$¬  -: #   *.eÿéóÑ-157'67333##5##"''3255#535#535#53673535Ž  :!   ++::$1  ¬   .9 #   +.tÿéóÑ.267'67333##5##"''3255#535#535#53673535›   3    $$11)¬  .9 #   */|FðÉ)733#"&55#'66567#53&''67&'Ô   @X  É   Q  XÿèñÐ#)/733##"''255#'67#53536#3'&'&'˜; E6EE&    Ð QO L:, .-Q#=6 I EÿçõÐ#)/733##"''255#'67#5353635#&'&'C S#!RR"    Ð SR OA(:S `@ J ÿæòÓ $*0733##"''3255#'67#5353635#&'&'qQ"" u $%5'uu9    Ó SR O<. .0Sa@ PyÿèõÏ7&'67&''67'&&'¯     Ï!93'IR0#,uÿéóÈ 7&'''667&'7''6Ô/ '+ÈBL>-+U! (0  *rÿèôÆ"7#5##53'>33267#"&5á<!    Æ‹yy‹(R.*  #,9 rÿìõÑ (7&''6#3267#"&553#"''326°  */1%  1 WÑ '&$&HY  o6 wÿéóÁ73##"''3255###535#w|  ZH-Á«  §'WfF5zÿèéÆ 7#5##5##535#33535#335é.I.ÆœTTœ<***f***yÿéðÇ$*73#3#5##535#35#35#"&55#'655#5#3yw$!J #5 J  JÇ"©©"""§* 7+ "772gÿìõÑ %73#53635#35#3#3#3#535#535#¡8s&KKKK6//;Ž?//7Ñ \\,97hÿíòÐ273673#3#3#3#3#535#'67#537#5367#53&'› ;79IO M(2y3   $ "- Ð "" !  mÿèóÏ#'+/7;?73533533#3#535#5#35#33533535#335335#5##535#35#m*))#y#*K""S" DDDDDÁOO -/[ [.gÿéïÇ#173#3#5##5##535#3#'3#3#73#3#5#535#535#g†;=+(::U**L((**M**Hr__bb_ÇEE4 a `ÿéõÑ (:IO73#53&'67#5&'67&'7''3##'3267#'67#3353#5#'6535#¬=‰8  /    \0  JN KJÑ   ,1 ! &" =/! 7i% " bÿéóÑ.26:>BJNRV733#3'67#73265#"&55'75#'65533#735#33535#3353#53535#35#35#™44E2$% ([[%:% w   Ñ     J>/ 17UH<# ! !$$$ ZÿèòÑ #8\afkq733#5'667#35#7'53373267#"&553633#3267#"&55#'67#5'67#367#335&'€F z A9VV# #=   KE,  , ") A <#4'  Ñ $   /  $     # '        oÿéòÏ $*0733##"''3255#'67#5353635#&'&'¯(  8"88    Ï MV S>/ /3M Z; I HÿéõÏ $(7&'7&'#"''3255##5'6735#35#É,   I & IIII# 1  9“6,ERÿññÈ73#3#535#535'2â $FFA–AEEEÈHOOGÿçðG73267#"&55#'665´  EG>  0!  (†ÿòòÉ735'273#3#535#†-./ ,,(`%-dHKLLM\ôÌ735'273#3#535#MJDD #JJ@’?Jž  STÐ 7#5'6A  $ÐT> ŠÿñôÉ735#'273#3#535#Š+,+ ++%^%+hF HRR]ÿéŽÐ 7'67#p  g 28°[ÿéõÏ #*.4:@7#5'673&''67&''75667#'3#7'6'6'6wG*     7F   ( *" 4 6Ϻ‰ 04     & ˜Z     ) ÿçëk 73#735#35#35#'67&'*¬¬„„„„„„ 8 *`))('k`D & %     ÿççQ7#5##5'66&'Ñ{K17 4+-' *)Q?.2C!%  ÿççc7#5##5'66&'Ñ{J26 3+!+( *)cTCFW'- $   ÿè]Î 73&'7367&'#5'67#   .¯ #    fd >•ÿñôÆ735'673#3#535#•& , &&!T &cG JLLPÿêóÏ"(767327#"''67&''7&537&'ìL   ", 5 78š  '+$!4$$##;  R<óÑ)7&'673265#"''67&''7&'7¾  ?R  "" 42Ñ     #     @YÐ 7#5'6G  (ÐfI %Sÿéó“)7&'673265#"''67&''7&'7¾  =Q   (' 22“  %SÿèðP(767327#"''67&''7&'77&'7Ó<  ( * 0+B        ÿç[R 7#5'6J  %RI7 pà¡ 73#735#335335ÁÁ)):((¡1PÿéôÏ 7#5'673533#&'#5'67#~ ))#   $Ï ª‚ 1 11D2{w-,;ÿéñ$ 7&'''67&''&'Ò  x  $   ÿèôÏ73533#&''67#7&'•!%$"  #!J  ‹DDP,#87%3KJ vÿéòÐ73533#&''67#7&'})3- )! %+(W ŠFFO-&?B#'XP DÿéöÏ73533#&''67#7&'S;MIA7; > 9p  ŠEEM-+GC-2GOHÿé‡Ñ 7#5'6u  Ñ¥z 8jÿéôÐ %17#5'673673267#"&55'673533##5#Œ /  !  @5555ÐT4'  !  c>>^ÿéôÐ $07#5'673673267#"&55'673533##5#… 2   $ F:;;:ÐS5'  ! d>>RÿéõÐ #/7#5'673673267#"&55'73533##5#  6 (NAAAAÐO5(     c>>ÿéîÐ +77'67#7376733276767##"&55'673533##5#8 + K '5  rceec‰") Sƒ1       ]::ÿê¦Z7&''67&'76  . * Z    ÿéò¡!73533#&'#5'67#7&'7'6bdV+0 /.%8 5$Q(  • ]DD.5UW1 )L   MÿéôÏ!73533#&'#5'67#7'6'&'`7@1# '& %'t Y  xWW9!#"8^"  ÿè¹f "7&'7'63533#&'#5'67#? m vF<<$ $ $9f    )),6D€òÑ#7#53533#&'##5#'67'6'&'t#EE- - $ m S  Ÿ!!  ;  WÿéôÏ!73533#&'#5'67#7'6'&'f6<- #" "&o S xWW:=pp=#"8^"  ÿéïÏ!73533#&'#5'67#7'6'&'dfS 3 7#!75O³ƒxWW7!@uu>$"4]   ÿéòˆ!73533#&'#5'67#7'6'&']_O"7 9%!9 4"L™  [  Q77%.NL)%<   PêÑ!73533##5'67#7&'7'6'___!6 . I%  $@¡0031$@   8(ƒÿéëÏ 73#5#533533ØUš±¡ÄÄ}ÿéôÅ733##"''3255#53567#Š`11  44JÅ\ Y"mÿéôÏ73533#&''67&'765#|="" !" "  <Ÿ00D+" 0}ÿéòÌ73##5#535'6Ú 2200 :Ì ?rr; gõÒ73&''67#'6e[1$ (92A f8T 7Ò   *  ÿéòu!73533673#&'#5'67#53&'E   1R"6 :$!; ;O3 g.. !'BB(  nÿìðÏ"73#"''32767#'67#'67##'6”S   D =1 +  Ï‘/*cn73cM'#C dÿîòÄ 733#537#537#33535#wfŽ!0(//)ÄÃOOOO°OnÿéöÏ)7365333265#"&55#'67#3533##5#x.  - #4::4²  K  =</e%%??uÿóóÎ 73&'73#3#536'&'{.1sV *~A2¤ G5>?66:2dÿéõÏ73533#3#&'#5'67#535#s1550%" %",1©&&&*%&-eb2$/&YÿèóÅ%7#3#3#"''3267#'667#'6655óji94   "  ÅX!B&07A"D5 1$^Pÿè®Ï"73533#&'#5'67#7&'7'6U"     K ~QQYY#+QÿèRÇ73#3#"''3267#735#<%.  / #)ÇK#L%4E)®ÿèòÇ73#3#"''267#735#²<%.  / #)ÇK#L%3E)YòÒ!73533#&'#5'67#7'6'&'\\H!3 8$"80#F¢ r  ¨** $-/%;    ÿçðj#73533533#3#535#5#'67&'&&://CÞ:&t:) *^%! $#V    gÿèôÆ!%73#3267#"&55#'67#735#35#35#zi  ) $CCCCCCÆ0   68+fBClÿïóÎ!%+173533533#3#3#5#535#35#35#35#'67&'q0jh{ (000000   2 µY6IY56(    cÿèóÐ*F73673#53&'''67'6776767'7''67'6776767&'Œ " ‰ )   X   Ъ %.,C' %.,C&\ÿëöÇ !%)-7#'6553#3#3#535#535#735#33535#335ór^'**2u3))'%:%ÇSD4 5>_!h<FkÿêöÏ!%)-BH73533&'73#3#5##5##535#35#33535#3353533##"''3255#&'o5 81151O1j]   ] » \\.-?     fÿèðÇ8>D73#3#"''3267'6765#535#5#53#3#"''3267'677#5&'7&'k=,+    ,,*p,?.0    09 O Ç:i   <:j  .DJP767'7&''667'7&''6'67&'7&''67''6'6'67'6š   ).8   A   & &'' 9) )+!: 91&T PÑ          2 ( wòÒ "7&'7'63533#&'#5#'67#=  Ž œaaK49!$5,AÒ     ÿçïŠ#'+1773#3#3#535#535#735#33535#3355#35#'67&',¦ ++=Þ;** 77K7‚77K7 @@@+ 'g#' )%ŠC    ( % (  "    fÿèóÏ#'+/7;?73533533#3#535#5#35#33533535#335335#5##535#35#f,++$~$,P##X# IIIIIÁOO ./[ [.SÿçöÑCGKQ7#35#535333##67&'#"''32655'675#535#'66553'73535&'ów/$$+ %     $$/ ?M ¾4 ##   %*    B3 0"\  2$*  _ÿê•Å75#53#3#"''3267#7‚2 "   #'I*K 9L_ÿèõÈ!%)?735#53#3#27&'7&''675#735#35#335'5#53#3#"''3267#7œ E  !' !! Z2 "   #tCCN! #p!€,,,*'I*K 9L ÿéòS!73533673#&'#5'67#53&'B ,P#3 9%!; <O. Q     64\ÿçôÇ)-159=73#3#3673#3#5##5'67#535#535#735#33533535#35#j„M(( 4 CH *>-&&%%OHHHHÇ5 [ J –0 eÿéïÇ'<BFJNRW7#5##535#3#3#&''275#535#535#3533##"''3255#&'735#33535#335'#2ïgg+$$#  *##$$,=  = #5# ÇÞÞÆµ  <  <  }  b " jÿéóÊ #AEIM73#5'675#73#5'675#&'7&'3&'73#3#3#3##5'65#5#35#t9'B9'0R1 ,*&&&&,d 6&&&&&ÊW  W     4 T   ÿèpj#73533#&'#5'67#7&'7'6&    O >,,  )16    ÿèYÏ"73533#&'#5'67#7&'7'6   <}RRWU"-QiÿéôÏ!73533#&'#5'67#7&'7'6q17'    j  xWW9!Bwv?'!9[   xïÏ#73533673#&'##5#'67#53&'D    ,A( 3" 9 .?- Ï      ÿêòœ"&2J7#3#'6553&'75##5#35#3353357'5337367326767#"&5ì>7°_))))<)#~3% 2  %  )… 0*! %/:   A ?      ÿçïÒ#'=J7#3#'6553&'75##5#35#3353353267#"&5536'33#67'ïC:·X+,,,?+( % % „33$ Ã3C< 3@]$#.   9 0ÿèñ 7&''&''&'''6Ú     &      ÿçïÒ#'=J7#3#'6553&'75##5#35#3353353267#"&5536'33#67'ïC9¶[---->-) %  #  ˆ99 ' $C< 3@\      .ÿèôP!73533#&'#5'67#7'6'&'6RVH- ."/ -D‘ d  /!! -- 0    ÿçõƒ"&<H7#3#'6553&'75##5#35#3353353267#"&5536'33#7'íE:±Y))));)( ( !&Š664# t -% ':  !   '   ÿçðÊ!7&'3673#7&''67&'vS!;à 8!#)e*!#'Ê#,,$g/22(j q ,"$'9ÿé’Ï 733#3#53533367'67''6iT ;   Ï775N J$”ÿéóÇ!7#5##5367#53#3'>&'ì(T!   ¡‚pqƒ  '@'  ""/ÿêÊ $(.473#3#53'#735#35#3##"''3255#735#&'''6EL Z&****I  ((3  4 ÊF* ( F2&  !(     Dÿæžž#)/73533#33#53535#35#35#35#35#&'''6LZ &&&&&&&&"    “  __ $ ! ! "    &ô73#3#5##537#'67&'×^LƒEca e["?+ )=*/ $  "™ÞË 73#735#'3#735#ŠTT//zTT..Ë22 ÿéß' #53#=##53#=#›V1gW0 >>  >> ÿèîÇ 7#53#3#5##533'6656&'u]Ñ^ Q}> j 8,%.& (-´|hh|&,E ) - ÿéoÏ 73'6573#'3# E#ÏpF0 *?pæÙ¿ ÿèvÂ73##53#67'5#'66RR f   Â>Z kA40 ÿê|Í 7'67&'3#"''3267#'67#1 0*O  Í5" ** &%R` IM"B@ÿéÏ73533#&'#5'67#F  22 ni'2ÿåïc 7'67&'67&'7''6]#*/a%)#+'-1'BK&c ' '%  iðÏ 7&'''6767'7&''6ª &#$H. /4& 2;Ï" "!  •ÿéõÈ 7&'''667&'7''6á %  È@F;*(V" )0 *hÿéó’ 7'67&'&''6767&'‹ N  ).’-(,1R (   ?>  ÿüyÇ 7'67&'67&'7&''6,<  %%Ç?"%2%, .$<O #* .ÿøsÆ 73#53#3#'35#35#735#[X_(## Î,PP,¨,2 ÿé|Ð)76767'67'67#53&'73#&''6(7  +,: 0    BnJ     !OÿèíÏ73#5##535333'>&'£<U/JXM  %¨kZZkBB'> fÿåðÏ 73#5##535333'6656&'°3E&=J  !$§jZZjD@8  "$\ÿåðÏ 73#5##535333'665&'«7L)BO ! $%§jZZjD@2   ##ÿèŸÎ(.47&''67&'763533##"''3255#&'''6‚ + Y855   8i  C Î   [N  J  ! hÿéóÎ(.47&''67&'763533##"''3255#'67&'×  \:66   : ]  Î    ZM  H & #! !dÿèôÑ!)73&'73#&''67&'#67#3#'3'65m5 : !! C ? (A$!¹    Dgg. $ ÿèkÎ(.47&''67&'763533##"''3255#&'''6V    ;###  #I  /  Î    ]P  L   UÿêòÏ 57&'67&'67&'63'67#&''6~   =   :   AMh _E )Ï   = M?  QÿéêÈ7#"''3255##53#3#735#ê  q ZZ HH""ÈÅ  ­Ìß,W3ÿèä‹7#"''3255##53#3#735#ä 'wwggBB‹‹  t‘£#9Kÿé´È7#"''3255##53#3#735#´ E1100ÈÅ  ­Ìß,V6ÿèâÏ#'7#5##535335335#33533535#35#35#â;'v'';'''';'';''¥½½****N;;;;;ˆ:::::PÿéíÏ#'7#5##535335335#33533535#35#35#íw/].w./¥¼¼****N<<<<<Š<<<<<rÿéïÏ#'7#5##535335335#33533535#35#35#ïV$F#V##¤»»++++M;;;;;ˆ<<<<<ÿéàV#'7#5##535335335#33533535#35#35#à:)t((:))((:)):))Jaa $8\ÿéôÏ 733#3#533#&'#5#”@@K”58˜K* 9Ï# FdnÿéñÏ 733#3#533#&'#5#77@€-/ƒ@$ 0Ï # EdÿéóÏ 733#3#533#&'#5#§//7q&(t8  )Ï # DdYÿæöÏ$*73533#3#&''67#53655#&'7'6n188?<' '2 2271  d  ¯ ;/1,(;   -öŽ73673#&'#'67#Po<' 1*= -@{   '%iîÈ 73#3#535#5#35#335335ÙE7À8C‚-6$$6-$È<<* ‡òË 73#3#535#5#35#335335 åG8Ç9HŒ28&&82%Ë ++  ŠòË 73#3#535#5#35#335335 åI:Ç;JŠ.:((:.'Ë ((   pòÉ 73#3#535#5#35#335335 åI:È;I‰.:((:.'É99( ÿèõ‘'+/4:D73#3#3&''67&''67#5'635#35#67''6#5'6uiqnLN ! +!,"  PPPP †   ‘ 9   2 %  -  m P@ ÿé€Ç 6<73#3#535#5#35#3353353673#&''67&'67#367o#h <    ]&: %  1 ÇBB1 F    ~ÿéòÏ!73#"''32767#'67#'67#'6›K   = 7%  Ï–. %mq;6fG$ < "c]óÇ 73#3#535#5#35#335335c-&ƒ&,R%%ÇFF5$$$$$WÿêïÇ##535#53#3#'#3#;5#35#Ög(-˜/))Q¨##¨Ë#tttt]ÿèåÄ73#735##53#=#eyySSˆbÄY5·mm66§ÿðìº 7#5##535#ì  ºÉʦ”?ÿéôÏ'7'67#53&'73#67&&'#67'h  -5D  HC%"  ! K #0$ "# HBa   föÐ73#&''67&'#53&'67€`$'8C0-< 1&`##Ð     #ÿéâd 73#3#"''3255##5##5##537##¸Vc  %%%HNd>  '<<< %4Å wwwww¡ÿê}Å"73#3#"''3255##5##5##5367#o04     !+Å wwwww¡r!òÆ "'73#3#535#35#35#"&55#'75#3'#65r€'!s"): M  M5Æzzƒ,&,(,3 gçÑ 73#7&'''6'6uMnŠ =s vÑ=5  #  mãÐ73533#"''3267#'67#73#735#$5   !& #~QQ++¿7 /$F$ÿíò„&7773267#"&55'75'75'6Ð -2`btv 4>$SVIL#&f„       ÿìòu&7773267#"&55'75'75'6Ð-3bdtv. 9)PSDG^u       KÿéôÇ$*73#3#5##535#35#35#"&55#'655#5#3K©5,p-6H,p pÇ"©©"""§* 7* !772dÿéòÇ$*73#3#5##535#35#35#"&55#'655#5#3dŽ+%[&,>$[ [Ç"©©"""§* 7* "772rÿéñÇ$*73#3#5##535#35#35#"&55#'655#5#3r&!Q"%8"Q  QÇ"©©"""§* 7* #772[ÿòõÅ 73#3#735#5#3#e‰‰ qqJJJJ)ššÅuC Q.ÿòìÅ 73#3#735#35#3#ÊÊžžvvvv2ØØÅuDPMÿòî— 73#3#735#35#3#.¢¢ŸŸwwww2ÜÜ—a9?9HÿòòÅ 73#3#735#35#3#S˜˜ ~~VVVV,ªªÅuDOMŽÿòôÅ 73#3#735#35#3#bb RR----ffÅuC!S!O^ÿïóÎ!%+173533533#3#3#5#535#35#35#35#'67&'c7wuˆ +777777 5 µY6IY56'     E€È)733'67##"''3255#'67#53&'767#b,    -   KÈ   )  %'  vÿòõÄ 73#3#735#5#3#~qq``;;;;#ÄuC Q/iÿòõÅ 73#3#735#5#3#q}} iiBBBB&ŒŒÅuC Q. ÿñòÄ 73#3#535#5#7'6'&'ÐDPæNDx \ ˆÄ­­­­­Š6+ *2,.1*YñÆ 73#3#535#35#'6'&'ÎDNãMBU""] ˆ  ÆHHHH PÿññÄ 73#3#535#5#'&'7'6_‹*1¡4%M+    Ä­­­­­ˆ-/2+4* )]ÿññÄ 73#3#535#5#7'6'&'f„'.”,#I?  a  Ä­­­­­‰8%.--/2+\ÿèöÏ#'7367'673#&'#'67#3'6673#h-$;0G$  $ ,$&!  1“,%+3( &*kUñÆ 73#3#535#35#'6'&'ÎDNãMBU""] ˆ  ÆLLLL Kÿïï‹ 7#53#3#537#37'6'&'~*’,5¤3(3 q  xvvva+ "# %Kÿêï$*73533#3#535#3533##"''3255#&'Y;<W$ ¤     < [ÿëóÆ#7#53#3#'655#5373#3##5#535#‚</ '.@´DA)$5VDrrDQðÈ!7#53#3#'67#5373#3##5#535#F*^!"") !/0Bc+00''%¶"-66ëÊ7#53#3##5#537#53#3##5#53?'`'((**q&_&((**» ÿçõ"&<H7#3#'6553&'75##5#35#3353353267#"&5536'33#7'íE:±Z*))):*( ) ! „664# z /& "(=  %  ,    ÿçïÒ#'3I7#3#'6553&'75##5#35#33533533#67'73267#"&5536ïC9¶[---->-)™99 E£ %  $   $C< 3@\     '   +ÿçïP!73#735#367#53#3#&'#'67#JŠŠhh'LE¦O]K7@D 5@P! 1  FÿéÎ7'6'67#p  %Î-a "' xÿéóÁ73#3##"''3255#šOO c"   -Á.k  hÿïïÏ73533#3#535#3533#3#535#$QQQaÖ`QSSSeÞdS­""""b$$##"‰ÝÉ 73#735#335335"»»%%7%%É@ ÿè~¤#'+/5;733533#3#3#535#535#5335#35#33535#335'67&'%/)c)1(?(13¤ HH !K,"    Hÿõ§Ï!73533#3#535#3533#67'75#T V")2&±W# &Tÿô¶Ï 73533#3#535#3533#7'75#_$\$"*4(±W$ 'bÿéóÊ (73#735#35#5#53#3#33#"&''6~ee@@@@ 4|4))&' Ê\6:OA"mÿïôÏ73533#3#535#3533#3#535#~...1~9.0//6‡=0­""""a$$$$<ÿùÄu73533#3#535#3533#3#535#F0007€60222;ˆ:2g2=Á¤73533#3#535#3533#3#535#K,++5ƒ:,---7ƒ8-DfõÐ'77&'3'33#673267#"''67&'#36533#'67#Ù  `B33      C!Ð  +/&-$YÿéöÏ!'573'33#67327#"&''67&'#7&'3533#'67#dA43     Ap  d$ £,,<'##2   K<   k%%;0gÿéöÏ#)773533#67327#"&''67&'#7&'3533#'67#hA33    Bo  ^ £,=%3! #2 #H<   k%%;0iÿéöÏ &473'33#67327#"''67&'#7&'3533#'67#r:--     ;g  [ £,4$6 #2% #H<   k%%;/sÿêóÏ!'773'33#67327#"''67&'#7&'36533#'67#v7..      7b  T£,2!$5"/$   K;  i6 *ÿæõÏ%+73533#3#&'#'67#536565#'6'&'#TUUhUJPM DQYT y  ¯ 1,!7<+   ÿëÏ"(73533#3#&''67#535#'6'&',1179 $ ( 25,\  A  ²;  (-D  ˜ÿéñÃ7#"''32654'7##5ï  .Ã@ DÇÚƒÿèôÇ%*733#"&55#'6653&''67&'#367Ü    ]     Ç<  0$  +d1   $bÿæöÏ$*73533#3#&''67#53655#&'7'6v.33;8# $- 0 04.  ^  ¯ ;01,(;  eÿéóÐ:7&'77673267#"''677&''7''7''7&'3 0<34;:   % ;<--,,Ð       "'    &pÿéóÐ:7&'77673267#"''67&''7&''7''7&'3Ç ,7//65    $76))()Ð      "&   &ÿèðÑ$A7673267#"''67&''7&'73#32767#"&55#'667#×b  ) 7# IAYÜM$".(EÇ         h5 <#" MÿëôÏ#@7673265#"''67&''7&'73#3267#"&55#'667#æI   ,*=•7          e8 >!%  ]ÿæôÏ#?7673265#"''67&''7&'73#3267#"&55#'667#ã<    "!7,   Á        e8 >#(   ÿè÷Ï">7673265#"''67&''7&'73#32767#"&55#'65#n/   ! 1m,<G: ¿   &    a9   >&& 4éÃ)/573#"''3255'675#'3#"''3255'675#&''&'¸1  81    C  +  Ö *>– *>  BÿåôÑ#>7673267#"''67&''7&'73#3267#"&55#'667#àH    1/G 5    (Á    c8 >#)  ÿèíš$>7673267#"&'''67&''7&'73#3267#"&55#'67#Ø\   +8 8# JA [×M$ I > B“      T ". ]ÿéôÏ'+/735333##3#3##5#535#535#535#535#33535f177<   ™ " ]J_ÿè÷Ï%+173#3#'6553&'#53&'367#'6'6'6® 4r 9 9 8 2,' ; 4, (> ?Ï 7-$ #,@ G'   iÿçòÇ 5;73#3#535#5#35#3353353673#&''67&'67#367oƒ)"w"'H!!p.G-+ ! $:ÇCC1E   jÿêõÊ '73#735#73#735#3#3#3#"''3265#7#w55,77Mmm‹TO  V !ÊB B ?4 %iÿïóÎ!%+173533533#3#3#5#535#35#35#35#'67&'n2mk~ (222222   3 µY6IY56'     ÿéôÏ'+/735333##3#3##5#535#535#535#535#33535%NRRZZllffQQOOffNb>>>½(((('ÿéñ—'+/735333##3#3##5#535#535#535#535#33535%ORR]]iiddYYQQffOa???‹     Qÿé«É #7'67&'&''6#5##535#k -  5  É$ %  0^^<+gÿéóÏ'+/735333##3#3##5#535#535#535#535#33535v+//33>>::,,**11+>»''''& ÿósÇ"7#3#3#3#67&'7''67#535k?<<< °# 2 9 #ˆˆq7& $5=6 :9|;. +RL=!L9&,(bÿç÷Ç 7'66553#&'7#3&'&'‹ q ! KK< " !|=, ) RK=M9'.* ÿèò7'6553#&'7#3&'&'<¯" / 7 !ˆˆo4$ "2:588T *" "(=<(6*"    hÿç÷Ç 7'66553#&'7#3&'&'’ k EE9 |;- +RK>!K9'.+BÿëñÏ1J736533#"''3267#'67#3533#"''3265#'65#73533#"''32765#'65#Z3D17 ,/%   ' W&   ' º 4 'QO57"*P.7"* ÿèQÎ 73533##5# –88›› ÿçóP 73673#&''67&'67#367 E‹9D("'J:!$ 7N >     DÿëñÏ1J736533#"''3267#'67#3533#"''3265#'65#'3533#"''32765#'65#e/B /4 (+B"   + #U"   + #º 3 %QO56#*O 16#*RaóÑ/G73#"''3267#'67#5363533#"''327#'67#73533#"''3267#'67# <   / 0# ,5E%   O%   Ñ   <       Vÿèó] 73#735#35#35#'67&'fYYYYYY  K]U= ! !    VÿéñÈ/37;?C73#3#"''3267#3#7&'7''75#535#'67#735#33533535#35#g„[j  9((*25))  &^*È>g+X// žÿéôÆ%*733#"&55#'6553&''67&'#367ã    G    Æ9  . +d -      Rÿð Î"73#35#535#53#56#53#7'7p (!K I!'Î jdŒ(  ÿêUÏ7367&''65''6+  Ï9  ( 0I2+$eÿéôÌ1J73673#"''3267#'67#3533#"''32765#'67#'3533#"''32765#'67#u,8  & 0 & (="    G"    ¸ 40 !TN/A6N/B7 YðÌ(,7367&'#"''325567#3'67#3#- '9  yT: 1=››Ì    (! & %6)ÿéÖM7#53#"''32=#335;­  ‡‡‡‡ #dI  0  wÿéðÏ#'7367326767#"&5#5##535#35#w,% +3 )<nFFFFFÏ   $zz-HÿéîÎ#73533533#3#535#35#'67&'-F..8Ý7-AFF "(m! $Ÿ////AAAA] &XÿèïÍ#73533533#3#535#35#'67&'b#!!%’"1##QŸ....AAAAZ +!eÿèïÍ#73533533#3#535#35#'67&'n!….LŸ....AAAAZ +!ÿéîm#73533533#3#535#35#'67&'.G//9Ü7.@GG!")k" %X4   dóÑ767&''6''6{ % L! A k:Ñ "' >  Kÿéñ‹#73533533#3#535#35#'67&'Y)!!)¡'2)) Uq&&&&?  ÿëS 7&'&''62 ,    # #  DóÐ#73533533#3#&'#'67#535#35#/>22A=  %%84 ,>=/B>>· $GÿýµÐ '73&'73#676767&'7&''67'6N'"`!  # ((² ) @%    "sÿèñÍ#73533533#3#535#35#'67&'{z*H Ÿ....AAAAZ +! ÿéxË #7'67&'&''6#5##535#,5!?***Ë $#  /^^<* ÿöMÏ7&'367'5#- ) Ï ?o  qGÿê÷Ï _ci7&''67&''63'33#673265#"''67&'#3#3#3#67'675#535#535#535#75#7&'f   2   Lu%$    $)3$4f Ï     )FF'+! "!3  jhGTUòÏ#73533533#3#535#35#'67&'a+%™#.++  Qº)    ÿìôe!&*/7'67&'#3#3#53&'#535#73&7##35#C J0 43 3J;Þ2L27F!0'4    E6ÿðÉr#73533533#3#535#35#&'''6@"&“'/""*#]2  ÿçð\#73533533#3#535#35#'67&'+F,,8Ü6+?FF$*+e(' '(M*   WóÏ.736533&'73#673267#"&''675#'67#BA -Y    $# 4 .<¶       4(ÿèíY-73#673267#"&''675#'67#53733&'« 2U   ' !6 .( "2R & 8ÿèô› (.73'67'677367&'3267#"&5'&'s> 1 * !  "K  ›a2 $  P*    0  ˆgÿçó+173'67'6767367&'3267#"&5'&'• ! #     8  K,& &  <%   , z_ÿé÷Ì"(.73&'3267#"&5'3'67'677'6'&'²   $(" S d  ÌeJ  ÈvC* "3 R & fÿçõÏ "7&''6&'67#53&'¤# ! (   Sk  'Ï..+'   X   ÿé‰Ì !'753'67'6767'53&'&'7'62'#B X n dh}>( #5B Æaj   ÿëðÆ!%73#3267#"&55#'67#735#35#35#W ! 111111Æ0  65*fBC{ÿëðÆ!%73#3267#"&55#'67#735#35#35#ŠY " 333333Æ0  66)fBCrÿéòÆ!%73#3267#"&55#'67#735#35#35#}d " >>>>>>Æ0  67+fBCÿìvÆ 73'32655#3#3#735#f#SII==ƾ  ¥O*]ÿëñÆ!%73#3267#"&55#'67#735#35#35#rm  - %GGGGGGÆ0  65*fBCÿérÏ733#3#5##5335#1--$0 00Ï-3ssR?ÿé{Î733#3#5##5335#255.;! ;;Î,0wwR@ÿé‹Å $7#3#3265#"&'#67'535'#€%*( " D$ÅI"% ),/B  Ò%%\%% ÿéÌ !7'67&'3#"''3267#'667#8!7+Q     Ì=)$4* &%SY'F-1*( ÿé€Ï73#3#"''3267#'67#53&'H+>6  $"!3Ïf&ND5 A`ÿéòÄ#7#5##53'>33267#"&5Ìs>/3 ''   *Ę…„—":-> $+I  tÿé÷Ì"(.73&'3267#"&5'3'67'677'6'&'º   # I  Z  Ìe I ÈxA* . T $  wÿðóÄ$73#67&'7''67#3533#3#535#}q5 (,%'++6|2'Ä( #  (u ''ÿèôu,159M73533#3#535#733#3&''67&'#535#5367'3#735#676767'27&'...)c(.ž++%    %((  ³``<<  36 i   !  1 )    ÿéñÏ#37;L73533#3&''67&'#535#67'3533#3#535#3#735#7677'7&'ƒ-..)  *-  È.//+g).cc<< 5@  ¬###6(  .#5 !T8=5  VòÑ159I73533#3#535#73533#3&''67&'767#535#3#735#7677'27'100+k-1y***%   G'*rff@@2? É      "   ÿèó_#)73&'&'7'67&''667#&'`\!0 :& 1! "2&1# .9Q1X< ;V_     2   ÿêô059=N73533#3#535#73533#3&''6677&'#535#67'3#735#67677'67' .//,g(.w...'  !   +. Ádd== 1< u   ! !*     †õÐ 73533##5#3533533##5##5#[[[[2`22`2Å     eóÒ-2BFK73533#3#535#73533#3&''67&'#535#67'3#7'27&'#735#277# 333-k,3|,,,&  +,  Ád 3>  @@ 'Ì        #  ! ÿìðk+/73533673#5##53&'3#3#3#535#535#735#F  0µ0”@WWfÞeWWAllg   ""!%   [ôÑ.3AEJ73533#3#535#73533#3&''67&'#535#67'3#7'7&'#735#77#333-i+3{,--&  (, ¿b 3>BB+È      "    $ GÿëúÅ,37327#"&55#'6553#3#"'#5##535#32655Ü [M  0ÅV66# .REN@; 1>_'R $zZj&F>OÿëúÅ,37327#"&55#'6553#3#"'#5##535#32655Þ VJ  .ÅV66#!/QEN?< 1>_'R $zZj&F>>ÿëúÆ27327#"&'5#'6553#3#"''3255##5##535#Û bU     "ÆW55! /QDM@; 2<`(R >zzZjJÿéõÐ&*73&''67&''667##5##535#ƒQ% *"0'  %EbYYYÐ        ^\ \>+XÿêöÃ"(73#3332677#"&547#67'7#&'XŽ]O    <#A  Ã!d(   %T}·NÿéË73#3#5##535#535'635#„ 555*@*55@!@@Ë%)hh)"µ1ÿéšË73#3#5##535#535'635#9;;-G.::E&GGË&)hh)$¶2 ÿïÇ7'67#53&3#7'75#Y& IRh  Xr/6‚9/} 5 6  8 ÿî›Ñ'+73#"''3255#3#3267#"&55'6#34R   C=.* 5 /Ñ}  f I1  t#((kÿéóÏ73#3#5##535#535'635#â ::.J0::>#JJÏ+&dd&'¶1=Î735'273#3#535#35#.22..$[$.77Ÿ <<@€=îÎ735'273#3#535#35#€-11 ..&["-77Ÿ <<@ 5~Ï!'-7&'3#3#7'75#535#5'6'6'&'C  A%%,/9+## '2  9 Ï  $&K    †ÿéóÏ73#3#5##535#535'635#ã --%8&--"088Ï+&dd&&¶19ÿéŠÎ 73&'#''6`  Î ¬¬& ÿéðÉ73#3#5##535#535'235#Ù (1ffO…Jff(-_:……É&&f f&$²3 ÿésË73#3#5##535#535'635#i ))"/!))!1//Ë&)gh)#·2 ÿéyÍ )73&'73#&'''667&''67&' *,kQ  %   $ «   !    cÿê÷È&,287367#53#3#&'#'67#3#"''3257&''&'''6n,+o1B% '#,  B   1 ™!"g  K#)+"!') ) !Nÿéòž73#3#5##535#535'635#Û9HH;d;HHF3ddž N OŽ" ÿçNŸ 7&'&''6+-Ÿ  #  $ &$"gÿéò73#3#5##535#535'635#Ý <<3P1<<*:'PPM NŒ!ÿêgœ 73533#67#"''32655'75#"     '"| !/ %%ÿõ` 7#5##535#35#`!!!!!›š<*f+fÿéóž73#3#3##5#'6…bTGGLL ž 0ˆ eÿéñ73#3#3##5#'6ŽXKDDEE  1‰ aÿéóÏ73#3#5##535#535'635#á ??2P2??C'PPÏ+%ee%'¶1€ðÉ73#3#535#535'635#ß//#Z$..%355É#NN *TíÌ73#3#535#535'635#Ö BB1|6BB ?"RRÌ!KK˜'– óÇ73#3#535#535'635#æ&&L%%+))ÇGG&LÿéøÊ37;7325#"&547#'6553#7'7''75#535'635#335á WW   ! !  ÊI8&%%;/?n<$">zV!Vk555 ÿéõÉ48<73267#"&55#'65535'673#7&'7&''75#735#335Š. :P! $  És4& 0=`m<# ?xGV 444ŒöÐ'+73#"''3265#3#3265#"&55'6#3¨9  -*   !Ð Y @6/   ] $ ÿéò\73#3#5##535#535'235#Ø'0iiR‘QiiS]C‘‘\  66  T ÿèó£%)73&''67&''667##5##535#a [%0 :(/B :( 0;Sbttt£    "  Wh}}}y    .??'&|ÝÈ 73#735#33535#335&··==Q>==Q>ÈL. ( wÿéôÐ$(73&''67&''667##5##535#œB     5G:::Ð       a\\<+ÿéxÐ#'73&''67''667##5##535#82 #  "'9%%%Ð    " Lbd?-oõÒ159=73&''67&''667#'3#3#7'75375#735#3#735#™>  $  ) 2 W!!!%+4 %55\^^<<Ò  $  # ,  dôÒ159=73&''67&''667#'3#3#7'75375#735#3#735#™>  #  + 4 ŽT%,7"11Ybb@@Ò   (  ' .$  ÿéÐ-26:@F73673#7'##"''3255#5'67&'767#&'35#35#'67&')5%       #V ,,,,   R  ·    @0 -6   &!1(    ÿéŠÍ #06<B73#5'675#73#5'675#&'7&''67&''6'67'6 7  %?7  %+P &  ! #& + ./ "C DÍU  U      K      + ÿéŽÑ"73#"''3265#'6##55#35*X  I H11Ñ ˜5)} "!m~-NÿêõÑ4733#'665537#'6#3267#"&553#"''326‹I(} QA "V3%.Z   Ñ=1( $G \T  l7 ÿçóÑ6733#'655'6367##3267#"&553#"''326T ] J¯! 1 X S€U +.  :9~ Ñ 9/.#,D   EN   d5 QÿêõÑ3733#'65537#'6#3267#"&553#"''326ŽF'xM= !P0 +U   Ñ=4& '0G \U  l7  ÿçô˜7733#'655'6367##"''3267#3267#"&55U] G°! 1KT  T *1  ;8˜ ($" 0 )'0E?ÿéñÐ4733#'665537#'6#3267#"&553#"''326€K+„ TA W6& 1]   Ð>4& %G [T  l6 oÿêõÑ2733#'65537#'6#3267#"&553#"''326Ÿ;`>0 D% # H Ñ=4& (0F [U  l7 ÿèpÐ"(,048733#"''3255##5#'655'6367#35#33535#3352'     !1!Ð – )%%+.N 6H ÿëƒÏ1733#'655367#'653#"''3255#67'533"Q* (  <    Ï =2' ).G  O0 U  kKÿéöÏ$73533#3#&'#5'67#535#'6t88@3&"),2@# à ''')#$+cc.##-'  ÿéõÏ$73533#3#&'#5'67#535#'6<1SSi]%: :&$9=$_i; Ë**+03]Z/-+ OÿéöÏ$73533#3#&'#5'67#535#'6v66>1&!',2>" à ''')#$*b_+"#-' bÿéöÏ#73533#3#&'#5'67#535#'6‚//6)!$'6 Ã''')##)`a-"#-' !YÿéöÏ$73533#3#&'#5'67#535#'6u66?5(!!%1?" à '''($"']_+"#-' hÿéöÏ$73533#3#&'#5'67#535#'6€007* )8à ''')#$)a^)!#+'  ÿéÏ%7#5'67#535#'673533#3#&[#.4  ..55@WW!!(' $ '''gÿèôÍ$73533#3#&'#5'67#535#'6†33<5! !/; Ä &&'60W^/%(2' CÿéöÏ$73533#3#&'#5'67#535#'6j ==F8*'+,5G( à ''')#&,fh/%#+' ÿé{Ï&7&'#5'67#535#'673533#3#O '-&&--VUR!(' " '''VÿêíÑ"73#"''3267#'6##55#35xj  ] P77$$Ñ™/… %$m~-aÿêíÑ $73#"''3267#'6##55#35ƒ_  S K44!!Ñ ’! $#m~-IÿêíÑ $73#"''3267#'6##55#35ot  i U<<))Ñ “! $#m~-AÿêìÑ $73#"''3267#'6##55#35jx n Y@@,,Ñ ’… #"m~-*8ÜÑ 73#3#53635#35#nZ– ²8.„„ŽŽÑ 79ƒ 1a&ÿèÚ. 73353#5#533w=¡=.'= 1~ÿéëÐ733#"''3255##5##53ª-  ,Ð%|  d¯¯”ÿéiÐ73#3#5##53635#352%?B. --.Ð MaÍ H)c++ÿìpÐ73#3#5##53635#35>"CG3//3Ð M`Ê H)d**ÿéhÐ73#3##53635#35#2%?AA --..Ð MNÎ I++ ÿèôA!73533673#&'#5'67#53&'B !# 'J4 7($80$L+@   ++ _ÿéóÊ*7#5##53#3533#3##5#535#'67#536ðg5LU '55>>CC: 'Ê-, ...‡ÔÉ 73#735#35#.¦¦ÉB( # ÿéí~)7#5##533#3##5#535#'67#53673#3í²gII]]``G +]k +~&):   eÿéóÊ*7#5##53#3533#3##5#535#'67#536ða2IQ $22::AA8 $Ê-, ..ÿéwÊ(7#5##5#5'75#'67#5373#3533#wB] '*& 39 ##Ê-,›.,dÿéôÏ$(-7533'67#3&''67#&'#'65535#67©1 , ¯  10!  #<. /8TD1Bÿéí’)7#5##533#3##5#535#'67#53673#3í²gII]]``G )`l -’*-A""   uÿéóÊ)7#5##53#3533#3##5#535#'67#536ñU,@G ++33770  Ê-,..ÿéÊ+7#5##5#5'75#'67#53673#3533#6Y}17;2 "BI --Ê-,Ÿ)'  :òÒ$0@DH73673#'67#'#"''3254'7##53#3#535##"''325##535#35#jYd   !pV&4{5]   IJJJJÆ + *€‘6 G ÿêôM #)7&''63#"''3257&'''67&'|2@ 65'B F    )  DŠ M(     tÿèöÐ $7&''6#5'753'&'&'µ " +RBE;  Ð !v64  ]ZO    ŽÿèöÐ #7&''6#5'753'&'&'  #@142  Ð !w74  ZWO     cÿéðÏ#73#3#3##5#535#535#536'&'Æ )922<<==449H 3  Ï##::## 8ÿéï¤#7&'73#3#3##5#535#535#536i ] 1LDDSSPPBBIb ¤   ++\ÿéðÏ#73#3#3##5#535#535#536'&'Å ,=66@@@@66$+( +Î: > ;(!&]$E*2*#kðÏ73673#3#3#535#535#53&'R 3 7`UUgàfUU_6Ï      ÿçîf'+/4973533533#3#"''3267##5#'67#735#335335365#35#>*99G  40 : ,7 2>R*%œ&#5,+[ - # ++" , ' gÿè÷Ï-1773533#3#3##'32765#&''67#735#535#35655#q1<<33? ,/ -&- /+**1E 5½6 &/'' )8$9 uÿëòÎ 7#5##53&'73##"''3255#òV3H|0   8­00 =[  W[ìÑ73#3#3#535#535#53&'736¬4^NNbÜfNN^5 7 Ð  [ÿéöÐ,0487&'#673265#"''67&'#'6553'33#3#735#â"    ;LF..//Ð /3!+3)$IH<- /6S##%E#bÿéöÏ!26:>7&'#5'63&'3#"''325'#"''3255##535#35#73#£% L  ->9  ( 8Ï " .s  mt  1‹%8-\?ÿéôÄ $(.47#3##"''3255#5367#'65535#35#'67&'ë6/&  '0!59999  X Ä _:  6_ [;4 ,6lQ=4"   ÿêêÈ#'-37#3##"''3255#537#'665535#35#'67&'êRG;  B4N 9llll  i ÈF  FSF4 1$_; & "    2ÿè÷Ê#'-37#3##"''32655#537#'65535#35#'67&'ñ< :-   1" = 8KKKKY Ê^> 9^\<7 -9nQ:3$  Pÿè÷Ê $(.47#3##"''3255#5367#'65535#35#'67&'ò21&  )03<<<<P  Ê ^>  9^ \<7 -9nQ:3$  Xÿè÷Ê $(.47#3##"''3255#5367#'65535#35#'67&'ò,,"  %,34444  M  Ê ^>  9^ \<6 ,9nQ:3#  cÿè÷Ê"&,27#3##"''3255#537#'65535#35#'67&'ó*)   $)20000  I Ê^>  9^\=6 ,:nQ:3#  [ÿè÷Ê $(.47#3##"''3255#5367#'65535#35#'67&'ò,,"  &/35555  M  Ê ^>  9^ \<7 .9nQ:3#  mÿêôÇ"&,27#3##"''3255#537#'65535#35#&'''6î$" &0))))5  4  Ç`A  >`QC5 4>^K>4!  ÿçìÍ 73#53&'3#'65536'&'_ÎX> ?±o F  Í  -0' &8[ÿçï—  73#53&'3#'665536'&'¬:„5* %n I*— ! $ *  ÿé]Ž7#"''32654'7##5] "Ž*.”¥`ÿêóÐ 73#53&'&'73#'65536µ1‚; D #oI Ð  -0( %9XÿëóÑ7#'6553&'73533#3#535#ñn82$%%/p.$·K>1 1:V  V44GGaÿéóÑ 7#'66553&'73533#3#535#ñf 6 2!##,i+!·L@0 -"X  W55HH\ÿèöÎ$7&'3673#3#&''67#535#€   O !9A;& '1 3 9=8Î (!474 #/+Bÿí¦Î &7#53673#3#&''67#5365'&'q' "% # !  ‰* "/ ^ gÿéöÎ#7&'3673#3#&''67#535#Œ  F  3:5" "0 0 471Î (! 361 !/+GÿèòÐ&7&'3673#3#&''67#53655#x  L  %>KI534 9<@5Ð )!50."-!>ÿîªÍ%73#3#&''67#535#536'&'‰$') " " "% /  Í !'+%Mÿè©Î &735#53673#3#&''67#7&']!    #  e# & (7yTíÑ%73#3#&''67#5365#536'&'¿ %;DF& !0 5=@8L 7  Ñ$"%   ÿèŒÍ$7&'35#53673#3#&''67#:  0)<  124 "(.Íc'' $. '4òÏ$7&'35#53673#3#&''67#±'!/#'(  "%Ï  Q% *>ÿñ¾w%73&'73673#3#'67#5365#&'E  0;= /&03.O W   * / @ÿèï£#)7&'73#3##5#'67#53655#5365#o  b '$++0+-#\.£   *DD/#O*SÿèõÏ#)73#3##5#'67#53655#5365#'&'à *$$,)#!I *  Ï6]]@5 %^6(sÿåóÑ!%7&'73#3##5#'67#535#5365#K  t ;1==B? 6>>/xBÑ   9YY9$-9b99bÿéóÐ &73673#3##5#'67#535#5#'&'oG  !$( !!Q"  ¢7\\@4777. wJÿéóÏ!%7&'73#3##5#'67#535#5365#~  ^ !((-, $%' [ ,Ï  8\\@48b88hÿéóÎ%73#3##5#'67#535#5365#'&'Í "' B!  Î8\\@48a88u dÿéóÏ!%7&'73#3##5#'67#535#5365#‹  R " &$ !"I%Ï 8\\@48a88vÿèóÐ#(7&'3673#3##5#'67#53655#5#“ @  GÐ $7\\?5 %77(ÿéßÐ733#5#535#535#53'&'7'6vR©©¡¡§UD  ª ÐR•''H  ! eÿéíÏ733#5#535#535#53'&'7'6 8nniim6)  s ÏQ•''H  ! nÿéíÏ733#5#535#535#53'&'7'6¤4ffaae2$ j  ÏQ•''H  ! [ÿèñÑ!)73&'73#&''67&'#67#3#'3'65c; : "# G A *C&#¹    Dgg. $ÿèð™#'73&'73#&''67&'#3673'673#_`0 #8A,(>6#'? W7 /O…     EA1DÿëÎ,B73533##'3267#'67#3733##'3265#'67#73733##'3265#'67# * #       ;      ². ' S E 09 1D 08 0zÿìôÎ %733#3#533#"''3265''67&'¬''1t/   K  Î!!S  C '   €ñÉ&7773267#"&55'75'75'6Ü.068  $& 0É" % ÿéùÎ $57'67&'''6767&'3'667332767#"&50 9 (,-  ' MW+Î# 3 &" 30( $+J   FõÐ%+73673673#3#&'#'67#5367#7&'T  ,o‡7% -F' +@ I+¬    5  =õÐ%+73673673#3#&'#'67#5367#7&'T! ,n‡8$ /F' -@ J+©    8   mõÐ$*73673673#3#&'#'67#5367#7&'Q   -p …5# ,G($< D+¶        (  dõÒ*7&'3673673#3#&'#'67#537#A  S *o„5! ,C) )?GÒ        ÿëä)3Q733#3#&''67#&''67#5353353#5#5335#&''67#&''67x,,Z9   5  7'"O¡    3           /WQ      ÿèè‹*.273733#5367#35#3#3#3##5#'7#535#735#35H!<'¿?)5/I¢F]]^^R_H}}B~   "&   / )ÿèØx 73&'73#3#3##5##535#)NL¯     {{{g    6 5ÿçÕr"(,06:733#"''3255##5#'655'667#35#33535#735f7; 84 13 ,  00B8{10B8r T  $   '  ÿéäp)/5767&'7'#"''3255#'67'67676'67&' ./>   &&""?# p f "       E     ÿéÜ`73533#&'#5'67#:=;;0" "#/ 42H7? lîÏ 73#5'6773533#3#535#'&'U '"222,k,2PÏc &  ÿéíà &,73'67#&'367533#'67#7&'&'Ð  ¸4*mV]QHcz(" 'à  N.11%NMÿéíŽ &,73'67#&'367533#'67#7&'&'Ð  ·3 *lU`K<] ~% $Ž   6 "% # ?  7   jóÐ;73#&''67&''667'3533#3#&'#5'67#535#¡D    ·&((--  !(-&Ð         ÿéíb 7#'655í¾b*& %) ÿéîg 7#'655î¾g*& &.1ÿéðQ&7773267#"&55'75'75'6Ì!%NPXZ *'FI>@MQ         /ÿêñO#7367&'#"''3265'3'67#‹" .  J:1(&O     91 $?u» 7&''6[  » Q +(' ÿèò\7373#&''67&'67#67# T  {/ L,'*E 6# Ex D K   % wGñÏ!'73'67#'6'&''673&''6ª2 &   E  &7  Ï R  ;   8 vÿîóJ !7&''67&''33267#"&5·  &  `  7  J   2 KÿêòÏ$*73'67#'6'&''6553&''6›L  =( ]) :&W Ï (# y/.C ?Y ,)' ÿêõg73#&'#5'67#535'2Ì "*haF B #< AZe"&Sg -0# _ÿè÷Ï%+7'673'67'&''6553&''6ª  B  p \' 5&A • 03$,†*(9''8Q   YòÏ$*73'67#&''667#'6'&''6vb  %0 ,4 % G7# 'Ï        1   cóÏ#)73'67#&''6767#'6'&''6vb  %1 -3 %  G 4! %Ï       ,   tíÏ#)7'667#'673'67#&'&''6›$ $  b %%!% 4! %    D  ! ÿéóÉ3767&''67&'#53353#32767#"&55#'6Q $ 5•=  "J CÁ     hvcbuI PRhÿéðÏ#'7367326767#"&5#5##535#35#h3, 1; 1BzRRRRRÏ   $zz-HrÿéðÏ#'7367326767#"&5#5##535#35#r.( .5 ,<rJJJJJÏ   $zz-H'ÿçðÏ#'736732767#"'&5#5##535#35#'OB HV>8 IJ °Ï  &}}.H~ÿéðÏ"&736732667#"&5#5##535#35#~)# (0 $6 hAAAAAÏ    $zz-HƒÿéðÏ"&736732667#"&5#5##535#35#ƒ%! &- "5 d=====Ï    $zz-HXÿêóÏ/73533#3673#3#3#"''3267#7'67#535#i#$$ +< TOQ! Q  # :+#µ  2,DÿêóÏ.73533#3673#3#3#"''327#7'67#535#V*** 1D`[[&[  '%B2*µ  2(._ÿêóÏ/73533#3673#3#3#"''3267#7'67#535#p  )9 PKM  N  !8) µ  2, ÿêóÏ07#'67#535#53533#3673#3#3#"''3267#T;0hWAAAA(! :X!z{ L  1 lÿêóÏ/73533#3673#3#3#"''3267#7'67#535#| %3 IDH  G 1#µ  2+  ÿêîÂ*A73#"''3267#'67#3#"''32665#'667#'3#"''32765#'667#  P O E Jfi  $)ti  $)Â7 : *R=' ""E$">& 7F%"ÿêî•)?73#"''3267#'67#3#"''32765#'667#'3#"''32765#'667##¿  O L BHdg  #(rg  $)•+ - =C $5 B #5 [íÈ7#533#"''3267#7#'6L,Ÿ 9   = D 72µ#1 #5"q[òÇ7#533#"''3267#7#'6•]"   $! ¶#2 #6"oÿèôj73533#&'#5'67#t33)  (O%(ED&% ÿékÐ#'73#"''3255'67#5353635#35#35#7'   ) *>  //////Ð º  *'p 299GÿéõÊ373#3#5335'6'333#"''67&'767#'7#ã  !Z 2p9  96/<   ""#Ê4<vc}($4QÿéõÊ373#3#5335'6'333#"''67&'767#'7#ã T  *f6 54-7      Ê4<vc}>' !'4ÿéó373#3#5335'667#'7#5333#"''67&'á 117|C„ "!.FOJCS   H5LO #*   WÿéõÊ273#3#5335'6'333#"''67&'767#'7#ã Q +g7  21*5   "Ê4<vc}A !4ÿé Ê273#3#5335'6'333#"''67&'767#'7#“ M +f4  /1*4   Ê4<vc}=( !&4ÿèñÃ$73#3#"''3255#353#5335##535#âg^  JzJ^gà  xP5HH5P—ªQÿðóÄ%73#67&'7&''67#3533#3#535#\‘F!$ 3737::H¢F7Ä( # (u ''Aÿó­Å&7#53#67&'7''63533#7'675#f`- !$&&&*-8&´)  H# 'eÿðóÄ%73#67&'7&''67#3533#3#535#n;  *0,011=Ž=0Ä(# (u ''8ÿðò“$73#67'7&''67#3533#3#535#C¤Y&)9@3AAATºRA“  PTÿó´Å&7#53#67'7&''63533#7'675#pY)  !  %(2 ´& H$  'KÿðóÄ%73#67&'7&''67#3533#3#535#V–H#& 4;5;<Ž<3    Cÿîð’%73#67&'7&''67#3533#3#535#Ó}15 FM=UUUhàdU’  R ÿônÄ$7#53#67'7''63533#7'75#/['   $"$(2("³%  E%  ) ÿîvÄ&73#67&'7''67#'75#53533#6 i0 !& "b+4)%%''Ä* #  ( ! fðÑ 73#5##53&''67&'®8f; 6 Ñ "#$    ÿì’%73#67'7&''67#3533#7'75#‚? ,2,///47C7/’   QFÿè÷Ï-1773533#3#3##'32767#&''67#735#535#35655#T=KK@@M  9< ;/: 7455=Q,A$¾8 ' -)* (:$7 ÿèôÏ-1673533#3#3#"''3267#&''67#735#535#3565#geeRRa   GQ OTTPPWWg{>T>½: ' (+3  /:%; cÿè÷Ï,0673533#3#3##'32767#&''67#735#535#35655#n2==55@ .0 /&/ /,++2F!6½6 &/'' )8$9 ÿçòŸ,27733#3#3#"''327#&''67#735#535#5335#367#u__LOd  D"@J%M >E LUU]]:9S9:Ÿ1   +,  /A$=ÿèôž-1673533#3#3#"''3267#&''67#735#535#3565#N@HH==Q  :@ A5 43 5::@S*@%’  1%!0 .CÿçøÏ5;7#67327#"&''67&'#3#"''3265#'6553&537&'ñ6     7/  I  ¨D,> )9$P%L39- -7P''   ÿèï97#67327#"''67&'#3#"''3267#'6553'33&'7êP     SC  /b ƒ% "*("" "2?&/+ $.=  ÿçöp87#67327#"''67&'#3#"''32667#'6553'33'7íO    XH  4h$a!  %0 " #. PÿçøÏ4:7#67327#"''67&'#3#"''3265#'6553'37&'ñ1      4+  F  ¨?*> )9#$P%M 59- .6P''   ÿéó‰87#673267#"''67&'#3#"''3267#'6553'33'7ìO     UE  2ft" %7  ,6)! !+3  ^ÿçøÏ6<7#673265#"''67&'#3#"''3265#'6553537&'ò-     0)  A ¨8!(> 9"%O%N0;+ .6P'''  wÿéòÌ'73#3#67'7&''67#535#535'2à &&29 &*+4&&3Ì"$1  & /$nÿéòÐ'73#3#67&'7''67#535#535'2ß **7@  *009))7Ð#%0  & /% '6óË73#67&'#67'735#35#'©E  [:# ËX  +#  e28÷È7#67&'#7'535#35æ+   +' AAAÈK    &-  ƒ(ÿéôŽ7#67&'#67'53535ÓP  $[0!* ƒƒƒŽX   57  œ"Ÿÿê÷È!7#67&'#67'5353535ë  %   '''Èq $IU  Ì /MÿíœÐ $*07&''63#3#67'675#535#&'7'6w   . %;Ð     E I3Zÿéô—73#67&'#67'735#35#Z=  E% YYYY—]  ;9 }9wÿêõÉ 7#67&&'#67'53535â0  ! CCCÉr  0(V  Ö0+&ð’73#67&'#7'735#35#+¡C  R#2.! yyyy’C     G % “ õÈ 7#67'53535&'76ä?  ---    È_F ®&-  +ÿèôÇ7#67&'#67'53535ËH + d/'$0 xxxÇx   GL  Ð!!2 ~ÿêõÉ 7#67&&'#67'53535ä-    >>>Ér  0(U  Ö0aéÅ 7#67'53535&'76Þi VVV"%1Å^B  «%-  #" ÿé…Ï'15N733#3#53533'67''6767&'3353#57#7''67''6767&F'',w    KJ\\JJ   Ï))7     -,Ž Q>       ÿé|Ï'15M733#3#53533'7''6767&'3353#57#7''7''6767&A##(o      GEWWEE     Ï))7     -,Ž P>      }ÿìùÌ&733#7'6753773673267#"&5¡% %    ÌFe ¢žÃK b$* „ÿèôÇ73#67&'#67'735#35#„^*  2 9999Çp  !NT  ¤N ÿéyÎ&733533#3#53'67&73#5537$$%n)  R@¼9KS $% 7t o[  ÿñìÉ!*.7367&'#"''325567#3'67#3#-ž$6  €T; 7@ØØÉ #+Nk q(?%"2w(ÿìóI73#3267#"&5535#0ž’jj2  "Ð 3SCb P=S3  ",ïÑ73#3#353#5335#535#'6B’Tee9«8cc5 Ñ 6*<<*6  ÿõyÐ 73#3#753#55375#535#'6$E'++J-- Ð.S=U ZEV. sÿéôÏ735333#&''67#535#337#13%" #&),#7¦))I1 10!%-6) 6pÿèõÏ73533#&'3##5#535'67#y/2) %£,,K#$FM00MC',FuÿèñÏ73533#&'3##5#535'67#y/2%  !£,,K#(Vb00cT.,HiÿéóÒ-2=CGKQ73&'73673#3#3##"''3255#535#5365#5#35#"&55#733567#35#&'i#   ,$  VVR'.K %U D U UU  ²  ]    ]  5    ? =    ÿçõf 73#3#353#5#5335#535#'6EFjj@¦?jj5 f. #  …NóË#(733#"&55#'665#53&''67&67#Ú   `  - Ë   H     ÿéîV73#3#353#5#5335#535#'66“Jcc:œ:cc;V  "   ÿéðX 73#3#353#5#5335#535#'6?”Nff<¢>ff?X  !  LëË #'+/37;73#735#33535#3353#735#33535#33573#735#33535#335-¥¥77J777J7¬dd)@)"cc(?(Ë=% <%%<% ÿ÷gÐ 73#55375#535#'673#3#7P; $$ 1$$ PY WEU0 $ 0Q^ÿçðÑ.OU733#3'67#73267#"&55'75#'6655335'673673##"''3255#7&'œ<<@-    /,!2+ 0  ,Ñ     G9/ '!Qƒ   )   ÿóbÐ73#3#753#55375#535#'6!4!"" 5 ""Ð0UA\ YFX0 ÿéðK 73#3#353#5#5335#535#'6<•UffA§>ff5K$ CëÑ &*.2O73&'73#3&'73#3#3#3#5'65#5#5#'6767767'67'67'67[dÖm '/****1s .!!!!!P   E& Ä 8    : (    ÿòx™ 73#55375#535#'673#3#7`I**  ?$))9F E1< 9 ÿéçÏ)173#"''32765#'6&''67&'763353#9¢  — a`s›Ï —7 t   RTgWÿéîÏ)173#"''32765#'6&''67&'763353#wi \ 9    ?DkÏ —6 €  XZlKÿéíÏ*273#"''32765#'6&''67&'763353#oq  e =    DKqÏ “ ~  XZlaÿéïÏ*273#"''32765#'6&''67&'763353#~c U 7   :>dÏ “"{  XZlLÿéóÐ373&''67&''667#35#53533#3##5#'L! ( ) % # ?9LL--33KÐ   j ##((eÿéóÐ473&''67&''667#35#53533#3##5#'C  !#     4 .??%%**>Ð   j ##((pÿéóÐ473&''67&''667#35#53533#3##5#'•>       / *::!!%%:Ð   j ##((HðÎ473&''67&''667#35#53533#3##5#'}M" +"( "  ? *BB88AA;Î     a`ÿéóÐ473&''67&''667#35#53533#3##5#'ŒE "$ !   6 0BB'',,AÐ   j ##((kÿìôÅ'+/73#"''3265#'67##32667#"&5535335ux   .4 +$u` # 5Å/# 8L(  {****bÿíöÐ$(,733#32667#"&55'6367#3535#Š<$`  0% / . Ð T1  }$3333ÿé‡ÏCGKO`7335#535#535#53533#3#3#35335#535#535#53533#3#3#353#3#3#735#7677'7&'  uyykkGG!3>ËT XX   Ue  * !    ÿëp¦!'-767&'7&''67'677&''4'''6X! $      ‘2#P^ÿîó¡!%)733#3267#"&55'6367#35#335Œ2'] ( 4# +'.¡  C  ` =!!!kÿíöÐ#'+733#32667#"&55'6367#3535#’7"Z  -# * )ÐT1  { $3333 ÿéxÐ(,073533#3#3#535#535##"''3255##535#35#,**$$+l.$$,b   55555à   Jh  *~"2uÿéôÑ *7#5##53&'73#3#33#"&''675#ïL.4S$$  "   !»**  3.1,> jEÿëðÑ/733#3'67#'665533265#"&55'753’CCJ g /;(  ! Ñ B6+ 'K!   EÿçðÑ/J733#3'67#'665533265#"&55'75333#"''32767#'67#536’CCJ g /;(  !  9  '/ & "Ñ B6+ 'K!   25 :+0ÿèîÑ/73267#"&55'75#'66553533#3'67#À0 '   7AAV D‚   C8+ )N1 Xÿìô@ !7&'&''33267#"&5''6²  7  @"  @  0  4%  ÿéìÑ5733#3'7#'66553367327667##"&55'67djjt  £ G+$$9 0  Ñ =3' $G6   ÿèðÑ/732667#"&55'75#'6553533#3'67#½>-),4F__p W‚    C9* ,4M1 >ÿèãF73673#"''3267#'67#H6Q=9/ 14  0 0  ÿèðÑ/732767#"&55'75#'6553533#3'67#½>".),4F__p V‚  C9* ,4M1 <ÿññH7353#3#3#5#<•““„˜%#"ÿèðÑ/732667#"&55'75#'6553533#3'67#½> -),4F__p W|   C9* ,4M1 5ÿíò;"7&'33267#"&57&'''6—     ( k  u ;-  2   0ÿéïª-E7#'6553533#3'7#73267#"&55'7#'66533267#"&5€&9NNS?02  & /(  M   s2:99%      F%   ÿèëÍ 7'66556ß Mf lÍ EE; 1!W4ÿìôP$*73673267#"&''677&'''67&'f'"#2 & 1   0  “  <'* ,  B    2ÿèég!%)-73#3#"''3265#'675#5367#735#33535#335G˜DR F= /9GA11C1t11C1g> " & ! /ÿèïe73#735#3#3#&''67#5367#GllšDXN19 ? 6EPDe( % 8ÿæóM73'73#&''67&'#67#@KL )3$!1 (T J<      ÿçñÏ1733#3'67#732767#"'&55575#'66553mTTq \GI( 7&..9 MÏ      H;. +Q ÿêíÐ4733#3'67#673267#"'"&55'675#'66553rfffQ$#( 2 > QÐ     I=/ +!SkÿéôÐ(76767'67'67#53&'73#&''6C ' #64F ;* OnJ "$_ðÑ(1073#'67'67#53'67#7&''6ƒfJ A;*Hb+ƒ8VÑ     .  mÿëôÐ(76767'67'67#53&'73#&''6C ' #64F ;& KnJ "$=ÿéÀ{(73#6767'67'67#53&'&''6~5F  > " $6=  , O{   1  7   ÿçñ¤*73#6767'67'67#53&'&''66c(( B: F '&Fjf2N HR¤   # I+ÿæ»l(73#6767'67'67#53&'&''6k:Q  )I / .GD &0 Yl  -   9   Jÿéðž(73#6767'67'67#53&'&''6œDY !J ,*?J5 až  :DNÿéôÐ(76767'67'67#53&'73#&''6t" %S 2 #-E  @WN%4 cm I "$ ÿé’Ð(76767'67'67#53&'73#&''6,D )%84G :  1 On J    ! ÿé„Ð(76767'67'67#53&'73#&''6*= $ 1.? 0   * FnJ !  !?ÿúã'7677'67'67#53&'73#&''6e D ) ):6E :- N^5      nÿéòÏ )73&'73#&'''667&''67&'u4 /xW*   # # ®      ÿé…Ï )73&'73#'67&'67&''67&' 1 *r H  0  & "«   -    ÿèñ‹ )73&'73#'67&'&''67&'76e^ÙB) $x"-. >'-? 8' t         ÿèó“ *73#53&''67&'&''67&'76|cÕ[& !v"-0 ='!!' 8%“ %     JÿèõÍ )73#53&''67&'&''67&'76Ÿ @˜B !S !# '0 .Í 4    \ÿèõÍ )73#53&''67&'&''67&'76ª9†7  M   "+ * Í 6    Uÿð£Ã '73&'73#'67&'''67&'76YH  ,      ª   +  ÿè^Ï"(.7676767&'7''67'67&'#'674'#     ,&‡9!   .+n" ÿétÍ )73&'73#&'''667&''67&' &)dK  #     «   !   QÿéôÏ'7'67#53&'73#67&&'#67'u (.@ 99   H #0# ( HB`  gÿé÷Ï'7'67#53&'73#67&&'#67'„!*9  30    F #.# #%JAb  fãÐ73533#"''3267#'67#73#735#$5   !%#~QQ++¹: "0 $J( ÿéò`%7'67#53&'73#67&'67'F:Sh ^b  %L&       A    ÿéò`$7'67#53'73#67&'67'D6L\ha  " X "      9 #  KóÇ!(.473#3##"''327#67#5367#3&'365##3'#3&'×  š;(L1*J#F(V*RÇ     šZãÏ 73#"''325'3#Ð  6Ï^  HHbîÑ173533'73#67&'#"''3255'67&'775#a)a   %(  (1 a¼       ÿéôa&73&'73#67&'67'5'67#h_`   K$ "  4LP    4 )  )  RfÉ7#"''3255#'65535#35#f   ( &&&&É^    0 .iQóÐ -73#"&55'633265##3#"''#326†V,. AK*%%- 3  Ð =  0! #  LvÑ &,73#"''3255#'67#5353635#&'&'B&   0 // Ñ[   1 >  )  ƒEòÉ(733#"&55#'6553&''67&'767#Ù "_    IÉ   9     ƒKòÉ)733#"&55#'6553&''67&'767#Ù "_   HÉ   9      KŽÉ "(.7'6553'#33#3##"''3255#&'''6, jFF9JJ[$ %K  2 £% $3&     MôÏ73533##"''32655#&'@ @  ¶@ ;  ÿéòW%7'67#53&'73#67&'67'F:Sg_b  %L&      A   ÿèï› 73673#&''67&'7#367D  ƒ1&#/)G ?$ %:R p%   ' !B„Ñ$7&'73#353#5#'67#5335#5363?0&"2DÑ    7!0 'wGçÅ7#"''3255#'65535#35#ç   53333Åf  -,"0ÿ鉘'7&'73#353#5#'67#533655#536.  I2 - ! "6K ˜  (C , 8' |ÿèéŽ7'3255#'65535#35#é "1//0/Ž‹   (S.J…ôÏ#7&'3673#353#5#'67#5335#¢  B  /$ #-Ï !  >-H%@->^ÿéòÒ7#'6553&'7òq<¾O@3 3=[ Q ðÏ#7&'73#353#5#'67#5335#536~  X,F%& 4 + 7%E\Ï   >3S %F3>Pð”"73&'73673#353#'67#5335#P$ $)G'= 0 $ 8*F~   ($ *%R—êÇ 73#735#'3#735#¥EE""dDD""Ç00Rÿé©Ç 73#3##"''3255'67#'735#aF    'Ç?e  K+!+60? ÿêŽÇ!73#3##"''3255'67#'735#t   *+2 1GÇ<c  T4!-4<'òÒ#73#3#3#"''3267#5363535bWŒ¸¸² ±1%zzzÒB  0 s  ÿéÚJ73533#&'#5'67#UH= 1 2 -2HA !&<;%kÿèêÄ73#735##53#=#rqqKKYÄY5·mm66 ÿè¡Ð/73673#&'3#5'67#3#3'7>7#7#8;/" >F  #.ƒOB F ³    H& ÿëõÐ173673#&'#5'67#3&'#3#3#"''3267#7#Ol?/ s .@:c%Eȃw z 0¸      33 'Bÿê•Ï,73673#&''67#3#3#3#"''3267#7#L   ))G&$  ( ¸    +2 #†ÿçõÏ!7&'7&'7&''5'6556Ú    %Ï‚BBŒ©$ ´-^< 6\9LÿèõÏ173673#&'#5'67#33&'3#3#"''3267#7#R9L.$ Z  -E M UŸi_ ` "¶    I1 'oÿéöÑ473673#&'#5'677#&'#3#3##'32667#7#t,=# J "^  wLL Pµ   $%' %ÿèó£37373#&'#5'67#3&'#3#33#"''3267#'7#SmB ) p &?CW " HÆ‚l n 1     ,' yÿéôÐ073673#&'#5'67#33&'3#3#"''3267#7#~%5  > 2 5 ;kB?  B µ    J1 %SÿèòÑ+7''6767&'3533#3##5#535#'6È 9A"'* G 66BBGG*  (# : 77 ^ÿèòÐ,7&''6767&'3533#3##5#535#'6Ì5;%( B22==AA% '$ : 77 f óÏ*767'7''63533#3##5#535#'6 % /777::;;"Ï  % (( GÿéñÐ+767&'7&''63533#3##5#535#'6‡(+ ;B! &??GGOO- Ð%  ' 77 rÿèôÏ,7&''6767&'3533#3##5#535#'6Î.3  :**4499 ¼ +( 7 66  ÿèwÐ+7&''6767&'3533#3##5#535#'6^ #' 2!!$$,, Â! &# 877 }ÿèôÏ,7&''6767&'3533#3##5#535#'6Ò ). 7&&//22 ¼ +) 8 66  ÿè~Ï#/FK73533#&'#5'675#&''6'&''63&''67''667#-**  -Q   <  +        ¼+  -   2      €ÿéóÅ#73#3#5##537#35#35#35#35#35#€s13H%. < Ŭ¬©zzzLLcÿéó  &,73&''67&''667#'6'6'6‹I"$( , < . 03 (@ B7 -U X       &      ÿêòÏ-73533#3#32667#"&55#'67#535#'6C-RRbI &'F =Ae6 Ì**+K ' RVF+% ÿè‚Ï%7#535#'673533#3#67'5#'60"4&&%  '%W) &&)))F TF)%uÿêõÏ-7#535#'673533#3#3267#"&55#'6–3)))"  W) #))))P $ S-.^ÿéóÏ-73533#3#3267#"&55#'667#535#'6~,,7(   -)B È***O# RK!($* "ÿêí›+73533#3#3267#"&55#'67#535#'6H%CCZ? $ #N GFb. •3 :B 1 eòÏ.73533#3#3267#"&55#'667#535#'6†++5)   : É !!!0  4!#  ! ÿêñ,73533#3#3267#"&55#'67#535#'6?.NNaI  $ % B<Df8 0 67( fÿéóÏ.73533#3#3267#"&55#'667#535#'6…((2#  *#=È**+P % SL!(%+ KîÏ,73533#3#32765#"&55#'67#535#'6m33C5   1 ,/H$ Ê !!!; AE 3! lÿèòÏ-73533#3#3267#"&55#'6767#535#'6Œ))0"  ) 4 à )))Q  UG() JÿçöÏ%+733#33267#"&55#'67#53'&'7'6—E2   ,)F1   Ï]X  \+5EN !  ÿêøÎ"(.733#327267##"&55#'667#537'6'&'2$0 I(  #&  6  ÎbU \5* &/Q'  "$ ÿéñ{"(733#3267#"&55#'67#537'6'&'uhM# / (M DEfM z  {7, 2A1*      BÿéñÏ &,733#3267#"&55#'667#53'&'7'6’F2  *E0   Ï]X  [.4+)M ! zÿèöÏ &,733#33267#"&55#'655#537'6'&'­3$  & 34 P  Ï\^ " b8%-K#  ÿí|Ò %73#53635#35#'75#535#53#3#9-` 8888Qk-$$*h*$$Ò YY)7w ÿèlÏ$73533#67'5#'665#7&'7'6 $&      I  uZZN `9/+4X "`ÿéòÊ #BFJN73#5'675#73#5'675#&'7&'3&'73#3#3#3##5'65#5#35#m;(E<)2V520++++2j 6%%%%%ÊW  W      4  U  ÿéô•$*733#3267#"&55#'67#537'6'&'vcK  #%&& ?>`P‚•B: A&( 5;  ÿèvÐ#)-159733#"''3255##5#'655'6367#35#33535#3356)    ! #5#Ð – )%%+.O 5HbÿéöÌ%+733#3267#"&55#'655#53'&'7'6¢=*   3 *2$  i  Ì[[ " ^7#,K    MÿéñÐ &7'6556&&'67'5676×2<>=  '% Ð JC6 .BYR (%F>…  ¢ Iÿê÷Ì %7'655667'56&'76Þ5@B9 #) .- 0 Ì HA4 .?V   œ* 2*` =ÿéõÌ &7'655667&&'67'56Ý9E G=$  =Ì HD5 /AW  !A:|  ›YÿéòÐ &7'6556&&'67'5676Û2;><    *% Ð JD5 /AYR )%CA…  ¢ >ÿèó• &7'655667&'#67'56Ô6ACB  9  2• 8/& !,F   "OV  q ÿé„Ï %7'6556&'7'56776m&+./  + "  Ï JC6 .BWU 0&Y   “ Vÿç…Ì 7&'&''6i   Ì  3  2 0- +}ÿéõÐ $7'6556&'7'5676á&. 0.   ! ÐKF3 /BXQ *$7k…  ¢ fÿèóq '7'6655667&'67'56á *9  B&   .  'q ( &   8A Y ÿè_i7#"''3255#'6555#35_   " 2!!ij   #5! ÿözÏ &73#53&'&''67'67676767'B)h(9 #* $Ï ” ')%>'aÿéõÎ $73#'667&&'67'56ˆ\f \  #5Î   82g „rZïÐ $73#'6&'67'5676‹U_X  +  -( ( Ð  '  +  6   \pÐ73#3#"''3265#'667#53&'E"85  $ *Ð  -   (ÿé×[7#"''3255##535#35#×  ‰‰‰‰‰[Z  $r ,ÿï‡È7#3#53#3#3#535#535#‡^Xl MS!ȳÙ,&%%&dÿïóÈ7#3#53#3#3#535#535#ó{{ `%""+j+##'ȳÙ,%%%%bÿïóÈ7#3#53#3#3#535#535#ó}}‘ b&##,k+##(ȳÙ,%%%%PÿïòÈ7#3#53#3#3#535#535#òŽŽ¢$l+((2x2))-ȳÙ,%%%%iÿïóÈ7#3#53#3#3#535#535#óvvŠ [#!!)e(!!$ȳÙ,%%%%$ÿïî‹7#3#53#3#3#535#535#é±¶Ê$–B::H¡G::B‹xœ ÿì‰Ï7#67&'7&''53&'73535€P% )+<<<²jD  "&  ¸ + 7|Ð7#7'7''53&'73535|I( " $'555ºN    z |?æÃ7#"''3255#'65535#35#æ   6 3343Ãk   E$7ÿìÏ7#67&'7&''53&'73535}P $ )+<<<²jC   ¸ +ÿìzÏ7#67&'7&''53&'73535vM # ()999²jC   ¸ +9ÿê–Ï,7#535#535#53533#3#3#3#"''3267#'6V" ,+   [ @ ). $Bÿê Ï,73533#3#3#3#"''3267#'67#535#535#X))    » ? )0 #C ÿöŒÐ$73#3#3#67'753675#535#'62I,00))7D 99  Ð&$ MIZ&M|Ï73533#3#3##5#535#535#)))$$**..$$)¼QåÅ73#"''3267#'667#…`   " ÅR:.& "&E|Ï73533#3#3##5#535#535#)))$$**..$$)º{>åÄ73#"''3267#'667#…`  "Ä_!G5- )/d|Ï73533#3#3##5#535#535#)))$$**..$$)     gåÈ73#"''3267#'667#…`   " ÈD+$ \|Ï73533#3#3##5#535#535#)))$$**..$$)ÁcåÇ73#"''3267#'667#…`   " ÇH/%!  ÿèòa$7367&'#"''3265'3'67#w  ;#  cS< 1=a   (3 F8 (Eh™Ï73533#3#3##5#535#535#G!!!!!!à     ˜eêÃ73#"''3255#'655# J  ÃC /+ $Bÿéîo*067676767&'7&'#"''3255'67'67'67&'z(,*"    )   _ O #      E    n}Ð73533#3#7#5'675#535#((($$(*$$(Å     rèÑ173533#3#3##5#535#535#73#"''3267#'665#)))##**,,##)t^   !Å      < …níÇ73#"''3267#'667#‡f   " Ç@ ' xìÏ/735#535#53533#3#3##5#73#"''3267#'67#/'',,,,'',,/yb  ! ’      E/ )  ÿçó(2N733#3#&''67#&''67#535333353#5#7'67#&''67#53#&xOOiG 9 C/*[ ²†  :  –        GG]      !ÿè߯73#3#5##5367#735#35#+ªSd–EB‚‚ ––ÆW kl 1¨6"ÿéÜ”73#3#5##5367#735#35#0žJ`’D?vv’’”AS S z" ÿéðÑ73#'6553&'…`¾ZÑPD3 5<] ÿøkÅ7'67#53&3#7'75#@ ,2F 9KS#}1  05_ÿèöÐ!2673#67&'7''67#53&''66733267#"&5'3#«9G  ,1'6" F  "Ð !  ! tL&$L OhlÿëóÏ +73533#735#33535#3353#3#"''3267#7#t01t1O1i‡XQ  T ¹V342, 'vÿëóÏ +73533#735#33535#3353#3#"''3267#7#|..n-G-`}PK  N ¹V342, 'fÿéñÈ2JPV73#27#5'275#35#35#75#73''67'767#67&'#5'6'6'6jG 17  %    8*   ÈB K+, F      N     !;Y    ÿëhÇ $(,28>7#3#3#3#"''3267&'77#55#5#35&'''67&'b  C%   ÇG "‚&%5! dÿèòÏ &*.26:NRX^73#5'675#537367325267##"&53#735#33535#3353533533#3#535#35#'67&'“''!  @rr0N0d !‡"- > Ï>     A' # ( !   kÿèòÏ &*.26:NRX^73#5'675#537367325267##"&53#735#33535#3353533533#3#535#35#'67&'•""  :mm-I-^ +9 Ï>    A' # ( !   eÿéòÑ QUY73#53&'3#735#73#735#3533533#3#3#67&'67'5'67#535#535#5#35#´6‚7.77166W "7   ,    (!P Ñ#++*     ,      IÿêõÇ!%)73#3#&'#5'67#535#735#33535#335`‚6F7'#* (;K9&&9#\&&9#Çd" 'KL.';@UÿêõÇ!%)73#3#&'#5'67#535#735#33535#335jz2@2$% &6D5""5T""5Çd"%HI+';@ ÿéôÒ#'+/5;73673#3#&'#5'67#535#735#33535#335'&''&'%~  PgT$4 :&#8 1%SeP==Q=Ž==Q=Q  ,  ¤ [  %=;" 67c     ÿéóg!%)73#3#&'#5'67#535#735#33535#3352D`L#1 9&%9 2%PdF33F1w33F1g:  %%  $  ÿèòÆ!%)73#3#&'#5'67#535#735#33535#335&³PhW"6 :#$64#UfO;;O<‹;;O<Æh"+IH*"<B ÿéxÉ &*.73#5##53535#335##"''3255##535#35#iK/+8  !!!!!É?##??/2f  '~#6 ÿé€É &*.73#5##53535#335##"''3255##535#35#pR21 =  %%%%%É?##??/2f  '~#6ÿépÉ &*.73#5##53535#335##"''3255##535#35#d @ * $ / É?##??/2i '~#6mÿëóÍ'733#7'7536773673267#"&5”' '  ÍGg ¤ ÅS ^! €ÿêóÎ#)73#3265#"&'#67'56'&'á #"  $  3(Î9346Gc  ÃD50r   ÿétÉ &*.73#5##53535#335##"''3255##535#35#g G,( 4  É?##??/2i '~#6pÿêøÐ %,487#5##53&'7'673&''67&7267##5##535#íO0, 5  " ) =555¸#%  O     5M M/ÿë÷Ð+73533#3#535#&'6733"&&#"'6632#TUUcÛdT'=)"<A-C@"  °   ;7,%    kÿèöÐ"1573#67&'7&''67#53&''66733267#"&5'3#±4@ (, "/ B  !Ð "  ! tK%%L Ohgÿêõ¡"&77#53&'73#67'7&''63'6673#733267#"&5—-0B   %-     z    &'!  OO9 p’íÐ 73#'6R\ Ð  QÿèôÐ $(>73&'73#67&'7''63'67673#7333252767##"'"5g7:ˆ% !%06    &'  ¶     ,8   0^^L ! ÿüiÀ767'7537567#533K (1  :QZ8 e`r* ÿò`¿73#3##5'67#35#P +$ '¿ hc +6‘CdÿæöÆ#'+073#3#&''67&'767#535#35#33535#335s~712/ -$  /04/L/Æa    a>? :ÿç÷¡)0473'73#67&'7&''67#33267#"&5''6673#R>  Ia '* 8@&h  T1+Š     9;  @= N ÿói’77'753757#533J(1 9P=+ OLZ!\ÿèôž$(;73&'73#67&'7''67#3'67673#73333665##"&5m49D   .4(  $& Š    <,   %KK6  X_èÆ #73#5'675#73#5'675#&'7&'^?-K?-:  X  Æg  )g  )      fãÊ #73#5'675#73#5'675#&'7&']%+ Io]%+ IcuÊc  "c  "    [ÿêïÆ#)/573#"''3255#'3#"''3255#&''&''6''6°? +I?  +U @  f = ÆÅ ¯Æ °   , )% $' )& $SÿêïÆ#)/573#"''3255#'3#"''3255#&''&''6''6­B .MB  .Y  Dk A ÆÅ ¯Æ °   , )% $' )& $Yÿëîq)-173#"''3255'675#'3#"''3255'675#'''ªD  2KD  2W @ qq " "q " "c‚éÈ 73#735#335335c††'ÈF$$$$$ ÿé\Ñ73&'73&'#5'67#   &7° !  e` ? ÿê‡Å)/575#53#"''3255'6'5#53#"''3255'67&''&'u&8  .%7    6  ,  ^VÄ 8 ^Æ 4 i   ÿçóU736533533##5#'67# C<??>9.A= @@;(’ÿéîÏ733#5##5##5335#35#·$%%Ï.uQQvUCCC ÿê€Å*0675#53#"''3255'6'5#53#"''3255'67&''&'n"4  -#5  8  .  Y[Ä 4 bvP / j   ÿêwÏ 73#'67#536&''6767&'6,/  ", Ï'G3 1=!& )EN oÿêëÆ)/575#53#"''3255'6'5#53#"''3255'67&''&'Ø"5   -&8  9  -  ^VÅ 9 TÅ ; ^   rãË #73#5'675#5#53#5'6'&'7&']%+ I¸Oc'-ŒoËT  S  !ÿéìr)733267#"&55#'67#5363533##5#fA   1< 1$(B^ãÉ #73#5'675#73#5'675#&'7&']%+ Io]%+ IcuÉj&j&     ÿésÆ)/573#"''3255'675#'3#"''3255'675#&''&'E.    5.    ? '  ÆÅ  2 XÅ  2 X  VÿçõÏ  7&''6&'67#53&'ž"' #$#. # \t%+Ï.10&   X ÿæö’ !7&''6&'67#53&'y5< 8:'7F*  "*“²,22’$  =  ÿëy— !7&''6&'67#53&'= #   BY —  Aoÿèó’ $73#3#537#35#35#35#'67&'s}40g#6CCCCCC  ;’ff, ) *     kÿéöž  7&''6&'67#53&'ª  )  G^ $ž     A fÁ73#3#7'75#535# Y&!+ Á:9   @:qÿêõÃ!'73#333267##"&547#67'7#&'qzO?  /9Ã$}  p} ¹P ÿéŽÏ !7'67&&'67#53&'N'   Nd %¬+(5!   V ÿívÏ !7'67&&'67#53&'A #   =R   ¦)&7    S  ‡ÿóóÐ 73&'73#3#536'&'Œ,  #bI %l4%  ¤ G5>?4581ÿí~Ï !7'67&&'67#53&'E &   BV  ¦&&7    S  uÿèöÏ !7&''6&'3&'767#®  %  _    IÏ ,1*&   &ŠÿçõÏ !7&''6&'67#53&'µ     G(p```Ð6& FiiF3ÿéëÐ7''6767&'#5##535#±$ WW 4#+wÿéöÃ$7#53#33267#"&55#67'7&'vJ4 $5 ±#… x| jÿéé”767&'7''6#5##535#U;@ OZ$›ŠŠŠ”#  3R R5"ÿéëš7&''6767&'#5##535#°%XX8 #9C 4…………  &' @R R5# ÿéô773533#&'#5'67#bcU&6 :&%8 1%N.  --  ÿéòE 7&'76'7'5'6Í %Q^ (  77  <  "   RêÏ7''6767&'3#735#Ã8>)+ PttLL¸  0-  HN)ÿèðÑ'+/5;73533533#3#"''3267##5#'67#735#3353355##655#=)::H  3, C 9 95=Q)&:)%µE"4 RRD 3HT" "Lÿçëž&*.2675#53533533#3#"''3267##5#'67#77353355##7#|%%**4  + #( 4)a7- AA6  &7>ÿèí“'+/3773533533#3#"''3267##5#'67#735#33533535#35#?'::F   1) C 8 =2?S'&›(#7''‚8,>>4 $57nÿéõÏ'+/3773533533#3#"''3267##5#'67#735#3353355##5#q$""+  % % $6"´D"1SS2!'F T"""" ÿé‡Ä73#"''3267#'67##5##535#t   ( , %$oCCCÄK2:,_iiF3 ÿéßÃ73#"''3267#'67##5##535#Ä T S H G¿………ÃH/L<_iiG4i,éÆ73#"''3267#'67#3#735#rw   2' kkDDÆ7*CE#4aÁ 7#55#35aG4!!Á>----tÿéðÄ73#"''3267#'67##5##535#wy   *+ %'uJJJÄL2=/_iiG4 ÿéáƒ73#"''3267#'67##5##535#Ç ] L B Bă1/  :N N3!PâÅ73#"''3267#'67#3#735#\† 4 5 - * yyQQÅ< "4(IQ+uÿéõÐ '-39?73#'633#3##"''327#67#536365#7&'67#7&'’NV  \   N97 (9 Ð  #  R$  ?ÿêwÈ 7&'&''6_   È  2  8 )& %F-õÑ'1<73#33#3##"''327#67#5367'65#3&'77#3&'7jy‚y t qZ( ]( Ñ  2 &   PÿéõÐ ',27=73#'633#3##"''327#67#5;7#7&'67#7&'sir  t  hNM 5O' Ð  )  S)%  [ÿéõÐ &,27=73#'633#3##"''327#67#536365#7&'67#7&'€^gk  aFD 2G Ð  S)%  ^ÿéõÐ '-38>73#'633#3##"''327#67#536365#7&'67#7&'€]f k  aFD 2G Ð    S)%   ÿçôÐ %*.2773#'636733#3##"''327#67#765#'#37##37#H• &+-’%& †+­+,*=/?+)Ð  #R<)  )));)))ÿçóÐ %*05;73#'636733#3##"''327#67#765#7&'7#7&'H• &&(’$% †&¨j/  Jl5  Ð  #R<)  )&  V)&  ÿç‚Ð $)/4:73#'636733#3##"''327#7#747#7&'67#7&'(@I R   ET/  #0  Ð V)$;)%  V)&  ÿèô¡ "'-3973#'636733#3##"''327#7#77#7&'67#7&';¦³ ¬  Ÿ º†* cˆ+"  ¡   F/ . = fÿéõÐ '-38>73#'633#3##"''3267#67#536365#7&'7#7&'ˆV_ e Y@> .@ Ð  #   S)%  mÿéõÐ %+16<73#'633#3##"''327#67#536365#7&'7#7&'OX b V<: ,<  Ð  :#  S)%  ÿéòw %*/497'67336733#3##"''327#67#75##&'#7##&'#7 "”Í!Ÿ ! “±7-z;+P  &NÿèñÍ 187'2'6'&'&'373#&''67&'67#67#á XIG1  (1 \ "!7 / 'W5 Í    7 #   0 mÿèñÍ 187'2'6'&'&'3673#&''67&'67#67#â 0E94 $   "G ) # C $Í    7 #  .  ÿçó• 187'2'6'&''&'373#&''677'67#367Ð JkYP >  0  F‰5 $! 0*A #; :Q •       &    wÿèóÍ 187'2'6'&'&'3673#&''67&'67#67#ã -B65"   !B "  @  Í   : #   . eÿèñÍ 177'2'6'&'&'3673#&''67&'67#67#á 2J<9 (    )L , % "K(Í    7 #   . Hÿí©Ð#)/7676767&'7&''67'67&''67&'c    !  3  ,…;%  0+k! •ÿëïÏ73#"''32767#'6&'¶/  "   Ï“+ "l '5 >ÿò•Ð!'-77&'7''67'67676&'&'''6„       ¯7  0.a!7ÿòŠÐ!'-77&'7''67'67676&''67&'w   -&¯8  0,b!|ÿéóÏ"&*73&'73#3#3#3##5'65#5#35#ž   #O *Ï   Ž+- 3 3  ÿééÏ*06767&'7&'#"''3255'67'67676'67&'©4?>3  8   &(%#!(##I $ ‡µ8" $ C  =.'j"# aÿèõÃ73&''67&'#367&'k'!( *$  , ÃU= !5J>/2; hÿéõÏ(733327#"&547#''67&'77#53—/   ! $$Ï-5&$#&"$7 )!36 *iÿêì¾7#53#"''32765#'>''6£-v  #    « 3&vS+  '0.%ÿçëm+1710776767&'7&'#"''3255#'67'67&'''6`( 47L)  !  ++&% W! !R ,'W #  #   <    ÿèöÏ 7&''63#'3'65|38 46.; NGW9 0Ï273+)Œ<2&GÿèöÏ 7&''63#'3'667"+ *"!, <5J)Ï')+*/Šˆ%G '$]ÿèöÏ 7&''63#'3'67¦& $% 3/A$Ï ')+*/Šˆ%G?`ÿèöÏ 7&''63#'3'67¦& $# 1.>' Ï ((+*/Šˆ%G@kóÏ 7&''63#'3'65¨"  )*1$ Ï#$# !mk$,! ÿèôŠ 7&''63#'3'667€ 19 86+; QEV6Š!#a`1âÏ 73353353#EDÅÁ!//!4kÿèöÏ 7&''63'6673#®  (3Ï *.) &(B40<‹Mÿéò£ 7&''63#'3'66™"+ ''% 44D £ %#!'mm6* $fÿèöÏ 7&''63'6773#ª$ !$ .%;Ï '')*1%G@"ˆ ÿèõ› 7&''63'66773# 0; 67+= Q@ R› %,%#!8 jvÿèôÏ 7&''63'6773#®  ' !2Ï'(**/%HA"ŠrÿçòÏ 7&''63'6773#°  % )".Ï ))**,'JC$ŠƒÿèõÏ 7&''63'6773#²  !)Ï ''+*0%HA#ŠsÿñïÉ73#3#535#535'2à 440t/334ÉIOOGiÿèöÑ 7'67&'&'3'67#¬*"  (k  ! X­(%2 '. +ÿçë‚+177676767&'7&'#"''3255#'67'67&'''6K#3;O* !  +++$ I! !R .(a '  )  %  K   jêÐ73533#3#5##535#XXX`¬_X½-.!zdË73#'3#Q0ËQHHiÿêöÏ"73#&''67#5353533655#335ç9# "'- 5,,,§N22/ ".N((N ** <lÿîóÏ %7#5##53&'736732767#"&5î]: 6% ,  ( ¬2 2 .0 0 ( aÿéöÐ 7&''6'6'6'6©$ #$. , +,8 631, GÐ !"$  $ - !&vÿéëÏ73353353#5#353#5#5335#vb0»?SS?XdLj ^LdkÿééÏ73353353#5#353#5#5335#k ##$k 4»?SS?XdLj _LdgÿéóÁ73##"''3255###535#gŒ  gP3 Á«  ¦'XfF5jÿéõÏ$*73&''67&''667#2&'&'š:  # !   ." !<* (;Ï #      ]  wÿéïÃ7#"''3255##53#735#ï   R!55ÿ  §ÇÚ7b@_ÿêõÎ!7#'6553&'7&''6767&'ói:  %&  ¯:?: 0=L c'- 5S^" hÿòòÆ&73&''67&'767#3533#3#535#up"  Y.008†:.Æ'    t((gÿé¨Î7'6'67#Ž ! Î-^ $ ydÿèìÏ&753753753#5&'#5&''655'6u    ’>9f5mçc!–\ .C,(9  !mÿé÷Ì"(.73&'3267#"&5'3'67'677'6'&'·   !%  L  ]  ÌeI ÈvC* "1  S &  ÿéêa)/5767'7&'#"''3255'67'67676&'''6¦)93* :   %'  # !T $ a     ;   gÿéòÐ>767&''67&'3#3533#"''3255##5##5'67#536‚ )NT+    "(Ì      1 8  !TT@=mÿêôÐ7767#"''32654''67''67&''7&'767&''6Ñ    "2 6$ + +#$ %    0Ð   !< !     xÿèìÄ73#735##53#=#ggAAtNÄY5·mm66HÿèöÎ287'#'67'6776767&'3&''67&''6767#Ñ <;* (-5GR+ //'   E•     - 0       ÿô‡Å 7#3#3#535#35#5#‡%"$'n##DD!!Å.Q.Ñ@.m-m..›áÊ 73#735#'3#735#ˆYY55|YY55Ê//ÿéé& #53#=##53#=#˜d=nc< =< =<tÿôëÆ 7#3#3#535#35#5#ê(&(+w''LL%%Æ-R-Ò@-l+k--pÿðóÄ 73#53#3+535#3'35#Â1ƒ€-.- ++Ô)^^)°:</é’ 7#3#3#535#35#5#å[NN_ËHH••HH’ + c ' ' P€É 7#3#3#55#535#735#€  k9'!!É5yg5U!‹OñÉ7#3267#"&5535åF '3ÉC  e,ÿóÞ‘ 7#3#3#535#35#5#ÔIGFR²77~~88‘@ž/II+ÿîå“ 7#3#3#55#535#735#Ô:98Jº\76F"__“", ÿéöJ767&''66''6|  M!N 908 J   "$ % ÿïòO 73&'73#3673#53&'YWÇ( E  9â5 ?  ÿéóo,73'67#&''635#53533#3##5#'86 O A '  _ '66##++6oU <   %%))OòÏ.7&''673'6735#53533#3##5#'@  3*A  &11!!&&6¦    ) +5 $$ OÿêóÏ&-735#53533#3##5#'''673'67&767® !!'0%?   … !R++RCC 6P43J  ÿêóÐ,73533#3##5#'735#'3'67#&''6‚9 $$= .9K8 T F(   ©''NJJ;N9…3(l   0 ÿéó—+73'67#&''673533#3##5#'735#=7a Q )  "V0&&++3#0— l&N   (844/&8ÿéàÑ %73#5##5365#7&''67&'76erš:lš{Ñ ÇÇ±ŽŽ…   gÿéìÐ %73#5##5365#7&''67&'76™I]&D]I    Ð ÆÆ±ƒ   ^ÿêîÈ $7#5##535#3533#&'#5'67#îkkk "##     ÈÞ Þ¿­'?C &eÿêîÈ "107#5##535#3533#&'#5'67#îccc     ÈÞ Þ¿­'>>'QÿêëÈ "7#5##535#3533#&'#5'67#ëttt &''    "ÈÞ Þ¿­'@G&mÿêïÈ "7#5##535#3533#&'#5'67#ï\\\   ÈÞ Þ¿­'==(iÿêïÈ "7#5##535#3533#&'#5'67#ï```   ÈÞ Þ¿­'>>&?ÿ÷Àq73#735#3533#&'#5'67#?]]    qzY     B½™73#735#3533#&'#5'67#B{{WW    ™—u )+ ˆÿêðÈ "7#5##535#3533#&'#5'67#ðDDD  ÈÞÞ¿®(71 &?ÿéŒÈ 73#735#35#35#'67&'H??   .  È¢qNO 8  ‰ÿéïÆ$(,73#3#"''3265#'67#'67#'67#735#35#“T0A  "     ....Æ] N18"!+&  7:ŠÿêïÑ#+/373673#53&'35#&'735'6#5##535#35#§e) 99999Ñ VV ]777  E    ÊTF6 5Aa5 /@( @/3     !#- <xÿéóÐ2:>73673#3#3#3673#53&'735#535#535#53&'#5##535#›  0,,44 { 33--1_CCCÐ    B B(vÿéñÆ73#3##5#535#'6'&'|n-4433-X B  ÆdSSd%#!#yÿèôÅ1673#7#5'75#35#35#675#7#53&''67&67#zF    )7  ÅŠ.'š&&]&^"T - )1 &3 ÿéwÏ5Q733#3#53533335&''67&''676753#57#677''67''7=!!(j-?    QQ?    ?Ï**!+       Q%      ÿïï‘ 73#3#735#3673#53&'ÐЪª‚‚ ; 9Þ;‘C6   ÿëtÁ 73#3#735#7'676'&' ggYY44. +4 "ÁJ&@ rÿèôÐ$).73#3##5#53665#53635#335367#3355#¤8((G  !,G, Ð l 44 l 9J  M _ÿèõÐ48<@DH73673#33##&'#5##5'67#535#535#535#53'5#3533535335Ž  (,$    ##!"26Ð   %%6II08'SìÅ 73#3#735#3#536'&'Z‰‰ rrLLG *™X 9  ÅD 7uÿïóÆ 73#3#735#3#536'&'u|| hh@@; "}F 2  ÆS/GuÿéêÐ"(733#"''3255##5335#35#7'6'&'¦1  I,IIIIJ  M  Ð=’  ;ª0Ly  uÿòòÆ'73&''67&'767#3533#3#535#€g     P*++2{6*Æ.    u((tÿêôÏ,73'33#3265#"'&'#7&'3#67'675#wA** Ag  V9 % £,F/5)f<   X@ FiÿéóÏ"(7&'73#3##5#'67#5365#5365#”  O !' BÏ8\\@4*b8- oÿèïÍ#73533533#3#535#35#'67&'w}*Gž////BBBBZ *!mÿéóÇ$73#3#"''3255#353#5335##535#m†;7  % J %79Ç   ‹aN_^Ma¦·nÿéôÌ  $7'67&'&''6#5##535#”B" (J555Ì &$# /Y Z:(lÿéòÃ#(,733##3#5##5'67#537#537#3353535#{fEV:  "#/%',3::Ã+, X H ,€*oÿîòÄ !-73#&'67&'67&'63#3#535#xww  0  1  Xw27ƒ92Ä   S**lÿçöÏ-73#35#535#53#3267#"&55#'667#56ŸH2   Ï  rJ M/- $(jjÿèñÆ673#3#"''3255#67&'7&''#5#67'7''#535#j‡;8  '  &  7;Æ “  }T &- /ŽT&.  <¬ pÿéñÈ%)-373#3#"''3265#&''67#'67#735#35#33#~fAV %   @@@@CVÈU Y =   31afÿéöÏ!26:>7&'#5'63&'3#"''325'#"''3255##535#35#73#¤$ J +; 7  & 6Ï  "-s  mt  1‹%8-\^ÿéöÈ/38<73533533#3#67&'#67'5#'66553'#335#35#Š-    *   qMMN&  9; G-# 2#`9(V kÿïõÆ#'73#735#'67&3533#75##5##5#dd>>  O t ŠmÆb@&  …OO======gÿéöÏ#)73533#3#&'#5'67#535#&'7'6u/1184" ! "58/  [  ²>-&MJ$*>    ÿéõÏ#)73533#3#&'#5'67#535#'6'&']]]iW"7 ;%#;:!Xi]«|  ·@/9ca5 -@  ^ÿéöÏ#)73533#3#&'#5'67#535#&'7'6m244<7$ $ $9<2  b  ²>.'NJ%+>    ÿéò£#)73533#3#&'#5'67#535#&'7'6YZZeU#6 8%#96"SeY%  ‰ Ž/%.KM-#/    RòÏ*73533#3673#&'##5'67#53&'735##SVV 4L9A'5 0%H* S¾   &$   ÿêwÃ473#67&'#"''32655'67&''67&''67#^&     "  #à     ' &  nÿéóÄ73#3##5#'67#535#5#}p &"I ÄMjj@* &7MMMMiÿéöÄ /7#3#3##535#73#535#3&''67&'767#©-****4==**B      -ÄO0Û=+M)L.$    ZÿéôÐ>DIN733#67&'#"''32655'67''67&''67#5'6367#367#335“2$=     "+0"# $  $/+  5#Ð 9   -0     /  / \ÿèôÐ I7'2'6'&''&''3#&'#'67#5367'67'6776767&'â 3K>8  )  %  i F4 (2 $+4  #%%Ð     1   $     #iÿéòÄ",73#3#367&'#"''3265'3'67#{ggww3      3,  Ä!  %9 W 2%qÿéòà &073#3#535#367&'#"''3265'3'67#€c'/r/(*      3-  Ã!!9   &8 V 2& ÿéóà '073#3#535#367&'#"''325'3'67# ½R_ÕbWW  :# YL8 46Ã9  3B  [: + ÿçöÏ)7'675#535#53533#3#3#33#"&&%"! ,<70O8.9 M   +cÿéôÏ(73533#3#3#33#"&''675#535#z*//9922  (   99*®!!!"0)7a!?ÿëñÐ(7#535#53533#3#3#33#"&''67m0/)2&j""""!1 /0 Kÿéõ”(73533#3#3#33#"&''675#535#^;;;BA44& 2%   BA;€!   'E ÿèôÏ(73533#3#3#33#"&''675#535##RTTaaVV,#@B %__R±#/+; ` ÿéóÑ05;7373#3#3#3##"''3255#'67#5367#537#5#&'Sgicg†-&&   b &DLB±!;AU 6  œ  ÇE0+ #++EEE    GÿèðÇ!73#'65535#53+37'6'&'Â.~*.”-*  a  n ,( !'2GG: pÿèóÇ!73#'65535#53+37'6'&'Ï$bo $  No.( !(1FF9  ÿéìÏ573#3#3#"''3255##5##535#535#535'6556à '.IIPPO  ;2FGGBB'*pÏB  *TTDWI>615[C îÏ3735#53533#3#3#"''3255##5##535#'6556Ü 4F80011661   18KÏ 01 EE6F0) &+MP@òÊ"(73#&'#5'67#535'2'6'&'Û  @,"$( %.@?1  M  Ê*.* (   &ð(73673#&'#5'67#53&'735'2Á # .P7@$9 2!K, %K   !   ‹ìÔ 7#5##53&'7'67&'ê¯a' 7 4N-, ,.Å     ÿéóË"(73#&'#5'67#535'2'6'&'Õ (0cR#7 9&#89M_',]Iy  ËB1:ff6 -@!  ÿézÎ"(73#&'#5'67#535'6'6'&'m ++ +-' @ C  Î;`W 07  jÿçöÈ7'6553#&'7#3™ d %<tÿéôÏ73533#3#3##5#535#535#x322..6677//3©&&&&>>&&ÿéuÎ"(73#&'#5'67#535'6'6'&'i )) )*$ >  ?  Î;`V /7  ÿépÎ#)73#&'#5'67#535'6'6'&'c && '( ;  ;  Î;_U/7  LÿæîÐ#(,059733#"''3255##5#'655'667#35#33535#735‡B# )& ";8##5)_$#5)Ð – #--&B7 .K IÿéªÎ#)-16:733#"''3255##5#'655'667#35#33535#735o    -Η )$ *0;  .G_ÿéíÏ#)-16:733#"''3255##5#'655'667#35#33535#7356(   "  - ' 1"S1"Ï • #66# */4 .KÿèäÔ#)-17;733#"''3255##5#'655'6367#35#33535#735TW C  A? 2K H ;;OA‘<;OAÔ  “ ::"A5 =N ›ÿéêÏ 736753#5'› Ä–”æ? ÿéõÏ735333#&''67#535#337‰!), !$'!5¦))I3 44 $.6)6ÿè߇"(,06:733#"''3255##5#'655'6367#35#33535#735MV : ;=$NJ 99L;ˆ:9L;‡]  !!( #. „ÿçóÌ%+73#3265#"&'#67'56&5&'Ú  $ ; Ì3  /,RQ µE0u  sÿçøÌ#7'65567&5567&'7''¤3*    ¬*Y> 6Y8 #?B6t"ª  gÿçõÏ7&'7'7''5'6556×     /ÏCC‹ª%³,^= 6]8ÿétÏ%+73533#3#3#3##5#535#53&'#535#367#! (!$$%% *! !¶11K}ÿîóÉ73533#3#535#3533#3#535#‡(((.p.()))0v2)¨!!!!_""##|ÿéôÐ%)73&''67&''667##5##535#Ÿ=     0C555Ð !    b\\<+ÿèçv"(,059733#"''3255##5#'667'667#35#33535#735\J; =B .MB ;;N=Ž>=P=vS  '   (   ÿébÏ73#5#'67#535#533O B6#Ïæ[E:"P>ÿêò) 7&'''67&''&'Ô  —{  % )   3îÒ7&'73#3#3#535#535#536V  m 6\RRgÝcRRYwÒ  ÿéò_"73673#3#3##5#535#535#53'\ )2RKKffkkMMV5 _        ÿðñÑ7;@73673#3#3#33#537#537#'67#5367#537#53&'37#367#S / 6e]ayn³: $ , 7KR?CS7 O42 76Ñ   -'"2 ¡6 cðÑ73673#3#3#535#535#53&'T 7 2`UUgàfUU_9Ñ  nÿîõ½7'67#53&3#3#535#´ ANe  Rj-49)p 4 77 ÿë€Å %*.733##3#5##5'67#5367#537#3353535#] :C+ #$.!)++Å+, Z D  + }) ]ÿîõÓ !%)73#53&'3#735#3#735#3#735#35#3#®>•>.[[@@*ppJJJJ'——Ó 'R4 " 9@& $ % ÿè Ð!)73&'73#&''67&'767#3#'3'66?7 0 'b\;´       Nki/* "@ÿë÷Ê #'7&'''6'#5##5'63&'35#´! , 2%0 X 0 n!XXÊ # % `Z !W0 ÿéó "&7'67&''#5##5'63&'35#Y" g19 p O ‡&!pp $ P P$#L'cÿéïÌ #7'67&'&''6#5##535#ŒC# !*K999Ì &$ " /Y Z:(XÿéöË !%7'67&''#5##5'63&'35# D # D 5aDDË &%#! %'^_ &'W-xÿñóÐ %73#5##53&'&'''63#3#535#¶3R0'  d)4z2'Ð-0 : .22yÿéïÌ #7'67&'&''6#5##535#š;   #B...Ì' % # .YZ;) ÿénË  $7'67&'&''6#5##535#(3 :$$$Ë $#  /^^<*GöÍ !%7'67&'&''66#5##535#€I$( ($/ !$KKKKÍ " " 4U U6$PÿëôÊ'+7&'73#3267#"&55#'665#53635#  S     J CVVÊ QE  H(& !"QV-ÿéõÎ (,7&'''63#3267#"&55#'667#735#¤#' '$F ) %’)    !)' jjÎ"% 'Q:  A ) +ÿçóÏ#)73#3267#"&55#'67#53635#7&'«)( % $ C> &[Xrr  Ï^>  BC6^`8? MÿèõÑ&*7&'73#3267#"&55#'667#53635#… Y    ECOOÑ  TE L&-$ TX0 ÿéÎ "7&'''6#'67#53#67''35#b -+ $X 22Î pE:RR3 S.aÿéôÏ"(73673#3265#"&55#'67#735#7&'w9     1 +BB  ¡XB  II>4< dÿèôÏ $(7'67&'3#3267#"&55#'67#735#’ D@d   / *>>Ï !$7IC  IH=&mÿéóÐ!%+73#3267#"&55#'667#53635#5&'Á   2 *<<  ÐSF   K,' #%SY1A*ÿèÞÐ8767#"''32654''67&''67''67&'767&''6¿ "  4J P4,; :. (2   ") \Ð  9))     xÿéîÏ!'+73#"''3267#3#"''3267#536&'3#œ6   <_   ^  "TTÏ E-WI1{ (   qÿèò "(.4:73#3#3#"''3267#5365#35&'''67&'#&'TfŽ··² ±'czz r b      B  / v"  O    ôÒ73267#"&''7&'37&'7èZ,  6 c[-  »       sÿððÏ %73#5##53&'36732767#"&5¯ 0T6(!+,  $Ï00 Q/(  uÿððÏ %73#5##53&'36732767#"&5± /Q5' *+ "Ï00 Q/(  ÿë‚Ñ#'+17=73#3#3#"''3267&'767#5363535&'#'67&'21L]][   [ :::+$Ñ L  @   ‡  b~ÿðòÏ $7#5##53&'73673267#"&5îK/4! "(  ! ¬00 5++  ÿé{Ï!'-73#"''3267#3#"''3267#536&''6:-   4P   O &(Ï F-WH0{ &h  ÿëxÎ%73#327#"&'#67'56'&'g    6  Î8K) ?L ¬E53j   ÿé‚Ï+<Ma73533#&''6765#67&''67&'767''67'67''67''67&''67&'1++   1    S        =  ¸;*!'!P         4           ÿó}Ä 7#3#53#735#3#735#73#735#|WSf AA%% %% Ä«Ñ#55E&E& ÿéuÈ#7367326767#"&53533##5##)  % +((+È%   ;GG ÿêvÏ 7'655##53533#5#367'J.'&  [4 =Y"5**3 W  oÿéíÏ!'+73#"''3267#3#"''3267#536&'3#ž2   ;^   ^  +\\Ï E-VI1z )   kÿëqÏ"73#"''32765#3##5"'635#,:  . .$Ï–+ wW^ 'n3lÿòöÄ#73#3#3#535#535#735#33535#335ws011<Š;000/L/ÄtB P kÿñóž#73#3#3#535#535#735#33535#335ur022<ˆ9///0N0žd;@ ÿêq¥5767#"''3265'67&''67''67&'767&''6P    %   ) '¥  +       ÿé‰Ë  $7'67&'&''6#5##535#4<'F222Ë $"  0^^<*pÿéñÅ!735#53#3##"''32655'67#'->m  ##,¦.>>^ Q='"9qÿèôÏ 7373#'67#&''6767&'}@B  [ §(dD GTX#) .RJ) ÿéöÍ !%7'67&'&'3#5##5'635#X&)n" 1: 74,•rLrrÍ ! +0he(m2ÿæëM!73533#&''275#735#335&'#6*NN  Ng2/N<  E$* !&ÿèô™/573533&'73#67&'#"''32655'675#&'g"&` $ :# +6 =-g, |   +8  , ÿéfÎ 73&'73#3#3##5##535#"!X EEEEH!!!² U T6%yÿéóÃ!&+/733##3#5##5'67#5367#5347#3353535#‰\<K2  )!"%,22Ã++ X >     - €+yÿéïÈ"&*.2767#533#"''3255##5##53&'35#33535#335– Me "  22P2¬   //8¥ =JtÿéõÌ ,27'2'6'&'&'3533##"''3255#&'å 0E8.  &    O  O  Ì  =H  E ŸÿéñÃ73#3##'3255##5##535#ŸR"   Ã&j  U‘‘p&dÿç•Ð 73'65'3#ƒ!Ð…<( $4eˆ ÿèôœ%)73&'73673#3265#"&55#'67#735#+  8 1("J @*}}x  I+ 2@1#<ÿþɧ"(73#3267#"&55#'67#53635#7&'¡    +'= 8AA  §  B  $)BD ;   1ÿóÉx$(73#3267#"&55#'67#53&'73635#•   / +  5CCx 9  % 9   50ÿöÉx&*73'7'#3265#"&55#'67#5'635#b 9    5 0 <   (% %(015 0 Ï?6 QJ,#BN-ÿéõÒ ,7#5##53&'73#3#3267#"&55#'67#ç¥^W‘‘ÐB '&'& >=¹--  /@   G*- =MÿçõÑ -7#5##53&'73#3#3267#"&55#'667#ìb< 9WW'   $´1 2  ,B  F*+  #"aÿçõÏ .7#5##53&'73#3#3267#"&55#'667#íW73MM|'   ´1 2  ,D  H,)  "#uÿè÷Ð .73#5##53&'3#3#33267#"&55#'665#º,N1BBr   Ð 1 !2 BC  G+)  "$IÿëõÏ -7#5##53&'73#3#3267#"&55#'667#ên@Bee™/  )±*,  ,B  E((  _ÿëõÏ -7#5##53&'73#3#3267#"&55#'667#ì_8 ;ZZ…'    #±*, +B F((  fÿéôÇ *73#535#535##5##53&''67&'767#wijVTTUx` g % (!   PÇK  G"#'  `ÿéöÇ *73#535#535##5##53&''67&'767#qop\ZZ[g m$ ($   UÇK  G"#'   iÿèõÐ*.273673#3#"''3267##5'67#735#53&'35#35”  0:  % !& (- 8+Ð  ?8Q@$!BK-qÿéêÐ"(733#"''3255##5335#35#7'6'&'¥1  M/MMMMN  Q  Ð=’  ;ª0Ly  qÿòóÏ"(73533#&'#5'67#'33#7&'7'6%%%  o‚(  W  {TT ?E  #Y¿Ê pÿéôÑ)/57676767&'7'#"''3255'67'67&'''6 %,$   42  ? ’5! Q  K($i  eÿçôÇ(,048<73#3#3673#3#5##5'67#535#535#735#33533535#35#p}G%% 1 @B '9*$$%#GBBBBÇ5 [ I –0_ÿéõÏ'/373533#3#535#3#735#3673#53&'#5##535#c>==4~7> vvRR%$–"mTTT   *1$ (= =!qxäË 73#735#35#qssKKKKËS11[ÿêïk)/573#"''3255'675#73#"''3255'675#&'7&'h<    !)K<    !)?  [  kj  #j  #   ZÿéñÇ"&,28>7#3#3#3#"''32767#55#5#35&''&'''67&'í3----7  r<((((   2  (  ÇN  2‚&%/  [ÿéôÉ 6=73#735#33535#335'67&'3&''67&''667#k%%6%[%%6%S N (9 %,&"  %  0É[59(        MÿèòÌ5:@7'23673#3#3&''67&''67#5367#53&'67'&'â 5N@& -KTYP  !  ' $$ )  %  Ì    $)  m z fÿîôÈ #'+/373#3#735#33535#3353#3#735#33535#3353#j……vv 2 R 2 j‡‡yy""3"U""3"qŽŽÈ @' # $ A' % %bÿéøÊ -73#735#3#735#73#735#3533#&'#5'67#zbb::$<<5<<\9>2" " #2Ê4177;   73 \ÿèõÐ%)/39?EKQ73#3#3#3#535#535#535#535'235#&'735'6'67&''&''&'è"??9988?>6699>>2?.&& 3&  Q  u    Ð ?    ?  e    `   ÿî™Ç !/73#&'67&'67&'63#67'75#„„   3   4    ]{5;I:2Ç   L' *ÿï…Å !/73#&'67&'67&'63#67'75#uu    /    .   Po-0;2.Å  H%  )ÿêÏ1733#3#5##535#53#"''3255'7567#536E005X6//D  26 :Q Ï''}'    ÿîôÆ !-73#&'67&'67&'63#3#535#ÆÆ&UX Ê]jçiYÆ   K33 ÿïuÅ !/73#&'67&'67&'63#7'675#ee  )   (   D`&")2'Å  I%  *YÿîòÄ !-73#&'67&'67&'63#3#535#c‹‹   7   9   iŒ=C™C<Ä   S**dÿîòÄ !-73#&'67&'67&'63#3#535#n€€  4   6   b‚8=Ž>7Ä   S**ÿíºk !-73#&'67&'67&'63#3#535#’’  A  ?  u–AK©KBk      (GÿîòÄ !-73#&'67&'67&'63#3#535#Rœœ   =   @   wGM«KCÄ   S** ÿéjÏ73533#67#5'675#'6 Ä"22: SI  D (Jÿîó— !-73#&'67&'67&'63#3#535#V””   :  :   m@M©I<—     9  MìÏ"73&''67&''6655667#× /=k   C  TÏ   % $En  ÿézÑ%)-7&'3&'73#67&'7''5'63535C! =    &***Ñ   W/   2"uÿéîÏ!7#"''3255#&''67##5373î    !4¨§  "B­¿''_ÿéòÐ "73&'73#3267#"&55#'655i5 5e  +§s  e-%44^ÿîóÎ-7#"''3267#5327667##"&55'753753ç      *  ŸR##$ _Yb  # d93D>Xÿêõ¿73#3267#"&55#'655#f…"   )%"¿© ! ¬RE, )  ” ` J # ~ÿéòÏ 73533##5#~2//2NN††‚ÿèøÏ7&''67&'±  Ï$r2 ]M02\!pÿéðÀ73#3##"''3255#535#ym*44  88/ÀAV  QAhÿéôÏ735#535333#&''67#75#n1''/9(&&* -`]6))I3 22 $.6)ˆÿéõÉ%7#"''3267#3&''67&'767##5è  9U     ?É4 J5$     qßzÿèóÏ 733#7&'7&''675#5335#35#®(( *3(((Ï,b6 ! 9bP>>> ÿémÏ&73533#3#3#"''3255##5#535#535# ""!!+   "" ±9 $WWwÿñôÅ 73#735#35#3#53535#35#35#ŠXX2222M }    Åg=C=JJJ99999aÿðôÅ 73#735#35#3#53535#35#35#zcc====Y“!!Åg=C=JJJ88888ZÿðôÅ 73#735#35#3#53535#35#35#tggBBBB^š#"Åg=C=JJJ88888Xÿðô’ 73#735#35#3#53535#35#35#pllHHHHdœ$$’J-,,:::***** ÿ÷b˜767'67'67676'6P (   %({( ! T 8ÿìõ 73#735#35#3#53535#35#35#X€€ZZZZz½,,W461;;;***** ÿíÀp 73#735#35#3#53535#35#35#&~~ZZZZu³**pD) # &$$$HÿðôÅ 73#735#35#3#53535#35#35#fqqKKKKi¬'&Åg=C=JJJ88888IÿéëÈ!773#3#"''32765#'67#735#35#3267#"&5536cySl g SSSS= "(  *"ÈU X" 8 31K    , WÿéëÈ!773#3#"''32765#'67#735#35#3267#"&5536noKb   [  IIII5 $ % ÈU Y! 8 31K    , `ÿéíÇ %7#5##535#35#535#53#3#3#7&'íggg " Q "UD  ÇÞÞ½«„++4  EÿïîÉ"&*735#53#3#3#3#535#535#'6735#35#`*9‚5==77CŸH661 \\\\g \\ Y8`ÿïóÉ"&*735#53#3#3#3#535#535#'6735#35#w!0p,22--8ˆ<--'JJJJj[[ V9 ÿÿeÀ73#3#67'675#535#V"  ( À6= D6cÿéöÏ!'+173533#3#&'#5'67#535#35#&'735'6p8771&" 18. ¹T DP.$TW.  ..  ÿéóÏ!'+173533#3#&'#5'67#535#35#&'735'6dddTA#3 9%": 5 @Td$@@ H@ »X"-NM*!XY2 22;ÿéòÏ"(,273533#3#&'#5#'67#535#35#&'735'6KIGG=,'%-(&=I ))5)  ¹T%LT/$TW.  .. UÿéõÏ!'+173533#3#&'#5'67#535#35#&'735'6c><<5'!( #"5>!!0!¹T"GR-$TW.  .. \ÿéõÏ!'+173533#3#&'#5'67#535#35#&'735'6i<992% ' ! 3< .¸R "GT.%RX0 00  ÿéô !'+173533#3#&'#5'67#535#35#&'735'6 iiiQ@$5 9%$77!?Qi+>>  E>  J !55 JI( ((  pÿéöÏ!&*073533#3#&'#5'67#535#35#'735'6u644.# /6, ¹TCJ*%TW.  ..  ÿçôÏ48<A73533#33##3#&''67'67#5367#535#535#535#353567\]]Pe‰: $ .$N :$9 =I9PddPP\p<<P  . ")#*55**1C!!!8! ¿       A  ÿçð¦59=B73533#33##3#&''67'67#5367#535#535#535#353567WXXMg…4 " /'E &/55@1NeeLLWk:::f4œ           7  TÿäôÏ#;B73533#33##535#535#535#35353#&''67'7#53667#a8DD7y/<T " 2 ,%#,,À"! !%    1   ÿç˜Ï#;A73533#33##535#535#535#35353&''67&'67#5367#2<<-g&22##2F.7  '' *¿" !    * ÿé…Ï#'?D735#53533#33###5#535#535#335353&''67&'67#5367##++44* *##..#5.2    #  '  + pÿéîÇ %7#5##535#35#535#53#3#3#7&'îXXXGK<ÇÞÞ½«„++3  rÿèôÆ"7#5##53'>33267#"&5ã@&    Æ‹yy‹(R.*  #,9 ÿío}7'67#535#53533#3#&D "'("""#  " jÿæòy"7#5##5333267#"&55'>Þ@*   . y^LL^% !   $KÿéëÅ 7#5##535#3533#3#535#35#ëzzz ***"W"*22ÅÛ ܽ«%FFO"aÿéîÅ 7#5##535#3533#3#535#35#îggg$##K$''ÅÜ Ü½«&EEP$hÿéîÅ 7#5##535#3533#3#535#35#î```! H!$$ÅÜ Ü½«&EEP$ÿéãŒ7#5##535#3#3#535#535#35#ãžžE55'c(55E/@@Œ£ £„q44UWÿèé– 7#5##535#3533#3#535#35#ékkk%%%N%((–® ®‘€888fÿéîÇ )7#5##535#3#&'#5'675#535'2îdddN !!  !!!ÇÞÞ¾¬ :0 #pÿêìÅ (7#5##535#3#&'#5'67#535'2ìXXXH     ÅÛ Û¼ª D2'RÿéìÊ  $7##535#35#7#"''3255#535#5#–1‡  1Ê\…á%87Ä b\%8aÿéîÊ  $7##535#35#7#"''3255#535#5#¡,y  ,Ê\…á%87Ä b\%8KÿèíÊ #7##535#35#7#"''255#535#5#’6$$$$‘ 9''''ÊO“â -/Ì zO -LÿéîÊ  $7##535#35#7#"''3255#535#5#”4 Ž  8%%%%Ê\…á&<6Ãa\&<FÿéêÉ $7#"''3255#535#5#'##535#35#ê  7&&&&$6$$$$ÉË  ‚D & 5Dœà & NÿèïÊ  $7##535#35#7#"''3255#535#5#˜8&&&&  9''''ÊCà ( 'È  €B ' VÿèïÉ #7##535#35#7#"''3255#535#5#›3!!!!‡ 5####ÉGšá ' +Ì ‚G ' ÿèæ— $7#"''3255#535#5#'##535#35#æ  I7777&G4444—–  HH ' 7Gf­ ' ÿéë– "7##535#35#7#'3255#535#5#tJ8888Á O====–He­ & ,” FH & žðÒ!73#&'#'673#&'#'6+B"pM( Ò   ÿèïž +/373#3#535#5#35#3353353#3##5#535#735#35#ÝH<Æ CÈÚ‰ÿéöÏ!%)-73533#3#&'#5'67#535#35#33535#335•%%%$  $%"4"»[' !;;![9; ÿéö !%)-73533#3#&'#5'67#535#35#33535#335gggQ?#7 :&$75#?Qg)>>R>>>R>’O "44 O.-Yÿéö›!%)-73533#3#&'#5'67#535#35#33535#335d<<<5& #" !)5<""4#W""4#G !84 G* % rÿéöÏ!%)-73533#3#&'#5'67#535#35#33535#335v455.%  #.4-H-»\"">?\9:YBôÍ!%)-73533#3#&'#5'67#535#35#33535#335e:<<4'"  #'4:""4"V""4"à  >   > # ! VÿéõÏ!%)-73533#3#&'#5'67#535#35#33535#335b><<71% !$',6>##6$Z##6$¼\#!>C#"\9: ÿéuÏ"&*.73533#3#&'#5'67#535#35#33535#335***$$   $*&:&½Z =BZ88ÿéñÇ"(.473#3#"''3255##5##535#&'7&'&''&'âg^  JJ^g3 i N Ç“  {‘‘™¬?   $qðÎ#73#3#5##5##535#3#73#3#73#ÄXfQQfX??d@@e??e@@Î (11' "yðÎ#73#3#5##5##535#3#73#3#'3#ÄYgRRgY??d@@@@e??Î%++$   ÿéLr 7&'&''6&  ) r      Eÿèôl#73533533#3#535#35#'67&'S4!!)¬(044  Y Z2   ƒÿéæk73#"''3255#'65533535œJ -)*+km 0("  ÿé…o#'+/73533533##3#3##5#535#535#5#5#35#335),,0055++Q) +h  &  &  - QÿéìÇ%)-73#3#"''32765#'67#'67#'67#735#35#msG_   2 -0+ KKKKÇ^ N -9!+* 7:nÿéìÇ$(,73#3#"''3265#'67#'67#'67#735#35#{f=R , &$    >>>>Ç^ N18"+' 7:fÿéíÇ$(,73#3#"''3265#'67#'67#'67#735#35#wkBW / )#   CCCCÇ^ N18"+% 7:ÿççe$(,73#3#"''3265#'67#'67#'67#735#35#0¢r‘   7- ;3)&}}}}e6+     !gòÐ")/57'6'#3#3#3#&''67#53567&'#7'6'6× 5-4TPPPP[ -1 #£ 90% #7 7Ð       ;V 8     WòÏ")/57'6'#3#3#3#&''67#535&'#67'6'6Õ 0.3VOOOO[  +1 L" 1 2$ %5 :Ï      D] C      JöÎ!'-37'6#5353#3#3#3#''63'#67'6'6ß 7/‘"n\TTTTb -3M # ‘ 2 5$ $9 :Î  OI   L    pÿéìÇ$(,73#3#"''3265#'67#'67#'67#735#35#{f=R , &    >>>>Ç^ N18" +# 7:NÿéóÏ %)-73533673#3#5##5'67#535#67#35#35#d-10F!YM  59-A MMMMµ ue TAzÿéóÏ#'+7353373#3#5##5'67#535#67#35#35#Š #2B3 $'2 3333µ ua oAKÿé©Ï #'+7353373#3#5##5'67#535#7#35#35#V " 5" ' """"´  ud pCÿéê&*.73533#3673#3#5##5'67537#535#35#35#D'## =Z hy  'PT'yyyy‹  [ B U- zõÐ73673#&'#'67#QqB, 6#% 9 (=½     }ÿéòÈ #'+/73#735#'3#735#3#3##5#535#735#33535#335¼44N33 h+1111**B*È22/V((355ÿèì$)-173533673#3#5##5'75367#535#67#35#35#H8) )FQb  DE8X &-bbbb€  Z E   @0Uÿéì{$(,73533#3673#3#5##5'67#535#35#35#e(  !8?M 64(MMMMk  I4 E % -ÿéá  7'66553'#3U Ÿzzt(8+ (N, ^ÿéóÏ#'+73533673#3#5##5'67#535#67#35#35#q$)/BOE -.$8 EEEEµ ub  nB ÿéóË,049=73#3#3673#3#5##5'675327#535#535#735#35#35#35#)¬\BB(& 'JRw%$iXBB=„„„„wj wwËE  T ;   ) % '…ÿéóÏ!$(,73533673#3#5##5'67#535#7#35#35#”  , <. "#. ....´  u`pCQÿö’Ï7&'367'5#j  * Ï ?m  pEÿéòÏ!$(,73533673#3#5##5'67#535#7#35#35#]/24I^Q #7;/C !QQQQµ  uf oA>ÿé„Ë#7#"''32655'67&''67&'76t       Ë 4=#$&   ;ÿê’Â473#67&'#"''32655'67''67&''67#CM           '$ ‰ÿéôÏ#'+7353373#3#5##5'67#535#67#35#35#™ &5* "* ****´ u[ pC òÏ%*.73533#3673#3#5'675367#535#35#35#%D22" 3OQŒhUD.dcdd½ V<  \3Cÿéñ¡$(,73533#3673#3#5##5'67#535#35#35#](**&>OT ")@7(TTTT  a M  W3 ÿèò”$(,73533#3673#3#5##5'67#535#35#35##K11)" 3Z cp'.(WbK.pppp„  Z I  R.lÿéóÏ $(,7353373#3#5##5'67#535#67#35#35#z%% '8 I> ..%8 >>>>µ  u` oANòÏ!%)73533#32673#3#5'67#535#35#35##D553 1W!]˜.'TVD0pppp    >.  ? $ ÿççD)/573#"''3255'675#73#"''3255'675#&'7&'b %-3&Pnb %-3&P`uDK   K     ÿé{Ç$(,7#"''3255#'6553533#3#535#3#735#{   A  3//ÇÅ  ¯SC7 3B_28;~ÿéïÆ(.473#"''3255'675#73#"''3255'675#&'7&'…3    !:0    /  IÆÃ  8 VÅ  5 X   ‚åÊ #73#5'675#73#5'675#&'7&'[%,In[%+IfrÊE  E  VÿèöÏ"(,0673533#673265#"''67&'#7&'3#735#'6\O44      P|  e??5%.2©&6$5!(#,->7 DC!:  2÷Ï'+/573&533&'73#67327#"&''67&'#3#735#'6 L     €[[44M1<?·  !%,   )5, ÿéúÐ"(,0673533#67327#"&''67&'#7&'3#735#'6€ML    À  žVV..M2=Cª&3#" /&7.=9   GK%I  JÿèïÏ#)-1773&533#673265#"''67&'#7&'3#735#'6NY66     %Yƒ  m@@3$+0©&%%4!-%1'@8 FC#< wïÏ 73#5##53&''67&'d¶b - )X(" #'Ï --$    ÿéó‰'+/573&533&'73#67327#"''67&'#3#735#'6s "P   uQQ--M2>@j   '/  !& 5+ ÿéó†(,0673&533&'73#67327#"''67&'#3#735#'6s "Q    tQQ--M2>@r  &#"2  #,7.  ÿékÏ!'73#3#5##537#53635#35#35#7&'S ""0(6 (000000Ï™™c?@®  ÿèíË ,73#535#535##5#3#"''3255##5##535##5)©©•‹‹•ÄNI  54HQËTN,1  GG5G/qÿéôÇ *73#535#535##5#3##'3255##5##535##5ggVOOVu'0 0'ÇPO/  FF7Gÿêì– ,73#535#535##5#3#"''3255##5##535##5-ž¡Ž††‹¿OH   66IQ–C  >!  33$3$tÿéî  ,73#535#535##5#3#"''3255##5##535##5}efTPPSq")  *$ @  =&  ::-< yÿéõÇ *73#535#535##5#3##'3255##5##535##5†bbPIIPo", ,"ÇPO/  FF7GSÿéöÏ48<B735333##67&'#"''32655'675#535#535#33535&'`288  ,    $33==2E%%%f »$%   &+ /$/  ,ÿéô¸37;A735333##67&'#"''3255'675#535#535#33535&'E@FF   1  +# 5"@@YY@T222x  š"#   "    "' ^ÿé÷Ï48<B735333##67&'#"''3255'675#535#535#33535&'o+44  '   #--55+>!!!` »$%   &*  /$2  hÿé÷Ï59=C735333##67&'#"''32655'675#535#535#33535&'|)--   "  #**22);Y »%$   $) 0%2  ÿéñÏ.26<B735333##67&'#"''32655#535#535#33535&''6'OJJ ! :!  PPddOc666•Q'13¼$%   /3 X$2   ÿévÏ$*073533#3#535#3#3##"''3255#&'''6*++$Z#* UU e(  *T  5  ½6=  9 cÿéõÑ "(.7#5##53&'73#3##"''3255#'67&'ï^73OO‰=   9 \³0/ *K  H ÿéì™ "(.7#5##53&'73#3##"''3255#'67&'ì³aV••Õa   ^1! {! ˆ)) #.  + GÿéóÑ "(.7#5##53&'73#3##"''3255#'67&'ìoA ;[[žF    Dl´10 +J  G hÿéõÑ "(.7#5##53&'73#3##"''3255#'67&'ðW2 .II8   4 X ³0/ *K  H !TïÎ "(.73#5##53&'3#3##"''3255#'67&'‚]°^7““Ñ^  `5 ' (x# "Î""!      MìÐ 73#5##53&'#3#535#35#¡ByGCYcvGGPPÐ #$078'_ÿèëª !73#5##53&'#3#5##535#35#…\­a^‰“€vv€€ª ,,16D ‹&]pÿéòÐ !7#5##53&'7#3#5##535#5#ò[5'MQ>99>>¶%%  +@M ¢-qpÿéòÐ !7#5##53&'7#3#5##535#5#ò\7&NR?;;??µ$$  *?L ¢.rfÿéñÐ !7#5##53&'7#3#5##535#5#ñe<(VZGCCGGµ$$  *?L ¢.r_ÿëôÏ $*73&'73#3##"''3255#735#'67&'i89†r.  1LLd  ³ HA  =$@ ' NÿëôÏ %+73&'73#3##"''32655#735#'67&'\9C’ x3   1PP l  ´  I> 9#@ ' SÿëôÏ %+73&'73#3##"''32655#735#'67&'`: =Ž v1   1MM j  ´  I> 9#@ '  ÿéqÏ"(.7676767&'7''67'67&''674'$   &%  5  0„:"  /*i   ÿënÏ"(.7676767'7&''67'67&'4'''6$  $!  …9"   /+iTÿèóÐ<@DHLRX73533#3#&''67#5365#'67#5365#53533#3#&3#735#35#35#'67&'^   n    u€€ZZZZZZ  P À   &  Y? # #     ƒÿëôÏ $*73&'73#3##"''3255#735#'67&'‹,%eY# #33   Q  ³ H?  :$@ &  hÿëôÏ $*73&'73#3##"''3255#735#'67&'u04z g*  *AA]  ³ H?  :$@ ( uÿéòÐ %+73#53&'3##"''32655#735#&'''6µ/v1c'   )==@  9 Ð  ˜C  >8    '  Ï  +„a %6F#   $,   ?ÿéõÑ *1773&'73##5'673&''67&''667#7&'KG G£!  =>"     .  ¶  ‰j %'A-    $#+ VÿèðÐ !7#5##53&'7#53#3#5'35#35ðtD 1hU_LBBLµ))  Á ¢>M jSiÿéñÐ  7#5##53'7#53#3#5'35#35ñb< *]JS@77@µ)) Á ¡>N jS2ëÒ 73#5##53'3#3#735#35ƒ`°`?›ˆ•¨uu‚Ò ,.,0 2O=_ÿèðÐ !7#5##53&'7#53#3#5'35#35ðk@ .dQZG>>Gµ))  Á ¢>M jSOÿèéÐ !7#5##53&'7#53#3#5'35#35érD 1jV`LBBL¶**   £?N kSUÿêî¤ !73#5##53&'#3#5##535#35#¡BtBGV\JCCJJ¤ ))/5A …%[ÿéíž !73#5##53&'#3#5##535#35#€b²b`‹™…ww……ž &',4 A „#VÿèñÑ #/73&'73#&''67&''63533##5#Y WÈ2 u  •ieei¸    -9CCÿéñ™".73'73#&''67&''63533##5#[  \Ï3   t  ”egge„       $//Fÿèð¦ #/73#53&'&''67&''63533##5#Ÿ>–B  V lLJJL¦ $  !   - 22ÿéïŽ ".73&'73#&''67&''63533##5# XTÁ+  x ”eddew       "'' ÿéó¤$073#&''67#&''67#53&'3533##5#‚X*   J   -_bihhi¤  !   v--rÿéòÏ #/73&'73#&''67&''63533##5#{-0s F   O5665´      -   ;>> ÿè…Ð/397'67#53655#'6553&'73#35#535333##&'3535R    /)[$  %% =1 2;Y  C('k(  ÿèñ¥/3973#35#535333##&''67#53655#'6553&'3535ŽU³L<<B87 <9 0/:L[001¥2   & 1' )0I? tÿèðÏ"&*73#3#"''3255##5##535#53635#35#£5+5  ""5. EEEEÏX<  %SSDVX -4 ÿéuÏ"&*73#3#"''3255##5##535#53635#35#5/%*  +& 9999ÏV5  YYDVV ,2iÿèïÏ!%)73#3#"''3255##5##535#5335#35#9/:  '&92%NNNNÏX<  %SSDVX#4oÿéïÐ"&*73#3#"''3255##5##535#53635#35# 7-7  $#6/"IIIIÐ [7  "QQ@P[ /9ÿè{Ð 73#3#3##5#535#536#335#:,NQ*----'*;;;>>Ð 77++JZÿèòÐ 73#3#3##5#535#53#335#’>gk7CCBB4&ATTTXXÐ88++‚JFÿéôÏ#'+73&'73#3#3#3##5'65#5#35#w/  04----6} "C44444Ï!!! ,+!!3!!3!Aÿÿ†®73''67&'767#K2      ®A#%•ÿèôÐ#'+73&'73#3#3#3##5'65#5#35#³< #Ð$##„..$$4##4#;ÿêœÏ48<@73533533##3#3#3#&''6677#537#535#535#5#5#35#335D  ##*'  ! 6  ¾ 2    2 =ÿéðA 733##5#53wffggA11BAíÑ#'+73&'73#3#3#3##5'6353535k*5<4444;}00000Ñ L ! !;JÌ 7&''6' Ì  A Eÿèó#'+73&'73#3#3#3##5'65#5#35#p+6;2222<‚ A33333  g&'V“ìÎ 73353353#V..–Å )) 2 ;îÑ$(,73&'73#3#3#3##5'65#5#5#F8 JQGGGGY® (M?AAAAÑ  L & !+ÿéÚ1 7#5##535#Ú‰‰‰1H H+7ÿéœÏ #8>D73&'73#&''67&''63533##"''3255#'67&'F#R  .     5% % C ´      $   - ? <&  }öÐ73673#&'#'67#Sn9' / , < ,>À  !  ÿéð’"&*7'673&'73#3#3#3##75#5#35#+ 9TSHHHHW±ZFFFFFK "&   j'&Zÿéð‘"&*73&'73#3#3#3##5'65#5#35## )-&&&&0l 8)))))‘   `!&&ÿê\Ž73533#7#"''3255'675#   w"-  !)dÿéõÐ $(,#5'673&'73#3#3#3'353535   (  &-''''/f$$$$$ $!"!™!!2""4!!oÿêìÇ$(,7#"''3255#'6553533#3#535#3#735#ì  M  @99ÇÄ  ¬l>!!>w27;fÿéóÐ#'+7'673&'73#3#3#3##75#5#35#€  (  *&&&&-_4 „ #( """‰""3""3" ÿéóÏ  $(73#''67&'#"''3255##53#735#u( #(~! &9   Ž2QQ++ÏLB $;z  b€“+C!ÿéäÏ $(733#"''3255##537'6'&'3#735#u[  žWQ} &bb::ÏB‹  s‘¤< \P*PÿèëÐ $(733#"''3255##537'6'&'3#735#”C  sDDe  %EEÐJ…  nŒž<  XG%mÿêìÎ $(733#"''3255##537'6'&'3#735#¢5  W59Q  <<ÎG‚  kŠ;  YG%| íÏ $(733#"''3255##537'6'&'3#735#«-   I/2  J  33Ï@n  Ys„4  K@rÿèîÐ $(733#"''3255##537'6'&'3#735#¦4   U48 Q  99ÐJ†  qž< ZF& ÿédÐ#'73#"''3255'67#5353635#35#35#2$   $ &7  ******Ð º  )'p 299PÿéõÐ%)-48>7&'73327#"&'##5#'655365##5#&547##5#'&'š  ? OO  Ð 0"$$$!(99# ']G. Ž  ÿé÷Ó!%*04:7&'73327#"'##5#'6553635#33&535#73''&'s  a :<zy99M7„:9M9ˆ  Ó q(.CAA& '3IG E r ÿòðÏ!'7&'73#3#535#53635#&'7'6P  r ;FNáIEƒ9""?  °Ï ‹‹³‹#+ *&1! ( ÿïó’ *7&''63#3#3673#53&'735#535#‡14 5607 E s/YY 2Þ4 YY0’   #   #DÿîóÏ#)/7&'#3#3#535#535#5'63&'&'7'6“%, 0CCK¥G??- 7 V  i  Ï& DD $ ^   RÿðõÏ$*07&'#3#3#535#535#5'63&''6'&'œ$% *==CœF;;) 2YB [  Ï)== %$_   }ÿðô¾7333267##"&5477#€_A  #8G¾}  !'$i ÿðò± &,7&''63#3#3673#535#535#&'€,8 52+; C y3YY  1Ñ^YY3  ± %!/// qÿêðÆ73##53#3#"''3265#ƒ^^ MK OÆ=*G-vÿéóÄ73#3##5#535#}q05544-ÄInnIrÿîôÏ-7#"''3265#532667#"'&&55'753753ê   )! ¤U$/ `Y g# cD<E=lÿêöÐ7&'5#5'66556Þ   4Ð$>C4t"¿7L? $5%HqÿéêÈ7#"''3255#'65535#35#ê <;;;;ÈÁ 3.) 5@_;(b'VÿéòÒ 48<7'2'6'&''&'33###"''3255#535#535#33535Ù 6NA> /    „8  77@@7L$$$Ò     ()!  )ÿéïÑ 48<7'2'6'&''&'35#533###"''3255#535#75#35#Õ LmZPB  /  aL®N  PPa¯:::Ñ       A((  (^ÿéóÏ 48<7'2'6'&''&'33###"''3255#535#535#33535Ù 3J=9  *    |7   22::1E###Ï      )("  ) ÿéó›48<7'23&'736733###"''3255#535#535#53&'3535Ô JjX5 #  R SSiiU T>>>›    !   wÿéôÑ 48<7'6'6'&''&'35#533###"''3255#535#75#35#Ú #C ?!!0)k /  ))0^Ñ    D)*"  ) ÿèöÑ#'-373#53535335335#33533535#35#35#'67&'Ýí8#o$$8#$“$$8##7$$I*'b'$ &&´B/////n.....F    `ÿçõÏ#'-373#53535335335#33533535#35#35#'67&'è ” #E"V"#5 N¯uu ;)))))c)))))@  IÿçóÏ#'-373#53535335335#33533535#35#35#'67&'ã© )O'e'(;!Y¯uu :(((((c)))))@  XÿçôÏ#'-373#53535335335#33533535#35#35#'67&'å› &I%[%$9Q¯uu :(((((c)))))?  JÿèñŸ#'-373#53535335335#33533535#35#35#'67&'ã§(O'd'(@ SŒ]]/K5   fÿèõÏ#'-373#53535335335#33533535#35#35#'67&'è "D"T""7O¯uu :(((((c)))))A  oÿçõÏ#'-373#53535335335#33533535#35#35#'67&'ê … ?M 2 I¯uu :(((((c)))))@ ÿèõˆ!%)73#3#&'#5'67#535#735#33535#335+¨LcN$5 ;)"8 3I^J66H8€66H8ˆO  10  /-ÿèï˜!%)73#3#&'#5'67#535#735#33535#335+«KcR#0 7"!62 QdL99M8…99M8˜T !8822dÿéíà 73#735#35##5##535#35#mvvNNNNlaaaaaÃ]797m m'<lÿéîà 73#735#35##5##535#35#uqqKKKKf\\\\\Ã]797m m'<ÿèâ 73#735#35##5##535#35#/¢¢|||| I,+)` `#5yÿéíà 73#735#35##5##535#35#€ee====YLLLLLÃZ578mm(>?ÿô¾s 73#735#35#3#735#35#IiiIIII]]]]s8" >% " G¸ 73#735#35#3#735#35#Ncc????qqKKKKF+ & (H+ ' ÿéïÄ 73#735#35##5##535#35#†aa;;;;VHHHHHÄ\677n n(=Zÿéôn73533##5#'67#535'6§ * *, #))#n00CC(‡tèÉ 73#735#35#‡aa====ÉU34€ÿèòo73533##5#'67#535'6¹   o00@@' ÿéžÅ $7#'6553#3#3#5##537#33535#\,x'//':''SSS,::Z>1 5=]=TTv~!dÿéðÅ $73533#3#5##535#'6553'#335#‡)--&7%*tOO877ˆTT@. 5>]=+!QÿéñÅ $7#'6553#3#3#5##537#33535#©1ƒ,55+B*,]]]1BBZ?0 5=]=TTv~! ÿé€Å $7'6553#3533#3#5##5357#335#-aO""'==(''Z?/ 2?]=TT[Ž"DÿéõÏ%+73533#3#3#3##5#535#53&'#535#367#T@@@N# ?NNMM>" !P@5& A¶ 11K \ÿéòÏ%*73533#3#3#3##5#535#53&'#535#367#j544@4@@@@3C5-4¶11K ÿé€Ï%*73533#3#3#3##5#535#53&'#535#37#&%%.&////&0&" (¶11K ÿéxÏ%+73533#3#3#3##5#535#53&'#535#367#"""+"++**!,"!¶ 11K ÿéóÏ%+73533#3#3#3##5#535#53&'#535#367#"UVVh1 'SggggT. ,jUB: \ ·11JLÿéïÏ%+73533#3#3#3##5#535#53&'#535#367#[:::G 8GGGG;I:1# ;¶ 11K ÿ逛$)73533#3#3#3##5#535#53'#535#367#&%%.%....$ 0&$%Š$$8 NòÏ%+73533#3#3#3##5#535#53&'#535#367#];::G 9GGGG9 J;1#:¼ ** > ÿçò$*73533#3#3#3##5#535#53'#535#367# VVVh3%O____M%1hVC:S 6 #‘ÝÈ 73#735#335335#ºº%%7%&È7^ÿðóÐ %73#5##53&'&'''63#3#535#¦=g;-  #y4B”?2Ð .1 : .22lÿñóÐ %73#5##53&'&'''63#3#535#®8\5)  n/;†8,Ð-0 : .22ÿïêe (7#5##53'7&'#3#535#5'673&'éª\,$ 7_Óa8 4 oW%&     @ÿéóÑ *7#5##53&'73#3#33#"&''675#éqBGt177/2 "0»**  6)0.;flÿëôÑ *7#5##53&'73#3#33#"&''675#îW2 9\$**#%   %»**  3.1,<j_ÿéôÑ *7#5##53&'73#3#33#"&''675#í[8=b'--')   '»**  3-0.; i ÿêõ¥ +73#5##53&'3#3#33#"&''675#„\­cF­LMM!0)E. /N¥ "" 9! #.N ÿé…Ñ *7#5##53&'73#3#33#"&''675#…L.4S$$     !»**  3.1-=kÿçîÑ#'+7&'73#3##5#535#53635#33535#335U  i &LddddLp i88M8…88M8Ñ  m..m@G ÿçiÏ#'+7&'73#3##5#535#53635#33535#335)  < ###''#/ '"1"Ï   g00g>CZÿéôÎ#'+7&'73#3##5#535#53635#33535#335‡  K #5CCDD5C>""5#X""5#Î  g..g?CSÿçòÑ#'+7&'73#3##5#535#53635#33535#335†  P 6EEGG6MH##6#Y##6#Ñ  g00g>ElÿæôÎ#'+7&'73#3##5#535#53635#33535#335“  B !/::;;/< 4/K/Îf11f?DGÿèò¥#'+7&'3673#3##5#535#735#33535#335|  _  "AJJNNA//B/q//B/¥   U33ÿèñš#'+73673#3##5#535#53&'35#33535#335T 1/PffiiQ) >>Q>>>Q>–  SS21~ÿæôÎ#'+7&'73#3##5#535#53635#33535#335   B +2222+= 5+D+Îe22e?E ÿæ’Ð#'+7&'73#3##5#535#53635#33535#3353  D !188::1? 70N0Ð g11g?Cÿîï¦ !73#5##53&'3#53535#35#35#€a­^`"Þ#qqqqqq¦ ,, 3nn%78iÿñóÏ !73#5##53&'3#53535#35#35#«;`7CŠ111111Ï *+ @††-GHRÿñôÏ !73#5##53&'3#53535#35#35#  CtBH ¢!;;;;;;Ï 01 @……,GGHÿèóÏ (.473&''667'6''63&''657'6''6›(! $/ ,< b 0+ .7 M? b  Ï$) A('#!* ÿç›Ï.27367&''676''367&''676''O "0 ( * 5 ". $ ) Ï  ,,   )jëÏ*067367&''657367&''65''67'6D   )l    (‰ p  Ï     !     !   7íd7#5##5íµd-,ÿíòG73533#3#535#)LKKhâeL,<ÿèôu /573&''66''67'6367&''66''6‹)$'&0-( l 4   7#81& u       +       ÿç†Ï-17367&''676''367&''676''D  (  # .  &  " Ï    ,,   )…ÿéõÏ!733#3&''67&'#5367ª22-  Ï"8(  0 ÿä³y177367&''676''6'6765367&''6_    "9 .)  58 *    Q  y       V    0  Hîs7#5##5î´s++ÿè÷P733265#"&'#3##5#535#/‘ ?44::*P,),*++{ÿèôÏ (.473&''667'6''63&''657'6''6³   ,  D ! % 4-  F Ï$ ( @(&#!* ÿéyÎ)73'67#'63#35#535#53#5##56/6  ' C0CÎ  %/ !!} x ÿó€Ò.73533#3#535#&'''63533#67'675#(((0s0(K * &$$0:&·0  " #Iît7#5##5î´t++Dÿèð  (.473&''66''67'63&''657'6''6˜(" "%+ .0  r9 + -6 N? d        4   ÿçíR 73#3##"''3255#'67&'6––¾R   X, * $|  R$     zÿèóÏ (.473&''667'6''63&''657'6''6¯  # -  K$ $ 1. LÏ$ (  A('"!*  YÍ.47'667367&''6'667367&''6I   6 $#    9 §   *  G     &  iÿèóÏ (.473&''667'6''63&''657'6''6ª  '!.  P ($ "+ <1  NÏ$' B(#"* 2ÿéÐY7#53#"''32=#335Ež  xxxx'pU  7  dÿèóÏ (.473&''667'6''63&''657'6''6¨ $#1  R(% $- ?2  Q Ï$' A(%" * ÿèó 06767&''6''667&''6''6{   00= b5 =   ' J  e9   # 1) $  .Uê %+17367&''65'367&''65''67'6°    )k    *  p             0íS7#5##5í¶S## ÿçô?7&''676''6À ") N#2) g n 4    9    …ëÎ#)/7367&''67&''6736''67'6E    %¡    (°oÎ     "    )    Qí~7#5##5íµ~--Qîx7#5##5î¶x''ˆêÏ%+17367&''657367&''65''67'6D    )l    )ˆ o Ï               gì…7#5##5ì´…ÿéìr 73#53&'3#3##5##535#`×^I¹¹¹¹³ŠŠŠr $  00 )ÿéØg"(,73#3#5##535#5#35#"&55#'67#333535#-§7;Š62^6Š( %!b#ŠŠŠg bb 2    <  F‡g 73#53&'P-z9g aÿéîÏ$*0673#"''3267#'63533##5'67#7'6'&'&'‹ Y   N (''  K1D Ïœ-$ !N::UA!B   8 HÿéíÏ%+1773#"''3267#'63533##5'67#7'6'&'&'{k b #111 !)^  A  O  Ï š/ˆ M::UEB   8 ÿèæ*0673#"''3267#'6'63533##5'67#7&'&'6    –| {KBB) &9(  \  u0a  ,,G@%;  <  `ÿéîÏ%+1773#"''3267#'63533##5'67#7'6'&'&'‰\   Q *(( #O6  H Ï œ-$ M::U@ B   8 cÿéðÏ%+1773#"''3267#'63533##5'67#7'6'&'&'Œ[  Q '$$   M5  B Ï š/% !N99WC@   9  ›ÿéñÏ973#"''327'#5'67#535&'75367'3#765#'6³3         )Ï )5>    +%  %_  ÿë_Ç!%)-39?7#3#3#3#"''3267&'765#55#5#35&'''67&'Z    8     ÇM  &&6!bÿé¡Ï#'+/73533533##3#3##5#535#535#5#5#35#335b   '¼%<((<%P ÿé~Î,2873#"''3267#'63533#&'#5'67#7'6'&',G  =    @ - Î ’„ R:: :6<  uÿéïÎ+1773#"''3267#'63533#&'#5'67#7'6'&'—M  @ "    D 0 Α &} Q:: 9><   ÿèzÏ.373533#3#3#3&''67''67#535#535#67#(%%""(51      .%%(4 %º      ‡ XÿéöÈ'-397367#53#3#&'#'677#3#"''3257&''&'''6a//w5I%!' &6   C    1 ™!# i  M#)+"!') ) " ÿêöÈ&,2873#3#&'#'67#5367#3#"''3257&''&'''6$·UqG77@ :JVMG X   JÈ "*/ Oc  F''+$!' )  *Mÿê÷È&,287367#53#3#&'#'67#3#"''3257&'''67&'_-+u7I$!$+##2   F  TQ  š  !$i  L#)+") ! !') <ÿéõÈ(.4:7367#53#3#&'#'6677#3#"''3257&''&'''6F87ŒBX-$'*  -C  I   8š  !$h  L#)+"!') ) !MÿèöÈ&,287367#53#3#&'#'67#3#"''3257&'7&'''6[,!n9H#$&, &&7   ,  .  T š  $2%!e  G%%)#% '(  ÿéó”%+17737#53#3#&'#'67#3#"''325''67&'7&'KM¿Zt=+ 3+: %8\   )e  7 l  !  M  1 %  LÿéðÐ $73#'66553&'33#3#5##5335#«:w :55,: ::Ð K>1 -!V ,&_ _B1bÿèñÐ#73#'6553&'33#3#5##5335#·0i7,,&2 22Ð M>3 3:X +&_ _B0?ÿêóÐ/573#'6553&''67#73533##"''3255#&'£ CJ  5  5  ÐI>. 08T %)‡‹""`  ]QÿêóÐ.473#'6553&''67#73533##"''3255#&'­<B .   .ÐI<0 /9T ƒ%)‡‹""a  ^ÿïñ— %7#5##53&'7'67&'3#3#535#í²^' - /K*&((f´QhâfO„*+     #""QÿèõÏ773#&'#5'67#535'633##"''3255#53567#Ø =- #& !*=/96u??  LLWÏ &*  f !  tÿéôÎ873#&'#5'67#535'23567#533##"''3255#ß 2% !0054 D]00  4Î'& •   LÿìöÑ +17=7&''6&'3'67#33267#"&57&'''67&'¤( ' # 4 )x  ` -  7 ƒ  Ñ    &; ;$ OÿìõÑ *06<7&''6&'3'67#33267#"&57&'''67&'£% &# 1 *u \) 4   Ñ     ';  8  $  ÿì˜Ñ !'7=7&''6&'3'67#&''&''33267#"&5''6M$(  !_  Hc      Ñ !  /  - 0 XÿìõÑ +17=7&''6&'3'67#33267#"&57&'''67&'¨$ # . $n V (  3 y  Ñ     ';  8  $ DÿìõÑ *06<7&''6&'3'67#33267#"&57&'''67&'Ÿ!* +"& 7 -€ f +  8 ‰  Ñ     ';  <  % rÿìöÑ +17=7&''6&'3'67#33267#"&57&'7&'''6¯  ( d  N   $   4  S  Ñ    '< = ! ÿéôÏ )-157&''63##"''32655##5##5##535#33533536 66)> Omm• $####6#$Ï#(# i 1111=‚2_ÿéøÏ (,047&''63##"''32655##5##5##535#335335¥!# ""% 1??` #Ï%($ i "2222>2!!!!! ÿéô› )-157&''63##"''32655##5##5##535#33533562 85(= O __Ž $%%%%6%$›  M $$$$-f'TÿéøÏ (,047&''63##"''32655##5##5##535#335335Ÿ#& %$) 4EEi &Ï$'$ i 1111=‚3!!!!!XÿéóÏ (,047&''63##"''32655##5##5##535#335335ž!$ !"% 1AAd %Ï&(%!i 1111=‚3!!!!!9ÿésÏ 7#5'6_  ϰ‚ 2 ÿébÐ73#&'#5'67#535'6R     ! %Ð'qq$!/  ÿéò¥-1597&'#5'63&'#"''3255##5##5##535#335335/8 k BWq   """""5""¥   *M  """",d'eÿéøÏ '+/37&''63##"''2655##5##5##535#335335§ " " $ .BB`  "Ï%'$ i "2222>2!!!!!lÿéóÏ (,047&''63##"''2655##5##5##535#335335§  )<3"""""ÿéŸÐ',17&'3#3#&''67#535#5'6367#335S! D*1   '0 ,/ÐA!A i  zÿéî¾73#"''32765#'667#'6_      ¾¦.-tdEHT!$ \ÿéôÐ"(.47&'#3#&''67#535#5'63'&'3655#335§ "  3/% ', , ,3 2:   3!Ð! A"'%A   bWÿéõÐ"&,27&'#3#&''67#535#5'63'3655#335¥#" !61( ). -.5 4< "!"5$Ð! A!(%AdmÿêóÏ!&,27&'#3#&''67#535#5'63&'3655#335« -) % & '- (0 -Ï  A!%#A  eQÿèøÐ&,287'3#&'#'67#537'6767&''6'6'6È L$ ) $1  . 0/ (> B: -T WÀ     S "*  ÿèŠÐ!'-397'3#'67#5367'6767&'&'''6'67'6j  :B$   # $# / 0- !@ AÀ     J   ! + ÿíõÏ'7773267#"&55'75'75'6Þ ')-/ &)! ,Ï &  )  4 5 (&DÿèøÐ(.4:7&'3#&'#'67#5367'6767&''6'6'6ÃR'  - '5  !3 44 -B H> 0[ ^À       S "* gÿè÷Ð'-397&'3#&'#'67#537'6767&''6'6'6Ï B !)  ' )( #4 91 'G JÀ   !  S "*  ÿê}È"(.4:@73#3#7'75#535#735#&'735'6&'#&''&'''6c)%%'-8-&&('    ÈX 7 77 w yÿðòÈ +73#3#535#5#35#3353353533#3#535#{s" n"@   Y*++3y2*ÈNN=,,,,,Y!! ÿêtÈ#)/5;A73#3#7'675#535#735#&'735'6&'#&''&'''6\&!!#)3$$& #  ÈX 7  77 v lÿìùÏ'159=A73673#'#3267#"&55##5'67#3533&'#35#33535#335x#>" )      (?(²  L   W");@FñÐ#7733#67#5'75#'67#5373#373##5#'66556J$$8/ =D ™ )L 1¤  5AA, #_ñÐ"6733#7#5'675#'67#5373#373##5#'66556J$$'5/ =Eš *M1«    ,22! =ÿéòÏ%:73673#3533#67#5'75#'67#73##5#'66556E(,    5. ¢ "=  +° $''@< $+ .<3 4E:ÿçóÏ%:73673#3533#67#5'675#'67#73##5#'66556#29   10 Í "-Y(  7¬ 94"6 -……>4 7F;Mÿë£Ï$73673#3533#7#5'675#'67#T#'   ( ±%'' ;3 $ÿçô˜$973673#3533#67#5'75#'67#73##5#'66556"5=  #51 Õ #0Y %  8}  &#'^^(' *2+IñÐ#7733#67#5'75#'67#5373#373##5#'66556J$$8/ =E ™ )L 1¤  6::, #ÿïóg)-7&'#3#3673#53'735#535#5'63&'„+:1MM7á8 MM/GKg       dÿïòÈ#'+/733##"''3255#53567#3#53535#35#35#xi88  ==No Ž "!È  hOOO=====nÿðôÈ#'+/733##"''3255#53567#3#53535#35#35#e44  ::Kj †   È  hNNN<<<<<VÿïòÈ#'+/733##"''3255#53567#3#53535#35#35#mr==  DDVx œ$%È  hOOO===== ÿéóÏ*0673673#&'#'67#3#3##"''3255#'67&'Sg@03!= -B8dd,¾U  U#…¶  ")';  8 !VÿéõÏ)/573673#&'#'67#3#3##"''3255#'67&'f/C(") % CC‚7   8bµ "((<  8 !  ÿéó›*0673673#&'#'67#3#3##"''3255#'67&'Rh?.2! < (!>kµ ")(;  8 !  ÿèöÐ 673673#&'#'67#3533##5#3533533##5#'67#L m;+ 4"& : +=/211234L33M0 '2· ,*0)==&EÿéôÏ!773673#&'#'67#3533##5#3533533##5#'67#R1U, &)&%%%%" 8$$9$ ²   '&,- ::$eÿéöÐ%;73673#&'##5#5'67#3533&'#3533533##5#'67#p)C#   !  ,)* ² ! !!  +T!!!!::%^ÿéóÏ$:73673#&'##5#5'67#3533&'#3533533##5#'67#k,B%  #  3+, ²   !!  +R ::%ÿé£Ð7353#5#535#535#733#3#3##"))!!"P%% **§)ç2++;),+1XÿéóÐ7353#5#535#535#33#3##533[&))$$&j$$..(§)ç1,++,1ç)JïÐ733#3#3##'3#5#535#535#53‘JJCCKK8GG<>88??d" ÿèòl73#5#535#535#53733#3#3##^QQHHKK.MMHHRRl„ `ÿéóÐ7353#5#535#535#33#3##533f"((!!"d))$¨(ç1*++*1ç(Lÿèïš735#535#5353#5#733#3#3##L1&&++1Y441177$²*ˆ+ÿéçU)/573#"''3255'675#73#"''3255'675#&'7&'b $,3%Onb $,3%O`vUY Y   ÿéð—73#5#535#535#53733#3#3##ZJJ>>DD6GGAAMM—®.. ÿåóI767&'67'5'6‚   .O)   OI ?%  +Cî¨7353#5#535#535#3#3##533GLLAAGAAKKHšb      e ÿéòÏ733#3#3##5#535#5335#35#mhhUMjjiiIB-Ï_++_'9WÿéòÏ733#3#3##5#535#5335#35#”EE;3DDDD4-TTTTÏ]++]';eÿèòÏ733#3#3##5#535#5335#35#ž>>72====1-QQQQÏ^--^':PÿéòÏ733#3#3##5#535#5335#35#HH=5GGGG5.WWWWÏ^++^&:_ÿéòÏ733#3#3##5#535#5335#35#—AA80????1*NNNNÏ^++^&:oÿéòÏ733#3#3##5#535#5335#35#¡881+8877+&DDDDÏ]++]';ÿéð733#3#3##5#535#5335#35#oaaTMggggMF2………… J  J *zóÏ733#3#3##5#535#5335#35#§440*3333("????Ï R&&R!1R îÎ733#3#3##5#535#5335#35#HH@6DDDD8/\\\\Î L L, ÿéwÏ733#3#3##5#535#5335#35#4//*',,,,&";;;;ÏZ--Z%8TÿçöÏ-73#35#535#53#3267#"&55#'667#56‘&&Z%%&9!    Ï  rJ M..$'j]ÿèöÏ-73#35#535#53#3267#"&55#'667#56–""S###6    Ï !!rK  N.- #'j{ÿçöÏ-73#35#535#53#3267#"&55#'665#56£B/   Ï  rJ M0, #)jiÿçöÏ-73#35#535#53#3267#"&55#'667#56œL 3   Ï  rJ M/- $(jÿêÏ$7#'67#5673#35#535#53#67'Z+&I 4  K?" 1n ""vE =Ä£*73#35#535#53#3267#"&55#'67#56l  K!!1   ,!£ W(  ..!Rÿèòi+73#35#535#53#3267#"&55#'67#56t &;;‰99@S6 "$ C < 0,i    G # AkÿéóÏ #73#5353335#35#35#'67&'³5q)=fKKKKKK  F«€€8978'     ^ÿèóÐ #73#535335#5#35#'67&'«7w,C OOOOO ! !@«††68&&(   HÿéñÏ #73#5353335#35#35#&'''6 AŠ5M‚ccccccI!)! «€€8988(     ÿçîÑ #733#3#5335#35#35#'67&'reeP¬H4„„„„„„!.*e(( ))Ñ……':9*   |ÿéóÏ #73#5353335#35#35#'67&'¼/f%4X@@@@@@ =«€€8978(     X ñÏ #733#3#5335#35#35#'67&'’JJ?„2]]]]]] JÏ rr!..#    ÿçíÆ $(735#53#3#67'7&''275#735#5##5#&QA•APPMd0-Q$mm}=>nDDE%  'k y!!!!ÿî~Ê!%)7&''275#535#53#3#67&''35#5##5#l $/''Q!(( -++3) )DDDD% t z####pÿéóÐ"&*7'673&'73#3#3#3##75#5#35#‡   $  &"""")Y0‚$' """‰""3""3" ÿéóÇ'+73#&'#'67#735#33535#3353'6673#'±$" ('2/$$::O:‰::O:uQÇk "! ?EH&&  UTÿéõÆ'+73#&'#'67#735#33535#3353'6673#d€   $$7#Z$$7#O 6Æj "! >EH(%   ULÿëíÉ'+/3773#3#"''3255#7'7&''75##535#735#33535#335V‹  + $ (<3 4!U 4!É]T  = "[m7;[ÿéîÉ'+/3773#3#"''3255#7&'7''75##535#735#33535#335d6A  .  '",?6##6#Y##6#É^V  ? "]o8:ÿéê˜)-15973#3#"''3255#67'7&''275##535#735#33535#3350¡F_  K 3AG[G44H4|44H4˜O <  (CS /-oÿëðÉ(,04873#3#"''3255#7&'7&''75##535#735#33535#335wp.7  %  !#6./K/É]T  = #[m7;'ãÇ#'+/3873#3#"''3255#''275##535#735#33535#335&'#&¯N\  & .<H[O<G[K77K7‚77K7ÉaS  ; Zm9;Oÿèñ™(,04873#3#"''3255#7&'7&''75##535#735#33535#335_‡:E 3 $,&7J:((;(c((;(™P ?  * DU 0.UìÉ'+/3773#3#"''3255#27'7''75##535#735#33535#335a€6A  .  %.*/C7##6#Y##6#ÉR@  *  BS10ÿë„É'+/3773#3#"''3255#7&'7''75##535#735#33535#335i+1   0++C+É^V  ?   #[m8:zÿëðÇ'+/3773#3#"''3255#7&'7''75##535#735#33535#335€g+4   "  1+*C*Ç^S  <Yk7<ƒÿëïÈ'+/3773#3#"''3255#7&'7''75##535#735#33535#335ˆ_'/     *%&;&È\V  @! $\m7<Šÿçêa7#"''3255##5ê 8aK 6hz ÿýz_ 73#67'75#`',9 ,%_-  2ÿèåÒ (,073533#735#33535#335#"''3255##53#3#[[ÊHH\H¤HH\H  Š!nnnnÁV42-`  Ifx"ÿéâÑ (,073533#735#33535#335#"''3255##535#35#YYÆEEYEžEEYE  †††††ÂU21.b  &y!. ôs73673#&'#'67#^nA* 4J9Nc " ÿéâÈ #7#5##53#3##5##535#35#35#⟈ˆˆˆ™‚‚‚‚‚‚‚ÈSAAS € €"12ÿêˆÈ !7#5##53#3###535#35#35#ˆOBBBBUS@@@@@@ÈWFFW r  //mÿéìÊ #7#5##53#3##5##535#35#35#ìYCCDD[PPPPPPPÊUCCU#€ €"12ÿé‹È #7#5##53#3##5##535#35#35#‹QDDDDWCCCCCCCÈWFFW!€ €"22fÿéíÈ #7#5##53#3##5##535#35#35#íaPPPPcOOOOOOOÈWFFW!€ €"22qÿééÈ #7#5##53#3##5##535#35#35#éTFFFFXDDDDDDDÈWFFW! !33ÿéêœ #7#5##53#3##5##535#35#35#ê«••••¢ˆˆˆˆˆˆˆœD33D  ee % % cÿéöÄ /7#3#3##535#73#535#3&''67&'767#¥/,,,,7AA,,E      /ÄO0Û=+M)L1"     ]ÿéöÄ /7#3#3##535#73#535#3&''67&'767#¢2////:CC..G       1ÄO0Û=+M)L1"      ÿèYÎ73533#67#5'675#'6  Ä009UJ D (Qÿçóš /7#3#3##535#73#535#3&''67&'767#’/..//ÿì~› ,73#3#535#33535#3353353533#7'75#l h .+V)''(/;0)›999= iÿðóÉ +73#3#535#5#35#3353353533#3#535#n&#{$'I##e100:Š<1ÉOO=+++++X!!;ÿïÁr#'+73#3#3#3#535#535#535#33535#335335>‚'#4//9†;//4#'85#r 0    0 /ÿéðÏ$(,04:73533#3#3##"''3255#535#535#35#33535#335&'eiiP66 ——‚Qe'>>P=>>P=w  ¿W%  "W35H ~ÿéòÇ #73#3#537#35#35#35#'67&'ƒn-*`!,:::::: <Ljˆ<==+   NƒÂ 767'533`  #x]  ²8ÿëá 7#5##535#'#5##535#á444$4442222ñ #73#3#537#35#35#35#'67&'ØiV¯E[&‰‰‰‰‰‰* !5 4Y+**)OO   ƒÿçõš #73#3#537#35#35#35#&'''6‡e('Z(666666/  šll///"   ÿ胡!%+1773673267#"&53#735#35#35#'33#'67&'* "0$ - PP,,,,,,-^pB ¡    H3,H   Bw˜ 733#67'B!! ˜%F Bÿé“Å$733'67##"''3255#53&'767#FF     1Å ! ^  Z ÿçñÆ %73#3#5367#35#35#35#'67&'•\% K#))))))  4  Æ ˆˆ =>>.    AÿêšÌ"&73533#3#&'#5'67#535#35#335H         ·H  <;$HL&&&–ÿçóÐ &7#5##5367#'6733'>&'í)#  "   ‰q``q  #8" –ÿçöÏ%+733#5367#'635#35#35#'67&'¹' G& %%%%%% / Ï tt  E23)      ÿèPÏ73&''67&'67#5367#%     Ï,W* #'l!:&[ÿéôÏ!%73533#3#&'#5'67#535#35#335i99941% " )/39 3 ¶E)$BE&,EL!!!ÿé†Ï"&73533#3#&'#5'67#535#35#335200,, %,2,¶F ?G'FM""" ÿézÏ"&73533#3#&'#5'67#535#35#335,**'' "(,'¶F >D!'FM"""KÿéµÏ"&73533#3#&'#5'67#535#35#335Y%$$""   "%!ºE GH#EI###/ÿç•Ï!%73533#3#&'#5'67#535#35#335A!!!  !  ¹MJE#MR+++ÿéóÇ 7#5##537#53#3'>&'ì,#[$ ¡‚pqƒ '@'  !"Kÿé Ï"&73533#3#&'#5'67#535#35#335R    µD  =A"DO$$$?ÿ鯞"&73533#3#&'#5'67#535#35#335L'''""  #'#‹9  )/9;·ÿèë› 73#"''325'3#Ù  "›™  }juÿéõÏ!%73533#3#&'#5'67#535#35#335{0//+  +0*·G(,OR.)GM!!! ÿ胓"&73533#3#&'#5'67#535#35#335/--''  '/'…8  -386‚ÿèó™73'67#'6'65&šE 6 % ,$™ $N#$H0N ñÐ &73533#3##5'67#535#35#335&'UBCC99$#'9B'':&¼CIE$CE: ÿéYÏ"&7#5'67#535#53533#3#&'35#335>   $  ,C>!DD  3$$$Jÿé˜Ï73533#"''3267#'655#_    §((,#u&S3 2G&•ÿêóÏ!%)/5;73#3#3#"''3267&'77#5363535&'''674'¶&7??@    @%%%    ÏL?  ˆ gmÿçô $73#3#5367#35#35#35#'67&'t{44i"3EEEEEE Doo 2.."     ÿêqœ%733'67##"''3255#53&'767#X     &,  @œ L  G  fÿéñ™ $73#3#5367#35#35#35#'67&'g‡:7r'9NNNNNN @™ll-,-!      ÿém—7367&''65''66    +  —. ': $ Wÿ窙 #73#3#537#35#35#35#'67&'WS!J))))))  1  ™gg0,+    iÿûšÃ 733#67'i  Ã=_  ÿèïÈ $73#3#5367#35#35#35#&'''6×gT¡7Z.||||||]+$")6!-*Ȉˆ >==-     jÿèöÈ %73#3#5367#35#35#35#'67&'t|32r+4KKKKKK  F È ˆˆ >==-      ÿèqÏ$73533#3&''665&'767#535# (((# "  C"(­""#+ # ÿéqÍ73##5#'66556e %B  -Í /„„<2 8E: ÿê{Æ 73&'73#67'5#'6553*+iQ  =¤† f,332ÿêqÅ%733'67##"''3255#53&'767#Z    $.  CÅ _  [   ÿé˜Ë+/37GKOS7#5'75#53#'35#35#5##5'75#53#'35#35#5#7#5'75#53#'35#35#5#—]~K888888*?&q*?&~ E?3 & ' f G;/ % &   G;/ % &  ÿé‹Ñ.26:>BMRV[733#3'67#73267#"&55'75#'65533#735#33535#335'635365#25#5#2;,,;*      & RR 0 .8Z  Ñ     J=0 07UH9"  < )‹ÿèòÇ #73#3#537#35#35#35#&'''6Œd('Z'5555552  Ljˆ==>,   ÿé‹Ç (AGMSY73#735#35#&''67#'6727677&'7''67'67777&'&''&'''67&'dd>>>>   I      3)ÇI- ( U   &     %"  ÿê‡Ñ !%+73&'73#3#735#3#735#3#735#35#'6 ,8znnJJ99%dd@@@@W2>CÁ  S6 " 8?& # ŒÿéòÇ #73#3#537#35#35#35#&'''6Œd'&V)222222.   Ljˆ=>>,    ÿê‚Ä%/57367&'#"''3255'67567#3353#5#7&' T       >Pb Ä     ;(¡‰  ÿèy*067676767&'7'#"''3255#'677'6&'''60  "#  B  0  y  '  + %  ?   ÿéƒÏ$(,7'6553&'73'#3#"''3255##535#35#/ )&@@  (((((~8; 2[2  ;*#l  .ƒ#3ÿñ{Æ !.73#&'67&'67&'63#7'75#ff   *    *   Fc)$*4+'Æ   I( +ÿèqD #53#=#,X2[\&&TÿçõÏ"/73533#&''6765#'677&''6`9AA#&,6 %9   L  µ4 O$:>%"ME "-S LÿçõÏ#073533#&''6765#'67&7&''6Y<EF%)/: (<   ]   ´3 P$;> ' LD %*E  ÿéòÏ#073533#&''655#&''67&''6_``NLK i_% ’ °! F$:9%3L!  %/!aÿçõÏ#073533#&''6765#'67&7&''6l4;;#'1 !4   M µ5 N#9<%"NE "-E  XÿèôÏ#073533#&''6765#&''67&''6k5<< #'6 '4  ^  µ: I#7<'!L    #*    D#ö¤#073533#&''655#&''67&''6N@II<:7 J@  n  ˜ $#&      Cÿíò:73533#3#535#Z877M¯O8%ÿéõ’$173533#&''6655#&''67&''6bbc&H C'R ;,b$ ‡  € 8400&     (  ÿèót$073533#67&'&''6767#&''6_aa  . B(J 4_'  f     +& %    ÿçó &373533#&''>5#&''67&''6aaa$H A&R*,a+ ‡  Š =53 )(   %  aÿè÷Ï$173533#&''6765#&''67&''6j5;;) 0 " 4   [ ¶69##7<' M    *    ÿç’Ï"-73533#&''665#&''6'''6122& 1`  D  µE0HP    ' ÿçƒÏ"-73533#&''665#&''6'''6,,, # ,T   =  µG/GQ    ' ! ÿê{Ð#/73533#&''655#&''6'&''6&'' 0&I   :  µA'*>A   +  ÿçyÐ'+7533'67#3''67&'767#'65535#@& #   8°  -+   <0 /9V@- ÿózÅ)73&''67&'767#3533#67'75#]   H*''.8.*Å $      i# ( ÿéŒl(.47&'#"''3255'67'67676767'&'''6q     !  2  K $     "     h…Ï !7&'7'63533#&'#5'67#+TY/00  &Ï   !!   gÿéôÃ73#3#3267#"&55#'667#|hh „%   !Ã2d # g:5,4 ÿéô¿"73#3#32667#"&55#'665#$¸¸ØE  $ ,- (?¿0a  h8/0 ÿèò 73#3#3267#"&55#'675#YY o#0 >2 I   O=5@ÿêòà 73#3#3267#"&55#'667#_yy˜0  & &Ã2c  f8.1ÿéòz 73#3#3267#"&55#'667#,¦¦ÙE " ) ))Cz. 4$   ë¡73533##"''3255#&';  ;  ‚H  B   ÿçœÃ73#3#67'5#'655#jj € %"Ã.`  pD- (<ˆÿéøÂ 73#3#3267#"&55#'665#šPP c   Â3e h=5-6ÿéŠÇ #04875#53#5'675#53#5'6'&'7&'3#5##5335#35#8)< O(;P G  1F'FFFF‘%_ #_ +  <j j&; ÿéò‹ 73#3#3267#"&55#'667#*ªª×E  # ,*(>‹=   C') OñÅ 73#3#33267#"&55#'665#a||—/!  'Å$K  Q/- '' ÿéÃ73#3#67'5#'655#__ v %!Ã,b  qD+ '; uïË73#3#3267#"&55#'67#.¢¢ÓH ( &$*? ?Ë    ÿéõp &*.26:7#3#;267##"&55#535#55#7533535#5#35#5#ÛGMM  ;!HH@@-?55;;86n;;86p$ 9  9 $ ck ;  |óË73#3#3267#"&55#'67#(°°ãX  ) ! F< CË     ÿëõ|"&*.273#3#32767#"&55##535#735#35#33535#335+¡JVV)0*=QDyy ==PB’==PB|( A  L  < % ÿçíÐ#<733533533##5##5##5#53#5##5#"''3255##5##5353:('(('(''³³¾  55IÏ:./28  !RR?QFÿéñÐ#<73533533533##5##5##5##5##5#"''3255##5##5353K¤ƒ˜  $$8° !++0;  &TTCTÿðóÏ'73533#3#535#3#735#3673#53&'eeeS»Ue±±‹‹ 7 =å> »6C7    ÿëÏ(73533#3#535#3#735#'7&'7767 100(d)1ff@@X3?  »6B N   ŠÿòóÅ 7#3#3#535#ðTKKWi99Å.S.Ó/:ÿí¬Ï(73533#3#535#3#735#7677'7&'E&%% R&OO++  1=" ·::0   ÿëÏ'73533#3#535#3#735#7677'7&' 0//'b(0cc== .9½8<6 LÿéëÈ !%7'6553'#3#5##5##535#35#35#x†&&8'sCCCCCCC…C= 4@\C1"ŒŒ%77UÿèïÈ !%7'6553'#3#5##5##535#35#35#$$6$nAAAAAAA†C< 2@`B0#Œ%88_ÿçìÈ !%7'6553'#3#5##5##535#35#35#‡x 2 e6666666†&@9 1=hB1 "ŒŒ%778ÿéçÈ !%7'6553'#3#5##5##535#35#35#i ‘--?,~OOOOOOO†C? 4A\B1 "ŒŒ%77WÿèïÇ !%7'6553'#3#5##5##535#35#35#|…&&9'p@@@@@@@…#E5 7>_B1 "‹‹%77ÿéåÇ "&7'66553'#3#5##5##535#35#35#; ¾??SC©rrrrrrr„"E4 2"_C0#‹ ‹%77 ÿé‘È !%7'6553'#3#5##5##535#35#35#/t0b5555555†)<7 .9kB1 "ŒŒ%77 ÿé‚Ç !%7'6553'#3#5##5##535#35#35#) k,X-------†471 )4uA1!!!"‹‹$89ÿèŽÐ,H73673#53&'&''67'6767767&'7&''67'6767767'2t"       N       Ъ '/-*?,   &/-*?+Cÿé‰Ê7#"''3255#'6555#35‰    &ÊÊ ?3& 7?a<*** ÿéöÏ)-73533#3#32767#"&55#'667#535#35#...& &A M4(.::¶G> C)# !!GM!y ôÆ 7&'''63#"''3267#'667#Í  P   ' Æ- / / %N7>JÿëôÇ "&*.7#'66553#3#3#535#535#735#33535#335ñ~ f)..77--*+C+ÇRC5 0$^!h<Fp ñÆ !%)-7#'6553#3#3#535#535#735#33535#335ïXK %]'  )  ÆD9+ -3OW    469ÿéðÇ "&*.7#'66553#3#3#535#535#735#33535#335ì… "k-22;ˆ;//,,G,ÇSE5 3#_!h<F4ÿéò­#'+73533#3#3##5#535#535#35#33535#335?LMMBBSSWWBBL00D/s00D/žXX249ÿéõÐ/37;7&'#673265#"''67&'#'66553'33#3#735#×  '-       N aV::88Ð 3"3!++)DG;. * R##$E# ÿè÷Ð)/37;7#67327#"''67&'#'66553&537&'3#3#735#ñD     b u$UUJJ$$¯5"'5")*%-4;H<. + T!  EJ& ÿèð™,0487#67327#"''67&'#'6553533&'73#3#735#ï?    " l} £TTNN**ƒ''"'-8.$ %+A "8UÿéöÐ-1597&'#673265#"''67&'#'6553533#3#735#à  !$     >NH0011Ð 03!+)#JH<- 05S##%E# ÿéòš,0487#67327#"''67&'#'6553533&'73#3#735#ì<    j| ¢UUKK''ƒ&*$ #$/:4 *7, !:SñÐ.26:7#673267#"''67&'#'6553&533&'73#3#735#î;      o¡^^RR11½  ###  $   & ÿéåc!',059733#"''255##5#'667'667#35#73535#735OT; =F   %RJ A@S=’CBT=cB     ^ÿéöÐ,0487&'#673265#"''67&'#'6553'33#3#735#â"    ;LF..//Ð /3!+3)$IH<- /6S##%E# ÿê’Ï'-1597#673265#"''67&'#'6553'37&'3#3#735#Ž      3E?''** ¬/7"$"4;H<- /6S### ?C%hÿé÷Ð-1597&'#673267#"''67&'#'6553'33#3#735#å      7ID,,.. Ð .5!)!'.@I:/ .8S##%E#h£Ï73533#3##535#35#h#¥***Sb*k/dÿéó•-1597#673265#"''67&'#'6553'33&'73#3#735#ð'     5F _(('' „&+  +)8/$ &*A  ; ! ÿêóÃ04873327#"'&'#3#7&'7&''275#535'25##5#¸  ¥ #;;*;P)":: F8)(È!&… H#  $H[%%%%lÿéôÃ048733265#"&547#'273#7&'7''75#53#335#lr a%'%  $# ÃE6$$ -$;-<) F#  &F(((WÿéóÇ#'+/37#5##53533#3#3##5#535#535#35#33535#335íj:::66DDGG66:$$5$Y$$5$Ç##)TT32^ÿéóÇ#'+/37#5##53533#3#3##5#535#535#35#33535#335îe67744AAAA336 3!T 3!Ç##)TT32FÿéóÇ#'+/37#5##53533#3#3##5#535#535#35#33535#335ìw@@@;;KKOO;;@((;(c((;(Ç##)TT32‚ôÇ#'+/37#5##53533#3#3##5#535#535#35#33535#335ôN''....''(>(Ç()!  N  N ,- ÿéÆ#'+/37#5##53533#3#3##5#535#535#35#33535#335O)))''))11'')(=(Æ""(SS43|ÿéôÆ#'+/37#5##53533#3#3##5#535#535#35#33535#335ñM)**))2233(())@)Æ"")TT33_ÿéôÏ1E73533#3#3##5#535#535#73#"''3265#'667#373#&''67#`MA     I<@:* *- ,6¾W?-% !'v  %)  "dÿéîÇ"&73#"''3255##53535#335#3#735#Û dAB..AA ÇUo  Yx‰UUC,B>ÿéïŸ#'+/37#5##53533#3#3##5#535#535#35#33535#335í¶IFFPPeeffPPI ==P===P=Ÿ--"  E  E & % _ÿèðÐ&*.373#3#3&''67&''67#5'635#35#67€^gcEO  #   EEEE  Ð M  B / ) A  ÿçó@767&'67'5'6   !" K) ! F@   )   ÿçòM767&'67'5'6†  " O!"  PM   /#  & pÿéñÈ#'+/37#5##53533#3#3##5#535#535#35#33535#335ïY211--7777--2-G-È#%)TT32ÿèñÈ#'+/37#5##53533#3#3##5#535#535#35#33535#335ë°HHHKKhhffKKH77K7‚77K7È6##6,VV20sÿéõ¿73#3267#"&55#'655#m  $!¿©   ¬SE, );S)ïÏ#'+73533#3#3##5#535#535#35#33535#335^^^MMffffNN^#;;O:‰;;O:à  I  I ) ) }ÿéóÄ73#3##5#535#„j.3300)ÄInnIN ñÈ#'+/37#5##53533#3#3##5#535#535#35#33535#335îy...99HHHH::.'':&`'':&È%%$  M  M ++ ÿé–Æ#'+/37#5##53533#3#3##5#535#535#35#33535#335–b4550088==0040M0Æ""(SS33sÿïíÎ!%+173533533#3#3#5#535#35#35#35#'67&'w*`^q%******  / µY6IY56'    lÿçõÉ 073#735#35#3#3#"''3265#'67#'67#'67#~ff@@@@!…PR  4 . %   ÉP0..F+7+"   dÿé¨Ï73#3#7#5'6356—$Ï %0-š{ÿéòÆ#'+/37#5##53533#3#3##5#535#535#35#33535#335ðN*++**2222****@*Æ"")SS33~íÐ/373733#5367#35#3#3#3##5#'735#535#735#‡'o')[%%&&1"55+77Â!  ".  }ÿéôÇ *73#735#35#35#3&'73#&''67#536‹[[666666  +! $ %!#'ÇtS23'   %'   €ÿéòÆ!737#53#33#537#35##5##535#‰!e1+r% 3555š5$$$IWW8'ƒÿéóÏ(,073533#3#3#535#535##"''3255##535#35#‰'++&&0p- '_  66666¾R]  %t 1|ÿèöË573#3#535#535'667#'7#53333#"''67&'ç 8 G*  !)"#  Ë6<<1e(3G#  -†ÿéóÐ$*73533#3#535#3533##"''3255#&'%%%+l.%F   F( ³N<  8  ÿçžÍ"JR73#35#535#53#563327#"&'&'67'5&'67'537537&''5&'= !!M$$0oD            "   Í\Yc8".  . + , r   #  !{ÿìî¾73#3#5##5'67#35#ƒk2C+ $'++¾% y ] +=£LOÿé¦Ï#'+/73533533##3#3##5#535#535#5#5#35#335P  !!##  7 ! »$;((;$OQÿéñÏ#'+/73533533##3#3##5#535#535#5#5#35#335U:&::EEHH::'j:'':(¼%<((<%OnÿéïÏ#'+/73533533##3#3##5#535#535#5#5#35#335o.00668800 W.1»$<((<$O„ÿéô”'+/736533#&''67#3##"''3255#3#735#Š)),  #p   M ::‡   'A > .€ÿéñŒ!%+17=7#3#3#3#"''3267#55#5#5#&''&'''67&'î%(   R+3 '#Œ  8 &a     kÿìì¤+1773#"''3267#'63533#&'#5'67#7'6'&'ŠW  O  $##   I8  ¤xk B..   1:# 5    ÿés¢#'+/73533533##3#3##5#535#535#5#5#35#335 !$$((''%%A! &–  4 ## 4 @ ÿêóÏ#'+/73533533##3#3##5#535#535#5#5#35#3357M772RRjjhhRR/7˜M#>>R>¼&=''=&OpÿèõÏ 7&''6&'3'67#«! ' !d$ NÏ &(' #.2&"'£ÿìõÏ73673267#"&55'7» ÏO c  ]sÿé¯Ï 7#5'6  Ï!¥{ /oÿèõÆ#7#533&''67&''6667#'7#Œ W #    ; &´2C%"?+ F,(3/AkÿéõÏ73533#3#&'#5'67#535#x133/%  ""+1©&&'44ba-$$1'wÿñîÇ 73#53535#35#35#á w 666666ÇÃÃA.o.o.a©Î7''67'6767677&'   J -#94  @% tÿéöÐ73#&'#5'67#535'6à *4,'  +44Ð (/0'%ls2 )4$bÿí¢Æ73#3#"''327#735#h:&(  )%&ÆK(F AM%žÿêóË7''6767&'Þ _2< Uo vC% gÿéõÏ"73#&''67#5353533655#335å 2 +(+ 0 2 +**§N000!+N((N--<{ÿéòÁ%73#3#"''3255#&''67##537#{w00   16Á"‹  t  1’¤" ÿèó~#'+/73533533##3#3##5#535#535#5#5#35#335,[,,9NNkkiiNN7,š[::L:v )  )  3 ™óÏ73533#&''655#""   "˜77)($5¢ÿôõÐ 73&'73#3#536'&'¦ K6 S&  £ H4=@4581_ÿì Ð73533#7#"''3255'675#b   ¤,,( Q  B 3pÿçõÏ048<@DHN735333##3#3#&''67&'7#5367#535#5#5;5#33535#33535#33567#|-- -/@L  (   1- -G-K1$ $Ä  .   .  ' D P  qÿïõÉ #'+/373#3#735#33535#3353#3#735#33535#3353#xzzmm-I-ayyoo.K.j„„É @& $ $ @' $ %ZÿçõÎ3Nhn73533&533&'73#67327#"''67&'#'67#7''67'6727677&'&''67'672777'&'i   2   #   F     H)]Z,   "   )N -%P cÿèôÊ 'DJPV\73#735#35#''67'6767677&'7676767&'7''67'67&''&'''67&'trrLLLL   2      : 0ÊO/-X    ! "    K  ‚ÿéëÉ 73#735##"''3255##535#35#†aa;;R  CCCCCÉ>>x  1&;~ÿèìÑ"(733#"''3255##5335#35#'&'7'6¬,   D*DDDD  [  Ñ=’  !@¬.Jt  uÿèôÐ8<@DHLP73533533#3#3#33##"''3255##5#53535#535#535#35#35#35#33535#35#{ 5-  E ,3-+E+ÀF  %%F-O6 ÿésÏ#'+/73533533##3#3##5#535#535#5#5#35#335 ##''++$$B $»$;((;$OÿóðÊ#733533#3#3#3#535#535#535#53\!BB</**=)f**=)ÏWWb5Iÿë Î%)-1573#3#3#7'75#535#535#535'235#33535#335’ ""R#""" - ÎS S ^5_êË$(,0473#3#3#3#535#535#535#535'235#33535#335Ø2;;0033<‹=330088-7$1P1Ë  J    J  T* ÿéñÎ73533#3267#"&55#'67#* .6B: ž00ˆ  Žv-)l ÿêŠÎ%)-1573#3#3#7'75#535#535#535'235#33535#335z 44,,--*s7//--55+4,E,ÎV V ^2LÿíôÏ&*.2673#3#3#3#535#535#535#535'235#33535#335Û  JJ<<@@J¨K??<.))<)e))<)ÏXXb4 ÿéîÌ%)-173#3#3#5##5'67#537#537'635#35#35#Þ.5`iŽˆt "3C3=Bf3ttttttÌ k 00 ÿéòÈ %73#735#35#35#'33##5#'67&'G™™ppppppBÁd]; 4 -o($ &&ÈqP--FyII  cÿòôÂ73#3#3#535335#p€2++6‘;ÂLL}}ªiÿêðÁ73#3#"''3267#5335#365#i‡>: j"7I&'Á*s(%bQnnX£Ð7''67'67676767&'•   M -#:6 @& uÿééà 7#5##535#35#35#éNNNNNNNÃÚÚ;)d)e*`ÿìõÏ&73#5#3267#"&55#5335#53533#3Ë  # -88CCˆ_3 9P?S ScÿêóÊ73##5#'66556é ,;g!4  EÊ .<3 5E9]ÿêóÏ#73&'7&'#3#"''3265#7#5'6‰ U 6 = B Ï'!+5 !G-3 $ ÿèñÎ &*.273#3#5##5367#'665566535#35#35#ß #)VWIq;O  t',%qqqqqqÎ  Œ Œ  J:7G=' P77 ÿéïÏ "&*73#''67&'3#5##5'635#35#35#v3– $0e k0ÏB: " ƒ p233fÿçõÄ73#3#67&'7&''67#vrrŒB%- 45 4Ä:F (/ ):mÿéìÏ7#5##5##535335#33535#335ì!"66""6!W""6!£†EE†,,1O ÿîóL#733533#3#3#3#535#535#535#53bHH@@FFVæUHH@@IILNN       ÿìôÏ26<@73533533#3#&'#327267##"&55'67#535#35#3&'#353=33B@% v+ @& '@A3G==b 9 b¸ . IG ?ÿìóÏ26<@73533533#3#&'#3267#"'"&55'67#535#35#3&'#35Q")"",* V.  7$  &-"6))P . C· 2  K G 3ÿñÎ15;?73533533#3#&'##3267#"&55'67#535#35#3&'##3>#'  I"+(  %&0## <&099t      & + 6ÿé¯Ï!%7#5'67#535#53533#3#&'35#335€ %))))&! 6$4KI"$FF5"""Dÿé±Ï"&73533#3#&'#5'67#535#35#335R&%%!!  !&"¹EGG!(EK$$$ ÿé Ï &73533#3##5'67#535#35#335&':6622 $(0:1 ¹EcL#'EI!!!>Bÿé¨Å 73#735#35#35#'67&'NPP****** <  Å¢rNN6 ÿéÆ 73#735#35#35#'67&'jjBBBBBBE Æ£rNN5  Lÿë³Å 73#735#35#35#&'''6XMM''''''* Å¡qMN6 [ÿé­Å 73#735#35#35#'67&'dDD 0  Å¢rNN6 ÿèëÐ&*.373#3#3&''67&''67#5'635#35#67F‹” Ž`j /:(&6 /#(! "ppppÐL    A + ) @ LÿèôÐ(,0573#3#3&'5'67&''67#5'635#35#67xit sOZ  )"   PPPP  Ð L I  / ) ? \ÿèöÐ(,0573#3#3&''67&''67#5'635#35#67ƒ_ghGR  $  GGGG  Ð L    E  .* ? fÿèôÐ(,0573#3#3&''67&''67#5'635#35#67„[ccBN  "   BBBB  ÐL   E0 * @ Sÿçô§%)-273#3&''67&''67#5'67335#35#67#{jF T ' )    flKKKK&D… A     : ( # E iÿèôÐ'+/473#3#3&''67&''67#5'635#35#67ŠW_]BM  !   ????  Ð  L   @/ * @ nÿêòÊ '73#735#73#735#3#3#3#"''3265#7#x44+55Jhh„PL  S ÊB B ?4 %nÿéóÐ+/3736733#5367#35#3#3#3##5#'7#535#735#35{5€'+&.o'**445 ;4GG&¾  " (4BcÿèóÍ!%)-73#3#5##537#'66556735#35#35#ç +/0(9#, G999999ÍŒŒJ: 9H<*K99pÿéñÆ"&73#"''3255##53535#335#3#735#à  ];>))==ÆTr  ]x‰TTC,D=vÿéòÏ!/3773673267#"&5'67'5333#5##53635#35#³    + $ 9EEEEEÏ!   "$ b 0 o o 5BfÿíîÏ73533#67'7&''75#75##5#n222  2;:2d  ¢--`6" 9<<<<tÿêòÊ '73#735#73#735#3#3#3#"''3265#7#|22)33Iee ~MI  Q ÊB B ?4 %mÿêôÅ48<73&''67&'#367&'3533#7'7&''275#735#335xm     ,/.. .<//Å"     X7  rÿèóÐ%)-273#3#3&''67&''67#5'635#35#67’OW V=G      <<<<  Ð L    > . * @ aÿèòÑ4<@DHLPT73#33##3#"''3255##5##535#535#'6553&'5#35#5353535#33535#335¹0.& &*  )%%.9 ..%7<)A)Ñ   T  !i  D2 4=]( '   D )  ÿèôÐ!57&'#'67#53673#&'&'3533#3##5#535#y  2+: 1=Ž  1?, 3|  #+..ZZXX+ÐJ/!w g//RÿéõÏ!57&'#'67#53673#&'&'3533#3##5#535#¤  !# X  & #U  8877ÏI+t k..`ÿèôÎ373673#3#3#535#535#53&'3533#&''67#• :11==119!:?5&) 9 77Î   Œ #  (!LÿèîÎ473673#3#3#535#535#53&'36533#&''67#ƒ  !@66CšC66?' @F;)/ B ==Î   Œ $ *$nÿèóÎ373673#3#3#535#535#53&'36533#&''67#3++6~5++227/!% 0 10Î   Œ #  &!eÿèôÎ473673#3#3#535#535#53&'36533#&''67#–  8//;ˆ://7!8<6&' 7 56Î   Œ #  % ÿéóÐ!%)7&'73#3#5##5367#53635#35#35#X e GnV;bˆ aÐ   ˜˜d>>OíÏ#73673#3#537#53&'35#35#35#x " )H?-A&\\\\\\Ï   {{ W66vÿéóÏ $(7&'73#3#5##5367#53635#35#35#”  E &6/A"2B /AAAAAAÏ — ™ c?@†ìÐ#73&'73673#3#537#35#35#35#† )$V)000000²  {{587Zÿèï.4873673#3#5##535#53&'5#35#"&55#'65#333535#‡$ !0(b*1%8(b  Lbbbœ  x x 5>  K ÿë\%7#"''32654''67&'''67&'76K         *.     KóÑ*0473'73673#3#5347#5#35#"&55#'67#333535#>  6 ?Q?Â<M‚%;, ) "q&»  TT .   4  ÿçõG!:>B73533#27&'7&''275#735#33573533#67&'7''275#735#335)(( '2)'*++  (3**; - , PìÐ*0473673#3#535#53&'5#35#"&55#'67#333535#-6,ˆ(2&9&b IbbbÐ   zz 6E  VÿéñÑ/7;73673#3#5##535#53&'5#35#"&55#'67#3327535#Y 1  BRA™=MBF<™ ' )l ™™™Ñ  ¢¢ @X !($l‹öÌ?73#672'#"''32655'67&'5'67&''67#53&'736Ô*         # Ì           bSôÐ?73673#67&'#"''32655'67&#'67&''67#53&'ˆ  $=  (* # #"#6Ð        P îÐ<73673#67&'#"''3265'67&''67&''67#53&'€   .H  , 0#$ ) !;/ Ï       ƒôÌ?73#672'#"''32654''67&''67&''67#53&'736Ð-   (   !  &  Ì   (         a5ôÏ?73673#67&'#"''3265'674''67&''67#53&'‹  &?      %* &4%Ï   " (      0_È7#"''3254'7##5_  $È+  ,‡˜ ÿèbÍ73&'73#3#"''32765#'665##)&  ­ %g! BO,>PL^Ç7#"''3254'7##5^   !Ç  "k{ ÿékÆ73#7#5'675#35#35#75# ^   Æ€93 R Y!DèÏ73#3#3##5#'6G—zeejj $Ïa #ÿéÅI73#"''3267#'67#7Ž  9 D? +ID'7( ÿé™Ï&,73533#3#"''3255##5'675##535#&'9993   *39[ ¹; %“N*#?Rp ÿé˜Ï+073533#3#3#"''3255##5##535#53'65#)00::4    2= *ÊG  0^^J] " ÿ雯 &7'6553'35#"''3255##5##53536uaM &ƒ>9 .%O  <; 3 B 8!7 'GÿéïÑ *73&'73#3#735##5##53##"''3255#M@Dš ……__ƒ‚t. 4½  3.1/#+  (gÿêòÐ *73#53&'3#735##5##53##"''3255#±9ˆ8%jjDDgeY   (Ð *3/1 /$*  'KÿéðÑ *73&'73#3#735##5##53##"''3255#R=B• [[€m&   3½  3/1 /#*  ' ÿéeÆ73#7#5'675#35#35#75# X   Æ93 R Y!ÿêða7#5'75#53#'35#35#5#î'' Í zffffffIC7 ) ' mÿéôÏ'-733#3#535#5335#35##5##5'66&'§++:‡:,,-(@( Ï22#UG7;K'%ÿèí§ *73#53&'3#735##5##53##"''3255#ƒ[Ñ`8““mm£´!—>  F§ (* %,, kÿêòÐ )73#53'3#735##5##53##"''3255#³8„5$ff@@b^S   %Ð  *3.2!0%)  &VÿéóÏ 173#53&'3#735##5##53267#"&55#'665¤<Š9#mmGGljm   % Ï ,0,01"9  ,#&EÿéóÏ 173#53&'3#735##5##53267#"&55#'665A”=%ttNNuts   ( Ï +0,0 1"9  ,"& ÿéôÒ &*.73#53&'3##"''3255#53&'7363#735#€_Ô^G B(   ¬<  7}hh@@Ò  *  e  a>AÿéïÐ .4:73#53&'3#3##"''3255#535#53&'736'67&'„VÉ\B 8b[[   [[e8  > V & !y%$Ð  * 5 2 \ UÿêòÏ .4:73#53&'3#3##"''3255#535#53&'736'67&'¬4ƒ8) (;66   99=$  < ]Ï  +5  1\  4ìÑ 673#53&'3673#5#3#"''3255##5##535##53&'„SÆ[ 5=OG   55HR<Ñ -#  //*;-aÿéïÑ 473&'73##5##53&'7367533##'2655##5##5f4 :„‰h#   +2 ¹  #((  2: &XX?PRÿéóÑ !%)73&'73#3673#53&'#5##535#35#X>A–! &  '¡( vTTTTT¹    An n(>OÿéžÑ #'73&'73#3673#53&'##55#35RJOA32!!¹   Ca m(Vÿéò’!%)73#3#53&'#53&'367##5##535#35#©<+œ(>#9SQQQQQ’  4 (UU .ÿé{Ñ #'73&'73#3673#53&'##55#35(*g mUFG33¹   Ca m(Iÿéó¦!%)73&'73#3#53&'#367##5##535#35#S>B.ª+6'?X[[[[[      .[ [!/ÿèä’!%)73#3#53&'#53&'67##5##535#35#ƒ>8Ê3 ?'Jc_____’   6 S S ' ÿéë¥!%)73#3#53&'#53&'367##5##535#35#ƒT!7×6 \? Yxzzzzz¥    : ,^^#4 ÿéóÑ !%)73#53&'3673#53&'#5##535#35#€ZÊY 7  Bæ@‘{{{{{Ñ %   Av v,Dÿé€Ñ !%)73&'73#3673#53&'#5##535#35#),j rX77777¹   Bn n(>zÿêîÎ"73#"''3267'6765#'6&'šJ  $+2"?   Î ˆ!.( #ƒÿèðÄ73#"''3267#'67##5##535#…k  &" f;;;ÄH0=1`j jK9 ÿélÑ  $(73&'73#3673#53&'#5##535#35#""Y _L+++++¹   Cm m'>ÿéxÑ !%)73&'73#3673#53&'#5##535#35#&(c jR33333¹   Cm m'>uÿìôÑ %73#53635#35#3#3#3#535#535#¨ 'j"CCCCu0**47**1Ñ]]-97yÿèõÈ #73#735#3#735#35#35#'67&'Š\\66hhBBBBBB  ?È41qP//%    oÿéóÏ*.373533533##5##5#3#&''67#53535335#335r$$s/ % % ) ) 4 +++¸);!#%;;)#)‰ÿéóÑ !%)73&'73#3673#53&'#5##535#35#Œ'(c jR11111¹   Cm m'>ÿéñm!%)73#3#53&'#53&'365##53#5'35#5#S$?á?L ;J¢~~~~~m 'X??!  XïÒ<R7#''53&'73535&'#6''67'67'6767676767#"''3254'7##5£ '''&7     ¦ Ã7  N  $2    .  Xg qÿïòÒ !%)73#53&'3#735#3#735#3#735#35#3#¸38+qqKK==&hhCCCCÒ%T8 " :?& # $pÿéõÏ159=OT73533533##5##5#'673&'73#3#3#3#7#35#5##53&''67&67#t## *,((''+n1 p   & 9 Á ?    Q   )     aÿéõÏ048<NS73533533##5##5#'673'73#3#3#3#7#35#5##53&''67&67#g##""##  "14--,,6}5$$$$$z  !& !% 7 Á 9     R   +     ÿégÐ!'-7#3#"''32767#53673#"''326'&''6M)C   A  ¥YL&{ F %  g   ÿé€Ç(9>DHL767#533'67###'3255#'67#53&'#"''3255##5&'''63#735#/ Kc$  &3Z    KG    33µ       Fd  Pkz  , ~ÿéóÑ !%)73&'73#3673#53&'#5##535#35#‚,,m uZ88888¹   Bm m'>~ÿèöÏ+1773533#3'67#535#&'36533#'67#7&'&'$%%- X,$ 4+0. #.  N ·  1   6*('=   <yÿïòÎ!%+173533533#3#3#5#535#35#35#35#'67&'€*^]o $******   ) µY6IY56)   qÿèõÐ!>DJQ767&'7''667&'7''6'67'7#&''674''6'6'67'6¡  %)2    7    2" #%6 0.*) @Ð         1   JÿèôÏ*0573533533##5##5#3#&''67#5353533655#335Q#6##6#?+3< 3D66#$6(·$;)7+ ;;##)Uÿè÷Ï*.273533533##5##5#3#&''67#53535335#335b00ƒB!% !%2 5;111!¸#<$'" <<)))`ÿèõÏ*.373533533##5##5#3#&''67#53535335#335d00€;% '. - 9 222¸#<#(& <<)$)kÿèõÏ*.273533533##5##5#'67#53535333#&'35#335m))F + ' 6 +0 0 #<+·y)!<<%E+++ ÿêók+1773533533##5##5#3#&'#'67#5353533655#335 ;I::I;Æ!Z?IQ@ZFE13E? 94* "B ;2/E JÏ 7.# #,@ G'   PÿèòÏ%+173#3#'6553&'#53&'367#'6'6'6§?!y"=":; 5 0) ? 8/ +B EÏ 7.# #,@ G'    ÿèñÐ/5;73#&''67&'767#53'#'67#'6655'6'6ˆ_+!;&2 ‘[~21F D'} « 4R VB 7h lÐ      W   2(! :+(  ÿé„Ð %+1773#53&'67''67&'#'655'6'6'6J+m,    bV O  & .)! 80Ð %   !0- #.2     ~ÿèõÏ%+173#3#'6553&'#53&'367#'6'6'6½, X   *() &" .(! / 1Ï 8-$ %*A G)   VÿèóÏ /7&''63#3#735#36533533##5#'67#£' &"% ,CCuuNN#$(&&*)"Ï   5< 33/]ÿèôÏ /7&''63#3#735#36533533##5#'67#¨ !" ""# )??rrLL""&$$((!Ï   5< 44/ ÿçóÑ37&'#5'63&'3#735#36533533##5#'67#€08 ` OME››ss;C<??? 5)>Ñ    46B //+EÿèóÏ /7&''63#3#735#36533533##5#'67#™&) )(* 0JJZZ%)/++1-$'Ï   5< 33/kÿèôÏ 07&''63#3#735#36533533##5#'67#¯  %88eeAA !$Ï   5<33/tÿèôÏ 07&''63#3#735#36533533##5#'67#±  #11aa==#Ï    5<33.FÿéóÐ '-3733##5#5333##5##5#53533#5##53'67&'–::;;$%%7%%7&[6V L!#"Ð*O=?QQ  PÿéóÐ '-3733##5#5333##5##5#53533#5##53'67&'œ4444&55R3S J Ð*O=>P P  kÿéòÏ '/573533##5#3533533##5##5##5##53'66&'|,,,,**o>+"!  ¼!N>?O)) "  ÿéóÏ#+3973533#325#"&'#7&'3#3##5##53'66&'Œ:9 )¼  ›iillmG-",(¬#K,0XY4 7  G68I)%  [ÿéóÐ!%-5;73&533#3265#"'&'#7&'3#3##5##53'66&'_R** R}d??CCF' «%H2 ,*m0  6  F66F&"   RÿèòÎ,73#35#535#53#5##5673#35#535'2ß !GG,!!!4k +EED΂*… |  (‚ZÿèóÎ,73#35#535#53#5##5673#35#535'2à  DD*2f)BB@΂*… |  (‚cÿèóË+73#35#535#53##5#5673#35#535'2à BB&0:9&<<2<Ëed//_ eÿé…Î*73#35#535#53##5673#35#535'2u 22&] 112Î(z |  ' ÿéóÏ+735'673#35#535#53#5##5673#35# hL VV %*jjB444I–% 33@h• }# „ }  $}rÿèòÎ,73#35#535#53#5##5673#35#535'2á 77 *T !665΂)… |  (‚kÿèòÎ,73#35#535#53#5##5673#35#535'2á ::"+X #::9΂)… |  )‚ ÿéóÏ-3;?73533#3&533#67327#"''67&'#535#7&'#5##535#/..BA@   $ 8/´  ::::²"*,  '#5'"*$ \X g=+GÿêôÏ/5;?73533#3&'33#673267#"''67&'#535#7&'##535#Q,0/     i(†  (8$$²!*+ # $!%," ZNa=,\ÿéòÐ,28<73533#3'33#673265#"''67&'#535#7&'##535#`&&&  Z x  &,²M+".$"3%&# YM`<+bÿæòÎ'-733#3#535#5335#35##5##5'66&'¢11=@112/K1"""Î55%SK;?O('WÿéðÏ'-733#3#535#5335#35##5##5'66&'š22C™C112/K.!"!Ï22#VF6:J&&OÿçòÏ'-733#3#535#5335#35##5##5'66&'—55H£H44"""5""1Q2#%"Ï22"UF6:J'&ÿéò›'-733#3#535#5335#35##5##5'66&'wOOhäiOO<<9WÿéíÇ #73#3#735##5##535#33535#35#W–– YYvk,,?,k,,?,,Ç;:o o*?!ÿéॠ#73#3#735##5##535#33535#35#2””ll•—BBUB—BBUBB¥ 0._ _"1ÿé’Ç #73#3#735##5##535#33535#35#~~nnHHcY##6#Y##6##Ç9:o o)? ÿéàš #73#3#735##5##535#33535#35#0ŸŸ••ll–˜CCUC˜CCUCCš -+Z Z /qÿéîÇ #73#3#735##5##535#33535#35#q||llFFbW""4#W""4##Ç9:o o*?aÿêëÇ #73#3#735##5##535#33535#35#aŠŠ vvPPmb((:(b((:((Ç9:n n*?ÿêí™ #73#3#735##5##535#33535#35#ÛÛ¢¢||ž™CCVC™CCVCC™ ,)Y Z -TæÉ 73#3#735#3#735#33535#335T’’ zzSS++>+i++>+É 0-V36ÿéyÇ #73#3#735##5##535#33535#35#ffYY33MA*A*Ç9:o o)?FÿéñÊ (73#735#35#5#53#3#33#"&''6httNNNN C’ÏvY!!#.<€ÿïõÐ 73#7&'''6'66±-2 KE %*Њj"++#4 + F) ÿéôÊ (73#735#35#3#3#33#"&''675#1žžxxxx3ÞdWW,">A+fÊ[674".  E ÿèô~ '73#735#35#3#3#33#"&''675#1¢¢zzzz1ÛdWW,"B> 'c~?& " #     $qöÈ '73#735#35#3#3#33#"&''675#‰]]7777w3,, #%   1ÈN/,+  %6yÿêõÉ (73#735#35#5#53#3#33#"&''6‹\\7777 .n-$$ # É[6:OB"{ñÉ #73#3#537#35#35#35#'67&'q1,` ,<<<<<< C Éxx744'    mÿëóÉ (73#735#35#5#53#3#33#"&''6aa9999 2t.%% #%  É[6:NB ! ÿé†É 073#735#35#3#3#"''3265#'67#'67#'67#\\6666!xHL  -) "   ÉP0..F+6*!  ÿéiÏ7#5##5##535335#33535#335i!""3"¢u QQv--+GEÿçðÉ 273#735#35#3#3#"''32765#'67#'67#'67#^zzUUUU&¦gf   4 ;/ '  )ÉP0..E '. +" H7óÉ /73#735#35#3#3#"''3265#'67#'67#'67#^{{VVVV'ªli   $ ) #   )É7!)     Eÿêñž 073#735#35#3#3#"''3265#'67#'67#'67#^[[[[$¥or   & !/ ( "ž?' " $8(" J+ôÉ /73#735#35#3#3#"''3265#'67#'67#'67#`WWWW$¤dh   ( ", $ )É<%  !*     Jÿçð‘ 073#735#35#3#3#"''3265#'67#'67#'67#bzzTTTT'¢eh  A 9- & &‘?&  #2 %    ÿìL¢ 7&'&''6*  ( ¢  "  $ &$ #\ÿåô› /73#735#35#3#3#"''3265#'67#'67#'67#vmmIIII#]\  . (' ! ›A' # $ 8*    N íÉ 073#735#35#3#3#"''3265#'67#'67#'67#duuOOOO)Ÿ`a   < 4* " *ÉC) $ &; ,"  ’ÿéõÈ .73#735#35#3#3#"''3267'67#'67#'67#£HH$$$$_66   & )     ÈO//.G"( )   _ÿè–È7##'32654'7##5–  È? @ÑàƒÿêõÈ /73#735#35#3#3#"''3265#'67#'67#'67#–RR....!nAE  * &    ÈO//.G-6*" ÿèñm#)/5;73#3#3#"''3267#5363535'67&''&'7&'Vp‘±±§   ¦"ž j`  m1  U6      HÿìóÄ *0673#735#33535#335&'33267#"&57&'''6^$$7#Z$$7#%    ^  kÄpAL6 : 9  # ^§Ï73533#3##535#35#^+¥***Sb*k/pÿéëÊ $(,73#735#33535#335#"''3255##535#35#p{{""4!U""4!  KKKKKÊU230g  (~$4*6׎ 73#735#33535#335*­­::M:‡::M:ŽX46 SÿìöÄ )/573#735#33535#335&'33267#"&57&'''6g||!!4"V!!4"%    Y  f  ÄpAL7 : 9 !  ÿêõÅ .473#735#33535#335&'&''33267#"&5''6Šaa(=(  -  4 ÅrA P 9 ;    ># qÿìòÄ )/573#735#33535#335&'33267#"&57&'''6~ff)@)    H  O  ÄpAL7:  9 " +/Ñ‚ 73#735#33535#335+¦¦99K9„99K9‚S11 ÿìŽÄ )/573#735#33535#335&'33267#"&57&'''6ee(@(     J R  ÄpAL7;  : !  ‰öÆ (.473#735#33535#335&'33267#"&57&'''6•VV"5"   ?   FÆ]6?,  4  5aÿë§Í73#&'#5'67#535'6      Í) a[#+$QÿèèÉ $(,73#735#33535#335#"''3255##535#35#Q——00A0q00A0   bbbbbÉT231g  )#4kÿèíÉ $(,73#735#33535#335#"''3255##535#35#k‚‚%%7%\%%7% OOOOOÉT231g  )#4ÿèàÈ $(,73#735#33535#335#"''3255##535#35#ÂÂCCWCšCCWC   „„„„„ÈV320e  '}"1 mÿëöÆ!%)-159=73#3#3267#"&55##535#735#33535#33535#33535#335qr/77  #51 2P 2S##5%Z##5%ÆOQ  `/1Q3 nÿëöÇ $(,048<73#3#3267#"&55##535#735#33535#33535#33535#335tm.55 ( !3-/L/P!!3#V!!3#ÇPR   _0/P2 ÿç÷j#'+/37;?73#3#336767#"&55##535#735#33535#33535#33535#335'±OVV 8,CUN;;O<‹;;O<“CCWB™CCWBj2 2  8 1ìÔ 7#5##53&'7'67&'ê¯a' #3 8J-,-.Å     HÿéõÈ )73#735#35#35#3&'73#&''67#536`~~VVVVVV!# A3 7; 2:@ÈuR22'  )+  dÿéõÈ '73#735#35#35#3'73#&''67#53rppHHHHHH  7+ / / & 27ÈuR22'  &(  ÿêŽÈ *73#735#35#35#'67#53673&'73#&iiCCCCCC( $ ,14 ÈvS12^"     9ÿêÅs#)73#3#&''67#537#735#35#35#&'#Gn > #  -7-KKKKKKHsL   5   AÿôÁŸ!%)73#3'73#&''67#5367#735#35#35#Ob( 6   )1'@@@@@@Ÿ^    B & &  ÿêyÇ )73#735#35#35#'67#53673'73#&YY333333 # #' . ÇtR34]#     lÿèòÇ 673#3##'3255##5##5##5367#3653#&''67#p€77 +53<6) '% % /Ç ? +8888BS z  ! &(  $[ÿèõÈ 573#3##'2655##5##5##5367#3653#&''67#e=<  /;8A:+ -. -2È > )7777AS y  ! &)  "IÿéóÈ473#3##'2655##5##5##537#3673#&''67#RšCC  4B?IA2 4& 29È> )7777ASy   ( "Pÿè÷È"773#3#"''3255##5##5##5367#3653#&''67#X™AB 3D7H=,29 42È @ +8888BT x  " .1  'TÿéñÈ&*.473#3#"''3265#&''67#'67#735#35#33#mtNg + & NNNNI\ÈVT>    41a ÿêòÅ#73#3#5##537#35#35#35#35#35# åo jžH b#%%y%%A......Ū ª©xxxIJlÿéòÅ#73#3#5##537#35#35#35#35#35#l†:=W,8F#Ŭ¬©yyyKKUÿéïÅ#73#3#5##537#35#35#35#35#35#UšC Ff2 CQ)Ŭ¬¨xxxJJ[ÿéöÏ ">7#5##53&'73'67#&''67#3267#"&553#"''325ïc=A$< 1   g   ;  µ$%  ) j)S  "e  zE  fÿéöÐ !=73#5##53&'3'67#&''67#3267#"&553#"''325®6\6#8 . a 7 Ð $% > k(!Q  !e  zE  GÿéòÅ#73#3#5##537#35#35#35#35#35#G«MMt;JY,Å­­¨xxxJJhÿéòÅ#73#3#5##537#35#35#35#35#35#hŠ<?[.:H$Ŭ¬¨xxxJKKóÇ 73#3#537#35#335335#3#3åj dÅL f %%77$67777Ç `` Z> >>   ÿê‚Å#73#3#5##537#35#35#35#35#35# u25F$/ : Ū ªªyyyKK ÿè„Ð,273533#3#3#3''67&''67#535#535#67#+..&&+:;"   "1''+ ,»"   s  ÿéyÐ#0HN73533#&'#5'675#&''6'&''63&''67&''667#+((  +J   6  -     !½.   ,     3     sÿîóÏCGKO_7335#535#535#53533#3#3#35335#535#535#53533#3#3#353#3#3#735#3673#53&'{     pvvff@@!€ÇQ   YY Ra  ." ~ÿéðÑ J73&'73#&''67&'763353#3#"''3255#67'7''67##537#€--pF    <A*5  ' )'½     11B I  3   Qb ÿçóÏ&2JP73533#&'#5'675#&''67&''63&''67&''667#bee0: 2.#= A(b+    “  AC  "$70 76? ¿(!  #(    !   1   !   ZêÔ"&<I7#3#'6553&'75##5#35#3353353673267#"&5'67'533êH;°U *+++;*+;  % D!(=È # -       %  ÿæ€_!%2?7#3#'6553&'75##5#35#33533567'533#67'533€"W +   ?   S #,   -   '   ' {ÿæö_"&;G7#3#'6553&'75##5#35#3353353673267#"&5'7'533ð!W  ,       S #,        ' ƒÿéòÏ!73533#3#67&'7&''67#535#Š%%%08 $(!*%£,,,6 #)  4,hÿéóÏ!73533#3#67&'7&''67#535#t000;H#&  05.<0¤+++6  %* 4+ ÿóxÏ73533#3##535#35# ,++"G%,33¢---To-m.YÿéöÎ+1597373#3#3#&'#5##5'67#537#537#3&'#35#35#f5>A>CV  = )"(1E+====»  U WX 0:FÿéôÎ.48<73673#3#3#&'#5##5'67#5367#5367#3&'#35#35#S:CFDKd$ H +%,6 P6HHHH»    X W  X .: ÿéñ¤.48<73673#3#3#&'#5##5'67#5367#5367#3&'35#35#Sejhqˆ+ o *=7@M>mdoooo˜  H I 8,.iÿéöÎ*04873673#3#3#&'#5##5'67#537#537#3&'#35#35#t/365:M  4  %#,<$4444»  U TX 0: ÿçõ=26:@73533#''275#735#335&'#73533#''275#735#335&'#2)( (2)' 5*+ (3** 6+   ' 6 *   &  BõÐ(-157373#3#3#&'#5'67#537#5367#3'#35#35#Udjbiƒ*  /A3;N8hMjjjjÅ   /(  :  eÿéöÎ*0487373#3#3#&'#5##5'67#537#537#3&'#35#35#p1698=P  7  &#->&7777» V TX 0: ÿèói,26:7373#3#3#&'#5##5'67#537#5367#3&'35#35#Vbgdk€.!e /C;DPD]\eeeeb    .,  ) ÿéxÏ"(,27#5'67#535#53533#3#&'35#&'735'6L (,,,,)) 5 %  &=I('PP52  22  ÿé€Ð "*.7&''63#'6553&'35#3#5##535#K   $! J(777"""Ð  K.$ %,B %  C D'[ÿé÷Ï%)-73533533##3#&'#5'67#535#5#35#5#e;&?0 $ $/<');;;;·K")EC%"K:aÿæõÏ%)-73533533##3#&'#5'67#535#5#35#5#g9%=. ! -:''9999·K#)HD%#K:NÿæôÏ%)-73533533##3#&'#5'67#535#5#35#5#Y?(C4 $( &3B*+????·K$*JI("K:^ÿêóÑ ,73'73#3#735#33##"''3255#53567#^<A’ zzTT‚BB  @@a½ 43   SÿòôÏ!%73533#&'#5'67##55#353#Y>@/%%# !.jWDDp››³1. :VV#0ZÿòôÏ!%73533#&'#5'67##55#353#`;<,$#! -ydQ??i‘‘³/,;VV#/JÿòöÏ!%73533#&'#5'67##55#353#SAG6((' $1…r_LLy¤¤³0/!;VV#0fÿñôÏ!%73533#&'#5'67##55#353#k77(  )q^K99_‡‡³.+;VV#1xÿóõÏ!%73533#&'#5'67##55#353#~./#  #dTB00Suu²,);VV#0ÿç~ÍDLU73#35#535#53#5633265#"'&'67'5&'67'53753745&'77&5&6 ?*`4        . &  Í [Wa,- )! / . , t  " ! !  fÿéöÏ-1573533#3673#3&'7&'#5##5'67#535#35#35#y  ,:  ; &E3 ;;;;¶!  l ^tBoÿé÷Ä-3733#67&'#"''3255'675#535#535#&'‚Z6     !:ZFFH  ÄM   "*  /W NÿäöÈ %73#735#3#7#5'75#35#35#675#knnHH,›5=*HHHH( HÈ<6V#!^6:ÿéëÈ %73#735#3#7#5'75#35#35#675#0žžxx+Ì b=,tttt0DtÈ:7Zb6=JÿäöÇ %73#735#3#7#5'75#35#35#675#gqqKK,ž 7>+JJJJ("JÇ:7W#!_7:ÿæò  $73#735#3#7#5'75#35#35#75#-¥¥€€+Ö!O\-}}}}}} ,*GN,0 rÿäöÇ!&73#735#3#7#5'75#35#35#675#†[[55$y ).#55555Ç:7W#!_7:ÿçíÐ"(.733#5'66367#35#35#35#'67&'ZS ,¥ M H }}}}}}!/+](' ()Ð r% /57*    fÿé÷Ï &,733#5'6367#35#35#35#&'''6’5$r + )LLLLLLB * Ï }q 256'    ÿèð¨!'-733#5'6367#35#35#35#&'''6bK,§ 6LC [)( (*1"/-¨j_%,,#   GÿèðÏ %+733#5'6367#35#35#35#'67&'wG"  < ; \\\\\\! N Ï~s )67(     CÿéóÏ  $(,73#7&'''6#5##5'6735#35#35#–=  S‚W Q5 WWWWWWÏB8  "< o& #32 ÿéê› "&*73#7&'''6#5##5'6735#35#35#uF\!Ÿ|!k? "+-||||||›-%   *k _  ' ' RÿéõÍ@733&'76767&'#3#&''67#535#5'67&'767#a: C      79(!$+ 8 25!   )Å( ,       " !"   DÿéóÍ@733&'76767&'#3#&''67#535#5'67&'767#T> J   #<>+%"(1 = 69$    ,Å'-       "!"   ZÿèõÍ@733&'76767&'#3#&''67#535#5'67&'767#i7 @      45" $ - /1   &Å' ,      " #"     ÿçó¢B733&'76767&'#3#&''67#5365#5'67&'767#] C  !(GK60 33DI AF#    F—      !  Wÿèõ%9?76767&''3'67&'767#3#3#'67#5365#&'¹     5 E=   )"A>B6 * .37   = '   2 ! . ÿöU™ 7#5##535#35#35#U™ž £-HH4ÿçÑw@733&'76767&'#3#&''67#537#5'67&'767#:< G       "7;"  " *-3!   )q             5ÿþÌ A733&'76767&'#3#&''67#5367#'67&'767#D3 /    %*   (.  "š              gÿéõÍ?733&'76767&'#3#&''67#535#5'67&'767#u3 :     ./!$ -+-    "Å) -    """   xÿéîÆ"&73#"''3255##53535#335#3#735#à  P 84%%66ÆTq  [x‰TTB+D<SÿêôÅ6:>73&''67&'#3367&'3533#7&'7&''275#735#335^# &' &   '1888! 8K&!8%%8%Å      X7 ;ÿêðÅ6:>73&''67&'#367&'3533#67&'7&''275#735#335LŒ' * %1 / *7=>>>T*$=**=*Å     X7  `ÿêôÅ6:>73&''67&'#367&'3533#7&'7&''275#735#335iz "$ #  #-3332C#3 4 Å!      X7 Dÿèô›37;7#53&''67&67#7&'3533#7'7&''75#75##5#` '3 /$)R  :=<<- EPP=x))Š    ?0‡óÇ59=73&''67&'#3267&'3533#7'7&'#'275#735#335a     #$$ (.#$Ç    C/   ÿèób159?7&''67&'#5#3267#&3533#&''35#735#335&'7#Ú'<-,A. Q &6 RPP ]miP<DT73#"''3267#''67''67&'767#'667#&'&'''6733267#"&5{`           -  C  d    Ï WA    U   # 0  Aÿìôž-39KQW73#"''3265#&''67&''67'767#'6#6&''3326767#"&57&'''6jm           S    "n  wž G,      <  $  )  1Þ¤*073#"''265#''67''67'767#'667#7˜       ' [ ¤ J/      ?ÿê‰Ð7'6#5'6v   !Ð , zb  ÿéõš$(,735'673#&'3#5##535'67#35#35#e"# l8 5cI5 ?#II#< -'M/v #VV#F, ÿéóÍ#'+73#&'3#5##535'67#535'235#35#Ð %-eO#2 6)J~H#7 5"RbHU;~~~~Í%7l l1!—=sÿçõÆ $7'6553'#33673267#"&5™gAA5 !  €F4 4A_F4"*  - &  pÿîôË"(.7&'36732765#"&55'67''67&'¢ +   k  Ë-K3I\D"  L,%"( & 'xÿêóÏ/733#3#5##535#533567#533##"''3255#«,,3R2++38 6N 11  8Ï""„ & #oÿìöÏ'159=A73673#&'#3265#"&55##5'67#3533&'#35#33535#335y$="'      &;&² J   W!)9@qÿéôÐ59=AEI73673#33##&'#5##5'67#535#535#535#53&'5#3533535335–  #'      !!//Ï   %%6II. <% bõÒ'+/373#&'#5##5'67#535'25##3&'35#35#ËIcK"2 v 4"JaFOcDvvvvÒ  +/  4    ÿè}c"&*.73#'#5##5'67#535'235&'35#35#k/' 3 /(0 !3333c   ;8   3   ! €ÿèöc!%)-7#5##5'67#535'273#'3&3535#35#æ3  /((/  (0 3333#;8      $ ! ZÿîòÏ !7#5##53&'73#3#735#35#3#îi=6TTqqJJJJ(˜˜·3!!3  #`8>8_ÿîòÏ !7#5##53&'73#3#735#35#3#îe=6RRooGGGG'““·3!!3  #`8>8IÿîòÏ !7#5##53&'73#3#735#35#3#ìvA9^^||TTTT,©©·3!!3  #`8>8mÿîòÏ !7#5##53&'73#3#735#35#3#ðZ51JJ ee>>>>$……·3!!3  #`8>8ÿññ§ !73#5##53&'3#3#735#35#3#…_´eB  §§0áá§ %%) L.+,fÿîòÏ !7#5##53&'73#3#735#35#3#ï_93MM hhBBBB&ŒŒ·3!!3  #`8>8NÿêôÐ $,487#5##53&'7'673&''67&77667##5##535#ég<6 F # *") #  ; LJJJ¸#$  M      5M M0WÿêôÐ $*267#5##53&'7'673&''67&767##5##535#êa93 ? " ( &   4 HFFF¸#$  N       6M M0VÿèòÌ7<B7'23673#3#3&''67&''67#5367#53&'67'&'ä 6M@( -JSWM  !  ! "'& )  %  Ì      $,  m z CÿèòÌ6;A7'23673#3#3&''67&''67#537#53&'67'&'ã >YJ. 3  W`eX  #(" % (-+ ,  *  Ì      %) m { ÿèïÐ/4:@7'23673#3#3&''67&''67#5367#67'&''&'Õ KkZWŒ  t…r#*2%#* '"+491@-/  Ð7 "# 3 G ~     ÿèò£ >C7'6'6'&''3#3#3&''67&'#'67#5367#53&'67Ï =t |1  C  6 zŠ{+8'$. %)5=-3£           Z  jÿèôÌ5:@7'23673#3#3&''67&''67#5367#53&'67'&'å 2I<# )  FPTJ  $   #!'   Ì        m z  ÿè„Ì7=C7'2'67#537#53&'73673#3#3&''67&767#7&'x *>2     <CF?   0Ì   #*        † vÿèõÌ7<B7'23673#3#3&''67&''67#5367#53&'67'&'é /D9! &  DLPF    #   Ì        m z GÿéöÐ 48<@D7'2'6'&''&'333##"''3255##5#535335#35#35#35#ã *4* CUÓ  YFGG      1  )" "BÿéõÏ%:733#5##5'667#&'''63673#&''67#|H )d '> : L  9GJB) .? 7>Ï UEEJ    4   !&  Aÿçô¥#8733#5##5'667#&'''63673#&''67#‚?#d *:<K   7GJ>& /7 ,:¥ H76A   *  \ÿìõÑ $73#53635#35#3#3#3#535#535#œ>??8" ·‰  r‘£/ ** YÿéóÏ873533#3#"''3255##535#3673#3##5#535#53&'YCCC=  h-Af "$$%%;z. ÿéç)-1597&'73#3#"''3255##5##5##5'65535#35#335335wP¤¯  ###‘‘##5##‹ 4 G !!)D# %28$YCëÐ,0487'6553&'73'#3#"''3255##5##5##535#35#35#i;<kk #$‰4/ '1; 4$#R ####,f*mÿéðÐ+/377'6553&'73'35#"''3255##5##5##535#35#35#‹  /.`M       w68:n  >-@g #$$&&;{/ÿèÐ)-157'6553&'73'35#'7655##5##5##535#35#35## .)WE   x79 !7o  >-Af "$$%%;z. ÿèðš +X73#'6'3#3#"''3267#'655#53&'#"''3265#5;267##"&55'753753„]e> 0,   '·  . š    I48# "2$.= !831  3 ÿèð¤ )V73#'6'3#3#"''3267#'655#53'#"''3265#5;267##"&55'753753„^f? 0,   ' ¸  ) ¤  ! O9<% $7#0A #;69  :YÿéöÎ $(7#5'673#3#&'#5'67#535#735# #Q!+$   ")++ήˆ .$M%! LH#))>ÿèöÏ $(7#5'673#3#&'#5'67#535#735#d /\'3*  &1#44ϰ‚ 2+N$ %VU) '( =ïÏ %)7#5'673#3#&'#5'67#535#735#B  '2y3FF## # 4E2TTÏaF  #.  ÿéö  $(7#5'673#3#&'#5'67#535#735#<  !3~5J;)&* (:I5YY ‚a ,)?CC" 1ð« #)7#5'673#3##5'67#535#735#&'<  #/z5HH& -A2TTF  « T= + &!   ;  UÿèöÏ $(7'67#73#3#&'#5'67#535#735#k   )T#-%  "*..z &,±ÙM%!"ON%()[ðÐ.47367&''66'3#&'#5'67#535'6'6­  ( ;%%%  (-"1F  Ð    (+  #' XÿéõÏ,2873&''667'3#'#5'67#535'6'6''6¿  (     !b8  Ï?% L!15EA7&e^#+"& %)  ÿéô˜1773#&'#5'67#535'67367&''66''6j$$  %, .d   % 6 $# ˜NN!( ! 20!<'$]ÿéõÏ-3973&''667'3#&'#5'67#535'6'6''6Á  &     !^3  Ï?% J 03DB;) a[!-$& %) [ÿé©È735'673#&'#5'67#b     …& + fe! eóÐ;73#&''67&''667'3533#3#&'#5'67#535#¡F    ¸***,,  $,*Ð       Qÿèð™ %*73#535#535##5##5#53&''67&67#gqr`nn_‚t o"+)# 7 ™B  < .    ÿëðÉ (73#735#'3#735#3#3#3#"''3267#'7#‹XX11…XX33®®ᘠ‹‡ 1É<<:0  ÿêó %73#735#73#735#3#3#3##'3267#7#VV11ZVV00u°°æ¤ ! ‘ .00.$ÿè•Ç '73#735#73#735#3#3#3#"''3267#7#88688Ttt ‡MA  D%ÇD"D"@1 !€ÿêóÉ '73#735#73#735#3#3#3#"''3265#7#†// $00 C[[ sGC  J ÉA A ?4 "FÿéìÇ"&,28>7#3#3#3#"''32767#55#5#35&''&'''67&'ç60000;  yA....# : /ÇM! 1‚%&- gÿéñÇ"&,28>7#3#3#3#"''32767#55#5#35&''&'''67&'í.))))2  l9&&&&   .' ÇO 1‚&%/ 6,ëÈ!%+17=7#3#3#3#"''3267#55#5#5#&'''67&''4'èA8888D  ‚@,,,,,Q  `  W  È   4 b           ?JÈ 7&''6,È  1%ÿéï‘!%+17=7#3#3#3#"''3267#55#5#5#&''&'''67&'âSKKKK` ´VBBBBBt    M ;  ‘5 m  ÿéï!%+17=7#3#3#3#"''3267#55#5#5#&''&'''67&'âSKKKK`´VBBBBBs    M ;     4 j      iÿéñÇ!%+17=7#3#3#3#"''3267#55#5#35&''&'''67&'í,''''0  f5""""  .  'ÇO8‚&%/  $í¨!%+17=7#3#3#3#"''3267#55#5#5#&'7&''&'''6ãUNNNN_   ±R@@@@@L9J  ¨ " R            ÿèô:7'6767&'2567'D>)  "$ S- "     &   ÿîoÏ *73353353#3#3#735#676767'67'ZbbWW22 '1 Ä**06,   ÿíóÌ7'6773267#"&55'7§/( 24  #&¢  <N & RuÿîõÎ,7#"''3267#532767#"'"&55'753753ì ,!  ŸT!3 ^Xh e;5E?|ÿèíÐ 73#'3'6573#ÚL&Ðçç„>& $4wÇ:íÊ!%+17=7#3#3#3#"''3265#55#5#5#&''&''&'''6æTMMMM[  ³XEEEEEw   Ê   1 Z        ÿèð@736533533##5#'67#C;;;>5*?. 44.Aÿè÷Ç#'+173#3#&''67&'767#535#35#33535#335QžF'':)c'':) I    I ( ( ‰ÿèôÄ $(,073#3#&''67&'767#535#35#33535#335’\&#$ #     !"##4#Äd    d<@ŽÿèôÄ $(,073#3#&''67&'767#535#35#33535#335—X$!!  !     !!0!Äd     d<@\îÐ73#3#3#535#535#53&'736§9]RRdÚbRR\0 1 Ï    ÿê€Ï?D7#"''32655'67&''67&''67#537#'733#67&'37#Z  "  $D696   6-->     //    xLÿéõÏ@D7#'733#67&'#"''32654''67&''67&''67#53'37¾MK(J      #0 5$$ $ $5_, C//   .0    /Z-ðÒ?D733#67&'#"''32655'67&''67&''67#537#'77#‚O I     !'-!$ $%#1dMXCÒ            ÿéôÑ>B733#67&'#"''32654''67&''67''67#537#'77#Oo:n +,  0> H-)8 6+ 25Un| e Ñ1+/     UÿéõÏ@D7#'733#67&'#"''32654''67&''67&''67#53'37ÀGF%F      !- 3!! " !1Y)=//   -0   /€ÿë÷Ï?C7#'733#67&'#"''32654''674''67''67#53'37Ê,03     !   9#‘0 1   (,      1IÎ767'67'67676'6}    ›3 3. f  ‰ÿëøÏ=B7#'733#67&'#"''32655'67''67&''67#53'37Î,-,       4#‘01  *,       1  ÿéò›@D7#'733#367&'##'32654''67&''67&''67#53'37 `^Ct   / / 1A D0%=6(( - VŽ@ Ye(%    "    % _ÿéôÊ:733#67&'#"''3265'67&''67&''67#535#535#va@    $ +  :dKKMÊH      _ÿéôÏ@D7#'733#67&'#"''32654''67&''67''67#53'37ÁDC"C       &.  -S%://   .0     /kÿêöÏ@E7#'733#67&'#"''32654''67&''67&''67#53'37Ã=?=      # '  *M!4//   ,/    /\ÿéòÐ*.273733#5367#35#3#3#3##5#'7#535#735#35j!>”#*713€,55::? E@XX .¾ " (4C`ÿéóÐ+/3736733#537#35#3#3#3##5#'7#535#735#35m";$+2,1z,//;;< C:RR ,¾ " (4BgÿéóÐ,04736733#5367#35#3#3#3##5#'7#535#735#35t8ˆ")/)/t),,779 @7LL*¾ " (4Bÿèñ­/373733#5367#35#3#3#3##5#'735#535#735#*;VÝC3@TLH°7CCB†14499F __Á ~ ..  > ÿé•Ð/3736733#537#35#3#3#3##5#'735#535#735#$0ˆ$-)"/l$--227 $@@5GG¿ % '2  6ÿéɇ.273733#5367#3353#3#3##5#'735#535#735#G#3“-1'Tm'..00: -<<4GG{   "  ÿèñÐ*.273733#537#3353#3#3##5#'7#535#735##3$7aà<0F]—´EQQ[[be[ŒŒG@P½$ 32B|ÿçòÐ")/73673#3&''6654&''67#677&'‡>?:    £8"   +?4 r   ÿé€Ð-173733#537#35#3#3#3##5#'735#535#735#'t' ,]%%&&1 !66,88¿%21rÿéóÐ*.2736733#537#35#3#3#3##5#'7#535#735#35~2 }!)(#,i%''11381CC$¾  " (4B~ÿèõÉ'7#3#3#3#67&'#67'5#535ëKDDCCT/   +  É   EM ZlvÿêóÑ -73&'73#3#735#33##"''3255#53567#{/3vff@@h33  77K½ 43   xÿéôT73533#&'#5'67#y34'   %K #IG&#~ÿèóÎ 17'2'6'&''&'3#35#535#53#5##56æ ,A4.  %    % H2HÎ  " #""„  ÿéuÐ ,073733#537#35#3#3#3##5#'735#535#735#% i$*V!!!!* 00(22¿ %21qÿéïÇ $<@D73#735#335335'673#"''32653533#67&'7''75#735#335~kkI  T  a"## !(%"#Ç=M  j'R . wÿéôÏ37;?C73533533##5##5#3#3#3#3##5#535#535#535#5##5#35#335wp 444444 R$½""(pÿè÷Ñ!%173#3#&'#'67#537#53635#35#3533##5#¢70B#  '" ????ÑU &"U ,3p""$$ ÿélÑ -173733#537#35#3#3#3##5#'735#535#735## `#$L $  ''!((À % (1 kÿçõÐ#'+/5;73533#3#535##5#;5#35#3#735#35#35#'67&'o9990s09~HH qqKKKKKK  G Å   "//)U=    HÿíñÏ673673#3#3#3#3#535#'67#5367#5367#53&'ƒ  &HDGZa]2?‘@% !%,(+7(Ï    ""%   LÿéóÑ C7#5##53&'73#67&'#"''32654''67&''67&''67#ìrAPŠE      ". 3#& ' #.º!!  *   05 "  EÿéóÐ'M7373#3#3#&'#'67#5367#5367#367&'#"''3255'67&'767U:DHCI_+ $(  ,%,6<     ¶   $  Q)      ÿñîÒ573673#3#3#3#3#535#'67#5367#5367#53&'T 1  6[X^ w „LYÆY6 *AMJP[4Ò     "" (   ;ÿíòÏ473#3#3#3#3#535#'67#5367#537#53&'736½(LHK`gc4B™C( &'/*.<,  Ï  ""&   ÿïñ 373673#3#3#3#3#535#'67#53567#537#53'[28mgl‚‹†HVºQ; )>I7Vcca _C ( 'Ï 9H 5 u   ÿçóÔ%+1773#3#3#"''3267#53635&'7&'''67&'m` ºº´ ´<0ŒB  ?  ~ <  Ô >C+‡ }  aÿçôÏ%+1773#3#3#"''3267#53635&'#&'''67&'œ<_nnk hL  * 'Ï 9G2 u iÿçõÏ%+1773#3#3#"''3267#53635&'&'''67&'Ÿ;\kkh fJ (  %Ï 9G1 u   dÿéòÐ!%).38AGM73533533#3#5##5'67#735#335335367#35#35#3'3267#3'67&'n$##-D  !$5X "3 4  @ 9¼1;/2: .4  1  58  Kÿé—Ï73#3#7#5'756ˆ""##( Ï %0.™ ÿé{Ï73#3#67#5'756l!(<<<<$ &.Ï  % -)š—ÿèöÆ#(733#"&55#'6653&''67&'#367ã    P   Æ<  0$  )c1    &TÿéÏ73#3#7#5'756Ž!!""'  Ï %0.™]ÿé¤Ï73#3#7#5'756• % Ï $1.™ ÿérž73#3#67#5'6756c%7777#" (ž  eÿéõ˜$)733#"&55#'6673&''67&'#367Õ '   m  !$ #   ˜( M   gðÈ &*.73#5##53535#335##"''3255##55#35Úe8?((L  2C22È8(*88( .X  m ZÿéïÆ &*.73#5##53535#335##"''3255##535#35#ÙqAD..T   =====Æ?""??.0d  '}#4JÿéìÆ &*.73#5##53535#335##"''3255##535#35#Õ~DN00^  GGGGGÆ?""??.0e  '}#4ÿéíš &*.73#5##53535#335##"''3255##535#35#Ê#´_&rLL::‰  vvvvvš6((66&  'L ` % lÿéôÆ &*.73#5##53535#335##"''3255##535#35#ád;=((N  77777Æ?""??.0e  '}#4ÿèïÊ &*.73#5##53535#335##"''3255##535#35#Ï ·!]-vII66‰  sssssÊC,-CC1#3g  +~"0‡ÿéñÃ73#3##5#535#^%++,,&ÃIllIƒÿéïÏ73533#3#5##535#35#ƒ,,,'9&,99221pp1}9xÿæôÎ)73#6767'67'67#53&'&''66¶ +: ; $ (6?$ (.Î J ! d #0xÿèöÐ +73#53&''67&'&''6677&'76º,o. B         Ð 6 " '    wÿëóÎ#(-7#5367#5373#3##'365#335367#335#3½F #!  5$$,.G,  cc#1¡ A  ) wÿêóÏ!%)-BH73533&'73#3#5##5##535#35#33535#3353533##"''3255#&'{0  0++0,D,`S   S » \\.-?     uÿéñÐ "&*73#53&'3#735##"''3255##53#735#µ5|0\\88W  P;;Ð +32[  Das 5mÿéôÐ !)1A7#5##53&'73533533##5##5##5##53'66733267#"&5ëO/9a:!   ½** -N=>m$P C vÿïòÐ #'+/?73533533#735#33533535#3353353#3#735#3673#53&'|!!qMbwwjjHH!|ÀO/1, .$    rÿçñÑ !%)-39?7#5##53'73#673#5'675#5#5#35#''6&'''6ïX40K  6d H?????  D"  ¾##    \M H   i }  kÿè÷Ð 8<@DHN73#5##53&'33533#3#32767#"&55#'67#535#53#335#35#35#&'±4Z3    * %./??????GÐ #  a  # a   ' '   dÿèóÆ -73#735#33535#3353#67&'#67'5#ouu1O1m‹<    5 Æc9A7  BC Qÿèñ› ,73#735#33535#3353#67&'#7'5#+¬¬::L:†::L:´àc   R!0" !›R2.-  7-   9ÿèöÊ ,73#735#33535#3353#67&'#7'5#-©©66J766J7²àh  $ W!%:'# (Êd;>6    DI  S^ÿèóÆ -73#735#33535#3353#67&'#67'5#jxx2 R2 q‘?   8 Æc9A7  AC QJÿèîÆ -73#735#33535#3353#67&'#67'5#Y‚‚$$7%\$$7%~¡E   : ! Æc9A7  BD  QWÿèóÆ -73#735#33535#3353#67&'#67'5#d}}""5"W""5"w˜C   :  Æc9A7  AD  QsÿèóÆ -73#735#33535#3353#67&'#67'5#~kk-F-c~6    . Æc9A7   AD  Qÿé¢Ï $,073533533##5##5#'67&3##5##535#,,G /& FZZ_EEE¸%Q Q5#\ÿéõÏ $,073533533##5##5#&''63##5##535#\. .F % "$# 0[[aGGG¸  !Q Q5$Gÿé÷£#(0473533533##5##5#&'#5'63&'#5##535#Q"1""1"L%' R <; M[[[’   *D D(lÿéõÏ $,073533533##5##5#&''63##5##535#l((>#   + QQX>>>¸ "Q Q5$sÿéöÏ $,073533533##5##5#&''63##5##535#x&&7  * IIS999¸  !Q Q5$rÿéöÈ)?73&'767#533'67##"''3255'67#3533#&'#5'67#z/ Ie1   )34' %›     @ #II($kÿéöÈ)?73&'767#533'67##"''3255'67#3533#&'#5'67#t2  Li4  " ,67) '›     @ $KK*#]ÿéõÈ(>73&'767#533'67##"''3255'67#3533#&'#5'67#g6  Ws,  % 0<=." " "-œ     @ #JL,"TÿéôÈ+A73&'767#5333'67##"''3255'67#3533#&'#5'67#^; Zx;   ' 4@;,$"% $0›     @ %LM," ÿéôœ(>767#533'67##"''3255#'67#53'3533#&'#5'67#f §#V  8   %< +'MTAbcV&7 :&&7 2$N‰   M  /.  ÿé€È(?73&'767#533'67##"''3255'67##5'67#53533#&+ C].   %C (2++ ›      q1?1[ÿóòÅ73#3#3#535#535#3535#5##3aŒ*&&/—,#%(9%%$#Å)W,,V*¬>3;<2HïÇ73#3#3#535#535#3535#5##3×G;;KÞH88EX%::%77Ç66[%$$eÿóòÅ73#3#3#535#535#3535#5##3k‚&##+) !$6""! Å)W,,V*¬>3;<2EïÇ73#3#3#535#535#3535#5##3×G;;KÞH88EX%::%77Ç77^&%%ÿð¸c73#3#3#535#535#3535#5##3š/**7§8**3B**)(c01QVïÈ73#3#3#535#535#3535#5##3×G;;KÞH88EX%::%77È11O  ÿèë` 73533#''275#735#335'7#6)MM Rk51M99M9 =G4 2jEôÆ73#3#3#535#535#3535#5##3n€# )Š* %8  !Æ89^"""™éÐ 73#3#53635#35#µ$8DJ73#'66553&'35#3#"''3255'675#73#"''3255'675#&''&'«7w =#cc 4 "=7 %   /   Ï 8'@1 +#X5:q   .q    *    TÿéõÑ -39G7#5##53&'7&'3673267#"&''67''67&'3353#5#533òr@  "(  "   ~  5#l#»..   /% . 0    @3+E;)cÿóôÆ 7#3#53#735#3#735#73#735#ðz~‘+TT..11-11Æ®Ó!98J,J,`ÿéóÉ#';73#3#5##5##535#3#73#3#73#3#3#3#"''3267#7#k|6?..?4 !!C J((J''Rƒƒ“c] b É #::# '    %SÿéõÑ 7;?CG7&'67&'67&'63#3#&'#5'67#535#735#33535#335x  <  8   i†;E6" #$ "1@8%%8'_%%8'Ñ      -O   46! /1Kÿç¯Ï!%73533#3#&'#5'67#535#35#335]     ¹M IC%MR+++ŸÿçöÐ73'67#'63&''65¾)     $Ð# *"31  #!1ÿçóÒ -1597&''63#3#"''3265'##'2655##535#35#73#x4: :5,4 Jii|   H 11111^Ò"&(# p gn /…$41[AÿéõÏ!26:>7&'#5'63&'3#"''325'#"''3255##535#35#73#”&- \ :NG   3  """""FÏ  " .s  mt  1‹%7-\FÿéõÑ $48<7&''63#&'67&'6'#"''3255##55#35œ#* (&"* < LLB    .   8 "4""Ñ$%! ! # (# #("q  -ˆ%ÿèøÒ +/3=7&''63#&'6'#"''3255##535#35#7&'6€/: 64,@O nnc     88888   Ò$*(  $ & !i  .†#42 $ÿçóœ"37;?7&''#5'6&'3#"''3265'#"''3255##535#35#73#}16 Z ML l B  66666`œ    S LS  "j ( 'G +öÐ"'+/<@7&'#53#"''3255##5'63&'35#35#73#"''325'3#€16 #"HU  8 Q : =8888’  -Ð  M ^  < & 'J  A<TÿéöÏ!26:>7&'#5'63'&'3#"''325'#"''3255##535#35#73#š#* V 1G D  1    AÏ   -s  mt  1‹%8-\ ÿé™Ï/37;73#5'67&3#"''325'#"''3255##535#35#73#T AH  0"  & 5³ #*s  mt  1‹%8-\:ÿêõÉ7;?7327#"&547#'65535#'273#67&'7''275#735#335Û a $%  *ÉH8$$+$;0>V>9 /ƒÿèòÆ!7#5##53'>33265#"&5ç4" ÆŒ{z‹(R/)  $,3 ’ÿèõÆ!7#5##53'>33267#"&5ê'  ÆŒ{z‹(Q2'  #- /  G ôÐ .26:7&''63#3#"''325'#"''3255##535#35#73#, #* + <VV]  0  #####DÐ   [  V_  $s .(M\ÿéòÊ /37'6553'#33673#3##5#'67#535#53&'5#‚[[:  5Ž+E5 7?a<) >>& EkÿçõÆ %7'66553'#33673267#"&5“ lFF8 #   €E5 2#_F4"*  -   ÿüpÍ73#67'756675#\!? +0 " Í  W ¨¨\aÿéôÏ1E73533#3#3##5#535#535#73#"''3265#'667#373#&''67#iE@     H;?9) ). - 5¾W>-% !'v  %)  " ÿçô–1F73533#3#3##5#535#535#73#"''3267#'667#373#&'#'67#)''$$((..$$)o`  # ucgT;G"TEVŠ      D*! \  #$ ÿéï‹1F73533#3#3##5#535#535#73#"''3267#'667#3673#&''67#(&& '')) (ia   " m\eY>F$O?O€      : # J !! Dÿèò¢0E735#535#53533#3#3##5#73#"''3267#'67#36533#&''67#NUI   [FPD64 E : B[   O=  1# 0[ QÿéôÏ2F73533#3#3##5#535#535#73#"''32765#'667#373#&''67#ZMF     PCF@. .4 2=¾W 5+& !&v  &)  "IÿéôÏ2G73533#3#3##5#535#535#73#"''32765#'667#3673#&''67#SPI    TGJD0 18 6A¾X 4-% #%w   '*  "hÿéðÐ %)-157&'67&'67&'6#5##535#33535#35#„  8   7   S 2!S 2!!Ð   Qyy-GiÿéõÐ !%73&'73#&'73#536#5##535#r05{  G *ŒM $DDD·    @[ [?-kÿéòÐ-1597&'3673#3#"''325'##53#"''3255#3#373#  R  ‡o  >A    Ð  „  :€ hE`OÿéòÐ.26:7&'3673#3#"''3265'##53#"''3255#3#373#y  d  (£ˆ   I(N ((((%Ð  „ 9€ hD`ÿéðÒ-1597&'73#5363#"''325'#"''3255##535#35#73#S  m A߆ + T 88888kÒ   0‡  z  6›*A6l`ÿéòÐ.26:7&'3673#3#"''3265'##53#"''3255#3#373#†  X  #’x   BF   "Ð  … 9‚  kE`ñÐ-15973673#53&'3#"''325'#"''3255##535#35#73#R 8>â<‹  Q  77777iÐ    +e  ^d  'y"2)O JôÒ,04873#53&'7363#"''325'#"''3255##535#35#73#®>ç@ :&  K @@@@@pÑ  $?  8B R 8ÿéðž-15973673#53&'3#"''325'#"''3255##535#35#73#M 99ß7   K   77777eœ  )g  \`  (u -*V]QòÓ+/3773&'73673#3#"''325'##'6655##55#3573#]#' !•y  , $6$$¿ @  8B T !7rÿéôÏ-1597&'73#5363#"''325'##53#"''3255#3#373#•  K ‚L   8< Ï   3…  :€ iE`ÿéïm.26:73&'73673#3#"''3265'#"''3255##55#3573#=:>ß»   D   DVDD+Y B :C V 9VÿéóÏ26:73533533##5##5#3#"''32765#3##5'65#35#d######c   U G;A)))¼v YY_ 1%ÿéeÏ73533#&'#5'67#"   ¡.. ‚v$)8 ÿééœ!73#"''3267#'6##55#35>  ’ {[[HHœ b [k%ÿñò 73#3#735#35#3#ÄÄŸŸwwww5ãã[594\ÿéóÏ #48<73#5'675#5373673267#"&5#"''3255##55#35‡((&    .  GZGGÏ]    h  *€" ÿéšÏ "37;73#5'675#5373673267#"&5#"''3255##55#357%%$    -  BUBBÏ]     h  *€#dÿèöÏ!9@73533#&'#5'67#7&'7'6373#&''67&'7#367n78, +  `  a+ H   0 #"722$">  r      KÿèõÏ!;A73533#&'#5'67#7'6'&'3673#&''67'67#367TBC3%%) #/{ Y 1`"   '9 *1 &;+ ž11*1 >    z      TÿèôÏ!:@73533#&'#5'67#7&'7'63673#&''67'67#367_==0! " . l  l1 P  %5 '' '<  ž11#%?   r      ÿèôÏ!<C733#&'#5'67#537'6'&'3673#&''67&'67#67#v]J- 2##1 +E]O|  Q~+ #(-&+F  %?  ) 3 ž11 <   x        ÿé÷Ï%5FJN767&'7&''67'67676'3'65733267#"&5'#"''3255##535#35#™")! ,6  o¡     AAAAA½"   hM1.BfÇ # ab  *z!05ÿéôÏ$3DHL7&''67'67676767''3'65733267#"&5'#53#"''32=#335«!$  W JM   ((((š   + :eM1 .DeÆ % (wc ?„ÿêëÉ7#"''3255#'65535#35#ë (''''ÉÉ ?/' )3v;(c)A•Ï73533#3##535#35#A  3 !!¥***Sb*k/GÿéòÐ/E733533##5##5#53&'3673#&'#'67#3533533##5#'67#q',,'$$^  r6I3+ 7 . $+#%'''!Ð    %$ ;77$ÿéëÒ#173#3#3#"''3267#53635353353##533[e–±±³ ±/ ‚‚‚W1ˆ0ÒO  A *Š a)9SÿéîÐ#173#3#3#"''3267#53635353353353##ŠEfxxy wSSSuaÐN  A'‰ a)_ÿéñÐ#173#3#3#"''3267#53635353353353##‘@_ooq pKKKlZÐN  B *‰ a)…ÿïñÆ ,27#3#53#735#35#373#&''67&'67#367ñZZlEE$$$$+    %Ʊ×D( & +     ÿï‚Æ ,27#3#53#735#35#373#&''67&'7#367‚\\oJJ((((+    $Ʊ×D) & )     wÿðñÅ ,37#3#53#735#35#373#&''677'67#367ñggzQQ////0    ) ųÕD) & )      ÿï‘Æ -37#3#53#735#35#373#&''677&'7#367‘jj|RR11111     +Ʊ×D) & )     nÿïñÆ ,37#3#53#735#35#3673#&''67&'7#367ñqqƒ VV4444 4     , Ʊ×D) & )    iÿïñÆ ,37#3#53#735#35#373#&''67&'67#367ñvvˆ!ZZ6666!9   , Ʊ×D) & )     YÿïñÆ -47#3#53#735#35#3673#&''67'67#367ñ††˜&bb@@@@'@  $ !2 Ʊ×D) & )   _ÿéùÍ#+<@73533533##5##5##53&'73#3#3'667333265#"&5'3#f((=7e[o  J   %º2  '" #<  @O ÿéê”39?EK7&''67'67676767&'73#"''32765#'6&'&''&'''6i #+  %=G :      N   ,M j% L % PÿéõË &>E7&''67&'76'3353#'67&'3&''67&''667#À    Ta…!L *G$ $ /*  ;Ë     @@Q         fÿéöË &=D7&''67&'76'3353#'67&'3&''67&''667#Å       NW} H ' B  '"   7Ë     AAQ        WÿéöË &>D7&''67&'76'3353#'67&'3&''67&''667#Ä     R]‚" J 'F#", ( !  :Ë     AAP         rÿéôË%=D7''67'76'3353#'67&'3&''67&''667#Ç      FMr D )9 "  -Ë      ==M         }ÿè÷Ë &>E7&''67'76'3353#'67&'3&''67&''667#Ì      BHl >  :      -Ë      AAP         cÿéñÏ"04873673267#"&5'67'5333#5##53635#35#¦    1&@R$RRRRÏ$   "$ b 0 o o 5B&ÿéõ !/3777'53373673267#"&53#5##53635#35#8C+& E '" '.  ,[…9+…………y  E"    [['2hÿéñÏ"04873673267#"&5'67'5333#5##53635#35#©    / %>N"NNNNÏ#   "$ b 0 o o 5BÿéÏ!/3773673267#"&5'67'5333#5##53635#35#E    " 2: ::::Ï#    #$ b!0 o o 5D ÿé‡Î#'+DHLP7&'#5'6&'3#735#73#735#'3#735##"''3255##5##5##535#335335C!  A (3&"" E"" 5"" @  !Î   0  0  0 1I """"*\#‹ÿéóÏ !/37767'53373265#"&55363#5##53635#35#  1   128 8888¥%  ]   S G qq 3BZ£Ï73533#3##535#35#Z,¥***Sb*k/ÿëç–'+173533#3##535#35#7'3255#'65535#35#..."C!. 11¸ #84454{GXQ%U‹ >>.JEÿéõÈ 57'66553'#33#67&'7&'3#3#535#535'67#r ŠddZo>   //8‡ , , 77ÿéú›48<7327#"&547#'6553#67'7''75#535'635#335á jm&""  $+."" ."›:-+-%1:&.BB5 5E›òË 7'6'6'6×* (- +( &Ë " # $oÿéøÉ59=7325#"&547#'6553#7&'7&''75#535'635#335å  GH    ÉH8&&#=0>n=##=yT  "Tk666 ÿëvÏ 77'2'6'&''&'3#3#753#55375#535#'6j (9/+  ;%((C'' Ï      1"> :'3  ÿénÇ #04875#53#5'675#53#5'6'&'7&'3#5##5335#35#+. =. <  8   $0 0000•!_ !_)   >j j&; ÿétÏ#AEIM733533##5##5#533#735#73#735#3&'73#3#3#3##5'6353535$,,%,,4!G Ï #--"  E ôÎ&*73#"''3267#'6#3267#"&5535#žD   : 2 #! Î Y D ;!   _, ÿìùÆ*7327#"'&5#'655&''67&'76Í  | q Æo4"#!-\83 *4o, !!  GÿçöÇ,73265#"&547#'65567&''67&'×N(   ÇG8' .%9/>f62 (4x.- ! #" ÿþ[Ï 73&'73#67'676'&'M3 % ¨ ;$ 5/)+.':ÿéòÑ"'-39?E7333#"''3267#'67#53637#37#'&'&'&'''67&'Š;    ‚ */61+!^Q  n    < 5 Ñ//J0 44@=q "  ;ÿéñÏ"&+17=CIO7'267333#"''3267#'65537#37#7&''&'&''&''67&'Ý :TE#    xQXcg3   i   0  +Ï 2%&A)'! -93%9 b  v  FÿéòÏ"&+17=CIO7'267333#"''3267#'65537#37#7&''&'&''&''67&'Þ 7OA     l HNW[,    d /  ,Ï 2%&B*) -93%: c  v SÿéòÐ!&,28>D737333#"''3265#'67#37#7#'&'&'&'''674'e02  h %*3&!/A  g 8  -®"0.K0 )E<    FÿéòÏ"&+17=CIO7'267333#"''3267#'65537#37#7&''&'&''&''67&'Þ 7OA     l HNW[,    d .  +Ï 2%&B*) -93%: b  u >ÿéñÐ"&,28>D7367333#"''3265#'67#37#7#'&'&'&'''674'Q88  w *2:-&6L  s > 2®0.K. )F=   ÿéí§"'-39?E7367333#"''3265#'67#33737'&''67&''&''4'F W !   ª  +!    7 - (--*#  Ñ gO  UE$;g 7E 3  ÿèõ¢#',15F73#332767#"&55'67##5335#335367#335&''6767'yNW ( /%JL3M:<   mÿéôÑ"&+/?73#3265#"&55#'67#53635#35#365#35#67'7''6ž9   0 & %'(%  Ñ gO  UD%;g 7E 3   ÿèôd27;?C73#67'7&''67#32767#"&55#'67#536365#33537#335N!      )+ QEARE:;ME‘68KHd;     !*;  iÿéòÊ  &*73##5#3#735#3##"''3255###535#k„ fII##‰  iR9&&ÊQQ56M  J4 ?$ ÿéóË  &*73##5#3#735#3##"''3255###535#Ó¤hh@@3æ#   ¯Š[HHËPP56L  I7F& ÿéŠË  &*73##5#3#735#3##"''3255###535#t T==~   YL/ËQQ56N  K5 B%LÿëóÊ  &*73##5#3#735##53##"''325'##535#U–q PP**T{§   A..ÊQQ5IJ  64 ?$IÿæõÆ!%)-173#3#3#&'#5'67#535#535#5#35#335335T—-+@K=& (' &=J@+,X))ÆD!'CA# D2 VÿéõÆ!%)-173#3#3#&'#5'67#535#535#5#35#335335`Œ*(;C6! #" "6B<))P''ÆD"&><"!D2  ÿèõÈ!%)-173#3#3#&'#5'67#535#535#5#35#335335ÜH    \1--  )ÉCC2!!!!!I 5;-ÿçóP!%)-173#3#3#&'#5'67#535#535#5#35#3353353¸;3JZI5<1 +?ZM3:l!2!!2!!P \ÿéðÆ "073#3#735##"''3255##5&'73##5#536\””uuOOp   k54 !/Æ 30f  Pk}   **OÿéðÇ "073#3#735##"''3255##5&'73##5#536O¡¡€€XX{  u9 ;$$4Ç 3/g  Pk~   **AÿéšÇ (.73#3#735###'3255##53673##5#7&'DQQGG%%@  8  Ç23e  Rn}4 ///  ÿé{Ç (.73#3#735###'3255##53673##5#7&'eeVV44Q  K  Ç22f  Qm~5 ///  iÿéðÆ "073#3#735##"''3255##5&'73##5#536i‡‡ mmGGf  _1.*Æ 30f  Pk}   **ÿéì¡ /73#3#735##"''3255##53&'73673##5#ØØ¤¤¨  ©(   94¡ /*K  6Rc.   sÿéðÆ *073#3#735##"''3255##53##5#536'&'t|| ff@@_ WM&Æ 30f  Pk} **  ÿévÉ "073#3#735##"''3255##5&'3673##5#hh RR00M  F$ É23f  Rn~   //qÿéóÑ<73#5'67335#35#3#3#"''3265#'67#'67#'67#–X_OS;;;;uPQ       ² C9 ( & '<#) #  ÿéìÇ "073#3#735##"''3255##5&'3673##5#ØØ¤¤¨  ªF  A  '55Ç74]  Few   ""†ÿçñÏ73533#7'7''675#75##5#‹&&& %,&M //f1" 4BBBB`ÿéóÏ#'+/73#3&'73#&''67#5365#53635#35#35#5/0& , 3 ) */'!DDDDDDÏo   (# o )/0ÿèíÏ%973#73#"''3265'3#3#3#535#535#73#3#3#535#535#É   ­P!!QYLOÏççÍ ÂÿçìÉ #73#735#3#735#35#35#'67&'4™™tt±±‰‰‰‰‰‰$+ (\/! -É4/xU23&      ÿé‰É #73#735#3#735#35#35#&'''6ee??ssLLLLLL4  É60qO//%  cÿèõÈ #73#735#3#735#35#35#'67&'vllEExxRRRRRR FÈ41rQ./%    ÿçí“ #73#735#3#735#35#35#'67&'3žžzz¶¶‘‘‘‘‘‘+ ;7U%) +&“* $V> !    ÿé”É #73#735#3#735#35#35#'67&'llFFzzUUUUUU A É54pO//%   ÿêó– !%)73#3#535#3#&'7#'67#735#35#35#Å[mægXµ9%) .) 8 8&%5–  $Z    ? " " \ÿèòÐ&.26:73#33##3#5##535#535#'6553'5#35#5353535#·23( (-?&"")8 ))"4??Ð #$J I ?3 0!Y02#`6ÿéòÑ(048<73#33##3#5##535#535#'66553&'5#35#5353535#£CD445N,,,9 I 99,?!!!MNNÑ #$J J A2 -#Z 13#aZÿéôÑ'/37;73#33##3#5##535#535#'6553&'5#35#5353535#µ54) )*;#""+9 ++"5:;;Ñ #$J J B1 3;Z 13#a ÿçô©'/37;7#33##3#5##535#535#'6553&'75#35#5353535#ñ[HHK}DFFRY RRFY555z}}–  9 9  5) *0J  )   HcÿéôÑ(048<73#33##3#5##535#535#'66553&'5#35#5353535#¶31( ((8!& 6 &&2688Ñ #$J J A2 -#Z 23#aqÿéôÑ(048<73#33##3#5##535#535#'66553&'5#35#5353535#¾*+# #$0! 2 !!..00Ñ #$J J @3 ,#Z 23#a ÿé†Ñ'/37;73#33##3#5##535#535#'6553&'5#35#5353535#Q*(! !!-"  / "",,--Ñ $$I I @3 2>>· L-" $,@$  +FE, ÿéWÏ73#5#'67#535#533D 8/Ïæ[F:"P>iÿèõÐ #+/7&''6'6553&'73'#335#5##535#¬ # ,1 #EEEE444Ðq." $+A K:  .D D* ÿéó¬"&.27&'#'655'63&'73&'3535#5##535#ƒ 05 ”! S:5!%$ttt¬ B*3+  (: :cÿèõÑ #+/7&''63#'6553&'#3#3#5##535#¨"  # / 'Z04FFFF666Ñ  J-% '36 #  E E, CóÐ#73#&'#'67#5353653367#335ÎF3 @A .<HJ$ 3H;¸6  6 6 $mÿî÷Ð!)-1573#&'#'67#535353367#3353#53535#35#35#á*! '$$ )** ‚    ¸6  66&gBBB22222 mÿéóÉ 06<73#3#535#5#35#3353353#3##"''3255#'67&'u~'"t!&F  Zff 5  7  ]  É<<+;) &  WÿðóÏ #'+73#7'673'3#7&'3#53535#35#35#Œ8  (‰p œ!!Ïw; ,+"/`$/NNN<<<<<4ÿòóÐ  $(,73#'6'3#'3#7&'3#53535#35#35#±5; .(v  (¿+*Ð #*mdZ% /QQQ>>>>>QÿñóÐ  $(,73#'6'3#'3#7&'3#53535#35#35#¾(/ )%l ¢#$Ð %/vla* 3MMM;;;;;_ìÐ  $(,73#'6'3#'3#7&'3#53535#35#35#¼'- )!g     Ð  "*jb['  )===,,,,,]ÿéôÐ8<@DHLP73533533#3#3#33##"''3255##5#53535#535#535#35#35#35#33535#35#d !$$(A5 V5>" 3!!!!!!3#V!!3##À E  ""E -O5 ÿêÏ8<@DHLP73533533#3#3#33##"''3255##5#53535#535#535#5#35#35#33535#35# 6, E -6! L,E,ÀE  ""EP4AÿèñÐ8<@DHLP73533533#3#3#33##"''3255##5#53535#535#535#35#35#35#33535#35#I()++""0M>  h=I*(;))))**=+h**=++À  E  $$E  ,O3 ÿéóœ8<@DHLP73533533#3#3#33##"''3255##5#53535#535#535#5#35#35#33535#3355499//CdQ  QgA//5|444*==P===P=    0 0    = ! Xÿèöš8<@DHLP73533533#3#3#33##"''3255##5#53535#535#535#5#35#35#33535#35#d #$$(A5    W4A% U###""4#W""4##  1  ##1  8 " hÿéôÐ8<@DHLP73533533#3#3#33##"''3255##5#53535#535#535#5#35#35#33535#35#n!7.  J/; M.J.¾@  ''@N0 ÿê‡Ï8<@DHLP73533533#3#3#33##"''3255##5#53535#535#535#5#35#35#33535#35#!7)  @ )1E)@)ÀD  &&DP4G óÏ8<@DHLP73533533#3#3#33##"''3255##5#53535#535#535#35#35#35#33535#35#T$%%% .K9  ^9H- $7%%%%&&9%^&&9%%    6  6   ' A ' kÿêóÐ$FJ767&'76'3'67&'767#3#3#3267#"&55#'67#535#335Ô   /T3   N     É   H   2  @!-  0+"!!!?ÿëöÎGK733&'76767&'#3#3267#"&55#'67#535#'67&'767#5#RG<   11  7 //0    2QÈ"#       *+   1-"*   n**5ÿéò£HL733&'76767&'#3#3267#"&55#'67#535#5'67&'767#5#DK c'      44   9 .13   7Xœ&        ".    Y ÿéòÏ 37733#5##537'6'&'3#32767#"&55#'67#735#u^©_Fp ‹%  %! F@ ccÏ05""5,    HF*!  /3% KÿéòÏ 26733#5##537'6'&'3#3267#"&55#'67#735#”BsE7 Q  d   2 -??Ï23!#5-  JF*  03%"UÿéòÏ 26733#5##537'6'&'3#3267#"&55#'67#735#™>lA4 N  ` / *::Ï23!#5-  JF* 03%"eÿéõÏ 15733#5##537'6'&'3#3267#"&55#'67#735#£5\81  F  Q  +'++Ï32!#4-  JD+  14&"KÿìóÈ#4:@73#735#36533#&''67#&'33267#"&57&'''6b~~XX   (  ^  hȉe    R, 2  rÿéòÏ+08<@73533673#673267#"&55'67#535#67##5##535#35#~ $8 % $ "'2 8@@@@@¾         X[ [!1aÿéóÎ2:>B73533#3673#6732767#"&55'67#535##5##535#35#n! '>)" &/ (& ,)!zPPPPP½     kX X -UÿéóÏ19=A73533#3673#673267#"&55'67#535##5##535#35#b)  *B .' ,4  )+ 64)SSSSS¾       lX X,aÿïôÆ $(73#735#&''63#53535#35#35#vkkEE! H “ ""Æc?    KNNN<<<<< ÿðÄp $(73#735#&''665#3#53535#35#35#%ƒƒ_'   &{·++p@    :$$$@ÿîõ— $(73#735#&''67#3#53535#35#35#]zzT   #vµ++—N0   N<<<,,,,,pÿïòÅ $(73#735#'67&3#53535#35#35#€dd>>   /‚    ÅfF,  /PPP@@@@@`ÿîòÏ#'+/7&'3673#'67&'3#53535#35#35#‹  J  ~  D  3 ’"!Ï (  2GGG66666iÿîòÏ#'+/7&'3673#'67&'3#53535#35#35#‘  D  v B  2 ‰   Ï (  2GGG66666?ÿïõÎ #'+/73673#7&'&'''63#53535#35#35#NZ  *›!  a !8" #µ**A ;  'HHH77777yÿéóÈ/37;?C73#3#"''3267#3#7&'7&''75#535#'67#735#33533535#35#mHU   +"" " !!    I"È>n$U//€ ÿê~Ð$(,17&'73#536'67&'7'753#?5#275#*  Ch> ' . ,<- ZA   Ð  ,    iYGA=;€ÿïôÏ#'+/7&'3673#'67&'3#53535#35#35#ž  7 g :  * t    Ï '  3HHH77777YÿêöÈ"&*0673#3#3267#"&55#'67#535#735#35#&'7'6ov0;,  3 ,$82PPPP  e  ÈW.,  /3&.45)    JÿêòÈ"&*0673#3#3267#"&55#'67#535#735#35#&'7'6d~5B0   8 1 )?6ZZZZ  p ÈW.-  04'.45)    DÿëïÈ"&*0673#3#3267#"&55#'67#535#735#35#&'7'6Uˆ:I6  2 --D:eeee | ÈW.-  03'.45)    PÿêöÈ"&*0673#3#3267#"&55#'67#535#735#35#&'7'6iz3@/   5 /&;3VVVV  l  ÈW.-  04'.45)    LÿêëË 5;A73#735#35#3#"''3255'675#73#"''3255'675#&'7&'b~~XXXX%G  $4SH  $4D  c ËT20.j   j      \ÿêîË 5;A73#735#35#3#"''3255'675#'3#"''3255'675#&''&'lwwOOOO.@  -K@  -Y  ;  ËS11.j   "j   "     ÿéŒÉ 38=73#735#35#3#"''255'675#73#"''255'675#'7'iiCCCC9  'A9  '6 L ÉS110i   -i   -    I ëË 5;A73#735#35#3#"''3255'675#73#"''3255'675#&'7&'_{{VVVV"I  &7RJ  '7@  c  ËK-+)S  S   tÿêîË 5;A73#735#35#3#"''3255'675#73#"''3255'675#&'7&'gg@@@@5    "@5    "5  O  ËS11.j  $j  &     kÿêîË 5;A73#735#35#3#"''3255'675#73#"''3255'675#&'7&'{kkCCCC9    &C9    &9  T  ËS11.j   &j   &    5ÿéÂv 59=7#55#353#"''3255'675#73#"''3255'675#'7'¸p^LLk>   .I>   .?Qv88  ;   ;     TÿéñÈ 15973'7#3#33#"&''675##"''3255##535#35#`.++ (""  9‚  QQQQQÈ    # 3Ta  +y -[ÿéñÈ 15973'7#3#33#"&''675##"''3255##535#35#gˆ +(( & !  6}  LLLLLÈ   # 3Ta  ,y ,GÿéñÈ!26:73'67#3#33#"&''675##"''3255##535#35#U™3//,&$# ?‹  XXXXXÈ   # 4Ta  ,y ,gÿéñÈ"37;73'67#3#33#"&''6735##"''3255##535#35#o~$$$ $  4v  EEEEEÈ  # 3Ta  ,y ,xÿéñÈ 15973'7#3#33#"&''675##"''3255##535#35#€o    ,g  :::::È  # 3Ua  ,y -EÿéóÐ048<@D73533533#3#3#"''3255##5##5'67#535#35#35#33535#335X$$$$-uk      -$7$$ 3 S 3 ·n ((*S)0^7@-íÏ048<@D73533533#3#3#"''3255##5##5'67#535#35#35#33535#335R%$%%+vp   $%  .%9$$%%7$[%%7$ I > #C %  .LÏ 7#5'6:  ÏuU &[ÿéóÐ/37;?C73533533#3#3#"''3255##5##5'67#535#35#35#33535#335o%\Y   %0,E,¶n ((*] '^7lÿêòÐ048<@D73533533#3#3#"''3255##5##5'67#535#35#35#33535#335y TT    $, (?(¶ o  ))*b_7 ÿéöÐ&>D73673#3#3#&'#'67#5367#537#3##5'67#535'6&'X^d]g…+ &T&.?4?R’ NN!3 .!>P H%! ##¿    " H@9 <  SÿèôÏ%B73733#3#3#&'#'67#537#537#3#&'#5'67#535'6`:?B>CW  / +#*7b::  -: &½  N  #1eÿêöÏ !8>D7'6733#3327#"&547#3533#&'#5'67#7'6'&'ˆ  XdUUn [%##   G3  ª   *, 02**  (04   Vÿé÷Ï 5;A73#'63#3327#"'&5#3533#&'#5'67#7'6'&'vem  ffq _ ,)) )O4  Ï  BB U0'' .. 3  MÿéöÏ 4:@73#'63#3327#"'&5#3533#&'#5'67#7'6'&'qjs  jju  c -++ *U<  Ï  AAT/&& ..4  CÿéöÏ 5;A73#'63#3327#"'&5#3533#&'#5'67#7&'7'6gr{  pp}  j 1.. . QÏ  AAT0''/05  ÿéfÑ%)-7&'3&'73#67&'7&''5'635356   4    """Ñ   X/   Ž !3"@ÿéóÏ%)-173#3&'73#&'#'67#5367#53635#35#35#‹D;  <1 7> 25<4*\\\\\\Ï p   *$ p */0OÿéóÏ$(,073#3&'73#&''67#5367#53635#35#35#“=5 6+ 1A 4 06/%QQQQQQÏ p   (! p */0 ÿéõÏ&@FL7373#3#3#&'#'67#5367#5367#367&'#"''3265'&''6Vbgck ‹:% .G' .> 9BQ\   #$   ) +&(½  "  V    D    ZÿèõÏ(E736733#3#3#&'#'67#537#5367#3#&'#5'67#535'6g5;>=CU! + +%+2[ 77  & $+9!½  L &1 <ÿéñÐ'D73733#3#3#&'#'67#5367#5367#3#&'#5'67#535'6JBFJEKa! 9# "1(/>m!BB&" % $4B+¾  M  3lÿéóÐ)F736533#3#3#&'#'67#5367#5367#3#&'#5'67#535'6u/3626G  $  #",P // $0 ½    M  "/ ÿçô«'>D73673#3##3#&'#'67#5367#5367#3##5'67#535'6&'T^c]f‹3 &S' -B1;NMM3 +7L)6!' %œ     =42  3  gÿèôÏ(D736533#3#3#&'#'67#5367#537#3#&'#5'67#535'6o57:6;M  '  )"'2[11  !'1#¾  O'. ÿéðÐ $(,AG73533'73#3#5##5##535#35#33535#3353533##"''3255#&'a2 bTA@Ta!@@TA•@@TAº ,,  < ½ bc-.<     UÿêöÏ!%)-BH73533&'73#3#5##5##535#35#33535#3353533##"''3255#&'Y? C9&&8?&&9&_&&9&|m!!   m# » \\.-?     ^ÿêõÏ!%)-BH73533&'73#3#5##5##535#35#33535#3353533##"''3255#&'b; ?6##6;##6#Y##6#uf   f! » \\.-?     [ÿéò« $(,AG73533'73#3#5##5##535#35#33535#3353533##"''3255#&'^B# @:)):B));)d));)€i  i š   UU & ' 5    Qÿêöš $(,AG73533'73#3#5##5##535#35#33535#3353533##"''3255#&'UA E;(':A'':(b'':(€p""   p# Š  I I ! ,    ÿçï¯!%)-BH73533&'73#3#5##5##535#5#'#35#'353533##"''3255#&'e?  bS@@Se¸@@@S@S@eœ..  œ; œ  RT %  4    Mÿèò§!%)-BH73533&'73#3#5##5##535#5#'#35#'353533##"''3255#&'NG% H<))0 *&  Y ,)#!( #_ÿéôÏ4:73#'67'6753&'3#3#&''67#5365#'6'&'¼+a 31%#  % &!#  1Ï K=1 (&  \ , *$ ( !<ÿéôÐ$(,573#67&'#"''32655#53635#35#3'67#A8  +  8.!]]]]!:# %Ð [   (- Y[ 07;1%mÿëõÑ !'673#53635#35#35#&'&'''6733267#"&5£2c======$9 U   Ñ {{ 066,    #   ÿìœÐ !'873#53635#35#35#&'3&'''6733267#"&5G7l!GGGGGG* 8 Z   Ð || 165.    ! UÿïõÑ !2873#53635#35#35#&'7&''33267#"&5''6™;t&MMMMMM/8  E    Ñ ww .333    - ( ;ÿëô¦  0673#53635#35#35#&'7&''33267#"&5''6„E~$ZZZZZZ& Q  `  '  ¦dd & ( ' !     %  ÿëõ© !1773#53635#35#35#&'7&''33267#"&5''6lX¡4({{{{{{9 bz * 5%© hh ***%     !  _ÿëõÑ !'873#53635#35#35#&'&'''6733267#"&5œ8m"FFFFFF' >  ]    Ñ {{ 067-    # GÿéöÑ %-17#5##53&'7'67&'&''6#5##535#írB * K !+ '%5 < !!k$(A;UQB # +Ñ $  "" Q  3 'GÿéòÏ 73#3#"''3267#'665#53&'¤AeV B"DÏ\"B&5?Gÿéð˜73#3#"''3267#'67#53&' av  d) 3=k˜ @ &6!L  ÿôWÎ73533#7&'74''75#735#335   ¨&&a4  8???QÿéžÏ73&'73#3#"''3267#'655#]$#     ©j#QN0 0C0NÿéóÌ >T7'6655633#3'67#73267#"'"&55'75#'6553#'66533265#"&5Ý /?  H 225   ! : Ì.H: 6E;    '62 (56f0 0 ]ÿé¤Ï#'+73533#3#3##5#535#535#35#33535#335^  &  ¼_++_:=Tÿê­Ç"673#3#"''32765#'67#735#35#&''3#536aC$3 ,  !!!!   &6 ÇQY @ 03E   =§ÿéòÍ73'67#'63&''66»-      Í &,+'2_ÿèòÏ.8N733533##5##5#53#5'67#53673#&'##533&'#33533533##5#'67#**% #/=% $! =./Ïr   6 1// ŒÿéôÏ$(,73533#3673#3#5##5'67#535#35#35#ž  "/% "%%%%´$ u_oC\ÿé–Î 73&'73#3#3##5##535#\:22334²  VV3"ÿéòc73&'73#3#"''3265#'67#efˆr  c6 ?DM  - $  4•çÐ#)/7367&''67367&''6''67'6I   .j   .…mÐ * '    hî7#5##5î´(( ÿêèÒ;73#53'3673#5##53''667#53&'73#3#"''3267\Ï]" E 0©/ "1$;_\ƒx Ò %  ""x##  /]ÿéðÑ =73#53&'3673#5##53&'3#3#"''3267#'667#53&'ª:†8 !h$46RL  9 "$>Ñ $  "" +  1 $""ÿéîŸ:73&'73#3#5##53&'#367#3&'73#3#"''3267#'67#Ud'8µ9$G>T;Z^‰~ m* / 0 $$  ; $   ,ÿèí¬:73#3#5##53&'#53&'37#3&'73#3#"''3267#'667#„X* ;´A-[4 J9PSwx "i2$/¬ !! 7A  %  QÿéñÐ 37#5##53&'73&'73#3#3##5#535#53&'736ìu@S?>“f,GCCCCF(  ¹,, 2   ,, kÿéïÎ 37#5##53&'73&'73#3#3##5#535#53&'736í\5C00uS"844447  ¹,, 4   ++ DÿêðÏ,26:7#5#3&'73#3#3#3##5'67##5375#5#35#ì> ",5////8y 18+....´+  q'0 [((NÿêðÏ,26:7#5#3&'73#3#3#3##5'67##53675#5#35#î8)1,,,,4q 04!****´+  f%-0  \ '(fÿêðÏ,26:7#5#3'73#3#3#3##5'67##53675##5#35#ï0 $*&&&&-c % ."####´+   q"/  \'( ÿéê¤,26:7#5#3'73#3#3#3##5'67##536735#3535êc *KPKKKKS© ;2P9C;CCC’"  \# B " ÿéê-37;7#5#3&'73#3#3#3##5'67##536735#3535êc +HMHHHHP© <2P9F>FFF"  Y#A   ÿèzÐ+/377#5#3&'73#3#3#3##5'67##53675#5#35#z! H )º( v%(, _((ÿéÑ (0473#5##53'3533#3#3##5#535#535##5##535#L-J/$$$&&//00''$[666Ñ ##7      LN P1  ÿèˆÇ9?E73#3#"''3267'6765#735#73#3#"''3267'6765#735#&'7&'8 #    '%?9 #    '&6 K Ç:h!   <:h!  <_   ÿé€Ð)159=A77&'74''535633#"''3267#'67##5##535#33535#35#5  ;    6B*B*Ð .   LB 65 -Xq q+D ÿéÏ 0DJP7&'''667&'7''63#"''255'675#'3#"''255'675#&''&'[      1    71    B +  Ï !   )j   j  "     v|É*7333'67##"''3255'67#53&'767#h    *4  NÉ       ~sòÏ73#&''67&''667#—G    *Ï     ÿëzÇ .473#3#"''2765#'67#735#35#&''6'33#Y6I C  5555  '4EÇR Y> 12H    ,gÿéóÎ 17'2'6'&''&'3#35#535#53#5##56æ 6M@9 /    /""X$$%8XÎ  " #""… ~MÿèóÎ 17'2'6'&''&'3#35#535#53#5##56å @\LE@    8++p//0Cp Î  ##""„ tÿèóÎ 17'2'6'&''&'3#35#535#53#5##56æ 0G91  &    *L 3LÎ  " #""„  ÿéƒÐ $9?E73#53&'&''6'67&3533##"''3255#'67&'K (n1    > Y3++  3 Q  Ð  *       ? <# wòÐ!'-37'6'#3#3#3#''67#53567'#7'6'6Ø 707UQQQQ[ -3 '¦ ;3# "9:Ð       2H0   ÿîõË)/57'6#5353#3#3#3#67&'7&''67'6'6Þ %- (†#jWPPPP\I!# -2Á $+ '* < 6Ë%iu$  q'-oòÐ")/57'6'#3#3#3#&''67#53567&'#7'6'6Ö 6-5UQQQQ[ ,3 # ¤ 5/$ #6 ;Ð       6O 3      ÿçòs"?EKQ767'7&''667&'7&''6'67&'77&''67''6'6'67'6j !" =C"R  ^  -;A1'@7%"%S .26 /?DM ;ips                ÿèñy #DH73533533#735#33533535#335335#3#67&'#67'5#'6553#$:(;Á((:()((:()»¾N    @) #œœs0    ! _ÿéòÏ/373533#3#535#'67#53#67&'67'735#q000;Š;0 o1   4 II½  99   : 0  _eÿéòÏ/373533#3#535#'67#53#67&'67'735#w...8„9. l.   0 FF¼Ž 99   8/  _ ÿèõÏ/373533#3#535#3#67&'27'5'67#735#$RRRhæiR­F    P /#  +#0……½6<   ; $  '  Kÿÿî¡!.73533#3#535#3##5'67#735#67&'Z999G F9A )YY9  –   %+, "  ÿèó“/373533#3#535#3#67&'#7'5'67#735#$SSShäiS ¥@ O7%  &$*€€ˆ   &/   1     TÿéóÐ0473533#3#535#3#67&&'#7'5'67#735#g444A–A4x,    ! PP¾48  ( '  ' AÿêóÏ1573533#3#535#3#67&&'#67'5'67#735#[888F¡G8~.  #!   "VV½48   ' +  )  QÿéóÎ0473533#3#535#3#67&'#67'5'67#735#p211:ˆ;2p)  2    "JJ¼19  +. -  , hÿéöÈ/38<73533533#3#67&'#67'5#'66553'#335#35#’)   '   kGGH$  8: G." 2#`9(V GÿéõÈ'+/39=73#3#67&'#67'5#'66553'#33533535#35#Ï":   4  „bbbL/ 8; G,$ 1$`9(E WÿéóÈ&*.28<73#3#67&'#67'5#'6553'#33533535#35#Ó4 /   ~WWWC*    18  F,# ,9m9(D aÿéòÈ.27;73533533#3#67&'#67'5#'6553'#335#35#„.  +   uPPQ&    9; G+% 4A_9(V TÿéóÇ.28<73533533#3#67&'#67'5#'6553'#335#35#{5 /  €ZZ[+Ž    17  E," ,9l9(V pÿéòÐ "&*73#53&'3#735##"''3255##53#735#²72cc??_   YBBÐ -1/[  Dbt!5[ÿéñÏ!%)A73&'73#3#3#3##5'65#5#5##533#"''3267#7#'6‚$*1****2i 2#####l   !). 'Ï     B    /) <  ÿéìÐ%)-E73&'73#3#3#3##5'65#5#35##533#"''3267#7#'6?7 KLDDDDN« #VGJJJJ.¤ 2 9 G@ 8Ï  Q   ! A#1 ÿéªÏ,B73#&'#5'675#535'23#5'675#5373673267#"&5‘ AA#  (>>;$  >   ÏLG? 'K=K      aÿæôÍ,A73#&'#5'675#535'23#5'675#5373673267#"&5Ú ;;' $ "668!  9   ÍI-!AB$,H@J     DÿéôÍ,A73#&'#5'675#535'23#5'675#5373267#"&5536Û "JJ0 +& /EEB)!!u    ÍB3'E@$+I9Q    ?oÿéòÏ,B73#&'#5'675#535'23#5'675#5373265#"&5536Ù 33!  "33/  X   ÏK)@>!&N8S    >FÿòóÏ @7'67&'&''667&'763#3#3673#53&'735#535#ˆ $"I   !# -$) $  Go/BB  %¨$ >>,Ï      =* * ÿðõÏ 9?7'67&'&''67&'763#3#3673#535#535#&'^ *++j'& &' /1 >-#F <"a=[[  7ßeZZ>  Ï           :++,  _ÿéšÐ 7#5'673#~  Ð ¤v5ž sÿéóÊ -273#735#35#3#735#3353353&''67&'#367€ggCCCCxx"dp $  !  ÊE* ' '41   mÿïôÑ6:>73&'73673#3#3#33#537#537#'67#5367#537#37#37#{ 1-1AHJy#   '"&,:! °    K  x:VÿïôÑ6:>73&'73673#3#3#33#537#537#'67#5367#5367#37#37#j    :59LUWŠ) $-(-4A&& (&°   K x:ÿéòÑ%AJ73673#3#3#535#535#53&'&'367&'#"''3255#3'67#T 1 7`UUgàfUU_600//e ! 4 PU4-=Ñ      ^    "$ 8 # 7ÿéôÑ$@I73673#3#3#535#535#53&'&'367&'#"''3255#3'67#ƒ  'C==I§J<22;(&$$%_ " 1Dz9/ ! $²      ;     " @ 2 >ÿè÷Î >W7'655633#3'67#7325667##"&55'75#'65533265#"&55#'6655ç W7'6556'6553533#3'67#3#3325265##"&=#33265#"&55#'6675æ 7I O!+++8 *  4    Î GF; 7=Xm0% &,C(    "6. '<ÿè·Ï 7;L7'6655633#3'67#73267#"&55'65535#7'5#'6655¦ "-  5 $     -  Ï /F9 5E:    41 (3:&+!  $1 §ÿèôÏ73'67#'6'6553&Ä#     Ï ! y&+>  ÿè›Ï 48H7'655633#3'67#73267#"&55'65535#7'5#'655‰ -;@../    :  Ï HF: 8>$ 6.$  1     .$KÿçíË#8<B73#353#5#'67#5335#536'&'7#"''3255#'665535#35# %' ! 0 "”    ËI-l- :ÿçíË#7;A73#353#5#'67#5335#536'&'7#"''3255#'65535#35#ˆ *, $ ); (  £ $!!"!ËI-l-  ÿèëÐ#7;A73#353#5#'67#5335#536'&'3#"''3255#'65535#35#e 50 (&3G 3  Ð  6 4454ÐG3V1%E2GÄ  51$&TT<*f+ _ÿêîË!59?73673#353#'67#5335#7&'7#'62655#'65535#35#_+      Œ žG1A0 &A1G= 50( 8=`:(d)JëÐ%8<B73673#353#'67#533655#7&'##'3255#'65535#35#L3  $" $ #  ˜  §)9/  !9)8  —  "$ "-X1 O JÿèôÐEIN733#3'67#73267#"&553&'73#&''67&'#'665535#67–88G6*, &.:#   6# Ð      & )M%K hÿéòÐJ733#3'67#73265#"&55'75#3'73#&''67&'767#'6553¤$$:&   %+   B +Ð     D      % -3NÿëõÐ048<@73#3#53&'#53&'367##53#327267##"&55#;5##;5#Y&=Þ<$Z 9PS¼U  2 ??AA??AAÐ    > ‡WW  X!GÿìõÐ-159=73#3#53&'#53&'367#3#3267#"&55#735#33535#335žD0¬.<#;2>  $<((;+f((;+Ð   ; -U  33]ÿêøÑ+/37;733#3#53&'#53367##53#3267#"&575#'#33535#œ=*—'8 66ƒ9 %:&""""&&Ñ / †VV N ÿìó¡/37;?73&'73#3#53&'#367#3#336767#"&55#735#33535#335ZY#<Þ:"C< X.¥K # 31G44G844G8Ž    $H   , ) ^ÿîóÊ047#3#533533##5##5#533673#3#5'67#35#ð€ƒ•0)),>>>[R.C!?     / ËN/,4## +     ÿððÐ/37;735#5#53533533##3#3#3#3#535#535#535#75#35#335$R155O550PP[[TTfßeRR\\RƒO!??S=€  2    Q ?TÿïòÏ/37;73533533##3#3#3#3#535#535#535#535#5#5#35#335^6(::==77EžF22::77!d6$$7(¾11?5ÿîî§/37;73533533##3#3#3#3#535#535#535#535#5#5#35#335?$<$$)DDGGCCR¹UCCIIEE'$s<22D0œ  (      (  3 &–Ð048<733533##3#3#3#7'75#535#535#535#5#53#335#335+,0000,,-6A8..3333 >,,> 3Ð  &  &  -  ÿïƒÎ159=73533533##3#3#3#67'75#535#535#535#5#5#35#335 $..--&&/90**..--M$-½ 2      2  >Fÿîô /37;73533533##3#3#3#3#535#535#535#535#5#5#35#335HG.@@EE??N®N@@GG@@.xG..@.“  )      )  4 ÿíòœ/37;73533533##3#3#3#3#535#535#535#535#5#5#35#3351V226QQ\\VViähVV^^RR41šV@@S@” *      *  2  ÿì“Ï159=73533533##3#3#3#67'75#535#535#535#5#5#35#335 '1133--9D9,,22//V'/» /    / ;uÿïòÏ/37;73533533##3#3#3#3#535#535#535#535#5#5#35#335u(0011))4}6))22.. R(.¾22 @ÿéñ£  $(73#53'3#735##"''3255##53#735#gád?££{{§ ©-vvQQ£  &+ &F 2IY) ÿç‚¡ "&*73&'73#3#735##"''3255##53#735#1.rXX44R  N 22  % F  0L]' ÿéóŒ "&*73&'73#3#735##"''3255##53#735#giå(””ll   ¨1llHH€ # =  )CR& ÿè‹Ï "&*73#53&'3#735##"''3255##53#735#N2}5XX//Q   N77Ï  -40\  Fct5`ÿéòÐ "&*73#53&'3#735##"''3255##53#735#ª>’<$mmIIk   g$FF""Ð -1/[  Dbt!5RÿèöÏ676767#535#5673#35335#535#53#3&''67&'y i67$$.78'/"%  6 [ ]]^"  BÿèôÎ473#35335#535#53#3&''67&'767#535#56|""--"""5@9 & /$!* $ s@@Æ]]]   Z<íÔ  $(73'73#3#735##"''3255##53#735#adÚ)ŠŠff˜   ¥0jjEEÆ! 5 ":I$ ÿéóŸ "&*73#53&'3#735##"''3255##53#735#kåd9——qq¤   ¯7ggEEŸ&+'=  )FU+ÿéíˆ "&*73&'73#3#735##"''3255##53#735#abØ"””llœ    ¨1llHH|# = )BQ$ ÿçíª !%)73#53'3#735##"''3255##53#735#†_Ùd;““kk›  ¥1jjFFª  (+ 'J  5N^+ Qÿèí¢ !%)73'73#3#735##"''3255##53#735#QCEœooHHr   r$PP--“  ) %G  4O^* )è°  $(73#53'3#735##"''3255##53#735#‡ZÒa@••mmœ  £(zzVV°! ) 0<! ÿ錤 "&*73&'73#3#735##"''3255##53#735#27}XX44U    P::” )&J  5O_) ÿéÐ "&*73#53&'3#735##"''3255##53#735#T65 bb==]   WAAÐ -1/[  Dbt!5QÿéòÐ "&*73#53&'3#735##"''3255##53#735#¡D¡F(vvRRu  q&LL((Ð .1/\  Fbt!5ÿèòÑ !%)73#53'3#735##"''3255##53#735#‚eäg<œœtt¤  «1ppJJÑ  -41]  Hct 6ÿé{Ñ "&*73#53&'3#735##"''3255##53#735#K+i*QQ..J   D11Ñ .32[  Fbs 5eÿéòÐ "&*73#53&'3#735##"''3255##53#735#®;;"iiEEf  a!DD Ð -1/[  Dbt!5\ÿéñÏ'/37;?76767&'7&''73#"''3267#'67##5##535#33535#35#\!   IL    @_&&9&_&&9&&¼  1  TF 0=1Vr r+CcÿéñÏ&.26:>7677&'7&''73#"''3267#'67##5##535#33535#35#c  FH    =Z$$7#Z$$7##¼  1  TF,>1Vr r+CpÿéïÐ&.26:>767&'7''56#53#"''3267#'6#5##535#33535#35#ž   2@ /P1P1Ð 0   MF.2 )q q+D0ÿîí¤!)-1573533#3#67&'7&''67#535#3#53535#35#35#L>==Nf $':@4P>’½,+•    U///?ÿíòÏ!)-1573533#3#67&'7&''67#535#3#53535#35#35#]888DY "$5<*D8³'(¸    h>>>-----˜ÿîõÎ (,0473533#3#67&'7''67#535#3#53535#35#35#¥ #   "J] ¶   hCCC55555<ÿéžÌ 'CGVZ`73353353#73533#3#3##5#535#535#73533#3#3##5#535#535#3#3#7'7&'#735#767#A  Y  &  8__O %.--ÈVZZUaV      Y )   5  ÿéiÏ7367&''65''61 '  Ï3 ) /D:,! ^ÿêöÏ"73#&''67#5353533655#335å>' %,2 :000§N14/ #-N((N ** <_ÿîõ½7'67#53&3#3#535#¬# HXo  [u3;@.q 4 77`ÿéöÃ473#67&'#"''32654''67''67&''67#o|:    !   $ (! " ",à  $  "'!^ÿéôÏ:73&''67&''667#3533#3#3##5#535#535#ŽG  $)" ,>*422..<<??..4Ï       >eÿêóÑ -73&'73#3#735#33##"''3255#53567#k86… qqLLx<<  ??X½ 43   \ÿéõÌ&.26:>73673#&'#5'67#53&'735'2##53#'#;5##;5#à 81 "# !.4=2Y}5$$$$$$$$Ì   (*  ÍggW$WÿéôÎ!8N7#53533#&'#5'67'6'&'35#53533#3##5#''3'67#&''6Ž,==. Y  I?$$() 1   “** )* J  Š((""J ,28 XÿèôÎ1K73533#3#535#3533#3#535#'3533#67'75#3#3267#"&55#'67#l222<ˆ92@LK #“-   0 '&¾5 2! $2  # ôÐ(,0473533533#3#3#&'#'67#5367#535#35#35#35#;;;;#_ ƒ0 ) G% '@1";N;;"€€€€Ã  I   I ( ' VÿèôÏ(,0473533533#3#3#&''67#5367#535#35#35#35#]##$$6D:- 02 ,195#7##XXXX¼R  %'   R40JÿéöÏ'+/373533533#3#3#&''67#5367#535#35#35#35#T((((8K>. 5<4;C6(;((\\\\¼R '*   R40bÿéöÏ'+/373533533#3#3#&''67#5367#535#35#35#35#k!!!!2?4( -0 (/6-!4!!LLLL¼R %'   R50 ÿévÈ #'+/73#735#73#735#3#3##5#535#735#33535#335..%//A\&""//&$8$È7 62V%%35TÿéòÐ>_73533#3#&''67#537#'67#5365#53533#3#&33#3##5#535#'67#5373#3`  i    644AAJJ: (V_ #¿   /     -     ÿèòÈ!'-39?73#3#3#535#535#735#&'735'6'67&''&'7&'&³PRReÜdRRP<<  B<  vµ `  N  È[9 99 ‚      ÿèòÈ#)/5;7#3#3#535#535#55##5#35335'67&''&'7&'ÙPRReÜdRRPŸ<<<<›µ `  N  È[[%u     ÿéõÏ$7#5#'667##537333267#"&5ï,  ¤6$EICA$6++GV DÿêíÇ"7#'67#'67#533#"''32765#'6µ94%<'[{+/k   DBd4&"*"&k! EL-)TÿéëÇ"7#'67#533#"''32765#'67#'6‹$9$Ut'-d    <8  0,d"&i# ?L/)B5&#mÿéìÆ"7#'67#533#"''32767#'67#'6™0Fb!$Q    1.  &#d#&i# AK0*A4'#ÿérÐ7#5##5##535335#33535#35#r(((=(ª€ MM €&&1 R ÿògÏ 7#5##53675#35g...®µ¼ M::::ˆÿéíÆ"7#'67#533#"'532765#'67#'6¨%7O?  %#  d#&l JK0+A5& #Xÿì“Ë 7&'&''6i   & Ë  ,  0 /-*bÿéíÇ"7#'67#533#"''32767#'67#'6’4"Om$)[    74  *&d"&l  >L/)B3%!ÿéäÇ!%7#"''32655##5&'''63#735#ä  ž€/bb<<ÇÄ ¬ËÞ & &O+RÿéìÈ $7#"''3255##5'67&'3#735#ì  s6  7  *HH""ÈÅ  ®Ìß  4O/eÿéíÈ $7#"''3255##5&'''63#735#í  `W  @@ÈÅ  ®Ìß  &O/MÿéôÎ67'673#3#535#5367#'7#533327#"''67&'º ' MP""&;",,1   ° 6;;. 67$UÿéòÎ57'673#3#535#5367#'7#533327#"''67&'º $ HL!#7(*.   ° 4<<0 !68% ÿêõ‰4735'673#3#535#67#'7#53333#"''67&'l6+7. 660q-6D .%1J#) JJCS  R        ÿèôÂ767#'7#53333#"''67&'+ 1-7O-OIAS O(5:( ÿéöÂ767#'7#5333#"''67&'& ($1H$TKDV   S(4># míÎ73#3#535#535'6Ù662w166* EÎ2;;/0ÿùÎw673#3#535#535#'2'3333#"''67&'767#'7#º $$O$$ $a4  4.)?  w     TÿòôÎ73533#3#3#535#535#'6r%9966?—D88+ Á667667 (MÿòòÎ73533#3#3#535#535#'6m&;;88A›F::, Á667667 )lÿòòÎ7'673533#3#3#535#535--++2}7--…(2667667€ðÎ7'673533#3#3#535#535› %%""*n1%%‘ !$++,,,, gîÐ73533#3#3#535#535#'6/=[[OOaÛgLLE  Ð   ÿñðƒ73533#3#3#535#535#'65:TTJJ`ÛfKKC  €ï73533#3#3#535#535#'6•##!!&d,$$ ‹  zÿòóÎ73533#3#3#535#535#'6((&&-s2''¾666666 )~ÿóóÎ73533#3#3#535#535#'6’&&$$+g)&&¾ 666666 %§ôÏ73533#3#3#535#535#'6· C ¾ ++,++,   ÿèîÐ#'+/5;733673#5##53&'733#735#3#735#35#35#'67&'u -².  A™™qq¬¬„„„„„„( 'c%% %&Ð  )* ( &Z? $ #      EÿèñÏ#'+/5;733673#5##53&'733#735#3#735#35#35#&'''6’   }!  .llGG!‹‹ddddddH" $ , *Ï '(  ) $^C " "   ÿè¦Ñ=Y]aei73533#3#&''67#5347#733#3#&''67#5347#533533#3#3##5#535#535#35#33535#335  _  `9<<66AACC669##6#Y##6#Ä       T  ?  ? ! RÿîñË!'-39?O73#3#3#535#535#735#&'735'6'67&'7&''&'3533#3#535#^‡:66C˜B55:((  5'  V 0` F566FŸF5ËG    # ##  d       " ^ÿîñË!'-39?O73#3#3#535#535#735#&'735'6'67&'7&''&'3533#3#535#g€611==117$$2#O /YA111@“@1ËG    # ##  d       " dÿïñË!'-39?O73#3#3#535#535#735#&'735'6'67&'7&''&'3533#3#535#ny3//:…9..4"" 0!K +V?.//==.ËG    %  %%  c       $\ÿèòÐ159=AQ73#'6553&'3#3#3#&'7#'67#535#535#735#33535#3353533#3#535#³3u=d)&&- '  -(()*A*P&''/q0&Ð PB3 4<[&>       & " b4ÿéòÐ159=AQ73#'6553&'3#3#3#&'7#'67#535#535#735#33535#3353533#3#535#¥AŽH#x2119 & !8223 3 S 3 d000:Š<0Ð OA2 3=Z'@ ' " d   EÿéòÐ159=AQ73#'6553&'3#3#3#&'7#'67#535#535#735#33535#3353533#3#535#­;€A n.,,3  # 3--..J.Z,,,5}7,Ð OC1 3<['@ ' " d   `kóÐ873533#3#&''67#5365#'67#5365#53533#3#&i  f    Á    )  _ÿèñg 73#735#35#35#'67&'lxxRRRRRR K gY? # #     KÿíòÏ'-3;U7367&''667367&''66''67'6#5##53#3#3&'73#535#535#s    P     i  T  I€o/33™B33.Ï      %       ?00!  ÿéðÏ'+/37=C73##5#'6655673##5#'665563#735#35#35#'67&'p !+Q  4’ !-T% 5™°°ˆˆˆˆˆˆ! 2 .^', .)Ï$  UfH ( ( !    WÿèòÍ%)-15;A73##5#'655673##5#'65563#735#35#35#'67&'Ÿ / "a / !hZZZZZZ GÍ $$ %  $$ %TdG % &     aÿèòÍ%)-15;A73##5#'655673##5#'65563#735#35#35#'67&'¢ ,  ] ,  cyyTTTTTT DÍ %% %  %% %TdG % &      ÿëòÏ5Bj73#"''3265#'67#'6'3#&'#5'67#535'2&''6367&'#"''32655'67&'767™C      $ #  $-#*B (- 80%E H"    #%   +"   Ï A)*   $$S         XÿéóÐ7Dci73#'#5'67#535'273#"''3267#'67#'6&''6367&'#"''3255'67'&'š     ;*    % "## 0       Ð"   ; '% 6           ÿèô¥1D\bh73##5'67#535'273#"''3265#'67#'67&''67&'367&'#"''325'&''6l ''  !+#+UE      . 25 94/7 ,+ % %$#)+! 3¥ '   5 !     ,     ,    ÿêîÇ&*=A73#3#5'67#35#3#3#5##5'67#35#'3#3#5##5'67#35#Î}—*6:pp1i9 =* &**—i9 <* &**Ç9$ 77M .B7M .Bÿéï&*=A73#3#5'67#35#3#3#5##5'67#35#73#3#5##5'67#35#Í‚ {–(3:pp@h<<( (((Ii;<( )(( 1  )*B (2. B (2 ÿéóÊ#'+/T73#3#3#&'#'67#535#535#735#35#5#35#367&'#"''3255'67&'77*­&00<0! *N) !,=00&ˆˆˆˆb;;;  !    (   ÊI     , ' *  %     RÿéõÈ#'+/T73#3#3#&'#'67#535#535#735#35#5#35#367&'#"''3255'67&'767iu$$,% * %)%%PPPP:%%%  !   ÈC ) ' **     @ÿé÷É#'+/V73#3#3#&'#'67#535#535#735#35#5#35#367&'#"''3255'67&'767Y€((0' !1# /4))ZZZZC(((     "ÉD ) & ))     ^ÿéöÈ#'+/V73#3#3#&'#'67#535#535#735#35#5#35#367&'#"''3255'67&'767tl&  $  $GGGG4   ÈC ) ' **       fÿéöÉ#'+/S73#3#3#&'#'67#535#535#735#35#5#35#367&'#"''3255'67&'77yl#! " #HHHH3    ÉD ) & ))    nÿéöÉ#'+/T73#3#3#&'#'67#535#535#735#35#5#35#367&'#"''3255'67'767~f     BBBB/  ÉD ) & ))      UÿéóÆ #'+37;?C73#735#33535#335#5##535#33535#35#'#5##535#33535#35#h||##5#X##5#!) ) 7) ) ÆT262xx-L(ËE* % $B( # (B( # BëË #'+/37;73#735#33535#3353#735#33535#335'3#735#33535#335,¦¦66I7€66I79dd(>(Ãcc(>(ËB( # "?& ! '?& ! ÿéìÈ #'+37;?C73#735#33535#335#5##535#33535#35#'#5##535#33535#35#*««88K8ƒ88K8+=(=(N=(=(ÈW450{ {/K@{ {/Kÿêîœ #'+37;?C73#735#33535#335#5##535#33535#35#7#5##535#33535#35#,©©88K8ƒ88K8GC*C*ŸC+C+œL-.*Z Z"01Z Z"0Fê¬ #'+/37;73#735#33535#3353#735#33535#33573#735#33535#335,¥¥88I888I8©aa&=&%``'>'¬1..\ÿéóÆ #'+37;?C73#735#33535#335#5##535#33535#35#'#5##535#33535#35#nww 2!S 2! &  &  5&  &  ÆT262xx-L   9    j5  _d__™  *0   6* 0 O   ÿê_Ï &,73#"''3255#'67#5353635#&'&'6 """ Ï ² Q=, -2KZ: H ÿçõ¯ (=MR73&'73#67#5&'7'567&'3#"''3255#'67#3353#5#'66535#gdà\    m &  ¿J    ‚ˆ „ƒŸ    '. 0  -$  ( [  ÿçõ„&;KP73&'73#7'567&'67#5&''3#"''3255#'67#3353#5#'66735#e eà½&  h   OJ    „Ž  ‡„y   !  ,  !  H  iÿé÷Ñ%7FL73#53'7#5&'67&'67''3##'3267#'67#3353#5#'6535#°:…7  -  V-  AD BAÑ  !   ,2  #"  =/  7i%  /ÿäó_ *=LQ73#53&'67#5&'7&'#67'56#3#"''327#'67#3353#5#'6635#‘Y½Q l   –?    nv  po_           5  ÿçõÐ'<KO73#53'67#5&'7'567&'3#"''3255#'67#3353#5#'6735#zdÛ`   l #  ¼J    ‚‡„‚Ð    **#  : % 71 % : g'.YÿèòÒ+/37;?EK7#35#53533533#3#3#535#'6553&'75#35#33535#335'67&'ðs/*f*2;*B*8 9¿1  LL @4 2>[ C<-"   ÿè¢Ò+/37;?EK7#35#53533533#3#3#535#'6553&'75#35#33535#335'67&'¡u1*g*2; ,D,: :¿1  LL C1 4=[ C<-"   \ñÐ*.26:>DJ7#35#53533533#3#3#535#'6553'735#35#33535#335'67&'ñq-%\%/7';'2 0Ä3 AA ;. .7RDC #   LÿèôÒ+/37;?EK7#35#53533533#3#3#535#'6553&'75#35#33535#335'67&'ò€  7/p/9B /L/> >¿1  LL @4 3=[ C<-"   NÿèôÒ,048<@FL7#35#53533533#3#3#535#'66553&'75#35#33535#335'67&'ò} 6.n.7 @ /K/= =¿1  LL A4 0"\ C<-"   7ÿèôÒ*.26:>DJ7#35#53533533#3#3#535#'6553'75#35#33535#335'67&'òŽ%"%?5}5>H """5"W""5"C" C¿1  LL A3 3=[  C<-"   UÿèôÑ*.26:>DJ7#35#53533533#3#3#535#'6553'75#35#33535#335'67&'òx4,j,4< ,F,; <¿1  LL B2 3>[  C<-"   MÿçªÏ/37;?CGN7367#535#5#53535333##3#3&''67&'7#735#33535#33535#33567N""$&     +  0#!  8,   ,  € ) F; ÿé˜Ñ 57'2'6'&''&'#"''3255'67567#53„ /D75  %!  9  TpÑ  ] ) "  ÿê‰Î!18P7#5'67#53533#&7'6'&'3'67&''667#35#53533#3##5#'V !//#  >     0 †"$  ++ S  b,0   (( ÿêŽÎ!17M7#5'67#53533#&'&'7'6'673'67&767#7335#53533#3##5#W "00# ?  Y P  3   % ‡#%  ++ T    ” "P   ((!! ÿèŠÐ #'+/5;73533##5#3533533##5##5#3#735#35#35#'67&'*,,* ,,hhBBBBBB  8 À gI *) "   E ðÐ"8O7'63533##5'67#7&'&'3'67#&''6#'735#53533#3##Ñ sAAA# * ^ F"! 1   c" $$Ð  ##21 4 0  !' &  6 ÿérÐ"&3@73#3#'6553&'5##5#35#33533567'533367'533C&L  %    '  Ð ;>+ 49Y =,]  V  VJÿéóÀ73&''67&'#367Y~%, +- .!  ÀP9"$$$1F<)05MÿîõÑ $73#'6333267##"&5477#‡bl'dC#  +DDÑ " @  ASÿëôÏ73267#"&''7&'37&'íX (0 --!  “ ;8 >I ))C GÿéùÄ'73'#"''327&''67&'765#67#[† (   42  0> .Ä`22' 2'#7&(W )L5LÿèøÐ 7&''6&'3'67#š%+ &(&3!  4†$ *pÐ&*)# *?0MÿéçÉ7#"''3255#'665535#35#ç  WSSTSÉÅ  /," 1$_?,k, kÿéëÉ7#"''3255#'665535#35#ë  E CCCCÉà ,,# 1%`?.n.i¼ 7##55#35i>>**¼£¸I6666+ÿèÕM 7#5##535#35#Õ‚‚‚‚‚Me e%7 ÿæéÊ%)-73#3#"''3265#'67#'67#'67#735#35#-©t — OK!!? 7*)Ê] L4<% 0- 79OÿéòÏ73#3##5#535#'6wi9>>LL$ Ï BbbB!ÿéîL73533#3##5#535#'6@/RR``hh? I   ÿêôÏ73533#3##5#535#'6A4TTccpp= !Ç00<SS< *ÿéì73533#3##5#535#'6C.HH``dd9 Œ ###88# iÿéóÏ733#3##5#535#'673¬++33==!  Ï1=RR= $.YÿèîÏ"'73673#"''3265##5'67#'67735¤7  #& *" .##Ï"7E(hZ0 ) ($H6TVòÏ7&'3265#"&''7&'7º  ;D   ( 85Ï      ÿéîƒ73533#3##5#535#'6:8YY``iiA   55  rÿéèÐ 73#5##53635#35# <N$NNNNÐ ÄÅ^:‡:mÿçóÐ+17#673267#"''67&'#67'53&'37&'ð8  !$ 6  ¦: 1*!Hn  *(   FÿéôÏ73533#3##5#535#'6q#<*PPffff5 É ZÿñòÎ#73533#33#53535#35#35#35#35#g988-˜-9 FFFFFFFF·‹‹;665]ÿìõÐ>DJ73673#3#&'#3#"''3267#3267#"&55'67#5367#7&'7'6l-:?N * J  +)) &([¦  *8 B  5   ^ÿèöÏ*0673533#3'67#535#&'36533#'67#7&'&'q///8 q9/ C8>; ,; Z ¸ 1  5*'&>   <ÿèîT73533#3##5#535#'6N!IIaaii/ Q!!  hÿéïÇ&,7#"''3255#353#5335##536735#&'ï  ( J(4a ÇÆ  pK\ÿéõÏ"&8<@73#&'#5'67#5367'23&'#35##"''32655##53#735#Ø I# V )"25 22X   WBB!!Ï   @-)Q ;Uf.\ÿèöÐ`f73673#3#3#535#535#53&'3&'33#67327#"''67&'#7#"''3255'75#535'67&' 811:Œ=118 '-+  !   #"UÐ      _   %      ÿèîp73533#3##5#535#'6D+IIaaii7 l ,, WÿóõÂ73#3#3#535#535#`?;;EžE::<ÂKKKKQÿéîÅ!73#3#"''3267##5#735#3355#\„;I  3@ 45I';#ÅI 5 aaD$$$V RÿìøÏ$7335#53533#353#3267#"&55#^%CCII&9   ( 7ŠAR RAS/ 5GÿîøÐ"7&'332767#"&57&'#'6“$ Q  bÐ.‰ }'. /&7-+ ÿèïÐ"'73673#3&''67&'#'67#67Fƒˆq. )%%2 0 ( 7A`   '   "4$7B1[ÿçøÏ*733327#"&547#&''67&'767#53Ž4  "$  Ï,6&$#&"&6 )! 67 DÿõòÁ73#33#537#537#5#P”J G®' &) 5t6Á&C!EÿéõÏ73533#3#&''67#53655#]788EE . 07 ;=A7¤++<:5 $2MÿèõÏ7367&''>''6—!+ 1= " ' ÏX  !=';B-633# $ZÿèóÏ7367&''>''6œ ( ,6  $ ÏX !=&:A.723# $dÿèìÏ%735#53533#3#3#"''3267##5#535#l...8833> +66.€; (VVŽÿèöÏ7367&''>''6º  #   Ïa A 24.82& !@ÿèöÏ067367&''>'3#&'#5'67#535'6'6º  # ,    !! %3  Ïa A 24.8`' db,""& ! ÿéôÏ73&''6676''67'6uO JW 0-9 žÏH [$*BK!4!2/! !'/" !ÿçöµ73&'#'>''67'6v.B B(S (/0•µ2 I@?!.;+ % -TõÒ73&''67#'6d]1$&73E j9U 6Ò   4 ÿçô7367&''66''6w  + HU<)7*?<<# )gÿèôÏ73533#&'#5'67#r181! #*¡..I <‚5#*AfÿéíÃ7#"''3255##53#735#í  a&<<ÿ  §ÇÚ7b@fÿéôÂ'73#3#"''3255#&''67##5365#fŽ=:  ' $9?‹  s  1’¥_ÿçô 73#735#&'''6xii??:)ÂzTv & %" -eÿêóÌ!'73##"''3255#535'2'6'&'ß >>  ==9.  M  ÌXA  <V#  ÿêíÏ#)733##"''3255#'67673'67&'{QQ   N _@DPC6 Š 4V  R -( "$ %! %"!TÿèñÆ!%73#"''32765#'66553'#33#735#p  \ ySSJ<<‡] AA3 1#^?-M>`ÿéòÐ !%73&'73#3#536'&'#5##535#i24|\ )’T 8  mMMM¸  R[ [>+{aóÏ73533#&'#5'67#‡%.% ³13UÿéóÊ73#3#535635#'67&'Í-0f(›9$22PÊ  8{€8Q ]ÿéóÏ#'73533533#3#535#35##5##535#35#e)"–$0))NLLLLL±  Crr'=dÿðóÐ %73#5##53&'&'''63#3#535#«;b8,  !t2?ŽE      |<<>w6(  ($‘ `ÿéîÇ #04873#5#73#5#&'7&''67'63#5##53635#35#`E2KC0B P ! X%9U-UUUUÇ_M_M       j j /;“ÿèðÏ073'67#&''63'67#&''6¹&! 4  "%;  Ï )0   D 0:   ^ÿê÷Ð59=A73533533##3#3#3#&'#'67#5367#535#535#5#5#35#335c4$6668D4( 02 &1:3544#a4 3"Å  2  "& 2 ? dÿéôÊ -273#735#35#3#735#3353353&''67&'#367rssMMMMˆˆ'q (!  #  ÊE* ' '40   YÿçôÈ#'+1773#3#3#535#535#735#33535#3355#5#'67&'jz $& !!3!T!!3!  K! ÈZ66;)   @ÿéòÏDHL735333##35#53353#5##535##5#3#5#'6753353#35#535#535#33535\;::,!!,.%!-<O7µµÈ   ")  *") XÿéõÏ#'9=A73#&'#5'67#5367'23&'#35##"''32655##53#735#Ø K% X *"4644Z   YCC""Ï   @ ))Q ;Uf.ZÿéóÒ-2=CGKQ73&'73673#3#3##"''3255#535#5365#5#35#"&55#733567#35#&'Z(  #0'""  bbZ+5V)a Laaa  ²  ]    ]  5    ? =   MÿêªÑ .73&'73#3#735##"''3255'7567#536URDD9#  %( 5M½ 4^   RÿêõÏ!%)/5;A73&'73#3#3#3#5'65#5#5#'67&''&''&'€".0++++3‚ 9))))) }     Ï Q ,&&  TÿéôÏ17;?EK73673#67&'##"''3255#5'67&'767#33&'35#35#'67&'b)B#  *   )    !8 > +AAAA c·    90  -5  F-)   VÿçìÇ(AOS73&'767#533'67##"''3255'67#'#53#"''3255&'#75#673&'35#`9 ]x/   ) /'ˆ  ?7 a 4        c Gye  / -K   3HÿçêÇ(AOS73&'767#533'67##"''3255'67#'#53#"''3255&'#75#673&'35#R? e‚3  $  - !5*“  F<k  6 $$      f Eye  0 -K   3>ÿéöÏ#'+17=C73&'73#3#3#3##5'65#5#5#&'''67&''&'r& 13----6x #C/////g  n  b    Ï   [ ($% \ÿéóÌ $(6<73#535#535#3#3##"''3255#535#735#'3#67'75#&'mvvc[[c>C   ii^C  %  ÌK  @:  K  UÿèöÑ )/C73#5##536'6'&'3533##5'67#&'3673#&''67#J\#3/ "!!   C  V7H9+ 28 *0Ñ~no "   $$-    2  && aÿéòÏ;?CGK733533##5##5#533#3#"''3255#7'7''275##535#735#33535#335„$$$$##|4?  , &+>5!!4"V!!4"Ï  $N D  /  J[ .,QÿêôÐ 7=SY733#3'67#&''67373&''67'767#'63533#&'#5'67#7&'€. %    0B   . h=D8  # &3 Ð !    #   W  ""  /,1 VÿèôÐag73673#3#3#535#535#53&'3&'33#67327#"''67&'#7#"''3255'75#535'67&'‡ :22<‘@33:)/-  "    $$#XÐ       _   %      XÿèôÏ%4:@X_7'23&'73673#5##53&'&''33267#"&57&'''63&''67&''667#à 4K>" i?     L  Q H  ' (!  - ; Ï    ###    !         `ÿéðÊ#D73#3#5##5##535#3#73#3#73#3#3#"''3255##5##5##537#i~6>+,>5""?!!F((F**TŽBC  08Ê (;;( ) :  $8888=N UÿîôÏ#)/5;CGKO735333##3#535#535#535#33535&'''67&''&'3#53535#35#35#l.77F•<--77.A$$$ d Ta Ÿ%%      ;      333#####YÿéóÏ$=EIO73533#3#535#3'67#3#3#535#3#3##"''3255#535##5##535#&'c;;;4z4;”e%B‘<-š --s?BÅ   $    #   D D*  SÿèôÏ$_s73&'73673#3#3#535#535#&''3&533#67327#"''67&'#&'#5'67#535#'63#3#"''3267#7#^  #<44@Ž<44%&?8-T0%! ÿéó’767&''66''6y " , M 8' 56= ’ % G#7 G1' ^ÿéõ‹7367&''65''6Ÿ ( .1 A  ‹6 +**%6&$ ÿè÷Ï7367&''66''6Æ     Ï/$ C /*"S;# cÿòñÇ7#3#5&''677&'76ðyzŽf  ǯÕ&#!  JÿéõÏ'735333267#"&55#'67#3533##5#Y(8  &1 &&GJJG²I  ;</d$$??SÿèëÏ%735#53533#3#3#"''3267##5#535#]444??;;F  1>>4;#VVOÿêòÐ%73#3#3&'767#'67#537#536”FK[a^ (; W,2 .3Ð    CÿíóÏ )7&''6#3267#"&553#"''326™%) '&!* 8>?!(  3/h  Ï '+)%CU l6 LÿéëÏ#)736733#"''32765#'667#7&'&'S9K 6 7"  N  ‹&&X7 T4C=-Og MÿèöÈ7'6553#&'7#3|!x&, 1&QQdQ+%UUdI!ZQ>UÿêóÏ &73&'73#67&'7''67'67676WC Bœn0/(/  =< 0!# ²  V  #)"&-)cÿéçÑ 73#5##53635#35#™D\*\\\\ÑÅÅ^:ˆ;FÿçòÇ $7'6553'#33673267#"&5s‡^^J&! &-   ( €D6 5@_G4! * * #  Mÿéõ 73#735#'67&'izzRR"#PÂzTu ,!"%Xÿéè¼ 7#5##535#33535#35#èh**>*h**>**¼ÓÓUBBB—BBB@ÿèòÏ*73#3533#"''3255##5##5'67#536Œ\d ):  && '&/Ï""U  =tt[V ,$GÿîóË"(.7&'36732767#"&55'67''67&'‹,8 &"  …  Ë,R*& ((I2 1GŠ #    1  ]  RÿèðÏ"(73'67#'63#"''3257&'''6zm  ^ / :PÏ   +ƒ  _)+.'/( )IÿéèÃ$(7#"''3255##5#'65535#33535#735è   '+))='d))='ý +CC-" 3>]<)))e))) ÿéõÏ$173533#&'#5'675#&''67&''6[XX)A <$(6 @*[# ˆ  µ8B6b\3:@  '  KÿéóÏ$1733#&'#5'675#53&''67&''6”AA- ($ *==)  d  ÏE7*TS*3G   '   QÿéëÈ$(,7#"''3255#'6553533#3#535#3#735#ë  b  ODDÈÅ  ¬SD5 5?_26=mÿôòÁ73#3#3#535#535#&'uv2++9…8,,0`  ÁARRAb mÿéïÁ7#5##53'>&'ãE,  Á˜‡†—-M+'  ")(2ÿð»Ð 73#'6#53#3#67'5#53^S^ $%c*44 22Ð 1$3  C ÿïÏ 73#'6#53#3#67'5#537@J L ** &&Ï 3$7  F ÿïqÏ 73#'6#53#3#67'5#5307@ @$$""Ï 3$8  ExÿçóÐ(.7#673265#"''67&'#67'53'37&'ð1    2  ¦:0$&)$Eo  **(   fÿèòÏ)73#3533#"''3255##5##5'67#53¡GL-    !Ï $##U  @uu\P /'lÿèìÇ 7#5##5##535#33535#335ì####7#Z##7#ÇTT<)))e)))`ÿéóÅ73#735#3#3#"''3267#7#snnDD(“_ Z  ]ÅD D!63_ÿêóÌ!'73##"''3255#535'2'6'&'ß AA  @@;0  T  ÌWA  <U#   ÿèõÒ  (,7&''63##"''3255##5#53#=#26 64.; P‚‚¤  0YN)Ò$(&!%B  )exWka 22cÿòòÇ 7#3#5##53533#"''3255##îw{J('   ǯÕMUgS >l`ÿèòÏ $(,73##"''3255##5#53535335#33535#35#æ  R 333R3´t*  %EEt:'''a'''RÿèðÂ7#5##53'>&'äL0&& " Â’}#G0 $+3ÿæíÉ 73#735##5##53'6656&'3——oo‰L.:8).'&!-É><_LM`&(  KÿéóÐ$*73533#3#535#3533##"''3255#&'X>>>H§K> r!!  r(  ²N<  8  KÿçóÐ%)73&'73#&''67&'#67#3'6573#V>?'*", (I > %!L¹      E1 )gBÿé¦Á7#5##53'>&'¡2#   Áš‰Š›'M'  "+0 DÿèöÏ#'7367'673#&'#'67#3'6673#Q6.G8  T/% .5*-'<“-$+3( $+kFÿç÷È 7'66553#&'7#3&'&'u „ " * \\K% "*% ''|;. +RL="J9&/*CÿèóÇ &733#535#3#3#&''67#535#'6apžp]g=IB03; A EI# Ç+/# )# Jÿè÷Å"&7#3#67&'#67'5#'6553#ëx}6  7 !bbÅC %P`  mD0 5=^*DÿéóÇ *73#535#535##5##53&''67&'767#Ynjjl“y ~) 2 + & eÇL  F"#(   DÿèîÆ $73#"''32765#'6553'#33#735#p~  j†``TBB‡]@A2 4?^?-M>HÿêðÏ17=7'6&'#"''32655'67'67676767&'&'''6Ï 5B ;> ,     #+7-% ^ Ï P? :  & 3  BÿéöÆ!73#3#33#"&''6735#735#d{177' 2% $  7UUÆU'4-9j1JÿêóÏ-37&'3533#67&'#"''32655'675#&'Å bA@@  ! "0A  Ï   ''1 #C ?%#*/ PÿéòÅ!73#33#537#537#35##5##535#X“PE¢% $00>6R[[[Å3"O"FW W8&cÿèöÑ!%7#67&'#67'53&'73535Þ7 ":! 24SSS²f  EK  ¿ +ZÿéçÐ"(733#"''3255##5335#35#'&'7'6˜<   c8&cccc  z Ð=’  ;ª.Lx   [ÿéëÈ#'+/3767#533#"''3255##5##53&'35#33535#335€d'   ,+D++>,j++>,±   //8¥ AJMÿéóÆ 73#3#5##535'67#35#7&'Z‘=7Y5' 2?YYD ÆZjj?(©6_LÿéóÐ 37'2'6'&''&'33##"''3255#53567#à;QO72 )  †EE   MMiÐ  ++ ' KÿéñÐ?767&''67&'3#3533#"''3255##5##5'67#536h  4!)]d4   !! )1Ì      /  8  !TT@= MÿíõÐ +73533#735#33535#3353#3#"''3267#7#Y<<‹))<)e))<)„¨ob g $¹W531( 'IÿéöÇ73#3#&'#5'67#535#735#by2E<* (& *&&9(Í  ?7PP. C I- ÿéóÏ"73533#3#535#'6#5##535#?0WWhæj; ¥………Î $$$$ `bb@-EÿêóÍ"73533#3#535#'6#5##535#m#>>F©O- „```Ç&&&& Z\\;(†ÿêóÍ"73533#3#535#'6#5##535#œ""'g-Y444Ç&&'' Z\\<*ÿéï"73533#3#535#'6#5##535#>1UUdÝe< ¤†††   FG G+ ÿëÌ!73533#3#535#'6#5##535#)''1‚=_888Å%%%% V``9&xÿêóÍ"73533#3#535#'6#5##535#’))/w5 b>>>Ç&&'' X\\<*kÿêóÍ"73533#3#535#'6#5##535#‰--4ƒ; kGGGÇ&&'' W\\<* ÿéwÏ"73533#3#535#'6#5##535# &h/X///Á ((((S\]<*eÿðôÏ)73533#3#535#7&''67&''6u.33:†9.   T  ;””''•)3( ÿèõ673533#&'#5'67#efN$0 :'": /#N.  32  ÿëˆÏ!'73#"''3267#3#"''3267#536'6@0   4T  V)&.1Ï 8 OK2s ‘  ÿðŒÐ7335#53533#353#67'5#7755- /ŠCYYCU.  8 ÿêÐ!73533#3#3#'67#53655#535#233--57 &# 47,,2± 0$  ÿêÏ733#5#'67##53367'@140+ Ï*7$h@ ?\&9:a   ÿêŒÆ 73&'73#67'5#'655321x\!D¤ † f,332 ÿéˆÏ73533#3#&''67#535#/1133 "023/¨''' * *<'Ë7#53&'73#3#'8 1RFZ‹v ÿï‹Ï7#'66553&'7‹S ,©D7, (Nÿî|Á73#67'535#hL+ &3 KSÁbZ  z< ÿý„Â767#53&''67&'+ Pf  ' (›&?))ÿé†Ï 7#5##535335#33535#335†I...I.¢¹¹--J777€777 ÿêzÏ73533533##5##5#35#35# ,,!,,,,£,,,,§§>>> ÿîðÆ"73#735#3267#"&553'#37#3-££{{ BC LM¾iAAUAAÆ=*   uH5"""!ÿêò’"73#735##32767#"&55#;5#.ssš¨=JSKT@@@@’2.;  SÿëÏ$*77'5673#3265#"&''&5&'+ ='&  )ie  È :4 :D3aÿéŽÂ$73#3#"''3255#&''67##537#|20   28Â"‹  t 3’¤" ÿëŠÏ!73533#3#535#3533#67'75#+++5|3+-++6C3-¯   Y$ ( ÿë‡Â 73#3#735#7'676'&' zz hhAA< 3>%! 3  ÂJ%@ ÿéƒÆ73#735##5##535#``88REEEÆW1_kkI6ÿè•Æ!73#33#537#537#35##5##535#€D7‡).-'>CCCÆ5"N"HWX7% ÿéÐ 27'2'6'&'&'#"''3255'7567#53 0F91  #  r2   3;SjÐ  [ + $  ÿç¤Ï4:7#67327#"''67&'#3#"''3265#'6553537&'ž-      0)  A  ¨<)?")9#R%M 5:, -7P''   ÿêšÏ$*073533#3##"''3255#'655#535#'67&'///5"  #!$5/i  ³l  gC+ ':G  ÿí‰Î%73#67'53&'3535&'76K &N %;;; !  Î fG  » ()1  "  ÿê–Æ#'+173#3#&''67&'767#535#35#33535#335|5/1& ##   .040L0Æb  b>>  ÿé–Å %*.733##3#5##5'67#5367#537#3353535#kCQ3  +.$6#%*033Å+, \S   + })ÿéÐ !%73&'73#3#536'&'#5##535#./rU"J 0 aCCC·  TZ Z>, ÿê’Ð %*073#3##5#5367#536365#335367#33535B:''K ##.K,#( Ð l //l :  J  .   ÿé”Ð(,073533#3#3#535#535##"''3255##535#35#:88009ˆ #&>>³-Z F Z)ÿéÏ #'+73533673#3#5##5'67#535#7#35#35#%%(:K: ,/%8::::µ ueoA ÿï—Î(,04873#3#3#67'75#535#535#535'25##5#35#335‡ 999E6778 'Î!! !!X!!!!3!!!­ÿèñÃ7#"''32654'7##5ð  ÃB$ 'BÊÛPÿê©Ð!73533#3#3#'67#53655#535#X " !¯!!" / $ "Kÿê¥Ð /73#53&'3#735#367#"''3255'7567#z"Z#KK$$S$  # 9Ð  .5/   Oÿë£Ä $7#53#"''3267#'67#535#5;5#35#lG ² 02 )))d)Aÿí¡Å &7#53#"''3267#'67#5367#5335#65#jL     %³¤-'5+))9`ÿé¥Ð73533#3#3#'67#535#535#a¯!!!!0 &!!Uÿé¥Ð73533#3#3#'67#535#535#Y¯!!!/ &!!?ÿè¢Ç +733#5367#'67#535#'673#3#&QA^7/" %( 7&( Ç Ÿ  ÿè¡Ó4<@DHLPT73#33##3#"''3255##5##535#535#'6553&'5#35#5353535#33535#335e43* *.  *&&.: ..&8A*F*Ó   T  !i  B4 3?\* '   D ) ÿêœÇ $(,0487#3#25267##"&55#535#55#7355##373535335‘*** (  &&'F I1]1Ç8h g8Ë»° º1* ÿç¦Ë%+PV\73#33#5##53537#35#35#35#&'7&''33267#"'3&''67&''67&567#''6‡@9s'3OOOOOO#  ;   D  G  -$ ) = Ë BB         ( 9 ÿé¢ÐCGKO`7335#535#535#53533#3#3#35335#535#535#53533#3#3#353#3#3#735#7677'7&'   ‹‹rrMM #"?M"ÈN VV N\  -#  ÿéðÇ-173#3#5##53565#5#35#327#"&55#'67#35#àL?ŸAN€ @Ÿ+  !% ,ŸŸÇ¯ ¯cP% )2&|lÿçóÐ#)/7373#33#53537#5#35#3#3&'''6u303/ „ )0^EEEEEE ' ¾ww/  ;ÿçëÈ$(,7#"''3255#'6553533#3#535#3#735#ë v&&&+f(&TT..ÈëTF5 6@`15?;ÿè™Ï73533#"''32765#'6655#E)  ) ¤++)'[ r*B?[ÿëôÇ"73#735#3267#"&553'#37#3dssMM &5  >3†L''9%%ÇB‰%   rI7%%%KÿéóÉ 7&'''6&''6767&'à &) 9*'M/8)" ÉC K>-*G(0 .RL)‹ÿéõÈ 7&'''6''6767&'Ü (7 %ÈCJ?,*G)/  /SP(VÿéóÉ 7&'''6''6767&'Ç #& 6&"K +4& ÉC K>-)F(0  .RG.aÿéòÉ 7&'''6''6767&'Ë "3"F (0# ÉC!J>.)F(0  .SM(7ÿñÅ® $73#53635#35#3#3#3#535#535#u 8r&NNNN600>Ž=//6®LL (-, uÏ767'5'753q5% c  mD?OÿéóÏ#'+73533#3#3##5#535#535#35#33535#335UBCC::HHHH;;B((<'c((<'¼e''e;AUÿéñÃ373##"''3255#7&'#53#"''3255#7&'#5UœœF   “    Û  …()!r²›  …()#u²LÿèñÆ*673#3#"''3255##5##535#&''67&'7''67&'L¥HC  00CI-  S  Æ “  }ŽŽš¬ @&,  &, IÿéöÏ !=7#5##53&'73'67#&''67#3267#"&553#"''325írAE*C 6   t  A  µ$%  ) j)R   d  yD  GÿîôÐ&*.2673#3#3#3#535#535#535#535'25##5#35#335Þ $FKD™CJCC)0Ð"(("Y"""":(((lÿéóÐ#73#3##"''32655#535#535'2å 0099 ::223<Ð-%7 3%*XÿéëÏ $(733#"''3255##537'6'&'3#735#™> kA<Y  FF ÏI„  m‹=  WG%PÿèñÏ%+73533533#3#535#35#35#35#'67&'Y>¡)>>>>>> % M ±mmBC1  KÿéëÇ$(,7#"''3255#'6553533#3#535#3#735#ë j!!SEE!!ÇÄ  ¬SE4 5?_/7=EÿéòÏ#'73533533#3#535#35##5##535#35#N!5!!(­*!455^^^^^^±!!!!Css)=\ÿëõÆ "/73#735#35#3267#"&5536'33#67'\……____w    w,, Æ_9;= )   ^1  KÿêõÏ!373'33#3267#"&'#7&''3#33#7'27537N_10  `‹ mBB#"-8‘>#='$DQD 8"/ `]IÿéîÈ&*.473#3#"''32765#&''67#'67#735#35#33#c|Sm  0  % VVVVViÈU X! 7    31aAÿéòÏ-173533533##5##5#3673#3#5##5'67#35#R#,%%,#9R\ dO %-1OO´,  [ F  Z)QÿéôÏ #48<73#5'675#5373673267#"&5#"''3255##535#35#…00#   :  YYYYYÏY      p  ,…$5RÿçóÇ 4;73#3#535#5#35#335335373#&''67&'67#367Yš2)Š'.U((„8S 5 ' ,C  ÇCC1E      MÿéóÏ /5973673#3#5##535#53&'3535#"&55#'67#333535#   /8.m091 ,@m RmmmÍ ›› - j  i[ÿéôÐ!06:73673#3#5##5365#53&'3535#"&55#'67#333535#„ %3*c-5) .:c LcccÏ››  / j  inÿéóÏ.4873673#3#5##535#53&'3535#"&55#'67#333535#‘  +$S',# + 0S  B SSSÏ›› /j   ilðÏ*0473&'73673#3#535#35#5#"&55#'67#75#335#l ,'x"); 2 TETT¯  uuX 1? ÿéŒÏ,2673673#3##535#53&'#335#"&55#'65#333535#+   ") _"' 8.M  ? MMMÏ Š› -V !&!fKÿçòÎ#)/73533#33#53535#5#3535#5#&'''6Y=CC8 4=vZZZZZZZ@+ " ¾ww. * )     CÿèôÐ59=AEI73673#33##&'#5##5'67#535#535#535#53&'5#3533535335w  17*   ",,"")* =BÐ &% !=IH6 8&MÿéòÏ 87'2'6'&''&''673#3#353#5#5335#535á ;TF> 0 (    n?GG+|+KKÏ     > 0%A 7%0 UÿèóÏ#'+/7;?73533533#3#535#5#35#33533535#335335#5##535#35#U122))1Y))g) VVVVVÁPP ,-\ \ -EÿêöÑ *0AGM73#53635#'673&''67&'767&''33267#"&5''67&'ŠP(gg$  +        $ ‰  Ñ }} vY      N 1 4    eÿéîÇ #/3773#5#73#5#&'7&''67'63#5##5335#35#fB/H@-@ N ! U %6Q+QQQQÇ_M_M       j j&;[ÿèöÐ#)/5;73#3533533##5#'6553&'#3&'''67&''4'±3lB7&  I  F Ð &::R)$OP [) # gÿèõÐ#'-373533#3#3#535#535#3#735#35#35#'67&'o355//;‹;,,3ssNNNNNN % <Ä     DbE ' '     ÿéìp 73#53&'3#3##5##535#ƒa×`M¹¹¹¹³ŠŠŠp"  00 UÿòóÇ 7#3#53#735#3#735#73#735#ò‹Œž-YY3322022ǯÕ!;6L,L,KÿéóÑ(,073'73#3#53&'#67#3#3##5#535#735#35#U@ ?)§(bB)‚8JJJJ7\\\\¼    R1.IÿèôÐ*;Oc73533#&''6655#67&''67'767''67'67&''67&''67&''67&'NHFF? 5?)$H     e          `    ¸+91: 7>+      .       LÿçõÐ.EL73673#&'7#'67#735#&''67&'763&''67&''667#\-F8 bbI     :  ''"  &) 4º  [ 7     O     _ÿéôÊ /473#735#35#3#735#33533533&''67&'#367nvvPPPP(w„ *" % ÊE) ' '41   WÿêòÎ*06<B73#35#535#53#3#"''3267#'67#56&''&''&'''6—''Z(((;\q   j  H    Îa L4 _  ÿéÏ%)157&'73#53635#33535#335'&'3##5##535#J  3 mD <,G,I  ƒƒt>>>Ï QQ33S  x F G) HÿéóÉ 06<73#3#535#5#35#3353353#3##"''3255#'67&'R¡3**2[))u‚‚£F   Jr  É>>,9 ( %  Dÿé÷Î #/5;B73#5'675#73#5'675#&'7&'&''6'6'67'66UE 2PE 2;  a   , 0#. :+ 1 35 )= AA .[ 59ÎV  V      5       + IÿêóÏ39?E733533#3#3#3#&'#'67#5367#535#53&'#53367#7'6'&'ƒ3 *D<<M<, 34 )99E;+/2 / 6<448, #7H  c  Ð333   '# ) H    @ÿëöÊ159=AFJ73267#"&55'67#5367#535#53#3#3#&''5#35#3353353&'##3x !*  5. )8-).›1*Nf+ &))[J27BB  2 ::   $™(UCÿçôÐ "&*067&''633#5367#53&'35#35#35#'67&'–') *) ( :* !ˆL S,cccccc$ !R Ð   jj K ( * !     GÿéôÐ>DIN733#67&'#"''32654''67''67&''67#5'6367#367#335ƒ;,K     "/4"' & !' *6 4 #,@.Ð :   -2     . 1 IÿèïÎ'-3;V7367&''667367&''66''6''6#5##536533#"''32767#'67#u   I    J “u*B  1( 2 &Î    '    ! @))0 ; #,MÿéóÏ(048<@73673#&'#5'67#53&'735'2##53#'#;5##;5#á #!:$ &% !6  !G8e‹=(())(())Ï    "'   Ï hhV$FÿéôÏ;CGK73533#3#&''67#5367#'67#5367#53533#3#&#5##535#35#P"   w    \\\\\¹      +      n n'= KÿéòÈ #'+/73#735#73#735#3#3##5#535#735#33535#335SEE>HH""\‹&&> ŠŠdddddd& #O! !Á gI ) ) "    SÿéëÊ  $7##535#35#7#"''3255#535#5#˜3!!!!†   5""""ÊN’à +.È  uN+ @ÿéòÏ/37;?73#673&'73#3#3#3##5'66553&'35#5#5#35#¤@„ #+/****2g A%qqC"""""Ï 5-   ^ +" .!W 4^!"IÿèóÈ-159=73#3#3673#&'#5'67#53&'735#535#5#35#335335T™0); 9$ %) #3#  ?)-W((ÈB'    7>$ 'B0NÿêøÈ"&*.26:7#3#325267##"&55#535#55#7355##373535335ç044 4 00.TV9!w9!È:h g:̺¯¹2*ÿéïÁ 73#735#&'''6.¤¤||b+ +; -0ÁySu!% +~òÅ 73#735#&'''6XX223  'Åa=\  ! Lÿéî’ 73#735#'67&'_}}UUV’b=V " \ÿéòÁ 73#735#&'''6sppHH?)ÁxTv % %! ,uÿéò 73#735#'67&'‡]]55? ÂzTu ,#!% &!‰ÿêò 73#735#&'''6’TT//2  # ÂzTt"') + # ;å} #'73#735#33535#33573#735#33535#335]]%9%^^&:&}B( & (B( & ‡æÉ #'73#735#33535#33573#735#33535#335]]%9%__&;&ÉB( & (B( & 2ÿèÏe 7#5##535#33535#35#Ïu00E0u00E00e}}/JDÿéér 7#5##535#33535#35#é}44H5}44H55rˆ ‰5###W###%OÞÄ 73#735#33535#335%¹¹??R?‘??R?ÄuDOCŒ» 7#5##535#35#35#35#Œ'    »¬ ¸I:::‚888ÿèðÏ73533#3##5#535#7'6'&'— !!$$$$ H  :  NN+JJ+O  %ZÜÆ 73#735#33535#335%··<FF::4::D  T µ"#<<.   "   {ÿé÷Ä!(73&'#"''3267&''67&'765#67#ŽX   "! (Ä^6. .($9  %Z 7=7Qÿé÷Ñ  ,97&''63#3#735#73#735#&''67&''6ž!+ * ' 6@@";;9;;E   U  Ñ!" ;;/   !   [ÿéöÑ ,97&''63#3#735#73#735#&''67&''6¡( '" 0>>99199B S  Ñ !$ ;;0   !   `ÿé÷Ñ  ,87&''63#3#735#73#735#&''67&''6¢( &" .>>88188A  P  Ñ !;;/      ÿëôÍ73'67#'6'6553&¬:  +   (Í  +i,);#FgÿéóÎ%)-736533#&''67#3##"''3255#3#735#r325 $ ) / Œ   hFF%%¶     9W  T9LÿéóÎ&*.736533#&''67#3##"''3255#3#735#Y?>A& &+ 1 : §  }SS..¶    9W  T:EÿèôÌ$KQW]7'23'73673#5##53&'&''33267##3&''67&''67&567#7&'''6Ü 9SD+  |>   Q" +! +( $ ) DGa Ì    "" '       G  [    >ÿçôÏ #=J73533533#735#33533535#335335#3#3#67'5#'655&'76V+.”*m*Š}}‹b  Š   6  ÁI- + &  - 6$ $7M ' @ÿéõË 3773#735#3353353#3#67&'#7'5'67#735#Z)€šš ‡6  9 &  # aaË:5 3    . !    FÿéõÒ!%+37;7'67&'#7&'3#735#&'735'6#5##535#35#u ;$) Q= -ŠŠ)) 4)&UUUUUŸ   B!  !!  2T T+@ÿèóÑ*/37?C733#&'#'67#3&'73#'655'667#3#3##5##535#L(   <5† +HA jjjjjEEEÑ     8* ,2= M  9 9 FÿçôÑ !%)-39?7#5##53'73#673#5'675#5#5#35#7'6&'''6ïzC9e)4O‚ -#)d\\\\\ V& $ &À%%    ]SG   i }   MÿèòÊ (BHNTZ73#735#35#&''67'6767677&'?7'7&''67'67&''&''&'''6^„„^^^^    8         ÊP0,W  !!    K  EÿéñÊ#F73#3#5##5##535#3#73#3#73#3#3#"''3255##5##5##5367#N™EL97JA))L**U22U44f¬RP   9FÊ (::( *  ;  $8888=O Sÿç÷Ò !%7@IW]cio7&'6'&'6'&'63#735#&''7&'76367'7367'733265#"'&'&'7&'&'7&'Õ ' & jjU   [  :  9 P   F   /   F   Ò         ,I(    8NZNY'! %2        @ÿêôÐ048<@DJPVf733#3'67#'65533265#"&55'75363#735#33535#335&'7&'''6733267#"&5‰CCP n3A" ,>jj,F,#  A  `     Ð  IÿéóÍ#6:>BFJ`m73#73##5##53#5##5&'7&'#3#'6553&'75##5#35#3353353673265#"&5'67'533OIIUII $$4   a   20+…B--    . )Í 2!!22!!2  *"# "+3        6 @ÿçòÐ$QUY]agm7'673533#3#67'5#'67#53535#'673533#3#32767#"&55#'67#3#735#35#35#'67&'Y      '0  #    E……`````` !P ²       )[A $ $      ÿëóÆ733267#"&5'3'>’ !D ƾ " ÀX(6 +7ÿéëÁ 7#5##535#35#ë88888ÁØØZH¢HyÿéóÆ#733&''67&''667#67#'7#‡U      @ #Æ3A'=* G_‰(44<ÿéäG 73533##5#735#335ZYYZGG[F62|ÿèôÍ73#&''67&'#53&'67µ .  0  ÍB+(F4& #6mÿéòÇ!%+17=7#3#3#3#"''3267#55#5#35&''&'''67&'í($$$$-  a4 -  (ÇO7‚&%/€ÿóòÅ7#3#5&''6767&'76ò^^rS      Å®Ò#%  „ÿéîÂ7#"''3255##53#735#î  D//¿  ¨ÇÙ7aA€ÿéôÅ73#3##5#535#'6'&'Šc*1100&O = ÅdSSd%#!#ÿéít!%+17=7#3#3#3#"''3277#55#5#5#'67&'7&''&'âSKKLL^   ´VCCCCC  8c    t   +Z    sóÑ.37373&''67&'67#36773&''67&'#3674 &  'b     à        ÿéí|!%+17=7#3#3#3#"''3267#55#5#5#'67&'7&''&'âSKKKK^  ³UAAAAA  :c   |   - `    xÿëôÏ#73533#&''67&'767#735#335,..% !   ,-°Z"     666rÿèõÐ  7&''6'6'6'66¯  ($ #'1 0*.& $*Ð""$ ! & ,!#'$vÿéìÈ7#"''3255##53#3#735#ì  O>>11ÈÅ  ­Ìß,X7yÿèôÇ73#7#5'75#35#35#75#}q S"000000Ç*( ™&&]%_"kÿèòÆ $73#"''32765#'6553'#33#735#e  QoII@99‡^ HC1 4?^?-M>pÿéôÇ *73#535#535##5##53&''67&'767#€bcPOOOpX `  #   JÇK  G"#'   kÿçóÏ5;73533#67676732765#"&''75#'655#7&'oT;  ! $  U  ›44(     za< ;VD ‚ÿéóÐ!%7#67&'#67'53&'73535ã*   1 $&999³f JK À ){ÿéñÈ 73#735#35#3#3##5#535#‰^^9999g+1122)ÈZ49733xÿéóÏ %73&'73#3#3##5#535#536'&'~. -qP "744..1F , ± #88#pÿéôÐ"'+7&'#3#3#5##535#535#5'63&'35#« ..'8$** )3  88Ð% VV # —$tÿèîÏ373#"''32765#'63#3#353#5335#535#'6T G  /!!Q$$ Ï ›6€  2$43#2 qÿéóÏ$173533#&'#5'675#&''67&''6z.--"  .   K  ´F5'PL'0K    (  ÿëôÆ !.73#735#35#3267#"&5536'33#67'jjCCCCU    Z Æ_9;= +   _0   ÿéõÏ*.73533#3#336767#"&55#'667#535#35#dffL63 '(% /Md+……¸K4 :''KT&AÿôõÂ73#33#537#'7#37#O£k]+´uW&$M LÂ"†/if1BÿéöÏ 73655#535333#&''67#75#M?22:H5458;z&])))I2!54"$/6)BÿéñÅ#735#53#3##"''32655#'67#'t ?T(..   "70&9¦==^ YB-"<=ÿçòÐ '-73673#3&''67&''67#67'&'N)bdT#(% $ +  , '0  £6#    !1M4!o EÿéóÏ"(.733##"''3255#'67#5373#3&'''6—>>  B&. RY,0LŠ-G  B$!$! # $ :ÿèôÒ .4:73#53&'3#735#3#735#3#3##"''3255#'67&'ŸL°N;’’nnPP,,+……¬L   M(  i Ò 'U3% 8       8ÿéöÑ 573#53&'3#735#3#735##535'273#&'#5'6¢I­N=‘‘kkKK## &'‡C-!  ‹x}#I / #-.ÿé“Ä$7#"''3255##5&''67&'76“  UN    Ä  ªÈÛ'   OÿçóË #'+1773#3#535#5#35#3353353#735#35#35#'67&'P£3+•-3]++{††``````$ I  Ë 55 $,bF % &     ÿéõÐ+05733#3267#"&55#'67#5367#'6367#35#R Z :@   ) % >;Z R./6E@;Ð P6   = 0P  ^**jÿéóÏ(,073533#3#3#535#535##"''3255##535#35#q27700=‰9))2s  FFFFFÀR\  $t 0aÿéóÏ27#53&'73#67&'7&'3267#"&55'67'6”(77E $    * #Ÿ  ) !% X # [OB ÿîžÐ (7&''6#3267#"&553#"''326O%!# /:6'  2! ^   Ð (&?\  r7 ÿ雯 $7#3#3267#"&'#67'535'#,20 * R*&ÆK$#)*1B  Ò%%\$$ÿè˜Ï!733#3#'67#535#537'6'&'I66;=# 57225  Q  ÏM*- "*B   ƒÏ 73533#3#67'7&''67#535#)**5=  ).$-)¬##3*   '3 ÿé’Ð73#&'#5'67#535'6„ 000(24/>Ð (#dg"$/% ÿìšÐ"73533#3#535#3533#67'675#222<Š;2233 >K2¯!!  Y$ ' ÿê›Ð*.273673#3#"''3267##5'67#735#53&'35#356  2<  ( "(.0 90Î  @9 O?$!C H- ÿñ¡Ï*7'75#53533#7&''6'&''6@OD;;55   Q  %‹‹"¨  %,  #ÿéÐ"(733#"''3255##5335#35#7'6'&'J3  O0OOOOQ  U  Ð=’  ;ª0Ly   ÿèôÑ,073673#3#32667#"&55#'665#5367#35#7695"(>- '3II·   I<  C('  I O% ÜÊ 73#"''3265'3#É   )Ê „{ ÿç Î !77#5##53&'73'67&'767#'67#67'53#"''325Ÿe>> 8   a ;  ·&(  !o+%" & o  D ÿé£Ï.287#'733#67&'#"''32655'675#53'37&'jCJ(=   #@S$ < S  ˆ8 6,  .6R ÿêšÏ'+/736533#&''67#3##"''3255#3#735#327  ' . Œ   gHH##¶     9V  S:^ÿèöÐ #'+/5;73533##5#3533533##5##5#3#735#35#35#'67&'o2992 44 xxSSSSSS G Á gI *) "    iÿéóÏ 7#5##53&'7&'''6îV2" (¯:((: 6Z$*`9:)mÿéòÐ#'+73533533##5##5##5##535#33535#35#m$$~P0 P0 ²(6%%%\&&& iÿéñÈ #'+/73#735#'3#735#3#3##5#535#735#33535#335´::V99 u2::;;01P1È11/W((45`ÿèôÐ I7'2'6'&''&'&'3#&''67#5367'67'6776767&'â 1H;6  (  #  g D3 '1 $)1  '$Ð     1   $     ! ÿêeÎ6767#"''32654''67&''67''7&'767&''6L     $   Î  "=!     rÿéóÏ &,733#5'667#35#35#35#'67&'œ2d ) & ======J  Ï  y  &87'  tÿèóÐ#'-373533#3#3#535#535#3#735#35#35#'67&'}.//))4|5)).jjDDDDDD  8Ä     DbE ' '    lÿêîÑ#+/373673#53&'35#&'735'6#5##535#35#’   ‚ $$3%  $NNNNNÑ VV ]666 ;cc#5 ÿéUÅ 73#735#35#35#'67&'AA  .  ÅŸn OM4 !båÌ 73#735#35#35#'67&'+««………………& #%U%%$$ÌU; !  ÿæés7#5##5'66&'Ñ{K25 2--) ,*s[HI\.2  +uÿèóÏ)/7767327#"''67&''7''74'37&'â/66    '()  £   $ " % 43  dÿè÷Å#'7#3#67&'#67'5#'66553#íbf,  -    RRÅC  $Q_ mB4 0%^*tÿêòÏ!'7#5##5367#'6733'665&'ë8+ !  -   "‰q__q # 9  #(pÿèöÏ*0673533#3'67#535#&'36533#'67#7&'&'~*++5 f3* </55 &4 Q ¸ 1  5*'&>   <_ÿèõÐ59=AEI73673#33##&'#5##5'67#535#535#535#53&'5#3533535335‘   &*#   !!! 14Ð  %%5II2 8'tÿêïÑ#+/373673#53&'35#&'735'6#5##535#35#™{""0" #HHHHHÑ VV ]666 ;cc#5bÿéóÒ*048@D733#&'#'67#3&'73#'655'667#3#3##5##535#•=  *)h 94 UUUUV333Ò     !  ,* )M M  7 7ÿèôÆ 73#3#33#"&''675#735#0¤EUU'#?A (!K||ÆX%0.: c2ÿéžÈ%+733#"&55#'66553533##5'67#&'| #  966 !1bÈ8  -  jbI%*ÿéžÄ.47#"''32655'675#535#535#533#67&'&'a #@cMMOb=  T   .M2  L ÿè¦ÅB73#67&'#"''32654''67&'''67&'7267&''67#Œ4     / 2#   & @Å  -3      6ñÐ-15973673#53&'3#"''325'#"''3255##535#35#73#P 9 >â=‹  F   >>>>>gÐ   (N  FM  b # 'Fÿéà073#"''3267#'67#É  ] E< D01 % ÿé£Ç"&73#"''3255##53535#335#3#735#   jCG00EE!!ÇUp  Zx‰UUC+C>ÿì›Ï (73353353#3#3#735#67677'7&' !zŠŠ ssMM  :H Â))/=3    ÿé¢É 06<73#3#535#5#35#3353353#3##"''3255#'67&'-&ƒ&,P%%huu’>   A i  É>>,9 ( %   ÿç©Ñ$@FLR767&'7&''667&'7&''6&''67''6767'7'6'63'6I  -3;   ( (-(    . ,0"A A4(] XÑ       4   ( ÿé¡Ð 7;?CG7&'67&'6'&'63#3#&'#5'67#535#735#33535#335,  e  #  55>:  "4@7$$7"Y$$7"Ð      -N -1 / *  ÿê¦Î#'+17=C73&'73#3#3#3##5'65#5#35#&''&'''67&'5 )*%%%%-e9&&&&&X  ?  2Î  X )%%0    ÿè³Ê '-=L7#'6553#353#'67#53365#536'&'73'67#'6'6753&©|E   I     ÊTE6 6@`5 /@( @/ 4     o  = #  ÿé¦Ò $*26:7'67&'#73&3#735#&'735'6#5##535#35#3 0% O"5Jyy!!  -!  !FFFFF " %D    1TT ) ÿë°Ï<KQ733#3#53533#673265#"''67&'#'6553533'73#67'675#'6Y--;•M$    DV  m;-    Ï##"$#  3/756  % ; >  ÿé¢Ê .173#735#3#735#73#735#3533#&'#5'67#37%ee??$==:>>a>??"" (5JÊ5066: /4 ÿèªÑ$9^cglr733#5'667#35#7'53373267#"&553633#3267#"&55#'67#5'667#37#335&'3H‚ D<\\'3%A   MG /  . $) D> !%5( Ñ &  .  $   # (           ÿç«Ð$PTX\`fl7'673533#3#7'5#'67#53535#'673533#3#3267#"&55#'67#3#735#35#35#'67&'       ",    A{{UUUUUU  J °        )[@ $ $       }½73#3#67&'7&''67#UUe,! (-#½(: $ 8 ÿó…¾7#5'67#53#3#=#; (t7 @( c (1s AAÿçÚK736533#"''32765#'67#M`!N. H H; 8   wæÍ73533##'32765#'67#Œ0 /*¢++l$ K]%!S ÿë÷Ï,05733#3276767#"&55#'67#5367#'637#35#:0 ! I*3/ $/'  'Ï I>  C8! /I  Z%%% ÿ÷È$(,073567#533##"''3255#3'7575#75#75#; Gc++  ;t8F   ˜  +> PM97520ÿéÐ28<@73#3#3&'73#3#3#3##5'67#5367#'65#5#35#"R4GL')""""([ $3 Ð    M #%  p"# ÿé‘Ï'/37;?73673#&'#5'67#53&'735'2##53#'#;5##;5#ˆ  )  ( .9&Fl,Ï!    &  Õ hhW' ÿê”Ñ !%+73&'73#3#735#3#735#3#735#35#'6 3?‡xxRR >>(nnHHHH\5AGÁT4# 8A( ! ÿèÏ37;?CK73533533##5##5#3#3#"''3255''75##535#735#33535#3355#7'7!! k-3   1++E+ Å  N K   N` 0 + _$  ÿè•Ñ DHLY73#53&'3#735#73#735##535#535#53533533#3#3#67'5'675#35#67&'S:„5*77088:!  !O   A   Ñ$++p     "    <    IÿéñÅ#735#53#3##"''32655#'67#'x Q%QQQQQQÐ ÈÈC#Y$[%ÿèóÈ7#'6553#33#"&7#3GT /CJ:--dO-&UUd=)2™>ÿééÇ#'73#"''32655##53535#335#3#735#Ë  ¤[(oGG44 ]]99ÇUo Yx‰UUB,B>ÿééÅ73#735##5##535#7#5##535#+ªªƒƒ6999À888ÅW1XqqP>qqP>\ÿéòÏ)57367325667##"'"&5'67'53333##5#53© 1$BB>>Ï,   .2 x&E>>QÿéõÇ*733#"&55#'66553533#&'#5'67#É$ ' =B4$ &$ "+Ç7  ,   o ',RW/'JÿèóÏ5;73533#6767673267#"&''675#'655#7&'XdG   &% ,# 1+o  ›44)      y`< :VC Yÿé÷ÏCI73&'33#67327#"''67&'#67#"''3255'675#535'67&'Ÿ #.-    $      $Y  Í. )*#)&" #.$ 6  + +)YÿéòÏ$*073533#3##"''3255#'655#535#'67&'d877B/  **0B8w  ° f  aE, ); D !"€ÿêõÃ73&''67&'#367†e   ÃT=  .P@(08 ÿè…Ð $*07&''63#3##"''3255#535#'67&'I ! , F**   -- U  Ð$I  E8"   ÿó}Ï!73533#3#67&'7&''67#535#&%%+2 (+#,&¬##31 "  13 ÿéõÒ 7&''63##5##535#~27 54/< Q'—— ‚‚‚Ò(.,&,ddA.QÿèôÏ 7373#'67#&''6767&'d%PS*) #n"( ¨'gE HWX#)  .QN&gÿè¹Æ73#3#"''3267#735#jM49  : 39ÆI'J$-M#dÿéîÐ#73#"''3255#&''675##53&'žC *   &4Ш  /( 7,!+8­ÀTÿì«Æ7#"''3255'67567#536¨!   9NpO C )´ÿîóÊ733267#"&5´  ÊÁ " )ÿé×D 7#5##535#׆††DZ [<) ÿéäÈ%)-7#"''3255#'66553533#3#535#3#735#ä ˜ &/007~3/ aa;;ÈÅ  ¬SD5 1$_07=[ÿéôÏ=7367#535#53533#3673#33##"''3255#53567#'67#i%D8--!! #4 8<<  EE2 i  ' $  ÿêól736533&'73#&''67#\+&[,(I! ] X YQ    <1.„ÿëóË73##5#'6556ä !>#&Ë$††81 ,0c[ÿéõÌ ,27'2'6'&'&'3533##"''3255#&'ä 9TE;/a&&   a%  Ì  =H  E Kÿæ÷Æ#'+073#3#&''67&'767#535#35#33535#335\”B9:7 6!, & 68?$$7%\$$7%Æa    a>? gÿëòÆ"73#735#3267#"&553'#37#3qkkEE #,  60I%%6$$Æ;‚.  yH6$$$YÿéóÈ$*.73#3#5##535#5#35#"&55#'655#333535#Yš2)a)1V'a Laaaȯ ¯f %# !)~UÿìöÏ'159=A73673#'#3267#"&55##5'67#3533&'#35#33535#335a,J*" 1 #!  0N0²  K  W '=?[ÿéòÐ,0473#67&'7&''67#53&'#"''3255##535#35#¨@R#&5=-@K  MMMMMÐ      ed  *|"2XÿèìÏ373#"''32765#'63#3#353#5335#535#'6{e  [ =&**_-- Ï–" z  2$43#2 WÿéëÎ(73'67#'63#35#535#53#5##56‘E  9 &''`''*>`Î "+ !!} xUÿèñÆ(473#3#"''3255##5##535#''67&'7''67&'UœEA .-@E)  M  Æ “  }ŽŽš¬ @&- &- KÿéñÏ,073533533##5##5#373#3#5##5'67#35#` '!!' 6 HQ YI (,+II³- Z D $Z) ÿéóÉ #'+/73#735#'3#735#3#3##5#535#735#33535#335‹VV00€WW11 ·RiiiiQ>>R@’>>R@É330V((34 ÿé÷Ï#'8<@73#&'#5'67#5367'2&'#35##"''3255##53#735#Ï +63" u(9I8V;B OO   ¢.iiEEÏ  *% >  )K  5Qb.YÿéòÏ 77'2'6'&''&''673#3#353#5#5335#535á 7OA: +%   h;BB't'CCÏ     =  0%A 7%0UÿéóË,CY767#53&''67'67#53&''67&'767#53&''67&'#53533#&'#5'6 Of  # 0C   T0C  4E??88EE77>[[[[[[ * #EÄ     DbE ' '     JÿéóÏ/;AG733#3'67#73267#"&55'75#'665533533533#7'6'&'•IIH 5'( !  5%)%ƒp V  Ï     E7- )O£LLLLV?ÿéøÑ%<73#'6553&'3533#'#5'67##5'67#53533#&'§>„C$  g   Ñ P:5 ,7b N%% `L",/TQ",%%4WÿìôÏ$1IQ7#5'675#53533#&7&''6'&''63&''67&''667#­ (99;; '    L    < +'  1n *) Q     6       YÿçôÏ#'-373533#3#535#735#3353#735#35#35#'67&'i355E›C3""5#kYYYYYY  >à +   E_C ' '     RÿèõÏ#'8<@73#&'#5'67#5367'23'#35##"''3255##53#735#Ú  N' [  /<8  88d  e"DD##Ï<) *R  556##3%   e Y  ÆcA   AA ƒ VÿèôË'+?CIO73265#"&553'#33267#"&553'#33533533#3#535#35#'67&'¼  ;c  ;++!!)ž$1++ K   Q<,   Q<,O+     ^ÿêíÑ#+/373673#53&'35#&'735'6#5##535#35#†   !#++  9+  &YYYYYÑ  WW [555 :dd$4‡áÊ 73#735#73#735#SS//\TT..ÊCCÿêâ4 7#5##535#7#5##535#u000±0004J J/J J/LÿèöÑ *0E73#5##536'6'&'3533##5'67#&'3673#&''67#‹Nc%6 3   $$$ F \=M=. 5; .5Ñno€ "   $$-    2 %'  IÿçôÏ #=J73533533#735#33533535#335335#3#3#67'5#'655&'76^)+(g(‚vv„^  ‚  3  ÁI,+'  - 6# %7M ' FÿêöÏ;JP733#3#53533#67327#"''67&'#'6553533&'73#67'675#'611?™R(    J[  s<0    Ï##"&&/!  -5-! #)>  # < ; >ÿìôÑ"(.BH73#3#53&'#53'37#3#735#35#&'7&''33325267##"&5''6šA .«.?& 6-††````6  I  ^$  !  Ñ   5 (K- *&     " Oÿè¬Î 73#"''3265'3#˜   IÎË ±£LÿèôÏ%5;AX_7'23&'73673#5##53&'&''33267#"&57&'''63&''67&''6367#ß 8QC& sB     P W N" +"+#  1 AÏ    ##%    !           RÿéõÔ!%+37;7&'#5'63&'3#735#&'735'6#5##535#35#¡!' C4) @ƒƒ''4$%QQQQQÔ    -B#  ##  0T T+RÿèöÐ #'+/5;73533##5#3533533##5##5#3#735#35#35#'67&'e6>>6 :"": [[[[[[" K Á gI *) "     YÿéðÏ '+/37733#5##537'6'&'3#735##5##535#33535#35#šCsA; ]  eeAAed));)d));))Ï#-.!     320d d&9RÿéñÊ #BFJN73#5'675#73#5'675#&'7&'3&'73#3#3#3##5'65#5#35#^B/MB/8]9$8711118v :+++++ÊW  W      4  U  RÿéôÏ048<@UZ733533##5##5#533&'73#3#3#3#5'65#353535#533&''67#&67#€#$$#$$!85//..4‡ ?-  ---4 ~ .( "( @Ï       5    7     RÿéòÊ#D73#3#5##5##535#3#73#3#73#3#3#"''3255##5##5##537#[Œ>F31D;%%E&&N..N00^ LJ  6@Ê (;;( * :  $8888=N OÿîôÏ#)/5;CGKO735333##3#535#535#535#33535&'''67&''&'3#53535#35#35#d399Iœ@33@@3F&&& f Vd¥&%Á     ;      333#####VÿéòÏ)37DQ733#3#53533&''6'67&53#5#5335&''6'67&¢11=œ   ?   }jS   >   Ï//    2–2::      KÿéóÌ+/37GKOS7#5'75#53#'35#35#5##5'75#53#'35#35#5#7#5'75#53#'35#35#5#ño•XEEEEEE 6 J,ˆ 3 K,~ D>3 % & f F:. % %   F:. % %  ÿéóÏ(26BO733#3#53533&''6'67&53#5#5335&''6'67&|NNdæ..'  X  ¶£|  X  Ï//   2–2::   …ÿéñÅ 735#53#3##"''3255'67#'— (:b   )¦.>>^  L9&!;€ÿ÷óÊ73533533533##5#3#5#5#€ 5Rf QŒ44>>>>JJo‚888…ÿéóÌ73##5#'66556ä (K   1Ì,††>3 5D:‰ÿòîÇ 73#53535#35#35#ä e ******ÇÃÃB0p.o/~ÿìòÄ 73#3#33#"''675#735#ŽW $$ 6 %//ÄU%5+,8  c1€ÿêóÐ'+/736533#&''67#3##"''3255#3#735#ˆ&'*   #s  Q 77¸    ;X  T;\ÿçòÇ 5;73#3#535#5#35#3353353673#&''67&'67#367c.&‚',O&&{4M 20 $ )?  ÇCC1E   QÿèõÓ $(47&'#67'5'63&'73&'3535&'76œ$ _  4' LLL  )- Ó&O4  !"  %%%   ÿìîÅ 7#'655î±Åa8 3=]:ÿôôš73533#3#535#LAAASºSAd66JJ ÿòóÐ)73533#3#535#7&''6'&''6VXXiägV™&| %C++‘ +8+WÿéõÐ1073#&''67&'#53&'67#© :' '+ + AA Ð@*'B#6 `ÿèðÐ73533#3##5#535#7&'7'6g577@@<<5  o ‚NN*JJ*O   WÿîòÌ73#3#535635#Û:1m#&›=)66Ì .j»¿jSÿçóÎ73#&'#5&'75#'66556Ü +:l* .! ?Î *6 ;I *)4,#xÿòðÌ%)-1573#3#3#3#535#535#535#535'25##5#35#335Ö '0b%**#`^ÌZ]"**$_O[9+(((<+Ì#$$#[####7$$$U?òÑ7&'3267#"&''7&'7° AM # - 1/Ñ   "%$ IÿíóÏ273733#'667#7&'32767#"&55'67536eTVa  !   ž119A =4?   K *!  M8^ÿéôÐ %73#53&'3#3##5#535#536'&'©:‹;6 -E==88>T:  Ð 0#88#]ÿêòÑ 27'2'6'&''&'33##"''3255#5357#ç 8QB9/  (   y??   @@&]Ñ    *+ 'aÿèíÈ$(,04733#"''3255##5##53&'767#35#33535#335a‡1  ,&< h&&9,e&&9,È  009¦  WJTÿéôÑ6>7&''6767'&'''63&'''67&''667667#È17! 5!B! (&   8 Ä   /      7  iÿêôÏ!%7#67&'#67'53&'73535à6  <#03PPP³f EL  À  )\ÿéñÇ *73#535#535##5##53&''67&'767#luwd__b„of$,!    NÇK  G$$'   WÿéöÏ:733533##5##5#53#5'673673267#"&55'67}00   <  Ï- s[#2 !/  #  `ÿèìÈ'+7#"''3255#&''67##5365#53#'35#ì  +   #(>2r.0KKee  N  %k} NN '*]ÿéôÏ#(-73#3533##5#5367#53635#335367#335š@=44P*' 3#V 3%Ï d33 d 6@ `ÿéóÏ(,073533#3#3#535#535##"''3255##535#35#h6;;44B“>--6z  MMMMMÀR\  $t 0NÿèøÎ';7373#3#3#&'#'67#5367#5367#3533#3##5#535#_9>B?F[$ / &!(5%$$9977%» b++ÿñ}Ï.73533#3#535#'67&'3533#67'675#$$$-l-$  ?  9%##5) %µ0    +!`ÿéíÈ&,7#"''3255#3673#353#5335##5&'í  f9  $ L+- ÈÆ  ¯8L;LL;L„ßEÿìôÉ )7#'6553533#3#535#3533#3#535#ó} +((1r.+-++5…=-ÉOA; 2>a2P!!I7òË73#3#&'#5'67#535#735#cy4D1 '& "/C2SSË< "44 XÿéóÑ $*0673673267#"&53#735#35#35#'33#'67&'l6*,<- 9%aa;;;;;;3|.  I Ï   T;   :\   ?ÿêôÑ#'=J73#3#'66553&'5##5#35#3353353673267#"&5'67'533¤A1,€ B*(    ) "Ñ =:- .!X =,3"    ! URÿèôÐ*:Nb73533#&''6655#67&''67'77''67'67&''67&''67&''67&'WCBB; 3:&"C     `           Z    ¸+:1: 6?+      .       0ôÏ ,26:V73#53&'733327#"&'&''67&'767#535#'3#735##"''3255'7567#53O+p/h$   #  aa<DJP767'7&''667'7''6'67&'7&''67''6'6'63'6Ž  /5A  H  #+ +#1-C0 -4$D D7*a \Ñ              2   (WÿéîÑ06:73#3#"''3255#3#3#535#535##53&'#53&'367#35#£A #  -$$E"",% ?A ##Ñ   w55’¤> ‚PÿéñÐ#'+/73533#3#535#'6'&'#53#=#73#735#X=@@H¡E=q  P  ˆb?? º**  ¥ tt CC9/XÿéóÐ3;?73673#3#3#3673#53&'735#535#535#53&'#5##535#„&  ?77CC ›#EE::B qXXXÐ     žA A'XÿíóÐ-159=73&'73#3#53&'#67#3#3#3#535#535#735#33535#335a9?#—%[9.€8>>D›D;;5""5&[""5&¼    N.,UÿéõÏ'/37;?73673#&'#5'67#53&'735'2##53#'#;5##;5#à ! 3" %" "0A4^ƒ:$$(($$((Ï   +(  Î ggW$@ÿé¡Ð .73#53&'3#735#367#"''3255'757#rU"KK%%L   !(2Ð  35.  YÿèöÏ #'+/5;73533##5#3533533##5##5#3#735#35#35#'67&'k4::4 6 6 ||VVVVVV I Á gI ) ) "    PÿèöÑ )/C73#5##536'6'&'3533##5'67#&'3673#&''67#M`%51 ###  E [=K<+ 4: ,5Ñ no€!   $$-    2  %' UÿéôÏ(048<73533#3#535#&''67&''6#5##535#3#735#]<AAHŸD<  _  $cccCCÀ::        =m mRA ) WÿéôÉ(,04:73#3#3#3#3##5#535#53&'#535#535#735#335335367#\>::F! $?HHBB5C99>*F!2É9 ‚ Sÿé÷Ð 473#53&'3#735#3#735##5'67#535'673#&¨CšB2‚‚^^DD$$% "6D1=< 8D7! #Ð$R2$ k./    AëÏ73533#3#535##5##5baaU½Tbׯ¼9./ÿìóÃ7#3267#"&553#"''3276Ó/ # V  °¨   ¿I.   ÿè|Î7373&''67&'67#67# $ 5 " "% ¢,Q,  "$@ %7#Œÿîé¼ 7#5##535#é555¼ÌΣ`ÿéòÇ %7'66553'#33673267#"&5‰ tLL< %   €D5 1#_G4! *  + ' kÿóóÏ7&'3#3#3#535#535#¤  &711<ˆ8//6Ï &7<<7`ÿéëÏ$*736733#"''32665#'6767#7&'&'i.@  , ,  K  ‹&&Y6 2?5B 0Of[ÿéõÏ73533#3#&'#5'67#535#r-22=4 !%)6-©&&%2$$2b`0%(2%`ÿéöÏ73533#3#&'#5'67#535#m4992)%&&/4©&&#,&%)`_/$)3#iíÏ 73#53&'&'73#536ˆO¼V!  h @Ô} Ï %   dÿéóÐ7'673#&'#5'67#53¡=4 9. !-6° );5ss4 ";eÿèïÏ!&7373#"''3267##5'67#'672735¥.66 # "" ##Ï#7@ 0iZ&" ($H6ZÿëòÆ 73#3#33#"&''675#735#vm-44&)  -GGÆU&3*: e1Zÿè÷Å"&7#3#67&'#67'5#'6553#ìmq0  2  ZZÅC   $Q`  mB4 4@^*YÿèöÏFL73&'33#673267#"''67&'#67#"''3255'675#535'67&'  !.-    "    'R  É)!)* &%1( ."<  0("  kÿéòÎ 73&'73#3#3##5##535#k:9‡vvvvsMMM² RR2 bÿèöÏ%+73533533#3#535#35#35#35#'67&'m8$888888 %D °nnDC/  XÿìôÊ )7#'6553533#3#535#3533#3#535#ór#((/k(#%((1{6%ÊOB: 1?b2O  ]ÿèôÏ3873533#3#535#'67&'3&''67&''667#q0//;ˆ90 O(D  "% $  $+ 7 ¹*       $ XÿìõÏ=CI7373#3#&'#3#"''3267#3267#"&55'67#5367#7&'7'6h0=BQ  / M   /# .& )*[¦)  -9 E  9    nÿéôÐ#+/373#327#"&'#67'56&'#5##535#35#ß 4/  * <"fNNNNNÐ   ]& Jm m'<YÿéëÇ$(,7#"''3255#'6553533#3#535#3#735#ë  ]K??ÇÄ  ¬SD5 5?_16=]ÿéñÏ,073533533##5##5#373#3#5##5'67#35#k##2CK S?  ),??³- Z F "Z)kÿéõÏ#'+73533533##5##5##5##535#33535#35#k$""$U!!3"U!!3""²%’’7%%%^(((eÿéóÏ 67'2'6'&''&''673#3#353#5#5335#535ä 3J=5 )"   `6==$m#==Ï   >  0%A 7%0]ÿêôÏBHN73533#3#3#676767&'7&'#"''3255'67'67#535#535#&'''6i777007e F  }X::º**  ¤ttCC8._ÿéóÐ3;?73673#3#3#3673#53&'735#535#535#53&'#5##535#†'>88BB’??66<mQQQÐ     B B(XÿéôÏ17;?EK73673#67&'##"''3255#5'67&'767#33&'35#35#'67&'e)A"  *  )   7 > +@@@@  `·    90  -5   E-(   ^ÿèôÏ37;?C73533533##5##5#3#3#3#3##5#535#535#535#5##5#35#335` *!!* …8==AA7b*¼!!( `ÿçôÏ#'-373533#3#535#735#3353#735#35#35#'67&'n233A”@22 fzzTTTTTT <à +   E_D ' &      XÿéøÊ 5973#735#3353353#3#67&'##67'5'67#735#i||$s u0   4   QQÊ87 3    . "  ! _ÿéõÏ'/37;73533#3#535#&''67&''6#5##535#3#735#f9<-/+  ) 8bKKKKKÁ   2 : Il l'<eÿèöÏ3:73533#3#535#&'''63&''67&''667#w-//;„6-S4  3  !'$ "  +º-         `ÿìöÏ)37;?C73673#&'#3265#"&55##5'67#3533&'#35#33535#335k(D&-     -G-²  J   V ';? ÿèö£"AGMS767&'7&'#'667'7&''6'67&''67&''6767''67'67'6f 1BBZ   ; 55 <7(< >#"%  : ; >< +T UB 8g j£        .   "  dÿéôÒ)-173&'73#3#53&'#67#3#3##5#535#735#35#l75#ŽR4$p/??>>.JJJJ¼   Q/0ÿéð›,AG73'67#&'&''6'3353#5#'67#535#3533##"''3255#&'Ÿ7YO, !f 8/LY  Y ›4 '     +1ªB2 '"    `ÿéöÏ $(.4:@73&'73#3#3#3#5'6353535&''&'''67&'ˆ')$$$$*s #####2  >  2Ï O *$%4 ^ÿèïÎ&,2:S737&''667367&''66''6''6#5##53733#"''32765#'67#ƒ  @     @ ƒd#:   ( 3 + Î    (     @))0>:,`ÿéóÏ"&6:>73#&'#5'67#537'23&'#35##"''255##53#735#× D! T ( 1100U S@@ Ï @ ')Q  ;Uf.cÿèôÎ0K73533#3#535#3533#3#535#'3533#7'75#3#3267#"&55#'67#v-..8~3-<FF!ˆ(   + #"À6 2! $2  #`ÿçõÏ #J73533533#735#33533535#335335#3#3#67&'#67'5#'655p%#|#X#qffr,  (   ÁJ- *(    -- 8& $7aÿèôÐag73673#3#3#535#535#53&'33#67327#"''67&'#7#"''3255'75#535'673&7&'6//:…8..50(&   !     +Ð       ]  (       RÿéöÐ&*.48Lf733#"''3255##5#'655367#'635#33535#73573#"''3267#'67#3533#3##5#535#'6u    "   '  C   Д %!!%AA RJ o6 , "F  22 ÿûTÊ736753#5'537+ ʱ‡¤ £—ÿëö¡@QUY73&'73#3#53'#367#733#3#3#3#3#3267#"&5535##"''3255##535#35#*+r)#OTTMNJJJJFF$-' NN 99999   (    b T g ) aÿéñÏ#CGKO733533##5##5#533#735#73#735#3&'73#3#3#3##5'65#5#35#‡ $$ ##==6==D2400005l 6&&&&&Ï  #.." J`ÿçõÐ%QUY]agm7'673533#3#67'5#'67#53535#'673533#3#3267#"&55#'67#3#735#35#35#'67&'s     *      >ttPPPPPP  F °      )[A $ #     ÿèñ•!%)73#3#&'#5'67#535#735#33535#335%´OaO"3 8$!9 1#M`Q>>R<Ž>>R<•V  "55  42rÿéòÁ73#3#"''3255##5##535#r€6/  08Á"i  R’’qƒ"nÿèõÉ'7#3#3#3#67&'#67'5#535êRJJII[3   / É   EM ZliÿèóÏ'-735#535#53533#3#3##5##5'66&'m6++3366..=†s@-"! | M<>O(*!  jÿèõÐ#'-373533#3#3#535#535#3#735#35#35#'67&'t244..:‡:--2ppIIIIII #<Ä     DbE ' '     eÿéóÉ %)/573#3#53'#735#35#3##"''3255#735#'67&'ur5;‹;(LLLLq.   0MM `  ÉE * % E3( %*  bÿéñÏU73533533##5##5##5##53#'67#53#67&'#"''32654''67&''67&j$ $„]LL  (…C      (- ! !¾ ,,)             ÿéŽÎ .7'2'6'&''&'3533#&'#5'67#„ 0F91 (    411  &*Î #   =  AJ(( ÿé›É 06<73#3#535#5#35#3353353#3##"''3255#&'''6‹+&%*O%%gtt=  >m  E É==,:( %   ÿé“É $(.473#3#53'#735#35#3##"''3255#735#'67&'n19†8(IIIIn+   0JJ V  ÉE  * % E2) &*    [ÿéëÎ73#"''32765#'6&'W  L"  ΂  n#/  ÿïòR !7&'7&'''6733267#"&5… Y • /  -#R  ( 7 `ÿéôÏ73533#&''67&'765#nF''$ &% %EŸ00>$+# 1_ÿéìÏ73#"''32765#'6&'‹Y   N   σ  n#2MÿêèÏ73#"''32765#'6&'yg\  Ïf@ j!- ƒëÎ73#"''32765#'6&'¤<  /   Î n P % !ÿëîT732677#"&547#5ÀF799 4M_hT   ÿòðÂ7333267##"&54767#ºt0822G$!f–Â\3 !$Q!ÿëîG7327677#"&547#5ÀF796 3L[dG  &ÿìëc7333267##"&5467#0•]%6%'I#$Kec,  &%ÿðî~7333267##"&54767#&®j+3,.E![„~:  3€ÿñò¾7333267##"&5477#€aG   5J¾  "' $ihÿîòÏ"736773267#"&55'75'7› AC03'.Ï" ;@ %  D9l@ò£(733327#"&547#''67'767#53˜)      £       _HõÏ(733327#"&'5#&''67&'767#53”-    Ï.%     ]ÿéòÏ*733327#"&547#&''67&'765#53Ž2  "!  %%Ï,6&#$&"$7 )" 58 AÿèøÏ*733327#"&547#&''67&'767#53;  & +,  %%Ï,6&$#&!%7 ) 87 gõÏ(73533327#"&55#&''67&'765#q.     ±M# ):  $&  ÿêó„+735333267#"&55#&''67&'765#DP  <64 Coh # Z  $   ÿêó„+735333267#"&55#&''67&'767#!?M  987 ?oh # Z "  `ÿéìÁ73#"''3267#'667#'6m   &) $2  Áš"%‰XNB`0! 'UUïÉ#737&'#"''32767#'667#'6ao     : + É  + 1M &$ SZæÇ73#"''3267#'67#'6_‡  & B ;9ÇL5A4V^ñÈ"737&'#"''32765#'667#'6`p    6 -  È- ,D  ^ÿéìÁ73#"''32767#'665#'6rz  "%$%0 Á¤3.qfEGU## ÿéóË73#&''67#53655'2Ò '/jaPJN N_c&+ZË "?&)ED*+; ;ôÌ7'673#&'#'67#536v&!f90kG6<F 6GU¯ !++ UÿéòË73#&''67#5375'6ß <8. )6=34.BË7J?;!I (AÿéòÊ73#&''67#535'2× "KI3 07<@DAÊ'H B;'+=5 ÿêòt73#&''67#5367'2¿ "f\L NTPZa#Ht  +)  ÿêô’73#&''67#5367'2Î $,j_P S [ U [`%)W’ ,40) Y\íÏ73#'67#5367#'2&'Ú>E1 '3<8 Ï "  < uÿêòÎ7#3#&''67#535'6Ý52$#*01 CÎ4D""66"+=4  ÿæ‹Ï(73533#3#&''67#53655#'6'$$.1 $ * 37À ** +%.  $LÿèñÏ&73533#3#&''67#53655#'6n66A>( ,3 49>$ Ä ** 773!#.   jÿéòÏ%73533#3#&''67#53655#'6‡))42 "%( -1 Ä ** 762#/ ;ƒÏ,73533#3#&'#'67#535#3533#7'75#(((/   0(*&&&/:0*Á    >VÿèôÏ 73#7&'''6'66 1 D `@5 19Ï—}"' &#5% &'+4 .U¦Ï7''67'6767767'”    M '%=8" ?&QôÌ)73673267#"&5'33#7'275367' +   & H)))7; Ì+   _+ KIfÿèðÎ73533#3#5##535#35#f;;;0M1;MMž002qq2~9¬ÿðêº 7#5##535#êºÉʦ”TÿéôÐ%+05733#3267#"&55#'67#5'667#367#335ˆA )(  73 + ". 4 !5)Ð  K;  ?7! )@   9'YÿêóÏ,27&'3533#67&'#"''3255'675#&'Ì  \;::      +; Ï   ''1 #B  <$$*. NÿëóÆ 73#3#33#"&''675#735#nt/77)- # 2NNÆU&4,8f1cÿéóÎ 37'2'6'4''&'3567#533##"''3255#á 2H;7 * !  @Xt;;  @Î  d , (bÿèôÍ ,27'2'6'&'&'3533##"''3255#&'å 7OB6 +  [##   [% Í <I  F gÿéêÉ 73#735##"''3255##535#35#mwwQQj  ]]]]]É>=x  2‘';\ÿéóÏ#'+73533#3#3##5#535#535#35#33535#335c<==88BBCC66<##5$Y##5$¼d''d;@Sÿé÷Î1;73673#3#3#'#3##5#535#5'67#537#537#3533&'d99>9?S )::<<* &#*4- !# ¼  ** D SÿèóÐ"',73#3##5#5367#5335#335367#3355#•A#44Y &)##6$Z"6)# Ð m44 m-I  M ]ÿéóÏ(,073533#3#3#535#535##"''3255##535#35#e7==66D–?..7}  OOOOOÀR\  $t 0]ÿéóÏ'+/736533#&''67#3##"''3255#3#735#g959#. 3 –   nKK))¶     9W  T:UÿèìÏ573#"''32765#'63#3#353#5335#535#'6yh   ^ ?'++a.. Ï –!€ 2$43#2 BÿéïÆ (7'66553'#33353353#353#5#5335#q hh[,e -‹)D5 1#_;*&)88):7)E :)7\ÿéñÎ -7'2'6'&''&'#5'67#53533#&à 7PB7 /    J )7<>9% Î #   gJG $$YÿéóÍ%+17'23#3#353#5#5335#535#536'&''&'æ 9QC5 ;DD(t&CC6Z+    Í 5'D :'5Nÿé§Å73#7#5'75#35#35#675#NX   ÅŠ/*—##W$a'Gÿï™Ï 73#'635#53#3#67'5#f'/   4  Ï  T$$9 D]ÿé÷Í #06<B73#5'675#73#5'675#&'7&'&''6'6'67'6a@ .J@ .5[ ) +& .# * +/ #5 7: *R SÍU  U      5       +YÿéðÑ K73&'73#&''67'763353#3#"''3255#67'7&''67##5367#Z@@–a    Q]9G   ;   84½    11CI  2   PbUÿéóÑ;73#5'67335#35#3#3#"''3265#'67#'67#'67#{mt imPPPP'›ff  %  )  !² C:  )& ';"* #  CÿèöÐ $*06<73#3533533##5#'6553&'#3&'''67&''4'©8w"I =+""  Q M  Ð &::-7#PP [) # QÿèïÎ'-3;W7367&''667367&''66''6''6#5##536533#"''32765#'67#z   F    G n'?   -& 0 $Î    '    ! A((/ : ",ÿèòI"73#3##"''3255#535#535'2Ô %-YYgg gg]]SZI   bÿçôÏ#'-373533#3#535#735#3353#735#35#35#'67&'q022@’?01 exxRRRRRR  ;à +   E_C ' '     lÿûÄl7#"''3255#'65535#35#Ä   &%%%%l^  ))-[ÿèôÏB73533673#5##53'3#735#3#3##"''3255#535#535"'2} p aa::S 88DD   BB77:È %% ))2.(      NÿèõÌ&Nf76767'33#"&55#'6653&'#33#3#"''3267#735#5'67&'767#67#53&''67&'µ        -# JD $!'   +     0N 5I  Ì    # +!93 8          HÿéôÏ28<@FL73673#67&'##"''3255#5'67&'767#33&'35#35#'67&'V/H'  .   -  &= D /HHHH j·    :0  -6   G-)   ZÿíñÏE73533#3#535#3#735##5##5#53#67'7&'3#3#535#535'6_=AA3y3= ttNNsq4d4   55@“@55Å   !% ''%        VÿçôÑ !%)-39?7#5##53'73#673#5'675#5#5#35#7'6&'''6ñm:3\$1Gw ( %ZQQQQQ  P#  "À%%   ]SG   i }   Yÿç÷Ò !%6?HV\bhn7&'6'&'6'&'63#735#&''7&'7367'7367'733265#"'&'&'7&'&'7&'Ö %  % ‹‹eeQ   K 8 7  L   D   -   D   Ò         ,I(    2NZNY%# %2        OÿèòÑ#8\afjp733#5'667#35#7'53373267#"&553633#3267#"&55#'67#5'67#367#335&'zI‚ D>^^(4 &A   NH /   . $+ F @ !%6* Ñ $   /  $     # '         SÿéóÌ+/37GKOS7#5'75#53#'35#35#5##5'75#53#'35#35#5#7#5'75#53#'35#35#5#ñlUBBBBBB1 J+ƒ1 J+~ D>3 % & f F:/ $ %   F:/ $ % …ÿèõÏ#73533#3&''67&'#535#67#Œ('''    $(4 +­"",/    &,nÿêŒÏ&*.73733#&''67#3##"''3255#3#735#..1  %) ~   \ AA¶    9V  S9\ÿèõÐ73#&''67&''667# [! %' ( ! >Ð!D, 1 0 :"= ÿò€Á73#3#7'675375#m&"2=3Á;I  ~zœ ÿ醯!73#33#537#537#35##5##535#s<7 z$'/(@???Æ5#O#HWW7%ÿèÒ 073#53&'3#735#367#"''3255'7567#G0r+]]66d  1 GÒ -51    ÿé{Ð !%73&'73#3#536'&'#5##535#),iLnA + ];;;¸  SZ Z>,ÿëŒÎ )73353353#3#3#735#76767'7&'qyykkDD  7C Ã**08*  ÿé‹Ñ/5973#3#"''255#3#3#535#535##53&'#53&'367#35#P3  "=! 31 Ñ    x55“¤ >‚ ÿèõjG73673673#3#&'#3#"''327#3267#"&55'67#5367#53&'J  +fw-# &D]  Q"=E. $6KL#g           qÿéòÈ'+/37;?73#3533#3#3##5#535#535#535335#735#33535#33535#335wv1 066550 21N1B%ÈM"".+ ZnÿéôÏ.:@F733#3'67#73265#"&55'75#'65533533533#7'6'&'¤77<'     + hWCÏ       E;, +7O¦LLLLRÿéía!%+17=7#3#3#3#"''3267#55#5#5#&'''67&''&'âSKKKK^   ´VBBBBBC  K  ‡  Aa  &N   qÿéóÐ'+/73'73#3#53&'#7#3#3##5#535#735#35#v20!‚ I((k+7766,EEEE¼    Q/0 nÿéîÇ #04873#5#73#5#&'7&''6''63#5##53635#35#r<*A;): I ! 5 !4G$GGGGÇ_N_N      j j /;nÿì÷Ñ *06FL73#53635#'673&''67&'767&'3&''33267#"&5''6 ?rLL       7 ?     Ñ {{ v[      O 1  3!pÿéóÐ H7'2'6'&''&''3#&''67#537'67'6776767&'â -C71  '    _ 8, " ( (/  Ð       4       $kÿéïÏ)?S73#"''32765#'63353#'67#5336'673#"''327653353#'67#5336‰W  N !&   M  <$$  ÏL .  !   V  K - "  oÿéóÏ4767#535#5673#35335#535#53#3&''67&'  V,. (./ '  6 [ ]]^   tÿêòÇ 5;73#3#535#5#35#3353353673#&''67&'67#367ww# p!%C  d'A )'  5 ÇCC1 E     ÿè÷Ï735333267#"&55#'655#(*= F6¤++™ ŠZ9 6PdÿèõË'+?CIO73267#"&553'#33265#"&553'#33533533#3#535#35#'67&'| 73 7m$%!.$$  G  Q<,   Q<,O+      ÿìPÏ73'67#'6367' #   Ï %$m  `ÿéöÐ %+17&''63#3##"''3255#535#'67&'§% " !,B77   ;; \  Ð #"!A  =8 $ oÿëôÆ !.73#735#35#3267#"&5536'33#67'ovvPPPPe   g&&Æ_9;= *  _1  fÿéìÎ)73'67#'63#35#535#53#5##56˜@  2!""U##%8UÎ  #, !!} xkÿéêÆ73#"''3255#3###535#qy  eXXQ7$$ÆÁ¨O [<*eÿçõÐ%QUY]agm73533#3#67'5#'67#535#'635#'673533#3#3267#"&55#'67#3#735#35#35#'67&'u    >      ;ppLLLLLL C Ð      )[A $ $    dÿèòÑ#8\bglr733#5'667#35#7'53373267#"&553633#3267#"&55'67#5"'667#367#335&'‡@s <4OO!-  8   F@)  ' & <5 0"   Ñ $  /  $    # '       ZÿéôÏ=LR733#3#53533#67327#"''67&'#'65534'33&'73#67'675#'6¥++8…M&    @Q l5)  Ï###!%  05-" %(>    ; :tÿïóÎ!)-1573533#3#67&'7&''67#535#3#53535#35#35#€'--7<  +/(2'j     ¸    e???-----tÿèõÏ,0473533533##5##5#3#3#&''67#5367#735#35#{j-6* #& ' ! -3*DDDD½P !(   00 vÿåôÐ#'-373533#3#535#735#3353#735#35#35#'67&',--7~5,,YllFFFFFF  7 Ä ,   CbE & &   lÿèôË'+?CIO73265#"&553'#33267#"&553'#33533533#3#535#35#'67&'Å 5S 4$!"ˆ,!!  B Q<,  Q<,O+     qÿéõÏ'/373533#3#535#3#735#3673#53&'#5##535#s566/q/5llGG„aIIIà   *0#  )= =!qÿé÷Ñ3:73#&'#5'67#535#'2373#&''67'7#367à 2% #11<#K  ( &/ Ñ''     {HòÊ$)733#"&55#'6653&''67&'#367Ö "  a   "   Ê   ;     ÿæîj"&*06<73#3#3#"''3267#&'7#53635#35'67&''&'dY±±ª   - z6%|||“ aj4   Z:      ÿéîn",08<73#3#3##5#535#53&'#53'37#'6553'#3#5##535#µ0 $4..%%.!+| [44"""n   1 ,+*3# <<$-ÿëÎz%/37;73#3#3##5#535#53&'#53&'367#'6553'#33#735#¦   P>++ z  $$ 6 % !36'=A òÏ -37;73#53&''6553'#373#3##5#535#536'&'3#735#ÊPQCP! $-=11 Ï  I;;;C2!33  4R05î¦#,04873&'73#3#3##5#535#53'#67#'6553'#33#735#|-12,,))1 C!Z661FF##˜   '.+ ÿêñ;73533#&'#5'67#eeR&. 6'%6 .'R/  .- ÿéï%.2:>73#3#3##5#535#53&'#53&'367#'6553'#3#5##535#¶+#3--''/ + #}[44  (( H.- '45B0&UU6%MÿèõÑ6BFJV7'67#537#5367#53&'73673#3#3#&'#67'73&'73&'#35#35#67&'j%1,2=(" DBGV' Y   $ FFFF/  )K     5 m " $  [ÿèôÏ#)/73533#33#53535#35#35#35#35#'67&'d8??3™.8OOOOOOOO  H¾xx-,,+"    UÿèñÓ4<@DHLPT73#33##3#"''3255##5##535#535#'6553&'5#35#5353535#33535#335²65+ +0  +((0< 00(:D+I+Ó   T  !i  C3 5=\+ (   C ) YÿïðÈ #'+/373#3#735#33535#3353#3#735#33535#3353#\’’ $$6#Y$$6#pŒŒ%%7$[%%7$y——È B' $ # B( $ %PÿêôÏ=LR733#3#53533#673265#"''67&'#'6553533&'73#67'675#'6 ..;M%    DU  l8-   Ï##"$%! 15-! #)>   < ; cÿçóË #'+1773#3#535#5#35#3353353#735#35#35#'67&'d-'„',Q&&kwwSSSSSS B Ë 55 $,bE & '     DÿéóÏ&*.26Kf733#"''3255##5#'655367#'635#33535#33573#"''3255#'67#3533#3##5#535#'6q        *  J   $$ Ï • )&&%@@  TGq7 $+!E 11  UÿèôÐ .4:73#53&'3#735#3#735#3#3##"''3255#'67&'¨>‘?/YY ??,ttŸF  G& Z Ð 'U8( ;     PÿíóÏ>F73533#3#535#3#735##5##53#&'3#3#535#535'67#&'7#6[?BB8ƒ8? ||VV{yn %88J£E55 S# Å  % !((          RÿéòË >BFJ73#735#35#35#73#735#35#35#3'73#3#3#3##5'65#5#35#a>>6AAG96////8u =*****Ë`E ()C`E () E   ÿë‡Ð+17''6767&'3#3#'67#5365#'6&'f ''+F)582 (,0 :Â! ! 6( B   ÿè„Æ%)-373#3#"''3265#&''67#'67#735#35#33#`8 M     ::::=PÆSVB      21X( ÿéÏ'/373533#3#535#3#735#3673#53&'#5##535#566.p/5kkFF #„dHHHà   *1$ (= =! ÿéŽÈ159=AE73#3#"''3267#3#7&'74''75#535#'67#735#33533535#35#qKY  -"""')""    K#È>iU/ /  qÍ737533#67'7 ,5¢…«DN gÿêõÏ#)73533#3#3267#"&55#535#35#'6u155)  :+1@@ $!¸G? EGN!::cÿòòÎ#73533#33#53535#35#35#35#35#p277-(2BBBBBBBB·‹‹;655`ÿîòÂ4873#"''325567###535#73''67&'767#3#iw ]. C4       \‘‘Âs y "d nR@/   y^ÿéóÑ<73#5'67335#35#3#3#"''3265#'67#'67#'67#‚gnbfJJJJ%’``   $   '   ²C8 )& ' ; ) #  MÿéöÏNRX73533#3'33#6735#"''67&'#3#3#3#67'75#535#535#535#535#75#7&']##.**      " -8!)! a  ¸="+1  #> —pB  uÍ7'675#535#53&'73#3#6r02##(& ') *;2 27 ÿéŽÏ(73533#3#535##5##567'5#'665/,,$_&/pKX   º8,-*8   9#  & ÿð|Ï(73#35#535#53#563533#67'675#7?-e+((09+Ï jb„ !ÿïyÏ$73#35#535#53#56'675#53#67?-eM5* &^%Ï jb©.)tÿèöÐ 1573#67&'7''67#53&''6733267#"&5'3#¶0;  %( ,= Ð #  ! tK?L OheéÎ&7773267#"&55'75'75'2Î '0UVgi! ,(WZHJ"(XÎ      ÿì÷Ñ(777327267##"&55'75'75'6O 3 P+ Ñ.(2   4)- ÿéŒË(AOS733'67##"''3255'67#53&'767##"''3255&'#5'#5673&'75#35#q,    +0   Wt  4  +  U"Ë       We  4 .,K|+   A ÿèÉ -273#735#3#735#73#735#3533#&'#5'67#37dd>>66177W565  *;É3055<16ÿéÏQ73533533#3#5##535#35#3##"''32654''67&''67&''67#53#67& V- FF;   $) $ %|>   ¾,,-\            ÿé’Ë ?CGK73#735#35#35#73#735#35#35#3&'73#3#3#3##5'65#5#35#77,99>-,&&&&.a 2 Ë_D (*C_D (* A RñÐ,28>BF7673#&''67&''##'2655##535367#''6'&'3#735#† ?    M.I  "2 ?  11ª#     ( /JX&&  *   3&  jË73#3#5#53&': #?;N (ËlÿêóÐ !73#'63#3327#"&547#6¥° ¸ ¥Ð   7'0!,GÿèõÃ73&''67&'#367T“,$ & 13#  "ÃT<"%""0M*<0;LÿèçÐ 73#'3'6573#Ôn7Ðçç|C) '9oÇFÿèõÏ73533#&''67#W9FA?4= H9’==S1-FF,6ORÿèíÏ73'67#&''6ŒX;+ aJ (Ï RA!v  0NÿïòÐ73#3#5#53&'™ Fwl‚H ЋŸQÿêðÆ73##53#3#"''3265#itt)Ÿb ] aÆ<*G+KÿîôÏ,7#"''3265#532767#"'&&55'753753ä  &", :( ¤U$/ _Y d ! cB< D>BÿèóÌ73673#&''67'67#367L2_  #"8 55 ,B + š@ #  #"$ 5gÿìõº753#3267#"&735#gzf- 7(RR¹oC   hI`ÿéèà 7#5##535#35#è`````ÃÚÚZG¡G^ÿéìÏ!7#"''3255#&''67##5373ì  * *?¨§  #A­¿''DÿèôÏ 73673#'67#''6767&'[$Z],+ !u %,! ¨fF GXX#)  -RH,JÿéõÐ "73#'63#33267#"&547#rhr ll jÐ  ! ! )bÿéóÐ)73#"''3267#'667#53&''67&'”G  9  Wo%# &! # Ð3""V3   JÿèóÐ+73#"''3267#'667#53&''67&'…S  E !j„*( . ) Ð 3$#Y2  TÿêðÁ73#3#"''3267#5335#367#TœGB  y)BU,/Á*q%bQnnIÿèõÏ 73#7&'''6'66š4G q4` 6@ÏŒm!'( )! ,J,JÿðóÊ733#3#53533¡88=©!!ÊGj••GÿèñÏ733#3#5#'65533¸%u\J!<Ì< jW7#+@n?lÿéõÐ$7367&'#"''325'3'67#®    4-   Ð5 ."!3R  ‡.#1?JÿèôÇ#(733#"&55#'665#53&''67&67#Í" 3  (' ,. ,,LÇ<  0#  *v3  YÿééÏ 7#5##535335#33535#335éj??,,?+j,,?+¡¸¸..I777888SÿéðÏ73533#3#3##5#535#535#X>AA<©&&"'>>'"[ÿòñÇ7#3#5&''67&'76ð‚–l  ǯÕ&#   GÿêêÏ73#'6753&'735#²8s; 6__§\%)3_^6 ÿéâÏ73##'6753&'735#–LŸ Q N‹‹§U$,"6WW/ ÿéyÐ753&'73#'6735#"% D11TUZ<$ "I6lÿêìÏ73#'6553&'735#À,X,'EE§\># 8W_7Sÿæ÷Æ#'-273#3#&''67&'767#535#35#3353655#335dŒ>785 2 ) % 46<""4#W!"4#Æa    a>? ÿéá\ 7#5##535#35#á›››››\ss)A^ÿêëÏ73#'6753&'35#¬ 0c2NNÏ\?"1_k6 ÿê„Ï#'73533533#3#535#35##5##535#35#x(>:::::±  Crr*AÿúˆÏ#)7367#53533#&'#5'3#7'6'&' #""   M`b  5  ÃŽ#MM =D'É ÿç“Ï/37;?CGM7367#535#5#53535333##3#3&''67&'7#735#33535#33535#33567!#0,,,,1<;  ',F,J08   8-   -    ) G; QÿéêÇ7#5##535#"&55#'7335655#êqq  WqÇÞÞ¹+ i/7" ~dix*/?ÿçóÏ"7&&'67'7&''5'6556Î  ! =ÏHT$%WR¨$ ²+_< 9Z9]ÿéäÄ 7#5##535#35#35#ä_______ÄÛÛ<)d)d(DÿèîÏ#(73673#"''3267##5'67#'676735œ!?  +1 2* --"Ï#8A 1ia3"( '#H6FÿéòÏ73#'675353335#¡Gt,Q~``¤N41FFW*SÿêòÏ73#'675353335#¬>f!(FnRR¤N4-JFW*uÿéòÏ73#'655353335#º3T 8YAA¤N5:;FW*zÿïóÏ73533#3#535#3533#3#535#…'''0q.''''3y3'­""$$c$$%%rÿêóÏ28>776767&'7&'#"''3255'67'67'6&'''6Ú . ! !&   D 6Ï 5  D  <   #  lÿéóÏ#@DHL733533##5##5#533#735#73#735#3'73#3#3#3##5'65#5#35#‘ !!99188@*(%%%%+a 4#####Ï  #..#  J lÿéõÐ#'>B[^73#3#&'#5'67#535#53635#35#''67'677767''''67'6767677'¨ 6-  .7  A        O        Ðf 78f 6H   '$   '"  uÿêøÇ $(,0487#3#25267##"&55#535#55#7355##373535335ï((( %  ###@ D/U/Ç8h  g8ʺ° º1*nÿèòÉ -73#735#3#735#'3#735#3533#&'#5'67#ƒWW33 77Q8876+  ,É82876  76nÿéöÉ 4873#735#3353353#3#67&'#67'5'67#735#uvv!j„„ q*   -   KKÉ75 4    4 $  $ wÿéòÌ  5;73#535#535#3#735#'3#7'75#3533##"''3255#&'„ccPKKP388P6T  T  ÌJ  B75      iÿéóÐ@FKP733#67&'#"''32654''67&''67&''67#5'6367#367#335’4"9       # '   ,) 1Ð  9   (*     - - lÿéóÏ"&8<@73#&'#5'67#5367'23&'#35##"''32655##53#735#Ù ? N   $,)++O  K77Ï >'+Q ;Uf.lÿéòÎ(048<@73673#&'#5'67#53&'735'2##53#'#;5##;5#ß  *  (5)Hl.Î   '$   Î hhW$ sÿéîÇ #/3773#5#73#5#&'7&''6''63#5##5335#35#t<*A9': H  4 2E%EEEEÇ_N_N      j j&;ÿçéi#'+/39?733#&'7#'67#5'6367#35#33535#335&''&'WM3      * MH 99J9ƒ99J9+   i <    1  "     sÿéóÏ#'+/DJ73533#3#&''275#535#35#33535#335'#3533##"''3255#&'u244/ +8,,2,I,ET  T!    JJ *,& !      vÿéòÐ'+/73'73#3#53&'#7#3#3##5#535#735#35#{/-|E&&g)3322*AAAA¼    Q/0gÿçóÑBFJP7#35#535333##67&'#"''3255'675#535#'6553&'73535&'ðe&%     &5?  ½4 ##   "'    ! C3 4=[ 0$.  nÿèðÇ9?E73#3#"''3267'6765#735#73#3#"''3267'6765#735#&'7&'p;"&    * (C;!&    *(: N Ç:h!   <:h!  <_   jÿéöÏ%*.?EJ73'67#&'''673&'67#3#3##"''3255#'67'‡ &  F)  / 8>>q/  / Q !Ï <-     K* 89 6 )pÿíôÆ ).>7#3#3#535#35#5#73&''677&'#3673533#3#535#±? 1<  g0229ƒ70Æ6ˆ)>>w8&   $)ŽvÿéóÏ 77'2'6'&''&''673#3#353#5#5335#535ä .C7/  $   S+22]44Ï     ? 1'B 6$1xÿéóÐ&.26:>77'7''535633#"''3267#'67##5##535#33535#35#¢ !A ;K/K/Ð /   LE/5 .Xq q+D[õÏ"7367&'#"''325'3'67#y ( ;#   dX@ 8AÏ    ,+  >."BÿéëÏ#)736733#"''32765#7&''67#7&'L;O:    3;9!  Œ&&Y7 T 9,.ROCÿéôÏ73533#3#&'#5'67#535#V>BB<.*&*.*8>©&&&*%*1mk9!#0&AÿçòÐ '-73673#3&''67&''67#67'&'V'\_R %## %  , $-   £8!     0N4!o  WÿéçÂ7#"''3255##53#735#ç  k)??¾  §ÇÙ6b@]ÿéæÏ73353353#5#353#5#5335#]&((*v%9»?SS?XeMj _MeZÿçóÑ27&'#673267#"''67&'#67'53&'3Ë  4A ' , >Ñ !<" +%Em  * ÿèóv"7367&'#"''325'3'67#x ' 8$   _T< 5>v  .;  S5(8ÿíóÏ4736733#'667#7&'32767#"&55'67536N'ab&r &" " ž9B =3A M )   M<9ÿêôÊ )7#'6553533#3#535#3533#3#535#óŠ!.//74.200;“D2ÊOA= 2?b2O  IÿéöÏ ;733#5##533'67#&''67#3267#"&553#"''325–DqA0*C 6   u  A  Ï$%) j)R   d  zE  HÿêîÄ%+57367&'#"''3255'67567#&''3353#5#_y     "^ €“Ä  $ (2 #  ¡TÿéòÎ"*.273#327#"'#7'56&'#5##535#35#Ù?7 <& NzbbbbbÎ 0  [* Fl l'<HÿééÎ)73'67#'63#35#535#53#5##56ŠL @ *++l,,/BlÎ  !* !!} x;ÿèëÏ473#"''32765#'63#3#353#5335#535#'6c}   t J044n88 Ï ”"  2$43#2 ;ÿéõÏ-73#'6553&'#3&'3733#&''67#  ;DHmm  L,<4 1,9 5)Ï ?>2 1:W #   **)*LÿéóÏ.287#"''32655'675#537#'733#67&'37&'©   $)!I]KQ-D   (A D _ ,) .8 6   £R MÿéòÐ+/373#67&'7''67#53&'#"''3255##535#35#¢DZ"$ 6=4IR  ZZZZZÐ     `i  )€$4OÿéóÏ(,073533#3#3#535#535###53#"''325'#;5#X=CC;;J¤F33=tW 5""""»2t\  G""eóÐ"7367&'#"''3255'67#53x & :% A 9BWÐ    $&  MÿéõÄ.4733#67&'#"''32655'675#535#535#&'dnE  (   $)"KrYY[ ÄM   %. .W HÿèöÏ*0673533#3'67#535#&'36533#'67#7&'&'^5::C  ‚A5 !OBHD 3E e! ¸ 1  5*'%>  ;@ÿèïÏ'-735#535#53533#3#3##5##5'66&'PB55>>AA88IŸ‡P4,/+&% #| M<>O'+ #  EÿéòÏ#'+73533533##5##5##5##535#33535#35#E$6--6$›g++>)g++>))¯ )‹‹5###Y%%%ŠÿïôÏ73533#3#535#3533#3#535#”"&&)c'""%%+j,"­""##c%%%%FÿèõÐ59=AEI73673#33##&'#5##5'67#535#535#535#53&'5#3533535335z   ,4*!  &00&&0- =AÐ %%9II6 8'EÿîðÏ '73353353#3#3#735#3673#53&'W)+ ¤¤„„^^ '  '«/ Â**/<2  BÿèíÇ9?E73#3#"''3267'6765#735#73#3#"''3267'6765#735#&'7&'ND,3   &7 *2TI,3   &7 *6R d Ç:h!   <:i    <_   IÿéïÐ#)/7373#33#53537#5#35#3#3&'''6QABE=¥2>y\\\\\\ (" ¾ww0  BÿèïÐ,I73673#53&'&''67'6767767&'7&''67'6767767&'p+  '¨) /   h   Щ $.)(B( $.((C'JÿéóÉ'+/37;?73#3533#3#3##5#535#535#535335#735#33535#33535#335U‘@@KKFF=>**=-j**=-\/ÉO""/+ ZGÿéíÏCG733533##3#"''32655&''67'75##5&''67&'75##535#5#53#3j8$$'G    4  0C&##K88Ï"s    =s(   :yŠ" HôÏ!38AEI73533#3#535#733#"&55#'665#53&''67&67#'6553'#3#5#011(d)0¾   `  +b(Ä     E       . @ÿéõÑ 7;?CG7&'67&'67&'63#3#&'#5'67#535#735#33535#335k  C  ?  u’BO>' ,) (;H=++>/m++>/Ñ      -O  "84  /1 ;ÿèõÒ!%+17=733#5'667#35#33535#335&'''67&''&'}D - '5 9 **>*h**>*  t f    Ò qe  0M7  PÿéîÇ 37;A7#5##535#&'3&533#67325#"''67&'#3#735#'6îxxx_ P<      <//!ÇÞ ÞÀ®   & 22,  BÿëìÏ !%)/573#5##53&'3#3#535#35#33535#335'67&'šFJ"f+=Œ<())<*f))<*Q " $L Ï +,1\\6:+    DÿèóÏ#'+/7;?73533533#3#535#5#35#33533535#335335#5##535#35#D888.™,8d,,r, _____ÁOO -/[ [.QÿéóÏ#'+1FL73533#3#&''275#535#35#33535#335&'#63533##"''3255#&'UACC: 8I#!88A%%8'_%%8'jn!!  n,    JJ *,& "     IÿððÏ+/7#5##53&'7353367#53#3#3#535#53'35#ëx    <%a)<BF73533533##3#3#3#&'#'>77#5367#535#535#5#5#35#335O> (==?@N<. 6: :D:<;;'p>'';)Å  2 #%   2 ?8ÿçòÑBFJP7#35#535333##67&'#"''3255'675#535#'6553&'73535&'òŽ9++2,   $   &++9F ] ¾3 "$   %-    A4 4=\ 2#+  BÿéôÇ/37;?EK73#676767&'7&'#"''3255'67'67#735#33535#335&'''6VH-,/&    .**<+g**<+ IÇP  $+ %  00v   ÿéñI$7367&'#"''3265'3'67#x  8$   cT4 *>I   5%CÿéóÐ39=A73#3#3&'73#3#3#3##5'67#537#'65#5#35#okEZb& 071111;| "*2  @-....Ð    U  ) p""CÿèõÌ&Nf76767&'33#"&55#'6673&'#33#3#"''3267#735#5'67&'767#67#53&''67&'±      /& MF"'")   -    2Q 8L  Ì    # *"93 8         >ÿîòÐ"&*.4DJP73533#3#''275#535#35#33535#335'#&''33267#"&57&'''6IFFF;  @I&!;;F((;(c((;(  &f  u  ¾I I+ ) '   (  ,  >ÿêñÎ*06<B73#35#535#53#3#"''3267#'67#56&''&''&'''6Œ--h../Bh  y  N       Îa K3_ BÿçòÏ#'+/5;73533533#3#3#535#535#35#35#33535#335'67&'Q!.##,M==K+!4..))<*f))<*P , &O ¹]]_;+    AÿçòÈ#'+1773#3#3#535#535#735#33535#3355#5#'67&'UŠ%%*¥*""'':(b'':(+++ ! T& #ÈZ66<(   ?ÿéóÌ $(5;73#535#535#3#3##"''3255#535#735#'3#7'75#&'Y‡‡skksII!!  €€##kM$+!!  ÌK  @:  K   >ÿèñÆ!'-39?73#3#3#535#535#735#&'735'6&'''67&''&'S?AAL¨I??>++  9, $  v b    ÆcA   AA  † EÿéóÉ &*0673#3#53&'#735#35#3##"''3255#735#'67&'[‡@K©H1aaaa†8   ;bb r ÉE* % D4( %*  xÿèòÐ'+/473#3#3&''67&''67#5'635#35#67•KTU7@       5555Ð L    E2* @ LÿéñÏ37;?C73533533##5##5#3#3#3#3##5#535#535#535#5##5#35#335P(,%%,(“?GGII@n/½ *KÿçíÑ2=HL7&'3#"''3255#3533#3#&'#5'67#535#35&'75#367'35'3#` 3k  Y-,,(     (-  )  nÑ Ä  ¬J 58JJ     ,6» OÿéïÏ '+/37733#5##537'6'&'3#735##5##535#33535#35#•G|F? g kkFFll--?-l--?--Ï#-.!    220d d&9?ÿéòÊ -73#735#3#735#73#735#3533#&'#5'67#aooGG(DDBCCmHI=) (* *=Ê4177:  ":9HÿéñÈ 5;73#735#3353353533#3#3#3##5#535#53&'#535#367#R––+;AAJ=JJLL<F;3*BÈ8;   8 GÿïóÐ #'+/>73533533#735#33533535#3353353#3#735#3673#53'Q..™,u,««ii- 0¬(ÁQ0/+ / #  LÿéòÏ<@DHL733533##5##5#533#3#"''3255#67'7''275##535#735#33535#335v*,,***‹=I 7  $-4G<));*e));*Ï #N D  /  J[ ., BÿìôÑ#)9?E73#3#53'#53'367#3#735#35#&''33267#"&57&'''6 @(£+=&9,YYYY+ ! )k  x  Ñ   5 (K- ) "  " ' BÿéñÏ(048<73533#3#535#&''67&''6#5##535#3#735#KDEEN¯MD  h  'mmmJJ%%¿;;       =kkRB * 8ÿêòÏ=LR733#3#53533#673265#"''67&'#'66553533'73#67'675#'6•55D¢ V+    O a y?3    Ï##"'&" 45,"  >  % < ;AÿéìË "073#735#35#35#'33#5#'65#535#73#3#5##53uOO++++++80 ;1‡24"1ËgI ) ) 5`n; "qpK;;o@ÿëõÈ'-GMS73#3#3#535#535#73#3#3#535#535#&'3673267#"&''67''67&'NGIRGJ #",!   ‰  ÈY  3' 3   9 8ÿéøÉ+;K]q7#53#3&'7'#"''3255##5##5'673''67'76'''67'7''67'76'&''67&'76M«L:   41 % *6    A    M    @    · ’  „žžž£           /       LÿéñÐ #)/7;?CT7#5##53&'7&''33267#"&57&'''63#53535#35#35#3##"''3255#ëtA   [ a ˆ¥&'e”>  C½         '''4 >ÿéñÏR73533533#3#5##535#35#3#'67#53#67&'#"''32655'67''67&F'2++&z"':22__" 3§W    *59)!, +!¾,,-(           CÿéõÏ048<NS73533533##5##5#3&'73#3#3#3#5'6#35#5##53&''67&67#K1)'')1*=@7766B–@00000 )#. (+KÁ      ?    (     :ÿíòÐ "(048<73#'63#53#3#'#335357&'3#53535#35#35#»+2 +ZX++M +¸+)Ð G3I"  5<<<***** >ÿéôÏ$)-15;A733#3#5##533#'#5##5'63&'35#35#35#''67&'ƒIIS}= hh3 %/ ] /K]]]]]] f Ï ## [ Y  & & o    @ÿéóÊ #BFJN73#5'675#73#5'675#&'7&'3&'73#3#3#3##5'65#5#35#KJ!6UL!9@  h @*A?9999Aˆ C44444ÊW  W      4  U  ~ðÐ"7367&'#"''325'3'67#| % :  _O>2:Ð     ' )  ƒÿèõÏ#'-373533#3#535#735#3353#735#35#35#&'''6Œ%&&/o-%&L__999999*  à +   DaD & ' "     EÿçôÌ%+RW]73#33#5##53537#35#35#35#&'7&''332767##3&''67&''67&567''6NžNC„-<``````. A R !R  ( $2)    ]  Ì DD "             '  3  IÿçñÐ#'+/5;73533#3#535##5#;5#35#3#735#35#35#'67&'NEJJ?;E›–YY ††`````` !T à   00)U=    :ÿèõÑ6BFJV7'67#5367#5367#53&'73673#3#3#&'#67'73&'73&'#35#35#67&'Z (629F.'$LIOa- d " + QQQQ6 #/N     6 m & $   <ÿçóÏ#V73533533##5##5#3#735#335335#67327#"''67&'#&''6553&533&'7M'),,)'””+<     @ O À 8A     !$$  MÿèòÐ -159?EU[73#5##53&'3533#3#3#3#535#535#535#35#335335&'7&''33267#"&5''6¡>o?)**77AA8‚8??55)&6 J  W $   Ð $%-    ..   [$      AÿèòÐ -159?EU[73#5##53&'3533#3#3#3#535#535#535#35#335335&'7&''33267#"&5''6˜Hy@ ,..<ÿéñÑ QUY73#53&'3#735#73#735#3533533#3#3#67&'67'5'67#535#535#5#35#—I¥F6??BAA!!e&-((##-L   =  #7, &e---Ñ$+ ++   ,     CÿìôÏCGKO_7335#535#535#53533#3#3#35335#535#535#53533#3#3#353#3#3#735#3673#53&'O™¦¦‰‰bb ( 0±.ËT XX Sc  -    AÿéóË+/37GKOS7#5'75#53#'35#35#5##5'75#53#'35#35#5#7#5'75#53#'35#35#5#ñy _LLLLLL  7 Q/“ 8 Q0~ E@4 & & f G;/ % %   G;/ % %  AÿéòË'+/37;?CGK73#3'753#3#3#3#3##5'67#735#35#35#35#35#35#5#5#35#RD( &GA<<5555?‚   Q""Q Q""Q Q""00000Ë` \` H  E ()J @ÿéìÏ#CGKO733533##5##5#533#735#73#735#3&'73#3#3#3##5'65#5#35#t(**())!CC!!?CC J':<6666=} ?.....Ï  #.." P :ÿçñÑ#`dhlp7#5##535#535#53533#3#535#35#3#35#53#3#3#3#67&'7'5'67#535#535#535#735#35#5#35#ñŽN>>JJKK??--@-- 9) ;++%%6N  A  #9.''E)))‡#$# # 4    %     1-òÐ(,73#"''3265#3#32767#"&55'635A‘  † j_4ITA !KÐN3;  H !Nÿé§Ë73##5#'66556œ 3  %Ë /;5 4E:[ÿçõÓ)/U7&'67327#"'#'67&''7&'7&'673267#"&''67&''7&'7® DC   "! +% RJ  % ,'Ó         H   '    ÿê[Ï733533#3#5#'65/)Ê=BB)O=3# #5 ÿèðÆ"735#53#3##"''3255#'67#'7 mŠÒ488 2T M-c¦??_  [>"0ÿêéX!735#53#3##"''3255#'67#'0oÉ&++   +UF#PE   $ bìÐ733673#5##53&'733#735#v-±.  @””llÐ" ++ - ÿéTÏ73533#7#5'675#'6     Ä11> PF G(ÿêˆÈ  $73#735#35##"''3255##535#35#qqMMMM[  AAAAAÈN..+n  -„#5ÿõîÁ73#3#3#535#535#Í]MMeÜcKK\ÁHKKHWÿèëÐ 73#'3'6573#×a#1Ðçç„=' #4wÇQÿæõÇ"7#5##5333267#"&55'>ÛC,  8 " Ç}}*9 ? 10(3ÿîîY73#3#3&'73#535#535#Î[SS4 ÜdTT_Y   iÿéóÆ"7#3#327#"&'#67'535'#ç.64 .! X-)ÆK$#$)-.B Ò%%\$$bÿëóÍ73#3#353#5#5335#535#'6…W2>>"k">> Í0VC` VDV0 gÿëõÆ .73#735#35#33#67'73267#"&5536g||XXXX)) }  Æ_9;11  ] )   ]"ÿîñd7#32767#"&55#;5#ܧ;KUFTAA@@d@  `\ÿêòÏ:73#5#5335#535#'677'67#&'&''6733#3#3Øn%@@   ]3   #K:T1==# 6.%  0     .%\ÿéóÉ %)/573#3#53&'#735#35#3##"''3255#735#'67&'nw7?”?*QQQQv0   3RRe  ÉE * % E3( %*   KÿéøÏ&+/37=C733#3#5##533#&'#5##5'63&'35#35#35#''67&'‹DDMs9``. $- V *FVVVVVV a Ï ## [ Y  & & o    |ÿè÷Í!7&'7'7''5"'66556ä      6ÍwGMª # ·/I= 8I< ÿèƒÑ DHLY73#53&'3#735#73#735##535#535#53533533#3#3#67'5'675#35#67&'K2s.%11*222D   8   Ñ%)  ) p    !   ;    |ÿêöÃ#73#33265#"&55#67'7#&'|pG2  4Ã$‡ z| ¸M ÿé|Ñ?767&'3#3533#"''3255##5##5'67#5367'67&'!  ;B%     Ê    ;  &YYGB [ÿêõÄ#73#33265#"&55#67'7#&'cˆ\D   4( 8  Ä#†  yz ·I ŠÿêöÃ#73#33265#"&55#67'7#&'Šc>,  .Ã$‡  z| ¸Mÿé{Ð !%73&'73#3#536'&'#5##535##X@i8 &  U444·  RZ Z>- ÿïƒÏ)-15973#3#3#67'675#535#535#535#'25##5#35#335l .  -,7+  ,- !Ï!! !!X!!!!3!!! ÿé|Ï"&*/DJ73533#3#''275#535#35#33535#335'#63533##"''3"55#&')**)'1'')(?( LI  I"Á JJ ,-' #     ÿéƒÑ06:73#3#"''3255#3#3#535#535##53&'#53&'367#35#G2   72 +Ñ Ž  y55“£ @‚ ÿëÎ $(,06<BH73#3#3#535#535'65##5##5#35#35#35#'67&''&''4'+P m P     F  a   Î1++ 7111111<+++++D  ÿé‹Ò)/37?C733#&'#'67#3'73#'655'667#3#3##5##535#19  $(_ 60 PPPPQ...Ò     !  ,* "(R M  7 7[ëÒ7'6'33#5##53'&'3#735#ÃCb®a< „„\\Ò  #8''8#  43ÿêöÉ!%)/473#3#33#"''67&'767#535#35#3353655#335v3*+JF>Q"  )+1+C+Ée  e<C ÿéíS73533533##5##5#35#35#'d((d':ddddDII0YÿéóÏ$173533#&'#5'675#&''67&''6e7::' # &7  Z ´F6)RM%.L   &  ,ÿêÔ] 7#5##535#35#35#Ô]s s ) * \ÿïñÍ+73533#3#535#'67&'3533#3#535#m044@‘=0I J222?•B2µ3    ,   fÿêóÐ#+/373#327#"&'#67'56&'#5##535#35#Þ 61   0# ?(mPPPPPÐ   ]& Jl l'<+ÿéÒS 7#5##535#35#35#Ò‚‚‚‚‚‚‚Sj j $ % ‡ÿçõÏ '-736533#3&''67&''67#677&'‘;<6      ¢7#     0N4!o    ÿè‘Î"&*.7373#3#5##5367#'6655635#35#35#‡ +:($'$/$ D//////Î ŒŒ H: 8G<v:9QÿèòÍ"&*.73#3#5##535#'66556535#35#35#å 44,A&0  N AAAAAAÍH;8G<*K99^ÿîõÏ'+/373533#3#67'7''67#535#3#53535#35#35#q/33>N   /5%9/v— #"¹    i???.....]ÿëîÏ !%)/573#5##53&'3#3#535#35#33535#335'67&'©=h>V#4y2 3!T 3!E  C Ï +,1\\6:+    WÿêöÐ )B7&''67&''6''6#5'6733#33#"&''673§   2  Y    P     Ð   %2   &/ ' yY "/ $E _ÿéðÑ I73&'73#&''67'763353#3#"''3255#67'7&''67##537#_=>‘]     OY7D  8  43½    11CI  2   Pb ZÿéñÏ#BFJN733533##5##5#533#735#73#735#3&'73#3#3#3##5'65#5#35#ƒ"&&"$$==8>>E!5611117p 7'''''Ï  #.." L+ÿéÒK 7#5##535#35#35#Ò‚‚‚‚‚‚‚Kbb " $ [ÿæðÑ3=GMSY_733673#5##53&'733#735#3#3#3#535#535#73&'735#336735'67&''&''&'œ p!  *ffDD ƒ988B’>558   &8  h  z Ð   # 4     U         ÿë€Ï 73#3#&''67#537#'67:(*  $-03  Ï &#;1lÿéôÆ733##"''3255#53567#ym==  99UÆ\ Y! ÿênÆ+733'67##"''3255'67#53&'767#U     ) =Æ  i  @ $,  ZÿéöÏ$7367&'#"''325'3'67#Ÿ " #  <2  Ï8!9"'6Y  Ž0$3? ÿèîT73#3#5##5'67#35#Úƒ …| +;7||T G6@kÿñðÎ73'67#&''6¤ C7% X6 'Î R: q   2iÿóóÐ 73&'73#3#536'&'q4  2|\.ŠG3  ¤ G4>>5591gÿèôÏ73533#&'3##5#535'67#n47- )£,,M!$LS00QC++GkÿòòÇ 7#3#5##53533#"''3255##ïps‡D$% ǯÕMUgS >mkÿéóÏ#'+73533#3#3##5#535#535#35#33535#335p47733<<99//40O0¼e''e;A_ÿñõÏ  &7&''63#&'3673#7&'¢% !"" / PP  )G /‰ Ï%*(&( !X-+/"e!eÿêøÅ@73#67&'##'32655'67&'&''67&'767''67#p,     & +    $<Å @#$? 04      iÿïôÏ&*.2673#3#3#3#535#535#535#535'25##5#35#335à 6:6}4656"'Ï&##&`&&&&6### ÿèîM73#3#5##5'67#35#Ú† ‡| &67||MB 3 7lÿèóÎ#)/73533#33#53535#35#35#35#35#'67&'q588- … -5HHHHHHHH  CÀyy-,,,"      ÿèîF73#3#5##5'67#35#Úƒ‚| )97||F ?/ 4UÿèôÂ!73#3##"''32655#&'''6rllŒ=  :o  HÂ&t p%* *% 5"\ÿêóÏ#)/733##"''3255#'67#53673#3'67&'¤44  8!(EK %$ Y Š.I  D$$! %!VÿéñÏ,047373#3#3#5'67#35##"''3255##535#35#e$RZZ2:‚+c   OOOOO¾ /-a  (x - VÿèòÊ &@FLRX73#735#35#&''67'676777'76777'7''67'67&''&'''67&'i{{VVVV    4     ?  3ÊO/-W    "    L  ÿéñ= 73533##5#ghhg+00 ÿé~Ï73265#"'&''7&'37&'k2   ‘ 9%%I!)G>  ÿèuÒ 7'67&&'3'67#E"  XC®(#3 .2( +#ÿèöÑ 7'67&'&'3'67#º"   Z   F¬($3 '/ +„ÿêóÏ73267#"&''7&'37&'î< #  “ =7"=K!)GA ÿéòÐ733#3#5##5335#­22*4 44Ð.0wwR@€ÿèñÏ73533533##5##5#35#35#€**$****£,,,,©©99ƒ8ÿæòƒ'-39?73#3#3#535#535#73&'735#336735'67&'7&''&''°MTThähUUO  ;O ˜  K  aƒ9        f         ÿçwÄ73#3##5#'655#535#5#`:ÄLjj>( %5LLLL ÿèâÏ73#'66553&'35#ˆN¬ Z>™™Ï 4+A1 /!Y 3 ÿúmÁ 767'7535#35#75#_(2  Á£ ²;(a(b# ÿéÏ#'+73&'73#3#3#3##5'65#5#35#,   "O *Ï   Ž +- 3 3  ÿénÎ $(,7'673&'73#3#3#3##75#5#35#  >'~ 4##$ˆ##4##4$jŸÎ7&'37'5#~  " Î 9e  kOÿÿ•®767#53&''67&'g $7    †>( %“ÿéóÏ"&*7'673&'73#3#3#3##75#5#35#£ ?(~ #'!"&Š!!3""7&ÿèuÉ#';73#3#5##5##535#3#'3#3#'3#3#3#67'7''67#S#,+!5+++LL c5  É-??-.    oÿèõÏ'7367'673#&'#'67#3#'3'66o0"5+ ;#  % # *M'“ -& -hh1) #nÿêöÏ%+05733#3267#"&55'67#5'676367#367#335—: '(  $ %%   & + -Ï L; /))B 7 F*ÿîód#735#53533533#3#3#3#535#535#@FFFF>>JJRäOGG@<  dc   ÿègÉ 7'6'6'6N+ % * %$# *É #" %$ ! ÿê|Ñ#)73673#3##5#'67#53655#5#'&'6  ?  ¢7ZZ=4 &77(w ÿêŒÑ#)73673#3##5#'67#53475#5#'&'F  # N!  ¢7ZZ>4 &77(wÿé•Ï4767#535#5673#35335#535#53#3&''67&'# b22  +15 ) #4 \  ``f    ÿê”Ç #)7#5##53#735#35#3#53&''67&'X ffAAAA&5‚7  EÇ  'S11' (   ÿ陯'<B7#'6553#3#67''3#67&'3533##"''3255#&'= yggH  RR*  (;  ;   zD5 7=_+$_#  4$ !   ÿé•Ñ'=Q73#"''3265#'63367353#'67#'673#"''327653353#'67#53368S  N   +$  T  >%" Ñ I1    : I ,     ÿê—Æ #'+73#3#535#35#'6'&'#5##535#35#$*Š'"5< WpBBBBBÆEEEE  Sf f$5 ÿè—Í.2673533533##5##5#'67#5367#53#3#&'35#35#!!:$ % .5*k+;A!9EEEE·˜  OOr-ÿé–Ñ"&2>73#3#'6553&'5##5#35#3353357'53367'533]0$#f5"G  Ñ =;- 2;X =,]   T U ÿé˜Ï#'+0EK73533#3#&''275#535#35#33535#335&'#3533##"''3255#&'4772 /=1141P1N^   ^(    JJ *,& !      ÿé›É#';73#3#5##5##535#3#73#3#73#3#3#3#"''3267#7#v0;*->3 CH%%H$$Q}} ‘_X ] É #;;# )  %ÿé‘Ê 273#735#35#3#735#33533567#53&''67&'kkEEEE~~#W [u  % ÊE) ' '4E     ÿèšÏ!%)-17=CI73#3#3#535#535'65##5##5#35#35#35#'67&''&''4'-[ ‹ R     J  l   Ï 0**:000000<*****C   ÿêœÊ(,0A76767&''33#5'67&'767#3#735#7677'7&'d   >7 9= $``:: F7 ' È   1&!       Q7,  #ÿîïf!73#735#3267#"&553'#37#32jj>FOH±b<E}‰6%! H JuuuK!    @BÒ "$\2  —  $*  !BÿèõÓ $(47'#67'5'63&'73&'3535&'76–'( g  :0 +TTT .1 Ó" Y5  “ # #$&  hÿèõÓ *.27&''6&'767#67'53&'73535¦ " +M #&  P  ) ->>>Ó!#"+W   1 ’ #ÿéjÏ76767'67'67'6767N(%5  Ÿ5 :9 4 30 ÿðfÏ73'67#&''61*< 1  Ï…3)k   .|ÿíõÏ#73533#353#3267#"&55#5335#|/11,  ,/³ZDV/ 5UCZ|ÿññÉ73#3#535#535'2ä //+l.331ÉKPPIÿéÑ $(7&'3&'73#3#3##5'635#35#A" EEEHH &3333Ñ R ™ C. ÿåõ~ $(47&'#67'5'63'73&'353567&'„.: Š'  B 30),www"  0,~  7 V   !  ¥ÿèðÊ7#"''3255#'65535#35#ð     ÊÆ :8!"?z;)e)ƒðÏ733#3#3##'353#5#535#535#NNFFPPzMRRIIMÏ  DK  mÿéñÐ 57&'7'6'33#5##533#735#3533#&'#5'67#ƒ a ,7]7QQ++*68.  *Ð   %+,"58  !9;! ÿçõh"&27'#67'5'63&'73&'353567&'† 56 ˆ% D /6*5vvv1  11h 1K   "  ÿëfÐ73'67#'6367')0  !   Ð &'v  `ÿêôÐ#cgkos73533#3#3#5##535#535#35#3353#35#53#3#3#3#67&'#67'5'67#535#535#535#735#35#5#35#b>>>33Ar>33>""4#[-  +%%)4    $ !3'&& 6È !&&! 0          1 lÿèõÏBFJ735333##35#53353#5##535##5#3#5#'6753353#35#535#535#33535y0/ /"  "#   #00990A ; &‚'&4 @@ 4& ?"' ; oÿé÷Ñ +77&''63#3#735#73#735#&''67&''6«"  *6644,44;   G  Ñ!";;/   #  tÿèòÉ 5;73#735#3353353533#3#3#3##5#535#53&'#535#367#uxx#_---5-4466/5-+%É::   7  eÿèòÐ48<BHNT73533533##5#5#3#5'75#5373673267#"&5'3#735#&'''67&''4'k FP J  2** 6  X  O ¾`      H[;R  mÿçòÐ "&*.4:@7#5##53&'73#673#5'675#35#35#35#''6'67&'íV3/K -jEEEEEE  =¾'(   ^J D $ % g z    ]ëÌ $*06<7#3#3#3#"''267#55#5#5#'67&''&''&'àQJJJJ\  «Q>>>>>  ‰ Ì   L        jÿéëÐ73#'6553&'35#¶*W,CCÐX8/ 0?Df3rÿíëÇ7#5##535#"&55#'7335655#ëUU  D UÇÙ Ú¹- g+8$ }bgw-+fÿèöÑ#'7367'673#&'#'67#3'6673#h.%3 1ÇP  #+ %  00v   aÿìòÑ *06FL73#53635#'673&''67&'767&'3&''33267#"&5''6˜Bv OO        8 @     Ñ || uZ      O /  2!ÿéôÊ -73#735#3#735#73#735#3533#&'#5'67#OO++22'11I+/%  $Ê4177;  41 /óÑ $*06<733#5'667#35#33535#335&'7&'''67&'UQ4³ 2L G==P===P=)  J  ŸD ÑL; *       €ÿóôÂ73#33#537#'7#37#€pFAtD<1/Â"†,li4}ÿêíÏ73#'6553&'735#Å(M$"::¦[<"9V_7xÿéòÏ 73533#3#67&'7''67#535#‚())4= %* )2(¬##37 %+ 23 ÿçñƒ $*06<733#5'6367#35#33535#335'67&''&''&'YU*¨ ' UP99J8‚99J8 ¸  (  +  ƒ F9 " &      ÿêïÏ735#53533#3#&'#5'67#-''''0*   $q%''%6,RT('1uÿèôÎ$73533#3#&'#5'67#535#'6((.# '3 ++(*&")Y[,"%.(  RõÏ73533#&'#5'67#_`P&6 5+$9 8"Oµ%,76+&fÿôôÂ73#33#537#'7#37#jˆYP#ŽWI?=Â"†/if1WÿéñÅ"735#53#3##"''32655'67#'… 3Dx %%  2,"0¦>>^ X>+ 9ÿéï‘73#3#3##5#'6M•yddll)‘*~ $ ÿçö}*7327#"'&5#'655&''67&'76Í  {# t   }H (# M5)% $H  ÿðð‹ 73&'73#3673#53&'\[Í1 -  DàB n  %*(')& ÿéó7#53##"''3265'##535#²¤å, &I55lh OFW3  ÿèßR 73353353#5# AB«=9NN9UHóÏ73#3#535635#ÆRA 6Eå"^JVVÏ'^e' ÿêà‚ 7#5##535335#33535#35#à˜VVBBVB˜BBVBBg}}1N!ÿéÞ[ 7#5##535#33535#35#Þ•@@U@•@@U@@[rr)>!ÿèÞV #53#5'#335#355#5½TAATATATA nn P!ÿèÞc #53#5'#335#355#5½TAAT@UAT@ {{Z!ÿéÞz 7#5##535#33535#35#Þ•@@U@•@@U@@z‘‘6###Z$$$!ÿèÞˆ 7#5##535#33535#35#Þ•@@TA•@@TAAˆ  ?,,,j,,,xÿéðÐ73#"''3267#'6&'¤F<    Њ$v $0" % ÿé…Ð $*06<733#5'667#35#33535#335&'''67&''4'3+Y    #5# M G Ð cY *C/" sÿéòÐ>767&''67&'3#3533#"''3255##5##5'67#536‹    GM)    !Í   2  9  $UUAA{ÿêøÏ#'+737#"''3255'67#5353635#35#35#§0 + (G  777777Ï[  ?  $!s 2<:tÿéòÎ %)-157&'6'&'67&'6#5##535#33535#35#º      ]   J.J.Î   Pyy-HsÿéóÏ(,073533#3#3#535#535##"''3255##535#35#z.22,,8€5&&.l   AAAAAÀR\  $t 0vÿïóÍ+73533#3#535#'67&'3533#3#535#„())2w2(B @(((3{4(µ3  -   tÿêòÐ6>BF733673#5##53&'733#735#3267#"'&5536#5##535#35#© Z  JJ((9"& '#):::::Ð -. '    6JJ & nÿçóÎ17=73#35335#533#335#535#535#533#535673&'''6ˆ! „ 6(„ CCLMZ  ’‰  f   ÿérÀ 7#'65535#rE22ÀY B2 2?[G5 ÿðtÏ7'67#535#53533#3#&A $%% #$0& )7)$$) ÿéô€7#5##53'66733267#"&5Ó~A/;8&'  4 €aNM`!;5*2  ÿéôm 7#5##53'66733267#"&5ÌxA/<9&  , mTBAS30&-  “ÿëõÆ $73#3265#"&55#'67#735#35#35#›N ))))))Æ2  87+dCClÿèöÐ #'+/5;73533##5#3533533##5##5#3#735#35#35#'67&'w/44/ // ssMMMMMM ; À gI ) ) !     dÿìöÑ!%+1DJ73#3#53&'#53&'367#3#735#35#&'7&''3325265##"&5''6¯2"ƒ /0%nnIIII*<  I     Ñ   8 *H, )"      #  JÿìöÑ $*0DJ73#3#53&'#53'367#3#735#35#&'7&''33325267##"&5''6¥;+—'83,}}WWWW2  E  V    Ñ    7 )I, ( "     " ^ÿèóÐ#'B]73#3#&'#5'67#535#53635#35#''67'6776767'7''67'6776767'¢ =2"  $4?      n    Ð h 9:h2F   &! )   &  - ^ÿêöÐ.26:>BHNTd733#3#"&55'75373267'67#'65533#735#33535#335&'7&'''6733267#"&5›77D  $ &_,]]&9&  :  T  Ð     I=/ 07SD?& !       EÿêòÐ/37;?CIO_e733#3'67#'655332567#"&55'7533#735#33535#335&'7&''33267#"&5''6‹AAKj1@$)#hh+D+"  ?  I     Ð   I=. .8T  '?& !      #  TÿéöÒ#)/5;CGK7'6733#736735#33535#335'67&''4'7&'#5##535#35#`!<%‰$ 8D&&9)b&&9)j ~ ;54SSSSS˜JW * '       PP %  eòÑ#(,7&'#3#3#53&'#535#5'63&'67##35#‚14 1P 8×2P3GQ%!.$Ñ    G cÿéíÊ  $CGKOU7##535#35#7#"''3255#535#5#3533#3#&'#5'67#535#35#'735'6¡,x   ,H'''##    "'   ÊEœá' *Ì „E '   6  #$ 6 4  ^ÿéóË+/37GKOS7#5'75#53#'35#35#5##5'75#53#'35#35#5#7#5'75#53#'35#35#5#ñe†P>>>>>>-C(z-C)~ E?3 & & f G;/ % %   G;/ % %  ÿêôQ!&*/7'67&'#3#3#53&'#535#73&7##35#CI1 25 3J:Þ1L27F 0&'   9 ÿïUÎ"(.7&'3#3#7'75#535#5'6'6'&'/ /$ ')ÎA F flÿêìÏ!%73#"''3267#3#"''3267#5363#”>   Ce  d$]]Ï < #NM6r ›tÿéëÏ 7#5##535335#35#35#35#ëR23 33 3¡¸¸..I777888aÿêöÏ73533#&'#5'67#7&'q174$ )+]  š55A.)9~{4(1>?   iÿèîÏ$*73'67#'63#"''3257&'''6W H  "  6  GÏ   ,ƒ  _)*-',% &[ÿéòÐ=767&''67&'3#3533#"''3255##5##5'67#536u "%#W]1   'Ì      1 8  !TT@? gÿðõÏ-73#'3#7#53&''67&67#3533#3#535#Ž CE    S/00<‹6&+- ) 16./2' 0  o  Ð333  &" ) J   pÿêõÐ27=C733533#3#3#3#&''67#537#535#53&'#5337#7'6'&'˜ $400:2$' * ( -1++/$ +=T  Ð333   %  ) H    ÿópÇ"7#3#3#3#67'7&''67#535m@====C/   " Ç'  (sgÿéïÄ (7'66553'#33353353#353#5#5335#Š wSSK%N#ˆ'B6 .%^<+'"//"38'D ;(8kÿéõÏ06:>DJ7373#327&'##"''3255#5'67&'767#33&'35#35#'67&'w#8  $ #  0 3 !5555  S ·  60  -4   G-)    ÿíóC73#3#3#535335#Î]MMiæ*+]C##3 ÿéóÏ&*.736533#&''67#3##"''3255#3#735#YZ]4+,0> I Sæ&  ¬"dd>>·    8W  T9vÿéóÏ&*.73533#&''67#3##"''3255#3#735#€+.0  ( }   Z ==¸     ;X  U= ÿé€Î'+/73673#&''67#3##"''3255#3#735#,*.    &s   S 77¶     9W  T8‚ÿéõÎ#'73533533#3#535#35##5##535#35#‡s%>77777°!!!!Crr)? oóÐ733533#3#53=(SSfæ0½;NÿêòN(2R733#3#&''67#&''67#5353353#5#53'65#'#&''67#35##&xJJhE 1  D*-W¯p  ! !Ÿ# N     !:8    }ÿñîÇ 73#53535#35#35#â q 000000ÇÃÃ@-o.o. ÿì€Î 73533#3#535#'6##535##%%+p2 \L99Ç'''' YJZ8% ÿé’Ï#AEIM733533##5##5#533#735#73#735#3&'73#3#3#3##5'6353535177/66<.-))))&X Ï  #.."     I    £ÿéñÎ73533#"''32765#'65#­$   ¢,,_Ejy. *q[ÿèóÐ*>Qc73533#&''6655#67''67&'767&''67&'67&''67&''67''67&'`?== 7 .7#!?      [           T    ¸,8/8 7?,      .          nÿèñÏ.4:@7#5##53533#35#&'#67&''6654'&'7&'&'ï[2??1[[(      "   H      ¸¸/—…  "  )    "  `ÿéóÏ"&*/DJ73533#3#''275#535#35#33535#335&'#3533##"''3255#&'c;==6 2A33; 2#U 2#Pc   c)    JJ *,& !     †ÿêïÏ73353353#5#353#5#5335#‡W*»?SS?X eLi _Me`ÿêöÐ (A7&''67&''6''6#5'6733#33#"&''673«  /  Q    K     Ð   '1  '/ )]"1 "H ¨áÏ 73353353#FEÃÌ$EÿéõÐ!+7&'367&'#"''3255#3'67#&" $$M   8 =%& 0(Ð 1;M ˆD!3ÿèîg73##"''3255###535#ß%   ¦„R@@gU  R9 E(SÿñóÇ#'73#3#3#535#535#35##335##35#35#Z’,''3 4''-@M&Ç*]++]***<9999v+b¹ 7#5##535#b"""¹ž§{hk¹ 7#5##535#k...¹ž§{h\¹ 7#5##535#\"""¹ž§{hU¹ 7##535#U-¹Ž§|jS¹ 7#5##535#S¹ž§{hYÿéóÐ"',7333#&''67#535367#'65##67#Š5 )9+ 0/) 59 ) QÐ C%4+ C j11OÿïôÏ #'+/73673#7&''67&'3#53535#35#35#^M  *$  B4¥%$B = /HHH77777M¸ 7#5##535#M¸¦{jI¸ 7#5##535#I¸¦{jZÿêóÐ3;?C733673#5##53&'733#735#3267#"&5536#5##535#35#œs&``<· 7#5##535#>  ·œ¥{kd¹ 7##535#d9%%¹Ž§{hS¹ 7##535#S,¹Ž§|jOÿéîÄ '7'6553'#33353353#353#5#5335#wŠddY+[*‡&D4 4?]=+'"//"47'D :(7M¹ 7##535#M'¹§|jX¹ 7#5##535#X¹ž§{hZÿéòÆ73#3##5#535#'6'&'e‡>  +  %  j %J8! +8 )5>! #&&Ð    3    $     #UÿéôÑ)-173&'73#3#53&'#67#3#3##5#535#735#35#^;;&ž$]?(|3EEFF6VVVV¼   R01DÿèñÓ5=AEIMQU73#33##3#"''3255##5##535#535#'66553&'5#35#5353535#33535#335©<;/ /4    "0..7 D77.AM1"S1"Ó   T   i  C3 0#\* '   C ) @ÿçöÑDHLR7#35#535333##67&'#"''32655'675#535#'66553&'73535&'òˆ7**1,   "    %**7 I[ ¾2 #$   %,    A4 0#[ 2#+  XÿéóÉ &*0673#3#53&'#735#35#3##"''3255#735#'67&'kz9A˜B+TTTTy2   4VVf  ÉE* % E3( %*  TÿêøÈ"&*.26:7#3#325267##"&55#535#55#7355##373535335è/331 ..,PS7 r7 È9h  g9Ì»°º2*RÿéóÌ $(6<73#535#535#3#3##"''3255#535#735#'3#67'75#&'iyyf^^fAC   qq`E  ' ÌK  @:  K  XÿéòÊ#D73#3#5##5##535#3#73#3#73#3#3#"''3255##5##5##537#b†:B/0C9%%D$$L--L..[™GG  5>Ê (;;( * :  $8888=N IÿéòË 26:>BFJ73#735#35#35#73#3#3#3#3##5'673&'7735#35#35#5#5#35#YAA:E< 893333<|  $  -----Ë`E ( ( D` F  A () I ÿì˜Ï/H733#3'67#732767#"&55'75#'65533753353#7'75#533F99?1%&    0  .9# Ï     F9- +6PVKH*%4  1"‹ÿêòº7#3267#"&5535éJ   !6ºoD ¹IIÿðç» 7#5##535#ç@@@»Ê˦“\ÿééÌ7#"''3255#'665535#35#é  H FFGFÌÆ40' 2%a=*f* Uÿé÷Ï7&&55#5'66556Û  !! =Ï#k1I@"À¾7J@ $6#IWÿèóÇ#(733#"&55#'667#53&''67&67#Í ,   u"# )) &*AÇ<  0$  *v2   Jÿèò 73#3##"''3255#&'''6hss”B   >tKÂ&u  r%* )& 5"^ÿééÐ 73#5##53635#35#35#”Jd, ddddddÐ ÈÈE#Y$[%]ÿòðÇ 73#53#3'35q“{ss_2-Õ.TA..EÿéðÇ73#7#5'75#35#35#675#OŸ6>!*GGGG%"GÇwA< R!XKÿèòÏ*07767327#"''67&''7&''7&537&'ÜDOM  !, 0 ==./  £  " &   65   LÿêðÑ&*.267&'3533#3#"''3255##5##535#35#33535#335È  kHJJ@   -(;H ((:-g((:-Ñ !!‡  336Ÿ@FMÿñòÇ73#735#35#53#3#3#535#`‚‚YY9>“A<=x  1‘(;JÿéòÐ"',7333#&''67#535367#'65##67#< (A0 4 71 ?@ / V#Ð  E# 0("E j22FÿïñÎ+73533#3#535#'67&'3533#3#535#Z8>>K§H8!O W;;;J«M;¶1 /   JÿêïÆ #'+73#3#535#35#'6'&'#5##535#35#S–,2¥2)<N  o  ˆUUUUUÆEEEE  Qff$6;ÿèïÇ #)7#5##53#735#35#3#53&''67&'æy‚‚\\\\0I«K 0 'O"!Ç  'S11( )   7ÿêòÑ-DJ7&''33#673265#"''67&'#'65533#67&''655#'6Ô  ))    N`HC   &  Ñ   /5%+$( 'HH<- .7S"%   &7   NÿèñÏ-39?7#5##53533#35#&''67&''67&'&'7&'&'ìx>RR>xx2        ]    ¸¸/—…  #   '  ÿêìÏ#737#5373#3#3&'767#'67#L@EZ^x} j -A #`Gƒ    ÿîòÌ73#3#535635#ÐWI§3<å[E``Ì -j»¿jÿêôÏ"(.733##"''3255#'67#53673#3&'''6TT   Y;E }†B: a* (‹.F  B$$ !# #ÿèðÃ$735#53#3#3#67&'7''67#535#)MXÅXNNe|8=  IR MgMŽ""". (  ,"ÿèðÌ'73#3#67&'7''67#535#535#'2Ö '/OOf|9>  IR MgMM&*[Ì"#. (  ,#! ÿéóÏ48<73#3533#3#&'#5'67#535#535335#535'25#'35Ï $,]]',,%Q<8#": 4$N"(()[[#'U6'=)ÏCC"}ÿé–ÏDJ_7'23&'73'6733#"''3255#67'7&''67##53'767#7&'3&''67'767# 4K>8 L  * )   7 Y;E   2Ï +     m Z   s‚  +  €     ÿê™Ï!18P7#5'67#53533#&7'6'&'3'67&''667#73533#3##5#'735#` -86*  J  !    /  ‡#!  ++ R   [ -0     '!!  ' ÿë”Ï)-273533#3#535##5##53#3#7'7'#735#767#877/q.8‚\KKj9G%DD$ *%) 2  9 ÿéðÀ73#3##"''32655#535#!½Qcc iiXÀAV QAÿéð¾73#3#67&'7''67#%µµás6A JJ *V¾2D (1 )8ÿéîÒ *73&'73#3#735##5##53##"''3255#f^Ý!œœtt¨³ @  I¼   1-1 / -  * ÿëñÇ 7#'655ñ¿ Çc< 4?] ÿçöÐ  7'67&'&'3&'767#€-= N(0: 64  F«! '; ް.(2 ,0) NÿêðÆ%733'67##"''32655#53&'767#^‚5  +  JO  hÆ  ^ Y  HÿèõÏ  7&''6&'3&'767#˜&( '&)4  ){#) bÏ /0/(   ( QÿéòÏ 73533#3#67&'7''67#535#\:::GT') 4:6E:¤+++5 $+  2+OÿéòÏ#7&'73#3#3##5#535#535#536x  Y .C==GGGG<B73673#'#3265#"&55##5'67#3533&'#35#33535#335N3S0'5  $$ #*$!  $$7!X$$7!²  N   X(9>LÿéòÐ !%73&'73#3#536'&'#5##535#W;:Œj .¦a A  {[[[¸  R[ [>+eÿêòÉ#6:>BFJ`l73#73##5##53#5##5&'7&'#3#'6553&'75##5#35#3353353673265#"&5'7'533q;;A::y! K )&!h  6$    !É 2$$21##1    #)"" #+1         6 QÿêóÌ#59=CGK`m73#73##5##53#5##5&'7&'#3#'65534'75##5#35#7353353673265#"&5'67'533_BBJDD ) V ,+(y <((    ( $Ì 2$$21##1    ! )' $,2         6 ‡ÿéöÐ&*73&''67&''667##5##535#¤;    ,A111Ð        ]\\<+5ÿï‹Å73#3#67'75375#735#CB #, ÅO 2 XSi-VÿéõÏ 0EKQ7'67&'67'7''63#"''3255'675#'3#"''3255'675#&''&'‡# O & -   #) F 3LE  2b  =  Ï #&/ +e   %e   % GÿéôÏ 1FLR7'67&'67&'7''63#"''3255'675#'3#"''3255'675#&''&'~% $Q , 1  &.#K "8SK "8i  C  Ï &&1+e   %e   % [ÿìôÎ (,0473533#3#67&'7''67#535#3#53535#35#35#n022;M   19&<0y ™!#¸  eCCC22222lÿéôÐ%73#3#3&'767#'67#537#536 ?BOST "( K!%#&Ð    ÿñf¿73#3#5##5'67#35# Y% /  !'¿ s _+6’DÿñW¿73#3#5##5'67#35# J ' #¿ s a +6’Cÿöêº 7#5##535#ê555ºÃÄ Ž eÿéñÏ '+/37733#5##537'6'&'3#735##5##535#33535#35#¡=h<6 T YY55ZZ$$7#Z$$7##Ï#-.!  420d d&9 pÿéñÏ '+/37733#5##537'6'&'3#735##5##535#33535#35#§7^71 K  SS//SQ 1 Q 1 Ï#-.!  420d d&9 `ÿìôÐ "(048<73#'63#53#3#'#335357&'3#53535#35#35#Å"("LI G  % ” ""Ð K‚4G# 6===***** kÿíóÐ "(048<73#'63#53#3#'#335357&'3#53535#35#35#É$ FDB  # ˆ  Ð J‚3H#  8<<<+++++^ÿèñÏ73#'655353335#³8a(>fLL¤N-& (2;FW*ÿèuÏ73#'655353335#J(F+H33¤N-& (2;FW*YÿéóÏ.287#"''32655'675#537#'733#67&'37&'¯  !%CVFL*?  #= ? X  +( .8 6   ¤R ÿëî[ 73#3##"''3255#&'''6'°°Üa  h! R . ([&  "  ÿéóJ 73#3##"''3255#'67&'(°°æg   l>" {  J        \ÿèíÏ473#"''32765#'63#3#353#5335#535#'6~c   Y 9%((^,,  Ï –"  2$43#2  [ÿéöÒ!%+37;7'67&'#7&'3#735#&'735'6#5##535#35#„ 0!& ?9 '‚‚&&   1%  "OOOOOŸ    A#  ##  2S S+ ÿècÏ73&'73&'#5'67#   $5°   fa ? ^ÿïóÐ #'+/>73533533#735#33533535#3353353#3#735#3673#53'e((‡'c'{”” ||YY$)•!ÁQ0/+ / #  UÿéôÑ RVZ73#53&'3#735#73#735#3533533#3#3#67&'67'5'67#535#535#5#35#£B”=.;;:<<[ %$$(B   4  0& X%%%Ñ#+ ++    +    ?ÿéõÎ $(,EIMQ7&'#5'63&'3#735#73#735#'3#735##"''3255##5##5##535#335335—() R =9 =,,_,,E--W   , Î    &0  0  0 0H !!!!)]$ RÿéöÏ $(,EIMQ7&'#5'63&'3#735#73#735#'3#735##"''3255##5##5##535#335335 $& K 692)) V)) @)) O  )Ï    "0  0  0 0H !!!!)]$ ]ÿéñÍ $(,EIMQ7&'#5'63&'3#735#73#735#'3#735##"''3255##5##5##535#335335¡# F /1 ;(( Q(( >(( K  &Í   *0  0  0 0H !!!!)]$ eÿéöÏ $(,EIMQ7&'#5'63&'3#735#73#735#73#735##"''3255##5##5##535#335335¨# > /- 0'' '' ''   $Ï    #0  0  0 1H !!!!)\"lÿêñÇ733#"&55#'665#5##535#Õ +  ^AAAÇ;  0#  )lqqO=uÿèïÐ"73#3#"''3255##5##535#53&'¯ 341  132Ð \  E‚‚ew  ŽÿêïÏ73533#"''32765#'655#¡( )'£,,d> hc> =Xÿçç–073'67#&''67363'67#&''6g@1] O.6  $G:@y k;4 T–6 ( .A3   ÿçZÐ73&''67&'67#53667#,"    ÐP,  "$~8# 3ÿïï€$73#67'7&''67#3533#3#535#@£]4)  8D/CJJV¼SC€ D ÿòò˜"(73#5'67&&'3673#7&'€T_ P25 39 Lx:Ì"  } .$8J  ÿèó8 7373#&''67&'67#367 D4 %! 5+E 2! :O3 0     iÿéóÏ"&*/DJ73533#3#''275#535#35#33535#335&'#3533##"''3255#&'k8882 /=1181 Q1 L\  \#    JJ *,& !     _ÿê÷ÏDHUbh73&533#673265#"''67&'#3#3#3#67'75#535#535#535#75#'&''67&''67&'_c     ")+    -   E‡!'( +! )  '+ jhW    pÿéóÐ*048@D7#&'#'67#3&'73#'655'673'3673#3##5##535#ó  &(a  <;13PPPPQ000     "  85 +61!   ^  7 7ÿçòÐ!(.7373#3&''6654&''67#677&'‡>@:    £-7#    (;4!r  {ÿéôÌ 7&'''63#"''3267#'67#Î" G   Ì?F?( $'_!HN"Cÿûì¼ 73#3#3# ÀÀ ªªØØ¼CERÿûó¼ 73#3#3#[¡¡¼CEÿêïÆ73##53#3#"''32765#2  <ß }…Æ<*F'CÿèôÐ07''6767&'3#3#&''67#5367#'6¿ 99"$* 9a<LB3 5 C : =B  Ä!    7$ -$ LÿèöÎ#)/7&'36732767#"&55'67''67&'—!/     Î -V-Xj6%" V5% +!#)*"dëÐ73673#''67&'67#367> €) ?'> 1  1J º      |ÿêïÈ7#"''3255##53#3#735#ï MAA77ÈÆ  ¯ÌÞ,W5ÿêÑ#+/373673#53&'35#&'735'6#5##535#35#5~##1#  #KKKKKÑ  WW \555 :dd#4 ÿè—Ï19=73673#3#3#3673#53'735#535#535#53&'#5##535#8#7//55 ‹ 22..6^BBBÏ      Ÿ? @" ÿèšÐ "(0487&''63#3#735#&'735'6#5##535#35#L## +EE~~##0# #KKKKKÐ  A#  ##  .T T,wÿëöÎ=73533#3673##33##"''3255#53567#'67#5367#535#   .-//  ;; & "9)º    sÿéòÄ373##"''3255#7&'#53#"''3255#7&'#5s;   x   Ä  ‡**&"|²  ‡**&"|²‡ÿëôÆ !.73#735#35#3267#"&5536'33#67'‡cc====M    S Æ_9;= +  _/  sÿé÷Í #06<B73#5'675#73#5'675#&'7&'&''6'6'67'6w8 &?8 &+Q # $ ' " "( + ./ #C EÍU  U      5       +tÿéóÉ +/373#3#535#5#35#3353353#3##5#535#735#35#yv##v!!A  ]k,5577,DDDDÉ22#/F  * & nÿéòÊ#D73#3#5##5##535#3#73#3#73#3#3#"''3255##5##5##537#wu39&%8/8A%%A&&N‚;>  ,3Ê (;;( * :  $8888=N lÿèóÏ&*04:@FLR73#3#3#3#535#535#535#535#'235#&'35#'6&''&'''67&'å 771100781144557&!!/    ; 0Ï >    >  f     `  ÿéôs73533#&'#5'67#bcS$7 ;%$9 8"R]*2YX.' ÿò€Ä!%7'75#535#53#3#6'35#33535#335}3>/**+h*))E+B+ tt P wÿéôÍ73'67#&''6§D-O4  !Í R;8e   .{ÿéóÅ#7#3#327#"&'#67'535'#ê'-+ % I&!ÅI#$)-.B Ò%%\%%kÿèòÏ)73#3533#"''3255##5##5'67#536¡FK .    !&Ï##U  @uu\O )(sÿìî¾73#3#5##5'67#35#{s6 H1 !()11¾#"y ` ,<£LzÿêòÇ733#"&55#'667#5##535#Ú & U999Ç;  0$  +lqqO=pÿéñÍ 73#535333&'#«F&@T(—#Y#=@rÿéóÏ27#53&'73#67&'7&'3267#"&55'67#'6 &3-:    %  Ÿ  ) $ S" UOAoÿêõÏ $(,737#"''3255'67#5353635#35#35# / "3 .!J  999999Ï]  @  %# s 2<:ÿéðÄ73#735##5##535#†cc;;VIIIÄT.\klI6yÿéøÑ!%73#67&'#67'53&'3535¨ -/8 ) EEEÑg  DK  Á **tÿçðÆ 73#735#35#35#'67&'~kkCCCCCCBÆ£rOL4 ÿçôÐ F7'2&'7'6'&'67'7'3#&''67#5367'67'6767å *;13  DL! 1+   (-. Ð       $    |ÿéôÐ59=AEI73673#33##&'#5##5'67#535#535#535#53&'5#3533535335œ  !&      ,  ,  Ï   %$6JJ)  <%dÿèôÐ+/37;?EK7#35#53533533#3#3#535#'6553&'75#35#33535#335'67&'òp/)c(1  8(?(7  8¿1  MM C1 5<[ C<-"   nÿêôÊ%7673#3267#"&'#67'7&'nB/,+ .! @» %1,!3@Z  {#;bÿéõÑ 7;?CG7&'67&'67&'63#3#&'#5'67#535#735#33535#335‚   6  3  ^{6>0  .922"T2"Ñ      -O   3/ /1dÿéðÐ K73&'73#&''67&'763353#3#"''3255#67'7''67##537#d;;Œ[    KU4A  4 51½      22B J  5   QbfÿéòÐ19=73673#3#3#3673#53&'735#535#535#53&'#5##535#   611::  Œ 99337iOOOÐ     œC C(ZÿéõË 4873#735#3353353#3#67&'#67'5'67#735#p{{"l„„v. 1    PPË:6 3    -     ÿøpÐ 73#55375#535#'673#3#7XB''   6 &&PV XET/ $ /Q cÿéòÏ#CGKO733533##5##5#533#735#73#735#3&'73#3#3#3##5'65#5#35#!!!!99399A,0,,,,1e 4"""""Ï  #.."   L   kÿéõÏ&.273533#3#535#3#735#3673#53&'#5##535#o7880t17 nnJJ  !ŠeKKKà   *1$  (= =!gÿçòÏ#'+/5;73533533#3#3#535#535#35#35#33535#335'67&'s!;0r/9 ,0N0@ !D ¹]]_;+    iÿîõÐ#'+17GMS73533#3#&''275#535#35#33535#335&'#6&''33265#"&57&'''6r333- /7..3-G-     U  [  ÀI I- ) '   (   ( aÿéöÏ#'+17=C7'673&'73#3#3#3##75#5#5#&'''67&''&'{  ))$$$$+`6#####S  \  V t).   e%% nÿéóÉ +/373#3#535#5#35#3353353#3##5#535#735#35#r}#"z"$H!!ao/9999-HHHHÉ22#1D* ( kÿéóÌ !6<73#535#535#3#735#'3#67'75#3533##"''3255#&'upp]VV]===U; !]   ]!  ÌK  @95     cÿèòÎ(.4<U7367&''667367&''66''6''6#5##53733#"''32765#'67#Š   =    > |[ !7   % 2 * Î     !'    ! A((/<:,hÿèóÐ`f73673#3#3#535#535#53&'3&'33#67327#"''67&'#7#"''3255'75#535'67&'•3--6€7**1"(&       NÐ       _    %      gÿéòÏ<@DHL733533##5##5#533#3#"''3255#7&'7''275##535#735#33535#335‰ ## ""w3=  *  $&811 Q1 Ï #N D  /  J[ .,iÿéïÏT73533533#3#5##535#35#3#'67#53#67&'##"''32654''67''67&k#Y/##JJ  &A      !& ¾,,-)           dÿéòÃ!&+/733##3#5##5'67#5367#5347#3353535#uoKYA ,/!3*,1?AAÃ+, X D    , €*`ÿìõÐ?EK73673#3#&'#3#"''3267#3267#"&55'67#5367#7&'7'6o,:>O  - N   *') $&  \ ¦  *8H  7   eÿìùÏ)37;?C73673#&'#3267#"&55##5'67#3533&'#35#33535#335q&B% *     +C+²  M   W ):@ ÿçóT%73&'73#67&'67'5'67#[h`   #P   9 SF  :   #  Iÿí’Æ73#3#"''3267#735#OC-0  2 ,/ÆK(E!,M%fÿéôÏ%736533#'67#7&'33267#"&5s(AB 2(X     – 9>E)`I Qn !& uÿçöÌ %73533#'67#7&'33267#"&5~!??,' R    •77o,(dJ Qk "& ÿèð—473733#3##'2655##537#3673#3##5#535#53'dil]–M`U04433/~^ Gcu#    ÿéñ—%*0473533673#3#5##5'75327#535#67#35#35#%F< 0Q\'(b\Ft 59x€    Y A @,[ÿêòÉ#6:>BFJ`m73#73##5##53#5##5&'7&'#3#'6553&'75##5#35#3353353673265#"&5'67'533h>>F==$ Q ,)$o 7'!    & É 2$$21##1    #)% $*1        6 _ÿéôÎ!8L7#53533#&'#5'67'6'&'35#53533#3##5#''3'67#&''6“(8:- W  NB""&' /  “** )) H  ƒ((""J .0 8  KÿéôÏ!8O7&'#53533#&'#5'67'635#53533#3##5#''3'67#&''6y  !.@C0"" ^ &&+,! " 7  Ï   2** ,+ L  „((""J-18 EÿéôÏ!8N7&'#53533#&'#5'67'635#53533#3##5#''3'67#&''6t  0CE3#$% d ((--!!$ 8  Ï   3** +, J  ƒ''!!I +38  ÿé~Î.26:>73673#&'3#5##535'67#53&'735'235#33535#35#u ,  % '=( #  '0&=&Î!    (g g,  ž=IÿéðÇ*7#53&''67&67#3533#3##5#535#c‰&- ) %(K,6<?3ÿóï 73533#&'73#536!V[Å-  y @܆g  !pÿñóÐ73533#3#536'&'z01uV *ƒF5¨(( H9B?3=>2]ÿéóÏ(,073533#3#3#535#535##"''3255##535#335e9;;44B–A//9}  O 3»M\  1t2""" ÿêŽÂ 73#3##5#'673# __ {52 MÂ&ŒŒ;# 2Uÿêl 73#3##5#'673#JJ [%#:Â&ŒŒ9% !2UlÿéóÏ(,073533#3#3#535#535###53#"''325'#;5#t06600<‡8((0]Dj  +»2t\  G""qÿéóÏ(,073533#3#3#535#535###53#"''325'#;5#v044..:‚5((0\Ci   +»2t\  G""IÿéóÍ473533#3#3#67&'#67'5'67#535#535#X9AA::H=  7  #4D449¶   R7  4 BÿéöÏ&+/@DJ73'67#&'''673&'67#3#3##"''3255#'''6h&&' 3   X6  <# DNN%—?  Cu,*M Ï D"3    !!P, 62  / #$  IÿçðÎ#@73533533#3#535#35#35#335335#5#3'67#&''67##5M('--#‘ (<''!*CF%QJ< %!À6643'3%  )ÿè¥Ï37;?C73533#3#33##5##"''32655#535#535#535#535#35#3353535?@@557 $   99AA9955?""5""$$$  0!*  0 -@!KÿèòÏ37;?C73533#3#33##5##"''32655#535#535#535#535#35#3353535V@DD99;(   >>KK==88@&&9&&(((  2 !*   2 ,?! ÿé¥Ï#'+0EK73533#3#&''275#535#35#33535#335'#63533##"''3255#&'9>>7 4D 559""5$Y""5$eh   h*    LL )* & "      ÿê¢Ç(9?EIM767#533'67##"''3255#'67#53'#"''3255##5&'''63#735#> ]8   ( .4p   fT   CC""³    Df Ohz   -ÿêñÑ"(,7#3#"''32767#53673#"''326'&'3#Ô4Q   N3     ??¥XK "z  E#  s ÿë‰Ï"&*06<B7'673&'73#3#3#3##75#5#5#&''&'''674'!  !!R1I  / ,…"& d%%  ÿì”Æ#7#3#3#"''3267#'667#'655”d^1/  ÆX!BE# 5C"B4 4>]ÿèÎc736533#"''32765#'67#-:R  B:03H  H%3& ÿêzÆ)733'67##"''3255'67#53&'767#a$    )/GÆ  i  F#$,   ÿèˆÏ#'-373533#3#3#535#535#3#735#35#35#'67&'.11++5z2)).iiCCCCCCA      ?aE & %      ÿòÏ 73533#3#67&'7''67#535#/..7B " ,1-9/¬##30 #)  ,3ÿó‚À73#67&'7&''735#35#aN  ' ::::ÀrB   “MCöÐ 7&''6&'3'67#}/: 73.: M(  ;“ vÐ',.&  " +ÿéÚA 7#5##535#Ú‡‡‡AX X9&(ÿé×T 7#5##535#ׇ‡‡TkkI6 TôÇ73#33#537#537#5#Ñs_)ç8 ,2H¡Q Ç5"M"" ÿéôÁ73##"''32655###535# ç&  ­|L88Á« ¦'XfE2WôÎ733#3#&'#5'67#535#53wccVC#3 9%%6/$AWffÎ %$ KÿåôÏ%+735#535#53533#3#3##5##53'67&'OF33;;DD77K¥V6T K yE35GJKÿæñÏ&,735#535#53533#3#3##5##53'66&'\=22::>>66E•‚P0V )$"}  K9;MO! ŸÿèðÏ#737#53673#3#3&'767#'7#Ÿ!&)(   "y' $ÿé×73#735##5##535#5––pp9:K K. JÿçôÐ)6:>DJPV73533533##5#5#3673267#"&5'3#5'75#5373#735#&'''67&''4'R",&&Q"`,<  Z 22G  l \   ¾   J_ ZAA+»-@S>>,$$7&%#c_#z i!W  "6À6621(, )BôÏ(733#7'7537736732667#"&5G'''3> G")  (Ï%6 ][q0  ( !  ÿñò573#3#(­­åå5_ÿñòÏ %7#5##53&'7&'''63#3#535#ðg= # z3@“?3³.1 % /22ÿêîœ &*.73#3##"''3255#53&'#53&'367##535#Y";  ­<$X :S:mEEœ  F  B : 077&`ÿèõÉ'7#3#3#3#67&'#67'5#535å`YYYYn@   7! É   IM  ZlMÿéóÐ 2BSW[73#53&'3#3#3#3#3265#"&5535#5333673#53&'#"''3255##535#35#vLR46443344 556Œ PD  #####Ð   2     wE   0i  *‚$8VÿêîÑ#73#"''3267#'6##535#35#€d  Y K7$$$$Ñ “$~ &"m~.J ÿéYÏ 73&'#''6.   Ï& «µ* UÿéñÐ "@73#5##53&'3'67#''67#3265#"&553#"''325¤:c:"$< 0    h : Ð $% >k(S  !e zE  _ÿéòÑ !%)73&'73#3673#53&'#5##535#35#d:9‰# " #“$nLLLLLº   An n(>cÿ÷óÊ73533533533##5#3#5#5#c@h|hŒ44>>>>JJo‚888aÿèõÏ%73533#3#&''67#53655#'61174$ &* + 04 Ä,, 572 $, oÿèöÏ"73533#3#&''67#535#'6Œ++30 &/25 Ä *** 65/ %03^ÿêôÏ7'2333#"''67#53&'Þ 2H; ++2 =NX/Ï3)  /@HÿèöÏ "(7373#'67#3&''657'6''6\!_a / ) P  "2 A/  C ³_81U7 .,'8! " \ÿéòÆ73#7#5'75#35#35#75#`/6)@@@@@@Æ{=9 Ž!!T!X QÿéîÑ)?S73#"''32765#'63353#'67#5336'673#"''327653353#'67#5336zh  c '.   e  F* '  ÑJ ,  %  % Z I * %  & V„Ð,73533#3#3#3#"''3265#'67#535#535#/,,%%)DB 7 4))/Ä        ÿéój 73&'73#3#3##5##535#amå´´³³µ’’’X  ,, XÿçóÏ048<@DHO735333##3#3#&''67&'7#537#535#5#5;5#33535#33535#33567#g5559FV   2 $&1+95 ""5"W""5"[&&9&* ,Ä  .   .  ' D Q GÿçõÏ048<@DHO735333##3#3#&''67&'67#537#535#5#5;5#33535#33535#33567#Z;::>Pc'  $5 % (3-@;));(c));(h..@+12Ä  /     /  ' C PPÿéöÏ).2CIO73'67#&'&''673&'67#3#3##"''3255#&'''6r### /   S0  7 @HH!Š:   =lH ÏD"3   !!P* 62  /   XÿéðÇ$)733#"&55#'667#53&''67&67#Ñ ,   t "' &% : Ç<  0#  )v'     'ÿèØN 7#5##535#33535#35#Ø‹==P;‹==P;;Nff';UÿíïÏ573673#3#3#3#3#535#'67#5367#5367#53&'‘ $?DJPV73533533##5#5#3673267#"&5'3#5'75#5373#735#&'''67&''4'[(##OZ(7    U //?  _U  ¾    J_ Z==4T4>TTž111pp1}9ZÿèõÏ%73533#3#&''67#53655#'6{44<9& )- . 38 Ä,, 661!#- eÿçõÐ*07#673265#"''67&'#67'53'37&'ñ<  $ ( :  ¦/$ -%+ Gj  Œ*(   Jÿë÷Ï#)/7&'36732767#"&55'67''67&'Ž"0    ~  Ï .W/Wj6$  X6% + ")*!VÿéðÇ*7#53&''67&67#3533#3##5#535#m ~#)% #& D)166AA??1µ!  N))dïÏ#7&'332667#"&57&'''6w     !  ˆÏ @  A  &Hÿè÷Ð 373'73#3#735#3#735#3#&'#5'67#535'6OJE¤ ŠŠddGG''M ;G6& (' %8I4@À  T6 $ 7 0.  RÿéõÏ#U73533533##5##5#3#735#335335#67327#"''67&'#&''6553'33'7\#)"")#‰‰'=     - <  À 9>    #$#  bÿéôÏ#7373#3#3&'767#'67#537#k/?CPTP &. H*.+²    ZÿèöÈ7'6553#&'7#3‡p#& -"IIdP,&TUdJ"YQ>`ÿóóÐ 73&'73#3#536'&'j67‚b0“M9 ¤ G4=?5591PÿçòÐ%+73673#3&''665&''67#677&'kUWK""  ( "  £+"7"    1M4 !q   rÿéóÇ73#3#3#3##5#535#535#535#wx3//007788....3Ç>>aÿéïÇ73#3#3#3##5#535#535#535#f„83344====33338Ç>>QÿéçÃ%)7#"''3255##5#'65535#33535#735ç   $&$$7$\%$7$ý +CC+$ 3>]=***f* **XÿóõÍ %7&''63#3673#7&'&' "' %#$ 2^^Q +’A    Í $%$#+d-+/"g !ÿéóÎ733#3&''67&'#5367ª//-!  !Î"7'0$!oÿèíÐ73533#"''32765#'667#}!<  ( £-d? hCHE=mÿêóÑ -73&'73#3#735#33##"''3255#53567#r47 llFFr88  ;;S½ 43   `ÿçõÏ.73#'6553&'#3&'36533#&''67#®/e4:RR@"-& &!- * Ï ?@1 1;X #  )'&*hÿèíÏ373#"''32765#'63#3#353#5335#535#'6†[ O 5!$$W'' Ï ›6 |  2$43#2  mÿéóÊ /473#735#35#3#735#33533533&''67&'#367{kkEEEE~~#gu   %  !  ÊE) ' '41    fÿèñÆ!'-39?73#3#3#535#535#735#&'735'6&'''67&''&'ut1008‚7//0/   [  PÆcA   AA ƒ iÿéìÈ#'+/3767#533#"''3255##5##53&'35#33535#335Œ Ys#  %$=$$8%]$$8%±   //8¥ BJ`ÿéôÏ8=737#535#5353373#33##"''3255#53567#'67#767#m#B7,,$$38::   BB2 H i  ( %  5 cÿéõÄ 73#3#33#"&''675#735#xj)// !(   .DDÄS&4+;j/]ÿèñÆ $73#"''32765#'6553'#33#735#‚o  [xRRJ==‡] EB2 4?^?-M> ÿèóa73#3#33#"''675#735#+ªJPP#.(o )Lƒƒa3    * ÿèó^73#3#33#"''675#735#+ªJPP#.(q ) Lƒƒ^2    )bÿìõÐ>DJ73673#3#&'#3#"''3267#3265#"&55'67#5367#7&'7'6q+8<L  * H   )'( %'  [ ¦  *8A  7   jÿïñÍ+73533#3#535#'67&'3533#3#535#|*--7‚7*E F-,,8‡;-µ3    ,   _ÿéóÎ -7'2'6'&''&'#5'67#53533#&á 4K=5 +   H ,59:7$ Î #   jGD"'( ÿèñS73#3#33#"&''675#735#-£GQQ82<. "H~~S.     'WÿêôÑ"&<H73#3#'6553&'5##5#35#3353353673267#"&5'7'533°6)&p;%     ! Ñ =;, 2;X =,3     !!   V]ÿèôÎ0J73533#3#535#3533#3#535#'3533#7'75#3#3267#"&55#'67#p000:ƒ60?II"Ž+   . %$¾5 2! $2 $XÿèóÐ#'@CZ_73#3#&'#5'67#535#53635#35#''67'6767677'7''67'677767'¢?3! !# %2>     n     Ð h !::h0F   &    &"\ÿéñÑ PTX73#53'3#735#73#735#3533533#3#3#67&'7'5'67#535#535#5#35#§=‹9+884::W#!!$<   0   .$S###Ñ#+ ++    ,     zÿëôÆ !.73#735#35#3267#"&5536'33#67'zmmHHHHZ    ^!! Æ_9;= +   _0  ]ÿñðÏ  &7&''63#&'3673#7&'£  (@@  6T  *“$  Ï(''&'  V,+."d!_ÿéòÏ573533#3#3#67&'#67'5'67#535#535#l27722=5  / *:,,2¼   U @ 7"TÿèõÌ&Nf76767'33#"&55#'6653&'#33#3#"''3267#735#5'67&'767#67#53&''67&'¸        +# GA# &   *     -K3G    Ì    # *#94#8          ‰ÿéòÆ!%73#3265#"&55#'67#735#35#35#’T ......Æ/ 46*fBC ÿé‰Ð37;?73#3#3&'73#3#3#3##5'67#5367#'65#5#35#$N-?E  ""Q")4Ð   Y & r"" ÿègÐ"(,048733#"''3255##5#'655'667#35#33535#335/$         + Ð   — *%%)0O  -H ÿé†Ð 73&'73#3#3##5##535# 22ydddda666² V V6# ÿé|Ð 73&'73#3#3##5##535# +/o TTTTS***² V V6#ÿéô† 73#53'3#3##5##535#ƒiægD©©©©§|||†  )  8 8 lÿèïÏ,73'67#&''6'673'67#&©7YO1    $ +3_W-  Ï =/   G9 jÿéôÏ1767&'7&''6'673#3#&''67#5367 *0 K(73& & 0 . .2Ï" " M  $ _ÿîóÐ"&*06FLR73533#3#''275#535#35#33535#335&'#6&''33267#"&57&'''6h8880 3;1181N1    X a  ÀI I- ) '   (  +  YÿèôÑ N73#53&'3#735##5##5#53##67#&'#"''32655'67''67&''6§?Š8.vvPPum/b4       ",2"' & Ñ )( "(          ZÿêõÎ _ci7&''67&''63&533#673265#"''67&'#3#3#3#67'75#535#535#535#75#7&'o   /   ?d"!   $,) ]Î    ( &F!+! ,)  jhG ÿéOÎ 73#53&'3#3##5##535#/C44445Î >TU5%PÿçöÇ+73265#"&547#'65567''67&'ÙI$    ÇG8( .$:/>f62 )3x., #2 "!! ÿêwÐ &73673#3##5#'67#535#5#'&'6  >  ¢7ZZ>2777(w ÿíƒÏ'+/3773##67'5#5'67#5363533&'#35#33535#335?3 $ % #  %7%Ï V *O G79ÿè†Ê/377#'6553#"''32=#35#'6553#"''32=#356  3  @  3  D1+ -3wË ''**<3) -3wË ''** ÿéwÐ#(-73#3533##5#5367#53635#335367#335;,,: %9%Ð b33 b6B uÿéòÏ"(.4:73#3#3#"''3267#5363535&''&'''674'8Q``` ` >>> *(ÏL? -‡ ^ ÿérÇ 7#5##5##535#33535#335r'<'ÇŸSS£>,,,g*** ÿêvÏ'73533#3#&''67#53655#'6 $' # *. Á ,,  (#- $ÿéqÉ 73#735##"''3255##535#35#UU//F   77777É>>w  1';ÿêxÏ1733#3#5##535#53#"''3255'7567#536;&&*F+&&8  (, )= Ï''}%   ÿêzÏ"(.4:73#3#3#"''267#5363535&'#&'''674'9*FKKN N 333   $ !Ï K A+† ^zÿéòÐ5;7''6767&'&'''63&''67&''667#Õ (-  +  2    &Ä # 1   %      tÿê÷Ð#'-37&'#3##"''3255#535#5'63'&'''6« ** ** $;! 1  9Ð) H E !)] !  ÿé|Ï*.273673#3#"''3267##5'67#735#53&'35#35*  (.   !"& 4$Í  >:Q7 AI-ÿëxÉ#'73533533#3#535#35##5##535#35#j%911111®""""Am q(> ÿèŠÎ +873#53&'#5'6'673''67&767#&'U+u5 "  )      Î  .…\&5#5;    %  zÿèòÏ%+73533533#3#535#35#35#35#&'''6})u%))))))+  !±llAB2   ÿêÑ6:>BFJ73673#33##&'#5##5'67#535#535#535#53&'5#35335353351 $   * + Ï   #%  1OO/! 5$yÿéòÇ!%+17=7#3#3#3#"''3267#55#5#35&'#&'''67&'î&!!!!*  \2  )"ÇO7‚&&.  ÿê{Ï#)/73533#33#53535#35#35#35#35#&'''6+))# n #+55555555* Ávv.,++"     ÿç‚Í&,28>D7'67#5'675#5367&'3#5'675#&'7&''6'67'6J  #4 4  "*J  !! & (' 8 :c  !V {X     Y    0  ÿéÉ +/373#3#535#5#35#3353353#3##5#535#735#35# t# o!?    Vd(//22)>>>>É22#/F* &  ÿè€Ï37;?C73533533##5##5#3#3#3#3##5#535#535#535#5##5#35#335g  -//11,  J ½$$) ÿéƒÉ'+/37;?73#3533#3#3##5#535#535#535335#735#33535#33535#335k, .0022/ -,E,<#ÉN##.,\ ÿé†Ð-159=AEK735333##3#3&''67'7#537#535#5#5;5#33535#33535#33567#)( (-99  $!   -) )?)C- +à  ,   ,  ( ES ÿè‡Ó4<@DHLPT73#33##3#"''3255##5##535#535#'6553&'5#35#5353535#33535#335V((!!# " ' 5 '' 10"3"Ó   T  "i  C3 5<\+ (   E+ÿëwÎ!%)/5;A73&'73#3#3#3##5'65#5#5#&''&'''674'#K.D ,  *Ì [ %%   ÿêŠÏCGK735333##35#53353#5##535##5#3#5#'6753353#35#535#535#33535,. .!  !     --88,< < &„)'4 BB4' =!' <  ÿç~Ï#'+/5;73533533#3#3#535#535#35#35#33535#335'67&'/*f)0')@)7 6 ¸\\`=+   ÿé‡Î"&7;?73#&'#5'67#5367'23'#35##"''3255##53#735#o ? E  "*4 ##D  E22Î  &  ? %*M 8Qa*  ÿéÐ-26:@F73673#7'##"''3255#5'67&'77#&'35#35#'67&')3#       "W ,,,,   M  ·    @0 -5 & 1)     ÿéƒÐ,1<BFJP73&'73673#3#3##"''3255#535#5365#5#35#"&55#733567#35#&'    #  LLF 'A F  :F  FF  ²  ]   ]  5 $  @ ;  ÿé€È26:>BF73#3#"''3265&''75#53533#7&'767#'67#735#33533535#35#hEP   #"  I     $!È>l& // M €ÿæõk!%)/5;73#3#3#"''3267#&'7#5335#35'67&''&'yS˜µµª   . xE2………›  _k1   W;      ÿéfÐ!%)/5;73#3#3#"''327&'77#5363535'67&'#4'*&9<BFJ`m73#'3##5##5##5##5&''&'#3#'6553&'75##5#35#33533536732765#"&5'67'533O66<66qY 3 b#`  1      É 1$$11$$1    $(' #+1        6 ÿótÏ73533#3#5##535#35#*((+*++¢---a o-r4 ÿóxÏ7#535#53533#3#=#($++,,#3 o,//,a 33ÿéår!'+73#"''3265#3#"''3267#536&'3#kS  p     2 N––r   !/ ?  7ÿèâ!'+7#3#"''3267#53673#"''326'&'3#µk˜   ˜+R   >  P——\*5 H # 9ÿë€Ï ;7'2'6'&''&'3#3#6753#5'5375#535#'6u ,?4,  !    G$,, '33 Ï   .A 6*2  tÿéõÑ  -:7&''63#3#735#73#735#&''67&''6¬  '3322-229   D  Ñ!";;/   %    ÿçïÒ#'4J7#3#'6553&'75##5#35#33533533#67'73267#"&5536ïC:·Z-,,,>-(˜99!(£ %  %  à *C< 3@]   /   #  ÿéƒÑ"&3@73#3#'6553&'5##5#35#33533567'53367'533O*Y,      = Ñ A90 0=Y 81!!!!!c  T  U„ÿèôÎ%+73533533#3#535#35#35#35#'67&' $ k $$$$$$ 9 °nnCD0 ˆÿóóÏ7&'3#3#3#535#535#·  b'##+k,##'Ï &:::: ÿé†Ï#'+/5;73533533#3#3#535#535#35#35#33535#335'67&'3+i+3)+C+6 6 ¹\\^<+    ÿê}Í%J73#&'&''675'67#535'2367&'#"''3255'67&'767i +$   "  ",)      Í      m%        ÿêŽÍ%I73#&'&''675'67#535'2367&'#"''3255'67&'77z 4*   $ %50     Í      m&        ~êÑ7#5##53&'73&'7367ê®) &   °. 2    ÿéòr'-39?73#3#3#535#535#73&'735#336735'67&''&''&''¯LTThähUUO  ;O ˜  ¹  , (r4       Z       |ÿèóÏ#)/73533#33#53535#5#3535#5#&'''6‚+//& w"+Q55555551 &½vv.++   qÿéöÏ "?73#5##53&'3'67&''667#7#3265#"&553#"''325µ1R12    H  0 Ï $% > j("  g  |F  ÿéòi$-39?73#3#3#'67#535#535#73&'735#336735&'7&''&''¯LTTh¿  hUUO  ;O Z  'i2       T      zÿêôÈ!%)-1597#3#3267##"&55#535#55#37355#355#5#735ë"##  $$"= ;<=>È7b  c7ο·¿3) ÿêuÆ #)7#5##53#735#35#3#53&''67&'t?SS....%d+ 6 Æ&S03' )   ÿéøX!%)-173#3#3267#"&55##535#735#35#33535#335'¥HUU#' 0/?QJ??RC•??RCX4   = 1 ÿéõÍ73#&''67&'#53&'667#¹,   //ÍD**FŽ1%7zÿëõÏ#)73&''67&''667#&'&'¡2  !    &) &Ï(     \  xÿéðÏ)=Q73#"''3265#'63353#'6677#5336'673#"''32653353#'67#5336‘P   F  $       F 7 !  ÏL2  !    V   L1 "  ^öÐ?PTX73&'73#3#53'#367#733#3#3#3#3#3267#"&5535##"''3255##535#35#*-r(#PTTMNJJJJFF# ,& NM ;;;;;Ä        <1 ?ÿëö|>OSW73&'73#3#53'#37#733#3#3#3#3#3267#"&5535##"''3255##535#35#*+r(%QUUMNKKKKGG$ -' NM ;;;;;p !      NB  S  ÿêóÐ7'673#&'#5'67#53¶-&!&      %± +8-lg$ 4 ÿéÑ $(,EIMQ7&'#5'6&'3#735#73#735#'3#735##"''3255##5##5##535#335335G$ E ,8 ### G$$ 6## C  "Ñ   0  0  0 1I """"*\#ÿï}Ï!%73533533#3#535#35###535#35#o'8E3333°  @ao(?RÿèóÏ48<73533#33##3#33#"&''675#535#535#535#3535e8@@4466++  0088008K"""¾$#' =#$­äÏ 73353353#FGÆÉ »ÿé÷Ï73533#&''67#7&'»  / @@T/&BA( 9Q= ÿèód#73533533#3#535#35#'67&'#(E((<æ>(@??C4(  ‡ðÒ 737#'733#737#™sw6â9ll— '!& ÿçð+73533533##5#'67#??;;B 6)9 %%!oñÏ733#537#'77#Ku:â“p iÏ. cðÏ7#'733#53'37«np9à’J d ‰688bðÉ 735#533#535#2€‰*ࢀŸT‡ðÉ 733#535#535#)*ࣂ‚ŠÉ2  ,wƒ !7&'7'63533#&'#5'67##  Q O**!    ƒ     |'ñ„ 7&'''63#"''3267#'67#Ê   N   „   $ 3ÿé²Ñ7'6#5'6Ž >;0GÑ ) se #…ÿéíÃ73#"''32765#'665#•X  è0+v_I GZ^ïÏ73533#3#535#feeWÀUf¯ ++`ðÏ73&'73#3#5#fc°ž³³  .AjðÏ73&'73#3#5#gc°ž³·  (;_ÿêõÏ73&''67#'6=%"  N- !Ï17!/ 8b+YÿèóÄ73#3#"''32767#'655#gŒTK6/2$Ä0r$ LQ5 9T/ 3ôÏ73533#&'#5'67#bbL 4 9&#:5 L¬##,8de6)QÿêóÈ733#"&55#'665#5##535#Ï! 5mQQQÈ<  0%  ,mqqO=]ìÄ73#3#5'67#35#ÓyŒ 0>EffÄ <&%C}òÐ"73#3533#3##5#535#'67#536¦=B ''0033.  Ð ##''ŠÿéôÆ73#7#5'75#35#35#75#f #!$$$$$$Æ€83 R X Cÿé‹Ä73#3#"''3267#735#I=)2  3(*ÄJ(H!.L& ÿçõd#73&'7&'#"''3267#'67#5'6V l 9  : M D '4b% *3  $ IÿéõÊ73#3#535635#'67&'×0=r"*§C/<<ZÊ !8{8P eóÏ7&'3673#&''67#´”^g]"@I# 4) BTÏ    ) ÿêóÏ736533#&''67#7&'£  @  Š''R+$, ,TI ÿêEÏ"7#"''32655'67&''67&'76        Ï +>'((  —ÿçõÐ$*733#5367#'635#35#35#'67&'¸#H  $$$$$$ 2  Ð || H98(  ÿé‘É*:IYg73#3&'7#"''3255##5##5"'6735#''7'76'&''7'7''7'76'''67'7y5"        4Y  .   5  .   É Š …™™ž    6     1      xÿéõÉ'6ETf73#3&'7#"''3255##5##5'6735#''67'7'&''7'7''67'7'&''67&'7€n/     0Q  ( 0  (É  Ž  „™™ž¢   6    1     ŒÿçôÏ73265#"'&''7&'37&'ñ3  !  ‘ ;'#O!)(? ;ÿêžÏ73533#"''32765#'667#O&    ¢--Ž*(\HJAE ÿèàÇ 7'66553'#39 »““€D5 1$_G4! ÿèëË 7'6553'#3/Ш¨¥>B= 2@d& }âÏ 73353#533vDÄDÏ?/BB/ÿêåi7#53#"''3267#'6]FÎ ^)$ @WZ"D12 ÿéçu73653#"''3267#'67#Vc O4' IR[  M%2"28 gôÐ7.''667&'f!E( #%$E &) ÐA# 1 HÿéôÅ73#3##5#'67#5365#5#b‹ '')3 -&(W&ÅLjjI!<:LL; ÿé‰Ï&733533#3#53'67&73#5537 ((-}-   \J¼:MQ !'  7t o[  ÿéîÏ373673#'67#7&'32767#"&55'67536PwzG ?Mš  *&  $  $b?6YB O/&  TC jÿèõÏ#'-373533#3#535#735#3353#735#35#35#&'''6v022<Š;0/`ssKKKKKK3 Ä ,   E_C & &    ]ÿçöÑBFJP7#35#535333##67&'#"''3255'675#535#'6553&'73535&'óo+!!) $     !!+;G ¾3 "$   #(    B3 3>\ 2#+  lÿéóÏ37;?C73533533##5##5#3#3#3#3##5#535#535#535#5##5#35#335l++{6::771 [(½ ( óÐ$=AEK73533#3#535#3'67#3#3#535#3#3##"''3255#535#3#735#&'_bbX½S_Þ ÉžDXÄZHæ   LLµQQ22a  È     *  ‰ÿéæÐ 7#53&+k‚çK ÿéóÆ73#735#3#3#"''3267#7#*¦¦~~1æ¢ ’/ÆED 8 3@óÏ/73673&'73#673267#"&''675#'67#BH $Y #  !$ 3 .<± (  0:$#*GÿêøÐ NRV73&'73#'67&'3533533#3#3#67&'#67'5'67#535#535#5#35#UCC›9 " D U#'##';  4   $4(#\'''À       ; '  '  UôÐ73673#&'#'67#SoF1 8< 0Fµ  (50#fðÉ 733#535#535#)*ࢀ€‰ÉP \õÏ73673#&'#'67#U mE2 8 < 3G¶ "0.! ÿçôA73673#&'#'67# dlZB KP @X/  &'   †õÑ73673#&'#'67#H {3 + >* 0à  aÿéëË $7#"''3255#535#5#'##535#35#ë --ËÉ  qR#1AQà"1 ÿòjÁ73#3#7'6353675#O)1$Á;N |  ÿìÑ/37;?73#3#53&'#53&'37#3#3#67'675#535#735#33535#335G,s*'e*))26**))@)Ñ   4 %N   .-uÿê÷Ð :>DS7'6655633#3'67#733267##"&55'655375#'6733267#"&5æ '4 <%       Ð /E; 6G<      2' '.E'H6+4   GõÎ 7&''6,? B)$F RÎ A>=&–òÏ73533#3#535# SXXläeS   lðÐ"73533#3#&''67#535#&'7#6$QVVi4GP=cQŒD .   + uÿéóÇ %+73#735#3353353#''67&'&'''6|qq.H   +ÇJ*****@Žˆ   -  % ÿêvÎ73#&''667&'767#53&'B &  !  A(Î0#($ "zÿéòÏ73655#535333#&''67#75#€%- 1"  !#Q])**H3"36 $06)  ÿé€Å733265#"&547#3##5#535#X ÅD5! /!9,:InnI ÿéyÂ$73533##"''3255#'67#75#7&' J &K&  e]]R O;. 1.KK? ÿêvÌ"7'2333"&&#"'67667#53&'l '9/!    F(Ì8# ,ÿè}Ð(767&'7''63533533##5#'655#2 %) Ð+  T####QQ0 & ÿésÅ73#735#3#3#"''3267#7#SS++f; >   A ÅD E!7 3 ÿómÏ73533#3#5##535#35# )%%, ),,¢--.a o.s3 fôÐ"73#&'#'67#535367367#335Ï4& /3< &6GH+BAÁ      ÿçñ…#)/5;73#3#3#"''3267#5363535'67&''&'7&'b\³³««4#|||– „  B ?…7(cC    ìÑ73&'73673#5##53&'9 *    &µ' Ï   &' zÿèòÍ 7#5##53&'7&'''6ìF* ¯=*+> 3\).\V5 =ÿîqÏ 7#5##53&'7&'#'6q=%   ¯:)*; ,@L O>U1 8 ÿévÏ73533#3#535##5##535# *++&]#*^...­""$$Occ@- ÿé‚Í73533#3#535##5##535# .22(a$.h555­ ##Oba?-ÿé?Ï 7#5'6-ϰ† ,@ÿêµÏ!'-3973#3#3#"''3267#533535&''&'''674'r%@OOP  N... &$ÏI  D/ [ nÇ 7'7&''62%  ‘/Vf93A-;eòÐH7&''3&'33#67327#"'#'67&'#7#"''3255'75#535'6Ç 7<HC    A(* 4799%5Ð            ÿé~Ï$(,7'6553&'73'#3#"''3255##535#35#,(%??  %%%%%$?/ 0;V  8(&m .‚"5 ÿèÎ&5<73#5#3'67&''67#53&'67#73#"'67'7#325Q(G      -  ",   Î %4(! %/' >X  „K ÿé€Ì&73#35#535#53#67'5#'665#567=-  Ì q@  L,( #&i ÿézÐ %)-157&'6'&'67&'6#5##535#33535#35#H     P  ?)?)Ð   Pv y-I ÿé|Ï#/73533#&'#5'675#''67''6+))  +  H  ²RLE!0L   )  ÿç}à '7'6553'#33353353#353#5#5335#' hDD=  >ˆ(D5 7=^;+�#39(D 9(9^ÿéîÆ '7'6553'#33353353#353#5#5335#‚~ZZP'T&Š(E4 6>_<+%)88):6(D ;)6`ÿéôÇ *7#'66553533#3#535#3533#3#535#ój $$+c&$%%-p1$ÇSD6 1$_0P## ÿéð)7#3#3#535#535#'65533#3#535#53îREEO¯KAAVhDDT¹PAA<3& (-F\ YÿèóÏ6:>75#535#535#535#53533#33##3#33#"&''673535y //66..88==2244()  ? : ;$# v$fÿéòÐ'7&''6767&'3533533##5#'67#Ñ .3 S"#) ´ ,* ]))))RR;,ÿçßÎ 73353353#5#CB­˜ŒÂÂŽ³ ÿéóÉ 87'6553'#3773276767#"&55'75'75'6.pII !(* )B6#& (+F5 5@`:)  ÿëñÐ"736533#32667#"&55#'67#PoM  (,. QO¡† $  ŒAJ*f ÿêòÏ!73533#32667#"&55#'655#6)& :4#Ÿ00‡ ! Ž+J. *A+ÿéîO733##"''3255#53&'ª00   ——d O'  #  ÿéîa733##"''3255#53&'ª00   ——d a4  0  ÿéîÏ73533##"''32655#&'’88 ’7¢--‰ „ % $!ÿèðÌ 15;73#535#535#3#7'75#73#3##"''3255#535#735#&''ªª˜’’˜]$',6+&ke))  ¤¤4>>Q ÌJ  C6  M   ÿéó>733###'3255#53567#4—!df   mmm>    >ì¤ 7#5##53&'7ì°` A..A ÿèPÏ73''677&'67#53667#(    Ï,U)  #' 8& ÿéóÑ-1573673#3#3#535#'67##"''3255##535#35#ž-36!U"  Q   %%%%%À   Kd  ){!1¤ÿéóÏ 7373#&''67&'67#67#¬'    ! Ÿ0Q   &)_B)! æÌ 73#735#'3#735#‰]]77[[55Ì//ÿéáa 7#5##535#3#735#á__<3 2;YZ+#"XÿëóÏ (7&''6#3267#"&553#"''326£ # !!) 356*  4 \   Ï .0),BW m6 _ñÏ'737533#7'773673267#"&5'$$"2>i! !(   #¾HW%[$    kÿèñÏ"73&'73#3#"''3255##5##535#k=483  2;¯  _  H‚‚k} ”ÿéïÏ733#5##5##5335#35#35#35#¹$%$$$Ï,„EE†2 QQÿæïÇ$*CI7#5##5##5##53'65'3'65&'7&'373#&''67&'7#367ê"dK"!  W z/Z    4 & &<" Ç^NL\^NL\'' ''+ #  "     SÿèôÐ"&.26733#5367#'635#33535#3353##5##535#35#†= *…F:  $''7'^''7'}œœ‹RRRRRÐBB 8 $ % R R ' GÿéðÑ I73#53'3#735##5##5#53#67&'#"''32654''67''67&''6£B—B0zzTT{t9!j3    '26)* )! Ñ )( "(            XÿçñÏ /37;A7#5##53&'73#673#&'7#'67#5'675#35#35#35#7'6îl>9`' &?!)   %&WWWWWW ½)*   `    R F ' & h ]ÿèô 73#3##"''3255#&'''6xhh „9  6j  EÂ&t  p%* *% 5"QÿèõÐ48<@DH73673#33##&'#5##5'67#535#535#535#53'5#3533535335ƒ   (0'   ",,"",)8;Ð   %%8II58'ÿê{Ñ 1573#5##53&'3533#3#3#3#5##535#535#535#35#H(F.#&&##''"3#++###33Ñ ##6    SS   |$ÿîð`73#3#3#53'#735#67#ÌÌ ±;à=‹‹gO` 0 4 ÿîð^73#3#3#53&'#735#367#àà¶">Þ>!’’,9J^ 1 4 kÿèôÈ:AIMQ73#3#'67#'737#'67#'737#53#3#3325#"&5'37'#5##535#35#q8 ] ;  9 QJJJJJÈ'   M      _ _!1 fÿèöÐ,04:@DHLRX75#53533533#3#"''3267##5#3#5'67#77353355##655#35#35#35#'67&'”$$$$-  cm   1$ IIIIII  >¬  *    [U ) &  F % #     aÿèðÏ2LPTX\733#3'67#673267#"&55'675#'65533#3##'667#'67#537#735#33535#335Ÿ55@ .    +[$+  '  $)%#7#Ï    I]ÿêöÏ;JP733#3#53533#673265#"''67&'#'6553533&'73#67'675#'6¨**7ƒH"   >O  d4)   Ï##""%/  05," "*>   < : iÿòóÃ#'+/3735#535#53#3#3#73#735#73#735#3#735#73#735#i:778ƒ9::>Š** C++ `** C++ QKKQ´3  3 R4  4 xÿòòÇ 7#3#53#735#ñdez(CCǯÕ<^:qÿíóÐ #73353#5333#32767#"&5535#¢ w1t\ " ./]`Ð:.A@-$@+@lÿéïÐ473533533##5##5##5##53733#"''32765#'67#lƒZ'3")&$·10=F )5+]ÿèöÑ48<73&'73#3#53&'#367#3#32767#"&55#'67#735#35#n54&Ž$1)+s  2 ( MMMM½    (R" (1 #00 `ÿéôÐ $(048733#5'6367#35#33535#3353##5##535#35#9#| 82##5"W##5"pLLLLLÐE=# % & P P ' ‘ÿèõÈ#'735#53#3#7&'7''675#735#35#335™!G!! "(!##!tCCN! #p!€,,, ÿéÏ $*07&''63#3##"''3255#535#&'''6A % E''   //C  3 Ï   J  G<   ÿé…Ñ L73&'73#&''67&'763353#3#"''3255#67&'7''67##5367#00vJ    @G-8  + ,)½      11CI  2  PblíÒ!%)73&'73#3#3#3#5'65#5#5#<;PUNNNN[¿ S@@@@@Ñ  5    |ÿé÷Ð 7&''6&'3'67#°  $#a   LÐ,**+/ !*pÿéòÏ-73673#3#'67#535#73#3##5#535#536u Y ¥7:' "37<7__7{ÿéòÏ-73673#3#'67#535#73#3##5#535#536€  P ¥7;& #27<7__7~ÿóõÆ7#53##5'67&'3#´0l' 6Err´ ŠY%,%#/,+ƒšÿêóÏ73533##"''3255#&'š1  1 ›44ƒ   rÿéêÐ733#5#535#535#5367#'6›7 %__YY\5 ( Ð Š $$zÿõòÇ 7#53##5'67&'3#¯/n*7Bss´‹Z%'*".+*vÿêòÐ2973#53'3673#53&'3673#&''67'67#367¶/s0  |#E  ' ")0 Ð ( P       bëÏ573#"''3265#'67#'6'3#&'#5'67#535'2›A ) # # &$   &.!*Ï B'*    pëÏ773#"''3265#'67#'6'3#&'#5'67#535#'2žA  &  % &!  %.*Ï8 &       vëÏ873#"''3265#'67#'6'3#&'#5'67#535#'2—J  & ! ! &&  &1,Ï1 "        rÿòóÃ#'+/3735#535#53#3#3#73#735#73#735#3#735#73#735#r5335|566:(( ?(( [(( ?(( RKKR´3  3 R4  4  jòÐ,DJ73533#3#3#3#"''3265#'67#535#535#73#&''67&''667#-..'',>=  4   2((-ŒB    +Ç             xñÑ273'67#&''67#3267#"&553#"''325?3PE,  ¢2(% X Ñ +    2  ZóÑ ',:>BFJ73#'673#&''67&''6673733#3#7#77##7#37#3372OZzE      ·X fZ!Ñ            ÿñ|¾*73#67&''67'675&''675#`&   +6  (¾n,5  0  5A f ÿêyÏ!&+17'6733#7'5#'67#5367367#335&'1  ,  # / -& «  JA   O>2J R&B  ÿèÑ 5I7'6'6'&'&'&''67'67676767&'3733#&''67#q #HD  !  V (,   G-/3   'Ñ       ?    '2    ÿèƒÍ#'-3973673267#"'"&53#735#35#35#'33#'67&'% '(! /GG######.Wi#  6 Í   aF & ( 3 2;YZ+#" ÿì„Ï'+/39?EK73#33533533533#3#535#535'635#35#35#&'''67&''4'$KQ    p       F  B Ï  &&&&&&&** p*****B  ÿè€Ê %+73#735#3353353#735#35#35#&'''6pp  Vdd<<<<<<3 $  ÊB =jK++!   fíÑ%8K73#35#535#535#533#5##535673767&''67&'&''67&'764$$–!!!!$6¶ $     .    ¤  B!!>  (      ŸðÓ73#3#5#53&'h¶›®cÓsÿêôÎ_e73&'73673#3#3#535#535#3'33#73265#"''67&'#7#"''3255'75#535'67&'v"1**4{4**3/ %$       !H  ¶      >   "       O÷Ï=735673#3#35335#533#335#535#535#533#&'#'67#  ))))5-,,.0&&&&(8),_+ *}G   4 +  J   ÿé†Ë %+17QX73#735#35#35#73#735#35#35#&'''67'67&'3673#&''67&'67#67#33+22'   B  #  [!A  & @ "ËbE))EbE))      $      añÏ #>BFJNTZ`flr73#53&'&''67'677677'7''67'67676767''3#3#3#735#'&'7&''&'7&'''67'6ƒN&           s====<<#   œ    œ   £ ˜ Ï #                         ÿè†Ï"(.7373#3533##"''32655#'67#&'''66; !!  - \  8 ­"$,,I D$]   gôÔ173'67#&''67#3267#"&553#"''325>6R H-   ¡2!,$ Y Ô 1$ -  @"  ÿéŒÊ%+DK7#5##53#5##53'6673'66&'7&'3673#&''67&'7#367Hv*  ?  , G a'@  ( 2 ÊXJJXXJJX1% #,1& #   %     |ëÏ733673#5##53&'733#735#u  ,³. :‰‰eeÏ ,, ! kñÍ'9P733#"&55#'6653#53#3#3#''#3'6677#535#'667#53&''67&'Ö $ YCjl50  ! $ \ BW    Í    MZ   7?       OÿçóÐ-73673#3#'67#535#73#3##5#535#536X  !!!r&& ¤8<% $18<8__8EÿèñÏ6:>73533#33##3#33#"&''675#535#535#535#3535W>CC77::.. ::BB99>R%%%¾$#  ' =#$ÿèøÇ!%73#3267#"&55#'67#735#35#35#¢F  %  Ç•/ 2.#jEF;ÿéôÏ:@F73673#3#3#&'#3#'67#53655#5'67#5367#5367#&'#&'G:MQPVm  3GM: 06?* !.#*6 DB#"¾   "  T ; ÿéŸÐ,173#&''67&'767#53&'3353#5#'66735#TA , &  `8@C  @@Ð     kv/ ÿé¡Ñ 157#5##53&'735#53533#3#3#3#5##535#535#35# g;D1**''22==2Q2??1QQ¿"" H L K_ ÿ퇼73#3#"''32765#'667#uB=  )( ¼d! 9Q+@SOïÎ 73#7&'''6'66{>`$ !} ;p BHÎC6 0 ÿèVÏ776553'67'56OÏy EE<5  % • ÿë™Î %733#3#533#"''3265''67&'B))4r*  ! [  Î"` K $  ÿèðR7#53&''677&67#4 ®%#0:*.=#"8&r?"  YÿèîÐ773533533##5##5##5##536733#"''32765#'67#b++Œf *5 %3'·10=  F (& +[ÿêòÏ 4;73#53&'3&'73673#3673#&''67&'67#367¬:‰99% &“/R  , ! &<Ï  L        [ÿéóÐ $(733#3#53533#"''3255##535#35# 33@˜C  TTTTTÐ--$q  /‰&:ŸÿëîÐ 73#5##53635#35#½%+ ++++ÐÂÂZ7‡>"TßÐ 73#3#53635#35#i^¡ª½@1ŽŽ––Ð/ .i ' G ÿéó[ 73533#&''275#735#335&'#"TQ Sp6/TAAU?2E1  -bÿéöÐ97&'3733#6767673265#"&5'675#'67#Ê LZ8  ! $   Ð +44#           xm3 1b[ÿèôÉ$73#735#333#&''67#5367#335“PP,,5b;$ '-- 28/B%ÉG%E2+-"!bÿôòÎ .7'2'6'&''&'3#3#535#535'2ä 4J=9 1    s >>7~3>>8Î    &#!!"nÿéóÏ 4A73#5335#&''67&'763673267#"&5'33#67'œ9v'PP;        A&& Ï ^^M<    G"   U*  RÿèóÑ<@HLPTX73#3&'73673#33##&'#5##5'67#535#'6553&'5##5#35#53533535335®9z'"     =%1Ñ   0@@* 2, 4<[U. ÿêaÃ7#"''3255'67567#53T   *š(6 .-}"   7 - WWWW½   (S  %0 "2/bÿéíÏ'+/7#53&'73675##5#35335#5##535#35#í‹! )**) WWWWW³XX  #1ee#3 ÿíõÏ #73#'6333265##"&5477#·/3 71  & Ï   V   A )ðË!'-39?73#3#3#535#535#735#&'735'6'67&''&'7&'"¼URRfÞdNNS@@!CB" x€ 4‹  ËB    "  "" `        RÿèòË %+17OV73#735#35#35#73#735#35#35#&'''67'67&'373#&''67&'67#67#XBB =DD!!!!!!/   V  ) x- \ - 6 '"U 3ËcG ( ( FcG ( (            {ÿéóÆ73#&'3#5##535'67#35#ƒk) (9# $/99Æ 5ij3(©7‚ÿéôÏ'/373533#3#535#3#735#3673#53&'#5##535#„.//(d).aa== rZ>>>à   *1$ (= =! ÿé~Ï'/373533#3#535#3#735#3673#53&'#5##535#.//(d).aa== rZ>>>à   *1$ (= =!OÿìôÎ+05;733#3267#"&55#'67##5367#'6367#335&'†9 .-    96 B 0 !%8/  Î I@ F< 1 U  Z%A jÿéõÒ1773'67#&'&''63533##"''32655#&'œB] S7  "!^   ^#  Ò A4  ^8 3 $ÿèò’ !-73#735#35#3267#"&5536'33#7'&²²ŠŠŠŠ§ %+  * %BB<(# ’O/+-   >   RÿéõÏ,08Q73533533533533#3265#"&55##5#'67#5##5##5#"''3255##5##5353R  3XVtŠ   1¶   ** )),7  !HH;M`ÿéðÎ8<@F73533533##5##5#3#3#"''3267#&''67#'67#735#35#33#h"##" qH_   #  '  KKKK3D GA.  + ) F$RÿéðÏ6:>BH735#53533533#3#3#"''3267#&''67#'67#75#35#35#33#g$$&''Ng   '   )  N&UUUUD733533#3#3#3#&'#5'67#535#535#53&'#53367#7'6'&'_J* ,\QQ`G+$ ,2'4 4E]OOX.(FCVt ”   $$$   '%     ;  gÿêõÐ&,2:P7367&''667367&''66''67'6#5##53533#&'#5'67#‰   @    U  B  B]25-" !-Ð    &    8""+86aÿêõÏ(.4<R7367&''667367&''66''67'6#5##53533#&'#5'67#…   B    X  D  Ca480$ #/Ï    &     8""+96`ÿéõÎ17=73#3#35335#533#335#535#535#533#5356&'''6ˆ  ) J0Î  99BUR "“‡¢  (óÑ &AEIMQW]ciou73#53&'&''67'67676767'7''67'6767767''3#3#3#735#'&'7&''&'7&'''67'6€P!           s<<<<=="  ¡  ž¡§˜Ñ  @  %0  % /   ,    _ÿèóÈ"&7#53#3#7'7&''75#53'35#35#35#œ,l-55!  6A=33GG!!4""†BBK!   #K'~'''VÿèõÍ733#&'#67'5#537'6okA4 ? X!ÍZ@!M`  kT *  ÿòõÐ  &7&''63#3673#7&'&'z/< 84,: L'| 9Ía*Ð)..('e,+."i""RÿõòÆ7#53##5'67&'3#žC•;+CZšš´ Šf (%".,*€ 9ôÑ573'67#&''67#3267#"&553#"''326<6* @'  £1 *# Y   Ñ;% I    Nc9  ÿóÐ7676767&'7&''67'67( '/$ +4"lT1 % #5&@>hÿòêµ 735#53#5#535#peh}mmej8ÃAgÿíôÎ "73353#5333#3267#"&5535#Ÿ#~"8za "*  4/agÎ9/A@.#@+A,ÞÈ"&*.2767#533#"''3255##5##53&'35#35#35#35#[“¼$G  @AHAAU@@UAAU@@²  W  l)/ AöÍ(73673#&'#5'67#53&'735'2Ñ %-  /R&7 ;'$: 8"P) &+YÍ'  "+) &_ÿèíÇ 07#"''3255##536735#&'3#3#5#53&'í  i; i  # <6H %ÇÄ  k‹ßC1  5 #4 ’ÿëõÏ &73&'73#67&'7''67'67676•&$\?   %   ± O,$  -+'WÿéøÒ $(:?73#3#3#3#5'673&'5#5#5#3&''67&'#367°4700008†  $)))))~ ',$  Ñ A    0     LÿèôÏ &*.26:NRX^73#5'675#537367;267##"&53#735#33535#3353533533#3#535#35#'67&'..'  % K‰‰));*e));*}#-$$+§+#5-- !#"H  Ï>     A' # ( !   ,óÒ %AEIMQW]ciou73#53&'&''67'6776767'7''67'67676767''3#3#3#735#'&'7&''&'7&'''67'6ƒP"           s<<<<==" ¡  ž¡§˜Ò  @ $/  $ -   +       QÿéïÐ(7&''6767&'3533533##5#'67#É28"$ V'""(2 (´  -+ ]) ))RR:,ÿênÎ73#3#'67#535#535'6e $##%& %&""-Î & 5! *("TÿèõÏ073673#&'#5'67#33&'3#3#"''3267#7#X7J,! U  +C J Q˜d[ \ µ    J0 'VÿèîÐ573533533##5##5##5##536533#"''3265#'67#^!(""(!m $D  12 ,!·10=  H/7*HÿèðÇ *7#'66553533#3#535#3533#3#535#îy )**4v0)*,,3x3*ÇSE5 2$_3L""IÿèóÏ48<73533#33##3#33#"&''675#535#535#535#3535\=DD6699.. 44<<33=P$$$¾$#& =#$EÿéíÐ$(,7&'73#'65535##"''3255##535#35#ž8xdds  BBBBB¹  <=9 .;Z*;i  -‚"2UÿïñÏ!'7&'73#3#535#53635#'6'&'ƒ Y ,.0œ5)I#=  `  Ï  ‹‹´‹/'&''*$PÿéöÏ&9?73673#3#3#&'#'67#537#537#3#3#'67#535#&'a0CGFK` ;  &#-O#-2C7 !!FM((M&&U„„ še] c "É #;;# )  %œÿêôÌ"(7673267#"''67&''7&'37&'ð,       — .#"# 0%?6IÿèöÏ9=A73673#&'#'67#3533#3673#3#5##5'67#535#35#35#]- Q( ! ( '>QO "%28OOOO¶    ] L  W2IÿéòÐ/KOSW[733#3'67#673267#"&55'75#'65533#3##'3267#'67#5367#735#33535#335“AAL =  ' $:k0? 1+ #.),G,Ð    J?. 07TJ=   & !  \ôÐ2EIM73#&''67&''667#'3533533##5##5#3##'3265#'63#735#¡@     &”S  H  77Ð       5 ! &  ÿëÐ?737#535#53533#3673#367#"''3255'67567#'67*@-##  #    m       ÿû‡Ð %7&''63#'767'&'&'L( DDShA    Ð  %Y ++,!N!!€ÿèõÏ"73#&'#'67&''67#6®@   & ¥<.  6?$%" ÿé”Î$(9=A73#&'#5'67#5367'2&'#35##"''3255##53#735#A J %04& ((P   Q 77Î  # A  'M 8Pa+  ÿèÑ2:>73673#3#3#3673#53&'735#535#535#53&'#5##535#/1++55‚ 66--4cEEEÑ        A A& ÿè“É)-15:73#3#3#3#3##5#535#53&'#535#535#735#33533537#5..54==66.9007$< /' 4.0./ÿóóÏ7&'3#3#3#535#535#º  _%""(f+&&'Ï &7<<7ÿÿK¼ 7##55#35K((¼£½J9977 ÿéò„#'+/37#5#3#3#3##5#535#535#535##535#33535#335íOHHKKhhiiKKHHP+99M9†99M9„$ <  < $I ÿîðR 7#55#353#Ô§“€€¯ßßRHH  &E¼ 7#5##55#35E"¼±¸J9966 RÿèôÐ-15;AEIMSY75#53533533#3#"''3267##5#3#5'67#77353355##655#35#35#35#'67&'†++**3   # q{  # 6* WWWWWW! F¬  *   [V ) &  E % $     SÿéòÉ HLPTX7#'6553533533##5##5#3#3#"''3255#67&'7&''675##535#735#33535#335ò{&&a)4 "   -''?'ÉSF4 6?`@C 2FU' % RÿèñÈ#AGKO73#3#5##5##535#3#'3#3#73#3'73#3#3#3##5'65##5#35#d€6C10C8P&&B$$$$B&&>4511119u 9())))È *--* %      I     ÿùjÐ!73533#3#67&'7&''67#535###  #«%%4/ "$ -4UÿêõÐ%+/3733#3267#"&55#'667#5'6367#35#335= +&   7 "1 0  3"Ð M:  @=E  C'''iõÏ 7'67&'&''67&'76Q) %q'% %' /0 ?*"F 8#Ï       Fÿé«Î#'+73533#3#3##5#535#535#35#33535#335Q !! ))++  - ¹_--_7;SÿèôÐ8<@DHLP73533533#3#3#33##"''3255##5#53535#535#535#35#35#35#33535#35#Z$$''+G:   ^7B&$7$$$$$$6(^$$6((ÀF  %%F-P6ÿñïh 7#55#353#Ó¦’~~¯ßßhVV#0XÿêíÈ73#735#373#"''3267#'67#vppJJ$H  7 6 , !ÈQ-^O6H8SÿéìÏ !73#'3'6573#'&'7&'''6Ùe#!2B]ÏæãtD+ (:oÖ¤""%!!"% $ ^ÿéóÆ 73#3#5##535'67#35#7&'h„72O0 -8OO?ÆZjj;(©6_fÿêóÏ/73533#3#5##535#33##"''3255#53567#l399;^73U@@  :: <¹$$:% ! oÿêôÏ/733#3#5##535#533567#533##"''3255#¦007Z60078 :Q ::  8Ï''„ ' #¤ÿôôÏ73533#3#535#¤FˆGGnn]ÿé÷Ñ $(7'667&'&'3'67##53#=#¦#  "% "#  'r VzU²"!    p SR %%ÿéxÐ&,73533#3#"''3255##5'675##535#&'(+++   %(O  ·4 “E$+-?Qh tÿéõÏ,73533#3#"''3255#&'#5'675##535#ƒ*...    "'*¶5  (0!IH$-)>P[ÿêóÏ 2973#53&'3&'73673#&'67#5373#&''67#6­8ƒ73"$Š4 !*P  3 'C*Ï  L  ^  >  žÿèõÏ#5'67#53533#&'5'Õ u)"/>1176 "cÿèõÐ(7673#3655335#535#53#&''67#n $,3* -, + -°  'rr'{-.+&JÿéòÎ%+1773673#3#3#535#535#7&'&'''674''4'^P  '?55D—?559&  n  m ]  ¤:  ¦ ?ÿéòÎ%+1773673#3#3#535#535#7&'&'''674''4'UV )C88H D::=)  p t a  ¤: ¦  ^ÿèôÅ !%)-3973##7#53737337#3#67#3#735#35#35#'67&'ì w1zzTTTTTT  GÅ dG ( (      VÿèôÅ "&*.4:73##7#53737337#3#67#3#735#35#35#'67&'ë |   !4YYYYYY  JÅ  dG ( (      NÿéõÈ  $(;@73#735#35#3#7#5'75#35#35#75#7#53&''67&67#eWWWW(£P!!=G      ÈF* ( &QZ46&)     `ÿéôÐ 57&'7'6'33#5##533#735#3533#&'#5'67#x n  1>i>%]]77,=@4  0Ð    %/0!68  #<="ŠÿéõÑ-1573673#3#3#535#'67##"''3255##535#35#˜07:&\%  U   )))))À   Ka  *{!1QÿèõÐ-39?EK73'67#&'&''63533#&''67#7&'&'''67&''&'x&81    ;   $8    c  YÐ M> 49    +44/')5B  ™  QÿéðÏ $(A73&'73#3#3#3#5'65#5#5#3353#"''3255##5##r"791111=‰ ;(((((;>  ;Í  @    ##8 %""> [ÿòóÃ#'+/3735#535#53#3#3#73#735#73#735#3#735#73#735#[A>>?‘@AAE˜--L--i--L--PLLP´3  3 R4  4 ›ÿéûÆ!%73#3267#"&55#'67#735#35#35#­@     ÆŽ6  :;.dAB ÿéƒÑ?767&'3#3533#"''3255##5##5'67#5367'67&'"   ?F (   # Ê     ;  &YYGC  ÿç™Ñ-NT733#3'67#73267#"&55'75#'655335'673673##"''3255#7&'G::> +  /, 1*.  ,Ñ     F9. .5P… "  *   ÿé’Î17=73#3#35335#533#335#535#535#533#5356&'''6,  ' ˆ C(Î  99BUR !“‡£ ˆòÏ733#3#53533vTThä(,Ï))’ñÏ733#3#53533zZZdã.+Ï $$HîÓ 73#53&'3#735##5##5aÜe?¢¢}}­¸Ó )* %(( ÿç’Ñ!>DJP767'7&''667'7&''6&''67&5'6767'7'6'63'6>   "&3    " !%!  ' %(5 5,#N IÑ          4 ( ÿé‡Ð,048<73&'73#3#53&'#37#3#3#7'75#535#735#33535#335+2z.#&g*..03?3--*,E,½   (J   -, ÿèzÐ"7367&'#"''3265'3'67#C    4/ ÐG  Z ŠH*4 ÿè‰Ð"7367&'#"''3265'3'67#J   5.  ÐE Y ŠH$8IÿêôÐ 873#'6#"''32767#5327667##"&55'753753}gr #w   " 6 Ð  )I GBD  F+% )$^ÿíöÏ 873#'6#"''32767#53325267##"&55'753753‰Zd o   B Ï$F "IEH  L#-(ÿìöÐ 873#'6#"''3265#53327667##"&55'753753«=C O   '   Ð )CJDM  N/)?ÿë•Í73#&'#5'67#535'6Š      %Í( d`",$>ÿïðÍ !'7&'7'63#3#535#35#'6'&'p  hnœ37²=+>J q  Í   ‹‹‹‹1 )'&(*%ÿé•Ð6:>BFJ73673#33##&'#5##5'67#535#535#535#53&'5#35335353355  *# #   !! 14Ï   #% 1OO2 5$”ÿèóÏ+177673&5#"''67&''7''7&537&'å$))     ¢  1$    53  ÿéóÂ73#3#"'#5##535#3255d)$ #);Â"o "’u‡"4` XNÿéñÏ&.26:>73&'73#676767&'7''67'67##5##535#33535#35#NICS  #$$& =D8•a&&:'a&&:''º   %     Zff$6{ÿêôÏ %+17&''63#3##"''3255#535#'67&'±  "4,, -- J Ï%&#D  A8 =ÿçöÐ"&273#3#&'#'67#537#53635#35#3533##5#‰H>U0 )+ &-:2'^^^^"(("ÐT '&T -2n%%BÿéõÏ,08Q73533533533533#3265#"&55##5#'67#5##5##5#"''3255##5##5353B 5b_•   %$7¶  ++ )),8  #II>( !@ ;ÿéÎ7'6'67#c # Î.a  ) x€ÿèõÈ $(735#53#3#27&'7&''75#735#35#335Š' R''  )11'..(tCCN! #p!€,,,—ÿîõÐ %73#'6333265##"&5477#´4: <%    $"Ð  "B   AŠÿèõÉ&7#3#3#3#67&'#7'5#535ë@9999G(  '  É  DM ZlBÿêƒÅ73#3#"''3267#735#H9%+   + "&ÅK)F!-M&5ÿû¢Ð %7&''63#'767'&'&'p $;;FX7   Ð  %Z **+ O!!ŒÿéðÐ"(733#"''3255##5335#35#'&'7'6¶%  4#4444  R  Ð=“  ;ª0Lu    ?ÿèñÌ 37;7'66553'#33533#3673#3#5##5'67#535#5#5#j iiY - ::%8)W:::–/E7 2$`6%#  V EUDÿëôÎ&EK73#&'&''675'67#535'2367&'#"''3255'67'&'Ú !KK* ", ''. 3$ +@KA    " ) Î  s+      GÿéóÏ E733533#537'6'&'3673#3#3#&'#5'67#535#535#53&'‚6 2Z n  ( ##D<ÿèìÈ#CHLP73#3#5##5##535#3#'3#3#'3#3&'73#3#3#3##5'65#5#35#RŽ>J76I=W**I((I**I)) ":;6666?‚ A.0000È *--* %     J     GÿéòË %+17OU73#735#35#35#73#735#35#35#&'''67'67&'373#&''67'67#367PFF""""""AEE!!!!!!2   Y  ) ~3a&  "< -. '<  ËcG ( ( FcG ( (              GÿéôÐ!048VZ`dj733533##5##5#53##535#35#7#"''3255#535#5#3533#3#&'#5'67#535#35#&'735'6l6**6%%#7''''— =,,,,Q300-(   /3 (  Ð  <‚¾ $¬ n< 3  !) 3-   MÿéóÐ!048W[aek733533##5##5#53##535#35#7#"''3255#535#5#3533#3#&'#5'67#535#35#&'735'6t4''4''"4""""   8((((L-..**    *- &  Ð  ;ƒ¾  #¬ o;  2  '  2,!ðÈ!*.7367&'#"''325567#3'67#3#4—  4  wX: 5BÄÄÈ   "9H N 6)S nóÆ73#3#3#535335#ÒaOOiæ-)]Æ&&4€ÿçõÏ+/7367&''66'''665367&''´ $ " 4 &   = Ï   + ,y )4  " S)ÿç‚Ï-17367&''676''367&''676''C  %  " + #  ! Ï  ,,   )TÿêõÐ'-3;Q7367&''667367&''66''67'6#5##53533#&'#5'67#z   I   `  K In<@6&! %4Ð    &     8"#,<8 bÿéòÎ #'<LPTZ7'2'6'&''&'3#735#3353353533##"''3255#'#67'7''535353&'ã 6NA. (    ††'*"   "3  !!!(Î    -3@  <>$ n   ÿèïS73'73#&'#'67#536t/  $X@ HO AQZR  01  )WÖÈ 73#735#35#35#)­­………………ÈqO-,NÿéíÐ$(,7&'73#'65535##"''3255##535#35#¡5q]]m  =====¹  <<: 09Z*;i  -‚"2}ÿéóÏ06<7'6&'#"''3255'67'67676767&''67&'Þ #* %(       "8 Q  Ï QA  ; & 4! 4ÿéóÎ#)-17;Oj733#"''3255##5#'655'667#35#33535#73573#"''3267#'67#3533#3##5#535#'6[   -P    (( Η )$ )1;  .Gp9 !, "A 11 ZÿêóË +27&'7'6'&'3673#&''67&'67#367¥J ]  .Q. ' '<Ë( N &  " ÿëyÅ %*.733##3#5##5'67#537#5365#3353535#X 6@(  !,&((Å+, Z C + })]ëÆ'73#3#3#535#535#73#3#3#535#535#c*&&)f+##'qb)%%)g+##&Æÿè÷[%+73673267#"&''677&'''67&'LA+0I"&  '$ !%  < ±F.%)   H  " oìÈ'73#3#3#535#535#73#3#3#535#535#c)%%(f+##'qc)%%(f+##'ȈÿéóÏ 73533#3#67&'7''67#535#$$$,3 #($,$«$$46 $+ 34ÿìñE73#3#3'73#535#535#Í\TT3  ÝeTT^E ”ÿêõÐ'+73&''67&''667##5##535#°.      1&&&Ð "       W` aA/ÿíðI73#3#3&'73#535#535#Ì\OO()àeOO\I IÿêôÍ17=73#35335#533#335#535#535#533#535673&'''6k, ¤ 6 . ƒ>>MK\ ‰ f ^ÿèòË %+17OU73#735#35#35#73#735#35#35#&'''67'67&'373#&''67'7#67#c??8@@-  O  ( q*V  1 #)!P -ËcG ( ( FcG ( (           ÿêð773#3#3#535#535#&'#ÔaU &ßeTT_›7  *ÿìð;73#3#3&'73#535#535#ÔaVV2 àfTT_;   ÿç{Ï-73673#3#'67#535#73#3##5#535#536  Q ¤8<% #28<8__8kÿêëÅ ,73#735#33535#33533#"''3267#'67#536}kk,E,6: ' - (%(Åa9?.@"*@2 ÿçóH 7#'67#53##¥B7 1Aæ:5? 0KqÿéóÅ2733'67#3#33#"&''6735#53&'767#~h& ,, "  6B  OÅ +3 !C h  ,QÑÒ 73#53635#35#_h¥%~~~~Ò gg 7AÿórÐ 73#5##53635#35#8/88888е »^<‰;+ÿè×q 73#5##53635#35#qW„=.„„„„q s s2B-]ÑÐ 73#53635#35#i\¤4%}}}}Ð ^^ /8ÿïðN735#53#3#3#535##R^Ñ_SSgàeR'-iÑÐ 73#53635#35#g]¤2$ÐTT +1)ÿéÕg 73#5##53635#35#`i†- ††††gg h,9`ÿèõÑ#'373#3#&'#'67#5367#53635#35#3533##5#˜=6K"   +'LLLL& &Ñ U    U -3k''ÿÿSÎ 73#5##5365#5#-Î ¤ ®Q22D33 =ÿêñÐ#'=JPV\b73#3#'6553&'5##5#35#3353353267#"&5536'33#67'&'''67&''&'¦=.+‡J.    a##m \  R   Ð 0 A1 3:Y2 '    1      JÿêòÐ#'>KQW]c73#3#'6553&'5##5#35#3353353265#"&5536'33#67'&'''67&''&'¯8*'{B*  Y  g  U  N  Ð 0 A1 3;Y2 &   1      WÿêòÐ7;?CGKQW]c73265#"&55#3#67'5#'6553&'73#3#6'5##5#35#335335&'''67&''&'ß  # >4'$$ '  P I b   6C @2 2 5B] *" .   *$ L22> RÿéõË 59=AY_73#735#'3#735##367#533#7#5'75#'665535#35#75#73#&''67&''667#«@@Y??|{%"5   -4%    Ë--)%  >J 4( #GR*+ g!      &[ÿéóÍ73673#3#5##5'67#35#g.JOY@  &).@@ s_&/ƒ?oÿéîÈ&*7#"''3255#&''67##5347#53#'35#î  #   #8+d')>>ed  M  %j| NN '*\ÿèòÏ%+177&'3673#3#3#535#535#'67&''4''4'“  D  &5//7ƒ7//5  u  Ï$p  ÿèfÑ73&'73&'#5'67#!    *?±   pc ? ÿéTÑ73&'73&'#5'67#  .° )  f\ @zÿéëÏ 73#5#533533×]š±¢ÄÄnÿõòÆ7#53##5'67&'3#«6{/ $8Mƒƒµ ˆa&+##/,+‚fÿéôÈ 73#735#35#3#3##5#535#wnnGGGG{5====2ÈZ49733ÿéiÐ73#&'#5'67#535'6`    #',Ð&  mj!#/  ÿçôI73#&'#5'67#535'2Ù (0fL". 9'#: 1$PeP]I/+  \ÿçõÑ"@FLR767&'7&''667'7''6'67&'7&''674''6'6'63'6–  +0;  C   ( '*) <+ +-!= =2'X SÑ           2 (‚ÿêóÐ7'673#&'#5'67#53±2* '*$ '.°  *6+ij+#3\ÿçòÏ#'+/5;73533533#3#3#535#535#35#35#33535#335'67&'i$%A5{3>#-$$ 3"U 3"E $F ¹]]`;*   \ÿíóÏ,048<73&'73#3#53&'#7#3#3#3#535#535#735#33535#335e;9#“#X9-~6;;A—C::5""5#X""5#¼    M-, OÿçôÏ #>K73533533#735#33533535#335335#3#3#67'5#'6655&'76d')‡'b'|qq~Y   }   0 ÁI,+(  - 6# 6K  (  sÿçôÏ#'-373533#3#535#735#3353#735#35#35#'67&'+,,77+,XllFFFFFF  5à +   D_C ' ' !      Uÿé÷Ò!%+17=EIM733#5'667#35#33535#335'67&''4'7&'#5##535#35#Š:$… %72%%8'_%%8'g YW  OOOOOÒJ; &       PP %  ÿé[Ð7'673#&'#5'67#53. #    ­( me%.wïÐ73#3#53&'#53&'367#~Y&>Þ=$Y 9PÐ   A bÿéôÏ1767&'7&''63#3#&''67#5365#'6œ -2M*;5) ) 5 1 16 Ï! ! )! %  ]ÿèøÑ#'+737#"''3255'67#5353635#35#35#›6  (?9&VBBBBBBÑc  ;  %"!s 3<: ÿè€Ñ .273&'73#3673#53&'#53#67'5#'66735#$ )c nX  44º   f::,  ;!  ,rÿéøÑ 5973&'73#3673#53&'3#3267#"&55#'6765#735#†$ )c nX   44º   ,:2  8   ÿè}Ñ,0473'73#3#53&'#367##53#67'5#'6'35#35#()o)]  ::::º    wNN! / Q.xÿé÷Ñ37;73'73#3#53&'#37##53#3267#"&55#'676'35#35#ˆ()o)^   ::::º   wNN#  )  @.ÿëæ~73653#"''3267#'67#OcO4%GLe  R(7'6 @ÿçðy73#3##5#535#Ç[ggeeXy&FF&ÿñî~73#3#5#53&'‡]¤›¯!d~ M` ÿéï€73#3##5#'67#535#5#!Ä,66L3 +36&„J€)HH2%))))ÿìò‚ 7#3#53#735#ç¶ÁÕ;qqII‚p–)Cÿêñ“%+73533#3#535#3533##"''32655#&'!VRRfâhV“(( “% † 9& #  !eâÏ7335#53353353#353#!CO;9MBÁ‹!+##-!)hÿèõÉ'7#3#3#3#67&'#67'5#535êWNNMM`7   2É   EN Zl eÿéòÐ!048W[aek733533##5##5#53##535#35#7#"''3255#535#5#3533#3#&'#5'67#535#35#&'735'6ƒ,, -z   0E'&&##   "'  "  Ð  ;ƒ¾ ! #­ p; !  2 " 2 .   aÿê¦Ï73533#&'#5'67#h  Ÿ00 mf '3]ÿèóÏ6:>735#535#535#535#53533#33##3#33#"&''673535|..55--77;;1 122'(  >: ;$# w$jÿêðÄ%+57367&'#"''3255'67567#&''3353#5#~a   J btÄ  $ $ 6&  ¡Yÿê£Ï73533#&'#5'67#`   ¡..  fl&/hÿéõÑ,067#'733#67&'#"''3255'675#53'37&'À@F&2       > 6   5 W( &Ï =,     L* 86  3 $&ÿéõÏ(733#3#5353333#33#"&''673¹""%e ""    Ï!">>+ B ƒÿèõÑ 57&''6'&''633#33#"&''673Ñ  ,      Ñ   )1  +7!- "F bÿéòÎ +/3773353353#3&'73#3#3#3##5'6#35#35#n$"#)-''''0e7"""""Â''-    i  %& ÿìÎ :7'2'6'&''&'3#3#753#5'53675#535#'6t ,>3. "   >#++&" --Î     ,=  1*0 hÿèôÐ #)AG73673#735#&''67'7&'''63&''67&''667#j"F€\\F      2 9 $  &1 ¾ T4     6        "  ÿéèV'-377'7&'#"''3255'67'67672&'''6¦(4Y;  %'( #T $ M       1   aÿçïÇ'8KO73'767#533'67##"''3255'67##"''3255##5'#5'673&'35#i5  Uo/   & ,…   \T = '      .f  Ohz  %%  3 XÿçôÏ #<I73533533#735#33533535#335335#3#3#7'5#'655&'76h&'ƒ&_&xmmzWy   .  ÁI,+(  - 7% $7L  ' ƒÿêôÏ28>D733533#3#3#3#&''67#537#535#53&'#53367#7'6'&'¨ )&'/'  " %*$$' 1I   Ï555   % G ÿìXÌ!'-767'7''67'67676&'#'674'C     )&±># /+k\ÿêõÏOSY73533#3&533#673755#"''67&'#3#3#3#67'75#535#535#535#535#75#7&'f'#"     '0%# X  ·!<'/ ) *9 –pC  ÿþsÏ767'67'67676'6` #'1 )36§:  0)o  lÿéçÐ733#5#535#535#5367#'6—; 'gg__g= , Њ ## ZÿæöÆ#'-373#3#&''67&'767#535#35#3353655#335k…:351 1( #  248 3R 3 Æa  a>? [ÿæôÎ473#&'#5'67#535'2#533#"''3267#7#'6Û ;/  (5,6]   ,Î (( ‚2 &.hÿéõÎ673#&'#5'67#535#'2#533#"''3267#7#'65à 4# #66"b   #&#!Î)) ‚4 2 )kÿéñÈ 73#735#35#3#3##5#535#{iiCCCCu299::0ÈZ49733bÿêôÎ17=73&''67''667#3533##"''3255#'67&'= " &# "   1'533  5Y  Î     _8  4 !  RÿèïÏ'-735#535#53533#3#3##5##5'66&'a://779900@ŽyE.))%"  | M<>O(* #  eÿìöÈ!%)-173#3#3267#"&55##535#735#35#33535#335mn.77 #5-JJ ##6%[##6%È8_   k`=^ÿéïÐ *.27#5##53&'7#5'673#3#5##5367#35#35#ïd;5 "R!"(((((¸")  Šc$  ES# lÿïòÐ #'+/?73533533#735#33533535#3353353#3#735#3673#53&'t"#x"U"j€€rrNN$†!ÀO/1, .$   ÿæõX$*73'67#&''67#'6'&''6|X )9 6  A H5"%X .'2      RóÐ%+1773&'73673#3#3#535#535#'67&'7&''&'4 0 6]WWgßeRR[ ‚O  cº     6        ÿèeÏ &7&'7'63#3#3#'67#537#535#%@  >S"$ ! Ï  /$ ÿèbÏ '7&'7'63#3#3#'67#5365#535#"@ :L!$Ï  .$ dñÑ.4:73673#3#3#&'7#'67#535#535#53&'&'7&'N 7 9]UUg   › gQQZ7) = Ñ    F    …ÿéôÍ 7&'''63#"''32765#'67#Ï  R    Í4 42 1h  BV"L‚ÿêîÎ73#"''3267#'6##535#¤B  8 1&Δ.#w 0WfF5„ÿèõÌ#7'673533#3#&''67#535£ ##)!  # # (* $(**'453"03+ÿè×f 73#5##53635#35#zQ†E8††††fk k 0; ÿéõÏ 0J73533#3#535#3533#7'75#73533#3#535##53#3267#"&55#'6%##)f*% :5p)/;6# À6 D $/   ÿéƒË '+773#735#3353353#3#67'5'67#735#67&'ll   [qqh9   BB'  Ë:6 3   )   ÿé‹Ï$=EIO73533#3#535#3'67#3#3#535#3#3##"''3255#535##53#=#7&'200*g*2x e P2r-" ~  ((]08Å   #    " "  A @@   )ÿé×> 7#5##535#35#׆††††>U U- ÿé‰Ç73#7#5'675#35#35#75#{ $* &,,,,,,Ç83Ž R Z ÿêðw7#5'75#53#'35#35#5#î*œ%Í#yggggggXQ?4/ uÿñõÊ733#3#53533¹'')€ÊGnŸŸrÿèôÏ#7#5#'667##537333267#"&5í#0  ¤6$FHD?&8++GV tÿé÷Ï7367&''66''6® # - $Ï ?%&<8*S1#mÿèõÏ $(,73##"''3255##5#53535335#33535#35#ç  H -,,H,³q/  +HHq8'''`'''uÿïòÄ73#3#3#535335#‚o***+}2ÄDX}}¯ kÿìöÑ#)/CI73#53'3673#53&'3#735#35#&'7&''33325267##"&5''6²/q- # jjFFFF*;  F     Ñ   .H, )"       # kÿêøÒ LPT73&'73#'67&'3533533#3#3#67&'7'5'67#535#535#5#35#t46},  :E/  +  .#J¾       9 &   &   rÿéóÎ #'<LPTZ7'2'6'&''&'3#735#3353353533##"''3255#'#7&'7''535353&'æ 1F:(  "{{#&  / &Î    ,4B  >=% n   cÿéôÐ 1FLR7&'''667'7&''63#"''3255'675#'3#"''3255'675#&''&'Ç '!   !&?  -F?  -]5Ð & ! ,d   &d   &    ÿéíV73#7#5'75#35#35#75#&´$& $$kkkkkkV:> " & ÿêðX7#5'75#53#'35#35#5#î'' Í zffffff@;1 $ #  ÿé^Æ73#7#5'75#35#35#675# Q    Æ€:3‘""T"[! gÿéñÐ #)/7;?CT7#5##53&'7&''33265#"&57&'''63#53535#35#35#3##"''3255#î^7     O T  s ‰V|2   7½         '''4 aÿèöÎ9CQ7676767'74''67'67676767'74''67'673##533'3353'67#y    ;    &s &$š . " . "a22i'"#'% ÿêðE7#5'75#53#'35#35#5#î''É zffffff 50( ÿèî'+/735333##3#3##5#535#535#535#535#33535-DG##GPPee``KKDDaaDW555v        ƒòÏ733#3#53533zVVdã'0Ï,,)ÿéÖs7#"''3255##535#35#Ö ……………sq  -Š&8,ÿéÒi7#"''3255##535#35#Ò €€€€€ii  )€$4iÿõòÆ 7#53##5'67&'3#©;‚1%:M„„µ‰b&*$#/,+gÿíóÏ .73733#'67#7&'3267#"&55'67536iPQb     22R( 'F? K &"   N7jèÑ/73#673267#"&''675#'67#53673&'´ #S   ( < 2@E<Ï    1 #  fÿêóÏ#)/733##"''3255#'67#53673#3'67&'©00  3 %?E !"  V Š.I  D$$! #! ÿê\É7#"''3255'6555#&'6\     1 ÉÄ  7 )">xq_ B  ÿêcÉ7#"''3255'6555#&'6c    6# ÉÄ  8 )!?xo]  D eÿêìÐ 73#"''3267#'6##535#‡Z  M G0ÐŒ|#&XgG5bóÇ!7#53'67#3#33#"&''67w^Ð E??<530 µ  ( _ÿêòÏ-37&'3533#67&'#"''3255'675#&'Ì  R655      )6 Ï   ''1 #A  <#$*. mÿê÷Ð&,273533#67&'#"''3255'675#7&'&'w/44    $/]  <  §))1 "@  7 "(2:   A wÿê÷Ï,27&'3533#67&'#"''3255'675#&'Ô  N222     2  Ï   ''1 "@  9#0 ¤ÿéõÐ$*73533#3#535#3533##"''3255#&'¨Q -   -  ´  KC  ?  ¢ÿêðÏ733#5##5##5335#35#35#35#Á      Ï-ƒEE…1 Q eÿéòÉ FJNRV7#'6553533533##5##5#3#3#"''3255#7&'7''675##535#735#33535#335òn\&-      +%$8$ÉTE5 7>` B E 3 GW ' $  ÿéñ@73267#"&55#'6655Ÿ ""= @8 + ÿéÎ!%)-73#3#5##535#535'6655635#35#35#w %# ;%%%%%%ÎŽ/J: 5I<u79 ÿéðl"&*73'73#3#3#3##5'65#5#35#C1  VSIIIIW® &SDDDDDk     I    qÿèòÏ(733533##5##5#53&''67&'76‹&&L   Ï7 1" #. # ÿêyÏ'73533533##5##5#67&''67&'    $´""##0$!‡òÒ(77&'77327#"&''7&''#5'6 (  !< ):.'   &Ð       ' ÿèòc"'7#3#3#3#&'#7'5#53567ÖŽ‡‡ˆˆª&  <@3# ((l c    ?OqñÐ *073'67'677367&'326767#"&5'&'b'8J#)"'  (e Ð&  !     D „ÿéðÎ*.2973#3#"''3255''75##535#535'235#3357'75#á ''.   ,%%/%  Î;R   !Yi;Jk $ ÿéò>73#3#&'#'67#5367#)¯OdN=GQ ER]M>"$ [ÿéíÊ  $CNX7##535#35#7#"''3255#535#5#3533#3#&'#5'67#535#35&'75#36735—*€   )L%&&""    "% !  ÊEœá ' )Ì „E '  ; %&; 6   ÿèç¢ $CNY7#"''3255#535#5#'##535#35#3533#3#&'#5'67#535#3&'735#33'6735ç   I5555-G5555 >??77 % "+7> &8  ¢]: ,;º 2  $  2,   8ÿíóÏ "&.26:73#'63#53#3#'#3353533#3#53535#35#35#¯17 %[X,,9BB7»+*Ï  G‚3G#%<<<***** RÿìôÐ "&.26:73#'63#53#3#'#335353#3#53535#35#35#¿$*#PN$$3880¢%$Ð I‚4G#!===***** _ÿè÷Ò !%6?IV\bhn7&'6'&'67&'63#735#&''7&'737'77367'7733265#"&'&''&'&'7&'Ö  M  6  ;~~ZZI   D 3 1   % AÒ      -F ,    1J OL P%% %1)        Kÿè÷Ï $7#67&'#67'7535335#35#Þ? %@##. 98aaaa³f   EH ´(C[ÿèöÐ $7#67&'#67'7535335#35#Þ5  !9 ( 12RRRR²f   DH  ´(Cÿî~Î!%7#67&'767''67535335#35#wI  & $$5555°j8   ª-D:ÿèóÏ%=D73533#3#3265#"&55#'67#535#3&''67&''667#X:;;I0   $$G:0 A & ,#%3 . -  :¹    @        YÿèòÏ$<D73533#3#3265#"&55#'67#535#3&''67&''667#l200=*   <2*8  $) % %  /¹    C       bÿèõÏ$<C73533#3#3265#"&55#'67#535#3&''67&''667#s000<&    :0+6  "' $  " -¹    C       ÿèô£&>E73533#3#3267#"&55#'67#535#3&''67&''667# VSScB ' >& :fVMS 9 ?(+; 2# 5L“    ,      ÿé—Ï/37;?CGN735333##3#3#&''67'67#537#535#5#5;5#33535#33535#33567#-//08E " &&0--J-M0(& Ä  -   -  ) GS CÿêòÎ"(73533#3##"''3255#735#'67&'NDK¢z3   2PP o³I>  9#@ " XÿëôÏ#)73533#3##"''32655#735#'67&'g7>ˆ q/   /KK g  ³H? :$@ ' _ÿêóÐ"(73533#3##"''3255#735#'67&'k7<† k+   ,EE c  ·GE  @#B) !UÿéôÏ "7&''63#3#"''3255##5#Ÿ"% $!%0CC!  1*Ï ,++&6 ![[_ÿéôÏ #7&''63#3#"''3255##5#£# !#.CCx   *(Ï*,(%6 ![[fÿéòÏ #7&''63#3#"''3255##5#¨! "+DDr   +!Ï*+'$!6 ![[ aÿïóÐ #'+/?73533533#735#33533535#3353353#3#735#3673#53&'h'(…&a&y‘‘ {{WW#(’ ÁQ0/+ / #   zÿéöÏ 7#5'753'&'&'ôBECNLH Іt-:ÿçõÏ(0473533#67&'7&''67#33267#"&5''6673#RCG\'+7?*d   V+³!  !IR % U,0('bLÿèõÏ*.673533#67&'7&''367#33267#"&5'3#'3'66c9?F !  69._('¶   HW Zb`-0'SÿçõÎ"2673533#67&'7&''367#3'66733267#"&5'3#k78B  47* K'´  G5, &1S Wf^ÿîôÀ7#533#3#537#5337#7#"y–!C.'0¬\PPHHcPPfÿðóÁ7#533#3#537#5337#7#š%t%;' (®XSSEEfSSZÿéóÎ!17IM7#53533673#&'#5'67&'3'67&''667#353533#3##5#7#3+;  -      ! ",,“** )) Hb -1     %1((0PÿéóÎ!37FL7#53533673#&'#5'67&'353533#3##5#7#3'3'67&''667#Š.?  #0!!!   . $00Z!   “**  *+ I¡-,,-> -1   FÿêóÎ@FL73533#3#3#67767&'7'#"''3255'67'67#535#535#&'''6T@@@88EZ ()"    @ /I::@oB ½    ) #   Š    AÿéôÏ-C73'667#&''673533#3##5#'735##53533#&'#5'6i$"   C*+*?JMA* )% (Ï'.! '$'p#'@>#;ÿéòÏ )-1573353353#3&'73#3#3#3##5'6#35#35#P//—0 :<4444@‡ I33333Ã''-   e %%MÿåðÏ1767&'7&''63#3#&''67#5365#'6$' 7? ]3HB-0. / ?D" Ï! ! (& ,$ EÿæõÈ %73#735#333#&'#'67#5367#335[[66<{A0 58 3=C6† #) x4!*<<GÿèõÏ7&'&''67&'36”R +( '!2 /.  (&ÏP/5F@.&UÿòòÏ!73733#3#537#35#35#35#'33#ZHE T  0 %(  h  ["2   # + !     <    }  = #(^ÿéóÏ'733533##5##5#53&''67&'76))J "( 'Ï7 +( &OÿêóÉ"&*.373#3#3&''67&''67#537#35#35#35#67Y•E=TW  %!.%  -<XXXXXX  É g    g * ) +; QÿéïÑ1J[_c73673#"''3267#'67#3733#"''3267#'67#73733#"''3267#'67##53#"''32=#5#f3:   -* (     P      ,| VVVà   * $ % r eP  )  NÿéëÏ #';?C73353353##"''3255#'65535#35#'#"''3255#'65535#35#b&&…‰          Â++0{ %)S(@9{ %*S(@ PÿçóÈ %+73#735#3353353#735#35#35#&'''6X••,y††``````@! !  È<5oN-,#    fÿèöË  $(:?73#735#35#3#7#5'75#35#35#75#73&''67&'#367vkkEEEE#ŠC  .=    ËI, ( (OY456)     MÿïõÑ 59=M73#5##53&'3533533#3#3#&'#'67#535#535#35#35#3533#3#535#žGu=)$ ### ")%@’?Ñ ..5  "0>EîÐ;S7#&''53&'73535&'#6''67'67'677676767#"''32654'67##5¡ ###67    §¾D f  )2:    # ;"  r€ RÿéòÎ #'<MQU[7'2'6'&''&'3#735#3353353533##"''3255#'#67&'7''535353&'ã =WI5 /    ““+/'   '8 &&&*  Î    -3@  <>$ n   BóÒ %BFJNRX^djpv73#53&'&''67'6776767''&''67'6776767&'73#3#3#735#7&''&'7&''&'7'6''6ƒPq      ‡     !<<<<==r  ‡  ‰†‘Ò  :   *    * *            {ÿòëµ 735#53#5#535#X[o\\Xj8ÃALóÑ173'67#&''67#"''255#3267#"&55C.F ?%  "² 3 +" Ñ I=   + A  X ÿéõÏ-7#673267#"''67&'#'6553533&'7ñ;    r„ °3,&5"" !'&7@I=/ .:T  bÿêëÅ ,73#735#33535#33533#"''3267#'67#534urr.K.9> * 2 , ,/Åa8=.@$+@1˜ÿëóÐ%73#53'67'7&''67'67676Å W# "  "  Ð@P,% -*(`ÿéñÆ%+17=73#3#3#535335#3##'3267#733&''&'''67&'i†900;:!l q\" 2  +Æ''5g:$; &  —ÿëðÆ ,73#735#33535#3353733#"''3267#'67#¤II  '  <*   Æb9@B @+5 , ÿë_Æ +73#735#33535#3353733#"''3267#'67#KK  )  <)   Æb9@BA 15 ,ÿéó{73567#533##"''3255#f€ž#jj  f@ / +¢íÑ 7#5##53&'7í³bÁ ¥íÑ 7#5##53&'7í³bÁ  ÿéð@73533##"''3255#&'ž00   ž7 '    mÿéðÏ7673#3#"''3265#3#y_``  cXX€<"/[$@!+ÿèÕ] 73353#5#533v6•8]S2TF2 ÿèîu73#3#5##5'67#35#Ú‚ Šz /@8zzu\ H [+ÿêî’#)/733##"''3255#'67#53673#3'67&'}LL   Q5B}ˆ 71+ )!![,  '    cÿéòÏ-C73'67&''667#73533#3##5#'735##53533#&'#5'6     0  .790  Ï '/    (!(q#!:9fÿéòÉ /73#735#3353353#5#535#535#53733#3#3##n$H,,##&&#))%%**É@5š$$LÿêõÎ@EK73533#3#3#676767&'7'#"''3255'67'67#535#535#'''6Z>>>66CW  )(! %   > /H99>l-A ½   ) #   ‹    JÿéòÏ +/3773353353#3&'73#3#3#3##5'6#35#35#`')‰+ 26////:yA,,,,,Ã''-  i "$& óÏ!'+/73533#3#535##53#3#53&'35##363#735#eggYÄWe2§Aå?jQ>~±±‹‹Å G))  ) LÿëõÏ,N7#5'67#53533#&7#5'67#53533#&3'67#3#33#"&''675#…    F    ‚%00$1#  >+#   &#  !  *L@ÿëõÏ,M7#5'67#53533#&7#5'67#53533#&3'7#3#33#"&''675#}    J    Œ–*55'!4% C‘,&   '&  !  3  KOÿéõÐ%)73#&''67&'#53&'67##5##535#¢@'*") &AHQOOOÐ     LDa aB0LÿêõÐ ?73&'73#3#735#3353353#67&'67'5'67#53&'WA?– ƒƒ$+B>  5   "5Gº >1  /   LÿèðÐ "(.4DQ73#53'3#735#3353353#7'6'&'&'''667'5'6767&'œFŸC/ˆˆ&:O!r_(!   *5   /Ð )/'11         ) ÿèöÏ-373533#3533#67327#"''67&'#535#7&'888D::     šB8¾  Á ++;"#)6+ #" #P  IÿéòÁ#73#3#'67#5367#33267#"&5jv4AD @; .1.=  ÁN0,AVL   Jÿë÷Ð#)/@7373#3#5367#35#335335&'&'''6733267#"&5WD@DDŠ1@(0  G  n#   ¸ EE J!!!!!9  # - CÿíòÏ!'-=C7367#53673#3#735#3353353#&'7&''33267#"&5''6[+8<DIG†&|››K  ER   – @=  %  & 3ìÒ$73673#3#5367#35#35#35#'33#c\aK†'^Faaaaaa>ºÌÅ ZZ% ! " 9^YÿîõÆ 73#735#35#3533#3#535#lyySSSS699DœF6Æj=EO&&[ÿéñÏ67'23#5##536'&''&'67#53&''67&'á 8QC2 !n_'     Og )  Ï ((  h   cÿéñÏ67'23#5##536'&''&'67#53&''67&'á 4L?/ gZ$     I`  &  Ï ((  h !  ÿèäe)/573#"''3255'675#73#"''3255'675#&'7&'`  #*1$Mjb  #+1%OS  {  ei &i  '   ‰ãÍ #75#53#5'675#53#5'6'&'7&'aJ^"(,’J]"(+‡u ¨C C    ŒãÌ $73#5'675#'3#5'675#4''4'…^#*-$Mn] %)"LQÌ< <     gÿë÷Ð#)/?7373#3#5367#35#335335&'&'''6733267#"&5p7589w*4"&  =  ^     ¸ DD J""""":  # /  fÿíôÏ &,<B737#5373#3#735#3353353#&'7&''33267#"&5''6x",05:9n  d~~@  8 B   • > =  &  '  IòÑ%.26:73#3#3##5#535#53&'#53&'367#'6553'#33#735#¹./**,,0 +'{Z552EE##Ñ   8 % $29(7 EòÑ#,04873#3#3##5#535#53&'#53'367#'6553'#33#735#¹. /**,,0 +({Z552EE##Ñ  :  %" %3:) 8 PòÑ%.26:73#3#3##5#535#53&'#53&'367#'6553'#33#735#¹./**,,0 *'{Z552EE##Ñ   4 # "/7&4 UòÑ%.26:73#3#3##5#535#53&'#53&'367#'6553'#33#735#¹./**,,0 *'{Z552EE##Ñ  1    -5%3 YòÑ%.26:73#3#3##5#535#53&'#53&'367#'6553'#33#735#¹./**,,0 *'{Z552EE##Ñ    0   ,3#2 aòÐ%.26:73#3#3##5#535#53&'#53&'367#'6553'#33#735#¸.0++,,0+'{Z662DD$$Ð    +  )0 . iòÑ#,04873#3#3##5#535#53&'#53&'37#'6553'#33#735#¸. 0++,,0 + '{Z662CC&&Ñ   * %, ) ^ÿêõÑ LPT73&'73#'67&'3533533#3#3#67&'7'5'67#535#535#5#35#b;<Œ3  @ M!"2  .  0"R!!!¿       : "   " _ÿëõÏ =EIM73533#3#&''67#5365#73533#3#&''67#537##5##535#35#g  F    6KKKKK»       Rn n)@ dôÐ3HLP73#&''677&''667#'3533533##5##5#3#"''3265#'63#735#¢@    &”S  H  77Ð     1  # TÿéöÐ $(,06<BH73#3#3#535#535'65##5##5#35#35#35#&'''67&''&'wg ‘]       f ] Ð.// 9......A/////I  YÿèôÈ?GOSW73#3#'67#'737#'67#'7367#53#3#33267#"&5'37'#5##535#35#`@  j  C  B  [VVVVVÈ &   H         _ _!1 ÿéöd 73&'73#3#3##5##535# gqë´´³³µ’’’T ** aÿèöÏ<@D73673#&'#'67#3533#3673#3#5##5'67347#535#35#35#o&H"  "   !5GD -1DDDD¶     ] J X2 [êÊ %+1773#735#35#35#'3#735#35#35#&''&'7'6''6ˆ[[777777~[[777777š c N  b  ÊQ9  :Q9         bóÑ $(DHLP73&'73#3#3#3#5'65#5#35'3'73#3#3#3#5'65#5#35—$&%g,–$&%g. Ï    2    T    2     ÿèöT 73'73#3#3##5##535# ojë´´³³µ‘‘‘G    ' ' ÿèõE73#&''67&''667B‘"&2G-+: .# #&E      ÿéó[ 73&'73#3#3##5##535# elç´´³³µŽŽŽM  (( šÿéòÐ"&*73&'73#3#3#3##5'65#5#35#¹ 2Ð$%$ ‰ -.$$5%%4$ ÿéaÐ"&*73&'73#3#3#3##5'65#5#35#( 2  Ð$%$ ‰ +,$$5%%4$±ÿéôÎ 73#53&'3#3##5##535#ÔC44445Î >TU5%YÿèõÏ7&'67&''67&'ž &" '& %'&) Ï";2)IT.4EOÿéôÏ!'=73'33#673267#"''67&'#7&'3533533##5#'67#XU22     V„  v  ¨''?,@%#,F:  l,,,,55;/YÿñóÀ %73#3#535#&''6'&''6a=?”A@z "   % 5Ñ "-/q"1 "$' !ZÿêõÇ/473#67#5'75#35#35#75#73&''67&'#367Z[  4F     ÇŒ,&š%%^(a"fK8*E1!%-gÿê÷Ç/473#67#5'75#35#35#75#73&''67&'#367iR .C    ÇŒ,&š%%^(a"fM6 *F4+$NÿêóÇ.373#7#5'75#35#35#75#73&''67&'#367Na  7J      ÇŒ,&š$$]'a"fN4*E1!%-XÿéòÏ%-159=73&'73#676767'7''67'67##5##535#33535#35#XD?N  "!!$  8@5[$$7$[$$7$$º   &     Zff$6 ÿéRÏ73533#7#"''3255'675#     ¥**2 G  8;PÿéôÐ (,07#5##53&'7#5'673#3#5##537#35#35#ôrC8  a))4&4444·!(  ‡g $ES#PÿéôÑ $*06<H73&'73#7&'7''67'6776'&'7'6'67&'33##5#53[A<“W   E   cx  6IIEE¹            "33TÿéòÍ37;?7&'#5##5367'&'3#3267#"&55#'67#735#35#35#  ^m\  V  a   3 ,======Í '1 1+ ?\ - B $ $ Gÿé÷Ï >EV\b7#5##53&'733&'33&'#5'67#&'&''62673##"''3255#'67&'ìs>5# E1   V =    Xy‹?  9 a »   $*     $     Q& # 9öÐ=735673#3#35335#533#335#535#535#533#&'#'67# ((((8-//-/%%$$(:8".P- #6sQ   #: 2  W   OÿæóÐ#'+/37QUY]7#'673#&'#3#3#535#35#5#73#735#3353353#3267#"&55#'67#735#35#35#¼9 ,C##=MM  &   5 -![[[[[[µ  !&`)). *N   %7   ;ÿêòÐ $(,048SW[_73#&'#'6'#3#3#535#35#5#73#735#3353353#3267#"&55#'67#735#35#35#¦:    N++CTT  ‹Š(   # 8#ffffffÐ   &_' (, )Q   8 ^ÿêòÐ#'+/37PTX\73#&'#'6'#3#3#535#35#5#73#735#3353353#3267#"&55#'67#735#35#35#².  A !!6FF  rt!   2 *PPPPPPÐ    &_' (, (S    :   \ÿéóÉ *HLPTZ`f73#735#3353357767'7&''67'6773'73#3#3#3##5'65#5#35#'&''67&'dˆˆ'i    6 9 "#%!É7U*   a '% KÿéôÒ6Idhlp7&''7'76'3#3#35#535#535#533#5##5356&''67&'763#3267#"&55#'67#735#35#35#­   . k+ €@     Dr  3NNNNNNÒ    O%%H"      77777·;h  +#3QÿéôÎ,B73'67&''667#733#3##5#5335#533533#&'#5'67#y%    N"!!m<?3! !& '-Î %.  >!$$(!o"(BA$ i˜îÑ 73#'6ƒ^g Ñ sÿêöÄ+73#3##'67#'7367#33267#"&5}t)-  *, 9@Ä&#(,86'fC &  GÿéóË 48<@X^73#735#73#735##367#533#67#5'75#'65535#35#75#73#&''67&''667#YBB!!>BB""6„)%8  ,=$        Ë--)% >J 3) )0GR*, j!      #  XÿêíÉ73#735#3#"''3267#'67#vqqII€  6: 0!ÉR,WT!,@$ $,E>----55=1KÿéòÄ )7#5##5'67&'3533#&'#5'67#ëj-@M;A6% #% )1Ä10! : (,PN('UÿéóÄ )7#5##5'67&'3533#&'#5'67#í`)= F5:3" $ ',Ä10! : )&IM&& ÿèõz 7&''63'67#53'19 68(: O" 5s? z    ZÿèõÒ -7&''63##53#5'357#"''3255##5¡$ # ) 2BB:`  Ò(+'$psh K::J  4o€XÿéùÎ%+D73673#3#&'#'67#5367#7&'7'6373#"''3267#'67#g1 >EY . $ )  j  Y#2   # , #     <    x A (+"CÿèñÅ573#7#5'675#35#35#675#7##'32654'7##5G[   !!!!!  Å…4/“""X%] x@"! DÉÚ8ÿêõÎ #06<733#3#537#53&''67&67##3#"''325''67&'a )] C U    &V   G  Î#5d3,)D(`  K $  EÿéöÏ !.4:733#3#5373&''67&'#3673#"''325''67&'j$T0U     j  C  Ï "F`4!-T@''@M`  K $  <ÿéöÎ*/5;733#3##"''32655#5373&''67&'#367'67&'b##+#  ) 8U     Œ J  Î$f bF`4!-T@''@^ %  EÿéöÏ).4:733#3##"''3255#5373&''67&'#367'67&'j$  $0U     C  Ï "f  bF`4!-T@''@^ $  KÿéöÏ).4:733#3##"''3255#5373&''67&'#367'67&'l#  #/T     }  C  Ï "f  bF_5"1O@'$CY  UÿéöÏ !.4:733#3#5373&''67&'#3673#"''325''67&'x!M0J    _   B  Ï "F_6!0R>&$@M`  K $  cÿéòÎ48<73#3#"''3255#7&'7&''675##535#535'235#335â  55@  . #)<55@$!!3#Î=R  ;Xj=IhÿéóÏ'+73533533##5##5#3533#3#5##535#35#h((;<<.J.; JJ¸3W XW'RÿèôÊ #)D7'6553##57#3'6'&''67&'3533#3##5#535#'6{†,,`` O 97))2244 ”/E5 5@_6::$     \ÿêïÏB733673#5##53&'733#735#35#53#3##"''3255#'67#'Ÿ e SS//=U†    - $)Ï" **  ,; !UÿèðÎ3K7'23#5##536'&''&'3'67#&''6#'735#53533#3##ä 8PB3 pd (     ; 1   ]% Î       1Y"C  A%,,*‡ÿéöÎ#)B73673#3#&'#'67#537#7'6'&'3653#"''3267#'67#“+0?      K  ;  #     ž  8   y A 00']ÿéòÉ /73#735#3353353#5#535#535#53733#3#3##f††'L00((,,%,,((--É@5š$$YÿéõÏ,\7&'#5'63&'3&'&''67&'763353#3#"''3255#67&'7&''67##537# !$ Y 2     IR/B  7   !! 52Ï       ((7;  &  @PSÿéõÏ -9>BSY_7#5##53'73'67&'767&'767#'6&'73'673#3##"''3255#'67&'íj>5!-        g  4 .!H33%z7  0 U º  4      & F /' $  VÿèòÐ #)/5ER73#53&'3#735#3353353#7'6'&'&'''667'5'6767&'¤B–>,$:MkZ'  '4  ,Ð )/'11         ) ÿêøÐ7'673#&'#5'67#53Â$      ¯  *:'f]5 PÿéóÉ *HLPTZ`f73#735#3353356777'7&''67'6773'73#3#3#3##5'65#5#35#'&'&'''6\)n   : = %$É7T,   b '%  DÿçóÐ$7'23&'7333"&&#"'663267#â >>J4   0.5J>‡  ±f  aE, (<D !"DÿéóÏ'/73533#3#535##5##533267#"&5'3'66NBDD;ˆ9B™rZ   0" ¸<2 !3!B F+' !FÿçîÐ673533533##5##5##5##53653#"''32765#'67#R!0##0!œs4= ,= 40±%))3  F &>.SÿèîÆ(,7#"''32655#&''67##5365#53#'35#î  1  /E7~45VVdd L  #i| MM ('‰ÿìöÍ733#&'#67'5#537'6šD% %  ;Í[C!S]  hV* JÿôñÎ .7'2'6'&''&'3#3#535#535'2à =WHA 9  "  … "KKA’=HHBÎ   &%%•ÿëóÇ73267#"&553'#37#3¨ " W4!NH ÅygUUUDÿñôÐ!%73673#33#535367#35#35#35#35#QDCF>°/@ ZZZZZZZZ» ““ :998BÿéñÐ.5767#53&'73#&''67#&'3353#5#'67635#q qEI +1!' "MR OM¨   ?v/  GÿòñÏ $(733#5'6367#35#33535#3353#…A *‹ +6 3 ((<)e((<)‰¨¨Ï  j\# 7FHVÿìòÈ!%)-173#3#3267#"&55##535#735#35#33535#335`u2;; "':0RR'':(b'':(È8_   k`=”ÿéóÇ73#3#3#3##5#535#535#535#—X" &&%%"Ç>>AÿéõÏ;AG73673#3#3#&'#3#'67#53655#5'67#5367#5367#&'#&'M8KONTk  -EK9 /5=$   ,"(4}A@#!¾     "  T ;BÿéòÏ *.2673353353#3&'73#3#3#3##5'6#35#35#X,+- 681111<€E11111Ã''- i "$&DÿêóÏ'-3;Q7367&''65'367&''657'6''6#5##53533#&'#5'67#¾    M    5 M™{?G=-&$/<Ï             C#$*=6 8ÿëõÑ #/5EKQ7'2'6'&''&'#53#3#533#535#535#&''33267#"&5''67&'Ú 9SDB  0 5>?K§I:‹‰vrrx= #     Ñ       $ @  6     !  DÿíóÏ3;?CG73533533##5##5#3533#3#67'7''67#535#3#53535#35#35#O"8##8"7::HW !$ 8?6J7ƒ¯'&Á   T222!!!!! FÿéòÎ #'=MQU[7'2'6'&''&'3#735#3353353533##"''32655#'#7&'7''535353&'â A^N: 2    žž.4+   +=   ***+  Î    -3@ <>$  n   AÿéóÒGLgkos7&''67&'76'3#3#3'67&'76735#535#535#533#5##53563&'3#3267#"&55#'67#735#35#35#ª     / &  %- ‰&9w   "6SSSSSSÒ            N%%GKU  = ! ÿóQ¾ 7#5##535#35#35#Q¾À Ë7%[$\&ÿóK¾ 7#5##535#35#35#K¾À Ë7&\%]&XÿíôÏ5=AEI73533533##5##5#3533#3#67&'7&''67#535#3#53535#35#35#c// 022>K .4/@0t›!"Á  T222!!!!!FÿèòÊ #-:GS73#5'675#'3#5'675#&''&'3&'73#&''67&''63533##5#œM!;QI!7_ P HG¤% ^  sKNNKÊU  P  8      $ %%`ÿèôÉ #-:GS75#53#5'6'5#53#5'67&''&'3&'73#&''67&''63533##5#Ù,>30B7 C ;:Š  V  `>DD>žK P >       $ %%tÿíðÏ &73&'73#67&'7''67'67676u42{V'&# ..% ²  U! ") #%.+  UÿêöÑ #/5;KQ7'2'6'&''&'#53#3#533#535#535#&'7&''33265#"&5''6ß3K>:  )0/r0>Š95}}jeej@ G  T !  Ñ       % @  4       dÿèòÏ %173&'73#&''67&''63533##5#l68‚  F  \====´     '  @??bÿéóÏ/73533#3#535##5##53'66733267#"&5h8;;1t/8…^! *  ¸<1 2!+% &F iÿéóÉ'+/37;?73#3533#3#3##5#535#535#535335#735#33535#33535#335n|6 499662 4!!3#V!!3#I&ÉN"".,[bÿéôÑ"&<I73#3#'6553&'5##5#35#3353353673267#"&5'67'533´3&$j6#      Ñ =<, 2;Y =,3    !   V lÿèôÅ !%)-3973##7#53737337#3#67#3#735#35#35#'67&'î m -ppJJJJJJ  BÅ dG ( (     gÿêïÏ  $(.473#5##53'3#3#535#35#33535#335'67&'¯9`9Q!0r0/L/=  > Ï +,2[[7;+    dÿíõÆ (-=7#3#3#535#35#5#73&''67&'#3673533#3#535#¬C!!8?     o277?=2Æ6ˆ)>>w8&  !,dÿéòÏ *.2673353353#3&'73#3#3#3##5'63#35#35#u v)-''''0e3"""""Ã''- \ !$&jÿéóÏ!%+1773267#"&5536#55#3535#'33#'67&'Û '6 ( 1$0-UE444440qƒ% C Ï   '4``  ' 8^    tÿéóÏ 4A73#53635#&''67&'73673267#"&5'33#67'Ÿ7q%KK8        >$$ Ï ^^ V<    @!    U)  tÿîòÏ '73353353#3#3#735#3673#53&'{p}} jjCC ~Â**/<2  iÿîóÐ'+/373533#3#67'7''67#535#3#53535#35#35#',,9A   */(4'h Š   º   eCCC22222]ÿéöÏ287367&''667'3#&'#5'67#535'6'6À    %     !(  Ï?  "A$.5EA;) a\!-$) eÿéòÏ573533#3#3#67&'#67'5'67#535#535#r/44//93  . '6**/¼  U @ 6#cÿéõÐ 173'33#3265#"'&'#7&''3#33#7'7537nJ'' Jr  \88%- ‘??5/8&[D 7$, _[fÿé÷Ï$173533#&'#5'675#&''67&''6q466%  &4 Q ´H3%ON"*K    "  lÿéóÐ $(733#3#53533#"''3255##535#35#¨,,8‡> IIIIIÐ..#q  /‰&:iÿèöÎ%+73533533#3#535#35#35#35#'67&'r3‰&333333 %A °nnCD/  sÿéòÑ,0473#67&'7&''67#53&'#"''3255##535#35#´2A *.)9C  AAAAAÑ      bi  *€$6cÿîôÏ&*.2673#3#3#3#535#535#535#535'25##5#35#335à ;91s/898#&Ï&$$&_&&&&6$$$eÿëóÐ 15973&'73#33##67&'#67'5'67#5#5;5#35#e=:j*  +  !DDDD»  "!  !3 (  $ !2gÿéóÒ3M73'73#6773#5#67&'7&''67##5367'67#3533#3##5#535#'6i7?D   2. $ !B 00088;;#À    )  ) b ## eÿéõÐ '-39?K73#53&'676767&'7&''67'677'6'&'&'''63533##5#¯6‚8     :  i  x Q   ;<<;Ð  R )     (   * 22aÿðóÏ)73533#3#535#7&''67&''6q055;‰;0  W   ;””''•'5)UÿðóÏ)73533#3#535#7&''67&''6f4;;E™A4  _   ;””&&•(4(DÿðòÏ)73533#3#535#7&''67&''6X:AAL¦G:  !g  ;””&&•'4(=ÿèôÊ$*E7'66553##57#3'6'&''67&'3533#3##5#535#'6j –22oo Z%A=11::<<”/D6 1$_6::$     LÿêòÏA733#3#535#5335#3353353#67&'67'5'67#53&'˜??9„8??%%+@;  1   #1BÏ>>M/  1  +öÐ>OSW73&'73#3#53'#367#733#3#3#3#3#3267#"&5535##"''3255##535#35#)+r($PTTMNJJJJFF# ,' MM 99999¾   %    WI [ # `ÿèôÅ"&7#3#67&'#67'5#'6553#êdh* '   RRÅB  !$Q]  hD3 6?^*_ÿéóÐ .5;73#53&'#5'673&''67&''667#&'±5„9  20      ! Ð  *„b))     $*   bÿêòÎ@FL73533#3#3#67767&'7'#"''3255'67'67#535#535#&'''6j677//;M ""   6 )>116b< ½  ) #   ‹    bÿéøÑ(Mm777;265##"&55'75'75'6773267#"&55'75'75'6'7767'5'75'75'6Ù:a;*e)XÿèôÑ<@IMQUY73#3&'73673#33##&'#5##5'67#535#'6553&'5##5#35#53533535335²5u&!    :%/Ñ   /@@*4* 4<[U)UÿèôÐ (,04Q73#53&'3&'73#3#3#3##5'65#5#35#'677'67'67'67676§EœB B &+ !     Ð "  o *+d+E$ Yÿì÷Ñ#)9?E73#3#53&'#53'367#3#735#35#&''33265#"&57&'''6¬8!%4/*qqKKKK&   "]  e  Ñ   5 (K- ) "  "  '  RÿêòÐ6:>BFJPV\b73265#"&55#3#67'5#'6553'73#3#6'5##5#35#335335&'''67&''&'ß  %  C5(&& ,  S L b   6C @2 2  " '`'N,,,Ð   (7TT5$~ÿéíÏ7#5##5##535335#335í-//¡wVVw..P===kÿéòÏ)159=73533#3#535#&''67&''6#5##535#3#735#r255;‡92  O  !OOO@@À<<      ?l lQA )  cÿéõÒ $*06<DHL733#5'667#35#33535#335'67&''4'7&'#5##535#35#‘6 w 2. 1!R1!]  s  43/FFFFFÒI=  '      OO &  ÿéXÏ$*73#"''255#'67#5353635#&'&'3     ϳ R<. -3L[; I YÿèòÏ $(FJNRdj7'673&'73#3#3#3#75#5#5#''673&'73#3#3#3#75#5#5##53&''67&67#°  BW  Az   *"( ? —      K?      K+    ÿéô}73267#"&55#'655&'¹   M21  }v   i/&56) qÿèõÊ 7&'''667&''67&'Ê"   !  Ê#) (% 1 &#+#!!! )nÿéðÁ$73#3#"''3255#&''67##537#n‚75  #   4:Á"‹  t 1’¤"bÿè÷Å'7#3#3#67&'#67'5#'6655ëb\\e& '    Å  $S_ lD3 0%^_ÿèíÊ'73353#35#53353#5##535#3#5#'65m&[(([% Ê75FF57áKHY YH0 /EqÿìöÈ!%)-173#3#3267#"&55##535#735#35#33535#335yf+22   1)BB1 Q1 È7^  ja=mÿéòÎ37;73#3#"''3255#7&'7&''75##535#535'235#335â 11;    (  $700: 0Î=R  ; Xj=IiÿîòÏ %73353353#3#3#735#3673#53'vtƒƒ mmGG  ‰# Â**/<2kÿéðÑ K73&'73#&''67'763353#3#"''3255#67&'7&''67##5367#k59…U    HP2?   2 0/½    11CI  2  PbbÿêôÑ S73#53&'3#735##5##553#67&'#"''3265'674''67&''2727637277¬:‡9(mmGGjj^&    ((## $ Ñ(( "(          ÿôRÎ 73533#67&'75'675#735#335¨&&a4  9???dÿèòË %+17NU73#735#35#35#73#735#35#35#&'''67'67&'373#&''67'7#67#i<<5==*  K ' l(R  . !' M +ËcG ( ( FcG ( (            ÿçñÓ7#'6553&'7ñÀdÃJV,$US ÿèôh$7'67#53&'73#67&'7'=@ W_iX  C!<)#      !:%  /NäÐ &73#"''3267#3#"''3267#536&'mL   u¢   ¡8Ð (/# O   ÿéòQ&7'67#53&'73#67&'7'F;Qh ^b   !L'(     9    jòÐ&73#67&'67'5'67#53&'€cb   P!  0HeÐ   9   ÿéòK#7'67#53&'73#67&'7'F;Pgba    K( (7     5    ÿé€Ì3:73#&'#5'67#535'23&''67'67#5367#p -- $../    " ! Ì  %f    1   ÿù‡Ã)73#67&''67'75&''675#m.  2>1  ,Ãn'3   ( 0>j ÿìˆÏ#'+73533#673#3#5##5'67#535#35#35#! 1?/ -.!////· vf nD ÿì‹Ð*073533#3#535#&'&'#33267#"&5''61,,(h-19 :  6    ®"",,B  7 <$  ÿìÆ7#53#67'5#'6735#35#35#.Z  % 3333336+ ;=  ’BB ÿìŒÎ!'-7&'3673267#"&55'67''67&':$     ^  Î0?/ATA%$  W0'&$*,#rëÑ 7#5##5367'&''&'믒  H  *  ª8&&8'      ÿê€Ç #)7#5##53#735#35#3#53&''67&'~G [[4444-n,  9 Ç  'S11&  ) ÿé†Ï G\7'2'6'&'3&'733#"''3255#67&'74''67##53'767#67#53''67&'| .A5+ ) ['  #   -  R& );   Ï        o \ v…  g   ÿéZÉ73#67'5#'65#735#35#35#A    É“3  D# 0hGHsëÑ7&'73#5##536'&'€  O 4±Œu  Ñ10    ÿé‡Ï*0673533#3'67#535#&'36533#'67#7&'&''((1  b1'  7,/ 5 - 3 O ¶  - 2 1. - 2  3  XÿêòÉ7&'73&''676'367'Š  I( ) j É")*!i%#!#.$N•  fÿêòÊ7&'73&''676'367'”  C! $ ` Ê#)*"n$!!.$O”  <ÿéòÏ 2:>73#53&''6553'#37&'73#3##5#535#536#5##535#ÊS!S@1  8!+ ;Ï SF$EFO>-!<<!)mmG58ÿéòÏ 2:>73#53&''6553'#37&'73#3##5#535#536#5##535#Æ!X$WH$$,  < #"/ >Ï SG#FFO>-!<<!(koH7HÿéòÏ 2:>73#53&''6553'35&'73#3##5#535#536#5##535#ÌMLC2 ) 3'4Ï SE%EFO>--!<<!(koH7ÿé‡Ï 73353353#3#67'535#qnQ.$(8 QZÂ0==0C=*  JÿésÏ 73353353#3#67'535#a]D%". CIÂ0==0C=*  JUÿêóÑ%+1=733#3#535333533#&'#5'67#7'6'&''3353#5## 88Až(#$     M 8  hhÑ((D++,*  9  yy— `ÿèôÐ08<@D7#'6553533#3'67#7326767#"&55'73#53535#35#35#œ.66A /$& "Ju444444—G;- .6R(      0VV* * ÿêíÐ4733#3'67#6732767#"'"&55'75#'66553rfff Q$B(2 6> QÐ     H<. * R ÿêmÎ.>733#3'67#'65533267#"&55'753#'665367'0))' 1  (     0 Î  E8, -3O"   H= ,? ÿêíÐ/7533#3'67#673267#"&5'675#'655sfffQ$#&1' 3?²     K>0 /9U ÿèrË4;73##5'67#535'2&'373&''67&'67#367g ## $+*"  ?+  !* Ë6&9  4    fðÑ !'7&'73#7&'''6&'7'6''63 t6  H Q · &A Ee Ñ  .+       gÿè÷Ñ<BHL7#673265#"''67&'#3673&''67&'67#'65535367#7&'3#ð  >     N.H J11¬. 5&&1%,C9 "   <. 07S%%y   4¬ÿéòÀ73#3###5##535#32655¬F  +À#q ’u†#4d] ÿêyÐ#'+735#53673#3#&'#5'67#75#5#35#,%.&-(   !R99999<m m 04raÿêíÏ !73353353#&''673'67r "{(    $JY PÃ5AA5G,  ,P@ ÿê”Ï 73353353#&''673'67v'  "GT LÃ5AA5G,  -P@AÿéóÏ*.?EK733#'#5'67#5;533#&'#5'67#3#3##"''3255#'67&'h 6   @~~žD   Gl Ï '' .(G+  (   QÿéòÏ+/@FL7#5'67#53533#73533#&'#5'67#3#3##"''3255#'67&'‚       @ $&  É7T -     c '%  ÿèóÏ73533#&'#5'67#•"$#    !¡..E -qi%*AGÿéöÏ,73533#&'#5'67#73533#&'#5'67#Y   J     ¡.. tx8"-I..E 0yx2*@ÿé¤Ï,73533#'#5'67#73533#&'#5'67#   H    //wp-*A//rp-(CÿéªÏ-73533#&'#5'67#73533#&'#5'67#   H    // sr0)B//nr-*A7ÿéöÏ+73533#'#5'67#73533#&'#5'67#I P!  ¡..ws8"1I..D!6~6)AKÿéöÏ+73533#'#5'67#73533#&'#5'67#[    H     ¡..ty*%7..E 1yv1*@ ÿéñv 7&''63&'73'67#€,;@0.<F(O E!1'Žv  ! $tëÉ'735#535#53#3#3#735#535#53#3#3#( &b)""(ct'!!&b)"")c†|ÿíðÏ &73&'73#67&'7''67'67676|0.tP$#  **#²  V  ") "&.* UÿéöÏ,73533#&'#5'67#73533#&'#5'67#e   C     ¡.. ux-&@..F4zy5)A5ÿéõÏ!27#53533#'#5'67&'''667&'7''6\   Œ*  #'Ž..!jt,${BJ@*)V" (0  +9ÿéõÏ!273533#'#5'67#7&'''6''6767&'E   “%5 "& ¡..'pt+'=:BJ=-*H(0  *VT$  ÿè@Ë 7&'&''6 ! Ë1  6 .+ )?ÿêôÐ4:PV733#3'67#&''67373&''67&'767#'63533#&'#5'67#7&'u=3*      1K   6  uEN@# "$' +9"  Ð ,!   #    X  ""0-2 BÿèòÐ (,04S73#53&'3&'73#3#3#3##5'65#5#35#'6767'67'67'67676›L¬J  K *,       Ð "  r "!*+d/ )($  ZÿêôÏ,06I73533#3#3#3#3##5#535#535#53'#535#37#'&'333#"&'&'75#•"##((!!((''""))""%6  '% . ¼:=Hf   WNÿëóÏ-28J73533#3#3#3#3##5#535#535#53&'#535#367#'&'333#"'&'75#Š&**/.''..,,&&.-&%+< # *+ 43 ¼ ::Ef  W ÿêYÏ7367&''65''6,   Ï<  ) 0G5+$TÿëôÏ"N73&''67&''667#'&'3533#3#3#3#"&'&'75#535#535#535#¥2      %: 0$$$ ''/1 "$'' $Ï       V   WfGÿëôÏ#O73&''67&''667#'&'3533#3#3#3#"&'&'75#5335#535#535#¡5      *?  4'(($$++46 %(++##'Ï       V   WfFÿêõÏ-39K73533#3#3#3#3##5#535#535#53&'#535#367#'&'333#"&''75#†***//((//..((00*&,H  ! .. 7  ¼  : E>g   W¨ÿéøË 7'6'6'6Þ! " ) "Ë #$ ) /!ŠÿéïÅ73#"''3255#3###535#Še RAA?+ÅÆ ±N Z<+NÿçöÐ!%173#3#&'#'67#537#53635#35#3533##5#”A9M* %) #)3-#SSSS##ÐT ')T ,2o%% ÿéó‹-1597&'#5'63&'#"''3255##5##5##535#33533523  Oj|   ($&&&8$(‹   'C  %Z$MÿéóÏCH7'23#5##536'&''&'3673#3&''67'&&''67#67Ü 6M?1lc "&KRO  *$ -  Ï  &&    F       '! \ÿéóÏ@E7'23#5##536'&''&'3673#3&''67&''67#67Þ 3J=.d] %GNM &# .  Ï  &&    G     %! \ÿìôÏ !%8<@7&'67&'67&'63#735#3267#"&553'#37#3v <  >  jyyTT '.  84‰N((:((Ï      //d  V:+SÿíõÐ%573673#&'#'67#33#3#535#5333#3#535#53^2H/& /, #(;))=‡7$$00B—B,,¶   %&;JÿéóÐ )-15R73#53&''673&'73#3#3#3##75#5#35#'677'67'67'6767¥F D  H,+  &    Ð n!%  t*+d-1,  $$’ÿêõÎ$*073533#3##"''3255#'667#535#'67&'   !  M³s  nB5 1<=$ '(+%mÿêòÑ )157#5##53&'735#53533#3#3##5#535##5##535#ï[4;*//22--;;77*oKKK¿"" H  1K K/qÿéøÑ #'7'67&'&'3'67##53#=#± (   "f KlF²" *"    p SR $$§ÿç÷Ï73533#&''67#7&'¯ 8‹DDN2"9;'\C‚ÿíôÎ (,0473533#3#67'7&''67#535#3#53535#35#35#‘"$$*4   && ("[r    ·    gBBB33333gÿéôÄ )7#5##5'67&'3533#&'#5'67#íZ, 5 C74)  *Ä10" < %)PP%#\ÿçôÐ(7673#3655335#535#53#&''67#d #(/6, 0- , 1°  'rr'{/.+&XÿèóÐ7>73#&'#5'67#535'23673#&''67&'67#3#26Ü ;,! $" "+;;H+W #/ & !e.%Ð #--€       ÿçéÎ 73353353#5#OLÀ˜‘ÇÇ“³ ÿéçÇ7#"''3255##5ç  ©ÇÆ  ¯ÌÞ"KïÅ7#3276767#"&55#;5#ݧ:'" SHS??BBÅE  c##bÿéòÐ -73#5##53&'373#'67#67&'7&''6­4[80"PTH $%Ð // S!I)$@ 2  # #hÿèòÒ48<7&''67&'76#"''3255##5'67#53673#35#35#Û  %! !   9"+GO 9999Ò     d^  ,\   +dÿëõÑ#'+735#53673#3#&'#5'67#75#5#35#e;,!7->0! .iGGGGG<n  n74rkÿêõÐ#'+735#53673#3#&'#5'67#75#5#35#r3*3*7* $]AAAAA<n  n78!r`ÿéöÒ%6:>7'6'&''66'&'&'''6#53#"''32=#3#3æ1- (0(. c 7g @@@@Ò  %!    Œ0‡n  D dÿéðÏ&+19=AIM7367&''667367&''66''67'6#5##53#735##5##535#‚     C     U CBcXX44TLLLÏ    !      5-./-99! ÿè‚ #)/73533#33#53535#5#5#35#5#'67&',..' u $,T:::::::>  —  __ &     ZÿèõÐ -159?EU[73#5##53&'3533#3#3#3#535#535#535#35#335335&'7&''33265#"&5''6¬9d:%$$++::3x399++%"/ D  Q "   Ð $%-    ..   [#      eòÑ(7&'3267#"&''7&'7'#5'6° AJ  , 2.K  'Ñ   " A0 TòÑ 7&''6&'3#67#x/: 82); J)6 lÑ"&%   ±ÿðíº 7#5##535#íºÉʦ”£ÿéóÁ73#3##"''3255#«@@P   "Á.m  i¬ÿëôÁ73#3###'3255#²==H  $Á.j  f°ÿêòÁ73#3##"''3255#µ::B   Á/l  i¼ÿêòÁ73#3##"''3255#Â..4   Á/l  iKÿéöÏ#'G73533#3#5##535#3#735#33535#3353'7#3#33#"''675#]<@@EtB<jj+F+o•-66"T @à  "# (A( % "    3WÿêîÏA733673#5##53&'733#735#35#53#3##"''3255#'67#' h  UU11?WŠ   . '+Ï" **  ,; ! HÿëôÑ-159?OU[7&'327#"''67&'#'6553&533#6'3#3#735#&'33267#"&57&'''6Ø    CT)( [2200  #d k Ñ .#   "$+322 33,  / SÿéõÏ+[7&'#5'63&'3&'&''67'763353#3#"''3255#67&'7&''7##5367#Ÿ$$ \ 5        LV0D  9   #"64Ï      ((7;  & @P ÿéÌ26:>BGKOU[aekq73#3##'3265#3#''75#5''6553#67#7#335#335335#335#335&'7#7'6'&'73#''67&'2fLR   (! '$  ƒr  ZaaL6#/L(0v&C6"   $ 2Ac(; n T - œ   %   ÿéÞÏ733#5#535#535#5367#'6ba3©©œœ©q X! 3Ï Š !#ÿÿR¼ 7##535#35#R-¼£½I88MÿêóÏ $(,73##"''3255##5#53535335#33535#35#â  X55!!5#X!!5##µu+  &CCu;(((b'''DÿèöÏ!73733#32765#"&55#'667#Y# $ ,"22‰ CE B=TÿêóÐ 773#'6#"''3267#5327667##"&55'753753„bm s     3 Ð (G' GAD  F+%*$VÿêöÏ#(.473#33##"''3255#'67#5367#535#&'''6œIP)++ D  )0 _  SÏ4A > \!  #]ÿêõÏ#(.473#33##"''3255#'67#537#535#&'''6 DK')) @ (/ Y  OÏ 4A >\!  #ÿèíi#73#3##"''3255#535#535#'2® KKbb  ccKK6i   VÿèõÒ  (,7&''63##"''3255##5#53#5'35ž!$ ""& 2FFj   8<Ò(,)$I  2n€]sh K99JÿèôÊ$*E7'66553##5'6757#3'6'&'&'3533#3##5#535#'6t Œ..ff SN:,,5577”/C7 1$_6:  "$    CÿèôÏ)Nm777;267##"&55'75'75'673267#"&55'75'75'67''677767'5'75'7Ñ!ACEG'  .0'*CR!"$   )#h )#  ! Ï      „         ! QÿèôË/3773#33##&'#5'67#535#535#535#535'235#35#Û CC7+$ $& &$3<<3388-9 $$$$Ë"! <>a2CÿçìÊ'73353#35#53353#5##535#3#5#'65[,k..k+Ê75GG57ãLHZ ZH- ,A\îÌ%7N7#3#53#3#3#&''67#535#'6733#"&55#'66767#53&''67&'?Wil51    %  $  AW    ½Mk          C   ÿíì'7#3#53#5#535#535#53733#3#3##꺼ÎM33,,00'<<66>>£sfÿòòÅ'7#3#53#5#535#535#53733#3#3##ðwx‹6# !!ůӞaÿéóÐ#'+77'673&'73#3#3#3##75#5#35#33##5#53x  !&*$$$$,c7$$$$$<<==‡ "%   \! *00TÿéóÐ#'+77'673&'73#3#3#3##75#5#35#33##5#53n  % */((((1m;)))))BBEE‰ "%   \! *00%ÿìò¡ ,F73#53635#&''67&'7667'53373325267##"&5536nd»@1““z & k!* >k "0 ' )¡UUL3 ^ B   5aÿèõÇ1G73#7#5'75#35#35#75#3&''67&'767#67#53&''67&'iƒc"======%=    )W /E     ÇJQ/22    %   aÿéôÉ'+/37;?73#3533#3#3##5#535#535#535335#735#33535#33535#335h‚98AA>>5 7$$6&\$$6&N(ÉO""/+ ZGÿèõÈ/E73#7#5'75#35#35#75#3''67&'767#67#53&''67&'P›w'KKKKKK+G    2d 6M   ÈJQ/22     &  NÿéòÈ3K73#7#5'75#35#35#75#67#53&''67&'73&''67&'767#\i%CCCCCC -B     FI    4ÈIP-1 H     # $     ÿçôÈ-2I7#5'75#53#'35#35#5##53&''67&67#'67#53&''67&'î'' Í zffffff9e  "2  H^   m SL=-..         JÿéõÏ &73353353#&''6&'3'67#\)*ŒA$( ('* 7  0€!cÃ++1    &)UÿèõÏ '73353353#&''6&'3'67#d'&†= ( $%$ 3+w]Ã++1   &*^ÿèõÏ '73353353#&''6&'3'67#l"#9$ !## 0 ,v[Ã++1   &*aÿé÷Å"&7#3#67&'#67'5#'6553#ìgl+    -  VVÅC   #R^ kA2 5>]*dÿñó¿ %73#3#535#&''6'&''6l‚8=Œ;6f O   ¿©©   1?  0nÿèöÊ$(7#3#67&'#67'5#'66553#ñ\\#   %   IIÊC   )Re qE6 1%a)nÿé÷Ñ "&7'67&'&'3'67##53#=#° '#   "e LnH²"* !  p SR %% >ÿèôÇ !%)/@DH7#'65536533#&''67#'3#735#35#7&'#"''3255##55#35ðb G@@k,   2 ÇSE4 4@_L0D*/* 'L>C( # % F[  %pMÿéöÒ%6:>7'6'&''66'&'&'''6#53#"''32=#3#3æ75 .8-!3 l Cp  JJJJÒ  #%!    Š2‡n  C ÿèõÏ!%)/5;A73&'#"''3255##5'63&'35#35#7'6'&'&'''6w3)   u g]-uuuuš""œ ’  L"#Ï & `  ,s   ;7“      ÿêƒÏ $(.4:@7'#"''3255##5'663&'35#35#7'6'&'&'''6A     . C ....= J P -Ïm 1}(; /3’      ÿéî£#CHLP7#53#3#5##5##533#73#3#73#3&'73#3#3#3##5'65##5#35#vZÈ\fTRdH==g<<<558 2  ' ÈSE5 5@_A&&r  m-C -  RÿéóÈ #'+1A7#'66553533##"''3255#'3#3#735#&'7677'7&'ò| V   =88337- % ÈSD6 0$`A&&s  o,C! -  ?ÿèòÏ^b73#67&'##'32654''67&''67''67#'66553533#3'67#733265#"&55#7Š#€;     &+  + 5FFN9+, & ##x   '      ;) )P*       #[ÿèõÏZ^73#67&'##'32655'67''67''67#'6553533#3'7#733267#"&55#7˜n5       #.;;C /!"  y  $       ;) -5P*     #jÿéõÏZ^73#67&'#'3255'67&#'67&''67#'6553533#3'7#73267#"&55#7 a0       )44<)   w  &       8, -5P*    #pÿêõÏ!%)/5;A73&'73#3#3#3#5'65#5#5#'67&''&''&'“!%!!!!(l .  g   Ï K -%&  ÿê„Î37;767&''67&'373#3#"''3255##5'67#5#5##     &:BH   2 ^222Î   <  _  (^ 2RîÌ'9P7#3#53#3#3#&''67#535#'6733#"&55#'66767#53&''67&'AWil2/    " š #  AW   ¼Uu         G   ÿëðÃ73#3#3#"''32765#7#537# ¿r ’ ‚ !ˆ:? 8Ã##K! ,6#RÿìòÃ73#3#3#"''3265#7#537#^†I `e \!a'+ (Ã""L!05"OÿéöÆ73327#"'&5#3##5#535#Sx  .''&&"Æp6!#/ƒFppFpÿèöÆ735#53327#"&'5#3##5#qf %mFj.-#!(JWFrreÿéöÆ73327#"'&5#3##5#535#hj  &!! Æcd#.†FppF?ÿèñÅ %7#'66553733#"''32765#'67#ñ€ < 'ÅRC4 2!^J..L8 P29'LRÿééÇ 7#5##535#'67&éooo6 & ÇÞÞ½ª^%)T#"ÿééŒ 7#5##535#&''676骪ªP$ $  Œ£ £‡t€ÿéîÆ 7#5##535#'67&îJJJ%   ÆÝݽ¬f&*Y$?ÿéóÏ$)/73&''67&''67#'7367367#7&'˜V # ""3, !Q< = &~&    &2@1"Z•QÿéôÑ&+173&''67&''67#'7367367#7&'¢O    -&J7 4 #  ~ ,     &3?1# Y” ÿéìÎ$)/73&''67&''67#'7367367#7&'pr% *#%1 . *>8-oOU6~ &    (3=0)"[”7ÿéò%*073&''67&''67#'7367367#7&'‰`$( %! -,!^A B.  ]    &0&Du ?ÿéïÐ .73#5##53&'373#'67#&''6767&'¢>nA;. X^'"'| .-!" Ð // R"G%"<0$ #46 pÿêôÏ273533533##5##5#3#3#"''3267#'667#53&'t""H/LE   1<¶? )3 ,1 hÿéòÐ373533533##5##5#3#3#"''3267#'667#53&'m%%G2MB  0""<¶  B)1/+ EÿèòÏ273533533##5##5#3#3#"''3267#'67#53&'O"6##6"UBbY  F ( /*H¶  A (4K 9ÿçëÊ'73353#35#53353#5##535#3#5#'65T/r22r.Ê75GG57ãLHZ ZH. ,A ÿø~Î073&533#327#"&'#7&'3#33#7'7537> >c J..%  ™78*/U@0"8 `\ ÿëÏ!373&533#3265#"'&'#7&''3#367533#7'7 C  ChQ33  & ™6D*0$e>Ddu"> ‚ÿéïÏ!'+73#"''3267#3#"''3267#536&'3#¨-   2P   Q  %NNÏ F 0XG2|#  pDÿêôÑ04873#53'33##67&'67'5'67#5#5;5#35#¢CžD,x0  @   &#RRRRÑ ."!   > -  +  !2jÿîõÐ$473673#&'#'67#33#3#535#5333#3#535#53s)>( & % !1""4t-))8‚7$$¶  &&;`ÿîõÐ%573673#&'#'67#33#3#535#5333#3#535#53j-C+" *' $6%%8}2!!,,<Œ=((¶   &'; iÿèôÐ(59=CIOU73533533##5#5#3673265#"&5'3#5'675#5373#735#&'''67&''4'i$!!GU$0  J  )) 7  R J¾    J_Z     +  ¢,9!    2M4 o   _ÿéðÑ *73&'73#3#735##5##53##"''3255#c7:† ppJJnl^$   '½  3.1/$, )\ÿéïÐ(=CI7&'73#'65535#3#"''3255'675#73#"''3255'675#&''&'£3n[[2  84 !  )  » >!?2 1<1  "2Í '..  &$ !$n !==!^ÿïóÐ$*26:>7&'3673267#"''67''67&'3#53535#35#35#˜ & s   ” "!Ð  94 ?  < UAAA00000dÿèóÏ#)/73533#33#53535#35#35#35#35#'67&'k5990,5KKKKKKKK  = ¿ww0+,+ #    ]ÿéôÐ $(:?73#3#3#3#5'673&'5#5#5#3&''67&'#367µ,0))))2x  """""v "(   Ð E ' /    dÿéòÉ#';73#3#5##5##535#3#73#3#73#3#3#3#"''3267#7#rs/:),=2 AG%%G##Ozz Ž\V \ É #;;# )  %[ÿêöÑ *0AGM73#53635#'673&''67&'767&''33267#"&5''67&'—F~#WW&        x  Ñ || uZ!      N 1 4   ¥ÿéõÏ"73533#3&''67&&'#535#67¬      ®!!-4%  -?H½ 7##55#3535#H&%½¦¹1!!!!T" \ÿìõÏ#)/5E73533#3##5#535#735#33535#335&'7&'''6733267#"&5l44499994""4"[''9'.  F  g  ¾24 4V6   * TÿéöÑ+/7;73#&''6'3#&''6&''63##5##535#¼)   A'  9#$ $%' 4XXeMMMÑ     ! M M1 bÿéöÑ)-5973#&''6'3#&''6&''63##5##535#Á% ;#  1! !" 0 OO\EEEÑ         M M1 F ñË +F7'66553'#3&'7367&'#5'673533#3##5#535#'6k ’ll_1   "2288<- .9S/C + * eóÏ#73533533#3#&'#'67#535#3351B00>8! .=4(9;1DB¿  bÿéíÉ.267#"''255#'65535#35#'#"''3255#'65535#35#í         ÉË A:#=y:(d*LË A8!!?y:(d*ÿéò€#'+73533#3#3##5#535#535#35#33535#335accQQiiggQQa$==Q=Ž==Q=s  B  B % " zíÏ73#5#535#535#53733#3#3##^LLEEGG/KKCCMMÏU     gÿé÷Ï;AG73673#3#3#&'#3#'67#53655#5'67#5367#5367#&'#&'t*8;+)  +/¸)   #I  <<<<<³ )  ##É5(      4(Y !n +   > ðÍ !7HLPf73#735#33533567&'7&''673267#"&5536#"''3255##535#35#73267#"&5536ÌÌ**>+)  +0¸) ##G   ;;;;;± )  "#Í.!     )#G \ #     3ÿèñ 7&''&'''67&'Ö  )  b E         ÿéð¤  7HLPf73#735#33533567'7&''6732767#"&5536#"''3255##535#35#73267#"&5536ÌÌ++=-*   ,1¸) # #I  <<<<<³ * $#¤-       )!H ] $     1 ]ÿèôÈ "7HLPf73#735#33533567&'7&''673267#"&5536#"''3255##535#35#73267#"&5536aŠŠ'g  x    /  n   È7*      4'Z  $o +   = OÿèôÈ !6GKOe73#735#33533567&'7&''673267#"&5536#"''3255##535#35#73267#"&5536T••+n  "ƒ   3  (((((    È7*       4&Z  $o +    = ÿéöÑ*.28>FJ735333##3#&'#5'67#535#535#535#33535&'7'6'3'6573#&ORR==  2>,; " 7  - 7[ÿéõÏ-O7#5'67#53533#&3#5'67#53533#&3'67#3#33#"&''675#Ž   A   y‡ %,,!,   9*$   #$    !  2 MMÿéòÏ  $*@73#53&'3#5##5'6553'#335#7&'73#3##5#535#536ÍNH, 9 9 3( Ï S kOGFO>-w7R!<<!ÿíò¬)-15973'73#3#53&'#37#3#3#3#535#535#735#33535#335 UT(=Ü<%E5E8ªKVVhãgUUK77K8ƒ77K8™  ?    & ! ÿðòÒ-159=73#3#53&'#53&'67#3#3#3#535#535#735#33535#335€[,Cá>/\-E=°NXXiägWWN;;O;Š;;O;Ò   3 M  .,ÿçöÐ37;73#3#53&'#53&'367#3#3265#"&55#'67#735#35#Y'=Þ<$Z : Q1©1 ' F ? /Ð   6 *Q!  '2 #1/ eÿéöË 48<@W]73#735#'3#735##367#533#7#5'75#'65535#35#75#73#&''67&''667#³<<S<<st# 1  *0      Ë- ,)'  ? L 1+ %3HS+, f $     ÿóðÇ#73#3#3#535#535#735#33535#335&¶QUUeßfWWQ==Q=Ž==Q=ÇxD!!!RtÿòðÇ7#3#5#"''3255##5##5353ïij|x  $DZÕ;Q ˆZ   1    ¸     W 3  -  %" Pÿé÷ÐEKQ7&'#673265#"''67&'#3533#3##"''3255#'6655353'67&'â   "   J  [P  9Ð .!6*$/ .I=5I  D=. , VkUÿíøÏ!17=7367#'6733#535#535#&''33267#"&57&'''6s9 2  <!ta]]_3   ]  l  •  [R  (  ,   ÿê‚Í-73#&'#5'67#535'2#'66537'm **  #/-  B Í  #.†+,A    ÿézÍ.73#&'#5'67#535'267'5#'655c &&  *(* Í  #+t9  ; &# ! ÿëÍ#,73#&'#5'67#535'2'67367'm **  #/-" )  Í  #.s? 1>  [ÿéïÏ%767653'67'56##53#"''325˜     ZA   Ï n QQ3'# ŠÆØš gÿéîÁ!%)7##53#"''325'#67&'7''53535Ü9  4/   ¯ÆØ› ˜sC & Ä!!2^ÿéîÁ!%)7##53#"''325'#67&'7''53535Ü>   :2   ¯ÆØ› ˜sC & Ã!!2lÿéïÏ&767653'67'56##53#"''325Ÿ   R:  Ïn OR3' $ ŠÇØš sÿéîÁ"&*7##53#"''325'#67&'7&''53535Ü5 -*  ¯ÆØ› ˜sB &  Ã!!2 ÿêpÍ-73#&'#5'67#535'2#'665367'^ ##   '& ;  Í  ")‡+.@  OÿéîÏ$77653'67'56##53#"''325‘   "bE   Ï n PU.) " ŠÆØš `ÿõ¦Á7#67&'7''53535 . ÁsC & Ä 1 ÿéÎ26:73#3#"''3255#7&'7''75##535#535'235#335€ ..8  % !4..8-Î=R  ;  Xj=Iÿé˜Ò%-159=73'73#676767'7&''67'67##5##535#33535#35#89G    3:+}Q2Q2½   $     \g g$6 ÿé—Æ%+17=73#3#3#535335#3##'3267#733&''&'''67&'„8//9ˆ9 j oZ! 0  ,Æ''4f:$; &   ÿè¤Ï%)-27;?CGMS75#53533533#3#'3267##5#'67#77353355##67#3#735#35#35#'67&':))))1 !#   5(wwRRRRRR  D«  *  ) &  -X? " "      ÿé”Ë 15;73#535#535#3#7'75#73#3##"''3255#535#735#&'iiVPPV ; C:   ``7  ËI  B8  K  ÿê™Ð+/>DH73&'73673#3#3#&''67#5365#535#5#35#"&55#'67#333535#  -'2>: % & 294&+E !R  >RRRµ    _  _ 6     >  ÿé¤Ï E733533#537'6'&'#5'67#535#535#53&'73673#3#3#&@.‘-S f  M" $2>339  =66@6 Ï///-   ®,.   ÿç¢Æ6MSY73#63#5'75#35#35#675#767#53&''67&'#5'6767&''6'6L   ; )=   ='   K    Æ? J )- - "    ~5X   )  ÿè˜Î#<B73533533##5##5#3#735#335335#5#3'67&''67##567#  ##  yy!-7>   )  .À 62& / &4  ÿå”Ò'+/37=C767'7&''563#"''3255#'67#3#735#35#35#&'''6@    &B    ;ttMMMMMM: Ò $    ?6 $/  !@jK++"    ÿç¥Ð(6:>DJPV73533533##5#5#3673267#"&5'3#5'675#5373#735#&'''67&''4' )$$O[)8  V 00> _ U ¾   J_ZDHLYn73#73##5##53#5##5&'7&'#3#'6553&'75##5#35#73533567'53373673265#"&5@@HCCˆ' W ++'v :'O#    Ì 0""0. !/    ! )) $-1   :  6       ÿç›Ð$JNRVZ`f7'673533#3#7'5#'67#535#'67#535#'673533#3#67'3#735#35#35#'67&'      L     _rrNNNNNN  E °       Z@ % %       ÿé•Î#:73#3#3#"''3267#53635353533#&'#5'67#B4Zjjk   l$GGG_)'' %Î@  6$o   [  )*GÿéòÈ%,073#3#5##535#5#35#"&55#'655#333535#G«:.p.8_-p T pppȵ µp 1# -5„{ÿé÷Ï(7365333267#"&55#'67#3533##5#})  % /00/² K  =</e%%??kÿèöÏ,73533#&'#5'67#73533#&'#5'67#q A    ž11 u[' 4I22H /kj+,BŸÿêîÊ7#"''3255#'65535#35#î   ÊÇ <8 !?y<+g+xÿèõÉ7&'73&''676'367'   = # & U  É")*!r& .%O’  wÿïóÐ#73533533##5##5#3#3#5#53&'w%%C-[Wi9µ Tg mêÒ#)73'67#&''67#'6'&''6ub %$!%$# < J 9"$Ò   #   #  aëÑ"(73'67#&''67#'6'&''6vc $% $$$ >  K 9!$Ñ    , .  ’ÿèöÇ )733#5367#3#3#&''67#535#'6¢A^7.8$"    $' Ç 2% " cÿéŸÏ 7#5'6Œ  Ï©†&`ÿè÷Ñ>CIM7#673265#"''67&'#373&''67&'67#'6553&5367#7&'3#ð  C    U1L O44¬1! 5&%1&/A9!  ;/ /8S%z’ 6iÿèóÏ49?DJ73533#3#3#535#535#33#3##"''3265#67#5363&'#33657##&'#s15500<Š;,,1m   ` $ $ " L ;  ÿèõS!:>B73533#67&'7&''275#735#33573533#67'7&''275#735#335((( '2(((++  '3()E1 0[ÿéóÏ#973#3#3#"''3267#53635353533#&'#5'67#—=`ppo  mNNNf++""  "#Ï; 4 j   X  "97`ÿéóÐ.D733533##5##5#533673'73#&'#'67#3533533##5#'67#†""+  ,# , ) !!!Ð7     $" ;77$  ÿéòÏ0F73533533##5##5#3673&'73#&'#'67#3533533##5#'67#;>??>; M A  E!1 >%A 4::;88=!8º%    "# ;66"  ÿéôÏCG73533533##5##5#3533533533##5#3#3#&'#5'67#535#5#5# <C>>C<$9,,]$¡XcW&8 9'#: 4#ObHŸ9¿ !!. 11?^ÿéöÏCG73533533##5##5#3533533533##5#3#3#&'#5'67#535#5#5#c##%%##C\/;-  !.9-c¿ !!- 2. =MÿéöÏCG73533533##5##5#3533533533##5#3#3#&'#5'67#535#5#5#S')**)'$Hh6C2" %$ #4@2o$¾ !!- !61 =bÿéöÐ.a733#3'67#'6553373265#"&55'73#67&'#"''3265'67''67&''67#£//8 [  4   e/    Ð  H;- .6Q   *         kÿíóÏ.6>BFJ73533533##5##5#3533#3#&''67#535#67&'7#3#53535#35#35#k..),,9 +0+7) A ‡   ¿  8!444$$$$$³ÿéóÏ73533##"''3255#&'·     ›44‰  „ªÿéóÐ73533##"''3255#&'«'  '  ›55„    dÿçõÑ$*.26:@F73'67#&''67#'6'&''63#735#35#35#&'''6¦= #   &  . (qqKKKKKK>* !Ñ    #    "  eH* )   ÿåí¬!%)-39?E73'67#&'3#53'67#'635#35#35#'&''6'67&'w^ ! 8 2_¢09 *}}}}}} 6 #- 24Y'#%%¬    WW  T ! ! ‹    r   GÿçõÑ!'+/37=C73'67#&''67#'6'&''63#735#35#35#'67&'šH   , % $ .  < 1ƒƒ]]]]]] ("NÑ     #   "  eH* )   qëÑ"(73'67#&''67#'6'&''6vb !$! $$# :   I 8!#Ñ          ÿèõc 9=A73533#67'7&''275#735#335'3533#67&'7''275#735#335…(++  '3()Æ(++  '3()R7  7  @óÒ;?F733#&'#"''32654''27&''67&''67#537#'77#67#"MeC# 2. 3F<= D@!' -ZaoZJGÒ      9  VÿéóÌ "(SW[_cgl7'6553'#3'6''73#''67&'3#3#"''3265#3#&''75#535#'67#735#33533535#335&'#{ŠffP",2AnPX  ,# ")&##  L%¥ )1¬( 1 @  ?????«)   * Ò       6 *J  b &   ;!òÒ*;?C\767&'7''673;267##"&5536#"''3255##535#35#7;667##"&5536>! )1¬( 1 @  ?????«) * Ò     < .Q  "i )   A CòÒ*:>B\767'7&''673;267##"&5536#"''327##535#35#7;667##"&5536?  *1­) / @  @@@@@¬)   , Ò      .&?  V     2XÿèôÍ&7;?U7''6767&'73267#"&5536#"''3255##535#35#73267#"&5536—   ]   1  u    ¶ (#    Pd  Ï  &" -)G+  (  QÿêõÈ37;?CIM73#3#3#&'#32767#"&55'677#537#535#5#35#3353353&'35#^-%?P' G& .(  +-&-R%%<8,55È8  $  0   8'D  5œÿéöÏ+75##535#53533#3#"''3255#&'#5'6à     L*6G5 !2BDOÿé¤Ï*75##535#53533#3#"''3255#'#5'6x     T"2C5 !8?FxÿéóÏ#+/37367&'#"''325'3'67##5##535#35#¬    .(  jAAAAAÏ   !$  ?(Qh h&9Fÿé÷Ï"*.27367&'#"''325'3'67##5##535#35#—  *   G;) #'‘_____Ï   #'  @)Sh h%8dÿéöÏ"*.27367&'#"''325'3'67##5##535#35#¥     <2  !{LLLLLÏ   #(  @)Qh h%9kÿéöÏ#+/37367&'#"''325'3'67##5##535#35#ª    80  tFFFFFÏ   !& @)Qh h%9UÿéóÐ %73353353##'6553#3##5#535#l$$‡w"a'--..'Æ))17.# %*@$77fÿéóÐ %73353353##'6553#3##5#535#ztyl[$))))$Æ!++ 17.$ %*A$77UÿéôÏ).4:733#3##"''3255#5373&''67&'#367'67&'v#  "1H      w  C  Ï#f  aFV8# %0J5'&6] (  _ÿèöÑ$>BFJO7#5#&''67##5'673#67#3#3&''67''67#735#35#35#67#ï(   # =1 sEH ) & OOOOOO* ; ©$   ,Q   :  = RÿèõÑ$?CGKP7#5#&''67##5'673#67#3#3&''67&''67#735#35#35#67#í+   & #A6 zJM ,")  VVVVVV.B©$   ,Q   :  = `çÒ#73#33#53537#535#5#5#5#W[EÍD\`C{{{{{{{ÒF F) ÿë`Ï 73#3#&''67#5365#'6#) ! Ï1 %$:ÿèñW!73#3#33#"&''675#735#/¦JRR!.)G/ IW0   *bÿéóÏ,08Q73533533533533#3265#"&55##5#'67#5##5##5#"''3255##5##5353b    /  M Nh  -¶  ++ )),8  #II?-  ÎcE58H&'!  ¹ÿéóÅ7#"''32654'7##5ò  Å?!! !CËÜRÿçôÏ)OWbh7'673533#3#3267#"&55#'67#535#'67#535#'673533#3#67'#5##53'665&'´       @     ZP1!$ '%«        M=>N' PÿçôÏ)MU]c7'673533#3#3265#"&55#'67#535#'67#535#'673533#3#67'#5##5'66&'±     C     ]S9&$!«          N=>O)- $ µÿéöÏ73533#&''67#7&'µ  5 ŠEE?B1#3" 5P? ÿèŠÐ,048I]733#3'67#'65533267#"&55'7533#3#735##"''3255##53&'73673##5#;00= P  %3%  !\\OO//E  <  Ð L>2 /;V     (  4 #>J   mÿðóÍ#73533533##5##5##53&'73#3#m((<1^Yl²I SÿêóJ73#3#33#"''675#735#-¨KSS!0*w  (K„„J*      " ÿèñD73#3#33#"&''675#735#1¥OTT$.)F/! DD%    ! kÿéõÑ 7;?CG7&'67&'67&'63#3#&'#5'67#535#735#33535#335ˆ  2  0  Xt29-  *5//N/Ñ      -O  1. /1eÿéïÎ9=AG73533533##5##5#3#3#"''3267#&''67#'67#735#35#33#l!! mD\      &  GGGG2D GC.    + ) F$ ]ÿëøÑ-159?OU[7&'327#"''67&'#'6553533#6'3#3#735#&'33267#"&57&'''6à    : L##Q----      \  b Ñ .    (*211 30,  / gÿéõË 4873#735#3353353#3#67&'#67'5'67#735#tvv"o‡‡ s* $   OOË973   "(    _ëÑ#'+73533#3#3##5#535#535#35#33535#335XYYMMcc``KKX99L:†99L:È 66  gÿéñÊ#E73#3#5##5##535#3#73#3#73#3#3#"''3255##5##5##537#py4;((;2;D''D((Qˆ?@  /6Ê (;;( * :  $8888=N^ÿéöÈ %AQ7#'6553#'#5'67#535'6#535'673#&'#5'63#53533533ðoB    -    /{&ÈKC? 4A] ) .   (" J22>RÿèòÆ %7#'655#"''3267#3265#"&55ñtw   + # ,ÆRD5 6?_3I1y   ’iÿèòÇ %7#'655#"''3267#3267#"&55ðem   %  ' Çl="!>y4I 2y  ’ ÿé{Æ 7#'655#"''3267#67'5zJ[  ÆRC6 6>^'P 7…  £ÿçñÆ &7#'6553#"''3267#3267#"&5ì²1| S0 >.ÆRE4 6>_3K1t  ÿè‹Ï$7373#3533#7#5'675#'67#) ;B %""!!$8!¯   84" oÿêóÌ!'73##"''3255#535'2'6'&'à ::  775+  I  ÌYB  =V"  eÿéîÁ6:73#3#"''3255#&''655#&''655##535#5#e‰'%        ')QÁ%Š  t  (:  # !1 ¡%%%%ÿçîj 733#3##5#535#'67#5373#3zFFaaiiJ -> x† 0A     qÿëôÆ !.73#735#35#3267#"&5536'33#67'quuOOOOc    f%% Æ_9;= *  _1  hÿèîÇ 07#"''3255##53#536'&'3#3#5#53&'î  `VP,!"82C "ÇÄ  ­Íß  5 #4 pÿéòÐ %)-157&'67&'67&'6#5##535#33535#35#ˆ   4   6   Q0!Q0!!Ð   Qyy-GNÿêîÎ %)-157&'67&'67&'6#5##535#35#35#35#p   C   C  l++=//=++=//Î   Qx x-GIÿèôÑ48<7&''67&'76#"''3255##5'67#5373#35#35#Ð  )' $&!"  F '1 ^g FFFFÑ     d^  ,` +\ÿéöÏ'=C73673#3#3#&'#'67#5367#537#3#3#'67#53655#&'j,?CAF[ 5  ")J7;2 ) -47¾    V # *uÿéóÏ %73&'73#3#3##5#535#536'&'z0 .uT #966//2G / ± #88#zÿéóÏ %73&'73#3#3##5#535#536'&'. ,pP !633--0D -  ± #88#‘ÿéóÏ %73#53&'3#3##5#535#536'&'¿&_$) *&&$$%3 "Ï /$88$ÿèðG73533##"''32655#&'œ00 œ>8$  ÿê’Ð73533533##5##5#35#35# 55'5555£----§§==Œ= ÿî“Î767&'7&''6#5##535#D & 11#P===Î7 # "Hdd>+ ÿü‡Í73#7'6756675#t.%U6= 0&Í Y §ª] ÿé›Ê#)/7&'36732765#"&55'677&'''6H  %   Q  Z  Ê +L0Ki3"   Z)23(9& .ÿò’Æ7#53#67'75'67&'P8v)2A5+3  ³ ˆ  f+  ÿó•Í736767''3#5'675#53W  $&&Í8  u  ËÚ*  Lÿø‚Ð733#5#535#535#5367#'6@0 !WWSSX5 $ !Ð {   ÿéŽÐ$*73533#3#535#3533##"''3255#&'---5€7- U  U  ¶!!Q<  8   ÿì“Ï;767&''67'33#"''3255##5##5'67#53673#3,   ! !.,  !+?HË   \7  #RRA>  ÿò‰Ç73#735#35#53#3#67'675#iiCC*/u3--9:*ÇL(w WÿñòÇ73#735#3#3#3#535#535#lwwOO=66A›F44<ÇM*RjÿñòÇ73#735#35#53#3#3#535#wooII/4|4--9ˆ;/ÇM)‚ÿñôÇ73#735#35#53#3#3#535#‡cc;;$(d(%%1r-$ÇM)!! ÿé™Ð59=767&''67&'#"''3255##5'67#53673#35#35#+  ) l =  (1EL====Ð     _b  &`  #3 ÿçqÐ+73'67#&''6&''673'67:)(2/ !   $#(30ÐB/  b   )S>dôÉ%+7'66533265#"&55'675#53#67&'’ S   ]#, $Z#S ·  #?  6% 4.eÿéóÐ73#3#535635#'67&'×&0Z$ˆ5",,JÐ #;†;R ! _ÿéöÑDJP7&'#67325#"''67&'#3533#3##"''3255#'655353'67&'ã       A RM 4Ñ ,:($  .H>5I  D1 08U    ' 6 &>J!  DÿíìÍ'+73673&''667&'67#3677#5##535#D &   0 c  ¢)H.  '$( "3+Éʦ”YÿëïÍ&*73&''67&'67#53667#7#5##535#|!       pÍ*K,  &%$3)wÉʦ• ÿîSÏ 73#'6#53#3#67'5#53%#*  /  Ï  0%9 HOÿëõÐ (J73#'6'3#3#"''3267#'655#53&'3'67#3#33#"&''675#­8? .   CI      Ð !% k"UM0 4N! A ,:  C#n\ÿéôÏ ,7<@QW]7#5##53&'73'67&'767&'767#'6&'33'673#3##"''3255#'67&'îf<3!)     f  2 ,D00$u4  .  Q »   3      % &A  /' $ Lÿè÷Ñ;BHL7#673265#"''67'&'#373&''67&'7#'6553'367#7&'3#ï#  J ]6 O Z;;­4!"3'$1'2(9"   ;/ .8T$${ —  5 ?ÿìöÑ #/5;KQ7'2'6'&''&'#53#3#533#535#535#&'7&''33267#"&5''6ß 6M@6  +/7ƒ9B”?9ƒ‚ollp: L  W  ! Ñ       " A  8       ! ÿéðZ73#3#3#"''3267#7#,¤¤à™€ 3Z  !  vÿéòÐ %5;AY_7'2'6'&''&'#5##5&'33267#"&5''67&''673&''67&767#ã +=2)     hN:      b  A  8 !  / Ð        N      ÿéï_73#5#535#535#53733#3#3##^MMBBLL1KK@@MM_v~ôÊ)7'66533265#"&55#&'3#67'75# R   0Z&.$ #"› 5  ,  4 oÿéóÐ37;767&''67&'3#3#"''3255##5'67#5335#35#ƒ  ! "GNN  8  $8888É   % b  '_ B0rÿêóÏ!'=73'33#673267#"''67&'#7&'3533533##5#'67#rB'&      Ce  V       £,,33'#C9   i%%%%556. >öÐ(,04733533#3#3#&'#'67#5367#535#53#335#35#N><<*f‚1 ,I( +D6'<>dŒŒŒŒÐ  7    7    `ñÐ,EK73533#3#3#3#"''3265#'67#535#535#73#&''67&''667#(00)).=8  .  !.$$(ŠC    +Ç           ÿèñf#(-73#3##5#5367#536367#33537#3355#an=SSz? 5."69JE,2DN>f; ;  "  $ cñÏ173533#7#"''32655'75#73##5#'6556'%%$' -'Ì &1\"*7» #(( .WÿéõÏ )H73#'6'3&'73#3#"''3265#'655#3#3#&''67#5365#'6³8> M #&    \'   ! Ï ! h$SO1 1D.)(  ÿçòš*I73#3#"''32765#'67#53&'73#'63#3#&''67#535#'6D"60  (]Q[ H/40 (#& % 06 š  P /9 ,N      añÑ (F73#'63&'73#3#"''3267#'667#73#3#&''67#5365#'6ŠT]s*#51   " †D&2)# #  06 Ñ  #       5ÿéõÐ (J73#'6'3#3#"''3267#'655#53&'3'67#3#33#"&''675#¡BJ 5 $#   OR ""     Ð # e(NM0 5L" > +8 E& mCÿéõÐ *L73#'6'3#3#"''3267#'655#53&'3'67#3#33#"&''675#¦>E 0    IL      Ð  $ fQN/ 5L" > +9 "D& m ÿèö¥ 'I73#'6'3#3#"''3267#'67#53&'3'67#3#33#"&''675#ŒW` B /.   &Zc ..&'   ¥  L5;! ,T 0  %#2T&ÿèØf 73'73#3#3##5##535#*KL®¢¢¤¤«ŒŒŒW  ..  ÿçÙY 73#53&'3#3##5##535#ƒQ¹R@££££¥€€€Y '( \ÿéîÊ $7#"''3255#535#5#'##535#35#î   .&,ÊÅ  d\%:J[…à$9cñÏ373533#7#"''32655'75#73##5#'6556'%%$' -'Ì &1]"- 7» #((  gñÏ373##5#'65563533#67#"''32655'675#á &1\"*7¤'%% ''Ï'' - XñÏ273533#7#"''32655'675#73##5#'6556'$$# &'Ë &1\!)7¹  &,, 3PñÏ173533#67#"''32655'75#73##5#'6556'$$&  &-'Ë &1\!(7·  ( 22 6GñÏ 373533#67#"''32655'75#73##5#'6556'%%  -'Ë &1\!(7µ * 99 9ÿîìs 73#735#35#3#7’’jjjj5ÖÖs`9::ZÿõòÆ7#53##5'67&'3#¢B8*?V’’´ Še *$".,*€aÿõòÆ 7#53##5'67&'3#¥?Š5 );Q‹‹´Šd$#,#"/,*€ÿèöÏ'733533##5##5#53&''67&'76•""F  Ï7 1! ". 5" ÿëôÐ -73#3#5363535#'&'333#"&''675#:di})PPVVV 2 %>F P,(Ð @A—!r} D`   PdÿèöÏ ,73#"'63'7#3#33#"&''675#Š]d j  //%'   #Ï  $+4A jZÿéóÏ ,73#'63'67#3#33#"&''6735#€cm t  //')  *Ï  &  +4 #BiUÿìôÐ .73#3#536#335#'&'333#"&'&'75#²&BFY"///33= % +& 1 Ð @?–W} F_   P^ÿìôÐ /73#3#536#335#'&'333#"&'&'675#´%@DW!---009 #)# -  Ð @?–W{  D_   PSÿéóÏ17#"''32655'75#53533#73##5#'6556“  d !>&iH : :++2l%ˆˆ=6 15]9ÿéóÏ'+/N735333##3#3##5#535#535#535#535#3353567#'7#5333#"''67&'‡**--44**##++1€ 3?<5B   ¼"!$,&: A!%TÿéóÏ'+/N735333##3#3##5#535#535#535#535#3353567#'7#53333#"''67&'—$ $&&,,""$$+j/  63,9  ¼""#- :!;%#PÿéôÏ'+/N735333##3#3##5#535#535#535#535#3353567#'7#53333#"''67&'•% %''--$$%%,m0  95.:  ¼""#-!:!;%$bÿéôÏ'+/N735333##3#3##5#535#535#535#535#3353567#'7#53333#"''67&'Ÿ" """((!!(`0  00)2  ¼""#-:!;& $dÿéôÏ'+/O735333##3#3##5#535#535#535#535#3353567#'7#53333#"''67&'¡ !!&& (],  .'2 ¼""#-:!:%"bÿéõÎ673#3#535#535'633327#"''67&'767#'7#æ C "_5&'+   "Î7<<28%  7YÿéõÎ573#3#535#535'633327#"''67&'767#'7#ä G#d9(*/    $Î7<<28% !6DÿéôÎ47'673#3#535#5367#'7#533327#"''67&'»*# !! R""Z$%&;$//5  ° 6;;. !67$ ÿéZÏ#'+/73533533##3#3##5#535#535#5#5#35#335   1  »$:)):$NJÿéôÏ'+/P735333##3#3##5#535#535#535#535#3353567#'7#53333#"&''67&'’' '))//%%''.t2"70""  ¼""#-":!;% $ ÿé’Î773#3#535#535'633327#"''67&'767#'7#ˆ @  Z3 $'    Î7<<2:$ !6 ÿéò™-15@7'6735333##3#3##5#535#535#535#535#33535'#5'6@ 4377<>3F$$$ˆ  '™ !         bN ÿéòž-15@7'6735333##3#3##5#535#535#535#535#33535'#5'6@ 6377<>3F$$$ˆ  'ž "     ePEÿêòÏ"(7365337&'#"''32767#'665#'6])6   "%# !) ¥*;"N)WHMCB $  ÿéóÏ!'733#"''32665#'665#53&'''6aI  4)*(%EEt  • Ï*bD ADGMD?*#) +!*! jÿé÷Ã#73#"''32765#'665#'67&'|[  " j ÃwK$€\M JW% #ÿéò@73#3#"''3267#7#äŸ  •1@  cóÐ573&''67&'67#5367#73&''67&'767#@2  # *%T]    FÐ   2 /    #ÿèÝg"/7'67##53673#"''3255#&#&''6w  7UP  D$ !  (9  ct[  E    hóÐ573&''67&'67#53667#73&''67&'767#@1  # *%F]    FÐ  8 2    _êÐ"&73&''67&'67#53667#73#735#B3  # + ']ZZ44Ð    =  +M)lêÐ!%73&''67&'67#5367#73#735#B0  & !, ']ZZ55Ð   /  'G$GñÐ873&''67&'67#5367#767#53&''67&'@2  % )%Y  La    Ð%   = *$   YôÐ673&''674'67#5367#767#53&''67&'@2  %$ ) %f  Ka   Ð   7 &  ^ñÐ773&''67&'67#5367#767#53&''67&'@1  # *& g  Ka    Ð   3 %   ZêÐ#'73&''67&'67#5367#73#735#B2  % ", % ]ZZ44Ð"    9 ,P. ZæÐ"736533#"''3267#'67#73#735##6  $&"SS,,¼ C'#*V1`ìÆ73#3#5'67#35#Ó~wŒ 1?FddÆ ='"AaÿêòÏ 73#53&'3#3##5##535#¦@‘;%xxxxwNNNÏ  >U U5# ,ó73533533##5##5# 5M<<M5r++++3333ÿçåÐ$73'67'567677##53#"''325YL 3  3*$y3Z ¦Y>($ …lbÆÙ   MóÇ73##5'67#&'Ô]%5E$_-!!,Ç`J"( ÿèáÐ7'6553&'73'#3B#PO‹‹f+6!MM  N;( xóÓ73&''67&''667Wm"0 :..= 4%   Ó      RÿçõÌ!%)-TY_73#33#&'75#&'7##53537#35#35#35#33265#"'3&''67&''67&567''6\’F=  6 3*8WWWWWW  K  %".%   S  Ì A   A       (  :  IÿêñÅ!735#53#3##"''32655'67#'x;Nˆ&,,   "6.$7¦-==] WA-#<LÿéóÐ '873#'6'3#'3#7&'#5##53'66733267#"&5¹.4 ,$m I*&& #!  Ð !&YQL  !SBCT./ &% \ÿèñÑ673#67&'7&''53&'35357#"''32654'7##5}2   b  ÑfL # Á **LB$ 'BÊÛeÿèñÑ673#67&'7&''53&'35357#"''32654'7##5ƒ,  _  ÑfL$ Á **LB$ 'BÊÛ‰ÿèóÑ673#67&'7&''53&'35357#"''32654'7##5ž    K ÑdL% Á **KF" 'DÌÛKÿéƒÎ73'67'67#'67276767s    e;0 4 &*26 iÿçõÏ159=AEIP735333##3#3#&''67&'67#5367#535#5#5;5#33535#33535#33567#u01 16DM  +  )%30 0N0R!!4#( 'Ä  -    -  ' E Q  kèÇ73#3#5'67#35#Î~—*6:ppÇ 9% 8aÿéòÆ73#7#5'75#35#35#675#g‹>'"@@@@%@Ɔ2/ ’$$\$Y ÿézÆ73#7#5'75#35#35#75# l $"%%%%%%Æ…3/ R \$ ÿé‡Ç73#7#5'75#35#35#75#q  &*#......Ç…30 R \$Y½ 7##55#3535#Y33!!!!!½¦º1!!!!T"T½ 7##535#35#35#T/½¦¹2"S!T"[ÿéòÏ#'+73533533##5##5##5##535#33535#35#[!0 0![%%7$[%%7$$¯ )‹‹6$$$Y$$$\ÿèôÏ4735#5673#35335#535#53#3&''67&'767#i56##-67& '"  eL[ ]]^ fÿèòÏ6735#5673#35335#535#53#3&''67&'77667#p12 +23! $    _L[ ]]^   PÿéòÐ+159AE7#&'#'67#3&'73#'655'673'3673#3##5##535#ò   20w!DE>?aaaaa<<<     ! :5 ,68   ^  7 7JÿéòÐ+159AE7#&'#'67#3&'73#'655'673'3673#3##5##535#ò 51|!GH@Aeeeed@@@     ! :5 ,67   ^  7 7 …òÑ73#&'#'673#&'#'68># tG*  Ñ " #   òÑ73#&'#'673#&'#'68>$  rH+   Ñ   %  tÁ7#3#567&''67&'tOOb%   ÁœÀ#  ÿÿ\¼ 7##55#35\66##¼£½I7766PÿèòÏ!73#3#"''32765#'667#53&'® 4VN  8> Ïc# 4+57QcÿéòÏ!73#3#"''32765#'665#53&'° 2QE   1; Ïf  <,4=GiÿéòÌ73##5#'66556à $0Z!'! 5Ì ,††?5 ._WÿêöÏ"73#&''67#5353533655#335ä?) (.3;222!§N130 ".N((N ** <dÿéõÐ *7&'367&'#"''255#3'67#‰# !! H   5 6  #Ð  3:R ŽH"6TÿéòÏ'+/3773533533#3#"''3267##5#'67#735#3353355##5#`$**4   *'( $6(µE"2 RR2 (G T""""Qÿê÷Ñ 26:73#53&'33##367&'67'5'67#5#5;5#35#­>“@(p-  :   $JJJJÑ ."!   = /  .  !2ÿéWÏ73533#&'#5'67#   // y|%%/ÿêQÐ73533#&'#5'67#    00  ‹x )QÿèòË15973533533#'3255#'655#'3255#'655#75#35#V <=  5cm^^^^W  R=%"? W  R=%"? MMMM `ÿéðË#+/37;73#3#5##5##535#3#73#3#'3##5##535#33535#35#h‚:@.,>6? !!>r\$$6&\$$6&&Ë (AA( -h h&: QÿéðË#+/37;73#3#5##5##535#3#73#3#73##5##535#33535#35#Y‘AG53E=""G##F!!F%%8i++=,i++=,,Ë (AA( -h h&: dÿéðË#+/37;73#3#5##5##535#3#73#3#73##5##535#33535#35#l~7=++=5=<< 1W""4#W""4##Ë (AA( -h h&: kÿéñË#+/37;73#3#5##5##535#3#73#3#73##5##535#33535#35#tv3:((:1999/R 2 R 2 Ë (BB( -h h&:VÿèïÐ+/377'73#'65535##"''3255##5##5##535#335335 7p]]s      µ >#73 (6a,@e  !####<{.CÿèîÐ,0487&'73#'65535##"''3255##5##5##535#335335—?€mmƒ  "µ  >#83 )5a,@d !####<{.sÿèõÏ"73533#3&''67&'#535#67#{111+   ,1: : ¨'')'    ')nJÿèôÏ '.73#5##53&'3#&''67'67#53667#¡ ?pC[ #!9 26 %- 3Ï++ 6&  [! ^ÿèóÏ &-73#5##53&'3#&''67'7#53667#¬8a:O 1 +.')Ï** 6&  'Z  aÿèóÏ &-73#5##53&'3#&''67'67#5367#®7]9M 0 +. %#'Ï** 6&  D  mÿèóÏ (.73#5##53&'3#&''67&'67#53667#¯4W4K * & ! %Ï** 6&   Z  vÿèòÏ (/73#5##53&'3#&''67&'67#53667#´/P2B ' $ " "Ï** 6'  Z!  ÿèzÏ &-73#5##53&'3#&''67&'7#53667#A)@) :  "   Ï** 7&&    'X XÿéóÏ$=EIO73533#3#535#3'67#3#3#535#3#3##"''3255#535##5##535#&'a9==7|29•€f%B’=.› ..u>CÅ   $    #   E E+  RÿéòÏ$=EIO73533#3#535#3'67#3#3#535#3#3##"''3255#535##5##535#&'\<@@95<› †l(F˜@2  22{AEÅ   $    #   E E+  dÿéóÏ$=EIO73533#3#535#3'67#3#3#535#3#3##"''3255#535##5##535#&'l5992t/5Œw a#=ˆ8+ ,,m:>Å   $    #   E E+  ÿè Ï'+/37733533#3#"''3267##5#'67#735#5335#35#35#35#9%%/ - %+ %%%M#ÏC"7 "SS3!(F 1 T""" ÿî‘Ä&73#67&'7&''67#'75#53533#~< */+}8E7//00Ä) # ( #  ÿéæÏ 73#"''325'3#Ò   2ÏÌ  ®‘ÿé¤Ï=73#3#'6553&'#53&'67#3533#3#7'75#535#'6_: &{(8!4((""'j1%%Ï :/% %-C B    ^óÏ !'73#'&'7&'''6'&''6''6™m ³  R L ¼ (N Rc Ï@@      #    WóÏ !'73#'&'7&'''6'&''6''6™m ³  R L ¼ )N Pa ÏDD         IóÏ !'73#'&'7&'''6'&''6''6˜l ´  S K ¼ *M Qb ÏLL    #   %   OðÈ +07#3#3#535#35#5#7#53&''67&&67#€%%%%i CCh`   $ 0È2x#33R0   ]ñÈ ).7#3#3#535#35#5#7#53&''67&67#€%%$$i EE!!i `   ! 3È-k-.I $    dôÉ */7#3#3#535#35#5#7#53&''67&67#€%%$$i EE!!k [   -É ( _ % % < %   FðÇ */7#3#3#535#35#5#7#53&''67&67#€%%%%i CCg`  " 0Ç5€%87Y4     " ÿõ‡Ï!%+173533533#3#3#5#535#35#35#35#&'''6 1ZOc %111111=   ¼V;MV44)   ÿùiË73753#5'53675" ˳ˆ¥ ¤˜eÿéóÐ'+/3773533533#3#"''3267##5#'67#735#3353355##5#k$%%. ( "& $6$´D"2SS2!'F T""""SÿéóÏ73533#3#535##5##535#SEGG<Š:EUUU®!!##Occ?,OÿéóÏ73533#3#535##5##535#OGII=>>5}4>‚LLL®!!##Occ?,mÿéóÏ73533#3#535##5##535#m8::1t/8zEEE®!!##Occ?,yÿéòÏ73533#3#535##5##535#y322+j+3q@@@­""##Pbb>,UÿèôÈ@HPTX73#3#'67#'737#'67#'7367#53#3#333267#"&5'37'#5##535#35#\A n  E  F  _XXXXXÈ '   F        _ _!1EÿèóÈ*2AIMQ73#3#'67#'737#73#3#'67#'737#37'733265#"&5#5##535#35#OG  RH   &  P ]]]]]È % & 7  %  _ _!1EÿéöÏ"&*@73&'73#3#3#3##5'65#5#5##53533#&'#5'6l&7<6655=z :*****   ÿê‹Ð',07'67#535#5'67&'3#3#&'367#335J   #+ & <%, /*!A & A5 ‚ÿéî¾73#"''32767#'667#'6–X     ¾¦.,tXLHU!$ ÿê‡Ñ"/<7&'3#5'63#735#'3#735#&''67&''6D  7= '!11L11  >  Ñ   0??3  %  ÿêŽÑ"/<7&'3#5'63#735#'3#735#&''67&''6I  :@ *"33O33 A   Ñ   0??3  $  ÿêÑ"/<7&'3#5'63#735#'3#735#&''67&''6I  :@  *"33O33 B  Ñ   /??3  %  GÿéðÌ #)PTX\`dhn7'6553'#3&'7'6'3#''67&'3#3#"''3265#3#''675#5'67#735#3353353535#335&'#6m•qqbk* 4ErQ]   /& $,%  N% ¤;B< 3@c(   $    #G/    /   ÿé¨Ï159=S73533533##5##5#'673&'73#3#3#3#7#35#5#67#53&''67'&!!!!&    56....7‚8%%%%% _{   +  à  >   R   -   lÿéôÏ!'+/573'33#67327#"''67&'#7&'3#735#'6lJ+*    Jp  [88)$&©&/3 ($$+"D7 DE!:  gÿéöÏ &*.473'33#673267#"''67&'#7&'3#735#'6iG0/   Hs  ^<<,&*©&&')#5 &8)/;7 CE!:  iÿéìÇ 26:@7#5##535#&'3533#67325#"''67&'#3#735#'6ìbbbS C0     0)) ÇÞ ÞÀ®$ %'2,  SÿéêÄ73#"''3267#'67##5##535#W“ ?1 *,Ž```ÄK.=1_iiG4 QòÑ &9NT73#"''3255#'67#5353635#&'733#"&55#'6553''67&'767#&'C&   0 00œ &_  !  % IAÑW   /5  #   4      hÿêóÒ&*.267&'3533#3#"''3255##5##535#35#33535#335Ò  \<==6   #"5<""4#W""4#Ò !!ˆ  555Ÿ@GbÿéðÒ%)-157&'3533#3#"''3255##5##535#35#33535#335Ï  ^>??:  '':>''8'_''8'Ò !!ˆ  665Ÿ@GbÿèõÍ733#&'#67'5#537'6ya5, 4 QÍZA!M`  kT * XÿéõÐ !73#'63#3327#"&547#~]f ddt `Ð  !%% %^ÿéëÆ7#5##535#"&55#'655#3335ëhh PÆÝݹ* k.7# -.ekdÿêëÇ7#5##535#"&55#'7335655#ëaa  KaÇÝݹ, i-8" ~cit*-mÿêíÇ7#5##535#"&55#'7335655#íZZ  G ZÇÝݹ, i-8" ~cis(-€ÿêîÇ7#5##535#"&55#'7335655#îII   =IÇÝݹ, i-7# ~cip'-wÿéòÐ767&'7''6#5##535#¥! +0T>>>Ð8 & FhhF4‚ÿéòÐ767&'7''6#5##535#« '+O999Ð6 & GhhF4oÿéòÐ767&'7&''6#5##535# "$ .4XDDDÐ8 & FhhF4dÿéóÐ767&'7&''6#5##535#š&( 49 _LLLÐ7 & FiiF3 KòÐ#'+>73533#3#3##5#535#535#35#33535#33573##5#'6556333,,//66,,3+E+w )K!.Æ  ;   ; " G AA$ 5KòÐ.26:>73##5#'6556'3533#3#3##5#535#535#35#33535#335å )K" .±133,,,,22,,1+F+Ð AA 2 ;   ; !  ]ÿéòÏ#'+/73533533##3#3##5#535#535#5#5#35#335`4#66AAAA55#a4##6$¼$<((<$N ÿì_Ï7367&''65''6-    $Ï> $% 5N&+$ÿê\Î7367&''65''6-  %Î=   & /F6+ #YÿèõÎ/3733533533##5#3#3#&'#5'67#535#5#535#s=c6;0" "% #-:.RÅ++: 88L+CõÐ/57367&''66'3#&'#5'67#535'6'6®  "<%%  *-"1F  Ð" $#17 (.  `ÿêìÐ73#"''3267#'6##535#„^   QI3 ÐŒ|%(XgG5oÿîóÄ 733#537#537#37#37#zo „(.-1/ÄÃTIII°TjÿéõÇ!%)73#3#&'#5'67#535#735#33535#335zk,5'  *7-,E,Çc(.LK*&9A`ÿé÷Ç!%)73#3#&'#5'67#535#735#33535#335rs0;, "# !/=10M0Çc(0ON,'9AnÿéòÇ!%)73#3#&'#5'67#535#735#33535#335}g*4'  &3+*A*Çc(-JH('9AuÿéõÇ!%)73#3#&'#5'67#535#735#33535#335ƒc(/"  %2)(=(Çc(.MJ)'9AeÿèîÈ  $(7#5##53#3##53#"''32=#335îcQQQQl  FFFFÈSCCS!c-e  A`ÿéôÈ !'73#3#535#3#735#35#35#'67&'mz4?Ž;2zzTTTTTT IÈ2nN.-$     XÿéôÈ !'73#3#535#3#735#35#35#'67&'f8C•>5YYYYYY KÈ2nN.-$     eÿéòÈ !'73#3#535#3#735#35#35#'67&'rt1<ˆ8/ttPPPPPP E È2nN..$     JÿéòÏBFJ735333##35#53353#5##535##5#3#5#'6753353#35#535#535#33535d88 8+  ++"  *99@@8I%%% ; &ƒ(&4 DD 4& > ' ; hôÑ "(AGM7&'7'673#&''67&''667#'33#"''3255##5##53'67&'(  _ 8=      $c/   2  3 Ñ        "1 226E   ÿéæ  $7##535#35#7#"''3255#535#5#vG4444·  I7777Jj´) ,›  LJ) ÿèõ^%73#333277#"&55#67'7#&'Þ˜l    ] '2k ^2   - W*  pÿéìÅ 7#5##535#3533#3#535#35#ìXXX?ÅÜ Ü¼ª&DDP$rÿéìÅ 7#5##535#3533#3#535#35#ìVVV<ÅÜ Ü½«&DDP$ÿêbÃ73#3'67#&''67#R*.91    ÃZG >C -9ÿê^Ã73#3'67#&''67#N&*5-    ÃZG =D -9ÿêSÂ73#3'67#&''67#E"$2 +   ÂYG >D  .9;ÿðóÅ 73#735#35#3#53535#35#35#_yyQQQQp¸))Åg=C=JJJ88888pÿðôÅ 73#735#35#3#53535#35#35#†ZZ4444P „    Åg=C=JJJ88888jÿðôÅ 73#735#35#3#53535#35#35#^^8888T ŠÅg=C=JJJ88888 ÿéxÎ $(,>BFJQ7&'#5'6&'3#735#'3#735#'3#735##"''#5##5##535#3353353255= 7 %.  % . . W      Î   0  0  0 1J """*]%$<ÿèñÓ4<@DHLPT73#33##3#"''3255##5##535#535#'6553&'5#35#5353535#33535#335¦?>2 27   $!400;I;;0BR!!3$W!!3$Ó   U  !j  C3 3>\) &   D ( CÿçòT*06767&'7&'#"''3255'67#'6767'67&'²!!#  $%- d R     4     GÿëôÏ>D73533#3#535#3#735##5##5#53#&'3#3#535#535'63'#6JKMM>?K‡‡``‡ˆ<"x (<B73673#&'#'67#3533#3673#3#5##5'67#535#35#35#$1     * 6/ '( ////½      _ N V4ÿóÍÄ7'673&'767U) l'"-2 6 15  A  '1ÿòðÇ "&54632'"32654&€/AA//AA/'65('66?,+??+,?Ä5$$44$$5vÿêŠË73#vËáOÿì°Ë73#'3#œMËßÃ4ÿéÐÌ 73#'3#73#¼ˆCÌãϲ¡’ÿîßÂ73&''67&'76´  ,M P")+ -*ÂL+7/# Xó¨ 7&'3#{ Xææ¨6 )óÀ 7&'3#3#zXææ»»À74ñÈ 7&'3#3#3#|  Yâ⯯ÔÔÈ0"(ÿøéÅ73&''67&'767#'6_^3%<?-+ 1) Q Å C0%  %- !ÿìüÌ '654'7&547'3#å߬¬8438;5791??1.=<0ÿìüÌ '654'7&54773#3#åß ³³8438;5791??1.=<0·qÿìüÌ!73654'7'7#&54773#3#"»¸ ££ ‘‘,)38;5791??1.=.'¨8ÿìüÌ (.3'654'7&5477#5##535#"&55#'7335655#åߦ€€! _€8438;5791??1.=<0Ç»»™" T#0 eNTd&#ÿìüÌ(,73#33654'7'7#&547537#537#35#0J F³+*- >5E5´5`1-38;579 1??1.=3+N5•NÿìüÌ #)'654'7&5477&'3#'67&'åßT  B®®.a8438;5791??1.=<0Ò0 <"*0+3 5*ÿìüÌ &'654'7&547733#"&55'753åß§]LR <?8438;5791??1.=<0— K MB@ÿìüÌ  '654'7&5477&'#'6åßr" 807 18438;5791??1.=<0À3U4zt:5ÿìüÌ 3'654'7&5477365333267#"&55#'667#åß-8  #$ ,8438;5791??1.=<0¤ # s<85,ÿìüÌ '654'7&547'3533##5#åßPQQP8438;5791??1.=<0…PPqqÿíþÌ )N#654'3'#&5473533#'#5'67#73533#3#&'#5'67#535#'6ãÎ   ] !!+$  *0??1-DC´1??0+CD-*)) vk%51 %%)&%SX))) ÿíþÌ 6:>#654'3'#&5473673#3#"''3255##5'67#35#35#ãÎ3X` b  Q #*/QQQQ0??1-DC´1??0+CD-  y  2s "(E=ÿêþÌ 4:#654'3'#&547#5'6327#"&''7&'37&'ãÎ/  "œA  %++0??1-DC´1??0+CD-Ÿz10"3E< &%; ÿÝÿÛ +"&54632'"32654&3#3#3#535335#€5KK54KK4.AA.-AArŠ744>š@#K54JJ45Kî@..AA..@*'3RRkÿÝÿÛ #"&54632'"32654&33#3#53€5KK54KK4.AA.-AAAFFO¢?#K54JJ45Kî@..AA..@/EÿÝÿÛ '+/"&54632'"32654&#5##5##535335#335€5KK54KK4.AA.-AA"55HG55G5#K54JJ45Kî@..AA..@>UEE W));)))ÿÝÿÛ %"&54632'"32654&3#&'#5#€5KK54KK4.AA.-AA€£L" C#K54JJ45Kî@..AA..@1Q„ÿÝÿÛ 4"&54632'"32654&3673#3#3#535#'67#€5KK54KK4.AA.-AA€2W]a1=–E( ! +#K54JJ45Kî@..AA..@3 ++ %#ÿÝÿÛ 15"&54632'"32654&3673#3#5##5'67#35#€5KK54KK4.AA.-AA}8SY `N  +11NN#K54JJ45Kî@..AA..@1  [ E #c- ÿçõÏ;KQW7&'#673265#"''67&'#3#"''32765#'655353'3#3##5#535#&'7'6Ú   %    ' 8²]'++++#  R  Ï  0!"= / :9%M/8. .6P''^SS^ % ÿéñÊ08<@HLTX73#'#5'67#535'6'3#"''3267#'67#'#5##535#35#7#5##535##5##535#°     W:    ÑSÊ )eb!$#M6A6¼ÈL;ˆBHNVZ767&'7''6'&''#"''3255#'65535#35#'##55#357&''67#5##535#À   &     ` ]Ï9$  3  Æ  :3$&UV<+h. bªÇM<<<<(  ,7.1.iiI8]êp73#ÕÕp ÿõðÇ7#53##5'67&'3#zfØZ(1?X'&!,€ÚÚ´Œq1#)$"-+*ÿøñÐ73533533533##5#3#5#5#!'111V'«¾!ž1’22>>>>VVt‡CCCÿèñÃ'7#"''3255#&''67##5365#53#â  F! +7CXhâf  u %$8•¨ÿ÷ñÊ 73533533#7'6'&'M MâÅ”   ÀÀÀÀ§:/ .5-78. ÿòñÏ  73&''65'3&''653# ,&1 @_!52ââÏBD 00)<@*5 4Mžÿ÷îÎ-1767'67'67676'67'67'676763#Ó $%+6#  ] %#)4! QÛÛ›<""!61=#" 50ÿèñÃ267#"''3255#&''67#&''67##535#53#'#3â  $ & (=MâG&&ŠŠ  rA  9¢&&&&ÿéõÏ&,273#67&&'#67'5#535#53533#&'7'6ˆhc  & 05 .$ *eXXYYI  “c  0)P  ^B  vÿêŠË73#vËá ÿéöÏ 7&''663#~#E C%(@-0 Ï ><9$02‘ÿéæÌ 7#56'&'Ó-/7~''Ì.Œ•!,!%ÿçåÐ7#5##5##535335#335åHEYYEEYH©{WW}''XEEE ÿéòÇ#73327#"'&5#3#3##5#535#535#º  M99GGGG>>DÇcc+%-„)%XX%)ÿæïÑ73533#3#3##5#535#535#X[[PPffccOOXª''"$EE$"ÿéãÌ73'67'536773353#5##YI < 5--Ì}>( / sfoya WÿèäÎ!%)-16;?73533533#3#5##5#'67##535#735#33533535#365#35#35##3*44>+2$" <3 3*!^**<%)9-*=++´;I ??(  I<>  \ÿèîÏ  73#'6&'#53#5'#335#Xa"1  z44 Ï&  ƒtvK888P!¯”7&'c//”07 61ÿòñÎ73#3#3#535#535#53&'{ _aTThâfSS_^Î6DD6ÿéïÍ!'73533533#3##5#'67#535#5#7&'/I0077K 4 * 47/ŒI¦''&&FQQ1 %FFFF9  ÿçïÂ$*073##"''255##53#"''3255##5&'7&'ßße  0Ç  68|—   ³—   ³/!"$ !"%0ÿéÄ 73'>y%!" ÄV&7+5 ÿéñÂ767667&''67&'?. *2 8)*C A'1Á_(8#'>  $!-a ÿñòÅ!7673267#"&55'75'6Ð *3878:$ /+V] #fÅ;K %  P;ÿêëÍ7'6767&'7&''6m#: ;&8Ê 9;,NQ2$"8DÿêäÐ!%73#"''3267#3#"''3267#5363#hW  oœ  ›+M  Ð 8 MN5s šÿéóÏ73#3#3##5#'66R™wbbffÏ&'=° ( ÿêõÌ"(73##"''32655#535'2'6'&'Ü )2jj kk(-`H ÌWB =V%   ÿèõË #7'6&'333"&&#"'663367#Ú Zm b  F¹3PL/HA$ J2šË   #1   * ÿéòÏ'73#&'#'67#5367'63#'3'66Í Ws=0 :'B 7CL^OÏ !0/ shh/) #ÿèôÐ 773##5#535'2353#5'675#73673267#"&5Ò $,ccff#(Vj:": "   Ð¥¥]} !+$ %  ÿóõÏ&7#"''327653325667##"&55'753Õ  f*" B%3:¦Q +_  # b MH ÿìéÑ #73#'6333267##"&54767#H š(ŠZ&1((B MfÑ &"=!% 6 ÿèÞÀ73#"''32765#&''6 ¾ ©*#" ##h9GJÀwM€  /ÿéÌÏ76767'67'67'67676¤,50& JQc 3;"#%-&$°@' :A/  3.ÿéèÏ#)7333#"''3267##5#535#5367#7&'fF1WTT>>:2>Ï-&C 1__4G4qLìÒ"&*.73533&'73#3#5##5##535#35#33535#335a<  aS??Ta"??T?“??T?¾  VV ' #  ÿê“Ð BHN7'2'6'&''&''67'67676767&'7'#"''3257&'''6… /C71  $   5       /  H Ð       k   , : )   ÿéúÒ+/37;?CGK733#3#3267#"&55#535#5367#'635#33535#33535#33535#335G V9N\\# 0.TTKZR +88K;†88K;AATIAATIÒ  H H   H H 8 & B & PÿëÄ 73#"''3265{ Ä¿  ÿéóÏ+/39?73533#33##5##"''32655#535#535#535#3535#'&'7'6]^^O; UUkkUU]q;;;;A  }  ¼))4  )<Aa    ÿèóÏ37;?C73533#3#33##5##"''32655#535#535#535#535#35#3353535cffRRUA [[ff[[QQc&==Q>>AAAà  3 !*   3 + ?!ñ²73#3#$··ã㲌 ÿóôÁ73#33#537#'7#7#Õ† z=è• t:lÁ#r-kh22ÿèó¾73##'6765#53##&´´~A2  @ã;¾=%H<„ÿôðÁ73#33#537#537#35#Ìh` à559P?cPÁx -Q “ B !     8  ÿîó£ 73#735#3#735#3#735#35#3##ºº””rrPP,¦¦€€€€3ææ£S3% 6?& ! "Bÿé¤Ð 7#5'6‘  7Щ‹9ç73#ÐÐ8ÿëò¤7'6553&”9M:K;$3K** $ $ ÿéöÐ 7'67&'&'3'67#€,> N(28 81  H«*#"ޝ0*4 06:+ ÿêxÐ73&''65> 3Ð=;$?\JÿíòÍ7'6767&'7''6“./B'.(, 7>9ÍP8 :e*  '/ 3O ’È 767'56‹ È   žÿçño 73&''65'3&''65œ"(+ 5_  .o#8&& -( %#4ÿêè 73533##5#7&'7'6b__b2    2^^55Q   GÿèôÏ '7'67333#"''32765#'67#536y #qiM  8#$ <,-š $+ \ 7*3@SñÇ 73#5##53&'3#Ÿ 9f=7žžÇH58K«FÿèñÃ!(73#3#3673#&''67&'67#67#^ƒƒ ——4b  ': - +Z 6 à % 2 FÿéòÏ073#'6553&'732765#"&55'75'6Ÿ @€<K 68  +.5Ï K?0 0:W /*< ?'@ÿéòÆ $73#735#33267#"&5'3'6673#V~~UUM  Z,ÆX2RY  [6. )/e>ÿéöÇ.267#3267#"&55#;5#'#"''3255#'65535#35#í? !  >   ÇyHÅUUà  89 !>x:(c*>ÿéšÏ73#3#"''3267#'655#53&'u/*   ) Ï!f!PK/ 1H'?ÿé’Ï73533#&'#5'67#F 22pk*/IÿéòÌ'=73673267#"&5'37533#7'633533#&'#5'67#§  S $,EF7% &$ #3Ì%  !  DGZ( ""+FE)"AFòÐ",7333#"''67&''667##53&o* 6=  \ Ð %     +[UÿéíÏ1733673#5##53'&'3#3#67&'7&''6˜   !sC&  ^^‘‘, !"17Ï38&'9+ L  AÿîŸÍ (73&'73#&'''6&''67&'76J#U9 2    «      @ÿéòÒEKQ7&'#67327#"''67&'#3533#3##"''3255#'6553&53'67&'Ø   #*    N$  !aT  ;Ò '.#?! % 5?<4H  C=- 09UkEÿéòÆ%+17=73#3#3#535335#3##'3267#733&''&'''67&'SœD::G¦E$~ ƒm*    ;  0  Æ''4f;#; &  Dÿë¥Í $(733#"''3255##537'6'&'3#735#k$   9&* ;  && ÍI‚  n‰™?   UF)BÿìŸÈ%+73533533#3#535#35#35#35#'67&'F  ]   .  ¯kkAB0  CÿêîÈ 'B^7'6553'#333##"''3255#5357#33##"''3255#5357#'37#"''3255'6757#i‘kk_i(1  66J::   #C9    "—4D5 5?_1    :    >ÿì¡Î $*73&'73#3##"''3255#735#&'''6F#!Y F  ""0  2  ³  GB  ?%@ *ôÏ,Y75##535#53533#3#"''3255#&'#5'6'5##535#53533#3#"''3255#&'#5'6°(,,//)   Z)--//)   s&7#  33  &7# !  /2{ Âb 7#55#35ÂG5##bXX$>ÿìõÏ.4:JPV7367&''65'3#&'#5'67#535'6'6&''33267#"&57&'''6¼     &.   #2    _  h Ï3  (# /.  62 "_  4  0" Uÿéð873353#"''3255##5##U@B   ?8##9 %##>>ÿë¯Ï1573533533##5##5#3#"''3267#3#5##5'635#L9  /,  ¹ n[ Q JS(BÿèõÏ,M7#5'67#53533#&7#5'67#53533#&3'7#3#33#"&''675#}    J    Œ–*55'!4% C‘)#   $#  !  . L:ÿè›e73#3#"''3265#'665#53&'t2.    $e 2 " !<ÿê¢Ï(.4:73#3#3##"''3255#535#53&'#53&'367#'67&'r    !   C  Ï E  @HW    CÿéðÐAEKQW]ci733533#67'275##5#533'67333#"''3265#'6553&'37#67#7&'&''&'''674'u-%% :R01-"" G     q QWX_2: +  *Ð    8    !2 0/ 32  _  p     Bÿé‹Î 73&'73#3#3##5##535#B I==>>>²  R V3! GÿæõÐ!59?CIOU[a7'673&''67&7673#73#3#3#535#535#735#&'735'6'67&'7&''&'{ O& &z599@’@993!! /#  R  X5  ;¢     ¤t8      T     GÿéóÉ +KOSW]ci73#735#33533567677&'7''67'6773&'73#3#3#3##5'65#5#35#'&''67&'Q™™-y   A B %(  * %É7T ,    d '%   9ÿéõÌ 159>V]73#735#'3#735##367#533#7#5'75#'65535#35#675#73#&''67&''667#žJJ))gJJ((•”0+>  1 / C*     Ì/.(% > J 4( */HS*+ g"      " ÿêô 73#32667#"&55#'>5#ÜC -%# " D¦ % ­J#3(1Nÿéò¬)767&'7&'32767#"&55'67'6i6; $ ' B ; /*¬-  !% S! WO@ÿéöË%+177'23533#32667#"&55#'67#73675#335&'Õ Mo\UMT<  ) OJE69MB Ë*K> #  E?3  %B  ÿëòÊ  (73#735#35#'3#33267#"&5'3'66lqqIIIIWc   %=%"ÊvDPCt >   C((  ÿèöÑ,16<7367#'6733#32667#"&55'67#7367#335&'(_ T ,^ 6D   -  4$ DC6=Q<   P8 #  8, 4*E  ÿèôÉ (73353#3267#"&55#'6765#7#5#35"L#L8 &' . '5ƒK$“É88{D ! K,+ #h88UUgîŸ 7'67&'Q- 'w&%Ÿ  ÿèòÏ '/@73#53635#35#'67'5673#535#'66733267#"&5t%T ....#  52!W%' %!8   #!Ïqq :KS ^  y~Xo$& 6   ÿèðÇ)673#3#"''3255##5##535#&''67&''&''67&'ßeY  EEYf”  I  Ç“  {ŽŽš­@%,%, ÿéôÅ 7&'''6''6767&'¯* - F)-k! HG /!3>Å=#,EH)/@(.  1HR  ÿïòË 7&'3#'67&' XÛÛG'(0l+*Ë8 D'07/38/ ÿêôÏ 7&'''63#3#"''3265#7#œ'1 -,.: 8–g m rÏ17 3"%(C'1ÿøðÏ 73#536'&'3#3#°>ÊuV  ªªààÏ q4ÿèôÐ%7'67#53565#53673#3#&'&'‚UU WZOp 4XbXJ FR <:6%-6Ï  ÿèòÎ%+73533533#3#535#35#35#35#'67&'!b +å0!5bbbbbb 4 -f*!#(²kkCB0 ÿèîÐ-K73673#53&'&''67'67676767&''&''67'67676767&'S 1 ?Ú> ›#* "']#* "'Ð¥ %.+E&  %/,E&  ÿéòÑ.6:73673#3#3#&'#'67#5367#537#53&'3'6673#Y .  3YQUq=) 5$> -ARDJY6 NÑ    "$    ˆ%"   S ÿèòÐ#7;?CGKOU[73673267#"&5'3#5'675#533#3#3#535#535#735#33535#3355#35#&'''6‹" #( #/%(!FF1ª!00>ä>22!99L8„99L8"@@@D## $$;) %Ð    (>  9F    , & (  !   ÿéãÐ!733#"''3255#&''67##53uZ G%) 6CWÐ-  … ''+(:§º#ÿéáÂ7#"''3255##535#335á –@@TB¿  MlÙ[HHH ÿêîÑ"73673#3#"''3255##5'67#H|… ‡ o,>´ x _~#+@¾œ767&''67&'R ""”! ÿêóÐ $(,73##"''3255##5#53535335#33535#35#Ù  ‰MM99M<‰99M<<µu*  %CCu;(((b''' ÿéòÉ ;@E7#5##53#3#33#3265#"&55#'67##5367#'6367#335Ô!qqqq O4;  *#I :$W L .18M9ÉK99K  9  & > B _ê¢7#5##5ꬢC00CÿêöŒ73267#"&55#'6655·  E!Œ & t4+);ÿèôÐ)7#5#'67667##5365333267#"&5ãP1 <Q    , ¦A.H ' 6(-@0o %  ÿëߦ73#"''3267#733#N• ” Á¤¤yO"1]Fÿçî²"73#3533#3##5#535#'67#536q ^i1EE__hhK$1 ²00ÿéñžD73#67&'#"''32654''67&'&''67&'767&''67#/¡E# () 0? G/ - ;Až 26 **       ‘èÆ7#5##5è§Æ5$$5ÿèí£ !73#735#35#3#53&'&'''65——oooo9bÚb4&" $$@ ' #£Y65' $  wܧ733#3&''67&'767#53,,(    >§   ÿìå¬+17=C73#35#535#53#3#"''32765#'67#56&''&'''67&'t$44z333Fw—  ” )W    C8  ¬  Q@  Lt   ÿéõËDHLPZ7#5##53533533#3#3#&'#"''3255##5##5'67#5367#535#35#35#35#3533&'#ï·#<$$ `…4% 0,  )?+#6<<{{{{20 G Ë-- A   ** ( A! ! G KÿëÅ 7&''6_)ÅU 941 ÿéôÒ$*73&''67&''667#&'&'^i'2 32.? <'4 \2# !0"V; 9TÒ       $ P  ?ÿë£Ï%+73533533#3#535#35#35#35#'67&'D  b   3  ³ii@A1  Bÿô°˜ 73##5##535#35#Bnne99999˜{ {0MTVîÊ73#3#535#535'2Ö DD>ˆ6BB<Ê ÿéõÐ+9=C[733&'76767&'#5'67'7267#3#3#53&'#735#367#3267#"&55#'667\ B  %M'  E!ˆEÙBbb'9J  D Ê         32  3  &  ÿéãÏ7335#53353353#5#353#5#FP<;;F´SLcQ>RR>XcLj ÿêðÏ735#53533#3#353#5#5335#gRRVVee=ž9go(%%(Q?`R?Q0ÐÅ%+7367&'#"''3255'67567#&'2ž$ $&   $ .| Å ! '2 #4ÓÇ173##'325567#3#735#67#53&''67&'9‚  e00V .@    Çp w &^: * ôÏ 373533533#7&'7'63#3##5#535#53&'736PSæ'  ¡   277882   š5555B   8   ÿçŒÉ73#'67#53655'6 013.46.;É1O$ D * ÿçò` 7#'6553#"''3267#'667#òº#  G, '.`!#! 3#7   ÿé¢ 7'67&3##5##535#U# 0% HOO\CCCr  K L.ÿë¡Î&EJPV77'7&''5673#"''3255#'67#'#3#7'75#535#5'63&''6'&'J   +D     445    & @2 2W[_w{7#'733#67&'#"''32655'27&''67''67#53'373533#67&'7''275#735#33573533#67'7''275#735#335xHM)E    "/)),* ?b3Bt #     "  ¤ !     | /   /  GlÎ73533#7#"''3255'675#%!!    %³  mÿêóÐ87'673&''67&76736533#"''32767#'67#“  ?      V$9   &1 +! *     # _ C2%!ÿçÄ973673#"''3267#'67#:-I  98 ,$' '$  ÿéÐ!%)-3973533533533##5#3#5#5#3#735#35#35#&'''6 =`s ];qqKKKKKK< # ½"3 1bE & %    ÿñ¹° 73'673'6oJ AL 63`°[/&Q>Z&Bÿëù­7673&''67&'8" +O H1<A"”!#3;&!70(!ÿù·’73'67#&''6d J#` W?   -’XF   "/ÿø»ž&*.2673533&'73#3#"''3255##5##535#35#33535#335/>;;   (*=>**=(e**=(‡  X  ###p.,ÿôÁ#'=73#"''3255'67#5353635#35#35#73#3#"''3267#535#K! 0'7&&&&&&B9(,    ,''„ O $ '(E86 8!ÿøÊ›"'+/73533533##5##5#&'#5'6&'3#735#322B#& `  ;>qqII‹    3; ä¨73##5#'66556Ï /?y26 J¨SS"" %-'5ñ®"=73673#3533#7#5'75#'67#735333267#"&55#'67#7%*  #*$ W' $ ™ ! e  XO D9ê¬73#5#535#535#53733#3#3##n55..33066//88¬ž1 é¯"&*06<7'673&'73#3#3#3##75#5#5#'&'&''6p   -/((((0g7%%%%%9  % m    d$$ƒ      5 éª #'+/73#735#73#735#3#3##5#535#735#33535#335=HH&&FII''^Ž>RROO=++>,j++>,ª& & #D   ) & 3ì²59=AE73#&'#'6'3#&'#'63#3##5#'67#535#735#35#35#5#¡7Q6 ‘ 33.  ,4llllllL+²      K   5 1 æ­ <@DH73#735#35#35#73#735#35#35#3&'73#3#3#3#5'65#5#5#8B ã° 73#3#&''67#5365#'6ha7FJ+ "$, 1AF! °! 2ð±107&'#5'65'3#735#Ž** b @F)……]]±  $B<é¨ $+73#735#35#373#&''67'67#67#Giiii-j  # 5 &-#_A¨D) " *     @ᣠ73#735#3#735#73#735#WooII+FF GFF £<;B B ÿêñÌ73533533##5##5#7L77L7tXXXXwwww ÿéðÏ !73533##5#3533533##5#'67#Z[[Z ,c))d2 *+®!!66C****ZZB5ÿèñÐ73533#3##5#535#7'6'&'[ZZffgg[­€  ‚NN*JJ*V   ÿéóÏ#733533533##5##5##5##5#53533\ !! ÊSSSWW{{ttuu{{XXÿòêÄ73353#3#5##535#rBV`tG[^Ã\]pK^buI ÿèôÏ#(-73#3##5#5367#53635#335367#33535oe9NN…! =?3??SA”6>RIR :Ï j11 j :F  + ÿçíÎ+17735#53533#3'67#&'36733#'67#7&'&'aTTUUc À8 /o S[ ;& Gd# v&!&Œ   4 !$=  9ÿèìÐ7&'3#&'#5#x  Q×f-  ]Ð .(\œÿéïÏ 73#535333&'#…jÜ^Oc/" !#šT">DÿæëÏ73#5#'6553533#3‡UMd#ŽŽ¦c.' (38…  Ï &?616`eT  j7 |Ð73#67'56u1GG!"'. 1Ð:I  ´AÿìåN7#3267#"&553#"''325§R +7  B9z  ;4  K&  \òÏ7733#'6655'667##"''3255#3265#"&55Š@ (j  91 _   $ %Ï 3- 1   . =   UÿíˆÑ (73#53635#35#32767#"&5536G0d >>>>F %- (&Ñ ]] 391  O =ÿèá¦736733#"''32765#'667#K0R  ?.}R3 L094+;ÿæð—73#3#"''3267#'667#D¬fX F1—[!B(13M3ÿóë¡733#3#535#53&'ƒBBT¸P>>:  ¡3UU" 7ÿóð©73533#3#535#33#3#535#53FBEEO¯LBBDDT¹QBB•9!!8ÿðó %73#67&'7&''67#3533#3#535#C¤X'* :@4BAAU»RB    W  9ÿèäª "(73#"''325'#5##53#'3'>&'Ñ   71lG  ª¦  ›€nm z`E'#  $% 3ÿñó¦#73#3#3#535#535#735#33535#335K?FFWÀWCC?++=-j++=-¦f<B6ÿéôª '73#735#35#33##"''3255#53567#Eššssss›$UU  TToªO0 +,  2ÿéñ±#)73673##"''3255#735#35#'67&'I1P> Cppppt› a7  2:=0 %9ÿëê¢ !'-73#"''3265'3#735#35#35#73#'67&'Ö  UU000000UQ 7 ¢œ “‚[98Lk     ,ÿçó®#4B7&'73#353#'67#53365#53673'67#'63&''65Q2 ! # (   #- :5 &  ) 2®  3 .?+!=, 4 $ 4*%-2ÿéô®%)-1673#3#3&''27&''67#5367#35#35#35#67#<®XJ]d" ,%%; , 3D jjjjjj> Q®X     X$ " ! G -ÿêò­ &973533##"''3255#'3#3#735#&'27677'67&'–4  4]ZZPP++\  R  0;  ‡&&q  l-D 4  2ÿéòª %,6>B73#735#35#333#"''67&''667#73&'##5##535#J””pppp7 8F   (V +nnnª;#         11 6ÿéõ¯-28>BF733#"''3255##5373#&''67&''667''6'&'3#735#_&   9&U2       F B  (( ¯=p  Zvˆ=3#  ) $)   EF&<ÿéë³8<@DHN733533##5##5#533#3#"''3255#&''275##535#735#33535#335#7&'i....--‹;M   )39L=**=(e**=(³ F : %  @Q * & 9 :ÿéó² &7;?KOSW[736533#&''67#'3#735#35#7&'#"''3255##55#35#53#5##575#'#3355#•!'  UKK....  <   8H88- 6H64444H6”  "-*  %, 8 _FF %  ÿçôÏ!73533#3#67&'7&''67#535#XVVks3; JJ ,\hX¬##33%, .3 ÿñôÑ'+/37&'3#&'#'67#537#'6767&'3#3#3#¦}C/816 +>M ), Khh„„»»Â   )&" k ÿèõÐ&,28767&'7&''6373#&'#'67#'6'67'6i.1 GO'?R zA"" )),: .Aˆ (@ B> 4S UN >s xÐ  / &"   - ÿéîÏ167'23#5##536'&''&'3&''67&'#367Ø Nq]M )°—?  -  ”"3 6(,: 9$)Ï ..N     ÿèóÏ/4735#5673#35335#535#53#3&''67&'#367(NW#66DA222FUM'#4=,-; 6')#I[  ``f   ÿéñÏ>CGKPbg733533673#3#3#3#27#5'75#535#535#53'#53&'7337#35#35#675#73&''67&'#367_*<6ZMMgv%,ePP\45' 8E'00000P`      Ï     ?I      [ ) + .  )ÿìØ¼ 7#5##535#؈ˆˆ¼ÐЧ”ÿêçÐ 73#"''3267#'6#5##535#K š ’ )cFFFБ1%v$3`f?-ÿèäÅ 73#735#3#"''3267#'67#537™™qq&b  PK BHMÅO)D G%.E5 ÿëïÏ73673#3#5##5'67#35#Oy€ r 9GErr¥re%,‚:U.È73#735#UssKK_9!ÿéäÆ73#"''3255#3###535#!à  ¯‚cPPÆÁ§M];) ÿéõÆ!&+/733##3#5##5'67#5367#5367#3353535#´""t ‹x &*5OTEYJOWqxxÆ,-Y D !  , ~&QÿèæI 7#5##535#æmmmI`a=+ ÿçôÊ %73#735#'333#&''67#53565#335I‰‰aa>¤\DM [ QX\PdAÊI#G2%  90$ÿéòÆ73#735#35#5333#'67&'V€€YYXª““"àI - )p#")ÆF"zn[1 aÿìõÏ,73533533##5##5#333267##"&5467#a((t? +5R­!!""(2  ,ÿéçÆ-73#735##"''32655#&''67##53672  xx¡  K! % A\ÆN(R_ G !ex  Bÿë¢Ï73673&''67&'67#67#T'   ¡C/   "$@&*#FõÐ#-7333#"''67&''667#73&'#E2 '"%S%&"  $  'S"Ð $     *.QÿöñÎ,0767'67'67676767'67'67673#’$  R$  ¡C$ (4/C$ (4-w‹óÉ73533#&''655#Œ%**  &%š// * *$4 )ÿèó¥%)73#3#67&'#67'5##5##535#KŠŠ"ÁP    N% %°mmm¥ *(  1BM M0:ÿéó£#'+735333##3#5##535#535#535#3353535#D<GGL{@<+>ÿé´Å $7'6553'#3###5##53533255j Y55  ƒO)"ONB00P jUgB 9Lÿè÷Ð ,287&''6'3#3#"''32767#'655#53&'&'&'Á  8/)   "WÐ%!%$0%g  =K. 0F-f.XLèÏ 73#"''3267#'67#'67#'6_    0 )% Ï N75$ -%  P±V73#735#Paa;;VG%AõÏ+07&'73#'65535#73#&''67&''667E!L88mC  #   ¹  J &.9(D"  Kÿé£Ð73#&'#5'67#535'6˜    #Ð+ id"0% ÿézÏ7'6553&'73'#3#5##535#+  )#99###d#67h L:()adB1JÿöªÏ%73#3#3#67'753675#535#'6g/ 1% ##  Ï &'VQY& MÿêŒÏ73533#7#"''3255'675#O   ¡..1 E  8:Pÿë—Î73533#67##'32655'75#T Ÿ//1? . < pòÏ73&'735#53533#3673# 7 __^^6å‚ '' ZÿéóÐ&733533#3#53'67&'3353#5#o55A™4   Q^r¾6HP".@cc AÿçòÐ#+177333###5#535#535#5335#35#3#'3'667'67&'˜5500==//""""(s    B  Ð'&uu';1ih0( #&#   ÿéóÐ3;?73673#3#3#3673#53&'735#535#535#53&'#5##535#S87\QQ^^8æ8__QQ\1–‡‡‡Ð      šD D( ÿéóÏ'+3773533#3#535#'3533#3#535##5##535#'#5##535#ƒ-//*h*-v-//*h*-Þ888*888®!!##!!##Occ?,cc?, ÿçæw !73'6573#'3#''67&'7&';1 )™Pe  Q  ]  w;4!';Œˆr"   Yÿð¨Ï '73#53635#35#3673267#"&5s"K''''  Ï \\ 2:1  ,ëÐ"&73#"''3265#'6'3#735###535#‚]   T ]BB3!!Ðj&Q zVC P2!ÿéëÇ %73#735#3#735#73#735#'3353#5#5335˜˜ss2TT..pTT--4D®EÇ=<== k&E 8&ÿëòÏ%)-1573533#3#67&'#67'5#535#3#735#73#735#`^^fi #W"-!* g` CCkBB»T  I@  NT54ÿéê‹"73533#3#535#'6#5##535#I'??\Ög2 ’zzz‹  AH H-QæÉ 73#735#3#735#73#735#6““kk0WW11aXX33É7076ÿëÐ#'73533#3#535##5##53##5##535#.11+g).pLBBP666¿-)+"T T6$ÿéƒÉ $(,73#735##"''3255##5##5##535#35#35#ff@@Z   É><}  .11??H’9'''''OÿèóÏ,0473353353#3#535#3533#67'7&''675#75##5#c!"6D˜A4799 @C%#7o&%È#**!2;>   SÿèñÏ>DJ73533#3#5##535#'67'6776767'7&'#"''32657&'''6_<>>?j=tTTTTTpI  %'' 0)Ìff- **       QÿéðÆ'-39?73#3#3#535335#3#"''3267#733&''&'''67&'^Ž=11A”>u  x j%7  .Æ''5g:$: $  Kÿô®Ï)73533#3#535#3#735#76767'7&'P%&&R %OO))   )3»2;4  CÿèñÈ @DH7'6553'#33#3#"''3255#63&'7&''675##535#535'235#335rŠee 0..5   # !%700-6#1—0D4 4>]1" , ? +@P , 7HÿéöÏ(JPTX^b737533#67'6773673267#"&533#"''255##5#'655'6367#35#33535#735Z %-R    )>' *( %52$$7*b%$7*¿2> E    [  '  #.Dÿï¤Ð%+73533533#3#535#35#35#35#'67&'O  V   2 ¬$$##^^9:*   PÿíóÉ%5;A73#3#3#535#535#3535#5##3&'#33267#"&57&'''6\Ž,%%0•.%%+>%%%%   ^  h  É78`%$$; 1 6 " Gÿë¢Ì $*73&'73#3##"''3255#735#&'''6O!RC  !!*  )  ²  EG  D#=Iÿè«Å#'7367#533#67#5'275#35#35#75#O,0F Ÿ  s"|JM Có{ 735#53#3# iVÀViæU ÿéóÈ ,48@D73#735#'3#735#3673&'73#&'#'67##5##535#7#5##535#SS,,€QQ--S. !L<G 2( ;DZ)))§,,,È>>A    % -M M2 M M2 ÿïôÅ#'+/37#53#3#3#535#53'3#735#73#735#3#735#73#735#vcÚcjjfàfiiSDD eDD ‹DD jDD ³NRRA55O55NÿïöÏ ,9F73#&'#'673#&'#'63#3#535#&''67&''6p*   S.  S?H¨M> d Ï  '   1dd   &  Hÿè£Ï /73#53635#35#3#3#"''3265#'665#53't$N ****.+   !Ï NN +-% 2 " ! KÿéðÏ/HLP733#3'67#37#53#3#&''67#'655333667#"&55'7533#735#•;;F o5-t6>7'+' " 0<9/ ) %``??Ï   H  /7R     () Hÿó™Ð73533#353#67'5#5335#H!    !®""XCT+ 5TCXKÿê¤Ð)/5;73&'73#3#3##"''32655#535#53&'#367#&'''6T     !##,  ¸  ? :+V    V…ðÐ 7#5##53'7'67&'ðt?"  @½        íÈ %+73#735#3353353#7&'''6'67&'ƒff   )' ,   C ÈH&&&&&=ql      >ÿê—Î$*073533#3##"''3255#'667#535#'67&'H   !L ³s  nA6 1;9$ OÿèòÏ8<@DHO73533#3#5##535#3#3#"''675'67#53#&'35#33535#335#7&']=>>CsC=p **ER h  P*C*!<à  "# …  * AAK %  NÿëºÎ%FKQW77&'7''563#"''3265#'67#&'#3#67'675#535#5'63&''6'&'u   4      !!+5$$!2 #.Î+  D; &! 4   !#  ;   ÿêìÐ#)/hlp7#5##535#535#53533#3#535#35#&'''63533533#3#3#67&'#67'5'67#535#535#5#35#ì²`LL]]^^MM::N::S%$#% ( $01455--Cb O   /G9''1x444…%%&& 0      *    nÎw$73'67#'6673#35#535#53#†9 &   8*Zw  #  AGÿê£Ï'-3973#3#3##"''3255#535#53&'#53&'367#&'''6w       $  ( Ï (F  AHY   BÿèžÏ"733#3#53533367'67#''6uN  & "  Ï885+/4ÿéìl!73533#3#535#'6#5##535#=,HHbØb6 ………k 789! ÿéõ”!%)-EK7#37#533#67#5'75#'65535#35#75#73#&''67&''667#î¿A9NA5$$$$$$[5      ”$  > H 3( (0HR ( * h!      #  LÿèôÏLPTX\bh73#&''67&''667#'3533#3#3#3#"''3265#'67#535#535#3#735#35#35#'67&'º0        m"## ,- ! $"‚‚]]]]]] # F Ï            WT<       SÿêòÏ59=AEI]aek73533533##5##5#3#3#"''3255#''75##535#735#33535#335'#3533#&''275#735#335&'#6U%+((+%{5E  "(&2C6&&6%[&&6%K:: :J$#:));)Ç 4(  -; .))  % LÿçöÏ0Ufj€73533533##5##5#3533#3#&'7#'7#535#73533327#"&5#&''67&'77#3533#7'75#3#3#67&'7&''67#N*.%%.*    "Q     N"*$£` # 29 +Æ       1;     %    "    DÿéöÏ!&*.26SY_ek73533533##5##5#33#5'667#35#33535#33573#&'#5'67#535'6'67&''&''4'O()**)(#H    %  W       %s M¾  S=  "5['NM&y =ÿì‰È 73#735#35#35#'67&'I88   *  È¡p O P!9  OÿéòÐ/Ycgt733#3#3##5#535#535#5373#"''3265#'67#33#3#&''67#&''67#535333#5#53335&''67&''6o 6I   <>>>4B7.%;ž     7 6˲+/373733#5367#3353#3#3##5#'7#535#735#35#F$:•&05ay&3311?K@TT/(¥  %&   = , Ò®9?E73673673#3#&'#32767#"&5535#5'67#5367#&'#'&';5 FT& A  +' B; !./b!       #      *T 1 ̰!-73533#3#535#3##5'67#735#&'76?777B—A7|C  "VVb   ¦   %,2$     6É« #73#735#3#735#35#35#'67&'KjjFF\\\\\\  A«) !W=  2Ï­!)-151073533#3#''67#535#7&'7#3#53535#35#35#C322?# *2 *>3&5 % ^ %$¢     / ,,,1 Ϊ#73#3#3#535#535#735#35#3#735#Cy2DD<‹ #§7 7    0а#'-373533#3#535#735#3353#735#35#35#'67&'@666F F6$$8$mXXXXXX F¨#  2J5  4Ê­ 5:73#735#3353353533#3#3#3##5#535#53&'#535#7#;ˆˆ(k0//=!5AAAA5  ?0G)­.,     &1Í« &*773#735#3353353#3#7'5'67#735#&'76>„„%}šš €K  XXa - «+$)      - б$AEIMQW]ciouŒ73'73#6777&'74''67'67276767&'7''67'673#3#3#735#&''&'7&'''6'&'''63533#&'#5'67#e5       _     L----)) T _ c   L   DCC$ ($7¢                             "  IðÄ73&''67&'#367*¦$$6<+*7 5&+#Ä#   #GæÆ !73#&'67&'6'&'6ÇÇeM o  Æ    RîÏ73#73&''67&'767#'3#O%g    PTÏ}w)    oLžÐ7''67'6767767&'Œ  M -#:7@$ ÿðôÎ+73533#3#535#&'''63533#3#535#&QVVdÛcQ‰"O !'OQQjçiO´4  "  CóÎ"3767'7&''667&'7&''6'67&'7''6f ') >GT  &+T %*Î   "  ""   ÿëõb"73533#67'675#73533#3#535#)))1<)t)**1v1)F( ,33>õÏG73533#7#"''32655'675#733327#"&'5#&''67&'767#53%    #%}+     ¯  2A/     ^ÿéóÏ '73&'73#&''67&'763#5#533^C>•b    o\© n 9çÐ#<7'6733#"&55#'665#5'66767#53&''67&'K$ "’ %   *  ,AXr$)  Ð !  ! K=     SBîÏ73##5#'66556Ù )8l#5  AÏFF "'"LÿêðÏ67#'66553&'767#533'67##"''3255#53&'ðy ? Md"   .5¹L?0 -!W  ?  C  ? ÿîñÌ4B735#535'273#3#3#535#73673267#"&5'353#5'675#Xff#)WX %-ddWWfâhX€  ! ‡66'ccl     ) J T¼ 7#5##535#35#¼° ¸G58 BöÏ,73533#&'#5'67#73533#&'#5'67#-&&  %p)0&  °CC %)(JK() ÿðöÏ#'+;73533533#3#&'#'67#535#35#35#35#3533#3#535#)_**3;% .A1 +:1);______ 000XÀT0¾T # T14A ÿèñÏ!1L73533#3#535#3533#7'675#73533#3#535#3#3267#"&55#'67#%QSSbÔ^Q #((+-3#q&''-n.&yàM   ( ) D< A 5 0  &- !=èÉ)7#67'7&''535357##53#"''325€K  ) 777i.S ÉQ&  ƒ zŒ\ Pÿòòi73#3#3#535335#[“@99D¢ @i#??T.îx 7'6'&'&'''6Ñ& !Œ ! !!w5# "2$%03x       Pÿêñz73#3#"''32767#'67#53&'ª>]R D $ ,&Cz  8 )< 8ñÊ?73#67&'7&''735#35#73#3#3267#"&55'67#'7365#XE!2222Rl(/$   " $ 1ÊT' f0/* $#!]ÿïõÏ%+;73673#3#&'#'67#5367#7'6'&'3533#3#535#l/69J# %!,+g  N  %&&9…:%š  # @     ŒI=ôÐ#(.47&'#3##"''3255#535#5'63&''67&'–#, "CC   BB+ 2> dÐ     B    YÿéôÆI7#5##53#67&'#"''32655'67&'''67&'767&''67#ím\#     ' .    (!Æ0 2(  02  "&       ÿïñ¢'+/?73533533#3#3#&'#'67#535#535#35#35#3533#3#535#/%2%%((>>( 2")3 +9=%%%72222,,,]ÅV,•  #/?K<òÊ73#735#35#53#3#3#535#^‡‡bbAF¢JFFK§JAÊ1KRÿéòÏ#06<B73533533#3#&'#'67#535#35#3#"''3257&'''67&'`!&#   "#0!!  A P P  µ" %$"""H[ =$ SÿðôÐ !%73#'63#735#3#53535#35#35#wku kkEEd¡%%Ð >@AAA/////(ÿïð573533#3#535#6JNN\ÈXJ#4òÏ#'+?73533#3#3##5#535#535#35#33535#33573##5#'6556244--2233--2.H.x )G.à  B  B & " P QQ,# #>3ÿòò¤,<73533#&'#5'67#'3533#&'#5'67#3533#3#535#”$     W    CFFW¾TC’  '*  .1P ÿïô§ 073#3#&'#'67#5367#735#35#3533#3#535#+ª]/ 'D ' -D6ƒƒƒƒ455V½S4§L   -* TJLõÊ%+17&'73&'##'32765#'67#&''6'6f ,U    / ,2 >    Ê    6MA    *WÈ7#"''3254'7##5W È)  )ž )\Ï 73&'#''60  Ï  s~UÿéõÏ5:>B73533533##5##5#3533673#3#5##5'67#535#67#35#35#X 3##3 +* +D ZR :<+E ! RRRR½   ] N  G1zÿüÈ` 7##535#35#È<****`W d#4=ŒÐ#'+/37335#535#535#53533#3#3#353#735#33535#33555--22//**66i-D-[< <[ ;ÿë¥Ï 173#53635#35#3&'73#3#"''3267#'667#r$Q ----# 41   ! Ï OO +-7  2   4~Ð6767#"''3265'67&''67''67&'767&''6`    # (#!  (Ð            5ñÍ37INTZ733#"&55#'665'6553'#33533533#3#535#35##53&''67&67#&'''6Ù  jgBB> [ D _     .F   Í  # 0534."       2î«"&*G73#3#3#3##5'673&'5#5#35#''67'67'676767676¹*0(())3i  !$$$$$/?!   «    A %   -     :òÏ!&*06CHL733#3#5##5373&''67'&'#3673#&'''67'#5'6&'35#D**7U0NV    ¡==C @  0 W %> 66Ï !2#        õÊ "&*0AEI7#'65536533#&''67#'3#735#35#7&'#"''3255##55#35ðÀ%  `VV5555  7   >O>>Ê>4' (0H:1-#,33  .; K lÐ&73'67#'6673#35#535#53#‹7 '    :*\  #    B LÿéïÍ #'+/3AH73#735#35#3#3#5##5##535#3#73#3#73#3#3#''67#&'#6[††````’@G43F@##G##W33W33T‡‡£ 8@ +~.$Í=% (++(        yîÏ#'-373533#3#535#735#3353#735#35#35#'67&'***2u0**See?????? 5 Ç) 9X> " !      DÊ73533#67'75#  “77L  V ?ÿêóÈ !'+17=CIY7#'6553#3#3#535#535#735#&'735'6'67&''&''&'3533#3#535#òv244:ƒ7332  .   I  s !877>”D8ÈSE5 6?_8       U         OÿéôÍ#GKOSfjo73#3#5##5##535#3#73#3#73#3533533##3#3##5#535#535#5#5#35#3357#"''255#'65535#35#Z>K95G=))G--V88V::`%% :  \   Í %//' " '  ' / ;n //#4ÿöíÏ73533#3#535#accZÈYa‰FFnn ÿòóÉ73#3#535#535'2Ø )1jj^Î\hh(-_É@RR> ÿçãr 7#'6553#5#3#35#Ï—¿AA—BB,,22XE ?ÿëåK7367327667##"&5??5 8E'=$K  # ÿêòp7#5##533267#"&5'3'66ë¯z'5!.-p./"F  L+,  "ÿðîm73#535##53#5+3'&'7'6¦HÚE.Î.((X    X0.X: ÿïï}#7#5#3#3#535#535##53535#5##3ì9//OÝL./8\///-}1==!3|)((ÿïí„'7#5##53#3#3#535#535#3535#5##3ì²¥/88KÛI88/B!88!88„2 !3 33R"!! ÿéó’-59?73'67#3#3#535#3#3##"''3255#535##5##535#&' à ̧C]Í^Ræ%   EE®`((([  ’   #   D C)  ÿëîÆ37'667#533#"''32767#7#3&''67&'767O +)¶. 3aN # #  fK-JLIaJI!%  #  ÿêõÐ!+73&&''67&''6767#73&'#>= >; ;>$ $  /b% ÐB%"(3*.BH\ ÿé÷Ñ973&''67&''667#3533#3#3##5#535#535#\l (/ =0+? 4& + ^2RRRMM^^^^MMRÑ     NÿéòÎ%7&''67&'7673&''6767p  k+(: >+(8 5 )-"  &@## ~$#3ÿéòÒ5:767&'7&''6&'''63&''67&''667h25 # KT+Y(" (> , 0k %6A,,: 5'2Ò  #     ÿèôÏ3873533#3#535#&'''673&''67&''667'OUUfÞdO( &J ( (. g!:?.+: 3& 3º*   ÿéñÈ%)-1673#3#3&''67&''67#5367#35#35#35#67#×fVhh+)B+,; 2$ '(>Y'‡‡‡‡‡‡FWÈg   g** *Q  ÿçòÓ &BFJNS733#5#&#'67##5'6367##53#3&''67&''6'35#35#35#67Z] 8G5 " >- g [ ,³qo  +?/0:%$ "Ó %  ‚RR   K  /  ÿéóÈ (:>DJbi73#3#5367#5#5#35'33#7'753773#3267#"&5735#'67&''3&''67&''667#ÝdBf~!!!![J  v<, g' )k- *:\'4A/)5 ," +SÈ VV"  @@=C3   !D          ÿáòÐ#3DHNTkq73#3#5367#53&'7365#5#35'33#7'753773#3267#"&5735#'67&''3&''67&''667#¢ Ai?d;3XL q7'  j# %m) '1 D 28*'6 -  /  ;Ð TT  @  @74A6    F          ÿèÍu73'67#&''6c j'‡ }!d   ;u  B3   ÿéñœ ")/7#5'673&''67&''667#7&'C  &NR!! &  !0D   œ{X /4C    %@0/  ÿæëÏ".26:>DJ733#5##53''67#&''635#53#3#735#33535#335'67&'fd&.b²`"* a3L 2 GC™BE 44H2z44H2[,)\* )Ï  ((  m  D) %      ÿçôÏ73533#3#&''67#535# SZZe\ DI"T S\`S¦)))BK:&%4- ÿéóÏ!735333#&'#'67#53655#335$JQ\G MM ET[J^?¦))I7G;&", "" 6 ÿçôÏ#73#&''67#5353533655#335Ô^F J8* N`II45I:¨N6E2!,N''N22;ÿéÏu73653#"''32765#'67#.AL  :F >=V O.H7 ÿçöÏ%73533#3#&''67#53655#'6@0PP`]H NN MX`9 Ç,, 5 55""- " ÿéáw 73533##5# WVVWC44GGÿéñÏ#7366533#'67#7&'&''vW^U Lm: ‰$@S&FH;*ƒZ2ÿéäj733##"''32655#53&'š66  ~~N  j: 6 ÿçÊb 73#'3'6573#¶0 '>byy./"*n)ÿïæ7#3#5&''67&'76Öšª½•  %  k    ÿé÷Ð")/7333#&''67#535367#'636565#335\W 2WD H\ R]a P -68L7Ð C& 41#C k% 0-ÿëó‘!%)7#3267#"&55##535335#33535#335ÎF % 1%3GG33G2y33G2w_  n'9ÿèÞ„'7675653'67'56##53#"''325u #$I 4  +{.V „ 5$9 QwŠR ÿéõÑ97373#3#3#&'#3533#3##5#535#5'67#537#537#!O]ack…* "T 777OOPP: -> 6?J½!''  ÿæôÏ(873533#3#535#'333#&''67#5367#73533#3#535#H?DDS¶P?1mb[B I M AYjg.CEEV»RCÄ   x '" L   ÿïñ’73533#3#535#3533#3#535#;:<<`Î\:NQQjàdN~A ÿçõÏ'A73673#3#3#&'#'67#5367#5367#3#3#&''67#5365#[Z`_h‚1# *J* 2D=GU/l,VZ4&).; AMS-¿ $ Y    SçÏ $73#5'677'673'67#&'&'P !7EJ A8 `  Ï|%   % <- 7ÿèð“"7#3#3#3#67&'7''67#535Ѐxxxx  58 IR8-“     aGïÏ$73353#5#'67#535#73533#3#535#   @4P755/p.7Ì"‡0  ##//=ãÑ 67'2'6'&''&'&'#'677'67676767&'Ù Or_O8 7 ‹NN "/3E$Ñ      0     "#ÿèÚ|"&*7#"''3255#'65535#35#'##55#35Ú  97777'33 |w  ")>(=Cl{.ÿè׊%)-7#"''3255#'66553533#3#535#3#735#×  ƒ &#''2r.#[[;;ŠŠ  t;0& $D   (1'ÿèØs$(,7#"''3255#'65535#35#'#5##535#35#Ø  75555#"""""so   !'9%9 >>>J11   ••u$@.3330 ¡      8    @  ÿèàŒ'-3973353#35#53353#5##535#3#5#'65'&'&''6S+h**i+ %  % ˆ"!1 1!&¤42B B2 &72     # ÿçñÏ$(,8<@73533#537#53#3#&''655#35#35#'3#3#537#35#35#ehh!T!D4 D I ]eŸ####šT#G####½?GK%@7%/D?I +LKK, + ÿéÛ "&*26:>B73&'73#3#3#3#5'65#5#35#5##535#33535#35#S$.777`’@@T>’@@T>>     1   HH # Kì… 73#735#33533573#735#335335gg hh  …::wóÉ 73#&''67&'67#53667#¤C  " $ ! É 2     c( hìÍ73353353#3#535#!@>Rc×`TÁ((/RòÑ &7'67#73533##"''32655#&'. ) /^## ^# ‘  Si; 6   ÿèôf9@73673#&''67&'7#67#'3&''67&'67#53667# ?  "  s3    " $ N      7&      A )ÿ#73#3&''67&'67#5367#67#:`*2 % "0 $‡       Q cÿèôÈ"73#735#333#5367#'67&'”NN--4a ‘b^% HÈF&F< ÿûÖ›733#&'#5'67#53¤   ›IO!)HñÇ0573#67#5'675#35#35#675#73&''67&'#367t&- $/////V`   ÇEQ-1 G.   "FJïÏ%).7533'67#3&''67&'#'65535#67›?*+   & && ¾    ! %#&& IâË!'-7#5##5##5##53'667'3'667&''&'à//Œ2 i1 ƒ [  ËZHI[ZHI[5 5 &  jÿéñÐ !%)-73#5##53&'#5##5##535335#33535#335­9a7R!!44!!4!U!!4!Ð .. Km// o*B`ÿçôÏ%+CJ73673#3#&'#'67#5367#7'6'&'373#&''67'67#67#j1;>L# ',k  O  #B  ( $@  £    :    €      [ÿçòÏ.2673353353#3#535##67'7&''675##535335#335o0>;1w5 :=""211#È#**!36;   H *OÿêµÏ  73353353#3#67'5#'655_  HHH= ½-??-> D  A '$ #Kÿé§Å ,73#735#33535#3353533#"''3267#'67#^II ) <$    $Åa8?@D 7 % 0YÿìôÉ!'-=CI7#3#53#3#'67#535#'6&'&''33267#"&57&'''6ëss†0=!*/ #  $,5    [  `Ég‹   0  -  1 4  M…Ê$7#3#3#&''67#537#'67#3#5…1,      XjÊ    [}4ÿçìJ73#&''67&'67#53667#{b"  "C 2 *5$3J      0  CôÏ59=A73533#3#&''67#535#73#3267#"&55#'67#735#35#35#"""(*   "$'"ck    EEEEEE¼   Z   !  A ! # xÿõÇl7#"''3255#'65535#35#Ç  !   l` ++-{ÿûÖg73533#&'#5'67#{%$$   U  /4  ;õÍ #)/573#3#537#35#35#35#''6'6'6&'''6f‹93u-=OOOOOO;% "#) %!!+ '‡ $ Í VV "  W       Qÿè¯Ï 073#53635#35#3&'73#3#"''3267#'67#| H$$$$""4+   Ï NN *-6   3! 8=ÿéöÏ+05;733#326767#"&55#'67#5367#'6367#335&'o%  -! - * $   "Ï  H; A5!*H  ^(E[ÿèôÓ#H73#3#3#"''3267#53635353#67&'7'5'67#53&'<^vvs   r KKK!C6  -   %2:Ó=   l  L    .      YÿîòÏ8<@DH73533533##5##5#3#3#3#3#535#535#535#535'235#33535#335^!(%%(!~3>>5566A™E8877AA4;&$$7"Y$$7"Á   E    E  K ' UÿêõÐ"&<Y7'6553&'73#3533#&'#5'6773533#&'#5'67#676767'7&''67'67x<PPb>$- "+ (#Ï       ]   \ ,    YÿéðÏ (,04EY]73353353#3&'73#3#3#3#5'67#35#5##"''3255##5'673&'7&'#735#f'#ƒ57////8€ 7&&&&&q  g%  (  ?Ê!  .    > +CR/   ÿèñÉ4733#"''3267#7#'67#33##"''3255#53567#&ž 5  7 C :7 .+`dd  iiGÉ+:"+B($6>. +ÿèïŽ733##"''3255#53567#2™#dd  ccxŽ : 6€áÑ 7'2'6'&''&'× Np]OC  +  Ñ      `îÏ73533#3#5##535#eddb±ce¹22 ÿèðÑ;737#535#53533#3673#33##"''3255#53567#'7#-4!rT@@::$ 2JN#[[ \\J'-1l  ) %  ÿéów733##"''3255#53567#7 hh    kkow. * /ÿéó‘5Q733##"''3255#53567#33##"''3255#5357#'37#"''3255'7567#R|GN UUXCV''  ..;^P   &) 4‘ ?   SíÑG73#67677'7&''67'67#67677&'7''67'67#53&'736ª ;+  $/ %-Y  $/ %-/9 < Ñ              ÿæóf %7#'6553567#533##"''3255#óÄPa†XX Pf*& %-H   GôÐ#'+JO73367353#3673#3#5'67#35#35#73'73#3#3##5#535#53&'#37#7 EHZ 3366X(+/**++/ +$Ç   $#D ) 6 _    Bð° 3873#3#53635#35#73#3#3##5#535#53&'#53&'367#>(FJ[5599* $2++**/-°' &X" > L    + ÿéèq73##"''3255#Ï[    `qZ  WÿéñŽ73533##"''32655#&'‘<< ‘;  n W R  ÿéô™73533#&'#5'67#^`S%9 ;%"9 : Pr''59jg5! 3ÿëï“ 7&'3#'67&'… ]ÛÛG%&-l)(“  ' 2#%#) +"ÿéò73#&''67#535'2 !'mbMJR S ac"L!*.-&ÿêò•&7''6767&''3#3#"''3267#735#Õ 00!%% ˜I:=  = 96:!) @T W1 _:8;ÿèí‹!73#3##"''32655#'67&'6““$Ûb e4 ‹E @ # 0ÿéÖ’ 7#3#5##535#35#Ç„“€rr€€’@Q ©.x!'ÿéÙ 7#5##535335#33535#35#ÙŠNN::N<Š::N<<}” ” 9&&&a(((ÿèðž373'667#&''67#3267#"&553#"''326@3(1,&#    œ3'& [   ž 8B 5'    'f " }= ÿñëƒ73#3#3#535#535#&'3–AKKcÕ^GGAwƒ%44%?  ÿéñš$73656533#'67#7&'&''r S]P ?e9 ˆ% C<17*i    >'ÿéð—%73533#3#3#&''67#53655#535#)MKKHHb^G G!I FXbIIM…  ("  ÿìï 17&'3673#6732767#"&5'675#'67#¦ {AxU( + A 9<  >*  LI/(?*ÿêÙŸ 73533##5#735#3355##5#*MNNM;;O;;;ƒi00>C(ÿèØ73#735##5##535#:ŠŠbbŠˆˆˆCASS2ÿéî•!%)-733##3#5##5'67#5367#5367#3353535#/¢tv !(AE/BPTZkvv•#" C 0   $\ÿéó¢ %73#53&'&'73#3##5#535#536…SÃX` Dh]]^^gˆ¢  )   $$ÿéï #'73533#3#3#3#5##535#535#535#35#/GGGMMeeHKddLLG“ J K|6PÉ› 73#735#35#6““kkkk›K- ) +ÿéÖŸ"(733#"''3255##5335#35#7&'7'6uM  ƒJ6ƒƒƒƒ  ‚ Ÿ*s  0Œ'<^  ÿèð“373#67&'#"''32655'67&''67&''67#.¤H  $% /A F0,6 9)#" -%>“   +0 #% !  ÿéí“#'7#'66553#3#67&'#67''3#X £¤O   D  zz:.# %G6   ;8 {ÿèõ›#473533533#3#5##535#35#3'66733267#"&5(-...!p!-A../?<&   ) ˆL::L1-+!$   ÿæòœ73#3#535635#&'''6·2@‡/Få3F2EES" !"@*'œ %Z`%=   [ÿéß73#3#5##537#35#35#[„7=[.8[[[[DJ ÿéñ  -7'2'6'&''&'3533#&'#5'67#À ñ¤%+7&'3673267#"&''67''67&'v5',7 , 7  ! °  ¤  -'+ +    ÿèô™!A733#3&''67&'767#53'3#3#3276767#"&55#'665#›330     CqKKe %  G3™     *M  R/+ ')ô¦'+/73533533#3#3#&'#'67#535#535#35#35#-%2&&D. !!W& (C%92222›  !0 ÿéì—873353#5#'67#535#73#3#"''3255#&''67##537#7-Oƒ66  $  5;‡$4®B-"<d  O  !k|ÿèô˜273353#5#'65#535#73533#3#&'#5'67#535#"  =-P011:-  &50‰.¯F '$"(ML#  ÿéìŸ 057#5'673#535#535##5##53&''67&'#367B  %/pn\UU^‰|m "* *%  Ÿ†j '$D  >        ÿèó¢'+/5;73533533#3#3#&'#'67#535#535#35#35#&'&'+&4++11F9 '%=0 (5A**&94444)$''DAAC“  2*   ÿéì™ %7<73#535#535#'&'&'#5##5'673&''67&'#367eljXQQZ,  Õv=j !)(#   ™D        "      ÿèðª$*GKOSW736732767#"''677&''67&'3##"''3255##5#53535335#33535#35#F:(*))83* 1¨    PQ==Q<==Q<<¢) !  4    :9  9  ) ÿçñ¬#)/7373#33#53537#35#35#35#35#'67&'+MHKG$Û<Itttttttt( %`$%$#  ee & # # #       ÿèø›=C767'7&''5'665567&'67'7''5'66556&'m     0…      3P  ›y ƒ!3+ )3, Z3:_| ˆ!3+ &4, $&ÿçö¨.26:73533533##5##5#3#3267#"&55#'667#735#35#35#0%4$$4%§. & 1 (",)+€€€€€€ž  h   K *,VÿçêŸ *73#535#535##5##53&''67&'767#gijYRRXƒo_  %$  HŸD  @!"     ÿèò¦)-1573533533#3#3#&'#'677#5367#535#35#35#35#,(0++%AaUBLM ;K[I$(:00#yyyyœ I '# I+ (  ÿêò¦ ,0AGM7&'73'67'3'67#&'&''63##53##"''325''67&'¿M?, .G A$    ,PPT¾W   # % s! !k F   4(  '%'     ÿåòª159=AEIP7#535#5#53535333##3#3#&''67&'67#53'35#33535#33535#33567#`=RIIKKTg„3 !%+%&L 2" 8I77K9„77K9@@TBC H5''    b! 8 F  ÿéò¥/37;A73#33#&'#"''3267#'67#5'67#535367#35#35#35#3&'#/ NQ .   5 A 4 # $38<yyyyyye H¥L   "   L# $ # %ÿèñ™ !);@73#535#535#3353#5#'665#535##5##5#53&''67&67#ljiWOOXQ  5,Ñg _ "((  + ™D  "1¯G   (     ÿèé $,073'73#'3353#5#'67#535#73#3##53#=#j1  8K 9-Ukkjj rN‰ #1¬D4 (E CB  ÿèéš%)1573353#5#'67#535#73#33#537#537#35##5##535# 6+Oh42†!$*$DBBB„1±G6 *?'97C C( ÿæóš&,28>D7367'675#53#5&''675'675#&'7&''67'67'6+L >Q/, 0:/9 +.!#":n $7 ;9 .F HJ 4b eš:  H           9     *  ÿéð§ -15973##7#53737337#37#33#&'7#'67#735#35#35#Ñ"$ž!#10H00G33‹¥ $%D , $(§  Y   @ # # ÿéò¢"*.26G7&''333267#"&5''67&'3#53535#3353353##"''3255#z #  )$ ¢ ã##5"%¢¸P U¢        &"""0   ÿéõ¨'+/DJP73533533#3#3#&'#'67#535#535#35#35#3533##"''3255#'67&',#<##!!>A, 3 3/ (:=""#6<<<<111  1  ] œ    !   ) :& #     ÿêô *.26:>B73673#&'#5##5'67#53&'735'635&'35#35#35#35#È K 3J*( € 0&L. CUA5^#G55G99G55G99    MN  Z  , ÿäí¢%-59>DJPV7'6553&'7'67333#"''3265'3'673&'73737&'''67&''4': UH      V& 59& $mt‚ g  Y  #.+  !4 Q     #   ÿëæ¡*06<B73#35#535#53#3#"''3265#'67#56&''&'''67&'m !77ƒ::DJ7373#67&'##"''3255#5'67&'767#3&'#35#35#'67&',< R6   @   B   ,'X !pppp x ”  8  8   + ( !   ÿéé¥#'+D73&'73#3#3#3#5'65#5#5#3353#"''3255##5##77GGMMMMW¿ UBDDDDN)P  )O(¤    4    (  .ÿèô¥ 3773#735#3353353#'67#53#67&'67'535#-¤¤0©ÕÕ, -%1¡F ! S  ||¥/*N))  +   E  ÿéò¢#'+?73#3#&'#5'67#535#53635#35#35#'3353#5#'67#535#Ÿ80@0  +;.#LLLLLLe 9-¢S   21  S !   8$2°F6 *ÿçóŸEc73673267#"&5733'67#3#33#"&''67335#53&'767#3#3#&''67#537#'6+  P[ * %%  "!   /(  >O8*-# ).Ÿ   $    # 4 N%       ÿçë®!6<@DHLRX73&'7'7##"''3255#'6'3#3#7'75#535#'63#735#35#35#'67&'D    [K ").+d ]¬¬„„„„„„ 7 0Z))('®   # !    U<   ÿêöªCTX\73#3#53&'#53&'37#733#3#3#3#3#;667##"&5535##"''3255##535#35#G* l( #JKKHHGGGGDD .  HH  77777ª  3 *  )    i V  #n,ÿæè¬48<@DJP73#3#353#5335#535#'6'3#3#67'75#535#3#735#35#35#'67&'B#//d00 hU"'+$"¢¢}}}}}} & "g%"$$¬        :U< ! "   $gÖÇ 735#53#535#)˜š¯²˜ `&UéÇ7#32767#"&5535Ж8D NF‚ÇB  [ƒñÏ73533##"''32655#&'ƒ> >  £,,_ Z 6ðÑ+3773673#3#5347#53&'5#35#"&55#'67#3327535#W , GR;³8OAF7! !eÑ  ff - 8   D ÿí—Î 9733533#537'6'&'3673#3#67'75#535#53&'5!€'O  `  &900";F7..6Î:::3 ?   0ÿèï\7&&';*X2 .\*\JO ÿèóÐ $73#'63#"''32657&'''6N˜£-A Di %Ð ,‚ _)0 2'5$ + ÿèóÐ#)7'673'673#"''32657&'''6E&˜  Q Di %–#*3 ‚ _)0 2'5$ +ÿêëÏ0733673#5##53'&'3#3#67&'7''67#v  2®a0   %Ép38  FPBÏ39&(;, M$   # ÿéöÐ28<@FL73673#67&'##"''3255#5'67&'767#3&'#35#35#'67&'QmG    :   A    F?U jjjj$ "ƒ·     :-  );    $1/)  ÿêòÐ8>73733#67676732667#"&5'675#'667#7&'/œv!+.&6=*3C " 5+ .‘ ¥++!     $ $  EHC>>  s'í½73#3#3#535#535#xp.**3z3''.½*44* ÿéôÏ ;73533##5#3533533##5##5#3#3267#"&55#'667#YXXY+c//c+ÚC$/%% >»$$3 9#%  ]óÍ "(048<73#'63#53#3#'#335357&'3#53535#35#35#Ä"'#LJ$$J  – #"Í  4g)7   -666%%%%%ÿèàÄ 7#'66553#5#3ÌŽ"·ŽŽl B5 2"]gT2 ÿéôÃ7'66553#5#&'7#3B#¯6ORJ‡‡i C3 0#]hD(/PG43ÿëõz73333267##"&''#'67#536rA  -B 8*+z P " EC6 _ÿïôs73673267#"&5_:2 7B/ :*s+) %  8ÿíõ&7773267#"&55'75'75'6Ñ $(PR]_ *  4@C472O      ,ÿêö"7367&'#"''325'3'67#‡ ! 2 JC3 ./  5D  X8,+ÿéìr 73#"''3267#3#735#+Á®ooIIrd%PD Bÿæïr73##53#67&'7&''6Vƒƒ#7­^,. ?Fr-"  ?ÿèé† 73#5##53&'&'''66• GzC>#& 9 +† .- ;5== <ÿçî‘73533#3#5##535#35#ÿòô…733533533##5#3#5#53#3X"##J•e""$$$$66I\#Cÿéèy 7#5##535335#33535#335è}HH44H5}44H5d{ {-H>ÿêëŒ %73#53&'3673#3##5#535#53&'G B *.PHHEEJ)Œ #  && óÆ733##"''3255#53567#R##  -- =Æ< 8?ÿèí„'/37#"''3254'7##53##"''32655##53#=#  Fh   G8„' +Š›o kXTO &&7ÿèòŠ173#3#'67#53475#53673#3##5#535#536i $ ] ''Š *  ==8ÿéõˆ#.7'63533533533##5#3#5#5#'#5'6` !9asZV ˆ''''??O`...A YE:ÿéê26:73#3#"''3255#7'7&''75##535#535#'235#335ß#+EEQ  = -719LBB!%R6//B2 /6  " BFM735333##3#3#&''67'7#537#535#5#5;5#33535#33535#33567#E?EEJas 4##7 #) &4*D?,,?3r,,?3x22E79 E•'     '5 =jÿæó(733533#3#5333#33#"&''67ƒ**3y&++ '   )5  &Cÿèç˜"*.273673#53'35#&'735'6#5##535#35#p( )¤& 88  @5 ,mmmmm˜ BB ?$  $$  /P P % jÿèõ›&*.473#3#3&''67&''67#5'635#35#67#ƒY``BC #"   AAAA  7›>    9 ) $ 6  sÿæïŒ!9?733673#&'#5'67#53&'733#&''67'67#53667#§ %  B " %  '  Œ   1    .iÿèô˜#48<73#&'#5'67#537'23'#35##"''3255##53#735#Ø C P +0 + ..V  U::˜    2 8 % %MQ9r ( T2'D 8'9Ä«73673#353#5335#53&'Y  ;"|#=  ªJ;LL;J ÿéí… 7#'66553#3##5#535#í· %šEOOKKA…7/" !@%77ÿåㆠ73'66'3#735#53#5#535#T# 2XUVjaaU†O 79Y#"Ž#ÿðñM73533#3#535#,JNNgàeJ2ÿçô•CI73&'33#673267#"''67&'#7#"''32655'75#535'67&'w8DA  $ ( ;(* 077" 5r  •  *$     cÿéñÆ #733#537#537#37#37#3353#5#533up Ž"+-*2/#k"Æe"S"  =gXXXXi:ƒ  $$ $ÿéõš $73#67&'#7'53&'3535}FE V":7%! K1‚‚‚š X  4/  ‘# ÿèô#173'67#'63533#67'675#73&''66ŒQ  B o &.( (0 &  !&&3 9 ($!+ ÿåô#'+/73#3#3#3##5#535#535#535#5##5#35#35#Ê..\ffbbZ**—*)))=**""'ÿèäŒ,7#"''3255##53673#353#5335#53&'ä  ¡D   "= z <$Œ‹  t’¤   -01- ÿæóœ5<73#&'#5'67#535'23#&''67&'67#53667#Ñ %.gL!0 ;'"8 *#JcLW{) D)$-H <%" 9I) Kœ L     3  ÿéï"473533#&'#5'67#7&'''6&''6767&'$!!   ¶!(B *+#q  OS%./71 < 00  ÿèå†.277'7655#'65535#35#7#"''3255#'65535#35#s 0 ////´  86666†  ! &+B*=K! '*C*=ÿçä‘ !'-73#"''3265'3#735#35#35#73#'67&'Ñ   ·iiCCCCCCtlC  ‘‹ ƒ|W55Ii      ÿæõ›EJ7#67327#"''67&'#37#53#3&''67'67#'66553533'767ï<    k \+4   % |  †+ + +.4   ) "B U  ÿéó‘07&'3533#&'#5'67#73#3#3#535#535#©  …$    "]}611<@114‘   PK ##((# ÿï~73533533##5##5#35#35# / /%////zxx""V"ÿïñ¡373673#3#3#3#3#535#'67#5367#537#53&'X3:oglŠ…BR¶P= *@J  š    -0'  ÿéõ 17;?EK73673#67&'##"''3255#5'67&'767#3&'#35#35#'67&'Ka>    8   C  ;8Q hhhh # !ƒŽ   /  .  % #      7ï—!J7#53#3#67'5#'67#'736573655#53#3#3267#"&55'67#'B,`"& %   O*a%)&     †         S0îÒ,047373#'677#3#3#535##"''3255##535#35#fVa   W$4{5!a  IIIIIÆ     ; LÿèôŽ !=K7#'66553'67#'6'&'73#353#'67#5335#5363&''65é´ ˆ2% M8' # "5= #  # 2Ž;2& "E  "   ', ,'  %ÿéò˜%7;?73#3#3##5#535#53&'#53&'367#'3#3#5##53635#35#³."4++++3!+ +h)BF2 //22˜ '' F9 :F ˜ 4i ÿéø›048=AEK733#67327#"''67&'#7#5'275#53&35#35#675#'3#735#7&'©20     --.  šy>>>>>``<<¬  ›  ?I8 ) , ‚0  ÿèí¢!%)B73&'73#3#3#3#5'65#5#5#3353#"''3255##5##6>QSLLLLY WBBBBBM)R  *P&¢    >    (  -ÿèñÐ 6LZ73#5##536#5'67#53&'735#'273673#&3673#&'#'67#3353353#5#p_”C , 87 2Ž_hR: E N=P?>¤ÐfUUfX         *''2 ÿèõŸFf73673267#"&5733'67#3#33#"&''655#53&'767#3#3#&''67#5365#'6# )  % ic' (("#   .7  L]=(+  $), Ÿ    %   !, J  (        ÿçòš .DJNRZ^73&'73#'67#"''32654''67&''67&'36533#&''67#7&'3#3##53#=#NQ,           ?  |@@AA@‚  '  5    ($'#!"@9  $ D ??  ÿèö›)-15W]c7&''3673&'73#3#3#3##5'675#5#35#7673267#"''67&''7'3'&'''6Ñ  }  #$+((((-`  ¾6      E 8 ›  &     _3   g !   %0.-    ÿéëµ!%)FLP73&'73#3#3#3#5'65#5#5##"''3255#'#5'67##53&'35#<8 VSIIIIU¾TAAAAA¬    j  IWEJJ³     9   <  ( "  BQ& ~ÿéò–7#53#3#5##533'>&'¬'m3*2 ; †_OO_"5  Tÿë÷Ï,MSW[_r733#3'7#73267#"&55'75#'6553673265#"''67&''7'37&'3#3#735#276777'67&'{''-       t     cFF@@ % Ï       G=- .7R%*( - '6HD4  Z ) !     ÿéî£#'3T7#3#3#7'2753635#536735#35#73#535#535##5#3#"''3255##5##535##5mDD""%21  &2233TdeTPPSq#*  *$“+ +  )&0a EL?  ;& 99,;†ÿèñ˜ '73#735#35#3533##5#'67#535'6Œ\\8888  ˜B( # %%11  dÿçòÑ"BF^d73673#53&'3#3#5335#35#73&'73#3#3##5#535#53&'#37#3#&''67'67#53667#–)* '):.  *M * ( # )+Ñ   + *aGD  W    )  ÿçõ¦ 59=AY_73#735#'3#735##367#533#7#5'635#'65535#35#75#73#&''67&''667#Š[[99[[99Á¿B9N $5%%%%%%\5      !¦( ( ! 19*! (;D  V      Sÿé¿Ï.26:JX^733#3'67#'65533267#"&55'7533#3#735##"''3255##53673##5#7&'{--4 B  *    GGAA""8  /   Ï LA. 19V    &! 7 &=J$   ÿðêÄ 7&'67&'67&'6GVXÄ7(/3 ;2066(.4 :2/77(.3 :00ÿêÜÊ 73#'3'6573#È›"LÊÞÙYP2 0ES¹ ÿæâÏ !73'6573#'3#7&'''67&'73 *—O/  €  M  ÏsH.(;sæáܪ%+ -$4 #*$* ,# ÿêõS733267#"&5'3'6673#®   y <SJ  L2)  "*b#–ÜÑ 7&'67&'67&'6E O N Ñ   ÿññÁ 73#3#535#Î^hâf\Áªª%ÿññÆ 7#3#3#535#ì³  ¸ÌŒŒÆ-T.Õ.ÿöòÅ %73#3#535#'67&''67&Ñ`gáf]¡  –  Å©©\$0I$$0I$"ÿîðÄ73#5#3276767#"&5535#"±†=%  PIšÄoX! &  q;ÿðïÁ73#5#32652667##"&55335#¸ƒ2 N-ƒ¤ÁrO- ‹!<$ÿòñÁ73267#"'&553#55#38:I PD ®††]M # ¶q Q>ÿïòÁ73#5#3265267##"&57#335#¿—9 S0UAAVBÁr M ) ¨AAA7ÿíãT73#3267#"&5535#Dy 27 A<ylT5-hòË'+73267#"&553'#33267#"&553'#3™)% Z44¢)% Z44  M;+  M;+"`ÞÏ73#35#535#53#56j%==”@@DX¼*Ï eaJëÎ 673533#3#3#'67#5367#5355#7#"''3254'7##5-//*,7> "  )#&-Ó  0À "  $fwÿéîÑ<733533##5#53533#37#3#5#3#"''3255##5##535##5u0##™ ///B006PG  52EOÑ((!-2  II:L.?ôÏ/3;73533533533533#3267#"&55##5#'67#5##5##5#))  @'  x³µ '' .0 ó¬!7367#53#3#&'#'67#735#35#J-ŒGw6 &%7"- %4:ffff\EE !G %  ø¥ 73#3#&'#'67#5367#735#35#2œ]‡5'-H) ) 1B*vvvv¥I  , ' RÿéôÏ4:>BV7#'6553&'73533#3#535#3533##"''3255#&''3#735#276767'67&'ô~A>9B  ?:: ' ¼MA2 2=Z '%%l  g1(    eÿéöÏ!37;L73#&''6#"'673#&3#67&'7&''735#35#7##53#"''325}% U )g?,   `;   Ï     Z-   o8#¡r ÿéí¾73#3##5#535# ÀVccccV¾DkkDÿêðÅ73#3##5#535#'6'&'ÇXeegg[¦   ÅdQQd ) "$ $ÿètÄ73#3#'67#5365#b')*) #'('Ä9 M"A4ÿçîÏ-73673#3#'67#535#73#3##5#535#536% % 0-00, 2/22$$$ ¥7;& "27<7__7sÿè÷Ñ 7&''63#3##5#535#¬" ']&//00$Ñ   ,&JJ&+ÿéãË7&''67'6767767&'¹BN,)$('!$&6;=1 I(0 #6$A6$%[/ qÿíç½73#"''32765#qv  a½ 0.jÿéëÏ (E73353353#5#77&'7''67'6767767&'7''67'67676ONÃJ    o     ²§ÄħÉ ;&  % 42=$  % 40ÿèõÑ9Vt734733&'33&'73#67327#"''67&'#&''67#7676767'7&''67'67&''67'67676767&'..  ( T  ( ) 6" ),™  #  W "'  H(ab'     #    $-k +  '   ,8ÿëî›73533#&'#5'67#CDJ@. *) +;t''89ca3  67ÿèìž$73533#3&''67&'77667#535#CGOOB!' 2#. )  v;G…'       ;ÿèî!73#3##"''32655'67#'735#IŸ)// ';?%G Db*? -'#&*?ÿêîš73##5#'6556Í 2Aˆ4@Gš ]] ,'!$NBÿéê–%733'67##"''3255#53&'767#M D  6   FJ m–C  >  0ÿéò”#7367#53#3#3267#"&55#'67#B?:“DS;  E =;]B HK;@ÿéî¡"73673#3533#3##5#535#'67#C/^i4::IIRRL#‰  %%?ÿôó‰7&'3673#7&'=h5´  ‰))-&}9EB5‚%..&4ÿìñ¢,073#"''3265#3#3276767#"&55'635co dKA&  G4.¢N8="  Z%Gÿçíž733#3#5##5335#XXMs:&ssž eeD1Kÿèß• 7#5##535335#33535#35#ßl??++?-l++?--z’’7$$$\%%%3ÿèó£ $*735333##&''67#53655#535#33535F>D<; >: 33;HH>R002'($0) ' 1ÿëôœ(7367533#7'6773673267#"&5F 0: _&   x‘5F ˜@ =  3ÿèï¤ &7#5'673533##"''32655#&'f  #M M  ¤„a /((_ Z  6ÿçî™17&'32767#"&55'675#'667#5373#6¾  )!   3)+lF™  ?  AG2;&%49ÿéîŸ 7#5'673533#&'#5'67#g  ,3*  #Ÿ‚d '&&2-``*11ÿèô¢%*73533533##5#5#3&''67&'#367<&=&&c&v=/• /9#%0 - &(("   1ÿîóŸ)73533#3#535#7&''6'&''6CCJJYÂUC| `  4kk  w "+ !7ÿèð¦=73533#3673#33##"''3255#53567#'67#5367#535#K0,, ,< 4EE  LL0) 8 ]@0š      8ÿçò¡=B73533#7#"''3255'675#73533#3&''67&'#535#67;  I)++'    )) ‚%4  (+ *      .4ÿéò¥#'+73533#3#3##5#535#535#35#33535#335?LMMBBSSWWBBL00D/s00D/š UU13)ÿêó¤97&'3673#67676732765#"&''75#'67#°  d'w]% ,. $23% &8#. " + 2 , $¤ "      !cO2,D.ÿèôŸ473#3#535#535'667#'7#5333#"''67&'×00*c&**.k $#(?!">?8C  Ÿ&((#R '(-  ?ÿëéœ$,073#3#5##535#5#35#"&55#'655#3327535#?ª8/v05`/v  ZvvvœŒ ŒP    `9ÿéõ¤3;73533#3#535#'67&'#3&''67&''667#K?DDR±M? ! ]"!+O+6&", ' + C–&         6ÿéñ£%273533#&'#5'675#&''67&''6CGKK#2 .) 3!G l  Ž5+"CA"&9   !  <ÿèí§"',73#3##5#5367#5335#335367#33535|U1??_ +%++=,i#*<2: !§]&& ]'; & =ÿéï¡#'73533533#3#535#35##5##535#35#H#0##.².#600[`````6\ \!04ÿìö¦*48<@D73673#&'#327667##"&55#5'67#3533&'#35#33535#335@7 [- 7  & 9  '(!% & &&8$\&&8$   F   A ,32ÿèòž%+73533533##5#5#&'''67&''&':(;,,a(v;\  qf    }!!!!::(((  "4ÿêñ¢"'73533533##5#5#'6553#&'7#3=&8))^&q8‡/ , 8#5``’S $6 #% 5ÿçïŸ(73#3655335#535#53#&''67#56u +/!!"6=3 <; :6œ\\i!%$f6ÿéêŸ  &,73#"''325'3#735#35#35#73#'67&'Ö  —UU......Y] ?  Ÿš  „];;Vy   <ÿéö˜+=B73#67&'7''735#35#7'665333#"&55#53&''67&67#."$ % %#+33((0)BI­   5AA1 +Aÿéï¦37#67&'7&''53'735357#"''3254'7##5‘<   ***p  '•_2  Ÿ &>67 ²5ÿèò¦/473#35335#535#53#3&''67&'#535#5667#r ""51##(;DC$-$&2 - HH;X¦ HHP K‹ &ÿéò²#'+17=C73533533#3#3#"''3265#7#535#335335&''&'''674'&9)9€•—   ›+99K)'  B  6—7 4 >e   ?ÿèô§ $7<T73#53635#35#7#'665333#"&5#53&''67&67#'367326767#"&5Z$N****| ; - R   "w#" § NN ),)(  /!         Aÿéó™B7#67'7&''5353573#3#33267#"&55'67#'7367#Œ8   &&&V  # ! ,™`7    ¦'76  %" .)*ÿçô¥J73#&''67&''667#'3533#3#3#3#"''3267#'67#535#535#¨>   #o $$",/    % ¥ :! "%*(/ (87ÿèï¬#'F73#53'#"''32654'7##53#735#367#"''3255'7567#p&b(  #bQQ--Z  , >¬  0 3¥¶+ (   4ÿèñ§#'+/HM735333##3#535#5#5;5#33535#33535#3353673#&''67'67#67#I>FFJ¢E>,,?2q,,?2w22E78o$ $$8(/ )f B ((7 $     :ÿêô¤ "5K7&''6'#"''3254'7##5367#53&'733#3#67&'7&''67#¬  )!  G5 C( f wA  )- ¤  ( , ±V    3ÿèö¡ $*06<B73#5'675#'37&''675'675#&'7&''6'67'6™M ;ZK,/ 0-#- + )9j "585 'LM> 2b c¡O =      B       9ÿéó¤-1R73533533533533#3267#"&55##5#'67#5##5#3#"''3255##5##535##5?   6 [b=7  %!37“   ## *,  ::/?*.ÿéð§"&<I7#3#'6553&'75##5#35#3353353673267#"&5'67'533ë6/ŽG1+   4 )“1/% '0G #!(     F 5ÿçí#'+1773#3#3#535#535#735#33535#3355#5#'67&'E™#((2·/%% 00B1s00B1"111 # Y% #I, ( -!   3ÿëó¥$*.2D73533##"''3255#'3533#3#535#&''3#735#7677'67&'ž0 0h***#[$*~  h]]99 0;~''i  e'(2'   8ÿîò©#)/?73#3#3#535#535#735#33535#335&'''63533#3#535#G—CEEP¯MCCB00B1s00B1 O***TºS*©@  ( ! I   ,ÿéõ£4:@73533533#3#535#35#35#35#73##5#'66556'67&'8 # f ######Ž  ?  ( <  ‘RR22e pp3* +6.€    6ÿèò¤#3AGM7&'3#3#67'75#535#5'673'67#'63&''65'6'&'h  :"" '1*## "T4 (    $0 6¤  / 2   .&" 0   9ÿêé¥#7;A73533#3#3##5#535#535#35#35#7#"''3255#'65535#35#;###""''" #----˜   # !!"!”NN.,W– #( )2K/L:ÿéö¥28>BF73#&''67&''667#'33#"''3255##537'6'&'3#735#¶0        X' @+- A  ..¥ 0# ' !02p  [x‰+    BC% 2ÿçõ¥!%)-17=CI73#3#3#535#535'65##5##5#35#35#35#'67&''&''&'Z†»u&'f –    ¥!   +!!!!!!1 6   8ÿèõ¨(Pg76767&'33#"&55#'6553&''33#3#"''3267#735#5'67&'767#67#53&''67&'¨       3TJ/3   6 ,   6Q =Q  ¥    3, 0  s    3ÿèð¨#'+/5;73533533#3#3#535#535#5#35#33535#335'67&'A"7))0UE›DM'"l711C2u11C2^ ,&U" !š  KK ; )     3ÿéó¨$*08<@7&'#5'63&'3#73&'7#33533'67335#5##535#35#”&. ^ 6 ; >šš       `````¨    6    .GG # ,ÿêñ£ ,2:>73&'73#'6553'#373#3##5#535#536'&'#5##535#™ TDM''W &##$3  $3- (28>-..0Z[9(8ÿéñ¤'/37;73533#3#535#&''67&''6#5##535#3#735#AJIIS¹SJ  h +yyyXX44™ ,,    3[[D5 $ 1ÿéð§!'+/48Lf733#"''3255##5#'655'6367#35#33535#73573#"''3265#'67#3533#3##5#535#'6Y"   ,U     !!&& § x !4# .8 Y/$ 6 ((  2ÿèô® 473'73#3#735#3#735#3#&'#5'67#535'2™™ssSS//2’’·T  P* h ® N0! 4     4ÿçï¡/37;RY73#&'7#'67#735#35#35#73#&'7#'67#735#35#35#373#&''67'67#67#?L   ++++++HP   ------u?d" :$ ;'2 1g :¡P  8  :P  8  6       .ÿéò¦ "(.FLRX^733#5'6367#35#33535#3357'6'&'3#3#3##5#535#535#&'''67&''4'T+O   / [  & U"%%!!  D @  ¦  XN 56_   11T  3ÿèû«/37;AQW]73653#3#5367#35#335335'3#7#5'75#35#35#75#3&'33267#"&5''67&'Œ'+.-a!#   ¡I l       `  › @@=5n'#xHJ  +  / 5ÿçõ¬ =AEIMQcgk7&'67&'67&'673&'73#3#3#3##5'65#5#35#'3#735#3267#"&553'#37#3L   )  (  +@#xQQ// &" \4$¬         y #++Q( U   B/! 1ÿçñ¦#W[_cgk73#73##5##53#5##5&'7&'#3#673267#"&55#3#7'5#'6553'75##5#35#335335=OOZSS '¨,5 g 66.0    2((% G !!!2¦!%%$ &   *    6# "3  3ÿíõ¦'+/37MScio73733#3#537#35#335335'3##5#535#35#35#35#3#'3#3#67'75#535#&''33267#"&57&'''6‰&(*([!$   œL7 5ddP5 ‹     F  K ™  :: 71¥U@ % ' "     "   &kíÎ73#3#5335'6Þ((1w HÎ3;wd~ÿèñÌ733533##5#'655#53IJ66J8 /::ÌTTT{{H.); ÿéóÏ73533533##5##5#35# 1\11\1E\\œ3333  qqÿçòÄ73#3##5#'67#53655#335#À'66H 62 =?,@FÄFooN# B /1 FråÐ7&''6767'ª LU)6<º" .+'ZëÈ7#32767#"&5535Ò—8DPE„ÈA  X^ëÎ73#67'7&''67#53&'„Z€38GO@fÎ      ]ïÃ73#3#3#535#535#ÄYOOeÞeNNWÃKïÏ$73353#5#'67#535#73533#3#535#% B7R6660p-6Ì"|' ""..ÿéöÎ7327#"&''7&537&'åh : E UV$ “7E+:LB&&?7ˆJ73#ooJÿûa 73#'6bbm5ACaÿèãÅ73#3#"''2667#735#µš¯4²–¡ÅK)G"0O%ÿôïÆ73#3#"''3267#735#3#!³—« ­ ’ŸÝÝÆA4 C¬ÿéìÐ#'-73673#3#"''3267##5#735#3355#7&'o  Ð B6 VJ*$C &Leÿè÷Ï!&73353#3&''67&'#535#53367#¥22 !& & 20 > Ï?3E'    "E3lÿéîÊ7=C767#735#53#3#"''327'6'67#735#53#3#"''327'67&''&'ÕOCG[DM  $*1OP CG[DM  $*2b  b  4BA\("  BA[)"  1    `ÿæôÑ)-1767&'7'3#7&'7&''675#535'635#335˜"% 66# 5B"445!!3$Ñ"  K#  %Kn''' ÿèˆÑ073533#3#535##5##53#3#3#"''327#735# 433&`&4zUEE\FM  O DJ  ( ! , $* WÿêóÑ,048J73673#53&'3##'3265'#"''3255##535#35#73#3##'3267#'67#„ " %–$f  2  """""C[‘ 6 : 0 4Ñ  (L EN  _ $ !>#3 , "ÿòÛº 73#5#535#535#&µ¥¥——¡ºÈB>ÿòíË 733#537#'77#Z t 8ÚŒz†hË <@@ÿéôl%7367&'#"''3255'67'&'z$ +0   (04+Jl+  #&  "  ÿèóm &73767&'#"''3265'&''6t #<   :  ?-$ 7k  "3F Q ÿêòn"8E73#3#&''67#5365#'673673265#"&5'33#67'd0!  !K     n     "  )  c1 \ë7#5##5ë°##w0ï„$*77677'7&'#5'67'67&'''6*   () r  "      )   ÿéôÐ:@DHN73&533#676767327#"''67&''67''67&'#7&'3#735#'6 ML  # &  €¸  ˜QQ++G,6;ª   $7!     8   HJ&F  X šÏ733#67'5#53i  ÏFN  ]?ÿí¬Î 73353353#3#7'5#'655L  QOOH ½/@@/A @  ;%# "Jÿþ¡Î 73353353#3#3#3#535#535#P  OU! V#!Â3??3DGÿýŸÏ #73353353#3##53#3#3#535#53N Q@@ R "V"Â(55(8!Lÿé¬Ï*.273&'73#7&'7''67##"''3255##535#35#L%'3 #  T   +++++¸    @f  (|#3=ÿì Ï /73#53635#35#3'73#3#"''3267#'67#j!M ))))$"3/   ÏPP )05  4 &1Dÿë¯Ï ;AG73353353#3#276767&'7&'#"''3255'67'67#&'''6OVX*       K6  Â++1 , &  S  Kÿê¦Ï %)73353353#3&'73#3#3##5##535#K[#!YMMMMM(((Ä** 2    B B'>ÿçøÌ ?^73#735#35#367#5335#53353353#353#3#&'#&''67#367&'#"''3255'67Uˆˆeeee&502;)+=7\h$ :  $T    ',!Ì>& ! _            ÿëôÌ#)/7&'36732667#"&55'67''67&'m 3(0>   &" #,² Ì ,_,_ m1"% j90 1+,3 5*ÿò€ 7&'33267#"&57&'''6Z     F  Q €%F  E # :ÿïöš"7&'33267#"&57&''6†   &k  x  šh $ i(0 1'2")ÿëºd%7&'3327667##"&5''67&'f   "    dA # ?%  FâÓ73#"''3267#'67#'67#'6LŠ   2,  :4 "ÓV =7*$-2%(GðÏ73673#&'#'67#&'RbJ9BL CLX­0@A1)   EñÎ 73&''65'3&''65›#)+ 5^  0Î#9'' .( % $5PÿîŸÎ7373&''67&'67#67#R$    £+J.  $'E )-ZBíÈ73533##"''32655#&'Z_!! _  ­> 9 9ÔÐ733#535#535#5'6367#T O6ª—––Ž 1F CÐ  ] ! GðÑ)73533&'73##5#3'66#"&55332[;  "a[%  ´  ±  WW$ 3/AñÐ 87#5'67#"''3267#53276767#"&55'753753G  &ª   " %93 ÐgQ 8 730  1$3HÍÐ 73#53635#7&'¥ -šVStt   ÐVV\2C  9ôÎ-373533##"''32655#'67#53&''67&'&'{E   EK G] ! !s  ® H C*     EôÐ !7#5'635'273#3#535#A  $HDD %MMC’=HÐ^E  "   DöÈ%+73267#"&55#'6553#7'75#4'Î 0gX$&.'"•Èd   V)! 00D I@åÏ"&73&''67&'67#5367#73#735#@2  ' ' # PQQ,,Ï    ?8b?CêÄ"767#53#3#"''3255##5##5##5iZÔd` +$$—  > )6666BT ÿêóÏ#06<B73533533#3#&'#'67#535#35#3#"''3257&'''67&'2B44?=& .9/(><2EBB    Xld  ¶$ &'$$$HY  ?" # $ AèÏ73533#3#535#35#33535#335]aaN¬L]#::L:†::L:½YY555íÐ 7#5'673533#&'#5'67#?  %%?D=,#" )8ÐlN ' &!%JH**F šº73##53#67&'7''6X;; H  º95 JÿèôÇ $7#'6655&'36533#&''67#ïx u  J)30( / 5)ÇSE4 2#_! ' %1F 48HMÿé­Á7#5##53'>&'¨/!   Áš‰Š›'O0&  #*0 aÿêòÏ '73&'73#&''67&'7673#5#533a?>‘]    !p]¬   ‘ }k 1óÒ$)/57&'#3##"''3255#535#5'63&''67&'~26 ,^^   YY. K]& )m&$Ò       @  FõÐ$:@7#"''32654''67&''67&'767367&''66''6\   "   T   $ ,#'  Ð !      "    &.$# Iÿé©Ð73#&'#5'67#535'6ž    'Ð, if#/& @ñÒ&,273#7#'6'#5'63#"''325''67&'€cV  /  $j $^ Ò  $iL % >  $ 3Ïm"7&'33267#"&57&''6z   Xf m5 ;' DïÎ+73533#&'#5'67#73##5#'66556)++  #Ì #/X*  8² > $  =ôÌ.4:733#3##"''3255#5373&''67&'767#&'''6?**40  2/F^ "  I  = Ì-  ($2   (  XEìÐ 73533#3#735#X1QQC†``@K) 4ñ©173'67#&''67#3267#"&553#"''326=7RG.  Ÿ. *$ R   © 5*  :L)  ÿìõÍ#'+1EKQ73533533#3#&'#'67#535#35#35#35#&'33276767#"&57&'''6&d)).7! -D. (6+&9dddddd1  " ; ~‰¾M M.-* +  %  Aÿè›Ï73533#&'#5'67#V   ž11 wb(5F[FêÓ 73#"''3267#'67#'67#'6yd   ! &!  Ó VB8*%.2& )@ÿëšÊ73##5#'66556  0   'Ê192 7E9'íÏ"&*/73533#3#&''75#535#35#33535#335'#6]__L Rc_LL]#::N8†::N8)Á N N ,,+  ÿìôe *06<B7&''&'33267#"&5'332767#"&57'6''67&''&'¹bb   q   Z  m  Ì  ` e   G  MG  @$ $    @ïÎ=73533#7#5'75#'673#"''32765#'67#'67#'6" .oX  9 5 "   Ä -' % 'V  6F8)  RÿèÉ73#3#"''3267#735#R<$(   (")ÉJ'M#3K&>ïÑ$7'673#3#3#535335###5'6J%#2…911>™!:  )Ñ %>>S I: -,Íy 73#735#33535#335-  11E3x11E3yM.,_ÿïïÆ(.47#3#53533#3#&''67#53655#'6'&'ëy} (((37     ).(P 9Ƴ×0    @âÏ"&73533#&'#5'67#73#735#35#35#,''  !*obb======² ?A$%‚[:9 -ôÐ'-157373#3#3#&'#5'67#5367#537#3&'#35#35#Uchaj…,! – ,>4>O3gP qqqqÄ   /2 B & GÿéôÒ <73#"''3265#'6#55#353567#533##"''3255#pr j \UD33hXh…BB  XÒR: == d   7å¨73673#3#5'67#35#I m~ |• #7;pp’   9' 88ñÐ  177'67&'73##5'67&'73##"''3255#'6C! C:NNX  8=\   , Ð     K9 "  5  1  6óÏ9=B73#&''67&''667#'#3#327#"&'#67'535&'# A    %'%'$ ! A#Ï (      $3$#  †6  .òÑ048<7#673265#"''67&'#'65534'33&'73#3#735#ìA      ev  ¥[[OO++º&$ ./' %7  "6Pÿëðz73#3#"''32765#'67#53&'©>\R C $ +&Bz 9 (;  7òÐ"49AE73533#3#535#733#"&55#'667#53&''67&67#'#5##53# 4..$a)4É    Z   ( ,L __½  T     %%+2õÏ1SY_7676767&'7&'#"''3255'67'67'667#5367#"''327655'67&'''6Ö !   !     E€ ?V     AÏ  %  "    " 1 *(    +ÿíô+ !7&''&''33267#"&5''6×  ;    ( +   " &  6ëÌ#'+/5;7&'733#5##5##53&'767#5#'#35#'35'&''6) B„!@.*6  gy.**B.>*z /Ì    k""k  9  1  #  )òÑ $6HM73#3#"''3267#'655635#35#733#"&55#'6673&''67&'#367q!+JKO   < 5#9999§  b     ÑD   P*2 $ B"  ( H     =íÑI73#67677&'7&''67'67#67677&'7&''67'67#53&'736ª <+  %2 &,[   $2 &,0: < Ñ  &  '     ÿéö£ -39?73#3#&'#'67#537#735#35#3#"''325''67&'7&',¦Rz6( /?!+ "4K =€€€€+  U  9  £E &  ) % @9  '   4òÏ#'+?73533#3#3##5#535#535#35#33535#33573##5#'6556333,,1144..3/I/w )G.à  C  C & " Q RR,# #>Hÿé¥Ï#'+73533#3#3##5#535#535#35#33535#335J$%% ##&&$ / ¹_++_9=|ìÐ73533#3#535#'63#735#&&)k/ VV00É"" KF$;óÏ/HNT73533&'73#67&'#"''32655'675#73#&''67&''667#&'1 .       "1ŠA    %€  ·     " !* %         +ïÏC73533#3#3#35#535#535#53533#3#3#3#535#535#535#535#535#+))$$**],,&&**++$$--»¨¤¤©#00&&+Ä          E      4öÏ*D7'67&''67&''6#5'6733#33#"&''67B$ "L  J y  &i77+-  Ï     "    H7  (  .ÿêõg /573#735#33535#335&'7&''33267#"&5''6G™™00B1s00B1> P d  (!  gC) %            1ôÏ#'->DJ73533##"''32655#'6553'#33#7&'3##"''3255#&'''6? ?` jEE9JJo  f[$ %K  2 °R N," !*>, "    ÿèòÒ $(,06<Bin7#3#3'67#'6553&'75##5#35#335335&'''67&''33267##3&''67&''67&567#òE<¹¾ ªZ +,,,?+(R 2 W   "e39#'4 . 2Nà *   B4 4=]  6          9  6ïÐ '37;7&'''673'67#'6'&''67&''6'3#735#Z %}= 1L  $}%   &…RR..Ð      $6 ÿèðË%+RW]73#33#5##53537#35#35#35#&'7&''33267#"'3&''67&''67&567#''6ÜoUµ=Z.7 [  m 'r"4E,(5 +! 1\.  Ë C%%C !            : E  ‚<ñÎ73533533##5#5#5#‚- SP---¯aa1Nÿò¶Ï*73533#3#535#3#735#676767'7&'R'**"U 'WW00  5)  ¹56(z7ìÏ 73#"''3267'675#'6&'œE  ( 39  ÏY  )  7ôÏ2?L73533#&'#5'675#7&'36533#&''67#'&''67&''6100  1Á  @'.* $  "%&e   N   ¿4 *+!/! 2)$3       %ôÐ !&*.I73#53&'73#&''67&''667'3#735#367#"''3255'7567#Q/w2fD    °[[77b .  25 JÐ *     %"' !   MÿéôÑ!N73#&'#'6#'673#&3533#3#"''3255#&'#5'675##535#k- g  ,u@CC@  -* %! ,*<@Ñ   '  ) 9:"&-? ,ôÏ7;?C73#&''67&''667'33#27#5'75#5367#35#35#75#£D    ¶W ,,KD666666Ï*     %"* DK-+- ‡=éÑ 73#53635#35#35#«0b;;;;;;Ñ ~~ 298rÿýÊb7#"''3255#'65535#35#Ê  ) ''''bP  $$ ( yÿúÕj73#&''67&'767#53&'¡&      8!j        Gÿé£Ð $*73&'73#3##"''3255#735#&'''6Q RA &  )  ³ EG  D#<  (îÒ (,04873#5##53'3533#3#3##5#535#535#3#735#335335‚d¸dGNOOKKeeaaJJN««!!1!"Ò %& (     =/ 4ôÏ3IMQ73#&''67&''667'3533533##5##5#3#"''32767#'63#735#¨>      ÄS   I <<Ï %      !C * * Hÿè¤Ï 173#53635#35#3#3#"''3265#'667#53&'r"J&&&&-+    Ï NN +-%  2 "&KÿîòÏ9=AEI73533533##5##5#3#3#3#3#535#535#535#535'235#33535#335Q%-((-%‰ EE::<-4"E6~Ð577#"''32654''67&''67''67&'77&''6f     (, # #.Ð  $      0ÿëó­!%)4:@RX73&'73#3#3#3#5'65#5#5#'#5'6&'7&''3326767#"&5''6~ *+%%%%,v 6&&&&&,  G  U  g , ª @n\Bb     "   8öÏ&OU73533#&'#5'67#'3533##5'67#767#533'7##"''3255'67#53&'&'¥    –  d-A       ±'DD-h;*  5       J*ôÐ,0Q73533533533533#3265#"&55##5#'67#5##5#3#"''3255##5##535##5J   1 U\7;    )"42Á      ..$31ñÏ%+AGMS733533#3#3#7'75#535#53&'#53367#73533##"''32655#''6'&'&'6  6--47A6..6' 1V7   7\—  Ï$$$     &  Q L-    /L.ëÎ(,8E733533##3#"''3255##5##535#5#53#3&''67&''&''67&'h?)G   31E)R??  6  Î  E  1DDIY  0   Kÿë·Î(INTZ767&'767#53#"''3267#'&5'56'675#535#5'67'#3#6'3&''6'&'s  6     M+5$$#  !!20  #0Î,  ; %  D½#   !P  ;    1öÏ $+1<@T73353#53373#&''67&''667#''6#5'673#3#3#3#535#535#mVH3         ‚    $JJU"#Y$!Ï#$ %    2 ! N9    #ôÍ0EIMQU[agm7676767&'7&'#"''3255'67'67'6'3##"''3255#53535#35#35#35#'67&'''6'&'ä  %   $ 7A ,  3 33333333  ¾ 5 #  Í  " / *  W% "W           /E‚X73#/SSX',um7&'5#!mtu73#735#UU--uY3(ÿë–$733#3##"''3255#53'67&'Z++0+  72 L  –E  @  ÿêöÉ ?73#3#537#35#35#35#3'33&'73#67327#"''67&'#ÝkY²D](ŒŒŒŒŒŒ(CF+x   %2= 9( MÉ ee * ) ' .     ÿéòÊH73#33#5##53537#35#35#35#67327#"''67&''7&'37&'7ÜiR»=_0~~~~~~–`   '.@ ;) JB- Ê L&&L % " " ,        ÿéòÏ28>FJN733#67327#"''67&'#53&'#53&'73#3&367#7&'#5##535#35#Ÿ;:     ™&93,X4  7FFFFFÏ-" &$$$ '"  $$0^r s)@ ÿ襉#'+1773#3#3#535#535#735#33535#3355#35#'67&'}(•$%%6#Y%%6#%%% @ ‰C    ( % '     8ÿéñk736533#&''6767#DCLD> <& %@S -0   ÿé~É73#'6655635#t !+CE 5"0/É N4!5D8l(Bÿêçd $7#"''3255##5##5##535#35#35#ç   00 dc  ++--5w0Lÿêéq73#5##5353335#‘Pn3X‰nnML L;GCÿæëh7#"''3255##53#735#ë  ‚,KK''hj  Sn€&>;ÿéòw$73#67&'67'5'67#53'Ž FM <  )4B w  ":0  .  6ÿèèi)/573#"''3255'675#73#"''3255'675#&'7&'@I %6TT ")AG b il  $l  $   KÿèºÎ73533#67#"''3255'75#P+++   )0+ž004 @  5  :g ¢È 767'56›È   žmÿçôÊ %7##53#"''3267#3&''67&67#Œ u  Mi  $3 awà5"D6%     "`ÿéôÏ $7#5##53&'7#'65533267#"&5èY3(%F  ²=**= I:7 ,8"q MŸÐ7&''67'67676767&'Ž  M .#:7 B$ HÿêõÏ 7733"&''67&''667#73265#"&55#'655b&-972   i ÏK6  7C$2$m ‚7@!@?XÿùñÍ7&'73#5363#3#„  T *…GOtt™™Í  \6 NõÐ#73533533#3#&'#'67#535#35#1A11<6! &!A, #8>1DAA»  `ÿìñÏ 37#5##53&'7&'''6333267##"&5477#ça: & g8 ! '6E®**   ")  ( RôÏ !7&'7'6'&'3#&'#'67#x  W y Ü7# ,=/ " :Ï   ' $# IÿêôÏ 3733##"''3255#53'&'&''333#"&'&'75#  66X  I  :* (' 0 "Ï1f  a) Ee    ULÿêóÏ.73533#&''655#'&'333#"&''75#$** )$"  ) '& /  !™66( *$4A Fe   UUÿèòÐ$(7367'673#&'#'67#3'6673#Z0;2O, #"  &#  8“ +(-2( #,h_ÿèôÎ"&*73#3##5#535#535'235#35#35#35#ã  BB99<5""5"W""5"L  ¾`  9<K _ÿèïÓ!'373#"''3267#3#"''3267#536&'3353353#•A   Ikj7aÓ 7! (MR >MM   NN>>§A    ' # M    UÿçõÆ $(,07<@7&''67#53#3'#5##5'6735#33535#33567''35#Ž  LH  H!##5$Y##5$L &HH@ JJ @ >p+ :  5 (P„Ñ#'+73533#3#3##5#535#535#35#33535#335100**//44**1*B*Ç  <   < " " Kÿê±Ï")/5107#"''3255&'#5'#535375#7675#7'6'&'§ '& D  >  ˆŠ #bj+0žGG"9C N   GôÓ!(,0J73#53'73#&''67&''667#'3#735#37#"''3255'7567#Q0w2h>    "ƒYY99b,  47 IÓ         !   wÏ`73#3#3#535#535#zR"X# `Kÿé¥Ð(,073533533#3#535#35##"''3255##535#35#NZ!/    '''''·oA&  &  444=**=4""4"^**<*Ñ #$$    2 B  C U~ òÏ-273533#3#3#3#3##5#535#535#53&'#535#367#…(--20((00//))1/(&.¼  :Oÿè¥Ï 073#53635#35#3#3#"''3265#'665#53&'u"I%%%%+)   Ï NN +-% 2 " !Kÿé÷Ç$59=73#"''3255##53535#335#'&'333#"&''75#73#735#å    I 11 <  " ,- 6  M-- ÇI`  LcrII9$, MU  G3 IÿèõÐ '+/?7&''6'3533#3#3##5#535#535#35#35#73#3##5#535#à  k! !%%%%C@!!Ð "Z''Z<:"FF"RÍ#'+/37335#535#535#53533#3#3#353#735#33535#33544''11//))66d(A(j 33 LKÿé®Ï(,073533533##5##5#3#"''3267#'63#735#K8  - 00º o\   M,Eÿë§Ï"9?73533##5'67#7&'7'6&'3&''67'7#5367#P!##   H   & ! ¦))3 4    4     0 `ñÑ !'+/3773#'6'#3#3#53535357&'3#735#335335˜LS '""&e==q 'jj   Ñ  *i. 0ÿéîV"73#3##"''3255#535#535'2« CCbb  ccJJ*1V      @ÿë”Ï573673267#"&53#3#&''67#537#'6P  +   !  Ï         lÿÿÚy $73&''67&''6673#735#Š4    9MM**y    2.KÿéôÏ,1@FJa73&'73673#3#3#&''67#5365#535#5#35#"&55#'67#333535#7#"''32654'7##5N  '*!   %)%!8;   3;;;“  ´     ^    ^ 6   > s?!! CËÜUÿéöÏ$59=N73#&'#'6#'673#&3#67'7&''735#35#7##53#"''325r+ ]  -qC1  mA Ï       Z.  o8#¡r TÿéòÑ-:KOSW73#&''673#&''63673#53&'3#"''325'##53#"''3255#3#373#q(  T(   2  +ž(d  H F  !Ñ      'a  &x_  I:LRÿø–Î73533#3#535##5##535#R><¯##IX\7' NÿçõÏ-159=AELdk737#535#5#53535333##3#3&''67&'7#735#33535#33535#3356773#&''67&''667#"P# %'&   -1#$  0*      8 +   +    ) G< ¨@) - #P)!‚ ðÑ &6:73&'73#3#"''3255##53&'#367#3533#3#535#35#‚*-  D0"7½  u `t…  0  // . ÿéóÐ)?RhŒ7&''67&'763533#&'#5'67#73533#&'#5'67#67&''67&'3673#&'#'67#3#3##"''3255#535#535#'2   v       7    RRv:" .59 +=“BBcc   bb@@ #Ð    %  $$   /       OÿéôÐCHY_e73533533##5##5#&'#5'67&'77'767#'6733&'73'673##"''3255#'67&'S%*((*%„  A       # 24 $ p|7   2  bà  M         H- *  WÿèïË >B7#5##535#3#735#3353353#3#67&'7'5'67#735#ïttt ``   Ukk\(  ::Ëâ ãÈ· + ( &     =ÿèöÐ/5IMQhn73#3#5#53&'3#735#3&''67&'67#53667#'#"''3255#'65535#35#'66533267#"&55&7# KŠ|ŽK5ŒŒhh&          i +   Ð  B) !   3  5Z  )(+" !1Y - MÿèöÐ SW[g7#5##53&'73533533#3#3#&'#67&'67'5'67#5'67#535#535#5#35#3&'73&'#ên?D* ,  0  # #)H!# & ½       3      4 KÿéöÑ MQU[_7#5##53&'73533533#3#3#'#3#33#"''675#5'67#535#535#5#35#3&'#35#õHG1&*88 Q  / &.K?' HH¿%&          '    &$ @ÿéôÑ"&3Im7#3#'6553'75##5#35#33533567'53373673267#"&5#5'67#53&'73533'673#&ð/)~>+S      $ #/#   , ¾ ' E? 6BO   3 ,     D&'     TÿçöÐ /8ANTZ`f7#5##53&'7&'''63#35#535#53#56367'7367'73327#"&'&'7&'&'7&'èp:     ))d++.@ˆ   :  7 KE ,E½)*      =9FF SG S! %"3        LÿéîÏ )-15FY]73353353#3&'73#3#3#3#5'67#35#5##"''3255##5'673&'7&'#735#[+'‹":92222:‰ =,,,,,z  p'  *  B Ê!   /    > +CR.   GÿéõÐ%;K[73#67#53533#'#5''6553&'#5'67#53533#&33#3#3##'353#5#535#535#¦?…    B@     '''&&**E$))""$Ð >    % @2 3;Y L(&     ^ h  Iÿé ÏI73'73#''7&'76#3353#3#"''3255#67&'7''67##537#I" W4   ++'     º       66DK  8  SbGÿé£Ï48<@73533533##3#3#3#&''67#5365#535#535#5#5#35#335K &'    !4  ¼ 0  0 < HÿèôÐ!<@DHou{73#&'#'673#&'#'63#3#&'#'677#5367#735#35#35#&'#"''3255'67'677767'&'''6t" N- MwE`)#- ,SSSSSSL    < Ð      E 0   D          KÿéòÐ<AZ^bz~‚733#67&'##"''32655'274''67''67#537#'77#3533#67'7&''275#735#335'3533#7'7&''275#735#335{L+J      !-(')+ 4ZHTA'  "  †  '  Ð          x+   +  ÿèñÏ"73533#3&''67&'#535#67ceeN)$3:+/?;&$Oc9!'®!!'0  1':-'bóÌ $(6<73#535#535#3#3##"''3255#535#735#'3#7'675#&'sqr_\\^7E  gg##U=   ÌD  <1    B  ÿæêÐ733#3&''67&'#5367n\\Q%"38(*6 5$B#Ð#$2    .'$ ÿèÜÏ73#&''67&''6667#S} %.*!/.# UÏB+$ 3*,ÿè‡ÎB767&''67&'373#367#"''3255'7567#'67#5367#  0 .7 7  26 )   &Ë     ?  !     ÿé…Å"&733#27#5'75#5367#35#35#75#R "&,A=000000Åe&#p DBE ÿçˆÐ)-17#53673#3#3#"''3267#'667#53&75#35C)!-&)I? . >$@@bV  V  + #  = ÿë‘Ï/373533533##5##5#3#"''3267#3##5'635#  M  A9+ » n)T DML"ÿèˆÎ";A73533##5'67#7&'7'6&'3673&''67&'67#367400 +Y  N'-  # 2 ¤**. 7   4  2     GóÐ;?C73#&''67&''667#'3533#3#&'#5'67#535#35#335¢?     &“322)"  ,3,Ð$     1  %  10ÿè‘Ñ!%)-K73533&'73#3#5##5##535#35#33535#35#3#3#"''3267#'67#53&'78-.7.J.3MC   4  ":¿  PQ & % ($ (ÿéŒÎ+17733#3#5##5335'673#&'#5'67#7'6'&'D445V2/4*1*0,  ".^ ?  Î '%]' *:88     ÿçòÓ#?CGKQ733#5##&''67##5'6367#3#3#&''67&''67#735#35#35#667#Z` 6G7!  B 0 g [ ³y  ,7/(F 1# !"M_Ó  %    /S    ; @  ÿêôr !.;7#'6553533#&'#5'675#&''6'&''6ïÀKNN&8 6$. 7$K†  g   r!-) !*2!   &$       ÿçñÎ73&'73#&''67&'#367g  a'# $; 8-*><))%;%% ¥ &8$(=6! 7ÿèÎd73353#5#'6535#/w|ywd|. )  ÿèÚd #73'6673#'3#3##5#&'''6) vaa s00] > d:.+1vuCC     [zÏ73&'73#&''67&'767#**   Eº     ÿìôˆ)-159AEIM73#3#35335#535#535#533#5##535635#3#3#735#3#53535#35#35#C %Y$.º-::,,(( U*è)ssssssˆ  >> ?  6:3  ): :ÿéñÍ73##5#'66556Û F\«<[)bÍ )……A81VÿçìÎ 73#&'#5&'75#'66556Ø F]¬H& " P# cÎ *2 BK  *)3-$xÿéëu73##5#'6556­ 3C©>XIu DD#  $* ÿï€Ç"&*7#53#3#67'675#535#53#3753'355#m2  0; 2 ;\kCA "ACk0/y!!!!!ÿìŸË6@[v767767'7''67'67676767'7''67'67'33#3#76777'74''67'6767774'7''67'67;     3    mwwo‚*   3   ¬        ]]O       ÿêèp 7&'&'   8& %5p6tÿéô…73567#533##"''3255#t8F^44  8G9 6mÿèò’73##5#'6556Ý %/^,6’[[ +& !#HoÿèõŽ$73##"''3255#'67#53535#&'á 666  ŽM1 .' MM<mÿëó%7'67773267#"&55'75'7š // 79DF $*-&)n  $kÿèïÐ *73#'63533#3#"''3255##5##535#‰Yb  244/  ,2Ð )M  6ffOadÿéó #7'6553'#33267#"&5536‘lDD $ Y86 ,686%#   L hÿèõ™73#3#&''67#5365#'6”K.95 , (1 2 36 ™ *)(, ÿéî–"(733#"''3255##5335#35#7'6'&'¬+   G-GGGGJ A  –0i +|!2Z   nÿèò®&*.736533#&''67#3##"''3255#3#735#‡$*-  ! „   _ ??š  +N  K4cÿçõ›%+73533533#3#535#35#35#35#&'''6r/†&//////-" #  SS12(    kÿé÷  06<73#3#535#5#35#3353353#3##"''3255#'67&'vv$!r"#B!![ff ‚4  ;U   22 #,      hÿéõ¥/37;?73#&'3#5##535'67#53&'735'273635#33535#35#Ë(" "0L0 " #//  @/L/Š  V W      ], cÿéõ©"&,48<7&'#5'63&'3#735#&'735'6#5##535#35#ª @ .+ 5zz$$ /"   FFFFF©   8  (CC ! \ÿêöÐ EK]73#'63&'73673#67'#"''32655'67''67&''67#'&'333#"&''75#~hp "  %  !     !  $&%.  Ð *            "F  9^ÿéóÎ ;AG73#'633#535673#3#35335#533#335#535#535#&'''6€^f P"    ' Î  oc ..8ADq   ÿèòÃ$73#3#32767#"&55#'67#5367#Ä\mT - PIUZTÃ^ dM0,AÿéóÂ'73#3#32767#"&55#'67#'7367#ÃQfN* PKG<^Â$T! ZD.*74-$iÿçöÄ)73#3##'67#'7367#33267#"&5}t)-8-)7@Ä&C3+:6'fC &  ÿùwÐ '73#53635#35#3267#"&55369+X 11118 '$ Ð \\ 4:3 A,ÿéÓÄ 7#5##535#35#ÓÄÛÛYF¢I"ÿï6¿73#"¿Ð1ˆ 7##55#35YYFFˆs‡1ÿççd 767'563#"''32765##5i #$"". *¥  <d 5  QE $`rnñÏ73533533#3#535#35#3B55=â;3GBBµ!!!!ÿèî]73#3#67&'7&''67#*ªªÛ„4=  PO#?]   ÿçñh%73#3#"''32765#'67#'67#'67#á” :6 - (!"5hD #-!") HòÏ *7333#"''67&''667#73&'#<7 I$P&'$  % +U" Ï $     &4)ÿé¹0 73#'3'66¥W0GG bÿéìo73533#"''3267#'67#x!@,:3!VM6B7ÿõòV 73533533#7&'7'6PNä$  ¬ OOOO]    ZóÇ%)73#67&'7''6'67&'7''63#ÓÓ’   %)]   %)!ææÇ       &jðÇ 73#3#535#35#'6'&'Ê@KàH=P''` “  Ç9999   ÿçôu"&7#'6553#3#67&'#67''3#RÈ´¸U   L œœ++44(   :* e ÿéó‘ $*73&'73#3##"''3255#735#'67&' hiæ¬H  P‡‡ # &~w   3&  #(  PÿéžÏ73533#&'#5'67#V  ¡..zc )8ÿéë^ 7#5##535#35#7#5##535#35#u88888Â;;;;;^u u,E>u u,E ÿéô†$+377353&'33#"''67&''667##5##535#;6  $R#*$   ,“xxx† >      >@ @& ÿèòt 773#&''67&''667#'3#3#67'753675#šH     .…e' 1= ,t#     %# B?R OçÓ $(73#3#&''67#5365#'673#735#2@'-/' ) /3 gZZ22Ó    S0LòÏ8<A73#&''67&''667#'#3#327#"&'#67'535&'#¢B     ((&&" % E$Ï     "  . t 2 2ÿëò­-59=73533#&'#5'67#73533#&'#5'67##5##535#35#9$    a$    Ammmmm˜  '&   '( @\ \"0UóË/?73#7&'7&''735#35#73#3#'67#'7367#33267#"&5^L/ ' ::::Zh(05    .8  ËC    T # (    4   ÿêõ¦"&,0473#3#&'#5##5'67#5367#735#35#3&'#35#35#*¨Y 4" n 0C8„„„„d B nnnn¦C  > ? ( % D  ' F êÌ *.2673#735#35#3&'73#3#3#3##5'65#5#35#Xˆˆbbbb#160000:w 9+++++ÌG+ %     F    Sÿè¡Ñ 073#53&'3#735#367#"''3255'7567#wH>>D    +Ñ  431  WóÓ )5E73#53635#35#73#3#'67#'7367#7'533733267#"&5;.a====Yi(05    /WF,( KE Ó99 (    E  )   2ÿèï²5=AE73#&'#5'67#535'6'3#&'#5'67#535'6#5##535#35#Ú#"   '4   "&({ppppp²  *)  *'  jX X -bâÌ!%)7#"''3255#'65535#35#'3#735#35#â  D AABAtJJ&&&&ÌT   $%+2U14ÿèñ|8O7#67'7&''53'73535''67'67'67767277#"''3254'67##5¤6 $$$I1 %    ® lO  z ;   E*  z‹Iÿè¦Ï 173#53635#35#3#3#"''3265#'667#53&'r"J&&&&/*    !Ï NN +-%  2 " ! ÿæ÷Ë RVZ^b73#3#53'#735#35#3#35#53#3#3#3#67&'7'5'67#535#535#535#735#35#5#35#(´SdáiKZ6]6==55Da    N 5# 5)[C55==499j<<666Ë6  2     !     1"ÿõÞ·7#5##535#3#Þ”””·ÂÁœ‰:VäÐ 73#"''3267#'67#'67#'6G•  ! 81 !=7!#Ð C',"#(  ÿéêz"(733#"''32765#&''67#'633#L œ I$ =/|z U" 4    ) RóÏ#'+/73533533#3#535#5#35#33533535#335335 M'LL;Â:M‡';((;'(((;'(ÁOO-QïÐ73533#3#535#35#33535#335dffWÀUd!CCWD›CCWD SS .1Fæz 73#735#335335ÍÍ((:.+z4aàÏ73673#53&'35#33535#335Q )  4Á2 EEYCœEEYCÏ  RR 40\àÒ#73673#53&'35#&'735'6Q *  5Á2 EE  JC&  Ð WW Z5  55  OóÏ;73533#3#&''67#5365#73533##3#&''67#5367#'$$*-  "(-'p'%%/* ! " #  $+'¼          JóÇC73#3#7'5#'67#'737#73#3#3267#"&55#'67#'7365#a'* 1 )! (je&. 2 ' ,Ç !   .'#  &% ÿèîÐ"&*73673#3#"''3255##5'67#35#35#Lx‚  p,BCpppp´ €  5}$+IAUêÑ'+073#767'5#53&'7#"''3255#'65535#35#= *G)# *¾ @ ;;=<Ñ 6 ?`   "."/ ÿéõÏ.26:@DH7'67#535#53533533#3#&'#"''3255##735#35#35#&'#35#35#9%2+&&d)).5   jddddddh JjjjjI : : G  %Á !! * * …WéÊ7#"''3255#'65535#35#é   4 3333ÊZ   /, ÿéóÏ73533#3#&'#5'67#535#$QSS]P%8 ;$$9 < P^Q©&&%79hh8  3% ÿéõÏ73533#3#&'#5'67#535#dee[O$< ;&#: :"LZd¨''&26gg6!3& ÿéôÐ73533#&'3##5#535'667#``Q$8 ;%9988#: 5Q£--H!'KW11UF*> ÿéðÏ (73533##5#7&'3'66#"&55332bggb™a  ´  œ33  E  GA ,0 fbÿèíÎ73533#&'#5'67#7&'cdX#5 7""5 8 W™“;;G %F‡†B&(AJ  \ÿéòÈ $(73#3##"''3255##5#535#53535#35#ß  GGGGGÈ?*7  2PP*??,i*>ÿè¿< 7&'&'g%* ,'@8 8=<  ÿêÔO73533#&'#5'67#B9,!2 5 - +3= "77 VÿéðÏ73#5##5#'665335#53533#3Ø!  6699!„‚tB7   $&DÌS7?%5A!>615gÿèòƒ73533#&'#5'67#WZG!5 :$!5 1Dd1;ea50gÿèðÏ&73533533##5##5##53#"''3255##g!!!!"| 7µ@X BzaÿùòÍ7&'73#5363#3#‹ N '}B Imm‘‘Í  \6Oÿÿ®73&''67&'767#Z7    $®<' &ZÿèõÏ%)/73##"''3255#'67#535'673#35#&'ã  H  ckGG  ‹L(  #& !E . L; ÿçùÏ.4K73533#3&533#67327#"&''67&'#535#7&'3533#&'#5'67#400=A@    " Ž<4²  £511  (2¶&' (%%7&+ b  8?!/KäÏ 733#3#5335#35#oaaQ¥@,}}}}Ï VV"0>éË*A767#53&''27&'67#53&''27'767#53&''27&'D'! € - "&<& Ka  w Kb ¶    9       SçÏ $73#5'677'673'67#&'&'P !7EJ A8 c  Ï|%   % <-  :ÿéòq &,27&''6367&'#"''3265'&''6})= 77(9 M  " "0    .  , ) -q  '    :   ÿîòt )7&''6'&''63533#3#535#¸ $x #WWWhähWt ! ,CCMÿç¤Ï73533#3#3#'67#5365#535#`!  ¥** , ! Uÿè Ï73#5#'67#535#533Œ  .&ÏæYE9TAEÿê¡Ç"(7#53#"''3267#'675#5367#5;5#365#hK   ´¥ ,2 %)g]ÿêï` 73533##"''3255#'67&'i755  7\  N7  3 !  Rÿè÷Ï ,73353#533'67&'#53#"''3267#'66Ÿ&…&$#P !*m  .7Ï1&88&+ 'GF/8 ÿèÑf73'67#&''6m e)Œ ‚#a   0f >.  X‹Í73#&'#5'67#535'2x 00  (61Í  !.ÿéóX73267#"&55#'665&'µ  J*XQ " C)&  !1!TÿçöÉ &7&''667#535#535#53#6''6Ù,8 "$2^YY]r-VX +7 //f   ÿéóÒ?GKO73#&'3533##3#&'#5'67#535#5#535335'67#53&'&'7#65##5#|g3  0''ChV!5 9$!72 RfC%%0 AdCB ,900Ò   !  23!  ) HMÿìöÍ "(8>7&'''667&'7''6&'#'6733267#"&57&'Ã/   ).$  4 "_  Í1 0.*  +& 7  8 ÿéõÏ#'+A73533533#3#&'#'67#535#35#35#35#3533#&'#5'67#%i&&,2 $!L+ &4*%8iiiiii;>*( )#-( )Ä J  J., ;!;:  ÿéïÏ#G73533#3#5#&'#5'675##535##5#&'#5'675##535#53533#cddL95& !.%5 <'=PcÅ;.7 -,$8 >'=PccccÅ  '  ( q1   ), 0  Uÿè÷Ð +177&''6'3#3#"''32765#'655#53&'&'&'à  3,'   S  Ð &!!$0%d# >J/ 0F-f. G{Î7&'73#'65535#C!I54º @ !+/KÿèôËBG7#"''3255#'65535#35#7#"''3255#3&''67'&'##567#—     &=  4ËÈ  :1) 6Aa=,h+ o9 $I 3   váž NðË573#7#5'275#35#35#75#767#53&''67&'u5" #111111d J`   ËH N.. -)   ;ôž#73&'7&'#"''3267#'67#5'6aw2-   5I ?. 0ž  $ '    cñÏ-73533#&'#5'67#73533#&'#5'67#,%% 'n(//   #º  -0 +/ Bÿê²Ï !7'673&3#"''32767#'67#l 0E  & Ï7 %())-*\JU"KdÿéêÇ )7#5#;5#35#'673'67#&ê~HG E % S< ÇLL****&  ; Iÿé¡Ð 73#3#&''67#535#'6g+ !Ð' ( (51 $Pÿé©Ä73#7#5'75#35#35#675#PY    Ĉ.*• U$`'Fÿé¶Î "7'67&''67#7&''6q 1 &  &2  Î/   p #(x™  . $! }QñÏ73#&''67&'767#'6˜J     - Ï     Fÿê©Ç+767#533'67##"''32655'67#53&'j 9Q      -® o L) (1  ÿéó[!73'67#3#33#"&''675#Ð D55<421 $`[  ( ASÿèôÈ .73#735#3353353#3#"''32765#'67#53&'e‡‡%,>ZQ   A & .!;ÈJ(((((> : +< QïÐ #)73#53635#35#'&'7'6&'''6y'W2222CÈk!Ð ff3DE *  TïÏ,73533#&'#5'67#73533#&'#5'67#,&& %l)/,   µ 3:'8;‡ʘ 73#735#3#735#‹::CC ˜8=< Fÿé‘Î73&'73&'#5'67#W     #"­   ie +2QÿèìÇ )-177'66553'#3'6'33#"''3255##5335#35#'&' ZZ ')  ;%;;;;*C6 0$^:(  j ,"3Q  ÿéƒÎ273533#3#535##5##53#3533#&'#5'67#/00,h(/sNDD)++  &½  )#'!  )-=ìÑ!-173#3#&''67#535#'63#53#3'35:3#'   *0 eWjhULL9Ñ     D|3"mÿèøÏ !7&''63533#&'#5'67#¬ ! ,&* #$Ï "%""?''//_L !-Lÿé¥Ð#'73#"''3255'67#5353635#35#35#y   * $/Ð · )&s7A9KÿæôÑ%C7#'6553&'73#3#535#535'6333#"''67&'767#'7#ó†B2 D$V, *,%/ »PC2 2>[   &))" '2   'HÿêöÏ$NTZ7'6'367#"''3255'67567#676767&'7'#"''3255'67'6&'''6à , .uA      -_     ";  4 Ï+N B< 4   E >T # MÿçöÐ"&273#3#&'#'67#537#53635#35#3533##5#–?7K) $* "$/+"PPPP !! ÐT '%T ,2o%%SÿéõÐ9B73&'73673#3#3#535#535#367&'#"''325'3'67#b%"=22A<33<<   $  A7% #±  M     9(„òÏ#'+73533#3#3##5#535#535#35#33535#335ˆ+++'',,//((+)>)»ZZ28<òÏ#'+?73533#3#3##5#535#535#35#33535#33573##5#'6556433,,1155//4/I/w )G-Ä  >  > $ ! K MM'! ;TÿéóÏ 6L73#53635#35#73#3#3265#"&55'67#'7367#3673267#"&5jB:O  ' &K    Ï [[4;A%Y  )%&?80&`     _'ðÇ "&*73#3#535#5#35#"&55#'733567#35#_‘0'~$.O#Z EZZZÇ{{K# )! W EöÏ6<BT73533#3#535#335333267#"&55#&''67&'77#'67&'3533#67'75#(((/p/(r$     X  A @(&&0;0(à ;/*         HÿêøÏ'7=CU735333267#"&55#&''67&'77#'3533#3#535#'67&'3533#67'75#Ÿ    OQ  0  , $,"£,,6&$$)#8J%+5 ($, 1 WÿèòÑ #)/573#53635#35#'63533##5#7&''67&'–>v"OOOOZwHAAH 1"D ÑSS .1+  B73533#3#&''67#535#73#3267#"&55#'67#735#35#35##""(*  "%(#bk   % GGGGGG¼    X   > ! " FñÑ *E73#53635#35#76777'7''67'6767767'7''67'67yAI    ¤     Ñnn 7J/ # 0 # EÿèõÐ ")-=AE7#5'673&''67&''667#3#7##53##'326=#35l  K3  &5]*M  **Ð!¢v 9<      ˜ *uc ?  BôÏ+?R73533#&'#5'67#73533#&'#5'67#'&''67&'76&''67&'76  ‰ !             ²  D6 " DA"-   9     ‚ êÉ$(,04733#"''3255##5##53&'767#35#35#35#35#ƒc!  *  I***É { ++.  G>SO÷Í!'-37&'73&'#"''32765#'667#'6'&''6k -V  . !  - %  Í   &  /J $$      ÿèóe'-173533#&'#5'67#73##"''3255###535#"     ![‰ bM0R <7M  H4 ?#WÿéõÏ+2N73533533##5#3&''67&''67#5#67#3533#3#3##5#535#535#Z!1##1 P (   !- =&7<<88DDBB337     *  @     SÿíôÐ!;73#&'#'6#'673#&3&'73#3#3#535#535#u) \ ,r;<@::G¡F55:Ð   !*  %''%Iÿé«Ð)/767&''67&'3533#&'#5'67#7&'_        >Ë    c$$  @>"3  WÿèúV767&''6''6£  +1 K* V # #) E @ÿêôÇ)-Gb7367#"''3255'67567#'66553'#3357#533##"''3255#3567#533##"''3255#jC    1 —qqh:Lh -5  :: )<   ™4  * /#^.8   A  •÷Ï*073267#"''67&'#67'53&533#67&'Ý    !"! {'  " "6[ z%e  PÿïõÐ.6>BFJ73&'73#67&'7&''67#3#733267#"&5'3'663#53535#35#35#WC@N  280<,  V  z£#&¾     244  !*===+++++\ÿéóÏ">BF73#&'#'6#'673#&3#"''32765#3#5'65#35#y' Y(Vc  VGQ @---Ï       c N SF/"Mÿè¬Ç"&7367#533#67#5'75#35#35#75#S).C  Ÿ s"|JMÿéóÑ %)-1H^73#'63533533533533#3#535#35#35#35#3533#&'#5'67#73533#&'#5'67#=£² Û",,.Ž,%$  %q&/%# Ñ   %>  99 @7u Ó]73#3#3#535#535#xY$&^&#]SÿêòÒKPV7#53&'73#3367&'#7'5'67'67&''67'67&''667#67#ƒ(> @YN 7,   ,  "! - % (J$8­   -         ; @öÐ !'-=MSY_e7&'''6733267#"&57&'&'7&'33265#"&5'33267#"&57&'''67'67&'u ;( " -!‰  {     p  CJ  s  c  Ð    # %     %   )#   )    >ôÐ$*.S73533#3673#3#5'675367#535#35#35#'#"''32654''67&''67&'76t%  "5 5h  92%D=DD8      Å   H) J " s +      _ÿéóÏ(.4<Y7367&''66'367&''667'6''6#5##533265#"&55#3##5#535#À   D    0A‰j\ "$$Ï     %    :()#G :,, Pÿå÷Ï#'+/5;73533533##5#5#3#3#535#35#33535#335'67&'X2 Yd21™E8ƒ8A%%8%]%%8%I (!J  ¾  TT .2'    Rÿé«Ï#73533#3#3##5#535#535#35#35#T!  && !))))¹[%%[=; YÿèîÈ #'+73#735#35##5##535#35#7#5##535#35#mppHHHH‚ÈX563x x-I>y y.I[;ïÐ #/7'2'6'&''&'#53#3#533#535#535#à3K=0  */0r/B”?5zyeeefÐ      9 Eÿë©Ñ .73#5335#35#3&'73#3#"''3267#'655#q!J((((!%4-   ÑSS"143 $ *ZÿîóÈ!%973#27#5'635#35#35#75#73#735#3#3#3#535#535#ZT 2 @88cŒ<22A˜E66>ÈI P0/IiEg~òÏ,7#5'67#53533#&'#5'67#53533#&¿ &.( €&//      J‡Í#'+/37335#535#535#53533#3#3#353#735#33535#33544''11..((55c'?'b 66 Q„ ëÇ!%73#"''3255##53535#335#3#735#à E 3 -"".. ÇJ` MbqJJ:%48  IòÒ1MQ73#&''67'767#'6'3533533##5##5#3#"''32765#3#5'635#¤? $ŠN   C8< Ò     @ % & @ÿê½È 06<73#3#535#5#35#3353353#3##"''3255#'67&'Qk c;    NZZ k,  , P  È99*7.  +   NÿêõÑ/5CI73673#3#3#535#535#53&'3'67#'6'&'3&''66''6†  =77B<55=$ *B 5 , S  $ 6 Ð  i          WQñÐ #)73#53635#35#'&'7'6&'''6žA5 Š   KÐ dd 3FD  ) IÿîöÑ $(,04873673#&'#3#5'67#3&'#3#735#3#735#73#735#W,T)" n…˜ " T  ZZ7733-33º  z‹  () #338ÿèôÑ *@QUY73#'6'3&'73#3#"''3267#'665#36533#&''67#3##"''3255#3#735#¥AH  M !#    S   d H66Ñ i#U28=L    'H E 4]ÿçôÐ ,28RVZ73#5##53&'&'3673267#"''67''67&'3533#67&'7&''675#735#335§9h<    $ o  e588 k>+ MMMMMM; "  (BZM ?# )@ÑQ  Q ! 1   "    õÉ048<@EI73#3#3#'#3265#"&55'67#5367#535#5#35#3353353'##3‚q!39  0 &   '( #@   7,  É 4    $ 4 $I  ^ÿíóÎ#'+/3733533##5##5#53#3#53#735#3#735#73#735#€+""+""r|}+NN**"11,11Î&„¨) (2  2 Éc 73#735#3#735#‚CC!!JJ))c( #-^ÿéöÏ!37;L73#&''6#'673#&3#67&'7&''735#35#7##53#"''325w* W +k@.   g>   Ï      Z-   o8#¡r  ô…)047'67&''67353&'3#"''"3#767#35#-!  #?  ? 6!š¨. 3 „„?  # #=4 HÿëõÐ9^u73#67&'767#533#3#"''32767#735#5''6553&'6767'33#"&55#'6653&'3&''67&'767#¬=„  %7 # &  A*       " 9   %Ð ? .*- @1 2:Y&      n     QÿèñÍBGKO73#&''673#&''63533673#3#5##5'67#535#67#5#5#s%K,!  B(" #7MH )41(E ,HHHÍ    ,   W J  C TÿéóÐ/<LPTX73#&'#'673#&'#'63673#53&'3#"''325'#"''3255##535#35#73#i,  O2  + #,˜#e  1  #####BÐ        ,g  ]e *y .*UGÿéôÏ4:>BS7#'6553&'73533#3#535#3533##"''3255#&''3#735#7677'7&'ôˆFDAH   E@@   #+¼MA2 2 +CR.   OÿçòÏ ?DHLPch7&'7'6'33533#3#3#3#27#5'75#535#535#53&'#5337#35#35#75#73&''67&'#367q  u H6$#=66FP! E77> 5$3!!!!!!9B     Ï      A K    \+- *    AÿéöÏ]aei73533533##5##5#3353533&'73#67375#"''7#53#3#367&'#'67#'67#535#35#35#5#R&(--(& Q      47A  A  ¿ #)  1"3( ) u/ I@5) &%) !36MÿçôÊ #'+/37;djp73#735#33535#3353#735#33535#33573#735#33535#335'67'67676767'7&'#"''3257&'''6d{{&&6%[&&6%|JJ+MM-F    !  %  & < Ê6 :" $:" T          MÿêôÑ9=E]aeƒ‡7#'733#67&'##"'''27&''67&''67#53'3732653533#27'7''275#735#33573533#27&'7&''275#735#335ÁLM@    &&,* 7_2C"  X $   $  £ #        W % -     -    ÿéóÍ&<NRVZ^tƒ‡Œ’73533#&'#5'67#7&''67&'73533#&'#5'67#&''7'763#735#3353353533##"''32655#'#''53535&'#7&'    |            kÉÉ-->*,OG G  &" 999'q  »  !        &-/ 0 ,=   X  $0  TÿìòË#'+/37;GS`73#3#5##5##535#3#'3#3#73#3#735#73#735#73#735#3#3#535#&''67&''6`‡;F55F;S''E$$''H((Y++%++$,,s>EžF>   \  Ë 0!11!0 # +  +  + '..    :ÿé©Ð#AGKO73533533##5##5#3#735#73#735#3&'73#3#3#3##5'65#5#35#F(( #(( 0 C (Á  11$ L   ÿéŠÐ $(,73'72655##5##5##5353335#335335P:+:e ¤!ƒ +33==E˜Mc00000 ÿëÏ1FLR7&'#5'6&'3#735#3#"''3255'675#73#"''3255'675#&'7&'U  H )9 XX44 5   $=4   #3  K  Ï    *%\   \      ÿè“É)=Qbfu{73''67'767#73&''67&'767#3&''67&'767#73&''67'767#3#3#5##5365#5#35#"&55#'67#333535#A    /C@    -CA     /C@     -E…-&V(-H 'V BVVVÉ   +      * ZZ . 8  ÿóóÏ733#3#53533„TT[æ'<ÏImžž ÿñóÂ73#3#3#535335#ÒZSSbæ&6dÂCU‚‚« ÿîõÎ(733#7'7537736732667#"&5Q%%$8EE  !'  ÎE`  ¡½L e ÿèõÏ"473&'33#3267#"'&'#7&'3#33#67'7537BA  Á  ™dd1,,=L“!<@''(RJ 4#3 ZWÿéñh 73#7&'''6'66yJh! "† 9n BFhHB  $ 0 ÿéá€!+73533#&'#5'67#7&'7'673#5#533<::2  /  i  )®šX((,, 6   o ÿçõ‘/>D7#67327#"''67&'#'6553'33&'73#67'675#'6ò;       r…¨b' = (       "-$0- #-6  "- 8 >  ÿêõ->DJ7#67327#"''67&'#'6553'33'73##"''3255#'67&'ò;       r… ªb&   )   N  )   1$0- #-6   "H  E 3ÿïó¬-=73533#&'#5'67#73533#&'#5'67#3#535335339#     a$   SÀ"#F”  -+  -,j//<0ÿîô°7G73#&'#5'67#535'6'3#&'#5'67#535'63#53533533á'    +5!     )=MÀ%*=° /+  *'”++9ÿîoÏ#'73#3#3#67'75375#53635#35#8$<< .. ! ))))Ï3 3 86As ,RÿéìÃ73#3'67#&''67#×p c&+A w'X 7PÃ< & ` $; ÿèõ¾173#6732667#"&55#3'67#&''67#âL$ $+ A> ] O0 #-¾F I  ©t-![  /;ÿèðÇ$)733#"&55#'6653&''67&'#367»(, U ²#%5:.,: 4& )!!Ç9  ,"  *h    ÿè|Ï!%73#3#"''3267#'655635#35#r !,FGJ7 5"3333Ï W 7 !' r<A5 ÿêƒÐ(.4767&''67&'3533##"''3255#&'''6   -&&   -W  9È      SP  K  ) ÿéŽÆ!%+173533533#3#'6553'#335#35#'67&'-e mHHJ% 8  Š!# 4>^<+f##:  ÿè…Ð873533#3#535##5##53#3533#7#5'75#'67#53100$_'1sN+4<!#25* ¼  ( "  ÿéˆÏ#'+3773533#3#3##5#535#535#35#33535#335#5##535#044,,3344--0-G-EEE  G  G ) ) JF F* ÿèÐ,2873#35#535#53#563533#&'#5'67#7'6'&'9A0g2,,  (Z?  Ð [VŠ))  .0 7   ÿíôK $7#53#3#537&''6'&''6xcØbiçkD  u  :<9      ÿéóÏ!*.2DIgmq‚†Š73533#3#535#733#"&55#'665'6553'#3#5#7#53&''67&67#3#&'#5'67#5367'23&'#35##"''3255##53#735#122)d*1½  n `(j^  *,9,z ,G1S(]KZZ‘   ž-ddDDÈ    O %     '& )8  ÿìðÆ 7733##7#7#57#337#37#37#* ¤œ o66K4T66C<9mYYnXGGGGYFFF ÿèóy#(-273#3##"''3265#'67#7#53735#3377##67#ÚF  F=:N=?=y,  ,,, J ÿèôÒ/dhmr733#5'6367#35#3673267#"&5'733#&'#3267#"&55'67#5'67'53337#367#335PW7© &JKL% + * TBS?   $&!? 79 HGO K4Ò #         '   # ! *3 # yçË 733#3#5335#š::-a!;;Ë'XXF4ÿéôÍ&9=AE7773267#"&55'75'75'6'3#67#5'675#35#35#75#Ø /18:  '*$'" 0—g $ #""""""Í%&3  6&" ‹1-•""W$`&!ÿìîZ)767673267#"&55'75'75'6Â#'*!N3(-1 ) 4("%J>A9RZ       g ðÏ&,273533#67&'#"''3255'675#7&'&'l4<< !     4^ =  ¨''"  !   :7  7 féÐ !73#53&'&'73#5363#735#¦4w. A"ƒM Fee??Ð  %  6E# ÿèód%E73267#"&55'75'75'277''277767'5'75'7æ4  ".- (*.- $&.0 $' #*     (     aõÆ /573#735#33535#335&'7&''33267#"&5''6vkk-G-  7  A   Æ`7>- 0  , P ðÍHLP7335#535#535#535#535#535333##3#3#3"&''67&'767#'7#3535a-  # ! !$$ 0*  bÀ!         "* _çÏ"&*.4735#53533533#3#3#'67#537#75#35#35#&'j%1>C + ! +3.L%NNNN6 ¡ I $ I ( B  aêÐ!'+/373#3#535#53635#'&''6'&'3#735#35#¶&*2‰%C R  W  ccAAAAÐ ''G'/  . 6O.1 ]ëÓ $(,04107'6733#736735#33535#3353#3#735#35#m $1 t 88!!1"S!!1"iƒƒ eeDDDD¥ ;H$  !?' ! ]éÓ $*06<@DH107'6733#736735#33535#335'67&''4'7&'3#735#35#m $1 t 88!!1"S!!1"[ KQYeeDDDD¥ ;H$          ?' ! $ÿèòÎ#)73#327#"&'#67'56&'&'Î "#MK)  6M YHK  Î&-6( !C6X  ÄO#9Y $ÿéóÆ %7#3#327#"&'#67'535&'#ßELH - U)%2 “BMÆK"%0.A  Í%%Y!ÿêlP73'66X,(P,) #ÿçŒM 73'6673#8@M-( ")bÿí¥Y 73#5#533533’z)*BU K8PPÿê»i 7'67&'3#"''3267#'67#C YD_ $+&i  '2 + ÿé©i 73533##5#735#33535#335<<<<++=*g++=*ZQ 0/ÿè¼u$*73&''67&''667#&'&'FF ),". ) !/ : ! =* )Ï< ( -*)=` ¼5M':ÿèõÑ-37&'3533#67&'#"''32655'675#&'± _b[ * B +49._!Ñ  %%# %$!Sk <#"< Šnµ¢7&'š  ¢ LÿêöÂ73327#"'&5#3533##5#O{  g&&&&Â__,~G>>kkYÿóí¶7#5##535#3#íjjj``¶ÃàŒ; ÿèöÏ,6I7367&'#"''325'33#5#'6675##533'67#3327267##"&5ª  s'2'D(  ,) >%Ï>  &:  ¡!8& z.EB );A&0*[   D ’Ï733#67'5#53Z$$  ÏFO \2ÿêšÏ73533#"''32765#'6655#O'   ¤++Œ) "bIJB>C šÊ767'7''67'6767  ! –C* *#:6!KÿçìÄ 7#5##53#"''32665#7333#ìt2`   b \•mmÄ12LS# &YJ@ÿê‰Ï 73533#67#"''3255'675#B      ¥**2 I  <:@ÿéŠÏ 7#5'673#f  ϰ‚2’Fÿéö™73533#&'#5'67#TCF9% &*+6r''89fe5  4FÿéµÃ &73533##"''3255#'655#75#7&'FN )N) `ccO M;# =RRAPÿéñÏ '7#5##53&'73533#3##5#535#'6íuD799EEHH# ³5##5  #$$(<<( I‡¼ 7#5##55#35‡,¼±¸J9966_ÿéòj73733#&''67#q*;3 / +9 4(K'*)'OÿèìÈ 17#5##535#3533#3#3#"''3267##5#535#535#ìwww "--**1   ##"Èàà¿­$99Fÿïõ[733533#3#53g 66H¯!GEY(0ÿéªÅ$(73533#"''3255##5##5'6553'#3_    [66ƒO  9jjU?%"MMB0DÿçôÊ #73#735#35#3#3#&''67#5367#Xˆˆ````“CQD3 6 C 9 ?E<ÊX545 '! 9ÿê­Î/73533#3#3#"''3255##5##535#535#'6W)),,( &-- Ê G  2__K[ 8ÿèˆÉ7#"''3255#'65535#35#ˆ ÉÉ ?0) *6v9&c*JÿêðÆ #73#735#33535#335#53##5#'66`€€##7#Z##7#U,¥%,Æb9@QQQ% ‹ÿèõÐ 7&''6&'&'·  & !%Ð $"$$<.^ÿèòi73533#&'#5'67#m390 " "%Q#&JO'!LJçÏ 73#"''3267#'67#'67#'6wf    3-&  Ï P88# /'  ÿéõt"E7367&'#"''3255'67#53'367&'#"''3255'67#53¦    " ##a    % ! 0t  %4  1%"'    0  ;*!CÿïðÎ#733533##5##5#5333#3#53533r-))-''%;;E­!Î+1AeeIÿèôÎ'733#3##5#53533'67&''66Ÿ22=EI"3 p  :2 26Î <<;;#     1ÿèíÊ #)/73'6573#'3533#7&'#5'67#7'6'&'O!Šo+,,    %W  A  ÊlH. ,=lá…[[ RM"-[! LÿëôÐ!'733#3#53533'6'&''67&'Ÿ::A¨"8 ^6$ (C Ð&BB  *BÿéóÌ 67'2'6'&''&'3533#3#&''67#5365#Þ ??IA5 5> : Ì  !   1 # )(! VÿèïÐ !%)-73#5##53&'#5##5##535335#33535#335¡ArA])'9:'':)c'':)Ð .. Gr 55t*CbTíÇ 7#3#3#535#êthhw‹UUÇ2sA<ÿëÍ273533#3#5##535#367#"''3255'7567#F!!%7%D  ' .¹ : %  MÿêõÏ%+/73533533##5##5#3##"''32655###535#M),++,)    rZ;((¶!m h< G+Dÿô„¾ 7##55#3535#„--¾¶Ê6%%$$\&BÿéôÏ*0673533#&'#5'67#3#3##"''3255#'67&'LGI< ! ' '=~~¥G  J!m¾ +)<4  1 >ÿé„Ï73533#7#"''3255'675#@    ¥**0 G  99;ÿêÏ73#3#'67#53655#535'6†  !Ï- 2 &'NÿèôÒ *.273#53&'3673#53&'#"''3255##535#35#¡?”= %(¦.w  `````Ò  *    5g  'y!0<ÿîõÐ '+/37&'''63#"''3267#'67#3#53535#35#35#Ã: f *0 +v¹(+Ð % 7#0#J@@@/////?ÿéóÏ/3733#3#3##5#535#535#533#3#5##5'67#35#•BB99JJII33>>DžceQ %&/QQÏg J 2 AMÿéòÐ373&'73##5##5#'67&'3#3#"''3267#735#R@B0, r tr €qy¼ ::;;    +0'1EÿðóÅ #73#735#35#3533533#7'6'&'Weeee&;:®— r  Åb:>‰VVVV^   :ÿé’Á7#5##53'>&'*   Áš‰Š›'O0&  ",0 7ÿé¢Ð %+17&''63#3##"''3255#535#'67&'k  !9!! ## I  Ð%J G:# FÿéóÇA7#67&'7''5353573#3#33267#"&55'67#'7367#‡/ X "   #. +Çx9   Á$$4"">% D  ,#!&>8/&>ÿèõÐ 973#5##53'&'''6&'67'7''5'66556žFyD+'! t  $     UÐ )'(  A'+LY  f)"  &!:ÿí£Î  73353353#3#67'5#'655H  MSSH ½,==,=C  @ %"  Eÿí‡Ï73#3#5##53635#5#_//Ï J_È E&++JÿéíÆ =7#5##535#3#67&'#"''3265'67&''67&''67#í~~~g+    "  'ÆÝ ݰ       CÿèñÏ=CI73533#3#5##535#'67'677767&'7&'#"''32657&'''6OBIIIyCBI%0**) %   )=  »)(}    ! -    AÿïòÈ =M73#&''67'67676767'7&''67'67676767'3533#3#535#Rœœ= !  c    ‡D@@M±PDÈI  .   -ADÿè€Ç7#"''3254'7##5€ ÇG#'EÎßyÿêõd 73533##"''3255#'67&'„,-- , P  S?  < ' EÿèóÏ-1573353353#3#535#3533#67&'7&''675#75##5#[%%8H¢G8;<< DH&&;w)'È#**!2=<  aÿéöÏ%+KQ73673#3#&'#'67#5367#7'6'&'367&'#"''3255'67'&'r+6;R#  $ &%a I  0      Ÿ   >     j.        GÿêòÈ '+7#5##5'67&'3#3#535##5##535#ïy: + +D!N„:H F8…aaaÈ&'    ";G G,AÿéŠÏ735#535#53533#3#3##5#AD&! !&IIKÿòòÏ/573533533##5##5##5##535#535#53#3#3#7&'K+'..'+¤{H55-n/66I£x  ¸0 0†!!0 AÿëôÐ *.N7#5##53&'733#3&''67'767#53'3#3#3276767#"&55#'665#ï{F!  * X>> N 4% ¸((  "    $M R1' %)?ÿêõÏ-EK73533#&'#5'67#73533#&'#5'67#3267#"&55#'667&'O    P$     *   : # ³  ,0 '#2* RI! <%"  , <ÿèôÓ 6:73#53&''67&'3#67&'67'5'67#735#™ G A# %S# "Iy/  9  2(TTÓ  & ;   : % 'FÿéöÑ */<BH73#5##53&'33#3#5373&''67&'#3673#"''325''67&'™ Hw?"S6G   g  D  Ñ ), 5*;'#/!#0B  9#Bÿé÷Ï#CI73533##5'67#&'&''6367&'#"''32655'67'&'P@FF& '4g  () )+ , ;   %   '-!!  º-"     )     uÿë÷Ð 17&''6'''633#33#"&''673Î  /   %%  "   Ð%3 )6 - !D 2ÿéñÅ +17=C7'66553'#33#3#"''3255##5##535#&''&'&''&'b ˜qqk‰?:  (%78_-G-,C2 /"[5$ _  Jbbfw+        @ÿë€È73#3#"''32767#535#@>&(  (&,ÈK(H" 0J)5ÿéöÉ$(9=A7#3#67&'#67'5#'66553##"''3255##535#35#쇇=  <   $ggo   PPPPPÉ# ) ("E6 3"`NS  "h ' >ÿïòÏ,<73533#&'#5'67#73533#&'#5'67#3533#3#535#M  O#      N???N±O?¯  A;' %B>#m>ÿçõÐ 37;?7'2'6'&''&'3#32767#"&55#'67#735#35#35#Û >O°O;Ï *+&    ==ÿéÐ#73533#3#3##5#535#535#35#35#B#%%""((#++++¼Y--Y977ÿé¢Ð %+17&''63#3##"''3255#535#'67&'k  !9!! ## I  Ð%J G:# FÿéœÏ#'+73533#3#3##5#535#535#35#33535#335H!!! ## ! - ¸]((]9=@ÿéñÆ%+17=73#3#3#535335#3##'3267#733&''&'''67&'O F< ! ;_ ;;;;Ð      ).b !( y$%tX  BKÿçôÎDJP73533533##5##5##5##567&'7&'#"''3255'67'677'67&'P"+''+"œxe**#   >  '  c »**#  )  "  A   9ÿçòÐ+/37?EK73533533##5##5#333###5#535#535#5335#35#3#'3'667'67&'L"3++3"D;;66CC55((((.|  $E  ¿  ^^  , &UT'  ?ÿø†Ê767'67'6767'6t$   "$¢6  !-'j CÿèöÒ0EKQ7&'#5'63'3#735#3#"''3255'675#73#"''3255'675#&''&'  $, K 9. .wwPP$H !'6QH #6  J Ò  #) 'V   V        9ÿê Ñ .73&'73#3#735##"''3255'7567#53B#!Z JJ##?  *. ;T½ 4^   cÿüÐv73#&''67&'767#53'¡'  !  @*v       jÐ`73#3#3#535#535#la&##)f*##(`FÿêíÐ $(04873#3#&''67#5365#'673#735##5##535#35#d1$&   )-  T==!^^^^^Ð       W5Xii%:@SôÏ%+73673#3#&'#'67#5367#7'6'&'Q>CJ]) #())96{ W  °   0  AÿéŸÏ#73533#3#3##5#535#535#35#35#F && ##&&  ,,,,ºZ,,Z478ÿé‰Ð73#&'#5'67#535'6      Ð,  fa$.&CÿêòÏ+:>B73533#&'#5'67#73533#&'#5'67###'3255##55#35M    O#     F bubbº  /, 0. H]  (u .ÿèóÈ (C_7'66553'#333##"''3255#5357#33##"''3255#53567#'367#"''3255'6757#] žxxfn9;  <<M=D    -KA    *˜4E6 3#`0   :     LÿèëÉ 9=7#5##535#3733#537#3353#3#3##5#'735#535#735#ë{{{+n%"F`!##%%) ,,->>Éá áÅ´ &&    4ÿìóÇ-39I[73#"''3255#'665#'3#"''3255#'667#'6''633#3#53533'33#7'75367žP  \P    c  V  }b\)1 ÇS >'$ &S >'$ !! <,II[ GC@ÿýžÏ #73353353#3##53#3#3#535#53IUDD#X#%\%Â(55(8!AÿéŸÐ+/37''67#53&'73#67&'#"''3255##535#35#‡  (#/   (((((¢    2k )€$66ÿè¢Ç $(7367#533#67#5'275#35#35#75#<11G   ! ! Ÿ  s!|JM@ÿè¤Ï%+73533533#3#535#35#35#35#&'''6B c"!   ±mmBC0  JÿêôÇ#'+/373#3#3#5##535#535#5#735+335#33535#35#Jª6//-t/006a,>11E/t11E//Ç9f g9eee&K7<ÿéÎ7'6'67#d #Î-b !( xEÿÿƒÎ 73#5##5365#5#_Î ¤ ®R22D33iÿõÒb73533#&'#5'67#k(,,  "S 26 qÃ` 73#735#35#qRR,,,,`Z687ÿèôÏ.26O733#3'7#73267#"&55'75#'665533#735#3#3#&''67#5365#Ž??O =,.  #&+ >ggCCw3B8', 6- 5;2Ï      I:/ +QE( #@ÿéõÐ#0@73533#3#3##5#535#535#35#35#7&''63#3##5#535#B  %%%%f  F%%##¼Y--Y97n)&MM&Eÿóòx %73#3#535#&''67&''6R–AK­NA a  xaa   %   ÿêíR#7367&'#"''325'3'67#y  6!   N@9-*R    #$  :1 #FÿæôÊ#/D73#3#5##5##535#3#73#3#'3#7&''63&'767#53&'Q“?H58KA**K((''L**< "+ )'!) 2&1 "3h>Ê ,##- '       .ÿ餯"&,273533533#3#'6553'#335#35#'67&'Y   Q ]77:   /  Š!#  5=^<+f##: >ÿê Ñ(.4:73&'73#3#3##"''3255#535#53&'#367#&'''6I ! %   $ % #  -¹  @  <*\    8ÿëñÑ,048>NTZ7&''33#67327#"''67&'#'65533#3#735#&''33267#"&57&'''6Ô  21     FW<5522  %mz Ñ #  !$-$!  @ 37, / } òÏ,173533#3#3#3#3##5#535#535#53'#535#37#„),,21))11..))1 0)' /¼ : |ïÈ"&73#"''3255##53535#335#3#735#ã  O54##44ÈKa  MaqKK:(77 8ÿéôÏ159>BFL733#673267#"''67&'#7#5'75#53&35#35#675#'3#735#7&'±%%      %+ vU.....MM))|  Ï+ #,   0R&#\!C38š8 Cÿó¾ 7#5##535#35#35#¾ÅË6$Z$]'PŸíÏ73533533##5##5#P&$,,$&Á:ÿéöÐ#'+TZ`735#53533533#3#&'#'67#735#35#5#'67'677767&'7&'#"''3257&'''6C!K!/ $/& '4KKKKKK:    + B z; ;A # " ]           @ÿé’Ð#73533#3#3##5#535#535#35#35#C %%%%¼Y--Y97>ÿî¯Î 8733533#53'&'7'63#3#7'75#535#53&'736_ _` &!!*+3'!!% Î:::1 6   >ÿêòÐIOUek73&'73673#676767&'7&''67'67#676767'7''67'67#&'7&''332767#"&5''6J%  +  ( !C "O L  X$0  ³    *   +   r  + -:ÿé Ñ473533#3#535##5##53#&'#5'67#535'6F%##!S %Z7?  !¾*''   /-?ÿéöÏ#PX\`73533#3#7'5#'67#535#'635#'673533#3#3267#"&55#'67##5##535#35#U   ' P  #   AeeeeeË & "     5d d$6EÿêñÏ&,2:T]7367&''65'367&''657'6''6#5##5367&'#"''325'3'67#½    R   8  P  ¢†N   )  ?:/$%Ï             @00   !  7(  =ÿçôÑ #'+/5;7#5##53&'7'6553'#3#5#3#735#35#35#'67&'ðJE,, # "      eÒq73#3#535#535'635#½// T#--+00q  ..  W ÿéóz.28CPV7&'733533##3#"''3255##5##535#5#53#3&'''67'7&''67&'''6"U:((&H   52E& M::¢s  V  … z   A  -CCHX          9ÿ鉯 73#735#35#35#'67&'CAA   4 Æ£rOM5 Dÿè€Ç7#"''3254'7##5€ ÇG#'EÎßMÿéî)73#7&'7&''735#35#7##53#"''325MI7!  %%%%}$G V*   h3!†˜j BÿèðÒ 6<BHN7##3#"''3265#'67#5#53'735#535#535#673&''&'''67&'ðY o  n  F=_**,9y&(    3-¿+DA* D+  P   B    ;ÿî÷Ñ$7INT\`dh73#'3255#'67#5353635#&'733#"&55#'6553&''67&'#367&'3#53535#35#35#g         Q    g€³)*Ñk $ 4 ?#  #     >       8777&&&&&DÿéôÐ 8@DHLP73#5##53&'3673#'#5##5'67#53&'735'63&'#535#35#35#35#žGyCB6# Z '2;+\ $$6$$6$$6$$Ð "%  Q P  ]..BÿëºÎ(JOU[767&'767#53#"''3267#'&''56&'#3#67'675#535#5'63&''6'&'n ;    %  %%0;)) &9 ' 6 Î,  ; %" DE  !#   =   6ÿè¡Ï973533#3#535##5##53#3#&'#5'67#535'6A&((#R&_>55>&&  $'*   $    +)9ÿêžÏ).4:73#3#3##"''32655#535#53&'#53&'367#'67&'n     !!&     C  Ï F AHj   CÿéôÐ#0Lag73533#3#3##5#535#535#35#35#7&''637&'#"''3255'675#73#"''3255'675#&'Cb  "'      0'     ,  ¼Y--Y97m   *!e )  6ˆ (  7  6ÿèöÐ/6JNSjo73#3#5#53&'3#735#3''67&'67#53667#'#"''3255#'65535#35#'66733267#"&55&7#œM€’N6ll(         l ,   Ð  B) !  4  7Z  )(+! !1Y - @ÿè‘Ð(,073533#3#3#535#535##"''3255##535#35#DO G  #####Á    Hi  *"25ÿèóÏW[_73533533##5##5#35333##35#53353#5##535##5#3#5#'6753353#35#535#535#33535D(9**9(;>>4&%43' #2;;LL;M+++Ä   2 "s"+ 99 * 5!2   KZòÏ&,2:7367&''66'367&''667'6''6#5##5Á   R   :  N  ›€Ï          6&'AÿèôÏ06HY]a73673#3#3#535#'67#'#"''3254'7##5&'333#"&'&'75#7#"''3255##535#35#›,31D  ! L"# %t  """""¼   G#'EÎß 4_ # Q R  i * 6ÿëôÐ!%)-?EK73533#3#535#735#335'&'3#735#35#35#'333#"&''75#'67&'{*,,5z2**z  7ffAAAAAAP& 04 >"  `  6 Ç )  0X> " ! 6c  UB   9ÿîôÏ5Y733#3'67#&''673767#53&'''67&'33533#3#3#3#535#535#535#53k ;2,    D 8L    '55..22>¸B33,,11Ï  / #      9ll  4ÿèœÎ-73#&'#5'67#535'267'5#'655‰ #"    & " Î  )) w6 5 '# " ]õÐ:?EKP73#&''67'767#'6'3#33#3##5#7#537'63'7#35#5#'#3'§@     + oQZRQ    Ð      &    ;ÿê©ÍCIO73533#3#535##5##53#676767'7'#"''3255'67'67&'''6B())"U"(^>55       ! .  Á   $  %     4   MÿéïÏ'-3;?CKO7367&''667367&''66''67'6#5##53#735##5##535#r   N    gQJzggAAb___Ï               6-./-99!CÿêvÄ73#3#"''3267#535#C1    ÄG+M5N$ 5ÿçñÊ -;AEW]73#735#335335367&'''6553#6673#"''3265''673#'37&''66''6Jžž-T   ¨–T  kP9    Ê2E     ) &+B: q k Y)     6ÿé¯Ñ  .;7'67&3#3#735#73#735#'67&7'67&s ' 5;;** ++ ,  . µ 99S #   #  Aÿåõ~#'+1773#3#3#535#535#735#33535#3355#35#'67&'VŠ&&0´0''((9-f((9-000 U ~A    ' $ &       ?ÿæóÌ #7;?CGKOU[73#5'675#'3#5'675#&''&'3#3#3#535#535#735#33535#3355#35#'67&'M!;RL ;^  M  &&3´/$$**<-i**<-...  Z ÌE  E     -@    & " &     ?ÿèôÐ.4HLPTX\`fl73533#3'33#673265#"''67&'#535#7&'3#3#3#535#535#735#33535#3355#35#'67&'I(((0+*   u0(”‡fv)B)6 Á->$5- 'O?A    ' ' '  !   _ÿñÐ~"(,273533#3#&'#5'67#535#35#&'735'6a/00))   ")/ &  u  6  $( 6 4   @UôÑ$+06<UZ733#3##"''327#7#537'673#36'7##3#&'7#7'673#&''67&767_F  E ? . Y 0      ·      #      9ÿéôÈ#:BFJN73#3#5##5##535#3#73#3#'3#3533#&'#5'67#7#5##535#35#35#QšDM;8JD,,J1166O11   +++++++È %33% & <:#w w-.^ÿðЂ#'+573#67&'#"''3255#53635#35#3'67#,"  %8888) ‚9     39  "  ?ÿïóÉ #'+/37;K73#735#33535#3353#735#33535#33573#735#33535#3353533#3#535#Uˆˆ''8(`''8(‚JJ  *  JJ * EEEP´PEÉD(( &F),)F),1 IÿïòÏ,048>FJNR73533533##5##5##3673#'#3#535#35#5#7&'3#53535#35#35#N'.((.'Y$) U11f + © *)     e+*  +,,,BÿëœÎ !%8<@7&'67&'67&'63#735##3267#"&5535335R  #  % ;EE%%     K q<XB%z  Jk & '==&!.Ð       !!   "!        ,+60   !   .ôz 7&''&'''67&'Í  YF  z"   ! ' ®èÁ73#ÐÐÁ%hÙË 735#53#535#-—²´Ÿ—¤cUÓÑ73'67#&'&''6k_*‘ ‡$L     6ÑC3   '0çÏ 733#3#5335#qbbS±J6‰‰Ï!PP=*cÿùñÍ7&'73#5363#3#  M &{A HkkŽŽÍ  \6 ,õÑ 7#5'6#5'67#53533#&D  &u) ,3@E8+&Ñ rZ%a_/ *%%$ #WæË7365#53#3##5#'67#7#34$®!--F (0‹ACœ 11+/ %fÐ 73533#7#"''32655'675#% %§))!" &&óË"'+733##3#5'67#5367#5367#3353535##±y’¡ #4IO?RMSZp{{Ë## ! " L!  : VVVVVÎ " ;        +P  !e %  &6Ê73'65#ÊH9# !/ïÏ%)/3973#3#3#3#535#535#535#535'235#&'735'6Ú (1ddTTUUeÝdWWSSffR]F@@DA" Ï  ?    ?  c    Vÿðò] 73&'73#3673#53&'`7=‰$   ,œ- N† êÈ$(,04733#"''3255##5##53&'767#35#35#35#35#ˆ^   .  H)))Èu **-†  H: ;öÏ*C7'67&''67&''6#5'6733#33#"&''67B$ !M  J y  &i77,-   Ï     !     E4  ' õÑ,CJ73533#3#3#3#"''3267#'67#535#535#73#&''67&''667#100,-0D@   0  !4+,1‘B  &¾ + #0 %($ "=óÑ,?EK767&''6''6367&''657367&''65''67'6| # M#@ l:     )k    $‰  m  Ñ     " 1           !óÏ!7;?73533533##5#5#3#5'675#5373267#"&5536'3#735#?;@@c?Ž;& **ª    s<<¿$$b     KO-|ÿýÍo7#"''3255#'65535#35#Í   &  %%%%oY  ))+wÿýØu73#&''67&'767#53&'¨(   7#u        LóÇ #'+/73#535#535#335#3357#3#3#55#35#5#`a! 266&)l/;;Ç{4T4{!#7r×j 7&'33267#"&5''67&'¢   Vj 2  +%ÿéÛ\73#735##5##535#5––qq”‘‘‘\/ )99  >óÐ 17=AEIM73#53&''367&''667367&''66''67'6'3#3#3#735#L?  ™  °  ›  PAAAAAAÐ  *     0A     2#  ' :ïÇ4h73#67&'#"''32655'67&''67&'#'67#73#67&'#"''32655'67&''67&''67#d*      " $md*     !  $Ç                   GÿéöÎ1V\b73533#3#67&'#"''3255#'655#535#73533#3##"''3255#'655#535#'67&'U   R Wž± G  o#?) $8#s  oB+ (:> 9îÏ)-15973#35335#535#535#533#5##53567335#3#3#735#1$Y!. · 66((&& •eed)*\  2V -  õœ5;AS73533#3#535#73533327#"&55#''67&'765#'67&'3533#67'75#(''/r1(m(     V  B <#%%/:0#‘   :&)      yÔw%73'67#'6673#35#535#53#“4 %   6(Vw  "  ? Oÿé÷Ñ!:>BZ^b73533#67&'7&''275#735#3353533#7&'7&''675#735#335'3533#67'7''675#735#335b677 7I#"6$$6%*  !  Œ"  Á5]88  3ó£+;K73533#'#5'67#73533#&'#5'67#3#5#535#535#53733#3#3##8$!!    \"(       <<88;;&AA??DDœ    # +R    QÿíóË#'+/37;GUb73#3#5##5##535#3#'3#3#'3#3#735#73#735#73#735#3#3#535#&''67&''6`‡;F55F;U!!D""D))J((..',,&--r@H¢G<  `  Ë /!22!/ " *  *  * %,,      ÿèóÐkosw{–73#3#35337#537#535#533#5#3#&'#5'67#535#3#&'73#&'#'67#5367#5'67#535##53'635#3#3#735#67&''66''6G"Z! 2(      J  - 'K0 -F    (099++..  6584$!  Ð  II G%         %BE;  |     GÿçöÐ6Mp„Š•›§73#3#353#5335#535#'63533#&'#5'675##5'67#53533#&#5#&'7#33'735&'753#5#5&''67&'767'6'''6'6'67'533Œ   8:     ^' XN       G Yv   % %m:IAÐ           " , 70!           "   ( ÿêòÏ7&&'#5'6556Ô ""aÏEO$$UN¾¼EA838X ÿéïÐ@E7'23'73673#5##53'3673#3&''67'&&''67#67Õ Lo\3     ¤& 8…  *:(%1 ' !.D!Ð    ()C      '!  ÿêðÑ 06;AGMS7'2'6'&''&'33#"''32765#'65537#37#&''67&'#4'× Np]Q>  0œ "   § "ot ’  m[  Ñ    %@ ! 0?% 0 $  ÿéóÎ#'=OSW]7'23&'73673#53&'35#3353353533##"''32655#'#67&'7&''535357&'â W|h=,(¾&&8((IE E L  * 9999  Î   22 3;? ; I  oÿéîÆ 7'67&'&''67&'76S &)r""')5 8,#B ="Æ 1&'&$8 )$*$ ÿèõÏ 17'67&'&''67&'763#"''3267##5#\)&k $ %",/ >)!L :$fž L)Ï  !     @< $__ ÿîõÏ 48<7'67&'&''67&'76#3276767#"&55#;5#\* &k $ &"-/ D&+@ 8#22&!Q@F2244Ï         ;D  b"" ÿé÷È+=Obv7#53#3&'7'#"''3255##5##5'673&''67'767&''67'76&''67&'767&''67&'76ucÙbC !  EA ! 8     ^    R   _    ¶   „   £             5        ÿéÀÏ73#5#'67#535#533¬G; 40ŒjVÏæVF4(P= ÿéåÍ733533#3#5#'65.JE£|j#Ì<==(lY6# *=VÿèôÒ6:>BFJ735#'6553&'73#3&'73#3#"''3255##5##535#'35#35#33535#335|15;6.5/  -1cc/N/w-B1 2=Z  3 Y   o 3q/SìÏ736533#&''67##5##5&MPT0+//, 7EƱ¿  2%)fÿéòÑ )73#5##53&'33##"''3255#53567#ª:^6V;;  == @Ñ .. C; 7JîÒ.573#67673#5#&''67##5367'67#53&'&'#6ƒa{ $W 7B6a #& E_<",Ò   )  * ` 5ÿéðž 673#7'6'&'&'''63533#3##5#535#'6ŠX!!„p  /"#$IIRRVV1 žC6        \‘Ï73#&'#5'67#535'6ƒ11  )6,9Ï  !'~XëÏ73#"''32765#'67#'6œ@  ) # ÏH *-"  ÿéôk573533#7#5'75#'673533#3##5#535#'6%9w))//66h)%   %%  MôÏ%+>73533#3#535#73533##"''32655#&'3533#67'675#)**1v1)t? ?  }-,,:=-à   = 8    1ÿéõ«+E73533#&'#5'67#'3533#&'#5'67#3533#3##5#535#'6”!%  Z     'IIUUZZ3 ™  (* ! <   1ÿçñI73533#3##5#535#'6U+CCQQ[[9 I  IîÎ!&;7#53#3#''75#53'35#35#35##7&'3#3#"''3267#735#¢'`'/ /9400<</µM8= @ 5;¡-- *  *B  c4#/ cÿèôÈ "7HLPe73#735#33533567&'7&''673267#"&5536#"''3255##535#35#73267#"&5536g„„(a  r    +     l    È7*      4'Z  $o +    = WîÐ!%)GKOS73&'73#3#3#3#5'65#5#3573&'73#3#3#3#5'65#5#35/  ^*M  ^ )Ð    A   \    @   BõÑ"',0Kchy73#3265#"&55'67#536365#335367#335'3#&'#5'67#535'23&''7&'67#53667#74''6767'¢3,    ! ,D)d(!"  ","+.   $ $¯  Ñ B'  B# $ 3     ;   "    QÿéöÏ!%5FJPVns73#3#5367#53&'7365#5#35'33#7'753773#3267#"&5735#'67&''3&''67&''667À'B+F' 9 . M) V  k 12 )%   # ÏUU =  A<7C5  #L           ¡ÖÏ7&'²Ï¦§ÓÑ7&'¸  Ñ `ÿêõÐ "(7#5##53&'73533##"''3255#&'í_3Q[&&  [#  ¶22  C[  V  TÿéˆÐ 73'65'3#v%"Ð…<& "4b†Tÿþ—Ê7&'37'5#p  + Ê 3j  p ÿèói "57&'7&'36533#&''67#73533#&''67#g  z  ²)*, !''n$5+ ' ( % $#i   %""$(&!NÿéòÍ%28>7'673'675#53#3#"''3267#73#"''3257&'''6¨B  g1!   $_  ,  3— 5! &L'J 7L   ^#%'"1 Lÿê²Î.73533#3#3#"''3255##5##535#535#'6e ##&&$ !&&ÊG  2__K[ _ÿéêÇ )7#5#;5#35#'673'67#&êƒKJ "H ' V@ÇLL****#  < Lÿë¿737#"''3255'7567#P@     +¿% O C7Kÿê¦Ï 73'73#3#3##5##535#K# "[ FFFFC  ° U U8&Tÿí“Ð73#3#5##53635#5#q,-Ð I^È E'Ž++Pÿé¢Ï#'+73533#3#3##5#535#535#35#33535#335Q! ""!  (  ¹_++_9=DðÐ,I73533#3#3#3#"''3265#'67#535#535#73#&''67'767#'6(00)).=9  -  !.$$(ŠD    + Ä              KÿìªÐ$*/7#"''3255##5##53537'6'&'&'''¦ & :  E "  Šˆ tŒŒŽžFF7   L"))!.Eÿé¡Ï 073#53635#35#3#3#"''3265#'665#53&'qC """".,    Ï RR +0" 5 $ !" ÿéË #'/373#735#73#735#3#735#33535#3353##53#=#88188Nmm-G-f„„sMË--(J, & '=::ÿè¬Ð/37;L`733#3'67#'655332767#"&55'7533#3#735##"''3255##53&'73673##5#FCCTq0A4 $'ss ``==X   P  Ð L?1 19V     (  4 #>J   GÿëzË73#3#"''32765#535#O&   ËK"WòÎ /73#735#35#3#3##'3255#'67#'67#'67#gxxRRRR)¡hg   %   "Î:$$     tïÍ#'73&'73#3#3#3#5'65#5#35— !c /Í  l &")_ÿêôÇ#'+/373#3#3#5##535#535#5#735+335#33535#35#_•/**)d)**.S%8))<(d))<((Ç9f g9eee&K7Gÿë¨Í/373533533##5##5#3#"''3267#3##5'635#R2   '$  º m+Y F FN(OÿíôÉ'-HNT73#3#3#535#535#'3#3#3#535#535#&'36732767#"&''67''67&'§BGLBG@ *     €  ÉX  4*4" 9   Dÿé¬Ñ"&*.M73533&'73#3#5##5##535#35#33535#3353#3#"''3267#'67#53&'Q"  ($!"!5!'83 % &½  Z X ' )*  "%ÿîîÏ)-159S73#35337#537#537#533#5##53'67335#3#3#735#3#3#3&'73#535#535#1Z1´ 88,,,, J§MGG(#ÕbDDH™dd`))Y 0S  ' G >ôÌ+<M]k7'#"''3255##5##5'6735#53#3&'''7&'767''7&'76&''7'767''7'76®## EB$ :cÜfCD    \    N    ]    ¹ H  9OOLV            ƒ òÐ06:73#3#"''3255#3#3#535#535##53&'#53&'367#35#½-  7, "Ð  x  b//u† 5 o;ôË)>P`o73#3'7&'#"''3255##5##5'6735#'3#3#"''327#735#&''67'77''67'76''67'7'&''67'7Z“@'     ((  "AH>'/  /%,|    >        4    Ë  N EZZZ` 41&3%       LÿéîÏ(.26T\`73353353#3#3#3#3#5'673'7##35#5##"''3255#&'#5'67##5&'7#35#[+'@9<3333<‰   AD*++++z  # B  c   Ê! /     > +  CR%   FÿçöÏ$(=AEKQW]c73533#3#535#3'67#3#3#535#3#3533##"''3255#'3#735#&''67&'7&''&'SGFF=Œ=G  o+D–@2¤¤J7  7C;;MG Z  > F  Ç      -    Dÿè»Ð+/37HZ733#3'7#'65533267#"&55'7533#3#735##"''3255##53'73673##5#w((3 E"%   GGCC##: 1   Ð  L?1 29V     (  7  (>J    ÿçóÏ$7&&'67&'7&''5'6556È" &%SÏHQ&%YP© $ ³+^= 7\8‡$õÏ#736533#3#&''67#5367#'6Ÿ *,  +.   %øÍ&*73#"''#3267#"&5533265#'6#3žE  & 3; (ÍO  \85 ) IûÏRX7"'655677&'7&''73#67&'##"''32655'67&''67&''67#&'9 0"    Em1       "   &  ´,% "#* S   o    !      zòÏ#7335#53533#353#3267#"&55#€//00, )“-::/C  !TöÏ,07=73533#3#535#7'6'6#3#27'63&'#735#2767#7'6533(h-5 (&'#,*œh 2=DD    5 -Ä      &  !  ÿé’}733533##5##5#53#3#3'00C0000|jj"ÿéßÏ7#5##5##535335#33535#35#ßB?SS??SB•??SBB­•@@”""9&&&^%%%ÿíôÏ!%)-7#327267##"&55##53535##5#35335ÒS  2 =Q??==?®ƒ    ' !!8%%%%%%%% ÿèæÓ!%)-73#"''32767#'6##55#'#33535#D› ”  ${po$%%%%$$Ó Ÿ1.k  #r‹/ÿêðÏ +73533#735#33535#3353#3#"''3267#7#&OS¶;;O?Ž;;O?¹áž •-ÁW51.3%âÍ 7'6'&''&'ÐM  5 Í )   cåÏ%77'7&''5633#"''3267#'67#i($   ,5g  #% Ï /   L= $1$ :÷Ñ&7&'3#&'#'67#5367'6767'¨ €8$ +"@$& /F %/4Å  #ÿèôn #*7&'''673&''67&''6367#¯$"`% !1b,. 44+?6% 0 Zn          ÿêõÊ'+/37;?73#3533#3#3##5#535#535#535335#735#33535#33535#335#¼T)//Yffhh]!,,)TAAU@•AAU@}))=)ÊU   21Y IõÑ#*067&'#3##"''3255#535#5'63&'#'67&'ƒ04 0XX   \\4 NP (,f( &Ñ      7    ÿèôÆ#'+17735#53#3#3#535#735#33535#335#35#'67&'4'·(55<â>4!==Q>==Q>(AAA &. )f3# "1YVV[4#+    ÿíîs7#5##53#53535#35#35#í±¸"Û"oooooos,,WW * ) ÿíñÐ#'+/37;?G735333##3#3#535#535#535#535#33535#5#;5##;5#'3353##SMMSSdÝeYYSSggSg:::>,,++,,++rªÑÄ        S??  ;;M ÿíðË #'+/373#3#735#33535#3353#3#735#33535#3353#ÙÙµµ==P?==P?·à൵==P?==P?µÞÞË B( $ #C) # $ ÿéóÐ)Fcimquy73&533&'73#67327#"&''67&'#7&''67'67676767&'7&''67'67676767&'##535#33535#335 s  )"Z     xV %   %  QT)A)e.=?,!!  *J  !  !LP ]!. ÿèóÏ37;EOYaeimq75#535#535#535#53533#33##3#33#"&''673535'&'67&'67&'6#5##535#33535#35#‹ &&,,%%--22* *** ! 6²   *   )   ='=': :#$ t#g   Kw w,HÿêîÌ %+3;?CG73#735#33535#335&'7'6'67&'#5##53#53535#35#35#+««88L8„88L8¡2" !0Ä '.)H #66E7% $5b¶¸"× mmmmmmÌA( #       ((EE  ÿé_È'75#53#3#"''3267#73533#67'75#F5H/8  ;  =`&K@Y ÿéôÀ#73'67#3#33#"&''675#Ó  HUU:2;/ %`À DK#=P0 ÿêõÏ59=73533#33##3#33#"&''675#535#535#535#3535fhhRRUU-#CC  #KKZZHHfz>>>¾$%& :#$ÿçòÏ#'I73533#3#5##535#3#735#33535#3353'67#3#33#"''675#XYYa®`X11D2v11D2¤Î F66"/)u  (^Ä  %% &F+ & %    *Kÿêõ¢73&''67#'6ƒA " !"W6  #¢*"% U  (Yÿéê• 73#5#533533Ö}),lƒr_‰‰PÿññŒ7353#3#3#5#P~juun‚GE20CKÿéï7'6675#53#3#"''32765‹+#™cR  D?-(#Q .Uÿèê‘"7#'67#533#"''32767#'67#'6„!4#Sy$)_   4.$ IU -;&"0&]ÿîôŽ7#3276767#"&55#;5#âq $A28$$&&ŽV/  Š33^ÿêôœ%767#3#3267#"&'#67'7&'^P(%41  2 BŽ  !0.)A `(Iÿèô˜(733#"&55#'6653&''67&'767#Ç! 1  |$# +#, % b˜%  L&    Dÿçî #7#'66553533#"''32765#'67#îz "5   "-&!=1( %G9P/A7Oÿíñ£ )7&''6#3267#"&553#"''326!( &$' 46;/  4$ a   £ "8?  T*Hÿêõ“*7327#"&547#'655''67&'76×OJ    “')),(=>.+ !,P%  Iÿòó¡7&'35#535#53#3#3#—  =K66<”D;;Kª¡  ‘(''(Nÿìö¡(,73#"''3265#3#3267#"&55'6#3re Y@5 #/  914!!¡V8 >#  W %<ÿéò¢*.7533'67#3&''67'&'767#'665535#—= )3!# (" %  X ** !*     1% #D3!Rÿéñ™73533533##5##5#35#35#RGG*GGGG|€ €%%a);ÿìò'737533#7'6773673267#"&5P,7 Y   wp”6I —:  F  Rÿðñ›73533533533##5#3#5#5#REwŠqp##++++FF[m444Oÿêó¥ 7&''6'6'67'66!( (# $ 6' 1 52 *? D= /Z 5;¥%4 Jÿëóž#7'2333"&&#"'663267#53&'Û 6O@ /:2!.-2e;ž$   Eÿðó“ 73#53535#35#35#Ú®SSSSSS“‘‘1OOHÿèôŸ73#3#&''67#5365#'6y`7B>3 /? = =A Ÿ +/)* Nÿèêž 73#"''32765#'6##535#qn   b W=**ž rP ? L.Xÿèí™73#5##5353335# Gg4MggibaO_.9ÿéìž$(73533#"''32767#'6655#7#5##535#P'  2 œ}!!l GZ 4/$¡ ¢ƒrLÿêì£ '73&'73#67&'7&''67'67676NEBžp/,&, >= -" ‹  @   $ Nÿêîž73#3#3##5#'6wlUIIPP ž-‡ Kÿðñ73#3#3#535335#Y=99E¦!?/5[[wPÿïó–7'67#53&3#3#535#¥ * P]v  #g…;FœC7W '  &&Xÿèé”7#"''3255##53#3#735#é  j!MMGG##”—  š«#?Lÿéð£"&*73#"''3255##5'67#53673#35#35#ƒa  P "*[cPPPPff *]     +1Oÿèñ¤(7'673'67#&'673'67#&p ' @$\ Q#;! 9,+)l b(, p  3 % C! <0 Iÿéó£<7367#535#53533#3673#33##"''3255#5357#'67#]. UB''!! (7 'CC  QQ$%S    Yÿéë˜"&*.2733#"''3255##5##53&'767#35#35#35#35#^ƒ:  .,4  c,,>..>,,>..˜  j  $$*‚  @6=ÿìó¡0673533#6767673265#"&''75#'67#7&'O!gF  $% !+' ": 1 j  ‚       d`%!S0 Eÿéó– 73#3#5##535'67#35#7&'OšB>g=( 4AggO!–FQ Q0~!S^ÿèæ¡"(733#"''3255##5335#35#7'6'&'–<  d8&ddddc[  ¡1p  -‡&7b    Cÿèò§5;7&''6767&'&'''6&''673&''6767¿8?#& 8% )L#+" ,* œ   & M   - Oÿêê§$(,047'3533#3#"''3255##5##535#35#33535#335À fCEE>  +(ÿìó¢%+173533#3#535#&'33267#"&57&'''6PBGG@;BI  %l  u  ‹3 0 6   Dÿðó¡)73533#3#535#7&''67&''6T=>>N¯M=  a   4mmu!*Iÿéó™!7#53#33#537#535##53#=#})”WIª*I8Yˆ''[ ED >ÿìõž !'73#'&'&'''6'&''6''6­K …  9 5 J ISžhd  %    7'  Gÿéï 573533533##5##5##5##53673#"''3267#'67#O%)**)% v(=  -9 / "'&,  7 $1 "Gÿéô£!%)-273533#3&''67&'#535#735#33535#33567Z;::8# ) "- ' =;))<(d))<(U “P    /.< Gÿéõ¢"',7333#&''67#535367#'65##67#€;(?0 72 +3: 0[!¢ 5 &)  5  T##Qÿééœ+/7&'3#"''3255#33#&'#5'67#53'3#r  +]   I %%    #Dœ ‘  z   9>Hÿêë•/387#"''3255#'65535#35#7#"''3255#'65535#35#™   €    •” (& (1H-H=” (' )/H-HIÿéõ,73533#&'#5'67#73533#&'#5'67#S   L!    ~ XS +1(c_!-Kÿéìž'+73#3#&''67#535#'67#5##535#f. %"# ž &   *&  Ÿ ygLÿèõ¡673#&'#5'67#535'63567#533##"''3255#Ó 7A.% ," ,=* 8NJ FaGG  J¡   z   Lÿíö£&26:>B73673#&'#3267#"&55#5'67#'&&'#35335#33535#335U7H.# 3  % 1 )q  11 Q1  B  >     -Hÿêë /<@73533#3#3#"''3255##5##535#535#'673#"''325'3#`""((&  &,,€   œ5  !II:J  ›  …sOÿèò— !73#735#33535#335#53##5#'6^„„$$7'^$$7'Y'£%/'"—P/.@<<+Iÿêò£#',73#3##5#5367#536367#33537#33535F+::[ %-'*<*c"54< $£ U"" U)2# Xÿêð "*.273#327#"'#7'56&5#5##535#35#Ø?7 :1! Kw[[[[[    (  J  =RR* Mÿèî #'+73&'73#3#3#3##5'65#5#35#s%45....6t@+++++    o  )+Mÿêò™&,07#"''3254'7##533##"''3255###535#‹ Bc   F:%™0  1ž¯†  ƒJ W8'Hÿèô 5<73#&'#5'67#535'23673#&''67&'67#67#Ó E3# )% "4E6=K4[$7 , )X-     \       Bÿè÷¡ +177&''6'3#3#"''3267#'655#53&'&'&'Á  >  4*   $\ ¡ ) T1;$ &7" O  "Hÿêô–?73#67&'#"''32654''67&'''67'767&''67#Yˆ:      - 0"    )5– .3       Dÿèõ¡ 57#5'6733#5367#3#3#&''67#5365#'6o -MwH8D'5- #! ' $ /4¡‡i %$ '   @ÿçñ£(73#3655335#535#53#&''67#56} #.281 930-š  NN e!(&^Qÿêì™"&73#"''3255##535335#35#3#735#Ù  u4L""RR00™BU  >[mB" 00/1MÿéðŸ'+733533##5##5#533533#3#5##535#35#q6""6##$HGG;a7H#aaŸ2L ME Dÿíô¢$*:73533#3673#&''677#53655#&'3533#3#535#S;FF  !?0 =1  1=;8;;L«L8•     TIÿèõ¡*0>73#353#'67#5335#53673'67#'6'&'&''66ˆ"  !/ 01# I g  ¡ B3C  C3B (  5+# -Cÿìö 17=CS73533#3#3##5#535#535#73#"''3267#'667#&'&'''6733267#"&5Q RI       N x $"– C,$ N     %  Iÿéóš %+73#735#3353353#''67&'&'''6W)? \   3" šB 7ic       Dÿéõ¢#'-39?73'#"''3255##5'63&'35#35#'&'7'6&'''6”9  S LA SSSS’  =¢ -G  !X. *x    ?ÿéñ® #+/7&''63#'66553&'3535#5##535#“$, ,%&5 6s ;aaaKKK®   D) 9  %: ;#=ÿèó£%8NT73#"''3255#'67#5353635#4'733#"&55#'6553&''67&'767#4'm   %  %%u  H     5,£  31*F P6  )(    K&        :ÿìô¦#'+0A73#3267#"&55'67#536365#33537#335''6767&'~W= #7 =4#,-@+k'+>0¦ X4  (&%X - 6 4  Rÿéì¤%-159=77&'7''563#"''3267#'67##5##535#33535#35#„   )T  ! Ki++=,i++=,,¤#   <2 , "D^ ^"2Jÿéô¡473#35335#535#53#3&''67&'767#535#56ƒ  ),!!!3>6# .$&  j;;žKKU      O9ÿéó¦1A733#3'67#673267#"&55'75#'66553353#3#3#5#MMZ C7  ' $ 2r_oo`s¦     :/%  Bj  Dÿèò§"&,273673#33#535367#5#3535#5#'67&'QA@D;®0>y[[[[[[[  U™ ``%        Cÿíõ±,04873#&'#6732767#"&55'67#53635#35#35#ŠD (0" "( . 9( &%YYYYYY± ^        ^ % & % ?ÿéô¥#'+A7&''3#3#3#3##5'65#5#35#'3&''67&'767#ÌA"M)Z4     !¥   n #&*i3$#  Aÿéó 06<73#3#535#5#35#3353353#3##"''3255#&'''6Ož3*)1Z&&n||¨M  I| S11!,     8ÿèö£ *J73#3#"''32767#'667#53&'73#'63#3#&''67#5365#'6s%$   " E=D 0%     #  £  V 1A$1>   !$%    Eÿéõ/37;?EK73#676767&'7&'#"''3255'67'67#735#33535#335&'''6XŒK  !%)"     +));+f));+ O  K       ,+_   @ÿëö¤"/5FLR73533#&''655#&''6'&''6&'33267#"&57&'''6N@II< 96 J@{   ]  A$ h  u˜ &#&     , ) &  Dÿêõ£ *D7&''6'&''6''6#5'6733#33#"&''673Ð  3   %   X%%  !  £   &   *"bK" 8Gÿæõœ #06<B73#5'675#73#5'675#&'7&'&''6'67'67'6QE4JG68 V &0 .-# 5 %"7(7 1D.X UœA D    #        % Cÿèò§'+/73#3#53'#53&'367#3#3##5#535#735#35#›F$3ª,B"/+‰=PPLL9cccc§ '(B  ) $ Cÿéô¦&+/@FK73'67#&'&''673&'67#3#3##"''3255#'67'f'/ (   T5  =&?CC(‘@   ?  c 0¦ +    ?% &( %   Lÿèç¤"DQ736533#"''32765#'67#73#735##"''3255#&''67##537&''6O$  [==+  .   )A   "–-" 8:P ;  Td *    ;ÿéñœ *06<B7'6553'#33#3#"''3255##5##535#&'7&'&''&'e”ookˆ>9 '"48"  ?   *  n#7) *2J. J 6KKP`        Bÿèò®#'+/IO735333##3#535#5#5;5#33535#33535#3353673#&''67&'67#67#T;@@E—?;((;-h((;-l,,?2‘2f!4 # 3$ $] ;£  (( 9 %    Fÿèô¢,0473533533##5##5#3#3#&''67#5367#735#35#M*+**+* ˆ>QE/95 )6B7dddd“ C  * " Lÿéö§06FLR7367#'6733#535#535#'#"''3254'7##5&''33267#"&57&'''6Ž+"  * ZHCCH  r     K  R  t    G 6+ /£²p    "Gÿçôš%+EL7#5##5##5##53'65'3'65&''&'3673#&''67'67#367è" !iPe F 35c,  (;))*? šA22AA22A         Fÿæð£#'+/5;73533533#3#3#535#535#35#35#33535#335'67&'U $((0L=Š:D( 4$$&&9*c&&9*R ! T ” II I (    >ÿêö§28<@FL73673#67&'##"''3255#5'67&'767#3&'#35#35#'67&'Q;D2   2   2  /.= SSSS  t–   5  3    , ' !    Jÿéô¡3U73&''67&''667#'##'32654'7##53#3#3#3#535#535#'6735#¤3   '  RA##-r2 ¡     & )ž¯C   Eÿéï©'-3;S7367&''66'367&''667'6''6#5##53533#"''3267#'67#Á     S     : P  ‡.D  1> 2,©    7##&0 /  Nÿéêž  $8<A7##535#35#7#"''3255#535#5##"''3255#'65535#35#’2 Š   5####   )((((žCrµ % (Ÿ YC % F  "! $ Dÿèõ¨'Mc76767&'33#"&55#'6553&'33#3#"''327#735#5'67'767#67#53&''67'®        2OF,0  2 )   3M 8L  ¨    ! 3+!0  s    Aÿèò  #'+/73#735#73#735#3#3##5#535#735#33535#335NEE##@DD##\?QQMM;))<-i))<- ..(G  + ) Oÿéîœ %73#735#3#735#'3#735#3353#5#533_}}YY6GG!!kGG!!2.‚/œ/)55*$7 -Dÿëô¦/3E7&'35#535#53#3#3#'35#53#3#7'675#73#67''67&'š(6Bf8 3==     ¦ C++Cb--,  37  Nÿèëž  $(,07##535#35#7#"''3255#535#5##55#35’2 ‹  6$$$$ G5##žCs¶ % (  ZC % OO  Hÿéô© $*26:7&'#5'63'3#735#&'735'6#5##535#35#ž  , G 2 .D‹‹++  6*  #ZZZZZ©     #6    (GG # 9ÿéö°"37;?EK[a7&'#5'6&'3#"''325'#"''3255##55#3573#&'''6733267#"&57&'  &) M 5EU  3   +=++  7 ! ! * q  °     ? :; L 6      =ÿèô£*1:>O7'673353353#73##&''67&''667#'#5'673#67'5#'655^    CU"    r #::8  £'55'5A* #:"qT  -  0  >ÿëõ§"(.4D73'73#3#53&'#3373#735#35#&'''67&''33265#"&5S?B*£(.)]‚‚\\\\6A Œ ` * ™ *B( #         =ÿèô¦ !%;A73#53&'3#5##5'6553'#335#73#3##5#535#536'&'ËNR4 Ce$#. ¦ D Y6<=?.b)I// Iÿèñ¡#+/37;73#3#5##5##535#3#'3#3#'3##5##535#33535#35#Q˜BJ8:LD_''M''M&&L&&‚n//A-n//A--¡ )11+ "  SV ,Hÿèí©=AEIM733533##5##5#53#"''3255#7&'7&''675##535#53#'35#33535#335r-))-**{   7 '/4F8†;9&&9)b&&9)©  n4   9I DD 4 % Rÿé÷£,5;AGM73#35#535#53#56367'733265#"&'367''&'7&'&''&'Š --i//0C!!  s 9  E 0 £   JEQNY*$ *6MW          <ÿèô©!'048@D733#&'#'67#'655'667#3'73#3#3##5##535#|T$  %   TI5:„ ppppsOOO©   2(9' /  ,, ;ÿèó›!%8NTZ73533533#3#'6553'#335#35#733#"&55#'6553&''67&'767#'67&'b   G T002  f   D       0; +  i )2K2#R Y$    J&    -  ;ÿèô¢59=FL7#5##5367#'673'3533#3#&'#5'67#535#35#3353'>&'ë( („     98   oSDCR    !9  269; "5  >ÿéø¯#)AEIM73#&'#5'67#53635#35#35#3&'#"''325##5##5##535#335335€R8)' [ 05'jjjjjj3\   ,¯M M -.  B :ÿèö¦Ed73673267#"&5733'67#3#33#"&''6735#53'767#3#3#&''67#535#'6L  MQ       &. ;E-    ¦     *  &  9P  .    @ÿéó®6BFJV73&'73673#3#3#&'#67'5'67#5367#5365#3&'73&'#35#3567&'M(!(H@BT* `  '1=25@0 MMM +ž   0  J A     Aÿêô¡!%CIO733#5'6367#35#33535#33573#3#3##5#535#535#536'&''6aI  )  P!$$""* !  #,.¡ TJ ,3]**  ‚ Dÿèð® 159J73#&'#'6#'673#&#67'7&''535357##53#"''325b, j   /64  """T"F ®       P#   €z‹^ ?ÿéõ¦ ).8<P73353353#''6'673#&''665&767#'#5'673##53#3#3#535#53p@  m &    n $66AD›** -5 7 -6  & nS  9ÿêó $@P7#'6553#&'#5'67#535'6#5'67#535'673#&3#53533533ïŒO    N     *<—0784 *6G  4    +&&3 ?ÿéò§#BFJN73#3#5##5##535#3#73#3#73#3&'73#3#3#3##5'65#5#35#T‘?L::L@ --M,,M--N++K)AA9999C† A11111§ /!(("0     <    4ÿéö«=AEKQ7'6733#536735#35#35#'3533#3#&'#5'67#535#35#335'67&'µ  &M$,,,,,,k   1  -    ii 2 *,h9 <; 9:Z  :ÿéô§48<@DJP73533#3#3#535#535#'3#&'#5'675#535'63#735#35#35#&'''6‡)++%%2q-!!)    +^^::::::-  œ     \R $DR:     œÿèô—7#5##537#53#3'66&'ê%Q!  r]NL[2* &  5ÿéô¬!%IOU[a733#5'667#35#33535#33573673#3#3##5#535#535#53&'&'''67&''4'_$L ,)   "$$"""  @  = ¬ [Q  &:_ 44 ‚ Dÿêð¤ %+17PV73#735#35#35#73#735#35#35#'67'67&''&'3673#&''67&'7#67#RCC%%%%%%ACC%%%%%%M  Y  ) G 73d# 0 ": )(] 7¤O7  8O7             Hÿé÷«.4:FLRXh7'2#"''32654'7##53#3#535#536'&''&'3#535#535#&'7&'''6733267#"&5ã '9.1  )2t0&@ _bPPPM( ;  V    «1 4©¸   77 /        AÿèðŸ =AEI73#735#35#35#73#735#35#35#3'73#3#3#3##5'65#5#5#SCC######?CC######N'985566B„ A/////ŸN7  8N7    6    Cÿèò© 37;?IMQcgk7&'67&'673&'73#3#3#3##5'6#35#35#'&'63#735##3267#"&5535335R %  H= %  ?HH((?B  $ ©       y +.„   *' #/   @ =ÿèôª#'+[_cgm73&'73#3#3#3##5'65#5#35#'3533533##3#3#3#'67#535#535#535#5#5#35#335&'´>  p ""), "  &6 !  ª   …0+.x*    * 1 P  œÿêó %73#3#5367#35#35#35#'67&'ŸS  G!&&&&&&  (  pp 310#    Dÿéö­ %@DHLPV\bhnt—£73#53&'''67'6727767&'7''67'6727767''3#3#3#735#'&''&'7&''&'''67'6#"''3255#&''67##537&''6:    ~    ]222200   ’  ‚  v  , 8 *E  ­ '                      ?  0    DP     ò´%76767&'3'67#&'” PcY) ?D´   1n (+ X  ÿçéŠ)733#"&55#'66567#53&''67&'¯-0 4/(‹¥  !(1E5%!Š% d    ÿîðŒ 73#3#735#3673#53&'WVV'››uu 3  Aà> ŒA4  "ÿèÞÐ 73#5##53635#35#mf”? 4””””Ð ÆÅ]:‹> ÿçõt!73673#&'#'67#3533##5#R s?* 4 )7 +>(====Y   !"($$ÿéód #7'6'&'3533##5#7&'''6Õ$"”gjjgŽ%$#$0 &(d   :JJ8   ÿéëf #'+73#5##53635#35#73#5##53635#35#86>>>>>†6>>>>>f i i 0:K i i 0:JÿêªÐ)-173#3#3#"''3267#'67#53&'7#53635#35#w" ,&     # "''''ÐP  6 (4 P )0 ÿçòÏ).7533'67#3&''67&'#535#'665567{U =G+ 5" 3 0# 9F B! ¯  00#  &0I;. + SV! ÿí‚Ï-59=A733#3'7#73265#"&55'75#'65537'7535#35#75#;//6 &     &8U $$$$$$Ï     [13eUGU & ) OîÅ73#3##"''3255#535##¼Raa  ggVÅ   QîÅ7#53#&'#5'6xU¼O =+ ,/(0G² ;C ?êÄ!73#3#"''3267##5#735#3355# ¾Vb MV IThCW9Ä6%++48 MóÒ7&'73#536'67&'E   6Ë~S4 +o&& &&Ò  1   BîÐ 7#535#53533#3#67'7&''6XGdRRSSe~>3  FU~   =óÓ47&'67327#"&''67&''7&''7&'37š  _d   %:0 F ^XA?c`Ó  0            UîÏ 73#'6'3#'3#7&'šJQ=0–Ï # -5zpc& JõÏ7&'#5'63&'3#735#}54 &d PN+››wwÏ!    6;óÒ-37&'3533#67&'#"''3255'675#&'­ Œabb  /.   )18*a Ò       &'    BáÌ!%)7#"''3255#'65535#35#'##535#35#á  CAAAA&<))))Ìn   &:&71i u+F*òÏ#)/5;735333##3#535#535#535#33535'67&''&''&'*KLLb×aMMggK_888•·  (  # à      >        %ôÓ &*9?C7&''637#53&'733#3#535#35#5#"&55#'67#75#335#€09 8;%= C;Ž m796TB¹ºÎ;"0 ,j&%$%Ð    ]C $ # =a     ÿçñÏ#)/733#33#53537#535#35#35#35#'67&'{X[F#â&?^aBsssssss* %a%% %&ÏxxA,+#  2ÿéÏa 7#5##535#35#35#Ïwwwwwwwax x ) ,ÿéñw $7'67#7&''6'&''6u b- "F   o Z sg , ! ÿéîÑ048<73673#3#3#3#5##5'67#537#537#53&'35#35#35#[ ,  8f\c~ˆzm);H=DU;mmmmmmÑ  pX  Ž * ) ^ôÐ!&,2733#3##5#5373&''67&'#367'67&':**72/)L]    § N  Ð 22    %    VˆÊ"73#3#327#"&'#67'735#'#g$%! $ CC Ê.  K-ÿéðÐ/37;A73#3#3#3#3##5#535#53&'#535#535#53635#33533567#boVMMg5.T^^__S-4fLLT8*''9%'2GÐ8      8 0u  ÿéóÏ%*.26<B733#3#5##533#&'#5##5'67#3&'35#35#35#7&'''6j\\o³V: C"Dx  C=_)xxxxxx{ u !Ï )(& ]]"// & & s   ÿéÑ#'+/73533#3#535##5##53##5##55#3535#///(d)/nJ;;M;L;;;;;Ä   ($% k l  ( Oÿë¢Ë73533#3#&''67#53655#` ¡**' ( (7$QÿéïÊ ?CG7'6553'#33#3#"''3255#7&'7&''75##535#535#'235#335z]] -**3  "  1**1+˜0D4 4?]2"+> ,AO+ 7Yÿè¯Ç $(7367#533#67#5'275#35#35#75#[',A   Ÿ s!|JNƒË` 73#735#35#ƒHH""""`Z68"mßÌ 73#735#35#35#73#735#35#35#"UU//////TVV//////Ì_C & & D_C & & VÿéóÏ>BFLRX7''67'6767767&'73#3#"''3255##5##535#53635#35#'67&''4'“    >!   ((((S 5e-*8n V=  )VVEUV .4Y# ZÿçóÑ#)/M73#3#5##53&'#53635#35#35#&'''63#3#"''3267#'667#53'ŽF3>k>+ MMMMMM; "  (BZM ?# )@ÑQ  Q ! 1   "   ÿéñf#9=AEJ73733#33#53537#35#35#35#35#'7537#53733#36'35#35#35#5#6‚.'*%o+22222222$57+.'*%H22222222X II K  E2 kÿïÛ3:>BG7367#533#673#&''67''67#5'75#67#35#35#675#l2     J 7 ^  . !   K(- ÿéíÆ*733'67##"''32655'67#53&'767#*¶*#G ? %F G"\j% “Æ &h W>&)7  ÿééÌ):NR733'67##"''3255#'67#53&'767##"''3255##5'673&'7&'#735#*©J  8   '; 3!NZ  й   0 B  ^::Ì     _Z  C`r9  ,ÿéóÏ73#3#&''67#5367#'6L ŠQ`[K G W T X]0$Ï;>2#!1  %ÿéó† 73#3#&''667#5365#'6GˆLcWB ET %* \b4 †   ,)  KéÏ73533#3#3##5#535#535#YZZOO__]]OOY¾S œÏ73767'5'7k  Ï:Y  cfÿéöÏ)7367&'#"''325'&'3'67#¥   & 3  !Ï?$ --$0N  » 5L&:eÿïõÐ 7&''63#3#535#¦$ "# +e(;‰8'Ð **- (7NN ÿéõÐ048<BF73533533#3#&'#3#5##5'67#5'67#535#35#35#35#&'#35#%c$$+3 c n^ &  %81%8ccccccn I ^^à F ;& F * ) 4 BLQñÏ"&+7533'7#3&''67&'#'65535#67Ÿ=(*  "$  %%  ¿      #"%$  \éÐ &73#53635#35#'3#3#7'75#535#¦3i CCCCyY#!!%*3(!!#Ð\\-8= NóÏ/37;?73##5#'66556#3533#3#3##5#535#535#35#33535#335ç *L 3´044,,))33,,0-G-Ï@@- $ 9   9   Nÿé¦Ï,73533#3#3#3#"''3265#'675#535#535#]$&   µ@ '464ÿéõ¨+?C73533#&'#5'67#'3533#&'#5'67#3#3#5##5'67#35#”!%  Z     ·m qe #01ee™ '& !  8E3< P‡Ð 73533#3#535#'6553'35335211)e*2  dRÇ   Q /!VÿèôÉ!%6:>7#3#67&'#7'5#'6553##"''3255##535#35#íqv6  2  ZZ_  AAAAAÉ#  ) ("F6 7?aNS  "h ' _ÿéòÍ!77&'7#3#3#535#'&''63533#&'#5'67#v ‚H??HZ--: #  ?=3"  4Í  1qB   0 86ˆ Êb 73#735#35#ˆBBbW35-ÿéõ³8LP73#&'#5'67#535'673#&'#5'67#535'63#3#5##5'67#35#†   %({#     (‹½t vi  !/1ii³#"    !"   e?0 6Wÿè¨Ï -73#53&'3#735#37#"''3255'7567#‚G@@C   +Ï  330  Oÿê©Ð 073#53635#35#3&'73#3#"''3267#'67#xD####+$    ÐPP *06 9 !)6 ÿíÔL73#3#5##5'67#35#+©i }v *$ vvL>-6ÿëÍH73#3#5##5'67#35#9‹Y wj %jjH<. 6]¤ñÒ73533533##5##5#]!,##,!Â[ÿéôÐEJ[ag73533533##5##5#&'#5'67&'767&'767#'6733&'73'673##"''3255#'67&'b $%%$ x   B     # 3+ iu0   2 \à  H        ! H- *  gÿçòË +159=AGM7#3#3#535#35#5#73&''67&'#37673#735#35#35#'67&'«H!!7B    quuOOOOOO  DË ) a & & M     S[@ ! !     ÿé÷Ñ&=Ryƒ7''67'763533#&'#5'67#73533#&'#5'67#''67&'763673#&'#3#5##5'67#5'67#3&'#35#”      z             wRt7$ S um %  );B` 6 mmÑ       '&   )-    2  <-   JXÿèóÎ%+1bhn7367&''66'367&''667'6''6#5#676767'7'#"''3255'67'67##5&'''6È    J   4H‹D  $    " %l = Î      :(    %   )a    SÿéòÏ159=AGMShn73533533##5##5#3533&'73#3#5##5##535#5#'#35#'35'&'&''673533##"''3255#&']"'&&'"#.  1*).X*)L    L  L À    D E "  E     *' %    SÿéôÈ#:BFJN73#3#5##5##535#3#'3#3#73#3533#&'#5'67#7#5##535#35#35#e†9B0/A;T""A""%%D''^    """""""È -66+ &   :5(w w-. ÿéóÁ 73#3##"''3255#&'''6%¶¶Úb   d¢$#_#Á-l  i$(+$ 5%8ÿë¾Ð73&'73&'#5'67#G+ ' 9$N²  gf B ÿèöÐ/4EKQ733&'33&'#5'67#&'&''67673##"''3255#&'''6=4h%E  {L)   y ¯Æ[   X" U + %Ð. #:  <    p/ , TñÓ 73#53&'3#735#3#735#‡bãhK²²ŒŒ__;;Ó "V4$  SìÑ#):E73#3#3#67'35375#535#'6''6##53#"''325'#5'6^:!%%""03((   ²=   ¢ Ñ  #"+   ^pQ 8 ;)Jÿé Í!'<7#53533#&'#5'67&'7'6&'3533#&''65#g!     ?  7""   "ˆ55  # P `     !ÿéäÒ&7#"''3255#67&'7&''67##537ä  L (-<U¬¥  ;"  $+  #<°Ã& ÿéèÎ59=73#3#"''3255#67&'7&''275##535#535'235#335Õ '/JJ_  K  0?FZJJ(.^866J6Î>N  7  Ug>LÿçíÒ $K73&'73#&''67'763353##"''32655#67&'7''67##537b `Û—  # & x’·Å  \   .37U»       11CJ 3   Qc ÿéòÐ *T[7'#5'63&'3&'&'''7&'763353#3#"''3255#&''67##5367#67'7#~/4  O(:%" %?4   &n‡Qd   . +/ +JE9  Ð    !    ((:: $   >P5  ÿéõÐ7'673#&'#5'67#53uUhW *.fYA>#9 7"Vf± .B!'?y}C'$@ÿèçQ7#533#"''3267#7#'6Y0 <  @ .G ?@2 H ÿèñË.2673#33##&'#5'67#535#535#535#535'235#35#Ô UddO:!3 8&!: 3!>RddRRaaKX <<<<Ë!! #AA!c1PñÑ 73&'73#3#735#3#735#fe⬬‡‡bb>>à X: $ kÿéïÐ #'+7'2'6'&''&'#5##535#35#'3#å 3J=5 *    €444448Ð /0OA~dÿéôÏ%=EIM73673'73#3267#"&55#'67#36732767#"'&5#5##535#35#h- +    )  %2+ 08 " 2- xPPPPP·       -   T T+ ÿèñF7#3#&'#5'67#535'6² %dH", 9&$6 $%Db QF   )+  ÿèˆÏ773533#3#535##5##53#3#&'#5'67#535'6 255,j*2vPFFK&11  .2!0À* "  $( Rÿì÷Ï%+;A73533#3#535#35#33535#335&'#'6733267#"&57&'e9@@7}49""4%Y""4%0  6   #b ¾VV54&   0 6 Xÿè÷Ñ#)/573#67'6775#53635#35#&'''6'67'6›@6 "@  2%UUUUWF \ +N R8 1` bÑ L" ! ,L (+"   &  ( 0ÿéõ¦,H73533#&'#5'67#73533#&'#5'67#3#&'#5'67#535'2=   W!%    C 'SE031 18ÿèö| 73653333267##"&547#'67##AK   6 LF =W2"  )?0ÿçát736533#"''32765#'667#Mc P. +"JZ  S 4&#ÿêË|73'67#&''6g \#~ tU    1|M?    ! ÿèáu73#3#"''3267#735##¯—© ¬ ”›u8':ÿñï™ 7'67&'3#3#535#c / 2V*$")l»TcØaS™ 788ÿêï|!73533533#3##5#'67#535#5#/?00<<A , ! 7:/‚?d 66  ÿïô…&733#353#5#3267#"&55#5335#53scc99 )  4%J6]]…, ;  2 ,ÿéõx73673#&''67#7&'_h`N M 3& GY S19*37 ÿêér'767'5'75373#"''32765#'667#q," "%5m  (X0  :%"`  >1. **ÿéá|73#3#3##5'6U€xnnoo'|  h-ÿéÓk 7#5##535#35#35#Ó~~~~~~~k‚ ‚$43ÿèðw!7'6333"&&#"'663367#53'Õ YgjK:1B,A@ 1-ŠSw   ÿíõ‚)-73#"''3265#3#32767#"&55'635C „ i_4JS@ !L‚F06  E!ÿêéz9736533#"''32767#'67#&''67'67676767&'8   &! %- )c [5*,>& # /"ÿèèw'73#3#"''3255#&''67##5365#Ô^Z  I% " 1BZcw R  <Yj ÿèôt 7&'67&'7''5'6556Ä  - 4   )#XtC#PU    `*%!/ ÿéä„7335#53353353#5#353#5# BO;;;F°4/;7$11%?;+G ÿçæƒ'73'67'56767657#"''3255##5bL 3  +' $%„  2]$: R 6 2P  8s†ÿîï†73533#3#535#3533#3#535#&RRRbÙdRTXXdÝfTt=ÿèï„ 7'673#3#353#5#5335#535J  †Occ:;ff` ); 1)-<Û…7&''6767&'°BJ(*-s #ÿéí"73533#3#535#'6#5##535#E*OO_Ûh7 ž………‡  BF G* ÿéó~ $)-733##3#5##5'67#5367#537#3353535#/£!!{’y !"?D(;X\anyy~; -   O ÿèò–9737#535#53533#3673#33##"''3255#5357#'67#*=lX1100/K8SS ^^%=(- M       #ÿêá#73533#3##5#535#735#33535#35#2CIIXXRRC00D6ˆ>>RDD~599[ÿéæ%)7#53#3#"''3255#&''67##53'35#w>=]  N  =W(ggL55=  '  CUÿéÝ)73'67#'63#35#535#53#5##56hP  K" 3 '<<’<<>R’,  b \ ÿçä‰ '7'6553'#3335#53353353#353#5#6½——‘7@-.A8•W/+ $*H2"L!%%!1 ÿèî˜17735#53533#3'67#&'3&'736533#'67#&'`TTUU`  º> 60 $biWF\Ž(" 'e    '  ÿèæ€"&73#"''3255##53535#335#3#735#Ì   ©\&nHH66nnJJ€8G  2O`88(  )- ÿéñ†+173533#3#535#'&'&'3533#3#535#''6Y8;;J£F8) G=@@MªJ= t    ' "ÿèí‰!9?733#&'#5'67#537'6'&'3673#&''67'7#367u[H2;$, '#CV=  _*Mw4C )#%D / 2=X+‰      O     ÿéå‰ $(>73#"''3255#'67#5353635#35#35#73#3#"''3267#735#I(   5 *@999999TV>H J ;C‰|   Q$ ' & F51 5 ÿçó‹%+1773673#3#3#535#535#53&''67&'7&''&'U ( 6ZPPeÞdOOZ6  H  ]  ‰  e    ,NÍŽ 7'2'6'&''&'Á ?ZKE 3  %  Ž       ÿéõ‡'+@FL73&''67&''667#'#5'673#73533##"''3255#'67&'ˆK&   - 9 ]  !977   9 Z‡   qX b&       ÿçö@F767'7&''5'665567&'57'7''5#'66556&'n    3Š       8L  \ g-$ #+%&+#J_  l-& $+& "ÿçìœ#)/73733#33#53537#35#35#35#35#'67&'%QNQJ×CNzzzzzzzz& "c& $‰ VV !     ÿçñ’#'+17=73#3#3#"''3&'73267#5363535'67&''&'sMޱ±«   ª@0zzz c    ’ ; ( h G     ÿæõš-159=AEL735333##3#3#''27'7#537#535#5#5;5#33535#33535#33567#)KQQZo„1 M Y%N."42C:TK99K?Š99K?‘@@RFA KŒ$   $0 ;ÿèî‹'+/3B776553'67'56'3#7#5'275#35#35#75#7##53##'265¤ )  yW  ¨9  ‹L>>-e^!g<?FŠ›_ iÿæõ{!%73#3267#"&55#'67#735#35#35#yg   ( "CCCCCC{a  % C) ' ÿèó‰,0AGM73'67#'6'3533#3#535#3&''667'3#3##"''3255#'67&'¡@ 3…333,i+3™% &Žbb v1  3  S ‰   +!        ÿéö“3V73&''67&''667#'#"''3254'7##53#3#3#3#535#535#'6735#‡M!+"$#   ='   bV"6633A•A,,& !“    & (ž>       ÿçöy!%<73#3267#"&55#'67#735#35#35#'3#3#67'675375#zf    . '@@@@@@x^" ,5*y` # C & % A$ GDUrìž73533#3#535#-GPPdØ`G‘   ÿèõ–(1:@FL73#35#535#53#56&'733267#"'367'7367'7&'&'7&'l &??•DDFX¹,   ¡$T'& N _ –   C=I   & YGRH R      ÿçô”'+/GM7#5373#3#3#"''3267#'667#5375#3573#&''67&''667#\bh733#33#"''3255#67&'7&''67##535335#&''67#'3#&'#5'67#535'2'6'&'Ÿ88/   .   #B  & ""   &*!  4  •  7= (  BR77(    0+  ;B)   ZìÏ73#767#53&''67&''3#M: Rj  XÏuR     #^…Aí‡735#53533#3##5#'00##&&*j   ÿéòÑ'73##"''32655#'6'3##5#'6™N   ZB'  Ñ ‹(+>««()ÿèæ…!73#"''32765#'67#'67#'6K  JH"@< $…d&BD3.91% 'ÿéò}"7#5##53'66533267#"&5Ìs?/84(   + }gUTf.2 ('$* &ÿéçˆ733#3#5##5335#ijjZ‰C/‰‰ˆ^ ^@-&ÿçÜ} 7#53#5##575#'#3355#:¶>R><<<pp44: ÿéàŒ 7#5##535335#33535#335à˜UUAAUC˜AAUCqˆˆ4!!!T!!!!ÿéã‚ !7#5##535#67#53''67&'ã››› _w 2% ‚™ ™~l(   ÿóï 73&'73#&'73#536!WVÅ-  y @܆g    ! ÿìò(,73#"''3265#'6#32767#"&5535>‘  ˆ u[2E  R?GM2:  Vÿèã€73#"''3255#3##53#=#'¼  ¨™™)uM€}  fK DC  ÿéñˆ %7#5'673533##"''3255#&'E  !1^##  ^$  ˆ rW R  M   ÿéó‡73533#&'3##5#535'67#\[G!4 :$4455(4 3"Hn)7<##95*ÿðî} 7#55#353#Ο‹ww§ÛÛ}gg*?ÿæéˆ#)7#'676753673#"''3267##5'6'35b2 &!+"(,] H!? : =1 !*KJ%*  ÿéè"&73#3#"''3267##5'67#735#33535#&¶P\ E!@ ?H ORf<“C?8/G?%8=ÿéë†73#3#3##5#'6J–xhhqq(†'v ÿèõ‡1673673&''67&'67#67#7#53&''67&67# 0     "O b  ! " /l 4     " "3     ÿéæŠ73#5'673#"''3266535#Bu{ ™! ‘SSe B;% i%*5Cÿéî–'7&''6767&'3533533##5#'67#©JS(14 ‡8J22J2 )8’ !  K@@' ÿæä†"&73733#"''32765#'667#7#5##535#.  Ì...md  A/1.(‰‰dQÿèñ}73##"''32655#3#735#â% ©kkCC}h cF  ÿéóˆ73#3#&''67#5367#'6CŠMi\$6 =%V Jci6 ˆ !,-#  ÿïó73533533533##5#3#5#5# %-0((X-¨¼%ª0]##$$11H[ ÿéò37&'67327#"&''67&''7''7&'37¢  Sc  "65 7/ `[DC_]  2         ÿéí…/73733&'73#673267#"&''675#'67#@@  +R "  $  83 œ  “|[[HHŒi*V Tb!ÿçä…$(7373&''67&'67#67#7#5##535##2   % "¦...n7     )"0Š ŠmZ ÿéó&,73265#"&55#'6553#67'75#&'Ò5dW#&1 '!  t " h0'67C Iÿéè†%)73533#7#"''32655'75#7#5##535#&%% $ $,&Î===l% Ž Žq^ÿèôœ.267'67333##5##"''3255#535#535#53673535L +" S+< DDccBd<<‘£  ÿéò#'+173#3#&''67&'767#535#35#33535#335ÞePS /KQ83&  MPd(< :$(6 @)[[8 } Š.'"99#&.        ÿéóš!%*.73#3533##5#53'67#536365#335367#335naiKKKˆ#-B3?@S>‘3k "0 &  (D31B0   3  N!   ARÿêò’7&''6767&'#5##535#¾  9B%%' 3]]]„ (! CMM4"ÿèôŽ +177&''6'3#3#"''32765#'655#53&'&'&'¬  &P#<3     ,r   Ž% J 1 / =  ! ÿêó£(.R7&'673267#"''67&''7&'7&'673267#"''677&''7&'7ž Pa   1A H WK  nj   #2C 1$^Q £       9        mÿïò 7#3#5#"''3255##5##5353ímr…}  $|¡*4  NNW“X+;;t2) (0)< 6 ,  ÿéñ 48<73533#67#"''3255'675#73533533##5##5#35#35#!     !Y77%7777s)  %xx##T ÿèó– FL7&'7'6#3267#"&55'67#5367#53673#3#&'#"''3257&'#:  •  Y .9 A; 5IFPfoƒ1   H –  g% +      &ÿêó’ !-73#735#35#3267#"&5536'33#7'&²²ŠŠŠŠ§ %+  )%œAA;'# ’O/+-     <  ÿéó— #@73#5##53'3'667##&''6#3267#"&553#"''326c²a*1&0+$% "—3 +% [  — %( & '1#   >U. ÿéó™'-735#53533#3#3#535##5##5'66&''OYYXXMMiæiO­‡S1= 9+2-0/s     $5%*:  ÿíê&,27#3#53533#3#&''67#53655#&'7'6峸Ë)=??ME$ $'( #5D=e£$        ÿéò“05I73533#7#"''3255'675#73&''67&'#3673533#3##5#535#    ^p'&   U,--::::,v .  $")    Dÿèá $7&''67#53#3'67'#;5#35#\  .7¸ji&ˆ |!$""##m##0   ?? 6 'O ÿéó›#+177333###5#535#535#5335#35#3#'3'667'67&'uPPMMffMM<<<<=   /V  › WW .&PP$   ÿéó“,073533533##5##5#3673#3#5##5'67#35#>C88C>V qwk +DGkk„!  G 5 < ÿêó—.26:73673#53&'3#"''3265'#"''3255##535#35#73#N 9Dæ=   T  88888i— )c Za  &w!.,RÿéŒ"(733#"''3255##5335#35#'&'7'6H0   N0NNNN  d  /`  %u"0Y   ÿéâŠ*7#677'7&''535357#"''3255##5{J$ 888y  +ŠZ2    —$5p  Y¡Eó$*7#'65533267#"&5'3#7'675#&'½.R  ¤R %-…  0 8!# KÿéóŽ *733#5367#3#3#&''67#5365#'6arh] h;I= 4 6 <5 =D%Ž #     ÿêï #)73#'#5##55#3535#7&'''6'6£<)<)))))§  < SL CfY ”&:T" 8 ÿé…Ž73533533##5##5#35#35# 00%0000rw w!!Y% ÿçõš!+/37'#67&'7#7'5'63'73&'3535~58 ;   01 E/! D64(uuuš  @      h     ÿéóœ #;A7'6'&'333#&'##5#'67#53373#&''67&'67#67#µ  l  A`F4A'2 *#E_gR  }=H*%'D 2  Bx :œ   ! B    .óš"73533#3#&''67#535#&'7#6!TSSj4 L\AhT” K?Œ   /  ÿêêœLU73#"''3265#353#3#"''3265#353#'67#5335#'67'67#5'66525#3H“  Y!;  ™  W)> ?-,&.  ( $0& œ8 :        ÿéì‘+17=C75#53#3#"''3267#775#53#3#"''3267#7&'7&''67'6`G[DL   Q µHZDO   T q { 0 &* (+q0K60/K506         ò’ )-7#"''325567#5367&'3'67#3#•   u–  5˜R: 1:››g '     /!2Nÿéò•(,073533#3#3#535#535##"''3255##535#35#W@AA99H¤I99@Ž  bbbbb‡    :? S ÿêô— !(,AGM7#5'6&''673&''6767#3#73533##"''3255#'67&'9  b  "A  &' ~<>>   <c  —€` &    %  k(       ÿéò˜ $673#3#&'#'67#5367#735#35#35#67&'7&''61žYƒ9" +A' 9K.xxxxxx)  /5˜Z   ? # # 7   Yÿéñ› %+17&''63#3##"''3255#535#'67&'ž $ #"' . S 77   44 ^›  )  %,   ÿéï›673#3#5##53635#3573#3#"''3255##5##535#9'DG4114z40  ,3› >J œ 6LdE  .eeL^Mñ™$*7'66533267#"&55'3#67'75#&'– H šR!&/%‚ ‰. ".  #    %@ä• 73#'6'3#'3#7&'–@H;-—  •  LFB   ÿçí™-5973'73#7#"''32654'7##5373#53&'#5##535#, 2uÔ  )e   ‚b@@@„ * .’¥  5I I.ÿèì  K73&'73#&''67&'763353#3#"''3255#67'7''67##537#d^Ù–    uŒWj  `  &+ .LF     ##48  "  ;L ÿéï˜'+/37;?73#3533#3#3##5#535#535#535335#735#33535#33535#335#¹S)**S``__P''*R??SA”??SA**>)˜F    * " F Jó‘273#67&'#"''32655'67&''67''67#SšB $   #/ 2(' & !A‘           ÿéó–&,28>73#3#3#3#3##'32667#7#535335#&''&'''67&'#Â`PPn§—›¬ ® *'+OŠ    I 7  –    /4"i      GóŸ873&''67&''667#3533#3#3##5#535#535#‘C &%4*  #1 =;9<<66BB@@449Ÿ    *       ÿêó¢ 1=CIO7&''677'&''6767''&''6767'&''6'67'67'6¨ 8>%=0 ! V#'  $/9 89)9 N. "> @= -U WG :l ož              "   ÿìó48<@F73#3#&'#"''3255#3267#"&55'67#5367#735#35#35#3&'#*ª]8!   W +7 ?6 0F6„„„„„„_AH     & 3 9  ÿéí™#):E73675#535#'673#3#3#67'7''67#"''3255##5#5'6Q))  1 /%   É   v 85C  ™ s ^–¨( _G Yà‘ 73#735#335335 ÀÀ''9(&‘8ÿéðœ"&*0EK73533#3#''275#535#35#33535#335&'#63533##"''3255#&' VYYR Q]1+SSV@@T@”@@T@ /˜¤++   ¤: ’ 9   9     ÿåîœ#'-373533#3#3#535#535#3#735#35#35#'67&''NRRMMeÛbJJN§§+ &a(&%'—  2O9   ÿéóŽ(.9733#3#3#33#"&''6735#53533''6#5'6 669944*, E[# $ !Ž  ,?++8 " VDÿçôœ (,J7#5##53&'733#3&''67'767#53'3#3#32767#"&55#'667#ìµf **0  =pKKe &< F3&&     !7  =$" W ð˜73533#3#535#'63#735#w55@™G&uuOO”  <7 ÿìôœ#'+;73533533#3#&'#'67#535#35#35#35#3533#3#535##a$$,3 *F- #2,#5aaaaaa,--V¼R, @ @ & & .   ÿçñ.26:>BFL735333##3#3#&''67'67#537#535#5#5;5#33535#33535#33567#)LNNWtˆ/ H -&&M /!4 0>4TL::N;‰::N;@@TCC J—%    %4 > ÿéñ•26>BF73533#7#"''3255'675#73533533#3#535#35##5##535#35#   T!$Œ!-!!LMMMMMy" .  "'2W W . ÿçõš"&<H7#3#'6553&'75##5#35#3353353267#"&5536'33#7'íE:°X))));)( (  "†662" Ž4. '-E"#+   2   |ÿèõ™73'67#'6&''6›E 5 " 2™  $+(/  ÿèô &*.:73&'73#'67#5#53533###67''35#35#67&'__×/60£ T # ~~~~O!  60Œ j    e '     ÿèõ™#);73#3#&'#'67#537#735#35#35#'67&''6767&'+¬b„8% 1B' +C 1„„„„„„  %*g7='! ™Q 9   7    ÿçòš*06:@7'6'#"''3255#3#3#535#535#'655'6##535#7'6×( &0  !FÃ+ )T.™0 /š  Ž  x    8@">@) 3;# $ ÿèõ›7;?733#533#3#3#67&'67'5'67#535#535#5#35#ueÕ^XÉ#22#M    H!  9G#22"“___›     .      ÿèð˜*06<73##5#'665563533#&'#5'67#7'6'&''33#ß %I-#     I 3Ui˜ !^^D (4,/22 *2> „;ñœ%+17&'73#"''3267#'67#'&'7&'''6'62 DJ7373#67&'##"''3255#5'67&'767#3&'#35#35#'67&'W kH     A   G     GEO !uuuu # { ‹   1  1  & &     ÿèô• #'+/73#735#73#735#3#3##5#535#735#33535#335ZZ88^ZZ88x¸RjjiiR??S@“??S@•--&F  * $ K²E 73#735#35#KggBBBBEE* ' 8ÿìÆN73#&''67&'767#53&'…: '  a?N       ÿèñž#'-373533#3#535#735#3353#735#35#35#&'''6(MPPiãgM::M=œ­­‡‡‡‡‡‡c,# "*4 9 4–$ 2O8   ÿéô™!%6:>73#&'#5'67#5367'23'#35##"''3255##53#735#Ä"+w9$ y'9OBQP4UUŒ  ,hhDD™    4 3 9I' ÿïñ–*.2A767#533&'76767&'#5'67&'3#735#3673#53'0  BX <    'N( ””ll/;Ô=z          (3$    ÿçô›#'+/5;73533533#3#3#535#535#35#35#33535#335'67&'.H44>iP´Qk<.@HH!>>Q>>>Q>p . 3e&& &'  JJ J) "    ÿèó˜'+?CIO73267#"&553'#33267#"&553'#33533533#3#535#35#'67&'0 &" X445! +$ Y55­)F((>æ=);FF+ (d,&))i  :/  :0 <   #    ÿèêš#)/G7367&''657367&''65''67'63533#"''3267#'67#E   *k    )ˆ o c6\  HK?5š             P / /  :ô–+L73#3##3267#"&55'677#'73655#'3#3#7'5#'67#'73655#†b!&    ! /l_ % $  ,–             ÿèðDHL735333##35#53353#5##535##5#3#5#'6653353#35#535#535#33535(MSSE5##5ED6*  1COOccM_@@@•(  f' 22 '  ,(   ÿçõ™26>B73&''67&''667#'3#3#67'75375#735##5##535#œ> !    /†R+5 %..¹<<<™       ;" JFPLI I- ÿéò’7=A73#&''67#3#&'#5##5'67#535#&''67#3&'35#"»   #f?& r )>g%   D c Orr’   /6 4 /    ? @ ÿèô˜$(-C73#3##5#5367#53637#335367#33535'33533#3#5#'65£=((R%$1$T/', <. ˜O$$ O (- X&,, R@( #6ÿçõ˜ (,0@7&''6'3533#3#3##5#535#535#35#35#73#3##5#535#¯ $Š*//&$--..&$*7777cQ----˜ I  I ) ) 66ÿèñŸ"+/3;?107'655'6733#&'#'7727#3'73#3#3##5##535#8 6([ 1 , AT JQ³————™tttk5+# ")/  &    (( ÿèö˜#',@DHLRX7533'7#3&&''67&'#'65535#67#'3533533#3#535#35#35#35#'67&'°) "    ) #¡' i"''''''9 † '    /$ %,B0UUKK-.%   ÿéê› '+/377&'7'6'33#5##533#735##5##535#33535#35#@ ‡ ?a¯`7]]‰Œ;;O=Œ;;O==›   ++) "MM ( Vë’!%73#"''3255##53535#335#3#735#Õ  m;G))GG''’7E 0DU77& )' ÿèb™ 73533#7#"''32655'675#   y "/ #( ÿçõ˜"&2A7&'#5'63&'3#735#'3#735#&''67&''6z36 { M c 6VV11wVV00  u   ˜   44&      ÿéð¢/<AEIMQU733##&'#5##5'67#535#'6553'73#3'3&'#35#535#767#353533535335¦/!" $ )5Uc23¯5((.eDJb  &..$   ) *.G   @    ÿéìš3GOSW[_7&''56767&''3533#67#"''3255'75#73#"''3267#'67##5##535#33535#35#“    m    "ŽF   B[$$5&[$$5&&{  > $!.  " '*1 2  %@W W, ÿçôž6:>GM7#5##5367#'673'3533#3#&'#5'67#535#35#3353'665&'ç92#  .½,**$$  %,&K@ fSCAQ  7  -275 1 2î— "(73#'6'#3#3#535#35#5#7&'£?G ' 'n""BB""‰ —    ( a ' (    ÿèö–17=73#3#35335#533#335#535#535#533#5356&'''6K))))7++++2%%$$':ìj*&)(?, (– ##,B>kb}    ÿéñ¢ J73#53&'3#735##5##53#67&'#"''3255'67''67&''67#gÚ^=  zz«¶¢P  #&   ,BB/)7 3)) +2¢           ÿëòŸ?F73533#3#535#3#735##5##53#&'3#3#535#535#'67#&'#6hhhS½Vh©©††±¶§ QQeÝdPP$$…9Aš     ÿéì˜#CGKO7675#53#5'7&'7675#53#5'7&'3&'73#3#3#3##5'65#5#35#<I\"'I\"Y2ROEEEESª !WDDDDD„ C   C "   B    ÿéð$(=EIO73533#3#535#3'67#3#3#535#3#3533##"''3255#'#5##535#&' T\\P­JT Ú Æ FXÁVGßßeK   K111g  —      11   ÿéñ˜&:>FJN73533#3#3#&'#5'67#535#535#73533533#3#535#35##5##535#35#$%%!!))   ))!!$h ~(DDDDDD‰  02 2W W . ÿéñŸ ,@DHLRX7'673'3&'73#3#"''32765#'655#3533533#3#535#35#35#35#&'''6  Rß&#/+  l+~#+++++++#    @'  6?' /C ;; " #    ÿèö˜$*>BFn73673#3#&'#'67#5367#7'6'&''#"''3255#'65535#35#3267&'#"''32655'67&'767k19>K  &**f I   s    u   0  ’ $&.Z0I!        ÿéôš(.4<@DHLR73673#&'#5'67#53&'735#'2'&'&'##53#'#;5##;5#''6×  .#.( *?m  À_…8''''''''p š          nPP?  * "  ÿèôœNRVZ^bfk73#&''67&''667#'35333##3#3&''67'67#5367#535#5#5;5#33535#33535#33567«:     ‘++ +.79 "!".+ ,E,G.6 œ +  ! (!   !0,ÿèõ›NSY735#53533#3'33#673265#"''67&'#3#3#3#7'75#535#535#535#275#7&'@4400B75     1&&##''#@E/))$$&&/A „  n   --%   .        ZY:  ÿéöœ$*>BFpv|73673#3#&'#'67#537#7'6'&''#"''3255#'65535#35#&'#"''3255'67'6767767&'&'''6k09?K  ' ))f I   £     8  |      -   ’ $&.Z0I            ÿêõœ#TZ`f7&'3#3#7'675#535#5'635#533#67&'#"''32655'675#535#&''6'&'?  =###*3!! "VGJ]3     4VG  3œ  - / :    #    ÿéó¡)MSv|‚7&''&'3#3#7'675#535#5'67673267#"''67&''7&'7&'67327#"''67&''7&'7#'6'&'¯ f  =###*3!! "·B   F=   $I3¡   - /        8   #       ÿèñ˜HLPTZ73533#7#"''3255'675#73#3#3#3#3##5#535#53&'#535#535#735#335335367#   Qƒ922<3==>>19..7%A!-y" .  "'//      ` ÿèóž=AEKQ7367#'6733#735#35#35#'3533#3#&'#5'67#535#35#335'67&'‡7'  . b>>>>>>†,**$$  %,&B  :k \B $ $ ]7  -275I   ÿëö›@QUY73&'73#3#53'#367#733#3#3#3#3#3267#"&5535##"''3255##535#35#)+r)#PTTMNJJJJFF$ -' NM ;;;;;Œ   '    ^ Q d & ÿèç˜ $BLV7#"''3255#535#5#'##535#35#3533#3#&'#5'67#535#3&'735#336735ç   F4444&J7777:::4/  (3:  "4  ˜•  S<,A+%%##$$,?š          *,  .       ZX ÿèôž"&*.GKOS7&'#5'63&'3#735#73#735#73#735##"''3255##5##5##535#335335|91 h QO@99599699$  &%%%%7%&ž   & & & !4  J ÿîóœ$JNRVZ`f7&'3#3#67'675#535#5'673#3#3#3#535#535#535#535#'235#33535#335'6'&'?  =##)1!! "¯,55--118€600--223 .J.{3œ  - /   ?  ?  G "    Gÿþï-^733#3'67#73267#"&55'75#'65533#67&'##'3255'67''67&''67#ŒGGM B12 $  7 €6     #%"  -       ,& "36           Nÿþñ¥ $(,I73#3#5#&'#'7##53&'#53635#35#35#3&'73#3#"''327#'67#•:2D L3*TTTTTT?9WR   F  !%¥;     ;7    ÿéñœ6ISm‡?67&'7''67'6776767&'7''67'6773##5#'6556'33#3#?7&'7''67'67777'7''67'67x      ?      Ÿ  : )¹rrm€,      /      …            hhL U-BJA         ÿçö¤,QUY]agm7'673533#3#3267#"&55#'67#535#'67#535#'673533#3#67'3#735#35#35#'67&'— '  *^ * ' $ "¥¥# #) &_%%#%ˆ             L6 ÿèñ¡ )-15Gx73&'73#73&'73#3#3#3##5'65#5#35#'&''67'76#3353#3#"''3255#67&'7&''67##5367#+/nŠ"!M )J    ;>%3   '  +(    x%)+]   $$4= '   @Q ÿéê˜ (HNRV\bh73#735#335335777'7''67'6773&'73#3#3#3##5'65#5#35#'&'''67&'#¹¹&&7'&™  &  P "*$$$$,] */  2  * ˜.=        G      ÿéö¡*.3JO_cgk73'73#3#5#3#735##"''3255#'65535#35#73267#"&55&''6555#'3#&'#'67#735#35#35#ahº¬¿®®ˆˆ   ±    ! eA   !!!!!!• ! H  $ # +@    %)#E  1 ÿèô›#'+/37;?C[_cgms7'#5'6&'73#3#537#35#35#35#'3#735#73#735#73#735##"''3255##5##5##535#335335'67&'JB +1  Uf+)W(555555’"" "" ""    4 3 ›   kk +,-2$ $ $ 7 J     ÿêòÐ .B73#53635#3533673#&'#5'67#53&'3#3#"''3267#7#fi¿?2™™&   ,*  /=å¡• ™ .Ðoo fK     \$ !KâÑ 673#53635#3673#&'#5'67#53&'735#'2qcÄK>ŸŸ†-! !1;ÑwwmU       ŠóÑ73533#3#535#'63#735#ž'f,LL&&Ç '' NE!3Tï¤!'-73533##5'67#'3533##5'67#&''&'™!""   ^!""  ¢  Q  •2# 2#       EóÑ ,04P73#53&'33653353#3673#3#5'67#35#35#73673#3##5#535#53&'¹)f'–2KL^  ::;;v  /((&&.Ñ    %$J+ 6 J    moÐ 7&'7&''68 "  ­ (,pmÏ73353353#353#5335#"["Å!!'$$vjñÉ73&''67&'#367zh    É      sòÏ&73533#&'#5'67#733267#"&50++  ,{  ½   $A aÿéõu)/57676767'7&'#"''3255#'67'67&'''6Œ #'.   *, = V ' "  @   bïÏ"47&''6767'&''6767&''&''6767&'ªCL%+/8 %( ^ %) Ê   -        ÿéõÐ#'+V\b73533533#3#&'#'67#535#35#35#35#67&'7&'#"''3255'67'67676&'''6'l""'<( 0<1 ';-':llllllZ(31(   #&3& M ! Å =  = $ # 0       :   \yÐ7&'73#'65535#C"N 99¼ 6 #%\ÿèôÎ"+377'67333#"'&''67&767#7#53#5##535#w  & ,     M CCC‡  . ,     .i&?VV5#iDõÊ 7&'''667&'7&''6Ê-  &*Ê0 0.+  TÿéôÏ $*073&'#73#33#537#'67#'6&'&'t "[31 a:# 6  d   Ï- ¢Ý'xx4/GL%  % aÿô™¾ 7##55#3535#™&&¾¶Ê6%%%%^(0ÿèí«1agm735#535#53533#3#3##5#73#"''3267#'655#&'#"''3255#'67#'673276767&'&'''6=  !!  ]Q    , "# * ,)< F  $n      D: "" U     "  Tÿó¨Å%7#53#67&'7''63533#7'75#rI   #+#³'  G" & \wÓ"+73#"''3255#&'#'67#5353635#&'#D'    1 ÓN    (, ÿéŒÎEKQ73533#3#535##5##53#676767'7&'#"''3255'67'67#&'''63771q-3xTH"   . D  3  Á   %     #    B     `ÿèôÏ$*TZ`73673#3#&'#'67#5367#7'6'&'&'#"''3255'67'6776767'&'''6n/7<M    +)d D  [       ! 3  ¬  5 }$   +    ÿéyÈ $*073##"''3255#53535#35#35#35#&'''6m +   ---------8  5 Èt@  =t * ) *(  òÏ#'+73533#3#3##5#535#535#35#33535#335'''%%((**$$'%8%»ZZ283ÿéñ¥!'-W]c73533##5'67#'3533##5'67#&''&'7&'7&'#"''3255'67'6766&'''6™"!!   ^"!!  £  Q  C"/K   #" J  —0" 0"      %       5   •ÿé÷Ð'+/7&'3#7&'7''75#535'6767'35#335Þ   "('    »L!#L($~***fÿçöÉ 9?73#735#35#35#3'67'6767367&'3267#"&5'&'pvvPPPPPP/      6  É`D ( & $%< &      ^   YÿèøÓ6:@FL73533#3&533#67327#"''67&'#7'675#535#75#7&''6'&'d'*)   (0&$ X  %>· (H)0.!  "6e m˜iL] N„Ñ#'+73533#3#3##5#535#535#35#33535#335100**//44**1*B*Ç  <   < " Zÿé­Ð(,073533533#3#535#35##"''3255##535#35#_  S )  !!!!!·(t  0  0  Kÿéé›0HLPV\b7#"''3255##5##57767&'74''67'6773533#7'74''675#735#335&'''674'é  NK,    ?    V $ !››  †––¡²;  9 <Vÿè§Ï 173#53635#35#3#3#"''3265#'667#53&'x E!!!!''    Ï NN +-%  2 " "O,õÑ3:733#535#535#'6367#73#&''67&''667#t# @/../ J-       Ñ a  3*    !6bÿîôÊ $?\y733#3#76777&'7''67'676777&'7''67'6767677&'7&''67'6767677&'7&''67'67b|||Žh   G   7  G  Ê^]»     !   z     ‹ òÏ#'73533#3#535#735#335#3#535#35'%%*g,'& >BT,,0à (  B- 0i9QÿéˆÏ 7#5'6tϵŒ$ ÿè÷Ÿ!%PV\1073#3#&'#'67#5367#735#35#35#27767&'7&'#"''3255#'67'67&'''6-¦\2# 0B' ,C1‚‚‚‚‚‚8 '$3    !!  9  O ŸH   2 ;        '   UÿèôÉ !%)-28>DT7#'6553#3#3#535#535#735#33535#335'67&''&'7&'3533#3#535#ñyc)**2t0))()A)N a -,9,++5€9,ÉTF6 6@a:  # I      WÿéðÏ)-15S[_73353353#3#3#3#3#5'673&'7##35#5##"''3255#'#5'67##5&'7#35#e'#;47////8‚   >>%%%%%r    ?  ^  Ê! /    > +  CR%   Bÿì¾Ð767'67'67676'6¤"'&,7%#%3?AŸ<#  &?;#h^ÿèôÎ"*267'67333#"''67&7767#7#53#5##535#x  &+2   L! CCC‡  . 0     /j%?VV5#Zÿô•¾ 7##55#3535#•))¾¶Ê6%%%%^(RÿçòÑ#'+/373#'6553&'3#3#3#535#535#735#33535#335³4v?b(++1x5++((>(Ñ MA1 2;Y 4_7=Wÿü—Ï733#3#535#53##535#o<&'Ï#YL W;*‚ ñÏ#'73533#3#535#735#335#3#535#35#‹'%%-o0''FK]4499 +   D**bFOÿè¤Ï 073#53635#35#3#3#"''3265#'665#53&'u!H$$$$*(    Ï NN +-% 2" ![êË%+7#5##53#5##53'6673'66&'7&'w2Å2I" n! U | ËJ88JJ88J$#  $#       <‰Ï 73533#3#535#'6553'#3#5#011(d)0b(à   T 0"NˆÑ#'+/37335#535#535#53533#3#3#353#735#33535#33511%%..++&&22a(=(h  3 3  O iÿéñÌ #'+/3BI73#735#35#3#3#5##5##535#3#73#3#73#3#3#&''67#'7#6sssPPPP|5:*)95<J**J**Gqq ˆ -3 #d (Ì;$ !  '++'      ÿéð© 7'67&'3#3##5#535#e # "JoÃWddggX©    "'@@'ÿéÑx7'673'67#&C )f%/J ‚"\5& % ; ÿêík 73##5'67#&'Ûc#7?!\“& !$kn[$$ÿèÜ}"7&'73#"''3265'3#'367'5#@• ;t7 %}  q ZR;H  Fÿèô~"7&'67&'7&''5'6556Í  + 2  ,a~H %U\ " h-' #1ÿîñ73#3#3#535#535#53&'y ]`TTgâgTT`] '' ÿîõ{ 73#53535#35#35#Ê+è*kkkkkk{zz*?> ÿèê,733'67##"''32655#'67#53&'767#&´& R  9 &B= Vf ‹  5 0- ÿçíŠ!73533#3#67&'7&''67#535#"NQQg{25 "IQH`Nm   ÿíí73533#3#535#3533#3#535#$QRRcØaQSXXdÚbSo;ÿéï 27'2'6'&''&'33##"''3255#53567#× Lm[NB  /  ¨#[b   ii        ÿéõ,7#5'67#53533#&73533#&'#5'67#S $)'' %)'  $6MN(0>&#$TN'0 ÿëö”(26:>B73673#&'#3267#"&55##5'67#3533&'#35#33535#335Fu8, D " .'2 *7:%10 22D0t22D0…D   F  '-ÿéóƒ733#3#3##'353#5#535#535#ŽNNGGRR}NPPHHNƒ%„˜&ÿèó‡*73533#3#535#73&'#'3533#7'75#133;†71” “0339=J;0y2  QB ÿèï„-?D[767#53&''67&''67#53&''67&'#53&''67&67#'67#53&''67&' Kb   i Lc   sb   ! 1 ˆ Lc   o          =        ÿïò#733#33#53537#535#35#35#35#v^`J"ã#@]^Iyyyyyyy kk 9  % & ÿé’Œ 73#53&'3#3##5##535#T4€5$kkkkkDDDŒ +  9 9!ï‰!7#535#53533#3#67&'7&''6[JeGGKKez6-BQH     ÿéÞ’$,048<7677'7''7#53#"''3267#'6#5##535#33535#35#+ &+   z`   # AŒ<Œ<>„  !   /. &WW .ÿéñ‰ #)-5973&'73#73533##"''32655#3#7&'3##5##535#60z‚:  :uddˆwddc;;;u  W R  7 9 ÿéí‹"&*5;73'73#3#3#3##5'65#5#35#'37&'#''6$  &.(())1h 8$$$$$S    Š   X #m  €…"  já– 7&'6'&'6'&'6Ð 9 = –     ÿéò#'->DJ73533##"''32655#'6553'#33#7&'3##"''3255#&'''6—4   4foMM>IIl  b[' "K  6  r] X0% &,C/ #   ÿèï'+/37;?73#3533#3#3##5#535#535#535335#735#33535#33535#335$¸S***S``__P((*Q??SA”??SA**>*B      ( $ B ÿçæŽ -37JP7#'6655367&''6673#"''3265''673#'367&''66''6æ± L   ! g  „  fF   "   Ž:1& "E      r h  Y)       ÿèã”#+/373&'73673#73&'735#336735#5##535#35#2.9ÇGY€€€€€„>    9R R ) ÿçç” $59=CIO767'7&''67'6767673#735##"''3255##535#35#&''4'''6X  %   )ffBB\  OOOOO(    ,  -*W  #l )  ÿéõË<@DHLPTXb73#3#3#3#&'#"''3255##5##5'67#5367#535#535#735#3353355#35#35#35#3533&'#Å0;;]‚1#  1.  ,A3!77/((:*(.AAA"ƒƒƒƒ -. EË+  ;   ##" ;   %   @ ÿéì“$(,IOU[73&'73#3#3#3##5'65#5#35#'7&'7&''67'67676&''4'''6 )$$$$*[ .9# $       “   _&&f) R ÿèó•'CMQU[agvz€7'67'677767&'7'#"''325'&''67'6776767&'73&'73#3#3#&'7&'''67'#5##53635#'&'à      u    C;;;; £À —  ? 97- , 6 Z   -+     8   ÿèôÊ '73#'3#5#53733##3533#3##5#535#w199d66…RSSiijjRÊIII    == ÿêôY7'67#'673&0B o/Y *!a9 A &     TïË %)733##3#5'67#5367#537#3353535#&¬zŒœ )?D0DW[_jttË+    @ ÿæõ^#)/73'67#&''67#'6'&'&''6€W '7 6  < B   8 ^ 0 &3      ÿéïW!7#'67#5367#535#53#3#3#&„S<R_NNQ¸SOPhS:G    ÿçôÒ%uy73673#3#3#535#535#53&'&''3'33#673265#"&''677&'#&'7#3#3#"''3267#7#5'67#535'635`-8]RRdÚbPP\@v >?A?   (   -4   3   %;&4" Ò      W            > ÿèôx773673#3#3#3#&'#'67#5367#535#535#53&'N =  5`RRhjfN:H#N;GSacQQ`6x          TÿéóÒ#'+/37_djp7#3#3'67#&'#'66553&'75##5#35#33533533265##3&''67&''67&567#''67&'ò'$qu# * <(D<       *   O   à *   C5 1#]  :      8 D 4ÿèò¥#Ko73673#3#3##5#535#535#53&'#53&'73673#3#3#'67#5347#5373673#3#3##5#535#535#53&'r%+E::KKII==C% # $!), !^ #%%$$#¥      n      (   ÿëêÆ)/573#"''3255'675#73#"''3255'675#&'7&'b + 4!Nqc + 5 NW  ƒ  Æ¿3[¿3[    ÿçóx 7&''6'63'67'6}47 8:-5 @5 + *<(N IDB;_x      #   ÿéêy#'+73&'73#3#3#3##5'65#5#35#D8LOEEEEQ£ %R?????y W ! ÿéðw".73#&''67#&''67#53&'3533##5#‚\-  N  '.^`heehw        S$$ ÿêòz*077#53&'#53&'73#3#3#&''67'7#5367367#67#bUB!UV! ?| ~1"$*#&@-35C8N(C<       J  ÿèó†#'+/37#5##53533#3#3##5#535#535#35#33535#335î¹MPPNNllhhLLM::L:†::L:†##CC" " uÿèõÐ 27#5'6733#535#'67#535#'673#3#&•&7 M0%   +  Ð¹Š +*-˜   "jÿéõÏ 5;A7&''63#"''3255'675#73#"''3255'675#&'7&'¬ $ .1 ;5 #8 M Ï! .ƒ & 2‚ ' 1    ÿèvÐ &273673#3#'67#537#735#35#&'33##5#53)%2:   % 4444+ º  O /-@   ))tÿëõÏ#'+1A73533#3#3##5#535#535#35#33535#335'&'333#"''75#   ""##  / Y# '*   »XX3:]Hf W ÿéóÐ;?73533673#'67#535#67###5'673#"''32765#35#$E8 A^7I 5+]ZEi4/N -n i  ::¼  \3 5  !P 1 " ÿèóÏ.3;?73533673#673#"''3267#5'67#535#67##5##535#'@E 7W+Q)1W†….6-bY@n@PPP¾  J4% f? ?%ÿîîV%73#&'3#3#535#535'67#67&'# ¾VVeÜcQQ$% *+-5D V      ÿéóÎ%735#53533#3#3#&'#5'67#535#*KVVVVMMdX$: 9%$7:#UaKˆ+.SS,(€ÿæìÈ 7#3#5##535#35#åQXD>>DDÈTi âB0«7 ÿéóÆ73#7#5'75#35#35#675#Ý&(LW+';hhhh9/hÆ„31!!V"[ÿêðw7#5'75#53#'35#35#5#î''®zffffffXP>3/zðÒ 7'67&''67&® .}1  ¤ !(  (  TæÈ $73#7#5'75#35#35#75#73#735#w (.%//////^RR,,ÈE M,- ?T2 ÿçôÉ5;A73#63#5'75#35#35#75#67&'#5'6'6'6p  $+"//////‰ &" =&!&]?  !/ / 1ÉB K +.     (BX  4ÿøÎq73#67#5'275#35#35#675#7“ 29 %FFFF'Fq?J ( , dæÐ"&73#3#&''67#537#'673#735#3C&/2 # % /6 gTT00Ð      K)iDõÊ 7&'''667&'7&''6Ê-  &*Ê0 0.+  cÿéöÎ:R7676767&'74''67'67676767'74''67'6736533533##5#'67#{    9    i#""$"š ." . "s ??9'xHáÎ 73#53635#7&'Ê i:5CCÎUU[2B  DƒÏ!73533#3#535#'6553'#37353..'d+3  c9Æ   O , \ôÏ )A7'67&''6'67&'#5'6333#33#"''67Á  ‘ N  ?   $a::!N   ¯  +    3&    ÿémÈ .73##5#535#35#35#35#35#53#3#67'675#^ D $$$$$$: ! ÈÏqM-.OÿèóÏ735333###5#535#535#33535,GNNKKeeG[:::²35OO"2"" ÿçóÏ'+17735333###5#535#535#335353'6573#'&'''6)KMMLLffK_999£%  >  ¾()qq(76.c\ $ ÿæõÏCGK735333##35#53353#5##535##5#3#5#'6753353#35#535#535#33535#QXXI4 !5ID3" 0CQQiiQcEEEÅ  7 $‰+$4 DD 4$ @"# 7  qòÐ,37&'73#'65535#73#&''67&''667#@'P;;mB      ,Â4 & (    ÿèïÑ $(73#3#5#53&'#"''3255##535#35#„`¨¤"gg  ~~~~~Ñ +Yq  /Š&6#ÿéÜÏ"(733#"''3255##5335#35#7'6'&'uO ŠO;ŠŠŠŠ†  Ï;  <«/Jw  'ÿéÝÉ!73#735##"''32655##55#351¢¢zz˜  Ž¢ŽŽÉE@u +Œ( ÿéçÏ%)-73#'66553&'#3#"''3255##535#35#‰S° Yažž  yyyyyÏ 8$>0 -V #"n  +†%7pëÑ73#67&'7&''67#53&'†Z~37JSBfÑ       ÿéñÓ6:>7&''67&'763673#3#"''3255##5'67#35#35#Ä ))40 "*,)&šR w†~  z !"A>zzzzÓ     > d  '^!22kÿêôÌ 7&'''63533##5#Ä!5885Ì6 9 8 #J..XXléÏ73#'3#73&''67&'767#K-Y`  !   IÏa[SS    aÿêòÏ"(735337&'#"''32765#'6655#'6o%.    $  ¥**;!N hJKA= % sïÏ #73#5'675#5373673267#"&5\%("FF." #(  !ÏY      ÿê–x!.7#"''3255#&''67##5353&''6–  %   #:  li  T   q 6   `ÿö­Ï!7#535#53533#3#67&'7&''6x  S5##5. % (ÿèô®.2673##53#367&'#7'#"''3255##535#35#A–– #½H H*'   ooooo®  +   S  !h ' gÿéôÐ0487'673&''67&767#"''3255##535#35#ˆ A  &'     AAAAA“ $     I`  &u /[ÿéöÅ '17'66553'#3367&'#"''325'3'67# zTT"    .' „#C5 !/$^A/"  &&0  Y 9.9ÿèï® %GT7#5'673533##"''3255#&'#"''3255#&''67##5365&''6e  M M  a   ;   2L .® ;,     /R  =  Wg )   \ÿéôÐ17=CG73673#3#&'3#"''3267#737#'67#537#3&'7'6'&'3#k)9@P#    Y)A  !'<  M  UU¦   3 &! V ¨dÿé÷Ï"BG73533##5'67#&'&''6367&'#"''3255'67'&'o377 (W ! "! -   # º-    *      Sÿí³Ï'73533#3#535#3#735#7677'7&'Z  H HH!! *3¹9;3 fÿèóÐ 6>B73#53&'3673#5#3#"''3255##5##535##53&'#5##535#­8‚6 #-7  $.'%`GGGÐ %    ''"2nI J,dÿéôÏ+8D73533533##5##5#3&'73#&''67&''63533##5#d--83 J  X=??=½#      %   ,,,ôÐ,0473673#3#3#535#'67##"''3255##55#35Œ;BB!'X   `   .@..½   CN   a 2ÿéì±"&*5EIM73'73#3#3#3##5'65#5#5#'#5'6#"''3255##535#35#z44++++1k 5(((((- — vvvvv°   .  O C+OJ ^ " TÿëõÇ$59=73#"''3255##535335#35#'&'333#"''675#3#735#ã   C # .6  $,, 64 R++ÇJ_ KdsJ%%;;1Il^7  P‰Ì !%+17'6553'#33533533#3#535#35#&'''6-fDD? [    ¥ ,+*'       _ÿì¨Î'73533#3#535#3#735#7677'7&'b?CC 'º9<6 QÿêöÏLPTX73533533##5##5##673265#"''7#53#3#367&'#'6553533&'735#35#5#W%($$(%•!   8@K ]  ^  ¿ +1"" * u. #?8A!?@  D23\ÿéóÏ !:>BFL7#5##53&'73533533##5##5#3#3267#"&55#'67#735#35#35#&'êb9Fr#  + %KKKKKKK   ½ % Y   ' ? " #   ÿéõ!%)-1EIOfl73#735#3#3#&'#5'67#535#735#33535#335'#"''3255#'65535#35#7'65533267#"&554',¤¤}}@     #  9    ‰<  +&6    " `  )),'$* **[ TRÿéùÏ^bfj73533533##5##5#33534'33&'73#673265#"''7#53#3#37&'#'67#'65#535#35#35#5#Z!'**'! O    32 ?   >  ¿ #) ,4(  y0 +=@6( '%. ' 59 GïÐ $(X\`dj73&'73#3#3#3#5'6353535'3533533##3#3#3#'67#5365#535#535#5#5#35#335&'Ÿ !#b  ¬*))--8>!  %0))''P* ' Ð  D  Z      #6 ÿôêÅ 73#53#3#'#33535[ËÈYKJGGG’’HÑ(U}(//B..ÿòôà #'+/73#535#535#335#3357#3#3#55#35#5#aa2::”) (m1>>ÃÑ/S***KQ**²@(9')4)'PK?0 -%;0I81 ©ƒ3I$wÿéñ”73#735##5##535#7#5##535#‹WW11i”:8bbC2bbC2 ÿéöÈ #'+/DHP73#535#535#335#3357#3#3#55#35#5#3673#&'#'67#3#'3'66`c#233„"%d+33†Qm8' / , ; );‡G  Èl,L,l -!   88  ÿèóV73673&'73#&'#'67# `/  +[@JN =U5   0+ ÿéó^ '7'67##7&''6'&''6u b-*G  o   ? YR        ÿíòÐ?F73533#3#535#3#735##5##53#&'3#3#535#535#'67#&'#6 iiiU¾Vi¬¬††³¸¨ RRgßePP$%%‰ 1EÇ   & !''        ÿéóÏ%73#35335#535#53#&''67#56\00AC332FT G MO PO#Æ !{v$x0..'qÿèât735#535#53#5##56732œCCBVœ0#)D'""  |  SáÌ73#35#535#53#56m!)BBœFFBVÄ/ÌrmÿéëÏ*06<B73#35#535#53#3#"''32765#'67#56&''&''&'''6n!99‰BBDVz •  &&Z      ÏfG )b’   ÿééÏ26:>B73#35#535#53#563#3#"''3267#'67#5367#735#33535#335o !*CCFFBUÂ//ÂYd  VL ;LZUDDWCšDDWCÏ    OHRH & # + (  ÿéòÐBHN735#'67#5373#3533#3#35#535#535#533#535673#3#35#'67&'Y#"  ( C####"5å""""M#, (m$ ""g  !…    8     ÿèòÏ17=73#3#35335#533#335#535#535#533#5356'67&'H!!!!0*00+4%%%%$7å +(h+% ((Í ::GSW"“‡¥    'ÿéØL73533#3##5#535#C344NNOO3=## ÿéë¬"&*D73#3#3#3#5'673&'5#5#353#35#535#53#5##56‚ STLLLLZ 6 BDDD !*EE—??DW—0¬   2#!   NJ ÿîòÏ)-159AEIM73#3#35335#535#535#533#5##535635#3#3#735#3#53535#35#35#E &W& !2 ³ 055**** P*æ+kkkkkkÏ  ]][&&SYP # >RR & '  ÿéóÏ3J7'23#5##536'&''&'3'67#&''673533#3##5#'735#à TxdK ,µ—:  .  5a P ( "V1&&+*4$1Ï(*    6[ ?   +++'+ÿéîÐAEQ]dh736733#3#5#&'3#3##5#'7#535'67##53&'67#537#3353&'67#3&'67#&'#635#)9Y ! WW^^S b )/ 0D2GQn< 6 ;9 4 1 &JC9  +   + I  % : ÿéóÐ $(,0D[73#3#3#535#535'635#35#35#5##5##5#3'67#&''673533#3##5#'735#@  !!Ä##  p/,w 4` N ) #U1&&+*4#1Ð  &? G-   ""d‹óÑ 73#&'#'673#&'#'6}$ J'  Ñ     ÿéêÐ!73353#'67#53373353353##5#:')$$J*(Їex. &xeee‡‡exKK ÿéó{76767&''67&'@- %#8 9+*B >&,w8!<     ; ÿéó7327#"&''7&537&'éi / :\['c ($30*  !!8   ÿêòÏ#735#535#53533533533#3#3##5# i],8888,]hhi4) \ll\*77*ÿéâ‡73#3#"''3267#735#,¨“£¢”‡</ =ÿóò73533#3#535#VXXiägVT--::#ÿíî73#3267#"&5535##®“:B HD“›J0 " D'ÿòîŒ73#3#5#53&'„ ]¤›¯!dŒUh  ÿéò‚73567#533##"''3255# nx•"ff    n< - )ÿéíÏ573353353##5#3353353##5#'3353#'67#53365)9>RM_()K+ & $É&,,)<%>LL=P11^3 >Q& P= ÿéðŽ73533#3#&''67#53655#'NQRdXE IQ HU[Nw  &41# ÿêó‘$733#353#3267#"&55#5335#53v\\:N "  .)O;]]‘;(:   ":(; ÿéòƒ#7367#53#3#3267#"&55#'67#WL½]oS  ) M BQR9  ?B3 ÿéò 73#'63#3327#"&'#7§° ““¨”  ((-#/ÿêò”$)733#"&55#'665#53&''67&67#¶). P¡ $7@-,9 5$0 _”$    ^     ÿéô˜73#&''67&'#53&'67„ ], ((>B./7 9' %m(% ˜&!    , -%"ÿêï‰ 73'67#'6'6767&E…  v  FJ2X M‰   1&" 0 ÿéò 7'67#73#3#/ ) 4‚‚¢¢@ ).t\ ÿéîƒ73##5'67#&'ÚW$9N&l”##ƒwb""- ÿêòƒ373327267##"&5'#"''3255'67567#536•  $   Tkƒw  F0 'ÿéáš73#5#'6553&'#3„PR\šO(! #+6 ," ÿéì&733'67##"''32655#53&'767#%»+$O  J iq ” 7 3  "ÿéò“%767'5673#3265#"&''3&'4* ]T ''TO % / SN$::  ‡!!+&1ÿéõŒ !7'67&'3#"''32765#'67#W4 0h'(&(a• C D = (Œ $(.J #>1 ÿéâ•#7#"''3255#&''67##53653â  H! - 2?Uzx  a -‘   ÿïó~ #7&'7&'''67332667#"&5~  _” 0   % ~ *0 /+;' /(X!  ÿéô–"735#53533#3&''67&'#367&QggccG$%4=,.D<% #!%S! !ÿèó‘ %7#5'673673265#"&55'67S  /E(!/$ / ‘xb %(: !5  0  ÿèæ…!73#"''32765#'67#'67#'6K  JH"@< $…d&BD3.91% 'ÿéò›7365#535333#&''67#75#cMML]?H"M A\³8F<'89 +) ÿóòƒ733#3#53533yQQeæ/*ƒ(BZZ ÿéó‚73#3#67&'7&''67#!¹¹æ|7@ ! QR)R‚, ! $ ÿèë73##5#'6556Í CY­=\"_ ZZ,'!$FÿééÒ)S73#"''32765#353#'67#53365#'63#"''32765#353#'67#533655#'6H  N!<: &*  " ™  P&=F 3*#2 Ò I ))  %YJ +( ( ÿèñ"73#3##"''32655'67#'735#Ð-66 2QK+fp.9 2,&. ÿêó{ 73&''65'3&''65¡+&1 @_ $6{3.'$,3" ) ': ÿêó˜ 7&'#53##7&'z  _æs5! ˜ 4oaÿéé™%73533#3#3#"''3267##5#535#535#W[[SSb  NWWHHW„/ AA ÿéì‡ "7#'65536733#"''3267#'67#ì´'/V  E+ & +‡9.$ $,B6 F"+8,ÿêï '7#'6655#3267#"&553#"''326è« ŽH '.  85o  ;1$ $E:L a3ÿéò†#7#5##53'665332767#"&5Ìu?073)  ( †m[Zl25 +*'.  ÿéò“7'63533#&&'#67'5#¼!- +‘8šcB )0$ 8“ (==6&%?  J ÿìò )7&''6#3267#"&553#"''326v4: 87!C LQX"@ J/€  "%$0; R,ÿõì† 73#5##53&'3#‚]°dZØØ† ;)*< w ÿéôˆ!'736733#"''32765#'67#&'''6OE6U NK¿  ¢ n  B1 AZJ    ÿëó—173'67#&''67#3267#"&553#"''326;7_ O ' "£4(% \   — k%O   ( l E ÿéó› $(,73##"''3255##5#53535335#33535#335Ø  ‹PP<8.ÿéò'767&'7&''6'3#3#"''3267#735#¬# ** {O8D  H5;_& ', 8R;7 9 ÿéó™#73#&'#'67#5353533655#335Ò [B HJ BXKK38K9};$4*;;  )ÿéë73#3#3##5#'6J–xhhqq(*|  ÿéô  &,73&''67&''6767#&'&'jT+4 ?2 : 0# 6 M3- 00NE JI      E     ÿéó–"73533#3#&''67#5365#'6=+PPg^H KQ IV^4  '2.#  ÿìó˜  &7'67&'3#"''32657&'''6~-9 @5/8 6; Dj z#- V A & (* ÿæð‹733##5##5#53533#335#²**g%%gggggg‹w v"4! ÿéóŽ73#3##5#535#&'7'6ÆYhhiiX   ” ŽB==B    ÿéó˜ &,2736733#3##"''327#67#767#7&'67#7&' ©  ˜¶' ]‚+# !X!""-( Y(   ÿéñ %7#5'673533##"''3255#&'E  !1^##  ^$   x[ W  R   ÿéó”!7&'67&'7''5#'6556Ó  ( .    !c”Y(.ew &  …+4+&*=ÿéâ7335#53353353#5#353#5#GP=;;F´65C=+99+EC5M ÿéó›17&'#673267#"&''67&'#'6553&'3Ä 4R     ) '$ M \›   +312 ÿæä•%)736533#"''32765#'667#7#5##535#%- #Ö...y  g K571/””mZ ÿêó—&,7&'3673#3&''67&''67#67#ÅŸF‡Œp.."&3- # 6@‚ U!—       "'/J ÿéó‚)733#67'7537736732667#"&5L!!2?A& .   ‚*3d_u1 4!  ÿéó™73533#3#&'#5'67#535#]__[N$8 8&#9 7#KZ]…(-LM*&SÿóîŒ73533#3#535#SCDD=Ž=C\00CC ÿëñÏ&*.73#5#53733##'673#"''326653#735#UFF@HHR!—  vvPPÏ7 #Y5 :5G# ÿñó•7'67#53&3#3#535#—4Qs.‹§ %" *—±PeÞeM\, ''ÿéìŒ2736533&'73#673267#"&55'675#'67#CC  *S    " !8 2@n 3 &)   @O$ Bÿèäž!'+7#3#"''3267#53673#"''326'&'3#¬e ›,M   >  Iššv78%W  (  Fÿéòa7#53327#"&'#3##5#53X%‹ >11AAN6-&*9 22 ÿéó£)77&'3&533#67327#"''67&'#3533#'67#È  ¥tRR " % u/+,2 )-£   . %  ' 6.$$*ÿéëŽ!767'56737#"''3255##5-#"0 -) &(Gw  9?)  x  Ip  XŒŸÿéî#+73#3#5##535#35#35#"&55#'67#5#32ÜI=Ÿ=I\$$>Ÿ$* "+Ÿ*z zv Hÿéò73533#&'#5'67#RBH=' && -8l##03a[."- ÿ鋊73#3'67#&''67#t79C@/  #)Š I<  & ÿéó–!73#735#3525#53#3#&''67#/¢¢}}3fN²QjZ=G!W O^–5Z #  ÿîï›73533#3#535#3533#3#535#$RQQbØbRUVVeÞeU‚Fÿéë‘!73533#67&'7&''675#735#335+KII SY00K77K5sD#  $ ÿéó™$*73'67#'6'&'3&''66''6y` R ?t!0 .=,!S™   ' 0,)+ (!" ÿéí‘73#'66556#5##535#Û Ha±±  nFfff‘0( (1+OR S5#Zÿðñ‘73#3#5#53&'®6ni}K‘\o ÿéóœ$73533#3#&'#5'67#535#'6</RReW%6 9%#8 8#Wd: –!")JG% !  ÿéò“97&''67'67676767'7''67'67676767&'f ',  $ … (,!! %' '1,E  '1, E ÿçã’%)7373&''67&'67#67#7#5##535##3   $ "¥///y<"    / (3” ”vdÿéòœ07#53&'73#67&'7&'3267#"&55'67'6_Kg[u03 $#&% A : .#t    8 <<.ÿéëÏ%;K733653353#'67#73353353##5#33653353#'67#73353353##5#/   n*)n1   n*)¾0 7& 0B  B1BB1C,0 6% 0B B0;;0B"" ÿéóŠ&,73265#"&55#'6553#7'675#&'Ò5dW# &1 !  Š #& s3!*;;K P ÿé Ï)-73533533##5##5#3#3##5#'67#535#35#!&!!&!} $" "$/##¶#'MM/%'''fÿéóˆ73#3##"''3255#pyy &  TˆF  Cÿéî 73#3#"''3255##5##5##537#Þe h  .,)Pb"Y  BSSSS\o"ÿèð¡ &-7#5##53&'7373#&''67&'7#367é®b€J ‚0 '# 2*= :?U  ˆ**  6   $ ÿéó—*73#&''67&''667#'3#5'5367Q"$ " 2F —0 &&(ª? cZ Kÿïï73#3#535#535'2Ý #IID—AIIC.55, ÿéò 073673##5'67#33##"''3255#53567#I|‡ .>[l@@  II PŒ dS ! $ !  ÿéö¡(,73#3#5#53&'3'66733267#"&5'3#„_­ž±h5 ! s   7¡ 1Z)  8  :N ÿéï™!%73673#3##5#'67#535#53&'5#I7  .0;;B9 /8=22 i>™&AA(& R&& ÿêí˜4O736733#"''3265#'67#36533#"''32765#'67#734733#"''32665#'67#!Jb  VF<A '-  (%t!1   "'Œ(' = <  " = "ÿéò˜%+73533#3#&'#'67#53655#&'7'6&PRSjVGJK @Q[P" ˆ ‚(,     ÿèó˜$(7367'273#&'#'67#3'6673#R!%RQ #*nB2 AD7D=!Nb   # HÿéíŸ '73#5##53&'3#3##"''32655#535#^³c> Daa  ggIŸ &&8# ÿèç*.37#"''32654'7##5#"''3255#'65535#35#o   *É B????(,’¤ $!<<,E ÿìï‰ &?67'67'6773#3#535#'6/# '0 8, +/ /062x*0("ÿé÷— &7&''6'#5##535#&'3'67#ª# " % 26‚ -|C 8c—  ~ „_M(3 !ÿèð¡/73533#3#5##535#3567#533##"''3255#_]]a°a_gr–#ee  g ++ \   ÿêæ—-373#"''3267'6765#'63533#67'75#7&'‰Q  )16-H o&/ ‰  — h-  //5 ;    ÿèó¡#(.47&'#3##"''3255#535#5'63&''67&'}29 *TT  QQ. JW#"€¡   0  -  M ÿçó¤49767'7&''6&'''673&''67&''667p*. IJ1M(! #'I#"= Z!24'/> 9( 6¤           ÿñï… %73#3#535#&''67&''6!¾UeßfU  ~   …pp /   ÿèò¤@7&'3#3533#"''3255##5##5'67#5367'67&'76Ê  ") ‡‘ .P  <3 )9F#.($¤   2  >>76  Nÿñô– 73#53&'3#536'&'£;‘@78¦XD  –  /3%0(")*"ÿèïœ&*.2673533&'73#3#"''3255##5##535#35#33535#335d: eQ  >=Pd'==Q>==Q>‡ e  %%&}46Jÿèõ‘$)733#"&55#'6653&''67&'#367È# 1  ‹- *#. ) # ‘&  M      ÿè’73#&'#5'67#535'6‚ 11  (469’KJ%ÿçñ‘#73#5#'67#535#5333533#3#535#TB:'3883s-3‘¨A' "6$//KKTÿêî‘73#3#3##5#'6yhVNNOO ‘,~ Rÿêõ’ 73#7&'''6'6š9 Kc'[ \’m_$ %) !" 6Zÿèñ 73#7&'''6'6¡6  D `$c ZfX $ :Eÿèô“ 7#5#'67##535333267#"&5å+Q H(<   v/L1+A/-B  ÿèà‹73#735##53#=#/  zzÀ˜‹?€UU ÿéäŽ7#5##535#357'67&'35#ä¡‘"¡#D %88Ž¥ ¤Q9xx97 c(ÿéô!73533#&'#5'67##5##535#[\Q ?A!$8 4!Gµ|||Š!  &18'BMM3! ÿéô #'+73533#3#3##5#535#535#35#33535#335dccVVjjkkTTd#AAUB—AAUB OO +- ÿéó %73#53&'3673#3##5#535#53&'€WË^5Di[[[[iB  /  **  ÿéò™ %*.733##3#5##5'67#537#5367#3353535#!®##t€  $2 HN@SKPWv€€™## F3  # a ÿéó•373#3#5335'2'3333#"''67&'767#'7#á 11:~=J$QJCU    +&0•'L:V0   #"ÿéô’473#3#535#535'667#'7#5333#"''67&'Ø 770q.66(>… *%4M%MHAQ  ’!!!O  *   ÿçô“)-73#&''67&''667#'#5'673#‘P$ (   1Z  $“.  %2}Y + kÿñíž+73533#3#535#7&''67&''6!TQQdÜdT '|  #2lls !&  ÿçõ /73#5##53&'3#3#3276767#"&55#'67#`±^7——ÔC 3 'J AA ++2) .:  * ÿéò§ #'7&''67&'33#5##5367#35#05 35.9 A49Š )‚f r ‚‚§ &%   F G F ÿèñŸ67'67#535'273#&'##533#"''3267#7#'67#4 /$Pe#(SS "(gM", 8$% 1 5 : A ;#>     ' 3%DñŸ73&'73#3#5#eg¯œ¯ ‡   2ÿêñ”$*.73#3#5##5365#33535#"&55#'67#333535#áPA >K^!_ . $''r(   ” ƒ ](Ufÿïñ 733#537#537#33737#um ‹1+;.,Ž8222|8*ÿéÕ¡"(733#"''3255##5335#35#7'6'&'vK  ƒK7ƒƒƒƒ…{¡1o  .‡$8d    )ÿéò "&73#67&'#7'53&'3535|GD S!74$ G-~~~  [  51  —$ ÿèó™'-37'233##"''3255#53567#536'&''&'Ô Mo\I $``  qq‡‚ 9+™        ÿçô˜FL73&'33#673267#"&''67&'#67#"''3255'75#535'67&'{ ,?DB    ) ' B  1335u˜  !      ÿéó˜ 73#3#&''67#5367#735#35#)¬Nm[H SUHW`J‡‡‡‡˜X $# 44ÿçé’ -73#735#33535#335373#"''3267#'67# ÀÀBBVB˜BBVB¸`b "R2( DX’R102 -  Iÿé÷73533#&'#5'67#Y<B8) )' ,2n""55c]-".ÿèð .73'67#'63533#7'75#7&''66‚V  G f %-#Š/ -1'  # ++6 9+'"*ÿëö™"(:73#3533#3##5#535#'67#536'&'333#"'&'75#‡OY 22<<EE7 (Z  1 %>' …H6  <#%  H $$=  eÿçñ‰736533#&''67#7&'i1?7 1*.00d  b7 00! :9 ÿñëŠ!7#5##535#535#53#3#3&'73#ë­_HHEŸGII" (ÓŠ,,‡""  ÿèëœ(.27#5##5#3#"''3267#53673#"''32'&'3#ë­žkœ œ/P  <  Q¡¡œ,,>+4 L  # <ÿéô—<B767767'67'673#5#3267#"&55#5335#53533#3'6+ #* ‘ ! 05588c(03^*% @ # *5$66   ÿ阘$*73533#3##'2655##5'675##535#&';994   * 4;^ ˆ( n7,=Q   ÿåóŸ #@73#5##53&'3'67#&''67#3267#"&553#"''326„\¯a17 [ K*   £6" -# \  Ÿ &) (V"=   D Z.ÿèî¤(,073533#3#3#535#535##"''3255##535#35#\^^VVeÜcTT\À  ˆˆˆˆˆ˜     >N b ' ÿìm• 73#5##53635#35#<&/////• ‡ ‹A X'dÿîôŽ 73#53#3'35w}ŠwllY E3!! ÿéó“0573#7#5'75#35#35#675#7#53&''67&67#ä~&+%.....Z d  ! 0“_#k=C&3   ÿãï/573533#3#5##535#35#'3673&''67&'67#367v155+=&1==ƒ(   . u [[ U#=7      $aÿñò‘ 73&'73#3#536'&'k37` .‘M7z  1%/*&(*%ÿëô–4735#533#67&'#"''32655'675#&'#535#2ƒ‰œ#`  # :%   +49-4 ¡ƒsC     ÿéó’#'73533#3#&'#5'67#535#'##535#n166>1 $ %.:12{'-JF%$o…]K"ÿçó— .73#735#35#33#67'73267#"&5536#²²‹‹‹‹DD$!, º #, " .&—O/+'" L   Aÿèó˜!%+7&'73##"''32655#&'3#75#''6'4¦!   r0HPP=*3 ˜  w r  ? &$"ÿéᜠ%)-157&'67&'6'&'6#5##535#33535#35#…   U  ˆ  ´™CCVC™CCVCCœ   6h h$:ÿéò';?C7#"''3267#3&''67&'767##5##"''3255#'65535#35#ã   A[      F   *****(/&     P£‡ )-W*Bkÿèë’ 7#5##535335#33535#35#ëX66""6"X""6""u‹ 6$$$\'''ÿél˜73533#7&'#5'67#%!!    #{ cQ!,ÿîôŸ&*.2673#3#3#3#535#535#535#535'25##5#35#335Ö '/a'00(b[ÉZc+//(`%*Z6'&&&:'ŸF+ÿèð› -7'2'6'&''&'3533#&'#5'67#Ù Nr^H =  .  ddW$5 7$!7 3!S›       , &@C& ÿèô•#DL73533#7#"''3255'75#7&'3673#3&''67&''67#67#"   $"¶  JTXM$"  + #:r##.  &"2  )    #9. rÿíé—73#"''32765#'67#'6–G   73  — ` FB0,7!ÿçõŸ#4:7#5'6733#'6'367##3535#33267#"&57&'vI )]4S a X.U R 6::>>   %!, .5 A2f 6%  +  Aÿêô˜$(-7533'67#3&''67&'#'665535#67›=  &4"$"  "&&„  %    ,$ "@2 1ÿêê–#)37367&'#"''3255'6757#&''3353#5#2$ $&   % ..w !­Á–      (  ^^y ÿëô›)-M73#"''3265#3#3267#"&55'6#3'3533#67#"''3255'675# ^ U B8 #,  5/1&&”  ›J4 :! U *B!!$/  %)ÿçõ +27#3#3#535#35#5#73&''67&&'#3267|!!! f AA [f       ;! 2 LM!!Œ L   '.,ÿèó¢;AGM7'67#5367#53673#3#&'#"''3255#3267#"&57&'#7'6'&'< 5GEOgp…2   Y .9  C;| H s  ~  ,      (< g   ÿèò”,73533#7&'#5'67#73##5#'66556(%%     %Æ $0b#,  8x `T +, !YY&$ (1* ÿèð¦05;7#'6553&'73#3267#"&'#67'5#56&'&'ð´ QA 72  ? H7<  +83 *5<    '$ + [+ / ÿæó¤ 2873#53&'3673#53&'3673#&''67&'7#367‚YÉZ 8 9Ú93H†7! ,&F '/=T ¤ "  4       ÿèó§ !%73#53&'3673#53&'#5##535#„UÅZ >Aæ<’{{{§  &  >O O1ÿèí¥ !%73#5##53&'3#3#5##5'67#35#…S Z\Ùy ‡y 0G=yy¥ ((:M > N ÿîñ#'+73#3#3#3#3#535#535#535#535#35#335ÖaRRZZQQgáfPPZZRRa"??S?2  20KÿèæŽ 73#"''32765#'67##5##535#\Š   </ ) #ŠcccŽ3 3 $EN O4"ÿçð–#'+/73#3#3#3##5#535#535#535#5##5#35#335Í##WffggV"",---A,–&&) ÿéò•#A7&'67&'7&''5'66556#"''3255'67567#53Ý  !   BU  ?X•Y-1bu  €2+ '2*=6 -  ÿèô¦3:73533#3#535#'67&''3&''67&''667# VSScÝfV. ' s&$ $%.T : >**; 2# 6M–#          ÿñó¤*/7&'#3#3673#53&'735#535#5'63&'37 2MM-Þ4  NN6 @g(¤  *   * ÿí÷¦*48<@D73673#'#3256767#"&55#5'67#3533&'#35#33535#335K v?0J #11H 0:6+. 044F8~44F8‘  =   > ,/ÿéã—/7#"''3255##53673#53&'3#3#5#53&'ã  ¢E   "57`Yk;—•  ~œ®   % &ÿéô¨&:7373#3#3#&'#'67#5367#537#3533#3##5#535#Uagdmˆ5%0K' .@ 7AO..,,OOOO.˜  % Qÿèô” +177&''6'3#3#"''32767#'655#53&'&'&'¬  %Q#<3    +t   ”  & M $3! "0 >  #Jÿïñ† 73#53535#35#35#×§IIIIII†……,HH ÿçå§%)-7'66553&'73'#3#"''3255##535#35#6 WTœœ rrrrra4' %G  /U  "j )  ÿéö,73533#&'#5'67#73533#&'#5'67#/%%  !-m)2(  &rYQ#,/.dX+6 ÿéô–.4:733#3##"''3255#5373&''67&'767#&'''6?**40  2/F^& *  H  = –B  =.<&! #6   ÿçï™%+73533533#3#535#35#35#35#'67&'h)Ý'0hhhhhh !('b%# $%ˆQQ10&   ÿèç¤373#"''32765#'63#3#353#5335#535#'67 §  Ÿ ^6KK#€&MM# ¤y, _ !&&! ÿéñ¥#(-73#3533##5#532677#5335#335367#335w X_ /OO% @J7AAU>“:@TD¥Y ## Y#5 ÿéð'7#3#3#3#67&'#67'5#535Öˆˆ‰‰¦d  $ V-!* "   44 @Z ÿêö.2773267#"&553'#37#3'#"''3255#'65535#35#Š(  3,sC/p  % ""#"8/  ŠWE333D‰  ! *4:)@  ÿéó•$173533#&'#5'675#&''67&''6[]]+? ;$*3 A(["  †2,&@?(*2  '   ÿèã‘%)-7#"''3255#'65535#35#'#5##535#35#ã DAABA(*****‘  ! (/H.IOƒ Œ5#T!Pÿèð’73#3##5#535#'6'&'\ˆ;GGEE9t Y  ’M99M   ÿèàœ)73'67#'63#35#535#53#5##56iY R<" ==”??BU”)œ   rj ÿéõ¨48<73533#33##3#33#"&''675#535#535#535#3535\^^MMTT-#EA  %LLggKK\p:::š    .    ÿèñ›$(,73&'73#3#3#3##5'65#5#5#F7JPFFFFX®  (Q?CCCC–   e '(( ÿçô«<73'73#&'''6767&'7&'3267#"&55'67'6 h içŸ" V"!=&+   ) ? 7 +•  &   0  42% ÿéó–!%73#3##5#'67#535#735#35#35#5#)°'AA=0 &?B&ŠŠŠŠŠŠc<–d&& G ' & +ÿèï”*733#3#5##5335#'##'2655##5##5353 ;;/; ;; $”%_ _C1E` KgxÿèÜ’ $7&''67#53#3'67'#;5#35#R   -4·m l&ˆ {" ##%%m$$/   ?? 5 &R ÿéé–0497#"''3255#'65535#35#7#"''3255#'65535#35#y  31111´  31111–‘$A@.I J‘$@@.I Iÿëö¤)-73#"''32765#3#3267#"&55'635#qe  Y C; * 5.))¤Y 9<)  Z !:ÿèë˜ 7#5##53#735##5##535#ë­"‘‘kk‘‘‘‘˜,,"86G G)ÿçë“28>7#5##5&'#"''3255#'67'6767767&'&'''6믧   )*(("&#38I& ! O $ “,,9 '  !    %  3   ÿæñ£'+177333###5#535#535#5335#35#3'6673#''67&'qUUPPbbPPBBBB^   tV £  ]] 1&(!  !SK ÿèëŸ$(,04973#3#"''3255#&''275##535#735#33535#335#67+ªLb   5CJ^J77K8ƒ77K8ŸP D  /  IZ /.=ÿçå™.26<73#3#"''3255#&''275##535#535'235#33567'7Ø (0JJ[   />FYIIR]<77K88 ™ 0=  (  AQ0 :=ÿç‘‘ 73#735#35#35#'67&'llGGGGGG @‘}Y56(     ÿèæ›"&73#"''3255##53535#335#3#735#Ì  ©^&rLL::nnJJ›CX  A^pCC2"06ÿꘔ"(733#"''3255##5335#35#7'6'&'L/   M0MMMMW T  ”0c  'z!1_    ÿèôœ(73#3655335#535#53#&''67#56\00=F001EPFIQ GJ#•AAd ).  #`Nÿéö›%)73&''67&''667##5##535#‚M! ( + & $  ?XLLL›     EJ J. ÿék–7#"''3255#'65535#35#k  )))))–– &)0]0K ÿìõ“  -7&''673#53535#35#35#'&''68 &®•CCCCCCO (“   &’’2 OO,  ":æ©7&''6767&'3#735#¬" MX+14 rªª††£  -1 ÿéì 37;73#"''32765#3#5'635#'#"''3255#'65535#35#ŒV JBC) #####  r* [ IG Y'H“ ')/[,Gÿçô¦:AY`73673#&''67'67#67#3673#&''67&'67#67#'3&''67'67#53667#D|.  -,< 0 / 6tE(E  $  9'u2  ! $ –    >       8  5  0ó¡ 7'67&'3#"''3267#'67#b$6 /Z)3 0.N‡  2= 1'¡   + ( ÿêó–$*<73533##"''3255#'3533#3#535#&'3533#67'75#‡? ?l'((/q/'„  w+**3@1+o''Y  U#   ÿêñ”'-397676767'7''67'6773#3#535#&'#'674'- #  %, ?m08z/*=  2f +  llP  ÿèöš 57#5'6733#535#3#3#&''67#53565#'6=  #*¬nnANB-4? 7>E% šƒg $#"&     ÿéô¨ 673#5##53&'&'''63673&'73#&'#'67#| _´c*. ,. + -8a- %U@ J1% @W¨ (($    *   - !ÿèð›#(-173533673#3#5##5'67537#535#67#5#5#2=) C\Xn(`_=[ #=gnn‹  ` E  D"ÿçé ,0473#3#"''3265#&''67#33#5'67#735#35#*¯w ’ B   % : h{ % ŠŠŠŠ M D-   #/.+ ÿéé™'1;I73533#"''32765#'65#'3#&'67&'67&'63#67'75#‡2  +&www   0   1   Vv51=2/{g K\$ R,   6 ÿèó’073#7#5'75#35#35#75#'3#3'2667#735#mb$::::::‚Q3@ '"A 2>’d lAAd<## < ÿêê©4:?CGKO73#"''32765#3#3#"''3255##5##535#5'6&'7##5#35#33535#3357§ ? I@   -(;Bs + ((;-h((;-©  h X  ##$l    9+ ÿïô¡&*.27&'3#535'67#53673&'#&'35#35#35# 9 æ <IRi0  %%8""4''x) 32! A.   6$$$$$ ÿêï›,H73533#3#5##535#3533#"''32665#'67##"''3255'7567#535557\75„1  6 1,    8; =WŽ   n!=(Z$ Q?  ÿìñ£ +/373#5##5335#35#7#5#327267##"&55353350#&&&&&»H  + £ ––8&b+Vh 3 •7777 ÿçó<736533533##5#'67# C<???7+?* //+ ÿçõ¢!'?F767'7''67#'6'#5'6&'3&''67&''667#†$% ", % 1  #ƒ!&K% .% ,$ %)@¢  # ‚i"       ÿæõžFK7#67327#"''67&'#37#53#3&''67'67#'66553533&'767ï<    k \+4   % } €  ‡- + ,-4   ) "B V Fì› 73#3#535#5#35#335335×F:À:E(8&&8(&›66%(ö273'67#&''67#3267#"&553#"''325@8RI1 ¤1!,% V 5*  4 H' ÿé÷› &C7#5##53533##"''3255#'3#&''3#3267#"&55#'675#í´m=  =^QQs  qk!5 =1(#›$$50  + 1  7/( Jö•$*7'66533267#"&55'3#67'675#&'Ž T ªT!(.!† …+ . " !   ÿêó§-5?E7&'#673267#"''67&'#'655353#5##53'>&'Î  ,6      p‚ 0!6  § + '2  *5:/% &,B"L=?N0  ÿèð›,067#5##53#3#5##5365#5#35#"&55#'67#3#7335ð¹²5:•<9j<•' !% &iiX!›$$ rs  [)   -<  ÿèö› $7JO73#3#"''3267#'655635#35#733#"&55#'667#53&&''67&67#q ",GHJ9 5#5555«     e  $ 2› E *  Y/5 % A#  ^   gÿéô›$*73533#3#535#3533##"''3255#&'n444?;4[   [  =-  *  Zÿè÷ !%73#67&'#67'53&'3535œ46  I 5WWW  U   09  ˜!!ÿèî§!%I73&''67&'7#53667#73#735#3#3##"''3255#535#535'28;  #  ,IXX33. !)RR`` jj[[%)T§   - %>2     yÿèó”7'67#535#535333#&'35± $'!!-2 (' #(:g ( ÿéõ)/5;A73#73#3##"''3255#73##"''3255#'67&'7'67&'ZZo]]yj)   /qo/   -_ P  7T  Q  NR  O *  )  ! !>ÿìóž #7#5'673#53&'3#536'&'o !Y+s4. &|D4žd '+ 07)0/*+.(ÿéë£ ,28<73#"''32765#'6'3&'73#67'76'&'##535#‹V  K n# Y>  '1/ $  «.£o X  0$ /*#,,$@O/Uÿéô˜#(.47&'#3##"''3255#535#5'63&''67&'§# #44   22& .L [˜  *  '  F   Dÿêô–"'-37&'#3##"''3255#535#5'63&'&'''6™#+ &EE   GG.7K <L– (  % H  ÿêó§-;?7#673267#"''67&'#'6553533&'733#3##5335#ë8    r„ ‹**%D 22-"/ !*2;3% '.E  6 A' ÿèñž"37;73533#3#3#535#535#'#5'6#"''3255##535#35#V?AA99H¤H88?  #´  aaaaa‘     €b (,F  Z ! kÿìñ“ 73#53535#35#35#á†>>>>>>“••2 PR"ÿèó¢-38=C7##53#5#33#3265#"&55#'67#5'667#367#335&'[5Ùr S >G " *  4( >= 0<H 4;ND 11    *$8<$$7*a$$7*€!!$2  (( k  **.ƒ03Qÿéñ¥%)-1573533'73#3#"''3255##5##535#35#33535#335QC%  K@  .-?C--?.m--?.Œ l  **+ƒ36 ÿêõ› )>DJP7'67&'676767&'7&''67'67#53#"''3267#'66&'#'674'D %* ˆ " #* \`    0 /=  2›-!(-/ -  !3M6?  ÿéï¥59=AEI73&'73673#33##&'#5##5'67#535#535#535#5#35335353354F4N9,'$!# %%1EE11Cx!!&[!&‘ 199- ÿçôŸ=73#'67'676553&'&'3#3#&''665#5365#'6‘KŸ!QZ  ac:E@4 2? '"‡8,0  " (oo MM    ÿéöŸ/LRX7676767&'7'#"''3255'67'67'6'367#"''3255'7567#'67&'Ú ( &*#    3  G›O$  # 9ffŸ *  -  '   ? 8c   ÿçî›#)7#5##53#3#53&'#735#35#'67&'í´!–CaÜe;pppp ' "q$#›**I, )B   ÿéð¬'/37;?7676767&'7&'#'67'67#53&'73##5##535#33535#35#T  67P' WW,Jdc~v@@R>@@R>>v        ?V V-ÿéñ¢ 2GMS7&'''67&''6767&'3#"''3255'675#73#"''3255'675#&'7&'ª!& $'G 0*b-3xa !(,#Nqc !(,$OU   ¢        0S   S    .î¢!73533#3#&''67#535#&'#6'PPPd2AM;dPŠ7 8“  4 ÿêñ¤ AH7&''6'&''6#535#53533#3#3&''67&''6#326½   {   ?gMMMMgŠ{%#1C+*6 -" &ud&¤    M,,     ;ìª.37353373#6732767#"'&55'67#535#67##B? :]$C8 =H 5/ :A (,%NUBg.Ÿ         ÿçô›>D767&'7''5'665567&'67&'7''5#'66556&'y       7‰     7Q›w   }!5, (4, FGINv  ~"6+ *5,'),% ÿéó¤#'<B7#3#67&'#67'5#'6553#3533##"''3255#&'賸L    B. &w**   w& ¤#  % (1. $/WS     ÿçó« 15973#53&'33##67&'67'5'67#5#5;5#35#ƒaÜe?$$>   M   3#2%%wwww« (   6   !   )  ÿéôš#'+;73533#3#3##5#535#535#35#33535#33573#3##5#535#011**4433))0+C+"_'..,,& F F,( M6OO6Jÿéò  $73'73#3673#53&'#5##535#V8C‘(#0¨-pYYYŠ   :JJ2!"íž73&'73673#3#3#535#535#0 5 4ZRRcÙbQQZ  "ÿíò¬#/E73#5363'67&'7#367335&'7'53373673267#"&5p _ºB58  :'# B$ $+  '!¬ WWL     55 7  D     ÿèî¦.;73#'66553&'533#3#535#535&''67&''6„^¹ W JJX¼QAA h ¦ =3' %G) 6P  !   ÿéñ™%)-F73#3#5##535#5#35#"&55#'733565#35#75#53#3267#"'"&55t$E#AE  9E EE¦@S? !™‰ ‰M+$]>2U=! T)Aç• 73#'6'3#'3#7&'šAI :-‹  • LFB   ÿ왜!%+733#5'667#5#'#335#35#'6:;j . +N-08EGœ UJ !%,ÿèì¨ L73&'73#&''67&'763353#3#"''3255#67&'7&''67##537#e^Ù–    uŒWj  `  &* /LF–    %%6;  % >O ÿæñ’$4>D733'67##"''3255#53&'767##5##537#53#3'6656&'\$   %-  BÕG(1t1 H %’  B  >  ZIJ[!2 &   ÿïô‘(-167#5#&'#3#3#53&'#535#5'67##53&7##35#íK18 1J>Þ3 J2 =)Li\ 3*‘)    )! `  ÿèó¢.26<@7'67#5367#53#3#&'3#'32665#7337#'35#35#&'#3#L #4F7¤Vy>* !)  ‚ DQ ~~~~g 0 5‚‚;  ;;    Y ! >7bÿéòŸ&-48<7'67&''673'#"''3255##767#''&'35#35#v  G  =/ 6 ? ====F    K  ƒ  )  &  ÿêóžJd73&''67&''667#'67#533'67##"''32655'67#53&'36533#"''32765#'67#–G   ) 9 q  E\   )2 V&2   "/ )$ž       H 4&%E6 'ÿèö¤#'+/39K733#"''3255##5##53'767#35#35#35#35#'&'333#"''675#`ƒ/  ,*8 ` **<,,<**<,,  1 %;IRJ ¤  ` !!"p 9 ,X  .X   Iÿèö¢<N7&'73#67&'#"''32654''67&''67&''67#333#"''675#( 3ŸB %  !, /%$ # &C=1 %;IRJ ¢          X   Iÿèö§$6R73&''67&''667#'&'333#"''675#33533#3#3##5#535#535#‰E  '$2( )  ?Z  1 %;IRJ N88833FFFF338§       .X   I     ÿèö¢!%)-1C7&'73533#3#3##5#535#535#35#33535#335'333#"''675#( 8AAA::FFGG88A&&9(a&&9(¾1 %;IRJ ¢    C  C ( % X   IKÿêö›57&''67&''633#33#"&''67{  N "88+-  " ›   (  ,/ Gÿéó’(733533#3#5333#33#"&''67l44?™,99%4# !‡-8  &Fÿèò¡"&73673#3##5#'67#5365#53&'5#|  & ''2, %$' % M0¡'DD0$!Z''ÿðì— 7#3#53#735#3#735#73#735#äµ½Ð>ppII-KK##EII##—…§/,11ÿéð«"&*/DJ73533#3#''275#535#35#33535#335'#63533##"''3255#&'cddR Q^1+SSc#@@T@”@@T@0™¤++   ¤: ¢  >  > " !    ÿéòœ#'+3773&'73#3#3#3##5'65#5#35#'#5##535#Š").''''2h1#####/œ   j $((l~„_N\ÿéò¡#'+73&'73#3#3#3##5'65#5#35#ˆ$)/((((3j3$$$$$¡  o ))ÿéîŸ"(9D73#3#3#7'75375#535#'6''67#"''3255##5#5'6f2_+++ Ì  x Ÿ74@   v a‘£$ ]Fÿéöœ7=CT73533#3#535#73533327#"&55#''67&'767#'67&'3533#7'75#())0p.(m*   R  > >*(((/9/*ŠL(7.: ##        ÿèõœ$*06<B7&''675'675#53673#5'675#&'7&''6'67'6}3: 79$B 8!")-$K^ \HBq #2 .>(RNH:ztk     53I    =      ! ÿèï¢'+/37;?73#3533#3#3##5#535#535#535335#735#33535#33535#335#¹S***S``__P((*R??SA”??SA**>*¢J      - ' L  ÿèî¦ '+/73#5##53'#5'673#3#5##537#35#35#„a¶d3 *6=S'7SSSS¦ -- (fI  %f f9:ÿêí¤ 08<73#53&'#"''32654'7##53673#53&'#5##535#N2x0´  -h `@@@¤  / 1™«    5L L/ÿéë -159=CI73#67767'7&'#"''3255#'67'67#735#33535#335&'''6#·v #*0D#   )(  )??S>‘??S> S$ ! G        , )a   JÿçõŸ3973533#3#&'#'67#535#3&''67&''667#T>@@K 0 !H>(J% -#+ $  - <‘  3       ÿçî•&8<@73#3#3267#"&55'67#'7367#'3#67&'7&''735#35#}k&1/  # / # 2dUB ! ////•; " +*!\0  t: ÿéñ¡ 06:73#3#535#5#35#3353353#3##"''3255#'67'ÕE9½7C}'8%%8''¡¨¨âe   j>" {@=¡ 00 +       ÿèó§3773#53''67&'3#67&'7'5'67#735#ƒcÚa* &r(& '(e–= M$2"  -#)qq§ !    4   5    ÿéô– &67367&''6573#735#35#''63#3##5#535#2   ';~~WWWWg  U8@@??5–0  '9;I*( $ && ÿéö¤4:@FL7&'733#3##"''3255#5373&''67&'767#'&''67&'''6) G ($  '#=K   6‚ o  3  ¤  A  <.?&!  !  ) '% #" ! ÿèó—!%5C73533#3#&'#5'67#535#35#33573'67#'6&''66///($  (/(3> 0   ! % Š 8 7788> '!, ÿçõ¦"&<H7#3#'6553&'75##5#35#3353353267#"&5536'33#7'íE:°Y))));)( ( "†662" ’5/" '0G "$-   5  ÿéõ   $6;CG73#53&'33#"&55#'6653#3##53&''67&67#'#5##535#<%^#© %  uNNNNsk " !# " 3?***   )  +      B D+ ÿéó™ C73#735#335335#673265#"''67&'#&''6553'33&'7 ¿¿&&8&'#Y   #!M \* ™;=         *   õ¡ $(733##3#5'67#5367#537#3353535#¸x Œ¡ $5NUEXNSYs{{¡ 1"     H ÿèí¤ /473#735#35#3#735#33533533&''27&'7#367+««…………&ÏÏ**>,.µ²=-.;-# +3¤>&/(    ÿéö ,0Q73533533533533#3267#"&55##5#'67#5##5#3#"''3255##5##535##5$ &&   D! !| }RK  92EMŒ    !!  %"  55'7& ÿéò­.6:>B733#3'7#73267#"&55'75#'665533#53535#35#35#mNNldPR + 8#$&+ Le Âaaaaaa­     >2( $FKII $ "  ÿçï©#)/5;73#'6553&'3533533##5#5#&'''67&'7&'€ [»[5%7--[%n7b  x ;  A  © 51, $-G<))   ÿéòš#'->DJ73533##"''32655#'6553'#33#7&'3##"''3255#&'''6—5   5isPPEPPs  ha' (R  :  {f a4& (/G1!' $  ÿèó˜,DK735#53533#3#3#3#"''3267#'67#535#73#&''67&''667#,//11++/GD  3 3,ŠF   )k  * #0= /  "ÿèô–!)-1573533#&'#5'67#7'6'&''#5##535#35#35#h;=/ !3q S  $$$$$$$X>>#"'TL)'L   ‘ ”(>=ÿæõª/37;?CGL735333##3#3#&''67'67#537#535#5#5;5#33535#33535#33567#)MNNVn„0 N 0(&N4$< 3C:UM::N;‰::N;AAUB? K ¢'     ' " 8 C kÿëó!'73&''67&''667&'&'’E      :  3. 10     A     ÿêò¢,1BHN733&'73&'#'67#&'&''6673##"''3255#&'''6:3 LL yG#    w ¦¶P   R„! !O% !¢    +    N$ !    ÿéó .CI73'67#&'&''6'3353#5#'67#535#3533##"''3255#&'›=XN3    f 7.J`  `     6 )   )/®F2 ' #  ÿéíª ,28F73#5##53&'&''3673267#"&''67''67&'3353#5#533€ \´d 1"'4 , 7!ª  K=¡=ª **  '   )   8#9 0&-Û© )73#53635#3&''67&'767#'67#oaµ=2I 3    -  0©kk cI       ÿéñ«#)/5;73#3#3#"''3267#5363535&'''67&''&'mZš¶¶± °</†††  vd    «D  2 x  T   ÿéé  PU7#5##535#'6'&'3673#3#&'#"''327#3267#"&55'67#5367#'#é«««ƒ W   6DKW       /  +#   ,.m / ··£“         $  & ÿéí¨ !%)/57#5##53&'73#3#535#35#33535#335'67&'í´d_£HJ¨JG66J6€66J6e+ (h'" $%–$$  OO ,."   ÿéñ048<@73##5#'665563533#3#3##5#535#535#35#33535#335å *K!  2´/00)),,22))/+B+ gg-' )4, QQ/1ÿèóž&,28>73#3#3#3#3##'32767#7#535335#&''&'''67&'ÒbPPm§—š© ª (&*^˜    H 8  ž    17%o    ÿçò¡%:LQW]767&''67'733#"&55#'6653533##"''3255#73&''67&'#367&'''6%  !À    …-++   -v_      i  7 ˜       '   N6  3      ÿçò— &-1;AG7&''673#&''67&''667#3#'767'&'#&'O(p?    #yDDRiD  —     " ""A  :ÿçô¤ +/O73#5##53&'33#3''67&'767#53'3#3#3276767#"&55#'667#„_´d)**/    >oKKe &  G4¤ &&(     ";  @%# ÿèî+/3=C7&'73#3##'32765#&''67#'67#735#35#37'5#33#7 ByPf /    SSSSg1 RQc FE (    * % \  Z* =ë£573#"''3265#'67#'6'3#&'#5'67#535'2—F      "   &- *£ =#*   !"  ÿéì¥&/59?EKQW7333#"''3265#'6553&'7'23'677&'37#67#&''&''4'''6Ø  ž! ^(d 6D (4pv~†‚    ¥ !/ 0/ +    71        ÿéôŸ+V73533#3##'6655#&'#5'675##535#73533#3##'3255#&'#5'675##535#***)    &*m+//(    $(+- -  19&0A,  '35"0A ÿéò¢'/37;?73673#&'#5'67#53&'735'2#5##535#33535#35#Ô &-#,R!5 :#$9 /'P+#S[]ƒ77K8ƒ77K88¢   [XX!.1ÿôÈJ "7&'&'''6733267#"&5€  E  c  !  J   + ÿèô¢ #'+/73#735#73#735#3#3##5#535#735#33535#335[[77\[[77x¸RjjiiR??S@“??S@¢//*L  . * ÿéó¦ +/373#3#535#5#35#3353353#3##5#535#735#35#ÙE9Á9E‚+9''9+&¢©KiillJ¦ )) %B  ' $  ï¦"&*73&'73#3#3#3##5'65#5#5#;8 QSIIII[°  TAAAAA£  K  @ÿéõ© 159=7&'#5'6&'3#"''325'#"''3255##535#35#73#–$1 ^ 1JU 6  '''''I©    U  NV  $m * 'F ÿèô¨$(=A73#&'#5'67#5367'2&'#35#33255##53#"''#735#Å $-w;( y+=O!Q81UU h eDD¨    1 ;& '@P<   ÿéëž $8<A7#"''3255#535#5#'##535#35##"''3255#'6555#35ë  M9999+M9999„  5C33ž›  UA % 3Atµ % $O  %%  †ÿéôš &7#"''3267#3&''67&'##567#æ  ;U     A $š)3)    [±}  ÿçò£!77&'7#3#3#535#'&''63533#&'#5'67#1 Äuhh{VVW 5  $edT$4 8%$8 2%S£   ' ]5     #  22 ÿçõ¡48@D73&''67&''667#'3#3#67'6753675#735##5##535#œ>     /†R+5$..¹<<<¡      =$ NJUPL L/ÿèò« 0J73533#3#535#3533#7'75#73533#3#535#3#3267#"&55#'67#!VUUcÙcV$&&(,7,$k%&&/m*%f¿; $+ @9 1    +  * $ í§"&*.73533#3#&''75#535#35#33535#335'#___L   RdcLL_&99L9…99L9 'œ  D  D % $ #  ÿéô¥ 073#735#3#735#73#735#3#3#3#"''3267#'7#5––nn0^^::d[[77{¸¸ç¡–‘/¥( ' '    ÿçñ¤)-1O7&'3673#3"'6267##5'67#735#3355#'3#&'#5'67#535'6‘  G  39  '$ %+ +0C2!""   )++¤  4C959]  KH ÿçõž !%)?73#735#35#3#7#5'75#35#35#75#73&''67&'767#+ªªƒƒƒƒ1æ} (.$222222S`    Kž@( ! ;D & ',      ÿçð§@GKOU[7367676767&'7'#"''3255'67'67''67&'67#5367#73#735#&'''6:7   (%,/>   *,&  $ +K[[77"!R 0*§           ( #=y   ÿèïª #'+/5;73533##5#3533533#3#535#35#35#35#35#'67&'"UUUU2U33­2DUU‡‡‡‡‡‡$ *-,e&% &'œ   [[ % # #     ÿäñ«#'-373533#3#535#735#3353#735#35#35#&'''6(NNNgãhN;;O;›­­‡‡‡‡‡‡c+# #)4 2 3¡ ' 7U<    ÿéî¥5L7&'&''2#5##53673'67#&''635#53533#3##5#'Ü ,8% am«”}( R A  "S &00))**6¥   )!$ I1   !!  ÿêó873#673"&&'#"''32655'67''67&''67#m& )65/   " & ! &1   '$'  ÿéðœ"&*L7'673&'73#3#3#3##75#5#35#'3#3##"''3255'67#'735#~  "&+%%%%-`2 |\   ! !"4]    l((s'A  8,%! 'ÿëe 7'6'6'6J( ") #" + &    + ÿèõ™ ;I7#'655&'73'67#'6'3#353#5#'67#5335#5363&''65ì»2l2 % ( *$  $7>( 4™A5) +1J    "))9 "1 )&+ %î¤!%)-173#3#3#535#535'65##5##5#35#35#35#8ŸÛ ’/0¤!""  *!!!!!!4"""""nÿéò¡)/5767&'7'#"''3255'67'67676&'''6Æ     B Œ/  7  1" T  ÿèî¨DHL735333##35#53353#5##535##5#3#5#'6653353#35#535#535#33535'POOC5##5CC4&  1BRRggPc<<< +  j& 44'  !- +   ÿéì *06<B73#35#535#53#3#"''3265#'67#56&''&''&'''6n%<<@@BTŸ  Ÿ *e    # O:  Kr   wÿëê—"(733#"''3255##5335#35#7'6'&'¥2 I+IIIIH  J  —2c  'z!1]    ÿèñ¡#'+@7&'3673#3##5#535#735#33535#33573##5#'665565 ? *1122*+C+p 'M  /¡   T$$22I bb,' )5, ÿèõ !%)8?L7'#67'535'63&'73&'3535'3'67&''667#67&'£$ Q  +"  ???&F   p  ,  D$m   b r(  '  F ÿåó¤#0D7#53#3#5##5##533#73#3#73#'&''667#53&'73&'vZÈZdQReH==g<0/• (%%(   7    Tð• /7#3#3##535#73#535#67#53&''67&'•01111;DD22  3F    •8'1C   ÿéï£ +IMQU73#53&'#'67'67'67676767673'73#3#3#3##5'65#5#35#…_Ûe5 &  .&-))((0d , £ ^ '  $A  X  ÿéò™6:>BH73#735#73#67327#"''67&'#7#5'75#53&'35#35#75#7&' ^^99xCA      #Xa555555˜  ™+     ,AHR ( + | ÿæð‘"6:@F7#3#3#3#67'7&''67#53533533#3#535#5335#'67&'vB;;;;A-  $' %n"x E  ‘    \-->-C    ÿéñ¡'+/R735333##3#3##5#535#535#535#535#33535'#3#3#3#67&'7''67#535z(/ /33;;66,,))22(;f=9999@. "" •  $$ 8 ` ÿêï¤#+/37;7#53#3#5##5##533#73#3#73##5##535#33535#35#vZÈZeRQdH==g<"   . (H%   " &1. &.RM    ÿéó¤!'OTZ`7'63&'73673#5##53&'&''33267#"'3&''67&''67&567#7&'''6ÙVlh? 5   ' "­*H #( 1 r%,;/,; /#  )6_f  † ¤    !#       9 I    ÿæõ¤0;?P7'673353353#73#&''67&'767#'6#5'673#67'5#'6651 'Sl2      v   !SSF  ¤  --.<4 )  bO+ ' " ÿéù¯$*08<@7&'#5'63&'3#73&'7#33533'67335#5##535#35#ƒ2; (`! C: O¼¼      {{{{{¯    6    3II &  ÿêõ§<KQ733#3#53533#67327#"''67&'#'65534'33'73#67'675#'6zYYcã.*x:    s„ ¦c' > *  §  !"    %(# #,   ( '   ÿ颣!%+17=733#5'667#35#33535#335&'''67&''4'6=r 60/L/  \Q  £ RE "4&   ÿèñ¨<S7#67&'7&''53&'73535#'67'67'672767767#"''32654'7##5¤6 $$$J0    ¬  ‘a/    )N$# / P7 :¡²ÿéõ¤!'Vj7'2733267#"&5''6'&''&'33#"''3255#7&'7&''67##53&'767#67#53''67' 4K>W  (&(  .   . " c0 2E   ¤ • ! ‹   S @    Zi Q     ÿêóª#)/5H73'73#3#53&'#37#3#735#35#&'7&'''6733272767#"&5XW*Bâ?$E8J5¦¦€€€€Fe  ›%  9(™   #?'         ÿéóœ,28PU73#&'#5'67#535'63533#3#535#&'''63&''67&''667]   !#'8,11=…6,W ; ": ")"  % œ OJ#!       ÿîóª #'+/373#3#735#33535#3353#3#735#33535#3353#Îβ²<7&'73#"''3255##53535#335#333#"''675#3#735#( ¶   dAD//j1 %;IRJ kCC##¢  :G 3GW::* X   I ' ÿéé¤ )-97#5##535#3533#3#535#3##5'67#735#67&'é«««433C˜C4r@ NN4 ¤»»¦• &*     ÿèö¢37I7&'3533#3#3#3#3##5#535#535#53&'#535#37#333#"''675#( ==<«? %P?žU= ! ! ,\7()WLMV*!  ÿéò¥#)-16:Ni733#"''3255##5#'655'6367#35#33535#73573#"''3267#'67#3533#3##5#535#'64,   %"&<&f   & ""''11¥ x "" !'3 18Z," 0''   ÿèö£#37;M73533#3&''67&'#535#67'3533#3#535#3#735#76767'7&'…*11*    )*  É2,,+i,2 cc??  2?Œ-      /I,4(      ÿéó’#<733533#3#3#3#535#535#535#53'3#3'667#&''67#(($$**0•1**##&&~]-,#  ’ŽŽ.7*  & ÿèï¯#'<BHNT73#3#'6553&'5##5#35#3353353#'62667#73&'#&'''67&'„aG;´[&),,,>)(€œ ’0   C  4 ¯ , 9+ +4N' 8  1   Iÿçó¨<73&'73#3#'6553&'#33673533#3#3#535#535#'6e6? '{%*7..))4…=++ • *%# !;  1  ÿêñ¡',AGM73&'73#3#3##"''3255#535#53'#67#73##5#'66556'67&'+/2,,  **0B%¤ +M"  4Ÿ O   &  "=cc.( *5-k  ÿçï§%)-15;AGM73#3#3#3#535#535#535#535'235#33535#335&'''67&''&'Ö '.eeNNXXeÞeYYOOffT\?<73#53635#35#7'6'&'&'''63533#&'#5'67#z&V 0000rª © h !edS%2 8%!; 4"R© WW)69      +  45ÿ莟"9@73533##5'67#7&'7'6&'3&''67'67#53667#744 .  Y 2  #    % }""$  0   *      /  ÿèó§$(,059=73#3673#3#5##5'67#535#535#735#33533567#35#35#Åb= ,P,b‚8.b_FFP));+(5;>‚‚‚‚§/ M <  F7 ' ÿèð¤%)/39?EKQ73#3#3#3#535#535#535#535'235#&'735'6&'''67&''&'Ú (1ddTTUUeÝdWWSSffR^G@@   DA" 1 –}  (  ¤33P  M      ÿéñš!'-LR73533##"''32655#'33533#537'6'&'373#3#7'75#535#53&'7&'”7   7^ ~'L _  &!6--46A6..6†  u%%` [6)))$   /  ÿçöž7&'333#"'&'75#(  2 %;IRJ!ž  ,X  I ÿéñ® "(.6:>BS7#5##53'7&''33267#"&57&'''63#53535#3353353##"''3255#íµ` $! *$‚   µâ""4&%·Û` hœ!!            )  ÿèó©$=EIO73533#3#535#3'67#3#3#535#3#3##"''3255#535##5##535#&' WYYM­MWÝ Ë¢HXÁVGæ   NN·b111d   ¡       44 ÿèî£#D7#53#3#5##5##533#73#3#73#3#3#"''3255##5##5##537#uYÈ[eSQcG==g<D73533#3#535#3#735##5##53#'3#3#535#535#'67#'#6hhhU½Th««……¯·§ PPeÞfQQ%%‚/ =§ " ##    ÿéôœ-?Rfy73#3&'7&'#"''32655##5##5'6735#''67&'767&''67&'76&''67&'767&''67&'76Úc?   A@ % 5cO    Y    M    Z    œg \sswz  .      (          ÿçô¡Ij73673267#"&5767#533'67#3#33#"&''66535#53&'3#3#&''67#537#'6" )  &  Of #((!#   0?f:(+  #(,¡           $ I        ÿèð 4JPV73#63#5'75#35#35#675#73&''67&'767#67&'#5'6'6'6t  (.#55555Wb  !  LB%*  -4G[. '*) $5 6 2 ;  #9    C   %G     ‰ÿèóŸ73'67#'6'667&¦: -  "Ÿ  "V!,.. ÿéòª#'+W\b73533533#3#&'#'67#535#35#35#35##'67#'67676767&'7&'#"''3257&'''6r$6 .?- '5$2rrrrrr5%%#  ('9    /? M +(¢5    5   X        ÿéä  &*.2:>73#735#3353353&'73#73#"''325'3#'3#3##5##535#ÉÉ,,>(+¿<<¾  '‹rroonJJJ 21f  VT= 00 ÿèô° 48<_73#5##53'3533533#3#3#&'#'67#535#535#35#35#3#3##"''3255#535#535'2„b³bF(4((&&E+ !P* !/B''(:4444G FF\\   [[DD".°  #       ÿèô¡17=73#3#35335#533#335#535#535#533#5356'67&'R%%%%1&**'0##$$&9ç(%n&" $$ --6DFrh‚      ÿéóž&:>FJN73533#3#3#&'#5'67#535#535#73533533#3#535#35##5##535#35#$%%!!))   ))!!$f€+CCCCCCŽ  135] ]"1ÿçì¢ #BFJN73#5'675#73#5'675#&'7&'3&'73#3#3#3##5'65#5#35#\"',$In\"&IOY3ROEEEESª "VDDDDD¢H  H      *     E    8î¢ "(73#'6'#3#3#535#35#5#7&'ŸBI  ) +r##DD##‰ ¢ *f,+  ÿîñª#)/5;CGKO735333##3#535#535#535#33535'67&''&'7&'3#53535#35#35#(NGGeÞeOOccNb555    fMCÞ!!3$$6        5      ))) ÿçô¤JNRV73353'33&'73#673267#"''67#53#3#367&'#'67#'65#535#7#33535s -    KN a +%d--"(   0%8 w/ H?5( %%/ ' !ÿèò°">BFJO7#5#&''67##5'673#7#3#33&''67&''67#735#35#35#67#ßA)    ; 5H@ £da "-A+1<2   "&||||||?OŒ$    'B  03 ÿèï #'+HN73#3##'2765#&''67#'67#735#35#'3#&'#5'67#535'633#wkEZ(    GGGG)##   '()7EW GH *   * ( + QK"mÿéñ !%IO733#5'6367#35#33535#33573673#3#3##5#535#535#53&''6;*_ " &;&2  ,((00++##(9- D  V@ *8Z .. ~  ÿèîž&NRVZ^73533#3#3#&'#5'67#535#535#73#3#"''3255#7&'7''75##535#735#33535#335"##&&    %&"dn.9   &  2-.I.Ž  12T <  & CU 20ÿëö£#'+<J]7&'3533#3#&'#5'67#535#35#33573'67#'63&''65'333#"&'&'75#% 1"""    " )/$   ­. %>K T.' £   4  +(  4 39  $W H ÿèñ©.26:>BH[ag733#3#"&55'75#'6553'67#732763#735#33535#335&''3;267##"&57&'''6pKKh .1-08Ni XCE$ ‘,,?+j,,?+2  #  3 p  } ©  <3' '/F!4       ÿèõ ?DWin73&'73#3#67&'#"''32655'67''67''67#53'#67#733#"&55#'6653&''67&'#367),1      '  #?#š  a     Š             9.  "!O     ÿçì«#'+/5;73533#3#535#3#735#3353353#735#35#35#'67&'```N°N`ÑÑ,,@,+­®®ˆˆˆˆˆˆ#!)(h$# #%¡ $ I4 ÿçôª4:@QUck73533#3#535#73533327#"&'#''67&'765#'67&'3533#7'75#3#3#''67#&'7#6)**0u2)q    U 8>)))03?1) ­­â4FO<  K 0¡ %%+-                ÿéö§-26<N73#3#"''3255#3#3#535#535##53'#53'67#35#'&'333#"&''75#ŸN#! -&&?##+#'B+6h  1 %BF`fl73673#3#&'#'67#5367#7'6'&''#"''3255#'65535#35#367&'#"''325'&''6h3;@M  " %!--i J   r    |  2 › ('2^2!N"     .   ÿèï¨ *DZ73#53635#35#727767'7''67'677767'7''67'673533#&'#5'67#z@H     Ÿ     %edO2 6#8 0Q¨ \\-:'  (  ]   0.  ÿéò®%;_7#'6553&'7#5'67#53533#&'3533#&'#5'67#3#3##"''3255#535#535'2ñÁ_4   !% Ÿ   ¥!)CCVV   WWEE#'QžC6, ,2L9    '   $      ÿìï¬7;?CGK73#3#673267#"&55#3#67'5#'66553&'5##5#35#335335ƒaE<;   ' =// V&'(((;')¬  ,   +  7 5( 'J(  Fÿéô£'/37;?73673#&'#5'67#53'735#'2##53#'#;5##;5#×  0#.' ,?5_…8''''''''£      ¦SSC ÿçó£.26:>BFLfm737#535#5#53535333##3#3&''67'7#735#33535#33535#3356773#&''67&''667#$#3..,,0?:  (/I/N!!4= A<       '*&&  l7 0Š # & )' ÿèó!'7;?E7&'73#3#535#5#35#335335&'3#3##5#535#735#35#''6'.¤3-š.2_,,¹DŽ?RROO<hhhh/   **    :  # '$ " ÿêí­7;?CY73#5#&'7#3#"''3255'67#53537'67##53&'35#35#35#73#3#"''3267#735#} _4 6 &   7.I" ,^9888888[XEM M AF­    j   D O =0+ 0 &ó¦#'->DJ73533##"''32655#'6553'#33#7&'3##"''3275#'67&'“;  ;hqMMATTv  mb' *  I  < 7# "3&      ÿêé£ 59Z`fl767&'7''5673#"''3265'3#"''3255#'67#3#''#3#67'675#535#5'63&'#'6'&'G    ¤   wC    OQ 33:I!33 50 2>£    3   •-  uR     -    ÿèõª8<AFK[73#'67'6753&'&'73#3267#"&55'67#53637#335367#33567'7''6”T® Xd oX:"9 6/ *,=-i$':2   ª ?4) #  M H* +$H$*    ÿéð"&*.2673533#&'#5'67#7#3#53#735#3#735#73#735#    Üvx‹-LL))// )-- }   Y_!,1ް. +88ÿéõž&AQ7#67#535'673#&'#5''655#5'67#535'673#&3#53533533î¿#*# !   «  $*#!%  7TÄ *CžC    +) %0TR#!     ,$$0 ÿéð¢ */37;?GK73#53&'3533673#3#5##5'67#535#67#35#35#'3#3##5##535#;&^"P$  0<> 4/$? >>>>yLLLLK%%%¢     ` I E7UE E) ÿéðž %)-`73533673#3#5##5'67#535#67#35#35#'3#67'#"''32655'67&''67''67#€! .89/,!: 9999†Z"     "  #  ` I  E7ƒ        ÿèï¨ -159=EIO73#53&'73#3##'2765#&''67#'67#735#35#3#3##5##535#733#7 $]"PkEZ'   GGGGqLLLLL)))LEW¨ GH *   * ( E G* ÿéë¥  $BFJN7##535#35#7#"''3255#535#5#3&'73#3#3#3##5'65#5#35#tM9999Ä   M9999G 41++++2m :(((((¥Bz¼ $ (£  \B $    =   ÿèø¡ #)?7&'73#735#&'33#735#73#735#'673533#&'#5'67#.DiiCC\>FF""DGG##‰ !NPA( , ) &?¡  -  ++ )&$  0/  ÿèö¥ %)-1AV\b73#53&'3#3267#"&55#'667#735#35#35#'3673#53&'3533##"''3255#'67&'D(d&Rd  ??????dn0%%   0  Q  ¥ q) , Q-.0   8 -  *    ÿèñŸ#'7=AEIOU73533#3#&'#5'67#535#35#335'6733#767#35#35#35#'67&'),,$$   $)%& .a1" ======  :• ? 03 ?? ! cc  **    ÿèñ¡GKOSX73533#7#"''3255'75#73#3#3#3#3##5#535#53&'#535#535#735#33533537#  O‹?88C  9DDAA3<009(E!.‚&5  *,,0      d ÿíð£#'+/37#53#3#3#535#53'3#735#73#735#3#735#73#735#vdÞffffÞdddVLL((cLL((‡LL((cLL((’ABB6+ + C,, ÿéõ§ #)/5;JY_e{7&''673326767#"&57&'&'7&'#&'7&''33267#"&5'33267#"&5''67'63533#&'#5'67#w ='  /!‰  }  ‚  @ ‡  ;   o     s  rdgN"/ :&!?7L§                    %  +,   ÿéõ!%)-8I`7'6733#5367#'635#33535#33573##5'673##"''3255#3653#&''67#7 B(^0 %:%==x  ŠD    j,,/   $&  DD 8 ( J bOV S4     ÿíò®048<@DLPTX733#3'67#732767#"&55'75#'665533#735#33535#3353#53535#35#35#pNNh XFH&" 11')2 K(ŽŽ--=-j--=-Å0/®         <2' #D>4   ÿéø£ $@FLRX733#5'667#35#33535#33573#&'#5'67#535'6&'''67&''4'/0` )#'<'{ ,#  &.(4C  N  G £ RC  "4]+*\W%&w   ÿèî¢ ".6HM73#735#335335#5##5#5'673#535#535##5##5#53&''67&67#-¨¨""2!!+¶1 #+nm[WW\‡z% m *(  ! 1 ¢& !UB /  *   ÿéõ  $08<@DJPV7&''67'6767677&'73#735#&''63#53535#35#35#&'''674'Y # %-llHH"  K “ "#j3(N%" 0SP3   8AAA11111   ÿét¡ 733#3#53533367'67''6>$g >  ¡..& ? 1 ÿèï«#):EIZ`f73#3#3#67'35375#535#'6''6##53#"''325'#5'63#3##"''3255#'67&'d7!%%""03)) " °<     ¬¬Þf   f> ,&w" «    "    L]@ * /! 9      ÿæõ«%;OS73#'6553&'3533#&'#5'67#'3533#&'#5'67#3#3#5##5'67#35#[¿a!%   Z      ·nnf  !/0ff«C7+ *4L)  !     -;* 1 ÿé÷  $@FLRX733#5'667#35#33535#335'3#&'#5'67#535'6&'''67&''4'‹<n 7/.J.t#""  $&+¤Y O   RE  !4U NC %s   ÿêó° 49O73#3#53635#35#73#3#3##5#535#53'#53&'67#3533#&'#5'67#8&BEV 1155Œ/#7//..5/$•eeS&/ 8%#8 .(S°--h$IS + N.. ÿèó­ QUY73&'73#3#735#73#735#'67#535#535#53533533#3#3#67&'67'75#35#__ÖTT00STT00R6S@..55755--?Z   K"  S777ž" " v      +  Sÿéð¤%7INcx~„7#3#53#3#3#&''67#5365#'6733#"&55#'6653&''67&'#3673#"''3255'675#73#"''3255'675#&'7&'?Xji21"  " Ÿ " Y   ³c  $-3%Qlc  $,1%Rbs—C ]        *   0B   B     ÿéó£ #AGMSY_733#5'667#35#33535#33573#3#3##5#535#535#536'&'&'''67&''4'+3_ ,%'<'d/((22,,##+>,  N  F £  RB   "4^ //  ~  ÿèó£48<@QUY_e73&'73#67&'7''67#'3533533#3#3#5#535#35#35#35#7##'32655##55#35'&'''6ƒ+0@  *. v,\Yk $,,,,,,¹   <N< k4     š22/22 8 £  5"% 11%      ÿéö©"(.:FLRbh7'2'#"''32654'7##5'6'&''&'#53#3#533#535#535#&'''6733267#"&57&'à5M?H  ¿ ) /3~:C”@7|{jiik7 0    "h  ©1 4©¹   5  +          ÿéó¡)=EIMQ73533#&''67#3#535#&''67#'3353#5#'67#535##5##535#3#735#a=9  B™D  G1(ÒdddEE!!˜    --   ,5¸I1%YYD4 # 3ò  %+1773#735#35#35#73#735#35#35#&'7&'''67'6[[777777`[[777777C { Ž  ~  N7   8N7          „ÿòñŸ73&'73#3#3#535#535#†*)+**.m,&&*ˆ  ,55, ÿéñ§9=BHNTZ`f7'2'3#5##53635#35#767333#"''3265#'65537#37#7&''&'&'''67&'4'Ù 2G;s"&&&&&™   c@EP X%c ? 9§  ‰ ŠC!X&0 4  %/( /Q    h    ÿèô¯"&*.GKOS7&'#5'6&'"3#735#73#735#73#735##"''3255##5##5##535#335335„ 82 hJS855477699%  &%%%%7%&¯   (  (  ( $:  !P  ÿéîŸ#+/7;CG7#53#3#5##5##533#73#3#73##5##535#7#5##535#7#5##535#uYÈ[eRQdG==g<CINy…73#'673#&''67&'767#'63733#3#7#77#'3&'47##3&'#66767&'7&'#"''3255#'67#'6&'''6:IX }9    & V ha#"$& * ;8K& ##   -.!$Y#!!I% "ª           #      1   ÿéð­BFJNRVp73#3#6732673#&'#5'67#53&'7"'5#'6553&'5##5#35#335335#"&55#3#73533'6„`B8=!&  -C$ -1 '=. Y*.///?.' C998  ­ "       !   +8+ +3L"  I%    ÿìsª#'7#3#3#67'75375#536735#35mDD!!21%222˜, , *(1d 8 ÿéë¥ $CGMQW7#"''3255#535#5#'##535#35#3533#3#&'#5'67#535#35#&'735'6ë  M;;;;+L::::>??88 # '-7>%% 2&¥¢  c:,:‚¼ 3  %  3.   ÿèê¡ -:@DY_73#735#335335#'655367&''6673#"''325''673#'&''66536''6ÇÇ**<*+´H    " !n  Š q"  !B ¡+ ()/..    V  P B      ô¤ <B7&'73#735#35##&'3#3#"''3265#'67#'67#'67#'6) C~~ZZZZX 8®yy  ! &    ¤  3   !        ÿçõ¦ $(,048JOU[a7676767'7&''67'6773#735#35#3#735#3353353&''67&'#367'&'''674'(   #  BFW[_dj73&'73#3&533#67327#"''67#53675&'&'#53'#367#5#35'3#7#5'35#35#35#675#7&'b %$   @I c %%ŽP    ¸  ’  "= % NH   G}n# tFJc ÿèø 26;@Eb{‚767'7#"'"&55'67#53673#327675'6'37#335367#335'3#&'#5'67#535'23&''67&'67#53667#Î    $ #8' ;,F*j $$  %+!( +       2   )$$T  T7  M 5 @      M  0  ÿéó¢#'+;?LRW[agms7'673&'73#3#3#3##75#5#35#'3#3#5##535#5#35#"&55#'7335655#35#&'''67&''&'ˆ  %'!!!!(T,ˆj!@9 @   6@ @@Á L E b  Y! `‡ ŠP  " 4"-`     ÿèõ¨"(.48>DTa73#53'#3&'#73#735#335335''6'6'&'73#7&'''6'6767'767&'§BGj  1‹‹'ÁÖ vB *#! 2 >   0¨    „—' !     ( ,    7  #ÿèñ¦ Y]aey73&'73#73&'73#3#3#3##5'#3#"''3255#67'7&''67##5367#5335365#5#35#'&''67&'76(2nˆ #"P %3   '  +(>+G    –   v @ *   CT6%%%)-a    ÿèô¯#'+/3FY^‡“73533#3#3#353#5335#535#535#35#33535#335733#"&55#'6553&''67&'#36766767'7&'#"''3255#'67#'6&'''6355**33h44++3-G-l $ _    t & 5;H&##   -."W# "!I % "¨ *  *-   .      ,       /   ÿéñ­;K[735#'66553'73#3#&'#5'67#535#3#&'#5'67#353#5#535#535#33#3##533?!+ _Y.)   =     1>>001g77@@:}B6+ &K         . c c  ÿèõ¨ #'+17;AGWd73#53&'3533#67'75#73#735#335335'6'&'73#7&'''6'6767'767&'©A”?† %Uƒƒ$n> (  - <   -¨ -//< B'     ( -    7   " ÿéé£ ,INRV\bh73#735#33533567677&'7&''67'6773'73#3#3#3##5'65#5#35#'&'#'67&'"»»&&7'&š "  &  P!(####*\ +.  3  * £0A      N   Iò¯ +?73#53&'67&'#7''7667#5&'#3#"''3267#'67#„cÛc-# 0   HE  $ ¯%   $<  #! %  ÿêö£ GZ_ekqw7''6'67&3'33#673267#"''67'75#535#535#535#7&'#3#3#3#7635#'&'7&'&''6` 8   Xq'&   2<+m&= SÉ  ´$ £      7+ %     T"   SE     !  # &$ #ÿèïD)/5766767&'7'#"''3255#'67#'6&'''6^ % 435811119t  \L5())))™<9"; &"&    < ;   ÿêî— #'73#735#73#735##5##535#7#5##535#8888vÍ—J*J*KR R6&R R6& ÿçô´)-159diyƒ‰73#3#35335#535#535#533#5##535635#3#3#735#3#3#&'#"''3267#'67#5'67#5365#5#35#"&55#'67#333535#3'7#D #\&- º-==--,, L­;A2$%   7D8 . $ @;b;•/ &f+•••_M´   88   869/# 5     5  #.ÿèõf67333#"''67&''667#73265#"&55#'665R,94.F  z  f .    0  &  4ÿêó^&73673###'3255#53&'735'2Û % "S #X$ !%M^"    !3ÿïïE 733533#537'6'&'v<¼C` z  EDDD@ 2ÿîîG73353353#3#535#533p,>¼>+GGG#//3ÿèñZ #733533#537'6'&''67&'r?¼=d|  0% !Y! !Z5553   >   &ÿè«\-73533#&'#5'67#73533#&'#5'67#0   :    L  :6 "   64  ÿé{Í"(.4733##"''3255#5335#35#7'6'&''67&'=%#   %#6666>  ?    J  Í6W>  ;W%5b   ‡   ^ÿéõÏ !'+?EKQW]cio7#5'673#&''67&''667#3#73#3#3#535#535#75#7767535&'35&'''67&''&'  99     & 0N ""&X$  2   ; 8 Ï­Œ).      t>      $  ?      `ôÊ7767#53&''67&'7&'G  Š¥!&7A/*8 0%? ´    oñÐ73353353#3#535#&<?RjâfOÄ((/ ]òÏ'73533#&'#5'675#733267#"&5/**  / · .*$*V  @íÏ73533#7&'7&''75#75##5#&PPP> Se_P <<µ:  ÿïœx73533#67'7''675#735#335522  5>!5""5 a=  ]ÿè¹Ã73#3'67#&''67#dU)'> 2    Ãz/$a   "B ÿéë˜&+/377'6733#"''32765#3#&''75#75#5##5#'#%+ ›   ^A AKOAA& z..hY h T88& &ÿèñ¬6:>73#3#&'#67'5#67#3533#7'7&''275#75##5#A””Ä  N! *‹ 5 cCBB6G^+(C…/1¬ '  ) .0 cÿé¶Ð73#&'#5'675#535'6¬     #Ð, g_)&WòÏ173#3#5335'2'3333#"''67&'767#'7#ã116x4=F#"JC=Z   )%+Ï1"2   _ÿë°Ï73#3#&''67#537#'6u&  Ï1%#;1Nñ¬ &,7&'36732767#"'"''67''67&'j /"%& ,  ©¬  $" (    RóÉ.267#"''3255#'65535#35#3267#"&553'#37#3p 0 ...._$ .) h<)É^   0 -Z?.hÿèôÐ#73533#3##5#535#73#5#53733##v/00====/Ajff">>"qI""˜ÿêòÐ &73#'63567#533##"''3255#·2: ! +?   !Ð %e7 5 ÿè÷Æ48<TX\735#53327#"&'5#3##5#3533#7'7&''275#735#33573533#7'7&''275#735#335CB¹  R==C((( &2(("((( &2((› " + &&77   7   ‡ÿèò¥%7&'73#353#5#'67#53365#536¡  C- #  +< ¥   -.H&@. .uÿèõÑ $(,7&''63533#67'7&''75#735#335µ  * ,,,  ,42,.Ñ  !:E" $!!!X†Ð73533#3#535##5##53#300+k-3tNcc  & #]ÿêóÏ2<@DJP73673#3#&'#67&'7''275#5'67#5367#3533&'35#35#'&'7'6i07=M!) .<+ )*.  1+2  c ¤/ . !  G‡   ÿê÷Ñ/37=D73&'73#&''67&'767#733#&''67#535#'#33655#35# )*    H1*# "" &+1*»     %u*-,%u3""""1 "  LïÐ@FL73#&''67&''667'3533#3##"''3255#'67#535#'67&'ŸC     ²)''+# "/) `  Ð $      + (8  *$  KñÐ+K73533#3#3#3#"''3265#'67#535#535#73#&''67&'767#'6(00)).=8  ,  !.$$(ŠD    * Ä                WåÊ 73#53#3'352³ÅIJ§§”v s 5$ 3ÿèô¾ %).BFJO73#3##5#53267#53635#335367#3355#3533#&''275#735#335&'#xc->>l.%44E3w-1B7- >FG G]+)F22E4(¾ ;  ; !   $ %-  + LèÏ ,0=7&'''673'67#'6'&'3#5'635#7&''6Z $~: / M DW $33x   #Ï       5'2(  NôÒ#)733&''67&'767#'6&'&'Tr"!5E--; .$ 'g &&(''&.,,-Ò    6     IóÕ .DJ73'7&''667#67&''3353#5#'67#535#3533##"''32655#&'¡D\ 1 0    x# @6Q`  ` Õ(    )$&„-$      ÿæù£&*.8<@F73#3#&'#&''275#5'67#5367#735#35#3533&'#35#335&'7#)ª^ …6(  DZ+(F  #1B3††††-. D 33G2 6£@ & " E $ *QëÈ)733'67##"''3255#'67#53&'767#.±#)X <  #@ 9Xc ‹È        iÿëöÊ$*0?EK73#&'##5#'67#535'2'6'&'&''33267#"&57&'''6â 8$    #79)  C:     R  Y Ê*00 (   b8  = !  fÿæõÓ (,048>D7#5'67&'3265#"&''7&'33#735#35#35#'67&'„ U .. "IooIIIIII@ Ó;$     >gH) ( !   _ÿéöÏ7=AEK73&'73#3&'33#673267#"''67#5367&'#53&'#367#35#35#7&'`)(    =F \  j  ²  #.Q/" &$\T ,! Y=dÿõ¸Î(73533#3#535#3#735#7677'7&'d""JII## !) »2;3  ^ÿéôÍ!77&'3#53#3'35'&''63533#&'#5'67#x :M^\KDD2p '  ;<0  ,Í  Nv3#   4 7:!.ÿèöº5BVZ^c7'673'67'3673#353#5#'67#53365#53&'3&''653533#&''275#735#335&'#´ 3 ‰) ! )s   {GK J`+)G44G7(—     -  &   <1  ,`éÑ 73533#''275#735#335&'#6+IO   Mf/.I66H;-Á51 EõÏ28>73533533#3#535#35#35#35#73##5#'6556&'''6,z',,,,,,« )Q# 2^ ( À;; " # PCC. 8c  _ÿïôÇ!%973#63#5'75#35#35#75#73#735#3#3#3#535#535#aS   ;55Yy3))<‹;++2ÇD M-. B_>_ IòÐ!'+/37Ke733##'3255##5#'655'6367#35#33535#33573#"''3267#'67#3533#3##5#535#'65)   %#'>'e   ( ##((11 ÐP   " % D    !     GóÐ>ENR73533533##5##5#3673#&''67&''#"''#5'6767#3265#35#""O C     4 † &L D Ä  '      * " $&% GõÎ#'+/3F]7335#535#535#53533#3#3#353#735#33535#335733#"&55#'6653&''67&'7367#44''11..((55e)@)f   S"  =b  5 5  U;   9  CïÑ9O7#&''53&'73535'#''67'67'676776767#"''3254'7##5£  &&& #7      ¦  ¾D  h   ):;  !4   !n} ÿåö‹"&<PTZ`73#735#3533#7'7''675#75##5#7#'6553265#"&5'#"''3255#'65535#35#74',¤¤~~  2  h<   ‹* 13  !$) ))Y _Y  )((  ?ôÏ48<@DHN733533#3#3#"''3255#&''275##535#535#53#335#33535#335#67&KCAA!Kb   5FM_L >>UCCc::L9…::L9Ï3&    +93(  =õÏ(,048HNT73533&'73#673265#"''67&'#'3#735#35#35#736533#'67#&'''6gD  5       FRJJ''''''@    ¸     #$lK,,     )õÑ)@Rf7&''67&'763533#&'#5'67#73533#&'#5'67#&''67'76373#&'#'67#’    y    !!         tR t5# -3< .?Ñ       ""   '(     1   LòÐ"&*IN73367353#3673#3#5'67#35#35#73&'73#3#3##5#535#53'#33676 IL^  77::W+,/,,++/$È   #!@ & 2 Z   CïÐ'+73533#3#535##5##53#3#5'67#35#fffR·SfÜ·¤t‚Ÿ  !{{Ç   #%%$  \ÿèôÏW[_73533533##5##5#35333##35#53353#5##535##5#3#5#'6753353#35#535#535#33535d+""+ 022& &(  '00>>0BÄ   2 "s"+ 99* 4! 2    EçÓ8FJ767#"''32655'67&''67&''67&'767&''673#"''3265'3#y   )9<*&4 1'!)   @ˆ ,Ó  !        l aX cÿéôÈ:LQf{‡733#"&55#'667##3#53#3#&''67#5347#'63&''67&'#3673#"''3255'675#73#"''3255'675#&'7&'ã    86F     /@   r@   .E@   .; L È    Rp          7Q   Q      1ÿèõ¾ %).BFJPdhlr73#3##5#53267#53635#335367#3355#3533#&''275#735#335&'#673533#&''275#735#335&'#6xc->>l/%44E3w-1B7- J"" !*"# '"# !*"" ¾ ;  ; !   $ %,*;,* %ôÐ%)-26:?RVZ`73533#'3#''275#535'275#735#335&'#35#335'7#73533#''275#735#335&'7#(OM 8F( '1**/,O==O;/e* 4+, '3++ Æ -   '  '  & 6 &7 (  (_ÿéðÏ(,04RZ^73353353#3#3#3#3#5'673&'7##35#5##"''3255#&'#5'67##5&'7#35#k#!8 14,,,,5{   <<#####l   =  Y Ê! .    > +  CR%    ?÷Ë?FJNR7#'67#'7#53#3#7''67#3573#3#3267#"&55'67#'7#67#353#735#35#P ! j(0[i(0*    *x¯¯‰‰‰‰ž   &   1=& AïÏ#3773533#3#3#5##535#535#35#3353#3#5'67#35#accOOf¸bNNa&;;O<•¤s€Ÿ  %{{È "##"0 #  FöÑ#(,04X^d7&''373#3#3#3##5'67'5#5#35#767327#"''67&''7&'3'&'''6Ò ~ +((((-` # !!!!!¾6    F 7  Ñ    K Q   "   ?ëÏ;~7&''67'67676767&'7&''67'67676767&'73#5#535#67767'7&''67'67#676767'7&''67'67#53_ "'   p "' $ÇÇ5     !'" K    "'" ó            !2            ÿöõÏ 733#5353635#35#35#pgê= 3$$7##6$$Ï   °Qÿê¨À73#3##5#535#UM##!!ÀJhhJAÿé­Î73533#&'#5'67#7&'P"   Hš44 zt1$BC Fÿð¨Ì 7'673#53#3#67'5#53e   ,1E"" ##˜  ( 5 EÿéEÎ7'6#5'64  Î "‚bJÿî²Í#73533533#3#535#5#&'''6Rh5%  %™444499999 # Iÿ驯!73#33#537#537#35##5##535#NR'' `/---Æ5$P$IWW7&Aÿé¸Ï*.373533#3##535#35#7#"''3255#'665535#35#A"d   ¤++*Sb*l1mÇ I7* 1$_6']'Cÿé·Ñ 673733#537#35#3#735##"''3255##5##535#53#R#f#+WW33L &/t3À "-0F  33%5  Bÿê°Ï"8733#5367#'635#33535#3353653#&''67#g*^. %:%R,*, $'Ï  WW E54     <ÿé¸Ê ?CGK73#735#35#35#73#735#35#35#3&'73#3#3#3##5'65#5#35#L//*009'("""")U -Ê^C*,A^C*,   H  ÿë÷Ï47'67#535#535#53533#3#3#67&'#67'E 4NhVV____VVh] + U ' .  !L <   ÿêïž 67&'''6767'7'67&'67'5'67'6©' !%M%(=-$ ) " L   4$+#ž !   ? ,  (   ÿë÷œ(,0733##67&'27'5'67#5#5;5#35#.¡%%H*P+0%  3$5""{{{{œ##    K '  (  #4'Oا 733##5#5335#35#vOOOO<<C7#533'67#67&'7'5'67'3255#'67#53&'67#W*ª? 0  ,L$ (  <+ !6 /OU;R      5    !$ =ðÑ $7#53&'73#67&'7&'#'63#735#`Pgdˆ J) " ST'©©‚‚¯    (5hÿìñÐ #73#'63#"''3257&'''6†ah ,   4  GÐ & ~  Y)*-',% &ÿèð¬BH73'67&''667#367&'7'5'67'67&''667#\[Mb" 1  KL8$&  @" 2" 4#%#1. ( Q 2¬&        7       _ÿëªÍ73''67&'67#5367#~     ÍK* $#c!1& ÿð“|&73&'73#67'5'67#67&'81=  !+A  g  7  0 _ÿé¤Ï7'6#5'6  Ï  f"'?îÓ#73#3#3#"''3267#5363535dWŒ´´«   ª2&zzzÓ<   k   ÿëò§/37;?73#3#3#67&'67'5'67#535#535#735#33535#335*¬LWWcX   G%  *(MdWWL::N9‡::N9§C   =      ) %  ÿéò¤27'67#5673#35#535#53#67&'#67'H .!0)#77778LB $L    WZ   < $   ÿéâe $7#"''3255##5'67&'3#735#â  šG  C ;WW//ed  Mj|   1ÿçò¢59=73#735##3267#67&'#7'5'67&&5535335'©©„„¤¯7Q  >    E$ + -&FE¢' $(   +       5 SïÏ$73353#5#'67#535#73533#3#535#% B7R6660p-6Ì v% ))‰óÐ"73#3533#3##5#535#'67#536°7< ""((00, Ð ##''WÿéóÐ273#'6553&'3#3533#3##5#535#'67#536² 0m< 29 !!((33+ ÐH># ´   *Ð  &&!1   j XK ?ðÊ!%)73#3#&'#5'67#535#735#33535#335$·RdC &8*#; 1$KaR??R?‘??R?ÊH    , & Bñ¦ *73#56737'673#&'#5'67#53/6( 1/ %2lS#  $ )ó°059>C73#67'7&''67#32767#"&55#'67#53365#335367#335Zl     %0"D 6=0@AS:Œ8/! !! ",Í    6           - 83ÿèô¨EIMZ7'67#5'67#535#535#53533533#3#3#'#67&'#67'75#35#&'##3&'7L /* '4>##''0%%%%@7*5   >  J000U;2        1   ‘ 8  .ím 7'6'&'73#7&'''6Ô$"‘P')(''5 .,m)  TÿéóÑ,048<7#'66553&'73533#3#3##5#535#535#35#33535#335òt =9-00))00//''-)@)¼M@2 -"Y %  VV 04‡òÏ#'+73533#3#3##5#535#535#35#33535#335‰)**((--++&&)'='½YY37ÿéôÏ(733#3#5353333#33#"&''673¹""%g      Ï!!??, ?  1ö¬6<BU73533#3#535#73533327#"&55#&''67'765#'67&'3533#67'675#*''1v2*s&      YA 7  0; £   %&         Cï¦ '+73#567373#3#&'#5'67#535#735#0;P(9a&5&  $1);;jT:#       ÿèó§ FJN73#735#73#735#'67#535#535#53533533#3#3#67&'7'75#35# VV//UYY44U 4%T?,,55:77//Bm %P)$ V:::§) ) ˆ        8  d  zÿÿÌd7#"''3255#'65535#35#Ì   $  ####dO   #$ $ \ÿèóÏ#'+9=A7'655#53533#&53#3#5367#35#35#'3#3#5367#35#35#¦'=AABB2 .54h63&% &9RR /– MM,0HLL,/Qÿê©Ï(.4:73#3#3##"''3255#535#53&'#53&'367#'67&'    =Ï F  AH W    VÿéôÎ-3GKOSW[_ek73533#3'33#673265#"''67&'#535#7&'3#3#3#535#535#735#33535#3355#35#'67&']"$$)$$   d("‚qWe"5" 2 Á  ++4#"8)! W =C  ) '(     Yÿé÷Í`735673#3#35335#533#335#535#535#533#&'#673#3##"''3255#535#535''67#^  '!  4 **<<  @@++   nP   (6 6  V      OÿéñÏFLdhlrx73533533##5##5##"''3255##5&'75''67'6763775##567&'73533#27&'7''75#735#335'67&'O0 -- 0¢  8      8( -    e      ‹— #    J¢³[ '8 8GÿêôÏ*?R‹7&''67'763533#7&'#5'67#'3533#'#5'67#&''67'76373#&'#673#3##"''3255#535#535"''67#¥      e    U    Q7 Q) "" ))99   ??,,  'Ï      %+  *      7         YÿçõÎ1S[mqˆ73533533##5##5#3533#3#&'7#'67#535#73533327#"'''67&'765#547#3533#67'75#3#3#''67#67&'7#Z"++"    !N       i %tt“ /5 (+)à           "      bìÈ 73#3#535#5#35#335335ØE9À;G(9''9(%È??.ÿçól"7333"&&#"'63267#53&''2Õ (0N%=4,A=! &@—V#&Zl  ÿèïi73#3##5#535#735#35#0¡GeeggF{{{{iK  . (  ÿèö’'+/FL73#3#3#"''3265#'67#53&'#53635#35#73#&''67&''667#A1-7H<   / ""2$>>>>rC    (’>" > #.$      ÿèõÈ#'73#3267#"&55#'667#735#35#35#-¥/ $  / #'*& *}}}}}}È•+   1!#hDDáÑ 7'2'6'&''&'× Mo\OC  3 Ñ    ÿçó‡"&73#3267#"&55#'67#735#35#35#8’* " . #% 8#kkkkkk‡m  L, *  ÿ逯$*735#53#3##5#367&''66''61(a'..12  ' "  ™ ,   &' lòÐ "(73#'6'#3#3#535#35#5#7&'§@G -&&,r FF ™Ð  "& ' ^ & &   Zîƒ7#5##5()OÿèõÎ/6@P7'673353353#3673#&''67&''65#67#'#5'67'5#'655g<;     5\ d O   Î( 0AA0@(8Z , ": #= ‡l%;  B $$ " lòÑ $(,04873#&'#'6'#3#3#535353573#735#335335•O& '!!&e==AjjÑ  & \   -  ÿê”Ð ;?CGKO73#5##53&'3673#&'3#5##5'67#53&'735#'23535#35#35#35#R1Y64  2 -G !+)  ,,,Ð #"'   V L [+-!wÚÏ 7'2#6'&''&'Ð IiXF2  0 Ï  upìÑ 73#'6&'— LV ' Ñ    ÿéòz7#5##5'66333267#"&5Ãc<67 5/ " -zZHGY!-3  +&  vÿéôÑ 17'67333#"''32765#'667#'667#'67#—  JhYG    $:¤   U3()  !!‚ÿéóÄ.73#"''3267#'67#3533#3##5#535#'6†h   % ##((11Ä6 -!> 11 xÿéôÐ:73673#3#3#535#535#53&'3533#3##5#535#'6ž  0**1v2--3++0055Ï   i&&  ÿè‡Ï>CGKPT73533#3#535##5##53#33#"''3255##5#'655'67#37#35#33535#735500(g,5xSE'(     '='Á  %%& J    "  ÿêÝn#(,059733#"''3255##5#'667'67#35#33535#735RS4 :<   )PK66I:…87K:nM '   $  YõÒ-3_c73673267##3#3#&''67#53'67&565#733'67#3#33#"''6735#53&'7#367"& @ )"   ' $]f **< 25Ï      .C   ! + fÿèóÐ %AEIMR7#5#&''67##5'673#67#3#33&''67&''67#735#35#35#67#ñ#     <1 l@@#"  HHHHHH% 5 ©$  ,P   8   = kÿéïÌ)-15T\`73353353#3#3#3#3#5'673'7##35#5##"''3255#&'#5'67##5&'7#35#u2,,&&&&-p   ?8a  4 T Ç   /    > * !  AQ#  ÿéôÐ 73&'73#3#3##5##535# jgç¶¶¶¶²‰‰‰²  V V6# ÿèíÑ+159AE733#&'#'67#3'73#'6655'6367#3#3##5##535#` PG' 8  #RN¶  , \ M žžšsssÑ     9* )HN  > ># ÿéõÐ !-15=A7&''&'73#&'#'67#5363&'73&'#3#3##5##535#z / ‹ 2/! ¤ , l71 O žžžžŸzzzÐ      A '  ? ?#8ÿöË~ 73'73#3#3#3#735#8=C“oooonnJJl     . ÿèõÐ#'+7;?GK73533533#3#&'#'67#535#35#35#35#&'#3&'73#3##5##535#'h''-4 ›  "0-':hhhhhhn V 5U’’’’¡‰‰‰Æ ?  ? % % -   -- _ÿïöÐ#+/3773&'7&'#3#"''3267#7#5'63#53535#35#35#ƒ N  4@D  m ! Ï ) ' ! r:::(((((]ÿéòÐ#)1573'67#'6'&'3&''66''6#5##535#£@  5 $  O ( ! % 4  pGGGÐ         /XV6$\ÿéòÐ'37;CG73&'73673673#3#'#'67#537#3&'73&'3#3##5##535#f   BP  k  )+.  JnnnnrQQQ¯       /  44 ;ôÒ 59=73#5##53&'3533533#3#3#&'#'67#535#535#35#35#„a·dD$4''..C3%+F+ %1=,,$74444Ò%%$     !  6ôÉ#'+/73#3#3#&'#'67#535#535#735#35#5#35#!¹*44=1" +L* -7--)’’’’iBBBÉ9       "  "  _ÿèðÈ$(173&'73673#3##5#535#'6553'#33#53'€  0))((-  WW*f(H   ''1!>y8' Uÿé¤Ð#'73#"''3255'67#5353635#35#35#}   1  ######Ð ¹  /j 286]‰Ï 73533#3#535#'6553'#3#5#111(d*1 a(Æ  E (‡òÏ#'+73533#3#3##5#535#535#35#33535#335Š***&&,,--&&*&:&¼ZZ38TÿéœÉ73#&'#5'67#535'6Ž    É' gd #!WñÍ,048<73#3#"''3267#535##53#3#&''75#53'35#35#35##7L45 33;-l.4 3>:33LL##4$$ Í- & - $$ %  % 9  Uÿè¬Ç"&7367#533#7#5'75#35#35#75#Y''=   Ÿ r#|JMWÿìôÇ,28K[73#"''3265#'6767#'3#"''3265#'667#'6''6'67537533#6733#3#53533­D   LD     PKC")  0Q  ÇP9(% "O8(% !"   † IESJ+HHVÿè«Ï /73#53&'3#735#367#"''3255'7567#ƒJCCJ   1Ï  330  vÿöÈ^7#"''255#'65535#35#È  %  ""#"^R   , ' \‡Ò$(,7#53673#3#3#"''327#'65#5375#35C($-)0H?    / !6'??˜..     $ Xÿð¢Î"73#35#535#53#56#53#7'7q $!H D$Î jd)  ÿéðÒ#,6:>BFNRZ^73673#3#3##5#535#535#53&'3#53'73#53&'3#'3#3#73##5##535#7#5##535#R 5 5`UUggeeTT^5"["‘$\"MM~MMMM~MM0---¾---Ò    ‰‰  W   #  00 0 1…òÏ#'73533#3#535#735#335#3#535#35‹)''-m/)( AEW//3à (   @, 0h8#ÿçÛj 73'73#3#3##5##535##PT¸œœœœ£†††Z    /0 YÿèìL'-373#"''255'675#73#"''3255'675#&'7&'^D   2IE  3@ P LP   P      [ÿéöÏ7CIOag73533#3#3##5#535#535#73533#3#3##5#535#535#3#535#535#&'7&''3336767#"&5''6cKEoppo@ B  N $           GC  9     ! `ÿèöÐ 6>BFJN73#5##53&'3673#&'#5'67#53&'735'2##53#'#;5##;5#¬8`9= (&# )20Nr.  Ð !#)   £ UUF RóÓ'8OSW7'6736733#537#35#73##5'673##"''3255##535#53#3#3##5#'735#5#3 /& l# C33„ ?  g1*Z$$$$,66Ó   >- / ,$    '-eÿí Î 73&'73#3#3##5##535#e;22336­ QW:* YÿéöÑ"(.:FLRbh7'2'#"'#532654'7#7'6'&'#&'#53#3#533#535#535#&'7&''33267#"&5''6Ý /&(j  S"(^%!VVDBBD%  :  A    Ñ <7ß—@     " A 5     "   ^ÿæðÑ3<EKQW]733673#5##53&'733#735#3#3#3#535#535#73'735#33735'67&''&''&'ž n   *eeCC866@=557   &8  g  x Ð   # 4     U        Mÿè¼Ï/37;L`733#3'67#'6553326565#"&55'7533#3#735##"''3255##53&'73673##5#u117 E  -    JJDD""7 0   Ï M=2 .K!   Tÿè®Ï#'+/5;73533#3#535#3#735#3353353#735#35#35#'67&'U###N#ZZ   ENN****** 'Ä   "+&X> ! !     Eÿñ¾Î7&'367'5#m I !5Î6} yFÿèõÏ#)1573'67#'6'&'&''66''6#5##535#—I  <  %  S% # * 9 PPPÏ  ' !  -[[8&RÿïóÐ$*26:>7&'36732765#"''67''67&'3#53535#35#35#“ &   z  ¡$#Ð  74 ?   = UAAA/////UÿêðÑ+/37I73673#53&'3##'3265'##'2655##535#35#73#3##'3267#'67#‚ "%–%e  2  #####C[‘ 5 : / 5Ñ   (L CL ] % >#3 , Kÿð®Ï2873733#3#"''3255##537#3#3##5#535#536'&'K+&(' 9#)8  ²}  iŠš,   z òÏ#'73533#3#535#735#335#3#535#35‚,++2x4,, KPb99>à (  B- 0i9rÿêòÇ'733265#"&55#3#3#'67#535#535#w_Çà  µ*&6! ,&*màÏ #73533533#735#33533535#3353359';Á'':''›'':''¾Q0/ ÿðóÐ/48<L733&'73&'#5'67#&'&''667#3#735#3673#53&'86b% I p B)     *xžžvv 4=Û<Ð  !,     -    I9*    héÏC7335#535#535#53533#3#3#353#5335#535#535#53533#3#3#3vÔÏU PbbP ÿéòg673#67&'#"''32654''67&''67&''67#Hl* #!&  ,<?.(6 4*"" (%%g       5ÿéòW473#67&'#"''32655'67&''67'"'67#>ªI      &2 5* + )!&3GW         ÿéñ£B73#735##5##53#67&'#"''32655'674''67''67#1  {{©¶!¢R %&  .?E,)7 4*) .4£( "!"    !      ÿë‰ÏN73533#3#535##5##53#3#67&'#"''32655'67&''67&''67#211+h*2sOAAj)    $' '   $          +ÔÁ3f73#67'#"''32654''67''67&''67#'3#67&'#"''32655'67''67&''67#‘=       _?          Á               ÿçðÈ !'73#3#535#3#735#35#35#'67&' ¿WhßcT±±‰‰‰‰‰‰' :2^&+ -)È1rP.-% yôÏ73267#"''7&'77&'æM "&k\  ³     '  yõÐ!73&'7&'#"''3267#'67#7'6In &/  9F 9 !/ *­  2 {ïÑ '7#5'6736732767#"&55'67I   &B)$'. # -"Ñ 8&     eõÐ 7&''63&'73'67#/7 92+> Q)C 0  tÐ"   }ôÉ 73##7#53737337#3#7#Ö"$¤ #3/01F3É  |óÐ73533#3#3#535#535#\^^PPjæhNN\Ä      ÿéïÑ#'-37'655673533#3#5355#5#35#&'''65lO LfQVVF™@ErrrrrS" #!*(%“J3-')}   pp/ $    ÿêöÏ!%)-17=73'33#3267#"'&'#7&''3#3#735#35#35#&'''6 •=<  –ÀkkvvQQQQQQ8% ¡..G* -X;  0nO + ,$    |êÏ73533533533##5#3#5#5#"4..Y"œ¯”4Á $4 xóÏ&733#7'753773673267#"&5F**-7;J" )'Ï 0/@   {ôÐ73533#3#535#735#335%PRRméjP==O?à -   ÿæô«#'-3736733#'#5'67#3&'#35#35#35#'67&'J ~5%› *:?g Awwwwww $*+]"" "#’ JN  0 % %    ÿçì‚ 73#735#35#35#&'''6-§§€€€€€€`($ &&=, (‚tS/0%     vôÔ'7&''#5'67327#"&''7&'3¯ `  $¼G   *@9Ô   <*    \íÏ&*.3873533533#3#"''327##5#'67#735#3353355##67#E&88G   2>+  5EX&%7(" +  , '  zèÐ'767&'7''563#"''3255#'67#k !# # )@b  !# Ð&  A5 #0  qèÐ7373#"''3267#'67#73#735#)2   ! * ! %}WW//¾/ . > yòÐ 73533##5#3533533##5##5#"URRU2^//^2¿   sîÒ 7#5'635'673#3#535#@  %IL.$KKA;IÒ:' {íÉ 73#3#535#5#35#335335ÚD;È;D‚+;((;+'É00 ÿèì£ &,273#673#5'675#35#35#35#7'6'67&'5”?+! &5R¤8-A ,',-\(% '&£  ^O B $ % g z     YõÏ$)/73533#&'&'#5'67&''67#67#75#]\ % <%*5 *  %‡ -'!      " fîÏ773533#3#&''67#5365#73533#3#&''67#537#'&&+/  # ! '.'r(&&+/  #-'À         4ÿéòµ159>BFJ736733##&'#&'7#'67#5'67#537#5367#335353'#35#35#35#G<H    9# $5=7KBHN73&'73#3#3#3#5'65#5#5#'#5'63#735#35#35#'67&'v"23..//3 ;++++++ ““oooooo #X#"!"¶  (   I ?& BS;     ÿåö‹ -AEKQW]73#735#3#735#35#35#7#'6553265#"&5'#"''3255#'65535#35#74''67&',¤¤~~<<d  <   žQ '  ‹* &P9 )#( )(Y _Y  )(( 1    gôÐ #'+17=L73533##5#735#335'#3#3#535#35#5#7&'7&'''6733267#"&5†))))*^e##CC##’   5  N     Ç *   ' ] % $       MÿèõÏ7=IMQU7#673265#"''67&'#'67#'65#535#53353537&'3#53#3#'#33535ò    A  R) 37  ²M$:& !' /IK=0 +(, #I81  ©ƒ1H $ ÿäó` %+1773#735#35#35#73#735#35#35#'67&'7'67&'``<<<<<<]dd@@@@@@d 4 X 8 `V< " >V= " !         0ÿæô¸!,28>DHLPTZ`73#3#3#"''3267#533535'#5'6'67&''&'7&'3#735#35#35#'67&'–@`ooi i NNNv(   Q"&R““qqqqqq! $( 3D&&%$¸+   J $ K4:       ?+ añË73#767#53&''67&''3#N5Xo $  [ËfI   #W6ÿçñ®!7#5##5367#5373#3'67&'Úa5W[LPZ P$ %pUBDW  #]  góÏ 7'67&'3#"''3267#'67#\!/ +j%, -&X‡  > 9 . Ï   (# xÿúòÄ 73#5##53&'3#´+I0)zzÄH68J±bñÐ733533##5#3#5#53533#3g122X"™¬##"D11Ð!!.@k}Ð767&'7&''56k !# " )Ð 1    L“wå¿73#735#“RR,,¿H&K_ïË7'673#3#535#53‘1F8 KK?;FF°ÿèêÏ+3;A73533#&''67#3#5##535#&''67##5##5'66&' VY   '`¯a!  !§i@/5 0**) )+¹   0)+0   PC25F!&   ,ÿãóµ',17=C7367333##&'#5##5'67#5367#537#33535&'#3'67&'F<<)  R (HO9K16< - V M!¡ (%()    / =   ÿéô·-15;CI7&'#5##5'67#535#535#53533533#3#3'35#35#&'#'66&'·! b *3>))!!,##,,@,,,,I 5 &.6 3) /*-,Y ,%'+     - ' - !  :ÿçò¶#'+6>FL73&'73#3#3#3##5'65#5#35#'#5'6#5##5'66&'Š +-))**0e 5#####,  ‹c:). +$(" !%¶    =   P  R=X6&(8  QõÑ$N7'673533#3#67'5#'67#5353'673533#3#3267#"&55#'67#535.   $  2I&&.#   $ -¨ '    #  ÿåö‹%9=CRX73#735##'6553265#"&5'#5##5##"''3255#'65535#35#73&''6574',¤¤~~Ž< 4    K S‹* 6$) ))Y _F78GY  )((   pÿèöÐ(06<73&''67&''667#35#53#3##5##53'67&' 8       -&2!Z&5zl9%G >!Ð      U 8((8;   ÿèòÏ%+173533#3##"''32655#'665#535#'67&'#RTTiG ,#! IgR¥ ·#h cC524#E . "!& &"mæÏ736533#"''32665#'67#}6  #,¥S5095=(SßÏ 73#5'5367Ë ÏÁ5  ~q•îË 73&'#•) Ë, uƒ ñ½73#3##5#535#ˆe*..--(½:RR:„êÌ 73353#533­fÌ£ƒ•”‚wìÏ!73#"''3267'6765#'6&'—I$*/'?   Ï )%  uôÎ 73#7&'''6'6«-  8J F BÎ{b" $5Ar óÏ!735333#'67#53655#335&'‚#+? * # #(#6 ¯ C/!  1Rn îÎ#(73673#"''3267##5'67#'672735¬.    $ Î-7 "\< + "=,kìà 73#53535#35#35#Ù111111Ù™3!RT#{èÅ73#"''3267#'67#3#735#i   %& aa;;ÅB +;0SL({æÏ73353353#353#5335#*k(º4II4FN2DF4N~ õÐ(.7#673267#"''67&'#67'53'37&'ê+         - §"+#0\  ))&  mäÐ733#535#535#5367#'6ž 5 aNGGJ+ + !Ð p  w òÏ73533#3#535##5##535#w366.l,3m<<<³CS S5#oôÎ#73533#3#&'#5'67#535#'6**//  "&/ Ä## @@!"  s ðÆ)/573#"''3255'675#73#"''3255'675#&'7&'{5  #<9  '1  P  Ƨ 0 D§ 2 D    k ñÎ$*73'67#'6'&'&''66''6°9 *  #  H   * Î  #" 6 + '""höÏ)/7367&'3267#"&5'3'67'67'&'´    $)"   Ï2   6  ¦b9$ ' 4d èÏ %)73#"''3265'3#3'67#&''67#3#Ö  jG#4 )   MϨ –_$H  5nw ñÇ 73#735#35#3#3##5#535#bb;;;;j+3344,ÇM.,,((móÎ(73533#3#535#7&''6'''6|*,,68*W I S{{%/ '{ ïÌ"(733#"''3255##535#5#7'6'&'¬+  G-,GGGN  K  Ì8p  0†$!v   jôÑ &,7&''63#3#3#535#535#&'7'6´ # ) N--66--  [ Ñ   44+  eëÐ*0673#"''3267#'63533#&'#5'67#7&'7'6ŒS  J  '!!  @Ѐ3q @11 49  !5   píÐ!%)73&'73#3#3#3#5'65#5#35•"!!!!#g -Ð w ,.còÇ773#7#5'275#35#35#675#767#53&''67&'eN   < -@    Ç{!ƒO S;:  fëÐ@V73#"''3265#'6336553353#3#"''3265#'67'67#336553353#'67#†Z U# T  Q  & 'ÐA'  D)  A" qîÏ#'+/5;73533533#3#3#535#535#35#35#33535#335'67&'x1*g*4,+C+9 9 ½LLQ * !  kõÏ!%6:>73#&'#5'67#5367'2&'#35##"''3255##53#735#Û -? P !0(3",,R  R 77Ï  : E 0EV( e ñÏ 0I73533#3#535#3533#7'75#73533#3#535#3#3265#"&55#'67#{,//8~4, ?E=y%   ' Á  -   (   ) m ñÐ!%)/5;A73&'73#3#3#3#5'65#5#5#&'''67&'7&'Ž$'####(n -"""""Q  T .-Ï L!! eóÊ .73#735#3#735#73#735##53533#&'#5'6~dd@@88399(08;;  $Ê-)-.A  &,f îË ">BFJ75#53#5'675#53#5'6'&'7&'3'73#3#3#3#5'65#5#35•(9 R(: L J +..''''.w 3%%%%£N  N  !   )   >  góÏ(MU`f7'673533#3#265#"&55#'67#535#'673533#3#67'5#'67#535#5##53'6652&'¶   K    [C*9  ®         F>./?$   j÷Í#Oaeim73533#3#7'5#'67#535#'635#'673533#3#32765#"&55#'67#3#&'7#'67#735#35#35#w     ?       :n  .  JJJJJJÌ            #R    9   cõÕ )-7#5'673#&''67&'767#'6'3#D  %eM '   2  3ÕJ6        MOÿé®Ñ"73#3#&''67#53655#'6s-  # Ñ&* &7& #YÿèôÍ &,7'655673#3#5##537733'67&'„@. 34,6*< 4‡*?4 15c VFHX+L[  ÿéö«@DHMQ73533533#3#3#&'#3#33#"''675#5'67#535#535#5#35#3&'#35#-'1&&&&A3( &2>##'j111[ =hh         $    %(VîÏ#'+>73533#3#3##5#535#535#35#33535#33573##5#'6556///((--00++/+D+y )H .Æ 55?66 /ŒóÏ#'+73533#3#3##5#535#535#35#33535#335‘&((%%**++&&&'<'¼ZZ39Yÿé¨Ð#)73'73#3##"''3255#735#&'''6[ M?  %%³ EG  D#< kÿèéŒ#'73533#3#5##5##535#735#33535#335t+//8&"4+-Q""4&{0 ? ## @ R‰ òÏ+173533#3#3#3#3##5#535#535#53'#535#367#$'',+$$++))$$+*$$'¼: UÿêóÐ<@D733533##5##5#533533#3673#3#5##5'675367#535#35#35#€-!!-*## 4 AG 93*GGGGÏ.  ` I  X4Oÿë¬Ï&*.73#3#3#"''3265#'67#53'7#53635#35#z +$     #!!!!ÏO4%6 O */ ÿèôÑ37;?CGLfj733533#3#3#"''3255#''275##535#535#53#335#33535#335#7&3#3#3#"''675#735#KCAA!Mb  # 1?L^J >>UCCc88J;…88J;#o›DQQi)s +EwwÑ  4   $24  )  &    VÿéõÐ &*.2Ia73#'635#53533533533533#3#7#3'#3'#33'67#&''673533#3##5#'735#yoz •s !%#,   ?"# "Ð P' 5)   ]ÿçôÐ&FJNRV733533##5##5#53&'3##'3255#3&'73#3#3#3##5'65#5#35#'3#„".."''  )_ N? %3Ï  ˜  „    U  ROÿê´Ï+/>DH73&'73673#3#3#&''67#537#535#5#35#"&55#'67#333535#W $(   ! $("16   -666´     ^   ^ 6     > RÿéôÏ4:>BT7#'6553&'73533#3#535#3533##"''3255#&''3#735#76767'67&'ô€C@<C  A<<  !( ¼M@3 1>Z '%%l  g1(  XÿèðÏ6B]v7&''67'6776767'7''67'6776767'73#5#535#53''67'6767767'7''67'677767'     O     ††ƒƒM     Q     ™  "   " :æ`D  " %   # OóÑ(9PTX736733#537#35#''673##5'673##"''3255##5#'7#535#53#3#3'35#5#O!& m'!C •00…  ”; ,.3+\ ##'R::É    C3 1 .=    3-tÿöØy"&*73&'73#3#3#3##5'65#5#35#C &x    K ‚ ïÈ159=AGK73#3#3#&'#3267#"&55'67#5367#535#5#35#3353353&'##3†c)0 -"   #$8    -'È2  $ # 2$J RÿèöÐ %+06<@DHL^c73&'73#'3&''6673&''667'6''6''67'673#3#3#735#3&''67&'#367’ ."    c    %  c + &&%%-- ?~ $#/*# » !4  0>. 0"( , *   Ið± 4973#3#53635#35#73&'73#3#3##5#535#53'#67#8'?@R --..M./%622..5!B ±' &X! > 8      OÿéóÐ'+RZ^b73533533##5##5#3533533#3#535#35#'3533#3#3#&'#5'67#535#535##5##535#35#[#-$$-#EU g    •'''''Á ' 928Z [!0 ^ÿèùÔ /5BJRX^d73#5##53&''67&'3#35#535#53#56&'73327#"'&'37'737''&'&'7&'¤:k= :  ''^((,>‚;  l 9      D   Ô         B=I  )")8H   SH   S      TÿçøÒ#6HLRXov73&'73673#3#537#35#35#35#'33#67'7536773327#"&553'#3'67&''3&''67&''667#[.  'D3F@+  f * e  ^ 5 ? " ,!  ! 6»    NN !   9 >: C, 0         \ÿçóÎ!%)/37VZ^b733533##5##5#53##'32655#535#5#'##535#35#3&'73#3#3#3##5'65#5#35#‚+""+&&n  2 3"""" F (Í   © cA # 2A|½ #    ?    ZÿèóÇ48<@7#67673&'73#3#3#3##5'#5'6555#5#35#ò{  A  bÇA+$(  n  kt 5, 7>`h,,ZÿéùÒ;?CGMSci7#67673&'73#3#3#3##5'#5''6553'75#5#35#&'7&''33267#"&5''6òz =  A7  @  À="  H F=D2 6;\  D$      # _ÿçðÑ +AEIMQW]7#5##53&'73#3#353#5335#535#'6'3#3#7'675#535#3#735#35#35#&'''6ðm=& A   @5 qqMMMMMM;( À         ;T= !    ÿéóÏ#'+73533#3#3##5#535#535#35#33535#335cggUUkkggQQc&==QA’==QA¼d$$d<>ÿçì; 7'6'&''67&'Ê "'„% #L&14M4$ "2;       ÿêö¯37;?C73533&'#'67#53673#&'#3#3##5#535#535#35#33535#335;;6:2 &E  w6) ;KKffffKK;88L9…88L9p       >  > $ ÿéí†#'+73533#3#3##5#535#535#35#33535#335;<;;HHccffJJ<77J577J5y  F  F & & ÿé™#'+73533#3#3##5#535#535#35#33535#3356991133;;//60N0  E  E % %  ÿçó©159=A736733#&'#'67#3533#3#3##5#535#535#35#33535#335? ‡3" +J& &-8::LLggggLL8::N9‡::N9–     @  @ % " vïÏ;73533#3#&''67#5367#73533#3#&''67#5365#'""'&     +'k$**5+ $  ##À       ÿèóÑ=AEIMSY73#3#35#535#535#53533#3#3#35#535#535#533#535635#33535#335'67&'GG''""%%&&""((G1æ/$6$I( %v))Ñ   E  E ! “‹@' \   ÿéˆÏ7;?CG73533#3#535##5##53#3533#3#3##5#535#535#35#33535#335433*j-4ySHH1..++1166,,1-G-Æ    ::  òÏ#'+73533#3#3##5#535#535#35#33535#335“%%%""''((##%$4$¼ZZ38 ÿé¯Ï#'+FW[_73533#3#3##5#535#535#35#33535#33573&'73#67'7&''67##"''3255##535#35#  '  (    H   """""»^))^<<[   Af  .} 1^ÿéóÇ 'B^7'6553'#3357#533##"''3255#3567#533##"''3255#'#"''3255'7567#53€\\V,7U #, ,33     4 —3F5 6?_0 7  A      \„Ð#'+/373533#3#3#353#5335#535#535#35#33535#335 444++11d33,,4-H-È /  /^ÿïºÐ 9733533#53'&'7'63&'73673#3#67'75#535#yN  L D  '-$Ï:;;2X  ÿèñq#'+73533#3#3##5#535#535#35#33535#335:=<>Q=Ž>>Q=e  ;  ; " ! ÿçôb#'+GKOSW73533#3#3##5#535#535#35#33535#33573533#3#3##5#535#535#35#33535#335///((,,11((/&=&,//))3300)),)@)\77  877  -ôÐ59=AE_ekqw}ƒ7&''67#'736767&'73533#3#3##5#535#535#35#33535#3357&''67#'73767&'&''&'7&'''6'&'''6I    "!!"  *  N      Œw–  2- )5  E  E & )   0&& ÿæôÐ!73#3533#3##5#535#'67#536j v<JJbbqqS:DÐ"** 77 "ÿæòI,767#53&''67&'767#53&''67&'/ E[   r  Kb  0          ÿç|!733#7#5'75#'67#5373#3P%%'<@1 #:C R#   ]ïÎ:73533#3#&''67#5367#73533#3#&''67#5365#'!"''   $+'k$)*5-! %   ##»          jëÏ73#5#535#535#53733#3#3##bNNBBJJ(JJCCNNÏd     7ðÐ!8PVZ735#'67#53673#3533#3##5#767'67'67676676767'67'67'673#T%' '. %‡$   ´ "   !$uEEc Z!"  #W ^ÿé­Î+/373&'73#67'7&''67##"''3255##535#35#^ (  G    ´    =d  .} 0 ÿèóÐ %73&'73#3&'73673#3##5#535#\ WÉB  9 @hUUXXj¯ +  #??#ÿéuÅ 7#'655#3#5##535#p? ?--ÅO*4!KK+2mmF4GÿéšÏ73533#"''3267#'655#_  %% £,,…w"S3 1H"Fÿë¦Ð(.47'67'67676767&'7'##'3257&'''6p     '  - N'!3#  S > "# ZÿêšÌ 73#53&'3#3##5##535#y@ >>>>=Ì @U U7%)ÿê÷œ73##53#67&'#67'H ,ÅU 'P# œ.  )HW @ôÊ"&7#3#67&'#67'5#'6553#ä«´[    N %‰‰Ê+ -'  1%22 ÿèòÏ #GK73533533#735#33533535#335335#3#67&'#67'5#'66553#":(:Ã'':(''':('½ÀS    D%  #à I- ' && -)  5$ 8eké~73#e„„~UïÏ 73533##5#UBDDBŠEEdd\%äÌ 73353#533–&ˆ&Ì”wЇtHîÏ $73#'6333267##"&5477#whr  f;  (8BÏ 1 & 0NöÇ73327#"&'5#3##5#535#T}1++**$ÇQ((*<&@>;UU;JîË-7#"''3267#53276767##"&55'753753à  " 2 «M(MHO R+&3.[ ðÎ733##"''32655#53&'¯--   TT;Î'm h'!"LíÇ7#53#3#"''32765#'65u#›fU  @))´%[  0A)&6Né 7'66553'#3} €XXv5( 'JL9&U"íÆ73#3#3#535#535#YŽ=77C˜A88=Æ.==.MìÏ$733#353#3267#"&55#5335#53’DD&:! 7#EEÏC3E' ,E3CYìË 77'56#"''3255##5•! !r %Ër € jž±RíÈ73#3#67&'7&''67#ett›R&( 3:2È+2 $ 0MðÏ "73&'73#3267#"&55#'655UA <‘q   /"ªP B" ('ZíÅ"735#53#3##"''32655'67#'t ATŠ"$$   1.>ª44A :, #RðÎ735733#&''67#7&'Z8DE'%%1 ;8k  ˜56"-%#/DE KðÄ73##5'67#&'W”9/"EjÄ †h#=,SêÐ7&'3#&''67&'767#™  /” - / `Ð$,   %[ çÏ7#5##5##535335#35#ç)';;'';))§k@@ j((J777O ðÎ!73533533#3##5#'67#535#35#V#$((++%"(*#7$$¥))))+EE- #+++QèÏ%73533#3#3#"''3267##5#535#535#R>DD99C  .??22>¶2 AAQ èÐ!73#3533#3##5#535#'67#53‰OV $44;;HH9 !)Ð !!&&M߯ 7#5#'665535#&'ÜO OO&!%Æh4( 'LE2S%##bèÏ 73533#3#735#b.DD:|TTr]+Z4JóÈ #7'6553'#33673265#"&5r‡ccN% %+ ! ,ŠG#ED>,#  "  KðË73533533533##5#3#5#5#KDvŠv++..--HH[n555DöÏ 473#'6##'3267#5;267##"&55'753753noy } $   = Ï <962  6!]éÇ7#"''3255##53#735#é  d%AAÇ  …¤·,Z5GíÏ"&73533#"''32765#'6655#7#5##535#Q&  & œ¬##v! J["32%© ªŠxLîÏ$*7'673'673#"''325''67&'x   j >   ( k  ¡ "'  i  P, $"") *"]áÎ 73533#735#33535#335]97„%%9#\%%9#§''‹O(((d)))bâÐ 73#5365#35#”@€*=XXXÐ šš L..F3P ìÏ&73533#3#&''67#53655#'6t55?D&* 2@G# È'' "%#"  M ìÍ73#3#3##5#'6yiUJJLL Í "* 'D òÈ "73&'#'3'67#&''6° G-  5  ÈCbº >+'L     "N ðÏ73533##5'67#7&'&'W>@@':u  ¤++„f3!<<  E$+ -#QïÍ!73533#3#67&'7&''67#535#]9>>ER#% 385E9«""* ! (JòÅ7'67#53&3#3#535#¡ , NRk !%au3B”>.‡& ((XéÄ 73#735#3#735#X‘‘iiCCĦ€R0[çÅ7#"''3255##53#3#735#ç  dPPBBÅž  †¡´%F&KòÏ"&*7#"''3255##5'67#53673#35#35#æ  P #2Vb PPPPh  *n   #0SðÏ%73#3##5#'67#535#5365#'&' "((.% T .   Ï*EE+*Q**e  H ôÑ!'-7&'3##"''32655#'6765#'67&'¥9—0   (    Ñ "n i9' >' $ " QñÈ '73#3#535#33535#"&55#'567#33275Q 6-’,1D?j   Q È‚‚ˆ 17 (,3PìÎ73533#3#535#3#735#PDDD9†9DwwOO´?L&G äÊ '+73#"''3265'3#3'67#&''67#3#Ñ   Z.+B 5    ]ʤ ˜ d&J  6uFáÏ!%73#"''32765#3##5'635#35#w^  SC6  ####ÏvYY ^ 77KñÏ "(.73'67'6773&'3267#"&57'6'&'€6 , &    3 n  Ïb8$+KN=  –   ^æÐ73#"''3255##5363#735#’I  `( <<Ð Œ  t‘¤,W4[ãÏ"(733#"''3255##5335#35#'&'7'6•8  Z6"ZZZZ x Ï5r  ,‹':g   NëÅ $(735#53#3#75#35#"&55#'67#333535#W,55+ŠN*c Kcccœ……R!" !aI ôÌ573#&'#5'67#535'2#533#"''3267#7#'66Ø  E6# (& $7D5@n '   , ( Ì  r+  NðÆ773#67&'#"''32655'67&'5'67&''67#W9 ' ,## %  )>Æ        N óÐ.47&'3533#67&'#"''32655'675#&'Ç  `>DD     " )>  Ð %%2( #1  I êÐ*.273673#3'3267##5'67#735#53&'355#z !8F1% &/5># _Ñ 6 32 TîÇ !-73#&'67&'6'&'63#3#535#Y••  k  "  <‹@F˜@9Ç      <$$EóÏ9?7367&''667'#"''32654''67&''67&'76'6µ  4          Ï4  &- 96( 3      " PðÓ 73&'73#3#3#3#735#PB F ‚‚‚‚XXµ ?ZéÉ#'+/3733#"''3255##5##53&'767#35#35#35#35#ZŒ5  +)?  j))=++=))=++É q ((+„   E7NìÇ73#33#537#537#35#3#735#T–RFž($15>81‚‚\\Ç,?=?IòÏ273533#7#"''32655'75#73##5#'6556P  "– ">)®!!&5 ) ,, "gg 1+ &)OVëÆ73#735#3#3#3#535#535#`||VV†;44D•?339Æ@;LñÏ73533#3#535#'63#735#t88F¥L$ ~~XXÌ OD!KèÑ:767'7&''6&'''63&''67&'767#'6‹!  4:!D8 %=   *" 7 %Ñ          F óÒ773&''67&''667#3533#3#3##5#535#535#‚K )!#0'  %  C'<??77FFBB33<Ò      CO îÏ#'+73533#3#3##5#535#535#35#33535#335PEEE;;GGFF;;E((:(b((:(¾WW33QòÈ$*073##"''3255##53##'25655##5&'7&'Q¡¡G  ” !)aÈ~  g„–~ g†˜*K öÐ26:@735333##67&'#"''3255'675#535#535#33535&'`4::     #(55II4F'''l Á""     #%  KïÆ'-37&'#"''3255#535#535#533#6'&''6Î    Gs\\fzHZ  /  (m    GB    PçÉ$(,7#"''3255#'6553533#3#535#3#735#ç  b!U!FF&&É¢  ‹C:) ,3M(23EïÒ!%)73&'73#3#3#3#5'65#5#35u+ 0911119‹ >,,,,Ò q &/OîÏ+73533#3#535#'67&'3533#3#535#_655C—@6 ]X566D™A5½*    #N îÎ4973#&'#5'67#535'23#&''67&'67#5367#Ò C2 %# .A/:[# . % '1#- Î  Q     - BôÏ#)9733#'67#5367#'6367#335&''33267#"&5r?2B0 (.=6  #)<*     Ï A0#A  K4  & P çÉ!%7#3'67#&''67#5#;5#35#çMJ\SB   ,VTÉH @2  H$$$$LïÍ)-73533533##5##5#'67#53673#3#735#Q!3$$3!$  '2;LU TiBB¼^    L'GðÎ!%)73#3#535#'66556535#35#35#á 6;;.j)8  T !FFFFFFÎrr=3 .<2#C1/M ïÆ 27'66553'#335#535#53&'73673#3#3##5#t ‡aa_3((. 0))443“ 6* 'L3"l   EðÏ)E73&'73#3#"''3267#'655#73#'63567#533##"''3255#J+&   c8? % %;  %° O"?=& ';0  Q  * 'VëÈ"&73#"''3255##53535#335#3#735#Ö   mAH//HH$$ÈH[  D\nHH7&55E ëÉ%)-373#3#"''3265#&''67#'67#735#35#33#_Of  .   " %ZZZZCUÉJL3   - ) P"M ñÏ-373533#3#3#3#3##5#535#535#53&'#535#367#W>>>H!&B99EEEE99B"H>2*>¾  7 PôÑ;7'67#&'&''6733#3#353#5335#535#'6t `:   %L>[7DD"'HH u %   ''(  UêÍ'+/37;767&'7&''563#"''3267#'67#3#735#33535#335…    +Q    Aˆˆ&&8)a&&8)Í(   D4 $0&JR21BøÎ-LRX767767'7'#"''3255'67'67'6367#"''3255'67567#&'''6á      8€E    0˜  3 Î (  5 .= 4* e   KêÈ %+73#735#3353353#''67&'&'''6UŒŒ(<Z5ÈF#####;mh     HïÏ#'73533#3#535#735#335#3#535#35#X;88H§K;'';$nr†ZZ^^à +   ?, ,b C JïÌ 77'2'6'&'7&''673#3#353#5#5335#535× 9RD< _  ?   dE73533#3#&'#5'67#535#35#33573#&''67''667#I###    !#" ..    ¼> 78>?"""A$! 0  PïÏ#'+/37;73533533#3#535#5#35#33533535#3353353#735#35#P100'Ž)1[**i*tWWWWà  FF  ( 'E) ' EðÐ#)/5;73#3533533##5#'6553&'5#&'''67&'7&'¨;€'LC*'A  R  -0Ð  .. 2& '.Eo G ôÈO7#'7655#'65535#35#73#67&'#"''32655'67&''67&''67#Œ   0Z&       !È   !$ %.Y4#W$W        M óÏ.2:S73533533533533#33265#"&55##5#'67#5##5##5#"''3255##5##5353P    0 RTtˆ  -º   $$ ''+(  ::,<LíÌ#0=73533533##5##5#3533#3#535#7&''67&''6L"4$$4"6::FœC6  \   ¾`KK\    L òÑ-2=AHLR73673#3#3##"''3255#535#5365#53&'3535#"&55#567#333535#&' "(90!!   ssi08+'=l  "N lll  Ñ   U   U 8    7 5 M ïÏ 0J73533#3#535#3533#7'75#73533#3#535#3#3267#"&55#'67#e044?‹:0 ( Q!PQ˜0  - (( /   )  ! JñÈ37;?CIM73#3#3#&'#3267#"&55'6765#537#535#5#35#3353353&'##3O3+HZ  P )  2* "30*2Z((SH13==È .  + (  . AM ðÌ)-AEKP73#3265#"&5735#'#326565#"&55353533533#3#535#35#'67' G7   &&5   %.!(%%-£*!3(( K /Ì/  &0  ES &   J éË(9?EIM73'767#533'67##"''3255#'67##"''3255##5'67&'3#735#O<  `1   - $7–   h/  3 (@@¨       $S  ?Td   ( IðÏ#'-373533#3#535#735#3353#735#35#35#'67&'X9<Ç(   :W> " !   C ïÐ-`733#3'67#73267#"&55'75#'65533#267&'#"''32655'67&5'67&''67#‰JJP?46  %$ !8}=   #&   $Ð       ='& %LA           JîÏ ,9F73#&''673#&''63#3#535#&''6'67&j(  P/   MŠ>GœC: `   Ï     #QQ     &  CòÏ -L73#53635#35#73#&''67'767#'63#3#"''3267#'67#53&'nL ))))V1       4&8,    +Ï II '-='   &0%P ïÏ6:>BFJ733533##5##5#53#"''3255#''75##535#53#'35#33535#335'#u-))-%%w   %-(.A9‡<8&&8)a&&8) Ï  k9  $ 8H CC 2 % A AñÑ=CGKQUi733673533#3##5#535#'#"''3255##5#'655'6367#35#33535#73573#"''3265#'67#f   ""      *  L  ÑB%%  4 33 !)(  ,; ^.$ M ïÑ Y73#5##53&''67&'3673#67&'#"''32654''67&''67&''67#53&' CzE  F *)D      "14%-+ #9+Ñ             F ñÈ &*773#735#3353353#3#7'5'67#735#67&'X‰‰%†¨¨{A # #!WW2   !$È3. ,     #    MíÑ !%)73#53&'3#735#3#735#3#735#35#3#œH B2ŠŠbbDD!!+wwSSSS!››Ñ %A ' ,5!AôÌ"&*.>767&'76'3'67'767#3#3#735#3673#53&'Ô  6uB6 -*@@yySS  )—$ Ê4.  " 2&   C ÷Ë"(9JZj73#3#"''3255##5##535#&'''6&''67'7'&''67'7''7&'7'''7&'7PE@    ..@FpB # e    8    F    7    Ë} h€€~  *         &  KðÓ"(.2673#3#5##53&'#53635#35#35#'67&'3#735#†L?IF1(aaaaaa 4E€€\\ÓN N   0 .L ðÒ"(.J7#53673#3#5##53'35#35#35#'67&'3&'73#3#"''327#'67#“1&=2E~L#RRRRRR  <U? 8WR  C  $~FF:-       P ñÑ !%6<IQZ`fl7&'6'&'6'&'63#735#67&''7'&'733265#"&'37''367'7&'&'7&'Ï  '  +  jj   R 47  U .   >  Ñ     !F$  .  ! ##); F7B     LôÒ#'+/3_e7&##5'67&''673'67''5#'#33535#33#67&'##'3255#&'#537#537#'6»"†     NJ  R)''''))M„E    ! xcfs3 &©&%  !        3  EìÉ#'+IMQU[ag73#76767'7''67'67#735#3353353&'73#3#3#3#5'65#5#5#'&''&'''6P—      ,4T %#   É1  "   % P C ñË#6:>BFJVl73#73##5##53#5##5&'7&'#3#'6553&'75##5#35#33533567'53373673267#"&5KPPULL $–&1 ] 30(ƒ D.X3 "     Ë#$$# ) . 7 0    ’ÕÑ 7&'6'&'67&'6 y N Ñ    ÿö”Ê 7'7537537'6'&'Ž7G +   W  % ·³±­;/ .6.88. ÿò”Ï"4767&''67&'373#'67#&''6767&'*   " 1 8?!)d %*Î     L 4! ',  2, ÿê£Å573#7#5'75#35#35#75#767#53&''67&'o  B -?    Å($˜$$[%`$69mÿêöÎ !%8<@7&'67&'67&'63#735#3267#"&553'#37#3‹ 3  6 \jjFF)  3*o?.Î    4,b W:* ÿæ™È )373#735#35#35#'33#&'6'&'67&'64XX4444445s†G      i   ÈaF & & :b   ]ÿó©Ð &73#53635#35#3673267#"&5tE   Ð bb 5>4     ÿæ˜Ï#37;?C73533533##5##5#3#735#73#735#3#3##5#535#735#33535#335 ( (88688Su2999900O0  --&L  . * ÿð¡É#+/7;CG73#3#5##5##535#3#73#3#73##53#=##53#=##53#=#w4@..@2 !!>""C&&C%%G+ e) 3+É*;;,0 ]bZ 11]Z 11^Z 11 ÿé—Ï '+/3DJPTX73353353#3'73#3#3#3#5'63#35#5##"''3255##5&'''63#735#" y/2++++3u 2!!!!!g  ]O   ::Ê   /    ? ,CR   ! eôÏ 7&''63&'73'67#€14 65*: A? 8  vÏ    yêÑ &73'67#&''6'3#5'67'&'Ÿ=S K6  %>"%  Ñ2 $  V  (  n­Ï73533#3##535#35#n'¥***Sb*k0oÿíôÏ!)-157#5#'67##535333267#"&53#53535#35#35#ë1- . …   º.!(1/'(  LLL;;;;;˜ îÎ"73533#&'#5'67#7'6'&'   ?0   MM9=%LlÿïóÏ 473533533##5##5#'67&3#3#3#535#535#q##= + EO((1t0''¶'   qÿðôÒ#'+73#3#5'67#53635#3#53535#35#35#§=EEY  &33L €    Ò:&  X9LLL:::::oÿïõÑ)-5=AEI7#53&'73#67'7&''633265#"&5'3#'3'663#53535#35#35#œ&03>  +08   ` †  ¬    !  #43 'AAA11111 rÿêòÈ#+/7;CG73#3#5##5##535#3#'3#3#'3##5##535#7#5##535#7#5##535#{p/6$&8/I:9%%E&& C C È .??. (  l lO?l lO?l lO?fÿéõÏ%;QW]7#'6553&'73533#'#5'67#'3533#'#5'67#3533#&'#5'67#7'6'&'óo ;  6   --"   !W 6»Q96 -7a %    Z!! +( /   ÿæõÔ)-159FK\bfˆ73#3#35335#535#535#533#5##535635#3#3#735#3#3#5365#5#35#"&55#'67#333535#3&'7&'#"''327#'67#5'6C $^'!0Ã->>55.. Oµ>D¾>=g<š2   #h-ššš o (   8A 31 .Ô  AA ?#$9=6 * ??! +    ’êÎ 7'67&'Z),c' ##Î bìÐ 73#'6'3#'3#7&'–NW =/” Ð  !$^WR Gÿô±Ð#)/7&'3#3#7'75#535#5'6'6'&'z  =""(+6*%% !4>  Ð= A  dvÿéóÊ.373#367&''66''63&''67&'#367zrr1   %!#  h      Ê    #L    \ëÐ 273#"''3267'67&'775#'63533#67'75#ŽQ  #  !F m#)1(#ÐF      " '[ÿé²Î73#&'#5'67#535'6©   'Î ) r_!+$d~É 7#3#3#535#35#5#~g AA É)e*) WÐ 733#3#533#7&'''67..7t*0  6  Ð 97    O`òÏ,73533#3#3267#"&55#'67#535#'6o77D/   - ')E&Ï      oÿéóÏ'/373533533##5##5#3#"''3267#'67##5##535#o++w  ,* $'sJJJ»9 "/$EP R7&iÿêóÐ;AG73533#3#5##535#'67'6776767'7&'#"''3257&'''6p4557\647   "    '1 ·)'x     1  !   HPòÒ-187#53&'73#&''6&'7#633267#"&5'3#''66€&=?"6=S + !   *&%  ²      "+*&  S÷Ò)-BHN73&''27&''667#'#5'673#73533##"''3255#'67&'‡P &%  FL  A?? A&R Ò    XA V*     Xÿè®Ð&,2767&''67&'3533##"''3255#'67&'h        9  Ë    XT Q   ŠòÌ#'+73533#3#3##5#535#535#35#33535#335Œ())%%--**##(#6#»XX48/ÿíô\ +7&''63#3#3673#53&'735#535#Œ(5<-!- 4_&HH&»&EE&\           F‰Ï 73533#3#535#'6553'#3#5#011(d)0b(Å   M .\ÿë’È73#3#"''3267#535#\4!   !"ÈK(H" ;J)bÿéöÐ "O73#'673#'6&''&'3533#3#"''3255#&'#5'675##535#"( H%+$85;;8  &' " (!35Ð      ) $9: $.?Zÿé¬Ð(,073533533#3#535#35##"''3255##535#35#^ R+   #####¶ . ) ** ÇI`  LcrII9$93  iÿêòÎ!'-39?V\7&'7&''67&'76'3353#7&'7&'&''67&'3&''67&''667#© %   MW} E   N+A  "   4Î        LM^H   "          VÿïºÐ 9733533#53'&'7'63&'73673#3#7'675#535#uS  U H  "%'- Ï:;;2X   QïÏ#'+/8BKW73533#3#3##5#535#535#35#33535#33573#&'67&'67&'63#3#535#/++''))11((/)?)ee '  *  DX$,i+"Ç :   :  :       $ añÑ "(,04873#'6'#3#3#53535357&'3#735#335335˜KS '""&e==q 'jj   Ñ +h / 0†óÏ#'73533#3#535#735#335#3#53535#%&&.m.%$ @EV//33à (  B, -fJ\ÿéóÏ*17Hd73533533##5##5#3&''67&''667#'&'333#"''75#73533#3#3##5#535#535#`$%&&%$F/     $1!''0/ +%%%&&%%%Á      1T F      ÿéôe).Y^7&'#3#3673#53'735#535#5'63&'''#3#7677'7&'735#535#5'63&'² ** m ,, &,  f &&  09 )) ";e               `ÿéõÏ159=AGMShn73533533##5##5#3533&'73#3#5##5##535#5#'#35#'35'&'&''673533##"''3255#&'i #### !+ .('+R'%F  G G À    D E "  E     *' %    ^ÿéóÐ MQU[_7#5##53&'73533533#3#3#'#3#33#"''6735#5'67#535#535#5#35#3&'#35#íj:9*#  %--D  &  "(> 5 ;;¿'(        '     )$fÿéðÏ(,04S[_73353353#3#3#3#3#5'673&'7##35#5##"''3255#&'#5'67##5&'7#35#r"6 .1****2u   :9!!!!!f   : U  Ê! ,    ? ,  CR%   YÿéöÐ]aei733533##5##5#53&'3353533#673265#"''67#53#3#367&'#'65#'67#535#35#35#5#ƒ&%%&$$] s K    .0 ;  <  Ð  '$ E. ( s+ G@4* )-!  22^ÿîóÍ #'+/37;K73#735#33535#3353#735#33535#33573#735#33535#3353533#3#535#orr/N/mAA  !  BB  !  y999B•A9ÍF* ( %K-0+K-04 HŠÏBH73533533673#3#3#35#535#53#5673#35#535#53'#53&'367#   111&j /// *%Î     - 40  -  'Wÿé´Á7#5##53'>&'¯,   Áš‰Š›'O0&  #*0 ‚ÿÿÕ•73#3##5#'655#535#35#‡K •+GG)  +++‹Ç 73#735#35#‹<<pBP"lÿéëÇ &7#5#;5#35#'673'67#&ëxDC A0 M8 ÇLL****‚% 2 ; Uÿë¤Ì73##5#'66556š . "Ì %††=3 3C8_DéÏ 73#"''3267#'67#'67#'6ˆW   /)"  Ï R::' !3)  _ÿéóÏ ;7&''63#3#735#3#3##"''3255#535#535#'2¥ " " " 0(( hhFFX 300;;   ==448Ï  )     Xÿè¦Ï /73#53&'3#735#37#"''3255'7567#IDDD    +Ï  330  kÿèéŒ#'73533#3#5##5##535#735#33535#335t+//8&"4+-Q""4&{0 ? ## @ RZÿë’È73#3#"''3267#535#Z6 "  " $ÈK(I!6J)ÿéðÈ(7#3#3#3#67&'#677'5#535ÜŽ„„„„Ÿb  &R / 5/) #(È  PM   VlÿêìÎ73533#&'#67'5#7'64–]D M# 4¯#3 -s[[? N\  lm +F-·?73#Fqq?9Ãd 7&''6w&  % 8d:Ã` 73#3#3#@||oo ‰‰`9ÿñÅa73#3##5#535#Dv1<<<<1a44@ Àc 73353#533v"€"cH9KK97ÿðÅh73#3##5#'67#535#5#Bz""%" !N$h55& 5Ìz 73#53&'3267#"&55#'6659ˆ:3   )  z (*  1ÿöÊs 73&'73#3#"''3267#'>5#6D :YO  >))[  . -ÿ÷Ãv73673#'67#&''6767';/EK1 ((g +0 e (& ,):Æ`73#3#3#535#535#Eu0--<Œ<..1`1Åv&7773267#"&55'75'75'6§68CE  ) 7:,/ÿï¾h 7##53#5##75#'#33535#u%€%7%%%%%%% UU /Z=ÿð¿w"73&'73#3#"''3255##5##535#=5994  "17d .  BB1A6ÿîÈk73#3##5#535#&'7'6@}4????5V  k.--. ;ÿóÈi73##"''3255#3#735#;   c BBiK  G/8ÿêÈx%7''6767'3533533##5#'67#-2  V#$##&% p   :..+1ÿüÐy#)7&'3673267#"''67''67&'€  & #   ~  y   7+ 3  5 6Ée73#3#3&'73#535#535#;‰>55 “>009e 1ÿïÐx!%73&''67&''667#3#735#fG '*  ) 8%jjDDx    .50ÿôÌs27&''#5'6767327#"''67&''7&'3¨   C  u/     s   ^F  #  =ÿøÇu 733#67'7&''275#5335#35#v22  /=44!!!5 u:  :)Gÿé¿}$7#67'53&'73535&'76¶]  - 1KKK  $iC$  r      BÿþÂn 73#735#3#735#KkkEE€€ZZn/.3„Õa 7'67&®   /4/ÿñÎ|'159=A73#&'#3265#"&55#5'677#5363533&'#35#33535#335xB * * !1  ,E,| 3  / *  ! 8ÿïÄ|"&*73#3#&''67&'767#53&'7#735#35#Hr8:# ' _:/NNNN|;    % 3ÿý¿w%73'67#'6673#35#535#53#bA  8 ! %%Y''';w  #    C+ÿðÔt,2873#3#"''3267#'667#53&'7&''6&'&'X*&    [      t  9 %) (      @ÿíÈz#'+73#327#"'#7'56'#55#35³ 4- 3. C,gtbPPz       ; 1?? 3ÿïÉy%)-373'33&'73#67327#"''67&'#3#735#'64M 4      O@@+$&f  % " 1( 8ÿéÇ#973673#5##53&'7'23&'767#53&''67'¹ #  k @"& ) D\  )  !! &  7     5ÿéÁƒ!%).73#3#3&''67&'7#535#735#35#35#67#Cw:<>:>>o0)55;ÿïÉ~ $73#3#53&'#53&'367#3#735#35#‚6&Ž$6*$jjEEEE~ ) $E* '1ÿðÌs%+7&'73#3#535#535'635#'&''6F ,,"W",,!/33@   s  55h6    5ÿéÉy!'+173533#3#&'#5'67#535#35#&'735'67>BB7. ! &+4>"" 0%n  7  **7 1  1ÿðÇ{!%)73533673#3#5'67#535#67#35#35#E*! 46c#87*E @@@@m   J6    6,8ÿêȈ$(,047373#33#&'7#'67#535367#5#3535#5#=:7<8/ (  &)5jOOOOOOO} O  O 1ÿêÅ"&*735#53673#3#&'#5'67#735#35#35#7@2*82<0 % #. RRRRRR!Q  Q  $ Q  >ÿðÂ| &73353353#3#3#735#3673#53&'F! s llKK"„!u &  6ÿðÈ{"*.2673533#3#&''67#535#&'7#23#53535#35#35#F/11>$ 17'9/T H ’ #"o      # &&&.ÿðÔu1?73'67#'6'3673#353#'67#53365#53'&''66˜/ ! Q  ""  $ o   u     -  - !  3ÿêÄq!%+17=7#3#3#3#"''3267#55#5#5#&'''67&''4'½1))))8  n6%%%%%GQF q .T        0ÿñÅ6:>B73533#3#&''67#5365#73#3267#"&55#'67#735#35#35#7 @J  ((((((j     !V"  %& = " " 8ÿðÈy+/73533673#5##53&'3#3#3#535#535#735#U  l Z$339622$88v   ""('   /ÿðÑ8<@E73#&''67&''667#'367#533#63#5'635#35#35#675#—/       m*);  9           = G ' * .ÿïÌŒ-AGM7&'#5'6'3#735#3#"''3255'675#73#"''3255'675#&'7&'|"! H 19!qqMM$D  4JD  4< S Œ    8   8       5ÿéÊv #'+/73#735#73#735#3#3##5#535#735#33535#3359@@ =AA!!W8BBAA7%%7&]%%7&v# # 7" 9ÿêdž&*.26733673#5##53&'733#735##5##535#35#35#35#v  j  "XX77YU!!3""3!!3""† (( $ DD # 6ÿìÉ~ 2673#735#3353353#3#67&'67'5'67#735#A~~!u’’t1     #&RR~%  !   /ÿóÉ…;AG73533#3#3#3#3#3#"''75#5335#535#535#53&'#535#367#'&'c(''.*&&--/30 #'**&&)/(&F   z   2?   , EP¹c73#Ettc;ÄŽ 73#3#3#Dxxpp ‰‰Ž O"­73#735#O^^66_9?¿‘7#53#3#3#535#53t/r/++7€5**"&&<ÿüÀ§%735#53533#3#3#"''3267##5#535#F+0066//=   )55+n+>>8ÿ÷¤733##5'67#53&'u77 *8.  ¤%vg0 .( < ÄŸ7373#'67#67'7&''6C+>C&F  "&… :$$,-   R­ 7#55#35­[H55ww2 !!=ÿþ¼£7&'3#&''67&'767#}  (y% #R£  !  ; ħ 73&'73#3267#"&55#'655A53|\   $ ‹  F >  :É™ !7&'&''33267#"&5''6~  C  <   ™ H G' Hÿõ½“ 7#53##5#;5##;5#x0u2-ff8*@ÿòÁ¨"7##5##535#53&'73#3#"''325¤.575:0   P^^@Q > >ÿö˜7'75#53#7#55#5#75—Vz,,,,[V&#7á*73533#&''65#'&'333#"'&'75#h%%     # 9 (* }$$   . -?  1<½¡7'7&''275#53533#535#35#ž,:-----&#AA!2I ·œ 73#735#3#735#SYY55nnHHœ?=C!4É 27&''#5'6767327#"''67&''7&'3§  > q/    pS   %  5ÿþϦ $73&''67&''667#3#735#f? & $!&   !  4ffBB¦       K>Lÿö¹¨%73&'73#67'735#35#67&'L*%Q  ????  #‘  T1 p3(  ?ÿöÁ¤167'23#5##536'&''&'3&''67&'#367² -@5* _V  T     ¤ ##    ;   HÿõĦ$(,073#327#"&&''#67'56&53#735#35#§.(    . 2 aa====¦    J :M-- ;µ§$7'673'6735#535#53#5673a  @  >L#5p€  T^[  1ÿ÷Σ,2873#3#"''3267#'667#53&'7&''6&'&']%"    Q      £  J46 ,7  !'  6ÿì¦!'+173533#3#&'#5'67#535#35#&'735'6<;882&  !%2;  .   ’ D;:D B'  ''   6 ÆŸ &,73#3#535#535'635#'&'&''6³ (( P&& $,,4   Ÿ<<}a    $ 9Æ«!)-157#535#53533#3#67&'7&''63#53535#35#35#j*7,,,,8@  ,1^r      /// ;ÿòĨ!%+173673#33#53537#35#35#35#35#'67&'E-6<4‰(<<<<<<<< @ ˜WW      7ÿøÊ©/5C73#67#'6'3#753#5'67'53765#536'&'&''66›# $    !' _  ©   '7% .$ '  6 %7ÿøÈ¡8<@D7'673#&''67&'767#5367#533#7#5'77#335#75#ž %      g +';   )s )2    # I!Q0gÿéóÐ73533#3#353#5#5335#535#p266>> e;;2¦**$W@\ P?W$ZÿóòÆ&,73#3#"''3255#&''65##535#33#fŒ9.    +A †˜Æu  `  )|&ŠXÿéóÐ 47&''6367#53&'733#3#67'7&''67#¡& $ # 3= P. $ v ŠJ!  /5*Ð !W        YÿïôÐ?73&''67&''667#35#53#3#3#3#535#535#'6F  $( #   8"Y#7722?–C//'Ð     P YÿïóÐ!73#35#535#53#563#3#535#”$$\&&%9„…:AšE7Ð rh‚,,} îÎ!%)73673#3#"''3255##5'67#35#35#†4=H   0  #0000³  j  )q :4MÿíóÐ77'67#5373#3#3#535'67#5373#3#3#535‚  # !+KRW1;Š<# ,!+KRW1;Š<   t   NÿéóÐ"(.473533##"''3255#735#35#7'67&''67&'j211   1QQQQ [B b†JJU0 -23r  Ž }îÐA73&'73673#67&'#"''32655'67&''67&''67#… +        "«             u óÐ-1573673#3#3#535#'67##53#"''32=#5#‚?GH &^&   $W   222À ‰bQ +  VÿêðÏ 159=CIOU73#7&'''63#3#3#3#"''3265#5'65#5#5#&'''67&''&'•, : [ R2....8  pG&&&&&B  L F  Ï9,      0 G(        ÿéuÏ*0673533#&'#5'67#3#3##"''3255#&'''6**    !UU e(  *T  5  ¼  $# 6=  9  ÿéóÐ%)-73&'73#3#3#3##5'65#5#35#H=  HPFFFFW° )SCEEEEÏ !!"‡ 0&!2!!4"ÿêòÐ#'+/5;73&'73#3#3#3##5'675363535357&'''6µ *GI>LDDDDPª >)[FFFFF8n ¯  zB9 F##°  \ÿéð¯#'+73&'73#3#3#3##5'65#5#35#! $*$$$$+b!5$$$$$¯   x ("+,2ÿéï#'+73&'73#3#3#3##5'65#5#35#\. BD====H‘H77777   W!#˜ñÏ73#5#53733##VGG@HHÏ7 ÿéæ>73353#"''3255##5## N'Q  +N'>&&= *''Eÿèí¨<@DH77676767'67'67'6773&'73#3#3#3##5'65#5#35#(   = + N  &+####+c5%%%%%l (F$ &$)   u !"**ÿéê› .26:73#735#'3#735#3'73#3#3#3##5'65#5#35#Š\\88‚]]775UQGGGGU«UCCCCC›//$    I    ÿí†Ï !%8<@7&'67&'67&'63#735#3267#"&553'#37#3& 0   0  PYY55!-& f>,Ï       2.d U:* ÿéîÓ*26:>Oae7'67#53367353#3#3#3#3#5'67&'7##35#5##"''3255##5'673'7&'#735#. 1PZ QSIIIIZÃa 9AAAAA¬  §3  ;  `<<•      7     > *CR.    ÿçõh#'+IMQU73&'73#3#3#3##5'65#5#35#73'73#3#3#3##5'65#5#35#- O )h"&!!!!*Y*g   F  [  A  "ÿéÛN 73#5#535#535##¸§§žž¦Ne  ÿçõd#73&'7&'#"''3267#'67#5'6T m 9  : M D '2b% *3  $  ÿéóa#73#&'#'67#5353533655#335ÕX;FRA]ML6:L=T( &( ( (  ÿæñl  7&''6&'67#53&'|/8 56*8 L& g‚!-l   5   ÿéóa73533533##5##5#35#35# *h,,h*>hhhhOTT< ÿêód)-73#"''3265#3#32767#"&55'6358   „h]1M  Y? Jd;-   4  ÿéîd)733'67##"''32655#'67#53'767##·B9 "B 9!T[ —d   ( $#  ÿçóx773&''27&'767#'636733#"''3267#'67#Uo!2F*)>"/ c 'ES E; (7x      B!   ÿéón 73&'73#3#3##5##535# eiæ ¥¥¥¥¥]   00 ÿéõc473#3#535#535'2'3333#"''67&'767#'7#Ò<<3u/88)4‹B ZC=a    )c      ÿéóe$(7#3#&'#67'5#'665567#'3#é¶¶ "R  †= U‘‘e#  0( 1" 3T 3Uÿéï` 73533#3#"''3255##5##535#UCDD<  )&9CT - ;;0Aÿïï[73#3#3#535#35#'6'&'1žžÈCMÞK?R W ‚  [////    ÿèìl#'+73&'73#3#3#3##5'65#5#35#:;TSHHHHV­ YCCCCCi  H ÿéów4973&'73#3#53&'7367#3673#&''67'67#67#\U6=Û8@‰J†8 &) /))L = ; ;| F g .     ÿéó`&,287&''6767&''3#3#"''327#735#'&'&''6Û$$}C/1  1+1(  - #*88  C. (+       _ÿéód733#3#5##5335#ŒTTGc-ccdG G. ÿêö`,73533#&'#5'67#73533#&'#5'67#,)) "t&.%  N  7A#ED Yÿéëk7#"''3255##53#3#735#ë  lVVNN,,kk Vq‚ . ÿéâa"&*73533#&'#5'67#7#5##535#35#35#*%%  "ÏDDDDDDDO  7> " t t *,ÿêæW #7#5##535#7#5##535#7#5##535#RpsWmmN=mmN=mmN=ÿéöh .77&''6'3&'73#7&''7&'7676773'67#²  *Œ($`¢4*5   .Z# Bh     2 #ÿéçz!%73#735#3#&'7#'67#735#35#35#.¡¡{{°7$# '' , !/ ( ;ŠŠŠŠŠŠz! H    3   ÿéès )159=A7567&'7&'773#"''3255#'67##5##535#33535#35#6   / Rf + %"Y“??QB“??QBB>*   $  +II (ÿéór =7#5##53#3#67&'#"''32655'67&''67''67#ä¥#††(×h  ).   2? B3,6 5+2.Hr""          ÿêòt39?7'63533#&'#5'67#73533#&'#5'67#7'6'6Û! !º   J   „% #0 .t   G@  HB   ÿêõpGa73&''67&''667#'67#533'67##"''32655'67#53&'36533#"''3265#'67#•G    + 8o B[#   &0 S'2   "/ '#p       . $ -(    ÿéòq#'->DJ73533##"''32655#'6553'#33#7&'3##"''3255#&'''6—4   4foMM>IIl  bY$ #K  7  [F A' $7)        ÿèív $(F73&'73#7#"''32654'7##53#735#367#537#"''3255'758‚Ü  (jjGG Rp $  ?i! #u†# 2    ÿéòo (.4:@7'6'&'3#3#535#535'67'67'&''&''&''6à>NKq ¸ #IIA”@II=A2 2G * o  #          ÿéåq #04875#53#5'675#53#5'6'&''&'3#5##53635#35#`K]%.N`!)  j WT‹F9‹‹‹‹P9  :     GG! # ÿçóx#'+17735#53#3#3#535#735#33535#335#35#'67&'4&¶+99@ã>4 ??R@’??R@*CCC '/ *e3$ "12 <<   ?   ÿéíx"&1>D7&'73#3#"''3265#'67#735#35#367'5#&''633#;  GnBX  U  LLLLo5  $  *?Px  :4   # J  E   Tÿîów'+/373533533#3#&'7#'67#3#5#535#35#35#35#\F+  $  !uˆ&FFFFFFn 4   "04    ÿîòu)6:@767#533&'76767&'#5'7&'3#3#53&'#735#367#*  C[ G   b" # ” <Ó:pp6J]       )  ,  ÿéót26GKO73#&''67&''667#'3533533#3#535#35##"''3255##535#35#¤=      &–|(?  =====t        B T  ÿéòm59AE73&''67&''6267#'3#3#67'275375#735##5##535#’A !!  6}Q-7"--¯666m     3 /-899 9!ÿéö€3V73&''67&''667#'#"''3254'7##53#3#3#3#535#535#'6735#‰L +"%$  <&   aV"6633A•B--' "€    " #}6      ÿèøs7;?DHL7&''33#67327#"''67&'#7#5'35#53&35#35#675#'3#735#ß$2/    .-/ šy>>>>>``<OSW73&'73#3#53'#37#733#3#3#3#3#3267#"&5535##"''3255##535#35#*+r(%QUUMNKKKKGG$ -' NM ;;;;;o       MA  R ÿíòq #0<73#735#73#735#73#735#3#3#535#&''67&''6<<;::<<<¥Ò^gæla-  ~ q(  (  ( $22       Bÿêîr %AQ7#'6553#&'#5'67#535'673#&'#5'67#535'63#53533533ì‰O   Y    8Œ+r1) &8               V  ÿéï~!@DHLRX^d73#3#3#"''267#533535'3&'73#3#3#3##5'65#5#35#&''&'''674'¤2P\\Z  Z ???£H%› $"~2 ([ )  P        ÿéï~!MSW[agms73#3#3#"''267#533535'#5#3&'73#3#3#3##5'67##53675#5#35#7&''&'''674'¤2P\\Z  Z ???Y&  I 'ž  &"~2 ([   B <            ÿèóÏ(,073533#3#3#535#535##53#"''3255'#335#\^^VVjæhTT\%¯  O88O;À’5u]  / ÿèóÏ(,073533#3#3#535#535##"''3255##535#35#\^^VVjæhTT\À  ‡‡‡‡‡ÀR]  &u / ÿéòÏ73#5#535#535#53733#3#3##ZMMAAAA5GG>>OOÏå0+*''*+1 ZòÐ73533#3#535#'63#735#<.YYiåh> ±±‹‹Ð  9,5ÿéó£-=M73533#7&'#5'67#73533#&'#5'67#35#535#5353#5#733#3#3##;     W"&   ]?::;;?cCCAAFF–        "" y mW 2ÿéóµ"&7;?OSW[_c736533#&''67#7&''3#735#35##"''3255##535#35#3#3#5##537#35#35#35#35#35#œ&"   A  ‘UU4444I   ;;;;;Ád]‰>JuQ??????—  .  *. ;' GI=)))BÿéôÐ!EIMQ73&''67&''667#3#73533533##3#3##5#535#535#5#5#35#335‹I' ( *>R2!88AABB99#_2&&9$Ð    —i  (  (  0 mÿèôÏ'C733533##5##5#533673##5'67#33##"''3255#53567#Ž  $EK   3C ""  && ,Ï4  nS * '   eóÑ"-?QV73#"''3255#&'7#'67#5353635#&'7#733#"&55#'665#53&''67&67#<.     7  ¦    c  ## - ÑI  (.    =  aÿê±Ð /73'73#3#735#367#"''3255'67567#b O@@G     2¹  31   SÿèòÈ159=AGKQd73#3#3#&'#3265#"&55'67#5367#535#5#35#3353353&'##3'&'333#"&'&'75#‡e*1 -#   %%9    /)S  ('1  È2  $  "2$J ‡QY   K ]†Ð733#3'67#&''673E..*!K B9      Ð  *   ÿéöÑ ;Ag7&''67&''63'33#673265#"''67&'#7&'37533#3#3#7'675#535#535#536  J _˜87    šÀ  ƒ**''++>B''$$&&Ñ     70''""$!?B  =vt XòÏ (<73#53&'67#5&'67&'67''3#"''3265#'67#€dßf   A"   " ‹J  Ï    +,   $ 5/! ÿé…Ñ +/?EI7'67&367#53&'733#3#5##535#5#35#"&55#'665#333535#M $ N9 ?" ! F!$= F   8 FFF·  5    dd5  =  ÿèƒÐ47333533##5#'67#535'67#&'&''6A2.  ?*    Ð 5%%BB)!6     ÿéxo 73#3##"''3255#&'''6XX k+ -X  9 o9  6  ! KÑ 7&''6&'3'67#@ %M  7Ñ    ÿñÇ %73#735#35#'75327537'6'&'[[5555N/:!R Ça8=y SPOLE ÿì€Î"(8>7#53533#&'#5'67'6'&'&'3653#'67#&'3#,// I @  WK-14 *  )K //  #Q    b    #  TñÏ&*.28>D733#3#67'675#5353373#3#537#35#35#35#''6'67&'C&* B /7w40h&1EEEEEEi  † /Ï  %$%DD     –ñÇ!735#53#3##"''32655'67#'ª 3Y   !®!//H 2œ!ïÎ 73533#3#735#œ$$ O((yU'X6ƒìÌ &*73#"''325'3#3'67&''67#67#73#Ý  U>&    *̦  šY#  1=3s‹ðÉ 073#735#35#3#3#"''3265#'67#'67#'67#›JJ((((b<>   &      ÉC) % $;#, #  —òÏ473#35335#535#53#3&''67&'767#535#56®  $!    ?!% Ê PPU        N’ôÏ(,048<767&'767#53#"''3255#'7&''563#735#33535#335±   2  YY$8$Ï ) A 0*  EWS03ƒòÉ #/5;A75#53#5'6'5#53#5'67&''&'&''6'67'67'6Ü. *. 0   -   6   " " !* " ŸII   (         ! ôÈ 06<73#3#535#5#35#3353353#3##"''3255#'67&'Žb]6    HUU g)  , D È11$. ' $   ÿçrÐ1FLR736533#&''67#7&'3533#''655#73533#&''67#7&''&'#&)    G<   2     * & ¯  /  ~   #   )0    ƒõÎ!'<Q7&'36533#&''67#&'7&''33#&''67#53#3533#&''67#×  @'*,  !#/ =   E   Î     +          …õÐ17;?EK73673#67&'##"''3255#5'67&'767#3&'#35#35#&'''6‹",   " %%%%/ * ¼   -% #,   ' & !    …ôÇ'-FLR73#3#3#535#535#73#3#3#535#535#&'3673267#"&''67''67&'‡. / 4-/         V  ÇE  * , ‹ñÏ373#35335#535#53#3&''67&'767#535#56§ #,*     N)*È VVU    Q ÿîÐ"8LR736533#&''67#7&'&'3533#&''655#73533#''67#'&'*,/  #&Q   Z  7   ²    .  W     "  )-  v òÍ,?EK767&''6''6367&''67'367&''67''67'6°   1 ;    :     =  Í      / ?   0   -      ÿçõÑ6AEIV73673#3#3#&'#367'5'67#5367#537#53&'#3'73&3535&'76U= 3pkrˆ+z#  1@;DR7jR32 kggg  $5 Ñ    @ _  U       aðÑ &,287&''6'3#3'67##&''67#3'67'67'6´  %†`24%+ > (    !% *,0 - 0Ñ   #        \ÿé¶Ï !7'67&'#5'67&''6|  '   $  Ï )  ! |_   ' '% #‹ÿèõÇ )733#5367#3#3#&''67#535#'6œFf=3=!(&   ! (*Ç 4 #" Oç±!%)G73&'73#3#3#3#5'65#5#5#'27676767'67'67'67(/****1s . N   C$ ±   . ,%     aòÏ)-15I^733#3'67#732727#"&55'65535#'3#735#3#3#"''3267#7#3267#"&55#'65Ÿ77; &   'pJJ&&b;8  ; Æ   Ï      '#      ÿçôÌ +GKOY]ags7#'655&''3#735#35#36533#'67#5##53'#67'5'675#353&'73&'35357&'67&'ñÁÀ   •TT3333K(*5W 1+ z /!55(.2'.hhh  " %'ÌUF7 7?a  )   4 @-* @2 : !d  l  Sÿí£Î(,073533533#3#535#35##"''3255##535#35#X  O  )    ²8g (z"1kêÐ"&73#3#5367#53&'73635#35#35#Á#61j$4"   2BBBBBBÎ zz  V55kÿê¢Ð 7#5'6  Ч 'oÿéóÆ37;73&'''67&'#367#67&'7&''275##535335#335zl!     + -<,,,Æ   R?   "O.nÿéïÏ/3733#3#3##5#535#535#533#3#5##5'67#35#©00))3333))002xCG7 !)77Ï g I + A ÿçõªCGKQUY]cio73533533#3#3#&'#3#3#3##'3267#&'7#5'67#535#535#35#35#3&'#5#5#5#'67&''&'-!=%%$$@9$ 6AA@@\  '   u  (2<!3====ZB"55555  ]  ¢    & <  !      còÏ!7733#3#67'675#'67#53533'#"''3254'7##5 //@= 3Y2   D  Ï     !!"  Wg ]ÿéóÏ?EIMQW]ci7'#3#3#3#"''3267#5'67#537#53&'73673673#3&'#5#5#5#&''&'''674'Ï ((((7 g *+  =J) @2  )ƒ    . B               ÿçéY $(,28>7#3#3#3#"''3267#&'7#55#5#5#'67&''&'Í=??GGY   " {K77777  h Y  " J       mÿéõÇ (Ea7'6553'#333##"''3255#5357##"''3255'7567#5373567#533##"''3255#Ž vRRHO %%  ++7%  / * ˜4F5 4@_/ _    _ÿë«Ï 073#53&'3#735#367#"''3255'67567#…L ==@     (Ï  -50  gÿêõÏEKOS73533#3'33#673265#"''67'75#535#53#3#6767&'#535#7&'3#735#p((  $H R"m  _CC»44/2!    %C   =+ fÿéðÇ#9AEIM73#3#5##5##535#3#73#3#73#3533#'#5'67#7#5##535#35#35#rw29'(:3=C C!!P   }Ç /55 0 &  <4w w,. jÿéðÏ (,04DR[_73353353#3&'73#3#3#3#5'67#35#5##"''#5'#5673&'75#&'325#35#u!w*+''''-r 5"""""b 2 "VV 5Ê!  .    >   )R%      ÿéñ\!%?CGKQW]ciou{7#3#3#3#"''3267#55#5#5#7#3#3#3#"''3267#55#5#5#&'7&'''67'6'&'7&''&'7&'}&!!!!(  V.Å&!!!!(  V.@ | «  w  = } {~\   (G:   (G             cñÏ06733#3#67'675#53533'#"''3254'7##5'6ž//@< 3Y(AB  g  Ï   !!"  Wg3   ÿêòª;?CHL73533533#3#3#&'3#"''3267#737#5'67#535#535#35#35#3'#3#/%2%%%%B- (  ŒNX #0@%%%72222fF3€€Ÿ     .     ' *?aÿéóÇ (Ea7'6553'#333##"''3255#5357##"''3255'757#5363567#533##"''3255#ƒ~YYNU ''  --<( !2 -  ˜4F5 5?_/ _    ‘ òÏ!%)73673#3#"''3255##5'67#5#5#˜,3 7  !  A!!!³ k  ,p ;"rÿéóÏ=CY73533533##5##5#3#673265#"&55#3'67&''67#67#3533533##5#'67#w  r!         ½      ; ,   # H** fÿçôÎ!26:L73673#3#3#535#'67#'&'#"''3255##55#35'333#"'&'75#”4::!M   |  '8''m&$ )+ ½    HN  a  !U EÿèíÑ"&*.27#53&'73#3#"''3255##537#335#35#3#735#@-aa-)  «*kWWWWWWWppJJ¬ P]  HctP 006ÿéò@73#3267#"&55#'667#ÜH & -+)B@) 0 ÿìõR*737537#"''327#53267#"'&55'717R  <77D$4C #L )-*&  ' ÿêóO $(73#3##"''3255##5#535#53535#35#Ø   ŠŠŠŠŠO   * ÿçòS$7#5#'67##536733267#"&5âM 2/ N=U "  . E*& .+ % ÿéàS73#"''3267#'67##5##535#Ç  RN<HÆ“““S $77 ÿîóP73##5'67#&'3#ÛS "?O#m†/*-,ƒååP/"  $ ÿèó[(-73673&'73#3&''67&''67#67#FJ  Šw"# /''2 '-<]H      ,  ÿéóV $(,73##"''3255##5#53535335#33535#335Ù  ŒNO;;O=Œ;;O=L4 4  %  ÿêõg,073#"''3265#3#;6767##"&55'635HŠ  h`1,U* ( Mg7 "*  4ÿéïl#',7533'67#3&''67&'#'65535#67#ƒXB:#+ 1%$*)JJTS_  ! !0%<  ÿéôT73#7#5'75#35#35#75#Ú',.¥,%8iiiiiiT6 ? " &UÿéóU73533#&'#5'67#^:=," &! +> "?>#ÿèól973&''67&''667#3533#3#3##5#535#535#\n&.?133&$ + \)LJJRR^^__TTLl   ,    ÿîóX +7&''67&''63533#3#535#@ $ƒ  %–WXXiähWX     #66ÿèçm )-73#"''3265'3#&'#5'67#535'63#Õ T51  &48'6Omk _33Mÿèîl !'-73#5##53'3##53##"''325''67&'‰\¶kAB_Ð^  ' % "r# ""l %$ (      ÿêê`0487#"''3255#'65535#35#7#"''3255#'65535#35#{  75555¶  75555`^  **+ 6^  **+ ÿçíc+/373533#3#5##535#5#7#"''3255#'6555#350.."7'0S7Á  <L::NA A7QV  ((  ÿêäh ,04873#"''3265'35#53533#3#&'#5'67#735#33573#Ñ  ¶5>>::33!  ".""5 $he =   )  "   J ÿéô[0673267&''65'3#&'#5'67#535'6'6«  " & 4>(#   (./D  [' #%  )'    $ðv/732767#"&55'67#535#53533#32673#Íz ,5 A8.*#KF//22 8 ?c9J      ÿéòm59=AEI73673#33##&'#5##5'67#535#535#535#53&'5#3533535335X7AL9+& %&& "(5II55F=G&&'_&'l    &&    ÿæòv"&,273673#33#535367#35#35#35#35#&'''6]VZQâGY ‡‡‡‡‡‡‡‡b))((H ) &l J J  ÿçðl  &,7#5##53'73#3#535635#&'''6ë¬g& 8EŽ4Là&N:GGZ&% &&A* &a!! /3   ÿçór-15733533##5##5#533#3#&'#'67#5367#735#35#R?;;?BB%¨Km_;H#G 6L`K„„„„q2  ÿçóh(,9D733533##3#"''3255##5##535#5#53#3&'7&''67'7''6GR662`  NI[200dRR_    h    h 4 "44:H  ;        ÿèñt -573#735#35#3#735#335335#53&''27&7#327)®®ŠŠŠŠ"ÎÎ--?.+™²$5+0@/#ka t-# *    ÿèàu!)-173673#53'3'735#336735#5##535#35#^9*À5DV€€€€€t55+    /@@  ÿåïg $*0673#3#537#35#35#35#''6'6'6&'''6dŠ>5t+8PPPPPP1 .& .* 71 "  gNNN         ÿæôm 4873#735#3353353#3#67&'#67'5'67#735#$··##5&&¼ææª=   N   ††m!  !    ÿçô€ %-157'#'63&'3#73&'735#336735#5##535#35#‡+8«K AaÆÆ" HZ  ŒŒŒŒŒ€   -    (43ÿèì !%)-39?73#5##53&'3#673#5'675#35#35#35#7'6'67&'‡`´d?‘>+" !)A¢7,?~~~~~~ *+',.V*%''  @5*C T  ÿèó}  $/7;AGSZagm7&''6'&''6'&''63#73'67'727#3353&'7&'73327#"'37'737''&'7&'  5  5  ½½!6&v…"MQeV2  œ/9 N-:/V}  2      :) 2) "   ÿéõƒ FJNRV[73#53&'3#35#53#3#3#3#&'367'5'67#535#535#535#735#35#5#35#67WÖjXW7V2??55D" "H$  6,_D66@@.44f33777-  ƒ     &       *  .ÿïÒs9=AF73#&''67&''667#'33#7#5'75#5367#35#35#675#*       e<  2-s        )  6@& # ' +ÿôÐ;CGX7#3#267#"&55##535#535#5335##535#535#5335#53673535#3567&''7&'Ê177* 4&--'5 4&..'5 4%  0kK(   |A     %     ee %))   OáÏ!'-37&'7&''67&'76'3353#7&''&'&'?, €ŸÅžbNÏ   ]]oY    !  ÿéóÐ4JRXj†Œ’˜¤73#3#353#5335#535#'63533#&'#5'67#'3533#&'#5'67##5##5&'7&''67&'76'37&'3&'735&'753#7'6'6'6'33#7'n, G   B  œ  äÀG $    C (  m® +%" + .! 6 8§NNO1+ Ð     "   " 9$$       ;;        ;ÿèêo ).73#3#735###53#"''3255#3##5#53367#;¯¯ff ¯    **$#o $ -6E6  $  róË!I7#53#3#67'5#'67#'73657#53#3#32767#"&55'67#'7365F/f%*   %  n+e(-&      ¼          ‡ðÏ#73533#3&''67&'767#535#‡+++!    E%+®!!!!    !ïË73##5#'6556à $E  ,Ë  ii/ E#‚ôÏ73533#3#&'#5'67#535#‰&$$+!    !-&¯ $&SSƒ ïÎ(.7#67327#"''67&'#67'53537&'í-     ( ­%"("  ,0a }!! ƒòÏ%+733#"''3255#'65535#5#'&'7'6±"  1 $111  _  Ï4u %J&'~   wìÌ!%)73&'73#3#3#3#5'65#35#5#œ ^ ,Ì  d"+HðÌ%+73533533#3#535#35#35#35#'67&'‚%q"%%%%%%9  ¸XX55+  † ëÅ 07#"''3255##536735#&'#53&'73#3#ë  B#B (Å   Qn¹: *  U€óÅ573#3#"''3255#67&'7&'#5#67&'7'#535#€s/+       ,2Å‚ l;  !' +=  "(-˜íÐ L73#53&'&''67&'763353#3#"''3255#67&'7''67##5367#»*l/    8=*1  %   $$Ð       ,,;?  ,  AQ ~òÈ 06<73#3#535#5#35#3353353#3##"''3255#'67&'~s# l"@ Tbbr/ 1 G  È77(4 !    1ÿè÷¨+_dins73533#&'#5'67#'3533#&'#5'67#3#67&'7''67#32767#"'"&55#'67#536367#335367#335”!&    Z     IS!    .8 -0:-14D;~(-?B         !@   !   $' @  # wõÎ8PUmr73733&'33'73#67327#"''677&'#&''67#7''67'6776767'7''67'6776767'x -     (      X      _!NV          E          {òÐ-F733#3'7#75373267#"&55'65533353673#535&'7«..3 F     %   ]  Ð      & !3K;;    'ÿèö²#'8<@jnsx|‚736533&'73#&''67#'3#735#35##"''3255##535#35#3#''67#32767#"&55'67#53635#335367#3357'7#—  $ [SS3333I ;;;;;s  "( KC;55H803F; –    +++ 62     !2  YóÐ#'+?EKQ73533#3#3##5#535#535#35#33535#33573##5#'66556'&'&''6?'++%%''&&##'$9$Z 8$¯  "  Ç 4  4===+ !      KÿçôÏ&,159=AEKQW]7'673&'33#5'67'67&767#3735#33535#335'3#7&'''67&'7&'ƒ > $,(s    3 ).1O1‹–  _  05œ    D9    : & & t›  hÿéºÍ73#&'#5'67#535'6±   (Í) i\")% ÿæ÷Ð.<AEIMQW]ci7#'67#5367#537#53&'73673#3#3#&'33#5'637#35#33535#335'67&''&'7&'§B8 %8K.        nÿéóÎ )157'67333#"''67&767#7#53&#5##535#…  #&.   C :::… - -    /i# LUU5#bÿè÷Ð +177&''6'3#3#"''32767#'655#53&'&'&'Æ  -($   L Ð $!$1%g  @L- 0G-g,kÿý À 7#5##535#35# À¹ ÃM<Œ?gÿêœÎ73#3#5##53635#5#y$$ Î MW ÄM-++`ÿé Ï73&'73&'#5'67#e  '° ) iZ ?dÿìòÏ#'+1B73533#3#3##5#535#535#35#33535#335'&'333#"&''75#“%##!!((&&!!% / [  #&!+  ¼ZZ48\Gd  U`ñÐ+CI73533#3#3#3#"''3265#'67#535#535#73#&''67&''667#(00)).=8  -  !.$$(ŠC    +Ç           @ÿç­Ï !%)-17=CI73353353##53#33#5'637#35#33535#335&'''67&''&'NUT,$W   #5# B ? Â,, 0 G3  ! )        œÿé÷Ï39?73&''67&''667#3533##"''3255#&'''6¹%     !  !G  +  Ï     !  BB ?  ^óË>73#67&'7&''735#35#73#3#3267#"&55'67#'7367#XF#5555Ti#-'    % 5Ë?J ! &      Œ õÂ573#67&'#"''32655'67&''67&''67#b&        )         h‘ïÑ73#&''6'3#&''6½& 7! Ñ     kÿèóÏ@LPT73673#3#3#&'#67&'7#67'5'67#535#535#53&'3&'73&'#3535– 4..6        5--3 $888Î    7    Z    q   ÿðô!%)7#5'673'3675#'#33535#3#Õ¢ 3FH A(6000066wàà_QA!   /.pÿø¤´ 7#5##535#35#¤´² ¼I8ƒ:7ÿêò¶!'-3973#3#3#"''267#5363535&''&'#'674'€Gw““Ž ,ddd   >  2¶ G  9 #y  R /ÿ꿬#'-39?73#3#3#3#"''3267#5365#3535&''&'''674'@y93Xddh   g$-FFF   1  +¬> 2 n   K    ÿêŒÏ8767'7675#53533#"''32765#'67''67'67676A    *    Œ=" "@,,Ž &dx/  +!74  läÏ &*73#"''325'3#3'67#&''67#73#Ñ   À†KD!P E ;  &’ÏF  = '   1=„¸ 73#67'75#C> %¸j r pßÐ#73&''67'7#5367#73#735#>8  & %& ,USS++Ð  $ > |èÏ !7'67#73#3#535#535'6'  £ FF@=EE S› 0Q    5ÿê„Ì1073533#&'#5'67#:   ž..yd#-= ÿì€Ä"2873#3267#"&5535#&'&''33265#"&5''6ZG ,GG3  .  1    Ä?3j  8  :" ÿéŠÊ"6:?73''67&'#53#"''3255#7#'#"''3255#'65535#35#^)   8    (  m/  \á: &~!}  42%'UV=-i,ÿéxÌ 67'2'6'&''&''67#53655#53533#3#&k '7.'     2  "&)$$!!*, Ì  !  ˆ! ÿë“Ï"573533#&'#5'67#7&'&'333#"&'&'75#>  @ I  &$ ( ¥**eU"-65  @e   U oïÏ067367&''66'3#&'#5'67#535'6'6­   % <)"  +.#1G  Ï  $      wñÏ#)/5;73#3#3#"''3267#53635#35&''&'''674'£2ISSV  V777 ( %ÏF  9 &x %  T  ÿé“Ï)-273533#3##535#35#7#"''3255#'6555#35%j   &¥***Sb*l1nÆ =/( 4@_9''+ ÿéÎ@733533##5##5#5333'67##"''32655'67#53&'767#*b"      &=  IÎ-  E 8%!   ÿê}Î 067#5##53&'7&'''636533#&''67#7&'}I-  */0 #&(W  ·)+     4&1 oëÏ'+0735#53533#3#735#7#"''3255#'65535#35#'0022&]::Á   <::::¨   , 3G  !! !  [òÑ:@FL73&'73#3#"''3267#'667#&''67'6767677&'&'4'#'6r74PE   3  %)  &    ¿  +        ÿäóÒ HLP[_clrx~7#5##53&'73533533#3#3#&'#3#3#"''&'7#5'67#535#535#35#35#3673&'#35353267'67&''&'íµda'3++%%B0# —´´©   „ %2=""':3333? C ……… © f¿$$    !   C  &7      tÿí¥Ï73#3##53635#35# !! Ï HRÆE'•2nÿéóÏ %)-39?E73#'63#3#3#"''3267#53635#35&'''67&''&'P[ 5Tbb^   ]BBB =  9 Ï    B  / r % M   qíÑE73#6777'7''67'67#6777&'7''67'67#53&'736ª 8/  + &.Y  !, &.*7 > Ñ                ÿéÏ>BFLRX73533#3#535##5##5#53#3#3#3#"''3267#&'7#5363535&'''67&'944.m/9{Z+T)4QbbY    A @@@  ' "Ä   &3 $ Y=      ÿé‡Ð6;AG73533533##5##5#'#3##"''3255#535#5'63&''67&'4 ** // )5M ¹   3 /  P    ÿé…Ð"&273#3#&'#'67#537#53635#35#3533##5#<-&9   % 4444 ÐQ   Q +1g++ ÿèÅ "&373#735#35##3#67'5#'6553#&'76gg@@@@UXY=  KKC   ÅD* & ''.  ; $&K   ÿé…Ð);73#&''6'3#&''63#534'76767'7&'V  8 7,v5   0;! Ð   "   +  +"' *" %yïÓ!%)-17'673&'73#3#3#3#75#5#5#'3#735#r  %.0++++4}8'''''n@@¤  6 >F' jòÐ"&@H73533#3#&'#5'67#535#35#33573##&''67&''6267#222**  !+2+1B      (Ç !  ! (    7gó¶#3A73673#353#'67#53367#53&'73'67#'63&''66O +(  )j5*   ¶          eñÓFLRX^d7&''67'67676767&'7'#"''3255'67'67676767&'&'7&''&'7'6''6l &."  "   k  †  ‰  \  m  ³                     oõÏ(59=73533533##5#5#3673265#"&5'3#5'75#533#735# C6GG\CŒ6Q    }*%%#KK))É     +9  & (ÿùib7#"''3255#'65535#35#i bU   /+(ÿúog73533#'#5'67#*   U  -1 dÿé­Ï /73#53&'3#735#37#"''3255'7567#ˆF??A     *Ï  330   ÿåö–)-1H\`flrx~73#735#3#3#3#"''327&'765#53635357#'65533267#"&5'#"''3255#'65535#35#74''67&''4',¤¤~~0!3999 :%%%B6  €     c  )  –% - # P)2 0/j qk  00!2  :    7ÿé›Ì 1E7'6'6'&'&'''67'67777&'3533#&''67#’'/.     M #    ;#$&  " ' Ì   :   1   còÒ%)-HO7#5373#3#3#"''327#'67#53&75#3573#&''67&''6667#C(%-)0H?    / "7(??2A    %# ›, ,      %    *  ÿé‹Ï*.26O733#3'67#73265#"&55'65535#3#735#'67#537#53#3#&A..5"     + NN++  !&"S (,Ï     ):0 ,9S'( [    _ñÍ"G73533#&'#5'67#73#735#35#3#3#"''3265#'67#'67#'67#$    ]rrQQQQ”jd         º  //!*         eôÑ#-AGKOSW767#"''32654''67&''67'3&'73#736533#&''67#7&'3#3#3#735#%        ;"!TX ; CCDDDD$$Ì      ! "%    ÿèñÏ16;GMQ73#&'#"''3267#3#"''3267#5'67#5353735#'#36#3673&&'3#Ë#>+   a    3G"@ B8)2, "/(O‹‹¿*  *2 @ * *    !  <hìÑ-1767'67'67276'67'67'676763#Ò +5 \*3  NØØ¿     =eõÏF73533#7#"''3255'75#733327#"&'5#&''67&'767#53&    $*&~*      ¹   '/        ;ÿê—Ì73533#&'#5'67#F   ž.. }m++?=„¸73#67'675#C> % ¸j r ÿé‰Ï)-273533#3##535#35#7#"''3255#'6555#35 &g    &¥***Sb*l1rÊ ?3& 7?a<***2ÿêì°=AEISY]7#5'673&'73#3#3#3#3#"''3265#3#"''3267#5375#5#5#'#5'6&'3#|  /,''''/]M   d‹   Œ*4#####, 1=~~V*  &:A   M A- ] (5íÏ.BHTX\`73#3#3#535#'63'735&'75#&'7#67&''67&'&''#3#3#535#35#5#ŒQ:==9o&  *  '     [44Ï \\" s       b8,DD ÿèóÐ"&3J73#3#'6553&'5##5#35#33533533#67'73267#"&5536ŠZ=9µ\(,+++=,%’44"   $!Ð >>/ 2=Y7-6%  Q  L  ˆÙÑ73'267#'6ii8Œ/T] 7Ñ % )ÿíòF 73#53535#35#35#ÔÉjjjjjjFJJ $ # /ÿêðD73#&'#5'67#535'2Ò &TD18/ 2FS"JD "   +ÿîóG73#3#3#535#535#53&'TUNN^ÈWIIOOG    (ÿçòZ+/573#&''67#53&''7#633267#"&5'3#'3'6S( CK8S< 7 *   /.* #Z &   ,*  +ÿåïN&7#3#3#67'5#'655673&'èž••| f   :N   *%%8  %,ÿêõO-73#35#535#53#326767#"&55#'67#56y 77~778J5 ' ; / .%O  ;  6†òÎ+73533#&'#5'67#73533#&'#5'67#*+*  #s'/&             0ÿéòS$,487353&'33#"''67&''667##5##535#X0   G   ( tttS *     ,, ;ÿéæP &7#5##535#3#&'#5'67#535'2懇‡m(60 +5)/PggSF       ~êÌ#73#73##5##53#5##5&'7&'bbnee3É3H | Ì 2##21""1  *ÿèõT.26:7#673267#"''67&'#'6553&533&'73#3#735#ï5     Zj ‰IIDD$$F  "    % 5ÿéôN +07#3#3##535#73#535##53&''67&67#ŠDAA@@22PUUCC [    *N$ b " +    5ÿéóO%)-173#3#"''3267#535#73#7#5'75#35#35#75#5A/5   5./LpP 333333O) % 7? # % +ÿèôU $(>D73#"''3255#'67#5353635#35#35#73533##"''32655#&'a%   ) ,666666M2  2  UP  6& 1 , &ÿéôu #'+/373#3#735#33535#3353#3#735#33535#3353#/Ãà ££88J588J5¡ÈÈ ªª;;M9†;;M9«ÌÌu ) ) 5ÿêõ¤,H73533#&'#5'67#'3533#&'#5'67#'#'67'67676767'”!%   Z     Œ ?@" )-9 ‘ )'   !  \    & 5ÿéõ¤,2C73533#&'#5'67#'3533#&'#5'67#'6767'7&''6”!%   Z     A#%5#&8?/‘ )'   !  7 (  5ÿíõ¬+Q73533#&'#5'67#73533#&'#5'67#773267#"&55'75'75'6:     Z!%  9"%MO_` " -#BE8;8N    #  .       5ÿéõ¨+/3;?73533#&'#5'67#'3533#&'#5'67#3#735##5##535#”!%  Z      ““mmŒ………›         /,+9 9 0ÿéõ¨+=AEJ[`73533#&'#5'67#'3533#&'#5'67#3#7#5'675#35#35#675#7#53&''67&67#”!%  Z     ·b"& %%%%%K O    " œ        0<F ' )      6ÿæò¬+KOSW[ag73533#&'#5'67#'3533#&'#5'67#3533#3533#3#535#535335#35#35#35#35#&'''6” "      Y     FGG))—))F,FFttttttY 8 $             CC      ÿéðÏ#'+1773533533##5#5#3#3#735#33535#335'67&'4K55s4“KKáá­­99L9…99L9j 3 /b*% ''¾ T22$     ÿéóÐ#'+/5;73533533#3#3#535#535#35#5#'#335#35#'67&'0F22=iM­Kh;0DFFg977777L991 3 /b*% ''º^^]&+    ÿèëš#'+/5;73533533#3#3#535#535#35#35#33535#335'67&'4#)$$DaJ¨JaB#7))+66J6€66J6` . 0W&% &&Ž  HH E *     ÿçòu#'+/5;73533533#3#3#535#535#5#35#33535#335'67&')';((ChO®LjC't;$99L<ˆ99L   X 36p°Ð7''67'67676767&'¤     M .#:5 =( >ÿé©Ï #'-17=CIO73353353#3#3#3#3#535#535#735#&'735'6&''&'''67&'G`\\\'&&+c+$$(  "   -  )Ç$=       Q     ÿçóÍ 0AEI]afjou{‡7#'6553#735#35#7&'36533#&''67#'#"''3255##55#5#3#3#3#535#535#73'#335337335'67&'7&'7&'ð¿RR3333 8*#    9I999£HFFVÀYHHJ … 8A=ÍVG7 7@a(     , 7$   E        ÿíöÆ"&*.26:7#3#327267##"&55#535#55#7355#355#5#735àGGG ; EEDn !4q1n4q111n4Æ9c  d9ǵ¬µ5)ÿìóÈ $(,0473#3#327667##"&55##535#735#35#33535#335&¥KYY  3 ATG AATFšAATFÈ:` $ s_;rñ’ 7'67&'` "0 8X,'**’   [ñ¡ 73#735#33535#3353#%³³AAQ@‘AAQ@¸ââ¡3 _ïÌ #'73#735#'3#735#3#735#33535#3353#„YY77}YY66¬¬<2 Z !t* , $ ÿéôf"(047353&'3#"''67&''667##5##535#9=  CG*+" -2 €€€f 4     (11 ÿéò« 2EJPV767#5&'67&'67''3#"''3265#'65#3353#5##5#'6735#&'''6n    A"    ‹J ‰<;‰‰f  ;«   )& " 2*"4 r:77'5      ÿéò« 2UZ`767#5&'67&'67''3#"''3265#'65#'667#'6753353#5#67&'35#'6n    A"    ‹J j & ;‰<   [‰‰ «   )& " 2*"‹ +5r>  @  #ð® 3BG767#5&'7&'7'563#"''3267#'67#3353#5#'6635#m  ƒ     !´L  Š’  ‹®   $%  $ 0$, >   ÿçô¯3CGKOSW]c767'567&'67#5&''3#"''3267#'67#3353#5#'65735#3#735#35#35#'67&'Å     _    MK   " ““““ssOOOOOO A›  )  -       ! i1 '$ E2    ÿæó®4cgk767'567&'67#5&'#53#"''3255#'63353#5#3#3#3#3#535#535#535#535#'67735#35#à !  `    8H   " š7**%%,,1˜5))$$((2ššBš -   2    &  ‡]  EB`K!ÿè¾O 73#'3'65ªd'%Odc/%ÿèÞm#+/73#535#535#535#53533533#3#3'3'6573#š)…( ""%% %—£    gg  R)3! *(x FóÏ.2EKOSfkqv733#3'67#73267#"&553#'65535#3533#&''67#7&'3#735###'3255##5##53&'#3367335IAAE @()    qq  2 t)!    = œ[[??V #  Ï    % "2""#$* *. 6  &"ÿéái 7'67&73#5#533{& 7«—6!+7t iWÿêô\)-MTt{ŠŽ’£§«73&'73#3#53'#367#73&'73#3#53'#37#'33#3#3#3#3#"&5535##32733#3#3#3#3#"&5535##32'#"''35##535#35#7#"''3255##535#35#  7  `  8  N###$ $#' K$$## ##( £ ™ R        ;>d    ;>64  A4 A ÿéóÏ"&*CGKO7&'#5'63'3#735#73#735#73#735##"''3255##5##5##535#335335€ 45 e PF%T66288477&  (%###6%(Ï   *2220H  !!!!+_# ÿéóÏ#'+73533#3#3##5#535#535#35#33535#335dffTTjjhhRRd&>>R@’>>R@¼d$$d;>zîÊ 73#3#535#5#35#335335ÙD6À6Aƒ04""40#Ê 44 "GÿéìÆ &*.73#5##5353535##3#"''3255##535#35#Õ0OO  IIIIIÆ?""?? !e  '}#4ÿéäÏ#'73533#3#5##5##535#735#33535#335'NPPZFFZN::O<—FF[F¹EV//Z{!!!ÿêæÐ!73#"''3267#'6#5##535#K ™ ’ )fFFFЊ&v$1`f?, ÿëøÓBGMQUYm733#3#;267##"&55##535#535#5335##535#535#5335#5'667#5#7355#35&''67&'76_Q1CGG  : @0>>/?>.==.>B 5XRR0e/d7    Ó  -Z    5  % ®Ÿ–Ÿ4<<       ÿëøÓBGMQUYm733#3#;267##"&55##535#535#5335##535#535#5335#5'667#5#7355#35&''67&'76_Q1CGG  : @0>>/?>.==.>B 5XRR0e/d7    Ó  -Z    5  % ®Ÿ–Ÿ4<<      JåÏ573533#3#67#5'675#535#73#"''3267#'667#)''""$$)n`  # ¼T=/' $' ÿïøÐ %+7&''63#3#3673#535#535#&'~-= 65.> Qz5ZZ :ßeZZ1  Ð%-+%"";;"=ÿéçƒ 73#3##"''3255#'67&'Cvv+Ï] ^1yƒ<  9 •ÿçõÂ#)/73#"''3267#'67#3#735#35#35#'67&' R    II%%%%%% 5  Â( 0nM/."     ÿçõÏ048>RVZ^bhn7#'67'6753533533#3#'#5'67#535#35#335'&'73#"''3267#'67#3#735#35#35#'67&'ó¶ YU     h —N    MM,,,,,,  , ÂRC4 ,*  ` +9=< 9:># *bF ' '    ÿéï•?CGKQW77&'7&'#5'67&'67673673#3#3#3##5'65#5#35#&'''6Z!'    <(,&&&&.b 2!!!!!*  9  ƒ+B?  #   f && ÿèït?CGKQW767'7&'#5'67&'67673673#3#3#3##5'65#5#35#'&'''6\     @%)####*[ 1!!!!!+  9  l# :7      R  fÿêôÇ%)-15;A73#6767&'7&'#5'67&'#'67#735#33535#335&'''6ox= !'$ $ $2 R2  @ÇP   CA 00v    ÿéøÏ!%)-1G7'67#5353367&'#73#735#33535#3353533#&'#5'67#/ #0     +%%'j EE nKK¡  !)e+* ÿè^Ï7353367&'#5'67#  !.®!!$  hc =\ÿóóÏ73533#3#536'&'h6:„g1—Q<¤++ G4=?5591 ÿéóÐ *.2H7#'655353#7&'767&''7535335#35#7#"''3254'7##5óÃ`<  " ****›   '¼M@2 3;Y+].     &9?78ž°ÿï~Ï7#7'7''7535335#35#yI+   ' %%6666·hB" ±+D ÿéõÏ$273533#&'#5'675#&''6'&''6aaa*@ <$'7 @*a¤  w µ<@3][1:?  $ !Aÿê÷Ñ "7&''63#3#"''3255##5#–$/ .$!+ 8 aaˆ 3.Ñ %'*'%: $TTÿçëÐ'-3M7367&''66'367&''66''67'63673#"''3267#'67#­   g    jfE\  LC 9>Ð     !     r 8!5 %HÿéóÐ1=AEIMd733#3'67#6753673267#"&55'655333##5#5;5#33535#33533##'3267#'67#536•>>I r & %:hh *D*0< - . # (2Ð     ,>/ /8TJ!      GÿëõÐ048<@DLPTX733#3'67#753673267#"&55'65533#735#33535#3353#53535#35#35#BBS p&.1ll,J, ‘!#Ð    *>. /8SH<$ ! !$$$ ÿìöÏ048<@DLPTX733#3#67#'655332767#"'&55'7533#735#33535#3353#53535#35#35#jbbrªKcZ )% 25 +-0’’//@.n//@. Ì 2##4 Ï   I84 +5X     +?' ! #%%% ÿíò®+/37;?CKOSW733#3'67#73267#"&55'665535#3#735#33535#3353#53535#35#35#pNNhYFH %%.12 K2ŽŽ-->,j-->,Å00®      "2' #E #4   ÿèíÐ3\`dhl733#3'67#6732767#"'"&5'675#'6655333##3#"''3265#'6765#'#5367#5#5;5#33535#335t]]eQ$ #$ 0 3> S-˜DR F= #  (GA00B2t00B2Ð      I=/ +!SH "   !  ÿéõh26>B73&''67&''667#'3#3#7'75375#735##5##535#’A #   7zR"-8"..¯666h    0)(237 7! .óÑ $*06<733#5'667#35#33535#335&'7&'''67&'UQ4³ 2L G;;O>;;O>) K  ŸE ÑM< +"         uòÑ37;?73&''67&''667#'3#3#67'635375#735#3#735#™> " ( 0 W!!!,6%55][[;;Ñ   "  ! * ]ÿéóÏ.287#"''32655'675#537#'733#67&'37#&'±    'AQAH+=   !C; ;  >; %8 6  ŠT  ÿéóÐ.26>DHN7#'733#67&'#"''3'3255'675#53'37'3#3##5#'673#7&'À9A$9      "2F2µFF \%$<.  Š7 8  .9  %8&ŒŒ9% !2UN  ÿéóÐ-1RX^d7#'733#67&'#"''53255'675#53'3767&'7&'#5'67&'676&'&'''6¾;C&;      4H4 {   5  2  Š7 8  .:  &8:! UP&"9   ÿëô™'+17737#'733#67&'#"''32655#737#&''6‡jm:]   9%   c7cb% H&02S +(  ++ :0 A   ÿéóÐ-1>Z`f7#'733#67&'#"''53255'675#53'37''67&3#3#677'675#535#&'&'¾;C&;      #4H4 Ž" 3< "+##\  N Š7 8  .:  &8     D  ÿæóÐ!%3K7#3533533#'665535335#33533567'7533732767#"&5536ë¶+,9µ [[++=,%}"+ 6U %¼>@/ 0"[V[% P   N  ÿìgÌ 73#3#3#3##5##535#MM\\ HHHHJ&&&ÌS U4! (õÐ'/?PTX73533#733#3#3#3#3#"&5535##326'3673#53&'#"''3255##535#35#(-hrUUMNOOOOO*' MM]L ²tX  ;;;;;Ä   &   Y Zw  &I \ # [ïÃ73#3#3#535#535#ÄYOOeÞeNNWÃÿé÷› (0@QUY733#3#3#3#3#"&5535##326'3533#3673#53&'#"''3255##535#35#‡UUMMNNNNN*( MN]J Ì),htV   ;;;;;›  )   ` b’   )O  c ' IöÐ'9?PTX733#3#3#3#3#3267#"&5535#'3533#3#53&'#367##"''3255##535#35#†VVNNOOOOJJ#,& LMn'+r'#> ?????Ð #      H  9 LÿéìÏ &7#5##53533533#3##5#535#'6ê®`E-HH``dd8 ·9&&9& %%&;;&  ÿêî\)-73#3#5##5'67#35#'3#3#5##5'67#35#…i9 =* &**—i9 <* &**\M .B7M .B -ës #'73#735#33535#33573#735#33535#335dd'@'"cc&?&sF)) *F))  ÿèñ¥26:>BFJQ735333##3#3#&''67&'67#5367#535#5#5;5#33535#33535#33567#I>FFJ_q# %#8% ;I-E>$$,,?2q,,?2w22E78 D‘ %   % 4 = 5ÿçôÐ#'-7'655673#3533#&''67'35&'b P: 9Kƒƒ6A;1 24 4 2p 1'! 26d  5  &($!](  1ÿéðÅ'+17=C73#3#"''3255##5##535#'66553'#3&''&'&''&'`A=  +(:< ›ss 1   J 1   _  Jbbfw B3 0"[5$\        ÿëìÉ%)-15;A73#6767&'7&'#5#'67&'67#735#33535#335&'''6$·c "48F& ! ))8==Q>==Q># !!O' $ÉV  % 54 44{      ÿëkÎ$*767'7'#5'67&'676&'''6Q   3  ­:! VP&"^  \ÿêòÄ$,73#3#"''3255#&''67##535#33##gŠ7,    -B ƒƒÄu  `' (|$Š ÿëmÎ$*767'7'#5'67&'676&'''6Q   3  ­9" UO&"_   ÿèôÏ48<@DHLTX^d73533#3#535#'3#3#73#735#3265#"&55#'67#3#3#73#735#35#35#'#5##535#&'''6b>BB:5>QGGUU\‡‡c   dBBBBZxxTTTTTT+–" Ç  6   Q: O Q1#   EóÑ +F73#53635#35#77'7''67&'676''67&'67677&'zAh    ‚  Ñ mm4K30  '  . ÿìaÉ 73#3#3#3##5##535#HHVV EEEECÉO Q1 ÿéòg $73#53535#3353353##"''3255#ÙÔ""5!%¹åf lg$$$1   >ïÏ.31073&''67&'67#53667#73&''67&'#367</ ! % !) "R` Ï     J:-!    9ñÏ#)/5;73533#6767&'7&''67&'67#&'7'6'67&'gdi  $ '. c  »…”¶ &         ÿèôÇ '+1773#735#33535#3353533533#3#535#5#'67&'$ºº@@T@”@@T@²2A88?â<2‡A &. )f3# "1ÇZ56:    ÿëê”!'-7'#5'67&''676767&'&'''6¼  (+  $ 39@3 $ !"I ' "_ B@ ' 3    ÿèóÓ@FLS767&'7''667'767'6767&'7'&''67''6'67'67'66e$&  ;B        6 C'Q +#` 3 55 -R VG 9m AGÓ         '#   &    )  . ÿïñª'+/?73533533#3#3#&'#'67#535#535#35#35#3533#3#535#0$2%%((>5$ .8. %1<%%$62222%466]ÅV4– 0> ÿéïÒ ,15973#"''325''67&'3#5##5'7532635#35#35#x   5– 'W (IRr Ò8 * " „ h C02 ÿéò¡373533533533##5#3#5#5#3533#&'#5'67##8,,^#¡´ž8eedS<A !@ 4 O’ ->=   #23 ÿé÷Î )-15=AG73&'#'3#7'63#3#32765#"&55#735#'3#3##5##535#7'6; *ŠHHr  nVVnf   ;@@wDDDDCg. *Î"   N; A* O Q1 2 ÿèõÐ*@F[`767&''67&'73265#"&55#'6553533#&'#5'67#7&'#53&''67&67#   Æ   %}))    P  7 n   % 8 È     B 8 m(( RR# %6  $0     ÿç÷Ç$(7#3#67&'#67'75#'66553#貸T 'P$. &ÇF  )HR WB2 1$`+ ÿéñ›73673#3#5##5'67#35# V z… …v -IAvvz  ] G %^* ÿéòÏ (073533#3#735##5##533267#"&5'3'66fdà$™™tt¥±{   " ;%' $ ½54,-", 2!#ÿéðÇ+<73#3#"''3255##5##535#3&''67#73&''67#ßeY  EEYf'( Z(  Ç–  ~‘‘œ¯H XYòÉ!73&'#"''32765#'67#'6cl    8 2,  É 3H?  ÿéfÏ"7353673#&'#5'67#7&'       ~Q. XV#/S ÿèòÏ!%7#67&'#67'67535335#35#ÖR !% W!1&,9LL„„„„²i  HF  ³*D ÿìeÉ 73#3#3#3##5##535#JJXXFFFFG"""ÉO Q1ÿéŸÈ@DHLP7#3533533##5##5#'6553#3#"''3255#7'675##535#735#33535#335Ÿv_)1    -'%<%È  7C7 5@`<@A - DS' & ÿéñÐ=AEIMQY]ae7#'665535333#3'67#73267#"&55'75#'65533#735#33535#3353#53535#35#35#ð¾ \WWe Q@A&$ '7uu/#R/# ¨()ÁQB4 /"\        ?4( (/G88"  ÿèóÏ *H73#'6#3533#3#"''3267#'655#&&'67'675676ŽV^  v'%1,  ×   %2#Ï  "((^TJ06N1 :4h |   9ÿêòÐ-159=AGMSc733#3#"&55'75#'6553'7#73263#735#33535#335&'7&'''6733267#"&5…EER , "5Y C2 imm-I-%  C a  # Ð   I=/ /8S'?& !       =ÿþ»m 73#3#537#735#35#Fo5B~(%HH ZZm/ 33 O cÿéóÌ8<@DYg73#73##5##53#5##53#73##3533533#'65535335#3353353673265#"&5'67'7533p;;A==}*B $k"j  55#"      Ì / /.. )$$ $-2 8        0  ÿèóÌ8<@D\k73#73##5##53#5##53#73##3533533#'65535335#335335367326767#"&5'67'63533bbnee3É6Jn<»*);³[\**<)'<%$  *B", 2Ì / #21" /  *$$ #.2 8     0  ÿéòÏ(HN73#"''3265#'67#'6&''6367&'#"''32655'67'&'›?      /8 67$A H#   !   !*0#( ÏA*+  ;           $ÿèô²9AEI73#&'#5'67#535'6'3#&'#5'67#535'6#5##535#35#á$      "+7     3636…rrrrr²(#    ! gX X -#ÿîô°7G73#&'#5'67#535'6'3#&'#5'67#535'63#53533533à (  ,7 "    +35JNÄ%)>° /+  *(‘++9_ÿèõÐ *F\73#53635#35#''67&'67767'74''67&'676767'3533#&'#5'67#¥*     n    {;;0 ! $0Ðgg 5G    ,    ( .  9:  <óÒ;?CGKOSY_ek7'#5'67&'676767''&'#5'67&'676767&'73#3#3#3#3#735#'&'7&'''67'6á      ‡      $88 JJ<<<<==!  Ÿ  ·   š   ‘ 1,   % 1,  (E   +         GÿçôÏ28>DJP73'67&''667#67&'736533#&''67#7&'&'''67&''&'p%F  #   =" ! $8    d Y Ï f$   '   '& &3A   š  5ÿäøÆ 7&'&'333#"''75#V- 13 =; $Æ    #\OUÿéòÏ!'073533#3#&'#5'67#535#35#&'76735d=<<4& ( " 4=!! 0  ¸T%)IS/$TW0 # 0CÿäøÌ#'+17I73533#3#3##5#535#535#35#33535#335'&'&'333#"'&'75#„+++((--..''+'='u    * .- 77!»XX36_    !\ O ÿéòÏ!'0PV\73533#3#&'#5'67#535#35#&'76735'67&'7'#5'67&'676&'''6k9992$ $ 09 . …  2  ¸T$)GS/%TW0! 0.9" UO&"^   ÿèöÐ6DHLQ[i767&'74''67&'67677'7&''67&'676'3##5'675#35#35#675#73##533'3353'67#Ÿ      L    Ì]! ±&s %$»0 *   Ê/‘&&\$V22k)"#*( ÿéðÏ;W[_cg73533#3#&''67#5367#73533#3#&''67#5365#3533#3#3##5#535#535#35#33535#335'""'&     +'k$**5+ $  ##k\^^PPiieeOO\ <   - ÿë÷Ï#'+17I73533#3#3##5#535#535#35#33535#335'&'&'333#"&''75#PEEE;;GGFF;;E((:(b((:(¬4 &GÏ:  /  d    < $8) ÿèõÏ #)-39?E73#"''3265'3#3'67&''67#67#73#&'''67&''&'Ò  »t7 8+  "( /^8  —}"  ω {/ $20k,   ÿçóÍ '+P73#"''325'3#3'67&''67#67#73#3533#67&'67'75'67#Ð   ¹t<8K  %! /`ˆcfc  $O!)  =TÍb  Y7 % *H.   :   ÿé÷Î )-15=AG73&'#'3#7'63#3#32765#"&55#735#'3#3##5##535#7'6; *ŠHHr  nVVnf   ;@@wDDDDCg. *Î"   N; A* O Q1 2 ÿéòÏMQUY]a7#35#535#535'6735'673#33##&'#5##5'67#535#'66553535#3533535335ð¾5""//% " $BB*! "" %7 a""L"ÂQ        /::)   .& 1!] Y   0ôÐ 7'67&'3#3'67#~); ?326 6UJJ'• |³#. ! XDõÑ 7&''63#3'67#§& &% *88m  VÑ    ÿèaÃ73#3'67&''67#67#Q*.;     Ãu.. (=M  ÿéò¡?EIMQUY73#33##&'#5##5'67#535#535#535#53&'7#'66553533#'#365#3533535335¸3@3(& $ "%..%%/* 0 Rf5 @$  O v   %//"     ?4) &H !   2ÿéʃ73#3#"''3255##5#Kjj˜ C.ƒ9 #__/ѱ #7&''63#3#"''3255##5#}1 0) +88p   . ±  % :: ÿéòÒ !'-59=AR7#5##5353&''33267#"&57&'''63#53535#3353353##"''3255#î´d !  ,%€Š ¹ã##6%(¸ÚZ   mÃ""       "'''1   ÿé|š%7&'3#5'63#"''3255##5#?  3; #\   "š   4* FF ÿégÏ 73#7&''6.&  1 ÏæÃ ) ÿíîÏ'-3;U7367&''657367&''65''67'6#5##53#3#3&'73#535#535#D   )k    (ˆ  o  _´«OLL' &Ó]HHJÏ             ;++   ÿègÏ'7&'73#3#7'67'767#535#536&< "$)  (Ï.$ ÿépÄ 73##5'75#35#35#675# c $""""""ÄÉ/$$[%W ÿîuÏ '-7'67&3#3#677'675#535#&'@!3?## &)4$$  ®"    C5 ÿéóv'7&'#5'63&'3#"''3255##5#~19 i OS:— B3v   %  55 ÿîòp 6@73#735#73#735#73#735#3#3#535#&''67#&''67#<<;::<<<¥Ò1  æj2    )Á  (p' ' ' " 2    2   2ÿîÏ "7&''63#3#"''3255##5#=  #66a "Ï,+* %5 "YYAÿé©Â73#3'67&''67#767#L]//D   Âz0$ .:M   ÿêa 73#3##5#'673#@@ S   4Â(ŒŒ:$ !2UÿêuÇ!*/373#3#5##535#5#35#"&55#'75#326655#35#g#=7 =  76 ==DZ´i 8' &4 CòÉ73#3#3#535#535#3535#5##3ÖG99NåL88DX#99#88É89a&%% ÿéöÏ >DHLRX7#5##53533673#67&'##"''3255#5'67&'767#3&'#35#35#'67&'ë°a^=S6   @   B  -&Y pppp x ¼,,'  8  8   * ( !   -ÿêò†#7367&'#"''325'3'67#‡  " /  NG$$ -3† 7I  ^B2 ÿéwÏ"7353673#&'#5'67#7&'( '' #  ~Q2 Z_%-S EóÑ +F73#53635#35#77'7''67&'676''67&'67677&'zAh    ‚  Ñ mm4K30  '  . ÿçõÏEKOSY_e7#'67'6753533673#67&'##"''3255#5'67&'767#3&'#35#35#'&''67&'ò° UG;D2   3  1   /.= SSSSV  [ r¼OB2 .(  [&   5  3    , ' h     ÿèõ06<B7&''675'675#'67#53673#5'675#'67#'6'67'6}3: 79$B 8!* 4 & $^ \ #'  (#2 .>(RNH:ztk       64J   F      !  ÿë÷Ê 7&'&'333#"&''75##4 &K!44 6  ¯ ;  '; '% 3   ' ) D  Vÿê§Ï73'67'756776‹    £Q3'"ƒ n FÿèìÐ'/37;?7''756767&'73#"''3267#'67##5##535#33535#35#†  ! %Q   Kg**?(g**?((§ E  0 #E 06 ,Wr q*CaåÑ'7''675677&'73#"''3267#'65#\ %*$#0j% ª H  4 !=%-! ÿërÎ%+767'7'#5'67&'676&'''6W    2  ±># TO&" _   ÿé~Ï!'8>7#5353673#&'#5'67&'&'3653#'665#&'3!*  .   PE*13( &E 0/  #O   c    $   ÿïòÏ 73533#&'''6feß“-!"+8(*2™6659>5 I*4 ÿèŸÆ/5;B737&''67#5'675'67#73#5'675'67#'6'67'6>' ( $ !G: !( &+7 432. JÆH     W    ^  \ÿïòÏ%573533#3#3265#"&55#'67#535#3533#3#535#m,88D( %9,044A•@0µ   $ m  IÿéóÇ 173#3#535#5#35#3353353533#&'#5'67#Yš2*).W))‡EF7%$) &5ÇFF5$$$$$L#FF)"bîÇ 73#3#535#5#35#335335ÙC5À:E‚*9&&9*"Ç??.9óÏ$*06<73533#6767&'7&''67&'67#&'7'6'67&'ifl  " %,  c  »ˆ•¶ (      "   ]ÿïói!73#3533#3#3#535#535#'6|\\7722?–C--'i   fÿéò›&*.473#3#3&''67&''67#5'635#35#67#€Ya_: > $!   AAAA  6› =    8 ) # 5  ÿæéh!7'673#"''3265#'67#'67> +˜  OK#? 6C J19$.* P‹Î73#&'#5'67#535'6ƒ.--  &26:Î () ÿéóÑ5BF7#'67'675353&'73#&'#5'67#535'673#"''325'3#ó´ Xz $$   #$)` &»M?2 *"  b  QN& ™  }iÿéí‰!%)4:7373#3#3#3##5'65#5#35#'37&'#''6~" -0**++3h 7"""""R    ‰  W   "q }!  ÿéòÐ:>BFJ73533#3#3#3#67&'67'275'67#535#535#735#33535#335eeÞ¬O[[gY  H%+-  +)NbUUK99K<‡99K<Ä  C   @    ( %  ÿèdÐ7353367&'#5'67#     $2§))   ed 7ÿóðÇ#73#3#3#535#535#735#33535#335&¶QRReßfWWQ==Q=Ž==Q=ÇxC"""R ÿéóÐUY]as73533#335367373#3#3#3##5'#3#"''3255#7&'7''2767##537#75#5#35#'&''67'76/4wF   !$O *5  ) 0-ªK     ½ 11&*)&"$"I  2  Pb""4$$2"y    @ÿèíÇ!C73#3#"''3267'6765'67#735#73#3#"''3267'6747'67#735#ND,3  $ 4 *2TI,3  #%6 *6Ç:h!    <:i     < ÿèóÐ#'73533#&''67&'#367#5##535#a]%%:D.,: 3&5"xxx¶  Zaa@- ÿéê£$(,048EIM7#373#3#3#3##5'67##535#35#5#5#35#7##'3255#535#5#v 7600007m /8888U$$$$$Ž O====£B   ; xº $ B  Œ¡  YC $  ÿé|Ï $9H7#3#'6553535##5#35#3353353673267#"&5'67'27533tN  %       ¸;:/ 1\$0 3¥¶+ (   ÿêóÀ7#53#3267#"&55#'>L7ØF "3  ­¥ $  ªK#2 '1ÿéöÏ'/BGKSW73533#3#535#73265#"&55#'667#5##5#53&''67&67#'3##5##535#...&`'.Ç    I‚\   -…??K333¿<  /$  (G)+.-      %U U6$HñÒ#'C7373#3#3#3#5'6353535'3533#&''67&'767#! %(####,s"""""ž$&     =Ò G N Xÿé÷ÉIP7#5##53#67&'##"''32654'''67&''67&'767&''67#67&'îe‚            &0&  É&&,  &1Y&G        M  ÿéôÐ,26:73533#'3353#5#53373673##"''3255#7&'3#735#m64~> ; 7N  *   e%  FF º#§€Ÿ ’€Xh  d8  E?NÿéóÏ3973533533##5##5##5'673533##"''3255#&'\/##/(  &9  9  ¶pX "R  N MÿéöÒ!E7&'3673#&'#'67#7&'3#3##"''3255#535#535'2Ÿ  5]  %# +!   Z 22BB   ??11  Ò 0)(B L    )AÇ 7&''6 Ç1 ,)'VÿéñÏ.4873533#3673#&'3#5##5'675367&##535#5#5#k$$$   9C   I9$a@CC·    s W v) 9òË 73&'#''63#'673&'#fJ!>"!RRBSGË('& )$" ÿônÐ73533#7'676'&'%(`D (1#¨(( =- >0.03, ÿëêÅ#)/573#"''3255#73#"''3255#'67'6'67'6b Nqc N:&'Š&'R - -Œ , ,ž¥¾¥  *!! ÿèíGKO7#"''3255#'65535#35#73#3#"''3265#&''67#33#5'67#735#35#_ GkF[  $   ! K]  EEEEŸ .-2a0M@G H0    ,+ ' ÿéö©BGLPT`emŠ7'6733#3#;267##"&55##535#535#5335##535#535#5335#767#5#7355#767567&35&''3#&'#5'67#535'6k "A'00 ' ) ( $$ 'J4 / ?? !!  l     !#|  % M    *   % w )&     PG! ÿéòÏ*.26:>BJN73533#3673#&'3#5##5'67#535#35#35#'3#3#3#3##5##535#y  $  @; &D3 ;;;;}EE ^^MMMMM&&&¶! p ^tB­Q Q3  ÿèôÏ(4:@73533#3#3#33#"&''6535#535##5'753'&'&'"""++""Y6/M6 00"ÔDGB  ²"/  $E S"D<9  zwj  '  F òÉ"7'65533&''67&''37367w#o) %  0%|C#CC&9    (:&&:"BôÏ)-17733#3267#"&55#'67#5367#'635#335&'r?20   3 -(=6  $$8(  Ï A- 03%A  K4   ÿê•Ï,0473533#3673#&'3#5##5'675367#535#3535""  7=  C,===·! r VZ+iÿéôÏ8=737#535#5353373#33##"''3255#53567#'67#767#u =3))! "0 566  >>/ C i  ( %  5 iÿéòÏ%+73533#3#3#3##5#535#5367#535#&'u/00;!877445L\;/¸ 11 .  `ÿèñÑ'+/73#3#3#"''3267#353#5335#5363535–:]nnl ?eIIIÑK C+$()$ˆ  ÿèóÑ"&*FK7&'#3#3#3##5'63&'35#35#7#3&''67&'#'65567<9MA@@@@ ! % ....ÃXV )Ñ P  “ ! < ,r->+  '0D3 4>^Q(!# ÿézÑ!%)7&'#3#3#3##5'63&'35#35#> : NBAAAA " & ////Ñ P  “ = , ÿèòÑ#'3?CG7'#3#3#3##5'63&'35#35#7#5##5353#53#3#5'35#35?2 L@???? # ! ----ÅV4"VCK8008Ñ  P  ” = ,e,,Ä ¢>M jSÿéÐ )-157#5##5353373#3#3#3##5'65#5#35#N1* P ,º-/  d"()ÿÿ ÿèòÑ&ó+îÿÿÿé÷Ð&+ï1ÿÿÿèîÏ&1PÿÿÿíóÏ&1 ÿÿÿéóÐ&÷1ÿÿ ÿëîÐ&(0ŒÿÿÿëîÆ&0(ÿÿÿéóÏ&1cÿÿÿéóÑ&%†1ÿÿÿëîÒ&(1eÿÿÿëóÏ&(0Ëÿÿ ÿéõÐ& ¡ÿÿ ÿêóÐ&11fÿÿÿéõÏ&—*[ÿÿ ÿêóÏ&1*\ÿÿ ÿëóÏ&*]1ÿÿ ÿñôÏ&+ + ÿÿ ÿêòÐ&›šÿÿÿèòÐ&)ã™ÿÿÿèòÐ&œ1pÿÿ ÿéòÐ&™1qÿÿÿéòÐ&œ1rÿÿ ÿéòÐ&™1sÿÿÿíòÐ&žÿÿ ÿéòÐ&œ&(1tÿÿ ÿîóÐ&œ1uÿÿÿïóÒ&ܺÿÿ õÎ&+ð1wÿÿ ÿéóÏ& ëØÿÿ ÿçóÏ&V ëÿÿ ÿéóÏ& ëÙÿÿ ÿèëÏ& ëÚÿÿ ÿéóÏ& ëÐÿÿ ÿëòÇ&*a1xÿÿ ÿéóÏ&%Œ îÿÿ ÿéæÐ& ë+ãÿÿ ÿéôÏ& ì%ÿÿ ÿèöÏ& îïÿÿ ÿéõÏ&¹ îÿÿ ÿéñÏ& îÛÿÿ ÿçõÐ&«1zÿÿ ÿéôÏ&6 ëÿÿ ÿìõÐ& ÿÿ ÿìõÐ&ÿÿ ÿéôÏ&! îÿÿ ÿéôÏ& ëÿÿ ÿéòÏ& ìÿÿ ÿèóÏ& îÿÿ ÿéòÐ& ëÿÿ ÿéêÏ& îãÿÿ ÿéõÐ&äÿÿ ÿéôÏ& îÉÿÿ ÿïõÐ&ÿÿ ÿéëÏ& îÿÿ ÿéóÏ& îŸÿÿ ÿéõÑ& î%ÿÿ ÿéôÏ& ë%Žÿÿ ÿéóÏ& ì*–ÿÿ ÿéòÏ& ë\ÿÿ ÿèõÏ&-ã îÿÿ ÿéòÏ& ë1{ÿÿ ÿéíÐ&Õ îÿÿ ÿéùÏ& î%ÿÿ ÿèõÏ&| ìÿÿ ÿéëÏ&Ì& í1|ÿÿ ÿèøÐ& ë%ÿÿ ÿéëÏ& îºÿÿ ÿéõÏ& î,ÿÿ ÿéçÏ& î%‘ÿÿ ÿéòÏ& î%–ÿÿ ÿéôÏ& ì% ÿÿ ÿèöÏ& ëŸÿÿ ÿéñÏ& î:ÿÿ ÿéõÏ& î%·ÿÿ ÿéôÏ& îÜÿÿ ÿéòÏ& î$—ÿÿ ÿéóÏ& î&vÿÿ ÿçòÏ& î^ÿÿ ÿéðÏ& î*cÿÿ ÿéîÏ&%¸ îÿÿ ÿéøÏ& î%¹ÿÿ ÿéøÐ&%º îÿÿ ÿéòÏ& ëbÿÿ ÿéôÏ& ìLÿÿ ÿèóÏ& î…ÿÿ ÿéòÏ&¸%½ÿÿ ÿèõÏ& îµÿÿ ÿéöÏ& îRÿÿ ÿéóÏ& ëGÿÿ ÿé÷Ï& ì%¾ÿÿ ÿéóÏ& îaÿÿ ÿéõÏ&Û îÿÿ ÿéñÏ& ì-âÿÿ ÿéõÏ& î%¿ÿÿ ÿçõÐ&1}ÿÿ ÿèôÏ&@ ëÿÿ ÿèõÏ&%À ëÿÿ ÿéñÏ&%þ ëÿÿ ÿéõÏ& î%ÿÿÿ ÿçõÏ& î`ÿÿ ÿêõÐ&1~ÿÿ ÿèëÏ&& ëÿÿ ÿéòÐ& ë&ÿÿ ÿéñÏ& ë0•ÿÿ ÿèôÏ& î1ÿÿ ÿèõÏ& î,Žÿÿ ÿéõÏ& ëEÿÿ ÿéóÏ& ì&ÿÿ ÿéëÏ&& ëÿÿ ÿéñÏ& ë1€ÿÿ ÿèöÏ& î&ÿÿ ÿéóÏ& î&ÿÿ ÿéçÑ& ë&ÿÿ ÿéòÏ& î¡ÿÿ ÿçòÏ& î&ÿÿ ÿéóÏ&_ îÿÿ ÿèóÏ& ìÿÿ ÿèõÏ& î*dÿÿ ÿéêÏ& ì{ÿÿ ÿéòÏ& îÝÿÿ ÿéêÏ& ì‰ÿÿ ÿéóÏ&d îÿÿ ÿéòÏ& ì.Ñÿÿ ÿèìÏ&e&&t¿ÿÿ ÿéòÏ& î,ÿÿ ÿéõÏ& ë&ÿÿ ÿéòÐ& îhÿÿ ÿéèÏ& ì& ÿÿ ÿé÷Ï& ì®ÿÿ ÿéòÐ& î¤ÿÿ ÿèòÏ&&  ìÿÿ ÿéòÏ&*e îÿÿ ÿéòÏ&Ü ìÿÿ ÿéóÐ& ëgÿÿ ÿèôÏ& î”ÿÿ ÿéóÏ&  îÿÿ ÿéóÏ& ìÝÿÿ ÿéóÏ& îÞÿÿ ÿçöÏ&` îÿÿ ÿèõÏ& î–ÿÿ ÿéóÏ& ëbÿÿ ÿéòÏ&} îÿÿ ÿéóÏ& ì& ÿÿ ÿéðÏ& î£ÿÿ ÿèñÏ&(, îÿÿ ÿéòÏ& î8ÿÿ ÿéóÏ& î Ëÿÿ ÿéëÏ& î¢ÿÿ ÿèñÏ& í1ÿÿ ÿèõÐ& î& ÿÿ ÿèðÏ& ë&ÿÿ ÿéõÐ& î&å™ÿÿ ÿéóÏ&Á îÿÿ ÿéèÏ& î&ÿÿ ÿéöÏ& îbÿÿ ÿéñÏ& ë¡ÿÿ ÿéúÏ&e`ÿÿ ÿéêÏ&# îÿÿ ÿéõÐ& îaÿÿ ÿéòÏ& îWÿÿ ÿéôÑ&Ü ìÿÿ ÿéòÏ& î*fÿÿ ÿéóÏ& ì¦ÿÿ ÿéõÏ& ìÿÿ ÿéòÏ&+ ëÿÿ ÿéóÏ& îäÿÿ ÿéóÏ& î0Óÿÿ ÿéöÏ&$ îÿÿ ÿéõÏ& · ëÿÿ ÿéõÑ& îŒÿÿ ÿéöÑ&] ëÿÿ ÿé÷Ï& î¥ÿÿ ÿèõÏ& î¨ÿÿ ÿéõÑ&¶& ìÿÿ ÿéõÏ& î9ÿÿ ÿéóÐ& ìxÿÿ ÿèëÏ& í&œàÿÿ ÿéóÏ&÷ ìÿÿ ÿéòÏ& î1‚ÿÿ ÿéòÏ& í1ƒÿÿ ÿéóÏ& î$ÿÿ ÿéôÐ& î ÿÿ ÿè÷Ï& ì5ÿÿ ÿèìÏ& ìÿÿ ÿèõÏ& îÑÿÿ ÿçöÏ& îéÿÿ ÿçìÏ&¸&¬ÿÿ ÿéóÐ& ì&"ÿÿ ÿéöÏ&¸œÿÿ ÿéôÏ& î¾ÿÿ ÿéòÏ& î§ÿÿ ÿèôÏ& î0¼ÿÿ ÿèòÑ& ëÕÿÿ ÿéïÏ& ìÃÿÿ ÿéîÏ& ë.ÿÿ ÿéòÐ& ëÛÿÿ ÿéìÑ& ì¡ÿÿ ÿèïÏ&Å ìÿÿ ÿçõÐ& ë(ÿÿ ÿéôÏ&À ìÿÿ ÿéìÏ&¸0Iÿÿ ÿæöÏ& ìpÿÿ ÿéïÏ& ì½ÿÿ ÿèåÏ& î¾ÿÿ ÿåôÑ& ìÿÿ ÿèíÏ& ë¡ÿÿ ÿéëÏ&à&e&$ÿÿ ÿèöÏ& ì&%ÿÿ ÿèóÏ& ëWÿÿ ÿçóÐ& î&#ÿÿ ÿèóÑ& î&úùÿÿ ÿçöÏ& îÕÿÿ ÿç÷Ï& î&&ÿÿ ÿéõÐ& ì ±ÿÿ ÿèóÏ& ì&'ÿÿ ÿè÷Ï& î&(ÿÿ ÿéòÏ& ì.˜ÿÿ ÿéóÏ& ì&)ÿÿ ÿéíÏ& îvÿÿ ÿèîÏ&¸&*ÿÿ ÿéôÏ& ì0yÿÿ ÿéõÏ&/= ìÿÿ ÿéôÏ&] îÿÿ ÿè÷Ï&¸"äÿÿ ÿèñÏ& ìŒÿÿ ÿæõÏ&-á ìÿÿ ÿéðÏ& ì&+ÿÿ ÿéöÏ& ì&,ÿÿ ÿéøÏ& ¹ íÿÿ ÿéóÏ& ë&-ÿÿ ÿéóÐ& ì&JIÿÿ ÿéòÏ&&. îÿÿ ÿèôÑ& ìXÿÿ ÿéôÏ& î&uÿÿ ÿèöÑ& ë&/ÿÿ ÿíõÌ&ªÿÿ ÿéçÐ&&0 îÿÿ ÿéìÐ& í&ùÿÿ ÿéëÏ& ì&1ÿÿ ÿéòÏ&% îÿÿ ÿéòÏ& î©ÿÿ ÿéóÏ&&2 îÿÿ ÿé÷Ï& ìUÿÿ ÿéóÐ& ì&3ÿÿ ÿéñÐ& î&4ÿÿ ÿéóÏ& î¬ÿÿ ÿéóÐ& ì«ÿÿ ÿéõÐ& î&5ÿÿ ÿéöÏ& î&6ÿÿ ÿåðÏ& î-àÿÿ ÿçõÏ& Ö îÿÿ ÿéóÏ& î&7ÿÿ ÿéóÏ& îÖÿÿ ÿéïÏ& î&8ÿÿ ÿéóÏ&&| îÿÿ ÿéòÏ& î)ÿÿ ÿèðÏ& î0ÿÿ ÿéïÏ&( îÿÿ ÿéñÏ& ì&}ÿÿ ÿéòÐ&õ&¸ùÿÿ ÿéõÏ& ëÈÿÿ ÿéôÑ&ÉWÿÿ ÿéóÐ& í ÿÿ ÿçïÏ& î*gÿÿ ÿéóÐ& î^ÿÿ ÿèôÒ& î_ÿÿ ÿéóÏ& î*åÿÿ ÿéõÏ& í.Rÿÿ ÿéöÏ&¸1„ÿÿ ÿéõÏ& í*hÿÿ ÿéöÏ&*i ìÿÿ ÿèóÐ& ì\ÿÿ ÿèòÏ& ì¯ÿÿ ÿèôÏ&Ç ìÿÿ ÿçóÐ& î,aÿÿ ÿéïÏ& î°ÿÿ ÿèñÏ& í&~ÿÿ ÿéöÏ& ì&ÿÿ ÿéëÏ&î íÿÿ ÿèðÐ&f îÿÿ ÿéòÐ& ì*jÿÿ ÿçóÏ&© ìÿÿ ÿéõÑ&W&KJÿÿ ÿéìÏ&¸îÿÿ ÿéëÏ&%&eàÿÿ ÿèóÏ&Ü îÿÿ ÿéïÏ& ì(@ÿÿ ÿéôÐ&&€ ìÿÿ ÿéõÏ& í¾ÿÿ ÿèîÏ& íÈÿÿ ÿéëÏ&&‚ îÿÿ ÿéóÑ&WVÿÿ ÿéóÏ& î&Èÿÿ ÿèñÏ& î&ƒÿÿ ÿéëÏ& ì&„ÿÿ ÿéôÏ&| ëÿÿ ÿéòÏ& ì&…ÿÿ ÿéôÏ&{ îÿÿ ÿééÏ& ì±ÿÿ ÿèñÏ& î,bÿÿ ÿéôÏ& í&,1…ÿÿ ÿéòÏ& î-äÿÿ ÿéóÐ& ì¯ÿÿ ÿéõÐ& Ê ìÿÿ ÿéóÑ&Q îÿÿ ÿéñÏ& ì­ÿÿ ÿéóÏ& î²ÿÿ ÿéöÏ& ì®ÿÿ ÿéøÏ& ì„ÿÿ ÿéòÏ& î³ÿÿ ÿæöÏ& í”ÿÿ ÿéõÏ& ì.ÿÿ ÿéõÏ&¸&&w1†ÿÿ ÿèóÏ& ì{ÿÿ ÿéõÏ& î&†ÿÿ ÿèòÏ& î*éÿÿ ÿéõÏ&&‡ îÿÿ ÿçðÏ& ìÿÿ ÿéôÏ& C ìÿÿ ÿéôÑ& É ìÿÿ ÿåôÏ&*ú ëÿÿ ÿèñÏ&.Qeÿÿ ÿéòÏ& îrÿÿ ÿéóÏ& ¢&}¸ÿÿ ÿèôÏ&!Þ ìÿÿ ÿéöÐ&"¬ ìÿÿ ÿéòÐ& ì#£ÿÿ ÿéñÏ& î#Éÿÿ ÿéôÏ& î!jÿÿ ÿéôÏ&"Z ìÿÿ ÿéöÏ& í´ÿÿ ÿéîÏ& î&ˆÿÿ ÿéîÐ& í »ÿÿ ÿéíÏ& ì-åÿÿ ÿéòÏ&w îÿÿ ÿéòÏ& ì&‰ÿÿ ÿéñÏ& î"ÿÿ ÿèóÏ& î#@ÿÿ ÿèîÐ& ì0ºÿÿ ÿèôÏ& î!åÿÿ ÿéõÏ& í#‹ÿÿ ÿéõÏ& î#Ùÿÿ ÿéôÏ& ì-ÿÿ ÿéóÏ& î"ÿÿ ÿéóÏ& ì"xÿÿ ÿéôÏ& ì&,ÿÿ ÿèòÏ& î!õÿÿ ÿéïÑ& í!³ÿÿ ÿéôÏ&&Š îÿÿ ÿèõÏ& î@ÿÿ ÿéõÑ& î'ÿÿ ÿçóÏ&&‹ îÿÿ ÿéðÏ& î#Mÿÿ ÿèöÏ&%ĸÿÿ ÿéóÑ& î!Âÿÿ ÿéóÏ& ì&Œÿÿ ÿéóÏ&0reÿÿ ÿéòÏ& î1‡ÿÿ ÿèðÏ& î"wÿÿ ÿéíÏ& ì"ÿÿ ÿéóÐ&"’& ì!8ÿÿ ÿéõÏ& î"Žÿÿ ÿéïÏ& î"Gÿÿ ÿäöÏ& î"oÿÿ ÿéòÐ&^ îÿÿ ÿéôÏ& ì#Ÿÿÿ ÿéóÏ& ì"(ÿÿ ÿèóÏ& î‡ÿÿ ÿéìÏ& í&!ÿÿ ÿéñÏ&¸ hÿÿ ÿéíÏ& í qÿÿ ÿéõÏ&¸#„ÿÿ ÿéõÑ& ì#…ÿÿ ÿèòÐ&¹& ì1ˆÿÿ ÿèõÏ& î#³ÿÿ ÿéòÑ&e#êÿÿ ÿéóÐ& ì!ðÿÿ ÿéíÏ& î1‰ÿÿ ÿéñÑ& î#óÿÿ ÿè÷Ï&$Þ ìÿÿ ÿéóÏ&-Ý îÿÿ ÿéöÏ& ì¶ÿÿ ÿéóÏ& íTÿÿ ÿæõÏ& ì$ÿÿ ÿèôÐ& î,Åÿÿ ÿéìÏ& î"Õÿÿ ÿéïÑ& íBÿÿ ÿçòÏ&&‘ îÿÿ ÿéôÏ& ì-Þÿÿ ÿèôÐ& ìfÿÿ ÿèôÐ&&’ îÿÿ ÿéðÏ&$« îÿÿ ÿèöÒ& î$%ÿÿ ÿéóÐ&$_ îÿÿ ÿéôÏ& ì&,1Šÿÿ ÿéöÑ& î$ÿÿ ÿéòÏ&&“ îÿÿ ÿéëÏ& î$Sÿÿ ÿéñÏ& î# ÿÿ ÿéóÑ& î# ÿÿ ÿéóÐ& î#ÿÿ ÿéòÏ& î$>ÿÿ ÿèðÐ&¸/Lÿÿ ÿéóÏ& ì&°#ÿÿ ÿéóÏ& ì&˜|ÿÿ ÿéòÏ&e-ßÿÿ ÿèñÓ&¸1ÿÿ ÿèóÏ& î&”ÿÿ ÿéöÑ&&• ìÿÿ ÿéíÏ& ì-:ÿÿ ÿéöÐ& ëEÿÿ ÿéôÏ&&,eÿÿ ÿéóÏ& ì)ÿÿ ÿéóÏ& ì&šÿÿ ÿèõÐ& í ÿÿ ÿéòÒ&¸1‹ÿÿ ÿéóÑ&b ìÿÿ ÿèôÐ& ì&œÿÿ ÿéöÏ& ì*æÿÿ ÿçõÐ& î&ÿÿ ÿéóÑ& î&›ÿÿ ÿéòÏ& í& ¢ ¡ÿÿ ÿéòÏ& ì1Œÿÿ ÿéõÏ&4&Ý îÿÿ ÿçõÏ&¸+ ÿÿ ÿéóÏ& ì&¡ÿÿ ÿéóÏ&Ð ìÿÿ ÿéôÏ&Ý& ìÜÿÿ ÿé÷Ï&¸&¢ÿÿ ÿéóÏ& îJÿÿ ÿéöÏ&&¥¸ÿÿ ÿçôÐ& ì&¦ÿÿ ÿéïÏ&è& ó íÿÿ ÿèôÏ& î’ÿÿ ÿéôÐ& î&§ÿÿ ÿéóÏ&Æ ìÿÿ ÿçôÏ& îÄÿÿ ÿéóÒ& ë%æÿÿ ÿéóÏ& ì&£ÿÿ ÿéõÏ&·¸ÿÿ ÿèïÏ&&¨ îÿÿ ÿèõÐ&e&1ÿÿ ÿéôÏ& ízÿÿ ÿéöÏ&¸&%1Žÿÿ ÿéîÏ& ì1ÿÿ ÿéñÏ& í#ëÿÿ ÿéóÏ& ì&©ÿÿ ÿéôÏ& ì&ªÿÿ ÿéòÏ&&«¸ÿÿ ÿèôÏ&¸&¬ÿÿ ÿè÷Ï& í&61ÿÿ ÿéìÑ& îšÿÿ ÿèöÐ&&­ ìÿÿ ÿéëÏ&&®&(L ìÿÿ ÿçêÏ&%ë îÿÿ ÿéöÏ&%ì ìÿÿ ÿèóÏ&¸0Ùÿÿ ÿéóÐ& ìtÿÿ ÿéôÏ& î*Óÿÿ ÿèõÏ&¢1‘ÿÿ ÿéòÏ& ì&¯ÿÿ ÿèóÏ&&° ìÿÿ ÿéìÏ&î& í1’ÿÿ ÿéõÏ&¼ ìÿÿ ÿéøÏ&¸&±ÿÿ ÿéóÏ& ì.äÿÿ ÿçóÏ&¸'ÿÿ ÿéòÏ& í.Ôÿÿ ÿéõÏ& ì1“ÿÿ ÿèôÏ& ì&Éÿÿ ÿéõÑ& ì-îÿÿ ÿèôÐ&½ íÿÿ ÿéðÑ& ì&€1”ÿÿ ÿçôÏ& ì&Êÿÿ ÿéóÒ& ì´ÿÿ ÿéôÑ& í'ˆÿÿ ÿéðÏ& ë'ÿÿ ÿèõÐ&&¸1•ÿÿ ÿéõÏ& ì&Ëÿÿ ÿéõÒ& î&Ìÿÿ ÿé÷Ñ& ì½ÿÿ ÿèõÏ& ì1–ÿÿ ÿèóÑ& ì&Íÿÿ ÿèôÏ& í&ä&,1—ÿÿ ÿçôÑ&¸&Îÿÿ ÿèòÏ&&Ï îÿÿ ÿéñÏ&e&Ðÿÿ ÿéôÏ&1 íÿÿ ÿéóÏ&¸¾ÿÿ ÿéõÑ&¸%vÿÿ ÿéòÐ&)'eÿÿ ÿéóÏ& í&†‡ÿÿ ÿéôÏ& ì¨ÿÿ ÿçðÏ&¸*çÿÿ ÿéñÐ& î)$ÿÿ ÿèñÏ&¸%Oÿÿ ÿç÷Ò& î&Ñÿÿ ÿéòÏ&¸¿ÿÿ ÿéôÐ& ì&Òÿÿ ÿé÷Ï& ë÷ÿÿ ÿçôÐ& î&Óÿÿ ÿéñÐ&-- ìÿÿ ÿéïÏ&e&çæÿÿ ÿçôÏ&e),ÿÿ ÿèøÏ&¸&,c1˜ÿÿ ÿé÷Ï&eÿÿ ÿéðÐ& î1™ÿÿ ÿéöÐ&H îÿÿ ÿéóÏ&e&-1šÿÿ ÿèòÑ& ì&Ôÿÿ ÿéñÑ&¸&Õÿÿ ÿæõÐ& í1›ÿÿ ÿéóÏ&¸&Öÿÿ ÿéóÏ&¸1œÿÿ ÿçòÐ&e&×ÿÿ ÿèôÐ&e&~}ÿÿ ÿæóÑ&¸Tÿÿ ÿéõÏ&e1ÿÿ ÿçòÏ&1&e%iÿÿÿéòÐ&™1ŸÿÿÿçóÑ&ÈðÿÿÿçòÐ&É&™1¤ÿÿÿéõÐ&™ÿÿ ÿéöÏ&!ÿÿ ÿéöÏ&!yÿÿ ÿéöÏ&!! ÿÿ ÿéöÏ&ï!ÿÿ ÿèõÏ&àáÿÿ ÿéöÏ&!Ýÿÿ ÿèõÏ&ÁÂÿÿ ÿéöÏ&! !ÿÿ ÿêøÐ&ê%€ÿÿ ÿçöÏ&ÑÿÿÿéçÇ&/1³ÿÿÿêîÉ&ª&«©ÿÿÿêöÆ&Ä1·ÿÿÿëíÆ&Ä1¹ÿÿÿçîÆ&Ä1ºÿÿ ÿðõÆ&ÞÄÿÿ ÿéòÆ&ÄYÿÿ ÿèòÈ&(& é ìÿÿÿéñÆ&Ä1»ÿÿ ÿèõÆ&Å1¼ÿÿÿèíÆ&Ä1½ÿÿ ÿèòÈ&(& é1¾ÿÿÿéíÆ&Ä"ÿÿÿìíÆ&Ä1¿ÿÿ ÿèéÈ&Ç:ÿÿ ÿéöÏ&ÇÆÿÿ ÿîõÈ&Ç&óÿÿ ÿéëÏ&Ǻÿÿ ÿéöÏ&Ç&ôÿÿÿéñÅ&±&õÿÿ ÿéöÈ&Çñÿÿ ÿéòÐ&Çiÿÿ ÿèõÏ&Ç*dÿÿ ÿçòÐ&Ç&öÿÿ ÿéóÏ&Ç&÷ÿÿ ÿéóÏ&Ç÷ÿÿÿçìÏ&&¬ÿÿ ÿéôÑ&ÇÜÿÿÿéóÑ& ÿÿÿéõÏ&ÿÿÿéëÈ&çÿÿ ÿéôÐ&Ç—ÿÿ ÿéõÏ&.òÿÿ ÿéôÑ&ÇÈÿÿ ÿëôÏ&Ç|ÿÿ ÿéóÏ&²Çÿÿ ÿçëÈ&Ç&sÿÿÿèóÏ&{ÿÿ ÿè÷Ñ&ÏÇÿÿ ÿîóÏ&Ç™ÿÿÿéõÐ&!ÿÿ ÿîõÉ& •Çÿÿ ÿéôÏ&,dÿÿÿé÷Ñ&$#ÿÿÿéðÑ&&€ÿÿÿèóÎ&ƒ&‚ÿÿÿèôÏ&Þ&1ÃÿÿÿèôÒ&&øÿÿ ÿéöÑ&&ùÇÿÿÿéóÎ&&†‡ÿÿ ÿèôÈ&hÿÿ ÿé÷Æ&XTÿÿÿéõÏ&_`ÿÿ ÿé÷Æ&TUÿÿ ÿé÷Æ&TVÿÿ ÿé÷Æ&TWÿÿ ÿéñÒ&-·°ÿÿ ÿé÷Æ&T1Äÿÿ ÿçðÐ&>&;1ÅÿÿÿêõÏ&´&±²ÿÿ ÿé÷Æ&T&zÿÿ ÿéúÏ&µ&¶³ÿÿÿêíÄ&÷öÿÿÿêíÅ&ö1ÉÿÿÿêíÇ&ö1ÊÿÿÿéôÏ&ø1Ëÿÿ ÿçëÏ&&{Èÿÿ ÿéæÏ&Ô¢ÿÿ ÿéëÐ&›Õÿÿ ÿéæÏ&'¢ÿÿÿçæÏ&¢1ÌÿÿÿéæÏ&Ö¢ÿÿÿéëÐ&&ú›ÿÿ ÿéëÎ&›kÿÿÿèæÏ&¢ÿÿÿéëÎ&R›ÿÿ ÿçëÎ& í›ÿÿÿéëÏ&&û›ÿÿ ÿéëÎ&&ü›ÿÿÿéæÏ&§¢ÿÿÿéæÏ&¢&ýÿÿ ÿèæÏ&¢&þÿÿÿéëÎ&&ÿ›ÿÿ ÿéëÐ&'›ÿÿÿèëÏ&›0Åÿÿ ÿèèÎ&óÒÿÿ ÿéëÎ&ß›ÿÿÿèìÏ&¤ÿÿÿéëÎ&›'ÿÿÿèëÏ&'›ÿÿ ÿéæÏ& ¢ÿÿ ÿéìÏ&Ç'ÿÿ ÿéëÑ&f›ÿÿ ÿéæÐ&'¢ÿÿÿéëÏ&£àÿÿÿèëÎ& ›ÿÿ ÿéëÎ&›eÿÿÿéëÎ&›dÿÿ ÿéæÏ&0Æ0Çÿÿ ÿé÷Ï&C0ÿÿ ÿéëÐ&›' ÿÿ ÿèëÐ&Í›ÿÿ ÿéëÎ&› ÿÿ ÿéëÏ&!¯›ÿÿÿéëÎ&!°›ÿÿ ÿéõÐ&ò!­ÿÿÿèëÎ&¤›ÿÿ ÿéëÏ&!®›ÿÿ ÿéæÐ&¢¡ÿÿ ÿéëÑ&Y›ÿÿ ÿéëÏ&›.ãÿÿÿéæÏ&¢.ÖÿÿÿéëÎ&'&›ÿÿ ÿèëÐ&T›ÿÿ ÿéëÐ&›' ÿÿ ÿéëÎ&£›ÿÿÿéæÏ&¢ ÿÿÿéæÏ&!€¢ÿÿ ÿéëÏ&›' ÿÿÿéëÐ&›' ÿÿ ÿèôÑ&' 'ÿÿ ÿéëÏ&!~›ÿÿ ÿéæÏ&¡¢ÿÿ ÿéîÑ&)™ÿÿÿéëÎ&¥›ÿÿ ÿéëÐ&›¢ÿÿ ÿéëÏ&£›ÿÿ ÿéëÎ&›¤ÿÿÿéëÐ& H›ÿÿÿéæÐ&0Ç&]ÿÿÿèëÏ&›*èÿÿÿéìÏ&ÿÿ ÿçëÎ&'›ÿÿÿéëÏ&›'ÿÿÿéëÏ&'›ÿÿ ÿèìÏ& ÙÿÿÿçëÏ&Ý›ÿÿÿèëÎ&'(›ÿÿÿéëÐ& &›ÿÿ ÿéëÐ&,e›ÿÿÿéëÎ&''›ÿÿ ÿéëÎ&›ºÿÿÿéìÏ&$ÑÿÿÿéñÐ&')'*ÿÿ ÿèìÏ&$ÿÿ ÿèìÏ&†ÿÿ ÿéìÏ&#ÚÿÿÿéìÏ&'+ÿÿÿéëÎ&›"ÿÿ ÿéëÏ&˜›ÿÿ ÿéîÐ&0à0áÿÿÿéëÑ&›,fÿÿÿéëÏ&#B›ÿÿÿéëÏ&',›ÿÿ ÿéëÐ&›ÿÿÿéìÏ&0Èÿÿ ÿéëÏ&›*êÿÿ ÿçóÐ&þ1Íÿÿ ÿéëÏ&à-æÿÿ ÿçìÑ&'.ÿÿ ÿéëÎ&›'-ÿÿ ÿéëÐ&›'/ÿÿ ÿéìÏ&*ëÿÿ ÿéëÎ&'0›ÿÿÿèëÏ&'1àÿÿ ÿéëÏ&³›ÿÿ ÿéëÏ&›&1ÎÿÿÿéëÐ&ßàÿÿ ÿéòÑ&É.ÿÿÿéëÎ&›1Ïÿÿ ÿéìÒ&'2ÿÿ ÿéëÎ&'4›ÿÿÿéìÏ&'3ÿÿÿéëÑ&&Á›ÿÿ ÿéìÏ&0æÿÿ ÿçìÑ&%uÿÿÿéîÑ&0á0âÿÿ ÿçëÐ&9›ÿÿÿé÷Ñ&&Â0âÿÿ ÿèëÑ&à'5ÿÿ ÿçëÐ&à'6ÿÿ ÿéëÒ&à1ÐÿÿÿéëÐ&à1Ñÿÿ ÿéóÏ&ê1ÿÿ ÿëèÐ&ëÿÿ ÿéíÏ&ìÆÿÿ ÿæêÏ&í*uÿÿÿèïÑ&NLÿÿ ÿéîÏ&,g ðÿÿÿêïÎ&,hûÿÿ ÿéíÏ&î´ÿÿ ÿéíÏ&î'7ÿÿ ÿéíÏ&ÜîÿÿÿêôÐ&û0‰ÿÿ ÿéîÏ&' ðÿÿ ÿéîÏ&¸ ðÿÿ ÿéîÏ&Œ ðÿÿ ÿæîÏ& ð(+ÿÿ ÿéîÏ&'8 ðÿÿ ÿéïÏ&*î òÿÿ ÿéíÏ&î’ÿÿÿèñÐ&v&÷*ïÿÿÿéëÍ&ÿÿÿéõÏ&!²!±ÿÿ ÿéîÏ& ð¥ÿÿÿéïÏ& ñÿÿ ÿéîÏ&, ðÿÿ ÿéîÐ& ð¢ÿÿÿçõÏ&(&'91Òÿÿ ÿèíÏ&îÁÿÿÿéîÏ&¤ ðÿÿ ÿéîÏ&* ðÿÿÿéïÏ&¥ ñÿÿÿéîÏ& ¯ ðÿÿÿéæÉ&ò ôÿÿ ÿë÷Ï&';':ÿÿ ÿèîÏ&’ ðÿÿ ÿéïÑ&“ ñÿÿ ÿéïÏ&'< ñÿÿ ÿéîÏ& ï ðÿÿÿéïÏ& ñ.ÿÿ ÿéïÏ& ñ!iÿÿÿéïÏ& ñ …ÿÿÿéîÈ& &àáÿÿ ÿéïÏ&] ñÿÿ ÿêóÐ&*ð1Óÿÿ ÿéïÏ&$ ñÿÿ ÿéöÏ&3-çÿÿÿèîÏ&L&å ÿÿ ÿçôÐ&%)1Ôÿÿ ÿéîÏ& ðŠÿÿÿé÷Ï&(.&† õÿÿ ÿèïÏ&*ñ ñÿÿ ÿéïÏ&#“ ñÿÿ ÿéïÐ&Ê ñÿÿÿéïÐ& ò'=ÿÿ ÿéïÐ& ñ1Õÿÿ ÿéïÏ& ò'>ÿÿ ÿéïÑ&'? òÿÿÿêõÎ&í"-ÿÿÿèïÏ&'@ òÿÿ ÿèõÐ&ô&õpÿÿ ÿèïÏ&ó òÿÿ ÿéïÏ& òÿÿÿéïÏ&& óÿÿ ÿèïÑ& ò'Aÿÿ ÿéïÏ& ò*ÿÿ ÿéçÐ&Y_ÿÿ ÿéçÐ&Y^ÿÿ ÿéçÐ&Y`ÿÿ ÿéçÐ&Y1Öÿÿ ÿéçÐ&Yaÿÿ ÿéùÐ&Y1×ÿÿ ÿéçÐ&bYÿÿ ÿéçÐ&Y1Øÿÿ ÿéíÍ&Z\ÿÿ ÿéçÏ&[cÿÿ ÿéçÏ&[dÿÿ ÿéíÍ&Z1Ùÿÿ ÿéíÍ&ëZÿÿ ÿêóÐ&üûÿÿ ÿéíÍ&Z1Úÿÿ ÿéíÍ&Z1Ûÿÿ ÿéóÏ& ëëÿÿ ÿçöÏ&.vÿÿÿòîÇ&âéÿÿÿòîÇ&âêÿÿÿòîÇ&âíÿÿÿòîÇ&â1ÜÿÿÿòîÇ&âéÿÿÿòîÇ&âãÿÿÿòîÇ&âçÿÿÿòîÇ&èâÿÿÿòñÇ&â1ÝÿÿÿòîÇ&â1ÞÿÿÿòôÇ&æ&âåÿÿÿòîÇ&äâÿÿÿòîÇ&â1ßÿÿÿòîÇ&â1àÿÿÿðîÊ&ðñÿÿÿòîÇ&âîÿÿÿòîÇ&â1áÿÿÿòîÇ&â1âÿÿÿòîÇ&â1ãÿÿÿòîÇ&â1äÿÿÿòðÇ&â1åÿÿÿòîÇ&â1æÿÿÿðîÊ&ñòÿÿÿòîÇ&âóÿÿÿòîÇ&â1çÿÿ ÿéõÏ&êÿÿ ÿèòÏ&"0ÿÿ ÿèñÏ&"!ÿÿ ÿèöÏ&"¶ÿÿÿéæÐ&*#+ãÿÿ ÿêëÏ&&SúÿÿÿêëÏ&úÃÿÿÿéêÎ&`1ôÿÿÿéêÐ&`1öÿÿ ÿéëÄ&ÌxÿÿÿéêÀ&*ó`ÿÿ ÿéëÏ&*òÌÿÿÿìõÏ&!±1÷ÿÿ ÿéëÐ&Ìÿÿ ÿéëÐ&¿Ìÿÿ ÿëðË&àáÿÿ ÿéëË&ÍÌÿÿ ÿéõÏ&1øÿÿÿéëÑ&Ì1ùÿÿÿèîÏ&,i/¸ÿÿ ÿèîÇ&Gsÿÿ ÿèîÇ&s1úÿÿ ÿæðÇ&s1ûÿÿ ÿèîÇ&?sÿÿ ÿèîÇ&s1üÿÿ ÿèîÇ&Csÿÿ ÿèîÇ&sÿÿ ÿèòÇ&‘sÿÿ ÿèîÊ&Aÿÿ ÿèòÇ&>sÿÿ ÿèðÇ&s1ýÿÿ ÿèóÇ&s1þÿÿ ÿèîÇ&s1ÿÿÿ ÿèðÇ&Îsÿÿ ÿèîÇ&sHÿÿ ÿèóÊ&A2ÿÿ ÿèòÇ&s! ÿÿ ÿèôÇ&s2ÿÿ ÿèòÇ&Isÿÿ ÿèóÇ&sJÿÿ ÿèóÇ&Ksÿÿ ÿèñÇ&s2ÿÿ ÿèîÇ&s2ÿÿ ÿèîÇ&Nsÿÿ ÿèïÇ&s&MLÿÿ ÿè÷Ê&AOÿÿ ÿçóÇ&s2ÿÿ ÿèôÊ&A2ÿÿ ÿæóÇ&@sÿÿ ÿèòÇ&s2ÿÿ ÿçõÇ&sPÿÿ ÿèòÇ&s2ÿÿ ÿèõÇ&s&—˜ÿÿ ÿèñÇ&sQÿÿ ÿèõÊ&A2ÿÿ ÿèîÊ&A2 ÿÿ ÿèóÊ&A2 ÿÿ ÿéõÏ&ëêÿÿ ÿèõÂ&œÿÿÿèõÏ&œBÿÿ ÿêõÎ&,j'Sÿÿ ÿéõÇ&0Ÿ'SÿÿÿèðÏ&,kïÿÿ ÿèõÐ&'Tœÿÿ ÿéòÏ&¿¾ÿÿ ÿéôÏ&'U4ÿÿÿíìÌ&âãÿÿ ÿéöÏ&™ÿÿÿéèÃ&Ò*.ÿÿÿéêÀ&`*$ÿÿÿéÞÏ&Ç*#ÿÿÿèöÊ&i*%ÿÿÿêóÄ&Ñ*#ÿÿÿíóÊ&ë*2ÿÿÿòîÇ&â2ÿÿÿéòÏ&j*#ÿÿÿçõÁ&Ó*#ÿÿÿè÷Ï&k*.ÿÿÿéíÏ&ì*#ÿÿÿééÃ&Ò*#ÿÿÿêõÂ&¹*2ÿÿÿéðÀ&l*#ÿÿÿîõÐ&m*%ÿÿÿéïÑ&NÿÿÿéîÊ&n*#ÿÿÿéôÆ&o*%ÿÿÿêòÏ&(*#ÿÿÿôöÏ&p*%ÿÿÿìóÌ&q*%ÿÿÿéóÃ&ï*%ÿÿÿêñÍ&s*%ÿÿÿèïÆ&;*#ÿÿÿéòÏ&¬*%ÿÿÿèóÏ&%Á*%ÿÿÿîóÄ&)*2ÿÿ ÿéöÐ&ÿÿ ÿéõÃ&«ÿÿÿèöÑ&Ó*%ÿÿÿéöÏ&*%ÿÿÿìõÍ&,*'ÿÿÿîõÎ&/*2ÿÿÿèöÏ&¡*%ÿÿÿéóÃ& *%ÿÿ ÿéóÇ&0‘*öÿÿÿìõÄ&õ*'ÿÿÿìõÏ&!q*'ÿÿÿéôÏ&Û*'ÿÿÿêñÏ&ú&Q*'ÿÿÿéöÐ&*ô*õÿÿÿéñÍ&ñ*2ÿÿÿéòÐ&±*2ÿÿÿèôÏ&Ê*'ÿÿ ÿèæÐ&0’2ÿÿÿèôÏ&'W*%ÿÿÿèõÏ&*#ÿÿÿèèÐ&/&'X*%ÿÿÿèôÏ&(/*%ÿÿÿéîÐ&'Y*'ÿÿÿéöÏ&B*'ÿÿÿéóÆ&…*/ÿÿÿéõÑ&²*%ÿÿÿìðÏ&{*'ÿÿÿìóÊ&'Z&'[**ÿÿÿéóÏ&%¢*%ÿÿ ÿéôÇ&*÷*õÿÿÿêñÅ&0•*2ÿÿÿêìÐ&*2*ÙÿÿÿèòÆ&0$*/ÿÿÿéõÍ&*2ÿÿ ÿéøÏ&'E'\ÿÿÿéöÏ&/*2ÿÿÿèöÄ&Ó*%ÿÿÿéñÌ&¦*2ÿÿÿèíÏ&£*%ÿÿÿéòÁ&*&. ÿÿÿìõÏ&*%2ÿÿÿéõÏ&¢*2ÿÿÿòñÇ&%þ*'ÿÿÿèñÅ&À*&ÿÿÿèðÂ&& *%ÿÿÿëóÏ&'F**ÿÿÿêìÏ&)þ*#ÿÿÿéóÏ&Ô*%ÿÿÿèõÏ&–*1ÿÿÿéóÅ&'ì*%ÿÿÿêðÇ&Ã*&ÿÿÿèôÂ&Ø*'ÿÿÿçõÏ&Y*'ÿÿÿèòÏ&&*2ÿÿÿêïÏ&(0&***ŠÿÿÿèóÏ&î*#ÿÿÿéõÃ&3*%ÿÿÿéõÏ&*#ÿÿÿêëÇ&*'0ñÿÿÿéòÆ&*3*'ÿÿÿéôÌ&(1ÿÿÿè÷Ï&ñ*%ÿÿÿíòÌ&-**ÿÿÿé÷Ï&S*/ÿÿÿêëÐ&W*2ÿÿÿéóÁ&¶*%ÿÿÿéöÏ&**&2ÿÿÿèìÇ&&*%ÿÿÿõòÆ&*20iÿÿÿéóÅ&&*%ÿÿÿéïÐ&R*#ÿÿÿêóÌ&&*'ÿÿÿõöÄ&Á*%ÿÿÿéóÏ&"W*#ÿÿÿòõÆ&*#ÿÿÿéóÎ&*%ÿÿÿòòÇ&&*'ÿÿÿééÏ&µ*%ÿÿÿíôÐ&?**ÿÿÿèöÎ&***™ÿÿÿéòÏ&**0°ÿÿÿêõÐ&'G*%ÿÿÿéóÐ&U*%ÿÿ ÿéêÐ&÷*uÿÿÿéóÐ&*%0÷ÿÿÿéõÐ&2ÿÿÿéõÐ&* *'ÿÿÿéòÎ&²*'ÿÿÿêóÏ&g&f*'ÿÿ ÿéóÆ&¸·ÿÿÿéñÍ&¡*'ÿÿÿèðÎ&(2*%ÿÿÿêêÏ&+Õ&(3*+ÿÿÿèòÏ&'H*'ÿÿÿíôÏ&'I*/ÿÿÿéõÏ&!*%ÿÿÿêòÏ&S*#ÿÿÿîóÏ&Â*%ÿÿÿöñÎ&**2ÿÿÿéóÇ&Á*#ÿÿÿé÷Ï&+*'ÿÿÿëòÐ&Ù*'ÿÿÿðóÄ&Õ*%ÿÿÿè÷Ï&)*2ÿÿÿèëÏ&­&à**ÿÿ ÿéóÐ&-&ÇÿÿÿéðÏ&¶*%ÿÿÿéôÏ&Z*/ÿÿÿèóÈ&'K&³ÿÿÿèõÍ&©*%ÿÿÿéòÏ&*%0ÕÿÿÿéöÏ&˜*/ÿÿÿéõÐ&\*2ÿÿÿêíÑ&ž*'ÿÿÿééÐ&'J*%ÿÿÿèöÎ&Ö*2ÿÿÿéóÐ&•*'ÿÿÿéóÏ&?*'ÿÿÿéóÏ&x*%ÿÿÿé÷Ì&B*'ÿÿÿòõÅ&Ô*%ÿÿÿéôÏ&N**ÿÿÿéëÉ& ³*'ÿÿÿëòÎ&±*'ÿÿÿéóÏ&K&q*+ÿÿÿéóÇ&Ö*'ÿÿÿéîÈ&.*'ÿÿÿèïÍ&0*'ÿÿÿéîÏ&v*'ÿÿÿèíÏ&*%ÿÿÿïóÏ&*%ÿÿÿéôÑ& ¸*%ÿÿÿéñÐ&ô*/ÿÿÿèíÒ&¾*%ÿÿÿéóÏ&*2-èÿÿÿêöÉ&$*'ÿÿÿïóÈ&*'ÿÿÿññÄ&ß*'ÿÿÿëóÉ&**&ú2 ÿÿÿëôÏ&*'ÿÿÿéòÏ&'N*'ÿÿÿêíÏ&*%.âÿÿÿêóÓ&¹*'ÿÿÿéõÐ&V**ÿÿÿéóÎ&Ø&Ù**ÿÿÿèóÏ&X*'ÿÿÿéõÇ&'O*'ÿÿÿèñÑ&ë*%ÿÿÿçôÏ&Ö*'ÿÿÿèòÐ&Ö*'ÿÿÿëïÏ&º&•**ÿÿÿêóÍ&E*%ÿÿÿæóÆ&Í*%ÿÿÿèóÏ&'P*/ÿÿÿé÷Ï&'Q*'ÿÿÿéòÏ&'R*'ÿÿÿéêÐ&c*2ÿÿÿñóÄ&ç*'ÿÿÿèñÅ&&i&&e*+ÿÿÿéöÆ&&,*/ÿÿÿêóÆ&¸'_ÿÿÿéôÏ&'^*'ÿÿÿêôÏ& *'ÿÿÿéñÏ&0fÿÿÿéóÏ&**0qÿÿÿèôÍ&(8*2ÿÿÿèñÍ&*'ÿÿÿéðÒ&0í*'ÿÿÿéôÈ&/*'ÿÿÿéóÏ&ò*%ÿÿÿæ÷Æ&'b*'ÿÿÿèîÐ&Î*!ÿÿÿêòÏ&*20ŽÿÿÿæîÐ&=*/ÿÿÿèôÆ&é*%ÿÿÿé÷Ñ&*2,ÍÿÿÿçõÑ&*% ÿÿ ÿèóÇ&s2!ÿÿÿëòÆ&'c*%ÿÿÿéôÏ&*'ÿÿÿçõÏ& Õ*'ÿÿÿéõÐ&ã**ÿÿÿéõÊ&**ÿÿÿéìÐ&–&*'ÿÿÿéòÐ&**'ÿÿ ÿéóÐ&Ç2"ÿÿÿðóÏ&*'.—ÿÿÿéóÈ&'d*'ÿÿÿéóÅ&Š*'ÿÿÿñóÇ&*"**ÿÿÿéóÏ&Í*%ÿÿÿéöÇ&”*%ÿÿÿèóÐ&*/2#ÿÿÿìöÎ&*ù2$ÿÿÿèîÐ&*2,lÿÿÿéðÐ&Q*2ÿÿÿëñÏ&­&¬*2ÿÿÿéóÐ&*'*(ÿÿÿêöÏ&`&_*'ÿÿÿéîÁ&**/¸ÿÿÿèøÎ&'±**ÿÿÿèòÏ&²*/ÿÿÿèöÎ&*'»ÿÿ ÿéîÑ&„…ÿÿÿéôÐ&Ÿ**ÿÿÿèïÏ&*1& 2%ÿÿÿéíÃ&°*/ÿÿÿçöÏ& _*2ÿÿÿçõÏ&Þ*&ÿÿÿçòÏ&Ù*'ÿÿÿìöÏ&*''eÿÿÿéóÏ&%Ó*%ÿÿÿéòÐ&*''fÿÿÿìõÑ&Ü&Ý*'ÿÿÿè÷Ð&**2&ÿÿÿéóÏ&&*+ÿÿÿêòÏ&*',mÿÿÿïòÏ&£*'ÿÿÿíòÏ&*/&è2'ÿÿÿèðÇ&, *1ÿÿÿéëÇ&'Û*2ÿÿÿéóÐ&*',nÿÿÿêøÅ&ü**ÿÿÿéòÏ& V*'ÿÿÿéòÏ&¿*'ÿÿÿéöÏ&*1.éÿÿÿéóÀ&*+&~*ÿÿÿéñÎ&*2(AÿÿÿñòÏ&+*'ÿÿÿêóÑ&"f*'ÿÿ ÿéóÏ&­ýÿÿÿéìÊ&f2(ÿÿÿéóÏ&1*'ÿÿÿèóÏ&*2+­ÿÿÿéîÄ&*/*0ÿÿÿéõÏ&2)ÿÿÿèìÏ&'g*'ÿÿÿèôÏ&Û*%ÿÿÿéëÎ&'h*'ÿÿÿèõÆ& Õ**ÿÿÿéìÐ&*1&¿2*ÿÿÿóòÅ&#j*'ÿÿ ÿèõÏ&-2+ÿÿÿéóÐ& I*'ÿÿÿèñÆ&'i*+ÿÿÿéïÏ&*+&[2,ÿÿÿèôÐ&]*%ÿÿÿèõÑ&&*+ÿÿÿêóÐ&*+&b2-ÿÿÿæñÏ&*'*ûÿÿÿëîÐ&**&,o2.ÿÿ ÿéòÏ&, 2/ÿÿÿèðÏ&*+&*ü 8ÿÿÿéóÐ&*'20ÿÿÿéôÏ&€&®*1ÿÿÿéôÑ&9*'ÿÿÿèñÑ&**0—ÿÿÿçòÐ&*'21ÿÿÿéìÑ&!À…ÿÿÿéîÑ&ã**ÿÿÿèóÏ&!ê*'ÿÿÿèöÏ&á&%Ã*+ÿÿÿïóÎ&Ù*'ÿÿÿêôÐ&"¤*'ÿÿÿëîÉ& r*'ÿÿÿèóÆ&#>*'ÿÿÿéóÏ&*'"µÿÿÿéëÏ&!}&à*1ÿÿÿéñÏ&#Ñ*'ÿÿÿé÷Ï&q&r*+ÿÿÿéöÐ&ß**ÿÿÿèóÎ&?**ÿÿ ÿçæÉ&924ÿÿÿéõÏ&.&/**ÿÿÿéîÇ&!#*'ÿÿÿéñÏ&'j*1ÿÿÿéóÑ&X*2ÿÿÿéôÏ&¼*'ÿÿÿçòÇ&'›*'ÿÿÿéõÈ&*%ÿÿÿéïÅ&"G**ÿÿÿèóÏ&â**ÿÿÿéõÏ&"ë*/ÿÿÿéõÏ&£**ÿÿÿéîÈ&&ˆ**ÿÿÿéïÏ&**&[25ÿÿÿèìÉ&à*'ÿÿÿéòÐ&"ö*'ÿÿÿèóÎ&!ö*'ÿÿÿêòÐ&L26ÿÿÿìõÑ&*2"±ÿÿÿèôÏ&$€*/ÿÿÿîòÏ&"*'ÿÿÿéóÏ&!»*'ÿÿÿéõÑ&ä**ÿÿÿéôÏ&!"*'ÿÿÿèòÏ&!æ*/ÿÿÿèõÓ&'œ**ÿÿÿëôÇ&! */ÿÿÿëïÐ&”&•*+ÿÿÿòôÏ&"g**ÿÿÿéòÏ&*'0¢ÿÿÿéëÑ&*ý.ÏÿÿÿéóÐ&!ñ*'ÿÿÿíõÐ&**/ÿÿÿéöÏ&*'ÿÿÿèôÏ&#´*'ÿÿÿéëÑ&.Ï29ÿÿ ÿèóÉ&è2:ÿÿÿèõÐ&T2;ÿÿÿéóÏ&'Þ*2ÿÿÿéòÐ&% *'ÿÿÿêóÉ&*/-éÿÿÿéóÏ&$l*'ÿÿÿèõÐ& */ÿÿÿìôÏ&*'/ÿÿÿêôÏ&*''ßÿÿÿéôÈ&*'1ÿÿÿéïÑ&**-êÿÿÿïôÏ&*'*)ÿÿÿçíË&$ì*+ÿÿÿéñÇ&*2ÿÿÿéóÏ&R*'ÿÿÿêðÑ&*2‡ÿÿÿîõÏ&)i*%ÿÿÿéõÏ&*'#CÿÿÿéóË&'n*'ÿÿÿçóÐ&æ*'ÿÿÿéôÆ&'o*/ÿÿÿèöÏ&% *'ÿÿÿéîÑ&$–*'ÿÿÿçñÏ&#*2ÿÿÿèõÐ&*''pÿÿÿéóÏ&$D*'ÿÿÿéðÆ&*'$ÿÿÿíóÐ&å*/ÿÿÿéõÏ&***oÿÿÿðôÅ&É*2ÿÿÿéïÉ&.þ2<ÿÿÿèóÏ&**2=ÿÿÿçöÐ&*/.ÿÿÿÿèñÏ&*'2>ÿÿ ÿéóÐ&/'qÿÿÿéõÐ&€&*+ÿÿÿêðÏ&$|*'ÿÿÿèôÏ&**$eÿÿÿéòÈ&**/;ÿÿÿèòÐ&**2?ÿÿÿìóÈ&$A*'ÿÿÿèöÐ&**2@ÿÿÿéóÏ&**/ÿÿÿíõÉ&*+&Þ2AÿÿÿéôÉ&*'/9ÿÿÿèõÐ&*+&2BÿÿÿíõÏ&*+&Þ2CÿÿÿéòÏ&**-<ÿÿÿèôÏ&%**'ÿÿÿéõÏ&*+&!ä2DÿÿÿéõÏ&ç&è&é*+ÿÿÿçôÐ&'r**ÿÿÿéîÑ&¹*2ÿÿÿéõÒ&~*1ÿÿÿéëÏ&'s&'t2EÿÿÿéôÑ&*'2Fÿÿÿê÷Ð&°**ÿÿÿéóÏ&¡**ÿÿ ÿçóÏ&'u2GÿÿÿéóÉ&'v**ÿÿÿçóÏ&**+ÿÿÿèôÌ&**2Hÿÿ ÿéöÏ& ‹ÿÿÿé÷Í&'w**ÿÿÿêòÏ&*'/SÿÿÿéîÇ&q*+ÿÿÿòóÇ&&š*+ÿÿÿéðÆ&*'2IÿÿÿèõÐ&'x**ÿÿÿéîÐ&d&efÿÿÿéóÏ&'y*/ÿÿÿéøÑ&'z*1ÿÿÿìôÏ&'{*'ÿÿÿèôÎ&>&’*+ÿÿÿéïÏ& ó&!;*+ÿÿÿèñÏ&$&&e*+ÿÿÿéëÏ&**-ëÿÿÿæïÇ&*+,(ÿÿÿéõÑ&**#}ÿÿÿéóÏ&†**ÿÿÿçóÈ&**-ìÿÿÿéõÈ&**,×ÿÿÿéøÏ&*+&.ý2JÿÿÿèïÎ&&¨*+ÿÿÿéòÏ&%à*+ÿÿÿéõÑ&¼**ÿÿÿèñÈ&*/2KÿÿÿèíÐ&ê&Æ*+ÿÿÿçôÏ&'|*'ÿÿÿéöÏ&*+2LÿÿÿèôÎ&<**ÿÿÿèôÐ&*+&Þ2MÿÿÿéöÐ&ë*+ÿÿÿéôÏ&%é*'ÿÿÿèõÏ&'}**ÿÿÿéòÏ&'~*'ÿÿÿéòÈ&*%ÿÿÿéóÉ&'*'ÿÿÿèñÆ&'€*'ÿÿÿèôË&'**ÿÿÿíóÉ&*'2NÿÿÿéóÒ&%æ*'ÿÿÿéòÏ&#ì*/ÿÿÿèôÈ&**0Øÿÿÿè÷Ì&*+&62OÿÿÿèõÐ&*+&2PÿÿÿçìÇ&%ê*'ÿÿÿêíÑ&'‚*'ÿÿÿêõÏ&%è**ÿÿÿîïÏ&2**ÿÿ ÿêóÊ&'ƒ&'„2QÿÿÿéóÏ&ÿ*/ÿÿÿîöË&%**ÿÿÿêóÐ&&¤*'ÿÿÿèõÊ&Ô*/ÿÿÿéõÏ&*'._ÿÿÿéóÌ&%í**ÿÿÿêóÑ&**.ØÿÿÿèôÐ&**,)ÿÿÿé÷Ñ&&Ã**ÿÿÿêõÏ&*+.üÿÿÿèöÑ&'…**ÿÿÿéñÈ&'*'ÿÿÿéòÏ&**.åÿÿÿçôÏ&'†*/ÿÿÿêöÏ&'‡*1ÿÿÿé÷Ê&ö**ÿÿÿìöÑ&)ô**ÿÿÿïöÏ&**2TÿÿÿèõÐ&*+&2UÿÿÿéõË&¤**ÿÿÿèôÏ&'Š*+ÿÿÿéóÐ&*+/ÿÿÿèôÏ&*/-ÿÿÿéõÔ&'‹**ÿÿÿéðÏ&*12VÿÿÿèöÐ&'Œ*'ÿÿÿéðÏ&**,†ÿÿÿéõÑ&!ä&!Ã*+ÿÿÿéöÐ&*-&2WÿÿÿéðÏ&'**ÿÿÿèôÐ&*+&Þ2XÿÿÿïõÑ&*+-îÿÿÿéòÏ&*1.ÕÿÿÿéòÐ&**&!Ä2YÿÿÿèóÊ&**.æÿÿÿéôÒ&Q**ÿÿÿéñÊ&'Ž**ÿÿÿíôÐ&P**ÿÿÿíõÈ&*+&Þ2ZÿÿÿéôÏ&'**ÿÿÿéòÊ&'*+ÿÿÿèôÏ&í*+ÿÿÿêõÎ&*-&/2[ÿÿÿéôÐ&*'7ÿÿÿçøÒ&Õ**ÿÿÿîôÏ&'‘**ÿÿÿéïÑ&*++ÿÿÿéöÏ&**/ ÿÿÿêóÐ&***,ÿÿÿéõÏ&**.`ÿÿÿèòÏ&**2\ÿÿÿéöÒ&**)øÿÿÿé÷È&÷*+ÿÿÿçõÌ&*+0”ÿÿÿéòÏ&'’**ÿÿÿééÊ&È&ÄÅÿÿÿîñË&%Q**ÿÿÿèôÒ&%*/ÿÿÿèïÏ&*+& 2]ÿÿ ÿéôË&’&“”ÿÿÿçôÐ&*+*þÿÿÿè÷È&R*1ÿÿÿéøÑ&û*/ÿÿÿéöÐ&H*+ÿÿÿéëÊ&*+&&®2_ÿÿÿèøÏ&*+&,c2`ÿÿÿèöÏ&*+&2aÿÿ ÿéóÏ&,~,€ÿÿÿèîÐ&»-ïÿÿÿèñÈ&**,ÀÿÿÿéòÑ&*ÿ+ÿÿÿéîÑ&¿2bÿÿ ÿéõÌ&,2cÿÿÿèòÑ&7*%ÿÿÿèòË&*+,}ÿÿÿéñÑ&%÷**ÿÿÿéòÎ&*+-ðÿÿÿèôÏ&*+2dÿÿÿéóÏ&O*+ÿÿÿéóÏ&*-&šYÿÿÿéóÌ&'“*+ÿÿÿëñË&ÿ& ¤ £ÿÿÿéóÏ&†&)“*-ÿÿ ÿéöÈ&%-$ÉÿÿÿêòÏ&**2eÿÿÿçöÏ&*+2fÿÿÿéóÉ&*+.çÿÿÿçìÑ&ï&îíÿÿÿçõÐ&**‡ÿÿÿéöÏ&*+2gÿÿ ÿéôÏ&,,€ÿÿÿêïÑ&*-&è2hÿÿÿéðÌ&*10åÿÿÿéòÐ&*+2iÿÿÿèïÏ&1*+ÿÿÿééÇ&2jÿÿÿééÇ&2kÿÿÿééÇ&2lÿÿÿééÇ&ÿÿÿééÇ&2mÿÿÿééÇ&zÿÿÿééÇ&2nÿÿÿééÇ&2oÿÿÿéèÇ&2pÿÿÿééÇ&2qÿÿÿééÇ&lÿÿÿééÇ&mÿÿÿééÇ&2rÿÿÿééÇ&nÿÿÿééÇ&oÿÿÿééÇ&2tÿÿÿééÇ&wÿÿÿééÇ&2uÿÿÿééÇ&xÿÿÿééÇ&2vÿÿÿééÇ&yÿÿÿééÇ&ÿÿÿééÇ&zÿÿÿéèÇ&2wÿÿÿéèÇ&&|{ÿÿÿéèÇ&}ÿÿÿéèÇ&2xÿÿÿéèÇ&2yÿÿÿéèÇ&2zÿÿÿéèÇ&ÿÿÿéèÇ&2{ÿÿÿéèÇ&2|ÿÿÿéèÇ&2}ÿÿÿéèÇ&›ÿÿÿéèÇ&‘ÿÿÿéèÇ&2~ÿÿÿéèÇ&2ÿÿÿéèÇ&2€ÿÿÿéèÇ&2ÿÿÿéèÇ&2‚ÿÿÿéèÇ&2ƒÿÿÿéèÇ&2„ÿÿÿéèÇ&2…ÿÿÿéèÇ&2†ÿÿÿéèÇ&2‡ÿÿÿéèÇ&2ˆÿÿÿíòÏ&2½ÿÿÿêóÌ&½ÑÿÿÿñòÄ&2‰ÿÿÿè÷Ï&ðkÿÿ ÿìôÅ&'ž'ÿÿÿéðÌ&ðlÿÿ ÿîõÐ&µmÿÿÿêðÌ&ðÿÿÿî÷Ì&ðÿÿÿìõÌ&ðÿÿ ÿîôÏ&µ·ÿÿÿèíÐ&Åðÿÿ ÿéìÏ&( µÿÿÿéóÌ&ðïÿÿ ÿéêÏ&µ¾ÿÿÿêóÐ&ð¿ÿÿ ÿéëË&µ%3ÿÿ ÿéñÍ&µñÿÿ ÿéóË&µ…ÿÿÿðóÌ&ðßÿÿ ÿèôË&µÿÿ ÿêóÎ&µÀÿÿ ÿèöÑ&µÓÿÿ ÿêìÐ&µ*Ùÿÿ ÿìøÏ&µ#ÿÿÿèòÏ&ð0«ÿÿ ÿéôÏ&Ûµÿÿ ÿðóÏ&›Ùÿÿ ÿèðË&lµÿÿÿéõÑ&ð²ÿÿ ÿéóË&µ ÿÿ ÿéòÏ&¶bÿÿ ÿéöÏ&Bµÿÿ ÿðòÆ&›2ŠÿÿÿñòÏ&2‹ÿÿÿçõÌ&ð!vÿÿ ÿèñË&µÀÿÿ ÿèðË&µ& ÿÿÿêìÏ&ð)þÿÿ ÿéõÐ&µ' ÿÿ ÿðóÏ&›&œšÿÿ ÿèôÏ&µÿÿ ÿèðÐ&'¡µÿÿ ÿéìÏ&µ!wÿÿÿòõÌ&ðÿÿ ÿõöË&µÁÿÿ ÿîóÏ&µÂÿÿ ÿéóÎ&µÿÿ ÿéóË&¶dÿÿ ÿéõÏ&µÿÿ ÿêðË&õÿÿ ÿéòÐ&kµÿÿ ÿõòË&µ,ÿÿ ÿéíË&%ʵÿÿ ÿêöÏ&0®µÿÿ ÿéòÏ&0°µÿÿ ÿéïÐ&& ò2Œÿÿ ÿîòÌ&µ'¢ÿÿ ÿèôË&µØÿÿ ÿéóË&µ¶ÿÿ ÿèôÏ&¶”ÿÿ ÿçóÎ&'£µÿÿ ÿçõÏ&¶‡ÿÿ ÿéòÎ&²µÿÿ ÿêóÌ&µ&ÿÿ ÿóóÐ&¶gÿÿ ÿðóÏ&+å›ÿÿ ÿíóÏ&µ'¦ÿÿ ÿêòÏ&µ(Êÿÿ ÿñòÑ&&I'¥ÿÿ ÿíôÐ&ø¶ÿÿ ÿðòË&›&žŸÿÿ ÿéîË&.µÿÿ ÿéôÏ& ¶ÿÿ ÿðóÎ&›2Žÿÿ ÿéóÐ&¶•ÿÿÿðñÉ&++ÿÿ ÿðòÏ&›ÿÿ ÿé÷Ì&¶Bÿÿ ÿéòÑ&¶&SQÿÿ ÿé÷Ï&+µÿÿ ÿëõÏ&+2ÿÿ ÿéõË&µ'Oÿÿ ÿêõÑ&µŽÿÿ ÿëòÐ&µÙÿÿ ÿéðÏ&¶µÿÿ ÿêöË&µ$ÿÿ ÿðóÑ&H&I›ÿÿ ÿéóË&µÿÿ ÿòõË&µÿÿÿ ÿðóË&µÕÿÿ ÿéôÑ&µ ¸ÿÿ ÿðóË&B›ÿÿ ÿèíÒ&µ¾ÿÿÿñòÆ&Þÿÿ ÿèõÏ&µáÿÿ ÿðõÏ&›2ÿÿ ÿèïÍ&µ0ÿÿ ÿññË&¶ßÿÿ ÿéõÐ&µóÿÿ ÿéôÏ&µ°ÿÿ ÿíôÎ&µ,”ÿÿ ÿéóÎ&¶&ØÙÿÿ ÿéóÏ&µ2‘ÿÿ ÿèóÏ&µ0½ÿÿ ÿéôÐ&µ'§ÿÿ ÿçõÑ&µŽÿÿ ÿèñÐ&&f&&e¶ÿÿ ÿêòÑ&'¨¶ÿÿ ÿîôÐ&è2’ÿÿ ÿðòÏ&˜&›2“ÿÿ ÿêðÏ&µ2”ÿÿ ÿéõÏ&/=¶ÿÿ ÿæ÷Ë&µ'bÿÿ ÿéôÏ&µÿÿ ÿæîÐ&¶=ÿÿ ÿèíË&µ'©ÿÿ ÿéôÑ&'ªµÿÿ ÿñóË&µçÿÿ ÿêôÏ&'«µÿÿ ÿçøÏ&¶:ÿÿ ÿéõË&ÿÿ ÿéñË&µ'¬ÿÿ ÿéõÌ&µ'aÿÿ ÿêòÓ&µiÿÿ ÿéðÒ&µ0íÿÿ ÿñòË&µ0@ÿÿ ÿéöÏ&µ'­ÿÿ ÿéóÏ&&2–ÿÿ ÿéôË&[µÿÿ ÿèìË&µ'®ÿÿ ÿðöÏ&›2—ÿÿ ÿìöÑ&¶ ÿÿ ÿèöÏ&¶<ÿÿ ÿèòÐ&µzÿÿ ÿóòË&µ#jÿÿ ÿìõÐ& Êÿÿ ÿéóÏ&µ1ÿÿ ÿéôÏ&'¯µÿÿ ÿéóÏ&µ'°ÿÿ ÿçðË&µÿÿ ÿéóÑ&µ!ÿÿ ÿèõÒ&¶ ÿÿ ÿéöÏ&Iÿÿ ÿèôÏ&µÛÿÿ ÿíòÏ&+¶ÿÿ ÿéñÎ&(Aµÿÿ ÿéöÏ&.Sÿÿ ÿèøÎ&'±¶ÿÿ ÿéëÎ&¶'hÿÿ ÿòòÎ&µrÿÿ ÿïòÏ&£¶ÿÿÿéøÏ&'²ÿÿÿ ÿéøÏ&ÿÀÿÿ ÿéìË&µÿÿ ÿéòÐ&µ%Ðÿÿ ÿêóÑ&"fµÿÿ ÿéôÏ&ÿµÿÿ ÿéóË&µŸÿÿ ÿéïË&¶(@ÿÿ ÿêõË&µ/ÿÿ ÿçöÏ& _µÿÿ ÿðòÇ&›0Ïÿÿ ÿéôÐ&¶Ÿÿÿ ÿéíË&'³µÿÿ ÿéòÐ&µ'fÿÿ ÿðòÇ&#k›ÿÿ ÿèíË&&ÇÆÿÿ ÿéîË&¶èÿÿ ÿìôÎ&,¶ÿÿ ÿèôÏ&'صÿÿ ÿìôË&¶'´ÿÿÿíòÐ&à 4ÿÿ ÿéòË&¶.Lÿÿ ÿîôÑ&y&èzÿÿ ÿðôË&¶ÿÿ ÿéíË&5µÿÿ ÿéõÏ&:¶ÿÿ ÿéîË&µ!#ÿÿ ÿèõÎ&¶@ÿÿ ÿé÷Ï&¶&rqÿÿ ÿîôÏ&$a&'µèÿÿ ÿéîË&¶ sÿÿ ÿéñË&µ"ÿÿ ÿñôÐ&&Ú#fÿÿ ÿè÷Ë&¶"Cÿÿ ÿéîË&&ˆ¶ÿÿ ÿéôÏ&&Øÿÿ ÿïóÎ&µÙÿÿ ÿéöÏ&¶ÿÿ ÿìõÑ&'¶ÿÿ ÿêôË&µ3ÿÿ ÿïñË&¶#ÉÿÿÿéôÏ&Á4ÿÿ ÿðòÉ&›2šÿÿ ÿéëË&µ þÿÿ ÿçõË&Yµÿÿ ÿéóÏ&¶Íÿÿ ÿéñÏ&µ)‚ÿÿ ÿéóÑ&µXÿÿ ÿêõË&¶:ÿÿ ÿîôÏ&¶!^ÿÿ ÿéõË&¶ pÿÿ ÿîôÐ&…& èÿÿ ÿòòË&J&¶2›ÿÿ ÿéöÐ&ßÿÿ ÿéòË&¶wÿÿ ÿîôÈ&„&è2œÿÿ ÿêñË&J&¶2ÿÿ ÿîôÊ&è2žÿÿ ÿéöÒ&/E¶ÿÿ ÿéñÑ&µ#ôÿÿÿíòÏ&Þ&ßàÿÿ ÿêîË&µ#{ÿÿ ÿéõÏ&32Ÿÿÿ ÿéöÏ&Nµÿÿ ÿîòÏ&Lµÿÿ ÿðòÑ&›&¿Àÿÿ ÿéóÐ&${&Mÿÿ ÿèóË& áµÿÿ ÿéõÏ&µ#Cÿÿ ÿéõÏ&¶*oÿÿ ÿîôÐ&ð&è2 ÿÿ ÿéôÑ&¶Ðÿÿ ÿéòÐ&¶% ÿÿ ÿéôË&¶2¡ÿÿ ÿéõË&¶$ÿÿ ÿéîÑ&$–¶ÿÿÿïñÑ&G2¢ÿÿ ÿéóÑ&µ'¶ÿÿ ÿçñÏ&µ#ÿÿ ÿîôÏ&è&…2£ÿÿ ÿèõË&¢µÿÿ ÿéðË&µ$ÿÿ ÿéòÏ&¶2¤ÿÿ ÿðôÐ&¶2¥ÿÿ ÿçóÐ&µæÿÿ ÿéôË&ȶÿÿ ÿðôË&¶Îÿÿ ÿéñÏ&¶%ÿÿ ÿîõÑ&…&èÿÿ ÿðòÐ&›&  ÿÿ ÿéóÑ&bÿÿ ÿéíÏ&-:ÿÿ ÿçðÒ&¼2¦ÿÿ ÿêôÑ&'·ÿÿ ÿîôÏ&è2§ÿÿ ÿéòÐ&Ç2¨ÿÿ ÿéöÏ&µNÿÿ ÿéóÏ&¶+ÿÿ ÿèôÐ&¶'¸ÿÿ ÿîôÏ&'¹èÿÿ ÿçôÐ&µ1ÿÿ ÿèôË&.™ÿÿ ÿéôË&µ&žÿÿ ÿéóË&¶'vÿÿ ÿèöÑ&¶,yÿÿ ÿîôÉ&é&êèÿÿ ÿéõÏ&,„¶ÿÿ ÿéôÑ&n¶ÿÿ ÿéïÏ& ó&#Nÿÿ ÿèñÓ&1ÿÿ ÿîöÐ&è0 ÿÿ ÿçõÑ&'ºÿÿ ÿèñÐ&&e&&gÿÿ ÿðòÒ&›sÿÿ ÿïòÏ&$þµÿÿ ÿéîÑ&'»ÿÿ ÿéóÏ&“¶ÿÿ ÿïôÏ&k2©ÿÿ ÿéòÏ&µ-<ÿÿ ÿéíÏ&µ,zÿÿ ÿéõÐ&ê&,{ÿÿ ÿéñÐ&'¼ÿÿ ÿé÷Ë&&ì2ªÿÿ ÿîôÏ&!¨&!§èÿÿ ÿèôÎ&<ÿÿ ÿêíÑ&¶'‚ÿÿ ÿéóÏ&¶'yÿÿ ÿéòË&µÿÿ ÿéóÐ&'½¶ÿÿ ÿêóÐ&&¤¶ÿÿ ÿíóÐ&¶'¾ÿÿ ÿéõÏ&¶'¿ÿÿ ÿîôË&,|èÿÿ ÿéôÐ&'À&,"ÿÿ ÿéóÒ&µ%æÿÿ ÿîôÒ&"ì&!¨èÿÿ ÿìóÑ&"Š&$2«ÿÿ ÿìóÑ&"Š&$2¬ÿÿ ÿîöË&¶%ÿÿ ÿéöÐ&ëÿÿ ÿèöÏ&µ'Áÿÿ ÿéõÏ&¶2­ÿÿ ÿéìË&&î2®ÿÿ ÿèöÑ&¶'Âÿÿ ÿéôÏ&'öÿÿ ÿîõÐ&u&è2¯ÿÿ ÿèõÐ&&2°ÿÿ ÿî÷Ð&C&è2±ÿÿ ÿéôË&¶'Äÿÿ ÿðòÑ&›.ÿÿ ÿîôÍ&è2²ÿÿ ÿîôÐ&œ&è2³ÿÿÿñòÑ&ÿÿ ÿïóÒ&´¶ÿÿ ÿé÷Ð&¶'Åÿÿ ÿèôÏ&¶-ÿÿ ÿêòÏ&.š¶ÿÿ ÿêøÐ&+æÿÿ ÿèõÐ&µ¢ÿÿ ÿíôÐ&P¶ÿÿ ÿîôÏ&è2´ÿÿ ÿíóÏ&.uÿÿ ÿìõÊ&$2µÿÿ ÿéòÏ&0Ãÿÿ ÿéïÑ&¶+ÿÿ ÿéðË&¶%óÿÿ ÿîôÏ&'‘ÿÿ ÿîôË&%jèÿÿ ÿèôÒ&%¶ÿÿ ÿéøÑ&û¶ÿÿ ÿèïË&&ô2¶ÿÿ ÿéïÍ&2·ÿÿ ÿëôÏ&Ü&2¸ÿÿ ÿèòÐ&¶.aÿÿ ÿìöÐ&$.›ÿÿ ÿè÷Ë&Rÿÿ ÿçõÏ&#&!2¹ÿÿ ÿéñÑ&¶%÷ÿÿ ÿêóË&2ºÿÿ ÿéóË&.Gÿÿ ÿîôÉ&&¹&&¸èÿÿ ÿéôÍ&2»ÿÿ ÿèôÏ&  ÿÿ ÿéíÏ&+Kÿÿ ÿçðÐ&+2¾ÿÿÿëëÏ&'Æ2¿ÿÿÿêòÏ&'Æ2ÀÿÿÿððÐ&+2ÁÿÿÿïïÏ&+ 2Âÿÿ ÿéñË&¶$ZÿÿÿïîÏ&V2Ãÿÿ ÿéóÐ&+ 2Äÿÿ ÿëóÏ& û úÿÿ ÿéóÒ&¶%æÿÿÿèïÑ&MNÿÿÿêòÏ&øðÿÿÿèñÑ&PQÿÿÿêëÏ&”úÿÿÿìóÎ&”'ÇÿÿÿéëÎ&”Ìÿÿ ÿèðÉ&+è2Ïÿÿ ÿéòÐ&™2Ðÿÿ ÿèïÏ&¸ÿÿ ÿèìÏ&ÿÿ ÿèïÏ&|ÿÿ ÿéôÐ&+ç2Õÿÿ ÿéôÐ&+ç2×ÿÿ ÿéôÐ&+ç2Ùÿÿ ÿçôÐ&+ç2Úÿÿ ÿéòÄ&}Kÿÿ ÿïõÐ&&2Ûÿÿ ÿëôÐ&¾2Ýÿÿ ÿèôÐ&¾2Þÿÿ ÿèõÏ&&º+éÿÿ ÿïôÐ&¾2áÿÿ ÿéóÏ&& ÿÿ ÿçôÐ&*‹¾ÿÿ ÿéóÏ&ñ ÿÿ ÿéòÏ&K2ãÿÿ ÿèôÐ&¾2äÿÿ ÿéòÏ& 2åÿÿ ÿçôÑ&+ê2æÿÿ ÿèõÐ&&2çÿÿ ÿéóÐ& J ÿÿ ÿèôÐ&¾2èÿÿ ÿéõÐ&&%ÿÿ ÿèõÇ&§2éÿÿ ÿçôÒ&+ê2êÿÿ ÿçôÒ&+ê2ëÿÿ ÿéòÏ&  ÿÿ ÿìôÐ&¾2ìÿÿ ÿêöÐ&‡2íÿÿ ÿèöÐ&‡2îÿÿ ÿéòÐ&j ÿÿ ÿéõÑ&+ë2ðÿÿ ÿéòÉ&Á& 2ñÿÿ ÿçôÒ&+ê-ñÿÿ ÿéòÏ&1'ÿÿ ÿèõÏ&%œÿÿ ÿèìÏ&(Þÿÿ ÿèóÏ& #ÿÿ ÿèóÏ&&%ÿÿ ÿèôÏ&·(ÿÿ ÿèôÏ&#üÿÿ ÿèóÏ&#ýÿÿ ÿèôÏ&‡oÿÿ ÿèêÏ&(¾ÿÿ ÿèöÏ&þ2òÿÿÿèôÏ&üÿÿ ÿèìÏ&#Yÿÿ ÿèéÎ&'È'Éÿÿ ÿè÷Ï&%ÿÿ ÿèòÏ&+Ãÿÿ ÿèóÏ&(Èÿÿ ÿèóÐ&  ÿÿ ÿèëÏ&-ò%ÿÿ ÿèïÏ&#;ÿÿ ÿèñÏ&‡®ÿÿ ÿèôÏ&(ÿÿ ÿèöÏ&(¡ÿÿ ÿèõÏ&‡Rÿÿ ÿèòÏ&‡&yÿÿ ÿèìÏ&‡(¾ÿÿ ÿèõÏ&#ÿÿ ÿèõÏ&`#ÿÿ ÿèöÑ&‡­ÿÿ ÿçõÏ&(!vÿÿ ÿèôÏ&((/ÿÿ ÿèòÍ&2óÿÿ ÿèôÏ&üKÿÿ ÿèóÏ&()ÿÿ ÿèîÏ&ð#ÿÿ ÿèóÏ&‡!rÿÿ ÿèôÏ&‡Úÿÿ ÿèõÏ&‡ÿÿ ÿèñÏ&(8ÿÿ ÿçóÎ&¤¥ÿÿ ÿèôÏ&%:ÿÿ ÿèöÏ&Þ(ÿÿ ÿèóÐ&#…ÿÿ ÿèöÏ&(Þÿÿ ÿèôÏ&‡…ÿÿ ÿèëÏ&('Ìÿÿ ÿèòÏ&‡Îÿÿ ÿèòÏ&('Êÿÿ ÿèëÏ&‡)ÿÿÿ ÿèêÏ&‡jÿÿ ÿèöÏ&(äÿÿ ÿèîÏ&*Xÿÿ ÿèóÑ&-ó Kÿÿ ÿèóÏ&‡'Ëÿÿ ÿèóÏ&(Ôÿÿ ÿèõÏ&('Íÿÿ ÿèöÏ&'Î(ÿÿ ÿè÷Ï&*ÿÿ ÿèïÐ&#Rÿÿ ÿèòÏ&'Ïÿÿ ÿèóÐ&‡'Ðÿÿ ÿèëÐ&*Wÿÿ ÿèôÏ&!m(ÿÿ ÿèóÏ&#ÿÿ ÿèôÏ&‡ÿÿ ÿèóÑ& K2ôÿÿ ÿçõÏ&¦‡ÿÿ ÿèïÏ&]‡ÿÿ ÿèïÏ&‡'Ñÿÿ ÿèòÐ&#0öÿÿ ÿèòÏ&&*ÿÿ ÿèöÏ&*¢ÿÿ ÿèöÏ&±#ÿÿ ÿèöÏ&‡ÿÿ ÿèõÏ&(ÿÿ ÿèóÏ&‡ÿÿ ÿèòÏ&*.Òÿÿ ÿèóÏ&ˆ‡ÿÿ ÿèóÐ&· Kÿÿ ÿèóÏ&(Áÿÿ ÿèõÏ&*Ïÿÿ ÿèóÏ&‡æÿÿ ÿèôÐ&*&@?ÿÿ ÿè÷Ï&(Eÿÿ ÿèìÏ&a‡ÿÿ ÿèóÐ&"é Kÿÿ ÿèöÏ&(—ÿÿ ÿèóÏ&‡0Öÿÿ ÿèíÒ&*¾ÿÿ ÿèóÏ&‡gÿÿ ÿèôÐ&(Pÿÿ ÿèóÏ&(ÿÿ ÿèòÏ&‡Xÿÿ ÿèôÐ&*š2õÿÿ ÿè÷Ï&)(ÿÿ ÿèóÓ&*¹ÿÿ ÿçóÐ&Õ*Œÿÿ ÿèëÏ&‡Æÿÿ ÿèóÏ&‡Çÿÿ ÿçóÑ&È#ÿÿ ÿèõÏ&(Ôÿÿ ÿèõÐ&ó*ÿÿ ÿèóÏ&(0½ÿÿ ÿèêÏ&(µÿÿ ÿèóÍ&¨&© Kÿÿ ÿçúÐ&_*Œÿÿ ÿèôÑ&* ¸ÿÿ ÿèòÏ&‡{ÿÿ ÿèóÏ&(æÿÿ ÿèòÐ&(Ùÿÿ ÿèóÏ&*âÿÿ ÿèìÏ&‡aÿÿ ÿèîÏ&‡ÿÿ ÿèõÑ&(2ÿÿ ÿèôÏ&*`ÿÿ ÿèòÏ& ÿÿ ÿçöÐ&ø*Œÿÿ ÿéõÏ&-ô2öÿÿ ÿèóÏ&(~ÿÿ ÿèñÏ&(àÿÿ ÿèõÏ&*¼ÿÿ ÿèöÏ&(áÿÿ ÿèòÏ&ðÿÿ ÿèóÏ&‡'ÿÿ ÿèñÏ&(Lÿÿ ÿèóÏ&(ëÿÿ ÿçôÐ&*Œ0Ýÿÿ ÿèóÏ& K0Êÿÿ ÿçóÐ&Ó*Œÿÿ ÿèôÏ&( ®ÿÿ ÿèòÏ&('Òÿÿ ÿçòÐ&%>*Œÿÿ ÿèõÐ&L‡ÿÿ ÿçòÐ&¨*Œÿÿ ÿèëÏ&‡-õÿÿ ÿèôÏ&(,sÿÿ ÿçñÐ&&e&&h*Œÿÿ ÿèñÏ&(Žÿÿ ÿèéÏ&‡ÿÿ ÿè÷Ï&*'Óÿÿ ÿèóÐ&(Œÿÿ ÿèöÏ&*'Ôÿÿ ÿèøÑ&* ÿÿ ÿèôÏ&(ÿÿ ÿèòÏ&‡'Õÿÿ ÿèôÏ&‡2÷ÿÿ ÿçòÏ&(¨ÿÿ ÿèìÏ&*'®ÿÿ ÿçëÐ&–&à*Œÿÿ ÿèíÏ&*&Ó2øÿÿ ÿçõÐ&*Œ.3ÿÿÿèòÇ&2ùÿÿ ÿçòÐ&£*Œÿÿ ÿèöÏ&‡'Öÿÿ ÿçóÐ&-ö&}*Œÿÿ ÿèîÏ&‡±ÿÿ ÿçôÐ&'×*Œÿÿ ÿçóÐ&"%&**Œÿÿ ÿèïÐ&‡xÿÿ ÿèõÑ&*Oÿÿ ÿèõÐ&‡#¡ÿÿÿèòÐ&•&2úÿÿ ÿèñÏ&*(Aÿÿ ÿèôÏ&*'Øÿÿ ÿçöÐ&"H*Œÿÿ ÿç÷Ð&*Œ+)ÿÿ ÿçöÐ&*Œ.Wÿÿ ÿèóÏ&‡&Çÿÿ ÿè÷Ï&(1ÿÿ ÿèõÑ&‰(ÿÿ ÿèôÏ&(Ûÿÿ ÿèóÐ& Kÿÿ ÿèóÏ&(,ÿÿ ÿèöÏ&* ÿÿ ÿèöÏ&( `ÿÿ ÿèõÏ&*'Ùÿÿ ÿèôÐ&‡'Úÿÿ ÿèôÏ&*Yÿÿ ÿèóÈ& K0Íÿÿ ÿçôÑ&9*Œÿÿ ÿèòÏ&*¿ÿÿ ÿèîÏ&*éÿÿ ÿèñÐ&‡gÿÿ ÿèóÏ&#)ÿÿ ÿçëÐ&'Û*Œÿÿ ÿèòÏ&‡ Wÿÿ ÿçóÐ&Ÿ*Œÿÿ ÿèóÏ&‡'ÿÿ ÿçóÐ&/ø*Œÿÿ ÿèõÏ&&bcÿÿ ÿèòÏ&‡,tÿÿ ÿèòÏ&(#jÿÿ ÿçõÐ&¨*ŒÿÿÿèòË&2ûÿÿ ÿèôÏ&*Òÿÿ ÿèñÐ&‡2üÿÿ ÿèðÑ&*/ƒÿÿ ÿçõÏ&*Yÿÿ ÿèòÏ&&Ú#aÿÿ ÿèóÏ&*"µÿÿ ÿèñÏ&‡'Üÿÿ ÿçöÐ& ‹*Œÿÿ ÿèñÏ&*$[ÿÿ ÿèõÏ&(#Iÿÿ ÿèôÏ&(!•ÿÿ ÿçòÐ&#¥*Œÿÿ ÿèöÏ&‡"_ÿÿ ÿèõÏ&'݇ÿÿ ÿèóÏ&*Íÿÿ ÿèóÏ&(ˆÿÿÿèòÉ&É&Êÿÿ ÿè÷Ï&"c(ÿÿ ÿèõÑ&‡1ÿÿ ÿèòÏ&‡"Fÿÿ ÿèôÏ&*!ëÿÿ ÿèòÏ&*"ÿÿ ÿçïÐ& ÿ*Œÿÿ ÿçòÐ&"¥*Œÿÿ ÿèóÏ&‡ fÿÿ ÿèòÐ&"Êÿÿ ÿèóÏ&("ÿÿ ÿèõÏ&(°ÿÿ ÿèìÏ& †‡ÿÿ ÿè÷Ï&¥(ÿÿ ÿçïÐ&*Œ0¹ÿÿ ÿèëÏ&‡"*ÿÿ ÿèîÏ&*!#ÿÿ ÿçíÐ&Æ&")*Œÿÿ ÿçôÐ&É*Œÿÿ ÿèöÏ&*"Äÿÿ ÿèöÏ&*ÿÿ ÿèòÐ&*#íÿÿ ÿèîÑ&(%®ÿÿ ÿèóÏ&('Þÿÿ ÿèóÏ&‡,uÿÿ ÿçõÑ&$‰*Œÿÿ ÿèòÏ&‡$Áÿÿ ÿçôÏ&32ýÿÿ ÿèöÐ&*Vÿÿ ÿèôÏ&(üÿÿ ÿçòÏ&(2þÿÿ ÿèñÏ&*%ÿÿ ÿçóÐ&÷&*Œ2ÿÿÿ ÿèòÑ&&¸&ÿÿ ÿçõÐ&*Œÿÿ ÿèñÏ&("Öÿÿ ÿçôÐ&$2*Œÿÿ ÿèôÑ&(Œÿÿ ÿèõÏ&*$nÿÿ ÿèõÑ&(—ÿÿ ÿèòÏ&‡0¤ÿÿ ÿç÷Ð&Ë*Œÿÿ ÿèöÏ&*Nÿÿ ÿèóÏ& á*ÿÿ ÿèóÏ&&  Kÿÿ ÿçôÐ&$…*Œÿÿ ÿèôÏ&*'ßÿÿ ÿèïÏ&*+ÿÿ ÿèõÐ&(ÿÿ ÿèóÏ&‡$¸ÿÿ ÿçóÐ&,&*Œ3ÿÿ ÿçóÐ&/*Œÿÿ ÿèòÐ&*/Mÿÿ ÿçôÐ&*Œ3ÿÿ ÿèóÏ&(†ÿÿ ÿèôÏ&#~*ÿÿ ÿèôÏ&*'àÿÿ ÿèõÐ&0L ­ÿÿ ÿèôÏ&(%ÞÿÿÿèóË&k&3ÿÿ ÿçôÑ&n*Œÿÿ ÿçöÑ&|*Œÿÿ ÿçôÒ&*£ÿÿ ÿèóÏ&*$ùÿÿ ÿçóÐ&ÿ&"Ï3ÿÿ ÿèîÑ&*¹ÿÿ ÿèôÏ&3ÿÿ ÿèñÏ&(-÷ÿÿ ÿèóÏ&*+ÿÿ ÿèñÏ&*Øÿÿ ÿç÷Ð&°*Œÿÿ ÿèôÏ& ­& ´,ÿÿ ÿè÷Ï&‡ÿÿ ÿèöÏ&(Nÿÿ ÿçöÐ&Ò*Œÿÿ ÿçðÑ&(pÿÿ ÿèóÏ&*#ÿÿ ÿèñÑ&*'áÿÿÿèôÏ&9ÿÿ ÿèóÐ&*/_ÿÿ ÿçöÐ&.#*Œÿÿ ÿçõÐ&%è*Œÿÿ ÿçóÑ&,&"%*Œÿÿ ÿèóÐ&'â*ÿÿ ÿçòÏ&*%âÿÿ ÿèëÏ&*&+ì3ÿÿ ÿèîÏ&*&ï3ÿÿ ÿçôÐ&'ã*Œÿÿ ÿçòÐ&´*Œÿÿ ÿèöÏ&(ÿÿ ÿçöÐ&F*Œÿÿ ÿèõÍ&+*3ÿÿ ÿèôÏ&*'äÿÿ ÿçôÏ&*'åÿÿ ÿçôÐ&%(*Œÿÿ ÿèõÏ&(šÿÿ ÿèòÏ&*ÿÿ ÿèõÏ&*%åÿÿ ÿèòÏ&(üÿÿ ÿèöÑ&*&Äÿÿ ÿçóÐ&÷&*Œ3ÿÿ ÿèöÏ&,&%M3 ÿÿ ÿèòÑ&.ÿÿ ÿèõÓ&(Sÿÿ ÿèõÏ&'ç*ÿÿ ÿçóÑ&,v*Œÿÿ ÿèøÏ&*'æÿÿ ÿèôÓ&*3 ÿÿ ÿçòÐ&*Œ3 ÿÿ ÿèøÏ&¡(ÿÿ ÿèôÏ&*%òÿÿ ÿèõÑ&*. ÿÿ ÿèôÒ&Q*ÿÿ ÿçõÐ&*Œ3 ÿÿ ÿèòÏ&‡ðÿÿ ÿçóÐ&%õ*Œÿÿ ÿçôÐ&%ô*Œÿÿ ÿçóÐ&*Œ3 ÿÿ ÿçóÑ&(¡ÿÿ ÿèðÏ&*%óÿÿ ÿçöÐ&'è*Œÿÿ ÿçñÊ&A3ÿÿ ÿèñÏ&*(PÿÿÿèòÉ&Ïÿÿ ÿëðÐ&-ø&-ù3ÿÿ ÿåöÓ&¨3ÿÿ ÿèòÐ&*8ÿÿ ÿçõÐ&*Œ3ÿÿ ÿèõÐ&*3ÿÿ ÿèïÏ& ­& 3ÿÿ ÿèõÏ&*3ÿÿ ÿçöÏ& ­& ¬ ´ÿÿ ÿçôÐ&+(*Œÿÿ ÿçðÐ&*Œ3ÿÿ ÿèòÑ&‡-ÿÿ ÿèõÏ& ­+,ÿÿ ÿèôÏ&,3ÿÿ ÿèðÏ&*3ÿÿ ÿçóÐ&*l*Œÿÿ ÿçóÓ&#…ÿÿ ÿçõÐ&-@*Œÿÿ ÿèðÏ&,0åÿÿ ÿêòÏ&03ÿÿ ÿéôÆ&ðoÿÿÿèïÑ&G3ÿÿ ÿéóÏ&ð>ÿÿÿéóÑ&-ú3ÿÿ ÿéóÏ&53ÿÿ ÿèõÏ&ðÿÿ ÿîóÇ&6 öÿÿ ÿêôÐ&ðøÿÿ ÿéóÏ&5ÿÿ ÿè÷Ï&ðñÿÿ ÿéóÐ&40Šÿÿ ÿéóÑ&.Ï3ÿÿ ÿéôÐ&ð7ÿÿ ÿéóÏ&5zÿÿ ÿêñÏ&ð{ÿÿÿèóÂ&~&yxÿÿ ÿéõÑ&ðOÿÿÿèøÒ&(Zÿÿÿ ÿèóÊ&E3ÿÿ ÿêòÅ&ð#lÿÿ ÿéóÑ&3ÿÿ ÿéóÏ&ÿÿ ÿæóÐ&þ3 ÿÿ ÿéóÑ&ÿÿ ÿéðÊ&ð%óÿÿ ÿêôÏ&,w%ôÿÿ ÿéôÐ&,3!ÿÿ ÿéóÑ&,&3"ÿÿ ÿéóÒ&,xÿÿÿéíÑ&03#ÿÿÿñíÑ&0/ÿÿÿéõÑ&GìÿÿÿéíÑ&GîÿÿÿéñÑ&03$ÿÿÿèïÑ&G¾ÿÿ ÿéôÑ&G3%ÿÿÿëïÑ&03&ÿÿÿéòÑ&G3'ÿÿ ÿéíÑ&ÓGÿÿÿìòÑ&ÔGÿÿÿëðÑ&Õ0ÿÿ ÿéîÑ&GÖÿÿÿêòÑ&G3(ÿÿÿèíÑ&G3)ÿÿÿéíÑ&G3*ÿÿÿéíÑ&G3+ÿÿÿèðÑ&G3,ÿÿ ÿðõÑ&GÞÿÿÿñíÑ&03-ÿÿÿéñÑ&G3.ÿÿÿéðÑ&G3/ÿÿ ÿìïÑ&G30ÿÿÿêíÑ&G31ÿÿ ÿèóÑ&G€ÿÿÿòîÑ&GÖÿÿÿïòÑ&GHÿÿ ÿéíÑ&G|ÿÿÿóíÑ&GËÿÿÿîíÑ&ÌGÿÿ ÿè÷Ñ&IGÿÿÿêíÑ&GãÿÿÿèíÑ&G32ÿÿ ÿèïÑ&G;ÿÿÿéîÑ&G33ÿÿÿéíÑ&G#ÿÿÿéóÑ&G34ÿÿÿéïÑ&G35ÿÿ ÿçóÑ&G&#36ÿÿÿéíÑ&G37ÿÿÿèðÑ&G38ÿÿÿéíÑ&G39ÿÿ ÿéóÑ&GVÿÿÿèõÑ&G3:ÿÿ ÿæòÑ&G3;ÿÿ ÿéíÑ&G&T3<ÿÿ ÿéñÑ&G3=ÿÿÿèóÑ&G3>ÿÿ ÿéôÑ&G3?ÿÿÿéôÑ&G3@ÿÿ ÿéôÑ&G3AÿÿÿçíÑ&G3BÿÿÿêñÑ&G&æ3CÿÿÿèôÑ&G3Dÿÿ ÿêíÑ&äGÿÿÿçíÑ&GãÿÿÿëôÑ&&â3EÿÿÿéíÑ&G" ÿÿ ÿîóÑ& ø&GÙÿÿ ÿéìÑ&3FÿÿÿèôÑ&G3Gÿÿ ÿéíÑ&G3Hÿÿ ÿèóÑ&G3IÿÿÿéíÑ&G tÿÿÿéòÑ&G"ÿÿÿéíÑ&G3JÿÿÿèðÑ&3KÿÿÿçñÑ&3Lÿÿ ÿèøÑ&3MÿÿÿçöÑ&3Nÿÿ ÿçìÑ&&3OÿÿÿèòÑ&3Pÿÿ ÿêòÑ&3QÿÿÿåòÑ&3Rÿÿ ÿéòÑ&3SÿÿÿèñÑ&G3Tÿÿ ÿèíÑ&G3Uÿÿ ÿèíÑ&G3Vÿÿ ÿæóÑ&G3WÿÿÿéðÑ&3XÿÿÿéòÑ&G3Yÿÿ ÿéõÑ&3Zÿÿ ÿêôÑ&G3[ÿÿÿäíÑ&G3\ÿÿÿëíÑ&G3]ÿÿÿéóÑ&G3^ÿÿ ÿéöÑ&G3_ÿÿÿéìÑ&3`ÿÿÿèôÑ&3aÿÿ ÿéòÑ&3bÿÿÿçóÑ&G3cÿÿÿçíÑ&-û3dÿÿÿêöÑ&-ü3eÿÿÿæíÑ&-ü3fÿÿ ÿêòÏ&(ÿÿÿéîÇ&,3gÿÿÿéîÇ&,3hÿÿ ÿêóÏ&'éÿÿ ÿêóÏ&‚ÿÿ ÿéóÐ&Öÿÿ ÿéöÏ&!3iÿÿ ÿéóÒ&0Þÿÿ ÿéõÒ&K,‚ÿÿ ÿéóÏ&|}ÿÿÿéðÑ&-ý3jÿÿ ÿéóÏ&}%ÿÿÿéóÏ&}3kÿÿÿçóÑ&tuÿÿÿèïÏ&@3lÿÿ ÿéõÐ&hÿÿ ÿéîÇ&ÜÝÿÿ ÿéóÍ&ÿÿ ÿñòÍ&Aÿÿ ÿèöÏ&B&CDÿÿ ÿçöÏ&"vÿÿ ÿïõÐ&"0Ðÿÿ ÿêòÏ&,»ÿÿ ÿêòÏ&,3rÿÿ ÿêóÏ&,¢ÿÿ ÿéñÎ&!g!hÿÿ ÿéõÏ&:<ÿÿ ÿéñÎ&!h#&ÿÿ ÿéñÐ&!h$1ÿÿ ÿéóÎ&!h3tÿÿ ÿèõÇ&+Ö3wÿÿ ÿèôÇ&+Ö3xÿÿ ÿèõÉ&:3yÿÿ ÿèöÉ&:3zÿÿ ÿèìÉ&:3{ÿÿ ÿèòÉ&:ÿÿ ÿæïÉ&:3|ÿÿ ÿèëÉ&:3}ÿÿ ÿçîÉ&:3~ÿÿ ÿèëÉ&:3ÿÿ ÿèëÉ&:&»ÿÿ ÿèëÉ&:3€ÿÿ ÿèôÉ&83ÿÿ ÿèëÉ&:3‚ÿÿ ÿèïÉ&8*ÿÿ ÿèëÉ&8Šÿÿ ÿèòÉ&89ÿÿ ÿèöÉ&;:ÿÿ ÿèñÉ&8<ÿÿ ÿèõÉ&8&>=ÿÿ ÿèëÉ&8?ÿÿ ÿèëÉ&@Aÿÿ ÿçõÍ&#™#˜ÿÿ ÿèëÉ&83ƒÿÿ ÿéóÉ&,3„ÿÿ ÿèíÉ&A3…ÿÿ ÿèòÉ&83†ÿÿ ÿèõÉ&83‡ÿÿ ÿèðÉ& L8ÿÿ ÿèëÉ&B3ˆÿÿ ÿèìÉ&A(ÿÿ ÿèòÉ&B#¶ÿÿ ÿèñÊ&E3‰ÿÿ ÿæóÉ&C&A3Šÿÿ ÿèëÉ&B3‹ÿÿ ÿèõÊ&E&F3Œÿÿ ÿæïÉ&>&A3ÿÿ ÿèóÉ&C&DBÿÿ ÿçñÊ&F&GEÿÿ ÿèôÊ&F&E3Žÿÿ ÿèñÊ&E3ÿÿ ÿèìË&+×3ÿÿ ÿæðË&+×3‘ÿÿ ÿéôÐ&+ÛäÿÿÿéçÏ&+Ú+ØÿÿÿêåÏ&+Ù+ØÿÿÿêòÎ&žÿÿÿî÷Ë&žÿÿÿêìË&žœÿÿÿéõË&´žÿÿÿéëÏ&a'ëÿÿÿéðÏ&ž-þÿÿÿçâÏ&¤3”ÿÿÿìðÏ&¤3•ÿÿÿéõË&ž`ÿÿÿéñË&žyÿÿÿìðÏ&žgÿÿÿéïÅ&3/ÿÿÿéóË&ž‡ÿÿÿèõÏ&eˆÿÿÿéôË&žÿÿÿéöË&ÞžÿÿÿèõÏ&žÿÿ ÿéóÏ&¤ÿÿÿèöÑ&ž­ÿÿÿêóË&!ržÿÿ ÿéóÏ&3Þÿÿ ÿèôÏ&£¤ÿÿÿòôË&ž…ÿÿÿéäÏ&e3–ÿÿÿèôË&äžÿÿ ÿèôÌ&-ÿ($ÿÿ ÿçöÏ&e)ÙÿÿÿîñÏ&“¤ÿÿÿéïÏ&)ØeÿÿÿêöÏ&ž±ÿÿÿòôË&žžÿÿÿéçÇ&/3˜ÿÿÿéóÏ&e)ÛÿÿÿêìÐ&ž-¯ÿÿÿéòÐ&ž&òóÿÿÿèóÏ&ŸÿÿÿððÏ&)Úeÿÿ ÿéâÏ&}eÿÿÿòõË&žÿÿ ÿèîÏ&.¤ÿÿÿèíÏ&vŒÿÿÿéëÏ&ž)ÿÿÿÿèéË&/0ÑÿÿÿçõÏ&ž¦ÿÿÿððÏ&žiÿÿÿõòË&ž-©ÿÿÿéïÏ&Ÿ&!I óÿÿ ÿèòÐ&)Ü&>%œÿÿÿéóÏ&)Ý3ÿÿÿéóÏ&žÿÿÿéèÐ&%žžÿÿÿéóË&ž'ìÿÿÿéíÏ&e3™ÿÿ ÿæöÏ&eZÿÿÿèóÏ&žîÿÿÿéóË&žÿÿÿêîÏ&.eÿÿÿéóÏ&žÿÿÿåãÏ&e3šÿÿÿêâÏ&)Þ¤ÿÿÿææÏ&e ÿÿÿéðË&ž*ÖÿÿÿéòÐ&ž,ÿÿÿé÷Ñ&ŸÞÿÿÿèõÏ&eßÿÿÿéõÐ&žÿÿ ÿéõÏ&Œ‚ÿÿ&ÿèÜÑ&£¢ÿÿÿêíÑ&ŸŸÿÿÿðóË&ž&ÿÿÿéôÏ&DŒÿÿÿêòË&žÄÿÿÿéôÐ&ž™ÿÿÿéìË&ažÿÿÿè÷Ï&ž7ÿÿÿëòÏ&YžÿÿÿèóÏ&ž0¾ÿÿÿêõÑ&2ŸÿÿÿòõË&ÜžÿÿÿéóÐ&0ÑNÿÿ ÿðöÏ&e&´3›ÿÿ ÿäâÏ&e&ª«ÿÿ ÿçôÏ&Œÿÿ ÿéìÏ&|ŒÿÿÿéñÏ&ž±ÿÿÿæöÏ&Ÿ³ÿÿÿèöÏ&áŸÿÿÿéóÑ&ž"ÿÿÿéíÑ&ˆ3ÿÿÿé÷Ï&žcÿÿÿçôÏ&Œ3œÿÿÿéïÌ&ŸWÿÿÿêóÒ&Ÿ0ìÿÿÿéêÐ&ž–ÿÿÿéòË&žÿÿ ÿæöÏ&'íÿÿÿéôÏ&žÿÿ ÿéñÐ&n3ÿÿÿèôË&žrÿÿÿéòÐ&/ŸÿÿÿêòÏ&Œ3žÿÿÿçøÏ&Ÿ?ÿÿ ÿêóÒ&âçÿÿÿæöË&žAÿÿÿçõÑ&Ÿ ÿÿÿçõÏ&Ÿ ØÿÿÿéóË&žýÿÿÿéõÏ&Œ3Ÿÿÿ ÿèôÏ&e3 ÿÿÿéïÐ&ž,9ÿÿÿéöÏ&žÿÿÿñóÐ&žÉÿÿÿéìÏ&ŒPÿÿ ÿåôÏ&Œ3¡ÿÿÿéðË&ž»ÿÿÿéóÏ&Ÿ'îÿÿÿéóÎ&ž&ÇÿÿÿéôÏ&ž'ïÿÿÿèòÏ&Œ,ƒÿÿÿéñÏ&Œnÿÿ ÿéðÏ&eˆÿÿÿéôË&ž,ÿÿ ÿéðÏ&Œ,ÿÿÿèäÏ&Œ3¢ÿÿÿéøÏ&Ÿ ÿÿ ÿéôÏ&Œ ÿÿÿèöÏ&'ðžÿÿÿéîË&Ÿ, ÿÿÿéóÏ&ž'ÿÿÿêóÑ&Ÿ%%ÿÿ ÿïóÏ&e˜ÿÿÿéôÏ&Ÿ ÿÿÿéôÑ&žÿÿÿæóÏ&3£ÿÿÿèòÒ&/žÿÿÿéóÎ&ž)ÿÿÿìöÏ&Ÿ'ñÿÿ ÿéïÏ&e3¤ÿÿÿèóÏ&Ÿ,ÿÿ ÿèåÏ&e3¥ÿÿÿçôÏ&Œ 9ÿÿÿéãÏ&ŒêÿÿÿéðÐ&ž# ÿÿÿçäÏ&Œ3¦ÿÿÿìõÑ&ž1ÿÿÿéöÏ&ŸÇÿÿ ÿæõÏ&Œ3§ÿÿÿçõË&ž!0ÿÿÿéòÏ&feÿÿÿéóÏ&ž&ŽÿÿÿèõË&žxÿÿÿéñÑ&žÿÿ ÿèðÏ&Œ"ÂÿÿÿéóË&žóÿÿÿêòÏ&Œ&"+€ÿÿÿçòË&žÿÿÿêõË&žÿÿ ÿéóÏ&e3¨ÿÿÿïóÎ&žÿÿ ÿéòÏ&Œ0}ÿÿÿè÷Ï&Ÿ ÿÿ ÿèðÏ&!Œÿÿ ÿèóÐ&=&>ÿÿÿêòÐ&Ÿ!´ÿÿÿæôÍ&$ÒŸÿÿÿçìË&!žÿÿ ÿèóÏ&Œ& ¹3©ÿÿÿëðË&ž uÿÿ ÿçúÏ&ŒhÿÿÿéðË&’žÿÿÿêëÇ&ßæÿÿÿôëÏ&eàÿÿÿéöË&Ÿ#ÍÿÿÿéóÏ&Ÿ#Þÿÿ ÿéóÏ&œ&›ŒÿÿÿçóÏ&Œ#‡ÿÿÿêîÏ&Œ+ÿÿÿèöÏ&#²žÿÿ ÿêóÓ&á&èéÿÿÿèöÒ&Ÿ$&ÿÿÿèòÐ&Ÿ$ÿÿÿéòÏ&Ÿ.ÿÿÿéïÐ&ž%ÿÿÿçñÏ&Ÿ#ÿÿÿéñÏ&%ÿÿÿèóÐ&ŸtÿÿÿèõÏ&Œ#õÿÿÿéðÑ&Ÿ$•ÿÿÿíòÐ&ž2ÿÿÿé÷Ð&$ŸÿÿÿëõÑ&Ÿ/ÿÿÿïñÏ&3ªÿÿÿéöÒ&/ŸÿÿÿèðË&wŸÿÿÿêöÐ&Ÿ*ÿÿÿèòÏ&Œ3«ÿÿÿé÷Í&žÿÿÿéôÒ&žÿÿÿéóÏ&Ÿ0ÿÿ ÿèöÏ&'òÿÿÿèõÏ&i&hŒÿÿÿéòË&Ÿ/‰ÿÿÿçõÏ&Ÿ0›ÿÿÿéôÒ&Ÿ'óÿÿÿéñÏ&Œ3¬ÿÿÿèóÏ&ž{ÿÿÿòóË&#`ŸÿÿÿéðÏ&'ôŒÿÿÿéðÏ&e&‰3­ÿÿÿéðÏ&e&ЉÿÿÿéòÏ&žÿÿÿèòÏ&gÿÿÿéîË&Ÿ%ÛÿÿÿéöÏ&Œ&Ò3®ÿÿÿêîÑ&ž'ÿÿÿêòÏ&üŸÿÿ ÿèóÏ&Œ& ¹3¯ÿÿÿéöÏ&'õŸÿÿÿèïÎ&Ÿ'öÿÿÿéôÎ&Ÿ*Ñÿÿ ÿéõÏ&3°ÿÿÿéïÏ&@&3±ÿÿÿîöË&ŸÿÿÿêîÒ&ç&!¨3²ÿÿÿéóÒ&žiÿÿÿéóÏ&Ÿ'÷ÿÿÿèôÏ&Œ3³ÿÿÿèôÎ&ž'øÿÿÿéôË&žØÿÿÿéíÑ&3,‡ÿÿÿçõÏ&'ùžÿÿÿéðÏ&,ˆÿÿÿèôÐ&'úŸÿÿÿéòÏ&Œ3´ÿÿÿé÷Ñ&Ÿ&Åÿÿ ÿéøÏ&3µÿÿÿéöÐ&'ü'ûÿÿÿéðÎ&Ÿ,…ÿÿ ÿèíÏ&3¶ÿÿÿéòË&Ÿ.ÿÿÿèõÏ&3¸ÿÿÿêõÏ&Ÿ,ŠÿÿÿéóÏ&Ÿ0ÄÿÿÿèóÏ&Œ&[\ÿÿÿéõÎ&,‹Ÿÿÿ ÿçòÏ&Œ3¹ÿÿ ÿèöÏ&Œ3ºÿÿÿéëÏ&*3»ÿÿÿëöÏ&'ýÿÿ ÿèòÏ&&/3¼ÿÿÿéðÏ&Ÿ/ÿÿÿë÷Ï&'ü3½ÿÿÿèòÑ&Ÿ7ÿÿ ÿéîÏ&3¾ÿÿ ÿèôÏ&Œ‹ÿÿÿéõÏ&ŽÿÿÿéñÏ&Ÿ'þÿÿÿçñÏ&%&3¿ÿÿÿçõÐ&Ÿ'ÿÿÿ ÿêóÑ&ç,ŒÿÿÿçõÏ&& ¦ ¥ÿÿ ÿçõÏ&/& ¥ÿÿÿçòÑ&Ÿ3Àÿÿ ÿçõÏ&+®3Áÿÿÿé÷Ï&+¯&'ü3Âÿÿ ÿêõÏ&+Â3Æÿÿ ÿëôÉ&\uÿÿÿèñÑ&(3Çÿÿ ÿêñÀ&ÅÆÿÿ ÿèôÁ&€ÿÿ ÿèöÐ&%&ÿÿ ÿèöÐ&;§ÿÿ ÿìóÌ&T,ÿÿ ÿíõÐ&ò3Ïÿÿ ÿèóË&+°3ÐÿÿÿéóÏ&ÑjÿÿÿééÐ&ÿÿÿèöÏ&÷ÿÿÿéðÏ&ÿÿÿéõÏ&÷ÿÿÿéìÐ&ÿÿ ÿéðÑ&¶·ÿÿÿèôÏ&÷!ÿÿÿéêÏ&÷ÿÿÿèõÏ&÷ÿÿÿéôÐ&0‰ÿÿÿèóÏ&÷ÿÿÿéêÐ&MjÿÿÿéòÏ&jNÿÿÿçòÐ&÷PÿÿÿèîÒ&+±­ÿÿÿéòÏ&÷(-ÿÿ"ÿéßÐ&‹ÿÿÿçôÏ&÷%ÌÿÿÿéíÏ&òÿÿ"ÿéßÏ&3ÑÿÿÿéóÏ&å÷ÿÿÿéôÑ&÷àÿÿ ÿéõÐ&+³ñÿÿÿéêÐ&0‹ÿÿÿéëÐ&¤¦ÿÿÿåðÏ&÷¢ÿÿÿéôÏ&÷bÿÿÿéêÐ&÷–ÿÿÿéòÐ&(¥ÿÿÿéëÎ&3ÒÿÿÿéñÏ&ÿÿÿéôÏ&÷*ÿÿÿèõÏ&÷(ÿÿÿçöÒ&÷fÿÿÿéôÏ&+²3ÔÿÿÿéõÐ&ý‹ÿÿÿèóÏ&(÷ÿÿÿéîÏ&÷ãÿÿÿéìÏ&ý †ÿÿÿéóÏ&÷6ÿÿÿèóÐ&p&@YÿÿÿéóÏ&÷ fÿÿÿèõÏ&j^ÿÿ ÿéóÈ&(&+´3ÕÿÿÿéóÐ&÷!ŠÿÿÿéõÏ&ýÿÿÿéîÏ&÷" ÿÿ ÿéòÐ&8gÿÿÿéòÏ&ý1ÿÿ ÿéòÑ&¸&(y8ÿÿÿéöÏ&ý$OÿÿÿéóÏ&ý$¸ÿÿÿèóÏ& â÷ÿÿÿéõÑ&ý—ÿÿÿçõÑ&xýÿÿÿéôÏ&p%Þÿÿ ÿéøÏ&k&+´3ÖÿÿÿéîÏ&poÿÿÿèõÐ&÷(ÿÿÿéöÏ&ý%,ÿÿÿéôÒ&ý'óÿÿÿéóÏ&ý(ÿÿÿéòÏ&pÿÿÿéóÐ&ý* ÿÿÿéöÏ&pFÿÿÿéöÐ&p.#ÿÿÿéòÏ&ýüÿÿÿéóÏ&ý´ÿÿÿéôÏ&98ÿÿÿéôÐ&8ùÿÿ ÿéòÑ&.8ÿÿÿéøÏ&ý¡ÿÿÿéóÒ&pÄÿÿÿèöÐ&ý'ÿÿÿéñÏ&ý(ÿÿÿéðÐ&h2ÿÿÿéóÏ&p0ÄÿÿÿéõÏ&÷ËÿÿÿéôÏ&p3×ÿÿÿéöÏ&ý3ØÿÿÿèõÐ&/ýÿÿÿèóÄ&!,3Ûÿÿ ÿè÷Ñ&¥3Ýÿÿ ÿíçÐ&,’3ßÿÿÿéíÏ&_îÿÿ ÿèðÐ&ü1ÿÿ ÿèíÐ&ü2ÿÿ ÿéóÐ&Æ ÿÿ ÿéòÐ&Æ3ÿÿ ÿèíÐ&ü4ÿÿ ÿéïÐ&ýÆÿÿ ÿéðÐ&0Æÿÿ ÿèñÐ&þÇÿÿ ÿéíÐ&Çÿÿÿ ÿéîÐ&Æ3âÿÿ ÿèíÐ&Æ3ãÿÿ ÿèîÐ&Æ3äÿÿ ÿéîÐ&Æ3åÿÿ ÿéíÐ&Æ3æÿÿ ÿæíÐ&Æùÿÿ ÿéòÐ&Æ3çÿÿ ÿéîÐ&Æ3èÿÿ ÿèóÐ&ü3éÿÿ ÿéðÐ&Æ•ÿÿ ÿéñÐ&Ç3êÿÿ ÿçíÐ&Æ3ëÿÿ ÿèíÐ&ü3ìÿÿ ÿèóÐ&Æ3íÿÿ ÿéôÐ&Æ3îÿÿ ÿèïÐ&Ç3ïÿÿ ÿçîÐ&ü3ðÿÿ ÿéòÐ&Æ/uÿÿ ÿéïÐ&Æ·ÿÿ ÿéòÐ&'Æÿÿ ÿèôÐ&ÆCÿÿ ÿèíÐ&ÆHÿÿ ÿéîÐ&Æ3ñÿÿ ÿèôÐ&Æ3òÿÿ ÿéóÐ&Æ3óÿÿ ÿèðÐ&Æ3ôÿÿ ÿçòÐ&Æ3õÿÿ ÿéòÐ&Ç3öÿÿ ÿéóÐ&Æ3÷ÿÿ ÿèôÐ&Ç3øÿÿ ÿéíÐ&Æ3ùÿÿ ÿèïÐ&Æàÿÿ ÿéõÐ&Æ3úÿÿ ÿéñÐ&Ç3ûÿÿ ÿèíÐ&Æ3üÿÿ ÿéïÐ&Æ3ýÿÿ ÿéöÐ&Ç3þÿÿ ÿèòÐ&Ç3ÿÿÿ ÿéôÑ&$Jÿÿ ÿéñÐ&Æ4ÿÿ ÿçïÐ&Æ4ÿÿ ÿéíÐ&Ç4ÿÿ ÿéíÐ&Çqÿÿ ÿéòÐ&$<Çÿÿ ÿéöÐ&Ç4ÿÿ ÿéíÐ&Ç%ÿÿ ÿé÷Ð&Ç#öÿÿ ÿéíÐ&Ç4ÿÿ ÿéòÐ&Ç4ÿÿ ÿèõÑ&$4ÿÿ ÿéïÐ&Ç4ÿÿ ÿèòÐ&Ç4ÿÿ ÿéòÐ&Ç4 ÿÿ ÿçõÐ&Ç& ¥ §ÿÿ ÿèôÐ&Ç4 ÿÿ ÿéóÐ&Ç4 ÿÿ ÿéðÑ&$$ÿÿÿ ÿçôÐ&Ç4 ÿÿ ÿèðÑ&$4 ÿÿ ÿèñÐ&Ç4ÿÿ ÿéôÐ&Ç4ÿÿ ÿèöÐ&Ç4ÿÿ ÿéóÐ&Ç4ÿÿ ÿéðÐ&Ç4ÿÿ ÿçíÐ&Ç4ÿÿ ÿéóÐ&Ç4ÿÿ ÿéòÑ&$4ÿÿ ÿéðÑ&*&)$ÿÿ ÿéõÐ&Ç4ÿÿ ÿèòÐ&Ç4ÿÿ ÿéíÐ&Ç4ÿÿ ÿéöÐ&Ç4ÿÿ ÿçõÐ&Ç4ÿÿ ÿèõÑ&$4ÿÿ ÿèðÑ&$4ÿÿ ÿéóÐ&Ç4ÿÿ ÿéñÐ&Ç4ÿÿ ÿéñÐ&Ç4ÿÿ ÿéðÐ&Ç4 ÿÿ ÿèôÑ&$4!ÿÿ ÿåòÑ&$4"ÿÿ ÿéóÑ&$œÿÿ ÿçïÐ&Ç4#ÿÿ ÿéòÐ&Ç4$ÿÿ ÿèûÑ&$4%ÿÿ ÿçõÑ&$4&ÿÿ ÿçñÑ&$4'ÿÿ ÿéõÐ&Ç4(ÿÿ ÿèôÎ&%@4)ÿÿ ÿèôÎ&%B%@ÿÿÿéöÏ&%AÿÿÿéöÏ&%A"ÿÿÿéöÆ&%A!ÿÿÿéöÆ&%A ÿÿ ÿêóÐ&+µ4-ÿÿ ÿçóÈ&M4.ÿÿ ÿçóÎ&M4/ÿÿ ÿçóÃ&M40ÿÿ ÿçóÏ&öÙÿÿ ÿçóÏ&ñöÿÿ ÿçóÏ&ö41ÿÿ ÿçôÏ&9öÿÿÿçòÏ&+¶43ÿÿÿçòÏ&+¶44ÿÿ ÿèôÏ& FÿÿÿéôÐ& ¦ÿÿÿéØÏ&ø ýÿÿÿèôÏ&ß þÿÿÿêôÏ&· üÿÿÿêõÇ&¸ ÿÿÿÿèõÍ& ü0îÿÿÿêóÆ&Á üÿÿÿè÷Ï& ÿ49ÿÿÿéòÎ& ü²ÿÿÿèóÏ& ÿîÿÿÿèîÏ&X üÿÿÿéóÏ& üÔÿÿÿè÷Ï&ñ üÿÿÿéêÆ&j ÿÿÿÿèóÐ&0ƒÿÿÿêõÆ& ÿ%"ÿÿÿéóÇ& ÿÁÿÿ ÿèõÐ&LÿÿÿèîÐ&úÿÿÿéêÐ& ÿcÿÿÿêòÆ&ʨÿÿÿêóÑ& ü"fÿÿÿèõÉ&+ üÿÿÿêôÏ& üYÿÿÿæôÑ& ü4;ÿÿÿéíÉ&ÉÊÿÿÿéôÎ& üÒÿÿÿèóÈ& ü,ÿÿÿèòÇ&o&ÿÿÿéóÑ& üjÿÿ ÿèôÑ&ÿ4<ÿÿÿéòÊ&#þ üÿÿÿéôÉ&Ê/9ÿÿÿêôÆ&Ê#~ÿÿÿêóÑ&Ê4=ÿÿÿéôÑ&ûÿÿÿéòÈ&ÊÿÿÿçòÏ&Ê%âÿÿÿêôÈ& ÿ ÿÿÿéõË&¤ÿÿÿé÷É&üÿÿ ÿèñÒ&,xÿÿÿéðÎ&ÃÿÿÿéêÏ&,“ÿÿÿéôÏ&+º4@ÿÿÿèóÉ&+»4AÿÿÿêòÏ&+¹4Bÿÿ ÿéóÒ&+·&14CÿÿÿçñÉ&+¸&+½&+¾+¼ÿÿÿçñÒ&+·&+¸&+½+¾ÿÿÿçðÒ&+·&+¸&+½4Dÿÿ ÿéõÍ&Ü&ÿÿ ÿéõÍ&m&ÿÿ ÿéõÎ&&(ÿÿÿéóÏ&$#ÿÿ ÿè÷Ê&('ÿÿÿè÷Ï&'.êÿÿ ÿêõÏ&%&ÿÿÿè÷Ê&'(ÿÿ ÿè÷Ð&‹'ÿÿ ÿéõÍ&&( ÿÿ ÿèëÏ& Ã( ÿÿ ÿèóÏ& Éÿÿ ÿèðÏ& Ã×ÿÿ ÿèóÏ& Ã&xÿÿ ÿèôÏ& Ã(·ÿÿ ÿéíÐ&+À&&4Fÿÿ ÿèôÐ&&ÿÿ ÿèôÏ& Ôÿÿ ÿçõÐ&‡&ÿÿ ÿéòÏ&8öÿÿ ÿéóÏ&ö Âÿÿ ÿèôÏ& Éÿÿ ÿèòÏ& ÃÝÿÿ ÿèôÏ& Ã%Dÿÿ ÿèõÏ& Ã%"ÿÿ ÿèóÐ& ÃIÿÿ ÿèóÏ& ÃÖÿÿ ÿêíÑ&&žÿÿ ÿèöÏ& Ã$ÿÿ ÿèðÏ& øÿÿ ÿéêÐ&&#ÿÿ ÿéöÐ&õÃÿÿ ÿèöÐ&&Âÿÿ ÿéõÐ&&ãÿÿ ÿêòÐ&&%ÿÿ ÿèóÏ& ÃÍÿÿ ÿèõÐ& Ä Ãÿÿ ÿèöÏ& Ãùÿÿ ÿèõÑ& Ãýÿÿ ÿèóÏ& Ãþÿÿ ÿéóÐ&ö]ÿÿ ÿèõÏ& ÃZÿÿ ÿèôÏ&ÿ Ãÿÿ ÿèìÐ&ø Ãÿÿ ÿèôÐ&öôÿÿ ÿèëÏ& Ã&ÿÿ ÿèóÏ& Ã&ÿÿ ÿèðÏ&õ&ûúÿÿ ÿèñÏ& Ã"ÿÿ ÿéöÏ&õ´ÿÿ ÿêõÑ&&'ÿÿ ÿèôÐ&ö!„ÿÿ ÿèóÏ&öZÿÿ ÿéîÐ& »õÿÿ ÿéîÑ&&$–ÿÿ ÿéòÐ&&&“ÿÿ ÿéôÏ&õ&,4Gÿÿ ÿèôÐ&föÿÿ ÿèôÐ&/.&ÿÿ ÿê÷Ð&&.ÿÿ ÿèõÐ&&õ4Hÿÿ ÿéôÏ&õ&,4Iÿÿ ÿèóÏ&øùÿÿ ÿéòÏ&.öÿÿ ÿèõÐ&õ&4Jÿÿ ÿéõÒ&ö&Ìÿÿ ÿéôÏ&õ&,4Kÿÿ ÿèõÐ&õ&4Lÿÿ ÿèõÐ&õ&4Mÿÿ ÿéóÐ&õ+ÿÿ ÿéñÑ&&%÷ÿÿ ÿçøÏ&ö4Nÿÿ ÿéóÏ&iØÿÿ ÿéèÏ&AÒÿÿ ÿîõÅ&[ÿÿ ÿîõÇ&æ[ÿÿ ÿéóÏ&Ÿ@ÿÿ ÿïóÏ&^]ÿÿ ÿïòÅ&^`ÿÿÿçòÏ&+¶4Pÿÿ ÿéòÏ&Aÿÿ ÿé÷Ï&Aÿÿ ÿéòÏ&7ÿÿ ÿïòÏ&+Á^ÿÿ ÿïòÏ&^+Âÿÿ ÿéóÏ&Aÿÿ ÿéöÐ&Æ4Qÿÿ ÿïöÐ&^ÿÿ ÿïòÐ&( ÿÿ ÿéëÏ&7 ÿÿ ÿèóÏ&…7ÿÿ ÿéòÏ&@¬ÿÿÿéõÒ&[4Rÿÿ ÿéòÏ&m7ÿÿ ÿéöÏ&Þiÿÿ ÿéóÏ&&x@ÿÿ ÿéöÏ&BAÿÿ ÿéìÐ&7ÿÿ ÿèõÏ&iÿÿ ÿèôÏ&7ÿÿ ÿéöÏ&A&ÿÿ ÿéôÏ&@"ÿÿ ÿéøÏ&7#ÿÿ ÿèöÑ&Ó@ÿÿ ÿîõÑ&ê[ÿÿ ÿéìÏ&'@ÿÿ ÿéóÏ&i)ÿÿ ÿéõÑ&²iÿÿ ÿéñÏ&iñÿÿ ÿîõÓ&[4Sÿÿ ÿéõÐ&+0ïÿÿ ÿîõÏ&Þ§ÿÿ ÿéõÏ&Û7ÿÿ ÿîõÏ&[4Tÿÿ ÿîõÎ&[4Uÿÿ ÿéòÏ&@. ÿÿ ÿéôÏ&@…ÿÿ ÿèõÏ&i0îÿÿ ÿéóÏ&9'Fÿÿ ÿéôÐ&–?ÿÿ ÿéêÏ&ijÿÿ ÿéõÏ&9cÿÿ ÿéòÏ&@,ÿÿ ÿéóÐ&U@ÿÿ ÿîõÏ&!¬[ÿÿ ÿçöÏ&–`ÿÿ ÿéëÐ&@Wÿÿ ÿèóÏ&@+-ÿÿ ÿîõÐ&0ˆ[ÿÿ ÿéöÏ&&–4Vÿÿ ÿéôÏ&‰@ÿÿ ÿéèÐ&i%žÿÿ ÿèòÏ&@+.ÿÿ ÿéóÏ&iÿÿ ÿéòÏ&+/@ÿÿ ÿéòÏ&@Ýÿÿ ÿéöÏ&@Áÿÿ ÿçõÏ&9‡ÿÿ ÿîõÄ&&¼[ÿÿ ÿéëÏ&@»ÿÿ ÿîõÏ&¦§ÿÿ ÿéòÐ&@kÿÿ ÿèõÏ&@+0ÿÿ ÿîõÐ&[&I4Wÿÿ ÿëõÐ&„4Xÿÿ ÿéóÏ&@dÿÿ ÿéôÏ&9%Dÿÿ ÿîõÑ&,‘[ÿÿ ÿéðÏ&@Ãÿÿ ÿé÷Ï&;iÿÿ ÿéòÏ&90°ÿÿ ÿéëÏ&90ðÿÿ ÿé÷Ï&@+2ÿÿ ÿéïÏ&9& ò(0ÿÿ ÿéòÏ&@*eÿÿ ÿîõÐ&*[ÿÿ ÿéôÏ&%Ë7ÿÿ ÿéõÏ&@3ÿÿ ÿçõÐ&+1@ÿÿ ÿéõÏ&–¥ÿÿ ÿîõÑ&§4Yÿÿ ÿîõÐ&§4Zÿÿ ÿéõÐ&– ´ÿÿ ÿîõÐ&§4[ÿÿ ÿîõÎ&[4\ÿÿ ÿéðÏ&@+3ÿÿ ÿèîÏ&iÿÿ ÿîõÐ&[4]ÿÿ ÿéîÑ&9+ÿÿ ÿéóÐ&iKÿÿ ÿéóÏ&90Óÿÿ ÿéòÏ&±@ÿÿ ÿéòÏ&iŽÿÿ ÿéóÏ&9ÿÿ ÿéðÏ&@ÿÿ ÿéñÏ&9!ÿÿ ÿîõÑ&ˆ[ÿÿ ÿé÷Ï&B@ÿÿ ÿé÷Ï&@+ÿÿ ÿîöÈ&[4^ÿÿ ÿéõÏ&ÿAÿÿ ÿéóÏ&@Öÿÿ ÿèôÐ&Piÿÿ ÿîõÏ&[4_ÿÿ ÿé÷Ï&@'ÿÿ ÿîõÐ&Å[ÿÿ ÿëõÏ&ƒ„ÿÿ ÿéêÏ&#–ÿÿ ÿîõÏ&§&ÿÿ ÿè÷Ï&@)ÿÿ ÿéîÏ&–vÿÿ ÿéòÏ&U@ÿÿ ÿèöÏ&9@ÿÿ ÿîõÏ&-&,[ÿÿ ÿéôÏ&9Àÿÿ ÿéöÎ&86ÿÿ ÿèíÒ&@¾ÿÿ ÿîõÄ&[4`ÿÿ ÿéöÏ&$@ÿÿ ÿîõÆ&f§ÿÿ ÿéõÐ&7\ÿÿ ÿéîÏ&9.ÿÿ ÿéõÏ&– ·ÿÿ ÿéöÑ&“9ÿÿ ÿîõÒ&Χÿÿ ÿéôÑ&@ ¸ÿÿ ÿèõÏ&á7ÿÿ ÿîõË&§Bÿÿ ÿîõÏ&§4bÿÿ ÿîõÆ&[âÿÿÿîõÐ&§4cÿÿ ÿéïÏ&–& ò4dÿÿ ÿèôÏ&@4eÿÿ ÿéôÏ&@,”ÿÿ ÿéëÏ&–&à4fÿÿ ÿéòÏ&@4gÿÿ ÿéóÏ&©9ÿÿÿêòÈ&€,•ÿÿ ÿèíÏ&9'©ÿÿ ÿéêÏ&i(9ÿÿ ÿéîÏ&9Þÿÿ ÿéêÐ&c@ÿÿ ÿéôÐ&7ÿÿ ÿëõÒ&„4hÿÿ ÿéõÐ&ã–ÿÿ ÿéôÏ&.09ÿÿ ÿîöÍ&,–[ÿÿ ÿîõÏ&[0gÿÿ ÿéõÐ&9. ÿÿ ÿéïÏ&–&8ÿÿ ÿéôÏ&7/ÿÿ ÿéóÏ&E@ÿÿ ÿîõÐ&[4iÿÿ ÿéòÏ&7'cÿÿ ÿéõÐ&–‚ÿÿ ÿéòÐ&@*ÿÿ ÿéôÏ&– ÿÿ ÿéôÐ&+(4ÿÿ ÿéùÏ&70ÿÿ ÿéôÑ&9'ªÿÿ ÿéòÏ&0@@ÿÿ ÿéóÏ&7çÿÿ ÿéóÏ&@Šÿÿ ÿëõÏ&ð&ñ„ÿÿ ÿéôÏ&7'«ÿÿ ÿëõÏ&x„ÿÿ ÿëõÐ&‚„ÿÿ ÿèõÑ&@`ÿÿ ÿéìÐ&&–4jÿÿ ÿîõÒ&§4kÿÿ ÿéîÏ&×7ÿÿ ÿìôÏ&%gÿÿ ÿìôÈ&gÿÿ ÿéõÏ&@*4ÿÿ ÿéíÐ&Ô&9Õÿÿ ÿéõÑ&ý9ÿÿ ÿéóÐ&–]ÿÿ ÿîõÐ&[ JÿÿÿêòÒ&.€ÿÿ ÿéóÏ&A1ÿÿ ÿèõÏ&i2ÿÿÿéìÊ&f4lÿÿ ÿçõÏ&Þ9ÿÿ ÿèõÏ& 9ÿÿÿêòÒ&€hÿÿ ÿèðÐ&7iÿÿ ÿéòÏ&9¿ÿÿ ÿéòÏ&9 Xÿÿ ÿéõÏ&9Ýÿÿ ÿéóÐ&7Èÿÿ ÿé÷Ï&9 ÿÿ ÿéøÏ&– ÿÿ ÿîõÎ&§4mÿÿ ÿéôÏ&A(mÿÿ ÿîõÌ&§4nÿÿ ÿéóÏ&–²ÿÿ ÿéëÏ&9&ÿÿ ÿéóÑ&@"fÿÿ ÿéõÏ&/@ÿÿ ÿîõÐ&ð&§4oÿÿ ÿéôÏ&@Zÿÿ ÿéñÐ&++ÿÿ ÿîõÑ&§&4pÿÿ ÿéõÏ&š–ÿÿ ÿèóÏ&–&.è4rÿÿ ÿë÷Ï&=„ÿÿ ÿéõÐ&i%ªÿÿ ÿèôÏ&@Ûÿÿ ÿéíÏ&i¾ÿÿ ÿèíÏ&9,—ÿÿ ÿèõÏ& Ý9ÿÿ ÿéõÓ&9&Ý4sÿÿ ÿéóÏ&@*5ÿÿ ÿéóÏ&@%Óÿÿ ÿéìÐ&9øÿÿ ÿéöÏ&>&%4tÿÿ ÿéôÐ&9ŸÿÿÿêòÏ&€4uÿÿ ÿîõÇ&§#mÿÿ ÿìôÏ&+44vÿÿ ÿîõÎ&§4wÿÿ ÿèõÏ&–&,˜4xÿÿ ÿéðÐ&@ ÿÿ ÿçïÏ&@*gÿÿ ÿèøÐ&– .ÿÿ ÿîõÑ&[4yÿÿ ÿìôÓ&g&0“4zÿÿ ÿéïÏ&94{ÿÿ ÿèôÏ&–&Þ 8ÿÿ ÿéôÏ&@Òÿÿ ÿçñÏ&9/Jÿÿ ÿéñÏ&@"‹ÿÿ ÿéïÏ&7Áÿÿ ÿéóÏ&@!ÿÿ ÿîõÏ&§4|ÿÿ ÿèóÏ&9âÿÿ ÿèóÏ&?9ÿÿ ÿéõÑ&9'ÿÿ ÿìôÐ&g4}ÿÿ ÿéôÒ&94~ÿÿ ÿëõÏ&l&„4ÿÿ ÿéóÏ&@7ÿÿ ÿéëÏ&!&–àÿÿ ÿèîÐ&–0ºÿÿ ÿéõÏ&9#Ìÿÿ ÿéñÏ&@"ÿÿ ÿèöÏ&%Ã&>áÿÿ ÿîõÐ&§0þÿÿ ÿéòÏ&9"ÿÿ ÿèóÏ&9#@ÿÿ ÿéñÏ&9!<ÿÿ ÿîõÐ&§4€ÿÿ ÿêöÐ&#ˆ€ÿÿ ÿéöÏ&–#‰ÿÿ ÿéíÏ&5iÿÿ ÿèòÏ&9Ÿÿÿ ÿëõÏ&„4ÿÿ ÿèöÐ&A!…ÿÿ ÿìôÒ&[gÿÿ ÿéïÏ&9"Gÿÿ ÿéêÏ&9Iÿÿ ÿéñÏ&7"Dÿÿ ÿçõÏ&Yiÿÿ ÿéòÑ&@+ÿÿ ÿéôÏ&93ÿÿ ÿéóÏ&@Ùÿÿ ÿéôÐ&9"£ÿÿÿêòÇ& v€ÿÿÿéõÐ&G&hFÿÿ ÿéïÏ&_–ÿÿ ÿéñÏ&–#Éÿÿ ÿëõÑ&„4‚ÿÿ ÿéôÏ&9Îÿÿ ÿéöÏ&–"ÿÿ ÿéðÏ&J&@4ƒÿÿ ÿéóÐ&!ñ9ÿÿ ÿéðÏ&9"ÿÿ ÿéñÑ&9#ôÿÿ ÿëõÐ&„4„ÿÿ ÿéôÏ&@1ÿÿ ÿéõÏ&–.ÿÿ ÿîõÑ&¿&[Àÿÿ ÿéòÏ&7'mÿÿ ÿéøÒ&,™7ÿÿ ÿéòÏ&@$Pÿÿ ÿéïÏ&9#'ÿÿ ÿèöÒ&–$%ÿÿ ÿéïÑ&–-êÿÿ ÿéòÏ&9Lÿÿ ÿëõÏ&„4…ÿÿ ÿéóÑ&@'¶ÿÿ ÿéöÏ&9$tÿÿ ÿéöÏ&–$hÿÿ ÿêôÈ&É4†ÿÿ ÿèôÐ&9*6ÿÿÿêòÈ&&~€ÿÿ ÿëõÌ&„4‡ÿÿ ÿéõÏ&9$ÿÿ ÿéôÏ&@"„ÿÿ ÿèóÏ&7$“ÿÿ ÿêòÑ&€4ˆÿÿ ÿîõÑ&§4‰ÿÿ ÿéñÏ&9,ðÿÿ ÿèõÐ&9'pÿÿÿêòÒ&/ë€ÿÿ ÿèöÏ&9ÿÿ ÿéõÏ&@ÿÿ ÿçóÐ&@æÿÿ ÿèóÏ&A áÿÿ ÿéòÏ&@/;ÿÿ ÿéóÏ&9¡ÿÿ ÿéóÏ&9'vÿÿ ÿéöÏ&9%+ÿÿ ÿéöÏ&n4Šÿÿ ÿéîÏ&–qÿÿ ÿçõÑ&9'ºÿÿ ÿëõÏ&„4‹ÿÿ ÿèôÏ&>&Þ4Œÿÿ ÿéòÏ&@.ÿÿ ÿìôÊ&$øgÿÿ ÿéôÑ&–*7ÿÿ ÿéïÏ&>& ó!fÿÿ ÿèôÐ&–'¸ÿÿ ÿéóÏ&9Jÿÿ ÿèôÏ&9,Öÿÿ ÿëõÏ&v„ÿÿ ÿéõÐ&Þ&@4ÿÿ ÿîõÏ&§4Žÿÿ ÿìôÏ&g4ÿÿ ÿéõÏ&œ–ÿÿ ÿéôÏ&@#~ÿÿ ÿëöÏ&„4ÿÿ ÿéòÏ&@$þÿÿ ÿêõÐ&˜4‘ÿÿ ÿéóÑ&7(Gÿÿ ÿêôÏ&€4’ÿÿ ÿéóÏ&@+ÿÿ ÿéòÏ&@ Þÿÿ ÿéõÏ&–%Úÿÿ ÿéíÏ&9:ÿÿ ÿèñÓ&–*8ÿÿ ÿçöÑ&–*9ÿÿ ÿëöÐ&„0 ÿÿ ÿçóÏ&@+ÿÿ ÿëõÐ&„ÿÿ ÿéõÒ&–~ÿÿ ÿîõÐ&§4”ÿÿ ÿé÷Ï&–'wÿÿ ÿéöÑ&9%Øÿÿ ÿìõÑ&gÄÿÿ ÿëõÎ&h&„4–ÿÿ ÿé÷Ï&–&n4—ÿÿ ÿêòÏ&$a&$`€ÿÿ ÿëõÏ&„úÿÿ ÿëõÏ&„&j4˜ÿÿ ÿèõÏ&9'}ÿÿ ÿéíÑ&9'‚ÿÿ ÿéôÏ&9*Òÿÿ ÿêòÈ&"Ø&"×€ÿÿ ÿçôÏ&'|+ÿÿ ÿéóÐ&º@ÿÿ ÿéöÐ&ž&4+ÿÿ ÿîõÏ&§4™ÿÿ ÿéòÏ&@'~ÿÿÿêóÏ&S.ÿÿ ÿéòÏ&@ÿÿ ÿéóÏ&9'ÿÿ ÿìôÐ&g4šÿÿ ÿèóÏ&–&°ÿÿ ÿéôÑ&–4›ÿÿ ÿèôÏ&–<ÿÿ ÿèöÐ&–'Œÿÿ ÿéóÐ&–'¾ÿÿ ÿêôÏ&€4œÿÿ ÿëõÑ&„&h4ÿÿ ÿéîÏ&–&0b4žÿÿ ÿéîÏ&9&0b4Ÿÿÿ ÿéóÏ&@*:ÿÿ ÿéôÏ&9'ãÿÿ ÿéöÐ&ë–ÿÿ ÿèôÏ&>&º&»¹ÿÿ ÿçìÏ&9%êÿÿ ÿè÷Ð&6&–4 ÿÿ ÿìôÒ&g4¡ÿÿ ÿéóÏ&*<9ÿÿ ÿéøÏ&–*;ÿÿ ÿîõÑ&§.ÿÿ ÿéöÑ&–)ôÿÿ ÿéõÏ&9.ÿÿ ÿéöÑ&7&Äÿÿ ÿéòÐ&0¥9ÿÿ ÿëõÏ&„4¢ÿÿ ÿéôÑ&9/"ÿÿ ÿèõÐ&–&4£ÿÿ ÿéõÏ&@¤ÿÿ ÿéòÏ&–4¤ÿÿ ÿìôÐ&%&g4¥ÿÿ ÿèôÏ&9-ÿÿ ÿé÷Ï&9öÿÿ ÿë÷Ð&„&C4¦ÿÿ ÿéöÐ&?>ÿÿ ÿéóÑ&$4§ÿÿ ÿèöÑ&9'…ÿÿ ÿîöÏ&§4¨ÿÿ ÿéñÏ&–)ÿÿ ÿè÷Ð&–+5ÿÿ ÿéõÐ&. 9ÿÿ ÿëõÏ&…&„„ÿÿ ÿçóÐ&<& ª «ÿÿ ÿèôÐ&(ˆ9ÿÿ ÿëõÏ&„&jiÿÿ ÿêôÐ&…&€4©ÿÿ ÿéñÏ&9FÿÿÿêòÏ&€4ªÿÿ ÿçøÒ&9Õÿÿ ÿêòÏ&…&€4«ÿÿ ÿéòÏ&–0Ãÿÿ ÿéöÏ&–/ ÿÿ ÿéòÏ&–*=ÿÿ ÿéôÒ&9Qÿÿ ÿèôÏ&/D>ÿÿ ÿèõÑ&(+ÿÿ ÿé÷Ï&9÷ÿÿ ÿèòÏ&%[9ÿÿ ÿèôÒ&9%ÿÿ ÿçõÏ&90”ÿÿ ÿèïÏ&>& 4¬ÿÿ ÿéõÏ&@+6ÿÿ ÿëöÏ&„4­ÿÿ ÿéõÏ&56ÿÿ ÿéóÐ&§9ÿÿ ÿçõÐ&=<ÿÿ ÿèðÐ&–. ÿÿ ÿìôÍ&g4®ÿÿ ÿéñÑ&9%÷ÿÿ ÿéôÏ&>++ÿÿ ÿèôÏ&>,šÿÿ ÿéòÏ&–*>ÿÿ ÿéóÏ&–Oÿÿ ÿéóÏ&9'“ÿÿ ÿëóÏ&3&ÿÿ ÿëõÒ&„,›ÿÿÿêòÏ&U€ÿÿ ÿæóÑ&>Tÿÿ ÿéðÏ&ÖÿÿÿêïÒ&SRÿÿ ÿìôÒ&1gÿÿ ÿèõÒ&“4¯ÿÿ ÿèõÒ&“4°ÿÿ ÿéôÏ&.ÿÿ ÿéöÐ&UVÿÿ ÿèúÐ&T4±ÿÿ ÿèóÏ&XKÿÿ ÿéòÐ&âZÿÿÿèóÏ&YXÿÿÿéõÐ&5[ÿÿ ÿéõÏ&-ô4²ÿÿÿèóÏ&\Xÿÿ ÿéòÐ&âáÿÿ ÿéòÐ&]âÿÿÿéõÐ&^5ÿÿ ÿêóÐ&4<ÿÿ ÿéõÐ&:5ÿÿ ÿéòÐ&‘âÿÿ ÿéõÐ&5_ÿÿ ÿéõÐ&5aÿÿ ÿéôÐ&=>ÿÿÿèöÏ&ghÿÿ ÿéòÐ&bâÿÿÿéõÐ&5cÿÿ ÿéóÐ&;<ÿÿÿéõÐ&d5ÿÿÿéõÐ&e5ÿÿ ÿéõÐ&*?5ÿÿÿéõÐ&f5ÿÿ ÿéõÐ&i5ÿÿ ÿèöÏ&. 4¶ÿÿ ÿéòÐ&*@(½ÿÿ ÿéñÐ&ü4·ÿÿ ÿçòÐ&ýþÿÿ ÿçóÐ&ýÿÿÿ ÿèóÏ&ÿÿ ÿéòÌ&0­4¸ÿÿ ÿèçÏ&4¹ÿÿ ÿèéÏ&4ºÿÿ ÿæëÏ&4»ÿÿ ÿèòÏ&4¼ÿÿ ÿèèÏ&4½ÿÿ ÿéòÐ&ÿÿ ÿèïÏ& Mÿÿ ÿèôÏ&áÿÿ ÿéðÏ&þœÿÿ ÿéèÐ&ëþÿÿ ÿéëÏ&úþÿÿ ÿèöÏ&-ïÿÿ ÿéóÏ&Ñ-ÿÿ ÿéìÏ&Þÿÿ ÿéôÏ&þ6ÿÿ ÿéóÏ&-ýÿÿ ÿéôÏ&-( ÿÿ ÿéìÏ&-( ÿÿ ÿéóÏ&È-ÿÿ ÿéðÏ&lÿÿ ÿéóÏ&-&ÿÿ ÿèõÏ&-šÿÿ ÿéôÏ&„·ÿÿ ÿéòÏ&-ÿÿ ÿéçÏ&þ*Aÿÿ ÿéõÏ&-³ÿÿ ÿéòÏ&-(ÿÿ ÿéóÏ&- ÿÿ ÿéòÏ&(ÿÿ ÿéìÏ&(ÿÿ ÿéóÐ&¿-ÿÿ ÿèíÏ&Óÿÿ ÿéêÏ&,“-ÿÿ ÿéëÏ&„%3ÿÿ ÿéóÏ&)„ÿÿ ÿéôÏ&„Ûÿÿ ÿéóÏ&-ßÿÿ ÿéóÏ&5ÿÿ ÿéóÏ&‰-ÿÿ ÿèöÑ&Óÿÿ ÿéóÏ&Àÿÿ ÿèöÏ& ÿÿ ÿéöÏ&„/ÿÿ ÿéòÏ&(%ÿÿÿéõÏ&ÂÿÿÿèôÏ&Âÿÿ ÿéõÑ&²ÿÿ ÿèòÏ&0$ÿÿ ÿéòÏ&ºÿÿ ÿéôÏ&.-ÿÿ ÿèõÏ&ÿÿ ÿèõÏ&Ëÿÿ ÿéôÏ&wÿÿ ÿéìÐ&„ÿÿ ÿèôÏ&(/ÿÿ ÿéõÏ&R-ÿÿ ÿéíÏ&ô-ÿÿ ÿééÏ&*Bÿÿ ÿéöÏ&B.7ÿÿ ÿéõÏ&„õÿÿ ÿéôÏ&"ÿÿ ÿçõÏ&!vÿÿ ÿéöÏ&Þÿÿ ÿèðÏ&„&ú4¿ÿÿ ÿéñÏ&„8ÿÿ ÿé÷Ï&*Cÿÿ ÿéõÏ&Ûÿÿ ÿèóÏ&*Dÿÿ ÿéõÏ&-‘ÿÿÿéòÐ&±ÿÿ ÿéñÏ&-ñÿÿ ÿéòÏ&„. ÿÿ ÿéõÐ&&ïðÿÿÿéòÏ&ÂAÿÿ ÿèöÏ&-+8ÿÿ ÿéôÏ&+7ÿÿ ÿéôÏ&…ÿÿ ÿéôÏ&Fÿÿ ÿéóÏ&„'Fÿÿ ÿéëÏ&-(Áÿÿ ÿçôÏ&-4Àÿÿ ÿéôÏ&%Ëÿÿ ÿéóÏ&-ÿÿ ÿéóÏ&/'ÿÿ ÿèòÏ&+..7ÿÿ ÿèôÏ&ÿÿ ÿéòÐ&kÿÿ ÿéôÏ&4Áÿÿ ÿèöÐ&Âÿÿ ÿéòÏ&ÝÿÿÿéöÏ&Â%!ÿÿ ÿéôÐ&?ÿÿ ÿéðÏ&Ãÿÿ ÿéóÏ&+ÿÿ ÿèóÏ&-îÿÿ ÿèõÏ&+ÿÿ ÿéôÏ&-+ÿÿ ÿéõÏ&-kÿÿ ÿéïÐ&Rÿÿ ÿèìÏ&&ÿÿ ÿéëÏ&»ÿÿ ÿéóÏ&"Wÿÿ ÿéóÏ&)Kÿÿ ÿéòÏ&0°ÿÿ ÿéóÏ&-'Ëÿÿ ÿéöÏ&-äÿÿ ÿçóÏ&-'£ÿÿ ÿèôÏ&„Øÿÿ ÿéóÏ&ÿÿ ÿéóÐ&+9ÿÿ ÿéòÏ&².7ÿÿ ÿèöÐ&/(&ð.7ÿÿ ÿèóÏ&-ÿÿ ÿéèÐ&-%žÿÿ ÿçõÏ&„‡ÿÿÿéóÐ&0ƒ^ÿÿ ÿéíÏ&,Çÿÿ ÿéóÏ&ÿÿ ÿéóÏ&Áÿÿ ÿéòÏ&-Îÿÿ ÿçòÐ&+:ÿÿ ÿéóÐ&/)ÿÿ ÿéïÐ&& ò4Âÿÿ ÿéëÐ&Wÿÿ ÿééÏ&µÿÿ ÿéïÐ&,œÿÿ ÿéêÏ&jÿÿÿéóÎ&,+;ÿÿ ÿéïÏ&+<ÿÿ ÿéòÏ&.Òÿÿ ÿéõÐ&4Ãÿÿ ÿéõÏ&ÿÿ ÿéóÏ&'¦ÿÿ ÿéöÏ&/*ÿÿ ÿéçÏ&+=ÿÿÿéñÏ&Â4Äÿÿ ÿéòÏ&Sÿÿ ÿéôÑ&/sÿÿ ÿéðÏ&„+3ÿÿ ÿéõÏ&~-ÿÿ ÿéõÏ&„ ·ÿÿ ÿéõÏ&sÿÿ ÿéòÏ&„0Õÿÿ ÿéöÏ&Uÿÿ ÿéòÐ&q&<ÿÿ ÿèïÏ&0ÿÿ ÿèõÐ&/,4Åÿÿ ÿèõÐ&L/,ÿÿ ÿéöÑ&“ÿÿ ÿéôÏ&P.7ÿÿ ÿéòÏ&¨ÿÿ ÿéóÏ&ÿÿ ÿèîÏ&ÿÿ ÿéñÏ&$.7ÿÿ ÿéõÏ&Šÿÿ ÿèöÏ&+ÿÿÿèõÏ&Âáÿÿ ÿçôÐ&%ÿÿ ÿéôÑ& ¸ÿÿÿéôÑ&íÍÿÿ ÿé÷Ï&õÿÿ ÿéóÐ&-Kÿÿ ÿéóÏ&„ÿÿÿéóÏ&ÕÂÿÿ ÿéðÏ&-ÿÿÿéðÏ&^&ÿÿÿèóÏ&Â0½ÿÿ ÿéðÏ&Íÿÿ ÿéõÐ&„\ÿÿ ÿèôÐ&Pÿÿ ÿèõÏ&,ž.7ÿÿ ÿéîÏ&.„ÿÿÿéðÐ&0†^ÿÿ ÿé÷Ï&BÿÿÿéòÐ&ÂÙÿÿ ÿéôÏ&­ÿÿ ÿéòÏ&+ÿÿ ÿéñÏ&4Æÿÿ ÿèôÏ&/,4ÇÿÿÿéõÏ&Í&ÉÊÿÿÿèòÐ&úËÿÿ ÿéôÏ&.74Èÿÿ ÿéóÏ&.74Éÿÿ ÿæöÏ&pÿÿ ÿéôÏ&„ÿÿ ÿéíÏ&-éÿÿ ÿèòÐ&.74Êÿÿ ÿéóÑ&!.7ÿÿ ÿèñÑ&„ëÿÿ ÿéóÏ&©ÿÿ ÿéïÏ&,ÿÿ ÿéôÏ&ÿÿ ÿéôÐ&ÿÿ ÿèñÏ&&i&&e.7ÿÿ ÿéóÏ&.7.—ÿÿ ÿéôÏ& ÿÿ ÿæ÷Ï&„(¿ÿÿ ÿè÷Ï&&(.7ÿÿÿéóÏ&^0Éÿÿ ÿéòÏ&%ÿÿ ÿéõÐ&ƒÿÿ ÿèñÏ&%Ïÿÿ ÿéòÏ&©ÿÿ ÿéòÏ&'cÿÿ ÿéôÏ&%=.7ÿÿ ÿéõÏ&.7ÿÿÿèñÏ&ÂŽÿÿ ÿéôÐ&(4ÿÿ ÿçõÏ& Õÿÿ ÿèôÏ&4Ëÿÿ ÿéôÏ&%$ÿÿÿéóÏ&EÂÿÿ ÿéóÏ&Š„ÿÿ ÿéôÏ&ì.7ÿÿ ÿéóÏ&(5ÿÿ ÿèíÏ&'©ÿÿ ÿéîÏ&Þÿÿ ÿéòÏ&„'Rÿÿ ÿéõÐ&ã.7ÿÿ ÿéóÏ&(6ÿÿ ÿéóÏ&(7ÿÿÿèôÏ&Â(8ÿÿ ÿèìÐ&&ÿÿ ÿéôÏ&/ÿÿ ÿéêÐ&cÿÿ ÿéõÏ&.ÿÿ ÿéêÏ&(9ÿÿ ÿçöÏ&4Ìÿÿ ÿéíÏ&?ÿÿ ÿéðÒ&0íÿÿ ÿçõÑ&Žÿÿ ÿéóÏ&Àÿÿ ÿéôÑ&'ª„ÿÿ ÿéóÏ&(:ÿÿ ÿéóÏ&ìÿÿ ÿéöÐ&4Íÿÿ ÿèîÐ&,Ÿ.7ÿÿÿéôÏ&Â[ÿÿ ÿéõÏ&+>ÿÿ ÿéõÐ&®ÿÿ ÿèïÓ&-4Îÿÿ ÿéöÏ&"H„ÿÿ ÿèøÏ&'±.7ÿÿ ÿèõÑ&&.7ÿÿ ÿçõÏ&Þÿÿ ÿéîÏ&.70bÿÿ ÿèóÐ&(<ÿÿ ÿéñÏ&¼ÿÿ ÿéëÏ&(&à.7ÿÿ ÿéõÏ&/ÿÿ ÿèðÏ&, ÿÿÿéõÐ&Â%ªÿÿ ÿçóÏ&©.7ÿÿ ÿéòÏ&£.7ÿÿ ÿèóÏ&„,¡ÿÿ ÿé÷Ï&Kÿÿ ÿéõÏ&„ @ÿÿ ÿéõÑ& ÿÿ ÿèõÑ&‰ÿÿÿéóÏ&1Âÿÿ ÿéõÏ&„Ýÿÿ ÿéõÐ&&!ä4Ïÿÿ ÿèõÏ&2.7ÿÿÿéðÐ& ÿÿ ÿéóÏ&Ÿÿÿ ÿéóÏ& ÿÿ ÿéóÏ&(=ÿÿ ÿèõÏ& Ý.7ÿÿ ÿéñÏ&..7ÿÿÿéòÏ& Vÿÿ ÿéòÐ&%ÐÿÿÿéôÏ&Â(mÿÿ ÿéóÏ&„(>ÿÿ ÿèìÏ&(?.7ÿÿ ÿéíÏ&%ÕÿÿÿèöÏ&»ÿÿ ÿéóÐ& I.7ÿÿÿéôÈ&Í0Íÿÿ ÿéõÏ&d.7ÿÿ ÿéòÏ&#jÿÿ ÿéïÏ&„(@ÿÿ ÿéòÒ&¢.7ÿÿ ÿé÷Ð&&ýìÿÿÿèöÏ& `Âÿÿ ÿèöÏ&<ÿÿ ÿèôÏ&Ûÿÿ ÿéôÑ&9ÿÿÿéôÏ&ÂYÿÿ ÿéñÏ&„(Aÿÿ ÿéòÏ&.L.7ÿÿÿéôÑ&Ï&ÐÍÿÿ ÿéíÏ&É„ÿÿ ÿéòÏ&„,mÿÿ ÿéòÏ&+ÿÿ ÿéôÐ&Ÿÿÿ ÿéöÏ&'e.7ÿÿ ÿéóÏ&%Óÿÿ ÿéóÏ&.5.7ÿÿ ÿéîÏ&¿ÿÿ ÿèöÏ& .7ÿÿ ÿéíÐ&,¢ÿÿ ÿéóÐ&,nÿÿ ÿéòÎ&á&âÑÿÿ ÿéóÏ&Ó&ÒÔÿÿ ÿéëÏ&Ðÿÿ ÿçðÑ&‡ÿÿ ÿéíÏ&Ñÿÿ ÿèõÒ&„/-ÿÿ ÿèñÐ&×&&eÿÿ ÿçòÑ&Óÿÿ ÿèøÐ& +.7ÿÿ ÿéôÏ&„4Ðÿÿ ÿçñÏ&/J.7ÿÿ ÿéñÏ&,£ÿÿ ÿéõÏ&"ë.7ÿÿ ÿéõÏ&:.7ÿÿ ÿéôÐ&&.7ÿÿ ÿéòÐ&#¥ÿÿ ÿéöÏ&#‰.7ÿÿ ÿéôÌ&Í4Ñÿÿ ÿéõÏ&"z.7ÿÿ ÿéóÏ&"µ.7ÿÿ ÿéíÏ&/›.7ÿÿ ÿéôÏ&#J.7ÿÿ ÿéëÏ&„" ÿÿ ÿèóÏ&#@.7ÿÿ ÿéöÏ&„",ÿÿ ÿéöÏ&,¤.7ÿÿ ÿéòÏ&„"ÿÿ ÿéòÏ&0¢ÿÿ ÿéñÏ&„"ÿÿ ÿèóÏ&!ö.7ÿÿ ÿéöÐ&ç&,êÿÿ ÿéóÏ&Ùÿÿ ÿäöÏ&„"oÿÿ ÿèóÏ&Z.7ÿÿ ÿéõÑ&'ÿÿ ÿèïÐ&„0¹ÿÿ ÿçõÏ&„Yÿÿ ÿéõÏ&£ÿÿ ÿèóÏ&!ê.7ÿÿ ÿéôÏ&¼.7ÿÿ ÿéóÑ&„Xÿÿ ÿéñÏ&„$Zÿÿ ÿéñÏ&#É.7ÿÿ ÿéõÏ&#Ù.7ÿÿ ÿèóÏ&â.7ÿÿ ÿéîÏ&/¸.7ÿÿ ÿéîÑ&ã.7ÿÿÿéôÐ&Ð&Í4Òÿÿ ÿéðÑ&/ƒÿÿ ÿéôÏ&-.7ÿÿ ÿèöÐ&&á%ÃÿÿÿéôÐ&Í"Êÿÿ ÿéñÏ&"Dÿÿ ÿéóÏ&!ÿÿ ÿéóÐ&&ÿÿ ÿéôÏ&!^ÿÿÿéôÏ&Ð&Í4Óÿÿ ÿèóÏ&?ÿÿ ÿéôÏ&„!"ÿÿ ÿèòÏ&„Ÿÿÿ ÿéôÏ&.70{ÿÿ ÿèñÏ&&e&(C.7ÿÿ ÿèóÏ&„!÷ÿÿ ÿéôÏ&„"hÿÿ ÿéóÏ&„#°ÿÿ ÿéóÏ&„(Bÿÿ ÿéóÐ&0–ÿÿ ÿéöÐ&(D&%ÿÿ ÿéóÐ&oÿÿ ÿéíÐ&Õ&.74Ôÿÿ ÿèôÏ&#´.7ÿÿ ÿéóÏ&4Õÿÿ ÿèôÐ&,Åÿÿ ÿéðÏ&$¬.7ÿÿ ÿéîÑ&+ÿÿ ÿè÷Ï&$ß.7ÿÿ ÿéõÏ&„$Êÿÿ ÿèõÏ&„¢ÿÿ ÿéóÏ&.74Öÿÿ ÿéöÏ&$tÿÿ ÿéòÏ&.6.7ÿÿ ÿéîÑ&$–.7ÿÿ ÿéóÐ&åÿÿ ÿéôÏ&"„.7ÿÿÿéõÏ&Â)iÿÿ ÿèòÏ&;.7ÿÿ ÿéîÐ&„#Áÿÿ ÿéóÏ&$D.7ÿÿ ÿéïÑ&-ê.7ÿÿ ÿèõÐ&'p.7ÿÿ ÿèöÏ&% .7ÿÿ ÿéòÐ&„% ÿÿ ÿéóÐ&&,Ä,¥ÿÿ ÿçíÐ&$ìÿÿ ÿé÷Ï&„&ì#vÿÿ ÿéðÏ&#wÿÿ ÿéóÐ&#z.7ÿÿ ÿéôÏ&*)ÿÿ ÿéöÑ&#u&â.7ÿÿ ÿèïÏ&„+ÿÿ ÿéóÏ&#|ÿÿ ÿéîÏ&#{.7ÿÿ ÿéôÑ&Ð.7ÿÿ ÿèôÑ&#x#yÿÿ ÿçõÐ&&&  ÿÿ ÿéõÏ&„#Cÿÿ ÿéõÏ&„$ÿÿ ÿèóÏ&„$“ÿÿÿéóÏ&ÂMÿÿ ÿéðÑ&‡ÿÿ ÿéôÑ&4×ÿÿÿéõÏ&Âÿÿ ÿèõÐ& ÿÿ ÿçóÐ&æÿÿ ÿéðÏ&$.7ÿÿ ÿèòÐ&Ýÿÿ ÿéóÏ&R.7ÿÿ ÿéîÏ&$‘„ÿÿ ÿéòÑ&á&â4ØÿÿÿçñÏ&Â#ÿÿ ÿéóÏ&$Aÿÿ ÿèôÏ&„4Ùÿÿ ÿéôÏ&1ÿÿ ÿéòÏ&/;.7ÿÿ ÿéõÑ&„&†4Úÿÿ ÿæïÏ&.74Ûÿÿ ÿéòÏ&'m.7ÿÿ ÿèòÐ&„/Mÿÿ ÿéóÏ&)’&)“.7ÿÿ ÿèôÐ&(&.74Üÿÿ ÿéôÐ&.8.7ÿÿ ÿé÷Ï&„(Eÿÿ ÿéöÐ&E.7ÿÿ ÿéîÏ&q.7ÿÿ ÿéòÏ&w.7ÿÿ ÿéôÐ&.ûÿÿ ÿéôÑ&.9.7ÿÿ ÿéîÑ&„¹ÿÿ ÿéõÏ&„4Ýÿÿ ÿéðÑ&(Fÿÿ ÿèôÏ&,Ö.7ÿÿ ÿéôÑ&'·.7ÿÿ ÿéôÏ&„'àÿÿ ÿçóÏ&+ÿÿ ÿéöÑ&%Øÿÿ ÿçîÐ&4Þÿÿ ÿéóÑ&(G.7ÿÿ ÿçõÐ&&ÏÐÿÿ ÿéòÏ&..7ÿÿ ÿèôÏ&„/.ÿÿ ÿéõÑ&$4ßÿÿ ÿéõÐ&&)ñ4àÿÿ ÿèöÑ&,y.7ÿÿ ÿèöÐ&„(HÿÿÿéôÐ&Í&#\+ÿÿ ÿéöÐ&Í0 ÿÿÿéöÏ&Í4áÿÿ ÿèôÐ&&Þ4âÿÿ ÿéôÏ&#~ÿÿ ÿéòÏ&,¦.7ÿÿ ÿéõÒ&~.7ÿÿ ÿéóÏ&+ÿÿ ÿéõÑ&„#€ÿÿ ÿéöÏ&%+ÿÿÿéõÏ&j4ãÿÿ ÿéíÏ&:ÿÿ ÿçõÑ&'º.7ÿÿ ÿéóÏ&„'vÿÿ ÿéöÏ&.7ÿÿ ÿçõÏ&4äÿÿÿèôÑ&k&#x4åÿÿ ÿæïÏ&,(.7ÿÿ ÿèõÏ&Ô.7ÿÿ ÿéóÐ&&÷4æÿÿ ÿèïÏ&(I.7ÿÿÿèôÓ&(J4çÿÿ ÿéëÐ&&&®(Lÿÿ ÿéîÏ&.7&0b4èÿÿ ÿçôÏ&(Kÿÿ ÿéóÏ&%íÿÿ ÿéîÏ&„/!ÿÿ ÿèôÏ&(Mÿÿ ÿèõÐ&&4éÿÿ ÿèôÏ&<.7ÿÿ ÿèôÐ&&Þ4êÿÿ ÿèõÐ&&.74ëÿÿ ÿçòÏ&%â.7ÿÿ ÿéöÑ&/Ž.7ÿÿ ÿéóÒ&„%æÿÿ ÿèõÐ&ì.7ÿÿ ÿéöÏ&%.7ÿÿ ÿéòÏ&„#îÿÿ ÿéóÐ&'¾ÿÿ ÿèõÏ&'}.7ÿÿ ÿéôÐ&ê&,§ÿÿ ÿéóÏ&„'ÿÿ ÿéòÏ&„ÿÿ ÿèõÐ&&4ìÿÿ ÿèõÏ&(N.7ÿÿ ÿèöÏ&,¨„ÿÿ ÿéôÐ&(Oÿÿ ÿéöÐ&ëÿÿ ÿèôÐ&//ÿÿ ÿéõÏ&'¿.7ÿÿ ÿéõÏ&.7,×ÿÿ ÿèôÏ&’ÿÿ ÿéõÑ&&,Ù ÿÿ ÿéóÐ&&¤ÿÿ ÿéõÐ&!ä&4íÿÿ ÿéòÐ&%..7ÿÿ ÿéóÐ&&4îÿÿ ÿèôÐ&,).7ÿÿ ÿéòÏ&.:.7ÿÿ ÿèóÑ&.74ïÿÿ ÿéõÐ&&Þ4ðÿÿ ÿéôÑ&/".7ÿÿ ÿèõÐ&&.74ñÿÿ ÿé÷Ï&.74òÿÿ ÿé÷Ñ&&Ã.7ÿÿ ÿèõÐ&4óÿÿ ÿéóÐ&/ÿÿ ÿéðÐ&0¶ÿÿ ÿçôÏ&„-Oÿÿ ÿéòÐ&,©.7ÿÿ ÿéóÒ&´.7ÿÿ ÿéôÏ&'Ä.7ÿÿ ÿéñÏ&'.7ÿÿÿèôÍ&k&#x4ôÿÿ ÿéðÏ&'.7ÿÿ ÿé÷Ï&ö.7ÿÿ ÿéôÐ&Í,ªÿÿ ÿéóÐ&&÷4õÿÿ ÿéõÏ&„¤ÿÿ ÿéõÏ&/#.7ÿÿ ÿéòÐ&.70¦ÿÿ ÿéðÏ&,†„ÿÿ ÿéòÐ&.Óÿÿ ÿéôÐ&Í.ÿÿ ÿèöÑ&'….7ÿÿ ÿèôÏ&-.7ÿÿ ÿéöÏ&/Þ.7ÿÿ ÿèõÐ&&4öÿÿ ÿçøÒ&Õ.7ÿÿ ÿéñÏ&(P.7ÿÿ ÿéóÏ&Êÿÿ ÿéòÏ&0Ã.7ÿÿÿéòÐ&Â8ÿÿÿéôÑ&Í4÷ÿÿ ÿé÷Ð&.;ÿÿ ÿéöÐ&.<4øÿÿ ÿèôÐ&$à&$áÿÿ ÿéðÏ&„%óÿÿ ÿèôÐ&/Dÿÿ ÿéóÐ&&‡4ùÿÿ ÿéôÏ&„'ÿÿ ÿçôÑ&(Q.7ÿÿ ÿéôÒ&Q.7ÿÿ ÿèïÐ&&ô4úÿÿ ÿéôÐ&4ûÿÿ ÿéöÏ&.74üÿÿ ÿèôÒ&%.7ÿÿ ÿéòÑ&.74ýÿÿ ÿçñÐ&& ©4þÿÿ ÿç÷Ò&(R.7ÿÿ ÿèôÐ&/£ÿÿ ÿçõÐ&4ÿÿÿ ÿéöÒ&)ø.7ÿÿ ÿèóÐ&@ÿÿ ÿçõÏ&.70”ÿÿ ÿéõÑ&&Þ5ÿÿ ÿèõÑ&(.7ÿÿ ÿéôÐ&.75ÿÿ ÿéòÐ&)÷.7ÿÿ ÿèóÐ&?&=.7ÿÿ ÿèóÐ&>&¡.7ÿÿ ÿèòÐ&)0.7ÿÿ ÿèñÏ&,À.7ÿÿ ÿèïÏ&.75ÿÿ ÿèöÐ&5ÿÿ ÿéøÑ&û.7ÿÿ ÿéöÐ&H.7ÿÿ ÿèöÐ&.75ÿÿ ÿçôÐ&0ÿÿ ÿéöÑ&5ÿÿ ÿéíÏ&£.7ÿÿ ÿé÷Ð&;ÿÿ ÿèòÏ&,}.7ÿÿ ÿéôÑ&.75ÿÿ ÿéñÑ&%÷.7ÿÿ ÿèòÑ&(Sÿÿ ÿçöÐ&5ÿÿ ÿéîÏ&.75 ÿÿ ÿéóÏ&(T.7ÿÿ ÿéõÐ&.75 ÿÿ ÿéòÐ&&.,5 ÿÿ ÿçõÐ&‡.7ÿÿ ÿèôÓ&…#xÿÿ ÿéòÐ&&.,5 ÿÿ ÿèôÐ&.75 ÿÿ ÿéóÐ&&Öÿÿ ÿçñÑ&%ø.7ÿÿ ÿéôÒ&.Aÿÿ ÿéðÐ&Ãÿÿ ÿæóÐ&.=ÿÿ ÿéòÐ&.75ÿÿ ÿèïÐ&1ÿÿÿèõÏ&(U(Vÿÿ ÿéóÏ&«5ÿÿÿèõÏ&Bÿÿ ÿèôÏ&)ÿÿ ÿèõÏ&AÿÿÿèõÏ&ÿÿ ÿèõÐ&Æ(Wÿÿ ÿèõÏ&,­Pÿÿ ÿèõÏ&Q-ÿÿ ÿèõÏ&(X-ÿÿÿéöÏ&NÿÿÿèôÏ&çOÿÿ ÿèôÏ&+‘ÿÿ ÿéöÑ&äÿÿ ÿèôÏ&oÿÿ ÿèôÏ&•ÿÿ ÿéóÎ&+?.Bÿÿ ÿèôÏ&nÿÿ ÿéöÐ&LÿÿÿèõÏ&-5ÿÿ ÿèôÐ&+çÿÿ ÿèõÐ&T-ÿÿ ÿèõÏ&a-ÿÿ ÿèôÏ&ç(Yÿÿ ÿèõÏ&- °ÿÿ ÿèõÏ&-ÿÿ ÿèôÏ&ÿÿ ÿèõÏ&-,=ÿÿ ÿèõÐ&,«-ÿÿ ÿèõÐ&,¬,­ÿÿ ÿèôÑ& çÿÿÿèôÏ&”æÿÿÿèôÏ&÷æÿÿ ÿèõÏ&-5ÿÿ ÿèõÏ&.C-ÿÿ ÿéöÏ&|ÿÿÿçõÏ&âãÿÿÿèõÒ&(Z-ÿÿÿéóÐ&$(Vÿÿ ÿèõÐ&-([ÿÿ ÿèôÏ& Úçÿÿ ÿçõÐ&-5ÿÿ ÿèôÏ&ç5ÿÿ ÿèôÏ&ç"ÿÿÿéóÐ&#$ÿÿÿèõÏ&-5ÿÿÿèóÐ&% $ÿÿÿèôÏ&æ(\ÿÿ ÿíóÐ&*5ÿÿÿèôÑ&æ(]ÿÿÿèõÐ&&ÿÿÿèôÑ&ç5ÿÿ ÿçôÏ&(ÄæÿÿÿèôÏ&ç5ÿÿÿéóÐ&$™ÿÿ ÿéóÐ&$(ÿÿ ÿèôÑ&æ,¯ÿÿ ÿèôÏ&,®æÿÿ ÿéóÎ&+?+„ÿÿ ÿèôÏ&,°æÿÿÿèôÑ&æ0äÿÿÿçôÑ&ûûÿÿ ÿêôÒ&/5ÿÿ ÿèõÐ&'&(&ÿÿ ÿéöÐ&5Áÿÿ ÿèöÐ&Á5ÿÿ ÿéöÐ&ÂÁÿÿ ÿéõÐ&¢)ÿÿ ÿèöÐ&Á5ÿÿ ÿèôÐ&ÿÿ ÿêôÏ&&øÿÿ ÿèïÐ&¢0¹ÿÿÿçíÑ&“”ÿÿ ÿçöÐ&&,²5 ÿÿÿéíÍ&“–ÿÿ ÿìõÑ&—5!ÿÿ ÿèøÏ&M•ÿÿ ÿèöÐ&BCÿÿ ÿéøÐ&LMÿÿÿéðÉ&&W,³ÿÿÿèöÐ&C,±ÿÿ ÿéöÏ&C]ÿÿ ÿéöÏ&$3Cÿÿ ÿèöÐ&´¥ÿÿ ÿéøÏ&M+šÿÿÿéõÏ&,Ã5$ÿÿ ÿéòÏ&K0­ÿÿ ÿêóÎ&¸}ÿÿ ÿêóÊ&»!rÿÿ ÿéóÏ& Í}ÿÿÿêóÏ&(Ã}ÿÿ ÿêóÏ&(Â}ÿÿ ÿèóÏ&}±ÿÿ ÿéóÑ&|}ÿÿ ÿêóË&}5%ÿÿ ÿêóÎ&}%ÿÿ ÿéðÍ& Ç#7ÿÿÿèôË&Þ5&ÿÿ ÿèôÌ&Þ/$ÿÿ ÿé÷Ï&w&§¹ÿÿ ÿéôÐ&w&º5'ÿÿ ÿéöÏ&y,éÿÿ ÿéôÏ&w&§5(ÿÿ ÿèòÏ&&y5)ÿÿ ÿèõÏ&y&5*ÿÿ ÿéóÏ&y&5+ÿÿ ÿéõÏ&yxÿÿ ÿèïÐ&y5,ÿÿ ÿèöÏ&w0mÿÿ ÿéôÏ&w&ÿÿ ÿéöÏ&]&yÿÿ ÿéóÐ&y&=5-ÿÿ ÿèõÏ&y&5.ÿÿ ÿçóÏ&D&yÿÿ ÿéõÐ&<&w=ÿÿ ÿéòÏ&y&5/ÿÿ ÿèòÏ&y&­50ÿÿ ÿçõÏ&y&­51ÿÿ ÿéòÏ&yüÿÿ ÿé÷Ñ&y&.E52ÿÿ ÿéõÑ&y&.E53ÿÿ ÿèõÑ&!©&.E54ÿÿ ÿèöÐ&!©55ÿÿ ÿèóÎ&!©56ÿÿÿçöÄ&U59ÿÿÿêöÐ&.F5:ÿÿ"ÿéèÃ&(¬5<ÿÿ ÿéçÏ&[5=ÿÿ ÿéõÏ& ¯Hÿÿ ÿéõÏ&òñÿÿ ÿèõÇ&IóÿÿÿéóÄ&V¨ÿÿÿéðÇ&ILÿÿ ÿéòÆ&KJÿÿÿéìÎ&Y–ÿÿÿéðÀ&lTÿÿ ÿè߯&)ÜJÿÿÿêòÏ&(–ÿÿÿêóÐ&¿0ªÿÿÿóôÂ&wVÿÿ ÿèôÆ&JÿÿÿéõÍ&TRÿÿÿìøÏ&V#ÿÿÿéòÏ&¬TÿÿÿêìÐ&*ÙVÿÿÿççÇ&I5>ÿÿ ÿéðÇ&w&ÿvÿÿÿèõÏ&‘0ªÿÿÿèóÆ&R…ÿÿ*ÿëïÇ&I3ÿÿÿêïÇ&¨§ÿÿÿêëÏ&0ª(ÁÿÿÿèòÏ&0ª0«ÿÿ ÿçôÇ&wªÿÿ ÿéõÃ&«òÿÿÿéáÇ&I(ÀÿÿÿéíÂ&ô–ÿÿÿéëÉ&%“%’ÿÿÿèìÐ&%”ÿÿÿéôÎ&TÚÿÿÿèöÑ&ÓTÿÿÿìðÏ&{VÿÿÿéñÏ&ò5?ÿÿÿéñÍ&ñRÿÿÿèîÇ&I5@ÿÿÿçñÇ&¨5AÿÿÿéòÏ&“5BÿÿÿéôÂ&T%ËÿÿÿêöÏ&R0®ÿÿÿéóÏ&VÔÿÿÿééÏ&TµÿÿÿçõÏ&DYÿÿ ÿéôÇ& ƒ&¨5Cÿÿÿé÷Ï&RSÿÿÿéóÐ&UTÿÿÿêëÐ&VWÿÿÿéêÄ&Tjÿÿ ÿéìÇ& €&¨5Dÿÿ ÿïóÇ&|wÿÿÿðòÇ&I,µÿÿÿéòÏ&D0°ÿÿÿèõÏ&R+0ÿÿÿçåÇ&¨,¶ÿÿÿêðÇ&RÃÿÿ ÿé÷Ñ&MNÿÿÿéõÐ&/&* ÿÿÿèðÈ&,· ÿÿ ÿçêÇ&¨&  €ÿÿ ÿîöÅ&æ0hÿÿÿõòÆ&J5EÿÿÿêõÏ&RÿÿÿéôÇ&*IÿÿÿéóÐ&TKÿÿ ÿéñÇ&ëwÿÿÿé÷Ï&R+ÿÿÿòõÅ&TÔÿÿÿéõÐ&Tóÿÿ ÿèóÇ&%”5FÿÿÿðóÄ&TÕÿÿÿéðÇ&ò5GÿÿÿèíÒ&¾VÿÿÿèîÈ& ½ÿÿÿèãÇ&¨¼ÿÿÿéóÇ&VÖÿÿÿëôÏ&RÿÿÿéôÐ&UVÿÿÿéíÉ&«ªÿÿÿéóÊ&T¬ÿÿÿé÷Ñ&V,ÍÿÿÿçõÑ&R ÿÿÿêøÑ& RÿÿÿéñÆ&TòÿÿÿééÉ&ª,¸ÿÿÿéòÐ&D*ÿÿ ÿçöÇ&¨<ÿÿÿéðÒ&0íVÿÿÿéñÏ&0eòÿÿÿéóÅ&VŠÿÿÿçõÑ&RŽÿÿÿéõÐ&RƒÿÿÿêóÍ&ETÿÿ ÿçôÈ& 5HÿÿÿêõÐ&R,ÂÿÿÿçôÏ&ªDÿÿÿéòÏ&V YÿÿÿéóÏ&V1ÿÿ ÿéóÉ&ª5IÿÿÿéóÏ&G&'`5JÿÿÿèôÏ&ÛTÿÿÿçöÏ&R _ÿÿ ÿéïÎ&òÜÿÿÿéóÏ&V(=ÿÿÿéëÇ&I5Kÿÿ ÿéôÉ&ª5Lÿÿ ÿèòÇ&¨5MÿÿÿéìÇ&Rÿÿ ÿéçÓ&ò5NÿÿÿìöÏ&D'eÿÿÿéöÏ&R"HÿÿÿëôÏ&RZÿÿÿéóÏ&D&f,¹ÿÿÿèóÎ&R%ÖÿÿÿéñÐ& :“ÿÿ ÿèòÇ&¨ NÿÿÿîòÏ&V"ÿÿÿéöÐ&,º&ç,êÿÿÿéöÄ&G Œÿÿ ÿéòË&ä,»ÿÿÿéóÇ&V!ÿÿÿèõÈ&TxÿÿÿéòÏ&“5OÿÿÿéõÍ&/&"zÿÿÿéðÈ&V’ÿÿÿè÷Ï&!ßRÿÿÿéòÐ&V"öÿÿÿéóÏ&V"¶ÿÿÿéõÏ&R:ÿÿÿèòÌ&RŸÿÿÿéóÑ&VXÿÿÿçõÉ&YVÿÿÿéöÎ&V"YÿÿÿèóÇ& áVÿÿÿîòÏ&LDÿÿÿéóË&%äÿÿÿéõÏ&D*oÿÿÿçõÏ&"!0ÿÿ ÿèòÇ&s5PÿÿÿéóÏ&/&/ÿÿÿîóË&,¼5QÿÿÿéöÏ&G+!ÿÿÿéòÏ& ÞDÿÿ ÿéòÐ&“0øÿÿ ÿêõÏ&l5RÿÿÿèôÏ&%*VÿÿÿêöÐ&,½EÿÿÿîóÊ&G$÷ÿÿÿéôÑ&VnÿÿÿêòÏ&D/Sÿÿÿê÷Ð&°GÿÿÿéóÏ&D+ÿÿÿéôÏ&D'ãÿÿ ÿëôÌ&u5SÿÿÿéòÈ&DÿÿÿéóÉ&D)PÿÿÿéôÏ&“9ÿÿÿéôÎ&D%'ÿÿÿèôÑ&G&,5TÿÿÿéõÏ&VšÿÿÿîïÏ&G2ÿÿÿîóÓ&,¼5UÿÿÿéóÏ&G†ÿÿ ÿèïÇ&s5VÿÿÿæòË&äãÿÿÿíóÐ&D'¾ÿÿÿèôÎ&G<ÿÿÿèôÐ&G,¾ÿÿÿéóÉ&D'ÿÿÿïêÌ&Ê5WÿÿÿèñÈ& 5XÿÿÿèõÐ&G&5YÿÿÿèôÏ&G'ŠÿÿÿéðÊ&D%óÿÿÿçôÇ&R€ÿÿÿéñÏ&GFÿÿÿèõÐ&V¢ÿÿÿéóÊ&GÊÿÿÿéõÈ&G%cÿÿÿéòÉ&,¿RÿÿÿèôÒ&%‚/&ÿÿÿçôÐ&G+(ÿÿÿèñÈ&G,ÀÿÿÿèôÏ&G%öÿÿÿéöÐ&HGÿÿÿéóÒ&@-ñÿÿÿêóÌ&G*lÿÿÿçñÑ&G%øÿÿÿéóË&.G,ºÿÿÿéäÐ&ò5\ÿÿ ÿéêÇ&w5]ÿÿ ÿéóÏ&“5^ÿÿÿéïÐ&“5_ÿÿ ÿéóÇ&w×ÿÿÿèðÉ&ª& 5`ÿÿÿéàÏ&ò5aÿÿÿèàÒ&%”5bÿÿÿéóÏ&ò5cÿÿ ÿéóÈ&  ÿÿ ÿéóÇ&“5dÿÿÿéöÏ&"ÛÿÿÿéðÐ&,Á’ÿÿ ÿéôÉ&õ çÿÿ ÿéôÉ&14ÿÿ ÿçõÏ&Yÿÿ ÿéëÏ&õ´ÿÿ ÿéìÐ&30ÿÿÿ ÿèìÎ&õ|ÿÿ ÿé÷Ì&3Eÿÿ ÿéöÎ&õØÿÿ ÿèõÐ&õLÿÿÿèêÏ&I ÿÿ ÿçõÑ&3 ÿÿÿïðÑ&-(5fÿÿ ÿèêÏ& Iÿÿ ÿèêÏ&°±ÿÿ ÿèõÏ&!àÿÿ ÿéöË&3#ÍÿÿÿïðÊ&-(&Ì5hÿÿ ÿèóÏ&-Nÿÿ ÿèñÏ&(ÿÿÿèöÐ&HÄÿÿ ÿéòÏ&ÿ2ÿÿÿèöÏ&¸iÿÿ ÿéëÏ&ÿúÿÿ ÿèôÈ&ÙÿÿÿéèÏ&Ò¸ÿÿ ÿéíÏ&îÿÿ ÿéôÏ&º&ÿÿÿéõÏ&¸¹ÿÿ ÿéßÏ&˜ÿÿ ÿéóÏ&MÿÿÿéîÏ&¸*ÿÿÿéóÏ&ѸÿÿÿêîÑ&Ûÿÿ ÿè÷Ï&ºkÿÿ ÿêõÏ&ñôÿÿÿçõÏ&¸ÓÿÿÿéðÏ&¸ÂÿÿÿéóÏ&¸&ÿÿÿéðÏ&¸ÿÿÿèõÏ&¸šÿÿ ÿéõÏ&õÿÿ ÿèöÏ&º‚ÿÿÿéõÏ&¸³ÿÿ ÿéôÏ&oÿÿ ÿéóÐ&~5ÿÿ ÿéõÏ&ÿÿ ÿéòÏ&ÿÿ ÿéòÏ&º(ÿÿÿéìÏ&¸Yÿÿ ÿé÷Ï&ºÿÿ ÿéôÏ&º( ÿÿ ÿèôÏ&”+ÃÿÿÿéóÏ&¸ÿÿ ÿéòÏ&ºÿÿÿéõÏ&¸yÿÿÿéöÏ&¸ÿÿÿé÷Ï&¸ÿÿÿéóÏ&¸ÈÿÿÿêóÑ&Vôÿÿ ÿñõÏ&ÿÿ ÿéêÏ&¾ºÿÿÿèóÏ&¸+ÅÿÿÿéõÏ&+ĸÿÿÿéíÏ&¸%9ÿÿÿèïÏ&;¸ÿÿ ÿèôÏ&º(/ÿÿÿéöÏ&Þ¸ÿÿ ÿéòÐ&º±ÿÿ ÿéóÏ&º ÿÿ ÿêôÏ&+ÆLÿÿ ÿèôÆ&JÙÿÿ ÿéõÏ&)ÕòÿÿÿéõÑ&¸²ÿÿ ÿéòÏ&¬ÿÿ ÿéòÏ&ººÿÿ ÿéõÏ&ºõÿÿ ÿéðÏ&+"ºÿÿ ÿéñÏ&º&ú/Õÿÿ ÿéõÏ&/ºÿÿÿéóÏ&¸1ÿÿ ÿéñÏ&º8ÿÿÿéóÐ&&¸ÿÿ ÿéòÏ&º&yÿÿ ÿéóÏ&ºÀÿÿ ÿéóÏ&º‰ÿÿ ÿéìÐ&º*ÙÿÿÿéìÏ&¸Òÿÿ ÿéôÏ&ºÿÿ ÿéõÏ&nºÿÿ ÿéõÏ&º,ÿÿ ÿéôÏ&ºwÿÿ ÿèòÏ&º0«ÿÿÿéôÏ&¸Úÿÿ ÿéóÏ&º!sÿÿ ÿéöÏ&/ ÿÿ ÿéòÏ& 5nÿÿÿéóÏ&¸!rÿÿÿéôÏ&¸)ÖÿÿÿéñÏ&¸)×ÿÿÿéõÏ&¸‘ÿÿ ÿèöÑ&ÓºÿÿÿèôÏ&¸!ÿÿ ÿéõÏ&…ÿÿ ÿèíÏ&º£ÿÿ ÿèòÏ&º0$ÿÿÿèõÏ&¸ÿÿ ÿèõÏ&ÿÿ ÿèöÏ&ºŸÿÿ ÿçõÏ&º!vÿÿÿéöÏ&¸iÿÿ ÿéñÏ&©ÿÿ ÿéôÏ&º…ÿÿ ÿèöÏ&‰5oÿÿ ÿèñÏ&ºÀÿÿÿéëÏ&¸(Áÿÿ ÿæõÏ&º)IÿÿÿèõÏ&¸ÿÿÿåñÐ&~šÿÿ ÿéóÏ&º,$ÿÿ ÿçöÏ&+dÿÿ ÿêßÒ&°5pÿÿ ÿéõÏ&ºÿÿ ÿéóÏ&ºÿÿ ÿéóÏ&ºÿÿ ÿèóÏ&èlÿÿÿçôÏ&¸%Ìÿÿ ÿéíÏ&º.Hÿÿ ÿéóÏ&&ÿÿ ÿèôÐ&”0Œÿÿ ÿéìÏ& Îÿÿ ÿéëÐ&Wºÿÿ ÿéôÐ&øÿÿÿéõÏ&k¸ÿÿ ÿéóÏ&ºQÿÿ ÿéðÏ&º5qÿÿ ÿèóÏ&ºÿÿ ÿéóÏ&ºÂÿÿ ÿéöÏ&,éÿÿ ÿçõÏ&Yÿÿ ÿéôÏ&%Ëÿÿ ÿéðÏ&ºÃÿÿÿéóÐ&¸)vÿÿÿèóÏ&¸ÿÿÿéôÏ&¸+ÿÿ ÿéõÏ&!ÿÿ ÿèôÑ&Ù¨ÿÿ ÿéêÏ&(oÿÿ ÿéöÏ&%! ÿÿ ÿéóÏ&º&/Õÿÿ ÿéèÐ&º%žÿÿ ÿèóÐ&è5rÿÿ ÿéóÏ&º™ÿÿ ÿèôÎ&”&˜—ÿÿ ÿèôÐ&•&–”ÿÿ ÿèóÉ&è5sÿÿ ÿéñÏ& Ãÿÿ ÿèìÏ&º&ÿÿ ÿéëÏ&º»ÿÿ ÿéìÏ&!wÿÿ ÿéóÏ&ºÁÿÿ ÿçóÏ&º'£ÿÿÿéóÐ&¸Uÿÿ ÿèòÏ&º&ÿÿÿéòÏ&¸Sÿÿ ÿèôÏ&º”ÿÿ ÿèõÏ&+ºÿÿ ÿéòÏ&ÝÿÿÿññÐ&,Æ~ÿÿ ÿéöÏ&ºÁÿÿ ÿè÷Ï&ºñÿÿ ÿéîÏ& &õ5tÿÿ ÿéïÐ& &!o òÿÿÿéòÏ&0°0²ÿÿ ÿçòÐ&ºPÿÿ ÿééÏ&ºµÿÿ ÿéóÏ&º¶ÿÿÿèñÐ&~4ÿÿ ÿéóÏ&º'Ëÿÿ ÿé÷Ï&ºÿÿ ÿéïÏ& /¶ÿÿ ÿèôÏ&,%”ÿÿÿéõÏ&¡0²ÿÿ ÿéíÏ&º0òÿÿ ÿéõÏ&3ºÿÿ ÿéòÏ&º0jÿÿ ÿéíÏ&º,ÇÿÿÿîòÐ&~5uÿÿ ÿéóÏ&º'ÿÿ ÿéòÏ&ººÿÿ ÿéôÏ&‰ÿÿ ÿèñÏ&º,&ÿÿ ÿéôÏ&º5vÿÿ ÿéïÐ&h&[5wÿÿ ÿèôÏ& Øÿÿ ÿæñÏ&5xÿÿÿèòÑ&.Ï5yÿÿÿèòÏ&0²0´ÿÿ ÿèôÏ&º)€ÿÿ ÿèõÏ&»ÿÿ ÿèðÏ&º5zÿÿ ÿéóÏ&'¦ÿÿ ÿéóÏ&º)ÿÿ ÿéòÏ&º(Êÿÿ ÿéõÏ&º ÿÿ ÿéòÏ&º5{ÿÿÿéóÏ&&0²5|ÿÿ ÿéíÑ&ºŸÿÿ ÿéöÑ&“ÿÿ ÿèóÏ&è&ÿÿ ÿéóÏ&Öÿÿ ÿèîÏ&ºÿÿ ÿèöÏ& Öÿÿ ÿéõÏ&Šºÿÿ ÿéóÏ&xºÿÿ ÿèôÈ&Ùçÿÿ ÿéóÐ&ãºÿÿ ÿèôÐ&>&=èÿÿ ÿèõÏ&º©ÿÿ ÿéóÏ&ºÿÿ ÿèôÏ&” ÿÿ ÿéìÏ&,Èhÿÿ ÿéòÏ& ÿÿÿéïÏ&,0²ÿÿ ÿéöÏ& –ÿÿ ÿéôÏ&Qÿÿ ÿéòÏ&ºÅÿÿÿéòÏ&¸0ÿÿ ÿéòÏ&{ºÿÿ ÿèïÏ&1ºÿÿ ÿéóÏ& ÿÿ ÿéòÏ&ºXÿÿ ÿèõÏ&5}ÿÿ ÿèïÏ&h&¦ ÿÿ ÿéóÏ&º¹ÿÿ ÿéôÐ&7ºÿÿ ÿéöÏ&$ÿÿÿéðÏ&¸ÿÿ ÿéõÏ& ºÿÿ ÿéõÐ&ºÿÿ ÿèôÏ&”ðÿÿ ÿèôÏ&Ø”ÿÿÿéóÏ&Ø&Ù0²ÿÿ ÿéóÏ&ºÿÿ ÿé÷Ï& Bÿÿ ÿé÷Ï&+ÿÿ ÿéõÑ&Žÿÿ ÿéóÏ&ÿÿ ÿèóÏ&Zèÿÿ ÿèóÏ&º0½ÿÿ ÿéõÐ&úòÿÿ ÿè÷Ï&)ÿÿ ÿèóÏ&è5ÿÿ ÿéõÐ&óÿÿ ÿéóÏ&ºÕÿÿÿèñÏ&Ä&&e0²ÿÿ ÿéîÏ& .ÿÿ ÿèôË&Ø5€ÿÿ ÿèôÍ&×Ùÿÿ ÿéõÏ&ºÿÿÿ ÿéòÏ&0ÕÿÿÿéìÏ&¸Òÿÿ ÿèòÐ&Öÿÿ ÿéóÏ& -ÿÿ ÿéóÏ&hÙÿÿ ÿéôÒ&ÿÿ ÿéñÏ&àÿÿ ÿèõÏ&¼ÿÿ ÿåðÏ&º£ÿÿ ÿéíÏ&ºéÿÿ ÿéôÏ&º,”ÿÿ ÿèöÏ& áÿÿ ÿéôÐ& Uÿÿ ÿèóÏ&Vÿÿ ÿèóÏ&è5ÿÿ ÿéóÑ&ºÔÿÿ ÿéöÏ&&`_ÿÿ ÿè÷Ï&º'Óÿÿ ÿéóÏ&,ɺÿÿ ÿéòÏ& 0@ÿÿÿéòÏ&¸©ÿÿ ÿéóÏ&º,Êÿÿ ÿéóÐ&º³ÿÿ ÿèñÏ&Žÿÿ ÿèíÏ& '©ÿÿ ÿéíÏ&?ÿÿ ÿéòÐ&*ÿÿÿèõÐ&¸Lÿÿ ÿéôÏ& %$ÿÿÿéòÐ&ç5‚ÿÿÿîòÐ&ç5ƒÿÿ ÿéòÏ&*Çÿÿ ÿéôÏ&º/ÿÿ ÿéùÏ&0ºÿÿ ÿèôÐ&•&(”ÿÿ ÿéóÏ&(5ÿÿ ÿéôÏ& %=ÿÿ ÿéõÐ& „ÿÿÿçñÏ&&e&0²5„ÿÿ ÿæðÏ&ºÎÿÿ ÿèôÏ&,Ì& 5…ÿÿ ÿèóÏ&è0fÿÿÿééÏ&¸ÿÿ ÿéöÐ&(lÿÿ ÿéñÏ&º&­¬ÿÿ ÿéóÏ&ºEÿÿ ÿéóÏ&ÿÿ ÿèöÏ& &K%Ãÿÿ ÿé÷Ñ&,Íÿÿ ÿéóÏ&*ºÿÿ ÿéóÏ& îÿÿ ÿèôÏ&ºéÿÿ ÿæ÷Ï&(¿ÿÿÿèñÏ&&&e5†ÿÿ ÿçõÏ& Øÿÿ ÿêõÐ&[&K5‡ÿÿÿéòÒ&ÊËÿÿ ÿèêÏ&ºÌÿÿ ÿéõÏ&º’ÿÿ ÿéêÐ&ºcÿÿ ÿèõÏ& /?ÿÿÿè÷Ï&0²5ˆÿÿ ÿèõÏ&b&c5‰ÿÿ ÿéóÏ& Šÿÿ ÿèôÏ&³&”5Šÿÿ ÿéóÏ&ºëÿÿÿéôÏ&¸õÿÿ ÿéöÏ&”ÿÿ ÿéóÏ&'ºÿÿ ÿçõÑ& ÿÿ ÿèñÏ&%Ϻÿÿ ÿèóÐ&ºŒÿÿ ÿéôÐ& .Kÿÿ ÿéîÏ&ßÿÿÿéôÐ&0²ÿÿ ÿèôÐ&º>ÿÿÿéõÏ&0²ÿÿ ÿéõÏ&b&c5‹ÿÿ ÿèóÏ&ºÓÿÿ ÿéóÏ&º%Òÿÿ ÿéõÏ&b&caÿÿ ÿéóÏ&+ÿÿ ÿéõÏ&)zºÿÿ ÿçöÏ&º5Œÿÿ ÿèôÏ& /0ÿÿ ÿéóÏ&  ÿÿ ÿéöÏ&5Žÿÿ ÿéóÏ&º(=ÿÿ ÿèïÏ&ºwÿÿ ÿèöÏ&»ºÿÿ ÿéóÏ&º+Nÿÿ ÿéôÏ&º(mÿÿ ÿéóÏ&.Mÿÿ ÿéòÏ&ºÿÿ ÿèôÐ&”ÿÿ ÿéóÐ&º Oÿÿ ÿé÷Ï& (;ÿÿ ÿéòÐ&%Ðÿÿ ÿéôÐ&'Úºÿÿ ÿéõÑ& ýÿÿ ÿèõÏ&2ÿÿÿéõÐ&,Î,Ïÿÿ ÿéíÏ& Éÿÿ ÿè÷Ð& 5‘ÿÿ ÿéîÏ& èÿÿ ÿéîÏ&ìÿÿ ÿèõÒ& ÿÿ ÿèôÏ&ÖØÿÿ ÿéïÏ&%­ÿÿ ÿé÷Ï&Kÿÿ ÿéóÏ&%Óÿÿ ÿéóÑ& !ÿÿ ÿçõÓ&)@ÿÿ ÿèôÐ&#f&è5’ÿÿÿçìÏ&/10²ÿÿ ÿèôÏ&ºÛÿÿ ÿèöÏ& <ÿÿ ÿéõÐ&º%ªÿÿ ÿéöÐ&~&€ÿÿ ÿéõÑ&º ÿÿÿéòÏ&£0²ÿÿ ÿèôÏ&'Ø ÿÿ ÿäôÏ&àÿÿ ÿéöÏ& ׺ÿÿÿèôÏ&0²5“ÿÿ ÿéõÏ& /ÿÿ ÿéóÏ&'îÿÿ ÿéõÏ& .3ÿÿ ÿèóË&è5”ÿÿ ÿéòÏ&º Yÿÿ ÿèðÐ& iÿÿ ÿèôÏ&k&Ø5•ÿÿ ÿçõÏ&b&c+Ìÿÿ ÿèõÏ&.N5–ÿÿ ÿéöÏ&"Hÿÿ ÿéóÑ&"fÿÿ ÿéóÏ&,Ðÿÿ ÿéóÏ&º&Çÿÿ ÿéëÏ&¾ºÿÿÿçðÏ&¸ÿÿ ÿéîÏ&ºþÿÿ ÿèòÏ&.ÿÿ ÿéôÏ&ºYÿÿ ÿéóÐ&ɺÿÿ ÿéòÏ& rÿÿ ÿéôÐ& Ÿÿÿ ÿéòÏ&#jÿÿ ÿéñÏ&¼ºÿÿ ÿéôÏ&'¯ºÿÿÿéöÏ&.Uÿÿ ÿéøÏ&ü ÿÿ ÿéðÐ&º# ÿÿ ÿèõÏ&,Ñ&h5—ÿÿ ÿéôÑ&9 ÿÿ ÿéîÏ&º±ÿÿ ÿéîÏ& ïÿÿ ÿéöÏ& '­ÿÿ ÿéôÐ&  (ÿÿ ÿèöÏ&%«ÿÿ ÿèôÐ&Ø 4ÿÿÿéëÏ&0²&à(ÿÿ ÿéíÏ& Ñÿÿ ÿçõÏ&¨ÿÿ ÿéêÏ&º5˜ÿÿ ÿéìÐ&h&¿5™ÿÿ ÿéùÏ& .Pÿÿ ÿèõÒ&.Oÿÿ ÿéóÐ&  ÿÿ ÿéñÏ& ,£ÿÿÿéòÏ&~&}0²ÿÿ ÿèóÏ&ˆºÿÿÿèñÏ&&e&0²5šÿÿ ÿéóÐ& "÷ÿÿ ÿèóÏ&#;ÿÿÿèöÏ&#&˜0²ÿÿÿéöÏ& Œ0²ÿÿ ÿéöÏ& #ÎÿÿÿéöÐ&ß0²ÿÿ ÿéöÐ&h&ç,êÿÿ ÿéóÏ& ÙÿÿÿéñÏ&#É0²ÿÿÿéòÏ&/&5›ÿÿ ÿéõÏ& "îÿÿ ÿéöÏ& "Yÿÿ ÿéòÏ&.Xÿÿ ÿéôÐ& "¤ÿÿ ÿéôÏ&"hÿÿ ÿèïÐ&º0¹ÿÿ ÿéíÏ&5ºÿÿ ÿéñÏ&º$[ÿÿ ÿéñÏ&'ܺÿÿ ÿçõÏ&YÿÿÿèïÏ& ±& 0²ÿÿ ÿèòÏ& !æÿÿ ÿéóÏ&!ÿÿ ÿéõÐ&/ ÿÿÿéõÏ&"z0²ÿÿ ÿéöÏ&",ÿÿ ÿéõÏ&: ÿÿÿéôÏ&#ž0²ÿÿ ÿéñÏ&º)‚ÿÿÿéîÏ&/¸0²ÿÿ ÿéôÏ&0{hÿÿ ÿèóÏ&è&Ú5œÿÿÿèõÏ&,Ñ&0²5ÿÿ ÿéõÏ&c&b5žÿÿ ÿéóÏ&.Yºÿÿ ÿéîÏ&h&KJÿÿ ÿéõÏ& :ÿÿ ÿèôÏ&5Ÿÿÿ ÿéðÑ& /ƒÿÿ ÿéóÏ& "µÿÿÿéõÑ&ä0²ÿÿÿéôÐ&&0²ÿÿ ÿéëÏ& þÿÿ ÿèòÏ& Ÿÿÿ ÿéòÏ&"žÿÿ ÿèóÐ& gºÿÿ ÿèôÏ&q&"å ÿÿ ÿèõÏ&º°ÿÿ ÿäöÏ&ºýÿÿ ÿéðÏ&’ÿÿÿéóÏ&#­+ÿÿ ÿèòÏ&)hÿÿ ÿèõÐ&º,Òÿÿ ÿéöÏ& 01ÿÿ ÿé÷Ï&"cÿÿ ÿéïÑ&!Áÿÿ ÿéñÏ& ÿÿ ÿèóÒ& /Òÿÿ ÿéòÐ&º<ÿÿÿèöÏ&á&%Ã0²ÿÿÿéóÏ&30²ÿÿ ÿéõÏ&#Iÿÿ ÿéõÑ&º"±ÿÿ ÿèôÏ& #´ÿÿ ÿèóÐ&è5 ÿÿ ÿéôÐ&#XºÿÿÿéñÏ&#­%×ÿÿ ÿéóÐ&Hÿÿ ÿéõÏ&hœÿÿÿèïÏ&+#5¡ÿÿÿéóÐ&0–0²ÿÿ ÿéöÏ& ÿÿ ÿéòÏ&º!¿ÿÿ ÿéíÐ& &Õ5¢ÿÿÿéõÏ&!”0²ÿÿ ÿéïÏ&h&,'5£ÿÿ ÿèìÏ&º5¤ÿÿ ÿéóÏ& -éÿÿ ÿéóÏ&º$Íÿÿ ÿéõÏ&$uÿÿ ÿéóÐ&/†ÿÿÿèñÑ&0—0²ÿÿ ÿéöÏ& Nÿÿ ÿèôÎ&ÿ5¥ÿÿ ÿéðÏ&#w ÿÿ ÿèôÑ&Ø5¦ÿÿ ÿéõÏ&#CÿÿÿéòÏ&,Ó0²ÿÿ ÿèõÏ& $nÿÿ ÿéðÑ&$•ÿÿ ÿè÷Ï& Ëÿÿ ÿèóÏ& áºÿÿ ÿéôÏ& 'ßÿÿÿéòÏ&'m0²ÿÿ ÿéôÏ& .Dÿÿ ÿéöÏ&º$Oÿÿ ÿèøÏ&¦5§ÿÿ ÿéòÏ&/4ÿÿ ÿéîÏ& $‘ÿÿ ÿèôÐ& $ñÿÿ ÿéñÏ&ÿÿÿéóÐ&/60²ÿÿ ÿéóÏ&$Ⱥÿÿ ÿéóÐ& &/â5¨ÿÿ ÿèôÏ&”& ÿÿÿéóÐ&L&M0²ÿÿ ÿéóÏ&$Cºÿÿ ÿé÷Ñ&0± ÿÿ ÿéõÏ&ºÿÿÿæôÑ&0²5©ÿÿ ÿéñÏ& %ÿÿ ÿéöÐ&Vÿÿ ÿèôÐ&$ÿÿ ÿéðÏ&º$­ÿÿ ÿéóÏ&$›ÿÿ ÿéîÏ&$Tÿÿ ÿéõÏ&)iºÿÿ ÿèôÐ&ºÿÿ ÿéóÏ&Mºÿÿ ÿéòÏ&L ÿÿ ÿéòÐ& % ÿÿÿéöÏ&5ªÿÿ ÿèóÑ&¸&ºèÿÿ ÿèóÏ&$“ÿÿÿéóÏ&#¬#­ÿÿ ÿéóÑ& '¶ÿÿÿéóÏ&$„0²ÿÿ ÿèôÑ&¿&ÀØÿÿ ÿèôÐ& ,Åÿÿ ÿéôÐ& 0oÿÿÿèõÐ& 0²ÿÿ ÿéïÏ&+$ ÿÿ ÿèõÐ&ÿÿ ÿéôÑ&Œÿÿ ÿéôÏ&1ÿÿ ÿçöÐ& 5«ÿÿ ÿéõÐ&5¬ÿÿ ÿèôÏ&(‚ºÿÿ ÿéóÐ&$0ÿÿ ÿéñÐ&º#Âÿÿ ÿéøÑ& $õÿÿ ÿèòÐ&/Mÿÿ ÿéóÏ& &.-ÿÿ ÿéîÏ&%ÛÿÿÿéõÏ&Þ&0²5­ÿÿ ÿéòÏ& /Sÿÿ ÿèñÓ&(ƒÿÿ ÿèôÏ&Ø5®ÿÿÿèñÑ&&e&%ç0²ÿÿÿéóÏ&5¯ÿÿ ÿéóÏ& +ÿÿÿçõÏ&Ï&Ðÿÿ ÿéõÒ&h~ÿÿ ÿçõÑ&xÿÿ ÿéöÏ&4&º5°ÿÿ ÿèôÏ&h&’>ÿÿ ÿèôÐ&ØjÿÿÿéõÐ&/U0²ÿÿ ÿéîÏ&qhÿÿ ÿèöÏ&Ø5±ÿÿÿéøÏ&5²ÿÿ ÿèòÑ& 5³ÿÿÿèøÏ&,c&0²5´ÿÿ ÿèöÏ&Ø5µÿÿ ÿéóÏ&¡ ÿÿ ÿéôÏ& &žÿÿ ÿéòÏ& $þÿÿ ÿéîÑ&º¹ÿÿ ÿé÷Ð& /Yÿÿ ÿèôÑ&Ø5¶ÿÿÿéöÏ&&  ÿÿ ÿéöÐ&Ehÿÿ ÿéðÐ&º*¸ÿÿÿèõÐ&0³5·ÿÿÿèöÑ&,y0²ÿÿÿèôÐ&'¸0²ÿÿ ÿéôÏ& 5¸ÿÿÿéõÏ&Þ&0²5¹ÿÿ ÿèôÏ&,Õÿÿ ÿèõÐ& 'xÿÿ ÿéôÏ&º'àÿÿÿé÷Ï&,&0²5ºÿÿÿéõÑ&#}0²ÿÿ ÿçôÐ& 1ÿÿ ÿéõÑ& #€ÿÿ ÿçöÐ& &##‚ÿÿ ÿèöÑ&ã5»ÿÿ ÿéóÏ& #ÿÿ ÿéóÏ& ¦ÿÿ ÿéóÏ&º†ÿÿ ÿé÷Ï&ºÿÿ ÿèôÐ&/Z ÿÿ ÿéôÏ&#~ ÿÿ ÿéôÑ& nÿÿ ÿéòÏ& Þÿÿ ÿéöÏ&Nºÿÿ ÿéîÏ&ºÿÿ ÿç÷Ð&$Ýÿÿ ÿéõÏ&5¼ÿÿÿéôÐ&0²5½ÿÿ ÿéôÐ&h&(5¾ÿÿ ÿèôÏ&.ZÿÿÿéóÐ&ë0²ÿÿ ÿçòÏ& %âÿÿÿéõÏ&%Ú0²ÿÿ ÿèúÏ&&¼5¿ÿÿÿéñÐ&'¼0²ÿÿ ÿéïÏ&º4ÿÿÿæïÏ&,(0²ÿÿ ÿéôÏ& /[ÿÿ ÿéöÏ&F ÿÿ ÿéõÏ& %èÿÿÿéõÏ&,×0²ÿÿ ÿéóÐ& &¤ÿÿÿéóÐ&P&/â0²ÿÿÿéóÏ&†0²ÿÿ ÿçôÏ&hÄÿÿÿéôÏ&0²5Àÿÿ ÿéóÒ& %æÿÿÿé÷Ð&&%5Áÿÿ ÿéòÏ&ºÿÿ ÿéõÐ&h5Âÿÿ ÿéóÏ& 5Ãÿÿ ÿèõÐ&h&5ÄÿÿÿçöÏ&/^0²ÿÿÿèôÏ&<0²ÿÿ ÿéôÏ& %'ÿÿÿèõÏ&'}0²ÿÿÿéîÏ&0b&0²5Æÿÿ ÿéòÏ&ºüÿÿ ÿéóÐ&'âºÿÿ ÿéõÏ&á&àßÿÿ ÿéôÏ&'ãÿÿÿèñÏ&70²ÿÿ ÿèðÏ& .\ÿÿÿèõÑ&&%ç0²ÿÿ ÿéïÏ&.[ÿÿ ÿéôÐ& ,Øÿÿ ÿçìÏ& %êÿÿÿéöÏ&%0²ÿÿÿéòÏ&%à0²ÿÿ ÿèõÏ& Ôÿÿ ÿéóÏ&'ÿÿ ÿéóÐ&º ÿÿÿéõÑ&¼0²ÿÿÿéòÒ&0²5Çÿÿ ÿéôÐ& ÿÿÿéõÑ&&,Ù ÿÿÿèöÐ&Ø5Èÿÿ ÿèõÐ&&B5Éÿÿ ÿéóÏ&´ºÿÿ ÿéíÑ& '‚ÿÿ ÿèöÐ&'ÿÿ ÿéóÏ& 5Êÿÿ ÿèõÐ& ,Úÿÿ ÿå÷Ï& 5Ëÿÿ ÿéóÐ&/_ ÿÿÿèïÏ&(I0²ÿÿÿéòÏ&/a0²ÿÿ ÿéõÏ&šÿÿ ÿéöÏ&h&!.]ÿÿ ÿé÷Ð&W&h5ÌÿÿÿèîÏ&0²5ÍÿÿÿèôÐ&,)0²ÿÿÿé÷Ï&"ð&"ï0²ÿÿ ÿéøÏ&¡ÿÿ ÿéðÏ&(„ ÿÿ ÿéõÓ& Sÿÿ ÿé÷Ð& 'Åÿÿ ÿèõÐ&z&&B5Îÿÿ ÿèõÑ&h&5Ïÿÿ ÿéôÏ& (…ÿÿÿéðÏ&,Û0²ÿÿ ÿéóÏ& 5Ðÿÿ ÿéòÏ& .^ÿÿ ÿé÷Ñ&.£ÿÿÿéõÏ&._0²ÿÿÿéòÐ&0¦0²ÿÿ ÿéðÏ& 0µÿÿÿçúÏ&#ß5Ñÿÿ ÿéôÏ&h&)(ÿÿÿéðÏ&'0²ÿÿ ÿèôÍ&Ø&&5ÒÿÿÿéõÏ&Þ&!¦0²ÿÿ ÿèóÑ&è.ÿÿÿéõÏ&.0²ÿÿÿéõÏ&&Þ5ÓÿÿÿéõÔ&'‹0²ÿÿ ÿéóÏ&(‡hÿÿ ÿçóÏ&(†ºÿÿ ÿèôÒ&Ø5Ôÿÿ ÿéôÏ&Øÿÿ ÿéöÑ&&Ä ÿÿ ÿéõÏ&'ç ÿÿÿéõÐ&!œ&Þ0²ÿÿÿèôÐ&%ñ0²ÿÿ ÿèïÏ& &h5Õÿÿ ÿéõÑ&5Öÿÿ ÿèôÐ&(ˆÿÿ ÿé÷Ð& &ì5×ÿÿ ÿéõÏ& .`ÿÿÿéñÏ&F0²ÿÿÿé÷Ð&&EFÿÿÿéóÏ&%õ0²ÿÿ ÿéóÏ&(‰hÿÿ ÿçñÏ& ,+ÿÿ ÿéôÏ&'ÿÿÿéöÑ&0²5Øÿÿ ÿé÷Ï& üÿÿÿéôÒ&Q0²ÿÿÿèôÑ&5Ùÿÿ ÿéðÑ&h,*ÿÿÿéôÐ&P0²ÿÿ ÿéñÏ&0"ÿÿ ÿçôÐ&5ÚÿÿÿéóÏ&Ê0²ÿÿÿéòÏ&0²5ÛÿÿÿçøÒ&Õ0²ÿÿ ÿéóÏ&—&“hÿÿÿéöÐ&&ÿÿ ÿçóÑ& 5Üÿÿ ÿçõÏ&þÿÿ ÿèòÏ&%[ÿÿÿéõÏ&Þ&0²5ÝÿÿÿêñË&(%kÿÿ ÿéóÏ&h%hÿÿÿéöÒ&)ø0²ÿÿ ÿéóÏ&5ÞÿÿÿéîÏ&0b&0²5ßÿÿ ÿéöÏ& 5àÿÿ ÿéôÐ&ã&â5áÿÿ ÿçôÏ&  ÿÿÿéøÐ&–&"ñÿÿÿèóÐ&@ÿÿ ÿéõÐ& 5âÿÿ ÿèñÏ& 5ãÿÿÿçóÏ&&%…÷ÿÿÿéóÐ&0²5äÿÿÿéôÏ&0²5åÿÿ ÿéôÏ&h&25æÿÿÿéøÑ&û0²ÿÿ ÿè÷Ï&hRÿÿ ÿèôÑ&Ø5çÿÿ ÿéûÏ&h&,Ý5èÿÿ ÿéóÏ& ,ÜÿÿÿéøÐ&.b&5éÿÿ ÿèòÐ&.a ÿÿ ÿêôÐ&(5êÿÿÿéöÐ&H0²ÿÿÿèòÐ&)00²ÿÿÿé÷Ï&0²5ëÿÿ ÿèôÏ& 5ìÿÿ ÿéñÏ&h5íÿÿ ÿèòÏ&h,}ÿÿ ÿéõÏ&h+,ÿÿ ÿéñÑ& %÷ÿÿ ÿéóÏ& 5îÿÿ ÿèòÑ& 7ÿÿ ÿéíÏ&£hÿÿÿéùÐ&5ïÿÿÿéîÏ&0²5ðÿÿ ÿçòÏ&h5ñÿÿ ÿéóÏ& Oÿÿ ÿéòÏ& (ŠÿÿÿéöÏ&5òÿÿ ÿéóÏ&h.cÿÿ ÿéóÏ& *lÿÿÿçõÐ&‡0²ÿÿÿêóÒ&(-ñÿÿÿçñÑ&%ø0²ÿÿ ÿæóÐ&h.=ÿÿÿéóÐ&-ÿÿÿéðÏ&0²0åÿÿÿçôÏ&5óÿÿÿéôÑ&0²5ôÿÿÿéòÏ&0²5öÿÿÿéûÐ&&,Ý5÷ÿÿÿéõÑ&±°ÿÿ ÿéõÑ&²ÿÿ ÿéöÏ&´³ÿÿ ÿéöÏ&³µÿÿÿéõÑ&T²ÿÿÿéõÏ&·ƒÿÿ ÿéõÏ&¸áÿÿ ÿéõÐ&¹ƒÿÿÿéõÏ&báÿÿ ÿéõÏ&eƒÿÿ ÿéõÑ&äáÿÿ ÿéõÏ&áxÿÿÿéöÐ&%5øÿÿÿéõÏ&ƒ ÿÿÿéõÏ& ¯áÿÿ ÿéõÑ&,Þáÿÿ ÿéõÐ&(‹áÿÿÿéõÏ&(Vàÿÿ ÿèõÏ&±ƒÿÿÿçõÏ&áâÿÿ ÿèõÏ&à Ûÿÿ ÿéõÑ&¶ÿÿ ÿéõÏ&áºÿÿÿéõÏ&èƒÿÿ ÿéõÏ&á]ÿÿ ÿéõÏ&à‰ÿÿÿéõÏ&á!øÿÿ ÿéõÏ&à –ÿÿÿéöÑ&³!Åÿÿ ÿèõÏ&á(Œÿÿ ÿéöÏ&%#ŠÿÿÿéõÐ&,íàÿÿÿèõÏ&á% ÿÿ ÿèöÏ&$â%ÿÿ ÿéõÏ&$áÿÿÿçõÏ&à#ÿÿ ÿéõÏ&€áÿÿÿéõÏ&™áÿÿ ÿçöÑ&%,ßÿÿÿéôÐ&&Æ'=ÿÿÿéöÏ&%dÿÿ ÿèõÐ&à%‰ÿÿ ÿéõÏ&à(ÿÿ ÿéõÏ&á5ùÿÿÿéõÑ&à0ãÿÿ ÿéöÏ&%(Žÿÿ ÿéõÏ&á Íÿÿ ÿéöÏ&%,àÿÿ ÿèöÏ&%5úÿÿÿéöÏ&S%ÿÿ ÿèõÏ&(ÿÿ ÿéóÐ&*5ÿÿÿ ÿïóÄ&}|ÿÿ ÿéóÐ&(NÿÿÿéòÏ&,á6ÿÿ ÿçõÏ&,â6ÿÿ ÿêõÏ&,â6ÿÿ ÿèóÇ&s6ÿÿÿèôÌ& w0ÿÿ ÿèôÊ&A6ÿÿÿéôÏ&E6ÿÿ ÿåóÊ& ‘ÿÿ ÿåðÏ&g‘ÿÿ ÿåóÐ&‘(°ÿÿ ÿåõË&‘`ÿÿÿçõÇ&UVÿÿ ÿåõÆ&‘ÿÿÿêöÏ&%!1ÿÿÿéöÐ&³1ÿÿ ÿåòÐ&‘0öÿÿ ÿåòÏ&&‘ÿÿ ÿåôÏ&‘ÁÿÿÿêíÑ&Ÿ1ÿÿ ÿåöÏ&‘—ÿÿ ÿåõÏ&‘»ÿÿÿêòÄ&©1ÿÿ ÿåóÐ&‘³ÿÿÿèôÏ& Ù1ÿÿ ÿåòÏ&‘-±ÿÿ ÿåõÏ&(‘ÿÿ ÿåöÈ&‘ÿÿ ÿåðÏ&‘+Uÿÿ ÿå÷Ï&‘Lÿÿ ÿåõÐ&‘#¡ÿÿ ÿåòÎ&‘(‘ÿÿÿêöÏ&'ñ1ÿÿÿçõÓ&)@1ÿÿ ÿåôÏ&'ï‘ÿÿ ÿåôÎ&‘ÔÿÿÿêòÃ&(’1ÿÿ ÿåõÈ&‘$ÿÿ ÿåôÅ&‘Éÿÿ ÿåóÏ&$‘ÿÿÿèòÐ&/M1ÿÿÿéóÏ&01ÿÿÿêóÏ&#_1ÿÿÿéóÑ&(“1ÿÿÿêïÏ&.„1ÿÿ ÿåóÐ&‘ÿÿÿè÷Ï&?&61ÿÿÿêïÏ&41ÿÿÿéòÈ&1ÿÿÿêôÈ& 1ÿÿÿé÷Ñ&&Å1ÿÿÿçóÑ&¡1ÿÿ ÿåõÐ&‘# ÿÿÿéöÏ&(”1ÿÿÿêõÏ&+,1ÿÿÿèõÇ&·Tÿÿ ÿèõÏ&# Tÿÿ ÿèõÍ&(•Uÿÿ ÿèõÏ&T6 ÿÿ ÿèõÐ&T6 ÿÿ ÿèôÏ&(–ÿÿÿ ÿèõÏ&wTÿÿ ÿèôÎ&/gÿÿÿ ÿèöÇ&˜6 ÿÿ ÿèõÏ&(—TÿÿÿèõÏ&(˜TÿÿÿèôÐ&ÿ6 ÿÿÿèöÏ&% ˜ÿÿÿèôÏ&ÿ6 ÿÿ ÿèõÐ&—TÿÿÿèöÇ&™˜ÿÿ ÿèõÐ&T6ÿÿ ÿè÷Ï&&&ÿÿ ÿíôÌ&/h6ÿÿ ÿèóÏ&+ 6ÿÿ ÿéóÏ&ÓÔÿÿ ÿçöÐ&‡(™ÿÿÿéðÐ&ÿÿÿé÷Î&21ÿÿÿì÷Í&pÿÿ&ÿëïÆ&3ÿÿÿçñÐ&45ÿÿÿê÷Ï&67ÿÿ ÿìòÑ&ÍÐÿÿÿìòÐ&ÐÎÿÿ ÿíôÐ&6ÿÿ ÿíôÐ&Ìÿÿÿê÷Ê&F6ÿÿ ÿéùÎ&ïðÿÿ ÿíõÐ&÷Yÿÿ ÿíôÐ&äYÿÿ ÿíôÐ&×YÿÿÿìîÓ&,ã6ÿÿ ÿíôÐ&Y6ÿÿ ÿíôÔ&YZÿÿ ÿíôÐ&Y6ÿÿ ÿèõÏ& Úÿÿ ÿèóÎ&(š6ÿÿ ÿèõÐ& - ,ÿÿÿéõÏ& ‡ÿÿ ÿíõÐ&Y6ÿÿ ÿéõÏ&#ŠÿÿÿéõÏ&"ŒÿÿÿëõÏ&$· -ÿÿÿì÷Ñ&(›6ÿÿ ÿéõÏ&$Uÿÿ ÿçõÑ&,äÿÿ ÿçõÏ&(Äÿÿÿì÷Ñ&(›6ÿÿ ÿéóÐ&&þÿÿ ÿéõÐ&,å -ÿÿ ÿéõÏ&(œÿÿÿêóÐ&[( ÿÿ ÿíôÐ&Y6ÿÿ ÿíôÓ&Y6ÿÿ ÿìõÑ&€ -ÿÿ ÿèõÏ&(ÿÿÿéõÏ&(žÿÿ ÿíôÓ&Y6ÿÿ ÿéõÏ&(Ÿÿÿ ÿéóË&(¡'ìÿÿÿçöÐ&Ø6"ÿÿÿæõÒ&[ÖÿÿÿçöÐ&ØXÿÿÿçöÐ&Ø6#ÿÿÿçöÐ&Ø6$ÿÿÿèõÒ&[ÕÿÿÿéõÒ&[6%ÿÿÿéõÒ&y[ÿÿÿèõÑ&\9ÿÿÿéõÒ&[6&ÿÿÿèõÑ&\6'ÿÿÿéõÒ&[6(ÿÿÿèõÒ&[[ÿÿÿéõÒ&[6)ÿÿÿèõÑ&\6*ÿÿÿæõÒ&[žÿÿÿéõÒ&[ºÿÿÿçõÑ&\6+ÿÿÿéõÒ&[6,ÿÿÿèõÑ&\6-ÿÿÿèõÑ&ª\ÿÿÿéõÒ&#n[ÿÿÿäõÒ&[äÿÿÿèõÑ&\6.ÿÿÿçóÐ&(¢6/ÿÿ ÿèõÑ&\Íÿÿ ÿèõÑ&\$Fÿÿ ÿèëÏ&Ú ÿÿ ÿéôÐ&žáÿÿ ÿéùÎ&  ÿÿ ÿéøÏ& ²áÿÿ ÿêóÍ&ÖÐÿÿ ÿçóÏ& Vÿÿ ÿèöÎ& ïÿÿ ÿéôÐ&+Ûžÿÿ ÿêîÍ&Öõÿÿ ÿéóÍ& ²%Œÿÿ ÿèõÍ& ²&(£65ÿÿ ÿéèÏ& (ÿÿ ÿèçÐ& (¤ÿÿ ÿèøÏ& (ÿÿ ÿèôÍ&Öƒÿÿ ÿèõÏ& (¥ÿÿ ÿèíÏ& (¦ÿÿ ÿéòÐ& ²(§ÿÿ ÿéõÑ&% ÿÿ ÿéêÏ& ²ãÿÿ ÿéóÎ& ¨ÿÿ ÿéðÎ& ¬ÿÿ ÿéòÎ& /jÿÿ ÿêöÍ&Ö66ÿÿ ÿéõÎ& («ÿÿ ÿèóÍ& ²(ªÿÿ ÿêôÍ&ÖÉÿÿ ÿéôÏ& ²(©ÿÿ ÿéðÍ& ²(¨ÿÿ ÿéöÏ& ²pÿÿ ÿéíÍ& ²%2ÿÿ ÿèõÏ& ²µÿÿ ÿèôÎ& ÿÿ ÿéôÎ& +Üÿÿ ÿéèÍ& ²(¬ÿÿ ÿéíÍ& ²67ÿÿ ÿéôÎ& wÿÿ ÿéòÏ&D ÿÿ ÿéìÏ& ²(­ÿÿ ÿèôÏ& ²(®ÿÿ ÿéõÏ& ²–ÿÿ ÿéöÏ& Cÿÿ ÿèóÎ& …ÿÿ ÿéóÎ& Áÿÿ ÿéìÐ& ÿÿ ÿéõÎ& ÿÿ ÿéòÏ&$—Öÿÿ ÿéêÍ& ²)ÿÿ ÿéöÏ& ²&ôÿÿ ÿéóÎ&® ÿÿ ÿéõÐ& ²(¯ÿÿ ÿéôÏ& ²Üÿÿ ÿèïÐ&*,çÿÿ ÿéøÐ& ²%ºÿÿ ÿéóÍ& ²aÿÿ ÿéòÍ& ²(&ÿÿ ÿçõÎ& `ÿÿ ÿéòÍ& ê ²ÿÿ ÿéòÏ&b ²ÿÿ ÿèõÏ& Ìÿÿ ÿéôÏ& ²Áÿÿ ÿéñÍ& ²-âÿÿ ÿéòÏ& mÿÿ ÿêòÏ&×Öÿÿ ÿêõÍ&Ö&óÿÿ ÿéñÏ&Ö&qúÿÿ ÿéìÏ& ²&Ç69ÿÿ ÿé÷Ï&Ö%¾ÿÿ ÿèóÐ& (±ÿÿÿéõÏ&(áòÿÿ ÿêðÍ&Ö(²ÿÿ ÿéðÏ& {ÿÿ ÿéëÏ&Öºÿÿ ÿéõÎ& 8ÿÿ ÿèõÏ& (³ÿÿ ÿéóÎ& (´ÿÿ ÿçòÏ&Ö^ÿÿ ÿèñÏ&(µ ÿÿ ÿèõÐ&(¶,æÿÿ ÿéëÐ& Óÿÿ ÿèôÎ& (·ÿÿ ÿèöÎ& &ÿÿ ÿéðÏ&Ö(¹ÿÿ ÿêñÍ&Ö(ºÿÿ ÿèñÍ&/n ²ÿÿ ÿéõÏ& ²Eÿÿ ÿéóÏ& ²&ÿÿ ÿéëÏ& ²(âÿÿ ÿéêÏ& (»ÿÿ ÿéôÏ&Ö(ãÿÿ ÿéõÏ& ²¤ÿÿ ÿéõÏ&¥ ²ÿÿ ÿéòÎ& Ýÿÿ ÿéñÍ&ÖÃÿÿ ÿêðÏ&Ö£ÿÿ ÿêôÐ&Ö,èÿÿ ÿéòÍ&Ö}ÿÿ ÿèöÐ& Âÿÿ ÿèòÍ& ²*Eÿÿ ÿçòÐ& ²(äÿÿ ÿéòÏ& ²8ÿÿ ÿééÏ& ²(¸ÿÿ ÿéèÍ& ²& ÿÿ ÿéòÐ& ²iÿÿ ÿéêÍ& ²0êÿÿ ÿéòÏ& ²¡ÿÿ ÿéóÏ& bÿÿ ÿéóÎ& +Çÿÿ ÿéêÍ& ²‰ÿÿ ÿé÷Í& ²òÿÿ ÿéçÍ& ²(åÿÿ ÿéëÎ& ¢ÿÿ ÿéóÍ& ²_ÿÿ ÿééÍ&/oÖÿÿ ÿèñÏ& (,ÿÿ ÿéìÏ& ²&¿6:ÿÿ ÿéçÑ& &ÿÿ ÿéóÍ& ²& ÿÿ ÿèõÏ&Ö–ÿÿ ÿéæÏ& ²(æÿÿ ÿéïÏ&Ö&  ñÿÿ ÿéïÏ& ²& ò6;ÿÿ ÿçóÏ&Ö(Æÿÿ ÿéòÍ& ²&õ*Ëÿÿ ÿéóÎ&Öÿÿ ÿéòÏ& ²*eÿÿ ÿéîÏ& ²/¼ÿÿ ÿéêÍ& ²(Åÿÿ ÿéóÍ& ²dÿÿ ÿéòÍ& ²Üÿÿ ÿçóÐ&Ö.dÿÿ ÿèôÏ& ”ÿÿ ÿçóÎ& '£ÿÿ ÿéòÐ& ²¤ÿÿ ÿèõÏ& ²*dÿÿ ÿéôÐ& ?ÿÿ ÿèôÏ& ÿÿ ÿéóÐ& gÿÿ ÿçñÍ& ²Éÿÿ ÿçóÑ&Ö(çÿÿ ÿéóÏ& ² Âÿÿ ÿéòÎ& %Eÿÿ ÿéäÍ& ²(Çÿÿ ÿéóÏ& Ôÿÿ ÿéóÐ&Ö&›šÿÿ ÿéóÏ&Ö&qÿÿ ÿèóÏ& ²ÿÿ ÿéõÍ& ²cÿÿ ÿçöÏ& ²`ÿÿ ÿèîÏ& (Èÿÿ ÿéõÐ& ´ ²ÿÿ ÿèòÄ&+Èÿÿ ÿèóÍ&(èÿÿ ÿéóÏ& ²(éÿÿ ÿéòÏ& (Éÿÿ ÿé÷Í& ²®ÿÿ ÿèöÏ& ² ÿÿ ÿçìÍ& ²6<ÿÿ ÿéóÏ&Ö/rÿÿ ÿéðÍ& ²*Õÿÿ ÿéõÎ& %"ÿÿ ÿçõÐ&( ²ÿÿ ÿéóÏ& ²0Ôÿÿ ÿèòÏ&A ²ÿÿ ÿéêÍ&#Öÿÿ ÿéëÍ&Öcÿÿ ÿéñÐ& ²ÿÿ ÿéóÍ& ²ÿÿ ÿéôÏ& ²Rÿÿ ÿéòÏ& ²*fÿÿ ÿèìÏ&&¥.ôÿÿ ÿéóÍ& ²Îÿÿ ÿééÐ& ²*Fÿÿ ÿéïÍ&ÖÃÿÿ ÿéóÎ& ²&ìíÿÿ ÿéòÐ& ²&MQÿÿ ÿèôË&Ñ.ôÿÿ ÿéóÍ& ²Öÿÿ ÿéóÏ&÷ ²ÿÿ ÿéóÏ& ²ÿÿ ÿéöÏ& ”ÿÿ ÿéóÐ&yÖÿÿ ÿéõÐ& ²aÿÿ ÿèôÏ&Ö0¼ÿÿ ÿéêÍ& ²©ÿÿ ÿè÷Ï& ²5ÿÿ ÿéñÐ& ²Øÿÿ ÿéóÏ&M&K ²ÿÿ ÿéöÐ& ²Ãÿÿ ÿéòÏ&§Öÿÿ ÿèõÍ& ²¨ÿÿ ÿçôÐ&Ö'ÿÿ ÿèïÍ& ²Åÿÿ ÿéôÏ& ²Âÿÿ ÿéøÍ& ²›ÿÿ ÿéòÍ& ²ÿÿ ÿé÷Í&¥ ²ÿÿ ÿèòÏ&&ÿÿ ÿêðÍ&Ö*Gÿÿ ÿéðÍ& ²*Hÿÿ ÿèèÏ& ²vÿÿ ÿèìÍ&.ô0Iÿÿ ÿéóÏ& ²äÿÿ ÿéìÑ& ²¡ÿÿ ÿéíÏ& ²wÿÿ ÿéóÍ& ²)ÿÿ ÿéñÏ& ²ìÿÿ ÿéòÍ& ²×ÿÿ ÿéèÏ& ¶ÿÿ ÿéõÏ& ² ·ÿÿ ÿêóÏ&Ö€ÿÿ ÿéôÑ& ²Üÿÿ ÿéõÍ& ²öÿÿ ÿèõÏ& ²Ñÿÿ ÿéíÏ&­ ²ÿÿ ÿèôÐ& ²oÿÿ ÿèòÐ& ²Ùÿÿ ÿæöÏ&p ²ÿÿ ÿéóÑ& ² ÿÿ ÿèòÏ& ²*IÿÿÿéñÏ&&ÿÿ ÿåôÑ& ²ÿÿ ÿèíÏ&¡ ÿÿ ÿèôÏ& ²’ÿÿ ÿèìÏ&&&$.ôÿÿ ÿèóÏ& ²Wÿÿ ÿçóÐ& ²&#ÿÿ ÿéìÐ&& ²³ÿÿ ÿéóÍ& ²²ÿÿ ÿéóÏ&°&¯Öÿÿ ÿçöÏ& ²Õÿÿ ÿèòÍ&± ²ÿÿ ÿèóÍ& ²Ôÿÿ ÿèóÏ&}&.ô6=ÿÿ ÿèôÑ&X ²ÿÿ ÿéõÍ&+Í ²ÿÿ ÿéöÍ& ²&,ÿÿ ÿèôÏ& ²&æ6>ÿÿ ÿéñÐ& ²&4ÿÿ ÿéôÏ&  ÿÿ ÿéòÍ& ²öÿÿ ÿéõÏ& ²‘ÿÿ ÿéïÐ&/v ²ÿÿ ÿéôÎ& &uÿÿ ÿêðÑ&Ö*Jÿÿ ÿêòÍ&Ö*Kÿÿ ÿéöÐ& ²&š6?ÿÿ ÿéóÍ& ²&:ÿÿ ÿèöÑ& &/ÿÿ ÿéóÍ& ²¬ÿÿ ÿè÷Í& ²"äÿÿ ÿéòÐ& ²*Lÿÿ ÿéóÎ& Šÿÿ ÿéëÐ& ²&ù›ÿÿ ÿé÷Í& ²Uÿÿ ÿé÷Í& ²&n6@ÿÿ ÿéñÏ& ²6Aÿÿ ÿéõÐ& ² ±ÿÿ ÿéóÍ& ²&)ÿÿ ÿçõÏ& ² Öÿÿ ÿèîÏ&N&l.ôÿÿ ÿéìÍ&%; ²ÿÿ ÿéóÐ&«Öÿÿ ÿèñÍ& ²ÿÿ ÿéóÏ& ².eÿÿ ÿæñÎ& êÿÿ ÿéñÎ& ñÿÿ ÿéóÐ& ²êÿÿ ÿéëÍ&à ²ÿÿ ÿéõÐ& ²&5ÿÿ ÿéõÎ& Ùÿÿ ÿèòË&4&.ô6Bÿÿ ÿéòÍ& ²«ÿÿ ÿéçÐ& ²&0ÿÿ ÿéïÎ& ²*Mÿÿ ÿéòÍ&ç&è ²ÿÿ ÿéôÐ&ê& ²šÿÿ ÿéëÍ& ²&1ÿÿ ÿéõÎ&Öéÿÿ ÿèõË&.ôÿÿ ÿéøÏ& ¹ ²ÿÿ ÿèôÏ& ²ëÿÿ ÿéôÍ& ²ìÿÿ ÿééÍ&Ö*Nÿÿ ÿéõÏ& ²/=ÿÿ ÿéïÏ& ²&8ÿÿ ÿêóÏ&Ö&Mjÿÿ ÿéôÐ& ²€ÿÿ ÿèôÐ& ²*˜ÿÿ ÿéòÏ& ²&6Cÿÿ ÿéòÏ& ²)ÿÿ ÿéóÏ&.f ²ÿÿ ÿçîÐ& ².gÿÿ ÿéöÏ&ù ÿÿ ÿèôÐ&Ü&+É.ôÿÿ ÿèìÍ& ²6Dÿÿ ÿèîÍ& ².hÿÿ ÿéòÐ& ²*Oÿÿ ÿéóÐ&Î&Ï ²ÿÿ ÿèìÎ&×& ²Öÿÿ ÿèìÎ&Ø& ²Öÿÿ ÿèöÍ&,ú&.i.ôÿÿ ÿêõÍ&Ö&( 6Eÿÿ ÿéòÐ&Ö*jÿÿ ÿèéÐ&Öjÿÿ ÿéïÏ&ÖÃÿÿ ÿèëÏ&à&.ô6Fÿÿ ÿéôÍ& ²(êÿÿ ÿèõÑ&e.ôÿÿ ÿêóÐ&¯Öÿÿ ÿéöÏ& ²(ëÿÿ ÿéîÍ& ²(ìÿÿ ÿè÷Ñ&ÖÏÿÿ ÿéëÍ& ²îÿÿ ÿéñÍ& ²­ÿÿ ÿèõÑ& ²&ÿÿ ÿêôÏ&Ö|ÿÿ ÿéòÎ&(í ²ÿÿ ÿé÷Ï& ²/õÿÿ ÿêøÍ&Öüÿÿ ÿéóÑ&ÖËÿÿ ÿéóÍ& ²þÿÿ ÿéõÐ& Ê ²ÿÿ ÿêîÎ&Ö0/ÿÿ ÿèóÏ&'—&™.ôÿÿ ÿèôÑ& ²00ÿÿ ÿèñÏ&Ö&ƒÿÿ ÿéïÍ&(@Öÿÿ ÿéõÍ& ².ÿÿ ÿééÎ& ²(îÿÿ ÿèöÏ&.ì.ôÿÿ ÿèëÐ& ²ÿÿ ÿéëÍ& ²&„ÿÿ ÿçôÍ& ²6Gÿÿ ÿééÍ&±Öÿÿ ÿèöÎ&.T.ôÿÿ ÿèõÏ&.ôÿÿ ÿèòÏ&Ö/yÿÿ ÿêôÐ&.lÖÿÿ ÿêñÎ&*PÖÿÿ ÿéòÏ& ²³ÿÿ ÿéíÏ& ²ÿÿÿ ÿèëÏ&(ï.ôÿÿ ÿéóÑ&ÖQÿÿ ÿéõÏ& ²(ðÿÿ ÿèëÏ&à&.ô6Hÿÿ ÿèîÍ& ²Èÿÿ ÿèóË&.k&.ô6Iÿÿ ÿèõÏ&.ó.ôÿÿ ÿçóÏ&© ²ÿÿ ÿéðÍ& ²6Jÿÿ ÿèóÏ& ²Üÿÿ ÿèïÏ& ²?ÿÿ ÿéöÏ& ²®ÿÿ ÿèõÐ&»&.ô6Kÿÿ ÿéóÏ&(ñÖÿÿ ÿêóÏ&Ö™ÿÿ ÿéôÑ& ²Èÿÿ ÿèóÏ&{ ²ÿÿ ÿéøÏ&„Öÿÿ ÿéñÎ& ².jÿÿ ÿéïÏ& ²°ÿÿ ÿèòÏ&&.ô6Lÿÿ ÿéôÐ& ²—ÿÿ ÿéòÐ&Ö(òÿÿ ÿêõÑ&Ö ÿÿ ÿéòÍ&Ö.Lÿÿ ÿèòÏ&¯ ²ÿÿ ÿéóÑ&[ ²ÿÿ ÿêòÏ&Ö&è6Mÿÿ ÿçëË&/z.ôÿÿ ÿéóÏ& ²&ÿÿ ÿêõÍ&Ö&†ÿÿ ÿéóÏ& ²(óÿÿ ÿéöÏ& ²*iÿÿ ÿèôÒ&_ ²ÿÿ ÿèõË& .ôÿÿÿéõÐ&(ô6Nÿÿ ÿêðÎ&Ö6Oÿÿ ÿéôÏ&Ö Eÿÿ ÿéòÏ& ²&‰ÿÿ ÿéóÏ&Ö²ÿÿ ÿéëÍ&Ï ²ÿÿ ÿéñÐ&.mÖÿÿ ÿèôÎ&Ö6Pÿÿ ÿèíË&.ô6Qÿÿ ÿéôÐ& ²6Rÿÿ ÿéõÍ&Ö(õÿÿ ÿèïÏ&Ö(÷ÿÿ ÿèöÏ& ²(öÿÿ ÿèòÏ& 6.ôÿÿ ÿêòÍ&.oÖÿÿ ÿéóÍ& ²6Sÿÿ ÿéñÏ& ².nÿÿ ÿéìÏ& ²&Úÿÿ ÿèïÐ& ²6Tÿÿ ÿèøÐ& ² .ÿÿ ÿéõÏ& ²"¯ÿÿ ÿéòÏ& ²/ÿÿ ÿèõÐ&!.ôÿÿ ÿéóÍ& ²"Bÿÿ ÿéõÏ& ²#„ÿÿ ÿéðÑ& ²!µÿÿ ÿèóÐ&•&è6Uÿÿ ÿèôÏ& ²$ÿÿ ÿéõÍ& ²#‹ÿÿ ÿéöÏ& ²"iÿÿ ÿèïÏ&*Š&.ô6Vÿÿ ÿèòË&#Û.ôÿÿ ÿéìÍ&q ²ÿÿ ÿéðÑ& ² ÿÿ ÿèîÍ& ²#?ÿÿ ÿéóÍ& ²1ÿÿ ÿèõÎ& ²@ÿÿ ÿéìÏ&& ²!ÿÿ ÿèèÍ&Ö"3ÿÿ ÿêõÏ&Ö6Wÿÿ ÿèóÏ&!z.ôÿÿ ÿèóÏ& ²!íÿÿ ÿéôÑ&Å& ²Æÿÿ ÿêòÏ&Ö"Ÿÿÿ ÿèôË&+Ê&+Ë.ôÿÿ ÿéñÎ& "Dÿÿ ÿèòÏ&(ø.ôÿÿ ÿèôÏ& ²!Þÿÿ ÿèòÎ&»&¹&º.ôÿÿ ÿèìÏ&/œ ²ÿÿ ÿèöÏ&_&.ô6Xÿÿ ÿéóÏ&"xÖÿÿ ÿèîÎ&Ö!–ÿÿ ÿêôÏ&!ýÖÿÿ ÿéóÍ&Ö!ÿÿ ÿèôÏ&.ô6Yÿÿ ÿéóÍ& ²"{ÿÿ ÿèõÑ&å&.ô6Zÿÿ ÿèóÏ&.p&.ô6[ÿÿ ÿèçË&!.ôÿÿ ÿéòÏ&Ö×ÿÿ ÿéóÑ& ²!Âÿÿ ÿèôÒ& ²6\ÿÿ ÿèóÏ&l&k.ôÿÿ ÿèõÏ&.q.ôÿÿ ÿéîÐ& ² »ÿÿ ÿèóÏ& ²‡ÿÿ ÿèòË&"J.ôÿÿ ÿéñÍ&Ö$\ÿÿ ÿäöÍ& ²"qÿÿ ÿéõÐ& ²6]ÿÿ ÿéòÐ& ²#£ÿÿ ÿèóÏ&0r.ôÿÿ ÿèëÏ&#¾&#½.ôÿÿ ÿèëÏ&r.ôÿÿ ÿèôÏ&#¼.ôÿÿ ÿèëÎ&#O.ôÿÿ ÿéñÍ&Ö"ÿÿ ÿéôÍ& ²-ÿÿ ÿéñÏ& h ²ÿÿ ÿéõÑ& 'ÿÿ ÿéóÏ&a ²ÿÿ ÿéíÍ& ² qÿÿ ÿéíÍ&Ö"ÿÿ ÿéóÍ&) ²ÿÿ ÿèöÐ&,ê&».ôÿÿ ÿéòÏ&m ²ÿÿ ÿéîÍ& ²Òÿÿ ÿéëÍ&¦ ²ÿÿ ÿéõÍ&Ö":ÿÿ ÿêóÏ&Ö!eÿÿ ÿèöÏ&,ë&%Ã.ôÿÿ ÿêõÍ&Ö •ÿÿ ÿçðÍ& ²"ÿÿ ÿéóÏ& ²6^ÿÿ ÿèòÍ& ²"¦ÿÿ ÿéòÏ& ²"·ÿÿ ÿéðÍ& ²,ìÿÿ ÿéôÐ& ²$†ÿÿ ÿéôÎ& ²"Zÿÿ ÿéóÏ& ²!¼ÿÿ ÿéðÍ& ²! ÿÿ ÿèöÏ&"Å.ôÿÿ ÿéòÐ&Ö6_ÿÿ ÿéóÍ& ²6`ÿÿ ÿéóÏ&K ²ÿÿ ÿèëÏ&!|&à.ôÿÿ ÿçòÏ&Ö"ÿÿ ÿèóÏ&,î&.ô6aÿÿ ÿèöÐ&&.ô6bÿÿ ÿèñÐ&#ï.ôÿÿ ÿèóË&.ô6cÿÿ ÿéöÎ& ²$;ÿÿ ÿéïÍ& ²*Qÿÿ ÿèôÏ&™&(ù.ôÿÿ ÿèòÑ&$.ôÿÿ ÿè÷Ë&Ê.ôÿÿ ÿéóÏ& ²$Çÿÿ ÿèõÐ& ²(úÿÿ ÿéòÎ& ²,ÔÿÿÿéðÑ&ð&€ÿÿ ÿéôÍ& ²$Óÿÿ ÿéòÏ& ²%ÿÿ ÿéóÐ& ²#ÿÿ ÿèõÐ&.ô6dÿÿ ÿéìÏ&Ö=ÿÿ ÿèñÐ&$4.ôÿÿ ÿèðË&"….ôÿÿ ÿèïË&*R.ôÿÿ ÿéòÏ& ²#ÿÿ ÿèóÐ&M&$z.ôÿÿ ÿèõÎ&$K.ôÿÿ ÿèóÏ& !& %.ôÿÿ ÿéìÍ&Ö"Õÿÿ ÿéöÏ& ²¶ÿÿ ÿèôÎ&,&.ô6eÿÿ ÿæõÍ& ²$ÿÿ ÿèôÐ& ²fÿÿ ÿéõÍ& ²Cÿÿ ÿèóÏ&,ï&.ô6fÿÿ ÿçíË&$í.ôÿÿ ÿéðÏ& ²(ûÿÿ ÿèñÐ&$o.ôÿÿ ÿèôÎ&% .ôÿÿ ÿèõÎ& ¢ÿÿ ÿéóÏ& ²$kÿÿ ÿéöÑ& ²$ÿÿ ÿèíË&.ô6gÿÿ ÿèñÏ&Ö6hÿÿ ÿèíÍ& ²(üÿÿ ÿéëÍ& ²$Sÿÿ ÿéòÍ& ²6iÿÿ ÿéôÍ& ²gÿÿ ÿéðÑ& ²#÷ÿÿ ÿèðÏ&&½&&¾.ôÿÿ ÿéñÏ&Ö,ðÿÿ ÿèôÏ&,ñ&.ô6jÿÿ ÿèïÑ&B.ôÿÿ ÿéôÏ& ²*pÿÿ ÿèöÒ& ²$%ÿÿ ÿéòÑ&*S ²ÿÿ ÿéðÍ& ²$ ÿÿ ÿéïÐ& ²(ýÿÿ ÿéòÐ& ²%ÿÿ ÿéïÍ& ²$Qÿÿ ÿéöÏ& ²$iÿÿ ÿèïÐ&(þ.ôÿÿ ÿèöÐ&Ë&Ê&.ô6kÿÿ ÿèóÏ& ²6lÿÿ ÿèòÏ&& ÿÿ ÿéóÏ&$Õ ²ÿÿ ÿéôÏ& ²-Þÿÿ ÿéìÍ& ²#(ÿÿ ÿêóÍ&$Öÿÿ ÿéñÐ&$©Öÿÿ ÿèóÎ& ²$¹ÿÿ ÿéöÏ&36mÿÿ ÿéôÑ& ²/~ÿÿ ÿéôÐ&& ²ÿÿ ÿéòÍ& ²6nÿÿ ÿèöÐ&/¯.ôÿÿ ÿçöÐ& ²,òÿÿ ÿéõÐ& ²$ôÿÿ ÿéñÐ&Ö øÿÿ ÿèôÐ&z&.ô6oÿÿ ÿèõÍ& ²/:ÿÿ ÿéóÏ&Öƒÿÿ ÿêòÏ&Ö6pÿÿ ÿèôÑ&‰&ˆ.ôÿÿ ÿéóÐ&$/ ²ÿÿ ÿçóÐ&ó&÷ ²ÿÿ ÿèðÐ& ²/Lÿÿ ÿèóÏ&|&˜.ôÿÿ ÿéöÐ&Ë& ²&ÊÌÿÿ ÿéöÐ& ²/Vÿÿ ÿéóÍ& ²(ÿÿÿ ÿçñÏ&°&±.ôÿÿ ÿèõÏ&,ó.ôÿÿ ÿéôÐ& ²6qÿÿ ÿçõÑ& ²'ºÿÿ ÿéîÑ&Ö'»ÿÿ ÿéôÑ& ²,ôÿÿ ÿèôÏ&,&.ôÿÿ ÿèñÏ& ²*Tÿÿ ÿèòÐ&µ&´.ôÿÿ ÿçðÑ&¶.ôÿÿ ÿéõÒ& ²¡ÿÿ ÿéõÏ& ²6rÿÿ ÿèñÓ& ²1ÿÿ ÿèôÓ&.ô6sÿÿ ÿèíÏ&).ôÿÿ ÿéôÏ&)ÿÿ ÿèõÒ&)Öÿÿ ÿéóÍ& ²&¡ÿÿ ÿéöÑ& ²6tÿÿ ÿéõÑ& ²)ÿÿ ÿé÷Ï& ²6uÿÿ ÿèöÏ&,ö&,õ.ôÿÿ ÿéöÏ& ²*æÿÿ ÿéôÑ& ²'·ÿÿ ÿçõÏ&+ .ôÿÿ ÿéîÍ& ²)ÿÿ ÿè÷Ð&,ö&.ô6vÿÿ ÿèñË&.ô6wÿÿ ÿèóÏ&S.ôÿÿ ÿèõË&,÷&.ô6xÿÿ ÿèðÑ&h.ôÿÿ ÿèìÏ&).ôÿÿ ÿèóÏ&).ôÿÿ ÿéöÍ& ²6yÿÿ ÿéóÏ& ²)ÿÿ ÿéóÍ& ²&šÿÿ ÿèòÏ&.ô6zÿÿ ÿçõÐ&Ö6{ÿÿ ÿêðÏ&Ö)ÿÿ ÿéöÏ&Ö%+ÿÿ ÿèóÑ&b.ôÿÿ ÿé÷Ð& ²) ÿÿ ÿèôË&Ü&Ý.ôÿÿ ÿçòÏ&.ô6|ÿÿ ÿèõÏ&.ô6}ÿÿ ÿéóÐ&&þÿÿ ÿèõÐ&,ø&.ô6~ÿÿ ÿèõÐ&.ô0]ÿÿ ÿèóÐ&0&'š.ôÿÿ ÿéóÍ&J ²ÿÿ ÿèõÐ& ² ÿÿ ÿèòÐ&#¿.ôÿÿ ÿçòÑ& ²) ÿÿ ÿéôÍ& ²) ÿÿ ÿéíÏ& ²-:ÿÿ ÿçöÏ& ²&%.ôÿÿ ÿèõË&,ù&,ú.ôÿÿ ÿéóÑ& ²&›ÿÿ ÿèôÐ&,&.ô6ÿÿ ÿéõÏ&y ²ÿÿ ÿèñÐ&&e& ²&gÿÿ ÿèôÏ&Þ&.ô6€ÿÿ ÿéñÍ&Ö6ÿÿ ÿèôÐ&&œ.ôÿÿ ÿé÷Î& ²&¢ÿÿ ÿéòÏ& ².rÿÿ ÿéôÑ& ²$Üÿÿ ÿéñÐ&j) ÿÿ ÿéñÍ& ²6‚ÿÿÿéóÏ&„6ƒÿÿ ÿèóÐ&.ô6„ÿÿ ÿéóÐ& ²) ÿÿ ÿçôÎ& ²6…ÿÿ ÿçòÐ&Ö6†ÿÿ ÿèõÐ&&,û.ôÿÿ ÿèóÏ&˜&.ô6‡ÿÿ ÿèóÏ&•&÷.ôÿÿ ÿçêÍ& ²%ëÿÿ ÿèöÏ&%ì.ôÿÿ ÿèõÍ& ²)ÿÿ ÿéôÐ& ²&§ÿÿ ÿèòÐ&).ôÿÿ ÿèîÏ&&&.ôÿÿ ÿéöÐ& ²ëÿÿ ÿéöÏ&0Ú ²ÿÿ ÿéóÏ& ²&©ÿÿ ÿéòÏ& ²%àÿÿ ÿèóË&.ô0Ùÿÿ ÿéôÏ&Ö&ªÿÿ ÿèöÒ&Ö6ˆÿÿ ÿèöÐ&*n&*m.ôÿÿ ÿèñÎ&).ôÿÿ ÿèôÍ&Ö’ÿÿ ÿèôÑ&,&.ô6‰ÿÿ ÿçòÏ& ²)ÿÿ ÿèíË&ð&.ô6Šÿÿ ÿèîË&ñ&.ô6‹ÿÿ ÿéôÏ& ²zÿÿ ÿéìÑ& ²šÿÿ ÿçòÍ&) ²ÿÿ ÿêíÐ&Ö6Œÿÿ ÿèôÏ&&.ô6ÿÿ ÿéòÍ& ²&«ÿÿ ÿèóË&i.ôÿÿ ÿèîÏ&J&.ô6Žÿÿ ÿèóÌ&).ôÿÿ ÿçôÏ&Ä ²ÿÿ ÿèðÐ&,ü&.ô6ÿÿ ÿèñÍ&,ýÖÿÿ ÿèóÏ&#&0.ôÿÿ ÿèñË&).ôÿÿ ÿéóÐ&&Öÿÿ ÿêòÏ&Ö6ÿÿ ÿèóË&.ô6‘ÿÿ ÿèôÎ&,þ.ôÿÿ ÿéóÐ&t ²ÿÿ ÿéóÐ&b ²ÿÿ ÿéôÏ& ²*Óÿÿ ÿèëË&.ô6’ÿÿ ÿèóË&.ô6“ÿÿ ÿéòÏ& ²²ÿÿ ÿèôÏ&,&.ô6”ÿÿ ÿêöÍ&Ö%ÿÿ ÿêòÏ&µÖÿÿ ÿèôÏ&&¬.ôÿÿ ÿèïÎ& ²&¨ÿÿ ÿèôÐ&,&.ô6•ÿÿ ÿèôÎ&,&.ô6–ÿÿ ÿéóÍ& ²)ÿÿ ÿèôÏ&Þ&.ô6—ÿÿ ÿèóÐ& ü&.ô/âÿÿ ÿè÷Ï& ü&'.ôÿÿ ÿèñÍ&Ö8ÿÿ ÿéôÍ& ²6˜ÿÿ ÿèòË&Ç&´.ôÿÿ ÿèòÐ&)&.ô6™ÿÿ ÿèóÎ&&;&.ô6šÿÿ ÿéñÏ&Ö)ÿÿ ÿèîË&ñ&.ô6›ÿÿ ÿèîË&ñ&.ô6œÿÿ ÿçíÑ&).ôÿÿ ÿèôÏ& ²6ÿÿ ÿèøË&&±.ôÿÿ ÿèòË&).ôÿÿ ÿéïÏ& ²)ÿÿ ÿèõÐ&.ô6žÿÿ ÿéñÍ& ²)ÿÿ ÿèóÎ&-.ôÿÿ ÿçñË& ¨& ©.ôÿÿ ÿéóÐ&) ²ÿÿ ÿéôÐ&& ²6ŸÿÿÿêíÑ&6 ÿÿ ÿæôË&.ô6¡ÿÿ ÿèòÏ&).ôÿÿ ÿçõÑ& ²/åÿÿ ÿèòÐ&.ö.ôÿÿ ÿéõÒ&Ö&Ìÿÿ ÿéôÐ& ².õÿÿ ÿé÷Ñ&½ ²ÿÿ ÿèôË&(&.ô6¢ÿÿ ÿéóÏ& ²,ÿÿÿ ÿèöÑ& ²jÿÿ ÿéõÍ& ²&Ëÿÿ ÿèôÑ&Þ&.ô6£ÿÿ ÿêóÒ&´Öÿÿ ÿèñÑ&.ô6¤ÿÿ ÿèóÑ&&Í.ôÿÿ ÿêôÑ&Ö)ÿÿ ÿèòÏ&.Ô.ôÿÿ ÿèòÏ& ²/cÿÿ ÿéôÏ& ²&Ü6¥ÿÿ ÿèóÏ&.ô.äÿÿ ÿèôÏ&ã&å&,.ôÿÿ ÿéðÍ&­ ²ÿÿ ÿèôÑ& ²-ÿÿ ÿçôÏ& ²&Êÿÿ ÿèôË&Ü&.ô6¦ÿÿ ÿèöÐ& ²&­ÿÿ ÿèñÏ&).ôÿÿ ÿèôÏ&.ô6§ÿÿ ÿèóÏ&˜&.ô6¨ÿÿ ÿéòÏ& ²) ÿÿ ÿéõÏ& ².ÿÿ ÿéóÏ&!d/ôÿÿ ÿéôÏ&"& ²6©ÿÿ ÿéìÍ&)! ²ÿÿ ÿéõÍ&)" ²ÿÿ ÿçóÐ& «& ².ôÿÿ ÿèóÏ&Ž& ³.ôÿÿ ÿèøË&)#.ôÿÿ ÿèòÍ& ²&Ïÿÿ ÿéñÐ& ²)$ÿÿ ÿéóÏ& ².sÿÿ ÿèñÏ&)%.ôÿÿ ÿèôÏ&¨.ôÿÿ ÿèöÐ&.ô6ªÿÿ ÿèòÐ&6.ôÿÿ ÿèõÑ&%v.ôÿÿ ÿèñÑ&-.ôÿÿ ÿèñË&&Ð.ôÿÿ ÿèòÍ&.{ ²ÿÿ ÿèöÐ&µ&.ô6«ÿÿ ÿèóÏ&¾.ôÿÿ ÿéõÑ&.t ²ÿÿ ÿèóÐ&.ô&/â6¬ÿÿ ÿèòÐ&.ô6­ÿÿ ÿèõÏ&)&.ôÿÿ ÿèòÐ&)'.ôÿÿ ÿèôÏ&)(.ôÿÿ ÿêóÏ&Ö.uÿÿ ÿèõÐ& !&* .ôÿÿ ÿèóË&)).ôÿÿ ÿèóÐ&˜&—.ôÿÿ ÿçôÑ&Ö&Îÿÿ ÿèôÑ&(&.ô6®ÿÿ ÿèöÏ&.ô6¯ÿÿ ÿèñÏ&.ô6°ÿÿ ÿçôÑ&.ô6±ÿÿ ÿèíË&ð&.ô6²ÿÿ ÿéóÐ&)*6³ÿÿ ÿèõÑ&ë&.ô6´ÿÿ ÿèõÏ&)+&.ô6µÿÿ ÿèóÐ& ²@ÿÿ ÿéôÐ& ²&Òÿÿ ÿéòÍ& ²-ÿÿ ÿçôÍ& ²),ÿÿ ÿèõÑ&)..ôÿÿ ÿéôÐ& ²&6¶ÿÿ ÿéòÏ& ²%Yÿÿ ÿçñÐ& ²)-ÿÿ ÿèôÒ& ²%ƒÿÿ ÿèðÒ& ²6·ÿÿ ÿè÷Ñ&.ô6¸ÿÿ ÿéôÐ& ²6¹ÿÿ ÿèñÐ&--.ôÿÿ ÿèòÐ&%U.ôÿÿ ÿçóÏ&)/.ôÿÿ ÿèïÏ& &.ô6ºÿÿ ÿè÷Ë&%d.ôÿÿ ÿèôÏ&(&.ô6»ÿÿ ÿçñÏ&Ž& ©.ôÿÿ ÿèìË&-.ôÿÿ ÿèòÐ&)1.ôÿÿ ÿéøÑ& ²Êÿÿ ÿèøÏ&,c&.ô6¼ÿÿ ÿèôÐ&.ô6½ÿÿ ÿèöÐ&.ô6¾ÿÿ ÿèôÏ&&$ .ôÿÿ ÿé÷Í& ²ÿÿ ÿèñÑ&#&.ô6¿ÿÿ ÿèóÏ&.ô6Àÿÿ ÿéòÏ&& ²6Áÿÿ ÿèôÏ&.ô6Âÿÿ ÿèôÐ&.ô6Ãÿÿ ÿèôÏ&.ô6Äÿÿ ÿèðÐ& ². ÿÿ ÿèôÏ&2&1.ôÿÿ ÿèöÐ&)2.ôÿÿ ÿçõÐ&)3& ².ôÿÿ ÿçñÎ& ©&.ô6Åÿÿ ÿèõÐ&#T6Æÿÿ ÿèôÍ&(&.ô6Çÿÿ ÿç÷Ì& ª&i.ôÿÿ ÿéïÏ&Ö6Èÿÿ ÿèõË&+Ó&.ô6Éÿÿ ÿçñË&.ô6Êÿÿ ÿèõÑ&&.ô6Ëÿÿ ÿèïÐ&)4.ôÿÿ ÿéòÍ&- ²ÿÿ ÿåõÑ&\& ²6Ìÿÿ ÿæóÍ& ²6Íÿÿ ÿè÷Î&)6.ôÿÿ ÿéõÎ& ²*„ÿÿ ÿèñÑ&)7.ôÿÿ ÿèôÐ&.ô6Îÿÿ ÿèñÑ&)8.ôÿÿ ÿèêË&ò&.ô6Ïÿÿ ÿçôÑ&1&.ô6Ðÿÿ ÿèôË&.ô6Ñÿÿ ÿèêË&ò&.ô6Òÿÿ ÿèòÎ&.v.ôÿÿ ÿéôÏ&)9 ²ÿÿ ÿéóÍ& ²):ÿÿ ÿéóÍ& ²6Óÿÿ ÿéòÏ& ²6Ôÿÿ ÿéòÍ& ²);ÿÿ ÿèóÏ&)“&.ô6Õÿÿ ÿèòË&.ô6Öÿÿ ÿèìÏ&)<.ôÿÿ ÿèóË&&.ôÿÿ ÿèóÏ&.ô6×ÿÿ ÿèóÍ&&Ö.ôÿÿ ÿçòÐ&&×.ôÿÿ ÿèôÐ&}&.ô6Øÿÿ ÿèõÒ&.ô6Ùÿÿ ÿèõÒ&&Œ.ôÿÿ ÿèôÐ&~&}.ôÿÿ ÿèóÒ&.w.ôÿÿ ÿèïÍ& ²&ó6Úÿÿ ÿèöË&&.ôÿÿ ÿèìÌ&.ô6Ûÿÿ ÿèíÌ&.ô6Üÿÿ ÿèòÐ&.>.ôÿÿ ÿèôÐ&-.ôÿÿ ÿçñÑ&)=.ôÿÿ ÿæöÐ&.ô6Ýÿÿ ÿèöÐ&.ô6Þÿÿ ÿæôÒ&#U&#V#Wÿÿ ÿç÷Ë&i&.ô6ßÿÿ ÿèöÑ&.ô6àÿÿ ÿèõÎ&%&%.ôÿÿ ÿçóÐ&.ô6áÿÿÿçöÁ&%Æ6ãÿÿ ÿêóÏ&mÑÿÿ ÿçëÏ&mÈÿÿ ÿêóÏ&mÊÿÿ ÿèóË& 6äÿÿ ÿêõÏ&ymÿÿÿèõÒ&%Ç ÿÿ ÿêóÏ& µ²ÿÿ ÿéìÏ&mYÿÿ ÿèóÐ&Æ ÿÿÿçôÑ&0%Èÿÿ ÿéêÏ&¾ µÿÿ ÿéíÏ&% %9ÿÿ ÿéòÏ& µ&yÿÿ ÿéöÏ&ÕÙÿÿ ÿéóÏ&)ëÿÿ ÿèóÇ& Iÿÿ ÿéõÏ& µRÿÿ ÿêìÏ&m(¾ÿÿ ÿéõÑ& µ²ÿÿ ÿèóÑ&3 ÿÿ ÿèôÏ& µ%Éÿÿ ÿèôÏ&% (/ÿÿ ÿéôÏ&”mÿÿ ÿéöÏ&ëBÿÿ ÿéòÏ& µíÿÿ ÿêõÏ& µÿÿ ÿéñÏ&ñëÿÿ ÿèóÑ& 6åÿÿÿèóÏ&V%Áÿÿ ÿèìÏ&% %Âÿÿ ÿêóÏ& µ'Fÿÿ ÿéòÏ&è% ÿÿ ÿéôÏ&!m% ÿÿ ÿéêÏ&j% ÿÿ ÿçõÏ&‡ µÿÿ ÿéóÏ&ëÔÿÿ ÿéóÏ&Á% ÿÿ ÿêôÐ& µøÿÿ ÿéíÏ&ë%ÊÿÿÿêòÐ&)>LÿÿÿåõÏ&¦ÿÿ ÿéôÏ&ë%Ëÿÿ ÿéöÏ&% %!ÿÿ ÿéõÏ& µ!ÿÿ ÿêóÏ& µ'Ëÿÿ ÿéóÐ& µUÿÿÿêòÏ&L6æÿÿ ÿéòÏ& µÎÿÿ ÿéõÏ&ë/+ÿÿ ÿçôÏ&%Ì µÿÿ ÿéõÏ&ë3ÿÿ ÿêóÏ& µ%Íÿÿ ÿéõÏ&ë ÿÿ ÿéñÏ&% 6çÿÿ ÿéõÏ&% %"ÿÿ ÿéôÏ&Àëÿÿ ÿéóÏ&ë&ØÙÿÿ ÿéöÑ&ë“ÿÿÿêòÍ&L&®¯ÿÿ ÿéðÏ& µ¶ÿÿ ÿéõÑ&O6èÿÿ ÿéóÏ&â% ÿÿ ÿèóË& 6éÿÿ ÿé÷Ï&ëEÿÿ ÿéõÏ&sëÿÿ ÿéóÏ&~% ÿÿ ÿéîÏ&ë.ÿÿ ÿéôÐ&7 µÿÿ ÿèïÏ& µ1ÿÿ ÿéõÐ& µ\ÿÿ ÿéôÑ& ¸ µÿÿ ÿêîÏ&!Ž µÿÿ ÿêõÏ& µÜÿÿ ÿèõÈ&-tÿÿ ÿé÷Ï&í&ìëÿÿ ÿéëÏ&ë ³ÿÿ ÿéòÏ&{% ÿÿÿéóÑ&32ÿÿ ÿéòÐ&ëÙÿÿ ÿêóÏ& µÿÿ ÿéóÏ&“% ÿÿ ÿèõÏ&¼% ÿÿ ÿéôÐ&ëUÿÿ ÿèóÏ& µVÿÿÿåõÏ&ð&ÿÿ ÿç÷Ï&ëÿÿ ÿèõÐ&t&(6êÿÿ ÿéòÐ&% *ÿÿ ÿéóÐ&³% ÿÿ ÿéíÏ&ë%<ÿÿ ÿéðÒ&ë0íÿÿ ÿéòÏ&©% ÿÿ ÿéôÏ&.ù%=ÿÿ ÿéõÏ&’ëÿÿ ÿéõÐ&ëƒÿÿ ÿèõÑ&ópÿÿ ÿèõÐ& µLÿÿ ÿéõÏ&ëÿÿ ÿéöÏ&% %#ÿÿ ÿéôÏ&ë%$ÿÿ ÿç÷Ï&o&nøÿÿ ÿéöÏ&X% ÿÿ ÿéõÏ&ò&óôÿÿ ÿèõË&t6ëÿÿ ÿééÏ& µÿÿ ÿæóÆ&- 6ìÿÿ ÿéôÏ&/øÿÿ ÿéóÏ&ò6íÿÿ ÿçõÑ& % ÿÿ ÿèóÐ&Œ% ÿÿÿêóÏ&L6îÿÿ ÿéóÏ&ëŠÿÿ ÿé÷Ñ& µ,Íÿÿ ÿéîÑ& µ ¶ÿÿ ÿéõÐ&ë®ÿÿ ÿéíÐ&ë&ÙÕÿÿ ÿèñÏ&ë%Ïÿÿ ÿèõÑ&,pÿÿ ÿéòÐ&ë%Ðÿÿ ÿèóÏ&b& %ÑÿÿÿèíÉ&6ïÿÿ ÿéôÏ&% (mÿÿ ÿéóÑ&% %%ÿÿ ÿéóÏ&ë%Óÿÿ ÿéóÏ&ë1ÿÿ ÿéóÐ&ë%Ôÿÿ ÿèõÄ&t6ðÿÿ ÿéöÏ&ë"Hÿÿ ÿèîÏ&ë1ÿÿ ÿçöÐ&ø&)?#‚ÿÿ ÿéòÏ&ë Yÿÿ ÿéíÏ& µ%ÕÿÿÿêóÑ&36ñÿÿ ÿéõÏ&d*ÿÿ ÿéñÏ&O6òÿÿ ÿéôÏ&›&·% ÿÿ ÿèóÏ&ë%Öÿÿ ÿéóÏ&ëÙÿÿÿéòÏ&0ú0üÿÿÿêõÐ&/0üÿÿ ÿéñÏ&ë!aÿÿ ÿèöÏ&ø&#"#!ÿÿ ÿéöÏ& ‹ëÿÿ ÿéóÏ&*!ÿÿ ÿéõÏ&ëØÿÿ ÿéòÏ&ë"žÿÿ ÿêóÏ& µ8ÿÿ ÿéõÑ&% "±ÿÿÿèöÏ&0ü&á%Ãÿÿ ÿèõÐ&#¦tÿÿ ÿè÷Ï&!ßëÿÿ ÿéóÐ&"ø% ÿÿ ÿèóÏ&ˆ% ÿÿÿçóÏ&O- ÿÿ ÿèõÊ&Ï&tÐÿÿ ÿèòÏ&øŸÿÿ ÿéöÐ&ë!ÿÿ ÿéôÏ&ëÿÿ ÿéóÊ&ò6óÿÿ ÿéôÏ&ë6ôÿÿ ÿèïÏ&!ëÿÿ ÿèõÐ&t6õÿÿ ÿèíÏ&"4% ÿÿ ÿèõÏ&ë0ýÿÿ ÿéôÏ&ë0tÿÿÿè÷Ï&å&æ ÿÿ ÿéõÏ&ë#Iÿÿ ÿé÷Ï&ë"cÿÿ ÿéõÏ&ë£ÿÿÿêòÏ&{&L6öÿÿ ÿèõÆ&{&ztÿÿ ÿèóÏ&#;% ÿÿ ÿèõÏ&x% ÿÿ ÿéóÏ& µ&Žÿÿ ÿçõÏ&ëYÿÿ ÿèõÏ&3pÿÿ ÿéñÏ&%×ëÿÿ ÿåõÏ&"Æÿÿ ÿéîÏ&*&JKÿÿ ÿèïÐ&ë0¹ÿÿ ÿéöÏ&ë&Jâÿÿ ÿéôÑ&ËÐÿÿ ÿéóÏ&ø#|ÿÿ ÿéöÏ&ë$Rÿÿ ÿéïÏ&ë6÷ÿÿ ÿé÷Ï&ë$gÿÿ ÿéõÑ&ë$‰ÿÿ ÿéõÏ&ëÿÿ ÿéõÏ&% )iÿÿ ÿéòÐ&ø% ÿÿ ÿèõÊ&Ñ&t&Ò6øÿÿ ÿèõÏ&ë¢ÿÿ ÿèõÒ&t/ìÿÿ ÿèõÏ&p6ùÿÿ ÿèóÏ& áëÿÿ ÿéóÏ&ò&Þÿÿ ÿéöÏ&ëNÿÿ ÿéôÏ&ë1ÿÿ ÿèõÐ& ëÿÿ ÿéñÏ&ë%ÿÿ ÿèõÄ&Ó&Ôtÿÿ ÿéôÏ&ë/9ÿÿ ÿéóÏ&†% ÿÿ ÿêöÑ&Ë%Øÿÿ ÿêôÑ&ø%Ùÿÿ ÿêõÏ&.ù%Úÿÿ ÿèõÑ&Wtÿÿ ÿéîÏ&ë%Ûÿÿ ÿçóÏ&.ù+ÿÿ ÿêôÏ&.ù.ûÿÿ ÿéòÏ&ë&E6úÿÿ ÿêõÏ&*&Þ6ûÿÿ ÿåöÏ&6üÿÿ ÿéöÏ&Õ&»¼ÿÿ ÿèõÐ&/Wëÿÿ ÿèðÏ&*&á%Üÿÿ ÿèõÑ&t6ýÿÿ ÿé÷Ð&ë%Ýÿÿ ÿéôÏ&ë#~ÿÿ ÿèùÏ&è&épÿÿ ÿéõÌ&5&6!ÿÿ ÿéôÏ& µ%Þÿÿ ÿéòÏ&ë”ÿÿ ÿèôÏ&*0ØÿÿÿèñÑ&%ä/ ÿÿ ÿéõÏ&ëšÿÿ ÿçôÏ&ë%ßÿÿ ÿéòÏ&.ù1 ÿÿÿéöÏ&F0üÿÿ ÿçòÏ&ë%âÿÿ ÿéòÏ&ëÿÿ ÿéóÏ&ø†ÿÿ ÿéóÏ&ë%ãÿÿ ÿèõÐ&ëìÿÿÿèôÏ&çèÿÿ ÿèôÒ&éçÿÿ ÿéõÏ&ë%åÿÿÿéóÒ&%æ0üÿÿ ÿêöÏ&*%ÿÿ ÿèõÑ&Ë&%çÿÿ ÿèôÑ&Ø6þÿÿ ÿéõÏ&ë%èÿÿ ÿéôÏ&ë%éÿÿ ÿçìÏ&ë%êÿÿ ÿéôÏ&ë%'ÿÿ ÿèôÏ&ë%(ÿÿ ÿéõÏ&% %&ÿÿ ÿèõÏ&t6ÿÿÿ ÿéóÏ&% %íÿÿ ÿéðÏ&ø&ú7ÿÿ ÿéðÏ&ú&ø7ÿÿ ÿåõÏ&…&"ÿÿÿçõÏ&- - ÿÿ ÿéöÇ&Õ7ÿÿ ÿéîÏ&Ë&0b7ÿÿÿéðÏ&& 7ÿÿ ÿèöÑ&ë%îÿÿ ÿéôÏ&Ø% ÿÿ ÿéóÑ&òÿÿ ÿéòÏ&ë%ïÿÿ ÿé÷Ï&ëöÿÿ ÿéôÐ&ë%ðÿÿ ÿêõÏ&ø&Þ!¦ÿÿÿèôÐ&%ñ0üÿÿ ÿéôÏ&*&)(ÿÿ ÿéñÏ&ë'ÿÿ ÿèóÐ& 7ÿÿ ÿèôÏ&ë%òÿÿ ÿé÷Ñ&ë.£ÿÿ ÿèóÑ&ë&&%ÿÿ ÿéöÒ&ë*€ÿÿ ÿêóÏ&*.øÿÿÿéðÎ&%ó0üÿÿÿåõÇ&7ÿÿ ÿèõÐ&ë¢ÿÿ ÿéôÏ&ë%ôÿÿ ÿéóÏ&ë%õÿÿ ÿèõÐ&+âpÿÿ ÿéóÏ&*Êÿÿ ÿéöÏ&*7ÿÿ ÿêôÐ&*Pÿÿ ÿéöÏ&Õ7ÿÿ ÿêõÐ&*- ÿÿ ÿéñÏ&ë%Rÿÿ ÿé÷Ï&ø÷ÿÿ ÿèõÏ&p&l7 ÿÿ ÿêòÐ&Ë-.ÿÿ ÿèôÑ&ë%„ÿÿ ÿèóÐ&*@ÿÿ ÿéøÑ&ûËÿÿ ÿéðÏ&Ë&ú7 ÿÿ ÿèôÏ&*%öÿÿ ÿéñÑ&ø%÷ÿÿ ÿéöÏ&**…ÿÿ ÿéíÏ&*–ÿÿ ÿéòÏ&ë- ÿÿ ÿé÷Ñ&0û7 ÿÿ ÿéóÏ&*Oÿÿ ÿèóÐ&Ç&‰7 ÿÿ ÿçñÑ&*%øÿÿ ÿæóÐ&*.=ÿÿ ÿêóÏ&*7 ÿÿ ÿçöÐ&*7ÿÿ ÿéöÑ&lkÿÿ ÿæõÏ&ÿÿ ÿéõÏ&U&VWÿÿÿòõÌ&Ðÿÿ ÿèôÏ&%ÉKÿÿ ÿéóÏ&¶$'ÿÿ ÿéóÏ&¹Kÿÿ ÿèöÒ&$'$&ÿÿ ÿéõÏ&$''çÿÿ ÿéòÏ& Âÿÿ ÿèóÏ& ÿÿ ÿéðÏ& |ÿÿ ÿçõÓ&#Œ)@ÿÿ ÿéóÏ& )ÿÿ ÿèöÏ&'! ÿÿ ÿèöÏ&#ŒÇÿÿ ÿèõÏ& ¯ÿÿ ÿéñÑ& $”ÿÿ ÿèôÒ&)A7ÿÿ ÿçôÏ&Å#ŒÿÿÿêìÐ&)D)Cÿÿ ÿéóÏ&xMÿÿ ÿéèÐ&ëxÿÿÿéîÐ&ö%ÿÿ ÿéôÏ&z·ÿÿ ÿéõÏ&xyÿÿÿéíÑ&0%™ÿÿ ÿéìÏ&œ¬ÿÿ ÿéòÏ&z%¡ÿÿ ÿèôÏ&xÿÿ ÿéðÏ&{zÿÿ ÿéóÏ&%š¬ÿÿ ÿèîÏ&¬%›ÿÿÿèòÐ&r&%¤%œÿÿ ÿéóÏ&¬ÿÿ ÿéëÏ&z)ÿÿÿ ÿéóÏ&¬%£ÿÿ ÿèôÏ&%¦ÿÿ ÿéòÏ&¬%§ÿÿÿèîÏ&%¤7ÿÿ ÿèõÐ&ñ%¥ÿÿ ÿéôÏ&`zÿÿ ÿéòÑ&z7ÿÿ ÿéóÐ&zJÿÿ ÿéòÏ&zÒÿÿ ÿèôÏ& 0Ýÿÿ ÿéòÏ&©¬ÿÿÿèîÒ&%¬7ÿÿ ÿéóÏ&¬ýÿÿ ÿéóÏ&zEÿÿ ÿèðÉ&A7ÿÿÿèîÏ&³&%¥7ÿÿÿèîÏ& &%¥7 ÿÿ ÿéóÏ&z&Çÿÿ ÿéïÏ&z%­ÿÿ ÿéòÏ&¬%©ÿÿ ÿéôÏ&%¨7!ÿÿ ÿéõÐ&z%ªÿÿ ÿéóÑ&¬%%ÿÿ ÿèöÏ&z%«ÿÿ ÿçóÏ&¬ÿÿ ÿèöÎ& ‹ ÿÿ ÿèóÏ& 0sÿÿ ÿèôÏ&%¬7"ÿÿ ÿèïÐ&z0¹ÿÿ ÿéôÐ&•&–zÿÿ ÿéõÏ&¬";ÿÿ ÿèòÐ& % ÿÿ ÿéîÑ&z%®ÿÿ ÿéóÐ&z$bÿÿÿèîÏ&ø&%¥ ÿÿ ÿéðÑ&z‡ÿÿ ÿéõÑ&$7#ÿÿ ÿéôÏ&%—&»¼ÿÿ ÿçóÐ&ÿ&þ7$ÿÿ ÿéóÏ&z´ÿÿ ÿéõÏ&z%´ÿÿÿéîÎ&%—7%ÿÿ ÿèöÐ&z%µÿÿ ÿéòÏ&z,¿ÿÿ ÿçôÏ&zÅÿÿ ÿç÷Ò&z±ÿÿ ÿèôÏ&z7&ÿÿ ÿéòÐ&z-/ÿÿ ÿéöÏ&z%eÿÿ ÿèôÏ&)E%öÿÿ ÿèîÐ&%¬7'ÿÿÿéõÑ&%—7(ÿÿ ÿèöÏ& 7)ÿÿ ÿéöÏ&ª7*ÿÿ ÿèïÑ&%»7+ÿÿ ÿéñÐ&nßÿÿ ÿéõÐ&$nÿÿ ÿçøÐ&n%¼ÿÿ ÿéòÐ&n}ÿÿ ÿéóÐ&n¨ÿÿ ÿéõÐ&nmÿÿ ÿèöÏ& ÿÿ ÿéóÐ&n¿ÿÿ ÿèïÐ&n;ÿÿ ÿéòÐ&mnÿÿ ÿéòÐ&obÿÿ ÿéñÐ&p'Bÿÿ ÿéöÐ&oCÿÿ ÿéìÐ&nÿÿ ÿéöÏ&$'ÿÿ ÿèôÐ&oÊÿÿ ÿéôÐ&wnÿÿ ÿéóÐ&o)ÿÿ ÿèóÐ&%Ánÿÿ ÿéñÐ&n®ÿÿ ÿéëÐ&n ÿÿ ÿèðÐ&n& ÿÿ ÿéòÐ&o0iÿÿÿèïÈ&--ÿÿ ÿèôÐ&n,,ÿÿ ÿéóÐ&n%£ÿÿ ÿéôÐ&nøÿÿ ÿèìÐ&n&ÿÿ ÿè÷Ð&ñnÿÿ ÿçõÐ&oYÿÿ ÿéòÐ&o0°ÿÿ ÿéðÐ&oÃÿÿ ÿéóÐ&p&›šÿÿ ÿéëÐ&nWÿÿ ÿçóÐ&n%Ÿÿÿ ÿéòÐ&oÝÿÿ ÿéòÐ&nèÿÿ ÿéèÐ&n%žÿÿ ÿéòÐ&¡oÿÿ ÿéòÐ&nSÿÿ ÿéõÐ&pÿÿÿ ÿéöÐ&o$ÿÿ ÿèôÐ&nPÿÿ ÿéõÐ&o\ÿÿ ÿé÷Ð&oBÿÿ ÿéòÐ&n0Õÿÿ ÿèõÐ&oÏÿÿ ÿéöÐ&oøÿÿ ÿéõÐ&n7,ÿÿ ÿéîÐ&o.ÿÿ ÿéòÐ&o±ÿÿ ÿéöÐ&oŽÿÿ ÿæöÐ&n³ÿÿ ÿéóÐ&p&,ï7-ÿÿ ÿèóÐ&nVÿÿ ÿéóÑ&o!ÿÿ ÿèôÐ&o&]7.ÿÿ ÿéôÐ&o&FEÿÿ ÿéõÐ&pãÿÿ ÿéóÐ&oÿÿ ÿéòÐ&o*ÿÿ ÿééÐ&nÿÿ ÿéóÐ&oìÿÿ ÿçõÐ&o Õÿÿ ÿéòÐ&n0Žÿÿ ÿçõÑ&n ÿÿ ÿèõÐ&nLÿÿ ÿèôÐ&néÿÿ ÿéóÐ&p0qÿÿ ÿéõÐ&pÿÿ ÿéöÐ&p&`_ÿÿ ÿéëÐ&o&–àÿÿ ÿéõÐ&pÈÿÿ ÿèòÐ&p²ÿÿ ÿéôÐ&o'¯ÿÿ ÿçöÐ&o _ÿÿ ÿèóÏ&+Î7/ÿÿ ÿèôÐ&]&aÿÿ ÿéóÐ&o%Óÿÿ ÿéíÐ&o-ÿÿ ÿéôÐ&n(mÿÿÿèôÐ&)F]ÿÿ ÿé÷Ð&n1ÿÿ ÿéòÏ&¿70ÿÿ ÿéîÐ&n±ÿÿ ÿéóÐ&n&Çÿÿ ÿèïÏ&¿& 71ÿÿ ÿéóÐ&¤oÿÿ ÿéòÐ&o >ÿÿ ÿéóÐ&o(=ÿÿ ÿéóÐ&o1ÿÿ ÿèõÑ&&pÿÿ ÿéõÐ&o•ÿÿ ÿéêÐ&o72ÿÿ ÿéîÐ&p&JKÿÿ ÿéõÐ&o#Ìÿÿ ÿéõÐ&o"zÿÿ ÿèóÐ&o#>ÿÿ ÿêõÏ&¿:ÿÿ ÿéîÐ&n!#ÿÿ ÿéóÐ&nÓÿÿ ÿéóÐ&n+ÿÿ ÿéòÐ&o0¢ÿÿ ÿèìÐ&oàÿÿ ÿéõÐ&n"îÿÿÿèôÏ&P"¸ÿÿ ÿèóÐ&nˆÿÿ ÿéôÐ&o!"ÿÿ ÿéôÐ&o#Jÿÿ ÿéðÐ&’nÿÿ ÿéöÐ&o ‹ÿÿ ÿé÷Ð&r&pqÿÿ ÿèïÐ&o0¹ÿÿ ÿéôÐ&&oÿÿ ÿèôÐ&&]ÿÿ ÿèïÐ&o!ÿÿ ÿéôÐ&o"hÿÿ ÿéòÐ&o'mÿÿ ÿéóÏ&¿&-73ÿÿ ÿéõÐ&o$uÿÿ ÿéóÐ&nMÿÿ ÿéóÐ&o$Æÿÿ ÿèöÐ&o% ÿÿ ÿè÷Ð&oËÿÿÿèôÐ&](\ÿÿ ÿêóÏ&¿&+Ï74ÿÿ ÿéñÐ&¿&†75ÿÿ ÿèõÐ&o$nÿÿ ÿèõÐ&o ÿÿ ÿéôÐ&o&žÿÿ ÿèöÑ&o,yÿÿ ÿéôÒ&stÿÿ ÿéöÐ&nNÿÿ ÿéôÑ&onÿÿ ÿèôÏ&¿&Þ76ÿÿÿæñÐ&77ÿÿ ÿèõÐ&pÔÿÿÿæóÏ&+'ÿÿ ÿéóÐ&¿&÷78ÿÿ ÿçòÐ&o%âÿÿ ÿéóÐ&o&¤ÿÿ ÿçêÐ&p%ëÿÿ ÿéóÐ&n´ÿÿ ÿèôÐ&p<ÿÿ ÿéôÐ&o%éÿÿ ÿéòÐ&o&Ÿÿÿ ÿéõÐ&o%åÿÿ ÿèôÐ&& ]ÿÿ ÿéôÐ&p&,79ÿÿ ÿéðÐ&p,†ÿÿ ÿéõÐ&p¤ÿÿ ÿéñÐ&o'ÿÿ ÿéöÐ&p'‡ÿÿ ÿéõÔ&o'‹ÿÿ ÿé÷Ñ&p½ÿÿ ÿéöÐ&?+Ðÿÿ ÿçóÐ& ª& «+Ðÿÿ ÿé÷Ð&püÿÿ ÿèõÐ&o¢ÿÿ ÿéôÒ&pQÿÿ ÿéôÐ&p'ÿÿ ÿéðÐ&o%óÿÿ ÿç÷Ò&p(Rÿÿ ÿçõÐ&p0”ÿÿ ÿèôÑ&o%„ÿÿ ÿèôÏ&P7:ÿÿ ÿçõÐ&¿& ´+ÑÿÿÿèóÐ&+Ï7;ÿÿ ÿéõÏ&¿&+Ó7<ÿÿ ÿéñÑ&p%÷ÿÿ ÿéóÐ&pOÿÿ ÿèôÏ&¿7=ÿÿ ÿéóÐ&p.cÿÿ ÿéóÐ&p.Gÿÿ ÿéòÏ&¿¾ÿÿ ÿéðÐ&pÃÿÿ ÿèõÏ&þ¹ÿÿ ÿíðÏ&þ.ðÿÿÿéòÐ&™&7>ÿÿ ÿéõÏ&xþÿÿÿõîÁ&)G7?ÿÿÿõîÁ&)G7@ÿÿ ÿêóÄ&ÑÀÿÿ ÿêõÂ&À¹ÿÿ ÿñóÂ&Ênÿÿ ÿéìÎ&nYÿÿ ÿèëÐ&À)Hÿÿ ÿéóÄ&&nÿÿ ÿêõÏ&À+Äÿÿ ÿéðÂ&nÂÿÿ ÿî÷Æ&nÿÿ ÿèïÆ&À;ÿÿ ÿéòÏ&¬Àÿÿ ÿéõÍ&Àÿÿ ÿèöÏ&À¡ÿÿ ÿéñÅ&X'Bÿÿ ÿéôÎ&ÀÚÿÿ ÿéôÏ&:nÿÿ ÿéëÉ&n%’ÿÿ ÿéöÏ&Bÿÿ ÿèõÏ&ÀËÿÿ ÿóôÂ&wÀÿÿ ÿéöÃ&ÀÞÿÿ ÿèõÏ&nÿÿ ÿèðÅ&lXÿÿ ÿìõÍ&,ÿÿ ÿèìÏ&À%Âÿÿ ÿéóÃ& ÿÿ ÿæõÇ&)IÀÿÿ ÿçõÏ&^Yÿÿ ÿçôÐ&@&X+Ôÿÿ ÿ÷óÊ&ÀQÿÿ ÿéóÎ&Àÿÿ ÿéóÅ&Àÿÿ ÿéóÏ&Àÿÿ ÿíôÐ&øÀÿÿ ÿéóÏ&ÀÔÿÿÿîîÐ&)J7Aÿÿ ÿèôÏ&ÿÿ ÿíöÌ&1ÿÿ ÿêëÐ&ÀWÿÿ ÿèìÇ&&ÿÿ ÿéêÄ&Àjÿÿ ÿéèÐ&À%žÿÿ ÿéóÂ&À¶ÿÿ ÿèòÏ&X&ÿÿ ÿéïÐ&Rnÿÿ ÿéòÐ&kÀÿÿ ÿòõÆ&nÿÿ ÿêìÏ&+Õ& ¿ÿÿ ÿéóÆ&À)Kÿÿ ÿèòË& Ÿÿÿ ÿéïÏ&X/¶ÿÿ ÿêõË&^5ÿÿ ÿèöÐ&ÀÂÿÿ ÿèîÏ&ÀXÿÿ ÿóôÂ&À&w7Bÿÿ ÿéòÏ&nTÿÿ ÿíóÏ&'¦ÿÿ ÿéóÏ&Àÿÿ ÿèôÐ&ÀPÿÿ ÿéôÏ&XPÿÿ ÿé÷Ï&+Xÿÿ ÿéóÏ&Xâÿÿ ÿèñÐ&'ÿÿ ÿèïÍ&^0ÿÿ ÿéìÉ&Àaÿÿ ÿéòÏ&À{ÿÿ ÿéðÏ&¶ÿÿ ÿêïÆ&Gÿÿ ÿéõÐ&\ÿÿ ÿéöÏ&–ÿÿ ÿêíÑ&žÀÿÿ ÿëóÍ&)Lÿÿ ÿéóÇ&ÀÁÿÿ ÿèíÒ&¾Àÿÿ ÿé÷Ì&Eÿÿ ÿéôÑ& ¸Àÿÿ ÿéóÎ&X&ÙØÿÿ ÿïóÏ&Àÿÿ ÿéóÏ&À'ÿÿ ÿèôÏ&&ÿÿ ÿëúÅ& ^ÿÿ ÿêóË&-ÿÿ ÿéóÊ&X©ÿÿ ÿé÷Ï&X'Qÿÿ ÿñòÇ&À0@ÿÿ ÿèóÐ&ÀŒÿÿ ÿêõÏ& &‡ÿÿ ÿéóÅ&Šÿÿ ÿçøÏ&=ÿÿ ÿéòÃ&*Çÿÿ ÿèôÆ&Àéÿÿ ÿéòÎ&X%>ÿÿ ÿéñÆ&Àòÿÿ ÿëóÆ&À(6ÿÿ ÿé÷Ñ&À,Íÿÿ ÿéóÏ&^0nÿÿ ÿêóÏ&X(5ÿÿ ÿéêÉ&(9ÿÿ ÿèõÐ&ÀLÿÿ ÿñóÄ&Xìÿÿ ÿæôÎ&-ÿÿ ÿéóÐ&³Àÿÿ ÿèôÐ&À>ÿÿ ÿèñÇ& &&e7Cÿÿ ÿéòÐ&X*ÿÿ ÿèõÏ&/>ÿÿ ÿéëÏ&à& 7Dÿÿ ÿêöÐ&X&âIÿÿ ÿéðÐ&ÀQÿÿ ÿçõÑ&Žÿÿ ÿçõÓ&X)@ÿÿ ÿéñÅ&¼Xÿÿ ÿéóÄ&.Mÿÿ ÿéöÈ&7Eÿÿ ÿçôÏ&ªXÿÿ ÿéöÏ&.V ÿÿ ÿêøÅ&Xüÿÿ ÿéòÐ&X%Ðÿÿ ÿéóÑ&!Xÿÿ ÿèòÏ&X²ÿÿ ÿéóÎ&À&Çÿÿ ÿëõÆ&X)Mÿÿ ÿéíÃ&X°ÿÿ ÿèöÎ&X»ÿÿ ÿé÷Î&(;Xÿÿ ÿéöÏ&"HXÿÿ ÿé÷Ä&ŒÀÿÿ ÿéõÑ&ýXÿÿ ÿèðÐ&iXÿÿ ÿèôÏ&ÛXÿÿ ÿéëÇ&À¾ÿÿ ÿéóÐ& OXÿÿ ÿéöÏ& .íÿÿ ÿéñÉ&.ï.îÿÿÿìòÆ&-9ÿÿÿîñÈ&)N-ÿÿ ÿéòÏ& YXÿÿ ÿçóÏ&X&ú-ÿÿ ÿëôÏ&X[ÿÿ ÿéóÇ&!ÿÿ ÿéòÎ&X.Xÿÿ ÿéíÈ& ˆXÿÿ ÿèïÇ&X!ÿÿ ÿéöÎ&X"Yÿÿ ÿîòÏ&À"žÿÿ ÿïóÉ&8Ôÿÿ ÿéöÐ& ßÿÿ ÿéóÐ&X"÷ÿÿ ÿéõÏ&X£ÿÿ ÿéòÐ&<^ÿÿ ÿèòÐ&X7Fÿÿ ÿéòÐ&#£ ÿÿ ÿéõÏ&"îÿÿ ÿçõÉ&Yÿÿ ÿéôÐ&#XXÿÿ ÿéöÄ& ‹Xÿÿ ÿèòÌ&Ÿÿÿ ÿéñÏ&¿Àÿÿ ÿéîÏ&J&K ÿÿ ÿè÷Ï&!ßXÿÿ ÿéöÏ&Xÿÿ ÿèóÏ&Àˆÿÿÿè÷Æ&--ÿÿ ÿéñÏ&%Xÿÿ ÿéöÏ&Nÿÿ ÿêðÏ&$|Àÿÿ ÿêòÏ&)OXÿÿ ÿðôÅ&ÀÉÿÿ ÿèõÏ&$nXÿÿ ÿêðÇ&#wXÿÿÿíëÏ& 7Gÿÿ ÿéñÇ&ÿÿ ÿéôÑ&Рÿÿ ÿéõÆ&$Xÿÿ ÿèñÑ&0— ÿÿ ÿéôÑ&ŒÀÿÿ ÿèôÏ&(‚Xÿÿ ÿèõÐ& Xÿÿ ÿíóÐ&Àåÿÿ ÿêôÅ&"†ÿÿ ÿéòÏ&X'mÿÿ ÿéóÏ& /ÿÿ ÿéôÏ&X7Hÿÿ ÿéóÏ&X+ÿÿ ÿêöÐ& Eÿÿ ÿïóÏ&#_ÿÿÿìóÌ&-&k7Iÿÿ ÿéòÏ&X-<ÿÿ ÿêöÑ&X%Øÿÿ ÿïöÍ&X7Jÿÿ ÿéðÑ& (Fÿÿ ÿéõÑ&X#€ÿÿ ÿé÷Í&X(Eÿÿ ÿéõÐ& 0^ÿÿ ÿèõÐ& &ÿÿ ÿëòÏ&X&ú7Kÿÿ ÿéòÏ&X Þÿÿ ÿéôÑ&Xnÿÿ ÿæïÇ& ,(ÿÿ ÿêõÐ& &-7Lÿÿ ÿîöË& %ÿÿ ÿêíÑ&X'‚ÿÿ ÿéóÌ&X%íÿÿÿìòÏ&ð&-7Mÿÿ ÿéôÏ& *Òÿÿ ÿçìÇ&X%êÿÿ ÿçòÏ&X%âÿÿ ÿêóÐ&X&¤ÿÿ ÿéóÉ&X)Pÿÿ ÿêòÏ&^üÿÿ ÿíõÍ&Þ& 7Nÿÿ ÿêôÇ&X7Oÿÿ ÿèôÐ&Xýÿÿ ÿèõÐ& &7Pÿÿ ÿèôÏ& 'Šÿÿ ÿíòÑ&-.ÿÿ ÿêôÐ& %ðÿÿ ÿèôÏ& -ÿÿ ÿéøÊ&À¡ÿÿ ÿéðÏ&X'ÿÿ ÿíõÇ& &!Þÿÿ ÿéõÔ&X'‹ÿÿ ÿéõË&¤ÿÿ ÿíôÉ& 7Qÿÿ ÿíõÌ& &Þ!¦ÿÿ ÿêôÍ&- ÿÿ ÿîôÏ&X%ôÿÿ ÿéóÑ&÷& 7Rÿÿ ÿçóÑ&¡ÿÿ ÿéóÏ&X%õÿÿ ÿèôÉ&.|Xÿÿ ÿìôÌ&-7Tÿÿ ÿéõÑ&5ÿÿ ÿéøÏ& )Qÿÿ ÿéðÊ&X%óÿÿ ÿéõÏ&X!×ÿÿ ÿíôÏ&X.zÿÿ ÿéóÆ& %hÿÿ ÿèóÐ& @ÿÿ ÿèòÍ&X%[ÿÿ ÿèõÑ&X/üÿÿ ÿíõÐ& &Þ7Uÿÿ ÿéöÐ&H ÿÿ ÿçôÏ&0 ÿÿ ÿèòË&X-ÿÿÿêôË&-7Vÿÿ ÿéñÑ& %÷ÿÿ ÿéîÏ& 7Wÿÿ ÿéóÏ&O ÿÿ ÿçöÏ& 7Xÿÿ ÿçõÐ& ‡ÿÿ ÿèöÐ& &07Yÿÿ ÿçòÏ&(¼ÿÿ ÿçõÏ&¼7[ÿÿ ÿçøÏ&¼7\ÿÿ ÿçøÏ&EüÿÿÿçõÏ&GFÿÿ ÿç÷Í&z)Rÿÿ ÿéóÐ&u&wvÿÿ ÿè÷Ñ&)S)Rÿÿ ÿçûÏ&! 7]ÿÿ ÿéóÎ&xÿÿ ÿêõÄ&)Vÿÿ ÿéóË&xyÿÿ ÿéöÏ&ynÿÿÿèõÏ&›1 ÿÿÿëõÄ&+·ÿÿ ÿéôÆ&x! ÿÿ ÿéóÉ&xïÿÿÿéõÏ&7^ÿÿÿêõÏ&_aÿÿ ÿéöÎ&mnÿÿ ÿêõÑ&*)™ÿÿ ÿèõÏ& 1 ÿÿÿéõÆ&Ýÿÿ ÿéöÑ&)T)UÿÿÿêõÐ&.ÿÿ ÿèöË&.ÝnÿÿÿéöÐ&)Xnÿÿ ÿêöÏ&)Y)Tÿÿ ÿçöÏ&- nÿÿ ÿèöÐ&)W"½ÿÿÿêõÑ&  ÿÿ ÿëõÉ& —+ÿÿÿéõÉ& ˜ÿÿÿçñÏ&& øÿÿ ÿêöÐ&.á)Tÿÿ ÿéöÑ&)[)WÿÿÿêöÄ&)WšÿÿÿçñÏ&&ÿÿ ÿéöÏ&)T)ZÿÿÿêöÏ&.7_ÿÿ ÿêõÎ&%‡0ÿÿÿéõÏ&µ+ÿÿ ÿêöÎ&)\)Wÿÿ ÿêõÑ&..ÿÿ ÿéöÒ&)])WÿÿÿêõÑ&-,ÿÿÿêõÉ&/.ÿÿ ÿèöÐ&0)WÿÿÿçòÏ&+¶7`ÿÿ ÿéóÏ&5&Nÿÿ ÿéöË&nÿÿ ÿêóÎ&³´ÿÿÿéíÒ&)`)^ÿÿ ÿòòÎ&%Fÿÿ ÿêëÎ&-!ÿÿÿêöÎ&)_%GÿÿÿèòÂ&~ÿÿ ÿèåÑ&€ÿÿÿêìÑ&ƒ&‚ÿÿ ÿêâÆ&ÿÿÿèóÂ&qoÿÿÿéíÆ&rÿÿÿèíÐ&FÅÿÿÿèÞÐ&)áÆÿÿÿðóÏ&p´ÿÿ ÿçóÆ&&¿-"ÿÿÿéâÍ&)â7eÿÿÿéìÐ&%5%4ÿÿ ÿéóÐ&ÂÃÿÿÿêïÎ&G*ØÿÿÿêòÎ&F¾ÿÿÿéöÎ&ÄFÿÿÿéõÏ&FEÿÿÿèôÏ&GÿÿÿèöÏ&F¥ÿÿÿéöÏ&{ÿÿ ÿêèÏ& µïÿÿÿèõÏ&(á)àÿÿÿèóÏ&ÿÿÿéåÏ&)ß7fÿÿ ÿè÷Ñ&M7gÿÿÿéöÐ&{³ÿÿÿèðÐ&-1+#ÿÿ ÿéõÐ&ÀÁÿÿÿèôÉ&-07hÿÿ!ÿéÞÐ&-))ßÿÿ ÿè÷Ð&-2äÿÿÿéóÐ&GNÿÿÿéõÐ&ÿÿÿïôÏ&Gôÿÿ ÿêõÈ&ƒ„ÿÿÿé÷Ï&®ÿÿ ÿèõÎ&-3Mÿÿ ÿèõÑ&ä7jÿÿÿêõÏ&GdÿÿÿçõÑ&{ ÿÿ ÿè÷Ñ&äåÿÿÿíîÈ&„7lÿÿÿéôÏ&|{ÿÿÿéöÇ& ×{ÿÿÿéóÎ&&ÇÿÿÿéöÐ&"IÿÿÿîñÏ&!aÿÿÿíóÏ&´ÿÿÿéôÎ&*Ñÿÿ ÿéôÈ& 7rÿÿÿéóÏ&0ÄÿÿÿîòÍ&& ÿÿ ÿèöÏ&?ÿÿ ÿèöÐ&?&ÿÿ ÿéóÐ&ˆ-#ÿÿ ÿèðÑ&‚sÿÿ ÿèðÑ&‚1ÿÿ ÿèðÑ&‚7ÿÿ ÿèðÑ&‚•ÿÿ ÿèðÑ&‚8ÿÿÿèóÒ&9}ÿÿ ÿèõÑ&‚7vÿÿ ÿèðÑ&‚7wÿÿ ÿèñÑ&‚7xÿÿ ÿèðÑ&‚7yÿÿ ÿèðÑ&‚7zÿÿ ÿèöÑ&%ù‚ÿÿ ÿèðÑ&‚7ÿÿ ÿèôÑ&‚7{ÿÿ ÿèòÑ&¦‚ÿÿ ÿèôÑ&‚7|ÿÿÿèóÒ&}:ÿÿ ÿèôÑ&‚7}ÿÿ ÿçðÑ&‚7~ÿÿ ÿèñÑ&‚7ÿÿ ÿèõÑ&‚7€ÿÿ ÿèóÑ&‚7ÿÿ ÿèöÑ&‚7‚ÿÿÿèóÒ&}7ƒÿÿ ÿèñÑ&‚7„ÿÿ ÿèðÑ&§‚ÿÿÿéòÒ&€7…ÿÿ ÿèñÑ&‚7†ÿÿÿèôÒ&}éÿÿ ÿèóÑ&‚7‡ÿÿ ÿèóÑ&‚7ˆÿÿÿèóÒ&}&´ÿÿÿæ÷Ò&}~ÿÿÿèóÒ&}7‰ÿÿ ÿèôÑ&‚7Šÿÿ ÿçðÑ&u‚ÿÿ ÿèðÑ&‚7‹ÿÿ ÿèðÑ&‚7ŒÿÿÿèóÒ&}7ÿÿÿèóÒ&}7Žÿÿ ÿèðÑ&‚7ÿÿÿèóÒ&}ÿÿ ÿèñÑ&‚7ÿÿ ÿèðÑ&‚‘ÿÿ ÿèóÑ&‚7‘ÿÿÿèôÒ&}_ÿÿÿèóÒ&}ÿÿ ÿèðÑ&‚7’ÿÿÿèôÒ&}9ÿÿ ÿèðÑ&‚Ÿÿÿ ÿèðÑ&‚7“ÿÿÿèóÒ&}uÿÿ ÿèñÑ&‚7”ÿÿÿèóÒ&}»ÿÿÿèóÒ&}äÿÿÿèôÒ&}Gÿÿ ÿèðÑ&‚ãÿÿÿèóÒ&}åÿÿ ÿèóÑ&÷‚ÿÿ ÿèóÑ&­‚ÿÿÿèóÒ&}7•ÿÿ ÿèðÑ&‚7–ÿÿÿèóÒ&}7—ÿÿÿèóÒ&}7˜ÿÿ ÿèðÑ&‚7™ÿÿÿèòÒ&€7šÿÿÿèóÒ&}7›ÿÿÿèóÒ&}7œÿÿÿèóÒ&}7ÿÿÿèóÒ&}7žÿÿÿèóÒ&}7ŸÿÿÿèõÒ&}7 ÿÿÿèóÒ&}7¡ÿÿ ÿèôÑ&‚7¢ÿÿ ÿèõÑ&‚7£ÿÿÿèóÒ&}7¤ÿÿ ÿèðÑ&‚7¥ÿÿ ÿèðÑ&k‚ÿÿ ÿèðÑ&ë‚ÿÿÿèóÒ& P}ÿÿÿèúÒ&€ÿÿ ÿèõÑ&‚7¦ÿÿ ÿèðÑ&‚7§ÿÿ ÿèõÑ&‚7¨ÿÿ ÿèñÑ&«‚ÿÿ ÿèöÑ&‚7©ÿÿÿèóÒ&}7ªÿÿ ÿèòÑ&‚7«ÿÿ ÿèòÑ&‚7¬ÿÿ ÿèðÑ&‚7­ÿÿ ÿèðÑ&ë‚ÿÿ ÿèðÑ&‚7®ÿÿÿéòÒ&€7¯ÿÿÿèôÒ&}7°ÿÿÿè÷Ò&}7±ÿÿÿèóÒ&}oÿÿÿèôÒ&}7²ÿÿÿèóÒ&}ÕÿÿÿèóÒ&#}ÿÿÿèóÒ&}#ÿÿÿèõÒ&}7³ÿÿÿéúÒ&€#àÿÿÿèóÒ&}& µ ¶ÿÿÿèóÒ&}"ÿÿÿèóÒ&}4ÿÿÿçóÒ&}7´ÿÿÿèóÒ&}7µÿÿÿçôÒ&}"°ÿÿÿèóÒ&}7¶ÿÿÿçóÒ&} ŽÿÿÿèóÒ&!Æ}ÿÿÿèôÒ&}7·ÿÿÿèôÒ&}#·ÿÿÿèôÒ&}"‡ÿÿÿèõÒ&}7¸ÿÿÿèöÒ&}7¹ÿÿÿèôÒ&}$ŠÿÿÿèóÒ&}7ºÿÿÿéöÒ&€& Ú ÛÿÿÿéõÒ&€ÌÿÿÿèõÒ&}7»ÿÿÿçòÓ&}7¼ÿÿÿèóÒ&€7½ÿÿÿèôÒ&}7¾ÿÿÿèóÒ&}7¿ÿÿ ÿèòÑ&‚#ÿÿ ÿèôÑ&‚7ÀÿÿÿéóÒ&€7ÁÿÿÿèóÒ&}7Âÿÿÿè÷Ò&}#DÿÿÿèõÒ&}7ÃÿÿÿèôÒ&}7ÄÿÿÿèóÒ&}7ÅÿÿÿèöÒ&}7ÆÿÿÿèõÒ&}7ÇÿÿÿéöÒ&€7ÈÿÿÿèõÒ&}7ÉÿÿÿæõÒ&}7ÊÿÿÿèòÒ&€7ËÿÿÿèôÒ&}7ÌÿÿÿèóÒ&}7ÍÿÿÿèóÒ&}7ÎÿÿÿçòÓ&}7ÏÿÿÿèôÒ&}7ÐÿÿÿèôÒ&}%ÿÿÿéöÒ&€7ÑÿÿÿçôÒ&}7ÒÿÿÿæóÒ&}7ÓÿÿÿèöÒ&}7ÔÿÿÿéôÒ&€7ÕÿÿÿçòÓ&}7ÖÿÿÿéòÒ&€7×ÿÿÿçõÓ&}7ØÿÿÿèòÒ&€7ÙÿÿÿèóÒ&}7ÚÿÿÿèôÒ&}7ÛÿÿÿèòÒ&€7ÜÿÿÿèôÒ&}7ÝÿÿÿçöÓ&}7ÞÿÿÿèôÒ&€7ßÿÿÿèõÒ&}7àÿÿÿèôÒ&}7áÿÿÿèòÒ&€7âÿÿÿçòÓ&}7ãÿÿÿè÷Ò&}7äÿÿÿèôÒ&}7åÿÿÿèóÒ&}7æÿÿÿèôÒ&}7çÿÿÿçøÓ&}7èÿÿÿèôÒ&€%wÿÿÿçöÓ&}7éÿÿÿçóÓ&}7êÿÿÿéôÒ&€7ëÿÿÿçòÓ&}7ìÿÿÿéõÒ&€7íÿÿÿèóÒ&}7îÿÿÿçòÓ&}7ïÿÿÿçöÓ&}7ðÿÿÿçôÓ&}7ñÿÿÿæôÒ&}&‘7òÿÿÿçôÓ&}7óÿÿÿçòÓ&}7ôÿÿÿç÷Ó&}7õÿÿÿèóÒ&}7öÿÿÿçòÓ&}7÷ÿÿÿçôÓ&}7øÿÿÿæóÒ&}&‘7ùÿÿÿçöÓ&}7úÿÿ ÿçöÏ&ƒ7üÿÿ ÿîöÏ&ƒ7ýÿÿ ÿçöÏ&ƒ&…„ÿÿÿìðÐ&œ-%ÿÿ'ÿìèÐ&T‹ÿÿÿéðÒ&-$ÿÿ ÿéðÐ&‹ÿÿ ÿêñÐ&ŒÿÿÿéìÏ&Y%6ÿÿ"ÿèòÏ&-&7ÿÿÿïðÐ&-'-(ÿÿÿèõÏ&‘ÿÿÿìöÎ&D-,ÿÿÿçõÏ&’Yÿÿ ÿçõÐ&-)7ÿÿÿÿèôÐ&PÿÿÿéóÏ&æ“ÿÿÿéóÐ&-)8ÿÿÿíôÎ&’,”ÿÿÿé÷Ï&c“ÿÿÿêóÎ&E’ÿÿÿéïÁ&ÿÿÿëõÏ&’’ÿÿ ÿéïÎ&-*ÜÿÿÿîòÏ&L-,ÿÿÿéëÐ&-)8ÿÿÿéòÐ&% -,ÿÿÿèöÒ&îíÿÿÿèõÑ&-+’ÿÿÿêòÏ&’üÿÿÿèõÐ&&-,8ÿÿÿèóÐ&+R-,ÿÿÿêòÐ&¨-.ÿÿÿéòÎ&- -,ÿÿÿçóÐ& ã!,ÿÿ ÿéóÐ&-4ûÿÿ ÿéôÐ&®-5ÿÿÿéôÏ&­®ÿÿ ÿéõÏ&%°†ÿÿÿéõÏ&+¬†ÿÿ ÿéõÏ&† ýÿÿ ÿéõÏ&†! ÿÿÿçôÐ&!! ãÿÿ ÿéõÑ&†%¯ÿÿ ÿéõÏ&†8ÿÿ ÿéõÐ&%±†ÿÿ ÿéõÐ&%²%³ÿÿ ÿîóÅ& ö8ÿÿ ÿîóÅ& ö8ÿÿ ÿîóÄ& ø8ÿÿ ÿîóÐ& ûÿÿ ÿîóÏ&Ù ÷ÿÿ ÿîóÈ&r øÿÿ ÿîóÐ& ÷ÿÿ ÿîóÒ& ö8ÿÿ ÿîóÐ& ø8 ÿÿ ÿîóÐ&$+ ÷ÿÿ ÿîóÓ& ø8 ÿÿ ÿîóÏ& û&ïîÿÿ ÿîóÏ& û8 ÿÿ ÿîõÏ& ø8 ÿÿ ÿîóÏ&ñ&ð øÿÿ ÿîóÏ&ò øÿÿ ÿîóÐ&Å øÿÿ ÿîóÏ&-&, øÿÿ ÿîóÐ&Æ øÿÿ ÿîóÒ&8 ÿÿ ÿîóÑ&‹ øÿÿ ÿîóÐ&•& öÇÿÿ ÿîôË& ùÿÿ ÿîóÐ& ú ÿÿ ÿîóÌ& ø8ÿÿ ÿîóÏ& ù8ÿÿ ÿîóÐ&Ð øÿÿ ÿîõÏ&;&ÿÿ ÿîóÏ&Èÿÿ ÿîôÓ&Ô8ÿÿ ÿîôÏ&Ó& øÒÿÿ ÿîôÏ&…&" úÿÿ ÿîóÒ&Ñ øÿÿ ÿîóÑ& ú8ÿÿÿêóÄ&ÑtÿÿÿéóÄ& ߨÿÿÿéðÀ&tÂÿÿÿðóÏ& ßÿÿÿêòÎ& ßÿÿÿêîÏ&t*ÚÿÿÿééÏ&¸!pÿÿÿìõÏ& à!qÿÿÿêóÊ& ß!rÿÿÿêóÏ& ß!sÿÿÿéôÎ&Ú ßÿÿÿçõÄ& ß!vÿÿÿêðÁ& ß!nÿÿÿèõÏ&‘0 ÿÿÿèôÏ& à(/ÿÿÿëôÏ& à"ÿÿÿíõÍ&n ßÿÿÿòôÇ& ß…ÿÿÿòôÂ& ß!mÿÿÿéïÏ& ß& ó!IÿÿÿéìÏ& ß!wÿÿÿçóÐ& ß%ŸÿÿÿèöÐ& ßÿÿÿéòÐ&t0öÿÿ ÿêîÐ&%H)bÿÿÿé÷Ï&S0¡ÿÿÿêòÏ&tSÿÿÿéóÅ& ß'ìÿÿ ÿéöÐ&2-6ÿÿÿèõÏ& ß+ÿÿÿêðÇ&Ã0¡ÿÿ ÿéôÏ&ƒ2ÿÿÿíöÌ&‡1ÿÿÿéêÄ&j ßÿÿÿê÷Ï&t³ÿÿÿéóÏ& ßÔÿÿÿïóÏ& ß~ÿÿÿéôÏ&0 ]ÿÿÿè÷Ï& à)ÿÿÿéòÆ&0 ßÿÿÿêêÏ&µ ßÿÿÿêíÑ& àžÿÿÿèíÏ& ßÿÿÿïóÈ& ßÿÿ ÿéõÐ&ñ8ÿÿÿèòÐ& ßÖÿÿÿèöÎ&Ö ßÿÿÿé÷Ì&B‡ÿÿÿéðÏ& ß¶ÿÿÿêõÉ& ß~ÿÿÿéòÐ&‡÷ÿÿÿéñÈ&8ÿÿÿéóÑ&‡!ÿÿÿçõÑ&  ßÿÿÿéêÐ&c ßÿÿÿéôÈ&‡/ÿÿÿëõÏ& ß’ÿÿÿèóÐ&Œ0 ÿÿÿééÉ& ßÿÿÿé÷Ï&‡'QÿÿÿêøÑ& 0 ÿÿÿëñÆ& ßLÿÿÿéòÐ& à*ÿÿÿêîÈ& àßÿÿÿéöÏ& ßÿÿÿñõÏ&)z ßÿÿÿèôÏ& àÛÿÿÿéóÏ& à ÿÿÿéöÏ&"H àÿÿÿèôÏ& à'ØÿÿÿçõÏ&Þ0¡ÿÿÿìôÊ&'×0 ÿÿÿéóÏ& ß'°ÿÿÿéóÏ& à¤ÿÿÿéóÏ& à)aÿÿÿéóÏ&1 ßÿÿÿìõÏ&‡'ÙÿÿÿïòÏ&‡£ÿÿÿéôÐ& ߟÿÿÿêôÐ&)b8ÿÿÿéôÏ&'¯ àÿÿÿïñÍ&‡)cÿÿÿêóÐ&‡)dÿÿÿèöÏ& à `ÿÿÿé÷Ä&Œ ßÿÿÿçòÈ&ÇÆÿÿÿèóÏ&+­0 ÿÿÿéñÎ& à(AÿÿÿéëÇ&¾ àÿÿÿéöÏ& àÿÿÿéóÏ&#\&)e8ÿÿÿéóÏ& ß+ÿÿÿéóÊ&"0 ÿÿÿèõÍ& à"|ÿÿ ÿêôÐ&#b&#c)bÿÿÿéõÈ& ß";ÿÿ ÿçõÏ&)f)gÿÿÿèòÍ&)h0¡ÿÿÿéòÏ&‡0¢ÿÿÿè÷Ï&‡&"Î%ýÿÿÿëòÎ&ú&ù‡ÿÿÿéõÏ&‡*oÿÿ ÿèõÑ&T8ÿÿÿçóÐ&‡æÿÿÿìôÎ&‡*qÿÿÿèôÏ&0 0£ÿÿÿêðÑ&‡‡ÿÿ ÿèóÊ&$ÅÿÿÿèóÇ& á àÿÿÿéôÏ&‡‚ÿÿÿéôÑ&‡nÿÿÿéòÏ&tÿÿÿèõÏ&£‡ÿÿÿëîÏ& à)jÿÿÿêöÐ&‡)kÿÿÿçôÐ& ß1ÿÿÿéðÑ&‡)lÿÿÿéòÏ& Þ‡ÿÿÿéóÉ&†‡ÿÿÿðóÏ& à¦ÿÿÿèøË&,c&0¡8ÿÿÿéñÏ&n&M&)nÿÿÿìõÏ& à/ÿÿÿéôÑ&û)eÿÿÿêõÏ&%è‡ÿÿÿêóÐ&‡&¤ÿÿÿéïÊ&0 8ÿÿÿîöË& ßÿÿÿéóÉ& ß%ãÿÿÿèðÎ&.\.xÿÿÿéôÏ&‡'ãÿÿÿèõÐ&‡&8 ÿÿÿèôÏ&‡'äÿÿÿçìÇ&.x%êÿÿÿíóÐ&‡'¾ÿÿÿéöÐ&‡.#ÿÿÿéôÎ&‡%'ÿÿÿçôÏ&‡'|ÿÿÿéîÊ&‡&ï8!ÿÿÿèôÏ&‡%òÿÿÿéöÒ&‡*€ÿÿÿéòÐ&0¡0¥ÿÿÿéöÑ&‡&ÄÿÿÿéòÑ&W)eÿÿ ÿéðÌ&-¹8"ÿÿÿéøÊ& ß¡ÿÿÿèõÐ&‡¢ÿÿÿéñÏ& ß(ÿÿÿçóÑ& ß¡ÿÿÿéóÏ&‡8#ÿÿÿèôÑ&%„0 ÿÿÿèõÑ& à/üÿÿÿçóÑ& à8$ÿÿÿèñÈ&.x,ÀÿÿÿéöÐ&}‡ÿÿÿéñÏ&‡)mÿÿ ÿéðÓ&)n…ÿÿ ÿéñÒ&/ý8%ÿÿÿæðÑ&‡)oÿÿÿèïÉ&ô&.y8&ÿÿÿéðÌ&/Œ0åÿÿ ÿèõÏ&!C)rÿÿ ÿéóÏ&)rÿÿÿéóÐ&ö8*ÿÿ ÿëêÏ&)p*uÿÿ ÿéèÐ&‹&/6ÿÿ ÿéõÏ&Š`ÿÿ ÿëôÏ&‹žÿÿ ÿèöÏ&Šáÿÿ ÿëôÏ&Š&Aÿÿ ÿëôÏ&Šøÿÿ ÿèõÑ&Љÿÿ ÿéõÏ&Š%´ÿÿ ÿêîÑ&Š'ÿÿ ÿéõÏ&Š!×ÿÿ ÿèôÏ&/¢/þÿÿ ÿêõÂ&»ªÿÿ ÿéôÆ&Ã)qÿÿ ÿéóÄ&Ã&ÿÿÿéõ¿&g´ÿÿÿìóÌ&gýÿÿ ÿêòÎ&Ãÿÿ ÿñóÁ&ÃÊÿÿÿñðÎ&g)uÿÿ ÿèöÁ&»ÿÿ ÿêóÐ&¿@ÿÿÿéíÇ&g%9ÿÿ ÿèïÆ&Ã;ÿÿ ÿèôÏ&@(/ÿÿ ÿéóÅ&p»ÿÿÿéöÏ&C)sÿÿ ÿéöÎ&ÃÄÿÿ ÿèîÏ&)t8+ÿÿÿéíÏ&C&+À8,ÿÿ ÿéõÑ&²Ãÿÿ ÿèöÏ&çÿÿÿêôÎ&¿gÿÿ ÿéóÆ&‡»ÿÿÿéñÅ&gyÿÿÿìõÍ&gÿÿ ÿèõÇ&»Uÿÿ ÿéôÅ&ÃÿÿÿéöÏ&giÿÿ ÿéôÐ&Ã*rÿÿÿèðÏ&·Cÿÿ ÿìõÏ&Ãÿÿ ÿæõÇ&@)IÿÿÿçöÇ&Ì+dÿÿÿéóÏ&*s¸ÿÿ ÿéóÐ&ÃUÿÿ ÿñõÆ&Ãÿÿ ÿñòÁ&Ã&ÿÿÿéóÁ&C¶ÿÿ ÿîóÏ&òÿÿÿèóÏ&gîÿÿ ÿèôÏ&63ÿÿÿéòÏ&gNÿÿÿéòÏ&/’CÿÿÿéóÐ&*s0ÒÿÿÿèôÂ&*sÿÿ ÿñóÏ&Ã'ËÿÿÿñóÐ&g)vÿÿÿê÷Ï&g³ÿÿ ÿèôÐ&)t0‰ÿÿ ÿéöÏ&Ã8-ÿÿ ÿéóÅ&ÃÿÿÿçõÏ&FgÿÿÿíôÐ&øCÿÿÿéò¿&Îgÿÿ ÿèóÏ&@ÿÿ ÿèôÏ&Ã)wÿÿ ÿéïÐ&ÃRÿÿÿñòÇ&C)xÿÿ ÿèòÏ&Ã-ºÿÿ ÿèóÅ&»lÿÿ ÿèóÏ&@)tÿÿ ÿïõÐ&Ã8.ÿÿÿêòÏ&g ÿÿ ÿêõÎ&»¹ÿÿ ÿéëÏ&ôÿÿÿéöÏ&˜CÿÿÿïóÏ&*s~ÿÿÿéìÐ&CÝÿÿÿéóÏ&g0Öÿÿ ÿéóÏ&Ãÿÿ ÿêõÑ&»2ÿÿ ÿéõÐ&Ãÿÿ ÿêõÉ&ÃHÿÿÿèïÏ&C&¡ ÿÿÿëóÆ&Cæÿÿ ÿéìÈ&ÃaÿÿÿéòÇ&ÒCÿÿ ÿéöÇ&Ã|ÿÿÿæöÏ&C³ÿÿ ÿæôÏ&Ãÿÿ ÿîòÄ&éÿÿ ÿéêÐ&Öÿÿ ÿêóÍ&ÃEÿÿ ÿèõÐ&ÃLÿÿÿéöÐ&C(lÿÿÿèôÏ&C Ùÿÿ ÿèôÏ&@0Ýÿÿ ÿéóÏ&Ã)yÿÿ ÿèñÏ&0e)tÿÿÿé÷Ï&gcÿÿ ÿèôÐ&>@ÿÿ ÿæöÆ&»AÿÿÿðóÍ&0gÿÿ ÿéíÏ&?Ãÿÿ ÿèôÆ&Ãrÿÿ ÿèóÏ&)t0ÊÿÿÿêîÈ&Cßÿÿ ÿéïÌ&ÃWÿÿ ÿéöÏ&Öÿÿ ÿñòÌ&Ã)ÇÿÿÿéõÏ&*t&.œÿÿ ÿêøÅ&»){ÿÿ ÿèñÈ&0Í)tÿÿÿéíÉ&ÉÌÿÿ ÿïôÏ&Ã)|ÿÿÿñóÐ&gÉÿÿ ÿéóÑ&Ã"ÿÿ ÿéóÏ&Ã'ÿÿÿéöÏ&.í*tÿÿÿéîÇ&ìCÿÿ ÿèñÐ&•&)}80ÿÿÿéôÑ&9Ìÿÿ ÿéëÇ&@¾ÿÿÿé÷Ä&ŒCÿÿÿêòÆ&*sÿÿÿèòÏ&C.ÿÿ ÿéóÏ&ÃÿÿÿèôÐ&*s]ÿÿ ÿéõÐ&ËÿÿÿéõÐ&C)ÿÿ ÿéóÎ&Ã&Çÿÿ ÿéøÏ&à $ÿÿÿéöÏ&C"HÿÿÿçôÏ&CªÿÿÿéïÇ&%­Cÿÿ ÿéõÐ&Ã#¡ÿÿÿåóÏ&-7*sÿÿ ÿè÷Ð&à /ÿÿ ÿèîÉ&-8)}ÿÿÿèõÎ&°*sÿÿÿéðÑ&C/ƒÿÿ ÿèóÏ&Ã/×ÿÿ ÿéñÆ&Ã!Œÿÿ ÿéðÈ&Ã’ÿÿÿèõÏ&g!áÿÿ ÿçíÐ&»:ÿÿ ÿèîÐ&)t81ÿÿ ÿèóÆ&Ã/®ÿÿ ÿèõÈ&Ã^ÿÿÿïóÎ&CÙÿÿÿéôÏ&C&##(ÿÿÿéöÄ& ‹Cÿÿ ÿçõÉ&Ã!0ÿÿ ÿéñÏ&ÂÃÿÿÿïñÏ&Ì,£ÿÿ ÿéöÐ&@!ÿÿÿéïÑ&C!ÁÿÿÿéóÏ&cCÿÿÿñôÏ&C"hÿÿ ÿéóÏ&» fÿÿ ÿîòÏ&Ã" ÿÿÿêðÏ&g$­ÿÿ ÿéòÐ&Ã$Îÿÿ ÿéñÇ&Ã"Ûÿÿ ÿéóÈ&@$ËÿÿÿèóÐ&CDÿÿÿéóÌ&$›Ìÿÿ ÿèòÐ&»$ÿÿÿéðÑ&C$¦ÿÿÿéïÆ&C#'ÿÿ ÿéôÑ&»#øÿÿÿéóË&Ì'nÿÿ ÿíòÐ&»2ÿÿ ÿèóÎ&)~ÃÿÿÿêðÑ&C‡ÿÿÿèôÐ&Cÿÿ ÿèõÐ&@ÿÿ ÿèòÑ&)&¸ºÿÿÿîòÏ&ÌLÿÿÿêôÍ&C/…ÿÿÿîõÏ&C)iÿÿÿëöÐ&C/°ÿÿÿéóÏ&C*ÿÿ ÿèóÏ&)82ÿÿÿèõÐ&Ì&83ÿÿÿéíÏ&C-9ÿÿÿé÷Í&(ECÿÿ ÿèñÏ&Ã*ÿÿÿéñÏ&*&#Q*tÿÿÿèóÐ&Ì*ÿÿÿçõÑ&xCÿÿ ÿéôÇ&Ã)Ëÿÿ ÿèõÐ&Ã(ÿÿ ÿéõÑ&$84ÿÿÿé÷Ð&Ì/±ÿÿÿéòÏ&C-;ÿÿ ÿèóÐ&k&)}85ÿÿÿèôÉ&C86ÿÿÿèôÐ&ýÌÿÿÿéòÍ&C87ÿÿ ÿêïÑ&Ã'#ÿÿÿîöË&CÿÿÿéîÊ&C&ï88ÿÿÿéôÎ&Ì%'ÿÿÿéóÉ&C%ãÿÿÿçòÏ&C%âÿÿÿêòÏ&Cüÿÿ ÿéöÏ&»0ÛÿÿÿèôÎ&%(*tÿÿÿéòÈ&Cÿÿ ÿèõÊ&A89ÿÿÿéöÏ&*s'õÿÿÿéõÏ&CšÿÿÿéõÏ&C%åÿÿÿèõÐ&&*t8:ÿÿÿèöÑ&%î*tÿÿÿèõÐ&&Ì8;ÿÿÿéðË&C0·ÿÿÿéõÏ&/¦Cÿÿ ÿííÑ&8<ÿÿÿéñÏ&C*vÿÿÿèöÐ&C%µÿÿÿîôÈ&C ÿÿÿéòÏ&*t/§ÿÿÿéõÎ&,‹CÿÿÿçóÑ&¡CÿÿÿèöË&Ž*tÿÿÿìôÐ&C*xÿÿ ÿëöÐ&.¬8=ÿÿÿéðÊ&C%óÿÿÿéóÏ&C-=ÿÿÿéòÒ&$v&*t8>ÿÿÿéõÏ&+6ÌÿÿÿéôÐ&C8?ÿÿÿçòË&C8@ÿÿÿèôÑ&Ì%„ÿÿÿéóÆ&%o*tÿÿ ÿéîË&n%tÿÿÿèòÍ&C%[ÿÿÿéòÈ&C->ÿÿÿèóÐ&C)õÿÿÿèòÈ&.¥CÿÿÿèóÎ&*t8BÿÿÿèöÈ&Ì-?ÿÿ ÿéöÐ&¯%tÿÿÿéòÏ&*t8CÿÿÿéôÈ&Ì8DÿÿÿçõÐ&*t‡ÿÿÿéõË&-@*tÿÿ ÿéòÐ&21ÿÿ ÿéìÑ&±Þÿÿ ÿéõÐ&1yÿÿ ÿéìÑ&±Yÿÿ ÿéõÑ&±ÿÿ ÿéðÐ&2ÿÿ ÿéóÑ&±²ÿÿ ÿèöÑ&± ÿÿ ÿéõÑ&`±ÿÿ ÿè÷Ñ&±¬ÿÿ ÿèôÑ&4ÿÿ ÿèñÑ&ñ-Dÿÿ ÿéóÑ&±ßÿÿ ÿéòÑ&±0¬ÿÿ ÿèõÑ&±Uÿÿ ÿéîÑ&4œÿÿ ÿèòÑ&Î-Dÿÿ ÿéóÑ&K-Aÿÿ ÿéòÑ&±(-ÿÿ ÿéêÑ&j4ÿÿ ÿçòÑ&±Pÿÿ ÿéòÑ&²4ÿÿ ÿéõÑ&54ÿÿ ÿéõÑ&±ÿÿ ÿèóÑ&î-Dÿÿ ÿéóÑ&±Uÿÿ ÿéóÑ&±¸ÿÿ ÿéóÑ&±ÿÿ ÿéõÑ&3±ÿÿ ÿèìÑ&!w-DÿÿÿèïÏ&.§.¦ÿÿ ÿéêÑ&4(oÿÿ ÿèôÏ&ƒ4ÿÿ ÿèîÑ&4Xÿÿ ÿéõÑ&±ÿÿÿ ÿéïÑ&4Hÿÿ ÿéðÑ&4¶ÿÿ ÿé÷Ñ&KEÿÿÿëîÈ&µ*}ÿÿ ÿéöÑ&4˜ÿÿ ÿéõÑ&4Žÿÿ ÿèôÑ& ¸-Dÿÿ ÿåðÑ&£-Dÿÿ ÿéóÑ&4Eÿÿ ÿéöÑ&”4ÿÿ ÿè÷Ñ&'Ó-Dÿÿ ÿéôÑ&K.Kÿÿ ÿéôÑ&±|ÿÿ ÿèìÑ&K'®ÿÿ ÿèíÑ&4*ÿÿ ÿèöÑ&4»ÿÿ ÿé÷Ñ&41ÿÿ ÿéóÑ&K1ÿÿ ÿéóÑ&4*|ÿÿ ÿéóÓ&*~8Hÿÿ ÿëõÏ&*}&bcÿÿ ÿèëÑ&¾-Dÿÿ ÿè÷Ñ&Œ-Dÿÿ ÿéôÑ&4Òÿÿ ÿéóÑ&4!ÿÿ ÿéôÑ&4!"ÿÿ ÿéôÑ&4 ™ÿÿ ÿéîÑ&K!#ÿÿ ÿèóÐ&* gÿÿ ÿéíÑ&45ÿÿ ÿèòÑ&4Ÿÿÿ ÿéîÑ&4 xÿÿ ÿéõÑ&4"îÿÿ ÿçõÑ&KYÿÿ ÿéóÑ&4"ÿÿ ÿèóÑ&"ø-Dÿÿ ÿé÷Ñ&4"cÿÿ ÿéöÑ&4",ÿÿ ÿéïÑ&4ÿÿ ÿèòÑ&-C-Dÿÿ ÿçóÑ&4æÿÿÿçîÏ&ì&ê ÿÿ ÿéôÑ&4žÿÿ ÿéóÑ&$›Kÿÿ ÿéñÑ&4ÿÿ ÿéïÑ&4&œ›ÿÿ ÿéîÑ&I&JKÿÿ ÿéóÑ&*~8Iÿÿ ÿèõÑ&š-Dÿÿ ÿèôÑ&ýKÿÿ ÿéòÑ&4ÿÿ ÿéóÑ&4'ÿÿ ÿèöÒ&**€ÿÿ ÿéôÑ&4&!¦úÿÿ ÿéóÑ&4*‚ÿÿ ÿéöÑ&K'èÿÿ ÿéóÑ&-E0Âÿÿ ÿéòÑ&K->ÿÿ ÿéôÑ&K*ƒÿÿ ÿéñÑ&K*†ÿÿ ÿçõÑ&‡-Eÿÿ ÿç÷Ñ&i&-E8Jÿÿ ÿêñÐ&·ÿÿÿèñÐ&·8Pÿÿ ÿèôÐ&ß÷ÿÿ ÿçóÐ&;ÿÿ ÿéóÍ&ýÿÿ ÿéóÍ&&ÿÿ ÿéëÏ&-FÿÿÿéñÐ&¸·ÿÿÿé÷Ð& °%úÿÿÿéîÐ& °œÿÿÿéôÐ& °ÚÿÿÿéõÐ&n °ÿÿÿéôÐ& °”ÿÿÿèôÐ& °(/ÿÿ ÿéõÐ&,-Rÿÿ ÿéôÏ& Òÿÿ ÿé÷Ï&ÿÿ ÿéðÐ&Ã-Rÿÿ ÿéöÐ&!Jÿÿ ÿéëÏ&)ÿÿÿ ÿéõÍ&ÿÿÿéòÐ& °-Gÿÿ ÿçòÐ&Pÿÿ ÿéõÏ&kÿÿ ÿéóÍ&ÿÿ ÿéöÏ&±ÿÿÿèöÐ& °+ÿÿ ÿèóÏ&îÿÿÿéöÐ& °*ÿÿ ÿéôÍ&žÿÿ ÿèîÏ&%›ÿÿÿéòÐ& °0öÿÿ ÿçôÍ&%ÌÿÿÿèîÐ&* °ÿÿ ÿêõÏ& ÿÿ ÿéóÍ&&ÿÿ ÿéóÏ&0ÖÿÿÿéòÐ& °ÚÿÿÿèíÐ& °ÿÿÿéíÐ& °&  ÿÿ ÿéòÐ&*-RÿÿÿèõÐ& °LÿÿÿéóÐ& °³ÿÿÿçõÑ& ° ÿÿÿéöÐ& °ÿÿ ÿèìÍ&ÁÿÿÿéôÐ& °-Hÿÿ ÿèóÐ&Œÿÿ ÿéòÍ&0Aÿÿ ÿéöÐ&(l-Rÿÿ ÿéêÐ&–ÿÿ ÿéôÏ&bÿÿ ÿéóÐ&  "ÿÿ ÿéñÍ&+Mÿÿ ÿéîÐ&è-RÿÿÿéóÐ& °ÿÿÿéõÑ& ° ÿÿ ÿéóÑ&%%ÿÿÿèôÐ& °]ÿÿ ÿèöÎ&»ÿÿ ÿéòÎ&%©ÿÿÿéõÐ&‹-Iÿÿ ÿéîÐ&ì-Rÿÿ ÿèôÐ&'Ø-Rÿÿ ÿé÷Í&1ÿÿ ÿçôÑ&-J8RÿÿÿéëÐ& °¾ÿÿ ÿèöÐ&<-Rÿÿ ÿéöÐ&D!JÿÿÿéóÐ& °"øÿÿ ÿèïÐ&-R0¹ÿÿ ÿéíÍ&5ÿÿÿéôÐ& °!_ÿÿ ÿéöÐ&#Î-RÿÿÿéñÐ& °$]ÿÿÿéóÐ& °"­ÿÿ ÿéïÐ&8Sÿÿ ÿéøÐ&-R/²ÿÿ ÿéöÐ&^-Rÿÿ ÿéôÐ&ž-Rÿÿ ÿèôÐ&(‚-Rÿÿ ÿéóÎ&$¸ÿÿÿéõÑ& °—ÿÿ ÿéôÐ&-R8Tÿÿ ÿèñÔ&% 8Uÿÿ ÿéòÐ&% -Rÿÿ ÿèôÏ&ÿ8Vÿÿ ÿé÷Ð&-R8Wÿÿ ÿéöÐ&N-RÿÿÿéöÐ&û+!ÿÿ ÿè÷Ñ&-R8Xÿÿ ÿçõÑ&-K-Rÿÿ ÿéõÐ&Æ8Yÿÿ ÿèôÐ&.Z-RÿÿÿéõÐ&›ûÿÿ ÿé÷Ð&(E-Rÿÿ ÿéóÐ&-L)åÿÿÿèõÐ&-I&˜ÿÿ ÿéõÏ&z!ÿÿ ÿéöÐ&.I-Rÿÿ ÿéõÒ&.J-Rÿÿ ÿéñÑ&'á-Rÿÿ ÿçòÐ&-M-Rÿÿ ÿèôÐ&ý-RÿÿÿéóÐ&ûºÿÿ ÿèôÐ&%(-Rÿÿ ÿéõÐ&%´-RÿÿÿéõÐ&û%èÿÿ ÿéóÐ&-N-RÿÿÿéõÐ&Þ&û!¦ÿÿ ÿçôÐ&-O-Rÿÿ ÿéõÐ&'ç-RÿÿÿéôÐ&û(…ÿÿ ÿéõÑ&-R. ÿÿ ÿçøÒ&Õ-Rÿÿ ÿçôÏ&-P/»ÿÿ ÿéöÑ&.~-Rÿÿ ÿéôÐ&'-Rÿÿ ÿèôÒ&%‚-Rÿÿ ÿé÷Ò&-Q-RÿÿÿéòÐ&û-.ÿÿ ÿéôÑ&*ƒ-Rÿÿ ÿéòÐ&- -RÿÿÿèîÑ&08ZÿÿÿìîÒ&(­ÿÿÿèöÒ&­8[ÿÿÿçîÒ&­8\ÿÿÿêîÒ&­8]ÿÿÿèëÑ&—8^ÿÿÿñïÑ&G8_ÿÿÿêîÒ&­®ÿÿÿêïÑ&—8`ÿÿ ÿêòÒ&­('ÿÿ ÿéëÑ&—˜ÿÿÿïôÑ&—8aÿÿÿéõÒ&­8bÿÿÿêëÑ&—8cÿÿÿéîÒ&­8dÿÿÿéîÒ&­8eÿÿÿèðÑ&—8fÿÿÿíõÑ&š8gÿÿÿêîÒ&­8hÿÿÿèëÑ&—8iÿÿÿèôÒ&­8jÿÿÿéîÑ&š8kÿÿÿçîÑ&š8lÿÿ ÿêëÑ&+&,—ÿÿÿéòÑ&°&¯šÿÿÿîïÑ&—8mÿÿÿèïÑ&š8nÿÿÿïðÑ&*—ÿÿ ÿîõÒ&­&§8oÿÿ ÿèñÑ&—¦ÿÿÿéôÑ&—ÿÿ ÿéóÑ&Š8pÿÿ ÿéóÑ&Љÿÿ ÿéóÑ&š8qÿÿ ÿèóÑ&Š8rÿÿÿêîÑ&š8sÿÿÿéîÑ&š8tÿÿÿéîÑ&š8uÿÿ ÿçìÔ&68vÿÿÿèõÑ&®‘ÿÿÿéóÏ&A@ÿÿÿéïÑ&pšÿÿÿæòÑ&š&æåÿÿÿèîÑ&‘8wÿÿ ÿèóÑ&Š!ÇÿÿÿèîÑ&š8xÿÿ ÿéñÑ&š8yÿÿÿéïÑ&‘#œÿÿ ÿèöÑ&š#ŽÿÿÿèîÑ&š8zÿÿ ÿéóÑ&Š8{ÿÿ ÿçóÑ&š8|ÿÿÿéîÑ&‘&l8}ÿÿ ÿéõÑ&š8~ÿÿÿéñÑ&š&™˜ÿÿ ÿçöÑ&š8ÿÿÿçìÔ&68€ÿÿÿçñÑ&š8ÿÿÿæõÔ&68‚ÿÿÿèîÑ&‘8ƒÿÿ ÿìôÑ&‘&5gÿÿ ÿæõÑ&å&Š8„ÿÿ ÿèóÑ&Š8…ÿÿÿéöÑ&‘8†ÿÿÿçöÑ&š8‡ÿÿÿåðÔ&6/]ÿÿÿç÷Ô&"8&"98ˆÿÿÿèõÔ&68‰ÿÿÿçôÔ&68ŠÿÿÿçíÔ&"98‹ÿÿÿéúÔ&"98Œÿÿ ÿèîÔ&"98ÿÿ ÿéóÏ&ijÿÿ ÿéóÏ&k ÿÿ ÿèôÏ&lmÿÿ ÿíõÏ&nlÿÿ ÿéôÏ&lÚÿÿÿðòÏ&,µ8Žÿÿ ÿèôÏ&l(/ÿÿ ÿêìÏ&lÿÿ ÿéóÏ&iÿÿ ÿêòÏ&lSÿÿ ÿóóÏ&kjÿÿÿçõÐ&ó-Sÿÿ ÿèñÏ&iDÿÿ ÿéôÏ&l-Tÿÿ ÿçõÑ&Š ÿÿ ÿéòÐ&Š/ÿÿ ÿèöÏ&lÿÿ ÿèøÑ&l-UÿÿÿïòÈ&Ö0Ìÿÿ ÿéóÑ&Š$ÿÿ ÿéðÏ&l’ÿÿ ÿéòÏ&Š#—ÿÿ ÿèóÏ&Šˆÿÿ ÿëöÏ&Šuÿÿ ÿèøÑ&-W-Vÿÿ ÿéóÔ&Œ&W&X8ÿÿ ÿè÷Ñ&-X-Yÿÿ ÿõóÑ& çÿÿ ÿëòÑ&Ô-Zÿÿ ÿèòÑ&³0¨ÿÿ ÿçòÑ&Ô-[ÿÿ ÿñòÑ&Ô-\ÿÿ ÿñóÑ& èÿÿ ÿêóÑ&(0§ÿÿ ÿéòÑ&<0§ÿÿ ÿéòÑ&-]0§ÿÿ ÿéòÑ& éÑÿÿ ÿîòÑ&“0§ÿÿ ÿéòÑ& ºÔÿÿ ÿéòÑ&Ôÿÿ ÿçöÑ&Ôÿÿ ÿèòÑ&O0§ÿÿ ÿéòÑ& ëÕÿÿ ÿèòÑ&Õ8‘ÿÿ ÿêôÑ&0§ÿÿ ÿèôÑ&0¨ÿÿ ÿéóÑ&0¨ÿÿ ÿéòÑ&;0§ÿÿ ÿíòÑ&ÏÕÿÿ ÿéòÑ&Õ8’ÿÿ ÿéòÑ&0¨8“ÿÿ ÿñòÑ&%I0¨ÿÿ ÿçòÑ&0¨8”ÿÿ ÿéòÑ&0¨8•ÿÿ ÿéòÑ&0§8–ÿÿ ÿéòÑ&o éÿÿ ÿóòÑ&Ô8—ÿÿ ÿèòÑ& éÿÿ ÿìòÑ&0§8˜ÿÿ ÿéòÑ&}0§ÿÿ ÿèòÑ&0§8™ÿÿ ÿéòÑ&Õ8šÿÿ ÿéóÑ&0¨8›ÿÿ ÿðòÑ&0¨8œÿÿ ÿæòÑ&0¨8ÿÿ ÿéòÑ& é8žÿÿ ÿæöÑ&Z0¨ÿÿ ÿéòÑ&0§8Ÿÿÿ ÿèõÑ&0§8 ÿÿ ÿèòÑ& évÿÿ ÿéòÑ&0§8¡ÿÿ ÿéòÑ& é8¢ÿÿ ÿæòÑ&0§8£ÿÿ ÿèòÑ&Ô8¤ÿÿ ÿèòÑ&Û&Ú0§ÿÿ ÿéóÑ&Ô8¥ÿÿ ÿìòÑ&-^0§ÿÿ ÿïóÑ&Õ8¦ÿÿ ÿéòÑ&Õ8§ÿÿ ÿçòÑ&Ô&ËÌÿÿ ÿéòÑ&0§8¨ÿÿ ÿèóÑ&ß&K0§ÿÿ ÿîòÑ&0¨8©ÿÿ ÿéôÑ&Õÿÿ ÿêòÑ&å0¨ÿÿ ÿéòÑ& êÿÿ ÿéòÑ&Ë&Õ8ªÿÿ ÿéóÑ&0§8«ÿÿ ÿêòÑ& ê-_ÿÿ ÿèóÑ&§ éÿÿ ÿèòÑ&0§8¬ÿÿ ÿîôÑ&”0¨ÿÿ ÿéòÑ&0§8­ÿÿ ÿçòÑ&Ô8®ÿÿ ÿéóÑ& é&TJÿÿ ÿïòÑ& éÿÿ ÿéóÑ&0¨8¯ÿÿ ÿèòÑ& éªÿÿ ÿèõÑ&ß0¨ÿÿ ÿéóÑ&Ì‹ÿÿ ÿéòÑ&0§8°ÿÿ ÿéòÑ& é*ÿÿ ÿéòÑ&&0§ÿÿÿèôÑ&i8±ÿÿ ÿêòÑ&0§8²ÿÿ ÿéòÑ& é8³ÿÿ ÿèóÑ&0§8´ÿÿ ÿéòÑ&£& é8µÿÿ ÿéòÑ& é8¶ÿÿ ÿéòÑ&0§8·ÿÿ ÿéõÑ& é8¸ÿÿ ÿéõÑ&ô éÿÿ ÿéòÑ& é8¹ÿÿ ÿéóÑ&>0§ÿÿ ÿéòÑ&Ï êÿÿ ÿðòÑ&Ô8ºÿÿ ÿéòÑ&ç& é8»ÿÿ ÿèôÑ&Ô&öõÿÿ ÿéòÑ& é8¼ÿÿ ÿêõÑ&%?0§ÿÿ ÿéõÑ& é Üÿÿ ÿéóÑ& é ÿÿ ÿéòÑ& é&<ÿÿ ÿéóÑ&0§8½ÿÿ ÿèòÑ& é8¾ÿÿ ÿéòÑ&- éÿÿ ÿîòÑ& é8¿ÿÿ ÿéòÑ& é8Àÿÿ ÿèóÑ& é8Áÿÿ ÿñóÑ&0¨8Âÿÿ ÿéòÑ&0§8Ãÿÿ ÿéóÑ&0¨8Äÿÿ ÿéóÑ& é8Åÿÿ ÿéóÑ& é8Æÿÿ ÿìòÑ& é8Çÿÿ ÿéòÑ&ê éÿÿ ÿêòÑ&5& é8Èÿÿ ÿèôÑ&0§8Éÿÿ ÿêóÑ&i8Êÿÿ ÿéòÑ& é&4£ÿÿ ÿêòÑ& ê&t8ËÿÿÿéòÑ&iµÿÿ ÿéóÑ&¥ÌÿÿÿéòÑ&i8Ìÿÿ ÿéõÑ&i8ÍÿÿÿêòÑ&iFÿÿ ÿèòÑ&Û&0§8Îÿÿ ÿéòÑ& é8Ïÿÿ ÿêóÑ&0§8Ðÿÿ ÿéóÑ& ê8ÑÿÿÿéòÑ&iÒÿÿ ÿéòÑ& é8Òÿÿ ÿêòÑ& é&Ýæÿÿ ÿéôÑ& é8Óÿÿ ÿïòÑ& é ÿÿ ÿéòÑ& é8Ôÿÿ ÿèóÑ& ê8Õÿÿ ÿêóÑ& ê8Öÿÿ ÿîòÑ& êÿÿÿéòÑ&ilÿÿ ÿéóÑ& é8×ÿÿ ÿéòÑ& énÿÿ ÿéóÑ&i8Øÿÿ ÿíòÑ& é8Ùÿÿ ÿéòÑ& é8Úÿÿ ÿèòÑ& ê8Ûÿÿ ÿèòÑ& éÖÿÿ ÿéóÑ&Ì8Üÿÿ ÿéóÑ& é8Ýÿÿ ÿêóÑ& é8Þÿÿ ÿéóÑ&T&"æ éÿÿ ÿéòÑ&s éÿÿ ÿéòÑ& ê!ÿÿ ÿéòÑ&"yiÿÿ ÿèòÑ&" éÿÿ ÿèòÑ&5 éÿÿ ÿéòÑ&#Ë éÿÿ ÿéòÑ&«& é8ßÿÿ ÿéòÑ& é8àÿÿ ÿíóÑ&i"²ÿÿ ÿéóÑ&Ü&Ý éÿÿ ÿéòÑ&i"òÿÿ ÿéòÑ&i"¾ÿÿ ÿèôÑ& ê&Ø8áÿÿ ÿéòÑ&5&4 éÿÿÿïòÑ&`iÿÿ ÿéóÑ& é&ç8âÿÿ ÿêòÑ&0§8ãÿÿ ÿèóÑ& é& ¹8äÿÿÿéòÑ&i"ÿÿ ÿçõÑ& ê8åÿÿ ÿéóÑ& é8æÿÿ ÿçòÑ&$ êÿÿ ÿîóÑ& ù&i8çÿÿ ÿêòÑ&i8èÿÿÿéóÑ&%iÿÿ ÿíòÑ& é/3ÿÿ ÿéòÑ& é8éÿÿ ÿèõÑ&p& é8êÿÿ ÿéóÑ&i$5ÿÿ ÿêõÑ& é&ÛÚÿÿ ÿéòÑ&T& é8ëÿÿ ÿêôÑ& ê8ìÿÿ ÿéòÑ&Ì8íÿÿ ÿêóÑ&$iÿÿÿéòÑ&$§iÿÿ ÿéòÑ&"Ù éÿÿ ÿèôÑ& ê$ÿÿÿìòÑ&i/7ÿÿ ÿéòÑ&$¯iÿÿ ÿéòÑ&Ò& é8îÿÿ ÿéòÑ& é8ïÿÿ ÿêòÑ&Ì$ãÿÿÿèôÑ&Ì&#x8ðÿÿ ÿìóÑ&i$öÿÿ ÿîóÑ& ø& é8ñÿÿ ÿçòÑ& é8òÿÿÿèòÑ&Ì8óÿÿÿçòÑ&i/RÿÿÿéòÑ&Ì8ôÿÿ ÿçöÑ& ê&á8õÿÿ ÿéóÑ&Ì8öÿÿÿçöÑ&Ì&á8÷ÿÿ ÿêóÑ&Ì8øÿÿ ÿìóÒ&÷8ùÿÿÿéòÑ&{iÿÿÿçòÑ&i ÿÿ ÿéòÑ&i8úÿÿ ÿèòÑ& é&˜8ûÿÿÿéòÑ&Ì8üÿÿÿåòÑ&Ì8ýÿÿ ÿéóÑ&0§8þÿÿÿèòÑ&inÿÿ ÿèòÑ&œ& é ÿÿ ÿèòÑ&iœÿÿÿçôÑ&i8ÿÿÿ ÿçòÑ& ê0[ÿÿ ÿèóÑ& é&ß9ÿÿ ÿìôÑ&Ì9ÿÿ ÿîóÑ&I& é úÿÿ ÿèóÑ& ¸& é ¹ÿÿÿçòÑ&Ì9ÿÿÿéòÑ&i9ÿÿ ÿçõÑ& é9ÿÿ ÿèõÑ&Ü& é9ÿÿ ÿèôÑ&i9ÿÿ ÿèõÑ&Ì9ÿÿ ÿçòÑ& ê9ÿÿ ÿèõÑ&i9 ÿÿ ÿèòÑ& é9 ÿÿ ÿèôÑ& é&Ø9 ÿÿÿéöÑ&i&$w9 ÿÿ ÿçòÑ&5& é" ÿÿ ÿéõÑ& ê9 ÿÿ ÿèôÑ&i9ÿÿÿèðÒ&÷øÿÿ ÿëóÑ&&ÌŒÿÿÿèòÑ&õ&i9ÿÿÿéòÑ&ö&i9ÿÿÿèòÑ&Ì9ÿÿ ÿéôÑ&i9ÿÿ ÿïòÑ& é9ÿÿ ÿçôÑ&Ì9ÿÿ ÿèóÑ&i9ÿÿÿèòÑ&&i9ÿÿÿéôÑ&@&i9ÿÿ ÿèòÑ&Ì9ÿÿ ÿçõÑ& é9ÿÿ ÿéòÑ& é9ÿÿ ÿèôÑ& ê9ÿÿ ÿçõÑ& é9ÿÿÿèòÑ&Ì9ÿÿÿèöÑ&i9ÿÿÿéòÑ&i9ÿÿ ÿèóÑ& ê&à9 ÿÿÿèöÑ&Ì&$69!ÿÿ ÿçõÑ& ê9"ÿÿ ÿéòÑ&Ì9#ÿÿÿêöÑ&i&$w5ÿÿÿéòÑ&i9$ÿÿ ÿçôÑ& é9%ÿÿ ÿîóÑ& ù&i9&ÿÿ ÿèöÑ& ê9'ÿÿÿéòÑ&i9(ÿÿÿêõÑ& š& ›iÿÿÿëòÒ&÷9)ÿÿÿéòÑ&i9*ÿÿÿéòÑ&Ì9+ÿÿ ÿéòÑ&i9,ÿÿ ÿéòÑ&Ì9-ÿÿ ÿèöÑ& é9.ÿÿ ÿéôÑ&i9/ÿÿÿèôÑ&i90ÿÿÿèõÑ&Ì91ÿÿÿéðÒ&/I÷ÿÿ ÿéöÑ& ê92ÿÿ ÿêõÑ& é93ÿÿ ÿíòÑ&©Ìÿÿ ÿéóÑ& é94ÿÿÿèòÑ&i95ÿÿ ÿèóÑ& é96ÿÿÿëöÑ&i97ÿÿÿèòÑ&i98ÿÿ ÿêõÑ& ê99ÿÿ ÿèôÑ&i9:ÿÿ ÿîóÑ& é9;ÿÿÿçöÑ&á&Ì9<ÿÿÿçöÑ&á&Ì9=ÿÿÿéòÑ&i9>ÿÿÿçöÒ&÷9?ÿÿÿèòÑ&i9@ÿÿ ÿéòÑ&i9Aÿÿ ÿéöÑ&i9Bÿÿ ÿèôÑ&i9Cÿÿ ÿéôÐ&_+Ûÿÿ ÿèóÏ&j•ÿÿ ÿèöÏ&˜/lÿÿ ÿèôÏ&•üÿÿ ÿèõÐ&•¡ÿÿ ÿéóÑ& `ÿÿ ÿèëÏ&•aÿÿ ÿèôÏ&•bÿÿ ÿèîÏ&•ÿÿ ÿèóÏ&•ÿÿ ÿèôÏ&•cÿÿ ÿèòÏ&•dÿÿ ÿéõÒ&efÿÿ ÿéõÏ&˜ÿÿ ÿèòÏ&•¾ÿÿ ÿèðÏ&•gÿÿ ÿèòÏ&•hÿÿ ÿèôÏ&•¿ÿÿ ÿéóÎ&2xÿÿ ÿèöÏ&•iÿÿ ÿèèÐ&+%&/•ÿÿ ÿèôÏ&•¤ÿÿ ÿèìÏ&•(¾ÿÿ ÿèõÏ&•ÿÿ ÿèóÏ&•jÿÿ ÿèôÏ&•žÿÿ ÿèêÐ&•Mÿÿ ÿèõÏ&•kÿÿ ÿèõÏ&•ÿÿ ÿèòÏ&•Nÿÿ ÿéòÏ&-`xÿÿ ÿèóÏ&•lÿÿ ÿèñÏ&•Óÿÿ ÿèìÏ&•aÿÿ ÿéòÑ&x/Óÿÿ ÿèôÏ&½žÿÿÿèòÏ&n&moÿÿ ÿéóÑ&˜Ôÿÿ ÿèñÏ&•ÿÿÿçòÒ&pqÿÿ ÿéòÏ&˜Äÿÿ ÿéôÑ&˜àÿÿ ÿèõÐ&•Lÿÿ ÿèôÏ&•rÿÿ ÿéòÐ&•&(!xÿÿ ÿéóÏ&yxÿÿ ÿæöÏ&•Aÿÿ ÿéóÏ&˜ëÿÿÿéòÑ&0‚9Eÿÿ ÿèòÏ&½.ÿÿ ÿèóÏ&•)ÿÿ ÿèõÏ&•-aÿÿ ÿéõÎ&åÿÿ ÿèõÑ&•Rÿÿ ÿéóÏ&˜'ÿÿ ÿè÷Ï&•1ÿÿ ÿèöÏ&•–ÿÿ ÿè÷Ð&• /ÿÿ ÿèóÏ&•sÿÿ ÿèöÏ&•#Hÿÿ ÿèóÐ&½&@Yÿÿ ÿéñÏ&˜$]ÿÿ ÿèîÏ&„&J#×ÿÿ ÿéòÐ&˜#¢ÿÿ ÿéóÏ&¾½ÿÿ ÿèóÐ&˜tÿÿ ÿéöÏ&˜uÿÿ ÿéöÏ&˜vÿÿ ÿèðÏ&½wÿÿ ÿéóÐ&½$bÿÿ ÿèôÏ&•!ÿÿ ÿèòÏ&˜-Cÿÿ ÿéôÑ&˜$ÿÿ ÿéóÏ&˜$ÿÿ ÿéõÑ&—˜ÿÿ ÿéõÑ&G&½9Fÿÿ ÿèôÐ&‹&Ç9Gÿÿ ÿçõÑ&˜xÿÿ ÿçòÒ&yzÿÿ ÿèóÏ&½{ÿÿ ÿçöÑ&½|ÿÿ ÿèõÏ&˜£ÿÿ ÿéòÏ&˜,ÿÿ ÿèõÏ&½~ÿÿ ÿéòÑ&.ÿÿ ÿçôÏ&˜™ÿÿ ÿèóÐ&•*yÿÿ ÿéñÏ&½0"ÿÿ ÿéïÏ&½ÿÿÿèôÑ&¨9Hÿÿ ÿéòÏ&½->ÿÿ ÿèóÐ&‚&ƒ9Iÿÿ ÿèóÏ&ƒ&‚9Jÿÿ ÿéòÐ&-b½ÿÿ ÿéðÏ&œ›ÿÿ ÿéóÏ&Ñ›ÿÿ ÿéßÏ&Z˜ÿÿ ÿé÷Ï&Zÿÿ ÿèõÏ&šQÿÿ ÿéóÏ&›ÿÿ ÿèíÐ&QÅÿÿ ÿéìÏ&YZÿÿ ÿéòÏ&Zÿÿ ÿéðÏ&´Zÿÿ ÿéòÏ& Ñÿÿ ÿéõÏ&Q›ÿÿ ÿéìÏ&œQÿÿ ÿçöÐ&Áÿÿ ÿéõÏ&RQÿÿ ÿéôÏ&*ÌQÿÿ ÿéîÏ&ð Ñÿÿ ÿéöÏ&Qÿÿ ÿéôÏ&” Ñÿÿ ÿéóÏ&Q1ÿÿ ÿéôÏ&ž›ÿÿ ÿèöÏ&Q§ÿÿ ÿéïÏ&*›ÿÿ ÿéòÏ&º Ñÿÿ ÿéõÏ&žÿÿ ÿéèÐ& Ñ&6/ÿÿ ÿèôÏ&Q(/ÿÿ ÿèôÏ&]mÿÿ ÿéôÏ& Ñ Òÿÿ ÿéóÏ&‡Qÿÿ ÿéôÏ&ÚQÿÿ ÿéöÏ&Z¬ÿÿ ÿéïÏ&Q¬ÿÿ ÿèôÏ&Q!ÿÿ ÿèöÑ& Ñ­ÿÿ ÿçóÏ&Óÿÿ ÿéòÏ&Q0¬ÿÿÿçëÐ&¯®ÿÿÿçñË&°&ª9Kÿÿ ÿéóÏ& Ñ&}/Õÿÿ ÿéòÏ&Q(-ÿÿ ÿçôÏ&ƒªÿÿ ÿéëÏ& Ð)ÿÿÿ ÿçòÏ&ª9LÿÿÿçëÉ&-0ÿÿ ÿéèÏ& ïÿÿ ÿèôÐ&žÐÿÿ ÿéóÏ& ÑQÿÿ ÿéïÐ&R Ñÿÿ ÿéòÏ& ÑSÿÿ ÿéòÏ& ÑTÿÿ ÿéôÏ&Qœÿÿ ÿéîÏ& Ñÿÿ ÿéöÏ& Ñÿÿ ÿéöÏ&ž±ÿÿ ÿéóÐ&¸0Òÿÿ ÿéóÏ& Ѳÿÿ ÿéöÐ& ѳÿÿ ÿéòÐ& Ñ0öÿÿ ÿéëÏ& Ñ´ÿÿ ÿéöÏ&ž,éÿÿ ÿéõÏ&ž·ÿÿ ÿéðÏ&Q.}ÿÿ ÿéõÏ&Qÿÿ ÿéïÏ& Ѹÿÿ ÿèðÏ&ÇZÿÿ ÿéìÏ&Zÿÿ ÿéêÐ&ZMÿÿ ÿèõÏ&Qgÿÿ ÿêõÏ&]¹ÿÿ ÿéòÏ&Qºÿÿ ÿéôÏ& &»/ÿÿ ÿçõÐ&(Qÿÿ ÿéòÏ&Z0×ÿÿ ÿéöÏ&]™ÿÿ ÿèìÏ&¸¼ÿÿ ÿéôÏ&Qôÿÿ ÿèñÏ&D Ñÿÿ ÿéôÏ&~ Ñÿÿ ÿéöÏ&Qóÿÿ ÿè÷Ï& Ñ ÿÿ ÿéõÐ&[&Y9MÿÿÿçíÐ&&ªÿÿ ÿéòÏ&Q¤ÿÿ ÿéôÏ&Q`ÿÿ ÿéõÐ&‹ Ñÿÿ ÿéíÑ& ÑŸÿÿ ÿéóÏ& åÿÿ ÿéôÑ&à Ñÿÿ ÿéìÏ& Ñaÿÿ ÿéöÏ&Aøÿÿ ÿé÷Ï& ѽÿÿ ÿéìÏ&ždÿÿÿéïÏ&19Nÿÿ ÿéõÐ&ùñÿÿÿçëÐ&ª0‡ÿÿ ÿéôÐ&™Qÿÿ ÿéóÏ&Q+ÿÿ ÿéóÏ& Ð'ÿÿ ÿéôÑ&˜ Ñÿÿ ÿéóÐ& Ðzÿÿ ÿèõÏ& ÑZÿÿ ÿéöÐ& ÑvÿÿÿéõÏ&É&ʾÿÿ ÿééÏ& Ñÿÿ ÿéòÐ& ¿ÿÿ ÿêøÑ&¸ ÿÿ ÿéôÐ& ÑÀÿÿ ÿèìÏ& ÑÁÿÿ ÿé÷Ð& Ñ-²ÿÿ ÿèöÏ&\ÿÿ ÿèñÐ&g&&e&jÿÿ ÿéêÐ& Ñ–ÿÿ ÿçõÏ&Qÿÿ ÿéöÏ&¸”ÿÿ ÿæöÏ&AQÿÿ ÿèõÐ&Q•ÿÿ ÿæôÏ&¸-ÿÿ ÿé÷Ï&ZÆÿÿ ÿéôÏ& Ñ,Ëÿÿ ÿéòÏ&j%>ÿÿ ÿèñÏ&žÿÿ ÿéïÏ& Ðáÿÿ ÿéóÐ& Ѳÿÿ ÿéòÏ&© Ðÿÿ ÿéôÏ&üíÿÿ ÿéõÏ&A!ÿÿ ÿéóÏ&—žÿÿ ÿéóÏ&.€¸ÿÿ ÿéóÏ&îAÿÿ ÿéõÑ&]Rÿÿ ÿéöÏ&-z.ñÿÿ ÿéôÏ& )|ÿÿ ÿçõÏ&]+Bÿÿ ÿéõÑ&-g ÿÿ ÿéôÏ&`\ÿÿ ÿéëÏ&¾Aÿÿ ÿìõÐ&g%ªÿÿ ÿêóÑ&¸%%ÿÿ ÿéöÐ& "Iÿÿ ÿéóÏ& Ñ'ÿÿ ÿçöÒ&f Ñÿÿ ÿéñÏ& Ð.ÿÿ ÿéõÐ&‹ Ñÿÿ ÿçòÐ&1&ª9Pÿÿ ÿèíÏ&¸+Cÿÿ ÿéñÐ&X Ñÿÿ ÿéíÏ&¸-iÿÿ ÿèïÏ&¸-hÿÿ ÿéöÏ& Ð ×ÿÿ ÿèõÏ&]-jÿÿ ÿèòÏ&¸.ÿÿ ÿéøÏ&¸ ÿÿ ÿèôÏ&¸9Qÿÿ ÿéóÏ&ž&Çÿÿ ÿéôÑ&QÌÿÿ ÿéóÏ&ž)ÿÿ ÿéòÏ& Zžÿÿ ÿèõÏ& ÑVÿÿ ÿèðÏ& Ñuÿÿ ÿéõÏ& Þÿÿ ÿèóÏ& Ð-dÿÿ ÿèóÏ&Qïÿÿ ÿéôÏ& (mÿÿ ÿêõÌ&-z.3ÿÿ ÿéìÏ&žÿÿ ÿéðÐ& # ÿÿ ÿèñÏ&¸ÿÿÿèñÈ&«0Íÿÿ ÿéôÐ& Ñÿÿ ÿèõÑ&¸‰ÿÿ ÿèöÏ&žhÿÿ ÿëõÏ&]&!9Rÿÿ ÿé÷Ï& Ñ"nÿÿ ÿéóÏ& Ñ6ÿÿ ÿéñÐ&Q-kÿÿ ÿéöÏ& Ñ"mÿÿ ÿéìÏ&tjÿÿ ÿéöÐ&A!ÿÿ ÿéïÏ& !=ÿÿ ÿéöÐ&]#Yÿÿ ÿèòÏ& Ð"@ÿÿ ÿéôÏ&víÿÿ ÿäöÏ&]ýÿÿ ÿéôÏ&g&!1(ÿÿ ÿéôÏ& Ð!_ÿÿ ÿéïÑ&žÿÿ ÿéóÏ&ž*ÿÿ ÿéõÏ&A"îÿÿ ÿèõÏ& аÿÿ ÿë÷Ï&]¥ÿÿ ÿéóÏ& Ñ&Žÿÿ ÿçíÐ&¸:ÿÿ ÿèõÏ& Ó]ÿÿ ÿéôÏ&g9Sÿÿ ÿéòÏ& Ð"Fÿÿ ÿèôÐ&ž!†ÿÿ ÿéóÐ&ž!Šÿÿ ÿéöÏ& Çÿÿ ÿèóÐ&Y& @ÿÿ ÿèöÏ&«&-l9Tÿÿ ÿèðÑ&$9Uÿÿ ÿéõÏ&]Ùÿÿ ÿèôÐ& &@"çÿÿ ÿéñÏ& Ñ!(ÿÿ ÿéóÏ&¸óÿÿ ÿéñÏ& !Œÿÿ ÿèõÐ&«&9Vÿÿ ÿéôÏ& Ð"óÿÿ ÿéñÏ& Ñ$]ÿÿ ÿéöÏ& "7ÿÿ ÿè÷Ñ&A/ÖÿÿÿéîÏ& &ø1ÿÿ ÿêðÏ&g#wÿÿ ÿéòÏ&Q$Jÿÿ ÿéôÐ&A0pÿÿ ÿéôÏ&Ã&úAÿÿ ÿéñÑ&Q$”ÿÿ ÿéóÏ& ÑMÿÿ ÿèïÓ&«&9Wÿÿ ÿçïÏ& -mÿÿ ÿèõÐ&]ÿÿ ÿéòÐ& Ð2ÿÿ ÿè÷Ï&¸Íÿÿ ÿé÷Ï&-n¸ÿÿ ÿéõÏ&Qÿÿ ÿêðÑ&g‡ÿÿ ÿéóÐ& Ñ.“ÿÿ ÿéóÏ& $¸ÿÿ ÿéöÏ& Ñvÿÿ ÿèõÏ&ž$ÿÿ ÿçòÐ&ž-oÿÿ ÿéòÐ& $Îÿÿ ÿéôÏ&üAÿÿÿèôÎ&ÿ9Xÿÿ ÿéïÏ& $ªÿÿ ÿèôÏ&39Yÿÿ ÿéôÏ&í9Zÿÿ ÿéôÏ&A'ßÿÿ ÿèôÏ&¸$rÿÿ ÿéòÏ&f&egÿÿ ÿéöÏ&A-pÿÿ ÿéóÏ&¸.ÿÿ ÿéôÏ&.úAÿÿ ÿéðÐ&j*¸ÿÿ ÿéïÐ&g-…ÿÿ ÿêïÏ&¸.„ÿÿ ÿéõÏ&g&f-qÿÿ ÿèõÑ&g&f-rÿÿ ÿéôÏ&¸)Ëÿÿ ÿéóÏ&¸*ÿÿ ÿéóÒ&.”žÿÿ ÿéôÏ&g%Þÿÿ ÿéöÐ&0 1ÿÿ ÿçõÏ& 0›ÿÿ ÿèõÏ&£ Ðÿÿ ÿéóÏ&¸†ÿÿ ÿêõÏ&]&G9[ÿÿ ÿéñÐ&Ç9\ÿÿ ÿèõÐ&«0Lÿÿ ÿéòÑ&A)Ìÿÿ ÿèõÐ&¸&˜ÿÿ ÿçõÑ&Axÿÿ ÿéòÏ& -sÿÿ ÿèóÐ&«ÿÿ ÿéõÐ&.•¸ÿÿ ÿéíÏ&A-9ÿÿÿèïÌ&«/2ÿÿ ÿéõÑ& Ð*·ÿÿ ÿé÷Ï&žÿÿ ÿéóÏ&-t-ÿÿ ÿé÷Ð&g&}9]ÿÿ ÿçöÏ& 9^ÿÿ ÿèøÓ&«9_ÿÿÿéóÑ&k&-v9`ÿÿ ÿèôÐ&-u Ðÿÿ ÿéòÏ&/¥jÿÿ ÿéóÐ&A+`ÿÿ ÿéóÏ&¸'÷ÿÿ ÿéïÑ&Q'#ÿÿ ÿèöÏ&]-íÿÿ ÿéöÏ&jFÿÿ ÿéòÐ&A*¹ÿÿ ÿèóÐ&«&÷9aÿÿ ÿéôÏ&¸*Ñÿÿ ÿçïÏ&¸-wÿÿ ÿéòÏ&žüÿÿ ÿéôÏ&]* ÿÿ ÿéóÒ&¸iÿÿ ÿéîÏ&ï&j9bÿÿ ÿèôÏ&¸'øÿÿ ÿéñÏ& Ð'ÿÿ ÿèóÏ&«9cÿÿ ÿéòÏ&j%áÿÿ ÿçôÏ& (Kÿÿ ÿêòÐ&¸·ÿÿ ÿèõÏ&g~ÿÿ ÿèòÏ&ž9ÿÿ ÿéôÏ&A.÷ÿÿ ÿéøÏ&Ÿ ÿÿ ÿéöÒ&g*€ÿÿÿèïÒ&«&9dÿÿÿéñÏ&l9eÿÿ ÿéõÏ&]*ºÿÿ ÿéøÏ&¡Aÿÿ ÿèöÐ&«'ûÿÿ ÿèõÐ&«&9fÿÿ ÿéòÐ&¾.ÿÿ ÿêõÓ&¸Sÿÿ ÿé÷Ñ&A.£ÿÿ ÿçôÏ&A-xÿÿ ÿéöÏ&j&â!£ÿÿ ÿéôÏ&]Øÿÿ ÿèöÑ&4&«9gÿÿ ÿéñÏ&¸0"ÿÿ ÿéôÏ&j9hÿÿ ÿçóÑ&¡ ÿÿ ÿéöÏ&j&â9iÿÿ ÿéóÐ&ž*yÿÿ ÿéôÏ&j&-y9jÿÿ ÿèõÐ&A¢ÿÿ ÿè÷Ò&÷9kÿÿ ÿéõÑ& Ð5ÿÿ ÿêñÏ&¸%Sÿÿ ÿéòÏ&´& å æÿÿ ÿç÷Ï&h&ijÿÿ ÿèóÐ&«&?=ÿÿ ÿèôÐ&]*µÿÿ ÿçõÏ&Aþÿÿ ÿçôÏ& ÐÅÿÿÿèïÍ&«ÿÿ ÿêõÏ&-z-{ÿÿ ÿèòÐ&A%Tÿÿ ÿç÷Ï&«&iªÿÿ ÿéóÑ&AÍÿÿ ÿèôÏ&«9lÿÿ ÿèòÏ&g-ÿÿ ÿèòÑ&g7ÿÿ ÿéôÑ& Ñêÿÿ ÿêõÎ&-z+bÿÿ ÿéðÏ&j9mÿÿ ÿçõÐ&«‡ÿÿ ÿéóÏ&j.@ÿÿ ÿéòÏ&A*Ðÿÿ ÿéôÐ&¬¢ÿÿ ÿêòÐ&¸.?ÿÿ ÿéÞÏ&Ç-|ÿÿ ÿéðÏ&,lÿÿ ÿñóÏ&,Èÿÿ ÿêòÏ&,(ÿÿ ÿéóÏ&, ÿÿ ÿîõÐ&m,ÿÿ ÿéìÏ&,Yÿÿ ÿéóÏ&£‰ÿÿ ÿçøÏ&,%¼ÿÿ ÿêóÐ&¿,ÿÿ ÿî÷Ï&,ÿÿ ÿéìÏ&,("ÿÿ ÿéêÏ&,ÿÿ ÿçõÏ&,!vÿÿ ÿìõÏ&,ÿÿ ÿìõÏ&,ÿÿ ÿèôÏ&,(/ÿÿ ÿéëÏ&š,ÿÿ ÿèíÏ&££ÿÿ ÿèöÏ&£Ÿÿÿ ÿìôÏ&,Fÿÿ ÿéôÏ&,Úÿÿ ÿêôÏ&,*¶ÿÿ ÿéõÏ&,Rÿÿ ÿéòÏ&,0¬ÿÿ ÿîóÏ&£)ÿÿ ÿéñÏ&,8ÿÿ ÿèõÏ&,»ÿÿ ÿéöÏ&,ÿÿ ÿ÷óÏ&,Qÿÿ ÿçòÐ&£Pÿÿ ÿêõÏ&£/+ÿÿ ÿòõÏ&,ÿÿ ÿéïÐ&R,ÿÿ ÿëèÏ&ï,ÿÿ ÿéòÏ&,&µÿÿ ÿéõÏ&,!ÿÿ ÿéçÐ&,-}ÿÿ ÿèðÐ&'¡£ÿÿ ÿéòÏ&-0°ÿÿ ÿééÏ&£µÿÿ ÿéêÏ&j,ÿÿ ÿéðÏ&,*Öÿÿ ÿîõÏ&,%"ÿÿ ÿéóÐ&,0÷ÿÿ ÿèñÐ&&e&-&kÿÿ ÿéöÏ&£øÿÿ ÿéóÏ&,0Öÿÿ ÿéõÐ&,óÿÿ ÿèõÏ&¼£ÿÿ ÿéóÏ&-&ÙØÿÿ ÿèóÏ&,Vÿÿ ÿéôÑ& ¸,ÿÿ ÿêíÑ&,Ÿÿÿ ÿéóÐ&,zÿÿ ÿéõÐ&,ÿÿ ÿíöÐ&,ÿÿ ÿèôÐ&,Pÿÿ ÿéóÏ&,'ÿÿ ÿæöÏ&,-~ÿÿ ÿéêÐ&,cÿÿ ÿééÏ&,ÿÿ ÿæôÏ&,-ÿÿ ÿèñÏ&,Žÿÿ ÿêôÏ&,-‚ÿÿ ÿòóÏ&—,ÿÿ ÿèóÐ&,Œÿÿ ÿèïÏ&£-ƒÿÿ ÿéóÏ&,6ÿÿ ÿèôÏ&£'Øÿÿ ÿèöÏ&£%«ÿÿ ÿéóÏ&,&Çÿÿ ÿéóÐ&£ Oÿÿ ÿèòÏ&£ Wÿÿ ÿéìÐ&£øÿÿ ÿëôÏ&,(mÿÿ ÿìöÏ&£-„ÿÿ ÿéõÐ&‹,ÿÿ ÿèïÏ&,wÿÿ ÿéñÏ&,.ÿÿ ÿéíÏ&-Éÿÿ ÿéëÏ&,¾ÿÿ ÿèôÏ&,9oÿÿ ÿìõÐ&£%ªÿÿ ÿéõÑ&£Oÿÿ ÿéôÑ&,Íÿÿ ÿéñÐ&,gÿÿ ÿé÷Ï&,Œÿÿ ÿéöÏ&£ ×ÿÿ ÿéðÐ&£# ÿÿ ÿéïÏ&,!=ÿÿ ÿéíÏ&.&2-ÿÿ ÿéöÐ&£!ÿÿ ÿéòÏ&,"Kÿÿ ÿéóÐ&-0–ÿÿ ÿéóÏ&,"ÿÿ ÿêöÏ&-&-l9pÿÿ ÿäöÏ&,ýÿÿ ÿæòÏ&,!ÿÿÿ ÿì÷Ï&,¥ÿÿ ÿïòÏ&-£ÿÿ ÿèöÏ&-&#"#!ÿÿ ÿèôÐ&,&@"çÿÿ ÿèóÐ&,&@Yÿÿ ÿìôÐ&£0oÿÿ ÿèõÏ&, Óÿÿ ÿéïÑ&,ÿÿ ÿèôÐ&£#¸ÿÿ ÿèïÐ&£0¹ÿÿ ÿéòÏ&+,ÿÿ ÿéõÏ&£"îÿÿ ÿêðÏ&£#wÿÿ ÿêöÏ&,vÿÿ ÿéôÏ&,üÿÿ ÿçóÐ&,&rÿÿ ÿëôÏ&-.ûÿÿ ÿéòÐ&£% ÿÿ ÿçòÑ&£9qÿÿ ÿéðÐ&£*¸ÿÿ ÿîòÏ&,$Iÿÿ ÿèõÐ&,ÿÿ ÿèòÐ&£/Mÿÿ ÿéóÏ&,†ÿÿ ÿéóÏ&,*ÿÿ ÿéôÏ&,)Ëÿÿ ÿæïÏ&-,(ÿÿ ÿéïÐ&£-…ÿÿ ÿé÷Ï&,ÿÿ ÿéõÑ&,*·ÿÿ ÿéóÏ&-&9rÿÿ ÿéôÏ&£* ÿÿ ÿéòÐ&,*¹ÿÿ ÿêîÑ&,'ÿÿ ÿîôÏ&, ÿÿ ÿíõÏ&-&Þ9sÿÿ ÿéøÏ&,¡ÿÿ ÿéõÏ&,*ºÿÿ ÿèõÐ&-&9tÿÿ ÿçôÏ&-0ÿÿ ÿðòÐ&¤ÿÿÿéñÑ&dOÿÿ ÿéôÐ&feÿÿ ÿèñÐ&ehÿÿ ÿèóÐ&çeÿÿ ÿèóÐ&*»ÿÿ ÿçõË&j9uÿÿ ÿçõÏ&j& ÿÿ ÿéóÏ&l&k9vÿÿ ÿçðÑ&qpÿÿ ÿéóÒ&oiÿÿ ÿèôÐ&o*ÿÿÿéòÑ&m&º9wÿÿÿéðÑ&rsÿÿÿéîÉ&ÏlÿÿÿéðË&mnÿÿ ÿéñÐ&o9xÿÿ ÿéóÑ&Îqÿÿ ÿéòÐ&o*¼ÿÿÿéðÆ&Ä9yÿÿÿéÝÈ&9zÿÿÿêíÈ&9{ÿÿÿèÝÈ&9|ÿÿÿèôÈ&9}ÿÿÿîñÈ&9~ÿÿ ÿîõÉ&ï9ÿÿ ÿèêÉ&Á9€ÿÿÿéîÈ&ÿÿ"ÿéêÉ&Á/ÿÿ ÿðóÈ&0ÿÿÿçíÈ&9ÿÿÿííÉ&ï9‚ÿÿ"ÿçÜÊ&1ÿÿÿéïÊ&9ƒÿÿÿéõÉ&ï9„ÿÿ ÿëöÊ&9…ÿÿÿéðÊ& [ÿÿÿéóÉ&ï9†ÿÿÿèóÉ&Á9‡ÿÿÿèïÉ&Á9ˆÿÿÿéóÉ&ÁBÿÿÿïòÊ&9‰ÿÿÿéâÉ&"&Á9ŠÿÿÿèðÉ&*ÎÁÿÿ ÿéñÉ&Á*Ïÿÿ ÿìôÉ&"/&gÁÿÿ ÿèõÉ&Á&p9‹ÿÿÿéïÊ&"ÚÿÿÿéÞÊ&9ŒÿÿÿéñÉ&Á9ÿÿÿéíÉ&Á9Žÿÿ ÿéóÊ&&19ÿÿ ÿéòÊ&9ÿÿÿèïÊ&9‘ÿÿÿçæÊ&9’ÿÿÿèãÊ&9“ÿÿ ÿîôÊ&¹ÿÿ ÿèîÊ&G&=ÿÿÿççÊ&9”ÿÿÿéìÊ&9–ÿÿÿèñÍ&/ /Ÿÿÿ ÿèôÊ&&G!>ÿÿ ÿèñÊ&&G!?ÿÿ ÿèóÊ&9—ÿÿ ÿéôÐ&½¾ÿÿ ÿéòÐ&Å ÿÿÿêôÑ&Å9™ÿÿ ÿèôÏ&¥¿ÿÿÿêòÒ&MLÿÿ ÿèõÏ&¥Uÿÿ ÿçõÏ&¥FÿÿÿçðÏ&ÂÁÿÿ ÿèòÏ&¥Äÿÿ ÿèòÏ&¥%§ÿÿ ÿèõÏ&¥Qÿÿ ÿè÷Ï&¥ÃÿÿÿæõÑ&-‡Åÿÿ ÿèöÏ&ú¥ÿÿ ÿéòË&N9šÿÿ ÿéòÏ&RNÿÿ ÿèòÏ&¥ÿÿ ÿçõÏ&¥ÿÿ ÿçõÑ& -‰ÿÿÿæõÑ&P9›ÿÿ ÿèöÐ&dÆÿÿ ÿèöÏ&dÇÿÿ ÿéóÐ&-ˆ9œÿÿ ÿèðÏ&d’ÿÿ ÿèóÏ&c-‰ÿÿ ÿè÷Ï&-‰Ìÿÿ ÿèòÏ&ü-‰ÿÿ ÿèöÐ&'-Šÿÿ ÿèõÓ&S-Šÿÿ ÿè÷Ñ&&Å-Šÿÿ ÿåöÓ&¨©ÿÿÿèôÑ&-‹9žÿÿ ÿèñÏ&-Š0"ÿÿ ÿèóÒ&-‰9Ÿÿÿ ÿèòÊ&E9 ÿÿ ÿêòÅ&Oÿÿ ÿçóÉ&MWÿÿ ÿéîÏ&LNÿÿÿèðÏ&›.ÿÿ ÿéôÍ&O-Œÿÿ ÿêëÏ&QPÿÿ ÿéòÏ&«ªÿÿÿéìË&SRÿÿ ÿçôË&UTÿÿ ÿèëÎ&VQÿÿ ÿêïÏ&G#éÿÿ ÿêóÐ&ObÿÿÿðòÉ&,µWÿÿ ÿéõÏ&X†ÿÿÿêëÏ&aQÿÿ ÿçóË&R9¢ÿÿ ÿèõÌ&O-Žÿÿ ÿêîÎ&O-ÿÿÿèãÊ&F-ÿÿ ÿèïÏ&G-ŠÿÿÿéóÑ&í Qÿÿ ÿèëÏ&Q ÿÿ ÿè÷Ï&ÿÿ ÿêñÐ&[&YZÿÿÿèóË&R Ýÿÿ ÿêôÏ&O_ÿÿÿéêË&R9£ÿÿÿéðË&R9¤ÿÿÿéïÐ& Qÿÿ ÿéïÇ&:9ÿÿ ÿêòË&R9¥ÿÿÿçòÏ&87ÿÿ ÿçôÒ&#§8ÿÿ ÿèôÊ&;"ÿÿ ÿèìÐ&^"½ÿÿ ÿè÷Ë&X#ÐÿÿÿéøÇ& î ïÿÿ ÿèóÍ&.9¦ÿÿ ÿèõÐ&X9§ÿÿÿéïÇ&:$ ÿÿÿéíÊ&;%ÿÿ ÿéõÏ&¦9¨ÿÿ ÿèëÐ&Q9©ÿÿ ÿéëÉ&äQÿÿÿéîÌ& Q/2ÿÿ ÿéõÏ&X9ªÿÿ ÿéòÏ&X+iÿÿ ÿêìÎ&%ˆ^ÿÿ ÿèìÐ&^%‰ÿÿ ÿéõÏ&-ÿÿ ÿéïÎ&:*ÔÿÿÿçïÌ&.zÿÿÿêìÏ&^]ÿÿ ÿéïË&-‘:ÿÿ ÿéìÏ&-’^ÿÿ ÿèòÏ&ïðÿÿ ÿêóÏ&[ZÿÿÿéñÏ&-“\ÿÿ ÿîñÏ&_9­ÿÿ ÿèóÅ&Æ Kÿÿ ÿéóÅ& ÆÿÿÿéôÌ&ÈÇÿÿÿéóÏ&}Çÿÿ ÿéóÏ&¤Éÿÿ ÿèôÏ&bùÿÿ ÿèõÏ&Eèÿÿ ÿèõÏ&­¹ÿÿ ÿèôÏ&è¤ÿÿ ÿèöÏ&¬­ÿÿ ÿèõÏ&­ÿÿ ÿèòÐ&è0öÿÿ ÿæìÏ&­9¯ÿÿ ÿéìÐ&ÝÎÿÿ ÿèôÏ&ž­ÿÿ ÿè÷Ñ&èçÿÿ ÿéïÐ&Ýßÿÿ ÿéïÐ&ú,9ÿÿ ÿèóÏ&èÿÿ ÿéìÐ&ú‘ÿÿ ÿéðÐ& uúÿÿ ÿèõÐ&ú&ûˆÿÿ ÿéôÐ&úüÿÿ ÿéôÐ&ú$7ÿÿ ÿéñÑ&ú$”ÿÿ ÿçõÐ&ú0›ÿÿ ÿçõÐ&Ý!Uÿÿ ÿçõÐ&Ýþÿÿ ÿèôÐ&ÿÝÿÿ ÿéòÏ&3-”ÿÿ ÿèóÇ&8qÿÿ ÿéðÇ&-”ÿÿ ÿêôÐ&¾9±ÿÿÿêðÒ&-•9²ÿÿ ÿèöÑ&0ž­ÿÿ ÿéõÊ&-–0žÿÿ ÿèôÏ&8-—ÿÿ ÿèôÏ&z0žÿÿ ÿé÷Ï&-˜0žÿÿ ÿèõÏ&-™!«ÿÿ ÿçõÏ&F8ÿÿ ÿéòÆ&-š0žÿÿ ÿéïÏ& ó&!I!¶ÿÿ ÿéïÏ&!«/¹ÿÿÿêðÑ&-«!·ÿÿ ÿéòÆ&&¶0žÿÿ ÿéòÎ&Ä8ÿÿ ÿéòÆ&Ê!«ÿÿ ÿéóÏ&0Ö8ÿÿ ÿèôÇ&&ç8ÿÿ ÿéóÏ&58ÿÿ ÿéöÎ&Ø8ÿÿ ÿïðÈ&-(9³ÿÿ ÿéóÏ&'!«ÿÿ ÿçôÉ&Î9´ÿÿ ÿéóÐ&É!«ÿÿ ÿéöÏ&0è!«ÿÿÿèíÉ&9µÿÿÿêðÐ&!·9¶ÿÿ ÿéõÊ&!«&!9·ÿÿ ÿéôÑ&!«Ìÿÿ ÿéòÐ&!´!¶ÿÿ ÿéõÍ&"‚!«ÿÿ ÿéöÎ&!«9¸ÿÿ ÿéôÏ&!¸8ÿÿ ÿéõÎ&!«&!9¹ÿÿ ÿéôÐ&-ž!«ÿÿ ÿèöÎ&-£!«ÿÿ ÿéòÑ&)Ì!«ÿÿÿéóÐ&#\&-Ÿ+ÿÿ ÿéîÏ&&-Ÿ9ºÿÿÿêôÏ&- 9»ÿÿ ÿé÷Ð&.!«ÿÿ ÿèõÏ&+ß8ÿÿ ÿéðË&®&¯°ÿÿ ÿéöÏ&F-¡ÿÿ ÿéóÒ&Ä!¶ÿÿ ÿéñÐ&-¢!¶ÿÿ ÿéôÏ&.9¼ÿÿ ÿê÷Ò&s-¤ÿÿ ÿåóÏ&‘ÿÿ ÿéóÐ&ˆ‰ÿÿ ÿéóÏ&‰ÿÿ ÿèòÐ&-¥9Àÿÿ ÿéôÉ&1›ÿÿ ÿèíÐ&1+@ÿÿ ÿéõÉ&1´ÿÿ ÿèôÑ&€ÿÿ ÿéòÎ&õÿÿ ÿéõÏ&õ›ÿÿ ÿéìÉ&œõÿÿ ÿéóÏ&1ÿÿ ÿéõÏ&õyÿÿ ÿéóÉ&õÊÿÿ ÿéõÍ&1õÿÿ ÿéóÉ&1&ÿÿ ÿéðÉ&1ÿÿ ÿéíÉ&3%9ÿÿ ÿèõÉ&õUÿÿ ÿèõÏ&õÿÿ ÿèöÑ&Ó3ÿÿ ÿéöÎ&õÄÿÿ ÿéõÉ&õÿÿ ÿéòÏ&õ0¬ÿÿ ÿéõÏ&õÿÿ ÿéôÏ&õ*Ìÿÿ ÿéîÏ&õ°ÿÿ ÿéöÏ&1aÿÿÿéòÏ&-¦-§ÿÿ ÿèôÏ&õmÿÿÿéëÑ&-¨9Åÿÿ ÿéóÊ&!rõÿÿ ÿéõÑ&1¶ÿÿ ÿéôÌ&õ9Çÿÿ ÿèïÏ&3Ùÿÿ ÿéõÏ&õEÿÿ ÿéôÏ&õ—ÿÿÿééÏ&-¨9Èÿÿ ÿéîÏ&õœÿÿ ÿèõÍ&õÿÿ ÿéòÏ&39Éÿÿ ÿéïÐ&õRÿÿ ÿéòÏ&õ(-ÿÿ ÿéõÉ&õêÿÿ ÿéõÏ&wõÿÿ ÿèôÐ&õÐÿÿ ÿéêÐ&M1ÿÿ ÿêìÐ&-®-¯ÿÿÿéïÏ&-¨9Êÿÿ ÿè÷Ï&ñÿÿ ÿéòÐ&õ0öÿÿ ÿèïÏ&õ'Ñÿÿ ÿéëÏ&´-®ÿÿ ÿçôÉ&õ%Ìÿÿ ÿèôÏ&ƒ‚ÿÿ ÿéóÏ&3&}/Õÿÿ ÿèóÏ&õÿÿ ÿéöÐ&3³ÿÿ ÿèóÏ&Ôÿÿ ÿéóÐ&3Uÿÿ ÿéòÉ&õ-©ÿÿ ÿèéÉ&õ/ÿÿ ÿéóÏ&1ÿÿ ÿèóÏ&õîÿÿ ÿéôÐ&3øÿÿ ÿéóÏ&õ"Wÿÿ ÿéêÏ&"X¡ÿÿ ÿéöÏ&3,éÿÿÿéóÇ&-¨-°ÿÿ ÿéöÏ&õ±ÿÿ ÿéóÏ&õ-ªÿÿ ÿéóÏ&õ-¬ÿÿ ÿéñÍ&õ*­ÿÿ ÿéòÏ&1(Ëÿÿ ÿéõÉ&3%"ÿÿÿèóÐ&0ƒ0„ÿÿ ÿéìÉ&õdÿÿ ÿéõÐ&3óÿÿ ÿè÷Ï&õ7ÿÿ ÿéöÌ&õóÿÿ ÿéôÐ&õ7ÿÿ ÿéõÐ&õÿÿ ÿéìÉ&3aÿÿ ÿéóÉ&3&ÿÿ ÿèôÐ&P3ÿÿ ÿéïÏ&3xÿÿ ÿéòÉ&Äõÿÿ ÿèóÏ&30¾ÿÿ ÿéôÎ&õ&»/ÿÿ ÿéóÎ&õåÿÿÿéõÐ&ñ&ò&óôÿÿÿêõÏ&÷9Ëÿÿ ÿéðÌ&-,ÿÿ ÿé÷Ì&E-­ÿÿ ÿéðÏ&õ.ÿÿ ÿèòÑ&ÈÉÿÿ ÿéõÑ&23ÿÿ ÿéôÌ&õ+ÿÿ ÿèòÐ&÷ÿÿ ÿéõÐ&30¯ÿÿ ÿèõÏ&õZÿÿ ÿéóÎ&õ¥ÿÿ ÿéóÑ&õÕÿÿ ÿèôÑ&õ¦ÿÿ ÿéòÐ&3&&*ÿÿ ÿéôÏ&õ…ÿÿ ÿçôÏ&3×ÿÿÿèïÏ&z0„ÿÿ ÿéøÑ&3 ÿÿ ÿéôÏ&õ,Ëÿÿ ÿçõÏ&õÿÿ ÿéòÏ&3-±ÿÿ ÿèïÏ&&[9Ìÿÿ ÿéòÉ&©õÿÿ ÿéóÏ&ôõÿÿ ÿéóÏ&3.–ÿÿ ÿèôÍ&3(8ÿÿ ÿèõÎ&0xÿÿ ÿèõÐ&„ÿÿ ÿèôÊ&A9Íÿÿ ÿèôÉ&.œ3ÿÿ ÿçõÏ&3 Øÿÿ ÿçõÑ& -­ÿÿ ÿéôÑ&W9Îÿÿ ÿèôÏ&õcÿÿ ÿéóÐ&õ³ÿÿ ÿéòÉ&õ0Aÿÿ ÿéóÒ&30ìÿÿ ÿéôÉ&ø3ÿÿ ÿéôÏ&õbÿÿ ÿèöÉ&9Ïÿÿ ÿéíË&3Øÿÿ ÿéîÉ&õ-Bÿÿ ÿéõÏ&3)zÿÿ ÿèõÉ&-aõÿÿ ÿèòÏ&3.ÿÿ ÿéõÐ&õ‹ÿÿ ÿèôÐ&3]ÿÿ ÿéõÐ&3+Lÿÿ ÿéôÏ&3.’ÿÿ ÿçõÏ&3¨ÿÿ ÿèõÑ&3‰ÿÿ ÿéóÏ&õ'ÿÿ ÿéóÏ&1ÿÿ ÿéóÐ&3.ÿÿ ÿéöÏ&3'ñÿÿ ÿéõÑ&3 ÿÿÿèñÈ&É0Íÿÿ ÿèöÉ& ×ÿÿ ÿèðÑ&$9ÐÿÿÿèóÐ&Ä ÿÿ ÿéðÉ&»õÿÿ ÿéóÐ&3 Oÿÿ ÿéóÐ&Éõÿÿ ÿèöÏ&"Hÿÿ ÿéõÑ&3Oÿÿ ÿéôÑ&3Íÿÿ ÿèõÉ&ÿÿ ÿæõÏ&3"dÿÿ ÿéôÏ&3"ôÿÿ ÿéóÐ&3Hÿÿ ÿèöÏ&01ÿÿ ÿéñÉ&õ!Œÿÿ ÿéîÉ&3 xÿÿ ÿéóÑ&3Xÿÿ ÿéóÉ&3Óÿÿ ÿéñÏ&3¿ÿÿ ÿéöÏ&3Çÿÿ ÿéòÏ&3"¹ÿÿ ÿéôÏ&õ!_ÿÿ ÿéõÊ&õÿÿ ÿé÷É&3¥ÿÿ ÿèóÍ&õ!‹ÿÿ ÿçòÉ&3ÿÿÿèôÏ&Ä0tÿÿ ÿèóÉ&3#;ÿÿÿèïÉ&Ä&[ÿÿ ÿèõÐ&3,Òÿÿ ÿèôÏ&&##(ÿÿ ÿéíÉ&53ÿÿ ÿçõÉ&õ!0ÿÿ ÿèðÐ&3!%ÿÿ ÿéöÐ&õ#Zÿÿ ÿèóÐ&!ôÿÿ ÿéòÉ&3"Kÿÿ ÿéôÉ&3Èÿÿ ÿèôÐ&9ÑÿÿÿèöÉ&Ä&Jâÿÿ ÿéðÑ&3$•ÿÿ ÿéïÐ&&ÿÿ ÿéôÉ&3$Eÿÿ ÿéòÎ&3.žÿÿ ÿéóÑ&3&#s#tÿÿ ÿéðÉ&3$ ÿÿ ÿéòÏ&3$Iÿÿ ÿéöÏ&õvÿÿÿéðÏ&ð& ÿÿ ÿéöÒ&3/ÿÿ ÿéóÏ&3,uÿÿ ÿèòÑ&ÿÿ ÿèòÐ&3/Mÿÿ ÿéóÑ&3(“ÿÿ ÿèîÉ&oÿÿ ÿéòÏ&-;3ÿÿ ÿéóÉ&3†ÿÿ ÿèõÉ&~ÿÿ ÿèóÏ&¦ÿÿ ÿèöÏ&Nÿÿ ÿé÷Ï&39Òÿÿ ÿéóÏ&3*ÿÿ ÿéõÐ&3.•ÿÿ ÿé÷Í&3ÿÿ ÿçõÏ&õ0›ÿÿ ÿéóÑ&3&/3ÿÿ ÿèïÉ&4ÿÿ ÿéôÎ&3*Ñÿÿ ÿêóÏ&´-­ÿÿÿè÷Ï&Ä&n9Óÿÿ ÿèôÏ&/\ÿÿ ÿèóÐ&39Ôÿÿ ÿèôÏ&* ÿÿ ÿèøÑ&.Ÿÿÿ ÿèôÎ&%(ÿÿ ÿéòÏ&3üÿÿÿèöÏ&ÄFÿÿ ÿéöÏ&3'õÿÿ ÿéòÐ&3*¹ÿÿ ÿèöÐ&.#ÿÿ ÿèôÏ&9Õÿÿ ÿèóÑ&0š&. 0™ÿÿ ÿèõÐ&&G9Öÿÿ ÿèöÐ&3'ÿÿ ÿéðÑ&$9×ÿÿ ÿèõÓ&SÿÿÿèõÉ&Ä9Øÿÿ ÿèóÒ&Äÿÿ ÿéöÒ&*€3ÿÿ ÿçôÏ&-xÿÿ ÿéóÌ&-&k9Ùÿÿ ÿèôÑ&.¡ÿÿ ÿéòÐ&.ÿÿÿèôÐ&Ä.¢ÿÿ ÿèõÏ&/dÿÿÿèñÉ&Ä'ÿÿ ÿè÷Ñ&.£ÿÿ ÿèðÏ&,4ÿÿ ÿéðÎ&3,…ÿÿ ÿé÷Ñ&&Å3ÿÿ ÿéøÊ&3¡ÿÿ ÿèõÏ&&/‹9Úÿÿ ÿéõÑ&35ÿÿ ÿçóÑ&¡3ÿÿ ÿèõÐ&¢ÿÿ ÿèñÊ&0"ÿÿ ÿéõÏ&3!×ÿÿÿèöÏ&Ä9ÛÿÿÿèòÐ&Ä.¤ÿÿ ÿèóÏ&9Üÿÿ ÿç÷Ò&±3ÿÿÿçôÐ&Ä+(ÿÿ ÿéóÑ&3Îÿÿ ÿèòÉ&.¥ÿÿ ÿéõÓ&§9Ýÿÿ ÿéòË&3.¨ÿÿÿèùÏ&Ä9Þÿÿ ÿèðÓ&„…ÿÿÿèïÐ&†9ßÿÿÿçõÐ&‡ÄÿÿÿèöÌ&®¬ÿÿÿéòÏ&&*Ü9ãÿÿ ÿèôÒ&ÎØÿÿ ÿèóÒ&Î9åÿÿÿéóÒ&Ï9æÿÿ ÿêõÑ&‘.áÿÿ ÿèõÏ&--ÿÿÿéõÐ&.ÿÿ ÿîóÄ&T2ÿÿ ÿçøÏ&1?ÿÿ ÿéóÐ&1$pÿÿÿèâÎ&¤9éÿÿ ÿçóÌ&ö9êÿÿ ÿéöÐ&.«9ïÿÿ ÿéñÏ&n9ðÿÿ ÿéôÏ&n ÒÿÿÿèõÏ&.©.ªÿÿ ÿèïÐ&)*ÿÿÿé÷Î&p.©ÿÿÿèóÏ&ñ.©ÿÿ ÿèóÐ&,+ÿÿ ÿéóÐ&.­,ÿÿ ÿèòÐ&+Vÿÿ ÿéìÑ&Çÿÿ ÿéòÑ&ÿÿ ÿéëÑ&aÿÿ ÿèõÑ&*ÿÿ ÿéõÑ&ÿÿ ÿéóÑ&Ãÿÿ ÿéóÑ&+ÿÿ ÿéöÑ&aÿÿ ÿéóÑ&…ÿÿ ÿèõÑ&Tÿÿ ÿé÷Ñ&ÿÿ ÿéìÑ&(¾ÿÿ ÿéíÑ&.®ÿÿ ÿèôÑ&,ÿÿ ÿéõÑ&-ÿÿ ÿçõÏ&A¦ÿÿ ÿéëÑ&·ÿÿ ÿæïÑ&¸ÿÿ ÿéðÑ&jÿÿ ÿéêÑ&Mÿÿ ÿéðÑ&.}ÿÿ ÿéóÑ&.ÿÿ ÿéòÑ&*¬ÿÿ ÿèéÑ&/ÿÿ ÿéòÑ&(Ëÿÿ ÿèñÑ&8ÿÿ ÿéóÑ&{ÿÿ ÿéðÑ&0ÿÿ ÿéöÑ&Òÿÿ ÿéóÑ&²ÿÿ ÿéôÑ&ÿÿ ÿèìÑ&!Xÿÿ ÿéõÑ&dÿÿ ÿéõÑ& 0wÿÿ ÿèóÑ&Õÿÿ ÿèôÏ&A =ÿÿ ÿèõÑ&A1ÿÿ ÿéóÑ&!›ÿÿ ÿèöÏ&A#Íÿÿ ÿçíÑ&: ÿÿ ÿèéÏ&A ‰ÿÿ ÿèõÏ&A¯ÿÿ ÿéðÑ& $•ÿÿ ÿèòÐ&A2ÿÿ ÿéòÑ& $Jÿÿ ÿèòÏ&A0¤ÿÿ ÿèõÐ&A$(ÿÿ ÿèóÏ&A3ÿÿ ÿçõÏ&A0›ÿÿ ÿéòÑ& 0ÿÿ ÿéïÑ& 4ÿÿ ÿçóÑ& &D9õÿÿ ÿéóÑ&´ ÿÿ ÿèñÏ&A*wÿÿ ÿèòÏ&A.¯ÿÿ ÿèðÑ&,3 ÿÿ ÿèóÐ&A*Äÿÿ ÿèóÐ&A*yÿÿ ÿéñÑ& (ÿÿ ÿéõÑ&%x5ÿÿ ÿéõÒ&.° ÿÿ ÿéóÑ& 6ÿÿ ÿèòÑ&7.±ÿÿ ÿèòÏ&.².±ÿÿ ÿêõÉ&~ÿÿ ÿéõÏ&€ÿÿ ÿéöÐ&uvÿÿ ÿèôÐ&xwÿÿ ÿêôÑ&‚)™ÿÿ ÿéôÐ&yzÿÿÿéôÐ&€zÿÿÿðñÐ&v(ÿÿÿêñÐ&uvÿÿ ÿèõÏ&öõÿÿ ÿéóÏ&õ9÷ÿÿ ÿéðÏ&"õÿÿÿèñÐ&vÏÿÿÿèñÐ&vçÿÿÿéôÐ&v.³ÿÿ ÿêóÐ&v(ÿÿ ÿéóÏ&m9øÿÿÿéðÏ&õ¹ÿÿÿéñÏ&mîÿÿÿéñÏ&m½ÿÿÿéñÏ&m9úÿÿÿóòÏ&õ9ûÿÿÿéñÐ&vdÿÿÿíñÐ&v9üÿÿÿòðÏ&õ9ýÿÿ ÿéòÐ&v9þÿÿÿéñÏ&mHÿÿÿéòÏ&;õÿÿÿéñÐ&v:ÿÿÿêóÏ&m:ÿÿÿèòÏ&Nmÿÿ ÿéòÏ&m:ÿÿÿéöÏ&õIÿÿ ÿéòÏ&Ñ:ÿÿÿêòÏ&Ñ:ÿÿ ÿéôÐ&v:ÿÿÿêñÐ&v:ÿÿ ÿéòÏ&m:ÿÿ ÿéñÏ&m:ÿÿ ÿêòÏ&m: ÿÿ ÿèõÐ&v¨ÿÿÿéñÏ&m: ÿÿ ÿéñÏ&m: ÿÿÿèõÐ&ˆvÿÿ ÿéóÏ&õÿÿÿéòÏ&m: ÿÿ ÿéòÏ&m ðÿÿÿéõÏ&m: ÿÿÿîñÐ&v“ÿÿÿéñÏ&m:ÿÿ ÿïóÏ&õ:ÿÿ ÿéôÐ&v:ÿÿÿèóÏ&m:ÿÿÿéñÐ&v$˜ÿÿÿèñÐ&v:ÿÿÿéòÏ&m:ÿÿÿéñÏ&Ômÿÿ ÿóòÏ&õ:ÿÿ ÿéóÐ&v:ÿÿ ÿèñÏ&m:ÿÿ ÿêôÏ&((mÿÿÿìòÏ&ÑÔÿÿÿèñÏ&Ñ:ÿÿÿéñÐ&ÚvÿÿÿéñÏ&ç&æmÿÿÿêôÐ&vÿÿ ÿêóÏ&õ:ÿÿ ÿéóÐ&%ûvÿÿ ÿêöÏ&?mÿÿ ÿêóÐ&v:ÿÿ ÿéðÏ&õ&!"ÿÿÿéñÏ&m:ÿÿ ÿéñÏ&m:ÿÿÿêñÏ&m:ÿÿÿéòÏ&m:ÿÿ ÿéòÏ&m:ÿÿ ÿìòÏ&m: ÿÿÿõðÏ&õ:!ÿÿ ÿéôÏ&õ:"ÿÿ ÿëóÏ&m:#ÿÿ ÿéóÏ&m:$ÿÿ ÿæöÐ&vZÿÿÿéñÏ&mpÿÿ ÿéñÐ&}vÿÿÿéñÐ&v:%ÿÿÿèñÐ&v)ãÿÿÿéñÐ&v:&ÿÿÿôñÐ&v:'ÿÿÿèñÏ&m:(ÿÿÿéñÏ&m:)ÿÿ ÿíòÏ&Ñ:*ÿÿ ÿéñÏ&m:+ÿÿÿèñÏ&mÿÿÿèñÏ&m:,ÿÿÿïñÏ&m:-ÿÿ ÿñòÐ&v:.ÿÿÿéñÐ&v:/ÿÿ ÿéòÐ&v:0ÿÿÿéñÏ&m&r:1ÿÿ ÿêóÏ&Ñ:2ÿÿÿéñÏ&m:3ÿÿÿéñÐ&v:4ÿÿÿéñÏ&/pmÿÿÿêñÏ&Ñ:5ÿÿ ÿéóÐ&v:6ÿÿÿéòÏ&m:7ÿÿ ÿéóÏ&m:8ÿÿÿéñÏ&m:9ÿÿ ÿéôÏ&Ñ::ÿÿ ÿðõÏ&mÞÿÿ ÿéóÏ&m:;ÿÿ ÿìóÐ&v:<ÿÿÿæñÏ&m:=ÿÿ ÿéóÏ&m:>ÿÿ ÿéóÏ&k:?ÿÿ ÿéñÏ&m:@ÿÿ ÿéóÏ&m:AÿÿÿëñÐ&ÕvÿÿÿèñÏ&vÑÿÿÿéñÐ&v:Bÿÿ ÿéóÐ&v:CÿÿÿêõÏ&m&ÛÞÿÿ ÿæñÏ&m:Dÿÿ ÿéñÏ&mŸÿÿÿæñÏ&m ÿÿ ÿêóÏ&m:Eÿÿ ÿéóÐ&v:Fÿÿ ÿéóÏ&m:GÿÿÿéñÏ&ç&m:Hÿÿ ÿñóÏ&Ñ:JÿÿÿéñÏ&m:KÿÿÿèñÏ&m:Lÿÿ ÿñòÐ&v&AWÿÿÿéòÏ&m& :Mÿÿ ÿèóÏ&l€ÿÿ ÿéñÏ&Ñ·ÿÿÿêñÏ&Ñÿÿ ÿéóÏ&m:NÿÿÿéñÏ&m:OÿÿÿéôÏ&DmÿÿÿéñÏ&Ñ:PÿÿÿðñÏ&+&mÿÿÿéòÏ&Ñ&ç:Qÿÿ ÿéñÏ&m&£:Rÿÿ ÿéóÏ&Ñ:SÿÿÿæñÏ& ÑÿÿÿîñÏ&m:Tÿÿ ÿéóÏ&m‹ÿÿÿéðÏ&õ:Uÿÿ ÿéóÏ&m:Vÿÿ ÿéñÏ&m:WÿÿÿéôÏ&ñmÿÿÿëñÏ&m&5:Xÿÿ ÿêñÏ&mVÿÿÿèóÐ&§vÿÿÿéôÏ&Ñ&çêÿÿÿéóÏ&m:Yÿÿ ÿéòÏ&m:ZÿÿÿëñÐ&5&8vÿÿÿéñÏ&mvÿÿÿéñÏ&ÑeÿÿÿéóÏ&ç&JmÿÿÿçñÏ&m:[ÿÿÿéòÏ&m:\ÿÿÿèñÏ&ªmÿÿ ÿéóÏ&m:^ÿÿ ÿéñÏ&Ñ#®ÿÿÿñòÏ&#¯mÿÿÿèóÏ&ÑÓÿÿ ÿîôÏ&”õÿÿÿçñÏ&ÑÿÿÿèõÐ&vßÿÿ ÿéëÏ&›:_ÿÿÿéóÏ& &m:`ÿÿ ÿéóÏ&mÛÿÿ ÿéñÏ&m&¢£ÿÿÿéñÏ&m:aÿÿÿèñÏ&m:bÿÿÿéóÏ&Ñ:cÿÿÿîñÏ&0ÑÿÿÿéñÏ&m&ç:dÿÿ ÿéòÏ&Ñ:eÿÿÿçòÏ&8mÿÿ ÿéöÏ&m:fÿÿ ÿéñÏ&Ñ:gÿÿ ÿêñÏ&m:hÿÿ ÿìòÏ&Ñ`ÿÿ ÿêñÏ&Ñ&ÿÿÿéòÏ&m:iÿÿÿèñÏ& ÑÿÿÿéòÏ&m+ÿÿ ÿèóÏ&Ñ:jÿÿ ÿéôÐ&[vÿÿÿèñÏ&mìÿÿÿåñÏ&&mÛÿÿÿéñÏ&m:kÿÿ ÿèôÏ&m& ÙÿÿÿéñÏ&ѯÿÿ ÿèòÐ&&vWÿÿÿèñÏ&%¶& mÿÿ ÿèóÐ&v&W ÿÿÿéòÏ&Ñÿÿ ÿèòÏ&mÿÿÿèñÐ&H&vGÿÿÿèñÏ&m:lÿÿÿéñÏ&mýÿÿÿìñÐ&v:mÿÿÿêòÏ&m:nÿÿÿêñÏ&m&¾:oÿÿÿïñÏ&ÑÿÿÿèõÏ&Ñ&t:pÿÿ ÿëõÏ&Ñ&#„ÿÿÿé÷Ï&Ñ:qÿÿÿéñÏ&Ñ&ç§ÿÿÿèñÏ&Ñ:rÿÿÿêñÏ&m:sÿÿÿéõÏ&m&%ü9ÿÿ ÿèóÏ&m:tÿÿ ÿçóÏ&‘mÿÿÿçóÏ&k:uÿÿ ÿèñÏ&Ñ;ÿÿÿññÏ&m:vÿÿ ÿèòÏ&k:wÿÿÿéôÏ&Ñ&ç:xÿÿÿèñÏ&m:yÿÿÿéñÏ&mÞÿÿ ÿçõÏ&Ñ&r:zÿÿ ÿèñÏ&Ñ&£:{ÿÿÿçñÏ&Ñ:|ÿÿÿéñÏ&Ñ&ç:}ÿÿ ÿèôÏ&kˆÿÿÿèôÏ&Ñÿÿ ÿçõÏ&Ñ&r:~ÿÿÿèñÏ&t&Ñ:ÿÿÿèôÏ&Ñ&5:€ÿÿÿèñÏ&m:ÿÿÿèôÏ&kˆÿÿÿéñÏ&Ñ:‚ÿÿÿéôÏ&Ñ:ƒÿÿÿíñÏ&ѵÿÿ ÿéôÏ&Ñ:„ÿÿ ÿéóÏ&m:…ÿÿ ÿéòÏ&l:†ÿÿ ÿéóÏ&Ñ:‡ÿÿÿéôÏ&m:ˆÿÿ ÿçôÏ&m:‰ÿÿÿññÏ&Ñ:ŠÿÿÿçõÏ&m:‹ÿÿ ÿéòÏ&Ñ:Œÿÿ ÿèñÏ&k:ÿÿ ÿçóÏ&Ñ Þÿÿ ÿîõÏ&§&Ñ:ŽÿÿÿêñÏ&Ñ:ÿÿÿéñÏ&Ñ&9:ÿÿÿéñÏ&l:‘ÿÿ ÿçõÏ&lóÿÿÿéòÏ&Ñ:’ÿÿ ÿèóÏ&Ñ:“ÿÿÿçôÏ&Ñ:”ÿÿ ÿéóÏ&Ñ:•ÿÿÿçñÏ&Ñ:–ÿÿÿé÷Ï&m&5:—ÿÿÿèñÐ&v:˜ÿÿ ÿéòÏ&ÑÿÿÿëöÏ&m:™ÿÿÿéõÏ&m:šÿÿÿçñÏ&Ñ:›ÿÿÿéôÏ&m:œÿÿ ÿòòÐ&v*ŽÿÿÿçñÏ&m&9:ÿÿÿéõÏ&9&8mÿÿÿññÏ&m:žÿÿÿèñÏ&l:ŸÿÿÿéôÏ&Ñ: ÿÿ ÿéñÏ&l&æ:¡ÿÿ ÿåóÏ&m:¢ÿÿÿèñÏ&l:£ÿÿÿêñÏ&Ñ&¿:¤ÿÿÿëôÏ&5&Ñ:¥ÿÿ ÿéöÏ&Ñ ÿÿÿèñÏ&Ulÿÿ ÿéóÏ&Ñ:¦ÿÿÿãñÏ&m:§ÿÿÿêòÏ&Š&m:¨ÿÿÿëôÏ&Ñ:©ÿÿÿèñÏ&Ñ ÿÿÿéóÏ&m:ªÿÿÿéñÏ&ÑÒÿÿÿçóÏ&l:«ÿÿÿèóÏ&m:¬ÿÿÿéñÏ&Ñ:­ÿÿÿèñÏ&¯lÿÿÿéòÏ&m:®ÿÿ ÿéòÏ&m #ÿÿÿèñÏ&l²ÿÿÿèñÏ&Ñ&Š:¯ÿÿÿéóÏ&Ñ&:°ÿÿÿîôÏ&k:±ÿÿÿéñÏ&l1 ÿÿÿèñÏ&Ñ:²ÿÿÿèôÏ&m:³ÿÿ ÿéñÏ&Ñ&œ:´ÿÿÿçõÏ&l:µÿÿ ÿçôÏ&Ñ&r:¶ÿÿÿêñÏ&Ñ:·ÿÿ ÿëôÏ&m:¸ÿÿÿçõÏ&m:¹ÿÿÿèóÏ&Ñ:ºÿÿ ÿèòÏ&Ñ:»ÿÿ ÿèñÏ&Ñ:¼ÿÿ ÿæóÏ&Ñ:½ÿÿ ÿèóÏ&Ñ:¾ÿÿÿèñÏ&Ñ:¿ÿÿÿîñÏ&n:ÀÿÿÿèñÏ&5&Ñ:Áÿÿ ÿèñÏ&ÑtÿÿÿçñÏ&Ñ:Âÿÿ ÿéòÏ&Ñ:ÃÿÿÿèôÏ&Ñ:ÄÿÿÿéñÏ&Ñ Rÿÿ ÿñóÏ&m:Åÿÿÿí÷Ï&l:ÆÿÿÿéñÏ&Ñ:ÇÿÿÿéôÏ&l:ÈÿÿÿêñÏ&FÑÿÿÿèôÏ&m:ÉÿÿÿêñÏ&Û&m:Êÿÿ ÿçñÏ&Ñ:Ëÿÿ ÿéöÏ&Ñ:ÌÿÿÿèóÏ&òmÿÿ ÿéôÏ&Ñ:Íÿÿ ÿéóÏ&Ñ ÿÿ ÿéñÏ&4&£ÑÿÿÿçñÏ&Ñ:Îÿÿ ÿéóÏ&Ñqÿÿ ÿèñÏ&l:ÏÿÿÿéñÏ&Ñ:ÐÿÿÿéñÏ&l:Ñÿÿ ÿêöÏ&Ñ:Òÿÿ ÿéóÏ&m:ÓÿÿÿçñÏ&lßÿÿÿèñÏ&m:Ôÿÿ ÿçñÏ&Ñ&r:ÕÿÿÿçñÏ&_žÿÿÿèñÏ&m:Öÿÿ ÿéõÏ&l:×ÿÿ ÿèñÏ&Ñ:Øÿÿ ÿçôÏ&l:ÙÿÿÿîñÏ&lÙÿÿ ÿéóÏ&m Aÿÿ ÿéóÏ&Ñ:ÚÿÿÿèñÏ&Ñ:ÛÿÿÿèñÏ&Ñ:Üÿÿ ÿéñÏ&Ñ:Ýÿÿ ÿçöÏ&Ñ&r:Þÿÿ ÿêõÏ&ÎÑÿÿÿçñÏ&m&  ÿÿÿèñÏ&Ñ:ßÿÿÿçñÏ&Ñ:àÿÿ ÿæñÏ&l:áÿÿÿçñÏ&Ñ&ÑÒÿÿ ÿéôÏ&Ñ"Ìÿÿ ÿéóÏ&Ñ&œ›ÿÿ ÿéõÏ&Ñ"“ÿÿÿèñÏ&l:âÿÿÿçñÏ&Ñ:ãÿÿÿèñÏ&n"vÿÿ ÿéôÏ&ÑÚÿÿÿññÏ&Ñ"¡ÿÿ ÿèòÏ&k"¨ÿÿÿéñÏ&"ºÑÿÿÿçñÏ&Ñ&£:äÿÿÿèñÏ&l:åÿÿ ÿéñÏ&k"[ÿÿÿêñÏ&æ&Ñ:æÿÿ ÿèôÏ&Ñ:çÿÿ ÿéñÏ&"¾ÑÿÿÿéñÏ&l ŠÿÿÿéñÏ&!ÈÑÿÿ ÿêóÏ&n"Òÿÿ ÿçöÏ&Ñ&r:èÿÿ ÿçòÏ&r&qÑÿÿÿéòÏ&t&slÿÿ ÿéôÏ& & lÿÿÿéóÏ&u&vÑÿÿ ÿéñÏ&w&l:éÿÿ ÿìõÏ&Ñ:êÿÿ ÿéöÏ&l"Çÿÿ ÿéôÏ&l#Kÿÿ ÿéôÏ&n&q:ëÿÿ ÿéñÏ&l:ìÿÿÿçôÏ&l:íÿÿÿêñÏ&l" ÿÿ ÿéòÏ&l0~ÿÿ ÿîóÏ&l& ù:îÿÿÿêóÏ&Ñ:ïÿÿ ÿêñÏ&m:ðÿÿ ÿèöÏ&l:ñÿÿÿéôÏ&Ñ:òÿÿÿèñÏ&Ñ:óÿÿ ÿéñÏ&msÿÿÿðñÏ&#RlÿÿÿçñÏ&l:ôÿÿ ÿçôÏ&#›Ñÿÿ ÿéñÏ&l:õÿÿÿèóÏ&Ñ:öÿÿ ÿíóÏ&Ñ"²ÿÿ ÿèñÏ&"Ð&Tlÿÿ ÿêñÏ&l:÷ÿÿ ÿïôÏ&Ñ:øÿÿÿðñÏ&n!cÿÿÿéñÏ&#¨lÿÿÿèñÏ&n"ùÿÿ ÿêñÏ&l:ùÿÿÿèñÏ&#<lÿÿÿìñÏ&l:úÿÿÿëñÏ&l#Øÿÿ ÿçóÏ&l&p:ûÿÿÿéñÏ&Ñ ÿÿ ÿéõÏ&l&#$#%ÿÿ ÿéñÏ&m#Ëÿÿ ÿçõÏ&Ñ:üÿÿ ÿëõÏ&l&"‘„ÿÿ ÿæõÏ&Ñ:ýÿÿ ÿèñÏ&Ñ!ÿÿ ÿçóÏ&l"}ÿÿÿèñÏ&!¹ÑÿÿÿéñÏ&n!$ÿÿÿêòÏ&"+&€mÿÿ ÿèñÏ&l"ÃÿÿÿæòÏ&n"rÿÿÿèòÏ&l&:þÿÿ ÿëöÏ&„&l:ÿÿÿÿéñÏ&l"ÿÿÿé÷Ï&l;ÿÿ ÿçöÏ&Ñ&#;ÿÿ ÿêóÏ&o;ÿÿÿéõÏ&#ÒÑÿÿÿéñÏ&l"ÿÿ ÿéòÏ&l ÿÿÿæñÏ&#¹mÿÿÿéñÏ&Ñ#ðÿÿ ÿçóÏ&ø&rmÿÿÿèñÏ&l;ÿÿÿèõÏ&Ñ#ùÿÿ ÿèöÏ&Ñ;ÿÿÿéôÏ&l&u;ÿÿÿè÷Ï&5&Ñ;ÿÿÿèñÏ&n;ÿÿÿéñÏ&n$ ÿÿ ÿèóÏ&Ñ&t;ÿÿ ÿéõÏ&m; ÿÿ ÿéñÏ&$~lÿÿ ÿçóÏ&l&r; ÿÿÿéñÏ&l; ÿÿ ÿëõÏ&l$‹ÿÿÿèñÏ&$¨lÿÿÿéôÏ&Ñ&Ò; ÿÿÿêôÏ&5&Ñ; ÿÿ ÿêóÏ&o;ÿÿ ÿèñÏ&l;ÿÿ ÿéñÏ&œ&Ñ;ÿÿÿçñÏ&$xkÿÿ ÿïñÏ&l#ÿÿÿèóÏ&n;ÿÿ ÿêñÏ&k;ÿÿ ÿéñÏ&Ñ$®ÿÿ ÿèïÏ& ;ÿÿ ÿçòÏ&Ñ;ÿÿ ÿéñÏ&l;ÿÿ ÿçñÏ&o&r;ÿÿÿèñÏ&Á&Âmÿÿ ÿêõÏ&Ñ;ÿÿ ÿèõÏ&l&qpÿÿÿéñÏ&j;ÿÿ ÿçôÏ&m;ÿÿÿéñÏ&n;ÿÿ ÿéóÏ&l$)ÿÿ ÿìôÏ&l&g;ÿÿÿçñÏ&%lÿÿÿéöÏ&l;ÿÿ ÿçôÏ&l$qÿÿÿçñÏ&l;ÿÿÿéñÏ&l;ÿÿ ÿç÷Ï&Ñ&rBÿÿ ÿéñÏ&kZÿÿÿéñÏ&#)lÿÿ ÿéôÏ&l$ŽÿÿÿéñÏ&n;ÿÿ ÿîóÏ&Ñ& ù; ÿÿÿêñÏ&l;!ÿÿ ÿéñÏ&n&f;"ÿÿ ÿçôÏ&l$ÿÿÿçôÏ&Ñ;#ÿÿ ÿéóÏ&n;$ÿÿ ÿçóÏ&l;%ÿÿ ÿéôÏ&l;&ÿÿÿéòÏ&ç&Ñ;'ÿÿ ÿèõÏ&t&l;(ÿÿÿíòÏ&l;)ÿÿ ÿéõÏ&Ñ&&ÿÿ ÿèñÏ&Ñ;*ÿÿ ÿéóÏ&Ñ&Þüÿÿ ÿéôÏ&l&ŒÿÿÿéñÏ&l;+ÿÿ ÿîóÏ&m& ø;,ÿÿ ÿêñÏ&æ&Ñ;-ÿÿÿèñÏ&l;.ÿÿ ÿæñÏ&Ñ;/ÿÿ ÿïôÏ&Ñ;0ÿÿ ÿèóÏ&n;1ÿÿ ÿéòÏ&l&üýÿÿ ÿéòÏ&l&ü;2ÿÿ ÿêóÏ&l;3ÿÿÿèöÏ&n;4ÿÿÿèöÏ&l;5ÿÿÿèöÏ&l;6ÿÿÿèöÏ&k;7ÿÿÿéöÏ&Ñ& ;8ÿÿÿéóÏ& &m;9ÿÿ ÿçòÏ&Ñ&r;:ÿÿÿðñÏ&Ñ;;ÿÿÿíòÏ&%lÿÿÿéñÏ&n;<ÿÿÿéòÏ&Ñ;=ÿÿÿéòÏ&l&t;>ÿÿÿéñÏ&Ñ;?ÿÿÿéöÏ&l;@ÿÿ ÿéõÏ&l0 ÿÿ ÿèõÏ&l;AÿÿÿèñÏ&n;BÿÿÿéñÏ&orÿÿ ÿèñÏ&l;CÿÿÿêñÏ&n;DÿÿÿéñÏ&n;Eÿÿ ÿçõÏ&l&r;FÿÿÿçñÏ&Ñ;GÿÿÿéñÏ&n;Hÿÿ ÿèóÏ&Ñ;Iÿÿ ÿéôÏ&Ñ;Jÿÿ ÿéöÏ&l;KÿÿÿèóÏ&Ñ;Lÿÿ ÿçõÏ&Ñ;MÿÿÿéõÏ&Ñ;NÿÿÿéñÏ&l•ÿÿ ÿéóÏ&Ñ;Oÿÿ ÿèõÏ&p&n;PÿÿÿèñÏ&n;Qÿÿ ÿêñÏ&l^ÿÿÿéöÏ&l;Rÿÿ ÿéòÏ&ž;Sÿÿ ÿçñÏ&l;Tÿÿ ÿèöÏ&k'òÿÿ ÿèöÏ&0_lÿÿ ÿéòÏ&Ñ;Uÿÿ ÿèóÏ&Ñ;VÿÿÿèôÏ&Ñ;WÿÿÿæõÏ&n;Xÿÿ ÿçòÏ&Ñ0[ÿÿÿëóÏ&&Ñ;Yÿÿ ÿêòÏ&l;Zÿÿ ÿéóÏ&l;[ÿÿÿéñÏ&l;\ÿÿ ÿìôÏ&g&l;]ÿÿÿéñÏ&l;^ÿÿÿçñÏ&l/QÿÿÿéñÏ&k;_ÿÿÿéñÏ&l;`ÿÿÿéñÏ&l;aÿÿÿèóÏ&l;bÿÿ ÿèôÏ&l&DCÿÿÿèôÏ&jpÿÿÿèñÏ&nmÿÿÿéöÏ&&kÒÿÿ ÿéøÏ&n/Xÿÿ ÿçòÏ&l;cÿÿ ÿêôÏ& & kÿÿÿèòÏ& lÿÿÿçñÏ& mÿÿÿçòÏ&Ñ;dÿÿÿæñÏ&Ú&ÛkÿÿÿçôÏ&Ñ;eÿÿÿèñÏ&l;fÿÿÿèôÏ&Ñÿÿ ÿèôÏ&n&Ø;gÿÿ ÿéñÏ&k;hÿÿ ÿéôÏ&l;iÿÿÿêòÏ&lkÿÿ ÿéòÏ&k;jÿÿÿéñÏ&l&ù;kÿÿ ÿçñÏ&l&r zÿÿ ÿéôÏ& &oÿÿ ÿèôÏ&n;lÿÿ ÿéóÏ&n;mÿÿ ÿèõÏ&k&p;nÿÿ ÿíõÏ&lJÿÿÿìñÏ&Klÿÿ ÿçõÏ&k&r;oÿÿ ÿèôÏ&ž;pÿÿÿéñÏ&l;qÿÿÿæòÏ&n:ÿÿÿéôÏ&l&Ä;rÿÿ ÿèñÏ&l&!ž!ÿÿ ÿçòÏ&l;sÿÿÿçõÏ&k;tÿÿÿèòÏ&n;uÿÿÿêñÏ&S&l;vÿÿ ÿéôÏ&n;wÿÿ ÿçñÏ&Ñ;xÿÿÿëóÏ&l&‚ÿÿ ÿçõÏ&l;yÿÿÿçñÏ&j;zÿÿÿèñÏ&l;{ÿÿÿäñÏ&n;|ÿÿÿéñÏ&k;}ÿÿ ÿêóÏ&m&%J;~ÿÿÿéñÏ&Ñ;ÿÿÿéñÏ&l& À;€ÿÿ ÿçôÏ&l&!‡ÿÿ ÿèõÏ&l;ÿÿ ÿêñÏ&n&"!"ÿÿ ÿèõÏ&l&p;‚ÿÿÿíòÏ&l/¨ÿÿÿéòÏ&k&;ƒÿÿ ÿèñÏ&n;„ÿÿÿéñÏ&l;…ÿÿ ÿéñÏ&Ñ&œ;†ÿÿÿèñÏ&Ñ;‡ÿÿ ÿðôÏ&Ë&ÊÑÿÿ ÿèõÏ&k;ˆÿÿÿåóÏ&j;‰ÿÿÿçôÏ&l& ¿ ¾ÿÿÿçöÏ&l&á;ŠÿÿÿéñÏ&Ñ;‹ÿÿ ÿéòÏ&k;Œÿÿ ÿæñÏ&m;ÿÿ ÿéñÏ&n;ŽÿÿÿêñÏ&j;ÿÿ ÿçõÏ&Ñ&r$GÿÿÿéôÏ&l;ÿÿÿéôÏ&l&;‘ÿÿ ÿçñÏ&k&%rÿÿ ÿçòÏ&k&r$yÿÿ ÿèñÏ&Ñ;’ÿÿÿéóÏ&k;“ÿÿ ÿæõÏ&k;”ÿÿ ÿéùÏ&n;•ÿÿ ÿêõÏ&n;–ÿÿ ÿéñÏ&Ñ&æ;—ÿÿÿåñÏ&n/äÿÿÿèñÏ&l;˜ÿÿÿéõÏ&n;™ÿÿÿçñÏ&L&K&&MnÿÿÿêóÏ&l;šÿÿ ÿéóÏ&l;›ÿÿ ÿîóÑ&;œÿÿÿéñÏ&n;ÿÿ ÿçôÏ&k;žÿÿÿèöÏ&l;ŸÿÿÿéñÏ&n; ÿÿÿèöÏ&l;¡ÿÿ ÿéõÏ&Ñ;¢ÿÿ ÿåôÏ&l&"";£ÿÿÿéòÏ&Ñ;¤ÿÿÿéñÏ&l(€ÿÿ ÿèñÏ&ž;¥ÿÿ ÿèõÏ&n;¦ÿÿ ÿèõÏ&l;§ÿÿ ÿéñÏ&l& À ÁÿÿÿèñÏ&n;¨ÿÿ ÿéòÏ&l;©ÿÿ ÿèöÏ&n;ªÿÿ ÿéóÏ&Ñ;«ÿÿÿèñÏ&n;¬ÿÿÿçñÏ&n&rqÿÿÿçñÏ&n&s&tuÿÿÿçóÏ&g&ž;­ÿÿ ÿêñÏ&l;®ÿÿÿçñÏ&ž;¯ÿÿ ÿçòÏ&l;°ÿÿÿèôÏ&n&;±ÿÿ ÿèóÏ&l;²ÿÿÿèñÏ&k;³ÿÿÿéñÏ&l;´ÿÿÿçöÏ&k&$Ä;µÿÿÿéñÏ&n;¶ÿÿ ÿèóÏ&n;·ÿÿ ÿêóÏ&w&vjÿÿÿèñÏ&n;¸ÿÿ ÿçóÏ&n&r;¹ÿÿ ÿìòÑ&;ºÿÿÿçõÏ&n%yÿÿÿêõÏ&n&  œÿÿ ÿéôÏ&k;»ÿÿÿçôÏ&l;¼ÿÿ ÿçôÏ&õ&ô&lóÿÿ ÿéõÏ&n&%áÿÿ ÿèñÏ&l;½ÿÿÿèóÏ&n&%;¾ÿÿ ÿéòÏ&ž;¿ÿÿÿéñÏ&n;Àÿÿ ÿèôÏ&n;Áÿÿ ÿèôÏ&k;ÂÿÿÿèõÏ&l&""~ÿÿ ÿéóÏ&l;ÃÿÿÿçñÏ&n;Äÿÿ ÿîóÏ&n& ù;ÅÿÿÿîñÏ&n;Æÿÿ ÿçôÏ&l;Çÿÿ ÿç÷Ï&n&Žÿÿ ÿèòÑ&;Èÿÿ ÿèñÏ&n;ÉÿÿÿéñÏ&Ñ;Êÿÿ ÿèñÏ&l;ËÿÿÿëöÏ&n;Ìÿÿ ÿèñÏ&n;ÍÿÿÿèõÏ&l;ÎÿÿÿçñÏ&n;Ïÿÿ ÿèôÏ&n%_ÿÿ ÿçôÑ&;ÐÿÿÿéñÏ&l%aÿÿÿêñÏ&l%mÿÿÿéöÏ&n;Ñÿÿ ÿèôÏ&n;ÒÿÿÿéñÏ&n/¡ÿÿ ÿèöÏ&l;ÓÿÿÿèñÏ&l;Ôÿÿ ÿéòÏ&n;Õÿÿ ÿèñÏ&k&‰;Öÿÿ ÿçôÏ&k&r;×ÿÿÿçóÏ&n;Øÿÿ ÿèóÏ&l;ÙÿÿÿçòÏ&j&gfÿÿ ÿêñÏ&l;Úÿÿ ÿìôÏ&g&n;ÛÿÿÿêñÏ&l;Üÿÿ ÿèõÏ&l;ÝÿÿÿéñÏ&l;ÞÿÿÿéõÏ&l;ßÿÿÿéñÏ&l;àÿÿ ÿéñÏ&n;áÿÿÿèñÏ&k;âÿÿÿéñÏ&n;ãÿÿ ÿèøÏ&k;äÿÿ ÿèöÏ&l;åÿÿ ÿèñÏ&l;æÿÿ ÿéñÏ&n/HÿÿÿèñÏ&l;çÿÿÿíñÏ&n;èÿÿÿéõÏ&l;éÿÿ ÿéõÏ&Ñ;êÿÿ ÿíòÏ&ž;ëÿÿ ÿéøÏ&Ñ;ìÿÿÿèñÏ&n;íÿÿÿéõÏ&l;îÿÿ ÿçôÏ&k& ¾;ïÿÿÿèñÏ&n;ðÿÿÿæõÏ&l;ñÿÿ ÿé÷Ï&k;òÿÿ ÿêóÑ&;óÿÿÿêñÏ&n&(~(ÿÿÿèóÏ&n;ôÿÿÿéñÏ&n;õÿÿ ÿéóÏ&l;öÿÿ ÿèóÏ&k;÷ÿÿ ÿéñÏ&n&!A!@ÿÿ ÿèôÏ&n;øÿÿÿéöÏ&n;ùÿÿ ÿéóÏ&j;úÿÿ ÿèóÏ&n&*;ûÿÿÿëöÏ&l'ýÿÿ ÿèñÏ&ð&n;üÿÿÿéñÏ&k;ýÿÿÿèôÏ&j;þÿÿÿéñÏ&l;ÿÿÿÿçöÏ&n<ÿÿÿèòÏ&n<ÿÿÿéñÏ&n<ÿÿ ÿéñÏ&n&G<ÿÿÿéñÏ&n<ÿÿ ÿèñÏ&n<ÿÿ ÿîôÏ&Ô&n<ÿÿ ÿçõÏ&n<ÿÿ ÿçóÏ&n<ÿÿ ÿïóÏ&n< ÿÿ ÿèóÏ&n&ð< ÿÿ ÿêôÑ&< ÿÿ ÿèöÏ&n< ÿÿÿèøÏ&k< ÿÿÿéóÏ&j<ÿÿ ÿèõÏ&ž<ÿÿÿèñÏ&k<ÿÿ ÿèôÑ&<ÿÿ ÿéñÏ&l<ÿÿ ÿèõÏ&ž<ÿÿ ÿéñÏ&j<ÿÿ ÿîóÏ&!x&ž<ÿÿÿêöÏ&j<ÿÿ ÿèòÑ&&%n<ÿÿÿéóÏ&Ñ<ÿÿÿçñÏ& Â&l<ÿÿ ÿçôÑ&<ÿÿ ÿéöÑ&&ÿÿÿèðÑ&‹ŒÿÿÿèñÑ&ŽÿÿÿèòÑ&ÿÿ ÿèïÍ&’‘ÿÿ ÿèôÏ&ÿÿÿæóÑ&–ÿÿ ÿçõÏ&—<ÿÿ ÿêóÐ&.Ú<ÿÿÿèôÑ&“ÿÿÿèðÑ&‹<ÿÿÿèðÑ&<ÿÿ ÿèíÐ&˜”ÿÿ ÿèïÐ&˜•ÿÿ ÿèôÏ&¶ÿÿ ÿèñÐ&.Ú<ÿÿ ÿèõÏ&¸·ÿÿ ÿéóÏ&/{¹ÿÿ ÿèöÏ&º»ÿÿ ÿéöÑ&¼ºÿÿÿéðÏ&¾½ÿÿ ÿè÷Ñ&™&'< ÿÿ ÿéóÏ&¹ÿÿÿéõÐ& ã.•ÿÿÿéóÑ&/ÙLÿÿÿéóÏ& ã+Dÿÿ ÿæöÐ&|0 ÿÿÿèöÑ&L&—ÿÿÿèõÑ&L£ÿÿ ÿæùÏ&nÿÿ ÿçõÑ&!Ÿ! ÿÿ ÿæòÐ&|òÿÿ ÿéôÑ&µ´ÿÿ ÿèòÎ&ò*Çÿÿ ÿéôÏ&0YµÿÿÿçòÐ&œ&C=ÿÿÿéõÐ&o‹ÿÿ ÿèóÑ&ÕÇÿÿ ÿæòÏ& qÿÿÿéôÐ&o›ÿÿÿç÷Ç& oÿÿÿé÷Ð&o-²ÿÿ ÿè÷Å&Ç'ÿÿ ÿæòÏ&Ïÿÿ ÿéõÏ&lkÿÿ ÿèõÐ&Ç.ÿÿÿéóÏ&o6ÿÿÿèöÏ&o'!ÿÿÿéìÇ&oÿÿ ÿãóÑ&$=ÿÿ ÿéòÐ&mnÿÿ ÿèóÏ&Çñÿÿ ÿéòÏ& °' ÿÿÿæóÐ&#\&Ï+ÿÿ ÿéöÏ&vlÿÿ ÿèõÐ&Ç'"ÿÿ ÿéôÑ&=ÿÿÿçòÎ&0Ÿÿÿ ÿçòÊ&A=ÿÿÿæõÑ&Ï=ÿÿÿéïÑ&o'#ÿÿ ÿèóÒ&Ç'$ÿÿ ÿåöÓ&§=ÿÿ ÿèöÑ&Œ=ÿÿ ÿèõÏ&-0ÿÿ ÿéõÎ&Ä0ÿÿ ÿèóÏ&Å0ÿÿ ÿêôÐ&‚0ÿÿ ÿêöÎ&00ÿÿ ÿéóÏ& j0ÿÿ ÿéóÏ&#0ÿÿ ÿéöÎ& ’ ‘ÿÿ ÿéôÑ& ‘$ ÿÿ ÿçôÏ&Ë=ÿÿ ÿçôÏ&Ë=ÿÿ ÿçôÏ&Ë=ÿÿ ÿçôÏ&#Ëÿÿ ÿçôÏ&Ë=ÿÿ ÿçôÏ&ËÉÿÿ ÿçôÏ&Ê=ÿÿ ÿçôÏ&Ê=ÿÿ ÿçôÏ&Ê=ÿÿ ÿçôÏ&Ê=ÿÿ ÿçôÏ&Ê=ÿÿ ÿçôÑ&ÌÊÿÿ ÿçôÏ&Ê= ÿÿ ÿçôÏ&Ê=!ÿÿ ÿçôÏ&Ê="ÿÿ ÿçõÐ&Ê=#ÿÿ ÿçôÐ&Ê=$ÿÿ ÿçôÏ&Ê=%ÿÿ ÿçôÏ&Ê ÿÿ ÿçôÏ&Ê=&ÿÿ ÿçôÏ&yÊÿÿ ÿçôÏ&Ê='ÿÿ ÿçôÏ&Ê=(ÿÿ ÿçöÏ&#=)ÿÿ ÿçöÏ&;#ÿÿ ÿçöÏ&#=*ÿÿ ÿçôÏ&Ê=+ÿÿ ÿçöÏ&#=,ÿÿ ÿçôÏ&Ê=-ÿÿ ÿçöÑ&#=.ÿÿ ÿçöÐ&#=/ÿÿ ÿçôÏ&Ê’ÿÿ ÿçôÏ& \Êÿÿ ÿçôÐ&Ê=0ÿÿ ÿçöÏ&#=1ÿÿ ÿçöÏ&&#ÿÿ ÿçöÏ&Ê"ÿÿ ÿçöÐ&#=2ÿÿ ÿçôÏ&Ê=3ÿÿ ÿçöÏ&#=4ÿÿ ÿçöÏ&#=5ÿÿ ÿçôÐ&Ê=6ÿÿ ÿçöÏ&#=7ÿÿ ÿçöÏ&#=8ÿÿ ÿçöÏ&#=9ÿÿ ÿç÷Ï&#=:ÿÿ ÿèöÊ&Ñôÿÿ ÿéìÎ&ÑYÿÿ ÿéðÃ&ŠÂÿÿ ÿêòÎ&Œÿÿ ÿèóÇ&¡+Jÿÿ ÿéôÏ&ÑÃÿÿ ÿéöÎ&ŠÄÿÿ ÿéôÅ&Ñÿÿ ÿíóÊ&ßÿÿ ÿéóÆ&чÿÿ ÿëõÇ&Ñÿÿ ÿèõÏ&Šÿÿ ÿéõË&`Šÿÿ ÿìõÑ&Š-ÿÿ ÿçóÎ&Ѹÿÿ ÿéòÐ&Ñ0öÿÿ ÿíóÊ&ŒQÿÿ ÿéöÐ&³ÿÿ ÿèôÐ&ÐÑÿÿ ÿèöÏ&Š+ÿÿ ÿéóÏ&Ñÿÿ ÿíðÏ&Šiÿÿ ÿêìÏ&ÎÍÿÿ ÿíöÌ&1Íÿÿ ÿíôÐ&Àÿÿ ÿíóÏ&Ñ'Ëÿÿ ÿéóÏ&OŠÿÿ ÿéòÄ&ÑÎÿÿ ÿéòÎ&²ÿÿ ÿèóÏ&Šÿÿ ÿíõÆ&Œÿÿ ÿèõË&ÏÍÿÿ ÿèóÏ&Ñÿÿ ÿêðÇ&ÍÃÿÿ ÿíôÆ&Šžÿÿ ÿêòÏ&Ñ ÿÿ ÿêõÉ&Ñ~ÿÿ ÿè÷Ï&Ñ7ÿÿ ÿéõÑ&Œôÿÿ ÿèîÏ&Ñÿÿ ÿéóÏ&Ñæÿÿ ÿèôÐ&PŒÿÿ ÿéöÏ&Ñ—ÿÿ ÿíöÑ&Œaÿÿ ÿéõÐ&óÿÿ ÿçõÐ&Ñ(ÿÿ ÿêõÑ&Ñ2ÿÿ ÿèôÉ&00ÿÿ ÿíóÏ&~Ñÿÿ ÿíóÄ&Ñ&ÿÿ ÿéõÐ&Š‹ÿÿ ÿéóÏ&æŒÿÿ ÿéóÐ&ŠNÿÿ ÿéôÑ&àÑÿÿ ÿé÷Ì&Eÿÿ ÿéñÇ&Óÿÿ ÿèõÏ&Œ»ÿÿ ÿæôÏ&Ñÿÿ ÿéòÏ&Š,ÿÿ ÿêûË&Œ&Hºÿÿ ÿéõÈ&Ñ<ÿÿ ÿèôÑ&Ѧÿÿ ÿèñÆ&+Iÿÿ ÿìõÄ& &!ÿÿ ÿèõÕ&+J=;ÿÿ ÿéôÏ&Œ$ÿÿ ÿéõÄ&Œ+Hÿÿ ÿéóÅ&Ðÿÿ ÿéêÐ&Š–ÿÿ ÿéóÎ&Ñ0ÿÿ ÿèóÏ&+K0Yÿÿ ÿçõÑ& Œÿÿ ÿéõÐ&„ÿÿ ÿéôÏ& ®ÿÿ ÿèõÐ&LÑÿÿ ÿéìÈ&+Fÿÿ ÿéóÏ&Žÿÿ ÿéôÏ&+Gÿÿ ÿéöÏ&Œ/öÿÿ ÿçõÓ&)@ÿÿ ÿèöÎ&Œ»ÿÿ ÿèõÑ&‰Œÿÿ ÿéõÐ&Œ‹ÿÿ ÿèòÏ&Ñ Wÿÿ ÿèóÏ&Œ-dÿÿ ÿéóÏ&Šÿÿ ÿìõÑ& ÿÿ ÿéîÏ& ÿÿ ÿêõÇ&.3/`ÿÿ ÿéøÏ& ÿÿ ÿíñÍ&Œ+Mÿÿ ÿèöÏ&Œ'ðÿÿ ÿé÷Ç&Œ1ÿÿ ÿéðÅ&Œ»ÿÿ ÿéíÑ&/&/`=<ÿÿ ÿéöÏ&Í"Hÿÿ ÿìõÐ&Œ+Lÿÿ ÿéíÇ&Ñÿÿ ÿéõÐ&Ñ#¡ÿÿ ÿèòÏ&Œ.ÿÿ ÿèõÐ&0Œÿÿ ÿéóÎ&Ñ&Çÿÿ ÿéôÏ&/ú/`ÿÿ ÿéóÎ&+Nÿÿ ÿéõÑ&Oÿÿ ÿíñÍ&0ÿÿ ÿèôÍ&==ÿÿ ÿèñÐ&Ñ ÿÿ ÿèñÎ&Í&Û&eÿÿ ÿéöÏ&/`ÿÿ ÿèôÏ&$‚ÿÿ ÿéöÎ&Œ"_ÿÿ ÿéíÉ&ÑJÿÿ ÿíñÏ&Œ!aÿÿ ÿéóÊ&Œóÿÿ ÿíóÎ&Œÿÿ ÿèóÏ&Ñ‹ÿÿ ÿéóÏ&/`0sÿÿ ÿçõÉ&ŒYÿÿ ÿéòÎ&Œ.¹ÿÿ ÿéíÇ&5ÿÿ ÿéõÏ&0/`ÿÿ ÿèõÎ&°ÿÿ ÿèïÐ&Í0¹ÿÿ ÿéõÈ&#Iÿÿ ÿéïÑ&Œÿÿ ÿíôÏ&Œ"jÿÿ ÿéöÑ&=>ÿÿ ÿéóÎ&Œ$¸ÿÿ ÿíòÐ&Œ2ÿÿ ÿèôÐ&ÿÿ ÿêîË&$Tÿÿ ÿèõÐ& /`ÿÿ ÿéòÈ&Œ$Ìÿÿ ÿçóÐ&æÿÿ ÿéöÒ&/ÿÿ ÿèõÇ&/8ÿÿ ÿéòÏ&Œÿÿ ÿéïÐ&Ñ%ÿÿ ÿèñÏ&+O=?ÿÿ ÿéôÉ&/9ÿÿ ÿé÷Ð&/Y/`ÿÿ ÿêôÑ&+Pÿÿ ÿèôÒ&0 +Oÿÿ ÿìóÏ&¦/`ÿÿ ÿéóÏ&0ÿÿ ÿéïÐ&-…ÿÿ ÿéõÏ&f&-q/`ÿÿ ÿéöÇ&Í& Ãÿÿ ÿèõÐ&Œ&˜ÿÿ ÿèöÐ&ÒÍÿÿÿéóÐ&ÓÛÿÿ ÿéîÑ&Œ¹ÿÿ ÿéòÏ&Í Þÿÿ ÿèõÑ&f&-r/`ÿÿ ÿêöÏ&â&/`=@ÿÿ ÿéíÏ&Í-9ÿÿ ÿèóÏ&{ÿÿ ÿèôÑ&û+Oÿÿ ÿçôÏ&(Kÿÿ ÿíöË&Œÿÿ ÿêîÑ&Œ'ÿÿ ÿêòÏ&üÿÿ ÿéóÈ&/`/bÿÿ ÿéóÒ&iÿÿ ÿèõÌ&þÿÿ ÿè÷Ð&6&/`=AÿÿÿèõÊ&ÔÓÿÿ ÿéôÎ&Œ*Ñÿÿ ÿèôÎ&+Qÿÿ ÿéóÏ&'÷ÿÿ ÿêóÐ&Í&¤ÿÿ ÿèðÑ&&3=Bÿÿ ÿéøÊ&Œ¡ÿÿ ÿëòÏ&ú&/`=Cÿÿ ÿèòÐ&/ÿ.ÿÿ ÿéñÈ&Í'ÿÿ ÿèõÏ&/dÿÿ ÿêóÐ&=Dÿÿ ÿèõÐ&&/`=Eÿÿ ÿéóÏ&%õ/`ÿÿ ÿçøÒ&Õ/`ÿÿ ÿéõÐ&/`=Gÿÿ ÿéòÊ&Œðÿÿ ÿéöÉ&'è/`ÿÿ ÿçôÐ&/`=Hÿÿ ÿç÷Ò&±ÿÿÿéóÏ&Ó&0=Iÿÿ ÿèóÐ&+R/`ÿÿ ÿèòÍ&%\ÿÿÿéòÐ&Ó%Vÿÿ ÿéôÏ&/`=Jÿÿ ÿèðÏ&/`=Kÿÿ ÿêóÑ&0=Lÿÿ ÿéöÐ&}ÿÿ ÿéðË&ú&/`=Mÿÿ ÿëòÈ&ú&/`=Nÿÿ ÿçôÏ&0/`ÿÿ ÿéíÊ&–/`ÿÿ ÿèöÐ&/`=Oÿÿ ÿéñÑ&Í+Sÿÿ ÿèòÑ&&0=Pÿÿ ÿéóÌ&(T/`ÿÿ ÿéòË&.¨ÿÿÿéóÐ&Ó=Qÿÿ ÿèùÔ&/`=Rÿÿ ÿçõÐ&‡/`ÿÿ ÿçøÒ&/`=Sÿÿ ÿéöÎ&Æ/`ÿÿ ÿçóÎ&/`=Tÿÿ ÿéêÐ&Ö×ÿÿ ÿéòÐ&Iÿÿ ÿèõÐ&gÿÿ ÿèôÐ&&ÿÿ ÿéõÐ&<ÿÿ ÿèîÐ&”“ÿÿ ÿéñÐ&#Ç”ÿÿ ÿèóÐ&!—ÿÿ ÿéóÐ&çÿÿ ÿèïÐ&'ö/‘ÿÿ ÿèóÐ&/‘=Uÿÿ ÿéòÐ&”-†ÿÿ ÿéùÒ&/‘=Vÿÿ ÿçðÑ&/‘=Wÿÿ ÿéóÏ& Ì1ÿÿ ÿéøÏ&à ÍÿÿÿéïÏ&!+…ÿÿ ÿéõÏ& Í!*ÿÿ ÿèöÏ& ÍHÿÿ ÿéóÏ& Í!,ÿÿ ÿéöÏ& ΄ÿÿ ÿéîÏ& Íÿÿ ÿèõÏ& ÎUÿÿ ÿéõÐ&¬ Îÿÿ ÿèòÏ& Î0%ÿÿ ÿéïÏ& Ξÿÿ ÿéôÏ& Î Òÿÿ ÿéôÏ& Τÿÿ ÿéõÏ& ̓ÿÿ ÿéñÏ& Í)µÿÿ ÿèõÏ&!C Îÿÿ ÿéöÏ& ͬÿÿÿçïÏ&!+=Yÿÿ ÿéìÐ&2 Íÿÿ ÿéõÏ& Îôÿÿ ÿèóÏ& Ôîÿÿ ÿçõÏ& ÔFÿÿ ÿéòÐ& Î0ôÿÿ ÿèõÐ& Î&åÿÿ ÿêöÒ&ø=Zÿÿ ÿéõÏ& Î!Mÿÿ ÿéñÏ& Îþÿÿ ÿçõÏ& ÎFÿÿ ÿéôÏ& Íœÿÿ ÿéòÏ& Ô²ÿÿ ÿéòÏ& Î/”ÿÿ ÿçòÐ& Î/‚ÿÿ ÿéëÏ& Í·ÿÿ ÿéòÏ& Í&¶ÿÿ ÿéîÏ& Îÿÿ ÿéóÏ& Î.ÿÿ ÿéòÏ& Î(-ÿÿ ÿéòÏ& Îÿÿ ÿéôÏ& Î ÿÿ ÿéóÏ& Î ÿÿ ÿéïÏ&E Îÿÿ ÿèïÏ& Î ÿÿ ÿéóÏ&/•=[ÿÿ ÿéòÏ& Τÿÿ ÿéõÐ& ΋ÿÿ ÿéôÐ& Ιÿÿ ÿéöÑ& Îbÿÿ ÿéòÏ&8 Îÿÿ ÿèöÏ&. =\ÿÿ ÿéóÏ& Î+ÿÿ ÿçóÏ&=]ÿÿ ÿéóÏ& Ô ÿÿ ÿéôÏ& Î ÿÿ ÿéêÐ& Îÿÿ ÿçõÏ& Ôÿÿ ÿéòÏ& Îÿÿ ÿéóÏ&Ñ Ôÿÿ ÿéöÏ& Î)–ÿÿ ÿéóÐ& Î,ÿÿ ÿéòÏ& Îÿÿ ÿéëÏ&¾ Ôÿÿ ÿçöÏ& Îÿÿ ÿèõÏ&V Îÿÿ ÿé÷Ï& Ô.4ÿÿ ÿçöÒ&f Îÿÿ ÿèñÏ& Ôÿÿ ÿéðÐ& Ô# ÿÿ ÿèñÏ&!(ïÿÿ ÿèïÏ& Ô-hÿÿ ÿéöÏ& Ô Øÿÿ ÿéõÏ& Î1ÿÿ ÿéíÏ& Ô/–ÿÿ ÿéóÏ& Î)éÿÿ ÿéôÏ& Î+TÿÿÿéòÏ&/—=^ÿÿ ÿéòÏ& Î,AÿÿÿéòÏ&/˜/—ÿÿ ÿéøÏ& Ô $ÿÿ ÿéôÏ& Î`ÿÿ ÿéöÏ& Î#Hÿÿ ÿäöÏ& Î"sÿÿ ÿéóÏ& ÎŒÿÿ ÿé÷Ï& Ô/™ÿÿ ÿéóÏ&/š Îÿÿ ÿéñÏ& Îÿÿ ÿéóÏ& Ô&Žÿÿ ÿéñÏ& Î/ÿÿ ÿéöÏ& Ôÿÿ ÿèóÐ& Ï Îÿÿ ÿéïÏ& Îÿÿ ÿéöÏ& Ôÿÿ ÿéõÏ& Îÿÿ ÿèôÏ&ÿ=`ÿÿ ÿèôÏ& Ô$rÿÿ ÿéòÑ& Ô/ÿÿ ÿéòÏ& Ô$Âÿÿ ÿéôÑ& Ô.‚ÿÿ ÿé÷Ï& Ôÿÿ ÿéõÑ& Î0ÿÿ ÿéóÏ& Î(Ùÿÿ ÿéõÏ& Ô&G=aÿÿ ÿéïÏ& Ô4ÿÿ ÿéóÏ&÷=bÿÿ ÿéóÏ& Ô'÷ÿÿ ÿéóÏ& Ô=cÿÿ ÿéõÐ& Î* ÿÿ ÿéõÏ& Ô*ÿÿ ÿèöÐ& Ô)òÿÿ ÿéòÏ& Ôüÿÿ ÿéôÏ& Ô*Ñÿÿ ÿéïÏ& Ô0ÿÿ ÿéòÏ&.¯ Ôÿÿ ÿéøÑ& Ô0ÿÿ ÿéõÏ& Ô0 ÿÿ ÿèôÐ&%q'úÿÿ ÿçóÐ&#h&#g=dÿÿ ÿéóÐ&%q&/á=eÿÿ ÿéñÏ& Ô0"ÿÿÿè÷Ï&,Y=fÿÿ ÿéóÐ& Ô*yÿÿÿçôÑ&0!=gÿÿ ÿéòÏ&%r%qÿÿ ÿéôÐ&…=hÿÿ ÿèóÐ&%q)õÿÿ ÿéöÏ& Ô0#ÿÿ ÿéóÑ& ÔÎÿÿÿèòÏ&0(3ÿÿ ÿéõÏ&þÿÿ ÿéóÏ&&þÿÿ ÿéìÏ&œþÿÿ ÿéôÐ&þ*rÿÿ ÿèòÏ&0%þÿÿ ÿéôÏ&þFÿÿ ÿéõÑ&þ²ÿÿÿæòÏ&ÿ=jÿÿ ÿéóÏ&þÿÿ ÿéóÐ&¶ÿÿ ÿèñÏ&þ*zÿÿ ÿéëÏ&þ)ÿÿÿ ÿçôÏ&þ%Ìÿÿ ÿéòÏ&þ(-ÿÿ ÿéóÏ&þ0)ÿÿ ÿéöÐ&þ³ÿÿ ÿéòÏ&þ ÿÿ ÿéðÏ&þ|ÿÿ ÿéõÏ&þQÿÿ ÿéõÏ&þ ÿÿÿçõÏ&÷=kÿÿ ÿéóÏ&þ&ÿÿ ÿèöÐ&áÿÿ ÿéöÑ&“ÿÿ ÿéõÐ&‹þÿÿ ÿéòÏ&¤þÿÿ ÿéóÐ&Ðÿÿ ÿéóÒ&0ìÿÿ ÿéîÐ&0*ÿÿÿçïÎ&0+=lÿÿÿçîÏ&0+=mÿÿ ÿèóÏ&°ïÿÿ ÿéôÏ&þ0,ÿÿ ÿèîÐ&0-ÿÿ ÿéöÐ& ×ÿÿ ÿéòÐ&þ0.ÿÿ ÿéöÐ&01ÿÿ ÿéíÐ&5ÿÿ ÿäöÏ&ýþÿÿ ÿéöÐ&ÿÿ ÿéðÐ&…=nÿÿ ÿéòÐ&$Áÿÿ ÿéðÑ&‡ÿÿ ÿéöÏ&þÿÿ ÿéôÑ&+Pÿÿ ÿéóÐ&&÷=oÿÿ ÿéôÐ&*Ñÿÿ ÿéóÏ&n02ÿÿÿéóÏ&03=qÿÿ ÿèöÐ&ÿÿ ÿéðÐ&É&Ê=rÿÿ ÿéðÐ&Ê&ÉËÿÿ ÿéðÐ&É&ÊÌÿÿ ÿéðÐ&É&ÊÍÿÿ ÿéðÐ&É&ÊÎÿÿÿéóÏ&.Í04ÿÿ ÿéðÐ&u&Ê=sÿÿ ÿéðÐ&Ê&u=tÿÿ ÿè÷Ç&s=uÿÿÿèôÊ&05=vÿÿ ÿèöÊ&/B%-ÿÿ ÿëôÉ&u=xÿÿ ÿëôÏ&ãuÿÿ ÿëôÉ&;uÿÿ ÿëôÏ&u=yÿÿ ÿëôÐ&äuÿÿ ÿëôÉ&–uÿÿ ÿëôÐ&åuÿÿ ÿëôÌ&u=zÿÿ ÿëôÊ&uÿÿ ÿëôÉ& uÿÿ ÿëôÏ&u={ÿÿ ÿëöÉ&u=|ÿÿ ÿëôË&u=}ÿÿ ÿëôÎ&u=~ÿÿ ÿëôÉ&u=ÿÿ ÿëôÉ&u=€ÿÿ ÿëôÉ&u=ÿÿ ÿëôÏ&u=‚ÿÿ ÿëôË&u=ƒÿÿ ÿëôÉ&u=„ÿÿ ÿëôÍ&¼uÿÿ ÿëôÏ&u=…ÿÿ ÿëôÉ&u=†ÿÿ ÿëôÏ&®uÿÿ ÿëôÏ&»uÿÿ ÿëôÎ&u=‡ÿÿ ÿëôÉ&®uÿÿ ÿëôÉ&u=ˆÿÿ ÿëôÐ&u=‰ÿÿ ÿëôÏ&u=Šÿÿ ÿëôÎ&u=‹ÿÿ ÿëôÉ& ñuÿÿ ÿëôÏ&u=Œÿÿ ÿëôÐ&u=ÿÿ ÿëôÉ&u=Žÿÿ ÿëôÏ&u=ÿÿ ÿëôÉ&u=ÿÿ ÿëôÉ&~uÿÿ ÿëôË&u=‘ÿÿ ÿëöÏ&u=’ÿÿ ÿëôÉ&u=“ÿÿ ÿëôÏ&u=”ÿÿ ÿëôÏ&suÿÿ ÿëôÏ&u=•ÿÿ ÿëôÎ&u=–ÿÿ ÿëôÐ&u=—ÿÿ ÿëôÏ&u=˜ÿÿ ÿëôÍ&u=™ÿÿ ÿëôÉ&u=šÿÿ ÿëôÏ&u=›ÿÿ ÿëôÍ&u=œÿÿ ÿëôÉ&u=ÿÿ ÿëôÉ&u=žÿÿ ÿëôÉ&u=Ÿÿÿ ÿëôÏ&u= ÿÿ ÿëôÏ&$uÿÿ ÿëôÏ&u=¡ÿÿ ÿëôÑ&u=¢ÿÿ ÿëôÉ&u=£ÿÿ ÿëôÑ& uÿÿ ÿëôÎ&u=¤ÿÿ ÿëôÊ&u=¥ÿÿ ÿëôÏ&u=¦ÿÿ ÿëôÉ&Muÿÿ ÿëôÑ&uÜÿÿ ÿëôÌ&zuÿÿ ÿëôÏ&u=§ÿÿ ÿëôÎ&|uÿÿ ÿëôË&uÿÿ ÿëôÏ&Åuÿÿ ÿëôÐ&u=¨ÿÿ ÿëôÏ&çuÿÿ ÿëôÏ&J&uIÿÿ ÿëôÑ&-uÿÿ ÿëôÏ&uÿÿ ÿëôÏ&u=©ÿÿ ÿëôÉ&u=ªÿÿ ÿëôÌ&u=«ÿÿ ÿëôÉ&u=¬ÿÿ ÿëôÐ&u=­ÿÿ ÿëôÐ&u=®ÿÿ ÿëôÏ&3uÿÿ ÿëôÑ&u=¯ÿÿ ÿëôÉ&u=°ÿÿ ÿëôÏ&u=±ÿÿ ÿëôÉ&uÿÿ ÿëôÓ&u=²ÿÿ ÿëôÉ&u=³ÿÿ ÿëôÏ&N&Muÿÿ ÿëôÉ&u=´ÿÿ ÿëôÏ&u=µÿÿ ÿëôÉ&u=¶ÿÿ ÿëôÐ& ºuÿÿ ÿëôÏ&u=·ÿÿ ÿëôÑ&u=¸ÿÿ ÿëôÒ&u=¹ÿÿ ÿëôÏ&u=ºÿÿ ÿëôÏ&d&cuÿÿ ÿëôÉ&u=»ÿÿ ÿëöÍ&\uÿÿ ÿëôÐ&Tuÿÿ ÿëöÐ&u=¼ÿÿ ÿëôÉ&u=½ÿÿ ÿëôÉ&u=¾ÿÿ ÿëôÒ&u=¿ÿÿ ÿëôÎ&u ]ÿÿ ÿëôÏ&u=Àÿÿ ÿëôÎ&u=Áÿÿ ÿëôÏ&u=Âÿÿ ÿëôÏ&uÅÿÿ ÿëôÉ&u=Ãÿÿ ÿëôÉ&" uÿÿ ÿëôÍ&u=Äÿÿ ÿëôÐ&#–uÿÿ ÿëôÉ&u"#ÿÿ ÿëôÎ&u=Åÿÿ ÿëôÐ&!¥uÿÿ ÿëôÏ&uÿÿ ÿëôÉ&u=Æÿÿ ÿëôÉ& {uÿÿ ÿëôÏ& kuÿÿ ÿëôÏ&u=Çÿÿ ÿëôÉ&!-uÿÿ ÿëôÐ&"¿uÿÿ ÿëôÉ&u=Èÿÿ ÿëôÉ&u=Éÿÿ ÿëôÉ& “uÿÿ ÿëôÐ&"³uÿÿ ÿëôÐ&!¡uÿÿ ÿëôÏ&u!šÿÿ ÿëôÏ&u=Êÿÿ ÿëôÏ&"ûuÿÿ ÿëôÏ&"¼uÿÿ ÿëôÏ&"uÿÿ ÿëôÏ&$9uÿÿ ÿëôÑ&u=Ëÿÿ ÿëôÍ&u=Ìÿÿ ÿëøÎ&u=Íÿÿ ÿëôÉ&u=Îÿÿ ÿëôÏ&$äuÿÿ ÿëôÏ&vuÿÿ ÿëôÐ&$ðuÿÿ ÿëôË&$Vuÿÿ ÿëôÏ&u=Ïÿÿ ÿëôÌ&u=Ðÿÿ ÿëôÐ&uÿÿ ÿëõÐ&u=Ñÿÿ ÿëôÒ&u=Òÿÿ ÿëõÏ&u=Óÿÿ ÿëôÏ&u=Ôÿÿ ÿëôÐ&u=Õÿÿ ÿëôÉ&u=Öÿÿ ÿëôÏ&u=×ÿÿ ÿëôË&/uÿÿ ÿëôÌ&u=Øÿÿ ÿëôÐ&%Šuÿÿ ÿëôÑ&u=Ùÿÿ ÿëôÏ&u=Úÿÿ ÿëôÉ&u=Ûÿÿ ÿëôÌ&u=Üÿÿ ÿëôË&u=Ýÿÿ ÿëôÏ&u=Þÿÿ ÿëôÉ&Æ&Çuÿÿ ÿëõÒ&uƒÿÿ ÿëôÐ&u=ßÿÿ ÿëôÏ&u=àÿÿ ÿëôÏ&(}uÿÿ ÿëôÏ&u=áÿÿ ÿëôÏ&u=âÿÿ ÿëôÑ&u=ãÿÿ ÿëôÑ&u=äÿÿ ÿëôÉ&u=åÿÿ ÿëôÑ&u=æÿÿ ÿëôÌ&u=çÿÿ ÿë÷Ë&u=èÿÿ ÿë÷Ð&‘&’uÿÿ ÿëôÓ&u=éÿÿ ÿëôÒ&u=êÿÿ ÿëôÑ&u=ëÿÿ ÿëôÒ&u=ìÿÿ ÿëôÉ&u=íÿÿ ÿëôË&u=îÿÿ ÿéðÃ&&LÿÿÿéðÃ&&KÿÿÿêòÑ&&P=ïÿÿ ÿéñÄ&'þÿÿÿéðÃ&µÿÿÿéñË&&IþÿÿÿéðÃ&Òÿÿ ÿéðÏ&&Jÿÿ ÿéðÏ&&Hÿÿ ÿéðÆ&&Gÿÿ ÿéñÌ&þPÿÿ ÿéðÏ&Qÿÿ ÿéñÅ&Üþÿÿ ÿéñÃ&åþÿÿ ÿéñÏ&þ&Fÿÿ ÿéñÐ&&Eþÿÿ ÿéñÃ& òþÿÿ ÿéñÐ&þ&Dÿÿ ÿéñÇ&Éþÿÿ ÿéðÏ&&CÿÿÿéñÏ&þ&Mÿÿ ÿéñÐ&06þÿÿ ÿéñÎ&þ07ÿÿ ÿéñÍ&08þÿÿ ÿèñÊ&09ŠÿÿÿèñÆ&0:ŠÿÿÿèñÃ&Š&Rÿÿ ÿéðÄ&xÿÿ ÿéñÍ&0;þÿÿÿéñÏ&þ&QÿÿÿéñÐ&0<þÿÿ ÿèñÊ&Š=ðÿÿ ÿéñÏ&`þÿÿ ÿéñÏ&þ&Sÿÿ ÿéñÏ&þšÿÿ ÿéñÐ&þ0=ÿÿÿèñÏ&Šÿÿ ÿéñÑ&þäÿÿ ÿéñÏ&þ=ñÿÿ ÿéñÄ&0Æþÿÿ ÿèñÑ&Š‹ÿÿ ÿèñÊ&Šÿÿ ÿéðÏ&¥ÿÿÿéðÐ&ÐÿÿÿéðÏ& ÿÿ ÿéñÏ&þýÿÿ ÿèñÑ&Š\ÿÿÿèñÍ&ÝþÿÿÿèñÊ&±ŠÿÿÿèñÊ&"&eÿÿ ÿçñÏ&&X&eÿÿ ÿéñÃ&&Tþÿÿ ÿèñÏ&0>ŠÿÿÿéñÆ&þ&UÿÿÿèñÐ&Š.ÿÿÿèñÆ&&VŠÿÿ ÿéñÐ&&Wþÿÿ ÿéñÌ&&=þÿÿ ÿèñÏ&&YŠÿÿÿéðÎ&&Zÿÿ ÿçñÏ&þ àÿÿ ÿèñÆ&Š&[ÿÿ ÿèñÅ&Š&\ÿÿÿéðÇ&0?ÿÿÿèñÏ&¥Šÿÿ ÿéñË&þÍÿÿÿéñÆ&þ!€ÿÿÿèñÄ&Š!ÿÿÿèñÉ&ŠŽÿÿÿéñÐ&þ&]ÿÿ ÿèñÐ&0CŠÿÿ ÿçñÏ&áŠÿÿ ÿéñÐ&&^þÿÿ ÿèñÐ&Š&_ÿÿ ÿéñÐ&&`þÿÿÿçñÏ&ÝŠÿÿÿèñÅ&&e=òÿÿ ÿéñÑ&þ)šÿÿ ÿèñÏ&Š&aÿÿÿèñÏ& cŠÿÿÿèñÏ&&e.ëÿÿ ÿèñÎ&&dŠÿÿ ÿèñÍ&Ë&&e/½ÿÿ ÿæñÐ&ŠØÿÿ ÿéðÈ&"<ÿÿ ÿçöÐ&0D=óÿÿ ÿæñÈ&Š=ôÿÿ ÿèñÐ&Š"üÿÿÿéñÏ&þ&cÿÿÿèñÆ&Š#Æÿÿ ÿèñÈ&Š!ÿÿ ÿèñÏ&Š&bÿÿ ÿèñÏ&Š#±ÿÿÿèñÇ&Š"Óÿÿ ÿèñÏ&Š#Šÿÿ ÿèñÉ&Š –ÿÿÿèñÎ&Š/¾ÿÿ ÿèñÆ&Š!.ÿÿ ÿèñÏ& í&&e&lÿÿÿèñÍ&Š^ÿÿ ÿèñÑ&0—ËÿÿÿèñÏ&Š)›ÿÿ ÿèñÇ&Š)œÿÿ ÿèñÐ&Š$ˆÿÿ ÿèñÆ&)ŠÿÿÿèñÒ&Š/¿ÿÿ ÿèñÆ&Š)Ÿÿÿ ÿèñÑ&Š)žÿÿÿçñÏ&Š#ÿÿ ÿèñÐ&Ë&&e=õÿÿÿèñÉ&$ŠÿÿÿèñÐ&Š%ÿÿ ÿèñÓ&&e&mÿÿ ÿèñÆ&‰Šÿÿ ÿèñÍ&) Šÿÿ ÿèñÐ&ŠÊÿÿÿèñÑ&)¡Šÿÿ ÿèñÍ&;&eÿÿ ÿèñÏ&%Šÿÿ ÿèñÏ&Š)¢ÿÿ ÿèñÉ&Š)£ÿÿ ÿèñÎ&/ËŠÿÿ ÿèñÆ&Š/Àÿÿ ÿèñÐ&‹ŠÿÿÿèñÊ&Š)¤ÿÿ ÿèñÏ&)¥Šÿÿ ÿèñÊ&)¦ŠÿÿÿîôÑ&ü)§ÿÿ ÿèñË&Š/Âÿÿ ÿèñÏ&&e/ÁÿÿÿèñÑ&Š*œÿÿÿèñÏ&Š#òÿÿÿèñÐ&/Êÿÿ ÿåñÒ&Š/Çÿÿ ÿèñÏ&*Šÿÿ ÿèñÎ&å&eÿÿ ÿèñÏ&Š'>ÿÿÿèñÈ&ŠeÿÿÿèñÇ&Š&nÿÿ ÿèñÏ&&e/Äÿÿ ÿèñÉ&(Šÿÿ ÿèñÐ&*žŠÿÿÿèñÎ&/ÆŠÿÿ ÿçñÆ&&e/ÅÿÿÿèñÒ&%&eÿÿÿèñÑ&%P&eÿÿ ÿçñË&&e&oÿÿ ÿæñÏ&Š=öÿÿ ÿçñÐ&&e/Èÿÿ ÿèñÑ&&e'5ÿÿÿèñÉ&&e=÷ÿÿÿèñÏ&ŠSÿÿ ÿèñÏ&Š=øÿÿÿèñÐ&&e&pÿÿ ÿçñÐ&/ÊŠÿÿ ÿèñÌ&&e/Éÿÿ ÿèóÈ&²qÿÿ ÿéðÐ&)äÿÿ ÿé÷È&²ÿÿ ÿéóÏ&ÿÿ ÿéôÏ&Ÿÿÿ ÿéóÈ&²!,ÿÿ ÿèòË&/Ì.ôÿÿ ÿéôÏ&=ùÿÿ ÿé÷Ï&²/Íÿÿ ÿéôÍ&¤ÿÿ ÿéõÏ&%1ÿÿ ÿéìË&²+ÿÿ ÿèõÈ&²Tÿÿ ÿéôÎ&#¿ÿÿ ÿéöÈ&¬ÿÿ ÿçõÏ&˜ÿÿ ÿéóÏ&²Oÿÿ ÿéóÈ&.ÿÿ ÿéòÏ&²lÿÿ ÿæïÐ&¸ÿÿ ÿèòÏ&¹ÿÿ ÿéïÏ&²#-ÿÿ ÿéöÐ&!JÿÿÿèõÐ&ÿÿ ÿéîÏ&ÿÿ ÿéôÐ&²…ÿÿ ÿèëÏ&Sÿÿ ÿéïÈ&*›ÿÿ ÿéðÏ&ÿÿ ÿéõÏ&²áÿÿ ÿéóÑ&=úÿÿ ÿéôÈ&0Bÿÿ ÿèôÐ&&²ÿÿ ÿéõÏ&dÿÿ ÿéöÎ&*Ÿÿÿ ÿéõÐ&ÿÿ ÿéóÍ&²&>ÿÿ ÿèõÑ&eÿÿ ÿéõÌ&²ÿÿ ÿéòÐ&Úÿÿ ÿèóÈ&1ÿÿ ÿéòÈ&#* ÿÿ ÿéóÐ&²&ëÿÿ ÿéóÐ&)ÿÿ ÿé÷È&#"nÿÿ ÿèöÏ&#/Îÿÿ ÿéùÏ&# 3ÿÿ ÿèîÏ&&ïÿÿ ÿéòÐ&²mÿÿ ÿçöÒ&fÿÿ ÿéóÑ&#ÿÿ ÿéòÏ&rÿÿ ÿéöÈ& Ö#ÿÿ ÿéóÏ&ÿÿ ÿéôÈ&²*¡ÿÿ ÿéõÉ&²"ÿÿ ÿéóÏ&#6ÿÿ ÿéîÏ&#&/Ï=ûÿÿ ÿéñÈ&²$^ÿÿ ÿéóÉ&²9ÿÿ ÿéôÏ&#=üÿÿ ÿéóÐ&$:#ÿÿ ÿèòÏ&ý0¤ÿÿ ÿèöÎ&#&=ýÿÿ ÿéóÏ&#=þÿÿ ÿéôÑ&#úÿÿ ÿéòÏ&#ÿÿ ÿéõÈ&#ÿÿÿéìÏ&ü&úùÿÿ ÿé÷Ð&$,#ÿÿ ÿéïÏ&#$’ÿÿ ÿéôÒ&=ÿÿÿ ÿéôÑ&$!ÿÿ ÿéóÒ& ÿÿ ÿéóÉ&#$-ÿÿ ÿéóÈ&çÿÿ ÿéðÑ&²"Qÿÿ ÿé÷Í&*¢ÿÿ ÿéóÌ&/2 ÿÿ ÿéóÕ& ÿÿ ÿéõÐ&* ÿÿ ÿéõÏ&/fÿÿ ÿéõÑ&#>ÿÿ ÿéóÉ&*£ÿÿ ÿèõÌ&þýÿÿ ÿçôÏ&-ÿÿ ÿéôÈ&ýØÿÿ ÿéòÐ&#-†ÿÿ ÿéõÏ&#/eÿÿ ÿéöÑ&#-›ÿÿ ÿéøÒ&#-œÿÿ ÿéòÊ&#*¤ÿÿ ÿèóÏ&#*¥ÿÿ ÿèòÐ&ý)Áÿÿ ÿéòÈ&#>ÿÿ ÿéõÏ&ý>ÿÿ ÿéôÑ&#êÿÿ ÿéóÎ&#-ÿÿ ÿéòÉ&ý*kÿÿ ÿéöË&ý/«ÿÿ ÿéôÎ&¤*¦ÿÿÿçöÎ&98ÿÿÿéëÏ&·;ÿÿÿéòÎ&<*×ÿÿÿéôÎ&ÙØÿÿ ÿéïÆ&*§*ÿÿ ÿìóÒ&/.ÿÿ ÿíòÑ&3˜ÿÿ ÿëôÎ&›ÿÿ ÿêïÑ&—˜ÿÿ ÿçëÏ&Èÿÿ ÿéæÑ&¢˜ÿÿ ÿèóÑ&q˜ÿÿ ÿêëÑ&ú˜ÿÿ ÿéøÏ&àÿÿ ÿéíÏ&îÿÿ ÿéòÑ&˜Àÿÿ ÿèøÑ&˜Áÿÿ ÿèöÏ&Úÿÿ ÿðòÎ&œ>ÿÿ ÿèóÏ&èÿÿ ÿéìÎ&Yÿÿ ÿêõÑ&&˜ÿÿ ÿèôÐ&Lÿÿ ÿñêÏ&*uÿÿ ÿèíÐ&Åÿÿ ÿéôÎ&bÿÿ ÿéöÏ&„ÿÿ ÿéðÎ&´ÿÿ ÿêðÎ&ÿÿ ÿéóÎ&žÿÿ ÿñòÏ&ÿÿ ÿêñÏ&$ÿÿ ÿèöÏ&Hÿÿ ÿîõÐ&¡ÿÿ ÿêóÏ&ÿÿ ÿèöÏ&Ÿÿÿ ÿîôÏ&Ÿÿÿ ÿñòÏ& ÿÿ ÿéôÎ&*¨ÿÿ ÿèôÏ&üÿÿ ÿéöÏ&ÿÿ ÿêöÐ& ÿÿ ÿéòÏ&0­ÿÿ ÿëõÎ&ÿÿ ÿéóÐ&…ÿÿ ÿèôÏ&â!ÿÿ ÿéóÎ&ÿÿ ÿéôÏ&”ÿÿ ÿéëÏ&%’ÿÿ ÿéòÎ&¨&yÿÿ ÿéöÎ&¨Äÿÿ ÿéïÏ&žÿÿ ÿëöÎ&6¨ÿÿ ÿéóÏ&£ÿÿ ÿëõÐ&¬ÿÿ ÿéïÐ&Öÿÿ ÿéèÐ&¨&/6ÿÿ ÿèöÑ&­ÿÿ ÿéóÐ&Ãÿÿ ÿèõÏ&!Fÿÿ ÿèõÏ&¹ÿÿ ÿëóÎ&¨1ÿÿ ÿêôÏ&¿ÿÿ ÿèõÏ&Íÿÿ ÿèôÏ&%ÿÿ ÿìõÏ&âÿÿ ÿèôÏ&âmÿÿ ÿêîÏ&*Úÿÿ ÿéòÎ&™ÿÿ ÿéíÎ&ôÿÿ ÿé÷Ï&â%úÿÿ ÿéôÏ&—ÿÿ ÿéöÏ&aÿÿ ÿêìÏ&(¾ÿÿ ÿçöÏ&9ÿÿ ÿîôÏ&6öÿÿ ÿçõÏ&"•ÿÿ ÿèïÐ&*‰¨ÿÿ ÿéóÎ&*©ÿÿ ÿçõÏ&¨Fÿÿ ÿéóÎ&.ÿÿ ÿçõÏ&¦ÿÿ ÿéòÐ&0ôÿÿ ÿéóÏ&¨ÿÿ ÿçòÐ&/‚ÿÿ ÿéôÏ&®¨ÿÿ ÿëòÎ&&¨ÿÿ ÿæïÐ&¸ÿÿ ÿèòÏ&¨*ªÿÿ ÿêëÏ&·ÿÿ ÿëèÎ&ï¨ÿÿ ÿèéÎ&/ÿÿ ÿèöÏ&+ÿÿ ÿëôÎ&žÿÿ ÿéóÏ&Oÿÿ ÿëðÏ&jÿÿ ÿëðÏ&.}ÿÿ ÿéîÎ&ÿÿ ÿëôÐ&¨Àÿÿ ÿéöÐ&!Jÿÿ ÿêõÏ&¤5ÿÿ ÿëóÏ&ˆ¨ÿÿ ÿñîÏ&!Hÿÿ ÿëîÎ&*«ÿÿ ÿéêÐ&Mÿÿ ÿëôÏ&œÿÿ ÿèðÑ&4ÿÿ ÿëðÎ&¨/­ÿÿ ÿéõÏ&â·ÿÿ ÿèòÏ&¹ÿÿ ÿéòÎ&&¶ÿÿ ÿéïÏ&â/¹ÿÿ ÿêòÎ&*¬ÿÿ ÿçóÐ&&ÿÿ ÿéõÏ&¨!Mÿÿ ÿèóÏ&âÿÿ ÿèõÏ&gÿÿ ÿéòÏ&¨Tÿÿ ÿêìÎ&ÿÿ ÿêöÏ&ÿÿ ÿïôÏ&!m¤ÿÿ ÿèôÎ&¨ÿÿ ÿééÎ&!p¨ÿÿ ÿëóÎ&íÿÿ ÿèõÎ&¨/Ðÿÿ ÿéíÎ&ˆÿÿ ÿêõÏ&¤¹ÿÿ ÿéñÎ&¨*­ÿÿ ÿîôÐ&ö>ÿÿ ÿèïÏ&â&¤ ÿÿ ÿèñÎ&8ÿÿ ÿéòÏ&¤¨ÿÿ ÿèïÏ&+^¨ÿÿ ÿèôÎ&&çÿÿ ÿéõÐ&‹¨ÿÿ ÿèóÐ&çÿÿ ÿéõÏ& âÿÿ ÿèôÏ&Ùÿÿ ÿéöÑ&éÿÿ ÿêõÎ&Lÿÿ ÿèëÏ&Sâÿÿ ÿéóÏ&*®ÿÿ ÿéìÎ&a¨ÿÿ ÿèñÎ&¨Dÿÿ ÿèôÏ&¨ÿÿ ÿëôÏ&¨ôÿÿ ÿéõÒ&¨™ÿÿ ÿéôÏ&¨&F/Õÿÿ ÿéôÏ&âSÿÿ ÿëóÎ&Tÿÿ ÿîôÉ&0Eöÿÿ ÿéóÏ&æâÿÿ ÿñöÑ&bÿÿ ÿêóÎ&=ÿÿ ÿè÷Ï&¨7ÿÿ ÿéöÏ&—¨ÿÿ ÿèóÏ&ÿÿ ÿêíÏ&¸ÿÿ ÿéöÑ&Òÿÿ ÿé÷Î&½¨ÿÿ ÿéóÏ&5ÿÿ ÿéôÐ&õ&/>ÿÿ ÿé÷Ï&â'ÿÿ ÿëòÏ&¨Yÿÿ ÿéòÏ&0×ÿÿ ÿéòÎ& ¨ÿÿÿëïÐ&Ü0Jÿÿ ÿéôÏ&¨…ÿÿ ÿé÷Ï&®¨ÿÿ ÿéìÎ&¨dÿÿ ÿéöÏ&óâÿÿ ÿèóÏ&¨0¿ÿÿ ÿéòÎ&@ÿÿ ÿéòÏ&€ÿÿ ÿëöÐ&ƒÿÿ ÿêóÏ&¨ÿÿ ÿéóÐ&¨ÿÿ ÿîôÐ&úöÿÿ ÿéñÎ&¨-ÿÿ ÿèôÏ&âcÿÿ ÿîôÏ&ö0cÿÿ ÿêõÏ&¨*¯ÿÿ ÿê÷Ð&¨-²ÿÿ ÿéêÐ&ÿÿ ÿéõÎ&¨-€ÿÿ ÿèõÐ&¨•ÿÿ ÿïôÏ&&Aâÿÿ ÿëóÎ&ÿÿ ÿéðÎ&*°ÿÿ ÿéõÐ&¨Šÿÿ ÿéøÑ&*±ÿÿ ÿéóÎ&>ÿÿ ÿéóÏ&&íÿÿ ÿîôÐ&ö>ÿÿ ÿçðÎ&*²ÿÿÿïóÐ& ;&()•ÿÿ ÿèöÏ&¤ÿÿ ÿé÷Î&ÆÿÿÿéõÐ&Ü!ÿÿ ÿéòÏ&»%>ÿÿ ÿêóÏ&"—ÿÿ ÿçõÏ&¨ÿÿ ÿè÷Ï& ßâÿÿ ÿéòÐ&¤/ÿÿ ÿèõÑ&¨eÿÿ ÿéôÏ&âÿÿÿéëÐ&Ü&à> ÿÿ ÿìóÏ&"¤ÿÿ ÿééÎ&¨ÿÿ ÿéôÎ&¨×ÿÿ ÿéóÎ&¨ýÿÿ ÿéõÎ&…¨ÿÿ ÿëòÎ&¨ÿÿ ÿéôÏ&â“ÿÿ ÿè÷Ï&â'ÿÿ ÿé÷Ñ&/CÿÿÿéïÐ&ßÜÿÿ ÿèòÎ&¨&èÿÿ ÿéòÏ&Jÿÿ ÿé÷Ï&c¨ÿÿ ÿëóÐ&/Ñÿÿ ÿêòÐ&Úÿÿ ÿéôÏ&bâÿÿ ÿçøÏ&¤?ÿÿ ÿêóÏ&Eâÿÿ ÿëóÎ&WÿÿÿéóÐ&HÜÿÿ ÿéóÐ&0Fâÿÿ ÿêõÏ&¨.ÿÿ ÿê÷Ï&¤.4ÿÿ ÿéðÏ&»âÿÿ ÿèõÎ&#ÿÿ ÿéöÐ&"IâÿÿÿïóÉ& ;&ì> ÿÿ ÿéíÏ&-iâÿÿ ÿèíÏ&â+Cÿÿ ÿèîÐ&¨“ÿÿ ÿéøÏ& $¨ÿÿ ÿéõÎ&¨1ÿÿ ÿèõÏ&¨Vÿÿ ÿéõÑ&â-gÿÿ ÿéóÏ&¨'ÿÿ ÿìõÐ&â*ÿÿ ÿéôÐ&¨'Úÿÿ ÿéíÏ&¤/–ÿÿ ÿéóÐ&¨/ùÿÿ ÿéóÎ&)¨ÿÿ ÿéíÎ&åÿÿ ÿïòÏ&¤#lÿÿ ÿéõÐ&‹¨ÿÿ ÿèóÏ&"%&*»ÿÿ ÿèóÐ&âÛÿÿ ÿéöÏ&¨/÷ÿÿ ÿëôÎ&+Tÿÿ ÿïóÐ&ì& ;> ÿÿ ÿïôÏ&)|âÿÿ ÿéòÐ&0.¨ÿÿ ÿéóÑ&â"ÿÿ ÿéöÐ&¨ Fÿÿ ÿéõÑ&Râÿÿ ÿêóÑ&¨+Aÿÿ ÿèóÏ&¨ïÿÿ ÿëôÑ&̨ÿÿ ÿéóÏ&*ÿÿ ÿçöÒ&f¨ÿÿ ÿéöÏ& ؤÿÿ ÿèöÎ&¨.ÿÿ ÿéòÑ&.‘ÿÿ ÿèðÏ&¨uÿÿ ÿéòÐ&V¨ÿÿ ÿéòÐ&'¨ÿÿ ÿéíÎ&³ÿÿ ÿçõÏ&¨­ÿÿ ÿéìÎ&¨ÿÿ ÿéôÏ& ¤ÿÿ ÿéëÏ&â¾ÿÿ ÿéìÎ&1 ¨ÿÿ ÿéóÏ&ÿÿ ÿé÷Î&"n¨ÿÿ ÿïòÏ&â <ÿÿ ÿëñÎ&¨+Mÿÿ ÿéöÏ&¨–ÿÿ ÿéòÏ&.Œâÿÿ ÿé÷Ï&.ލÿÿ ÿéóÐ&¨.ÿÿ ÿéòÐ&/x¨ÿÿ ÿéõÐ&¤.ÿÿ ÿéöÐ&¨ BÿÿÿïóÐ& ; 5ÿÿ ÿëôÏ&¨`ÿÿ ÿéíÏ&ûâÿÿ ÿéòÏ&!ÿÿ ÿéóÏ&!Ѩÿÿ ÿéóÏ&âóÿÿ ÿéöÏ&¤Õÿÿ ÿéñÎ&!Œ¨ÿÿÿïóÏ&]& ;> ÿÿ ÿèôÑ&¨$ƒÿÿ ÿèóÏ&â!‹ÿÿ ÿéóÐ&¨!Šÿÿ ÿéöÏ&â#Íÿÿ ÿèôÐ&¨!ˆÿÿ ÿéôÎ&&#ÿÿ ÿêòÎ&¨!‰ÿÿ ÿéñÎ&¨!(ÿÿ ÿëíÎ&¨!/ÿÿ ÿéñÑ&¨ÿÿÿéëÐ&à&Ü!‚ÿÿ ÿìôÐ&•&<=ÿÿ ÿçõÎ&¨!0ÿÿ ÿéôÏ&â&!1(ÿÿ ÿéõÏ&â ÿÿ ÿéóÏ& f¨ÿÿ ÿëõÑ&¨1ÿÿ ÿèóÎ&¨#Aÿÿ ÿéóÏ&¨> ÿÿ ÿèòÎ&¨!úÿÿ ÿéôÏ&»!"ÿÿ ÿêñÏ&â/ÿÿ ÿîôÏ&#c&#eöÿÿÿéöÐ&Ü.‹ÿÿÿèóÐ&@&ÜYÿÿ ÿéöÏ&¨ÿÿ ÿèôÏ&â"©ÿÿ ÿèõÎ&¯¨ÿÿ ÿéóÏ&0s»ÿÿ ÿì÷Ï&¤¥ÿÿ ÿîõÐ&¤/€ÿÿ ÿëòÏ&¨" ÿÿ ÿéóÏ&6âÿÿ ÿéñÏ&âÿÿ ÿéöÐ&¤!ÿÿ ÿëôÏ&¨!_ÿÿÿéõÐ&ÜAÿÿ ÿèóÎ&!—¨ÿÿÿçõÐ&ú&Üûÿÿ ÿëòÐ&¨2ÿÿ ÿêîÎ&¨$Xÿÿ ÿéñÑ&¨$”ÿÿ ÿèõÐ&¤ÿÿ ÿêóÐ&¨>ÿÿ ÿêöÏ&$fâÿÿ ÿìôÒ&ð&¶>ÿÿ ÿëóÐ&.Ѝÿÿ ÿéôÏ&¤üÿÿ ÿé÷Ð&â$ÿÿ ÿèòÐ&¤$ÿÿ ÿéîÐ&$}¨ÿÿ ÿèõÑ&â$*ÿÿ ÿéðÎ&¨$ÿÿÿêîÐ&$ïÜÿÿÿêõÐ&#r&GÜÿÿ ÿêöÏ&¨vÿÿ ÿéõÏ&¨#Eÿÿ ÿéóÏ&.ˆ¨ÿÿ ÿé÷Ï&-nâÿÿÿèóÐ&ÜDÿÿ ÿçõÏ&¨#ÿÿÿïóÏ& & ;ÿÿ ÿêöÏ&â$Oÿÿ ÿì÷Ò&=>ÿÿ ÿéïÐ&¨%ÿÿ ÿëòÏ&.‰ÿÿ ÿéóÐ&â/5ÿÿÿèôÐ&Ü&(>ÿÿ ÿèòÏ&â0¤ÿÿ ÿéòÐ&¨$Îÿÿ ÿéóÏ&â.‡ÿÿ ÿçóÐ&¨&rÿÿÿèñÑ&Ü0˜ÿÿ ÿëòÏ&¨$Jÿÿ ÿéòÏ&$¤ÿÿ ÿéõÏ&â$@ÿÿ ÿêõÐ&¨/ÿÿ ÿëõÑ&¤$Œÿÿ ÿéõÑ&¤—ÿÿ ÿéñÎ&¨"Ûÿÿ ÿêõÏ&¤&GÞÿÿ ÿèñÏ&â*ÿÿ ÿëóÎ&çÿÿÿéõÐ&Ü0Zÿÿ ÿéóÏ&â*ÿÿ ÿèôÎ&¨.ƒÿÿÿëõÐ&)ý0LÿÿÿëòÐ&ú&Ü>ÿÿ ÿìôÐ&¶&#\+ÿÿ ÿèõÏ&â£ÿÿ ÿéôÎ&èÿÿ ÿéóÏ&â.ÿÿ ÿêòÏ&¨/Pÿÿ ÿé÷Ï&âÿÿ ÿéîÑ&â¹ÿÿ ÿçõÑ&âxÿÿ ÿêïÏ&¤.„ÿÿ ÿéôÑ&â.‚ÿÿ ÿçôÒ&ê>ÿÿÿíõÐ&Ü.…ÿÿÿéóÐ&ÛÜÿÿ ÿéòÏ&â.†ÿÿ ÿéöÑ&¤0Gÿÿ ÿëóÏ&¨§ÿÿ ÿéóÎ&¨+Dÿÿ ÿçôÒ&£¤ÿÿ ÿéôÒ&âÿÿ ÿèöÑ&â,:ÿÿ ÿéóÎ&¨$-ÿÿÿèöÐ&ÜÒÿÿ ÿçõÏ&â0›ÿÿÿéïÐ&Ü-…ÿÿÿêöÐ&Ü*ÿÿ ÿìôÏ&¶&>ÿÿ ÿìôÐ&¶0ùÿÿ ÿçðÑ&¤pÿÿ ÿçöÑ&¤+àÿÿ ÿéóÏ&+á¨ÿÿ ÿéöÏ&¤0Ûÿÿ ÿêõÐ&* ¨ÿÿ ÿëõÐ&¨*¿ÿÿÿèõÐ&Ü&,<>ÿÿ ÿèõÏ&âþÿÿ ÿéôÏ&â*Ñÿÿ ÿéöÐ&¤>ÿÿÿéòÐ&Ü%áÿÿÿéóÐ&÷&Ü>ÿÿ ÿéñÐ&â)ºÿÿ ÿèõÏ&¨+ßÿÿ ÿèóÏ&#6ÿÿ ÿçòÏ&¨*¾ÿÿ ÿìôÐ&=&!ª!¤ÿÿ ÿêòÏ&âüÿÿÿèõÐ&Ü(jÿÿ ÿèõÏ&»&"%"$ÿÿÿçõÐ&ú&Ü"&ÿÿ ÿéóÐ&â(Ôÿÿ ÿêíÎ&ž¨ÿÿ ÿéöÏ&*Àâÿÿ ÿéóÒ&¨ÿÿ ÿéõÏ&¨*ÿÿ ÿéóÐ&»&lmÿÿ ÿèòÏ&â*ÃÿÿÿèõÐ&Ü&>ÿÿ ÿéóÎ&*Á¨ÿÿ ÿèôÏ&â,1ÿÿ ÿèôÐ&»ýÿÿ ÿíóÏ&´âÿÿ ÿîöÏ&âÿÿ ÿìõÏ&¤&#q!ÿÿ ÿéôÐ&âcÿÿ ÿêóÐ&¼»ÿÿ ÿéóÐ&ýÿÿ ÿèöÐ&¨,2ÿÿÿêöÐ&Ü&â!£ÿÿ ÿèðÏ&,3âÿÿ ÿéðÏ&â,4ÿÿ ÿêöÏ&¤,5ÿÿÿèôÑ&Ü.¡ÿÿ ÿéøÏ&¡¤ÿÿ ÿé÷Ñ&¨)¿ÿÿ ÿêðÏ&¤#7ÿÿ ÿéñÏ&¤0¸ÿÿ ÿéóÏ&»>ÿÿ ÿéõÎ&0 ¨ÿÿ ÿéöÏ&¤/Ýÿÿ ÿéôÎ&بÿÿ ÿêõÏ&¤&G>ÿÿ ÿéñÏ&¨*wÿÿ ÿéöÐ&â/ßÿÿ ÿèöÐ&¨)òÿÿÿêôÐ&Ü.÷ÿÿ ÿïóÐ& ;.ÿÿ ÿìöÑ&â)óÿÿÿèôÐ&Ü%òÿÿ ÿêòÏ&â>ÿÿÿéóÐ&Ü0Äÿÿ ÿêõÐ&¤,‰ÿÿÿéóÐ&Ü&/á>ÿÿ ÿéõÑ&â5ÿÿÿéöÐ&Ü'èÿÿ ÿëóÏ&/à¨ÿÿ ÿéõÏ&!Öÿÿ ÿìôÏ&¶>ÿÿ ÿçóÑ&¡âÿÿ ÿéñÐ&-¢¤ÿÿÿéñÐ&Ü0"ÿÿ ÿíóÐ&â*yÿÿ ÿìôÑ&¶>ÿÿÿêöÐ&Ü&â>ÿÿ ÿèòÏ&¤%\ÿÿÿéóÐ&Ü%^ÿÿÿèóÐ&Ü&%W%XÿÿÿèòÒ&Ü%~ÿÿÿéóÐ&Ü> ÿÿ ÿç÷Ò&¤±ÿÿÿèóÐ&Ü)õÿÿ ÿêöÐ&¤)öÿÿÿêòÐ&Ü-/ÿÿ ÿéöÏ&â%fÿÿÿéöÐ&Ü}ÿÿ ÿïóÏ&¤,6ÿÿ ÿéóÑ&¤Îÿÿ ÿéôÑ&)ù>!ÿÿÿéíÐ&Ü)úÿÿ ÿéõÏ&¤>"ÿÿÿê÷Ð&Ü*‘ÿÿ ÿéöÏ&â*‡ÿÿÿèòÑ&Ü7ÿÿ ÿéòÑ&¤-ÿÿÿéóÐ&Ü>#ÿÿ ÿéðÏ&¨>$ÿÿ ÿéóÏ&¨(ÎÿÿÿéöÐ&Ü>%ÿÿÿéóÐ&Ü)ûÿÿÿíóÐ&Ü>&ÿÿÿéóÐ&Ü.@ÿÿ ÿçõÐ&â(pÿÿ ÿìôÒ&¶,xÿÿ ÿêôÏ&¹&)ü>'ÿÿÿèöÏ&0H&0)ýÿÿ ÿéóÏ&»ÅÿÿÿæðÑ&)ý)oÿÿÿéóÐ&Ü/éÿÿ ÿíòÏ&&3ÿÿ ÿïôÏ&w›ÿÿ ÿéóÏ&wjÿÿ ÿèóÏ&qwÿÿ ÿéæÏ&¢&ÿÿ ÿêëÏ&wúÿÿ ÿêïÏ&w—ÿÿ ÿïòÏ& &ÿÿ ÿéóÏ&Q ÿÿ ÿèíÐ&Å&ÿÿ ÿêõÏ&wõÿÿ ÿéöÏ&„&ÿÿ ÿéìÏ&Y&ÿÿ ÿèðÒ&Q¡ÿÿ ÿèôÏ&ü&ÿÿ ÿèöÏ&Ÿ&ÿÿ ÿéòÏ&™&ÿÿ ÿéôÏ&Q›ÿÿ ÿéôÏ&—&ÿÿ ÿïôÏ&ž&ÿÿ ÿìõÏ&Qÿÿ ÿèôÏ&O(/ÿÿ ÿéîÏ&Qœÿÿ ÿéîÏ&Q°ÿÿ ÿéïÏ&Q&ÿÿ ÿéëÏ&Ošÿÿ ÿêòÏ&O¾ÿÿ ÿèöÑ&­Qÿÿ ÿéêÏ&¡&ÿÿ ÿéõÑ&¶&ÿÿ ÿêïÏ&w*Øÿÿ ÿêìÏ&O)þÿÿ ÿéíÐ&QÕÿÿ ÿéöÏ&a&ÿÿ ÿéóÐ&…&ÿÿ ÿé÷Ï&%ú&ÿÿ ÿéôÏ&”&ÿÿ ÿîóÏ&Q1ÿÿ ÿëõÏ&&ÿÿ ÿïòÏ&Q&ÿÿ ÿèõÏ&P»ÿÿ ÿïôÏ&O!mÿÿ ÿéöÏ&Qÿÿ ÿéóÏ&Qÿÿ ÿèôÏ&Q)wÿÿ ÿéóÏ&Q.ÿÿ ÿïòÏ&Q,7ÿÿ ÿèòÏ&O&ÿÿ ÿèôÐ&OÐÿÿ ÿçóÐ&&&ÿÿ ÿéóÏ&Qÿÿ ÿééÏ&Q!pÿÿ ÿïóÏ&î&ÿÿ ÿèìÏ&O&ÿÿ ÿëèÏ&Qïÿÿ ÿéëÏ&Q)ÿÿÿ ÿéòÏ&Q(-ÿÿ ÿéêÐ&M&ÿÿ ÿçõÏ&¦Qÿÿ ÿêòÏ&Q ÿÿ ÿêñÏ&Q*ˆÿÿ ÿéïÏ&O/¹ÿÿ ÿèïÐ&Q*‰ÿÿ ÿíðÏ&Q.}ÿÿ ÿïðÏ&j&ÿÿ ÿë÷Ï&Pÿÿ ÿéòÏ&O'Êÿÿ ÿèóÏ&Oÿÿ ÿéðÏ&Q*Öÿÿ ÿêóÏ&Qÿÿ ÿëòÏ&QYÿÿ ÿéóÏ&ÁQÿÿ ÿéòÏ&Q€ÿÿ ÿçøÏ&P?ÿÿ ÿæöÏ&Qÿÿ ÿèñÏ&à&È&eÿÿ ÿæôÏ&Qÿÿ ÿéíÏ&Qêÿÿ ÿéìÏ&aOÿÿ ÿèìÏ&Á&ÿÿ ÿéîÏ&Q‹ÿÿ ÿéìÏ&Qdÿÿ ÿíóÐ&Q,8ÿÿ ÿèïÏ&à& >(ÿÿ ÿèôÏ&O›ÿÿ ÿéóÏ&Qæÿÿ ÿéõÏ&P0xÿÿ ÿéòÐ&QAÿÿ ÿéôÐ&QTÿÿ ÿïöÑ&Oaÿÿ ÿéóÏ&Q¥ÿÿ ÿéôÑ&Qàÿÿ ÿé÷Ï&PEÿÿ ÿéõÐ&Q‹ÿÿ ÿêíÏ&¸&ÿÿ ÿéóÑ&Q"ÿÿ ÿíöÐ&Qvÿÿ ÿèôÐ&POÿÿ ÿé÷Ï&O®ÿÿ ÿêóÐ&OÓÿÿ ÿéóÏ&O'ÿÿ ÿéíÏ&à&%7%8ÿÿ ÿèóÏ&Q0¾ÿÿ ÿêõÏ&Q~ÿÿ ÿëïÏ&à0Jÿÿ ÿéóÏ&PŽÿÿ ÿéïÐ&Q,9ÿÿ ÿêóÒ&P0ìÿÿ ÿéöÏ&Oÿÿ ÿéóÐ&PHÿÿ ÿëóÐ&à&Iúÿÿ ÿïõÏ&O*ÿÿ ÿéêÐ&cOÿÿ ÿçôÏ&RQÿÿ ÿïóÏ&Oëÿÿ ÿïòÏ&Q0Aÿÿ ÿéïÐ&ßàÿÿ ÿéîÏ&-BOÿÿ ÿêóÏ&Q&?ÿÿ ÿé÷Ï&Ocÿÿ ÿæôÏ&O-ÿÿ ÿïôÏ&O&Aÿÿ ÿéõÏ&Q…ÿÿ ÿéôÏ&Oÿÿ ÿéóÏ&Q*ÿÿ ÿèöÐ&Q&ÿÿ ÿèíÏ&P&Ó>)ÿÿ ÿèíÏ&P&Ó>*ÿÿ ÿéôÏ&Obÿÿ ÿèóÐ&ŒPÿÿ ÿèõÐ&QLÿÿ ÿéöÏ&P”ÿÿ ÿèòÏ&Q&èÿÿ ÿéóÏ&à&~*ÿÿ ÿéóÏ&O'ÿÿ ÿéóÏ&Q6ÿÿ ÿéñÏ&O'Üÿÿ ÿéóÏ&&ÿÿ ÿéõÏ&O'Ýÿÿ ÿéóÏ&P Gÿÿ ÿêòÏ&Oÿÿ ÿéõÏ&O1ÿÿ ÿëôÏ&O(mÿÿ ÿéíÏ&Oÿÿ ÿéìÏ&Q1 ÿÿ ÿéëÏ&Q>+ÿÿ ÿïòÏ&£àÿÿ ÿéõÐ&O‹ÿÿ ÿéïÐ&Qxÿÿ ÿçöÐ&à&#‚>,ÿÿ ÿìõÐ&O*ÿÿ ÿìõÏ&P&!>-ÿÿ ÿéõÐ&Q#¡ÿÿ ÿèóÏ&Qïÿÿ ÿëôÑ&QÌÿÿ ÿéóÏ&à0sÿÿ ÿéðÏ&P»ÿÿ ÿïòÏ&O <ÿÿ ÿéðÐ&O# ÿÿ ÿéôÏ&O#šÿÿ ÿéòÐ&Q<ÿÿ ÿéòÐ&à&0ßÿÿ ÿì÷Ï&O¥ÿÿ ÿêõÏ&Oÿÿ ÿèòÏ&Q!ûÿÿ ÿéõÏ&à"Ëÿÿ ÿîôÏ&!_Qÿÿ ÿèöÏ&à&#"#!ÿÿ ÿèòÏ&Q0¤ÿÿ ÿèõÏ&P Óÿÿ ÿéóÐ&O]ÿÿ ÿèôÑ&O$ƒÿÿ ÿèôÏ&O!˜ÿÿ ÿèôÐ&P#¸ÿÿ ÿéòÐ&#Oÿÿ ÿçìÏ&O!ÿÿ ÿéöÏ&PNÿÿ ÿçóÐ&Q&rÿÿ ÿéðÏ&O$ ÿÿ ÿèõÏ&/8Pÿÿ ÿéñÏ&P$Ïÿÿ ÿëõÑ&O/ÿÿ ÿéóÏ&O>.ÿÿ ÿéïÐ&O%ÿÿ ÿéòÐ&P%ÿÿ ÿéðÑ&Q$•ÿÿ ÿîòÏ&P$Iÿÿ ÿéõÑ&P—ÿÿ ÿêóÐ&O/Nÿÿ ÿéóÏ&P†ÿÿ ÿïóÏ&à¦ÿÿ ÿéóÏ&O*ÿÿ ÿéôÏ&P)Ëÿÿ ÿçôÒ&à£ÿÿ ÿèöÑ&P,:ÿÿ ÿéîÑ&P¹ÿÿ ÿéõÏ&à0Zÿÿ ÿëõÐ&0K0Lÿÿ ÿèôÏ&O*ÿÿ ÿèóÏ&Püÿÿ ÿéôÏ&P* ÿÿ ÿêóÐ&O* ÿÿ ÿéôÐ&P,;ÿÿ ÿèõÐ&à&>/ÿÿ ÿçíÑ&à”ÿÿ ÿéôÐ&Ocÿÿ ÿèðÑ&P&3>0ÿÿ ÿèõÏ&à&,<>1ÿÿ ÿîöÏ&Pÿÿ ÿéõÏ&P!×ÿÿ ÿéðÏ&P0·ÿÿ ÿéñÏ&P'ÿÿ ÿèôÑ&à.¡ÿÿ ÿìöÑ&O)óÿÿ ÿéôÏ&à0Mÿÿ ÿêòÐ&à-/ÿÿ ÿéòÑ&O-ÿÿ ÿêòÎ&(*ÿÿ ÿèöÏ&+ÿÿ ÿéïÇ&* * ÿÿ ÿéõÏ&*ÿÿÿéìÊ&f>4ÿÿÿéìÊ&f>5ÿÿÿéìÊ&f>6ÿÿÿéìÊ&feÿÿÿéíË&ü>7ÿÿÿéìÊ&f~ÿÿÿéìÊ&f>8ÿÿÿéíË&ü>9ÿÿÿèíÉ&>:ÿÿÿéíË&ü>;ÿÿÿèíÉ&><ÿÿÿéìÊ&f>=ÿÿÿèíÉ&>>ÿÿÿéìÊ&f>?ÿÿÿèíÉ&>@ÿÿÿéìÊ&f>AÿÿÿèíÉ&>BÿÿÿèíÉ&>CÿÿÿèíÉ&>DÿÿÿèíÉ&>EÿÿÿèíÉ&>FÿÿÿèíÉ&>GÿÿÿèíÉ&>HÿÿÿèíÉ&>IÿÿÿéíË&ü>JÿÿÿèíÉ&œÿÿÿèíÉ&ßÿÿÿèíÉ&>KÿÿÿèíÉ&êÿÿÿèíÉ&>LÿÿÿèíÉ&>ÿÿÿèíÉ&õÿÿÿèíÉ&>MÿÿÿèíÉ&ãÿÿÿèíÉ&>NÿÿÿèíÉ&>OÿÿÿèíÉ&%CÿÿÿèíÉ&ÓÿÿÿèíÉ&‹ÿÿÿèíÉ&ŠÿÿÿéëÈ&ÛdÿÿÿèíÉ&´ÿÿ ÿèíÎ&û&§>PÿÿÿèíÉ&>QÿÿÿéëÈ&d>RÿÿÿèíÉ&>SÿÿÿèíÉ&>TÿÿÿèíÉ&>UÿÿÿèíÉ&>VÿÿÿéëÈ&d>WÿÿÿéëÈ&d!{ÿÿÿèíÉ&"=ÿÿÿéëÈ&d>XÿÿÿèíÉ&>YÿÿÿèíÉ&>ZÿÿÿéëÈ&d"ýÿÿÿèíÉ& ŸÿÿÿèíÉ&>[ÿÿÿçíÉ&"€ÿÿÿèíÉ&>\ÿÿÿèíÉ&>]ÿÿÿéëÈ&d>^ÿÿÿéëÈ&d>_ÿÿÿèíÉ&$YÿÿÿèíÉ&>`ÿÿÿèíÉ&>aÿÿÿèíÉ&>bÿÿÿèíÉ&>cÿÿÿèíÉ&&ÒÿÿÿéëÈ&d>dÿÿÿèíÉ&>eÿÿÿèíÉ&$ÿÿÿéëÈ&d>fÿÿÿéëÈ&d>gÿÿÿéëÈ&deÿÿÿèíÉ&>hÿÿÿèíÉ&(|ÿÿÿéëÈ&d>iÿÿÿéëÈ&d>jÿÿÿéëÈ&d>kÿÿÿèæÒ&’>lÿÿÿèæÒ&’¤ÿÿÿèæÒ&’>mÿÿÿèæÒ&’ÿÿÿèæÒ&’>nÿÿÿèæÒ&F’ÿÿÿèæÒ&’>oÿÿÿèæÒ&’>pÿÿÿèæÒ&’>qÿÿÿèæÒ&’>rÿÿÿèæÒ&’>sÿÿÿèæÒ&’>tÿÿÿèæÒ&’>uÿÿÿèæÒ&’>vÿÿÿèæÒ&’>wÿÿÿèæÒ&’>xÿÿÿèæÒ&’öÿÿÿèæÒ&’>yÿÿÿèæÒ&’>zÿÿÿèæÒ&’>{ÿÿÿèæÒ&’>|ÿÿÿèæÒ&’>}ÿÿÿèæÒ&’>~ÿÿÿèæÒ&£’ÿÿÿèæÒ&’äÿÿÿèæÒ&’ïÿÿÿèæÒ&‰’ÿÿÿèæÒ&’>ÿÿÿèæÒ&‘’ÿÿÿèæÒ&’ÿÿÿèæÒ&µ’ÿÿÿèæÒ&’ dÿÿÿèæÒ&’>€ÿÿÿèæÒ&’>ÿÿÿèæÒ&’>‚ÿÿÿèæÒ&’>ƒÿÿÿèæÒ&’“ÿÿÿèæÒ&’>„ÿÿÿèæÒ&">’ÿÿÿèæÒ&’>…ÿÿÿèæÒ&’"ÿÿÿèæÒ&’>†ÿÿÿèæÒ&’>‡ÿÿÿèæÒ&’>ˆÿÿÿèæÒ&’>‰ÿÿÿèíÏ&ÝìÿÿÿçõÏ&Ý«ÿÿÿéöÆ&m/mÿÿÿéóÊ&m ÿÿÿéõÆ&m³ÿÿÿéõÐ&mmÿÿÿéóÎ&m²ÿÿÿèòÆ&m0$ÿÿÿèõÏ&Ý‘ÿÿÿéòÐ&m±ÿÿÿèõÏ&ÝËÿÿÿèöÇ&ÝÞÿÿÿèóÊ&ßÝÿÿÿéõÍ&m,ÿÿÿèóÏ&m=ÿÿÿèòÏ&Ý0«ÿÿÿèíÇ&ôÝÿÿÿèëÉ&Ý%’ÿÿÿèðÏ&·mÿÿÿèöÏ&Ý¡ÿÿÿèóÏ&Ý"WÿÿÿèóÏ&ÝîÿÿÿèõÇ&ÝÿÿÿèóÐ&ÝUÿÿÿèóÏ&ÝÿÿÿéóÆ&m¶ÿÿÿéóÏ&mÂÿÿÿèóÏ&mÿÿÿéôÆ&m%ËÿÿÿéóÐ&m&@}ÿÿÿèôÆ&m)€ÿÿÿèóÐ&Ý>ŠÿÿÿéóÏ&m'¦ÿÿÿéóÏ&m)ÿÿÿéõÆ&m%"ÿÿÿèíÏ&mÿÿÿéòÆ&>‹ÿÿÿéóÆ&mÿÿÿéóÐ&m}ÿÿÿéöÏ&–mÿÿÿèòÏ&Ý{ÿÿÿèõÉ&~ÝÿÿÿéòÆ&mÅÿÿÿéõÐ&móÿÿÿéôÐ&m7ÿÿÿæöÏ&m³ÿÿÿéòÆ&m©ÿÿÿéóÏ&mÿÿÿèôÏ&m ÙÿÿÿéõÏ&àmÿÿÿéóÍ&&ªÿÿÿéóÎ&mÀÿÿÿéóÏ&ÍmÿÿÿéõÏ&m’ÿÿÿéóÏ&m(:ÿÿÿèõÐ&ZãÿÿÿèîÑ&Ý ¶ÿÿÿéóÏ&m+ÿÿÿéõÆ&m.ÿÿÿéôÈ&m[ÿÿÿéõÏ&)zmÿÿÿéòÐ&m%ÐÿÿÿéóÐ& OmÿÿÿèõÇ&Z.3ÿÿÿéóÎ&m&ÇÿÿÿéøÏ&m ÿÿÿéóÐ&m>ŒÿÿÿéîÏ&¿ÿÿÿéòÏ&£ÿÿÿéõÏ&mÿÿÿéôÏ&m'¯ÿÿÿèôÏ&m'ØÿÿÿèìÏ&m'gÿÿÿéëÎ&m'hÿÿÿéñÍ&m)cÿÿÿéõÐ&m )ÿÿÿéóÏ&+mÿÿÿçõÉ&YÿÿÿèõÈ&m"AÿÿÿéôÏ&!^ÿÿÿéöÏ&mÿÿÿéóÊ&m"ÿÿÿéîÉ& rÿÿÿéôÐ&m>ÿÿÿè÷Ñ&0NÿÿÿèóÆ&m#;ÿÿÿéóÐ&m>ŽÿÿÿéøÐ&m©ÿÿÿéñÏ&m)‚ÿÿÿéõÑ&m"±ÿÿÿéñÏ&m#ÓÿÿÿèòÎ&Z&ú>ÿÿÿéøÏ&/²mÿÿÿéòÏ&mLÿÿÿèõÐ&'pÿÿÿéóÐ&m>ÿÿÿéðÆ&m$ ÿÿÿèõÈ&m$ÿÿÿéñÑ&m#ôÿÿÿéôÏ&*)ÿÿÿéóÐ&m>‘ÿÿÿéöÏ& ÿÿÿéôÑ&mnÿÿÿéîÏ&mÿÿÿèõÐ&Z&ÿÿÿéöÏ&.IÿÿÿéõÒ&¢ÿÿÿèôÎ&%(ÿÿÿéôÎ&%'ÿÿÿçôÏ&'|mÿÿÿéõÐ&&Þ>’ÿÿÿéõÐ&&Þ>“ÿÿÿèöÑ&'Âÿÿÿé÷Ñ&&Ãÿÿÿè÷É&ZüÿÿÿçøÒ&ÕÿÿÿèóÏ&Z%õÿÿÿèòÊ&)ƒÿÿÿéöÑ&0OÿÿÿéðÏ&m>”ÿÿ ÿêôÒ&³²ÿÿÿèöÐ&ZHÿÿ ÿé÷Ï&1Nÿÿ ÿé÷Ï&1>•ÿÿÿèðÒ& ÿÿ ÿéñÑ&„)„ÿÿ ÿéõÐ&‹ÿÿ ÿéóÐ& n)…ÿÿ ÿèðÇ&s&R>˜ÿÿ ÿèóÒ&)† nÿÿ ÿçóÐ&)Œ nÿÿ ÿéóÐ& nPÿÿ ÿéõÐ&‡‹ÿÿ ÿèôÐ&UØÿÿ ÿèïÏ&)>™ÿÿ ÿéñÏ&ˆ>šÿÿ ÿéõÐ&‹ŠÿÿÿéóÐ&)tÿÿÿééÒ&>›ÿÿ ÿéóÐ&7)ÿÿÿèòÐ&œ>œÿÿ ÿéõÐ&)Ž‹ÿÿ ÿéõÐ&‹-5ÿÿ ÿéóÐ&(&')ÿÿÿéóÐ&†ÿÿ ÿéóÐ&,= nÿÿ ÿéóÏ&˜)ÿÿ ÿéóÏ&9˜ÿÿÿéóÐ& m nÿÿ ÿéòÏ&¦&¨§ÿÿ ÿéõÏ&q!×ÿÿ ÿèðÐ&r& sÿÿÿéñÏ&k>ÿÿ ÿéóÑ&˜%¯ÿÿ ÿéóÐ&$»˜ÿÿ ÿéóÏ&˜>žÿÿ ÿèóÑ&),Qÿÿ ÿéóÏ&)‘&))“ÿÿ ÿéóÏ&˜Šÿÿ ÿéóÑ&,>˜ÿÿ ÿéóÏ&€˜ÿÿ ÿçõÒ&,?> ÿÿÿéðË&0P ¾ÿÿÿéïË& ¼>¡ÿÿÿéïË& ¼ Áÿÿ ÿèôË& ½ÿÿ ÿçõË& ¾>¢ÿÿÿéòË&$¢ ¾ÿÿ ÿèïË&(&' ¾ÿÿ ÿéóË& ¾>£ÿÿÿæñÌ& À>¤ÿÿÿèïË&+# ¼ÿÿ ÿéóË& ¾>¥ÿÿ ÿêóË& ¾>¦ÿÿ ÿéïÌ& À>§ÿÿ ÿéóË& ¾)ÿÿÿçóÌ& À>¨ÿÿÿéôË& ¾*ÿÿÿèïË& ½QÿÿÿèïÌ& À+ÿÿÿçôË&-&, ¾ÿÿÿéïÌ&R Àÿÿ ÿéóÌ& À>©ÿÿ ÿéõÌ& À>ªÿÿ ÿéóÌ& À>«ÿÿÿçïË& ¾&->¬ÿÿÿéòÌ& À‹ÿÿÿïïË& ¾>­ÿÿÿæðÌ& ¿.ÿÿ ÿèïÌ& À>®ÿÿ ÿéóÎ&>¯ÿÿ ÿèòÎ& Nÿÿ ÿéóÌ& À>°ÿÿÿçóÌ& À&->±ÿÿÿè÷Ð&,@;ÿÿÿèòÌ& À eÿÿ ÿêöË& ¾>²ÿÿ ÿéðÎ&&>³ÿÿ ÿêóÎ&!âÿÿ ÿéïË& ¾>´ÿÿÿêïË& ¾>µÿÿÿéòÌ& À ”ÿÿ ÿèôÎ&&ÿÿÿéöÌ& À>¶ÿÿ ÿèóÎ&ý&üÿÿÿéïÌ& ¿>·ÿÿÿéðÎ&>¸ÿÿÿéóÎ&>¹ÿÿ ÿêòÎ&>ºÿÿÿêõÎ&>»ÿÿ ÿéòÎ&>¼ÿÿ ÿèðÎ&>½ÿÿ ÿéòÎ&>¾ÿÿÿéðÎ&>¿ÿÿ ÿçóÌ& ¿>ÀÿÿÿéïÌ& ¿>ÁÿÿÿéóÌ&°& ¿>Âÿÿ ÿîòÎ&>Ãÿÿ ÿéóÎ&>Äÿÿ ÿéòÎ&>ÅÿÿÿéöÌ& ¿>Æÿÿ ÿèøÎ&>Çÿÿ ÿéðÎ&&ÿÿ ÿéðÎ&({ÿÿÿéóÎ&>Èÿÿ ÿîòÌ& ¿>Éÿÿ ÿçõÌ& ¿%zÿÿÿèòÎ&>Êÿÿ ÿéóÎ&>Ëÿÿ ÿêðÌ& ¿>Ìÿÿ ÿéðÎ&>ÍÿÿÿéïÌ& ¿>ÎÿÿÿèöÐ&&)”>ÏÿÿÿëöÌ& ¿>Ðÿÿ ÿíòÎ&>ÑÿÿÿèòÐ&0Q)”ÿÿ ÿéðÎ&&>Òÿÿ ÿéïÌ& ¿>Óÿÿ ÿéïÌ& ¿>Ôÿÿ ÿèôÐ&,„ÿÿ ÿéóÏ&'+íÿÿ ÿéóÏ&Š'°ÿÿ ÿéôÐ&w„ÿÿ ÿéóÑ&„%ÿÿ ÿéðÐ&„Iÿÿ ÿéôÑ&…„ÿÿ ÿéôÑ&„¦ÿÿ ÿéòÐ&0R>Øÿÿ ÿéóÐ&Ç>Ùÿÿ ÿêóÑ&õ"Mÿÿ ÿèîÇ&E&sDÿÿ ÿêðÆ&H"Mÿÿ ÿèóÊ&A>Úÿÿ ÿèóÏ&qÿÿ ÿèöÏ&Ÿÿÿ ÿéóÏ&!,ÿÿ ÿéîÏ&ÿÿ ÿéìÏ&Yÿÿ ÿéõÏ&!*ÿÿ ÿéöÏ&ÿÿ ÿèöÑ&!Z­ÿÿ ÿéóÏ&}ÿÿ ÿéõÏ&&!E!Dÿÿ ÿéõÏ&ÿÿ ÿéèÐ&/& 6ÿÿ ÿèõÏ&!Fÿÿ ÿéîÏ& °ÿÿ ÿéõÏ&!Gÿÿ ÿéîÏ&!Hÿÿ ÿéóÏ&íÿÿ ÿèðÏ&Çÿÿ ÿéóÏ& Qÿÿ ÿéïÏ& & ó!Iÿÿ ÿéîÏ&ÿÿ ÿéôÏ& ®ÿÿ ÿéöÐ&!Jÿÿ ÿéóÏ& &!L!Kÿÿ ÿéõÏ&3ÿÿ ÿéõÏ& !Mÿÿ ÿéòÏ&!Nÿÿ ÿé÷Ñ&çÿÿ ÿéòÏ&0×ÿÿ ÿéôÏ& ôÿÿ ÿèóÏ& 0¿ÿÿ ÿéõÏ&Lÿÿ ÿèôÊ&0S!Oÿÿ ÿéóÏ&²ÿÿ ÿèöÏ& &!Pÿÿ ÿèõÏ&!Z)•ÿÿ ÿéöÏ&)–ÿÿ ÿéôÐ& í>Ûÿÿ ÿèìÑ&!Xÿÿ ÿéëÏ&!Wÿÿ ÿéóÐ&Yÿÿ ÿéöÏ& /÷ÿÿ ÿèîÐ& “ÿÿ ÿéóÏ&) ÿÿ ÿéðÏ&  ÿÿ ÿéõÐ& &!R!Qÿÿ ÿèõÏ&¯ ÿÿ ÿéöÏ&&ÿÿ ÿèóÐ& 2ÿÿ ÿèôÏ& !îÿÿ ÿéõÏ& "Íÿÿ ÿéñÏ& ÿÿ ÿèóÐ&0&1ÿÿ ÿéñÏ&!Z/ÿÿ ÿéóÏ&0s0zÿÿ ÿèôÏ& &Q"èÿÿ ÿéóÏ& óÿÿ ÿèôÏ& >Üÿÿ ÿèôÐ&!Z!Yÿÿ ÿéöÏ&!Z#Fÿÿ ÿéòÐ& $cÿÿ ÿèóÑ& >Ýÿÿ ÿèóÉ&  ÿÿ ÿéóÐ&0& >Þÿÿ ÿçõÏ& !Sÿÿ ÿçõÏ& !Uÿÿ ÿéõÏ&!Tÿÿ ÿéôÏ& .÷ÿÿ ÿéõÏ& !Öÿÿ ÿèôÏ& !Vÿÿ ÿèòÏ& >ßÿÿ ÿçõÐ& # ÿÿ ÿéõÏ&Ë ÿÿ ÿéôÐ&)5 ÿÿ ÿéîÐ&#ÿÿ ÿçòÐ&"ÿ#ÿÿ ÿéõÐ&#!Gÿÿ ÿèõÐ&öóÿÿ ÿé÷Ñ&ç#ÿÿ ÿéóÐ&¦#ÿÿ ÿèõÐ&##ÿÿ ÿéóÑ&##ÿÿ ÿéôÐ&&##ÿÿ ÿéòÐ&!2#ÿÿ ÿéöÐ&·#ÿÿ ÿçöÐ&!3tÿÿ ÿèóÐ&##ÿÿ ÿèôÐ&!Y#ÿÿ ÿéôÐ&Ç#ÿÿ ÿè÷Ñ&# #ÿÿ ÿéôÐ&##ÿÿ ÿéïÐ&##ÿÿ ÿçõÑ&# # ÿÿ ÿéîÎ&q 'ÿÿ ÿèíÏ&¥rÿÿ ÿçöÏ&stÿÿ ÿéôÏ&Æ1ÿÿ ÿèóÏ&Æ$ºÿÿ ÿóóÏ&Ñ!\ÿÿ ÿîóÐ&)—&ì>àÿÿ ÿîòÏ&!]>âÿÿÿéîÑ&!Ë!ÊÿÿÿèðÑ&!Ì!Êÿÿ ÿéôÑ&!ÍÿÿÿéôÑ&!Î!Ïÿÿ ÿéóÑ&!Í!ÑÿÿÿèõÑ&!Î!Ðÿÿ ÿéóÑ&!Ò>ãÿÿ ÿéóÐ&™!ÓÿÿÿéñÒ&!Ô!Óÿÿ ÿéõÑ&!Í!Öÿÿ ÿèõÈ&yxÿÿÿèõÈ&:xÿÿ ÿèõÎ&^ÿÿ ÿèõÈ&xÿÿ ÿèõÏ&]^ÿÿ ÿèöÈ& Åÿÿ ÿèõÉ&)˜xÿÿ ÿèõÈ&Àxÿÿ ÿéõÇ&ž!ÿÿ ÿèõÏ&^ Æÿÿ ÿèõÍ&^ Çÿÿ ÿéõÇ&! ÈÿÿÿéõÇ& É!ÿÿ ÿèõÇ&!—ÿÿ ÿéõÍ&!˜ÿÿ ÿéõÍ&"!ÿÿ ÿéõÏ&!¿ÿÿÿçóÐ& ã„ÿÿÿéõÏ&!cÿÿ ÿéóÇ&„ äÿÿ ÿèõÏ&!­ÿÿ ÿéõÐ&!ëÿÿ ÿéóÍ&o„ÿÿ ÿèóÏ&„0Áÿÿ ÿéòÏ&“´ÿÿ ÿéõÏ&,!ÿÿÿéõÇ&!Ÿÿÿ ÿéóÐ&„ ÿÿÿèõÐ&^¥ÿÿ ÿéóÇ&X„ÿÿ ÿéóÑ&„)™ÿÿ ÿéòÌ&´Gÿÿ ÿéóÏ&D„ÿÿ ÿéõÇ&!ÿÿ ÿèòÐ& ¡>äÿÿ ÿçóÏ& á„ÿÿ ÿçñÎ& ©0|ÿÿ ÿéõÌ&5&!>åÿÿ ÿéòÇ&R´ÿÿ ÿèóÑ& Ô&„>æÿÿÿéóÇ&„ Óÿÿ ÿéóÍ&/µ„ÿÿ ÿéóÐ&=„ÿÿ ÿéóÏ& °„ÿÿ ÿéóÍ&/³„ÿÿ ÿéòÑ&´ÏÿÿÿçóÏ&„- ÿÿÿéòÇ&´ ÐÿÿÿéòÏ& Ò´ÿÿ ÿéóÊ&|„ÿÿ ÿèóÌ&5& Ñ„ÿÿ ÿéõÏ&!ÿÿÿèóÐ&„yÿÿ ÿéòÇ&´"0ÿÿ ÿçöÊ&"tÿÿ ÿéòÏ&´„ÿÿ ÿéóÇ&„zÿÿ ÿéòÐ&!è ¡ÿÿ ÿéòÇ& ¡! ÿÿ ÿéòÏ& Ï!ÿÿÿéòÉ&´ |ÿÿ ÿèòÏ&´Žÿÿ ÿèòÏ&´…ÿÿ ÿéóÇ&„>çÿÿÿéòÏ&)¨ ¡ÿÿ ÿéòÇ&³´ÿÿÿéòË&´ÿÿ ÿéòÏ&´)©ÿÿ ÿçóÏ&„‚ÿÿ ÿéòÏ&$8´ÿÿ ÿéòÉ& ¡4ÿÿ ÿéòÎ& ¡>èÿÿ ÿéòÍ&´Šÿÿ ÿçòÈ&´zÿÿ ÿéòÏ&´)ªÿÿ ÿéöÑ&€ÿÿ ÿéòÎ&´‚ÿÿ ÿéòÉ&ƒ´ÿÿÿéòÐ&i´ÿÿÿéòÑ&´0âÿÿÿéòÏ& Ï]ÿÿ ÿéòÑ&´ Îÿÿ ÿéòÇ& Í´ÿÿÿèñÏ&0X>éÿÿ ÿèòÑ& Ë Ìÿÿ ÿéòÍ&x&ý&î´ÿÿ ÿéöË& Êÿÿ ÿéòÏ&´~ÿÿ ÿéóÇ&y“ÿÿÿéóÇ&:;ÿÿ ÿéöÇ&|ÿÿ ÿéóÇ&;ÿÿ ÿéóÏ&}–ÿÿ ÿèóÉ&;)˜ÿÿ ÿéóÇ&À“ÿÿ ÿèöÇ&|—ÿÿ ÿéóÇ&}0&ÿÿ ÿéóÍ&{"ÿÿ ÿéóÍ& Ç{ÿÿ ÿéóÍ&{˜ÿÿ ÿéóÇ&{žÿÿ ÿéóÇ& È{ÿÿÿéöÇ&}|ÿÿÿèóÏ&{*{ÿÿÿéöÏ&a|ÿÿÿçóÐ&{ ãÿÿ ÿéóÇ&{’ÿÿ ÿéóÏ&,{ÿÿ ÿéóÏ&D{ÿÿ ÿéóÐ&{)«ÿÿ ÿéóÌ&5&<}ÿÿ ÿéóÐ&{ ÿÿÿéóÇ&{Ÿÿÿ ÿéóÐ&{=ÿÿ ÿéóÍ&}/³ÿÿ ÿéóÒ&}ÿÿ ÿéóÏ&{zÿÿ ÿéóÊ&}|ÿÿ ÿçöÊ&u~ÿÿ ÿéóÇ&z}ÿÿ ÿèóÏ&}ÿÿ ÿèóÏ&…ÿÿ ÿéóÏ&„ÿÿ ÿçóÇ&ƒÿÿÿéóÏ&€ÿÿ ÿçóÏ&}‚ÿÿÿéóË&ÿÿ ÿéóÉ&ƒÿÿ ÿéóÑ&€ÿÿ ÿèóÈ&}ÿÿÿèñÑ&{0Xÿÿ ÿéóÏ&~ÿÿ ÿéóË&á#áÿÿ ÿçúË&hgÿÿ ÿéóÉ&á>êÿÿ ÿéóÎ&á>ëÿÿ ÿéöÐ&~}ÿÿ ÿéøÏ&|#éÿÿ ÿéõÐ&âáÿÿ ÿéóÌ&°>ìÿÿ ÿéóÉ&{áÿÿ ÿéôÉ&áÿÿ ÿéöÈ&"1°ÿÿ ÿéóÉ&°>íÿÿ ÿéøÏ&#ã#âÿÿ ÿéóÏ&á>îÿÿ ÿéøÏ&"Þãÿÿ ÿéóÈ&"ˆ°ÿÿÿéøÉ&éãÿÿ ÿéõÏ&}>ïÿÿ ÿéøÉ&#äãÿÿ ÿéóÉ&°>ðÿÿ ÿéôÈ&°>ñÿÿ ÿéøÉ&äãÿÿ ÿçøÐ&ã>òÿÿ ÿéõÎ&°>óÿÿ ÿéõÐ&°>ôÿÿ ÿéôÈ&°>õÿÿÿéøÏ&#â#åÿÿ ÿêôÎ&#æÿÿ ÿçöÏ&#è#éÿÿ ÿêñÈ&ÿÿ ÿêñÏ&>öÿÿ ÿç÷É&)¬)­ÿÿ ÿç÷Ð&)­>÷ÿÿ ÿêõÍ&)®>øÿÿ ÿéøÎ& ÿÿ ÿèõÓ&Ç)°ÿÿÿéõÑ&>´ÿÿÿèóÑ&>qÿÿÿéóÑ&>"ßÿÿ ÿèõÓ&)³)±ÿÿÿèõÓ&)²)±ÿÿÿéõÑ&)´ ÅÿÿÿéñÑ& Å)µÿÿÿéòÑ&(* ÆÿÿÿéðÑ&ü&û ÅÿÿÿéõÑ&)¶ƒÿÿ ÿéóÑ&¯ÃÿÿÿéõÑ&ƒ>ÿÿÿéòÑ& Å0ôÿÿÿéôÑ& È"–ÿÿÿéïÑ& Ç/¹ÿÿ ÿçôÌ&0ÿÿÿéíÑ&ˆ ÅÿÿÿéõÑ&3 ÅÿÿÿèðÑ&4 ÅÿÿÿèöÑ& Å0kÿÿÿéóÑ& Å5ÿÿÿèõÑ& ÆQÿÿÿèóÑ&ç ÆÿÿÿéìÑ&a Çÿÿ ÿåõÏ& )·ÿÿÿèíÑ&b ÆÿÿÿèôÑ&&ç Åÿÿ ÿæõÊ&C&BAÿÿ ÿçôÐ&æÿÿÿéóÑ&"— ÆÿÿÿèóÑ& Å’ÿÿÿé÷Ñ&c ÇÿÿÿéòÑ& ÆÚÿÿÿèõÑ& ÈeÿÿÿéóÑ& ÆÿÿÿéõÑ& · ÅÿÿÿéõÑ&d ÆÿÿÿéóÑ& È0TÿÿÿéôÑ& Æ&ñÿÿÿéôÑ&/w ÈÿÿÿçöÒ&f ÆÿÿÿéöÒ&g ×ÿÿÿéòÑ& Ç,AÿÿÿéíÑ&å ÅÿÿÿèóÑ& Æíÿÿ ÿçôÏ&)¹ÿÿÿé÷Ñ&(x ÈÿÿÿéòÑ& ÈWÿÿÿçõÑ& È&©ªÿÿÿèòÑ& Ç"@ÿÿÿéòÑ& Å!2ÿÿÿèðÒ&)¸&g!ÿÿÿéòÑ& Èÿÿ ÿåõÑ&)·>úÿÿÿéöÑ& È"õÿÿÿéòÒ&g&/>ûÿÿÿèõÒ&g&@>üÿÿÿèðÑ&0 ÆÿÿÿçõÑ& È!0ÿÿÿèóÑ& È#Aÿÿ ÿéóÑ&¯#1ÿÿÿéöÑ&$f$jÿÿÿéôÑ& È#úÿÿÿéïÑ&% ÈÿÿÿéóÑ&(g ÆÿÿÿéóÑ& È(sÿÿÿéôÑ& È1ÿÿÿèõÑ& È(tÿÿÿéóÑ& Æ$ØÿÿÿéóÑ& Ç#`ÿÿÿéôÑ&$Ù ÈÿÿÿéòÑ& Æ%ÿÿÿåôÑ&(u ÆÿÿÿèôÑ& Ç(vÿÿÿéóÑ& ÆýÿÿÿéõÑ& È(wÿÿÿéðÑ& È5ÿÿÿçõÑ&$j!UÿÿÿèóÑ& Ç#6ÿÿÿéñÑ& Ç)ºÿÿ ÿçõÒ&)»&º>ýÿÿÿçõÏ&Ì>þÿÿÿéôÒ&g(rÿÿÿçõÒ&!ÔÌÿÿÿéòÒ& È!ÕÿÿÿèöÑ& È)òÿÿÿéñÑ&$j(ÿÿÿèòÑ& Ç(qÿÿÿéòÑ&$j-ÿÿÿçõÑ&$j(pÿÿ ÿêõÐ&«ªÿÿ ÿéíÐ&«%9ÿÿ ÿëõÐ&¬«ÿÿ ÿç÷Ð&­)¼ÿÿ ÿëñÐ&«®ÿÿ ÿéõÐ&«`ÿÿ ÿçóÐ&¯)¼ÿÿ ÿêòÐ&«¾ÿÿ ÿéõÑ&«¶ÿÿ ÿèõÐ&»)¼ÿÿ ÿèîÑ&¿)¼ÿÿ ÿëõÐ&«3ÿÿ ÿéêÐ&(o)¼ÿÿ ÿéòÐ&«0öÿÿ ÿéóÐ&Á)¼ÿÿ ÿèõÐ&¼)¼ÿÿ ÿèíÒ&½¾ÿÿ ÿéòÐ&«¤ÿÿ ÿéóÐ&æ)¼ÿÿ ÿêôÐ&)¼,Ëÿÿ ÿé÷Ð&c)¼ÿÿ ÿéöÐ&½(lÿÿ ÿèñÐ&“)¼ÿÿ ÿëôÐ&(m)¼ÿÿ ÿéìÐ&«(nÿÿ ÿéñÐ&½Xÿÿ ÿëôÐ&"j)¼ÿÿ ÿæòÐ&!ÿ)¼ÿÿ ÿèòÐ&½0¤ÿÿ ÿéóÐ&½pÿÿ ÿèõÐ&£)¼ÿÿ ÿéñÐ&½%ÿÿ ÿëôÑ&½$Úÿÿ ÿëóÐ&#_)¼ÿÿ ÿéôÐ&½%Þÿÿ ÿèõÐ&&(k?ÿÿ ÿèõÐ&½(jÿÿ ÿêôÐ&½)½ÿÿ ÿè÷Ð&(i?ÿÿ ÿèöÏ&Chÿÿ ÿçõÉ&Ã"•ÿÿ ÿéôË&Ã"–ÿÿ ÿéóÏ&"—Ãÿÿ ÿéôÐ&ÃÂÿÿ ÿéöÏ&Ã"˜ÿÿ ÿèóÐ&Ã!‘ÿÿ ÿéôÐ&Ã"™ÿÿ ÿéôÉ&Ã1ÿÿ ÿé÷Ð&™šÿÿ ÿèöÒ&"š&"›"œÿÿÿèõÇ&æœÿÿ ÿéìÈ&Ç"ÕÿÿÿéóÇ&!,æÿÿÿéðÐ&æ)äÿÿÿèöÏ&æHÿÿÿéóÌ&"߈ÿÿÿéõÎ&"àæÿÿÿèíÐ&ˆ"áÿÿÿèðÊ&"ã"âÿÿÿéëÇ&ˆ&ÙÿÿÿéóÇ&ˆ&ÚÿÿÿéõÏ&ÎæÿÿÿèôÍ&ˆ&ÜÿÿÿéíÊ&&Û"âÿÿÿçøÏ&ùˆÿÿ ÿéòÈ&q&ÝÿÿÿéõÏ&ˆBÿÿÿéôÏ&—éÿÿÿèøÈ&úˆÿÿÿéòÇ&&ÞæÿÿÿèõÏ&ˆ-ÿÿÿéóÌ&æ äÿÿÿéîÇ&ˆ&ßÿÿÿèðÇ&!̈ÿÿ ÿéôÔ&,]ÎÿÿÿéôÇ&æ&àÿÿÿéëÏ&æ·ÿÿÿéóÇ&ˆ ûÿÿÿéôÏ&æœÿÿÿéóÑ&&â&áÿÿÿéìÎ&éÿÿÿéõÇ&æ,CÿÿÿéîÇ&æ!HÿÿÿéíÐ&&ã'qÿÿÿèöÏ&dæÿÿÿèõÐ&æ&åÿÿÿéòÐ&æ0ôÿÿÿéóÐ&é&,D?ÿÿÿéôÏ&æ&äÿÿÿéôË&é"–ÿÿÿéðÏ&éiÿÿÿéîÇ&æ0óÿÿÿèóÐ&æçÿÿÿé÷Ì&SæÿÿÿéóÇ&æTÿÿÿéóÏ&ûéÿÿÿéìÈ&é&æÿÿÿéöÑ&ébÿÿÿèòÏ&èéÿÿÿèôÇ&&çæÿÿÿæôÎ&æ#.ÿÿÿéòÏ&¤éÿÿÿéíÉ&æwÿÿÿéõÐ&é‹ÿÿ ÿèëÏ&S*ÿÿÿéóÏ&é0UÿÿÿèòÇ&é&èÿÿÿéõÏ&æ ·ÿÿÿéôÇ&é&éÿÿÿçóÏ&é&êÿÿÿéóÐ&&ëæÿÿÿéñÈ&æ&ìÿÿÿéõÏ&déÿÿÿéëÉ&æ!WÿÿÿéóÐ&édÿÿÿèõÑ&éeÿÿÿéóÏ&é(ÿÿÿéóÏ&æ&íÿÿÿéôÏ&é+_ÿÿÿé÷Ç&é"nÿÿÿéõÐ&é‹ÿÿÿéìÇ&éŒÿÿÿéôÐ&æ&îÿÿÿéòÏ&æ,BÿÿÿéôÇ&æ&ñÿÿÿèîÏ&æ&ïÿÿÿéóÏ&é&ðÿÿÿéòÇ&é1ÿÿÿéóÐ&é)ÈÿÿÿèòÏ&æ+tÿÿÿéôÐ&ÂéÿÿÿéóÇ&é?ÿÿÿéóÏ&é *ÿÿÿéñÈ&éÿÿÿéòÇ&é(hÿÿ ÿéòÐ&Î&01ÿÿÿéðÐ&é"ÀÿÿÿéôÏ&é#ªÿÿÿéøÉ&éÿÿÿéôÑ&æ!Ïÿÿ ÿéôÏ&*0vÿÿÿéïÏ&é?ÿÿÿéóÉ&é"ÿÿ ÿéõÇ&* ÿÿÿéôË&é#ÏÿÿÿéõÍ&é"‚ÿÿÿéöÈ&é&ÿÿÿéîÇ&é"ƒÿÿÿéóÐ&kæÿÿÿéôÑ&æ#úÿÿÿéóÏ&é(gÿÿ ÿçõÑ&?ÿÿÿéòÏ&é$ÔÿÿÿéòÏ&(a?ÿÿÿéïÐ&é%ÿÿ ÿèóÏ&?ÿÿÿéóÐ&é(eÿÿÿéóÏ&æWÿÿÿéòÇ&$úéÿÿÿéôÇ&éÏÿÿÿéïÏ&é(fÿÿÿéôÇ&é!ÿÿ ÿéïÏ&)Ê*ÿÿÿéòÏ&%éÿÿ ÿéöÐ&0(aÿÿÿéîÇ&é(cÿÿÿéôÇ&èæÿÿÿéóÐ&é(bÿÿ ÿçöÐ&0V?ÿÿÿéñÐ&(a0WÿÿÿèõÐ&é!ÝÿÿÿéóÉ&é$-ÿÿÿé÷Ñ&é(dÿÿÿéóÇ&æçÿÿÿéòÈ&é(_ÿÿÿéíÇ&éžÿÿÿéôÏ&é(`ÿÿÿéôÏ&é#ÿÿÿèõÏ&)¾éÿÿÿèóÏ&é#6ÿÿÿéôÎ&éÿÿÿéõÇ&é? ÿÿ ÿèõÐ&& ? ÿÿÿéòÈ&æ'ÿÿÿéïÑ&é'#ÿÿÿéóÉ&é*£ÿÿÿéóÏ&é(Õÿÿ ÿêõÏ&*? ÿÿÿé÷Ñ&é)¿ÿÿÿéòÒ&é!Õÿÿ ÿéôÐ&ÍÎÿÿÿèòÉ&é)ÀÿÿÿçòÐ&)Âéÿÿ ÿéñÈ&)*ÿÿ ÿèòÐ&)Á*ÿÿ ÿéóÑ&*6ÿÿÿéôÑ&êéÿÿÿèòÏ&,éÿÿ ÿéðÇ&*? ÿÿ ÿéñÏ& &  ÿÿÿéðÏ&é? ÿÿÿéóÏ&é(Îÿÿ ÿêòÉ&**kÿÿ ÿéñÌ&)Ã?ÿÿ ÿèõÈ&œ?ÿÿ ÿèöÏ&?HÿÿÿèíÐ&Å@ÿÿÿêöÏ&@ÿÿÿêòÇ&@Iÿÿ ÿçøÏ&?ùÿÿÿéëÐ&)Ä@ÿÿ ÿêîÈ&?!HÿÿÿêôÏ&@&äÿÿÿêëÇ&)ÅAÿÿÿéóÏ&A&/ÕÿÿÿêìÎ&@ÿÿÿéêÐ&@,Eÿÿ ÿêôÏ&?œÿÿÿêðÏ&@iÿÿÿéóÐ&0ƒBÿÿÿééÐ&ÎCÿÿÿéòÇ&@*×ÿÿÿéòÐ&@0ôÿÿÿæôÏ&EÿÿÿééÉ&,³<ÿÿÿèöÑ&E)ÆÿÿÿéôÐ&ATÿÿÿéõÐ&@‹ÿÿÿéôÐ&@™ÿÿÿéóÎ&AåÿÿÿéòÇ&ATÿÿÿêóÏ&E'ÿÿÿêòÌ&A)ÇÿÿÿèõÑ&e@ÿÿÿèòÎ&@)ÉÿÿÿêóÐ&@)ÈÿÿÿéõÇ&A1ÿÿÿéõÐ&A‹ÿÿÿè÷Ð&E /ÿÿÿçíÐ&E:ÿÿÿêñÏ&#d?ÿÿÿêôÇ&@!ÿÿ ÿêôÐ&#c&#b#dÿÿÿêôÐ&#b&#^#dÿÿÿéïÐ&@%ÿÿ ÿêòÑ&?ÿÿÿéïÏ&E)ÊÿÿÿéóÉ&$-AÿÿÿéôÇ&A)ËÿÿÿêòÑ&E)ÌÿÿÿéóÇ&E?ÿÿÿéñÈ&)EÿÿÿèòÏ&E+ÿÿÿéòÑ&E-ÿÿ ÿéøÏ&ÿ3ÿÿ ÿéñÉ&#,4ÿÿ ÿéöÏ&a3ÿÿ ÿèõÉ&T3ÿÿ ÿçòÏ&ª3ÿÿ ÿéõÏ&†4ÿÿÿéóÍ&65ÿÿ ÿéóÎ&73ÿÿ ÿéïÏ&#-4ÿÿ ÿæôÎ&4#.ÿÿ ÿèöÐ&4#/ÿÿ ÿèóÌ&3ÿÿ ÿéôÐ&Ô3ÿÿ ÿéôÐ&…3ÿÿ ÿèõÉ&R3ÿÿ ÿèóÐ&ç4ÿÿ ÿéóÎ�3ÿÿ ÿéõÉ&31ÿÿ ÿéøÐ&98ÿÿÿèöÏ&&5?ÿÿ ÿéòÏ&3#ºÿÿ ÿéóÏ3ÿÿ ÿéñÐ&3#2ÿÿ ÿéòÏ&#3ÿÿ ÿéôÐ3ÿÿ ÿéôÐ&/O3ÿÿÿèòÐ5ÿÿ ÿçóÏ&3#5ÿÿ ÿèóÏ&8#6ÿÿ ÿéðÉ8ÿÿ ÿéóÏ&8?ÿÿÿçôÎ&5?ÿÿ ÿéòÐ&8#8ÿÿ ÿçñÑ 8ÿÿ ÿè÷Ð&#:8ÿÿÿèòÑ&%&¸¹ÿÿÿéôÑ&%)Íÿÿ ÿéöÎ& ?ÿÿ ÿìõÏ&?ÿÿ ÿéòÏ&$¢ÿÿ ÿêóÏ&ë?ÿÿ ÿêóÏ&[ÿÿ ÿçòÏ&?ÿÿ ÿéôÏ&@ÿÿ ÿéòÏ&?ÿÿ ÿîóÏ&?ÿÿ ÿèöÎ& 8ÿÿ ÿèóÏ&ë?ÿÿ ÿéóÏ&?ÿÿ ÿêõÏ&?ÿÿÿéòÐ&?ÿÿ ÿéòÐ&,´ÿÿÿçòÐ&Âÿÿ ÿéôÏ&? ÿÿ ÿéòÏ&¯ÿÿ ÿçóÏ&L&ë?!ÿÿ ÿéòÏ&ÿÿ ÿéóÏ&nÿÿÿèóÐ&?"ÿÿ ÿîóÏ&?#ÿÿÿéòÐ&RÿÿÿèòÐ&?$ÿÿÿèòÐ&?%ÿÿÿçòÐ&ÿÿÿêòÐ&?&ÿÿ ÿèöÏ&ìëÿÿ ÿèõÐ&(^ÿÿ ÿèóÐ&"`ÿÿÿéòÐ&#«ÿÿ ÿçòÐ&"ÿÿ ÿçòÏ&?'ÿÿ ÿêòÐ&?(ÿÿ ÿéôÏ&?)ÿÿÿéòÐ&$¾&a?*ÿÿÿéòÐ&?+ÿÿÿæòÐ&$¾?,ÿÿÿçòÐ&?-ÿÿÿçóÐ&$¾?.ÿÿ ÿçòÐ&$¾$¿ÿÿÿçóÐ&?/ÿÿÿèòÐ&$¼?0ÿÿÿèòÐ&$¾?1ÿÿ ÿåòÐ&?2ÿÿÿèòÐ&$À$¼ÿÿ ÿæôÐ&$¾?3ÿÿ ÿçôÐ&$¼?4ÿÿÿèòÐ&$¼?5ÿÿÿèóÐ&$¼?6ÿÿÿéõÐ&$¼?7ÿÿÿèîÏ&"#ÿÿÿèîÏ&$&ÿÿÿèîÏ&&%ÿÿÿèîÏ&&'ÿÿÿèîÏ&"?8ÿÿÿèîÏ&!?9ÿÿÿìèÏ&T?:ÿÿÿèíÐ&.Ü?<ÿÿÿéóË&™?=ÿÿÿè÷Ë&$ #ÐÿÿÿéóÑ&$$ÿÿÿéïÏ&š™ÿÿ ÿéôÏ&Ž?>ÿÿ ÿéõÏ&Š??ÿÿ ÿéõÏ&Š‹ÿÿ ÿéôÑ&Œÿÿ ÿéôÏ&ŽÿÿÿéôÑ&Œÿÿ ÿéôÏ&Ž?@ÿÿ ÿéóÐ&+?Aÿÿ ÿè÷Ç&F&Esÿÿ ÿéóÐ&+?Bÿÿ ÿéóÐ&)+ÿÿ ÿéôÐ&*+ÿÿ ÿéóÐ&+?Cÿÿ ÿéóÐ&+?Dÿÿ ÿéóÐ&+?Eÿÿ ÿéóÐ&+?Fÿÿ ÿèôÑ&.Ý#üÿÿ ÿéóÐ&+?Gÿÿ ÿéóÐ&+?Hÿÿ ÿè÷Ñ&$?Iÿÿ ÿéõÐ&+?Jÿÿ ÿéóÐ&+?Kÿÿ ÿèöÊ&A?Lÿÿ ÿèôÐ&#ý?Mÿÿ ÿéìÐ&íÇÿÿ ÿéòÐ&íÿÿ ÿéóÐ&Oýÿÿ ÿéðÐ&)ä)åÿÿ ÿéõÐ&!E&!DOÿÿ ÿèõÐ&¹íÿÿ ÿéöÐ&Qaÿÿ ÿéõÐ&¬Oÿÿ ÿéóÐ&QÃÿÿ ÿéóÑ&)Î@ÿÿ ÿèôÐ&í&Üÿÿ ÿéôÐ&í)Ïÿÿ ÿéíÐ&í)Ðÿÿ ÿéóÐ&…Qÿÿ ÿéôÐ&¿Qÿÿ ÿéïÐ&Ožÿÿ ÿçöÐ&*Ííÿÿ ÿéóÐ&O.ÿÿ ÿèéÐ&O/ÿÿ ÿéòÐ&O)Ñÿÿ ÿèòÐ&¹Oÿÿ ÿéòÐ&Q,Fÿÿ ÿéðÐ&Ojÿÿ ÿéîÐ&O/qÿÿ ÿéñÐ&Oþÿÿ ÿéôÐ&†"–ÿÿÿçôÌ&)Ò0ÿÿ ÿéïÐ&)Óíÿÿ ÿéìÐ&Oÿÿ ÿéòÐ&)‰)åÿÿ ÿéóÐ&í%Kÿÿ ÿéòÐ&O0ôÿÿ ÿéóÐ&ûOÿÿ ÿéóÐ&O&!E/âÿÿ ÿæïÐ&í¸ÿÿ ÿéïÐ&)å#-ÿÿ ÿéîÐ&O!:ÿÿ ÿéõÐ&)å+¦ÿÿ ÿéóÐ&fíÿÿ ÿéôÐ&Oîÿÿ ÿéïÐ&OEÿÿ ÿèòÐ&O0Àÿÿ ÿéõÑ&Q‘ÿÿ ÿé÷Ð&SOÿÿ ÿèôÐ&)ÔQÿÿÿçòÍ&±&²ÿÿ ÿéôÐ&…Oÿÿ ÿéìÐ&†&æÿÿ ÿéðÐ&Oÿÿ ÿéòÐ&€Oÿÿ ÿèõÐ&Qíÿÿ ÿéóÐ&í(Ìÿÿ ÿéòÐ&íNÿÿ ÿé÷Ñ&Oçÿÿ ÿéòÐ&Y†ÿÿ ÿèóÐ&†&.è/Õÿÿ ÿéóÐ&Qdÿÿ ÿéóÐ&Qƒÿÿ ÿéöÐ&O)–ÿÿ ÿèìÑ&!XOÿÿ ÿèõÑ&Oeÿÿ ÿéõÐ&dOÿÿ ÿéôÐ&†Òÿÿ ÿéóÐ&O(Íÿÿ ÿèõÐ&ROÿÿ ÿéòÐ&O!5ÿÿ ÿé÷Ð&O-³ÿÿ ÿéòÐ&OÚÿÿ ÿè÷Ð&Qÿÿ ÿçôÏ&¸?Nÿÿ ÿéóÐ&WQÿÿÿçñÑ&.Þ)Òÿÿ ÿèïÐ&†& ?Oÿÿ ÿéòÐ&O)æÿÿ ÿéìÐ&îOÿÿ ÿéøÐ&O)çÿÿ ÿéòÐ&Q)èÿÿ ÿèòÐ&O+tÿÿ ÿéóÐ&O)éÿÿ ÿéóÑ&O#ÿÿ ÿéôÐ&O ÿÿ ÿéìÐ&OŒÿÿ ÿéòÐ&O,Gÿÿ ÿéõÐ&OHÿÿ ÿéóÐ&&ðQÿÿ ÿéôÐ&†ÿÿ ÿçöÐ&Qÿÿ ÿéìÐ&†!ÿÿ ÿéôÐ&O&ñÿÿ ÿéóÐ&Q)êÿÿ ÿéóÐ&O?Qÿÿ ÿéïÐ&¶Oÿÿ ÿéòÐ&Qaÿÿ ÿèõÐ&VOÿÿ ÿê÷Ï&.4ÿÿ ÿçòÏ&,Hÿÿ ÿéöÑ&Q ÿÿ ÿèõÐ&†·ÿÿ ÿè÷Ð&†?Rÿÿ ÿé÷Ò&OPÿÿ ÿéóÐ&Q"ÿÿ ÿèöÐ&†&/Ô/Õÿÿ ÿéîÐ&†/ºÿÿ ÿèõÐ&O"«ÿÿ ÿè÷Ñ&†.ßÿÿ ÿéðÐ&O }ÿÿ ÿéöÐ&†"\ÿÿ ÿéöÐ&Q#Gÿÿ ÿéöÐ&·Qÿÿ ÿéòÐ&Oÿÿ ÿéöÐ&QÛÿÿ ÿéñÐ&†#Åÿÿ ÿéóÐ&†&Žÿÿ ÿè÷Ï&%ý&"2ÿÿ ÿéôÏ&0uÿÿ ÿéòÐ&O!ÿÿ ÿèóÐ&† Ïÿÿ ÿéòÐ&Q".ÿÿ ÿé÷Ð&†!ÿÿ ÿéõÏ& ÿÿ ÿèíÏ&&Æ!ÿÿ ÿéôÐ&Q1ÿÿ ÿéôÐ&Q"™ÿÿ ÿéñÐ&O#2ÿÿ ÿéóÐ&Q(àÿÿ ÿéõÐ&-´&ù?Sÿÿ ÿéóÎ&ù&?Tÿÿ ÿéóÐ&+óOÿÿ ÿéîÐ&†$Wÿÿ ÿéóÐ&O(ßÿÿ ÿéóÐ&O$ÿÿ ÿéòÐ&$ò†ÿÿ ÿéòÐ&O$Bÿÿ ÿéòÐ&Q$Hÿÿ ÿèðÐ&†(Üÿÿ ÿéðÏ&ù&-µ?Uÿÿ ÿêòÏ&?VÿÿÿçòÐ&?Wÿÿ ÿéñÏ&úùÿÿ ÿçõÐ&õ&?Xÿÿ ÿéöÐ&†(Ýÿÿ ÿé÷Ð&$×&ù?Yÿÿ ÿéôÐ&†(Þÿÿ ÿèõÐ&!ÝOÿÿ ÿéóÐ&O$Øÿÿ ÿéîÐ&(×Qÿÿ ÿèóÐ&†3ÿÿ ÿéóÐ&$-†ÿÿ ÿèóÐ&O'ÿÿÿçóË&(Ø?Zÿÿ ÿêûÏ&H&?[ÿÿ ÿéóÐ&O(Ùÿÿ ÿéòÐ&(ÚQÿÿ ÿçóÑ&†(Ûÿÿ ÿéïÐ&¯OÿÿÿçôÑ&1 ÿÿ ÿéíÐ&ž†ÿÿ ÿéôÐ&(`Oÿÿ ÿéóÐ&†(Ôÿÿ ÿèõÏ&þÿÿ ÿéóÐ&†(Õÿÿ ÿèõÐ&)¾†ÿÿ ÿéóÒ&†ÿÿ ÿéóÐ&ýOÿÿ ÿéòÐ&†(Öÿÿ ÿèóÎ&ùüÿÿ ÿéôÐ&†ÿÿ ÿéòÐ&O(Óÿÿ ÿèóÐ&†#6ÿÿ ÿéõÐ&†(wÿÿ ÿéöÐ&†(ÒÿÿÿçòÑ&¿ÿÿ ÿéõÒ&Oïÿÿ ÿèòÐ&†(Ñÿÿ ÿéòÒ&!ÕQÿÿ ÿéøÐ&†(Ðÿÿ ÿéòÐ&O#8ÿÿ ÿéòÐ&O)ëÿÿ ÿé÷Ñ&†%{ÿÿ ÿçóÐ&)ì†ÿÿ ÿéòÏ&-¶ÿÿ ÿéõÐ&†(Ïÿÿ ÿéõÐ&ˆÿÿ ÿéóÑ&!&†?\ÿÿ ÿèóÐ&†?]ÿÿ ÿéóÐ&,K†ÿÿ ÿéóÑ&†6ÿÿ ÿéóÐ&†(Îÿÿ ÿéòÐ&†*kÿÿ ÿéõÏ&O:ÿÿ ÿéóÑ&@Bÿÿ ÿéóÐ&C…ÿÿ ÿçòÐ&C"ÿÿÿ ÿéñÏ&Cþÿÿ ÿéóÏ&COÿÿ ÿéòÏ&C(Ëÿÿ ÿéóÐ&D&F/âÿÿ ÿíóÐ&CûÿÿÿðôÑ&.Ï?^ÿÿ ÿéòÐ&C0ôÿÿ ÿïóÏ&C(Ìÿÿ ÿéòÏ&C0×ÿÿ ÿéòÏ&D€ÿÿ ÿêïÏ&DEÿÿ ÿèõÑ&DKÿÿ ÿèõÐ&CQÿÿ ÿéòÏ&NOÿÿ ÿíõÐ&LMÿÿ ÿêòÏ&CSÿÿ ÿèõÏ&CRÿÿ ÿéòÏ&JTÿÿ ÿêûÐ&I&HJÿÿ ÿðõÏ&DUÿÿ ÿñóÏ&CWÿÿ ÿéóÐ&J&/â?_ÿÿ ÿêóÏ&þJÿÿ ÿêóÏ&D(ÍÿÿÿðôÏ&ûúÿÿ ÿè÷Ð&Cÿÿ ÿéîÏ&û/·ÿÿ ÿéóÏ&D)éÿÿ ÿèõÏ&DVÿÿ ÿê÷Ï&ÿ.4ÿÿ ÿéôÐ&DÂÿÿ ÿëôÏ&D&ñÿÿ ÿéíÏ&D³ÿÿ ÿéìÏ&J1ÿÿ ÿçöÏ&Cÿÿ ÿìöÑ&D ÿÿ ÿêìÏ&DŒÿÿ ÿéòÐ&Caÿÿ ÿéôÏ&J!ÿÿ ÿéòÏ&C)èÿÿ ÿéòÏ&D!òÿÿ ÿèõÏ&D¯ÿÿ ÿìòÏ&D".ÿÿ ÿêòÏ&D!ÿÿ ÿè÷Ï&%ý&û"2ÿÿ ÿèóÐ&D Ïÿÿ ÿíòÏ&Dÿÿ ÿçíÐ&ÿ:ÿÿ ÿêôÏ&D!ÿÿ ÿíòÐ&œ0Wÿÿ ÿèôÏ&J&bcÿÿ ÿêîÏ&J$Xÿÿ ÿéóÏ&+óCÿÿ ÿéóÏ&D(ßÿÿ ÿéñÏ&ûúÿÿ ÿéóÏ&D$-ÿÿ ÿéïÏ&D¯ÿÿ ÿíôÑ&œ1 ÿÿ ÿéóÏ&D$Øÿÿ ÿíóÑ&œ,Nÿÿ ÿèòÑ&J!’ÿÿ ÿèóÏ&ûüÿÿ ÿéóÐ&Dýÿÿ ÿéôÏ&Jÿÿ ÿéóÒ&Dÿÿ ÿïòÐ&D#8ÿÿÿëòÑ&k(ÿÿ ÿçôÒ&ðñÿÿ ÿéòÐ&ÿÿÿÿéîÐ&ÿÿ ÿèòÊ&A?`ÿÿ ÿéòÒ&Ê-·ÿÿ ÿéòÏ&Äÿÿ ÿéòÏ&,ÿÿ ÿéóÐ&ÿÿ ÿéòÏ&)íÿÿ ÿé÷Æ&T?aÿÿÿéîÏ&*%ÿÿ ÿèòÐ&+öóÿÿÿèôÒ&hgÿÿ ÿèòÏ& Æÿÿ ÿèòÒ&)†ÿÿ ÿéòÏ&)îÿÿ ÿéòÏ&˜ÿÿ ÿéòÏ&+ôÿÿ ÿçòÏ&)Œÿÿ ÿéòÏ&nÿÿ ÿéòÏ&uÿÿ ÿéòÏ&+n+õÿÿÿéòÏ&+itÿÿ ÿéòÏ&+÷ÿÿ ÿéòÏ&r+iÿÿ ÿéòÏ&qÿÿ ÿèóÊ&A&RSÿÿ ÿéòÏ&‡ÿÿÿéòÏ&+i0©ÿÿ ÿçñÐ&'q+ýÿÿÿéòÏ&cÿÿ ÿéòÏ&+i+øÿÿÿëòÑ&klÿÿ ÿéòÏ&kÿÿÿèòÐ&+ùÿÿ ÿéòÏ&‹ÿÿ ÿçñÑ&,M+ýÿÿÿéòÏ&tÿÿ ÿéòÏ&+úÿÿÿéòÏ&nÿÿ ÿéòÏ&ïÿÿ ÿéôÏ&þ?bÿÿ ÿéòÏ&+ûÿÿ ÿèòÏ&+ÿ+nÿÿ ÿçôÐ&+ü+ýÿÿÿéòÏ&+jÿÿ ÿéòÏ&+kÿÿÿéòÏ&+i,ÿÿÿéóÐ&{ÿÿÿèòÑ&+þgÿÿÿéòÐ&§ÿÿ ÿéòÏ&ü+iÿÿÿèòÐ&óËÿÿ ÿéòÏ&nÿÿ ÿéòÏ&-»ÿÿ ÿèòÏ&+i0Áÿÿ ÿéòÏ&,ÿÿ ÿèïÏ& ?cÿÿÿéòÏ&ÿÿ ÿèòÐ&ó&.ô?dÿÿ ÿèñÐ&P?eÿÿ ÿèòÐ&Úÿÿ ÿéòÐ&‘ÿÿ ÿèñÏ&P?fÿÿ ÿéòÏ&¬ÿÿÿèòÐ&,&ó?gÿÿ ÿéòÑ&ÿÿ ÿéòÏ&2+iÿÿÿéòÐ&†ÿÿ ÿéôÏ&ö&,/ÿÿ ÿéòÏ&0žÿÿ ÿéòÐ& âÿÿ ÿèòÐ&ó?hÿÿ ÿê÷Ð&+p+nÿÿÿèòÐ&ó/ÿÿÿéòÏ&,OÿÿÿéòÏ&+i+lÿÿ ÿéòÏ&9ÿÿÿéòÏ&+mÿÿ ÿéòÐ&+o+nÿÿÿéòÏ&Uÿÿ ÿèòÑ&)Uóÿÿ ÿéòÏ&+i-ÿÿ ÿéòÏ&Æÿÿ ÿéòÏ&+i/´ÿÿ ÿéòÏ&+i§ÿÿ ÿèòÐ&ó+qÿÿ ÿèòÐ&òóÿÿÿæòÐ&,(zÿÿ ÿèóÑ&OPÿÿ ÿéòÏ&&@+iÿÿ ÿèòÐ&ó/|ÿÿ ÿèòÐ&+i’ÿÿ ÿéòÐ&+eÿÿ ÿèòÐ&+ÿÿ ÿéôÏ&þ?iÿÿ ÿèòÐ&+fóÿÿ ÿéòÏ& ^+iÿÿÿèôÏ&+gþÿÿ ÿéòÐ&+hÿÿ ÿèòÐ&ó9ÿÿÿéòÏ&'²+iÿÿÿéòÏ&+r+iÿÿÿèòÐ&,óÿÿ ÿèòÐ&ó ÿÿ ÿèòÐ&ó,Pÿÿ ÿèòÐ&,óÿÿ ÿèòÐ&ó*ÿÿ ÿèïÏ& ,Iÿÿ ÿèôÏ&+sþÿÿ ÿéóÐ&)+nÿÿ ÿèòÐ& Üóÿÿ ÿèòÏ&¸&¹+nÿÿ ÿèòÏ&+n+tÿÿÿéòÏ&b+iÿÿ ÿèòÐ&, óÿÿ ÿéòÐ&„+iÿÿ ÿèòÐ&, óÿÿ ÿéòÏ&, ÿÿ ÿéòÏ&ÿÿÿèòÐ&óâÿÿÿéòÑ&+i+ÿÿ ÿèòÐ&sóÿÿ ÿèòÐ&ó½ÿÿÿçòÐ&ó, ÿÿÿéòÏ&+i?jÿÿÿéôÏ&þ?kÿÿ ÿéóÏ& »& ¼ ½ÿÿ ÿèòÐ&#Lóÿÿ ÿèïÏ& ?lÿÿ ÿçöÏ&t?mÿÿÿéóÏ& ½?nÿÿÿèòÐ&!óÿÿ ÿèòÐ&ózÿÿÿèòÐ&ó#Ôÿÿ ÿèòÐ&ó?oÿÿÿèòÐ&ó"ÿÿ ÿèòÐ&-¸óÿÿ ÿèòÐ&ó?pÿÿÿèòÐ&ó#ÄÿÿÿèòÐ&$·óÿÿ ÿèòÐ&"ªóÿÿÿèòÐ&ó"Áÿÿ ÿèòÐ&ó! ÿÿ ÿèïÏ& ?qÿÿ ÿèòÐ&ó!ãÿÿ ÿèòÐ&"êóÿÿ ÿèòÏ&$µ&$¶Pÿÿ ÿéòÏ&"?+iÿÿÿéòÏ&"aÿÿ ÿèòÐ&"bóÿÿ ÿèòÑ&"'?rÿÿ ÿèñÐ&$£&$¤Pÿÿ ÿèòÐ&$´óÿÿ ÿèòÏ&.à&$æ?tÿÿ ÿèòÐ&$°óÿÿ ÿèòÑ&ó%¯ÿÿ ÿéñÑ&$"ÿÿÿèòÑ&ó$±ÿÿ ÿèôÏ&þ$²ÿÿÿèòÐ&ó$³ÿÿ ÿèòÐ&/Góÿÿ ÿéòÏ&3+iÿÿÿèòÑ&ó+uÿÿ ÿéóÏ&¥?uÿÿ ÿèïÑ& ?vÿÿ ÿèòÐ&ó$MÿÿÿèòÐ&ó+”ÿÿÿèòÐ&$ûóÿÿ ÿèòÑ&ó,Qÿÿ ÿèòÐ&$óÿÿÿèòÐ&,Róÿÿ ÿèöÏ&˜?wÿÿ ÿèõÏ&$æ$åÿÿÿèòÐ&$ óÿÿ ÿèòÐ&ó?xÿÿ ÿéòÏ&+v+nÿÿ ÿéòÐ&+i.áÿÿ ÿèòÐ&ó?yÿÿ ÿéòÏ&+i+wÿÿ ÿèòÐ&ó?zÿÿÿèòÐ&ó?{ÿÿ ÿèòÐ&ó)Zÿÿ ÿèòÐ&,Sóÿÿ ÿèñÓ&P?|ÿÿ ÿèñÍ& ,`ÿÿÿèòÐ&ópÿÿ ÿèòÐ& ?}ÿÿ ÿèöÐ& 0ÿÿ ÿçòÐ&ó+xÿÿÿèñÓ&+}ÿÿ ÿèòÐ&ó+|ÿÿ ÿèòÐ&ó+{ÿÿÿèòÐ&óoÿÿ ÿæñÑ&0\(zÿÿ ÿèòÐ&,Tóÿÿ ÿèóÊ&A&¬?~ÿÿ ÿèòÐ&ó+yÿÿ ÿéòÏ&+i%-ÿÿ ÿèñÓ& ?ÿÿÿéòÏ&+i+~ÿÿ ÿèòÐ&ó+zÿÿ ÿèôÔ&Ÿ ÿÿÿéñÑ&+ÿÿ ÿçòÐ&ó+€ÿÿ ÿèòÐ&ó*ÔÿÿÿéñÑ&+ÿÿ ÿéñÑ&+‚ÿÿ ÿèõÏ& ?€ÿÿ ÿèñÓ&B&A ÿÿÿéôÏ&þ&ý?ÿÿÿéôÏ&þ&ý?‚ÿÿ ÿèñÑ&ÿÿÿ ÿèòÐ&,Uóÿÿ ÿèòÐ&ó+ƒÿÿ ÿéñÑ&!Ùÿÿ ÿèòÓ&«¬ÿÿ ÿèõÒ&‡ ÿÿ ÿéóÐ&+†&÷?ƒÿÿ ÿèïÑ& ,VÿÿÿèòÐ&+‡óÿÿ ÿæòÑ&(z.ÿÿ ÿèòÐ&ó+„ÿÿ ÿåöÓ&,W?„ÿÿ ÿèóÏ& ½&.ô?…ÿÿ ÿèôÏ&,X$æÿÿÿæõÒ&+…?†ÿÿ ÿèòÐ&ó+ˆÿÿ ÿéôÏ&þ?‡ÿÿ ÿèòÐ&%±óÿÿ ÿèñÍ&"'?ˆÿÿ ÿèòÐ&ó+‰ÿÿÿéôÏ&þ+Òÿÿ ÿèòÐ&ó+ŠÿÿÿéñÑ&+‹ÿÿ ÿèôÑ&"'?‰ÿÿÿæ÷Ï&+…,Yÿÿ ÿéôÏ&þ%pÿÿ ÿèöÏ&%g$æÿÿ ÿéñÑ&+ŒÿÿÿçöÐ&++¨ÿÿÿèòÐ&ó+Žÿÿ ÿéñÑ&,Zÿÿ ÿèòÐ&ó~ÿÿÿéôÏ&þ+ÿÿ ÿæñÏ&,[(zÿÿ ÿéîÐ&ÿfÿÿ ÿèîÑ&ÿÿÿçóÒ&ÿÿÿèîÑ&*%ÿÿÿêïÏ&·ÿÿ ÿéíÏ&‡sÿÿ ÿéîÏ&qfÿÿ ÿéîÏ&frÿÿÿêîÏ&+ÿÿÿèñÑ&,\*ìÿÿÿèïÏ&*{ÿÿÿéîÏ&+jÿÿÿêïÏ&nÿÿÿéîÏ&ftÿÿ ÿèôÔ&,]+“ÿÿÿéòÏ&lmÿÿÿéìÑ&+’?‹ÿÿÿéõÏ&D?ŒÿÿÿêïÏ&Ëÿÿ ÿéîÏ&fnÿÿ ÿéðÏ& & í?ÿÿ ÿêïÐ&‘ÿÿÿèïÏ&+“õÿÿ ÿèïÏ&&.ô?ŽÿÿÿêïÏ&+mÿÿÿéðÏ& !ÿÿÿéïÏ&+lÿÿ ÿéïÏ&&@ÿÿ ÿéïÏ&ò/}ÿÿ ÿéïÏ&~ÿÿÿéíÏ&ÿÿ ÿéñÑ&*ì+qÿÿ ÿéïÏ&/|/}ÿÿÿêïÏ&+rÿÿ ÿéïÐ&,"ÿÿ ÿêïÏ&+fÿÿ ÿéïÐ&+hÿÿÿèíÏ&+gÿÿÿéïÑ&+ÿÿ ÿéðÏ& ?ÿÿ ÿêïÏ&zÿÿ ÿéïÏ&4ÿÿÿèíÐ&!ÿÿ ÿéíÏ&!ÿÿ ÿéòÐ&D&01ÿÿÿêïÏ&+”ÿÿÿêïÑ&+uÿÿ ÿèïÐ&$°ÿÿ ÿéíÏ&,^ÿÿ ÿéíÐ&,#ÿÿ ÿéíÏ&Šÿÿ ÿéíÐ&‰ÿÿ ÿéíÏ&*íÿÿÿéñÓ&C&ADÿÿ ÿéñÑ&*ì!Ùÿÿ ÿéôÒ&ˆDÿÿ ÿéðÑ&$?ÿÿ ÿéõÐ&!Ø!×ÿÿ ÿéíÏ&~ÿÿÿéõÐ&tuÿÿÿé÷Ð&vtÿÿ ÿéòÏ&##ÿÿ ÿéõÑ&#+•ÿÿ ÿîóÏ& ù?‘ÿÿ ÿéòÏ&##ÿÿ ÿçïÒ&=¼ÿÿ ÿêòÑ&‡?“ÿÿ ÿçóÒ&¼½ÿÿ ÿçñÒ&‰ˆÿÿ ÿçòÒ&Š?”ÿÿ ÿçðÒ&+–?•ÿÿ ÿçóÒ&+–?–ÿÿ ÿçõÊ&èéÿÿ ÿçõÏ&†Œÿÿ ÿçôÒ&‹Šÿÿ ÿçïÒ&ëêÿÿ ÿçòÒ&ê?—ÿÿ ÿåïÒ&ê?˜ÿÿ ÿçõÒ&Š?™ÿÿ ÿèôÑ&+—+˜ÿÿ ÿçõÎ&é?šÿÿ ÿçòÒ&ê?›ÿÿ ÿçïÒ&ê?œÿÿ ÿçõÌ&Œ?ÿÿ ÿçõÒ&ê?žÿÿ ÿçôÒ&ê?Ÿÿÿ ÿçóÒ&ê? ÿÿ ÿçóÒ&$êÿÿ ÿçôÒ&ê?¡ÿÿ ÿéôÐ&%‹ÿÿÿéôÓ&$é?¢ÿÿ ÿäóÒ&%|$êÿÿ ÿæöÔ&"U&"S"Tÿÿ ÿéóÏ&ÂÃÿÿ ÿèõÐ&ô&¿¾ÿÿ ÿèõÏ&ôÀÿÿ ÿèñÏ&Áôÿÿ ÿèóÑ&õôÿÿ ÿèòÏ&ô"Vÿÿ ÿèôÏ&ÛÜÿÿ ÿèïÏ&Ü ÿÿ ÿèóÏ&ÜÝÿÿ ÿèðÑ&Ü"Qÿÿ ÿéóÐ&"O"Pÿÿ ÿèõÐ&"NÀÿÿ ÿèïÏ&  ÿÿ ÿèóÏ& Ýÿÿ ÿèõÐ&Ç&FGÿÿ ÿéõÐ&Ç?£ÿÿ ÿéõÐ&Ç?¤ÿÿ ÿéõÑ&$?¥ÿÿ ÿéõÑ&$?¦ÿÿ ÿéõÑ&$?§ÿÿ ÿæòÑ&$?¨ÿÿ ÿéóÏ&+™+šÿÿÿèëÑ&+?«ÿÿ ÿçòÑ&,V?¬ÿÿ ÿéòÐ&+œ)‰ÿÿ ÿéðÑ&"Q+›ÿÿ ÿêóÏ&)ˆöÿÿ ÿèöÑ&ö)‡ÿÿ ÿéõÏ&o%1ÿÿ ÿéöÈ&o?®ÿÿ ÿèôÐ&]öÿÿ ÿèõÎ&t?¯ÿÿ ÿèñÏ&o)ŠÿÿÿæòÒ&-¼)‹ÿÿ ÿêïÏ&*oÿÿ ÿéñÐ&*& 2?°ÿÿ ÿéóÏ&Ooÿÿ ÿèïÏ&oÿÿ ÿéóÏ&foÿÿ ÿèõÑ& 0eÿÿ ÿêùÏ& 2 3ÿÿÿéòÐ&caÿÿÿéöÏ&+ cÿÿ ÿéòÏ&,J+žÿÿÿéòÏ&,_+žÿÿ ÿèöÏ&!Ûoÿÿÿé÷Ò&cPÿÿ ÿêòÈ& 1 0ÿÿÿèôÏ&!ïcÿÿ ÿêòÎ& 0!Üÿÿ ÿéóÑ&!Ú 0ÿÿ ÿèóÏ& 0+Ÿÿÿ ÿèõÐ& 2!ÝÿÿÿéòÍ&+¡,`ÿÿ ÿèõÐ&ù&?±ÿÿÿéóÐ&*’cÿÿÿéóÏ&cÿÿ ÿéôÏ&1*”ÿÿ ÿçòÐ&1*“ÿÿ ÿéóÐ&10ÿÿ ÿéõË& ó ôÿÿÿéõÏ&+ñ& ô?µÿÿÿéøÐ&+òýÿÿ ÿêôÈ&+¢+£ÿÿ ÿéøÐ&,LýÿÿÿéøÑ&ýüÿÿÿéöË&þ&ÿ?¶ÿÿ ÿéöÐ&.ÿÿÿëõË& ö õÿÿÿéøÌ&+¤?·ÿÿÿèíÈ&('ÿÿ ÿèðÌ&#$ÿÿ ÿèëÐ&%&ÿÿÿèôÑ&]Xÿÿ ÿêõÐ&ZYÿÿÿèôÐ&-¾Uÿÿ ÿèôÑ&]?¸ÿÿÿèôÑ&]0Xÿÿ ÿèóÒ&-½[ÿÿ ÿéôÒ&[?¹ÿÿ ÿèóÒ&"‰[ÿÿ ÿìóÒ&[\ÿÿÿéôÌ&÷*”ÿÿÿèôÐ&]÷ÿÿÿéõÍ&÷+¥ÿÿÿçòÐ&÷"ÿÿÿÿéõÏ&÷+¦ÿÿÿéìÐ&÷2ÿÿÿéóÎ&÷%LÿÿÿéîÌ&÷!:ÿÿÿéíÏ&÷øÿÿÿçóÐ&!9!8ÿÿÿèöÌ&÷!7ÿÿÿéòÌ&÷!5ÿÿÿéðÍ&nðÿÿÿéóÏ&÷!6ÿÿÿéôÌ&÷!!ÿÿÿéôÍ&n!4ÿÿÿéñÍ&n#ÃÿÿÿéóÏ&lnÿÿÿçõÐ&"l$çÿÿÿçôÐ&*³nÿÿÿçôÐ&"l*´ÿÿÿéôÌ&÷$üÿÿ ÿéøÐ&,àÿÿ ÿéòÐ&43ÿÿ ÿéõÐ&,+ÿÿ ÿéìÐ&,2ÿÿ ÿé÷Ð&,1ÿÿ ÿèðÐ&,0ÿÿ ÿéõÐ&,"kÿÿ ÿéôÐ&,/ÿÿ ÿèôÐ&+.ÿÿ ÿçôÐ&-,ÿÿ ÿéôÐ&+*ÿÿ ÿéòÒ&º?ºÿÿ ÿéòÒ&º?»ÿÿ ÿëóÒ&Œ&º?¼ÿÿ ÿçôÒ&º?½ÿÿ ÿæóÒ&º?¾ÿÿ ÿèöÐ&?¿ÿÿ ÿèöÐ&Á?ÀÿÿÿéñÏ&úùÿÿÿéõÐ&NùÿÿÿéñÏ&N'•ÿÿÿéóÏ&'—NÿÿÿèõÏ&«Nÿÿ ÿéóÑ&õOÿÿ ÿéóÏ&'–Oÿÿ ÿéùÏ&POÿÿ ÿéòÐ&0õOÿÿÿéòÏ&&·NÿÿÿéîÏ&N'˜ÿÿ ÿèðÏ&!ÌOÿÿÿçõÏ&eNÿÿ ÿçõÐ&feÿÿÿéìÐ&2Nÿÿ ÿëõÒ&õôÿÿÿç÷Ð&)NÿÿÿéõÏ&Nÿÿ ÿçóÏ& !ÿÿ ÿéòÏ&'™Oÿÿ ÿéðÏ&åOÿÿ ÿéòÏ&O!5ÿÿ ÿéóÐ&O'šÿÿ ÿçöÏ& aÿÿ ÿéõÏ&*Oÿÿ ÿèôÏ&ÿÿÿ ÿéðÏ&-¿Oÿÿ ÿèõÏ&!éOÿÿ ÿéïÏ& ~Oÿÿ ÿéóÏ&O"Ôÿÿ ÿéôÏ&O#Üÿÿ ÿéôÏ&$NOÿÿ ÿéòÓ&##ÿÿ ÿéðÏ&+§ÿÿ ÿèóÏ&+Ÿÿÿ ÿêóÏ&*?Áÿÿ ÿéóÐ&*?Âÿÿ ÿéñÏ&+Ýúÿÿ ÿéîÎ&R*ÿÿ ÿèöÐ&dgÿÿ ÿéóÐ&ûRÿÿ ÿèðÎ&!ÌRÿÿ ÿéùÎ&PRÿÿ ÿèôÎ&QRÿÿ ÿéòÎ&R!5ÿÿ ÿéòÎ&R'™ÿÿ ÿéðÎ&R-¿ÿÿ ÿéòÐ&g#Ýÿÿ ÿéóÐ&rÇÿÿ ÿéöÐ&¯-Àÿÿ ÿé÷Ò&s7ÿÿ ÿç÷Ò&¸sÿÿ ÿë÷Ô&ì+©ÿÿÿêöÐ&+¨?ÃÿÿÿçóÏ&@?ÿÿÿèóÑ&íAÿÿÿéöÐ&û<ÿÿ ÿéóÑ&+ª+«ÿÿ ÿéóÏ&#Ö#Õÿÿ ÿéòÑ& Ï+«ÿÿÿëöÎ&˜UÿÿÿëõÎ&®&\ xÿÿÿëõÎ&®&[ ›ÿÿÿéõÎ&®&[ °ÿÿÿðöÎ&£&X »ÿÿÿéõÎ&®&[ ÚÿÿÿçõÎ&¹&a ïÿÿÿìõÎ&®&[ ÿÿÿíõÎ&®&[ ÿÿÿëõÎ&¹&a .ÿÿÿíõÎ&¹&a CÿÿÿñõÎ&¹&a XÿÿÿìõÎ&¹&a mÿÿÿíõÎ&¹&a ‚ÿÿÿíõÎ&¹&a —ÿÿÿëõÎ&¹&a ¬ÿÿÿïõÎ&®&[ ÁÿÿÿîõÎ&®&[ ÖÿÿÿêõÎ&¹&a ëÿÿÿèöÎ&®&^ ÿÿÿéõÎ&®&[ ÿÿÿçöÎ&®&^ *ÿÿÿçõÎ&®&[ 1ÿÿÿçõÎ&¹&[ TÿÿÿëõÎ&®&[ iÿÿÿìõÎ&¹&a ~ÿÿÿíõÎ&®&[ “ÿÿÿçõÎ&¹&[ ¨ÿÿ ÿëÐÎ&šdÿÿ ÿëÐÎ&°&j ˆÿÿ ÿëÐÎ&°&j ÿÿ ÿéìÎ&°&j ²ÿÿ ÿíÚÎ&¥&g Çÿÿ ÿéãÎ&°&j Üÿÿ ÿçßÎ&»&p ñÿÿ ÿìÕÎ&°&j ÿÿ ÿíÕÎ&»&p ÿÿ ÿëÐÎ&»&p 0ÿÿ ÿíÐÎ&»&p Eÿÿ ÿìÐÎ&»&p Zÿÿ ÿìéÎ&»&p oÿÿ ÿíÔÎ&»&p „ÿÿ ÿíÕÎ&»&p ™ÿÿ ÿëÛÎ&»&p ®ÿÿ ÿïÐÎ&°&j Ãÿÿ ÿîÐÎ&°&j Øÿÿ ÿêìÎ&»&p íÿÿ ÿèåÎ&°&m ÿÿ ÿééÎ&°&j ÿÿ ÿçÔÎ&°&m ,ÿÿ ÿçÞÎ&°&j Aÿÿ ÿçÞÎ&»&j Vÿÿ ÿëÐÎ&°&j kÿÿ ÿìÕÎ&»&p €ÿÿ ÿíØÎ&°&j •ÿÿ ÿçÜÎ&»&j ªÿÿÿëöÎ&˜sÿÿÿëöÎ&®&y †ÿÿÿëöÎ&®&y ›ÿÿÿéöÎ&®&y °ÿÿÿíöÎ&£&v ÅÿÿÿéöÎ&®&y ÚÿÿÿçöÎ&¹& ïÿÿÿìöÎ&®&y ÿÿÿíöÎ&¹& ÿÿÿëöÎ&¹& .ÿÿÿíöÎ&¹& CÿÿÿñöÎ&¹& XÿÿÿìöÎ&¹& mÿÿÿíöÎ&¹& ‚ÿÿÿíöÎ&¹& —ÿÿÿëöÎ&¹& ¬ÿÿÿïöÎ&®&y ÁÿÿÿîöÎ&®&y ÖÿÿÿêöÎ&¹& ëÿÿÿèöÎ&®&| ÿÿÿéöÎ&®&y ÿÿÿçöÎ&®&| *ÿÿÿçöÎ&®&y ?ÿÿÿçöÎ&¹&y TÿÿÿëöÎ&®&y iÿÿÿìöÎ&¹& ~ÿÿÿíöÎ&®&y “ÿÿÿçöÎ&¹&y ¨ÿÿ ÿëÐÎ&š‚ÿÿ ÿëÐÎ&°&ˆ ˆÿÿ ÿëÐÎ&°&ˆ ÿÿ ÿéìÎ&°&ˆ ²ÿÿ ÿíÚÎ&¥&… Çÿÿ ÿéãÎ&°&ˆ Üÿÿ ÿçßÎ&»&Ž ñÿÿ ÿìÕÎ&°&ˆ ÿÿ ÿíÕÎ&»&Ž ÿÿ ÿëÐÎ&»&Ž 0ÿÿ ÿíÐÎ&»&Ž Eÿÿ ÿìÐÎ&»&Ž Zÿÿ ÿìéÎ&»&Ž oÿÿ ÿíÔÎ&»&Ž „ÿÿ ÿíÕÎ&»&Ž ™ÿÿ ÿëÛÎ&»&Ž ®ÿÿ ÿïÐÎ&°&ˆ Ãÿÿ ÿîÐÎ&°&ˆ Øÿÿ ÿêìÎ&»&Ž íÿÿ ÿèåÎ&°&‹ ÿÿ ÿééÎ&°&ˆ ÿÿ ÿçÔÎ&°&‹ ,ÿÿ ÿçÞÎ&°&ˆ Aÿÿ ÿçÞÎ&»&ˆ Vÿÿ ÿëÐÎ&°&ˆ kÿÿ ÿìÕÎ&»&Ž €ÿÿ ÿíØÎ&°&ˆ •ÿÿ ÿçÜÎ&»&ˆ ªÿÿ ÿëÐÎ&™‘ÿÿ ÿëÐÎ&¯&¤ ‡ÿÿ ÿëÐÎ&¯&¤ œÿÿ ÿéìÎ&¯&¤ ±ÿÿÿðÕÎ&›& ¸£ÿÿ ÿéãÎ&¯&¤ Ûÿÿ ÿçßÎ&º&® ðÿÿ ÿìÕÎ&¯&¤ ÿÿ ÿíÕÎ& &¯¤ÿÿ ÿëÐÎ&º&® /ÿÿ ÿíÐÎ&º&® Dÿÿ ÿìÐÎ&º&® Yÿÿ ÿìéÎ&º&® nÿÿ ÿíÔÎ&º&® ƒÿÿ ÿíÕÎ&º&® ˜ÿÿ ÿëÛÎ&º&® ­ÿÿ ÿïÐÎ&¯&¤ Âÿÿ ÿîÐÎ&¯&¤ ×ÿÿ ÿêìÎ&º&® ìÿÿ ÿèãÎ&¸& ó¤ÿÿ ÿééÎ&¯&¤ ÿÿ ÿçÔÎ&¯&¸ +ÿÿ ÿçÞÎ&¯&¤ @ÿÿ ÿçÞÎ&º&® Uÿÿ ÿëÐÎ&¯&¤ jÿÿ ÿìÕÎ&º&® ÿÿ ÿíØÎ&¯&¤ ”ÿÿ ÿçÖÎ&º&® ©ÿÿ ÿëÐÎ&šÂÿÿ ÿëÐÎ&°&Ô ˆÿÿ ÿëÐÎ&°&Ô ÿÿ ÿéìÎ&°&Ô ²ÿÿ ÿíÚÎ&¥&Ê Çÿÿ ÿéãÎ&°&Ô Üÿÿ ÿçßÎ&»&Þ ñÿÿ ÿìÕÎ&°&Ô ÿÿ ÿíÕÎ&»&Þ ÿÿ ÿëÐÎ&»&Þ 0ÿÿ ÿíÐÎ&»&Þ Eÿÿ ÿìÐÎ&»&Þ Zÿÿ ÿìéÎ&»&Þ oÿÿ ÿíÔÎ&»&Þ „ÿÿ ÿíÕÎ&»&Þ ™ÿÿ ÿëÛÎ&»&Þ ®ÿÿ ÿïÐÎ&°&Ô Ãÿÿ ÿîÐÎ&°&Ô Øÿÿ ÿêìÎ&»&Þ íÿÿ ÿèåÎ&°&è ÿÿ ÿééÎ&Ý&° ÿÿ ÿçÔÎ&°&è ,ÿÿ ÿçÞÎ&°&Ô Aÿÿ ÿçÞÎ&»&Þ Vÿÿ ÿëÐÎ&°&Ô kÿÿ ÿìÕÎ&»&Þ €ÿÿ ÿíØÎ&°&Ô •ÿÿ ÿçÜÎ&»&Þ ªÿÿ ÿëÐÎ&™òÿÿ ÿëÐÎ&¯& ‡ ÿÿ ÿëÐÎ&¯&  œÿÿ ÿéìÎ&¯&  ±ÿÿ ÿðÕÎ&ü& ¸¤ÿÿ ÿéãÎ&¯&  Ûÿÿ ÿçßÎ&º&  ðÿÿ ÿìÕÎ&¯&  ÿÿ ÿíÕÎ&¯&  ÿÿ ÿëÐÎ&º&  /ÿÿ ÿíÐÎ&º&  Dÿÿ ÿìÐÎ&º&  Yÿÿ ÿìéÎ&º&  nÿÿ ÿíÔÎ&º&  ƒÿÿ ÿíÕÎ&º&  ˜ÿÿ ÿëÛÎ&º&  ­ÿÿ ÿïÐÎ&¯&  Âÿÿ ÿîÐÎ&¯&  ×ÿÿ ÿêìÎ&º&  ìÿÿ ÿèäÎ&¯&  ÿÿ ÿééÎ&¯&  ÿÿ ÿçÔÎ& &¯ +ÿÿ ÿçÞÎ&¯&  @ÿÿ ÿçÞÎ&º&  Uÿÿ ÿëÐÎ&¯&  jÿÿ ÿìÕÎ&º&  ÿÿ ÿíØÎ&¯&  ”ÿÿ ÿçÖÎ&º&  ©ÿÿ ÿëÐÎ&š !ÿÿ ÿëÐÎ&°& 5 ˆÿÿ ÿëÐÎ&°& 5 ÿÿ ÿéìÎ&°& 5 ²ÿÿ ÿíÚÎ&¥& + Çÿÿ ÿéãÎ&°& 5 Üÿÿ ÿçßÎ&»& ? ñÿÿ ÿìÕÎ&°& 5 ÿÿ ÿíÕÎ&»& ? ÿÿ ÿëÐÎ&»& ? 0ÿÿ ÿíÐÎ&»& ? Eÿÿ ÿìÐÎ&»& ? Zÿÿ ÿìéÎ&»& ? oÿÿ ÿíÔÎ&»& ? „ÿÿ ÿíÕÎ&»& ? ™ÿÿ ÿëÛÎ&»& ? ®ÿÿ ÿïÐÎ&°& 5 Ãÿÿ ÿîÐÎ&°& 5 Øÿÿ ÿêìÎ&»& ? íÿÿ ÿèåÎ&°& I ÿÿ ÿééÎ&°& 5 ÿÿ ÿçÔÎ&°& I ,ÿÿ ÿçÞÎ&°& 5 Aÿÿ ÿçÞÎ&»& ? Vÿÿ ÿëÐÎ&°& 5 kÿÿ ÿìÕÎ&»& ? €ÿÿ ÿíØÎ&°& 5 •ÿÿ ÿçÜÎ&»& ? ªÿÿ ê½&› SÿÿÿëêÉ&±& d ‰ÿÿÿëêÉ&±& d žÿÿÿéêÉ&±& d ³ÿÿÿíêÉ&¦& [ ÈÿÿÿéêÉ&±& d ÝÿÿÿçêÉ&¼& n òÿÿÿíêÉ&±& d ÿÿÿíêÉ&¼& n ÿÿÿìêÉ&¼& n 1ÿÿÿíêÉ&¼& n FÿÿÿíêÉ&¼& n [ÿÿÿìêÉ&¼& n pÿÿÿìêÉ&¼& n …ÿÿÿíêÉ&¼& n šÿÿÿëêÉ&¼& n ¯ÿÿÿïêÉ&±& d ÄÿÿÿîêÉ&±& d ÙÿÿÿêìÉ&¼& n îÿÿÿèêÉ& õ& d¼ÿÿÿéêÉ&±& d ÿÿÿçêÉ&¼& d -ÿÿÿçêÉ&±& d BÿÿÿçêÉ&¼& n WÿÿÿëêÉ&±& d lÿÿÿìêÉ&¼& n ÿÿÿíêÉ&±& d –ÿÿÿçêÉ&¼& n «ÿÿÿëöÎ& ÿÿÿëöÎ&³& • ŠÿÿÿëöÎ&³& • ŸÿÿÿêöÎ&³& • ´ÿÿÿðöÎ& ‹&¨ »ÿÿÿéöÎ&³& • ÞÿÿÿçöÎ&¾& Ÿ óÿÿÿìöÎ&³& • ÿÿÿíöÎ&¾& Ÿ ÿÿÿëöÎ&¾& Ÿ 2ÿÿÿîöÎ&¾& Ÿ GÿÿÿîöÎ&¾& Ÿ \ÿÿÿìöÎ&¾& Ÿ qÿÿÿíöÎ&¾& Ÿ †ÿÿÿíöÎ&¾& Ÿ ›ÿÿÿéöÎ&¾& Ÿ °ÿÿÿïöÎ&³& • ÅÿÿÿîöÎ&³& • ÚÿÿÿêöÎ&¾& Ÿ ïÿÿÿçöÎ&³& © ÿÿÿéöÎ&³& • ÿÿÿçöÎ&³& © .ÿÿÿçöÎ&³& • CÿÿÿçöÎ&¾& Ÿ XÿÿÿëöÎ&³& • mÿÿÿìöÎ&¾& Ÿ ‚ÿÿÿíöÎ&³& • —ÿÿÿçöÎ&¾& Ÿ ¬ÿÿÿëÐÎ&Ÿ ³ÿÿÿëÐÎ&µ& Ç ŠÿÿÿëÐÎ&µ& Ç ŸÿÿÿêëÎ&µ& Ç ´ÿÿÿíÚÎ&ª& ½ ÉÿÿÿéãÎ&µ& Ç ÞÿÿÿçßÎ&À& Ñ óÿÿÿìÕÎ&µ& Ç ÿÿÿíÕÎ&À& Ñ ÿÿÿëÐÎ&À& Ñ 2ÿÿÿîÐÎ&À& Ñ GÿÿÿîÐÎ&À& Ñ \ÿÿÿììÎ&À& Ñ qÿÿÿíÔÎ&À& Ñ †ÿÿÿíÕÎ&À& Ñ ›ÿÿÿéÛÎ&À& Ñ °ÿÿÿïÐÎ&µ& Ç ÅÿÿÿîÐÎ&µ& Ç ÚÿÿÿêìÎ&À& Ñ ïÿÿÿçåÎ&µ& Û ÿÿÿééÎ&µ& Ç ÿÿÿçÔÎ&µ& Û .ÿÿÿçâÎ&µ& Ç CÿÿÿçâÎ&À& Ñ XÿÿÿëÐÎ&µ& Ç mÿÿÿìÕÎ&À& Ñ ‚ÿÿÿíØÎ&µ& Ç —ÿÿÿçÜÎ&À& Ñ ¬ÿÿÿëÐÎ& åÿÿÿëÐÎ&³& ø ŠÿÿÿëÐÎ&³& ø ŸÿÿÿêëÎ&³& ø ´ÿÿÿíÚÎ&¨& î ÉÿÿÿéãÎ&³& ø ÞÿÿÿçßÎ&¾&  óÿÿÿìÕÎ&³& ø ÿÿÿíÕÎ&¾&  ÿÿÿëÐÎ&¾&  2ÿÿÿîÐÎ&¾&  GÿÿÿîÐÎ&¾&  \ÿÿÿììÎ&¾&  qÿÿÿíÔÎ&¾&  †ÿÿÿíÕÎ&¾&  ›ÿÿÿéÛÎ&¾&  °ÿÿÿïÐÎ&³& ø ÅÿÿÿîÐÎ&³& ø ÚÿÿÿêìÎ&¾&  ïÿÿÿçåÎ&³&  ÿÿÿééÎ&³& ø ÿÿÿçÔÎ&³&  .ÿÿÿçâÎ&³& ø CÿÿÿçâÎ&¾&  XÿÿÿëÐÎ&³& ø mÿÿÿìÕÎ&¾&  ‚ÿÿÿíØÎ&³& ø —ÿÿÿçÜÎ&¾&  ¬ÿÿ ë½&› ÿÿÿëëÉ&±& * ‰ÿÿÿëëÉ&±& * žÿÿÿéëÉ&±& * ³ÿÿÿíëÉ&¦&  ÈÿÿÿéëÉ&±& * ÝÿÿÿçëÉ&¼& 4 òÿÿÿíëÉ&±& * ÿÿÿíëÉ&¼& 4 ÿÿÿìëÉ&¼& 4 1ÿÿÿíëÉ&¼& 4 FÿÿÿíëÉ&¼& 4 [ÿÿÿìëÉ&¼& 4 pÿÿÿìëÉ&¼& 4 …ÿÿÿíëÉ&¼& 4 šÿÿÿëëÉ&¼& 4 ¯ÿÿÿïëÉ&±& * ÄÿÿÿîëÉ&±& * ÙÿÿÿêìÉ&¼& 4 îÿÿÿèëÉ&±& > ÿÿÿéëÉ&±& * ÿÿÿçëÉ&±& > -ÿÿÿçëÉ&±& * BÿÿÿçëÉ&¼& 4 WÿÿÿëëÉ&±& * lÿÿÿìëÉ&¼& 4 ÿÿÿíëÉ&±& * –ÿÿÿçëÉ&¼& 4 «ÿÿÿëëÉ& HœÿÿÿëëÉ& ‰&² SÿÿÿëëÉ&²& S žÿÿÿéëÉ&²& S ³ÿÿÿíëÉ&§& N ÈÿÿÿéëÉ&²& S ÝÿÿÿçëÉ&½& h òÿÿÿïëÊ& U& ùÂÿÿÿíëÉ&½& h ÿÿÿìëÉ&½& h 1ÿÿÿíëÉ&½& h FÿÿÿíëÉ&½& h [ÿÿÿìëÉ&½& h pÿÿÿìëÉ&½& h …ÿÿÿíëÉ&½& h šÿÿÿëëÉ&½& h ¯ÿÿÿïëÉ&²& S ÄÿÿÿîëÉ&²& S ÙÿÿÿêìÉ&½& h îÿÿÿèëÉ&²& a ÿÿÿéëÉ&²& S ÿÿÿçëÉ& & U²ÿÿÿçëÉ&²& S BÿÿÿçëÉ&½& Z WÿÿÿëëÉ&²& S lÿÿÿìëÉ&½& h ÿÿÿíëÉ&²& S –ÿÿÿçëÉ&½& Z «ÿÿÿëÐÎ&ž oÿÿÿëÐÎ&´& } ŒÿÿÿëÐÎ&´& } ¡ÿÿÿéìÎ&´& } ¶ÿÿÿíÚÎ& v& Ë´ÿÿÿéãÎ&´& } àÿÿÿçßÎ&¿& ’ õÿÿÿìÕÎ&´& } ÿÿÿíÕÎ&¿& ’ ÿÿÿëÐÎ&¿& ’ 4ÿÿÿîÐÎ&¿& ’ IÿÿÿîÐÎ&¿& ’ ^ÿÿÿìéÎ&¿& ’ sÿÿÿíÔÎ&¿& ’ ˆÿÿÿíÕÎ&¿& ’ ÿÿÿèÛÎ&¿& ’ ²ÿÿÿïÐÎ&´& } ÇÿÿÿîÐÎ&´& } ÜÿÿÿêìÎ&¿& ’ ñÿÿÿçåÎ&´& ‹ ÿÿÿééÎ&´& } ÿÿÿçÔÎ&´& ‹ 0ÿÿÿçÞÎ&´& } EÿÿÿçßÎ&¿& „ ZÿÿÿëÐÎ&´& } oÿÿÿìÕÎ&¿& ’ „ÿÿÿíØÎ&´& } ™ÿÿÿçÜÎ&¿& „ ®ÿÿÿëÐÎ&  ™ÿÿÿëÐÎ&¶& § ŒÿÿÿëÐÎ&¶& § ¡ÿÿÿéìÎ&¶& § ¶ÿÿÿíÚÎ&«&   ËÿÿÿéãÎ&¶& § àÿÿÿçßÎ&Á& ¼ õÿÿÿìÕÎ&¶& § ÿÿÿíÕÎ&Á& ¼ ÿÿÿëÐÎ&Á& ¼ 4ÿÿÿîÐÎ&Á& ¼ IÿÿÿîÐÎ&Á& ¼ ^ÿÿÿìéÎ&Á& ¼ sÿÿÿíÔÎ&Á& ¼ ˆÿÿÿíÕÎ&Á& ¼ ÿÿÿèÛÎ&Á& ¼ ²ÿÿÿïÐÎ&¶& § ÇÿÿÿîÐÎ&¶& § ÜÿÿÿêìÎ&Á& ¼ ñÿÿÿçåÎ&¶& µ ÿÿÿééÎ&¶& § ÿÿÿçÔÎ&¶& µ 0ÿÿÿçÞÎ&¶& § EÿÿÿçßÎ&Á& ® ZÿÿÿëÐÎ&¶& § oÿÿÿìÕÎ&Á& ¼ „ÿÿÿíØÎ&¶& § ™ÿÿÿçÜÎ&Á& ® ®ÿÿÿëÐÎ&ž ÃÿÿÿëÐÎ&´& Ñ ‹ÿÿÿëÐÎ&´& Ñ  ÿÿÿêëÎ&´& Ñ µÿÿÿíÚÎ&©& Ê ÊÿÿÿéãÎ&´& Ñ ßÿÿÿçßÎ&¿& æ ôÿÿÿìÕÎ&´& Ñ ÿÿÿíÕÎ&¿& æ ÿÿÿëÐÎ&¿& æ 3ÿÿÿîÐÎ&¿& æ HÿÿÿîÐÎ&¿& æ ]ÿÿÿìéÎ&¿& æ rÿÿÿíÔÎ&¿& æ ‡ÿÿÿíÕÎ&¿& æ œÿÿÿëÛÎ&¿& æ ±ÿÿÿïÐÎ&´& Ñ ÆÿÿÿîÐÎ&´& Ñ ÛÿÿÿêìÎ&¿& æ ðÿÿÿçåÎ&´& ß ÿÿÿééÎ&´& Ñ ÿÿÿçÔÎ&´& ß /ÿÿÿçÞÎ&´& Ñ DÿÿÿçßÎ&¿& Ø YÿÿÿëÐÎ&´& Ñ nÿÿÿìÕÎ&¿& æ ƒÿÿÿíØÎ&´& Ñ ˜ÿÿÿç×Î&¿& Ø ­ÿÿÿíëÉ&œ íÿÿÿëëÉ&²& û ‰ÿÿÿëëÉ&²& û žÿÿÿéëÉ&²& û ³ÿÿÿíëÉ&§& ô ÈÿÿÿéëÉ&²& û ÝÿÿÿçëÉ&½&  òÿÿÿíëÉ&²& û ÿÿÿíëÉ&½&  ÿÿÿìëÉ&½&  1ÿÿÿíëÉ&½&  FÿÿÿíëÉ&½&  [ÿÿÿìëÉ&½&  pÿÿÿìëÉ&½&  …ÿÿÿíëÉ&½&  šÿÿÿëëÉ&½&  ¯ÿÿÿïëÉ&²& û ÄÿÿÿîëÉ&²& û ÙÿÿÿêìÉ&½&  îÿÿÿèëÉ&²&  ÿÿÿéëÉ&²& û ÿÿÿçëÉ&²&  -ÿÿÿçëÉ&²& û BÿÿÿçëÉ&½&  WÿÿÿëëÉ&²& û lÿÿÿìëÉ&½&  ÿÿÿíëÉ&²& û –ÿÿÿçëÉ&½&  «ÿÿ ë½&¡ ÿÿÿëëÉ&§& $ ‰ÿÿÿëëÉ&·& $ žÿÿÿéëÉ&·& $ ³ÿÿÿíëÊ& È&¬ &ÿÿÿéëÉ&·& $ ÝÿÿÿçëÊ&Â& 9 òÿÿÿíëÉ&·& $ ÿÿÿíëÊ&Â& 9 ÿÿÿìëÊ&Â& 9 1ÿÿÿíëÊ&Â& 9 FÿÿÿíëÊ&Â& 9 [ÿÿÿìëÊ&Â& 9 pÿÿÿìëÊ&Â& 9 …ÿÿÿíëÊ&Â& 9 šÿÿÿëëÊ&Â& 9 ¯ÿÿÿïëÊ& $& ÄÿÿÿîëÉ&·& $ ÙÿÿÿêìÊ&Â& 9 îÿÿÿèëÉ&·& 2 ÿÿÿéëÉ&·& $ ÿÿÿçëÉ&·& 2 -ÿÿÿçëÉ&·& $ BÿÿÿçëÊ&Â& + WÿÿÿëëÉ&·& $ lÿÿÿìëÊ&Â& 9 ÿÿÿíëÉ&·& $ –ÿÿÿçëÊ&Â& + «ÿÿÿëÐÎ&¢ @ÿÿÿëÐÎ&¸& M ŠÿÿÿëÐÎ&¸& M ŸÿÿÿêëÎ&¸& M ´ÿÿÿíÚÎ&­& F ÉÿÿÿéãÎ&¸& M ÞÿÿÿçßÎ&Ã& b óÿÿÿìÕÎ&¸& M ÿÿÿíÕÎ&Ã& b ÿÿÿëÐÎ&Ã& b 2ÿÿÿîÐÎ&Ã& b GÿÿÿîÐÎ&Ã& b \ÿÿÿììÎ&Ã& b qÿÿÿíÔÎ&Ã& b †ÿÿÿíÕÎ&Ã& b ›ÿÿÿéÛÎ&Ã& b °ÿÿÿïÐÎ&¸& M ÅÿÿÿîÐÎ&¸& M ÚÿÿÿêìÎ&Ã& b ïÿÿÿçåÎ&¸& [ ÿÿÿééÎ&¸& M ÿÿÿçÔÎ&¸& [ .ÿÿÿçâÎ&¸& M CÿÿÿçâÎ&Ã& T XÿÿÿëÐÎ&¸& M mÿÿÿìÕÎ&Ã& b ‚ÿÿÿíØÎ&¸& M —ÿÿÿçÜÎ&Ã& T ¬ÿÿÿëÐÎ&˜ iÿÿÿëÐÎ&®& o ‡ÿÿÿëÐÎ&®& o œÿÿÿéìÎ&®& o ±ÿÿÿðÕÎ& l&£ »ÿÿÿéãÎ&®& o ÛÿÿÿçßÎ&¹& u ðÿÿÿìÕÎ&®& o ÿÿÿíÕÎ&¹& u ÿÿÿëÐÎ&¹& u /ÿÿÿíÐÎ&¹& u DÿÿÿìÐÎ&¹& u YÿÿÿìéÎ&¹& u nÿÿÿíÔÎ&¹& u ƒÿÿÿíÕÎ&¹& u ˜ÿÿÿëÛÎ&¹& u ­ÿÿ ÿïÐÎ&¯& p ÂÿÿÿîÐÎ&®& o ×ÿÿÿêìÎ&¹& u ìÿÿÿèäÎ&®& r ÿÿÿééÎ&®& o ÿÿÿçÔÎ&®& r +ÿÿÿçÞÎ&®& o @ÿÿÿçÞÎ&¹& o UÿÿÿëÐÎ&®& o jÿÿÿìÕÎ&¹& u ÿÿÿíØÎ&®& u ”ÿÿÿçÖÎ&¹& o ©ÿÿÿëöÎ&VÄÿÿÿëõÎ&Ú&\ †ÿÿÿëõÎ&Ú&\ ›ÿÿÿéõÎ&Ú&\ °ÿÿÿíöÎ&Ï&Y ÅÿÿÿéõÎ&Ú&\ ÚÿÿÿçõÎ&å&b ïÿÿÿìõÎ&Ú&\ ÿÿÿíõÎ&å&b ÿÿÿëõÎ&å&b .ÿÿÿíõÎ&å&b CÿÿÿñõÎ&å&b XÿÿÿìõÎ&å&b mÿÿÿíõÎ&å&b ‚ÿÿÿíõÎ&å&b —ÿÿÿëõÎ&å&b ¬ÿÿÿïõÎ&Ú&\ ÁÿÿÿîõÎ&Ú&\ ÖÿÿÿêõÎ&å&b ëÿÿÿèõÎ&Ú&_ ÿÿÿéõÎ&Ú&\ ÿÿÿçõÎ&Ú&_ *ÿÿÿçõÎ&Ú&\ ?ÿÿÿçõÎ&å&\ TÿÿÿëõÎ&Ú&\ iÿÿÿìõÎ&å&b ~ÿÿÿíõÎ&Ú&\ “ÿÿÿçõÎ&å&\ ¨ÿÿ ÿëÑÎ&Æeÿÿ ÿëÐÎ&Ü&k ˆÿÿ ÿëÐÎ&Ü&k ÿÿ ÿéìÎ&Ü&k ²ÿÿÿíÚÎ&Ñ&h Çÿÿ ÿéãÎ&Ü&k Üÿÿ ÿçßÎ&ç&q ñÿÿ ÿìÕÎ&Ü&k ÿÿ ÿíÕÎ&ç&q ÿÿ ÿëÑÎ&ç&q 0ÿÿ ÿíÑÎ&ç&q Eÿÿ ÿìÑÎ&ç&q Zÿÿ ÿìéÎ&ç&q oÿÿ ÿíÔÎ&ç&q „ÿÿ ÿíÕÎ&ç&q ™ÿÿ ÿëÛÎ&ç&q ®ÿÿ ÿïÐÎ&Ü&k Ãÿÿ ÿîÐÎ&Ü&k Øÿÿ ÿêìÎ&ç&q íÿÿ ÿèåÍ&Ü&n ÿÿ ÿééÎ&Ü&k ÿÿ ÿçÔÍ&Ü&n ,ÿÿ ÿçÞÎ&Ü&k Aÿÿ ÿçÞÎ&ç&k Vÿÿ ÿëÐÎ&Ü&k kÿÿ ÿìÕÎ&ç&q €ÿÿ ÿíØÎ&Ü&k •ÿÿ ÿçÜÎ&ç&k ªÿÿÿëöÎ&ÄtÿÿÿëöÎ&Ú&z †ÿÿÿëöÎ&Ú&z ›ÿÿÿéöÎ&Ú&z °ÿÿÿíöÎ&Ï&w ÅÿÿÿéöÎ&Ú&z ÚÿÿÿçöÎ&å&€ ïÿÿÿìöÎ&Ú&z ÿÿÿíöÎ&å&€ ÿÿÿëöÎ&å&€ .ÿÿÿíöÎ&å&€ CÿÿÿñöÎ&å&€ XÿÿÿìöÎ&å&€ mÿÿÿíöÎ&å&€ ‚ÿÿÿíöÎ&å&€ —ÿÿÿëöÎ&å&€ ¬ÿÿÿïöÎ&Ú&z ÁÿÿÿîöÎ&Ú&z ÖÿÿÿêöÎ&å&€ ëÿÿÿèöÎ&Ú&} ÿÿÿéöÎ&Ú&z ÿÿÿçöÎ&Ú&} *ÿÿÿçöÎ&Ú&z ?ÿÿÿçöÎ&å&z TÿÿÿëöÎ&Ú&z iÿÿÿìöÎ&å&€ ~ÿÿÿíöÎ&Ú&z “ÿÿÿçöÎ&å&z ¨ÿÿ ÿëÐÎ&ƃÿÿ ÿëÐÎ&Ü&‰ ˆÿÿ ÿëÐÎ&Ü&‰ ÿÿ ÿéìÎ&Ü&‰ ²ÿÿÿíÚÎ&Ñ&† Çÿÿ ÿéãÎ&Ü&‰ Üÿÿ ÿçßÎ&ç& ñÿÿ ÿìÕÎ&Ü&‰ ÿÿ ÿíÕÎ&ç& ÿÿ ÿëÐÎ&ç& 0ÿÿ ÿíÐÎ&ç& Eÿÿ ÿìÐÎ&ç& Zÿÿ ÿìéÎ&ç& oÿÿ ÿíÔÎ&ç& „ÿÿ ÿíÕÎ&ç& ™ÿÿ ÿëÛÎ&ç& ®ÿÿ ÿïÐÎ&Ü&‰ Ãÿÿ ÿîÐÎ&Ü&‰ Øÿÿ ÿêìÎ&ç& íÿÿ ÿèåÎ&Ü&Œ ÿÿ ÿééÎ&Ü&‰ ÿÿ ÿçÔÎ&Ü&Œ ,ÿÿ ÿçÞÎ&Ü&‰ Aÿÿ ÿçÞÎ&ç&‰ Vÿÿ ÿëÐÎ&Ü&‰ kÿÿ ÿìÕÎ&ç& €ÿÿ ÿíØÎ&Ü&‰ •ÿÿ ÿçÜÎ&ç&‰ ªÿÿ ÿëÐÎ&Å’ÿÿ ÿëÐÎ&Û&¥ ‡ÿÿ ÿëÐÎ&Û&¥ œÿÿ ÿéìÎ&Û&¥ ±ÿÿ ÿíÚÎ&Ð&œ Æÿÿ ÿéãÎ&Û&¥ Ûÿÿ ÿçßÎ&æ&¯ ðÿÿ ÿìÕÎ&Û&¥ ÿÿ ÿíÕÎ&æ&¯ ÿÿ ÿëÐÎ&æ&¯ /ÿÿ ÿíÐÎ&æ&¯ Dÿÿ ÿìÐÎ&æ&¯ Yÿÿ ÿìéÎ&æ&¯ nÿÿ ÿíÔÎ&æ&¯ ƒÿÿ ÿíÕÎ&æ&¯ ˜ÿÿ ÿëÛÎ&æ&¯ ­ÿÿ ÿïÐÎ&Û&¥ Âÿÿ ÿîÐÎ&Û&¥ ×ÿÿ ÿêìÎ&æ&¯ ìÿÿ ÿèäÎ&Û&¹ ÿÿ ÿééÎ&Û&¥ ÿÿ ÿçÔÎ&Û&¹ +ÿÿ ÿçÞÎ&Û&¥ @ÿÿ ÿçÞÎ&æ&¯ Uÿÿ ÿëÐÎ&Û&¥ jÿÿ ÿìÕÎ&æ&¯ ÿÿ ÿíØÎ&Û&¥ ”ÿÿ ÿçÖÎ&æ&¯ ©ÿÿ ÿëÐÎ&ÃÆÿÿ ÿëÐÎ&Ü&Õ ˆÿÿ ÿëÐÎ&Ü&Õ ÿÿ ÿéìÎ&Ü&Õ ²ÿÿÿíÚÎ&Ñ&Ë Çÿÿ ÿéãÎ&Ü&Õ Üÿÿ ÿçßÎ&ç&ß ñÿÿ ÿìÕÎ&Ü&Õ ÿÿ ÿíÕÎ&ç&ß ÿÿ ÿëÐÎ&ç&ß 0ÿÿ ÿíÐÎ&ç&ß Eÿÿ ÿìÐÎ&ç&ß Zÿÿ ÿìéÎ&ç&ß oÿÿ ÿíÔÎ&ç&ß „ÿÿ ÿíÕÎ&ç&ß ™ÿÿ ÿëÛÎ&ç&ß ®ÿÿ ÿïÐÎ&Ü&Õ Ãÿÿ ÿîÐÎ&Ü&Õ Øÿÿ ÿêìÎ&ç&ß íÿÿ ÿèåÎ&Ü&é ÿÿ ÿééÎ&Ü&Õ ÿÿ ÿçÔÎ&Ü&é ,ÿÿ ÿçÞÎ&Ü&Õ Aÿÿ ÿçÞÎ&ç&ß Vÿÿ ÿëÐÎ&Ü&Õ kÿÿ ÿìÕÎ&ç&ß €ÿÿ ÿíØÎ&Ü&Õ •ÿÿ ÿçÜÎ&ç&ß ªÿÿ ÿëÐÎ&Åóÿÿ ÿëÐÎ&Û&  ‡ÿÿ ÿëÐÎ&Û&  œÿÿ ÿéìÎ&Û&  ±ÿÿ ÿíÚÎ&Ð&ý Æÿÿ ÿéãÎ&Û&  Ûÿÿ ÿçßÎ&æ&  ðÿÿ ÿìÕÎ&Û&  ÿÿ ÿíÕÎ&æ&  ÿÿ ÿëÐÎ&æ&  /ÿÿ ÿíÐÎ&æ&  Dÿÿ ÿìÐÎ&æ&  Yÿÿ ÿìéÎ&æ&  nÿÿ ÿíÔÎ&æ&  ƒÿÿ ÿíÕÎ&æ&  ˜ÿÿ ÿëÛÎ&æ&  ­ÿÿ ÿïÐÎ&Û&  Âÿÿ ÿîÐÎ&Û&  ×ÿÿ ÿêìÎ&æ&  ìÿÿ ÿèäÎ&Û&  ÿÿ ÿééÎ&Û&  ÿÿ ÿçÔÎ&Û&  +ÿÿ ÿçÞÎ&Û&  @ÿÿ ÿçÞÎ&æ&  Uÿÿ ÿëÐÎ&Û&  jÿÿ ÿìÕÎ&æ&  ÿÿ ÿíØÎ&Û&  ”ÿÿ ÿçÖÎ&æ&  ©ÿÿ ÿëÐÎ&Æ "ÿÿ ÿëÐÎ&Ü& 6 ˆÿÿ ÿëÐÎ&Ü& 6 ÿÿ ÿéìÎ&Ü& 6 ²ÿÿÿíÚÎ&Ñ& , Çÿÿ ÿéãÎ&Ü& 6 Üÿÿ ÿçßÎ&ç& @ ñÿÿ ÿìÕÎ&Ü& 6 ÿÿ ÿíÕÎ&ç& @ ÿÿ ÿëÐÎ&ç& @ 0ÿÿ ÿíÐÎ&ç& @ Eÿÿ ÿìÐÎ&ç& @ Zÿÿ ÿìéÎ&ç& @ oÿÿ ÿíÔÎ&ç& @ „ÿÿ ÿíÕÎ&ç& @ ™ÿÿ ÿëÛÎ&ç& @ ®ÿÿ ÿïÐÎ&Ü& 6 Ãÿÿ ÿîÐÎ&Ü& 6 Øÿÿ ÿêìÎ&ç& @ íÿÿ ÿèåÎ&Ü& J ÿÿ ÿééÎ&Ü& 6 ÿÿ ÿçÔÎ&Ü& J ,ÿÿ ÿçÞÎ&Ü& 6 Aÿÿ ÿçÞÎ&ç& @ Vÿÿ ÿëÐÎ&Ü& 6 kÿÿ ÿìÕÎ&ç& @ €ÿÿ ÿíØÎ&Ü& 6 •ÿÿ ÿçÜÎ&ç& @ ªÿÿ êÀ&Ç TÿÿÿëêÉ&Ý& e ‰ÿÿÿëêÉ&Ý& e žÿÿÿéêÉ&Ý& e ³ÿÿÿíêÉ&Ò& \ ÈÿÿÿéêÉ&Ý& e ÝÿÿÿçêÉ&è& o òÿÿÿíêÉ&Ý& e ÿÿÿíêÉ&è& o ÿÿÿìêÉ&è& o 1ÿÿÿíêÉ&è& o FÿÿÿíêÉ&è& o [ÿÿÿìêÉ&è& o pÿÿÿìêÉ&è& o …ÿÿÿíêÉ&è& o šÿÿÿëêÉ&è& o ¯ÿÿÿïêÉ&Ý& e ÄÿÿÿîêÉ&Ý& e ÙÿÿÿêìÉ&è& o îÿÿÿèêÉ&Ý& x ÿÿÿéêÉ&Ý& e ÿÿÿçêÉ&Ý& x -ÿÿÿçêÉ&Ý& e BÿÿÿçêÉ&è& o WÿÿÿëêÉ&Ý& e lÿÿÿìêÉ&è& o ÿÿÿíêÉ&Ý& e –ÿÿÿçêÉ&è& o «ÿÿÿëöÎ&É ‚ÿÿÿëöÎ&ß& – ŠÿÿÿëöÎ&ß& – ŸÿÿÿêöÎ&ß& – ´ÿÿÿíöÎ&Ô& Œ ÉÿÿÿéöÎ&ß& – ÞÿÿÿçöÎ&ê&   óÿÿÿìöÎ&ß& – ÿÿÿíöÎ&ê&   ÿÿÿëöÎ&ê&   2ÿÿÿîöÎ&ê&   GÿÿÿîöÎ&ê&   \ÿÿÿìöÎ&ê&   qÿÿÿíöÎ&ê&   †ÿÿÿíöÎ&ê&   ›ÿÿÿéöÎ&ê&   °ÿÿÿïöÎ&ß& – ÅÿÿÿîöÎ&ß& – ÚÿÿÿêöÎ&ê&   ïÿÿÿçöÎ&ß& ª ÿÿÿéöÎ&ß& – ÿÿÿçöÎ&ß& ª .ÿÿÿçöÎ&ß& – CÿÿÿçöÎ&ê&   XÿÿÿëöÎ&ß& – mÿÿÿìöÎ&ê&   ‚ÿÿÿíöÎ&ß& – —ÿÿÿçöÎ&ê&   ¬ÿÿÿëÐÎ&Ë ´ÿÿÿëÐÎ&á& È ŠÿÿÿëÐÎ&á& È ŸÿÿÿêëÎ&á& È ´ÿÿÿíÚÎ&Ö& ¾ ÉÿÿÿéãÎ&á& È ÞÿÿÿçßÎ&ì& Ò óÿÿÿìÕÎ&á& È ÿÿÿíÕÎ&ì& Ò ÿÿÿëÐÎ&ì& Ò 2ÿÿÿîÐÎ&ì& Ò GÿÿÿîÐÎ&ì& Ò \ÿÿÿììÎ&ì& Ò qÿÿÿíÔÎ&ì& Ò †ÿÿÿíÕÎ&ì& Ò ›ÿÿÿéÛÎ&ì& Ò °ÿÿÿïÐÎ&á& È ÅÿÿÿîÐÎ&á& È ÚÿÿÿêìÎ&ì& Ò ïÿÿÿçåÎ&á& Ü ÿÿÿééÎ&á& È ÿÿÿçÔÎ&á& Ü .ÿÿÿçâÎ&á& È CÿÿÿçâÎ&ì& Ò XÿÿÿëÐÎ&á& È mÿÿÿìÕÎ&ì& Ò ‚ÿÿÿíØÎ&á& È —ÿÿÿçÜÎ&ì& Ò ¬ÿÿÿëÐÎ&É æÿÿÿëÐÎ&ß& ù ŠÿÿÿëÐÎ&ß& ù ŸÿÿÿêëÎ&ß& ù ´ÿÿÿíÚÎ&Ô& ï ÉÿÿÿéãÎ&ß& ù ÞÿÿÿçßÎ&ê&  óÿÿÿìÕÎ&ß& ù ÿÿÿíÕÎ&ê&  ÿÿÿëÐÎ&ê&  2ÿÿÿîÐÎ&ê&  GÿÿÿîÐÎ&ê&  \ÿÿÿììÎ&ê&  qÿÿÿíÔÎ&ê&  †ÿÿÿíÕÎ&ê&  ›ÿÿÿéÛÎ&ê&  °ÿÿÿïÐÎ&ß& ù ÅÿÿÿîÐÎ&ß& ù ÚÿÿÿêìÎ&ê&  ïÿÿÿçåÎ&ß&  ÿÿÿééÎ&ß& ù ÿÿÿçÔÎ&ß&  .ÿÿÿçâÎ&ß& ù CÿÿÿçâÎ&ê&  XÿÿÿëÐÎ&ß& ù mÿÿÿìÕÎ&ê&  ‚ÿÿÿíØÎ&ß& ù —ÿÿÿçÜÎ&ê&  ¬ÿÿ ëÀ&Ç ÿÿÿëëÉ&Ý& + ‰ÿÿÿëëÉ&Ý& + žÿÿÿéëÉ&Ý& + ³ÿÿÿíëÉ&Ò& ! ÈÿÿÿéëÉ&Ý& + ÝÿÿÿçëÉ&è& 5 òÿÿÿíëÉ&Ý& + ÿÿÿíëÉ&è& 5 ÿÿÿìëÉ&è& 5 1ÿÿÿíëÉ&è& 5 FÿÿÿíëÉ&è& 5 [ÿÿÿìëÉ&è& 5 pÿÿÿìëÉ&è& 5 …ÿÿÿíëÉ&è& 5 šÿÿÿëëÉ&è& 5 ¯ÿÿÿïëÉ&Ý& + ÄÿÿÿîëÉ&Ý& + ÙÿÿÿêìÉ&è& 5 îÿÿÿèëÉ&Ý& ? ÿÿÿéëÉ&Ý& + ÿÿÿçëÉ&Ý& ? -ÿÿÿçëÉ&Ý& + BÿÿÿçëÉ&è& 5 WÿÿÿëëÉ&Ý& + lÿÿÿìëÉ&è& 5 ÿÿÿíëÉ&Ý& + –ÿÿÿçëÉ&è& 5 «ÿÿÿëëÉ&È HÿÿÿëëÉ&Þ& S ‰ÿÿÿëëÉ&Þ& S žÿÿÿéëÉ&Þ& S ³ÿÿÿíëÉ&Ó& N ÈÿÿÿéëÉ&Þ& S ÝÿÿÿçëÉ&é& h òÿÿÿíëÉ&Þ& S ÿÿÿíëÉ&é& h ÿÿÿìëÉ&é& h 1ÿÿÿíëÉ&é& h FÿÿÿíëÉ&é& h [ÿÿÿìëÉ&é& h pÿÿÿìëÉ&é& h …ÿÿÿíëÉ&é& h šÿÿÿëëÉ&é& h ¯ÿÿÿïëÉ&Þ& S ÄÿÿÿîëÉ&Þ& S ÙÿÿÿêìÉ&é& h îÿÿÿèëÉ&Þ& a ÿÿÿéëÉ&Þ& S ÿÿÿçëÉ&Þ& S -ÿÿÿçëÉ&Þ& S BÿÿÿçëÉ&é& Z WÿÿÿëëÉ&Þ& S lÿÿÿìëÉ&é& h ÿÿÿíëÉ&Þ& S –ÿÿÿçëÉ&é& Z «ÿÿÿëÐÎ&Ê oÿÿÿëÐÎ&à& } ŒÿÿÿëÐÎ&à& } ¡ÿÿÿéìÎ&à& } ¶ÿÿÿíÚÎ&Õ& v ËÿÿÿéãÎ&à& } àÿÿÿçßÎ&ë& ’ õÿÿÿìÕÎ&à& } ÿÿÿíÕÎ&ë& ’ ÿÿÿëÐÎ&ë& ’ 4ÿÿÿîÐÎ&ë& ’ IÿÿÿîÐÎ&ë& ’ ^ÿÿÿìéÎ&ë& ’ sÿÿÿíÔÎ&ë& ’ ˆÿÿÿíÕÎ&ë& ’ ÿÿÿèÛÎ&ë& ’ ²ÿÿÿïÐÎ&à& } ÇÿÿÿîÐÎ&à& } ÜÿÿÿêìÎ&ë& ’ ñÿÿÿçåÎ&à& ‹ ÿÿÿééÎ&à& } ÿÿÿçÔÎ&à& ‹ 0ÿÿÿçÞÎ&à& } EÿÿÿçßÎ&ë& „ ZÿÿÿëÐÎ&à& } oÿÿÿìÕÎ&ë& ’ „ÿÿÿíØÎ&à& } ™ÿÿÿçÜÎ&ë& „ ®ÿÿÿëÐÎ&Ì ™ÿÿÿëÐÎ&â& § ŒÿÿÿëÐÎ&â& § ¡ÿÿÿéìÎ&â& § ¶ÿÿÿíÚÎ&×&   ËÿÿÿéãÎ&â& § àÿÿÿçßÎ&í& ¼ õÿÿÿìÕÎ&â& § ÿÿÿíÕÎ&í& ¼ ÿÿÿëÐÎ&í& ¼ 4ÿÿÿîÐÎ&í& ¼ IÿÿÿîÐÎ&í& ¼ ^ÿÿÿìéÎ&í& ¼ sÿÿÿíÔÎ&í& ¼ ˆÿÿÿíÕÎ&í& ¼ ÿÿÿèÛÎ&í& ¼ ²ÿÿÿïÐÎ&â& § ÇÿÿÿîÐÎ&â& § ÜÿÿÿêìÎ&í& ¼ ñÿÿÿçåÎ&â& µ ÿÿÿééÎ&â& § ÿÿÿçÔÎ&â& µ 0ÿÿÿçÞÎ&â& § EÿÿÿçßÎ&í& ® ZÿÿÿëÐÎ&â& § oÿÿÿìÕÎ&í& ¼ „ÿÿÿíØÎ&â& § ™ÿÿÿçÜÎ&í& ® ®ÿÿÿëÐÎ& ÃÊÿÿÿëÐÎ&à& Ñ ‹ÿÿÿëÐÎ&à& Ñ  ÿÿÿêëÎ&à& Ñ µÿÿÿíÚÎ&Õ& Ê ÊÿÿÿéãÎ&à& Ñ ßÿÿÿçßÎ&ë& æ ôÿÿÿìÕÎ&à& Ñ ÿÿÿíÕÎ&ë& æ ÿÿÿëÐÎ&ë& æ 3ÿÿÿîÐÎ&ë& æ HÿÿÿîÐÎ&ë& æ ]ÿÿÿìéÎ&ë& æ rÿÿÿíÔÎ&ë& æ ‡ÿÿÿíÕÎ&ë& æ œÿÿÿëÛÎ&ë& æ ±ÿÿÿïÐÎ&à& Ñ ÆÿÿÿîÐÎ&à& Ñ ÛÿÿÿêìÎ&ë& æ ðÿÿÿçåÎ&à& ß ÿÿÿééÎ&à& Ñ ÿÿÿçÔÎ&à& ß /ÿÿÿçÞÎ&à& Ñ DÿÿÿçßÎ&ë& Ø YÿÿÿëÐÎ&à& Ñ nÿÿÿìÕÎ&ë& æ ƒÿÿÿíØÎ&à& Ñ ˜ÿÿÿç×Î&ë& Ø ­ÿÿÿíëÉ&È íÿÿÿëëÉ&Þ& û ‰ÿÿÿëëÉ&Þ& û žÿÿÿéëÉ&Þ& û ³ÿÿÿíëÉ&Ó& ô ÈÿÿÿéëÉ&Þ& û ÝÿÿÿçëÉ&é&  òÿÿÿíëÉ&Þ& û ÿÿÿíëÉ&é&  ÿÿÿìëÉ&é&  1ÿÿÿíëÉ&é&  FÿÿÿíëÉ&é&  [ÿÿÿìëÉ&é&  pÿÿÿìëÉ&é&  …ÿÿÿíëÉ&é&  šÿÿÿëëÉ&é&  ¯ÿÿÿïëÉ&Þ& û ÄÿÿÿîëÉ&Þ& û ÙÿÿÿêìÉ&é&  îÿÿÿèëÉ&Þ&  ÿÿÿéëÉ&Þ& û ÿÿÿçëÉ&Þ&  -ÿÿÿçëÉ&Þ& û BÿÿÿçëÉ&é&  WÿÿÿëëÉ&Þ& û lÿÿÿìëÉ&é&  ÿÿÿíëÉ&Þ& û –ÿÿÿçëÉ&é&  «ÿÿ ë½&Í ÿÿÿëëÉ&ã& $ ‰ÿÿÿëëÉ&ã& $ žÿÿÿéëÉ&ã& $ ³ÿÿÿíëÉ&Ø&  ÈÿÿÿéëÉ&ã& $ ÝÿÿÿçëÉ&î& 9 òÿÿÿíëÉ&ã& $ ÿÿÿíëÉ&ã& $ ÿÿÿìëÉ&î& 9 1ÿÿÿíëÉ&î& 9 FÿÿÿíëÉ&î& 9 [ÿÿÿìëÉ&î& 9 pÿÿÿìëÉ&î& 9 …ÿÿÿíëÉ&î& 9 šÿÿÿëëÉ&î& 9 ¯ÿÿÿïëÉ&ã& $ ÄÿÿÿîëÉ&ã& $ ÙÿÿÿêìÉ&î& 9 îÿÿÿèëÉ&ã& 2 ÿÿÿéëÉ&ã& $ ÿÿÿçëÉ&ã& 2 -ÿÿÿçëÉ&ã& $ BÿÿÿçëÉ&î& + WÿÿÿëëÉ&ã& $ lÿÿÿìëÉ&ã& $ ÿÿÿíëÉ&ã& $ –ÿÿÿçëÉ&î& + «ÿÿÿëÐÎ&Î @ÿÿÿëÐÎ&ä& M ŠÿÿÿëÐÎ&ä& M ŸÿÿÿêëÎ&ä& M ´ÿÿÿíÚÎ&Ù& F ÉÿÿÿéãÎ&ä& M ÞÿÿÿçßÎ&ï& b óÿÿÿìÕÎ&ä& M ÿÿÿíÕÎ&ï& b ÿÿÿëÐÎ&ï& b 2ÿÿÿîÐÎ&ï& b GÿÿÿîÐÎ&ï& b \ÿÿÿììÎ&ï& b qÿÿÿíÔÎ&ï& b †ÿÿÿíÕÎ&ï& b ›ÿÿÿéÛÎ&ï& b °ÿÿÿïÐÎ&ä& M ÅÿÿÿîÐÎ&ä& M ÚÿÿÿêìÎ&ï& b ïÿÿÿçåÎ&ä& [ ÿÿÿééÎ&ä& M ÿÿÿçÔÎ&ä& [ .ÿÿÿçâÎ&ä& M CÿÿÿçâÎ&ï& T XÿÿÿëÐÎ&ä& M mÿÿÿìÕÎ&ï& b ‚ÿÿÿíØÎ&ä& M —ÿÿÿçÜÎ&ï& T ¬ÿÿ ÿëÐÎ& jÅÿÿÿëÐÎ&Ú& p ‡ÿÿÿëÐÎ&Ú& p œÿÿÿéìÎ&Ú& p ±ÿÿÿíÚÎ&Ï& m ÆÿÿÿéãÎ&Ú& p ÛÿÿÿçßÎ&å& v ðÿÿÿìÕÎ&Ú& p ÿÿÿíÕÎ&å& v ÿÿÿëÐÎ&å& v /ÿÿÿíÐÎ&å& v DÿÿÿìÐÎ&å& v YÿÿÿìéÎ&å& v nÿÿÿíÔÎ&å& v ƒÿÿÿíÕÎ&å& v ˜ÿÿÿëÛÎ&å& v ­ÿÿÿïÐÎ&Ú& p ÂÿÿÿîÐÎ&Ú& p ×ÿÿÿêìÎ&å& v ìÿÿÿèäÎ&Ú& s ÿÿÿééÎ&Ú& p ÿÿÿçÔÎ&Ú& s +ÿÿÿçÞÎ&Ú& p @ÿÿÿçÞÎ&å& p UÿÿÿëÐÎ&Ú& p jÿÿÿìÕÎ&å& v ÿÿÿíØÎ&Ú& p ”ÿÿÿçÖÎ&å& p ©ÿÿÿëöÎ&UðÿÿÿëõÎ&&[ xÿÿÿëõÎ&&[ ÿÿÿèõÎ&&[ ¢ÿÿÿíöÎ&ù&Z ÅÿÿÿêõÎ&&[ ÌÿÿÿçõÎ& &a áÿÿÿïõÎ&&[ öÿÿÿíõÎ&[&  ÿÿÿëõÎ& &a ÿÿÿíõÎ& &a 5ÿÿÿíõÎ& &a JÿÿÿìõÎ& &a _ÿÿÿíõÎ& &a tÿÿÿíõÎ& &a ‰ÿÿÿçõÎ& &a žÿÿÿïõÎ&&[ ³ÿÿÿîõÎ&&[ ÈÿÿÿèõÎ& &a ÝÿÿÿèöÎ&&^ òÿÿÿéõÎ&&[ ÿÿÿçöÎ&&^ ÿÿÿçõÎ&&[ 1ÿÿÿçõÎ& &[ FÿÿÿëõÎ&&[ [ÿÿÿìõÎ& &a pÿÿÿíõÎ&&[ …ÿÿÿçõÎ& &[ šÿÿÿëÐÎ&òdÿÿÿëÐÎ&&j zÿÿÿëÐÎ&&j ÿÿÿèìÎ&&j ¤ÿÿÿðÕÎ&û&… ¹ÿÿÿéãÎ&&j ÎÿÿÿçßÎ& &p ãÿÿÿïÕÎ&&j øÿÿÿíÕÎ& &p ÿÿÿëÐÎ& &p "ÿÿÿíÐÎ& &p 7ÿÿÿíÐÎ& &p LÿÿÿìéÎ& &p aÿÿÿíÔÎ& &p vÿÿÿíÕÎ& &p ‹ÿÿÿçÛÎ& &p  ÿÿÿïÐÎ& µ&jÿÿÿîÐÎ&&j ÊÿÿÿèìÎ& &p ßÿÿÿèàÎ&&m ôÿÿÿééÎ&&j ÿÿÿçÔÎ&&m ÿÿÿçÞÎ&&j 3ÿÿÿçÞÎ& &j HÿÿÿëÐÎ&&j ]ÿÿÿìÕÎ& &p rÿÿÿíØÎ&&j ‡ÿÿÿçÜÎ& &j œÿÿÿëöÎ&ðsÿÿÿëöÎ&&y xÿÿÿëöÎ&&y ÿÿÿèöÎ&&y ¢ÿÿÿðöÎ&ù&v ·ÿÿÿêöÎ&&y ÌÿÿÿçöÎ& & áÿÿÿïöÎ&&y öÿÿÿíöÎ& & ÿÿÿëöÎ& & ÿÿÿíöÎ& & 5ÿÿÿíöÎ& & JÿÿÿìöÎ& & _ÿÿÿíöÎ& & tÿÿÿíöÎ& & ‰ÿÿÿçöÎ& & žÿÿÿïöÎ&&y ³ÿÿÿîöÎ&&y ÈÿÿÿèöÎ& & ÝÿÿÿèöÎ&&| òÿÿÿéöÎ&&y ÿÿÿçöÎ&&| ÿÿÿçöÎ&&y 1ÿÿÿçöÎ& &y FÿÿÿëöÎ&&y [ÿÿÿìöÎ& & pÿÿÿíöÎ&&y …ÿÿÿçöÎ& &y šÿÿÿëÐÎ&ò‚ÿÿÿëÐÎ&&ˆ zÿÿÿëÐÎ&&ˆ ÿÿÿèìÎ&&ˆ ¤ÿÿÿðÕÎ&û&… ¹ÿÿÿéãÎ&&ˆ ÎÿÿÿçßÎ& &Ž ãÿÿÿïÕÎ&&ˆ øÿÿÿíÕÎ& &Ž ÿÿÿëÐÎ& &Ž "ÿÿÿíÐÎ& &Ž 7ÿÿÿíÐÎ& &Ž LÿÿÿìéÎ& &Ž aÿÿÿíÔÎ& &Ž vÿÿÿíÕÎ& &Ž ‹ÿÿÿçÛÎ& &Ž  ÿÿÿïÐÎ&&ˆ µÿÿÿîÐÎ&&ˆ ÊÿÿÿèìÎ& &Ž ßÿÿÿèàÎ&&‹ ôÿÿÿééÎ&&ˆ ÿÿÿçÔÎ&&‹ ÿÿÿçÞÎ&&ˆ 3ÿÿÿçÞÎ& &ˆ HÿÿÿëÐÎ&&ˆ ]ÿÿÿìÕÎ& &Ž rÿÿÿíØÎ&&ˆ ‡ÿÿÿçÜÎ& &ˆ œÿÿÿëÐÎ&ñ”ÿÿÿëÐÎ&&§ yÿÿÿëÐÎ&&§ ŽÿÿÿèìÎ&&§ £ÿÿÿðÕÎ&ú&ž ¸ÿÿÿéãÎ&&§ ÍÿÿÿçßÎ& &± âÿÿÿïÕÎ&&§ ÷ÿÿÿíÕÎ& &± ÿÿÿëÐÎ& &± !ÿÿÿíÐÎ& &± 6ÿÿÿíÐÎ& &± KÿÿÿìéÎ& &± `ÿÿÿíÔÎ& &± uÿÿÿíÕÎ& &± ŠÿÿÿçÛÎ& &± ŸÿÿÿïÐÎ&¤& ´ÿÿÿîÐÎ&&§ ÉÿÿÿèìÎ& &± ÞÿÿÿèãÎ&&» óÿÿÿééÎ&&§ ÿÿÿçÔÎ&&» ÿÿÿçÞÎ&&§ 2ÿÿÿçÞÎ& &± GÿÿÿëÐÎ&&§ \ÿÿÿìÕÎ& &± qÿÿÿíØÎ&&§ †ÿÿÿçÖÎ& &± ›ÿÿÿëÐÎ&òÄÿÿÿëÐÎ&&× zÿÿÿëÐÎ&&× ÿÿÿèìÎ&&× ¤ÿÿÿðÕÎ&û&Í ¹ÿÿÿéãÎ&&× ÎÿÿÿçßÎ& &á ãÿÿÿïÕÎ&&× øÿÿÿíÕÎ& &á ÿÿÿëÐÎ& &á "ÿÿÿíÐÎ& &á 7ÿÿÿíÐÎ& &á LÿÿÿìéÎ& &á aÿÿÿíÔÎ& &á vÿÿÿíÕÎ& &á ‹ÿÿÿçÛÎ& &á  ÿÿÿïÐÎ&&× µÿÿÿîÐÎ&&× ÊÿÿÿèìÎ& &á ßÿÿÿèàÎ&&ë ôÿÿÿééÎ&&× ÿÿÿçÔÎ&&ë ÿÿÿçÞÎ&&× 3ÿÿÿçÞÎ& &á HÿÿÿëÐÎ&&× ]ÿÿÿìÕÎ& &á rÿÿÿíØÎ&&× ‡ÿÿÿçÜÎ& &á œÿÿÿëÐÎ&ñõÿÿÿëÐÎ&&  yÿÿÿëÐÎ&&  ŽÿÿÿèìÎ&&  £ÿÿÿðÕÎ&ú&  ¸ÿÿÿéãÎ&&  ÍÿÿÿçßÎ& &  âÿÿÿïÕÎ&&  ÷ÿÿÿíÕÎ& &  ÿÿÿëÐÎ& &  !ÿÿÿíÐÎ& &  6ÿÿÿíÐÎ& &  KÿÿÿìéÎ& &  `ÿÿÿíÔÎ& &  uÿÿÿíÕÎ& &  ŠÿÿÿçÛÎ& &  ŸÿÿÿïÐÎ& &  µÿÿÿîÐÎ&&  ÉÿÿÿèìÎ& &  ÞÿÿÿèãÎ&&  óÿÿÿééÎ&&  ÿÿÿçÔÎ&&  ÿÿÿçÞÎ&&  2ÿÿÿçÞÎ& &  GÿÿÿëÐÎ&&  \ÿÿÿìÕÎ& &  qÿÿÿíØÎ&&  †ÿÿÿçÖÎ& &  ›ÿÿÿëÐÎ&ò $ÿÿÿëÐÎ&& 8 zÿÿÿëÐÎ&& 8 ÿÿÿèìÎ&& 8 ¤ÿÿÿðÕÎ&û& . ¹ÿÿÿéãÎ&& 8 ÎÿÿÿçßÎ& & B ãÿÿÿïÕÎ&& 8 øÿÿÿíÕÎ& & B ÿÿÿëÐÎ& & B "ÿÿÿíÐÎ& & B 7ÿÿÿíÐÎ& & B LÿÿÿìéÎ& & B aÿÿÿíÔÎ& & B vÿÿÿíÕÎ& & B ‹ÿÿÿçÛÎ& & B  ÿÿÿïÐÎ&& 8 µÿÿÿîÐÎ&& 8 ÊÿÿÿèìÎ& & B ßÿÿÿèàÎ&& L ôÿÿÿééÎ&& 8 ÿÿÿçÔÎ&& L ÿÿÿçÞÎ&& 8 3ÿÿÿçÞÎ& & B HÿÿÿëÐÎ&& 8 ]ÿÿÿìÕÎ& & B rÿÿÿíØÎ&& 8 ‡ÿÿÿçÜÎ& & B œÿÿ êÅ&ó VÿÿÿëêÏ&& g {ÿÿÿëêÏ&& g ÿÿÿêêÏ&& g ¥ÿÿÿíêÏ&& È zÿÿÿéêÏ&& g ÏÿÿÿçêÏ&& q äÿÿÿïêÏ&& g ùÿÿÿíêÏ&& q ÿÿÿëêÏ&& q #ÿÿÿíêÏ&& q 8ÿÿÿðêÏ&& q MÿÿÿìêÏ&& q bÿÿÿíêÏ&& q wÿÿÿíêÏ&& q ŒÿÿÿçêÏ&& q ¡ÿÿÿïêÏ&& g ¶ÿÿÿîêÏ&& g ËÿÿÿèìÏ&& q àÿÿÿèêÏ&& z õÿÿÿéêÏ&& g ÿÿÿçêÏ&& z ÿÿÿçêÏ&& g 4ÿÿÿçêÏ&& q IÿÿÿëêÏ&& g ^ÿÿÿìêÏ&& q sÿÿÿíêÏ&& g ˆÿÿÿçêÏ&& g ÿÿÿëöÎ&õ „ÿÿÿëöÎ&& ˜ |ÿÿÿëöÎ&& ˜ ‘ÿÿÿêöÎ&& ˜ ¦ÿÿÿðöÎ&þ& Ž »ÿÿÿêöÎ&& ˜ ÐÿÿÿçöÎ&& ¢ åÿÿÿïöÎ&& ˜ úÿÿÿíöÎ&& ¢ ÿÿÿëöÎ&& ¢ $ÿÿÿíöÎ&& ¢ 9ÿÿÿíöÎ&& ¢ NÿÿÿìöÎ&& ¢ cÿÿÿîöÎ&& ¢ xÿÿÿíöÎ&& ¢ ÿÿÿçöÎ&& ¢ ¢ÿÿÿïöÎ&& ˜ ·ÿÿÿîöÎ&& ˜ ÌÿÿÿèöÎ&& ¢ áÿÿÿèöÎ&& ¬ öÿÿÿéöÎ&& ˜ ÿÿÿçöÎ&& ¬ ÿÿÿçöÎ&& ˜ 5ÿÿÿçöÎ&& ¢ JÿÿÿëöÎ&& ˜ _ÿÿÿìöÎ&& ¢ tÿÿÿíöÎ&& ˜ ‰ÿÿÿçöÎ&& ¢ žÿÿÿëÐÎ&÷ ¶ÿÿÿëÐÎ& & Ê |ÿÿÿëÐÎ& & Ê ‘ÿÿÿêëÎ& & Ê ¦ÿÿÿðÕÎ&& À »ÿÿÿêãÎ& & Ê ÐÿÿÿçßÎ&& Ô åÿÿÿïÕÎ& & Ê úÿÿÿíÕÎ&& Ô ÿÿÿëÐÎ&& Ô $ÿÿÿíÐÎ&& Ô 9ÿÿÿíÐÎ&& Ô NÿÿÿìéÎ&& Ô cÿÿÿîÔÎ&& Ô xÿÿÿíÕÎ&& Ô ÿÿÿçÛÎ&& Ô ¢ÿÿÿïÐÎ& & Ê ·ÿÿÿîÐÎ& & Ê ÌÿÿÿèéÎ&& Ô áÿÿÿèãÎ& & Þ öÿÿÿééÎ& & Ê ÿÿÿçÔÎ& & Þ ÿÿÿçâÎ& & Ê 5ÿÿÿçâÎ&& Ô JÿÿÿëÐÎ& & Ê _ÿÿÿìÕÎ&& Ô tÿÿÿíØÎ& & Ê ‰ÿÿÿçÜÎ&& Ô žÿÿÿëÐÎ&õ èÿÿÿëÐÎ&& û |ÿÿÿëÐÎ&& û ‘ÿÿÿêëÎ&& û ¦ÿÿÿðÕÎ&þ& ñ »ÿÿÿêãÎ&& û ÐÿÿÿçßÎ&&  åÿÿÿïÕÎ&& û úÿÿÿíÕÎ&&  ÿÿÿëÐÎ&&  $ÿÿÿíÐÎ&&  9ÿÿÿíÐÎ&&  NÿÿÿìéÎ&&  cÿÿÿîÔÎ&&  xÿÿÿíÕÎ&&  ÿÿÿçÛÎ&&  ¢ÿÿÿïÐÎ&& û ·ÿÿÿîÐÎ&& û ÌÿÿÿèéÎ&&  áÿÿÿèãÎ&&  öÿÿÿééÎ&& û ÿÿÿçÔÎ&&  ÿÿÿçâÎ&& û 5ÿÿÿçâÎ&&  JÿÿÿëÐÎ&& û _ÿÿÿìÕÎ&&  tÿÿÿíØÎ&& û ‰ÿÿÿçÜÎ&&  žÿÿ ëÅ&ó ÿÿÿëëÏ&& - {ÿÿÿëëÏ&& - ÿÿÿêëÏ&& - ¥ÿÿÿðëÏ&ü& # ºÿÿÿéëÏ&& - ÏÿÿÿçëÏ&& 7 äÿÿÿïëÏ&& - ùÿÿÿíëÏ&& 7 ÿÿÿëëÏ&& 7 #ÿÿÿíëÏ&& 7 8ÿÿÿðëÏ&& 7 MÿÿÿìëÏ&& 7 bÿÿÿíëÏ&& 7 wÿÿÿíëÏ&& 7 ŒÿÿÿçëÏ&& 7 ¡ÿÿÿïëÏ&& - ¶ÿÿÿîëÏ&& - ËÿÿÿèìÏ&& 7 àÿÿÿèëÏ&& A õÿÿÿéëÏ&& - ÿÿÿçëÏ&& A ÿÿÿçëÏ&& - 4ÿÿÿçëÏ&& 7 IÿÿÿëëÏ&& - ^ÿÿÿìëÏ&& 7 sÿÿÿíëÏ&& - ˆÿÿÿçëÏ&& 7 ÿÿÿëëÏ& JôÿÿÿëëÏ&& U {ÿÿÿëëÏ&& U ÿÿÿêëÏ&& U ¥ÿÿÿíëÏ&& N ÈÿÿÿéëÏ&& U ÏÿÿÿçëÏ&& j äÿÿÿïëÏ&& U ùÿÿÿíëÏ&& j ÿÿÿëëÏ&& j #ÿÿÿíëÏ&& j 8ÿÿÿðëÏ&& j MÿÿÿìëÏ&& j bÿÿÿíëÏ&& j wÿÿÿíëÏ&& j ŒÿÿÿçëÏ&& j ¡ÿÿÿïëÏ&& U ¶ÿÿÿîëÏ&& U ËÿÿÿèìÏ&& j àÿÿÿèëÏ&& c õÿÿÿéëÏ&& U ÿÿÿçëÏ&& U ÿÿÿçëÏ&& U 4ÿÿÿçëÏ&& \ IÿÿÿëëÏ&& U ^ÿÿÿìëÏ&& j sÿÿÿíëÏ&& U ˆÿÿÿçëÏ&& \ ÿÿÿëÐÎ&ö qÿÿÿëÐÎ&&  ~ÿÿÿëÐÎ&&  “ÿÿÿêëÎ&&  ¨ÿÿÿðÕÎ&ÿ& x ½ÿÿÿéãÎ&&  ÒÿÿÿçßÎ&& ” çÿÿÿïÕÎ&&  üÿÿÿíÕÎ&& ” ÿÿÿëÐÎ&& ” &ÿÿÿíÐÎ&& ” ;ÿÿÿíÐÎ&& ” PÿÿÿëéÎ&& ” eÿÿÿîÔÎ&& ” zÿÿÿíÖÎ&& ” ÿÿÿçÛÎ&& ” ¤ÿÿÿïÐÎ&&  ¹ÿÿÿîÐÎ&&  ÎÿÿÿêéÎ&& ” ãÿÿÿèãÎ&&  øÿÿÿçéÎ&&  ÿÿÿçÔÎ&&  "ÿÿÿçÞÎ&&  7ÿÿÿíÞÎ&& † LÿÿÿëÐÎ&&  aÿÿÿìÕÎ&& ” vÿÿÿíØÎ&&  ‹ÿÿÿçÜÎ&& †  ÿÿÿëÐÎ&ø ›ÿÿÿëÐÎ& & © ~ÿÿÿëÐÎ& & © “ÿÿÿêëÎ& & © ¨ÿÿÿðÕÎ&& ¢ ½ÿÿÿéãÎ& & © ÒÿÿÿçßÎ&& ¾ çÿÿÿïÕÎ& & © üÿÿÿíÕÎ&& ¾ ÿÿÿëÐÎ&& ¾ &ÿÿÿíÐÎ&& ¾ ;ÿÿÿíÐÎ&& ¾ PÿÿÿëéÎ&& ¾ eÿÿÿîÔÎ&& ¾ zÿÿÿíÖÎ&& ¾ ÿÿÿçÛÎ&& ¾ ¤ÿÿÿïÐÎ& & © ¹ÿÿÿîÐÎ& & © ÎÿÿÿêéÎ&& ¾ ãÿÿÿèãÎ& & · øÿÿÿçéÎ& & © ÿÿÿçÔÎ& & · "ÿÿÿçÞÎ& & © 7ÿÿÿíÞÎ&& ° LÿÿÿëÐÎ& & © aÿÿÿìÕÎ&& ¾ vÿÿÿíØÎ& & © ‹ÿÿÿçÜÎ&& °  ÿÿÿëÐÎ&ö ÅÿÿÿëÐÎ&& Ó }ÿÿÿëÐÎ&& Ó ’ÿÿÿêëÎ&& Ó §ÿÿÿðÕÎ&ÿ& Ì ¼ÿÿÿéãÎ&& Ó ÑÿÿÿçßÎ&& è æÿÿÿïÕÎ&& Ó ûÿÿÿíÕÎ&& è ÿÿÿëÐÎ&& è %ÿÿÿíÐÎ&& è :ÿÿÿíÐÎ&& è OÿÿÿëèÎ&& è dÿÿÿîÔÎ&& è yÿÿÿíÖÎ&& è ŽÿÿÿçÛÎ&& è £ÿÿÿïÐÎ&& Ó ¸ÿÿÿîÐÎ&& Ó ÍÿÿÿêéÎ&& è âÿÿÿèãÎ&& á ÷ÿÿÿçéÎ&& Ó ÿÿÿçÔÎ&& á !ÿÿÿçÞÎ&& Ó 6ÿÿÿçÞÎ&& Ú KÿÿÿëÐÎ&& Ó `ÿÿÿìÕÎ&& è uÿÿÿíØÎ&& Ó ŠÿÿÿçÖÎ&& Ú ŸÿÿÿíëÏ&ô ïÿÿÿëëÏ&& ý {ÿÿÿëëÏ&& ý ÿÿÿêëÏ&& ý ¥ÿÿÿðëÏ&ý& ö ºÿÿÿéëÏ&& ý ÏÿÿÿçëÏ&&  äÿÿÿïëÏ&& ý ùÿÿÿíëÏ&&  ÿÿÿëëÏ&&  #ÿÿÿíëÏ&&  8ÿÿÿðëÏ&&  MÿÿÿìëÏ&&  bÿÿÿíëÏ&&  wÿÿÿíëÏ&&  ŒÿÿÿçëÏ&&  ¡ÿÿÿïëÏ&& ý ¶ÿÿÿîëÏ&& ý ËÿÿÿèìÏ&&  àÿÿÿèëÏ&&  õÿÿÿéëÏ&& ý ÿÿÿçëÏ&&  ÿÿÿçëÏ&& ý 4ÿÿÿçëÏ&&  IÿÿÿëëÏ&& ý ^ÿÿÿìëÏ&&  sÿÿÿíëÏ&& ý ˆÿÿÿçëÏ&&  ÿÿ ëÅ&ó ÿÿÿëëÏ&& & {ÿÿÿëëÏ&& & ÿÿÿêëÏ&& & ¥ÿÿÿíëÏ&& & ÈÿÿÿéëÏ&& & ÏÿÿÿçëÏ&& ; äÿÿÿïëÏ&& & ùÿÿÿíëÏ& & ;ÿÿÿëëÏ&& ; #ÿÿÿíëÏ&& ; 8ÿÿÿðëÏ&& ; MÿÿÿìëÏ&& ; bÿÿÿíëÏ&& ; wÿÿÿíëÏ&& ; ŒÿÿÿçëÏ&& ; ¡ÿÿÿïëÏ&& & ¶ÿÿÿîëÏ&& & ËÿÿÿèìÏ&& ; àÿÿÿèëÏ&& 4 õÿÿÿéëÏ&& & ÿÿÿçëÏ&& & -ÿÿÿçëÏ&& & 4ÿÿÿçëÏ&& - IÿÿÿëëÏ&& & ^ÿÿÿìëÏ&& ; sÿÿÿíëÏ&& & ˆÿÿÿçëÏ&& - ÿÿÿëÐÎ&õ BÿÿÿëÐÎ&& O |ÿÿÿëÐÎ&& O ‘ÿÿÿêëÎ&& O ¦ÿÿÿðÕÎ&þ& H »ÿÿÿêãÎ&& O ÐÿÿÿçßÎ&& d åÿÿÿïÕÎ&& O úÿÿÿíÕÎ&& d ÿÿÿëÐÎ&& d $ÿÿÿíÐÎ&& d 9ÿÿÿíÐÎ&& d NÿÿÿìéÎ&& d cÿÿÿîÔÎ&& d xÿÿÿíÕÎ&& d ÿÿÿçÛÎ&& d ¢ÿÿÿïÐÎ&& O ·ÿÿÿîÐÎ&& O ÌÿÿÿèéÎ&& d áÿÿÿèãÎ&& ] öÿÿÿééÎ&& O ÿÿÿçÔÎ&& ] ÿÿÿçâÎ&& O 5ÿÿÿçâÎ&& V JÿÿÿëÐÎ&& O _ÿÿÿìÕÎ&& d tÿÿÿíØÎ&& O ‰ÿÿÿçÜÎ&& V žÿÿÿëÐÎ& iðÿÿÿëÐÎ&& o yÿÿÿëÐÎ&& o ŽÿÿÿèìÎ&& o £ÿÿÿðÕÎ&ù& l ¸ÿÿÿéãÎ&& o ÍÿÿÿçßÎ& & u âÿÿÿïÕÎ&& o ÷ÿÿÿíÕÎ& & u ÿÿÿëÐÎ& & u !ÿÿÿíÐÎ& & u 6ÿÿÿíÐÎ& & u KÿÿÿìéÎ& & u `ÿÿÿíÔÎ& & u uÿÿÿíÕÎ& & u ŠÿÿÿçÛÎ& & u ŸÿÿÿïÐÎ&& ´ oÿÿÿîÐÎ&& o ÉÿÿÿèìÎ& & u ÞÿÿÿèãÎ&& r óÿÿÿééÎ&& o ÿÿÿçÔÎ&& r ÿÿÿçÞÎ&& o 2ÿÿÿçÞÎ& & o GÿÿÿëÐÎ&& o \ÿÿÿìÕÎ& & u qÿÿÿíØÎ&& o †ÿÿÿçÖÎ& & o ›ÿÿÿëöÎ&UÿÿÿëõÎ&&&[ xÿÿÿëõÎ&&&[ ÿÿÿèõÎ&&&[ ¢ÿÿÿðöÎ&X& ·ÿÿÿêõÎ&&&[ ÌÿÿÿçõÎ&/&a áÿÿÿïõÎ&&&[ öÿÿÿíõÎ&/&[ ÿÿÿëõÎ&/&a ÿÿÿíõÎ&/&a 5ÿÿÿíõÎ&/&a JÿÿÿìõÎ&/&a _ÿÿÿíõÎ&/&a tÿÿÿíõÎ&/&a ‰ÿÿÿçõÎ&/&a žÿÿÿïõÎ&[&/ ³ÿÿÿîõÎ&[&/ ÈÿÿÿèõÎ&/&a ÝÿÿÿèöÎ&&&^ òÿÿÿéõÎ&&&[ ÿÿÿçõÎ&&&` ÿÿÿçõÎ&&&[ 1ÿÿÿçõÎ&/&[ FÿÿÿëõÎ&&&[ [ÿÿÿìõÎ&/&a pÿÿÿíõÎ&&&[ …ÿÿÿçõÎ&/&[ šÿÿÿëÐÎ&dÿÿÿëÐÎ&(&j zÿÿÿëÐÎ&(&j ÿÿÿèìÎ&(&j ¤ÿÿÿðÕÎ&&g ¹ÿÿÿéãÎ&(&j ÎÿÿÿçßÎ&1&p ãÿÿÿïÕÎ&(&j øÿÿÿíÕÎ&1&p ÿÿÿëÐÎ&1&p "ÿÿÿíÐÎ&1&p 7ÿÿÿíÐÎ&1&p LÿÿÿìéÎ&1&p aÿÿÿíÔÎ&1&p vÿÿÿíÕÎ&1&p ‹ÿÿÿçÛÎ&1&p  ÿÿÿïÐÎ&(&j µÿÿÿîÐÎ&(&j ÊÿÿÿèìÎ&1&p ßÿÿÿèàÎ&(&m ôÿÿÿééÎ&(&j ÿÿÿçÔÎ&(&m ÿÿÿçÞÎ&(&j 3ÿÿÿçÞÎ&1&j HÿÿÿëÐÎ&(&j ]ÿÿÿìÕÎ&1&p rÿÿÿíØÎ&(&j ‡ÿÿÿçÜÎ&1&j œÿÿÿëöÎ&sÿÿÿëöÎ&&&y xÿÿÿëöÎ&&&y ÿÿÿèöÎ&&&y ¢ÿÿÿðöÎ&&v ·ÿÿÿêöÎ&&&y ÌÿÿÿçöÎ&/& áÿÿÿïöÎ&&&y öÿÿÿíöÎ&/& ÿÿÿëöÎ&/& ÿÿÿíöÎ&/& 5ÿÿÿíöÎ&/& JÿÿÿìöÎ&/& _ÿÿÿíöÎ&/& tÿÿÿíöÎ&/& ‰ÿÿÿçöÎ&/& žÿÿÿïöÎ&&&y ³ÿÿÿîöÎ&&&y ÈÿÿÿèöÎ&/& ÝÿÿÿèöÎ&&&| òÿÿÿéöÎ&&&y ÿÿÿçöÎ&&&| ÿÿÿçöÎ&&&y 1ÿÿÿçöÎ&/&y FÿÿÿëöÎ&&&y [ÿÿÿìöÎ&/& pÿÿÿíöÎ&&&y …ÿÿÿçöÎ&/&y šÿÿÿëÐÎ&‚ÿÿÿëÐÎ&(&ˆ zÿÿÿëÐÎ&(&ˆ ÿÿÿèìÎ&(&ˆ ¤ÿÿÿðÕÎ&&… ¹ÿÿÿéãÎ&(&ˆ ÎÿÿÿçßÎ&1&Ž ãÿÿÿïÕÎ&(&ˆ øÿÿÿíÕÎ&1&Ž ÿÿÿëÐÎ&1&Ž "ÿÿÿíÐÎ&1&Ž 7ÿÿÿíÐÎ&1&Ž LÿÿÿìéÎ&1&Ž aÿÿÿíÔÎ&1&Ž vÿÿÿíÕÎ&1&Ž ‹ÿÿÿçÛÎ&1&Ž  ÿÿÿïÐÎ&(&ˆ µÿÿÿîÐÎ&(&ˆ ÊÿÿÿèìÎ&1&Ž ßÿÿÿèàÎ&(&‹ ôÿÿÿééÎ&(&ˆ ÿÿÿçÔÎ&(&‹ ÿÿÿçÞÎ&(&ˆ 3ÿÿÿçÞÎ&1&ˆ HÿÿÿëÐÎ&(&ˆ ]ÿÿÿìÕÎ&1&Ž rÿÿÿíØÎ&(&ˆ ‡ÿÿÿçÜÎ&1&ˆ œÿÿÿëÐÎ&”ÿÿÿëÐÎ&'&© yÿÿÿëÐÎ&'&© ŽÿÿÿèìÎ&'&© £ÿÿÿðÕÎ&'&ž ¸ÿÿÿéãÎ&'&© ÍÿÿÿçßÎ&0&³ âÿÿÿïÕÎ&'&© ÷ÿÿÿíÕÎ&0&³ ÿÿÿëÐÎ&0&³ !ÿÿÿíÐÎ&0&³ 6ÿÿÿíÐÎ&0&³ KÿÿÿìéÎ&0&³ `ÿÿÿíÔÎ&0&³ uÿÿÿíÕÎ&0&³ ŠÿÿÿçÛÎ&0&³ ŸÿÿÿïÐÎ&'&© ´ÿÿÿîÐÎ&'&© ÉÿÿÿèìÎ&0&³ ÞÿÿÿèãÎ&'&½ óÿÿÿééÎ&'&© ÿÿÿçÔÎ&'&½ ÿÿÿçÞÎ&'&© 2ÿÿÿçÞÎ&0&³ GÿÿÿëÐÎ&'&© \ÿÿÿìÕÎ&0&³ qÿÿÿíØÎ&'&© †ÿÿÿçÖÎ&0&³ ›ÿÿÿëÐÎ&ÄÿÿÿëÐÎ&(&Ù zÿÿÿëÐÎ&(&Ù ÿÿÿèìÎ&(&Ù ¤ÿÿÿðÕÎ&&Ï ¹ÿÿÿéãÎ&(&Ù ÎÿÿÿçßÎ&1&ã ãÿÿÿïÕÎ&(&Ù øÿÿÿíÕÎ&1&ã ÿÿÿëÐÎ&1&ã "ÿÿÿíÐÎ&1&ã 7ÿÿÿíÐÎ&1&ã LÿÿÿìéÎ&1&ã aÿÿÿíÔÎ&1&ã vÿÿÿíÕÎ&1&ã ‹ÿÿÿçÛÎ&1&ã  ÿÿÿïÐÎ&(&Ù µÿÿÿîÐÎ&(&Ù ÊÿÿÿèìÎ&1&ã ßÿÿÿèàÎ&(&í ôÿÿÿééÎ&(&Ù ÿÿÿçÔÎ&(&í ÿÿÿçÞÎ&(&Ù 3ÿÿÿçÞÎ&1&ã HÿÿÿëÐÎ&(&Ù ]ÿÿÿìÕÎ&1&ã rÿÿÿíØÎ&(&Ù ‡ÿÿÿçÜÎ&1&ã œÿÿÿëÐÎ&÷ÿÿÿëÐÎ&'&  yÿÿÿëÐÎ&'&  ŽÿÿÿèìÎ&'&  £ÿÿÿðÕÎ&&ÿ ¸ÿÿÿéãÎ&'&  ÍÿÿÿçßÎ&0&  âÿÿÿïÕÎ&'&  ÷ÿÿÿíÕÎ&0&  ÿÿÿëÐÎ&0&  !ÿÿÿíÐÎ&0&  6ÿÿÿíÐÎ&0&  KÿÿÿìéÎ&0&  `ÿÿÿíÔÎ&0&  uÿÿÿíÕÎ&0&  ŠÿÿÿçÛÎ&0&  ŸÿÿÿïÐÎ&'&  ´ÿÿÿîÐÎ&'&  ÉÿÿÿèìÎ&0&  ÞÿÿÿèãÎ&'&  óÿÿÿééÎ&'&  ÿÿÿçÔÎ&'&  ÿÿÿçÞÎ&'&  2ÿÿÿçÞÎ&0&  GÿÿÿëÐÎ&'&  \ÿÿÿìÕÎ&0&  qÿÿÿíØÎ&'&  †ÿÿÿçÖÎ&0&  ›ÿÿÿëÐÎ& &ÿÿÿëÐÎ&(& : zÿÿÿëÐÎ&(& : ÿÿÿèìÎ&(& : ¤ÿÿÿðÕÎ&& 0 ¹ÿÿÿéãÎ&(& : ÎÿÿÿçßÎ&1& D ãÿÿÿïÕÎ&(& : øÿÿÿíÕÎ&1& D ÿÿÿëÐÎ&1& D "ÿÿÿíÐÎ&1& D 7ÿÿÿíÐÎ&1& D LÿÿÿìéÎ&1& D aÿÿÿíÔÎ&1& D vÿÿÿíÕÎ&1& D ‹ÿÿÿçÛÎ&1& D  ÿÿÿïÐÎ&(& : µÿÿÿîÐÎ&(& : ÊÿÿÿèìÎ&1& D ßÿÿÿèàÎ&(& N ôÿÿÿééÎ&(& : ÿÿÿçÔÎ&(& N ÿÿÿçÞÎ&(& : 3ÿÿÿçÞÎ&1& D HÿÿÿëÐÎ&(& : ]ÿÿÿìÕÎ&1& D rÿÿÿíØÎ&(& : ‡ÿÿÿçÜÎ&1& D œÿÿ ê½& VÿÿÿëêÉ&)& h {ÿÿÿëêÉ&)& h ÿÿÿêêÉ&)& h ¥ÿÿÿðêÉ& & ^ ºÿÿÿéêÉ&)& h ÏÿÿÿçêÉ&2& r äÿÿÿïêÉ&)& h ùÿÿÿíêÉ& &2 rÿÿÿëêÉ&2& r #ÿÿÿíêÉ&2& r 8ÿÿÿðêÉ&2& r MÿÿÿìêÉ&2& r bÿÿÿíêÉ&2& r wÿÿÿíêÉ&2& r ŒÿÿÿçêÉ&2& r ¡ÿÿÿïêÉ&)& h ¶ÿÿÿîêÉ&)& h ËÿÿÿèìÉ&2& r àÿÿÿèêÉ&)& { õÿÿÿéêÉ&)& h ÿÿÿçêÉ&)& h ÿÿÿçêÉ&)& h 4ÿÿÿçêÉ&2& r IÿÿÿëêÉ&)& h ^ÿÿÿìêÉ&2& r sÿÿÿíêÉ&)& h ˆÿÿÿçêÉ&2& r ÿÿÿëöÎ& …ÿÿÿëöÎ&+& ™ |ÿÿÿëöÎ&+& ™ ‘ÿÿÿêöÎ&+& ™ ¦ÿÿÿðöÎ&"&  »ÿÿÿêöÎ&+& ™ ÐÿÿÿçöÎ&4& £ åÿÿÿïöÎ&+& ™ úÿÿÿíöÎ&4& £ ÿÿÿëöÎ&4& £ $ÿÿÿíöÎ&4& £ 9ÿÿÿíöÎ&4& £ NÿÿÿìöÎ&4& £ cÿÿÿîöÎ&4& £ xÿÿÿíöÎ&4& £ ÿÿÿçöÎ&4& £ ¢ÿÿÿïöÎ&+& ™ ·ÿÿÿîöÎ&+& ™ ÌÿÿÿèöÎ&4& £ áÿÿÿèöÎ&+& ­ öÿÿÿéöÎ&+& ™ ÿÿÿçöÎ&+& ­ ÿÿÿçöÎ&+& ™ 5ÿÿÿçöÎ&4& £ JÿÿÿëöÎ&+& ™ _ÿÿÿìöÎ&4& £ tÿÿÿíöÎ&+& ™ ‰ÿÿÿçöÎ&4& £ žÿÿÿëÐÎ& ·ÿÿÿëÐÎ&-& Ë |ÿÿÿëÐÎ&-& Ë ‘ÿÿÿêëÎ&-& Ë ¦ÿÿÿðÕÎ&$& Á »ÿÿÿêãÎ&-& Ë ÐÿÿÿçßÎ&6& Õ åÿÿÿïÕÎ&-& Ë úÿÿÿíÕÎ&6& Õ ÿÿÿëÐÎ&6& Õ $ÿÿÿíÐÎ&6& Õ 9ÿÿÿíÐÎ&6& Õ NÿÿÿìéÎ&6& Õ cÿÿÿîÔÎ&6& Õ xÿÿÿíÕÎ&6& Õ ÿÿÿçÛÎ&6& Õ ¢ÿÿÿïÐÎ&-& Ë ·ÿÿÿîÐÎ&-& Ë ÌÿÿÿèéÎ&6& Õ áÿÿÿèãÎ&-& ß öÿÿÿééÎ&-& Ë ÿÿÿçÔÎ&-& ß ÿÿÿçâÎ&-& Ë 5ÿÿÿçâÎ&6& Õ JÿÿÿëÐÎ&-& Ë _ÿÿÿìÕÎ&6& Õ tÿÿÿíØÎ&-& Ë ‰ÿÿÿçÜÎ&6& Õ žÿÿÿëÐÎ& èÿÿÿëÐÎ&+& ü |ÿÿÿëÐÎ&+& ü ‘ÿÿÿêëÎ&+& ü ¦ÿÿÿðÕÎ& ñ& »#ÿÿÿêãÎ&+& ü ÐÿÿÿçßÎ&4&  åÿÿÿïÕÎ&+& ü úÿÿÿíÕÎ& &4 ÿÿÿëÐÎ&4&  $ÿÿÿíÐÎ&4&  9ÿÿÿíÐÎ&4&  NÿÿÿìéÎ&4&  cÿÿÿîÔÎ&4&  xÿÿÿíÕÎ&4&  ÿÿÿçÛÎ&4&  ¢ÿÿÿïÐÎ&+& ü ·ÿÿÿîÐÎ&+& ü ÌÿÿÿèéÎ&4&  áÿÿÿèãÎ&+&  öÿÿÿééÎ&+& ü ÿÿÿçÔÎ&+&  ÿÿÿçâÎ&+& ü 5ÿÿÿçâÎ&4&  JÿÿÿëÐÎ&+& ü _ÿÿÿìÕÎ&4&  tÿÿÿíØÎ&+& ü ‰ÿÿÿçÜÎ&4&  žÿÿ ë½& ÿÿÿëëÉ&)& . {ÿÿÿëëÉ&)& . ÿÿÿêëÉ&)& . ¥ÿÿÿðëÉ& & $ ºÿÿÿéëÉ&)& . ÏÿÿÿçëÉ&2& 8 äÿÿÿïëÉ&)& . ùÿÿÿíëÉ&2& 8 ÿÿÿëëÉ&2& 8 #ÿÿÿíëÉ&2& 8 8ÿÿÿðëÉ&2& 8 MÿÿÿìëÉ&2& 8 bÿÿÿíëÉ&2& 8 wÿÿÿíëÉ&2& 8 ŒÿÿÿçëÉ&2& 8 ¡ÿÿÿïëÉ&)& . ¶ÿÿÿîëÉ&)& . ËÿÿÿèìÉ&2& 8 àÿÿÿèëÉ&)& B õÿÿÿéëÉ&)& . ÿÿÿçëÉ&)& B ÿÿÿçëÉ&)& . 4ÿÿÿçëÉ&2& 8 IÿÿÿëëÉ&)& . ^ÿÿÿìëÉ&2& 8 sÿÿÿíëÉ&)& . ˆÿÿÿçëÉ&2& 8 ÿÿÿëëÉ& JÿÿÿëëÉ&*& V {ÿÿÿëëÉ&*& V ÿÿÿêëÉ&*& V ¥ÿÿÿðëÉ&!& Q ºÿÿÿéëÉ&*& V ÏÿÿÿçëÉ&3& k äÿÿÿïëÉ&*& V ùÿÿÿíëÉ&3& k ÿÿÿëëÉ&3& k #ÿÿÿíëÉ&3& k 8ÿÿÿðëÉ&3& k MÿÿÿìëÉ&3& k bÿÿÿíëÉ&3& k wÿÿÿíëÉ&3& k ŒÿÿÿçëÉ&3& k ¡ÿÿÿïëÉ&*& V ¶ÿÿÿîëÉ&*& V ËÿÿÿèìÉ&3& k àÿÿÿèëÉ&*& d õÿÿÿéëÉ&*& V ÿÿÿçëÉ& &* VÿÿÿçëÉ&*& V 4ÿÿÿçëÉ&3& ] IÿÿÿëëÉ&*& V ^ÿÿÿìëÉ&3& k sÿÿÿíëÉ&*& V ˆÿÿÿçëÉ&3& ] ÿÿÿëÐÎ& rÿÿÿëÐÎ&,& € ~ÿÿÿëÐÎ&,& € “ÿÿÿêëÎ&,& € ¨ÿÿÿðÕÎ&#& y ½ÿÿÿéãÎ&,& € ÒÿÿÿçßÎ&5& • çÿÿÿïÕÎ&,& € üÿÿÿíÕÎ&5& • ÿÿÿëÐÎ&5& • &ÿÿÿíÐÎ&5& • ;ÿÿÿíÐÎ&5& • PÿÿÿëéÎ&5& • eÿÿÿîÔÎ&5& • zÿÿÿíÖÎ&5& • ÿÿÿçÛÎ&5& • ¤ÿÿÿïÐÎ&,& € ¹ÿÿÿîÐÎ&,& € ÎÿÿÿêéÎ&5& • ãÿÿÿèãÎ&,& Ž øÿÿÿçéÎ&,& € ÿÿÿçÔÎ&,& Ž "ÿÿÿçÞÎ&,& € 7ÿÿÿíÞÎ&5& ‡ LÿÿÿëÐÎ&,& € aÿÿÿìÕÎ&5& • vÿÿÿíØÎ&,& € ‹ÿÿÿçÜÎ&5& ‡  ÿÿÿëÐÎ& œÿÿÿëÐÎ&.& ª ~ÿÿÿëÐÎ&.& ª “ÿÿÿêëÎ&.& ª ¨ÿÿÿðÕÎ&%& £ ½ÿÿÿéãÎ&.& ª ÒÿÿÿçßÎ&7& ¿ çÿÿÿïÕÎ&.& ª üÿÿÿíÕÎ&7& ¿ ÿÿÿëÐÎ&7& ¿ &ÿÿÿíÐÎ&7& ¿ ;ÿÿÿíÐÎ&7& ¿ PÿÿÿëéÎ&7& ¿ eÿÿÿîÔÎ&7& ¿ zÿÿÿíÖÎ&7& ¿ ÿÿÿçÛÎ&7& ¿ ¤ÿÿÿïÐÎ&.& ª ¹ÿÿÿîÐÎ&.& ª ÎÿÿÿêéÎ&7& ¿ ãÿÿÿèãÎ&.& ¸ øÿÿÿçéÎ&.& ª ÿÿÿçÔÎ&.& ¸ "ÿÿÿçÞÎ&.& ª 7ÿÿÿíÞÎ&7& ± LÿÿÿëÐÎ&.& ª aÿÿÿìÕÎ&7& ¿ vÿÿÿíØÎ&.& ª ‹ÿÿÿçÜÎ&7& ±  ÿÿÿëÐÎ& ÆÿÿÿëÐÎ&,& Ô }ÿÿÿëÐÎ&,& Ô ’ÿÿÿêëÎ&,& Ô §ÿÿÿðÕÎ&#& Í ¼ÿÿÿéãÎ&,& Ô ÑÿÿÿçßÎ&5& é æÿÿÿïÕÎ&,& Ô ûÿÿÿíÕÎ&5& é ÿÿÿëÐÎ&5& é %ÿÿÿíÐÎ&5& é :ÿÿÿíÐÎ&5& é OÿÿÿëèÎ&5& é dÿÿÿîÔÎ&5& é yÿÿÿíÖÎ&5& é ŽÿÿÿçÛÎ&5& é £ÿÿÿïÐÎ&,& Ô ¸ÿÿÿîÐÎ&,& Ô ÍÿÿÿêéÎ&5& é âÿÿÿèãÎ&,& â ÷ÿÿÿçéÎ&,& Ô ÿÿÿçÔÎ&,& â !ÿÿÿçÞÎ&,& Ô 6ÿÿÿçÞÎ&5& Û KÿÿÿëÐÎ&,& Ô `ÿÿÿìÕÎ&5& é uÿÿÿíØÎ&,& Ô ŠÿÿÿçÖÎ&5& Û ŸÿÿÿíëÉ& ðÿÿÿëëÉ&*& þ {ÿÿÿëëÉ&*& þ ÿÿÿêëÉ&*& þ ¥ÿÿÿðëÉ&!& ÷ ºÿÿÿéëÉ&*& þ ÏÿÿÿçëÉ&3&  äÿÿÿïëÉ&*& þ ùÿÿÿíëÉ&3&  ÿÿÿëëÉ&3&  #ÿÿÿíëÉ&3&  8ÿÿÿðëÉ&3&  MÿÿÿìëÉ&3&  bÿÿÿíëÉ&3&  wÿÿÿíëÉ&3&  ŒÿÿÿçëÉ&3&  ¡ÿÿÿïëÉ&*& þ ¶ÿÿÿîëÉ&*& þ ËÿÿÿèìÉ&3&  àÿÿÿèëÉ&*&  õÿÿÿéëÉ&*& þ ÿÿÿçëÉ&*&  ÿÿÿçëÉ&*& þ 4ÿÿÿçëÉ&3&  IÿÿÿëëÉ&*& þ ^ÿÿÿìëÉ&3&  sÿÿÿíëÉ&*& þ ˆÿÿÿçëÉ&3&  ÿÿ ë½& ÿÿÿëëÉ&!& ' {ÿÿÿëëÉ&)& ' ÿÿÿêëÉ&)& ' ¥ÿÿÿðëÉ&!& º ÿÿÿéëÉ&)& ' ÏÿÿÿçëÉ&2& < äÿÿÿïëÉ&!& ù 'ÿÿÿíëÉ& <& 3ÿÿÿëëÉ&2& < #ÿÿÿíëÉ&2& < 8ÿÿÿðëÉ&2& < MÿÿÿìëÉ&2& < bÿÿÿíëÉ&2& < wÿÿÿíëÉ&2& < ŒÿÿÿçëÉ&2& < ¡ÿÿÿïëÉ&)& ' ¶ÿÿÿîëÉ&)& ' ËÿÿÿèìÉ&2& < àÿÿÿèëÉ&)& 5 õÿÿÿéëÉ&)& ' ÿÿÿçëÉ&)& 5 ÿÿÿçëÉ&)& ' 4ÿÿÿçëÉ&2& . IÿÿÿëëÉ&)& ' ^ÿÿÿìëÉ&2& < sÿÿÿíëÉ&)& ' ˆÿÿÿçëÉ&2& . ÿÿÿëÐÎ& CÿÿÿëÐÎ&+& P |ÿÿÿëÐÎ&+& P ‘ÿÿÿêëÎ&+& P ¦ÿÿÿðÕÎ&"& I »ÿÿÿêãÎ&+& P ÐÿÿÿçßÎ&4& e åÿÿÿïÕÎ&+& P úÿÿÿíÕÎ&4& e ÿÿÿëÐÎ&4& e $ÿÿÿíÐÎ&4& e 9ÿÿÿíÐÎ&4& e NÿÿÿìéÎ&4& e cÿÿÿîÔÎ&4& e xÿÿÿíÕÎ&4& e ÿÿÿçÛÎ&4& e ¢ÿÿÿïÐÎ&+& P ·ÿÿÿîÐÎ&+& P ÌÿÿÿèéÎ&4& e áÿÿÿèãÎ&+& ^ öÿÿÿééÎ&+& P ÿÿÿçÔÎ&+& ^ ÿÿÿçâÎ&+& P 5ÿÿÿçâÎ&4& W JÿÿÿëÐÎ&+& P _ÿÿÿìÕÎ&4& e tÿÿÿíØÎ&+& P ‰ÿÿÿçÜÎ&4& W žÿÿÿëÐÎ& iÿÿÿëÐÎ&&& o yÿÿÿëÐÎ&&& o ŽÿÿÿèìÎ&&& o £ÿÿÿðÕÎ&& l ¸ÿÿÿéãÎ&&& o ÍÿÿÿçßÎ&/& u âÿÿÿïÕÎ&&& o ÷ÿÿÿíÕÎ&/& u ÿÿÿëÐÎ&/& u !ÿÿÿíÐÎ&/& u 6ÿÿÿíÐÎ&/& u KÿÿÿìéÎ&/& u `ÿÿÿíÔÎ&/& u uÿÿÿíÕÎ&/& u ŠÿÿÿçÛÎ&/& u ŸÿÿÿïÐÎ&&& o ´ÿÿÿîÐÎ&&& o ÉÿÿÿèìÎ&/& u ÞÿÿÿèãÎ&&& r óÿÿÿééÎ&&& o ÿÿÿçÔÎ&&& r ÿÿÿçÞÎ&&& o 2ÿÿÿçÞÎ&/& o GÿÿÿëÐÎ&&& o \ÿÿÿìÕÎ&/& u qÿÿÿíØÎ&&& o †ÿÿÿçÖÎ&/& o ›ÿÿÿëöÎ&9VÿÿÿëõÎ&J&\ xÿÿÿëõÎ&J&\ ÿÿÿèõÎ&J&\ ¢ÿÿÿðöÎ&A&Y ·ÿÿÿêõÎ&J&\ ÌÿÿÿçõÎ&S&b áÿÿÿïõÎ&J&\ öÿÿÿíõÎ&S&b ÿÿÿëõÎ&S&b ÿÿÿíõÎ&S&b 5ÿÿÿíõÎ&S&b JÿÿÿìõÎ&S&b _ÿÿÿíõÎ&S&b tÿÿÿíõÎ&S&b ‰ÿÿÿçõÎ&S&b žÿÿÿïõÎ&J&\ ³ÿÿÿîõÎ&J&\ ÈÿÿÿèõÎ&S&b ÝÿÿÿèõÎ&J&_ òÿÿÿéõÎ&J&\ ÿÿÿçõÎ&_& *JÿÿÿçõÎ&J&\ 1ÿÿÿçõÎ&S&\ FÿÿÿëõÎ&J&\ [ÿÿÿìõÎ&S&b pÿÿÿíõÎ&J&\ …ÿÿÿçõÎ&S&\ šÿÿÿëÑÎ&e:ÿÿÿëÐÎ&L&k zÿÿÿëÐÎ&L&k ÿÿÿèìÎ&L&k ¤ÿÿÿðÕÎ&C&h ¹ÿÿÿéãÎ&L&k ÎÿÿÿçßÎ&U&q ãÿÿÿïÕÎ&L&k øÿÿÿíÕÎ&U&q ÿÿÿëÑÎ&U&q "ÿÿÿíÑÎ&U&q 7ÿÿÿíÑÎ&U&q LÿÿÿìéÎ&U&q aÿÿÿíÔÎ&U&q vÿÿÿíÕÎ&U&q ‹ÿÿÿçÛÎ&U&q  ÿÿÿïÐÎ&L&k µÿÿÿîÐÎ&L&k ÊÿÿÿèìÎ&U&q ßÿÿÿèàÍ&L&n ôÿÿÿééÎ&L&k ÿÿÿçÔÍ&L&n ÿÿÿçÞÎ&L&k 3ÿÿÿçÞÎ&U&k HÿÿÿëÐÎ&L&k ]ÿÿÿìÕÎ&U&q rÿÿÿíØÎ&L&k ‡ÿÿÿçÜÎ&U&k œÿÿÿëöÎ&8tÿÿÿëöÎ&J&z xÿÿÿëöÎ&J&z ÿÿÿèöÎ&J&z ¢ÿÿÿðöÎ&A&w ·ÿÿÿêöÎ&J&z ÌÿÿÿçöÎ&S&€ áÿÿÿïöÎ&J&z öÿÿÿíöÎ&S&€ ÿÿÿëöÎ&S&€ ÿÿÿíöÎ&S&€ 5ÿÿÿíöÎ&S&€ JÿÿÿìöÎ&S&€ _ÿÿÿíöÎ&S&€ tÿÿÿíöÎ&S&€ ‰ÿÿÿçöÎ&S&€ žÿÿÿïöÎ&J&z ³ÿÿÿîöÎ&J&z ÈÿÿÿèöÎ&S&€ ÝÿÿÿèöÎ&J&} òÿÿÿéöÎ&J&z ÿÿÿçöÎ&J&} ÿÿÿçöÎ&J&z 1ÿÿÿçöÎ&S&z FÿÿÿëöÎ&J&z [ÿÿÿìöÎ&S&€ pÿÿÿíöÎ&J&z …ÿÿÿçöÎ&S&z šÿÿÿëÐÎ&:ƒÿÿÿëÐÎ&L&‰ zÿÿÿëÐÎ&L&‰ ÿÿÿèìÎ&L&‰ ¤ÿÿÿðÕÎ&C&† ¹ÿÿÿéãÎ&L&‰ ÎÿÿÿçßÎ&U& ãÿÿÿïÕÎ&L&‰ øÿÿÿíÕÎ&U& ÿÿÿëÐÎ&U& "ÿÿÿíÐÎ&U& 7ÿÿÿíÐÎ&U& LÿÿÿìéÎ&U& aÿÿÿíÔÎ&U& vÿÿÿíÕÎ&U& ‹ÿÿÿçÛÎ&U&  ÿÿÿïÐÎ&L&‰ µÿÿÿîÐÎ&L&‰ ÊÿÿÿèìÎ&U& ßÿÿÿèàÎ&L&Œ ôÿÿÿééÎ&L&‰ ÿÿÿçÔÎ&L&Œ ÿÿÿçÞÎ&L&‰ 3ÿÿÿçÞÎ&U&‰ HÿÿÿëÐÎ&L&‰ ]ÿÿÿìÕÎ&U& rÿÿÿíØÎ&L&‰ ‡ÿÿÿçÜÎ&U&‰ œÿÿÿëÐÎ&9“ÿÿÿëÐÎ&K&¦ yÿÿÿëÐÎ&K&¦ ŽÿÿÿèìÎ&K&¦ £ÿÿÿðÕÎ&B& ¸ÿÿÿéãÎ&K&¦ ÍÿÿÿçßÎ&T&° âÿÿÿïÕÎ&K&¦ ÷ÿÿÿíÕÎ&¤& KÿÿÿëÐÎ&T&° !ÿÿÿíÐÎ&T&° 6ÿÿÿíÐÎ&T&° KÿÿÿìéÎ&T&° `ÿÿÿíÔÎ&T&° uÿÿÿíÕÎ&T&° ŠÿÿÿçÛÎ&T&° ŸÿÿÿïÐÎ&K&¦ ´ÿÿÿîÐÎ&K&¦ ÉÿÿÿèìÎ&T&° ÞÿÿÿèãÎ&K&º óÿÿÿééÎ&K&¦ ÿÿÿçÔÎ&K&º ÿÿÿçÞÎ&K&¦ 2ÿÿÿçÞÎ&T&° GÿÿÿëÐÎ&K&¦ \ÿÿÿìÕÎ&T&° qÿÿÿíØÎ&K&¦ †ÿÿÿçÖÎ&T&° ›ÿÿÿëÐÎ&:ÃÿÿÿëÐÎ&L&Ö zÿÿÿëÐÎ&L&Ö ÿÿÿèìÎ&L&Ö ¤ÿÿÿðÕÎ&C&Ì ¹ÿÿÿéãÎ&L&Ö ÎÿÿÿçßÎ&U&à ãÿÿÿïÕÎ&L&Ö øÿÿÿíÕÎ&U&à ÿÿÿëÐÎ&U&à "ÿÿÿíÐÎ&U&à 7ÿÿÿíÐÎ&U&à LÿÿÿìéÎ&U&à aÿÿÿíÔÎ&U&à vÿÿÿíÕÎ&U&à ‹ÿÿÿçÛÎ&U&à  ÿÿÿïÐÎ&L&Ö µÿÿÿîÐÎ&L&Ö ÊÿÿÿèìÎ&U&à ßÿÿÿèàÎ&L&ê ôÿÿÿééÎ&L&Ö ÿÿÿçÔÎ&L&ê ÿÿÿçÞÎ&L&Ö 3ÿÿÿçÞÎ&U&à HÿÿÿëÐÎ&L&Ö ]ÿÿÿìÕÎ&U&à rÿÿÿíØÎ&L&Ö ‡ÿÿÿçÜÎ&U&à œÿÿÿëÐÎ&9ôÿÿÿëÐÎ&K&  yÿÿÿëÐÎ&K&  ŽÿÿÿèìÎ&K&  £ÿÿÿðÕÎ&B&þ ¸ÿÿÿéãÎ&K&  ÍÿÿÿçßÎ&T&  âÿÿÿïÕÎ&K&  ÷ÿÿÿíÕÎ&T&  ÿÿÿëÐÎ&T&  !ÿÿÿíÐÎ&T&  6ÿÿÿíÐÎ&T&  KÿÿÿìéÎ&T&  `ÿÿÿíÔÎ&T&  uÿÿÿíÕÎ&T&  ŠÿÿÿçÛÎ&T&  ŸÿÿÿïÐÎ&K&  ´ÿÿÿîÐÎ&K&  ÉÿÿÿèìÎ&T&  ÞÿÿÿèãÎ&K&  óÿÿÿééÎ&K&  ÿÿÿçÔÎ&K&  ÿÿÿçÞÎ&K&  2ÿÿÿçÞÎ&T&  GÿÿÿëÐÎ&K&  \ÿÿÿìÕÎ&T&  qÿÿÿíØÎ&K&  †ÿÿÿçÖÎ&T&  ›ÿÿÿëÐÎ&: #ÿÿÿëÐÎ&L& 7 zÿÿÿëÐÎ&L& 7 ÿÿÿèìÎ&L& 7 ¤ÿÿÿðÕÎ&C& - ¹ÿÿÿéãÎ&L& 7 ÎÿÿÿçßÎ&U& A ãÿÿÿïÕÎ&L& 7 øÿÿÿíÕÎ&U& A ÿÿÿëÐÎ&U& A "ÿÿÿíÐÎ&U& A 7ÿÿÿíÐÎ&U& A LÿÿÿìéÎ&U& A aÿÿÿíÔÎ&U& A vÿÿÿíÕÎ&U& A ‹ÿÿÿçÛÎ&U& A  ÿÿÿïÐÎ&L& 7 µÿÿÿîÐÎ&L& 7 ÊÿÿÿèìÎ&U& A ßÿÿÿèàÎ&L& K ôÿÿÿééÎ&L& 7 ÿÿÿçÔÎ&L& K ÿÿÿçÞÎ&L& 7 3ÿÿÿçÞÎ&U& A HÿÿÿëÐÎ&L& 7 ]ÿÿÿìÕÎ&U& A rÿÿÿíØÎ&L& 7 ‡ÿÿÿçÜÎ&U& A œÿÿ ê½&; UÿÿÿëêÊ&M& h {ÿÿÿëêÊ&M& h ÿÿÿêêÊ&M& h ¥ÿÿÿðêÊ&D& ^ ºÿÿÿéêÊ&M& h ÏÿÿÿçêÉ&V& r äÿÿÿïêÊ&M& h ùÿÿÿíêÉ&V& r ÿÿÿëêÉ&V& r #ÿÿÿíêÉ&V& r 8ÿÿÿðêÉ&V& r MÿÿÿìêÉ&V& r bÿÿÿíêÉ&V& r wÿÿÿíêÉ&V& r ŒÿÿÿçêÉ&V& r ¡ÿÿÿïêÊ&M& h ¶ÿÿÿîêÊ&M& h ËÿÿÿèìÉ&V& r àÿÿÿèêÊ&M& { õÿÿÿéêÊ&M& h ÿÿÿçêÊ&M& { ÿÿÿçêÊ&M& h 4ÿÿÿçêÉ&V& r IÿÿÿëêÊ&M& h ^ÿÿÿìêÉ&V& r sÿÿÿíêÊ&M& h ˆÿÿÿçêÉ&V& r ÿÿÿëöÎ&= …ÿÿÿëöÎ&O& ™ |ÿÿÿëöÎ&O& ™ ‘ÿÿÿêöÎ&O& ™ ¦ÿÿÿðöÎ&F&  »ÿÿÿêöÎ&O& ™ ÐÿÿÿçöÎ&X& £ åÿÿÿïöÎ&O& ™ úÿÿÿíöÎ&X& £ ÿÿÿëöÎ&X& £ $ÿÿÿíöÎ&X& £ 9ÿÿÿíöÎ&X& £ NÿÿÿìöÎ&X& £ cÿÿÿîöÎ&X& £ xÿÿÿíöÎ&X& £ ÿÿÿçöÎ&X& £ ¢ÿÿÿïöÎ&O& ™ ·ÿÿÿîöÎ&O& ™ ÌÿÿÿèöÎ&X& £ áÿÿÿèöÎ&O& ­ öÿÿÿéöÎ&O& ™ ÿÿÿçöÎ&O& ­ ÿÿÿçöÎ&O& ™ 5ÿÿÿçöÎ&X& £ JÿÿÿëöÎ&O& ™ _ÿÿÿìöÎ&X& £ tÿÿÿíöÎ&O& ™ ‰ÿÿÿçöÎ&X& £ žÿÿÿëÐÎ&? ·ÿÿÿëÐÎ&Q& Ë |ÿÿÿëÐÎ&Q& Ë ‘ÿÿÿêëÎ&Q& Ë ¦ÿÿÿðÕÎ&H& Á »ÿÿÿêãÎ&Q& Ë ÐÿÿÿçßÎ&Z& Õ åÿÿÿïÕÎ&Q& Ë úÿÿÿíÕÎ&Z& Õ ÿÿÿëÐÎ&Z& Õ $ÿÿÿíÐÎ&Z& Õ 9ÿÿÿíÐÎ&Z& Õ NÿÿÿìéÎ&Z& Õ cÿÿÿîÔÎ&Z& Õ xÿÿÿíÕÎ&Z& Õ ÿÿÿçÛÎ&Z& Õ ¢ÿÿÿïÐÎ&Q& Ë ·ÿÿÿîÐÎ&Q& Ë ÌÿÿÿèéÎ&Z& Õ áÿÿÿèãÎ&Q& ß öÿÿÿééÎ&Q& Ë ÿÿÿçÔÎ&Q& ß ÿÿÿçâÎ&Q& Ë 5ÿÿÿçâÎ&Z& Õ JÿÿÿëÐÎ&Q& Ë _ÿÿÿìÕÎ&Z& Õ tÿÿÿíØÎ&Q& Ë ‰ÿÿÿçÜÎ&Z& Õ žÿÿÿëÐÎ&= éÿÿÿëÐÎ&O& ü |ÿÿÿëÐÎ&O& ü ‘ÿÿÿêëÎ&O& ü ¦ÿÿÿðÕÎ&F& ò »ÿÿÿêãÎ&O& ü ÐÿÿÿçßÎ&X&  åÿÿÿïÕÎ&O& ü úÿÿÿíÕÎ&X&  ÿÿÿëÐÎ&X&  $ÿÿÿíÐÎ&X&  9ÿÿÿíÐÎ&X&  NÿÿÿìéÎ&X&  cÿÿÿîÔÎ&X&  xÿÿÿíÕÎ&X&  ÿÿÿçÛÎ&X&  ¢ÿÿÿïÐÎ&O& ü ·ÿÿÿîÐÎ&O& ü ÌÿÿÿèéÎ&X&  áÿÿÿèãÎ&O&  öÿÿÿééÎ&O& ü ÿÿÿçÔÎ&O&  ÿÿÿçâÎ&O& ü 5ÿÿÿçâÎ&X&  JÿÿÿëÐÎ&O& ü _ÿÿÿìÕÎ&X&  tÿÿÿíØÎ&O& ü ‰ÿÿÿçÜÎ&X&  žÿÿ ë½&; ÿÿÿëëÊ&M& . {ÿÿÿëëÊ&M& . ÿÿÿêëÊ&M& . ¥ÿÿÿðëÊ&D& $ ºÿÿÿéëÊ&M& . ÏÿÿÿçëÉ&V& 8 äÿÿÿïëÊ&M& . ùÿÿÿíëÉ&V& 8 ÿÿÿëëÉ&V& 8 #ÿÿÿíëÉ&V& 8 8ÿÿÿðëÉ&V& 8 MÿÿÿìëÉ&V& 8 bÿÿÿíëÉ&V& 8 wÿÿÿíëÉ&V& 8 ŒÿÿÿçëÉ&V& 8 ¡ÿÿÿïëÊ&M& . ¶ÿÿÿîëÊ&M& . ËÿÿÿèìÉ&V& 8 àÿÿÿèëÊ&M& B õÿÿÿéëÊ&M& . ÿÿÿçëÊ&M& B ÿÿÿçëÊ&M& . 4ÿÿÿçëÉ&V& 8 IÿÿÿëëÊ&M& . ^ÿÿÿìëÉ&V& 8 sÿÿÿíëÊ&M& . ˆÿÿÿçëÉ&V& 8 ÿÿÿëëÊ&< IÿÿÿëëÊ&N& T {ÿÿÿëëÊ&N& T ÿÿÿêëÊ&N& T ¥ÿÿÿðëÊ&E& O ºÿÿÿéëÊ&N& T ÏÿÿÿçëÉ&W& i äÿÿÿïëÊ&N& T ùÿÿÿíëÉ&W& i ÿÿÿëëÉ&W& i #ÿÿÿíëÉ&W& i 8ÿÿÿðëÉ&W& i MÿÿÿìëÉ&W& i bÿÿÿíëÉ&W& i wÿÿÿíëÉ&W& i ŒÿÿÿçëÉ&W& i ¡ÿÿÿïëÊ&N& T ¶ÿÿÿîëÊ&N& T ËÿÿÿèìÉ&W& i àÿÿÿèëÊ&N& b õÿÿÿéëÊ&N& T ÿÿÿçëÊ&N& T ÿÿÿçëÊ&N& T 4ÿÿÿçëÉ&W& [ IÿÿÿëëÊ&N& T ^ÿÿÿìëÉ&W& i sÿÿÿíëÊ&N& T ˆÿÿÿçëÉ&W& [ ÿÿÿëÐÎ&> pÿÿÿëÐÎ&P& ~ ~ÿÿÿëÐÎ&P& ~ “ÿÿÿêëÎ&P& ~ ¨ÿÿÿðÕÎ&G& w ½ÿÿÿéãÎ&P& ~ ÒÿÿÿçßÎ&Y& “ çÿÿÿïÕÎ&P& ~ üÿÿÿíÕÎ&Y& “ ÿÿÿëÐÎ&Y& “ &ÿÿÿíÐÎ&Y& “ ;ÿÿÿíÐÎ&Y& “ PÿÿÿëéÎ&Y& “ eÿÿÿîÔÎ&Y& “ zÿÿÿíÖÎ&Y& “ ÿÿÿçÛÎ&Y& “ ¤ÿÿÿïÐÎ&P& ~ ¹ÿÿÿîÐÎ&P& ~ ÎÿÿÿêéÎ&Y& “ ãÿÿÿèãÎ&P& Œ øÿÿÿçéÎ&P& ~ ÿÿÿçÔÎ&P& Œ "ÿÿÿçÞÎ&P& ~ 7ÿÿÿíÞÎ&Y& … LÿÿÿëÐÎ&P& ~ aÿÿÿìÕÎ&Y& “ vÿÿÿíØÎ&P& ~ ‹ÿÿÿçÜÎ&Y& …  ÿÿÿëÐÎ&@ šÿÿÿëÐÎ&R& ¨ ~ÿÿÿëÐÎ&R& ¨ “ÿÿÿêëÎ&R& ¨ ¨ÿÿÿðÕÎ&I& ¡ ½ÿÿÿéãÎ&R& ¨ ÒÿÿÿçßÎ&[& ½ çÿÿÿïÕÎ&R& ¨ üÿÿÿíÕÎ&[& ½ ÿÿÿëÐÎ&[& ½ &ÿÿÿíÐÎ&[& ½ ;ÿÿÿíÐÎ&[& ½ PÿÿÿëéÎ&[& ½ eÿÿÿîÔÎ&[& ½ zÿÿÿíÖÎ&[& ½ ÿÿÿçÛÎ&[& ½ ¤ÿÿÿïÐÎ&R& ¨ ¹ÿÿÿîÐÎ&R& ¨ ÎÿÿÿêéÎ&[& ½ ãÿÿÿèãÎ&R& ¶ øÿÿÿçéÎ&R& ¨ ÿÿÿçÔÎ&R& ¶ "ÿÿÿçÞÎ&R& ¨ 7ÿÿÿíÞÎ&[& ¯ LÿÿÿëÐÎ&R& ¨ aÿÿÿìÕÎ&[& ½ vÿÿÿíØÎ&R& ¨ ‹ÿÿÿçÜÎ&[& ¯  ÿÿÿëÐÎ&> ÄÿÿÿëÐÎ&P& Ò }ÿÿÿëÐÎ&P& Ò ’ÿÿÿêëÎ&P& Ò §ÿÿÿðÕÎ&G& Ë ¼ÿÿÿéãÎ&P& Ò ÑÿÿÿçßÎ&Y& ç æÿÿÿïÕÎ&P& Ò ûÿÿÿíÕÎ&Y& ç ÿÿÿëÐÎ&Y& ç %ÿÿÿíÐÎ&Y& ç :ÿÿÿíÐÎ&Y& ç OÿÿÿëèÎ&Y& ç dÿÿÿîÔÎ&Y& ç yÿÿÿíÖÎ&Y& ç ŽÿÿÿçÛÎ&Y& ç £ÿÿÿïÐÎ&P& Ò ¸ÿÿÿîÐÎ&P& Ò ÍÿÿÿêéÎ&Y& ç âÿÿÿèãÎ&P& à ÷ÿÿÿçéÎ&P& Ò ÿÿÿçÔÎ&P& à !ÿÿÿçÞÎ&P& Ò 6ÿÿÿçÞÎ&Y& Ù KÿÿÿëÐÎ&P& Ò `ÿÿÿìÕÎ&Y& ç uÿÿÿíØÎ&P& Ò ŠÿÿÿçÖÎ&Y& Ù ŸÿÿÿíëÊ&< îÿÿÿëëÊ&N& ü {ÿÿÿëëÊ&N& ü ÿÿÿêëÊ&N& ü ¥ÿÿÿðëÊ&E& õ ºÿÿÿéëÊ&N& ü ÏÿÿÿçëÉ&W&  äÿÿÿïëÊ&N& ü ùÿÿÿíëÉ&W&  ÿÿÿëëÉ&W&  #ÿÿÿíëÉ&W&  8ÿÿÿðëÉ&W&  MÿÿÿìëÉ&W&  bÿÿÿíëÉ&W&  wÿÿÿíëÉ&W&  ŒÿÿÿçëÉ&W&  ¡ÿÿÿïëÊ&N& ü ¶ÿÿÿîëÊ&N& ü ËÿÿÿèìÉ&W&  àÿÿÿèëÊ&N&  õÿÿÿéëÊ&N& ü ÿÿÿçëÊ&N&  ÿÿÿçëÊ&N& ü 4ÿÿÿçëÉ&W&  IÿÿÿëëÊ&N& ü ^ÿÿÿìëÉ&W&  sÿÿÿíëÊ&N& ü ˆÿÿÿçëÉ&W&  ÿÿ ë½&; ÿÿÿëëÊ& %& {MÿÿÿëëÊ&M& % ÿÿÿêëÊ&M& % ¥ÿÿÿðëÊ&D&  ºÿÿÿéëÊ&M& % ÏÿÿÿçëÉ&V& : äÿÿÿïëÊ&M& % ùÿÿÿíëÉ&V& : ÿÿÿëëÉ&V& : #ÿÿÿíëÉ&V& : 8ÿÿÿðëÉ&V& : MÿÿÿìëÉ&V& : bÿÿÿíëÉ&V& : wÿÿÿíëÉ&V& : ŒÿÿÿçëÉ&V& : ¡ÿÿÿïëÊ&M& % ¶ÿÿÿîëÊ&M& % ËÿÿÿèìÉ&V& : àÿÿÿèëÊ& 3& õMÿÿÿéëÊ&M& % ÿÿÿçëÊ&M& 3 ÿÿÿçëÊ&M& % 4ÿÿÿçëÉ&V& , IÿÿÿëëÊ&M& % ^ÿÿÿìëÉ&V& : sÿÿÿíëÊ&M& % ˆÿÿÿçëÉ&V& , ÿÿÿëÐÎ&= AÿÿÿëÐÎ&O& N |ÿÿÿëÐÎ&O& N ‘ÿÿÿêëÎ&O& N ¦ÿÿÿðÕÎ&F& G »ÿÿÿêãÎ&O& N ÐÿÿÿçßÎ&X& c åÿÿÿïÕÎ&O& N úÿÿÿíÕÎ&X& c ÿÿÿëÐÎ&X& c $ÿÿÿíÐÎ&X& c 9ÿÿÿíÐÎ&X& c NÿÿÿìéÎ&X& c cÿÿÿîÔÎ&X& c xÿÿÿíÕÎ&X& c ÿÿÿçÛÎ&X& c ¢ÿÿÿïÐÎ&O& N ·ÿÿÿîÐÎ&O& N ÌÿÿÿèéÎ&X& c áÿÿÿèãÎ&O& \ öÿÿÿééÎ&O& N ÿÿÿçÔÎ&O& \ ÿÿÿçâÎ&O& N 5ÿÿÿçâÎ&X& U JÿÿÿëÐÎ&O& N _ÿÿÿìÕÎ&X& c tÿÿÿíØÎ&O& N ‰ÿÿÿçÜÎ&X& U žÿÿÿëÐÎ&8 jÿÿÿëÐÎ&J& p yÿÿÿëÐÎ&J& p ŽÿÿÿèìÎ&J& p £ÿÿÿðÕÎ&A& m ¸ÿÿÿéãÎ&J& p ÍÿÿÿçßÎ&S& v âÿÿÿïÕÎ&J& p ÷ÿÿÿíÕÎ&S& v ÿÿÿëÐÎ&S& v !ÿÿÿíÐÎ&S& v 6ÿÿÿíÐÎ&S& v KÿÿÿìéÎ&S& v `ÿÿÿíÔÎ&S& v uÿÿÿíÕÎ&S& v ŠÿÿÿçÛÎ&S& v ŸÿÿÿïÐÎ&J& p ´ÿÿÿîÐÎ&J& p ÉÿÿÿèìÎ&S& v ÞÿÿÿèãÎ&J& s óÿÿÿééÎ&J& p ÿÿÿçÔÎ&J& s ÿÿÿçÞÎ&J& p 2ÿÿÿçÞÎ&S& p GÿÿÿëÐÎ&J& p \ÿÿÿìÕÎ&S& v qÿÿÿíØÎ&J& p †ÿÿÿçÖÎ&S& p ›ÿÿÿëöÎ&U\ÿÿÿëõÎ&[& ymÿÿÿëõÎ&m&[ ÿÿÿèõÎ&m&[ ¢ÿÿÿðöÎ&e& ·XÿÿÿêõÎ&m&[ ÌÿÿÿçõÎ&v&a áÿÿÿïõÎ&m&[ öÿÿÿíõÎ&v&a ÿÿÿëõÎ&v&a ÿÿÿíõÎ&v&a 5ÿÿÿíõÎ&v&a JÿÿÿìõÎ&v&a _ÿÿÿíõÎ&v&a tÿÿÿíõÎ&v&a ‰ÿÿÿçõÎ&v&a žÿÿÿïõÎ&[&m ³ÿÿÿîõÎ&m&[ ÈÿÿÿèõÎ&v&a ÝÿÿÿèöÎ&m&^ òÿÿÿéõÎ&m&[ ÿÿÿçöÎ&m&^ ÿÿÿçõÎ&m&[ 1ÿÿÿçõÎ&v&[ FÿÿÿëõÎ&m&[ [ÿÿÿìõÎ&v&a pÿÿÿíõÎ&m&[ …ÿÿÿçõÎ&v&[ šÿÿÿëÐÎ&d^ÿÿÿëÐÎ&o&j zÿÿÿëÐÎ&o&j ÿÿÿèìÎ&o&j ¤ÿÿÿðÕÎ&f&g ¹ÿÿÿéãÎ&o&j ÎÿÿÿçßÎ&x&p ãÿÿÿïÕÎ&o&j øÿÿÿíÕÎ&x&p ÿÿÿëÐÎ&x&p "ÿÿÿíÐÎ&x&p 7ÿÿÿíÐÎ&x&p LÿÿÿìéÎ&x&p aÿÿÿíÔÎ&x&p vÿÿÿíÕÎ&x&p ‹ÿÿÿçÛÎ&x&p  ÿÿÿïÐÎ&o&j µÿÿÿîÐÎ&o&j ÊÿÿÿèìÎ&x&p ßÿÿÿèàÎ&o&m ôÿÿÿééÎ&o&j ÿÿÿçÔÎ&o&m ÿÿÿçÞÎ&o&j 3ÿÿÿçÞÎ&x&j HÿÿÿëÐÎ&o&j ]ÿÿÿìÕÎ&x&p rÿÿÿíØÎ&o&j ‡ÿÿÿçÜÎ&x&j œÿÿÿëöÎ&\sÿÿÿëöÎ&m& yyÿÿÿëöÎ&m&y ÿÿÿèöÎ&m&y ¢ÿÿÿðöÎ&e&v ·ÿÿÿêöÎ&m&y ÌÿÿÿçöÎ&v& áÿÿÿïöÎ&m&y öÿÿÿíöÎ&v& ÿÿÿëöÎ&v& ÿÿÿíöÎ&v& 5ÿÿÿíöÎ&v& JÿÿÿìöÎ&v& _ÿÿÿíöÎ&v& tÿÿÿíöÎ&v& ‰ÿÿÿçöÎ&v& žÿÿÿïöÎ&m&y ³ÿÿÿîöÎ&m&y ÈÿÿÿèöÎ&v& ÝÿÿÿèöÎ&m&| òÿÿÿéöÎ&m&y ÿÿÿçöÎ&m&| ÿÿÿçöÎ&m&y 1ÿÿÿçöÎ&v&y FÿÿÿëöÎ&m&y [ÿÿÿìöÎ&v& pÿÿÿíöÎ&m&y …ÿÿÿçöÎ&v&y šÿÿÿëÐÎ&^‚ÿÿÿëÐÎ&o&ˆ zÿÿÿëÐÎ&o&ˆ ÿÿÿèìÎ&o&ˆ ¤ÿÿÿðÕÎ&f&… ¹ÿÿÿéãÎ&o&ˆ ÎÿÿÿçßÎ&x&Ž ãÿÿÿïÕÎ&o&ˆ øÿÿÿíÕÎ&x&Ž ÿÿÿëÐÎ&x&Ž "ÿÿÿíÐÎ&x&Ž 7ÿÿÿíÐÎ&x&Ž LÿÿÿìéÎ&x&Ž aÿÿÿíÔÎ&x&Ž vÿÿÿíÕÎ&x&Ž ‹ÿÿÿçÛÎ&x&Ž  ÿÿÿïÐÎ&o&ˆ µÿÿÿîÐÎ&o&ˆ ÊÿÿÿèìÎ&x&Ž ßÿÿÿèàÎ&o&‹ ôÿÿÿééÎ&o&ˆ ÿÿÿçÔÎ&o&‹ ÿÿÿçÞÎ&o&ˆ 3ÿÿÿçÞÎ&x&ˆ HÿÿÿëÐÎ&o&ˆ ]ÿÿÿìÕÎ&x&Ž rÿÿÿíØÎ&o&ˆ ‡ÿÿÿçÜÎ&x&ˆ œÿÿÿëÐÎ&]šÿÿÿëÐÎ&n&¬ yÿÿÿëÐÎ&n&¬ ŽÿÿÿèìÎ&n&¬ £ÿÿÿðÕÍ& ¸&e ÿÿÿéãÎ&n&¬ ÍÿÿÿçßÎ&w&¶ âÿÿÿïÕÎ&n&¬ ÷ÿÿÿíÕÎ&w&¶ ÿÿÿëÐÎ&w&¶ !ÿÿÿíÐÎ&w&¶ 6ÿÿÿíÐÎ&w&¶ KÿÿÿìéÎ&w&¶ `ÿÿÿíÔÎ&w&¶ uÿÿÿíÕÎ&w&¶ ŠÿÿÿçÛÎ&w&¶ ŸÿÿÿïÐÎ&n& ´¬ÿÿÿîÐÎ&¬&n ÉÿÿÿèìÎ&w&¶ ÞÿÿÿèãÎ&n&À óÿÿÿééÎ&n&¬ ÿÿÿçÔÎ&n&À ÿÿÿçÞÎ&n&¬ 2ÿÿÿçÞÎ&w&¶ GÿÿÿëÐÎ&n&¬ \ÿÿÿìÕÎ&w&¶ qÿÿÿíØÎ&n&¬ †ÿÿÿçÜÎ& š&¶wÿÿÿëÐÎ&^ÃÿÿÿëÐÎ&o&Ü zÿÿÿëÐÎ&o&Ü ÿÿÿèìÎ&o&Ü ¤ÿÿÿðÕÎ&f&Ò ¹ÿÿÿéãÎ&o&Ü ÎÿÿÿçßÎ&x&æ ãÿÿÿïÕÎ&o&Ü øÿÿÿíÕÎ&x&æ ÿÿÿëÐÎ&x&æ "ÿÿÿíÐÎ&x&æ 7ÿÿÿíÐÎ&x&æ LÿÿÿìéÎ&x&æ aÿÿÿíÔÎ&x&æ vÿÿÿíÕÎ&x&æ ‹ÿÿÿçÛÎ&x&æ  ÿÿÿïÐÎ&o&Ü µÿÿÿîÐÎ&o&Ü ÊÿÿÿèìÎ&x&æ ßÿÿÿèàÎ&o&ð ôÿÿÿééÎ&o&Ü ÿÿÿçÔÎ&o&ð ÿÿÿçÞÎ&o&Ü 3ÿÿÿçÞÎ&x&æ HÿÿÿëÐÎ&o&Ü ]ÿÿÿìÕÎ&x&æ rÿÿÿíØÎ&o&Ü ‡ÿÿÿçÜÎ&x&æ œÿÿÿëÐÎ&]÷ÿÿÿëÐÎ& &n yÿÿÿëÐÎ&n&  ŽÿÿÿèìÎ&n&  £ÿÿÿðÕÎ&ÿ&f ¹ÿÿÿéãÎ&n&  ÍÿÿÿçßÎ&w&  âÿÿÿïÕÎ&n&  ÷ÿÿÿíÕÎ&w&  ÿÿÿëÐÎ&w&  !ÿÿÿíÐÎ&w&  6ÿÿÿíÐÎ&w&  KÿÿÿìéÎ&w&  `ÿÿÿíÔÎ&w&  uÿÿÿíÕÎ&w&  ŠÿÿÿçÛÎ&w&  ŸÿÿÿïÐÎ&n&  ´ÿÿÿîÐÎ&n&  ÉÿÿÿèìÎ&w&  ÞÿÿÿèãÎ&n&  óÿÿÿééÎ&n&  ÿÿÿçÔÎ&n&  ÿÿÿçÞÎ&n&  2ÿÿÿçÞÎ&w&  GÿÿÿëÐÎ&n&  \ÿÿÿìÕÎ&w&  qÿÿÿíØÎ&n&  †ÿÿÿçÖÎ&w&  ›ÿÿÿëÐÎ&^ &ÿÿÿëÐÎ&o& = zÿÿÿëÐÎ&o& = ÿÿÿèìÎ&o& = ¤ÿÿÿðÕÎ&f& 3 ¹ÿÿÿéãÎ&o& = ÎÿÿÿçßÎ&x& G ãÿÿÿïÕÎ&o& = øÿÿÿíÕÎ&x& G ÿÿÿëÐÎ&x& G "ÿÿÿíÐÎ&x& G 7ÿÿÿíÐÎ&x& G LÿÿÿìéÎ&x& G aÿÿÿíÔÎ&x& G vÿÿÿíÕÎ&x& G ‹ÿÿÿçÛÎ&x& G  ÿÿÿïÐÎ&o& = µÿÿÿîÐÎ&o& = ÊÿÿÿèìÎ&x& G ßÿÿÿèàÎ&o& Q ôÿÿÿééÎ&o& = ÿÿÿçÔÎ&o& Q ÿÿÿçÞÎ&o& = 3ÿÿÿçÞÎ&x& G HÿÿÿëÐÎ&o& = ]ÿÿÿìÕÎ&x& G rÿÿÿíØÎ&o& = ‡ÿÿÿçÜÎ&x& G œÿÿ ê¿&_ WÿÿÿëêÉ&p& m {ÿÿÿëêÉ&p& m ÿÿÿêêÉ&p& m ¥ÿÿÿðêÉ&g& c ºÿÿÿéêÉ&p& m ÏÿÿÿçêÌ&y& w äÿÿÿïêÉ&p& m ùÿÿÿíêÌ&y& w ÿÿÿëêÌ&y& w #ÿÿÿíêÌ&y& w 8ÿÿÿðêÌ&y& w MÿÿÿìêÌ&y& w bÿÿÿíêÌ&y& w wÿÿÿíêÌ&y& w ŒÿÿÿçêÌ&y& w ¡ÿÿÿïêÉ&p& m ¶ÿÿÿîêÉ&p& m ËÿÿÿèìÌ&y& w àÿÿÿèêÉ&p& € õÿÿÿéêÉ&p& m ÿÿÿçêÉ&p& € ÿÿÿçêÉ&p& m 4ÿÿÿçêÌ&y& w IÿÿÿëêÉ&p& m ^ÿÿÿìêÌ&y& w sÿÿÿíêÉ&p& m ˆÿÿÿçêÌ&y& w ÿÿÿëöÎ&a ŠÿÿÿëöÎ&r& ž ƒÿÿÿëöÎ&r& ž ˜ÿÿÿêöÎ&r& ž ­ÿÿÿíöÎ&i& ” ÂÿÿÿéöÎ&r& ž ×ÿÿÿçöÎ&{& ¨ ìÿÿÿíöÎ&r& ž ÿÿÿíöÎ&{& ¨ ÿÿÿëöÎ&{& ¨ +ÿÿÿíöÎ&{& ¨ @ÿÿÿìöÎ&{& ¨ UÿÿÿìöÎ&{& ¨ jÿÿÿîöÎ&{& ¨ ÿÿÿíöÎ&{& ¨ ”ÿÿÿèöÎ&{& ¨ ©ÿÿÿïöÎ&r& ž ¾ÿÿÿîöÎ&r& ž ÓÿÿÿêöÎ&{& ¨ èÿÿÿêöÎ&r& ² ýÿÿÿìöÎ&r& ž ÿÿÿêöÎ&r& ² 'ÿÿÿëöÎ&r& ž <ÿÿÿëöÎ&{& ¨ QÿÿÿëöÎ&r& ž fÿÿÿíöÎ&{& ¨ {ÿÿÿðöÎ&r& ž ÿÿÿçöÎ&{& ¨ ¥ÿÿÿëÐÎ&c ¼ÿÿÿëÐÎ&t& Ð ƒÿÿÿëÐÎ&t& Ð ˜ÿÿÿêëÎ&t& Ð ­ÿÿÿíÕÎ&k& Æ ÂÿÿÿéãÎ&t& Ð ×ÿÿÿçßÎ&}& Ú ìÿÿÿíÕÎ&t& Ð ÿÿÿíÕÎ&}& Ú ÿÿÿëÐÎ&}& Ú +ÿÿÿíÐÎ&}& Ú @ÿÿÿìÐÎ&}& Ú UÿÿÿìéÎ&}& Ú jÿÿÿîÔÎ&}& Ú ÿÿÿíÕÎ&}& Ú ”ÿÿÿèÛÎ&}& Ú ©ÿÿÿïÐÎ&t& Ð ¾ÿÿÿîÐÎ&t& Ð ÓÿÿÿêéÎ&}& Ú èÿÿÿêãÎ&t& ä ýÿÿÿìéÎ&t& Ð ÿÿÿêÔÎ&t& ä 'ÿÿÿëâÎ&t& Ð <ÿÿÿëáÎ&}& Ú QÿÿÿëÐÎ&t& Ð fÿÿÿíÕÎ&}& Ú {ÿÿÿðØÎ&t& Ð ÿÿÿçÜÎ&}& Ú ¥ÿÿÿëÐÎ& íaÿÿÿëÐÎ&r&  ƒÿÿÿëÐÎ&r&  ˜ÿÿÿêëÎ&r&  ­ÿÿÿíÕÎ&i& ÷ ÂÿÿÿéãÎ&r&  ×ÿÿÿçßÎ&{&  ìÿÿÿíÕÎ&r&  ÿÿÿíÕÎ&{&  ÿÿÿëÐÎ&{&  +ÿÿÿíÐÎ&{&  @ÿÿÿìÐÎ&{&  UÿÿÿìéÎ&{&  jÿÿÿîÔÎ&{&  ÿÿÿíÕÎ&{&  ”ÿÿÿèÛÎ&{&  ©ÿÿÿïÐÎ&r&  ¾ÿÿÿîÐÎ&r&  ÓÿÿÿêéÎ&{&  èÿÿÿêãÎ&r&  ýÿÿÿìéÎ&r&  ÿÿÿêÔÎ&r&  'ÿÿÿëâÎ&r&  <ÿÿÿëáÎ&{&  QÿÿÿëÐÎ&r&  fÿÿÿíÕÎ&{&  {ÿÿÿðØÎ&r&  ÿÿÿçÜÎ&{&  ¥ÿÿ ë¿&_ ÿÿÿëëÉ&p& 3 {ÿÿÿëëÉ&p& 3 ÿÿÿêëÉ&p& 3 ¥ÿÿÿðëÉ&g& ) ºÿÿÿéëÉ&p& 3 ÏÿÿÿçëÌ&y& = äÿÿÿïëÉ&p& 3 ùÿÿÿíëÌ&y& = ÿÿÿëëÌ&y& = #ÿÿÿíëÌ&y& = 8ÿÿÿðëÌ&y& = MÿÿÿìëÌ&y& = bÿÿÿíëÌ&y& = wÿÿÿíëÌ&y& = ŒÿÿÿçëÌ&y& = ¡ÿÿÿïëÉ&p& 3 ¶ÿÿÿîëÉ&p& 3 ËÿÿÿèìÌ&y& = àÿÿÿèëÉ&p& G õÿÿÿéëÉ&p& 3 ÿÿÿçëÉ&p& G ÿÿÿçëÉ&p& 3 4ÿÿÿçëÌ&y& = IÿÿÿëëÉ&p& 3 ^ÿÿÿìëÌ&y& = sÿÿÿíëÉ&p& 3 ˆÿÿÿçëÌ&y& = ÿÿÿëëÉ&` KÿÿÿëëÌ& Y&q {ÿÿÿëëÌ&q& Y ÿÿÿêëÌ&q& Y ¥ÿÿÿðëÌ&h& R ºÿÿÿéëÌ&q& Y ÏÿÿÿçëÌ&z& n äÿÿÿïëÌ&q& Y ùÿÿÿíëÌ& &z nÿÿÿëëÌ&z& n #ÿÿÿíëÌ&z& n 8ÿÿÿðëÌ&z& n MÿÿÿìëÌ&z& n bÿÿÿíëÌ&z& n wÿÿÿíëÌ&z& n ŒÿÿÿçëÌ&z& n ¡ÿÿÿïëÌ& Y& ¶qÿÿÿîëÌ&z& Y ËÿÿÿèìÌ&z& n àÿÿÿèëÌ&q& g õÿÿÿéëÌ&q& Y ÿÿÿçëÌ&q& Y ÿÿÿçëÌ&q& Y 4ÿÿÿçëÌ&z& ` IÿÿÿëëÌ&q& Y ^ÿÿÿìëÌ&z& n sÿÿÿíëÌ&q& Y ˆÿÿÿçëÌ&z& ` ÿÿÿëÐÎ&b uÿÿÿëÐÎ&s& ƒ …ÿÿÿëÐÎ&s& ƒ šÿÿÿêëÎ&s& ƒ ¯ÿÿÿíÕÎ&j& | ÄÿÿÿéãÎ&s& ƒ ÙÿÿÿçßÎ&|& ˜ îÿÿÿìÕÎ&s& ƒ ÿÿÿíÕÎ&|& ˜ ÿÿÿëÐÎ&|& ˜ -ÿÿÿíÐÎ&|& ˜ BÿÿÿìÐÎ&|& ˜ WÿÿÿìéÎ&|& ˜ lÿÿÿîÔÎ&|& ˜ ÿÿÿîÕÎ&|& ˜ –ÿÿÿçÛÎ&|& ˜ «ÿÿÿïÐÎ&s& ƒ ÀÿÿÿîÐÎ&s& ƒ ÕÿÿÿêéÎ&|& ˜ êÿÿÿêäÎ&s& ‘ ÿÿÿÿìéÎ&s& ƒ ÿÿÿêÔÎ&s& ‘ )ÿÿÿêÞÎ&s& ƒ >ÿÿÿëÞÎ&|& Š SÿÿÿëÐÎ&s& ƒ hÿÿÿíÕÎ&|& ˜ }ÿÿÿðØÎ&s& ƒ ’ÿÿÿçÜÎ&|& Š §ÿÿÿëÐÎ&d ŸÿÿÿëÐÎ&u& ­ …ÿÿÿëÐÎ&u& ­ šÿÿÿêëÎ&u& ­ ¯ÿÿÿíÕÎ&l& ¦ ÄÿÿÿéãÎ&u& ­ ÙÿÿÿçßÎ&~&  îÿÿÿìÕÎ&u& ­ ÿÿÿíÕÎ&~&  ÿÿÿëÐÎ&~&  -ÿÿÿíÐÎ&~&  BÿÿÿìÐÎ&~&  WÿÿÿìéÎ&~&  lÿÿÿîÔÎ&~&  ÿÿÿîÕÎ&~&  –ÿÿÿçÛÎ&~&  «ÿÿÿïÐÎ&u& ­ ÀÿÿÿîÐÎ&u& ­ ÕÿÿÿêéÎ&~&  êÿÿÿêäÎ&u& » ÿÿÿÿìéÎ&u& ­ ÿÿÿêÔÎ&u& » )ÿÿÿêÞÎ&u& ­ >ÿÿÿëÞÎ&~& ´ SÿÿÿëÐÎ&u& ­ hÿÿÿíÕÎ&~&  }ÿÿÿðØÎ&u& ­ ’ÿÿÿçÜÎ&~& ´ §ÿÿÿëÑÎ&b ÉÿÿÿëÐÎ&s& × „ÿÿÿëÐÎ&s& × ™ÿÿÿêëÎ&s& × ®ÿÿÿíÕÎ&j& Ð ÃÿÿÿéãÎ&s& × ØÿÿÿçßÎ&|& ì íÿÿÿìÕÎ&s& × ÿÿÿíÕÎ&|& ì ÿÿÿëÐÎ&|& ì ,ÿÿÿíÐÎ&|& ì AÿÿÿìÐÎ&|& ì VÿÿÿìéÎ&|& ì kÿÿÿîÔÎ&|& ì €ÿÿÿîÕÎ&|& ì •ÿÿÿèÛÎ&|& ì ªÿÿÿïÐÎ&s& × ¿ÿÿÿîÐÎ&s& × ÔÿÿÿêéÎ&|& ì éÿÿÿêãÎ&s& å þÿÿÿìéÎ&s& × ÿÿÿêÔÎ&s& å (ÿÿÿêÞÎ&s& × =ÿÿÿëÞÎ&|& Þ RÿÿÿëÐÎ&s& × gÿÿÿíÕÎ&|& ì |ÿÿÿðØÎ&s& × ‘ÿÿÿç×Î&|& Þ ¦ÿÿÿíëÉ&` ðÿÿÿëëÌ&q&  {ÿÿÿëëÌ&q&  ÿÿÿêëÌ&q&  ¥ÿÿÿðëÌ&h& ú ºÿÿÿéëÌ&q&  ÏÿÿÿçëÌ&z&  äÿÿÿïëÌ&q&  ùÿÿÿíëÌ&z&  ÿÿÿëëÌ&z&  #ÿÿÿíëÌ&z&  8ÿÿÿðëÌ&z&  MÿÿÿìëÌ&z&  bÿÿÿíëÌ&z&  wÿÿÿíëÌ&z&  ŒÿÿÿçëÌ&z&  ¡ÿÿÿïëÌ&q&  ¶ÿÿÿîëÌ&q&  ËÿÿÿèìÌ&z&  àÿÿÿèëÌ&q&  õÿÿÿéëÌ&q&  ÿÿÿçëÌ&q&  ÿÿÿçëÌ&q&  4ÿÿÿçëÌ&z&  IÿÿÿëëÌ&q&  ^ÿÿÿìëÌ&z&  sÿÿÿíëÌ&q&  ˆÿÿÿçëÌ&z&  ÿÿ ë¿&_ ÿÿÿëëÉ&p& * {ÿÿÿëëÉ&p& * ÿÿÿêëÉ&p& * ¥ÿÿÿðëÌ& & ºhÿÿÿéëÉ&p& * ÏÿÿÿçëÌ&y& ? äÿÿÿïëÉ&p& * ùÿÿÿíëÉ&p&  ?ÿÿÿëëÌ&y& ? #ÿÿÿíëÌ&y& ? 8ÿÿÿðëÌ&y& ? MÿÿÿìëÌ&y& ? bÿÿÿíëÌ&y& ? wÿÿÿíëÌ&y& ? ŒÿÿÿçëÌ&y& ? ¡ÿÿÿïëÉ&p& ¶ 'ÿÿÿîëÉ&p& * ËÿÿÿèìÌ&y& ? àÿÿÿèëÉ&p& * õÿÿÿéëÉ&p& * ÿÿÿçëÉ&p& 8 ÿÿÿçëÉ&p& * 4ÿÿÿçëÌ&y& 1 IÿÿÿëëÉ&p& * ^ÿÿÿìëÌ&y& ? sÿÿÿíëÉ&p& * ˆÿÿÿçëÌ&y& 1 ÿÿÿëÐÎ&a EÿÿÿëÐÎ&r& S ƒÿÿÿëÐÎ&r& S ˜ÿÿÿêëÎ&r& S ­ÿÿÿíÕÎ&i& L ÂÿÿÿéãÎ&r& S ×ÿÿÿçßÎ&{& h ìÿÿÿíÕÎ&r& S ÿÿÿíÕÎ&{& h ÿÿÿëÐÎ&{& h +ÿÿÿíÐÎ&{& h @ÿÿÿìÐÎ&{& h UÿÿÿìéÎ&{& h jÿÿÿîÔÎ&{& h ÿÿÿíÕÎ&{& h ”ÿÿÿèÛÎ&{& h ©ÿÿÿïÐÎ&r& S ¾ÿÿÿîÐÎ&r& S ÓÿÿÿêéÎ&{& h èÿÿÿêãÎ&r& a ýÿÿÿìéÎ&r& S ÿÿÿêÔÎ&r& a 'ÿÿÿëâÎ&r& S <ÿÿÿëáÎ&{& Z QÿÿÿëÐÎ&r& S fÿÿÿíÕÎ&{& h {ÿÿÿðØÎ&r& S ÿÿÿçÜÎ&{& Z ¥ÿÿÿëÐÎ&\ iÿÿÿëÐÎ&m& o yÿÿÿëÐÎ&m& o ŽÿÿÿèìÎ&m& o £ÿÿÿðÕÎ&e& l ¸ÿÿÿéãÎ&m& o ÍÿÿÿçßÎ&v& u âÿÿÿïÕÎ&m& o ÷ÿÿÿíÕÎ&v& u ÿÿÿëÐÎ&v& u !ÿÿÿíÐÎ&v& u 6ÿÿÿíÐÎ&v& u KÿÿÿìéÎ&v& u `ÿÿÿíÔÎ&v& u uÿÿÿíÕÎ&v& u ŠÿÿÿçÛÎ&v& u ŸÿÿÿïÐÎ&m& o ´ÿÿÿîÐÎ&m& o ÉÿÿÿèìÎ&v& u ÞÿÿÿèãÎ&m& r óÿÿÿééÎ&m& o ÿÿÿçÔÎ&m& r ÿÿÿçÞÎ&m& o 2ÿÿÿçÞÎ&v& o GÿÿÿëÐÎ&m& o \ÿÿÿìÕÎ&v& u qÿÿÿíØÎ&m& o †ÿÿÿçÖÎ&v& o ›ÿÿÿëöÎ&UÿÿÿëõÎ&[& xÿÿÿëõÎ&&[ ÿÿÿèõÎ&&[ ¢ÿÿÿðöÎ&&X ·ÿÿÿêõÎ&&[ ÌÿÿÿçõÎ&&[ áÿÿÿïõÎ&&[ öÿÿÿíõÎ&& aÿÿÿëõÎ&™&a ÿÿÿíõÎ&™&a 5ÿÿÿíõÎ&™&a JÿÿÿìõÎ&™&a _ÿÿÿíõÎ&™&a tÿÿÿíõÎ&™&a ‰ÿÿÿçõÎ&™&a žÿÿÿïõÎ&&[ ³ÿÿÿîõÎ&&[ ÈÿÿÿèõÎ&™&a ÝÿÿÿèöÎ&&^ òÿÿÿéõÎ&&[ ÿÿÿçöÎ&&^ ÿÿÿçõÎ&&\ ?ÿÿÿçõÎ&™&[ FÿÿÿëõÎ&&[ [ÿÿÿìõÎ&&a pÿÿÿíõÎ&&[ …ÿÿÿçõÎ&™&[ šÿÿÿëÐÎ&dÿÿÿëÐÎ&j&’ zÿÿÿëÐÎ&’&j ÿÿÿèìÎ&’&j ¤ÿÿÿðÕÎ&‰&g ¹ÿÿÿéãÎ&’&j ÎÿÿÿçßÎ&›&p ãÿÿÿïÕÎ&’&j øÿÿÿíÕÎ&›&p ÿÿÿëÐÎ&›&p "ÿÿÿíÐÎ&›&p 7ÿÿÿíÐÎ&›&p LÿÿÿìéÎ&›&p aÿÿÿíÔÎ&›&p vÿÿÿíÕÎ&›&p ‹ÿÿÿçÛÎ&›&p  ÿÿÿïÐÎ&’&j µÿÿÿîÐÎ&’&j ÊÿÿÿèìÎ&›&p ßÿÿÿèàÎ&’&m ôÿÿÿééÎ&’&j ÿÿÿçÔÎ&’&m ÿÿÿçÞÎ&’&j 3ÿÿÿçÞÎ&›&j HÿÿÿëÐÎ&’&j ]ÿÿÿìÕÎ&›&p rÿÿÿíØÎ&’&j ‡ÿÿÿçÜÎ&›&j œÿÿÿëöÎ&sÿÿÿëöÎ&&y xÿÿÿëöÎ&&y ÿÿÿèöÎ&&y ¢ÿÿÿðöÎ&ˆ&v ·ÿÿÿêöÎ&&y ÌÿÿÿçöÎ&™& áÿÿÿïöÎ&&y öÿÿÿíöÎ&™& ÿÿÿëöÎ&™& ÿÿÿíöÎ&™& 5ÿÿÿíöÎ&™& JÿÿÿìöÎ&™& _ÿÿÿíöÎ&™& tÿÿÿíöÎ&™& ‰ÿÿÿçöÎ&™& žÿÿÿïöÎ&&y ³ÿÿÿîöÎ&&y ÈÿÿÿèöÎ&™& ÝÿÿÿèöÎ&&| òÿÿÿéöÎ&&y ÿÿÿçöÎ&&| ÿÿÿçöÎ&&y 1ÿÿÿçöÎ&™&y FÿÿÿëöÎ&&y [ÿÿÿìöÎ&™& pÿÿÿíöÎ&&y …ÿÿÿçöÎ&™&y šÿÿÿëÐÎ&‚ÿÿÿëÐÎ&’&ˆ zÿÿÿëÐÎ&’&ˆ ÿÿÿèìÎ&’&ˆ ¤ÿÿÿðÕÎ&‰&… ¹ÿÿÿéãÎ&’&ˆ ÎÿÿÿçßÎ&›&Ž ãÿÿÿïÕÎ&’&ˆ øÿÿÿíÕÎ&›&Ž ÿÿÿëÐÎ&›&Ž "ÿÿÿíÐÎ&›&Ž 7ÿÿÿíÐÎ&›&Ž LÿÿÿìéÎ&›&Ž aÿÿÿíÔÎ&›&Ž vÿÿÿíÕÎ&›&Ž ‹ÿÿÿçÛÎ&›&Ž  ÿÿÿïÐÎ&’&ˆ µÿÿÿîÐÎ&’&ˆ ÊÿÿÿèìÎ&›&Ž ßÿÿÿèàÎ&’&‹ ôÿÿÿééÎ&’&ˆ ÿÿÿçÔÎ&’&‹ ÿÿÿçÞÎ&’&ˆ 3ÿÿÿçÞÎ&›&ˆ HÿÿÿëÐÎ&’&ˆ ]ÿÿÿìÕÎ&›&Ž rÿÿÿíØÎ&’&ˆ ‡ÿÿÿçÜÎ&›&ˆ œÿÿÿëÐÎ&€•ÿÿÿëÐÎ&‘&¨ yÿÿÿëÐÎ&‘&¨ ŽÿÿÿèìÎ&‘&¨ £ÿÿÿíÚÍ&ˆ& Æ ÿÿÿéãÎ&‘&¨ ÍÿÿÿçßÎ&š&² âÿÿÿïÕÎ&‘&¨ ÷ÿÿÿíÕÎ&š&² ÿÿÿëÐÎ&š&² !ÿÿÿíÐÎ&š&² 6ÿÿÿíÐÎ&š&² KÿÿÿìéÎ&š&² `ÿÿÿíÔÎ&š&² uÿÿÿíÕÎ&š&² ŠÿÿÿçÛÎ&š&² ŸÿÿÿïÐÎ&‘&¨ ´ÿÿÿîÐÎ&‘&¨ ÉÿÿÿèìÎ&š&² ÞÿÿÿèãÎ&‘&¼ óÿÿÿééÎ&‘&¨ ÿÿÿçÔÎ&‘&¼ ÿÿÿçÞÎ&‘&¨ 2ÿÿÿçÞÎ&š&² GÿÿÿëÐÎ&‘&¨ \ÿÿÿìÕÎ&š&² qÿÿÿíØÎ&‘&¨ †ÿÿÿçÖÎ&š&² ›ÿÿÿëÐÎ&ÅÿÿÿëÐÎ&’&Ø zÿÿÿëÐÎ&’&Ø ÿÿÿèìÎ&’&Ø ¤ÿÿÿðÕÎ&‰&Î ¹ÿÿÿéãÎ&’&Ø ÎÿÿÿçßÎ&›&â ãÿÿÿïÕÎ&’&Ø øÿÿÿíÕÎ&›&â ÿÿÿëÐÎ&›&â "ÿÿÿíÐÎ&›&â 7ÿÿÿíÐÎ&›&â LÿÿÿìéÎ&›&â aÿÿÿíÔÎ&›&â vÿÿÿíÕÎ&›&â ‹ÿÿÿçÛÎ&›&â  ÿÿÿïÐÎ&’&Ø µÿÿÿîÐÎ&’&Ø ÊÿÿÿèìÎ&›&â ßÿÿÿèàÎ&’&ì ôÿÿÿééÎ&’&Ø ÿÿÿçÔÎ&’&ì ÿÿÿçÞÎ&’&Ø 3ÿÿÿçÞÎ&›&â HÿÿÿëÐÎ&’&Ø ]ÿÿÿìÕÎ&›&â rÿÿÿíØÎ&’&Ø ‡ÿÿÿçÜÎ&›&â œÿÿÿëÐÎ&€÷ÿÿÿëÐÎ&‘&  yÿÿÿëÐÎ&‘&  ŽÿÿÿèìÎ&‘&  £ÿÿÿðÕÎ&ÿ& ¸‘ÿÿÿéãÎ&‘&  ÍÿÿÿçßÎ&š&  âÿÿÿïÕÎ&‘&  ÷ÿÿÿíÕÎ&š&  ÿÿÿëÐÎ&š&  !ÿÿÿíÐÎ&š&  6ÿÿÿíÐÎ&š&  KÿÿÿìéÎ&š&  `ÿÿÿíÔÎ&š&  uÿÿÿíÕÎ&š&  ŠÿÿÿçÛÎ&š&  ŸÿÿÿïÐÎ&‘&  ´ÿÿÿîÐÎ&‘&  ÉÿÿÿèìÎ&š&  ÞÿÿÿèãÎ&‘&  óÿÿÿééÎ&‘&  ÿÿÿçÔÎ&‘&  ÿÿÿçÞÎ&‘&  2ÿÿÿçÞÎ&‘&  GÿÿÿëÐÎ&‘&  \ÿÿÿìÕÎ&š&  qÿÿÿíØÎ&‘&  †ÿÿÿçÖÎ&š&  ›ÿÿÿëÐÎ& %ÿÿÿëÐÎ&’& 9 zÿÿÿëÐÎ&’& 9 ÿÿÿèìÎ&’& 9 ¤ÿÿÿðÕÎ&‰& / ¹ÿÿÿéãÎ&’& 9 ÎÿÿÿçßÎ&›& C ãÿÿÿïÕÎ&’& 9 øÿÿÿíÕÎ&›& C ÿÿÿëÐÎ&›& C "ÿÿÿíÐÎ&›& C 7ÿÿÿíÐÎ&›& C LÿÿÿìéÎ&›& C aÿÿÿíÔÎ&›& C vÿÿÿíÕÎ&›& C ‹ÿÿÿçÛÎ&›& C  ÿÿÿïÐÎ&’& 9 µÿÿÿîÐÎ&’& 9 ÊÿÿÿèìÎ&›& C ßÿÿÿèàÎ&’& M ôÿÿÿééÎ&’& 9 ÿÿÿçÔÎ&’& M ÿÿÿçÞÎ&’& 9 3ÿÿÿçÞÎ&›& C HÿÿÿëÐÎ&’& 9 ]ÿÿÿìÕÎ&›& C rÿÿÿíØÎ&’& 9 ‡ÿÿÿçÜÎ&›& C œÿÿ ê½&‚ VÿÿÿëêÉ& h&“ {ÿÿÿëêÉ& h& “ÿÿÿêêÉ&“& h ¥ÿÿÿðêÉ&Š& ^ ºÿÿÿéêÉ&“& h ÏÿÿÿçêÉ&œ& r äÿÿÿïêÉ&“& h ùÿÿÿíêÉ&œ& r ÿÿÿëêÉ&œ& r #ÿÿÿíêÉ&œ& r 8ÿÿÿðêÉ&œ& r MÿÿÿìêÉ&œ& r bÿÿÿíêÉ&œ& r wÿÿÿíêÉ&œ& r ŒÿÿÿçêÉ&œ& r ¡ÿÿÿïêÉ&“& h ¶ÿÿÿîêÉ&“& h ËÿÿÿèìÉ&œ& r àÿÿÿèêÉ& õ& {“ÿÿÿéêÉ&“& h ÿÿÿçêÉ&“& { ÿÿÿçêÉ&“& h 4ÿÿÿçêÉ&œ& r IÿÿÿëêÉ&“& h ^ÿÿÿìêÉ&œ& r sÿÿÿíêÉ&“& h ˆÿÿÿçêÉ&œ& r ÿÿÿëöÎ&„ …ÿÿÿëöÎ&•& ™ |ÿÿÿëöÎ&•& ™ ‘ÿÿÿêöÎ&•& ™ ¦ÿÿÿðöÎ&Œ&  »ÿÿÿêöÎ&•& ™ ÐÿÿÿçöÎ&ž& £ åÿÿÿïöÎ&•& ™ úÿÿÿíöÎ&ž& £ ÿÿÿëöÎ&ž& £ $ÿÿÿíöÎ&ž& £ 9ÿÿÿíöÎ&ž& £ NÿÿÿìöÎ&ž& £ cÿÿÿîöÎ&ž& £ xÿÿÿíöÎ&ž& £ ÿÿÿçöÎ&ž& £ ¢ÿÿÿïöÎ&•& ™ ·ÿÿÿîöÎ&•& ™ ÌÿÿÿèöÎ&ž& £ áÿÿÿèöÎ&•& ­ öÿÿÿéöÎ&•& ™ ÿÿÿçöÎ&•& ­ ÿÿÿçöÎ&•& ™ 5ÿÿÿçöÎ&ž& £ JÿÿÿëöÎ&•& ™ _ÿÿÿìöÎ&ž& £ tÿÿÿíöÎ&•& ™ ‰ÿÿÿçöÎ&ž& £ žÿÿÿëÐÎ&† ·ÿÿÿëÐÎ&—& Ë |ÿÿÿëÐÎ&—& Ë ‘ÿÿÿêëÎ&—& Ë ¦ÿÿÿðÕÎ&Ž& Á »ÿÿÿêãÎ&—& Ë ÐÿÿÿçßÎ& & Õ åÿÿÿïÕÎ&—& Ë úÿÿÿíÕÎ& & Õ ÿÿÿëÐÎ& & Õ $ÿÿÿíÐÎ& & Õ 9ÿÿÿíÐÎ& & Õ NÿÿÿìéÎ& & Õ cÿÿÿîÔÎ& & Õ xÿÿÿíÕÎ& & Õ ÿÿÿçÛÎ& & Õ ¢ÿÿÿïÐÎ&—& Ë ·ÿÿÿîÐÎ&—& Ë ÌÿÿÿèéÎ& & Õ áÿÿÿèãÎ&—& ß öÿÿÿééÎ&—& Ë ÿÿÿçÔÎ&—& ß ÿÿÿçâÎ&—& Ë 5ÿÿÿçâÎ& & Õ JÿÿÿëÐÎ&—& Ë _ÿÿÿìÕÎ& & Õ tÿÿÿíØÎ&—& Ë ‰ÿÿÿçÜÎ& & Õ žÿÿÿëÐÎ&„ éÿÿÿëÐÎ&•& ü |ÿÿÿëÐÎ&•& ü ‘ÿÿÿêëÎ&•& ü ¦ÿÿÿðÕÎ&Œ& ò »ÿÿÿêãÎ&•& ü ÐÿÿÿçßÎ&ž&  åÿÿÿïÕÎ&•& ü úÿÿÿíÕÎ&ž&  ÿÿÿëÐÎ&ž&  $ÿÿÿíÐÎ&ž&  9ÿÿÿíÐÎ&ž&  NÿÿÿìéÎ&ž&  cÿÿÿîÔÎ&ž&  xÿÿÿíÕÎ&ž&  ÿÿÿçÛÎ&ž&  ¢ÿÿÿïÐÎ&•& ü ·ÿÿÿîÐÎ&•& ü ÌÿÿÿèéÎ&ž&  áÿÿÿèãÎ&•&  öÿÿÿééÎ&•& ü ÿÿÿçÔÎ&•&  ÿÿÿçâÎ&•& ü 5ÿÿÿçâÎ&ž&  JÿÿÿëÐÎ&•& ü _ÿÿÿìÕÎ&ž&  tÿÿÿíØÎ&•& ü ‰ÿÿÿçÜÎ&ž&  žÿÿ ë½&‚ ÿÿÿëëÉ&“& . {ÿÿÿëëÉ&“& . ÿÿÿêëÉ&“& . ¥ÿÿÿðëÉ&Š& $ ºÿÿÿéëÉ&“& . ÏÿÿÿçëÉ&œ& 8 äÿÿÿïëÉ&“& . ùÿÿÿíëÉ&œ& 8 ÿÿÿëëÉ&œ& 8 #ÿÿÿíëÉ&œ& 8 8ÿÿÿðëÉ&œ& 8 MÿÿÿìëÉ&œ& 8 bÿÿÿíëÉ&œ& 8 wÿÿÿíëÉ&œ& 8 ŒÿÿÿçëÉ&œ& 8 ¡ÿÿÿïëÉ&“& . ¶ÿÿÿîëÉ&“& . ËÿÿÿèìÉ&œ& 8 àÿÿÿèëÉ&“& B õÿÿÿéëÉ&“& . ÿÿÿçëÉ&“& B ÿÿÿçëÉ&“& . 4ÿÿÿçëÉ&œ& 8 IÿÿÿëëÉ&“& . ^ÿÿÿìëÉ&œ& 8 sÿÿÿíëÉ&“& . ˆÿÿÿçëÉ&œ& 8 ÿÿÿëëÉ&ƒ JÿÿÿëëÉ&”& V {ÿÿÿëëÉ&”& V ÿÿÿêëÉ&”& V ¥ÿÿÿðëÉ& Q& º‹ÿÿÿéëÉ&”& V ÏÿÿÿçëÉ&& k äÿÿÿïëÉ&‹& ù VÿÿÿíëÉ& k& ÿÿÿëëÉ&& k #ÿÿÿíëÉ&& k 8ÿÿÿðëÉ&& k MÿÿÿìëÉ&& k bÿÿÿíëÉ&& k wÿÿÿíëÉ&& k ŒÿÿÿçëÉ&& k ¡ÿÿÿïëÉ&”& V ¶ÿÿÿîëÉ&”& V ËÿÿÿèìÉ&& k àÿÿÿèëÉ&”& d õÿÿÿéëÉ&”& V ÿÿÿçëÉ&”& V ÿÿÿçëÉ&”& V 4ÿÿÿçëÉ&& ] IÿÿÿëëÉ&”& V ^ÿÿÿìëÉ&& k sÿÿÿíëÉ&”& V ˆÿÿÿçëÉ&& ] ÿÿÿëÐÎ&… rÿÿÿëÐÎ&–& € ~ÿÿÿëÐÎ&–& € “ÿÿÿêëÎ&–& € ¨ÿÿÿðÕÎ&& y ½ÿÿÿéãÎ&–& € ÒÿÿÿçßÎ&Ÿ& • çÿÿÿïÕÎ&–& € üÿÿÿíÕÎ&Ÿ& • ÿÿÿëÐÎ&Ÿ& • &ÿÿÿíÐÎ&Ÿ& • ;ÿÿÿíÐÎ&Ÿ& • PÿÿÿëéÎ&Ÿ& • eÿÿÿîÔÎ&Ÿ& • zÿÿÿíÖÎ&Ÿ& • ÿÿÿçÛÎ&Ÿ& • ¤ÿÿÿïÐÎ&–& € ¹ÿÿÿîÐÎ&–& € ÎÿÿÿêéÎ&Ÿ& • ãÿÿÿèãÎ&–& Ž øÿÿÿçéÎ&–& € ÿÿÿçÔÎ&–& Ž "ÿÿÿçÞÎ&–& € 7ÿÿÿíÞÎ&Ÿ& ‡ LÿÿÿëÐÎ&–& € aÿÿÿìÕÎ&Ÿ& • vÿÿÿíØÎ&–& € ‹ÿÿÿçÜÎ&Ÿ& ‡  ÿÿÿëÐÎ&‡ œÿÿÿëÐÎ&˜& ª ~ÿÿÿëÐÎ&˜& ª “ÿÿÿêëÎ&˜& ª ¨ÿÿÿðÕÎ&& £ ½ÿÿÿéãÎ&˜& ª ÒÿÿÿçßÎ&¡& ¿ çÿÿÿïÕÎ&˜& ª üÿÿÿíÕÎ&¡& ¿ ÿÿÿëÐÎ&¡& ¿ &ÿÿÿíÐÎ&¡& ¿ ;ÿÿÿíÐÎ&¡& ¿ PÿÿÿëéÎ&¡& ¿ eÿÿÿîÔÎ&¡& ¿ zÿÿÿíÖÎ&¡& ¿ ÿÿÿçÛÎ&¡& ¿ ¤ÿÿÿïÐÎ&˜& ª ¹ÿÿÿîÐÎ&˜& ª ÎÿÿÿêéÎ&¡& ¿ ãÿÿÿèãÎ&˜& ¸ øÿÿÿçéÎ&˜& ª ÿÿÿçÔÎ&˜& ¸ "ÿÿÿçÞÎ&˜& ª 7ÿÿÿíÞÎ&¡& ± LÿÿÿëÐÎ&˜& ª aÿÿÿìÕÎ&¡& ¿ vÿÿÿíØÎ&˜& ª ‹ÿÿÿçÜÎ&¡& ±  ÿÿÿëÐÎ&… ÆÿÿÿëÐÎ&–& Ô }ÿÿÿëÐÎ&–& Ô ’ÿÿÿêëÎ&–& Ô §ÿÿÿðÕÎ&& Í ¼ÿÿÿéãÎ&–& Ô ÑÿÿÿçßÎ&Ÿ& é æÿÿÿïÕÎ&–& Ô ûÿÿÿíÕÎ&Ÿ& é ÿÿÿëÐÎ&Ÿ& é %ÿÿÿíÐÎ&Ÿ& é :ÿÿÿíÐÎ&Ÿ& é OÿÿÿëèÎ&Ÿ& é dÿÿÿîÔÎ&Ÿ& é yÿÿÿíÖÎ&Ÿ& é ŽÿÿÿçÛÎ&Ÿ& é £ÿÿÿïÐÎ&–& Ô ¸ÿÿÿîÐÎ&–& Ô ÍÿÿÿêéÎ&Ÿ& é âÿÿÿèãÎ&–& â ÷ÿÿÿçéÎ&–& Ô ÿÿÿçÔÎ&–& â !ÿÿÿçÞÎ&–& Ô 6ÿÿÿçÞÎ&Ÿ& Û KÿÿÿëÐÎ&–& Ô `ÿÿÿìÕÎ&Ÿ& é uÿÿÿíØÎ&–& Ô ŠÿÿÿçÖÎ&Ÿ& Û ŸÿÿÿíëÉ&ƒ ðÿÿÿëëÉ&”& þ {ÿÿÿëëÉ&”& þ ÿÿÿêëÉ&”& þ ¥ÿÿÿðëÉ&‹& ÷ ºÿÿÿéëÉ&”& þ ÏÿÿÿçëÉ&&  äÿÿÿïëÉ&”& þ ùÿÿÿíëÉ&&  ÿÿÿëëÉ&&  #ÿÿÿíëÉ&&  8ÿÿÿðëÉ&&  MÿÿÿìëÉ&&  bÿÿÿíëÉ&&  wÿÿÿíëÉ&&  ŒÿÿÿçëÉ&&  ¡ÿÿÿïëÉ&”& þ ¶ÿÿÿîëÉ&”& þ ËÿÿÿèìÉ&&  àÿÿÿèëÉ&”&  õÿÿÿéëÉ&”& þ ÿÿÿçëÉ&”&  ÿÿÿçëÉ&”& þ 4ÿÿÿçëÉ&&  IÿÿÿëëÉ&”& þ ^ÿÿÿìëÉ&&  sÿÿÿíëÉ&”& þ ˆÿÿÿçëÉ&&  ÿÿ ë½&‚ ÿÿÿëëÉ&“& ' {ÿÿÿëëÉ&“& ' ÿÿÿêëÉ&“& ' ¥ÿÿÿðëÉ&Š&  ºÿÿÿéëÉ&“& ' ÏÿÿÿçëÉ&œ& < äÿÿÿïëÉ&“& ' ùÿÿÿíëÉ&œ& < ÿÿÿëëÉ&œ& < #ÿÿÿíëÉ&œ& < 8ÿÿÿðëÉ&œ& < MÿÿÿìëÉ&œ& < bÿÿÿíëÉ&œ& < wÿÿÿíëÉ&œ& < ŒÿÿÿçëÉ&œ& < ¡ÿÿÿïëÉ&“& ' ¶ÿÿÿîëÉ&“& ' ËÿÿÿèìÉ&œ& < àÿÿÿèëÉ&“& 5 õÿÿÿéëÉ&“& ' ÿÿÿçëÉ&“& 5 ÿÿÿçëÉ&“& ' 4ÿÿÿçëÉ&œ& . IÿÿÿëëÉ&“& ' ^ÿÿÿìëÉ&œ& < sÿÿÿíëÉ&“& ' ˆÿÿÿçëÉ&œ& . ÿÿÿëÐÎ&„ CÿÿÿëÐÎ&•& P |ÿÿÿëÐÎ&•& P ‘ÿÿÿêëÎ&•& P ¦ÿÿÿðÕÎ&Œ& I »ÿÿÿêãÎ&•& P ÐÿÿÿçßÎ&ž& e åÿÿÿïÕÎ&•& P úÿÿÿíÕÎ&ž& e ÿÿÿëÐÎ&ž& e $ÿÿÿíÐÎ&ž& e 9ÿÿÿíÐÎ&ž& e NÿÿÿìéÎ&ž& e cÿÿÿîÔÎ&ž& e xÿÿÿíÕÎ&ž& e ÿÿÿçÛÎ&ž& e ¢ÿÿÿïÐÎ&•& P ·ÿÿÿîÐÎ&•& P ÌÿÿÿèéÎ&ž& e áÿÿÿèãÎ&•& ^ öÿÿÿééÎ&•& P ÿÿÿçÔÎ&•& ^ ÿÿÿçâÎ&•& P 5ÿÿÿçâÎ&ž& W JÿÿÿëÐÎ&•& P _ÿÿÿìÕÎ&ž& e tÿÿÿíØÎ&•& P ‰ÿÿÿçÜÎ&ž& W žÿÿÿëÐÎ&€ iÿÿÿëÐÎ&& o yÿÿÿëÐÎ&& o ŽÿÿÿèìÎ&& o £ÿÿÿðÕÎ& l&ˆ ·ÿÿÿéãÎ&& o ÍÿÿÿçßÎ&™& u âÿÿÿïÕÎ&& ö oÿÿÿíÕÎ& o& ÿÿÿëÐÎ&™& u !ÿÿÿíÐÎ&™& u 6ÿÿÿíÐÎ&™& u KÿÿÿìéÎ&™& u `ÿÿÿíÔÎ&™& u uÿÿÿíÕÎ&™& u ŠÿÿÿçÛÎ&™& u ŸÿÿÿïÐÎ&& o ´ÿÿÿîÐÎ&& o ÉÿÿÿèìÎ&™& u ÞÿÿÿèãÎ&& r óÿÿÿééÎ&& o ÿÿÿçÔÎ&& r ÿÿÿçÞÎ&& o 2ÿÿÿçÞÎ& p& G‘ÿÿÿëÐÎ&& o \ÿÿÿìÕÎ&™& u qÿÿÿíØÎ&& o †ÿÿÿçÖÎ&™& o ›ÿÿÿëöÎ&U¢ÿÿÿëõÎ&³&\ xÿÿÿëõÎ&³&\ ÿÿÿèõÎ&³&[ ¢ÿÿÿðöÎ&³&X ·ÿÿÿêõÎ&³&[ ÌÿÿÿçõÎ&¼&a áÿÿÿìõÎ&\& ¼ÿÿÿíõÎ&¼& aÿÿÿëõÎ&¼& aÿÿÿíõÎ&¼&a 5ÿÿÿíõÎ&¼&a JÿÿÿìõÎ&¼&a _ÿÿÿíõÎ&¼&a tÿÿÿíõÎ&¼&a ‰ÿÿÿçõÎ&¼&a žÿÿÿïõÎ&³&[ ³ÿÿÿîõÎ&³&[ ÈÿÿÿèõÎ&¼&a ÝÿÿÿèöÎ&³&^ òÿÿÿéõÎ&³&[ ÿÿÿçöÎ&^&³ ÿÿÿçõÎ&³&[ 1ÿÿÿçõÎ&¼&[ FÿÿÿëõÎ&³&[ [ÿÿÿìõÎ&¼&a pÿÿÿíõÎ&³&[ …ÿÿÿçõÎ&¼&[ šÿÿÿëÐÎ&¤dÿÿÿëÐÎ&j&µ zÿÿÿëÐÎ&µ&j ÿÿÿèìÎ&µ&j ¤ÿÿÿðÕÎ&¬&g ¹ÿÿÿéãÎ&µ&j ÎÿÿÿçßÎ&¾&p ãÿÿÿïÕÎ&µ&j øÿÿÿíÕÎ&¾&p ÿÿÿëÐÎ&¾&p "ÿÿÿíÐÎ&¾&p 7ÿÿÿíÐÎ&¾&p LÿÿÿìéÎ&¾&p aÿÿÿíÔÎ&¾&p vÿÿÿíÕÎ&¾&p ‹ÿÿÿçÛÎ&¾&p  ÿÿÿïÐÎ&µ&j µÿÿÿîÐÎ&µ&j ÊÿÿÿèìÎ&¾&p ßÿÿÿèàÎ&µ&m ôÿÿÿééÎ&µ&j ÿÿÿçÔÎ&µ&m ÿÿÿçÞÎ&µ&j 3ÿÿÿçÞÎ&¾&j HÿÿÿëÐÎ&µ&j ]ÿÿÿìÕÎ&¾&p rÿÿÿíØÎ&µ&j ‡ÿÿÿçÜÎ&¾&j œÿÿÿëöÎ&¢sÿÿÿëöÎ&³&y xÿÿÿëöÎ&³&y ÿÿÿèöÎ&³&y ¢ÿÿÿðöÎ&«&v ·ÿÿÿêöÎ&³&y ÌÿÿÿçöÎ&¼& áÿÿÿïöÎ&³&y öÿÿÿíöÎ&¼& ÿÿÿëöÎ&¼& ÿÿÿíöÎ&¼& 5ÿÿÿíöÎ&¼& JÿÿÿìöÎ&¼& _ÿÿÿíöÎ&¼& tÿÿÿíöÎ&¼& ‰ÿÿÿçöÎ&¼& žÿÿÿïöÎ&³&y ³ÿÿÿîöÎ&³&y ÈÿÿÿèöÎ&¼& ÝÿÿÿèöÎ&³&| òÿÿÿéöÎ&³&y ÿÿÿçöÎ&³&| ÿÿÿçöÎ&³&y 1ÿÿÿçöÎ&¼&y FÿÿÿëöÎ&³&y [ÿÿÿìöÎ&¼& pÿÿÿíöÎ&³&y …ÿÿÿçöÎ&¼&y šÿÿÿëÐÎ&¤‚ÿÿÿëÐÎ&µ&ˆ zÿÿÿëÐÎ&µ&ˆ ÿÿÿèìÎ&µ&ˆ ¤ÿÿÿðÕÎ&¬&… ¹ÿÿÿéãÎ&µ&ˆ ÎÿÿÿçßÎ&¾&Ž ãÿÿÿïÕÎ&µ&ˆ øÿÿÿíÕÎ&¾&Ž ÿÿÿëÐÎ&¾&Ž "ÿÿÿíÐÎ&¾&Ž 7ÿÿÿíÐÎ&¾&Ž LÿÿÿìéÎ&¾&Ž aÿÿÿíÔÎ&¾&Ž vÿÿÿíÕÎ&¾&Ž ‹ÿÿÿçÛÎ&¾&Ž  ÿÿÿïÐÎ&µ&ˆ µÿÿÿîÐÎ&µ&ˆ ÊÿÿÿèìÎ&¾&Ž ßÿÿÿèàÎ&µ&‹ ôÿÿÿééÎ&µ&ˆ ÿÿÿçÔÎ&µ&‹ ÿÿÿçÞÎ&µ&ˆ 3ÿÿÿçÞÎ&¾&ˆ HÿÿÿëÐÎ&µ&ˆ ]ÿÿÿìÕÎ&¾&Ž rÿÿÿíØÎ&µ&ˆ ‡ÿÿÿçÜÎ&¾&ˆ œÿÿÿëÐÎ&£™ÿÿÿëÐÎ&´&¨ yÿÿÿëÐÎ&´&¨ ŽÿÿÿèìÎ&´&¨ £ÿÿÿðÕÍ&«& ¸ ÿÿÿéãÎ&´&¨ ÍÿÿÿçßÎ&½&² âÿÿÿïÕÎ&´&¨ ÷ÿÿÿíÕÎ&´& ²ÿÿÿëÐÎ&½&² !ÿÿÿíÐÎ&½&² 6ÿÿÿíÐÎ&½&² KÿÿÿìéÎ&½&² `ÿÿÿíÔÎ&½&² uÿÿÿíÕÎ&½&² ŠÿÿÿçÛÎ&½&² ŸÿÿÿïÐÎ&²&½ ´ÿÿÿîÐÎ&½&² ÉÿÿÿèìÎ&½&² ÞÿÿÿèãÎ& ó&¼´ÿÿÿééÎ&´&¨ ÿÿÿçÔÎ&´&¼ ÿÿÿçÞÎ&´&¨ 2ÿÿÿçÞÎ&½&² GÿÿÿëÐÎ&´&¨ \ÿÿÿìÕÎ&½&² qÿÿÿíØÎ&´&¨ †ÿÿÿçÖÎ&½&² ›ÿÿÿëÐÎ&¤ÅÿÿÿëÐÎ&µ&Ø zÿÿÿëÐÎ&µ&Ø ÿÿÿèìÎ&µ&Ø ¤ÿÿÿðÕÎ&¬&Î ¹ÿÿÿéãÎ&µ&Ø ÎÿÿÿçßÎ&¾&â ãÿÿÿïÕÎ&µ&Ø øÿÿÿíÕÎ&¾&â ÿÿÿëÐÎ&¾&â "ÿÿÿíÐÎ&¾&â 7ÿÿÿíÐÎ&¾&â LÿÿÿìéÎ&¾&â aÿÿÿíÔÎ&¾&â vÿÿÿíÕÎ&¾&â ‹ÿÿÿçÛÎ&¾&â  ÿÿÿïÐÎ&µ&Ø µÿÿÿîÐÎ&µ&Ø ÊÿÿÿèìÎ&¾&â ßÿÿÿèàÎ&µ&ì ôÿÿÿééÎ&µ&Ø ÿÿÿçÔÎ&µ&ì ÿÿÿçÞÎ&µ&Ø 3ÿÿÿçÞÎ&¾&â HÿÿÿëÐÎ&µ&Ø ]ÿÿÿìÕÎ&¾&â rÿÿÿíØÎ&µ&Ø ‡ÿÿÿçÜÎ&¾&â œÿÿÿëÐÎ&£öÿÿÿëÐÎ&´&  yÿÿÿëÐÎ&´&  ŽÿÿÿèìÎ&´&  £ÿÿÿðÕÎ&´&ÿ ¸ÿÿÿéãÎ&´&  ÍÿÿÿçßÎ&½&  âÿÿÿïÕÎ&´&  ÷ÿÿÿíÕÎ&½&  ÿÿÿëÐÎ&½&  !ÿÿÿíÐÎ&½&  6ÿÿÿíÐÎ&½&  KÿÿÿìéÎ&½&  `ÿÿÿíÔÎ&½&  uÿÿÿíÕÎ&½&  ŠÿÿÿçÛÎ&½&  ŸÿÿÿïÐÎ&´&  ´ÿÿÿîÐÎ&´&  ÉÿÿÿèìÎ&½&  ÞÿÿÿèãÎ&´&  óÿÿÿééÎ&´&  ÿÿÿçÔÎ&´&  ÿÿÿçÞÎ&´&  2ÿÿÿçÞÎ&½&  GÿÿÿëÐÎ&´&  \ÿÿÿìÕÎ&½&  qÿÿÿíØÎ&´&  †ÿÿÿçÖÎ&½&  ›ÿÿÿëÐÎ&¤ %ÿÿÿëÐÎ&µ& 9 zÿÿÿëÐÎ&µ& 9 ÿÿÿèìÎ&µ& 9 ¤ÿÿÿðÕÎ&¬& / ¹ÿÿÿéãÎ&µ& 9 ÎÿÿÿçßÎ&¾& C ãÿÿÿïÕÎ&µ& 9 øÿÿÿíÕÎ&¾& C ÿÿÿëÐÎ&¾& C "ÿÿÿíÐÎ&¾& C 7ÿÿÿíÐÎ&¾& C LÿÿÿìéÎ&¾& C aÿÿÿíÔÎ&¾& C vÿÿÿíÕÎ&¾& C ‹ÿÿÿçÛÎ&¾& C  ÿÿÿïÐÎ&µ& 9 µÿÿÿîÐÎ&µ& 9 ÊÿÿÿèìÎ&¾& C ßÿÿÿèàÎ&µ& M ôÿÿÿééÎ&µ& 9 ÿÿÿçÔÎ&µ& M ÿÿÿçÞÎ&µ& 9 3ÿÿÿçÞÎ&¾& C HÿÿÿëÐÎ&µ& 9 ]ÿÿÿìÕÎ&¾& C rÿÿÿíØÎ&µ& 9 ‡ÿÿÿçÜÎ&¾& C œÿÿ ê½&¥ WÿÿÿëêÉ&¶& h {ÿÿÿëêÉ&¶& h ÿÿÿêêÉ&¶& h ¥ÿÿÿðêÉ& ^& º­ÿÿÿéêÉ&¶& h ÏÿÿÿçêÉ&¿& r äÿÿÿïêÉ&¶& h ùÿÿÿíêÉ&¿& r ÿÿÿëêÉ&¿& r #ÿÿÿíêÉ&¿& r 8ÿÿÿðêÉ&¿& r MÿÿÿìêÉ&¿& r bÿÿÿíêÉ&¿& r wÿÿÿíêÉ&¿& r ŒÿÿÿçêÉ&¿& r ¡ÿÿÿïêÉ&¶& h ¶ÿÿÿîêÉ&¶& h ËÿÿÿèìÉ&¿& r àÿÿÿèêÉ&¶& { õÿÿÿéêÉ&¶& h ÿÿÿçêÉ&¶& { ÿÿÿçêÉ&¶& h 4ÿÿÿçêÉ&¿& r IÿÿÿëêÉ&¶& h ^ÿÿÿìêÉ&¿& r sÿÿÿíêÉ&¶& h ˆÿÿÿçêÉ&¿& r ÿÿÿëöÎ&§ …ÿÿÿëöÎ&¸& ™ |ÿÿÿëöÎ&¸& ™ ‘ÿÿÿêöÎ&¸& ™ ¦ÿÿÿðöÎ&¯&  »ÿÿÿêöÎ&¸& ™ ÐÿÿÿçöÎ&Á& £ åÿÿÿïöÎ&¸& ™ úÿÿÿíöÎ&Á& £ ÿÿÿëöÎ&Á& £ $ÿÿÿíöÎ&Á& £ 9ÿÿÿíöÎ&Á& £ NÿÿÿìöÎ&Á& £ cÿÿÿîöÎ&Á& £ xÿÿÿíöÎ&Á& £ ÿÿÿçöÎ&Á& £ ¢ÿÿÿïöÎ&¸& ™ ·ÿÿÿîöÎ&¸& ™ ÌÿÿÿèöÎ&Á& £ áÿÿÿèöÎ&¸& ­ öÿÿÿéöÎ&¸& ™ ÿÿÿçöÎ&¸& ­ ÿÿÿçöÎ&¸& ™ 5ÿÿÿçöÎ&Á& £ JÿÿÿëöÎ&¸& ™ _ÿÿÿìöÎ&Á& £ tÿÿÿíöÎ&¸& ™ ‰ÿÿÿçöÎ&Á& £ žÿÿÿëÐÎ&© ·ÿÿÿëÐÎ&º& Ë |ÿÿÿëÐÎ&º& Ë ‘ÿÿÿêëÎ&º& Ë ¦ÿÿÿðÕÎ&±& Á »ÿÿÿêãÎ&º& Ë ÐÿÿÿçßÎ&Ã& Õ åÿÿÿïÕÎ&º& Ë úÿÿÿíÕÎ&Ã& Õ ÿÿÿëÐÎ&Ã& Õ $ÿÿÿíÐÎ&Ã& Õ 9ÿÿÿíÐÎ&Ã& Õ NÿÿÿìéÎ&Ã& Õ cÿÿÿîÔÎ&Ã& Õ xÿÿÿíÕÎ&Ã& Õ ÿÿÿçÛÎ&Ã& Õ ¢ÿÿÿïÐÎ&º& Ë ·ÿÿÿîÐÎ&º& Ë ÌÿÿÿèéÎ&Ã& Õ áÿÿÿèãÎ&º& ß öÿÿÿééÎ&º& Ë ÿÿÿçÔÎ&º& ß ÿÿÿçâÎ&º& Ë 5ÿÿÿçâÎ&Ã& Õ JÿÿÿëÐÎ&º& Ë _ÿÿÿìÕÎ&Ã& Õ tÿÿÿíØÎ&º& Ë ‰ÿÿÿçÜÎ&Ã& Õ žÿÿÿëÐÎ&§ éÿÿÿëÐÎ&¸& ü |ÿÿÿëÐÎ&¸& ü ‘ÿÿÿêëÎ&¸& ü ¦ÿÿÿðÕÎ&¯& ò »ÿÿÿêãÎ&¸& ü ÐÿÿÿçßÎ&Á&  åÿÿÿïÕÎ&¸& ü úÿÿÿíÕÎ&Á&  ÿÿÿëÐÎ&Á&  $ÿÿÿíÐÎ&Á&  9ÿÿÿíÐÎ&Á&  NÿÿÿìéÎ&Á&  cÿÿÿîÔÎ&Á&  xÿÿÿíÕÎ&Á&  ÿÿÿçÛÎ&Á&  ¢ÿÿÿïÐÎ&¸& ü ·ÿÿÿîÐÎ&¸& ü ÌÿÿÿèéÎ&Á&  áÿÿÿèãÎ&¸&  öÿÿÿééÎ&¸& ü ÿÿÿçÔÎ&¸&  ÿÿÿçâÎ&¸& ü 5ÿÿÿçâÎ&Á&  JÿÿÿëÐÎ&¸& ü _ÿÿÿìÕÎ&Á&  tÿÿÿíØÎ&¸& ü ‰ÿÿÿçÜÎ&Á&  žÿÿ ë½&¥ ÿÿÿëëÉ&¶& . {ÿÿÿëëÉ&¶& . ÿÿÿêëÉ&¶& . ¥ÿÿÿðëÉ&­& $ ºÿÿÿéëÉ&¶& . ÏÿÿÿçëÉ&¿& 8 äÿÿÿïëÉ&¶& . ùÿÿÿíëÉ&¿& 8 ÿÿÿëëÉ&¿& 8 #ÿÿÿíëÉ&¿& 8 8ÿÿÿðëÉ&¿& 8 MÿÿÿìëÉ&¿& 8 bÿÿÿíëÉ&¿& 8 wÿÿÿíëÉ&¿& 8 ŒÿÿÿçëÉ&¿& 8 ¡ÿÿÿïëÉ&¶& . ¶ÿÿÿîëÉ&¶& . ËÿÿÿèìÉ&¿& 8 àÿÿÿèëÉ&¶& B õÿÿÿéëÉ&¶& . ÿÿÿçëÉ&¶& B ÿÿÿçëÉ&¶& . 4ÿÿÿçëÉ&¿& 8 IÿÿÿëëÉ&¶& . ^ÿÿÿìëÉ&¿& 8 sÿÿÿíëÉ&¶& . ˆÿÿÿçëÉ&¿& 8 ÿÿÿëëÉ&¦ JÿÿÿëëÉ&·& V {ÿÿÿëëÉ&·& V ÿÿÿêëÉ&·& V ¥ÿÿÿðëÉ& Q&® ºÿÿÿéëÉ&·& V ÏÿÿÿçëÉ&À& k äÿÿÿïëÉ&·& V ùÿÿÿíëÉ&·& V ÿÿÿëëÉ&À& k #ÿÿÿíëÉ&À& k 8ÿÿÿðëÉ&À& k MÿÿÿìëÉ&À& k bÿÿÿíëÉ&À& k wÿÿÿíëÉ&À& k ŒÿÿÿçëÉ&À& k ¡ÿÿÿïëÉ&·& V ¶ÿÿÿîëÉ&·& V ËÿÿÿèìÉ&À& k àÿÿÿèëÉ&·& d õÿÿÿéëÉ&·& V ÿÿÿçëÉ&·& V ÿÿÿçëÉ&·& V 4ÿÿÿçëÉ&À& ] IÿÿÿëëÉ&·& V ^ÿÿÿìëÉ&·& k sÿÿÿíëÉ&·& V ˆÿÿÿçëÉ&À& ] ÿÿÿëÐÎ&¨ rÿÿÿëÐÎ&¹& € ~ÿÿÿëÐÎ&¹& € “ÿÿÿêëÎ&¹& € ¨ÿÿÿðÕÎ&°& y ½ÿÿÿéãÎ&¹& € ÒÿÿÿçßÎ&Â& • çÿÿÿïÕÎ&¹& € üÿÿÿíÕÎ&Â& • ÿÿÿëÐÎ&Â& • &ÿÿÿíÐÎ&Â& • ;ÿÿÿíÐÎ&Â& • PÿÿÿëéÎ&Â& • eÿÿÿîÔÎ&Â& • zÿÿÿíÖÎ&Â& • ÿÿÿçÛÎ&Â& • ¤ÿÿÿïÐÎ&¹& € ¹ÿÿÿîÐÎ&¹& € ÎÿÿÿêéÎ&Â& • ãÿÿÿèãÎ&¹& Ž øÿÿÿçéÎ&¹& € ÿÿÿçÔÎ&¹& Ž "ÿÿÿçÞÎ&¹& € 7ÿÿÿíÞÎ&Â& ‡ LÿÿÿëÐÎ&¹& € aÿÿÿìÕÎ&Â& • vÿÿÿíØÎ&¹& € ‹ÿÿÿçÜÎ&Â& ‡  ÿÿÿëÐÎ&ª œÿÿÿëÐÎ&»& ª ~ÿÿÿëÐÎ&»& ª “ÿÿÿêëÎ&»& ª ¨ÿÿÿðÕÎ&²& £ ½ÿÿÿéãÎ&»& ª ÒÿÿÿçßÎ&Ä& ¿ çÿÿÿïÕÎ&»& ª üÿÿÿíÕÎ&Ä& ¿ ÿÿÿëÐÎ&Ä& ¿ &ÿÿÿíÐÎ&Ä& ¿ ;ÿÿÿíÐÎ&Ä& ¿ PÿÿÿëéÎ&Ä& ¿ eÿÿÿîÔÎ&Ä& ¿ zÿÿÿíÖÎ&Ä& ¿ ÿÿÿçÛÎ&Ä& ¿ ¤ÿÿÿïÐÎ&»& ª ¹ÿÿÿîÐÎ&»& ª ÎÿÿÿêéÎ&Ä& ¿ ãÿÿÿèãÎ&»& ¸ øÿÿÿçéÎ&»& ª ÿÿÿçÔÎ&»& ¸ "ÿÿÿçÞÎ&»& ª 7ÿÿÿíÞÎ&Ä& ± LÿÿÿëÐÎ&»& ª aÿÿÿìÕÎ&Ä& ¿ vÿÿÿíØÎ&»& ª ‹ÿÿÿçÜÎ&Ä& ±  ÿÿÿëÐÎ&¨ ÆÿÿÿëÐÎ&¹& Ô }ÿÿÿëÐÎ&¹& Ô ’ÿÿÿêëÎ&¹& Ô §ÿÿÿðÕÎ&°& Í ¼ÿÿÿéãÎ&¹& Ô ÑÿÿÿçßÎ&Â& é æÿÿÿïÕÎ&¹& Ô ûÿÿÿíÕÎ&Â& é ÿÿÿëÐÎ&Â& é %ÿÿÿíÐÎ&Â& é :ÿÿÿíÐÎ&Â& é OÿÿÿëèÎ&Â& é dÿÿÿîÔÎ&Â& é yÿÿÿíÖÎ&Â& é ŽÿÿÿçÛÎ&Â& é £ÿÿÿïÐÎ&¹& Ô ¸ÿÿÿîÐÎ&¹& Ô ÍÿÿÿêéÎ&Â& é âÿÿÿèãÎ&¹& â ÷ÿÿÿçéÎ&¹& Ô ÿÿÿçÔÎ&¹& â !ÿÿÿçÞÎ&¹& Ô 6ÿÿÿçÞÎ&Â& Û KÿÿÿëÐÎ&¹& Ô `ÿÿÿìÕÎ&Â& é uÿÿÿíØÎ&¹& Ô ŠÿÿÿçÖÎ&Â& Û ŸÿÿÿíëÉ&¦ ðÿÿÿëëÉ&·& þ {ÿÿÿëëÉ&·& þ ÿÿÿêëÉ&·& þ ¥ÿÿÿðëÉ&®& ÷ ºÿÿÿéëÉ&·& þ ÏÿÿÿçëÉ&À&  äÿÿÿïëÉ&·& þ ùÿÿÿíëÉ&À&  ÿÿÿëëÉ&À&  #ÿÿÿíëÉ&À&  8ÿÿÿðëÉ&À&  MÿÿÿìëÉ&À&  bÿÿÿíëÉ&À&  wÿÿÿíëÉ&À&  ŒÿÿÿçëÉ&À&  ¡ÿÿÿïëÉ&·& þ ¶ÿÿÿîëÉ&·& þ ËÿÿÿèìÉ&À&  àÿÿÿèëÉ&·&  õÿÿÿéëÉ&·& þ ÿÿÿçëÉ&·&  ÿÿÿçëÉ&·& þ 4ÿÿÿçëÉ&À&  IÿÿÿëëÉ&·& þ ^ÿÿÿìëÉ&À&  sÿÿÿíëÉ&·& þ ˆÿÿÿçëÉ&À&  ÿÿ ë½&¥ ÿÿÿëëÉ&¶& ' {ÿÿÿëëÉ&¶& ' ÿÿÿêëÉ&¶& ' ¥ÿÿÿðëÉ&­&  ºÿÿÿéëÉ&¶& ' ÏÿÿÿçëÉ&¿& < äÿÿÿïëÉ&¶& ' ùÿÿÿíëÉ&¿& < ÿÿÿëëÉ&¿& < #ÿÿÿíëÉ&¿& < 8ÿÿÿðëÉ&¿& < MÿÿÿìëÉ&¿& < bÿÿÿíëÉ&¿& < wÿÿÿíëÉ&¿& < ŒÿÿÿçëÉ&¿& < ¡ÿÿÿïëÉ&¶& ' ¶ÿÿÿîëÉ&¶& ' ËÿÿÿèìÉ&¿& < àÿÿÿèëÉ&¶& 5 õÿÿÿéëÉ&¶& ' ÿÿÿçëÉ&¶& 5 ÿÿÿçëÉ&¶& ' 4ÿÿÿçëÉ&¿& . IÿÿÿëëÉ&¶& ' ^ÿÿÿìëÉ&¿& < sÿÿÿíëÉ&¶& ' ˆÿÿÿçëÉ&¿& . ÿÿÿëÐÎ&§ CÿÿÿëÐÎ&¸& P |ÿÿÿëÐÎ&¸& P ‘ÿÿÿêëÎ&¸& P ¦ÿÿÿðÕÎ&¯& I »ÿÿÿêãÎ&¸& P ÐÿÿÿçßÎ&Á& e åÿÿÿïÕÎ&¸& P úÿÿÿíÕÎ&Á& e ÿÿÿëÐÎ&Á& e $ÿÿÿíÐÎ&Á& e 9ÿÿÿíÐÎ&Á& e NÿÿÿìéÎ&Á& e cÿÿÿîÔÎ&Á& e xÿÿÿíÕÎ&Á& e ÿÿÿçÛÎ&Á& e ¢ÿÿÿïÐÎ&¸& P ·ÿÿÿîÐÎ&¸& P ÌÿÿÿèéÎ&Á& e áÿÿÿèãÎ&¸& ^ öÿÿÿééÎ&¸& P ÿÿÿçÔÎ&¸& ^ ÿÿÿçâÎ&¸& P 5ÿÿÿçâÎ&Á& W JÿÿÿëÐÎ&¸& P _ÿÿÿìÕÎ&Á& e tÿÿÿíØÎ&¸& P ‰ÿÿÿçÜÎ&Á& W žÿÿÿëÐÎ&¢ iÿÿÿëÐÎ&³& o yÿÿÿëÐÎ&³& o ŽÿÿÿèìÎ&³& o £ÿÿÿðÕÎ&«& l ¸ÿÿÿéãÎ&³& o ÍÿÿÿçßÎ&¼& u âÿÿÿïÕÎ&³& o ÷ÿÿÿíÕÎ&¼& u ÿÿÿëÐÎ&¼& u !ÿÿÿíÐÎ&¼& u 6ÿÿÿíÐÎ&¼& u KÿÿÿìéÎ&¼& u `ÿÿÿíÔÎ&¼& u uÿÿÿíÕÎ&¼& u ŠÿÿÿçÛÎ&¼& u ŸÿÿÿïÐÎ&³& o ´ÿÿÿîÐÎ&³& o ÉÿÿÿèìÎ&¼& u ÞÿÿÿèãÎ&³& r óÿÿÿééÎ&³& o ÿÿÿçÔÎ&³& r ÿÿÿçÞÎ& o&³ 2ÿÿÿçÞÎ&¼& o GÿÿÿëÐÎ&³& o \ÿÿÿìÕÎ&¼& u qÿÿÿíØÎ&³& o †ÿÿÿçÖÎ&¼& o ›ÿÿÿëöÎ&ÅWÿÿÿëöÎ&×&] xÿÿÿëöÎ&×&] ÿÿÿèöÎ&×&] ¢ÿÿÿðöÎ&Î&Z ·ÿÿÿêöÎ&×&] ÌÿÿÿçõÎ&à&c áÿÿÿïöÎ&×&] öÿÿÿíõÎ&à&c ÿÿÿëõÎ&à&c ÿÿÿíõÎ&à&c 5ÿÿÿíõÎ&à&c JÿÿÿìõÎ&à&c _ÿÿÿíõÎ&à&c tÿÿÿíõÎ&à&c ‰ÿÿÿçõÎ&à&c žÿÿÿïöÎ&×&] ³ÿÿÿîöÎ&×&] ÈÿÿÿèõÎ&à&c ÝÿÿÿèõÎ&×&` òÿÿÿéöÎ&×&] ÿÿÿçõÎ&×&` ÿÿÿçöÎ&×&] 1ÿÿÿçöÎ&à&] FÿÿÿëöÎ&×&] [ÿÿÿìõÎ&à&c pÿÿÿíöÎ&×&] …ÿÿÿçöÎ&à&] šÿÿÿëÑÎ&ÇfÿÿÿëÑÎ&Ù&l zÿÿÿëÑÎ&Ù&l ÿÿÿèìÎ&Ù&l ¤ÿÿÿðÕÎ&Ð&i ¹ÿÿÿéãÎ&Ù&l ÎÿÿÿçßÎ&â&r ãÿÿÿïÕÎ&Ù&l øÿÿÿíÕÎ&â&r ÿÿÿëÑÎ&â&r "ÿÿÿíÑÎ&â&r 7ÿÿÿíÑÎ&â&r LÿÿÿìéÎ&â&r aÿÿÿíÔÎ&â&r vÿÿÿíÕÎ&â&r ‹ÿÿÿçÛÎ&â&r  ÿÿÿïÑÎ&Ù&l µÿÿÿîÑÎ&Ù&l ÊÿÿÿèìÎ&â&r ßÿÿÿèàÎ&Ù&o ôÿÿÿééÎ&Ù&l ÿÿÿçÔÎ&Ù&o ÿÿÿçÞÎ&Ù&l 3ÿÿÿçÞÎ&â&l HÿÿÿëÑÎ&Ù&l ]ÿÿÿìÕÎ&â&r rÿÿÿíØÎ&Ù&l ‡ÿÿÿçÜÎ&â&l œÿÿÿëöÎ&ÅuÿÿÿëöÎ&×&{ xÿÿÿëöÎ&×&{ ÿÿÿèöÎ&×&{ ¢ÿÿÿðöÎ&Î&x ·ÿÿÿêöÎ&×&{ ÌÿÿÿçöÎ&à& áÿÿÿïöÎ&×&{ öÿÿÿíöÎ&à& ÿÿÿëöÎ&à& ÿÿÿíöÎ&à& 5ÿÿÿíöÎ&à& JÿÿÿìöÎ&à& _ÿÿÿíöÎ&à& tÿÿÿíöÎ&à& ‰ÿÿÿçöÎ&à& žÿÿÿïöÎ&×&{ ³ÿÿÿîöÎ&×&{ ÈÿÿÿèöÎ&à& ÝÿÿÿèöÎ&×&~ òÿÿÿéöÎ&×&{ ÿÿÿçöÎ&×&~ ÿÿÿçöÎ&×&{ 1ÿÿÿçöÎ&à&{ FÿÿÿëöÎ&×&{ [ÿÿÿìöÎ&à& pÿÿÿíöÎ&×&{ …ÿÿÿçöÎ&à&{ šÿÿÿëÐÎ&Ç„ÿÿÿëÑÎ&Ù&Š zÿÿÿëÑÎ&Ù&Š ÿÿÿèìÎ&Ù&Š ¤ÿÿÿðÕÎ&Ð&‡ ¹ÿÿÿéãÎ&Ù&Š ÎÿÿÿçßÎ&â& ãÿÿÿïÕÎ&Ù&Š øÿÿÿíÕÎ&â& ÿÿÿëÑÎ&â& "ÿÿÿíÑÎ&â& 7ÿÿÿíÑÎ&â& LÿÿÿìéÎ&â& aÿÿÿíÔÎ&â& vÿÿÿíÕÎ&â& ‹ÿÿÿçÛÎ&â&  ÿÿÿïÑÎ&Ù&Š µÿÿÿîÑÎ&Ù&Š ÊÿÿÿèìÎ&â& ßÿÿÿèàÎ&Ù& ôÿÿÿééÎ&Ù&Š ÿÿÿçÔÎ&Ù& ÿÿÿçÞÎ&Ù&Š 3ÿÿÿçÞÎ&â&Š HÿÿÿëÑÎ&Ù&Š ]ÿÿÿìÕÎ&â& rÿÿÿíØÎ&Ù&Š ‡ÿÿÿçÜÎ&â&Š œÿÿÿëÑÎ&ƘÿÿÿëÐÎ&Ø&« yÿÿÿëÐÎ&Ø&« ŽÿÿÿèìÎ&Ø&« £ÿÿÿðÕÎ&Ï&¡ ¸ÿÿÿéãÎ&Ø&« ÍÿÿÿçßÎ&á&µ âÿÿÿïÕÎ&Ø&« ÷ÿÿÿíÕÎ&á&µ ÿÿÿëÐÎ&á&µ !ÿÿÿíÐÎ&á&µ 6ÿÿÿíÐÎ&á&µ KÿÿÿìéÎ&á&µ `ÿÿÿíÔÎ&á&µ uÿÿÿíÕÎ&á&µ ŠÿÿÿçÛÎ&á&µ ŸÿÿÿïÐÎ&Ø&« ´ÿÿÿîÐÎ&Ø&« ÉÿÿÿèìÎ&á&µ ÞÿÿÿèãÎ&Ø&¿ óÿÿÿééÎ&Ø&« ÿÿÿçÔÎ&Ø&¿ ÿÿÿçÞÎ&Ø&« 2ÿÿÿçÞÎ&á&µ GÿÿÿëÐÎ&Ø&« \ÿÿÿìÕÎ&á&µ qÿÿÿíØÎ&Ø&« †ÿÿÿçÖÎ&á&µ ›ÿÿÿëÐÎ&ÇÈÿÿÿëÑÎ&Ù&Û zÿÿÿëÑÎ&Ù&Û ÿÿÿèìÎ&Ù&Û ¤ÿÿÿðÕÎ&Ð&Ñ ¹ÿÿÿéãÎ&Ù&Û ÎÿÿÿçßÎ&â&å ãÿÿÿïÕÎ&Ù&Û øÿÿÿíÕÎ&â&å ÿÿÿëÑÎ&â&å "ÿÿÿíÑÎ&â&å 7ÿÿÿíÑÎ&â&å LÿÿÿìéÎ&â&å aÿÿÿíÔÎ&â&å vÿÿÿíÕÎ&â&å ‹ÿÿÿçÛÎ&â&å  ÿÿÿïÑÎ&Ù&Û µÿÿÿîÑÎ&Ù&Û ÊÿÿÿèìÎ&â&å ßÿÿÿèàÎ&Ù&ï ôÿÿÿééÎ&Ù&Û ÿÿÿçÔÎ&Ù&ï ÿÿÿçÞÎ&Ù&Û 3ÿÿÿçÞÎ&â&å HÿÿÿëÑÎ&Ù&Û ]ÿÿÿìÕÎ&â&å rÿÿÿíØÎ&Ù&Û ‡ÿÿÿçÜÎ&â&å œÿÿÿëÐÎ&ÆùÿÿÿëÐÎ&Ø&  yÿÿÿëÐÎ&Ø&  ŽÿÿÿèìÎ&Ø&  £ÿÿÿðÕÎ&Ï&  ¸ÿÿÿéãÎ&Ø&  ÍÿÿÿçßÎ&á&  âÿÿÿïÕÎ&Ø&  ÷ÿÿÿíÕÎ&á&  ÿÿÿëÐÎ&á&  !ÿÿÿíÐÎ&á&  6ÿÿÿíÐÎ&á&  KÿÿÿìéÎ&á&  `ÿÿÿíÔÎ&á&  uÿÿÿíÕÎ&á&  ŠÿÿÿçÛÎ&á&  ŸÿÿÿïÐÎ&Ø&  ´ÿÿÿîÐÎ&Ø&  ÉÿÿÿèìÎ&á&  ÞÿÿÿèãÎ&Ø&  óÿÿÿééÎ&Ø&  ÿÿÿçÔÎ&Ø&  ÿÿÿçÞÎ&Ø&  2ÿÿÿçÞÎ&á&  GÿÿÿëÐÎ&Ø&  \ÿÿÿìÕÎ&á&  qÿÿÿíØÎ&Ø&  †ÿÿÿçÖÎ&á&  ›ÿÿÿëÐÎ&Ç (ÿÿÿëÐÎ&Ù& < zÿÿÿëÐÎ&Ù& < ÿÿÿèìÎ&Ù& < ¤ÿÿÿðÕÎ&Ð& 2 ¹ÿÿÿéãÎ&Ù& < ÎÿÿÿçßÎ&â& F ãÿÿÿïÕÎ&Ù& < øÿÿÿíÕÎ&â& F ÿÿÿëÑÎ&â& F "ÿÿÿíÑÎ&â& F 7ÿÿÿíÑÎ&â& F LÿÿÿìéÎ&â& F aÿÿÿíÔÎ&â& F vÿÿÿíÕÎ&â& F ‹ÿÿÿçÛÎ&â& F  ÿÿÿïÐÎ&Ù& < µÿÿÿîÐÎ&Ù& < ÊÿÿÿèìÎ&â& F ßÿÿÿèàÎ&Ù& P ôÿÿÿééÎ&Ù& < ÿÿÿçÔÎ&Ù& P ÿÿÿçÞÎ&Ù& < 3ÿÿÿçÞÎ&â& F HÿÿÿëÐÎ&Ù& < ]ÿÿÿìÕÎ&â& F rÿÿÿíØÎ&Ù& < ‡ÿÿÿçÜÎ&â& F œÿÿ ê¼&È YÿÿÿëêÈ&Ú& k {ÿÿÿëêÈ&Ú& k ÿÿÿêêÈ&Ú& k ¥ÿÿÿðêÈ&Ñ& a ºÿÿÿéêÈ&Ú& k ÏÿÿÿçêÈ&ã& u äÿÿÿïêÈ&Ú& k ùÿÿÿíêÈ&ã& u ÿÿÿëêÈ&ã& u #ÿÿÿíêÈ&ã& u 8ÿÿÿðêÈ&ã& u MÿÿÿìêÈ&ã& u bÿÿÿíêÈ&ã& u wÿÿÿíêÈ&ã& u ŒÿÿÿçêÈ&ã& u ¡ÿÿÿïêÈ&Ú& k ¶ÿÿÿîêÈ&Ú& k ËÿÿÿèìÈ&ã& u àÿÿÿèêÈ&Ú& ~ õÿÿÿéêÈ&Ú& k ÿÿÿçêÈ&Ú& ~ ÿÿÿçêÈ&Ú& k 4ÿÿÿçêÈ&ã& u IÿÿÿëêÈ&Ú& k ^ÿÿÿìêÈ&ã& u sÿÿÿíêÈ&Ú& k ˆÿÿÿçêÈ&ã& u ÿÿÿëöÎ&Ê ˆÿÿÿëõÎ&Ü& œ |ÿÿÿëõÎ&Ü& œ ‘ÿÿÿêõÎ&Ü& œ ¦ÿÿÿðöÎ&Ó& ’ »ÿÿÿêõÎ&Ü& œ ÐÿÿÿçöÎ&å& ¦ åÿÿÿïõÎ&Ü& œ úÿÿÿíöÎ&å& ¦ ÿÿÿëöÎ&å& ¦ $ÿÿÿíöÎ&å& ¦ 9ÿÿÿíöÎ&å& ¦ NÿÿÿìöÎ&å& ¦ cÿÿÿîöÎ&å& ¦ xÿÿÿíöÎ&å& ¦ ÿÿÿçöÎ&å& ¦ ¢ÿÿÿïõÎ&Ü& œ ·ÿÿÿîõÎ&Ü& œ ÌÿÿÿèöÎ&å& ¦ áÿÿÿèöÎ&Ü& ° öÿÿÿéõÎ&Ü& œ ÿÿÿçöÎ&Ü& ° ÿÿÿçõÎ&Ü& œ 5ÿÿÿçöÎ&å& ¦ JÿÿÿëõÎ&Ü& œ _ÿÿÿìöÎ&å& ¦ tÿÿÿíõÎ&Ü& œ ‰ÿÿÿçöÎ&å& ¦ žÿÿÿëÐÎ&Ì ºÿÿÿëÐÎ&Þ& Î |ÿÿÿëÐÎ&Þ& Î ‘ÿÿÿêëÎ&Þ& Î ¦ÿÿÿðÕÎ&Õ& Ä »ÿÿÿêãÎ&Þ& Î ÐÿÿÿçßÎ&ç& Ø åÿÿÿïÕÎ&Þ& Î úÿÿÿíÕÎ&ç& Ø ÿÿÿëÐÎ&ç& Ø $ÿÿÿíÐÎ&ç& Ø 9ÿÿÿíÐÎ&ç& Ø NÿÿÿìéÎ&ç& Ø cÿÿÿîÔÎ&ç& Ø xÿÿÿíÕÎ&ç& Ø ÿÿÿçÛÎ&ç& Ø ¢ÿÿÿïÐÎ&Þ& Î ·ÿÿÿîÐÎ&Þ& Î ÌÿÿÿèéÎ&ç& Ø áÿÿÿèãÎ&Þ& â öÿÿÿééÎ&Þ& Î ÿÿÿçÔÎ&Þ& â ÿÿÿçâÎ&Þ& Î 5ÿÿÿçâÎ&ç& Ø JÿÿÿëÐÎ&Þ& Î _ÿÿÿìÕÎ&ç& Ø tÿÿÿíØÎ&Þ& Î ‰ÿÿÿçÜÎ&ç& Ø žÿÿÿëÑÎ&Ê ëÿÿÿëÐÎ&Ü& ÿ |ÿÿÿëÐÎ&Ü& ÿ ‘ÿÿÿêëÎ&Ü& ÿ ¦ÿÿÿðÕÎ&Ó& õ »ÿÿÿêãÎ&Ü& ÿ ÐÿÿÿçßÎ&å&  åÿÿÿïÕÎ&Ü& ÿ úÿÿÿíÕÎ&å&  ÿÿÿëÑÎ&å&  $ÿÿÿíÑÎ&å&  9ÿÿÿíÑÎ&å&  NÿÿÿìéÎ&å&  cÿÿÿîÔÎ&å&  xÿÿÿíÕÎ&å&  ÿÿÿçÛÎ&å&  ¢ÿÿÿïÐÎ&Ü& ÿ ·ÿÿÿîÐÎ&Ü& ÿ ÌÿÿÿèéÎ&å&  áÿÿÿèãÎ&Ü&  öÿÿÿééÎ&Ü& ÿ ÿÿÿçÔÎ&Ü&  ÿÿÿçâÎ&Ü& ÿ 5ÿÿÿçâÎ&å&  JÿÿÿëÐÎ&Ü& ÿ _ÿÿÿìÕÎ&å&  tÿÿÿíØÎ&Ü& ÿ ‰ÿÿÿçÜÎ&å&  žÿÿ ë¼&È ÿÿÿëëÈ&Ú& 1 {ÿÿÿëëÈ&Ú& 1 ÿÿÿêëÈ&Ú& 1 ¥ÿÿÿðëÈ&Ñ& ' ºÿÿÿéëÈ&Ú& 1 ÏÿÿÿçëÈ&ã& ; äÿÿÿïëÈ&Ú& 1 ùÿÿÿíëÈ&ã& ; ÿÿÿëëÈ&ã& ; #ÿÿÿíëÈ&ã& ; 8ÿÿÿðëÈ&ã& ; MÿÿÿìëÈ&ã& ; bÿÿÿíëÈ&ã& ; wÿÿÿíëÈ&ã& ; ŒÿÿÿçëÈ&ã& ; ¡ÿÿÿïëÈ&Ú& 1 ¶ÿÿÿîëÈ&Ú& 1 ËÿÿÿèìÈ&ã& ; àÿÿÿèëÈ&Ú& E õÿÿÿéëÈ&Ú& 1 ÿÿÿçëÈ&Ú& E ÿÿÿçëÈ&Ú& 1 4ÿÿÿçëÈ&ã& ; IÿÿÿëëÈ&Ú& 1 ^ÿÿÿìëÈ&ã& ; sÿÿÿíëÈ&Ú& 1 ˆÿÿÿçëÈ&ã& ; ÿÿÿëëÈ&É LÿÿÿëëÈ&Û& X {ÿÿÿëëÈ&Û& X ÿÿÿêëÈ&Û& X ¥ÿÿÿðëÈ&Ò& º QÿÿÿéëÈ&Û& X ÏÿÿÿçëÈ&ä& m äÿÿÿïëÈ&Û& X ùÿÿÿíëÈ&ä& m ÿÿÿëëÈ&ä& m #ÿÿÿíëÈ&ä& m 8ÿÿÿðëÈ&ä& m MÿÿÿìëÈ&ä& m bÿÿÿíëÈ&ä& m wÿÿÿíëÈ&ä& m ŒÿÿÿçëÈ&ä& m ¡ÿÿÿïëÈ&Û& X ¶ÿÿÿîëÈ&Û& X ËÿÿÿèìÈ&ä& m àÿÿÿèëÈ&Û& f õÿÿÿéëÈ&Û& X ÿÿÿçëÈ&Û& X ÿÿÿçëÈ&Û& X 4ÿÿÿçëÈ&ä& _ IÿÿÿëëÈ&Û& X ^ÿÿÿìëÈ&ä& m sÿÿÿíëÈ&Û& X ˆÿÿÿçëÈ&ä& _ ÿÿÿëÐÎ&Ë tÿÿÿëÐÎ&Ý& ‚ ~ÿÿÿëÐÎ&Ý& ‚ “ÿÿÿêëÎ&Ý& ‚ ¨ÿÿÿðÕÎ&Ô& { ½ÿÿÿéãÎ&Ý& ‚ ÒÿÿÿçßÎ&æ& — çÿÿÿïÕÎ&Ý& ‚ üÿÿÿíÕÎ&æ& — ÿÿÿëÐÎ&æ& — &ÿÿÿíÐÎ&æ& — ;ÿÿÿíÐÎ&æ& — PÿÿÿëéÎ&æ& — eÿÿÿîÔÎ&æ& — zÿÿÿíÖÎ&æ& — ÿÿÿçÛÎ&æ& — ¤ÿÿÿïÐÎ&Ý& ‚ ¹ÿÿÿîÐÎ&Ý& ‚ ÎÿÿÿêéÎ&æ& — ãÿÿÿèãÎ&Ý&  øÿÿÿçéÎ&Ý& ‚ ÿÿÿçÔÎ&Ý&  "ÿÿÿçÞÎ&Ý& ‚ 7ÿÿÿíÞÎ&æ& ‰ LÿÿÿëÐÎ&Ý& ‚ aÿÿÿìÕÎ&æ& — vÿÿÿíØÎ&Ý& ‚ ‹ÿÿÿçÜÎ&æ& ‰  ÿÿÿëÐÎ&Í žÿÿÿëÐÎ&ß& ¬ ~ÿÿÿëÐÎ&ß& ¬ “ÿÿÿêëÎ&ß& ¬ ¨ÿÿÿðÕÎ&Ö& ¥ ½ÿÿÿéãÎ&ß& ¬ ÒÿÿÿçßÎ&è& Á çÿÿÿïÕÎ&ß& ¬ üÿÿÿíÕÎ&è& Á ÿÿÿëÐÎ&è& Á &ÿÿÿíÐÎ&è& Á ;ÿÿÿíÐÎ&è& Á PÿÿÿëéÎ&è& Á eÿÿÿîÔÎ&è& Á zÿÿÿíÖÎ&è& Á ÿÿÿçÛÎ&è& Á ¤ÿÿÿïÐÎ&ß& ¬ ¹ÿÿÿîÐÎ&ß& ¬ ÎÿÿÿêéÎ&è& Á ãÿÿÿèãÎ&ß& º øÿÿÿçéÎ&ß& ¬ ÿÿÿçÔÎ&ß& º "ÿÿÿçÞÎ&ß& ¬ 7ÿÿÿíÞÎ&è& ³ LÿÿÿëÐÎ&ß& ¬ aÿÿÿìÕÎ&è& Á vÿÿÿíØÎ&ß& ¬ ‹ÿÿÿçÜÎ&è& ³  ÿÿÿëÑÎ&Ë ÈÿÿÿëÐÎ&Ý& Ö }ÿÿÿëÐÎ&Ý& Ö ’ÿÿÿêëÎ&Ý& Ö §ÿÿÿðÕÎ&Ô& Ï ¼ÿÿÿéãÎ&Ý& Ö ÑÿÿÿçßÎ&æ& ë æÿÿÿïÕÎ&Ý& Ö ûÿÿÿíÕÎ&æ& ë ÿÿÿëÐÎ&æ& ë %ÿÿÿíÐÎ&æ& ë :ÿÿÿíÐÎ&æ& ë OÿÿÿëèÎ&æ& ë dÿÿÿîÔÎ&æ& ë yÿÿÿíÖÎ&æ& ë ŽÿÿÿçÛÎ&æ& ë £ÿÿÿïÐÎ&Ý& Ö ¸ÿÿÿîÐÎ&Ý& Ö ÍÿÿÿêéÎ&æ& ë âÿÿÿèãÎ&Ý& ä ÷ÿÿÿçéÎ&Ý& Ö ÿÿÿçÔÎ&Ý& ä !ÿÿÿçÞÎ&Ý& Ö 6ÿÿÿçÞÎ&æ& Ý KÿÿÿëÐÎ&Ý& Ö `ÿÿÿìÕÎ&æ& ë uÿÿÿíØÎ&Ý& Ö ŠÿÿÿçÖÎ&æ& Ý ŸÿÿÿíëÈ&É òÿÿÿëëÈ&Û&  {ÿÿÿëëÈ&Û&  ÿÿÿêëÈ&Û&  ¥ÿÿÿðëÈ&Ò& ù ºÿÿÿéëÈ&Û&  ÏÿÿÿçëÈ&ä&  äÿÿÿïëÈ&Û&  ùÿÿÿíëÈ&ä&  ÿÿÿëëÈ&ä&  #ÿÿÿíëÈ&ä&  8ÿÿÿðëÈ&ä&  MÿÿÿìëÈ&ä&  bÿÿÿíëÈ&ä&  wÿÿÿíëÈ&ä&  ŒÿÿÿçëÈ&ä&  ¡ÿÿÿïëÈ&Û&  ¶ÿÿÿîëÈ&Û&  ËÿÿÿèìÈ&ä&  àÿÿÿèëÈ&Û&  õÿÿÿéëÈ&Û&  ÿÿÿçëÈ&Û&  ÿÿÿçëÈ&Û&  4ÿÿÿçëÈ&ä&  IÿÿÿëëÈ&Û&  ^ÿÿÿìëÈ&ä&  sÿÿÿíëÈ&Û&  ˆÿÿÿçëÈ&ä&  ÿÿ ë¼&È ÿÿÿëëÈ&Ú& ) {ÿÿÿëëÈ&Ú& ) ÿÿÿêëÈ&Ú& ) ¥ÿÿÿðëÈ&Ñ& " ºÿÿÿéëÈ&Ú& ) ÏÿÿÿçëÈ&ã& > äÿÿÿïëÈ&Ú& ) ùÿÿÿíëÈ&ã& > ÿÿÿëëÈ&ã& > #ÿÿÿíëÈ&ã& > 8ÿÿÿðëÈ&ã& > MÿÿÿìëÈ&ã& > bÿÿÿíëÈ&ã& > wÿÿÿíëÈ&ã& > ŒÿÿÿçëÈ&ã& > ¡ÿÿÿïëÈ&Ú& ) ¶ÿÿÿîëÈ&Ú& ) ËÿÿÿèìÈ&ã& > àÿÿÿèëÈ&Ú& 7 õÿÿÿéëÈ&Ú& ) ÿÿÿçëÈ&Ú& 7 ÿÿÿçëÈ&Ú& ) 4ÿÿÿçëÈ&ã& 0 IÿÿÿëëÈ&Ú& ) ^ÿÿÿìëÈ&ã& > sÿÿÿíëÈ&Ú& ) ˆÿÿÿçëÈ&ã& 0 ÿÿÿëÐÎ&Ê DÿÿÿëÐÎ&Ü& R |ÿÿÿëÐÎ&Ü& R ‘ÿÿÿêëÎ&Ü& R ¦ÿÿÿðÕÎ&Ó& K »ÿÿÿêãÎ&Ü& R ÐÿÿÿçßÎ&å& g åÿÿÿïÕÎ&Ü& R úÿÿÿíÕÎ&å& g ÿÿÿëÐÎ&å& g $ÿÿÿíÐÎ&å& g 9ÿÿÿíÐÎ&å& g NÿÿÿìéÎ&å& g cÿÿÿîÔÎ&å& g xÿÿÿíÕÎ&å& g ÿÿÿçÛÎ&å& g ¢ÿÿÿïÐÎ&Ü& R ·ÿÿÿîÐÎ&Ü& R ÌÿÿÿèéÎ&å& g áÿÿÿèãÎ&Ü& ` öÿÿÿééÎ&Ü& R ÿÿÿçÔÎ&Ü& ` ÿÿÿçâÎ&Ü& R 5ÿÿÿçâÎ&å& Y JÿÿÿëÐÎ&Ü& R _ÿÿÿìÕÎ&å& g tÿÿÿíØÎ&Ü& R ‰ÿÿÿçÜÎ&å& Y žÿÿÿëÑÎ&Å kÿÿÿëÐÎ&×& q yÿÿÿëÐÎ&×& q ŽÿÿÿèìÎ&×& q £ÿÿÿðÕÎ&Î& n ¸ÿÿÿéãÎ&×& q ÍÿÿÿçßÎ&à& w âÿÿÿïÕÎ&×& q ÷ÿÿÿíÕÎ&à& w ÿÿÿëÑÎ&à& w !ÿÿÿíÑÎ&à& w 6ÿÿÿíÑÎ&à& w KÿÿÿìéÎ&à& w `ÿÿÿíÔÎ&à& w uÿÿÿíÕÎ&à& w ŠÿÿÿçÛÎ&à& w ŸÿÿÿïÐÎ&×& q ´ÿÿÿîÐÎ&×& q ÉÿÿÿèìÎ&à& w ÞÿÿÿèãÎ&×& t óÿÿÿééÎ&×& q ÿÿÿçÔÎ&×& t ÿÿÿçÞÎ&×& q 2ÿÿÿçÞÎ&à& q GÿÿÿëÐÎ&×& q \ÿÿÿìÕÎ&à& w qÿÿÿíØÎ&×& q †ÿÿÿçÖÎ&à& q ›ÿÿÿëöÎ&Uéÿÿ ÿëõÎ&û&[ xÿÿ ÿëõÎ&û&[ ÿÿ ÿèõÎ&û&[ ¢ÿÿ ÿðöÎ&ò& ·Xÿÿ ÿêõÎ&û&[ Ìÿÿ ÿçõÎ&&a áÿÿ ÿïõÎ&û&[ öÿÿ ÿíõÎ&a& ûÿÿ ÿëõÎ&&a ÿÿ ÿíõÎ&&a 5ÿÿ ÿíõÎ&&a Jÿÿ ÿìõÎ&&a _ÿÿ ÿíõÎ&&a tÿÿ ÿíõÎ&&a ‰ÿÿ ÿçõÎ&&a žÿÿ ÿïõÎ&û&[ ³ÿÿ ÿîõÎ&û&[ Èÿÿ ÿèõÎ&&a Ýÿÿ ÿèöÎ&û&^ òÿÿ ÿéõÎ&û&[ ÿÿ ÿçöÎ&û&^ -ÿÿ ÿçõÎ&û&[ 1ÿÿ ÿçõÎ&&[ Fÿÿ ÿëõÎ&û&[ [ÿÿ ÿìõÎ&&a pÿÿ ÿíõÎ&û&[ …ÿÿ ÿçõÎ&&[ šÿÿ ÿëÐÎ&ëdÿÿÿëÐÎ&j&ý zÿÿÿëÐÎ&ý&j ÿÿÿèìÎ&ý&j ¤ÿÿÿðÕÎ&ô&g ¹ÿÿÿéãÎ&ý&j Îÿÿ ÿçßÎ&&p ãÿÿÿïÕÎ&ý&j øÿÿ ÿíÕÎ&&p ÿÿ ÿëÐÎ&&p "ÿÿ ÿíÐÎ&&p 7ÿÿ ÿíÐÎ&&p Lÿÿ ÿìéÎ&&p aÿÿ ÿíÔÎ&&p vÿÿ ÿíÕÎ&&p ‹ÿÿ ÿçÛÎ&&p  ÿÿÿïÐÎ&ý&j µÿÿÿîÐÎ&ý&j Êÿÿ ÿèìÎ&&p ßÿÿÿèàÎ&ý&m ôÿÿÿééÎ&ý&j ÿÿÿçÔÎ&m& ,ýÿÿÿçÞÎ&ý&j 3ÿÿ ÿçÞÎ&&j HÿÿÿëÐÎ&ý&j ]ÿÿ ÿìÕÎ&&p rÿÿÿíØÎ&ý&j ‡ÿÿ ÿçÜÎ&&j œÿÿÿëöÎ&ésÿÿ ÿëöÎ&û&y xÿÿ ÿëöÎ&û&y ÿÿ ÿèöÎ&û&y ¢ÿÿ ÿðöÎ&ò&v ·ÿÿ ÿêöÎ&û&y Ìÿÿ ÿçöÎ&& áÿÿ ÿïöÎ&û&y öÿÿ ÿíöÎ&& ÿÿ ÿëöÎ&& ÿÿ ÿíöÎ&& 5ÿÿ ÿíöÎ&& Jÿÿ ÿìöÎ&& _ÿÿ ÿíöÎ&& tÿÿ ÿíöÎ&& ‰ÿÿ ÿçöÎ&& žÿÿ ÿïöÎ&û&y ³ÿÿ ÿîöÎ&û&y Èÿÿ ÿèöÎ&& Ýÿÿ ÿèöÎ&û&| òÿÿ ÿéöÎ&û&y ÿÿ ÿçöÎ&û&| ÿÿ ÿçöÎ&û&y 1ÿÿ ÿçöÎ&&y Fÿÿ ÿëöÎ&û&y [ÿÿ ÿìöÎ&& pÿÿ ÿíöÎ&û&y …ÿÿ ÿçöÎ&&y šÿÿ ÿëÐÎ&ë‚ÿÿÿëÐÎ&ý&ˆ zÿÿÿëÐÎ&ý&ˆ ÿÿÿèìÎ&ý&ˆ ¤ÿÿÿðÕÎ&ô&… ¹ÿÿÿéãÎ&ý&ˆ Îÿÿ ÿçßÎ&&Ž ãÿÿÿïÕÎ&ý&ˆ øÿÿ ÿíÕÎ&&Ž ÿÿ ÿëÐÎ&&Ž "ÿÿ ÿíÐÎ&&Ž 7ÿÿ ÿíÐÎ&&Ž Lÿÿ ÿìéÎ&&Ž aÿÿ ÿíÔÎ&&Ž vÿÿ ÿíÕÎ&&Ž ‹ÿÿ ÿçÛÎ&&Ž  ÿÿÿïÐÎ&ý&ˆ µÿÿÿîÐÎ&ý&ˆ Êÿÿ ÿèìÎ&&Ž ßÿÿÿèàÎ&ý&‹ ôÿÿÿééÎ&ý&ˆ ÿÿÿçÔÎ&ý&‹ ÿÿÿçÞÎ&ý&ˆ 3ÿÿ ÿçÞÎ&&ˆ HÿÿÿëÐÎ&ý&ˆ ]ÿÿ ÿìÕÎ&&Ž rÿÿÿíØÎ&ý&ˆ ‡ÿÿ ÿçÜÎ&&ˆ œÿÿÿëÐÎ&ê‘ÿÿÿëÐÎ&¤& ‡üÿÿÿëÐÎ&ü&© ŽÿÿÿèìÎ&ü&© £ÿÿÿðÕÎ&ó& ¸›ÿÿÿéãÎ&ü&© ÍÿÿÿçßÎ&&³ âÿÿÿïÕÎ&ü&© ÷ÿÿÿíÕÎ&ü& ·ÿÿÿëÐÎ&&³ !ÿÿÿíÐÎ&&³ 6ÿÿÿíÐÎ&&³ KÿÿÿìéÎ&&³ `ÿÿÿíÔÎ&&³ uÿÿÿíÕÎ&&³ ŠÿÿÿçÛÎ&&³ ŸÿÿÿïÐÎ&ü&© ´ÿÿÿîÐÎ&ü&© ÉÿÿÿèìÎ&&³ ÞÿÿÿèãÎ&ü&½ óÿÿÿééÎ&ü&© ÿÿÿçÔÎ&À&ü ÿÿÿçÞÎ&ü&© 2ÿÿÿçÞÎ&&³ GÿÿÿëÐÎ&ü&© \ÿÿÿìÕÎ&&³ qÿÿÿíØÎ&ü&© †ÿÿÿçÖÎ&&³ ›ÿÿ ÿëÐÎ&ëÉÿÿÿëÐÎ&ý&Ù zÿÿÿëÐÎ&ý&Ù ÿÿÿèìÎ&ý&Ù ¤ÿÿÿðÕÎ&ô&Ï ¹ÿÿÿéãÎ&ý&Ù Îÿÿ ÿçßÎ&&ã ãÿÿÿïÕÎ&ý&Ù øÿÿ ÿíÕÎ&&ã ÿÿ ÿëÐÎ&&ã "ÿÿ ÿíÐÎ&&ã 7ÿÿ ÿíÐÎ&&ã Lÿÿ ÿìéÎ&&ã aÿÿ ÿíÔÎ&&ã vÿÿ ÿíÕÎ&&ã ‹ÿÿ ÿçÛÎ&&ã  ÿÿÿïÐÎ&ý&Ù µÿÿÿîÐÎ&ý&Ù Êÿÿ ÿèìÎ&&ã ßÿÿÿèàÎ&ý&í ôÿÿÿééÎ&ý&Ù ÿÿÿçÔÎ&ý&í ÿÿÿçÞÎ&ý&Ù 3ÿÿ ÿçÞÎ&&ã HÿÿÿëÐÎ&ý&Ù ]ÿÿ ÿìÕÎ&&ã rÿÿÿíØÎ&ý&Ù ‡ÿÿ ÿçÜÎ&&ã œÿÿÿëÐÎ&ê÷ÿÿÿëÐÎ&ü&  yÿÿÿëÐÎ&ü&  ŽÿÿÿèìÎ&ü&  £ÿÿÿðÕÎ&ó&ÿ ¸ÿÿÿéãÎ&ü&  ÍÿÿÿçßÎ&&  âÿÿÿïÕÎ&ü&  ÷ÿÿÿíÕÎ&&  ÿÿÿëÐÎ&&  !ÿÿÿíÐÎ&&  6ÿÿÿíÐÎ&&  KÿÿÿìéÎ&&  `ÿÿÿíÔÎ&&  uÿÿÿíÕÎ&&  ŠÿÿÿçÛÎ&&  ŸÿÿÿïÐÎ&ü&  ´ÿÿÿîÐÎ&ü&  ÉÿÿÿèìÎ&&  ÞÿÿÿèãÎ&ü&  óÿÿÿééÎ&ü&  ÿÿÿçÔÎ&ü&  ÿÿÿçÞÎ&ü&  2ÿÿÿçÞÎ&&  GÿÿÿëÐÎ&ü&  \ÿÿÿìÕÎ&&  qÿÿÿíØÎ&ü&  †ÿÿÿçÖÎ&&  ›ÿÿ ÿëÐÎ&ë &ÿÿÿëÐÎ&ý& : zÿÿÿëÐÎ&ý& : ÿÿÿèìÎ&ý& : ¤ÿÿÿðÕÎ&ô& 0 ¹ÿÿÿéãÎ&ý& : Îÿÿ ÿçßÎ&& D ãÿÿÿïÕÎ&ý& : øÿÿ ÿíÕÎ&& D ÿÿ ÿëÐÎ&& D "ÿÿ ÿíÐÎ&& D 7ÿÿ ÿíÐÎ&& D Lÿÿ ÿìéÎ&& D aÿÿ ÿíÔÎ&& D vÿÿ ÿíÕÎ&& D ‹ÿÿ ÿçÛÎ&& D  ÿÿÿïÐÎ&ý& : µÿÿÿîÐÎ&ý& : Êÿÿ ÿèìÎ&& D ßÿÿÿèàÎ&ý& N ôÿÿÿééÎ&ý& : ÿÿÿçÔÎ&ý& N ÿÿÿçÞÎ&ý& : 3ÿÿ ÿçÞÎ&& D HÿÿÿëÐÎ&ý& : ]ÿÿ ÿìÕÎ&& D rÿÿÿíØÎ&ý& : ‡ÿÿ ÿçÜÎ&& D œÿÿ êÃ&ì WÿÿÿëêÎ& i&þ {ÿÿÿëêÎ&þ& i ÿÿÿêêÎ&þ& i ¥ÿÿÿðêÎ&õ& _ ºÿÿÿéêÎ&þ& i ÏÿÿÿçêÎ&& s äÿÿÿïêÎ&þ& i ùÿÿÿíêÎ&& s ÿÿÿëêÎ&& s #ÿÿÿíêÎ&& s 8ÿÿÿðêÎ&& s MÿÿÿìêÎ&& s bÿÿÿíêÎ&& s wÿÿÿíêÎ&& s ŒÿÿÿçêÎ&& s ¡ÿÿÿïêÎ&þ& i ¶ÿÿÿîêÎ&þ& i ËÿÿÿèìÎ&& s àÿÿÿèêÎ&þ& | õÿÿÿéêÎ&þ& i ÿÿÿçêÎ&þ& | ÿÿÿçêÎ&þ& i 4ÿÿÿçêÎ&& s IÿÿÿëêÎ&þ& i ^ÿÿÿìêÎ&& s sÿÿÿíêÎ&þ& i ˆÿÿÿçêÎ&& s ÿÿÿëöÎ&î †ÿÿÿëöÎ&& š |ÿÿÿëöÎ&& š ‘ÿÿÿêöÎ&& š ¦ÿÿÿðöÎ&÷&  »ÿÿÿêöÎ&& š ÐÿÿÿçöÎ& & ¤ åÿÿÿïöÎ&& š úÿÿÿíöÎ& & ¤ ÿÿÿëöÎ& & ¤ $ÿÿÿíöÎ& & ¤ 9ÿÿÿíöÎ& & ¤ NÿÿÿìöÎ& & ¤ cÿÿÿîöÎ& & ¤ xÿÿÿíöÎ& & ¤ ÿÿÿçöÎ& & ¤ ¢ÿÿÿïöÎ&& š ·ÿÿÿîöÎ&& š ÌÿÿÿèöÎ& & ¤ áÿÿÿèöÎ&& ® öÿÿÿéöÎ&& š ÿÿÿçöÎ&& ® ÿÿÿçöÎ&& š 5ÿÿÿçöÎ& & ¤ JÿÿÿëöÎ&& š _ÿÿÿìöÎ& & ¤ tÿÿÿíöÎ&& š ‰ÿÿÿçöÎ& & ¤ žÿÿ ÿëÐÎ&ð ¸ÿÿÿëÐÎ&& Ì |ÿÿÿëÐÎ&& Ì ‘ÿÿÿêëÎ&& Ì ¦ÿÿÿðÕÎ&ù&  »ÿÿÿêãÎ&& Ì ÐÿÿÿçßÎ& & Ö åÿÿÿïÕÎ&& Ì úÿÿÿíÕÎ& & Ö ÿÿÿëÐÎ& & Ö $ÿÿÿíÐÎ& & Ö 9ÿÿÿíÐÎ& & Ö NÿÿÿìéÎ& & Ö cÿÿÿîÔÎ& & Ö xÿÿÿíÕÎ& & Ö ÿÿÿçÛÎ& & Ö ¢ÿÿÿïÐÎ&& Ì ·ÿÿÿîÐÎ&& Ì ÌÿÿÿèéÎ& & Ö áÿÿÿèãÎ&& à öÿÿÿééÎ&& Ì ÿÿÿçÔÎ&& à ÿÿÿçâÎ&& Ì 5ÿÿÿçâÎ& & Ö JÿÿÿëÐÎ&& Ì _ÿÿÿìÕÎ& & Ö tÿÿÿíØÎ&& Ì ‰ÿÿÿçÜÎ& & Ö žÿÿÿëÐÎ&î íÿÿÿëÐÎ&& ý |ÿÿÿëÐÎ&& ý ‘ÿÿÿêëÎ&& ý ¦ÿÿÿðÕÎ&÷& ó »ÿÿÿêãÎ&& ý ÐÿÿÿçßÎ& &  åÿÿÿïÕÎ&& ý úÿÿÿíÕÎ& &  ÿÿÿëÐÎ& &  $ÿÿÿíÐÎ& &  9ÿÿÿíÐÎ& &  NÿÿÿìéÎ& &  cÿÿÿîÔÎ& &  xÿÿÿíÕÎ& &  ÿÿÿçÛÎ& &  ¢ÿÿÿïÐÎ&& ý ·ÿÿÿîÐÎ&& ý ÌÿÿÿèéÎ& &  áÿÿÿèãÎ&&  öÿÿÿééÎ&& ý ÿÿÿçÔÎ&&  ÿÿÿçâÎ&& ý 5ÿÿÿçâÎ& &  JÿÿÿëÐÎ&& ý _ÿÿÿìÕÎ& &  tÿÿÿíØÎ&& ý ‰ÿÿÿçÜÎ& &  žÿÿ ëÃ&ì ÿÿÿëëÎ&þ& / {ÿÿÿëëÎ&þ& / ÿÿÿêëÎ&þ& / ¥ÿÿÿðëÎ&õ& % ºÿÿÿéëÎ&þ& / ÏÿÿÿçëÎ&& 9 äÿÿÿïëÎ&þ& / ùÿÿÿíëÎ&& 9 ÿÿÿëëÎ&& 9 #ÿÿÿíëÎ&& 9 8ÿÿÿðëÎ&& 9 MÿÿÿìëÎ&& 9 bÿÿÿíëÎ&& 9 wÿÿÿíëÎ&& 9 ŒÿÿÿçëÎ&& 9 ¡ÿÿÿïëÎ&þ& / ¶ÿÿÿîëÎ&þ& / ËÿÿÿèìÎ&& 9 àÿÿÿèëÎ&þ& C õÿÿÿéëÎ&þ& / ÿÿÿçëÎ&þ& C ÿÿÿçëÎ&þ& / 4ÿÿÿçëÎ&& 9 IÿÿÿëëÎ&þ& / ^ÿÿÿìëÎ&& 9 sÿÿÿíëÎ&þ& / ˆÿÿÿçëÎ&& 9 ÿÿÿëëÎ& KíÿÿÿëëÎ&ÿ& V {ÿÿÿëëÎ&ÿ& V ÿÿÿêëÎ&ÿ& V ¥ÿÿÿðëÎ&ÿ& Q ºÿÿÿéëÎ&ÿ& V ÏÿÿÿçëÎ&& k äÿÿÿïëÎ&ÿ& V ùÿÿÿíëÎ&& k ÿÿÿëëÎ&& k #ÿÿÿíëÎ&& k 8ÿÿÿðëÎ&& k MÿÿÿìëÎ&& k bÿÿÿíëÎ&& k wÿÿÿíëÎ&& k ŒÿÿÿçëÎ&& k ¡ÿÿÿïëÎ&ÿ& V ¶ÿÿÿîëÎ&ÿ& V ËÿÿÿèìÎ&& k àÿÿÿèëÎ&ÿ& d õÿÿÿéëÎ&ÿ& V ÿÿÿçëÎ&ÿ& V ÿÿÿçëÎ&ÿ& V 4ÿÿÿçëÎ&& ] IÿÿÿëëÎ&ÿ& V ^ÿÿÿìëÎ&& k sÿÿÿíëÎ&ÿ& V ˆÿÿÿçëÎ&& ] ÿÿÿëÐÎ&ï rÿÿÿëÐÎ&& € ~ÿÿÿëÐÎ&& € “ÿÿÿêëÎ&& € ¨ÿÿÿðÕÎ&ø& y ½ÿÿÿéãÎ&& € ÒÿÿÿçßÎ& & • çÿÿÿïÕÎ&& € üÿÿÿíÕÎ& & • ÿÿÿëÐÎ& & • &ÿÿÿíÐÎ& & • ;ÿÿÿíÐÎ& & • PÿÿÿëéÎ& & • eÿÿÿîÔÎ& & • zÿÿÿíÖÎ& & • ÿÿÿçÛÎ& & • ¤ÿÿÿïÐÎ&& € ¹ÿÿÿîÐÎ&& € ÎÿÿÿêéÎ& & • ãÿÿÿèãÎ&& Ž øÿÿÿçéÎ&& € ÿÿÿçÔÎ&& Ž "ÿÿÿçÞÎ&& € 7ÿÿÿíÞÎ& & ‡ LÿÿÿëÐÎ&& € aÿÿÿìÕÎ& & • vÿÿÿíØÎ&& € ‹ÿÿÿçÜÎ& & ‡  ÿÿÿëÐÎ&ñ œÿÿÿëÐÎ&& ª ~ÿÿÿëÐÎ&& ª “ÿÿÿêëÎ&& ª ¨ÿÿÿðÕÎ&ú& £ ½ÿÿÿéãÎ&& ª ÒÿÿÿçßÎ& & ¿ çÿÿÿïÕÎ&& ª üÿÿÿíÕÎ& & ¿ ÿÿÿëÐÎ& & ¿ &ÿÿÿíÐÎ& & ¿ ;ÿÿÿíÐÎ& & ¿ PÿÿÿëéÎ& & ¿ eÿÿÿîÔÎ& & ¿ zÿÿÿíÖÎ& & ¿ ÿÿÿçÛÎ& & ¿ ¤ÿÿÿïÐÎ&& ª ¹ÿÿÿîÐÎ&& ª ÎÿÿÿêéÎ& & ¿ ãÿÿÿèãÎ&& ¸ øÿÿÿçéÎ&& ª ÿÿÿçÔÎ&& ¸ "ÿÿÿçÞÎ&& ª 7ÿÿÿíÞÎ& & ± LÿÿÿëÐÎ&& ª aÿÿÿìÕÎ& & ¿ vÿÿÿíØÎ&& ª ‹ÿÿÿçÜÎ& & ±  ÿÿÿëÐÎ&ï ÆÿÿÿëÐÎ&& Ô }ÿÿÿëÐÎ&& Ô ’ÿÿÿêëÎ&& Ô §ÿÿÿðÕÎ&ø& Í ¼ÿÿÿéãÎ&& Ô ÑÿÿÿçßÎ& & é æÿÿÿïÕÎ&& Ô ûÿÿÿíÕÎ& & é ÿÿÿëÐÎ& & é %ÿÿÿíÐÎ& & é :ÿÿÿíÐÎ& & é OÿÿÿëèÎ& & é dÿÿÿîÔÎ& & é yÿÿÿíÖÎ& & é ŽÿÿÿçÛÎ& & é £ÿÿÿïÐÎ&& Ô ¸ÿÿÿîÐÎ&& Ô ÍÿÿÿêéÎ& & é âÿÿÿèãÎ&& â ÷ÿÿÿçéÎ&& Ô ÿÿÿçÔÎ&& â !ÿÿÿçÞÎ&& Ô 6ÿÿÿçÞÎ& & Û KÿÿÿëÐÎ&& Ô `ÿÿÿìÕÎ& & é uÿÿÿíØÎ&& Ô ŠÿÿÿçÖÎ& & Û ŸÿÿÿíëÎ&í ðÿÿÿëëÎ&ÿ& þ {ÿÿÿëëÎ&ÿ& þ ÿÿÿêëÎ&ÿ& þ ¥ÿÿÿðëÎ&ö& ÷ ºÿÿÿéëÎ&ÿ& þ ÏÿÿÿçëÎ&&  äÿÿÿïëÎ&ÿ& þ ùÿÿÿíëÎ&&  ÿÿÿëëÎ&&  #ÿÿÿíëÎ&&  8ÿÿÿðëÎ&&  MÿÿÿìëÎ&&  bÿÿÿíëÎ&&  wÿÿÿíëÎ&&  ŒÿÿÿçëÎ&&  ¡ÿÿÿïëÎ&ÿ& þ ¶ÿÿÿîëÎ&ÿ& þ ËÿÿÿèìÎ&&  àÿÿÿèëÎ&ÿ&  õÿÿÿéëÎ&ÿ& þ ÿÿÿçëÎ&ÿ&  ÿÿÿçëÎ&ÿ& þ 4ÿÿÿçëÎ&&  IÿÿÿëëÎ&ÿ& þ ^ÿÿÿìëÎ&&  sÿÿÿíëÎ&ÿ& þ ˆÿÿÿçëÎ&&  ÿÿ ëÃ&ì ÿÿÿëëÎ&þ& ' {ÿÿÿëëÎ&þ& ' ÿÿÿêëÎ&þ& ' ¥ÿÿÿðëÎ& & ºöÿÿÿéëÎ&þ& ' ÏÿÿÿçëÎ&& < äÿÿÿïëÎ&þ& ' ùÿÿÿíëÎ&& < ÿÿÿëëÎ&& < #ÿÿÿíëÎ&& < 8ÿÿÿðëÎ&& < MÿÿÿìëÎ&& < bÿÿÿíëÎ&& < wÿÿÿíëÎ&& < ŒÿÿÿçëÎ&& < ¡ÿÿÿïëÎ&þ& ' ¶ÿÿÿîëÎ&þ& ' ËÿÿÿèìÎ&& < àÿÿÿèëÎ&þ& 5 õÿÿÿéëÎ&þ& ' ÿÿÿçëÎ& 5& þÿÿÿçëÎ&þ& ' 4ÿÿÿçëÎ&& . IÿÿÿëëÎ&þ& ' ^ÿÿÿìëÎ&& < sÿÿÿíëÎ&þ& ' ˆÿÿÿçëÎ&& . ÿÿÿëÐÎ&î CÿÿÿëÐÎ&& P |ÿÿÿëÐÎ&& P ‘ÿÿÿêëÎ&& P ¦ÿÿÿðÕÎ&÷& I »ÿÿÿêãÎ&& P ÐÿÿÿçßÎ& & e åÿÿÿïÕÎ&& P úÿÿÿíÕÎ& & e ÿÿÿëÐÎ& & e $ÿÿÿíÐÎ& & e 9ÿÿÿíÐÎ& & e NÿÿÿìéÎ& & e cÿÿÿîÔÎ& & e xÿÿÿíÕÎ& & e ÿÿÿçÛÎ& & e ¢ÿÿÿïÐÎ&& P ·ÿÿÿîÐÎ&& P ÌÿÿÿèéÎ& & e áÿÿÿèãÎ&& ^ öÿÿÿééÎ&& P ÿÿÿçÔÎ&& ^ ÿÿÿçâÎ&& P 5ÿÿÿçâÎ& & W JÿÿÿëÐÎ&& P _ÿÿÿìÕÎ& & e tÿÿÿíØÎ&& P ‰ÿÿÿçÜÎ& & W žÿÿÿëÐÎ&é iÿÿ ÿëÐÎ& o&û xÿÿ ÿëÐÎ&û& o Žÿÿ ÿèìÎ&û& o £ÿÿ ÿðÕÎ& l& »òÿÿ ÿéãÎ&û& o Íÿÿ ÿçßÎ&& u âÿÿ ÿïÕÎ&û& o ÷ÿÿ ÿíÕÎ&û& o ÿÿ ÿëÐÎ&& u !ÿÿ ÿíÐÎ&& u 6ÿÿ ÿíÐÎ&& u Kÿÿ ÿìéÎ&& u `ÿÿ ÿíÔÎ&& u uÿÿ ÿíÕÎ&& u Šÿÿ ÿçÛÎ&& u Ÿÿÿ ÿïÐÎ&û& o ´ÿÿ ÿîÐÎ&û& o Éÿÿ ÿèìÎ&& u Þÿÿ ÿèãÎ&û& r óÿÿ ÿééÎ&û& o ÿÿ ÿçÔÎ&û& r ÿÿ ÿçÞÎ&û& o 2ÿÿ ÿçÞÎ&& o Gÿÿ ÿëÐÎ&û& o \ÿÿ ÿìÕÎ&& u qÿÿ ÿíØÎ&û& o †ÿÿ ÿçÖÎ&& o ›ÿÿ ÿëöÎ&V ÿÿÿëõÎ&&\ xÿÿÿëõÎ&&\ ÿÿÿèõÎ&&\ ¢ÿÿ ÿðöÎ&&Y ·ÿÿÿêõÎ&&\ Ìÿÿ ÿçõÎ&(&b áÿÿÿïõÎ&&\ öÿÿ ÿíõÎ&(&b ÿÿ ÿëõÎ&(&b ÿÿ ÿíõÎ&(&b 5ÿÿ ÿíõÎ&(&b Jÿÿ ÿìõÎ&(&b _ÿÿ ÿíõÎ&(&b tÿÿ ÿíõÎ&(&b ‰ÿÿ ÿçõÎ&(&b žÿÿÿïõÎ&&\ ³ÿÿÿîõÎ&&\ Èÿÿ ÿèõÎ&(&b ÝÿÿÿèõÎ&&_ òÿÿÿéõÎ&&\ ÿÿÿçõÎ&&_ ÿÿÿçõÎ&&\ 1ÿÿ ÿçõÎ&(&\ FÿÿÿëõÎ&&\ [ÿÿ ÿìõÎ&(&b pÿÿÿíõÎ&&\ …ÿÿ ÿçõÎ&(&\ šÿÿÿëÑÎ&eÿÿÿëÐÎ&!&k zÿÿÿëÐÎ&!&k ÿÿÿèìÎ&!&k ¤ÿÿÿðÕÎ&&h ¹ÿÿÿéãÎ&!&k ÎÿÿÿçßÎ&*&q ãÿÿÿïÕÎ&!&k øÿÿÿíÕÎ&*&q ÿÿÿëÑÎ&*&q "ÿÿÿíÑÎ&*&q 7ÿÿÿíÑÎ&*&q LÿÿÿìéÎ&*&q aÿÿÿíÔÎ&*&q vÿÿÿíÕÎ&*&q ‹ÿÿÿçÛÎ&*&q  ÿÿÿïÐÎ&!&k µÿÿÿîÐÎ&!&k ÊÿÿÿèìÎ&*&q ßÿÿÿèàÍ&!&n ôÿÿÿééÎ&!&k ÿÿÿçÔÍ&!&n ÿÿÿçÞÎ&!&k 3ÿÿÿçÞÎ&*&k HÿÿÿëÐÎ&!&k ]ÿÿÿìÕÎ&*&q rÿÿÿíØÎ&!&k ‡ÿÿÿçÜÎ&*&k œÿÿ ÿëöÎ& tÿÿÿëöÎ&&z xÿÿÿëöÎ&&z ÿÿÿèöÎ&&z ¢ÿÿ ÿðöÎ&&w ·ÿÿÿêöÎ&&z Ìÿÿ ÿçöÎ&(&€ áÿÿÿïöÎ&&z öÿÿ ÿíöÎ&(&€ ÿÿ ÿëöÎ&(&€ ÿÿ ÿíöÎ&(&€ 5ÿÿ ÿíöÎ&(&€ Jÿÿ ÿìöÎ&(&€ _ÿÿ ÿíöÎ&(&€ tÿÿ ÿíöÎ&(&€ ‰ÿÿ ÿçöÎ&(&€ žÿÿÿïöÎ&&z ³ÿÿÿîöÎ&&z Èÿÿ ÿèöÎ&(&€ ÝÿÿÿèöÎ&&} òÿÿÿéöÎ&&z ÿÿÿçöÎ&&} ÿÿÿçöÎ&&z 1ÿÿ ÿçöÎ&(&z FÿÿÿëöÎ&&z [ÿÿ ÿìöÎ&(&€ pÿÿÿíöÎ&&z …ÿÿ ÿçöÎ&(&z šÿÿÿëÐÎ&ƒÿÿÿëÐÎ&!&‰ zÿÿÿëÐÎ&!&‰ ÿÿÿèìÎ&!&‰ ¤ÿÿÿðÕÎ&&† ¹ÿÿÿéãÎ&!&‰ ÎÿÿÿçßÎ&*& ãÿÿÿïÕÎ&!&‰ øÿÿÿíÕÎ&*& ÿÿÿëÐÎ&*& "ÿÿÿíÐÎ&*& 7ÿÿÿíÐÎ&*& LÿÿÿìéÎ&*& aÿÿÿíÔÎ&*& vÿÿÿíÕÎ&*& ‹ÿÿÿçÛÎ&*&  ÿÿÿïÐÎ&!&‰ µÿÿÿîÐÎ&!&‰ ÊÿÿÿèìÎ&*& ßÿÿÿèàÎ&!&Œ ôÿÿÿééÎ&!&‰ ÿÿÿçÔÎ&!&Œ ÿÿÿçÞÎ&!&‰ 3ÿÿÿçÞÎ&*&‰ HÿÿÿëÐÎ&!&‰ ]ÿÿÿìÕÎ&*& rÿÿÿíØÎ&!&‰ ‡ÿÿÿçÜÎ&*&‰ œÿÿÿëÐÎ&“ÿÿÿëÐÎ& &¦ yÿÿÿëÐÎ& &¦ ŽÿÿÿèìÎ& &¦ £ÿÿÿðÕÎ&& ¸ÿÿÿéãÎ& &¦ ÍÿÿÿçßÎ&)&° âÿÿÿïÕÎ& &¦ ÷ÿÿÿíÕÎ&)&° ÿÿÿëÐÎ&)&° !ÿÿÿíÐÎ&)&° 6ÿÿÿíÐÎ&)&° KÿÿÿìéÎ&)&° `ÿÿÿíÔÎ&)&° uÿÿÿíÕÎ&)&° ŠÿÿÿçÛÎ&)&° ŸÿÿÿïÐÎ& &¦ ´ÿÿÿîÐÎ& &¦ ÉÿÿÿèìÎ&)&° ÞÿÿÿèãÎ& &º óÿÿÿééÎ& &¦ ÿÿÿçÔÎ& &º ÿÿÿçÞÎ& &¦ 2ÿÿÿçÞÎ&)&° GÿÿÿëÐÎ& &¦ \ÿÿÿìÕÎ&)&° qÿÿÿíØÎ& &¦ †ÿÿÿçÖÎ&)&° ›ÿÿÿëÐÎ&ÃÿÿÿëÐÎ&!&Ö zÿÿÿëÐÎ&!&Ö ÿÿÿèìÎ&!&Ö ¤ÿÿÿðÕÎ&&Ì ¹ÿÿÿéãÎ&!&Ö ÎÿÿÿçßÎ&*&à ãÿÿÿïÕÎ&!&Ö øÿÿÿíÕÎ&*&à ÿÿÿëÐÎ&*&à "ÿÿÿíÐÎ&*&à 7ÿÿÿíÐÎ&*&à LÿÿÿìéÎ&*&à aÿÿÿíÔÎ&*&à vÿÿÿíÕÎ&*&à ‹ÿÿÿçÛÎ&*&à  ÿÿÿïÐÎ&!&Ö µÿÿÿîÐÎ&!&Ö ÊÿÿÿèìÎ&*&à ßÿÿÿèàÎ&!&ê ôÿÿÿééÎ&!&Ö ÿÿÿçÔÎ&!&ê ÿÿÿçÞÎ&!&Ö 3ÿÿÿçÞÎ&*&à HÿÿÿëÐÎ&!&Ö ]ÿÿÿìÕÎ&*&à rÿÿÿíØÎ&!&Ö ‡ÿÿÿçÜÎ&*&à œÿÿÿëÐÎ&ôÿÿÿëÐÎ& &  yÿÿÿëÐÎ& &  ŽÿÿÿèìÎ& &  £ÿÿÿðÕÎ&&þ ¸ÿÿÿéãÎ& &  ÍÿÿÿçßÎ&)&  âÿÿÿïÕÎ& &  ÷ÿÿÿíÕÎ&)&  ÿÿÿëÐÎ&)&  !ÿÿÿíÐÎ&)&  6ÿÿÿíÐÎ&)&  KÿÿÿìéÎ&)&  `ÿÿÿíÔÎ&)&  uÿÿÿíÕÎ&)&  ŠÿÿÿçÛÎ&)&  ŸÿÿÿïÐÎ& &  ´ÿÿÿîÐÎ& &  ÉÿÿÿèìÎ&)&  ÞÿÿÿèãÎ& &  óÿÿÿééÎ& &  ÿÿÿçÔÎ& &  ÿÿÿçÞÎ& &  2ÿÿÿçÞÎ&)&  GÿÿÿëÐÎ& &  \ÿÿÿìÕÎ&)&  qÿÿÿíØÎ& &  †ÿÿÿçÖÎ&)&  ›ÿÿÿëÐÎ& #ÿÿÿëÐÎ&!& 7 zÿÿÿëÐÎ&!& 7 ÿÿÿèìÎ&!& 7 ¤ÿÿÿðÕÎ&& - ¹ÿÿÿéãÎ&!& 7 ÎÿÿÿçßÎ&*& A ãÿÿÿïÕÎ&!& 7 øÿÿÿíÕÎ&*& A ÿÿÿëÐÎ&*& A "ÿÿÿíÐÎ&*& A 7ÿÿÿíÐÎ&*& A LÿÿÿìéÎ&*& A aÿÿÿíÔÎ&*& A vÿÿÿíÕÎ&*& A ‹ÿÿÿçÛÎ&*& A  ÿÿÿïÐÎ&!& 7 µÿÿÿîÐÎ&!& 7 ÊÿÿÿèìÎ&*& A ßÿÿÿèàÎ&!& K ôÿÿÿééÎ&!& 7 ÿÿÿçÔÎ&!& K ÿÿÿçÞÎ&!& 7 3ÿÿÿçÞÎ&*& A HÿÿÿëÐÎ&!& 7 ]ÿÿÿìÕÎ&*& A rÿÿÿíØÎ&!& 7 ‡ÿÿÿçÜÎ&*& A œÿÿ ê¾& UÿÿÿëêÎ&"& f {ÿÿÿëêÎ&"& f ÿÿÿêêÎ&"& f ¥ÿÿÿðêË&& ] ºÿÿÿéêÎ&"& f ÏÿÿÿçêË&+& p äÿÿÿïêÎ&"& f ùÿÿÿíêË&+& p ÿÿÿëêË&+& p #ÿÿÿíêË&+& p 8ÿÿÿðêË&+& p MÿÿÿìêË&+& p bÿÿÿíêË&+& p wÿÿÿíêË&+& p ŒÿÿÿçêË&+& p ¡ÿÿÿïêÎ&"& f ¶ÿÿÿîêÎ&"& f ËÿÿÿèìË&+& p àÿÿÿèêÎ&"& y õÿÿÿéêÎ&"& f ÿÿÿçêÎ&"& y ÿÿÿçêÎ&"& f 4ÿÿÿçêË&+& p IÿÿÿëêÎ&"& f ^ÿÿÿìêË&+& p sÿÿÿíêÎ&"& f ˆÿÿÿçêË&+& p ÿÿ ÿëöÎ& ƒÿÿÿëöÎ&$& — |ÿÿÿëöÎ&$& — ‘ÿÿÿêöÎ&$& — ¦ÿÿÿðöÎ&&  »ÿÿÿêöÎ&$& — ÐÿÿÿçöÎ&-& ¡ åÿÿÿïöÎ&$& — úÿÿÿíöÎ&-& ¡ ÿÿÿëöÎ&-& ¡ $ÿÿÿíöÎ&-& ¡ 9ÿÿÿíöÎ&-& ¡ NÿÿÿìöÎ&-& ¡ cÿÿÿîöÎ&-& ¡ xÿÿÿíöÎ&-& ¡ ÿÿÿçöÎ&-& ¡ ¢ÿÿÿïöÎ&$& — ·ÿÿÿîöÎ&$& — ÌÿÿÿèöÎ&-& ¡ áÿÿÿèöÎ&$& « öÿÿÿéöÎ&$& — ÿÿÿçöÎ&$& « ÿÿÿçöÎ&$& — 5ÿÿÿçöÎ&-& ¡ JÿÿÿëöÎ&$& — _ÿÿÿìöÎ&-& ¡ tÿÿÿíöÎ&$& — ‰ÿÿÿçöÎ&-& ¡ žÿÿ ÿëÐÎ& µÿÿ ÿëÐÎ&&& É |ÿÿ ÿëÐÎ&&& É ‘ÿÿ ÿêëÎ&&& É ¦ÿÿ ÿðÕÎ&& ¿ »ÿÿ ÿêãÎ&&& É ÐÿÿÿçßÎ&/& Ó åÿÿ ÿïÕÎ&&& É úÿÿÿíÕÎ&/& Ó ÿÿÿëÐÎ&/& Ó $ÿÿÿíÐÎ&/& Ó 9ÿÿÿíÐÎ&/& Ó NÿÿÿìéÎ&/& Ó cÿÿÿîÔÎ&/& Ó xÿÿÿíÕÎ&/& Ó ÿÿÿçÛÎ&/& Ó ¢ÿÿ ÿïÐÎ&&& É ·ÿÿ ÿîÐÎ&&& É ÌÿÿÿèéÎ&/& Ó áÿÿ ÿèãÎ&&& Ý öÿÿ ÿééÎ&&& É ÿÿ ÿçÔÎ&&& Ý ÿÿ ÿçâÎ&&& É 5ÿÿÿçâÎ&/& Ó Jÿÿ ÿëÐÎ&&& É _ÿÿÿìÕÎ&/& Ó tÿÿ ÿíØÎ&&& É ‰ÿÿÿçÜÎ&/& Ó žÿÿ ÿëÐÎ& çÿÿÿëÐÎ&$& ú |ÿÿÿëÐÎ&$& ú ‘ÿÿÿêëÎ&$& ú ¦ÿÿÿðÕÎ&& ð »ÿÿÿêãÎ&$& ú ÐÿÿÿçßÎ&-&  åÿÿÿïÕÎ&$& ú úÿÿÿíÕÎ&-&  ÿÿÿëÐÎ&-&  $ÿÿÿíÐÎ&-&  9ÿÿÿíÐÎ&-&  NÿÿÿìéÎ&-&  cÿÿÿîÔÎ&-&  xÿÿÿíÕÎ&-&  ÿÿÿçÛÎ&-&  ¢ÿÿÿïÐÎ&$& ú ·ÿÿÿîÐÎ&$& ú ÌÿÿÿèéÎ&-&  áÿÿÿèãÎ&$&  öÿÿÿééÎ&$& ú ÿÿÿçÔÎ&$&  ÿÿÿçâÎ&$& ú 5ÿÿÿçâÎ&-&  JÿÿÿëÐÎ&$& ú _ÿÿÿìÕÎ&-&  tÿÿÿíØÎ&$& ú ‰ÿÿÿçÜÎ&-&  žÿÿ ë¾& ÿÿÿëëÎ&"& , {ÿÿÿëëÎ&"& , ÿÿÿêëÎ&"& , ¥ÿÿÿðëË&& " ºÿÿÿéëÎ&"& , ÏÿÿÿçëË&+& 6 äÿÿÿïëÎ&"& , ùÿÿÿíëË&+& 6 ÿÿÿëëË&+& 6 #ÿÿÿíëË&+& 6 8ÿÿÿðëË&+& 6 MÿÿÿìëË&+& 6 bÿÿÿíëË&+& 6 wÿÿÿíëË&+& 6 ŒÿÿÿçëË&+& 6 ¡ÿÿÿïëÎ&"& , ¶ÿÿÿîëÎ&"& , ËÿÿÿèìË&+& 6 àÿÿÿèëÎ&"& @ õÿÿÿéëÎ&"& , ÿÿÿçëÎ&"& @ ÿÿÿçëÎ&"& , 4ÿÿÿçëË&+& 6 IÿÿÿëëÎ&"& , ^ÿÿÿìëË&+& 6 sÿÿÿíëÎ&"& , ˆÿÿÿçëË&+& 6 ÿÿÿëëË& IÿÿÿëëË&#& T {ÿÿÿëëË&#& T ÿÿÿêëË&#& T ¥ÿÿÿðëË&& O ºÿÿÿéëË&#& T ÏÿÿÿçëË&,& i äÿÿÿïëË&#& T ùÿÿÿíëË&,& i ÿÿÿëëË&,& i #ÿÿÿíëË&,& i 8ÿÿÿðëË&,& i MÿÿÿìëË&,& i bÿÿÿíëË&,& i wÿÿÿíëË&,& i ŒÿÿÿçëË&,& i ¡ÿÿÿïëË&#& T ¶ÿÿÿîëË&#& T ËÿÿÿèìË&,& i àÿÿÿèëË&#& b õÿÿÿéëË&#& T ÿÿÿçëË&#& T ÿÿÿçëË&#& T 4ÿÿÿçëË&,& [ IÿÿÿëëË&#& T ^ÿÿÿìëË&,& i sÿÿÿíëË&#& T ˆÿÿÿçëË&,& [ ÿÿ ÿëÐÎ& pÿÿÿëÐÎ&%& ~ ~ÿÿÿëÐÎ&%& ~ “ÿÿÿêëÎ&%& ~ ¨ÿÿÿðÕÎ&& w ½ÿÿÿéãÎ&%& ~ ÒÿÿÿçßÎ&.& “ çÿÿÿïÕÎ&%& ~ üÿÿÿíÕÎ&.& “ ÿÿÿëÐÎ&.& “ &ÿÿÿíÐÎ&.& “ ;ÿÿÿíÐÎ&.& “ PÿÿÿëéÎ&.& “ eÿÿÿîÔÎ&.& “ zÿÿÿíÖÎ&.& “ ÿÿÿçÛÎ&.& “ ¤ÿÿÿïÐÎ&%& ~ ¹ÿÿÿîÐÎ&%& ~ ÎÿÿÿêéÎ&.& “ ãÿÿÿèãÎ&%& Œ øÿÿÿçéÎ&%& ~ ÿÿÿçÔÎ&%& Œ "ÿÿÿçÞÎ&%& ~ 7ÿÿÿíÞÎ&.& … LÿÿÿëÐÎ&%& ~ aÿÿÿìÕÎ&.& “ vÿÿÿíØÎ&%& ~ ‹ÿÿÿçÜÎ&.& …  ÿÿ ÿëÐÎ& šÿÿÿëÐÎ&'& ¨ ~ÿÿÿëÐÎ&'& ¨ “ÿÿÿêëÎ&'& ¨ ¨ÿÿ ÿðÕÎ&& ¡ ½ÿÿÿéãÎ&'& ¨ Òÿÿ ÿçßÎ&0& ½ çÿÿÿïÕÎ&'& ¨ üÿÿ ÿíÕÎ&0& ½ ÿÿ ÿëÐÎ&0& ½ &ÿÿ ÿíÐÎ&0& ½ ;ÿÿ ÿíÐÎ&0& ½ Pÿÿ ÿëéÎ&0& ½ eÿÿ ÿîÔÎ&0& ½ zÿÿ ÿíÖÎ&0& ½ ÿÿ ÿçÛÎ&0& ½ ¤ÿÿÿïÐÎ&'& ¨ ¹ÿÿÿîÐÎ&'& ¨ Îÿÿ ÿêéÎ&0& ½ ãÿÿÿèãÎ&'& ¶ øÿÿÿçéÎ&'& ¨ ÿÿÿçÔÎ&'& ¶ "ÿÿÿçÞÎ&'& ¨ 7ÿÿ ÿíÞÎ&0& ¯ LÿÿÿëÐÎ&'& ¨ aÿÿ ÿìÕÎ&0& ½ vÿÿÿíØÎ&'& ¨ ‹ÿÿ ÿçÜÎ&0& ¯  ÿÿ ÿëÐÎ& ÄÿÿÿëÐÎ&%& Ò }ÿÿÿëÐÎ&%& Ò ’ÿÿÿêëÎ&%& Ò §ÿÿÿðÕÎ&& Ë ¼ÿÿÿéãÎ&%& Ò ÑÿÿÿçßÎ&.& ç æÿÿÿïÕÎ&%& Ò ûÿÿÿíÕÎ&.& ç ÿÿÿëÐÎ&.& ç %ÿÿÿíÐÎ&.& ç :ÿÿÿíÐÎ&.& ç OÿÿÿëèÎ&.& ç dÿÿÿîÔÎ&.& ç yÿÿÿíÖÎ&.& ç ŽÿÿÿçÛÎ&.& ç £ÿÿÿïÐÎ&%& Ò ¸ÿÿÿîÐÎ&%& Ò ÍÿÿÿêéÎ&.& ç âÿÿÿèãÎ&%& à ÷ÿÿÿçéÎ&%& Ò ÿÿÿçÔÎ&%& à !ÿÿÿçÞÎ&%& Ò 6ÿÿÿçÞÎ&.& Ù KÿÿÿëÐÎ&%& Ò `ÿÿÿìÕÎ&.& ç uÿÿÿíØÎ&%& Ò ŠÿÿÿçÖÎ&.& Ù ŸÿÿÿíëË& îÿÿÿëëË&#& ü {ÿÿÿëëË&#& ü ÿÿÿêëË&#& ü ¥ÿÿÿðëË&& õ ºÿÿÿéëË&#& ü ÏÿÿÿçëË&,&  äÿÿÿïëË&#& ü ùÿÿÿíëË&,&  ÿÿÿëëË&,&  #ÿÿÿíëË&,&  8ÿÿÿðëË&,&  MÿÿÿìëË&,&  bÿÿÿíëË&,&  wÿÿÿíëË&,&  ŒÿÿÿçëË&,&  ¡ÿÿÿïëË&#& ü ¶ÿÿÿîëË&#& ü ËÿÿÿèìË&,&  àÿÿÿèëË&#&  õÿÿÿéëË&#& ü ÿÿÿçëË&#&  ÿÿÿçëË&#& ü 4ÿÿÿçëË&,&  IÿÿÿëëË&#& ü ^ÿÿÿìëË&,&  sÿÿÿíëË&#& ü ˆÿÿÿçëË&,&  ÿÿ ë¾& ÿÿÿëëÎ&"& % {ÿÿÿëëÎ&"& % ÿÿÿêëÎ&"& % ¥ÿÿÿðëË&&  ºÿÿÿéëÎ&"& % ÏÿÿÿçëË&+& : äÿÿÿïëÎ&"& % ùÿÿÿíëË&+& : ÿÿÿëëË&+& : #ÿÿÿíëË&+& : 8ÿÿÿðëË&+& : MÿÿÿìëË&+& : bÿÿÿíëË&+& : wÿÿÿíëË&+& : ŒÿÿÿçëË&+& : ¡ÿÿÿïëÎ&"& % ¶ÿÿÿîëÎ&"& % ËÿÿÿèìË&+& : àÿÿÿèëÎ&"& 3 õÿÿÿéëÎ&"& % ÿÿÿçëÎ&"& 3 ÿÿÿçëÎ&"& % 4ÿÿÿçëË&+& , IÿÿÿëëÎ&"& % ^ÿÿÿìëË&+& : sÿÿÿíëÎ&"& % ˆÿÿÿçëË&+& , ÿÿ ÿëÐÎ& AÿÿÿëÐÎ&$& N |ÿÿÿëÐÎ&$& N ‘ÿÿÿêëÎ&$& N ¦ÿÿÿðÕÎ&& G »ÿÿÿêãÎ&$& N ÐÿÿÿçßÎ&-& c åÿÿÿïÕÎ&$& N úÿÿÿíÕÎ&-& c ÿÿÿëÐÎ&-& c $ÿÿÿíÐÎ&-& c 9ÿÿÿíÐÎ&-& c NÿÿÿìéÎ&-& c cÿÿÿîÔÎ&-& c xÿÿÿíÕÎ&-& c ÿÿÿçÛÎ&-& c ¢ÿÿÿïÐÎ&$& N ·ÿÿÿîÐÎ&$& N ÌÿÿÿèéÎ&-& c áÿÿÿèãÎ&$& \ öÿÿÿééÎ&$& N ÿÿÿçÔÎ&$& \ ÿÿÿçâÎ&$& N 5ÿÿÿçâÎ&-& U JÿÿÿëÐÎ&$& N _ÿÿÿìÕÎ&-& c tÿÿÿíØÎ&$& N ‰ÿÿÿçÜÎ&-& U žÿÿ ÿëÐÎ&  jÿÿÿëÐÎ&& p yÿÿÿëÐÎ&& p ŽÿÿÿèìÎ&& p £ÿÿ ÿðÕÎ&& m ¸ÿÿÿéãÎ&& p Íÿÿ ÿçßÎ&(& v âÿÿÿïÕÎ&& p ÷ÿÿ ÿíÕÎ&(& v ÿÿ ÿëÐÎ&(& v !ÿÿ ÿíÐÎ&(& v 6ÿÿ ÿíÐÎ&(& v Kÿÿ ÿìéÎ&(& v `ÿÿ ÿíÔÎ&(& v uÿÿ ÿíÕÎ&(& v Šÿÿ ÿçÛÎ&(& v ŸÿÿÿïÐÎ&& p ´ÿÿÿîÐÎ&& p Éÿÿ ÿèìÎ&(& v ÞÿÿÿèãÎ&& s óÿÿÿééÎ&& p ÿÿÿçÔÎ&& s ÿÿÿçÞÎ&& p 2ÿÿ ÿçÞÎ&(& p GÿÿÿëÐÎ&& p \ÿÿ ÿìÕÎ&(& v qÿÿÿíØÎ&& p †ÿÿ ÿçÖÎ&(& p ›ÿÿÿëöÎ&U1ÿÿÿëõÎ&A&[ xÿÿÿëõÎ&A&[ ÿÿÿèõÎ&A&[ ¢ÿÿÿðöÎ&:&X »ÿÿÿêõÎ&A&[ ÌÿÿÿçõÎ& ã&aJÿÿÿïõÎ&A&[ öÿÿÿíõÎ&J&a ÿÿÿëõÎ&J&a ÿÿÿíõÎ&J&a 5ÿÿÿíõÎ&J&a JÿÿÿìõÎ&J&a _ÿÿÿíõÎ&J&a tÿÿÿíõÎ&J&a ‰ÿÿÿçõÎ&J&a žÿÿÿïõÎ&J&a ³ÿÿÿîõÎ&A&[ ÈÿÿÿèõÎ&J&a ÝÿÿÿèöÎ&A&^ òÿÿÿéõÎ&A&a ÿÿÿçöÎ&A&^ ÿÿÿçõÎ&A&[ 1ÿÿÿçõÎ&J&[ FÿÿÿëõÎ&A&[ [ÿÿÿìõÎ&J&a pÿÿÿíõÎ&J&a …ÿÿÿçõÎ&J&[ šÿÿÿëÐÎ&3dÿÿÿëÐÎ&C&j zÿÿÿëÐÎ&C&j ÿÿÿèìÎ&C&j ¤ÿÿÿðÕÎ&;&g ¹ÿÿÿéãÎ&C&j ÎÿÿÿçßÎ&L&p ãÿÿÿïÕÎ&C&j øÿÿÿíÕÎ&L&p ÿÿÿëÐÎ&L&p "ÿÿÿíÐÎ&L&p 7ÿÿÿíÐÎ&L&p LÿÿÿìéÎ&L&p aÿÿÿíÔÎ&L&p vÿÿÿíÕÎ&L&p ‹ÿÿÿçÛÎ&L&p  ÿÿÿïÐÎ&C&j µÿÿÿîÐÎ&C&j ÊÿÿÿèìÎ&L&p ßÿÿÿèàÎ&C&m ôÿÿÿééÎ&C&j ÿÿÿçÔÎ&C&m ÿÿÿçÞÎ&C&j 3ÿÿÿçÞÎ&L&j HÿÿÿëÐÎ&C&j ]ÿÿÿìÕÎ&L&p rÿÿÿíØÎ&C&j ‡ÿÿÿçÜÎ&L&j œÿÿÿëöÎ&s1ÿÿÿëöÎ&A&y xÿÿÿëöÎ&A&y ÿÿÿèöÎ&A&y ¢ÿÿÿðöÎ&:&v ·ÿÿÿêöÎ&A&y ÌÿÿÿçöÎ&J& áÿÿÿïöÎ&A&y öÿÿÿíöÎ&J& ÿÿÿëöÎ&J& ÿÿÿíöÎ&J& 5ÿÿÿíöÎ&J& JÿÿÿìöÎ&J& _ÿÿÿíöÎ&J& tÿÿÿíöÎ&J& ‰ÿÿÿçöÎ&J& žÿÿÿïöÎ&A&y ³ÿÿÿîöÎ&A&y ÈÿÿÿèöÎ&J& ÝÿÿÿèöÎ&A&| òÿÿÿéöÎ&A&y ÿÿÿçöÎ&A&| *ÿÿÿçöÎ&A&y 1ÿÿÿçöÎ&J&y FÿÿÿëöÎ&A&y [ÿÿÿìöÎ&J& pÿÿÿíöÎ&A&y …ÿÿÿçöÎ&J&y šÿÿÿëÐÎ&3‚ÿÿÿëÐÎ&C&ˆ zÿÿÿëÐÎ&C&ˆ ÿÿÿèìÎ&C&ˆ ¤ÿÿÿðÕÎ&;&… ¹ÿÿÿéãÎ&C&ˆ ÎÿÿÿçßÎ&L&Ž ãÿÿÿïÕÎ&C&ˆ øÿÿÿíÕÎ&L&Ž ÿÿÿëÐÎ&L&Ž "ÿÿÿíÐÎ&L&Ž 7ÿÿÿíÐÎ&L&Ž LÿÿÿìéÎ&L&Ž aÿÿÿíÔÎ&L&Ž vÿÿÿíÕÎ&L&Ž ‹ÿÿÿçÛÎ&L&Ž  ÿÿÿïÐÎ&C&ˆ µÿÿÿîÐÎ&C&ˆ ÊÿÿÿèìÎ&L&Ž ßÿÿÿèàÎ&C&‹ ôÿÿÿééÎ&C&ˆ ÿÿÿçÔÎ&C&‹ ÿÿÿçÞÎ&C&ˆ 3ÿÿÿçÞÎ&L&ˆ HÿÿÿëÐÎ&C&ˆ ]ÿÿÿìÕÎ&L&Ž rÿÿÿíØÎ&C&ˆ ‡ÿÿÿçÜÎ&L&ˆ œÿÿÿëÐÎ&2‘ÿÿÿëÐÎ&B&¨ yÿÿÿëÐÎ&B&¨ ŽÿÿÿèìÎ&B&¨ £ÿÿÿðÕÎ& ¸&›:ÿÿÿéãÎ&B&¨ ÍÿÿÿçßÎ&K&² âÿÿÿïÕÎ&B&¨ ÷ÿÿÿíÕÎ& &¨BÿÿÿëÐÎ&K&² !ÿÿÿíÐÎ&K&² 6ÿÿÿíÐÎ&K&² KÿÿÿìéÎ&K&² `ÿÿÿíÔÎ&K&² uÿÿÿíÕÎ&K&² ŠÿÿÿçÛÎ&K&² ŸÿÿÿïÐÎ&B&¨ ´ÿÿÿîÐÎ&B&¨ ÉÿÿÿèìÎ&B&¨ ÞÿÿÿèãÎ&B& ó¼ÿÿÿééÎ&B&¨ ÿÿÿçÔÎ&B&¼ ÿÿÿçÞÎ&B&¨ 2ÿÿÿçÞÎ&K&² GÿÿÿëÐÎ&B&¨ \ÿÿÿìÕÎ&K&² qÿÿÿíØÎ&B&¨ †ÿÿÿçÖÎ&K&² ›ÿÿÿëÐÎ&É3ÿÿÿëÐÎ&C&Ø zÿÿÿëÐÎ&C&Ø ÿÿÿèìÎ&C&Ø ¤ÿÿÿðÕÎ&;&Î ¹ÿÿÿéãÎ&C&Ø ÎÿÿÿçßÎ&L&â ãÿÿÿïÕÎ&C&Ø øÿÿÿíÕÎ&L&â ÿÿÿëÐÎ&L&â "ÿÿÿíÐÎ&L&â 7ÿÿÿíÐÎ&L&â LÿÿÿìéÎ&L&â aÿÿÿíÔÎ&L&â vÿÿÿíÕÎ&L&â ‹ÿÿÿçÛÎ&L&â  ÿÿÿïÐÎ&C&Ø µÿÿÿîÐÎ&C&Ø ÊÿÿÿèìÎ&L&â ßÿÿÿèàÎ&C&ì ôÿÿÿééÎ&C&Ø ÿÿÿçÔÎ&C&ì ÿÿÿçÞÎ&C&Ø 3ÿÿÿçÞÎ&L&â HÿÿÿëÐÎ&C&Ø ]ÿÿÿìÕÎ&L&â rÿÿÿíØÎ&C&Ø ‡ÿÿÿçÜÎ&L&â œÿÿÿëÐÎ&2÷ÿÿÿëÐÎ& &B yÿÿÿëÐÎ&B&  ŽÿÿÿèìÎ&B&  £ÿÿÿðÕÎ& ¸&:ÿÿÿÿéãÎ&B&  ÍÿÿÿçßÎ&K&  âÿÿÿïÕÎ&B&  ÷ÿÿÿíÕÎ&B&  ÿÿÿëÐÎ&K&  !ÿÿÿíÐÎ&K&  6ÿÿÿíÐÎ&K&  KÿÿÿìéÎ&K&  `ÿÿÿíÔÎ&K&  uÿÿÿíÕÎ&K&  ŠÿÿÿçÛÎ&K&  ŸÿÿÿïÐÎ&B&  ´ÿÿÿîÐÎ&B&  ÉÿÿÿèìÎ&K&  ÞÿÿÿèãÎ&B&  óÿÿÿééÎ&B&  ÿÿÿçÔÎ& &B ÿÿÿçÞÎ&B&  2ÿÿÿçÞÎ&K&  GÿÿÿëÐÎ&B&  \ÿÿÿìÕÎ&K&  qÿÿÿíØÎ&B&  †ÿÿÿçÖÎ&K&  ›ÿÿÿëÐÎ& &3ÿÿÿëÐÎ&C& 9 zÿÿÿëÐÎ&C& 9 ÿÿÿèìÎ&C& 9 ¤ÿÿÿðÕÎ&;& / ¹ÿÿÿéãÎ&C& 9 ÎÿÿÿçßÎ&L& C ãÿÿÿïÕÎ&C& 9 øÿÿÿíÕÎ&L& C ÿÿÿëÐÎ&L& C "ÿÿÿíÐÎ&L& C 7ÿÿÿíÐÎ&L& C LÿÿÿìéÎ&L& C aÿÿÿíÔÎ&L& C vÿÿÿíÕÎ&L& C ‹ÿÿÿçÛÎ&L& C  ÿÿÿïÐÎ&C& 9 µÿÿÿîÐÎ&C& 9 ÊÿÿÿèìÎ&L& C ßÿÿÿèàÎ&C& M ôÿÿÿééÎ&C& 9 ÿÿÿçÔÎ&C& M ÿÿÿçÞÎ&C& 9 3ÿÿÿçÞÎ&L& C HÿÿÿëÐÎ&C& 9 ]ÿÿÿìÕÎ&L& C rÿÿÿíØÎ&C& 9 ‡ÿÿÿçÜÎ&L& C œÿÿ êÁ&4 WÿÿÿëêÏ&D& h {ÿÿÿëêÏ&D& h ÿÿÿêêÏ&D& h ¥ÿÿÿðêÏ& ^& º<ÿÿÿéêÏ&D& h ÏÿÿÿçêÏ&M& r äÿÿÿïêÏ&D& h ùÿÿÿíêÏ&M&  rÿÿÿëêÏ&M& r #ÿÿÿíêÏ&M& r 8ÿÿÿðêÏ&M& r MÿÿÿìêÏ&M& r bÿÿÿíêÏ&M& r wÿÿÿíêÏ&M& r ŒÿÿÿçêÏ&M& r ¡ÿÿÿïêÏ&D& h ¶ÿÿÿîêÏ&D& h ËÿÿÿèìÏ&M& r àÿÿÿèêÏ&D& { õÿÿÿéêÏ&D& h ÿÿÿçêÏ&D& { ÿÿÿçêÏ&D& h 4ÿÿÿçêÏ&M& r IÿÿÿëêÏ&D& h ^ÿÿÿìêÏ&M& r sÿÿÿíêÏ&D& h ˆÿÿÿçêÏ&M& r ÿÿÿëöÎ&6 †ÿÿÿëöÎ&F& ™ |ÿÿÿëöÎ&F& ™ ‘ÿÿÿêöÎ&F& ™ ¦ÿÿÿðöÎ& & »=ÿÿÿêöÎ&F& ™ ÐÿÿÿçöÎ&O& £ åÿÿÿïöÎ&F& ™ úÿÿÿíöÎ&O& £ ÿÿÿëöÎ&O& £ $ÿÿÿíöÎ&O& £ 9ÿÿÿíöÎ&O& £ NÿÿÿìöÎ&O& £ cÿÿÿîöÎ&O& £ xÿÿÿíöÎ&O& £ ÿÿÿçöÎ&O& £ ¢ÿÿÿïöÎ&F& ™ ·ÿÿÿîöÎ&F& ™ ÌÿÿÿèöÎ&O& £ áÿÿÿèöÎ&F& ­ öÿÿÿéöÎ&F& ™ ÿÿÿçöÎ& ­&F ÿÿÿçöÎ&F& ™ 5ÿÿÿçöÎ&O& £ JÿÿÿëöÎ&F& ™ _ÿÿÿìöÎ&O& £ tÿÿÿíöÎ&F& ™ ‰ÿÿÿçöÎ&O& £ žÿÿÿëÐÎ&8 ·ÿÿÿëÐÎ&H& Ë |ÿÿÿëÐÎ&H& Ë ‘ÿÿÿêëÎ&H& Ë ¦ÿÿÿðÕÎ&?& Á »ÿÿÿêãÎ&H& Ë ÐÿÿÿçßÎ&Q& Õ åÿÿÿïÕÎ&H& Ë úÿÿÿíÕÎ&Q& Õ ÿÿÿëÐÎ&Q& Õ $ÿÿÿíÐÎ&Q& Õ 9ÿÿÿíÐÎ&Q& Õ NÿÿÿìéÎ&Q& Õ cÿÿÿîÔÎ&Q& Õ xÿÿÿíÕÎ&Q& Õ ÿÿÿçÛÎ&Q& Õ ¢ÿÿÿïÐÎ&H& Ë ·ÿÿÿîÐÎ&H& Ë ÌÿÿÿèéÎ&Q& Õ áÿÿÿèãÎ&H& ß öÿÿÿééÎ&H& Ë ÿÿÿçÔÎ&H& ß ÿÿÿçâÎ&H& Ë 5ÿÿÿçâÎ&Q& Õ JÿÿÿëÐÎ&H& Ë _ÿÿÿìÕÎ&Q& Õ tÿÿÿíØÎ&H& Ë ‰ÿÿÿçÜÎ&Q& Õ žÿÿÿëÐÎ&6 íÿÿÿëÐÎ&F& ü |ÿÿÿëÐÎ&F& ü ‘ÿÿÿêëÎ&F& ü ¦ÿÿÿðÕÎ&=& ò »ÿÿÿêãÎ&F& ü ÐÿÿÿçßÎ&O&  åÿÿÿïÕÎ&F& ü úÿÿÿíÕÎ&O&  ÿÿÿëÐÎ&O&  $ÿÿÿíÐÎ&O&  9ÿÿÿíÐÎ&O&  NÿÿÿìéÎ&O&  cÿÿÿîÔÎ&O&  xÿÿÿíÕÎ&O&  ÿÿÿçÛÎ&O&  ¢ÿÿÿïÐÎ&F& ü ·ÿÿÿîÐÎ&F& ü ÌÿÿÿèéÎ&O&  áÿÿÿèãÎ&F&  öÿÿÿééÎ&F& ü ÿÿÿçÔÎ&F&  ÿÿÿçâÎ&F& ü 5ÿÿÿçâÎ&O&  JÿÿÿëÐÎ&F& ü _ÿÿÿìÕÎ&O&  tÿÿÿíØÎ&F& ü ‰ÿÿÿçÜÎ&O&  žÿÿ ëÁ&4 ÿÿÿëëÏ&D& . {ÿÿÿëëÏ&D& . ÿÿÿêëÏ&D& . ¥ÿÿÿðëÏ&<& $ ºÿÿÿéëÏ&D& . ÏÿÿÿçëÏ&M& 8 äÿÿÿïëÏ&D& . ùÿÿÿíëÏ&M& 8 ÿÿÿëëÏ&M& 8 #ÿÿÿíëÏ&M& 8 8ÿÿÿðëÏ&M& 8 MÿÿÿìëÏ&M& 8 bÿÿÿíëÏ&M& 8 wÿÿÿíëÏ&M& 8 ŒÿÿÿçëÏ&M& 8 ¡ÿÿÿïëÏ&D& . ¶ÿÿÿîëÏ&D& . ËÿÿÿèìÏ&M& 8 àÿÿÿèëÏ&D& B õÿÿÿéëÏ&D& . ÿÿÿçëÏ& .&D ÿÿÿçëÏ&D& . 4ÿÿÿçëÏ&M& 8 IÿÿÿëëÏ&D& . ^ÿÿÿìëÏ&M& 8 sÿÿÿíëÏ&D& . ˆÿÿÿçëÏ&M& 8 ÿÿÿëëÏ&5 KÿÿÿëëÏ&E& V {ÿÿÿëëÏ&E& V ÿÿÿêëÏ&E& V ¥ÿÿÿðëÏ& Q& º<ÿÿÿéëÏ&E& V ÏÿÿÿçëÏ&N& k äÿÿÿïëÏ&E& V ùÿÿÿíëÏ&E& V ÿÿÿëëÏ&N& k #ÿÿÿíëÏ&N& k 8ÿÿÿðëÏ&N& k MÿÿÿìëÏ&N& k bÿÿÿíëÏ&N& k wÿÿÿíëÏ&N& k ŒÿÿÿçëÏ&N& k ¡ÿÿÿïëÏ& ¶& YEÿÿÿîëÏ&E& V ËÿÿÿèìÏ&N& k àÿÿÿèëÏ&E& õ VÿÿÿéëÏ&E& V ÿÿÿçëÏ&E& V ÿÿÿçëÏ&E& 4 VÿÿÿçëÏ&N& ] IÿÿÿëëÏ&E& V ^ÿÿÿìëÏ&N& k sÿÿÿíëÏ&E& V ˆÿÿÿçëÏ&N& ] ÿÿÿëÐÎ& r7ÿÿÿëÐÎ&G& € ~ÿÿÿëÐÎ&G& € “ÿÿÿêëÎ&G& € ¨ÿÿÿðÕÎ&>& y ½ÿÿÿéãÎ&G& € ÒÿÿÿçßÎ&P& • çÿÿÿïÕÎ&G& € üÿÿÿíÕÎ&G& € ÿÿÿëÐÎ&P& • &ÿÿÿíÐÎ&P& • ;ÿÿÿíÐÎ&P& • PÿÿÿëéÎ&P& • eÿÿÿîÔÎ&P& • zÿÿÿíÖÎ&P& • ÿÿÿçÛÎ&P& • ¤ÿÿÿïÐÎ&G& € ¹ÿÿÿîÐÎ&G& € ÎÿÿÿêéÎ&P& • ãÿÿÿèãÎ&G& Ž øÿÿÿçéÎ&G& € ÿÿÿçÔÎ&G& Ž "ÿÿÿçÞÎ&G& € 7ÿÿÿíÞÎ&P& ‡ LÿÿÿëÐÎ&G& € aÿÿÿìÕÎ&P& • vÿÿÿíØÎ&G& € ‹ÿÿÿçÜÎ&P& ‡  ÿÿÿëÐÎ&9 œÿÿÿëÐÎ&I& ª ~ÿÿÿëÐÎ&I& ª “ÿÿÿêëÎ&I& ª ¨ÿÿÿðÕÎ&@& £ ½ÿÿÿéãÎ&I& ª ÒÿÿÿçßÎ&R& ¿ çÿÿÿïÕÎ&I& ª üÿÿÿíÕÎ&R& ¿ ÿÿÿëÐÎ&R& ¿ &ÿÿÿíÐÎ&R& ¿ ;ÿÿÿíÐÎ&R& ¿ PÿÿÿëéÎ&R& ¿ eÿÿÿîÔÎ&R& ¿ zÿÿÿíÖÎ&R& ¿ ÿÿÿçÛÎ&R& ¿ ¤ÿÿÿïÐÎ&I& ª ¹ÿÿÿîÐÎ&I& ª ÎÿÿÿêéÎ&R& ¿ ãÿÿÿèãÎ&I& ¸ øÿÿÿçéÎ&I& ª ÿÿÿçÔÎ&I& ¸ "ÿÿÿçÞÎ&I& ª 7ÿÿÿíÞÎ&R& ± LÿÿÿëÐÎ&I& ª aÿÿÿìÕÎ&R& ¿ vÿÿÿíØÎ&I& ª ‹ÿÿÿçÜÎ&R& ±  ÿÿÿëÐÎ&7 ÆÿÿÿëÐÎ&G& Ô }ÿÿÿëÐÎ&G& Ô ’ÿÿÿêëÎ&G& Ô §ÿÿÿðÕÎ&>& Í ¼ÿÿÿéãÎ&G& Ô ÑÿÿÿçßÎ&P& é æÿÿÿïÕÎ&G& Ô ûÿÿÿíÕÎ&P& é ÿÿÿëÐÎ&P& é %ÿÿÿíÐÎ&P& é :ÿÿÿíÐÎ&P& é OÿÿÿëèÎ&P& é dÿÿÿîÔÎ&P& é yÿÿÿíÖÎ&P& é ŽÿÿÿçÛÎ&P& é £ÿÿÿïÐÎ&G& Ô ¸ÿÿÿîÐÎ&G& Ô ÍÿÿÿêéÎ&P& é âÿÿÿèãÎ&G& â ÷ÿÿÿçéÎ&G& Ô ÿÿÿçÔÎ&G& â !ÿÿÿçÞÎ&G& Ô 6ÿÿÿçÞÎ&P& Û KÿÿÿëÐÎ&G& Ô `ÿÿÿìÕÎ&P& é uÿÿÿíØÎ&G& Ô ŠÿÿÿçÖÎ&P& Û ŸÿÿÿíëÏ& ð5ÿÿÿëëÏ&E& þ {ÿÿÿëëÏ&E& þ ÿÿÿêëÏ&E& þ ¥ÿÿÿðëÏ&<& ÷ ºÿÿÿéëÏ&E& þ ÏÿÿÿçëÏ&N&  äÿÿÿïëÏ&E& þ ùÿÿÿíëÏ&N&  ÿÿÿëëÏ&N&  #ÿÿÿíëÏ&N&  8ÿÿÿðëÏ&N&  MÿÿÿìëÏ&N&  bÿÿÿíëÏ&N&  wÿÿÿíëÏ&N&  ŒÿÿÿçëÏ&N&  ¡ÿÿÿïëÏ&E& þ ¶ÿÿÿîëÏ&E& þ ËÿÿÿèìÏ&N&  àÿÿÿèëÏ&E&  õÿÿÿéëÏ&E& þ ÿÿÿçëÏ&E& þ ÿÿÿçëÏ&E& þ 4ÿÿÿçëÏ&N&  IÿÿÿëëÏ&E& þ ^ÿÿÿìëÏ&N&  sÿÿÿíëÏ&E& þ ˆÿÿÿçëÏ&N&  ÿÿ ëÁ&4 ÿÿÿëëÏ&D& ' {ÿÿÿëëÏ&D& ' ÿÿÿêëÏ&D& ' ¥ÿÿÿðëÏ& & º<ÿÿÿéëÏ&D& ' ÏÿÿÿçëÏ&M& < äÿÿÿïëÏ&D& ' ùÿÿÿíëÏ&M&  <ÿÿÿëëÏ&M& < #ÿÿÿíëÏ&M& < 8ÿÿÿðëÏ&M& < MÿÿÿìëÏ&M& < bÿÿÿíëÏ&M& < wÿÿÿíëÏ&M& < ŒÿÿÿçëÏ&M& < ¡ÿÿÿïëÏ& ¶& 'DÿÿÿîëÏ&D& ' ËÿÿÿèìÏ&M& < àÿÿÿèëÏ&D& 5 õÿÿÿéëÏ&D& ' ÿÿÿçëÏ&D& 5 ÿÿÿçëÏ&D& ' 4ÿÿÿçëÏ&M& . IÿÿÿëëÏ&D& ' ^ÿÿÿìëÏ&M& < sÿÿÿíëÏ&D& ' ˆÿÿÿçëÏ&M& . ÿÿÿëÐÎ&6 CÿÿÿëÐÎ&F& P |ÿÿÿëÐÎ&F& P ‘ÿÿÿêëÎ&F& P ¦ÿÿÿðÕÎ&=& I »ÿÿÿêãÎ&F& P ÐÿÿÿçßÎ&O& e åÿÿÿïÕÎ&F& P úÿÿÿíÕÎ&O& e ÿÿÿëÐÎ&O& e $ÿÿÿíÐÎ&O& e 9ÿÿÿíÐÎ&O& e NÿÿÿìéÎ&O& e cÿÿÿîÔÎ&O& e xÿÿÿíÕÎ&O& e ÿÿÿçÛÎ&O& e ¢ÿÿÿïÐÎ&F& P ·ÿÿÿîÐÎ&F& P ÌÿÿÿèéÎ&O& e áÿÿÿèãÎ&F& ^ öÿÿÿééÎ&F& P ÿÿÿçÔÎ&F& ^ ÿÿÿçâÎ&F& P 5ÿÿÿçâÎ&O& W JÿÿÿëÐÎ&F& P _ÿÿÿìÕÎ&O& e tÿÿÿíØÎ&F& P ‰ÿÿÿçÜÎ&O& W žÿÿÿëÐÎ& i2ÿÿÿëÐÎ&A& o yÿÿÿëÐÎ&A& o ŽÿÿÿèìÎ&A& o £ÿÿÿðÕÎ&:& » lÿÿÿéãÎ&A& o ÍÿÿÿçßÎ&J& u âÿÿÿïÕÎ&A& o ÷ÿÿÿíÕÎ& o&B ÿÿÿëÐÎ&J& u !ÿÿÿíÐÎ&J& u 6ÿÿÿíÐÎ&J& u KÿÿÿìéÎ&J& u `ÿÿÿíÔÎ&J& u uÿÿÿíÕÎ&J& u ŠÿÿÿçÛÎ&J& u ŸÿÿÿïÐÎ&J& o ´ÿÿÿîÐÎ&J& o ÉÿÿÿèìÎ&J& u ÞÿÿÿèãÎ&A& r óÿÿÿééÎ&B& o ÿÿÿçÔÎ&A& r ÿÿÿçÞÎ&A& o 2ÿÿÿçÞÎ&J& o GÿÿÿëÐÎ&A& o \ÿÿÿìÕÎ&J& u qÿÿÿíØÎ&A& o †ÿÿÿçÖÎ&J& o ›ÿÿÿëöÎ&USÿÿÿëõÎ&e&[ xÿÿÿëõÎ&e&[ ÿÿÿèõÎ&e&[ ¢ÿÿÿðöÎ&\&X ·ÿÿÿêõÎ&e&[ ÌÿÿÿçõÎ&n&a áÿÿÿïõÎ&e&[ öÿÿÿíõÎ&n&a ÿÿÿëõÎ&n&a ÿÿÿíõÎ&n&a 5ÿÿÿíõÎ&n&a JÿÿÿìõÎ&n&a _ÿÿÿíõÎ&n&a tÿÿÿíõÎ&n&a ‰ÿÿÿçõÎ&n&a žÿÿÿïõÎ&e&[ ³ÿÿÿîõÎ&e&[ ÈÿÿÿèõÎ&n&a ÝÿÿÿèöÎ&e&^ òÿÿÿéõÎ&e&[ ÿÿÿçöÎ&e&^ *ÿÿÿçõÎ&e&[ 1ÿÿÿçõÎ&n&[ FÿÿÿëõÎ&e&[ [ÿÿÿìõÎ&n&a pÿÿÿíõÎ&e&[ …ÿÿÿçõÎ&n&[ šÿÿÿëÐÎ&Udÿÿ ÿëÐÎ&g&j zÿÿ ÿëÐÎ&g&j ÿÿ ÿèìÎ&g&j ¤ÿÿ ÿðÕÎ&^&g ¹ÿÿ ÿéãÎ&g&j Îÿÿ ÿçßÎ&p&p ãÿÿ ÿïÕÎ&g&j øÿÿ ÿíÕÎ&p&p ÿÿ ÿëÐÎ&p&p "ÿÿ ÿíÐÎ&p&p 7ÿÿ ÿíÐÎ&p&p Lÿÿ ÿìéÎ&p&p aÿÿ ÿíÔÎ&p&p vÿÿ ÿíÕÎ&p&p ‹ÿÿ ÿçÛÎ&p&p  ÿÿ ÿïÐÎ&g&j µÿÿ ÿîÐÎ&g&j Êÿÿ ÿèìÎ&p&p ßÿÿ ÿèàÎ&g&m ôÿÿ ÿééÎ&g&j ÿÿ ÿçÔÎ&m&g ÿÿ ÿçÞÎ&g&j 3ÿÿ ÿçÞÎ&p&j Hÿÿ ÿëÐÎ&g&j ]ÿÿ ÿìÕÎ&p&p rÿÿ ÿíØÎ&g&j ‡ÿÿ ÿçÜÎ&p&j œÿÿÿëöÎ&SsÿÿÿëöÎ&e&y xÿÿÿëöÎ&e&y ÿÿÿèöÎ&e&y ¢ÿÿÿðöÎ&\&v ·ÿÿÿêöÎ&e&y ÌÿÿÿçöÎ&n& áÿÿÿïöÎ&e&y öÿÿÿíöÎ&n& ÿÿÿëöÎ&n& ÿÿÿíöÎ&n& 5ÿÿÿíöÎ&n& JÿÿÿìöÎ&n& _ÿÿÿíöÎ&n& tÿÿÿíöÎ&n& ‰ÿÿÿçöÎ&n& žÿÿÿïöÎ&e&y ³ÿÿÿîöÎ&e&y ÈÿÿÿèöÎ&n& ÝÿÿÿèöÎ&e&| òÿÿÿéöÎ&e&y ÿÿÿçöÎ&e&| ÿÿÿçöÎ&e&y 1ÿÿÿçöÎ&n&y FÿÿÿëöÎ&e&y [ÿÿÿìöÎ&n& pÿÿÿíöÎ&e&y …ÿÿÿçöÎ&n&y šÿÿÿëÐÎ&U‚ÿÿ ÿëÐÎ&g&ˆ zÿÿ ÿëÐÎ&g&ˆ ÿÿ ÿèìÎ&g&ˆ ¤ÿÿ ÿðÕÎ&^&… ¹ÿÿ ÿéãÎ&g&ˆ Îÿÿ ÿçßÎ&p&Ž ãÿÿ ÿïÕÎ&g&ˆ øÿÿ ÿíÕÎ&p&Ž ÿÿ ÿëÐÎ&p&Ž "ÿÿ ÿíÐÎ&p&Ž 7ÿÿ ÿíÐÎ&p&Ž Lÿÿ ÿìéÎ&p&Ž aÿÿ ÿíÔÎ&p&Ž vÿÿ ÿíÕÎ&p&Ž ‹ÿÿ ÿçÛÎ&p&Ž  ÿÿ ÿïÐÎ&g&ˆ µÿÿ ÿîÐÎ&g&ˆ Êÿÿ ÿèìÎ&p&Ž ßÿÿ ÿèàÎ&g&‹ ôÿÿ ÿééÎ&g&ˆ ÿÿ ÿçÔÎ&g&‹ ÿÿ ÿçÞÎ&g&ˆ 3ÿÿ ÿçÞÎ&p&ˆ Hÿÿ ÿëÐÎ&g&ˆ ]ÿÿ ÿìÕÎ&p&Ž rÿÿ ÿíØÎ&g&ˆ ‡ÿÿ ÿçÜÎ&p&ˆ œÿÿ ÿëÐÎ&T‘ÿÿÿëÐÎ&e& x©ÿÿ ÿëÐÎ&f&© Žÿÿ ÿèìÎ&f&© £ÿÿ ÿðÕÎ&]& ¸›ÿÿ ÿéãÎ&f&© Íÿÿ ÿçßÎ&o&³ âÿÿ ÿïÕÎ&f&© ÷ÿÿÿíÕÎ& &n³ÿÿ ÿëÐÎ&o&³ !ÿÿ ÿíÐÎ&o&³ 6ÿÿ ÿíÐÎ&o&³ Kÿÿ ÿìéÎ&o&³ `ÿÿ ÿíÔÎ&o&³ uÿÿ ÿíÕÎ&o&³ Šÿÿ ÿçÛÎ&o&³ ŸÿÿÿïÐÎ&³&n ´ÿÿ ÿîÐÎ&f&© Éÿÿ ÿèìÎ&o&³ Þÿÿ ÿèãÎ&f&½ óÿÿ ÿééÎ&f&© ÿÿ ÿçÔÎ&f&½ ÿÿ ÿçÞÎ&f&© 2ÿÿ ÿçÞÎ&o&³ Gÿÿ ÿëÐÎ&f&© \ÿÿ ÿìÕÎ&o&³ qÿÿ ÿíØÎ&f&© †ÿÿ ÿçÖÎ&o&³ ›ÿÿÿëÐÎ&ÉUÿÿ ÿëÐÎ&g&Ù zÿÿ ÿëÐÎ&g&Ù ÿÿ ÿèìÎ&g&Ù ¤ÿÿ ÿðÕÎ&^&Ï ¹ÿÿ ÿéãÎ&g&Ù Îÿÿ ÿçßÎ&p&ã ãÿÿ ÿïÕÎ&g&Ù øÿÿ ÿíÕÎ&p&ã ÿÿ ÿëÐÎ&p&ã "ÿÿ ÿíÐÎ&p&ã 7ÿÿ ÿíÐÎ&p&ã Lÿÿ ÿìéÎ&p&ã aÿÿ ÿíÔÎ&p&ã vÿÿ ÿíÕÎ&p&ã ‹ÿÿ ÿçÛÎ&p&ã  ÿÿ ÿïÐÎ&g&Ù µÿÿ ÿîÐÎ&g&Ù Êÿÿ ÿèìÎ&p&ã ßÿÿ ÿèàÎ&g&í ôÿÿ ÿééÎ&g&Ù ÿÿ ÿçÔÎ&g&í ÿÿ ÿçÞÎ&g&Ù 3ÿÿ ÿçÞÎ&p&ã Hÿÿ ÿëÐÎ&g&Ù ]ÿÿ ÿìÕÎ&p&ã rÿÿ ÿíØÎ&g&Ù ‡ÿÿ ÿçÜÎ&p&ã œÿÿ ÿëÐÎ&T÷ÿÿ ÿëÐÎ&f&  yÿÿ ÿëÐÎ&f&  Žÿÿ ÿèìÎ&f&  £ÿÿ ÿðÕÎ&]&ÿ ¸ÿÿ ÿéãÎ&f&  Íÿÿ ÿçßÎ&o&  âÿÿ ÿïÕÎ&f&  ÷ÿÿ ÿíÕÎ&o&  ÿÿ ÿëÐÎ&o&  !ÿÿ ÿíÐÎ&o&  6ÿÿ ÿíÐÎ&o&  Kÿÿ ÿìéÎ&o&  `ÿÿ ÿíÔÎ&o&  uÿÿ ÿíÕÎ&o&  Šÿÿ ÿçÛÎ&o&  Ÿÿÿ ÿïÐÎ&f&  ´ÿÿ ÿîÐÎ&f&  Éÿÿ ÿèìÎ&o&  Þÿÿ ÿèãÎ&f&  óÿÿ ÿééÎ& & fÿÿ ÿçÔÎ&f&  ÿÿ ÿçÞÎ&f&  2ÿÿ ÿçÞÎ&o&  Gÿÿ ÿëÐÎ&f&  \ÿÿ ÿìÕÎ&o&  qÿÿ ÿíØÎ&f&  †ÿÿ ÿçÖÎ&o&  ›ÿÿÿëÐÎ&U &ÿÿ ÿëÐÎ&g& : zÿÿ ÿëÐÎ&g& : ÿÿ ÿèìÎ&g& : ¤ÿÿ ÿðÕÎ&^& 0 ¹ÿÿ ÿéãÎ&g& : Îÿÿ ÿçßÎ&p& D ãÿÿ ÿïÕÎ&g& : øÿÿ ÿíÕÎ&p& D ÿÿ ÿëÐÎ&p& D "ÿÿ ÿíÐÎ&p& D 7ÿÿ ÿíÐÎ&p& D Lÿÿ ÿìéÎ&p& D aÿÿ ÿíÔÎ&p& D vÿÿ ÿíÕÎ&p& D ‹ÿÿ ÿçÛÎ&p& D  ÿÿ ÿïÐÎ&g& : µÿÿ ÿîÐÎ&g& : Êÿÿ ÿèìÎ&p& D ßÿÿ ÿèàÎ&g& N ôÿÿ ÿééÎ&g& : ÿÿ ÿçÔÎ&g& N ÿÿ ÿçÞÎ&g& : 3ÿÿ ÿçÞÎ&p& D Hÿÿ ÿëÐÎ&g& : ]ÿÿ ÿìÕÎ&p& D rÿÿ ÿíØÎ&g& : ‡ÿÿ ÿçÜÎ&p& D œÿÿ ê½&V WÿÿÿëêÉ&h& i {ÿÿÿëêÉ&h& i ÿÿÿêêÉ&h& i ¥ÿÿÿðêÉ& º&h _ÿÿÿéêÉ&h& i ÏÿÿÿçêÉ&q& s äÿÿÿïêÉ&h& i ùÿÿÿíêÉ&q& s ÿÿÿëêÉ&q& s #ÿÿÿíêÉ&q& s 8ÿÿÿðêÉ&q& s MÿÿÿìêÉ&q& s bÿÿÿíêÉ&q& s wÿÿÿíêÉ&q& s ŒÿÿÿçêÉ&q& s ¡ÿÿÿïêÉ&h& i ¶ÿÿÿîêÉ&h& i ËÿÿÿèìÉ&q& s àÿÿÿèêÉ&h& | õÿÿÿéêÉ&h& i ÿÿÿçêÉ&h& | ÿÿÿçêÉ&h& i 4ÿÿÿçêÉ&q& s IÿÿÿëêÉ&h& i ^ÿÿÿìêÉ&q& s sÿÿÿíêÉ&h& i ˆÿÿÿçêÉ&q& s ÿÿÿëöÎ& †XÿÿÿëöÎ&j& š |ÿÿÿëöÎ&j& š ‘ÿÿÿêöÎ&j& š ¦ÿÿÿðöÎ&a&  »ÿÿÿêöÎ&j& š ÐÿÿÿçöÎ&s& ¤ åÿÿÿïöÎ&j& š úÿÿÿíöÎ&s& ¤ ÿÿÿëöÎ&s& ¤ $ÿÿÿíöÎ&s& ¤ 9ÿÿÿíöÎ&s& ¤ NÿÿÿìöÎ&s& ¤ cÿÿÿîöÎ&s& ¤ xÿÿÿíöÎ&s& ¤ ÿÿÿçöÎ&s& ¤ ¢ÿÿÿïöÎ&j& š ·ÿÿÿîöÎ&j& š ÌÿÿÿèöÎ&s& ¤ áÿÿÿèöÎ&j& ® öÿÿÿéöÎ&j& š ÿÿÿçöÎ&j& ® ÿÿÿçöÎ&j& š 5ÿÿÿçöÎ&s& ¤ JÿÿÿëöÎ&j& š _ÿÿÿìöÎ&s& ¤ tÿÿÿíöÎ&j& š ‰ÿÿÿçöÎ&s& ¤ žÿÿÿëÐÎ&Z ¸ÿÿÿëÐÎ&l& Ì |ÿÿÿëÐÎ&l& Ì ‘ÿÿÿêëÎ&l& Ì ¦ÿÿÿðÕÎ&c&  »ÿÿÿêãÎ&l& Ì ÐÿÿÿçßÎ&u& Ö åÿÿÿïÕÎ&l& Ì úÿÿÿíÕÎ&u& Ö ÿÿÿëÐÎ&u& Ö $ÿÿÿíÐÎ&u& Ö 9ÿÿÿíÐÎ&u& Ö NÿÿÿìéÎ&u& Ö cÿÿÿîÔÎ&u& Ö xÿÿÿíÕÎ&u& Ö ÿÿÿçÛÎ&u& Ö ¢ÿÿÿïÐÎ&l& Ì ·ÿÿÿîÐÎ&l& Ì ÌÿÿÿèéÎ&u& Ö áÿÿÿèãÎ&l& à öÿÿÿééÎ&l& Ì ÿÿÿçÔÎ&l& à ÿÿÿçâÎ&l& Ì 5ÿÿÿçâÎ&u& Ö JÿÿÿëÐÎ&l& Ì _ÿÿÿìÕÎ&u& Ö tÿÿÿíØÎ&l& Ì ‰ÿÿÿçÜÎ&u& Ö žÿÿÿëÐÎ&X íÿÿÿëÐÎ&j& ý |ÿÿÿëÐÎ&j& ý ‘ÿÿÿêëÎ&j& ý ¦ÿÿÿðÕÎ&a& ó »ÿÿÿêãÎ&j& ý ÐÿÿÿçßÎ&s&  åÿÿÿïÕÎ&j& ý úÿÿÿíÕÎ&s&  ÿÿÿëÐÎ&s&  $ÿÿÿíÐÎ&s&  9ÿÿÿíÐÎ&s&  NÿÿÿìéÎ&s&  cÿÿÿîÔÎ&s&  xÿÿÿíÕÎ&s&  ÿÿÿçÛÎ&s&  ¢ÿÿÿïÐÎ&j& ý ·ÿÿÿîÐÎ&j& ý ÌÿÿÿèéÎ&s&  áÿÿÿèãÎ&j&  öÿÿÿééÎ&j& ý ÿÿÿçÔÎ&j&  ÿÿÿçâÎ&j& ý 5ÿÿÿçâÎ&s&  JÿÿÿëÐÎ&j& ý _ÿÿÿìÕÎ&s&  tÿÿÿíØÎ&j& ý ‰ÿÿÿçÜÎ&s&  žÿÿ ë½&V ÿÿÿëëÉ&h& / {ÿÿÿëëÉ&h& / ÿÿÿêëÉ&h& / ¥ÿÿÿðëÉ&_& % ºÿÿÿéëÉ&h& / ÏÿÿÿçëÉ&q& 9 äÿÿÿïëÉ&h& / ùÿÿÿíëÉ&q& 9 ÿÿÿëëÉ&q& 9 #ÿÿÿíëÉ&q& 9 8ÿÿÿðëÉ&q& 9 MÿÿÿìëÉ&q& 9 bÿÿÿíëÉ&q& 9 wÿÿÿíëÉ&q& 9 ŒÿÿÿçëÉ&q& 9 ¡ÿÿÿïëÉ&h& / ¶ÿÿÿîëÉ&h& / ËÿÿÿèìÉ&q& 9 àÿÿÿèëÉ&h& C õÿÿÿéëÉ&h& / ÿÿÿçëÉ&h& C ÿÿÿçëÉ&h& / 4ÿÿÿçëÉ&q& 9 IÿÿÿëëÉ&h& / ^ÿÿÿìëÉ&q& 9 sÿÿÿíëÉ&h& / ˆÿÿÿçëÉ&q& 9 ÿÿÿëëÉ&W KÿÿÿëëÉ&i& V {ÿÿÿëëÉ&i& V ÿÿÿêëÉ&i& V ¥ÿÿÿðëÉ& Q& º`ÿÿÿéëÉ&i& V ÏÿÿÿçëÉ&r& k äÿÿÿïëÉ&i& ù VÿÿÿíëÉ& &r kÿÿÿëëÉ&r& k #ÿÿÿíëÉ&r& k 8ÿÿÿðëÉ&r& k MÿÿÿìëÉ&r& k bÿÿÿíëÉ&r& k wÿÿÿíëÉ&r& k ŒÿÿÿçëÉ&r& k ¡ÿÿÿïëÉ&i& V ¶ÿÿÿîëÉ&i& V ËÿÿÿèìÉ&r& k àÿÿÿèëÉ&i& d õÿÿÿéëÉ&i& V ÿÿÿçëÉ&i&  VÿÿÿçëÉ&i& V 4ÿÿÿçëÉ&r& ] IÿÿÿëëÉ&i& V ^ÿÿÿìëÉ&r& k sÿÿÿíëÉ&i& V ˆÿÿÿçëÉ&r& ] ÿÿÿëÐÎ&Y rÿÿÿëÐÎ&k& € ~ÿÿÿëÐÎ&k& € “ÿÿÿêëÎ&k& € ¨ÿÿÿðÕÎ&b& y ½ÿÿÿéãÎ&k& € ÒÿÿÿçßÎ&t& • çÿÿÿïÕÎ&k& € üÿÿÿíÕÎ&t& • ÿÿÿëÐÎ&t& • &ÿÿÿíÐÎ&t& • ;ÿÿÿíÐÎ&t& • PÿÿÿëéÎ&t& • eÿÿÿîÔÎ&t& • zÿÿÿíÖÎ&t& • ÿÿÿçÛÎ&t& • ¤ÿÿÿïÐÎ&k& € ¹ÿÿÿîÐÎ&k& € ÎÿÿÿêéÎ&t& • ãÿÿÿèãÎ&k& Ž øÿÿÿçéÎ&k& € ÿÿÿçÔÎ&k& Ž "ÿÿÿçÞÎ&k& € 7ÿÿÿíÞÎ&t& ‡ LÿÿÿëÐÎ&k& € aÿÿÿìÕÎ&t& • vÿÿÿíØÎ&k& € ‹ÿÿÿçÜÎ&t& ‡  ÿÿÿëÐÎ&[ œÿÿÿëÐÎ&m& ª ~ÿÿÿëÐÎ&m& ª “ÿÿÿêëÎ&m& ª ¨ÿÿÿðÕÎ&d& £ ½ÿÿÿéãÎ&m& ª ÒÿÿÿçßÎ&v& ¿ çÿÿÿïÕÎ&m& ª üÿÿÿíÕÎ&v& ¿ ÿÿÿëÐÎ&v& ¿ &ÿÿÿíÐÎ&v& ¿ ;ÿÿÿíÐÎ&v& ¿ PÿÿÿëéÎ&v& ¿ eÿÿÿîÔÎ&v& ¿ zÿÿÿíÖÎ&v& ¿ ÿÿÿçÛÎ&v& ¿ ¤ÿÿÿïÐÎ&m& ª ¹ÿÿÿîÐÎ&m& ª ÎÿÿÿêéÎ&v& ¿ ãÿÿÿèãÎ&m& ¸ øÿÿÿçéÎ&m& ª ÿÿÿçÔÎ&m& ¸ "ÿÿÿçÞÎ&m& ª 7ÿÿÿíÞÎ&v& ± LÿÿÿëÐÎ&m& ª aÿÿÿìÕÎ&v& ¿ vÿÿÿíØÎ&m& ª ‹ÿÿÿçÜÎ&v& ±  ÿÿÿëÐÎ&Y ÆÿÿÿëÐÎ&k& Ô }ÿÿÿëÐÎ&k& Ô ’ÿÿÿêëÎ&k& Ô §ÿÿÿðÕÎ&b& Í ¼ÿÿÿéãÎ&k& Ô ÑÿÿÿçßÎ&t& é æÿÿÿïÕÎ&k& Ô ûÿÿÿíÕÎ&t& é ÿÿÿëÐÎ&t& é %ÿÿÿíÐÎ&t& é :ÿÿÿíÐÎ&t& é OÿÿÿëèÎ&t& é dÿÿÿîÔÎ&t& é yÿÿÿíÖÎ&t& é ŽÿÿÿçÛÎ&t& é £ÿÿÿïÐÎ&k& Ô ¸ÿÿÿîÐÎ&k& Ô ÍÿÿÿêéÎ&t& é âÿÿÿèãÎ&k& â ÷ÿÿÿçéÎ&k& Ô ÿÿÿçÔÎ&k& â !ÿÿÿçÞÎ&k& Ô 6ÿÿÿçÞÎ&t& Û KÿÿÿëÐÎ&k& Ô `ÿÿÿìÕÎ&t& é uÿÿÿíØÎ&k& Ô ŠÿÿÿçÖÎ&t& Û ŸÿÿÿíëÉ&W ðÿÿÿëëÉ&i& þ {ÿÿÿëëÉ&i& þ ÿÿÿêëÉ&i& þ ¥ÿÿÿðëÉ&`& ÷ ºÿÿÿéëÉ&i& þ ÏÿÿÿçëÉ&r&  äÿÿÿïëÉ&i& þ ùÿÿÿíëÉ&r&  ÿÿÿëëÉ&r&  #ÿÿÿíëÉ&r&  8ÿÿÿðëÉ&r&  MÿÿÿìëÉ&r&  bÿÿÿíëÉ&r&  wÿÿÿíëÉ&r&  ŒÿÿÿçëÉ&r&  ¡ÿÿÿïëÉ&i& þ ¶ÿÿÿîëÉ&i& þ ËÿÿÿèìÉ&r&  àÿÿÿèëÉ&i&  õÿÿÿéëÉ&i& þ ÿÿÿçëÉ&i&  ÿÿÿçëÉ&i& þ 4ÿÿÿçëÉ&r&  IÿÿÿëëÉ&i& þ ^ÿÿÿìëÉ&r&  sÿÿÿíëÉ&i& þ ˆÿÿÿçëÉ&r&  ÿÿ ë½&V ÿÿÿëëÉ&h& ' {ÿÿÿëëÉ&h& ' ÿÿÿêëÉ&h& ' ¥ÿÿÿðëÉ&_&  ºÿÿÿéëÉ&h& ' ÏÿÿÿçëÉ&q& < äÿÿÿïëÉ&h& ' ùÿÿÿíëÉ&q& < ÿÿÿëëÉ&q& < #ÿÿÿíëÉ&q& < 8ÿÿÿðëÉ&q& < MÿÿÿìëÉ&q& < bÿÿÿíëÉ&q& < wÿÿÿíëÉ&q& < ŒÿÿÿçëÉ&q& < ¡ÿÿÿïëÉ&h& ¶ %ÿÿÿîëÉ&h& ' ËÿÿÿèìÉ&q& < àÿÿÿèëÉ&h& 5 õÿÿÿéëÉ&h& ' ÿÿÿçëÉ&h&  5ÿÿÿçëÉ&h& ' 4ÿÿÿçëÉ&q& . IÿÿÿëëÉ&h& ' ^ÿÿÿìëÉ&q& < sÿÿÿíëÉ&h& ' ˆÿÿÿçëÉ&q& . ÿÿÿëÐÎ&X CÿÿÿëÐÎ&j& P |ÿÿÿëÐÎ&j& P ‘ÿÿÿêëÎ&j& P ¦ÿÿÿðÕÎ&a& I »ÿÿÿêãÎ&j& P ÐÿÿÿçßÎ&s& e åÿÿÿïÕÎ&j& P úÿÿÿíÕÎ&s& e ÿÿÿëÐÎ&s& e $ÿÿÿíÐÎ&s& e 9ÿÿÿíÐÎ&s& e NÿÿÿìéÎ&s& e cÿÿÿîÔÎ&s& e xÿÿÿíÕÎ&s& e ÿÿÿçÛÎ&s& e ¢ÿÿÿïÐÎ&j& P ·ÿÿÿîÐÎ&j& P ÌÿÿÿèéÎ&s& e áÿÿÿèãÎ&j& ^ öÿÿÿééÎ&j& P ÿÿÿçÔÎ&j& ^ ÿÿÿçâÎ&j& P 5ÿÿÿçâÎ&s& W JÿÿÿëÐÎ&j& P _ÿÿÿìÕÎ&s& e tÿÿÿíØÎ&j& P ‰ÿÿÿçÜÎ&s& W žÿÿ ÿëÐÎ&T iÿÿÿëÐÎ& o&e xÿÿÿëÐÎ&e& o ŽÿÿÿèìÎ&e& o £ÿÿÿðÕÎ&\& l »ÿÿÿéãÎ&e& o ÍÿÿÿçßÎ&n& u âÿÿÿïÕÎ&e& o ÷ÿÿÿíÕÎ&e&  uÿÿÿëÐÎ&n& u !ÿÿÿíÐÎ&n& u 6ÿÿÿíÐÎ&n& u KÿÿÿìéÎ&n& u `ÿÿÿíÔÎ&n& u uÿÿÿíÕÎ&n& u ŠÿÿÿçÛÎ&n& u ŸÿÿÿïÐÎ&n& u ´ÿÿÿîÐÎ&n& u ÉÿÿÿèìÎ&n& u ÞÿÿÿèãÎ&n& r óÿÿÿééÎ&e& o ÿÿÿçÔÎ&e& r ÿÿÿçÞÎ&e& o 2ÿÿÿçÞÎ&n& o GÿÿÿëÐÎ&e& o \ÿÿÿìÕÎ&n& u qÿÿÿíØÎ&e& o †ÿÿÿçÖÎ&n& o ›ÿÿ ÿëöÎ&wVÿÿÿëõÎ&‰&\ xÿÿÿëõÎ&‰&\ ÿÿÿèõÎ&‰&\ ¢ÿÿ ÿðöÎ&€&Y ·ÿÿÿêõÎ&‰&\ ÌÿÿÿçõÎ&’&b áÿÿÿïõÎ&‰&\ öÿÿÿíõÎ&’&b ÿÿÿëõÎ&’&b ÿÿÿíõÎ&’&b 5ÿÿÿíõÎ&’&b JÿÿÿìõÎ&’&b _ÿÿÿíõÎ&’&b tÿÿÿíõÎ&’&b ‰ÿÿÿçõÎ&’&b žÿÿÿïõÎ&‰&\ ³ÿÿÿîõÎ&‰&\ ÈÿÿÿèõÎ&’&b ÝÿÿÿèõÎ&‰&_ òÿÿÿéõÎ&‰&\ ÿÿÿçõÎ&‰&_ ÿÿÿçõÎ&‰&\ 1ÿÿÿçõÎ&’&\ FÿÿÿëõÎ&‰&\ [ÿÿÿìõÎ&’&b pÿÿÿíõÎ&‰&\ …ÿÿÿçõÎ&’&\ šÿÿÿëÑÎ&yeÿÿÿëÐÎ&‹&k zÿÿÿëÐÎ&‹&k ÿÿÿèìÎ&‹&k ¤ÿÿÿðÕÎ&‚&h ¹ÿÿÿéãÎ&‹&k ÎÿÿÿçßÎ&”&q ãÿÿÿïÕÎ&‹&k øÿÿÿíÕÎ&”&q ÿÿÿëÑÎ&”&q "ÿÿÿíÑÎ&”&q 7ÿÿÿíÑÎ&”&q LÿÿÿìéÎ&”&q aÿÿÿíÔÎ&”&q vÿÿÿíÕÎ&”&q ‹ÿÿÿçÛÎ&”&q  ÿÿÿïÐÎ&‹&k µÿÿÿîÐÎ&‹&k ÊÿÿÿèìÎ&”&q ßÿÿÿèàÍ&‹&n ôÿÿÿééÎ&‹&k ÿÿÿçÔÍ&‹&n ÿÿÿçÞÎ&‹&k 3ÿÿÿçÞÎ&”&k HÿÿÿëÐÎ&‹&k ]ÿÿÿìÕÎ&”&q rÿÿÿíØÎ&‹&k ‡ÿÿÿçÜÎ&”&k œÿÿ ÿëöÎ&wtÿÿÿëöÎ&‰&z xÿÿÿëöÎ&‰&z ÿÿÿèöÎ&‰&z ¢ÿÿ ÿðöÎ&€&w ·ÿÿÿêöÎ&‰&z ÌÿÿÿçöÎ&’&€ áÿÿÿïöÎ&‰&z öÿÿÿíöÎ&’&€ ÿÿÿëöÎ&’&€ ÿÿÿíöÎ&’&€ 5ÿÿÿíöÎ&’&€ JÿÿÿìöÎ&’&€ _ÿÿÿíöÎ&’&€ tÿÿÿíöÎ&’&€ ‰ÿÿÿçöÎ&’&€ žÿÿÿïöÎ&‰&z ³ÿÿÿîöÎ&‰&z ÈÿÿÿèöÎ&’&€ ÝÿÿÿèöÎ&‰&} òÿÿÿéöÎ&‰&z ÿÿÿçöÎ&‰&} ÿÿÿçöÎ&‰&z 1ÿÿÿçöÎ&’&z FÿÿÿëöÎ&‰&z [ÿÿÿìöÎ&’&€ pÿÿÿíöÎ&‰&z …ÿÿÿçöÎ&’&z šÿÿÿëÐÎ&yƒÿÿÿëÐÎ&‹&‰ zÿÿÿëÐÎ&‹&‰ ÿÿÿèìÎ&‹&‰ ¤ÿÿÿðÕÎ&‚&† ¹ÿÿÿéãÎ&‹&‰ ÎÿÿÿçßÎ&”& ãÿÿÿïÕÎ&‹&‰ øÿÿÿíÕÎ&”& ÿÿÿëÐÎ&”& "ÿÿÿíÐÎ&”& 7ÿÿÿíÐÎ&”& LÿÿÿìéÎ&”& aÿÿÿíÔÎ&”& vÿÿÿíÕÎ&”& ‹ÿÿÿçÛÎ&”&  ÿÿÿïÐÎ&‹&‰ µÿÿÿîÐÎ&‹&‰ ÊÿÿÿèìÎ&”& ßÿÿÿèàÎ&‹&Œ ôÿÿÿééÎ&‹&‰ ÿÿÿçÔÎ&‹&Œ ÿÿÿçÞÎ&‹&‰ 3ÿÿÿçÞÎ&”&‰ HÿÿÿëÐÎ&‹&‰ ]ÿÿÿìÕÎ&”& rÿÿÿíØÎ&‹&‰ ‡ÿÿÿçÜÎ&”&‰ œÿÿ ÿëÐÎ&x“ÿÿÿëÐÎ&Š&¦ yÿÿÿëÐÎ&Š&¦ ŽÿÿÿèìÎ&Š&¦ £ÿÿÿðÕÎ&& ¸ÿÿÿéãÎ&Š&¦ ÍÿÿÿçßÎ&“&° âÿÿÿïÕÎ&Š&¦ ÷ÿÿÿíÕÎ&Š&¦ ÿÿÿëÐÎ&“&° !ÿÿÿíÐÎ&“&° 6ÿÿÿíÐÎ&“&° KÿÿÿìéÎ&“&° `ÿÿÿíÔÎ&“&° uÿÿÿíÕÎ&“&° ŠÿÿÿçÛÎ&“&° ŸÿÿÿïÐÎ&Š&¦ ´ÿÿÿîÐÎ&Š&¦ ÉÿÿÿèìÎ&“&° ÞÿÿÿèãÎ&Š&º óÿÿÿééÎ&Š&¦ ÿÿÿçÔÎ&Š&º ÿÿÿçÞÎ&Š&¦ 2ÿÿÿçÞÎ&“&° GÿÿÿëÐÎ&Š&¦ \ÿÿÿìÕÎ&“&° qÿÿÿíØÎ&Š&¦ †ÿÿÿçÖÎ&“&° ›ÿÿÿëÐÎ&yÃÿÿÿëÐÎ&‹&Ö zÿÿÿëÐÎ&‹&Ö ÿÿÿèìÎ&‹&Ö ¤ÿÿÿðÕÎ&‚&Ì ¹ÿÿÿéãÎ&‹&Ö ÎÿÿÿçßÎ&”&à ãÿÿÿïÕÎ&‹&Ö øÿÿÿíÕÎ&”&à ÿÿÿëÐÎ&”&à "ÿÿÿíÐÎ&”&à 7ÿÿÿíÐÎ&”&à LÿÿÿìéÎ&”&à aÿÿÿíÔÎ&”&à vÿÿÿíÕÎ&”&à ‹ÿÿÿçÛÎ&”&à  ÿÿÿïÐÎ&‹&Ö µÿÿÿîÐÎ&‹&Ö ÊÿÿÿèìÎ&”&à ßÿÿÿèàÎ&‹&ê ôÿÿÿééÎ&‹&Ö ÿÿÿçÔÎ&‹&ê ÿÿÿçÞÎ&‹&Ö 3ÿÿÿçÞÎ&”&à HÿÿÿëÐÎ&‹&Ö ]ÿÿÿìÕÎ&”&à rÿÿÿíØÎ&‹&Ö ‡ÿÿÿçÜÎ&”&à œÿÿ ÿëÐÎ&xôÿÿÿëÐÎ&Š&  yÿÿÿëÐÎ&Š&  ŽÿÿÿèìÎ&Š&  £ÿÿÿðÕÎ&&þ ¸ÿÿÿéãÎ&Š&  ÍÿÿÿçßÎ&“&  âÿÿÿïÕÎ&Š&  ÷ÿÿÿíÕÎ&“&  ÿÿÿëÐÎ&“&  !ÿÿÿíÐÎ&“&  6ÿÿÿíÐÎ&“&  KÿÿÿìéÎ&“&  `ÿÿÿíÔÎ&“&  uÿÿÿíÕÎ&“&  ŠÿÿÿçÛÎ&“&  ŸÿÿÿïÐÎ&Š&  ´ÿÿÿîÐÎ&Š&  ÉÿÿÿèìÎ&“&  ÞÿÿÿèãÎ&Š&  óÿÿÿééÎ&Š&  ÿÿÿçÔÎ&Š&  ÿÿÿçÞÎ&Š&  2ÿÿÿçÞÎ&“&  GÿÿÿëÐÎ&Š&  \ÿÿÿìÕÎ&“&  qÿÿÿíØÎ&Š&  †ÿÿÿçÖÎ&“&  ›ÿÿÿëÐÎ&y #ÿÿÿëÐÎ&‹& 7 zÿÿÿëÐÎ&‹& 7 ÿÿÿèìÎ&‹& 7 ¤ÿÿÿðÕÎ&‚& - ¹ÿÿÿéãÎ&‹& 7 ÎÿÿÿçßÎ&”& A ãÿÿÿïÕÎ&‹& 7 øÿÿÿíÕÎ&”& A ÿÿÿëÐÎ&”& A "ÿÿÿíÐÎ&”& A 7ÿÿÿíÐÎ&”& A LÿÿÿìéÎ&”& A aÿÿÿíÔÎ&”& A vÿÿÿíÕÎ&”& A ‹ÿÿÿçÛÎ&”& A  ÿÿÿïÐÎ&‹& 7 µÿÿÿîÐÎ&‹& 7 ÊÿÿÿèìÎ&”& A ßÿÿÿèàÎ&‹& K ôÿÿÿééÎ&‹& 7 ÿÿÿçÔÎ&‹& K ÿÿÿçÞÎ&‹& 7 3ÿÿÿçÞÎ&”& A HÿÿÿëÐÎ&‹& 7 ]ÿÿÿìÕÎ&”& A rÿÿÿíØÎ&‹& 7 ‡ÿÿÿçÜÎ&”& A œÿÿ ê½&z UÿÿÿëêÉ&Œ& f {ÿÿÿëêÉ&Œ& f ÿÿÿêêÉ&Œ& f ¥ÿÿÿðêÉ&ƒ& ] ºÿÿÿéêÉ&Œ& f ÏÿÿÿçêÉ&•& p äÿÿÿïêÉ&Œ& f ùÿÿÿíêÉ&•& p ÿÿÿëêÉ&•& p #ÿÿÿíêÉ&•& p 8ÿÿÿðêÉ&•& p MÿÿÿìêÉ&•& p bÿÿÿíêÉ&•& p wÿÿÿíêÉ&•& p ŒÿÿÿçêÉ&•& p ¡ÿÿÿïêÉ&Œ& f ¶ÿÿÿîêÉ&Œ& f ËÿÿÿèìÉ&•& p àÿÿÿèêÉ&Œ& y õÿÿÿéêÉ&Œ& f ÿÿÿçêÉ&Œ& y ÿÿÿçêÉ&Œ& f 4ÿÿÿçêÉ&•& p IÿÿÿëêÉ&Œ& f ^ÿÿÿìêÉ&•& p sÿÿÿíêÉ&Œ& f ˆÿÿÿçêÉ&•& p ÿÿ ÿëöÎ&| ƒÿÿ ÿëöÎ&Ž& — |ÿÿ ÿëöÎ&Ž& — ‘ÿÿ ÿêöÎ&Ž& — ¦ÿÿ ÿðöÎ&…&  »ÿÿ ÿêöÎ&Ž& — Ðÿÿ ÿçöÎ&—& ¡ åÿÿ ÿïöÎ&Ž& — úÿÿ ÿíöÎ&—& ¡ ÿÿ ÿëöÎ&—& ¡ $ÿÿ ÿíöÎ&—& ¡ 9ÿÿ ÿíöÎ&—& ¡ Nÿÿ ÿìöÎ&—& ¡ cÿÿ ÿîöÎ&—& ¡ xÿÿ ÿíöÎ&—& ¡ ÿÿ ÿçöÎ&—& ¡ ¢ÿÿ ÿïöÎ&Ž& — ·ÿÿ ÿîöÎ&Ž& — Ìÿÿ ÿèöÎ&—& ¡ áÿÿ ÿèöÎ&Ž& « öÿÿ ÿéöÎ&Ž& — ÿÿ ÿçöÎ&Ž& « ÿÿ ÿçöÎ&Ž& — 5ÿÿ ÿçöÎ&—& ¡ Jÿÿ ÿëöÎ&Ž& — _ÿÿ ÿìöÎ&—& ¡ tÿÿ ÿíöÎ&Ž& — ‰ÿÿ ÿçöÎ&—& ¡ žÿÿ ÿëÐÎ&~ µÿÿ ÿëÐÎ&& É |ÿÿ ÿëÐÎ&& É ‘ÿÿ ÿêëÎ&& É ¦ÿÿ ÿðÕÎ&‡& ¿ »ÿÿ ÿêãÎ&& É Ðÿÿ ÿçßÎ&™& Ó åÿÿ ÿïÕÎ&& É úÿÿ ÿíÕÎ&™& Ó ÿÿ ÿëÐÎ&™& Ó $ÿÿ ÿíÐÎ&™& Ó 9ÿÿ ÿíÐÎ&™& Ó Nÿÿ ÿìéÎ&™& Ó cÿÿ ÿîÔÎ&™& Ó xÿÿ ÿíÕÎ&™& Ó ÿÿ ÿçÛÎ&™& Ó ¢ÿÿ ÿïÐÎ&& É ·ÿÿ ÿîÐÎ&& É Ìÿÿ ÿèéÎ&™& Ó áÿÿ ÿèãÎ&& Ý öÿÿ ÿééÎ&& É ÿÿ ÿçÔÎ&& Ý ÿÿ ÿçâÎ&& É 5ÿÿ ÿçâÎ&™& Ó Jÿÿ ÿëÐÎ&& É _ÿÿ ÿìÕÎ&™& Ó tÿÿ ÿíØÎ&& É ‰ÿÿ ÿçÜÎ&™& Ó žÿÿ ÿëÐÎ&| çÿÿ ÿëÐÎ&Ž& ú |ÿÿ ÿëÐÎ&Ž& ú ‘ÿÿ ÿêëÎ&Ž& ú ¦ÿÿ ÿðÕÎ&…& ð »ÿÿ ÿêãÎ&Ž& ú Ðÿÿ ÿçßÎ&—&  åÿÿ ÿïÕÎ&Ž& ú úÿÿ ÿíÕÎ&—&  ÿÿ ÿëÐÎ&—&  $ÿÿ ÿíÐÎ&—&  9ÿÿ ÿíÐÎ&—&  Nÿÿ ÿìéÎ&—&  cÿÿ ÿîÔÎ&—&  xÿÿ ÿíÕÎ&—&  ÿÿ ÿçÛÎ&—&  ¢ÿÿ ÿïÐÎ&Ž& ú ·ÿÿ ÿîÐÎ&Ž& ú Ìÿÿ ÿèéÎ&—&  áÿÿ ÿèãÎ&Ž&  öÿÿ ÿééÎ&Ž& ú ÿÿ ÿçÔÎ&Ž&  ÿÿ ÿçâÎ&Ž& ú 5ÿÿ ÿçâÎ&—&  Jÿÿ ÿëÐÎ&Ž& ú _ÿÿ ÿìÕÎ&—&  tÿÿ ÿíØÎ&Ž& ú ‰ÿÿ ÿçÜÎ&—&  žÿÿ ë½&z ÿÿÿëëÉ&Œ& , {ÿÿÿëëÉ&Œ& , ÿÿÿêëÉ&Œ& , ¥ÿÿÿðëÉ&ƒ& " ºÿÿÿéëÉ&Œ& , ÏÿÿÿçëÉ&•& 6 äÿÿÿïëÉ&Œ& , ùÿÿÿíëÉ&•& 6 ÿÿÿëëÉ&•& 6 #ÿÿÿíëÉ&•& 6 8ÿÿÿðëÉ&•& 6 MÿÿÿìëÉ&•& 6 bÿÿÿíëÉ&•& 6 wÿÿÿíëÉ&•& 6 ŒÿÿÿçëÉ&•& 6 ¡ÿÿÿïëÉ&Œ& , ¶ÿÿÿîëÉ&Œ& , ËÿÿÿèìÉ&•& 6 àÿÿÿèëÉ&Œ& @ õÿÿÿéëÉ&Œ& , ÿÿÿçëÉ&Œ& @ ÿÿÿçëÉ&Œ& , 4ÿÿÿçëÉ&•& 6 IÿÿÿëëÉ&Œ& , ^ÿÿÿìëÉ&•& 6 sÿÿÿíëÉ&Œ& , ˆÿÿÿçëÉ&•& 6 ÿÿÿëëÉ&{ IÿÿÿëëÉ&& T {ÿÿÿëëÉ&& T ÿÿÿêëÉ&& T ¥ÿÿÿðëÉ&„& O ºÿÿÿéëÉ&& T ÏÿÿÿçëÉ&–& i äÿÿÿïëÉ&& T ùÿÿÿíëÉ&–& i ÿÿÿëëÉ&–& i #ÿÿÿíëÉ&–& i 8ÿÿÿðëÉ&–& i MÿÿÿìëÉ&–& i bÿÿÿíëÉ&–& i wÿÿÿíëÉ&–& i ŒÿÿÿçëÉ&–& i ¡ÿÿÿïëÉ&& T ¶ÿÿÿîëÉ&& T ËÿÿÿèìÉ&–& i àÿÿÿèëÉ&& b õÿÿÿéëÉ&& T ÿÿÿçëÉ&& T ÿÿÿçëÉ&& T 4ÿÿÿçëÉ&–& [ IÿÿÿëëÉ&& T ^ÿÿÿìëÉ&–& i sÿÿÿíëÉ&& T ˆÿÿÿçëÉ&–& [ ÿÿ ÿëÐÎ&} pÿÿ ÿëÐÎ&& ~ ~ÿÿ ÿëÐÎ&& ~ “ÿÿ ÿêëÎ&& ~ ¨ÿÿÿðÕÎ&†& w ½ÿÿ ÿéãÎ&& ~ Òÿÿ ÿçßÎ&˜& “ çÿÿ ÿïÕÎ&& ~ üÿÿ ÿíÕÎ&˜& “ ÿÿ ÿëÐÎ&˜& “ &ÿÿ ÿíÐÎ&˜& “ ;ÿÿ ÿíÐÎ&˜& “ Pÿÿ ÿëéÎ&˜& “ eÿÿ ÿîÔÎ&˜& “ zÿÿ ÿíÖÎ&˜& “ ÿÿ ÿçÛÎ&˜& “ ¤ÿÿ ÿïÐÎ&& ~ ¹ÿÿ ÿîÐÎ&& ~ Îÿÿ ÿêéÎ&˜& “ ãÿÿ ÿèãÎ&& Œ øÿÿ ÿçéÎ&& ~ ÿÿ ÿçÔÎ&& Œ "ÿÿ ÿçÞÎ&& ~ 7ÿÿ ÿíÞÎ&˜& … Lÿÿ ÿëÐÎ&& ~ aÿÿ ÿìÕÎ&˜& “ vÿÿ ÿíØÎ&& ~ ‹ÿÿ ÿçÜÎ&˜& …  ÿÿ ÿëÐÎ& šÿÿ ÿëÐÎ&‘& ¨ ~ÿÿ ÿëÐÎ&‘& ¨ “ÿÿ ÿêëÎ&‘& ¨ ¨ÿÿ ÿðÕÎ&ˆ& ¡ ½ÿÿ ÿéãÎ&‘& ¨ Òÿÿ ÿçßÎ&š& ½ çÿÿ ÿïÕÎ&‘& ¨ üÿÿ ÿíÕÎ&š& ½ ÿÿ ÿëÐÎ&š& ½ &ÿÿ ÿíÐÎ&š& ½ ;ÿÿ ÿíÐÎ&š& ½ Pÿÿ ÿëéÎ&š& ½ eÿÿ ÿîÔÎ&š& ½ zÿÿ ÿíÖÎ&š& ½ ÿÿ ÿçÛÎ&š& ½ ¤ÿÿ ÿïÐÎ&‘& ¨ ¹ÿÿ ÿîÐÎ&‘& ¨ Îÿÿ ÿêéÎ&š& ½ ãÿÿ ÿèãÎ&‘& ¶ øÿÿ ÿçéÎ&‘& ¨ ÿÿ ÿçÔÎ&‘& ¶ "ÿÿ ÿçÞÎ&‘& ¨ 7ÿÿ ÿíÞÎ&š& ¯ Lÿÿ ÿëÐÎ&‘& ¨ aÿÿ ÿìÕÎ&š& ½ vÿÿ ÿíØÎ&‘& ¨ ‹ÿÿ ÿçÜÎ&š& ¯  ÿÿ ÿëÐÎ&} Äÿÿ ÿëÐÎ&& Ò }ÿÿ ÿëÐÎ&& Ò ’ÿÿ ÿêëÎ&& Ò §ÿÿÿðÕÎ&†& Ë ¼ÿÿ ÿéãÎ&& Ò Ñÿÿ ÿçßÎ&˜& ç æÿÿ ÿïÕÎ&& Ò ûÿÿ ÿíÕÎ&˜& ç ÿÿ ÿëÐÎ&˜& ç %ÿÿ ÿíÐÎ&˜& ç :ÿÿ ÿíÐÎ&˜& ç Oÿÿ ÿëèÎ&˜& ç dÿÿ ÿîÔÎ&˜& ç yÿÿ ÿíÖÎ&˜& ç Žÿÿ ÿçÛÎ&˜& ç £ÿÿ ÿïÐÎ&& Ò ¸ÿÿ ÿîÐÎ&& Ò Íÿÿ ÿêéÎ&˜& ç âÿÿ ÿèãÎ&& à ÷ÿÿ ÿçéÎ&& Ò ÿÿ ÿçÔÎ&& à !ÿÿ ÿçÞÎ&& Ò 6ÿÿ ÿçÞÎ&˜& Ù Kÿÿ ÿëÐÎ&& Ò `ÿÿ ÿìÕÎ&˜& ç uÿÿ ÿíØÎ&& Ò Šÿÿ ÿçÖÎ&˜& Ù ŸÿÿÿíëÉ&{ îÿÿÿëëÉ&& ü {ÿÿÿëëÉ&& ü ÿÿÿêëÉ&& ü ¥ÿÿÿðëÉ&„& õ ºÿÿÿéëÉ&& ü ÏÿÿÿçëÉ&–&  äÿÿÿïëÉ&& ü ùÿÿÿíëÉ&–&  ÿÿÿëëÉ&–&  #ÿÿÿíëÉ&–&  8ÿÿÿðëÉ&–&  MÿÿÿìëÉ&–&  bÿÿÿíëÉ&–&  wÿÿÿíëÉ&–&  ŒÿÿÿçëÉ&–&  ¡ÿÿÿïëÉ&& ü ¶ÿÿÿîëÉ&& ü ËÿÿÿèìÉ&–&  àÿÿÿèëÉ&&  õÿÿÿéëÉ&& ü ÿÿÿçëÉ&&  ÿÿÿçëÉ&& ü 4ÿÿÿçëÉ&–&  IÿÿÿëëÉ&& ü ^ÿÿÿìëÉ&–&  sÿÿÿíëÉ&& ü ˆÿÿÿçëÉ&–&  ÿÿ ë½&z ÿÿÿëëÉ&Œ& % {ÿÿÿëëÉ&Œ& % ÿÿÿêëÉ&Œ& % ¥ÿÿÿðëÉ&ƒ&  ºÿÿÿéëÉ&Œ& % ÏÿÿÿçëÉ&•& : äÿÿÿïëÉ&Œ& % ùÿÿÿíëÉ&•& : ÿÿÿëëÉ&•& : #ÿÿÿíëÉ&•& : 8ÿÿÿðëÉ&•& : MÿÿÿìëÉ&•& : bÿÿÿíëÉ&•& : wÿÿÿíëÉ&•& : ŒÿÿÿçëÉ&•& : ¡ÿÿÿïëÉ&Œ& % ¶ÿÿÿîëÉ&Œ& % ËÿÿÿèìÉ&•& : àÿÿÿèëÉ&Œ& 3 õÿÿÿéëÉ&Œ& % ÿÿÿçëÉ&Œ& 3 ÿÿÿçëÉ&Œ& % 4ÿÿÿçëÉ&•& , IÿÿÿëëÉ&Œ& % ^ÿÿÿìëÉ&•& : sÿÿÿíëÉ&Œ& % ˆÿÿÿçëÉ&•& , ÿÿ ÿëÐÎ&| Aÿÿ ÿëÐÎ&Ž& N |ÿÿ ÿëÐÎ&Ž& N ‘ÿÿ ÿêëÎ&Ž& N ¦ÿÿ ÿðÕÎ&…& G »ÿÿ ÿêãÎ&Ž& N Ðÿÿ ÿçßÎ&—& c åÿÿ ÿïÕÎ&Ž& N úÿÿ ÿíÕÎ&—& c ÿÿ ÿëÐÎ&—& c $ÿÿ ÿíÐÎ&—& c 9ÿÿ ÿíÐÎ&—& c Nÿÿ ÿìéÎ&—& c cÿÿ ÿîÔÎ&—& c xÿÿ ÿíÕÎ&—& c ÿÿ ÿçÛÎ&—& c ¢ÿÿ ÿïÐÎ&Ž& N ·ÿÿ ÿîÐÎ&Ž& N Ìÿÿ ÿèéÎ&—& c áÿÿ ÿèãÎ&Ž& \ öÿÿ ÿééÎ&Ž& N ÿÿ ÿçÔÎ&Ž& \ ÿÿ ÿçâÎ&Ž& N 5ÿÿ ÿçâÎ&—& U Jÿÿ ÿëÐÎ&Ž& N _ÿÿ ÿìÕÎ&—& c tÿÿ ÿíØÎ&Ž& N ‰ÿÿ ÿçÜÎ&—& U žÿÿ ÿëÐÎ&w jÿÿÿëÐÎ&‰& p yÿÿÿëÐÎ&‰& p ŽÿÿÿèìÎ&‰& p £ÿÿ ÿðÕÎ&€& m ¸ÿÿÿéãÎ&‰& p ÍÿÿÿçßÎ&’& v âÿÿÿïÕÎ&‰& p ÷ÿÿÿíÕÎ&’& v ÿÿÿëÐÎ&’& v !ÿÿÿíÐÎ&’& v 6ÿÿÿíÐÎ&’& v KÿÿÿìéÎ&’& v `ÿÿÿíÔÎ&’& v uÿÿÿíÕÎ&’& v ŠÿÿÿçÛÎ&’& v ŸÿÿÿïÐÎ&‰& p ´ÿÿÿîÐÎ&‰& p ÉÿÿÿèìÎ&’& v ÞÿÿÿèãÎ&‰& s óÿÿÿééÎ&‰& p ÿÿÿçÔÎ&‰& s ÿÿÿçÞÎ&‰& p 2ÿÿÿçÞÎ&’& p GÿÿÿëÐÎ&‰& p \ÿÿÿìÕÎ&’& v qÿÿÿíØÎ&‰& p †ÿÿÿçÖÎ&’& p ›ÿÿÿëöÎ&›UÿÿÿëõÎ&­&[ xÿÿÿëõÎ&­&[ ÿÿÿèõÎ&­&[ ¢ÿÿÿðöÎ&¤& ·XÿÿÿêõÎ&­&[ ÌÿÿÿçõÎ&¶&a áÿÿÿïõÎ&­&[ öÿÿÿíõÎ&¶&a ÿÿÿëõÎ&¶&a ÿÿÿíõÎ&¶&a 5ÿÿÿíõÎ&¶&a JÿÿÿìõÎ&¶&a _ÿÿÿíõÎ&¶&a tÿÿÿíõÎ&¶&a ‰ÿÿÿçõÎ&¶&a žÿÿÿïõÎ&[& ³­ÿÿÿîõÎ&­&[ ÈÿÿÿèõÎ&¶&a ÝÿÿÿèöÎ&­&^ òÿÿÿéõÎ&­&[ ÿÿÿçöÎ&­&^ ÿÿÿçõÎ&­&[ 1ÿÿÿçõÎ&¶&[ FÿÿÿëõÎ&­&[ [ÿÿÿìõÎ&¶&a pÿÿÿíõÎ&­&[ …ÿÿÿçõÎ&¶&[ šÿÿ ÿëÐÎ&dÿÿ ÿëÐÎ&¯&j zÿÿ ÿëÐÎ&¯&j ÿÿ ÿèìÎ&¯&j ¤ÿÿ ÿðÕÎ&¦&g ¹ÿÿ ÿéãÎ&¯&j Îÿÿ ÿçßÎ&¸&p ãÿÿ ÿïÕÎ&¯&j øÿÿ ÿíÕÎ&¸&p ÿÿ ÿëÐÎ&¸&p "ÿÿ ÿíÐÎ&¸&p 7ÿÿ ÿíÐÎ&¸&p Lÿÿ ÿìéÎ&¸&p aÿÿ ÿíÔÎ&¸&p vÿÿ ÿíÕÎ&¸&p ‹ÿÿ ÿçÛÎ&¸&p  ÿÿ ÿïÐÎ&¯&j µÿÿ ÿîÐÎ&¯&j Êÿÿ ÿèìÎ&¸&p ßÿÿ ÿèàÎ&¯&m ôÿÿ ÿééÎ&¯&j ÿÿ ÿçÔÎ&¯&m ÿÿ ÿçÞÎ&¯&j 3ÿÿ ÿçÞÎ&¸&j Hÿÿ ÿëÐÎ&¯&j ]ÿÿ ÿìÕÎ&¸&p rÿÿ ÿíØÎ&¯&j ‡ÿÿ ÿçÜÎ&¸&j œÿÿÿëöÎ&›sÿÿÿëöÎ&­&y xÿÿÿëöÎ&­&y ÿÿÿèöÎ&­&y ¢ÿÿÿðöÎ&¤&v ·ÿÿÿêöÎ&­&y ÌÿÿÿçöÎ&¶& áÿÿÿïöÎ&­&y öÿÿÿíöÎ&¶& ÿÿÿëöÎ&¶& ÿÿÿíöÎ&¶& 5ÿÿÿíöÎ&¶& JÿÿÿìöÎ&¶& _ÿÿÿíöÎ&¶& tÿÿÿíöÎ&¶& ‰ÿÿÿçöÎ&¶& žÿÿÿïöÎ&­&y ³ÿÿÿîöÎ&­&y ÈÿÿÿèöÎ&¶& ÝÿÿÿèöÎ&­&| òÿÿÿéöÎ&­&y ÿÿÿçöÎ&­&| ÿÿÿçöÎ&­&y 1ÿÿÿçöÎ&¶&y FÿÿÿëöÎ&­&y [ÿÿÿìöÎ&¶& pÿÿÿíöÎ&­&y …ÿÿÿçöÎ&¶&y šÿÿ ÿëÐÎ&‚ÿÿ ÿëÐÎ&¯&ˆ zÿÿ ÿëÐÎ&¯&ˆ ÿÿ ÿèìÎ&¯&ˆ ¤ÿÿ ÿðÕÎ&¦&… ¹ÿÿ ÿéãÎ&¯&ˆ Îÿÿ ÿçßÎ&¸&Ž ãÿÿ ÿïÕÎ&¯&ˆ øÿÿ ÿíÕÎ&¸&Ž ÿÿ ÿëÐÎ&¸&Ž "ÿÿ ÿíÐÎ&¸&Ž 7ÿÿ ÿíÐÎ&¸&Ž Lÿÿ ÿìéÎ&¸&Ž aÿÿ ÿíÔÎ&¸&Ž vÿÿ ÿíÕÎ&¸&Ž ‹ÿÿ ÿçÛÎ&¸&Ž  ÿÿ ÿïÐÎ&¯&ˆ µÿÿ ÿîÐÎ&¯&ˆ Êÿÿ ÿèìÎ&¸&Ž ßÿÿ ÿèàÎ&¯&‹ ôÿÿ ÿééÎ&¯&ˆ ÿÿ ÿçÔÎ&¯&‹ ÿÿ ÿçÞÎ&¯&ˆ 3ÿÿ ÿçÞÎ&¸&ˆ Hÿÿ ÿëÐÎ&¯&ˆ ]ÿÿ ÿìÕÎ&¸&Ž rÿÿ ÿíØÎ&¯&ˆ ‡ÿÿ ÿçÜÎ&¸&ˆ œÿÿ ÿëÐÎ&‘œÿÿ ÿëÐÎ&®&­ yÿÿ ÿëÐÎ&®&­ Žÿÿ ÿèìÎ&®&­ £ÿÿ ÿðÕÎ&¥& ¸£ÿÿ ÿéãÎ&®&­ Íÿÿ ÿçßÎ&·&· âÿÿ ÿïÕÎ&®&­ ÷ÿÿ ÿíÕÎ&·&· ÿÿ ÿëÐÎ&·&· !ÿÿ ÿíÐÎ&·&· 6ÿÿ ÿíÐÎ&·&· Kÿÿ ÿìéÎ&·&· `ÿÿ ÿíÔÎ&·&· uÿÿ ÿíÕÎ&·&· Šÿÿ ÿçÛÎ&·&· Ÿÿÿ ÿïÐÎ&®&­ ´ÿÿ ÿîÐÎ&®&­ Éÿÿ ÿèìÎ&·&· ÞÿÿÿèãÎ& ó&­Áÿÿ ÿééÎ&®&­ ÿÿÿçÔÎ&Á&­ ÿÿ ÿçÞÎ&®&­ 2ÿÿ ÿçÞÎ&·&· Gÿÿ ÿëÐÎ&®&­ \ÿÿ ÿìÕÎ&·&· qÿÿ ÿíØÎ&®&­ †ÿÿ ÿçÖÎ&·&· ›ÿÿ ÿëÐÎ&Éÿÿ ÿëÐÎ&¯&Ý zÿÿ ÿëÐÎ&¯&Ý ÿÿ ÿèìÎ&¯&Ý ¤ÿÿ ÿðÕÎ&¦&Ó ¹ÿÿ ÿéãÎ&¯&Ý Îÿÿ ÿçßÎ&¸&ç ãÿÿ ÿïÕÎ&¯&Ý øÿÿ ÿíÕÎ&¸&ç ÿÿ ÿëÐÎ&¸&ç "ÿÿ ÿíÐÎ&¸&ç 7ÿÿ ÿíÐÎ&¸&ç Lÿÿ ÿìéÎ&¸&ç aÿÿ ÿíÔÎ&¸&ç vÿÿ ÿíÕÎ&¸&ç ‹ÿÿ ÿçÛÎ&¸&ç  ÿÿ ÿïÐÎ&¯&Ý µÿÿ ÿîÐÎ&¯&Ý Êÿÿ ÿèìÎ&¸&ç ßÿÿ ÿèàÎ&¯&ñ ôÿÿ ÿééÎ&¯&Ý ÿÿ ÿçÔÎ&¯&ñ ÿÿ ÿçÞÎ&¯&Ý 3ÿÿ ÿçÞÎ&¸&ç Hÿÿ ÿëÐÎ&¯&Ý ]ÿÿ ÿìÕÎ&¸&ç rÿÿ ÿíØÎ&¯&Ý ‡ÿÿ ÿçÜÎ&¸&ç œÿÿ ÿëÐÎ&œûÿÿ ÿëÐÎ&®&  yÿÿ ÿëÐÎ&®&  Žÿÿ ÿèìÎ&®&  £ÿÿ ÿðÕÎ&¥&  ¸ÿÿ ÿéãÎ&®&  Íÿÿ ÿçßÎ&·&  âÿÿ ÿïÕÎ&®&  ÷ÿÿ ÿíÕÎ&·&  ÿÿ ÿëÐÎ&·&  !ÿÿ ÿíÐÎ&·&  6ÿÿ ÿíÐÎ&·&  Kÿÿ ÿìéÎ&·&  `ÿÿ ÿíÔÎ&·&  uÿÿ ÿíÕÎ&·&  Šÿÿ ÿçÛÎ&·&  Ÿÿÿ ÿïÐÎ&®&  ´ÿÿ ÿîÐÎ&®&  Éÿÿ ÿèìÎ&·&  Þÿÿ ÿèãÎ&®&  óÿÿ ÿééÎ& &® ÿÿ ÿçÔÎ&®&  ÿÿ ÿçÞÎ&®&  2ÿÿ ÿçÞÎ&·&  Gÿÿ ÿëÐÎ&®&  \ÿÿ ÿìÕÎ&·&  qÿÿ ÿíØÎ&®&  †ÿÿ ÿçÖÎ&·&  ›ÿÿ ÿëÐÎ& *ÿÿ ÿëÐÎ&¯& > zÿÿ ÿëÐÎ&¯& > ÿÿ ÿèìÎ&¯& > ¤ÿÿ ÿðÕÎ&¦& 4 ¹ÿÿ ÿéãÎ&¯& > Îÿÿ ÿçßÎ&¸& H ãÿÿ ÿïÕÎ&¯& > øÿÿ ÿíÕÎ&¸& H ÿÿ ÿëÐÎ&¸& H "ÿÿ ÿíÐÎ&¸& H 7ÿÿ ÿíÐÎ&¸& H Lÿÿ ÿìéÎ&¸& H aÿÿ ÿíÔÎ&¸& H vÿÿ ÿíÕÎ&¸& H ‹ÿÿ ÿçÛÎ&¸& H  ÿÿ ÿïÐÎ&¯& > µÿÿ ÿîÐÎ&¯& > Êÿÿ ÿèìÎ&¸& H ßÿÿ ÿèàÎ&¯& R ôÿÿ ÿééÎ&¯& > ÿÿ ÿçÔÎ&¯& R ÿÿ ÿçÞÎ&¯& > 3ÿÿ ÿçÞÎ&¸& H Hÿÿ ÿëÐÎ&¯& > ]ÿÿ ÿìÕÎ&¸& H rÿÿ ÿíØÎ&¯& > ‡ÿÿ ÿçÜÎ&¸& H œÿÿ êÉ&ž ZÿÿÿëêÏ&°& m {ÿÿÿëêÏ&°& m ÿÿÿêêÏ&°& m ¥ÿÿÿðêÏ&§& c ºÿÿÿéêÏ&°& m ÏÿÿÿçêÏ&¹& w äÿÿÿïêÏ&°& m ùÿÿÿíêÏ&¹& w ÿÿÿëêÏ&¹& w #ÿÿÿíêÏ&¹& w 8ÿÿÿðêÏ&¹& w MÿÿÿìêÏ&¹& w bÿÿÿíêÏ&¹& w wÿÿÿíêÏ&¹& w ŒÿÿÿçêÏ&¹& w ¡ÿÿÿïêÏ&°& m ¶ÿÿÿîêÏ&°& m ËÿÿÿèìÏ&¹& w àÿÿÿèêÏ&°& € õÿÿÿéêÏ&°& m ÿÿÿçêÏ&°& € ÿÿÿçêÏ&°& m 4ÿÿÿçêÏ&¹& w IÿÿÿëêÏ&°& m ^ÿÿÿìêÏ&¹& w sÿÿÿíêÏ&°& m ˆÿÿÿçêÏ&¹& w ÿÿÿëöÎ&  ŠÿÿÿëöÎ&²& ž ƒÿÿÿëöÎ&²& ž ˜ÿÿÿêöÎ&²& ž ­ÿÿÿíöÎ&©& ” ÂÿÿÿéöÎ&²& ž ×ÿÿÿçöÎ&»& ¨ ìÿÿÿíöÎ&²& ž ÿÿÿíöÎ&»& ¨ ÿÿÿëöÎ&»& ¨ +ÿÿÿíöÎ&»& ¨ @ÿÿÿìöÎ&»& ¨ UÿÿÿìöÎ&»& ¨ jÿÿÿîöÎ&»& ¨ ÿÿÿíöÎ&»& ¨ ”ÿÿÿèöÎ&»& ¨ ©ÿÿÿïöÎ&²& ž ¾ÿÿÿîöÎ&²& ž ÓÿÿÿêöÎ&»& ¨ èÿÿÿêöÎ&²& ² ýÿÿÿìöÎ&²& ž ÿÿÿêöÎ&²& ² 'ÿÿÿëöÎ&²& ž <ÿÿÿëöÎ&»& ¨ QÿÿÿëöÎ&²& ž fÿÿÿíöÎ&»& ¨ {ÿÿÿðöÎ&²& ž ÿÿÿçöÎ&»& ¨ ¥ÿÿÿëÐÎ&¢ ¼ÿÿÿëÐÎ&´& Ð ƒÿÿÿëÐÎ&´& Ð ˜ÿÿÿêëÎ&´& Ð ­ÿÿ ÿíÕÎ&«& Æ ÂÿÿÿéãÎ&´& Ð ×ÿÿ ÿçßÎ&½& Ú ìÿÿÿíÕÎ&´& Ð ÿÿ ÿíÕÎ&½& Ú ÿÿ ÿëÐÎ&½& Ú +ÿÿ ÿíÐÎ&½& Ú @ÿÿ ÿìÐÎ&½& Ú Uÿÿ ÿìéÎ&½& Ú jÿÿ ÿîÔÎ&½& Ú ÿÿ ÿíÕÎ&½& Ú ”ÿÿ ÿèÛÎ&½& Ú ©ÿÿÿïÐÎ&´& Ð ¾ÿÿÿîÐÎ&´& Ð Óÿÿ ÿêéÎ&½& Ú èÿÿÿêãÎ&´& ä ýÿÿÿìéÎ&´& Ð ÿÿÿêÔÎ&´& ä 'ÿÿÿëâÎ&´& Ð <ÿÿ ÿëáÎ&½& Ú QÿÿÿëÐÎ&´& Ð fÿÿ ÿíÕÎ&½& Ú {ÿÿÿðØÎ&´& Ð ÿÿ ÿçÜÎ&½& Ú ¥ÿÿÿëÐÎ&  íÿÿÿëÐÎ&²&  ƒÿÿÿëÐÎ&²&  ˜ÿÿÿêëÎ&²&  ­ÿÿÿíÕÎ&©& ÷ ÂÿÿÿéãÎ&²&  ×ÿÿÿçßÎ&»&  ìÿÿÿíÕÎ&²&  ÿÿÿíÕÎ&»&  ÿÿÿëÐÎ&»&  +ÿÿÿíÐÎ&»&  @ÿÿÿìÐÎ&»&  UÿÿÿìéÎ&»&  jÿÿÿîÔÎ&»&  ÿÿÿíÕÎ&»&  ”ÿÿÿèÛÎ&»&  ©ÿÿÿïÐÎ&²&  ¾ÿÿÿîÐÎ&²&  ÓÿÿÿêéÎ&»&  èÿÿÿêãÎ&²&  ýÿÿÿìéÎ&²&  ÿÿÿêÔÎ&²&  'ÿÿÿëâÎ&²&  <ÿÿÿëáÎ&»&  QÿÿÿëÐÎ&²&  fÿÿÿíÕÎ&»&  {ÿÿÿðØÎ&²&  ÿÿÿçÜÎ&»&  ¥ÿÿ ëÉ&ž ÿÿÿëëÏ&°& 3 {ÿÿÿëëÏ&°& 3 ÿÿÿêëÏ&°& 3 ¥ÿÿÿðëÏ&§& ) ºÿÿÿéëÏ&°& 3 ÏÿÿÿçëÏ&¹& = äÿÿÿïëÏ&°& 3 ùÿÿÿíëÏ&¹& = ÿÿÿëëÏ&¹& = #ÿÿÿíëÏ&¹& = 8ÿÿÿðëÏ&¹& = MÿÿÿìëÏ&¹& = bÿÿÿíëÏ&¹& = wÿÿÿíëÏ&¹& = ŒÿÿÿçëÏ&¹& = ¡ÿÿÿïëÏ&°& 3 ¶ÿÿÿîëÏ&°& 3 ËÿÿÿèìÏ&¹& = àÿÿÿèëÏ&°& G õÿÿÿéëÏ&°& 3 ÿÿÿçëÏ&°& G ÿÿÿçëÏ&°& 3 4ÿÿÿçëÏ&¹& = IÿÿÿëëÏ&°& 3 ^ÿÿÿìëÏ&¹& = sÿÿÿíëÏ&°& 3 ˆÿÿÿçëÏ&¹& = ÿÿÿëëÏ&Ÿ MÿÿÿëëÏ&±& Y {ÿÿÿëëÏ&±& Y ÿÿÿêëÏ&±& Y ¥ÿÿÿðëÏ&¨& R ºÿÿÿéëÏ&±& Y ÏÿÿÿçëÏ&º& n äÿÿÿïëÏ&±& Y ùÿÿÿíëÏ&±& k ÿÿÿëëÏ&º& n #ÿÿÿíëÏ&º& n 8ÿÿÿðëÏ&º& n MÿÿÿìëÏ&º& n bÿÿÿíëÏ&º& n wÿÿÿíëÏ&º& n ŒÿÿÿçëÏ&º& n ¡ÿÿÿïëÏ&±& Y ¶ÿÿÿîëÏ&±& Y ËÿÿÿèìÏ&º& n àÿÿÿèëÏ&±& g õÿÿÿéëÏ&±& Y ÿÿÿçëÏ&±& Y ÿÿÿçëÏ&±& Y 4ÿÿÿçëÏ&º& ` IÿÿÿëëÏ&±& Y ^ÿÿÿìëÏ&º& n sÿÿÿíëÏ&±& Y ˆÿÿÿçëÏ&º& ` ÿÿÿëÐÎ&¡ uÿÿÿëÐÎ&³& ƒ …ÿÿÿëÐÎ&³& ƒ šÿÿÿêëÎ&³& ƒ ¯ÿÿÿíÕÎ&ª& | ÄÿÿÿéãÎ&³& ƒ ÙÿÿÿçßÎ&¼& ˜ îÿÿÿìÕÎ&³& ƒ ÿÿÿíÕÎ&¼& ˜ ÿÿÿëÐÎ&¼& ˜ -ÿÿÿíÐÎ&¼& ˜ BÿÿÿìÐÎ&¼& ˜ WÿÿÿìéÎ&¼& ˜ lÿÿÿîÔÎ&¼& ˜ ÿÿÿîÕÎ&¼& ˜ –ÿÿÿçÛÎ&¼& ˜ «ÿÿÿïÐÎ&³& ƒ ÀÿÿÿîÐÎ&³& ƒ ÕÿÿÿêéÎ&¼& ˜ êÿÿÿêäÎ&³& ‘ ÿÿÿÿìéÎ&³& ƒ ÿÿÿêÔÎ&³& ‘ )ÿÿÿêÞÎ&³& ƒ >ÿÿÿëÞÎ&¼& Š SÿÿÿëÐÎ&³& ƒ hÿÿÿíÕÎ&¼& ˜ }ÿÿÿðØÎ&³& ƒ ’ÿÿÿçÜÎ&¼& Š §ÿÿÿëÐÎ&£ Ÿÿÿ ÿëÐÎ&µ& ­ …ÿÿ ÿëÐÎ&µ& ­ šÿÿ ÿêëÎ&µ& ­ ¯ÿÿÿíÕÎ&¬& ¦ Äÿÿ ÿéãÎ&µ& ­ Ùÿÿ ÿçßÎ&¾&  îÿÿ ÿìÕÎ&µ& ­ ÿÿ ÿíÕÎ&¾&  ÿÿ ÿëÐÎ&¾&  -ÿÿ ÿíÐÎ&¾&  Bÿÿ ÿìÐÎ&¾&  Wÿÿ ÿìéÎ&¾&  lÿÿ ÿîÔÎ&¾&  ÿÿ ÿîÕÎ&¾&  –ÿÿ ÿçÛÎ&¾&  «ÿÿ ÿïÐÎ&µ& ­ Àÿÿ ÿîÐÎ&µ& ­ Õÿÿ ÿêéÎ&¾&  êÿÿ ÿêäÎ&µ& » ÿÿÿ ÿìéÎ&µ& ­ ÿÿ ÿêÔÎ&µ& » )ÿÿ ÿêÞÎ&µ& ­ >ÿÿ ÿëÞÎ&¾& ´ Sÿÿ ÿëÐÎ&µ& ­ hÿÿ ÿíÕÎ&¾&  }ÿÿ ÿðØÎ&µ& ­ ’ÿÿ ÿçÜÎ&¾& ´ §ÿÿÿëÑÎ&¡ ÉÿÿÿëÐÎ&³& × „ÿÿÿëÐÎ&³& × ™ÿÿÿêëÎ&³& × ®ÿÿÿíÕÎ&ª& Ð ÃÿÿÿéãÎ&³& × ØÿÿÿçßÎ&¼& ì íÿÿÿìÕÎ&³& × ÿÿÿíÕÎ&¼& ì ÿÿÿëÐÎ&¼& ì ,ÿÿÿíÐÎ&¼& ì AÿÿÿìÐÎ&¼& ì VÿÿÿìéÎ&¼& ì kÿÿÿîÔÎ&¼& ì €ÿÿÿîÕÎ&¼& ì •ÿÿÿèÛÎ&¼& ì ªÿÿÿïÐÎ&³& × ¿ÿÿÿîÐÎ&³& × ÔÿÿÿêéÎ&¼& ì éÿÿÿêãÎ&³& å þÿÿÿìéÎ&³& × ÿÿÿêÔÎ&³& å (ÿÿÿêÞÎ&³& × =ÿÿÿëÞÎ&¼& Þ RÿÿÿëÐÎ&³& × gÿÿÿíÕÎ&¼& ì |ÿÿÿðØÎ&³& × ‘ÿÿÿç×Î&¼& Þ ¦ÿÿÿíëÏ&Ÿ óÿÿÿëëÏ&±&  {ÿÿÿëëÏ&±&  ÿÿÿêëÏ&±&  ¥ÿÿÿðëÏ&¨& ú ºÿÿÿéëÏ&±&  ÏÿÿÿçëÏ&º&  äÿÿÿïëÏ&±&  ùÿÿÿíëÏ&º&  ÿÿÿëëÏ&º&  #ÿÿÿíëÏ&º&  8ÿÿÿðëÏ&º&  MÿÿÿìëÏ&º&  bÿÿÿíëÏ&º&  wÿÿÿíëÏ&º&  ŒÿÿÿçëÏ&º&  ¡ÿÿÿïëÏ&±&  ¶ÿÿÿîëÏ&±&  ËÿÿÿèìÏ&º&  àÿÿÿèëÏ&±&  õÿÿÿéëÏ&±&  ÿÿÿçëÏ&±&  ÿÿÿçëÏ&±&  4ÿÿÿçëÏ&º&  IÿÿÿëëÏ&±&  ^ÿÿÿìëÏ&º&  sÿÿÿíëÏ&±&  ˆÿÿÿçëÏ&º&  ÿÿ ëÉ&ž ÿÿÿëëÏ&°& * {ÿÿÿëëÏ&°& * ÿÿÿêëÏ&°& * ¥ÿÿÿðëÏ&§& # ºÿÿÿéëÏ&°& * ÏÿÿÿçëÏ&¹& ? äÿÿÿïëÏ&°& * ùÿÿÿíëÏ&¹& ? ÿÿÿëëÏ&¹& ? #ÿÿÿíëÏ&¹& ? 8ÿÿÿðëÏ&¹& ? MÿÿÿìëÏ&¹& ? bÿÿÿíëÏ&¹& ? wÿÿÿíëÏ&¹& ? ŒÿÿÿçëÏ&¹& ? ¡ÿÿÿïëÏ&°& * ¶ÿÿÿîëÏ&°& * ËÿÿÿèìÏ&¹& ? àÿÿÿèëÏ&°& 8 õÿÿÿéëÏ&°& * ÿÿÿçëÏ&°& 8 ÿÿÿçëÏ&°& * 4ÿÿÿçëÏ&¹& 1 IÿÿÿëëÏ&°& * ^ÿÿÿìëÏ&¹& ? sÿÿÿíëÏ&°& * ˆÿÿÿçëÏ&¹& 1 ÿÿÿëÐÎ&  EÿÿÿëÐÎ&²& S ƒÿÿÿëÐÎ&²& S ˜ÿÿÿêëÎ&²& S ­ÿÿÿíÕÎ&©& L ÂÿÿÿéãÎ&²& S ×ÿÿÿçßÎ&»& h ìÿÿÿíÕÎ&²& S ÿÿÿíÕÎ&»& h ÿÿÿëÐÎ&»& h +ÿÿÿíÐÎ&»& h @ÿÿÿìÐÎ&»& h UÿÿÿìéÎ&»& h jÿÿÿîÔÎ&»& h ÿÿÿíÕÎ&»& h ”ÿÿÿèÛÎ&»& h ©ÿÿÿïÐÎ&²& S ¾ÿÿÿîÐÎ&²& S ÓÿÿÿêéÎ&»& h èÿÿÿêãÎ&²& a ýÿÿÿìéÎ&²& S ÿÿÿêÔÎ&²& a 'ÿÿÿëâÎ&²& S <ÿÿÿëáÎ&»& Z QÿÿÿëÐÎ&²& S fÿÿÿíÕÎ&»& h {ÿÿÿðØÎ&²& S ÿÿÿçÜÎ&»& Z ¥ÿÿÿëÐÎ&› iÿÿÿëÐÎ& o&­ xÿÿÿëÐÎ&­& o ŽÿÿÿèìÎ&­& o £ÿÿÿðÕÎ& l& ¸¤ÿÿÿéãÎ&­& o ÍÿÿÿçßÎ&¶& u âÿÿÿïÕÎ&­& o ÷ÿÿÿíÕÎ&­&  uÿÿÿëÐÎ&¶& u !ÿÿÿíÐÎ&¶& u 6ÿÿÿíÐÎ&¶& u KÿÿÿìéÎ&¶& u `ÿÿÿíÔÎ&¶& u uÿÿÿíÕÎ&¶& u ŠÿÿÿçÛÎ&¶& u ŸÿÿÿïÐÎ& u&­ ³ÿÿÿîÐÎ&­& o ÉÿÿÿèìÎ&¶& u ÞÿÿÿèãÎ&­& r óÿÿÿééÎ&­& o ÿÿÿçÔÎ&­& r !ÿÿÿçÞÎ&­& o 2ÿÿÿçÞÎ&¶& o GÿÿÿëÐÎ&­& o \ÿÿÿìÕÎ&¶& u qÿÿÿíØÎ&­& o †ÿÿÿçÖÎ&¶& o ›ÿÿÿëöÎ&¿UÿÿÿëõÎ&Ô&[ xÿÿÿëõÎ&Ô&[ ÿÿÿèõÎ&Ô&[ ¢ÿÿÿðöÎ&Ê&X ·ÿÿÿêõÎ&Ô&[ ÌÿÿÿçõÎ&ß&a áÿÿÿïõÎ&Ô&[ öÿÿÿíõÎ&ß&a ÿÿÿëõÎ&ß&a ÿÿÿíõÎ&ß&a 5ÿÿÿíõÎ&ß&a JÿÿÿìõÎ&ß&a _ÿÿÿíõÎ&ß&a tÿÿÿíõÎ&ß&a ‰ÿÿÿçõÎ&ß&a žÿÿÿïõÎ&Ô&[ ³ÿÿÿîõÎ&Ô&[ ÈÿÿÿèõÎ&ß&a ÝÿÿÿèöÎ&Ô&^ òÿÿÿéõÎ&Ô&[ ÿÿÿçöÎ&Ô&^ ÿÿÿçõÎ&Ô&[ 1ÿÿÿçõÎ&ß&[ FÿÿÿëõÎ&Ô&[ [ÿÿÿìõÎ&ß&a pÿÿÿíõÎ&Ô&[ …ÿÿÿçõÎ&ß&[ šÿÿ ÿëÐÎ&Ádÿÿ ÿëÐÎ&Ö&j zÿÿ ÿëÐÎ&Ö&j ÿÿ ÿèìÎ&Ö&j ¤ÿÿ ÿðÕÎ&Ì&g ¹ÿÿ ÿéãÎ&Ö&j Îÿÿ ÿçßÎ&á&p ãÿÿ ÿïÕÎ&Ö&j øÿÿ ÿíÕÎ&á&p ÿÿ ÿëÐÎ&á&p "ÿÿ ÿíÐÎ&á&p 7ÿÿ ÿíÐÎ&á&p Lÿÿ ÿìéÎ&á&p aÿÿ ÿíÔÎ&á&p vÿÿ ÿíÕÎ&á&p ‹ÿÿ ÿçÛÎ&á&p  ÿÿ ÿïÐÎ&Ö&j µÿÿ ÿîÐÎ&Ö&j Êÿÿ ÿèìÎ&á&p ßÿÿ ÿèàÎ&Ö&m ôÿÿ ÿééÎ&Ö&j ÿÿ ÿçÔÎ&Ö&m ÿÿ ÿçÞÎ&Ö&j 3ÿÿ ÿçÞÎ&á&j Hÿÿ ÿëÐÎ&Ö&j ]ÿÿ ÿìÕÎ&á&p rÿÿ ÿíØÎ&Ö&j ‡ÿÿ ÿçÜÎ&á&j œÿÿÿëöÎ&¿sÿÿÿëöÎ&Ô&y xÿÿÿëöÎ&Ô&y ÿÿÿèöÎ&Ô&y ¢ÿÿÿðöÎ&Ê&v ·ÿÿÿêöÎ&Ô&y ÌÿÿÿçöÎ&ß& áÿÿÿïöÎ&Ô&y öÿÿÿíöÎ&ß& ÿÿÿëöÎ&ß& ÿÿÿíöÎ&ß& 5ÿÿÿíöÎ&ß& JÿÿÿìöÎ&ß& _ÿÿÿíöÎ&ß& tÿÿÿíöÎ&ß& ‰ÿÿÿçöÎ&ß& žÿÿÿïöÎ&Ô&y ³ÿÿÿîöÎ&Ô&y ÈÿÿÿèöÎ&ß& ÝÿÿÿèöÎ&Ô&| òÿÿÿéöÎ&Ô&y ÿÿÿçöÎ&Ô&| ÿÿÿçöÎ&Ô&y 1ÿÿÿçöÎ&ß&y FÿÿÿëöÎ&Ô&y [ÿÿÿìöÎ&ß& pÿÿÿíöÎ&Ô&y …ÿÿÿçöÎ&ß&y šÿÿ ÿëÐÎ&Á‚ÿÿ ÿëÐÎ&Ö&ˆ zÿÿ ÿëÐÎ&Ö&ˆ ÿÿ ÿèìÎ&Ö&ˆ ¤ÿÿ ÿðÕÎ&Ì&… ¹ÿÿ ÿéãÎ&Ö&ˆ Îÿÿ ÿçßÎ&á&Ž ãÿÿ ÿïÕÎ&Ö&ˆ øÿÿ ÿíÕÎ&á&Ž ÿÿ ÿëÐÎ&á&Ž "ÿÿ ÿíÐÎ&á&Ž 7ÿÿ ÿíÐÎ&á&Ž Lÿÿ ÿìéÎ&á&Ž aÿÿ ÿíÔÎ&á&Ž vÿÿ ÿíÕÎ&á&Ž ‹ÿÿ ÿçÛÎ&á&Ž  ÿÿ ÿïÐÎ&Ö&ˆ µÿÿ ÿîÐÎ&Ö&ˆ Êÿÿ ÿèìÎ&á&Ž ßÿÿ ÿèàÎ&Ö&‹ ôÿÿ ÿééÎ&Ö&ˆ ÿÿ ÿçÔÎ&Ö&‹ ÿÿ ÿçÞÎ&Ö&ˆ 3ÿÿ ÿçÞÎ&á&ˆ Hÿÿ ÿëÐÎ&Ö&ˆ ]ÿÿ ÿìÕÎ&á&Ž rÿÿ ÿíØÎ&Ö&ˆ ‡ÿÿ ÿçÜÎ&á&ˆ œÿÿ ÿëÐÎ&À‘ÿÿ ÿëÐÎ&Õ&¤ yÿÿ ÿëÐÎ&Õ&¤ Žÿÿ ÿèìÎ&Õ&¤ £ÿÿ ÿðÕÎ&Ë&› ¸ÿÿ ÿéãÎ&Õ&¤ Íÿÿ ÿçßÎ&à&® âÿÿ ÿïÕÎ&Õ&¤ ÷ÿÿ ÿíÕÎ&à&® ÿÿ ÿëÐÎ&à&® !ÿÿ ÿíÐÎ&à&® 6ÿÿ ÿíÐÎ&à&® Kÿÿ ÿìéÎ&à&® `ÿÿ ÿíÔÎ&à&® uÿÿ ÿíÕÎ&à&® Šÿÿ ÿçÛÎ&à&® Ÿÿÿ ÿïÐÎ&Õ&¤ ´ÿÿ ÿîÐÎ&Õ&¤ Éÿÿ ÿèìÎ&à&® Þÿÿ ÿèãÎ&Õ&¸ óÿÿ ÿééÎ&Õ&¤ ÿÿ ÿçÔÎ&Õ&¸ ÿÿ ÿçÞÎ&Õ&¤ 2ÿÿ ÿçÞÎ&à&® Gÿÿ ÿëÐÎ&Õ&¤ \ÿÿ ÿìÕÎ&à&® qÿÿ ÿíØÎ&Õ&¤ †ÿÿ ÿçÖÎ&à&® ›ÿÿ ÿëÐÎ&ÂÁÿÿ ÿëÐÎ&Ö&Ô zÿÿ ÿëÐÎ&Ö&Ô ÿÿ ÿèìÎ&Ö&Ô ¤ÿÿ ÿðÕÎ&Ì&Ê ¹ÿÿ ÿéãÎ&Ö&Ô Îÿÿ ÿçßÎ&á&Þ ãÿÿ ÿïÕÎ&Ö&Ô øÿÿ ÿíÕÎ&á&Þ ÿÿ ÿëÐÎ&á&Þ "ÿÿ ÿíÐÎ&á&Þ 7ÿÿ ÿíÐÎ&á&Þ Lÿÿ ÿìéÎ&á&Þ aÿÿ ÿíÔÎ&á&Þ vÿÿ ÿíÕÎ&á&Þ ‹ÿÿ ÿçÛÎ&á&Þ  ÿÿ ÿïÐÎ&Ö&Ô µÿÿ ÿîÐÎ&Ö&Ô Êÿÿ ÿèìÎ&á&Þ ßÿÿ ÿèàÎ&Ö&è ôÿÿ ÿééÎ&Ö&Ô ÿÿ ÿçÔÎ&Ö&è ÿÿ ÿçÞÎ&Ö&Ô 3ÿÿ ÿçÞÎ&á&Þ Hÿÿ ÿëÐÎ&Ö&Ô ]ÿÿ ÿìÕÎ&á&Þ rÿÿ ÿíØÎ&Ö&Ô ‡ÿÿ ÿçÜÎ&á&Þ œÿÿ ÿëÐÎ&Àòÿÿ ÿëÐÎ&Õ&  yÿÿ ÿëÐÎ&Õ&  Žÿÿ ÿèìÎ&Õ&  £ÿÿ ÿðÕÎ&Ë&ü ¸ÿÿ ÿéãÎ&Õ&  Íÿÿ ÿçßÎ&à&  âÿÿ ÿïÕÎ&Õ&  ÷ÿÿ ÿíÕÎ&à&  ÿÿ ÿëÐÎ&à&  !ÿÿ ÿíÐÎ&à&  6ÿÿ ÿíÐÎ&à&  Kÿÿ ÿìéÎ&à&  `ÿÿ ÿíÔÎ&à&  uÿÿ ÿíÕÎ&à&  Šÿÿ ÿçÛÎ&à&  Ÿÿÿ ÿïÐÎ&Õ&  ´ÿÿ ÿîÐÎ&Õ&  Éÿÿ ÿèìÎ&à&  Þÿÿ ÿèãÎ&Õ&  óÿÿ ÿééÎ&Õ&  ÿÿ ÿçÔÎ&Õ&  ÿÿ ÿçÞÎ&Õ&  2ÿÿ ÿçÞÎ&à&  Gÿÿ ÿëÐÎ&Õ&  \ÿÿ ÿìÕÎ&à&  qÿÿ ÿíØÎ&Õ&  †ÿÿ ÿçÖÎ&à&  ›ÿÿ ÿëÐÎ&Á !ÿÿ ÿëÐÎ&Ö& 5 zÿÿ ÿëÐÎ&Ö& 5 ÿÿ ÿèìÎ&Ö& 5 ¤ÿÿ ÿðÕÎ&Ì& + ¹ÿÿ ÿéãÎ&Ö& 5 Îÿÿ ÿçßÎ&á& ? ãÿÿ ÿïÕÎ&Ö& 5 øÿÿ ÿíÕÎ&á& ? ÿÿ ÿëÐÎ&á& ? "ÿÿ ÿíÐÎ&á& ? 7ÿÿ ÿíÐÎ&á& ? Lÿÿ ÿìéÎ&á& ? aÿÿ ÿíÔÎ&á& ? vÿÿ ÿíÕÎ&á& ? ‹ÿÿ ÿçÛÎ&á& ?  ÿÿ ÿïÐÎ&Ö& 5 µÿÿ ÿîÐÎ&Ö& 5 Êÿÿ ÿèìÎ&á& ? ßÿÿ ÿèàÎ&Ö& I ôÿÿ ÿééÎ&Ö& 5 ÿÿ ÿçÔÎ&Ö& I ÿÿ ÿçÞÎ&Ö& 5 3ÿÿ ÿçÞÎ&á& ? Hÿÿ ÿëÐÎ&Ö& 5 ]ÿÿ ÿìÕÎ&á& ? rÿÿ ÿíØÎ&Ö& 5 ‡ÿÿ ÿçÜÎ&á& ? œÿÿ ê½& SÿÿÿëêÉ&×& l {ÿÿÿëêÉ&×& l ÿÿÿêêÉ&×& l ¥ÿÿÿðêÉ&Í& b ºÿÿÿéêÉ&×& l ÏÿÿÿçêÉ&â& v äÿÿÿïêÉ&×& l ùÿÿÿíêÉ&â& v ÿÿÿëêÉ&â& v #ÿÿÿíêÉ&â& v 8ÿÿÿðêÉ&â& v MÿÿÿìêÉ&â& v bÿÿÿíêÉ&â& v wÿÿÿíêÉ&â& v ŒÿÿÿçêÉ&â& v ¡ÿÿÿïêÉ&×& l ¶ÿÿÿîêÉ&×& l ËÿÿÿèìÉ&â& v àÿÿÿèêÉ&×&  õÿÿÿéêÉ&×& l ÿÿÿçêÉ&×&  ÿÿÿçêÉ&×& l 4ÿÿÿçêÉ&â& v IÿÿÿëêÉ&×& l ^ÿÿÿìêÉ&â& v sÿÿÿíêÉ&×& l ˆÿÿÿçêÉ&â& v ÿÿÿëöÎ&Ä ‰ÿÿÿëöÎ&Ù&  |ÿÿÿëöÎ&Ù&  ‘ÿÿÿêöÎ&Ù&  ¦ÿÿÿðöÎ&Ï& “ »ÿÿÿêöÎ&Ù&  ÐÿÿÿçöÎ&ä& § åÿÿÿïöÎ&Ù&  úÿÿÿíöÎ&ä& § ÿÿÿëöÎ&ä& § $ÿÿÿíöÎ&ä& § 9ÿÿÿíöÎ&ä& § NÿÿÿìöÎ&ä& § cÿÿÿîöÎ&ä& § xÿÿÿíöÎ&ä& § ÿÿÿçöÎ&ä& § ¢ÿÿÿïöÎ&Ù&  ·ÿÿÿîöÎ&Ù&  ÌÿÿÿèöÎ&ä& § áÿÿÿèöÎ&Ù& ± öÿÿÿéöÎ&Ù&  ÿÿÿçöÎ&Ù& ± ÿÿÿçöÎ&Ù&  5ÿÿÿçöÎ&ä& § JÿÿÿëöÎ&Ù&  _ÿÿÿìöÎ&ä& § tÿÿÿíöÎ&Ù&  ‰ÿÿÿçöÎ&ä& § žÿÿÿëÐÎ&Æ »ÿÿÿëÐÎ&Û& Ï |ÿÿÿëÐÎ&Û& Ï ‘ÿÿÿêëÎ&Û& Ï ¦ÿÿÿðÕÎ&Ñ& Å »ÿÿÿêãÎ&Û& Ï ÐÿÿÿçßÎ&æ& Ù åÿÿÿïÕÎ&Û& Ï úÿÿÿíÕÎ&æ& Ù ÿÿÿëÐÎ&æ& Ù $ÿÿÿíÐÎ&æ& Ù 9ÿÿÿíÐÎ&æ& Ù NÿÿÿìéÎ&æ& Ù cÿÿÿîÔÎ&æ& Ù xÿÿÿíÕÎ&æ& Ù ÿÿÿçÛÎ&æ& Ù ¢ÿÿÿïÐÎ&Û& Ï ·ÿÿÿîÐÎ&Û& Ï ÌÿÿÿèéÎ&æ& Ù áÿÿÿèãÎ&Û& ã öÿÿÿééÎ&Û& Ï ÿÿÿçÔÎ&Û& ã ÿÿÿçâÎ&Û& Ï 5ÿÿÿçâÎ&æ& Ù JÿÿÿëÐÎ&Û& Ï _ÿÿÿìÕÎ&æ& Ù tÿÿÿíØÎ&Û& Ï ‰ÿÿÿçÜÎ&æ& Ù žÿÿÿëÐÎ&Ä ìÿÿÿëÐÎ&Ù&  |ÿÿÿëÐÎ&Ù&  ‘ÿÿÿêëÎ&Ù&  ¦ÿÿÿðÕÎ&Ï& ö »ÿÿÿêãÎ&Ù&  ÐÿÿÿçßÎ&ä&  åÿÿÿïÕÎ&Ù&  úÿÿÿíÕÎ&ä&  ÿÿÿëÐÎ&ä&  $ÿÿÿíÐÎ&ä&  9ÿÿÿíÐÎ&ä&  NÿÿÿìéÎ&ä&  cÿÿÿîÔÎ&ä&  xÿÿÿíÕÎ&ä&  ÿÿÿçÛÎ&ä&  ¢ÿÿÿïÐÎ&Ù&  ·ÿÿÿîÐÎ&Ù&  ÌÿÿÿèéÎ&ä&  áÿÿÿèãÎ&Ù&  öÿÿÿééÎ&Ù&  ÿÿÿçÔÎ&Ù&  ÿÿÿçâÎ&Ù&  5ÿÿÿçâÎ&ä&  JÿÿÿëÐÎ&Ù&  _ÿÿÿìÕÎ&ä&  tÿÿÿíØÎ&Ù&  ‰ÿÿÿçÜÎ&ä&  žÿÿ ë½& ÿÿÿëëÉ&×& 2 {ÿÿÿëëÉ&×& 2 ÿÿÿêëÉ&×& 2 ¥ÿÿÿðëÉ&Í& ( ºÿÿÿéëÉ&×& 2 ÏÿÿÿçëÉ&â& < äÿÿÿïëÉ&×& 2 ùÿÿÿíëÉ&â& < ÿÿÿëëÉ&â& < #ÿÿÿíëÉ&â& < 8ÿÿÿðëÉ&â& < MÿÿÿìëÉ&â& < bÿÿÿíëÉ&â& < wÿÿÿíëÉ&â& < ŒÿÿÿçëÉ&â& < ¡ÿÿÿïëÉ&×& 2 ¶ÿÿÿîëÉ&×& 2 ËÿÿÿèìÉ&â& < àÿÿÿèëÉ&×& F õÿÿÿéëÉ&×& 2 ÿÿÿçëÉ&×& F ÿÿÿçëÉ&×& 2 4ÿÿÿçëÉ&â& < IÿÿÿëëÉ&×& 2 ^ÿÿÿìëÉ&â& < sÿÿÿíëÉ&×& 2 ˆÿÿÿçëÉ&â& < ÿÿÿëëÉ&à HÿÿÿëëÉ&Ø& U {ÿÿÿëëÉ&Ø& U ÿÿÿêëÉ&Ø& U ¥ÿÿÿðëÉ&Î& P ºÿÿÿéëÉ&Ø& U ÏÿÿÿçëÉ&ã& j äÿÿÿïëÉ&Ø& U ùÿÿÿíëÉ&ã& j ÿÿÿëëÉ&ã& j #ÿÿÿíëÉ&ã& j 8ÿÿÿðëÉ&ã& j MÿÿÿìëÉ&ã& j bÿÿÿíëÉ&ã& j wÿÿÿíëÉ&ã& j ŒÿÿÿçëÉ&ã& j ¡ÿÿÿïëÉ&Ø& U ¶ÿÿÿîëÉ&Ø& U ËÿÿÿèìÉ&ã& j àÿÿÿèëÉ&Ø& c õÿÿÿéëÉ&Ø& U ÿÿÿçëÉ&Ø& U ÿÿÿçëÉ&Ø& U 4ÿÿÿçëÉ&ã& \ IÿÿÿëëÉ&Ø& U ^ÿÿÿìëÉ&ã& j sÿÿÿíëÉ&Ø& U ˆÿÿÿçëÉ&ã& \ ÿÿÿëÐÎ&Å qÿÿÿëÐÎ&Ú&  ~ÿÿÿëÐÎ&Ú&  “ÿÿÿêëÎ&Ú&  ¨ÿÿÿðÕÎ&Ð& x ½ÿÿÿéãÎ&Ú&  ÒÿÿÿçßÎ&å& ” çÿÿÿïÕÎ&Ú&  üÿÿÿíÕÎ&å& ” ÿÿÿëÐÎ&å& ” &ÿÿÿíÐÎ&å& ” ;ÿÿÿíÐÎ&å& ” PÿÿÿëéÎ&å& ” eÿÿÿîÔÎ&å& ” zÿÿÿíÖÎ&å& ” ÿÿÿçÛÎ&å& ” ¤ÿÿÿïÐÎ&Ú&  ¹ÿÿÿîÐÎ&Ú&  ÎÿÿÿêéÎ&å& ” ãÿÿÿèãÎ&Ú&  øÿÿÿçéÎ&Ú&  ÿÿÿçÔÎ&Ú&  "ÿÿÿçÞÎ&Ú&  7ÿÿÿíÞÎ&å& † LÿÿÿëÐÎ&Ú&  aÿÿÿìÕÎ&å& ” vÿÿÿíØÎ&Ú&  ‹ÿÿÿçÜÎ&å& †  ÿÿÿëÐÎ&Ç ›ÿÿÿëÐÎ&Ü& © ~ÿÿÿëÐÎ&Ü& © “ÿÿÿêëÎ&Ü& © ¨ÿÿÿðÕÎ&Ò& ¢ ½ÿÿÿéãÎ&Ü& © ÒÿÿÿçßÎ&ç& ¾ çÿÿÿïÕÎ&Ü& © üÿÿÿíÕÎ&ç& ¾ ÿÿÿëÐÎ&ç& ¾ &ÿÿÿíÐÎ&ç& ¾ ;ÿÿÿíÐÎ&ç& ¾ PÿÿÿëéÎ&ç& ¾ eÿÿÿîÔÎ&ç& ¾ zÿÿÿíÖÎ&ç& ¾ ÿÿÿçÛÎ&ç& ¾ ¤ÿÿÿïÐÎ&Ü& © ¹ÿÿÿîÐÎ&Ü& © ÎÿÿÿêéÎ&ç& ¾ ãÿÿÿèãÎ&Ü& · øÿÿÿçéÎ&Ü& © ÿÿÿçÔÎ&Ü& · "ÿÿÿçÞÎ&Ü& © 7ÿÿÿíÞÎ&ç& ° LÿÿÿëÐÎ&Ü& © aÿÿÿìÕÎ&ç& ¾ vÿÿÿíØÎ&Ü& © ‹ÿÿÿçÜÎ&ç& °  ÿÿÿëÐÎ& ÃÅÿÿÿëÐÎ&Ú& Ó }ÿÿÿëÐÎ&Ú& Ó ’ÿÿÿêëÎ&Ú& Ó §ÿÿÿðÕÎ&Ð& Ì ¼ÿÿÿéãÎ&Ú& Ó ÑÿÿÿçßÎ&å& è æÿÿÿïÕÎ&Ú& Ó ûÿÿÿíÕÎ&å& è ÿÿÿëÐÎ&å& è %ÿÿÿíÐÎ&å& è :ÿÿÿíÐÎ&å& è OÿÿÿëèÎ&å& è dÿÿÿîÔÎ&å& è yÿÿÿíÖÎ&å& è ŽÿÿÿçÛÎ&å& è £ÿÿÿïÐÎ&Ú& Ó ¸ÿÿÿîÐÎ&Ú& Ó ÍÿÿÿêéÎ&å& è âÿÿÿèãÎ&Ú& á ÷ÿÿÿçéÎ&Ú& Ó ÿÿÿçÔÎ&Ú& á !ÿÿÿçÞÎ&Ú& Ó 6ÿÿÿçÞÎ&å& Ú KÿÿÿëÐÎ&Ú& Ó `ÿÿÿìÕÎ&å& è uÿÿÿíØÎ&Ú& Ó ŠÿÿÿçÖÎ&å& Ú ŸÿÿÿíëÉ&à ïÿÿÿëëÉ&Ø& ý {ÿÿÿëëÉ&Ø& ý ÿÿÿêëÉ&Ø& ý ¥ÿÿÿðëÉ&Î& ö ºÿÿÿéëÉ&Ø& ý ÏÿÿÿçëÉ&ã&  äÿÿÿïëÉ&Ø& ý ùÿÿÿíëÉ&ã&  ÿÿÿëëÉ&ã&  #ÿÿÿíëÉ&ã&  8ÿÿÿðëÉ&ã&  MÿÿÿìëÉ&ã&  bÿÿÿíëÉ&ã&  wÿÿÿíëÉ&ã&  ŒÿÿÿçëÉ&ã&  ¡ÿÿÿïëÉ&Ø& ý ¶ÿÿÿîëÉ&Ø& ý ËÿÿÿèìÉ&ã&  àÿÿÿèëÉ&Ø&  õÿÿÿéëÉ&Ø& ý ÿÿÿçëÉ&Ø&  ÿÿÿçëÉ&Ø& ý 4ÿÿÿçëÉ&ã&  IÿÿÿëëÉ&Ø& ý ^ÿÿÿìëÉ&ã&  sÿÿÿíëÉ&Ø& ý ˆÿÿÿçëÉ&ã&  ÿÿ ë½&È ÿÿÿëëÉ&Ý& & {ÿÿÿëëÉ&Ý& & ÿÿÿêëÉ&Ý& & ¥ÿÿÿðëÉ& &&Ý ºÿÿÿéëÉ&Ý& & ÏÿÿÿçëÉ&è& ; äÿÿÿïëÉ&Ý& & ùÿÿÿíëÉ&è& ; ÿÿÿëëÉ&è& ; #ÿÿÿíëÉ&è& ; 8ÿÿÿðëÉ&è& ; MÿÿÿìëÉ&è& ; bÿÿÿíëÉ&è& ; wÿÿÿíëÉ&è& ; ŒÿÿÿçëÉ&è& ; ¡ÿÿÿïëÉ&Ý& & ¶ÿÿÿîëÉ&Ý& & ËÿÿÿèìÉ&è& ; àÿÿÿèëÉ&Ý& 4 õÿÿÿéëÉ&Ý& & ÿÿÿçëÉ&Ý& 4 ÿÿÿçëÉ&Ý& & 4ÿÿÿçëÉ&è& - IÿÿÿëëÉ&Ý& & ^ÿÿÿìëÉ&è& ; sÿÿÿíëÉ&Ý& & ˆÿÿÿçëÉ&è& - ÿÿÿëÐÎ&É BÿÿÿëÐÎ&Þ& O |ÿÿÿëÐÎ&Þ& O ‘ÿÿÿêëÎ&Þ& O ¦ÿÿÿðÕÎ&Ó& H »ÿÿÿêãÎ&Þ& O ÐÿÿÿçßÎ&é& d åÿÿÿïÕÎ&Þ& O úÿÿÿíÕÎ&é& d ÿÿÿëÐÎ&é& d $ÿÿÿíÐÎ&é& d 9ÿÿÿíÐÎ&é& d NÿÿÿìéÎ&é& d cÿÿÿîÔÎ&é& d xÿÿÿíÕÎ&é& d ÿÿÿçÛÎ&é& d ¢ÿÿÿïÐÎ&Þ& O ·ÿÿÿîÐÎ&Þ& O ÌÿÿÿèéÎ&é& d áÿÿÿèãÎ&Þ& ] öÿÿÿééÎ&Þ& O ÿÿÿçÔÎ&Þ& ] ÿÿÿçâÎ&Þ& O 5ÿÿÿçâÎ&é& V JÿÿÿëÐÎ&Þ& O _ÿÿÿìÕÎ&é& d tÿÿÿíØÎ&Þ& O ‰ÿÿÿçÜÎ&é& V žÿÿÿëÐÎ&¿ iÿÿÿëÐÎ&Ô& o yÿÿÿëÐÎ&Ô& o ŽÿÿÿèìÎ&Ô& o £ÿÿÿðÕÎ&Ê& l ¸ÿÿÿéãÎ&Ô& o ÍÿÿÿçßÎ&ß& u âÿÿÿïÕÎ&Ô& o ÷ÿÿÿíÕÎ&ß& u ÿÿÿëÐÎ&ß& u !ÿÿÿíÐÎ&ß& u 6ÿÿÿíÐÎ&ß& u KÿÿÿìéÎ&ß& u `ÿÿÿíÔÎ&ß& u uÿÿÿíÕÎ&ß& u ŠÿÿÿçÛÎ&ß& u ŸÿÿÿïÐÎ&Ô& o ´ÿÿÿîÐÎ&Ô& o ÉÿÿÿèìÎ&ß& u ÞÿÿÿèãÎ&Ô& r óÿÿÿééÎ&Ô& o ÿÿÿçÔÎ&Ô& r ÿÿÿçÞÎ&Ô& o 2ÿÿÿçÞÎ&ß& o GÿÿÿëÐÎ&Ô& o \ÿÿÿìÕÎ&ß& u qÿÿÿíØÎ&Ô& o †ÿÿÿçÖÎ&ß& o ›ÿÿÿëöÎ&UêÿÿÿëõÎ&ü&[ xÿÿÿëõÎ&ü&[ ÿÿÿèõÎ&ü&[ ¢ÿÿÿðöÎ&ó& ¸XÿÿÿêõÎ&ü&[ ÌÿÿÿçõÎ&&a áÿÿÿïõÎ&ü& ö[ÿÿÿíõÎ&[& ÿÿÿëõÎ&&a ÿÿÿíõÎ&&a 5ÿÿÿíõÎ&&a JÿÿÿìõÎ&&a _ÿÿÿíõÎ&&a tÿÿÿíõÎ&&a ‰ÿÿÿçõÎ&&a žÿÿÿïõÎ&ü&[ ³ÿÿÿîõÎ&ü&[ ÈÿÿÿèõÎ&&a ÝÿÿÿèöÎ&ü&^ òÿÿÿéõÎ&ü&[ ÿÿÿçöÎ&ü&^ ÿÿÿçõÎ&ü&[ 1ÿÿÿçõÎ&&[ FÿÿÿëõÎ&ü&[ [ÿÿÿìõÎ&&a pÿÿÿíõÎ&ü&[ …ÿÿÿçõÎ&&[ šÿÿÿëÐÎ&dìÿÿÿëÐÎ&þ&j zÿÿÿëÐÎ&þ&j ÿÿÿèìÎ&þ&j ¤ÿÿÿðÕÎ&õ&g ¹ÿÿÿéãÎ&þ&j ÎÿÿÿçßÎ&&p ãÿÿÿïÕÎ&þ&j øÿÿÿíÕÎ&&p ÿÿÿëÐÎ&&p "ÿÿÿíÐÎ&&p 7ÿÿÿíÐÎ&&p LÿÿÿìéÎ&&p aÿÿÿíÔÎ&&p vÿÿÿíÕÎ&&p ‹ÿÿÿçÛÎ&&p  ÿÿÿïÐÎ&þ&j µÿÿÿîÐÎ&þ&j ÊÿÿÿèìÎ&&p ßÿÿÿèàÎ&þ&m ôÿÿÿééÎ&þ&j ÿÿÿçÔÎ&þ&m ÿÿÿçÞÎ&þ&j 3ÿÿÿçÞÎ&&j HÿÿÿëÐÎ&þ&j ]ÿÿÿìÕÎ&&p rÿÿÿíØÎ&þ&j ‡ÿÿÿçÜÎ&&j œÿÿÿëöÎ&êsÿÿÿëöÎ&ü&y xÿÿÿëöÎ&ü&y ÿÿÿèöÎ&ü&y ¢ÿÿÿðöÎ&ó&v ·ÿÿÿêöÎ&ü&y ÌÿÿÿçöÎ&& áÿÿÿïöÎ&ü&y öÿÿÿíöÎ&& ÿÿÿëöÎ&& ÿÿÿíöÎ&& 5ÿÿÿíöÎ&& JÿÿÿìöÎ&& _ÿÿÿíöÎ&& tÿÿÿíöÎ&& ‰ÿÿÿçöÎ&& žÿÿÿïöÎ&ü&y ³ÿÿÿîöÎ&ü&y ÈÿÿÿèöÎ&& ÝÿÿÿèöÎ&ü&| òÿÿÿéöÎ&ü&y ÿÿÿçöÎ&ü&| ÿÿÿçöÎ&ü&y 1ÿÿÿçöÎ&&y FÿÿÿëöÎ&ü&y [ÿÿÿìöÎ&& pÿÿÿíöÎ&ü&y …ÿÿÿçöÎ&&y šÿÿÿëÐÎ&ì‚ÿÿÿëÐÎ&þ&ˆ zÿÿÿëÐÎ&þ&ˆ ÿÿÿèìÎ&þ&ˆ ¤ÿÿÿðÕÎ&õ&… ¹ÿÿÿéãÎ&þ&ˆ ÎÿÿÿçßÎ&&Ž ãÿÿÿïÕÎ&þ&ˆ øÿÿÿíÕÎ&&Ž ÿÿÿëÐÎ&&Ž "ÿÿÿíÐÎ&&Ž 7ÿÿÿíÐÎ&&Ž LÿÿÿìéÎ&&Ž aÿÿÿíÔÎ&&Ž vÿÿÿíÕÎ&&Ž ‹ÿÿÿçÛÎ&&Ž  ÿÿÿïÐÎ&þ&ˆ µÿÿÿîÐÎ&þ&ˆ ÊÿÿÿèìÎ&&Ž ßÿÿÿèàÎ&þ&‹ ôÿÿÿééÎ&þ&ˆ ÿÿÿçÔÎ&þ&‹ ÿÿÿçÞÎ&þ&ˆ 3ÿÿÿçÞÎ&&ˆ HÿÿÿëÐÎ&þ&ˆ ]ÿÿÿìÕÎ&&Ž rÿÿÿíØÎ&þ&ˆ ‡ÿÿÿçÜÎ&&ˆ œÿÿÿëÐÎ&ë‘ÿÿÿëÐÎ&¬&ý yÿÿÿëÐÎ&ý&¬ ŽÿÿÿèìÎ&ý&¬ £ÿÿÿðÕÎ&ô&¢ ¸ÿÿÿéãÎ&ý&¬ ÍÿÿÿçßÎ&&¶ âÿÿÿïÕÎ&ý&¬ ÷ÿÿÿíÕÎ&&¶ ÿÿÿëÐÎ&&¶ !ÿÿÿíÐÎ&&¶ 6ÿÿÿíÐÎ&&¶ KÿÿÿìéÎ&&¶ `ÿÿÿíÔÎ&&¶ uÿÿÿíÕÎ&&¶ ŠÿÿÿçÛÎ&&¶ ŸÿÿÿïÐÎ&ý&¬ ´ÿÿÿîÐÎ&ý&¬ ÉÿÿÿèìÎ&&¶ ÞÿÿÿèãÎ&ý&À óÿÿÿééÎ&ý&¬ ÿÿÿçÔÎ&ý&À ÿÿÿçÞÎ&ý&¬ 2ÿÿÿçÞÎ&&¶ GÿÿÿëÐÎ&ý&¬ \ÿÿÿìÕÎ&&¶ qÿÿÿíØÎ&ý&¬ †ÿÿÿçÖÎ&&¶ ›ÿÿÿëÐÎ&ìÉÿÿÿëÐÎ&þ&Ü zÿÿÿëÐÎ&þ&Ü ÿÿÿèìÎ&þ&Ü ¤ÿÿÿðÕÎ&õ&Ò ¹ÿÿÿéãÎ&þ&Ü ÎÿÿÿçßÎ&&æ ãÿÿÿïÕÎ&þ&Ü øÿÿÿíÕÎ&ã& ÿÿÿëÐÎ&&æ "ÿÿÿíÐÎ&&æ 7ÿÿÿíÐÎ&&æ LÿÿÿìéÎ&&æ aÿÿÿíÔÎ&&æ vÿÿÿíÕÎ&&æ ‹ÿÿÿçÛÎ&&æ  ÿÿÿïÐÎ&þ&Ü µÿÿÿîÐÎ&þ&Ü ÊÿÿÿèìÎ&&æ ßÿÿÿèàÎ&þ&ð ôÿÿÿééÎ&þ&Ü ÿÿÿçÔÎ&þ&ð ÿÿÿçÞÎ&þ&Ü 3ÿÿÿçÞÎ&&æ HÿÿÿëÐÎ&þ&Ü ]ÿÿÿìÕÎ&&æ rÿÿÿíØÎ&þ&Ü ‡ÿÿÿçÜÎ&&æ œÿÿÿëÐÎ&ëúÿÿÿëÐÎ&ý&  yÿÿÿëÐÎ&ý&  ŽÿÿÿèìÎ&ý&  £ÿÿÿðÕÎ&ô&  ¸ÿÿÿéãÎ&ý&  ÍÿÿÿçßÎ&&  âÿÿÿïÕÎ&ý&  ÷ÿÿÿíÕÎ&&  ÿÿÿëÐÎ&&  !ÿÿÿíÐÎ&&  6ÿÿÿíÐÎ&&  KÿÿÿìéÎ&&  `ÿÿÿíÔÎ&&  uÿÿÿíÕÎ&&  ŠÿÿÿçÛÎ&&  ŸÿÿÿïÐÎ&ý&  ´ÿÿÿîÐÎ&ý&  ÉÿÿÿèìÎ&&  ÞÿÿÿèãÎ&ý&  óÿÿÿééÎ&ý&  ÿÿÿçÔÎ&ý&  ÿÿÿçÞÎ&ý&  2ÿÿÿçÞÎ&&  GÿÿÿëÐÎ&ý&  \ÿÿÿìÕÎ&&  qÿÿÿíØÎ&ý&  †ÿÿÿçÖÎ&&  ›ÿÿÿëÐÎ&ì )ÿÿÿëÐÎ&þ& = zÿÿÿëÐÎ&þ& = ÿÿÿèìÎ&þ& = ¤ÿÿÿðÕÎ&õ& 3 ¹ÿÿÿéãÎ&þ& = ÎÿÿÿçßÎ&& G ãÿÿÿïÕÎ&þ& = øÿÿÿíÕÎ&& G ÿÿÿëÐÎ&& G "ÿÿÿíÐÎ&& G 7ÿÿÿíÐÎ&& G LÿÿÿìéÎ&& G aÿÿÿíÔÎ&& G vÿÿÿíÕÎ&& G ‹ÿÿÿçÛÎ&& G  ÿÿÿïÐÎ&þ& = µÿÿÿîÐÎ&þ& = ÊÿÿÿèìÎ&& G ßÿÿÿèàÎ&þ& Q ôÿÿÿééÎ&þ& = ÿÿÿçÔÎ&þ& Q ÿÿÿçÞÎ&þ& = 3ÿÿÿçÞÎ&& G HÿÿÿëÐÎ&þ& = ]ÿÿÿìÕÎ&& G rÿÿÿíØÎ&þ& = ‡ÿÿÿçÜÎ&& G œÿÿ ê¿&í WÿÿÿëêÉ&ÿ& m {ÿÿÿëêÉ&ÿ& m ÿÿÿêêÉ&ÿ& m ¥ÿÿÿðêÉ&ö& c ºÿÿÿéêÉ&ÿ& m ÏÿÿÿçêÌ&& w äÿÿÿïêÉ&ÿ& m ùÿÿÿíêÌ&& w ÿÿÿëêÌ&& w #ÿÿÿíêÌ&& w 8ÿÿÿðêÌ&& w MÿÿÿìêÌ&& w bÿÿÿíêÌ&& w wÿÿÿíêÌ&& w ŒÿÿÿçêÌ&& w ¡ÿÿÿïêÉ&ÿ& m ¶ÿÿÿîêÉ&ÿ& m ËÿÿÿèìÌ&& w àÿÿÿèêÉ&ÿ& € õÿÿÿéêÉ&ÿ& m ÿÿÿçêÉ& &ÿ €ÿÿÿçêÉ&ÿ& m 4ÿÿÿçêÌ&& w IÿÿÿëêÉ&ÿ& m ^ÿÿÿìêÌ&& w sÿÿÿíêÉ&ÿ& m ˆÿÿÿçêÌ&& w ÿÿÿëöÎ&ï ŠÿÿÿëöÎ&& ž ƒÿÿÿëöÎ&& ž ˜ÿÿÿêöÎ&& ž ­ÿÿÿíöÎ&ø& ” ÂÿÿÿéöÎ&& ž ×ÿÿÿçöÎ& & ¨ ìÿÿÿíöÎ&& ž ÿÿÿíöÎ& & ¨ ÿÿÿëöÎ& & ¨ +ÿÿÿíöÎ& & ¨ @ÿÿÿìöÎ& & ¨ UÿÿÿìöÎ& & ¨ jÿÿÿîöÎ& & ¨ ÿÿÿíöÎ& & ¨ ”ÿÿÿèöÎ& & ¨ ©ÿÿÿïöÎ&& ž ¾ÿÿÿîöÎ&& ž ÓÿÿÿêöÎ& & ¨ èÿÿÿêöÎ&& ² ýÿÿÿìöÎ&& ž ÿÿÿêöÎ&& ² 'ÿÿÿëöÎ&& ž <ÿÿÿëöÎ& & ¨ QÿÿÿëöÎ&& ž fÿÿÿíöÎ& & ¨ {ÿÿÿðöÎ&& ž ÿÿÿçöÎ& & ¨ ¥ÿÿÿëÐÎ&ñ ¼ÿÿÿëÐÎ&& Ð ƒÿÿÿëÐÎ&& Ð ˜ÿÿÿêëÎ&& Ð ­ÿÿÿíÕÎ&ú& Æ ÂÿÿÿéãÎ&& Ð ×ÿÿÿçßÎ& & Ú ìÿÿÿíÕÎ&& Ð ÿÿÿíÕÎ& & Ú ÿÿÿëÐÎ& & Ú +ÿÿÿíÐÎ& & Ú @ÿÿÿìÐÎ& & Ú UÿÿÿìéÎ& & Ú jÿÿÿîÔÎ& & Ú ÿÿÿíÕÎ& & Ú ”ÿÿÿèÛÎ& & Ú ©ÿÿÿïÐÎ&& Ð ¾ÿÿÿîÐÎ&& Ð ÓÿÿÿêéÎ& & Ú èÿÿÿêãÎ&& ä ýÿÿÿìéÎ&& Ð ÿÿÿêÔÎ&& ä 'ÿÿÿëâÎ&& Ð <ÿÿÿëáÎ& & Ú QÿÿÿëÐÎ&& Ð fÿÿÿíÕÎ& & Ú {ÿÿÿðØÎ&& Ð ÿÿÿçÜÎ& & Ú ¥ÿÿÿëÐÎ&ï íÿÿÿëÐÎ&&  ƒÿÿÿëÐÎ&&  ˜ÿÿÿêëÎ&&  ­ÿÿÿíÕÎ&ø& ÷ ÂÿÿÿéãÎ&&  ×ÿÿÿçßÎ& &  ìÿÿÿíÕÎ&&  ÿÿÿíÕÎ& &  ÿÿÿëÐÎ& &  +ÿÿÿíÐÎ& &  @ÿÿÿìÐÎ& &  UÿÿÿìéÎ& &  jÿÿÿîÔÎ& &  ÿÿÿíÕÎ& &  ”ÿÿÿèÛÎ& &  ©ÿÿÿïÐÎ&&  ¾ÿÿÿîÐÎ&&  ÓÿÿÿêéÎ& &  èÿÿÿêãÎ&&  ýÿÿÿìéÎ&&  ÿÿÿêÔÎ&&  'ÿÿÿëâÎ&&  <ÿÿÿëáÎ& &  QÿÿÿëÐÎ&&  fÿÿÿíÕÎ& &  {ÿÿÿðØÎ&&  ÿÿÿçÜÎ& &  ¥ÿÿ ë¿&í ÿÿÿëëÉ&ÿ& 3 {ÿÿÿëëÉ&ÿ& 3 ÿÿÿêëÉ&ÿ& 3 ¥ÿÿÿðëÉ&ö& ) ºÿÿÿéëÉ&ÿ& 3 ÏÿÿÿçëÌ&& = äÿÿÿïëÉ&ÿ& 3 ùÿÿÿíëÌ&& = ÿÿÿëëÌ&& = #ÿÿÿíëÌ&& = 8ÿÿÿðëÌ&& = MÿÿÿìëÌ&& = bÿÿÿíëÌ&& = wÿÿÿíëÌ&& = ŒÿÿÿçëÌ&& = ¡ÿÿÿïëÉ&ÿ& 3 ¶ÿÿÿîëÉ&ÿ& 3 ËÿÿÿèìÌ&& = àÿÿÿèëÉ&ÿ& G õÿÿÿéëÉ&ÿ& 3 ÿÿÿçëÉ&ÿ& G ÿÿÿçëÉ&ÿ& 3 4ÿÿÿçëÌ&& = IÿÿÿëëÉ&ÿ& 3 ^ÿÿÿìëÌ&& = sÿÿÿíëÉ&ÿ& 3 ˆÿÿÿçëÌ&& = ÿÿÿëëÉ&î MÿÿÿëëÌ&& Y {ÿÿÿëëÌ&& Y ÿÿÿêëÌ&& Y ¥ÿÿÿðëÌ&÷& R ºÿÿÿéëÌ&& Y ÏÿÿÿçëÌ& & n äÿÿÿïëÌ&& Y ùÿÿÿíëÌ& & n ÿÿÿëëÌ& & n #ÿÿÿíëÌ& & n 8ÿÿÿðëÌ& & n MÿÿÿìëÌ& & n bÿÿÿíëÌ& & n wÿÿÿíëÌ& & n ŒÿÿÿçëÌ& & n ¡ÿÿÿïëÌ&& Y ¶ÿÿÿîëÌ&& Y ËÿÿÿèìÌ& & n àÿÿÿèëÌ&& g õÿÿÿéëÌ&& Y ÿÿÿçëÌ&& Y ÿÿÿçëÌ&& Y 4ÿÿÿçëÌ& & ` IÿÿÿëëÌ&& Y ^ÿÿÿìëÌ& & n sÿÿÿíëÌ&& Y ˆÿÿÿçëÌ& & ` ÿÿÿëÐÎ&ð uÿÿÿëÐÎ&& ƒ …ÿÿÿëÐÎ&& ƒ šÿÿÿêëÎ&& ƒ ¯ÿÿÿíÕÎ&ù& | ÄÿÿÿéãÎ&& ƒ ÙÿÿÿçßÎ& & ˜ îÿÿÿìÕÎ&& ƒ ÿÿÿíÕÎ& & ˜ ÿÿÿëÐÎ& & ˜ -ÿÿÿíÐÎ& & ˜ BÿÿÿìÐÎ& & ˜ WÿÿÿìéÎ& & ˜ lÿÿÿîÔÎ& & ˜ ÿÿÿîÕÎ& & ˜ –ÿÿÿçÛÎ& & ˜ «ÿÿÿïÐÎ&& ƒ ÀÿÿÿîÐÎ&& ƒ ÕÿÿÿêéÎ& & ˜ êÿÿÿêäÎ&& ‘ ÿÿÿÿìéÎ&& ƒ ÿÿÿêÔÎ&& ‘ )ÿÿÿêÞÎ&& ƒ >ÿÿÿëÞÎ& & Š SÿÿÿëÐÎ&& ƒ hÿÿÿíÕÎ& & ˜ }ÿÿÿðØÎ&& ƒ ’ÿÿÿçÜÎ& & Š §ÿÿÿëÐÎ&ò ŸÿÿÿëÐÎ&& ­ …ÿÿÿëÐÎ&& ­ šÿÿÿêëÎ&& ­ ¯ÿÿÿíÕÎ&û& ¦ ÄÿÿÿéãÎ&& ­ ÙÿÿÿçßÎ& &  îÿÿÿìÕÎ&& ­ ÿÿÿíÕÎ& &  ÿÿÿëÐÎ& &  -ÿÿÿíÐÎ& &  BÿÿÿìÐÎ& &  WÿÿÿìéÎ& &  lÿÿÿîÔÎ& &  ÿÿÿîÕÎ& &  –ÿÿÿçÛÎ& &  «ÿÿÿïÐÎ&& ­ ÀÿÿÿîÐÎ&& ­ ÕÿÿÿêéÎ& &  êÿÿÿêäÎ&& » ÿÿÿÿìéÎ&& ­ ÿÿÿêÔÎ&& » )ÿÿÿêÞÎ&& ­ >ÿÿÿëÞÎ& & ´ SÿÿÿëÐÎ&& ­ hÿÿÿíÕÎ& &  }ÿÿÿðØÎ&& ­ ’ÿÿÿçÜÎ& & ´ §ÿÿÿëÑÎ&ð ÉÿÿÿëÐÎ&& × „ÿÿÿëÐÎ&& × ™ÿÿÿêëÎ&& × ®ÿÿÿíÕÎ&ù& Ð ÃÿÿÿéãÎ&& × ØÿÿÿçßÎ& & ì íÿÿÿìÕÎ&& × ÿÿÿíÕÎ& & ì ÿÿÿëÐÎ& & ì ,ÿÿÿíÐÎ& & ì AÿÿÿìÐÎ& & ì VÿÿÿìéÎ& & ì kÿÿÿîÔÎ& & ì €ÿÿÿîÕÎ& & ì •ÿÿÿèÛÎ& & ì ªÿÿÿïÐÎ&& × ¿ÿÿÿîÐÎ&& × ÔÿÿÿêéÎ& & ì éÿÿÿêãÎ&& å þÿÿÿìéÎ&& × ÿÿÿêÔÎ&& å (ÿÿÿêÞÎ&& × =ÿÿÿëÞÎ& & Þ RÿÿÿëÐÎ&& × gÿÿÿíÕÎ& & ì |ÿÿÿðØÎ&& × ‘ÿÿÿç×Î& & Þ ¦ÿÿÿíëÉ&î óÿÿÿëëÌ&&  {ÿÿÿëëÌ&&  ÿÿÿêëÌ&&  ¥ÿÿÿðëÌ&÷& ú ºÿÿÿéëÌ&&  ÏÿÿÿçëÌ& &  äÿÿÿïëÌ&&  ùÿÿÿíëÌ& &  ÿÿÿëëÌ& &  #ÿÿÿíëÌ& &  8ÿÿÿðëÌ& &  MÿÿÿìëÌ& &  bÿÿÿíëÌ& &  wÿÿÿíëÌ& &  ŒÿÿÿçëÌ& &  ¡ÿÿÿïëÌ&&  ¶ÿÿÿîëÌ&&  ËÿÿÿèìÌ& &  àÿÿÿèëÌ&&  õÿÿÿéëÌ&&  ÿÿÿçëÌ&&  ÿÿÿçëÌ&&  4ÿÿÿçëÌ& &  IÿÿÿëëÌ&&  ^ÿÿÿìëÌ& &  sÿÿÿíëÌ&&  ˆÿÿÿçëÌ& &  ÿÿ ë¿&í ÿÿÿëëÉ&ÿ& * {ÿÿÿëëÉ&ÿ& * ÿÿÿêëÉ&ÿ& * ¥ÿÿÿðëÉ& º& #öÿÿÿéëÉ&ÿ& * ÏÿÿÿçëÌ&& ? äÿÿÿïëÉ&ÿ& * ùÿÿÿíëÌ&& ? ÿÿÿëëÌ&& ? #ÿÿÿíëÌ&& ? 8ÿÿÿðëÌ&& ? MÿÿÿìëÌ&& ? bÿÿÿíëÌ&& ? wÿÿÿíëÌ&& ? ŒÿÿÿçëÌ&& ? ¡ÿÿÿïëÉ&ÿ& * ¶ÿÿÿîëÉ&ÿ& * ËÿÿÿèìÌ&& ? àÿÿÿèëÉ&ÿ& 8 õÿÿÿéëÉ&ÿ& * ÿÿÿçëÉ&ÿ& 8 ÿÿÿçëÉ&ÿ& * 4ÿÿÿçëÌ&& 1 IÿÿÿëëÉ&ÿ& * ^ÿÿÿìëÌ&& ? sÿÿÿíëÉ&ÿ& * ˆÿÿÿçëÌ&& 1 ÿÿÿëÐÎ&ï EÿÿÿëÐÎ&& S ƒÿÿÿëÐÎ&& S ˜ÿÿÿêëÎ&& S ­ÿÿÿíÕÎ&ø& L ÂÿÿÿéãÎ&& S ×ÿÿÿçßÎ& & h ìÿÿÿíÕÎ&& S ÿÿÿíÕÎ& & h ÿÿÿëÐÎ& & h +ÿÿÿíÐÎ& & h @ÿÿÿìÐÎ& & h UÿÿÿìéÎ& & h jÿÿÿîÔÎ& & h ÿÿÿíÕÎ& & h ”ÿÿÿèÛÎ& & h ©ÿÿÿïÐÎ&& S ¾ÿÿÿîÐÎ&& S ÓÿÿÿêéÎ& & h èÿÿÿêãÎ&& a ýÿÿÿìéÎ&& S ÿÿÿêÔÎ&& a 'ÿÿÿëâÎ&& S <ÿÿÿëáÎ& & Z QÿÿÿëÐÎ&& S fÿÿÿíÕÎ& & h {ÿÿÿðØÎ&& S ÿÿÿçÜÎ& & Z ¥ÿÿÿëÐÎ&ê iÿÿÿëÐÎ&ü& o yÿÿÿëÐÎ&ü& o ŽÿÿÿèìÎ&ü& o £ÿÿÿðÕÎ&ó& l ¸ÿÿÿéãÎ&ü& o ÍÿÿÿçßÎ&& u âÿÿÿïÕÎ&ü& o ÷ÿÿÿíÕÎ&& u ÿÿÿëÐÎ&& u !ÿÿÿíÐÎ&& u 6ÿÿÿíÐÎ&& u KÿÿÿìéÎ&& u `ÿÿÿíÔÎ&& u uÿÿÿíÕÎ&& u ŠÿÿÿçÛÎ&& u ŸÿÿÿïÐÎ&ü& o ´ÿÿÿîÐÎ&ü& o ÉÿÿÿèìÎ&& u ÞÿÿÿèãÎ&ü& r óÿÿÿééÎ&ü& o ÿÿÿçÔÎ&ü& r ÿÿÿçÞÎ&ü& o 2ÿÿÿçÞÎ&& o GÿÿÿëÐÎ&ü& o \ÿÿÿìÕÎ&& u qÿÿÿíØÎ&ü& o †ÿÿÿçÖÎ&& o ›ÿÿÿëöÎ&UÿÿÿëõÎ& &[ xÿÿÿëõÎ& &[ ÿÿÿèõÎ& &[ ¢ÿÿÿðöÎ&&X ·ÿÿÿêõÎ& &[ ÌÿÿÿçõÎ&)&a áÿÿÿïõÎ& &[ öÿÿÿíõÎ&)&a ÿÿÿëõÎ&)&a ÿÿÿíõÎ&)&a 5ÿÿÿíõÎ&)&a JÿÿÿìõÎ&)&a _ÿÿÿíõÎ&)&a tÿÿÿíõÎ&)&a ‰ÿÿÿçõÎ&)&a žÿÿÿïõÎ& &[ ³ÿÿÿîõÎ& &[ ÈÿÿÿèõÎ&)&a ÝÿÿÿèöÎ& &^ òÿÿÿéõÎ& &[ ÿÿÿçöÎ& &^ ÿÿÿçõÎ& &[ 1ÿÿÿçõÎ&)&[ FÿÿÿëõÎ& &[ [ÿÿÿìõÎ&)&a pÿÿÿíõÎ& &[ …ÿÿÿçõÎ&)&[ šÿÿ ÿëÐÎ&dÿÿ ÿëÐÎ&"&j zÿÿ ÿëÐÎ&"&j ÿÿ ÿèìÎ&"&j ¤ÿÿ ÿðÕÎ&&g ¹ÿÿ ÿéãÎ&"&j Îÿÿ ÿçßÎ&+&p ãÿÿ ÿïÕÎ&"&j øÿÿ ÿíÕÎ&+&p ÿÿ ÿëÐÎ&+&p "ÿÿ ÿíÐÎ&+&p 7ÿÿ ÿíÐÎ&+&p Lÿÿ ÿìéÎ&+&p aÿÿ ÿíÔÎ&+&p vÿÿ ÿíÕÎ&+&p ‹ÿÿ ÿçÛÎ&+&p  ÿÿ ÿïÐÎ&"&j µÿÿ ÿîÐÎ&"&j Êÿÿ ÿèìÎ&+&p ßÿÿ ÿèàÎ&"&m ôÿÿ ÿééÎ&"&j ÿÿ ÿçÔÎ&"&m ÿÿ ÿçÞÎ&"&j 3ÿÿ ÿçÞÎ&+&j Hÿÿ ÿëÐÎ&"&j ]ÿÿ ÿìÕÎ&+&p rÿÿ ÿíØÎ&"&j ‡ÿÿ ÿçÜÎ&+&j œÿÿÿëöÎ&sÿÿÿëöÎ& &y xÿÿÿëöÎ& &y ÿÿÿèöÎ& &y ¢ÿÿÿðöÎ&&v ·ÿÿÿêöÎ& &y ÌÿÿÿçöÎ&)& áÿÿÿïöÎ& &y öÿÿÿíöÎ&)& ÿÿÿëöÎ&)& ÿÿÿíöÎ&)& 5ÿÿÿíöÎ&)& JÿÿÿìöÎ&)& _ÿÿÿíöÎ&)& tÿÿÿíöÎ&)& ‰ÿÿÿçöÎ&)& žÿÿÿïöÎ& &y ³ÿÿÿîöÎ& &y ÈÿÿÿèöÎ&)& ÝÿÿÿèöÎ& &| òÿÿÿéöÎ& &y ÿÿÿçöÎ& &| ÿÿÿçöÎ& &y 1ÿÿÿçöÎ&)&y FÿÿÿëöÎ& &y [ÿÿÿìöÎ&)& pÿÿÿíöÎ& &y …ÿÿÿçöÎ&)&y šÿÿ ÿëÐÎ&‚ÿÿ ÿëÐÎ&"&ˆ zÿÿ ÿëÐÎ&"&ˆ ÿÿ ÿèìÎ&"&ˆ ¤ÿÿ ÿðÕÎ&&… ¹ÿÿ ÿéãÎ&"&ˆ Îÿÿ ÿçßÎ&+&Ž ãÿÿ ÿïÕÎ&"&ˆ øÿÿ ÿíÕÎ&+&Ž ÿÿ ÿëÐÎ&+&Ž "ÿÿ ÿíÐÎ&+&Ž 7ÿÿ ÿíÐÎ&+&Ž Lÿÿ ÿìéÎ&+&Ž aÿÿ ÿíÔÎ&+&Ž vÿÿ ÿíÕÎ&+&Ž ‹ÿÿ ÿçÛÎ&+&Ž  ÿÿ ÿïÐÎ&"&ˆ µÿÿ ÿîÐÎ&"&ˆ Êÿÿ ÿèìÎ&+&Ž ßÿÿ ÿèàÎ&"&‹ ôÿÿ ÿééÎ&"&ˆ ÿÿ ÿçÔÎ&"&‹ ÿÿ ÿçÞÎ&"&ˆ 3ÿÿ ÿçÞÎ&+&ˆ Hÿÿ ÿëÐÎ&"&ˆ ]ÿÿ ÿìÕÎ&+&Ž rÿÿ ÿíØÎ&"&ˆ ‡ÿÿ ÿçÜÎ&+&ˆ œÿÿ ÿëÐÎ&–ÿÿ ÿëÐÎ&!&© yÿÿ ÿëÐÎ&!&© Žÿÿ ÿèìÎ&!&© £ÿÿ ÿðÕÎ&&Ÿ ¸ÿÿ ÿéãÎ&!&© Íÿÿ ÿçßÎ&*&³ âÿÿ ÿïÕÎ&!&© ÷ÿÿ ÿíÕÎ&*&³ ÿÿ ÿëÐÎ&*&³ !ÿÿ ÿíÐÎ&*&³ 6ÿÿ ÿíÐÎ&*&³ Kÿÿ ÿìéÎ&*&³ `ÿÿ ÿíÔÎ&*&³ uÿÿ ÿíÕÎ&*&³ Šÿÿ ÿçÛÎ&*&³ Ÿÿÿ ÿïÐÎ&!&© ´ÿÿ ÿîÐÎ&!&© Éÿÿ ÿèìÎ&*&³ Þÿÿ ÿèãÎ&!&½ óÿÿ ÿééÎ&!&© ÿÿ ÿçÔÎ&!&½ ÿÿ ÿçÞÎ&!&© 2ÿÿ ÿçÞÎ&*&³ Gÿÿ ÿëÐÎ&!&© \ÿÿ ÿìÕÎ&*&³ qÿÿ ÿíØÎ&!&© †ÿÿ ÿçÖÎ&*&³ ›ÿÿ ÿëÐÎ&Æÿÿ ÿëÐÎ&"&Ù zÿÿ ÿëÐÎ&"&Ù ÿÿ ÿèìÎ&"&Ù ¤ÿÿ ÿðÕÎ&&Ï ¹ÿÿ ÿéãÎ&"&Ù Îÿÿ ÿçßÎ&+&ã ãÿÿ ÿïÕÎ&"&Ù øÿÿ ÿíÕÎ&+&ã ÿÿ ÿëÐÎ&+&ã "ÿÿ ÿíÐÎ&+&ã 7ÿÿ ÿíÐÎ&+&ã Lÿÿ ÿìéÎ&+&ã aÿÿ ÿíÔÎ&+&ã vÿÿ ÿíÕÎ&+&ã ‹ÿÿ ÿçÛÎ&+&ã  ÿÿ ÿïÐÎ&"&Ù µÿÿ ÿîÐÎ&"&Ù Êÿÿ ÿèìÎ&+&ã ßÿÿ ÿèàÎ&"&í ôÿÿ ÿééÎ&"&Ù ÿÿ ÿçÔÎ&"&í ÿÿ ÿçÞÎ&"&Ù 3ÿÿ ÿçÞÎ&+&ã Hÿÿ ÿëÐÎ&"&Ù ]ÿÿ ÿìÕÎ&+&ã rÿÿ ÿíØÎ&"&Ù ‡ÿÿ ÿçÜÎ&+&ã œÿÿ ÿëÐÎ&÷ÿÿ ÿëÐÎ&!&  yÿÿ ÿëÐÎ&!&  Žÿÿ ÿèìÎ&!&  £ÿÿ ÿðÕÎ&& ¸ ÿÿ ÿéãÎ&!&  Íÿÿ ÿçßÎ&*&  âÿÿ ÿïÕÎ&!&  ÷ÿÿ ÿíÕÎ&*&  ÿÿ ÿëÐÎ&*&  !ÿÿ ÿíÐÎ&*&  6ÿÿ ÿíÐÎ&*&  Kÿÿ ÿìéÎ&*&  `ÿÿ ÿíÔÎ&*&  uÿÿ ÿíÕÎ&*&  Šÿÿ ÿçÛÎ&*&  Ÿÿÿ ÿïÐÎ&!&  ´ÿÿ ÿîÐÎ&!&  Éÿÿ ÿèìÎ&*&  Þÿÿ ÿèãÎ&!&  óÿÿ ÿééÎ&!&  ÿÿ ÿçÔÎ& &! ÿÿ ÿçÞÎ&!&  2ÿÿ ÿçÞÎ&*&  Gÿÿ ÿëÐÎ&!&  \ÿÿ ÿìÕÎ&*&  qÿÿ ÿíØÎ&!&  †ÿÿ ÿçÖÎ&*&  ›ÿÿ ÿëÐÎ& &ÿÿ ÿëÐÎ&"& : zÿÿ ÿëÐÎ&"& : ÿÿ ÿèìÎ&"& : ¤ÿÿ ÿðÕÎ&& 0 ¹ÿÿ ÿéãÎ&"& : Îÿÿ ÿçßÎ&+& D ãÿÿ ÿïÕÎ&"& : øÿÿ ÿíÕÎ&+& D ÿÿ ÿëÐÎ&+& D "ÿÿ ÿíÐÎ&+& D 7ÿÿ ÿíÐÎ&+& D Lÿÿ ÿìéÎ&+& D aÿÿ ÿíÔÎ&+& D vÿÿ ÿíÕÎ&+& D ‹ÿÿ ÿçÛÎ&+& D  ÿÿ ÿïÐÎ&"& : µÿÿ ÿîÐÎ&"& : Êÿÿ ÿèìÎ&+& D ßÿÿ ÿèàÎ&"& N ôÿÿ ÿééÎ&"& : ÿÿ ÿçÔÎ&"& N ÿÿ ÿçÞÎ&"& : 3ÿÿ ÿçÞÎ&+& D Hÿÿ ÿëÐÎ&"& : ]ÿÿ ÿìÕÎ&+& D rÿÿ ÿíØÎ&"& : ‡ÿÿ ÿçÜÎ&+& D œÿÿ ê¾& WÿÿÿëêË&#& h {ÿÿÿëêË&#& h ÿÿÿêêË&#& h ¥ÿÿÿðêË&& ^ ºÿÿÿéêË&#& h ÏÿÿÿçêË&,& r äÿÿÿïêË&#& h ùÿÿÿíêË&,& r ÿÿÿëêË&,& r #ÿÿÿíêË&,& r 8ÿÿÿðêË&,& r MÿÿÿìêË&,& r bÿÿÿíêË&,& r wÿÿÿíêË&,& r ŒÿÿÿçêË&,& r ¡ÿÿÿïêË&#& h ¶ÿÿÿîêË&#& h ËÿÿÿèìË&,& r àÿÿÿèêË&#& { õÿÿÿéêË&#& h ÿÿÿçêË&#& { ÿÿÿçêË&#& h 4ÿÿÿçêË&,& r IÿÿÿëêË&#& h ^ÿÿÿìêË&,& r sÿÿÿíêË&#& h ˆÿÿÿçêË&,& r ÿÿÿëöÎ& …ÿÿÿëöÎ&%& ™ |ÿÿÿëöÎ&%& ™ ‘ÿÿÿêöÎ&%& ™ ¦ÿÿÿðöÎ&&  »ÿÿÿêöÎ&%& ™ ÐÿÿÿçöÎ&.& £ åÿÿÿïöÎ&%& ™ úÿÿÿíöÎ&.& £ ÿÿÿëöÎ&.& £ $ÿÿÿíöÎ&.& £ 9ÿÿÿíöÎ&.& £ NÿÿÿìöÎ&.& £ cÿÿÿîöÎ&.& £ xÿÿÿíöÎ&.& £ ÿÿÿçöÎ&.& £ ¢ÿÿÿïöÎ&%& ™ ·ÿÿÿîöÎ&%& ™ ÌÿÿÿèöÎ&.& £ áÿÿÿèöÎ&%& ­ öÿÿÿéöÎ&%& ™ ÿÿÿçöÎ&%& ­ ÿÿÿçöÎ&%& ™ 5ÿÿÿçöÎ&.& £ JÿÿÿëöÎ&%& ™ _ÿÿÿìöÎ&.& £ tÿÿÿíöÎ&%& ™ ‰ÿÿÿçöÎ&.& £ žÿÿÿëÐÎ& ·ÿÿÿëÐÎ&'& Ë |ÿÿÿëÐÎ&'& Ë ‘ÿÿÿêëÎ&'& Ë ¦ÿÿÿðÕÎ&& Á »ÿÿÿêãÎ&'& Ë ÐÿÿÿçßÎ&0& Õ åÿÿÿïÕÎ&'& Ë úÿÿÿíÕÎ&0& Õ ÿÿÿëÐÎ&0& Õ $ÿÿÿíÐÎ&0& Õ 9ÿÿÿíÐÎ&0& Õ NÿÿÿìéÎ&0& Õ cÿÿÿîÔÎ&0& Õ xÿÿÿíÕÎ&0& Õ ÿÿÿçÛÎ&0& Õ ¢ÿÿÿïÐÎ&'& Ë ·ÿÿÿîÐÎ&'& Ë ÌÿÿÿèéÎ&0& Õ áÿÿÿèãÎ&'& ß öÿÿÿééÎ&'& Ë ÿÿÿçÔÎ&'& ß ÿÿÿçâÎ&'& Ë 5ÿÿÿçâÎ&0& Õ JÿÿÿëÐÎ&'& Ë _ÿÿÿìÕÎ&0& Õ tÿÿÿíØÎ&'& Ë ‰ÿÿÿçÜÎ&0& Õ žÿÿÿëÐÎ& éÿÿÿëÐÎ&%& ü |ÿÿÿëÐÎ&%& ü ‘ÿÿÿêëÎ&%& ü ¦ÿÿÿðÕÎ&& ò »ÿÿÿêãÎ&%& ü ÐÿÿÿçßÎ&.&  åÿÿÿïÕÎ&%& ü úÿÿÿíÕÎ&.&  ÿÿÿëÐÎ&.&  $ÿÿÿíÐÎ&.&  9ÿÿÿíÐÎ&.&  NÿÿÿìéÎ&.&  cÿÿÿîÔÎ&.&  xÿÿÿíÕÎ&.&  ÿÿÿçÛÎ&.&  ¢ÿÿÿïÐÎ&%& ü ·ÿÿÿîÐÎ&%& ü ÌÿÿÿèéÎ&.&  áÿÿÿèãÎ&%&  öÿÿÿééÎ&%& ü ÿÿÿçÔÎ&%&  ÿÿÿçâÎ&%& ü 5ÿÿÿçâÎ&.&  JÿÿÿëÐÎ&%& ü _ÿÿÿìÕÎ&.&  tÿÿÿíØÎ&%& ü ‰ÿÿÿçÜÎ&.&  žÿÿ ë¾& ÿÿÿëëË&#& . {ÿÿÿëëË&#& . ÿÿÿêëË&#& . ¥ÿÿÿðëË&& $ ºÿÿÿéëË&#& . ÏÿÿÿçëË&,& 8 äÿÿÿïëË&#& . ùÿÿÿíëË&,& 8 ÿÿÿëëË&,& 8 #ÿÿÿíëË&,& 8 8ÿÿÿðëË&,& 8 MÿÿÿìëË&,& 8 bÿÿÿíëË&,& 8 wÿÿÿíëË&,& 8 ŒÿÿÿçëË&,& 8 ¡ÿÿÿïëË&#& . ¶ÿÿÿîëË&#& . ËÿÿÿèìË&,& 8 àÿÿÿèëË&#& B õÿÿÿéëË&#& . ÿÿÿçëË&#& B ÿÿÿçëË&#& . 4ÿÿÿçëË&,& 8 IÿÿÿëëË&#& . ^ÿÿÿìëË&,& 8 sÿÿÿíëË&#& . ˆÿÿÿçëË&,& 8 ÿÿÿëëË& KÿÿÿëëË&$& V {ÿÿÿëëË&$& V ÿÿÿêëË&$& V ¥ÿÿÿðëË&& Q ºÿÿÿéëË&$& V ÏÿÿÿçëË&-& k äÿÿÿïëË&$& V ùÿÿÿíëË&-& k ÿÿÿëëË&-& k #ÿÿÿíëË&-& k 8ÿÿÿðëË&-& k MÿÿÿìëË&-& k bÿÿÿíëË&-& k wÿÿÿíëË&-& k ŒÿÿÿçëË&-& k ¡ÿÿÿïëË&-& ¶ VÿÿÿîëË&$& V ËÿÿÿèìË&-& k àÿÿÿèëË&$& d õÿÿÿéëË&$& V ÿÿÿçëË&$& V ÿÿÿçëË&$& V 4ÿÿÿçëË&-& ] IÿÿÿëëË&$& V ^ÿÿÿìëË&-& k sÿÿÿíëË&$& V ˆÿÿÿçëË&-& ] ÿÿÿëÐÎ& rÿÿÿëÐÎ&&& € ~ÿÿÿëÐÎ&&& € “ÿÿÿêëÎ&&& € ¨ÿÿÿðÕÎ&& y ½ÿÿÿéãÎ&&& € ÒÿÿÿçßÎ&/& • çÿÿÿïÕÎ&&& € üÿÿÿíÕÎ&/& • ÿÿÿëÐÎ&/& • &ÿÿÿíÐÎ&/& • ;ÿÿÿíÐÎ&/& • PÿÿÿëéÎ&/& • eÿÿÿîÔÎ&/& • zÿÿÿíÖÎ&/& • ÿÿÿçÛÎ&/& • ¤ÿÿÿïÐÎ&&& € ¹ÿÿÿîÐÎ&&& € ÎÿÿÿêéÎ&/& • ãÿÿÿèãÎ&&& Ž øÿÿÿçéÎ&&& € ÿÿÿçÔÎ&&& Ž "ÿÿÿçÞÎ&&& € 7ÿÿÿíÞÎ&/& ‡ LÿÿÿëÐÎ&&& € aÿÿÿìÕÎ&/& • vÿÿÿíØÎ&&& € ‹ÿÿÿçÜÎ&/& ‡  ÿÿÿëÐÎ& œÿÿÿëÐÎ&(& ª ~ÿÿÿëÐÎ&(& ª “ÿÿÿêëÎ&(& ª ¨ÿÿÿðÕÎ&& £ ½ÿÿÿéãÎ&(& ª ÒÿÿÿçßÎ&1& ¿ çÿÿÿïÕÎ&(& ª üÿÿÿíÕÎ&1& ¿ ÿÿÿëÐÎ&1& ¿ &ÿÿÿíÐÎ&1& ¿ ;ÿÿÿíÐÎ&1& ¿ PÿÿÿëéÎ&1& ¿ eÿÿÿîÔÎ&1& ¿ zÿÿÿíÖÎ&1& ¿ ÿÿÿçÛÎ&1& ¿ ¤ÿÿÿïÐÎ&(& ª ¹ÿÿÿîÐÎ&(& ª ÎÿÿÿêéÎ&1& ¿ ãÿÿÿèãÎ&(& ¸ øÿÿÿçéÎ&(& ª ÿÿÿçÔÎ&(& ¸ "ÿÿÿçÞÎ&(& ª 7ÿÿÿíÞÎ&1& ± LÿÿÿëÐÎ&(& ª aÿÿÿìÕÎ&1& ¿ vÿÿÿíØÎ&(& ª ‹ÿÿÿçÜÎ&1& ±  ÿÿÿëÐÎ& ÆÿÿÿëÐÎ&&& Ô }ÿÿÿëÐÎ&&& Ô ’ÿÿÿêëÎ&&& Ô §ÿÿÿðÕÎ&& Í ¼ÿÿÿéãÎ&&& Ô ÑÿÿÿçßÎ&/& é æÿÿÿïÕÎ&&& Ô ûÿÿÿíÕÎ&/& é ÿÿÿëÐÎ&/& é %ÿÿÿíÐÎ&/& é :ÿÿÿíÐÎ&/& é OÿÿÿëèÎ&/& é dÿÿÿîÔÎ&/& é yÿÿÿíÖÎ&/& é ŽÿÿÿçÛÎ&/& é £ÿÿÿïÐÎ&&& Ô ¸ÿÿÿîÐÎ&&& Ô ÍÿÿÿêéÎ&/& é âÿÿÿèãÎ&&& â ÷ÿÿÿçéÎ&&& Ô ÿÿÿçÔÎ&&& â !ÿÿÿçÞÎ&&& Ô 6ÿÿÿçÞÎ&/& Û KÿÿÿëÐÎ&&& Ô `ÿÿÿìÕÎ&/& é uÿÿÿíØÎ&&& Ô ŠÿÿÿçÖÎ&/& Û ŸÿÿÿíëË& ðÿÿÿëëË&$& þ {ÿÿÿëëË&$& þ ÿÿÿêëË&$& þ ¥ÿÿÿðëË&& ÷ ºÿÿÿéëË&$& þ ÏÿÿÿçëË&-&  äÿÿÿïëË&$& þ ùÿÿÿíëË&-&  ÿÿÿëëË&-&  #ÿÿÿíëË&-&  8ÿÿÿðëË&-&  MÿÿÿìëË&-&  bÿÿÿíëË&-&  wÿÿÿíëË&-&  ŒÿÿÿçëË&-&  ¡ÿÿÿïëË&$& þ ¶ÿÿÿîëË&$& þ ËÿÿÿèìË&-&  àÿÿÿèëË&$&  õÿÿÿéëË&$& þ ÿÿÿçëË&$&  ÿÿÿçëË&$& þ 4ÿÿÿçëË&-&  IÿÿÿëëË&$& þ ^ÿÿÿìëË&-&  sÿÿÿíëË&$& þ ˆÿÿÿçëË&-&  ÿÿ ë¾& ÿÿÿëëË&#& ' {ÿÿÿëëË&#& ' ÿÿÿêëË&#& ' ¥ÿÿÿðëË&&  ºÿÿÿéëË&#& ' ÏÿÿÿçëË&,& < äÿÿÿïëË&#& ' ùÿÿÿíëË&,& < ÿÿÿëëË&,& < #ÿÿÿíëË&,& < 8ÿÿÿðëË&,& < MÿÿÿìëË&,& < bÿÿÿíëË&,& < wÿÿÿíëË&,& < ŒÿÿÿçëË&,& < ¡ÿÿÿïëË&#& ' ¶ÿÿÿîëË&#& ' ËÿÿÿèìË&,& < àÿÿÿèëË&#& 5 õÿÿÿéëË&#& ' ÿÿÿçëË&#& 5 ÿÿÿçëË&#& ' 4ÿÿÿçëË&,& . IÿÿÿëëË&#& ' ^ÿÿÿìëË&,& < sÿÿÿíëË&#& ' ˆÿÿÿçëË&,& . ÿÿÿëÐÎ& CÿÿÿëÐÎ&%& P |ÿÿÿëÐÎ&%& P ‘ÿÿÿêëÎ&%& P ¦ÿÿÿðÕÎ&& I »ÿÿÿêãÎ&%& P ÐÿÿÿçßÎ&.& e åÿÿÿïÕÎ&%& P úÿÿÿíÕÎ&.& e ÿÿÿëÐÎ&.& e $ÿÿÿíÐÎ&.& e 9ÿÿÿíÐÎ&.& e NÿÿÿìéÎ&.& e cÿÿÿîÔÎ&.& e xÿÿÿíÕÎ&.& e ÿÿÿçÛÎ&.& e ¢ÿÿÿïÐÎ&%& P ·ÿÿÿîÐÎ&%& P ÌÿÿÿèéÎ&.& e áÿÿÿèãÎ&%& ^ öÿÿÿééÎ&%& P ÿÿÿçÔÎ&%& ^ ÿÿÿçâÎ&%& P 5ÿÿÿçâÎ&.& W JÿÿÿëÐÎ&%& P _ÿÿÿìÕÎ&.& e tÿÿÿíØÎ&%& P ‰ÿÿÿçÜÎ&.& W žÿÿ ÿëÐÎ& iÿÿÿëÐÎ& & o yÿÿÿëÐÎ& & o ŽÿÿÿèìÎ& & o £ÿÿÿðÕÎ&& l ¸ÿÿÿéãÎ& & o ÍÿÿÿçßÎ&)& u âÿÿÿïÕÎ& & o ÷ÿÿÿíÕÎ&)&  uÿÿÿëÐÎ&)& u !ÿÿÿíÐÎ&)& u 6ÿÿÿíÐÎ&)& u KÿÿÿìéÎ&)& u `ÿÿÿíÔÎ&)& u uÿÿÿíÕÎ&)& u ŠÿÿÿçÛÎ&)& u ŸÿÿÿïÐÎ& & o ´ÿÿÿîÐÎ& & o ÉÿÿÿèìÎ&)& u ÞÿÿÿèãÎ& & r óÿÿÿééÎ& & o ÿÿÿçÔÎ& & r ÿÿÿçÞÎ& & o 2ÿÿÿçÞÎ&)& o GÿÿÿëÐÎ& & o \ÿÿÿìÕÎ&)& u qÿÿÿíØÎ& & o †ÿÿÿçÖÎ&)& o ›ÿÿÿëöÎ&U2ÿÿÿëõÎ&C&[ xÿÿÿëõÎ&C&[ ‘ÿÿÿêõÎ&C&[ ¦ÿÿÿðöÎ&;& »XÿÿÿêõÎ&C&[ ÐÿÿÿçõÎ&L&a åÿÿÿïõÎ&C&[ úÿÿÿíõÎ&a&L ÿÿÿëõÎ&L&a $ÿÿÿíõÎ&L&a 9ÿÿÿíõÎ&L&a NÿÿÿìõÎ&L&a cÿÿÿîõÎ&L&a xÿÿÿíõÎ&L&a ÿÿÿçõÎ&L&a ¢ÿÿÿïõÎ&\&C ·ÿÿÿîõÎ&C&\ ÈÿÿÿèõÎ&L&a áÿÿÿèöÎ&C&^ öÿÿÿéõÎ&C&[ ÿÿÿçöÎ&C&^ *ÿÿÿçõÎ&C&[ 5ÿÿÿçõÎ&L&[ JÿÿÿëõÎ&C&[ _ÿÿÿìõÎ&L&a tÿÿÿíõÎ&C&[ ‰ÿÿÿçõÎ&L&[ žÿÿÿëÐÎ&4dÿÿÿëÐÎ&j&E zÿÿÿëÐÎ&E&j “ÿÿÿêëÎ&E&j ¨ÿÿÿðÕÎ&g& ¹<ÿÿÿéãÎ&E&j ÒÿÿÿçßÎ&N&p çÿÿÿïÕÎ&E&j üÿÿÿíÕÎ&N&p ÿÿÿëÐÎ&N&p &ÿÿÿíÐÎ&N&p ;ÿÿÿíÐÎ&N&p PÿÿÿëéÎ&N&p eÿÿÿîÔÎ&N&p zÿÿÿíÖÎ&N&p ÿÿÿçÛÎ&N&p ¤ÿÿÿïÐÎ&E&j ¹ÿÿÿîÐÎ&E&j ÎÿÿÿêéÎ&N&p ãÿÿÿèãÎ&E&m øÿÿÿçéÎ&p&E ÿÿÿçÔÎ& "&EmÿÿÿçÞÎ&E&j 7ÿÿÿíÞÎ&N&j LÿÿÿëÐÎ&E&j aÿÿÿìÕÎ&N&p vÿÿÿíØÎ&E&j ‹ÿÿÿçÜÎ&N&j  ÿÿÿëöÎ&2sÿÿÿëöÎ&C&y |ÿÿÿëöÎ&C&y ‘ÿÿÿêöÎ&C&y ¦ÿÿÿðöÎ&;&v »ÿÿÿêöÎ&C&y ÐÿÿÿçöÎ&L& åÿÿÿïöÎ&C&y úÿÿÿíöÎ&L& ÿÿÿëöÎ&L& $ÿÿÿíöÎ&L& 9ÿÿÿíöÎ&L& NÿÿÿìöÎ&L& cÿÿÿîöÎ&L& xÿÿÿíöÎ&L& ÿÿÿçöÎ&L& ¢ÿÿÿïöÎ&C&y ·ÿÿÿîöÎ&C&y ÌÿÿÿèöÎ&L& áÿÿÿèöÎ&C&| öÿÿÿéöÎ&C&y ÿÿÿçöÎ&C& !|ÿÿÿçöÎ&C&y 5ÿÿÿçöÎ&L&y JÿÿÿëöÎ&C&y _ÿÿÿìöÎ&L& tÿÿÿíöÎ&C&y ‰ÿÿÿçöÎ&L&y žÿÿÿëÐÎ&4‚ÿÿÿëÐÎ&E&ˆ ~ÿÿÿëÐÎ&E&ˆ “ÿÿÿêëÎ&E&ˆ ¨ÿÿÿðÕÎ&<&… ½ÿÿÿéãÎ&E&ˆ ÒÿÿÿçßÎ&N&Ž çÿÿÿïÕÎ&E&ˆ üÿÿÿíÕÎ&N&Ž ÿÿÿëÐÎ&N&Ž &ÿÿÿíÐÎ&N&Ž ;ÿÿÿíÐÎ&N&Ž PÿÿÿëéÎ&N&Ž eÿÿÿîÔÎ&N&Ž zÿÿÿíÖÎ&N&Ž ÿÿÿçÛÎ&N&Ž ¤ÿÿÿïÐÎ&E&ˆ ¹ÿÿÿîÐÎ&E&ˆ ÎÿÿÿêéÎ&N&Ž ãÿÿÿèãÎ&E&‹ øÿÿÿçéÎ&E&ˆ ÿÿÿçÔÎ&E&‹ "ÿÿÿçÞÎ&E&ˆ 7ÿÿÿíÞÎ&N&ˆ LÿÿÿëÐÎ&E&ˆ aÿÿÿìÕÎ&N&Ž vÿÿÿíØÎ&E&ˆ ‹ÿÿÿçÜÎ&N&ˆ  ÿÿÿëÐÎ&3—ÿÿÿëÐÎ&D&ª }ÿÿÿëÐÎ&D&ª ’ÿÿÿêëÎ&D&ª §ÿÿÿðÕÎ&;&£ ¸ÿÿÿéãÎ&D&ª ÑÿÿÿçßÎ&M&´ æÿÿÿïÕÎ&D&ª ûÿÿÿíÕÎ&M&´ ÿÿÿëÐÎ&M&´ %ÿÿÿíÐÎ&M&´ :ÿÿÿíÐÎ&M&´ OÿÿÿëèÎ&M&´ dÿÿÿîÔÎ&M&´ yÿÿÿíÖÎ&M&´ ŽÿÿÿçÛÎ&M&´ £ÿÿÿïÐÎ&M&ª ´ÿÿÿîÐÎ&D&ª ÍÿÿÿêéÎ&M&´ âÿÿÿèãÎ& ö&¾DÿÿÿçéÎ&D&ª ÿÿÿçÔÎ&D&¾ !ÿÿÿçÞÎ&D&ª 6ÿÿÿçÞÎ&M&´ KÿÿÿëÐÎ&D&ª `ÿÿÿìÕÎ&M&´ uÿÿÿíØÎ&D&ª ŠÿÿÿçÖÎ&M&´ ŸÿÿÿëÐÎ&4ÇÿÿÿëÐÎ&E&Ú ~ÿÿÿëÐÎ&E&Ú “ÿÿÿêëÎ&E&Ú ¨ÿÿÿðÕÎ&<&Ð ½ÿÿÿéãÎ&E&Ú ÒÿÿÿçßÎ&N&ä çÿÿÿïÕÎ&E&Ú üÿÿÿíÕÎ&N&ä ÿÿÿëÐÎ&N&ä &ÿÿÿíÐÎ&N&ä ;ÿÿÿíÐÎ&N&ä PÿÿÿëéÎ&N&ä eÿÿÿîÔÎ&N&ä zÿÿÿíÖÎ&N&ä ÿÿÿçÛÎ&N&ä ¤ÿÿÿïÐÎ&E&Ú ¹ÿÿÿîÐÎ&E&Ú ÎÿÿÿêéÎ&N&ä ãÿÿÿèãÎ&E&î øÿÿÿçéÎ&E&Ú ÿÿÿçÔÎ&E&î "ÿÿÿçÞÎ&E&Ú 7ÿÿÿíÞÎ&N&ä LÿÿÿëÐÎ&E&Ú aÿÿÿìÕÎ&N&ä vÿÿÿíØÎ&E&Ú ‹ÿÿÿçÜÎ&N&ä  ÿÿÿëÐÎ&3øÿÿÿëÐÎ&D&  }ÿÿÿëÐÎ&D&  ’ÿÿÿêëÎ&D&  §ÿÿÿðÕÎ&;& » ÿÿÿéãÎ&D&  ÑÿÿÿçßÎ&M&  æÿÿÿïÕÎ&D&  ûÿÿÿíÕÎ&M&  ÿÿÿëÐÎ&M&  %ÿÿÿíÐÎ&M&  :ÿÿÿíÐÎ&M&  OÿÿÿëèÎ&M&  dÿÿÿîÔÎ&M&  yÿÿÿíÖÎ&M&  ŽÿÿÿçÛÎ&M&  £ÿÿÿïÐÎ&D&  ¸ÿÿÿîÐÎ& &D ÍÿÿÿêéÎ&M&  âÿÿÿèãÎ&D&  ÷ÿÿÿçéÎ&D&  ÿÿÿçÔÎ& &C ÿÿÿçÞÎ&D&  6ÿÿÿçÞÎ&M&  KÿÿÿëÐÎ&D&  `ÿÿÿìÕÎ&M&  uÿÿÿíØÎ&D&  ŠÿÿÿçÖÎ&M&  ŸÿÿÿëÐÎ&4 'ÿÿÿëÐÎ&E& ; ~ÿÿÿëÐÎ&E& ; “ÿÿÿêëÎ&E& ; ¨ÿÿÿðÕÎ&<& 1 ½ÿÿÿéãÎ&E& ; ÒÿÿÿçßÎ&N& E çÿÿÿïÕÎ&E& ; üÿÿÿíÕÎ&N& E ÿÿÿëÐÎ&N& E &ÿÿÿíÐÎ&N& E ;ÿÿÿíÐÎ&N& E PÿÿÿëéÎ&N& E eÿÿÿîÔÎ&N& E zÿÿÿíÖÎ&N& E ÿÿÿçÛÎ&N& E ¤ÿÿÿïÐÎ&E& ; ¹ÿÿÿîÐÎ&E& ; ÎÿÿÿêéÎ&N& E ãÿÿÿèãÎ&E& O øÿÿÿçéÎ&E& ; ÿÿÿçÔÎ&E& O "ÿÿÿçÞÎ&E& ; 7ÿÿÿíÞÎ&N& E LÿÿÿëÐÎ&E& ; aÿÿÿìÕÎ&N& E vÿÿÿíØÎ&E& ; ‹ÿÿÿçÜÎ&N& E  ÿÿ êÅ&5 XÿÿÿëêÏ&F& j ÿÿÿëêÏ&F& j ”ÿÿÿêêÏ&F& j ©ÿÿÿðêÏ&=& ` ¾ÿÿÿéêÏ&F& j ÓÿÿÿçêÏ&O& t èÿÿÿïêÏ&F& j ýÿÿÿíêÏ&O&  tÿÿÿëêÏ&O& t 'ÿÿÿîêÏ&O& t <ÿÿÿîêÏ&O& t QÿÿÿìêÏ&O& t fÿÿÿîêÏ&O& t {ÿÿÿìêÏ&O& t ÿÿÿçêÏ&O& t ¥ÿÿÿïêÏ&F& j ºÿÿÿîêÏ&F& j ÏÿÿÿêêÏ&O& t äÿÿÿèêÏ&F& } ùÿÿÿéêÏ&F& j ÿÿÿçêÏ&F& } #ÿÿÿçêÏ&F& j 8ÿÿÿçêÏ&O& t MÿÿÿëêÏ&F& j bÿÿÿíêÏ&O& t wÿÿÿíêÏ&F& j ŒÿÿÿçêÏ&O& t ¡ÿÿÿëöÎ& ‡7ÿÿÿëöÎ&H& › €ÿÿÿëöÎ&H& › •ÿÿÿêöÎ&H& › ªÿÿÿðöÎ&?& ¿ ‘ÿÿÿéöÎ&H& › ÔÿÿÿçöÎ&Q& ¥ éÿÿÿïöÎ&H& › þÿÿÿíöÎ& ›& QÿÿÿëöÎ&Q& ¥ (ÿÿÿíöÎ&Q& ¥ =ÿÿÿíöÎ&Q& ¥ RÿÿÿìöÎ&Q& ¥ gÿÿÿîöÎ&Q& ¥ |ÿÿÿîöÎ&Q& ¥ ‘ÿÿÿçöÎ&Q& ¥ ¦ÿÿÿïöÎ&H& › »ÿÿÿîöÎ&H& › ÐÿÿÿêöÎ&Q& ¥ åÿÿÿèöÎ&H& ¯ úÿÿÿéöÎ&H& › ÿÿÿçöÎ&H& ¯ $ÿÿÿçöÎ&H& › 9ÿÿÿçöÎ&Q& ¥ NÿÿÿëöÎ&H& › cÿÿÿíöÎ&Q& ¥ xÿÿÿíöÎ&H& › ÿÿÿçöÎ&Q& ¥ ¢ÿÿÿëÐÎ&9 ¹ÿÿÿëÐÎ&J& Í €ÿÿÿëÐÎ&J& Í •ÿÿÿêëÎ&J& Í ªÿÿÿðÕÎ&A& à ¿ÿÿÿéãÎ&J& Í ÔÿÿÿçßÎ&S& × éÿÿÿïÕÎ&J& Í þÿÿÿíÕÎ&S& × ÿÿÿëÐÎ&S& × (ÿÿÿíÐÎ&S& × =ÿÿÿíÐÎ&S& × RÿÿÿìéÎ&S& × gÿÿÿîÔÎ&S& × |ÿÿÿîÖÎ&S& × ‘ÿÿÿçÛÎ&S& × ¦ÿÿÿïÐÎ&J& Í »ÿÿÿîÐÎ&J& Í ÐÿÿÿêéÎ&S& × åÿÿÿèãÎ&J& á úÿÿÿééÎ&J& Í ÿÿÿçÔÎ&J& á $ÿÿÿçâÎ&J& Í 9ÿÿÿçáÎ&S& × NÿÿÿëÐÎ&J& Í cÿÿÿíÕÎ&S& × xÿÿÿíØÎ&J& Í ÿÿÿçÜÎ&S& × ¢ÿÿÿëÐÎ& ê7ÿÿÿëÐÎ&H& þ €ÿÿÿëÐÎ&H& þ •ÿÿÿêëÎ&H& þ ªÿÿÿðÕÎ&?& ô ¿ÿÿÿéãÎ&H& þ ÔÿÿÿçßÎ&Q&  éÿÿÿïÕÎ&H& þ þÿÿÿíÕÎ&Q&  ÿÿÿëÐÎ&Q&  (ÿÿÿíÐÎ&Q&  =ÿÿÿíÐÎ&Q&  RÿÿÿìéÎ&Q&  gÿÿÿîÔÎ&Q&  |ÿÿÿîÖÎ&Q&  ‘ÿÿÿçÛÎ&Q&  ¦ÿÿÿïÐÎ&H& þ »ÿÿÿîÐÎ&H& þ ÐÿÿÿêéÎ&Q&  åÿÿÿèãÎ&H&  úÿÿÿééÎ&H& þ ÿÿÿçÔÎ&H&  $ÿÿÿçâÎ&H& þ 9ÿÿÿçáÎ&Q&  NÿÿÿëÐÎ&H& þ cÿÿÿíÕÎ&Q&  xÿÿÿíØÎ&H& þ ÿÿÿçÜÎ&Q&  ¢ÿÿ ëÅ&5 ÿÿÿëëÏ&F& 0 ÿÿÿëëÏ&F& 0 ”ÿÿÿêëÏ&F& 0 ©ÿÿÿðëÏ&=& & ¾ÿÿÿéëÏ&F& 0 ÓÿÿÿçëÏ&O& : èÿÿÿïëÏ&F& 0 ýÿÿÿíëÏ&O& : ÿÿÿëëÏ&O& : 'ÿÿÿîëÏ&O& : <ÿÿÿîëÏ&O& : QÿÿÿìëÏ&O& : fÿÿÿîëÏ&O& : {ÿÿÿìëÏ&O& : ÿÿÿçëÏ&O& : ¥ÿÿÿïëÏ&F& 0 ºÿÿÿîëÏ&F& 0 ÏÿÿÿêëÏ&O& : äÿÿÿèëÏ&F& D ùÿÿÿéëÏ&F& 0 ÿÿÿçëÏ&F& D #ÿÿÿçëÏ&F& 0 8ÿÿÿçëÏ&O& : MÿÿÿëëÏ&F& 0 bÿÿÿíëÏ&O& : wÿÿÿíëÏ&F& 0 ŒÿÿÿçëÏ&O& : ¡ÿÿÿëëÏ&6 MÿÿÿëëÏ&G& W ÿÿÿëëÏ&G& W ”ÿÿÿêëÏ&G& W ©ÿÿÿðëÏ&>& R ºÿÿÿéëÏ&G& W ÓÿÿÿçëÏ&P& l èÿÿÿïëÏ&G& W ýÿÿÿíëÏ&P& l ÿÿÿëëÏ&P& l 'ÿÿÿîëÏ&P& l <ÿÿÿîëÏ&P& l QÿÿÿìëÏ&P& l fÿÿÿîëÏ&P& l {ÿÿÿìëÏ&P& l ÿÿÿçëÏ&P& l ¥ÿÿÿïëÏ&G& W ºÿÿÿîëÏ&G& W ÏÿÿÿêëÏ&P& l äÿÿÿèëÏ&G& e ùÿÿÿéëÏ&G& W ÿÿÿçëÏ&G& W #ÿÿÿçëÏ&G& W 8ÿÿÿçëÏ&P& ^ MÿÿÿëëÏ&G& W bÿÿÿíëÏ&P& l wÿÿÿíëÏ&G& W ŒÿÿÿçëÏ&P& ^ ¡ÿÿÿëÐÎ&8 sÿÿÿëÐÎ&I&  ‚ÿÿÿëÐÎ&I&  —ÿÿÿêëÎ&I&  ¬ÿÿÿðÕÎ&@& z ÁÿÿÿéãÎ&I&  ÖÿÿÿçßÎ&R& – ëÿÿÿïÕÎ&I&  ÿÿÿíÕÎ&R& – ÿÿÿëÐÎ&R& – *ÿÿÿíÐÎ&R& – ?ÿÿÿíÐÎ&R& – TÿÿÿìéÎ&R& – iÿÿÿîÔÎ&R& – ~ÿÿÿîÖÎ&R& – “ÿÿÿçÛÎ&R& – ¨ÿÿÿïÐÎ&I&  ½ÿÿÿîÐÎ&I&  ÒÿÿÿêéÎ&R& – çÿÿÿèãÎ&I&  üÿÿÿééÎ&I&  ÿÿÿçÔÎ&I&  &ÿÿÿçÞÎ&I&  ;ÿÿÿçÞÎ&R& ˆ PÿÿÿëÐÎ&I&  eÿÿÿíÕÎ&R& – zÿÿÿíØÎ&I&  ÿÿÿçÜÎ&R& ˆ ¤ÿÿÿëÐÎ&: ÿÿÿëÐÎ&K& « ‚ÿÿÿëÐÎ&K& « —ÿÿÿêëÎ&K& « ¬ÿÿÿðÕÎ&B& ¤ ÁÿÿÿéãÎ&K& « ÖÿÿÿçßÎ&T& À ëÿÿÿïÕÎ&K& « ÿÿÿíÕÎ&T& À ÿÿÿëÐÎ&T& À *ÿÿÿíÐÎ&T& À ?ÿÿÿíÐÎ&T& À TÿÿÿìéÎ&T& À iÿÿÿîÔÎ&T& À ~ÿÿÿîÖÎ&T& À “ÿÿÿçÛÎ&T& À ¨ÿÿÿïÐÎ&K& « ½ÿÿÿîÐÎ&K& « ÒÿÿÿêéÎ&T& À çÿÿÿèãÎ&K& ¹ üÿÿÿééÎ&K& « ÿÿÿçÔÎ&K& ¹ &ÿÿÿçÞÎ&K& « ;ÿÿÿçÞÎ&T& ² PÿÿÿëÐÎ&K& « eÿÿÿíÕÎ&T& À zÿÿÿíØÎ&K& « ÿÿÿçÜÎ&T& ² ¤ÿÿÿëÐÎ&8 ÇÿÿÿëÐÎ&I& Õ ÿÿÿëÐÎ&I& Õ –ÿÿÿêëÎ&I& Õ «ÿÿÿðÕÎ&@& Î ÀÿÿÿéãÎ&I& Õ ÕÿÿÿçßÎ&R& ê êÿÿÿïÕÎ&I& Õ ÿÿÿÿíÕÎ&R& ê ÿÿÿëÐÎ&R& ê )ÿÿÿíÐÎ&R& ê >ÿÿÿíÐÎ&R& ê SÿÿÿëéÎ&R& ê hÿÿÿîÔÎ&R& ê }ÿÿÿîÖÎ&R& ê ’ÿÿÿçÛÎ&R& ê §ÿÿÿïÐÎ&I& Õ ¼ÿÿÿîÐÎ&I& Õ ÑÿÿÿêéÎ&R& ê æÿÿÿèãÎ&I& ã ûÿÿÿééÎ&I& Õ ÿÿÿçÔÎ&I& ã %ÿÿÿçÞÎ&I& Õ :ÿÿÿçÞÎ&R& Ü OÿÿÿëÐÎ&I& Õ dÿÿÿíÕÎ&R& ê yÿÿÿíØÎ&I& Õ ŽÿÿÿçÖÎ&R& Ü £ÿÿÿíëÏ&6 ñÿÿÿëëÏ&G& ÿ ÿÿÿëëÏ&G& ÿ ”ÿÿÿêëÏ&G& ÿ ©ÿÿÿðëÏ&>& ø ¾ÿÿÿéëÏ&G& ÿ ÓÿÿÿçëÏ&P&  èÿÿÿïëÏ&G& ÿ ýÿÿÿíëÏ&P&  ÿÿÿëëÏ&P&  'ÿÿÿîëÏ&P&  <ÿÿÿîëÏ&P&  QÿÿÿìëÏ&P&  fÿÿÿîëÏ&P&  {ÿÿÿìëÏ&P&  ÿÿÿçëÏ&P&  ¥ÿÿÿïëÏ&G& ÿ ºÿÿÿîëÏ&G& ÿ ÏÿÿÿêëÏ&P&  äÿÿÿèëÏ&G&  ùÿÿÿéëÏ&G& ÿ ÿÿÿçëÏ&G&  #ÿÿÿçëÏ&G& ÿ 8ÿÿÿçëÏ&P&  MÿÿÿëëÏ&G& ÿ bÿÿÿíëÏ&P&  wÿÿÿíëÏ&G& ÿ ŒÿÿÿçëÏ&P&  ¡ÿÿ ëÅ&5 ÿÿÿëëÏ&F& ( ÿÿÿëëÏ&F& ( ”ÿÿÿêëÏ&F& ( ©ÿÿÿðëÏ&=& ! ¾ÿÿÿéëÏ&F& ( ÓÿÿÿçëÏ&O& = èÿÿÿïëÏ&F& ( ýÿÿÿíëÏ&O& = ÿÿÿëëÏ&O& = 'ÿÿÿîëÏ&O& = <ÿÿÿîëÏ&O& = QÿÿÿìëÏ&O& = fÿÿÿîëÏ&O& = {ÿÿÿìëÏ&O& = ÿÿÿçëÏ&O& = ¥ÿÿÿïëÏ&F& ( ºÿÿÿîëÏ&F& ( ÏÿÿÿêëÏ&O& = äÿÿÿèëÏ&F& 6 ùÿÿÿéëÏ&F& ( ÿÿÿçëÏ&F& 6 #ÿÿÿçëÏ&F& ( 8ÿÿÿçëÏ&O& / MÿÿÿëëÏ&F& ( bÿÿÿíëÏ&O& = wÿÿÿíëÏ&F& ( ŒÿÿÿçëÏ&O& / ¡ÿÿÿëÐÎ& E7ÿÿÿëÐÎ&H& Q €ÿÿÿëÐÎ&H& Q •ÿÿÿêëÎ&H& Q ªÿÿÿðÕÎ&?& J ¿ÿÿÿéãÎ&H& Q ÔÿÿÿçßÎ&Q& f éÿÿÿïÕÎ&H& Q þÿÿÿíÕÎ&Q& f ÿÿÿëÐÎ&Q& f (ÿÿÿíÐÎ&Q& f =ÿÿÿíÐÎ&Q& f RÿÿÿìéÎ&Q& f gÿÿÿîÔÎ&Q& f |ÿÿÿîÖÎ&Q& f ‘ÿÿÿçÛÎ&Q& f ¦ÿÿÿïÐÎ&H& Q »ÿÿÿîÐÎ&H& Q ÐÿÿÿêéÎ&Q& f åÿÿÿèãÎ&H& _ úÿÿÿééÎ&H& Q ÿÿÿçÔÎ&H& _ $ÿÿÿçâÎ&H& Q 9ÿÿÿçáÎ&Q& X NÿÿÿëÐÎ&H& Q cÿÿÿíÕÎ&Q& f xÿÿÿíØÎ&H& Q ÿÿÿçÜÎ&Q& X ¢ÿÿÿëÐÎ&3 iÿÿÿëÐÎ&C& o }ÿÿÿëÐÎ&C& o ’ÿÿÿêëÎ&C& o §ÿÿÿðÕÎ&;& l ¼ÿÿÿéãÎ&C& o ÑÿÿÿçßÎ&L& u æÿÿÿïÕÎ&C& o ûÿÿÿíÕÎ& u&M ÿÿÿëÐÎ&L& u %ÿÿÿíÐÎ&L& u :ÿÿÿíÐÎ&L& u OÿÿÿëèÎ&L& u dÿÿÿîÔÎ&L& u yÿÿÿíÖÎ&L& u ŽÿÿÿçÛÎ&L& u £ÿÿÿïÐÎ&M& ´ oÿÿÿîÐÎ&C& o ÍÿÿÿêéÎ&L& u âÿÿÿèãÎ&C& r ÷ÿÿÿçéÎ&C& o ÿÿÿçÔÎ&C& r !ÿÿÿçÞÎ&C& o 6ÿÿÿçÞÎ&L& o KÿÿÿëÐÎ&C& o `ÿÿÿìÕÎ&L& u uÿÿÿíØÎ&C& o ŠÿÿÿçÖÎ&L& o ŸÿÿÿîïÏ&eªÿÿÿèîÊ&*?Æÿÿ ÿéìÍ& ²?Çÿÿ ÿéóÏ& ?ÌÿÿÿèïÏ&*+& ± ÿÿ ÿéôÐ&¾?Îÿÿ ÿçõÏ&<& ª?Ïÿÿ ÿéïÊ&?ÑÿÿÿèñÏ&n&s?ÒÿÿÿêôÑ&L?Óÿÿ ÿèôÑ&Ø?Öÿÿ ÿéõÐ& ²aÿÿ ÿéõÐ&\% ÿÿ ÿéõÐ&\ÿÿ ÿçöÏ&Ñ&r?×ÿÿ ÿèôÐ&…?ØÿÿÿéôÐ&æ…ÿÿÿéõÒ&—?ÙÿÿÿéíÏ&-Á0²ÿÿ ÿéíÏ&-Á.ùÿÿÿèñÏ&n-Âÿÿ ÿèðÒ& ?Ûÿÿ ÿéöÏ&Œ?Üÿÿ ÿèóÏ&-Ã.ôÿÿ ÿîóÏ&n& ù?Ýÿÿ ÿèôÐ&-Ä?Þÿÿ ÿéóÏ&?ßÿÿ ÿè÷Ò&3-Åÿÿ ÿè÷Ò&-ÅÿÿÿéêÎ&°-Èÿÿ ÿè÷Ï& ²-Æÿÿ ÿèöÐ&o-ÇÿÿÿéðÏ&?áÿÿÿê÷Ñ&±?ãÿÿÿçîÐ&å?äÿÿ ÿéóÐ&.7?åÿÿÿéöÒ&)ø0²ÿÿ ÿêõÐ&Ë?æÿÿ ÿíòÏ&ž?èÿÿ ÿéõÐ&Š‹ÿÿ ÿéõÌ& À?êÿÿ ÿéóÑ&@?ëÿÿ ÿèòÑ&P?ìÿÿÿéóÏ&C?íÿÿÿëôÏ&Ñ?ðÿÿ ÿéøÏ& ?óÿÿ ÿìõÐ&$?ôÿÿ ÿçóÃ&M?õÿÿÿé÷Ñ&Ì?öÿÿÿêöÐ&-¤?÷ÿÿ ÿêîÇ&0œ?ùÿÿ ÿéõÐ&ÈÿÿÿèïË& ¼+#ÿÿ ÿìóÈ&„&$?úÿÿ ÿèñÊ&E?ûÿÿ ÿçôÐ& 1ÿÿ ÿçôÐ&.ô?üÿÿ ÿèðË&.ô?ýÿÿ ÿçõÏ&0›?ÿÿÿÿéòÆ&@ÿÿ ÿéîÏ& ï ðÿÿ ÿèíÐ&1+@ÿÿ ÿéöÑ&&ùÇÿÿ ÿèóÏ&-ÉÇÿÿ ÿèòÐ&-Ê-Rÿÿ ÿèõÏ&-Ë@ÿÿÿèôÏ&Ñ-ÌÿÿÿèòÏ&m-ÊÿÿÿèóÐ&Ë0…ÿÿ ÿèôÑ&Ø@ÿÿ ÿéñÏ&'Ü@ÿÿ ÿéòÑ&&ƒ@ÿÿ ÿîõÏ&[@ÿÿÿéñÏ&@ÿÿÿêòÏ&ügÿÿ ÿè÷Ï& í"äÿÿ ÿèôÐ&ö!„ÿÿ ÿéóÍ& ²& ÿÿ ÿèôÏ&-ÍæÿÿÿëêÐ&¯@ ÿÿÿïñÑ&@ ÿÿ ÿéòÏ&k@ ÿÿ ÿéôÏ& ²Áÿÿ ÿéôÑ& ¸ÿÿ ÿéñÐ&v@ÿÿ ÿéôÏ&-ÏÿÿÿéõÐ&ÿÿ ÿêòÎ&-ÎÇÿÿ ÿèôÐ&•&”@ÿÿ ÿéòÏ&,@ÿÿ ÿêóÐ&-Ð@ÿÿÿéïÏ& ó@ÿÿ ÿéòÐ& ²@ÿÿÿéòÈ&C->ÿÿÿèíÉ&@ÿÿ ÿéóÌ&*@ÿÿ ÿéòÏ& @ÿÿ ÿèôÇ&s@ ÿÿ ÿèôÊ&A@!ÿÿ ÿèõÐ& Ô@"ÿÿ ÿéóÏ&9-Üÿÿ ÿëõÒ&„@#ÿÿ ÿçôÐ&@$ÿÿ ÿäøË&Ý&.ô@%ÿÿ ÿéòÏ&ë@&ÿÿ ÿäøÌ& @'ÿÿÿéñÐ&·¸ÿÿ ÿéõÏ&l@+ÿÿ ÿéòÏ&¤@-ÿÿÿéæÏ&¢@.ÿÿÿéëÉ&*' ³ÿÿ ÿîõÐ&[@4ÿÿ ÿéõÑ&&è@5ÿÿ ÿè÷Ñ&&Å@6ÿÿ ÿéòÑ&i@7ÿÿ ÿè÷Ò&o-Åÿÿ ÿéôÐ&á@8ÿÿÿééÇ&@9ÿÿ ÿèóÏ&Œ&\@;ÿÿ ÿéôÏ&-Ñ@<ÿÿ ÿéôÏ&À-Òÿÿ ÿèôÏ&-Ò@>ÿÿ ÿéòÏ&-Ó@?ÿÿ ÿéòÏ&-Ó@@ÿÿ ÿéóÌ& À@Aÿÿ ÿîòÎ&@BÿÿÿéõÏ&!@Cÿÿ ÿéìÏ&¸&@Dÿÿ ÿêóÐ&-Ô@EÿÿÿêòÐ@Fÿÿ ÿé÷Ï&N1ÿÿ ÿîõÉ&§@Gÿÿ ÿéôÏ& ízÿÿ ÿèòÉ&:@Iÿÿ ÿéöÏ&-Õ@Jÿÿ ÿèôÑ&Ø@Kÿÿ ÿéôÏ&ë'ãÿÿ ÿèõÏ&l@Mÿÿ ÿë÷Ò&ƒ@Nÿÿ ÿéòË&ä&ú@PÿÿÿèöÇ&ÝÞÿÿ ÿéëÐ&›@Qÿÿ ÿéôÏ&-Ùºÿÿ ÿéïÏ& &[@Rÿÿ ÿçõÏ& ²-Öÿÿ ÿèìÐ&Ö@Sÿÿ ÿèõÏ&X-×ÿÿÿéåÑ&)ß@Tÿÿ ÿçõÎ&-Ø@ÿÿ ÿëóÎ&-Ú@Uÿÿ ÿéóÏ&„@Vÿÿ ÿèóÐ&<@XÿÿÿéòÏ&m@Yÿÿ ÿéøÏ& ì„ÿÿ ÿéôÏ&Œ ÿÿ ÿéøÏ&„ ²ÿÿ ÿéøÏ& Ô $ÿÿ ÿéôÐ&& ÿÿ ÿéóÏ&9@Zÿÿ ÿèôÇ&”@[ÿÿÿéóÏ&@\ÿÿÿéóÍ&m&Y@]ÿÿ ÿéæÐ&÷¢ÿÿ ÿèòÊ&E&F@^ÿÿ ÿæéÇ&I@_ÿÿ ÿéóÐ&~5ÿÿ ÿèôÎ&©&”@`ÿÿ ÿçñÍ& ²Éÿÿ ÿñóÄ&çÿÿÿéíÉ&Á@bÿÿ ÿèóÐ&ì@dÿÿÿðîÊ&ñóÿÿ ÿèíÍ& ²@gÿÿ ÿéóÏ&ë-Ûÿÿ ÿéóÎ&X-ÛÿÿÿéñÏ&n@iÿÿÿéôÎ&%'ÿÿ ÿéôÐ&†ÿÿ ÿéôÏ&*Û@jÿÿÿéõÏ&¸…ÿÿ ÿèöÏ&.ì.ôÿÿÿéòÏ&*Ü&@kÿÿ ÿóòÑ&Ô*Þÿÿ ÿéóÐ&*ß@lÿÿ ÿéöÏ&Kÿÿ ÿèóÑ& @mÿÿ ÿçóÏ& ëVÿÿ ÿéöÏ&m@oÿÿ ÿéëÏ&›@pÿÿ ÿéñÏ&ÃÿÿÿìíÐ&í@tÿÿ ÿéêÍ& ²©ÿÿ ÿéõÉ&ª@uÿÿ ÿéïÏ& Îÿÿ ÿèóÏ& Ã×ÿÿÿé÷Ò&m@vÿÿÿèñÒ& @{ÿÿ ÿéöË&Xµÿÿ ÿé÷Ë&¶@|ÿÿÿéóÏ&V*àÿÿÿéóÏ&0²@~ÿÿ ÿéöÒ&h@ÿÿ ÿèõÈ&Ñ&t&Ò@€ÿÿ ÿéñÐ&o@ÿÿ ÿîóË& ö@‚ÿÿ ÿêòÏ&3*áÿÿÿéïÐ&*âRÿÿÿéóÏ&*â¹ÿÿÿéîÇ&*â" ÿÿ ÿéóÐ&*ã@ƒÿÿ ÿèóÏ&*ä•ÿÿ ÿèñÏ&l@…ÿÿ ÿéöÏ&k@†ÿÿÿòóÏ& æÿÿ ÿëôÉ&u@‰ÿÿ ÿëôÏ&u@Šÿÿ ÿèñÏ&Š@‹ÿÿ ÿéôÏ&¨@Œÿÿ ÿéòÏ&¨@ÿÿÿèñÑ&Ý@Žÿÿ ÿéíÑ&ˆ@ÿÿÿéñÑ&@’ $ÿö,7 9 : ;< =FÿøGÿøHÿøJÿûRÿøTÿøWYZ\wÿëÿø€ÿøÿø‚ÿø†ÿø‡ÿø‰ÿø¢ÿø°ÿö $ÿö , 7 9 : ; < = Fÿø Gÿø Hÿø Jÿû Rÿø Tÿø W Y Z \ wÿë ÿø €ÿø ÿø ‚ÿø †ÿø ‡ÿø ‰ÿø ¢ÿø °ÿö -&ÿø*ÿø2ÿø4ÿø7ÿó8ÿû9ÿó:ÿö<ÿózÿø&ÿø*ÿø2ÿø4ÿø7ÿó8ÿû9ÿó:ÿö<ÿózÿø$ÿö$ ÿö$&ÿý$*ÿý$2ÿý$4ÿý$7ÿñ$8ÿý$9ÿø$:ÿû$<ÿó$zÿý$¡ÿû$#ÿö$%ÿö%7ÿý%9ÿý%;ÿý%<ÿý&& & &&ÿû&*ÿû&2ÿû&4ÿû&@&`&zÿû&#&%'ÿø'ÿø'$ÿý',ÿý'7ÿø'9ÿý':ÿý';ÿý'<ÿû'=ÿý'wÿø'°ÿý)) ) )ÿó)ÿó)")$ÿû)9):)<)@)`)wÿø)°ÿû)#)%,, ,&ÿý,*ÿý,2ÿý,4ÿý,zÿý,¡ÿý,#,%.. .&ÿû.*ÿû.2ÿû.4ÿû.zÿû.¡ÿû.#.%/ÿó/ ÿó/&ÿý/*ÿý/2ÿý/4ÿý/7ÿñ/8ÿý/9ÿö/:ÿø/<ÿó/zÿý/¡ÿý/#ÿó/%ÿó2ÿø2ÿø2$ÿý2,ÿý27ÿø29ÿû2:ÿý2;ÿû2<ÿû2=ÿý2wÿû2°ÿý3ÿæ3ÿæ3$ÿö3&ÿý3;ÿý3<ÿý3=ÿû3wÿî3°ÿö4ÿø4ÿø4$ÿý4,ÿý47ÿø49ÿû4:ÿý4;ÿû4<ÿû4=ÿý4wÿø4°ÿý57ÿý7 7 7ÿó7ÿó7"7$ÿñ7&ÿø7*ÿø72ÿø74ÿø76ÿý777Dÿñ7Fÿñ7Gÿñ7Hÿñ7Jÿó7Pÿö7Qÿö7Rÿñ7Sÿö7Tÿñ7Uÿö7Vÿñ7Xÿö7Yÿø7Zÿø7[ÿø7\ÿø7]ÿø7wÿî7zÿø7}ÿñ7~ÿñ7ÿñ7€ÿñ7ÿñ7‚ÿñ7†ÿñ7‡ÿñ7‰ÿñ7Šÿö7‹ÿö7Œÿö7¡ÿø7¢ÿñ7°ÿñ7# 7% 8ÿû8ÿû8$ÿý8wÿû8°ÿý9 9 9ÿó9ÿó9"9$ÿø9&ÿû9*ÿû92ÿû94ÿû9Dÿø9Fÿø9Gÿø9Hÿø9Jÿø9Pÿû9Qÿû9Rÿø9Sÿû9Tÿø9Uÿû9Vÿû9Xÿû9wÿñ9zÿû9}ÿø9~ÿø9ÿø9€ÿø9ÿø9‚ÿø9†ÿø9‡ÿø9‰ÿø9Šÿû9‹ÿû9Œÿû9¡ÿû9¢ÿø9°ÿø9# 9% : : :ÿö:ÿö:$ÿû:&ÿý:*ÿý:2ÿý:4ÿý:Dÿû:Fÿû:Gÿû:Hÿû:Jÿý:Pÿý:Qÿý:Rÿû:Sÿý:Tÿû:Uÿý:Vÿû:Xÿý:]ÿý:wÿö:zÿý:}ÿû:~ÿû:ÿû:€ÿû:ÿû:‚ÿû:†ÿû:‡ÿû:‰ÿû:Šÿý:‹ÿý:Œÿý:¡ÿý:¢ÿû:°ÿû:# :% ;; ;&ÿû;*ÿû;2ÿû;4ÿû;Fÿý;Gÿý;Hÿý;Rÿý;Tÿý;zÿû;ÿý;€ÿý;ÿý;‚ÿý;†ÿý;‡ÿý;‰ÿý;¡ÿû;¢ÿý;#;%< < <ÿó<ÿó<"<$ÿó<&ÿû<*ÿû<2ÿû<4ÿû<6ÿý<Dÿó<Fÿó<Gÿó<Hÿó<Jÿó<Pÿø<Qÿø<Rÿó<Sÿø<Tÿó<Uÿø<Vÿö<Xÿø<[ÿû<\ÿý<]ÿø<wÿî<zÿû<}ÿó<~ÿó<ÿó<€ÿó<ÿó<‚ÿó<†ÿó<‡ÿó<‰ÿó<Šÿø<‹ÿø<Œÿø<¡ÿû<¢ÿó<°ÿó<# <% == =&ÿý=*ÿý=2ÿý=4ÿý=zÿý=¡ÿû=#=%>-D ÿûE ÿûFF F#F%H ÿûI I IYIZI\I# I% JJ JJJ#J%K ÿøNN N#N%P ÿûQ ÿûR[ÿûR]ÿýU U UDÿýUJÿýU}ÿýU~ÿýU# U% VV V#V%W W WWW# W% YY YIY# Y% Z Z ZIZ# Z% [[ [Rÿû[ÿû[†ÿû[‡ÿû[‰ÿû[¢ÿû[#[%\\ \I\#\%]Rÿý]ÿý]†ÿý]‡ÿý]‰ÿý]¢ÿý^-x$ÿýx°ÿýzÿøzÿøz$ÿýz,ÿýz7ÿøz9ÿûz:ÿýz;ÿûz<ÿûz=ÿýzwÿûz°ÿý} ÿû~ ÿû[ÿû]ÿý€ÿó€ ÿûÿó ÿû‚ÿó‚ ÿû†ÿî† ÿî†Iÿû†[ÿû†]ÿý‡ÿî‡ ÿî‡Iÿû‡[ÿû‡]ÿý‰[ÿû‰]ÿýŠÿøŠ ÿø‹ÿø‹ ÿøŒÿøŒ ÿø›ÿó› ÿó›&ÿý›*ÿý›2ÿý›4ÿý›7ÿñ›8ÿý›9ÿö›:ÿø›<ÿó›zÿý›¡ÿý›#ÿó›%ÿó¢[ÿû¢]ÿý¯ÿó¯ ÿó¯&ÿý¯*ÿý¯2ÿý¯4ÿý¯7ÿñ¯8ÿý¯9ÿö¯:ÿø¯<ÿó¯zÿý¯¡ÿý¯#ÿó¯%ÿó°ÿö° ÿö°&ÿý°*ÿý°2ÿý°4ÿý°7ÿñ°8ÿý°9ÿø°:ÿû°<ÿó°zÿý°¡ÿû°#ÿö°%ÿö· · ·ÿó·ÿó·"·$ÿø·&ÿû·*ÿû·2ÿû·4ÿû·Dÿø·Fÿø·Gÿø·Hÿø·Jÿø·Pÿû·Qÿû·Rÿø·Sÿû·Tÿø·Uÿû·Vÿû·Xÿû·wÿñ·zÿû·}ÿø·~ÿø·ÿø·€ÿø·ÿø·‚ÿø·†ÿø·‡ÿø·‰ÿø·Šÿû·‹ÿû·Œÿû·¡ÿû·¢ÿø·°ÿø·# ·% ÇÇ ÇIÇ#Ç%ÉÉ É#É%Ë ÿûÍ Í ÍÍÍ# Í% ÎÎ Î#Î%ÏÏ ÏIÏ#Ï%ÑÑ ÑIÑ# Ñ% × × ×××# ×% Ù[ÿûÙ]ÿýÚÚ ÚIÚ#Ú%Û[ÿûÛ]ÿýÝÝ ÝÿûÝ&ÿýÝ-Ý2ÿýÝ4ÿýÝzÿýÝ#Ý% #% #% #% #% #% #% #%ÿó ÿû"$ÿö","7 "9 ": ";"< "="Fÿø"Gÿø"Hÿø"Jÿû"Rÿø"Tÿø"W"Y"Z"wÿë"ÿø"€ÿø"ÿø"‚ÿø"†ÿø"‡ÿø"‰ÿø"¢ÿø"°ÿö$$ÿö$,$7 $9 $: $;$< $=$Fÿø$Gÿø$Hÿø$Jÿû$Rÿø$Tÿø$W$Y$Z$wÿë$ÿø$€ÿø$ÿø$‚ÿø$†ÿø$‡ÿø$‰ÿø$¢ÿø$°ÿö ¢ d &d Š <˜ &d Ô "ì œ (ª Ò Ðî 8¾ \öDigitized data copyright Google Corporation © 2006Droid Sans FallbackRegularAscender - Droid Sans FallbackVersion 2.00DroidSansFallbackDroid is a trademark of Google and may be registered in certain jurisdictions.Ascender CorporationSteve MattesonDroid Sans is a humanist sans serif typeface designed for user interfaces and electronic communications.http://www.ascendercorp.com/http://www.ascendercorp.com/typedesigners.htmlÿì ÿÿrt-5.0.1/share/static/css/000755 000765 000024 00000000000 14005011336 016162 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/static/images/000755 000765 000024 00000000000 14005011336 016637 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/static/js/000755 000765 000024 00000000000 14005011336 016006 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/static/RichText/000755 000765 000024 00000000000 14005011336 017124 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/static/RichText/plugins/000755 000765 000024 00000000000 14005011336 020605 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/static/RichText/contents.css000644 000765 000024 00000006035 14005011336 021477 0ustar00sunnavystaff000000 000000 /* Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ body { /* Font */ /* Emoji fonts are added to visualise them nicely in Internet Explorer. */ font-family: sans-serif, Arial, Verdana, "Trebuchet MS", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 12px; /* Text color */ color: #333; /* Remove the background color to make it transparent. */ background-color: transparent; margin: 20px; } .cke_editable { font-size: 13px; line-height: 1.6; /* Fix for missing scrollbars with RTL texts. (#10488) */ word-wrap: break-word; } blockquote { font-style: italic; font-family: Georgia, Times, "Times New Roman", serif; padding: 2px 0; border-style: solid; border-color: #ccc; border-width: 0; } .cke_contents_ltr blockquote { padding-left: 20px; padding-right: 8px; border-left-width: 5px; } .cke_contents_rtl blockquote { padding-left: 8px; padding-right: 20px; border-right-width: 5px; } a { color: #0782C1; } ol,ul,dl { /* IE7: reset rtl list margin. (#7334) */ *margin-right: 0px; /* Preserved spaces for list items with text direction different than the list. (#6249,#8049)*/ padding: 0 40px; } h1,h2,h3,h4,h5,h6 { font-weight: normal; line-height: 1.2; } hr { border: 0px; border-top: 1px solid #ccc; } img.right { border: 1px solid #ccc; float: right; margin-left: 15px; padding: 5px; } img.left { border: 1px solid #ccc; float: left; margin-right: 15px; padding: 5px; } pre { white-space: pre-wrap; /* CSS 2.1 */ word-wrap: break-word; /* IE7 */ -moz-tab-size: 4; tab-size: 4; } .marker { background-color: Yellow; } span[lang] { font-style: italic; } figure { text-align: center; outline: solid 1px #ccc; background: rgba(0,0,0,0.05); padding: 10px; margin: 10px 20px; display: inline-block; } figure > figcaption { text-align: center; display: block; /* For IE8 */ } a > img { padding: 1px; margin: 1px; border: none; outline: 1px solid #0782C1; } /* Widget Styles */ .code-featured { border: 5px solid red; } .math-featured { padding: 20px; box-shadow: 0 0 2px rgba(200, 0, 0, 1); background-color: rgba(255, 0, 0, 0.05); margin: 10px; } .image-clean { border: 0; background: none; padding: 0; } .image-clean > figcaption { font-size: .9em; text-align: right; } .image-grayscale { background-color: white; color: #666; } .image-grayscale img, img.image-grayscale { filter: grayscale(100%); } .embed-240p { max-width: 426px; max-height: 240px; margin:0 auto; } .embed-360p { max-width: 640px; max-height: 360px; margin:0 auto; } .embed-480p { max-width: 854px; max-height: 480px; margin:0 auto; } .embed-720p { max-width: 1280px; max-height: 720px; margin:0 auto; } .embed-1080p { max-width: 1920px; max-height: 1080px; margin:0 auto; } rt-5.0.1/share/static/RichText/skins/000755 000765 000024 00000000000 14005011336 020253 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/static/RichText/config.js000644 000765 000024 00000003550 14005011336 020732 0ustar00sunnavystaff000000 000000 /** * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. * For licensing, see https://ckeditor.com/legal/ckeditor-oss-license */ CKEDITOR.editorConfig = function( config ) { // Define changes to default configuration here. // For complete reference see: // https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html config.toolbarGroups = [ { name: 'styles', groups: [ 'styles' ] }, { name: 'colors', groups: [ 'colors' ] }, { name: 'clipboard', groups: [ 'clipboard', 'undo' ] }, { name: 'editing', groups: [ 'find', 'selection', 'spellchecker', 'editing' ] }, { name: 'links', groups: [ 'links' ] }, { name: 'insert', groups: [ 'insert' ] }, { name: 'forms', groups: [ 'forms' ] }, { name: 'tools', groups: [ 'tools' ] }, { name: 'document', groups: [ 'mode', 'document', 'doctools' ] }, { name: 'others', groups: [ 'others' ] }, { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] }, { name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi', 'paragraph' ] }, { name: 'about', groups: [ 'about' ] } ]; config.removeButtons = 'Underline,Subscript,Superscript,About,Link,Image,HorizontalRule,SpecialChar,Source,DocProps,Unlink,Anchor,Strike,Cut,Copy,Outdent,Indent'; // Remove some buttons provided by the standard plugins, which are // not needed in the Standard(s) toolbar. // config.removeButtons = 'Underline,Subscript,Superscript'; // Set the most common block elements. config.format_tags = 'p;h1;h2;h3;pre'; // Simplify the dialog windows. config.removeDialogTabs = 'image:advanced;link:advanced'; if ( RT.Config.WebDefaultStylesheet.match(/dark/) ) { config.contentsCss = [ RT.Config.WebPath + '/static/RichText/contents.css', RT.Config.WebPath + '/static/RichText/contents-dark.css' ]; } }; rt-5.0.1/share/static/RichText/ckeditor.min.js000644 000765 000024 00002266661 14005011336 022073 0ustar00sunnavystaff000000 000000 /* Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ (function(){if(window.CKEDITOR&&window.CKEDITOR.dom)return;window.CKEDITOR||(window.CKEDITOR=function(){var a=/(^|.*[\\\/])ckeditor\.js(?:\?.*|;.*)?$/i,d={timestamp:"J8Q8",version:"4.13.0",revision:"83e9de8d6b",rnd:Math.floor(900*Math.random())+100,_:{pending:[],basePathSrcPattern:a},status:"unloaded",basePath:function(){var b=window.CKEDITOR_BASEPATH||"";if(!b)for(var c=document.getElementsByTagName("script"),d=0;de.getListenerIndex(d)){e=e.listeners;h||(h=this);isNaN(g)&&(g=10);var x=this;l.fn=d;l.priority=g;for(var t=e.length-1;0<=t;t--)if(e[t].priority<=g)return e.splice(t+1,0,l),{removeListener:u};e.unshift(l)}return{removeListener:u}}, once:function(){var a=Array.prototype.slice.call(arguments),b=a[1];a[1]=function(a){a.removeListener();return b.apply(this,arguments)};return this.on.apply(this,a)},capture:function(){CKEDITOR.event.useCapture=1;var a=this.on.apply(this,arguments);CKEDITOR.event.useCapture=0;return a},fire:function(){var a=0,b=function(){a=1},h=0,k=function(){h=1};return function(g,l,u){var e=d(this)[g];g=a;var x=h;a=h=0;if(e){var t=e.listeners;if(t.length)for(var t=t.slice(0),z,I=0;Idocument.documentMode),mobile:-1c||b.quirks);b.gecko&&(d=a.match(/rv:([\d\.]+)/))&&(d=d[1].split("."),c=1E4*d[0]+100*(d[1]||0)+1*(d[2]||0));b.air&&(c=parseFloat(a.match(/ adobeair\/(\d+)/)[1])); b.webkit&&(c=parseFloat(a.match(/ applewebkit\/(\d+)/)[1]));b.version=c;b.isCompatible=!(b.ie&&7>c)&&!(b.gecko&&4E4>c)&&!(b.webkit&&534>c);b.hidpi=2<=window.devicePixelRatio;b.needsBrFiller=b.gecko||b.webkit||b.ie&&10c;b.cssClass="cke_browser_"+(b.ie?"ie":b.gecko?"gecko":b.webkit?"webkit":"unknown");b.quirks&&(b.cssClass+=" cke_browser_quirks");b.ie&&(b.cssClass+=" cke_browser_ie"+(b.quirks?"6 cke_browser_iequirks":b.version));b.air&&(b.cssClass+=" cke_browser_air"); b.iOS&&(b.cssClass+=" cke_browser_ios");b.hidpi&&(b.cssClass+=" cke_hidpi");return b}()); "unloaded"==CKEDITOR.status&&function(){CKEDITOR.event.implementOn(CKEDITOR);CKEDITOR.loadFullCore=function(){if("basic_ready"!=CKEDITOR.status)CKEDITOR.loadFullCore._load=1;else{delete CKEDITOR.loadFullCore;var a=document.createElement("script");a.type="text/javascript";a.src=CKEDITOR.basePath+"ckeditor.js";document.getElementsByTagName("head")[0].appendChild(a)}};CKEDITOR.loadFullCoreTimeout=0;CKEDITOR.add=function(a){(this._.pending||(this._.pending=[])).push(a)};(function(){CKEDITOR.domReady(function(){var a= CKEDITOR.loadFullCore,d=CKEDITOR.loadFullCoreTimeout;a&&(CKEDITOR.status="basic_ready",a&&a._load?a():d&&setTimeout(function(){CKEDITOR.loadFullCore&&CKEDITOR.loadFullCore()},1E3*d))})})();CKEDITOR.status="basic_loaded"}();"use strict";CKEDITOR.VERBOSITY_WARN=1;CKEDITOR.VERBOSITY_ERROR=2;CKEDITOR.verbosity=CKEDITOR.VERBOSITY_WARN|CKEDITOR.VERBOSITY_ERROR;CKEDITOR.warn=function(a,d){CKEDITOR.verbosity&CKEDITOR.VERBOSITY_WARN&&CKEDITOR.fire("log",{type:"warn",errorCode:a,additionalData:d})}; CKEDITOR.error=function(a,d){CKEDITOR.verbosity&CKEDITOR.VERBOSITY_ERROR&&CKEDITOR.fire("log",{type:"error",errorCode:a,additionalData:d})}; CKEDITOR.on("log",function(a){if(window.console&&window.console.log){var d=console[a.data.type]?a.data.type:"log",b=a.data.errorCode;if(a=a.data.additionalData)console[d]("[CKEDITOR] Error code: "+b+".",a);else console[d]("[CKEDITOR] Error code: "+b+".");console[d]("[CKEDITOR] For more information about this error go to https://ckeditor.com/docs/ckeditor4/latest/guide/dev_errors.html#"+b)}},null,null,999);CKEDITOR.dom={}; (function(){function a(a,b,e){this._minInterval=a;this._context=e;this._lastOutput=this._scheduledTimer=0;this._output=CKEDITOR.tools.bind(b,e||{});var c=this;this.input=function(){function a(){c._lastOutput=(new Date).getTime();c._scheduledTimer=0;c._call()}if(!c._scheduledTimer||!1!==c._reschedule()){var x=(new Date).getTime()-c._lastOutput;x/g,k=/|\s) /g,function(a,b){return b+"\x26nbsp;"}).replace(/ (?=<)/g,"\x26nbsp;")},getNextNumber:function(){var a=0;return function(){return++a}}(),getNextId:function(){return"cke_"+this.getNextNumber()},getUniqueId:function(){for(var a="e",b=0;8>b;b++)a+=Math.floor(65536*(1+Math.random())).toString(16).substring(1);return a},override:function(a, b){var e=b(a);e.prototype=a.prototype;return e},setTimeout:function(a,b,e,c,g){g||(g=window);e||(e=g);return g.setTimeout(function(){c?a.apply(e,[].concat(c)):a.apply(e)},b||0)},throttle:function(a,b,e){return new this.buffers.throttle(a,b,e)},trim:function(){var a=/(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g;return function(b){return b.replace(a,"")}}(),ltrim:function(){var a=/^[ \t\n\r]+/g;return function(b){return b.replace(a,"")}}(),rtrim:function(){var a=/[ \t\n\r]+$/g;return function(b){return b.replace(a, "")}}(),indexOf:function(a,b){if("function"==typeof b)for(var e=0,c=a.length;eparseFloat(b);e&&(b=b.replace("-",""));a.setStyle("width",b);b=a.$.clientWidth;return e?-b:b}return b}}(),repeat:function(a,b){return Array(b+1).join(a)},tryThese:function(){for(var a,b=0,e=arguments.length;bb;b++)a[b]=("0"+parseInt(a[b],10).toString(16)).slice(-2);return"#"+a.join("")})},normalizeHex:function(a){return a.replace(/#(([0-9a-f]{3}){1,2})($|;|\s+)/gi,function(a,b,e,c){a=b.toLowerCase();3==a.length&& (a=a.split(""),a=[a[0],a[0],a[1],a[1],a[2],a[2]].join(""));return"#"+a+c})},parseCssText:function(a,b,e){var c={};e&&(a=(new CKEDITOR.dom.element("span")).setAttribute("style",a).getAttribute("style")||"");a&&(a=CKEDITOR.tools.normalizeHex(CKEDITOR.tools.convertRgbToHex(a)));if(!a||";"==a)return c;a.replace(/"/g,'"').replace(/\s*([^:;\s]+)\s*:\s*([^;]+)\s*(?=;|$)/g,function(a,e,x){b&&(e=e.toLowerCase(),"font-family"==e&&(x=x.replace(/\s*,\s*/g,",")),x=CKEDITOR.tools.trim(x));c[e]=x});return c}, writeCssText:function(a,b){var e,c=[];for(e in a)c.push(e+":"+a[e]);b&&c.sort();return c.join("; ")},objectCompare:function(a,b,e){var c;if(!a&&!b)return!0;if(!a||!b)return!1;for(c in a)if(a[c]!=b[c])return!1;if(!e)for(c in b)if(a[c]!=b[c])return!1;return!0},objectKeys:function(a){return CKEDITOR.tools.object.keys(a)},convertArrayToObject:function(a,b){var e={};1==arguments.length&&(b=!0);for(var c=0,g=a.length;ce;e++)a.push(Math.floor(256*Math.random()));for(e=0;em)for(l=m;3>l;l++)g[l]=0;d[0]=(g[0]&252)>>2;d[1]=(g[0]&3)<<4|g[1]>>4;d[2]=(g[1]&15)<<2|(g[2]&192)>>6;d[3]=g[2]&63;for(l=0;4>l;l++)b=l<=m? b+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(d[l]):b+"\x3d"}return b},style:{parse:{_colors:{aliceblue:"#F0F8FF",antiquewhite:"#FAEBD7",aqua:"#00FFFF",aquamarine:"#7FFFD4",azure:"#F0FFFF",beige:"#F5F5DC",bisque:"#FFE4C4",black:"#000000",blanchedalmond:"#FFEBCD",blue:"#0000FF",blueviolet:"#8A2BE2",brown:"#A52A2A",burlywood:"#DEB887",cadetblue:"#5F9EA0",chartreuse:"#7FFF00",chocolate:"#D2691E",coral:"#FF7F50",cornflowerblue:"#6495ED",cornsilk:"#FFF8DC",crimson:"#DC143C", cyan:"#00FFFF",darkblue:"#00008B",darkcyan:"#008B8B",darkgoldenrod:"#B8860B",darkgray:"#A9A9A9",darkgreen:"#006400",darkgrey:"#A9A9A9",darkkhaki:"#BDB76B",darkmagenta:"#8B008B",darkolivegreen:"#556B2F",darkorange:"#FF8C00",darkorchid:"#9932CC",darkred:"#8B0000",darksalmon:"#E9967A",darkseagreen:"#8FBC8F",darkslateblue:"#483D8B",darkslategray:"#2F4F4F",darkslategrey:"#2F4F4F",darkturquoise:"#00CED1",darkviolet:"#9400D3",deeppink:"#FF1493",deepskyblue:"#00BFFF",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1E90FF", firebrick:"#B22222",floralwhite:"#FFFAF0",forestgreen:"#228B22",fuchsia:"#FF00FF",gainsboro:"#DCDCDC",ghostwhite:"#F8F8FF",gold:"#FFD700",goldenrod:"#DAA520",gray:"#808080",green:"#008000",greenyellow:"#ADFF2F",grey:"#808080",honeydew:"#F0FFF0",hotpink:"#FF69B4",indianred:"#CD5C5C",indigo:"#4B0082",ivory:"#FFFFF0",khaki:"#F0E68C",lavender:"#E6E6FA",lavenderblush:"#FFF0F5",lawngreen:"#7CFC00",lemonchiffon:"#FFFACD",lightblue:"#ADD8E6",lightcoral:"#F08080",lightcyan:"#E0FFFF",lightgoldenrodyellow:"#FAFAD2", lightgray:"#D3D3D3",lightgreen:"#90EE90",lightgrey:"#D3D3D3",lightpink:"#FFB6C1",lightsalmon:"#FFA07A",lightseagreen:"#20B2AA",lightskyblue:"#87CEFA",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#B0C4DE",lightyellow:"#FFFFE0",lime:"#00FF00",limegreen:"#32CD32",linen:"#FAF0E6",magenta:"#FF00FF",maroon:"#800000",mediumaquamarine:"#66CDAA",mediumblue:"#0000CD",mediumorchid:"#BA55D3",mediumpurple:"#9370DB",mediumseagreen:"#3CB371",mediumslateblue:"#7B68EE",mediumspringgreen:"#00FA9A", mediumturquoise:"#48D1CC",mediumvioletred:"#C71585",midnightblue:"#191970",mintcream:"#F5FFFA",mistyrose:"#FFE4E1",moccasin:"#FFE4B5",navajowhite:"#FFDEAD",navy:"#000080",oldlace:"#FDF5E6",olive:"#808000",olivedrab:"#6B8E23",orange:"#FFA500",orangered:"#FF4500",orchid:"#DA70D6",palegoldenrod:"#EEE8AA",palegreen:"#98FB98",paleturquoise:"#AFEEEE",palevioletred:"#DB7093",papayawhip:"#FFEFD5",peachpuff:"#FFDAB9",peru:"#CD853F",pink:"#FFC0CB",plum:"#DDA0DD",powderblue:"#B0E0E6",purple:"#800080",rebeccapurple:"#663399", red:"#FF0000",rosybrown:"#BC8F8F",royalblue:"#4169E1",saddlebrown:"#8B4513",salmon:"#FA8072",sandybrown:"#F4A460",seagreen:"#2E8B57",seashell:"#FFF5EE",sienna:"#A0522D",silver:"#C0C0C0",skyblue:"#87CEEB",slateblue:"#6A5ACD",slategray:"#708090",slategrey:"#708090",snow:"#FFFAFA",springgreen:"#00FF7F",steelblue:"#4682B4",tan:"#D2B48C",teal:"#008080",thistle:"#D8BFD8",tomato:"#FF6347",turquoise:"#40E0D0",violet:"#EE82EE",windowtext:"windowtext",wheat:"#F5DEB3",white:"#FFFFFF",whitesmoke:"#F5F5F5",yellow:"#FFFF00", yellowgreen:"#9ACD32"},_borderStyle:"none hidden dotted dashed solid double groove ridge inset outset".split(" "),_widthRegExp:/^(thin|medium|thick|[\+-]?\d+(\.\d+)?[a-z%]+|[\+-]?0+(\.0+)?|\.\d+[a-z%]+)$/,_rgbaRegExp:/rgba?\(\s*\d+%?\s*,\s*\d+%?\s*,\s*\d+%?\s*(?:,\s*[0-9.]+\s*)?\)/gi,_hslaRegExp:/hsla?\(\s*[0-9.]+\s*,\s*\d+%\s*,\s*\d+%\s*(?:,\s*[0-9.]+\s*)?\)/gi,background:function(a){var b={},e=this._findColor(a);e.length&&(b.color=e[0],CKEDITOR.tools.array.forEach(e,function(b){a=a.replace(b,"")})); if(a=CKEDITOR.tools.trim(a))b.unprocessed=a;return b},margin:function(a){return CKEDITOR.tools.style.parse.sideShorthand(a,function(a){return a.match(/(?:\-?[\.\d]+(?:%|\w*)|auto|inherit|initial|unset|revert)/g)||["0px"]})},sideShorthand:function(a,b){function e(a){c.top=g[a[0]];c.right=g[a[1]];c.bottom=g[a[2]];c.left=g[a[3]]}var c={},g=b?b(a):a.split(/\s+/);switch(g.length){case 1:e([0,0,0,0]);break;case 2:e([0,1,0,1]);break;case 3:e([0,1,2,1]);break;case 4:e([0,1,2,3])}return c},border:function(a){return CKEDITOR.tools.style.border.fromCssRule(a)}, _findColor:function(a){var b=[],e=CKEDITOR.tools.array,b=b.concat(a.match(this._rgbaRegExp)||[]),b=b.concat(a.match(this._hslaRegExp)||[]);return b=b.concat(e.filter(a.split(/\s+/),function(a){return a.match(/^\#[a-f0-9]{3}(?:[a-f0-9]{3})?$/gi)?!0:a.toLowerCase()in CKEDITOR.tools.style.parse._colors}))}}},array:{filter:function(a,b,e){var c=[];this.forEach(a,function(g,m){b.call(e,g,m,a)&&c.push(g)});return c},find:function(a,b,e){for(var c=a.length,g=0;gCKEDITOR.env.version&&(!a||"object"!==typeof a)){b=[];if("string"===typeof a)for(e=0;eCKEDITOR.env.version)for(g=0;gCKEDITOR.env.version&&(this.type==CKEDITOR.NODE_ELEMENT||this.type==CKEDITOR.NODE_DOCUMENT_FRAGMENT)&&c(f);return f},hasPrevious:function(){return!!this.$.previousSibling},hasNext:function(){return!!this.$.nextSibling},insertAfter:function(a){a.$.parentNode.insertBefore(this.$,a.$.nextSibling);return a},insertBefore:function(a){a.$.parentNode.insertBefore(this.$, a.$);return a},insertBeforeMe:function(a){this.$.parentNode.insertBefore(a.$,this.$);return a},getAddress:function(a){for(var d=[],b=this.getDocument().$.documentElement,c=this;c&&c!=b;){var f=c.getParent();f&&d.unshift(this.getIndex.call(c,a));c=f}return d},getDocument:function(){return new CKEDITOR.dom.document(this.$.ownerDocument||this.$.parentNode.ownerDocument)},getIndex:function(a){function d(a,b){var c=b?a.getNext():a.getPrevious();return c&&c.type==CKEDITOR.NODE_TEXT?c.isEmpty()?d(c,b):c: null}var b=this,c=-1,f;if(!this.getParent()||a&&b.type==CKEDITOR.NODE_TEXT&&b.isEmpty()&&!d(b)&&!d(b,!0))return-1;do if(!a||b.equals(this)||b.type!=CKEDITOR.NODE_TEXT||!f&&!b.isEmpty())c++,f=b.type==CKEDITOR.NODE_TEXT;while(b=b.getPrevious());return c},getNextSourceNode:function(a,d,b){if(b&&!b.call){var c=b;b=function(a){return!a.equals(c)}}a=!a&&this.getFirst&&this.getFirst();var f;if(!a){if(this.type==CKEDITOR.NODE_ELEMENT&&b&&!1===b(this,!0))return null;a=this.getNext()}for(;!a&&(f=(f||this).getParent());){if(b&& !1===b(f,!0))return null;a=f.getNext()}return!a||b&&!1===b(a)?null:d&&d!=a.type?a.getNextSourceNode(!1,d,b):a},getPreviousSourceNode:function(a,d,b){if(b&&!b.call){var c=b;b=function(a){return!a.equals(c)}}a=!a&&this.getLast&&this.getLast();var f;if(!a){if(this.type==CKEDITOR.NODE_ELEMENT&&b&&!1===b(this,!0))return null;a=this.getPrevious()}for(;!a&&(f=(f||this).getParent());){if(b&&!1===b(f,!0))return null;a=f.getPrevious()}return!a||b&&!1===b(a)?null:d&&a.type!=d?a.getPreviousSourceNode(!1,d,b): a},getPrevious:function(a){var d=this.$,b;do b=(d=d.previousSibling)&&10!=d.nodeType&&new CKEDITOR.dom.node(d);while(b&&a&&!a(b));return b},getNext:function(a){var d=this.$,b;do b=(d=d.nextSibling)&&new CKEDITOR.dom.node(d);while(b&&a&&!a(b));return b},getParent:function(a){var d=this.$.parentNode;return d&&(d.nodeType==CKEDITOR.NODE_ELEMENT||a&&d.nodeType==CKEDITOR.NODE_DOCUMENT_FRAGMENT)?new CKEDITOR.dom.node(d):null},getParents:function(a){var d=this,b=[];do b[a?"push":"unshift"](d);while(d=d.getParent()); return b},getCommonAncestor:function(a){if(a.equals(this))return this;if(a.contains&&a.contains(this))return a;var d=this.contains?this:this.getParent();do if(d.contains(a))return d;while(d=d.getParent());return null},getPosition:function(a){var d=this.$,b=a.$;if(d.compareDocumentPosition)return d.compareDocumentPosition(b);if(d==b)return CKEDITOR.POSITION_IDENTICAL;if(this.type==CKEDITOR.NODE_ELEMENT&&a.type==CKEDITOR.NODE_ELEMENT){if(d.contains){if(d.contains(b))return CKEDITOR.POSITION_CONTAINS+ CKEDITOR.POSITION_PRECEDING;if(b.contains(d))return CKEDITOR.POSITION_IS_CONTAINED+CKEDITOR.POSITION_FOLLOWING}if("sourceIndex"in d)return 0>d.sourceIndex||0>b.sourceIndex?CKEDITOR.POSITION_DISCONNECTED:d.sourceIndex=document.documentMode||!d||(a=d+":"+a);return new CKEDITOR.dom.nodeList(this.$.getElementsByTagName(a))},getHead:function(){var a=this.$.getElementsByTagName("head")[0]; return a=a?new CKEDITOR.dom.element(a):this.getDocumentElement().append(new CKEDITOR.dom.element("head"),!0)},getBody:function(){return new CKEDITOR.dom.element(this.$.body)},getDocumentElement:function(){return new CKEDITOR.dom.element(this.$.documentElement)},getWindow:function(){return new CKEDITOR.dom.window(this.$.parentWindow||this.$.defaultView)},write:function(a){this.$.open("text/html","replace");CKEDITOR.env.ie&&(a=a.replace(/(?:^\s*]*?>)|^/i,'$\x26\n\x3cscript data-cke-temp\x3d"1"\x3e('+ CKEDITOR.tools.fixDomain+")();\x3c/script\x3e"));this.$.write(a);this.$.close()},find:function(a){return new CKEDITOR.dom.nodeList(this.$.querySelectorAll(a))},findOne:function(a){return(a=this.$.querySelector(a))?new CKEDITOR.dom.element(a):null},_getHtml5ShivFrag:function(){var a=this.getCustomData("html5ShivFrag");a||(a=this.$.createDocumentFragment(),CKEDITOR.tools.enableHtml5Elements(a,!0),this.setCustomData("html5ShivFrag",a));return a}});CKEDITOR.dom.nodeList=function(a){this.$=a}; CKEDITOR.dom.nodeList.prototype={count:function(){return this.$.length},getItem:function(a){return 0>a||a>=this.$.length?null:(a=this.$[a])?new CKEDITOR.dom.node(a):null},toArray:function(){return CKEDITOR.tools.array.map(this.$,function(a){return new CKEDITOR.dom.node(a)})}};CKEDITOR.dom.element=function(a,d){"string"==typeof a&&(a=(d?d.$:document).createElement(a));CKEDITOR.dom.domObject.call(this,a)}; CKEDITOR.dom.element.get=function(a){return(a="string"==typeof a?document.getElementById(a)||document.getElementsByName(a)[0]:a)&&(a.$?a:new CKEDITOR.dom.element(a))};CKEDITOR.dom.element.prototype=new CKEDITOR.dom.node;CKEDITOR.dom.element.createFromHtml=function(a,d){var b=new CKEDITOR.dom.element("div",d);b.setHtml(a);return b.getFirst().remove()}; CKEDITOR.dom.element.setMarker=function(a,d,b,c){var f=d.getCustomData("list_marker_id")||d.setCustomData("list_marker_id",CKEDITOR.tools.getNextNumber()).getCustomData("list_marker_id"),h=d.getCustomData("list_marker_names")||d.setCustomData("list_marker_names",{}).getCustomData("list_marker_names");a[f]=d;h[b]=1;return d.setCustomData(b,c)};CKEDITOR.dom.element.clearAllMarkers=function(a){for(var d in a)CKEDITOR.dom.element.clearMarkers(a,a[d],1)}; CKEDITOR.dom.element.clearMarkers=function(a,d,b){var c=d.getCustomData("list_marker_names"),f=d.getCustomData("list_marker_id"),h;for(h in c)d.removeCustomData(h);d.removeCustomData("list_marker_names");b&&(d.removeCustomData("list_marker_id"),delete a[f])}; (function(){function a(a,b){return-1<(" "+a+" ").replace(h," ").indexOf(" "+b+" ")}function d(a){var b=!0;a.$.id||(a.$.id="cke_tmp_"+CKEDITOR.tools.getNextNumber(),b=!1);return function(){b||a.removeAttribute("id")}}function b(a,b){var c=CKEDITOR.tools.escapeCss(a.$.id);return"#"+c+" "+b.split(/,\s*/).join(", #"+c+" ")}function c(a){for(var b=0,c=0,e=k[a].length;cCKEDITOR.env.version?this.$.text+=a:this.append(new CKEDITOR.dom.text(a))},appendBogus:function(a){if(a||CKEDITOR.env.needsBrFiller){for(a=this.getLast();a&&a.type==CKEDITOR.NODE_TEXT&&!CKEDITOR.tools.rtrim(a.getText());)a=a.getPrevious();a&&a.is&&a.is("br")||(a=this.getDocument().createElement("br"),CKEDITOR.env.gecko&&a.setAttribute("type","_moz"),this.append(a))}},breakParent:function(a,b){var c=new CKEDITOR.dom.range(this.getDocument());c.setStartAfter(this);c.setEndAfter(a); var e=c.extractContents(!1,b||!1),d;c.insertNode(this.remove());if(CKEDITOR.env.ie&&!CKEDITOR.env.edge){for(c=new CKEDITOR.dom.element("div");d=e.getFirst();)d.$.style.backgroundColor&&(d.$.style.backgroundColor=d.$.style.backgroundColor),c.append(d);c.insertAfter(this);c.remove(!0)}else e.insertAfterNode(this)},contains:document.compareDocumentPosition?function(a){return!!(this.$.compareDocumentPosition(a.$)&16)}:function(a){var b=this.$;return a.type!=CKEDITOR.NODE_ELEMENT?b.contains(a.getParent().$): b!=a.$&&b.contains(a.$)},focus:function(){function a(){try{this.$.focus()}catch(b){}}return function(b){b?CKEDITOR.tools.setTimeout(a,100,this):a.call(this)}}(),getHtml:function(){var a=this.$.innerHTML;return CKEDITOR.env.ie?a.replace(/<\?[^>]*>/g,""):a},getOuterHtml:function(){if(this.$.outerHTML)return this.$.outerHTML.replace(/<\?[^>]*>/,"");var a=this.$.ownerDocument.createElement("div");a.appendChild(this.$.cloneNode(!0));return a.innerHTML},getClientRect:function(a){var b=CKEDITOR.tools.extend({}, this.$.getBoundingClientRect());!b.width&&(b.width=b.right-b.left);!b.height&&(b.height=b.bottom-b.top);return a?CKEDITOR.tools.getAbsoluteRectPosition(this.getWindow(),b):b},setHtml:CKEDITOR.env.ie&&9>CKEDITOR.env.version?function(a){try{var b=this.$;if(this.getParent())return b.innerHTML=a;var c=this.getDocument()._getHtml5ShivFrag();c.appendChild(b);b.innerHTML=a;c.removeChild(b);return a}catch(e){this.$.innerHTML="";b=new CKEDITOR.dom.element("body",this.getDocument());b.$.innerHTML=a;for(b=b.getChildren();b.count();)this.append(b.getItem(0)); return a}}:function(a){return this.$.innerHTML=a},setText:function(){var a=document.createElement("p");a.innerHTML="x";a=a.textContent;return function(b){this.$[a?"textContent":"innerText"]=b}}(),getAttribute:function(){var a=function(a){return this.$.getAttribute(a,2)};return CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)?function(a){switch(a){case "class":a="className";break;case "http-equiv":a="httpEquiv";break;case "name":return this.$.name;case "tabindex":return a=this.$.getAttribute(a, 2),0!==a&&0===this.$.tabIndex&&(a=null),a;case "checked":return a=this.$.attributes.getNamedItem(a),(a.specified?a.nodeValue:this.$.checked)?"checked":null;case "hspace":case "value":return this.$[a];case "style":return this.$.style.cssText;case "contenteditable":case "contentEditable":return this.$.attributes.getNamedItem("contentEditable").specified?this.$.getAttribute("contentEditable"):null}return this.$.getAttribute(a,2)}:a}(),getAttributes:function(a){var b={},c=this.$.attributes,e;a=CKEDITOR.tools.isArray(a)? a:[];for(e=0;e=document.documentMode){var b=this.$.scopeName;"HTML"!=b&&(a=b.toLowerCase()+":"+a)}this.getName=function(){return a};return this.getName()},getValue:function(){return this.$.value},getFirst:function(a){var b=this.$.firstChild;(b=b&&new CKEDITOR.dom.node(b))&&a&&!a(b)&&(b=b.getNext(a));return b},getLast:function(a){var b=this.$.lastChild;(b=b&&new CKEDITOR.dom.node(b))&&a&&!a(b)&&(b=b.getPrevious(a));return b},getStyle:function(a){return this.$.style[CKEDITOR.tools.cssStyleToDomStyle(a)]}, is:function(){var a=this.getName();if("object"==typeof arguments[0])return!!arguments[0][a];for(var b=0;bCKEDITOR.env.version&&this.is("a")){var c=this.getParent();c.type==CKEDITOR.NODE_ELEMENT&&(c=c.clone(),c.setHtml(b),b=c.getHtml(),c.setHtml(a),a=c.getHtml())}return b==a},isVisible:function(){var a=(this.$.offsetHeight||this.$.offsetWidth)&&"hidden"!=this.getComputedStyle("visibility"),b,c;a&&CKEDITOR.env.webkit&&(b=this.getWindow(),!b.equals(CKEDITOR.document.getWindow())&&(c=b.$.frameElement)&&(a=(new CKEDITOR.dom.element(c)).isVisible()));return!!a},isEmptyInlineRemoveable:function(){if(!CKEDITOR.dtd.$removeEmpty[this.getName()])return!1; for(var a=this.getChildren(),b=0,c=a.count();bCKEDITOR.env.version?function(b){return"name"==b?!!this.$.name:a.call(this,b)}:a:function(a){return!!this.$.attributes.getNamedItem(a)}}(),hide:function(){this.setStyle("display","none")},moveChildren:function(a,b){var c=this.$;a=a.$;if(c!=a){var e;if(b)for(;e=c.lastChild;)a.insertBefore(c.removeChild(e),a.firstChild);else for(;e=c.firstChild;)a.appendChild(c.removeChild(e))}},mergeSiblings:function(){function a(b,c,e){if(c&&c.type==CKEDITOR.NODE_ELEMENT){for(var d= [];c.data("cke-bookmark")||c.isEmptyInlineRemoveable();)if(d.push(c),c=e?c.getNext():c.getPrevious(),!c||c.type!=CKEDITOR.NODE_ELEMENT)return;if(b.isIdentical(c)){for(var g=e?b.getLast():b.getFirst();d.length;)d.shift().move(b,!e);c.moveChildren(b,!e);c.remove();g&&g.type==CKEDITOR.NODE_ELEMENT&&g.mergeSiblings()}}}return function(b){if(!1===b||CKEDITOR.dtd.$removeEmpty[this.getName()]||this.is("a"))a(this,this.getNext(),!0),a(this,this.getPrevious())}}(),show:function(){this.setStyles({display:"", visibility:""})},setAttribute:function(){var a=function(a,b){this.$.setAttribute(a,b);return this};return CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)?function(b,c){"class"==b?this.$.className=c:"style"==b?this.$.style.cssText=c:"tabindex"==b?this.$.tabIndex=c:"checked"==b?this.$.checked=c:"contenteditable"==b?a.call(this,"contentEditable",c):a.apply(this,arguments);return this}:CKEDITOR.env.ie8Compat&&CKEDITOR.env.secure?function(b,c){if("src"==b&&c.match(/^http:\/\//))try{a.apply(this, arguments)}catch(e){}else a.apply(this,arguments);return this}:a}(),setAttributes:function(a){for(var b in a)this.setAttribute(b,a[b]);return this},setValue:function(a){this.$.value=a;return this},removeAttribute:function(){var a=function(a){this.$.removeAttribute(a)};return CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)?function(a){"class"==a?a="className":"tabindex"==a?a="tabIndex":"contenteditable"==a&&(a="contentEditable");this.$.removeAttribute(a)}:a}(),removeAttributes:function(a){if(CKEDITOR.tools.isArray(a))for(var b= 0;bCKEDITOR.env.version?(a=Math.round(100*a),this.setStyle("filter",100<=a?"":"progid:DXImageTransform.Microsoft.Alpha(opacity\x3d"+a+")")):this.setStyle("opacity",a)},unselectable:function(){this.setStyles(CKEDITOR.tools.cssVendorPrefix("user-select", "none"));if(CKEDITOR.env.ie){this.setAttribute("unselectable","on");for(var a,b=this.getElementsByTag("*"),c=0,e=b.count();cf||0f?f:d);c&&(0>t||0t?t:e,0)},setState:function(a,b,c){b=b||"cke";switch(a){case CKEDITOR.TRISTATE_ON:this.addClass(b+"_on");this.removeClass(b+ "_off");this.removeClass(b+"_disabled");c&&this.setAttribute("aria-pressed",!0);c&&this.removeAttribute("aria-disabled");break;case CKEDITOR.TRISTATE_DISABLED:this.addClass(b+"_disabled");this.removeClass(b+"_off");this.removeClass(b+"_on");c&&this.setAttribute("aria-disabled",!0);c&&this.removeAttribute("aria-pressed");break;default:this.addClass(b+"_off"),this.removeClass(b+"_on"),this.removeClass(b+"_disabled"),c&&this.removeAttribute("aria-pressed"),c&&this.removeAttribute("aria-disabled")}}, getFrameDocument:function(){var a=this.$;try{a.contentWindow.document}catch(b){a.src=a.src}return a&&new CKEDITOR.dom.document(a.contentWindow.document)},copyAttributes:function(a,b){var c=this.$.attributes;b=b||{};for(var e=0;eCKEDITOR.env.version){var d=e.ownerDocument.createEventObject(),f;for(f in b)d[f]=b[f];e.fireEvent(c, d)}else e[e[a]?a:c](b)},isDetached:function(){var a=this.getDocument(),b=a.getDocumentElement();return b.equals(this)||b.contains(this)?!CKEDITOR.env.ie||8=D.getChildCount()?(D=D.getChild(B-1),q=!0):D=D.getChild(B):w=q=!0;p.type==CKEDITOR.NODE_TEXT?l?v=!0:p.split(G):0da)for(;L;)L=h(L,K,!0);K=A}l|| g()}}function b(){var a=!1,b=CKEDITOR.dom.walker.whitespaces(),c=CKEDITOR.dom.walker.bookmark(!0),d=CKEDITOR.dom.walker.bogus();return function(f){return c(f)||b(f)?!0:d(f)&&!a?a=!0:f.type==CKEDITOR.NODE_TEXT&&(f.hasAscendant("pre")||CKEDITOR.tools.trim(f.getText()).length)||f.type==CKEDITOR.NODE_ELEMENT&&!f.is(h)?!1:!0}}function c(a){var b=CKEDITOR.dom.walker.whitespaces(),c=CKEDITOR.dom.walker.bookmark(1);return function(d){return c(d)||b(d)?!0:!a&&k(d)||d.type==CKEDITOR.NODE_ELEMENT&&d.is(CKEDITOR.dtd.$removeEmpty)}} function f(a){return function(){var b;return this[a?"getPreviousNode":"getNextNode"](function(a){!b&&u(a)&&(b=a);return l(a)&&!(k(a)&&a.equals(b))})}}var h={abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,"var":1},k=CKEDITOR.dom.walker.bogus(),g=/^[\t\r\n ]*(?: |\xa0)$/,l=CKEDITOR.dom.walker.editable(),u=CKEDITOR.dom.walker.ignored(!0);CKEDITOR.dom.range.prototype={clone:function(){var a= new CKEDITOR.dom.range(this.root);a._setStartContainer(this.startContainer);a.startOffset=this.startOffset;a._setEndContainer(this.endContainer);a.endOffset=this.endOffset;a.collapsed=this.collapsed;return a},collapse:function(a){a?(this._setEndContainer(this.startContainer),this.endOffset=this.startOffset):(this._setStartContainer(this.endContainer),this.startOffset=this.endOffset);this.collapsed=!0},cloneContents:function(a){var b=new CKEDITOR.dom.documentFragment(this.document);this.collapsed|| d(this,2,b,!1,"undefined"==typeof a?!0:a);return b},deleteContents:function(a){this.collapsed||d(this,0,null,a)},extractContents:function(a,b){var c=new CKEDITOR.dom.documentFragment(this.document);this.collapsed||d(this,1,c,a,"undefined"==typeof b?!0:b);return c},equals:function(a){return this.startOffset===a.startOffset&&this.endOffset===a.endOffset&&this.startContainer.equals(a.startContainer)&&this.endContainer.equals(a.endContainer)},createBookmark:function(a){function b(a){return a.getAscendant(function(a){var b; if(b=a.data&&a.data("cke-temp"))b=-1===CKEDITOR.tools.array.indexOf(["cke_copybin","cke_pastebin"],a.getAttribute("id"));return b},!0)}var c=this.startContainer,d=this.endContainer,f=this.collapsed,h,m,g,k;h=this.document.createElement("span");h.data("cke-bookmark",1);h.setStyle("display","none");h.setHtml("\x26nbsp;");a&&(g="cke_bm_"+CKEDITOR.tools.getNextNumber(),h.setAttribute("id",g+(f?"C":"S")));f||(m=h.clone(),m.setHtml("\x26nbsp;"),a&&m.setAttribute("id",g+"E"),k=this.clone(),b(d)&&(d=b(d), k.moveToPosition(d,CKEDITOR.POSITION_AFTER_END)),k.collapse(),k.insertNode(m));k=this.clone();b(c)&&(d=b(c),k.moveToPosition(d,CKEDITOR.POSITION_BEFORE_START));k.collapse(!0);k.insertNode(h);m?(this.setStartAfter(h),this.setEndBefore(m)):this.moveToPosition(h,CKEDITOR.POSITION_AFTER_END);return{startNode:a?g+(f?"C":"S"):h,endNode:a?g+"E":m,serializable:a,collapsed:f}},createBookmark2:function(){function a(b){var e=b.container,d=b.offset,m;m=e;var f=d;m=m.type!=CKEDITOR.NODE_ELEMENT||0===f||f==m.getChildCount()? 0:m.getChild(f-1).type==CKEDITOR.NODE_TEXT&&m.getChild(f).type==CKEDITOR.NODE_TEXT;m&&(e=e.getChild(d-1),d=e.getLength());if(e.type==CKEDITOR.NODE_ELEMENT&&0=a.offset&&(a.offset=d.getIndex(),a.container=d.getParent()))}}var c=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_TEXT,!0);return function(c){var d=this.collapsed,f={container:this.startContainer,offset:this.startOffset},m={container:this.endContainer,offset:this.endOffset};c&&(a(f),b(f,this.root), d||(a(m),b(m,this.root)));return{start:f.container.getAddress(c),end:d?null:m.container.getAddress(c),startOffset:f.offset,endOffset:m.offset,normalized:c,collapsed:d,is2:!0}}}(),moveToBookmark:function(a){if(a.is2){var b=this.document.getByAddress(a.start,a.normalized),c=a.startOffset,d=a.end&&this.document.getByAddress(a.end,a.normalized);a=a.endOffset;this.setStart(b,c);d?this.setEnd(d,a):this.collapse(!0)}else b=(c=a.serializable)?this.document.getById(a.startNode):a.startNode,a=c?this.document.getById(a.endNode): a.endNode,this.setStartBefore(b),b.remove(),a?(this.setEndBefore(a),a.remove()):this.collapse(!0)},getBoundaryNodes:function(){var a=this.startContainer,b=this.endContainer,c=this.startOffset,d=this.endOffset,f;if(a.type==CKEDITOR.NODE_ELEMENT)if(f=a.getChildCount(),f>c)a=a.getChild(c);else if(1>f)a=a.getPreviousSourceNode();else{for(a=a.$;a.lastChild;)a=a.lastChild;a=new CKEDITOR.dom.node(a);a=a.getNextSourceNode()||a}if(b.type==CKEDITOR.NODE_ELEMENT)if(f=b.getChildCount(),f>d)b=b.getChild(d).getPreviousSourceNode(!0); else if(1>f)b=b.getPreviousSourceNode();else{for(b=b.$;b.lastChild;)b=b.lastChild;b=new CKEDITOR.dom.node(b)}a.getPosition(b)&CKEDITOR.POSITION_FOLLOWING&&(a=b);return{startNode:a,endNode:b}},getCommonAncestor:function(a,b){var c=this.startContainer,d=this.endContainer,c=c.equals(d)?a&&c.type==CKEDITOR.NODE_ELEMENT&&this.startOffset==this.endOffset-1?c.getChild(this.startOffset):c:c.getCommonAncestor(d);return b&&!c.is?c.getParent():c},optimize:function(){var a=this.startContainer,b=this.startOffset; a.type!=CKEDITOR.NODE_ELEMENT&&(b?b>=a.getLength()&&this.setStartAfter(a):this.setStartBefore(a));a=this.endContainer;b=this.endOffset;a.type!=CKEDITOR.NODE_ELEMENT&&(b?b>=a.getLength()&&this.setEndAfter(a):this.setEndBefore(a))},optimizeBookmark:function(){var a=this.startContainer,b=this.endContainer;a.is&&a.is("span")&&a.data("cke-bookmark")&&this.setStartAt(a,CKEDITOR.POSITION_BEFORE_START);b&&b.is&&b.is("span")&&b.data("cke-bookmark")&&this.setEndAt(b,CKEDITOR.POSITION_AFTER_END)},trim:function(a, b){var c=this.startContainer,d=this.startOffset,f=this.collapsed;if((!a||f)&&c&&c.type==CKEDITOR.NODE_TEXT){if(d)if(d>=c.getLength())d=c.getIndex()+1,c=c.getParent();else{var h=c.split(d),d=c.getIndex()+1,c=c.getParent();this.startContainer.equals(this.endContainer)?this.setEnd(h,this.endOffset-this.startOffset):c.equals(this.endContainer)&&(this.endOffset+=1)}else d=c.getIndex(),c=c.getParent();this.setStart(c,d);if(f){this.collapse(!0);return}}c=this.endContainer;d=this.endOffset;b||f||!c||c.type!= CKEDITOR.NODE_TEXT||(d?(d>=c.getLength()||c.split(d),d=c.getIndex()+1):d=c.getIndex(),c=c.getParent(),this.setEnd(c,d))},enlarge:function(a,b){function c(a){return a&&a.type==CKEDITOR.NODE_ELEMENT&&a.hasAttribute("contenteditable")?null:a}var d=new RegExp(/[^\s\ufeff]/);switch(a){case CKEDITOR.ENLARGE_INLINE:var f=1;case CKEDITOR.ENLARGE_ELEMENT:var h=function(a,b){var c=new CKEDITOR.dom.range(g);c.setStart(a,b);c.setEndAt(g,CKEDITOR.POSITION_BEFORE_END);var c=new CKEDITOR.dom.walker(c),e;for(c.guard= function(a){return!(a.type==CKEDITOR.NODE_ELEMENT&&a.isBlockBoundary())};e=c.next();){if(e.type!=CKEDITOR.NODE_TEXT)return!1;N=e!=a?e.getText():e.substring(b);if(d.test(N))return!1}return!0};if(this.collapsed)break;var m=this.getCommonAncestor(),g=this.root,k,u,l,p,D,G=!1,B,N;B=this.startContainer;var q=this.startOffset;B.type==CKEDITOR.NODE_TEXT?(q&&(B=!CKEDITOR.tools.trim(B.substring(0,q)).length&&B,G=!!B),B&&((p=B.getPrevious())||(l=B.getParent()))):(q&&(p=B.getChild(q-1)||B.getLast()),p||(l=B)); for(l=c(l);l||p;){if(l&&!p){!D&&l.equals(m)&&(D=!0);if(f?l.isBlockBoundary():!g.contains(l))break;G&&"inline"==l.getComputedStyle("display")||(G=!1,D?k=l:this.setStartBefore(l));p=l.getPrevious()}for(;p;)if(B=!1,p.type==CKEDITOR.NODE_COMMENT)p=p.getPrevious();else{if(p.type==CKEDITOR.NODE_TEXT)N=p.getText(),d.test(N)&&(p=null),B=/[\s\ufeff]$/.test(N);else if((p.$.offsetWidth>(CKEDITOR.env.webkit?1:0)||b&&p.is("br"))&&!p.data("cke-bookmark"))if(G&&CKEDITOR.dtd.$removeEmpty[p.getName()]){N=p.getText(); if(d.test(N))p=null;else for(var q=p.$.getElementsByTagName("*"),C=0,w;w=q[C++];)if(!CKEDITOR.dtd.$removeEmpty[w.nodeName.toLowerCase()]){p=null;break}p&&(B=!!N.length)}else p=null;B&&(G?D?k=l:l&&this.setStartBefore(l):G=!0);if(p){B=p.getPrevious();if(!l&&!B){l=p;p=null;break}p=B}else l=null}l&&(l=c(l.getParent()))}B=this.endContainer;q=this.endOffset;l=p=null;D=G=!1;B.type==CKEDITOR.NODE_TEXT?CKEDITOR.tools.trim(B.substring(q)).length?G=!0:(G=!B.getLength(),q==B.getLength()?(p=B.getNext())||(l=B.getParent()): h(B,q)&&(l=B.getParent())):(p=B.getChild(q))||(l=B);for(;l||p;){if(l&&!p){!D&&l.equals(m)&&(D=!0);if(f?l.isBlockBoundary():!g.contains(l))break;G&&"inline"==l.getComputedStyle("display")||(G=!1,D?u=l:l&&this.setEndAfter(l));p=l.getNext()}for(;p;){B=!1;if(p.type==CKEDITOR.NODE_TEXT)N=p.getText(),h(p,0)||(p=null),B=/^[\s\ufeff]/.test(N);else if(p.type==CKEDITOR.NODE_ELEMENT){if((0=m.getLength()?h.setStartAfter(m):(h.setStartBefore(m),c=0):h.setStartBefore(m));g&&g.type==CKEDITOR.NODE_TEXT&&(l?l>=g.getLength()?h.setEndAfter(g):(h.setEndAfter(g),u=0):h.setEndBefore(g));var h=new CKEDITOR.dom.walker(h),p=CKEDITOR.dom.walker.bookmark(),D=CKEDITOR.dom.walker.bogus();h.evaluator=function(b){return b.type==(a==CKEDITOR.SHRINK_ELEMENT?CKEDITOR.NODE_ELEMENT:CKEDITOR.NODE_TEXT)};var G;h.guard=function(b,c){if(f&&D(b)||p(b))return!0;if(a==CKEDITOR.SHRINK_ELEMENT&& b.type==CKEDITOR.NODE_TEXT||c&&b.equals(G)||!1===d&&b.type==CKEDITOR.NODE_ELEMENT&&b.isBlockBoundary()||b.type==CKEDITOR.NODE_ELEMENT&&b.hasAttribute("contenteditable"))return!1;c||b.type!=CKEDITOR.NODE_ELEMENT||(G=b);return!0};c&&(m=h[a==CKEDITOR.SHRINK_ELEMENT?"lastForward":"next"]())&&this.setStartAt(m,b?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_START);u&&(h.reset(),(h=h[a==CKEDITOR.SHRINK_ELEMENT?"lastBackward":"previous"]())&&this.setEndAt(h,b?CKEDITOR.POSITION_BEFORE_END:CKEDITOR.POSITION_AFTER_END)); return!(!c&&!u)}},insertNode:function(a){this.optimizeBookmark();this.trim(!1,!0);var b=this.startContainer,c=b.getChild(this.startOffset);c?a.insertBefore(c):b.append(a);a.getParent()&&a.getParent().equals(this.endContainer)&&this.endOffset++;this.setStartBefore(a)},moveToPosition:function(a,b){this.setStartAt(a,b);this.collapse(!0)},moveToRange:function(a){this.setStart(a.startContainer,a.startOffset);this.setEnd(a.endContainer,a.endOffset)},selectNodeContents:function(a){this.setStart(a,0);this.setEnd(a, a.type==CKEDITOR.NODE_TEXT?a.getLength():a.getChildCount())},setStart:function(b,c){b.type==CKEDITOR.NODE_ELEMENT&&CKEDITOR.dtd.$empty[b.getName()]&&(c=b.getIndex(),b=b.getParent());this._setStartContainer(b);this.startOffset=c;this.endContainer||(this._setEndContainer(b),this.endOffset=c);a(this)},setEnd:function(b,c){b.type==CKEDITOR.NODE_ELEMENT&&CKEDITOR.dtd.$empty[b.getName()]&&(c=b.getIndex()+1,b=b.getParent());this._setEndContainer(b);this.endOffset=c;this.startContainer||(this._setStartContainer(b), this.startOffset=c);a(this)},setStartAfter:function(a){this.setStart(a.getParent(),a.getIndex()+1)},setStartBefore:function(a){this.setStart(a.getParent(),a.getIndex())},setEndAfter:function(a){this.setEnd(a.getParent(),a.getIndex()+1)},setEndBefore:function(a){this.setEnd(a.getParent(),a.getIndex())},setStartAt:function(b,c){switch(c){case CKEDITOR.POSITION_AFTER_START:this.setStart(b,0);break;case CKEDITOR.POSITION_BEFORE_END:b.type==CKEDITOR.NODE_TEXT?this.setStart(b,b.getLength()):this.setStart(b, b.getChildCount());break;case CKEDITOR.POSITION_BEFORE_START:this.setStartBefore(b);break;case CKEDITOR.POSITION_AFTER_END:this.setStartAfter(b)}a(this)},setEndAt:function(b,c){switch(c){case CKEDITOR.POSITION_AFTER_START:this.setEnd(b,0);break;case CKEDITOR.POSITION_BEFORE_END:b.type==CKEDITOR.NODE_TEXT?this.setEnd(b,b.getLength()):this.setEnd(b,b.getChildCount());break;case CKEDITOR.POSITION_BEFORE_START:this.setEndBefore(b);break;case CKEDITOR.POSITION_AFTER_END:this.setEndAfter(b)}a(this)},fixBlock:function(a, b){var c=this.createBookmark(),d=this.document.createElement(b);this.collapse(a);this.enlarge(CKEDITOR.ENLARGE_BLOCK_CONTENTS);this.extractContents().appendTo(d);d.trim();this.insertNode(d);var f=d.getBogus();f&&f.remove();d.appendBogus();this.moveToBookmark(c);return d},splitBlock:function(a,b){var c=new CKEDITOR.dom.elementPath(this.startContainer,this.root),d=new CKEDITOR.dom.elementPath(this.endContainer,this.root),f=c.block,h=d.block,m=null;if(!c.blockLimit.equals(d.blockLimit))return null;"br"!= a&&(f||(f=this.fixBlock(!0,a),h=(new CKEDITOR.dom.elementPath(this.endContainer,this.root)).block),h||(h=this.fixBlock(!1,a)));c=f&&this.checkStartOfBlock();d=h&&this.checkEndOfBlock();this.deleteContents();f&&f.equals(h)&&(d?(m=new CKEDITOR.dom.elementPath(this.startContainer,this.root),this.moveToPosition(h,CKEDITOR.POSITION_AFTER_END),h=null):c?(m=new CKEDITOR.dom.elementPath(this.startContainer,this.root),this.moveToPosition(f,CKEDITOR.POSITION_BEFORE_START),f=null):(h=this.splitElement(f,b|| !1),f.is("ul","ol")||f.appendBogus()));return{previousBlock:f,nextBlock:h,wasStartOfBlock:c,wasEndOfBlock:d,elementPath:m}},splitElement:function(a,b){if(!this.collapsed)return null;this.setEndAt(a,CKEDITOR.POSITION_BEFORE_END);var c=this.extractContents(!1,b||!1),d=a.clone(!1,b||!1);c.appendTo(d);d.insertAfter(a);this.moveToPosition(a,CKEDITOR.POSITION_AFTER_END);return d},removeEmptyBlocksAtEnd:function(){function a(e){return function(a){return b(a)||c(a)||a.type==CKEDITOR.NODE_ELEMENT&&a.isEmptyInlineRemoveable()|| e.is("table")&&a.is("caption")?!1:!0}}var b=CKEDITOR.dom.walker.whitespaces(),c=CKEDITOR.dom.walker.bookmark(!1);return function(b){for(var c=this.createBookmark(),d=this[b?"endPath":"startPath"](),f=d.block||d.blockLimit,h;f&&!f.equals(d.root)&&!f.getFirst(a(f));)h=f.getParent(),this[b?"setEndAt":"setStartAt"](f,CKEDITOR.POSITION_AFTER_END),f.remove(1),f=h;this.moveToBookmark(c)}}(),startPath:function(){return new CKEDITOR.dom.elementPath(this.startContainer,this.root)},endPath:function(){return new CKEDITOR.dom.elementPath(this.endContainer, this.root)},checkBoundaryOfElement:function(a,b){var d=b==CKEDITOR.START,f=this.clone();f.collapse(d);f[d?"setStartAt":"setEndAt"](a,d?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END);f=new CKEDITOR.dom.walker(f);f.evaluator=c(d);return f[d?"checkBackward":"checkForward"]()},checkStartOfBlock:function(){var a=this.startContainer,c=this.startOffset;CKEDITOR.env.ie&&c&&a.type==CKEDITOR.NODE_TEXT&&(a=CKEDITOR.tools.ltrim(a.substring(0,c)),g.test(a)&&this.trim(0,1));this.trim();a=new CKEDITOR.dom.elementPath(this.startContainer, this.root);c=this.clone();c.collapse(!0);c.setStartAt(a.block||a.blockLimit,CKEDITOR.POSITION_AFTER_START);a=new CKEDITOR.dom.walker(c);a.evaluator=b();return a.checkBackward()},checkEndOfBlock:function(){var a=this.endContainer,c=this.endOffset;CKEDITOR.env.ie&&a.type==CKEDITOR.NODE_TEXT&&(a=CKEDITOR.tools.rtrim(a.substring(c)),g.test(a)&&this.trim(1,0));this.trim();a=new CKEDITOR.dom.elementPath(this.endContainer,this.root);c=this.clone();c.collapse(!1);c.setEndAt(a.block||a.blockLimit,CKEDITOR.POSITION_BEFORE_END); a=new CKEDITOR.dom.walker(c);a.evaluator=b();return a.checkForward()},getPreviousNode:function(a,b,c){var d=this.clone();d.collapse(1);d.setStartAt(c||this.root,CKEDITOR.POSITION_AFTER_START);c=new CKEDITOR.dom.walker(d);c.evaluator=a;c.guard=b;return c.previous()},getNextNode:function(a,b,c){var d=this.clone();d.collapse();d.setEndAt(c||this.root,CKEDITOR.POSITION_BEFORE_END);c=new CKEDITOR.dom.walker(d);c.evaluator=a;c.guard=b;return c.next()},checkReadOnly:function(){function a(b,c){for(;b;){if(b.type== CKEDITOR.NODE_ELEMENT){if("false"==b.getAttribute("contentEditable")&&!b.data("cke-editable"))return 0;if(b.is("html")||"true"==b.getAttribute("contentEditable")&&(b.contains(c)||b.equals(c)))break}b=b.getParent()}return 1}return function(){var b=this.startContainer,c=this.endContainer;return!(a(b,c)&&a(c,b))}}(),moveToElementEditablePosition:function(a,b){if(a.type==CKEDITOR.NODE_ELEMENT&&!a.isEditable(!1))return this.moveToPosition(a,b?CKEDITOR.POSITION_AFTER_END:CKEDITOR.POSITION_BEFORE_START), !0;for(var c=0;a;){if(a.type==CKEDITOR.NODE_TEXT){b&&this.endContainer&&this.checkEndOfBlock()&&g.test(a.getText())?this.moveToPosition(a,CKEDITOR.POSITION_BEFORE_START):this.moveToPosition(a,b?CKEDITOR.POSITION_AFTER_END:CKEDITOR.POSITION_BEFORE_START);c=1;break}if(a.type==CKEDITOR.NODE_ELEMENT)if(a.isEditable())this.moveToPosition(a,b?CKEDITOR.POSITION_BEFORE_END:CKEDITOR.POSITION_AFTER_START),c=1;else if(b&&a.is("br")&&this.endContainer&&this.checkEndOfBlock())this.moveToPosition(a,CKEDITOR.POSITION_BEFORE_START); else if("false"==a.getAttribute("contenteditable")&&a.is(CKEDITOR.dtd.$block))return this.setStartBefore(a),this.setEndAfter(a),!0;var d=a,f=c,h=void 0;d.type==CKEDITOR.NODE_ELEMENT&&d.isEditable(!1)&&(h=d[b?"getLast":"getFirst"](u));f||h||(h=d[b?"getPrevious":"getNext"](u));a=h}return!!c},moveToClosestEditablePosition:function(a,b){var c,d=0,f,h,m=[CKEDITOR.POSITION_AFTER_END,CKEDITOR.POSITION_BEFORE_START];a?(c=new CKEDITOR.dom.range(this.root),c.moveToPosition(a,m[b?0:1])):c=this.clone();if(a&& !a.is(CKEDITOR.dtd.$block))d=1;else if(f=c[b?"getNextEditableNode":"getPreviousEditableNode"]())d=1,(h=f.type==CKEDITOR.NODE_ELEMENT)&&f.is(CKEDITOR.dtd.$block)&&"false"==f.getAttribute("contenteditable")?(c.setStartAt(f,CKEDITOR.POSITION_BEFORE_START),c.setEndAt(f,CKEDITOR.POSITION_AFTER_END)):!CKEDITOR.env.needsBrFiller&&h&&f.is(CKEDITOR.dom.walker.validEmptyBlockContainers)?(c.setEnd(f,0),c.collapse()):c.moveToPosition(f,m[b?1:0]);d&&this.moveToRange(c);return!!d},moveToElementEditStart:function(a){return this.moveToElementEditablePosition(a)}, moveToElementEditEnd:function(a){return this.moveToElementEditablePosition(a,!0)},getEnclosedNode:function(){var a=this.clone();a.optimize();if(a.startContainer.type!=CKEDITOR.NODE_ELEMENT||a.endContainer.type!=CKEDITOR.NODE_ELEMENT)return null;var a=new CKEDITOR.dom.walker(a),b=CKEDITOR.dom.walker.bookmark(!1,!0),c=CKEDITOR.dom.walker.whitespaces(!0);a.evaluator=function(a){return c(a)&&b(a)};var d=a.next();a.reset();return d&&d.equals(a.previous())?d:null},getTouchedStartNode:function(){var a=this.startContainer; return this.collapsed||a.type!=CKEDITOR.NODE_ELEMENT?a:a.getChild(this.startOffset)||a},getTouchedEndNode:function(){var a=this.endContainer;return this.collapsed||a.type!=CKEDITOR.NODE_ELEMENT?a:a.getChild(this.endOffset-1)||a},getNextEditableNode:f(),getPreviousEditableNode:f(1),_getTableElement:function(a){a=a||{td:1,th:1,tr:1,tbody:1,thead:1,tfoot:1,table:1};var b=this.getTouchedStartNode(),c=this.getTouchedEndNode(),d=b.getAscendant("table",!0),c=c.getAscendant("table",!0);return d&&!this.root.contains(d)? null:this.getEnclosedNode()?this.getEnclosedNode().getAscendant(a,!0):d&&c&&(d.equals(c)||d.contains(c)||c.contains(d))?b.getAscendant(a,!0):null},scrollIntoView:function(){var a=new CKEDITOR.dom.element.createFromHtml("\x3cspan\x3e\x26nbsp;\x3c/span\x3e",this.document),b,c,d,f=this.clone();f.optimize();(d=f.startContainer.type==CKEDITOR.NODE_TEXT)?(c=f.startContainer.getText(),b=f.startContainer.split(f.startOffset),a.insertAfter(f.startContainer)):f.insertNode(a);a.scrollIntoView();d&&(f.startContainer.setText(c), b.remove());a.remove()},getClientRects:function(){function a(b,c){var d=CKEDITOR.tools.array.map(b,function(a){return a}),e=new CKEDITOR.dom.range(c.root),f,h,g;c.startContainer instanceof CKEDITOR.dom.element&&(h=0===c.startOffset&&c.startContainer.hasAttribute("data-widget"));c.endContainer instanceof CKEDITOR.dom.element&&(g=(g=c.endOffset===(c.endContainer.getChildCount?c.endContainer.getChildCount():c.endContainer.length))&&c.endContainer.hasAttribute("data-widget"));h&&e.setStart(c.startContainer.getParent(), c.startContainer.getIndex());g&&e.setEnd(c.endContainer.getParent(),c.endContainer.getIndex()+1);if(h||g)c=e;e=c.cloneContents().find("[data-cke-widget-id]").toArray();if(e=CKEDITOR.tools.array.map(e,function(a){var b=c.root.editor;a=a.getAttribute("data-cke-widget-id");return b.widgets.instances[a].element}))return e=CKEDITOR.tools.array.map(e,function(a){var b;b=a.getParent().hasClass("cke_widget_wrapper")?a.getParent():a;f=this.root.getDocument().$.createRange();f.setStart(b.getParent().$,b.getIndex()); f.setEnd(b.getParent().$,b.getIndex()+1);b=f.getClientRects();b.widgetRect=a.getClientRect();return b},c),CKEDITOR.tools.array.forEach(e,function(a){function b(e){CKEDITOR.tools.array.forEach(d,function(b,f){var h=CKEDITOR.tools.objectCompare(a[e],b);h||(h=CKEDITOR.tools.objectCompare(a.widgetRect,b));h&&(Array.prototype.splice.call(d,f,a.length-e,a.widgetRect),c=!0)});c||(earguments.length||(this.range=a,this.forceBrBreak=0,this.enlargeBr=1,this.enforceRealBlocks=0,this._||(this._={}))}function d(a){var b=[];a.forEach(function(a){if("true"==a.getAttribute("contenteditable"))return b.push(a),!1},CKEDITOR.NODE_ELEMENT,!0);return b}function b(a,c,f,h){a:{null==h&&(h=d(f));for(var g;g=h.shift();)if(g.getDtd().p){h={element:g,remaining:h};break a}h=null}if(!h)return 0;if((g=CKEDITOR.filter.instances[h.element.data("cke-filter")])&&!g.check(c))return b(a, c,f,h.remaining);c=new CKEDITOR.dom.range(h.element);c.selectNodeContents(h.element);c=c.createIterator();c.enlargeBr=a.enlargeBr;c.enforceRealBlocks=a.enforceRealBlocks;c.activeFilter=c.filter=g;a._.nestedEditable={element:h.element,container:f,remaining:h.remaining,iterator:c};return 1}function c(a,b,c){if(!b)return!1;a=a.clone();a.collapse(!c);return a.checkBoundaryOfElement(b,c?CKEDITOR.START:CKEDITOR.END)}var f=/^[\r\n\t ]+$/,h=CKEDITOR.dom.walker.bookmark(!1,!0),k=CKEDITOR.dom.walker.whitespaces(!0), g=function(a){return h(a)&&k(a)},l={dd:1,dt:1,li:1};a.prototype={getNextParagraph:function(a){var d,k,t,z,I;a=a||"p";if(this._.nestedEditable){if(d=this._.nestedEditable.iterator.getNextParagraph(a))return this.activeFilter=this._.nestedEditable.iterator.activeFilter,d;this.activeFilter=this.filter;if(b(this,a,this._.nestedEditable.container,this._.nestedEditable.remaining))return this.activeFilter=this._.nestedEditable.iterator.activeFilter,this._.nestedEditable.iterator.getNextParagraph(a);this._.nestedEditable= null}if(!this.range.root.getDtd()[a])return null;if(!this._.started){var r=this.range.clone();k=r.startPath();var m=r.endPath(),M=!r.collapsed&&c(r,k.block),y=!r.collapsed&&c(r,m.block,1);r.shrink(CKEDITOR.SHRINK_ELEMENT,!0);M&&r.setStartAt(k.block,CKEDITOR.POSITION_BEFORE_END);y&&r.setEndAt(m.block,CKEDITOR.POSITION_AFTER_START);k=r.endContainer.hasAscendant("pre",!0)||r.startContainer.hasAscendant("pre",!0);r.enlarge(this.forceBrBreak&&!k||!this.enlargeBr?CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:CKEDITOR.ENLARGE_BLOCK_CONTENTS); r.collapsed||(k=new CKEDITOR.dom.walker(r.clone()),m=CKEDITOR.dom.walker.bookmark(!0,!0),k.evaluator=m,this._.nextNode=k.next(),k=new CKEDITOR.dom.walker(r.clone()),k.evaluator=m,k=k.previous(),this._.lastNode=k.getNextSourceNode(!0,null,r.root),this._.lastNode&&this._.lastNode.type==CKEDITOR.NODE_TEXT&&!CKEDITOR.tools.trim(this._.lastNode.getText())&&this._.lastNode.getParent().isBlockBoundary()&&(m=this.range.clone(),m.moveToPosition(this._.lastNode,CKEDITOR.POSITION_AFTER_END),m.checkEndOfBlock()&& (m=new CKEDITOR.dom.elementPath(m.endContainer,m.root),this._.lastNode=(m.block||m.blockLimit).getNextSourceNode(!0))),this._.lastNode&&r.root.contains(this._.lastNode)||(this._.lastNode=this._.docEndMarker=r.document.createText(""),this._.lastNode.insertAfter(k)),r=null);this._.started=1;k=r}m=this._.nextNode;r=this._.lastNode;for(this._.nextNode=null;m;){var M=0,y=m.hasAscendant("pre"),J=m.type!=CKEDITOR.NODE_ELEMENT,E=0;if(J)m.type==CKEDITOR.NODE_TEXT&&f.test(m.getText())&&(J=0);else{var p=m.getName(); if(CKEDITOR.dtd.$block[p]&&"false"==m.getAttribute("contenteditable")){d=m;b(this,a,d);break}else if(m.isBlockBoundary(this.forceBrBreak&&!y&&{br:1})){if("br"==p)J=1;else if(!k&&!m.getChildCount()&&"hr"!=p){d=m;t=m.equals(r);break}k&&(k.setEndAt(m,CKEDITOR.POSITION_BEFORE_START),"br"!=p&&(this._.nextNode=m));M=1}else{if(m.getFirst()){k||(k=this.range.clone(),k.setStartAt(m,CKEDITOR.POSITION_BEFORE_START));m=m.getFirst();continue}J=1}}J&&!k&&(k=this.range.clone(),k.setStartAt(m,CKEDITOR.POSITION_BEFORE_START)); t=(!M||J)&&m.equals(r);if(k&&!M)for(;!m.getNext(g)&&!t;){p=m.getParent();if(p.isBlockBoundary(this.forceBrBreak&&!y&&{br:1})){M=1;J=0;t||p.equals(r);k.setEndAt(p,CKEDITOR.POSITION_BEFORE_END);break}m=p;J=1;t=m.equals(r);E=1}J&&k.setEndAt(m,CKEDITOR.POSITION_AFTER_END);m=this._getNextSourceNode(m,E,r);if((t=!m)||M&&k)break}if(!d){if(!k)return this._.docEndMarker&&this._.docEndMarker.remove(),this._.nextNode=null;d=new CKEDITOR.dom.elementPath(k.startContainer,k.root);m=d.blockLimit;M={div:1,th:1,td:1}; d=d.block;!d&&m&&!this.enforceRealBlocks&&M[m.getName()]&&k.checkStartOfBlock()&&k.checkEndOfBlock()&&!m.equals(k.root)?d=m:!d||this.enforceRealBlocks&&d.is(l)?(d=this.range.document.createElement(a),k.extractContents().appendTo(d),d.trim(),k.insertNode(d),z=I=!0):"li"!=d.getName()?k.checkStartOfBlock()&&k.checkEndOfBlock()||(d=d.clone(!1),k.extractContents().appendTo(d),d.trim(),I=k.splitBlock(),z=!I.wasStartOfBlock,I=!I.wasEndOfBlock,k.insertNode(d)):t||(this._.nextNode=d.equals(r)?null:this._getNextSourceNode(k.getBoundaryNodes().endNode, 1,r))}z&&(z=d.getPrevious())&&z.type==CKEDITOR.NODE_ELEMENT&&("br"==z.getName()?z.remove():z.getLast()&&"br"==z.getLast().$.nodeName.toLowerCase()&&z.getLast().remove());I&&(z=d.getLast())&&z.type==CKEDITOR.NODE_ELEMENT&&"br"==z.getName()&&(!CKEDITOR.env.needsBrFiller||z.getPrevious(h)||z.getNext(h))&&z.remove();this._.nextNode||(this._.nextNode=t||d.equals(r)||!r?null:this._getNextSourceNode(d,1,r));return d},_getNextSourceNode:function(a,b,c){function d(a){return!(a.equals(c)||a.equals(f))}var f= this.range.root;for(a=a.getNextSourceNode(b,null,d);!h(a);)a=a.getNextSourceNode(b,null,d);return a}};CKEDITOR.dom.range.prototype.createIterator=function(){return new a(this)}})(); CKEDITOR.command=function(a,d){this.uiItems=[];this.exec=function(b){if(this.state==CKEDITOR.TRISTATE_DISABLED||!this.checkAllowed())return!1;this.editorFocus&&a.focus();return!1===this.fire("exec")?!0:!1!==d.exec.call(this,a,b)};this.refresh=function(a,b){if(!this.readOnly&&a.readOnly)return!0;if(this.context&&!b.isContextFor(this.context)||!this.checkAllowed(!0))return this.disable(),!0;this.startDisabled||this.enable();this.modes&&!this.modes[a.mode]&&this.disable();return!1===this.fire("refresh", {editor:a,path:b})?!0:d.refresh&&!1!==d.refresh.apply(this,arguments)};var b;this.checkAllowed=function(c){return c||"boolean"!=typeof b?b=a.activeFilter.checkFeature(this):b};CKEDITOR.tools.extend(this,d,{modes:{wysiwyg:1},editorFocus:1,contextSensitive:!!d.context,state:CKEDITOR.TRISTATE_DISABLED});CKEDITOR.event.call(this)}; CKEDITOR.command.prototype={enable:function(){this.state==CKEDITOR.TRISTATE_DISABLED&&this.checkAllowed()&&this.setState(this.preserveState&&"undefined"!=typeof this.previousState?this.previousState:CKEDITOR.TRISTATE_OFF)},disable:function(){this.setState(CKEDITOR.TRISTATE_DISABLED)},setState:function(a){if(this.state==a||a!=CKEDITOR.TRISTATE_DISABLED&&!this.checkAllowed())return!1;this.previousState=this.state;this.state=a;this.fire("state");return!0},toggleState:function(){this.state==CKEDITOR.TRISTATE_OFF? this.setState(CKEDITOR.TRISTATE_ON):this.state==CKEDITOR.TRISTATE_ON&&this.setState(CKEDITOR.TRISTATE_OFF)}};CKEDITOR.event.implementOn(CKEDITOR.command.prototype);CKEDITOR.ENTER_P=1;CKEDITOR.ENTER_BR=2;CKEDITOR.ENTER_DIV=3; CKEDITOR.config={customConfig:"config.js",autoUpdateElement:!0,language:"",defaultLanguage:"en",contentsLangDirection:"",enterMode:CKEDITOR.ENTER_P,forceEnterMode:!1,shiftEnterMode:CKEDITOR.ENTER_BR,docType:"\x3c!DOCTYPE html\x3e",bodyId:"",bodyClass:"",fullPage:!1,height:200,contentsCss:CKEDITOR.getUrl("contents.css"),extraPlugins:"",removePlugins:"",protectedSource:[],tabIndex:0,width:"",baseFloatZIndex:1E4,blockedKeystrokes:[CKEDITOR.CTRL+66,CKEDITOR.CTRL+73,CKEDITOR.CTRL+85]}; (function(){function a(a,b,c,d,e){var f,p;a=[];for(f in b){p=b[f];p="boolean"==typeof p?{}:"function"==typeof p?{match:p}:C(p);"$"!=f.charAt(0)&&(p.elements=f);c&&(p.featureName=c.toLowerCase());var n=p;n.elements=k(n.elements,/\s+/)||null;n.propertiesOnly=n.propertiesOnly||!0===n.elements;var w=/\s*,\s*/,q=void 0;for(q in Q){n[q]=k(n[q],w)||null;var h=n,m=S[q],g=k(n[S[q]],w),H=n[q],v=[],D=!0,O=void 0;g?D=!1:g={};for(O in H)"!"==O.charAt(0)&&(O=O.slice(1),v.push(O),g[O]=!0,D=!1);for(;O=v.pop();)H[O]= H["!"+O],delete H["!"+O];h[m]=(D?!1:g)||null}n.match=n.match||null;d.push(p);a.push(p)}b=e.elements;e=e.generic;var F;c=0;for(d=a.length;c=--g&&(h&&CKEDITOR.document.getDocumentElement().removeStyle("cursor"),e(b))},t=function(b,c){a[b]=1;var f=d[b];delete d[b];for(var e=0;e=CKEDITOR.env.version||CKEDITOR.env.ie9Compat)?e.$.onreadystatechange=function(){if("loaded"==e.$.readyState||"complete"==e.$.readyState)e.$.onreadystatechange=null,t(b,!0)}:(e.$.onload=function(){setTimeout(function(){e.$.onload=null;e.$.onerror=null;t(b,!0)},0)},e.$.onerror=function(){e.$.onload=null;e.$.onerror=null;t(b,!1)}));e.appendTo(CKEDITOR.document.getHead())}}};h&&CKEDITOR.document.getDocumentElement().setStyle("cursor", "wait");for(var I=0;I]+)>)|(?:!--([\S|\s]*?)--\x3e)|(?:([^\/\s>]+)((?:\s+[\w\-:.]+(?:\s*=\s*?(?:(?:"[^"]*")|(?:'[^']*')|[^\s"'\/>]+))?)*)[\S\s]*?(\/?)>))/g}}; (function(){var a=/([\w\-:.]+)(?:(?:\s*=\s*(?:(?:"([^"]*)")|(?:'([^']*)')|([^\s>]+)))|(?=\s|$))/g,d={checked:1,compact:1,declare:1,defer:1,disabled:1,ismap:1,multiple:1,nohref:1,noresize:1,noshade:1,nowrap:1,readonly:1,selected:1};CKEDITOR.htmlParser.prototype={onTagOpen:function(){},onTagClose:function(){},onText:function(){},onCDATA:function(){},onComment:function(){},parse:function(b){for(var c,f,h=0,k;c=this._.htmlPartsRegex.exec(b);){f=c.index;if(f>h)if(h=b.substring(h,f),k)k.push(h);else this.onText(h); h=this._.htmlPartsRegex.lastIndex;if(f=c[1])if(f=f.toLowerCase(),k&&CKEDITOR.dtd.$cdata[f]&&(this.onCDATA(k.join("")),k=null),!k){this.onTagClose(f);continue}if(k)k.push(c[0]);else if(f=c[3]){if(f=f.toLowerCase(),!/="/.test(f)){var g={},l,u=c[4];c=!!c[5];if(u)for(;l=a.exec(u);){var e=l[1].toLowerCase();l=l[2]||l[3]||l[4]||"";g[e]=!l&&d[e]?e:CKEDITOR.tools.htmlDecodeAttr(l)}this.onTagOpen(f,g,c);!k&&CKEDITOR.dtd.$cdata[f]&&(k=[])}}else if(f=c[2])this.onComment(f)}if(b.length>h)this.onText(b.substring(h, b.length))}}})(); CKEDITOR.htmlParser.basicWriter=CKEDITOR.tools.createClass({$:function(){this._={output:[]}},proto:{openTag:function(a){this._.output.push("\x3c",a)},openTagClose:function(a,d){d?this._.output.push(" /\x3e"):this._.output.push("\x3e")},attribute:function(a,d){"string"==typeof d&&(d=CKEDITOR.tools.htmlEncodeAttr(d));this._.output.push(" ",a,'\x3d"',d,'"')},closeTag:function(a){this._.output.push("\x3c/",a,"\x3e")},text:function(a){this._.output.push(a)},comment:function(a){this._.output.push("\x3c!--",a, "--\x3e")},write:function(a){this._.output.push(a)},reset:function(){this._.output=[];this._.indent=!1},getHtml:function(a){var d=this._.output.join("");a&&this.reset();return d}}});"use strict"; (function(){CKEDITOR.htmlParser.node=function(){};CKEDITOR.htmlParser.node.prototype={remove:function(){var a=this.parent.children,d=CKEDITOR.tools.indexOf(a,this),b=this.previous,c=this.next;b&&(b.next=c);c&&(c.previous=b);a.splice(d,1);this.parent=null},replaceWith:function(a){var d=this.parent.children,b=CKEDITOR.tools.indexOf(d,this),c=a.previous=this.previous,f=a.next=this.next;c&&(c.next=a);f&&(f.previous=a);d[b]=a;a.parent=this.parent;this.parent=null},insertAfter:function(a){var d=a.parent.children, b=CKEDITOR.tools.indexOf(d,a),c=a.next;d.splice(b+1,0,this);this.next=a.next;this.previous=a;a.next=this;c&&(c.previous=this);this.parent=a.parent},insertBefore:function(a){var d=a.parent.children,b=CKEDITOR.tools.indexOf(d,a);d.splice(b,0,this);this.next=a;(this.previous=a.previous)&&(a.previous.next=this);a.previous=this;this.parent=a.parent},getAscendant:function(a){var d="function"==typeof a?a:"string"==typeof a?function(b){return b.name==a}:function(b){return b.name in a},b=this.parent;for(;b&& b.type==CKEDITOR.NODE_ELEMENT;){if(d(b))return b;b=b.parent}return null},wrapWith:function(a){this.replaceWith(a);a.add(this);return a},getIndex:function(){return CKEDITOR.tools.indexOf(this.parent.children,this)},getFilterContext:function(a){return a||{}}}})();"use strict";CKEDITOR.htmlParser.comment=function(a){this.value=a;this._={isBlockLike:!1}}; CKEDITOR.htmlParser.comment.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_COMMENT,filter:function(a,d){var b=this.value;if(!(b=a.onComment(d,b,this)))return this.remove(),!1;if("string"!=typeof b)return this.replaceWith(b),!1;this.value=b;return!0},writeHtml:function(a,d){d&&this.filter(d);a.comment(this.value)}});"use strict"; (function(){CKEDITOR.htmlParser.text=function(a){this.value=a;this._={isBlockLike:!1}};CKEDITOR.htmlParser.text.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_TEXT,filter:function(a,d){if(!(this.value=a.onText(d,this.value,this)))return this.remove(),!1},writeHtml:function(a,d){d&&this.filter(d);a.text(this.value)}})})();"use strict"; (function(){CKEDITOR.htmlParser.cdata=function(a){this.value=a};CKEDITOR.htmlParser.cdata.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_TEXT,filter:function(){},writeHtml:function(a){a.write(this.value)}})})();"use strict";CKEDITOR.htmlParser.fragment=function(){this.children=[];this.parent=null;this._={isBlockLike:!0,hasInlineStarted:!1}}; (function(){function a(a){return a.attributes["data-cke-survive"]?!1:"a"==a.name&&a.attributes.href||CKEDITOR.dtd.$removeEmpty[a.name]}var d=CKEDITOR.tools.extend({table:1,ul:1,ol:1,dl:1},CKEDITOR.dtd.table,CKEDITOR.dtd.ul,CKEDITOR.dtd.ol,CKEDITOR.dtd.dl),b={ol:1,ul:1},c=CKEDITOR.tools.extend({},{html:1},CKEDITOR.dtd.html,CKEDITOR.dtd.body,CKEDITOR.dtd.head,{style:1,script:1}),f={ul:"li",ol:"li",dl:"dd",table:"tbody",tbody:"tr",thead:"tr",tfoot:"tr",tr:"td"};CKEDITOR.htmlParser.fragment.fromHtml= function(h,k,g){function l(a){var b;if(0k;k++)if(h=d[k]){h=h.exec(a,c,this);if(!1===h)return null;if(h&&h!=c)return this.onNode(a,h);if(c.parent&&!c.name)break}return c}, onNode:function(a,c){var d=c.type;return d==CKEDITOR.NODE_ELEMENT?this.onElement(a,c):d==CKEDITOR.NODE_TEXT?new CKEDITOR.htmlParser.text(this.onText(a,c.value)):d==CKEDITOR.NODE_COMMENT?new CKEDITOR.htmlParser.comment(this.onComment(a,c.value)):null},onAttribute:function(a,c,d,h){return(d=this.attributesRules[d])?d.exec(a,h,c,this):h}}});CKEDITOR.htmlParser.filterRulesGroup=a;a.prototype={add:function(a,c,d){this.rules.splice(this.findIndex(c),0,{value:a,priority:c,options:d})},addMany:function(a, c,d){for(var h=[this.findIndex(c),0],k=0,g=a.length;k/g,"\x26gt;")+"\x3c/textarea\x3e");return"\x3ccke:encoded\x3e"+encodeURIComponent(a)+"\x3c/cke:encoded\x3e"})}function x(a){return a.replace(n,function(a,b){return decodeURIComponent(b)})}function t(a){return a.replace(/\x3c!--(?!{cke_protected})[\s\S]+?--\x3e/g, function(a){return"\x3c!--"+J+"{C}"+encodeURIComponent(a).replace(/--/g,"%2D%2D")+"--\x3e"})}function z(a){return CKEDITOR.tools.array.reduce(a.split(""),function(a,b){var c=b.toLowerCase(),d=b.toUpperCase(),e=I(c);c!==d&&(e+="|"+I(d));return a+("("+e+")")},"")}function I(a){var b;b=a.charCodeAt(0);var c=b.toString(16);b={htmlCode:"\x26#"+b+";?",hex:"\x26#x0*"+c+";?",entity:{"\x3c":"\x26lt;","\x3e":"\x26gt;",":":"\x26colon;"}[a]};for(var d in b)b[d]&&(a+="|"+b[d]);return a}function r(a){return a.replace(/\x3c!--\{cke_protected\}\{C\}([\s\S]+?)--\x3e/g, function(a,b){return decodeURIComponent(b)})}function m(a,b){var c=b._.dataStore;return a.replace(/\x3c!--\{cke_protected\}([\s\S]+?)--\x3e/g,function(a,b){return decodeURIComponent(b)}).replace(/\{cke_protected_(\d+)\}/g,function(a,b){return c&&c[b]||""})}function M(a,b){var c=[],d=b.config.protectedSource,e=b._.dataStore||(b._.dataStore={id:1}),f=/<\!--\{cke_temp(comment)?\}(\d*?)--\x3e/g,d=[/|$)/gi,//gi,//gi].concat(d);a=a.replace(/\x3c!--[\s\S]*?--\x3e/g, function(a){return"\x3c!--{cke_tempcomment}"+(c.push(a)-1)+"--\x3e"});for(var n=0;n]+\s*=\s*(?:[^'"\s>]+|'[^']*'|"[^"]*"))|[^\s=\/>]+))+\s*\/?>/g,function(a){return a.replace(/\x3c!--\{cke_protected\}([^>]*)--\x3e/g, function(a,b){e[e.id]=decodeURIComponent(b);return"{cke_protected_"+e.id++ +"}"})});return a=a.replace(/<(title|iframe|textarea)([^>]*)>([\s\S]*?)<\/\1>/g,function(a,c,d,e){return"\x3c"+c+d+"\x3e"+m(r(e),b)+"\x3c/"+c+"\x3e"})}CKEDITOR.htmlDataProcessor=function(b){var c,f,n=this;this.editor=b;this.dataFilter=c=new CKEDITOR.htmlParser.filter;this.htmlFilter=f=new CKEDITOR.htmlParser.filter;this.writer=new CKEDITOR.htmlParser.basicWriter;c.addRules(G);c.addRules(B,{applyToAll:!0});c.addRules(a(b,"data"), {applyToAll:!0});f.addRules(N);f.addRules(q,{applyToAll:!0});f.addRules(a(b,"html"),{applyToAll:!0});b.on("toHtml",function(a){a=a.data;var c=a.dataValue,f,c=c.replace(O,""),c=M(c,b),c=e(c,S),c=u(c),c=e(c,Q),c=c.replace(H,"$1cke:$2"),c=c.replace(K,"\x3ccke:$1$2\x3e\x3c/cke:$1\x3e"),c=c.replace(/(]*>)(\r\n|\n)/g,"$1$2$2"),c=c.replace(/([^a-z0-9<\-])(on\w{3,})(?!>)/gi,"$1data-cke-"+CKEDITOR.rnd+"-$2");f=a.context||b.editable().getName();var n;CKEDITOR.env.ie&&9>CKEDITOR.env.version&&"pre"== f&&(f="div",c="\x3cpre\x3e"+c+"\x3c/pre\x3e",n=1);f=b.document.createElement(f);f.setHtml("a"+c);c=f.getHtml().substr(1);c=c.replace(new RegExp("data-cke-"+CKEDITOR.rnd+"-","ig"),"");n&&(c=c.replace(/^
|<\/pre>$/gi,""));c=c.replace(F,"$1$2");c=x(c);c=r(c);f=!1===a.fixForBody?!1:d(a.enterMode,b.config.autoParagraph);c=CKEDITOR.htmlParser.fragment.fromHtml(c,a.context,f);f&&(n=c,!n.children.length&&CKEDITOR.dtd[n.name][f]&&(f=new CKEDITOR.htmlParser.element(f),n.add(f)));a.dataValue=c},null,null,
5);b.on("toHtml",function(a){a.data.filter.applyTo(a.data.dataValue,!0,a.data.dontFilter,a.data.enterMode)&&b.fire("dataFiltered")},null,null,6);b.on("toHtml",function(a){a.data.dataValue.filterChildren(n.dataFilter,!0)},null,null,10);b.on("toHtml",function(a){a=a.data;var b=a.dataValue,c=new CKEDITOR.htmlParser.basicWriter;b.writeChildrenHtml(c);b=c.getHtml(!0);a.dataValue=t(b)},null,null,15);b.on("toDataFormat",function(a){var c=a.data.dataValue;a.data.enterMode!=CKEDITOR.ENTER_BR&&(c=c.replace(/^
/i, ""));a.data.dataValue=CKEDITOR.htmlParser.fragment.fromHtml(c,a.data.context,d(a.data.enterMode,b.config.autoParagraph))},null,null,5);b.on("toDataFormat",function(a){a.data.dataValue.filterChildren(n.htmlFilter,!0)},null,null,10);b.on("toDataFormat",function(a){a.data.filter.applyTo(a.data.dataValue,!1,!0)},null,null,11);b.on("toDataFormat",function(a){var c=a.data.dataValue,d=n.writer;d.reset();c.writeChildrenHtml(d);c=d.getHtml(!0);c=r(c);c=m(c,b);a.data.dataValue=c},null,null,15)};CKEDITOR.htmlDataProcessor.prototype= {toHtml:function(a,b,c,d){var e=this.editor,f,n,q,w;b&&"object"==typeof b?(f=b.context,c=b.fixForBody,d=b.dontFilter,n=b.filter,q=b.enterMode,w=b.protectedWhitespaces):f=b;f||null===f||(f=e.editable().getName());return e.fire("toHtml",{dataValue:a,context:f,fixForBody:c,dontFilter:d,filter:n||e.filter,enterMode:q||e.enterMode,protectedWhitespaces:w}).dataValue},toDataFormat:function(a,b){var c,d,e;b&&(c=b.context,d=b.filter,e=b.enterMode);c||null===c||(c=this.editor.editable().getName());return this.editor.fire("toDataFormat", {dataValue:a,filter:d||this.editor.filter,context:c,enterMode:e||this.editor.enterMode}).dataValue}};var y=/(?: |\xa0)$/,J="{cke_protected}",E=CKEDITOR.dtd,p="caption colgroup col thead tfoot tbody".split(" "),D=CKEDITOR.tools.extend({},E.$blockLimit,E.$block),G={elements:{input:g,textarea:g}},B={attributeNames:[[/^on/,"data-cke-pa-on"],[/^srcdoc/,"data-cke-pa-srcdoc"],[/^data-cke-expando$/,""]],elements:{iframe:function(a){if(a.attributes&&a.attributes.src){var b=a.attributes.src.toLowerCase().replace(/[^a-z]/gi, "");if(0===b.indexOf("javascript")||0===b.indexOf("data"))a.attributes["data-cke-pa-src"]=a.attributes.src,delete a.attributes.src}}}},N={elements:{embed:function(a){var b=a.parent;if(b&&"object"==b.name){var c=b.attributes.width,b=b.attributes.height;c&&(a.attributes.width=c);b&&(a.attributes.height=b)}},a:function(a){var b=a.attributes;if(!(a.children.length||b.name||b.id||a.attributes["data-cke-saved-name"]))return!1}}},q={elementNames:[[/^cke:/,""],[/^\?xml:namespace$/,""]],attributeNames:[[/^data-cke-(saved|pa)-/, ""],[/^data-cke-.*/,""],["hidefocus",""]],elements:{$:function(a){var b=a.attributes;if(b){if(b["data-cke-temp"])return!1;for(var c=["name","href","src"],d,e=0;ed? 1:-1})},param:function(a){a.children=[];a.isEmpty=!0;return a},span:function(a){"Apple-style-span"==a.attributes["class"]&&delete a.name},html:function(a){delete a.attributes.contenteditable;delete a.attributes["class"]},body:function(a){delete a.attributes.spellcheck;delete a.attributes.contenteditable},style:function(a){var b=a.children[0];b&&b.value&&(b.value=CKEDITOR.tools.trim(b.value));a.attributes.type||(a.attributes.type="text/css")},title:function(a){var b=a.children[0];!b&&k(a,b=new CKEDITOR.htmlParser.text); b.value=a.attributes["data-cke-title"]||""},input:l,textarea:l},attributes:{"class":function(a){return CKEDITOR.tools.ltrim(a.replace(/(?:^|\s+)cke_[^\s]*/g,""))||!1}}};CKEDITOR.env.ie&&(q.attributes.style=function(a){return a.replace(/(^|;)([^\:]+)/g,function(a){return a.toLowerCase()})});var C=/<(a|area|img|input|source)\b([^>]*)>/gi,w=/([\w-:]+)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi,v=/^(href|src|name)$/i,Q=/(?:])[^>]*>[\s\S]*?<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi, S=/(])[^>]*>)([\s\S]*?)(?:<\/textarea>)/gi,n=/([^<]*)<\/cke:encoded>/gi,O=new RegExp("("+z("\x3ccke:encoded\x3e")+"(.*?)"+z("\x3c/cke:encoded\x3e")+")|("+z("\x3c")+z("/")+"?"+z("cke:encoded\x3e")+")","gi"),H=/(<\/?)((?:object|embed|param|html|body|head|title)([\s][^>]*)?>)/gi,F=/(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi,K=/]*?)\/?>(?!\s*<\/cke:\1)/gi})();"use strict"; CKEDITOR.htmlParser.element=function(a,d){this.name=a;this.attributes=d||{};this.children=[];var b=a||"",c=b.match(/^cke:(.*)/);c&&(b=c[1]);b=!!(CKEDITOR.dtd.$nonBodyContent[b]||CKEDITOR.dtd.$block[b]||CKEDITOR.dtd.$listItem[b]||CKEDITOR.dtd.$tableContent[b]||CKEDITOR.dtd.$nonEditable[b]||"br"==b);this.isEmpty=!!CKEDITOR.dtd.$empty[a];this.isUnknown=!CKEDITOR.dtd[a];this._={isBlockLike:b,hasInlineStarted:this.isEmpty||!b}}; CKEDITOR.htmlParser.cssStyle=function(a){var d={};((a instanceof CKEDITOR.htmlParser.element?a.attributes.style:a)||"").replace(/"/g,'"').replace(/\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g,function(a,c,f){"font-family"==c&&(f=f.replace(/["']/g,""));d[c.toLowerCase()]=f});return{rules:d,populate:function(a){var c=this.toString();c&&(a instanceof CKEDITOR.dom.element?a.setAttribute("style",c):a instanceof CKEDITOR.htmlParser.element?a.attributes.style=c:a.style=c)},toString:function(){var a=[],c; for(c in d)d[c]&&a.push(c,":",d[c],";");return a.join("")}}}; (function(){function a(a){return function(b){return b.type==CKEDITOR.NODE_ELEMENT&&("string"==typeof a?b.name==a:b.name in a)}}var d=function(a,b){a=a[0];b=b[0];return ab?1:0},b=CKEDITOR.htmlParser.fragment.prototype;CKEDITOR.htmlParser.element.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_ELEMENT,add:b.add,clone:function(){return new CKEDITOR.htmlParser.element(this.name,this.attributes)},filter:function(a,b){var d=this,k,g;b=d.getFilterContext(b);if(!d.parent)a.onRoot(b, d);for(;;){k=d.name;if(!(g=a.onElementName(b,k)))return this.remove(),!1;d.name=g;if(!(d=a.onElement(b,d)))return this.remove(),!1;if(d!==this)return this.replaceWith(d),!1;if(d.name==k)break;if(d.type!=CKEDITOR.NODE_ELEMENT)return this.replaceWith(d),!1;if(!d.name)return this.replaceWithChildren(),!1}k=d.attributes;var l,u;for(l in k){for(g=k[l];;)if(u=a.onAttributeName(b,l))if(u!=l)delete k[l],l=u;else break;else{delete k[l];break}u&&(!1===(g=a.onAttribute(b,d,u,g))?delete k[u]:k[u]=g)}d.isEmpty|| this.filterChildren(a,!1,b);return!0},filterChildren:b.filterChildren,writeHtml:function(a,b){b&&this.filter(b);var h=this.name,k=[],g=this.attributes,l,u;a.openTag(h,g);for(l in g)k.push([l,g[l]]);a.sortAttributes&&k.sort(d);l=0;for(u=k.length;lCKEDITOR.env.version||CKEDITOR.env.quirks))this.hasFocus&&(this.focus(),b());else if(this.hasFocus)this.focus(),a();else this.once("focus",function(){a()},null,null,-999)},getHtmlFromRange:function(a){if(a.collapsed)return new CKEDITOR.dom.documentFragment(a.document); a={doc:this.getDocument(),range:a.clone()};J.eol.detect(a,this);J.bogus.exclude(a);J.cell.shrink(a);a.fragment=a.range.cloneContents();J.tree.rebuild(a,this);J.eol.fix(a,this);return new CKEDITOR.dom.documentFragment(a.fragment.$)},extractHtmlFromRange:function(a,b){var c=E,d={range:a,doc:a.document},e=this.getHtmlFromRange(a);if(a.collapsed)return a.optimize(),e;a.enlarge(CKEDITOR.ENLARGE_INLINE,1);c.table.detectPurge(d);d.bookmark=a.createBookmark();delete d.range;var f=this.editor.createRange(); f.moveToPosition(d.bookmark.startNode,CKEDITOR.POSITION_BEFORE_START);d.targetBookmark=f.createBookmark();c.list.detectMerge(d,this);c.table.detectRanges(d,this);c.block.detectMerge(d,this);d.tableContentsRanges?(c.table.deleteRanges(d),a.moveToBookmark(d.bookmark),d.range=a):(a.moveToBookmark(d.bookmark),d.range=a,a.extractContents(c.detectExtractMerge(d)));a.moveToBookmark(d.targetBookmark);a.optimize();c.fixUneditableRangePosition(a);c.list.merge(d,this);c.table.purge(d,this);c.block.merge(d,this); if(b){c=a.startPath();if(d=a.checkStartOfBlock()&&a.checkEndOfBlock()&&c.block&&!a.root.equals(c.block)){a:{var d=c.block.getElementsByTag("span"),f=0,g;if(d)for(;g=d.getItem(f++);)if(!t(g)){d=!0;break a}d=!1}d=!d}d&&(a.moveToPosition(c.block,CKEDITOR.POSITION_BEFORE_START),c.block.remove())}else c.autoParagraph(this.editor,a),z(a.startContainer)&&a.startContainer.appendBogus();a.startContainer.mergeSiblings();return e},setup:function(){var a=this.editor;this.attachListener(a,"beforeGetData",function(){var b= this.getData();this.is("textarea")||!1!==a.config.ignoreEmptyParagraph&&(b=b.replace(r,function(a,b){return b}));a.setData(b,null,1)},this);this.attachListener(a,"getSnapshot",function(a){a.data=this.getData(1)},this);this.attachListener(a,"afterSetData",function(){this.setData(a.getData(1))},this);this.attachListener(a,"loadSnapshot",function(a){this.setData(a.data,1)},this);this.attachListener(a,"beforeFocus",function(){var b=a.getSelection();(b=b&&b.getNative())&&"Control"==b.type||this.focus()}, this);this.attachListener(a,"insertHtml",function(a){this.insertHtml(a.data.dataValue,a.data.mode,a.data.range)},this);this.attachListener(a,"insertElement",function(a){this.insertElement(a.data)},this);this.attachListener(a,"insertText",function(a){this.insertText(a.data)},this);this.setReadOnly(a.readOnly);this.attachClass("cke_editable");a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?this.attachClass("cke_editable_inline"):a.elementMode!=CKEDITOR.ELEMENT_MODE_REPLACE&&a.elementMode!=CKEDITOR.ELEMENT_MODE_APPENDTO|| this.attachClass("cke_editable_themed");this.attachClass("cke_contents_"+a.config.contentsLangDirection);a.keystrokeHandler.blockedKeystrokes[8]=+a.readOnly;a.keystrokeHandler.attach(this);this.on("blur",function(){this.hasFocus=!1},null,null,-1);this.on("focus",function(){this.hasFocus=!0},null,null,-1);if(CKEDITOR.env.webkit)this.on("scroll",function(){a._.previousScrollTop=a.editable().$.scrollTop},null,null,-1);if(CKEDITOR.env.edge&&14CKEDITOR.env.version?q.$.styleSheet.cssText=k:q.setText(k)):(k=f.appendStyleText(k),k=new CKEDITOR.dom.element(k.ownerNode||k.owningElement),g.setCustomData("stylesheet",k),k.data("cke-temp",1))}g=f.getCustomData("stylesheet_ref")||0;f.setCustomData("stylesheet_ref", g+1);this.setCustomData("cke_includeReadonly",!a.config.disableReadonlyStyling);this.attachListener(this,"click",function(a){a=a.data;var b=(new CKEDITOR.dom.elementPath(a.getTarget(),this)).contains("a");b&&2!=a.$.button&&b.isReadOnly()&&a.preventDefault()});var C={8:1,46:1};this.attachListener(a,"key",function(b){if(a.readOnly)return!0;var c=b.data.domEvent.getKey(),d;b=a.getSelection();if(0!==b.getRanges().length){if(c in C){var e,f=b.getRanges()[0],q=f.startPath(),g,k,l,c=8==c;CKEDITOR.env.ie&& 11>CKEDITOR.env.version&&(e=b.getSelectedElement())||(e=h(b))?(a.fire("saveSnapshot"),f.moveToPosition(e,CKEDITOR.POSITION_BEFORE_START),e.remove(),f.select(),a.fire("saveSnapshot"),d=1):f.collapsed&&((g=q.block)&&(l=g[c?"getPrevious":"getNext"](x))&&l.type==CKEDITOR.NODE_ELEMENT&&l.is("table")&&f[c?"checkStartOfBlock":"checkEndOfBlock"]()?(a.fire("saveSnapshot"),f[c?"checkEndOfBlock":"checkStartOfBlock"]()&&g.remove(),f["moveToElementEdit"+(c?"End":"Start")](l),f.select(),a.fire("saveSnapshot"), d=1):q.blockLimit&&q.blockLimit.is("td")&&(k=q.blockLimit.getAscendant("table"))&&f.checkBoundaryOfElement(k,c?CKEDITOR.START:CKEDITOR.END)&&(l=k[c?"getPrevious":"getNext"](x))?(a.fire("saveSnapshot"),f["moveToElementEdit"+(c?"End":"Start")](l),f.checkStartOfBlock()&&f.checkEndOfBlock()?l.remove():f.select(),a.fire("saveSnapshot"),d=1):(k=q.contains(["td","th","caption"]))&&f.checkBoundaryOfElement(k,c?CKEDITOR.START:CKEDITOR.END)&&(d=1))}return!d}});a.blockless&&CKEDITOR.env.ie&&CKEDITOR.env.needsBrFiller&& this.attachListener(this,"keyup",function(b){b.data.getKeystroke()in C&&!this.getFirst(c)&&(this.appendBogus(),b=a.createRange(),b.moveToPosition(this,CKEDITOR.POSITION_AFTER_START),b.select())});this.attachListener(this,"dblclick",function(b){if(a.readOnly)return!1;b={element:b.data.getTarget()};a.fire("doubleclick",b)});CKEDITOR.env.ie&&this.attachListener(this,"click",b);CKEDITOR.env.ie&&!CKEDITOR.env.edge||this.attachListener(this,"mousedown",function(b){var c=b.data.getTarget();c.is("img","hr", "input","textarea","select")&&!c.isReadOnly()&&(a.getSelection().selectElement(c),c.is("input","textarea","select")&&b.data.preventDefault())});CKEDITOR.env.edge&&this.attachListener(this,"mouseup",function(b){(b=b.data.getTarget())&&b.is("img")&&!b.isReadOnly()&&a.getSelection().selectElement(b)});CKEDITOR.env.gecko&&this.attachListener(this,"mouseup",function(b){if(2==b.data.$.button&&(b=b.data.getTarget(),!b.getAscendant("table")&&!b.getOuterHtml().replace(r,""))){var c=a.createRange();c.moveToElementEditStart(b); c.select(!0)}});CKEDITOR.env.webkit&&(this.attachListener(this,"click",function(a){a.data.getTarget().is("input","select")&&a.data.preventDefault()}),this.attachListener(this,"mouseup",function(a){a.data.getTarget().is("input","textarea")&&a.data.preventDefault()}));CKEDITOR.env.webkit&&this.attachListener(a,"key",function(b){if(a.readOnly)return!0;var c=b.data.domEvent.getKey();if(c in C&&(b=a.getSelection(),0!==b.getRanges().length)){var c=8==c,d=b.getRanges()[0];b=d.startPath();if(d.collapsed)a:{var f= b.block;if(f&&d[c?"checkStartOfBlock":"checkEndOfBlock"]()&&d.moveToClosestEditablePosition(f,!c)&&d.collapsed){if(d.startContainer.type==CKEDITOR.NODE_ELEMENT){var n=d.startContainer.getChild(d.startOffset-(c?1:0));if(n&&n.type==CKEDITOR.NODE_ELEMENT&&n.is("hr")){a.fire("saveSnapshot");n.remove();b=!0;break a}}d=d.startPath().block;if(!d||d&&d.contains(f))b=void 0;else{a.fire("saveSnapshot");var q;(q=(c?d:f).getBogus())&&q.remove();q=a.getSelection();n=q.createBookmarks();(c?f:d).moveChildren(c? d:f,!1);b.lastElement.mergeSiblings();e(f,d,!c);q.selectBookmarks(n);b=!0}}else b=!1}else c=d,q=b.block,d=c.endPath().block,q&&d&&!q.equals(d)?(a.fire("saveSnapshot"),(f=q.getBogus())&&f.remove(),c.enlarge(CKEDITOR.ENLARGE_INLINE),c.deleteContents(),d.getParent()&&(d.moveChildren(q,!1),b.lastElement.mergeSiblings(),e(q,d,!0)),c=a.getSelection().getRanges()[0],c.collapse(1),c.optimize(),""===c.startContainer.getHtml()&&c.startContainer.appendBogus(),c.select(),b=!0):b=!1;if(!b)return;a.getSelection().scrollIntoView(); a.fire("saveSnapshot");return!1}},this,null,100)}},getUniqueId:function(){var a;try{this._.expandoNumber=a=CKEDITOR.dom.domObject.prototype.getUniqueId.call(this)}catch(b){a=this._&&this._.expandoNumber}return a}},_:{cleanCustomData:function(){this.removeClass("cke_editable");this.restoreAttrs();for(var a=this.removeCustomData("classes");a&&a.length;)this.removeClass(a.pop());if(!this.is("textarea")){var a=this.getDocument(),b=a.getHead();if(b.getCustomData("stylesheet")){var c=a.getCustomData("stylesheet_ref"); --c?a.setCustomData("stylesheet_ref",c):(a.removeCustomData("stylesheet_ref"),b.removeCustomData("stylesheet").remove())}}}}});CKEDITOR.editor.prototype.editable=function(a){var b=this._.editable;if(b&&a)return 0;if(!arguments.length)return b;a?b=a instanceof CKEDITOR.editable?a:new CKEDITOR.editable(this,a):(b&&b.detach(),b=null);return this._.editable=b};CKEDITOR.on("instanceLoaded",function(b){var c=b.editor;c.on("insertElement",function(a){a=a.data;a.type==CKEDITOR.NODE_ELEMENT&&(a.is("input")|| a.is("textarea"))&&("false"!=a.getAttribute("contentEditable")&&a.data("cke-editable",a.hasAttribute("contenteditable")?"true":"1"),a.setAttribute("contentEditable",!1))});c.on("selectionChange",function(b){if(!c.readOnly){var d=c.getSelection();d&&!d.isLocked&&(d=c.checkDirty(),c.fire("lockSnapshot"),a(b),c.fire("unlockSnapshot"),!d&&c.resetDirty())}})});CKEDITOR.on("instanceCreated",function(a){var b=a.editor;b.on("mode",function(){var a=b.editable();if(a&&a.isInline()){var c=b.title;a.changeAttr("role", "textbox");a.changeAttr("aria-multiline","true");a.changeAttr("aria-label",c);c&&a.changeAttr("title",c);var d=b.fire("ariaEditorHelpLabel",{}).label;if(d&&(c=this.ui.space(this.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"top":"contents"))){var e=CKEDITOR.tools.getNextId(),d=CKEDITOR.dom.element.createFromHtml('\x3cspan id\x3d"'+e+'" class\x3d"cke_voice_label"\x3e'+d+"\x3c/span\x3e");c.append(d);a.changeAttr("aria-describedby",e)}}})});CKEDITOR.addCss(".cke_editable{cursor:text}.cke_editable img,.cke_editable input,.cke_editable textarea{cursor:default}"); x=CKEDITOR.dom.walker.whitespaces(!0);t=CKEDITOR.dom.walker.bookmark(!1,!0);z=CKEDITOR.dom.walker.empty();I=CKEDITOR.dom.walker.bogus();r=/(^|]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:]*>| |\u00A0| )?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi;m=function(){function a(b){return b.type==CKEDITOR.NODE_ELEMENT}function b(c,d){var e,f,q,g,h=[],k=d.range.startContainer;e=d.range.startPath();for(var k=w[k.getName()],C=0,v=c.getChildren(),l=v.count(),m=-1,u=-1,Q=0,B=e.contains(w.$list);C< l;++C)e=v.getItem(C),a(e)?(q=e.getName(),B&&q in CKEDITOR.dtd.$list?h=h.concat(b(e,d)):(g=!!k[q],"br"!=q||!e.data("cke-eol")||C&&C!=l-1||(Q=(f=C?h[C-1].node:v.getItem(C+1))&&(!a(f)||!f.is("br")),f=f&&a(f)&&w.$block[f.getName()]),-1!=m||g||(m=C),g||(u=C),h.push({isElement:1,isLineBreak:Q,isBlock:e.isBlockBoundary(),hasBlockSibling:f,node:e,name:q,allowed:g}),f=Q=0)):h.push({isElement:0,node:e,allowed:1});-1CKEDITOR.env.version&&d.getChildCount()&&d.getFirst().remove())}return function(d){var e=d.startContainer,f=e.getAscendant("table", 1),g=!1;c(f.getElementsByTag("td"));c(f.getElementsByTag("th"));f=d.clone();f.setStart(e,0);f=a(f).lastBackward();f||(f=d.clone(),f.setEndAt(e,CKEDITOR.POSITION_BEFORE_END),f=a(f).lastForward(),g=!0);f||(f=e);f.is("table")?(d.setStartAt(f,CKEDITOR.POSITION_BEFORE_START),d.collapse(!0),f.remove()):(f.is({tbody:1,thead:1,tfoot:1})&&(f=b(f,"tr",g)),f.is("tr")&&(f=b(f,f.getParent().is("thead")?"th":"td",g)),(e=f.getBogus())&&e.remove(),d.moveToPosition(f,g?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END))}}(); y=function(){function a(b){b=new CKEDITOR.dom.walker(b);b.guard=function(a,b){if(b)return!1;if(a.type==CKEDITOR.NODE_ELEMENT)return a.is(CKEDITOR.dtd.$list)||a.is(CKEDITOR.dtd.$listItem)};b.evaluator=function(a){return a.type==CKEDITOR.NODE_ELEMENT&&a.is(CKEDITOR.dtd.$listItem)};return b}return function(b){var c=b.startContainer,d=!1,e;e=b.clone();e.setStart(c,0);e=a(e).lastBackward();e||(e=b.clone(),e.setEndAt(c,CKEDITOR.POSITION_BEFORE_END),e=a(e).lastForward(),d=!0);e||(e=c);e.is(CKEDITOR.dtd.$list)? (b.setStartAt(e,CKEDITOR.POSITION_BEFORE_START),b.collapse(!0),e.remove()):((c=e.getBogus())&&c.remove(),b.moveToPosition(e,d?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END),b.select())}}();J={eol:{detect:function(a,b){var c=a.range,d=c.clone(),e=c.clone(),f=new CKEDITOR.dom.elementPath(c.startContainer,b),g=new CKEDITOR.dom.elementPath(c.endContainer,b);d.collapse(1);e.collapse();f.block&&d.checkBoundaryOfElement(f.block,CKEDITOR.END)&&(c.setStartAfter(f.block),a.prependEolBr=1);g.block&& e.checkBoundaryOfElement(g.block,CKEDITOR.START)&&(c.setEndBefore(g.block),a.appendEolBr=1)},fix:function(a,b){var c=b.getDocument(),d;a.appendEolBr&&(d=this.createEolBr(c),a.fragment.append(d));!a.prependEolBr||d&&!d.getPrevious()||a.fragment.append(this.createEolBr(c),1)},createEolBr:function(a){return a.createElement("br",{attributes:{"data-cke-eol":1}})}},bogus:{exclude:function(a){var b=a.range.getBoundaryNodes(),c=b.startNode,b=b.endNode;!b||!I(b)||c&&c.equals(b)||a.range.setEndBefore(b)}}, tree:{rebuild:function(a,b){var c=a.range,d=c.getCommonAncestor(),e=new CKEDITOR.dom.elementPath(d,b),f=new CKEDITOR.dom.elementPath(c.startContainer,b),c=new CKEDITOR.dom.elementPath(c.endContainer,b),g;d.type==CKEDITOR.NODE_TEXT&&(d=d.getParent());if(e.blockLimit.is({tr:1,table:1})){var h=e.contains("table").getParent();g=function(a){return!a.equals(h)}}else if(e.block&&e.block.is(CKEDITOR.dtd.$listItem)&&(f=f.contains(CKEDITOR.dtd.$list),c=c.contains(CKEDITOR.dtd.$list),!f.equals(c))){var k=e.contains(CKEDITOR.dtd.$list).getParent(); g=function(a){return!a.equals(k)}}g||(g=function(a){return!a.equals(e.block)&&!a.equals(e.blockLimit)});this.rebuildFragment(a,b,d,g)},rebuildFragment:function(a,b,c,d){for(var e;c&&!c.equals(b)&&d(c);)e=c.clone(0,1),a.fragment.appendTo(e),a.fragment=e,c=c.getParent()}},cell:{shrink:function(a){a=a.range;var b=a.startContainer,c=a.endContainer,d=a.startOffset,e=a.endOffset;b.type==CKEDITOR.NODE_ELEMENT&&b.equals(c)&&b.is("tr")&&++d==e&&a.shrink(CKEDITOR.SHRINK_TEXT)}}};E=function(){function a(b,c){var d= b.getParent();if(d.is(CKEDITOR.dtd.$inline))b[c?"insertBefore":"insertAfter"](d)}function b(c,d,e){a(d);a(e,1);for(var f;f=e.getNext();)f.insertAfter(d),d=f;z(c)&&c.remove()}function c(a,b){var d=new CKEDITOR.dom.range(a);d.setStartAfter(b.startNode);d.setEndBefore(b.endNode);return d}return{list:{detectMerge:function(a,b){var d=c(b,a.bookmark),e=d.startPath(),f=d.endPath(),g=e.contains(CKEDITOR.dtd.$list),h=f.contains(CKEDITOR.dtd.$list);a.mergeList=g&&h&&g.getParent().equals(h.getParent())&&!g.equals(h); a.mergeListItems=e.block&&f.block&&e.block.is(CKEDITOR.dtd.$listItem)&&f.block.is(CKEDITOR.dtd.$listItem);if(a.mergeList||a.mergeListItems)d=d.clone(),d.setStartBefore(a.bookmark.startNode),d.setEndAfter(a.bookmark.endNode),a.mergeListBookmark=d.createBookmark()},merge:function(a,c){if(a.mergeListBookmark){var d=a.mergeListBookmark.startNode,e=a.mergeListBookmark.endNode,f=new CKEDITOR.dom.elementPath(d,c),g=new CKEDITOR.dom.elementPath(e,c);if(a.mergeList){var h=f.contains(CKEDITOR.dtd.$list),k= g.contains(CKEDITOR.dtd.$list);h.equals(k)||(k.moveChildren(h),k.remove())}a.mergeListItems&&(f=f.contains(CKEDITOR.dtd.$listItem),g=g.contains(CKEDITOR.dtd.$listItem),f.equals(g)||b(g,d,e));d.remove();e.remove()}}},block:{detectMerge:function(a,b){if(!a.tableContentsRanges&&!a.mergeListBookmark){var c=new CKEDITOR.dom.range(b);c.setStartBefore(a.bookmark.startNode);c.setEndAfter(a.bookmark.endNode);a.mergeBlockBookmark=c.createBookmark()}},merge:function(a,c){if(a.mergeBlockBookmark&&!a.purgeTableBookmark){var d= a.mergeBlockBookmark.startNode,e=a.mergeBlockBookmark.endNode,f=new CKEDITOR.dom.elementPath(d,c),g=new CKEDITOR.dom.elementPath(e,c),f=f.block,g=g.block;f&&g&&!f.equals(g)&&b(g,d,e);d.remove();e.remove()}}},table:function(){function a(c){var e=[],f,g=new CKEDITOR.dom.walker(c),h=c.startPath().contains(d),n=c.endPath().contains(d),k={};g.guard=function(a,g){if(a.type==CKEDITOR.NODE_ELEMENT){var l="visited_"+(g?"out":"in");if(a.getCustomData(l))return;CKEDITOR.dom.element.setMarker(k,a,l,1)}if(g&& h&&a.equals(h))f=c.clone(),f.setEndAt(h,CKEDITOR.POSITION_BEFORE_END),e.push(f);else if(!g&&n&&a.equals(n))f=c.clone(),f.setStartAt(n,CKEDITOR.POSITION_AFTER_START),e.push(f);else{if(l=!g)l=a.type==CKEDITOR.NODE_ELEMENT&&a.is(d)&&(!h||b(a,h))&&(!n||b(a,n));if(!l&&(l=g))if(a.is(d))var l=h&&h.getAscendant("table",!0),m=n&&n.getAscendant("table",!0),p=a.getAscendant("table",!0),l=l&&l.contains(p)||m&&m.contains(p);else l=void 0;l&&(f=c.clone(),f.selectNodeContents(a),e.push(f))}};g.lastForward();CKEDITOR.dom.element.clearAllMarkers(k); return e}function b(a,c){var d=CKEDITOR.POSITION_CONTAINS+CKEDITOR.POSITION_IS_CONTAINED,e=a.getPosition(c);return e===CKEDITOR.POSITION_IDENTICAL?!1:0===(e&d)}var d={td:1,th:1,caption:1};return{detectPurge:function(a){var b=a.range,c=b.clone();c.enlarge(CKEDITOR.ENLARGE_ELEMENT);var c=new CKEDITOR.dom.walker(c),e=0;c.evaluator=function(a){a.type==CKEDITOR.NODE_ELEMENT&&a.is(d)&&++e};c.checkForward();if(1f&&e&&e.intersectsNode(c.$)){var g=[{node:d.anchorNode,offset:d.anchorOffset}, {node:d.focusNode,offset:d.focusOffset}];d.anchorNode==c.$&&d.anchorOffset>f&&(g[0].offset-=f);d.focusNode==c.$&&d.focusOffset>f&&(g[1].offset-=f)}}c.setText(t(c.getText(),1));g&&(c=a.getDocument().$,d=c.getSelection(),c=c.createRange(),c.setStart(g[0].node,g[0].offset),c.collapse(!0),d.removeAllRanges(),d.addRange(c),d.extend(g[1].node,g[1].offset))}}function t(a,b){return b?a.replace(J,function(a,b){return b?" ":""}):a.replace(y,"")}function z(a,b){var c=b&&CKEDITOR.tools.htmlEncode(b)||"\x26nbsp;", c=CKEDITOR.dom.element.createFromHtml('\x3cdiv data-cke-hidden-sel\x3d"1" data-cke-temp\x3d"1" style\x3d"'+(CKEDITOR.env.ie&&14>CKEDITOR.env.version?"display:none":"position:fixed;top:0;left:-1000px;width:0;height:0;overflow:hidden;")+'"\x3e'+c+"\x3c/div\x3e",a.document);a.fire("lockSnapshot");a.editable().append(c);var d=a.getSelection(1),e=a.createRange(),f=d.root.on("selectionchange",function(a){a.cancel()},null,null,0);e.setStartAt(c,CKEDITOR.POSITION_AFTER_START);e.setEndAt(c,CKEDITOR.POSITION_BEFORE_END); d.selectRanges([e]);f.removeListener();a.fire("unlockSnapshot");a._.hiddenSelectionContainer=c}function I(a){var b={37:1,39:1,8:1,46:1};return function(c){var d=c.data.getKeystroke();if(b[d]){var e=a.getSelection().getRanges(),f=e[0];1==e.length&&f.collapsed&&(d=f[38>d?"getPreviousEditableNode":"getNextEditableNode"]())&&d.type==CKEDITOR.NODE_ELEMENT&&"false"==d.getAttribute("contenteditable")&&(a.getSelection().fake(d),c.data.preventDefault(),c.cancel())}}}function r(a){for(var b=0;b=d.getLength()?h.setStartAfter(d):h.setStartBefore(d));e&&e.type==CKEDITOR.NODE_TEXT&&(g?h.setEndAfter(e): h.setEndBefore(e));d=new CKEDITOR.dom.walker(h);d.evaluator=function(d){if(d.type==CKEDITOR.NODE_ELEMENT&&d.isReadOnly()){var e=c.clone();c.setEndBefore(d);c.collapsed&&a.splice(b--,1);d.getPosition(h.endContainer)&CKEDITOR.POSITION_CONTAINS||(e.setStartAfter(d),e.collapsed||a.splice(b+1,0,e));return!0}return!1};d.next()}}return a}var m="function"!=typeof window.getSelection,M=1,y=CKEDITOR.tools.repeat("​",7),J=new RegExp(y+"( )?","g"),E,p,D,G=CKEDITOR.dom.walker.invisible(1),B=function(){function a(b){return function(a){var c= a.editor.createRange();c.moveToClosestEditablePosition(a.selected,b)&&a.editor.getSelection().selectRanges([c]);return!1}}function b(a){return function(b){var c=b.editor,d=c.createRange(),e;if(!c.readOnly)return(e=d.moveToClosestEditablePosition(b.selected,a))||(e=d.moveToClosestEditablePosition(b.selected,!a)),e&&c.getSelection().selectRanges([d]),c.fire("saveSnapshot"),b.selected.remove(),e||(d.moveToElementEditablePosition(c.editable()),c.getSelection().selectRanges([d])),c.fire("saveSnapshot"), !1}}var c=a(),d=a(1);return{37:c,38:c,39:d,40:d,8:b(),46:b(1)}}();CKEDITOR.on("instanceCreated",function(a){function b(){var a=c.getSelection();a&&a.removeAllRanges()}var c=a.editor;c.on("contentDom",function(){function a(){p=new CKEDITOR.dom.selection(c.getSelection());p.lock()}function b(){f.removeListener("mouseup",b);l.removeListener("mouseup",b);var a=CKEDITOR.document.$.selection,c=a.createRange();"None"!=a.type&&c.parentElement()&&c.parentElement().ownerDocument==e.$&&c.select()}function d(a){a= a.getRanges()[0];return a?(a=a.startContainer.getAscendant(function(a){return a.type==CKEDITOR.NODE_ELEMENT&&a.hasAttribute("contenteditable")},!0))&&"false"===a.getAttribute("contenteditable")?a:null:null}var e=c.document,f=CKEDITOR.document,g=c.editable(),q=e.getBody(),l=e.getDocumentElement(),A=g.isInline(),C,p;CKEDITOR.env.gecko&&g.attachListener(g,"focus",function(a){a.removeListener();0!==C&&(a=c.getSelection().getNative())&&a.isCollapsed&&a.anchorNode==g.$&&(a=c.createRange(),a.moveToElementEditStart(g), a.select())},null,null,-2);g.attachListener(g,CKEDITOR.env.webkit||CKEDITOR.env.gecko?"focusin":"focus",function(){if(C&&(CKEDITOR.env.webkit||CKEDITOR.env.gecko)){C=c._.previousActive&&c._.previousActive.equals(e.getActive());var a=null!=c._.previousScrollTop&&c._.previousScrollTop!=g.$.scrollTop;CKEDITOR.env.webkit&&C&&a&&(g.$.scrollTop=c._.previousScrollTop)}c.unlockSelection(C);C=0},null,null,-1);g.attachListener(g,"mousedown",function(){C=0});if(CKEDITOR.env.ie||CKEDITOR.env.gecko||A)m?g.attachListener(g, "beforedeactivate",a,null,null,-1):g.attachListener(c,"selectionCheck",a,null,null,-1),g.attachListener(g,CKEDITOR.env.webkit||CKEDITOR.env.gecko?"focusout":"blur",function(){var a=p&&(p.isFake||2>p.getRanges());CKEDITOR.env.gecko&&!A&&a||(c.lockSelection(p),C=1)},null,null,-1),g.attachListener(g,"mousedown",function(){C=0});if(CKEDITOR.env.ie&&!A){var u;g.attachListener(g,"mousedown",function(a){2==a.data.$.button&&((a=c.document.getSelection())&&a.getType()!=CKEDITOR.SELECTION_NONE||(u=c.window.getScrollPosition()))}); g.attachListener(g,"mouseup",function(a){2==a.data.$.button&&u&&(c.document.$.documentElement.scrollLeft=u.x,c.document.$.documentElement.scrollTop=u.y);u=null});if("BackCompat"!=e.$.compatMode){if(CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat){var r,t;l.on("mousedown",function(a){function b(a){a=a.data.$;if(r){var c=q.$.createTextRange();try{c.moveToPoint(a.clientX,a.clientY)}catch(d){}r.setEndPoint(0>t.compareEndPoints("StartToStart",c)?"EndToEnd":"StartToStart",c);r.select()}}function c(){l.removeListener("mousemove", b);f.removeListener("mouseup",c);l.removeListener("mouseup",c);r.select()}a=a.data;if(a.getTarget().is("html")&&a.$.yCKEDITOR.env.version)l.on("mousedown",function(a){a.data.getTarget().is("html")&&(f.on("mouseup",b),l.on("mouseup",b))})}}g.attachListener(g,"selectionchange",h, c);g.attachListener(g,"keyup",k,c);g.attachListener(g,"touchstart",k,c);g.attachListener(g,"touchend",k,c);CKEDITOR.env.ie&&g.attachListener(g,"keydown",function(a){var b=this.getSelection(1),c=d(b);c&&!c.equals(g)&&(b.selectElement(c),a.data.preventDefault())},c);g.attachListener(g,CKEDITOR.env.webkit||CKEDITOR.env.gecko?"focusin":"focus",function(){c.forceNextSelectionCheck();c.selectionChange(1)});if(A&&(CKEDITOR.env.webkit||CKEDITOR.env.gecko)){var z;g.attachListener(g,"mousedown",function(){z= 1});g.attachListener(e.getDocumentElement(),"mouseup",function(){z&&k.call(c);z=0})}else g.attachListener(CKEDITOR.env.ie?g:e.getDocumentElement(),"mouseup",k,c);CKEDITOR.env.webkit&&g.attachListener(e,"keydown",function(a){switch(a.data.getKey()){case 13:case 33:case 34:case 35:case 36:case 37:case 39:case 8:case 45:case 46:g.hasFocus&&x(g)}},null,null,-1);g.attachListener(g,"keydown",I(c),null,null,-1)});c.on("setData",function(){c.unlockSelection();CKEDITOR.env.webkit&&b()});c.on("contentDomUnload", function(){c.unlockSelection()});if(CKEDITOR.env.ie9Compat)c.on("beforeDestroy",b,null,null,9);c.on("dataReady",function(){delete c._.fakeSelection;delete c._.hiddenSelectionContainer;c.selectionChange(1)});c.on("loadSnapshot",function(){var a=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_ELEMENT),b=c.editable().getLast(a);b&&b.hasAttribute("data-cke-hidden-sel")&&(b.remove(),CKEDITOR.env.gecko&&(a=c.editable().getFirst(a))&&a.is("br")&&a.getAttribute("_moz_editor_bogus_node")&&a.remove())},null,null, 100);c.on("key",function(a){if("wysiwyg"==c.mode){var b=c.getSelection();if(b.isFake){var d=B[a.data.keyCode];if(d)return d({editor:c,selected:b.getSelectedElement(),selection:b,keyEvent:a})}}})});if(CKEDITOR.env.webkit)CKEDITOR.on("instanceReady",function(a){var b=a.editor;b.on("selectionChange",function(){var a=b.editable(),c=a.getCustomData("cke-fillingChar");c&&(c.getCustomData("ready")?(x(a),a.editor.fire("selectionCheck")):c.setCustomData("ready",1))},null,null,-1);b.on("beforeSetMode",function(){x(b.editable())}, null,null,-1);b.on("getSnapshot",function(a){a.data&&(a.data=t(a.data))},b,null,20);b.on("toDataFormat",function(a){a.data.dataValue=t(a.data.dataValue)},null,null,0)});CKEDITOR.editor.prototype.selectionChange=function(a){(a?h:k).call(this)};CKEDITOR.editor.prototype.getSelection=function(a){return!this._.savedSelection&&!this._.fakeSelection||a?(a=this.editable())&&"wysiwyg"==this.mode?new CKEDITOR.dom.selection(a):null:this._.savedSelection||this._.fakeSelection};CKEDITOR.editor.prototype.lockSelection= function(a){a=a||this.getSelection(1);return a.getType()!=CKEDITOR.SELECTION_NONE?(!a.isLocked&&a.lock(),this._.savedSelection=a,!0):!1};CKEDITOR.editor.prototype.unlockSelection=function(a){var b=this._.savedSelection;return b?(b.unlock(a),delete this._.savedSelection,!0):!1};CKEDITOR.editor.prototype.forceNextSelectionCheck=function(){delete this._.selectionPreviousPath};CKEDITOR.dom.document.prototype.getSelection=function(){return new CKEDITOR.dom.selection(this)};CKEDITOR.dom.range.prototype.select= function(){var a=this.root instanceof CKEDITOR.editable?this.root.editor.getSelection():new CKEDITOR.dom.selection(this.root);a.selectRanges([this]);return a};CKEDITOR.SELECTION_NONE=1;CKEDITOR.SELECTION_TEXT=2;CKEDITOR.SELECTION_ELEMENT=3;CKEDITOR.dom.selection=function(a){if(a instanceof CKEDITOR.dom.selection){var b=a;a=a.root}var c=a instanceof CKEDITOR.dom.element;this.rev=b?b.rev:M++;this.document=a instanceof CKEDITOR.dom.document?a:a.getDocument();this.root=c?a:this.document.getBody();this.isLocked= 0;this._={cache:{}};if(b)return CKEDITOR.tools.extend(this._.cache,b._.cache),this.isFake=b.isFake,this.isLocked=b.isLocked,this;a=this.getNative();var d,e;if(a)if(a.getRangeAt)d=(e=a.rangeCount&&a.getRangeAt(0))&&new CKEDITOR.dom.node(e.commonAncestorContainer);else{try{e=a.createRange()}catch(f){}d=e&&CKEDITOR.dom.element.get(e.item&&e.item(0)||e.parentElement())}if(!d||d.type!=CKEDITOR.NODE_ELEMENT&&d.type!=CKEDITOR.NODE_TEXT||!this.root.equals(d)&&!this.root.contains(d))this._.cache.type=CKEDITOR.SELECTION_NONE, this._.cache.startElement=null,this._.cache.selectedElement=null,this._.cache.selectedText="",this._.cache.ranges=new CKEDITOR.dom.rangeList;return this};var N={img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1,a:1,input:1,form:1,select:1,textarea:1,button:1,fieldset:1,thead:1,tfoot:1};CKEDITOR.tools.extend(CKEDITOR.dom.selection,{_removeFillingCharSequenceString:t,_createFillingCharSequenceNode:e,FILLING_CHAR_SEQUENCE:y});CKEDITOR.dom.selection.prototype={getNative:function(){return void 0!== this._.cache.nativeSel?this._.cache.nativeSel:this._.cache.nativeSel=m?this.document.$.selection:this.document.getWindow().$.getSelection()},getType:m?function(){var a=this._.cache;if(a.type)return a.type;var b=CKEDITOR.SELECTION_NONE;try{var c=this.getNative(),d=c.type;"Text"==d&&(b=CKEDITOR.SELECTION_TEXT);"Control"==d&&(b=CKEDITOR.SELECTION_ELEMENT);c.createRange().parentElement()&&(b=CKEDITOR.SELECTION_TEXT)}catch(e){}return a.type=b}:function(){var a=this._.cache;if(a.type)return a.type;var b= CKEDITOR.SELECTION_TEXT,c=this.getNative();if(!c||!c.rangeCount)b=CKEDITOR.SELECTION_NONE;else if(1==c.rangeCount){var c=c.getRangeAt(0),d=c.startContainer;d==c.endContainer&&1==d.nodeType&&1==c.endOffset-c.startOffset&&N[d.childNodes[c.startOffset].nodeName.toLowerCase()]&&(b=CKEDITOR.SELECTION_ELEMENT)}return a.type=b},getRanges:function(){var a=m?function(){function a(b){return(new CKEDITOR.dom.node(b)).getIndex()}var b=function(b,c){b=b.duplicate();b.collapse(c);var d=b.parentElement();if(!d.hasChildNodes())return{container:d, offset:0};for(var e=d.children,f,g,h=b.duplicate(),k=0,l=e.length-1,q=-1,m,p;k<=l;)if(q=Math.floor((k+l)/2),f=e[q],h.moveToElementText(f),m=h.compareEndPoints("StartToStart",b),0m)k=q+1;else return{container:d,offset:a(f)};if(-1==q||q==e.length-1&&0>m){h.moveToElementText(d);h.setEndPoint("StartToStart",b);h=h.text.replace(/(\r\n|\r)/g,"\n").length;e=d.childNodes;if(!h)return f=e[e.length-1],f.nodeType!=CKEDITOR.NODE_TEXT?{container:d,offset:e.length}:{container:f,offset:f.nodeValue.length}; for(d=e.length;0c.length?this.selectElement(b): this.selectRanges(c))}},reset:function(){this._.cache={};this.isFake=0;var a=this.root.editor;if(a&&a._.fakeSelection)if(this.rev==a._.fakeSelection.rev){delete a._.fakeSelection;var b=a._.hiddenSelectionContainer;if(b){var c=a.checkDirty();a.fire("lockSnapshot");b.remove();a.fire("unlockSnapshot");!c&&a.resetDirty()}delete a._.hiddenSelectionContainer}else CKEDITOR.warn("selection-fake-reset");this.rev=M++},selectElement:function(a){var b=new CKEDITOR.dom.range(this.root);b.setStartBefore(a);b.setEndAfter(a); this.selectRanges([b])},selectRanges:function(a){var b=this.root.editor,c=b&&b._.hiddenSelectionContainer;this.reset();if(c)for(var c=this.root,g,h=0;h]*>)[ \t\r\n]*/gi,"$1");g=g.replace(/([ \t\n\r]+| )/g, " ");g=g.replace(/]*>/gi,"\n");if(CKEDITOR.env.ie){var h=a.getDocument().createElement("div");h.append(f);f.$.outerHTML="\x3cpre\x3e"+g+"\x3c/pre\x3e";f.copyAttributes(h.getFirst());f=h.getFirst().remove()}else f.setHtml(g);b=f}else g?b=t(c?[a.getHtml()]:e(a),b):a.moveChildren(b);b.replace(a);if(d){var c=b,k;(k=c.getPrevious(v))&&k.type==CKEDITOR.NODE_ELEMENT&&k.is("pre")&&(d=x(k.getHtml(),/\n$/,"")+"\n\n"+x(c.getHtml(),/^\n/,""),CKEDITOR.env.ie?c.$.outerHTML="\x3cpre\x3e"+d+"\x3c/pre\x3e": c.setHtml(d),k.remove())}else c&&m(b)}function e(a){var b=[];x(a.getOuterHtml(),/(\S\s*)\n(?:\s|(]+data-cke-bookmark.*?\/span>))*\n(?!$)/gi,function(a,b,c){return b+"\x3c/pre\x3e"+c+"\x3cpre\x3e"}).replace(/([\s\S]*?)<\/pre>/gi,function(a,c){b.push(c)});return b}function x(a,b,c){var d="",e="";a=a.replace(/(^]+data-cke-bookmark.*?\/span>)|(]+data-cke-bookmark.*?\/span>$)/gi,function(a,b,c){b&&(d=b);c&&(e=c);return""});return d+a.replace(b,c)+e}function t(a,b){var c; 1=c?(h=f.createText(""),h.insertAfter(this)):(a=f.createText(""),a.insertAfter(h),a.remove()));return h},substring:function(a,d){return"number"!=typeof d?this.$.nodeValue.substr(a):this.$.nodeValue.substring(a,d)}}); (function(){function a(a,c,d){var h=a.serializable,k=c[d?"endContainer":"startContainer"],g=d?"endOffset":"startOffset",l=h?c.document.getById(a.startNode):a.startNode;a=h?c.document.getById(a.endNode):a.endNode;k.equals(l.getPrevious())?(c.startOffset=c.startOffset-k.getLength()-a.getPrevious().getLength(),k=a.getNext()):k.equals(a.getPrevious())&&(c.startOffset-=k.getLength(),k=a.getNext());k.equals(l.getParent())&&c[g]++;k.equals(a.getParent())&&c[g]++;c[d?"endContainer":"startContainer"]=k;return c} CKEDITOR.dom.rangeList=function(a){if(a instanceof CKEDITOR.dom.rangeList)return a;a?a instanceof CKEDITOR.dom.range&&(a=[a]):a=[];return CKEDITOR.tools.extend(a,d)};var d={createIterator:function(){var a=this,c=CKEDITOR.dom.walker.bookmark(),d=[],h;return{getNextRange:function(k){h=void 0===h?0:h+1;var g=a[h];if(g&&1b?-1:1}),f=0,g;fCKEDITOR.env.version? a[h].$.styleSheet.cssText+=g:a[h].$.innerHTML+=g}}var h={};CKEDITOR.skin={path:a,loadPart:function(c,d){CKEDITOR.skin.name!=CKEDITOR.skinName.split(",")[0]?CKEDITOR.scriptLoader.load(CKEDITOR.getUrl(a()+"skin.js"),function(){b(c,d)}):b(c,d)},getPath:function(a){return CKEDITOR.getUrl(d(a))},icons:{},addIcon:function(a,b,c,d){a=a.toLowerCase();this.icons[a]||(this.icons[a]={path:b,offset:c||0,bgsize:d||"16px"})},getIconStyle:function(a,b,c,d,f){var g;a&&(a=a.toLowerCase(),b&&(g=this.icons[a+"-rtl"]), g||(g=this.icons[a]));a=c||g&&g.path||"";d=d||g&&g.offset;f=f||g&&g.bgsize||"16px";a&&(a=a.replace(/'/g,"\\'"));return a&&"background-image:url('"+CKEDITOR.getUrl(a)+"');background-position:0 "+d+"px;background-size:"+f+";"}};CKEDITOR.tools.extend(CKEDITOR.editor.prototype,{getUiColor:function(){return this.uiColor},setUiColor:function(a){var b=c(CKEDITOR.document);return(this.setUiColor=function(a){this.uiColor=a;var c=CKEDITOR.skin.chameleon,d="",h="";"function"==typeof c&&(d=c(this,"editor"),h= c(this,"panel"));a=[[l,a]];f([b],d,a);f(g,h,a)}).call(this,a)}});var k="cke_ui_color",g=[],l=/\$color/g;CKEDITOR.on("instanceLoaded",function(a){if(!CKEDITOR.env.ie||!CKEDITOR.env.quirks){var b=a.editor;a=function(a){a=(a.data[0]||a.data).element.getElementsByTag("iframe").getItem(0).getFrameDocument();if(!a.getById("cke_ui_color")){var d=c(a);g.push(d);b.on("destroy",function(){g=CKEDITOR.tools.array.filter(g,function(a){return d!==a})});(a=b.getUiColor())&&f([d],CKEDITOR.skin.chameleon(b,"panel"), [[l,a]])}};b.on("panelShow",a);b.on("menuShow",a);b.config.uiColor&&b.setUiColor(b.config.uiColor)}})})(); (function(){if(CKEDITOR.env.webkit)CKEDITOR.env.hc=!1;else{var a=CKEDITOR.dom.element.createFromHtml('\x3cdiv style\x3d"width:0;height:0;position:absolute;left:-10000px;border:1px solid;border-color:red blue"\x3e\x3c/div\x3e',CKEDITOR.document);a.appendTo(CKEDITOR.document.getHead());try{var d=a.getComputedStyle("border-top-color"),b=a.getComputedStyle("border-right-color");CKEDITOR.env.hc=!(!d||d!=b)}catch(c){CKEDITOR.env.hc=!1}a.remove()}CKEDITOR.env.hc&&(CKEDITOR.env.cssClass+=" cke_hc");CKEDITOR.document.appendStyleText(".cke{visibility:hidden;}"); CKEDITOR.status="loaded";CKEDITOR.fireOnce("loaded");if(a=CKEDITOR._.pending)for(delete CKEDITOR._.pending,d=0;dc;c++){var f=a,h=c,d;d=parseInt(a[c],16);d=("0"+(0>e?0|d*(1+e):0|d+(255-d)*e).toString(16)).slice(-2);f[h]=d}return"#"+a.join("")}}(),c=function(){var b=new CKEDITOR.template("background:#{to};background-image:-webkit-gradient(linear,lefttop,leftbottom,from({from}),to({to}));background-image:-moz-linear-gradient(top,{from},{to});background-image:-webkit-linear-gradient(top,{from},{to});background-image:-o-linear-gradient(top,{from},{to});background-image:-ms-linear-gradient(top,{from},{to});background-image:linear-gradient(top,{from},{to});filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='{from}',endColorstr='{to}');");return function(c, a){return b.output({from:c,to:a})}}(),f={editor:new CKEDITOR.template("{id}.cke_chrome [border-color:{defaultBorder};] {id} .cke_top [ {defaultGradient}border-bottom-color:{defaultBorder};] {id} .cke_bottom [{defaultGradient}border-top-color:{defaultBorder};] {id} .cke_resizer [border-right-color:{ckeResizer}] {id} .cke_dialog_title [{defaultGradient}border-bottom-color:{defaultBorder};] {id} .cke_dialog_footer [{defaultGradient}outline-color:{defaultBorder};border-top-color:{defaultBorder};] {id} .cke_dialog_tab [{lightGradient}border-color:{defaultBorder};] {id} .cke_dialog_tab:hover [{mediumGradient}] {id} .cke_dialog_contents [border-top-color:{defaultBorder};] {id} .cke_dialog_tab_selected, {id} .cke_dialog_tab_selected:hover [background:{dialogTabSelected};border-bottom-color:{dialogTabSelectedBorder};] {id} .cke_dialog_body [background:{dialogBody};border-color:{defaultBorder};] {id} .cke_toolgroup [{lightGradient}border-color:{defaultBorder};] {id} a.cke_button_off:hover, {id} a.cke_button_off:focus, {id} a.cke_button_off:active [{mediumGradient}] {id} .cke_button_on [{ckeButtonOn}] {id} .cke_toolbar_separator [background-color: {ckeToolbarSeparator};] {id} .cke_combo_button [border-color:{defaultBorder};{lightGradient}] {id} a.cke_combo_button:hover, {id} a.cke_combo_button:focus, {id} .cke_combo_on a.cke_combo_button [border-color:{defaultBorder};{mediumGradient}] {id} .cke_path_item [color:{elementsPathColor};] {id} a.cke_path_item:hover, {id} a.cke_path_item:focus, {id} a.cke_path_item:active [background-color:{elementsPathBg};] {id}.cke_panel [border-color:{defaultBorder};] "), panel:new CKEDITOR.template(".cke_panel_grouptitle [{lightGradient}border-color:{defaultBorder};] .cke_menubutton_icon [background-color:{menubuttonIcon};] .cke_menubutton:hover .cke_menubutton_icon, .cke_menubutton:focus .cke_menubutton_icon, .cke_menubutton:active .cke_menubutton_icon [background-color:{menubuttonIconHover};] .cke_menuseparator [background-color:{menubuttonIcon};] a:hover.cke_colorbox, a:focus.cke_colorbox, a:active.cke_colorbox [border-color:{defaultBorder};] a:hover.cke_colorauto, a:hover.cke_colormore, a:focus.cke_colorauto, a:focus.cke_colormore, a:active.cke_colorauto, a:active.cke_colormore [background-color:{ckeColorauto};border-color:{defaultBorder};] ")}; return function(g,e){var a=g.uiColor,a={id:"."+g.id,defaultBorder:b(a,-0.1),defaultGradient:c(b(a,0.9),a),lightGradient:c(b(a,1),b(a,0.7)),mediumGradient:c(b(a,0.8),b(a,0.5)),ckeButtonOn:c(b(a,0.6),b(a,0.7)),ckeResizer:b(a,-0.4),ckeToolbarSeparator:b(a,0.5),ckeColorauto:b(a,0.8),dialogBody:b(a,0.7),dialogTabSelected:c("#FFFFFF","#FFFFFF"),dialogTabSelectedBorder:"#FFF",elementsPathColor:b(a,-0.6),elementsPathBg:a,menubuttonIcon:b(a,0.5),menubuttonIconHover:b(a,0.3)};return f[e].output(a).replace(/\[/g, "{").replace(/\]/g,"}")}}();CKEDITOR.plugins.add("dialogui",{onLoad:function(){var h=function(b){this._||(this._={});this._["default"]=this._.initValue=b["default"]||"";this._.required=b.required||!1;for(var a=[this._],d=1;darguments.length)){var c=h.call(this,a);c.labelId=CKEDITOR.tools.getNextId()+ "_label";this._.children=[];var e={role:a.role||"presentation"};a.includeLabel&&(e["aria-labelledby"]=c.labelId);CKEDITOR.ui.dialog.uiElement.call(this,b,a,d,"div",null,e,function(){var e=[],g=a.required?" cke_required":"";"horizontal"!=a.labelLayout?e.push('\x3clabel class\x3d"cke_dialog_ui_labeled_label'+g+'" ',' id\x3d"'+c.labelId+'"',c.inputId?' for\x3d"'+c.inputId+'"':"",(a.labelStyle?' style\x3d"'+a.labelStyle+'"':"")+"\x3e",a.label,"\x3c/label\x3e",'\x3cdiv class\x3d"cke_dialog_ui_labeled_content"', a.controlStyle?' style\x3d"'+a.controlStyle+'"':"",' role\x3d"presentation"\x3e',f.call(this,b,a),"\x3c/div\x3e"):(g={type:"hbox",widths:a.widths,padding:0,children:[{type:"html",html:'\x3clabel class\x3d"cke_dialog_ui_labeled_label'+g+'" id\x3d"'+c.labelId+'" for\x3d"'+c.inputId+'"'+(a.labelStyle?' style\x3d"'+a.labelStyle+'"':"")+"\x3e"+CKEDITOR.tools.htmlEncode(a.label)+"\x3c/label\x3e"},{type:"html",html:'\x3cspan class\x3d"cke_dialog_ui_labeled_content"'+(a.controlStyle?' style\x3d"'+a.controlStyle+ '"':"")+"\x3e"+f.call(this,b,a)+"\x3c/span\x3e"}]},CKEDITOR.dialog._.uiElementBuilders.hbox.build(b,g,e));return e.join("")})}},textInput:function(b,a,d){if(!(3>arguments.length)){h.call(this,a);var f=this._.inputId=CKEDITOR.tools.getNextId()+"_textInput",c={"class":"cke_dialog_ui_input_"+a.type,id:f,type:a.type};a.validate&&(this.validate=a.validate);a.maxLength&&(c.maxlength=a.maxLength);a.size&&(c.size=a.size);a.inputStyle&&(c.style=a.inputStyle);var e=this,m=!1;b.on("load",function(){e.getInputElement().on("keydown", function(a){13==a.data.getKeystroke()&&(m=!0)});e.getInputElement().on("keyup",function(a){13==a.data.getKeystroke()&&m&&(b.getButton("ok")&&setTimeout(function(){b.getButton("ok").click()},0),m=!1);e.bidi&&w.call(e,a)},null,null,1E3)});CKEDITOR.ui.dialog.labeledElement.call(this,b,a,d,function(){var b=['\x3cdiv class\x3d"cke_dialog_ui_input_',a.type,'" role\x3d"presentation"'];a.width&&b.push('style\x3d"width:'+a.width+'" ');b.push("\x3e\x3cinput ");c["aria-labelledby"]=this._.labelId;this._.required&& (c["aria-required"]=this._.required);for(var e in c)b.push(e+'\x3d"'+c[e]+'" ');b.push(" /\x3e\x3c/div\x3e");return b.join("")})}},textarea:function(b,a,d){if(!(3>arguments.length)){h.call(this,a);var f=this,c=this._.inputId=CKEDITOR.tools.getNextId()+"_textarea",e={};a.validate&&(this.validate=a.validate);e.rows=a.rows||5;e.cols=a.cols||20;e["class"]="cke_dialog_ui_input_textarea "+(a["class"]||"");"undefined"!=typeof a.inputStyle&&(e.style=a.inputStyle);a.dir&&(e.dir=a.dir);if(f.bidi)b.on("load", function(){f.getInputElement().on("keyup",w)},f);CKEDITOR.ui.dialog.labeledElement.call(this,b,a,d,function(){e["aria-labelledby"]=this._.labelId;this._.required&&(e["aria-required"]=this._.required);var a=['\x3cdiv class\x3d"cke_dialog_ui_input_textarea" role\x3d"presentation"\x3e\x3ctextarea id\x3d"',c,'" '],b;for(b in e)a.push(b+'\x3d"'+CKEDITOR.tools.htmlEncode(e[b])+'" ');a.push("\x3e",CKEDITOR.tools.htmlEncode(f._["default"]),"\x3c/textarea\x3e\x3c/div\x3e");return a.join("")})}},checkbox:function(b, a,d){if(!(3>arguments.length)){var f=h.call(this,a,{"default":!!a["default"]});a.validate&&(this.validate=a.validate);CKEDITOR.ui.dialog.uiElement.call(this,b,a,d,"span",null,null,function(){var c=CKEDITOR.tools.extend({},a,{id:a.id?a.id+"_checkbox":CKEDITOR.tools.getNextId()+"_checkbox"},!0),e=[],d=CKEDITOR.tools.getNextId()+"_label",g={"class":"cke_dialog_ui_checkbox_input",type:"checkbox","aria-labelledby":d};t(c);a["default"]&&(g.checked="checked");"undefined"!=typeof c.inputStyle&&(c.style=c.inputStyle); f.checkbox=new CKEDITOR.ui.dialog.uiElement(b,c,e,"input",null,g);e.push(' \x3clabel id\x3d"',d,'" for\x3d"',g.id,'"'+(a.labelStyle?' style\x3d"'+a.labelStyle+'"':"")+"\x3e",CKEDITOR.tools.htmlEncode(a.label),"\x3c/label\x3e");return e.join("")})}},radio:function(b,a,d){if(!(3>arguments.length)){h.call(this,a);this._["default"]||(this._["default"]=this._.initValue=a.items[0][1]);a.validate&&(this.validate=a.validate);var f=[],c=this;a.role="radiogroup";a.includeLabel=!0;CKEDITOR.ui.dialog.labeledElement.call(this, b,a,d,function(){for(var e=[],d=[],g=(a.id?a.id:CKEDITOR.tools.getNextId())+"_radio",k=0;karguments.length)){var f=h.call(this,a);a.validate&&(this.validate=a.validate);f.inputId=CKEDITOR.tools.getNextId()+"_select";CKEDITOR.ui.dialog.labeledElement.call(this,b,a,d,function(){var c=CKEDITOR.tools.extend({},a,{id:a.id?a.id+"_select":CKEDITOR.tools.getNextId()+"_select"},!0),e=[],d=[],g={id:f.inputId,"class":"cke_dialog_ui_input_select","aria-labelledby":this._.labelId};e.push('\x3cdiv class\x3d"cke_dialog_ui_input_', a.type,'" role\x3d"presentation"');a.width&&e.push('style\x3d"width:'+a.width+'" ');e.push("\x3e");void 0!==a.size&&(g.size=a.size);void 0!==a.multiple&&(g.multiple=a.multiple);t(c);for(var k=0,l;karguments.length)){void 0===a["default"]&&(a["default"]="");var f=CKEDITOR.tools.extend(h.call(this,a),{definition:a,buttons:[]});a.validate&&(this.validate=a.validate);b.on("load",function(){CKEDITOR.document.getById(f.frameId).getParent().addClass("cke_dialog_ui_input_file")});CKEDITOR.ui.dialog.labeledElement.call(this,b,a,d,function(){f.frameId=CKEDITOR.tools.getNextId()+"_fileInput";var b=['\x3ciframe frameborder\x3d"0" allowtransparency\x3d"0" class\x3d"cke_dialog_ui_input_file" role\x3d"presentation" id\x3d"', f.frameId,'" title\x3d"',a.label,'" src\x3d"javascript:void('];b.push(CKEDITOR.env.ie?"(function(){"+encodeURIComponent("document.open();("+CKEDITOR.tools.fixDomain+")();document.close();")+"})()":"0");b.push(')"\x3e\x3c/iframe\x3e');return b.join("")})}},fileButton:function(b,a,d){var f=this;if(!(3>arguments.length)){h.call(this,a);a.validate&&(this.validate=a.validate);var c=CKEDITOR.tools.extend({},a),e=c.onClick;c.className=(c.className?c.className+" ":"")+"cke_dialog_ui_button";c.onClick=function(c){var d= a["for"];c=e?e.call(this,c):!1;!1!==c&&("xhr"!==c&&b.getContentElement(d[0],d[1]).submit(),this.disable())};b.on("load",function(){b.getContentElement(a["for"][0],a["for"][1])._.buttons.push(f)});CKEDITOR.ui.dialog.button.call(this,b,c,d)}},html:function(){var b=/^\s*<[\w:]+\s+([^>]*)?>/,a=/^(\s*<[\w:]+(?:\s+[^>]*)?)((?:.|\r|\n)+)$/,d=/\/$/;return function(f,c,e){if(!(3>arguments.length)){var m=[],g=c.html;"\x3c"!=g.charAt(0)&&(g="\x3cspan\x3e"+g+"\x3c/span\x3e");var k=c.focus;if(k){var l=this.focus; this.focus=function(){("function"==typeof k?k:l).call(this);this.fire("focus")};c.isFocusable&&(this.isFocusable=this.isFocusable);this.keyboardFocusable=!0}CKEDITOR.ui.dialog.uiElement.call(this,f,c,m,"span",null,null,"");m=m.join("").match(b);g=g.match(a)||["","",""];d.test(g[1])&&(g[1]=g[1].slice(0,-1),g[2]="/"+g[2]);e.push([g[1]," ",m[1]||"",g[2]].join(""))}}}(),fieldset:function(b,a,d,f,c){var e=c.label;this._={children:a};CKEDITOR.ui.dialog.uiElement.call(this,b,c,f,"fieldset",null,null,function(){var a= [];e&&a.push("\x3clegend"+(c.labelStyle?' style\x3d"'+c.labelStyle+'"':"")+"\x3e"+e+"\x3c/legend\x3e");for(var b=0;ba.getChildCount()?(new CKEDITOR.dom.text(b,CKEDITOR.document)).appendTo(a):a.getChild(0).$.nodeValue= b;return this},getLabel:function(){var b=CKEDITOR.document.getById(this._.labelId);return!b||1>b.getChildCount()?"":b.getChild(0).getText()},eventProcessors:v},!0);CKEDITOR.ui.dialog.button.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.uiElement,{click:function(){return this._.disabled?!1:this.fire("click",{dialog:this._.dialog})},enable:function(){this._.disabled=!1;var b=this.getElement();b&&b.removeClass("cke_disabled")},disable:function(){this._.disabled=!0;this.getElement().addClass("cke_disabled")}, isVisible:function(){return this.getElement().getFirst().isVisible()},isEnabled:function(){return!this._.disabled},eventProcessors:CKEDITOR.tools.extend({},CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors,{onClick:function(b,a){this.on("click",function(){a.apply(this,arguments)})}},!0),accessKeyUp:function(){this.click()},accessKeyDown:function(){this.focus()},keyboardFocusable:!0},!0);CKEDITOR.ui.dialog.textInput.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.labeledElement,{getInputElement:function(){return CKEDITOR.document.getById(this._.inputId)}, focus:function(){var b=this.selectParentTab();setTimeout(function(){var a=b.getInputElement();a&&a.$.focus()},0)},select:function(){var b=this.selectParentTab();setTimeout(function(){var a=b.getInputElement();a&&(a.$.focus(),a.$.select())},0)},accessKeyUp:function(){this.select()},setValue:function(b){if(this.bidi){var a=b&&b.charAt(0);(a="‪"==a?"ltr":"‫"==a?"rtl":null)&&(b=b.slice(1));this.setDirectionMarker(a)}b||(b="");return CKEDITOR.ui.dialog.uiElement.prototype.setValue.apply(this,arguments)}, getValue:function(){var b=CKEDITOR.ui.dialog.uiElement.prototype.getValue.call(this);if(this.bidi&&b){var a=this.getDirectionMarker();a&&(b=("ltr"==a?"‪":"‫")+b)}return b},setDirectionMarker:function(b){var a=this.getInputElement();b?a.setAttributes({dir:b,"data-cke-dir-marker":b}):this.getDirectionMarker()&&a.removeAttributes(["dir","data-cke-dir-marker"])},getDirectionMarker:function(){return this.getInputElement().data("cke-dir-marker")},keyboardFocusable:!0},q,!0);CKEDITOR.ui.dialog.textarea.prototype= new CKEDITOR.ui.dialog.textInput;CKEDITOR.ui.dialog.select.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.labeledElement,{getInputElement:function(){return this._.select.getElement()},add:function(b,a,d){var f=new CKEDITOR.dom.element("option",this.getDialog().getParentEditor().document),c=this.getInputElement().$;f.$.text=b;f.$.value=void 0===a||null===a?b:a;void 0===d||null===d?CKEDITOR.env.ie?c.add(f.$):c.add(f.$,null):c.add(f.$,d);return this},remove:function(b){this.getInputElement().$.remove(b); return this},clear:function(){for(var b=this.getInputElement().$;0b-a;c--)if(this._.tabs[this._.tabIdList[c%a]][0].$.offsetHeight)return this._.tabIdList[c%a];return null}function D(){for(var a=this._.tabIdList.length,b=CKEDITOR.tools.indexOf(this._.tabIdList,this._.currentTabId),c=b+1;ck.width-c.width-g?k.width-c.width+("rtl"==f.lang.dir?0:h[1]):d.x;c=d.y+h[0]k.height-c.height-g?k.height-c.height+h[2]:d.y;q=Math.floor(q);c=Math.floor(c); a.move(q,c,1);b.data.preventDefault()}function c(){CKEDITOR.document.removeListener("mousemove",b);CKEDITOR.document.removeListener("mouseup",c);if(CKEDITOR.env.ie6Compat){var a=u.getChild(0).getFrameDocument();a.removeListener("mousemove",b);a.removeListener("mouseup",c)}}var e=null,d=null,f=a.getParentEditor(),g=f.config.dialog_magnetDistance,h=CKEDITOR.skin.margins||[0,0,0,0];"undefined"==typeof g&&(g=20);a.parts.title.on("mousedown",function(g){if(!a._.moved){var f=a._.element;f.getFirst().setStyle("position", "absolute");f.removeStyle("display");a._.moved=!0;a.layout()}e={x:g.data.$.screenX,y:g.data.$.screenY};CKEDITOR.document.on("mousemove",b);CKEDITOR.document.on("mouseup",c);d=a.getPosition();CKEDITOR.env.ie6Compat&&(f=u.getChild(0).getFrameDocument(),f.on("mousemove",b),f.on("mouseup",c));g.data.preventDefault()},a)}function aa(a){function b(b){var c="rtl"==f.lang.dir,q=k.width,l=k.height,w=q+(b.data.$.screenX-n.x)*(c?-1:1)*(a._.moved?1:2),B=l+(b.data.$.screenY-n.y)*(a._.moved?1:2),E=a._.element.getFirst(), E=c&&parseInt(E.getComputedStyle("right")),v=a.getPosition();v.x=v.x||0;v.y=v.y||0;v.y+B>p.height&&(B=p.height-v.y);(c?E:v.x)+w>p.width&&(w=p.width-(c?E:v.x));B=Math.floor(B);w=Math.floor(w);if(d==CKEDITOR.DIALOG_RESIZE_WIDTH||d==CKEDITOR.DIALOG_RESIZE_BOTH)q=Math.max(e.minWidth||0,w-g);if(d==CKEDITOR.DIALOG_RESIZE_HEIGHT||d==CKEDITOR.DIALOG_RESIZE_BOTH)l=Math.max(e.minHeight||0,B-h);a.resize(q,l);a._.moved&&N(a,a._.position.x,a._.position.y);a._.moved||a.layout();b.data.preventDefault()}function c(){CKEDITOR.document.removeListener("mouseup", c);CKEDITOR.document.removeListener("mousemove",b);q&&(q.remove(),q=null);if(CKEDITOR.env.ie6Compat){var a=u.getChild(0).getFrameDocument();a.removeListener("mouseup",c);a.removeListener("mousemove",b)}}var e=a.definition,d=e.resizable;if(d!=CKEDITOR.DIALOG_RESIZE_NONE){var f=a.getParentEditor(),g,h,p,n,k,q,l=CKEDITOR.tools.addFunction(function(d){function e(a){return a.isVisible()}k=a.getSize();var f=a.parts.contents,l=f.$.getElementsByTagName("iframe").length,w=!(CKEDITOR.env.gecko||CKEDITOR.env.ie&& CKEDITOR.env.quirks);l&&(q=CKEDITOR.dom.element.createFromHtml('\x3cdiv class\x3d"cke_dialog_resize_cover" style\x3d"height: 100%; position: absolute; width: 100%; left:0; top:0;"\x3e\x3c/div\x3e'),f.append(q));h=k.height-a.parts.contents.getFirst(e).getSize("height",w);g=k.width-a.parts.contents.getFirst(e).getSize("width",1);n={x:d.screenX,y:d.screenY};p=CKEDITOR.document.getWindow().getViewPaneSize();CKEDITOR.document.on("mousemove",b);CKEDITOR.document.on("mouseup",c);CKEDITOR.env.ie6Compat&& (f=u.getChild(0).getFrameDocument(),f.on("mousemove",b),f.on("mouseup",c));d.preventDefault&&d.preventDefault()});a.on("load",function(){var b="";d==CKEDITOR.DIALOG_RESIZE_WIDTH?b=" cke_resizer_horizontal":d==CKEDITOR.DIALOG_RESIZE_HEIGHT&&(b=" cke_resizer_vertical");b=CKEDITOR.dom.element.createFromHtml('\x3cdiv class\x3d"cke_resizer'+b+" cke_resizer_"+f.lang.dir+'" title\x3d"'+CKEDITOR.tools.htmlEncode(f.lang.common.resize)+'" onmousedown\x3d"CKEDITOR.tools.callFunction('+l+', event )"\x3e'+("ltr"== f.lang.dir?"â—¢":"â—£")+"\x3c/div\x3e");a.parts.footer.append(b,1)});f.on("destroy",function(){CKEDITOR.tools.removeFunction(l)})}}function N(a,b,c){var e=a.parts.dialog.getParent().getClientSize(),d=a.getSize(),f=a._.viewportRatio,g=Math.max(e.width-d.width,0),e=Math.max(e.height-d.height,0);f.width=g?b/g:f.width;f.height=e?c/e:f.height;a._.viewportRatio=f}function J(a){a.data.preventDefault(1)}function O(a){var b=a.config,c=CKEDITOR.skinName||a.config.skin,e=b.dialog_backgroundCoverColor||("moono-lisa"== c?"black":"white"),c=b.dialog_backgroundCoverOpacity,d=b.baseFloatZIndex,b=CKEDITOR.tools.genKey(e,c,d),f=C[b];CKEDITOR.document.getBody().addClass("cke_dialog_open");f?f.show():(d=['\x3cdiv tabIndex\x3d"-1" style\x3d"position: ',CKEDITOR.env.ie6Compat?"absolute":"fixed","; z-index: ",d,"; top: 0px; left: 0px; ","; width: 100%; height: 100%;",CKEDITOR.env.ie6Compat?"":"background-color: "+e,'" class\x3d"cke_dialog_background_cover"\x3e'],CKEDITOR.env.ie6Compat&&(e="\x3chtml\x3e\x3cbody style\x3d\\'background-color:"+ e+";\\'\x3e\x3c/body\x3e\x3c/html\x3e",d.push('\x3ciframe hidefocus\x3d"true" frameborder\x3d"0" id\x3d"cke_dialog_background_iframe" src\x3d"javascript:'),d.push("void((function(){"+encodeURIComponent("document.open();("+CKEDITOR.tools.fixDomain+")();document.write( '"+e+"' );document.close();")+"})())"),d.push('" style\x3d"position:absolute;left:0;top:0;width:100%;height: 100%;filter: progid:DXImageTransform.Microsoft.Alpha(opacity\x3d0)"\x3e\x3c/iframe\x3e')),d.push("\x3c/div\x3e"),f=CKEDITOR.dom.element.createFromHtml(d.join("")), f.setOpacity(void 0!==c?c:.5),f.on("keydown",J),f.on("keypress",J),f.on("keyup",J),f.appendTo(CKEDITOR.document.getBody()),C[b]=f);a.focusManager.add(f);u=f;CKEDITOR.env.mac&&CKEDITOR.env.webkit||f.focus()}function P(a){CKEDITOR.document.getBody().removeClass("cke_dialog_open");u&&(a.focusManager.remove(u),u.hide())}var x=CKEDITOR.tools.cssLength,F=!CKEDITOR.env.ie||CKEDITOR.env.edge,X='\x3cdiv class\x3d"cke_reset_all cke_dialog_container {editorId} {editorDialogClass} {hidpi}" dir\x3d"{langDir}" style\x3d"'+ (F?"display:flex":"")+'" lang\x3d"{langCode}" role\x3d"dialog" aria-labelledby\x3d"cke_dialog_title_{id}"\x3e\x3ctable class\x3d"cke_dialog '+CKEDITOR.env.cssClass+' cke_{langDir}" style\x3d"'+(F?"margin:auto":"position:absolute")+'" role\x3d"presentation"\x3e\x3ctr\x3e\x3ctd role\x3d"presentation"\x3e\x3cdiv class\x3d"cke_dialog_body" role\x3d"presentation"\x3e\x3cdiv id\x3d"cke_dialog_title_{id}" class\x3d"cke_dialog_title" role\x3d"presentation"\x3e\x3c/div\x3e\x3ca id\x3d"cke_dialog_close_button_{id}" class\x3d"cke_dialog_close_button" href\x3d"javascript:void(0)" title\x3d"{closeTitle}" role\x3d"button"\x3e\x3cspan class\x3d"cke_label"\x3eX\x3c/span\x3e\x3c/a\x3e\x3cdiv id\x3d"cke_dialog_tabs_{id}" class\x3d"cke_dialog_tabs" role\x3d"tablist"\x3e\x3c/div\x3e\x3ctable class\x3d"cke_dialog_contents" role\x3d"presentation"\x3e\x3ctr\x3e\x3ctd id\x3d"cke_dialog_contents_{id}" class\x3d"cke_dialog_contents_body" role\x3d"presentation"\x3e\x3c/td\x3e\x3c/tr\x3e\x3ctr\x3e\x3ctd id\x3d"cke_dialog_footer_{id}" class\x3d"cke_dialog_footer" role\x3d"presentation"\x3e\x3c/td\x3e\x3c/tr\x3e\x3c/table\x3e\x3c/div\x3e\x3c/td\x3e\x3c/tr\x3e\x3c/table\x3e\x3c/div\x3e'; CKEDITOR.dialog=function(a,b){function c(){var a=m._.focusList;a.sort(function(a,b){return a.tabIndex!=b.tabIndex?b.tabIndex-a.tabIndex:a.focusIndex-b.focusIndex});for(var b=a.length,c=0;cb.length)){var c=m._.currentFocusIndex;m._.tabBarMode&&0>a&&(c=0);try{b[c].getInputElement().$.blur()}catch(d){}var e=c,g=1arguments.length)){var h=(e.call?e(b):e)||"div",p=["\x3c",h," "],n=(d&&d.call?d(b):d)||{},k=(f&&f.call?f(b):f)||{},q=(g&&g.call?g.call(this,a,b):g)||"",l=this.domId=k.id||CKEDITOR.tools.getNextId()+"_uiElement";b.requiredContent&&!a.getParentEditor().filter.check(b.requiredContent)&&(n.display="none",this.notAllowed=!0);k.id=l;var r={};b.type&&(r["cke_dialog_ui_"+ b.type]=1);b.className&&(r[b.className]=1);b.disabled&&(r.cke_disabled=1);for(var m=k["class"]&&k["class"].split?k["class"].split(" "):[],l=0;lCKEDITOR.env.version?"cke_dialog_ui_focused":"";b.on("focus",function(){a._.tabBarMode=!1;a._.hasFocus=!0;t.fire("focus"); c&&this.addClass(c)});b.on("blur",function(){t.fire("blur");c&&this.removeClass(c)})}});CKEDITOR.tools.extend(this,b);this.keyboardFocusable&&(this.tabIndex=b.tabIndex||0,this.focusIndex=a._.focusList.push(this)-1,this.on("focus",function(){a._.currentFocusIndex=t.focusIndex}))}},hbox:function(a,b,c,e,d){if(!(4>arguments.length)){this._||(this._={});var f=this._.children=b,g=d&&d.widths||null,h=d&&d.height||null,p,n={role:"presentation"};d&&d.align&&(n.align=d.align);CKEDITOR.ui.dialog.uiElement.call(this, a,d||{type:"hbox"},e,"table",{},n,function(){var a=['\x3ctbody\x3e\x3ctr class\x3d"cke_dialog_ui_hbox"\x3e'];for(p=0;parguments.length)){this._||(this._={});var f=this._.children=b,g=d&&d.width||null,h=d&&d.heights||null;CKEDITOR.ui.dialog.uiElement.call(this,a,d||{type:"vbox"},e,"div",null,{role:"presentation"},function(){var b=['\x3ctable role\x3d"presentation" cellspacing\x3d"0" border\x3d"0" ']; b.push('style\x3d"');d&&d.expand&&b.push("height:100%;");b.push("width:"+x(g||"100%"),";");CKEDITOR.env.webkit&&b.push("float:none;");b.push('"');b.push('align\x3d"',CKEDITOR.tools.htmlEncode(d&&d.align||("ltr"==a.getParentEditor().lang.dir?"left":"right")),'" ');b.push("\x3e\x3ctbody\x3e");for(var e=0;earguments.length)return this._.children.concat();a.splice||(a=[a]);return 2>a.length?this._.children[a[0]]:this._.children[a[0]]&&this._.children[a[0]].getChild?this._.children[a[0]].getChild(a.slice(1,a.length)):null}},!0);CKEDITOR.ui.dialog.vbox.prototype=new CKEDITOR.ui.dialog.hbox;(function(){var a={build:function(a,c,e){for(var d=c.children,f,g=[],h=[],p=0;pe.length&&(a=g.document.createElement(g.config.enterMode==CKEDITOR.ENTER_P?"p":"div"),b=h.shift(),c.insertNode(a),a.append(new CKEDITOR.dom.text("",g.document)),c.moveToBookmark(b),c.selectNodeContents(a),c.collapse(!0),b=c.createBookmark(),e.push(a),h.unshift(b));d=e[0].getParent();c=[];for(b=0;ba||(this.notifications.splice(a,1),b.element.remove(),this.element.getChildCount()||(this._removeListeners(),this.element.remove()))},_createElement:function(){var b=this.editor,a=b.config,c=new CKEDITOR.dom.element("div");c.addClass("cke_notifications_area");c.setAttribute("id","cke_notifications_area_"+b.name);c.setStyle("z-index",a.baseFloatZIndex-2);return c},_attachListeners:function(){var b=CKEDITOR.document.getWindow(),a=this.editor;b.on("scroll",this._uiBuffer.input);b.on("resize",this._uiBuffer.input); a.on("change",this._changeBuffer.input);a.on("floatingSpaceLayout",this._layout,this,null,20);a.on("blur",this._layout,this,null,20)},_removeListeners:function(){var b=CKEDITOR.document.getWindow(),a=this.editor;b.removeListener("scroll",this._uiBuffer.input);b.removeListener("resize",this._uiBuffer.input);a.removeListener("change",this._changeBuffer.input);a.removeListener("floatingSpaceLayout",this._layout);a.removeListener("blur",this._layout)},_layout:function(){function b(){a.setStyle("left", k(n+d.width-g-h))}var a=this.element,c=this.editor,d=c.ui.contentsElement.getClientRect(),e=c.ui.contentsElement.getDocumentPosition(),f,l,u=a.getClientRect(),m,g=this._notificationWidth,h=this._notificationMargin;m=CKEDITOR.document.getWindow();var p=m.getScrollPosition(),t=m.getViewPaneSize(),q=CKEDITOR.document.getBody(),r=q.getDocumentPosition(),k=CKEDITOR.tools.cssLength;g&&h||(m=this.element.getChild(0),g=this._notificationWidth=m.getClientRect().width,h=this._notificationMargin=parseInt(m.getComputedStyle("margin-left"), 10)+parseInt(m.getComputedStyle("margin-right"),10));c.toolbar&&(f=c.ui.space("top"),l=f.getClientRect());f&&f.isVisible()&&l.bottom>d.top&&l.bottomp.y?a.setStyles({position:"fixed",top:0}):a.setStyles({position:"absolute",top:k(e.y+d.height-u.height)});var n="fixed"==a.getStyle("position")?d.left:"static"!=q.getComputedStyle("position")?e.x-r.x:e.x;d.width< g+h?e.x+g+h>p.x+t.width?b():a.setStyle("left",k(n)):e.x+g+h>p.x+t.width?a.setStyle("left",k(n)):e.x+d.width/2+g/2+h>p.x+t.width?a.setStyle("left",k(n-e.x+p.x+t.width-g-h)):0>d.left+d.width-g-h?b():0>d.left+d.width/2-g/2?a.setStyle("left",k(n-e.x+p.x)):a.setStyle("left",k(n+d.width/2-g/2-h/2))}};CKEDITOR.plugins.notification=q})();(function(){var c='\x3ca id\x3d"{id}" class\x3d"cke_button cke_button__{name} cke_button_{state} {cls}"'+(CKEDITOR.env.gecko&&!CKEDITOR.env.hc?"":" href\x3d\"javascript:void('{titleJs}')\"")+' title\x3d"{title}" tabindex\x3d"-1" hidefocus\x3d"true" role\x3d"button" aria-labelledby\x3d"{id}_label" aria-describedby\x3d"{id}_description" aria-haspopup\x3d"{hasArrow}" aria-disabled\x3d"{ariaDisabled}"';CKEDITOR.env.gecko&&CKEDITOR.env.mac&&(c+=' onkeypress\x3d"return false;"');CKEDITOR.env.gecko&&(c+= ' onblur\x3d"this.style.cssText \x3d this.style.cssText;"');var k="";CKEDITOR.env.ie&&(k='return false;" onmouseup\x3d"CKEDITOR.tools.getMouseButton(event)\x3d\x3dCKEDITOR.MOUSE_BUTTON_LEFT\x26\x26');var c=c+(' onkeydown\x3d"return CKEDITOR.tools.callFunction({keydownFn},event);" onfocus\x3d"return CKEDITOR.tools.callFunction({focusFn},event);" onclick\x3d"'+k+'CKEDITOR.tools.callFunction({clickFn},this);return false;"\x3e\x3cspan class\x3d"cke_button_icon cke_button__{iconName}_icon" style\x3d"{style}"')+ '\x3e\x26nbsp;\x3c/span\x3e\x3cspan id\x3d"{id}_label" class\x3d"cke_button_label cke_button__{name}_label" aria-hidden\x3d"false"\x3e{label}\x3c/span\x3e\x3cspan id\x3d"{id}_description" class\x3d"cke_button_label" aria-hidden\x3d"false"\x3e{ariaShortcut}\x3c/span\x3e{arrowHtml}\x3c/a\x3e',v=CKEDITOR.addTemplate("buttonArrow",'\x3cspan class\x3d"cke_button_arrow"\x3e'+(CKEDITOR.env.hc?"\x26#9660;":"")+"\x3c/span\x3e"),w=CKEDITOR.addTemplate("button",c);CKEDITOR.plugins.add("button",{beforeInit:function(a){a.ui.addHandler(CKEDITOR.UI_BUTTON, CKEDITOR.ui.button.handler)}});CKEDITOR.UI_BUTTON="button";CKEDITOR.ui.button=function(a){CKEDITOR.tools.extend(this,a,{title:a.label,click:a.click||function(b){b.execCommand(a.command)}});this._={}};CKEDITOR.ui.button.handler={create:function(a){return new CKEDITOR.ui.button(a)}};CKEDITOR.ui.button.prototype={render:function(a,b){function c(){var f=a.mode;f&&(f=this.modes[f]?void 0!==p[f]?p[f]:CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,f=a.readOnly&&!this.readOnly?CKEDITOR.TRISTATE_DISABLED: f,this.setState(f),this.refresh&&this.refresh())}var p=null,q=CKEDITOR.env,r=this._.id=CKEDITOR.tools.getNextId(),g="",d=this.command,k,l,m;this._.editor=a;var e={id:r,button:this,editor:a,focus:function(){CKEDITOR.document.getById(r).focus()},execute:function(){this.button.click(a)},attach:function(a){this.button.attach(a)}},x=CKEDITOR.tools.addFunction(function(a){if(e.onkey)return a=new CKEDITOR.dom.event(a),!1!==e.onkey(e,a.getKeystroke())}),y=CKEDITOR.tools.addFunction(function(a){var b;e.onfocus&& (b=!1!==e.onfocus(e,new CKEDITOR.dom.event(a)));return b}),u=0;e.clickFn=k=CKEDITOR.tools.addFunction(function(){u&&(a.unlockSelection(1),u=0);e.execute();q.iOS&&a.focus()});this.modes?(p={},a.on("beforeModeUnload",function(){a.mode&&this._.state!=CKEDITOR.TRISTATE_DISABLED&&(p[a.mode]=this._.state)},this),a.on("activeFilterChange",c,this),a.on("mode",c,this),!this.readOnly&&a.on("readOnly",c,this)):d&&(d=a.getCommand(d))&&(d.on("state",function(){this.setState(d.state)},this),g+=d.state==CKEDITOR.TRISTATE_ON? "on":d.state==CKEDITOR.TRISTATE_DISABLED?"disabled":"off");var n;if(this.directional)a.on("contentDirChanged",function(b){var c=CKEDITOR.document.getById(this._.id),d=c.getFirst();b=b.data;b!=a.lang.dir?c.addClass("cke_"+b):c.removeClass("cke_ltr").removeClass("cke_rtl");d.setAttribute("style",CKEDITOR.skin.getIconStyle(n,"rtl"==b,this.icon,this.iconOffset))},this);d?(l=a.getCommandKeystroke(d))&&(m=CKEDITOR.tools.keystrokeToString(a.lang.common.keyboard,l)):g+="off";l=this.name||this.command;var h= null,t=this.icon;n=l;this.icon&&!/\./.test(this.icon)?(n=this.icon,t=null):(this.icon&&(h=this.icon),CKEDITOR.env.hidpi&&this.iconHiDpi&&(h=this.iconHiDpi));h?(CKEDITOR.skin.addIcon(h,h),t=null):h=n;g={id:r,name:l,iconName:n,label:this.label,cls:(this.hasArrow?"cke_button_expandable ":"")+(this.className||""),state:g,ariaDisabled:"disabled"==g?"true":"false",title:this.title+(m?" ("+m.display+")":""),ariaShortcut:m?a.lang.common.keyboardShortcut+" "+m.aria:"",titleJs:q.gecko&&!q.hc?"":(this.title|| "").replace("'",""),hasArrow:"string"===typeof this.hasArrow&&this.hasArrow||(this.hasArrow?"true":"false"),keydownFn:x,focusFn:y,clickFn:k,style:CKEDITOR.skin.getIconStyle(h,"rtl"==a.lang.dir,t,this.iconOffset),arrowHtml:this.hasArrow?v.output():""};w.output(g,b);if(this.onRender)this.onRender();return e},setState:function(a){if(this._.state==a)return!1;this._.state=a;var b=CKEDITOR.document.getById(this._.id);return b?(b.setState(a,"cke_button"),b.setAttribute("aria-disabled",a==CKEDITOR.TRISTATE_DISABLED), this.hasArrow?b.setAttribute("aria-expanded",a==CKEDITOR.TRISTATE_ON):a===CKEDITOR.TRISTATE_ON?b.setAttribute("aria-pressed",!0):b.removeAttribute("aria-pressed"),!0):!1},getState:function(){return this._.state},toFeature:function(a){if(this._.feature)return this._.feature;var b=this;this.allowedContent||this.requiredContent||!this.command||(b=a.getCommand(this.command)||b);return this._.feature=b}};CKEDITOR.ui.prototype.addButton=function(a,b){this.add(a,CKEDITOR.UI_BUTTON,b)}})();(function(){function D(a){function d(){for(var b=f(),e=CKEDITOR.tools.clone(a.config.toolbarGroups)||v(a),n=0;na.order?-1:0>b.order?1:b.order]+data-cke-bookmark[^<]*?<\/span>/ig, "");d&&r(a,c)})}function t(){if("wysiwyg"==a.mode){var b=q("paste");a.getCommand("cut").setState(q("cut"));a.getCommand("copy").setState(q("copy"));a.getCommand("paste").setState(b);a.fire("pasteState",b)}}function q(b){var c=a.getSelection(),c=c&&c.getRanges()[0];if((a.readOnly||c&&c.checkReadOnly())&&b in{paste:1,cut:1})return CKEDITOR.TRISTATE_DISABLED;if("paste"==b)return CKEDITOR.TRISTATE_OFF;b=a.getSelection();c=b.getRanges();return b.getType()==CKEDITOR.SELECTION_NONE||1==c.length&&c[0].collapsed? CKEDITOR.TRISTATE_DISABLED:CKEDITOR.TRISTATE_OFF}var p=CKEDITOR.plugins.clipboard,m=0,u=0;(function(){a.on("key",n);a.on("contentDom",b);a.on("selectionChange",t);if(a.contextMenu){a.contextMenu.addListener(function(){return{cut:q("cut"),copy:q("copy"),paste:q("paste")}});var c=null;a.on("menuShow",function(){c&&(c.removeListener(),c=null);var b=a.contextMenu.findItemByCommandName("paste");b&&b.element&&(c=b.element.on("touchend",function(){a._.forcePasteDialog=!0}))})}if(a.ui.addButton)a.once("instanceReady", function(){a._.pasteButtons&&CKEDITOR.tools.array.forEach(a._.pasteButtons,function(b){if(b=a.ui.get(b))if(b=CKEDITOR.document.getById(b._.id))b.on("touchend",function(){a._.forcePasteDialog=!0})})})})();(function(){function b(c,d,e,h,f){var n=a.lang.clipboard[d];a.addCommand(d,e);a.ui.addButton&&a.ui.addButton(c,{label:n,command:d,toolbar:"clipboard,"+h});a.addMenuItems&&a.addMenuItem(d,{label:n,command:d,group:"clipboard",order:f})}b("Cut","cut",c("cut"),10,1);b("Copy","copy",c("copy"),20,4);b("Paste", "paste",d(),30,8);a._.pasteButtons||(a._.pasteButtons=[]);a._.pasteButtons.push("Paste")})();a.getClipboardData=function(b,c){function d(a){a.removeListener();a.cancel();c(a.data)}function e(a){a.removeListener();a.cancel();c({type:f,dataValue:a.data.dataValue,dataTransfer:a.data.dataTransfer,method:"paste"})}var h=!1,f="auto";c||(c=b,b=null);a.on("beforePaste",function(a){a.removeListener();h=!0;f=a.data.type},null,null,1E3);a.on("paste",d,null,null,0);!1===x()&&(a.removeListener("paste",d),a._.forcePasteDialog&& h&&a.fire("pasteDialog")?(a.on("pasteDialogCommit",e),a.on("dialogHide",function(a){a.removeListener();a.data.removeListener("pasteDialogCommit",e);a.data._.committed||c(null)})):c(null))}}function y(a){if(CKEDITOR.env.webkit){if(!a.match(/^[^<]*$/g)&&!a.match(/^(
<\/div>|
[^<]*<\/div>)*$/gi))return"html"}else if(CKEDITOR.env.ie){if(!a.match(/^([^<]|)*$/gi)&&!a.match(/^(

([^<]|)*<\/p>|(\r\n))*$/gi))return"html"}else if(CKEDITOR.env.gecko){if(!a.match(/^([^<]|)*$/gi))return"html"}else return"html"; return"htmlifiedtext"}function z(a,b){function c(a){return CKEDITOR.tools.repeat("\x3c/p\x3e\x3cp\x3e",~~(a/2))+(1==a%2?"\x3cbr\x3e":"")}b=b.replace(/(?!\u3000)\s+/g," ").replace(/> +/gi,"\x3cbr\x3e");b=b.replace(/<\/?[A-Z]+>/g,function(a){return a.toLowerCase()});if(b.match(/^[^<]$/))return b;CKEDITOR.env.webkit&&-1(
|)<\/div>)(?!$|(

(
|)<\/div>))/g,"\x3cbr\x3e").replace(/^(
(
|)<\/div>){2}(?!$)/g,"\x3cdiv\x3e\x3c/div\x3e"), b.match(/
(
|)<\/div>/)&&(b="\x3cp\x3e"+b.replace(/(
(
|)<\/div>)+/g,function(a){return c(a.split("\x3c/div\x3e\x3cdiv\x3e").length+1)})+"\x3c/p\x3e"),b=b.replace(/<\/div>
/g,"\x3cbr\x3e"),b=b.replace(/<\/?div>/g,""));CKEDITOR.env.gecko&&a.enterMode!=CKEDITOR.ENTER_BR&&(CKEDITOR.env.gecko&&(b=b.replace(/^

$/,"\x3cbr\x3e")),-1){2,}/g,function(a){return c(a.length/4)})+"\x3c/p\x3e"));return A(a,b)}function B(a){function b(){var a= {},b;for(b in CKEDITOR.dtd)"$"!=b.charAt(0)&&"div"!=b&&"span"!=b&&(a[b]=1);return a}var c={};return{get:function(d){return"plain-text"==d?c.plainText||(c.plainText=new CKEDITOR.filter(a,"br")):"semantic-content"==d?((d=c.semanticContent)||(d=new CKEDITOR.filter(a,{}),d.allow({$1:{elements:b(),attributes:!0,styles:!1,classes:!1}}),d=c.semanticContent=d),d):d?new CKEDITOR.filter(a,d):null}}}function v(a,b,c){b=CKEDITOR.htmlParser.fragment.fromHtml(b);var d=new CKEDITOR.htmlParser.basicWriter;c.applyTo(b, !0,!1,a.activeEnterMode);b.writeHtml(d);return d.getHtml()}function A(a,b){a.enterMode==CKEDITOR.ENTER_BR?b=b.replace(/(<\/p>

)+/g,function(a){return CKEDITOR.tools.repeat("\x3cbr\x3e",a.length/7*2)}).replace(/<\/?p>/g,""):a.enterMode==CKEDITOR.ENTER_DIV&&(b=b.replace(/<(\/)?p>/g,"\x3c$1div\x3e"));return b}function C(a){a.data.preventDefault();a.data.$.dataTransfer.dropEffect="none"}function D(a){var b=CKEDITOR.plugins.clipboard;a.on("contentDom",function(){function c(b,c,d){c.select();r(a,{dataTransfer:d, method:"drop"},1);d.sourceEditor.fire("saveSnapshot");d.sourceEditor.editable().extractHtmlFromRange(b);d.sourceEditor.getSelection().selectRanges([b]);d.sourceEditor.fire("saveSnapshot")}function d(c,d){c.select();r(a,{dataTransfer:d,method:"drop"},1);b.resetDragDataTransfer()}function g(b,c,d){var e={$:b.data.$,target:b.data.getTarget()};c&&(e.dragRange=c);d&&(e.dropRange=d);!1===a.fire(b.name,e)&&b.data.preventDefault()}function h(a){a.type!=CKEDITOR.NODE_ELEMENT&&(a=a.getParent());return a.getChildCount()} var e=a.editable(),f=CKEDITOR.plugins.clipboard.getDropTarget(a),l=a.ui.space("top"),m=a.ui.space("bottom");b.preventDefaultDropOnElement(l);b.preventDefaultDropOnElement(m);e.attachListener(f,"dragstart",g);e.attachListener(a,"dragstart",b.resetDragDataTransfer,b,null,1);e.attachListener(a,"dragstart",function(c){b.initDragDataTransfer(c,a)},null,null,2);e.attachListener(a,"dragstart",function(){var c=b.dragRange=a.getSelection().getRanges()[0];CKEDITOR.env.ie&&10>CKEDITOR.env.version&&(b.dragStartContainerChildCount= c?h(c.startContainer):null,b.dragEndContainerChildCount=c?h(c.endContainer):null)},null,null,100);e.attachListener(f,"dragend",g);e.attachListener(a,"dragend",b.initDragDataTransfer,b,null,1);e.attachListener(a,"dragend",b.resetDragDataTransfer,b,null,100);e.attachListener(f,"dragover",function(a){if(CKEDITOR.env.edge)a.data.preventDefault();else{var b=a.data.getTarget();b&&b.is&&b.is("html")?a.data.preventDefault():CKEDITOR.env.ie&&CKEDITOR.plugins.clipboard.isFileApiSupported&&a.data.$.dataTransfer.types.contains("Files")&& a.data.preventDefault()}});e.attachListener(f,"drop",function(c){if(!c.data.$.defaultPrevented&&(c.data.preventDefault(),!a.readOnly)){var d=c.data.getTarget();if(!d.isReadOnly()||d.type==CKEDITOR.NODE_ELEMENT&&d.is("html")){var d=b.getRangeAtDropPosition(c,a),e=b.dragRange;d&&g(c,e,d)}}},null,null,9999);e.attachListener(a,"drop",b.initDragDataTransfer,b,null,1);e.attachListener(a,"drop",function(e){if(e=e.data){var h=e.dropRange,f=e.dragRange,g=e.dataTransfer;g.getTransferType(a)==CKEDITOR.DATA_TRANSFER_INTERNAL? setTimeout(function(){b.internalDrop(f,h,g,a)},0):g.getTransferType(a)==CKEDITOR.DATA_TRANSFER_CROSS_EDITORS?c(f,h,g):d(h,g)}},null,null,9999)})}var m;CKEDITOR.plugins.add("clipboard",{requires:"dialog,notification,toolbar",init:function(a){var b,c=B(a);a.config.forcePasteAsPlainText?b="plain-text":a.config.pasteFilter?b=a.config.pasteFilter:!CKEDITOR.env.webkit||"pasteFilter"in a.config||(b="semantic-content");a.pasteFilter=c.get(b);w(a);D(a);CKEDITOR.dialog.add("paste",CKEDITOR.getUrl(this.path+ "dialogs/paste.js"));if(CKEDITOR.env.gecko){var d=["image/png","image/jpeg","image/gif"],g;a.on("paste",function(b){var c=b.data,f=c.dataTransfer;if(!c.dataValue&&"paste"==c.method&&f&&1==f.getFilesCount()&&g!=f.id&&(f=f.getFile(0),-1!=CKEDITOR.tools.indexOf(d,f.type))){var l=new FileReader;l.addEventListener("load",function(){b.data.dataValue='\x3cimg src\x3d"'+l.result+'" /\x3e';a.fire("paste",b.data)},!1);l.addEventListener("abort",function(){a.fire("paste",b.data)},!1);l.addEventListener("error", function(){a.fire("paste",b.data)},!1);l.readAsDataURL(f);g=c.dataTransfer.id;b.stop()}},null,null,1)}a.on("paste",function(b){b.data.dataTransfer||(b.data.dataTransfer=new CKEDITOR.plugins.clipboard.dataTransfer);if(!b.data.dataValue){var c=b.data.dataTransfer,d=c.getData("text/html");if(d)b.data.dataValue=d,b.data.type="html";else if(d=c.getData("text/plain"))b.data.dataValue=a.editable().transformPlainTextToHtml(d),b.data.type="text"}},null,null,1);a.on("paste",function(a){var b=a.data.dataValue, c=CKEDITOR.dtd.$block;-1 <\/span>/gi," "),"html"!=a.data.type&&(b=b.replace(/]*>([^<]*)<\/span>/gi,function(a,b){return b.replace(/\t/g,"\x26nbsp;\x26nbsp; \x26nbsp;")})),-1/,"")),b=b.replace(/(<[^>]+) class="Apple-[^"]*"/gi,"$1")); if(b.match(/^<[^<]+cke_(editable|contents)/i)){var d,g,n=new CKEDITOR.dom.element("div");for(n.setHtml(b);1==n.getChildCount()&&(d=n.getFirst())&&d.type==CKEDITOR.NODE_ELEMENT&&(d.hasClass("cke_editable")||d.hasClass("cke_contents"));)n=g=d;g&&(b=g.getHtml().replace(/
$/i,""))}CKEDITOR.env.ie?b=b.replace(/^ (?: |\r\n)?<(\w+)/g,function(b,d){return d.toLowerCase()in c?(a.data.preSniffing="html","\x3c"+d):b}):CKEDITOR.env.webkit?b=b.replace(/<\/(\w+)>


<\/div>$/,function(b,d){return d in c?(a.data.endsWithEOL=1,"\x3c/"+d+"\x3e"):b}):CKEDITOR.env.gecko&&(b=b.replace(/(\s)
$/,"$1"));a.data.dataValue=b},null,null,3);a.on("paste",function(b){b=b.data;var d=a._.nextPasteType||b.type,f=b.dataValue,g,m=a.config.clipboard_defaultContentType||"html",n=b.dataTransfer.getTransferType(a)==CKEDITOR.DATA_TRANSFER_EXTERNAL,k=!0===a.config.forcePasteAsPlainText;g="html"==d||"html"==b.preSniffing?"html":y(f);delete a._.nextPasteType;"htmlifiedtext"==g&&(f=z(a.config,f));if("text"==d&&"html"==g)f= v(a,f,c.get("plain-text"));else if(n&&a.pasteFilter&&!b.dontFilter||k)f=v(a,f,a.pasteFilter);b.startsWithEOL&&(f='\x3cbr data-cke-eol\x3d"1"\x3e'+f);b.endsWithEOL&&(f+='\x3cbr data-cke-eol\x3d"1"\x3e');"auto"==d&&(d="html"==g||"html"==m?"html":"text");b.type=d;b.dataValue=f;delete b.preSniffing;delete b.startsWithEOL;delete b.endsWithEOL},null,null,6);a.on("paste",function(b){b=b.data;b.dataValue&&(a.insertHtml(b.dataValue,b.type,b.range),setTimeout(function(){a.fire("afterPaste")},0))},null,null, 1E3);a.on("pasteDialog",function(b){setTimeout(function(){a.openDialog("paste",b.data)},0)})}});CKEDITOR.plugins.clipboard={isCustomCopyCutSupported:CKEDITOR.env.ie&&16>CKEDITOR.env.version||CKEDITOR.env.iOS&&605>CKEDITOR.env.version?!1:!0,isCustomDataTypesSupported:!CKEDITOR.env.ie||16<=CKEDITOR.env.version,isFileApiSupported:!CKEDITOR.env.ie||9CKEDITOR.env.version||b.isInline()?b:a.document},fixSplitNodesAfterDrop:function(a,b,c,d){function g(a,c,d){var g=a;g.type==CKEDITOR.NODE_TEXT&&(g=a.getParent());if(g.equals(c)&&d!=c.getChildCount())return a=b.startContainer.getChild(b.startOffset-1),c=b.startContainer.getChild(b.startOffset),a&&a.type==CKEDITOR.NODE_TEXT&&c&&c.type==CKEDITOR.NODE_TEXT&&(d=a.getLength(),a.setText(a.getText()+c.getText()),c.remove(),b.setStart(a,d),b.collapse(!0)),!0}var h=b.startContainer;"number"==typeof d&&"number"== typeof c&&h.type==CKEDITOR.NODE_ELEMENT&&(g(a.startContainer,h,c)||g(a.endContainer,h,d))},isDropRangeAffectedByDragRange:function(a,b){var c=b.startContainer,d=b.endOffset;return a.endContainer.equals(c)&&a.endOffset<=d||a.startContainer.getParent().equals(c)&&a.startContainer.getIndex()CKEDITOR.env.version&&this.fixSplitNodesAfterDrop(a,b,g.dragStartContainerChildCount,g.dragEndContainerChildCount);(f=this.isDropRangeAffectedByDragRange(a,b))||(e=a.createBookmark(!1));g=b.clone().createBookmark(!1);f&&(e=a.createBookmark(!1));a=e.startNode;b=e.endNode;f=g.startNode;b&&a.getPosition(f)&CKEDITOR.POSITION_PRECEDING&&b.getPosition(f)&CKEDITOR.POSITION_FOLLOWING&&f.insertBefore(a);a=d.createRange();a.moveToBookmark(e);h.extractHtmlFromRange(a,1);b=d.createRange(); g.startNode.getCommonAncestor(h)||(g=d.getSelection().createBookmarks()[0]);b.moveToBookmark(g);r(d,{dataTransfer:c,method:"drop",range:b},1);d.fire("unlockSnapshot")},getRangeAtDropPosition:function(a,b){var c=a.data.$,d=c.clientX,g=c.clientY,h=b.getSelection(!0).getRanges()[0],e=b.createRange();if(a.data.testRange)return a.data.testRange;if(document.caretRangeFromPoint&&b.document.$.caretRangeFromPoint(d,g))c=b.document.$.caretRangeFromPoint(d,g),e.setStart(CKEDITOR.dom.node(c.startContainer),c.startOffset), e.collapse(!0);else if(c.rangeParent)e.setStart(CKEDITOR.dom.node(c.rangeParent),c.rangeOffset),e.collapse(!0);else{if(CKEDITOR.env.ie&&8l&&!f;l++){if(!f)try{c.moveToPoint(d,g-l),f=!0}catch(m){}if(!f)try{c.moveToPoint(d,g+l),f=!0}catch(n){}}if(f){var k="cke-temp-"+(new Date).getTime();c.pasteHTML('\x3cspan id\x3d"'+k+'"\x3e​\x3c/span\x3e'); var t=b.document.getById(k);e.moveToPosition(t,CKEDITOR.POSITION_BEFORE_START);t.remove()}else{var q=b.document.$.elementFromPoint(d,g),p=new CKEDITOR.dom.element(q),r;if(p.equals(b.editable())||"html"==p.getName())return h&&h.startContainer&&!h.startContainer.equals(b.editable())?h:null;r=p.getClientRect();d/i,bodyRegExp:/([\s\S]*)<\/body>/i,fragmentRegExp:/\x3c!--(?:Start|End)Fragment--\x3e/g, data:{},files:[],nativeHtmlCache:"",normalizeType:function(a){a=a.toLowerCase();return"text"==a||"text/plain"==a?"Text":"url"==a?"URL":a}};this._.fallbackDataTransfer=new CKEDITOR.plugins.clipboard.fallbackDataTransfer(this);this.id=this.getData(m);this.id||(this.id="Text"==m?"":"cke-"+CKEDITOR.tools.getUniqueId());b&&(this.sourceEditor=b,this.setData("text/html",b.getSelectedHtml(1)),"Text"==m||this.getData("text/plain")||this.setData("text/plain",b.getSelection().getSelectedText()))};CKEDITOR.DATA_TRANSFER_INTERNAL= 1;CKEDITOR.DATA_TRANSFER_CROSS_EDITORS=2;CKEDITOR.DATA_TRANSFER_EXTERNAL=3;CKEDITOR.plugins.clipboard.dataTransfer.prototype={getData:function(a,b){a=this._.normalizeType(a);var c="text/html"==a&&b?this._.nativeHtmlCache:this._.data[a];if(void 0===c||null===c||""===c){if(this._.fallbackDataTransfer.isRequired())c=this._.fallbackDataTransfer.getData(a,b);else try{c=this.$.getData(a)||""}catch(d){c=""}"text/html"!=a||b||(c=this._stripHtml(c))}"Text"==a&&CKEDITOR.env.gecko&&this.getFilesCount()&&"file://"== c.substring(0,7)&&(c="");if("string"===typeof c)var g=c.indexOf("\x3c/html\x3e"),c=-1!==g?c.substring(0,g+7):c;return c},setData:function(a,b){a=this._.normalizeType(a);"text/html"==a?(this._.data[a]=this._stripHtml(b),this._.nativeHtmlCache=b):this._.data[a]=b;if(CKEDITOR.plugins.clipboard.isCustomDataTypesSupported||"URL"==a||"Text"==a)if("Text"==m&&"Text"==a&&(this.id=b),this._.fallbackDataTransfer.isRequired())this._.fallbackDataTransfer.setData(a,b);else try{this.$.setData(a,b)}catch(c){}},storeId:function(){"Text"!== m&&this.setData(m,this.id)},getTransferType:function(a){return this.sourceEditor?this.sourceEditor==a?CKEDITOR.DATA_TRANSFER_INTERNAL:CKEDITOR.DATA_TRANSFER_CROSS_EDITORS:CKEDITOR.DATA_TRANSFER_EXTERNAL},cacheData:function(){function a(a){a=b._.normalizeType(a);var c=b.getData(a);"text/html"==a&&(b._.nativeHtmlCache=b.getData(a,!0),c=b._stripHtml(c));c&&(b._.data[a]=c)}if(this.$){var b=this,c,d;if(CKEDITOR.plugins.clipboard.isCustomDataTypesSupported){if(this.$.types)for(c=0;cc.width&&(a.resize_minWidth=c.width);a.resize_minHeight>c.height&&(a.resize_minHeight=c.height);CKEDITOR.document.on("mousemove",f);CKEDITOR.document.on("mouseup",k);b.document&&(b.document.on("mousemove",f),b.document.on("mouseup",k));d.preventDefault&&d.preventDefault()});b.on("destroy", function(){CKEDITOR.tools.removeFunction(q)});b.on("uiSpace",function(a){if("bottom"==a.data.space){var e="";h&&!p&&(e=" cke_resizer_horizontal");!h&&p&&(e=" cke_resizer_vertical");var c='\x3cspan id\x3d"'+r+'" class\x3d"cke_resizer'+e+" cke_resizer_"+g+'" title\x3d"'+CKEDITOR.tools.htmlEncode(b.lang.common.resize)+'" onmousedown\x3d"CKEDITOR.tools.callFunction('+q+', event)"\x3e'+("ltr"==g?"â—¢":"â—£")+"\x3c/span\x3e";"ltr"==g&&"ltr"==e?a.data.html+=c:a.data.html=c+a.data.html}},b,null,100);b.on("maximize", function(a){b.ui.space("resizer")[a.data==CKEDITOR.TRISTATE_ON?"hide":"show"]()})}}});(function(){function x(a,e,b){b=a.config.forceEnterMode||b;if("wysiwyg"==a.mode){e||(e=a.activeEnterMode);var l=a.elementPath();l&&!l.isContextFor("p")&&(e=CKEDITOR.ENTER_BR,b=1);a.fire("saveSnapshot");e==CKEDITOR.ENTER_BR?u(a,e,null,b):r(a,e,null,b);a.fire("saveSnapshot")}}function y(a){a=a.getSelection().getRanges(!0);for(var e=a.length-1;0CKEDITOR.env.version?n.createText("\r"):n.createElement("br"),b.deleteContents(),b.insertNode(a),CKEDITOR.env.needsBrFiller?(n.createText("").insertAfter(a),g&&(k||f.blockLimit).appendBogus(),a.getNext().$.nodeValue="",b.setStartAt(a.getNext(), CKEDITOR.POSITION_AFTER_START)):b.setStartAt(a,CKEDITOR.POSITION_AFTER_END)),b.collapse(!0),b.select(),b.scrollIntoView()):r(a,e,b,l)}}};v=CKEDITOR.plugins.enterkey;u=v.enterBr;r=v.enterBlock;w=/^h[1-6]$/})();(function(){function k(a,f){var g={},c=[],e={nbsp:" ",shy:"­",gt:"\x3e",lt:"\x3c",amp:"\x26",apos:"'",quot:'"'};a=a.replace(/\b(nbsp|shy|gt|lt|amp|apos|quot)(?:,|$)/g,function(a,b){var d=f?"\x26"+b+";":e[b];g[d]=f?e[b]:"\x26"+b+";";c.push(d);return""});a=a.replace(/,$/,"");if(!f&&a){a=a.split(",");var b=document.createElement("div"),d;b.innerHTML="\x26"+a.join(";\x26")+";";d=b.innerHTML;b=null;for(b=0;bc?d+c:b.width>c?d-a.left:d-a.right+b.width):mc?d-c:b.width>c?d-a.right+b.width:d-a.left);c=a.top;b.height-a.tope?p-e:b.height>e?p-a.bottom+b.height:p-a.top);CKEDITOR.env.ie&&!CKEDITOR.env.edge&&((b=a=k.$.offsetParent&&new CKEDITOR.dom.element(k.$.offsetParent))&&"html"==b.getName()&&(b=b.getDocument().getBody()),b&&"rtl"==b.getComputedStyle("direction")&&(d=CKEDITOR.env.ie8Compat?d-2*k.getDocument().getDocumentElement().$.scrollLeft:d-(a.$.scrollWidth-a.$.clientWidth))); var a=k.getFirst(),f;(f=a.getCustomData("activePanel"))&&f.onHide&&f.onHide.call(this,1);a.setCustomData("activePanel",this);k.setStyles({top:p+"px",left:d+"px"});k.setOpacity(1);g&&g()},this);n.isLoaded?a():n.onLoad=a;CKEDITOR.tools.setTimeout(function(){var a=CKEDITOR.env.webkit&&CKEDITOR.document.getWindow().getScrollPosition().y;this.focus();l.element.focus();CKEDITOR.env.webkit&&(CKEDITOR.document.getBody().$.scrollTop=a);this.allowBlur(!0);this._.markFirst&&(CKEDITOR.env.ie?CKEDITOR.tools.setTimeout(function(){l.markFirstDisplayed? l.markFirstDisplayed():l._.markFirstDisplayed()},0):l.markFirstDisplayed?l.markFirstDisplayed():l._.markFirstDisplayed());this._.editor.fire("panelShow",this)},0,this)},CKEDITOR.env.air?200:0,this);this.visible=1;this.onShow&&this.onShow.call(this)},reposition:function(){var a=this._.showBlockParams;this.visible&&this._.showBlockParams&&(this.hide(),this.showBlock.apply(this,a))},focus:function(){if(CKEDITOR.env.webkit){var a=CKEDITOR.document.getActive();a&&!a.equals(this._.iframe)&&a.$.blur()}(this._.lastFocused|| this._.iframe.getFrameDocument().getWindow()).focus()},blur:function(){var a=this._.iframe.getFrameDocument().getActive();a&&a.is("a")&&(this._.lastFocused=a)},hide:function(a){if(this.visible&&(!this.onHide||!0!==this.onHide.call(this))){this.hideChild();CKEDITOR.env.gecko&&this._.iframe.getFrameDocument().$.activeElement.blur();this.element.setStyle("display","none");this.visible=0;this.element.getFirst().removeCustomData("activePanel");if(a=a&&this._.returnFocus)CKEDITOR.env.webkit&&a.type&&a.getWindow().$.focus(), a.focus();delete this._.lastFocused;this._.showBlockParams=null;this._.editor.fire("panelHide",this)}},allowBlur:function(a){var b=this._.panel;void 0!==a&&(b.allowBlur=a);return b.allowBlur},showAsChild:function(a,b,c,f,h,g){if(this._.activeChild!=a||a._.panel._.offsetParentId!=c.getId())this.hideChild(),a.onHide=CKEDITOR.tools.bind(function(){CKEDITOR.tools.setTimeout(function(){this._.focused||this.hide()},0,this)},this),this._.activeChild=a,this._.focused=!1,a.showBlock(b,c,f,h,g),this.blur(), (CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat)&&setTimeout(function(){a.element.getChild(0).$.style.cssText+=""},100)},hideChild:function(a){var b=this._.activeChild;b&&(delete b.onHide,delete this._.activeChild,b.hide(),a&&this.focus())}}});CKEDITOR.on("instanceDestroyed",function(){var a=CKEDITOR.tools.isEmpty(CKEDITOR.instances),b;for(b in f){var c=f[b];a?c.destroy():c.element.hide()}a&&(f={})})})();CKEDITOR.plugins.add("listblock",{requires:"panel",onLoad:function(){var f=CKEDITOR.addTemplate("panel-list",'\x3cul role\x3d"presentation" class\x3d"cke_panel_list"\x3e{items}\x3c/ul\x3e'),g=CKEDITOR.addTemplate("panel-list-item",'\x3cli id\x3d"{id}" class\x3d"cke_panel_listItem" role\x3dpresentation\x3e\x3ca id\x3d"{id}_option" _cke_focus\x3d1 hidefocus\x3dtrue title\x3d"{title}" draggable\x3d"false" ondragstart\x3d"return false;" href\x3d"javascript:void(\'{val}\')" onclick\x3d"{onclick}CKEDITOR.tools.callFunction({clickFn},\'{val}\'); return false;" role\x3d"option"\x3e{text}\x3c/a\x3e\x3c/li\x3e'), h=CKEDITOR.addTemplate("panel-list-group",'\x3ch1 id\x3d"{id}" draggable\x3d"false" ondragstart\x3d"return false;" class\x3d"cke_panel_grouptitle" role\x3d"presentation" \x3e{label}\x3c/h1\x3e'),k=/\'/g;CKEDITOR.ui.panel.prototype.addListBlock=function(a,b){return this.addBlock(a,new CKEDITOR.ui.listBlock(this.getHolderElement(),b))};CKEDITOR.ui.listBlock=CKEDITOR.tools.createClass({base:CKEDITOR.ui.panel.block,$:function(a,b){b=b||{};var c=b.attributes||(b.attributes={});(this.multiSelect=!!b.multiSelect)&& (c["aria-multiselectable"]=!0);!c.role&&(c.role="listbox");this.base.apply(this,arguments);this.element.setAttribute("role",c.role);c=this.keys;c[40]="next";c[9]="next";c[38]="prev";c[CKEDITOR.SHIFT+9]="prev";c[32]=CKEDITOR.env.ie?"mouseup":"click";CKEDITOR.env.ie&&(c[13]="mouseup");this._.pendingHtml=[];this._.pendingList=[];this._.items={};this._.groups={}},_:{close:function(){if(this._.started){var a=f.output({items:this._.pendingList.join("")});this._.pendingList=[];this._.pendingHtml.push(a); delete this._.started}},getClick:function(){this._.click||(this._.click=CKEDITOR.tools.addFunction(function(a){var b=this.toggle(a);if(this.onClick)this.onClick(a,b)},this));return this._.click}},proto:{add:function(a,b,c){var d=CKEDITOR.tools.getNextId();this._.started||(this._.started=1,this._.size=this._.size||0);this._.items[a]=d;var e;e=CKEDITOR.tools.htmlEncodeAttr(a).replace(k,"\\'");a={id:d,val:e,onclick:CKEDITOR.env.ie?'return false;" onmouseup\x3d"CKEDITOR.tools.getMouseButton(event)\x3d\x3d\x3dCKEDITOR.MOUSE_BUTTON_LEFT\x26\x26': "",clickFn:this._.getClick(),title:CKEDITOR.tools.htmlEncodeAttr(c||a),text:b||a};this._.pendingList.push(g.output(a))},startGroup:function(a){this._.close();var b=CKEDITOR.tools.getNextId();this._.groups[a]=b;this._.pendingHtml.push(h.output({id:b,label:a}))},commit:function(){this._.close();this.element.appendHtml(this._.pendingHtml.join(""));delete this._.size;this._.pendingHtml=[]},toggle:function(a){var b=this.isMarked(a);b?this.unmark(a):this.mark(a);return!b},hideGroup:function(a){var b=(a= this.element.getDocument().getById(this._.groups[a]))&&a.getNext();a&&(a.setStyle("display","none"),b&&"ul"==b.getName()&&b.setStyle("display","none"))},hideItem:function(a){this.element.getDocument().getById(this._.items[a]).setStyle("display","none")},showAll:function(){var a=this._.items,b=this._.groups,c=this.element.getDocument(),d;for(d in a)c.getById(a[d]).setStyle("display","");for(var e in b)a=c.getById(b[e]),d=a.getNext(),a.setStyle("display",""),d&&"ul"==d.getName()&&d.setStyle("display", "")},mark:function(a){this.multiSelect||this.unmarkAll();a=this._.items[a];var b=this.element.getDocument().getById(a);b.addClass("cke_selected");this.element.getDocument().getById(a+"_option").setAttribute("aria-selected",!0);this.onMark&&this.onMark(b)},markFirstDisplayed:function(){var a=this;this._.markFirstDisplayed(function(){a.multiSelect||a.unmarkAll()})},unmark:function(a){var b=this.element.getDocument();a=this._.items[a];var c=b.getById(a);c.removeClass("cke_selected");b.getById(a+"_option").removeAttribute("aria-selected"); this.onUnmark&&this.onUnmark(c)},unmarkAll:function(){var a=this._.items,b=this.element.getDocument(),c;for(c in a){var d=a[c];b.getById(d).removeClass("cke_selected");b.getById(d+"_option").removeAttribute("aria-selected")}this.onUnmark&&this.onUnmark()},isMarked:function(a){return this.element.getDocument().getById(this._.items[a]).hasClass("cke_selected")},focus:function(a){this._.focusIndex=-1;var b=this.element.getElementsByTag("a"),c,d=-1;if(a)for(c=this.element.getDocument().getById(this._.items[a]).getFirst();a= b.getItem(++d);){if(a.equals(c)){this._.focusIndex=d;break}}else this.element.focus();c&&setTimeout(function(){c.focus()},0)}}})}});CKEDITOR.plugins.add("richcombo",{requires:"floatpanel,listblock,button",beforeInit:function(c){c.ui.addHandler(CKEDITOR.UI_RICHCOMBO,CKEDITOR.ui.richCombo.handler)}}); (function(){var c='\x3cspan id\x3d"{id}" class\x3d"cke_combo cke_combo__{name} {cls}" role\x3d"presentation"\x3e\x3cspan id\x3d"{id}_label" class\x3d"cke_combo_label"\x3e{label}\x3c/span\x3e\x3ca class\x3d"cke_combo_button" title\x3d"{title}" tabindex\x3d"-1"'+(CKEDITOR.env.gecko&&!CKEDITOR.env.hc?"":" href\x3d\"javascript:void('{titleJs}')\"")+' hidefocus\x3d"true" role\x3d"button" aria-labelledby\x3d"{id}_label" aria-haspopup\x3d"listbox"',h="";CKEDITOR.env.gecko&&CKEDITOR.env.mac&&(c+=' onkeypress\x3d"return false;"'); CKEDITOR.env.gecko&&(c+=' onblur\x3d"this.style.cssText \x3d this.style.cssText;"');CKEDITOR.env.ie&&(h='return false;" onmouseup\x3d"CKEDITOR.tools.getMouseButton(event)\x3d\x3dCKEDITOR.MOUSE_BUTTON_LEFT\x26\x26');var c=c+(' onkeydown\x3d"return CKEDITOR.tools.callFunction({keydownFn},event,this);" onfocus\x3d"return CKEDITOR.tools.callFunction({focusFn},event);" onclick\x3d"'+h+'CKEDITOR.tools.callFunction({clickFn},this);return false;"\x3e\x3cspan id\x3d"{id}_text" class\x3d"cke_combo_text cke_combo_inlinelabel"\x3e{label}\x3c/span\x3e\x3cspan class\x3d"cke_combo_open"\x3e\x3cspan class\x3d"cke_combo_arrow"\x3e'+ (CKEDITOR.env.hc?"\x26#9660;":CKEDITOR.env.air?"\x26nbsp;":"")+"\x3c/span\x3e\x3c/span\x3e\x3c/a\x3e\x3c/span\x3e"),n=CKEDITOR.addTemplate("combo",c);CKEDITOR.UI_RICHCOMBO="richcombo";CKEDITOR.ui.richCombo=CKEDITOR.tools.createClass({$:function(a){CKEDITOR.tools.extend(this,a,{canGroup:!1,title:a.label,modes:{wysiwyg:1},editorFocus:1});a=this.panel||{};delete this.panel;this.id=CKEDITOR.tools.getNextNumber();this.document=a.parent&&a.parent.getDocument()||CKEDITOR.document;a.className="cke_combopanel"; a.block={multiSelect:a.multiSelect,attributes:a.attributes};a.toolbarRelated=!0;this._={panelDefinition:a,items:{},listeners:[]}},proto:{renderHtml:function(a){var b=[];this.render(a,b);return b.join("")},render:function(a,b){function f(){if(this.getState()!=CKEDITOR.TRISTATE_ON){var b=this.modes[a.mode]?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED;a.readOnly&&!this.readOnly&&(b=CKEDITOR.TRISTATE_DISABLED);this.setState(b);this.setValue("");b!=CKEDITOR.TRISTATE_DISABLED&&this.refresh&&this.refresh()}} var c=CKEDITOR.env,k="cke_"+this.id,e=CKEDITOR.tools.addFunction(function(b){l&&(a.unlockSelection(1),l=0);g.execute(b)},this),d=this,g={id:k,combo:this,focus:function(){CKEDITOR.document.getById(k).getChild(1).focus()},execute:function(b){var c=d._;if(c.state!=CKEDITOR.TRISTATE_DISABLED)if(d.createPanel(a),c.on)c.panel.hide();else{d.commit();var f=d.getValue();f?c.list.mark(f):c.list.unmarkAll();c.panel.showBlock(d.id,new CKEDITOR.dom.element(b),4)}},clickFn:e};this._.listeners.push(a.on("activeFilterChange", f,this));this._.listeners.push(a.on("mode",f,this));this._.listeners.push(a.on("selectionChange",f,this));!this.readOnly&&this._.listeners.push(a.on("readOnly",f,this));var m=CKEDITOR.tools.addFunction(function(a,b){a=new CKEDITOR.dom.event(a);var d=a.getKeystroke();switch(d){case 13:case 32:case 40:CKEDITOR.tools.callFunction(e,b);break;default:g.onkey(g,d)}a.preventDefault()}),h=CKEDITOR.tools.addFunction(function(){g.onfocus&&g.onfocus()}),l=0;g.keyDownFn=m;c={id:k,name:this.name||this.command, label:this.label,title:this.title,cls:this.className||"",titleJs:c.gecko&&!c.hc?"":(this.title||"").replace("'",""),keydownFn:m,focusFn:h,clickFn:e};n.output(c,b);if(this.onRender)this.onRender();return g},createPanel:function(a){if(!this._.panel){var b=this._.panelDefinition,c=this._.panelDefinition.block,h=b.parent||CKEDITOR.document.getBody(),k="cke_combopanel__"+this.name,e=new CKEDITOR.ui.floatPanel(a,h,b),b=e.addListBlock(this.id,c),d=this;e.onShow=function(){this.element.addClass(k);d.setState(CKEDITOR.TRISTATE_ON); d._.on=1;d.editorFocus&&!a.focusManager.hasFocus&&a.focus();if(d.onOpen)d.onOpen()};e.onHide=function(b){this.element.removeClass(k);d.setState(d.modes&&d.modes[a.mode]?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED);d._.on=0;if(!b&&d.onClose)d.onClose()};e.onEscape=function(){e.hide(1)};b.onClick=function(a,b){d.onClick&&d.onClick.call(d,a,b);e.hide()};this._.panel=e;this._.list=b;e.getBlock(this.id).onHide=function(){d._.on=0;d.setState(CKEDITOR.TRISTATE_OFF)};this.init&&this.init()}},setValue:function(a, b){this._.value=a;var c=this.document.getById("cke_"+this.id+"_text");c&&(a||b?c.removeClass("cke_combo_inlinelabel"):(b=this.label,c.addClass("cke_combo_inlinelabel")),c.setText("undefined"!=typeof b?b:a))},getValue:function(){return this._.value||""},unmarkAll:function(){this._.list.unmarkAll()},mark:function(a){this._.list.mark(a)},hideItem:function(a){this._.list.hideItem(a)},hideGroup:function(a){this._.list.hideGroup(a)},showAll:function(){this._.list.showAll()},add:function(a,b,c){this._.items[a]= c||a;this._.list.add(a,b,c)},startGroup:function(a){this._.list.startGroup(a)},commit:function(){this._.committed||(this._.list.commit(),this._.committed=1,CKEDITOR.ui.fire("ready",this));this._.committed=1},setState:function(a){if(this._.state!=a){var b=this.document.getById("cke_"+this.id);b.setState(a,"cke_combo");a==CKEDITOR.TRISTATE_DISABLED?b.setAttribute("aria-disabled",!0):b.removeAttribute("aria-disabled");this._.state=a}},getState:function(){return this._.state},enable:function(){this._.state== CKEDITOR.TRISTATE_DISABLED&&this.setState(this._.lastState)},disable:function(){this._.state!=CKEDITOR.TRISTATE_DISABLED&&(this._.lastState=this._.state,this.setState(CKEDITOR.TRISTATE_DISABLED))},destroy:function(){CKEDITOR.tools.array.forEach(this._.listeners,function(a){a.removeListener()});this._.listeners=[]}},statics:{handler:{create:function(a){return new CKEDITOR.ui.richCombo(a)}}}});CKEDITOR.ui.prototype.addRichCombo=function(a,b){this.add(a,CKEDITOR.UI_RICHCOMBO,b)}})();CKEDITOR.plugins.add("format",{requires:"richcombo",init:function(a){if(!a.blockless){for(var f=a.config,c=a.lang.format,l=f.format_tags.split(";"),d={},m=0,n=[],g=0;gd.length)return!1;e=b.getParents(!0);for(a=0;am;a++)h[a].indent+=e;e=CKEDITOR.plugins.list.arrayToList(h,q,null,f.config.enterMode,b.getDirection());if(!k.isIndent){var t;if((t=b.getParent())&&t.is("li"))for(var d=e.listNode.getChildren(), r=[],l,a=d.count()-1;0<=a;a--)(l=d.getItem(a))&&l.is&&l.is("li")&&r.push(l)}e&&e.listNode.replace(b);if(r&&r.length)for(a=0;ag.length)){f=g[g.length-1].getNext();b=e.createElement(this.type);for(d.push(b);g.length;)d=g.shift(),a=e.createElement("li"),c=d,c.is("pre")||M.test(c.getName())||"false"==c.getAttribute("contenteditable")?d.appendTo(a):(d.copyAttributes(a),h&&d.getDirection()&&(a.removeStyle("direction"), a.removeAttribute("dir")),d.moveChildren(a),d.remove()),a.appendTo(b);h&&k&&b.setAttribute("dir",h);f?b.insertBefore(f):b.appendTo(l)}}function N(a,l,d){function f(b){if(!(!(c=k[b?"getFirst":"getLast"]())||c.is&&c.isBlockBoundary()||!(p=l.root[b?"getPrevious":"getNext"](CKEDITOR.dom.walker.invisible(!0)))||p.is&&p.isBlockBoundary({br:1})))a.document.createElement("br")[b?"insertBefore":"insertAfter"](c)}for(var e=CKEDITOR.plugins.list.listToArray(l.root,d),g=[],b=0;be[b-1].indent+1){g=e[b-1].indent+1-e[b].indent;for(h=e[b].indent;e[b]&&e[b].indent>=h;)e[b].indent+=g,b++;b--}var k=CKEDITOR.plugins.list.arrayToList(e,d,null,a.config.enterMode,l.root.getAttribute("dir")).listNode,c,p; f(!0);f();k.replace(l.root);a.fire("contentDomInvalidated")}function C(a,l){this.name=a;this.context=this.type=l;this.allowedContent=l+" li";this.requiredContent=l}function F(a,l,d,f){for(var e,g;e=a[f?"getLast":"getFirst"](O);)(g=e.getDirection(1))!==l.getDirection(1)&&e.setAttribute("dir",g),e.remove(),d?e[f?"insertBefore":"insertAfter"](d):l.append(e,f),d=e}function G(a){function l(d){var f=a[d?"getPrevious":"getNext"](t);f&&f.type==CKEDITOR.NODE_ELEMENT&&f.is(a.getName())&&(F(a,f,null,!d),a.remove(), a=f)}l();l(1)}function H(a){return a.type==CKEDITOR.NODE_ELEMENT&&(a.getName()in CKEDITOR.dtd.$block||a.getName()in CKEDITOR.dtd.$listItem)&&CKEDITOR.dtd[a.getName()]["#"]}function D(a,l,d){a.fire("saveSnapshot");d.enlarge(CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS);var f=d.extractContents();l.trim(!1,!0);var e=l.createBookmark(),g=new CKEDITOR.dom.elementPath(l.startContainer),b=g.block,g=g.lastElement.getAscendant("li",1)||b,h=new CKEDITOR.dom.elementPath(d.startContainer),k=h.contains(CKEDITOR.dtd.$listItem), h=h.contains(CKEDITOR.dtd.$list);b?(b=b.getBogus())&&b.remove():h&&(b=h.getPrevious(t))&&z(b)&&b.remove();(b=f.getLast())&&b.type==CKEDITOR.NODE_ELEMENT&&b.is("br")&&b.remove();(b=l.startContainer.getChild(l.startOffset))?f.insertBefore(b):l.startContainer.append(f);k&&(f=A(k))&&(g.contains(k)?(F(f,k.getParent(),k),f.remove()):g.append(f));for(;d.checkStartOfBlock()&&d.checkEndOfBlock();){h=d.startPath();f=h.block;if(!f)break;f.is("li")&&(g=f.getParent(),f.equals(g.getLast(t))&&f.equals(g.getFirst(t))&& (f=g));d.moveToPosition(f,CKEDITOR.POSITION_BEFORE_START);f.remove()}d=d.clone();f=a.editable();d.setEndAt(f,CKEDITOR.POSITION_BEFORE_END);d=new CKEDITOR.dom.walker(d);d.evaluator=function(a){return t(a)&&!z(a)};(d=d.next())&&d.type==CKEDITOR.NODE_ELEMENT&&d.getName()in CKEDITOR.dtd.$list&&G(d);l.moveToBookmark(e);l.select();a.fire("saveSnapshot")}function A(a){return(a=a.getLast(t))&&a.type==CKEDITOR.NODE_ELEMENT&&a.getName()in u?a:null}var u={ol:1,ul:1},P=CKEDITOR.dom.walker.whitespaces(),I=CKEDITOR.dom.walker.bookmark(), t=function(a){return!(P(a)||I(a))},z=CKEDITOR.dom.walker.bogus();CKEDITOR.plugins.list={listToArray:function(a,l,d,f,e){if(!u[a.getName()])return[];f||(f=0);d||(d=[]);for(var g=0,b=a.getChildCount();g=b.$.documentMode&&m.append(b.createText(" ")),m.append(c.listNode),c=c.nextIndex;else if(-1==q.indent&&!d&&g){u[g.getName()]?(m=q.element.clone(!1,!0),n!=g.getDirection(1)&&m.setAttribute("dir",n)):m=new CKEDITOR.dom.documentFragment(b);var k=g.getDirection(1)!=n,w=q.element,B=w.getAttribute("class"),E=w.getAttribute("style"),J=m.type==CKEDITOR.NODE_DOCUMENT_FRAGMENT&&(f!=CKEDITOR.ENTER_BR||k||E||B),v,z=q.contents.length,x;for(g=0;gc&&at.version?" ":J,f=a.hotNode&&a.hotNode.getText()==d&&a.element.equals(a.hotNode)&&a.lastCmdDirection===!!c;S(a,function(d){f&&a.hotNode&&a.hotNode.remove();d[c?"insertAfter":"insertBefore"](b);d.setAttributes({"data-cke-magicline-hot":1,"data-cke-magicline-dir":!!c});a.lastCmdDirection=!!c});t.ie||a.enterMode==CKEDITOR.ENTER_BR||a.hotNode.scrollIntoView();a.line.detach()}return function(b){b=b.getSelection().getStartElement(); var e;b=b.getAscendant(Z,1);if(!aa(a,b)&&b&&!b.equals(a.editable)&&!b.contains(a.editable)){(e=P(b))&&"false"==e.getAttribute("contenteditable")&&(b=e);a.element=b;e=w(a,b,!c);var f;n(e)&&e.is(a.triggers)&&e.is(ma)&&(!w(a,e,!c)||(f=w(a,e,!c))&&n(f)&&f.is(a.triggers))?d(e):(f=O(a,b),n(f)&&(w(a,f,!c)?(b=w(a,f,!c))&&n(b)&&b.is(a.triggers)&&d(f):d(f)))}}}()}}function A(a,c){if(!c||c.type!=CKEDITOR.NODE_ELEMENT||!c.$)return!1;var d=a.line;return d.wrap.equals(c)||d.wrap.contains(c)}function n(a){return a&& a.type==CKEDITOR.NODE_ELEMENT&&a.$}function u(a){if(!n(a))return!1;var c;(c=ba(a))||(n(a)?(c={left:1,right:1,center:1},c=!(!c[a.getComputedStyle("float")]&&!c[a.getAttribute("align")])):c=!1);return c}function ba(a){return!!{absolute:1,fixed:1}[a.getComputedStyle("position")]}function L(a,c){return n(c)?c.is(a.triggers):null}function aa(a,c){if(!c)return!1;for(var d=c.getParents(1),b=d.length;b--;)for(var e=a.tabuList.length;e--;)if(d[b].hasAttribute(a.tabuList[e]))return!0;return!1}function na(a, c,d){c=c[d?"getLast":"getFirst"](function(b){return a.isRelevant(b)&&!b.is(oa)});if(!c)return!1;r(a,c);return d?c.size.top>a.mouse.y:c.size.bottom(a.inInlineMode?b.editable.top+b.editable.height/2:Math.min(b.editable.height,b.pane.height)/2),c=c[f?"getLast":"getFirst"](function(a){return!(E(a)||F(a))});if(!c)return null;A(a,c)&&(c=a.line.wrap[f?"getPrevious":"getNext"](function(a){return!(E(a)||F(a))})); if(!n(c)||u(c)||!L(a,c))return null;r(a,c);return!f&&0<=c.size.top&&q(d.y,0,c.size.top+e)?(a=a.inInlineMode||0===b.scroll.y?C:x,new z([null,c,I,M,a])):f&&c.size.bottom<=b.pane.height&&q(d.y,c.size.bottom-e,b.pane.height)?(a=a.inInlineMode||q(c.size.bottom,b.pane.height-e,b.pane.height)?D:x,new z([c,null,da,M,a])):null}function ea(a){var c=a.mouse,d=a.view,b=a.triggerOffset,e=O(a);if(!e)return null;r(a,e);var b=Math.min(b,0|e.size.outerHeight/2),f=[],k,h;if(q(c.y,e.size.top-1,e.size.top+b))h=!1;else if(q(c.y, e.size.bottom-b,e.size.bottom+1))h=!0;else return null;if(u(e)||na(a,e,h)||e.getParent().is(fa))return null;var g=w(a,e,!h);if(g){if(g&&g.type==CKEDITOR.NODE_TEXT)return null;if(n(g)){if(u(g)||!L(a,g)||g.getParent().is(fa))return null;f=[g,e][h?"reverse":"concat"]().concat([T,M])}}else e.equals(a.editable[h?"getLast":"getFirst"](a.isRelevant))?(H(a),h&&q(c.y,e.size.bottom-b,d.pane.height)&&q(e.size.bottom,d.pane.height-b,d.pane.height)?k=D:q(c.y,0,e.size.top+b)&&(k=C)):k=x,f=[null,e][h?"reverse": "concat"]().concat([h?da:I,M,k,e.equals(a.editable[h?"getLast":"getFirst"](a.isRelevant))?h?D:C:x]);return 0 in f?new z(f):null}function U(a,c,d,b){for(var e=c.getDocumentPosition(),f={},k={},h={},g={},l=y.length;l--;)f[y[l]]=parseInt(c.getComputedStyle.call(c,"border-"+y[l]+"-width"),10)||0,h[y[l]]=parseInt(c.getComputedStyle.call(c,"padding-"+y[l]),10)||0,k[y[l]]=parseInt(c.getComputedStyle.call(c,"margin-"+y[l]),10)||0;d&&!b||N(a,b);g.top=e.y-(d?0:a.view.scroll.y);g.left=e.x-(d?0:a.view.scroll.x); g.outerWidth=c.$.offsetWidth;g.outerHeight=c.$.offsetHeight;g.height=g.outerHeight-(h.top+h.bottom+f.top+f.bottom);g.width=g.outerWidth-(h.left+h.right+f.left+f.right);g.bottom=g.top+g.outerHeight;g.right=g.left+g.outerWidth;a.inInlineMode&&(g.scroll={top:c.$.scrollTop,left:c.$.scrollLeft});return v({border:f,padding:h,margin:k,ignoreScroll:d},g,!0)}function r(a,c,d){if(!n(c))return c.size=null;if(!c.size)c.size={};else if(c.size.ignoreScroll==d&&c.size.date>new Date-ga)return null;return v(c.size, U(a,c,d),{date:+new Date},!0)}function H(a,c){a.view.editable=U(a,a.editable,c,!0)}function N(a,c){a.view||(a.view={});var d=a.view;if(!(!c&&d&&d.date>new Date-ga)){var b=a.win,d=b.getScrollPosition(),b=b.getViewPaneSize();v(a.view,{scroll:{x:d.x,y:d.y,width:a.doc.$.documentElement.scrollWidth-b.width,height:a.doc.$.documentElement.scrollHeight-b.height},pane:{width:b.width,height:b.height,bottom:b.height+d.y},date:+new Date},!0)}}function pa(a,c,d,b){for(var e=b,f=b,k=0,h=!1,g=!1,l=a.view.pane.height, p=a.mouse;p.y+ke.left-f.x&&de.top-f.y&&cCKEDITOR.env.version,B=CKEDITOR.dtd,K={},I=128,da=64,T=32,M=16,C=4,D=2,x=1,J=" ",fa=B.$listItem,oa=B.$tableContent,ma=v({},B.$nonEditable,B.$empty),Z=B.$block,ga=100,Q="width:0px;height:0px;padding:0px;margin:0px;display:block;z-index:9999;color:#fff;position:absolute;font-size: 0px;line-height:0px;", X=Q+"border-color:transparent;display:block;border-style:solid;",W="\x3cspan\x3e"+J+"\x3c/span\x3e";K[CKEDITOR.ENTER_BR]="br";K[CKEDITOR.ENTER_P]="p";K[CKEDITOR.ENTER_DIV]="div";z.prototype={set:function(a,c,d){this.properties=a+c+(d||x);return this},is:function(a){return(this.properties&a)==a}};var ha=function(){function a(a,d){var b=a.$.elementFromPoint(d.x,d.y);return b&&b.nodeType?new CKEDITOR.dom.element(b):null}return function(c,d,b){if(!c.mouse)return null;var e=c.doc,f=c.line.wrap;b=b||c.mouse; var k=a(e,b);d&&A(c,k)&&(f.hide(),k=a(e,b),f.show());return!k||k.type!=CKEDITOR.NODE_ELEMENT||!k.$||t.ie&&9>t.version&&!c.boundary.equals(k)&&!c.boundary.contains(k)?null:k}}(),E=CKEDITOR.dom.walker.whitespaces(),F=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_COMMENT),ia=function(){function a(a){var b=a.element,e,f,k;if(!n(b)||b.contains(a.editable)||b.isReadOnly())return null;k=pa(a,function(a,b){return!b.equals(a)},function(a,b){return ha(a,!0,b)},b);e=k.upper;f=k.lower;if(V(a,e,f))return k.set(T, 8);if(e&&b.contains(e))for(;!e.getParent().equals(b);)e=e.getParent();else e=b.getFirst(function(b){return c(a,b)});if(f&&b.contains(f))for(;!f.getParent().equals(b);)f=f.getParent();else f=b.getLast(function(b){return c(a,b)});if(!e||!f)return null;r(a,e);r(a,f);if(!q(a.mouse.y,e.size.top,f.size.bottom))return null;for(var b=Number.MAX_VALUE,h,g,l,p;f&&!f.equals(e)&&(g=e.getNext(a.isRelevant));)h=Math.abs(ka(a,e,g)-a.mouse.y),h|<\/font>)/,b=/=parseInt(d.getAttribute("border"),10))&&d.addClass("cke_show_border")})},afterInit:function(a){var b=a.dataProcessor;a=b&&b.dataFilter;b=b&&b.htmlFilter;a&&a.addRules({elements:{table:function(a){a=a.attributes;var b=a["class"],c=parseInt(a.border,10);c&&!(0>=c)||b&&-1!=b.indexOf("cke_show_border")||(a["class"]=(b||"")+" cke_show_border")}}});b&&b.addRules({elements:{table:function(a){a=a.attributes;var b=a["class"];b&&(a["class"]=b.replace("cke_show_border","").replace(/\s{2}/," ").replace(/^\s+|\s+$/, ""))}}})}});CKEDITOR.on("dialogDefinition",function(a){var b=a.data.name;if("table"==b||"tableProperties"==b)if(a=a.data.definition,b=a.getContents("info").get("txtBorder"),b.commit=CKEDITOR.tools.override(b.commit,function(a){return function(b,c){a.apply(this,arguments);var e=parseInt(this.getValue(),10);c[!e||0>=e?"addClass":"removeClass"]("cke_show_border")}}),a=(a=a.getContents("advanced"))&&a.get("advCSSClasses"))a.setup=CKEDITOR.tools.override(a.setup,function(a){return function(){a.apply(this, arguments);this.setValue(this.getValue().replace(/cke_show_border/,""))}}),a.commit=CKEDITOR.tools.override(a.commit,function(a){return function(b,c){a.apply(this,arguments);parseInt(c.getAttribute("border"),10)||c.addClass("cke_show_border")}})})})();(function(){CKEDITOR.plugins.add("sourcearea",{init:function(a){function d(){var a=e&&this.equals(CKEDITOR.document.getActive());this.hide();this.setStyle("height",this.getParent().$.clientHeight+"px");this.setStyle("width",this.getParent().$.clientWidth+"px");this.show();a&&this.focus()}if(a.elementMode!=CKEDITOR.ELEMENT_MODE_INLINE){var f=CKEDITOR.plugins.sourcearea;a.addMode("source",function(e){var b=a.ui.space("contents").getDocument().createElement("textarea");b.setStyles(CKEDITOR.tools.extend({width:CKEDITOR.env.ie7Compat? "99%":"100%",height:"100%",resize:"none",outline:"none","text-align":"left"},CKEDITOR.tools.cssVendorPrefix("tab-size",a.config.sourceAreaTabSize||4)));b.setAttribute("dir","ltr");b.addClass("cke_source").addClass("cke_reset").addClass("cke_enable_context_menu");a.ui.space("contents").append(b);b=a.editable(new c(a,b));b.setData(a.getData(1));CKEDITOR.env.ie&&(b.attachListener(a,"resize",d,b),b.attachListener(CKEDITOR.document.getWindow(),"resize",d,b),CKEDITOR.tools.setTimeout(d,0,b));a.fire("ariaWidget", this);e()});a.addCommand("source",f.commands.source);a.ui.addButton&&a.ui.addButton("Source",{label:a.lang.sourcearea.toolbar,command:"source",toolbar:"mode,10"});a.on("mode",function(){a.getCommand("source").setState("source"==a.mode?CKEDITOR.TRISTATE_ON:CKEDITOR.TRISTATE_OFF)});var e=CKEDITOR.env.ie&&9==CKEDITOR.env.version}}});var c=CKEDITOR.tools.createClass({base:CKEDITOR.editable,proto:{setData:function(a){this.setValue(a);this.status="ready";this.editor.fire("dataReady")},getData:function(){return this.getValue()}, insertHtml:function(){},insertElement:function(){},insertText:function(){},setReadOnly:function(a){this[(a?"set":"remove")+"Attribute"]("readOnly","readonly")},detach:function(){c.baseProto.detach.call(this);this.clearCustomData();this.remove()}}})})(); CKEDITOR.plugins.sourcearea={commands:{source:{modes:{wysiwyg:1,source:1},editorFocus:!1,readOnly:1,exec:function(c){"wysiwyg"==c.mode&&c.fire("saveSnapshot");c.getCommand("source").setState(CKEDITOR.TRISTATE_DISABLED);c.setMode("source"==c.mode?"wysiwyg":"source")},canUndo:!1}}};CKEDITOR.plugins.add("specialchar",{availableLangs:{af:1,ar:1,az:1,bg:1,ca:1,cs:1,cy:1,da:1,de:1,"de-ch":1,el:1,en:1,"en-au":1,"en-ca":1,"en-gb":1,eo:1,es:1,"es-mx":1,et:1,eu:1,fa:1,fi:1,fr:1,"fr-ca":1,gl:1,he:1,hr:1,hu:1,id:1,it:1,ja:1,km:1,ko:1,ku:1,lt:1,lv:1,nb:1,nl:1,no:1,oc:1,pl:1,pt:1,"pt-br":1,ro:1,ru:1,si:1,sk:1,sl:1,sq:1,sr:1,"sr-latn":1,sv:1,th:1,tr:1,tt:1,ug:1,uk:1,vi:1,zh:1,"zh-cn":1},requires:"dialog",init:function(a){var c=this;CKEDITOR.dialog.add("specialchar",this.path+"dialogs/specialchar.js"); a.addCommand("specialchar",{exec:function(){var b=a.langCode,b=c.availableLangs[b]?b:c.availableLangs[b.replace(/-.*/,"")]?b.replace(/-.*/,""):"en";CKEDITOR.scriptLoader.load(CKEDITOR.getUrl(c.path+"dialogs/lang/"+b+".js"),function(){CKEDITOR.tools.extend(a.lang.specialchar,c.langEntries[b]);a.openDialog("specialchar")})},modes:{wysiwyg:1},canUndo:!1});a.ui.addButton&&a.ui.addButton("SpecialChar",{label:a.lang.specialchar.toolbar,command:"specialchar",toolbar:"insert,50"})}}); CKEDITOR.config.specialChars="! \x26quot; # $ % \x26amp; ' ( ) * + - . / 0 1 2 3 4 5 6 7 8 9 : ; \x26lt; \x3d \x26gt; ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ \x26euro; \x26lsquo; \x26rsquo; \x26ldquo; \x26rdquo; \x26ndash; \x26mdash; \x26iexcl; \x26cent; \x26pound; \x26curren; \x26yen; \x26brvbar; \x26sect; \x26uml; \x26copy; \x26ordf; \x26laquo; \x26not; \x26reg; \x26macr; \x26deg; \x26sup2; \x26sup3; \x26acute; \x26micro; \x26para; \x26middot; \x26cedil; \x26sup1; \x26ordm; \x26raquo; \x26frac14; \x26frac12; \x26frac34; \x26iquest; \x26Agrave; \x26Aacute; \x26Acirc; \x26Atilde; \x26Auml; \x26Aring; \x26AElig; \x26Ccedil; \x26Egrave; \x26Eacute; \x26Ecirc; \x26Euml; \x26Igrave; \x26Iacute; \x26Icirc; \x26Iuml; \x26ETH; \x26Ntilde; \x26Ograve; \x26Oacute; \x26Ocirc; \x26Otilde; \x26Ouml; \x26times; \x26Oslash; \x26Ugrave; \x26Uacute; \x26Ucirc; \x26Uuml; \x26Yacute; \x26THORN; \x26szlig; \x26agrave; \x26aacute; \x26acirc; \x26atilde; \x26auml; \x26aring; \x26aelig; \x26ccedil; \x26egrave; \x26eacute; \x26ecirc; \x26euml; \x26igrave; \x26iacute; \x26icirc; \x26iuml; \x26eth; \x26ntilde; \x26ograve; \x26oacute; \x26ocirc; \x26otilde; \x26ouml; \x26divide; \x26oslash; \x26ugrave; \x26uacute; \x26ucirc; \x26uuml; \x26yacute; \x26thorn; \x26yuml; \x26OElig; \x26oelig; \x26#372; \x26#374 \x26#373 \x26#375; \x26sbquo; \x26#8219; \x26bdquo; \x26hellip; \x26trade; \x26#9658; \x26bull; \x26rarr; \x26rArr; \x26hArr; \x26diams; \x26asymp;".split(" ");(function(){CKEDITOR.plugins.add("stylescombo",{requires:"richcombo",init:function(c){var l=c.config,g=c.lang.stylescombo,f={},k=[],m=[];c.on("stylesSet",function(a){if(a=a.data.styles){for(var b,h,d,e=0,n=a.length;e=g)for(b=this.getNextSourceNode(k,CKEDITOR.NODE_ELEMENT);b;){if(b.isVisible()&&0===b.getTabIndex()){d=b;break}b=b.getNextSourceNode(!1,CKEDITOR.NODE_ELEMENT)}else for(b=this.getDocument().getBody().getFirst();b=b.getNextSourceNode(!1,CKEDITOR.NODE_ELEMENT);){if(!f)if(!c&&b.equals(this)){if(c=!0,k){if(!(b=b.getNextSourceNode(!0,CKEDITOR.NODE_ELEMENT)))break;f=1}}else c&&!this.contains(b)&& (f=1);if(b.isVisible()&&!(0>(a=b.getTabIndex()))){if(f&&a==g){d=b;break}a>g&&(!d||!e||a(b=a.getTabIndex())))if(0>=g){if(f&&0===b){d=a;break}b>e&&(d=a,e=b)}else{if(f&&b==g){d=a;break}be)&&(d=a,e=b)}}d&&d.focus()};CKEDITOR.plugins.add("table",{requires:"dialog",init:function(a){function f(c){return CKEDITOR.tools.extend(c||{},{contextSensitive:1,refresh:function(c,b){this.setState(b.contains("table",1)?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED)}})}if(!a.blockless){var e=a.lang.table;a.addCommand("table",new CKEDITOR.dialogCommand("table",{context:"table",allowedContent:"table{width,height,border-collapse}[align,border,cellpadding,cellspacing,summary];caption tbody thead tfoot;th td tr[scope];td{border*,background-color,vertical-align,width,height}[colspan,rowspan];"+ (a.plugins.dialogadvtab?"table"+a.plugins.dialogadvtab.allowedContent():""),requiredContent:"table",contentTransformations:[["table{width}: sizeToStyle","table[width]: sizeToAttribute"],["td: splitBorderShorthand"],[{element:"table",right:function(c){if(c.styles){var a;if(c.styles.border)a=CKEDITOR.tools.style.parse.border(c.styles.border);else if(CKEDITOR.env.ie&&8===CKEDITOR.env.version){var b=c.styles;b["border-left"]&&b["border-left"]===b["border-right"]&&b["border-right"]===b["border-top"]&& b["border-top"]===b["border-bottom"]&&(a=CKEDITOR.tools.style.parse.border(b["border-top"]))}a&&a.style&&"solid"===a.style&&a.width&&0!==parseFloat(a.width)&&(c.attributes.border=1);"collapse"==c.styles["border-collapse"]&&(c.attributes.cellspacing=0)}}}]]}));a.addCommand("tableProperties",new CKEDITOR.dialogCommand("tableProperties",f()));a.addCommand("tableDelete",f({exec:function(a){var d=a.elementPath().contains("table",1);if(d){var b=d.getParent(),e=a.editable();1!=b.getChildCount()||b.is("td", "th")||b.equals(e)||(d=b);a=a.createRange();a.moveToPosition(d,CKEDITOR.POSITION_BEFORE_START);d.remove();a.select()}}}));a.ui.addButton&&a.ui.addButton("Table",{label:e.toolbar,command:"table",toolbar:"insert,30"});CKEDITOR.dialog.add("table",this.path+"dialogs/table.js");CKEDITOR.dialog.add("tableProperties",this.path+"dialogs/table.js");a.addMenuItems&&a.addMenuItems({table:{label:e.menu,command:"tableProperties",group:"table",order:5},tabledelete:{label:e.deleteTable,command:"tableDelete",group:"table", order:1}});a.on("doubleclick",function(a){a.data.element.is("table")&&(a.data.dialog="tableProperties")});a.contextMenu&&a.contextMenu.addListener(function(){return{tabledelete:CKEDITOR.TRISTATE_OFF,table:CKEDITOR.TRISTATE_OFF}})}}});(function(){function n(a,b){return CKEDITOR.tools.array.reduce(b,function(a,b){return b(a)},a)}var g=[CKEDITOR.CTRL+90,CKEDITOR.CTRL+89,CKEDITOR.CTRL+CKEDITOR.SHIFT+90],p={8:1,46:1};CKEDITOR.plugins.add("undo",{init:function(a){function b(a){d.enabled&&!1!==a.data.command.canUndo&&d.save()}function c(){d.enabled=a.readOnly?!1:"wysiwyg"==a.mode;d.onChange()}var d=a.undoManager=new e(a),l=d.editingHandler=new k(d),f=a.addCommand("undo",{exec:function(){d.undo()&&(a.selectionChange(),this.fire("afterUndo"))}, startDisabled:!0,canUndo:!1}),h=a.addCommand("redo",{exec:function(){d.redo()&&(a.selectionChange(),this.fire("afterRedo"))},startDisabled:!0,canUndo:!1});a.setKeystroke([[g[0],"undo"],[g[1],"redo"],[g[2],"redo"]]);d.onChange=function(){f.setState(d.undoable()?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED);h.setState(d.redoable()?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED)};a.on("beforeCommandExec",b);a.on("afterCommandExec",b);a.on("saveSnapshot",function(a){d.save(a.data&&a.data.contentOnly)}); a.on("contentDom",l.attachListeners,l);a.on("instanceReady",function(){a.fire("saveSnapshot")});a.on("beforeModeUnload",function(){"wysiwyg"==a.mode&&d.save(!0)});a.on("mode",c);a.on("readOnly",c);a.ui.addButton&&(a.ui.addButton("Undo",{label:a.lang.undo.undo,command:"undo",toolbar:"undo,10"}),a.ui.addButton("Redo",{label:a.lang.undo.redo,command:"redo",toolbar:"undo,20"}));a.resetUndo=function(){d.reset();a.fire("saveSnapshot")};a.on("updateSnapshot",function(){d.currentImage&&d.update()});a.on("lockSnapshot", function(a){a=a.data;d.lock(a&&a.dontUpdate,a&&a.forceUpdate)});a.on("unlockSnapshot",d.unlock,d)}});CKEDITOR.plugins.undo={};var e=CKEDITOR.plugins.undo.UndoManager=function(a){this.strokesRecorded=[0,0];this.locked=null;this.previousKeyGroup=-1;this.limit=a.config.undoStackSize||20;this.strokesLimit=25;this._filterRules=[];this.editor=a;this.reset();CKEDITOR.env.ie&&this.addFilterRule(function(a){return a.replace(/\s+data-cke-expando=".*?"/g,"")})};e.prototype={type:function(a,b){var c=e.getKeyGroup(a), d=this.strokesRecorded[c]+1;b=b||d>=this.strokesLimit;this.typing||(this.hasUndo=this.typing=!0,this.hasRedo=!1,this.onChange());b?(d=0,this.editor.fire("saveSnapshot")):this.editor.fire("change");this.strokesRecorded[c]=d;this.previousKeyGroup=c},keyGroupChanged:function(a){return e.getKeyGroup(a)!=this.previousKeyGroup},reset:function(){this.snapshots=[];this.index=-1;this.currentImage=null;this.hasRedo=this.hasUndo=!1;this.locked=null;this.resetType()},resetType:function(){this.strokesRecorded= [0,0];this.typing=!1;this.previousKeyGroup=-1},refreshState:function(){this.hasUndo=!!this.getNextImage(!0);this.hasRedo=!!this.getNextImage(!1);this.resetType();this.onChange()},save:function(a,b,c){var d=this.editor;if(this.locked||"ready"!=d.status||"wysiwyg"!=d.mode)return!1;var e=d.editable();if(!e||"ready"!=e.status)return!1;e=this.snapshots;b||(b=new f(d));if(!1===b.contents)return!1;if(this.currentImage)if(b.equalsContent(this.currentImage)){if(a||b.equalsSelection(this.currentImage))return!1}else!1!== c&&d.fire("change");e.splice(this.index+1,e.length-this.index-1);e.length==this.limit&&e.shift();this.index=e.push(b)-1;this.currentImage=b;!1!==c&&this.refreshState();return!0},restoreImage:function(a){var b=this.editor,c;a.bookmarks&&(b.focus(),c=b.getSelection());this.locked={level:999};this.editor.loadSnapshot(a.contents);a.bookmarks?c.selectBookmarks(a.bookmarks):CKEDITOR.env.ie&&(c=this.editor.document.getBody().$.createTextRange(),c.collapse(!0),c.select());this.locked=null;this.index=a.index; this.currentImage=this.snapshots[this.index];this.update();this.refreshState();b.fire("change")},getNextImage:function(a){var b=this.snapshots,c=this.currentImage,d;if(c)if(a)for(d=this.index-1;0<=d;d--){if(a=b[d],!c.equalsContent(a))return a.index=d,a}else for(d=this.index+1;d=this.undoManager.strokesLimit&&(this.undoManager.type(a.keyCode,!0),this.keyEventsStack.resetInputs())}},onKeyup:function(a){var b=this.undoManager;a=a.data.getKey();var c=this.keyEventsStack.getTotalInputs();this.keyEventsStack.remove(a);if(!(e.ieFunctionalKeysBug(a)&&this.lastKeydownImage&&this.lastKeydownImage.equalsContent(new f(b.editor, !0))))if(0=this.rect.right||a<=this.rect.top||a>=this.rect.bottom)&&this.hideVisible();(0>=b||b>=this.winTopPane.width||0>=a||a>=this.winTopPane.height)&&this.hideVisible()},this);b.attachListener(a,"resize",c);b.attachListener(a,"mode",g);a.on("destroy",g);this.lineTpl=(new CKEDITOR.template('\x3cdiv data-cke-lineutils-line\x3d"1" class\x3d"cke_reset_all" style\x3d"{lineStyle}"\x3e\x3cspan style\x3d"{tipLeftStyle}"\x3e\x26nbsp;\x3c/span\x3e\x3cspan style\x3d"{tipRightStyle}"\x3e\x26nbsp;\x3c/span\x3e\x3c/div\x3e')).output({lineStyle:CKEDITOR.tools.writeCssText(CKEDITOR.tools.extend({}, t,this.lineStyle,!0)),tipLeftStyle:CKEDITOR.tools.writeCssText(CKEDITOR.tools.extend({},q,{left:"0px","border-left-color":"red","border-width":"6px 0 6px 6px"},this.tipCss,this.tipLeftStyle,!0)),tipRightStyle:CKEDITOR.tools.writeCssText(CKEDITOR.tools.extend({},q,{right:"0px","border-right-color":"red","border-width":"6px 6px 6px 0"},this.tipCss,this.tipRightStyle,!0))})}function l(a){var d;if(d=a&&a.type==CKEDITOR.NODE_ELEMENT)d=!(r[a.getComputedStyle("float")]||r[a.getAttribute("align")]);return d&& !u[a.getComputedStyle("position")]}CKEDITOR.plugins.add("lineutils");CKEDITOR.LINEUTILS_BEFORE=1;CKEDITOR.LINEUTILS_AFTER=2;CKEDITOR.LINEUTILS_INSIDE=4;m.prototype={start:function(a){var d=this,b=this.editor,c=this.doc,e,g,f,h,k=CKEDITOR.tools.eventsBuffer(50,function(){b.readOnly||"wysiwyg"!=b.mode||(d.relations={},(g=c.$.elementFromPoint(f,h))&&g.nodeType&&(e=new CKEDITOR.dom.element(g),d.traverseSearch(e),isNaN(f+h)||d.pixelSearch(e,f,h),a&&a(d.relations,f,h)))});this.listener=this.editable.attachListener(this.target, "mousemove",function(a){f=a.data.$.clientX;h=a.data.$.clientY;k.input()});this.editable.attachListener(this.inline?this.editable:this.frame,"mouseout",function(){k.reset()})},stop:function(){this.listener&&this.listener.removeListener()},getRange:function(){var a={};a[CKEDITOR.LINEUTILS_BEFORE]=CKEDITOR.POSITION_BEFORE_START;a[CKEDITOR.LINEUTILS_AFTER]=CKEDITOR.POSITION_AFTER_END;a[CKEDITOR.LINEUTILS_INSIDE]=CKEDITOR.POSITION_AFTER_START;return function(d){var b=this.editor.createRange();b.moveToPosition(this.relations[d.uid].element, a[d.type]);return b}}(),store:function(){function a(a,b,c){var e=a.getUniqueId();e in c?c[e].type|=b:c[e]={element:a,type:b}}return function(d,b){var c;b&CKEDITOR.LINEUTILS_AFTER&&l(c=d.getNext())&&c.isVisible()&&(a(c,CKEDITOR.LINEUTILS_BEFORE,this.relations),b^=CKEDITOR.LINEUTILS_AFTER);b&CKEDITOR.LINEUTILS_INSIDE&&l(c=d.getFirst())&&c.isVisible()&&(a(c,CKEDITOR.LINEUTILS_BEFORE,this.relations),b^=CKEDITOR.LINEUTILS_INSIDE);a(d,b,this.relations)}}(),traverseSearch:function(a){var d,b,c;do if(c=a.$["data-cke-expando"], !(c&&c in this.relations)){if(a.equals(this.editable))break;if(l(a))for(d in this.lookups)(b=this.lookups[d](a))&&this.store(a,b)}while((!a||a.type!=CKEDITOR.NODE_ELEMENT||"true"!=a.getAttribute("contenteditable"))&&(a=a.getParent()))},pixelSearch:function(){function a(a,c,e,g,f){for(var h=0,k;f(e);){e+=g;if(25==++h)break;if(k=this.doc.$.elementFromPoint(c,e))if(k==a)h=0;else if(d(a,k)&&(h=0,l(k=new CKEDITOR.dom.element(k))))return k}}var d=CKEDITOR.env.ie||CKEDITOR.env.webkit?function(a,c){return a.contains(c)}: function(a,c){return!!(a.compareDocumentPosition(c)&16)};return function(b,c,d){var g=this.win.getViewPaneSize().height,f=a.call(this,b.$,c,d,-1,function(a){return 0this.rect.bottom)return!1;this.inline? e.left=b.elementRect.left-this.rect.relativeX:(0[^<]*e});0>d&&(d=a._.upcasts.length);a._.upcasts.splice(d,0,[CKEDITOR.tools.bind(b,c),c.name,e])}var e=b.upcast,f=b.upcastPriority||10;e&&("string"==typeof e?d(c,b,f):d(e,b,f))}function x(a,b){a.focused=null;if(b.isInited()){var c=b.editor.checkDirty();a.fire("widgetBlurred",{widget:b});b.setFocused(!1);!c&&b.editor.resetDirty()}} function P(a){a=a.data;if("wysiwyg"==this.editor.mode){var b=this.editor.editable(),c=this.instances,d,e,f,g;if(b){for(d in c)c[d].isReady()&&!b.contains(c[d].wrapper)&&this.destroy(c[d],!0);if(a&&a.initOnlyNew)c=this.initOnAll();else{var l=b.find(".cke_widget_wrapper"),c=[];d=0;for(e=l.count();d a.selected.length||F(c,"cut"===b.name)}var c=a.editor;c.on("contentDom",function(){var a=c.editable();a.attachListener(a,"copy",b);a.attachListener(a,"cut",b)})}function Q(a){var b=a.editor;b.on("selectionCheck",function(){a.fire("checkSelection")});a.on("checkSelection",a.checkSelection,a);b.on("selectionChange",function(c){var d=(c=h.getNestedEditable(b.editable(),c.data.selection.getStartElement()))&&a.getByElement(c),e=a.widgetHoldingFocusedEditable;e?e===d&&e.focusedEditable.equals(c)||(p(a, e,null),d&&c&&p(a,d,c)):d&&c&&p(a,d,c)});b.on("dataReady",function(){G(a).commit()});b.on("blur",function(){var b;(b=a.focused)&&x(a,b);(b=a.widgetHoldingFocusedEditable)&&p(a,b,null)})}function O(a){var b=a.editor,c={};b.on("toDataFormat",function(b){var e=CKEDITOR.tools.getNextNumber(),f=[];b.data.downcastingSessionId=e;c[e]=f;b.data.dataValue.forEach(function(b){var c=b.attributes,d;if("data-cke-widget-white-space"in c){d=z(b);var e=A(b);d.parent.attributes["data-cke-white-space-first"]&&(d.value= d.value.replace(/^ /g," "));e.parent.attributes["data-cke-white-space-last"]&&(e.value=e.value.replace(/ $/g," "))}if("data-cke-widget-id"in c){if(c=a.instances[c["data-cke-widget-id"]])d=b.getFirst(h.isParserWidgetElement),f.push({wrapper:b,element:d,widget:c,editables:{}}),"1"!=d.attributes["data-cke-widget-keep-attr"]&&delete d.attributes["data-widget"]}else if("data-cke-widget-editable"in c)return 0CKEDITOR.tools.indexOf(b,a)&&c.push(a);a=CKEDITOR.tools.indexOf(d,a);0<=a&&d.splice(a,1);return this},focus:function(a){e=a;return this}, commit:function(){var f=a.focused!==e,g,l;a.editor.fire("lockSnapshot");for(f&&(g=a.focused)&&x(a,g);g=d.pop();)b.splice(CKEDITOR.tools.indexOf(b,g),1),g.isInited()&&(l=g.editor.checkDirty(),g.setSelected(!1),!l&&g.editor.resetDirty());f&&e&&(l=a.editor.checkDirty(),a.focused=e,a.fire("widgetFocused",{widget:e}),e.setFocused(!0),!l&&a.editor.resetDirty());for(;g=c.pop();)b.push(g),g.setSelected(!0);a.editor.fire("unlockSnapshot")}}}function da(a){a&&a.addFilterRule(function(a){return a.replace(/\s*cke_widget_selected/g, "").replace(/\s*cke_widget_focused/g,"").replace(/]*cke_widget_drag_handler_container[^>]*.*?<\/span>/gmi,"")})}function H(a,b,c){var d=0;b=I(b);var e=a.data.classes||{},f;if(b){for(e=CKEDITOR.tools.clone(e);f=b.pop();)c?e[f]||(d=e[f]=1):e[f]&&(delete e[f],d=1);d&&a.setData("classes",e)}}function J(a){a.cancel()}function F(a,b){function c(){var b=a.getSelectedHtml(!0);if(a.widgets.focused)return a.widgets.focused.getClipboardHtml();a.once("toDataFormat",function(a){a.data.widgetsCopy=!0}, null,null,-1);return a.dataProcessor.toDataFormat(b)}var d=a.widgets.focused,e,f,g;u.hasCopyBin(a)||(f=new u(a,{beforeDestroy:function(){!b&&d&&d.focus();g&&a.getSelection().selectBookmarks(g);e&&CKEDITOR.plugins.widgetselection.addFillers(a.editable())},afterDestroy:function(){b&&!a.readOnly&&(d?a.widgets.del(d):a.extractSelectedHtml(),a.fire("saveSnapshot"))}}),d||(e=CKEDITOR.env.webkit&&CKEDITOR.plugins.widgetselection.isWholeContentSelected(a.editable()),g=a.getSelection().createBookmarks(!0)), f.handle(c()))}function I(a){return(a=(a=a.getDefinition().attributes)&&a["class"])?a.split(/\s+/):null}function K(){var a=CKEDITOR.document.getActive(),b=this.editor,c=b.editable();(c.isInline()?c:b.document.getWindow().getFrame()).equals(a)&&b.focusManager.focus(c)}function L(){CKEDITOR.env.gecko&&this.editor.unlockSelection();CKEDITOR.env.webkit||(this.editor.forceNextSelectionCheck(),this.editor.selectionChange(1))}function V(a,b){ea(a);fa(a);ga(a);ha(a);ia(a);ja(a);ka(a);if(CKEDITOR.env.ie&& 9>CKEDITOR.env.version)a.wrapper.on("dragstart",function(b){var d=b.data.getTarget();h.getNestedEditable(a,d)||a.inline&&h.isDomDragHandler(d)||b.data.preventDefault()});a.wrapper.removeClass("cke_widget_new");a.element.addClass("cke_widget_element");a.on("key",function(b){b=b.data.keyCode;if(13==b)a.edit();else{if(b==CKEDITOR.CTRL+67||b==CKEDITOR.CTRL+88){F(a.editor,b==CKEDITOR.CTRL+88);return}if(b in M||CKEDITOR.CTRL&b||CKEDITOR.ALT&b)return}return!1},null,null,999);a.on("doubleclick",function(b){a.edit()&& b.cancel()});if(b.data)a.on("data",b.data);if(b.edit)a.on("edit",b.edit)}function ea(a){(a.wrapper=a.element.getParent()).setAttribute("data-cke-widget-id",a.id)}function fa(a){if(a.parts){var b={},c,d;for(d in a.parts)c=a.wrapper.findOne(a.parts[d]),b[d]=c;a.parts=b}}function ga(a){var b=a.editables,c,d;a.editables={};if(a.editables)for(c in b)d=b[c],a.initEditable(c,"string"==typeof d?{selector:d}:d)}function ha(a){if(!0===a.mask)la(a);else if(a.mask){var b=new CKEDITOR.tools.buffers.throttle(250, ma,a),c=CKEDITOR.env.gecko?300:0,d,e;a.on("focus",function(){b.input();d=a.editor.on("change",b.input);e=a.on("blur",function(){d.removeListener();e.removeListener()})});a.editor.on("instanceReady",function(){setTimeout(function(){b.input()},c)});a.editor.on("mode",function(){setTimeout(function(){b.input()},c)});if(CKEDITOR.env.gecko){var f=a.element.find("img");CKEDITOR.tools.array.forEach(f.toArray(),function(a){a.on("load",function(){b.input()})})}for(var g in a.editables)a.editables[g].on("focus", function(){a.editor.on("change",b.input);e&&e.removeListener()}),a.editables[g].on("blur",function(){a.editor.removeListener("change",b.input)});b.input()}}function la(a){var b=a.wrapper.findOne(".cke_widget_mask");b||(b=new CKEDITOR.dom.element("img",a.editor.document),b.setAttributes({src:CKEDITOR.tools.transparentImageData,"class":"cke_reset cke_widget_mask"}),a.wrapper.append(b));a.mask=b}function ma(){if(this.wrapper){this.maskPart=this.maskPart||this.mask;var a=this.parts[this.maskPart],b;if(a){b= this.wrapper.findOne(".cke_widget_partial_mask");b||(b=new CKEDITOR.dom.element("img",this.editor.document),b.setAttributes({src:CKEDITOR.tools.transparentImageData,"class":"cke_reset cke_widget_partial_mask"}),this.wrapper.append(b));this.mask=b;var c=b.$,d=a.$,e=!(c.offsetTop==d.offsetTop&&c.offsetLeft==d.offsetLeft);if(c.offsetWidth!=d.offsetWidth||c.offsetHeight!=d.offsetHeight||e)c=a.getParent(),d=CKEDITOR.plugins.widget.isDomWidget(c),b.setStyles({top:a.$.offsetTop+(d?0:c.$.offsetTop)+"px", left:a.$.offsetLeft+(d?0:c.$.offsetLeft)+"px",width:a.$.offsetWidth+"px",height:a.$.offsetHeight+"px"})}}}function ia(a){if(a.draggable){var b=a.editor,c=a.wrapper.getLast(h.isDomDragHandlerContainer),d;c?d=c.findOne("img"):(c=new CKEDITOR.dom.element("span",b.document),c.setAttributes({"class":"cke_reset cke_widget_drag_handler_container",style:"background:rgba(220,220,220,0.5);background-image:url("+b.plugins.widget.path+"images/handle.png);display:none;"}),d=new CKEDITOR.dom.element("img",b.document), d.setAttributes({"class":"cke_reset cke_widget_drag_handler","data-cke-widget-drag-handler":"1",src:CKEDITOR.tools.transparentImageData,width:15,title:b.lang.widget.move,height:15,role:"presentation"}),a.inline&&d.setAttribute("draggable","true"),c.append(d),a.wrapper.append(c));a.wrapper.on("dragover",function(a){a.data.preventDefault()});a.wrapper.on("mouseenter",a.updateDragHandlerPosition,a);setTimeout(function(){a.on("data",a.updateDragHandlerPosition,a)},50);if(!a.inline&&(d.on("mousedown", na,a),CKEDITOR.env.ie&&9>CKEDITOR.env.version))d.on("dragstart",function(a){a.data.preventDefault(!0)});a.dragHandlerContainer=c}}function na(a){function b(){var b;for(r.reset();b=h.pop();)b.removeListener();var c=k;b=a.sender;var d=this.repository.finder,e=this.repository.liner,f=this.editor,g=this.editor.editable();CKEDITOR.tools.isEmpty(e.visible)||(c=d.getRange(c[0]),this.focus(),f.fire("drop",{dropRange:c,target:c.startContainer}));g.removeClass("cke_widget_dragging");e.hideVisible();f.fire("dragend", {target:b})}if(CKEDITOR.tools.getMouseButton(a)===CKEDITOR.MOUSE_BUTTON_LEFT){var c=this.repository.finder,d=this.repository.locator,e=this.repository.liner,f=this.editor,g=f.editable(),h=[],k=[],n,m;this.repository._.draggedWidget=this;var w=c.greedySearch(),r=CKEDITOR.tools.eventsBuffer(50,function(){n=d.locate(w);k=d.sort(m,1);k.length&&(e.prepare(w,n),e.placeLine(k[0]),e.cleanup())});g.addClass("cke_widget_dragging");h.push(g.on("mousemove",function(a){m=a.data.$.clientY;r.input()}));f.fire("dragstart", {target:a.sender});h.push(f.document.once("mouseup",b,this));g.isInline()||h.push(CKEDITOR.document.once("mouseup",b,this))}}function ja(a){var b=null;a.on("data",function(){var a=this.data.classes,d;if(b!=a){for(d in b)a&&a[d]||this.removeClass(d);for(d in a)this.addClass(d);b=a}})}function ka(a){a.on("data",function(){if(a.wrapper){var b=this.getLabel?this.getLabel():this.editor.lang.widget.label.replace(/%1/,this.pathName||this.element.getName());a.wrapper.setAttribute("role","region");a.wrapper.setAttribute("aria-label", b)}},null,null,9999)}function v(a){a.element.data("cke-widget-data",encodeURIComponent(JSON.stringify(a.data)))}function oa(){function a(){}function b(a,b,c){return c&&this.checkElement(a)?(a=c.widgets.getByElement(a,!0))&&a.checkStyleActive(this):!1}function c(a){function b(a,c,d){for(var e=a.length,f=0;f)?(?:<(?:div|span)(?: style="[^"]+")?>)?]*data-cke-copybin-start="1"[^>]*>.?<\/span>([\s\S]+)]*data-cke-copybin-end="1"[^>]*>.?<\/span>(?:<\/(?:div|span)>)?(?:<\/(?:div|span)>)?$/i,M={37:1,38:1,39:1,40:1,8:1,46:1};M[CKEDITOR.SHIFT+121]= 1;var u=CKEDITOR.tools.createClass({$:function(a,b){this._.createCopyBin(a,b);this._.createListeners(b)},_:{createCopyBin:function(a){var b=a.document,c=CKEDITOR.env.edge&&16<=CKEDITOR.env.version,d=!a.blockless&&!CKEDITOR.env.ie||c?"div":"span",c=b.createElement(d),b=b.createElement(d);b.setAttributes({id:"cke_copybin","data-cke-temp":"1"});c.setStyles({position:"absolute",width:"1px",height:"1px",overflow:"hidden"});c.setStyle("ltr"==a.config.contentsLangDirection?"left":"right","-5000px");this.editor= a;this.copyBin=c;this.container=b},createListeners:function(a){a&&(a.beforeDestroy&&(this.beforeDestroy=a.beforeDestroy),a.afterDestroy&&(this.afterDestroy=a.afterDestroy))}},proto:{handle:function(a){var b=this.copyBin,c=this.editor,d=this.container,e=CKEDITOR.env.ie&&9>CKEDITOR.env.version,f=c.document.getDocumentElement().$,g=c.createRange(),h=this,k=CKEDITOR.env.mac&&CKEDITOR.env.webkit,n=k?100:0,m=window.requestAnimationFrame&&!k?requestAnimationFrame:setTimeout,p,r,q;b.setHtml('\x3cspan data-cke-copybin-start\x3d"1"\x3e​\x3c/span\x3e'+ a+'\x3cspan data-cke-copybin-end\x3d"1"\x3e​\x3c/span\x3e');c.fire("lockSnapshot");d.append(b);c.editable().append(d);p=c.on("selectionChange",J,null,null,0);r=c.widgets.on("checkSelection",J,null,null,0);e&&(q=f.scrollTop);g.selectNodeContents(b);g.select();e&&(f.scrollTop=q);return new CKEDITOR.tools.promise(function(a){m(function(){h.beforeDestroy&&h.beforeDestroy();d.remove();p.removeListener();r.removeListener();c.fire("unlockSnapshot");h.afterDestroy&&h.afterDestroy();a()},n)})}},statics:{hasCopyBin:function(a){return!!u.getCopyBin(a)}, getCopyBin:function(a){return a.document.getById("cke_copybin")}}});CKEDITOR.plugins.widget=h;h.repository=q;h.nestedEditable=t})();(function(){function k(a){this.editor=a;this.loaders=[]}function l(a,c,b){var d=a.config.fileTools_defaultFileName;this.editor=a;this.lang=a.lang;"string"===typeof c?(this.data=c,this.file=n(this.data),this.loaded=this.total=this.file.size):(this.data=null,this.file=c,this.total=this.file.size,this.loaded=0);b?this.fileName=b:this.file.name?this.fileName=this.file.name:(a=this.file.type.split("/"),d&&(a[0]=d),this.fileName=a.join("."));this.uploaded=0;this.responseData=this.uploadTotal=null;this.status= "created";this.abort=function(){this.changeStatus("abort")}}function n(a){var c=a.match(m)[1];a=a.replace(m,"");a=atob(a);var b=[],d,f,g,e;for(d=0;dd.status||299=c&&(c="0"+c);return String(c)}function n(c){var a=new Date,a=[a.getFullYear(),a.getMonth()+1,a.getDate(),a.getHours(),a.getMinutes(),a.getSeconds()];e+=1;return"image-"+CKEDITOR.tools.array.map(a,l).join("")+"-"+e+"."+c}var e=0;CKEDITOR.plugins.add("uploadimage",{requires:"uploadwidget",onLoad:function(){CKEDITOR.addCss(".cke_upload_uploading img{opacity: 0.3}")},isSupportedEnvironment:function(){return CKEDITOR.plugins.clipboard.isFileApiSupported},init:function(c){if(this.isSupportedEnvironment()){var a= CKEDITOR.fileTools,e=a.getUploadUrl(c.config,"image");e&&(a.addUploadWidget(c,"uploadimage",{supportedTypes:/image\/(jpeg|png|gif|bmp)/,uploadUrl:e,fileToElement:function(){var b=new CKEDITOR.dom.element("img");b.setAttribute("src","data:image/gif;base64,R0lGODlhDgAOAIAAAAAAAP///yH5BAAAAAAALAAAAAAOAA4AAAIMhI+py+0Po5y02qsKADs\x3d");return b},parts:{img:"img"},onUploading:function(b){this.parts.img.setAttribute("src",b.data)},onUploaded:function(b){var a=this.parts.img.$;this.replaceWith('\x3cimg src\x3d"'+ b.url+'" width\x3d"'+(b.responseData.width||a.naturalWidth)+'" height\x3d"'+(b.responseData.height||a.naturalHeight)+'"\x3e')}}),c.on("paste",function(b){if(b.data.dataValue.match(/=a.length)return!0;a=a.replace(/[\n|\t]*/g,"").toLowerCase();return!a||"
"==a||"

 

"==a||"


"==a||"

 

"==a||" "==a||" "==a||" 
"==a||"
"==a?!0:!1}function l(a){var b=a.editor,c=b.editable?b.editable():"wysiwyg"==b.mode?b.document&&b.document.getBody():b.textarea,i=a.listenerData;if(c){if("wysiwyg"==b.mode){if(CKEDITOR.dialog._.currentTop||!c)return;h(c.getHtml())&&(c.setHtml(i),c.addClass("placeholder"))}"source"== b.mode&&(m?"mode"==a.name&&c.setAttribute("placeholder",i):h(c.getValue())&&(c.setValue(i),c.addClass("placeholder")))}}function n(a){var a=a.editor,b=a.editable?a.editable():"wysiwyg"==a.mode?a.document&&a.document.getBody():a.textarea;if(b){if("wysiwyg"==a.mode){if(!b.hasClass("placeholder"))return;b.removeClass("placeholder");if(CKEDITOR.dtd[b.getName()].p){b.setHtml("

");var c=new CKEDITOR.dom.range(a.document);c.moveToElementEditablePosition(b.getFirst(),!0);a.getSelection().selectRanges([c])}else b.setHtml(" ")}"source"== a.mode&&b.hasClass("placeholder")&&(b.removeClass("placeholder"),b.setValue(""))}}function o(a){return!a?null:a.getAttribute("lang")||o(a.getParent())}var m="placeholder"in document.createElement("textarea");CKEDITOR.plugins.add("ccmsconfighelper",{getPlaceholderCss:function(){return".placeholder{ color: #999; }"},onLoad:function(){CKEDITOR.addCss&&CKEDITOR.addCss(this.getPlaceholderCss())},init:function(a){a.on("mode",function(a){a.editor.focusManager.hasFocus=!1});var b=a.element.getAttribute("placeholder")|| a.config.placeholder;if(b){a.addCss&&a.addCss(this.getPlaceholderCss());var c=CKEDITOR.document.getHead().append("style");c.setAttribute("type","text/css");CKEDITOR.env.ie&&11>CKEDITOR.env.version?c.$.styleSheet.cssText="textarea.placeholder { color: #999; font-style: italic; }":c.$.innerHTML="textarea.placeholder { color: #999; font-style: italic; }";a.on("getData",function(b){var j=a.editable?a.editable():"wysiwyg"==a.mode?a.document&&a.document.getBody():a.textarea;j&&j.hasClass("placeholder")&& (b.data.dataValue="")});a.on("setData",function(b){if(!CKEDITOR.dialog._.currentTop&&!("source"==a.mode&&m)){var j=a.editable?a.editable():"wysiwyg"==a.mode?a.document&&a.document.getBody():a.textarea;j&&(h(b.data.dataValue)?l(b):j.hasClass("placeholder")&&j.removeClass("placeholder"))}});a.on("blur",l,null,b);a.on("mode",l,null,b);a.on("focus",n);a.on("beforeModeUnload",n)}if((b=a.config.contentsLanguage||o(a.element))&&!a.config.scayt_sLang)a.config.scayt_sLang={en:"en_US","en-us":"en_US","en-gb":"en_GB", "pt-br":"pt_BR",da:"da_DK","da-dk":"da_DK","nl-nl":"nl_NL","en-ca":"en_CA","fi-fi":"fi_FI",fr:"fr_FR","fr-fr":"fr_FR","fr-ca":"fr_CA",de:"de_DE","de-de":"de_DE","el-gr":"el_GR",it:"it_IT","it-it":"it_IT","nb-no":"nb_NO",pt:"pt_PT","pt-pt":"pt_PT",es:"es_ES","es-es":"es_ES","sv-se":"sv_SE"}[b.toLowerCase()];var i=function(a){if("object"==typeof a)return a;var a=a.split(";"),b={},c;for(c=0;cCKEDITOR.env.version?d.$.styleSheet.cssText="textarea.placeholder { color: #999; font-style: italic; }":d.$.innerHTML="textarea.placeholder { color: #999; font-style: italic; }";a.on("getData",function(b){var c=a.editable();c&&c.hasClass("placeholder")&&(b.data.dataValue="")});a.on("setData",function(b){if(!(CKEDITOR.dialog._.currentTop||"source"==a.mode&&n)){var c=a.editable(); c&&(m(b.data.dataValue)?h(b):c.hasClass("placeholder")&&c.removeClass("placeholder"))}});a.on("blur",h,null,b);a.on("mode",h,null,b);a.on("contentDom",h,null,b);a.on("focus",l);a.on("key",l);a.on("beforeModeUnload",l);a.on("readOnly",r,null,b)}if((b=a.config.contentsLanguage||p(a.element))&&a.plugins.scayt&&!a.config.scayt_sLang){try{localStorage&&localStorage.removeItem("scayt_0_lang")}catch(t){}a.config.scayt_sLang={en:"en_US","en-us":"en_US","en-gb":"en_GB","pt-br":"pt_BR",da:"da_DK","da-dk":"da_DK", "nl-nl":"nl_NL","en-ca":"en_CA","fi-fi":"fi_FI",fr:"fr_FR","fr-fr":"fr_FR","fr-ca":"fr_CA",de:"de_DE","de-de":"de_DE","el-gr":"el_GR",it:"it_IT","it-it":"it_IT","nb-no":"nb_NO",pt:"pt_PT","pt-pt":"pt_PT",es:"es_ES","es-es":"es_ES","sv-se":"sv_SE"}[b.toLowerCase()]}var q=function(a){if("object"==typeof a)return a;a=a.split(";");var b={},c;for(c=0;cCKEDITOR.env.version&&b.enterMode!=CKEDITOR.ENTER_DIV&&e("div");if(CKEDITOR.env.webkit||CKEDITOR.env.ie&&10this.$.offsetHeight){var d=b.createRange();d[33==c?"moveToElementEditStart":"moveToElementEditEnd"](this);d.select();a.data.preventDefault()}});CKEDITOR.env.ie&&this.attachListener(c,"blur",function(){try{c.$.selection.empty()}catch(a){}});CKEDITOR.env.iOS&&this.attachListener(c,"touchend",function(){a.focus()});d=b.document.getElementsByTag("title").getItem(0); d.data("cke-title",d.getText());CKEDITOR.env.ie&&(b.document.$.title=this._.docTitle);CKEDITOR.tools.setTimeout(function(){"unloaded"==this.status&&(this.status="ready");b.fire("contentDom");this._.isPendingFocus&&(b.focus(),this._.isPendingFocus=!1);setTimeout(function(){b.fire("dataReady")},0)},0,this)}}function n(a){function e(){var c;a.editable().attachListener(a,"selectionChange",function(){var d=a.getSelection().getSelectedElement();d&&(c&&(c.detachEvent("onresizestart",b),c=null),d.$.attachEvent("onresizestart", b),c=d.$)})}function b(a){a.returnValue=!1}if(CKEDITOR.env.gecko)try{var c=a.document.$;c.execCommand("enableObjectResizing",!1,!a.config.disableObjectResizing);c.execCommand("enableInlineTableEditing",!1,!a.config.disableNativeTableHandles)}catch(d){}else CKEDITOR.env.ie&&11>CKEDITOR.env.version&&a.config.disableObjectResizing&&e(a)}function p(){var a=[];if(8<=CKEDITOR.document.$.documentMode){a.push("html.CSS1Compat [contenteditable\x3dfalse]{min-height:0 !important}");var e=[],b;for(b in CKEDITOR.dtd.$removeEmpty)e.push("html.CSS1Compat "+ b+"[contenteditable\x3dfalse]");a.push(e.join(",")+"{display:inline-block}")}else CKEDITOR.env.gecko&&(a.push("html{height:100% !important}"),a.push("img:-moz-broken{-moz-force-broken-image-icon:1;min-width:24px;min-height:24px}"));a.push("html{cursor:text;*cursor:auto}");a.push("img,input,textarea{cursor:default}");return a.join("\n")}var l;CKEDITOR.plugins.add("wysiwygarea",{init:function(a){a.config.fullPage&&a.addFeature({allowedContent:"html head title; style [media,type]; body (*)[id]; meta link [*]", requiredContent:"body"});a.addMode("wysiwyg",function(e){function b(b){b&&b.removeListener();a.isDestroyed()||a.isDetached()||(a.editable(new l(a,d.$.contentWindow.document.body)),a.setData(a.getData(1),e))}var c="document.open();"+(CKEDITOR.env.ie?"("+CKEDITOR.tools.fixDomain+")();":"")+"document.close();",c=CKEDITOR.env.air?"javascript:void(0)":CKEDITOR.env.ie&&!CKEDITOR.env.edge?"javascript:void(function(){"+encodeURIComponent(c)+"}())":"",d=CKEDITOR.dom.element.createFromHtml('\x3ciframe src\x3d"'+ c+'" frameBorder\x3d"0"\x3e\x3c/iframe\x3e');d.setStyles({width:"100%",height:"100%"});d.addClass("cke_wysiwyg_frame").addClass("cke_reset");c=a.ui.space("contents");c.append(d);var f=CKEDITOR.env.ie&&!CKEDITOR.env.edge||CKEDITOR.env.gecko;if(f)d.on("load",b);var g=a.title,h=a.fire("ariaEditorHelpLabel",{}).label;g&&(CKEDITOR.env.ie&&h&&(g+=", "+h),d.setAttribute("title",g));if(h){var g=CKEDITOR.tools.getNextId(),k=CKEDITOR.dom.element.createFromHtml('\x3cspan id\x3d"'+g+'" class\x3d"cke_voice_label"\x3e'+ h+"\x3c/span\x3e");c.append(k,1);d.setAttribute("aria-describedby",g)}a.on("beforeModeUnload",function(a){a.removeListener();k&&k.remove()});d.setAttributes({tabIndex:a.tabIndex,allowTransparency:"true"});!f&&b();a.fire("ariaWidget",d)})}});CKEDITOR.editor.prototype.addContentsCss=function(a){var e=this.config,b=e.contentsCss;CKEDITOR.tools.isArray(b)||(e.contentsCss=b?[b]:[]);e.contentsCss.push(a)};l=CKEDITOR.tools.createClass({$:function(){this.base.apply(this,arguments);this._.frameLoadedHandler= CKEDITOR.tools.addFunction(function(a){CKEDITOR.tools.setTimeout(m,0,this,a)},this);this._.docTitle=this.getWindow().getFrame().getAttribute("title")},base:CKEDITOR.editable,proto:{setData:function(a,e){var b=this.editor;if(e)this.setHtml(a),this.fixInitialSelection(),b.fire("dataReady");else{this._.isLoadingData=!0;b._.dataStore={id:1};var c=b.config,d=c.fullPage,f=c.docType,g=CKEDITOR.tools.buildStyleHtml(p()).replace(/").appendTo(o)),a.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",a.opacity)),a.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",a.zIndex)),this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this._addClass(this.helper,"ui-sortable-helper"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,n,o,a=this.options,r=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY=0;i--)if(s=this.items[i],n=s.item[0],o=this._intersectsWithPointer(s),o&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===o?"next":"prev"]()[0]!==n&&!t.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!t.contains(this.element[0],n):!0)){if(this.direction=1===o?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger("change",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,n=this.placeholder.offset(),o=this.options.axis,a={};o&&"x"!==o||(a.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollLeft)),o&&"y"!==o||(a.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(a,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp(new t.Event("mouseup",{target:null})),"original"===this.options.helper?(this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("deactivate",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||"id")||"").match(e.expression||/(.+)[\-=_](.+)/);i&&s.push((e.key||i[1]+"[]")+"="+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+"="),s.join("&")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||"id")||"")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,o=t.left,a=o+t.width,r=t.top,h=r+t.height,l=this.offset.click.top,c=this.offset.click.left,u="x"===this.options.axis||s+l>r&&h>s+l,d="y"===this.options.axis||e+c>o&&a>e+c,p=u&&d;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>t[this.floating?"width":"height"]?p:e+this.helperProportions.width/2>o&&a>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&h>n-this.helperProportions.height/2},_intersectsWithPointer:function(t){var e,i,s="x"===this.options.axis||this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top,t.height),n="y"===this.options.axis||this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left,t.width),o=s&&n;return o?(e=this._getDragVerticalDirection(),i=this._getDragHorizontalDirection(),this.floating?"right"===i||"down"===e?2:1:e&&("down"===e?2:1)):!1},_intersectsWithSides:function(t){var e=this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),i=this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),s=this._getDragVerticalDirection(),n=this._getDragHorizontalDirection();return this.floating&&n?"right"===n&&i||"left"===n&&!i:s&&("down"===s&&e||"up"===s&&!e)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?"down":"up")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?"right":"left")},refresh:function(t){return this._refreshItems(t),this._setHandleClassName(),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){function i(){r.push(this)}var s,n,o,a,r=[],h=[],l=this._connectWith();if(l&&e)for(s=l.length-1;s>=0;s--)for(o=t(l[s],this.document[0]),n=o.length-1;n>=0;n--)a=t.data(o[n],this.widgetFullName),a&&a!==this&&!a.options.disabled&&h.push([t.isFunction(a.options.items)?a.options.items.call(a.element):t(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a]);for(h.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=h.length-1;s>=0;s--)h[s][0].each(i);return t(r)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)"); this.items=t.grep(this.items,function(t){for(var i=0;e.length>i;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,n,o,a,r,h,l,c=this.items,u=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(n=t(d[i],this.document[0]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&(u.push([t.isFunction(o.options.items)?o.options.items.call(o.element[0],e,{item:this.currentItem}):t(o.options.items,o.element),o]),this.containers.push(o));for(i=u.length-1;i>=0;i--)for(a=u[i][1],r=u[i][0],s=0,l=r.length;l>s;s++)h=t(r[s]),h.data(this.widgetName+"-item",a),c.push({item:h,instance:a,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.floating=this.items.length?"x"===this.options.axis||this._isFloating(this.items[0].item):!1,this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,o;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=n.outerWidth(),s.height=n.outerHeight()),o=n.offset(),s.left=o.left,s.top=o.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)o=this.containers[i].element.offset(),this.containers[i].containerCache.left=o.left,this.containers[i].containerCache.top=o.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),n=t("<"+s+">",e.document[0]);return e._addClass(n,"ui-sortable-placeholder",i||e.currentItem[0].className)._removeClass(n,"ui-sortable-helper"),"tbody"===s?e._createTrPlaceholder(e.currentItem.find("tr").eq(0),t("",e.document[0]).appendTo(n)):"tr"===s?e._createTrPlaceholder(e.currentItem,n):"img"===s&&n.attr("src",e.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(t,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_createTrPlaceholder:function(e,i){var s=this;e.children().each(function(){t(" ",s.document[0]).attr("colspan",t(this).attr("colspan")||1).appendTo(i)})},_contactContainers:function(e){var i,s,n,o,a,r,h,l,c,u,d=null,p=null;for(i=this.containers.length-1;i>=0;i--)if(!t.contains(this.currentItem[0],this.containers[i].element[0]))if(this._intersectsWith(this.containers[i].containerCache)){if(d&&t.contains(this.containers[i].element[0],d.element[0]))continue;d=this.containers[i],p=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",e,this._uiHash(this)),this.containers[i].containerCache.over=0);if(d)if(1===this.containers.length)this.containers[p].containerCache.over||(this.containers[p]._trigger("over",e,this._uiHash(this)),this.containers[p].containerCache.over=1);else{for(n=1e4,o=null,c=d.floating||this._isFloating(this.currentItem),a=c?"left":"top",r=c?"width":"height",u=c?"pageX":"pageY",s=this.items.length-1;s>=0;s--)t.contains(this.containers[p].element[0],this.items[s].item[0])&&this.items[s].item[0]!==this.currentItem[0]&&(h=this.items[s].item.offset()[a],l=!1,e[u]-h>this.items[s][r]/2&&(l=!0),n>Math.abs(e[u]-h)&&(n=Math.abs(e[u]-h),o=this.items[s],this.direction=l?"up":"down"));if(!o&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[p])return this.currentContainer.containerCache.over||(this.containers[p]._trigger("over",e,this._uiHash()),this.currentContainer.containerCache.over=1),void 0;o?this._rearrange(e,o,null,!0):this._rearrange(e,null,this.containers[p].element,!0),this._trigger("change",e,this._uiHash()),this.containers[p]._trigger("change",e,this._uiHash(this)),this.currentContainer=this.containers[p],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[p]._trigger("over",e,this._uiHash(this)),this.containers[p].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||t("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===this.document[0].body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,"document"===n.containment?this.document.width():this.window.width()-this.helperProportions.width-this.margins.left,("document"===n.containment?this.document.height()||document.body.parentNode.scrollHeight:this.window.height()||this.document[0].body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(e=t(n.containment)[0],i=t(n.containment).offset(),s="hidden"!==t(e).css("overflow"),this.containment=[i.left+(parseInt(t(e).css("borderLeftWidth"),10)||0)+(parseInt(t(e).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css("borderTopWidth"),10)||0)+(parseInt(t(e).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css("borderLeftWidth"),10)||0)-(parseInt(t(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css("borderTopWidth"),10)||0)-(parseInt(t(e).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s}},_generatePosition:function(e){var i,s,n=this.options,o=e.pageX,a=e.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.leftthis.containment[2]&&(o=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(a=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((a-this.originalPageY)/n.grid[1])*n.grid[1],a=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((o-this.originalPageX)/n.grid[0])*n.grid[0],o=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:r.scrollTop()),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:r.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){function i(t,e,i){return function(s){i._trigger(t,s,e._uiHash(e))}}this.reverting=!1;var s,n=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)("auto"===this._storedCSS[s]||"static"===this._storedCSS[s])&&(this._storedCSS[s]="");this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&n.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||n.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(n.push(function(t){this._trigger("remove",t,this._uiHash())}),n.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),n.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)e||n.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(n.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.cancelHelperRemoval||(this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null),!e){for(s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!this.cancelHelperRemoval},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}}),t.ui.safeActiveElement=function(t){var e;try{e=t.activeElement}catch(i){e=t.body}return e||(e=t.body),e.nodeName||(e=t.body),e},t.widget("ui.menu",{version:"1.12.1",defaultElement:"
    ",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault()},"click .ui-menu-item":function(e){var i=t(e.target),s=t(t.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&s.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){if(!this.previousFilter){var i=t(e.target).closest(".ui-menu-item"),s=t(e.currentTarget);i[0]===s[0]&&(this._removeClass(s.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(e,s))}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.find(this.options.items).eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){var i=!t.contains(this.element[0],t.ui.safeActiveElement(this.document[0]));i&&this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t),this.mouseHandled=!1}})},_destroy:function(){var e=this.element.find(".ui-menu-item").removeAttr("role aria-disabled"),i=e.children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),i.children().each(function(){var e=t(this);e.data("ui-menu-submenu-caret")&&e.remove()})},_keydown:function(e){var i,s,n,o,a=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:a=!1,s=this.previousFilter||"",o=!1,n=e.keyCode>=96&&105>=e.keyCode?""+(e.keyCode-96):String.fromCharCode(e.keyCode),clearTimeout(this.filterTimer),n===s?o=!0:n=s+n,i=this._filterMenuItems(n),i=o&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(e.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(e,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}a&&e.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i,s,n,o,a=this,r=this.options.icons.submenu,h=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),s=h.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),i=e.prev(),s=t("").data("ui-menu-submenu-caret",!0);a._addClass(s,"ui-menu-icon","ui-icon "+r),i.attr("aria-haspopup","true").prepend(s),e.attr("aria-labelledby",i.attr("id"))}),this._addClass(s,"ui-menu","ui-widget ui-widget-content ui-front"),e=h.add(this.element),i=e.find(this.options.items),i.not(".ui-menu-item").each(function(){var e=t(this);a._isDivider(e)&&a._addClass(e,"ui-menu-divider","ui-widget-content")}),n=i.not(".ui-menu-item, .ui-menu-divider"),o=n.children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(n,"ui-menu-item")._addClass(o,"ui-menu-item-wrapper"),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){if("icons"===t){var i=this.element.find(".ui-menu-icon");this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)}this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t+""),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i,s,n;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children(".ui-menu-item-wrapper"),this._addClass(s,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),n=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(n,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,o,a,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,o=this.activeMenu.scrollTop(),a=this.activeMenu.height(),r=e.outerHeight(),0>n?this.activeMenu.scrollTop(o+n):n+r>a&&this.activeMenu.scrollTop(o+n-a+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this._removeClass(this.active.children(".ui-menu-item-wrapper"),null,"ui-state-active"),this._trigger("blur",t,{item:this.active}),this.active=null)},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this._removeClass(s.find(".ui-state-active"),null,"ui-state-active"),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false")},_closeOnDocumentClick:function(e){return!t(e.target).closest(".ui-menu").length},_isDivider:function(t){return!/[^\-\u2014\u2013\s]/.test(t.text())},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(e),void 0)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items).first())),void 0):(this.next(e),void 0)},_hasScroll:function(){return this.element.outerHeight()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var e,i,s,n=this.element[0].nodeName.toLowerCase(),o="textarea"===n,a="input"===n;this.isMultiLine=o||!a&&this._isContentEditable(this.element),this.valueMethod=this.element[o||a?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return e=!0,s=!0,i=!0,void 0;e=!1,s=!1,i=!1;var o=t.ui.keyCode;switch(n.keyCode){case o.PAGE_UP:e=!0,this._move("previousPage",n);break;case o.PAGE_DOWN:e=!0,this._move("nextPage",n);break;case o.UP:e=!0,this._keyEvent("previous",n);break;case o.DOWN:e=!0,this._keyEvent("next",n);break;case o.ENTER:this.menu.active&&(e=!0,n.preventDefault(),this.menu.select(n));break;case o.TAB:this.menu.active&&this.menu.select(n);break;case o.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(e)return e=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=t.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(t){return s?(s=!1,t.preventDefault(),void 0):(this._searchTimeout(t),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(t),this._change(t),void 0)}}),this._initSource(),this.menu=t("
      ").appendTo(this._appendTo()).menu({role:null}).hide().menu("instance"),this._addClass(this.menu.element,"ui-autocomplete","ui-front"),this._on(this.menu.element,{mousedown:function(e){e.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,this.element[0]!==t.ui.safeActiveElement(this.document[0])&&this.element.trigger("focus")})},menufocus:function(e,i){var s,n;return this.isNewMenu&&(this.isNewMenu=!1,e.originalEvent&&/^mouse/.test(e.originalEvent.type))?(this.menu.blur(),this.document.one("mousemove",function(){t(e.target).trigger(e.originalEvent)}),void 0):(n=i.item.data("ui-autocomplete-item"),!1!==this._trigger("focus",e,{item:n})&&e.originalEvent&&/^key/.test(e.originalEvent.type)&&this._value(n.value),s=i.item.attr("aria-label")||n.value,s&&t.trim(s).length&&(this.liveRegion.children().hide(),t("
      ").text(s).appendTo(this.liveRegion)),void 0)},menuselect:function(e,i){var s=i.item.data("ui-autocomplete-item"),n=this.previous;this.element[0]!==t.ui.safeActiveElement(this.document[0])&&(this.element.trigger("focus"),this.previous=n,this._delay(function(){this.previous=n,this.selectedItem=s})),!1!==this._trigger("select",e,{item:s})&&this._value(s.value),this.term=this._value(),this.close(e),this.selectedItem=s}}),this.liveRegion=t("
      ",{role:"status","aria-live":"assertive","aria-relevant":"additions"}).appendTo(this.document[0].body),this._addClass(this.liveRegion,null,"ui-helper-hidden-accessible"),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(t,e){this._super(t,e),"source"===t&&this._initSource(),"appendTo"===t&&this.menu.element.appendTo(this._appendTo()),"disabled"===t&&e&&this.xhr&&this.xhr.abort()},_isEventTargetInWidget:function(e){var i=this.menu.element[0];return e.target===this.element[0]||e.target===i||t.contains(i,e.target)},_closeOnClickOutside:function(t){this._isEventTargetInWidget(t)||this.close()},_appendTo:function(){var e=this.options.appendTo;return e&&(e=e.jquery||e.nodeType?t(e):this.document.find(e).eq(0)),e&&e[0]||(e=this.element.closest(".ui-front, dialog")),e.length||(e=this.document[0].body),e},_initSource:function(){var e,i,s=this;t.isArray(this.options.source)?(e=this.options.source,this.source=function(i,s){s(t.ui.autocomplete.filter(e,i.term))}):"string"==typeof this.options.source?(i=this.options.source,this.source=function(e,n){s.xhr&&s.xhr.abort(),s.xhr=t.ajax({url:i,data:e,dataType:"json",success:function(t){n(t)},error:function(){n([])}})}):this.source=this.options.source},_searchTimeout:function(t){clearTimeout(this.searching),this.searching=this._delay(function(){var e=this.term===this._value(),i=this.menu.element.is(":visible"),s=t.altKey||t.ctrlKey||t.metaKey||t.shiftKey;(!e||e&&!i&&!s)&&(this.selectedItem=null,this.search(null,t))},this.options.delay)},search:function(t,e){return t=null!=t?t:this._value(),this.term=this._value(),t.length").append(t("
      ").text(i.label)).appendTo(e)},_move:function(t,e){return this.menu.element.is(":visible")?this.menu.isFirstItem()&&/^previous/.test(t)||this.menu.isLastItem()&&/^next/.test(t)?(this.isMultiLine||this._value(this.term),this.menu.blur(),void 0):(this.menu[t](e),void 0):(this.search(null,e),void 0)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(t,e){(!this.isMultiLine||this.menu.element.is(":visible"))&&(this._move(t,e),e.preventDefault())},_isContentEditable:function(t){if(!t.length)return!1;var e=t.prop("contentEditable");return"inherit"===e?this._isContentEditable(t.parent()):"true"===e}}),t.extend(t.ui.autocomplete,{escapeRegex:function(t){return t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(e,i){var s=RegExp(t.ui.autocomplete.escapeRegex(i),"i");return t.grep(e,function(t){return s.test(t.label||t.value||t)})}}),t.widget("ui.autocomplete",t.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(t){return t+(t>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(e){var i;this._superApply(arguments),this.options.disabled||this.cancelSearch||(i=e&&e.length?this.options.messages.results(e.length):this.options.messages.noResults,this.liveRegion.children().hide(),t("
      ").text(i).appendTo(this.liveRegion))}}),t.ui.autocomplete,t.extend(t.ui,{datepicker:{version:"1.12.1"}});var l;t.extend(i.prototype,{markerClassName:"hasDatepicker",maxRows:4,_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(t){return o(this._defaults,t||{}),this},_attachDatepicker:function(e,i){var s,n,o;s=e.nodeName.toLowerCase(),n="div"===s||"span"===s,e.id||(this.uuid+=1,e.id="dp"+this.uuid),o=this._newInst(t(e),n),o.settings=t.extend({},i||{}),"input"===s?this._connectDatepicker(e,o):n&&this._inlineDatepicker(e,o)},_newInst:function(e,i){var n=e[0].id.replace(/([^A-Za-z0-9_\-])/g,"\\\\$1");return{id:n,input:e,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:i,dpDiv:i?s(t("
      ")):this.dpDiv} },_connectDatepicker:function(e,i){var s=t(e);i.append=t([]),i.trigger=t([]),s.hasClass(this.markerClassName)||(this._attachments(s,i),s.addClass(this.markerClassName).on("keydown",this._doKeyDown).on("keypress",this._doKeyPress).on("keyup",this._doKeyUp),this._autoSize(i),t.data(e,"datepicker",i),i.settings.disabled&&this._disableDatepicker(e))},_attachments:function(e,i){var s,n,o,a=this._get(i,"appendText"),r=this._get(i,"isRTL");i.append&&i.append.remove(),a&&(i.append=t(""+a+""),e[r?"before":"after"](i.append)),e.off("focus",this._showDatepicker),i.trigger&&i.trigger.remove(),s=this._get(i,"showOn"),("focus"===s||"both"===s)&&e.on("focus",this._showDatepicker),("button"===s||"both"===s)&&(n=this._get(i,"buttonText"),o=this._get(i,"buttonImage"),i.trigger=t(this._get(i,"buttonImageOnly")?t("").addClass(this._triggerClass).attr({src:o,alt:n,title:n}):t("").addClass(this._triggerClass).html(o?t("").attr({src:o,alt:n,title:n}):n)),e[r?"before":"after"](i.trigger),i.trigger.on("click",function(){return t.datepicker._datepickerShowing&&t.datepicker._lastInput===e[0]?t.datepicker._hideDatepicker():t.datepicker._datepickerShowing&&t.datepicker._lastInput!==e[0]?(t.datepicker._hideDatepicker(),t.datepicker._showDatepicker(e[0])):t.datepicker._showDatepicker(e[0]),!1}))},_autoSize:function(t){if(this._get(t,"autoSize")&&!t.inline){var e,i,s,n,o=new Date(2009,11,20),a=this._get(t,"dateFormat");a.match(/[DM]/)&&(e=function(t){for(i=0,s=0,n=0;t.length>n;n++)t[n].length>i&&(i=t[n].length,s=n);return s},o.setMonth(e(this._get(t,a.match(/MM/)?"monthNames":"monthNamesShort"))),o.setDate(e(this._get(t,a.match(/DD/)?"dayNames":"dayNamesShort"))+20-o.getDay())),t.input.attr("size",this._formatDate(t,o).length)}},_inlineDatepicker:function(e,i){var s=t(e);s.hasClass(this.markerClassName)||(s.addClass(this.markerClassName).append(i.dpDiv),t.data(e,"datepicker",i),this._setDate(i,this._getDefaultDate(i),!0),this._updateDatepicker(i),this._updateAlternate(i),i.settings.disabled&&this._disableDatepicker(e),i.dpDiv.css("display","block"))},_dialogDatepicker:function(e,i,s,n,a){var r,h,l,c,u,d=this._dialogInst;return d||(this.uuid+=1,r="dp"+this.uuid,this._dialogInput=t(""),this._dialogInput.on("keydown",this._doKeyDown),t("body").append(this._dialogInput),d=this._dialogInst=this._newInst(this._dialogInput,!1),d.settings={},t.data(this._dialogInput[0],"datepicker",d)),o(d.settings,n||{}),i=i&&i.constructor===Date?this._formatDate(d,i):i,this._dialogInput.val(i),this._pos=a?a.length?a:[a.pageX,a.pageY]:null,this._pos||(h=document.documentElement.clientWidth,l=document.documentElement.clientHeight,c=document.documentElement.scrollLeft||document.body.scrollLeft,u=document.documentElement.scrollTop||document.body.scrollTop,this._pos=[h/2-100+c,l/2-150+u]),this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),d.settings.onSelect=s,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),t.blockUI&&t.blockUI(this.dpDiv),t.data(this._dialogInput[0],"datepicker",d),this},_destroyDatepicker:function(e){var i,s=t(e),n=t.data(e,"datepicker");s.hasClass(this.markerClassName)&&(i=e.nodeName.toLowerCase(),t.removeData(e,"datepicker"),"input"===i?(n.append.remove(),n.trigger.remove(),s.removeClass(this.markerClassName).off("focus",this._showDatepicker).off("keydown",this._doKeyDown).off("keypress",this._doKeyPress).off("keyup",this._doKeyUp)):("div"===i||"span"===i)&&s.removeClass(this.markerClassName).empty(),l===n&&(l=null))},_enableDatepicker:function(e){var i,s,n=t(e),o=t.data(e,"datepicker");n.hasClass(this.markerClassName)&&(i=e.nodeName.toLowerCase(),"input"===i?(e.disabled=!1,o.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().removeClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!1)),this._disabledInputs=t.map(this._disabledInputs,function(t){return t===e?null:t}))},_disableDatepicker:function(e){var i,s,n=t(e),o=t.data(e,"datepicker");n.hasClass(this.markerClassName)&&(i=e.nodeName.toLowerCase(),"input"===i?(e.disabled=!0,o.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().addClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!0)),this._disabledInputs=t.map(this._disabledInputs,function(t){return t===e?null:t}),this._disabledInputs[this._disabledInputs.length]=e)},_isDisabledDatepicker:function(t){if(!t)return!1;for(var e=0;this._disabledInputs.length>e;e++)if(this._disabledInputs[e]===t)return!0;return!1},_getInst:function(e){try{return t.data(e,"datepicker")}catch(i){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(e,i,s){var n,a,r,h,l=this._getInst(e);return 2===arguments.length&&"string"==typeof i?"defaults"===i?t.extend({},t.datepicker._defaults):l?"all"===i?t.extend({},l.settings):this._get(l,i):null:(n=i||{},"string"==typeof i&&(n={},n[i]=s),l&&(this._curInst===l&&this._hideDatepicker(),a=this._getDateDatepicker(e,!0),r=this._getMinMaxDate(l,"min"),h=this._getMinMaxDate(l,"max"),o(l.settings,n),null!==r&&void 0!==n.dateFormat&&void 0===n.minDate&&(l.settings.minDate=this._formatDate(l,r)),null!==h&&void 0!==n.dateFormat&&void 0===n.maxDate&&(l.settings.maxDate=this._formatDate(l,h)),"disabled"in n&&(n.disabled?this._disableDatepicker(e):this._enableDatepicker(e)),this._attachments(t(e),l),this._autoSize(l),this._setDate(l,a),this._updateAlternate(l),this._updateDatepicker(l)),void 0)},_changeDatepicker:function(t,e,i){this._optionDatepicker(t,e,i)},_refreshDatepicker:function(t){var e=this._getInst(t);e&&this._updateDatepicker(e)},_setDateDatepicker:function(t,e){var i=this._getInst(t);i&&(this._setDate(i,e),this._updateDatepicker(i),this._updateAlternate(i))},_getDateDatepicker:function(t,e){var i=this._getInst(t);return i&&!i.inline&&this._setDateFromField(i,e),i?this._getDate(i):null},_doKeyDown:function(e){var i,s,n,o=t.datepicker._getInst(e.target),a=!0,r=o.dpDiv.is(".ui-datepicker-rtl");if(o._keyEvent=!0,t.datepicker._datepickerShowing)switch(e.keyCode){case 9:t.datepicker._hideDatepicker(),a=!1;break;case 13:return n=t("td."+t.datepicker._dayOverClass+":not(."+t.datepicker._currentClass+")",o.dpDiv),n[0]&&t.datepicker._selectDay(e.target,o.selectedMonth,o.selectedYear,n[0]),i=t.datepicker._get(o,"onSelect"),i?(s=t.datepicker._formatDate(o),i.apply(o.input?o.input[0]:null,[s,o])):t.datepicker._hideDatepicker(),!1;case 27:t.datepicker._hideDatepicker();break;case 33:t.datepicker._adjustDate(e.target,e.ctrlKey?-t.datepicker._get(o,"stepBigMonths"):-t.datepicker._get(o,"stepMonths"),"M");break;case 34:t.datepicker._adjustDate(e.target,e.ctrlKey?+t.datepicker._get(o,"stepBigMonths"):+t.datepicker._get(o,"stepMonths"),"M");break;case 35:(e.ctrlKey||e.metaKey)&&t.datepicker._clearDate(e.target),a=e.ctrlKey||e.metaKey;break;case 36:(e.ctrlKey||e.metaKey)&&t.datepicker._gotoToday(e.target),a=e.ctrlKey||e.metaKey;break;case 37:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,r?1:-1,"D"),a=e.ctrlKey||e.metaKey,e.originalEvent.altKey&&t.datepicker._adjustDate(e.target,e.ctrlKey?-t.datepicker._get(o,"stepBigMonths"):-t.datepicker._get(o,"stepMonths"),"M");break;case 38:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,-7,"D"),a=e.ctrlKey||e.metaKey;break;case 39:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,r?-1:1,"D"),a=e.ctrlKey||e.metaKey,e.originalEvent.altKey&&t.datepicker._adjustDate(e.target,e.ctrlKey?+t.datepicker._get(o,"stepBigMonths"):+t.datepicker._get(o,"stepMonths"),"M");break;case 40:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,7,"D"),a=e.ctrlKey||e.metaKey;break;default:a=!1}else 36===e.keyCode&&e.ctrlKey?t.datepicker._showDatepicker(this):a=!1;a&&(e.preventDefault(),e.stopPropagation())},_doKeyPress:function(e){var i,s,n=t.datepicker._getInst(e.target);return t.datepicker._get(n,"constrainInput")?(i=t.datepicker._possibleChars(t.datepicker._get(n,"dateFormat")),s=String.fromCharCode(null==e.charCode?e.keyCode:e.charCode),e.ctrlKey||e.metaKey||" ">s||!i||i.indexOf(s)>-1):void 0},_doKeyUp:function(e){var i,s=t.datepicker._getInst(e.target);if(s.input.val()!==s.lastVal)try{i=t.datepicker.parseDate(t.datepicker._get(s,"dateFormat"),s.input?s.input.val():null,t.datepicker._getFormatConfig(s)),i&&(t.datepicker._setDateFromField(s),t.datepicker._updateAlternate(s),t.datepicker._updateDatepicker(s))}catch(n){}return!0},_showDatepicker:function(i){if(i=i.target||i,"input"!==i.nodeName.toLowerCase()&&(i=t("input",i.parentNode)[0]),!t.datepicker._isDisabledDatepicker(i)&&t.datepicker._lastInput!==i){var s,n,a,r,h,l,c;s=t.datepicker._getInst(i),t.datepicker._curInst&&t.datepicker._curInst!==s&&(t.datepicker._curInst.dpDiv.stop(!0,!0),s&&t.datepicker._datepickerShowing&&t.datepicker._hideDatepicker(t.datepicker._curInst.input[0])),n=t.datepicker._get(s,"beforeShow"),a=n?n.apply(i,[i,s]):{},a!==!1&&(o(s.settings,a),s.lastVal=null,t.datepicker._lastInput=i,t.datepicker._setDateFromField(s),t.datepicker._inDialog&&(i.value=""),t.datepicker._pos||(t.datepicker._pos=t.datepicker._findPos(i),t.datepicker._pos[1]+=i.offsetHeight),r=!1,t(i).parents().each(function(){return r|="fixed"===t(this).css("position"),!r}),h={left:t.datepicker._pos[0],top:t.datepicker._pos[1]},t.datepicker._pos=null,s.dpDiv.empty(),s.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),t.datepicker._updateDatepicker(s),h=t.datepicker._checkOffset(s,h,r),s.dpDiv.css({position:t.datepicker._inDialog&&t.blockUI?"static":r?"fixed":"absolute",display:"none",left:h.left+"px",top:h.top+"px"}),s.inline||(l=t.datepicker._get(s,"showAnim"),c=t.datepicker._get(s,"duration"),s.dpDiv.css("z-index",e(t(i))+1),t.datepicker._datepickerShowing=!0,t.effects&&t.effects.effect[l]?s.dpDiv.show(l,t.datepicker._get(s,"showOptions"),c):s.dpDiv[l||"show"](l?c:null),t.datepicker._shouldFocusInput(s)&&s.input.trigger("focus"),t.datepicker._curInst=s))}},_updateDatepicker:function(e){this.maxRows=4,l=e,e.dpDiv.empty().append(this._generateHTML(e)),this._attachHandlers(e);var i,s=this._getNumberOfMonths(e),o=s[1],a=17,r=e.dpDiv.find("."+this._dayOverClass+" a");r.length>0&&n.apply(r.get(0)),e.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),o>1&&e.dpDiv.addClass("ui-datepicker-multi-"+o).css("width",a*o+"em"),e.dpDiv[(1!==s[0]||1!==s[1]?"add":"remove")+"Class"]("ui-datepicker-multi"),e.dpDiv[(this._get(e,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),e===t.datepicker._curInst&&t.datepicker._datepickerShowing&&t.datepicker._shouldFocusInput(e)&&e.input.trigger("focus"),e.yearshtml&&(i=e.yearshtml,setTimeout(function(){i===e.yearshtml&&e.yearshtml&&e.dpDiv.find("select.ui-datepicker-year:first").replaceWith(e.yearshtml),i=e.yearshtml=null},0))},_shouldFocusInput:function(t){return t.input&&t.input.is(":visible")&&!t.input.is(":disabled")&&!t.input.is(":focus")},_checkOffset:function(e,i,s){var n=e.dpDiv.outerWidth(),o=e.dpDiv.outerHeight(),a=e.input?e.input.outerWidth():0,r=e.input?e.input.outerHeight():0,h=document.documentElement.clientWidth+(s?0:t(document).scrollLeft()),l=document.documentElement.clientHeight+(s?0:t(document).scrollTop());return i.left-=this._get(e,"isRTL")?n-a:0,i.left-=s&&i.left===e.input.offset().left?t(document).scrollLeft():0,i.top-=s&&i.top===e.input.offset().top+r?t(document).scrollTop():0,i.left-=Math.min(i.left,i.left+n>h&&h>n?Math.abs(i.left+n-h):0),i.top-=Math.min(i.top,i.top+o>l&&l>o?Math.abs(o+r):0),i},_findPos:function(e){for(var i,s=this._getInst(e),n=this._get(s,"isRTL");e&&("hidden"===e.type||1!==e.nodeType||t.expr.filters.hidden(e));)e=e[n?"previousSibling":"nextSibling"];return i=t(e).offset(),[i.left,i.top]},_hideDatepicker:function(e){var i,s,n,o,a=this._curInst;!a||e&&a!==t.data(e,"datepicker")||this._datepickerShowing&&(i=this._get(a,"showAnim"),s=this._get(a,"duration"),n=function(){t.datepicker._tidyDialog(a)},t.effects&&(t.effects.effect[i]||t.effects[i])?a.dpDiv.hide(i,t.datepicker._get(a,"showOptions"),s,n):a.dpDiv["slideDown"===i?"slideUp":"fadeIn"===i?"fadeOut":"hide"](i?s:null,n),i||n(),this._datepickerShowing=!1,o=this._get(a,"onClose"),o&&o.apply(a.input?a.input[0]:null,[a.input?a.input.val():"",a]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),t.blockUI&&(t.unblockUI(),t("body").append(this.dpDiv))),this._inDialog=!1)},_tidyDialog:function(t){t.dpDiv.removeClass(this._dialogClass).off(".ui-datepicker-calendar")},_checkExternalClick:function(e){if(t.datepicker._curInst){var i=t(e.target),s=t.datepicker._getInst(i[0]);(i[0].id!==t.datepicker._mainDivId&&0===i.parents("#"+t.datepicker._mainDivId).length&&!i.hasClass(t.datepicker.markerClassName)&&!i.closest("."+t.datepicker._triggerClass).length&&t.datepicker._datepickerShowing&&(!t.datepicker._inDialog||!t.blockUI)||i.hasClass(t.datepicker.markerClassName)&&t.datepicker._curInst!==s)&&t.datepicker._hideDatepicker()}},_adjustDate:function(e,i,s){var n=t(e),o=this._getInst(n[0]);this._isDisabledDatepicker(n[0])||(this._adjustInstDate(o,i+("M"===s?this._get(o,"showCurrentAtPos"):0),s),this._updateDatepicker(o))},_gotoToday:function(e){var i,s=t(e),n=this._getInst(s[0]);this._get(n,"gotoCurrent")&&n.currentDay?(n.selectedDay=n.currentDay,n.drawMonth=n.selectedMonth=n.currentMonth,n.drawYear=n.selectedYear=n.currentYear):(i=new Date,n.selectedDay=i.getDate(),n.drawMonth=n.selectedMonth=i.getMonth(),n.drawYear=n.selectedYear=i.getFullYear()),this._notifyChange(n),this._adjustDate(s)},_selectMonthYear:function(e,i,s){var n=t(e),o=this._getInst(n[0]);o["selected"+("M"===s?"Month":"Year")]=o["draw"+("M"===s?"Month":"Year")]=parseInt(i.options[i.selectedIndex].value,10),this._notifyChange(o),this._adjustDate(n)},_selectDay:function(e,i,s,n){var o,a=t(e);t(n).hasClass(this._unselectableClass)||this._isDisabledDatepicker(a[0])||(o=this._getInst(a[0]),o.selectedDay=o.currentDay=t("a",n).html(),o.selectedMonth=o.currentMonth=i,o.selectedYear=o.currentYear=s,this._selectDate(e,this._formatDate(o,o.currentDay,o.currentMonth,o.currentYear)))},_clearDate:function(e){var i=t(e);this._selectDate(i,"")},_selectDate:function(e,i){var s,n=t(e),o=this._getInst(n[0]);i=null!=i?i:this._formatDate(o),o.input&&o.input.val(i),this._updateAlternate(o),s=this._get(o,"onSelect"),s?s.apply(o.input?o.input[0]:null,[i,o]):o.input&&o.input.trigger("change"),o.inline?this._updateDatepicker(o):(this._hideDatepicker(),this._lastInput=o.input[0],"object"!=typeof o.input[0]&&o.input.trigger("focus"),this._lastInput=null)},_updateAlternate:function(e){var i,s,n,o=this._get(e,"altField");o&&(i=this._get(e,"altFormat")||this._get(e,"dateFormat"),s=this._getDate(e),n=this.formatDate(i,s,this._getFormatConfig(e)),t(o).val(n))},noWeekends:function(t){var e=t.getDay();return[e>0&&6>e,""]},iso8601Week:function(t){var e,i=new Date(t.getTime());return i.setDate(i.getDate()+4-(i.getDay()||7)),e=i.getTime(),i.setMonth(0),i.setDate(1),Math.floor(Math.round((e-i)/864e5)/7)+1},parseDate:function(e,i,s){if(null==e||null==i)throw"Invalid arguments";if(i="object"==typeof i?""+i:i+"",""===i)return null;var n,o,a,r,h=0,l=(s?s.shortYearCutoff:null)||this._defaults.shortYearCutoff,c="string"!=typeof l?l:(new Date).getFullYear()%100+parseInt(l,10),u=(s?s.dayNamesShort:null)||this._defaults.dayNamesShort,d=(s?s.dayNames:null)||this._defaults.dayNames,p=(s?s.monthNamesShort:null)||this._defaults.monthNamesShort,f=(s?s.monthNames:null)||this._defaults.monthNames,g=-1,m=-1,_=-1,v=-1,b=!1,y=function(t){var i=e.length>n+1&&e.charAt(n+1)===t;return i&&n++,i},w=function(t){var e=y(t),s="@"===t?14:"!"===t?20:"y"===t&&e?4:"o"===t?3:2,n="y"===t?s:1,o=RegExp("^\\d{"+n+","+s+"}"),a=i.substring(h).match(o);if(!a)throw"Missing number at position "+h;return h+=a[0].length,parseInt(a[0],10)},k=function(e,s,n){var o=-1,a=t.map(y(e)?n:s,function(t,e){return[[e,t]]}).sort(function(t,e){return-(t[1].length-e[1].length)});if(t.each(a,function(t,e){var s=e[1];return i.substr(h,s.length).toLowerCase()===s.toLowerCase()?(o=e[0],h+=s.length,!1):void 0}),-1!==o)return o+1;throw"Unknown name at position "+h},D=function(){if(i.charAt(h)!==e.charAt(n))throw"Unexpected literal at position "+h;h++};for(n=0;e.length>n;n++)if(b)"'"!==e.charAt(n)||y("'")?D():b=!1;else switch(e.charAt(n)){case"d":_=w("d");break;case"D":k("D",u,d);break;case"o":v=w("o");break;case"m":m=w("m");break;case"M":m=k("M",p,f);break;case"y":g=w("y");break;case"@":r=new Date(w("@")),g=r.getFullYear(),m=r.getMonth()+1,_=r.getDate();break;case"!":r=new Date((w("!")-this._ticksTo1970)/1e4),g=r.getFullYear(),m=r.getMonth()+1,_=r.getDate();break;case"'":y("'")?D():b=!0;break;default:D()}if(i.length>h&&(a=i.substr(h),!/^\s+/.test(a)))throw"Extra/unparsed characters found in date: "+a;if(-1===g?g=(new Date).getFullYear():100>g&&(g+=(new Date).getFullYear()-(new Date).getFullYear()%100+(c>=g?0:-100)),v>-1)for(m=1,_=v;;){if(o=this._getDaysInMonth(g,m-1),o>=_)break;m++,_-=o}if(r=this._daylightSavingAdjust(new Date(g,m-1,_)),r.getFullYear()!==g||r.getMonth()+1!==m||r.getDate()!==_)throw"Invalid date";return r},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:1e7*60*60*24*(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925)),formatDate:function(t,e,i){if(!e)return"";var s,n=(i?i.dayNamesShort:null)||this._defaults.dayNamesShort,o=(i?i.dayNames:null)||this._defaults.dayNames,a=(i?i.monthNamesShort:null)||this._defaults.monthNamesShort,r=(i?i.monthNames:null)||this._defaults.monthNames,h=function(e){var i=t.length>s+1&&t.charAt(s+1)===e;return i&&s++,i},l=function(t,e,i){var s=""+e;if(h(t))for(;i>s.length;)s="0"+s;return s},c=function(t,e,i,s){return h(t)?s[e]:i[e]},u="",d=!1;if(e)for(s=0;t.length>s;s++)if(d)"'"!==t.charAt(s)||h("'")?u+=t.charAt(s):d=!1;else switch(t.charAt(s)){case"d":u+=l("d",e.getDate(),2);break;case"D":u+=c("D",e.getDay(),n,o);break;case"o":u+=l("o",Math.round((new Date(e.getFullYear(),e.getMonth(),e.getDate()).getTime()-new Date(e.getFullYear(),0,0).getTime())/864e5),3);break;case"m":u+=l("m",e.getMonth()+1,2);break;case"M":u+=c("M",e.getMonth(),a,r);break;case"y":u+=h("y")?e.getFullYear():(10>e.getFullYear()%100?"0":"")+e.getFullYear()%100;break;case"@":u+=e.getTime();break;case"!":u+=1e4*e.getTime()+this._ticksTo1970;break;case"'":h("'")?u+="'":d=!0;break;default:u+=t.charAt(s)}return u},_possibleChars:function(t){var e,i="",s=!1,n=function(i){var s=t.length>e+1&&t.charAt(e+1)===i;return s&&e++,s};for(e=0;t.length>e;e++)if(s)"'"!==t.charAt(e)||n("'")?i+=t.charAt(e):s=!1;else switch(t.charAt(e)){case"d":case"m":case"y":case"@":i+="0123456789";break;case"D":case"M":return null;case"'":n("'")?i+="'":s=!0;break;default:i+=t.charAt(e)}return i},_get:function(t,e){return void 0!==t.settings[e]?t.settings[e]:this._defaults[e]},_setDateFromField:function(t,e){if(t.input.val()!==t.lastVal){var i=this._get(t,"dateFormat"),s=t.lastVal=t.input?t.input.val():null,n=this._getDefaultDate(t),o=n,a=this._getFormatConfig(t);try{o=this.parseDate(i,s,a)||n}catch(r){s=e?"":s}t.selectedDay=o.getDate(),t.drawMonth=t.selectedMonth=o.getMonth(),t.drawYear=t.selectedYear=o.getFullYear(),t.currentDay=s?o.getDate():0,t.currentMonth=s?o.getMonth():0,t.currentYear=s?o.getFullYear():0,this._adjustInstDate(t)}},_getDefaultDate:function(t){return this._restrictMinMax(t,this._determineDate(t,this._get(t,"defaultDate"),new Date))},_determineDate:function(e,i,s){var n=function(t){var e=new Date;return e.setDate(e.getDate()+t),e},o=function(i){try{return t.datepicker.parseDate(t.datepicker._get(e,"dateFormat"),i,t.datepicker._getFormatConfig(e))}catch(s){}for(var n=(i.toLowerCase().match(/^c/)?t.datepicker._getDate(e):null)||new Date,o=n.getFullYear(),a=n.getMonth(),r=n.getDate(),h=/([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,l=h.exec(i);l;){switch(l[2]||"d"){case"d":case"D":r+=parseInt(l[1],10);break;case"w":case"W":r+=7*parseInt(l[1],10);break;case"m":case"M":a+=parseInt(l[1],10),r=Math.min(r,t.datepicker._getDaysInMonth(o,a));break;case"y":case"Y":o+=parseInt(l[1],10),r=Math.min(r,t.datepicker._getDaysInMonth(o,a))}l=h.exec(i)}return new Date(o,a,r)},a=null==i||""===i?s:"string"==typeof i?o(i):"number"==typeof i?isNaN(i)?s:n(i):new Date(i.getTime());return a=a&&"Invalid Date"==""+a?s:a,a&&(a.setHours(0),a.setMinutes(0),a.setSeconds(0),a.setMilliseconds(0)),this._daylightSavingAdjust(a)},_daylightSavingAdjust:function(t){return t?(t.setHours(t.getHours()>12?t.getHours()+2:0),t):null},_setDate:function(t,e,i){var s=!e,n=t.selectedMonth,o=t.selectedYear,a=this._restrictMinMax(t,this._determineDate(t,e,new Date));t.selectedDay=t.currentDay=a.getDate(),t.drawMonth=t.selectedMonth=t.currentMonth=a.getMonth(),t.drawYear=t.selectedYear=t.currentYear=a.getFullYear(),n===t.selectedMonth&&o===t.selectedYear||i||this._notifyChange(t),this._adjustInstDate(t),t.input&&t.input.val(s?"":this._formatDate(t))},_getDate:function(t){var e=!t.currentYear||t.input&&""===t.input.val()?null:this._daylightSavingAdjust(new Date(t.currentYear,t.currentMonth,t.currentDay));return e},_attachHandlers:function(e){var i=this._get(e,"stepMonths"),s="#"+e.id.replace(/\\\\/g,"\\");e.dpDiv.find("[data-handler]").map(function(){var e={prev:function(){t.datepicker._adjustDate(s,-i,"M")},next:function(){t.datepicker._adjustDate(s,+i,"M")},hide:function(){t.datepicker._hideDatepicker()},today:function(){t.datepicker._gotoToday(s)},selectDay:function(){return t.datepicker._selectDay(s,+this.getAttribute("data-month"),+this.getAttribute("data-year"),this),!1},selectMonth:function(){return t.datepicker._selectMonthYear(s,this,"M"),!1},selectYear:function(){return t.datepicker._selectMonthYear(s,this,"Y"),!1}};t(this).on(this.getAttribute("data-event"),e[this.getAttribute("data-handler")])})},_generateHTML:function(t){var e,i,s,n,o,a,r,h,l,c,u,d,p,f,g,m,_,v,b,y,w,k,D,x,C,I,M,P,T,S,A,O,N,H,E,W,z,F,L,R=new Date,Y=this._daylightSavingAdjust(new Date(R.getFullYear(),R.getMonth(),R.getDate())),B=this._get(t,"isRTL"),j=this._get(t,"showButtonPanel"),K=this._get(t,"hideIfNoPrevNext"),q=this._get(t,"navigationAsDateFormat"),V=this._getNumberOfMonths(t),U=this._get(t,"showCurrentAtPos"),X=this._get(t,"stepMonths"),$=1!==V[0]||1!==V[1],G=this._daylightSavingAdjust(t.currentDay?new Date(t.currentYear,t.currentMonth,t.currentDay):new Date(9999,9,9)),Q=this._getMinMaxDate(t,"min"),J=this._getMinMaxDate(t,"max"),Z=t.drawMonth-U,te=t.drawYear;if(0>Z&&(Z+=12,te--),J)for(e=this._daylightSavingAdjust(new Date(J.getFullYear(),J.getMonth()-V[0]*V[1]+1,J.getDate())),e=Q&&Q>e?Q:e;this._daylightSavingAdjust(new Date(te,Z,1))>e;)Z--,0>Z&&(Z=11,te--);for(t.drawMonth=Z,t.drawYear=te,i=this._get(t,"prevText"),i=q?this.formatDate(i,this._daylightSavingAdjust(new Date(te,Z-X,1)),this._getFormatConfig(t)):i,s=this._canAdjustMonth(t,-1,te,Z)?""+i+"":K?"":""+i+"",n=this._get(t,"nextText"),n=q?this.formatDate(n,this._daylightSavingAdjust(new Date(te,Z+X,1)),this._getFormatConfig(t)):n,o=this._canAdjustMonth(t,1,te,Z)?""+n+"":K?"":""+n+"",a=this._get(t,"currentText"),r=this._get(t,"gotoCurrent")&&t.currentDay?G:Y,a=q?this.formatDate(a,r,this._getFormatConfig(t)):a,h=t.inline?"":"",l=j?"
      "+(B?h:"")+(this._isInRange(t,r)?"":"")+(B?"":h)+"
      ":"",c=parseInt(this._get(t,"firstDay"),10),c=isNaN(c)?0:c,u=this._get(t,"showWeek"),d=this._get(t,"dayNames"),p=this._get(t,"dayNamesMin"),f=this._get(t,"monthNames"),g=this._get(t,"monthNamesShort"),m=this._get(t,"beforeShowDay"),_=this._get(t,"showOtherMonths"),v=this._get(t,"selectOtherMonths"),b=this._getDefaultDate(t),y="",k=0;V[0]>k;k++){for(D="",this.maxRows=4,x=0;V[1]>x;x++){if(C=this._daylightSavingAdjust(new Date(te,Z,t.selectedDay)),I=" ui-corner-all",M="",$){if(M+="
      "}for(M+="
      "+(/all|left/.test(I)&&0===k?B?o:s:"")+(/all|right/.test(I)&&0===k?B?s:o:"")+this._generateMonthYearHeader(t,Z,te,Q,J,k>0||x>0,f,g)+"
      "+"",P=u?"":"",w=0;7>w;w++)T=(w+c)%7,P+="";for(M+=P+"",S=this._getDaysInMonth(te,Z),te===t.selectedYear&&Z===t.selectedMonth&&(t.selectedDay=Math.min(t.selectedDay,S)),A=(this._getFirstDayOfMonth(te,Z)-c+7)%7,O=Math.ceil((A+S)/7),N=$?this.maxRows>O?this.maxRows:O:O,this.maxRows=N,H=this._daylightSavingAdjust(new Date(te,Z,1-A)),E=0;N>E;E++){for(M+="",W=u?"":"",w=0;7>w;w++)z=m?m.apply(t.input?t.input[0]:null,[H]):[!0,""],F=H.getMonth()!==Z,L=F&&!v||!z[0]||Q&&Q>H||J&&H>J,W+="",H.setDate(H.getDate()+1),H=this._daylightSavingAdjust(H);M+=W+""}Z++,Z>11&&(Z=0,te++),M+="
      "+this._get(t,"weekHeader")+"=5?" class='ui-datepicker-week-end'":"")+">"+""+p[T]+"
      "+this._get(t,"calculateWeek")(H)+""+(F&&!_?" ":L?""+H.getDate()+"":""+H.getDate()+"")+"
      "+($?"
      "+(V[0]>0&&x===V[1]-1?"
      ":""):""),D+=M}y+=D}return y+=l,t._keyEvent=!1,y},_generateMonthYearHeader:function(t,e,i,s,n,o,a,r){var h,l,c,u,d,p,f,g,m=this._get(t,"changeMonth"),_=this._get(t,"changeYear"),v=this._get(t,"showMonthAfterYear"),b="
      ",y="";if(o||!m)y+=""+a[e]+"";else{for(h=s&&s.getFullYear()===i,l=n&&n.getFullYear()===i,y+=""}if(v||(b+=y+(!o&&m&&_?"":" ")),!t.yearshtml)if(t.yearshtml="",o||!_)b+=""+i+"";else{for(u=this._get(t,"yearRange").split(":"),d=(new Date).getFullYear(),p=function(t){var e=t.match(/c[+\-].*/)?i+parseInt(t.substring(1),10):t.match(/[+\-].*/)?d+parseInt(t,10):parseInt(t,10);return isNaN(e)?d:e},f=p(u[0]),g=Math.max(f,p(u[1]||"")),f=s?Math.max(f,s.getFullYear()):f,g=n?Math.min(g,n.getFullYear()):g,t.yearshtml+="",b+=t.yearshtml,t.yearshtml=null}return b+=this._get(t,"yearSuffix"),v&&(b+=(!o&&m&&_?"":" ")+y),b+="
      "},_adjustInstDate:function(t,e,i){var s=t.selectedYear+("Y"===i?e:0),n=t.selectedMonth+("M"===i?e:0),o=Math.min(t.selectedDay,this._getDaysInMonth(s,n))+("D"===i?e:0),a=this._restrictMinMax(t,this._daylightSavingAdjust(new Date(s,n,o)));t.selectedDay=a.getDate(),t.drawMonth=t.selectedMonth=a.getMonth(),t.drawYear=t.selectedYear=a.getFullYear(),("M"===i||"Y"===i)&&this._notifyChange(t)},_restrictMinMax:function(t,e){var i=this._getMinMaxDate(t,"min"),s=this._getMinMaxDate(t,"max"),n=i&&i>e?i:e;return s&&n>s?s:n},_notifyChange:function(t){var e=this._get(t,"onChangeMonthYear");e&&e.apply(t.input?t.input[0]:null,[t.selectedYear,t.selectedMonth+1,t])},_getNumberOfMonths:function(t){var e=this._get(t,"numberOfMonths");return null==e?[1,1]:"number"==typeof e?[1,e]:e},_getMinMaxDate:function(t,e){return this._determineDate(t,this._get(t,e+"Date"),null)},_getDaysInMonth:function(t,e){return 32-this._daylightSavingAdjust(new Date(t,e,32)).getDate()},_getFirstDayOfMonth:function(t,e){return new Date(t,e,1).getDay()},_canAdjustMonth:function(t,e,i,s){var n=this._getNumberOfMonths(t),o=this._daylightSavingAdjust(new Date(i,s+(0>e?e:n[0]*n[1]),1));return 0>e&&o.setDate(this._getDaysInMonth(o.getFullYear(),o.getMonth())),this._isInRange(t,o)},_isInRange:function(t,e){var i,s,n=this._getMinMaxDate(t,"min"),o=this._getMinMaxDate(t,"max"),a=null,r=null,h=this._get(t,"yearRange");return h&&(i=h.split(":"),s=(new Date).getFullYear(),a=parseInt(i[0],10),r=parseInt(i[1],10),i[0].match(/[+\-].*/)&&(a+=s),i[1].match(/[+\-].*/)&&(r+=s)),(!n||e.getTime()>=n.getTime())&&(!o||e.getTime()<=o.getTime())&&(!a||e.getFullYear()>=a)&&(!r||r>=e.getFullYear())},_getFormatConfig:function(t){var e=this._get(t,"shortYearCutoff");return e="string"!=typeof e?e:(new Date).getFullYear()%100+parseInt(e,10),{shortYearCutoff:e,dayNamesShort:this._get(t,"dayNamesShort"),dayNames:this._get(t,"dayNames"),monthNamesShort:this._get(t,"monthNamesShort"),monthNames:this._get(t,"monthNames")}},_formatDate:function(t,e,i,s){e||(t.currentDay=t.selectedDay,t.currentMonth=t.selectedMonth,t.currentYear=t.selectedYear);var n=e?"object"==typeof e?e:this._daylightSavingAdjust(new Date(s,i,e)):this._daylightSavingAdjust(new Date(t.currentYear,t.currentMonth,t.currentDay));return this.formatDate(this._get(t,"dateFormat"),n,this._getFormatConfig(t))}}),t.fn.datepicker=function(e){if(!this.length)return this;t.datepicker.initialized||(t(document).on("mousedown",t.datepicker._checkExternalClick),t.datepicker.initialized=!0),0===t("#"+t.datepicker._mainDivId).length&&t("body").append(t.datepicker.dpDiv);var i=Array.prototype.slice.call(arguments,1);return"string"!=typeof e||"isDisabled"!==e&&"getDate"!==e&&"widget"!==e?"option"===e&&2===arguments.length&&"string"==typeof arguments[1]?t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this[0]].concat(i)):this.each(function(){"string"==typeof e?t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this].concat(i)):t.datepicker._attachDatepicker(this,e) }):t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this[0]].concat(i))},t.datepicker=new i,t.datepicker.initialized=!1,t.datepicker.uuid=(new Date).getTime(),t.datepicker.version="1.12.1",t.datepicker,t.widget("ui.slider",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"slide",options:{animate:!1,classes:{"ui-slider":"ui-corner-all","ui-slider-handle":"ui-corner-all","ui-slider-range":"ui-corner-all ui-widget-header"},distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null,change:null,slide:null,start:null,stop:null},numPages:5,_create:function(){this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this._calculateNewMax(),this._addClass("ui-slider ui-slider-"+this.orientation,"ui-widget ui-widget-content"),this._refresh(),this._animateOff=!1},_refresh:function(){this._createRange(),this._createHandles(),this._setupEvents(),this._refreshValue()},_createHandles:function(){var e,i,s=this.options,n=this.element.find(".ui-slider-handle"),o="",a=[];for(i=s.values&&s.values.length||1,n.length>i&&(n.slice(i).remove(),n=n.slice(0,i)),e=n.length;i>e;e++)a.push(o);this.handles=n.add(t(a.join("")).appendTo(this.element)),this._addClass(this.handles,"ui-slider-handle","ui-state-default"),this.handle=this.handles.eq(0),this.handles.each(function(e){t(this).data("ui-slider-handle-index",e).attr("tabIndex",0)})},_createRange:function(){var e=this.options;e.range?(e.range===!0&&(e.values?e.values.length&&2!==e.values.length?e.values=[e.values[0],e.values[0]]:t.isArray(e.values)&&(e.values=e.values.slice(0)):e.values=[this._valueMin(),this._valueMin()]),this.range&&this.range.length?(this._removeClass(this.range,"ui-slider-range-min ui-slider-range-max"),this.range.css({left:"",bottom:""})):(this.range=t("
      ").appendTo(this.element),this._addClass(this.range,"ui-slider-range")),("min"===e.range||"max"===e.range)&&this._addClass(this.range,"ui-slider-range-"+e.range)):(this.range&&this.range.remove(),this.range=null)},_setupEvents:function(){this._off(this.handles),this._on(this.handles,this._handleEvents),this._hoverable(this.handles),this._focusable(this.handles)},_destroy:function(){this.handles.remove(),this.range&&this.range.remove(),this._mouseDestroy()},_mouseCapture:function(e){var i,s,n,o,a,r,h,l,c=this,u=this.options;return u.disabled?!1:(this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()},this.elementOffset=this.element.offset(),i={x:e.pageX,y:e.pageY},s=this._normValueFromMouse(i),n=this._valueMax()-this._valueMin()+1,this.handles.each(function(e){var i=Math.abs(s-c.values(e));(n>i||n===i&&(e===c._lastChangedValue||c.values(e)===u.min))&&(n=i,o=t(this),a=e)}),r=this._start(e,a),r===!1?!1:(this._mouseSliding=!0,this._handleIndex=a,this._addClass(o,null,"ui-state-active"),o.trigger("focus"),h=o.offset(),l=!t(e.target).parents().addBack().is(".ui-slider-handle"),this._clickOffset=l?{left:0,top:0}:{left:e.pageX-h.left-o.width()/2,top:e.pageY-h.top-o.height()/2-(parseInt(o.css("borderTopWidth"),10)||0)-(parseInt(o.css("borderBottomWidth"),10)||0)+(parseInt(o.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(e,a,s),this._animateOff=!0,!0))},_mouseStart:function(){return!0},_mouseDrag:function(t){var e={x:t.pageX,y:t.pageY},i=this._normValueFromMouse(e);return this._slide(t,this._handleIndex,i),!1},_mouseStop:function(t){return this._removeClass(this.handles,null,"ui-state-active"),this._mouseSliding=!1,this._stop(t,this._handleIndex),this._change(t,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation="vertical"===this.options.orientation?"vertical":"horizontal"},_normValueFromMouse:function(t){var e,i,s,n,o;return"horizontal"===this.orientation?(e=this.elementSize.width,i=t.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(e=this.elementSize.height,i=t.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),s=i/e,s>1&&(s=1),0>s&&(s=0),"vertical"===this.orientation&&(s=1-s),n=this._valueMax()-this._valueMin(),o=this._valueMin()+s*n,this._trimAlignValue(o)},_uiHash:function(t,e,i){var s={handle:this.handles[t],handleIndex:t,value:void 0!==e?e:this.value()};return this._hasMultipleValues()&&(s.value=void 0!==e?e:this.values(t),s.values=i||this.values()),s},_hasMultipleValues:function(){return this.options.values&&this.options.values.length},_start:function(t,e){return this._trigger("start",t,this._uiHash(e))},_slide:function(t,e,i){var s,n,o=this.value(),a=this.values();this._hasMultipleValues()&&(n=this.values(e?0:1),o=this.values(e),2===this.options.values.length&&this.options.range===!0&&(i=0===e?Math.min(n,i):Math.max(n,i)),a[e]=i),i!==o&&(s=this._trigger("slide",t,this._uiHash(e,i,a)),s!==!1&&(this._hasMultipleValues()?this.values(e,i):this.value(i)))},_stop:function(t,e){this._trigger("stop",t,this._uiHash(e))},_change:function(t,e){this._keySliding||this._mouseSliding||(this._lastChangedValue=e,this._trigger("change",t,this._uiHash(e)))},value:function(t){return arguments.length?(this.options.value=this._trimAlignValue(t),this._refreshValue(),this._change(null,0),void 0):this._value()},values:function(e,i){var s,n,o;if(arguments.length>1)return this.options.values[e]=this._trimAlignValue(i),this._refreshValue(),this._change(null,e),void 0;if(!arguments.length)return this._values();if(!t.isArray(arguments[0]))return this._hasMultipleValues()?this._values(e):this.value();for(s=this.options.values,n=arguments[0],o=0;s.length>o;o+=1)s[o]=this._trimAlignValue(n[o]),this._change(null,o);this._refreshValue()},_setOption:function(e,i){var s,n=0;switch("range"===e&&this.options.range===!0&&("min"===i?(this.options.value=this._values(0),this.options.values=null):"max"===i&&(this.options.value=this._values(this.options.values.length-1),this.options.values=null)),t.isArray(this.options.values)&&(n=this.options.values.length),this._super(e,i),e){case"orientation":this._detectOrientation(),this._removeClass("ui-slider-horizontal ui-slider-vertical")._addClass("ui-slider-"+this.orientation),this._refreshValue(),this.options.range&&this._refreshRange(i),this.handles.css("horizontal"===i?"bottom":"left","");break;case"value":this._animateOff=!0,this._refreshValue(),this._change(null,0),this._animateOff=!1;break;case"values":for(this._animateOff=!0,this._refreshValue(),s=n-1;s>=0;s--)this._change(null,s);this._animateOff=!1;break;case"step":case"min":case"max":this._animateOff=!0,this._calculateNewMax(),this._refreshValue(),this._animateOff=!1;break;case"range":this._animateOff=!0,this._refresh(),this._animateOff=!1}},_setOptionDisabled:function(t){this._super(t),this._toggleClass(null,"ui-state-disabled",!!t)},_value:function(){var t=this.options.value;return t=this._trimAlignValue(t)},_values:function(t){var e,i,s;if(arguments.length)return e=this.options.values[t],e=this._trimAlignValue(e);if(this._hasMultipleValues()){for(i=this.options.values.slice(),s=0;i.length>s;s+=1)i[s]=this._trimAlignValue(i[s]);return i}return[]},_trimAlignValue:function(t){if(this._valueMin()>=t)return this._valueMin();if(t>=this._valueMax())return this._valueMax();var e=this.options.step>0?this.options.step:1,i=(t-this._valueMin())%e,s=t-i;return 2*Math.abs(i)>=e&&(s+=i>0?e:-e),parseFloat(s.toFixed(5))},_calculateNewMax:function(){var t=this.options.max,e=this._valueMin(),i=this.options.step,s=Math.round((t-e)/i)*i;t=s+e,t>this.options.max&&(t-=i),this.max=parseFloat(t.toFixed(this._precision()))},_precision:function(){var t=this._precisionOf(this.options.step);return null!==this.options.min&&(t=Math.max(t,this._precisionOf(this.options.min))),t},_precisionOf:function(t){var e=""+t,i=e.indexOf(".");return-1===i?0:e.length-i-1},_valueMin:function(){return this.options.min},_valueMax:function(){return this.max},_refreshRange:function(t){"vertical"===t&&this.range.css({width:"",left:""}),"horizontal"===t&&this.range.css({height:"",bottom:""})},_refreshValue:function(){var e,i,s,n,o,a=this.options.range,r=this.options,h=this,l=this._animateOff?!1:r.animate,c={};this._hasMultipleValues()?this.handles.each(function(s){i=100*((h.values(s)-h._valueMin())/(h._valueMax()-h._valueMin())),c["horizontal"===h.orientation?"left":"bottom"]=i+"%",t(this).stop(1,1)[l?"animate":"css"](c,r.animate),h.options.range===!0&&("horizontal"===h.orientation?(0===s&&h.range.stop(1,1)[l?"animate":"css"]({left:i+"%"},r.animate),1===s&&h.range[l?"animate":"css"]({width:i-e+"%"},{queue:!1,duration:r.animate})):(0===s&&h.range.stop(1,1)[l?"animate":"css"]({bottom:i+"%"},r.animate),1===s&&h.range[l?"animate":"css"]({height:i-e+"%"},{queue:!1,duration:r.animate}))),e=i}):(s=this.value(),n=this._valueMin(),o=this._valueMax(),i=o!==n?100*((s-n)/(o-n)):0,c["horizontal"===this.orientation?"left":"bottom"]=i+"%",this.handle.stop(1,1)[l?"animate":"css"](c,r.animate),"min"===a&&"horizontal"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({width:i+"%"},r.animate),"max"===a&&"horizontal"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({width:100-i+"%"},r.animate),"min"===a&&"vertical"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({height:i+"%"},r.animate),"max"===a&&"vertical"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({height:100-i+"%"},r.animate))},_handleEvents:{keydown:function(e){var i,s,n,o,a=t(e.target).data("ui-slider-handle-index");switch(e.keyCode){case t.ui.keyCode.HOME:case t.ui.keyCode.END:case t.ui.keyCode.PAGE_UP:case t.ui.keyCode.PAGE_DOWN:case t.ui.keyCode.UP:case t.ui.keyCode.RIGHT:case t.ui.keyCode.DOWN:case t.ui.keyCode.LEFT:if(e.preventDefault(),!this._keySliding&&(this._keySliding=!0,this._addClass(t(e.target),null,"ui-state-active"),i=this._start(e,a),i===!1))return}switch(o=this.options.step,s=n=this._hasMultipleValues()?this.values(a):this.value(),e.keyCode){case t.ui.keyCode.HOME:n=this._valueMin();break;case t.ui.keyCode.END:n=this._valueMax();break;case t.ui.keyCode.PAGE_UP:n=this._trimAlignValue(s+(this._valueMax()-this._valueMin())/this.numPages);break;case t.ui.keyCode.PAGE_DOWN:n=this._trimAlignValue(s-(this._valueMax()-this._valueMin())/this.numPages);break;case t.ui.keyCode.UP:case t.ui.keyCode.RIGHT:if(s===this._valueMax())return;n=this._trimAlignValue(s+o);break;case t.ui.keyCode.DOWN:case t.ui.keyCode.LEFT:if(s===this._valueMin())return;n=this._trimAlignValue(s-o)}this._slide(e,a,n)},keyup:function(e){var i=t(e.target).data("ui-slider-handle-index");this._keySliding&&(this._keySliding=!1,this._stop(e,i),this._change(e,i),this._removeClass(t(e.target),null,"ui-state-active"))}}})});rt-5.0.1/share/static/js/Chart.min.js000644 000765 000024 00000462061 14005011336 020200 0ustar00sunnavystaff000000 000000 /*! * Chart.js v2.8.0 * https://www.chartjs.org * (c) 2019 Chart.js Contributors * Released under the MIT License */ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(function(){try{return require("moment")}catch(t){}}()):"function"==typeof define&&define.amd?define(["require"],function(t){return e(function(){try{return t("moment")}catch(t){}}())}):t.Chart=e(t.moment)}(this,function(t){"use strict";t=t&&t.hasOwnProperty("default")?t.default:t;var e={rgb2hsl:i,rgb2hsv:n,rgb2hwb:a,rgb2cmyk:o,rgb2keyword:s,rgb2xyz:l,rgb2lab:d,rgb2lch:function(t){return x(d(t))},hsl2rgb:u,hsl2hsv:function(t){var e=t[0],i=t[1]/100,n=t[2]/100;if(0===n)return[0,0,0];return[e,100*(2*(i*=(n*=2)<=1?n:2-n)/(n+i)),100*((n+i)/2)]},hsl2hwb:function(t){return a(u(t))},hsl2cmyk:function(t){return o(u(t))},hsl2keyword:function(t){return s(u(t))},hsv2rgb:h,hsv2hsl:function(t){var e,i,n=t[0],a=t[1]/100,o=t[2]/100;return e=a*o,[n,100*(e=(e/=(i=(2-a)*o)<=1?i:2-i)||0),100*(i/=2)]},hsv2hwb:function(t){return a(h(t))},hsv2cmyk:function(t){return o(h(t))},hsv2keyword:function(t){return s(h(t))},hwb2rgb:c,hwb2hsl:function(t){return i(c(t))},hwb2hsv:function(t){return n(c(t))},hwb2cmyk:function(t){return o(c(t))},hwb2keyword:function(t){return s(c(t))},cmyk2rgb:f,cmyk2hsl:function(t){return i(f(t))},cmyk2hsv:function(t){return n(f(t))},cmyk2hwb:function(t){return a(f(t))},cmyk2keyword:function(t){return s(f(t))},keyword2rgb:w,keyword2hsl:function(t){return i(w(t))},keyword2hsv:function(t){return n(w(t))},keyword2hwb:function(t){return a(w(t))},keyword2cmyk:function(t){return o(w(t))},keyword2lab:function(t){return d(w(t))},keyword2xyz:function(t){return l(w(t))},xyz2rgb:p,xyz2lab:m,xyz2lch:function(t){return x(m(t))},lab2xyz:v,lab2rgb:y,lab2lch:x,lch2lab:k,lch2xyz:function(t){return v(k(t))},lch2rgb:function(t){return y(k(t))}};function i(t){var e,i,n=t[0]/255,a=t[1]/255,o=t[2]/255,r=Math.min(n,a,o),s=Math.max(n,a,o),l=s-r;return s==r?e=0:n==s?e=(a-o)/l:a==s?e=2+(o-n)/l:o==s&&(e=4+(n-a)/l),(e=Math.min(60*e,360))<0&&(e+=360),i=(r+s)/2,[e,100*(s==r?0:i<=.5?l/(s+r):l/(2-s-r)),100*i]}function n(t){var e,i,n=t[0],a=t[1],o=t[2],r=Math.min(n,a,o),s=Math.max(n,a,o),l=s-r;return i=0==s?0:l/s*1e3/10,s==r?e=0:n==s?e=(a-o)/l:a==s?e=2+(o-n)/l:o==s&&(e=4+(n-a)/l),(e=Math.min(60*e,360))<0&&(e+=360),[e,i,s/255*1e3/10]}function a(t){var e=t[0],n=t[1],a=t[2];return[i(t)[0],100*(1/255*Math.min(e,Math.min(n,a))),100*(a=1-1/255*Math.max(e,Math.max(n,a)))]}function o(t){var e,i=t[0]/255,n=t[1]/255,a=t[2]/255;return[100*((1-i-(e=Math.min(1-i,1-n,1-a)))/(1-e)||0),100*((1-n-e)/(1-e)||0),100*((1-a-e)/(1-e)||0),100*e]}function s(t){return _[JSON.stringify(t)]}function l(t){var e=t[0]/255,i=t[1]/255,n=t[2]/255;return[100*(.4124*(e=e>.04045?Math.pow((e+.055)/1.055,2.4):e/12.92)+.3576*(i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92)+.1805*(n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92)),100*(.2126*e+.7152*i+.0722*n),100*(.0193*e+.1192*i+.9505*n)]}function d(t){var e=l(t),i=e[0],n=e[1],a=e[2];return n/=100,a/=108.883,i=(i/=95.047)>.008856?Math.pow(i,1/3):7.787*i+16/116,[116*(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116)-16,500*(i-n),200*(n-(a=a>.008856?Math.pow(a,1/3):7.787*a+16/116))]}function u(t){var e,i,n,a,o,r=t[0]/360,s=t[1]/100,l=t[2]/100;if(0==s)return[o=255*l,o,o];e=2*l-(i=l<.5?l*(1+s):l+s-l*s),a=[0,0,0];for(var d=0;d<3;d++)(n=r+1/3*-(d-1))<0&&n++,n>1&&n--,o=6*n<1?e+6*(i-e)*n:2*n<1?i:3*n<2?e+(i-e)*(2/3-n)*6:e,a[d]=255*o;return a}function h(t){var e=t[0]/60,i=t[1]/100,n=t[2]/100,a=Math.floor(e)%6,o=e-Math.floor(e),r=255*n*(1-i),s=255*n*(1-i*o),l=255*n*(1-i*(1-o));n*=255;switch(a){case 0:return[n,l,r];case 1:return[s,n,r];case 2:return[r,n,l];case 3:return[r,s,n];case 4:return[l,r,n];case 5:return[n,r,s]}}function c(t){var e,i,n,a,o=t[0]/360,s=t[1]/100,l=t[2]/100,d=s+l;switch(d>1&&(s/=d,l/=d),n=6*o-(e=Math.floor(6*o)),0!=(1&e)&&(n=1-n),a=s+n*((i=1-l)-s),e){default:case 6:case 0:r=i,g=a,b=s;break;case 1:r=a,g=i,b=s;break;case 2:r=s,g=i,b=a;break;case 3:r=s,g=a,b=i;break;case 4:r=a,g=s,b=i;break;case 5:r=i,g=s,b=a}return[255*r,255*g,255*b]}function f(t){var e=t[0]/100,i=t[1]/100,n=t[2]/100,a=t[3]/100;return[255*(1-Math.min(1,e*(1-a)+a)),255*(1-Math.min(1,i*(1-a)+a)),255*(1-Math.min(1,n*(1-a)+a))]}function p(t){var e,i,n,a=t[0]/100,o=t[1]/100,r=t[2]/100;return i=-.9689*a+1.8758*o+.0415*r,n=.0557*a+-.204*o+1.057*r,e=(e=3.2406*a+-1.5372*o+-.4986*r)>.0031308?1.055*Math.pow(e,1/2.4)-.055:e*=12.92,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:i*=12.92,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:n*=12.92,[255*(e=Math.min(Math.max(0,e),1)),255*(i=Math.min(Math.max(0,i),1)),255*(n=Math.min(Math.max(0,n),1))]}function m(t){var e=t[0],i=t[1],n=t[2];return i/=100,n/=108.883,e=(e/=95.047)>.008856?Math.pow(e,1/3):7.787*e+16/116,[116*(i=i>.008856?Math.pow(i,1/3):7.787*i+16/116)-16,500*(e-i),200*(i-(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116))]}function v(t){var e,i,n,a,o=t[0],r=t[1],s=t[2];return o<=8?a=(i=100*o/903.3)/100*7.787+16/116:(i=100*Math.pow((o+16)/116,3),a=Math.pow(i/100,1/3)),[e=e/95.047<=.008856?e=95.047*(r/500+a-16/116)/7.787:95.047*Math.pow(r/500+a,3),i,n=n/108.883<=.008859?n=108.883*(a-s/200-16/116)/7.787:108.883*Math.pow(a-s/200,3)]}function x(t){var e,i=t[0],n=t[1],a=t[2];return(e=360*Math.atan2(a,n)/2/Math.PI)<0&&(e+=360),[i,Math.sqrt(n*n+a*a),e]}function y(t){return p(v(t))}function k(t){var e,i=t[0],n=t[1];return e=t[2]/360*2*Math.PI,[i,n*Math.cos(e),n*Math.sin(e)]}function w(t){return M[t]}var M={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},_={};for(var C in M)_[JSON.stringify(M[C])]=C;var S=function(){return new T};for(var P in e){S[P+"Raw"]=function(t){return function(i){return"number"==typeof i&&(i=Array.prototype.slice.call(arguments)),e[t](i)}}(P);var I=/(\w+)2(\w+)/.exec(P),A=I[1],D=I[2];(S[A]=S[A]||{})[D]=S[P]=function(t){return function(i){"number"==typeof i&&(i=Array.prototype.slice.call(arguments));var n=e[t](i);if("string"==typeof n||void 0===n)return n;for(var a=0;a=0&&e<1?H(Math.round(255*e)):"")},rgbString:function(t,e){if(e<1||t[3]&&t[3]<1)return N(t,e);return"rgb("+t[0]+", "+t[1]+", "+t[2]+")"},rgbaString:N,percentString:function(t,e){if(e<1||t[3]&&t[3]<1)return W(t,e);var i=Math.round(t[0]/255*100),n=Math.round(t[1]/255*100),a=Math.round(t[2]/255*100);return"rgb("+i+"%, "+n+"%, "+a+"%)"},percentaString:W,hslString:function(t,e){if(e<1||t[3]&&t[3]<1)return V(t,e);return"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"},hslaString:V,hwbString:function(t,e){void 0===e&&(e=void 0!==t[3]?t[3]:1);return"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"},keyword:function(t){return j[t.slice(0,3)]}};function O(t){if(t){var e=[0,0,0],i=1,n=t.match(/^#([a-fA-F0-9]{3,4})$/i),a="";if(n){a=(n=n[1])[3];for(var o=0;oi?(e+.05)/(i+.05):(i+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb;return(299*t[0]+587*t[1]+114*t[2])/1e3<128},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;e<3;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){var e=this.values.hsl;return e[2]+=e[2]*t,this.setValues("hsl",e),this},darken:function(t){var e=this.values.hsl;return e[2]-=e[2]*t,this.setValues("hsl",e),this},saturate:function(t){var e=this.values.hsl;return e[1]+=e[1]*t,this.setValues("hsl",e),this},desaturate:function(t){var e=this.values.hsl;return e[1]-=e[1]*t,this.setValues("hsl",e),this},whiten:function(t){var e=this.values.hwb;return e[1]+=e[1]*t,this.setValues("hwb",e),this},blacken:function(t){var e=this.values.hwb;return e[2]+=e[2]*t,this.setValues("hwb",e),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){var e=this.values.alpha;return this.setValues("alpha",e-e*t),this},opaquer:function(t){var e=this.values.alpha;return this.setValues("alpha",e+e*t),this},rotate:function(t){var e=this.values.hsl,i=(e[0]+t)%360;return e[0]=i<0?360+i:i,this.setValues("hsl",e),this},mix:function(t,e){var i=t,n=void 0===e?.5:e,a=2*n-1,o=this.alpha()-i.alpha(),r=((a*o==-1?a:(a+o)/(1+a*o))+1)/2,s=1-r;return this.rgb(r*this.red()+s*i.red(),r*this.green()+s*i.green(),r*this.blue()+s*i.blue()).alpha(this.alpha()*n+i.alpha()*(1-n))},toJSON:function(){return this.rgb()},clone:function(){var t,e,i=new Y,n=this.values,a=i.values;for(var o in n)n.hasOwnProperty(o)&&(t=n[o],"[object Array]"===(e={}.toString.call(t))?a[o]=t.slice(0):"[object Number]"===e?a[o]=t:console.error("unexpected color value:",t));return i}},Y.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},Y.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},Y.prototype.getValues=function(t){for(var e=this.values,i={},n=0;n=0;a--)e.call(i,t[a],a);else for(a=0;a=1?t:-(Math.sqrt(1-t*t)-1)},easeOutCirc:function(t){return Math.sqrt(1-(t-=1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,i=0,n=1;return 0===t?0:1===t?1:(i||(i=.3),n<1?(n=1,e=i/4):e=i/(2*Math.PI)*Math.asin(1/n),-n*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/i))},easeOutElastic:function(t){var e=1.70158,i=0,n=1;return 0===t?0:1===t?1:(i||(i=.3),n<1?(n=1,e=i/4):e=i/(2*Math.PI)*Math.asin(1/n),n*Math.pow(2,-10*t)*Math.sin((t-e)*(2*Math.PI)/i)+1)},easeInOutElastic:function(t){var e=1.70158,i=0,n=1;return 0===t?0:2==(t/=.5)?1:(i||(i=.45),n<1?(n=1,e=i/4):e=i/(2*Math.PI)*Math.asin(1/n),t<1?n*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/i)*-.5:n*Math.pow(2,-10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/i)*.5+1)},easeInBack:function(t){var e=1.70158;return t*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:function(t){return 1-Z.easeOutBounce(1-t)},easeOutBounce:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},easeInOutBounce:function(t){return t<.5?.5*Z.easeInBounce(2*t):.5*Z.easeOutBounce(2*t-1)+.5}},$={effects:Z};G.easingEffects=Z;var J=Math.PI,Q=J/180,tt=2*J,et=J/2,it=J/4,nt=2*J/3,at={clear:function(t){t.ctx.clearRect(0,0,t.width,t.height)},roundedRect:function(t,e,i,n,a,o){if(o){var r=Math.min(o,a/2,n/2),s=e+r,l=i+r,d=e+n-r,u=i+a-r;t.moveTo(e,l),se.left-1e-6&&t.xe.top-1e-6&&t.y0&&this.requestAnimationFrame()},advance:function(){for(var t,e,i,n,a=this.animations,o=0;o=i?(ut.callback(t.onAnimationComplete,[t],e),e.animating=!1,a.splice(o,1)):++o}},xt=ut.options.resolve,yt=["push","pop","shift","splice","unshift"];function kt(t,e){var i=t._chartjs;if(i){var n=i.listeners,a=n.indexOf(e);-1!==a&&n.splice(a,1),n.length>0||(yt.forEach(function(e){delete t[e]}),delete t._chartjs)}}var wt=function(t,e){this.initialize(t,e)};ut.extend(wt.prototype,{datasetElementType:null,dataElementType:null,initialize:function(t,e){this.chart=t,this.index=e,this.linkScales(),this.addElements()},updateIndex:function(t){this.index=t},linkScales:function(){var t=this,e=t.getMeta(),i=t.getDataset();null!==e.xAxisID&&e.xAxisID in t.chart.scales||(e.xAxisID=i.xAxisID||t.chart.options.scales.xAxes[0].id),null!==e.yAxisID&&e.yAxisID in t.chart.scales||(e.yAxisID=i.yAxisID||t.chart.options.scales.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(t){return this.chart.scales[t]},_getValueScaleId:function(){return this.getMeta().yAxisID},_getIndexScaleId:function(){return this.getMeta().xAxisID},_getValueScale:function(){return this.getScaleForId(this._getValueScaleId())},_getIndexScale:function(){return this.getScaleForId(this._getIndexScaleId())},reset:function(){this.update(!0)},destroy:function(){this._data&&kt(this._data,this)},createMetaDataset:function(){var t=this.datasetElementType;return t&&new t({_chart:this.chart,_datasetIndex:this.index})},createMetaData:function(t){var e=this.dataElementType;return e&&new e({_chart:this.chart,_datasetIndex:this.index,_index:t})},addElements:function(){var t,e,i=this.getMeta(),n=this.getDataset().data||[],a=i.data;for(t=0,e=n.length;ti&&this.insertElements(i,n-i)},insertElements:function(t,e){for(var i=0;is;)a-=2*Math.PI;for(;a=r&&a<=s,d=o>=i.innerRadius&&o<=i.outerRadius;return l&&d}return!1},getCenterPoint:function(){var t=this._view,e=(t.startAngle+t.endAngle)/2,i=(t.innerRadius+t.outerRadius)/2;return{x:t.x+Math.cos(e)*i,y:t.y+Math.sin(e)*i}},getArea:function(){var t=this._view;return Math.PI*((t.endAngle-t.startAngle)/(2*Math.PI))*(Math.pow(t.outerRadius,2)-Math.pow(t.innerRadius,2))},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,i=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*i,y:t.y+Math.sin(e)*i}},draw:function(){var t,e=this._chart.ctx,i=this._view,n=i.startAngle,a=i.endAngle,o="inner"===i.borderAlign?.33:0;e.save(),e.beginPath(),e.arc(i.x,i.y,Math.max(i.outerRadius-o,0),n,a),e.arc(i.x,i.y,i.innerRadius,a,n,!0),e.closePath(),e.fillStyle=i.backgroundColor,e.fill(),i.borderWidth&&("inner"===i.borderAlign?(e.beginPath(),t=o/i.outerRadius,e.arc(i.x,i.y,i.outerRadius,n-t,a+t),i.innerRadius>o?(t=o/i.innerRadius,e.arc(i.x,i.y,i.innerRadius-o,a+t,n-t,!0)):e.arc(i.x,i.y,o,a+Math.PI/2,n-Math.PI/2),e.closePath(),e.clip(),e.beginPath(),e.arc(i.x,i.y,i.outerRadius,n,a),e.arc(i.x,i.y,i.innerRadius,a,n,!0),e.closePath(),e.lineWidth=2*i.borderWidth,e.lineJoin="round"):(e.lineWidth=i.borderWidth,e.lineJoin="bevel"),e.strokeStyle=i.borderColor,e.stroke()),e.restore()}}),Ct=ut.valueOrDefault,St=st.global.defaultColor;st._set("global",{elements:{line:{tension:.4,backgroundColor:St,borderWidth:3,borderColor:St,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",capBezierPoints:!0,fill:!0}}});var Pt=pt.extend({draw:function(){var t,e,i,n,a=this._view,o=this._chart.ctx,r=a.spanGaps,s=this._children.slice(),l=st.global,d=l.elements.line,u=-1;for(this._loop&&s.length&&s.push(s[0]),o.save(),o.lineCap=a.borderCapStyle||d.borderCapStyle,o.setLineDash&&o.setLineDash(a.borderDash||d.borderDash),o.lineDashOffset=Ct(a.borderDashOffset,d.borderDashOffset),o.lineJoin=a.borderJoinStyle||d.borderJoinStyle,o.lineWidth=Ct(a.borderWidth,d.borderWidth),o.strokeStyle=a.borderColor||l.defaultColor,o.beginPath(),u=-1,t=0;tt.x&&(e=Ot(e,"left","right")):t.basei?i:n,r:l.right||a<0?0:a>e?e:a,b:l.bottom||o<0?0:o>i?i:o,l:l.left||r<0?0:r>e?e:r}}function Bt(t,e,i){var n=null===e,a=null===i,o=!(!t||n&&a)&&Rt(t);return o&&(n||e>=o.left&&e<=o.right)&&(a||i>=o.top&&i<=o.bottom)}st._set("global",{elements:{rectangle:{backgroundColor:Ft,borderColor:Ft,borderSkipped:"bottom",borderWidth:0}}});var Nt=pt.extend({draw:function(){var t=this._chart.ctx,e=this._view,i=function(t){var e=Rt(t),i=e.right-e.left,n=e.bottom-e.top,a=zt(t,i/2,n/2);return{outer:{x:e.left,y:e.top,w:i,h:n},inner:{x:e.left+a.l,y:e.top+a.t,w:i-a.l-a.r,h:n-a.t-a.b}}}(e),n=i.outer,a=i.inner;t.fillStyle=e.backgroundColor,t.fillRect(n.x,n.y,n.w,n.h),n.w===a.w&&n.h===a.h||(t.save(),t.beginPath(),t.rect(n.x,n.y,n.w,n.h),t.clip(),t.fillStyle=e.borderColor,t.rect(a.x,a.y,a.w,a.h),t.fill("evenodd"),t.restore())},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){return Bt(this._view,t,e)},inLabelRange:function(t,e){var i=this._view;return Lt(i)?Bt(i,t,null):Bt(i,null,e)},inXRange:function(t){return Bt(this._view,t,null)},inYRange:function(t){return Bt(this._view,null,t)},getCenterPoint:function(){var t,e,i=this._view;return Lt(i)?(t=i.x,e=(i.y+i.base)/2):(t=(i.x+i.base)/2,e=i.y),{x:t,y:e}},getArea:function(){var t=this._view;return Lt(t)?t.width*Math.abs(t.y-t.base):t.height*Math.abs(t.x-t.base)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}}),Wt={},Vt=_t,Et=Pt,Ht=Tt,jt=Nt;Wt.Arc=Vt,Wt.Line=Et,Wt.Point=Ht,Wt.Rectangle=jt;var qt=ut.options.resolve;st._set("bar",{hover:{mode:"label"},scales:{xAxes:[{type:"category",categoryPercentage:.8,barPercentage:.9,offset:!0,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}});var Yt=Mt.extend({dataElementType:Wt.Rectangle,initialize:function(){var t;Mt.prototype.initialize.apply(this,arguments),(t=this.getMeta()).stack=this.getDataset().stack,t.bar=!0},update:function(t){var e,i,n=this.getMeta().data;for(this._ruler=this.getRuler(),e=0,i=n.length;e0?Math.min(r,n-i):r,i=n;return r}(i,l):-1,pixels:l,start:r,end:s,stackCount:n,scale:i}},calculateBarValuePixels:function(t,e){var i,n,a,o,r,s,l=this.chart,d=this.getMeta(),u=this._getValueScale(),h=u.isHorizontal(),c=l.data.datasets,f=+u.getRightValue(c[t].data[e]),g=u.options.minBarLength,p=u.options.stacked,m=d.stack,v=0;if(p||void 0===p&&void 0!==m)for(i=0;i=0&&a>0)&&(v+=a));return o=u.getPixelForValue(v),s=(r=u.getPixelForValue(v+f))-o,void 0!==g&&Math.abs(s)=0&&!h||f<0&&h?o-g:o+g),{size:s,base:o,head:r,center:r+s/2}},calculateBarIndexPixels:function(t,e,i){var n=i.scale.options,a="flex"===n.barThickness?function(t,e,i){var n,a=e.pixels,o=a[t],r=t>0?a[t-1]:null,s=t');var i=t.data,n=i.datasets,a=i.labels;if(n.length)for(var o=0;o'),a[o]&&e.push(a[o]),e.push("");return e.push("
    "),e.join("")},legend:{labels:{generateLabels:function(t){var e=t.data;return e.labels.length&&e.datasets.length?e.labels.map(function(i,n){var a=t.getDatasetMeta(0),o=e.datasets[0],r=a.data[n],s=r&&r.custom||{},l=t.options.elements.arc;return{text:i,fillStyle:Gt([s.backgroundColor,o.backgroundColor,l.backgroundColor],void 0,n),strokeStyle:Gt([s.borderColor,o.borderColor,l.borderColor],void 0,n),lineWidth:Gt([s.borderWidth,o.borderWidth,l.borderWidth],void 0,n),hidden:isNaN(o.data[n])||a.data[n].hidden,index:n}}):[]}},onClick:function(t,e){var i,n,a,o=e.index,r=this.chart;for(i=0,n=(r.data.datasets||[]).length;i=Math.PI?-1:m<-Math.PI?1:0))+g,b={x:Math.cos(m),y:Math.sin(m)},x={x:Math.cos(v),y:Math.sin(v)},y=m<=0&&v>=0||m<=2*Math.PI&&2*Math.PI<=v,k=m<=.5*Math.PI&&.5*Math.PI<=v||m<=2.5*Math.PI&&2.5*Math.PI<=v,w=m<=-Math.PI&&-Math.PI<=v||m<=Math.PI&&Math.PI<=v,M=m<=.5*-Math.PI&&.5*-Math.PI<=v||m<=1.5*Math.PI&&1.5*Math.PI<=v,_=f/100,C={x:w?-1:Math.min(b.x*(b.x<0?1:_),x.x*(x.x<0?1:_)),y:M?-1:Math.min(b.y*(b.y<0?1:_),x.y*(x.y<0?1:_))},S={x:y?1:Math.max(b.x*(b.x>0?1:_),x.x*(x.x>0?1:_)),y:k?1:Math.max(b.y*(b.y>0?1:_),x.y*(x.y>0?1:_))},P={width:.5*(S.x-C.x),height:.5*(S.y-C.y)};d=Math.min(s/P.width,l/P.height),u={x:-.5*(S.x+C.x),y:-.5*(S.y+C.y)}}for(e=0,i=c.length;e0&&!isNaN(t)?2*Math.PI*(Math.abs(t)/e):0},getMaxBorderWidth:function(t){var e,i,n,a,o,r,s,l,d=0,u=this.chart;if(!t)for(e=0,i=u.data.datasets.length;e(d=s>d?s:d)?l:d);return d},setHoverStyle:function(t){var e=t._model,i=t._options,n=ut.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth},e.backgroundColor=Zt(i.hoverBackgroundColor,n(i.backgroundColor)),e.borderColor=Zt(i.hoverBorderColor,n(i.borderColor)),e.borderWidth=Zt(i.hoverBorderWidth,i.borderWidth)},_resolveElementOptions:function(t,e){var i,n,a,o=this.chart,r=this.getDataset(),s=t.custom||{},l=o.options.elements.arc,d={},u={chart:o,dataIndex:e,dataset:r,datasetIndex:this.index},h=["backgroundColor","borderColor","borderWidth","borderAlign","hoverBackgroundColor","hoverBorderColor","hoverBorderWidth"];for(i=0,n=h.length;i0&&ee(l[t-1]._model,s)&&(i.controlPointPreviousX=d(i.controlPointPreviousX,s.left,s.right),i.controlPointPreviousY=d(i.controlPointPreviousY,s.top,s.bottom)),t');var i=t.data,n=i.datasets,a=i.labels;if(n.length)for(var o=0;o'),a[o]&&e.push(a[o]),e.push("");return e.push("
"),e.join("")},legend:{labels:{generateLabels:function(t){var e=t.data;return e.labels.length&&e.datasets.length?e.labels.map(function(i,n){var a=t.getDatasetMeta(0),o=e.datasets[0],r=a.data[n].custom||{},s=t.options.elements.arc;return{text:i,fillStyle:ae([r.backgroundColor,o.backgroundColor,s.backgroundColor],void 0,n),strokeStyle:ae([r.borderColor,o.borderColor,s.borderColor],void 0,n),lineWidth:ae([r.borderWidth,o.borderWidth,s.borderWidth],void 0,n),hidden:isNaN(o.data[n])||a.data[n].hidden,index:n}}):[]}},onClick:function(t,e){var i,n,a,o=e.index,r=this.chart;for(i=0,n=(r.data.datasets||[]).length;i0&&(o=t.getDatasetMeta(o[0]._datasetIndex).data),o},"x-axis":function(t,e){return me(t,e,{intersect:!1})},point:function(t,e){return fe(t,he(e,t))},nearest:function(t,e,i){var n=he(e,t);i.axis=i.axis||"xy";var a=pe(i.axis);return ge(t,n,i.intersect,a)},x:function(t,e,i){var n=he(e,t),a=[],o=!1;return ce(t,function(t){t.inXRange(n.x)&&a.push(t),t.inRange(n.x,n.y)&&(o=!0)}),i.intersect&&!o&&(a=[]),a},y:function(t,e,i){var n=he(e,t),a=[],o=!1;return ce(t,function(t){t.inYRange(n.y)&&a.push(t),t.inRange(n.x,n.y)&&(o=!0)}),i.intersect&&!o&&(a=[]),a}}};function be(t,e){return ut.where(t,function(t){return t.position===e})}function xe(t,e){t.forEach(function(t,e){return t._tmpIndex_=e,t}),t.sort(function(t,i){var n=e?i:t,a=e?t:i;return n.weight===a.weight?n._tmpIndex_-a._tmpIndex_:n.weight-a.weight}),t.forEach(function(t){delete t._tmpIndex_})}function ye(t,e){ut.each(t,function(t){e[t.position]+=t.isHorizontal()?t.height:t.width})}st._set("global",{layout:{padding:{top:0,right:0,bottom:0,left:0}}});var ke={defaults:{},addBox:function(t,e){t.boxes||(t.boxes=[]),e.fullWidth=e.fullWidth||!1,e.position=e.position||"top",e.weight=e.weight||0,t.boxes.push(e)},removeBox:function(t,e){var i=t.boxes?t.boxes.indexOf(e):-1;-1!==i&&t.boxes.splice(i,1)},configure:function(t,e,i){for(var n,a=["fullWidth","position","weight"],o=a.length,r=0;rdiv{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}"}))&&we.default||we,_e="$chartjs",Ce="chartjs-size-monitor",Se="chartjs-render-monitor",Pe="chartjs-render-animation",Ie=["animationstart","webkitAnimationStart"],Ae={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"};function De(t,e){var i=ut.getStyle(t,e),n=i&&i.match(/^(\d+)(\.\d+)?px$/);return n?Number(n[1]):void 0}var Te=!!function(){var t=!1;try{var e=Object.defineProperty({},"passive",{get:function(){t=!0}});window.addEventListener("e",null,e)}catch(t){}return t}()&&{passive:!0};function Fe(t,e,i){t.addEventListener(e,i,Te)}function Le(t,e,i){t.removeEventListener(e,i,Te)}function Re(t,e,i,n,a){return{type:t,chart:e,native:a||null,x:void 0!==i?i:null,y:void 0!==n?n:null}}function Oe(t){var e=document.createElement("div");return e.className=t||"",e}function ze(t,e,i){var n,a,o,r,s=t[_e]||(t[_e]={}),l=s.resizer=function(t){var e=Oe(Ce),i=Oe(Ce+"-expand"),n=Oe(Ce+"-shrink");i.appendChild(Oe()),n.appendChild(Oe()),e.appendChild(i),e.appendChild(n),e._reset=function(){i.scrollLeft=1e6,i.scrollTop=1e6,n.scrollLeft=1e6,n.scrollTop=1e6};var a=function(){e._reset(),t()};return Fe(i,"scroll",a.bind(i,"expand")),Fe(n,"scroll",a.bind(n,"shrink")),e}((n=function(){if(s.resizer){var n=i.options.maintainAspectRatio&&t.parentNode,a=n?n.clientWidth:0;e(Re("resize",i)),n&&n.clientWidth0){var o=t[0];o.label?i=o.label:o.xLabel?i=o.xLabel:a>0&&o.index-1?t.split("\n"):t}function Xe(t){var e=st.global;return{xPadding:t.xPadding,yPadding:t.yPadding,xAlign:t.xAlign,yAlign:t.yAlign,bodyFontColor:t.bodyFontColor,_bodyFontFamily:je(t.bodyFontFamily,e.defaultFontFamily),_bodyFontStyle:je(t.bodyFontStyle,e.defaultFontStyle),_bodyAlign:t.bodyAlign,bodyFontSize:je(t.bodyFontSize,e.defaultFontSize),bodySpacing:t.bodySpacing,titleFontColor:t.titleFontColor,_titleFontFamily:je(t.titleFontFamily,e.defaultFontFamily),_titleFontStyle:je(t.titleFontStyle,e.defaultFontStyle),titleFontSize:je(t.titleFontSize,e.defaultFontSize),_titleAlign:t.titleAlign,titleSpacing:t.titleSpacing,titleMarginBottom:t.titleMarginBottom,footerFontColor:t.footerFontColor,_footerFontFamily:je(t.footerFontFamily,e.defaultFontFamily),_footerFontStyle:je(t.footerFontStyle,e.defaultFontStyle),footerFontSize:je(t.footerFontSize,e.defaultFontSize),_footerAlign:t.footerAlign,footerSpacing:t.footerSpacing,footerMarginTop:t.footerMarginTop,caretSize:t.caretSize,cornerRadius:t.cornerRadius,backgroundColor:t.backgroundColor,opacity:0,legendColorBackground:t.multiKeyBackground,displayColors:t.displayColors,borderColor:t.borderColor,borderWidth:t.borderWidth}}function Ke(t,e){return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-t.xPadding:t.x+t.xPadding}function Ge(t){return Ye([],Ue(t))}var Ze=pt.extend({initialize:function(){this._model=Xe(this._options),this._lastActive=[]},getTitle:function(){var t=this._options.callbacks,e=t.beforeTitle.apply(this,arguments),i=t.title.apply(this,arguments),n=t.afterTitle.apply(this,arguments),a=[];return a=Ye(a,Ue(e)),a=Ye(a,Ue(i)),a=Ye(a,Ue(n))},getBeforeBody:function(){return Ge(this._options.callbacks.beforeBody.apply(this,arguments))},getBody:function(t,e){var i=this,n=i._options.callbacks,a=[];return ut.each(t,function(t){var o={before:[],lines:[],after:[]};Ye(o.before,Ue(n.beforeLabel.call(i,t,e))),Ye(o.lines,n.label.call(i,t,e)),Ye(o.after,Ue(n.afterLabel.call(i,t,e))),a.push(o)}),a},getAfterBody:function(){return Ge(this._options.callbacks.afterBody.apply(this,arguments))},getFooter:function(){var t=this._options.callbacks,e=t.beforeFooter.apply(this,arguments),i=t.footer.apply(this,arguments),n=t.afterFooter.apply(this,arguments),a=[];return a=Ye(a,Ue(e)),a=Ye(a,Ue(i)),a=Ye(a,Ue(n))},update:function(t){var e,i,n,a,o,r,s,l,d,u,h=this,c=h._options,f=h._model,g=h._model=Xe(c),p=h._active,m=h._data,v={xAlign:f.xAlign,yAlign:f.yAlign},b={x:f.x,y:f.y},x={width:f.width,height:f.height},y={x:f.caretX,y:f.caretY};if(p.length){g.opacity=1;var k=[],w=[];y=qe[c.position].call(h,p,h._eventPosition);var M=[];for(e=0,i=p.length;en.width&&(a=n.width-e.width),a<0&&(a=0)),"top"===u?o+=h:o-="bottom"===u?e.height+h:e.height/2,"center"===u?"left"===d?a+=h:"right"===d&&(a-=h):"left"===d?a-=c:"right"===d&&(a+=c),{x:a,y:o}}(g,x,v=function(t,e){var i,n,a,o,r,s=t._model,l=t._chart,d=t._chart.chartArea,u="center",h="center";s.yl.height-e.height&&(h="bottom");var c=(d.left+d.right)/2,f=(d.top+d.bottom)/2;"center"===h?(i=function(t){return t<=c},n=function(t){return t>c}):(i=function(t){return t<=e.width/2},n=function(t){return t>=l.width-e.width/2}),a=function(t){return t+e.width+s.caretSize+s.caretPadding>l.width},o=function(t){return t-e.width-s.caretSize-s.caretPadding<0},r=function(t){return t<=f?"top":"bottom"},i(s.x)?(u="left",a(s.x)&&(u="center",h=r(s.y))):n(s.x)&&(u="right",o(s.x)&&(u="center",h=r(s.y)));var g=t._options;return{xAlign:g.xAlign?g.xAlign:u,yAlign:g.yAlign?g.yAlign:h}}(this,x),h._chart)}else g.opacity=0;return g.xAlign=v.xAlign,g.yAlign=v.yAlign,g.x=b.x,g.y=b.y,g.width=x.width,g.height=x.height,g.caretX=y.x,g.caretY=y.y,h._model=g,t&&c.custom&&c.custom.call(h,g),h},drawCaret:function(t,e){var i=this._chart.ctx,n=this._view,a=this.getCaretPosition(t,e,n);i.lineTo(a.x1,a.y1),i.lineTo(a.x2,a.y2),i.lineTo(a.x3,a.y3)},getCaretPosition:function(t,e,i){var n,a,o,r,s,l,d=i.caretSize,u=i.cornerRadius,h=i.xAlign,c=i.yAlign,f=t.x,g=t.y,p=e.width,m=e.height;if("center"===c)s=g+m/2,"left"===h?(a=(n=f)-d,o=n,r=s+d,l=s-d):(a=(n=f+p)+d,o=n,r=s-d,l=s+d);else if("left"===h?(n=(a=f+u+d)-d,o=a+d):"right"===h?(n=(a=f+p-u-d)-d,o=a+d):(n=(a=i.caretX)-d,o=a+d),"top"===c)s=(r=g)-d,l=r;else{s=(r=g+m)+d,l=r;var v=o;o=n,n=v}return{x1:n,x2:a,x3:o,y1:r,y2:s,y3:l}},drawTitle:function(t,e,i){var n=e.title;if(n.length){t.x=Ke(e,e._titleAlign),i.textAlign=e._titleAlign,i.textBaseline="top";var a,o,r=e.titleFontSize,s=e.titleSpacing;for(i.fillStyle=e.titleFontColor,i.font=ut.fontString(r,e._titleFontStyle,e._titleFontFamily),a=0,o=n.length;a0&&i.stroke()},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var i={width:e.width,height:e.height},n={x:e.x,y:e.y},a=Math.abs(e.opacity<.001)?0:e.opacity,o=e.title.length||e.beforeBody.length||e.body.length||e.afterBody.length||e.footer.length;this._options.enabled&&o&&(t.save(),t.globalAlpha=a,this.drawBackground(n,e,t,i),n.y+=e.yPadding,this.drawTitle(n,e,t),this.drawBody(n,e,t),this.drawFooter(n,e,t),t.restore())}},handleEvent:function(t){var e,i=this,n=i._options;return i._lastActive=i._lastActive||[],"mouseout"===t.type?i._active=[]:i._active=i._chart.getElementsAtEventForMode(t,n.mode,n),(e=!ut.arrayEquals(i._active,i._lastActive))&&(i._lastActive=i._active,(n.enabled||n.custom)&&(i._eventPosition={x:t.x,y:t.y},i.update(!0),i.pivot())),e}}),$e=qe,Je=Ze;Je.positioners=$e;var Qe=ut.valueOrDefault;function ti(){return ut.merge({},[].slice.call(arguments),{merger:function(t,e,i,n){if("xAxes"===t||"yAxes"===t){var a,o,r,s=i[t].length;for(e[t]||(e[t]=[]),a=0;a=e[t].length&&e[t].push({}),!e[t][a].type||r.type&&r.type!==e[t][a].type?ut.merge(e[t][a],[He.getScaleDefaults(o),r]):ut.merge(e[t][a],r)}else ut._merger(t,e,i,n)}})}function ei(){return ut.merge({},[].slice.call(arguments),{merger:function(t,e,i,n){var a=e[t]||{},o=i[t];"scales"===t?e[t]=ti(a,o):"scale"===t?e[t]=ut.merge(a,[He.getScaleDefaults(o.type),o]):ut._merger(t,e,i,n)}})}function ii(t){return"top"===t||"bottom"===t}st._set("global",{elements:{},events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"nearest",intersect:!0,animationDuration:400},onClick:null,maintainAspectRatio:!0,responsive:!0,responsiveAnimationDuration:0});var ni=function(t,e){return this.construct(t,e),this};ut.extend(ni.prototype,{construct:function(t,e){var i=this;e=function(t){var e=(t=t||{}).data=t.data||{};return e.datasets=e.datasets||[],e.labels=e.labels||[],t.options=ei(st.global,st[t.type],t.options||{}),t}(e);var n=Ve.acquireContext(t,e),a=n&&n.canvas,o=a&&a.height,r=a&&a.width;i.id=ut.uid(),i.ctx=n,i.canvas=a,i.config=e,i.width=r,i.height=o,i.aspectRatio=o?r/o:null,i.options=e.options,i._bufferedRender=!1,i.chart=i,i.controller=i,ni.instances[i.id]=i,Object.defineProperty(i,"data",{get:function(){return i.config.data},set:function(t){i.config.data=t}}),n&&a?(i.initialize(),i.update()):console.error("Failed to create chart: can't acquire context from the given item")},initialize:function(){var t=this;return Ee.notify(t,"beforeInit"),ut.retinaScale(t,t.options.devicePixelRatio),t.bindEvents(),t.options.responsive&&t.resize(!0),t.ensureScalesHaveIDs(),t.buildOrUpdateScales(),t.initToolTip(),Ee.notify(t,"afterInit"),t},clear:function(){return ut.canvas.clear(this),this},stop:function(){return bt.cancelAnimation(this),this},resize:function(t){var e=this,i=e.options,n=e.canvas,a=i.maintainAspectRatio&&e.aspectRatio||null,o=Math.max(0,Math.floor(ut.getMaximumWidth(n))),r=Math.max(0,Math.floor(a?o/a:ut.getMaximumHeight(n)));if((e.width!==o||e.height!==r)&&(n.width=e.width=o,n.height=e.height=r,n.style.width=o+"px",n.style.height=r+"px",ut.retinaScale(e,i.devicePixelRatio),!t)){var s={width:o,height:r};Ee.notify(e,"resize",[s]),i.onResize&&i.onResize(e,s),e.stop(),e.update({duration:i.responsiveAnimationDuration})}},ensureScalesHaveIDs:function(){var t=this.options,e=t.scales||{},i=t.scale;ut.each(e.xAxes,function(t,e){t.id=t.id||"x-axis-"+e}),ut.each(e.yAxes,function(t,e){t.id=t.id||"y-axis-"+e}),i&&(i.id=i.id||"scale")},buildOrUpdateScales:function(){var t=this,e=t.options,i=t.scales||{},n=[],a=Object.keys(i).reduce(function(t,e){return t[e]=!1,t},{});e.scales&&(n=n.concat((e.scales.xAxes||[]).map(function(t){return{options:t,dtype:"category",dposition:"bottom"}}),(e.scales.yAxes||[]).map(function(t){return{options:t,dtype:"linear",dposition:"left"}}))),e.scale&&n.push({options:e.scale,dtype:"radialLinear",isDefault:!0,dposition:"chartArea"}),ut.each(n,function(e){var n=e.options,o=n.id,r=Qe(n.type,e.dtype);ii(n.position)!==ii(e.dposition)&&(n.position=e.dposition),a[o]=!0;var s=null;if(o in i&&i[o].type===r)(s=i[o]).options=n,s.ctx=t.ctx,s.chart=t;else{var l=He.getScaleConstructor(r);if(!l)return;s=new l({id:o,type:r,options:n,ctx:t.ctx,chart:t}),i[s.id]=s}s.mergeTicksOptions(),e.isDefault&&(t.scale=s)}),ut.each(a,function(t,e){t||delete i[e]}),t.scales=i,He.addScalesToLayout(this)},buildOrUpdateControllers:function(){var t=this,e=[];return ut.each(t.data.datasets,function(i,n){var a=t.getDatasetMeta(n),o=i.type||t.config.type;if(a.type&&a.type!==o&&(t.destroyDatasetMeta(n),a=t.getDatasetMeta(n)),a.type=o,a.controller)a.controller.updateIndex(n),a.controller.linkScales();else{var r=ue[a.type];if(void 0===r)throw new Error('"'+a.type+'" is not a chart type.');a.controller=new r(t,n),e.push(a.controller)}},t),e},resetElements:function(){var t=this;ut.each(t.data.datasets,function(e,i){t.getDatasetMeta(i).controller.reset()},t)},reset:function(){this.resetElements(),this.tooltip.initialize()},update:function(t){var e,i,n=this;if(t&&"object"==typeof t||(t={duration:t,lazy:arguments[1]}),i=(e=n).options,ut.each(e.scales,function(t){ke.removeBox(e,t)}),i=ei(st.global,st[e.config.type],i),e.options=e.config.options=i,e.ensureScalesHaveIDs(),e.buildOrUpdateScales(),e.tooltip._options=i.tooltips,e.tooltip.initialize(),Ee._invalidate(n),!1!==Ee.notify(n,"beforeUpdate")){n.tooltip._data=n.data;var a=n.buildOrUpdateControllers();ut.each(n.data.datasets,function(t,e){n.getDatasetMeta(e).controller.buildOrUpdateElements()},n),n.updateLayout(),n.options.animation&&n.options.animation.duration&&ut.each(a,function(t){t.reset()}),n.updateDatasets(),n.tooltip.initialize(),n.lastActive=[],Ee.notify(n,"afterUpdate"),n._bufferedRender?n._bufferedRequest={duration:t.duration,easing:t.easing,lazy:t.lazy}:n.render(t)}},updateLayout:function(){!1!==Ee.notify(this,"beforeLayout")&&(ke.update(this,this.width,this.height),Ee.notify(this,"afterScaleUpdate"),Ee.notify(this,"afterLayout"))},updateDatasets:function(){if(!1!==Ee.notify(this,"beforeDatasetsUpdate")){for(var t=0,e=this.data.datasets.length;t=0;--i)e.isDatasetVisible(i)&&e.drawDataset(i,t);Ee.notify(e,"afterDatasetsDraw",[t])}},drawDataset:function(t,e){var i=this.getDatasetMeta(t),n={meta:i,index:t,easingValue:e};!1!==Ee.notify(this,"beforeDatasetDraw",[n])&&(i.controller.draw(e),Ee.notify(this,"afterDatasetDraw",[n]))},_drawTooltip:function(t){var e=this.tooltip,i={tooltip:e,easingValue:t};!1!==Ee.notify(this,"beforeTooltipDraw",[i])&&(e.draw(),Ee.notify(this,"afterTooltipDraw",[i]))},getElementAtEvent:function(t){return ve.modes.single(this,t)},getElementsAtEvent:function(t){return ve.modes.label(this,t,{intersect:!0})},getElementsAtXAxis:function(t){return ve.modes["x-axis"](this,t,{intersect:!0})},getElementsAtEventForMode:function(t,e,i){var n=ve.modes[e];return"function"==typeof n?n(this,t,i):[]},getDatasetAtEvent:function(t){return ve.modes.dataset(this,t,{intersect:!0})},getDatasetMeta:function(t){var e=this.data.datasets[t];e._meta||(e._meta={});var i=e._meta[this.id];return i||(i=e._meta[this.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null}),i},getVisibleDatasetCount:function(){for(var t=0,e=0,i=this.data.datasets.length;e3?i[2]-i[1]:i[1]-i[0];Math.abs(n)>1&&t!==Math.floor(t)&&(n=t-Math.floor(t));var a=ut.log10(Math.abs(n)),o="";if(0!==t)if(Math.max(Math.abs(i[0]),Math.abs(i[i.length-1]))<1e-4){var r=ut.log10(Math.abs(t));o=t.toExponential(Math.floor(r)-Math.floor(a))}else{var s=-1*Math.floor(a);s=Math.max(Math.min(s,20),0),o=t.toFixed(s)}else o="0";return o},logarithmic:function(t,e,i){var n=t/Math.pow(10,Math.floor(ut.log10(t)));return 0===t?"0":1===n||2===n||5===n||0===e||e===i.length-1?t.toExponential():""}}},di=ut.valueOrDefault,ui=ut.valueAtIndexOrDefault;function hi(t){var e,i,n=[];for(e=0,i=t.length;ed&&ot.maxHeight){o--;break}o++,l=r*s}t.labelRotation=o},afterCalculateTickRotation:function(){ut.callback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){ut.callback(this.options.beforeFit,[this])},fit:function(){var t=this,e=t.minSize={width:0,height:0},i=hi(t._ticks),n=t.options,a=n.ticks,o=n.scaleLabel,r=n.gridLines,s=t._isVisible(),l=n.position,d=t.isHorizontal(),u=ut.options._parseFont,h=u(a),c=n.gridLines.tickMarkLength;if(e.width=d?t.isFullWidth()?t.maxWidth-t.margins.left-t.margins.right:t.maxWidth:s&&r.drawTicks?c:0,e.height=d?s&&r.drawTicks?c:0:t.maxHeight,o.display&&s){var f=u(o),g=ut.options.toPadding(o.padding),p=f.lineHeight+g.height;d?e.height+=p:e.width+=p}if(a.display&&s){var m=ut.longestText(t.ctx,h.string,i,t.longestTextCache),v=ut.numberOfLabelLines(i),b=.5*h.size,x=t.options.ticks.padding;if(t._maxLabelLines=v,t.longestLabelWidth=m,d){var y=ut.toRadians(t.labelRotation),k=Math.cos(y),w=Math.sin(y)*m+h.lineHeight*v+b;e.height=Math.min(t.maxHeight,e.height+w+x),t.ctx.font=h.string;var M,_,C=ci(t.ctx,i[0],h.string),S=ci(t.ctx,i[i.length-1],h.string),P=t.getPixelForTick(0)-t.left,I=t.right-t.getPixelForTick(i.length-1);0!==t.labelRotation?(M="bottom"===l?k*C:k*b,_="bottom"===l?k*b:k*S):(M=C/2,_=S/2),t.paddingLeft=Math.max(M-P,0)+3,t.paddingRight=Math.max(_-I,0)+3}else a.mirror?m=0:m+=x+b,e.width=Math.min(t.maxWidth,e.width+m),t.paddingTop=h.size/2,t.paddingBottom=h.size/2}t.handleMargins(),t.width=e.width,t.height=e.height},handleMargins:function(){var t=this;t.margins&&(t.paddingLeft=Math.max(t.paddingLeft-t.margins.left,0),t.paddingTop=Math.max(t.paddingTop-t.margins.top,0),t.paddingRight=Math.max(t.paddingRight-t.margins.right,0),t.paddingBottom=Math.max(t.paddingBottom-t.margins.bottom,0))},afterFit:function(){ut.callback(this.options.afterFit,[this])},isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(t){if(ut.isNullOrUndef(t))return NaN;if(("number"==typeof t||t instanceof Number)&&!isFinite(t))return NaN;if(t)if(this.isHorizontal()){if(void 0!==t.x)return this.getRightValue(t.x)}else if(void 0!==t.y)return this.getRightValue(t.y);return t},getLabelForIndex:ut.noop,getPixelForValue:ut.noop,getValueForPixel:ut.noop,getPixelForTick:function(t){var e=this,i=e.options.offset;if(e.isHorizontal()){var n=(e.width-(e.paddingLeft+e.paddingRight))/Math.max(e._ticks.length-(i?0:1),1),a=n*t+e.paddingLeft;i&&(a+=n/2);var o=e.left+a;return o+=e.isFullWidth()?e.margins.left:0}var r=e.height-(e.paddingTop+e.paddingBottom);return e.top+t*(r/(e._ticks.length-1))},getPixelForDecimal:function(t){var e=this;if(e.isHorizontal()){var i=(e.width-(e.paddingLeft+e.paddingRight))*t+e.paddingLeft,n=e.left+i;return n+=e.isFullWidth()?e.margins.left:0}return e.top+t*e.height},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var t=this.min,e=this.max;return this.beginAtZero?0:t<0&&e<0?e:t>0&&e>0?t:0},_autoSkip:function(t){var e,i,n=this,a=n.isHorizontal(),o=n.options.ticks.minor,r=t.length,s=!1,l=o.maxTicksLimit,d=n._tickSize()*(r-1),u=a?n.width-(n.paddingLeft+n.paddingRight):n.height-(n.paddingTop+n.PaddingBottom),h=[];for(d>u&&(s=1+Math.floor(d/u)),r>l&&(s=Math.max(s,1+Math.floor(r/l))),e=0;e1&&e%s>0&&delete i.label,h.push(i);return h},_tickSize:function(){var t=this,e=t.isHorizontal(),i=t.options.ticks.minor,n=ut.toRadians(t.labelRotation),a=Math.abs(Math.cos(n)),o=Math.abs(Math.sin(n)),r=i.autoSkipPadding||0,s=t.longestLabelWidth+r||0,l=ut.options._parseFont(i),d=t._maxLabelLines*l.lineHeight+r||0;return e?d*a>s*o?s/a:d/o:d*o0&&n>0&&(t.min=0)}var a=void 0!==e.min||void 0!==e.suggestedMin,o=void 0!==e.max||void 0!==e.suggestedMax;void 0!==e.min?t.min=e.min:void 0!==e.suggestedMin&&(null===t.min?t.min=e.suggestedMin:t.min=Math.min(t.min,e.suggestedMin)),void 0!==e.max?t.max=e.max:void 0!==e.suggestedMax&&(null===t.max?t.max=e.suggestedMax:t.max=Math.max(t.max,e.suggestedMax)),a!==o&&t.min>=t.max&&(a?t.max=t.min+1:t.min=t.max-1),t.min===t.max&&(t.max++,e.beginAtZero||t.min--)},getTickLimit:function(){var t,e=this.options.ticks,i=e.stepSize,n=e.maxTicksLimit;return i?t=Math.ceil(this.max/i)-Math.floor(this.min/i)+1:(t=this._computeTickLimit(),n=n||11),n&&(t=Math.min(n,t)),t},_computeTickLimit:function(){return Number.POSITIVE_INFINITY},handleDirectionalChanges:mi,buildTicks:function(){var t=this,e=t.options.ticks,i=t.getTickLimit(),n={maxTicks:i=Math.max(2,i),min:e.min,max:e.max,precision:e.precision,stepSize:ut.valueOrDefault(e.fixedStepSize,e.stepSize)},a=t.ticks=function(t,e){var i,n,a,o,r=[],s=t.stepSize,l=s||1,d=t.maxTicks-1,u=t.min,h=t.max,c=t.precision,f=e.min,g=e.max,p=ut.niceNum((g-f)/d/l)*l;if(p<1e-14&&vi(u)&&vi(h))return[f,g];(o=Math.ceil(g/p)-Math.floor(f/p))>d&&(p=ut.niceNum(o*p/d/l)*l),s||vi(c)?i=Math.pow(10,ut._decimalPlaces(p)):(i=Math.pow(10,c),p=Math.ceil(p*i)/i),n=Math.floor(f/p)*p,a=Math.ceil(g/p)*p,s&&(!vi(u)&&ut.almostWhole(u/p,p/1e3)&&(n=u),!vi(h)&&ut.almostWhole(h/p,p/1e3)&&(a=h)),o=(a-n)/p,o=ut.almostEquals(o,Math.round(o),p/1e3)?Math.round(o):Math.ceil(o),n=Math.round(n*i)/i,a=Math.round(a*i)/i,r.push(vi(u)?n:u);for(var m=1;mt.max&&(t.max=n))})});t.min=isFinite(t.min)&&!isNaN(t.min)?t.min:0,t.max=isFinite(t.max)&&!isNaN(t.max)?t.max:1,this.handleTickRangeOptions()},_computeTickLimit:function(){var t;return this.isHorizontal()?Math.ceil(this.width/40):(t=ut.options._parseFont(this.options.ticks),Math.ceil(this.height/t.lineHeight))},handleDirectionalChanges:function(){this.isHorizontal()||this.ticks.reverse()},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},getPixelForValue:function(t){var e=this,i=e.start,n=+e.getRightValue(t),a=e.end-i;return e.isHorizontal()?e.left+e.width/a*(n-i):e.bottom-e.height/a*(n-i)},getValueForPixel:function(t){var e=this,i=e.isHorizontal(),n=i?e.width:e.height,a=(i?t-e.left:e.bottom-t)/n;return e.start+(e.end-e.start)*a},getPixelForTick:function(t){return this.getPixelForValue(this.ticksAsNumbers[t])}}),ki=xi;yi._defaults=ki;var wi=ut.valueOrDefault;var Mi={position:"left",ticks:{callback:li.formatters.logarithmic}};function _i(t,e){return ut.isFinite(t)&&t>=0?t:e}var Ci=fi.extend({determineDataLimits:function(){var t=this,e=t.options,i=t.chart,n=i.data.datasets,a=t.isHorizontal();function o(e){return a?e.xAxisID===t.id:e.yAxisID===t.id}t.min=null,t.max=null,t.minNotZero=null;var r=e.stacked;if(void 0===r&&ut.each(n,function(t,e){if(!r){var n=i.getDatasetMeta(e);i.isDatasetVisible(e)&&o(n)&&void 0!==n.stack&&(r=!0)}}),e.stacked||r){var s={};ut.each(n,function(n,a){var r=i.getDatasetMeta(a),l=[r.type,void 0===e.stacked&&void 0===r.stack?a:"",r.stack].join(".");i.isDatasetVisible(a)&&o(r)&&(void 0===s[l]&&(s[l]=[]),ut.each(n.data,function(e,i){var n=s[l],a=+t.getRightValue(e);isNaN(a)||r.data[i].hidden||a<0||(n[i]=n[i]||0,n[i]+=a)}))}),ut.each(s,function(e){if(e.length>0){var i=ut.min(e),n=ut.max(e);t.min=null===t.min?i:Math.min(t.min,i),t.max=null===t.max?n:Math.max(t.max,n)}})}else ut.each(n,function(e,n){var a=i.getDatasetMeta(n);i.isDatasetVisible(n)&&o(a)&&ut.each(e.data,function(e,i){var n=+t.getRightValue(e);isNaN(n)||a.data[i].hidden||n<0||(null===t.min?t.min=n:nt.max&&(t.max=n),0!==n&&(null===t.minNotZero||n0?t.minNotZero=t.min:t.max<1?t.minNotZero=Math.pow(10,Math.floor(ut.log10(t.max))):t.minNotZero=1)},buildTicks:function(){var t=this,e=t.options.ticks,i=!t.isHorizontal(),n={min:_i(e.min),max:_i(e.max)},a=t.ticks=function(t,e){var i,n,a=[],o=wi(t.min,Math.pow(10,Math.floor(ut.log10(e.min)))),r=Math.floor(ut.log10(e.max)),s=Math.ceil(e.max/Math.pow(10,r));0===o?(i=Math.floor(ut.log10(e.minNotZero)),n=Math.floor(e.minNotZero/Math.pow(10,i)),a.push(o),o=n*Math.pow(10,i)):(i=Math.floor(ut.log10(o)),n=Math.floor(o/Math.pow(10,i)));var l=i<0?Math.pow(10,Math.abs(i)):1;do{a.push(o),10==++n&&(n=1,l=++i>=0?1:l),o=Math.round(n*Math.pow(10,i)*l)/l}while(ia?{start:e-i,end:e}:{start:e,end:e+i}}function Ri(t){return 0===t||180===t?"center":t<180?"left":"right"}function Oi(t,e,i,n){var a,o,r=i.y+n/2;if(ut.isArray(e))for(a=0,o=e.length;a270||t<90)&&(i.y-=e.h)}function Bi(t){return ut.isNumber(t)?t:0}var Ni=bi.extend({setDimensions:function(){var t=this;t.width=t.maxWidth,t.height=t.maxHeight,t.paddingTop=Fi(t.options)/2,t.xCenter=Math.floor(t.width/2),t.yCenter=Math.floor((t.height-t.paddingTop)/2),t.drawingArea=Math.min(t.height-t.paddingTop,t.width)/2},determineDataLimits:function(){var t=this,e=t.chart,i=Number.POSITIVE_INFINITY,n=Number.NEGATIVE_INFINITY;ut.each(e.data.datasets,function(a,o){if(e.isDatasetVisible(o)){var r=e.getDatasetMeta(o);ut.each(a.data,function(e,a){var o=+t.getRightValue(e);isNaN(o)||r.data[a].hidden||(i=Math.min(o,i),n=Math.max(o,n))})}}),t.min=i===Number.POSITIVE_INFINITY?0:i,t.max=n===Number.NEGATIVE_INFINITY?0:n,t.handleTickRangeOptions()},_computeTickLimit:function(){return Math.ceil(this.drawingArea/Fi(this.options))},convertTicksToLabels:function(){var t=this;bi.prototype.convertTicksToLabels.call(t),t.pointLabels=t.chart.data.labels.map(t.options.pointLabels.callback,t)},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},fit:function(){var t=this.options;t.display&&t.pointLabels.display?function(t){var e,i,n,a=ut.options._parseFont(t.options.pointLabels),o={l:0,r:t.width,t:0,b:t.height-t.paddingTop},r={};t.ctx.font=a.string,t._pointLabelSizes=[];var s,l,d,u=Ti(t);for(e=0;eo.r&&(o.r=f.end,r.r=h),g.starto.b&&(o.b=g.end,r.b=h)}t.setReductions(t.drawingArea,o,r)}(this):this.setCenterPoint(0,0,0,0)},setReductions:function(t,e,i){var n=this,a=e.l/Math.sin(i.l),o=Math.max(e.r-n.width,0)/Math.sin(i.r),r=-e.t/Math.cos(i.t),s=-Math.max(e.b-(n.height-n.paddingTop),0)/Math.cos(i.b);a=Bi(a),o=Bi(o),r=Bi(r),s=Bi(s),n.drawingArea=Math.min(Math.floor(t-(a+o)/2),Math.floor(t-(r+s)/2)),n.setCenterPoint(a,o,r,s)},setCenterPoint:function(t,e,i,n){var a=this,o=a.width-e-a.drawingArea,r=t+a.drawingArea,s=i+a.drawingArea,l=a.height-a.paddingTop-n-a.drawingArea;a.xCenter=Math.floor((r+o)/2+a.left),a.yCenter=Math.floor((s+l)/2+a.top+a.paddingTop)},getIndexAngle:function(t){return t*(2*Math.PI/Ti(this))+(this.chart.options&&this.chart.options.startAngle?this.chart.options.startAngle:0)*Math.PI*2/360},getDistanceFromCenterForValue:function(t){var e=this;if(null===t)return 0;var i=e.drawingArea/(e.max-e.min);return e.options.ticks.reverse?(e.max-t)*i:(t-e.min)*i},getPointPosition:function(t,e){var i=this.getIndexAngle(t)-Math.PI/2;return{x:Math.cos(i)*e+this.xCenter,y:Math.sin(i)*e+this.yCenter}},getPointPositionForValue:function(t,e){return this.getPointPosition(t,this.getDistanceFromCenterForValue(e))},getBasePosition:function(){var t=this.min,e=this.max;return this.getPointPositionForValue(0,this.beginAtZero?0:t<0&&e<0?e:t>0&&e>0?t:0)},draw:function(){var t=this,e=t.options,i=e.gridLines,n=e.ticks;if(e.display){var a=t.ctx,o=this.getIndexAngle(0),r=ut.options._parseFont(n);(e.angleLines.display||e.pointLabels.display)&&function(t){var e=t.ctx,i=t.options,n=i.angleLines,a=i.gridLines,o=i.pointLabels,r=Pi(n.lineWidth,a.lineWidth),s=Pi(n.color,a.color),l=Fi(i);e.save(),e.lineWidth=r,e.strokeStyle=s,e.setLineDash&&(e.setLineDash(Ai([n.borderDash,a.borderDash,[]])),e.lineDashOffset=Ai([n.borderDashOffset,a.borderDashOffset,0]));var d=t.getDistanceFromCenterForValue(i.ticks.reverse?t.min:t.max),u=ut.options._parseFont(o);e.font=u.string,e.textBaseline="middle";for(var h=Ti(t)-1;h>=0;h--){if(n.display&&r&&s){var c=t.getPointPosition(h,d);e.beginPath(),e.moveTo(t.xCenter,t.yCenter),e.lineTo(c.x,c.y),e.stroke()}if(o.display){var f=0===h?l/2:0,g=t.getPointPosition(h,d+f+5),p=Ii(o.fontColor,h,st.global.defaultFontColor);e.fillStyle=p;var m=t.getIndexAngle(h),v=ut.toDegrees(m);e.textAlign=Ri(v),zi(v,t._pointLabelSizes[h],g),Oi(e,t.pointLabels[h]||"",g,u.lineHeight)}}e.restore()}(t),ut.each(t.ticks,function(e,s){if(s>0||n.reverse){var l=t.getDistanceFromCenterForValue(t.ticksAsNumbers[s]);if(i.display&&0!==s&&function(t,e,i,n){var a,o=t.ctx,r=e.circular,s=Ti(t),l=Ii(e.color,n-1),d=Ii(e.lineWidth,n-1);if((r||s)&&l&&d){if(o.save(),o.strokeStyle=l,o.lineWidth=d,o.setLineDash&&(o.setLineDash(e.borderDash||[]),o.lineDashOffset=e.borderDashOffset||0),o.beginPath(),r)o.arc(t.xCenter,t.yCenter,i,0,2*Math.PI);else{a=t.getPointPosition(0,i),o.moveTo(a.x,a.y);for(var u=1;u=0&&r<=s;){if(a=t[(n=r+s>>1)-1]||null,o=t[n],!a)return{lo:null,hi:o};if(o[e]i))return{lo:a,hi:o};s=n-1}}return{lo:o,hi:null}}(t,e,i),o=a.lo?a.hi?a.lo:t[t.length-2]:t[0],r=a.lo?a.hi?a.hi:t[t.length-1]:t[1],s=r[e]-o[e],l=s?(i-o[e])/s:0,d=(r[n]-o[n])*l;return o[n]+d}function Ki(t,e){var i=t._adapter,n=t.options.time,a=n.parser,o=a||n.format,r=e;return"function"==typeof a&&(r=a(r)),ut.isFinite(r)||(r="string"==typeof o?i.parse(r,o):i.parse(r)),null!==r?+r:(a||"function"!=typeof o||(r=o(e),ut.isFinite(r)||(r=i.parse(r))),r)}function Gi(t,e){if(ut.isNullOrUndef(e))return null;var i=t.options.time,n=Ki(t,t.getRightValue(e));return null===n?n:(i.round&&(n=+t._adapter.startOf(n,i.round)),n)}function Zi(t){for(var e=qi.indexOf(t)+1,i=qi.length;e=a&&i<=o&&d.push(i);return n.min=a,n.max=o,n._unit=s.unit||function(t,e,i,n,a){var o,r;for(o=qi.length-1;o>=qi.indexOf(i);o--)if(r=qi[o],ji[r].common&&t._adapter.diff(a,n,r)>=e.length)return r;return qi[i?qi.indexOf(i):0]}(n,d,s.minUnit,n.min,n.max),n._majorUnit=Zi(n._unit),n._table=function(t,e,i,n){if("linear"===n||!t.length)return[{time:e,pos:0},{time:i,pos:1}];var a,o,r,s,l,d=[],u=[e];for(a=0,o=t.length;ae&&s=0&&t0?r:1}}),Qi={position:"bottom",distribution:"linear",bounds:"data",adapters:{},time:{parser:!1,format:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{autoSkip:!1,source:"auto",major:{enabled:!1}}};Ji._defaults=Qi;var tn={category:gi,linear:yi,logarithmic:Ci,radialLinear:Ni,time:Ji},en={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};si._date.override("function"==typeof t?{_id:"moment",formats:function(){return en},parse:function(e,i){return"string"==typeof e&&"string"==typeof i?e=t(e,i):e instanceof t||(e=t(e)),e.isValid()?e.valueOf():null},format:function(e,i){return t(e).format(i)},add:function(e,i,n){return t(e).add(i,n).valueOf()},diff:function(e,i,n){return t.duration(t(e).diff(t(i))).as(n)},startOf:function(e,i,n){return e=t(e),"isoWeek"===i?e.isoWeekday(n).valueOf():e.startOf(i).valueOf()},endOf:function(e,i){return t(e).endOf(i).valueOf()},_create:function(e){return t(e)}}:{}),st._set("global",{plugins:{filler:{propagate:!0}}});var nn={dataset:function(t){var e=t.fill,i=t.chart,n=i.getDatasetMeta(e),a=n&&i.isDatasetVisible(e)&&n.dataset._children||[],o=a.length||0;return o?function(t,e){return e=i)&&n;switch(o){case"bottom":return"start";case"top":return"end";case"zero":return"origin";case"origin":case"start":case"end":return o;default:return!1}}function on(t){var e,i=t.el._model||{},n=t.el._scale||{},a=t.fill,o=null;if(isFinite(a))return null;if("start"===a?o=void 0===i.scaleBottom?n.bottom:i.scaleBottom:"end"===a?o=void 0===i.scaleTop?n.top:i.scaleTop:void 0!==i.scaleZero?o=i.scaleZero:n.getBasePosition?o=n.getBasePosition():n.getBasePixel&&(o=n.getBasePixel()),null!=o){if(void 0!==o.x&&void 0!==o.y)return o;if(ut.isFinite(o))return{x:(e=n.isHorizontal())?o:null,y:e?null:o}}return null}function rn(t,e,i){var n,a=t[e].fill,o=[e];if(!i)return a;for(;!1!==a&&-1===o.indexOf(a);){if(!isFinite(a))return a;if(!(n=t[a]))return!1;if(n.visible)return a;o.push(a),a=n.fill}return!1}function sn(t){var e=t.fill,i="dataset";return!1===e?null:(isFinite(e)||(i="boundary"),nn[i](t))}function ln(t){return t&&!t.skip}function dn(t,e,i,n,a){var o;if(n&&a){for(t.moveTo(e[0].x,e[0].y),o=1;o0;--o)ut.canvas.lineTo(t,i[o],i[o-1],!0)}}var un={id:"filler",afterDatasetsUpdate:function(t,e){var i,n,a,o,r=(t.data.datasets||[]).length,s=e.propagate,l=[];for(n=0;ne?e:t.boxWidth}st._set("global",{legend:{display:!0,position:"top",fullWidth:!0,reverse:!1,weight:1e3,onClick:function(t,e){var i=e.datasetIndex,n=this.chart,a=n.getDatasetMeta(i);a.hidden=null===a.hidden?!n.data.datasets[i].hidden:null,n.update()},onHover:null,onLeave:null,labels:{boxWidth:40,padding:10,generateLabels:function(t){var e=t.data;return ut.isArray(e.datasets)?e.datasets.map(function(e,i){return{text:e.label,fillStyle:ut.isArray(e.backgroundColor)?e.backgroundColor[0]:e.backgroundColor,hidden:!t.isDatasetVisible(i),lineCap:e.borderCapStyle,lineDash:e.borderDash,lineDashOffset:e.borderDashOffset,lineJoin:e.borderJoinStyle,lineWidth:e.borderWidth,strokeStyle:e.borderColor,pointStyle:e.pointStyle,datasetIndex:i}},this):[]}}},legendCallback:function(t){var e=[];e.push('
    ');for(var i=0;i'),t.data.datasets[i].label&&e.push(t.data.datasets[i].label),e.push("");return e.push("
"),e.join("")}});var gn=pt.extend({initialize:function(t){ut.extend(this,t),this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1},beforeUpdate:hn,update:function(t,e,i){var n=this;return n.beforeUpdate(),n.maxWidth=t,n.maxHeight=e,n.margins=i,n.beforeSetDimensions(),n.setDimensions(),n.afterSetDimensions(),n.beforeBuildLabels(),n.buildLabels(),n.afterBuildLabels(),n.beforeFit(),n.fit(),n.afterFit(),n.afterUpdate(),n.minSize},afterUpdate:hn,beforeSetDimensions:hn,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:hn,beforeBuildLabels:hn,buildLabels:function(){var t=this,e=t.options.labels||{},i=ut.callback(e.generateLabels,[t.chart],t)||[];e.filter&&(i=i.filter(function(i){return e.filter(i,t.chart.data)})),t.options.reverse&&i.reverse(),t.legendItems=i},afterBuildLabels:hn,beforeFit:hn,fit:function(){var t=this,e=t.options,i=e.labels,n=e.display,a=t.ctx,o=ut.options._parseFont(i),r=o.size,s=t.legendHitBoxes=[],l=t.minSize,d=t.isHorizontal();if(d?(l.width=t.maxWidth,l.height=n?10:0):(l.width=n?10:0,l.height=t.maxHeight),n)if(a.font=o.string,d){var u=t.lineWidths=[0],h=0;a.textAlign="left",a.textBaseline="top",ut.each(t.legendItems,function(t,e){var n=fn(i,r)+r/2+a.measureText(t.text).width;(0===e||u[u.length-1]+n+i.padding>l.width)&&(h+=r+i.padding,u[u.length-(e>0?0:1)]=i.padding),s[e]={left:0,top:0,width:n,height:r},u[u.length-1]+=n+i.padding}),l.height+=h}else{var c=i.padding,f=t.columnWidths=[],g=i.padding,p=0,m=0,v=r+c;ut.each(t.legendItems,function(t,e){var n=fn(i,r)+r/2+a.measureText(t.text).width;e>0&&m+v>l.height-c&&(g+=p+i.padding,f.push(p),p=0,m=0),p=Math.max(p,n),m+=v,s[e]={left:0,top:0,width:n,height:r}}),g+=p,f.push(p),l.width+=g}t.width=l.width,t.height=l.height},afterFit:hn,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var t=this,e=t.options,i=e.labels,n=st.global,a=n.defaultColor,o=n.elements.line,r=t.width,s=t.lineWidths;if(e.display){var l,d=t.ctx,u=cn(i.fontColor,n.defaultFontColor),h=ut.options._parseFont(i),c=h.size;d.textAlign="left",d.textBaseline="middle",d.lineWidth=.5,d.strokeStyle=u,d.fillStyle=u,d.font=h.string;var f=fn(i,c),g=t.legendHitBoxes,p=t.isHorizontal();l=p?{x:t.left+(r-s[0])/2+i.padding,y:t.top+i.padding,line:0}:{x:t.left+i.padding,y:t.top+i.padding,line:0};var m=c+i.padding;ut.each(t.legendItems,function(n,u){var h=d.measureText(n.text).width,v=f+c/2+h,b=l.x,x=l.y;p?u>0&&b+v+i.padding>t.left+t.minSize.width&&(x=l.y+=m,l.line++,b=l.x=t.left+(r-s[l.line])/2+i.padding):u>0&&x+m>t.top+t.minSize.height&&(b=l.x=b+t.columnWidths[l.line]+i.padding,x=l.y=t.top+i.padding,l.line++),function(t,i,n){if(!(isNaN(f)||f<=0)){d.save();var r=cn(n.lineWidth,o.borderWidth);if(d.fillStyle=cn(n.fillStyle,a),d.lineCap=cn(n.lineCap,o.borderCapStyle),d.lineDashOffset=cn(n.lineDashOffset,o.borderDashOffset),d.lineJoin=cn(n.lineJoin,o.borderJoinStyle),d.lineWidth=r,d.strokeStyle=cn(n.strokeStyle,a),d.setLineDash&&d.setLineDash(cn(n.lineDash,o.borderDash)),e.labels&&e.labels.usePointStyle){var s=f*Math.SQRT2/2,l=t+f/2,u=i+c/2;ut.canvas.drawPoint(d,n.pointStyle,s,l,u)}else 0!==r&&d.strokeRect(t,i,f,c),d.fillRect(t,i,f,c);d.restore()}}(b,x,n),g[u].left=b,g[u].top=x,function(t,e,i,n){var a=c/2,o=f+a+t,r=e+a;d.fillText(i.text,o,r),i.hidden&&(d.beginPath(),d.lineWidth=2,d.moveTo(o,r),d.lineTo(o+n,r),d.stroke())}(b,x,n,h),p?l.x+=v+i.padding:l.y+=m})}},_getLegendItemAt:function(t,e){var i,n,a,o=this;if(t>=o.left&&t<=o.right&&e>=o.top&&e<=o.bottom)for(a=o.legendHitBoxes,i=0;i=(n=a[i]).left&&t<=n.left+n.width&&e>=n.top&&e<=n.top+n.height)return o.legendItems[i];return null},handleEvent:function(t){var e,i=this,n=i.options,a="mouseup"===t.type?"click":t.type;if("mousemove"===a){if(!n.onHover&&!n.onLeave)return}else{if("click"!==a)return;if(!n.onClick)return}e=i._getLegendItemAt(t.x,t.y),"click"===a?e&&n.onClick&&n.onClick.call(i,t.native,e):(n.onLeave&&e!==i._hoveredItem&&(i._hoveredItem&&n.onLeave.call(i,t.native,i._hoveredItem),i._hoveredItem=e),n.onHover&&e&&n.onHover.call(i,t.native,e))}});function pn(t,e){var i=new gn({ctx:t.ctx,options:e,chart:t});ke.configure(t,i,e),ke.addBox(t,i),t.legend=i}var mn={id:"legend",_element:gn,beforeInit:function(t){var e=t.options.legend;e&&pn(t,e)},beforeUpdate:function(t){var e=t.options.legend,i=t.legend;e?(ut.mergeIf(e,st.global.legend),i?(ke.configure(t,i,e),i.options=e):pn(t,e)):i&&(ke.removeBox(t,i),delete t.legend)},afterEvent:function(t,e){var i=t.legend;i&&i.handleEvent(e)}},vn=ut.noop;st._set("global",{title:{display:!1,fontStyle:"bold",fullWidth:!0,padding:10,position:"top",text:"",weight:2e3}});var bn=pt.extend({initialize:function(t){ut.extend(this,t),this.legendHitBoxes=[]},beforeUpdate:vn,update:function(t,e,i){var n=this;return n.beforeUpdate(),n.maxWidth=t,n.maxHeight=e,n.margins=i,n.beforeSetDimensions(),n.setDimensions(),n.afterSetDimensions(),n.beforeBuildLabels(),n.buildLabels(),n.afterBuildLabels(),n.beforeFit(),n.fit(),n.afterFit(),n.afterUpdate(),n.minSize},afterUpdate:vn,beforeSetDimensions:vn,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:vn,beforeBuildLabels:vn,buildLabels:vn,afterBuildLabels:vn,beforeFit:vn,fit:function(){var t=this,e=t.options,i=e.display,n=t.minSize,a=ut.isArray(e.text)?e.text.length:1,o=ut.options._parseFont(e),r=i?a*o.lineHeight+2*e.padding:0;t.isHorizontal()?(n.width=t.maxWidth,n.height=r):(n.width=r,n.height=t.maxHeight),t.width=n.width,t.height=n.height},afterFit:vn,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var t=this,e=t.ctx,i=t.options;if(i.display){var n,a,o,r=ut.options._parseFont(i),s=r.lineHeight,l=s/2+i.padding,d=0,u=t.top,h=t.left,c=t.bottom,f=t.right;e.fillStyle=ut.valueOrDefault(i.fontColor,st.global.defaultFontColor),e.font=r.string,t.isHorizontal()?(a=h+(f-h)/2,o=u+l,n=f-h):(a="left"===i.position?h+l:f-l,o=u+(c-u)/2,n=c-u,d=Math.PI*("left"===i.position?-.5:.5)),e.save(),e.translate(a,o),e.rotate(d),e.textAlign="center",e.textBaseline="middle";var g=i.text;if(ut.isArray(g))for(var p=0,m=0;m=0;n--){var a=t[n];if(e(a))return a}},ut.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},ut.almostEquals=function(t,e,i){return Math.abs(t-e)t},ut.max=function(t){return t.reduce(function(t,e){return isNaN(e)?t:Math.max(t,e)},Number.NEGATIVE_INFINITY)},ut.min=function(t){return t.reduce(function(t,e){return isNaN(e)?t:Math.min(t,e)},Number.POSITIVE_INFINITY)},ut.sign=Math.sign?function(t){return Math.sign(t)}:function(t){return 0==(t=+t)||isNaN(t)?t:t>0?1:-1},ut.log10=Math.log10?function(t){return Math.log10(t)}:function(t){var e=Math.log(t)*Math.LOG10E,i=Math.round(e);return t===Math.pow(10,i)?i:e},ut.toRadians=function(t){return t*(Math.PI/180)},ut.toDegrees=function(t){return t*(180/Math.PI)},ut._decimalPlaces=function(t){if(ut.isFinite(t)){for(var e=1,i=0;Math.round(t*e)/e!==t;)e*=10,i++;return i}},ut.getAngleFromPoint=function(t,e){var i=e.x-t.x,n=e.y-t.y,a=Math.sqrt(i*i+n*n),o=Math.atan2(n,i);return o<-.5*Math.PI&&(o+=2*Math.PI),{angle:o,distance:a}},ut.distanceBetweenPoints=function(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))},ut.aliasPixel=function(t){return t%2==0?0:.5},ut._alignPixel=function(t,e,i){var n=t.currentDevicePixelRatio,a=i/2;return Math.round((e-a)*n)/n+a},ut.splineCurve=function(t,e,i,n){var a=t.skip?e:t,o=e,r=i.skip?e:i,s=Math.sqrt(Math.pow(o.x-a.x,2)+Math.pow(o.y-a.y,2)),l=Math.sqrt(Math.pow(r.x-o.x,2)+Math.pow(r.y-o.y,2)),d=s/(s+l),u=l/(s+l),h=n*(d=isNaN(d)?0:d),c=n*(u=isNaN(u)?0:u);return{previous:{x:o.x-h*(r.x-a.x),y:o.y-h*(r.y-a.y)},next:{x:o.x+c*(r.x-a.x),y:o.y+c*(r.y-a.y)}}},ut.EPSILON=Number.EPSILON||1e-14,ut.splineCurveMonotone=function(t){var e,i,n,a,o,r,s,l,d,u=(t||[]).map(function(t){return{model:t._model,deltaK:0,mK:0}}),h=u.length;for(e=0;e0?u[e-1]:null,(a=e0?u[e-1]:null,a=e=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},ut.previousItem=function(t,e,i){return i?e<=0?t[t.length-1]:t[e-1]:e<=0?t[0]:t[e-1]},ut.niceNum=function(t,e){var i=Math.floor(ut.log10(t)),n=t/Math.pow(10,i);return(e?n<1.5?1:n<3?2:n<7?5:10:n<=1?1:n<=2?2:n<=5?5:10)*Math.pow(10,i)},ut.requestAnimFrame="undefined"==typeof window?function(t){t()}:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)},ut.getRelativePosition=function(t,e){var i,n,a=t.originalEvent||t,o=t.target||t.srcElement,r=o.getBoundingClientRect(),s=a.touches;s&&s.length>0?(i=s[0].clientX,n=s[0].clientY):(i=a.clientX,n=a.clientY);var l=parseFloat(ut.getStyle(o,"padding-left")),d=parseFloat(ut.getStyle(o,"padding-top")),u=parseFloat(ut.getStyle(o,"padding-right")),h=parseFloat(ut.getStyle(o,"padding-bottom")),c=r.right-r.left-l-u,f=r.bottom-r.top-d-h;return{x:i=Math.round((i-r.left-l)/c*o.width/e.currentDevicePixelRatio),y:n=Math.round((n-r.top-d)/f*o.height/e.currentDevicePixelRatio)}},ut.getConstraintWidth=function(t){return i(t,"max-width","clientWidth")},ut.getConstraintHeight=function(t){return i(t,"max-height","clientHeight")},ut._calculatePadding=function(t,e,i){return(e=ut.getStyle(t,e)).indexOf("%")>-1?i*parseInt(e,10)/100:parseInt(e,10)},ut._getParentNode=function(t){var e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e},ut.getMaximumWidth=function(t){var e=ut._getParentNode(t);if(!e)return t.clientWidth;var i=e.clientWidth,n=i-ut._calculatePadding(e,"padding-left",i)-ut._calculatePadding(e,"padding-right",i),a=ut.getConstraintWidth(t);return isNaN(a)?n:Math.min(n,a)},ut.getMaximumHeight=function(t){var e=ut._getParentNode(t);if(!e)return t.clientHeight;var i=e.clientHeight,n=i-ut._calculatePadding(e,"padding-top",i)-ut._calculatePadding(e,"padding-bottom",i),a=ut.getConstraintHeight(t);return isNaN(a)?n:Math.min(n,a)},ut.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},ut.retinaScale=function(t,e){var i=t.currentDevicePixelRatio=e||"undefined"!=typeof window&&window.devicePixelRatio||1;if(1!==i){var n=t.canvas,a=t.height,o=t.width;n.height=a*i,n.width=o*i,t.ctx.scale(i,i),n.style.height||n.style.width||(n.style.height=a+"px",n.style.width=o+"px")}},ut.fontString=function(t,e,i){return e+" "+t+"px "+i},ut.longestText=function(t,e,i,n){var a=(n=n||{}).data=n.data||{},o=n.garbageCollect=n.garbageCollect||[];n.font!==e&&(a=n.data={},o=n.garbageCollect=[],n.font=e),t.font=e;var r=0;ut.each(i,function(e){null!=e&&!0!==ut.isArray(e)?r=ut.measureText(t,a,o,r,e):ut.isArray(e)&&ut.each(e,function(e){null==e||ut.isArray(e)||(r=ut.measureText(t,a,o,r,e))})});var s=o.length/2;if(s>i.length){for(var l=0;ln&&(n=o),n},ut.numberOfLabelLines=function(t){var e=1;return ut.each(t,function(t){ut.isArray(t)&&t.length>e&&(e=t.length)}),e},ut.color=X?function(t){return t instanceof CanvasGradient&&(t=st.global.defaultColor),X(t)}:function(t){return console.error("Color.js not found!"),t},ut.getHoverColor=function(t){return t instanceof CanvasPattern||t instanceof CanvasGradient?t:ut.color(t).saturate(.5).darken(.1).rgbString()}}(),ai._adapters=si,ai.Animation=vt,ai.animationService=bt,ai.controllers=ue,ai.DatasetController=Mt,ai.defaults=st,ai.Element=pt,ai.elements=Wt,ai.Interaction=ve,ai.layouts=ke,ai.platform=Ve,ai.plugins=Ee,ai.Scale=fi,ai.scaleService=He,ai.Ticks=li,ai.Tooltip=Je,ai.helpers.each(tn,function(t,e){ai.scaleService.registerScaleType(e,t,t._defaults)}),yn)yn.hasOwnProperty(_n)&&ai.plugins.register(yn[_n]);ai.platform.initialize();var Cn=ai;return"undefined"!=typeof window&&(window.Chart=ai),ai.Chart=ai,ai.Legend=yn.legend._element,ai.Title=yn.title._element,ai.pluginService=ai.plugins,ai.PluginBase=ai.Element.extend({}),ai.canvasHelpers=ai.helpers.canvas,ai.layoutService=ai.layouts,ai.LinearScaleBase=bi,ai.helpers.each(["Bar","Bubble","Doughnut","Line","PolarArea","Radar","Scatter"],function(t){ai[t]=function(e,i){return new ai(e,ai.helpers.merge(i||{},{type:t.charAt(0).toLowerCase()+t.slice(1)}))}}),Cn}); rt-5.0.1/share/static/js/d3.min.js000644 000765 000024 00000744045 14005011336 017452 0ustar00sunnavystaff000000 000000 // https://d3js.org v5.15.1 Copyright 2020 Mike Bostock !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t=t||self).d3=t.d3||{})}(this,function(t){"use strict";function n(t,n){return tn?1:t>=n?0:NaN}function e(t){var e;return 1===t.length&&(e=t,t=function(t,r){return n(e(t),r)}),{left:function(n,e,r,i){for(null==r&&(r=0),null==i&&(i=n.length);r>>1;t(n[o],e)<0?r=o+1:i=o}return r},right:function(n,e,r,i){for(null==r&&(r=0),null==i&&(i=n.length);r>>1;t(n[o],e)>0?i=o:r=o+1}return r}}}var r=e(n),i=r.right,o=r.left;function a(t,n){return[t,n]}function u(t){return null===t?NaN:+t}function c(t,n){var e,r,i=t.length,o=0,a=-1,c=0,f=0;if(null==n)for(;++a1)return f/(o-1)}function f(t,n){var e=c(t,n);return e?Math.sqrt(e):e}function s(t,n){var e,r,i,o=t.length,a=-1;if(null==n){for(;++a=e)for(r=i=e;++ae&&(r=e),i=e)for(r=i=e;++ae&&(r=e),i0)return[t];if((r=n0)for(t=Math.ceil(t/a),n=Math.floor(n/a),o=new Array(i=Math.ceil(n-t+1));++u=0?(o>=y?10:o>=_?5:o>=b?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(o>=y?10:o>=_?5:o>=b?2:1)}function w(t,n,e){var r=Math.abs(n-t)/Math.max(0,e),i=Math.pow(10,Math.floor(Math.log(r)/Math.LN10)),o=r/i;return o>=y?i*=10:o>=_?i*=5:o>=b&&(i*=2),n=1)return+e(t[r-1],r-1,t);var r,i=(r-1)*n,o=Math.floor(i),a=+e(t[o],o,t);return a+(+e(t[o+1],o+1,t)-a)*(i-o)}}function T(t,n){var e,r,i=t.length,o=-1;if(null==n){for(;++o=e)for(r=e;++or&&(r=e)}else for(;++o=e)for(r=e;++or&&(r=e);return r}function A(t){for(var n,e,r,i=t.length,o=-1,a=0;++o=0;)for(n=(r=t[i]).length;--n>=0;)e[--a]=r[n];return e}function S(t,n){var e,r,i=t.length,o=-1;if(null==n){for(;++o=e)for(r=e;++oe&&(r=e)}else for(;++o=e)for(r=e;++oe&&(r=e);return r}function k(t){if(!(i=t.length))return[];for(var n=-1,e=S(t,E),r=new Array(e);++n=0&&(e=t.slice(r+1),t=t.slice(0,r)),t&&!n.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:e}})}function V(t,n){for(var e,r=0,i=t.length;r0)for(var e,r,i=new Array(e),o=0;o=0&&"xmlns"!==(n=t.slice(0,e))&&(t=t.slice(e+1)),$.hasOwnProperty(n)?{space:$[n],local:t}:t}function Z(t){var n=W(t);return(n.local?function(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}:function(t){return function(){var n=this.ownerDocument,e=this.namespaceURI;return e===G&&n.documentElement.namespaceURI===G?n.createElement(t):n.createElementNS(e,t)}})(n)}function Q(){}function K(t){return null==t?Q:function(){return this.querySelector(t)}}function J(){return[]}function tt(t){return null==t?J:function(){return this.querySelectorAll(t)}}function nt(t){return function(){return this.matches(t)}}function et(t){return new Array(t.length)}function rt(t,n){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=n}rt.prototype={constructor:rt,appendChild:function(t){return this._parent.insertBefore(t,this._next)},insertBefore:function(t,n){return this._parent.insertBefore(t,n)},querySelector:function(t){return this._parent.querySelector(t)},querySelectorAll:function(t){return this._parent.querySelectorAll(t)}};var it="$";function ot(t,n,e,r,i,o){for(var a,u=0,c=n.length,f=o.length;un?1:t>=n?0:NaN}function ct(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function ft(t,n){return t.style.getPropertyValue(n)||ct(t).getComputedStyle(t,null).getPropertyValue(n)}function st(t){return t.trim().split(/^|\s+/)}function lt(t){return t.classList||new ht(t)}function ht(t){this._node=t,this._names=st(t.getAttribute("class")||"")}function dt(t,n){for(var e=lt(t),r=-1,i=n.length;++r=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var Mt={};(t.event=null,"undefined"!=typeof document)&&("onmouseenter"in document.documentElement||(Mt={mouseenter:"mouseover",mouseleave:"mouseout"}));function Nt(t,n,e){return t=Tt(t,n,e),function(n){var e=n.relatedTarget;e&&(e===this||8&e.compareDocumentPosition(this))||t.call(this,n)}}function Tt(n,e,r){return function(i){var o=t.event;t.event=i;try{n.call(this,this.__data__,e,r)}finally{t.event=o}}}function At(t){return function(){var n=this.__on;if(n){for(var e,r=0,i=-1,o=n.length;r=m&&(m=b+1);!(_=g[m])&&++m=0;)(r=i[o])&&(a&&4^r.compareDocumentPosition(a)&&a.parentNode.insertBefore(r,a),a=r);return this},sort:function(t){function n(n,e){return n&&e?t(n.__data__,e.__data__):!n-!e}t||(t=ut);for(var e=this._groups,r=e.length,i=new Array(r),o=0;o1?this.each((null==n?function(t){return function(){this.style.removeProperty(t)}}:"function"==typeof n?function(t,n,e){return function(){var r=n.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,e)}}:function(t,n,e){return function(){this.style.setProperty(t,n,e)}})(t,n,null==e?"":e)):ft(this.node(),t)},property:function(t,n){return arguments.length>1?this.each((null==n?function(t){return function(){delete this[t]}}:"function"==typeof n?function(t,n){return function(){var e=n.apply(this,arguments);null==e?delete this[t]:this[t]=e}}:function(t,n){return function(){this[t]=n}})(t,n)):this.node()[t]},classed:function(t,n){var e=st(t+"");if(arguments.length<2){for(var r=lt(this.node()),i=-1,o=e.length;++i=0&&(n=t.slice(e+1),t=t.slice(0,e)),{type:t,name:n}})}(t+""),a=o.length;if(!(arguments.length<2)){for(u=n?St:At,null==e&&(e=!1),r=0;r>8&15|n>>4&240,n>>4&15|240&n,(15&n)<<4|15&n,1):8===e?new bn(n>>24&255,n>>16&255,n>>8&255,(255&n)/255):4===e?new bn(n>>12&15|n>>8&240,n>>8&15|n>>4&240,n>>4&15|240&n,((15&n)<<4|15&n)/255):null):(n=on.exec(t))?new bn(n[1],n[2],n[3],1):(n=an.exec(t))?new bn(255*n[1]/100,255*n[2]/100,255*n[3]/100,1):(n=un.exec(t))?gn(n[1],n[2],n[3],n[4]):(n=cn.exec(t))?gn(255*n[1]/100,255*n[2]/100,255*n[3]/100,n[4]):(n=fn.exec(t))?Mn(n[1],n[2]/100,n[3]/100,1):(n=sn.exec(t))?Mn(n[1],n[2]/100,n[3]/100,n[4]):ln.hasOwnProperty(t)?vn(ln[t]):"transparent"===t?new bn(NaN,NaN,NaN,0):null}function vn(t){return new bn(t>>16&255,t>>8&255,255&t,1)}function gn(t,n,e,r){return r<=0&&(t=n=e=NaN),new bn(t,n,e,r)}function yn(t){return t instanceof Jt||(t=pn(t)),t?new bn((t=t.rgb()).r,t.g,t.b,t.opacity):new bn}function _n(t,n,e,r){return 1===arguments.length?yn(t):new bn(t,n,e,null==r?1:r)}function bn(t,n,e,r){this.r=+t,this.g=+n,this.b=+e,this.opacity=+r}function mn(){return"#"+wn(this.r)+wn(this.g)+wn(this.b)}function xn(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}function wn(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function Mn(t,n,e,r){return r<=0?t=n=e=NaN:e<=0||e>=1?t=n=NaN:n<=0&&(t=NaN),new An(t,n,e,r)}function Nn(t){if(t instanceof An)return new An(t.h,t.s,t.l,t.opacity);if(t instanceof Jt||(t=pn(t)),!t)return new An;if(t instanceof An)return t;var n=(t=t.rgb()).r/255,e=t.g/255,r=t.b/255,i=Math.min(n,e,r),o=Math.max(n,e,r),a=NaN,u=o-i,c=(o+i)/2;return u?(a=n===o?(e-r)/u+6*(e0&&c<1?0:a,new An(a,u,c,t.opacity)}function Tn(t,n,e,r){return 1===arguments.length?Nn(t):new An(t,n,e,null==r?1:r)}function An(t,n,e,r){this.h=+t,this.s=+n,this.l=+e,this.opacity=+r}function Sn(t,n,e){return 255*(t<60?n+(e-n)*t/60:t<180?e:t<240?n+(e-n)*(240-t)/60:n)}Qt(Jt,pn,{copy:function(t){return Object.assign(new this.constructor,this,t)},displayable:function(){return this.rgb().displayable()},hex:hn,formatHex:hn,formatHsl:function(){return Nn(this).formatHsl()},formatRgb:dn,toString:dn}),Qt(bn,_n,Kt(Jt,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new bn(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new bn(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:mn,formatHex:mn,formatRgb:xn,toString:xn})),Qt(An,Tn,Kt(Jt,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new An(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new An(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),n=isNaN(t)||isNaN(this.s)?0:this.s,e=this.l,r=e+(e<.5?e:1-e)*n,i=2*e-r;return new bn(Sn(t>=240?t-240:t+120,i,r),Sn(t,i,r),Sn(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===t?")":", "+t+")")}}));var kn=Math.PI/180,En=180/Math.PI,Cn=.96422,Pn=1,zn=.82521,Rn=4/29,Dn=6/29,qn=3*Dn*Dn,Ln=Dn*Dn*Dn;function Un(t){if(t instanceof Bn)return new Bn(t.l,t.a,t.b,t.opacity);if(t instanceof Xn)return Gn(t);t instanceof bn||(t=yn(t));var n,e,r=Hn(t.r),i=Hn(t.g),o=Hn(t.b),a=Fn((.2225045*r+.7168786*i+.0606169*o)/Pn);return r===i&&i===o?n=e=a:(n=Fn((.4360747*r+.3850649*i+.1430804*o)/Cn),e=Fn((.0139322*r+.0971045*i+.7141733*o)/zn)),new Bn(116*a-16,500*(n-a),200*(a-e),t.opacity)}function On(t,n,e,r){return 1===arguments.length?Un(t):new Bn(t,n,e,null==r?1:r)}function Bn(t,n,e,r){this.l=+t,this.a=+n,this.b=+e,this.opacity=+r}function Fn(t){return t>Ln?Math.pow(t,1/3):t/qn+Rn}function Yn(t){return t>Dn?t*t*t:qn*(t-Rn)}function In(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function Hn(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function jn(t){if(t instanceof Xn)return new Xn(t.h,t.c,t.l,t.opacity);if(t instanceof Bn||(t=Un(t)),0===t.a&&0===t.b)return new Xn(NaN,0=1?(e=1,n-1):Math.floor(e*n),i=t[r],o=t[r+1],a=r>0?t[r-1]:2*i-o,u=r180||e<-180?e-360*Math.round(e/360):e):ue(isNaN(t)?n:t)}function se(t){return 1==(t=+t)?le:function(n,e){return e-n?function(t,n,e){return t=Math.pow(t,e),n=Math.pow(n,e)-t,e=1/e,function(r){return Math.pow(t+r*n,e)}}(n,e,t):ue(isNaN(n)?e:n)}}function le(t,n){var e=n-t;return e?ce(t,e):ue(isNaN(t)?n:t)}Qt(re,ee,Kt(Jt,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new re(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new re(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=isNaN(this.h)?0:(this.h+120)*kn,n=+this.l,e=isNaN(this.s)?0:this.s*n*(1-n),r=Math.cos(t),i=Math.sin(t);return new bn(255*(n+e*($n*r+Wn*i)),255*(n+e*(Zn*r+Qn*i)),255*(n+e*(Kn*r)),this.opacity)}}));var he=function t(n){var e=se(n);function r(t,n){var r=e((t=_n(t)).r,(n=_n(n)).r),i=e(t.g,n.g),o=e(t.b,n.b),a=le(t.opacity,n.opacity);return function(n){return t.r=r(n),t.g=i(n),t.b=o(n),t.opacity=a(n),t+""}}return r.gamma=t,r}(1);function de(t){return function(n){var e,r,i=n.length,o=new Array(i),a=new Array(i),u=new Array(i);for(e=0;eo&&(i=n.slice(o,i),u[a]?u[a]+=i:u[++a]=i),(e=e[0])===(r=r[0])?u[a]?u[a]+=r:u[++a]=r:(u[++a]=null,c.push({i:a,x:me(e,r)})),o=Me.lastIndex;return o180?n+=360:n-t>180&&(t+=360),o.push({i:e.push(i(e)+"rotate(",null,r)-2,x:me(t,n)})):n&&e.push(i(e)+"rotate("+n+r)}(o.rotate,a.rotate,u,c),function(t,n,e,o){t!==n?o.push({i:e.push(i(e)+"skewX(",null,r)-2,x:me(t,n)}):n&&e.push(i(e)+"skewX("+n+r)}(o.skewX,a.skewX,u,c),function(t,n,e,r,o,a){if(t!==e||n!==r){var u=o.push(i(o)+"scale(",null,",",null,")");a.push({i:u-4,x:me(t,e)},{i:u-2,x:me(n,r)})}else 1===e&&1===r||o.push(i(o)+"scale("+e+","+r+")")}(o.scaleX,o.scaleY,a.scaleX,a.scaleY,u,c),o=a=null,function(t){for(var n,e=-1,r=c.length;++e=0&&n._call.call(null,t),n=n._next;--tr}function pr(){or=(ir=ur.now())+ar,tr=nr=0;try{dr()}finally{tr=0,function(){var t,n,e=Ke,r=1/0;for(;e;)e._call?(r>e._time&&(r=e._time),t=e,e=e._next):(n=e._next,e._next=null,e=t?t._next=n:Ke=n);Je=t,gr(r)}(),or=0}}function vr(){var t=ur.now(),n=t-ir;n>rr&&(ar-=n,ir=t)}function gr(t){tr||(nr&&(nr=clearTimeout(nr)),t-or>24?(t<1/0&&(nr=setTimeout(pr,t-ur.now()-ar)),er&&(er=clearInterval(er))):(er||(ir=ur.now(),er=setInterval(vr,rr)),tr=1,cr(pr)))}function yr(t,n,e){var r=new lr;return n=null==n?0:+n,r.restart(function(e){r.stop(),t(e+n)},n,e),r}lr.prototype=hr.prototype={constructor:lr,restart:function(t,n,e){if("function"!=typeof t)throw new TypeError("callback is not a function");e=(null==e?fr():+e)+(null==n?0:+n),this._next||Je===this||(Je?Je._next=this:Ke=this,Je=this),this._call=t,this._time=e,gr()},stop:function(){this._call&&(this._call=null,this._time=1/0,gr())}};var _r=I("start","end","cancel","interrupt"),br=[],mr=0,xr=1,wr=2,Mr=3,Nr=4,Tr=5,Ar=6;function Sr(t,n,e,r,i,o){var a=t.__transition;if(a){if(e in a)return}else t.__transition={};!function(t,n,e){var r,i=t.__transition;function o(c){var f,s,l,h;if(e.state!==xr)return u();for(f in i)if((h=i[f]).name===e.name){if(h.state===Mr)return yr(o);h.state===Nr?(h.state=Ar,h.timer.stop(),h.on.call("interrupt",t,t.__data__,h.index,h.group),delete i[f]):+fmr)throw new Error("too late; already scheduled");return e}function Er(t,n){var e=Cr(t,n);if(e.state>Mr)throw new Error("too late; already running");return e}function Cr(t,n){var e=t.__transition;if(!e||!(e=e[n]))throw new Error("transition not found");return e}function Pr(t,n){var e,r,i,o=t.__transition,a=!0;if(o){for(i in n=null==n?null:n+"",o)(e=o[i]).name===n?(r=e.state>wr&&e.state=0&&(t=t.slice(0,n)),!t||"start"===t})}(n)?kr:Er;return function(){var a=o(this,t),u=a.on;u!==r&&(i=(r=u).copy()).on(n,e),a.on=i}}(e,t,n))},attr:function(t,n){var e=W(t),r="transform"===e?Le:Rr;return this.attrTween(t,"function"==typeof n?(e.local?function(t,n,e){var r,i,o;return function(){var a,u,c=e(this);if(null!=c)return(a=this.getAttributeNS(t.space,t.local))===(u=c+"")?null:a===r&&u===i?o:(i=u,o=n(r=a,c));this.removeAttributeNS(t.space,t.local)}}:function(t,n,e){var r,i,o;return function(){var a,u,c=e(this);if(null!=c)return(a=this.getAttribute(t))===(u=c+"")?null:a===r&&u===i?o:(i=u,o=n(r=a,c));this.removeAttribute(t)}})(e,r,zr(this,"attr."+t,n)):null==n?(e.local?function(t){return function(){this.removeAttributeNS(t.space,t.local)}}:function(t){return function(){this.removeAttribute(t)}})(e):(e.local?function(t,n,e){var r,i,o=e+"";return function(){var a=this.getAttributeNS(t.space,t.local);return a===o?null:a===r?i:i=n(r=a,e)}}:function(t,n,e){var r,i,o=e+"";return function(){var a=this.getAttribute(t);return a===o?null:a===r?i:i=n(r=a,e)}})(e,r,n))},attrTween:function(t,n){var e="attr."+t;if(arguments.length<2)return(e=this.tween(e))&&e._value;if(null==n)return this.tween(e,null);if("function"!=typeof n)throw new Error;var r=W(t);return this.tween(e,(r.local?function(t,n){var e,r;function i(){var i=n.apply(this,arguments);return i!==r&&(e=(r=i)&&function(t,n){return function(e){this.setAttributeNS(t.space,t.local,n.call(this,e))}}(t,i)),e}return i._value=n,i}:function(t,n){var e,r;function i(){var i=n.apply(this,arguments);return i!==r&&(e=(r=i)&&function(t,n){return function(e){this.setAttribute(t,n.call(this,e))}}(t,i)),e}return i._value=n,i})(r,n))},style:function(t,n,e){var r="transform"==(t+="")?qe:Rr;return null==n?this.styleTween(t,function(t,n){var e,r,i;return function(){var o=ft(this,t),a=(this.style.removeProperty(t),ft(this,t));return o===a?null:o===e&&a===r?i:i=n(e=o,r=a)}}(t,r)).on("end.style."+t,qr(t)):"function"==typeof n?this.styleTween(t,function(t,n,e){var r,i,o;return function(){var a=ft(this,t),u=e(this),c=u+"";return null==u&&(this.style.removeProperty(t),c=u=ft(this,t)),a===c?null:a===r&&c===i?o:(i=c,o=n(r=a,u))}}(t,r,zr(this,"style."+t,n))).each(function(t,n){var e,r,i,o,a="style."+n,u="end."+a;return function(){var c=Er(this,t),f=c.on,s=null==c.value[a]?o||(o=qr(n)):void 0;f===e&&i===s||(r=(e=f).copy()).on(u,i=s),c.on=r}}(this._id,t)):this.styleTween(t,function(t,n,e){var r,i,o=e+"";return function(){var a=ft(this,t);return a===o?null:a===r?i:i=n(r=a,e)}}(t,r,n),e).on("end.style."+t,null)},styleTween:function(t,n,e){var r="style."+(t+="");if(arguments.length<2)return(r=this.tween(r))&&r._value;if(null==n)return this.tween(r,null);if("function"!=typeof n)throw new Error;return this.tween(r,function(t,n,e){var r,i;function o(){var o=n.apply(this,arguments);return o!==i&&(r=(i=o)&&function(t,n,e){return function(r){this.style.setProperty(t,n.call(this,r),e)}}(t,o,e)),r}return o._value=n,o}(t,n,null==e?"":e))},text:function(t){return this.tween("text","function"==typeof t?function(t){return function(){var n=t(this);this.textContent=null==n?"":n}}(zr(this,"text",t)):function(t){return function(){this.textContent=t}}(null==t?"":t+""))},textTween:function(t){var n="text";if(arguments.length<1)return(n=this.tween(n))&&n._value;if(null==t)return this.tween(n,null);if("function"!=typeof t)throw new Error;return this.tween(n,function(t){var n,e;function r(){var r=t.apply(this,arguments);return r!==e&&(n=(e=r)&&function(t){return function(n){this.textContent=t.call(this,n)}}(r)),n}return r._value=t,r}(t))},remove:function(){return this.on("end.remove",function(t){return function(){var n=this.parentNode;for(var e in this.__transition)if(+e!==t)return;n&&n.removeChild(this)}}(this._id))},tween:function(t,n){var e=this._id;if(t+="",arguments.length<2){for(var r,i=Cr(this.node(),e).tween,o=0,a=i.length;o0&&(r=o-P),M<0?d=p-z:M>0&&(u=c-z),x=Mi,B.attr("cursor",Pi.selection),I());break;default:return}xi()},!0).on("keyup.brush",function(){switch(t.event.keyCode){case 16:R&&(g=y=R=!1,I());break;case 18:x===Ti&&(w<0?f=h:w>0&&(r=o),M<0?d=p:M>0&&(u=c),x=Ni,I());break;case 32:x===Mi&&(t.event.altKey?(w&&(f=h-P*w,r=o+P*w),M&&(d=p-z*M,u=c+z*M),x=Ti):(w<0?f=h:w>0&&(r=o),M<0?d=p:M>0&&(u=c),x=Ni),B.attr("cursor",Pi[m]),I());break;default:return}xi()},!0),Ht(t.event.view)}mi(),Pr(b),s.call(b),U.start()}function Y(){var t=D(b);!R||g||y||(Math.abs(t[0]-L[0])>Math.abs(t[1]-L[1])?y=!0:g=!0),L=t,v=!0,xi(),I()}function I(){var t;switch(P=L[0]-q[0],z=L[1]-q[1],x){case Mi:case wi:w&&(P=Math.max(S-r,Math.min(E-f,P)),o=r+P,h=f+P),M&&(z=Math.max(k-u,Math.min(C-d,z)),c=u+z,p=d+z);break;case Ni:w<0?(P=Math.max(S-r,Math.min(E-r,P)),o=r+P,h=f):w>0&&(P=Math.max(S-f,Math.min(E-f,P)),o=r,h=f+P),M<0?(z=Math.max(k-u,Math.min(C-u,z)),c=u+z,p=d):M>0&&(z=Math.max(k-d,Math.min(C-d,z)),c=u,p=d+z);break;case Ti:w&&(o=Math.max(S,Math.min(E,r-P*w)),h=Math.max(S,Math.min(E,f+P*w))),M&&(c=Math.max(k,Math.min(C,u-z*M)),p=Math.max(k,Math.min(C,d+z*M)))}h1e-6)if(Math.abs(s*u-c*f)>1e-6&&i){var h=e-o,d=r-a,p=u*u+c*c,v=h*h+d*d,g=Math.sqrt(p),y=Math.sqrt(l),_=i*Math.tan((Qi-Math.acos((p+l-v)/(2*g*y)))/2),b=_/y,m=_/g;Math.abs(b-1)>1e-6&&(this._+="L"+(t+b*f)+","+(n+b*s)),this._+="A"+i+","+i+",0,0,"+ +(s*h>f*d)+","+(this._x1=t+m*u)+","+(this._y1=n+m*c)}else this._+="L"+(this._x1=t)+","+(this._y1=n);else;},arc:function(t,n,e,r,i,o){t=+t,n=+n,o=!!o;var a=(e=+e)*Math.cos(r),u=e*Math.sin(r),c=t+a,f=n+u,s=1^o,l=o?r-i:i-r;if(e<0)throw new Error("negative radius: "+e);null===this._x1?this._+="M"+c+","+f:(Math.abs(this._x1-c)>1e-6||Math.abs(this._y1-f)>1e-6)&&(this._+="L"+c+","+f),e&&(l<0&&(l=l%Ki+Ki),l>Ji?this._+="A"+e+","+e+",0,1,"+s+","+(t-a)+","+(n-u)+"A"+e+","+e+",0,1,"+s+","+(this._x1=c)+","+(this._y1=f):l>1e-6&&(this._+="A"+e+","+e+",0,"+ +(l>=Qi)+","+s+","+(this._x1=t+e*Math.cos(i))+","+(this._y1=n+e*Math.sin(i))))},rect:function(t,n,e,r){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+n)+"h"+ +e+"v"+ +r+"h"+-e+"Z"},toString:function(){return this._}};function uo(){}function co(t,n){var e=new uo;if(t instanceof uo)t.each(function(t,n){e.set(n,t)});else if(Array.isArray(t)){var r,i=-1,o=t.length;if(null==n)for(;++ir!=d>r&&e<(h-f)*(r-s)/(d-s)+f&&(i=-i)}return i}function wo(t,n,e){var r,i,o,a;return function(t,n,e){return(n[0]-t[0])*(e[1]-t[1])==(e[0]-t[0])*(n[1]-t[1])}(t,n,e)&&(i=t[r=+(t[0]===n[0])],o=e[r],a=n[r],i<=o&&o<=a||a<=o&&o<=i)}function Mo(){}var No=[[],[[[1,1.5],[.5,1]]],[[[1.5,1],[1,1.5]]],[[[1.5,1],[.5,1]]],[[[1,.5],[1.5,1]]],[[[1,1.5],[.5,1]],[[1,.5],[1.5,1]]],[[[1,.5],[1,1.5]]],[[[1,.5],[.5,1]]],[[[.5,1],[1,.5]]],[[[1,1.5],[1,.5]]],[[[.5,1],[1,.5]],[[1.5,1],[1,1.5]]],[[[1.5,1],[1,.5]]],[[[.5,1],[1.5,1]]],[[[1,1.5],[1.5,1]]],[[[.5,1],[1,1.5]]],[]];function To(){var t=1,n=1,e=M,r=u;function i(t){var n=e(t);if(Array.isArray(n))n=n.slice().sort(_o);else{var r=s(t),i=r[0],a=r[1];n=w(i,a,n),n=g(Math.floor(i/n)*n,Math.floor(a/n)*n,n)}return n.map(function(n){return o(t,n)})}function o(e,i){var o=[],u=[];return function(e,r,i){var o,u,c,f,s,l,h=new Array,d=new Array;o=u=-1,f=e[0]>=r,No[f<<1].forEach(p);for(;++o=r,No[c|f<<1].forEach(p);No[f<<0].forEach(p);for(;++u=r,s=e[u*t]>=r,No[f<<1|s<<2].forEach(p);++o=r,l=s,s=e[u*t+o+1]>=r,No[c|f<<1|s<<2|l<<3].forEach(p);No[f|s<<3].forEach(p)}o=-1,s=e[u*t]>=r,No[s<<2].forEach(p);for(;++o=r,No[s<<2|l<<3].forEach(p);function p(t){var n,e,r=[t[0][0]+o,t[0][1]+u],c=[t[1][0]+o,t[1][1]+u],f=a(r),s=a(c);(n=d[f])?(e=h[s])?(delete d[n.end],delete h[e.start],n===e?(n.ring.push(c),i(n.ring)):h[n.start]=d[e.end]={start:n.start,end:e.end,ring:n.ring.concat(e.ring)}):(delete d[n.end],n.ring.push(c),d[n.end=s]=n):(n=h[s])?(e=d[f])?(delete h[n.start],delete d[e.end],n===e?(n.ring.push(c),i(n.ring)):h[e.start]=d[n.end]={start:e.start,end:n.end,ring:e.ring.concat(n.ring)}):(delete h[n.start],n.ring.unshift(r),h[n.start=f]=n):h[f]=d[s]={start:f,end:s,ring:[r,c]}}No[s<<3].forEach(p)}(e,i,function(t){r(t,e,i),function(t){for(var n=0,e=t.length,r=t[e-1][1]*t[0][0]-t[e-1][0]*t[0][1];++n0?o.push([t]):u.push(t)}),u.forEach(function(t){for(var n,e=0,r=o.length;e0&&a0&&u0&&o>0))throw new Error("invalid size");return t=r,n=o,i},i.thresholds=function(t){return arguments.length?(e="function"==typeof t?t:Array.isArray(t)?bo(yo.call(t)):bo(t),i):e},i.smooth=function(t){return arguments.length?(r=t?u:Mo,i):r===u},i}function Ao(t,n,e){for(var r=t.width,i=t.height,o=1+(e<<1),a=0;a=e&&(u>=o&&(c-=t.data[u-o+a*r]),n.data[u-e+a*r]=c/Math.min(u+1,r-1+o-u,o))}function So(t,n,e){for(var r=t.width,i=t.height,o=1+(e<<1),a=0;a=e&&(u>=o&&(c-=t.data[a+(u-o)*r]),n.data[a+(u-e)*r]=c/Math.min(u+1,i-1+o-u,o))}function ko(t){return t[0]}function Eo(t){return t[1]}function Co(){return 1}var Po={},zo={},Ro=34,Do=10,qo=13;function Lo(t){return new Function("d","return {"+t.map(function(t,n){return JSON.stringify(t)+": d["+n+'] || ""'}).join(",")+"}")}function Uo(t){var n=Object.create(null),e=[];return t.forEach(function(t){for(var r in t)r in n||e.push(n[r]=r)}),e}function Oo(t,n){var e=t+"",r=e.length;return r9999?"+"+Oo(t,6):Oo(t,4)}(t.getUTCFullYear())+"-"+Oo(t.getUTCMonth()+1,2)+"-"+Oo(t.getUTCDate(),2)+(i?"T"+Oo(n,2)+":"+Oo(e,2)+":"+Oo(r,2)+"."+Oo(i,3)+"Z":r?"T"+Oo(n,2)+":"+Oo(e,2)+":"+Oo(r,2)+"Z":e||n?"T"+Oo(n,2)+":"+Oo(e,2)+"Z":"")}function Fo(t){var n=new RegExp('["'+t+"\n\r]"),e=t.charCodeAt(0);function r(t,n){var r,i=[],o=t.length,a=0,u=0,c=o<=0,f=!1;function s(){if(c)return zo;if(f)return f=!1,Po;var n,r,i=a;if(t.charCodeAt(i)===Ro){for(;a++=o?c=!0:(r=t.charCodeAt(a++))===Do?f=!0:r===qo&&(f=!0,t.charCodeAt(a)===Do&&++a),t.slice(i+1,n-1).replace(/""/g,'"')}for(;a=(o=(v+y)/2))?v=o:y=o,(s=e>=(a=(g+_)/2))?g=a:_=a,i=d,!(d=d[l=s<<1|f]))return i[l]=p,t;if(u=+t._x.call(null,d.data),c=+t._y.call(null,d.data),n===u&&e===c)return p.next=d,i?i[l]=p:t._root=p,t;do{i=i?i[l]=new Array(4):t._root=new Array(4),(f=n>=(o=(v+y)/2))?v=o:y=o,(s=e>=(a=(g+_)/2))?g=a:_=a}while((l=s<<1|f)==(h=(c>=a)<<1|u>=o));return i[h]=d,i[l]=p,t}function ba(t,n,e,r,i){this.node=t,this.x0=n,this.y0=e,this.x1=r,this.y1=i}function ma(t){return t[0]}function xa(t){return t[1]}function wa(t,n,e){var r=new Ma(null==n?ma:n,null==e?xa:e,NaN,NaN,NaN,NaN);return null==t?r:r.addAll(t)}function Ma(t,n,e,r,i,o){this._x=t,this._y=n,this._x0=e,this._y0=r,this._x1=i,this._y1=o,this._root=void 0}function Na(t){for(var n={data:t.data},e=n;t=t.next;)e=e.next={data:t.data};return n}var Ta=wa.prototype=Ma.prototype;function Aa(t){return t.x+t.vx}function Sa(t){return t.y+t.vy}function ka(t){return t.index}function Ea(t,n){var e=t.get(n);if(!e)throw new Error("missing: "+n);return e}function Ca(t){return t.x}function Pa(t){return t.y}Ta.copy=function(){var t,n,e=new Ma(this._x,this._y,this._x0,this._y0,this._x1,this._y1),r=this._root;if(!r)return e;if(!r.length)return e._root=Na(r),e;for(t=[{source:r,target:e._root=new Array(4)}];r=t.pop();)for(var i=0;i<4;++i)(n=r.source[i])&&(n.length?t.push({source:n,target:r.target[i]=new Array(4)}):r.target[i]=Na(n));return e},Ta.add=function(t){var n=+this._x.call(null,t),e=+this._y.call(null,t);return _a(this.cover(n,e),n,e,t)},Ta.addAll=function(t){var n,e,r,i,o=t.length,a=new Array(o),u=new Array(o),c=1/0,f=1/0,s=-1/0,l=-1/0;for(e=0;es&&(s=r),il&&(l=i));if(c>s||f>l)return this;for(this.cover(c,f).cover(s,l),e=0;et||t>=i||r>n||n>=o;)switch(u=(nh||(o=c.y0)>d||(a=c.x1)=y)<<1|t>=g)&&(c=p[p.length-1],p[p.length-1]=p[p.length-1-f],p[p.length-1-f]=c)}else{var _=t-+this._x.call(null,v.data),b=n-+this._y.call(null,v.data),m=_*_+b*b;if(m=(u=(p+g)/2))?p=u:g=u,(s=a>=(c=(v+y)/2))?v=c:y=c,n=d,!(d=d[l=s<<1|f]))return this;if(!d.length)break;(n[l+1&3]||n[l+2&3]||n[l+3&3])&&(e=n,h=l)}for(;d.data!==t;)if(r=d,!(d=d.next))return this;return(i=d.next)&&delete d.next,r?(i?r.next=i:delete r.next,this):n?(i?n[l]=i:delete n[l],(d=n[0]||n[1]||n[2]||n[3])&&d===(n[3]||n[2]||n[1]||n[0])&&!d.length&&(e?e[h]=d:this._root=d),this):(this._root=i,this)},Ta.removeAll=function(t){for(var n=0,e=t.length;n1?r[0]+r.slice(2):r,+t.slice(e+1)]}function qa(t){return(t=Da(Math.abs(t)))?t[1]:NaN}var La,Ua=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Oa(t){if(!(n=Ua.exec(t)))throw new Error("invalid format: "+t);var n;return new Ba({fill:n[1],align:n[2],sign:n[3],symbol:n[4],zero:n[5],width:n[6],comma:n[7],precision:n[8]&&n[8].slice(1),trim:n[9],type:n[10]})}function Ba(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+""}function Fa(t,n){var e=Da(t,n);if(!e)return t+"";var r=e[0],i=e[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")}Oa.prototype=Ba.prototype,Ba.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var Ya={"%":function(t,n){return(100*t).toFixed(n)},b:function(t){return Math.round(t).toString(2)},c:function(t){return t+""},d:function(t){return Math.round(t).toString(10)},e:function(t,n){return t.toExponential(n)},f:function(t,n){return t.toFixed(n)},g:function(t,n){return t.toPrecision(n)},o:function(t){return Math.round(t).toString(8)},p:function(t,n){return Fa(100*t,n)},r:Fa,s:function(t,n){var e=Da(t,n);if(!e)return t+"";var r=e[0],i=e[1],o=i-(La=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,a=r.length;return o===a?r:o>a?r+new Array(o-a+1).join("0"):o>0?r.slice(0,o)+"."+r.slice(o):"0."+new Array(1-o).join("0")+Da(t,Math.max(0,n+o-1))[0]},X:function(t){return Math.round(t).toString(16).toUpperCase()},x:function(t){return Math.round(t).toString(16)}};function Ia(t){return t}var Ha,ja=Array.prototype.map,Va=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"];function Xa(t){var n,e,r=void 0===t.grouping||void 0===t.thousands?Ia:(n=ja.call(t.grouping,Number),e=t.thousands+"",function(t,r){for(var i=t.length,o=[],a=0,u=n[0],c=0;i>0&&u>0&&(c+u+1>r&&(u=Math.max(1,r-c)),o.push(t.substring(i-=u,i+u)),!((c+=u+1)>r));)u=n[a=(a+1)%n.length];return o.reverse().join(e)}),i=void 0===t.currency?"":t.currency[0]+"",o=void 0===t.currency?"":t.currency[1]+"",a=void 0===t.decimal?".":t.decimal+"",u=void 0===t.numerals?Ia:function(t){return function(n){return n.replace(/[0-9]/g,function(n){return t[+n]})}}(ja.call(t.numerals,String)),c=void 0===t.percent?"%":t.percent+"",f=void 0===t.minus?"-":t.minus+"",s=void 0===t.nan?"NaN":t.nan+"";function l(t){var n=(t=Oa(t)).fill,e=t.align,l=t.sign,h=t.symbol,d=t.zero,p=t.width,v=t.comma,g=t.precision,y=t.trim,_=t.type;"n"===_?(v=!0,_="g"):Ya[_]||(void 0===g&&(g=12),y=!0,_="g"),(d||"0"===n&&"="===e)&&(d=!0,n="0",e="=");var b="$"===h?i:"#"===h&&/[boxX]/.test(_)?"0"+_.toLowerCase():"",m="$"===h?o:/[%p]/.test(_)?c:"",x=Ya[_],w=/[defgprs%]/.test(_);function M(t){var i,o,c,h=b,M=m;if("c"===_)M=x(t)+M,t="";else{var N=(t=+t)<0||1/t<0;if(t=isNaN(t)?s:x(Math.abs(t),g),y&&(t=function(t){t:for(var n,e=t.length,r=1,i=-1;r0&&(i=0)}return i>0?t.slice(0,i)+t.slice(n+1):t}(t)),N&&0==+t&&"+"!==l&&(N=!1),h=(N?"("===l?l:f:"-"===l||"("===l?"":l)+h,M=("s"===_?Va[8+La/3]:"")+M+(N&&"("===l?")":""),w)for(i=-1,o=t.length;++i(c=t.charCodeAt(i))||c>57){M=(46===c?a+t.slice(i+1):t.slice(i))+M,t=t.slice(0,i);break}}v&&!d&&(t=r(t,1/0));var T=h.length+t.length+M.length,A=T>1)+h+t+M+A.slice(T);break;default:t=A+h+t+M}return u(t)}return g=void 0===g?6:/[gprs]/.test(_)?Math.max(1,Math.min(21,g)):Math.max(0,Math.min(20,g)),M.toString=function(){return t+""},M}return{format:l,formatPrefix:function(t,n){var e=l(((t=Oa(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor(qa(n)/3))),i=Math.pow(10,-r),o=Va[8+r/3];return function(t){return e(i*t)+o}}}}function Ga(n){return Ha=Xa(n),t.format=Ha.format,t.formatPrefix=Ha.formatPrefix,Ha}function $a(t){return Math.max(0,-qa(Math.abs(t)))}function Wa(t,n){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(qa(n)/3)))-qa(Math.abs(t)))}function Za(t,n){return t=Math.abs(t),n=Math.abs(n)-t,Math.max(0,qa(n)-qa(t))+1}function Qa(){return new Ka}function Ka(){this.reset()}Ga({decimal:".",thousands:",",grouping:[3],currency:["$",""],minus:"-"}),Ka.prototype={constructor:Ka,reset:function(){this.s=this.t=0},add:function(t){tu(Ja,t,this.t),tu(this,Ja.s,this.s),this.s?this.t+=Ja.t:this.s=Ja.t},valueOf:function(){return this.s}};var Ja=new Ka;function tu(t,n,e){var r=t.s=n+e,i=r-n,o=r-i;t.t=n-o+(e-i)}var nu=1e-6,eu=1e-12,ru=Math.PI,iu=ru/2,ou=ru/4,au=2*ru,uu=180/ru,cu=ru/180,fu=Math.abs,su=Math.atan,lu=Math.atan2,hu=Math.cos,du=Math.ceil,pu=Math.exp,vu=Math.log,gu=Math.pow,yu=Math.sin,_u=Math.sign||function(t){return t>0?1:t<0?-1:0},bu=Math.sqrt,mu=Math.tan;function xu(t){return t>1?0:t<-1?ru:Math.acos(t)}function wu(t){return t>1?iu:t<-1?-iu:Math.asin(t)}function Mu(t){return(t=yu(t/2))*t}function Nu(){}function Tu(t,n){t&&Su.hasOwnProperty(t.type)&&Su[t.type](t,n)}var Au={Feature:function(t,n){Tu(t.geometry,n)},FeatureCollection:function(t,n){for(var e=t.features,r=-1,i=e.length;++r=0?1:-1,i=r*e,o=hu(n=(n*=cu)/2+ou),a=yu(n),u=qu*a,c=Du*o+u*hu(i),f=u*r*yu(i);Lu.add(lu(f,c)),Ru=t,Du=o,qu=a}function Hu(t){return[lu(t[1],t[0]),wu(t[2])]}function ju(t){var n=t[0],e=t[1],r=hu(e);return[r*hu(n),r*yu(n),yu(e)]}function Vu(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]}function Xu(t,n){return[t[1]*n[2]-t[2]*n[1],t[2]*n[0]-t[0]*n[2],t[0]*n[1]-t[1]*n[0]]}function Gu(t,n){t[0]+=n[0],t[1]+=n[1],t[2]+=n[2]}function $u(t,n){return[t[0]*n,t[1]*n,t[2]*n]}function Wu(t){var n=bu(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=n,t[1]/=n,t[2]/=n}var Zu,Qu,Ku,Ju,tc,nc,ec,rc,ic,oc,ac,uc,cc,fc,sc,lc,hc,dc,pc,vc,gc,yc,_c,bc,mc,xc,wc=Qa(),Mc={point:Nc,lineStart:Ac,lineEnd:Sc,polygonStart:function(){Mc.point=kc,Mc.lineStart=Ec,Mc.lineEnd=Cc,wc.reset(),Ou.polygonStart()},polygonEnd:function(){Ou.polygonEnd(),Mc.point=Nc,Mc.lineStart=Ac,Mc.lineEnd=Sc,Lu<0?(Zu=-(Ku=180),Qu=-(Ju=90)):wc>nu?Ju=90:wc<-nu&&(Qu=-90),oc[0]=Zu,oc[1]=Ku},sphere:function(){Zu=-(Ku=180),Qu=-(Ju=90)}};function Nc(t,n){ic.push(oc=[Zu=t,Ku=t]),nJu&&(Ju=n)}function Tc(t,n){var e=ju([t*cu,n*cu]);if(rc){var r=Xu(rc,e),i=Xu([r[1],-r[0],0],r);Wu(i),i=Hu(i);var o,a=t-tc,u=a>0?1:-1,c=i[0]*uu*u,f=fu(a)>180;f^(u*tcJu&&(Ju=o):f^(u*tc<(c=(c+360)%360-180)&&cJu&&(Ju=n)),f?tPc(Zu,Ku)&&(Ku=t):Pc(t,Ku)>Pc(Zu,Ku)&&(Zu=t):Ku>=Zu?(tKu&&(Ku=t)):t>tc?Pc(Zu,t)>Pc(Zu,Ku)&&(Ku=t):Pc(t,Ku)>Pc(Zu,Ku)&&(Zu=t)}else ic.push(oc=[Zu=t,Ku=t]);nJu&&(Ju=n),rc=e,tc=t}function Ac(){Mc.point=Tc}function Sc(){oc[0]=Zu,oc[1]=Ku,Mc.point=Nc,rc=null}function kc(t,n){if(rc){var e=t-tc;wc.add(fu(e)>180?e+(e>0?360:-360):e)}else nc=t,ec=n;Ou.point(t,n),Tc(t,n)}function Ec(){Ou.lineStart()}function Cc(){kc(nc,ec),Ou.lineEnd(),fu(wc)>nu&&(Zu=-(Ku=180)),oc[0]=Zu,oc[1]=Ku,rc=null}function Pc(t,n){return(n-=t)<0?n+360:n}function zc(t,n){return t[0]-n[0]}function Rc(t,n){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nru?t+Math.round(-t/au)*au:t,n]}function $c(t,n,e){return(t%=au)?n||e?Xc(Zc(t),Qc(n,e)):Zc(t):n||e?Qc(n,e):Gc}function Wc(t){return function(n,e){return[(n+=t)>ru?n-au:n<-ru?n+au:n,e]}}function Zc(t){var n=Wc(t);return n.invert=Wc(-t),n}function Qc(t,n){var e=hu(t),r=yu(t),i=hu(n),o=yu(n);function a(t,n){var a=hu(n),u=hu(t)*a,c=yu(t)*a,f=yu(n),s=f*e+u*r;return[lu(c*i-s*o,u*e-f*r),wu(s*i+c*o)]}return a.invert=function(t,n){var a=hu(n),u=hu(t)*a,c=yu(t)*a,f=yu(n),s=f*i-c*o;return[lu(c*i+f*o,u*e+s*r),wu(s*e-u*r)]},a}function Kc(t){function n(n){return(n=t(n[0]*cu,n[1]*cu))[0]*=uu,n[1]*=uu,n}return t=$c(t[0]*cu,t[1]*cu,t.length>2?t[2]*cu:0),n.invert=function(n){return(n=t.invert(n[0]*cu,n[1]*cu))[0]*=uu,n[1]*=uu,n},n}function Jc(t,n,e,r,i,o){if(e){var a=hu(n),u=yu(n),c=r*e;null==i?(i=n+r*au,o=n-c/2):(i=tf(a,i),o=tf(a,o),(r>0?io)&&(i+=r*au));for(var f,s=i;r>0?s>o:s1&&n.push(n.pop().concat(n.shift()))},result:function(){var e=n;return n=[],t=null,e}}}function ef(t,n){return fu(t[0]-n[0])=0;--o)i.point((s=f[o])[0],s[1]);else r(h.x,h.p.x,-1,i);h=h.p}f=(h=h.o).z,d=!d}while(!h.v);i.lineEnd()}}}function af(t){if(n=t.length){for(var n,e,r=0,i=t[0];++r=0?1:-1,T=N*M,A=T>ru,S=v*x;if(uf.add(lu(S*N*yu(T),g*w+S*hu(T))),a+=A?M+N*au:M,A^d>=e^b>=e){var k=Xu(ju(h),ju(_));Wu(k);var E=Xu(o,k);Wu(E);var C=(A^M>=0?-1:1)*wu(E[2]);(r>C||r===C&&(k[0]||k[1]))&&(u+=A^M>=0?1:-1)}}return(a<-nu||a0){for(l||(i.polygonStart(),l=!0),i.lineStart(),t=0;t1&&2&c&&h.push(h.pop().concat(h.shift())),a.push(h.filter(lf))}return h}}function lf(t){return t.length>1}function hf(t,n){return((t=t.x)[0]<0?t[1]-iu-nu:iu-t[1])-((n=n.x)[0]<0?n[1]-iu-nu:iu-n[1])}var df=sf(function(){return!0},function(t){var n,e=NaN,r=NaN,i=NaN;return{lineStart:function(){t.lineStart(),n=1},point:function(o,a){var u=o>0?ru:-ru,c=fu(o-e);fu(c-ru)0?iu:-iu),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(u,r),t.point(o,r),n=0):i!==u&&c>=ru&&(fu(e-i)nu?su((yu(n)*(o=hu(r))*yu(e)-yu(r)*(i=hu(n))*yu(t))/(i*o*a)):(n+r)/2}(e,r,o,a),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(u,r),n=0),t.point(e=o,r=a),i=u},lineEnd:function(){t.lineEnd(),e=r=NaN},clean:function(){return 2-n}}},function(t,n,e,r){var i;if(null==t)i=e*iu,r.point(-ru,i),r.point(0,i),r.point(ru,i),r.point(ru,0),r.point(ru,-i),r.point(0,-i),r.point(-ru,-i),r.point(-ru,0),r.point(-ru,i);else if(fu(t[0]-n[0])>nu){var o=t[0]0,i=fu(n)>nu;function o(t,e){return hu(t)*hu(e)>n}function a(t,e,r){var i=[1,0,0],o=Xu(ju(t),ju(e)),a=Vu(o,o),u=o[0],c=a-u*u;if(!c)return!r&&t;var f=n*a/c,s=-n*u/c,l=Xu(i,o),h=$u(i,f);Gu(h,$u(o,s));var d=l,p=Vu(h,d),v=Vu(d,d),g=p*p-v*(Vu(h,h)-1);if(!(g<0)){var y=bu(g),_=$u(d,(-p-y)/v);if(Gu(_,h),_=Hu(_),!r)return _;var b,m=t[0],x=e[0],w=t[1],M=e[1];x0^_[1]<(fu(_[0]-m)ru^(m<=_[0]&&_[0]<=x)){var A=$u(d,(-p+y)/v);return Gu(A,h),[_,Hu(A)]}}}function u(n,e){var i=r?t:ru-t,o=0;return n<-i?o|=1:n>i&&(o|=2),e<-i?o|=4:e>i&&(o|=8),o}return sf(o,function(t){var n,e,c,f,s;return{lineStart:function(){f=c=!1,s=1},point:function(l,h){var d,p=[l,h],v=o(l,h),g=r?v?0:u(l,h):v?u(l+(l<0?ru:-ru),h):0;if(!n&&(f=c=v)&&t.lineStart(),v!==c&&(!(d=a(n,p))||ef(n,d)||ef(p,d))&&(p[0]+=nu,p[1]+=nu,v=o(p[0],p[1])),v!==c)s=0,v?(t.lineStart(),d=a(p,n),t.point(d[0],d[1])):(d=a(n,p),t.point(d[0],d[1]),t.lineEnd()),n=d;else if(i&&n&&r^v){var y;g&e||!(y=a(p,n,!0))||(s=0,r?(t.lineStart(),t.point(y[0][0],y[0][1]),t.point(y[1][0],y[1][1]),t.lineEnd()):(t.point(y[1][0],y[1][1]),t.lineEnd(),t.lineStart(),t.point(y[0][0],y[0][1])))}!v||n&&ef(n,p)||t.point(p[0],p[1]),n=p,c=v,e=g},lineEnd:function(){c&&t.lineEnd(),n=null},clean:function(){return s|(f&&c)<<1}}},function(n,r,i,o){Jc(o,t,e,i,n,r)},r?[0,-t]:[-ru,t-ru])}var vf=1e9,gf=-vf;function yf(t,n,e,r){function i(i,o){return t<=i&&i<=e&&n<=o&&o<=r}function o(i,o,u,f){var s=0,l=0;if(null==i||(s=a(i,u))!==(l=a(o,u))||c(i,o)<0^u>0)do{f.point(0===s||3===s?t:e,s>1?r:n)}while((s=(s+u+4)%4)!==l);else f.point(o[0],o[1])}function a(r,i){return fu(r[0]-t)0?0:3:fu(r[0]-e)0?2:1:fu(r[1]-n)0?1:0:i>0?3:2}function u(t,n){return c(t.x,n.x)}function c(t,n){var e=a(t,1),r=a(n,1);return e!==r?e-r:0===e?n[1]-t[1]:1===e?t[0]-n[0]:2===e?t[1]-n[1]:n[0]-t[0]}return function(a){var c,f,s,l,h,d,p,v,g,y,_,b=a,m=nf(),x={point:w,lineStart:function(){x.point=M,f&&f.push(s=[]);y=!0,g=!1,p=v=NaN},lineEnd:function(){c&&(M(l,h),d&&g&&m.rejoin(),c.push(m.result()));x.point=w,g&&b.lineEnd()},polygonStart:function(){b=m,c=[],f=[],_=!0},polygonEnd:function(){var n=function(){for(var n=0,e=0,i=f.length;er&&(h-o)*(r-a)>(d-a)*(t-o)&&++n:d<=r&&(h-o)*(r-a)<(d-a)*(t-o)&&--n;return n}(),e=_&&n,i=(c=A(c)).length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),o(null,null,1,a),a.lineEnd()),i&&of(c,u,n,o,a),a.polygonEnd());b=a,c=f=s=null}};function w(t,n){i(t,n)&&b.point(t,n)}function M(o,a){var u=i(o,a);if(f&&s.push([o,a]),y)l=o,h=a,d=u,y=!1,u&&(b.lineStart(),b.point(o,a));else if(u&&g)b.point(o,a);else{var c=[p=Math.max(gf,Math.min(vf,p)),v=Math.max(gf,Math.min(vf,v))],m=[o=Math.max(gf,Math.min(vf,o)),a=Math.max(gf,Math.min(vf,a))];!function(t,n,e,r,i,o){var a,u=t[0],c=t[1],f=0,s=1,l=n[0]-u,h=n[1]-c;if(a=e-u,l||!(a>0)){if(a/=l,l<0){if(a0){if(a>s)return;a>f&&(f=a)}if(a=i-u,l||!(a<0)){if(a/=l,l<0){if(a>s)return;a>f&&(f=a)}else if(l>0){if(a0)){if(a/=h,h<0){if(a0){if(a>s)return;a>f&&(f=a)}if(a=o-c,h||!(a<0)){if(a/=h,h<0){if(a>s)return;a>f&&(f=a)}else if(h>0){if(a0&&(t[0]=u+f*l,t[1]=c+f*h),s<1&&(n[0]=u+s*l,n[1]=c+s*h),!0}}}}}(c,m,t,n,e,r)?u&&(b.lineStart(),b.point(o,a),_=!1):(g||(b.lineStart(),b.point(c[0],c[1])),b.point(m[0],m[1]),u||b.lineEnd(),_=!1)}p=o,v=a,g=u}return x}}var _f,bf,mf,xf=Qa(),wf={sphere:Nu,point:Nu,lineStart:function(){wf.point=Nf,wf.lineEnd=Mf},lineEnd:Nu,polygonStart:Nu,polygonEnd:Nu};function Mf(){wf.point=wf.lineEnd=Nu}function Nf(t,n){_f=t*=cu,bf=yu(n*=cu),mf=hu(n),wf.point=Tf}function Tf(t,n){t*=cu;var e=yu(n*=cu),r=hu(n),i=fu(t-_f),o=hu(i),a=r*yu(i),u=mf*e-bf*r*o,c=bf*e+mf*r*o;xf.add(lu(bu(a*a+u*u),c)),_f=t,bf=e,mf=r}function Af(t){return xf.reset(),Cu(t,wf),+xf}var Sf=[null,null],kf={type:"LineString",coordinates:Sf};function Ef(t,n){return Sf[0]=t,Sf[1]=n,Af(kf)}var Cf={Feature:function(t,n){return zf(t.geometry,n)},FeatureCollection:function(t,n){for(var e=t.features,r=-1,i=e.length;++r0&&(i=Ef(t[o],t[o-1]))>0&&e<=i&&r<=i&&(e+r-i)*(1-Math.pow((e-r)/i,2))nu}).map(c)).concat(g(du(o/d)*d,i,d).filter(function(t){return fu(t%v)>nu}).map(f))}return _.lines=function(){return b().map(function(t){return{type:"LineString",coordinates:t}})},_.outline=function(){return{type:"Polygon",coordinates:[s(r).concat(l(a).slice(1),s(e).reverse().slice(1),l(u).reverse().slice(1))]}},_.extent=function(t){return arguments.length?_.extentMajor(t).extentMinor(t):_.extentMinor()},_.extentMajor=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],u=+t[0][1],a=+t[1][1],r>e&&(t=r,r=e,e=t),u>a&&(t=u,u=a,a=t),_.precision(y)):[[r,u],[e,a]]},_.extentMinor=function(e){return arguments.length?(n=+e[0][0],t=+e[1][0],o=+e[0][1],i=+e[1][1],n>t&&(e=n,n=t,t=e),o>i&&(e=o,o=i,i=e),_.precision(y)):[[n,o],[t,i]]},_.step=function(t){return arguments.length?_.stepMajor(t).stepMinor(t):_.stepMinor()},_.stepMajor=function(t){return arguments.length?(p=+t[0],v=+t[1],_):[p,v]},_.stepMinor=function(t){return arguments.length?(h=+t[0],d=+t[1],_):[h,d]},_.precision=function(h){return arguments.length?(y=+h,c=Of(o,i,90),f=Bf(n,t,y),s=Of(u,a,90),l=Bf(r,e,y),_):y},_.extentMajor([[-180,-90+nu],[180,90-nu]]).extentMinor([[-180,-80-nu],[180,80+nu]])}function Yf(t){return t}var If,Hf,jf,Vf,Xf=Qa(),Gf=Qa(),$f={point:Nu,lineStart:Nu,lineEnd:Nu,polygonStart:function(){$f.lineStart=Wf,$f.lineEnd=Kf},polygonEnd:function(){$f.lineStart=$f.lineEnd=$f.point=Nu,Xf.add(fu(Gf)),Gf.reset()},result:function(){var t=Xf/2;return Xf.reset(),t}};function Wf(){$f.point=Zf}function Zf(t,n){$f.point=Qf,If=jf=t,Hf=Vf=n}function Qf(t,n){Gf.add(Vf*t-jf*n),jf=t,Vf=n}function Kf(){Qf(If,Hf)}var Jf=1/0,ts=Jf,ns=-Jf,es=ns,rs={point:function(t,n){tns&&(ns=t);nes&&(es=n)},lineStart:Nu,lineEnd:Nu,polygonStart:Nu,polygonEnd:Nu,result:function(){var t=[[Jf,ts],[ns,es]];return ns=es=-(ts=Jf=1/0),t}};var is,os,as,us,cs=0,fs=0,ss=0,ls=0,hs=0,ds=0,ps=0,vs=0,gs=0,ys={point:_s,lineStart:bs,lineEnd:ws,polygonStart:function(){ys.lineStart=Ms,ys.lineEnd=Ns},polygonEnd:function(){ys.point=_s,ys.lineStart=bs,ys.lineEnd=ws},result:function(){var t=gs?[ps/gs,vs/gs]:ds?[ls/ds,hs/ds]:ss?[cs/ss,fs/ss]:[NaN,NaN];return cs=fs=ss=ls=hs=ds=ps=vs=gs=0,t}};function _s(t,n){cs+=t,fs+=n,++ss}function bs(){ys.point=ms}function ms(t,n){ys.point=xs,_s(as=t,us=n)}function xs(t,n){var e=t-as,r=n-us,i=bu(e*e+r*r);ls+=i*(as+t)/2,hs+=i*(us+n)/2,ds+=i,_s(as=t,us=n)}function ws(){ys.point=_s}function Ms(){ys.point=Ts}function Ns(){As(is,os)}function Ts(t,n){ys.point=As,_s(is=as=t,os=us=n)}function As(t,n){var e=t-as,r=n-us,i=bu(e*e+r*r);ls+=i*(as+t)/2,hs+=i*(us+n)/2,ds+=i,ps+=(i=us*t-as*n)*(as+t),vs+=i*(us+n),gs+=3*i,_s(as=t,us=n)}function Ss(t){this._context=t}Ss.prototype={_radius:4.5,pointRadius:function(t){return this._radius=t,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._context.closePath(),this._point=NaN},point:function(t,n){switch(this._point){case 0:this._context.moveTo(t,n),this._point=1;break;case 1:this._context.lineTo(t,n);break;default:this._context.moveTo(t+this._radius,n),this._context.arc(t,n,this._radius,0,au)}},result:Nu};var ks,Es,Cs,Ps,zs,Rs=Qa(),Ds={point:Nu,lineStart:function(){Ds.point=qs},lineEnd:function(){ks&&Ls(Es,Cs),Ds.point=Nu},polygonStart:function(){ks=!0},polygonEnd:function(){ks=null},result:function(){var t=+Rs;return Rs.reset(),t}};function qs(t,n){Ds.point=Ls,Es=Ps=t,Cs=zs=n}function Ls(t,n){Ps-=t,zs-=n,Rs.add(bu(Ps*Ps+zs*zs)),Ps=t,zs=n}function Us(){this._string=[]}function Os(t){return"m0,"+t+"a"+t+","+t+" 0 1,1 0,"+-2*t+"a"+t+","+t+" 0 1,1 0,"+2*t+"z"}function Bs(t){return function(n){var e=new Fs;for(var r in t)e[r]=t[r];return e.stream=n,e}}function Fs(){}function Ys(t,n,e){var r=t.clipExtent&&t.clipExtent();return t.scale(150).translate([0,0]),null!=r&&t.clipExtent(null),Cu(e,t.stream(rs)),n(rs.result()),null!=r&&t.clipExtent(r),t}function Is(t,n,e){return Ys(t,function(e){var r=n[1][0]-n[0][0],i=n[1][1]-n[0][1],o=Math.min(r/(e[1][0]-e[0][0]),i/(e[1][1]-e[0][1])),a=+n[0][0]+(r-o*(e[1][0]+e[0][0]))/2,u=+n[0][1]+(i-o*(e[1][1]+e[0][1]))/2;t.scale(150*o).translate([a,u])},e)}function Hs(t,n,e){return Is(t,[[0,0],n],e)}function js(t,n,e){return Ys(t,function(e){var r=+n,i=r/(e[1][0]-e[0][0]),o=(r-i*(e[1][0]+e[0][0]))/2,a=-i*e[0][1];t.scale(150*i).translate([o,a])},e)}function Vs(t,n,e){return Ys(t,function(e){var r=+n,i=r/(e[1][1]-e[0][1]),o=-i*e[0][0],a=(r-i*(e[1][1]+e[0][1]))/2;t.scale(150*i).translate([o,a])},e)}Us.prototype={_radius:4.5,_circle:Os(4.5),pointRadius:function(t){return(t=+t)!==this._radius&&(this._radius=t,this._circle=null),this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._string.push("Z"),this._point=NaN},point:function(t,n){switch(this._point){case 0:this._string.push("M",t,",",n),this._point=1;break;case 1:this._string.push("L",t,",",n);break;default:null==this._circle&&(this._circle=Os(this._radius)),this._string.push("M",t,",",n,this._circle)}},result:function(){if(this._string.length){var t=this._string.join("");return this._string=[],t}return null}},Fs.prototype={constructor:Fs,point:function(t,n){this.stream.point(t,n)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};var Xs=16,Gs=hu(30*cu);function $s(t,n){return+n?function(t,n){function e(r,i,o,a,u,c,f,s,l,h,d,p,v,g){var y=f-r,_=s-i,b=y*y+_*_;if(b>4*n&&v--){var m=a+h,x=u+d,w=c+p,M=bu(m*m+x*x+w*w),N=wu(w/=M),T=fu(fu(w)-1)n||fu((y*E+_*C)/b-.5)>.3||a*h+u*d+c*p2?t[2]%360*cu:0,S()):[g*uu,y*uu,_*uu]},T.angle=function(t){return arguments.length?(b=t%360*cu,S()):b*uu},T.precision=function(t){return arguments.length?(a=$s(u,N=t*t),k()):bu(N)},T.fitExtent=function(t,n){return Is(T,t,n)},T.fitSize=function(t,n){return Hs(T,t,n)},T.fitWidth=function(t,n){return js(T,t,n)},T.fitHeight=function(t,n){return Vs(T,t,n)},function(){return n=t.apply(this,arguments),T.invert=n.invert&&A,S()}}function Js(t){var n=0,e=ru/3,r=Ks(t),i=r(n,e);return i.parallels=function(t){return arguments.length?r(n=t[0]*cu,e=t[1]*cu):[n*uu,e*uu]},i}function tl(t,n){var e=yu(t),r=(e+yu(n))/2;if(fu(r)0?n<-iu+nu&&(n=-iu+nu):n>iu-nu&&(n=iu-nu);var e=i/gu(fl(n),r);return[e*yu(r*t),i-e*hu(r*t)]}return o.invert=function(t,n){var e=i-n,o=_u(r)*bu(t*t+e*e);return[lu(t,fu(e))/r*_u(e),2*su(gu(i/o,1/r))-iu]},o}function ll(t,n){return[t,n]}function hl(t,n){var e=hu(t),r=t===n?yu(t):(e-hu(n))/(n-t),i=e/r+t;if(fu(r)=0;)n+=e[r].value;else n=1;t.value=n}function El(t,n){var e,r,i,o,a,u=new Rl(t),c=+t.value&&(u.value=t.value),f=[u];for(null==n&&(n=Cl);e=f.pop();)if(c&&(e.value=+e.data.value),(i=n(e.data))&&(a=i.length))for(e.children=new Array(a),o=a-1;o>=0;--o)f.push(r=e.children[o]=new Rl(i[o])),r.parent=e,r.depth=e.depth+1;return u.eachBefore(zl)}function Cl(t){return t.children}function Pl(t){t.data=t.data.data}function zl(t){var n=0;do{t.height=n}while((t=t.parent)&&t.height<++n)}function Rl(t){this.data=t,this.depth=this.height=0,this.parent=null}_l.invert=function(t,n){for(var e,r=n,i=r*r,o=i*i*i,a=0;a<12&&(o=(i=(r-=e=(r*(dl+pl*i+o*(vl+gl*i))-n)/(dl+3*pl*i+o*(7*vl+9*gl*i)))*r)*i*i,!(fu(e)nu&&--i>0);return[t/(.8707+(o=r*r)*(o*(o*o*o*(.003971-.001529*o)-.013791)-.131979)),r]},wl.invert=il(wu),Ml.invert=il(function(t){return 2*su(t)}),Nl.invert=function(t,n){return[-n,2*su(pu(t))-iu]},Rl.prototype=El.prototype={constructor:Rl,count:function(){return this.eachAfter(kl)},each:function(t){var n,e,r,i,o=this,a=[o];do{for(n=a.reverse(),a=[];o=n.pop();)if(t(o),e=o.children)for(r=0,i=e.length;r=0;--e)i.push(n[e]);return this},sum:function(t){return this.eachAfter(function(n){for(var e=+t(n.data)||0,r=n.children,i=r&&r.length;--i>=0;)e+=r[i].value;n.value=e})},sort:function(t){return this.eachBefore(function(n){n.children&&n.children.sort(t)})},path:function(t){for(var n=this,e=function(t,n){if(t===n)return t;var e=t.ancestors(),r=n.ancestors(),i=null;for(t=e.pop(),n=r.pop();t===n;)i=t,t=e.pop(),n=r.pop();return i}(n,t),r=[n];n!==e;)n=n.parent,r.push(n);for(var i=r.length;t!==e;)r.splice(i,0,t),t=t.parent;return r},ancestors:function(){for(var t=this,n=[t];t=t.parent;)n.push(t);return n},descendants:function(){var t=[];return this.each(function(n){t.push(n)}),t},leaves:function(){var t=[];return this.eachBefore(function(n){n.children||t.push(n)}),t},links:function(){var t=this,n=[];return t.each(function(e){e!==t&&n.push({source:e.parent,target:e})}),n},copy:function(){return El(this).eachBefore(Pl)}};var Dl=Array.prototype.slice;function ql(t){for(var n,e,r=0,i=(t=function(t){for(var n,e,r=t.length;r;)e=Math.random()*r--|0,n=t[r],t[r]=t[e],t[e]=n;return t}(Dl.call(t))).length,o=[];r0&&e*e>r*r+i*i}function Bl(t,n){for(var e=0;e(a*=a)?(r=(f+a-i)/(2*f),o=Math.sqrt(Math.max(0,a/f-r*r)),e.x=t.x-r*u-o*c,e.y=t.y-r*c+o*u):(r=(f+i-a)/(2*f),o=Math.sqrt(Math.max(0,i/f-r*r)),e.x=n.x+r*u-o*c,e.y=n.y+r*c+o*u)):(e.x=n.x+e.r,e.y=n.y)}function jl(t,n){var e=t.r+n.r-1e-6,r=n.x-t.x,i=n.y-t.y;return e>0&&e*e>r*r+i*i}function Vl(t){var n=t._,e=t.next._,r=n.r+e.r,i=(n.x*e.r+e.x*n.r)/r,o=(n.y*e.r+e.y*n.r)/r;return i*i+o*o}function Xl(t){this._=t,this.next=null,this.previous=null}function Gl(t){if(!(i=t.length))return 0;var n,e,r,i,o,a,u,c,f,s,l;if((n=t[0]).x=0,n.y=0,!(i>1))return n.r;if(e=t[1],n.x=-e.r,e.x=n.r,e.y=0,!(i>2))return n.r+e.r;Hl(e,n,r=t[2]),n=new Xl(n),e=new Xl(e),r=new Xl(r),n.next=r.previous=e,e.next=n.previous=r,r.next=e.previous=n;t:for(u=3;uh&&(h=u),g=s*s*v,(d=Math.max(h/g,g/l))>p){s-=u;break}p=d}y.push(a={value:s,dice:c1?n:1)},e}(gh);var bh=function t(n){function e(t,e,r,i,o){if((a=t._squarify)&&a.ratio===n)for(var a,u,c,f,s,l=-1,h=a.length,d=t.value;++l1?n:1)},e}(gh);function mh(t,n,e){return(n[0]-t[0])*(e[1]-t[1])-(n[1]-t[1])*(e[0]-t[0])}function xh(t,n){return t[0]-n[0]||t[1]-n[1]}function wh(t){for(var n=t.length,e=[0,1],r=2,i=2;i1&&mh(t[e[r-2]],t[e[r-1]],t[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function Mh(){return Math.random()}var Nh=function t(n){function e(t,e){return t=null==t?0:+t,e=null==e?1:+e,1===arguments.length?(e=t,t=0):e-=t,function(){return n()*e+t}}return e.source=t,e}(Mh),Th=function t(n){function e(t,e){var r,i;return t=null==t?0:+t,e=null==e?1:+e,function(){var o;if(null!=r)o=r,r=null;else do{r=2*n()-1,o=2*n()-1,i=r*r+o*o}while(!i||i>1);return t+e*o*Math.sqrt(-2*Math.log(i)/i)}}return e.source=t,e}(Mh),Ah=function t(n){function e(){var t=Th.source(n).apply(this,arguments);return function(){return Math.exp(t())}}return e.source=t,e}(Mh),Sh=function t(n){function e(t){return function(){for(var e=0,r=0;rr&&(n=e,e=r,r=n),function(t){return Math.max(e,Math.min(r,t))}}function Hh(t,n,e){var r=t[0],i=t[1],o=n[0],a=n[1];return i2?jh:Hh,i=o=null,l}function l(n){return isNaN(n=+n)?e:(i||(i=r(a.map(t),u,c)))(t(f(n)))}return l.invert=function(e){return f(n((o||(o=r(u,a.map(t),me)))(e)))},l.domain=function(t){return arguments.length?(a=Rh.call(t,Oh),f===Fh||(f=Ih(a)),s()):a.slice()},l.range=function(t){return arguments.length?(u=Dh.call(t),s()):u.slice()},l.rangeRound=function(t){return u=Dh.call(t),c=Ae,s()},l.clamp=function(t){return arguments.length?(f=t?Ih(a):Fh,l):f!==Fh},l.interpolate=function(t){return arguments.length?(c=t,s()):c},l.unknown=function(t){return arguments.length?(e=t,l):e},function(e,r){return t=e,n=r,s()}}function Gh(t,n){return Xh()(t,n)}function $h(n,e,r,i){var o,a=w(n,e,r);switch((i=Oa(null==i?",f":i)).type){case"s":var u=Math.max(Math.abs(n),Math.abs(e));return null!=i.precision||isNaN(o=Wa(a,u))||(i.precision=o),t.formatPrefix(i,u);case"":case"e":case"g":case"p":case"r":null!=i.precision||isNaN(o=Za(a,Math.max(Math.abs(n),Math.abs(e))))||(i.precision=o-("e"===i.type));break;case"f":case"%":null!=i.precision||isNaN(o=$a(a))||(i.precision=o-2*("%"===i.type))}return t.format(i)}function Wh(t){var n=t.domain;return t.ticks=function(t){var e=n();return m(e[0],e[e.length-1],null==t?10:t)},t.tickFormat=function(t,e){var r=n();return $h(r[0],r[r.length-1],null==t?10:t,e)},t.nice=function(e){null==e&&(e=10);var r,i=n(),o=0,a=i.length-1,u=i[o],c=i[a];return c0?r=x(u=Math.floor(u/r)*r,c=Math.ceil(c/r)*r,e):r<0&&(r=x(u=Math.ceil(u*r)/r,c=Math.floor(c*r)/r,e)),r>0?(i[o]=Math.floor(u/r)*r,i[a]=Math.ceil(c/r)*r,n(i)):r<0&&(i[o]=Math.ceil(u*r)/r,i[a]=Math.floor(c*r)/r,n(i)),t},t}function Zh(t,n){var e,r=0,i=(t=t.slice()).length-1,o=t[r],a=t[i];return a0){for(;hc)break;v.push(l)}}else for(;h=1;--s)if(!((l=f*s)c)break;v.push(l)}}else v=m(h,d,Math.min(d-h,p)).map(r);return n?v.reverse():v},i.tickFormat=function(n,o){if(null==o&&(o=10===a?".0e":","),"function"!=typeof o&&(o=t.format(o)),n===1/0)return o;null==n&&(n=10);var u=Math.max(1,a*n/i.ticks().length);return function(t){var n=t/r(Math.round(e(t)));return n*a0))return u;do{u.push(a=new Date(+e)),n(e,o),t(e)}while(a=n)for(;t(n),!e(n);)n.setTime(n-1)},function(t,r){if(t>=t)if(r<0)for(;++r<=0;)for(;n(t,-1),!e(t););else for(;--r>=0;)for(;n(t,1),!e(t););})},e&&(i.count=function(n,r){return hd.setTime(+n),dd.setTime(+r),t(hd),t(dd),Math.floor(e(hd,dd))},i.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?function(n){return r(n)%t==0}:function(n){return i.count(0,n)%t==0}):i:null}),i}var vd=pd(function(){},function(t,n){t.setTime(+t+n)},function(t,n){return n-t});vd.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?pd(function(n){n.setTime(Math.floor(n/t)*t)},function(n,e){n.setTime(+n+e*t)},function(n,e){return(e-n)/t}):vd:null};var gd=vd.range,yd=6e4,_d=6048e5,bd=pd(function(t){t.setTime(t-t.getMilliseconds())},function(t,n){t.setTime(+t+1e3*n)},function(t,n){return(n-t)/1e3},function(t){return t.getUTCSeconds()}),md=bd.range,xd=pd(function(t){t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds())},function(t,n){t.setTime(+t+n*yd)},function(t,n){return(n-t)/yd},function(t){return t.getMinutes()}),wd=xd.range,Md=pd(function(t){t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds()-t.getMinutes()*yd)},function(t,n){t.setTime(+t+36e5*n)},function(t,n){return(n-t)/36e5},function(t){return t.getHours()}),Nd=Md.range,Td=pd(function(t){t.setHours(0,0,0,0)},function(t,n){t.setDate(t.getDate()+n)},function(t,n){return(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*yd)/864e5},function(t){return t.getDate()-1}),Ad=Td.range;function Sd(t){return pd(function(n){n.setDate(n.getDate()-(n.getDay()+7-t)%7),n.setHours(0,0,0,0)},function(t,n){t.setDate(t.getDate()+7*n)},function(t,n){return(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*yd)/_d})}var kd=Sd(0),Ed=Sd(1),Cd=Sd(2),Pd=Sd(3),zd=Sd(4),Rd=Sd(5),Dd=Sd(6),qd=kd.range,Ld=Ed.range,Ud=Cd.range,Od=Pd.range,Bd=zd.range,Fd=Rd.range,Yd=Dd.range,Id=pd(function(t){t.setDate(1),t.setHours(0,0,0,0)},function(t,n){t.setMonth(t.getMonth()+n)},function(t,n){return n.getMonth()-t.getMonth()+12*(n.getFullYear()-t.getFullYear())},function(t){return t.getMonth()}),Hd=Id.range,jd=pd(function(t){t.setMonth(0,1),t.setHours(0,0,0,0)},function(t,n){t.setFullYear(t.getFullYear()+n)},function(t,n){return n.getFullYear()-t.getFullYear()},function(t){return t.getFullYear()});jd.every=function(t){return isFinite(t=Math.floor(t))&&t>0?pd(function(n){n.setFullYear(Math.floor(n.getFullYear()/t)*t),n.setMonth(0,1),n.setHours(0,0,0,0)},function(n,e){n.setFullYear(n.getFullYear()+e*t)}):null};var Vd=jd.range,Xd=pd(function(t){t.setUTCSeconds(0,0)},function(t,n){t.setTime(+t+n*yd)},function(t,n){return(n-t)/yd},function(t){return t.getUTCMinutes()}),Gd=Xd.range,$d=pd(function(t){t.setUTCMinutes(0,0,0)},function(t,n){t.setTime(+t+36e5*n)},function(t,n){return(n-t)/36e5},function(t){return t.getUTCHours()}),Wd=$d.range,Zd=pd(function(t){t.setUTCHours(0,0,0,0)},function(t,n){t.setUTCDate(t.getUTCDate()+n)},function(t,n){return(n-t)/864e5},function(t){return t.getUTCDate()-1}),Qd=Zd.range;function Kd(t){return pd(function(n){n.setUTCDate(n.getUTCDate()-(n.getUTCDay()+7-t)%7),n.setUTCHours(0,0,0,0)},function(t,n){t.setUTCDate(t.getUTCDate()+7*n)},function(t,n){return(n-t)/_d})}var Jd=Kd(0),tp=Kd(1),np=Kd(2),ep=Kd(3),rp=Kd(4),ip=Kd(5),op=Kd(6),ap=Jd.range,up=tp.range,cp=np.range,fp=ep.range,sp=rp.range,lp=ip.range,hp=op.range,dp=pd(function(t){t.setUTCDate(1),t.setUTCHours(0,0,0,0)},function(t,n){t.setUTCMonth(t.getUTCMonth()+n)},function(t,n){return n.getUTCMonth()-t.getUTCMonth()+12*(n.getUTCFullYear()-t.getUTCFullYear())},function(t){return t.getUTCMonth()}),pp=dp.range,vp=pd(function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},function(t,n){t.setUTCFullYear(t.getUTCFullYear()+n)},function(t,n){return n.getUTCFullYear()-t.getUTCFullYear()},function(t){return t.getUTCFullYear()});vp.every=function(t){return isFinite(t=Math.floor(t))&&t>0?pd(function(n){n.setUTCFullYear(Math.floor(n.getUTCFullYear()/t)*t),n.setUTCMonth(0,1),n.setUTCHours(0,0,0,0)},function(n,e){n.setUTCFullYear(n.getUTCFullYear()+e*t)}):null};var gp=vp.range;function yp(t){if(0<=t.y&&t.y<100){var n=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return n.setFullYear(t.y),n}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function _p(t){if(0<=t.y&&t.y<100){var n=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return n.setUTCFullYear(t.y),n}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function bp(t,n,e){return{y:t,m:n,d:e,H:0,M:0,S:0,L:0}}function mp(t){var n=t.dateTime,e=t.date,r=t.time,i=t.periods,o=t.days,a=t.shortDays,u=t.months,c=t.shortMonths,f=kp(i),s=Ep(i),l=kp(o),h=Ep(o),d=kp(a),p=Ep(a),v=kp(u),g=Ep(u),y=kp(c),_=Ep(c),b={a:function(t){return a[t.getDay()]},A:function(t){return o[t.getDay()]},b:function(t){return c[t.getMonth()]},B:function(t){return u[t.getMonth()]},c:null,d:Zp,e:Zp,f:nv,H:Qp,I:Kp,j:Jp,L:tv,m:ev,M:rv,p:function(t){return i[+(t.getHours()>=12)]},q:function(t){return 1+~~(t.getMonth()/3)},Q:Pv,s:zv,S:iv,u:ov,U:av,V:uv,w:cv,W:fv,x:null,X:null,y:sv,Y:lv,Z:hv,"%":Cv},m={a:function(t){return a[t.getUTCDay()]},A:function(t){return o[t.getUTCDay()]},b:function(t){return c[t.getUTCMonth()]},B:function(t){return u[t.getUTCMonth()]},c:null,d:dv,e:dv,f:_v,H:pv,I:vv,j:gv,L:yv,m:bv,M:mv,p:function(t){return i[+(t.getUTCHours()>=12)]},q:function(t){return 1+~~(t.getUTCMonth()/3)},Q:Pv,s:zv,S:xv,u:wv,U:Mv,V:Nv,w:Tv,W:Av,x:null,X:null,y:Sv,Y:kv,Z:Ev,"%":Cv},x={a:function(t,n,e){var r=d.exec(n.slice(e));return r?(t.w=p[r[0].toLowerCase()],e+r[0].length):-1},A:function(t,n,e){var r=l.exec(n.slice(e));return r?(t.w=h[r[0].toLowerCase()],e+r[0].length):-1},b:function(t,n,e){var r=y.exec(n.slice(e));return r?(t.m=_[r[0].toLowerCase()],e+r[0].length):-1},B:function(t,n,e){var r=v.exec(n.slice(e));return r?(t.m=g[r[0].toLowerCase()],e+r[0].length):-1},c:function(t,e,r){return N(t,n,e,r)},d:Fp,e:Fp,f:Xp,H:Ip,I:Ip,j:Yp,L:Vp,m:Bp,M:Hp,p:function(t,n,e){var r=f.exec(n.slice(e));return r?(t.p=s[r[0].toLowerCase()],e+r[0].length):-1},q:Op,Q:$p,s:Wp,S:jp,u:Pp,U:zp,V:Rp,w:Cp,W:Dp,x:function(t,n,r){return N(t,e,n,r)},X:function(t,n,e){return N(t,r,n,e)},y:Lp,Y:qp,Z:Up,"%":Gp};function w(t,n){return function(e){var r,i,o,a=[],u=-1,c=0,f=t.length;for(e instanceof Date||(e=new Date(+e));++u53)return null;"w"in o||(o.w=1),"Z"in o?(i=(r=_p(bp(o.y,0,1))).getUTCDay(),r=i>4||0===i?tp.ceil(r):tp(r),r=Zd.offset(r,7*(o.V-1)),o.y=r.getUTCFullYear(),o.m=r.getUTCMonth(),o.d=r.getUTCDate()+(o.w+6)%7):(i=(r=yp(bp(o.y,0,1))).getDay(),r=i>4||0===i?Ed.ceil(r):Ed(r),r=Td.offset(r,7*(o.V-1)),o.y=r.getFullYear(),o.m=r.getMonth(),o.d=r.getDate()+(o.w+6)%7)}else("W"in o||"U"in o)&&("w"in o||(o.w="u"in o?o.u%7:"W"in o?1:0),i="Z"in o?_p(bp(o.y,0,1)).getUTCDay():yp(bp(o.y,0,1)).getDay(),o.m=0,o.d="W"in o?(o.w+6)%7+7*o.W-(i+5)%7:o.w+7*o.U-(i+6)%7);return"Z"in o?(o.H+=o.Z/100|0,o.M+=o.Z%100,_p(o)):yp(o)}}function N(t,n,e,r){for(var i,o,a=0,u=n.length,c=e.length;a=c)return-1;if(37===(i=n.charCodeAt(a++))){if(i=n.charAt(a++),!(o=x[i in wp?n.charAt(a++):i])||(r=o(t,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}return b.x=w(e,b),b.X=w(r,b),b.c=w(n,b),m.x=w(e,m),m.X=w(r,m),m.c=w(n,m),{format:function(t){var n=w(t+="",b);return n.toString=function(){return t},n},parse:function(t){var n=M(t+="",!1);return n.toString=function(){return t},n},utcFormat:function(t){var n=w(t+="",m);return n.toString=function(){return t},n},utcParse:function(t){var n=M(t+="",!0);return n.toString=function(){return t},n}}}var xp,wp={"-":"",_:" ",0:"0"},Mp=/^\s*\d+/,Np=/^%/,Tp=/[\\^$*+?|[\]().{}]/g;function Ap(t,n,e){var r=t<0?"-":"",i=(r?-t:t)+"",o=i.length;return r+(o68?1900:2e3),e+r[0].length):-1}function Up(t,n,e){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(n.slice(e,e+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),e+r[0].length):-1}function Op(t,n,e){var r=Mp.exec(n.slice(e,e+1));return r?(t.q=3*r[0]-3,e+r[0].length):-1}function Bp(t,n,e){var r=Mp.exec(n.slice(e,e+2));return r?(t.m=r[0]-1,e+r[0].length):-1}function Fp(t,n,e){var r=Mp.exec(n.slice(e,e+2));return r?(t.d=+r[0],e+r[0].length):-1}function Yp(t,n,e){var r=Mp.exec(n.slice(e,e+3));return r?(t.m=0,t.d=+r[0],e+r[0].length):-1}function Ip(t,n,e){var r=Mp.exec(n.slice(e,e+2));return r?(t.H=+r[0],e+r[0].length):-1}function Hp(t,n,e){var r=Mp.exec(n.slice(e,e+2));return r?(t.M=+r[0],e+r[0].length):-1}function jp(t,n,e){var r=Mp.exec(n.slice(e,e+2));return r?(t.S=+r[0],e+r[0].length):-1}function Vp(t,n,e){var r=Mp.exec(n.slice(e,e+3));return r?(t.L=+r[0],e+r[0].length):-1}function Xp(t,n,e){var r=Mp.exec(n.slice(e,e+6));return r?(t.L=Math.floor(r[0]/1e3),e+r[0].length):-1}function Gp(t,n,e){var r=Np.exec(n.slice(e,e+1));return r?e+r[0].length:-1}function $p(t,n,e){var r=Mp.exec(n.slice(e));return r?(t.Q=+r[0],e+r[0].length):-1}function Wp(t,n,e){var r=Mp.exec(n.slice(e));return r?(t.s=+r[0],e+r[0].length):-1}function Zp(t,n){return Ap(t.getDate(),n,2)}function Qp(t,n){return Ap(t.getHours(),n,2)}function Kp(t,n){return Ap(t.getHours()%12||12,n,2)}function Jp(t,n){return Ap(1+Td.count(jd(t),t),n,3)}function tv(t,n){return Ap(t.getMilliseconds(),n,3)}function nv(t,n){return tv(t,n)+"000"}function ev(t,n){return Ap(t.getMonth()+1,n,2)}function rv(t,n){return Ap(t.getMinutes(),n,2)}function iv(t,n){return Ap(t.getSeconds(),n,2)}function ov(t){var n=t.getDay();return 0===n?7:n}function av(t,n){return Ap(kd.count(jd(t)-1,t),n,2)}function uv(t,n){var e=t.getDay();return t=e>=4||0===e?zd(t):zd.ceil(t),Ap(zd.count(jd(t),t)+(4===jd(t).getDay()),n,2)}function cv(t){return t.getDay()}function fv(t,n){return Ap(Ed.count(jd(t)-1,t),n,2)}function sv(t,n){return Ap(t.getFullYear()%100,n,2)}function lv(t,n){return Ap(t.getFullYear()%1e4,n,4)}function hv(t){var n=t.getTimezoneOffset();return(n>0?"-":(n*=-1,"+"))+Ap(n/60|0,"0",2)+Ap(n%60,"0",2)}function dv(t,n){return Ap(t.getUTCDate(),n,2)}function pv(t,n){return Ap(t.getUTCHours(),n,2)}function vv(t,n){return Ap(t.getUTCHours()%12||12,n,2)}function gv(t,n){return Ap(1+Zd.count(vp(t),t),n,3)}function yv(t,n){return Ap(t.getUTCMilliseconds(),n,3)}function _v(t,n){return yv(t,n)+"000"}function bv(t,n){return Ap(t.getUTCMonth()+1,n,2)}function mv(t,n){return Ap(t.getUTCMinutes(),n,2)}function xv(t,n){return Ap(t.getUTCSeconds(),n,2)}function wv(t){var n=t.getUTCDay();return 0===n?7:n}function Mv(t,n){return Ap(Jd.count(vp(t)-1,t),n,2)}function Nv(t,n){var e=t.getUTCDay();return t=e>=4||0===e?rp(t):rp.ceil(t),Ap(rp.count(vp(t),t)+(4===vp(t).getUTCDay()),n,2)}function Tv(t){return t.getUTCDay()}function Av(t,n){return Ap(tp.count(vp(t)-1,t),n,2)}function Sv(t,n){return Ap(t.getUTCFullYear()%100,n,2)}function kv(t,n){return Ap(t.getUTCFullYear()%1e4,n,4)}function Ev(){return"+0000"}function Cv(){return"%"}function Pv(t){return+t}function zv(t){return Math.floor(+t/1e3)}function Rv(n){return xp=mp(n),t.timeFormat=xp.format,t.timeParse=xp.parse,t.utcFormat=xp.utcFormat,t.utcParse=xp.utcParse,xp}Rv({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});var Dv=Date.prototype.toISOString?function(t){return t.toISOString()}:t.utcFormat("%Y-%m-%dT%H:%M:%S.%LZ");var qv=+new Date("2000-01-01T00:00:00.000Z")?function(t){var n=new Date(t);return isNaN(n)?null:n}:t.utcParse("%Y-%m-%dT%H:%M:%S.%LZ"),Lv=1e3,Uv=60*Lv,Ov=60*Uv,Bv=24*Ov,Fv=7*Bv,Yv=30*Bv,Iv=365*Bv;function Hv(t){return new Date(t)}function jv(t){return t instanceof Date?+t:+new Date(+t)}function Vv(t,n,r,i,o,a,u,c,f){var s=Gh(Fh,Fh),l=s.invert,h=s.domain,d=f(".%L"),p=f(":%S"),v=f("%I:%M"),g=f("%I %p"),y=f("%a %d"),_=f("%b %d"),b=f("%B"),m=f("%Y"),x=[[u,1,Lv],[u,5,5*Lv],[u,15,15*Lv],[u,30,30*Lv],[a,1,Uv],[a,5,5*Uv],[a,15,15*Uv],[a,30,30*Uv],[o,1,Ov],[o,3,3*Ov],[o,6,6*Ov],[o,12,12*Ov],[i,1,Bv],[i,2,2*Bv],[r,1,Fv],[n,1,Yv],[n,3,3*Yv],[t,1,Iv]];function M(e){return(u(e)=1?Py:t<=-1?-Py:Math.asin(t)}function Dy(t){return t.innerRadius}function qy(t){return t.outerRadius}function Ly(t){return t.startAngle}function Uy(t){return t.endAngle}function Oy(t){return t&&t.padAngle}function By(t,n,e,r,i,o,a){var u=t-e,c=n-r,f=(a?o:-o)/ky(u*u+c*c),s=f*c,l=-f*u,h=t+s,d=n+l,p=e+s,v=r+l,g=(h+p)/2,y=(d+v)/2,_=p-h,b=v-d,m=_*_+b*b,x=i-o,w=h*v-p*d,M=(b<0?-1:1)*ky(Ty(0,x*x*m-w*w)),N=(w*b-_*M)/m,T=(-w*_-b*M)/m,A=(w*b+_*M)/m,S=(-w*_+b*M)/m,k=N-g,E=T-y,C=A-g,P=S-y;return k*k+E*E>C*C+P*P&&(N=A,T=S),{cx:N,cy:T,x01:-s,y01:-l,x11:N*(i/x-1),y11:T*(i/x-1)}}function Fy(t){this._context=t}function Yy(t){return new Fy(t)}function Iy(t){return t[0]}function Hy(t){return t[1]}function jy(){var t=Iy,n=Hy,e=xy(!0),r=null,i=Yy,o=null;function a(a){var u,c,f,s=a.length,l=!1;for(null==r&&(o=i(f=no())),u=0;u<=s;++u)!(u=s;--l)u.point(g[l],y[l]);u.lineEnd(),u.areaEnd()}v&&(g[f]=+t(h,f,c),y[f]=+e(h,f,c),u.point(n?+n(h,f,c):g[f],r?+r(h,f,c):y[f]))}if(d)return u=null,d+""||null}function f(){return jy().defined(i).curve(a).context(o)}return c.x=function(e){return arguments.length?(t="function"==typeof e?e:xy(+e),n=null,c):t},c.x0=function(n){return arguments.length?(t="function"==typeof n?n:xy(+n),c):t},c.x1=function(t){return arguments.length?(n=null==t?null:"function"==typeof t?t:xy(+t),c):n},c.y=function(t){return arguments.length?(e="function"==typeof t?t:xy(+t),r=null,c):e},c.y0=function(t){return arguments.length?(e="function"==typeof t?t:xy(+t),c):e},c.y1=function(t){return arguments.length?(r=null==t?null:"function"==typeof t?t:xy(+t),c):r},c.lineX0=c.lineY0=function(){return f().x(t).y(e)},c.lineY1=function(){return f().x(t).y(r)},c.lineX1=function(){return f().x(n).y(e)},c.defined=function(t){return arguments.length?(i="function"==typeof t?t:xy(!!t),c):i},c.curve=function(t){return arguments.length?(a=t,null!=o&&(u=a(o)),c):a},c.context=function(t){return arguments.length?(null==t?o=u=null:u=a(o=t),c):o},c}function Xy(t,n){return nt?1:n>=t?0:NaN}function Gy(t){return t}Fy.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:this._context.lineTo(t,n)}}};var $y=Zy(Yy);function Wy(t){this._curve=t}function Zy(t){function n(n){return new Wy(t(n))}return n._curve=t,n}function Qy(t){var n=t.curve;return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t.curve=function(t){return arguments.length?n(Zy(t)):n()._curve},t}function Ky(){return Qy(jy().curve($y))}function Jy(){var t=Vy().curve($y),n=t.curve,e=t.lineX0,r=t.lineX1,i=t.lineY0,o=t.lineY1;return t.angle=t.x,delete t.x,t.startAngle=t.x0,delete t.x0,t.endAngle=t.x1,delete t.x1,t.radius=t.y,delete t.y,t.innerRadius=t.y0,delete t.y0,t.outerRadius=t.y1,delete t.y1,t.lineStartAngle=function(){return Qy(e())},delete t.lineX0,t.lineEndAngle=function(){return Qy(r())},delete t.lineX1,t.lineInnerRadius=function(){return Qy(i())},delete t.lineY0,t.lineOuterRadius=function(){return Qy(o())},delete t.lineY1,t.curve=function(t){return arguments.length?n(Zy(t)):n()._curve},t}function t_(t,n){return[(n=+n)*Math.cos(t-=Math.PI/2),n*Math.sin(t)]}Wy.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(t,n){this._curve.point(n*Math.sin(t),n*-Math.cos(t))}};var n_=Array.prototype.slice;function e_(t){return t.source}function r_(t){return t.target}function i_(t){var n=e_,e=r_,r=Iy,i=Hy,o=null;function a(){var a,u=n_.call(arguments),c=n.apply(this,u),f=e.apply(this,u);if(o||(o=a=no()),t(o,+r.apply(this,(u[0]=c,u)),+i.apply(this,u),+r.apply(this,(u[0]=f,u)),+i.apply(this,u)),a)return o=null,a+""||null}return a.source=function(t){return arguments.length?(n=t,a):n},a.target=function(t){return arguments.length?(e=t,a):e},a.x=function(t){return arguments.length?(r="function"==typeof t?t:xy(+t),a):r},a.y=function(t){return arguments.length?(i="function"==typeof t?t:xy(+t),a):i},a.context=function(t){return arguments.length?(o=null==t?null:t,a):o},a}function o_(t,n,e,r,i){t.moveTo(n,e),t.bezierCurveTo(n=(n+r)/2,e,n,i,r,i)}function a_(t,n,e,r,i){t.moveTo(n,e),t.bezierCurveTo(n,e=(e+i)/2,r,e,r,i)}function u_(t,n,e,r,i){var o=t_(n,e),a=t_(n,e=(e+i)/2),u=t_(r,e),c=t_(r,i);t.moveTo(o[0],o[1]),t.bezierCurveTo(a[0],a[1],u[0],u[1],c[0],c[1])}var c_={draw:function(t,n){var e=Math.sqrt(n/Cy);t.moveTo(e,0),t.arc(0,0,e,0,zy)}},f_={draw:function(t,n){var e=Math.sqrt(n/5)/2;t.moveTo(-3*e,-e),t.lineTo(-e,-e),t.lineTo(-e,-3*e),t.lineTo(e,-3*e),t.lineTo(e,-e),t.lineTo(3*e,-e),t.lineTo(3*e,e),t.lineTo(e,e),t.lineTo(e,3*e),t.lineTo(-e,3*e),t.lineTo(-e,e),t.lineTo(-3*e,e),t.closePath()}},s_=Math.sqrt(1/3),l_=2*s_,h_={draw:function(t,n){var e=Math.sqrt(n/l_),r=e*s_;t.moveTo(0,-e),t.lineTo(r,0),t.lineTo(0,e),t.lineTo(-r,0),t.closePath()}},d_=Math.sin(Cy/10)/Math.sin(7*Cy/10),p_=Math.sin(zy/10)*d_,v_=-Math.cos(zy/10)*d_,g_={draw:function(t,n){var e=Math.sqrt(.8908130915292852*n),r=p_*e,i=v_*e;t.moveTo(0,-e),t.lineTo(r,i);for(var o=1;o<5;++o){var a=zy*o/5,u=Math.cos(a),c=Math.sin(a);t.lineTo(c*e,-u*e),t.lineTo(u*r-c*i,c*r+u*i)}t.closePath()}},y_={draw:function(t,n){var e=Math.sqrt(n),r=-e/2;t.rect(r,r,e,e)}},__=Math.sqrt(3),b_={draw:function(t,n){var e=-Math.sqrt(n/(3*__));t.moveTo(0,2*e),t.lineTo(-__*e,-e),t.lineTo(__*e,-e),t.closePath()}},m_=Math.sqrt(3)/2,x_=1/Math.sqrt(12),w_=3*(x_/2+1),M_={draw:function(t,n){var e=Math.sqrt(n/w_),r=e/2,i=e*x_,o=r,a=e*x_+e,u=-o,c=a;t.moveTo(r,i),t.lineTo(o,a),t.lineTo(u,c),t.lineTo(-.5*r-m_*i,m_*r+-.5*i),t.lineTo(-.5*o-m_*a,m_*o+-.5*a),t.lineTo(-.5*u-m_*c,m_*u+-.5*c),t.lineTo(-.5*r+m_*i,-.5*i-m_*r),t.lineTo(-.5*o+m_*a,-.5*a-m_*o),t.lineTo(-.5*u+m_*c,-.5*c-m_*u),t.closePath()}},N_=[c_,f_,h_,y_,g_,b_,M_];function T_(){}function A_(t,n,e){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+n)/6,(t._y0+4*t._y1+e)/6)}function S_(t){this._context=t}function k_(t){this._context=t}function E_(t){this._context=t}function C_(t,n){this._basis=new S_(t),this._beta=n}S_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:A_(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:A_(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}},k_.prototype={areaStart:T_,areaEnd:T_,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._x2=t,this._y2=n;break;case 1:this._point=2,this._x3=t,this._y3=n;break;case 2:this._point=3,this._x4=t,this._y4=n,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+n)/6);break;default:A_(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}},E_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var e=(this._x0+4*this._x1+t)/6,r=(this._y0+4*this._y1+n)/6;this._line?this._context.lineTo(e,r):this._context.moveTo(e,r);break;case 3:this._point=4;default:A_(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}},C_.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var t=this._x,n=this._y,e=t.length-1;if(e>0)for(var r,i=t[0],o=n[0],a=t[e]-i,u=n[e]-o,c=-1;++c<=e;)r=c/e,this._basis.point(this._beta*t[c]+(1-this._beta)*(i+r*a),this._beta*n[c]+(1-this._beta)*(o+r*u));this._x=this._y=null,this._basis.lineEnd()},point:function(t,n){this._x.push(+t),this._y.push(+n)}};var P_=function t(n){function e(t){return 1===n?new S_(t):new C_(t,n)}return e.beta=function(n){return t(+n)},e}(.85);function z_(t,n,e){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-n),t._y2+t._k*(t._y1-e),t._x2,t._y2)}function R_(t,n){this._context=t,this._k=(1-n)/6}R_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:z_(this,this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2,this._x1=t,this._y1=n;break;case 2:this._point=3;default:z_(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var D_=function t(n){function e(t){return new R_(t,n)}return e.tension=function(n){return t(+n)},e}(0);function q_(t,n){this._context=t,this._k=(1-n)/6}q_.prototype={areaStart:T_,areaEnd:T_,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._x3=t,this._y3=n;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=n);break;case 2:this._point=3,this._x5=t,this._y5=n;break;default:z_(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var L_=function t(n){function e(t){return new q_(t,n)}return e.tension=function(n){return t(+n)},e}(0);function U_(t,n){this._context=t,this._k=(1-n)/6}U_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:z_(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var O_=function t(n){function e(t){return new U_(t,n)}return e.tension=function(n){return t(+n)},e}(0);function B_(t,n,e){var r=t._x1,i=t._y1,o=t._x2,a=t._y2;if(t._l01_a>Ey){var u=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,c=3*t._l01_a*(t._l01_a+t._l12_a);r=(r*u-t._x0*t._l12_2a+t._x2*t._l01_2a)/c,i=(i*u-t._y0*t._l12_2a+t._y2*t._l01_2a)/c}if(t._l23_a>Ey){var f=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,s=3*t._l23_a*(t._l23_a+t._l12_a);o=(o*f+t._x1*t._l23_2a-n*t._l12_2a)/s,a=(a*f+t._y1*t._l23_2a-e*t._l12_2a)/s}t._context.bezierCurveTo(r,i,o,a,t._x2,t._y2)}function F_(t,n){this._context=t,this._alpha=n}F_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3;default:B_(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Y_=function t(n){function e(t){return n?new F_(t,n):new R_(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);function I_(t,n){this._context=t,this._alpha=n}I_.prototype={areaStart:T_,areaEnd:T_,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=n;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=n);break;case 2:this._point=3,this._x5=t,this._y5=n;break;default:B_(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var H_=function t(n){function e(t){return n?new I_(t,n):new q_(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);function j_(t,n){this._context=t,this._alpha=n}j_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:B_(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var V_=function t(n){function e(t){return n?new j_(t,n):new U_(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);function X_(t){this._context=t}function G_(t){return t<0?-1:1}function $_(t,n,e){var r=t._x1-t._x0,i=n-t._x1,o=(t._y1-t._y0)/(r||i<0&&-0),a=(e-t._y1)/(i||r<0&&-0),u=(o*i+a*r)/(r+i);return(G_(o)+G_(a))*Math.min(Math.abs(o),Math.abs(a),.5*Math.abs(u))||0}function W_(t,n){var e=t._x1-t._x0;return e?(3*(t._y1-t._y0)/e-n)/2:n}function Z_(t,n,e){var r=t._x0,i=t._y0,o=t._x1,a=t._y1,u=(o-r)/3;t._context.bezierCurveTo(r+u,i+u*n,o-u,a-u*e,o,a)}function Q_(t){this._context=t}function K_(t){this._context=new J_(t)}function J_(t){this._context=t}function tb(t){this._context=t}function nb(t){var n,e,r=t.length-1,i=new Array(r),o=new Array(r),a=new Array(r);for(i[0]=0,o[0]=2,a[0]=t[0]+2*t[1],n=1;n=0;--n)i[n]=(a[n]-i[n+1])/o[n];for(o[r-1]=(t[r]+i[r-1])/2,n=0;n1)for(var e,r,i,o=1,a=t[n[0]],u=a.length;o=0;)e[n]=n;return e}function ob(t,n){return t[n]}function ab(t){var n=t.map(ub);return ib(t).sort(function(t,e){return n[t]-n[e]})}function ub(t){for(var n,e=-1,r=0,i=t.length,o=-1/0;++eo&&(o=n,r=e);return r}function cb(t){var n=t.map(fb);return ib(t).sort(function(t,e){return n[t]-n[e]})}function fb(t){for(var n,e=0,r=-1,i=t.length;++r0)){if(o/=h,h<0){if(o0){if(o>l)return;o>s&&(s=o)}if(o=r-c,h||!(o<0)){if(o/=h,h<0){if(o>l)return;o>s&&(s=o)}else if(h>0){if(o0)){if(o/=d,d<0){if(o0){if(o>l)return;o>s&&(s=o)}if(o=i-f,d||!(o<0)){if(o/=d,d<0){if(o>l)return;o>s&&(s=o)}else if(d>0){if(o0||l<1)||(s>0&&(t[0]=[c+s*h,f+s*d]),l<1&&(t[1]=[c+l*h,f+l*d]),!0)}}}}}function wb(t,n,e,r,i){var o=t[1];if(o)return!0;var a,u,c=t[0],f=t.left,s=t.right,l=f[0],h=f[1],d=s[0],p=s[1],v=(l+d)/2,g=(h+p)/2;if(p===h){if(v=r)return;if(l>d){if(c){if(c[1]>=i)return}else c=[v,e];o=[v,i]}else{if(c){if(c[1]1)if(l>d){if(c){if(c[1]>=i)return}else c=[(e-u)/a,e];o=[(i-u)/a,i]}else{if(c){if(c[1]=r)return}else c=[n,a*n+u];o=[r,a*r+u]}else{if(c){if(c[0]=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,n),this._context.lineTo(t,n);else{var e=this._x*(1-this._t)+t*this._t;this._context.lineTo(e,this._y),this._context.lineTo(e,n)}}this._x=t,this._y=n}},db.prototype={constructor:db,insert:function(t,n){var e,r,i;if(t){if(n.P=t,n.N=t.N,t.N&&(t.N.P=n),t.N=n,t.R){for(t=t.R;t.L;)t=t.L;t.L=n}else t.R=n;e=t}else this._?(t=yb(this._),n.P=null,n.N=t,t.P=t.L=n,e=t):(n.P=n.N=null,this._=n,e=null);for(n.L=n.R=null,n.U=e,n.C=!0,t=n;e&&e.C;)e===(r=e.U).L?(i=r.R)&&i.C?(e.C=i.C=!1,r.C=!0,t=r):(t===e.R&&(vb(this,e),e=(t=e).U),e.C=!1,r.C=!0,gb(this,r)):(i=r.L)&&i.C?(e.C=i.C=!1,r.C=!0,t=r):(t===e.L&&(gb(this,e),e=(t=e).U),e.C=!1,r.C=!0,vb(this,r)),e=t.U;this._.C=!1},remove:function(t){t.N&&(t.N.P=t.P),t.P&&(t.P.N=t.N),t.N=t.P=null;var n,e,r,i=t.U,o=t.L,a=t.R;if(e=o?a?yb(a):o:a,i?i.L===t?i.L=e:i.R=e:this._=e,o&&a?(r=e.C,e.C=t.C,e.L=o,o.U=e,e!==a?(i=e.U,e.U=t.U,t=e.R,i.L=t,e.R=a,a.U=e):(e.U=i,i=e,t=e.R)):(r=t.C,t=e),t&&(t.U=i),!r)if(t&&t.C)t.C=!1;else{do{if(t===this._)break;if(t===i.L){if((n=i.R).C&&(n.C=!1,i.C=!0,vb(this,i),n=i.R),n.L&&n.L.C||n.R&&n.R.C){n.R&&n.R.C||(n.L.C=!1,n.C=!0,gb(this,n),n=i.R),n.C=i.C,i.C=n.R.C=!1,vb(this,i),t=this._;break}}else if((n=i.L).C&&(n.C=!1,i.C=!0,gb(this,i),n=i.L),n.L&&n.L.C||n.R&&n.R.C){n.L&&n.L.C||(n.R.C=!1,n.C=!0,vb(this,n),n=i.L),n.C=i.C,i.C=n.L.C=!1,gb(this,i),t=this._;break}n.C=!0,t=i,i=i.U}while(!t.C);t&&(t.C=!1)}}};var Ab,Sb=[];function kb(){pb(this),this.x=this.y=this.arc=this.site=this.cy=null}function Eb(t){var n=t.P,e=t.N;if(n&&e){var r=n.site,i=t.site,o=e.site;if(r!==o){var a=i[0],u=i[1],c=r[0]-a,f=r[1]-u,s=o[0]-a,l=o[1]-u,h=2*(c*l-f*s);if(!(h>=-jb)){var d=c*c+f*f,p=s*s+l*l,v=(l*d-f*p)/h,g=(c*p-s*d)/h,y=Sb.pop()||new kb;y.arc=t,y.site=i,y.x=v+a,y.y=(y.cy=g+u)+Math.sqrt(v*v+g*g),t.circle=y;for(var _=null,b=Yb._;b;)if(y.yHb)u=u.L;else{if(!((i=o-Ob(u,a))>Hb)){r>-Hb?(n=u.P,e=u):i>-Hb?(n=u,e=u.N):n=e=u;break}if(!u.R){n=u;break}u=u.R}!function(t){Fb[t.index]={site:t,halfedges:[]}}(t);var c=Rb(t);if(Bb.insert(n,c),n||e){if(n===e)return Cb(n),e=Rb(n.site),Bb.insert(c,e),c.edge=e.edge=_b(n.site,c.site),Eb(n),void Eb(e);if(e){Cb(n),Cb(e);var f=n.site,s=f[0],l=f[1],h=t[0]-s,d=t[1]-l,p=e.site,v=p[0]-s,g=p[1]-l,y=2*(h*g-d*v),_=h*h+d*d,b=v*v+g*g,m=[(g*_-d*b)/y+s,(h*b-v*_)/y+l];mb(e.edge,f,p,m),c.edge=_b(f,t,null,m),e.edge=_b(t,p,null,m),Eb(n),Eb(e)}else c.edge=_b(n.site,c.site)}}function Ub(t,n){var e=t.site,r=e[0],i=e[1],o=i-n;if(!o)return r;var a=t.P;if(!a)return-1/0;var u=(e=a.site)[0],c=e[1],f=c-n;if(!f)return u;var s=u-r,l=1/o-1/f,h=s/f;return l?(-h+Math.sqrt(h*h-2*l*(s*s/(-2*f)-c+f/2+i-o/2)))/l+r:(r+u)/2}function Ob(t,n){var e=t.N;if(e)return Ub(e,n);var r=t.site;return r[1]===n?r[0]:1/0}var Bb,Fb,Yb,Ib,Hb=1e-6,jb=1e-12;function Vb(t,n,e){return(t[0]-e[0])*(n[1]-t[1])-(t[0]-n[0])*(e[1]-t[1])}function Xb(t,n){return n[1]-t[1]||n[0]-t[0]}function Gb(t,n){var e,r,i,o=t.sort(Xb).pop();for(Ib=[],Fb=new Array(t.length),Bb=new db,Yb=new db;;)if(i=Ab,o&&(!i||o[1]Hb||Math.abs(i[0][1]-i[1][1])>Hb)||delete Ib[o]}(a,u,c,f),function(t,n,e,r){var i,o,a,u,c,f,s,l,h,d,p,v,g=Fb.length,y=!0;for(i=0;iHb||Math.abs(v-h)>Hb)&&(c.splice(u,0,Ib.push(bb(a,d,Math.abs(p-t)Hb?[t,Math.abs(l-t)Hb?[Math.abs(h-r)Hb?[e,Math.abs(l-e)Hb?[Math.abs(h-n)=u)return null;var c=t-i.site[0],f=n-i.site[1],s=c*c+f*f;do{i=o.cells[r=a],a=null,i.halfedges.forEach(function(e){var r=o.edges[e],u=r.left;if(u!==i.site&&u||(u=r.right)){var c=t-u[0],f=n-u[1],l=c*c+f*f;lr?(r+i)/2:Math.min(0,r)||Math.max(0,i),a>o?(o+a)/2:Math.min(0,o)||Math.max(0,a))}Kb.prototype=Zb.prototype,t.FormatSpecifier=Ba,t.active=function(t,n){var e,r,i=t.__transition;if(i)for(r in n=null==n?null:n+"",i)if((e=i[r]).state>xr&&e.name===n)return new Ur([[t]],yi,n,+r);return null},t.arc=function(){var t=Dy,n=qy,e=xy(0),r=null,i=Ly,o=Uy,a=Oy,u=null;function c(){var c,f,s=+t.apply(this,arguments),l=+n.apply(this,arguments),h=i.apply(this,arguments)-Py,d=o.apply(this,arguments)-Py,p=wy(d-h),v=d>h;if(u||(u=c=no()),lEy)if(p>zy-Ey)u.moveTo(l*Ny(h),l*Sy(h)),u.arc(0,0,l,h,d,!v),s>Ey&&(u.moveTo(s*Ny(d),s*Sy(d)),u.arc(0,0,s,d,h,v));else{var g,y,_=h,b=d,m=h,x=d,w=p,M=p,N=a.apply(this,arguments)/2,T=N>Ey&&(r?+r.apply(this,arguments):ky(s*s+l*l)),A=Ay(wy(l-s)/2,+e.apply(this,arguments)),S=A,k=A;if(T>Ey){var E=Ry(T/s*Sy(N)),C=Ry(T/l*Sy(N));(w-=2*E)>Ey?(m+=E*=v?1:-1,x-=E):(w=0,m=x=(h+d)/2),(M-=2*C)>Ey?(_+=C*=v?1:-1,b-=C):(M=0,_=b=(h+d)/2)}var P=l*Ny(_),z=l*Sy(_),R=s*Ny(x),D=s*Sy(x);if(A>Ey){var q,L=l*Ny(b),U=l*Sy(b),O=s*Ny(m),B=s*Sy(m);if(p1?0:t<-1?Cy:Math.acos(t)}((F*I+Y*H)/(ky(F*F+Y*Y)*ky(I*I+H*H)))/2),V=ky(q[0]*q[0]+q[1]*q[1]);S=Ay(A,(s-V)/(j-1)),k=Ay(A,(l-V)/(j+1))}}M>Ey?k>Ey?(g=By(O,B,P,z,l,k,v),y=By(L,U,R,D,l,k,v),u.moveTo(g.cx+g.x01,g.cy+g.y01),kEy&&w>Ey?S>Ey?(g=By(R,D,L,U,s,-S,v),y=By(P,z,O,B,s,-S,v),u.lineTo(g.cx+g.x01,g.cy+g.y01),S>a,f=i+2*u>>a,s=bo(20);function l(r){var i=new Float32Array(c*f),l=new Float32Array(c*f);r.forEach(function(r,o,s){var l=+t(r,o,s)+u>>a,h=+n(r,o,s)+u>>a,d=+e(r,o,s);l>=0&&l=0&&h>a),So({width:c,height:f,data:l},{width:c,height:f,data:i},o>>a),Ao({width:c,height:f,data:i},{width:c,height:f,data:l},o>>a),So({width:c,height:f,data:l},{width:c,height:f,data:i},o>>a),Ao({width:c,height:f,data:i},{width:c,height:f,data:l},o>>a),So({width:c,height:f,data:l},{width:c,height:f,data:i},o>>a);var d=s(i);if(!Array.isArray(d)){var p=T(i);d=w(0,p,d),(d=g(0,Math.floor(p/d)*d,d)).shift()}return To().thresholds(d).size([c,f])(i).map(h)}function h(t){return t.value*=Math.pow(2,-2*a),t.coordinates.forEach(d),t}function d(t){t.forEach(p)}function p(t){t.forEach(v)}function v(t){t[0]=t[0]*Math.pow(2,a)-u,t[1]=t[1]*Math.pow(2,a)-u}function y(){return c=r+2*(u=3*o)>>a,f=i+2*u>>a,l}return l.x=function(n){return arguments.length?(t="function"==typeof n?n:bo(+n),l):t},l.y=function(t){return arguments.length?(n="function"==typeof t?t:bo(+t),l):n},l.weight=function(t){return arguments.length?(e="function"==typeof t?t:bo(+t),l):e},l.size=function(t){if(!arguments.length)return[r,i];var n=Math.ceil(t[0]),e=Math.ceil(t[1]);if(!(n>=0||n>=0))throw new Error("invalid size");return r=n,i=e,y()},l.cellSize=function(t){if(!arguments.length)return 1<=1))throw new Error("invalid cell size");return a=Math.floor(Math.log(t)/Math.LN2),y()},l.thresholds=function(t){return arguments.length?(s="function"==typeof t?t:Array.isArray(t)?bo(yo.call(t)):bo(t),l):s},l.bandwidth=function(t){if(!arguments.length)return Math.sqrt(o*(o+1));if(!((t=+t)>=0))throw new Error("invalid bandwidth");return o=Math.round((Math.sqrt(4*t*t+1)-1)/2),y()},l},t.contours=To,t.create=function(t){return Rt(Z(t).call(document.documentElement))},t.creator=Z,t.cross=function(t,n,e){var r,i,o,u,c=t.length,f=n.length,s=new Array(c*f);for(null==e&&(e=a),r=o=0;rt?1:n>=t?0:NaN},t.deviation=f,t.dispatch=I,t.drag=function(){var n,e,r,i,o=Gt,a=$t,u=Wt,c=Zt,f={},s=I("start","drag","end"),l=0,h=0;function d(t){t.on("mousedown.drag",p).filter(c).on("touchstart.drag",y).on("touchmove.drag",_).on("touchend.drag touchcancel.drag",b).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function p(){if(!i&&o.apply(this,arguments)){var u=m("mouse",a.apply(this,arguments),Bt,this,arguments);u&&(Rt(t.event.view).on("mousemove.drag",v,!0).on("mouseup.drag",g,!0),Ht(t.event.view),Yt(),r=!1,n=t.event.clientX,e=t.event.clientY,u("start"))}}function v(){if(It(),!r){var i=t.event.clientX-n,o=t.event.clientY-e;r=i*i+o*o>h}f.mouse("drag")}function g(){Rt(t.event.view).on("mousemove.drag mouseup.drag",null),jt(t.event.view,r),It(),f.mouse("end")}function y(){if(o.apply(this,arguments)){var n,e,r=t.event.changedTouches,i=a.apply(this,arguments),u=r.length;for(n=0;nc+d||if+d||ou.index){var p=c-a.x-a.vx,v=f-a.y-a.vy,g=p*p+v*v;gt.r&&(t.r=t[n].r)}function u(){if(n){var r,i,o=n.length;for(e=new Array(o),r=0;r=a)){(t.data!==n||t.next)&&(0===s&&(d+=(s=ya())*s),0===l&&(d+=(l=ya())*l),d1?(null==e?u.remove(t):u.set(t,d(e)),n):u.get(t)},find:function(n,e,r){var i,o,a,u,c,f=0,s=t.length;for(null==r?r=1/0:r*=r,f=0;f1?(f.on(t,e),n):f.on(t)}}},t.forceX=function(t){var n,e,r,i=ga(.1);function o(t){for(var i,o=0,a=n.length;o=.12&&i<.234&&r>=-.425&&r<-.214?u:i>=.166&&i<.234&&r>=-.214&&r<-.115?c:a).invert(t)},s.stream=function(e){return t&&n===e?t:(r=[a.stream(n=e),u.stream(e),c.stream(e)],i=r.length,t={point:function(t,n){for(var e=-1;++ePc(r[0],r[1])&&(r[1]=i[1]),Pc(i[0],r[1])>Pc(r[0],r[1])&&(r[0]=i[0])):o.push(r=i);for(a=-1/0,n=0,r=o[e=o.length-1];n<=e;r=i,++n)i=o[n],(u=Pc(r[1],i[0]))>a&&(a=u,Zu=i[0],Ku=r[1])}return ic=oc=null,Zu===1/0||Qu===1/0?[[NaN,NaN],[NaN,NaN]]:[[Zu,Qu],[Ku,Ju]]},t.geoCentroid=function(t){ac=uc=cc=fc=sc=lc=hc=dc=pc=vc=gc=0,Cu(t,Dc);var n=pc,e=vc,r=gc,i=n*n+e*e+r*r;return i2?t[2]+90:90]):[(t=e())[0],t[1],t[2]-90]},e([0,0,90]).scale(159.155)},t.geoTransverseMercatorRaw=Nl,t.gray=function(t,n){return new Bn(t,0,0,null==n?1:n)},t.hcl=Vn,t.hierarchy=El,t.histogram=function(){var t=v,n=s,e=M;function r(r){var o,a,u=r.length,c=new Array(u);for(o=0;ol;)h.pop(),--d;var p,v=new Array(d+1);for(o=0;o<=d;++o)(p=v[o]=[]).x0=o>0?h[o-1]:s,p.x1=o1)&&(t-=Math.floor(t));var n=Math.abs(t-.5);return hy.h=360*t-100,hy.s=1.5-1.5*n,hy.l=.8-.9*n,hy+""},t.interpolateRdBu=_g,t.interpolateRdGy=mg,t.interpolateRdPu=Ig,t.interpolateRdYlBu=wg,t.interpolateRdYlGn=Ng,t.interpolateReds=ay,t.interpolateRgb=he,t.interpolateRgbBasis=pe,t.interpolateRgbBasisClosed=ve,t.interpolateRound=Ae,t.interpolateSinebow=function(t){var n;return t=(.5-t)*Math.PI,dy.r=255*(n=Math.sin(t))*n,dy.g=255*(n=Math.sin(t+py))*n,dy.b=255*(n=Math.sin(t+vy))*n,dy+""},t.interpolateSpectral=Ag,t.interpolateString=Ne,t.interpolateTransformCss=qe,t.interpolateTransformSvg=Le,t.interpolateTurbo=function(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(34.61+t*(1172.33-t*(10793.56-t*(33300.12-t*(38394.49-14825.05*t)))))))+", "+Math.max(0,Math.min(255,Math.round(23.31+t*(557.33+t*(1225.33-t*(3574.96-t*(1073.77+707.56*t)))))))+", "+Math.max(0,Math.min(255,Math.round(27.2+t*(3211.1-t*(15327.97-t*(27814-t*(22569.18-6838.66*t)))))))+")"},t.interpolateViridis=yy,t.interpolateWarm=sy,t.interpolateYlGn=Xg,t.interpolateYlGnBu=jg,t.interpolateYlOrBr=$g,t.interpolateYlOrRd=Zg,t.interpolateZoom=Ie,t.interrupt=Pr,t.interval=function(t,n,e){var r=new lr,i=n;return null==n?(r.restart(t,n,e),r):(n=+n,e=null==e?fr():+e,r.restart(function o(a){a+=i,r.restart(o,i+=n,e),t(a)},n,e),r)},t.isoFormat=Dv,t.isoParse=qv,t.json=function(t,n){return fetch(t,n).then(la)},t.keys=function(t){var n=[];for(var e in t)n.push(e);return n},t.lab=On,t.lch=function(t,n,e,r){return 1===arguments.length?jn(t):new Xn(e,n,t,null==r?1:r)},t.line=jy,t.lineRadial=Ky,t.linkHorizontal=function(){return i_(o_)},t.linkRadial=function(){var t=i_(u_);return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t},t.linkVertical=function(){return i_(a_)},t.local=qt,t.map=co,t.matcher=nt,t.max=T,t.mean=function(t,n){var e,r=t.length,i=r,o=-1,a=0;if(null==n)for(;++o=r.length)return null!=t&&e.sort(t),null!=n?n(e):e;for(var c,f,s,l=-1,h=e.length,d=r[i++],p=co(),v=a();++lr.length)return e;var a,u=i[o-1];return null!=n&&o>=r.length?a=e.entries():(a=[],e.each(function(n,e){a.push({key:e,values:t(n,o)})})),null!=u?a.sort(function(t,n){return u(t.key,n.key)}):a}(o(t,0,lo,ho),0)},key:function(t){return r.push(t),e},sortKeys:function(t){return i[r.length-1]=t,e},sortValues:function(n){return t=n,e},rollup:function(t){return n=t,e}}},t.now=fr,t.pack=function(){var t=null,n=1,e=1,r=Zl;function i(i){return i.x=n/2,i.y=e/2,t?i.eachBefore(Jl(t)).eachAfter(th(r,.5)).eachBefore(nh(1)):i.eachBefore(Jl(Kl)).eachAfter(th(Zl,1)).eachAfter(th(r,i.r/Math.min(n,e))).eachBefore(nh(Math.min(n,e)/(2*i.r))),i}return i.radius=function(n){return arguments.length?(t=$l(n),i):t},i.size=function(t){return arguments.length?(n=+t[0],e=+t[1],i):[n,e]},i.padding=function(t){return arguments.length?(r="function"==typeof t?t:Ql(+t),i):r},i},t.packEnclose=ql,t.packSiblings=function(t){return Gl(t),t},t.pairs=function(t,n){null==n&&(n=a);for(var e=0,r=t.length-1,i=t[0],o=new Array(r<0?0:r);e0&&(d+=l);for(null!=n?p.sort(function(t,e){return n(v[t],v[e])}):null!=e&&p.sort(function(t,n){return e(a[t],a[n])}),u=0,f=d?(y-h*b)/d:0;u0?l*f:0)+b,v[c]={data:a[c],index:u,value:l,startAngle:g,endAngle:s,padAngle:_};return v}return a.value=function(n){return arguments.length?(t="function"==typeof n?n:xy(+n),a):t},a.sortValues=function(t){return arguments.length?(n=t,e=null,a):n},a.sort=function(t){return arguments.length?(e=t,n=null,a):e},a.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:xy(+t),a):r},a.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:xy(+t),a):i},a.padAngle=function(t){return arguments.length?(o="function"==typeof t?t:xy(+t),a):o},a},t.piecewise=function(t,n){for(var e=0,r=n.length-1,i=n[0],o=new Array(r<0?0:r);eu!=f>u&&a<(c-e)*(u-r)/(f-r)+e&&(s=!s),c=e,f=r;return s},t.polygonHull=function(t){if((e=t.length)<3)return null;var n,e,r=new Array(e),i=new Array(e);for(n=0;n=0;--n)f.push(t[r[o[n]][2]]);for(n=+u;n0?a[n-1]:r[0],n=o?[a[o-1],r]:[a[n-1],a[n]]},c.unknown=function(t){return arguments.length?(n=t,c):c},c.thresholds=function(){return a.slice()},c.copy=function(){return t().domain([e,r]).range(u).unknown(n)},Ch.apply(Wh(c),arguments)},t.scaleSequential=function t(){var n=Wh(Xv()(Fh));return n.copy=function(){return Gv(n,t())},Ph.apply(n,arguments)},t.scaleSequentialLog=function t(){var n=rd(Xv()).domain([1,10]);return n.copy=function(){return Gv(n,t()).base(n.base())},Ph.apply(n,arguments)},t.scaleSequentialPow=$v,t.scaleSequentialQuantile=function t(){var e=[],r=Fh;function o(t){if(!isNaN(t=+t))return r((i(e,t)-1)/(e.length-1))}return o.domain=function(t){if(!arguments.length)return e.slice();e=[];for(var r,i=0,a=t.length;i0)for(var e,r,i,o,a,u,c=0,f=t[n[0]].length;c0?(r[0]=o,r[1]=o+=i):i<0?(r[1]=a,r[0]=a+=i):(r[0]=0,r[1]=i)},t.stackOffsetExpand=function(t,n){if((r=t.length)>0){for(var e,r,i,o=0,a=t[0].length;o0){for(var e,r=0,i=t[n[0]],o=i.length;r0&&(r=(e=t[n[0]]).length)>0){for(var e,r,i,o=0,a=1;a0)throw new Error("cycle");return o}return e.id=function(n){return arguments.length?(t=Wl(n),e):t},e.parentId=function(t){return arguments.length?(n=Wl(t),e):n},e},t.style=ft,t.sum=function(t,n){var e,r=t.length,i=-1,o=0;if(null==n)for(;++i=0;--i)u.push(e=n.children[i]=new ph(r[i],i)),e.parent=n;return(a.parent=new ph(null,0)).children=[a],a}(i);if(c.eachAfter(o),c.parent.m=-c.z,c.eachBefore(a),r)i.eachBefore(u);else{var f=i,s=i,l=i;i.eachBefore(function(t){t.xs.x&&(s=t),t.depth>l.depth&&(l=t)});var h=f===s?1:t(f,s)/2,d=h-f.x,p=n/(s.x+h+d),v=e/(l.depth||1);i.eachBefore(function(t){t.x=(t.x+d)*p,t.y=t.depth*v})}return i}function o(n){var e=n.children,r=n.parent.children,i=n.i?r[n.i-1]:null;if(e){!function(t){for(var n,e=0,r=0,i=t.children,o=i.length;--o>=0;)(n=i[o]).z+=e,n.m+=e,e+=n.s+(r+=n.c)}(n);var o=(e[0].z+e[e.length-1].z)/2;i?(n.z=i.z+t(n._,i._),n.m=n.z-o):n.z=o}else i&&(n.z=i.z+t(n._,i._));n.parent.A=function(n,e,r){if(e){for(var i,o=n,a=n,u=e,c=o.parent.children[0],f=o.m,s=a.m,l=u.m,h=c.m;u=lh(u),o=sh(o),u&&o;)c=sh(c),(a=lh(a)).a=n,(i=u.z+l-o.z-f+t(u._,o._))>0&&(hh(dh(u,n,r),n,i),f+=i,s+=i),l+=u.m,f+=o.m,h+=c.m,s+=a.m;u&&!lh(a)&&(a.t=u,a.m+=l-s),o&&!sh(c)&&(c.t=o,c.m+=f-h,r=n)}return r}(n,i,n.parent.A||r[0])}function a(t){t._.x=t.z+t.parent.m,t.m+=t.parent.m}function u(t){t.x*=n,t.y=t.depth*e}return i.separation=function(n){return arguments.length?(t=n,i):t},i.size=function(t){return arguments.length?(r=!1,n=+t[0],e=+t[1],i):r?null:[n,e]},i.nodeSize=function(t){return arguments.length?(r=!0,n=+t[0],e=+t[1],i):r?[n,e]:null},i},t.treemap=function(){var t=_h,n=!1,e=1,r=1,i=[0],o=Zl,a=Zl,u=Zl,c=Zl,f=Zl;function s(t){return t.x0=t.y0=0,t.x1=e,t.y1=r,t.eachBefore(l),i=[0],n&&t.eachBefore(eh),t}function l(n){var e=i[n.depth],r=n.x0+e,s=n.y0+e,l=n.x1-e,h=n.y1-e;l=e-1){var s=u[n];return s.x0=i,s.y0=o,s.x1=a,void(s.y1=c)}for(var l=f[n],h=r/2+l,d=n+1,p=e-1;d>>1;f[v]c-o){var _=(i*y+a*g)/r;t(n,d,g,i,o,_,c),t(d,e,y,_,o,a,c)}else{var b=(o*y+c*g)/r;t(n,d,g,i,o,a,b),t(d,e,y,i,b,a,c)}}(0,c,t.value,n,e,r,i)},t.treemapDice=rh,t.treemapResquarify=bh,t.treemapSlice=vh,t.treemapSliceDice=function(t,n,e,r,i){(1&t.depth?vh:rh)(t,n,e,r,i)},t.treemapSquarify=_h,t.tsv=sa,t.tsvFormat=Ko,t.tsvFormatBody=Jo,t.tsvFormatRow=na,t.tsvFormatRows=ta,t.tsvFormatValue=ea,t.tsvParse=Zo,t.tsvParseRows=Qo,t.utcDay=Zd,t.utcDays=Qd,t.utcFriday=ip,t.utcFridays=lp,t.utcHour=$d,t.utcHours=Wd,t.utcMillisecond=vd,t.utcMilliseconds=gd,t.utcMinute=Xd,t.utcMinutes=Gd,t.utcMonday=tp,t.utcMondays=up,t.utcMonth=dp,t.utcMonths=pp,t.utcSaturday=op,t.utcSaturdays=hp,t.utcSecond=bd,t.utcSeconds=md,t.utcSunday=Jd,t.utcSundays=ap,t.utcThursday=rp,t.utcThursdays=sp,t.utcTuesday=np,t.utcTuesdays=cp,t.utcWednesday=ep,t.utcWednesdays=fp,t.utcWeek=Jd,t.utcWeeks=ap,t.utcYear=vp,t.utcYears=gp,t.values=function(t){var n=[];for(var e in t)n.push(t[e]);return n},t.variance=c,t.version="5.15.1",t.voronoi=function(){var t=lb,n=hb,e=null;function r(r){return new Gb(r.map(function(e,i){var o=[Math.round(t(e,i,r)/Hb)*Hb,Math.round(n(e,i,r)/Hb)*Hb];return o.index=i,o.data=e,o}),e)}return r.polygons=function(t){return r(t).polygons()},r.links=function(t){return r(t).links()},r.triangles=function(t){return r(t).triangles()},r.x=function(n){return arguments.length?(t="function"==typeof n?n:sb(+n),r):t},r.y=function(t){return arguments.length?(n="function"==typeof t?t:sb(+t),r):n},r.extent=function(t){return arguments.length?(e=null==t?null:[[+t[0][0],+t[0][1]],[+t[1][0],+t[1][1]]],r):e&&[[e[0][0],e[0][1]],[e[1][0],e[1][1]]]},r.size=function(t){return arguments.length?(e=null==t?null:[[0,0],[+t[0],+t[1]]],r):e&&[e[1][0]-e[0][0],e[1][1]-e[0][1]]},r},t.window=ct,t.xml=da,t.zip=function(){return k(arguments)},t.zoom=function(){var n,e,r=nm,i=em,o=am,a=im,u=om,c=[0,1/0],f=[[-1/0,-1/0],[1/0,1/0]],s=250,l=Ie,h=I("start","zoom","end"),d=500,p=150,v=0;function g(t){t.property("__zoom",rm).on("wheel.zoom",M).on("mousedown.zoom",N).on("dblclick.zoom",T).filter(u).on("touchstart.zoom",A).on("touchmove.zoom",S).on("touchend.zoom touchcancel.zoom",k).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function y(t,n){return(n=Math.max(c[0],Math.min(c[1],n)))===t.k?t:new Zb(n,t.x,t.y)}function _(t,n,e){var r=n[0]-e[0]*t.k,i=n[1]-e[1]*t.k;return r===t.x&&i===t.y?t:new Zb(t.k,r,i)}function b(t){return[(+t[0][0]+ +t[1][0])/2,(+t[0][1]+ +t[1][1])/2]}function m(t,n,e){t.on("start.zoom",function(){x(this,arguments).start()}).on("interrupt.zoom end.zoom",function(){x(this,arguments).end()}).tween("zoom",function(){var t=this,r=arguments,o=x(t,r),a=i.apply(t,r),u=null==e?b(a):"function"==typeof e?e.apply(t,r):e,c=Math.max(a[1][0]-a[0][0],a[1][1]-a[0][1]),f=t.__zoom,s="function"==typeof n?n.apply(t,r):n,h=l(f.invert(u).concat(c/f.k),s.invert(u).concat(c/s.k));return function(t){if(1===t)t=s;else{var n=h(t),e=c/n[2];t=new Zb(e,u[0]-n[0]*e,u[1]-n[1]*e)}o.zoom(null,t)}})}function x(t,n,e){return!e&&t.__zooming||new w(t,n)}function w(t,n){this.that=t,this.args=n,this.active=0,this.extent=i.apply(t,n),this.taps=0}function M(){if(r.apply(this,arguments)){var t=x(this,arguments),n=this.__zoom,e=Math.max(c[0],Math.min(c[1],n.k*Math.pow(2,a.apply(this,arguments)))),i=Bt(this);if(t.wheel)t.mouse[0][0]===i[0]&&t.mouse[0][1]===i[1]||(t.mouse[1]=n.invert(t.mouse[0]=i)),clearTimeout(t.wheel);else{if(n.k===e)return;t.mouse=[i,n.invert(i)],Pr(this),t.start()}tm(),t.wheel=setTimeout(function(){t.wheel=null,t.end()},p),t.zoom("mouse",o(_(y(n,e),t.mouse[0],t.mouse[1]),t.extent,f))}}function N(){if(!e&&r.apply(this,arguments)){var n=x(this,arguments,!0),i=Rt(t.event.view).on("mousemove.zoom",function(){if(tm(),!n.moved){var e=t.event.clientX-u,r=t.event.clientY-c;n.moved=e*e+r*r>v}n.zoom("mouse",o(_(n.that.__zoom,n.mouse[0]=Bt(n.that),n.mouse[1]),n.extent,f))},!0).on("mouseup.zoom",function(){i.on("mousemove.zoom mouseup.zoom",null),jt(t.event.view,n.moved),tm(),n.end()},!0),a=Bt(this),u=t.event.clientX,c=t.event.clientY;Ht(t.event.view),Jb(),n.mouse=[a,this.__zoom.invert(a)],Pr(this),n.start()}}function T(){if(r.apply(this,arguments)){var n=this.__zoom,e=Bt(this),a=n.invert(e),u=n.k*(t.event.shiftKey?.5:2),c=o(_(y(n,u),e,a),i.apply(this,arguments),f);tm(),s>0?Rt(this).transition().duration(s).call(m,c,e):Rt(this).call(g.transform,c)}}function A(){if(r.apply(this,arguments)){var e,i,o,a,u=t.event.touches,c=u.length,f=x(this,arguments,t.event.changedTouches.length===c);for(Jb(),i=0;i transitions this.create_nodes = []; } // Generate nodes from config NodesFromConfig(config) { var self = this; self.nodes = []; jQuery.each(['initial', 'active', 'inactive'], function (i, type) { if ( config[type] ) { config[type].forEach(function(element) { self.nodes = self.nodes.concat({id: ++self.nodes_seq, name: element, type: type}); }); } }); } // Find all links associated with node object LinksForNodeFromConfig (node, config) { var config = config || this.config; for (let [fromNode, toList] of Object.entries(config.transitions)) { if ( fromNode == '' ) { this.create_nodes = toList; } else if ( fromNode.toLowerCase() == node.toLowerCase() ) { return toList; } } return []; } // Create a new node for our JS model AddNode(point) { var self = this; var i = 0, name; while (1) { name = 'status #' + ++i; var index = self.nodes.findIndex(function(x) { return x.name.toLowerCase() == name.toLowerCase() }); if ( index < 0 ) { break; } } self.nodes.push({id: ++self.nodes_seq, name: name, type: 'active', x: point[0], y: point[1]}); } AddLink(source, target) { var self = this; if (!source || !target) return; var link = self.links.filter(function(l) { return (l.source.id === target.id && l.target.id === source.id); })[0]; if ( link ) { link.start = true; } else { link = self.links.filter(function(l) { return (l.source.id === source.id && l.target.id === target.id); })[0]; if (!link ) { self.links.push({id: ++self.links_seq, source: source, target: target, start: false, end: true}); } } } ToggleLink(d) { var self = this; var index = self.links.findIndex(function(x) { return x.id == d.id }); var link = self.links[index]; // delete link if we have both transitions already if ( link.start && link.end ) { self.links.splice(index, 1); var from = d.source.name.toLowerCase(); var to = d.target.name.toLowerCase(); var pattern = from + ' *-> *' + to + '|' + to + ' *-> *' + from; self.DeleteRights(null, pattern); self.DeleteActions(null, pattern); } else if( link.start ) { link.end = true; } else { link.start = true; } } NodeById(id) { var self = this; var nodeMap = d3.map(self.nodes, function(d) { return d.id; }); return nodeMap.get( id ); } NodeByStatus(status) { var self = this; var nodeMap = d3.map(self.nodes, function(d) { return d.name; }); return nodeMap.get( status ); } DeleteNode(d) { var self = this; var index = self.nodes.findIndex(function(x) { return x.id == d.id }); self.DeleteLinksForNode(self.nodes[index]); self.DeleteRights(d); self.DeleteDefaults(d); self.DeleteActions(d); self.nodes.splice(index, 1); } LinksForNode (node) { var self = this; return self.links.filter(function(link) { if ( link.source.id === node.id ) { return true; } else if ( link.target.id === node.id && link.start ) { return true; } else { return false; } }); } DeleteDefaults(d) { var self = this; jQuery.each(self.config.defaults, function (key, value) { if (value && value.toLowerCase() === d.name.toLowerCase()) { delete self.config.defaults[key]; } }); } DeleteRights(d, pattern) { var self = this; if ( !pattern ) { pattern = d.name.toLowerCase() + " *->|-> *" + d.name.toLowerCase(); } var re = new RegExp(pattern); jQuery.each(self.config.rights, function(key, value) { if ( re.test(key.toLowerCase()) ) { delete self.config.rights[key]; } }); } DeleteActions(d, pattern) { var self = this; if ( !pattern ) { pattern = d.name.toLowerCase() + " *->|-> *" + d.name.toLowerCase(); } var re = new RegExp(pattern); var actions = []; var tempArr = self.config.actions || []; var i = tempArr.length / 2; while (i--) { var action, info; [action, info] = tempArr.splice(0, 2); if (!action) continue; if ( ! re.test(action) ) { actions.push(action); actions.push(info); } } self.config.actions = actions; } DeleteLinksForNode(node) { var self = this; if ( !node ) { return; } self.links = jQuery.grep(self.links, function (transition) { if (transition.source.id == node.id || transition.target.id == node.id) { return false; } return true; }); } UpdateNodeModel(node, args) { var self = this; var nodeIndex = self.nodes.findIndex(function(x) { return x.id == node.id }); var oldValue = self.nodes[nodeIndex]; self.nodes[nodeIndex] = {...self.nodes[nodeIndex], ...args}; var nodeUpdated = self.nodes[nodeIndex]; // Update any links with node being changed as source var links = self.links.filter(function(l) { return ( ( l.source.id === node.id ) ) }); links.forEach(function(link) { var index = self.links.findIndex(function(x) { return x.id == link.id }); self.links[index] = {...link, source: nodeUpdated} }); // Update any links with node being changed as target var links = self.links.filter(function(l) { return ( ( l.target.id === node.id ) ) }); links.forEach(function(link) { var index = self.links.findIndex(function(x) { return x.id == link.id }); self.links[index] = {...link, target: nodeUpdated} }); if ( oldValue.name === nodeUpdated.name ) return; // Only need to check for target var tempArr = []; self.create_nodes.forEach(function(target) { if ( target != oldValue.name ) { tempArr.push(target); } else { tempArr.push(nodeUpdated.name); } }); self.create_nodes = tempArr; for (let type in self.config.defaults) { if ( self.config.defaults[type] === oldValue.name ) { self.config.defaults[type] = nodeUpdated.name; } } let re_from = new RegExp(oldValue.name + ' *->'); let re_to = new RegExp('-> *' + oldValue.name); for (let action in self.config.rights) { let updated = action.replace(re_from, nodeUpdated.name + ' ->').replace(re_to, '-> ' + nodeUpdated.name); if ( action != updated ) { self.config.rights[updated] = self.config.rights[action]; delete self.config.rights[action]; } } let actions = []; if ( self.config.actions ) { for ( let i = 0; i < self.config.actions.length; i += 2 ) { let action = self.config.actions[i]; let info = self.config.actions[i+1]; let updated = action.replace(re_from, nodeUpdated.name + ' ->').replace(re_to, '-> ' + nodeUpdated.name); actions.push(updated); actions.push(info); } } self.config.actions = actions; let config_name = jQuery('form[name=ModifyLifecycle] input[name=Name]').val(); for (let item in self.maps) { if ( item.match(config_name + ' *->')) { let maps = self.maps[item]; for ( let from in maps ) { if ( from === oldValue.name ) { maps[nodeUpdated.name] = maps[from]; delete maps[from]; } } } else if ( item.match('-> *' + config_name) ) { let maps = self.maps[item]; for ( let from in maps ) { if ( maps[from] === oldValue.name ) { maps[from] = nodeUpdated.name; } } } } } ExportAsConfiguration () { var self = this; var config = { type: self.type, initial: [], active: [], inactive: [], transitions: {}, }; // Grab our status nodes ['initial', 'active', 'inactive'].forEach(function(type) { config[type] = self.nodes.filter(function(n) { return n.type == type }).map(function(n) { return n.name }); }); // Grab our links config.transitions[""] = self.create_nodes; var seen = {}; self.nodes.forEach(function(source) { var links = self.LinksForNode(source); var targets = links.map(link => { if ( link.source.id === source.id ) { return link.target.name; } else { return link.source.name; } }); config.transitions[source.name] = targets; seen[source.name] = 1; }); for (let transition in config.transitions) { if ( transition && ( !seen[transition] || !config.transitions[transition].length ) ) { delete config.transitions[transition]; } } self.config = {...self.config, ...config}; var field = jQuery('form[name=ModifyLifecycle] input[name=Config]'); field.val(JSON.stringify(self.config)); var pos; if ( !jQuery('#enableSimulation').is(":checked") ) { pos = {}; self.nodes.forEach( function(d) { pos[d.name] = [Math.round(d.x), Math.round(d.y)]; }); } var layout = jQuery('form[name=ModifyLifecycle] input[name=Layout]'); layout.val(pos ? JSON.stringify(pos) : ''); var maps = jQuery('form[name=ModifyLifecycle] input[name=Maps]'); maps.val(JSON.stringify(self.maps)); }; } rt-5.0.1/share/static/js/jquery.tablesorter.min.js000644 000765 000024 00000040743 14005011336 023002 0ustar00sunnavystaff000000 000000 /* * * TableSorter 2.0 - Client-side table sorting with ease! * Version 2.0.5b * @requires jQuery v1.2.3 * * Copyright (c) 2007 Christian Bach * Examples and docs at: http://tablesorter.com * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html * */ (function($){$.extend({tablesorter:new function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:true,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'/\.|\,/g',onRenderHeader:null,selectorHeaders:'thead th',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}if(table.tBodies.length==0)return;var rows=table.tBodies[0].rows;if(rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function checkHeaderOptionsSortingLocked(table,i){if((table.config.headers[i])&&(table.config.headers[i].lockedOrder))return table.config.headers[i].lockedOrder;return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i b["+i+"]) ? 1 : 0));";};function makeSortTextDesc(i){return"((b["+i+"] < a["+i+"]) ? -1 : ((b["+i+"] > a["+i+"]) ? 1 : 0));";};function makeSortNumeric(i){return"a["+i+"]-b["+i+"];";};function makeSortNumericDesc(i){return"b["+i+"]-a["+i+"];";};function sortText(a,b){if(table.config.sortLocaleCompare)return a.localeCompare(b);return((ab)?1:0));};function sortTextDesc(a,b){if(table.config.sortLocaleCompare)return b.localeCompare(a);return((ba)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$.data(this,"tablesorter",config);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){$this.trigger("sortStart");var $cell=$(this);var i=this.column;this.order=this.count++%2;if(this.lockedOrder)this.order=this.lockedOrder;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i 10 ? 300 : self.nodes.length * 35; if ( !self.enableSimulation ) { self.simulation .force("link", null) .force("charge", null) .force("center", null) .force('collision', null); } else { self.simulation .force("link", d3.forceLink().distance(link_size < 100 ? 200 : link_size).strength(0.2)) .force("charge", d3.forceManyBody().strength(-200)) .force("center", d3.forceCenter(self.width / 2, self.height / 2)) .force('collision', d3.forceCollide().radius(function(d) { return d.radius })); } self.SetUp(); self.RenderNode(); self.RenderLink(); self.simulation .nodes(self.nodes) .on("tick", function (t) { self.node.attr("transform", function (d) { var x = d.x, y = d.y; if ( d.x + self.node_radius / 2 > self.width ) x = self.width - self.node_radius; if ( d.x - self.node_radius / 2 <= 0 ) x = self.node_radius; if ( d.y + self.node_radius / 2 > self.height ) y = self.height - self.node_radius; if ( d.y - self.node_radius / 2 <= 0 ) y = self.node_radius; if ( !self.enableSimulation ) { d.fx = x; d.fy = y; } else { d.fx = null; d.fy = null; } return "translate(" + x + "," + y + ")"; }); self.link.attr('d', (function(d) { var sx = d.source.x, sy = d.source.y, tx = d.target.x, ty = d.target.y; if ( sx + self.node_radius / 2 > self.width ) sx = self.width - self.node_radius; if ( sx - self.node_radius / 2 <= 0 ) sx = self.node_radius; if ( sy + self.node_radius / 2 > self.height ) sy = self.height - self.node_radius; if ( sy - self.node_radius / 2 <= 0 ) sy = self.node_radius; if ( tx + self.node_radius / 2 > self.width ) tx = self.width - self.node_radius; if ( tx - self.node_radius / 2 <= 0 ) tx = self.node_radius; if ( ty + self.node_radius / 2 > self.height ) ty = self.height - self.node_radius; if ( ty - self.node_radius / 2 <= 0 ) ty = self.node_radius; var deltaX = tx - sx, deltaY = ty - sy, dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY), normX = deltaX / dist, normY = deltaY / dist, sourcePadding = 45, targetPadding = 45, sourceX = sx + (sourcePadding * normX), sourceY = sy + (sourcePadding * normY), targetX = tx - (targetPadding * normX), targetY = ty - (targetPadding * normY); return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY; }) ); }); self.ExportAsConfiguration(); self.Refresh(); } SetUp() { var self = this; // define arrow markers for graph links self.svg.append('svg:defs').append('svg:marker') .attr('id', 'end-arrow') .attr('viewBox', '0 -5 10 10') .attr('refX', 6) .attr('markerWidth', 5) .attr('markerHeight', 5) .attr('orient', 'auto') .append('svg:path') .attr('d', 'M0,-5L10,0L0,5') .attr('fill', '#000'); self.svg.append('svg:defs').append('svg:marker') .attr('id', 'start-arrow') .attr('viewBox', '0 -5 10 10') .attr('refX', 6) .attr('markerWidth', 5) .attr('markerHeight', 5) .attr('orient', 'auto') .append('svg:path') .attr('d', 'M10,-5L0,0L10,5') .attr('fill', '#000'); // line displayed when dragging new nodes self.drag_line = self.svg.append('svg:path') .attr('class', 'dragline hidden') .attr('d', 'M0,0L0,0') .attr('fill', '#000') .attr('markerWidth', 8) .attr('markerHeight', 8) .attr("stroke-width", 1) .attr("style", "stroke: black; stroke-opacity: 0.6;"); self.svg .on('click', function () { d3.event.preventDefault(); d3.event.stopPropagation(); if ( self.selected_node || self.editing_node ) { self.Deselect(); } else { self.simulation.stop(); self.Deselect(); self.AddNode(d3.mouse(this)); self.ExportAsConfiguration(); self.Refresh(); } }) .on('contextmenu', function() { d3.event.preventDefault(); }) .on('mousemove', function() { self.Mousemove(this); }) .on('mouseup', function() { self.Mouseup(this); }) .on('mousedown', function() { self.Mousedown(this); }); d3.select("body").on("keydown", function (d) { if ( !self.editing_node && self.selected_node && ( d3.event.keyCode == 68 || d3.event.keyCode == 46 ) ) { d3.event.preventDefault(); d3.event.stopPropagation(); self.simulation.stop(); self.svg.selectAll('.node-selected').each(function(d) { self.DeleteNode(d); self.ExportAsConfiguration(); self.Deselect(); self.Refresh(); }); } }); jQuery('#enableSimulation').click(function(e) { self.ToggleSimulation(); return true; }); } RenderNode() { var self = this; self.node = self.svg.selectAll(".node") .data(self.nodes.filter(function(d) { return d.id >= 0 })); self.node.exit() .remove(); // Add new nodes and draw them var nodeEnter = self.node.enter().append("g") .attr("class", "node"); nodeEnter.append("circle"); nodeEnter.append("text"); nodeEnter.append("title"); self.node = nodeEnter.merge(self.node) .attr("id", function(d) { return d.id }); self.node.call(d3.drag() .on("start", function(d) { if (!d3.event.active) self.simulation.alphaTarget(0.3).restart(); d.fx = d.x, d.fy = d.y; }) .on("drag", function(d) { d.fx = d3.event.x, d.fy = d3.event.y; }) .on("end", function(d) { if (!d3.event.active) self.simulation.alphaTarget(0); if ( !self.enableSimulation ) { d.fx = null, d.fy = null; } })); // Add our circle to our new node self.node.select("circle") .attr("r", self.node_radius) .attr("stroke", "black") .attr("fill", function(d) { switch(d.type) { case 'active': return '#547CCC'; case 'inactive': return '#4bb2cc'; case 'initial': return '#599ACC'; } }) .on("click", function() { d3.event.stopPropagation(); d3.event.preventDefault(); self.SelectNode(this); }) .on('mousedown', function(d) { if(!d3.event.ctrlKey || self.mousedown_node || self.mousedown_link) return; d3.event.preventDefault(); d3.event.stopPropagation(); // select node self.mousedown_node = d; if ( !self.mousedown_node ) { self.mousedown_node = null; return; } // reposition drag line self.drag_line .style('marker-end', 'url(#end-arrow)') .classed('hidden', false) .attr('d', 'M' + self.mousedown_node.x + ',' + self.mousedown_node.y + 'L' + self.mousedown_node.x + ',' + self.mousedown_node.y); self.Refresh(); }) .on('mouseup', function(d) { self.Mouseup(d); }); self.node.select("text") .text(function(d) { return d.name; }) .each(function () { self.TruncateLabel(this, self); }) .attr("x", function(d) { var node = d3.select(this), textLength = node.node().getComputedTextLength(); if ( textLength > self.node_radius*2 ) textLength = self.node_radius*2; return -textLength/2; }) .attr("y", 0) .style("font-size", "10px") .on("click", function(d) { d3.event.stopPropagation(); d3.event.preventDefault(); self.UpdateNode(d); }); self.node.select("title") .text(function(d) { return d.type; }); } UpdateNode(element) { var self = this; const nodeInput = jQuery("#lifeycycle-ui-edit-node"); if ( event.pageX ) { var posX = event.pageX; var posY = event.pageY; if ( posX + nodeInput.width() > self.width ) posX = self.width - nodeInput.width(); if ( posY + nodeInput.height() > self.height ) posY = self.height - nodeInput.height(); nodeInput.css( {position:"absolute", top:posY - self.node_radius, left: posX - self.node_radius}); } var list = document.getElementById('lifeycycle-ui-edit-node').querySelectorAll('input, select'); if ( element ) { for (let item of list) { jQuery(item).val(element[item.name]); } self.editing_node = element; jQuery('.selectpicker').selectpicker('refresh'); } else { var name = document.getElementsByName('name')[0].value; if ( self.editing_node.name != name && self.nodes.reduce(function(n, x) { return n + (x.name === name) }, 0) >= 1 || name === '' ) { var form = jQuery('#lifeycycle-ui-edit-node'); form.find('div.invalid-name').removeClass('hidden'); return; } var values = {}; for (let item of list) { if ( item.name === 'id' ) { values.index = self.nodes.findIndex(function(x) { return x.id == item.value }); } values[item.name] = item.value; } self.UpdateNodeModel(self.nodes[values.index], values); self.ExportAsConfiguration(); self.Refresh(); } nodeInput.toggle(); } RenderLink() { var self = this; self.link = self.svg.selectAll(".link") .data(self.links); self.link.exit() .each(function () { var length = this.getTotalLength(); var path = d3.select(this); path.attr("stroke-dasharray", length + " " + length) .attr("stroke-dashoffset", 0) .style("marker-end", "none") .style("marker-start", "none") .transition().duration(200 * self.animationFactor).ease(d3.easeLinear) .attr("stroke-dashoffset", length) .remove(); }); // Add new links and draw them var linkEnter = self.link.enter().append("g") .append("path") .attr("class", 'link') .style("marker-start", function(d) { return d.start ? 'url(#start-arrow)' : '' }) .style("marker-end", function(d) { return d.end ? 'url(#end-arrow)' : '' }) .attr("transform", "translate(0,0)") .on("click", function(d) { d3.event.stopPropagation(); self.simulation.stop(); self.ToggleLink(d); self.ExportAsConfiguration(); self.Refresh(); }); self.link = linkEnter.merge(self.link); self.link .style("marker-start", function(d) { return d.start ? 'url(#start-arrow)' : '' }) .style("marker-end", function(d) { return d.end ? 'url(#end-arrow)' : '' }); } Refresh() { var self = this; const link_size = self.nodes.length > 10 ? 300 : self.nodes.length * 35; self.simulation .force("link", d3.forceLink().distance(link_size < 100 ? 200 : link_size).strength(0.2)) self.simulation .nodes(self.nodes) .force("link") .links(self.links) .id(function(d) { return d.id }); self.RenderLink(); self.RenderNode(); jQuery('#lifeycycle-ui-edit-node div.alert').addClass('hidden'); // This is our "cooling" factor self.simulation.alpha(0.05).restart(); } SelectNode(node) { var self = this; self.Deselect(); self.selected_node = node; d3.select(node) .classed('node-selected', true); } Deselect() { var self = this; if ( jQuery("#lifeycycle-ui-edit-node").is(':visible') ) { jQuery("#lifeycycle-ui-edit-node").toggle(); jQuery("#lifeycycle-ui-edit-node div.alert").addClass('hidden'); } self.editing_node = null; if (!self.selected_node) return; self.selected_node = null; self.svg.selectAll("foreignObject").remove(); self.svg.selectAll('.node-selected') .classed('node-selected', false); } TruncateLabel(element, self) { var node = d3.select(element), textLength = node.node().getComputedTextLength(), text = node.text(); var diameter = self.node_radius * 2 - textLength/4; while (textLength > diameter && text.length > 0) { text = text.slice(0, -1); node.text(text + '…'); textLength = node.node().getComputedTextLength(); } } Mousemove(d) { var self = this; if (!self.mousedown_node) return; self.drag_line.attr('d', 'M' + self.mousedown_node.x + ',' + self.mousedown_node.y + 'L' + d3.mouse(d)[0] + ',' + d3.mouse(d)[1]); self.Refresh(); } Mouseup(d) { var self = this; if( self.mousedown_node ) { // needed by FF self.drag_line .classed('hidden', true) .style('marker-end', ''); if ( d.id && d.id != self.mousedown_node.id ) { self.mouseup_node = d; self.simulation.stop(); // add link to model self.AddLink(self.mousedown_node, self.mouseup_node); self.ExportAsConfiguration(); self.Refresh(); } self.svg.classed('ctrl', false); } // because :active only works in WebKit? self.svg.classed('active', false); self.ResetMouseVars(); } Mousedown(d) { d3.event.preventDefault(); d3.event.stopPropagation(); } ResetMouseVars(){ var self = this; self.mousedown_link = null; self.mousedown_node = null; self.mouseup_node = null; } ToggleSimulation(){ var self = this; self.enableSimulation = jQuery('#enableSimulation').is(":checked"); const link_size = self.nodes.length > 10 ? 300 : self.nodes.length * 35; if ( !self.enableSimulation ) { self.simulation .force("link", null) .force("charge", null) .force("center", null) .force('collision', null); self.ExportAsConfiguration(); } else { self.nodes.forEach(function(d) { d.fx = null, d.fy = null; }); self.simulation .force("link", d3.forceLink().distance(link_size < 100 ? 200 : link_size).strength(0.2)) .force("charge", d3.forceManyBody().strength(-200)) .force("center", d3.forceCenter(self.width / 2, self.height / 2)) .force('collision', d3.forceCollide().radius(function(d) { return d.radius })) self.simulation.force("link") .links(self.links) .id(function(d) { return d.id }); } self.ExportAsConfiguration(); } } }); rt-5.0.1/share/static/js/selectize.min.js000644 000765 000024 00000131411 14005011336 021116 0ustar00sunnavystaff000000 000000 /*! selectize.js - v0.12.6 | https://github.com/selectize/selectize.js | Apache License (v2) */ !function(a,b){"function"==typeof define&&define.amd?define("sifter",b):"object"==typeof exports?module.exports=b():a.Sifter=b()}(this,function(){var a=function(a,b){this.items=a,this.settings=b||{diacritics:!0}};a.prototype.tokenize=function(a){if(!(a=e(String(a||"").toLowerCase()))||!a.length)return[];var b,c,d,g,i=[],j=a.split(/ +/);for(b=0,c=j.length;b0)&&d.items.push({score:c,id:e})}):g.iterator(g.items,function(a,b){d.items.push({score:1,id:b})}),e=g.getSortFunction(d,b),e&&d.items.sort(e),d.total=d.items.length,"number"==typeof b.limit&&(d.items=d.items.slice(0,b.limit)),d};var b=function(a,b){return"number"==typeof a&&"number"==typeof b?a>b?1:ab?1:b>a?-1:0)},c=function(a,b){var c,d,e,f;for(c=1,d=arguments.length;c=0&&a.data.length>0){var f=a.data.match(c),g=document.createElement("span");g.className="highlight";var h=a.splitText(e),i=(h.splitText(f[0].length),h.cloneNode(!0));g.appendChild(i),h.parentNode.replaceChild(g,h),b=1}}else if(1===a.nodeType&&a.childNodes&&!/(script|style)/i.test(a.tagName)&&("highlight"!==a.className||"SPAN"!==a.tagName))for(var j=0;j/g,">").replace(/"/g,""")},m={};m.before=function(a,b,c){var d=a[b];a[b]=function(){return c.apply(a,arguments),d.apply(a,arguments)}},m.after=function(a,b,c){var d=a[b];a[b]=function(){var b=d.apply(a,arguments);return c.apply(a,arguments),b}};var n=function(a){var b=!1;return function(){b||(b=!0,a.apply(this,arguments))}},o=function(a,b){var c;return function(){var d=this,e=arguments;window.clearTimeout(c),c=window.setTimeout(function(){a.apply(d,e)},b)}},p=function(a,b,c){var d,e=a.trigger,f={};a.trigger=function(){var c=arguments[0];if(-1===b.indexOf(c))return e.apply(a,arguments);f[c]=arguments},c.apply(a,[]),a.trigger=e;for(d in f)f.hasOwnProperty(d)&&e.apply(a,f[d])},q=function(a,b,c,d){a.on(b,c,function(b){for(var c=b.target;c&&c.parentNode!==a[0];)c=c.parentNode;return b.currentTarget=c,d.apply(this,[b])})},r=function(a){var b={};if("selectionStart"in a)b.start=a.selectionStart,b.length=a.selectionEnd-b.start;else if(document.selection){a.focus();var c=document.selection.createRange(),d=document.selection.createRange().text.length;c.moveStart("character",-a.value.length),b.start=c.text.length-d,b.length=d}return b},s=function(a,b,c){var d,e,f={};if(c)for(d=0,e=c.length;d").css({position:"absolute",top:-99999,left:-99999,width:"auto",padding:0,whiteSpace:"pre"}).appendTo("body")),w.$testInput.text(b),s(c,w.$testInput,["letterSpacing","fontSize","fontFamily","fontWeight","textTransform"]),w.$testInput.width()):0},u=function(a){var b=null,c=function(c,d){var e,f,g,h,i,j,k,l;c=c||window.event||{},d=d||{},c.metaKey||c.altKey||(d.force||!1!==a.data("grow"))&&(e=a.val(),c.type&&"keydown"===c.type.toLowerCase()&&(f=c.keyCode,g=f>=48&&f<=57||f>=65&&f<=90||f>=96&&f<=111||f>=186&&f<=222||32===f,46===f||8===f?(l=r(a[0]),l.length?e=e.substring(0,l.start)+e.substring(l.start+l.length):8===f&&l.start?e=e.substring(0,l.start-1)+e.substring(l.start+1):46===f&&void 0!==l.start&&(e=e.substring(0,l.start)+e.substring(l.start+1))):g&&(j=c.shiftKey,k=String.fromCharCode(c.keyCode),k=j?k.toUpperCase():k.toLowerCase(),e+=k)),h=a.attr("placeholder"),!e&&h&&(e=h),(i=t(e,a)+4)!==b&&(b=i,a.width(i),a.triggerHandler("resize")))};a.on("keydown keyup update blur",c),c()},v=function(a){var b=document.createElement("div");return b.appendChild(a.cloneNode(!0)),b.innerHTML},w=function(c,d){var e,f,g,h,i=this;h=c[0],h.selectize=i;var j=window.getComputedStyle&&window.getComputedStyle(h,null);if(g=j?j.getPropertyValue("direction"):h.currentStyle&&h.currentStyle.direction,g=g||c.parents("[dir]:first").attr("dir")||"",a.extend(i,{order:0,settings:d,$input:c,tabIndex:c.attr("tabindex")||"",tagType:"select"===h.tagName.toLowerCase()?1:2,rtl:/rtl/i.test(g),eventNS:".selectize"+ ++w.count,highlightedValue:null,isBlurring:!1,isOpen:!1,isDisabled:!1,isRequired:c.is("[required]"),isInvalid:!1,isLocked:!1,isFocused:!1,isInputHidden:!1,isSetup:!1,isShiftDown:!1,isCmdDown:!1,isCtrlDown:!1,ignoreFocus:!1,ignoreBlur:!1,ignoreHover:!1,hasOptions:!1,currentResults:null,lastValue:"",caretPos:0,loading:0,loadedSearches:{},$activeOption:null,$activeItems:[],optgroups:{},options:{},userOptions:{},items:[],renderCache:{},onSearchChange:null===d.loadThrottle?i.onSearchChange:o(i.onSearchChange,d.loadThrottle)}),i.sifter=new b(this.options,{diacritics:d.diacritics}),i.settings.options){for(e=0,f=i.settings.options.length;e").addClass(r.wrapperClass).addClass(m).addClass(l),c=a("
").addClass(r.inputClass).addClass("items").appendTo(b),d=a('').appendTo(c).attr("tabindex",w.is(":disabled")?"-1":p.tabIndex),k=a(r.dropdownParent||b),e=a("
").addClass(r.dropdownClass).addClass(l).hide().appendTo(k),j=a("
").addClass(r.dropdownContentClass).appendTo(e),(o=w.attr("id"))&&(d.attr("id",o+"-selectized"),a("label[for='"+o+"']").attr("for",o+"-selectized")),p.settings.copyClassesToDropdown&&e.addClass(m),b.css({width:w[0].style.width}),p.plugins.names.length&&(n="plugin-"+p.plugins.names.join(" plugin-"),b.addClass(n),e.addClass(n)),(null===r.maxItems||r.maxItems>1)&&1===p.tagType&&w.attr("multiple","multiple"),p.settings.placeholder&&d.attr("placeholder",r.placeholder),!p.settings.splitOn&&p.settings.delimiter){var x=p.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&");p.settings.splitOn=new RegExp("\\s*"+x+"+\\s*")}w.attr("autocorrect")&&d.attr("autocorrect",w.attr("autocorrect")),w.attr("autocapitalize")&&d.attr("autocapitalize",w.attr("autocapitalize")),d[0].type=w[0].type,p.$wrapper=b,p.$control=c,p.$control_input=d,p.$dropdown=e,p.$dropdown_content=j,e.on("mouseenter mousedown click","[data-disabled]>[data-selectable]",function(a){a.stopImmediatePropagation()}),e.on("mouseenter","[data-selectable]",function(){return p.onOptionHover.apply(p,arguments)}),e.on("mousedown click","[data-selectable]",function(){return p.onOptionSelect.apply(p,arguments)}),q(c,"mousedown","*:not(input)",function(){return p.onItemSelect.apply(p,arguments)}),u(d),c.on({mousedown:function(){return p.onMouseDown.apply(p,arguments)},click:function(){return p.onClick.apply(p,arguments)}}),d.on({mousedown:function(a){a.stopPropagation()},keydown:function(){return p.onKeyDown.apply(p,arguments)},keyup:function(){return p.onKeyUp.apply(p,arguments)},keypress:function(){return p.onKeyPress.apply(p,arguments)},resize:function(){p.positionDropdown.apply(p,[])},blur:function(){return p.onBlur.apply(p,arguments)},focus:function(){return p.ignoreBlur=!1,p.onFocus.apply(p,arguments)},paste:function(){return p.onPaste.apply(p,arguments)}}),v.on("keydown"+s,function(a){p.isCmdDown=a[f?"metaKey":"ctrlKey"],p.isCtrlDown=a[f?"altKey":"ctrlKey"],p.isShiftDown=a.shiftKey}),v.on("keyup"+s,function(a){a.keyCode===h&&(p.isCtrlDown=!1),16===a.keyCode&&(p.isShiftDown=!1),a.keyCode===g&&(p.isCmdDown=!1)}),v.on("mousedown"+s,function(a){if(p.isFocused){if(a.target===p.$dropdown[0]||a.target.parentNode===p.$dropdown[0])return!1;p.$control.has(a.target).length||a.target===p.$control[0]||p.blur(a.target)}}),t.on(["scroll"+s,"resize"+s].join(" "),function(){p.isOpen&&p.positionDropdown.apply(p,arguments)}),t.on("mousemove"+s,function(){p.ignoreHover=!1}),this.revertSettings={$children:w.children().detach(),tabindex:w.attr("tabindex")},w.attr("tabindex",-1).hide().after(p.$wrapper),a.isArray(r.items)&&(p.setValue(r.items),delete r.items),i&&w.on("invalid"+s,function(a){a.preventDefault(),p.isInvalid=!0,p.refreshState()}),p.updateOriginalInput(),p.refreshItems(),p.refreshState(),p.updatePlaceholder(),p.isSetup=!0,w.is(":disabled")&&p.disable(),p.on("change",this.onChange),w.data("selectize",p),w.addClass("selectized"),p.trigger("initialize"),!0===r.preload&&p.onSearchChange("")},setupTemplates:function(){var b=this,c=b.settings.labelField,d=b.settings.optgroupLabelField,e={optgroup:function(a){return'
'+a.html+"
"},optgroup_header:function(a,b){return'
'+b(a[d])+"
"},option:function(a,b){return'
'+b(a[c])+"
"},item:function(a,b){return'
'+b(a[c])+"
"},option_create:function(a,b){return'
Add '+b(a.input)+"
"}};b.settings.render=a.extend({},e,b.settings.render)},setupCallbacks:function(){var a,b,c={initialize:"onInitialize",change:"onChange",item_add:"onItemAdd",item_remove:"onItemRemove",clear:"onClear",option_add:"onOptionAdd",option_remove:"onOptionRemove",option_clear:"onOptionClear",optgroup_add:"onOptionGroupAdd",optgroup_remove:"onOptionGroupRemove",optgroup_clear:"onOptionGroupClear",dropdown_open:"onDropdownOpen",dropdown_close:"onDropdownClose",type:"onType",load:"onLoad",focus:"onFocus",blur:"onBlur"};for(a in c)c.hasOwnProperty(a)&&(b=this.settings[c[a]])&&this.on(a,b)},onClick:function(a){var b=this;b.isFocused&&b.isOpen||(b.focus(),a.preventDefault())},onMouseDown:function(b){var c=this,d=b.isDefaultPrevented();a(b.target);if(c.isFocused){if(b.target!==c.$control_input[0])return"single"===c.settings.mode?c.isOpen?c.close():c.open():d||c.setActiveItem(null),!1}else d||window.setTimeout(function(){c.focus()},0)},onChange:function(){this.$input.trigger("change")},onPaste:function(b){var c=this;if(c.isFull()||c.isInputHidden||c.isLocked)return void b.preventDefault();c.settings.splitOn&&setTimeout(function(){var b=c.$control_input.val();if(b.match(c.settings.splitOn))for(var d=a.trim(b).split(c.settings.splitOn),e=0,f=d.length;eh&&(j=g,g=h,h=j),e=g;e<=h;e++)i=l.$control[0].childNodes[e],-1===l.$activeItems.indexOf(i)&&(a(i).addClass("active"),l.$activeItems.push(i));c.preventDefault()}else"mousedown"===d&&l.isCtrlDown||"keydown"===d&&this.isShiftDown?b.hasClass("active")?(f=l.$activeItems.indexOf(b[0]),l.$activeItems.splice(f,1),b.removeClass("active")):l.$activeItems.push(b.addClass("active")[0]):(a(l.$activeItems).removeClass("active"),l.$activeItems=[b.addClass("active")[0]]);l.hideInput(),this.isFocused||l.focus()}},setActiveOption:function(b,c,d){var e,f,g,h,i,k=this;k.$activeOption&&k.$activeOption.removeClass("active"),k.$activeOption=null,b=a(b),b.length&&(k.$activeOption=b.addClass("active"),!c&&j(c)||(e=k.$dropdown_content.height(),f=k.$activeOption.outerHeight(!0),c=k.$dropdown_content.scrollTop()||0,g=k.$activeOption.offset().top-k.$dropdown_content.offset().top+c,h=g,i=g-e+f,g+f>e+c?k.$dropdown_content.stop().animate({scrollTop:i},d?k.settings.scrollDuration:0):g=0;c--)-1!==f.items.indexOf(k(d.items[c].id))&&d.items.splice(c,1);return d},refreshOptions:function(b){var c,e,f,g,h,i,j,l,m,n,o,p,q,r,s,t;void 0===b&&(b=!0);var u=this,w=a.trim(u.$control_input.val()),x=u.search(w),y=u.$dropdown_content,z=u.$activeOption&&k(u.$activeOption.attr("data-value"));for(g=x.items.length,"number"==typeof u.settings.maxOptions&&(g=Math.min(g,u.settings.maxOptions)),h={},i=[],c=0;c0||q,u.hasOptions?(x.items.length>0?(s=z&&u.getOption(z),s&&s.length?r=s:"single"===u.settings.mode&&u.items.length&&(r=u.getOption(u.items[0])),r&&r.length||(r=t&&!u.settings.addPrecedence?u.getAdjacentOption(t,1):y.find("[data-selectable]:first"))):r=t,u.setActiveOption(r),b&&!u.isOpen&&u.open()):(u.setActiveOption(null),b&&u.isOpen&&u.close())},addOption:function(b){var c,d,e,f=this;if(a.isArray(b))for(c=0,d=b.length;c=0&&e0),b.$control_input.data("grow",!c&&!d)},isFull:function(){ return null!==this.settings.maxItems&&this.items.length>=this.settings.maxItems},updateOriginalInput:function(a){var b,c,d,e,f=this;if(a=a||{},1===f.tagType){for(d=[],b=0,c=f.items.length;b'+l(e)+"");d.length||this.$input.attr("multiple")||d.push(''),f.$input.html(d.join(""))}else f.$input.val(f.getValue()),f.$input.attr("value",f.$input.val());f.isSetup&&(a.silent||f.trigger("change",f.$input.val()))},updatePlaceholder:function(){if(this.settings.placeholder){var a=this.$control_input;this.items.length?a.removeAttr("placeholder"):a.attr("placeholder",this.settings.placeholder),a.triggerHandler("update",{force:!0})}},open:function(){var a=this;a.isLocked||a.isOpen||"multi"===a.settings.mode&&a.isFull()||(a.focus(),a.isOpen=!0,a.refreshState(),a.$dropdown.css({visibility:"hidden",display:"block"}),a.positionDropdown(),a.$dropdown.css({visibility:"visible"}),a.trigger("dropdown_open",a.$dropdown))},close:function(){var a=this,b=a.isOpen;"single"===a.settings.mode&&a.items.length&&(a.hideInput(),a.isBlurring||a.$control_input.blur()),a.isOpen=!1,a.$dropdown.hide(),a.setActiveOption(null),a.refreshState(),b&&a.trigger("dropdown_close",a.$dropdown)},positionDropdown:function(){var a=this.$control,b="body"===this.settings.dropdownParent?a.offset():a.position();b.top+=a.outerHeight(!0),this.$dropdown.css({width:a[0].getBoundingClientRect().width,top:b.top,left:b.left})},clear:function(a){var b=this;b.items.length&&(b.$control.children(":not(input)").remove(),b.items=[],b.lastQuery=null,b.setCaret(0),b.setActiveItem(null),b.updatePlaceholder(),b.updateOriginalInput({silent:a}),b.refreshState(),b.showInput(),b.trigger("clear"))},insertAtCaret:function(a){var b=Math.min(this.caretPos,this.items.length),c=a[0],d=this.buffer||this.$control[0];0===b?d.insertBefore(c,d.firstChild):d.insertBefore(c,d.childNodes[b]),this.setCaret(b+1)},deleteSelection:function(b){var c,d,e,f,g,h,i,j,k,l=this;if(e=b&&8===b.keyCode?-1:1,f=r(l.$control_input[0]),l.$activeOption&&!l.settings.hideSelected&&(i=l.getAdjacentOption(l.$activeOption,-1).attr("data-value")),g=[],l.$activeItems.length){for(k=l.$control.children(".active:"+(e>0?"last":"first")),h=l.$control.children(":not(input)").index(k),e>0&&h++,c=0,d=l.$activeItems.length;c0&&f.start===l.$control_input.val().length&&g.push(l.items[l.caretPos]));if(!g.length||"function"==typeof l.settings.onDelete&&!1===l.settings.onDelete.apply(l,[g]))return!1;for(void 0!==h&&l.setCaret(h);g.length;)l.removeItem(g.pop());return l.showInput(),l.positionDropdown(),l.refreshOptions(!0),i&&(j=l.getOption(i),j.length&&l.setActiveOption(j)),!0},advanceSelection:function(a,b){var c,d,e,f,g,h=this;0!==a&&(h.rtl&&(a*=-1),c=a>0?"last":"first",d=r(h.$control_input[0]),h.isFocused&&!h.isInputHidden?(f=h.$control_input.val().length,(a<0?0===d.start&&0===d.length:d.start===f)&&!f&&h.advanceCaret(a,b)):(g=h.$control.children(".active:"+c),g.length&&(e=h.$control.children(":not(input)").index(g),h.setActiveItem(null),h.setCaret(a>0?e+1:e))))},advanceCaret:function(a,b){var c,d,e=this;0!==a&&(c=a>0?"next":"prev",e.isShiftDown?(d=e.$control_input[c](),d.length&&(e.hideInput(),e.setActiveItem(d),b&&b.preventDefault())):e.setCaret(e.caretPos+a))},setCaret:function(b){var c=this;if(b="single"===c.settings.mode?c.items.length:Math.max(0,Math.min(c.items.length,b)),!c.isPending){var d,e,f,g;for(f=c.$control.children(":not(input)"),d=0,e=f.length;d
'+a.title+'×
'}},b),c.setup=function(){var d=c.setup;return function(){d.apply(c,arguments),c.$dropdown_header=a(b.html(b)),c.$dropdown.prepend(c.$dropdown_header)}}()}),w.define("optgroup_columns",function(b){var c=this;b=a.extend({equalizeWidth:!0,equalizeHeight:!0},b),this.getAdjacentOption=function(b,c){var d=b.closest("[data-group]").find("[data-selectable]"),e=d.index(b)+c;return e>=0&&e
',a=a.firstChild,c.body.appendChild(a),b=d.width=a.offsetWidth-a.clientWidth,c.body.removeChild(a)),b},e=function(){var e,f,g,h,i,j,k;if(k=a("[data-group]",c.$dropdown_content),(f=k.length)&&c.$dropdown_content.width()){if(b.equalizeHeight){for(g=0,e=0;e1&&(i=j-h*(f-1),k.eq(f-1).css({width:i})))}};(b.equalizeHeight||b.equalizeWidth)&&(m.after(this,"positionDropdown",e),m.after(this,"refreshOptions",e))}),w.define("remove_button",function(b){b=a.extend({label:"×",title:"Remove",className:"remove",append:!0},b);if("single"===this.settings.mode)return void function(b,c){c.className="remove-single";var d=b,e=''+c.label+"",f=function(b,c){return a("").append(b).append(c)};b.setup=function(){var g=d.setup;return function(){if(c.append){var h=a(d.$input.context).attr("id"),i=(a("#"+h),d.settings.render.item);d.settings.render.item=function(a){return f(i.apply(b,arguments),e)}}g.apply(b,arguments),b.$control.on("click","."+c.className,function(a){a.preventDefault(),d.isLocked||d.clear()})}}()}(this,b);!function(b,c){var d=b,e=''+c.label+"",f=function(a,b){var c=a.search(/(<\/[^>]+>\s*)$/);return a.substring(0,c)+b+a.substring(c)};b.setup=function(){var g=d.setup;return function(){if(c.append){var h=d.settings.render.item;d.settings.render.item=function(a){return f(h.apply(b,arguments),e)}}g.apply(b,arguments),b.$control.on("click","."+c.className,function(b){if(b.preventDefault(),!d.isLocked){var c=a(b.currentTarget).parent();d.setActiveItem(c),d.deleteSelection()&&d.setCaret(d.items.length)}})}}()}(this,b)}),w.define("restore_on_backspace",function(a){var b=this;a.text=a.text||function(a){return a[this.settings.labelField]},this.onKeyDown=function(){var c=b.onKeyDown;return function(b){var d,e;return 8===b.keyCode&&""===this.$control_input.val()&&!this.$activeItems.length&&(d=this.caretPos-1)>=0&&d totalRight) { $u.css('margin-left', ($u.parents('ul').length === 1 ? totalRight - menuRight : -(menuWidth + parentWidth)) + 'px'); } var windowHeight = $w.height(), offsetTop = $u.offset().top, menuHeight = $u.height(), baseline = windowHeight + _offset('y'); var expandUp = (offsetTop + menuHeight > baseline); if (expandUp) { $u.css('margin-top',baseline - (menuHeight + offsetTop)); } $u.css('display','none'); }); }; return this.each(function() { var $this = $(this), o = $this.data('sf-options'); /* get this menu's options */ /* if callbacks already set, store them */ var _onInit = o.onInit, _onBeforeShow = o.onBeforeShow, _onHide = o.onHide; $.extend($this.data('sf-options'),{ onInit: function() { onInit.call(this); /* fire our Supposition callback */ _onInit.call(this); /* fire stored callbacks */ }, onBeforeShow: function() { onBeforeShow.call(this); /* fire our Supposition callback */ _onBeforeShow.call(this); /* fire stored callbacks */ }, onHide: function() { onHide.call(this); /* fire our Supposition callback */ _onHide.call(this); /* fire stored callbacks */ } }); }); }; })(jQuery);rt-5.0.1/share/static/js/farbtastic.js000644 000765 000024 00000023342 14005011336 020472 0ustar00sunnavystaff000000 000000 /** * Farbtastic Color Picker 1.2 * © 2008 Steven Wittens * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 USA */ (function ($){ jQuery.fn.farbtastic = function (callback) { $.farbtastic(this, callback); return this; }; jQuery.farbtastic = function (container, callback) { var container = $(container).get(0); return container.farbtastic || (container.farbtastic = new jQuery._farbtastic(container, callback)); } jQuery._farbtastic = function (container, callback) { // Store farbtastic object var fb = this; // Insert markup $(container).html('
'); var e = $('.farbtastic', container); fb.wheel = $('.wheel', container).get(0); // Dimensions fb.radius = 84; fb.square = 100; fb.width = 194; // Fix background PNGs in IE6 if (navigator.appVersion.match(/MSIE [0-6]\./)) { $('*', e).each(function () { if (this.currentStyle.backgroundImage != 'none') { var image = this.currentStyle.backgroundImage; image = this.currentStyle.backgroundImage.substring(5, image.length - 2); $(this).css({ 'backgroundImage': 'none', 'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')" }); } }); } /** * Link to the given element(s) or callback. */ fb.linkTo = function (callback) { // Unbind previous nodes if (typeof fb.callback == 'object') { $(fb.callback).unbind('keyup', fb.updateValue); } // Reset color fb.color = null; // Bind callback or elements if (typeof callback == 'function') { fb.callback = callback; } else if (typeof callback == 'object' || typeof callback == 'string') { fb.callback = $(callback); fb.callback.bind('keyup', fb.updateValue); if (fb.callback.get(0).value) { fb.setColor(fb.callback.get(0).value); } } return this; } fb.updateValue = function (event) { if (this.value && this.value != fb.color) { fb.setColor(this.value); } } /** * Change color with HTML syntax #123456 */ fb.setColor = function (color) { var unpack = fb.unpack(color); if (fb.color != color && unpack) { fb.color = color; fb.rgb = unpack; fb.hsl = fb.RGBToHSL(fb.rgb); fb.updateDisplay(); } return this; } /** * Change color with HSL triplet [0..1, 0..1, 0..1] */ fb.setHSL = function (hsl) { fb.hsl = hsl; fb.rgb = fb.HSLToRGB(hsl); fb.color = fb.pack(fb.rgb); fb.updateDisplay(); return this; } ///////////////////////////////////////////////////// /** * Retrieve the coordinates of the given event relative to the center * of the widget. */ fb.widgetCoords = function (event) { var x, y; var el = event.target || event.srcElement; var reference = fb.wheel; if (typeof event.offsetX != 'undefined') { // Use offset coordinates and find common offsetParent var pos = { x: event.offsetX, y: event.offsetY }; // Send the coordinates upwards through the offsetParent chain. var e = el; while (e) { e.mouseX = pos.x; e.mouseY = pos.y; pos.x += e.offsetLeft; pos.y += e.offsetTop; e = e.offsetParent; } // Look for the coordinates starting from the wheel widget. var e = reference; var offset = { x: 0, y: 0 } while (e) { if (typeof e.mouseX != 'undefined') { x = e.mouseX - offset.x; y = e.mouseY - offset.y; break; } offset.x += e.offsetLeft; offset.y += e.offsetTop; e = e.offsetParent; } // Reset stored coordinates e = el; while (e) { e.mouseX = undefined; e.mouseY = undefined; e = e.offsetParent; } } else { // Use absolute coordinates var pos = fb.absolutePosition(reference); x = (event.pageX || 0*(event.clientX + $('html').get(0).scrollLeft)) - pos.x; y = (event.pageY || 0*(event.clientY + $('html').get(0).scrollTop)) - pos.y; } // Subtract distance to middle return { x: x - fb.width / 2, y: y - fb.width / 2 }; } /** * Mousedown handler */ fb.mousedown = function (event) { // Capture mouse if (!document.dragging) { $(document).bind('mousemove', fb.mousemove).bind('mouseup', fb.mouseup); document.dragging = true; } // Check which area is being dragged var pos = fb.widgetCoords(event); fb.circleDrag = Math.max(Math.abs(pos.x), Math.abs(pos.y)) * 2 > fb.square; // Process fb.mousemove(event); return false; } /** * Mousemove handler */ fb.mousemove = function (event) { // Get coordinates relative to color picker center var pos = fb.widgetCoords(event); // Set new HSL parameters if (fb.circleDrag) { var hue = Math.atan2(pos.x, -pos.y) / 6.28; if (hue < 0) hue += 1; fb.setHSL([hue, fb.hsl[1], fb.hsl[2]]); } else { var sat = Math.max(0, Math.min(1, -(pos.x / fb.square) + .5)); var lum = Math.max(0, Math.min(1, -(pos.y / fb.square) + .5)); fb.setHSL([fb.hsl[0], sat, lum]); } return false; } /** * Mouseup handler */ fb.mouseup = function () { // Uncapture mouse $(document).unbind('mousemove', fb.mousemove); $(document).unbind('mouseup', fb.mouseup); document.dragging = false; } /** * Update the markers and styles */ fb.updateDisplay = function () { // Markers var angle = fb.hsl[0] * 6.28; $('.h-marker', e).css({ left: Math.round(Math.sin(angle) * fb.radius + fb.width / 2) + 'px', top: Math.round(-Math.cos(angle) * fb.radius + fb.width / 2) + 'px' }); $('.sl-marker', e).css({ left: Math.round(fb.square * (.5 - fb.hsl[1]) + fb.width / 2) + 'px', top: Math.round(fb.square * (.5 - fb.hsl[2]) + fb.width / 2) + 'px' }); // Saturation/Luminance gradient $('.color', e).css('backgroundColor', fb.pack(fb.HSLToRGB([fb.hsl[0], 1, 0.5]))); // Linked elements or callback if (typeof fb.callback == 'object') { // Set background/foreground color $(fb.callback).css({ backgroundColor: fb.color, color: fb.hsl[2] > 0.5 ? '#000' : '#fff' }); // Change linked value $(fb.callback).each(function() { if (this.value && this.value != fb.color) { this.value = fb.color; } }); } else if (typeof fb.callback == 'function') { fb.callback.call(fb, fb.color); } } /** * Get absolute position of element */ fb.absolutePosition = function (el) { var r = { x: el.offsetLeft, y: el.offsetTop }; // Resolve relative to offsetParent if (el.offsetParent) { var tmp = fb.absolutePosition(el.offsetParent); r.x += tmp.x; r.y += tmp.y; } return r; }; /* Various color utility functions */ fb.pack = function (rgb) { var r = Math.round(rgb[0] * 255); var g = Math.round(rgb[1] * 255); var b = Math.round(rgb[2] * 255); return '#' + (r < 16 ? '0' : '') + r.toString(16) + (g < 16 ? '0' : '') + g.toString(16) + (b < 16 ? '0' : '') + b.toString(16); } fb.unpack = function (color) { if (color.length == 7) { return [parseInt('0x' + color.substring(1, 3)) / 255, parseInt('0x' + color.substring(3, 5)) / 255, parseInt('0x' + color.substring(5, 7)) / 255]; } else if (color.length == 4) { return [parseInt('0x' + color.substring(1, 2)) / 15, parseInt('0x' + color.substring(2, 3)) / 15, parseInt('0x' + color.substring(3, 4)) / 15]; } } fb.HSLToRGB = function (hsl) { var m1, m2, r, g, b; var h = hsl[0], s = hsl[1], l = hsl[2]; m2 = (l <= 0.5) ? l * (s + 1) : l + s - l*s; m1 = l * 2 - m2; return [this.hueToRGB(m1, m2, h+0.33333), this.hueToRGB(m1, m2, h), this.hueToRGB(m1, m2, h-0.33333)]; } fb.hueToRGB = function (m1, m2, h) { h = (h < 0) ? h + 1 : ((h > 1) ? h - 1 : h); if (h * 6 < 1) return m1 + (m2 - m1) * h * 6; if (h * 2 < 1) return m2; if (h * 3 < 2) return m1 + (m2 - m1) * (0.66666 - h) * 6; return m1; } fb.RGBToHSL = function (rgb) { var min, max, delta, h, s, l; var r = rgb[0], g = rgb[1], b = rgb[2]; min = Math.min(r, Math.min(g, b)); max = Math.max(r, Math.max(g, b)); delta = max - min; l = (min + max) / 2; s = 0; if (l > 0 && l < 1) { s = delta / (l < 0.5 ? (2 * l) : (2 - 2 * l)); } h = 0; if (delta > 0) { if (max == r && max != g) h += (g - b) / delta; if (max == g && max != b) h += (2 + (b - r) / delta); if (max == b && max != r) h += (4 + (r - g) / delta); h /= 6; } return [h, s, l]; } // Install mousedown handler (the others are set on the document on-demand) $('*', e).mousedown(fb.mousedown); // Init color fb.setColor('#000000'); // Set linked elements/callback if (callback) { fb.linkTo(callback); } } })(jQuery); rt-5.0.1/share/static/js/chartjs-plugin-colorschemes.min.js000644 000765 000024 00000121122 14005011336 024543 0ustar00sunnavystaff000000 000000 /* * @license * chartjs-plugin-colorschemes * https://github.com/nagix/chartjs-plugin-colorschemes/ * Version: 0.3.0 * * Copyright 2019 Akihiko Kusanagi * Released under the MIT license * https://github.com/nagix/chartjs-plugin-colorschemes/blob/master/LICENSE.md */ !function(f,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("chart.js")):"function"==typeof define&&define.amd?define(["chart.js"],e):f["chartjs-plugin-colorschemes"]=e(f.Chart)}(this,function(f){"use strict";f=f&&f.hasOwnProperty("default")?f.default:f;var o={brewer:{YlGn3:["#f7fcb9","#addd8e","#31a354"],YlGn4:["#ffffcc","#c2e699","#78c679","#238443"],YlGn5:["#ffffcc","#c2e699","#78c679","#31a354","#006837"],YlGn6:["#ffffcc","#d9f0a3","#addd8e","#78c679","#31a354","#006837"],YlGn7:["#ffffcc","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#005a32"],YlGn8:["#ffffe5","#f7fcb9","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#005a32"],YlGn9:["#ffffe5","#f7fcb9","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#006837","#004529"],YlGnBu3:["#edf8b1","#7fcdbb","#2c7fb8"],YlGnBu4:["#ffffcc","#a1dab4","#41b6c4","#225ea8"],YlGnBu5:["#ffffcc","#a1dab4","#41b6c4","#2c7fb8","#253494"],YlGnBu6:["#ffffcc","#c7e9b4","#7fcdbb","#41b6c4","#2c7fb8","#253494"],YlGnBu7:["#ffffcc","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#0c2c84"],YlGnBu8:["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#0c2c84"],YlGnBu9:["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"],GnBu3:["#e0f3db","#a8ddb5","#43a2ca"],GnBu4:["#f0f9e8","#bae4bc","#7bccc4","#2b8cbe"],GnBu5:["#f0f9e8","#bae4bc","#7bccc4","#43a2ca","#0868ac"],GnBu6:["#f0f9e8","#ccebc5","#a8ddb5","#7bccc4","#43a2ca","#0868ac"],GnBu7:["#f0f9e8","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#08589e"],GnBu8:["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#08589e"],GnBu9:["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#0868ac","#084081"],BuGn3:["#e5f5f9","#99d8c9","#2ca25f"],BuGn4:["#edf8fb","#b2e2e2","#66c2a4","#238b45"],BuGn5:["#edf8fb","#b2e2e2","#66c2a4","#2ca25f","#006d2c"],BuGn6:["#edf8fb","#ccece6","#99d8c9","#66c2a4","#2ca25f","#006d2c"],BuGn7:["#edf8fb","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#005824"],BuGn8:["#f7fcfd","#e5f5f9","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#005824"],BuGn9:["#f7fcfd","#e5f5f9","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#006d2c","#00441b"],PuBuGn3:["#ece2f0","#a6bddb","#1c9099"],PuBuGn4:["#f6eff7","#bdc9e1","#67a9cf","#02818a"],PuBuGn5:["#f6eff7","#bdc9e1","#67a9cf","#1c9099","#016c59"],PuBuGn6:["#f6eff7","#d0d1e6","#a6bddb","#67a9cf","#1c9099","#016c59"],PuBuGn7:["#f6eff7","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016450"],PuBuGn8:["#fff7fb","#ece2f0","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016450"],PuBuGn9:["#fff7fb","#ece2f0","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016c59","#014636"],PuBu3:["#ece7f2","#a6bddb","#2b8cbe"],PuBu4:["#f1eef6","#bdc9e1","#74a9cf","#0570b0"],PuBu5:["#f1eef6","#bdc9e1","#74a9cf","#2b8cbe","#045a8d"],PuBu6:["#f1eef6","#d0d1e6","#a6bddb","#74a9cf","#2b8cbe","#045a8d"],PuBu7:["#f1eef6","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#034e7b"],PuBu8:["#fff7fb","#ece7f2","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#034e7b"],PuBu9:["#fff7fb","#ece7f2","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#045a8d","#023858"],BuPu3:["#e0ecf4","#9ebcda","#8856a7"],BuPu4:["#edf8fb","#b3cde3","#8c96c6","#88419d"],BuPu5:["#edf8fb","#b3cde3","#8c96c6","#8856a7","#810f7c"],BuPu6:["#edf8fb","#bfd3e6","#9ebcda","#8c96c6","#8856a7","#810f7c"],BuPu7:["#edf8fb","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#6e016b"],BuPu8:["#f7fcfd","#e0ecf4","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#6e016b"],BuPu9:["#f7fcfd","#e0ecf4","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#810f7c","#4d004b"],RdPu3:["#fde0dd","#fa9fb5","#c51b8a"],RdPu4:["#feebe2","#fbb4b9","#f768a1","#ae017e"],RdPu5:["#feebe2","#fbb4b9","#f768a1","#c51b8a","#7a0177"],RdPu6:["#feebe2","#fcc5c0","#fa9fb5","#f768a1","#c51b8a","#7a0177"],RdPu7:["#feebe2","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177"],RdPu8:["#fff7f3","#fde0dd","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177"],RdPu9:["#fff7f3","#fde0dd","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177","#49006a"],PuRd3:["#e7e1ef","#c994c7","#dd1c77"],PuRd4:["#f1eef6","#d7b5d8","#df65b0","#ce1256"],PuRd5:["#f1eef6","#d7b5d8","#df65b0","#dd1c77","#980043"],PuRd6:["#f1eef6","#d4b9da","#c994c7","#df65b0","#dd1c77","#980043"],PuRd7:["#f1eef6","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#91003f"],PuRd8:["#f7f4f9","#e7e1ef","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#91003f"],PuRd9:["#f7f4f9","#e7e1ef","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#980043","#67001f"],OrRd3:["#fee8c8","#fdbb84","#e34a33"],OrRd4:["#fef0d9","#fdcc8a","#fc8d59","#d7301f"],OrRd5:["#fef0d9","#fdcc8a","#fc8d59","#e34a33","#b30000"],OrRd6:["#fef0d9","#fdd49e","#fdbb84","#fc8d59","#e34a33","#b30000"],OrRd7:["#fef0d9","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#990000"],OrRd8:["#fff7ec","#fee8c8","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#990000"],OrRd9:["#fff7ec","#fee8c8","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#b30000","#7f0000"],YlOrRd3:["#ffeda0","#feb24c","#f03b20"],YlOrRd4:["#ffffb2","#fecc5c","#fd8d3c","#e31a1c"],YlOrRd5:["#ffffb2","#fecc5c","#fd8d3c","#f03b20","#bd0026"],YlOrRd6:["#ffffb2","#fed976","#feb24c","#fd8d3c","#f03b20","#bd0026"],YlOrRd7:["#ffffb2","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#b10026"],YlOrRd8:["#ffffcc","#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#b10026"],YlOrRd9:["#ffffcc","#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#bd0026","#800026"],YlOrBr3:["#fff7bc","#fec44f","#d95f0e"],YlOrBr4:["#ffffd4","#fed98e","#fe9929","#cc4c02"],YlOrBr5:["#ffffd4","#fed98e","#fe9929","#d95f0e","#993404"],YlOrBr6:["#ffffd4","#fee391","#fec44f","#fe9929","#d95f0e","#993404"],YlOrBr7:["#ffffd4","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#8c2d04"],YlOrBr8:["#ffffe5","#fff7bc","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#8c2d04"],YlOrBr9:["#ffffe5","#fff7bc","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#993404","#662506"],Purples3:["#efedf5","#bcbddc","#756bb1"],Purples4:["#f2f0f7","#cbc9e2","#9e9ac8","#6a51a3"],Purples5:["#f2f0f7","#cbc9e2","#9e9ac8","#756bb1","#54278f"],Purples6:["#f2f0f7","#dadaeb","#bcbddc","#9e9ac8","#756bb1","#54278f"],Purples7:["#f2f0f7","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#4a1486"],Purples8:["#fcfbfd","#efedf5","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#4a1486"],Purples9:["#fcfbfd","#efedf5","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#54278f","#3f007d"],Blues3:["#deebf7","#9ecae1","#3182bd"],Blues4:["#eff3ff","#bdd7e7","#6baed6","#2171b5"],Blues5:["#eff3ff","#bdd7e7","#6baed6","#3182bd","#08519c"],Blues6:["#eff3ff","#c6dbef","#9ecae1","#6baed6","#3182bd","#08519c"],Blues7:["#eff3ff","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#084594"],Blues8:["#f7fbff","#deebf7","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#084594"],Blues9:["#f7fbff","#deebf7","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#08519c","#08306b"],Greens3:["#e5f5e0","#a1d99b","#31a354"],Greens4:["#edf8e9","#bae4b3","#74c476","#238b45"],Greens5:["#edf8e9","#bae4b3","#74c476","#31a354","#006d2c"],Greens6:["#edf8e9","#c7e9c0","#a1d99b","#74c476","#31a354","#006d2c"],Greens7:["#edf8e9","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#005a32"],Greens8:["#f7fcf5","#e5f5e0","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#005a32"],Greens9:["#f7fcf5","#e5f5e0","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#006d2c","#00441b"],Oranges3:["#fee6ce","#fdae6b","#e6550d"],Oranges4:["#feedde","#fdbe85","#fd8d3c","#d94701"],Oranges5:["#feedde","#fdbe85","#fd8d3c","#e6550d","#a63603"],Oranges6:["#feedde","#fdd0a2","#fdae6b","#fd8d3c","#e6550d","#a63603"],Oranges7:["#feedde","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#8c2d04"],Oranges8:["#fff5eb","#fee6ce","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#8c2d04"],Oranges9:["#fff5eb","#fee6ce","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#a63603","#7f2704"],Reds3:["#fee0d2","#fc9272","#de2d26"],Reds4:["#fee5d9","#fcae91","#fb6a4a","#cb181d"],Reds5:["#fee5d9","#fcae91","#fb6a4a","#de2d26","#a50f15"],Reds6:["#fee5d9","#fcbba1","#fc9272","#fb6a4a","#de2d26","#a50f15"],Reds7:["#fee5d9","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#99000d"],Reds8:["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#99000d"],Reds9:["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#a50f15","#67000d"],Greys3:["#f0f0f0","#bdbdbd","#636363"],Greys4:["#f7f7f7","#cccccc","#969696","#525252"],Greys5:["#f7f7f7","#cccccc","#969696","#636363","#252525"],Greys6:["#f7f7f7","#d9d9d9","#bdbdbd","#969696","#636363","#252525"],Greys7:["#f7f7f7","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525"],Greys8:["#ffffff","#f0f0f0","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525"],Greys9:["#ffffff","#f0f0f0","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525","#000000"],PuOr3:["#f1a340","#f7f7f7","#998ec3"],PuOr4:["#e66101","#fdb863","#b2abd2","#5e3c99"],PuOr5:["#e66101","#fdb863","#f7f7f7","#b2abd2","#5e3c99"],PuOr6:["#b35806","#f1a340","#fee0b6","#d8daeb","#998ec3","#542788"],PuOr7:["#b35806","#f1a340","#fee0b6","#f7f7f7","#d8daeb","#998ec3","#542788"],PuOr8:["#b35806","#e08214","#fdb863","#fee0b6","#d8daeb","#b2abd2","#8073ac","#542788"],PuOr9:["#b35806","#e08214","#fdb863","#fee0b6","#f7f7f7","#d8daeb","#b2abd2","#8073ac","#542788"],PuOr10:["#7f3b08","#b35806","#e08214","#fdb863","#fee0b6","#d8daeb","#b2abd2","#8073ac","#542788","#2d004b"],PuOr11:["#7f3b08","#b35806","#e08214","#fdb863","#fee0b6","#f7f7f7","#d8daeb","#b2abd2","#8073ac","#542788","#2d004b"],BrBG3:["#d8b365","#f5f5f5","#5ab4ac"],BrBG4:["#a6611a","#dfc27d","#80cdc1","#018571"],BrBG5:["#a6611a","#dfc27d","#f5f5f5","#80cdc1","#018571"],BrBG6:["#8c510a","#d8b365","#f6e8c3","#c7eae5","#5ab4ac","#01665e"],BrBG7:["#8c510a","#d8b365","#f6e8c3","#f5f5f5","#c7eae5","#5ab4ac","#01665e"],BrBG8:["#8c510a","#bf812d","#dfc27d","#f6e8c3","#c7eae5","#80cdc1","#35978f","#01665e"],BrBG9:["#8c510a","#bf812d","#dfc27d","#f6e8c3","#f5f5f5","#c7eae5","#80cdc1","#35978f","#01665e"],BrBG10:["#543005","#8c510a","#bf812d","#dfc27d","#f6e8c3","#c7eae5","#80cdc1","#35978f","#01665e","#003c30"],BrBG11:["#543005","#8c510a","#bf812d","#dfc27d","#f6e8c3","#f5f5f5","#c7eae5","#80cdc1","#35978f","#01665e","#003c30"],PRGn3:["#af8dc3","#f7f7f7","#7fbf7b"],PRGn4:["#7b3294","#c2a5cf","#a6dba0","#008837"],PRGn5:["#7b3294","#c2a5cf","#f7f7f7","#a6dba0","#008837"],PRGn6:["#762a83","#af8dc3","#e7d4e8","#d9f0d3","#7fbf7b","#1b7837"],PRGn7:["#762a83","#af8dc3","#e7d4e8","#f7f7f7","#d9f0d3","#7fbf7b","#1b7837"],PRGn8:["#762a83","#9970ab","#c2a5cf","#e7d4e8","#d9f0d3","#a6dba0","#5aae61","#1b7837"],PRGn9:["#762a83","#9970ab","#c2a5cf","#e7d4e8","#f7f7f7","#d9f0d3","#a6dba0","#5aae61","#1b7837"],PRGn10:["#40004b","#762a83","#9970ab","#c2a5cf","#e7d4e8","#d9f0d3","#a6dba0","#5aae61","#1b7837","#00441b"],PRGn11:["#40004b","#762a83","#9970ab","#c2a5cf","#e7d4e8","#f7f7f7","#d9f0d3","#a6dba0","#5aae61","#1b7837","#00441b"],PiYG3:["#e9a3c9","#f7f7f7","#a1d76a"],PiYG4:["#d01c8b","#f1b6da","#b8e186","#4dac26"],PiYG5:["#d01c8b","#f1b6da","#f7f7f7","#b8e186","#4dac26"],PiYG6:["#c51b7d","#e9a3c9","#fde0ef","#e6f5d0","#a1d76a","#4d9221"],PiYG7:["#c51b7d","#e9a3c9","#fde0ef","#f7f7f7","#e6f5d0","#a1d76a","#4d9221"],PiYG8:["#c51b7d","#de77ae","#f1b6da","#fde0ef","#e6f5d0","#b8e186","#7fbc41","#4d9221"],PiYG9:["#c51b7d","#de77ae","#f1b6da","#fde0ef","#f7f7f7","#e6f5d0","#b8e186","#7fbc41","#4d9221"],PiYG10:["#8e0152","#c51b7d","#de77ae","#f1b6da","#fde0ef","#e6f5d0","#b8e186","#7fbc41","#4d9221","#276419"],PiYG11:["#8e0152","#c51b7d","#de77ae","#f1b6da","#fde0ef","#f7f7f7","#e6f5d0","#b8e186","#7fbc41","#4d9221","#276419"],RdBu3:["#ef8a62","#f7f7f7","#67a9cf"],RdBu4:["#ca0020","#f4a582","#92c5de","#0571b0"],RdBu5:["#ca0020","#f4a582","#f7f7f7","#92c5de","#0571b0"],RdBu6:["#b2182b","#ef8a62","#fddbc7","#d1e5f0","#67a9cf","#2166ac"],RdBu7:["#b2182b","#ef8a62","#fddbc7","#f7f7f7","#d1e5f0","#67a9cf","#2166ac"],RdBu8:["#b2182b","#d6604d","#f4a582","#fddbc7","#d1e5f0","#92c5de","#4393c3","#2166ac"],RdBu9:["#b2182b","#d6604d","#f4a582","#fddbc7","#f7f7f7","#d1e5f0","#92c5de","#4393c3","#2166ac"],RdBu10:["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#d1e5f0","#92c5de","#4393c3","#2166ac","#053061"],RdBu11:["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#f7f7f7","#d1e5f0","#92c5de","#4393c3","#2166ac","#053061"],RdGy3:["#ef8a62","#ffffff","#999999"],RdGy4:["#ca0020","#f4a582","#bababa","#404040"],RdGy5:["#ca0020","#f4a582","#ffffff","#bababa","#404040"],RdGy6:["#b2182b","#ef8a62","#fddbc7","#e0e0e0","#999999","#4d4d4d"],RdGy7:["#b2182b","#ef8a62","#fddbc7","#ffffff","#e0e0e0","#999999","#4d4d4d"],RdGy8:["#b2182b","#d6604d","#f4a582","#fddbc7","#e0e0e0","#bababa","#878787","#4d4d4d"],RdGy9:["#b2182b","#d6604d","#f4a582","#fddbc7","#ffffff","#e0e0e0","#bababa","#878787","#4d4d4d"],RdGy10:["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#e0e0e0","#bababa","#878787","#4d4d4d","#1a1a1a"],RdGy11:["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#ffffff","#e0e0e0","#bababa","#878787","#4d4d4d","#1a1a1a"],RdYlBu3:["#fc8d59","#ffffbf","#91bfdb"],RdYlBu4:["#d7191c","#fdae61","#abd9e9","#2c7bb6"],RdYlBu5:["#d7191c","#fdae61","#ffffbf","#abd9e9","#2c7bb6"],RdYlBu6:["#d73027","#fc8d59","#fee090","#e0f3f8","#91bfdb","#4575b4"],RdYlBu7:["#d73027","#fc8d59","#fee090","#ffffbf","#e0f3f8","#91bfdb","#4575b4"],RdYlBu8:["#d73027","#f46d43","#fdae61","#fee090","#e0f3f8","#abd9e9","#74add1","#4575b4"],RdYlBu9:["#d73027","#f46d43","#fdae61","#fee090","#ffffbf","#e0f3f8","#abd9e9","#74add1","#4575b4"],RdYlBu10:["#a50026","#d73027","#f46d43","#fdae61","#fee090","#e0f3f8","#abd9e9","#74add1","#4575b4","#313695"],RdYlBu11:["#a50026","#d73027","#f46d43","#fdae61","#fee090","#ffffbf","#e0f3f8","#abd9e9","#74add1","#4575b4","#313695"],Spectral3:["#fc8d59","#ffffbf","#99d594"],Spectral4:["#d7191c","#fdae61","#abdda4","#2b83ba"],Spectral5:["#d7191c","#fdae61","#ffffbf","#abdda4","#2b83ba"],Spectral6:["#d53e4f","#fc8d59","#fee08b","#e6f598","#99d594","#3288bd"],Spectral7:["#d53e4f","#fc8d59","#fee08b","#ffffbf","#e6f598","#99d594","#3288bd"],Spectral8:["#d53e4f","#f46d43","#fdae61","#fee08b","#e6f598","#abdda4","#66c2a5","#3288bd"],Spectral9:["#d53e4f","#f46d43","#fdae61","#fee08b","#ffffbf","#e6f598","#abdda4","#66c2a5","#3288bd"],Spectral10:["#9e0142","#d53e4f","#f46d43","#fdae61","#fee08b","#e6f598","#abdda4","#66c2a5","#3288bd","#5e4fa2"],Spectral11:["#9e0142","#d53e4f","#f46d43","#fdae61","#fee08b","#ffffbf","#e6f598","#abdda4","#66c2a5","#3288bd","#5e4fa2"],RdYlGn3:["#fc8d59","#ffffbf","#91cf60"],RdYlGn4:["#d7191c","#fdae61","#a6d96a","#1a9641"],RdYlGn5:["#d7191c","#fdae61","#ffffbf","#a6d96a","#1a9641"],RdYlGn6:["#d73027","#fc8d59","#fee08b","#d9ef8b","#91cf60","#1a9850"],RdYlGn7:["#d73027","#fc8d59","#fee08b","#ffffbf","#d9ef8b","#91cf60","#1a9850"],RdYlGn8:["#d73027","#f46d43","#fdae61","#fee08b","#d9ef8b","#a6d96a","#66bd63","#1a9850"],RdYlGn9:["#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850"],RdYlGn10:["#a50026","#d73027","#f46d43","#fdae61","#fee08b","#d9ef8b","#a6d96a","#66bd63","#1a9850","#006837"],RdYlGn11:["#a50026","#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850","#006837"],Accent3:["#7fc97f","#beaed4","#fdc086"],Accent4:["#7fc97f","#beaed4","#fdc086","#ffff99"],Accent5:["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0"],Accent6:["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f"],Accent7:["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f","#bf5b17"],Accent8:["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f","#bf5b17","#666666"],"Dark2-3":["#1b9e77","#d95f02","#7570b3"],"Dark2-4":["#1b9e77","#d95f02","#7570b3","#e7298a"],"Dark2-5":["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e"],"Dark2-6":["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02"],"Dark2-7":["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d"],"Dark2-8":["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d","#666666"],Paired3:["#a6cee3","#1f78b4","#b2df8a"],Paired4:["#a6cee3","#1f78b4","#b2df8a","#33a02c"],Paired5:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99"],Paired6:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c"],Paired7:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f"],Paired8:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00"],Paired9:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6"],Paired10:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a"],Paired11:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#ffff99"],Paired12:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#ffff99","#b15928"],"Pastel1-3":["#fbb4ae","#b3cde3","#ccebc5"],"Pastel1-4":["#fbb4ae","#b3cde3","#ccebc5","#decbe4"],"Pastel1-5":["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6"],"Pastel1-6":["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc"],"Pastel1-7":["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd"],"Pastel1-8":["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec"],"Pastel1-9":["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec","#f2f2f2"],"Pastel2-3":["#b3e2cd","#fdcdac","#cbd5e8"],"Pastel2-4":["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4"],"Pastel2-5":["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9"],"Pastel2-6":["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae"],"Pastel2-7":["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae","#f1e2cc"],"Pastel2-8":["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae","#f1e2cc","#cccccc"],"Set1-3":["#e41a1c","#377eb8","#4daf4a"],"Set1-4":["#e41a1c","#377eb8","#4daf4a","#984ea3"],"Set1-5":["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00"],"Set1-6":["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33"],"Set1-7":["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628"],"Set1-8":["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628","#f781bf"],"Set1-9":["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628","#f781bf","#999999"],"Set2-3":["#66c2a5","#fc8d62","#8da0cb"],"Set2-4":["#66c2a5","#fc8d62","#8da0cb","#e78ac3"],"Set2-5":["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854"],"Set2-6":["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f"],"Set2-7":["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f","#e5c494"],"Set2-8":["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f","#e5c494","#b3b3b3"],"Set3-3":["#8dd3c7","#ffffb3","#bebada"],"Set3-4":["#8dd3c7","#ffffb3","#bebada","#fb8072"],"Set3-5":["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3"],"Set3-6":["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462"],"Set3-7":["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69"],"Set3-8":["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5"],"Set3-9":["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9"],"Set3-10":["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd"],"Set3-11":["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd","#ccebc5"],"Set3-12":["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd","#ccebc5","#ffed6f"]},office:{Adjacency6:["#a9a57c","#9cbebd","#d2cb6c","#95a39d","#c89f5d","#b1a089"],Advantage6:["#663366","#330f42","#666699","#999966","#f7901e","#a3a101"],Angles6:["#797b7e","#f96a1b","#08a1d9","#7c984a","#c2ad8d","#506e94"],Apex6:["#ceb966","#9cb084","#6bb1c9","#6585cf","#7e6bc9","#a379bb"],Apothecary6:["#93a299","#cf543f","#b5ae53","#848058","#e8b54d","#786c71"],Aspect6:["#f07f09","#9f2936","#1b587c","#4e8542","#604878","#c19859"],Atlas6:["#f81b02","#fc7715","#afbf41","#50c49f","#3b95c4","#b560d4"],Austin6:["#94c600","#71685a","#ff6700","#909465","#956b43","#fea022"],Badge6:["#f8b323","#656a59","#46b2b5","#8caa7e","#d36f68","#826276"],Banded6:["#ffc000","#a5d028","#08cc78","#f24099","#828288","#f56617"],Basis6:["#f09415","#c1b56b","#4baf73","#5aa6c0","#d17df9","#fa7e5c"],Berlin6:["#a6b727","#df5327","#fe9e00","#418ab3","#d7d447","#818183"],BlackTie6:["#6f6f74","#a7b789","#beae98","#92a9b9","#9c8265","#8d6974"],Blue6:["#0f6fc6","#009dd9","#0bd0d9","#10cf9b","#7cca62","#a5c249"],BlueGreen6:["#3494ba","#58b6c0","#75bda7","#7a8c8e","#84acb6","#2683c6"],BlueII6:["#1cade4","#2683c6","#27ced7","#42ba97","#3e8853","#62a39f"],BlueRed6:["#4a66ac","#629dd1","#297fd5","#7f8fa9","#5aa2ae","#9d90a0"],BlueWarm6:["#4a66ac","#629dd1","#297fd5","#7f8fa9","#5aa2ae","#9d90a0"],Breeze6:["#2c7c9f","#244a58","#e2751d","#ffb400","#7eb606","#c00000"],Capital6:["#4b5a60","#9c5238","#504539","#c1ad79","#667559","#bad6ad"],Celestial6:["#ac3ec1","#477bd1","#46b298","#90ba4c","#dd9d31","#e25247"],Circuit6:["#9acd4c","#faa93a","#d35940","#b258d3","#63a0cc","#8ac4a7"],Civic6:["#d16349","#ccb400","#8cadae","#8c7b70","#8fb08c","#d19049"],Clarity6:["#93a299","#ad8f67","#726056","#4c5a6a","#808da0","#79463d"],Codex6:["#990000","#efab16","#78ac35","#35aca2","#4083cf","#0d335e"],Composite6:["#98c723","#59b0b9","#deae00","#b77bb4","#e0773c","#a98d63"],Concourse6:["#2da2bf","#da1f28","#eb641b","#39639d","#474b78","#7d3c4a"],Couture6:["#9e8e5c","#a09781","#85776d","#aeafa9","#8d878b","#6b6149"],Crop6:["#8c8d86","#e6c069","#897b61","#8dab8e","#77a2bb","#e28394"],Damask6:["#9ec544","#50bea3","#4a9ccc","#9a66ca","#c54f71","#de9c3c"],Depth6:["#41aebd","#97e9d5","#a2cf49","#608f3d","#f4de3a","#fcb11c"],Dividend6:["#4d1434","#903163","#b2324b","#969fa7","#66b1ce","#40619d"],Droplet6:["#2fa3ee","#4bcaad","#86c157","#d99c3f","#ce6633","#a35dd1"],Elemental6:["#629dd1","#297fd5","#7f8fa9","#4a66ac","#5aa2ae","#9d90a0"],Equity6:["#d34817","#9b2d1f","#a28e6a","#956251","#918485","#855d5d"],Essential6:["#7a7a7a","#f5c201","#526db0","#989aac","#dc5924","#b4b392"],Excel16:["#9999ff","#993366","#ffffcc","#ccffff","#660066","#ff8080","#0066cc","#ccccff","#000080","#ff00ff","#ffff00","#0000ff","#800080","#800000","#008080","#0000ff"],Executive6:["#6076b4","#9c5252","#e68422","#846648","#63891f","#758085"],Exhibit6:["#3399ff","#69ffff","#ccff33","#3333ff","#9933ff","#ff33ff"],Expo6:["#fbc01e","#efe1a2","#fa8716","#be0204","#640f10","#7e13e3"],Facet6:["#90c226","#54a021","#e6b91e","#e76618","#c42f1a","#918655"],Feathered6:["#606372","#79a8a4","#b2ad8f","#ad8082","#dec18c","#92a185"],Flow6:["#0f6fc6","#009dd9","#0bd0d9","#10cf9b","#7cca62","#a5c249"],Focus6:["#ffb91d","#f97817","#6de304","#ff0000","#732bea","#c913ad"],Folio6:["#294171","#748cbc","#8e887c","#834736","#5a1705","#a0a16a"],Formal6:["#907f76","#a46645","#cd9c47","#9a92cd","#7d639b","#733678"],Forte6:["#c70f0c","#dd6b0d","#faa700","#93e50d","#17c7ba","#0a96e4"],Foundry6:["#72a376","#b0ccb0","#a8cdd7","#c0beaf","#cec597","#e8b7b7"],Frame6:["#40bad2","#fab900","#90bb23","#ee7008","#1ab39f","#d5393d"],Gallery6:["#b71e42","#de478e","#bc72f0","#795faf","#586ea6","#6892a0"],Genesis6:["#80b606","#e29f1d","#2397e2","#35aca2","#5430bb","#8d34e0"],Grayscale6:["#dddddd","#b2b2b2","#969696","#808080","#5f5f5f","#4d4d4d"],Green6:["#549e39","#8ab833","#c0cf3a","#029676","#4ab5c4","#0989b1"],GreenYellow6:["#99cb38","#63a537","#37a76f","#44c1a3","#4eb3cf","#51c3f9"],Grid6:["#c66951","#bf974d","#928b70","#87706b","#94734e","#6f777d"],Habitat6:["#f8c000","#f88600","#f83500","#8b723d","#818b3d","#586215"],Hardcover6:["#873624","#d6862d","#d0be40","#877f6c","#972109","#aeb795"],Headlines6:["#439eb7","#e28b55","#dcb64d","#4ca198","#835b82","#645135"],Horizon6:["#7e97ad","#cc8e60","#7a6a60","#b4936d","#67787b","#9d936f"],Infusion6:["#8c73d0","#c2e8c4","#c5a6e8","#b45ec7","#9fdafb","#95c5b0"],Inkwell6:["#860908","#4a0505","#7a500a","#c47810","#827752","#b5bb83"],Inspiration6:["#749805","#bacc82","#6e9ec2","#2046a5","#5039c6","#7411d0"],Integral6:["#1cade4","#2683c6","#27ced7","#42ba97","#3e8853","#62a39f"],Ion6:["#b01513","#ea6312","#e6b729","#6aac90","#5f9c9d","#9e5e9b"],IonBoardroom6:["#b31166","#e33d6f","#e45f3c","#e9943a","#9b6bf2","#d53dd0"],Kilter6:["#76c5ef","#fea022","#ff6700","#70a525","#a5d848","#20768c"],Madison6:["#a1d68b","#5ec795","#4dadcf","#cdb756","#e29c36","#8ec0c1"],MainEvent6:["#b80e0f","#a6987d","#7f9a71","#64969f","#9b75b2","#80737a"],Marquee6:["#418ab3","#a6b727","#f69200","#838383","#fec306","#df5327"],Median6:["#94b6d2","#dd8047","#a5ab81","#d8b25c","#7ba79d","#968c8c"],Mesh6:["#6f6f6f","#bfbfa5","#dcd084","#e7bf5f","#e9a039","#cf7133"],Metail6:["#6283ad","#324966","#5b9ea4","#1d5b57","#1b4430","#2f3c35"],Metro6:["#7fd13b","#ea157a","#feb80a","#00addc","#738ac8","#1ab39f"],Metropolitan6:["#50b4c8","#a8b97f","#9b9256","#657689","#7a855d","#84ac9d"],Module6:["#f0ad00","#60b5cc","#e66c7d","#6bb76d","#e88651","#c64847"],NewsPrint6:["#ad0101","#726056","#ac956e","#808da9","#424e5b","#730e00"],Office6:["#5b9bd5","#ed7d31","#a5a5a5","#ffc000","#4472c4","#70ad47"],"Office2007-2010-6":["#4f81bd","#c0504d","#9bbb59","#8064a2","#4bacc6","#f79646"],Opulent6:["#b83d68","#ac66bb","#de6c36","#f9b639","#cf6da4","#fa8d3d"],Orange6:["#e48312","#bd582c","#865640","#9b8357","#c2bc80","#94a088"],OrangeRed6:["#d34817","#9b2d1f","#a28e6a","#956251","#918485","#855d5d"],Orbit6:["#f2d908","#9de61e","#0d8be6","#c61b1b","#e26f08","#8d35d1"],Organic6:["#83992a","#3c9770","#44709d","#a23c33","#d97828","#deb340"],Oriel6:["#fe8637","#7598d9","#b32c16","#f5cd2d","#aebad5","#777c84"],Origin6:["#727ca3","#9fb8cd","#d2da7a","#fada7a","#b88472","#8e736a"],Paper6:["#a5b592","#f3a447","#e7bc29","#d092a7","#9c85c0","#809ec2"],Parallax6:["#30acec","#80c34f","#e29d3e","#d64a3b","#d64787","#a666e1"],Parcel6:["#f6a21d","#9bafb5","#c96731","#9ca383","#87795d","#a0988c"],Perception6:["#a2c816","#e07602","#e4c402","#7dc1ef","#21449b","#a2b170"],Perspective6:["#838d9b","#d2610c","#80716a","#94147c","#5d5ad2","#6f6c7d"],Pixel6:["#ff7f01","#f1b015","#fbec85","#d2c2f1","#da5af4","#9d09d1"],Plaza6:["#990000","#580101","#e94a00","#eb8f00","#a4a4a4","#666666"],Precedent6:["#993232","#9b6c34","#736c5d","#c9972b","#c95f2b","#8f7a05"],Pushpin6:["#fda023","#aa2b1e","#71685c","#64a73b","#eb5605","#b9ca1a"],Quotable6:["#00c6bb","#6feba0","#b6df5e","#efb251","#ef755f","#ed515c"],Red6:["#a5300f","#d55816","#e19825","#b19c7d","#7f5f52","#b27d49"],RedOrange6:["#e84c22","#ffbd47","#b64926","#ff8427","#cc9900","#b22600"],RedViolet6:["#e32d91","#c830cc","#4ea6dc","#4775e7","#8971e1","#d54773"],Retrospect6:["#e48312","#bd582c","#865640","#9b8357","#c2bc80","#94a088"],Revolution6:["#0c5986","#ddf53d","#508709","#bf5e00","#9c0001","#660075"],Saddle6:["#c6b178","#9c5b14","#71b2bc","#78aa5d","#867099","#4c6f75"],Savon6:["#1cade4","#2683c6","#27ced7","#42ba97","#3e8853","#62a39f"],Sketchbook6:["#a63212","#e68230","#9bb05e","#6b9bc7","#4e66b2","#8976ac"],Sky6:["#073779","#8fd9fb","#ffcc00","#eb6615","#c76402","#b523b4"],Slate6:["#bc451b","#d3ba68","#bb8640","#ad9277","#a55a43","#ad9d7b"],Slice6:["#052f61","#a50e82","#14967c","#6a9e1f","#e87d37","#c62324"],Slipstream6:["#4e67c8","#5eccf3","#a7ea52","#5dceaf","#ff8021","#f14124"],SOHO6:["#61625e","#964d2c","#66553e","#848058","#afa14b","#ad7d4d"],Solstice6:["#3891a7","#feb80a","#c32d2e","#84aa33","#964305","#475a8d"],Spectrum6:["#990000","#ff6600","#ffba00","#99cc00","#528a02","#333333"],Story6:["#1d86cd","#732e9a","#b50b1b","#e8950e","#55992b","#2c9c89"],Studio6:["#f7901e","#fec60b","#9fe62f","#4ea5d1","#1c4596","#542d90"],Summer6:["#51a6c2","#51c2a9","#7ec251","#e1dc53","#b54721","#a16bb1"],Technic6:["#6ea0b0","#ccaf0a","#8d89a4","#748560","#9e9273","#7e848d"],Thatch6:["#759aa5","#cfc60d","#99987f","#90ac97","#ffad1c","#b9ab6f"],Tradition6:["#6b4a0b","#790a14","#908342","#423e5c","#641345","#748a2f"],Travelogue6:["#b74d21","#a32323","#4576a3","#615d9a","#67924b","#bf7b1b"],Trek6:["#f0a22e","#a5644e","#b58b80","#c3986d","#a19574","#c17529"],Twilight6:["#e8bc4a","#83c1c6","#e78d35","#909ce1","#839c41","#cc5439"],Urban6:["#53548a","#438086","#a04da3","#c4652d","#8b5d3d","#5c92b5"],UrbanPop6:["#86ce24","#00a2e6","#fac810","#7d8f8c","#d06b20","#958b8b"],VaporTrail6:["#df2e28","#fe801a","#e9bf35","#81bb42","#32c7a9","#4a9bdc"],Venture6:["#9eb060","#d09a08","#f2ec86","#824f1c","#511818","#553876"],Verve6:["#ff388c","#e40059","#9c007f","#68007f","#005bd3","#00349e"],View6:["#6f6f74","#92a9b9","#a7b789","#b9a489","#8d6374","#9b7362"],Violet6:["#ad84c6","#8784c7","#5d739a","#6997af","#84acb6","#6f8183"],VioletII6:["#92278f","#9b57d3","#755dd9","#665eb8","#45a5ed","#5982db"],Waveform6:["#31b6fd","#4584d3","#5bd078","#a5d028","#f5c040","#05e0db"],Wisp6:["#a53010","#de7e18","#9f8351","#728653","#92aa4c","#6aac91"],WoodType6:["#d34817","#9b2d1f","#a28e6a","#956251","#918485","#855d5d"],Yellow6:["#ffca08","#f8931d","#ce8d3e","#ec7016","#e64823","#9c6a6a"],YellowOrange6:["#f0a22e","#a5644e","#b58b80","#c3986d","#a19574","#c17529"]},tableau:{Tableau10:["#4E79A7","#F28E2B","#E15759","#76B7B2","#59A14F","#EDC948","#B07AA1","#FF9DA7","#9C755F","#BAB0AC"],Tableau20:["#4E79A7","#A0CBE8","#F28E2B","#FFBE7D","#59A14F","#8CD17D","#B6992D","#F1CE63","#499894","#86BCB6","#E15759","#FF9D9A","#79706E","#BAB0AC","#D37295","#FABFD2","#B07AA1","#D4A6C8","#9D7660","#D7B5A6"],ColorBlind10:["#1170aa","#fc7d0b","#a3acb9","#57606c","#5fa2ce","#c85200","#7b848f","#a3cce9","#ffbc79","#c8d0d9"],SeattleGrays5:["#767f8b","#b3b7b8","#5c6068","#d3d3d3","#989ca3"],Traffic9:["#b60a1c","#e39802","#309143","#e03531","#f0bd27","#51b364","#ff684c","#ffda66","#8ace7e"],MillerStone11:["#4f6980","#849db1","#a2ceaa","#638b66","#bfbb60","#f47942","#fbb04e","#b66353","#d7ce9f","#b9aa97","#7e756d"],SuperfishelStone10:["#6388b4","#ffae34","#ef6f6a","#8cc2ca","#55ad89","#c3bc3f","#bb7693","#baa094","#a9b5ae","#767676"],NurielStone9:["#8175aa","#6fb899","#31a1b3","#ccb22b","#a39fc9","#94d0c0","#959c9e","#027b8e","#9f8f12"],JewelBright9:["#eb1e2c","#fd6f30","#f9a729","#f9d23c","#5fbb68","#64cdcc","#91dcea","#a4a4d5","#bbc9e5"],Summer8:["#bfb202","#b9ca5d","#cf3e53","#f1788d","#00a2b3","#97cfd0","#f3a546","#f7c480"],Winter10:["#90728f","#b9a0b4","#9d983d","#cecb76","#e15759","#ff9888","#6b6b6b","#bab2ae","#aa8780","#dab6af"],GreenOrangeTeal12:["#4e9f50","#87d180","#ef8a0c","#fcc66d","#3ca8bc","#98d9e4","#94a323","#c3ce3d","#a08400","#f7d42a","#26897e","#8dbfa8"],RedBlueBrown12:["#466f9d","#91b3d7","#ed444a","#feb5a2","#9d7660","#d7b5a6","#3896c4","#a0d4ee","#ba7e45","#39b87f","#c8133b","#ea8783"],PurplePinkGray12:["#8074a8","#c6c1f0","#c46487","#ffbed1","#9c9290","#c5bfbe","#9b93c9","#ddb5d5","#7c7270","#f498b6","#b173a0","#c799bc"],HueCircle19:["#1ba3c6","#2cb5c0","#30bcad","#21B087","#33a65c","#57a337","#a2b627","#d5bb21","#f8b620","#f89217","#f06719","#e03426","#f64971","#fc719e","#eb73b3","#ce69be","#a26dc2","#7873c0","#4f7cba"],OrangeBlue7:["#9e3d22","#d45b21","#f69035","#d9d5c9","#77acd3","#4f81af","#2b5c8a"],RedGreen7:["#a3123a","#e33f43","#f8816b","#ced7c3","#73ba67","#44914e","#24693d"],GreenBlue7:["#24693d","#45934d","#75bc69","#c9dad2","#77a9cf","#4e7fab","#2a5783"],RedBlue7:["#a90c38","#e03b42","#f87f69","#dfd4d1","#7eaed3","#5383af","#2e5a87"],RedBlack7:["#ae123a","#e33e43","#f8816b","#d9d9d9","#a0a7a8","#707c83","#49525e"],GoldPurple7:["#ad9024","#c1a33b","#d4b95e","#e3d8cf","#d4a3c3","#c189b0","#ac7299"],RedGreenGold7:["#be2a3e","#e25f48","#f88f4d","#f4d166","#90b960","#4b9b5f","#22763f"],SunsetSunrise7:["#33608c","#9768a5","#e7718a","#f6ba57","#ed7846","#d54c45","#b81840"],OrangeBlueWhite7:["#9e3d22","#e36621","#fcad52","#ffffff","#95c5e1","#5b8fbc","#2b5c8a"],RedGreenWhite7:["#ae123a","#ee574d","#fdac9e","#ffffff","#91d183","#539e52","#24693d"],GreenBlueWhite7:["#24693d","#529c51","#8fd180","#ffffff","#95c1dd","#598ab5","#2a5783"],RedBlueWhite7:["#a90c38","#ec534b","#feaa9a","#ffffff","#9ac4e1","#5c8db8","#2e5a87"],RedBlackWhite7:["#ae123a","#ee574d","#fdac9d","#ffffff","#bdc0bf","#7d888d","#49525e"],OrangeBlueLight7:["#ffcc9e","#f9d4b6","#f0dccd","#e5e5e5","#dae1ea","#cfdcef","#c4d8f3"],Temperature7:["#529985","#6c9e6e","#99b059","#dbcf47","#ebc24b","#e3a14f","#c26b51"],BlueGreen7:["#feffd9","#f2fabf","#dff3b2","#c4eab1","#94d6b7","#69c5be","#41b7c4"],BlueLight7:["#e5e5e5","#e0e3e8","#dbe1ea","#d5dfec","#d0dcef","#cadaf1","#c4d8f3"],OrangeLight7:["#e5e5e5","#ebe1d9","#f0ddcd","#f5d9c2","#f9d4b6","#fdd0aa","#ffcc9e"],Blue20:["#b9ddf1","#afd6ed","#a5cfe9","#9bc7e4","#92c0df","#89b8da","#80b0d5","#79aacf","#72a3c9","#6a9bc3","#6394be","#5b8cb8","#5485b2","#4e7fac","#4878a6","#437a9f","#3d6a98","#376491","#305d8a","#2a5783"],Orange20:["#ffc685","#fcbe75","#f9b665","#f7ae54","#f5a645","#f59c3c","#f49234","#f2882d","#f07e27","#ee7422","#e96b20","#e36420","#db5e20","#d25921","#ca5422","#c14f22","#b84b23","#af4623","#a64122","#9e3d22"],Green20:["#b3e0a6","#a5db96","#98d687","#8ed07f","#85ca77","#7dc370","#75bc69","#6eb663","#67af5c","#61a956","#59a253","#519c51","#49964f","#428f4d","#398949","#308344","#2b7c40","#27763d","#256f3d","#24693d"],Red20:["#ffbeb2","#feb4a6","#fdab9b","#fca290","#fb9984","#fa8f79","#f9856e","#f77b66","#f5715d","#f36754","#f05c4d","#ec5049","#e74545","#e13b42","#da323f","#d3293d","#ca223c","#c11a3b","#b8163a","#ae123a"],Purple20:["#eec9e5","#eac1df","#e6b9d9","#e0b2d2","#daabcb","#d5a4c4","#cf9dbe","#ca96b8","#c48fb2","#be89ac","#b882a6","#b27ba1","#aa759d","#a27099","#9a6a96","#926591","#8c5f86","#865986","#81537f","#7c4d79"],Brown20:["#eedbbd","#ecd2ad","#ebc994","#eac085","#e8b777","#e5ae6c","#e2a562","#de9d5a","#d99455","#d38c54","#ce8451","#c9784d","#c47247","#c16941","#bd6036","#b85636","#b34d34","#ad4433","#a63d32","#9f3632"],Gray20:["#d5d5d5","#cdcecd","#c5c7c6","#bcbfbe","#b4b7b7","#acb0b1","#a4a9ab","#9ca3a4","#939c9e","#8b9598","#848e93","#7c878d","#758087","#6e7a81","#67737c","#616c77","#5b6570","#555f6a","#4f5864","#49525e"],GrayWarm20:["#dcd4d0","#d4ccc8","#cdc4c0","#c5bdb9","#beb6b2","#b7afab","#b0a7a4","#a9a09d","#a29996","#9b938f","#948c88","#8d8481","#867e7b","#807774","#79706e","#736967","#6c6260","#665c51","#5f5654","#59504e"],BlueTeal20:["#bce4d8","#aedcd5","#a1d5d2","#95cecf","#89c8cc","#7ec1ca","#72bac6","#66b2c2","#59acbe","#4ba5ba","#419eb6","#3b96b2","#358ead","#3586a7","#347ea1","#32779b","#316f96","#2f6790","#2d608a","#2c5985"],OrangeGold20:["#f4d166","#f6c760","#f8bc58","#f8b252","#f7a84a","#f69e41","#f49538","#f38b2f","#f28026","#f0751e","#eb6c1c","#e4641e","#de5d1f","#d75521","#cf4f22","#c64a22","#bc4623","#b24223","#a83e24","#9e3a26"],GreenGold20:["#f4d166","#e3cd62","#d3c95f","#c3c55d","#b2c25b","#a3bd5a","#93b958","#84b457","#76af56","#67a956","#5aa355","#4f9e53","#479751","#40914f","#3a8a4d","#34844a","#2d7d45","#257740","#1c713b","#146c36"],RedGold21:["#f4d166","#f5c75f","#f6bc58","#f7b254","#f9a750","#fa9d4f","#fa9d4f","#fb934d","#f7894b","#f47f4a","#f0774a","#eb6349","#e66549","#e15c48","#dc5447","#d64c45","#d04344","#ca3a42","#c43141","#bd273f","#b71d3e"],Classic10:["#1f77b4","#ff7f0e","#2ca02c","#d62728","#9467bd","#8c564b","#e377c2","#7f7f7f","#bcbd22","#17becf"],ClassicMedium10:["#729ece","#ff9e4a","#67bf5c","#ed665d","#ad8bc9","#a8786e","#ed97ca","#a2a2a2","#cdcc5d","#6dccda"],ClassicLight10:["#aec7e8","#ffbb78","#98df8a","#ff9896","#c5b0d5","#c49c94","#f7b6d2","#c7c7c7","#dbdb8d","#9edae5"],Classic20:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"],ClassicGray5:["#60636a","#a5acaf","#414451","#8f8782","#cfcfcf"],ClassicColorBlind10:["#006ba4","#ff800e","#ababab","#595959","#5f9ed1","#c85200","#898989","#a2c8ec","#ffbc79","#cfcfcf"],ClassicTrafficLight9:["#b10318","#dba13a","#309343","#d82526","#ffc156","#69b764","#f26c64","#ffdd71","#9fcd99"],ClassicPurpleGray6:["#7b66d2","#dc5fbd","#94917b","#995688","#d098ee","#d7d5c5"],ClassicPurpleGray12:["#7b66d2","#a699e8","#dc5fbd","#ffc0da","#5f5a41","#b4b19b","#995688","#d898ba","#ab6ad5","#d098ee","#8b7c6e","#dbd4c5"],ClassicGreenOrange6:["#32a251","#ff7f0f","#3cb7cc","#ffd94a","#39737c","#b85a0d"],ClassicGreenOrange12:["#32a251","#acd98d","#ff7f0f","#ffb977","#3cb7cc","#98d9e4","#b85a0d","#ffd94a","#39737c","#86b4a9","#82853b","#ccc94d"],ClassicBlueRed6:["#2c69b0","#f02720","#ac613c","#6ba3d6","#ea6b73","#e9c39b"],ClassicBlueRed12:["#2c69b0","#b5c8e2","#f02720","#ffb6b0","#ac613c","#e9c39b","#6ba3d6","#b5dffd","#ac8763","#ddc9b4","#bd0a36","#f4737a"],ClassicCyclic13:["#1f83b4","#12a2a8","#2ca030","#78a641","#bcbd22","#ffbf50","#ffaa0e","#ff7f0e","#d63a3a","#c7519c","#ba43b4","#8a60b0","#6f63bb"],ClassicGreen7:["#bccfb4","#94bb83","#69a761","#339444","#27823b","#1a7232","#09622a"],ClassicGray13:["#c3c3c3","#b2b2b2","#a2a2a2","#929292","#838383","#747474","#666666","#585858","#4b4b4b","#3f3f3f","#333333","#282828","#1e1e1e"],ClassicBlue7:["#b4d4da","#7bc8e2","#67add4","#3a87b7","#1c73b1","#1c5998","#26456e"],ClassicRed9:["#eac0bd","#f89a90","#f57667","#e35745","#d8392c","#cf1719","#c21417","#b10c1d","#9c0824"],ClassicOrange7:["#f0c294","#fdab67","#fd8938","#f06511","#d74401","#a33202","#7b3014"],ClassicAreaRed11:["#f5cac7","#fbb3ab","#fd9c8f","#fe8b7a","#fd7864","#f46b55","#ea5e45","#e04e35","#d43e25","#c92b14","#bd1100"],ClassicAreaGreen11:["#dbe8b4","#c3e394","#acdc7a","#9ad26d","#8ac765","#7abc5f","#6cae59","#60a24d","#569735","#4a8c1c","#3c8200"],ClassicAreaBrown11:["#f3e0c2","#f6d29c","#f7c577","#f0b763","#e4aa63","#d89c63","#cc8f63","#c08262","#bb7359","#bb6348","#bb5137"],ClassicRedGreen11:["#9c0824","#bd1316","#d11719","#df513f","#fc8375","#cacaca","#a2c18f","#69a761","#2f8e41","#1e7735","#09622a"],ClassicRedBlue11:["#9c0824","#bd1316","#d11719","#df513f","#fc8375","#cacaca","#67add4","#3a87b7","#1c73b1","#1c5998","#26456e"],ClassicRedBlack11:["#9c0824","#bd1316","#d11719","#df513f","#fc8375","#cacaca","#9b9b9b","#777777","#565656","#383838","#1e1e1e"],ClassicAreaRedGreen21:["#bd1100","#c82912","#d23a21","#dc4930","#e6583e","#ef654d","#f7705b","#fd7e6b","#fe8e7e","#fca294","#e9dabe","#c7e298","#b1de7f","#a0d571","#90cb68","#82c162","#75b65d","#69aa56","#5ea049","#559633","#4a8c1c"],ClassicOrangeBlue13:["#7b3014","#a33202","#d74401","#f06511","#fd8938","#fdab67","#cacaca","#7bc8e2","#67add4","#3a87b7","#1c73b1","#1c5998","#26456e"],ClassicGreenBlue11:["#09622a","#1e7735","#2f8e41","#69a761","#a2c18f","#cacaca","#67add4","#3a87b7","#1c73b1","#1c5998","#26456e"],ClassicRedWhiteGreen11:["#9c0824","#b41f27","#cc312b","#e86753","#fcb4a5","#ffffff","#b9d7b7","#74af72","#428f49","#297839","#09622a"],ClassicRedWhiteBlack11:["#9c0824","#b41f27","#cc312b","#e86753","#fcb4a5","#ffffff","#bfbfbf","#838383","#575757","#393939","#1e1e1e"],ClassicOrangeWhiteBlue11:["#7b3014","#a84415","#d85a13","#fb8547","#ffc2a1","#ffffff","#b7cde2","#6a9ec5","#3679a8","#2e5f8a","#26456e"],ClassicRedWhiteBlackLight10:["#ffc2c5","#ffd1d3","#ffe0e1","#fff0f0","#ffffff","#f3f3f3","#e8e8e8","#dddddd","#d1d1d1","#c6c6c6"],ClassicOrangeWhiteBlueLight11:["#ffcc9e","#ffd6b1","#ffe0c5","#ffead8","#fff5eb","#ffffff","#f3f7fd","#e8effa","#dce8f8","#d0e0f6","#c4d8f3"],ClassicRedWhiteGreenLight11:["#ffb2b6","#ffc2c5","#ffd1d3","#ffe0e1","#fff0f0","#ffffff","#f1faed","#e3f5db","#d5f0ca","#c6ebb8","#b7e6a7"],ClassicRedGreenLight11:["#ffb2b6","#fcbdc0","#f8c7c9","#f2d1d2","#ecdbdc","#e5e5e5","#dde6d9","#d4e6cc","#cae6c0","#c1e6b4","#b7e6a7"]}},n=f.helpers,c=2===f.DatasetController.prototype.removeHoverStyle.length;f.defaults.global.plugins.colorschemes={scheme:"brewer.Paired12",fillAlpha:.5,reverse:!1};var e={id:"colorschemes",beforeUpdate:function(d,c){var a,b,r,l,f=c.scheme.split("."),e=o[f[0]];return e&&(a=e[f[1]],b=a.length,a&&d.config.data.datasets.forEach(function(f,e){switch(r=e%b,l=a[c.reverse?b-r-1:r],f.colorschemes={},f.type||d.config.type){case"line":case"radar":case"scatter":void 0===f.backgroundColor&&(f.backgroundColor=n.color(l).alpha(c.fillAlpha).rgbString(),f.colorschemes.backgroundColor=!0),void 0===f.borderColor&&(f.borderColor=l,f.colorschemes.borderColor=!0),void 0===f.pointBackgroundColor&&(f.pointBackgroundColor=n.color(l).alpha(c.fillAlpha).rgbString(),f.colorschemes.pointBackgroundColor=!0),void 0===f.pointBorderColor&&(f.pointBorderColor=l,f.colorschemes.pointBorderColor=!0);break;case"doughnut":case"pie":void 0===f.backgroundColor&&(f.backgroundColor=f.data.map(function(f,e){return r=e%b,a[c.reverse?b-r-1:r]}),f.colorschemes.backgroundColor=!0);break;default:void 0===f.backgroundColor&&(f.backgroundColor=l,f.colorschemes.backgroundColor=!0)}})),!0},afterUpdate:function(f){f.config.data.datasets.forEach(function(f){f.colorschemes&&(f.colorschemes.backgroundColor&&delete f.backgroundColor,f.colorschemes.borderColor&&delete f.borderColor,f.colorschemes.pointBackgroundColor&&delete f.pointBackgroundColor,f.colorschemes.pointBorderColor&&delete f.pointBorderColor,delete f.colorschemes)})},beforeEvent:function(f,e,d){return c&&this.beforeUpdate(f,d),!0},afterEvent:function(f){c&&this.afterUpdate(f)}};return f.colorschemes=o,f.plugins.register(e),e});rt-5.0.1/share/static/js/jquery-3.4.1.min.js000644 000765 000024 00000254121 14005011336 021113 0ustar00sunnavystaff000000 000000 /*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0]+>/g,"")),s&&(a=w(a)),a=a.toUpperCase(),o="contains"===i?0<=a.indexOf(t):a.startsWith(t)))break}return o}function A(e){return parseInt(e,10)||0}z.fn.triggerNative=function(e){var t,i=this[0];i.dispatchEvent?(u?t=new Event(e,{bubbles:!0}):(t=document.createEvent("Event")).initEvent(e,!0,!1),i.dispatchEvent(t)):i.fireEvent?((t=document.createEventObject()).eventType=e,i.fireEvent("on"+e,t)):this.trigger(e)};var f={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g","\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i","\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O","\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S","\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w","\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe","\u0149":"'n","\u017f":"s"},m=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,g=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\u1ab0-\\u1aff\\u1dc0-\\u1dff]","g");function b(e){return f[e]}function w(e){return(e=e.toString())&&e.replace(m,b).replace(g,"")}var I,x,$,y,S,E=(I={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},x=function(e){return I[e]},$="(?:"+Object.keys(I).join("|")+")",y=RegExp($),S=RegExp($,"g"),function(e){return e=null==e?"":""+e,y.test(e)?e.replace(S,x):e}),C={32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",65:"A",66:"B",67:"C",68:"D",69:"E",70:"F",71:"G",72:"H",73:"I",74:"J",75:"K",76:"L",77:"M",78:"N",79:"O",80:"P",81:"Q",82:"R",83:"S",84:"T",85:"U",86:"V",87:"W",88:"X",89:"Y",90:"Z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"},L=27,N=13,D=32,H=9,B=38,W=40,M={success:!1,major:"3"};try{M.full=(z.fn.dropdown.Constructor.VERSION||"").split(" ")[0].split("."),M.major=M.full[0],M.success=!0}catch(e){}var R=0,U=".bs.select",j={DISABLED:"disabled",DIVIDER:"divider",SHOW:"open",DROPUP:"dropup",MENU:"dropdown-menu",MENURIGHT:"dropdown-menu-right",MENULEFT:"dropdown-menu-left",BUTTONCLASS:"btn-default",POPOVERHEADER:"popover-title",ICONBASE:"glyphicon",TICKICON:"glyphicon-ok"},V={MENU:"."+j.MENU},F={span:document.createElement("span"),i:document.createElement("i"),subtext:document.createElement("small"),a:document.createElement("a"),li:document.createElement("li"),whitespace:document.createTextNode("\xa0"),fragment:document.createDocumentFragment()};F.a.setAttribute("role","option"),F.subtext.className="text-muted",F.text=F.span.cloneNode(!1),F.text.className="text",F.checkMark=F.span.cloneNode(!1);var _=new RegExp(B+"|"+W),G=new RegExp("^"+H+"$|"+L),q=function(e,t,i){var s=F.li.cloneNode(!1);return e&&(1===e.nodeType||11===e.nodeType?s.appendChild(e):s.innerHTML=e),void 0!==t&&""!==t&&(s.className=t),null!=i&&s.classList.add("optgroup-"+i),s},K=function(e,t,i){var s=F.a.cloneNode(!0);return e&&(11===e.nodeType?s.appendChild(e):s.insertAdjacentHTML("beforeend",e)),void 0!==t&&""!==t&&(s.className=t),"4"===M.major&&s.classList.add("dropdown-item"),i&&s.setAttribute("style",i),s},Y=function(e,t){var i,s,n=F.text.cloneNode(!1);if(e.content)n.innerHTML=e.content;else{if(n.textContent=e.text,e.icon){var o=F.whitespace.cloneNode(!1);(s=(!0===t?F.i:F.span).cloneNode(!1)).className=e.iconBase+" "+e.icon,F.fragment.appendChild(s),F.fragment.appendChild(o)}e.subtext&&((i=F.subtext.cloneNode(!1)).textContent=e.subtext,n.appendChild(i))}if(!0===t)for(;0
'},maxOptions:!1,mobile:!1,selectOnTab:!1,dropdownAlignRight:!1,windowPadding:0,virtualScroll:600,display:!1,sanitize:!0,sanitizeFn:null,whiteList:e},J.prototype={constructor:J,init:function(){var i=this,e=this.$element.attr("id");R++,this.selectId="bs-select-"+R,this.$element[0].classList.add("bs-select-hidden"),this.multiple=this.$element.prop("multiple"),this.autofocus=this.$element.prop("autofocus"),this.$element[0].classList.contains("show-tick")&&(this.options.showTick=!0),this.$newElement=this.createDropdown(),this.$element.after(this.$newElement).prependTo(this.$newElement),this.$button=this.$newElement.children("button"),this.$menu=this.$newElement.children(V.MENU),this.$menuInner=this.$menu.children(".inner"),this.$searchbox=this.$menu.find("input"),this.$element[0].classList.remove("bs-select-hidden"),!0===this.options.dropdownAlignRight&&this.$menu[0].classList.add(j.MENURIGHT),void 0!==e&&this.$button.attr("data-id",e),this.checkDisabled(),this.clickListener(),this.options.liveSearch?(this.liveSearchListener(),this.focusedParent=this.$searchbox[0]):this.focusedParent=this.$menuInner[0],this.setStyle(),this.render(),this.setWidth(),this.options.container?this.selectPosition():this.$element.on("hide"+U,function(){if(i.isVirtual()){var e=i.$menuInner[0],t=e.firstChild.cloneNode(!1);e.replaceChild(t,e.firstChild),e.scrollTop=0}}),this.$menu.data("this",this),this.$newElement.data("this",this),this.options.mobile&&this.mobile(),this.$newElement.on({"hide.bs.dropdown":function(e){i.$element.trigger("hide"+U,e)},"hidden.bs.dropdown":function(e){i.$element.trigger("hidden"+U,e)},"show.bs.dropdown":function(e){i.$element.trigger("show"+U,e)},"shown.bs.dropdown":function(e){i.$element.trigger("shown"+U,e)}}),i.$element[0].hasAttribute("required")&&this.$element.on("invalid"+U,function(){i.$button[0].classList.add("bs-invalid"),i.$element.on("shown"+U+".invalid",function(){i.$element.val(i.$element.val()).off("shown"+U+".invalid")}).on("rendered"+U,function(){this.validity.valid&&i.$button[0].classList.remove("bs-invalid"),i.$element.off("rendered"+U)}),i.$button.on("blur"+U,function(){i.$element.trigger("focus").trigger("blur"),i.$button.off("blur"+U)})}),setTimeout(function(){i.createLi(),i.$element.trigger("loaded"+U)})},createDropdown:function(){var e=this.multiple||this.options.showTick?" show-tick":"",t=this.multiple?' aria-multiselectable="true"':"",i="",s=this.autofocus?" autofocus":"";M.major<4&&this.$element.parent().hasClass("input-group")&&(i=" input-group-btn");var n,o="",r="",l="",a="";return this.options.header&&(o='
'+this.options.header+"
"),this.options.liveSearch&&(r=''),this.multiple&&this.options.actionsBox&&(l='
"),this.multiple&&this.options.doneButton&&(a='
"),n='",z(n)},setPositionData:function(){this.selectpicker.view.canHighlight=[];for(var e=this.selectpicker.view.size=0;e=this.options.virtualScroll||!0===this.options.virtualScroll},createView:function(A,e,t){var L,N,D=this,i=0,H=[];if(this.selectpicker.current=A?this.selectpicker.search:this.selectpicker.main,this.setPositionData(),e)if(t)i=this.$menuInner[0].scrollTop;else if(!D.multiple){var s=D.$element[0],n=(s.options[s.selectedIndex]||{}).liIndex;if("number"==typeof n&&!1!==D.options.size){var o=D.selectpicker.main.data[n],r=o&&o.position;r&&(i=r-(D.sizeInfo.menuInnerHeight+D.sizeInfo.liHeight)/2)}}function l(e,t){var i,s,n,o,r,l,a,c,d,h,p=D.selectpicker.current.elements.length,u=[],f=!0,m=D.isVirtual();D.selectpicker.view.scrollTop=e,!0===m&&D.sizeInfo.hasScrollBar&&D.$menu[0].offsetWidth>D.sizeInfo.totalMenuWidth&&(D.sizeInfo.menuWidth=D.$menu[0].offsetWidth,D.sizeInfo.totalMenuWidth=D.sizeInfo.menuWidth+D.sizeInfo.scrollBarWidth,D.$menu.css("min-width",D.sizeInfo.menuWidth)),i=Math.ceil(D.sizeInfo.menuInnerHeight/D.sizeInfo.liHeight*1.5),s=Math.round(p/i)||1;for(var v=0;vp-1?0:D.selectpicker.current.data[p-1].position-D.selectpicker.current.data[D.selectpicker.view.position1-1].position,I.firstChild.style.marginTop=b+"px",w+"px"):I.firstChild.style.marginTop=0,I.firstChild.appendChild(x)}if(D.prevActiveIndex=D.activeIndex,D.options.liveSearch){if(A&&t){var z,T=0;D.selectpicker.view.canHighlight[T]||(T=1+D.selectpicker.view.canHighlight.slice(1).indexOf(!0)),z=D.selectpicker.view.visibleElements[T],D.defocusItem(D.selectpicker.view.currentActive),D.activeIndex=(D.selectpicker.current.data[T]||{}).index,D.focusItem(z)}}else D.$menuInner.trigger("focus")}l(i,!0),this.$menuInner.off("scroll.createView").on("scroll.createView",function(e,t){D.noScroll||l(this.scrollTop,t),D.noScroll=!1}),z(window).off("resize"+U+"."+this.selectId+".createView").on("resize"+U+"."+this.selectId+".createView",function(){D.$newElement.hasClass(j.SHOW)&&l(D.$menuInner[0].scrollTop)})},focusItem:function(e,t,i){if(e){t=t||this.selectpicker.main.data[this.activeIndex];var s=e.firstChild;s&&(s.setAttribute("aria-setsize",this.selectpicker.view.size),s.setAttribute("aria-posinset",t.posinset),!0!==i&&(this.focusedParent.setAttribute("aria-activedescendant",s.id),e.classList.add("active"),s.classList.add("active")))}},defocusItem:function(e){e&&(e.classList.remove("active"),e.firstChild&&e.firstChild.classList.remove("active"))},setPlaceholder:function(){var e=!1;if(this.options.title&&!this.multiple){this.selectpicker.view.titleOption||(this.selectpicker.view.titleOption=document.createElement("option")),e=!0;var t=this.$element[0],i=!1,s=!this.selectpicker.view.titleOption.parentNode;if(s)this.selectpicker.view.titleOption.className="bs-title-option",this.selectpicker.view.titleOption.value="",i=void 0===z(t.options[t.selectedIndex]).attr("selected")&&void 0===this.$element.data("selected");(s||0!==this.selectpicker.view.titleOption.index)&&t.insertBefore(this.selectpicker.view.titleOption,t.firstChild),i&&(t.selectedIndex=0)}return e},createLi:function(){var c=this,f=this.options.iconBase,m=':not([hidden]):not([data-hidden="true"])',v=[],g=[],d=0,b=0,e=this.setPlaceholder()?1:0;this.options.hideDisabled&&(m+=":not(:disabled)"),!c.options.showTick&&!c.multiple||F.checkMark.parentNode||(F.checkMark.className=f+" "+c.options.tickIcon+" check-mark",F.a.appendChild(F.checkMark));var t=this.$element[0].querySelectorAll("select > *"+m);function w(e){var t=g[g.length-1];t&&"divider"===t.type&&(t.optID||e.optID)||((e=e||{}).type="divider",v.push(q(!1,j.DIVIDER,e.optID?e.optID+"div":void 0)),g.push(e))}function I(e,t){if((t=t||{}).divider="true"===e.getAttribute("data-divider"),t.divider)w({optID:t.optID});else{var i=g.length,s=e.style.cssText,n=s?E(s):"",o=(e.className||"")+(t.optgroupClass||"");t.optID&&(o="opt "+o),t.text=e.textContent,t.content=e.getAttribute("data-content"),t.tokens=e.getAttribute("data-tokens"),t.subtext=e.getAttribute("data-subtext"),t.icon=e.getAttribute("data-icon"),t.iconBase=f;var r=Y(t),l=q(K(r,o,n),"",t.optID);l.firstChild&&(l.firstChild.id=c.selectId+"-"+i),v.push(l),e.liIndex=i,t.display=t.content||t.text,t.type="option",t.index=i,t.option=e,t.disabled=t.disabled||e.disabled,g.push(t);var a=0;t.display&&(a+=t.display.length),t.subtext&&(a+=t.subtext.length),t.icon&&(a+=1),d li")},render:function(){this.setPlaceholder();var e,t,i=this,s=this.$element[0],n=function(e,t){var i,s=e.selectedOptions,n=[];if(t){for(var o=0,r=s.length;o")).length&&o>t[1]||1===t.length&&2<=o),!1===e){for(var h=0;h option"+m+", optgroup"+m+" option"+m).length,g="function"==typeof this.options.countSelectedText?this.options.countSelectedText(o,v):this.options.countSelectedText;c=Y({text:g.replace("{0}",o.toString()).replace("{1}",v.toString())},!0)}if(null==this.options.title&&(this.options.title=this.$element.attr("title")),c.childNodes.length||(c=Y({text:void 0!==this.options.title?this.options.title:this.options.noneSelectedText},!0)),r.title=c.textContent.replace(/<[^>]*>?/g,"").trim(),this.options.sanitize&&d&&P([c],i.options.whiteList,i.options.sanitizeFn),l.innerHTML="",l.appendChild(c),M.major<4&&this.$newElement[0].classList.contains("bs3-has-addon")){var b=r.querySelector(".filter-expand"),w=l.cloneNode(!0);w.className="filter-expand",b?r.replaceChild(w,b):r.appendChild(w)}this.$element.trigger("rendered"+U)},setStyle:function(e,t){var i,s=this.$button[0],n=this.$newElement[0],o=this.options.style.trim();this.$element.attr("class")&&this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi,"")),M.major<4&&(n.classList.add("bs3"),n.parentNode.classList.contains("input-group")&&(n.previousElementSibling||n.nextElementSibling)&&(n.previousElementSibling||n.nextElementSibling).classList.contains("input-group-addon")&&n.classList.add("bs3-has-addon")),i=e?e.trim():o,"add"==t?i&&s.classList.add.apply(s.classList,i.split(" ")):"remove"==t?i&&s.classList.remove.apply(s.classList,i.split(" ")):(o&&s.classList.remove.apply(s.classList,o.split(" ")),i&&s.classList.add.apply(s.classList,i.split(" ")))},liHeight:function(e){if(e||!1!==this.options.size&&!this.sizeInfo){this.sizeInfo||(this.sizeInfo={});var t=document.createElement("div"),i=document.createElement("div"),s=document.createElement("div"),n=document.createElement("ul"),o=document.createElement("li"),r=document.createElement("li"),l=document.createElement("li"),a=document.createElement("a"),c=document.createElement("span"),d=this.options.header&&0this.sizeInfo.menuExtras.vert&&l+this.sizeInfo.menuExtras.vert+50>this.sizeInfo.selectOffsetBot)),"auto"===this.options.size)n=3this.options.size){for(var g=0;gthis.sizeInfo.selectOffsetRight&&this.sizeInfo.selectOffsetRightthis.sizeInfo.menuInnerHeight&&(this.sizeInfo.hasScrollBar=!0,this.sizeInfo.totalMenuWidth=this.sizeInfo.menuWidth+this.sizeInfo.scrollBarWidth,this.$menu.css("min-width",this.sizeInfo.totalMenuWidth)),this.dropdown&&this.dropdown._popper&&this.dropdown._popper.update()},setSize:function(e){if(this.liHeight(e),this.options.header&&this.$menu.css("padding-top",0),!1!==this.options.size){var t=this,i=z(window);this.setMenuSize(),this.options.liveSearch&&this.$searchbox.off("input.setMenuSize propertychange.setMenuSize").on("input.setMenuSize propertychange.setMenuSize",function(){return t.setMenuSize()}),"auto"===this.options.size?i.off("resize"+U+"."+this.selectId+".setMenuSize scroll"+U+"."+this.selectId+".setMenuSize").on("resize"+U+"."+this.selectId+".setMenuSize scroll"+U+"."+this.selectId+".setMenuSize",function(){return t.setMenuSize()}):this.options.size&&"auto"!=this.options.size&&this.selectpicker.current.elements.length>this.options.size&&i.off("resize"+U+"."+this.selectId+".setMenuSize scroll"+U+"."+this.selectId+".setMenuSize"),t.createView(!1,!0,e)}},setWidth:function(){var i=this;"auto"===this.options.width?requestAnimationFrame(function(){i.$menu.css("min-width","0"),i.$element.on("loaded"+U,function(){i.liHeight(),i.setMenuSize();var e=i.$newElement.clone().appendTo("body"),t=e.css("width","auto").children("button").outerWidth();e.remove(),i.sizeInfo.selectWidth=Math.max(i.sizeInfo.totalMenuWidth,t),i.$newElement.css("width",i.sizeInfo.selectWidth+"px")})}):"fit"===this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width","").addClass("fit-width")):this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width",this.options.width)):(this.$menu.css("min-width",""),this.$newElement.css("width","")),this.$newElement.hasClass("fit-width")&&"fit"!==this.options.width&&this.$newElement[0].classList.remove("fit-width")},selectPosition:function(){this.$bsContainer=z('
');var s,n,o,r=this,l=z(this.options.container),e=function(e){var t={},i=r.options.display||!!z.fn.dropdown.Constructor.Default&&z.fn.dropdown.Constructor.Default.display;r.$bsContainer.addClass(e.attr("class").replace(/form-control|fit-width/gi,"")).toggleClass(j.DROPUP,e.hasClass(j.DROPUP)),s=e.offset(),l.is("body")?n={top:0,left:0}:((n=l.offset()).top+=parseInt(l.css("borderTopWidth"))-l.scrollTop(),n.left+=parseInt(l.css("borderLeftWidth"))-l.scrollLeft()),o=e.hasClass(j.DROPUP)?0:e[0].offsetHeight,(M.major<4||"static"===i)&&(t.top=s.top-n.top+o,t.left=s.left-n.left),t.width=e[0].offsetWidth,r.$bsContainer.css(t)};this.$button.on("click.bs.dropdown.data-api",function(){r.isDisabled()||(e(r.$newElement),r.$bsContainer.appendTo(r.options.container).toggleClass(j.SHOW,!r.$button.hasClass(j.SHOW)).append(r.$menu))}),z(window).off("resize"+U+"."+this.selectId+" scroll"+U+"."+this.selectId).on("resize"+U+"."+this.selectId+" scroll"+U+"."+this.selectId,function(){r.$newElement.hasClass(j.SHOW)&&e(r.$newElement)}),this.$element.on("hide"+U,function(){r.$menu.data("height",r.$menu.height()),r.$bsContainer.detach()})},setOptionStatus:function(e){var t=this;if(t.noScroll=!1,t.selectpicker.view.visibleElements&&t.selectpicker.view.visibleElements.length)for(var i=0;i
');$[2]&&(y=y.replace("{var}",$[2][1"+y+"
")),d=!1,C.$element.trigger("maxReached"+U)),b&&I&&(E.append(z("
"+S+"
")),d=!1,C.$element.trigger("maxReachedGrp"+U)),setTimeout(function(){C.setSelected(r,!1)},10),E.delay(750).fadeOut(300,function(){z(this).remove()})}}}else c.selected=!1,p.selected=!0,C.setSelected(r,!0);!C.multiple||C.multiple&&1===C.options.maxOptions?C.$button.trigger("focus"):C.options.liveSearch&&C.$searchbox.trigger("focus"),d&&(C.multiple||a!==s.selectedIndex)&&(T=[p.index,u.prop("selected"),l],C.$element.triggerNative("change"))}}),this.$menu.on("click","li."+j.DISABLED+" a, ."+j.POPOVERHEADER+", ."+j.POPOVERHEADER+" :not(.close)",function(e){e.currentTarget==this&&(e.preventDefault(),e.stopPropagation(),C.options.liveSearch&&!z(e.target).hasClass("close")?C.$searchbox.trigger("focus"):C.$button.trigger("focus"))}),this.$menuInner.on("click",".divider, .dropdown-header",function(e){e.preventDefault(),e.stopPropagation(),C.options.liveSearch?C.$searchbox.trigger("focus"):C.$button.trigger("focus")}),this.$menu.on("click","."+j.POPOVERHEADER+" .close",function(){C.$button.trigger("click")}),this.$searchbox.on("click",function(e){e.stopPropagation()}),this.$menu.on("click",".actions-btn",function(e){C.options.liveSearch?C.$searchbox.trigger("focus"):C.$button.trigger("focus"),e.preventDefault(),e.stopPropagation(),z(this).hasClass("bs-select-all")?C.selectAll():C.deselectAll()}),this.$element.on("change"+U,function(){C.render(),C.$element.trigger("changed"+U,T),T=null}).on("focus"+U,function(){C.options.mobile||C.$button.trigger("focus")})},liveSearchListener:function(){var u=this,f=document.createElement("li");this.$button.on("click.bs.dropdown.data-api",function(){u.$searchbox.val()&&u.$searchbox.val("")}),this.$searchbox.on("click.bs.dropdown.data-api focus.bs.dropdown.data-api touchend.bs.dropdown.data-api",function(e){e.stopPropagation()}),this.$searchbox.on("input propertychange",function(){var e=u.$searchbox.val();if(u.selectpicker.search.elements=[],u.selectpicker.search.data=[],e){var t=[],i=e.toUpperCase(),s={},n=[],o=u._searchStyle(),r=u.options.liveSearchNormalize;r&&(i=w(i)),u._$lisSelected=u.$menuInner.find(".selected");for(var l=0;l=a.selectpicker.view.canHighlight.length&&(t=0),a.selectpicker.view.canHighlight[t+f]||(t=t+1+a.selectpicker.view.canHighlight.slice(t+f+1).indexOf(!0))),e.preventDefault();var m=f+t;e.which===B?0===f&&t===c.length-1?(a.$menuInner[0].scrollTop=a.$menuInner[0].scrollHeight,m=a.selectpicker.current.elements.length-1):d=(o=(n=a.selectpicker.current.data[m]).position-n.height)u+a.sizeInfo.menuInnerHeight),s=a.selectpicker.main.elements[v],a.activeIndex=b[x],a.focusItem(s),s&&s.firstChild.focus(),d&&(a.$menuInner[0].scrollTop=o),r.trigger("focus")}}i&&(e.which===D&&!a.selectpicker.keydown.keyHistory||e.which===N||e.which===H&&a.options.selectOnTab)&&(e.which!==D&&e.preventDefault(),a.options.liveSearch&&e.which===D||(a.$menuInner.find(".active a").trigger("click",!0),r.trigger("focus"),a.options.liveSearch||(e.preventDefault(),z(document).data("spaceSelect",!0))))}},mobile:function(){this.$element[0].classList.add("mobile-device")},refresh:function(){var e=z.extend({},this.options,this.$element.data());this.options=e,this.checkDisabled(),this.setStyle(),this.render(),this.createLi(),this.setWidth(),this.setSize(!0),this.$element.trigger("refreshed"+U)},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()},destroy:function(){this.$newElement.before(this.$element).remove(),this.$bsContainer?this.$bsContainer.remove():this.$menu.remove(),this.$element.off(U).removeData("selectpicker").removeClass("bs-select-hidden selectpicker"),z(window).off(U+"."+this.selectId)}};var X=z.fn.selectpicker;z.fn.selectpicker=Q,z.fn.selectpicker.Constructor=J,z.fn.selectpicker.noConflict=function(){return z.fn.selectpicker=X,this},z(document).off("keydown.bs.dropdown.data-api").on("keydown"+U,'.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input',J.prototype.keydown).on("focusin.modal",'.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input',function(e){e.stopPropagation()}),z(window).on("load"+U+".data-api",function(){z(".selectpicker").each(function(){var e=z(this);Q.call(e,e.data())})})}(e)}); rt-5.0.1/share/static/js/late.js000644 000765 000024 00000002634 14005011336 017276 0ustar00sunnavystaff000000 000000 jQuery(function() { sync_grouped_custom_fields() } ); function sync_grouped_custom_fields() { var all_inputs = jQuery("input,textarea,select"); var parse_cf = /^Object-([\w:]+)-(\d*)-CustomField(?::\w+)?-(\d+)-(.*)$/; all_inputs.each(function() { var elem = jQuery(this); var parsed = parse_cf.exec(elem.attr("name")); if (parsed == null) return; if (/-Magic$/.test(parsed[4])) return; var name_filter_regex = new RegExp( "^Object-"+parsed[1]+"-"+parsed[2]+ "-CustomField(?::\\w+)?-"+parsed[3]+"-"+parsed[4]+"$" ); var update_elems = all_inputs.filter(function () { return name_filter_regex.test(jQuery(this).attr("name")); }).not(elem); if (update_elems.length == 0) return; var trigger_func = function() { var curval = elem.val(); if ((elem.attr("type") == "checkbox") || (elem.attr("type") == "radio")) { curval = [ ]; jQuery('[name="'+elem.attr("name")+'"]:checked').each( function() { curval.push( jQuery(this).val() ); }); } update_elems.val(curval); }; if ((elem.attr("type") == "text") || (elem.attr("tagName") == "TEXTAREA")) elem.keyup( trigger_func ); else elem.change( trigger_func ); }); } rt-5.0.1/share/static/js/history-folding.js000644 000765 000024 00000001541 14005011336 021466 0ustar00sunnavystaff000000 000000 function fold_message_stanza(e,showmsg, hidemsg) { var box = jQuery(e).next('.message-stanza'); if ( box.hasClass('closed') ) { jQuery([e, box[0]]).removeClass('closed').addClass('open'); jQuery(e).text( hidemsg); } else { jQuery([e, box[0]]).addClass('closed').removeClass('open'); jQuery(e).text( showmsg); } } function toggle_all_folds(e, showmsg, hidemsg) { var link = jQuery(e); var history = link.closest(".history"); var dir = link.attr('data-direction'); if (dir == 'open') { history.find(".message-stanza-folder.closed").click(); link.attr('data-direction', 'closed').text(hidemsg); } else if (dir == 'closed') { history.find(".message-stanza-folder.open").click(); link.attr('data-direction', 'open').text(showmsg); } return false; } rt-5.0.1/share/static/js/chosen.jquery.min.js000644 000765 000024 00000066541 14005011336 021737 0ustar00sunnavystaff000000 000000 /* Chosen v1.4.2 | (c) 2011-2015 by Harvest | MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md */ (function(){var a,AbstractChosen,Chosen,SelectParser,b,c={}.hasOwnProperty,d=function(a,b){function d(){this.constructor=a}for(var e in b)c.call(b,e)&&(a[e]=b[e]);return d.prototype=b.prototype,a.prototype=new d,a.__super__=b.prototype,a};SelectParser=function(){function SelectParser(){this.options_index=0,this.parsed=[]}return SelectParser.prototype.add_node=function(a){return"OPTGROUP"===a.nodeName.toUpperCase()?this.add_group(a):this.add_option(a)},SelectParser.prototype.add_group=function(a){var b,c,d,e,f,g;for(b=this.parsed.length,this.parsed.push({array_index:b,group:!0,label:this.escapeExpression(a.label),title:a.title?a.title:void 0,children:0,disabled:a.disabled,classes:a.className}),f=a.childNodes,g=[],d=0,e=f.length;e>d;d++)c=f[d],g.push(this.add_option(c,b,a.disabled));return g},SelectParser.prototype.add_option=function(a,b,c){return"OPTION"===a.nodeName.toUpperCase()?(""!==a.text?(null!=b&&(this.parsed[b].children+=1),this.parsed.push({array_index:this.parsed.length,options_index:this.options_index,value:a.value,text:a.text,html:a.innerHTML,title:a.title?a.title:void 0,selected:a.selected,disabled:c===!0?c:a.disabled,group_array_index:b,group_label:null!=b?this.parsed[b].label:null,classes:a.className,style:a.style.cssText})):this.parsed.push({array_index:this.parsed.length,options_index:this.options_index,empty:!0}),this.options_index+=1):void 0},SelectParser.prototype.escapeExpression=function(a){var b,c;return null==a||a===!1?"":/[\&\<\>\"\'\`]/.test(a)?(b={"<":"<",">":">",'"':""","'":"'","`":"`"},c=/&(?!\w+;)|[\<\>\"\'\`]/g,a.replace(c,function(a){return b[a]||"&"})):a},SelectParser}(),SelectParser.select_to_array=function(a){var b,c,d,e,f;for(c=new SelectParser,f=a.childNodes,d=0,e=f.length;e>d;d++)b=f[d],c.add_node(b);return c.parsed},AbstractChosen=function(){function AbstractChosen(a,b){this.form_field=a,this.options=null!=b?b:{},AbstractChosen.browser_is_supported()&&(this.is_multiple=this.form_field.multiple,this.set_default_text(),this.set_default_values(),this.setup(),this.set_up_html(),this.register_observers(),this.on_ready())}return AbstractChosen.prototype.set_default_values=function(){var a=this;return this.click_test_action=function(b){return a.test_active_click(b)},this.activate_action=function(b){return a.activate_field(b)},this.active_field=!1,this.mouse_on_container=!1,this.results_showing=!1,this.result_highlighted=null,this.allow_single_deselect=null!=this.options.allow_single_deselect&&null!=this.form_field.options[0]&&""===this.form_field.options[0].text?this.options.allow_single_deselect:!1,this.disable_search_threshold=this.options.disable_search_threshold||0,this.disable_search=this.options.disable_search||!1,this.enable_split_word_search=null!=this.options.enable_split_word_search?this.options.enable_split_word_search:!0,this.group_search=null!=this.options.group_search?this.options.group_search:!0,this.search_contains=this.options.search_contains||!1,this.single_backstroke_delete=null!=this.options.single_backstroke_delete?this.options.single_backstroke_delete:!0,this.max_selected_options=this.options.max_selected_options||1/0,this.inherit_select_classes=this.options.inherit_select_classes||!1,this.display_selected_options=null!=this.options.display_selected_options?this.options.display_selected_options:!0,this.display_disabled_options=null!=this.options.display_disabled_options?this.options.display_disabled_options:!0,this.include_group_label_in_selected=this.options.include_group_label_in_selected||!1},AbstractChosen.prototype.set_default_text=function(){return this.default_text=this.form_field.getAttribute("data-placeholder")?this.form_field.getAttribute("data-placeholder"):this.is_multiple?this.options.placeholder_text_multiple||this.options.placeholder_text||AbstractChosen.default_multiple_text:this.options.placeholder_text_single||this.options.placeholder_text||AbstractChosen.default_single_text,this.results_none_found=this.form_field.getAttribute("data-no_results_text")||this.options.no_results_text||AbstractChosen.default_no_result_text},AbstractChosen.prototype.choice_label=function(a){return this.include_group_label_in_selected&&null!=a.group_label?""+a.group_label+""+a.html:a.html},AbstractChosen.prototype.mouse_enter=function(){return this.mouse_on_container=!0},AbstractChosen.prototype.mouse_leave=function(){return this.mouse_on_container=!1},AbstractChosen.prototype.input_focus=function(){var a=this;if(this.is_multiple){if(!this.active_field)return setTimeout(function(){return a.container_mousedown()},50)}else if(!this.active_field)return this.activate_field()},AbstractChosen.prototype.input_blur=function(){var a=this;return this.mouse_on_container?void 0:(this.active_field=!1,setTimeout(function(){return a.blur_test()},100))},AbstractChosen.prototype.results_option_build=function(a){var b,c,d,e,f;for(b="",f=this.results_data,d=0,e=f.length;e>d;d++)c=f[d],b+=c.group?this.result_add_group(c):this.result_add_option(c),(null!=a?a.first:void 0)&&(c.selected&&this.is_multiple?this.choice_build(c):c.selected&&!this.is_multiple&&this.single_set_selected_text(this.choice_label(c)));return b},AbstractChosen.prototype.result_add_option=function(a){var b,c;return a.search_match?this.include_option_in_results(a)?(b=[],a.disabled||a.selected&&this.is_multiple||b.push("active-result"),!a.disabled||a.selected&&this.is_multiple||b.push("disabled-result"),a.selected&&b.push("result-selected"),null!=a.group_array_index&&b.push("group-option"),""!==a.classes&&b.push(a.classes),c=document.createElement("li"),c.className=b.join(" "),c.style.cssText=a.style,c.setAttribute("data-option-array-index",a.array_index),c.innerHTML=a.search_text,a.title&&(c.title=a.title),this.outerHTML(c)):"":""},AbstractChosen.prototype.result_add_group=function(a){var b,c;return a.search_match||a.group_match?a.active_options>0?(b=[],b.push("group-result"),a.classes&&b.push(a.classes),c=document.createElement("li"),c.className=b.join(" "),c.innerHTML=a.search_text,a.title&&(c.title=a.title),this.outerHTML(c)):"":""},AbstractChosen.prototype.results_update_field=function(){return this.set_default_text(),this.is_multiple||this.results_reset_cleanup(),this.result_clear_highlight(),this.results_build(),this.results_showing?this.winnow_results():void 0},AbstractChosen.prototype.reset_single_select_options=function(){var a,b,c,d,e;for(d=this.results_data,e=[],b=0,c=d.length;c>b;b++)a=d[b],a.selected?e.push(a.selected=!1):e.push(void 0);return e},AbstractChosen.prototype.results_toggle=function(){return this.results_showing?this.results_hide():this.results_show()},AbstractChosen.prototype.results_search=function(){return this.results_showing?this.winnow_results():this.results_show()},AbstractChosen.prototype.winnow_results=function(){var a,b,c,d,e,f,g,h,i,j,k,l;for(this.no_results_clear(),d=0,f=this.get_search_text(),a=f.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),i=new RegExp(a,"i"),c=this.get_search_regex(a),l=this.results_data,j=0,k=l.length;k>j;j++)b=l[j],b.search_match=!1,e=null,this.include_option_in_results(b)&&(b.group&&(b.group_match=!1,b.active_options=0),null!=b.group_array_index&&this.results_data[b.group_array_index]&&(e=this.results_data[b.group_array_index],0===e.active_options&&e.search_match&&(d+=1),e.active_options+=1),b.search_text=b.group?b.label:b.html,(!b.group||this.group_search)&&(b.search_match=this.search_string_match(b.search_text,c),b.search_match&&!b.group&&(d+=1),b.search_match?(f.length&&(g=b.search_text.search(i),h=b.search_text.substr(0,g+f.length)+""+b.search_text.substr(g+f.length),b.search_text=h.substr(0,g)+""+h.substr(g)),null!=e&&(e.group_match=!0)):null!=b.group_array_index&&this.results_data[b.group_array_index].search_match&&(b.search_match=!0)));return this.result_clear_highlight(),1>d&&f.length?(this.update_results_content(""),this.no_results(f)):(this.update_results_content(this.results_option_build()),this.winnow_results_set_highlight())},AbstractChosen.prototype.get_search_regex=function(a){var b;return b=this.search_contains?"":"^",new RegExp(b+a,"i")},AbstractChosen.prototype.search_string_match=function(a,b){var c,d,e,f;if(b.test(a))return!0;if(this.enable_split_word_search&&(a.indexOf(" ")>=0||0===a.indexOf("["))&&(d=a.replace(/\[|\]/g,"").split(" "),d.length))for(e=0,f=d.length;f>e;e++)if(c=d[e],b.test(c))return!0},AbstractChosen.prototype.choices_count=function(){var a,b,c,d;if(null!=this.selected_option_count)return this.selected_option_count;for(this.selected_option_count=0,d=this.form_field.options,b=0,c=d.length;c>b;b++)a=d[b],a.selected&&(this.selected_option_count+=1);return this.selected_option_count},AbstractChosen.prototype.choices_click=function(a){return a.preventDefault(),this.results_showing||this.is_disabled?void 0:this.results_show()},AbstractChosen.prototype.keyup_checker=function(a){var b,c;switch(b=null!=(c=a.which)?c:a.keyCode,this.search_field_scale(),b){case 8:if(this.is_multiple&&this.backstroke_length<1&&this.choices_count()>0)return this.keydown_backstroke();if(!this.pending_backstroke)return this.result_clear_highlight(),this.results_search();break;case 13:if(a.preventDefault(),this.results_showing)return this.result_select(a);break;case 27:return this.results_showing&&this.results_hide(),!0;case 9:case 38:case 40:case 16:case 91:case 17:break;default:return this.results_search()}},AbstractChosen.prototype.clipboard_event_checker=function(){var a=this;return setTimeout(function(){return a.results_search()},50)},AbstractChosen.prototype.container_width=function(){return null!=this.options.width?this.options.width:""+this.form_field.offsetWidth+"px"},AbstractChosen.prototype.include_option_in_results=function(a){return this.is_multiple&&!this.display_selected_options&&a.selected?!1:!this.display_disabled_options&&a.disabled?!1:a.empty?!1:!0},AbstractChosen.prototype.search_results_touchstart=function(a){return this.touch_started=!0,this.search_results_mouseover(a)},AbstractChosen.prototype.search_results_touchmove=function(a){return this.touch_started=!1,this.search_results_mouseout(a)},AbstractChosen.prototype.search_results_touchend=function(a){return this.touch_started?this.search_results_mouseup(a):void 0},AbstractChosen.prototype.outerHTML=function(a){var b;return a.outerHTML?a.outerHTML:(b=document.createElement("div"),b.appendChild(a),b.innerHTML)},AbstractChosen.browser_is_supported=function(){return"Microsoft Internet Explorer"===window.navigator.appName?document.documentMode>=8:/iP(od|hone)/i.test(window.navigator.userAgent)?!1:/Android/i.test(window.navigator.userAgent)&&/Mobile/i.test(window.navigator.userAgent)?!1:!0},AbstractChosen.default_multiple_text="Select Some Options",AbstractChosen.default_single_text="Select an Option",AbstractChosen.default_no_result_text="No results match",AbstractChosen}(),a=jQuery,a.fn.extend({chosen:function(b){return AbstractChosen.browser_is_supported()?this.each(function(){var c,d;c=a(this),d=c.data("chosen"),"destroy"===b&&d instanceof Chosen?d.destroy():d instanceof Chosen||c.data("chosen",new Chosen(this,b))}):this}}),Chosen=function(c){function Chosen(){return b=Chosen.__super__.constructor.apply(this,arguments)}return d(Chosen,c),Chosen.prototype.setup=function(){return this.form_field_jq=a(this.form_field),this.current_selectedIndex=this.form_field.selectedIndex,this.is_rtl=this.form_field_jq.hasClass("chosen-rtl")},Chosen.prototype.set_up_html=function(){var b,c;return b=["chosen-container"],b.push("chosen-container-"+(this.is_multiple?"multi":"single")),this.inherit_select_classes&&this.form_field.className&&b.push(this.form_field.className),this.is_rtl&&b.push("chosen-rtl"),c={"class":b.join(" "),style:"width: "+this.container_width()+";",title:this.form_field.title},this.form_field.id.length&&(c.id=this.form_field.id.replace(/[^\w]/g,"_")+"_chosen"),this.container=a("
",c),this.is_multiple?this.container.html('
    '):this.container.html(''+this.default_text+'
      '),this.form_field_jq.hide().after(this.container),this.dropdown=this.container.find("div.chosen-drop").first(),this.search_field=this.container.find("input").first(),this.search_results=this.container.find("ul.chosen-results").first(),this.search_field_scale(),this.search_no_results=this.container.find("li.no-results").first(),this.is_multiple?(this.search_choices=this.container.find("ul.chosen-choices").first(),this.search_container=this.container.find("li.search-field").first()):(this.search_container=this.container.find("div.chosen-search").first(),this.selected_item=this.container.find(".chosen-single").first()),this.results_build(),this.set_tab_index(),this.set_label_behavior()},Chosen.prototype.on_ready=function(){return this.form_field_jq.trigger("chosen:ready",{chosen:this})},Chosen.prototype.register_observers=function(){var a=this;return this.container.bind("touchstart.chosen",function(b){return a.container_mousedown(b),b.preventDefault()}),this.container.bind("touchend.chosen",function(b){return a.container_mouseup(b),b.preventDefault()}),this.container.bind("mousedown.chosen",function(b){a.container_mousedown(b)}),this.container.bind("mouseup.chosen",function(b){a.container_mouseup(b)}),this.container.bind("mouseenter.chosen",function(b){a.mouse_enter(b)}),this.container.bind("mouseleave.chosen",function(b){a.mouse_leave(b)}),this.search_results.bind("mouseup.chosen",function(b){a.search_results_mouseup(b)}),this.search_results.bind("mouseover.chosen",function(b){a.search_results_mouseover(b)}),this.search_results.bind("mouseout.chosen",function(b){a.search_results_mouseout(b)}),this.search_results.bind("mousewheel.chosen DOMMouseScroll.chosen",function(b){a.search_results_mousewheel(b)}),this.search_results.bind("touchstart.chosen",function(b){a.search_results_touchstart(b)}),this.search_results.bind("touchmove.chosen",function(b){a.search_results_touchmove(b)}),this.search_results.bind("touchend.chosen",function(b){a.search_results_touchend(b)}),this.form_field_jq.bind("chosen:updated.chosen",function(b){a.results_update_field(b)}),this.form_field_jq.bind("chosen:activate.chosen",function(b){a.activate_field(b)}),this.form_field_jq.bind("chosen:open.chosen",function(b){a.container_mousedown(b)}),this.form_field_jq.bind("chosen:close.chosen",function(b){a.input_blur(b)}),this.search_field.bind("blur.chosen",function(b){a.input_blur(b)}),this.search_field.bind("keyup.chosen",function(b){a.keyup_checker(b)}),this.search_field.bind("keydown.chosen",function(b){a.keydown_checker(b)}),this.search_field.bind("focus.chosen",function(b){a.input_focus(b)}),this.search_field.bind("cut.chosen",function(b){a.clipboard_event_checker(b)}),this.search_field.bind("paste.chosen",function(b){a.clipboard_event_checker(b)}),this.is_multiple?this.search_choices.bind("click.chosen",function(b){a.choices_click(b)}):this.container.bind("click.chosen",function(a){a.preventDefault()})},Chosen.prototype.destroy=function(){return a(this.container[0].ownerDocument).unbind("click.chosen",this.click_test_action),this.search_field[0].tabIndex&&(this.form_field_jq[0].tabIndex=this.search_field[0].tabIndex),this.container.remove(),this.form_field_jq.removeData("chosen"),this.form_field_jq.show()},Chosen.prototype.search_field_disabled=function(){return this.is_disabled=this.form_field_jq[0].disabled,this.is_disabled?(this.container.addClass("chosen-disabled"),this.search_field[0].disabled=!0,this.is_multiple||this.selected_item.unbind("focus.chosen",this.activate_action),this.close_field()):(this.container.removeClass("chosen-disabled"),this.search_field[0].disabled=!1,this.is_multiple?void 0:this.selected_item.bind("focus.chosen",this.activate_action))},Chosen.prototype.container_mousedown=function(b){return this.is_disabled||(b&&"mousedown"===b.type&&!this.results_showing&&b.preventDefault(),null!=b&&a(b.target).hasClass("search-choice-close"))?void 0:(this.active_field?this.is_multiple||!b||a(b.target)[0]!==this.selected_item[0]&&!a(b.target).parents("a.chosen-single").length||(b.preventDefault(),this.results_toggle()):(this.is_multiple&&this.search_field.val(""),a(this.container[0].ownerDocument).bind("click.chosen",this.click_test_action),this.results_show()),this.activate_field())},Chosen.prototype.container_mouseup=function(a){return"ABBR"!==a.target.nodeName||this.is_disabled?void 0:this.results_reset(a)},Chosen.prototype.search_results_mousewheel=function(a){var b;return a.originalEvent&&(b=a.originalEvent.deltaY||-a.originalEvent.wheelDelta||a.originalEvent.detail),null!=b?(a.preventDefault(),"DOMMouseScroll"===a.type&&(b=40*b),this.search_results.scrollTop(b+this.search_results.scrollTop())):void 0},Chosen.prototype.blur_test=function(){return!this.active_field&&this.container.hasClass("chosen-container-active")?this.close_field():void 0},Chosen.prototype.close_field=function(){return a(this.container[0].ownerDocument).unbind("click.chosen",this.click_test_action),this.active_field=!1,this.results_hide(),this.container.removeClass("chosen-container-active"),this.clear_backstroke(),this.show_search_field_default(),this.search_field_scale()},Chosen.prototype.activate_field=function(){return this.container.addClass("chosen-container-active"),this.active_field=!0,this.search_field.val(this.search_field.val()),this.search_field.focus()},Chosen.prototype.test_active_click=function(b){var c;return c=a(b.target).closest(".chosen-container"),c.length&&this.container[0]===c[0]?this.active_field=!0:this.close_field()},Chosen.prototype.results_build=function(){return this.parsing=!0,this.selected_option_count=null,this.results_data=SelectParser.select_to_array(this.form_field),this.is_multiple?this.search_choices.find("li.search-choice").remove():this.is_multiple||(this.single_set_selected_text(),this.disable_search||this.form_field.options.length<=this.disable_search_threshold?(this.search_field[0].readOnly=!0,this.container.addClass("chosen-container-single-nosearch")):(this.search_field[0].readOnly=!1,this.container.removeClass("chosen-container-single-nosearch"))),this.update_results_content(this.results_option_build({first:!0})),this.search_field_disabled(),this.show_search_field_default(),this.search_field_scale(),this.parsing=!1},Chosen.prototype.result_do_highlight=function(a){var b,c,d,e,f;if(a.length){if(this.result_clear_highlight(),this.result_highlight=a,this.result_highlight.addClass("highlighted"),d=parseInt(this.search_results.css("maxHeight"),10),f=this.search_results.scrollTop(),e=d+f,c=this.result_highlight.position().top+this.search_results.scrollTop(),b=c+this.result_highlight.outerHeight(),b>=e)return this.search_results.scrollTop(b-d>0?b-d:0);if(f>c)return this.search_results.scrollTop(c)}},Chosen.prototype.result_clear_highlight=function(){return this.result_highlight&&this.result_highlight.removeClass("highlighted"),this.result_highlight=null},Chosen.prototype.results_show=function(){return this.is_multiple&&this.max_selected_options<=this.choices_count()?(this.form_field_jq.trigger("chosen:maxselected",{chosen:this}),!1):(this.container.addClass("chosen-with-drop"),this.results_showing=!0,this.search_field.focus(),this.search_field.val(this.search_field.val()),this.winnow_results(),this.form_field_jq.trigger("chosen:showing_dropdown",{chosen:this}))},Chosen.prototype.update_results_content=function(a){return this.search_results.html(a)},Chosen.prototype.results_hide=function(){return this.results_showing&&(this.result_clear_highlight(),this.container.removeClass("chosen-with-drop"),this.form_field_jq.trigger("chosen:hiding_dropdown",{chosen:this})),this.results_showing=!1},Chosen.prototype.set_tab_index=function(){var a;return this.form_field.tabIndex?(a=this.form_field.tabIndex,this.form_field.tabIndex=-1,this.search_field[0].tabIndex=a):void 0},Chosen.prototype.set_label_behavior=function(){var b=this;return this.form_field_label=this.form_field_jq.parents("label"),!this.form_field_label.length&&this.form_field.id.length&&(this.form_field_label=a("label[for='"+this.form_field.id+"']")),this.form_field_label.length>0?this.form_field_label.bind("click.chosen",function(a){return b.is_multiple?b.container_mousedown(a):b.activate_field()}):void 0},Chosen.prototype.show_search_field_default=function(){return this.is_multiple&&this.choices_count()<1&&!this.active_field?(this.search_field.val(this.default_text),this.search_field.addClass("default")):(this.search_field.val(""),this.search_field.removeClass("default"))},Chosen.prototype.search_results_mouseup=function(b){var c;return c=a(b.target).hasClass("active-result")?a(b.target):a(b.target).parents(".active-result").first(),c.length?(this.result_highlight=c,this.result_select(b),this.search_field.focus()):void 0},Chosen.prototype.search_results_mouseover=function(b){var c;return c=a(b.target).hasClass("active-result")?a(b.target):a(b.target).parents(".active-result").first(),c?this.result_do_highlight(c):void 0},Chosen.prototype.search_results_mouseout=function(b){return a(b.target).hasClass("active-result")?this.result_clear_highlight():void 0},Chosen.prototype.choice_build=function(b){var c,d,e=this;return c=a("
    • ",{"class":"search-choice"}).html(""+this.choice_label(b)+""),b.disabled?c.addClass("search-choice-disabled"):(d=a("",{"class":"search-choice-close","data-option-array-index":b.array_index}),d.bind("click.chosen",function(a){return e.choice_destroy_link_click(a)}),c.append(d)),this.search_container.before(c)},Chosen.prototype.choice_destroy_link_click=function(b){return b.preventDefault(),b.stopPropagation(),this.is_disabled?void 0:this.choice_destroy(a(b.target))},Chosen.prototype.choice_destroy=function(a){return this.result_deselect(a[0].getAttribute("data-option-array-index"))?(this.show_search_field_default(),this.is_multiple&&this.choices_count()>0&&this.search_field.val().length<1&&this.results_hide(),a.parents("li").first().remove(),this.search_field_scale()):void 0},Chosen.prototype.results_reset=function(){return this.reset_single_select_options(),this.form_field.options[0].selected=!0,this.single_set_selected_text(),this.show_search_field_default(),this.results_reset_cleanup(),this.form_field_jq.trigger("change"),this.active_field?this.results_hide():void 0},Chosen.prototype.results_reset_cleanup=function(){return this.current_selectedIndex=this.form_field.selectedIndex,this.selected_item.find("abbr").remove()},Chosen.prototype.result_select=function(a){var b,c;return this.result_highlight?(b=this.result_highlight,this.result_clear_highlight(),this.is_multiple&&this.max_selected_options<=this.choices_count()?(this.form_field_jq.trigger("chosen:maxselected",{chosen:this}),!1):(this.is_multiple?b.removeClass("active-result"):this.reset_single_select_options(),b.addClass("result-selected"),c=this.results_data[b[0].getAttribute("data-option-array-index")],c.selected=!0,this.form_field.options[c.options_index].selected=!0,this.selected_option_count=null,this.is_multiple?this.choice_build(c):this.single_set_selected_text(this.choice_label(c)),(a.metaKey||a.ctrlKey)&&this.is_multiple||this.results_hide(),this.search_field.val(""),(this.is_multiple||this.form_field.selectedIndex!==this.current_selectedIndex)&&this.form_field_jq.trigger("change",{selected:this.form_field.options[c.options_index].value}),this.current_selectedIndex=this.form_field.selectedIndex,a.preventDefault(),this.search_field_scale())):void 0},Chosen.prototype.single_set_selected_text=function(a){return null==a&&(a=this.default_text),a===this.default_text?this.selected_item.addClass("chosen-default"):(this.single_deselect_control_build(),this.selected_item.removeClass("chosen-default")),this.selected_item.find("span").html(a)},Chosen.prototype.result_deselect=function(a){var b;return b=this.results_data[a],this.form_field.options[b.options_index].disabled?!1:(b.selected=!1,this.form_field.options[b.options_index].selected=!1,this.selected_option_count=null,this.result_clear_highlight(),this.results_showing&&this.winnow_results(),this.form_field_jq.trigger("change",{deselected:this.form_field.options[b.options_index].value}),this.search_field_scale(),!0)},Chosen.prototype.single_deselect_control_build=function(){return this.allow_single_deselect?(this.selected_item.find("abbr").length||this.selected_item.find("span").first().after(''),this.selected_item.addClass("chosen-single-with-deselect")):void 0},Chosen.prototype.get_search_text=function(){return a("
      ").text(a.trim(this.search_field.val())).html()},Chosen.prototype.winnow_results_set_highlight=function(){var a,b;return b=this.is_multiple?[]:this.search_results.find(".result-selected.active-result"),a=b.length?b.first():this.search_results.find(".active-result").first(),null!=a?this.result_do_highlight(a):void 0},Chosen.prototype.no_results=function(b){var c;return c=a('
    • '+this.results_none_found+' ""
    • '),c.find("span").first().html(b),this.search_results.append(c),this.form_field_jq.trigger("chosen:no_results",{chosen:this})},Chosen.prototype.no_results_clear=function(){return this.search_results.find(".no-results").remove()},Chosen.prototype.keydown_arrow=function(){var a;return this.results_showing&&this.result_highlight?(a=this.result_highlight.nextAll("li.active-result").first())?this.result_do_highlight(a):void 0:this.results_show()},Chosen.prototype.keyup_arrow=function(){var a;return this.results_showing||this.is_multiple?this.result_highlight?(a=this.result_highlight.prevAll("li.active-result"),a.length?this.result_do_highlight(a.first()):(this.choices_count()>0&&this.results_hide(),this.result_clear_highlight())):void 0:this.results_show()},Chosen.prototype.keydown_backstroke=function(){var a;return this.pending_backstroke?(this.choice_destroy(this.pending_backstroke.find("a").first()),this.clear_backstroke()):(a=this.search_container.siblings("li.search-choice").last(),a.length&&!a.hasClass("search-choice-disabled")?(this.pending_backstroke=a,this.single_backstroke_delete?this.keydown_backstroke():this.pending_backstroke.addClass("search-choice-focus")):void 0)},Chosen.prototype.clear_backstroke=function(){return this.pending_backstroke&&this.pending_backstroke.removeClass("search-choice-focus"),this.pending_backstroke=null},Chosen.prototype.keydown_checker=function(a){var b,c;switch(b=null!=(c=a.which)?c:a.keyCode,this.search_field_scale(),8!==b&&this.pending_backstroke&&this.clear_backstroke(),b){case 8:this.backstroke_length=this.search_field.val().length;break;case 9:this.results_showing&&!this.is_multiple&&this.result_select(a),this.mouse_on_container=!1;break;case 13:this.results_showing&&a.preventDefault();break;case 32:this.disable_search&&a.preventDefault();break;case 38:a.preventDefault(),this.keyup_arrow();break;case 40:a.preventDefault(),this.keydown_arrow()}},Chosen.prototype.search_field_scale=function(){var b,c,d,e,f,g,h,i,j;if(this.is_multiple){for(d=0,h=0,f="position:absolute; left: -1000px; top: -1000px; display:none;",g=["font-size","font-style","font-weight","font-family","line-height","text-transform","letter-spacing"],i=0,j=g.length;j>i;i++)e=g[i],f+=e+":"+this.search_field.css(e)+";";return b=a("
      ",{style:f}),b.text(this.search_field.val()),a("body").append(b),h=b.width()+25,b.remove(),c=this.container.outerWidth(),h>c-10&&(h=c-10),this.search_field.css({width:h+"px"})}},Chosen}(AbstractChosen)}).call(this);rt-5.0.1/share/static/js/autocomplete.js000644 000765 000024 00000020750 14005011336 021051 0ustar00sunnavystaff000000 000000 if (!window.RT) window.RT = {} if (!window.RT.Autocomplete) window.RT.Autocomplete = {} window.RT.Autocomplete.Classes = { Users: 'user', Owners: 'owner', Groups: 'group', Tickets: 'tickets', Queues: 'queues', Articles: 'articles' }; Selectize.define('rt_drag_drop', function(options) { this.require('drag_drop'); var self = this; self.setup = (function() { var original = self.setup; return function() { original.apply(this, arguments); self.$control.sortable('option', 'connectWith', '.selectize-input'); self.$control.on('sortreceive', function(e, ui) { var input = jQuery(e.target).parent().prev('input'); var self = input.selectize()[0].selectize; var value = ui.item.attr('data-value'); self.createItem(value, false); self.getItem(value).children('span').text(ui.item.children('span').text()); self.getItem(value).insertBefore(ui.item); ui.item.remove(); self.setCaret(self.items.length); }); self.$control.on('sortremove', function(e, ui) { var input = jQuery(e.target).parent().prev('input'); var self = input.selectize()[0].selectize; var value = ui.item.attr('data-value'); self.removeItem(value, true); self.trigger('item_remove', value, ui.item); }); }; })(); }); window.RT.Autocomplete.bind = function(from) { jQuery("input[data-autocomplete]", from).each(function(){ var input = jQuery(this); var what = input.attr("data-autocomplete"); var wants = input.attr("data-autocomplete-return"); if (!what || !window.RT.Autocomplete.Classes[what]) return; if (what === 'Users' && input.is('[data-autocomplete-multiple]')) { var options = input.attr('data-options'); var items = input.attr('data-items'); input.selectize({ plugins: ['remove_button', 'rt_drag_drop'], options: options ? JSON.parse(options) : null, // If input value contains multiple items, selectize only // renders the first item somehow. Here we explicitly set // items to get around this issue. items: items ? JSON.parse(items) : null, valueField: 'value', labelField: 'label', searchField: ['label', 'value', 'text'], create: true, closeAfterSelect: true, maxItems: null, allowEmptyOption: false, openOnFocus: false, selectOnTab: true, placeholder: input.attr('placeholder'), render: { option_create: function(data, escape) { return '
      ' + escape(data.input) + '
      '; }, option: function(data, escape) { return '
      ' + (data.selectize_option || escape(data.label)) + '
      '; }, item: function(data, escape) { return '
      ' + (data.selectize_item || escape(data.label)) + '
      '; } }, onItemRemove: function(value) { // We do not want dropdown to show on removing items, but there is no such option. // Here we temporarily lock the selectize to achieve it. var self = input[0].selectize; self.lock(); setTimeout( function() { self.unlock(); },100); }, load: function(input, callback) { if (!input.length) return callback(); jQuery.ajax({ url: RT.Config.WebPath + '/Helpers/Autocomplete/Users', type: 'GET', dataType: 'json', data: { delim: ',', term: input, return: wants }, error: function() { callback(); }, success: function(res) { callback(res); } }); } }); return; } // Don't re-bind the autocompleter if (input.data("ui-autocomplete")) return; var queryargs = []; var options = { source: RT.Config.WebHomePath + "/Helpers/Autocomplete/" + what }; if ( wants ) { queryargs.push("return=" + wants); } if (what == 'Queues') { options.minLength = 2; options.delay = 2; } else if (what == 'Owners') { options.minLength = 2; } if (input.is('[data-autocomplete-privileged]')) { queryargs.push("privileged=1"); } if (input.is('[data-autocomplete-include-nobody]')) { queryargs.push("include_nobody=1"); } if (input.is('[data-autocomplete-include-system]')) { queryargs.push("include_system=1"); } if (input.is('[data-autocomplete-multiple]')) { if ( what != 'Tickets' ) { queryargs.push("delim=,"); } options.focus = function () { // prevent value inserted on focus return false; } options.select = function(event, ui) { var terms = this.value.split(what == 'Tickets' ? /\s+/ : /,\s*/); terms.pop(); // remove current input if ( what == 'Tickets' ) { // remove non-integers in case subject search with spaces in (like "foo bar") var new_terms = []; for ( var i = 0; i < terms.length; i++ ) { if ( terms[i].match(/\D/) ) { break; // Items after the first non-integers are all parts of search string } new_terms.push(terms[i]); } terms = new_terms; } terms.push( ui.item.value ); // add selected item terms.push(''); // add trailing delimeter so user can input another value directly this.value = terms.join(what == 'Tickets' ? ' ' : ", "); jQuery(this).change(); return false; } } if (input.attr("data-autocomplete-autosubmit")) { options.select = function( event, ui ) { jQuery(event.target).val(ui.item.value); var form = jQuery(event.target).closest("form"); if ( what === 'Queues' ) { form.find('input[name=QueueChanged]').val(1); } form.submit(); }; } var queue = input.attr("data-autocomplete-queue"); if (queue) queryargs.push("queue=" + queue); var checkRight = input.attr("data-autocomplete-checkright"); if (checkRight) queryargs.push("right=" + checkRight); var exclude = input.attr('data-autocomplete-exclude'); if (exclude) { queryargs.push("exclude="+exclude); } var limit = input.attr("data-autocomplete-limit"); if (limit) { queryargs.push("limit="+limit); } if (queryargs.length) options.source += "?" + queryargs.join("&"); input.addClass('autocompletes-' + window.RT.Autocomplete.Classes[what] ) .autocomplete(options) .data("ui-autocomplete") ._renderItem = function(ul, item) { var rendered = jQuery("
      "); if (item.html == null) rendered.text( item.label ); else rendered.html( item.html ); return jQuery("
    • ") .data( "item.autocomplete", item ) .append( rendered ) .appendTo( ul ); }; }); }; jQuery(function(){ RT.Autocomplete.bind(document) }); rt-5.0.1/share/static/js/event-registration.js000644 000765 000024 00000011614 14005011336 022200 0ustar00sunnavystaff000000 000000 // Disable chosing individual objects when a scrip is applied globally jQuery(function() { var global_checkboxes = [ "form[name=AddRemoveScrip] input[type=checkbox][name^=AddScrip-][value=0]", "form input[type=checkbox][name^=AddCustomField-][value=0]" ]; jQuery(global_checkboxes.join(", ")) .change(function(){ var self = jQuery(this); var checked = self.prop("checked"); self.closest("form") .find("table.collection input[type=checkbox]") .prop("disabled", checked); }); }); // Replace user references in history with the HTML versions function ReplaceUserReferences() { var users = jQuery(".user[data-replace=user]"); var ids = users.map(function(){ return "id=" + encodeURIComponent(jQuery(this).attr("data-user-id")) }).toArray().join(";"); if (!ids.length) return jQuery.get( RT.Config.WebPath + "/Helpers/UserInfo?" + ids, function(json) { users.each(function() { var user = jQuery(this); var uid = user.attr("data-user-id"); if (!json[uid]) return user.removeAttr("data-replace") .html( jQuery(json[uid]._html).html() ); }); } ); } jQuery(ReplaceUserReferences); // Cascaded selects jQuery(function() { jQuery("select.cascade-by-optgroup").each(function(){ var name = this.name; if (!name) return; // Generate elements for cascading based on the master we just generated var selected = jQuery("option[selected]", this).parent().attr("label"); if (selected === undefined) selected = ""; jQuery('option[value="' + selected + '"]', groups).attr("selected", "selected"); jQuery(this).selectpicker(); groups.selectpicker(); // Wire it all up groups.change(function(){ var name = this.name.replace(/-Groups$/, ''); var field = jQuery(this); var subfield = field.closest('fieldset').find("select[name=" + name + "]"); var complete = field.closest('fieldset').find("select[name=" + name + "-Complete]"); var value = field.val(); filter_cascade_select( subfield[0], complete[0], value ); }).change(); }); jQuery('[data-cascade-based-on-name]').each( function() { var based_on_name = jQuery(this).attr('data-cascade-based-on-name'); var based_on = jQuery('[name^="' + based_on_name + '"][type!="hidden"]:input:not(.hidden)'); var id = jQuery(this).attr('id'); based_on.each( function() { var oldchange = jQuery(this).onchange; jQuery(this).change( function () { var vals; if ( jQuery(this).is('select') ) { vals = based_on.first().val(); } else { vals = []; jQuery(based_on).each( function() { if ( jQuery(this).is(':checked') ) { vals.push(jQuery(this).val()); } }); } filter_cascade_by_id( id, vals ); if (oldchange != null) oldchange(); }); }); if ( based_on.is('select') ) { based_on.change(); } else { based_on.first().change(); } }); }); jQuery( function() { jQuery("input[type=file]").change( function() { var input = jQuery(this); var warning = input.next(".invalid"); if ( !input.val().match(/"/) ) { warning.hide(); } else { if (warning.length) { warning.show(); } else { input.val(""); jQuery("") .text(loc_key("quote_in_filename")) .insertAfter(input); } } }); }); jQuery(function() { jQuery("#UpdateType").change(function(ev) { jQuery(".messagebox-container") .removeClass("action-response action-private") .addClass("action-"+ev.target.value); }); }); rt-5.0.1/share/static/js/bootstrap.min.js000644 000765 000024 00000154666 14005011336 021165 0ustar00sunnavystaff000000 000000 /*! * Bootstrap v4.2.1 (https://getbootstrap.com/) * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("popper.js"),require("jquery")):"function"==typeof define&&define.amd?define(["exports","popper.js","jquery"],e):e(t.bootstrap={},t.Popper,t.jQuery)}(this,function(t,u,g){"use strict";function i(t,e){for(var n=0;nthis._items.length-1||t<0))if(this._isSliding)g(this._element).one(Q.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=ndocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
    • ',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},De="show",we="out",Ae={HIDE:"hide"+Ee,HIDDEN:"hidden"+Ee,SHOW:"show"+Ee,SHOWN:"shown"+Ee,INSERTED:"inserted"+Ee,CLICK:"click"+Ee,FOCUSIN:"focusin"+Ee,FOCUSOUT:"focusout"+Ee,MOUSEENTER:"mouseenter"+Ee,MOUSELEAVE:"mouseleave"+Ee},Ne="fade",Oe="show",ke=".tooltip-inner",Pe=".arrow",Le="hover",je="focus",He="click",Re="manual",Ue=function(){function i(t,e){if("undefined"==typeof u)throw new TypeError("Bootstrap's tooltips require Popper.js (https://popper.js.org/)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=g(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(g(this.getTipElement()).hasClass(Oe))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),g.removeData(this.element,this.constructor.DATA_KEY),g(this.element).off(this.constructor.EVENT_KEY),g(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&g(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,(this._activeTrigger=null)!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===g(this.element).css("display"))throw new Error("Please use show on visible elements");var t=g.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){g(this.element).trigger(t);var n=_.findShadowRoot(this.element),i=g.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!i)return;var o=this.getTipElement(),r=_.getUID(this.constructor.NAME);o.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&g(o).addClass(Ne);var s="function"==typeof this.config.placement?this.config.placement.call(this,o,this.element):this.config.placement,a=this._getAttachment(s);this.addAttachmentClass(a);var l=this._getContainer();g(o).data(this.constructor.DATA_KEY,this),g.contains(this.element.ownerDocument.documentElement,this.tip)||g(o).appendTo(l),g(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new u(this.element,o,{placement:a,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:Pe},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}}),g(o).addClass(Oe),"ontouchstart"in document.documentElement&&g(document.body).children().on("mouseover",null,g.noop);var c=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,g(e.element).trigger(e.constructor.Event.SHOWN),t===we&&e._leave(null,e)};if(g(this.tip).hasClass(Ne)){var h=_.getTransitionDurationFromElement(this.tip);g(this.tip).one(_.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},t.hide=function(t){var e=this,n=this.getTipElement(),i=g.Event(this.constructor.Event.HIDE),o=function(){e._hoverState!==De&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),g(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(g(this.element).trigger(i),!i.isDefaultPrevented()){if(g(n).removeClass(Oe),"ontouchstart"in document.documentElement&&g(document.body).children().off("mouseover",null,g.noop),this._activeTrigger[He]=!1,this._activeTrigger[je]=!1,this._activeTrigger[Le]=!1,g(this.tip).hasClass(Ne)){var r=_.getTransitionDurationFromElement(n);g(n).one(_.TRANSITION_END,o).emulateTransitionEnd(r)}else o();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){g(this.getTipElement()).addClass(Ce+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(g(t.querySelectorAll(ke)),this.getTitle()),g(t).removeClass(Ne+" "+Oe)},t.setElementContent=function(t,e){var n=this.config.html;"object"==typeof e&&(e.nodeType||e.jquery)?n?g(e).parent().is(t)||t.empty().append(e):t.text(g(e).text()):t[n?"html":"text"](e)},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},t._getContainer=function(){return!1===this.config.container?document.body:_.isElement(this.config.container)?g(this.config.container):g(document).find(this.config.container)},t._getAttachment=function(t){return be[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)g(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==Re){var e=t===Le?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===Le?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;g(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}}),g(this.element).closest(".modal").on("hide.bs.modal",function(){i.element&&i.hide()}),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?je:Le]=!0),g(e.getTipElement()).hasClass(Oe)||e._hoverState===De?e._hoverState=De:(clearTimeout(e._timeout),e._hoverState=De,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===De&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?je:Le]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=we,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===we&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){return"number"==typeof(t=l({},this.constructor.Default,g(this.element).data(),"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),_.typeCheckConfig(pe,t,this.constructor.DefaultType),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(Te);null!==e&&e.length&&t.removeClass(e.join(""))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(g(t).removeClass(Ne),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=g(this).data(ve),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),g(this).data(ve,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.2.1"}},{key:"Default",get:function(){return Ie}},{key:"NAME",get:function(){return pe}},{key:"DATA_KEY",get:function(){return ve}},{key:"Event",get:function(){return Ae}},{key:"EVENT_KEY",get:function(){return Ee}},{key:"DefaultType",get:function(){return Se}}]),i}();g.fn[pe]=Ue._jQueryInterface,g.fn[pe].Constructor=Ue,g.fn[pe].noConflict=function(){return g.fn[pe]=ye,Ue._jQueryInterface};var We="popover",xe="bs.popover",Fe="."+xe,qe=g.fn[We],Me="bs-popover",Ke=new RegExp("(^|\\s)"+Me+"\\S+","g"),Qe=l({},Ue.Default,{placement:"right",trigger:"click",content:"",template:''}),Be=l({},Ue.DefaultType,{content:"(string|element|function)"}),Ve="fade",Ye="show",Xe=".popover-header",ze=".popover-body",Ge={HIDE:"hide"+Fe,HIDDEN:"hidden"+Fe,SHOW:"show"+Fe,SHOWN:"shown"+Fe,INSERTED:"inserted"+Fe,CLICK:"click"+Fe,FOCUSIN:"focusin"+Fe,FOCUSOUT:"focusout"+Fe,MOUSEENTER:"mouseenter"+Fe,MOUSELEAVE:"mouseleave"+Fe},Je=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),(e.prototype.constructor=e).__proto__=n;var o=i.prototype;return o.isWithContent=function(){return this.getTitle()||this._getContent()},o.addAttachmentClass=function(t){g(this.getTipElement()).addClass(Me+"-"+t)},o.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},o.setContent=function(){var t=g(this.getTipElement());this.setElementContent(t.find(Xe),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(ze),e),t.removeClass(Ve+" "+Ye)},o._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},o._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(Ke);null!==e&&0=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t 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); rt-5.0.1/share/static/js/quoteselection.js000644 000765 000024 00000003221 14005011336 021405 0ustar00sunnavystaff000000 000000 jQuery(function() { var reply_from_selection = function(ev) { var link = jQuery(this); var selection; if (window.getSelection) selection = window.getSelection(); else if (document.getSelection) selection = document.getSelection(); else if (document.selection) selection = document.selection.createRange().text; if (selection.toString) selection = selection.toString(); if (typeof(selection) !== "string" || selection.length < 3) return; // TODO: wrap long lines before quoting selection = selection.replace(/^/gm, "> "); if ( RT.Config.MessageBoxRichText ) { selection = selection.replace(/\r?\n/g, "
      "); selection = selection.concat("

      "); } else { selection = selection.concat("\n\n"); } selection = encodeURIComponent(selection); if ( !link.prop('data-href') ) { link.prop('data-href', link.attr('href')); } link.attr("href", link.prop("data-href").concat("&UpdateContent=" + selection)); }; var apply_quote = function() { var link = jQuery(this); if (link.data("quote-selection")) return; link.data("quote-selection",true); link.click(reply_from_selection); }; jQuery( ".reply-link, " + ".comment-link, " + "#page-actions-reply, " + "#page-actions-comment" ).each(apply_quote); jQuery(document).ajaxComplete(function(ev){ jQuery(".reply-link, .comment-link").each(apply_quote); }); }); rt-5.0.1/share/static/js/mousetrap.min.js000644 000765 000024 00000011130 14005011336 021141 0ustar00sunnavystaff000000 000000 /* mousetrap v1.5.3 craig.is/killing/mice */ (function(C,r,g){function t(a,b,h){a.addEventListener?a.addEventListener(b,h,!1):a.attachEvent("on"+b,h)}function x(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return l[a.which]?l[a.which]:p[a.which]?p[a.which]:String.fromCharCode(a.which).toLowerCase()}function D(a){var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");return b}function u(a){return"shift"==a||"ctrl"==a||"alt"==a|| "meta"==a}function y(a,b){var h,c,e,g=[];h=a;"+"===h?h=["+"]:(h=h.replace(/\+{2}/g,"+plus"),h=h.split("+"));for(e=0;em||l.hasOwnProperty(m)&&(k[l[m]]=m)}e=k[h]?"keydown":"keypress"}"keypress"==e&&g.length&&(e="keydown");return{key:c,modifiers:g,action:e}}function B(a,b){return null===a||a===r?!1:a===b?!0:B(a.parentNode,b)}function c(a){function b(a){a= a||{};var b=!1,n;for(n in q)a[n]?b=!0:q[n]=0;b||(v=!1)}function h(a,b,n,f,c,h){var g,e,l=[],m=n.type;if(!d._callbacks[a])return[];"keyup"==m&&u(a)&&(b=[a]);for(g=0;g":".","?":"/","|":"\\"},z={option:"alt",command:"meta","return":"enter", escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},k;for(g=1;20>g;++g)l[111+g]="f"+g;for(g=0;9>=g;++g)l[g+96]=g;c.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};c.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};c.prototype.trigger=function(a,b){if(this._directMap[a+":"+b])this._directMap[a+":"+b]({},a);return this};c.prototype.reset=function(){this._callbacks={};this._directMap= {};return this};c.prototype.stopCallback=function(a,b){return-1<(" "+b.className+" ").indexOf(" mousetrap ")||B(b,this.target)?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable};c.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};c.init=function(){var a=c(r),b;for(b in a)"_"!==b.charAt(0)&&(c[b]=function(b){return function(){return a[b].apply(a,arguments)}}(b))};c.init();C.Mousetrap=c;"undefined"!==typeof module&&module.exports&&(module.exports= c);"function"===typeof define&&define.amd&&define(function(){return c})})(window,document); rt-5.0.1/share/static/js/keyboard-shortcuts.js000644 000765 000024 00000013470 14005011336 022205 0ustar00sunnavystaff000000 000000 jQuery(function() { var goBack = function() { window.history.back(); }; var goForward = function() { window.history.forward(); }; var goHome = function() { var homeLink = jQuery('a#home'); window.location.href = homeLink.attr('href'); }; var simpleSearch = function() { var searchInput = jQuery('#simple-search').find('input'); if (!searchInput.length) { // try SelfService simple search searchInput = jQuery('#GotoTicket').find('input'); } if (!searchInput.length) return; searchInput.focus(); searchInput.select(); return false; // prevent '/' character from being typed in search box }; var openHelp = function() { var modal = jQuery('.modal'); if (modal.length) { jQuery.modal.close(); return; } var is_search = jQuery('body#comp-Search-Results').length > 0; var is_bulk_update = jQuery('body#comp-Search-Bulk').length > 0; var is_ticket_reply = jQuery('a#page-actions-reply').length > 0; var is_ticket_comment = jQuery('a#page-actions-comment').length > 0; var url = RT.Config.WebHomePath + '/Helpers/ShortcutHelp' + '?show_search=' + ( is_search || is_bulk_update ? '1' : '0' ) + '&show_bulk_update=' + ( is_bulk_update ? '1' : '0' ) + '&show_ticket_reply=' + ( is_ticket_reply ? '1' : '0' ) + '&show_ticket_comment=' + ( is_ticket_comment ? '1' : '0' ); jQuery.ajax({ url: url, success: showModal, error: function(xhr, reason) { // give the browser a chance to redraw the readout setTimeout(function () { alert(loc_key("shortcut_help_error") + " " + reason); }, 100); } }); }; var showModal = function(html) { var modal = jQuery(""); modal.append(html).appendTo("body"); modal.bind('modal:close', function() { modal.remove(); }) modal.on('hide.bs.modal', function() { modal.remove(); }) modal.modal('show'); }; Mousetrap.bind('g b', goBack); Mousetrap.bind('g f', goForward); Mousetrap.bind('g h', goHome); Mousetrap.bind('/', simpleSearch); Mousetrap.bind('?', openHelp); }); jQuery(function() { // Only load these shortcuts if there is a ticket list on the page var hasTicketList = jQuery('table.ticket-list').length; if (!hasTicketList) return; var currentRow; var nextTicket = function() { var nextRow; var searchResultsTable = jQuery('.ticket-list.collection-as-table'); if (!currentRow || !(nextRow = currentRow.next('tbody.list-item')).length) { nextRow = searchResultsTable.find('tbody.list-item').first(); } setNewRow(nextRow); }; var setNewRow = function(newRow) { if (currentRow) currentRow.removeClass('selected-row'); currentRow = newRow; currentRow.addClass('selected-row'); scrollToJQueryObject(currentRow); }; var prevTicket = function() { var prevRow, searchResultsTable = jQuery('.ticket-list.collection-as-table'); if (!currentRow || !(prevRow = currentRow.prev('tbody.list-item')).length) { prevRow = searchResultsTable.find('tbody.list-item').last(); } setNewRow(prevRow); }; var generateTicketLink = function(ticketId) { if (!ticketId) return ''; return RT.Config.WebHomePath + '/Ticket/Display.html?id=' + ticketId; }; var generateUpdateLink = function(ticketId, action) { if (!ticketId) return ''; return RT.Config.WebHomePath + '/Ticket/Update.html?Action=' + action + '&id=' + ticketId; }; var navigateToCurrentTicket = function() { if (!currentRow) return; var ticketId = currentRow.closest('tbody').data('recordId'); var ticketLink = generateTicketLink(ticketId); if (!ticketLink) return; window.location.href = ticketLink; }; var toggleTicketCheckbox = function() { if (!currentRow) return; var ticketCheckBox = currentRow.find('input[type=checkbox]'); if (!ticketCheckBox.length) return; ticketCheckBox.prop("checked", !ticketCheckBox.prop("checked")); }; var replyToTicket = function() { if (!currentRow) return; var ticketId = currentRow.closest('tbody').data('recordId'); var replyLink = generateUpdateLink(ticketId, 'Respond'); if (!replyLink) return; window.location.href = replyLink; }; var commentOnTicket = function() { if (!currentRow) return; var ticketId = currentRow.closest('tbody').data('recordId'); var commentLink = generateUpdateLink(ticketId, 'Comment'); if (!commentLink) return; window.location.href = commentLink; }; Mousetrap.bind('j', nextTicket); Mousetrap.bind('k', prevTicket); Mousetrap.bind(['enter','o'], navigateToCurrentTicket); Mousetrap.bind('r', replyToTicket); Mousetrap.bind('c', commentOnTicket); Mousetrap.bind('x', toggleTicketCheckbox); }); jQuery(function() { // Only load these shortcuts if reply or comment action is on page var ticket_reply = jQuery('a#page-actions-reply'); var ticket_comment = jQuery('a#page-actions-comment'); if (!ticket_reply.length && !ticket_comment.length) return; var replyToTicket = function() { if (!ticket_reply.length) return; window.location.href = ticket_reply.attr('href'); }; var commentOnTicket = function() { if (!ticket_comment.length) return; window.location.href = ticket_comment.attr('href'); }; Mousetrap.bind('r', replyToTicket); Mousetrap.bind('c', commentOnTicket); }); rt-5.0.1/share/static/js/forms.js000644 000765 000024 00000012215 14005011336 017473 0ustar00sunnavystaff000000 000000 jQuery(function() { jQuery('.selectionbox-js').each(function () { var container = jQuery(this); var source = container.find('.source'); var form = container.closest('form'); var submit = form.find('input[name=UpdateSearches]'); var copyHelper; var draggedIntoDestination; container.find('.destination ul').sortable({ connectWith: '.destination ul', placeholder: 'placeholder', forcePlaceholderSize: true, cancel: '.remove', // drag a clone of the source item receive: function (e, ui) { draggedIntoDestination = true; copyHelper = null; }, over: function () { removeIntent = false; }, out: function () { removeIntent = true; }, beforeStop: function (event, ui) { if(removeIntent == true){ ui.item.remove(); } }, }).on('click', '.remove', function (e) { e.preventDefault(); jQuery(e.target).closest('li').remove(); // dispose of the bootstrap tooltip. // without manually clearing here, the tooltip lingers after clicking remove. var bs_tooltip = jQuery('div[id^="tooltip"]'); bs_tooltip.tooltip('hide'); bs_tooltip.tooltip('dispose'); return false; }); source.find('ul').sortable({ connectWith: '.destination ul', containment: container, placeholder: 'placeholder', forcePlaceholderSize: true, // drag a clone of the source item helper: function (e, li) { copyHelper = li.clone().insertAfter(li); return li.clone(); }, start: function (e, ui) { draggedIntoDestination = false; }, stop: function (e, ui) { if (copyHelper) { copyHelper.remove(); } if (!draggedIntoDestination) { jQuery(this).sortable('cancel'); } } }); var searchField = source.find('input[name=search]'); var filterField = source.find('select[name=filter]'); var refreshSource = function () { var searchTerm = searchField.val().toLowerCase(); var filterType = filterField.val(); source.find('.section').each(function () { var section = jQuery(this); var sectionLabel = section.find('h3').text(); var sectionMatches = sectionLabel.toLowerCase().indexOf(searchTerm) > -1; var visibleItem = false; section.find('li').each(function () { var item = jQuery(this); var itemType = item.data('type'); if (filterType) { // component and dashboard matches on data-type if (filterType == 'component' || itemType == 'component' || filterType == 'dashboard' || itemType == 'dashboard') { if (itemType != filterType) { item.hide(); return; } } // everything else matches on data-search-type else { var searchType = item.data('search-type'); if (searchType === '') { searchType = 'ticket' } if (searchType.toLowerCase() != filterType) { item.hide(); return; } } } if (sectionMatches || item.text().toLowerCase().indexOf(searchTerm) > -1) { visibleItem = true; item.show(); } else { item.hide(); } }); if (visibleItem) { section.show(); } else { section.hide(); } }); source.find('.contents').scrollTop(0); }; searchField.on('propertychange change keyup paste input', function () { refreshSource(); }); filterField.on('change keyup', function () { refreshSource(); }); refreshSource(); submit.click(function () { container.find('.destination').each(function () { var pane = jQuery(this); var name = pane.data('pane'); pane.find('li').each(function () { var item = jQuery(this).data(); delete item.sortableItem; form.append(''); }); }); return true; }); }); }); rt-5.0.1/share/static/js/dropzone.min.js000644 000765 000024 00000100003 14005011336 020760 0ustar00sunnavystaff000000 000000 (function(){var a,b,c,d,e,f,g,h,i=[].slice,j={}.hasOwnProperty,k=function(a,b){function c(){this.constructor=a}for(var d in b)j.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a};g=function(){},b=function(){function a(){}return a.prototype.addEventListener=a.prototype.on,a.prototype.on=function(a,b){return this._callbacks=this._callbacks||{},this._callbacks[a]||(this._callbacks[a]=[]),this._callbacks[a].push(b),this},a.prototype.emit=function(){var a,b,c,d,e,f;if(d=arguments[0],a=2<=arguments.length?i.call(arguments,1):[],this._callbacks=this._callbacks||{},c=this._callbacks[d])for(e=0,f=c.length;f>e;e++)b=c[e],b.apply(this,a);return this},a.prototype.removeListener=a.prototype.off,a.prototype.removeAllListeners=a.prototype.off,a.prototype.removeEventListener=a.prototype.off,a.prototype.off=function(a,b){var c,d,e,f,g;if(!this._callbacks||0===arguments.length)return this._callbacks={},this;if(d=this._callbacks[a],!d)return this;if(1===arguments.length)return delete this._callbacks[a],this;for(e=f=0,g=d.length;g>f;e=++f)if(c=d[e],c===b){d.splice(e,1);break}return this},a}(),a=function(a){function c(a,b){var e,f,g;if(this.element=a,this.version=c.version,this.defaultOptions.previewTemplate=this.defaultOptions.previewTemplate.replace(/\n*/g,""),this.clickableElements=[],this.listeners=[],this.files=[],"string"==typeof this.element&&(this.element=document.querySelector(this.element)),!this.element||null==this.element.nodeType)throw new Error("Invalid dropzone element.");if(this.element.dropzone)throw new Error("Dropzone already attached.");if(c.instances.push(this),this.element.dropzone=this,e=null!=(g=c.optionsForElement(this.element))?g:{},this.options=d({},this.defaultOptions,e,null!=b?b:{}),this.options.forceFallback||!c.isBrowserSupported())return this.options.fallback.call(this);if(null==this.options.url&&(this.options.url=this.element.getAttribute("action")),!this.options.url)throw new Error("No URL provided.");if(this.options.acceptedFiles&&this.options.acceptedMimeTypes)throw new Error("You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated.");this.options.acceptedMimeTypes&&(this.options.acceptedFiles=this.options.acceptedMimeTypes,delete this.options.acceptedMimeTypes),this.options.method=this.options.method.toUpperCase(),(f=this.getExistingFallback())&&f.parentNode&&f.parentNode.removeChild(f),this.options.previewsContainer!==!1&&(this.previewsContainer=this.options.previewsContainer?c.getElement(this.options.previewsContainer,"previewsContainer"):this.element),this.options.clickable&&(this.clickableElements=this.options.clickable===!0?[this.element]:c.getElements(this.options.clickable,"clickable")),this.init()}var d,e;return k(c,a),c.prototype.Emitter=b,c.prototype.events=["drop","dragstart","dragend","dragenter","dragover","dragleave","addedfile","removedfile","thumbnail","error","errormultiple","processing","processingmultiple","uploadprogress","totaluploadprogress","sending","sendingmultiple","success","successmultiple","canceled","canceledmultiple","complete","completemultiple","reset","maxfilesexceeded","maxfilesreached","queuecomplete"],c.prototype.defaultOptions={url:null,method:"post",withCredentials:!1,parallelUploads:1,uploadMultiple:!1,maxFilesize:256,paramName:"file",createImageThumbnails:!0,maxThumbnailFilesize:10,thumbnailWidth:120,thumbnailHeight:120,filesizeBase:1e3,maxFiles:null,filesizeBase:1e3,params:{},clickable:!0,ignoreHiddenFiles:!0,acceptedFiles:null,acceptedMimeTypes:null,autoProcessQueue:!0,autoQueue:!0,addRemoveLinks:!1,previewsContainer:null,capture:null,dictDefaultMessage:"Drop files here to upload",dictFallbackMessage:"Your browser does not support drag'n'drop file uploads.",dictFallbackText:"Please use the fallback form below to upload your files like in the olden days.",dictFileTooBig:"File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.",dictInvalidFileType:"You can't upload files of this type.",dictResponseError:"Server responded with {{statusCode}} code.",dictCancelUpload:"Cancel upload",dictCancelUploadConfirmation:"Are you sure you want to cancel this upload?",dictRemoveFile:"Remove file",dictRemoveFileConfirmation:null,dictMaxFilesExceeded:"You can not upload any more files.",accept:function(a,b){return b()},init:function(){return g},forceFallback:!1,fallback:function(){var a,b,d,e,f,g;for(this.element.className=""+this.element.className+" dz-browser-not-supported",g=this.element.getElementsByTagName("div"),e=0,f=g.length;f>e;e++)a=g[e],/(^| )dz-message($| )/.test(a.className)&&(b=a,a.className="dz-message");return b||(b=c.createElement('
      '),this.element.appendChild(b)),d=b.getElementsByTagName("span")[0],d&&(d.textContent=this.options.dictFallbackMessage),this.element.appendChild(this.getFallbackForm())},resize:function(a){var b,c,d;return b={srcX:0,srcY:0,srcWidth:a.width,srcHeight:a.height},c=a.width/a.height,b.optWidth=this.options.thumbnailWidth,b.optHeight=this.options.thumbnailHeight,null==b.optWidth&&null==b.optHeight?(b.optWidth=b.srcWidth,b.optHeight=b.srcHeight):null==b.optWidth?b.optWidth=c*b.optHeight:null==b.optHeight&&(b.optHeight=1/c*b.optWidth),d=b.optWidth/b.optHeight,a.heightd?(b.srcHeight=a.height,b.srcWidth=b.srcHeight*d):(b.srcWidth=a.width,b.srcHeight=b.srcWidth/d),b.srcX=(a.width-b.srcWidth)/2,b.srcY=(a.height-b.srcHeight)/2,b},drop:function(){return this.element.classList.remove("dz-drag-hover")},dragstart:g,dragend:function(){return this.element.classList.remove("dz-drag-hover")},dragenter:function(){return this.element.classList.add("dz-drag-hover")},dragover:function(){return this.element.classList.add("dz-drag-hover")},dragleave:function(){return this.element.classList.remove("dz-drag-hover")},paste:g,reset:function(){return this.element.classList.remove("dz-started")},addedfile:function(a){var b,d,e,f,g,h,i,j,k,l,m,n,o;if(this.element===this.previewsContainer&&this.element.classList.add("dz-started"),this.previewsContainer){for(a.previewElement=c.createElement(this.options.previewTemplate.trim()),a.previewTemplate=a.previewElement,this.previewsContainer.appendChild(a.previewElement),l=a.previewElement.querySelectorAll("[data-dz-name]"),f=0,i=l.length;i>f;f++)b=l[f],b.textContent=a.name;for(m=a.previewElement.querySelectorAll("[data-dz-size]"),g=0,j=m.length;j>g;g++)b=m[g],b.innerHTML=this.filesize(a.size);for(this.options.addRemoveLinks&&(a._removeLink=c.createElement('
      '+this.options.dictRemoveFile+""),a.previewElement.appendChild(a._removeLink)),d=function(b){return function(d){return d.preventDefault(),d.stopPropagation(),a.status===c.UPLOADING?c.confirm(b.options.dictCancelUploadConfirmation,function(){return b.removeFile(a)}):b.options.dictRemoveFileConfirmation?c.confirm(b.options.dictRemoveFileConfirmation,function(){return b.removeFile(a)}):b.removeFile(a)}}(this),n=a.previewElement.querySelectorAll("[data-dz-remove]"),o=[],h=0,k=n.length;k>h;h++)e=n[h],o.push(e.addEventListener("click",d));return o}},removedfile:function(a){var b;return a.previewElement&&null!=(b=a.previewElement)&&b.parentNode.removeChild(a.previewElement),this._updateMaxFilesReachedClass()},thumbnail:function(a,b){var c,d,e,f;if(a.previewElement){for(a.previewElement.classList.remove("dz-file-preview"),f=a.previewElement.querySelectorAll("[data-dz-thumbnail]"),d=0,e=f.length;e>d;d++)c=f[d],c.alt=a.name,c.src=b;return setTimeout(function(){return function(){return a.previewElement.classList.add("dz-image-preview")}}(this),1)}},error:function(a,b){var c,d,e,f,g;if(a.previewElement){for(a.previewElement.classList.add("dz-error"),"String"!=typeof b&&b.error&&(b=b.error),f=a.previewElement.querySelectorAll("[data-dz-errormessage]"),g=[],d=0,e=f.length;e>d;d++)c=f[d],g.push(c.textContent=b);return g}},errormultiple:g,processing:function(a){return a.previewElement&&(a.previewElement.classList.add("dz-processing"),a._removeLink)?a._removeLink.textContent=this.options.dictCancelUpload:void 0},processingmultiple:g,uploadprogress:function(a,b){var c,d,e,f,g;if(a.previewElement){for(f=a.previewElement.querySelectorAll("[data-dz-uploadprogress]"),g=[],d=0,e=f.length;e>d;d++)c=f[d],g.push("PROGRESS"===c.nodeName?c.value=b:c.style.width=""+b+"%");return g}},totaluploadprogress:g,sending:g,sendingmultiple:g,success:function(a){return a.previewElement?a.previewElement.classList.add("dz-success"):void 0},successmultiple:g,canceled:function(a){return this.emit("error",a,"Upload canceled.")},canceledmultiple:g,complete:function(a){return a._removeLink&&(a._removeLink.textContent=this.options.dictRemoveFile),a.previewElement?a.previewElement.classList.add("dz-complete"):void 0},completemultiple:g,maxfilesexceeded:g,maxfilesreached:g,queuecomplete:g,previewTemplate:'
      \n
      \n
      \n
      \n
      \n
      \n
      \n
      \n
      \n \n Check\n \n \n \n \n \n
      \n
      \n \n Error\n \n \n \n \n \n \n \n
      \n
      '},d=function(){var a,b,c,d,e,f,g;for(d=arguments[0],c=2<=arguments.length?i.call(arguments,1):[],f=0,g=c.length;g>f;f++){b=c[f];for(a in b)e=b[a],d[a]=e}return d},c.prototype.getAcceptedFiles=function(){var a,b,c,d,e;for(d=this.files,e=[],b=0,c=d.length;c>b;b++)a=d[b],a.accepted&&e.push(a);return e},c.prototype.getRejectedFiles=function(){var a,b,c,d,e;for(d=this.files,e=[],b=0,c=d.length;c>b;b++)a=d[b],a.accepted||e.push(a);return e},c.prototype.getFilesWithStatus=function(a){var b,c,d,e,f;for(e=this.files,f=[],c=0,d=e.length;d>c;c++)b=e[c],b.status===a&&f.push(b);return f},c.prototype.getQueuedFiles=function(){return this.getFilesWithStatus(c.QUEUED)},c.prototype.getUploadingFiles=function(){return this.getFilesWithStatus(c.UPLOADING)},c.prototype.getActiveFiles=function(){var a,b,d,e,f;for(e=this.files,f=[],b=0,d=e.length;d>b;b++)a=e[b],(a.status===c.UPLOADING||a.status===c.QUEUED)&&f.push(a);return f},c.prototype.init=function(){var a,b,d,e,f,g,h;for("form"===this.element.tagName&&this.element.setAttribute("enctype","multipart/form-data"),this.element.classList.contains("dropzone")&&!this.element.querySelector(".dz-message")&&this.element.appendChild(c.createElement('
      '+this.options.dictDefaultMessage+"
      ")),this.clickableElements.length&&(d=function(a){return function(){return a.hiddenFileInput&&document.body.removeChild(a.hiddenFileInput),a.hiddenFileInput=document.createElement("input"),a.hiddenFileInput.setAttribute("type","file"),(null==a.options.maxFiles||a.options.maxFiles>1)&&a.hiddenFileInput.setAttribute("multiple","multiple"),a.hiddenFileInput.className="dz-hidden-input",null!=a.options.acceptedFiles&&a.hiddenFileInput.setAttribute("accept",a.options.acceptedFiles),null!=a.options.capture&&a.hiddenFileInput.setAttribute("capture",a.options.capture),a.hiddenFileInput.style.visibility="hidden",a.hiddenFileInput.style.position="absolute",a.hiddenFileInput.style.top="0",a.hiddenFileInput.style.left="0",a.hiddenFileInput.style.height="0",a.hiddenFileInput.style.width="0",document.body.appendChild(a.hiddenFileInput),a.hiddenFileInput.addEventListener("change",function(){var b,c,e,f;if(c=a.hiddenFileInput.files,c.length)for(e=0,f=c.length;f>e;e++)b=c[e],a.addFile(b);return d()})}}(this))(),this.URL=null!=(g=window.URL)?g:window.webkitURL,h=this.events,e=0,f=h.length;f>e;e++)a=h[e],this.on(a,this.options[a]);return this.on("uploadprogress",function(a){return function(){return a.updateTotalUploadProgress()}}(this)),this.on("removedfile",function(a){return function(){return a.updateTotalUploadProgress()}}(this)),this.on("canceled",function(a){return function(b){return a.emit("complete",b)}}(this)),this.on("complete",function(a){return function(){return 0===a.getUploadingFiles().length&&0===a.getQueuedFiles().length?setTimeout(function(){return a.emit("queuecomplete")},0):void 0}}(this)),b=function(a){return a.stopPropagation(),a.preventDefault?a.preventDefault():a.returnValue=!1},this.listeners=[{element:this.element,events:{dragstart:function(a){return function(b){return a.emit("dragstart",b)}}(this),dragenter:function(a){return function(c){return b(c),a.emit("dragenter",c)}}(this),dragover:function(a){return function(c){var d;try{d=c.dataTransfer.effectAllowed}catch(e){}return c.dataTransfer.dropEffect="move"===d||"linkMove"===d?"move":"copy",b(c),a.emit("dragover",c)}}(this),dragleave:function(a){return function(b){return a.emit("dragleave",b)}}(this),drop:function(a){return function(c){return b(c),a.drop(c)}}(this),dragend:function(a){return function(b){return a.emit("dragend",b)}}(this)}}],this.clickableElements.forEach(function(a){return function(b){return a.listeners.push({element:b,events:{click:function(d){return b!==a.element||d.target===a.element||c.elementInside(d.target,a.element.querySelector(".dz-message"))?a.hiddenFileInput.click():void 0}}})}}(this)),this.enable(),this.options.init.call(this)},c.prototype.destroy=function(){var a;return this.disable(),this.removeAllFiles(!0),(null!=(a=this.hiddenFileInput)?a.parentNode:void 0)&&(this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput),this.hiddenFileInput=null),delete this.element.dropzone,c.instances.splice(c.instances.indexOf(this),1)},c.prototype.updateTotalUploadProgress=function(){var a,b,c,d,e,f,g,h;if(d=0,c=0,a=this.getActiveFiles(),a.length){for(h=this.getActiveFiles(),f=0,g=h.length;g>f;f++)b=h[f],d+=b.upload.bytesSent,c+=b.upload.total;e=100*d/c}else e=100;return this.emit("totaluploadprogress",e,c,d)},c.prototype._getParamName=function(a){return"function"==typeof this.options.paramName?this.options.paramName(a):""+this.options.paramName+(this.options.uploadMultiple?"["+a+"]":"")},c.prototype.getFallbackForm=function(){var a,b,d,e;return(a=this.getExistingFallback())?a:(d='
      ',this.options.dictFallbackText&&(d+="

      "+this.options.dictFallbackText+"

      "),d+='
      ',b=c.createElement(d),"FORM"!==this.element.tagName?(e=c.createElement('
      '),e.appendChild(b)):(this.element.setAttribute("enctype","multipart/form-data"),this.element.setAttribute("method",this.options.method)),null!=e?e:b)},c.prototype.getExistingFallback=function(){var a,b,c,d,e,f;for(b=function(a){var b,c,d;for(c=0,d=a.length;d>c;c++)if(b=a[c],/(^| )fallback($| )/.test(b.className))return b},f=["div","form"],d=0,e=f.length;e>d;d++)if(c=f[d],a=b(this.element.getElementsByTagName(c)))return a},c.prototype.setupEventListeners=function(){var a,b,c,d,e,f,g;for(f=this.listeners,g=[],d=0,e=f.length;e>d;d++)a=f[d],g.push(function(){var d,e;d=a.events,e=[];for(b in d)c=d[b],e.push(a.element.addEventListener(b,c,!1));return e}());return g},c.prototype.removeEventListeners=function(){var a,b,c,d,e,f,g;for(f=this.listeners,g=[],d=0,e=f.length;e>d;d++)a=f[d],g.push(function(){var d,e;d=a.events,e=[];for(b in d)c=d[b],e.push(a.element.removeEventListener(b,c,!1));return e}());return g},c.prototype.disable=function(){var a,b,c,d,e;for(this.clickableElements.forEach(function(a){return a.classList.remove("dz-clickable")}),this.removeEventListeners(),d=this.files,e=[],b=0,c=d.length;c>b;b++)a=d[b],e.push(this.cancelUpload(a));return e},c.prototype.enable=function(){return this.clickableElements.forEach(function(a){return a.classList.add("dz-clickable")}),this.setupEventListeners()},c.prototype.filesize=function(a){var b,c,d,e,f,g,h,i;for(g=["TB","GB","MB","KB","b"],d=e=null,c=h=0,i=g.length;i>h;c=++h)if(f=g[c],b=Math.pow(this.options.filesizeBase,4-c)/10,a>=b){d=a/Math.pow(this.options.filesizeBase,4-c),e=f;break}return d=Math.round(10*d)/10,""+d+" "+e},c.prototype._updateMaxFilesReachedClass=function(){return null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(this.getAcceptedFiles().length===this.options.maxFiles&&this.emit("maxfilesreached",this.files),this.element.classList.add("dz-max-files-reached")):this.element.classList.remove("dz-max-files-reached")},c.prototype.drop=function(a){var b,c;a.dataTransfer&&(this.emit("drop",a),b=a.dataTransfer.files,b.length&&(c=a.dataTransfer.items,c&&c.length&&null!=c[0].webkitGetAsEntry?this._addFilesFromItems(c):this.handleFiles(b)))},c.prototype.paste=function(a){var b,c;if(null!=(null!=a&&null!=(c=a.clipboardData)?c.items:void 0))return this.emit("paste",a),b=a.clipboardData.items,b.length?this._addFilesFromItems(b):void 0},c.prototype.handleFiles=function(a){var b,c,d,e;for(e=[],c=0,d=a.length;d>c;c++)b=a[c],e.push(this.addFile(b));return e},c.prototype._addFilesFromItems=function(a){var b,c,d,e,f;for(f=[],d=0,e=a.length;e>d;d++)c=a[d],f.push(null!=c.webkitGetAsEntry&&(b=c.webkitGetAsEntry())?b.isFile?this.addFile(c.getAsFile()):b.isDirectory?this._addFilesFromDirectory(b,b.name):void 0:null!=c.getAsFile?null==c.kind||"file"===c.kind?this.addFile(c.getAsFile()):void 0:void 0);return f},c.prototype._addFilesFromDirectory=function(a,b){var c,d;return c=a.createReader(),d=function(a){return function(c){var d,e,f;for(e=0,f=c.length;f>e;e++)d=c[e],d.isFile?d.file(function(c){return a.options.ignoreHiddenFiles&&"."===c.name.substring(0,1)?void 0:(c.fullPath=""+b+"/"+c.name,a.addFile(c))}):d.isDirectory&&a._addFilesFromDirectory(d,""+b+"/"+d.name)}}(this),c.readEntries(d,function(a){return"undefined"!=typeof console&&null!==console&&"function"==typeof console.log?console.log(a):void 0})},c.prototype.accept=function(a,b){return a.size>1024*this.options.maxFilesize*1024?b(this.options.dictFileTooBig.replace("{{filesize}}",Math.round(a.size/1024/10.24)/100).replace("{{maxFilesize}}",this.options.maxFilesize)):c.isValidFile(a,this.options.acceptedFiles)?null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(b(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}",this.options.maxFiles)),this.emit("maxfilesexceeded",a)):this.options.accept.call(this,a,b):b(this.options.dictInvalidFileType)},c.prototype.addFile=function(a){return a.upload={progress:0,total:a.size,bytesSent:0},this.files.push(a),a.status=c.ADDED,this.emit("addedfile",a),this._enqueueThumbnail(a),this.accept(a,function(b){return function(c){return c?(a.accepted=!1,b._errorProcessing([a],c)):(a.accepted=!0,b.options.autoQueue&&b.enqueueFile(a)),b._updateMaxFilesReachedClass()}}(this))},c.prototype.enqueueFiles=function(a){var b,c,d;for(c=0,d=a.length;d>c;c++)b=a[c],this.enqueueFile(b);return null},c.prototype.enqueueFile=function(a){if(a.status!==c.ADDED||a.accepted!==!0)throw new Error("This file can't be queued because it has already been processed or was rejected.");return a.status=c.QUEUED,this.options.autoProcessQueue?setTimeout(function(a){return function(){return a.processQueue()}}(this),0):void 0},c.prototype._thumbnailQueue=[],c.prototype._processingThumbnail=!1,c.prototype._enqueueThumbnail=function(a){return this.options.createImageThumbnails&&a.type.match(/image.*/)&&a.size<=1024*this.options.maxThumbnailFilesize*1024?(this._thumbnailQueue.push(a),setTimeout(function(a){return function(){return a._processThumbnailQueue()}}(this),0)):void 0},c.prototype._processThumbnailQueue=function(){return this._processingThumbnail||0===this._thumbnailQueue.length?void 0:(this._processingThumbnail=!0,this.createThumbnail(this._thumbnailQueue.shift(),function(a){return function(){return a._processingThumbnail=!1,a._processThumbnailQueue()}}(this)))},c.prototype.removeFile=function(a){return a.status===c.UPLOADING&&this.cancelUpload(a),this.files=h(this.files,a),this.emit("removedfile",a),0===this.files.length?this.emit("reset"):void 0},c.prototype.removeAllFiles=function(a){var b,d,e,f;for(null==a&&(a=!1),f=this.files.slice(),d=0,e=f.length;e>d;d++)b=f[d],(b.status!==c.UPLOADING||a)&&this.removeFile(b);return null},c.prototype.createThumbnail=function(a,b){var c;return c=new FileReader,c.onload=function(d){return function(){var e;return"image/svg+xml"===a.type?(d.emit("thumbnail",a,c.result),void(null!=b&&b())):(e=document.createElement("img"),e.onload=function(){var c,g,h,i,j,k,l,m;return a.width=e.width,a.height=e.height,h=d.options.resize.call(d,a),null==h.trgWidth&&(h.trgWidth=h.optWidth),null==h.trgHeight&&(h.trgHeight=h.optHeight),c=document.createElement("canvas"),g=c.getContext("2d"),c.width=h.trgWidth,c.height=h.trgHeight,f(g,e,null!=(j=h.srcX)?j:0,null!=(k=h.srcY)?k:0,h.srcWidth,h.srcHeight,null!=(l=h.trgX)?l:0,null!=(m=h.trgY)?m:0,h.trgWidth,h.trgHeight),i=c.toDataURL("image/png"),d.emit("thumbnail",a,i),null!=b?b():void 0},e.onerror=b,e.src=c.result)}}(this),c.readAsDataURL(a)},c.prototype.processQueue=function(){var a,b,c,d;if(b=this.options.parallelUploads,c=this.getUploadingFiles().length,a=c,!(c>=b)&&(d=this.getQueuedFiles(),d.length>0)){if(this.options.uploadMultiple)return this.processFiles(d.slice(0,b-c));for(;b>a;){if(!d.length)return;this.processFile(d.shift()),a++}}},c.prototype.processFile=function(a){return this.processFiles([a])},c.prototype.processFiles=function(a){var b,d,e;for(d=0,e=a.length;e>d;d++)b=a[d],b.processing=!0,b.status=c.UPLOADING,this.emit("processing",b);return this.options.uploadMultiple&&this.emit("processingmultiple",a),this.uploadFiles(a)},c.prototype._getFilesWithXhr=function(a){var b,c;return c=function(){var c,d,e,f;for(e=this.files,f=[],c=0,d=e.length;d>c;c++)b=e[c],b.xhr===a&&f.push(b);return f}.call(this)},c.prototype.cancelUpload=function(a){var b,d,e,f,g,h,i;if(a.status===c.UPLOADING){for(d=this._getFilesWithXhr(a.xhr),e=0,g=d.length;g>e;e++)b=d[e],b.status=c.CANCELED;for(a.xhr.abort(),f=0,h=d.length;h>f;f++)b=d[f],this.emit("canceled",b);this.options.uploadMultiple&&this.emit("canceledmultiple",d)}else((i=a.status)===c.ADDED||i===c.QUEUED)&&(a.status=c.CANCELED,this.emit("canceled",a),this.options.uploadMultiple&&this.emit("canceledmultiple",[a]));return this.options.autoProcessQueue?this.processQueue():void 0},e=function(){var a,b;return b=arguments[0],a=2<=arguments.length?i.call(arguments,1):[],"function"==typeof b?b.apply(this,a):b},c.prototype.uploadFile=function(a){return this.uploadFiles([a])},c.prototype.uploadFiles=function(a){var b,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L;for(w=new XMLHttpRequest,x=0,B=a.length;B>x;x++)b=a[x],b.xhr=w;p=e(this.options.method,a),u=e(this.options.url,a),w.open(p,u,!0),w.withCredentials=!!this.options.withCredentials,s=null,g=function(c){return function(){var d,e,f;for(f=[],d=0,e=a.length;e>d;d++)b=a[d],f.push(c._errorProcessing(a,s||c.options.dictResponseError.replace("{{statusCode}}",w.status),w));return f}}(this),t=function(c){return function(d){var e,f,g,h,i,j,k,l,m;if(null!=d)for(f=100*d.loaded/d.total,g=0,j=a.length;j>g;g++)b=a[g],b.upload={progress:f,total:d.total,bytesSent:d.loaded};else{for(e=!0,f=100,h=0,k=a.length;k>h;h++)b=a[h],(100!==b.upload.progress||b.upload.bytesSent!==b.upload.total)&&(e=!1),b.upload.progress=f,b.upload.bytesSent=b.upload.total;if(e)return}for(m=[],i=0,l=a.length;l>i;i++)b=a[i],m.push(c.emit("uploadprogress",b,f,b.upload.bytesSent));return m}}(this),w.onload=function(b){return function(d){var e;if(a[0].status!==c.CANCELED&&4===w.readyState){if(s=w.responseText,w.getResponseHeader("content-type")&&~w.getResponseHeader("content-type").indexOf("application/json"))try{s=JSON.parse(s)}catch(f){d=f,s="Invalid JSON response from server."}return t(),200<=(e=w.status)&&300>e?b._finished(a,s,d):g()}}}(this),w.onerror=function(){return function(){return a[0].status!==c.CANCELED?g():void 0}}(this),r=null!=(G=w.upload)?G:w,r.onprogress=t,j={Accept:"application/json","Cache-Control":"no-cache","X-Requested-With":"XMLHttpRequest"},this.options.headers&&d(j,this.options.headers);for(h in j)i=j[h],w.setRequestHeader(h,i);if(f=new FormData,this.options.params){H=this.options.params;for(o in H)v=H[o],f.append(o,v)}for(y=0,C=a.length;C>y;y++)b=a[y],this.emit("sending",b,w,f);if(this.options.uploadMultiple&&this.emit("sendingmultiple",a,w,f),"FORM"===this.element.tagName)for(I=this.element.querySelectorAll("input, textarea, select, button"),z=0,D=I.length;D>z;z++)if(l=I[z],m=l.getAttribute("name"),n=l.getAttribute("type"),"SELECT"===l.tagName&&l.hasAttribute("multiple"))for(J=l.options,A=0,E=J.length;E>A;A++)q=J[A],q.selected&&f.append(m,q.value);else(!n||"checkbox"!==(K=n.toLowerCase())&&"radio"!==K||l.checked)&&f.append(m,l.value);for(k=F=0,L=a.length-1;L>=0?L>=F:F>=L;k=L>=0?++F:--F)f.append(this._getParamName(k),a[k],a[k].name);return w.send(f)},c.prototype._finished=function(a,b,d){var e,f,g;for(f=0,g=a.length;g>f;f++)e=a[f],e.status=c.SUCCESS,this.emit("success",e,b,d),this.emit("complete",e);return this.options.uploadMultiple&&(this.emit("successmultiple",a,b,d),this.emit("completemultiple",a)),this.options.autoProcessQueue?this.processQueue():void 0},c.prototype._errorProcessing=function(a,b,d){var e,f,g;for(f=0,g=a.length;g>f;f++)e=a[f],e.status=c.ERROR,this.emit("error",e,b,d),this.emit("complete",e);return this.options.uploadMultiple&&(this.emit("errormultiple",a,b,d),this.emit("completemultiple",a)),this.options.autoProcessQueue?this.processQueue():void 0},c}(b),a.version="4.0.1",a.options={},a.optionsForElement=function(b){return b.getAttribute("id")?a.options[c(b.getAttribute("id"))]:void 0},a.instances=[],a.forElement=function(a){if("string"==typeof a&&(a=document.querySelector(a)),null==(null!=a?a.dropzone:void 0))throw new Error("No Dropzone found for given element. This is probably because you're trying to access it before Dropzone had the time to initialize. Use the `init` option to setup any additional observers on your Dropzone.");return a.dropzone},a.autoDiscover=!0,a.discover=function(){var b,c,d,e,f,g;for(document.querySelectorAll?d=document.querySelectorAll(".dropzone"):(d=[],b=function(a){var b,c,e,f;for(f=[],c=0,e=a.length;e>c;c++)b=a[c],f.push(/(^| )dropzone($| )/.test(b.className)?d.push(b):void 0);return f},b(document.getElementsByTagName("div")),b(document.getElementsByTagName("form"))),g=[],e=0,f=d.length;f>e;e++)c=d[e],g.push(a.optionsForElement(c)!==!1?new a(c):void 0);return g},a.blacklistedBrowsers=[/opera.*Macintosh.*version\/12/i],a.isBrowserSupported=function(){var b,c,d,e,f;if(b=!0,window.File&&window.FileReader&&window.FileList&&window.Blob&&window.FormData&&document.querySelector)if("classList"in document.createElement("a"))for(f=a.blacklistedBrowsers,d=0,e=f.length;e>d;d++)c=f[d],c.test(navigator.userAgent)&&(b=!1);else b=!1;else b=!1;return b},h=function(a,b){var c,d,e,f;for(f=[],d=0,e=a.length;e>d;d++)c=a[d],c!==b&&f.push(c);return f},c=function(a){return a.replace(/[\-_](\w)/g,function(a){return a.charAt(1).toUpperCase()})},a.createElement=function(a){var b;return b=document.createElement("div"),b.innerHTML=a,b.childNodes[0]},a.elementInside=function(a,b){if(a===b)return!0;for(;a=a.parentNode;)if(a===b)return!0;return!1},a.getElement=function(a,b){var c;if("string"==typeof a?c=document.querySelector(a):null!=a.nodeType&&(c=a),null==c)throw new Error("Invalid `"+b+"` option provided. Please provide a CSS selector or a plain HTML element.");return c},a.getElements=function(a,b){var c,d,e,f,g,h,i,j;if(a instanceof Array){e=[];try{for(f=0,h=a.length;h>f;f++)d=a[f],e.push(this.getElement(d,b))}catch(k){c=k,e=null}}else if("string"==typeof a)for(e=[],j=document.querySelectorAll(a),g=0,i=j.length;i>g;g++)d=j[g],e.push(d);else null!=a.nodeType&&(e=[a]);if(null==e||!e.length)throw new Error("Invalid `"+b+"` option provided. Please provide a CSS selector, a plain HTML element or a list of those.");return e},a.confirm=function(a,b,c){return window.confirm(a)?b():null!=c?c():void 0},a.isValidFile=function(a,b){var c,d,e,f,g;if(!b)return!0;for(b=b.split(","),d=a.type,c=d.replace(/\/.*$/,""),f=0,g=b.length;g>f;f++)if(e=b[f],e=e.trim(),"."===e.charAt(0)){if(-1!==a.name.toLowerCase().indexOf(e.toLowerCase(),a.name.length-e.length))return!0}else if(/\/\*$/.test(e)){if(c===e.replace(/\/.*$/,""))return!0}else if(d===e)return!0;return!1},"undefined"!=typeof jQuery&&null!==jQuery&&(jQuery.fn.dropzone=function(b){return this.each(function(){return new a(this,b)})}),"undefined"!=typeof module&&null!==module?module.exports=a:window.Dropzone=a,a.ADDED="added",a.QUEUED="queued",a.ACCEPTED=a.QUEUED,a.UPLOADING="uploading",a.PROCESSING=a.UPLOADING,a.CANCELED="canceled",a.ERROR="error",a.SUCCESS="success",e=function(a){var b,c,d,e,f,g,h,i,j,k;for(h=a.naturalWidth,g=a.naturalHeight,c=document.createElement("canvas"),c.width=1,c.height=g,d=c.getContext("2d"),d.drawImage(a,0,0),e=d.getImageData(0,0,1,g).data,k=0,f=g,i=g;i>k;)b=e[4*(i-1)+3],0===b?f=i:k=i,i=f+k>>1;return j=i/g,0===j?1:j},f=function(a,b,c,d,f,g,h,i,j,k){var l;return l=e(b),a.drawImage(b,c,d,f,g,h,i,j,k/l)},d=function(a,b){var c,d,e,f,g,h,i,j,k;if(e=!1,k=!0,d=a.document,j=d.documentElement,c=d.addEventListener?"addEventListener":"attachEvent",i=d.addEventListener?"removeEventListener":"detachEvent",h=d.addEventListener?"":"on",f=function(c){return"readystatechange"!==c.type||"complete"===d.readyState?(("load"===c.type?a:d)[i](h+c.type,f,!1),!e&&(e=!0)?b.call(a,c.type||c):void 0):void 0 },g=function(){var a;try{j.doScroll("left")}catch(b){return a=b,void setTimeout(g,50)}return f("poll")},"complete"!==d.readyState){if(d.createEventObject&&j.doScroll){try{k=!a.frameElement}catch(l){}k&&g()}return d[c](h+"DOMContentLoaded",f,!1),d[c](h+"readystatechange",f,!1),a[c](h+"load",f,!1)}},a._autoDiscoverFunction=function(){return a.autoDiscover?a.discover():void 0},d(window,a._autoDiscoverFunction)}).call(this);rt-5.0.1/share/static/js/popper.min.js000644 000765 000024 00000050706 14005011336 020443 0ustar00sunnavystaff000000 000000 /* Copyright (C) Federico Zivolo 2018 Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function r(e){return 11===e?pe:10===e?se:pe||se}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),le({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=fe({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f],10),E=parseFloat(w['border'+f+'Width'],10),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},le(n,m,$(v)),le(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ge.FLIP:p=[n,i];break;case ge.CLOCKWISE:p=G(n);break;case ge.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u);(m||b||y)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),y&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=fe({},e.offsets.popper,D(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport'},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=C(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.rightwindow.devicePixelRatio||!me),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=H('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=fe({},E,e.attributes),e.styles=fe({},m,e.styles),e.arrowStyles=fe({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return j(e.instance.popper,e.styles),V(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&j(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),j(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ue}); rt-5.0.1/share/static/js/superfish.min.js000644 000765 000024 00000010577 14005011336 021150 0ustar00sunnavystaff000000 000000 /* * jQuery Superfish Menu Plugin - v1.7.10 * Copyright (c) 2018 Joel Birch * * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html */ ;!function(a,b){"use strict";var c=function(){var c={bcClass:"sf-breadcrumb",menuClass:"sf-js-enabled",anchorClass:"sf-with-ul",menuArrowClass:"sf-arrows"},d=function(){var b=/^(?![\w\W]*Windows Phone)[\w\W]*(iPhone|iPad|iPod)/i.test(navigator.userAgent);return b&&a("html").css("cursor","pointer").on("click",a.noop),b}(),e=function(){var a=document.documentElement.style;return"behavior"in a&&"fill"in a&&/iemobile/i.test(navigator.userAgent)}(),f=function(){return!!b.PointerEvent}(),g=function(a,b,d){var e,f=c.menuClass;b.cssArrows&&(f+=" "+c.menuArrowClass),e=d?"addClass":"removeClass",a[e](f)},h=function(b,d){return b.find("li."+d.pathClass).slice(0,d.pathLevels).addClass(d.hoverClass+" "+c.bcClass).filter(function(){return a(this).children(d.popUpSelector).hide().show().length}).removeClass(d.pathClass)},i=function(a,b){var d=b?"addClass":"removeClass";a.children("a")[d](c.anchorClass)},j=function(a){var b=a.css("ms-touch-action"),c=a.css("touch-action");c=c||b,c="pan-y"===c?"auto":"pan-y",a.css({"ms-touch-action":c,"touch-action":c})},k=function(a){return a.closest("."+c.menuClass)},l=function(a){return k(a).data("sfOptions")},m=function(){var b=a(this),c=l(b);clearTimeout(c.sfTimer),b.siblings().superfish("hide").end().superfish("show")},n=function(b){b.retainPath=a.inArray(this[0],b.$path)>-1,this.superfish("hide"),this.parents("."+b.hoverClass).length||(b.onIdle.call(k(this)),b.$path.length&&a.proxy(m,b.$path)())},o=function(){var b=a(this),c=l(b);clearTimeout(c.sfTimer),c.sfTimer=setTimeout(a.proxy(n,b,c),c.delay)},p=function(b){var c=a(this),d=l(c),e=c.siblings(b.data.popUpSelector);return d.onHandleTouch.call(e)===!1?this:void(e.length>0&&e.is(":hidden")&&(c.one("click.superfish",!1),"MSPointerDown"===b.type||"pointerdown"===b.type?c.trigger("focus"):a.proxy(m,c.parent("li"))()))},q=function(b,c){var g="li:has("+c.popUpSelector+")";a.fn.hoverIntent&&!c.disableHI?b.hoverIntent(m,o,g):b.on("mouseenter.superfish",g,m).on("mouseleave.superfish",g,o);var h="MSPointerDown.superfish";f&&(h="pointerdown.superfish"),d||(h+=" touchend.superfish"),e&&(h+=" mousedown.superfish"),b.on("focusin.superfish","li",m).on("focusout.superfish","li",o).on(h,"a",c,p)};return{hide:function(b){if(this.length){var c=this,d=l(c);if(!d)return this;var e=d.retainPath===!0?d.$path:"",f=c.find("li."+d.hoverClass).add(this).not(e).removeClass(d.hoverClass).children(d.popUpSelector),g=d.speedOut;if(b&&(f.show(),g=0),d.retainPath=!1,d.onBeforeHide.call(f)===!1)return this;f.stop(!0,!0).animate(d.animationOut,g,function(){var b=a(this);d.onHide.call(b)})}return this},show:function(){var a=l(this);if(!a)return this;var b=this.addClass(a.hoverClass),c=b.children(a.popUpSelector);return a.onBeforeShow.call(c)===!1?this:(c.stop(!0,!0).animate(a.animation,a.speed,function(){a.onShow.call(c)}),this)},destroy:function(){return this.each(function(){var b,d=a(this),e=d.data("sfOptions");return!!e&&(b=d.find(e.popUpSelector).parent("li"),clearTimeout(e.sfTimer),g(d,e),i(b),j(d),d.off(".superfish").off(".hoverIntent"),b.children(e.popUpSelector).attr("style",function(a,b){if("undefined"!=typeof b)return b.replace(/display[^;]+;?/g,"")}),e.$path.removeClass(e.hoverClass+" "+c.bcClass).addClass(e.pathClass),d.find("."+e.hoverClass).removeClass(e.hoverClass),e.onDestroy.call(d),void d.removeData("sfOptions"))})},init:function(b){return this.each(function(){var d=a(this);if(d.data("sfOptions"))return!1;var e=a.extend({},a.fn.superfish.defaults,b),f=d.find(e.popUpSelector).parent("li");e.$path=h(d,e),d.data("sfOptions",e),g(d,e,!0),i(f,!0),j(d),q(d,e),f.not("."+c.bcClass).superfish("hide",!0),e.onInit.call(this)})}}}();a.fn.superfish=function(b,d){return c[b]?c[b].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof b&&b?a.error("Method "+b+" does not exist on jQuery.fn.superfish"):c.init.apply(this,arguments)},a.fn.superfish.defaults={popUpSelector:"ul,.sf-mega",hoverClass:"sfHover",pathClass:"overrideThisToUse",pathLevels:1,delay:800,animation:{opacity:"show"},animationOut:{opacity:"hide"},speed:"normal",speedOut:"fast",cssArrows:!0,disableHI:!1,onInit:a.noop,onBeforeShow:a.noop,onShow:a.noop,onBeforeHide:a.noop,onHide:a.noop,onIdle:a.noop,onDestroy:a.noop,onHandleTouch:a.noop}}(jQuery,window); rt-5.0.1/share/static/js/bootstrap-combobox.js000644 000765 000024 00000034230 14005011336 022171 0ustar00sunnavystaff000000 000000 /* ============================================================= * bootstrap-combobox.js v1.2.0 * ============================================================= * Copyright 2019 Daniel Farrell * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ============================================================ */ (function( $ ) { "use strict"; /* COMBOBOX PUBLIC CLASS DEFINITION * ================================ */ var hasPopper = typeof Popper !== 'undefined'; var Combobox = function ( element, options ) { this.options = $.extend({}, $.fn.combobox.defaults, options); this.template = this.options.template || this.template this.$source = $(element); this.$container = this.setup(); this.$element = this.$container.find('input[type=text]'); this.$target = this.$container.find('input[type=hidden]'); this.$button = this.$container.find('.dropdown-toggle'); this.$menu = $(this.options.menu).appendTo('body'); this.matcher = this.options.matcher || this.matcher; this.sorter = this.options.sorter || this.sorter; this.highlighter = this.options.highlighter || this.highlighter; this.shown = false; this.selected = false; this.renderLimit = this.options.renderLimit || -1; this.clearIfNoMatch = this.options.clearIfNoMatch; this.refresh(); this.transferAttributes(); this.listen(); }; Combobox.prototype = { constructor: Combobox , setup: function () { var combobox = $(this.template()); this.$source.before(combobox); this.$source.hide(); return combobox; } , disable: function() { this.$element.prop('disabled', true); this.$button.attr('disabled', true); this.disabled = true; this.$container.addClass('combobox-disabled'); } , enable: function() { this.$element.prop('disabled', false); this.$button.attr('disabled', false); this.disabled = false; this.$container.removeClass('combobox-disabled'); } , parse: function () { var that = this , map = {} , source = [] , selected = false , selectedValue = ''; this.$source.find('option').each(function() { var option = $(this); if (option.val() === '') { that.options.placeholder = option.text(); return; } map[option.text()] = option.val(); source.push(option.text()); if (option.prop('selected')) { selected = option.text(); selectedValue = option.val(); } }) this.map = map; if (selected) { this.$element.val(selected); this.$target.val(selectedValue); this.$container.addClass('combobox-selected'); this.selected = true; } return source; } , transferAttributes: function() { this.options.placeholder = this.$source.attr('data-placeholder') || this.options.placeholder if(this.options.appendId !== undefined) { this.$element.attr('id', this.$source.attr('id') + this.options.appendId); } this.$element.attr('placeholder', this.options.placeholder) this.$target.prop('name', this.$source.prop('name')) this.$target.val(this.$source.val()) this.$source.removeAttr('name') // Remove from source otherwise form will pass parameter twice. this.$element.attr('required', this.$source.attr('required')) this.$element.attr('rel', this.$source.attr('rel')) this.$element.attr('title', this.$source.attr('title')) this.$element.attr('class', this.$source.attr('class')) this.$element.attr('tabindex', this.$source.attr('tabindex')) this.$source.removeAttr('tabindex') if (this.$source.attr('disabled')!==undefined) this.disable(); } , select: function () { var val = this.$menu.find('.active').attr('data-value'); this.$element.val(this.updater(val)).trigger('change'); this.$target.val(this.map[val]).trigger('change'); this.$source.val(this.map[val]).trigger('change'); this.$container.addClass('combobox-selected'); this.selected = true; return this.hide(); } , updater: function (item) { return item; } , show: function () { var pos = $.extend({}, this.$element.position(), { height: this.$element[0].offsetHeight }); this.$menu .insertAfter(this.$element) .css({ top: pos.top + pos.height , left: pos.left }) .show(); $('.dropdown-menu').on('mousedown', $.proxy(this.scrollSafety, this)); this.shown = true; return this; } , hide: function () { this.$menu.hide(); $('.dropdown-menu').off('mousedown', $.proxy(this.scrollSafety, this)); this.$element.on('blur', $.proxy(this.blur, this)); this.shown = false; return this; } , lookup: function (event) { this.query = this.$element.val(); return this.process(this.source); } , process: function (items) { var that = this; items = $.grep(items, function (item) { return that.matcher(item); }) items = this.sorter(items); if (!items.length) { return this.shown ? this.hide() : this; } return this.render(items.slice(0, this.options.items)).show(); } , template: function() { if (this.options.bsVersion == '2') { return '
      ' } else if (this.options.bsVersion == '3') { return '
      ' } else { return '
      ' + '' + '' + (this.options.iconCaret ? '' : '') + (this.options.iconRemove ? '' : '') + '' + '
      '; } } , matcher: function (item) { return ~item.toLowerCase().indexOf(this.query.toLowerCase()); } , sorter: function (items) { var beginswith = [] , caseSensitive = [] , caseInsensitive = [] , item; while (item = items.shift()) { if (!item.toLowerCase().indexOf(this.query.toLowerCase())) {beginswith.push(item);} else if (~item.indexOf(this.query)) {caseSensitive.push(item);} else {caseInsensitive.push(item);} } return beginswith.concat(caseSensitive, caseInsensitive); } , highlighter: function (item) { var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&'); return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) { return '' + match + ''; }) } , render: function (items) { var that = this; items = $(items).map(function (i, item) { if(~that.renderLimit && i >= that.renderLimit) return; i = $(that.options.item).attr('data-value', item); i.find('a').html(that.highlighter(item)); return i[0]; }) items.first().addClass('active'); this.$menu.html(items); return this; } , next: function (event) { var active = this.$menu.find('.active').removeClass('active') , next = active.next(); if (!next.length) { next = $(this.$menu.find('li')[0]); } next.addClass('active'); } , prev: function (event) { var active = this.$menu.find('.active').removeClass('active') , prev = active.prev(); if (!prev.length) { prev = this.$menu.find('li').last(); } prev.addClass('active'); } , toggle: function () { if (!this.disabled) { if (this.$container.hasClass('combobox-selected')) { this.clearTarget(); this.triggerChange(); this.clearElement(); } else { if (this.shown) { this.hide(); } else { this.clearElement(); this.lookup(); } } } } , scrollSafety: function(e) { if (e.target.tagName == 'UL') { this.$element.off('blur'); } } , clearElement: function () { this.$element.val('').focus(); } , clearTarget: function () { this.$source.val(''); this.$target.val(''); this.$container.removeClass('combobox-selected'); this.selected = false; } , triggerChange: function () { this.$source.trigger('change'); } , refresh: function () { this.source = this.parse(); this.options.items = this.source.length; } , listen: function () { this.$element .on('focus', $.proxy(this.focus, this)) .on('blur', $.proxy(this.blur, this)) .on('keypress', $.proxy(this.keypress, this)) .on('keyup', $.proxy(this.keyup, this)); if (this.eventSupported('keydown')) { this.$element.on('keydown', $.proxy(this.keydown, this)); } this.$menu .on('click', $.proxy(this.click, this)) .on('mouseenter', 'li', $.proxy(this.mouseenter, this)) .on('mouseleave', 'li', $.proxy(this.mouseleave, this)); this.$button .on('click', $.proxy(this.toggle, this)); } , eventSupported: function(eventName) { var isSupported = eventName in this.$element; if (!isSupported) { this.$element.setAttribute(eventName, 'return;'); isSupported = typeof this.$element[eventName] === 'function'; } return isSupported; } , move: function (e) { if (!this.shown) {return;} switch(e.keyCode) { case 9: // tab case 13: // enter case 27: // escape e.preventDefault(); break; case 38: // up arrow e.preventDefault(); this.prev(); this.fixMenuScroll(); break; case 40: // down arrow e.preventDefault(); this.next(); this.fixMenuScroll(); break; } e.stopPropagation(); } , fixMenuScroll: function(){ var active = this.$menu.find('.active'); if(active.length){ var top = active.position().top; var bottom = top + active.height(); var scrollTop = this.$menu.scrollTop(); var menuHeight = this.$menu.height(); if(bottom > menuHeight){ this.$menu.scrollTop(scrollTop + bottom - menuHeight); } else if(top < 0){ this.$menu.scrollTop(scrollTop + top); } } } , keydown: function (e) { this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27]); this.move(e); } , keypress: function (e) { if (this.suppressKeyPressRepeat) {return;} this.move(e); } , keyup: function (e) { switch(e.keyCode) { case 40: // down arrow if (!this.shown){ this.toggle(); } break; case 39: // right arrow case 38: // up arrow case 37: // left arrow case 36: // home case 35: // end case 16: // shift case 17: // ctrl case 18: // alt break; case 9: // tab case 13: // enter if (!this.shown) {return;} this.select(); break; case 27: // escape if (!this.shown) {return;} this.hide(); break; default: this.clearTarget(); this.lookup(); } e.stopPropagation(); e.preventDefault(); } , focus: function (e) { this.focused = true; } , blur: function (e) { var that = this; this.focused = false; var val = this.$element.val(); if (!this.selected && val !== '' ) { if(that.clearIfNoMatch) this.$element.val(''); this.$source.val('').trigger('change'); this.$target.val('').trigger('change'); } if (!this.mousedover && this.shown) {setTimeout(function () { that.hide(); }, 200);} } , click: function (e) { e.stopPropagation(); e.preventDefault(); this.select(); this.$element.focus(); } , mouseenter: function (e) { this.mousedover = true; this.$menu.find('.active').removeClass('active'); $(e.currentTarget).addClass('active'); } , mouseleave: function (e) { this.mousedover = false; } }; /* COMBOBOX PLUGIN DEFINITION * =========================== */ $.fn.combobox = function ( option ) { return this.each(function () { var $this = $(this) , data = $this.data('combobox') , options = typeof option == 'object' && option; if(!data) {$this.data('combobox', (data = new Combobox(this, options)));} if (typeof option == 'string') {data[option]();} }); }; $.fn.combobox.defaults = { bsVersion: '4' , menu: '' , item: '
    • ' , iconCaret: undefined , iconRemove: undefined , clearIfNoMatch: true }; $.fn.combobox.Constructor = Combobox; }( window.jQuery )); rt-5.0.1/share/static/js/i18n.js000644 000765 000024 00000000703 14005011336 017123 0ustar00sunnavystaff000000 000000 function loc_key(key) { if (arguments.length > 1 && console && console.log) console.log("loc_key() does not support substitution! (for key: " + key + ")") var msg; if (RT.I18N && RT.I18N.Catalog) msg = RT.I18N.Catalog[key]; if (msg == null && console && console.log) { console.log("I18N key '" + key + "' not found in catalog"); msg = "(no translation for key: " + key + ")"; } return msg; } rt-5.0.1/share/static/js/jquery.cookie.js000644 000765 000024 00000004307 14005011336 021137 0ustar00sunnavystaff000000 000000 /*! * jQuery Cookie Plugin v1.3.1 * https://github.com/carhartl/jquery-cookie * * Copyright 2013 Klaus Hartl * Released under the MIT license */ (function (factory) { if (typeof define === 'function' && define.amd && define.amd.jQuery) { // AMD. Register as anonymous module. define(['jquery'], factory); } else { // Browser globals. factory(jQuery); } }(function ($) { var pluses = /\+/g; function raw(s) { return s; } function decoded(s) { return decodeURIComponent(s.replace(pluses, ' ')); } function converted(s) { if (s.indexOf('"') === 0) { // This is a quoted cookie as according to RFC2068, unescape s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); } try { return config.json ? JSON.parse(s) : s; } catch(er) {} } var config = $.cookie = function (key, value, options) { // write if (value !== undefined) { options = $.extend({}, config.defaults, options); if (typeof options.expires === 'number') { var days = options.expires, t = options.expires = new Date(); t.setDate(t.getDate() + days); } value = config.json ? JSON.stringify(value) : String(value); return (document.cookie = [ encodeURIComponent(key), '=', config.raw ? value : encodeURIComponent(value), options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE options.path ? '; path=' + options.path : '', options.domain ? '; domain=' + options.domain : '', options.secure ? '; secure' : '' ].join('')); } // read var decode = config.raw ? raw : decoded; var cookies = document.cookie.split('; '); var result = key ? undefined : {}; for (var i = 0, l = cookies.length; i < l; i++) { var parts = cookies[i].split('='); var name = decode(parts.shift()); var cookie = decode(parts.join('=')); if (key && key === name) { result = converted(cookie); break; } if (!key) { result[name] = converted(cookie); } } return result; }; config.defaults = {}; $.removeCookie = function (key, options) { if ($.cookie(key) !== undefined) { $.cookie(key, '', $.extend(options, { expires: -1 })); return true; } return false; }; })); rt-5.0.1/share/static/js/jquery-ui-timepicker-addon.js000644 000765 000024 00000176271 14005011336 023531 0ustar00sunnavystaff000000 000000 /* * jQuery timepicker addon * By: Trent Richardson [http://trentrichardson.com] * Version 1.2 * Last Modified: 02/02/2013 * * Copyright 2013 Trent Richardson * You may use this project under MIT or GPL licenses. * http://trentrichardson.com/Impromptu/GPL-LICENSE.txt * http://trentrichardson.com/Impromptu/MIT-LICENSE.txt */ /*jslint evil: true, white: false, undef: false, nomen: false */ (function($) { /* * Lets not redefine timepicker, Prevent "Uncaught RangeError: Maximum call stack size exceeded" */ $.ui.timepicker = $.ui.timepicker || {}; if ($.ui.timepicker.version) { return; } /* * Extend jQueryUI, get it started with our version number */ $.extend($.ui, { timepicker: { version: "1.2" } }); /* * Timepicker 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. */ var Timepicker = function() { this.regional = []; // Available regional settings, indexed by language code this.regional[''] = { // Default regional settings currentText: 'Now', closeText: 'Done', amNames: ['AM', 'A'], pmNames: ['PM', 'P'], timeFormat: 'HH:mm', timeSuffix: '', timeOnlyTitle: 'Choose Time', timeText: 'Time', hourText: 'Hour', minuteText: 'Minute', secondText: 'Second', millisecText: 'Millisecond', timezoneText: 'Time Zone', isRTL: false }; 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: null, useLocalTimezone: false, defaultTimezone: "+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, altTimeFormat: null, altSeparator: null, altTimeSuffix: null, pickerTimeFormat: null, pickerTimeSuffix: null, showTimepicker: true, timezoneIso8601: false, timezoneList: null, addSliderAccess: false, sliderAccessArgs: null, controlType: 'slider', defaultValue: null, parse: 'strict' }; $.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: null, defaultTimezone: "+0000", hourMinOriginal: null, minuteMinOriginal: null, secondMinOriginal: null, millisecMinOriginal: null, hourMaxOriginal: null, minuteMaxOriginal: null, secondMaxOriginal: null, millisecMaxOriginal: null, ampm: '', formattedDate: '', formattedTime: '', formattedDateTime: '', timezoneList: null, units: ['hour','minute','second','millisec'], control: 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 = {}, fns = {}, overrides, i; for (var attrName in this._defaults) { if(this._defaults.hasOwnProperty(attrName)){ var attrValue = $input.attr('time:' + attrName); if (attrValue) { try { inlineSettings[attrName] = eval(attrValue); } catch (err) { inlineSettings[attrName] = attrValue; } } } } overrides = { beforeShow: function (input, dp_inst) { if ($.isFunction(tp_inst._defaults.evnts.beforeShow)) { return tp_inst._defaults.evnts.beforeShow.call($input[0], 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(tp_inst._defaults.evnts.onChangeMonthYear)) { tp_inst._defaults.evnts.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(tp_inst._defaults.evnts.onClose)) { tp_inst._defaults.evnts.onClose.call($input[0], dateText, dp_inst, tp_inst); } } }; for (i in overrides) { if (overrides.hasOwnProperty(i)) { fns[i] = o[i] || null; } } tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, o, overrides, { evnts:fns, 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(); }); // controlType is string - key to our this._controls if(typeof(tp_inst._defaults.controlType) === 'string'){ if($.fn[tp_inst._defaults.controlType] === undefined){ tp_inst._defaults.controlType = 'select'; } tp_inst.control = tp_inst._controls[tp_inst._defaults.controlType]; } // controlType is an object and must implement create, options, value methods else{ tp_inst.control = tp_inst._defaults.controlType; } if (tp_inst._defaults.timezoneList === null) { var timezoneList = ['-1200', '-1100', '-1000', '-0930', '-0900', '-0800', '-0700', '-0600', '-0500', '-0430', '-0400', '-0330', '-0300', '-0200', '-0100', '+0000', '+0100', '+0200', '+0300', '+0330', '+0400', '+0430', '+0500', '+0530', '+0545', '+0600', '+0630', '+0700', '+0800', '+0845', '+0900', '+0930', '+1000', '+1030', '+1100', '+1130', '+1200', '+1245', '+1300', '+1400']; if (tp_inst._defaults.timezoneIso8601) { timezoneList = $.map(timezoneList, function(val) { return val == '+0000' ? 'Z' : (val.substring(0, 3) + ':' + val.substring(3)); }); } tp_inst._defaults.timezoneList = timezoneList; } tp_inst.timezone = tp_inst._defaults.timezone; tp_inst.hour = tp_inst._defaults.hour < tp_inst._defaults.hourMin? tp_inst._defaults.hourMin : tp_inst._defaults.hour > tp_inst._defaults.hourMax? tp_inst._defaults.hourMax : tp_inst._defaults.hour; tp_inst.minute = tp_inst._defaults.minute < tp_inst._defaults.minuteMin? tp_inst._defaults.minuteMin : tp_inst._defaults.minute > tp_inst._defaults.minuteMax? tp_inst._defaults.minuteMax : tp_inst._defaults.minute; tp_inst.second = tp_inst._defaults.second < tp_inst._defaults.secondMin? tp_inst._defaults.secondMin : tp_inst._defaults.second > tp_inst._defaults.secondMax? tp_inst._defaults.secondMax : tp_inst._defaults.second; tp_inst.millisec = tp_inst._defaults.millisec < tp_inst._defaults.millisecMin? tp_inst._defaults.millisecMin : tp_inst._defaults.millisec > tp_inst._defaults.millisecMax? tp_inst._defaults.millisecMax : 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()); } tp_inst.$input.bind('focus', function() { tp_inst._onFocus(); }); 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) { if (!this.inst) { this.inst = $.datepicker._getInst(this.$input[0]); } if (withDate || !this._defaults.timeOnly) { var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat'); try { var parseRes = parseDateTimeInternal(dp_dateFormat, this._defaults.timeFormat, timeString, $.datepicker._getFormatConfig(this.inst), this._defaults); if (!parseRes.timeObj) { return false; } $.extend(this, parseRes.timeObj); } catch (err) { $.timepicker.log("Error parsing the date/time string: " + err + "\ndate/time string = " + timeString + "\ntimeFormat = " + this._defaults.timeFormat + "\ndateFormat = " + dp_dateFormat); return false; } return true; } else { var timeObj = $.datepicker.parseTime(this._defaults.timeFormat, timeString, this._defaults); if (!timeObj) { return false; } $.extend(this, timeObj); return true; } }, /* * generate and inject html for timepicker into ui datepicker */ _injectTimePicker: function() { var $dp = this.inst.dpDiv, o = this.inst.settings, tp_inst = this, litem = '', uitem = '', max = {}, gridSize = {}, size = null; // Prevent displaying twice if ($dp.find("div.ui-timepicker-div").length === 0 && o.showTimepicker) { var noDisplay = ' style="display:none;"', html = '
      ' + '
      ' + o.timeText + '
      ' + '
      '; // Create the markup for(var i=0,l=this.units.length; i' + o[litem +'Text'] + '' + '
      '; if (o['show'+uitem] && o[litem+'Grid'] > 0) { html += '
      '; if(litem == 'hour'){ for (var h = o[litem+'Min']; h <= max[litem]; h += parseInt(o[litem+'Grid'], 10)) { gridSize[litem]++; var tmph = $.datepicker.formatTime(useAmpm(o.pickerTimeFormat || o.timeFormat)? 'hht':'HH', {hour:h}, o); html += ''; } } else{ for (var m = o[litem+'Min']; m <= max[litem]; m += parseInt(o[litem+'Grid'], 10)) { gridSize[litem]++; html += ''; } } html += '
      ' + tmph + '' + ((m < 10) ? '0' : '') + m + '
      '; } html += '
      '; } // Timezone html += '
      ' + o.timezoneText + '
      '; html += '
      '; // Create the elements from string html += '
      '; var $tp = $(html); // if we only want time picker... if (o.timeOnly === true) { $tp.prepend('
      ' + '
      ' + o.timeOnlyTitle + '
      ' + '
      '); $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide(); } // add sliders, adjust grids, add events for(var i=0,l=tp_inst.units.length; i 0) { size = 100 * gridSize[litem] * o[litem+'Grid'] / (max[litem] - o[litem+'Min']); $tp.find('.ui_tpicker_'+litem+' table').css({ width: size + "%", marginLeft: o.isRTL? '0' : ((size / (-2 * gridSize[litem])) + "%"), marginRight: o.isRTL? ((size / (-2 * gridSize[litem])) + "%") : '0', borderCollapse: 'collapse' }).find("td").click(function(e){ var $t = $(this), h = $t.html(), n = parseInt(h.replace(/[^0-9]/g),10), ap = h.replace(/[^apm]/ig), f = $t.data('for'); // loses scope, so we use data-for if(f == 'hour'){ if(ap.indexOf('p') !== -1 && n < 12){ n += 12; } else{ if(ap.indexOf('a') !== -1 && n === 12){ n = 0; } } } tp_inst.control.value(tp_inst, tp_inst[f+'_slider'], litem, n); tp_inst._onTimeChange(); tp_inst._onSelectHandler(); }) .css({ cursor: 'pointer', width: (100 / gridSize[litem]) + '%', textAlign: 'center', overflow: 'hidden' }); } // end if grid > 0 } // end for loop // Add timezone options this.timezone_select = $tp.find('.ui_tpicker_timezone').append('').find("select"); $.fn.append.apply(this.timezone_select, $.map(o.timezoneList, function(val, idx) { return $("
      ').addClass(c&&c.position?c.position:a.jGrowl.defaults.position).appendTo(c&&c.appendTo?c.appendTo:a.jGrowl.defaults.appendTo),a("#jGrowl").jGrowl(b,c)},a.fn.jGrowl=function(b,c){if(void 0===c&&a.isPlainObject(b)&&(c=b,b=c.message),a.isFunction(this.each)){var d=arguments;return this.each(function(){void 0===a(this).data("jGrowl.instance")&&(a(this).data("jGrowl.instance",a.extend(new a.fn.jGrowl,{notifications:[],element:null,interval:null})),a(this).data("jGrowl.instance").startup(this)),a.isFunction(a(this).data("jGrowl.instance")[b])?a(this).data("jGrowl.instance")[b].apply(a(this).data("jGrowl.instance"),a.makeArray(d).slice(1)):a(this).data("jGrowl.instance").create(b,c)})}},a.extend(a.fn.jGrowl.prototype,{defaults:{pool:0,header:"",group:"",sticky:!1,position:"top-right",appendTo:"body",glue:"after",theme:"default",themeState:"highlight",corners:"10px",check:250,life:3e3,closeDuration:"normal",openDuration:"normal",easing:"swing",closer:!0,closeTemplate:"×",closerTemplate:"
      [ close all ]
      ",log:function(){},beforeOpen:function(){},afterOpen:function(){},open:function(){},beforeClose:function(){},close:function(){},click:function(){},animateOpen:{opacity:"show"},animateClose:{opacity:"hide"}},notifications:[],element:null,interval:null,create:function(b,c){var d=a.extend({},this.defaults,c);"undefined"!=typeof d.speed&&(d.openDuration=d.speed,d.closeDuration=d.speed),this.notifications.push({message:b,options:d}),d.log.apply(this.element,[this.element,b,d])},render:function(b){var c=this,d=b.message,e=b.options;e.themeState=""===e.themeState?"":"ui-state-"+e.themeState;var f=a("
      ").addClass("jGrowl-notification alert "+e.themeState+" ui-corner-all"+(void 0!==e.group&&""!==e.group?" "+e.group:"")).append(a("
      % #Manually flush the content buffer after each titlebox is displayed % $m->flush_buffer(); <%ARGS> $title => undef $content => undef rt-5.0.1/share/html/Widgets/Form/000755 000765 000024 00000000000 14005011336 017360 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Widgets/SavedSearch000644 000765 000024 00000016441 14005011336 020576 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%method new> <%init> return \%ARGS; <%method process> <%init> my @actions; $self->{SearchId} ||= $args->{'SavedChartSearchId'} || 'new'; my $SearchParams = { map { $_ => $args->{$_} } @{$self->{SearchFields}} }; if ( my ( $container_object, $search_id ) = _parse_saved_search( $args->{'SavedSearchLoad'} || $args->{'SavedChartSearchId'} ) ) { my $search = RT::Attribute->new( $session{'CurrentUser'} ); $search->Load($search_id); # We have a $search and now; import the others $self->{SearchId} = $args->{'SavedSearchLoad'} || $args->{'SavedChartSearchId'}; $self->{CurrentSearch}{Object} = $search; if ( $args->{'SavedSearchLoad'} ) { for ( @{ $self->{SearchFields} } ) { $args->{$_} = $search->SubValue($_) } } $args->{SavedChartSearchId} = $args->{'SavedSearchLoad'} if $args->{'SavedSearchLoad'}; # saved search in /Search/Chart.html is different from /Search/Build.html # the former is of type 'Chart', while the latter is of type 'Ticket'. # After loading a saved search from the former after loading one from the # latter, accessing /Search/Build.html will still show the old one, so we # need to delete $session{CurrentSearchHash} to let it not show the old one. # of course, the new one should not be shown there either because it's of # different type delete $session{'CurrentSearchHash'}; } # look for the current one in the available saved searches if ($self->{SearchId} eq 'new') { my @Objects = RT::SavedSearch->new( $session{CurrentUser} )->ObjectsForLoading; push @Objects, RT::System->new($session{'CurrentUser'}) if $session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'SuperUser' ); for my $obj (@Objects) { for ( $m->comp( "/Search/Elements/SearchesForObject", Object => $obj ) ) { my ( $desc, $loc_desc, $search ) = @$_; use Data::Dumper; # FFS local $Data::Dumper::Sortkeys = 1; if ( Dumper( $search->Content ) eq Dumper( { %$SearchParams, SearchType => $self->{SearchType} } ) ) { $self->{CurrentSearch}{Object} = $search; $self->{SearchId} = $search->Id; } } } } if ( $args->{SavedSearchSave} ) { if ( my $search = $self->{CurrentSearch}{Object} ) { # rename $search->SetDescription( $args->{SavedSearchDescription} ); $search->SetSubValues(%$SearchParams); push @actions, loc( '[_1] [_2] updated.', loc($self->{SearchType}), $args->{SavedSearchDescription} ); } else { # new saved search $SearchParams->{$_} //= $defaults->{$_} for @{$self->{SearchFields}}; my $saved_search = RT::SavedSearch->new( $session{'CurrentUser'} ); my ( $ok, $search_msg ) = $saved_search->Save( Privacy => $args->{'SavedSearchOwner'}, Name => $args->{'SavedSearchDescription'}, Type => $self->{'SearchType'}, SearchParams => $SearchParams ); if ($ok) { $self->{CurrentSearch}{Object} = $saved_search->{Attribute}; $self->{SearchId} = $args->{SavedChartSearchId} = 'RT::User-' . $session{CurrentUser}->id . '-SavedSearch-' . $saved_search->Id; push @actions, loc( '[_1] [_2] saved.', loc($self->{SearchType}), $args->{SavedSearchDescription} ); } else { push @actions, [ loc("Can't save [_1]", loc($self->{SearchType})) . ': ' . loc($search_msg), 0 ]; } } } if ( $args->{SavedSearchDelete} && $self->{CurrentSearch}{Object} ) { my ($ok, $msg) = $self->{CurrentSearch}{Object}->Delete; push @actions, $ok ? loc( '[_1] [_2] deleted.', loc($self->{SearchType}), $self->{CurrentSearch}{Object}->Description ) : $msg; delete $self->{CurrentSearch}{Object}; delete $self->{SearchId}; delete $args->{SavedChartSearchId}; } $self->{CurrentSearch}{Description} = $self->{CurrentSearch}{Object}->Description if $self->{CurrentSearch}{Object}; return @actions; <%ARGS> $self $args $defaults => {} <%method show>
      <& /Search/Elements/EditSearches, Id => $self->{SearchId} || 'new', Type => $self->{SearchType}, CurrentSearch => $self->{CurrentSearch}, Title => $Title, AllowCopy => 0, $self->{CurrentSearch}{Object} ? ( Object => $self->{CurrentSearch}{Object}, Description => $self->{CurrentSearch}{Object}->Description, ) : (), &>
      <%PERL> foreach my $field ( @{$self->{SearchFields}} ) { if ( ref($ARGS{$field}) && ref($ARGS{$field}) ne 'ARRAY' ) { $RT::Logger->error("Couldn't store '$field'. it's reference to ". ref($ARGS{$field}) ); next; } foreach my $value ( grep defined, ref($ARGS{$field})? @{ $ARGS{$field} } : $ARGS{$field} ) { % } % }
      <%ARGS> $self => undef $Action => '' $Title => loc('Saved searches') <%init> rt-5.0.1/share/html/Widgets/BulkEdit000644 000765 000024 00000004765 14005011336 020117 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % for my $type ( @$Types ) { <& $Meta->{$type}{'Widget'}, Default => $Default, %{ $m->comp('/Widgets/FinalizeWidgetArguments', WidgetArguments => $Meta->{$type}{'WidgetArguments'} ) }, Name => $type, exists $CurrentValue->{$type} ? ( CurrentValue => $CurrentValue->{$type} ) : (), exists $DefaultValue->{$type} ? ( DefaultValue => $DefaultValue->{$type} ) : (), &> % } <%args> $Types $Meta $Default => 0 $CurrentValue => {} $DefaultValue => {} rt-5.0.1/share/html/Widgets/Spinner000644 000765 000024 00000004274 14005011336 020025 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <% loc( "Loading...") %>
      rt-5.0.1/share/html/Widgets/ComboBox000644 000765 000024 00000005016 14005011336 020112 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%once> # the $z_index here is to fix wrong z-index bug in ie7 my $z_index = 9999;
      <%ARGS> $Name $Size => undef $Rows => 5 $Default => '' @Values => () $Class => '' rt-5.0.1/share/html/Widgets/SelectionBox000644 000765 000024 00000017322 14005011336 021003 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# The SelectionBox Widget %# %# SYNOPSIS %# %# <%init>: %# my $sel = $m->comp ('/Widgets/SelectionBox:new', %# Action => me.html', %# Name => 'my-selection', %# Available => \@items, %# # you can do things with @{$sel->{Current}} in the %# # OnSubmit callback %# OnSubmit => sub { my $sel = shift; }, %# Selected => \@selected); %# %# $m->comp ('/Widgets/SelectionBox:process', %ARGS, self => $sel) %# %# where @items is an arrayref, each element is [value, label], %# and @selected is an arrayref of selected values from @items. %# %# and in html: %# <& /Widgets/SelectionBox:show, self => $sel &> %# %# if the SelectionBox is created with AutoSave option, OnSubmit will be called %# on every button clicked <%method new> <%init> $ARGS{_item_map} = {map {$_->[0] => $_->[1]} @{$ARGS{Available}}}; return \%ARGS; <%method process> <%init> unless ($ARGS{$self->{Name}.'-Submit'}) { # init $self->{Current} = $self->{Selected}; $self->{Selected} = []; return; } $self->{Selected} = $ARGS{$self->{Name}.'-Selected'}; if ($self->{Selected} && !ref($self->{Selected})) { $self->{Selected} = [$self->{Selected}]; } my $current = $self->{Current} = $ARGS{$self->{Name}.'-Current'}; if ($current && !ref ($current)) { $current = [$current]; } unless ($self->{ReadOnly}) { ++$self->{Modified}; if ($ARGS{add}) { my $choosed = $ARGS{$self->{Name}.'-Available'}; for my $add (ref($choosed) ? @$choosed : $choosed) { next if grep { $_ eq $add } @$current; push @$current, $add; } } if ($ARGS{remove}) { my $choosed = $ARGS{$self->{Name}.'-Selected'}; for my $del (ref($choosed) ? @$choosed : $choosed) { @$current = map { $_ eq $del ? () : $_ } @$current; } } if ($ARGS{moveup} or $ARGS{movedown}) { my $offset = $ARGS{moveup} ? 1 : 0; my $choosed = $ARGS{$self->{Name}.'-Selected'}; $choosed = [$choosed] unless ref ($choosed); my $canmove = 0; # not in the cornor for my $i ($ARGS{moveup} ? 0..$#{$current} : reverse 0..$#{$current}) { if (grep {$_ eq $current->[$i]} @$choosed) { if ($canmove) { splice (@$current, $i-$offset, 2, @{$current}[$i+1-$offset,$i-$offset]); } } else { ++$canmove; } } } if ($ARGS{clear}) { $current = []; } $self->{Current} = $current; } @{$self->{Current}} = grep { exists $self->{_item_map}{$_} } @{$self->{Current}}; if ($self->{AutoSave} or $ARGS{$self->{Name}.'-Save'}) { $self->{OnSubmit}->($self); delete $self->{Modified}; } <%ARGS> $self => undef <%method current> % for (@{$self->{Current}}) { % } <%INIT> <%ARGS> $self => undef <%method show>
      <& SelectionBox:current, self => $self &>
      <&|/l&>Available:
      % unless ($self->{ReadOnly}) {
      % }
      % unless ($self->{'ReadOnly'}) {
      % unless ($ARGS{'NoArrows'}) { % } % if ($ARGS{'Clear'}) { % } % if ( $ARGS{'ShowUpdate'} ) { % }
      % }
      % my $caption = ""; % unless ($self->{'AutoSave'}) { % if ($self->{Modified}) { % $caption = loc('Selections modified. Please save your changes'); % }
      <& /Elements/Submit, Caption => loc($caption), Label => loc('Save'), Name => $name.'-Save' &>
      % }
      <%ARGS> $self => undef $size => 10 <%INIT> my $name = $self->{Name}; my %selected = map {$_ => 1} @{$self->{Selected}}; rt-5.0.1/share/html/Widgets/TitleBox000644 000765 000024 00000004452 14005011336 020137 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      % if ($hide_chrome) { <% $content | n %> % } else { <& TitleBoxStart, %ARGS &><% $content | n %><& TitleBoxEnd &> % }
      <%ARGS> $class => '' $hide_empty => 0 $hide_chrome => 0 <%INIT> my $content = $m->content; return if $hide_empty && $content =~ /^\s*$/s; rt-5.0.1/share/html/Widgets/FinalizeWidgetArguments000644 000765 000024 00000004775 14005011336 023210 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> my %args = %$WidgetArguments; %args = (%args, %{ $args{Callback}->() }) if $args{Callback}; $args{'Description'} = loc( $args{'Description'} ) if $args{'Description'}; $args{'Hints'} = loc( $args{'Hints'} ) if $args{'Hints'}; if ( $args{'ValuesLabel'} ) { my %labels; $labels{$_} = loc( $args{'ValuesLabel'}->{$_} ) for keys %{$args{'ValuesLabel'}}; $args{'ValuesLabel'} = \%labels; } return \%args; <%args> $WidgetArguments => {} rt-5.0.1/share/html/Widgets/BulkProcess000644 000765 000024 00000005036 14005011336 020640 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> for my $type ( @$Types ) { my $value = $m->comp( $Meta->{$type}{Widget} . ':Process', Name => $type, Arguments => $Arguments, Default => $Default, DefaultValue => $DefaultValue->{$type}, %{ $Meta->{$type}{WidgetArguments} }, ); unless ( $KeepUndef || defined $value ) { delete $Store->{$type}; } else { $Store->{$type} = $value; } } <%args> $Arguments $Types $Store $Meta => {} $KeepUndef => 0 $Default => 0 $DefaultValue => {} rt-5.0.1/share/html/Widgets/SearchSelection000644 000765 000024 00000007637 14005011336 021470 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      % if ( @filters ) {
      % }
      % for my $section (@sections) { % my $label = $section->{label}; % my $items = $section->{items};

      <% $label | n %>

        % for my $item (sort {$a->{'label'} cmp $b->{'label'}} @$items) { <& /Elements/ShowSelectSearch, %$item &> % }
      % }
      % for my $pane (sort keys %pane_name) {

      <% $pane_name{$pane} %>

        % for my $item (@{ $selected{$pane} }) { <& /Elements/ShowSelectSearch, %$item &> % }
      % }
      <%INIT> use utf8; $m->callback( CallbackName => 'Default', sections => \@sections, selected => \%selected, filters => \@filters, ); <%ARGS> %pane_name @filters @sections %selected rt-5.0.1/share/html/Widgets/TitleBoxStart000644 000765 000024 00000007430 14005011336 021154 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <% $rolledup ? " rolled-up" : ""%>" id="<% $id %>"\ % for my $key (keys %$data) { data-<% $key %>="<% $data->{$key} %>"\ % } >
      "> % if ($hideable) { " data-toggle="collapse" data-target="#<%$tid|n%>" title="<% loc('Toggle visibility') %>"> % } <% $title_href ? qq[] : '' | n %><% $title %><% $title_raw |n %><% $title_href ? "" : '' |n%> \ <% $titleright_href ? qq[] : '' | n %>\ <% $titleright %><% $titleright_raw |n%><% $titleright_href ? "" : '' |n%>\
      <% " $content_class" || '' %>" id="<% $tid %>">
      <%ARGS> $class => '' $bodyclass => '' $title_href => '' $title => '' $title_raw => '' $title_class => '' $titleright_href => '' $titleright => '' $titleright_raw => '' $id => '' $hideable => 1 $rolledup => 0 $data => {} $content_class => '' <%init> $hideable = 1 if $rolledup; # # This should be pretty bulletproof # my $page = $m->request_comp->path; my $title_b64 = MIME::Base64::encode_base64(Encode::encode( "UTF-8", $title), ''); my $tid = "TitleBox--$page--" . join '--', ($class, $bodyclass, $title_b64, $id); # Replace anything that ISN'T alphanumeric, a hyphen, or an underscore $tid =~ s{[^A-Za-z0-9\-_]}{_}g; my $i = 0; $i++ while $m->notes("$tid-$i"); $m->notes("$tid-$i" => 1); $tid = "$tid-$i"; rt-5.0.1/share/html/Widgets/Form/MultilineString000644 000765 000024 00000006430 14005011336 022437 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%DOC> see docs/extending/using_forms_widgets.pod
      % if( $LabelLink ) { <% $Description %> % } else { <% $Description %> % }
      <& SELF:InputOnly, %ARGS &> % if ( $Default ) { <% $DefaultLabel %> % } <% $Hints %>
      <%ARGS> $Name $Class => '' $Description => undef, $Hints => '' $CurrentValue => '', $Default => 0, $DefaultValue => '', $DefaultLabel => loc( 'Default: [_1]', $DefaultValue ), $LabelLink => '' $LabelCols => 3 $ValueCols => 9 <%METHOD InputOnly> <%ARGS> $Name $Cols => 80 $Rows => 6 $CurrentValue => '', <%METHOD Process> <%ARGS> $Name $Arguments => {}, $Default => 0, $DefaultValue => '', <%INIT> my $value = $Arguments->{ $Name }; $value = '' unless defined $value; if ( $value eq '' ) { return $DefaultValue unless $Default; return undef; } return $value; rt-5.0.1/share/html/Widgets/Form/Code000644 000765 000024 00000004414 14005011336 020160 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%DOC> see docs/extending/using_forms_widgets.pod <& /Widgets/Form/MultilineString, Class => 'code', %ARGS &> <%METHOD InputOnly> <& /Widgets/Form/MultilineString:InputOnly, %ARGS &> <%METHOD Process> <& /Widgets/Form/MultilineString:Process, %ARGS &> rt-5.0.1/share/html/Widgets/Form/CustomDateRanges000644 000765 000024 00000006343 14005011336 022521 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%DOC> see docs/extending/using_forms_widgets.pod This is a special widget only for custom date ranges, it obeys the InputOnly/Process basic structure but no general arguments like Name, Default, etc. <& SELF:InputOnly, %ARGS &> <%METHOD InputOnly> <&|/Widgets/TitleBox, title => loc('Custom Date Ranges In Config Files'), class => 'mx-auto max-width-xl' &> % if ( $config && keys %{$config->{'RT::Ticket'}} ) { <& /Elements/ShowCustomDateRanges, CustomDateRanges => $config->{'RT::Ticket'} || {}, ObjectType => 'RT::Ticket' &> % } % else {

      <&|/l&>No custom date ranges in config files

      % } <&|/Widgets/TitleBox, title => loc('Custom Date Ranges'), class => 'mx-auto max-width-xl' &> <& /Elements/EditCustomDateRanges, CustomDateRanges => $content->{'RT::Ticket'} || {}, ObjectType => 'RT::Ticket' &> <%INIT> my $config = RT->Config->Get('CustomDateRanges'); my $db_config = RT::Configuration->new( $session{CurrentUser} ); $db_config->LoadByCols( Name => 'CustomDateRangesUI', Disabled => 0 ); my $content; $content = $db_config->_DeserializeContent( $db_config->Content ) if $db_config->id; <%METHOD Process> rt-5.0.1/share/html/Widgets/Form/JSON000644 000765 000024 00000004414 14005011336 020057 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%DOC> see docs/extending/using_forms_widgets.pod <& /Widgets/Form/MultilineString, Class => 'json', %ARGS &> <%METHOD InputOnly> <& /Widgets/Form/MultilineString:InputOnly, %ARGS &> <%METHOD Process> <& /Widgets/Form/MultilineString:Process, %ARGS &> rt-5.0.1/share/html/Widgets/Form/Boolean000644 000765 000024 00000011712 14005011336 020664 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%DOC> see docs/extending/using_forms_widgets.pod
      % if( $LabelLink ) { <% $Description %> % } else { <% $Description %> % }
      <& SELF:InputOnly, %ARGS &> <% $Hints %>
      <%ARGS> $Name => undef, $Description => undef, $Hints => '' $LabelLink => '' $LabelCols => 3 $ValueCols => 9 <%METHOD InputOnly> <%ARGS> $Name => undef, $Default => 0, $DefaultValue => 0, $DefaultLabel => loc( 'Use default ([_1])', $DefaultValue? loc('Yes'): loc('No') ), $RadioStyle => 0 $CurrentValue => undef, % if ( !$Default && !$RadioStyle ) { \
      >
      % } else {
      >
      % if ($Default) {
      >
      >
      % } else {
      >
      % }
      % } <%METHOD Process> <%ARGS> $Name $Arguments => {}, $Default => 0, $DefaultValue => 0, <%INIT> my $value = $Arguments->{ $Name }; if ( $Default ) { return undef if !defined $value || $value eq '__empty_value__'; return $value? 1: 0; } else { return $value? 1: 0 if defined $value; return $DefaultValue; } rt-5.0.1/share/html/Widgets/Form/Integer000644 000765 000024 00000006576 14005011336 020716 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%DOC> see docs/extending/using_forms_widgets.pod
      % if( $LabelLink ) { <% $Description %> % } else { <% $Description %> % }
      <& SELF:InputOnly, %ARGS &> % if ( $Default ) { <% $DefaultLabel %> % } <% $Hints %>
      <%INIT> $_ = '' foreach grep !defined, $CurrentValue, $DefaultValue; $DefaultLabel ||= loc( 'Default: [_1]', $DefaultValue ); <%ARGS> $Name $Description => undef, $Hints => '' $CurrentValue => '', $Default => 0, $DefaultValue => 0, $DefaultLabel => undef $LabelLink => '' $LabelCols => 3 $ValueCols => 9 <%METHOD InputOnly> \ <%ARGS> $Name $CurrentValue => '', $Size => 20 <%INIT> $CurrentValue = '' unless defined $CurrentValue; <%METHOD Process> <%ARGS> $Name $Arguments => {}, $Default => 0, $DefaultValue => '', <%INIT> my $value = $Arguments->{ $Name }; if ( !defined $value || $value eq '' ) { return $DefaultValue unless $Default; return undef; } return $value; rt-5.0.1/share/html/Widgets/Form/String000644 000765 000024 00000007030 14005011336 020551 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%DOC> see docs/extending/using_forms_widgets.pod
      % if( $LabelLink ) { <% $Description %> % } else { <% $Description // '' %> % }
      <& SELF:InputOnly, %ARGS &> % if ( $Default ) { <% $DefaultLabel %> % } <% $Hints %>
      <%ARGS> $Name $Description => undef, $Hints => '' $CurrentValue => '', $Default => 0, $DefaultValue => '', $DefaultLabel => loc( 'Default: [_1]', $DefaultValue ), $LabelLink => '', $LabelCols => 3 $ValueCols => 9 <%METHOD InputOnly> \ <%ARGS> $Name $CurrentValue => '', $Type => 'text' $Size => 20 <%METHOD Process> <%ARGS> $Name $Arguments => {}, $Default => 0, $DefaultValue => '', <%INIT> my $value = $Arguments->{ $Name }; $value = '' unless defined $value; # canonicalize: delete leading and trailing spaces, replaces newlines # with one space and then replace all continuouse spaces with one ' ' # (including tabs and other fancy things) $value =~ s/^\s+//; $value =~ s/\s+$//; $value =~ s/\r*\n/ /g; $value =~ s/\s+$/ /g; if ( $value eq '' ) { return $DefaultValue unless $Default; return undef; } return $value; rt-5.0.1/share/html/Widgets/Form/Select000644 000765 000024 00000012244 14005011336 020525 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%DOC> see docs/extending/using_forms_widgets.pod
      % if( $LabelLink ) { <% $Description %> % } else { <% $Description %> % }
      <& SELF:InputOnly, %ARGS &> <% $Hints %>
      <%ARGS> $Name $Description => undef, $Hints => '' $LabelLink => '' $LabelCols => 3 $ValueCols => 9 <%METHOD InputOnly> <%ARGS> $Name $Description => undef, @Values => (), $ValuesCallback => undef, %ValuesLabel => (), @CurrentValue => (), $Default => 1, @DefaultValue => (), $DefaultLabel => undef, $Alternative => 0, $AlternativeLabel => loc('other...'), $Multiple => 0, % if ( $Alternative ) { % } <%INIT> my %CurrentValue = map {$_ => 1} grep defined, @CurrentValue; if ( $ValuesCallback ) { my $values = $ValuesCallback->( CurrentUser => $session{'CurrentUser'}, Name => $Name, ); if ( ref $values eq 'ARRAY' ) { @Values = @$values; } else { %ValuesLabel = %$values; @Values = keys %ValuesLabel; } } unless (defined $DefaultLabel ) { $DefaultLabel = loc('Use system default ([_1])', join ', ', map loc(ref($ValuesLabel{$_}) ? @{ $ValuesLabel{$_ }} : $ValuesLabel{$_} || $_), grep defined, @DefaultValue ); } <%METHOD Process> <%ARGS> $Name $Arguments => {}, @Values => (), %ValuesLabel => (), $Default => 0, @DefaultValue => (), $Alternative => 0, $Multiple => 0, <%INIT> my $value = $Arguments->{ $Name }; if( !defined $value || $value eq '__empty_value__' ) { return undef if $Default; return [ @DefaultValue ] if $Multiple; return $DefaultValue[0]; } $value = [$value] unless ref $value; if ( $Alternative ) { my $alt = $Arguments->{ "Alternative-". $Name }; if( $Multiple ) { push @$value, split /\s*,\s*/, $alt; } else { push @$value, $alt; } } splice @$value, 1 unless $Multiple; # XXX: check values return $value->[0] unless $Multiple; return $value; rt-5.0.1/share/html/REST/1.0/000755 000765 000024 00000000000 14005011336 016122 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/REST/1.0/Forms/000755 000765 000024 00000000000 14005011336 017210 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/REST/1.0/NoAuth/000755 000765 000024 00000000000 14005011336 017320 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/REST/1.0/dhandler000644 000765 000024 00000025010 14005011336 017624 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/dhandler %# <%ARGS> @id => () $fields => undef $format => undef $content => undef <%INIT> use RT::Interface::REST; my $output = ""; my $status = "200 Ok"; my $object = $m->dhandler_arg; my $name = qr{[\w.-]+}; my $list = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+'; my $label = '[^,\\/]+'; my $field = RT::Interface::REST->field_spec; my $labels = "(?:$label,)*$label"; # We must handle requests such as the following: # # 1. http://.../REST/1.0/show (with a list of object specifications). # 2. http://.../REST/1.0/edit (with a self-contained list of forms). # 3. http://.../REST/1.0/ticket/show (implicit type specification). # http://.../REST/1.0/ticket/edit # 4. http://.../REST/1.0/ticket/nn (all possibly with a single form). # http://.../REST/1.0/ticket/nn/history # http://.../REST/1.0/ticket/nn/comment # http://.../REST/1.0/ticket/nn/attachment/1 # # Objects are specified by their type, and either a unique numeric ID, # or a unique name (e.g. ticket/1, queue/foo). Multiple objects of the # same type may be specified by a comma-separated list of identifiers # (e.g., user/ams,rai or ticket/1-3,5-7). # # Ultimately, we want a list of object specifications to operate upon. # The URLs in (4) provide enough information to identify an object. We # will assemble submitted information into that format in other cases. # my (@objects, $forms); my $utype; if ($object eq 'show' || # $REST/show (($utype) = ($object =~ m{^($name)/show$}))) # $REST/ticket/show { # We'll convert type/range specifications ("ticket/1-3,7-9/history") # into a list of singular object specifications ("ticket/1/history"). # If the URL specifies a type, we'll accept only that one. foreach my $id (@id) { $id =~ s|^(?:$utype/)?|$utype/| if $utype; if (my ($type, $oids, $extra) = ($id =~ m#^($name)/($list|$labels)(?:(/.*))?$#o)) { $extra ||= ''; my ($attr, $args) = $extra =~ m{^(?:/($name)(?:/(.*))?)?$}o; my $tids; if ($attr and $attr eq 'history' and $args) { ($tids) = $args =~ m#id/(\d.*)#o; } # expand transaction and attachment range specifications # (if applicable) foreach my $oid (expand_list($oids)) { if ($tids) { push(@objects, "$type/$oid/$attr/id/$_") for expand_list($tids); } else { push(@objects, "$type/$oid$extra"); } } } else { $status = "400 Bad Request"; $output = "Invalid object ID specified: '$id'"; goto OUTPUT; } } } elsif ($object eq 'edit' || # $REST/edit (($utype) = ($object =~ m{^($name)/edit$}))) # $REST/ticket/edit { # We'll make sure each of the submitted forms is syntactically valid # and sufficiently identifies an object to operate upon, then add to # the object list as above. my @output; $forms = form_parse($content); foreach my $form (@$forms) { my ($c, $o, $k, $e) = @$form; if ($e) { push @output, [ "# Syntax error.", $o, $k, $e ]; } else { my ($type, $id); # Look for matching types in the ID, form, and URL. $type = $utype || $k->{id}; $type =~ s|^([^/]+)/\d+$|$1| if !$utype; $type =~ s|^(?:$utype)?|$utype/| if $utype; $type =~ s|/$|| if $type; if (exists $k->{id}) { $id = $k->{id}; $id =~ s|^(?:$type/)?|$type/| if $type; if ($id =~ m#^$name/(?:$label|\d+)(?:/.*)?#o) { push @objects, $id; } else { push @output, [ "# Invalid object ID: '$id'", $o, $k, $e ]; } } else { push @output, [ "# No object ID specified.", $o, $k, $e ]; } } } # If we saw any errors at this stage, we won't process any part of # the submitted data. if (@output) { unshift @output, [ "# Please resubmit with errors corrected." ]; $status = "409 Syntax Error"; $output = form_compose(\@output); goto OUTPUT; } } else { # We'll assume that this is in the correct format already. Otherwise # it will be caught by the loop below. push @objects, $object; if ($content) { $forms = form_parse($content); if (@$forms > 1) { $status = "400 Bad Request"; $output = "You may submit only one form to this object."; goto OUTPUT; } my ($c, $o, $k, $e) = @{ $forms->[0] }; if ($e) { $status = "409 Syntax Error"; $output = form_compose([ ["# Syntax error.", $o, $k, $e] ]); goto OUTPUT; } } } # Make sure we have something to do. unless (@objects) { $status = "400 Bad Request"; $output = "No objects specified."; goto OUTPUT; } # Parse and validate any field specifications. my (%fields, @fields); if ($fields) { unless ($fields =~ /^(?:$field,)*$field$/) { $status = "400 Bad Request"; $output = "Invalid field specification: $fields"; goto OUTPUT; } @fields = map lc, split /\s*,\s*/, $fields; @fields{@fields} = (); unless (exists $fields{id}) { unshift @fields, "id"; $fields{id} = (); } # canonicalize cf-foo to cf.{foo} for my $field (@fields) { if ($field =~ /^(c(?:ustom)?f(?:ield)?)-(.+)/) { $fields{"cf.{$2}"} = delete $fields{"$1-$2"}; # overwrite the element in @fields $field = "cf.{$2}"; } } } my (@comments, @output); foreach $object (@objects) { my ($handler, $type, $id, $attr, $args); my ($c, $o, $k, $e) = ("", ["id"], {id => $object}, 0); my $i = 0; if ($object =~ m{^($name)/(\d+|$label)(?:/($name)(?:/(.*))?)?$}o || $object =~ m{^($name)/(new)$}o) { ($type, $id, $attr, $args) = ($1, $2, ($3 || 'default'), $4); $handler = "Forms/$type/$attr"; unless ($m->comp_exists($handler)) { $args = defined $args ? "$attr/$args" : $attr; $handler = "Forms/$type/default"; unless ($m->comp_exists($handler)) { $i = 2; $c = "# Unknown object type: $type"; } } elsif ($id ne 'new' && $id !~ /^\d+$/) { my $ns = "Forms/$type/ns"; # Can we resolve named objects? unless ($m->comp_exists($ns)) { $i = 3; $c = "# Objects of type $type must be specified by numeric id."; } else { my ($n, $s) = $m->comp("Forms/$type/ns", id => $id); if ($n <= 0) { $i = 4; $c = "# $s"; } else { $i = 0; $id = $n; } } } else { $i = 0; } } else { $i = 1; $c = "# Invalid object specification: '$object'"; } if ($i != 0) { if ($content) { (undef, $o, $k, $e) = @{ shift @$forms }; } push @output, [ $c, $o, $k ]; next; } unless ($content) { my $d = $m->comp($handler, id => $id, args => $args, format => $format, fields => \%fields); my ($c, $o, $k, $e) = @$d; if (!$e && @$o && keys %fields) { my %lk = map { lc $_ => $_ } keys %$k; @$o = map { $lk{$_} } @fields; foreach my $key (keys %$k) { delete $k->{$key} unless exists $fields{lc $key}; } } push(@output, [ $c, $o, $k ]) if ($c || @$o || keys %$k); } else { my ($c, $o, $k, $e) = @{ shift @$forms }; my $d = $m->comp($handler, id => $id, args => $args, format => $format, changes => $k); ($c, $o, $k, $e) = @$d; # We won't pass $e through to compose, trusting instead that the # handler added suitable comments for the user. if ($e) { if (@$o) { $status = "409 Syntax Error"; } else { $status = "400 Bad Request"; } push @output, [ $c, $o, $k ]; } else { push @comments, $c; } } } unshift(@output, [ join "\n", @comments ]) if @comments; $output = form_compose(\@output); OUTPUT: $m->out("RT/".$RT::VERSION ." ".$status ."\n\n$output\n") if ($output || $status !~ /^200/); return; rt-5.0.1/share/html/REST/1.0/autohandler000644 000765 000024 00000004236 14005011336 020360 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/autohandler %# <%INIT> use RT::Interface::REST; $r->content_type('text/plain; charset=utf-8'); $m->error_format('text'); $m->call_next(); $m->abort(); rt-5.0.1/share/html/REST/1.0/logout000644 000765 000024 00000004234 14005011336 017361 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%PERL> if (keys %session) { RT::Interface::Web::InstantiateNewSession(); $session{CurrentUser} = RT::CurrentUser->new(); } RT/<% $RT::VERSION %> 200 Ok rt-5.0.1/share/html/REST/1.0/search/000755 000765 000024 00000000000 14005011336 017367 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/REST/1.0/ticket/000755 000765 000024 00000000000 14005011336 017405 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/REST/1.0/ticket/merge000644 000765 000024 00000006225 14005011336 020434 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/ticket/merge %# <%ARGS> $id => undef $into <%INIT> use RT::Interface::REST; my $output; my $status = "200 Ok"; my $ticket = RT::Ticket->new($session{CurrentUser}); my $object = $r->path_info; # http://.../REST/1.0/ticket/merge/1 $object =~ s#^/##; if ($id && $object && $id != $object) { $output = "Different ids in URL (`$object') and submitted form (`$id').\n"; $status = "400 Bad Request"; goto OUTPUT; } $id ||= $object; unless ($id =~ /^\d+$/ && $into =~ /^\d+$/) { my $bad = ($id !~ /^\d+$/) ? $id : $into; $output = $r->path_info. "\n"; $output .= "Invalid ticket id: `$bad'.\n"; $status = "400 Bad Request"; goto OUTPUT; } $ticket->Load($id); unless ($ticket->Id) { $output = "Couldn't load ticket id: `$id'.\n"; $status = "404 Ticket not found"; goto OUTPUT; } unless ($ticket->CurrentUserHasRight('ModifyTicket')) { $output = "You are not allowed to modify ticket $id.\n"; $status = "403 Permission denied"; goto OUTPUT; } my ($n, $s) = $ticket->MergeInto($into); if ($n == 0) { $status = "500 Error"; } $output = $s; OUTPUT: RT/<% $RT::VERSION %> <% $status %> <% $output |n %> rt-5.0.1/share/html/REST/1.0/ticket/comment000644 000765 000024 00000011401 14005011336 020767 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/ticket/comment %# <%ARGS> $content <%INIT> use MIME::Entity; use RT::Interface::REST; my $ticket = RT::Ticket->new($session{CurrentUser}); my $object = $r->path_info; my $status = "200 Ok"; my $output; my $action; # http://.../REST/1.0/ticket/1/comment my ($c, $o, $k, $e) = @{ form_parse($content)->[0] }; if ($e || !$o) { if (!$o) { $output = "Empty form submitted.\n"; } else { $c = "# Syntax error."; $output = form_compose([[$c, $o, $k, $e]]); } $status = "400 Bad Request"; goto OUTPUT; } $object =~ s#^/##; $object ||= $k->{Ticket}; unless ($object =~ /^\d+/) { $output = "Invalid ticket id: `$object'.\n"; $status = "400 Bad Request"; goto OUTPUT; } if ($k->{Ticket} && $object ne $k->{Ticket}) { $output = "The submitted form and URL specify different tickets.\n"; $status = "400 Bad Request"; goto OUTPUT; } ($action = $k->{Action}) =~ s/^(.)(.*)$/\U$1\L$2\E/; unless ($action =~ /^(?:Comment|Correspond)$/) { $output = "Invalid action: `$action'.\n"; $status = "400 Bad Request"; goto OUTPUT; } my $text = $k->{Text}; my @atts = @{ vsplit($k->{Attachment}) }; if (!$k->{Text} && @atts == 0) { $status = "400 Bad Request"; $output = "Empty comment with no attachments submitted.\n"; goto OUTPUT; } my $cgi = $m->cgi_object; my $ent = MIME::Entity->build( Type => "multipart/mixed", 'X-RT-Interface' => 'REST', ); $ent->attach( Type => "text/plain", Charset => "UTF-8", Data => Encode::encode( "UTF-8", $k->{Text} ), ) if $k->{Text}; { my ($res, $msg) = process_attachments($ent, @atts); unless ( $res ) { $status = "400 Bad Request"; $output = "$msg\n"; goto OUTPUT; } } $ticket->Load($object); unless ($ticket->Id) { $output = "Couldn't load ticket id: `$object'.\n"; $status = "404 Ticket not found"; goto OUTPUT; } unless ($ticket->CurrentUserHasRight('ModifyTicket') || ($action eq "Comment" && $ticket->CurrentUserHasRight("CommentOnTicket")) || ($action eq "Correspond" && $ticket->CurrentUserHasRight("ReplyToTicket"))) { $output = "You are not allowed to $action on ticket $object.\n"; $status = "403 Permission denied"; goto OUTPUT; } my $cc = join ", ", @{ vsplit($k->{Cc}) }; my $bcc = join ", ", @{ vsplit($k->{Bcc}) }; my ($n, $s) = $ticket->$action(MIMEObj => $ent, CcMessageTo => $cc, BccMessageTo => $bcc, TimeTaken => $k->{TimeWorked} || 0); $output = $s; if ($k->{Status}) { my ($status_n, $status_s) = $ticket->SetStatus($k->{'Status'} ); $output .= "\n".$status_s; } OUTPUT: RT/<% $RT::VERSION %> <% $status %> <% $output |n %> rt-5.0.1/share/html/REST/1.0/ticket/link000644 000765 000024 00000007532 14005011336 020274 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/ticket/link %# <%ARGS> $id => undef $del => 0 $rel $to <%INIT> use RT::Interface::REST; my $output; my $status = "200 Ok"; my $ticket = RT::Ticket->new($session{CurrentUser}); my $object = $r->path_info; my @fields = qw(DependsOn DependedOnBy RefersTo ReferredToBy HasMember MemberOf); my %fields = map { lc $_ => $_ } @fields; my %lfields = ( HasMember => { Type => 'MemberOf', Mode => 'Base' }, ReferredToBy => { Type => 'RefersTo', Mode => 'Base' }, DependedOnBy => { Type => 'DependsOn', Mode => 'Base' }, MemberOf => { Type => 'MemberOf', Mode => 'Target' }, RefersTo => { Type => 'RefersTo', Mode => 'Target' }, DependsOn => { Type => 'DependsOn', Mode => 'Target' }, ); # http://.../REST/1.0/ticket/link/1 $object =~ s#^/REST/1.0/ticket/link##; if ($id && $object && $id != $object) { $output = "Different ids in URL (`$object') and submitted form (`$id').\n"; $status = "400 Bad Request"; goto OUTPUT; } $id ||= $object; unless ($id =~ /^\d+$/) { $output = $r->path_info. "\n"; $output .= "Invalid ticket id: '$id'.\n"; $status = "400 Bad Request"; goto OUTPUT; } unless (exists $fields{lc $rel}) { $output = "Invalid link: '$rel'.\n"; $status = "400 Bad Request"; goto OUTPUT; } $rel = $fields{lc $rel}; $ticket->Load($id); unless ($ticket->Id) { $output = "Couldn't load ticket id: '$id'.\n"; $status = "404 Ticket not found"; goto OUTPUT; } my $type = $lfields{$rel}->{Type}; my $mode = $lfields{$rel}->{Mode}; my $n = 1; my $op = $del ? "DeleteLink" : "AddLink"; ($n, $output) = $ticket->$op(Type => $type, $mode => $to); if ($n == 0) { $status = "500 Error"; } else { my $action = $del ? "Deleted" : "Created"; $output .= " $action link " . $ticket->Id . " $rel $to"; } OUTPUT: RT/<% $RT::VERSION %> <% $status %> <% $output |n %> rt-5.0.1/share/html/REST/1.0/search/dhandler000644 000765 000024 00000021745 14005011336 021104 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/search/dhandler %# <%ARGS> $query $format => undef $orderby => undef $fields => undef <%INIT> my $type = $m->dhandler_arg; my ( $status, $output ); if ( $type =~ /^(ticket|queue|user|group)$/i ) { $status = "200 Ok"; $output = ''; my $type = lc $1; if ( $type eq 'user' && !$session{CurrentUser}->HasRight( Object => $RT::System, Right => 'AdminUsers', ) ) { $status = "403 Forbidden"; $output = "Permission denied"; goto OUTPUT; } my $class = 'RT::' . ucfirst $type . 's'; my $objects = $class->new( $session{CurrentUser} ); # Parse and validate any field specifications. require RT::Interface::REST; my $field = RT::Interface::REST->field_spec; my ( %fields, @fields ); if ($fields) { $format ||= "l"; unless ( $fields =~ /^(?:$field,)*$field$/ ) { $status = "400 Bad Request"; $output = "Invalid field specification: $fields"; goto OUTPUT; } @fields = map lc, split /\s*,\s*/, $fields; @fields{@fields} = (); unless ( exists $fields{id} ) { unshift @fields, "id"; $fields{id} = (); } } $format ||= "s"; if ( $format !~ /^[isl]$/ ) { $status = "400 Bad request"; $output = "Unknown listing format: $format. (Use i, s, or l.)\n"; goto OUTPUT; } my ( $n, $s ); $n = 0; my @output; if ( $type eq 'group' ) { $objects->LimitToUserDefinedGroups; } if ( defined $query && length $query ) { if ( $type eq 'ticket' ) { my ( $n, $s ); eval { ( $n, $s ) = $objects->FromSQL($query); }; if ( $@ || $n == 0 ) { $s ||= $@; $status = "400 Bad request"; $output = "Invalid query: '$s'.\n"; goto OUTPUT; } } else { require Text::ParseWords; my ( $field, $op, $value ) = Text::ParseWords::shellwords($query); if ( $op !~ /^(?:[!<>]?=|[<>]|(NOT )?LIKE|STARTSWITH|ENDSWITH|MATCHES)$/i ) { $status = "400 Bad Request"; $output = "Invalid operator specification: $op"; goto OUTPUT; } if ( ! $search_whitelist{$type}{lc $field} ) { $status = "400 Bad Request"; $output = "Invalid field specification: $field"; goto OUTPUT; } if ( $field && $op && defined $value ) { if ( $field eq 'Disabled' ) { if ($value) { if ( $type eq 'queue' ) { $objects->FindAllRows; $objects->Limit( FIELD => $field, OPERATOR => uc $op, VALUE => $value ); } else { $objects->LimitToDeleted; } } else { if ( $type eq 'queue' ) { $objects->UnLimit; } else { $objects->LimitToEnabled; } } } else { $objects->Limit( FIELD => $field, OPERATOR => uc $op, VALUE => $value, CASESENSITIVE => 0, ); } } else { $output = "Invalid query specification: $query"; goto OUTPUT; } } } else { if ( $type eq 'queue' ) { $objects->UnLimit; } elsif ( $type eq 'user' ) { $objects->LimitToPrivileged; } } if ($orderby) { my ( $order, $field ) = $orderby =~ /^([\+\-])?(.+)/; $order = $order && $order eq '-' ? 'DESC' : 'ASC'; $objects->OrderBy( FIELD => $field, ORDER => $order ); } while ( my $object = $objects->Next ) { next if $type eq 'user' && ( $object->id == RT->SystemUser->id || $object->id == RT->Nobody->id ); $n++; my $id = $object->Id; if ( $format eq "i" ) { $output .= "$type/" . $id . "\n"; } elsif ( $format eq "s" ) { if ($fields) { my $result = $m->comp( "/REST/1.0/Forms/$type/default", id => $id, format => $format, fields => \%fields ); my ( $notes, $order, $key_values, $errors ) = @$result; # If it's the first time through, add our header if ( $n == 1 ) { $output .= join( "\t", @$order ) . "\n"; } # Cut off the annoying $type/ before the id; $key_values->{'id'} = $id; $output .= join( "\t", map { ref $key_values->{$_} eq 'ARRAY' ? join( ', ', @{ $key_values->{$_} } ) : $key_values->{$_} } @$order ) . "\n"; } else { if ( $type eq 'ticket' ) { $output .= $object->Id . ": " . $object->Subject . "\n"; } else { $output .= $object->Id . ": " . $object->Name . "\n"; } } } else { my $d = $m->comp( "/REST/1.0/Forms/$type/default", id => $id, format => $format, fields => \%fields ); my ( $c, $o, $k, $e ) = @$d; push @output, [ $c, $o, $k ]; } } if ( $n == 0 && $format ne "i" ) { $output = "No matching results.\n"; } $output = form_compose( \@output ) if @output; } else { $status = "500 Server Error"; $output = "Unsupported object type."; goto OUTPUT; } OUTPUT: $m->out("RT/". $RT::VERSION . " " . $status ."\n\n"); $m->out($output ); <%ONCE> my %search_whitelist = ( queue => { map { lc $_ => 1 } grep { $RT::Record::_TABLE_ATTR->{'RT::Queue'}{$_}{read} } keys %{ $RT::Record::_TABLE_ATTR->{'RT::Queue'} } }, user => { disabled => 1, map { lc $_ => 1 } grep { $RT::Record::_TABLE_ATTR->{'RT::User'}{$_}{read} } keys %{ $RT::Record::_TABLE_ATTR->{'RT::User'} } }, group => { disabled => 1, map { lc $_ => 1 } grep { $RT::Record::_TABLE_ATTR->{'RT::Group'}{$_}{read} } keys %{ $RT::Record::_TABLE_ATTR->{'RT::Group'} } } ); rt-5.0.1/share/html/REST/1.0/NoAuth/mail-gateway000644 000765 000024 00000006035 14005011336 021630 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%flags> inherit => undef # inhibit UTF8 conversion done in /autohandler <%ARGS> $queue => 1 $action => "correspond" $ticket => undef <%init> $m->callback( %ARGS, CallbackName => 'Pre' ); use RT::Interface::Email; $r->content_type('text/plain; charset=utf-8'); $m->error_format('text'); my ( $status, $error, $Ticket ) = RT::Interface::Email::Gateway( \%ARGS ); if ( $status == 1 ) { $m->out("ok\n"); if ( $Ticket && $Ticket->Id ) { $m->out( 'Ticket: ' . ($Ticket->Id || '') . "\n" ); $m->out( 'Queue: ' . ($Ticket->QueueObj->Name || '') . "\n" ); $m->out( 'Owner: ' . ($Ticket->OwnerObj->Name || '') . "\n" ); $m->out( 'Status: ' . ($Ticket->Status || '') . "\n" ); $m->out( 'Subject: ' . ($Ticket->Subject || '') . "\n" ); $m->out( 'Requestor: ' . ($Ticket->Requestors->MemberEmailAddressesAsString || '') . "\n" ); } } else { if ( $status == -75 ) { $m->out( "temporary failure - $error\n" ); } else { $m->out( "not ok - $error\n" ); } } $m->abort(); rt-5.0.1/share/html/REST/1.0/Forms/transaction/000755 000765 000024 00000000000 14005011336 021535 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/REST/1.0/Forms/group/000755 000765 000024 00000000000 14005011336 020344 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/REST/1.0/Forms/attachment/000755 000765 000024 00000000000 14005011336 021340 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/REST/1.0/Forms/user/000755 000765 000024 00000000000 14005011336 020166 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/REST/1.0/Forms/queue/000755 000765 000024 00000000000 14005011336 020334 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/REST/1.0/Forms/ticket/000755 000765 000024 00000000000 14005011336 020473 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/REST/1.0/Forms/ticket/merge000644 000765 000024 00000005610 14005011336 021517 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/ticket/merge %# <%ARGS> $id $args <%INIT> use RT::Interface::REST; my $into = $args; my $ticket = RT::Ticket->new($session{CurrentUser}); my $ticket_into = RT::Ticket->new($session{CurrentUser}); my ($c, $o, $k, $e) = ("", [], {}, 0); # http://.../REST/1.0/ticket/1/merge/6 (merges ticket 1 into ticket 6) $ticket->Load($id); if (!$ticket->Id) { $e = 1; $c = "# Ticket $id does not exist."; goto OUTPUT; } $ticket_into->Load($into); if (!$ticket_into->Id) { $e = 1; $c = "# Ticket $into does not exist."; goto OUTPUT; } if (!$ticket->CurrentUserHasRight('ModifyTicket')) { $e = 1; $c = "# You are not allowed to modify ticket $id."; goto OUTPUT; } my ($n, $s) = $ticket->MergeInto($into); if ($n == 0) { $e = 1; $c = "# Could not complete the merge."; } else { $c = "# Merge completed."; } OUTPUT: return [ $c, $o, $k, $e ]; rt-5.0.1/share/html/REST/1.0/Forms/ticket/comment000644 000765 000024 00000010112 14005011336 022053 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/ticket/comment %# <%ARGS> $id %changes <%INIT> use MIME::Entity; use RT::Interface::REST; $RT::Logger->debug("Got ticket id=$id for comment"); $RT::Logger->debug("Got args @{[keys(%changes)]}."); my $ticket = RT::Ticket->new($session{CurrentUser}); my ($c, $o, $k, $e) = ("", [], {}, 0); # http://.../REST/1.0/ticket/1/comment $ticket->Load($id); if (!$ticket->Id) { $e = 1; $c = "# Ticket $id does not exist."; goto OUTPUT; } my $action; ($action = $changes{Action}) =~ s/^(.)(.*)$/\U$1\L$2\E/; unless ($action =~ /^(?:Comment|Correspond)$/) { $e = 1; $c = "# Invalid action: `$action'."; goto OUTPUT; } my $text = $changes{Text}; my @atts = @{ vsplit($changes{Attachment}) }; if (!$changes{Text} && @atts == 0) { $e = 1; $c = "# Empty comment with no attachments submitted."; goto OUTPUT; } my $ent = MIME::Entity->build( Type => "multipart/mixed", 'X-RT-Interface' => 'REST', ); $ent->attach( Type => $changes{'Content-Type'} || 'text/plain', Charset => "UTF-8", Data => Encode::encode("UTF-8", $changes{Text} ), ) if $changes{Text}; { my ($status, $msg) = process_attachments($ent, @atts); unless ( $status ) { $e = 1; $c = "# $msg"; goto OUTPUT; } } unless ($ticket->CurrentUserHasRight('ModifyTicket') || ($action eq "Comment" && $ticket->CurrentUserHasRight("CommentOnTicket")) || ($action eq "Correspond" && $ticket->CurrentUserHasRight("ReplyToTicket"))) { $e = 1; $c = "# You are not allowed to $action on ticket $id."; goto OUTPUT; } my $cc = join ", ", @{ vsplit($changes{Cc}) }; my $bcc = join ", ", @{ vsplit($changes{Bcc}) }; my ($n, $s) = $ticket->$action(MIMEObj => $ent, CcMessageTo => $cc, BccMessageTo => $bcc, TimeTaken => $changes{TimeWorked} || 0); $c = "# ".$s; if ($changes{Status}) { my ($status_n, $status_s) = $ticket->SetStatus($changes{'Status'} ); $c .= "\n# ".$status_s; } OUTPUT: return [ $c, $o, $k, $e ]; rt-5.0.1/share/html/REST/1.0/Forms/ticket/default000644 000765 000024 00000043551 14005011336 022052 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/ticket/default %# <%ARGS> $id $changes => {} $fields => undef $args => undef <%INIT> use MIME::Entity; use RT::Interface::REST; my $cf_spec = RT::Interface::REST->custom_field_spec(1); my @comments; my ($c, $o, $k, $e) = ("", [], {}, 0); my %data = %$changes; my $ticket = RT::Ticket->new($session{CurrentUser}); my @dates = qw(Created Starts Started Due Resolved Told LastUpdated); my @people = qw(Requestors Cc AdminCc); my @create = qw(Queue Requestor Subject Cc AdminCc Owner Status Priority InitialPriority FinalPriority TimeEstimated TimeWorked TimeLeft Starts Started Due Resolved Content-Type SLA); my @simple = qw(Subject Status Priority Disabled TimeEstimated TimeWorked TimeLeft InitialPriority FinalPriority); my %dates = map {lc $_ => $_} @dates; my %people = map {lc $_ => $_} @people; my %create = map {lc $_ => $_} @create; my %simple = map {lc $_ => $_} @simple; # Are we dealing with an existing ticket? if ($id ne 'new') { $ticket->Load($id); if (!$ticket->Id) { return [ "# Ticket $id does not exist.", [], {}, 1 ]; } elsif ( %data ) { if ( $data{status} && lc $data{status} eq 'deleted' && ! grep { $_ ne 'id' && $_ ne 'status' } keys %data ) { if ( !$ticket->CurrentUserHasRight('DeleteTicket') ) { return [ "# You are not allowed to delete ticket $id.", [], {}, 1 ]; } } elsif ( !$ticket->CurrentUserHasRight('ModifyTicket') ) { return [ "# You are not allowed to modify ticket $id.", [], {}, 1 ]; } } elsif (!$ticket->CurrentUserHasRight('ShowTicket')) { return [ "# You are not allowed to display ticket $id.", [], {}, 1 ]; } } else { if (!keys(%data)) { # GET ticket/new: Return a suitable default form. # We get defaults from queue/1 (XXX: What if it isn't there?). my $due = RT::Date->new($session{CurrentUser}); my $queue = RT::Queue->new($session{CurrentUser}); my $starts = RT::Date->new($session{CurrentUser}); $queue->Load(1); if ( $queue->DefaultValue('Due') ) { $due->Set( Format => 'unknown', Value => $queue->DefaultValue('Due') ); } if ( $queue->DefaultValue('Starts') ) { $starts->Set( Format => 'unknown', Value => $queue->DefaultValue('Starts') ); } else { $starts->SetToNow; } return [ "# Required: id, Queue", [ qw(id Queue Requestor Subject Cc AdminCc Owner Status Priority InitialPriority FinalPriority TimeEstimated Starts Due), ( $queue->SLADisabled ? () : 'SLA' ), qw(Attachment Text) ], { id => "ticket/new", Queue => $queue->Name, Requestor => $session{CurrentUser}->Name, Subject => "", Cc => [], AdminCc => [], Owner => "", Status => "new", Priority => $queue->DefaultValue('InitialPriority'), InitialPriority => $queue->DefaultValue('InitialPriority'), FinalPriority => $queue->DefaultValue('FinalPriority'), TimeEstimated => 0, Starts => $starts->ISO(Timezone => 'user'), Due => $due->IsSet ? $due->ISO(Timezone => 'user') : undef, SLA => '', Attachment => '', Text => "", }, 0 ]; } else { # We'll create a new ticket, and fall through to set fields that # can't be set in the call to Create(). my (%v, $text, @atts); foreach my $k (keys %data) { # flexibly parse any dates if ($dates{lc $k}) { my $time = RT::Date->new($session{CurrentUser}); $time->Set(Format => 'unknown', Value => $data{$k}); $data{$k} = $time->ISO; } if (exists $create{lc $k}) { $v{$create{lc $k}} = delete $data{$k}; } # Set custom field elsif ($k =~ /^$cf_spec/) { my $key = $1 || $2; my $cf = RT::CustomField->new( $session{CurrentUser} ); $cf->LoadByName( Name => $key, LookupType => RT::Ticket->CustomFieldLookupType, ObjectId => $data{Queue} || $v{Queue}, IncludeGlobal => 1, ); if (not $cf->id) { push @comments, "# Invalid custom field name ($key)"; delete $data{$k}; next; } my $val = delete $data{$k}; next unless defined $val && length $val; $v{"CustomField-".$cf->Id()} = $cf->SingleValue ? $val : vsplit($val,1); } elsif (lc $k eq 'text') { $text = delete $data{$k}; } elsif (lc $k eq 'attachment') { push @atts, @{ vsplit(delete $data{$k}) }; } elsif ( $k !~ /^(?:id|requestors)$/i ) { $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 $v{$_} = vsplit($v{$_}) foreach ( grep $create{lc $_}, @people ); if ($text || @atts) { $v{MIMEObj} = MIME::Entity->build( Type => "multipart/mixed", From => Encode::encode( "UTF-8", $session{CurrentUser}->EmailAddress ), Subject => Encode::encode( "UTF-8", $v{Subject}), 'X-RT-Interface' => 'REST', ); $v{MIMEObj}->attach( Type => $v{'Content-Type'} || 'text/plain', Charset => "UTF-8", Data => Encode::encode( "UTF-8", $text ), ) if $text; my ($status, $msg) = process_attachments($v{'MIMEObj'}, @atts); unless ($status) { push(@comments, "# $msg"); goto DONE; } $v{MIMEObj}->make_singlepart; } my($tid,$trid,$terr) = $ticket->Create(%v); unless ($tid) { push(@comments, "# Could not create ticket."); push(@comments, "# " . $terr); goto DONE; } delete $data{id}; $id = $ticket->Id; push(@comments, "# Ticket $id created."); # see if the hash is empty goto DONE if ! keys(%data); } } # Now we know we're dealing with an existing ticket. if (!keys(%data)) { my ($time, $key, $val, @data); push @data, [ id => "ticket/".$ticket->Id ]; push @data, [ Queue => $ticket->QueueObj->Name ] if (!%$fields || exists $fields->{lc 'Queue'}); push @data, [ Owner => $ticket->OwnerObj->Name ] if (!%$fields || exists $fields->{lc 'Owner'}); push @data, [ Creator => $ticket->CreatorObj->Name ] if (!%$fields || exists $fields->{lc 'Creator'}); foreach (qw(Subject Status Priority InitialPriority FinalPriority)) { next unless (!%$fields || (exists $fields->{lc $_})); push @data, [$_ => $ticket->$_ ]; } foreach $key (@people) { next unless (!%$fields || (exists $fields->{lc $key})); push @data, [ $key => [ $ticket->$key->MemberEmailAddresses ] ]; } $time = RT::Date->new ($session{CurrentUser}); foreach $key (@dates) { next unless (!%$fields || (exists $fields->{lc $key})); $time->Set(Format => 'sql', Value => $ticket->$key); push @data, [ $key => $time->AsString ]; } $time = RT::Date->new ($session{CurrentUser}); foreach $key (qw(TimeEstimated TimeWorked TimeLeft)) { next unless (!%$fields || (exists $fields->{lc $key})); $val = $ticket->$key || 0; $val = "$val minutes" if $val; push @data, [ $key => $val ]; } if ( !$ticket->QueueObj->SLADisabled && (!%$fields || (exists $fields->{sla})) ) { push @data, [ SLA => $ticket->SLA ]; } # Display custom fields my $CustomFields = $ticket->CustomFields; while (my $cf = $CustomFields->Next()) { next unless !%$fields || exists $fields->{"cf.{".lc($cf->Name)."}"} || exists $fields->{"cf-".lc $cf->Name}; my $vals = $ticket->CustomFieldValues($cf->Id()); my @out = (); if ( $cf->SingleValue ) { my $v = $vals->Next; push @out, $v->Content if $v; } else { while (my $v = $vals->Next()) { my $content = $v->Content; if ( $v->Content =~ /,/ ) { $content =~ s/([\\'])/\\$1/g; push @out, q{'} . $content . q{'}; } else { push @out, $content; } } } push @data, [ ('CF.{' . $cf->Name . '}') => join ',', @out ]; } my %k = map {@$_} @data; $o = [ map {$_->[0]} @data ]; $k = \%k; } else { my ($get, $set, $key, $val, $n, $s); my $updated; foreach $key (keys %data) { $val = $data{$key}; $key = lc $key; $n = 1; if (ref $val eq 'ARRAY') { unless ($key =~ /^(?:Requestors|Cc|AdminCc)$/i) { $n = 0; $s = "$key may have only one value."; goto SET; } } if ($key =~ /^queue$/i) { next if $val eq $ticket->QueueObj->Name; ($n, $s) = $ticket->SetQueue($val); } elsif ($key =~ /^owner$/i) { next if $val eq $ticket->OwnerObj->Name; ($n, $s) = $ticket->SetOwner($val); } elsif (exists $simple{$key}) { $key = $simple{$key}; $set = "Set$key"; my $current = $ticket->$key; $current = '' unless defined $current; next if ($val eq $current) or ($current =~ /^\d+$/ && $val =~ /^\d+$/ && $val == $current); ($n, $s) = $ticket->$set("$val"); } elsif (exists $dates{$key}) { $key = $dates{$key}; # We try to detect whether it should update a field by checking # whether its current value equals the entered value. Since the # LastUpdated field is automatically updated as other columns are # changed, it is not properly skipped. Users cannot update this # field anyway. next if $key eq 'LastUpdated'; $set = "Set$key"; my $time = RT::Date->new($session{CurrentUser}); $time->Set(Format => 'sql', Value => $ticket->$key); next if ($val =~ /^not set$/i || $val eq $time->AsString); $time->Set(Format => 'unknown', Value => $val); ($n, $s) = $ticket->$set($time->ISO); } elsif (exists $people{$key}) { $key = $people{$key}; my ($p, @msgs); my %new = map {$_=>1} @{ vsplit($val) }; my %old; my $members = $ticket->$key->MembersObj; while (my $member = $members->Next) { my $principal = $member->MemberObj; if ($principal->IsGroup) { $old{ $principal->Id } = 1; } else { $old{ $principal->Object->EmailAddress } = 1; } } my $type = $key eq 'Requestors' ? 'Requestor' : $key; foreach $p (keys %old) { unless (exists $new{$p}) { my $key = "Email"; $key = "PrincipalId" if $p =~ /^\d+$/; ($s, $n) = $ticket->DeleteWatcher(Type => $type, $key => $p); push @msgs, [ $s, $n ]; } } foreach $p (keys %new) { my $key = "Email"; $key = "PrincipalId" if $p =~ /^\d+$/; unless ($ticket->IsWatcher(Type => $type, $key => $p)) { ($s, $n) = $ticket->AddWatcher(Type => $type, $key => $p); push @msgs, [ $s, $n ]; } } $n = 1; if (@msgs = grep {$_->[0] == 0} @msgs) { $n = 0; $s = join "\n", map {"# ".$_->[1]} @msgs; $s =~ s/^# //; } } # Set custom field elsif ($key =~ /^$cf_spec/) { $key = $1 || $2; my $cf = RT::CustomField->new( $session{CurrentUser} ); $cf->ContextObject( $ticket ); $cf->LoadByName( Name => $key, LookupType => RT::Ticket->CustomFieldLookupType, ObjectId => $ticket->Queue, IncludeGlobal => 1, ); if (not $cf->id) { $n = 0; $s = "Unknown custom field."; } else { my $vals = $ticket->CustomFieldValues($cf->id); if ( $ticket->CustomFieldValueIsEmpty( Field => $cf, Value => $val ) ) { while ( my $val = $vals->Next ) { ($n, $s) = $ticket->DeleteCustomFieldValue( Field => $cf, ValueId => $val->id, ); $s =~ s/^# // if defined $s; } } elsif ( $cf->SingleValue ) { ($n, $s) = $ticket->AddCustomFieldValue( Field => $cf, Value => $val ); $s =~ s/^# // if defined $s; } else { my @new = @{vsplit($val, 1)}; my %new; $new{$_}++ for @new; while (my $v = $vals->Next()) { my $c = $v->Content; if ( $new{$c} ) { $new{$c}--; } else { $ticket->DeleteCustomFieldValue( Field => $cf, ValueId => $v->id ); } } for ( @new ) { while ( $new{$_} && $new{$_}-- ) { next if $ticket->CustomFieldValueIsEmpty( Field => $cf, Value => $_, ); ($n, $s) = $ticket->AddCustomFieldValue( Field => $cf, Value => $_ ); $s =~ s/^# // if defined $s; } } } } } elsif ( $key eq 'sla' ) { my $current = $ticket->SLA // ''; next if $val eq $current; ($n, $s) = $ticket->SetSLA($val); } elsif ($key ne 'id' && $key ne 'type' && $key ne 'creator' && $key ne 'content-type' ) { $n = 0; $s = "Unknown field."; } SET: if ($n == 0) { $e = 1; push @comments, "# $key: $s"; unless (@$o) { # move id forward @$o = ("id", grep { $_ ne 'id' } keys %$changes); $k = $changes; } } else { $updated ||= 1; } } push(@comments, "# Ticket ".$ticket->id." updated.") if $updated; } DONE: $c ||= join("\n", @comments) if @comments; return [$c, $o, $k, $e]; rt-5.0.1/share/html/REST/1.0/Forms/ticket/take000644 000765 000024 00000010066 14005011336 021345 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/ticket/take %# <%ARGS> $id %changes <%INIT> use RT::Interface::REST; my $ticket = RT::Ticket->new($session{CurrentUser}); my ($c, $o, $k, $e) = ("", [], {}, 0); # http://.../REST/1.0/ticket/1/take $ticket->Load( $id ); unless ( $ticket->Id ) { $e = 1; $c = "# Ticket $id does not exist."; goto OUTPUT; } my $action; my @comments; ($action = $changes{Action}) =~ s/^(.)(.*)$/\U$1\L$2\E/; unless ($action =~ /^(?:Take|Steal|Untake)$/) { $e = 1; $c = "# Invalid action: `$action'."; goto OUTPUT; } my ($status, $msg) = $ticket->$action(); $c = "# $msg"; $e = 1 unless $status; goto OUTPUT; #unless ($ticket->CurrentUserHasRight('ModifyTicket') || # ( ($action eq "Take" || $action eq 'Untake') && # $ticket->CurrentUserHasRight("TakeTicket")) || # ($action eq "Steal" && # $ticket->CurrentUserHasRight("StealTicket"))) #{ # $e = 1; # $c = "# You are not allowed to $action ticket $id."; # goto OUTPUT; #} #if ( keys %changes ) { #} #else { # # process the form data structure # my ($key, $val); # # foreach $key (keys %data) { # $val = $data{$key}; # # if ($key =~ /^force$/i) { # if ($val !~ /^(?:0|1)$/) { # push(@comments, "# invalid value for 'force': $val"); # goto DONE; # } # my ($ret_id, $msg); # # ### take # if ($val == 0) { # ($ret_id, $msg) = $ticket->Take; # if (!$ret_id) { # push(@comments, "# Couldn't take ticket $id: $msg"); # goto DONE; # } # push(@comments, "# Ticket $id taken."); # } # ### steal # else { # ($ret_id, $msg) = $ticket->Steal; # if (!$ret_id) { # push(@comments, "# Couldn't steal ticket $id: $msg"); # goto DONE; # } # push(@comments, "# Ticket $id stolen."); # } # } # } #} OUTPUT: return [ $c, $o, $k, $e ]; rt-5.0.1/share/html/REST/1.0/Forms/ticket/links000644 000765 000024 00000014015 14005011336 021537 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/ticket/links %# <%ARGS> $id $format => 's' $changes => undef <%INIT> my @data; my $ticket = RT::Ticket->new($session{CurrentUser}); $ticket->Load($id); if (!$ticket->Id) { return [ "# Ticket $id does not exist.", [], {}, 1 ]; } my ($c, $o, $k, $e) = ("", [], {}, 0); my @fields = qw(DependsOn DependedOnBy RefersTo ReferredToBy Members MemberOf); my %fields = map { lc $_ => $_ } @fields; my %lfields = ( Members => { Type => 'MemberOf', Mode => 'Base' }, ReferredToBy => { Type => 'RefersTo', Mode => 'Base' }, DependedOnBy => { Type => 'DependsOn', Mode => 'Base' }, MemberOf => { Type => 'MemberOf', Mode => 'Target' }, RefersTo => { Type => 'RefersTo', Mode => 'Target' }, DependsOn => { Type => 'DependsOn', Mode => 'Target' }, ); if ($changes) { my ($get, $set, $key, $val, $n, $s); my %data = %$changes; my @comments; foreach $key (keys %data) { $val = $data{$key}; $key = lc $key; $n = 1; if (exists $fields{$key}) { $key = $fields{$key}; my %old; my $field = $lfields{$key}->{Mode}; my $mode_obj = $field . 'Obj'; while (my $link = $ticket->$key->Next) { next if UNIVERSAL::isa($link->$mode_obj, 'RT::Article') && $link->$mode_obj->Disabled; next if $field eq 'Base' && $link->Type eq 'RefersTo' && $link->BaseObj->__Value('Type') eq 'reminder'; $old{$link->$field} = 1; } my %new; foreach my $nkey (@{vsplit($val)}) { if ($nkey =~ /^\d+$/) { my $uri = RT::URI->new($session{CurrentUser}); my $tick = RT::Ticket->new($session{CurrentUser}); $tick->Load($nkey); if ($tick->Id) { $uri->FromObject($tick); $nkey = $uri->URI; } else { $n = 0; $s = "Ticket $nkey does not exist."; goto SET; } } $new{$nkey} = 1; } foreach my $u (keys %old) { if (exists $new{$u}) { delete $new{$u}; } else { my $type = $lfields{$key}->{Type}; my $mode = $lfields{$key}->{Mode}; ($n, $s) = $ticket->DeleteLink(Type => $type, $mode => $u); } } foreach my $u (keys %new) { my $type = $lfields{$key}->{Type}; my $mode = $lfields{$key}->{Mode}; ($n, $s) = $ticket->AddLink(Type => $type, $mode => $u); } } elsif ($key ne 'id' && $key ne 'type') { $n = 0; $s = "Unknown field: $key"; } SET: if ($n == 0) { $e = 1; push @comments, "# $key: $s"; unless (@$o) { @$o = ("id", @fields); %$k = %data; } } } push(@comments, "# Links for ticket $id updated.") unless @comments; $c = join("\n", @comments) if @comments; } else { my @data; push @data, [ id => "ticket/".$ticket->Id."/links" ]; foreach my $key (@fields) { my @val; my $field = $lfields{$key}->{Mode}; my $mode_obj = $field . 'Obj'; while (my $link = $ticket->$key->Next) { next if UNIVERSAL::isa($link->$mode_obj, 'RT::Article') && $link->$mode_obj->Disabled; next if $field eq 'Base' && $link->Type eq 'RefersTo' && UNIVERSAL::isa($link->$mode_obj, 'RT::Ticket') && $link->BaseObj->__Value('Type') eq 'reminder'; push @val, $link->$field; } push(@val, "") if (@val == 0 && defined $format && $format eq 'l'); push @data, [ $key => [ @val ] ] if @val; } my %k = map {@$_} @data; $o = [ map {$_->[0]} @data ]; $k = \%k; } return [ $c, $o, $k, $e ]; rt-5.0.1/share/html/REST/1.0/Forms/ticket/history000644 000765 000024 00000015111 14005011336 022116 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/ticket/history %# <%ARGS> $id $args => undef $format => undef $fields => undef <%INIT> my $ticket = RT::Ticket->new($session{CurrentUser}); my ($c, $o, $k, $e) = ("", [], {}, ""); $ticket->Load($id); unless ($ticket->Id) { return [ "# Ticket $id does not exist.", [], {}, 1 ]; } my $trans = $ticket->Transactions(); my $total = $trans->Count(); if ( $args ) { chomp $args; } else { $args = ''; } my @arglist = split('/', $args ); my ($type, $tid); if (defined $arglist[0] && $arglist[0] eq 'type') { $type = $arglist[1]; } elsif ( defined $arglist[0] && $arglist[0] eq 'id') { $tid = $arglist[1]; } else { $type = $args; } if ($type) { # Create, Set, Status, Correspond, Comment, Give, Steal, Take, Told # CustomField, AddLink, DeleteLink, AddWatcher, DelWatcher if ($args =~ /^links?$/) { $trans->Limit(FIELD => 'Type', OPERATOR => 'LIKE', VALUE => '%Link'); } elsif ($args =~ /^watchers?$/) { $trans->Limit(FIELD => 'Type', OPERATOR => 'LIKE', VALUE => '%Watcher'); } else { $trans->Limit(FIELD => 'Type', OPERATOR => '=', VALUE => $type); } } elsif ($tid) { $trans->Limit(FIELD => 'Id', OPERATOR => '=', VALUE => $tid); } if ($tid) { my @data; my $t = RT::Transaction->new($session{CurrentUser}); # this paragraph limits the transaction ID query to transactions on this ticket. # Otherwise you can query any transaction from any ticket, which makes no sense. my $Transactions = $ticket->Transactions; my $tok=0; while (my $T = $Transactions->Next()) { $tok=1 if ($T->Id == $tid) } if ($tok) { $t->Load($tid); } else { return [ "# Transaction $tid is not related to Ticket $id", [], {}, 1 ]; } push @data, [ id => $t->Id ]; push @data, [ Ticket => $t->Ticket ] if (!%$fields || exists $fields->{lc 'Ticket'}); push @data, [ TimeTaken => $t->TimeTaken ] if (!%$fields || exists $fields->{lc 'TimeTaken'}); push @data, [ Type => $t->Type ] if (!%$fields || exists $fields->{lc 'Type'}); push @data, [ Field => $t->Field ] if (!%$fields || exists $fields->{lc 'Field'}); push @data, [ OldValue => $t->OldValue ] if (!%$fields || exists $fields->{lc 'OldValue'}); push @data, [ NewValue => $t->NewValue ] if (!%$fields || exists $fields->{lc 'NewValue'}); push @data, [ Data => $t->Data ] if (!%$fields || exists $fields->{lc 'Data'}); push @data, [ Description => $t->Description ] if (!%$fields || exists $fields->{lc 'Description'}); push @data, [ Content => $t->Content ] if (!%$fields || exists $fields->{lc 'Content'}); if (!%$fields || exists $fields->{lc 'Content'}) { my $creator = RT::User->new($session{CurrentUser}); $creator->Load($t->Creator); push @data, [ Creator => $creator->Name ]; } push @data, [ Created => $t->Created ] if (!%$fields || exists $fields->{lc 'Created'}); if (!%$fields || exists $fields->{lc 'Attachments'}) { my $attachlist; my $attachments = $t->Attachments; while (my $a = $attachments->Next) { my $size = length($a->Content||''); if ($size > 1024) { $size = int($size/102.4)/10 . "k" } else { $size .= "b" } my $name = (defined $a->Filename and length $a->Filename) ? $a->Filename : "untitled"; $attachlist .= "\n" . $a->Id.": $name ($size)"; } push @data, [Attachments => $attachlist]; } my %k = map {@$_} @data; $o = [ map {$_->[0]} @data ]; $k = \%k; } else { my (@data, $tids); $format ||= "s"; $format = "l" if (%$fields); while (my $t = $trans->Next) { my $tid = $t->Id; if ($format eq "l") { $tids .= "," if $tids; $tids .= $tid; } else { push @$o, $tid; $k->{$tid} = $t->Description; } } if ($format eq "l") { my @tid; push @tid, "ticket/$id/history/id/$tids"; my $fieldstring; foreach my $key (keys %$fields) { $fieldstring .= "," if $fieldstring; $fieldstring .= $key; } my ($content, $forms); $m->subexec("/REST/1.0/show", id => \@tid, format => $format, fields => $fieldstring); return [ $c, $o, $k, $e ]; } } if (!$c) { my $sub = $trans->Count(); $c = "# $sub/$total ($args/total)"; } return [ $c, $o, $k, $e ]; rt-5.0.1/share/html/REST/1.0/Forms/ticket/attachments000644 000765 000024 00000011367 14005011336 022741 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/ticket/attachments %# <%ARGS> $id $args => undef <%INIT> my @data; my ($c, $o, $k, $e) = ("", [], {}, ""); my $ticket = RT::Ticket->new($session{CurrentUser}); $ticket->Load($id); unless ($ticket->Id) { return [ "# Ticket $id does not exist.", [], {}, 1 ]; } my @arglist = split('/', $args || ''); my ($aid, $content); if ( defined $arglist[1] && $arglist[1] eq 'content') { $aid = $arglist[0]; $content = 1; } else { $aid = $args; $content = 0; } if ($aid) { unless ($aid =~ /^\d+$/) { return [ "# Invalid attachment id: $aid", [], {}, 1 ]; } my $attachment = RT::Attachment->new($session{CurrentUser}); $attachment->Load($aid); unless ($attachment->Id eq $aid) { return [ "# Invalid attachment id: $aid", [], {}, 1 ]; } if ($content) { $c = $attachment->OriginalContent; # if we're sending a binary attachment (and only the attachment) # flag it so bin/rt knows to special case it if ($attachment->ContentType !~ /^text\//) { $r->content_type($attachment->ContentType); } } else { my @data; push @data, [ id => $attachment->Id ]; push @data, [ Subject => $attachment->Subject ]; push @data, [ Creator => $attachment->Creator ]; push @data, [ Created => $attachment->Created ]; push @data, [ Transaction => $attachment->TransactionId ]; push @data, [ Parent => $attachment->Parent ]; push @data, [ MessageId => $attachment->MessageId ]; push @data, [ Filename => $attachment->Filename ]; push @data, [ ContentType => $attachment->ContentType ]; push @data, [ ContentEncoding => $attachment->ContentEncoding ]; push @data, [ Headers => $attachment->Headers ]; push @data, [ Content => $attachment->Content ]; my %k = map {@$_} @data; $o = [ map {$_->[0]} @data ]; $k = \%k; } } else { my @attachments; my $transactions = $ticket->Transactions; while (my $t = $transactions->Next) { my $attachments = $t->Attachments; while (my $a = $attachments->Next) { my $size = length($a->Content || ''); if ($size > 1024) { $size = int($size/102.4)/10 . "k" } else { $size .= "b" } my $name = (defined $a->Filename and length $a->Filename) ? $a->Filename : "(Unnamed)"; push @attachments, $a->Id.": $name (".$a->ContentType . " / $size)"; } } if (@attachments) { $o = [ "id", "Attachments" ]; $k = { id => "ticket/".$ticket->Id."/attachments", Attachments => \@attachments }; } } return [ $c, $o, $k, $e ]; rt-5.0.1/share/html/REST/1.0/Forms/queue/ns000644 000765 000024 00000004466 14005011336 020711 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/queue/ns %# <%ARGS> $id <%perl> use RT::Queues; my $queues = RT::Queues->new($session{CurrentUser}); $queues->Limit(FIELD => 'Name', OPERATOR => '=', VALUE => $id, CASESENSITIVE => 0 ); if ($queues->Count == 0) { return (0, "No queue named $id exists."); } return $queues->Next->Id; rt-5.0.1/share/html/REST/1.0/Forms/queue/ticketcustomfields000644 000765 000024 00000006116 14005011336 024170 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/queue/ticketcustomfields %# <%ARGS> $id $format => 's' $changes => {} <%perl> my @comments; my ($c, $o, $k, $e) = ("", [], {}, 0); my $queue = RT::Queue->new($session{CurrentUser}); $queue->Load($id); if (!$queue->Id) { return [ "# Queue $id does not exist.", [], {}, 1 ]; } if (%$changes) { $e = 1; $c = "Cannot modify Queue CF definitions via REST"; goto DONE; } my @data; push @data, [ id => "queue/" . $queue->Id ]; my $qcfs = RT::CustomFields->new($session{CurrentUser});; $qcfs->LimitToGlobalOrQueue($id); while ( my $qcf = $qcfs->Next() ) { if ( $format eq "l" ) { my $cfadmin = ( $qcf->SingleValue ? 'SingleValue' : 'MultiValue' ) . $qcf->Type . ( $qcf->Pattern ? ( ' ' . $qcf->FriendlyPattern ) : '' ); push @data, [ $qcf->Name . ' (' . $qcf->Description . ')' => $cfadmin ]; } else { push @data, [ $qcf->Name => "" ]; } } my %k = map {@$_} @data; $o = [ map {$_->[0]} @data ]; $k = \%k; DONE: $c ||= join("\n", @comments) if @comments; return [ $c, $o, $k, $e ]; rt-5.0.1/share/html/REST/1.0/Forms/queue/default000644 000765 000024 00000013011 14005011336 021677 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/queue/default %# <%ARGS> $id $format => 's' $fields => undef # these are the fields passed to the rt "-f" flag. $changes => {} <%perl> my @comments; my ($c, $o, $k, $e) = ("", [], {}, 0); my %data = %$changes; my $queue = RT::Queue->new($session{CurrentUser}); my @fields = qw(Name Description CorrespondAddress CommentAddress Disabled SLADisabled); if ( $fields && %$fields ) { @fields = grep { exists $fields->{ lc $_ } } @fields; } my %fields = map { lc $_ => $_ } @fields; if ($id ne 'new') { $queue->Load($id); if (!$queue->Id) { return [ "# Queue $id does not exist.", [], {}, 1 ]; } } else { if (keys %data == 0) { return [ "# Required: Name", [ "id", @fields ], { id => 'queue/new', Name => '', Description => "", CommentAddress => "", CorrespondAddress => "", SLADisabled => 1, }, 0 ]; } else { my %v; my %create = %fields; foreach my $k (keys %data) { if (exists $create{lc $k}) { $v{$create{lc $k}} = delete $data{$k}; } } if ($v{Name} eq '') { my %o = keys %$changes; delete @o{"id", @fields}; return [ "# Please set the queue name.", [ "id", @fields, keys %o ], $changes, 1 ]; } $queue->Create(%v); unless ($queue->Id) { return [ "# Could not create queue.", [], {}, 1 ]; } delete $data{id}; $id = $queue->Id; push(@comments, "# Queue $id created."); goto DONE if keys %data == 0; } } if ( keys %data == 0) { my @data; push @data, [ id => "queue/".$queue->Id ]; foreach my $key (@fields) { push @data, [ $key => $queue->$key ]; } # Custom fields my $CustomFields = $queue->CustomFields; while ( my $CustomField = $CustomFields->Next() ) { next unless ( !%$fields || exists $fields->{ lc "CF-" . $CustomField->Name } ); next unless $CustomField->CurrentUserHasRight('SeeCustomField'); my $CFvalues = $queue->CustomFieldValues( $CustomField->Id ); my @CFvalues; while ( my $CFvalue = $CFvalues->Next() ) { push @CFvalues, $CFvalue->Content; } push @data, [ "CF-" . $CustomField->Name => \@CFvalues ]; } my %k = map {@$_} @data; $o = [ map {$_->[0]} @data ]; $k = \%k; } else { my ($get, $set, $key, $val, $n, $s); my $updated; foreach $key (keys %data) { $val = $data{$key}; $key = lc $key; $n = 1; if (exists $fields{$key}) { $key = $fields{$key}; $set = "Set$key"; next if $val eq $queue->$key; ($n, $s) = $queue->$set($val); } elsif ($key ne 'id') { $n = 0; $s = "Unknown field."; } SET: if ($n == 0) { $e = 1; push @comments, "# $key: $s"; unless (@$o) { my %o = keys %$changes; delete @o{"id", @fields}; @$o = ("id", @fields, keys %o); $k = $changes; } } else { $updated ||= 1; } } push(@comments, "# Queue $id updated.") if $updated; } DONE: $c ||= join("\n", @comments) if @comments; return [ $c, $o, $k, $e ]; rt-5.0.1/share/html/REST/1.0/Forms/queue/customfields000644 000765 000024 00000006017 14005011336 022764 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/queue/customfields %# <%ARGS> $id $format => 's' $changes => {} <%perl> my @comments; my ($c, $o, $k, $e) = ("", [], {}, 0); my $queue = RT::Queue->new($session{CurrentUser}); $queue->Load($id); if (!$queue->Id) { return [ "# Queue $id does not exist.", [], {}, 1 ]; } if (%$changes) { $e = 1; $c = "Cannot modify Queue CF definitions via REST"; goto DONE; } my @data; push @data, [ id => "queue/" . $queue->Id ]; my $qcfs = $queue->CustomFields; while ( my $qcf = $qcfs->Next() ) { if ( $format eq "l" ) { my $cfadmin = ( $qcf->SingleValue ? 'SingleValue' : 'MultiValue' ) . $qcf->Type . ( $qcf->Pattern ? ( ' ' . $qcf->FriendlyPattern ) : '' ); push @data, [ $qcf->Name . ' (' . $qcf->Description . ')' => $cfadmin ]; } else { push @data, [ $qcf->Name => "" ]; } } my %k = map {@$_} @data; $o = [ map { $_->[0] } @data ]; $k = \%k; DONE: $c ||= join("\n", @comments) if @comments; return [ $c, $o, $k, $e ]; rt-5.0.1/share/html/REST/1.0/Forms/user/ns000644 000765 000024 00000004552 14005011336 020537 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/user/ns %# <%ARGS> $id <%perl> use RT::Users; my $field = "Name"; $field = "EmailAddress" if $id =~ /\@/; my $users = RT::Users->new($session{CurrentUser}); $users->Limit(FIELD => $field, OPERATOR => '=', VALUE => $id, CASESENSITIVE => 0); if ($users->Count == 0) { return (0, "No user named $id exists."); } return $users->Next->Id; rt-5.0.1/share/html/REST/1.0/Forms/user/default000644 000765 000024 00000015003 14005011336 021534 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/user/default %# <%ARGS> $id $format => 's' $changes => {} $fields => undef <%perl> my @comments; my ($c, $o, $k, $e) = ("", [], {}, 0); my %data = %$changes; my $user = RT::User->new($session{CurrentUser}); my @fields = qw(Name EmailAddress RealName NickName Gecos Organization Address1 Address2 City State Zip Country HomePhone WorkPhone MobilePhone PagerPhone FreeformContactInfo Comments Signature Lang Privileged Disabled); if ( $fields && %$fields ) { @fields = grep { exists $fields->{ lc $_ } } @fields; } my %fields = map { lc $_ => $_ } @fields; if ($id ne 'new') { $user->Load($id); if (!$user->Id) { return [ "# User $id does not exist.", [], {}, 1 ]; } } else { if (keys %data == 0) { return [ "# Required: Name, EmailAddress", [ qw(id Name EmailAddress Organization Password Comments) ], { id => "user/new", Name => "", EmailAddress => "", Organization => "", Password => "", Comments => "" }, 0 ]; } else { my %v; my %create = %fields; $create{name} = "Name"; $create{password} = "Password"; $create{emailaddress} = "EmailAddress"; $create{contactinfo} = "FreeformContactInfo"; # Do any fields need to be excluded here? foreach my $k (keys %data) { if (exists $create{lc $k}) { $v{$create{lc $k}} = delete $data{$k}; } } $user->Create(%v); unless ($user->Id) { return [ "# Could not create user.", [], {}, 1 ]; } $id = $user->Id; delete $data{id}; push(@comments, "# User $id created."); goto DONE if keys %data == 0; } } if (keys %data == 0) { my @data; push @data, [ id => "user/".$user->Id ]; unless ( $fields && %$fields && !exists $fields->{'password'} ) { push @data, [ Password => '********' ]; } for my $key (@fields) { my $val = $user->$key; if ( ( $fields && exists $fields->{ lc $key } ) || ( defined $format && $format eq 'l' ) || ( defined $val && $val ne '' ) ) { $key = "ContactInfo" if $key eq 'FreeformContactInfo'; push @data, [ $key => $val ]; } } # Custom fields my $CustomFields = $user->CustomFields; while ( my $CustomField = $CustomFields->Next() ) { # show cf unless there are specified fields that don't include it next unless ( !%$fields || exists $fields->{ lc "CF-" . $CustomField->Name } ); next unless $CustomField->CurrentUserHasRight('SeeCustomField'); my $CFvalues = $user->CustomFieldValues( $CustomField->Id ); my @CFvalues; while ( my $CFvalue = $CFvalues->Next() ) { push @CFvalues, $CFvalue->Content; } push @data, [ "CF-" . $CustomField->Name => \@CFvalues ]; } my %k = map {@$_} @data; $o = [ map {$_->[0]} @data ]; $k = \%k; } else { my ($get, $set, $key, $val, $n, $s); my $updated; foreach $key (keys %data) { $val = $data{$key}; $key = lc $key; $n = 1; if ($key eq 'name' || $key eq 'emailaddress' || $key eq 'contactinfo' || exists $fields{$key}) { if (exists $fields{$key}) { $key = $fields{$key}; } else { $key = "FreeformContactInfo" if $key eq 'contactinfo'; $key = "EmailAddress" if $key eq 'emailaddress'; $key = "Name" if $key eq 'name'; } $set = "Set$key"; next if $val eq $user->$key; ($n, $s) = $user->$set($val); } elsif ($key eq 'password') { ($n, $s) = $user->SetPassword($val) unless $val =~ /^\**$/; } elsif ($key ne 'id') { $n = 0; $s = "Unknown field."; } SET: if ($n == 0) { $e = 1; push @comments, "# $key: $s"; unless (@$o) { my %o = keys %$changes; delete @o{"id", @fields}; @$o = ("id", @fields, keys %o); $k = $changes; } } else { $updated ||= 1; } } push(@comments, "# User $id updated.") if $updated; } DONE: $c ||= join("\n", @comments) if @comments; return [ $c, $o, $k, $e ]; rt-5.0.1/share/html/REST/1.0/Forms/attachment/default000644 000765 000024 00000007460 14005011336 022716 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/attachment/default %# <%ARGS> $id $args => undef <%INIT> my @data; my ($c, $o, $k, $e) = ("", [], {}, ""); my $attachment = RT::Attachment->new($session{CurrentUser}); $attachment->Load($id); unless ( $attachment->Id ) { return [ "# Attachment $id does not exist.", [], {}, 1 ]; } unless ( $attachment->Id eq $id ) { return [ "# Invalid attachment id: $id", [], {}, 1 ]; } my @arglist = split( '/', $args || "" ); my $content; if ( @arglist and $arglist[0] eq 'content' ) { $c = $attachment->OriginalContent; $r->content_type($attachment->ContentType) if $attachment->ContentType !~ /^text\//; } else { my @data; push @data, [ id => "attachment/" . $attachment->Id ]; push @data, [ Subject => $attachment->Subject ]; push @data, [ Creator => $attachment->Creator ]; push @data, [ Created => $attachment->Created ]; push @data, [ Transaction => $attachment->TransactionId ]; push @data, [ Parent => "attachment/" . $attachment->Parent ]; push @data, [ MessageId => $attachment->MessageId ]; push @data, [ Filename => $attachment->Filename ]; push @data, [ ContentType => $attachment->ContentType ]; push @data, [ ContentEncoding => $attachment->ContentEncoding ]; push @data, [ Headers => $attachment->Headers ]; if ( $attachment->ContentType =~ m|^text/| ) { push @data, [ Content => $attachment->Content ]; } else { push @data, [ Content => "Content is not text and will not be displayed!\n" . "Use \"rt show attachment//content [> file.ext]\" to get the content." ]; } my %k = map {@$_} @data; $o = [ map { $_->[0] } @data ]; $k = \%k; } return [ $c, $o, $k, $e ]; rt-5.0.1/share/html/REST/1.0/Forms/group/ns000644 000765 000024 00000004465 14005011336 020720 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/group/ns %# <%ARGS> $id <%perl> use RT::Groups; my $groups = RT::Groups->new($session{CurrentUser}); $groups->Limit(FIELD => 'Name', OPERATOR => '=', VALUE => $id, CASESENSITIVE => 0); if ($groups->Count == 0) { return (0, "No group named $id exists."); } return $groups->Next->Id; rt-5.0.1/share/html/REST/1.0/Forms/group/default000644 000765 000024 00000014574 14005011336 021726 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/group/default %# <%ARGS> $id $format => 's' $fields => undef # these are the fields passed to the rt "-f" flag. $changes => {} <%perl> my @comments; my ($c, $o, $k, $e) = ("", [], {}, 0); my %data = %$changes; my $group = RT::Group->new($session{CurrentUser}); my @fields = qw(Name Description Disabled); if ( $fields && %$fields ) { @fields = grep { exists $fields->{ lc $_ } } @fields; } my %fields = map { lc $_ => $_ } @fields; if ($id ne 'new') { $group->Load($id); if (!$group->Id) { return [ "# Group $id does not exist.", [], {}, 1 ]; } } else { if (%data == 0) { return [ "# Required: Name", [ qw(id Name Description) ], { id => "group/new", Name => "", Description => "" }, 0 ]; } else { my %v; my %create = %fields; $create{name} = "Name"; $create{description} = "Description"; # Do any fields need to be excluded here? foreach my $k (keys %data) { if (exists $create{lc $k}) { $v{$create{lc $k}} = delete $data{$k}; } } $group->CreateUserDefinedGroup(%v); unless ($group->Id) { return [ "# Could not create group.", [], {}, 1 ]; } $id = $group->Id; delete $data{id}; push(@comments, "# Group $id created."); goto DONE if %data == 0; } } if (%data == 0) { my @data; push @data, [ id => "group/".$group->Id ]; foreach my $key (@fields) { push @data, [ $key => $group->$key ]; } # Members unless ( $fields && !exists $fields->{members} ) { my $gms = []; my $GroupMembers = $group->MembersObj(); while ( my $mo = $GroupMembers->Next() ) { if ( $mo->MemberObj->IsGroup ) { my $us = $mo->MemberObj->Object->UserMembersObj(); my @users; while ( my $u = $us->Next() ) { push @users, $u->RealName . ' <' . $u->EmailAddress . '>'; } push @$gms, 'GROUP [' . $mo->MemberObj->Object->Name . ']' . ' (' . join( ';', @users ) . ')'; } elsif ( $mo->MemberObj->IsUser ) { push @$gms, $mo->MemberObj->Object->RealName . ' <' . $mo->MemberObj->Object->EmailAddress . '>'; } } push @data, [ Members => $gms ]; } # Custom fields my $CustomFields = $group->CustomFields; while ( my $CustomField = $CustomFields->Next() ) { next unless ( !%$fields || exists $fields->{ lc "CF-" . $CustomField->Name } ); next unless $CustomField->CurrentUserHasRight('SeeCustomField'); my $CFvalues = $group->CustomFieldValues( $CustomField->Id ); my @CFvalues; while ( my $CFvalue = $CFvalues->Next() ) { push @CFvalues, $CFvalue->Content; } push @data, [ "CF-" . $CustomField->Name => \@CFvalues ]; } my %k = map {@$_} @data; $o = [ map {$_->[0]} @data ]; $k = \%k; } else { my ($get, $set, $key, $val, $n, $s); my $updated; foreach $key (keys %data) { $val = $data{$key}; $key = lc $key; $n = 1; if ($key eq 'name' || $key eq 'description' || exists $fields{$key}) { if (exists $fields{$key}) { $key = $fields{$key}; } else { $key = "Description" if $key eq 'description'; $key = "Name" if $key eq 'name'; } $set = "Set$key"; next if $val eq $group->$key; ($n, $s) = $group->$set($val); } elsif ($key ne 'id') { $n = 0; $s = "Unknown field."; } SET: if ($n == 0) { $e = 1; push @comments, "# $key: $s"; unless (@$o) { my %o = keys %$changes; delete @o{"id", @fields}; @$o = ("id", @fields, keys %o); $k = $changes; } } else { $updated ||= 1; } } push(@comments, "# Group $id updated.") if $updated; } DONE: $c ||= join("\n", @comments) if @comments; return [ $c, $o, $k, $e ]; rt-5.0.1/share/html/REST/1.0/Forms/group/customfields000644 000765 000024 00000006017 14005011336 022774 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/group/customfields %# <%ARGS> $id $format => 's' $changes => {} <%perl> my @comments; my ($c, $o, $k, $e) = ("", [], {}, 0); my $group = RT::Group->new($session{CurrentUser}); $group->Load($id); if (!$group->Id) { return [ "# Group $id does not exist.", [], {}, 1 ]; } if (%$changes) { $e = 1; $c = "Cannot modify Group CF definitions via REST"; goto DONE; } my @data; push @data, [ id => "group/" . $group->Id ]; my $gcfs = $group->CustomFields; while ( my $gcf = $gcfs->Next() ) { if ( $format eq "l" ) { my $cfadmin = ( $gcf->SingleValue ? 'SingleValue' : 'MultiValue' ) . $gcf->Type . ( $gcf->Pattern ? ( ' ' . $gcf->FriendlyPattern ) : '' ); push @data, [ $gcf->Name . ' (' . $gcf->Description . ')' => $cfadmin ]; } else { push @data, [ $gcf->Name => "" ]; } } my %k = map {@$_} @data; $o = [ map { $_->[0] } @data ]; $k = \%k; DONE: $c ||= join("\n", @comments) if @comments; return [ $c, $o, $k, $e ]; rt-5.0.1/share/html/REST/1.0/Forms/transaction/default000644 000765 000024 00000012017 14005011336 023105 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# REST/1.0/Forms/transaction %# <%ARGS> $id $format => undef $fields => undef <%INIT> my $trans = RT::Transactions->new($session{CurrentUser}); my ($c, $o, $k, $e) = ("", [], {} , ""); my $tid = $id; $trans->Limit(FIELD => 'Id', OPERATOR => '=', VALUE => $tid); if ($tid) { my @data; my $t = RT::Transaction->new($session{CurrentUser}); $t->Load($tid); if ($format eq "l") { push @data, [ id => $t->Id ]; push @data, [ Ticket => $t->Ticket ] if (!%$fields || exists $fields->{lc 'Ticket'}); push @data, [ TimeTaken => $t->TimeTaken ] if (!%$fields || exists $fields->{lc 'TimeTaken'}); push @data, [ Type => $t->Type ] if (!%$fields || exists $fields->{lc 'Type'}); push @data, [ Field => $t->Field ] if (!%$fields || exists $fields->{lc 'Field'}); push @data, [ OldValue => $t->OldValue ] if (!%$fields || exists $fields->{lc 'OldValue'}); push @data, [ NewValue => $t->NewValue ] if (!%$fields || exists $fields->{lc 'NewValue'}); push @data, [ Data => $t->Data ] if (!%$fields || exists $fields->{lc 'Data'}); push @data, [ Description => $t->Description ] if (!%$fields || exists $fields->{lc 'Description'}); push @data, [ Content => $t->Content ] if (!%$fields || exists $fields->{lc 'Content'}); if (!%$fields || exists $fields->{lc 'Content'}) { my $creator = RT::User->new($session{CurrentUser}); $creator->Load($t->Creator); push @data, [ Creator => $creator->Name ]; } push @data, [ Created => $t->Created ] if (!%$fields || exists $fields->{lc 'Created'}); if (!%$fields || exists $fields->{lc 'Attachments'}) { my $attachlist; my $attachments = $t->Attachments; while (my $a = $attachments->Next) { my $size = length($a->Content); if ($size > 1024) { $size = int($size/102.4)/10 . "k"; } else { $size .= "b"; } my $name = (defined $a->Filename and length $a->Filename) ? $a->Filename : "untitled"; $attachlist .= "\n" . $a->Id.": $name ($size)"; } push @data, [Attachments => $attachlist]; } } else { push @data, [ id => $t->Id ]; push @data, [ Description => $t->Description ]; } my %k = map {@$_} @data; $o = [ map {$_->[0]} @data ]; $k = \%k; } #else { # my (@data, $tids); # $format ||= "s"; # $format = "l" if (%$fields); # # while (my $t = $trans->Next) { # my $tid = $t->Id; # if ($format eq "l") { # $tids .= "," if $tids; # $tids .= $tid; # } else { # push @$o, $tid; # $k->{$tid} = $t->Description; # } # } #} return [ $c, $o, $k, $e ]; rt-5.0.1/share/html/Helpers/RightsInspector/000755 000765 000024 00000000000 14005011336 021600 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Helpers/Autocomplete/000755 000765 000024 00000000000 14005011336 021112 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Helpers/autohandler000644 000765 000024 00000004135 14005011336 020705 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> RT::Interface::Web::CacheControlExpiresHeaders( Time => 'no-cache' ); $m->call_next; rt-5.0.1/share/html/Helpers/ShowSimplifiedRecipients000644 000765 000024 00000014757 14005011336 023366 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> $m->abort unless RT->Config->Get('SimplifiedRecipients', $session{'CurrentUser'}); my $TicketObj = RT::Ticket->new($session{CurrentUser}); $TicketObj->Load($ARGS{id}); $m->abort unless $TicketObj->id && $ARGS{UpdateType}; if ( $ARGS{UpdateType} eq 'private' ) { $m->abort unless $TicketObj->CurrentUserHasRight( 'CommentOnTicket' ) || $TicketObj->CurrentUserHasRight( 'ModifyTicket' ); } else { $m->abort unless $TicketObj->CurrentUserHasRight( 'ReplyToTicket' ) || $TicketObj->CurrentUserHasRight( 'ModifyTicket' ); } my @dryrun = $TicketObj->DryRun( sub { local $ARGS{UpdateContent} ||= "Content"; ProcessUpdateMessage(ARGSRef => \%ARGS, TicketObj => $TicketObj ); ProcessTicketWatchers(ARGSRef => \%ARGS, TicketObj => $TicketObj ); ProcessTicketBasics( ARGSRef => \%ARGS, TicketObj => $TicketObj ); ProcessTicketLinks( ARGSRef => \%ARGS, TicketObj => $TicketObj ); ProcessTicketDates( ARGSRef => \%ARGS, TicketObj => $TicketObj ); ProcessObjectCustomFieldUpdates(ARGSRef => \%ARGS, Object => $TicketObj ); ProcessTicketReminders( ARGSRef => \%ARGS, TicketObj => $TicketObj ); } ); $m->abort unless @dryrun; my %headers = (To => {}, Cc => {}, Bcc => {}); my %no_squelch = (To => {}, Cc => {}, Bcc => {}); my @scrips = map {@{$_->Scrips->Prepared}} @dryrun; if (@scrips) { for my $scrip (grep $_->ActionObj->Action->isa('RT::Action::SendEmail'), @scrips) { my $action = $scrip->ActionObj->Action; for my $type (qw(To Cc Bcc)) { for my $addr ($action->$type()) { if (grep {$addr->address eq $_} @{$action->{NoSquelch}{$type} || []}) { $no_squelch{$type}{$addr->address} = $addr; } else { $headers{$type}{$addr->address} = $addr; } } } } } my %recips; my %squelched = ProcessTransactionSquelching( \%ARGS ); my $squelched_config = !( RT->Config->Get('SquelchedRecipients', $session{'CurrentUser'}) ); my %submitted; $submitted{$_} = 1 for split /,/, $ARGS{TxnRecipients}; % if ( scalar(map { keys %{$headers{$_}} } qw(To Cc Bcc)) ) {
      % } % for my $type (qw(To Cc Bcc)) { % next unless keys %{$headers{$type}} or keys %{$no_squelch{$type}};
      • <% $type %>:
        % for my $addr (sort {$a->address cmp $b->address} values %{$headers{$type}}) { % my $checked = $submitted{$addr->address} ? not $squelched{$addr->address} : $squelched_config; % $m->callback(CallbackName => 'BeforeAddress', Ticket => $TicketObj, Address => $addr, Type => $type, Checked => \$checked); % $recips{$addr->address}++;
        value="<%$addr->address%>" id="TxnSendMailTo-<% $addr->address %>-<% $recips{$addr->address} %>" class="custom-control-input" />
        % $m->callback(CallbackName => 'AfterAddress', Ticket => $TicketObj, Address => $addr, Type => $type); % } % for my $addr (sort {$a->address cmp $b->address} values %{$no_squelch{$type}}) {
        % if ( $type eq 'Cc' ) { (<&|/l&>explicit one-time Cc) % } else { (<&|/l&>explicit one-time Bcc) % }
        % }
      % } % $m->callback( CallbackName => 'AfterRecipients', TicketObj => $TicketObj );

      <&|/l, RT->Config->Get('WebPath')."/Ticket/ModifyPeople.html?id=".$TicketObj->Id, &>Uncheck boxes to disable notifications to the listed recipients for this transaction only; persistent squelching is managed on the People page.

      % unless ($TicketObj->CurrentUserHasRight('ShowOutgoingEmail')) { " /> % } % $m->abort(); rt-5.0.1/share/html/Helpers/TicketHistory000644 000765 000024 00000005205 14005011336 021203 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $id <%INIT> my $TicketObj = RT::Ticket->new($session{'CurrentUser'}); $TicketObj->Load($id); my $attachments = $TicketObj->Attachments; my $attachment_content = $TicketObj->TextAttachments; my %extra_args; $m->callback( CallbackName => 'ExtraShowHistoryArguments', Ticket => $TicketObj, ExtraArgs => \%extra_args ); <& /Elements/ShowHistory, Object => $TicketObj, ShowHeaders => $ARGS{'ShowHeaders'}, ReverseTxns => $ARGS{'ReverseTxns'}, Attachments => $attachments, AttachmentContent => $attachment_content, %extra_args, &> % $m->abort(); rt-5.0.1/share/html/Helpers/Admin/000755 000765 000024 00000000000 14005011336 017501 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Helpers/AddTimeWorked000644 000765 000024 00000006334 14005011336 021065 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $id $seconds => 0 $comment => undef <%INIT> my $Ticket = RT::Ticket->new($session{'CurrentUser'}); $Ticket->Load( $id ); # round up 30s or more my $minutes = int(0.5 + $seconds / 60); my ($ok, $msg); if ($minutes == 0) { # avoid "That is already the current value" error $ok = 1; $msg = loc("Worked [quant,_1,minute,minutes]", 0); if ($comment) { my ($comment_ok, $comment_msg) = $Ticket->Comment(Content => $comment); if (!$comment_ok) { ($ok, $msg) = ($comment_ok, $comment_msg); } } } else { if ($comment) { ($ok, $msg) = $Ticket->Comment( Content => $comment, TimeTaken => $minutes, ); } else { my $total_worked = $Ticket->TimeWorked + $minutes; ($ok, $msg) = $Ticket->SetTimeWorked($total_worked); } if ($ok) { if ($minutes < 60) { $msg = loc("Worked [quant,_1,minute,minutes]", $minutes); } else { $msg = loc("Worked [quant,_1,hour,hours] ([quant,_2,minute,minutes])", sprintf("%.2f", $minutes / 60), $minutes); } } } $r->content_type('application/json; charset=utf-8'); $m->print(JSON({ ok => $ok, msg => $msg })); $m->abort; rt-5.0.1/share/html/Helpers/Toggle/000755 000765 000024 00000000000 14005011336 017672 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Helpers/PreviewScrips000644 000765 000024 00000016215 14005011336 021206 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> my $TicketObj = RT::Ticket->new($session{CurrentUser}); $TicketObj->Load($ARGS{id}); $m->abort unless $TicketObj->id && $ARGS{UpdateType}; $m->abort unless $TicketObj->CurrentUserHasRight('ShowOutgoingEmail'); if ( $ARGS{UpdateType} eq 'private' ) { $m->abort unless $TicketObj->CurrentUserHasRight( 'CommentOnTicket' ) || $TicketObj->CurrentUserHasRight( 'ModifyTicket' ); } else { $m->abort unless $TicketObj->CurrentUserHasRight( 'ReplyToTicket' ) || $TicketObj->CurrentUserHasRight( 'ModifyTicket' ); } my @dryrun = $TicketObj->DryRun( sub { local $ARGS{UpdateContent} ||= "Content"; ProcessUpdateMessage(ARGSRef => \%ARGS, TicketObj => $TicketObj ); ProcessTicketWatchers(ARGSRef => \%ARGS, TicketObj => $TicketObj ); ProcessTicketBasics( ARGSRef => \%ARGS, TicketObj => $TicketObj ); ProcessTicketLinks( ARGSRef => \%ARGS, TicketObj => $TicketObj ); ProcessTicketDates( ARGSRef => \%ARGS, TicketObj => $TicketObj ); ProcessObjectCustomFieldUpdates(ARGSRef => \%ARGS, Object => $TicketObj ); ProcessTicketReminders( ARGSRef => \%ARGS, TicketObj => $TicketObj ); } ); my %recips; $m->abort unless @dryrun; my %squelched = ProcessTransactionSquelching( \%ARGS ); my $squelched_config = !( RT->Config->Get('SquelchedRecipients', $session{'CurrentUser'}) ); my %submitted; $submitted{$_} = 1 for split /,/, $ARGS{TxnRecipients};

      <&|/l, RT->Config->Get('WebPath')."/Ticket/ModifyPeople.html?id=".$TicketObj->Id, &>Uncheck boxes to disable notifications to the listed recipients for this transaction only; persistent squelching is managed on the People page.

      % my @scrips = grep {$_->ActionObj->Action->isa('RT::Action::SendEmail')} % map {@{$_->Scrips->Prepared}} @dryrun; % if (@scrips) { % if ( grep { % my $s = $_; % my $action = $s->ActionObj->Action; % scalar(map { $action->$_ } qw(To Cc Bcc)) % } @scrips ) {
      value="1" class="custom-control-input">
      % } % for my $scrip (@scrips) { <% $scrip->Description || loc('Scrip #[_1]',$scrip->id) %>
      <&|/l, loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->Template)&>[_1] [_2] with template [_3]
      % for my $type (qw(To Cc Bcc)) { % my $action = $scrip->ActionObj->Action; % my @addresses = $action->$type(); % next unless @addresses;
        % for my $addr (@addresses) {
      • % my $checked = $submitted{$addr->address} ? not $squelched{$addr->address} : $squelched_config; % $m->callback(CallbackName => 'BeforeAddress', Ticket => $TicketObj, Address => $addr, Type => $type, Checked => \$checked); % $recips{$addr->address}++;
        <%loc($type)%>:
        % my $show_checkbox = 1; % if ( grep {$_ eq $addr->address} @{$action->{NoSquelch}{$type}} ) { % $show_checkbox = 0; % }
        % if ( $show_checkbox ) { value="<%$addr->address%>" id="TxnSendMailTo-<% $addr->address %>-<% $recips{$addr->address} %>" /> % } % $m->callback(CallbackName => 'AfterAddress', Ticket => $TicketObj, Address => $addr, Type => $type); % unless ( $show_checkbox ) { % if ( $type eq 'Cc' ) { (<&|/l&>explicit one-time Cc) % } % else { (<&|/l&>explicit one-time Bcc) % } % }
      • % }
      % } % if (RT->Config->Get('PreviewScripMessages')) { % }
      % } % } % $m->callback( CallbackName => 'AfterRecipients', TicketObj => $TicketObj ); " /> % $m->abort(); rt-5.0.1/share/html/Helpers/BuildFormatString000644 000765 000024 00000005323 14005011336 021776 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> my ( $Format, $AvailableColumns, $CurrentFormat ) = $m->comp( '/Search/Elements/BuildFormatString', %ARGS ); $r->content_type('application/json; charset=utf-8'); my $CurrentDisplayColumns = ''; my $i=0; my $current = $ARGS{CurrentDisplayColumns} || ''; $current =~ s/^\d+>//; for my $field ( @$CurrentFormat ) { $CurrentDisplayColumns .= qq!'; $i++; } $m->out( JSON({ status => 'success', Format => $Format, CurrentDisplayColumns => $CurrentDisplayColumns, }) ); $m->abort; rt-5.0.1/share/html/Helpers/SpawnLinkedTicket000644 000765 000024 00000004513 14005011336 021762 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> my $query_string = $m->comp('/Elements/QueryString', CloneTicket => $CloneTicket, Queue => $CloneQueue, $LinkType => $CloneTicket, ); RT::Interface::Web::Redirect( RT->Config->Get('WebURL') ."Ticket/Create.html?$query_string" ); <%ARGS> $CloneTicket => undef $CloneQueue => undef $LinkType => undef rt-5.0.1/share/html/Helpers/UserInfo000644 000765 000024 00000005250 14005011336 020130 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $id <%init> my %users; $id = [$id] unless ref($id) eq 'ARRAY'; for my $uid (@$id) { next if exists $users{$uid}; my $user = RT::User->new($session{'CurrentUser'}); $user->Load($uid); unless ($user->id) { $users{$uid} = undef; next; } my %user = map { $_ => $user->$_ } qw(id Name EmailAddress RealName); $user{Privileged} = $user->Privileged ? JSON::true : JSON::false; $user{_formatted} = $user->Format; $user{_html} = $m->scomp('/Elements/ShowUser', User => $user); $users{$uid} = \%user; } $r->content_type('application/json; charset=utf-8'); $m->out( JSON(\%users) ); $m->abort; rt-5.0.1/share/html/Helpers/TextDiff000644 000765 000024 00000005134 14005011336 020114 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> my ( $old, $new ); if ($TransactionId) { my $txn = RT::Transaction->new( $session{'CurrentUser'} ); $txn->Load($TransactionId); if ( $txn->Id ) { $old = $txn->OldValue; $new = $txn->NewValue; } else { RT->Logger->error("Could not load transaction #$TransactionId"); $m->abort; } } else { $m->abort; } use Text::WordDiff; my $diff = word_diff( \$old, \$new, { STYLE => 'HTML' } ); $diff = $m->comp( '/Elements/ScrubHTML', Content => $diff ); $diff =~ s|\n|
      |g; <% $diff |n %> % $m->abort(); <%ARGS> $TransactionId => undef rt-5.0.1/share/html/Helpers/CollectionListRow000644 000765 000024 00000005546 14005011336 022025 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $DisplayFormat => undef $ObjectClass => undef $MaxItems => undef $InlineEdit => undef $i => undef $ObjectId => undef $Warning => undef <%INIT> # Scrub the html of the format string to remove any potential nasties. $DisplayFormat = $m->comp('/Elements/ScrubHTML', Content => $DisplayFormat); my @Format = $m->comp('/Elements/CollectionAsTable/ParseFormat', Format => $DisplayFormat); $m->abort unless ( $ObjectClass // '' ) eq 'RT::Ticket'; my $record = $ObjectClass->new($session{CurrentUser}); $record->Load($ObjectId); $m->abort unless $record->id; $m->comp('/Elements/CollectionAsTable/Row', i => $i, Format => \@Format, record => $record, maxitems => $MaxItems, Class => $record->ColumnMapClassName, Warning => $Warning, InlineEdit => $InlineEdit, ); $m->abort; rt-5.0.1/share/html/Helpers/ShortcutHelp000644 000765 000024 00000004234 14005011336 021023 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $show_search => 0 $show_bulk_update => 0 $show_ticket_reply => 0 $show_ticket_comment => 0 <& /Elements/ShortcutHelp, %ARGS &> % $m->abort; rt-5.0.1/share/html/Helpers/TicketTimer000644 000765 000024 00000020266 14005011336 020626 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $id <%INIT> my $Ticket = RT::Ticket->new($session{'CurrentUser'}); $Ticket->Load( $id ); my $Now = RT::Date->new($session{'CurrentUser'}); $Now->SetToNow; my $SubmitURL = RT->Config->Get('WebPath') . '/Helpers/AddTimeWorked'; <& /Elements/Header, Title => loc('Timer for #[_1]: [_2]', $Ticket->Id, $Ticket->Subject), RichText => 0, ShowBar => 0, ShowTitle => 0 &>
      <& /Elements/MessageBox, Name => 'Comment', Type => 'text/plain', Height => 3, Width => undef, # sets width:100% CSS Placeholder => loc('Comments for the ticket'), SuppressAttachmentWarning => 1, IncludeSignature => 0, IncludeArticle => 0, &>
      <&|/l, $Now->AsString &>Started at [_1].
      % if ($Ticket->TimeEstimated) {
      <&|/l&>Time estimated: <& /Ticket/Elements/ShowTime, minutes => $Ticket->TimeEstimated &>
      % }
      <&|/l&>An error occurred while submitting time. Please submit your time manually.
      % $m->abort(); rt-5.0.1/share/html/Helpers/TicketAttachments000644 000765 000024 00000004553 14005011336 022022 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $id $Selectable => undef $Count => undef @AttachExisting => () <%INIT> my $TicketObj = RT::Ticket->new($session{'CurrentUser'}); $TicketObj->Load($id); <& /Ticket/Elements/ShowAttachments, Ticket => $TicketObj, HideTitleBox => 1, Selectable => $Selectable, Count => $Count, Checked => \@AttachExisting, &> % $m->abort(); rt-5.0.1/share/html/Helpers/TicketUpdate000644 000765 000024 00000006047 14005011336 020771 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $r->content_type('application/json; charset=utf-8'); <% JSON({ actions => \@Actions, }) |n %> % $m->abort; <%ARGS> $id <%INIT> my @Actions; my $TicketObj = LoadTicket($id); # fill ACL cache $TicketObj->CurrentUser->PrincipalObj->HasRights( Object => $TicketObj ); $m->callback(CallbackName => 'ProcessArguments', Ticket => $TicketObj, ARGSRef => \%ARGS, Actions => \@Actions); push @Actions, ProcessUpdateMessage( ARGSRef => \%ARGS, Actions => \@Actions, TicketObj => $TicketObj, ); push @Actions, ProcessTicketWatchers(ARGSRef => \%ARGS, TicketObj => $TicketObj ); push @Actions, ProcessTicketBasics( ARGSRef => \%ARGS, TicketObj => $TicketObj ); push @Actions, ProcessTicketLinks( ARGSRef => \%ARGS, TicketObj => $TicketObj ); push @Actions, ProcessTicketDates( ARGSRef => \%ARGS, TicketObj => $TicketObj ); push @Actions, ProcessObjectCustomFieldUpdates(ARGSRef => \%ARGS, TicketObj => $TicketObj ); push @Actions, ProcessTicketReminders( ARGSRef => \%ARGS, TicketObj => $TicketObj ); rt-5.0.1/share/html/Helpers/Upload/000755 000765 000024 00000000000 14005011336 017675 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Helpers/TicketHistoryPage000644 000765 000024 00000007140 14005011336 022000 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $id $oldestTransactionsFirst => RT->Config->Get("OldestTransactionsFirst", $session{CurrentUser}); $lastTransactionId => undef $focusTransactionId => undef $loadAll => 0 <%INIT> my $TicketObj = RT::Ticket->new($session{'CurrentUser'}); $TicketObj->Load($id); my %extra_args; $m->callback( CallbackPage => '/Helpers/TicketHistory', CallbackName => 'ExtraShowHistoryArguments', Ticket => $TicketObj, ExtraArgs => \%extra_args ); my $transactions = $TicketObj->Transactions; my $order = $oldestTransactionsFirst ? 'ASC' : 'DESC'; if ($lastTransactionId) { $transactions->Limit( FIELD => 'id', OPERATOR => $oldestTransactionsFirst ? '>' : '<', VALUE => $lastTransactionId ); } $transactions->OrderByCols( { FIELD => 'Created', ORDER => $order }, { FIELD => 'id', ORDER => $order }, ); if ($focusTransactionId) { # make sure we load enough if we need to focus a transaction $transactions->Limit( FIELD => 'id', OPERATOR => $oldestTransactionsFirst ? '<=' : '>=', VALUE => $focusTransactionId ); } elsif (!$loadAll) { # otherwise, just load the standard page of 10 transactions $transactions->RowsPerPage(10); $transactions->FirstPage(); } <& /Elements/ShowHistoryPage, Object => $TicketObj, ShowHeaders => $ARGS{'ShowHeaders'}, Transactions => $transactions, %extra_args, &> % $m->abort(); rt-5.0.1/share/html/Helpers/Upload/Delete000644 000765 000024 00000004373 14005011336 021031 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $Name => '' $Token => '' <%init> delete $session{'Attachments'}{ $Token }{ $Name }; $session{'Attachments'} = $session{'Attachments'}; $r->content_type('application/json; charset=utf-8'); $m->out( JSON({status => 'success'}) ); $m->abort; rt-5.0.1/share/html/Helpers/Upload/Add000644 000765 000024 00000004550 14005011336 020314 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $Token => '' <%init> my ( $status, $msg ) = ProcessAttachments( Token => $Token, ARGSRef => \%ARGS ); if ( $status ) { $r->content_type( 'application/json; charset=utf-8' ); $m->out( JSON( { status => 'success' } ) ); } else { $r->status( 400 ); $r->content_type( 'text/plain; charset=utf-8' ); $m->out( $msg ); } $m->abort; rt-5.0.1/share/html/Helpers/Toggle/TicketBookmark000644 000765 000024 00000004170 14005011336 022530 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $id $Toggle => 1 <%INIT> $m->comp('/Ticket/Elements/Bookmark', id => $id, Toggle => $Toggle ); $m->abort(); rt-5.0.1/share/html/Helpers/Toggle/ShowRequestor000644 000765 000024 00000005042 14005011336 022450 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> my $TicketTemplate = "/Ticket/Elements/ShowRequestorTickets$Status"; $TicketTemplate = "/Ticket/Elements/ShowRequestorTicketsActive" unless RT::Interface::Web->ComponentPathIsSafe($TicketTemplate) and $m->comp_exists($TicketTemplate); my $user_obj = RT::User->new($session{CurrentUser}); my ($val, $msg) = $user_obj->Load($Requestor); unless ($val) { $RT::Logger->error("Unable to load User $Requestor: $msg"); } else { $m->comp( $TicketTemplate, Requestor => $user_obj ); } $m->abort(); <%ARGS> $Status $Requestor rt-5.0.1/share/html/Helpers/Admin/EditCustomFieldValue000644 000765 000024 00000006432 14005011336 023452 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $r->content_type('application/json; charset=utf-8'); <% JSON( $out ) |n %> % $m->abort; <%INIT> my $out = {}; if ( $cf_id ) { my $cf = RT::CustomField->new($session{CurrentUser}); $cf->LoadById($cf_id); if ( !$cf->Name ) { $out = { status => 0, message => loc("Current User cannot see this Custom Field, terminating") }; } else { my ( $ret, $msg, $html ); if ( $action eq 'add' ) { ( $ret, $msg ) = $cf->AddValue( Name => $ARGS{name}, SortOrder => $ARGS{sort_order} || 0, Description => $ARGS{description}, Category => $ARGS{category}, ); $html = $m->scomp('/Admin/Elements/EditCustomFieldValues', CustomField => $cf ) if $ret; } elsif ( $action eq 'delete' ) { ( $ret, $msg ) = $cf->DeleteValue( $ARGS{value_id} ); $html = $m->scomp('/Admin/Elements/EditCustomFieldValues', CustomField => $cf ) if $ret; } else { ( $ret, $msg ) = ( 0, loc('Invalid action') ); } $out = { status => $ret || 0, message => $msg, html => $html }; } } else { $out = { status => 0, message => loc("No CustomField provided") }; } <%ARGS> $action => '' $cf_id => undef rt-5.0.1/share/html/Helpers/Autocomplete/Tickets000644 000765 000024 00000007166 14005011336 022455 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $r->content_type('application/json; charset=utf-8'); <% JSON( \@suggestions ) |n %> % $m->abort; <%ARGS> $return => '' $term => undef $max => undef $exclude => '' $limit => undef <%INIT> # Only allow certain return fields $return = 'id' unless $return =~ /^(?:id|Subject)$/; $m->abort unless defined $return and defined $term and length $term; my $CurrentUser = $session{'CurrentUser'}; # Require privileged users $m->abort unless $CurrentUser->Privileged; my @excludes; (my $prev, $term) = $term =~ /^((?:\d+\s+)*)(.*)/; @excludes = split ' ', $prev if $prev; push @excludes, split ' ', $exclude if $exclude; $m->abort unless $term; my %fields = %{ RT->Config->Get('TicketAutocompleteFields') || { id => 'STARTSWITH', Subject => 'LIKE' } }; my $tickets = RT::Tickets->new( $CurrentUser ); my @clauses; $term =~ s/(['\\])/\\$1/g; #' while (my ($name, $op) = each %fields) { $op = 'STARTSWITH' unless $op =~ /^(?:LIKE|(?:START|END)SWITH|=|!=)$/i; push @clauses, qq{$name $op '$term'}; } my $sql = join ' OR ', @clauses; if ( @excludes ) { # exclude ids already these $sql = join ' AND ', "($sql)", map { qq{id != '$_'} } @excludes; } $m->callback( CallbackName => 'ModifyMaxResults', max => \$max ); $max //= 10; # Add additional limit to SQL if provided if ( $limit ) { $sql = "($sql) AND ($limit)"; } $tickets->FromSQL($sql); $tickets->RowsPerPage( $max ); my @suggestions; while ( my $ticket = $tickets->Next ) { my $formatted = loc("#[_1]: [_2]", $ticket->Id, $ticket->Subject); push @suggestions, { label => $formatted, value => $ticket->$return }; } rt-5.0.1/share/html/Helpers/Autocomplete/autohandler000644 000765 000024 00000004132 14005011336 023343 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> RT::Interface::Web::CacheControlExpiresHeaders( Time => 2 * 60 ); $m->call_next; rt-5.0.1/share/html/Helpers/Autocomplete/Articles000644 000765 000024 00000006327 14005011336 022613 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $r->content_type('application/json; charset=utf-8'); <% JSON( \@suggestions ) |n %> % $m->abort; <%ARGS> $term => undef $max => 10 $op => 'STARTSWITH' $right => undef $return => 'Name' $queue => undef <%INIT> # Only allow certain return fields $return = 'Name' unless $return =~ /^(?:id|Name)$/; $m->abort unless defined $return and defined $term and length $term; # Sanity check the operator $op = 'STARTSWITH' unless $op =~ /^(?:LIKE|(?:START|END)SWITH|=|!=)$/i; $m->callback( CallbackName => 'ModifyMaxResults', max => \$max ); my $articles = RT::Articles->new( $session{CurrentUser} ); if( $queue ) { $articles->LimitAppliedClasses( Queue => $queue ); } $articles->RowsPerPage( $max ); $articles->Limit( FIELD => 'Name', OPERATOR => $op, VALUE => $term, ENTRYAGGREGATOR => 'OR', CASESENSITIVE => 0, ); my @suggestions; while (my $a = $articles->Next) { next if $right and not $a->CurrentUserHasRight($right); my $value = $a->$return; push @suggestions, { label => $a->Name, value => $value }; $m->callback( CallbackName => "ModifySuggestion", suggestions => @suggestions, label => $a ); } rt-5.0.1/share/html/Helpers/Autocomplete/Groups000644 000765 000024 00000006312 14005011336 022316 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $r->content_type('application/json; charset=utf-8'); <% JSON( \@suggestions ) |n %> % $m->abort; <%ARGS> $term => undef $max => undef $exclude => '' $op => 'LIKE' <%INIT> $m->abort unless defined $term and length $term; my $CurrentUser = $session{'CurrentUser'}; # Require privileged users $m->abort unless $CurrentUser->Privileged; # Sanity check the operator $op = 'LIKE' unless $op =~ /^(?:LIKE|(?:START|END)SWITH|=|!=)$/i; $m->callback( CallbackName => 'ModifyMaxResults', max => \$max ); $max //= 10; my $groups = RT::Groups->new( $CurrentUser ); $groups->RowsPerPage( $max ); $groups->LimitToUserDefinedGroups(); $groups->Limit( FIELD => 'Name', OPERATOR => $op, VALUE => $term, CASESENSITIVE => 0, ); # Exclude groups we don't want foreach (split /\s*,\s*/, $exclude) { $groups->Limit(FIELD => 'id', VALUE => $_, OPERATOR => '!=', ENTRYAGGREGATOR => 'AND'); } my @suggestions; while ( my $group = $groups->Next ) { my $suggestion = { id => $group->Id, label => $group->Label, value => $group->Name }; $m->callback( CallbackName => "ModifySuggestion", suggestion => $suggestion, group => $group ); push @suggestions, $suggestion; } rt-5.0.1/share/html/Helpers/Autocomplete/CustomFieldValues000644 000765 000024 00000011662 14005011336 024441 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $r->content_type('application/json; charset=utf-8'); <% JSON( \@suggestions ) |n %> % $m->abort; <%INIT> # Only autocomplete the last value my $term = (split /\n/, $ARGS{term} || '')[-1]; my $abort = sub { $r->content_type('application/json; charset=utf-8'); $m->out(JSON( [] )); $m->abort; }; unless ( exists $ARGS{ContextType} and exists $ARGS{ContextId} ) { RT->Logger->debug("No context provided"); $abort->(); } # Use _ParseObjectCustomFieldArgs to avoid duplicating the regex. # See the docs for _ParseObjectCustomFieldArgs for details on the data # structure returned. There will be only one CF, so drill down 2 layers # to get the cf id, if one is there. my %custom_fields = _ParseObjectCustomFieldArgs(\%ARGS, IncludeBulkUpdate => 1); my $CustomField; foreach my $class ( keys %custom_fields ){ foreach my $id ( keys %{$custom_fields{$class}} ){ ($CustomField) = keys %{$custom_fields{$class}{$id}}; } } unless ( $CustomField ) { RT->Logger->debug("No CustomField provided"); $abort->(); } my $SystemCustomFieldObj = RT::CustomField->new( RT->SystemUser ); my ($id, $msg) = $SystemCustomFieldObj->LoadById( $CustomField ) ; unless ( $id ) { RT->Logger->debug("Invalid CustomField provided: $msg"); $abort->(); } my $context_object = $SystemCustomFieldObj->LoadContextObject( $ARGS{ContextType}, $ARGS{ContextId} ); $abort->() unless $context_object; my $CustomFieldObj = RT::CustomField->new( $session{'CurrentUser'} ); if ( $SystemCustomFieldObj->ValidateContextObject($context_object) ) { # drop our privileges that came from calling LoadContextObject as the System User $context_object->new($session{'CurrentUser'}); $context_object->LoadById($ARGS{ContextId}); $CustomFieldObj->SetContextObject( $context_object ); } else { RT->Logger->debug("Invalid Context Object ".$context_object->id." for Custom Field ".$SystemCustomFieldObj->id); $abort->(); } ($id, $msg) = $CustomFieldObj->LoadById( $CustomField ); unless ( $CustomFieldObj->Name ) { RT->Logger->debug("Current User cannot see this Custom Field, terminating"); $abort->(); } my $values = $CustomFieldObj->Values; $values->Limit( FIELD => 'Name', OPERATOR => 'LIKE', VALUE => $term, SUBCLAUSE => 'autocomplete', CASESENSITIVE => 0, ); $values->Limit( ENTRYAGGREGATOR => 'OR', FIELD => 'Description', OPERATOR => 'LIKE', VALUE => $term, SUBCLAUSE => 'autocomplete', CASESENSITIVE => 0, ); $m->callback( CallbackName => 'ModifyMaxResults', max => \$ARGS{max}, term => $term, CustomField => $CustomFieldObj, ); $values->RowsPerPage( $ARGS{max} // 10 ); my @suggestions; while( my $value = $values->Next ) { push @suggestions, { value => $value->Name, label => $value->Description ? $value->Name . ' (' . $value->Description . ')' : $value->Name, }; } rt-5.0.1/share/html/Helpers/Autocomplete/Users000644 000765 000024 00000010652 14005011336 022142 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $r->content_type('application/json; charset=utf-8') if $abort; <% JSON( \@suggestions ) |n %> % $m->abort if $abort; <%ARGS> $return => '' $term => undef $delim => undef $max => undef $privileged => undef $exclude => '' $op => undef $include_nobody => 0 $include_system => 0 $abort => 1 <%INIT> # Only allow certain return fields $return = 'EmailAddress' unless $return =~ /^(?:EmailAddress|Name|RealName)$/; $m->abort unless defined $return and defined $term and length $term; # Use our delimeter if we have one if ( defined $delim and length $delim ) { if ( $delim eq ',' ) { $delim = qr/,\s*/; } else { $delim = qr/\Q$delim\E/; } # If the field handles multiple values, pop the last one off $term = (split $delim, $term)[-1] if $term =~ $delim; } my $CurrentUser = $session{'CurrentUser'}; # Require privileged users or overriding config $m->abort unless $CurrentUser->Privileged or RT->Config->Get('AllowUserAutocompleteForUnprivileged'); # the API wants a list of ids my @exclude = split /\s*,\s*/, $exclude; push @exclude, RT->SystemUser->id unless $include_system; push @exclude, RT->Nobody->id unless $include_nobody; $m->callback( CallbackName => 'ModifyMaxResults', max => \$max ); $max //= 10; my $users = RT::Users->new($CurrentUser); $users->SimpleSearch( Privileged => $privileged, Return => $return, Term => $term, Max => $max, Exclude => \@exclude, # If an operator is provided, check against only # the returned field using that operator $op ? ( Fields => { $return => $op } ) : (), ); my @suggestions; while ( my $user = $users->Next ) { my @searched_values; for my $field ( sort keys %{ RT->Config->Get('UserSearchFields') } ) { if ( $field =~ /^CF\.(?:\{(.*)}|(.*))$/ ) { push @searched_values, $user->CustomFieldValuesAsString( $1 || $2 ); } else { push @searched_values, $user->$field; } } my $text = join "\n", grep defined $_, @searched_values; my $suggestion = { id => $user->id, label => $user->Format, value => $user->$return, text => $text }; $m->callback( CallbackName => "ModifySuggestion", suggestion => $suggestion, user => $user ); push @suggestions, $suggestion; } rt-5.0.1/share/html/Helpers/Autocomplete/Queues000644 000765 000024 00000005674 14005011336 022320 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $r->content_type('application/json'); <% JSON( \@suggestions ) |n %> % $m->abort; <%ARGS> $term => undef $max => 10 $op => 'STARTSWITH' $right => undef $return => 'Name'; <%INIT> # Only allow certain return fields $return = 'Name' unless $return =~ /^(?:id|Name)$/; $m->abort unless defined $return and defined $term and length $term; # Sanity check the operator $op = 'STARTSWITH' unless $op =~ /^(?:LIKE|(?:START|END)SWITH|=|!=)$/i; my $queues = RT::Queues->new( $session{CurrentUser} ); $queues->RowsPerPage( $max ); $queues->Limit( FIELD => 'Name', OPERATOR => $op, VALUE => $term, ENTRYAGGREGATOR => 'OR', CASESENSITIVE => 0, ); my @suggestions; while (my $q = $queues->Next) { next if $right and not $q->CurrentUserHasRight($right); my $value = $q->$return; push @suggestions, { label => $q->Name, value => $value }; } rt-5.0.1/share/html/Helpers/Autocomplete/Owners000644 000765 000024 00000011013 14005011336 022306 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $r->content_type('application/json; charset=utf-8'); <% JSON( \@suggestions ) |n %> % $m->abort; <%ARGS> $return => 'Name' $limit => undef $term => undef $max => undef <%INIT> # Only allow certain return fields $return = 'Name' unless $return =~ /^(?:EmailAddress|Name|RealName|id)$/; $m->abort unless defined $return and defined $term and defined $limit; my $CurrentUser = $session{'CurrentUser'}; my %user_uniq_hash; my $isSU = $session{CurrentUser} ->HasRight( Right => 'SuperUser', Object => $RT::System ); $m->callback( CallbackName => 'ModifyMaxResults', max => \$max ); $max //= 10; # Turn RT::Ticket-1|RT::Queue-2 into ['RT::Ticket', 1], ['RT::Queue', 2] foreach my $spec (map { [split /\-/, $_, 2] } split /\|/, $limit) { next unless $spec->[0] =~ /^RT::(Ticket|Queue)$/; my $object = $spec->[0]->new( $session{'CurrentUser'} ); if ( $spec->[1] ) { $object->Load( $spec->[1] ); # Warn if we couldn't load an object unless ( $object->id ) { $RT::Logger->warn("Owner autocomplete couldn't load an '$spec->[0]' with id '$spec->[1]'"); next; } } my $Users = RT::Users->new( $session{CurrentUser} ); # Limit by our autocomplete term BEFORE we limit to OwnTicket because that # does a funky union hack $Users->SimpleSearch( Max => $max, Term => $term, Return => $return, ); $m->callback( CallbackName => 'ModifyOwnerAutocompleteSearch', ARGSRef => \%ARGS, Users => \$Users ); $Users->WhoHaveRight( Right => 'OwnTicket', Object => $object, IncludeSystemRights => 1, IncludeSuperusers => $isSU ); while ( my $User = $Users->Next() ) { next if $user_uniq_hash{ $User->Id }; $user_uniq_hash{ $User->Id() } = [ $User, $User->Format, ]; } } # Make sure we add Nobody if we don't already have it my $nobody = qr/^n(?:o(?:b(?:o(?:d(?:y)?)?)?)?)?$/i; if ( not $user_uniq_hash{RT->Nobody->id} and $term =~ $nobody ) { $user_uniq_hash{RT->Nobody->id} = [ RT->Nobody, RT->Nobody->Format, ]; } my @users = sort { lc $a->[1] cmp lc $b->[1] } values %user_uniq_hash; my @suggestions; my $count = 1; for my $tuple ( @users ) { last if $count > $max; my $formatted = $tuple->[1]; $formatted =~ s/\n//g; push @suggestions, { label => $formatted, value => $tuple->[0]->$return }; $count++; } rt-5.0.1/share/html/Helpers/RightsInspector/Revoke000644 000765 000024 00000004577 14005011336 022773 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $id => undef <%INIT> my $ACE = RT::ACE->new($session{CurrentUser}); $ACE->Load($id); my $Principal = $ACE->PrincipalObj; my $Object = $ACE->Object; my $Right = $ACE->RightName; my ($ok, $msg) = $Principal->RevokeRight(Object => $Object, Right => $Right); $r->content_type('application/json; charset=utf-8'); $m->out(JSON({ok => $ok, msg => $msg})); $m->abort; rt-5.0.1/share/html/Helpers/RightsInspector/Search000644 000765 000024 00000004355 14005011336 022737 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> use RT::RightsInspector; my $results = RT::RightsInspector->Search(%ARGS); $r->content_type('application/json; charset=utf-8'); RT::Interface::Web::CacheControlExpiresHeaders( Time => 'no-cache' ); $m->out(JSON($results)); $m->abort; rt-5.0.1/share/html/m/tickets/000755 000765 000024 00000000000 14005011336 016751 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/m/index.html000644 000765 000024 00000004172 14005011336 017304 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&| _elements/wrapper, title => loc("RT for [_1]",RT->Config->Get('rtname'))&> <& _elements/menu &> <& _elements/full_site_link &> rt-5.0.1/share/html/m/dhandler000644 000765 000024 00000004223 14005011336 017010 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> # deal with users who don't have options indexes set right RT::Interface::Web::Redirect(RT->Config->Get('WebURL')."m/index.html"); $m->abort(); rt-5.0.1/share/html/m/logout000644 000765 000024 00000004275 14005011336 016547 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> if (keys %session) { RT::Interface::Web::InstantiateNewSession(); $session{'CurrentUser'} = RT::CurrentUser->new; } RT::Interface::Web::Redirect(RT->Config->Get('WebURL')."m/"); rt-5.0.1/share/html/m/ticket/000755 000765 000024 00000000000 14005011336 016566 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/m/_elements/000755 000765 000024 00000000000 14005011336 017256 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/m/_elements/ticket_menu000644 000765 000024 00000005052 14005011336 021512 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $ticket
      <%init> my @menu = ( { label => loc("Basics"), url => '/m/ticket/show?id='.$ticket->id }, { label => loc("History"), url => '/m/ticket/history?id='.$ticket->id }, #{ label => loc("Modify"), url => '/m/ticket/modify?id='.$ticket->id }, { label => loc("Reply"), url => '/m/ticket/reply?id='.$ticket->id } ); my $width = int(100/ ($#menu +1))-5; rt-5.0.1/share/html/m/_elements/ticket_list000644 000765 000024 00000010621 14005011336 021517 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $order => undef $order_by => undef $query => '' $page => 1 <%init> my $rows = 10; my $collection = RT::Tickets->new($session{'CurrentUser'}); $collection->FromSQL($query); $collection->RowsPerPage($rows); $collection->GotoPage($page-1); # XXX: ->{'order_by'} is hacky, but there is no way to check if # collection is ordered or not if ( $order_by) { my @order_by = split /\|/, $order_by; my @order = split /\|/,$order; $collection->OrderByCols( map { { FIELD => $order_by[$_], ORDER => $order[$_] } } ( 0 .. $#order_by ) ); } $collection->RedoSearch(); if ($page > 1 && ! @{$collection->ItemsArrayRef||[]}) { RT::Interface::Web::Redirect( RT->Config->Get('WebURL')."m/tickets/search?page=".($page-1)."&query=".$query."&order=$order&order_by=$order_by"); } <&| /m/_elements/wrapper, title => loc("Found [quant,_1,ticket,tickets]",$collection->CountAll) &> <&|/Widgets/TitleBox, class => 'search' &>
        % while (my $ticket = $collection->Next()) {
      • <%$ticket->id%>: <%$ticket->Subject%>
      • % }
      % if ($page > 1) { Back % } Page <%$page%> % if ($collection->CountAll > $page * $rows) { Next % }
      rt-5.0.1/share/html/m/_elements/footer000644 000765 000024 00000004447 14005011336 020510 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Logo, ShowName => 1, OnlyCustom => 1 &>
      rt-5.0.1/share/html/m/_elements/wrapper000644 000765 000024 00000004450 14005011336 020664 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $title => '' $show_home_button => 1 <%init> if ($session{'NotMobile'}) { RT::Interface::Web::Redirect(RT->Config->Get('WebURL')); $m->abort(); } $m->comp('header', title => $title, show_home_button => $show_home_button); $m->out($m->content); $m->comp('footer'); $m->abort(); rt-5.0.1/share/html/m/_elements/full_site_link000644 000765 000024 00000004155 14005011336 022211 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&|/l&>Not using a mobile browser? rt-5.0.1/share/html/m/_elements/menu000644 000765 000024 00000010171 14005011336 020145 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&| /Widgets/TitleBox, class => 'menu'&> <%init> my @menu = ( { html => '' }, { label => loc("New ticket"), url => '/m/ticket/select_create_queue', }, { label => loc("Bookmarked tickets"), url => '/m/tickets/search?name=Bookmarked%20Tickets', }, { label => loc("Tickets I own"), url => '/m/tickets/search?name=My%20Tickets', }, { label => loc("Unowned tickets"), url => '/m/tickets/search?name=Unowned%20Tickets', }, { label => loc("All tickets"), url => '/m/tickets/search?query=id!%3d0&order_by=id&order=DESC' }, ); if ( $session{'CurrentUser'}->HasRight( Right => 'LoadSavedSearch', Object => $RT::System)) { my @Objects = RT::SavedSearch->new( $session{CurrentUser} )->ObjectsForLoading; push @Objects, RT::System->new( $session{'CurrentUser'} ) if $session{'CurrentUser'}->HasRight( Object => $RT::System, Right => 'SuperUser' ); foreach my $object (@Objects) { my @searches = $object->Attributes->Named('SavedSearch'); foreach my $search (@searches) { next if $search->SubValue("SearchType") && $search->SubValue("SearchType") ne 'Ticket'; push @menu, { label => $search->Description, url => '/m/tickets/search?query=' . $search->SubValue("Query").'&order='.$search->SubValue("Order").'&order_by='.$search->SubValue("OrderBy") }; } } } push @menu, { label => loc("Logout"), url => '/m/logout', } ; $m->callback( CallbackName => 'MassageMenu', Menu => \@menu ); rt-5.0.1/share/html/m/_elements/login000644 000765 000024 00000007265 14005011336 020323 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $m->callback( %ARGS, CallbackName => 'Header' ); <&| /m/_elements/wrapper, title => loc('RT for [_1]', RT->Config->Get('rtname')), show_home_button => 0 &> <& /m/_elements/full_site_link &> <%ARGS> $user => "" $pass => undef $goto => undef $actions => undef $next => "" rt-5.0.1/share/html/m/_elements/header000644 000765 000024 00000005525 14005011336 020440 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $title => loc('RT for [_1]', RT->Config->Get('rtname')) $show_home_button => 1 <%init> $r->headers_out->{'Pragma'} = 'no-cache'; $r->headers_out->{'Cache-control'} = 'no-cache'; <%$title%> % my ($jquery) = grep { /^jquery-\d+\./ } RT::Interface::Web->JSFiles; <& /Elements/Framekiller &> % if ($show_home_button) { % # The align is for older browsers, like the blackberry % } % if ($title) {

      <%$title%>

      % } rt-5.0.1/share/html/m/ticket/select_create_queue000644 000765 000024 00000004714 14005011336 022525 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> my $queues = RT::Queues->new($session{'CurrentUser'}); $queues->UnLimit(); <&| /m/_elements/wrapper, title => loc("Create a ticket") &>
      <&|/Widgets/TitleBox, title => loc("Select a queue") &>
      rt-5.0.1/share/html/m/ticket/autohandler000644 000765 000024 00000004317 14005011336 021024 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> # Redirect to the approvals view if we're trying to get an approvals ticket MaybeRedirectToApproval( Whitelist => qr{/(?:create|select_create_queue)$}i, ARGSRef => \%ARGS, ); $m->call_next; rt-5.0.1/share/html/m/ticket/show000644 000765 000024 00000037223 14005011336 017500 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $id => undef <%init> my $Ticket; my @Actions; unless ($id) { Abort('No ticket specified'); } if ($ARGS{'id'} eq 'new') { # {{{ Create a new ticket my $Queue = RT::Queue->new( $session{'CurrentUser'} ); $Queue->Load($ARGS{'Queue'}); unless ( $Queue->id ) { Abort('Queue not found'); } unless ( $Queue->CurrentUserHasRight('CreateTicket') ) { Abort('You have no permission to create tickets in that queue.'); } ($Ticket, @Actions) = CreateTicket( %ARGS ); unless ( $Ticket->CurrentUserHasRight('ShowTicket') ) { Abort("No permission to view newly created ticket #".$Ticket->id."."); } } else { $Ticket ||= LoadTicket($ARGS{'id'}); $Ticket->Atomic(sub{ $m->callback( CallbackName => 'BeforeProcessArguments', TicketObj => $Ticket, ActionsRef => \@Actions, ARGSRef => \%ARGS ); if ( defined $ARGS{'Action'} ) { if ($ARGS{'Action'} =~ /^(Steal|Delete|Take|SetTold)$/) { my $action = $1; my ($res, $msg) = $Ticket->$action(); push(@Actions, $msg); } } $m->callback(CallbackName => 'ProcessArguments', Ticket => $Ticket, ARGSRef => \%ARGS, Actions => \@Actions); push @Actions, ProcessUpdateMessage( ARGSRef => \%ARGS, Actions => \@Actions, TicketObj => $Ticket, ); #Process status updates push @Actions, ProcessTicketWatchers(ARGSRef => \%ARGS, TicketObj => $Ticket ); push @Actions, ProcessTicketBasics( ARGSRef => \%ARGS, TicketObj => $Ticket ); push @Actions, ProcessTicketLinks( ARGSRef => \%ARGS, TicketObj => $Ticket ); push @Actions, ProcessTicketDates( ARGSRef => \%ARGS, TicketObj => $Ticket ); push @Actions, ProcessObjectCustomFieldUpdates(ARGSRef => \%ARGS, Object => $Ticket ); push @Actions, ProcessTicketReminders( ARGSRef => \%ARGS, TicketObj => $Ticket ); }); unless ($Ticket->CurrentUserHasRight('ShowTicket')) { if (@Actions) { Abort("A change was applied successfully, but you no longer have permissions to view the ticket", Actions => \@Actions); } else { Abort("No permission to view ticket"); } } if ( $ARGS{'MarkAsSeen'} ) { $Ticket->SetAttribute( Name => 'User-'. $Ticket->CurrentUser->id .'-SeenUpTo', Content => $Ticket->LastUpdated, ); push @Actions, loc('Marked all messages as seen'); } } $m->callback( CallbackName => 'BeforeDisplay', TicketObj => \$Ticket, Actions => \@Actions, ARGSRef => \%ARGS, ); # This code does automatic redirection if any updates happen. if (@Actions) { # We've done something, so we need to clear the decks to avoid # resubmission on refresh. # But we need to store Actions somewhere too, so we don't lose them. my $key = Digest::MD5::md5_hex( rand(1024) ); push @{ $session{"Actions"}->{$key} ||= [] }, @Actions; $session{'i'}++; my $url = RT->Config->Get('WebURL') . "m/ticket/show?id=" . $Ticket->id . "&results=" . $key; $url .= '#' . $ARGS{Anchor} if $ARGS{Anchor}; RT::Interface::Web::Redirect($url); } # If we haven't been passed in an Attachments object (through the precaching mechanism) # then we need to find one my $Attachments = $Ticket->Attachments; my %documents; while ( my $attach = $Attachments->Next() ) { next unless ($attach->Filename()); unshift( @{ $documents{ $attach->Filename } }, $attach ); } my $CustomFields = $Ticket->CustomFields; $m->callback( CallbackName => 'MassageCustomFields', Object => $Ticket, CustomFields => $CustomFields, ); my $print_value = sub { my ($cf, $value) = @_; my $linked = $value->LinkValueTo; if ( defined $linked && length $linked ) { my $linked = $m->interp->apply_escapes( $linked, 'h' ); $m->out(''); } my $comp = "ShowCustomField". $cf->Type; $m->callback( CallbackName => 'ShowComponentName', Name => \$comp, CustomField => $cf, Object => $Ticket, ); if ( $m->comp_exists( $comp ) ) { $m->comp( $comp, Object => $value ); } else { $m->out( $m->interp->apply_escapes( $value->Content, 'h' ) ); } $m->out('') if defined $linked && length $linked; # This section automatically populates a div with the "IncludeContentForValue" for this custom # field if it's been defined if ( $cf->IncludeContentForValue ) { my $vid = $value->id; $m->out( '\n} ); $m->out( qq{\n} ); } }; <&| /m/_elements/wrapper, title => loc("#[_1]: [_2]", $Ticket->Id, $Ticket->Subject || '') &>
      <& /m/_elements/ticket_menu, ticket => $Ticket &> <&| /Widgets/TitleBox, title => loc('The Basics'), class => 'ticket-info-basics', &>
      <&|/l&>Id:
      <%$Ticket->Id %>
      <&|/l&>Status:
      <% loc($Ticket->Status) %>
      % if ($Ticket->TimeEstimated) {
      <&|/l&>Estimated:
      <& /Ticket/Elements/ShowTime, minutes => $Ticket->TimeEstimated &>
      % } % if ($Ticket->TimeWorked) {
      <&|/l&>Worked:
      <& /Ticket/Elements/ShowTime, minutes => $Ticket->TimeWorked &>
      % } % if ($Ticket->TimeLeft) {
      <&|/l&>Left:
      <& /Ticket/Elements/ShowTime, minutes => $Ticket->TimeLeft &>
      % }
      <&|/l&>Priority:
      <& /Ticket/Elements/ShowPriority, Ticket => $Ticket &>
      <&|/l&>Queue:
      <& /Ticket/Elements/ShowQueue, QueueObj => $Ticket->QueueObj &>
      <&|/l&>Bookmark:
      <& /Ticket/Elements/Bookmark, id => $Ticket->id &>
      % if ($CustomFields->Count) { <&| /Widgets/TitleBox, title => loc('Custom Fields'), class => 'ticket-info-cfs', &> % while ( my $CustomField = $CustomFields->Next ) { % my $Values = $Ticket->CustomFieldValues( $CustomField->Id ); % my $count = $Values->Count;
      <% $CustomField->Name %>:
      % unless ( $count ) { <&|/l&>(no value) % } elsif ( $count == 1 ) { % $print_value->( $CustomField, $Values->First ); % } else {
        % while ( my $Value = $Values->Next ) {
      • % $print_value->( $CustomField, $Value );
      • % }
      % }
      % } % } <&| /Widgets/TitleBox, title => loc('People'), class => 'ticket-info-people' &>
      <&|/l&>Owner:
      <& /Elements/ShowUser, User => $Ticket->OwnerObj, Ticket => $Ticket, Link => 0 &>
      <&|/l&>Requestors:
      <& /Ticket/Elements/ShowGroupMembers, Group => $Ticket->Requestors, Ticket => $Ticket, Link => 0 &>
      <&|/l&>Cc:
      <& /Ticket/Elements/ShowGroupMembers, Group => $Ticket->Cc, Ticket => $Ticket, Link => 0 &>
      <&|/l&>AdminCc:
      <& /Ticket/Elements/ShowGroupMembers, Group => $Ticket->AdminCc, Ticket => $Ticket, Link => 0 &>
      % if (keys %documents) { <&| /Widgets/TitleBox, title => loc('Attachments'), title_class=> 'inverse', class => 'ticket-info-attachments', color => "#336699" &> % foreach my $key (keys %documents) { <%$key%>
      % } % } % # too painful to deal with reminders % if ( 0 && RT->Config->Get('EnableReminders') ) { <&|/Widgets/TitleBox, title => loc("Reminders"), class => 'ticket-info-reminders', &>
      <& /Ticket/Elements/Reminders, Ticket => $Ticket, ShowCompleted => 0 &>
      % } <&| /Widgets/TitleBox, title => loc("Dates"), class => 'ticket-info-dates', &>
      <&|/l&>Created:
      <% $Ticket->CreatedObj->AsString %>
      <&|/l&>Starts:
      <% $Ticket->StartsObj->AsString %>
      <&|/l&>Started:
      <% $Ticket->StartedObj->AsString %>
      <&|/l&>Last Contact:
      <% $Ticket->ToldObj->AsString %>
      <&|/l&>Due:
      % my $due = $Ticket->DueObj; % if ( $due && $due->IsSet && $due->Diff < 0 && $Ticket->QueueObj->IsActiveStatus($Ticket->Status) ) {
      <% $due->AsString %>
      % } else {
      <% $due->AsString %>
      % }
      <&|/l&>Closed:
      <% $Ticket->ResolvedObj->AsString %>
      <&|/l&>Updated:
      % my $UpdatedString = $Ticket->LastUpdated ? loc("[_1] by [_2]", $Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name) : loc("Never");
      <% $UpdatedString | h %>
      <&| /Widgets/TitleBox, title => loc('Links'), class => 'ticket-info-links' &>
      <% loc('Depends on')%>:
      <%PERL> my ( @active, @inactive, @not_tickets ); for my $link ( @{ $Ticket->DependsOn->ItemsArrayRef } ) { my $target = $link->TargetObj; if ( $target && $target->isa('RT::Ticket') ) { if ( $target->QueueObj->IsInactiveStatus( $target->Status ) ) { push( @inactive, $link->TargetURI ); } else { push( @active, $link->TargetURI ); } } elsif ( not (UNIVERSAL::isa($link->TargetObj, 'RT::Article') && $link->TargetObj->Disabled) ) { push( @not_tickets, $link->TargetURI ); } }
        % for my $Link (@not_tickets, @active, @inactive) {
      • <& /Elements/ShowLink, URI => $Link &>
      • % }
      <% loc('Depended on by')%>:
        % while (my $Link = $Ticket->DependedOnBy->Next) { % next if UNIVERSAL::isa($Link->BaseObj, 'RT::Article') && $Link->BaseObj->Disabled;
      • <& /Elements/ShowLink, URI => $Link->BaseURI &>
      • % }
      <% loc('Parents') %>:
      <& /Elements/ShowLinksOfType, Object => $Ticket, Type => 'MemberOf' &>
      <% loc('Children')%>:
      <& /Elements/ShowLinksOfType, Object => $Ticket, Type => 'Members' &>
      <% loc('Refers to')%>:
        % while (my $Link = $Ticket->RefersTo->Next) { % next if UNIVERSAL::isa($Link->TargetObj, 'RT::Article') && $Link->TargetObj->Disabled;
      • <& /Elements/ShowLink, URI => $Link->TargetURI &>
      • % }
      <% loc('Referred to by')%>:
        % while (my $Link = $Ticket->ReferredToBy->Next) { % next if UNIVERSAL::isa($Link->BaseObj, 'RT::Article') && $Link->BaseObj->Disabled; % next if (UNIVERSAL::isa($Link->BaseObj, 'RT::Ticket') && $Link->BaseObj->__Value('Type') eq 'reminder');
      • <& /Elements/ShowLink, URI => $Link->BaseURI &>
      • % }
      rt-5.0.1/share/html/m/ticket/reply000644 000765 000024 00000021665 14005011336 017656 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&|/m/_elements/wrapper, title => loc('Update ticket #[_1]', $t->id) &> <& /m/_elements/ticket_menu, ticket => $t &> <& /Elements/ListActions, actions => \@results &>
      <&|/Widgets/TitleBox &>
      % if ($gnupg_widget) { <& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget &> % }
      <&|/l&>Status:
      <& /Ticket/Elements/SelectStatus, Name=>"Status", TicketObj => $t, Default => $DefaultStatus &>
      <&|/l&>Owner:
      <& /Elements/SelectOwner, Name => "Owner", TicketObj => $t, QueueObj => $t->QueueObj, DefaultLabel => loc("[_1] (Unchanged)", $t->OwnerObj->Format), Default => $ARGS{'Owner'} &>
      <&|/l&>Worked: <& /Elements/EditTimeValue, Name => 'UpdateTimeWorked', Default => $ARGS{UpdateTimeWorked}||'', &>
      % $m->callback( %ARGS, CallbackName => 'AfterWorked', Ticket => $t );
      <&|/l&>Update Type:
      <&|/l&>Subject:
      % $m->callback( %ARGS, CallbackName => 'AfterSubject' );
      <&|/l&>One-time Cc:<& /Elements/EmailInput, Name => 'UpdateCc', Size => '60', Default => $ARGS{UpdateCc} &>
      <&|/l&>One-time Bcc:<& /Elements/EmailInput, Name => 'UpdateBcc', Size => '60', Default => $ARGS{UpdateBcc} &>
      <&|/l&>Message:
      % if (exists $ARGS{UpdateContent}) { % # preserve QuoteTransaction so we can use it to set up sane references/in/reply to % my $temp = $ARGS{'QuoteTransaction'}; % delete $ARGS{'QuoteTransaction'}; <& /Elements/MessageBox, Name=>"UpdateContent", Default=>$ARGS{UpdateContent}, IncludeSignature => 0, SuppressAttachmentWarning => 1, %ARGS&> % $ARGS{'QuoteTransaction'} = $temp; % } else { % my $IncludeSignature = 1; % $IncludeSignature = 0 if $Action ne 'Respond' && !RT->Config->Get('MessageBoxIncludeSignatureOnComment'); <& /Elements/MessageBox, Name=>"UpdateContent", IncludeSignature => $IncludeSignature, SuppressAttachmentWarning => 1, %ARGS &> % }
      % if (exists $session{'Attachments'}) { <%loc("Attached file") %> <%loc("Check box to delete")%>
      % foreach my $attach_name (keys %{$session{'Attachments'}}) { <%$attach_name%>
      % } # end of foreach % } # end of if
      <&|/l&>Attach file:
      " />
      % if ( $gnupg_widget ) { <& /Elements/Crypt/SignEncryptWidget, self => $gnupg_widget, QueueObj => $t->QueueObj &> % } <& /Elements/Submit, Label => loc('Update Ticket'), Name => 'SubmitTicket' &>
      <%INIT> my $CanRespond = 0; my $CanComment = 0; my $checks_failure = 0; my $title; my $t = LoadTicket($id); my @results; $m->callback( Ticket => $t, ARGSRef => \%ARGS, results => \@results, CallbackName => 'Initial' ); unless($DefaultStatus){ $DefaultStatus=($ARGS{'Status'} ||$t->Status()); } if ($DefaultStatus eq 'new'){ $DefaultStatus='open'; } if ($DefaultStatus eq 'resolved') { $title = loc("Resolve ticket #[_1] ([_2])", $t->id, $t->Subject); } else { $title = loc("Update ticket #[_1] ([_2])", $t->id, $t->Subject); } # Things needed in the template - we'll do the processing here, just # for the convenience: my ($CommentDefault, $ResponseDefault); if ($Action ne 'Respond') { $CommentDefault = qq[ selected="selected"]; $ResponseDefault = ""; } else { $CommentDefault = ""; $ResponseDefault = qq[ selected="selected"]; } $CanRespond = 1 if ( $t->CurrentUserHasRight('ReplyToTicket') or $t->CurrentUserHasRight('ModifyTicket') ); $CanComment = 1 if ( $t->CurrentUserHasRight('CommentOnTicket') or $t->CurrentUserHasRight('ModifyTicket') ); ProcessAttachments(ARGSRef => \%ARGS); # check email addresses for RT's { foreach my $field ( qw(UpdateCc UpdateBcc) ) { my $value = $ARGS{ $field }; next unless defined $value && length $value; my @emails = Email::Address->parse( $value ); foreach my $email ( grep RT::EmailParser->IsRTAddress($_->address), @emails ) { push @results, loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop", $email->format, loc(substr($field, 6)) ); $checks_failure = 1; $email = undef; } $ARGS{ $field } = join ', ', map $_->format, grep defined, @emails; } } my $gnupg_widget = $m->comp('/Elements/Crypt/SignEncryptWidget:new', Arguments => \%ARGS ); $m->comp( '/Elements/Crypt/SignEncryptWidget:Process', self => $gnupg_widget, TicketObj => $t, ); my $skip_update = 0; $m->callback( CallbackName => 'BeforeUpdate', ARGSRef => \%ARGS, skip_update => \$skip_update, checks_failure => $checks_failure, results => \@results, TicketObj => $t ); if ( !$checks_failure && !$skip_update && exists $ARGS{SubmitTicket} ) { my $status = $m->comp('/Elements/Crypt/SignEncryptWidget:Check', self => $gnupg_widget, TicketObj => $t, ); $checks_failure = 1 unless $status; $m->callback( Ticket => $t, ARGSRef => \%ARGS, CallbackName => 'BeforeDisplay' ); return $m->comp('/m/ticket/show', TicketObj => $t, %ARGS); } <%ARGS> $id => undef $Action => 'Respond' $DefaultStatus => undef rt-5.0.1/share/html/m/ticket/history000644 000765 000024 00000005310 14005011336 020211 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $id => undef <%init> my $t = RT::Ticket->new($session{CurrentUser}); $t->Load($id); my $history = $t->Transactions()->ItemsArrayRef; <&| /m/_elements/wrapper, title => loc("#[_1]: [_2]", $t->Id, $t->Subject || '') &>
      <& /m/_elements/ticket_menu, ticket => $t &> <&|/Widgets/TitleBox &>
        % for my $entry (reverse @$history) {
      • <% $entry->CreatedObj->AgeAsString() %> - <& /Elements/ShowUser, User => $entry->CreatorObj, Link => 0 &> - <%$entry->BriefDescription%> % if ($entry->Type !~ /EmailRecord/) { % if ($entry->ContentObj) {
        <%$entry->Content%>
        %} % }
      • % }
      rt-5.0.1/share/html/m/ticket/create000644 000765 000024 00000033057 14005011336 017764 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $QuoteTransaction => undef $CloneTicket => undef <%init> $m->callback( CallbackName => "Init", ARGSRef => \%ARGS ); my $Queue = $ARGS{Queue}; my $escape = sub { $m->interp->apply_escapes(shift, 'h') }; my $showrows = sub { my @pairs = @_; while (@pairs) { my $key = shift @pairs; my $val = shift @pairs; $m->out("
      $key
      $val
      "); } }; my $CloneTicketObj; if ($CloneTicket) { $CloneTicketObj = RT::Ticket->new( $session{CurrentUser} ); $CloneTicketObj->Load($CloneTicket) or Abort( loc("Ticket could not be loaded") ); my $clone = { Requestors => join( ',', $CloneTicketObj->RequestorAddresses ), Cc => join( ',', $CloneTicketObj->CcAddresses ), AdminCc => join( ',', $CloneTicketObj->AdminCcAddresses ), InitialPriority => $CloneTicketObj->Priority, }; $clone->{$_} = $CloneTicketObj->$_() for qw/Owner Subject FinalPriority TimeEstimated TimeWorked Status TimeLeft/; $clone->{$_} = $CloneTicketObj->$_->AsString for grep { $CloneTicketObj->$_->IsSet } map { $_ . "Obj" } qw/Starts Started Due Resolved/; my $get_link_value = sub { my ($link, $type) = @_; my $uri_method = $type . 'URI'; my $local_method = 'Local' . $type; my $uri = $link->$uri_method; return if $uri->IsLocal and $uri->Object and $uri->Object->isa('RT::Ticket') and $uri->Object->__Value('Type') eq 'reminder'; return $link->$local_method || $uri->URI; }; my (@refers, @refers_by); my $refers = $CloneTicketObj->RefersTo; while ( my $refer = $refers->Next ) { my $refer_value = $get_link_value->($refer, 'Target'); push @refers, $refer_value if defined $refer_value; } $clone->{'new-RefersTo'} = join ' ', @refers; my $refers_by = $CloneTicketObj->ReferredToBy; while ( my $refer_by = $refers_by->Next ) { my $refer_by_value = $get_link_value->($refer_by, 'Base'); push @refers_by, $refer_by_value if defined $refer_by_value; } $clone->{'RefersTo-new'} = join ' ', @refers_by; my $cfs = $CloneTicketObj->QueueObj->TicketCustomFields(); while ( my $cf = $cfs->Next ) { my $cf_id = $cf->id; my $cf_values = $CloneTicketObj->CustomFieldValues( $cf->id ); my @cf_values; while ( my $cf_value = $cf_values->Next ) { push @cf_values, $cf_value->Content; } $clone->{GetCustomFieldInputName( CustomField => $cf )} = join "\n", @cf_values; } for ( keys %$clone ) { $ARGS{$_} = $clone->{$_} if not defined $ARGS{$_}; } } my @results; my $title = loc("Create a ticket"); my $QueueObj = RT::Queue->new($session{'CurrentUser'}); $QueueObj->Load($Queue) || Abort(loc("Queue could not be loaded.")); $m->callback( QueueObj => $QueueObj, title => \$title, results => \@results, ARGSRef => \%ARGS ); $QueueObj->Disabled && Abort(loc("Cannot create tickets in a disabled queue.")); ProcessAttachments(ARGSRef => \%ARGS); my $checks_failure = 0; { my ($status, @msg) = $m->comp( '/Elements/ValidateCustomFields', CustomFields => $QueueObj->TicketCustomFields, ARGSRef => \%ARGS ); unless ( $status ) { $checks_failure = 1; push @results, @msg; } } my $gnupg_widget = $m->comp('/Elements/Crypt/SignEncryptWidget:new', Arguments => \%ARGS ); $m->comp( '/Elements/Crypt/SignEncryptWidget:Process', self => $gnupg_widget, QueueObj => $QueueObj, ); if ( !exists $ARGS{'AddMoreAttach'} && ($ARGS{'id'}||'') eq 'new' ) { my $status = $m->comp('/Elements/Crypt/SignEncryptWidget:Check', self => $gnupg_widget, Operation => 'Create', QueueObj => $QueueObj, ); $checks_failure = 1 unless $status; } # check email addresses for RT's { foreach my $field ( qw(Requestors Cc AdminCc) ) { my $value = $ARGS{ $field }; next unless defined $value && length $value; my @emails = Email::Address->parse( $value ); foreach my $email ( grep RT::EmailParser->IsRTAddress($_->address), @emails ) { push @results, loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop", $email->format, loc($field =~ /^(.*?)s?$/) ); $checks_failure = 1; $email = undef; } $ARGS{ $field } = join ', ', map $_->format, grep defined, @emails; } } my $skip_create = 0; $m->callback( CallbackName => 'BeforeCreate', ARGSRef => \%ARGS, skip_create => \$skip_create, checks_failure => $checks_failure, results => \@results ); if ((!exists $ARGS{'AddMoreAttach'}) and (defined($ARGS{'id'}) and $ARGS{'id'} eq 'new')) { # new ticket? if ( !$checks_failure && !$skip_create ) { $m->comp('show', %ARGS); $RT::Logger->crit("After display call; error is $@"); $m->abort(); } } <&| /m/_elements/wrapper, title => $title &> <& /Elements/ListActions, actions => \@results &>
      % $m->callback( CallbackName => 'FormStart', QueueObj => $QueueObj, ARGSRef => \%ARGS ); % if ($gnupg_widget) { <& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget &> % }
      <&| /Widgets/TitleBox, title => $QueueObj->Name &> <%perl> $showrows->( loc("Subject") => ''); <& /Elements/MessageBox, exists $ARGS{Content} ? (Default => $ARGS{Content}, IncludeSignature => 0 ) : ( QuoteTransaction => $QuoteTransaction ), Height => 5, SuppressAttachmentWarning => 1 &> <&/Elements/Submit, Label => loc("Create") &>
      <&| /Widgets/TitleBox &> <%perl> $showrows->( # loc('Queue') => $m->scomp( '/Ticket/Elements/ShowQueue', QueueObj => $QueueObj ) , loc('Status') => $m->scomp( "/Ticket/Elements/SelectStatus", Name => "Status", QueueObj => $QueueObj, ), loc("Owner") => $m->scomp( "/Elements/SelectOwner", Name => "Owner", QueueObj => $QueueObj, Default => $ARGS{Owner} || RT->Nobody->Id, DefaultValue => 0 ), loc("Requestors") => $m->scomp( "/Elements/EmailInput", Name => 'Requestors', Size => '40', Default => $ARGS{Requestors} // $session{CurrentUser}->EmailAddress ), loc("Cc") => $m->scomp( "/Elements/EmailInput", Name => 'Cc', Size => '40', Default => $ARGS{Cc} ) . '' . loc( "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people will receive future updates.)" ) . '', loc("Admin Cc") => $m->scomp( "/Elements/EmailInput", Name => 'AdminCc', Size => '40', Default => $ARGS{AdminCc} ) . '' . loc( "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people will receive future updates.)" ) . '', ); <& /Elements/EditCustomFields, %ARGS, Object => RT::Ticket->new($session{CurrentUser}), CustomFields => $QueueObj->TicketCustomFields, AsTable => 0, &> <& /Ticket/Elements/EditTransactionCustomFields, %ARGS, QueueObj => $QueueObj &> % if ( my $attachments = $session{'Attachments'}{ $ARGS{'Token'} }) { <%loc("Attached file") %> <%loc("Check box to delete")%>
      % foreach my $attach_name ( keys %$attachments ) {
      % } # end of foreach % } # end of if <%perl> $showrows->( loc("Attach file") => ' ' ); % if ( $gnupg_widget ) { <& /Elements/Crypt/SignEncryptWidget, self => $gnupg_widget, QueueObj => $QueueObj &> % }
      <&| /Widgets/TitleBox, title => loc('The Basics'), title_class=> 'inverse', color => "#993333" &> <%perl> $showrows->( loc("Priority") => $m->scomp( "/Elements/SelectPriority", Name => "InitialPriority", Default => $ARGS{InitialPriority} ? $ARGS{InitialPriority} : $QueueObj->DefaultValue('InitialPriority'), ), loc("Final Priority") => $m->scomp( "/Elements/SelectPriority", Name => "FinalPriority", Default => $ARGS{FinalPriority} ? $ARGS{FinalPriority} : $QueueObj->DefaultValue('FinalPriority'), ), loc("Time Estimated") => ''.$m->scomp( "/Elements/EditTimeValue", Name => 'TimeEstimated', Default => $ARGS{TimeEstimated} || '', ).'', loc("Time Worked") => ''.$m->scomp( "/Elements/EditTimeValue", Name => 'TimeWorked', Default => $ARGS{TimeWorked} || '', ). '', loc("Time Left") => ''.$m->scomp( "/Elements/EditTimeValue", Name => 'TimeLeft', Default => $ARGS{TimeLeft} || '', ).'', ); <&|/Widgets/TitleBox, title => loc("Dates"), title_class=> 'inverse', color => "#663366" &> <%perl> $showrows->( loc("Starts") => $m->scomp( "/Elements/SelectDate", Name => "Starts", Default => ( $ARGS{Starts} || $QueueObj->DefaultValue('Starts') || '' )), loc("Due") => $m->scomp( "/Elements/SelectDate", Name => "Due", Default => ($ARGS{Due} || $QueueObj->DefaultValue('Due') || '' )) ); <&|/Widgets/TitleBox, title => loc('Links'), title_class=> 'inverse' &> <%loc("(Enter ticket ids or URLs, separated with spaces)")%> <%perl> $showrows->( loc("Depends on") => '', loc("Depended on by") => '', loc("Parents") => '', loc("Children") => '', loc("Refers to") => '', loc("Referred to by") => '' ); <& /Elements/Submit, Label => loc("Create") &> rt-5.0.1/share/html/m/tickets/search000644 000765 000024 00000006621 14005011336 020146 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $page => 1 $order_by => 'id' $order => 'desc' $name => undef <%init> use RT::Search::Simple; my $query = $ARGS{'query'}; if ($ARGS{'q'}) { my $tickets = RT::Tickets->new( $session{'CurrentUser'} ); my %args = ( Argument => $ARGS{q}, TicketsObj => $tickets, ); my $search = RT::Search::Simple->new(%args); $query = $search->QueryToSQL(); } elsif ($ARGS{'name'}) { my $search_arg; my $search; if ($name) { ($search) = RT::System->new( $session{'CurrentUser'} )->Attributes->Named( 'Search - ' . $name ); unless ( $search && $search->Id ) { my (@custom_searches) = RT::System->new( $session{'CurrentUser'} )->Attributes->Named('SavedSearch'); foreach my $custom (@custom_searches) { if ( $custom->Description eq $name ) { $search = $custom; last } } unless ( $search && $search->id ) { $m->out(loc("Predefined search [_1] not found", $m->interp->apply_escapes($name, 'h'))); return; } } $search_arg = $session{'CurrentUser'}->UserObj->Preferences( $search, $search->Content ); } $query = $search_arg->{Query}; $order_by = $search_arg->{OrderBy}; $order = $search_arg->{Order}; } $m->comp('../_elements/ticket_list', query => $query, page => $page, order_by => $order_by, order => $order); $m->abort(); rt-5.0.1/share/html/Approvals/index.html000644 000765 000024 00000007064 14005011336 021022 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => loc("My approvals") &> <& /Elements/Tabs &> <& /Elements/ListActions, actions => \@actions &>
      <& Elements/PendingMyApproval, %ARGS &>
      <& /Elements/Submit, Label => loc('Go!') &>
      <%init> my (@actions); foreach my $arg ( keys %ARGS ) { next unless ( $arg =~ /Approval-(\d+)-Action/ ); my ( $notesval, $notesmsg ); my $ticket = LoadTicket($1); my $skip_update = 0; $m->callback( CallbackName => 'BeforeApproval', skip_update => \$skip_update, Ticket => $ticket, actions => \@actions); next if $skip_update; if ( $ARGS{ "Approval-" . $ticket->Id . "-Notes" } ) { my ( $notesval, $notesmsg ) = $ticket->Correspond( Content => $ARGS{ "Approval-" . $ticket->Id . "-Notes" } ); if ($notesval) { push ( @actions, loc("Approval #[_1]: Notes recorded",$ticket->Id )); } else { push ( @actions, loc("Approval #[_1]: Notes not recorded due to a system error",$ticket->Id )); } } my ($val, $msg); if ( $ARGS{$arg} eq 'deny' && $ticket->Status !~ /^(rejected|deleted)/ ) { ( $val, $msg ) = $ticket->SetStatus('rejected'); } elsif ( $ARGS{$arg} eq 'approve' && $ticket->Status ne 'resolved') { ( $val, $msg ) = $ticket->SetStatus('resolved'); } push ( @actions, loc("Approval #[_1]: [_2]",$ticket->id, $msg )) if ($msg); } rt-5.0.1/share/html/Approvals/autohandler000644 000765 000024 00000004310 14005011336 021245 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> if ( $session{'CurrentUser'}->UserObj->HasRight( Right => 'ShowApprovalsTab', Object => $RT::System, ) ) { $m->call_next(%ARGS); } else { Abort("No permission to view approval"); } rt-5.0.1/share/html/Approvals/Display.html000644 000765 000024 00000005333 14005011336 021315 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => $title &> <& /Elements/Tabs &>
      <&| /Widgets/TitleBox, title => $title &> <& /Elements/ShowHistory , Object => $Ticket, ShowTitle => 0, ShowHeaders => 0, ShowDisplayModes => 0, ShowActions => 0, PathPrefix => RT->Config->Get('WebPath')."/Ticket/" &>
      <& Elements/Approve, ticket => $Ticket, ShowApproving => 0 &>
      <& /Elements/Submit &>
      <& Elements/ShowDependency, Ticket => $Ticket &> <%init> my $Ticket = LoadTicket($id); my $title = loc("Approval #[_1]: [_2]", $Ticket->Id, $Ticket->Subject); <%ARGS> $id => undef rt-5.0.1/share/html/Approvals/Elements/000755 000765 000024 00000000000 14005011336 020572 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Approvals/Elements/ShowDependency000644 000765 000024 00000007616 14005011336 023446 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % my $approving = $Ticket->DependedOnBy(); % if ($approving->Count) {

      <&|/l&>Tickets which depend on this approval:

      <%PERL> my %show; while (my $link = $approving->Next()) { next unless ($link->BaseURI->IsLocal()); my $text = ''; $show{$link->BaseObj->Id} = { text => $text, head => $head, }; } my $refer; foreach my $id (sort keys %show) { if ($_seen->{$id}++) { $refer .= "" . $show{$id}{head} . ""; next; } $m->print($show{$id}{text}); } $m->print($refer);
      % } <%ARGS> $Ticket $_seen => {} rt-5.0.1/share/html/Approvals/Elements/PendingMyApproval000644 000765 000024 00000014362 14005011336 024122 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % my %done; % foreach ($tickets, $group_tickets) { % while (my $ticket = $_->Next() ) { % next if !$ARGS{'ShowDependent'} and $ticket->HasUnresolvedDependencies( Type => 'approval' ); % next if $done{$ticket->Id}++; # don't show duplicate tickets <& Approve, ticket => $ticket &> % } % } <&| /Widgets/TitleBox, title => loc("Search for approvals") &>
      />
      />
      />
      />
      <&|/l_unsafe, qq{"&>Only show approvals for requests created before [_1]
      <&|/l_unsafe, qq{"&>Only show approvals for requests created after [_1]
      <%init> my $tickets = RT::Tickets->new( $session{'CurrentUser'} ); $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'}->EmailAddress, TYPE => 'AdminCc' ); my $created_before = RT::Date->new( $session{'CurrentUser'} ); my $created_after = RT::Date->new( $session{'CurrentUser'} ); foreach ($tickets, $group_tickets) { $_->LimitType( VALUE => 'approval' ); if ( $ARGS{'ShowResolved'} ) { $_->LimitStatus( VALUE => 'resolved' ); } if ( $ARGS{'ShowRejected'} ) { $_->LimitStatus( VALUE => 'rejected' ); } if ( $ARGS{'ShowPending'} || ( !$ARGS{'ShowRejected'} && !$ARGS{'Resolved'} ) ) { $_->LimitStatus( VALUE => 'open' ); $_->LimitStatus( VALUE => 'new' ); $_->LimitStatus( VALUE => 'stalled' ); } if ( $ARGS{'CreatedBefore'} ) { $created_before->Set( Format => 'unknown', Value => $ARGS{'CreatedBefore'} ); $_->LimitCreated( OPERATOR => "<=", VALUE => $created_before->ISO ); } if ( $ARGS{'CreatedAfter'} ) { $created_after->Set( Format => 'unknown', Value => $ARGS{'CreatedAfter'} ); $_->LimitCreated( OPERATOR => ">=", VALUE => $created_after->ISO ); } $_->OrderBy( FIELD => 'id' ); } rt-5.0.1/share/html/Approvals/Elements/Approve000644 000765 000024 00000012651 14005011336 022136 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      % if ($ShowApproving) { % foreach my $approving ( $ticket->AllDependedOnBy( Type => 'ticket' ) ) {
      <&|/l, $approving->Id, $approving->Subject &>Originating ticket: #[_1]
      % if ($ShowCustomFields) { <& /Ticket/Elements/ShowCustomFields, Ticket => $approving &> % } % if ($ShowHistory) { <& /Elements/ShowHistory, Object => $approving, ShowTitle => 0, ShowHeaders => 0, ShowDisplayModes => 0, ShowActions => 0, PathPrefix => RT->Config->Get('WebPath')."/Ticket/" &> % }
      % } % }
      % if ( $inactive && $status eq 'resolved' ) {
      % } else {
      % }
      % if ( $inactive && $status ne 'resolved' ) {
      % } else {
      % }
      % unless ( $inactive ) {
      % }
      <%ARGS> $ShowApproving => 1 $ShowCustomFields => 1 $ShowHistory => 1 $ticket => undef <%INIT> my $status = $ticket->Status; my $inactive = $ticket->LifecycleObj->IsInactive( $status ); rt-5.0.1/share/html/Errors/WebRemoteUser/000755 000765 000024 00000000000 14005011336 021053 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Errors/WebRemoteUser/Deauthorized000644 000765 000024 00000004314 14005011336 023427 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&| Wrapper, %ARGS, Title => loc("No longer authorized") &>

      <&|/l&>You were logged out of RT by your authentication system. This may be a temporary hiccup, in which case refreshing this page may help.

      rt-5.0.1/share/html/Errors/WebRemoteUser/Wrapper000644 000765 000024 00000005666 14005011336 022433 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $Title => loc("An error occurred") $Error => '' <%init> my $login_url = $m->interp->apply_escapes(RT::Interface::Web::TangentForLoginURL(\%ARGS), 'h'); <% $Title %>

      <% $Title %>

      <% $m->content |n%>

      % if (my $owner = RT->Config->Get('OwnerEmail')) { % $owner = $m->interp->apply_escapes($owner, 'h'); <&|/l_unsafe, qq[], $owner, '' &>Contact your RT administrator via [_1]email to [_2][_3]. % } else { <&|/l&>Contact your RT administrator. % }

      % if (RT->Config->Get('WebRemoteUserAuth') and RT->Config->Get('WebFallbackToRTLogin')) {

      <&|/l_unsafe, qq[], '' &>If you have an internal RT login, you may [_1]try it instead[_2].

      % }

      rt-5.0.1/share/html/Errors/WebRemoteUser/NoRemoteUser000644 000765 000024 00000004137 14005011336 023372 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&| Wrapper, %ARGS, Title => loc("Unauthorized") &>

      <&|/l&>You are not authorized to use RT.

      rt-5.0.1/share/html/Errors/WebRemoteUser/NoInternalUser000644 000765 000024 00000004164 14005011336 023713 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&| Wrapper, %ARGS, Title => loc("Unauthorized") &>

      <&|/l, $ARGS{User} &>You ([_1]) are not authorized to use RT.

      rt-5.0.1/share/html/Errors/WebRemoteUser/UserAutocreateDefaultsOnLogin000644 000765 000024 00000004317 14005011336 026714 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&| Wrapper, %ARGS, Title => loc("Automatic account setup failed") &>

      <&|/l&>Unfortunately, RT couldn't automatically setup an account for you. Your RT administator will find more information in the logs.

      rt-5.0.1/share/html/Ticket/ShowEmailRecord.html000644 000765 000024 00000010707 14005011336 022214 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $Attachment => undef $Transaction => undef <%INIT> my $plain_text_mono = RT->Config->Get( 'PlainTextMono', $session{'CurrentUser'} ); my $use_brs = !$plain_text_mono; my $show_content = sub { my $attach = shift; if ( $attach->ContentType =~ m{^(?:text|message)/}i ) { my $content = $m->interp->apply_escapes( $attach->Content, 'h' ); $content =~ s{(\r?\n)}{
      }g if $use_brs; $m->out( $content ); return; } my $href = RT->System->ExternalStorageURLFor($attach) || RT->Config->Get('WebPath') .'/Ticket/Attachment/' . $attach->TransactionId .'/'. $attach->id .'/' . $m->interp->apply_escapes( $attach->Filename, 'u' ); $m->out( ''. loc('download') .'' ); }; my $show; $show = sub { my $attach = shift; $m->out('
      '); # Close rt-header-container $m->out('
      '); $m->out('
      ') if $plain_text_mono; my $headers = $m->interp->apply_escapes( $attach->Headers, 'h' ); $headers =~ s{(\r?\n)}{
      }g if $use_brs; $m->out( $headers ); $m->out( $use_brs ? "

      " : "\n\n" ); if ( $attach->ContentType =~ m{^multipart/}i ) { my $children = $attach->Children; while ( my $child = $children->Next ) { $show->( $child ); } } else { $show_content->( $attach ); } $m->out('
      ') if $plain_text_mono; $m->out('
      '); }; # Set error for error message below. Abort doesn't display well # because ShowEmailRecord doesn't use the standard RT menus # and headers. my ($title, $error); my $AttachmentObj = RT::Attachment->new($session{'CurrentUser'}); $AttachmentObj->Load($Attachment); if ( not $AttachmentObj->id or not $AttachmentObj->TransactionId() == $Transaction ) { $title = loc("Error loading attachment"); $error = loc("Attachment '[_1]' could not be loaded", $Attachment); } elsif ( not $AttachmentObj->TransactionObj->CurrentUserCanSee("Transaction")){ $title = loc("Permission Denied"); $error = loc("Permission Denied"); } else{ $title = loc("Email Source for Ticket [_1], Attachment [_2]", $AttachmentObj->TransactionObj->ObjectId, $AttachmentObj->Id); } <& /Elements/Header, ShowBar => 0, Title => $title &> % if ( $error ){
      <% $error %>
      % } % else{ % $show->( $AttachmentObj ); % } % $m->abort; rt-5.0.1/share/html/Ticket/Update.html000644 000765 000024 00000042176 14005011336 020414 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => $title &> <& /Elements/Tabs &> % $m->callback(CallbackName => 'BeforeActionList', ARGSRef => \%ARGS, Ticket => $TicketObj); <& /Elements/ListActions, actions => \@results &>
      % $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS, Ticket => $TicketObj, CanRespond => $CanRespond, CanComment => $CanComment, ResponseDefault => $ResponseDefault, CommentDefault => $CommentDefault ); <& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
      <&|/Widgets/TitleBox, title => loc('Ticket and Transaction'), class => 'ticket-info-basics' &>
      % $m->callback(CallbackName => 'AfterTableOpens', ARGSRef => \%ARGS, Ticket => $TicketObj); % my $skip; % $m->callback( %ARGS, CallbackName => 'BeforeUpdateType', skip => \$skip ); % if (!$skip) { % } % if ( $TicketObj->CurrentUserHasRight('SeeQueue') ) {
      <&|/l&>Queue:
      <% $TicketObj->QueueObj->Name %>
      % }
      <&|/l&>Update Type:
      % $m->callback( %ARGS, CallbackName => 'AfterUpdateType' );
      <& /Ticket/Elements/EditBasics, TicketObj => $TicketObj, InTable => 1, fields => [ { name => 'Status', comp => '/Ticket/Elements/SelectStatus', args => { Name => 'Status', Default => $DefaultStatus, TicketObj => $TicketObj, }, }, { name => 'Owner', comp => '/Elements/SelectOwner', args => { Name => "Owner", TicketObj => $TicketObj, QueueObj => $TicketObj->QueueObj, DefaultLabel => loc("[_1] (Unchanged)", $TicketObj->OwnerObj->Format), Default => $ARGS{'Owner'} } }, { special => 'roles' }, { name => 'Worked', comp => '/Elements/EditTimeValue', args => { Name => 'UpdateTimeWorked', Default => $ARGS{UpdateTimeWorked}||'', } }, ] &> % $m->callback( %ARGS, CallbackName => 'AfterWorked', Ticket => $TicketObj ); <& /Ticket/Elements/EditTransactionCustomFields, %ARGS, TicketObj => $TicketObj &>
      % $m->callback( %ARGS, CallbackName => 'RightColumnBottom', Ticket => $TicketObj );
      % if ( RT->Config->Get('SimplifiedRecipients', $session{'CurrentUser'}) ) { <&|/Widgets/TitleBox, title => loc('Recipients'), id => 'recipients' &> % unless ($TicketObj->CurrentUserHasRight('ShowOutgoingEmail')) { % for my $address (grep {defined} ref $ARGS{TxnSendMailTo} ? @{$ARGS{TxnSendMailTo}} : $ARGS{TxnSendMailTo}) { % } % } %} <&|/Widgets/TitleBox, title => loc('Message'), class => 'messagedetails' &> <& /Ticket/Elements/UpdateCc, %ARGS, TicketObj => $TicketObj &> % if ( $gnupg_widget ) {
      <& /Elements/Crypt/SignEncryptWidget, self => $gnupg_widget, TicketObj => $TicketObj, &>
      % } % $m->callback( %ARGS, CallbackName => 'AfterGnuPG' );
      <&|/l&>Subject:
      % $m->callback( %ARGS, CallbackName => 'AfterSubject' );
      <& /Articles/Elements/BeforeMessageBox, %ARGS &>
      % $m->callback( %ARGS, CallbackName => 'BeforeMessageBox' ); % if (exists $ARGS{UpdateContent}) { % # preserve QuoteTransaction so we can use it to set up sane references/in/reply to % my $temp = $ARGS{'QuoteTransaction'}; % delete $ARGS{'QuoteTransaction'}; <& /Elements/MessageBox, Name=>"UpdateContent", Default=>$ARGS{UpdateContent}, IncludeSignature => 0, %ARGS&> % $ARGS{'QuoteTransaction'} = $temp; % } else { % my $IncludeSignature = 1; % $IncludeSignature = 0 if $Action ne 'Respond' && !RT->Config->Get('MessageBoxIncludeSignatureOnComment'); <& /Elements/MessageBox, Name=>"UpdateContent", IncludeSignature => $IncludeSignature, %ARGS &> % } % $m->callback( %ARGS, CallbackName => 'AfterMessageBox' );
      <& /Ticket/Elements/AddAttachments, %ARGS, TicketObj => $TicketObj &> % $m->callback( %ARGS, CallbackName => 'BeforeSubmit', Ticket => $TicketObj );
      <& /Elements/Submit, Label => loc('Update Ticket'), Name => 'SubmitTicket', id => 'SubmitTicket' &>
      % $m->callback( %ARGS, CallbackName => 'BeforeScrips', Ticket => $TicketObj ); % if ($TicketObj->CurrentUserHasRight('ShowOutgoingEmail')) { <&|/Widgets/TitleBox, title => loc('Scrips and Recipients'), id => 'previewscrips', rolledup => RT->Config->Get('SimplifiedRecipients', $session{'CurrentUser'}), class => 'ticket-info-people' &> % for my $address (grep {defined} ref $ARGS{TxnSendMailTo} ? @{$ARGS{TxnSendMailTo}} : $ARGS{TxnSendMailTo}) { % } % } %# Close #ticket-update-message
      % $m->callback( %ARGS, CallbackName => 'AfterScrips', Ticket => $TicketObj );

      % $m->callback( %ARGS, CallbackName => 'AfterForm', Ticket => $TicketObj ); % if ($TicketObj->CurrentUserHasRight('ShowOutgoingEmail') or RT->Config->Get('SimplifiedRecipients', $session{'CurrentUser'})) { % } <%INIT> my $CanRespond = 0; my $CanComment = 0; my $checks_failure = 0; my $TicketObj = LoadTicket($id); my @results; $m->callback( Ticket => $TicketObj, ARGSRef => \%ARGS, checks_failure => \$checks_failure, results => \@results, CallbackName => 'Initial' ); $m->scomp( '/Articles/Elements/SubjectOverride', Ticket => $TicketObj, ARGSRef => \%ARGS, results => \@results ); unless($DefaultStatus){ $DefaultStatus=($ARGS{'Status'} ||$TicketObj->Status()); } my $title = loc("Update ticket #[_1]: [_2]", $TicketObj->id, $TicketObj->Subject); # Things needed in the template - we'll do the processing here, just # for the convenience: my ($CommentDefault, $ResponseDefault); if ($Action ne 'Respond') { $CommentDefault = qq[ selected="selected"]; $ResponseDefault = ""; } else { $CommentDefault = ""; $ResponseDefault = qq[ selected="selected"]; } my $type = $ARGS{'UpdateType'} ? $ARGS{'UpdateType'} : lc $Action eq 'respond' ? 'response' : lc $Action eq 'comment' ? 'private' : 'none' ; $CanRespond = 1 if ( $TicketObj->CurrentUserHasRight('ReplyToTicket') or $TicketObj->CurrentUserHasRight('ModifyTicket') ); $CanComment = 1 if ( $TicketObj->CurrentUserHasRight('CommentOnTicket') or $TicketObj->CurrentUserHasRight('ModifyTicket') ); ProcessAttachments(ARGSRef => \%ARGS); my %squelched = ProcessTransactionSquelching( \%ARGS ); $ARGS{'SquelchMailTo'} = [keys %squelched] if keys %squelched; my $gnupg_widget = $m->comp('/Elements/Crypt/SignEncryptWidget:new', Arguments => \%ARGS ); $m->comp( '/Elements/Crypt/SignEncryptWidget:Process', self => $gnupg_widget, TicketObj => $TicketObj, ); if ( $ARGS{'SubmitTicket'} ) { my ($status, @msg) = $m->comp( '/Elements/ValidateCustomFields', CustomFields => $TicketObj->TransactionCustomFields, Object => RT::Transaction->new( $session{'CurrentUser'} ), ARGSRef => \%ARGS ); unless ( $status ) { push @results, @msg; $checks_failure = 1; } $status = $m->comp('/Elements/Crypt/SignEncryptWidget:Check', self => $gnupg_widget, TicketObj => $TicketObj, ); $checks_failure = 1 unless $status; } # check email addresses for RT's { foreach my $field ( qw(UpdateCc UpdateBcc) ) { my $value = $ARGS{ $field }; next unless defined $value && length $value; my @emails = Email::Address->parse( $value ); foreach my $email ( grep RT::EmailParser->IsRTAddress($_->address), @emails ) { push @results, loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop", $email->format, loc(substr($field, 6)) ); $checks_failure = 1; $email = undef; } $ARGS{ $field } = join ', ', map $_->format, grep defined, @emails; } } # $skip_update is provided below by reference to allow a final check to stop # the update and print a message for the user to fix something. my $skip_update = 0; $m->callback( CallbackName => 'BeforeUpdate', ARGSRef => \%ARGS, skip_update => \$skip_update, checks_failure => $checks_failure, results => \@results, TicketObj => $TicketObj ); if ( !$checks_failure && !$skip_update && exists $ARGS{SubmitTicket} ) { $m->callback( Ticket => $TicketObj, ARGSRef => \%ARGS, CallbackName => 'BeforeDisplay' ); return $m->comp('Display.html', TicketObj => $TicketObj, %ARGS); } $TicketObj->CurrentUser->AddRecentlyViewedTicket($TicketObj); <%ARGS> $id => undef $Action => '' $DefaultStatus => undef rt-5.0.1/share/html/Ticket/Crypt.html000644 000765 000024 00000007016 14005011336 020265 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => $title &> <& /Elements/Tabs &> % $m->callback( CallbackName => 'BeforeActionList', %ARGS, Actions => \@results, ARGSRef => \%ARGS ); <& /Elements/ListActions, actions => \@results &>
      <% loc('Return back to the ticket') %> <& /Elements/Submit, Label => ($encrypted? loc('Decrypt'): loc('Encrypt')), Name => ($encrypted? 'Decrypt': 'Encrypt'), &>
      <%ARGS> $id => undef $Encrypt => 0 $Decrypt => 0 <%INIT> my $txn = RT::Transaction->new( $session{'CurrentUser'} ); $txn->Load( $id ); unless ( $txn->id ) { Abort(loc("Couldn't load transaction #[_1]", $id)); } $id = $txn->id; my @results; my $encrypted = 0; my $attachments = $txn->Attachments; while ( my $attachment = $attachments->Next ) { next unless $attachment->ContentType =~ m{^x-application-rt/[^-]+-encrypted\b}; $encrypted = 1; last; } $attachments->GotoFirstItem; if ( $Encrypt || $Decrypt ) { my $done = 1; while ( my $attachment = $attachments->Next ) { my ($status, $msg) = $Decrypt? $attachment->Decrypt : $attachment->Encrypt; push @results, $msg; unless ( $status ) { $done = 0; last; } } $encrypted = !$encrypted if $done; } my $title = loc("Encrypt/Decrypt transaction #[_1] of ticket #[_2]: [_3]", $id, $txn->Ticket, $txn->TicketObj->Subject); rt-5.0.1/share/html/Ticket/ModifyPeople.html000644 000765 000024 00000015514 14005011336 021562 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => loc('Modify people related to ticket #[_1]: [_2]', $Ticket->id, $Ticket->Subject) &> <& /Elements/Tabs &> % $m->callback(CallbackName => 'BeforeActionList', Actions => \@results, ARGSRef => \%ARGS, Ticket => $Ticket); <& /Elements/ListActions, actions => \@results &>
      % $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS ); <&| /Widgets/TitleBox, title => loc('Modify people related to ticket #[_1]', $Ticket->Id), width => "100%", color=> "#333399", class=>'ticket-info-people' &> <& Elements/EditPeople, Ticket => $Ticket, UserField => $UserField, UserString => $UserString, UserOp => $UserOp, GroupString => $GroupString, GroupOp => $GroupOp, GroupField => $GroupField &> <&| /Widgets/TitleBox, title => loc("Modify who receives mail for ticket #[_1]", $Ticket->Id), width => "100%", color=> "#333399", class=>'ticket-info-squelch' &>

      <&|/l&>The checked users may receive email related to this ticket depending on the action taken. Uncheck users to stop sending email to them about this ticket.

      <%PERL> my $all_recipients_checked = (grep { !$_ } values %recips) ? 0 : 1;
      >
        % for my $addr (sort keys %recips) {
      • >
      • % }
      <& /Elements/Submit, Name => 'SubmitTicket', Label => loc('Save Changes'), Caption => loc("If you've updated anything above, be sure to"), color => "#333399" &>
      % $m->callback(CallbackName => 'AfterForm', ARGSRef => \%ARGS, Ticket => $Ticket); <%INIT> my @results; my $Ticket = LoadTicket($id); $m->callback( TicketObj => $Ticket, ARGSRef => \%ARGS ); # Update the squelch list my %squelched = map {$_->Content => 1} $Ticket->SquelchMailTo; my %checked = map {$_ => 1} grep {defined} (ref $ARGS{'checked_recipient'} eq "ARRAY" ? @{$ARGS{'checked_recipient'}} : defined $ARGS{'checked_recipient'} ? ($ARGS{'checked_recipient'}) : ()); my @all = grep {defined} (ref $ARGS{'autorecipient'} eq "ARRAY" ? @{$ARGS{'autorecipient'}} : defined $ARGS{'autorecipient'} ? ($ARGS{'autorecipient'}) : ()); $Ticket->UnsquelchMailTo($_) for grep {$squelched{$_}} keys %checked; $Ticket->SquelchMailTo($_) for grep {!$squelched{$_} and !$checked{$_}} @all; # if we're trying to search for watchers and nothing else unless ($OnlySearchForPeople or $OnlySearchForGroup) { $Ticket->Atomic(sub{ push @results, ProcessTicketBasics( TicketObj => $Ticket, ARGSRef => \%ARGS); push @results, ProcessTicketWatchers( TicketObj => $Ticket, ARGSRef => \%ARGS); push @results, ProcessObjectCustomFieldUpdates( Object => $Ticket, ARGSRef => \%ARGS ); }); } # Use the ticket's scrips to figure out the new list of recipients. my @txns = $Ticket->DryRun( sub { my $MIME = MIME::Entity->build( Type => "text/plain", Data => "" ); $Ticket->Comment(MIMEObj => $MIME); $Ticket->Correspond(MIMEObj => $MIME); } ); my %recips=(); for my $scrip (map {@{$_->Scrips->Prepared}} @txns) { next unless $scrip->ActionObj->Action->isa('RT::Action::SendEmail'); for my $type (qw(To Cc Bcc)) { $recips{$_->address} = 1 for $scrip->ActionObj->Action->$type(); } } for my $rule (map {@{$_->Rules}} @txns) { next unless $rule->{hints} && $rule->{hints}{class} eq "SendEmail"; for my $type (qw(To Cc Bcc)) { $recips{$_} = 1 for @{$rule->{hints}{recips}{$type}}; } } # Use tkt squelch list to get recipients who will NOT get mail: $recips{$_->Content} = 0 for $Ticket->SquelchMailTo; $Ticket->CurrentUser->AddRecentlyViewedTicket($Ticket); <%ARGS> $OnlySearchForPeople => undef $OnlySearchForGroup => undef $UserField => undef $UserOp => undef $UserString => undef $GroupField => undef $GroupOp => undef $GroupString => undef $id => undef rt-5.0.1/share/html/Ticket/Modify.html000644 000765 000024 00000011722 14005011336 020412 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => loc('Modify ticket #[_1]: [_2]', $TicketObj->Id, $TicketObj->Subject) &> <& /Elements/Tabs &> % $m->callback(CallbackName => 'BeforeActionList', Actions => \@results, ARGSRef => \%ARGS, Ticket => $TicketObj); <& /Elements/ListActions, actions => \@results &>
      % $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS ); <&| /Widgets/TitleBox, title => loc('Modify ticket #[_1]',$TicketObj->Id), class=>'ticket-info-basics' &>
      <& Elements/EditBasics, TicketObj => $TicketObj, defaults => \%ARGS, InTable => 1 &> <& /Elements/EditCustomFields, %ARGS, Object => $TicketObj, Grouping => 'Basics', InTable => 1 &>
      % $m->callback( CallbackName => 'AfterBasics', Ticket => $TicketObj ); <& /Elements/EditCustomFieldCustomGroupings, %ARGS, Object => $TicketObj, AsTable => !!RT->Config->Get('EditCustomFieldsSingleColumn', $session{'CurrentUser'}) &>
      <& /Elements/Submit, Name => 'SubmitTicket', Label => loc('Save Changes'), Caption => loc("If you've updated anything above, be sure to"), color => "#993333" &>
      % $m->callback(CallbackName => 'AfterForm', ARGSRef => \%ARGS, Ticket => $TicketObj); <%INIT> my $TicketObj = LoadTicket($id); my $CustomFields = $TicketObj->CustomFields; my @results; my $skip_update = 0; # Now let callbacks have a chance at editing %ARGS $m->callback( TicketObj => $TicketObj, CustomFields => $CustomFields, ARGSRef => \%ARGS, skip_update => \$skip_update, results => \@results ); { my ($status, @msg) = $m->comp( '/Elements/ValidateCustomFields', Object => $TicketObj, CustomFields => $CustomFields, ARGSRef => \%ARGS, ); unless ($status) { push @results, @msg; $skip_update = 1; } } unless ($skip_update) { $TicketObj->Atomic(sub{ push @results, ProcessTicketBasics(TicketObj => $TicketObj, ARGSRef => \%ARGS); push @results, ProcessTicketWatchers(TicketObj => $TicketObj, ARGSRef => \%ARGS); push @results, ProcessObjectCustomFieldUpdates(Object => $TicketObj, ARGSRef => \%ARGS); $m->callback( CallbackName => 'ProcessUpdates', TicketObj => $TicketObj, ARGSRef => \%ARGS, Results => \@results ); }); MaybeRedirectForResults( Actions => \@results, Path => "/Ticket/Modify.html", Arguments => { id => $TicketObj->id }, ); } unless ($TicketObj->CurrentUserHasRight('ShowTicket')) { if (@results) { Abort("A change was applied successfully, but you no longer have permissions to view the ticket", Actions => \@results); } else { Abort("No permission to view ticket"); } } $TicketObj->CurrentUser->AddRecentlyViewedTicket($TicketObj); <%ARGS> $id => undef rt-5.0.1/share/html/Ticket/autohandler000644 000765 000024 00000005033 14005011336 020524 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> # Redirect to the approvals view if we're trying to get an approvals ticket # Exceptions: # - Display handles redirecting for approvals itself after mobile redirect/processing # - Create doesn't have an existing ticket # - Forward and ShowEmailRecord are used by the approvals view # - anything not ending in a .html my $whitelist = qr{ (?:/(?:Display|Create|Forward|ShowEmailRecord)\.html |(? $whitelist, ARGSRef => \%ARGS, ); $m->call_next; rt-5.0.1/share/html/Ticket/Attachment/000755 000765 000024 00000000000 14005011336 020362 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Ticket/Create.html000644 000765 000024 00000044722 14005011336 020374 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => $title, &> <& /Elements/Tabs &> <& /Elements/ListActions, actions => \@results &>
      % if ( $ARGS{'AddGroupCc'} ){ % } % $m->callback( CallbackName => 'FormStart', QueueObj => $QueueObj, ARGSRef => \%ARGS ); % if ($gnupg_widget) { <& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget &> % }
      <&| /Widgets/TitleBox, title => loc("Basics"), class=>'ticket-info-basics' &>
      <& /Ticket/Elements/EditBasics, InTable => 1, QueueObj => $QueueObj, defaults => \%ARGS, fields => [ { name => 'Queue', comp => '/Elements/SelectQueue', args => { Name => 'Queue', Default => $Queue, ShowNullOption => 0, AutoSubmit => 1, ShowAllQueues => 0, }, }, { name => 'Status', comp => '/Ticket/Elements/SelectStatus', args => { Name => "Status", QueueObj => $QueueObj, }, }, { name => 'Owner', comp => '/Elements/SelectOwner', args => { Name => "Owner", Default => $ARGS{Owner} || RT->Nobody->Id, DefaultValue => 0, QueueObj => $QueueObj, }, }, { special => 'roles' }, $QueueObj->SLADisabled ? () : ( { name => 'SLA', comp => '/Elements/SelectSLA', args => { Name => "SLA", Default => $ARGS{SLA} || RT::SLA->GetDefaultServiceLevel(Queue => $QueueObj), DefaultValue => RT::SLA->GetDefaultServiceLevel(Queue => $QueueObj) ? 0 : 1, QueueObj => $QueueObj, }, }), ] &> % $m->callback( CallbackName => 'AfterOwner', ARGSRef => \%ARGS ); <& /Elements/EditCustomFields, %ARGS, Object => $ticket, CustomFields => $QueueObj->TicketCustomFields, Grouping => 'Basics', InTable => 1, ForCreation => 1, &> <& /Ticket/Elements/EditTransactionCustomFields, %ARGS, QueueObj => $QueueObj, InTable => 1 &>
      <& /Ticket/Elements/ShowAssetsOnCreate, QueueObj => $QueueObj, ARGSRef => \%ARGS &> % $m->callback( CallbackName => 'AfterBasics', QueueObj => $QueueObj, ARGSRef => \%ARGS ); <& /Elements/EditCustomFieldCustomGroupings, %ARGS, Object => $ticket, CustomFieldGenerator => sub { $QueueObj->TicketCustomFields }, ForCreation => 1, &>
      <&| /Widgets/TitleBox, title => $title, class => 'messagedetails' &>
      % $m->callback(CallbackName => 'BeforeRequestors', QueueObj => $QueueObj, ARGSRef => \%ARGS);
      <&|/l&>Requestors:
      <& /Elements/EmailInput, Name => 'Requestors', Size => undef, Default => $ARGS{Requestors} // $session{CurrentUser}->EmailAddress, AutocompleteMultiple => 1 &> % $m->callback( CallbackName => 'AfterRequestors', QueueObj => $QueueObj, ARGSRef => \%ARGS );
      <&|/l&>Cc:
      <& /Elements/EmailInput, Name => 'Cc', Size => undef, Default => $ARGS{Cc}, AutocompleteMultiple => 1 &>
      <&|/l&>Admin Cc:
      <& /Elements/EmailInput, Name => 'AdminCc', Size => undef, Default => $ARGS{AdminCc}, AutocompleteMultiple => 1 &>
      % my $roles = $QueueObj->CustomRoles; % $roles->LimitToMultipleValue; % my @hidden = $QueueObj->HiddenCustomRoleIDsForURL; % $roles->Limit(FIELD => 'id', OPERATOR => 'NOT IN', VALUE => \@hidden) if @hidden; % $m->callback( CallbackName => 'ModifyCustomRoles', ARGSRef => \%ARGS, CustomRoles => $roles ); % while (my $role = $roles->Next) {
      <% $role->Name %>: % if ($role->EntryHint) { % }
      <& /Elements/EmailInput, Name => $role->GroupType, Size => undef, Default => $ARGS{$role->GroupType}, AutocompleteMultiple => 1 &>
      % } <& /Elements/EditCustomFields, %ARGS, Object => $ticket, CustomFields => $QueueObj->TicketCustomFields, Grouping => 'People', InTable => 1, ForCreation => 1, &>
      <&|/l&>Subject:
      % $m->callback( %ARGS, CallbackName => 'AfterSubject' );
      % if ( $gnupg_widget ) {
      <& /Elements/Crypt/SignEncryptWidget, self => $gnupg_widget, QueueObj => $QueueObj &>
      % } % if ( RT->Config->Get('ArticleOnTicketCreate')) { <& /Articles/Elements/BeforeMessageBox, %ARGS, QueueObj => $QueueObj &> % }
      % $m->callback( %ARGS, QueueObj => $QueueObj, CallbackName => 'BeforeMessageBox' ); % if (exists $ARGS{Content}) { <& /Elements/MessageBox, QueueObj => $QueueObj, Default => $ARGS{Content}, IncludeSignature => 0, IncludeDefaultArticle => 0 &> % } elsif ( $QuoteTransaction ) { <& /Elements/MessageBox, QueueObj => $QueueObj, QuoteTransaction => $QuoteTransaction, IncludeDefaultArticle => 0 &> % } else { <& /Elements/MessageBox, QueueObj => $QueueObj, IncludeDefaultArticle => 1 &> %} % $m->callback( %ARGS, QueueObj => $QueueObj, CallbackName => 'AfterMessageBox' );
      <& /Ticket/Elements/AddAttachments, %ARGS, QueueObj => $QueueObj &>
      <& /Elements/Submit, Label => loc("Create"), Name => 'SubmitTicket', id => 'SubmitTicket' &>
      <%INIT> $m->callback( CallbackName => "Init", ARGSRef => \%ARGS ); my $Queue = $ARGS{Queue}; # Use default queue from config site or user prefs if none provided unless ($Queue) { $Queue = GetDefaultQueue(); } # pick first in list in normal order unless queue provided from form/url/defaults unless ($Queue) { my $cache_key = SetObjectSessionCache( ObjectType => 'Queue', CheckRight => 'CreateTicket', CacheNeedsUpdate => RT->System->QueueCacheNeedsUpdate, ShowAll => 0, ); $Queue = $session{$cache_key}{objects}[0]->{Id} if $session{$cache_key}{objects}[0]; } Abort( loc( "Permission Denied" ) ) unless $Queue; $session{DefaultQueue} = $Queue; my $current_user = $session{'CurrentUser'}; if ($CloneTicket) { my $CloneTicketObj = RT::Ticket->new( $session{CurrentUser} ); $CloneTicketObj->Load($CloneTicket) or Abort( loc("Ticket could not be loaded"), Code => HTTP::Status::HTTP_BAD_REQUEST ); my $clone = { Requestors => join( ',', $CloneTicketObj->RequestorAddresses ), Cc => join( ',', $CloneTicketObj->CcAddresses ), AdminCc => join( ',', $CloneTicketObj->AdminCcAddresses ), InitialPriority => $CloneTicketObj->Priority, }; $clone->{$_} = $CloneTicketObj->$_() for qw/Owner Subject FinalPriority Status/; $clone->{$_} = $CloneTicketObj->$_->AsString for grep { $CloneTicketObj->$_->IsSet } map { $_ . "Obj" } qw/Starts Started Due Resolved/; my $get_link_value = sub { my ($link, $type) = @_; my $uri_method = $type . 'URI'; my $local_method = 'Local' . $type; my $uri = $link->$uri_method; return if $uri->IsLocal and $uri->Object and $uri->Object->isa('RT::Ticket') and $uri->Object->__Value('Type') eq 'reminder'; return $link->$local_method || $uri->URI; }; my (@refers, @refers_by); my $refers = $CloneTicketObj->RefersTo; while ( my $refer = $refers->Next ) { my $refer_value = $get_link_value->($refer, 'Target'); push @refers, $refer_value if defined $refer_value; } $clone->{'new-RefersTo'} = join ' ', @refers; my $refers_by = $CloneTicketObj->ReferredToBy; while ( my $refer_by = $refers_by->Next ) { my $refer_by_value = $get_link_value->($refer_by, 'Base'); push @refers_by, $refer_by_value if defined $refer_by_value; } $clone->{'RefersTo-new'} = join ' ', @refers_by; my $cfs = $CloneTicketObj->QueueObj->TicketCustomFields(); while ( my $cf = $cfs->Next ) { my $cf_id = $cf->id; my $cf_values = $CloneTicketObj->CustomFieldValues( $cf->id ); my @cf_values; while ( my $cf_value = $cf_values->Next ) { push @cf_values, $cf_value->Content; } if ( @cf_values > 1 && $cf->Type eq 'Select' ) { $clone->{GetCustomFieldInputName( CustomField => $cf )} = \@cf_values; } else { $clone->{GetCustomFieldInputName( CustomField => $cf )} = join "\n", @cf_values; } } $m->callback( CallbackName => 'MassageCloneArgs', ARGSRef => $clone, Queue => $Queue ); for ( keys %$clone ) { $ARGS{$_} = $clone->{$_} if not defined $ARGS{$_}; } } my @results; my $QueueObj = RT::Queue->new($current_user); $QueueObj->Load($Queue) || Abort(loc("Queue [_1] could not be loaded.", $Queue||''), Code => HTTP::Status::HTTP_BAD_REQUEST); my $title = loc("Create a new ticket in [_1]", $m->scomp("/Ticket/Elements/ShowQueue", QueueObj => $QueueObj, Escape => 0)); $m->callback( QueueObj => $QueueObj, title => \$title, results => \@results, ARGSRef => \%ARGS ); $m->scomp( '/Articles/Elements/SubjectOverride', ARGSRef => \%ARGS, QueueObj => $QueueObj, results => \@results ); $QueueObj->Disabled && Abort(loc("Cannot create tickets in a disabled queue."), Code => HTTP::Status::HTTP_NOT_FOUND); my $ticket = RT::Ticket->new($current_user); # empty ticket object ProcessAttachments(ARGSRef => \%ARGS); my $checks_failure = 0; { my ($status, @msg) = $m->comp( '/Elements/ValidateCustomFields', CustomFields => $QueueObj->TicketCustomFields, ARGSRef => \%ARGS ); unless ($status) { $checks_failure = 1; push @results, @msg; } } my $gnupg_widget = $m->comp('/Elements/Crypt/SignEncryptWidget:new', Arguments => \%ARGS ); $m->comp( '/Elements/Crypt/SignEncryptWidget:Process', self => $gnupg_widget, QueueObj => $QueueObj, ); if ( $ARGS{SubmitTicket} ) { my $status = $m->comp('/Elements/Crypt/SignEncryptWidget:Check', self => $gnupg_widget, Operation => 'Create', QueueObj => $QueueObj, ); $checks_failure = 1 unless $status; } # check email addresses for RT's { foreach my $field ( qw(Requestors Cc AdminCc) ) { my $value = $ARGS{ $field }; next unless defined $value && length $value; my @emails = Email::Address->parse( $value ); foreach my $email ( grep RT::EmailParser->IsRTAddress($_->address), @emails ) { push @results, loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop", $email->format, loc($field =~ /^(.*?)s?$/) ); $checks_failure = 1; $email = undef; } $ARGS{ $field } = join ', ', map $_->format, grep defined, @emails; } } my $skip_create = 0; $m->callback( CallbackName => 'BeforeCreate', ARGSRef => \%ARGS, skip_create => \$skip_create, checks_failure => $checks_failure, results => \@results ); $m->comp( '/Articles/Elements/CheckSkipCreate', ARGSRef => \%ARGS, skip_create => \$skip_create, checks_failure => $checks_failure, results => \@results ); if ( !$checks_failure && !$skip_create && $ARGS{SubmitTicket} ) { $m->comp('Display.html', %ARGS); $RT::Logger->crit("After display call; error is $@"); $m->abort(); } PageMenu->child( basics => raw_html => q[] . loc('Basics') . q[]); PageMenu->child( details => raw_html => q[] . loc('Details') . q[]); <%ARGS> $DependsOn => undef $DependedOnBy => undef $MemberOf => undef $QuoteTransaction => undef $CloneTicket => undef $AddGroupCc => undef rt-5.0.1/share/html/Ticket/Display.html000644 000765 000024 00000026505 14005011336 020575 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => $title, LinkRel => \%link_rel &> <& /Elements/Tabs &> % $m->callback(CallbackName => 'BeforeActionList', %ARGS, Actions => \@Actions, ARGSRef => \%ARGS, Ticket => $TicketObj); <& /Elements/ListActions, actions => \@Actions &> <& Elements/ShowUpdateStatus, Ticket => $TicketObj &> <& Elements/ShowDependencyStatus, Ticket => $TicketObj &> % $m->callback( %ARGS, Ticket => $TicketObj, Transactions => $transactions, Attachments => $attachments, CallbackName => 'BeforeShowSummary' ); <%PERL> my $show_label = $m->interp->apply_escapes( loc("Show unset fields"), 'h' ); my $hide_label = $m->interp->apply_escapes( loc("Hide unset fields"), 'h' ); my $initial_label = $HideUnsetFields ? $show_label : $hide_label; my $url = "?HideUnsetFields=" . ($HideUnsetFields ? 0 : 1) . ";id=$ARGS{id}"; my $url_html = $m->interp->apply_escapes($url, 'h'); my $search_menu = $m->scomp( '/Elements/Menu', menu => HTML::Mason::Commands::SearchResultsPageMenu(), class => 'page-menu page-nav-shadow sf-menu sf-js-enabled sf-shadow', id => 'search-results-page-menu', 'parent_id' => 'page' ) || ''; my $alt = loc('Options'); my $titleright = qq{ };
      <&| /Widgets/TitleBox, title => loc('Ticket metadata'), titleright_raw => $titleright, class => 'fullwidth' &> <& /Ticket/Elements/ShowSummary, Ticket => $TicketObj, Attachments => $attachments, InlineEdit => $InlineEdit &>
      % $m->callback( Ticket => $TicketObj, %ARGS, Transactions => $transactions, Attachments => $attachments, CallbackName => 'BeforeShowHistory' ); % my $ShowHistory = RT->Config->Get("ShowHistory", $session{'CurrentUser'}); % if ($ShowHistory eq "scroll") { <& /Ticket/Elements/ScrollShowHistory, Ticket => $TicketObj, ShowHeaders => $ARGS{'ShowHeaders'}, ReverseTxns => $ARGS{'ReverseTxns'}, &> % } elsif ($ShowHistory eq "delay") { <& /Ticket/Elements/DelayShowHistory, Ticket => $TicketObj, ShowHeaders => $ARGS{'ShowHeaders'}, ReverseTxns => $ARGS{'ReverseTxns'}, &> % } elsif (not $ForceShowHistory and $ShowHistory eq "click") { <& /Ticket/Elements/ClickToShowHistory, Ticket => $TicketObj, ShowHeaders => $ARGS{'ShowHeaders'}, ReverseTxns => $ARGS{'ReverseTxns'}, &> % } else { <& /Elements/ShowHistory , Object => $TicketObj, Transactions => $transactions, ShowHeaders => $ARGS{'ShowHeaders'}, ReverseTxns => $ARGS{'ReverseTxns'}, Attachments => $attachments, AttachmentContent => $attachment_content &> % } % $m->callback( %ARGS, % Ticket => $TicketObj, % Transactions => $transactions, % Attachments => $attachments, % CallbackName => 'AfterShowHistory', % ); <%ARGS> $TicketObj => undef $ShowHeaders => 0 $HideUnsetFields => RT->Config->Get('HideUnsetFieldsOnDisplay', $session{CurrentUser}) $ForceShowHistory => 0 $InlineEdit => RT->Config->Get('InlineEdit', $session{CurrentUser}) <%INIT> $m->callback( TicketObj => $TicketObj, ARGSRef => \%ARGS, CallbackName => 'Initial' ); if ( ! $ARGS{'NoRedirect'} && RT::Interface::Web->MobileClient()) { $ARGS{'id'} ||= $TicketObj->id if $TicketObj; RT::Interface::Web::Redirect(RT->Config->Get('WebURL').'m/ticket/show?id='.$ARGS{'id'}); $m->abort; } my (@Actions, $title); unless ($ARGS{'id'} || $TicketObj) { Abort('No ticket specified'); } if ($ARGS{'id'} eq 'new') { # Create a new ticket my $Queue = RT::Queue->new( $session{'CurrentUser'} ); $Queue->Load($ARGS{'Queue'}); unless ( $Queue->id ) { Abort('Queue not found', Code => HTTP::Status::HTTP_NOT_FOUND); } unless ( $Queue->CurrentUserHasRight('CreateTicket') ) { Abort('You have no permission to create tickets in that queue.', Code => HTTP::Status::HTTP_FORBIDDEN); } ($TicketObj, @Actions) = CreateTicket( %ARGS ); unless ( $TicketObj->CurrentUserHasRight('ShowTicket') ) { Abort("No permission to view newly created ticket #".$TicketObj->id.".", Code => HTTP::Status::HTTP_FORBIDDEN); } } else { $TicketObj ||= LoadTicket($ARGS{'id'}); # fill ACL cache $TicketObj->CurrentUser->PrincipalObj->HasRights( Object => $TicketObj ); my $SkipProcessing; $TicketObj->Atomic(sub{ $m->callback( CallbackName => 'BeforeProcessArguments', TicketObj => $TicketObj, ActionsRef => \@Actions, ARGSRef => \%ARGS, SkipProcessing => \$SkipProcessing ); return if $SkipProcessing; if ( defined $ARGS{'Action'} ) { if ($ARGS{'Action'} =~ /^(Steal|Delete|Take|Untake|SetTold)$/) { my $action = $1; my ($res, $msg) = $TicketObj->$action(); push(@Actions, $msg); } } $m->callback(CallbackName => 'ProcessArguments', Ticket => $TicketObj, ARGSRef => \%ARGS, Actions => \@Actions); push @Actions, ProcessUpdateMessage( ARGSRef => \%ARGS, Actions => \@Actions, TicketObj => $TicketObj, ); #Process status updates push @Actions, ProcessTicketWatchers(ARGSRef => \%ARGS, TicketObj => $TicketObj ); push @Actions, ProcessTicketBasics( ARGSRef => \%ARGS, TicketObj => $TicketObj ); push @Actions, ProcessTicketLinks( ARGSRef => \%ARGS, TicketObj => $TicketObj ); push @Actions, ProcessTicketDates( ARGSRef => \%ARGS, TicketObj => $TicketObj ); push @Actions, ProcessObjectCustomFieldUpdates(ARGSRef => \%ARGS, Object => $TicketObj ); push @Actions, ProcessTicketReminders( ARGSRef => \%ARGS, TicketObj => $TicketObj ); }); if ( !$SkipProcessing ) { unless ($TicketObj->CurrentUserHasRight('ShowTicket')) { if (@Actions) { Abort("A change was applied successfully, but you no longer have permissions to view the ticket", Actions => \@Actions, Code => HTTP::Status::HTTP_FORBIDDEN); } else { Abort("No permission to view ticket", Code => HTTP::Status::HTTP_FORBIDDEN); } } if ( $ARGS{'MarkAsSeen'} ) { $TicketObj->SetAttribute( Name => 'User-'. $TicketObj->CurrentUser->id .'-SeenUpTo', Content => $TicketObj->LastUpdated, ); push @Actions, loc('Marked all messages as seen'); } $TicketObj->CurrentUser->AddRecentlyViewedTicket($TicketObj); } } $title = loc("#[_1]: [_2]", $TicketObj->Id, $TicketObj->Subject || ''); if ( $ARGS{'id'} and $ARGS{'id'} eq 'new' ) { if ( $ARGS{'AddGroupCc'} ){ my $group = RT::Group->new($session{'CurrentUser'}); my ($ret, $msg) = $group->LoadUserDefinedGroup($ARGS{'AddGroupCc'}); unless ( $ret ){ RT::Logger->warn("Unable to load group " . $ARGS{'AddGroupCc'} . ", $msg. Not adding as Cc."); return; } ( $ret, $msg ) = $TicketObj->AddWatcher( Type => 'Cc', PrincipalId => $group->Id ); RT::Logger->warn("Unable to add group " . $group->Name . ": " . $group->Id . " as a Cc: $msg") unless $ret; } } $m->callback( CallbackName => 'BeforeDisplay', TicketObj => \$TicketObj, Actions => \@Actions, title => \$title, ARGSRef => \%ARGS, ); # This code does automatic redirection if any updates happen. MaybeRedirectForResults( Actions => \@Actions, $TicketObj->Type eq 'approval' && RT->Config->Get('ForceApprovalsView') ? (Path => "/Approvals/Display.html", Force => 1) : (Path => "/Ticket/Display.html") , Anchor => $ARGS{'Anchor'}, Arguments => { id => $TicketObj->id }, ); my $transactions; if ( $ARGS{'ReverseTxns'} ) { $transactions = $TicketObj->SortedTransactions($ARGS{'ReverseTxns'}); } else { $transactions = $TicketObj->SortedTransactions; } my $attachments = $TicketObj->Attachments; my $attachment_content = $TicketObj->TextAttachments; my %link_rel; if (defined $session{'collection-RT::Tickets'} and ($ARGS{'Query'} or $session{'CurrentSearchHash'}->{'Query'})) { my $item_map = $session{'collection-RT::Tickets'}->ItemMap; $link_rel{first} = "/Ticket/Display.html?id=" . $item_map->{first} if $item_map->{$TicketObj->Id}{prev}; $link_rel{prev} = "/Ticket/Display.html?id=" . $item_map->{$TicketObj->Id}{prev} if $item_map->{$TicketObj->Id}{prev}; $link_rel{next} = "/Ticket/Display.html?id=" . $item_map->{$TicketObj->Id}{next} if $item_map->{$TicketObj->Id}{next}; $link_rel{last} = "/Ticket/Display.html?id=" . $item_map->{last} if $item_map->{$TicketObj->Id}{next} && $item_map->{last}; } rt-5.0.1/share/html/Ticket/ModifyAll.html000644 000765 000024 00000020507 14005011336 021044 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => loc("Ticket #[_1] Jumbo update: [_2]", $Ticket->Id, $Ticket->Subject) &> <& /Elements/Tabs &> % $m->callback(CallbackName => 'BeforeActionList', Actions => \@results, ARGSRef => \%ARGS, Ticket => $Ticket); <& /Elements/ListActions, actions => \@results &>
      % $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS ); <&| /Widgets/TitleBox, title => loc('Modify ticket # [_1]', $Ticket->Id), class=>'ticket-info-basics' &>
      <& Elements/EditBasics, TicketObj => $Ticket, defaults => \%ARGS, ExcludeCustomRoles => 1, InTable => 1 &> <& /Elements/EditCustomFields, Object => $Ticket, Grouping => 'Basics', InTable => 1 &>
      % $m->callback(CallbackName => 'AfterBasics', Ticket => $Ticket); <& /Elements/EditCustomFieldCustomGroupings, Object => $Ticket &> <&| /Widgets/TitleBox, title => loc('Dates'), class=>'ticket-info-dates'&> <& Elements/EditDates, TicketObj => $Ticket &> <&| /Widgets/TitleBox, title => loc('People'), class=>'ticket-info-people' &> <& Elements/EditPeople, Ticket => $Ticket, UserField => $UserField, UserString => $UserString, UserOp => $UserOp, GroupString => $GroupString, GroupOp => $GroupOp, GroupField => $GroupField &> <&| /Widgets/TitleBox, title => loc('Links'), class=>'ticket-info-links' &> <& /Elements/EditLinks, Object => $Ticket &> <&| /Widgets/TitleBox, title => loc('Merge'), class=>'ticket-info-merge' &> <& Elements/EditMerge, Ticket => $Ticket, %ARGS &> <&| /Widgets/TitleBox, title => loc('Update ticket'), class => 'messagedetails' &>
      <&|/l&>Update Type:
      % $m->callback( %ARGS, CallbackName => 'AfterUpdateType' );
      <&|/l&>Subject:
      % $m->callback( %ARGS, CallbackName => 'AfterSubject' );
      <& /Ticket/Elements/EditTransactionCustomFields, %ARGS, TicketObj => $Ticket, LabelCols => 2 &>
      % $m->callback( %ARGS, CallbackName => 'BeforeMessageBox' ); % if (defined $ARGS{UpdateContent} && length($ARGS{UpdateContent})) { <& /Elements/MessageBox, Name=>"UpdateContent", Default=>$ARGS{UpdateContent}, IncludeSignature => 0 &> % } else { <& /Elements/MessageBox, Name=>"UpdateContent", QuoteTransaction=>$ARGS{QuoteTransaction} &> % }
      <& /Ticket/Elements/AddAttachments, %ARGS, TicketObj => $Ticket &>
      <& /Elements/Submit, Name => 'SubmitTicket', Label => loc('Save Changes'), Caption => loc("If you've updated anything above, be sure to"), color => "#333399" &>
      % $m->callback(CallbackName => 'AfterForm', ARGSRef => \%ARGS, Ticket => $Ticket); <%INIT> my $Ticket = LoadTicket($id); my $CustomFields = $Ticket->CustomFields; my $CanRespond = 0; my $CanComment = 0; $CanRespond = 1 if ( $Ticket->CurrentUserHasRight('ReplyToTicket') or $Ticket->CurrentUserHasRight('ModifyTicket') ); $CanComment = 1 if ( $Ticket->CurrentUserHasRight('CommentOnTicket') or $Ticket->CurrentUserHasRight('ModifyTicket') ); ProcessAttachments(ARGSRef => \%ARGS); my @results; my $skip_update = 0; $m->callback( TicketObj => $Ticket, ARGSRef => \%ARGS, skip_update => \$skip_update, results => \@results ); { my ($status, @msg) = $m->comp( '/Elements/ValidateCustomFields', Object => $Ticket, CustomFields => $CustomFields, ARGSRef => \%ARGS, ); unless ($status) { push @results, @msg; $skip_update = 1; } } # There might be two owners. if ( ref ($ARGS{'Owner'} )) { my @owners =@{$ARGS{'Owner'}}; delete $ARGS{'Owner'}; foreach my $owner(@owners){ if (defined($owner) && $owner =~ /\D/) { $ARGS{'Owner'} = $owner unless ($Ticket->OwnerObj->Name eq $owner); } elsif (length $owner) { $ARGS{'Owner'} = $owner unless ($Ticket->OwnerObj->id == $owner); } } } unless ($skip_update or $OnlySearchForPeople or $OnlySearchForGroup or $ARGS{'AddMoreAttach'} ) { $Ticket->Atomic(sub { push @results, ProcessTicketWatchers( TicketObj => $Ticket, ARGSRef => \%ARGS); push @results, ProcessObjectCustomFieldUpdates( Object => $Ticket, ARGSRef => \%ARGS); push @results, ProcessTicketDates( TicketObj => $Ticket, ARGSRef => \%ARGS); push @results, ProcessUpdateMessage( TicketObj => $Ticket, ARGSRef=>\%ARGS ); push @results, ProcessTicketBasics( TicketObj => $Ticket, ARGSRef => \%ARGS ); push @results, ProcessTicketLinks( TicketObj => $Ticket, ARGSRef => \%ARGS); }); MaybeRedirectForResults( Actions => \@results, Path => "/Ticket/ModifyAll.html", Arguments => { id => $Ticket->id }, ); } # If they've gone and moved the ticket to somewhere they can't see, etc... unless ($Ticket->CurrentUserHasRight('ShowTicket')) { if (@results) { Abort("A change was applied successfully, but you no longer have permissions to view the ticket", Actions => \@results); } else { Abort("No permission to view ticket"); } } $Ticket->CurrentUser->AddRecentlyViewedTicket($Ticket); <%ARGS> $OnlySearchForPeople => undef $OnlySearchForGroup => undef $UserField => undef $UserOp => undef $UserString => undef $GroupString => undef $GroupOp => undef $GroupField => undef $id => undef rt-5.0.1/share/html/Ticket/ModifyLinks.html000644 000765 000024 00000007600 14005011336 021413 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => loc("Link ticket #[_1]: [_2]", $Ticket->Id, $Ticket->Subject) &> <& /Elements/Tabs &> % $m->callback(CallbackName => 'BeforeActionList', Actions => \@results, ARGSRef => \%ARGS, Ticket => $Ticket); <& /Elements/ListActions, actions => \@results &>
      % $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS ); % my $alt = loc('Graph ticket links'); % my (@extra); % push @extra, titleright_raw => '' unless RT->Config->Get('DisableGraphViz'); <&| /Widgets/TitleBox, title => loc('Edit Links'), class=>'ticket-info-links', @extra &> <& /Elements/EditLinks, Object => $Ticket &> <&| /Widgets/TitleBox, title => loc('Merge'), class=>'ticket-info-merge' &> <& Elements/EditMerge, Ticket => $Ticket, %ARGS &>
      <& /Elements/Submit, Name => 'SubmitTicket', Label => loc('Save Changes') &>
      % $m->callback(CallbackName => 'AfterForm', ARGSRef => \%ARGS, Ticket => $Ticket); <%INIT> my $Ticket = LoadTicket($id); my @results; $Ticket->Atomic(sub{ $m->callback( TicketObj => $Ticket, ARGSRef => \%ARGS, Results => \@results ); push @results, ProcessTicketLinks( TicketObj => $Ticket, ARGSRef => \%ARGS ); push @results, ProcessObjectCustomFieldUpdates( Object => $Ticket, ARGSRef => \%ARGS ); }); MaybeRedirectForResults( Actions => \@results, Arguments => { id => $id }, ); $Ticket->CurrentUser->AddRecentlyViewedTicket($Ticket); <%ARGS> $id => undef rt-5.0.1/share/html/Ticket/Elements/000755 000765 000024 00000000000 14005011336 020046 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Ticket/History.html000644 000765 000024 00000005465 14005011336 020633 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => loc("Ticket History #[_1]: [_2]", $Ticket->Id, $Ticket->Subject) &> <& /Elements/Tabs &> % $m->callback( %ARGS, Ticket => $Ticket, CallbackName => 'BeforeActionList' ); <& /Elements/ShowHistory, Object => $Ticket, ShowHeaders => $ARGS{'ShowHeaders'}, Attachments => $attachments, AttachmentContent => $attachment_content, DisplayPath => 'History.html', &> % $m->callback( %ARGS, CallbackName => 'AfterShowHistory', Ticket => $Ticket ); <%ARGS> $id => undef <%INIT> my $Ticket = LoadTicket ($id); unless ($Ticket->CurrentUserHasRight('ShowTicket')) { Abort("No permission to view ticket"); } $Ticket->CurrentUser->AddRecentlyViewedTicket($Ticket); my $attachments = $Ticket->Attachments; my $attachment_content = $Ticket->TextAttachments; rt-5.0.1/share/html/Ticket/Forward.html000644 000765 000024 00000015404 14005011336 020570 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => $Title &> <& /Elements/Tabs &> % $m->callback(CallbackName => 'BeforeActionList', Actions => \@results, ARGSRef => \%ARGS, Ticket => $TicketObj); <& /Elements/ListActions, actions => \@results &>
      % $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS ); <& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget &> <&|/Widgets/TitleBox, title => loc('Message'), class => 'messagedetails' &>
      <&|/l&>From:
      <% $from %>
      <&|/l&>Subject:
      <&|/l&>To:
      <& /Elements/EmailInput, Name => "To", AutocompleteMultiple => 1, Default => $ARGS{'To'} &>
      <&|/l&>Cc:
      <& /Elements/EmailInput, Name => "Cc", AutocompleteMultiple => 1, Default => $ARGS{'Cc'} &>
      <&|/l&>Bcc:
      <& /Elements/EmailInput, Name => "Bcc", AutocompleteMultiple => 1, Default => $ARGS{'Bcc'} &>
      % if ( $gnupg_widget ) {
       
      <& /Elements/Crypt/SignEncryptWidget, self => $gnupg_widget, TicketObj => $TicketObj, &>
      % }
      % if (exists $ARGS{Content}) { <& /Elements/MessageBox, Default => $ARGS{Content}, IncludeSignature => 0, SuppressAttachmentWarning => 1 &> % } else { <& /Elements/MessageBox, SuppressAttachmentWarning => 1 &> %}
      % $m->callback(CallbackName => 'AfterMessageBox', ARGSRef => \%ARGS, TicketObj => $TicketObj); <& /Ticket/Elements/ShowAttachments, Ticket => $TicketObj, Attachments => $attachments, Count => RT->Config->Get('AttachmentListCount') &>
      <& /Elements/Submit, Label => loc('Forward Message and Return'), Name => 'ForwardAndReturn' &> <& /Elements/Submit, Label => loc('Forward Message'), Name => 'Forward' &>
      <%INIT> my ($status, $msg); my $checks_failure = 0; my $TicketObj = LoadTicket($id); $id = $ARGS{'id'} = $TicketObj->id; $m->callback(CallbackName => 'Initial', ARGSRef => \%ARGS, TicketObj => $TicketObj); my $gnupg_widget = $m->comp('/Elements/Crypt/SignEncryptWidget:new', Arguments => \%ARGS ); $m->comp( '/Elements/Crypt/SignEncryptWidget:Process', self => $gnupg_widget, TicketObj => $TicketObj, ); Abort( loc("Permission Denied") ) unless $TicketObj->CurrentUserHasRight('ForwardMessage'); my $txn; if ( $QuoteTransaction ) { $txn = RT::Transaction->new( $session{'CurrentUser'} ); $txn->Load( $QuoteTransaction ); Abort( loc("Couldn't load transaction #[_1]", $QuoteTransaction) ) unless $txn->id; } if ( $Forward || $ForwardAndReturn ) { $status = $m->comp('/Elements/Crypt/SignEncryptWidget:Check', self => $gnupg_widget, TicketObj => $TicketObj, Operation => 'Forward', ); $checks_failure = 1 unless $status; } my @results; if ( !$checks_failure && ($Forward || $ForwardAndReturn) ) { ( $status, $msg ) = $TicketObj->Forward( Transaction => $txn, %ARGS ); push @results, $msg; if ( $ForwardAndReturn ) { $session{'i'}++; my $key = Digest::MD5::md5_hex(rand(1024)); push @{ $session{"Actions"}->{$key} ||= [] }, @results; RT::Interface::Web::Redirect( RT->Config->Get('WebURL') ."Ticket/Display.html?id=". $id."&results=".$key); } } my $Title = $txn ? loc('Forward transaction #[_1]: [_2]', $txn->id, $TicketObj->Subject) : loc('Forward ticket #[_1]: [_2]', $TicketObj->id, $TicketObj->Subject); my $from = RT::Interface::Email::GetForwardFrom( $txn ? ( Transaction => $txn ) : ( Ticket => $TicketObj ) ); my $subject = "Fwd: ".($txn || $TicketObj)->Subject; my $attachments = RT::Interface::Email::GetForwardAttachments( Ticket => $TicketObj, $txn ? ( Transaction => $txn ) : (), ); <%ARGS> $id => undef $QuoteTransaction => undef $ForwardAndReturn => 0, $Forward => $ForwardAndReturn, rt-5.0.1/share/html/Ticket/Reminders.html000644 000765 000024 00000006031 14005011336 021110 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => loc("Reminders for ticket #[_1]: [_2]", $Ticket->Id, $Ticket->Subject) &> <& /Elements/Tabs &> % $m->callback(CallbackName => 'BeforeActionList', ARGSRef => \%ARGS, Ticket => $Ticket); <& /Elements/ListActions, actions => \@actions &>
      <&|/Widgets/TitleBox, title => loc("Reminders"), class=>'ticket-info-reminders' &>
      <& /Ticket/Elements/Reminders, Ticket => $Ticket, ShowCompleted => 1, Edit => 1, ShowSave => 0 &>
      <& /Elements/Submit, Label => loc('Save Changes') &>
      <%INIT> my $Ticket = LoadTicket($id); my @actions = $Ticket->Atomic(sub{ ProcessTicketReminders( TicketObj => $Ticket, ARGSRef => \%ARGS ); }); $Ticket->CurrentUser->AddRecentlyViewedTicket($Ticket); <%ARGS> $id => undef rt-5.0.1/share/html/Ticket/Graphs/000755 000765 000024 00000000000 14005011336 017516 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Ticket/ModifyDates.html000644 000765 000024 00000006513 14005011336 021375 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => loc('Modify dates for #[_1]: [_2]', $TicketObj->Id, $TicketObj->Subject) &> <& /Elements/Tabs &> % $m->callback(CallbackName => 'BeforeActionList', Actions => \@results, ARGSRef => \%ARGS, Ticket => $TicketObj); <& /Elements/ListActions, actions => \@results &>
      % $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS ); <&| /Widgets/TitleBox,title => loc('Modify dates for ticket #[_1]', $TicketObj->Id), class=> 'ticket-info-dates' &> <& Elements/EditDates, TicketObj => $TicketObj &>
      <& /Elements/Submit, Name => 'SubmitTicket', Label => loc('Save Changes') &>
      % $m->callback(CallbackName => 'AfterForm', ARGSRef => \%ARGS, Ticket => $TicketObj); <%INIT> my $TicketObj = LoadTicket($id); my @results; $m->callback( TicketObj => $TicketObj, ARGSRef => \%ARGS, results => \@results ); $TicketObj->Atomic(sub{ push @results, ProcessTicketDates( TicketObj => $TicketObj, ARGSRef => \%ARGS); push @results, ProcessObjectCustomFieldUpdates(Object => $TicketObj, ARGSRef => \%ARGS); }); $TicketObj->CurrentUser->AddRecentlyViewedTicket($TicketObj); <%ARGS> $id => undef rt-5.0.1/share/html/Ticket/Graphs/index.html000644 000765 000024 00000010324 14005011336 021513 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => $title &> <& /Elements/Tabs &> % $m->callback(CallbackName => 'BeforeActionList', Actions => \@results, ARGSRef => \%ARGS, Ticket => $ticket); <& /Elements/ListActions, actions => \@results &> <& Elements/ShowGraph, %ARGS, Ticket => $ticket &>
      % $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS ); <& Elements/EditGraphProperties, %ARGS, Ticket => $ticket &> <& /Search/Elements/EditSearches, %$saved_search, Title => loc('Manage saved graphs'), Type => 'Graph', SearchFields => \@save_arguments, CurrentSearch => { map { $_ => $ARGS{$_} } @save_arguments }, AllowCopy => 0, &>
      % $m->callback(CallbackName => 'AfterForm', ARGSRef => \%ARGS, Ticket => $ticket); <%ARGS> <%INIT> use RT::Graph::Tickets; my $id = $ARGS{'id'}; my $ticket = LoadTicket( $id ); $ARGS{'id'} = $id = $ticket->id; $m->callback( TicketObj => $ticket, ARGSRef => \%ARGS ); my @results; my @save_arguments = qw(id Direction LeadingLink ShowLinks MaxDepth FillUsing ShowLinkDescriptions); foreach my $level ( 0 .. 6 ) { push @save_arguments, "Level-". $level ."-Properties"; } my $saved_search = { Type => 'Graph' }; push @results, $m->comp( '/Search/Elements/EditSearches:Init', %ARGS, Query => \%ARGS, SavedSearch => $saved_search, SearchFields => \@save_arguments, ); $ARGS{'LeadingLink'} ||= 'Members'; if ( $ARGS{'ShowLinks'} && !ref $ARGS{'ShowLinks'} ) { $ARGS{'ShowLinks'} = [$ARGS{'ShowLinks'}]; } elsif ( !$ARGS{'ShowLinks'} ) { $ARGS{'ShowLinks'} = [ qw(MemberOf DependsOn RefersTo) ]; } $ARGS{'ShowLinks'} = [ grep $_ ne $ARGS{'LeadingLink'}, @{ $ARGS{'ShowLinks'} } ]; $ARGS{'MaxDepth'} = 3 unless defined $ARGS{'MaxDepth'} && length $ARGS{'MaxDepth'}; push @results, $m->comp( '/Search/Elements/EditSearches:Save', %ARGS, Query => \%ARGS, SavedSearch => $saved_search, SearchFields => \@save_arguments, ); my $title = loc( "Ticket #[_1] relationships graph", $id ); rt-5.0.1/share/html/Ticket/Graphs/dhandler000644 000765 000024 00000005144 14005011336 021226 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> my $arg = $m->dhandler_arg; my $id; if ( $arg =~ m{^(\d+)$}i ) { ($id) = ($1); } else { return $m->abort( 404 ); } my $ticket = RT::Ticket->new($session{'CurrentUser'} ); $ticket->Load( $id ); unless ( $ticket->id ) { $RT::Logger->error("Couldn't load ticket #$id"); return $m->abort( 404 ); } require RT::Graph::Tickets; my $graph = RT::Graph::Tickets->TicketLinks( %ARGS, Graph => undef, Ticket => $ticket, ); $r->content_type( 'image/png' ); $m->clear_buffer; my $png; use RT::Util 'safe_run_child'; safe_run_child { $graph->as_png(\$png) }; $m->out( $png ); $m->abort; rt-5.0.1/share/html/Ticket/Graphs/Elements/000755 000765 000024 00000000000 14005011336 021272 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Ticket/Graphs/Elements/ShowLegends000644 000765 000024 00000006107 14005011336 023443 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&| /Widgets/TitleBox, title => loc('Legends'), hideable => $hideable &>
      <% loc('Status') %>:
      % foreach my $status ( sort keys %RT::Graph::Tickets::ticket_status_style ) { % my $style = $RT::Graph::Tickets::ticket_status_style{ $status }; <% loc($status) %> % }
      % if ( $FillUsing ) {
      <% loc($FillUsing) %>:
      % foreach my $value ( sort keys %RT::Graph::Tickets::fill_cache ) { % my $color = $RT::Graph::Tickets::fill_cache{ $value }; <% loc($value) %> % }
      % } <%ARGS> $FillUsing => '' $hideable => 1 rt-5.0.1/share/html/Ticket/Graphs/Elements/EditGraphProperties000644 000765 000024 00000017200 14005011336 025141 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&| /Widgets/TitleBox, title => loc('Graph Properties') &>
      <% loc('Direction') %>
      <% loc('Main type of links') %>
      <% loc('maximum depth') %>
      <% loc('Show as well') %>:
      % foreach my $type ( @link_types ) { % my $checked = ''; % $checked = 'checked="checked"' if grep $type eq $_, @ShowLinks;
      />
      % }
      % my @properties = RT::Graph::Tickets->TicketProperties( $session{'CurrentUser'} );
      <% loc('Fill boxes with color using') %>:
      % if ( RT::Link->can('Description' ) ) { % my $checked = ''; % $checked = 'checked="checked"' if $ShowLinkDescriptions;
      />
      % } <%PERL> for my $i ( 1..($MaxDepth||6) ) { my @default; if ( my $tmp = $ARGS{ 'Level-'. $i .'-Properties' } ) { @default = ref $tmp? @$tmp : ($tmp); } $m->comp('SELF:Properties', Level => $i, Available => \@properties, Default => \@default, ); }
      <& /Elements/Submit, Label => loc('Update Graph'), Name => 'Update' &>
      <%ARGS> $id => undef $Direction => 'TB' $LeadingLink => 'Members' @ShowLinks => ('MemberOf', 'DependsOn', 'RefersTo') $MaxDepth => 3 $FillUsing => '' $ShowLinkDescriptions => 0 <%INIT> require RT::Graph::Tickets; require RT::Link; my @link_types = qw(Members MemberOf RefersTo ReferredToBy DependsOn DependedOnBy); #loc_qw @ShowLinks = grep $_ ne $LeadingLink, @ShowLinks; <%METHOD Properties> <%ARGS> @Available => () @Default => () $Level => 1, <%INIT> my $id = "graph-properties-box-$Level"; my $class = ''; $class = 'class="hidden"' if $Level != 1 && !@Default;
      <% loc('Show Tickets Properties on [_1] level', $Level) %> (<% loc('open/close') %>):
      > % while ( my ($group, $list) = (splice @Available, 0, 2) ) {
      <% loc($group) %>:
      % foreach my $prop ( @$list ) { % my $checked = ''; % $checked = 'checked="checked"' if grep $_ eq $prop, @Default;
      />
      % }
      % }
      rt-5.0.1/share/html/Ticket/Graphs/Elements/ShowGraph000644 000765 000024 00000005306 14005011336 023123 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <% safe_run_child { Encode::decode( "UTF-8", $graph->as_cmapx ) } |n %>
      <& ShowLegends, %ARGS, Ticket => $ticket &> <%ARGS> $id => undef <%INIT> use RT::Util 'safe_run_child'; my $ticket = RT::Ticket->new( $session{'CurrentUser'} ); $ticket->Load( $id ); unless ( $ticket->id ) { $RT::Logger->error("Couldn't load ticket $id"); return; } $ARGS{'id'} = $id = $ticket->id; require RT::Graph::Tickets; my $graph = RT::Graph::Tickets->TicketLinks( %ARGS, Graph => undef, Ticket => $ticket, ); rt-5.0.1/share/html/Ticket/Elements/ShowDates000644 000765 000024 00000013161 14005011336 021674 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <&|/l&>Created:
      \
      <% $Ticket->CreatedObj->AsString %>
      % $m->callback( %ARGS, CallbackName => 'AfterCreated', TicketObj => $Ticket );
      <&|/l&>Starts:
      \
      <% $Ticket->StartsObj->AsString %>
      % $m->callback( %ARGS, CallbackName => 'AfterStarts', TicketObj => $Ticket );
      <&|/l&>Started:
      \
      <% $Ticket->StartedObj->AsString %>
      % $m->callback( %ARGS, CallbackName => 'AfterStarted', TicketObj => $Ticket );
      % if ( $Ticket->CurrentUserHasRight('ModifyTicket' ) ) { <&|/l&>Last Contact: % } else { <&|/l&>Last Contact: % }
      <% $Ticket->ToldObj->AsString %>
      % $m->callback( %ARGS, CallbackName => 'AfterTold', TicketObj => $Ticket );
      <&|/l&>Due:
      \ % my $due = $Ticket->DueObj; % if ( $due && $due->IsSet && $due->Diff < 0 && $Ticket->QueueObj->IsActiveStatus($Ticket->Status) ) {
      <% $due->AsString %>
      % } else {
      <% $due->AsString %>
      % }
      % $m->callback( %ARGS, CallbackName => 'AfterDue', TicketObj => $Ticket );
      <&|/l&>Closed:
      \
      <% $Ticket->ResolvedObj->AsString %>
      % $m->callback( %ARGS, CallbackName => 'AfterResolved', TicketObj => $Ticket );
      <&|/l&>Updated:
      \ % my $UpdatedString = $Ticket->LastUpdated ? loc("[_1] by [_2]", $Ticket->LastUpdatedAsString, $m->scomp('/Elements/ShowUser', User => $Ticket->LastUpdatedByObj)) : loc("Never"); % if ($UpdatedLink) { % } else {
      <% $UpdatedString | n %>
      % }
      % $m->callback( %ARGS, CallbackName => 'AfterUpdated', TicketObj => $Ticket ); <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Grouping => 'Dates', Table => 0 &> % $m->callback( %ARGS, CallbackName => 'EndOfList', TicketObj => $Ticket );
      <%ARGS> $Ticket => undef $UpdatedLink => 1 <%INIT> if ($UpdatedLink and $Ticket) { my $txns = $Ticket->Transactions; $txns->OrderByCols( { FIELD => "Created", ORDER => "DESC" }, { FIELD => "id", ORDER => "DESC" }, ); $txns->RowsPerPage(1); if (my $latest = $txns->First) { $UpdatedLink = "#txn-" . $latest->id; } else { undef $UpdatedLink; } } rt-5.0.1/share/html/Ticket/Elements/AddAttachments000644 000765 000024 00000027747 14005011336 022676 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % if ( $attachments ) {
      <&|/l&>Attached file:
      % foreach my $attach_name ( sort keys %$attachments ) {
      % $m->callback( ARGSRef => \%ARGS, CallbackName => 'BeforeDeleteLink', AttachmentName => $attach_name ); (<&|/l&>Delete)
      % } # end of foreach
      % } # end of if
      % if ($HasExisting) {
      <&|/l&>Include attachments:
      <& /Ticket/Elements/ShowAttachments, Ticket => $TicketObj, Selectable => 1, HideTitleBox => 1, Checked => \@AttachExisting, Count => RT->Config->Get('AttachmentListCount'), &>
      % } % $m->callback( %ARGS, CallbackName => 'End' ); <%ARGS> $Token => '' @AttachExisting => () $QuoteTransaction => '' $TicketObj => undef <%INIT> my $attachments; if ( exists $session{'Attachments'}{ $Token } && keys %{ $session{'Attachments'}{ $Token } } ) { $attachments = $session{'Attachments'}{ $Token }; } my $HasExisting = 0; if ($TicketObj && $TicketObj->id) { my $Existing = $TicketObj->Attachments; $Existing->LimitHasFilename; $HasExisting = 1 if $Existing->Count; } rt-5.0.1/share/html/Ticket/Elements/UpdateCc000644 000765 000024 00000017430 14005011336 021466 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $m->callback(CallbackName => 'BeforeCc', ARGSRef => \%ARGS, Ticket => $TicketObj, one_time_Ccs => \@one_time_Ccs, txn_addresses => \%txn_addresses);
      <&|/l&>One-time Cc:
      <& /Elements/EmailInput, Name => 'UpdateCc', Size => undef, Default => $ARGS{UpdateCc}, AutocompleteMultiple => 1, Options => \@one_time_Ccs &>
      %if (scalar @one_time_Ccs) { % if ($hide_cc_suggestions) { (<&|/l&>show suggestions) %}
      <&|/l&>One-time Bcc:
      <& /Elements/EmailInput, Name => 'UpdateBcc', Size => undef, Default => $ARGS{UpdateBcc}, AutocompleteMultiple => 1, Options => \@one_time_Ccs &>
      %if (scalar @one_time_Ccs) { % if ($hide_cc_suggestions) { (<&|/l&>show suggestions) %}
      <%args> $TicketObj <%init> my %txn_addresses = %{$TicketObj->TransactionAddresses}; # Get people already added as watchers on the ticket so we can filter # them out of the one-time list my @people_addresses = Email::Address->parse( $TicketObj->RequestorAddresses ); push @people_addresses, Email::Address->parse( $TicketObj->CcAddresses ); push @people_addresses, Email::Address->parse( $TicketObj->AdminCcAddresses ); my @one_time_Ccs; foreach my $addr ( keys %txn_addresses) { next if ( grep {$addr eq lc $_->address} @people_addresses ); push @one_time_Ccs,$addr; } @one_time_Ccs = sort @one_time_Ccs; my $hide_cc_suggestions = RT->Config->Get('HideOneTimeSuggestions', $session{CurrentUser}); my $show_label = $m->interp->apply_escapes( loc("show suggestions"), 'h' ); my $hide_label = $m->interp->apply_escapes( loc("hide suggestions"), 'h' ); rt-5.0.1/share/html/Ticket/Elements/EditPeople000644 000765 000024 00000015557 14005011336 022040 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}

      <&|/l&>New watchers

      <&|/l&>Find people whose
      <& /Elements/SelectUsers &>

      <&|/l&>Find groups whose
      <& /Elements/SelectGroups &>
      <& AddWatchers, Ticket => $Ticket, UserString => $UserString, UserOp => $UserOp, UserField => $UserField, GroupString => $GroupString, GroupOp => $GroupOp, GroupField => $GroupField, PrivilegedOnly => $PrivilegedOnly &>

      <&|/l&>People

      <&|/l&>Owner:
      <& /Elements/SelectOwner, Name => 'Owner', QueueObj => $Ticket->QueueObj, TicketObj => $Ticket, Default => $Ticket->OwnerObj->Id, DefaultValue => 0&>
      % my @role_fields; % my $single_roles = $Ticket->QueueObj->CustomRoles; % $single_roles->LimitToSingleValue; % my @hidden = $Ticket->QueueObj->HiddenCustomRoleIDsForURL; % $single_roles->Limit(FIELD => 'id', OPERATOR => 'NOT IN', VALUE => \@hidden) if @hidden; % $m->callback( CustomRoles => $single_roles, SingleRoles => 1, Ticket => $Ticket, %ARGS, CallbackName => 'ModifyCustomRoles' ); % while (my $role = $single_roles->Next) {
      <% $role->Name %>: % if ( my $hint = $role->EntryHint ) { % }
      <& /Elements/SingleUserRoleInput, role => $role, Ticket => $Ticket &>
      % }

      <&|/l&>Current watchers

      <&|/l&>(Check box to delete)
      <& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->Requestors &>
      <& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->Cc &>
      <& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->AdminCc &>
      % my $multi_roles = $Ticket->QueueObj->CustomRoles; % $multi_roles->LimitToMultipleValue; % $multi_roles->Limit(FIELD => 'id', OPERATOR => 'NOT IN', VALUE => \@hidden) if @hidden; % $m->callback( CustomRoles => $multi_roles, SingleRoles => 0, Ticket => $Ticket, %ARGS, CallbackName => 'ModifyCustomRoles' ); % while (my $role = $multi_roles->Next) { % my $group = $Ticket->RoleGroup($role->GroupType);
      <& EditWatchers, TicketObj => $Ticket, Watchers => $group &>
      % } <& /Elements/EditCustomFields, Object => $Ticket, Grouping => 'People', InTable => 1 &>
      <%ARGS> $UserField => undef $UserOp => undef $UserString => undef $GroupField => undef $GroupOp => undef $GroupString => undef $PrivilegedOnly => undef $Ticket => undef rt-5.0.1/share/html/Ticket/Elements/ShowAssetsOnCreate000644 000765 000024 00000010662 14005011336 023522 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $QueueObj $ARGSRef <%init> my @linked_assets; foreach my $key ( map {+("new-$_", "$_-new")} keys %RT::Link::DIRMAP ) { next unless $ARGSRef->{$key}; for my $linktext (grep $_, split ' ', $ARGSRef->{$key}) { my $uri = RT::URI->new( $session{'CurrentUser'} ); next unless $uri->FromURI( $linktext ); next unless $uri->IsLocal and $uri->Object and $uri->Object->id and $uri->Object->isa("RT::Asset"); push @linked_assets, $uri->Object->id; } } my $asset_queue; if (RT->Config->Get('AssetQueues')) { $asset_queue = 1 if grep {$_ eq $QueueObj->Name} @{RT->Config->Get('AssetQueues')} } else { $asset_queue = 0; } return unless @linked_assets or $asset_queue; my $assets = RT::Assets->new( $session{CurrentUser} ); $assets->OrderBy( FIELD => "Name", ORDER => "ASC" ); if ( @linked_assets ) { $assets->Limit( FIELD => "id", OPERATOR => "IN", VALUE => \@linked_assets, ); } my $Format = RT->Config->Get("AssetSummaryFormat") || q[ '__Name__/TITLE:Name', Description, Status, Catalog, ]; $m->callback( CallbackName => 'ModifyCollection', Queue => $QueueObj, Assets => $assets, Format => \$Format, ); <&| /Widgets/TitleBox, title => loc('Assets'), class => 'ticket-assets', title_class => "inverse", &> % $m->callback( CallbackName => "Start", Queue => $QueueObj, Assets => $assets );
      % while (my $asset = $assets->Next) {
      <& /Elements/ShowRecord, Object => $asset, Format => $Format, TrustFormat => 1, &> % $m->callback( CallbackName => "PerAsset", Queue => $QueueObj, Asset => $asset );
      % }
      % $m->callback( CallbackName => "End", Queue => $QueueObj, Assets => $assets ); rt-5.0.1/share/html/Ticket/Elements/ShowRequestorTicketsInactive000644 000765 000024 00000004414 14005011336 025640 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& ShowRequestorTickets, %ARGS, Description => loc('inactive'), conditions => $conditions, Rows => $Rows &> <%INIT> unless ( @$conditions ) { push @$conditions, { cond => "Status = '__Inactive__'" }; } <%ARGS> $Requestor => undef $conditions => [] $Rows => 10 rt-5.0.1/share/html/Ticket/Elements/Bookmark000644 000765 000024 00000005767 14005011336 021555 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> my $ticket = RT::Ticket->new( $session{'CurrentUser'} ); $ticket->Load( $id ); my $is_bookmarked; if ($Toggle) { $is_bookmarked = $session{'CurrentUser'}->UserObj->ToggleBookmark($ticket); } else { $is_bookmarked = $session{'CurrentUser'}->UserObj->HasBookmark($ticket); } <%ARGS> $id $Toggle => 0 % my $url = RT->Config->Get('WebPath') ."/Helpers/Toggle/TicketBookmark?id=". $id; % if ( $is_bookmarked ) { % my $alt = loc('Remove Bookmark'); % } else { % my $alt = loc('Add Bookmark'); % } rt-5.0.1/share/html/Ticket/Elements/ShowCustomFields000644 000765 000024 00000004117 14005011336 023236 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/ShowCustomFields, %ARGS, Object => $Ticket &> <%ARGS> $Ticket => undef rt-5.0.1/share/html/Ticket/Elements/ShowAttachments000644 000765 000024 00000013305 14005011336 023107 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&| /Widgets/TitleBox, title => loc('Attachments'), title_class=> 'inverse', class => 'ticket-info-attachments', color => "#336699", hide_chrome => $HideTitleBox &> % $m->callback( %ARGS, CallbackName => 'BeforeList', TicketObj => $Ticket, Attachments => $Attachments, Documents => \%documents, IsChecked => \%is_checked, ShowMore => \$show_more );
      % foreach my $key (sort { lc($a) cmp lc($b) } keys %documents) { <%$key%> % } % $m->callback( %ARGS, CallbackName => 'AfterList', TicketObj => $Ticket, Attachments => $Attachments, Documents => \%documents, IsChecked => \%is_checked, ShowMore => \$show_more ); % if ($show_more) { % my %params = %ARGS; % delete $params{Ticket}; % delete $params{Attachments}; % delete $params{Count}; % my $query = $m->comp('/Elements/QueryString', %params, id => $Ticket->id ); % my $url = RT->Config->Get('WebPath')."/Helpers/TicketAttachments?$query"; <% loc('Show all') %> % }
      <%INIT> # If we haven't been passed in an Attachments object (through the precaching mechanism) # then we need to find one $Attachments ||= $Ticket->Attachments; # Avoid applying limits to this collection that may be used elsewhere # (e.g. transaction display) $Attachments = $Attachments->Clone; # Remember, each message in a transaction is an attachment; we only # want named attachments (real files) $Attachments->LimitHasFilename; my $show_more = 0; my %documents; # show newest first $Attachments->OrderByCols( { FIELD => 'Created', ORDER => 'DESC' }, { FIELD => 'id', ORDER => 'DESC' }, ); while ( my $attach = $Attachments->Next() ) { # display "show more" only when there will be more attachments if (defined($Count) && --$Count < 0) { $show_more = 1; last; } push @{ $documents{ $attach->Filename } }, $attach; } my %is_checked = map { $_ => 1 } @Checked; return if !$show_more && keys %documents == 0; <%ARGS> $Ticket => undef $Attachments => undef $DisplayPath => $session{'CurrentUser'}->Privileged ? 'Ticket' : 'SelfService' $HideTitleBox => 0 $Selectable => 0 $Count => undef @Checked => () rt-5.0.1/share/html/Ticket/Elements/ShowAssets000644 000765 000024 00000017670 14005011336 022107 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $Ticket $ShowRelatedTickets => 10 <%init> my $target_assets = $Ticket->Links("Base")->Clone; $target_assets->Limit( FIELD => "Target", OPERATOR => "STARTSWITH", VALUE => RT::URI::asset->LocalURIPrefix, ); my $base_assets = $Ticket->Links("Target")->Clone; $base_assets->Limit( FIELD => "Base", OPERATOR => "STARTSWITH", VALUE => RT::URI::asset->LocalURIPrefix, ); my @linked_assets; push @linked_assets, grep { defined } map { $_->TargetURI->IsLocal } @{ $target_assets->ItemsArrayRef }; push @linked_assets, grep { defined } map { $_->BaseURI->IsLocal } @{ $base_assets->ItemsArrayRef }; my $asset_queue; if (RT->Config->Get('AssetQueues')) { $asset_queue = 1 if grep {$_ eq $Ticket->QueueObj->Name} @{RT->Config->Get('AssetQueues')} } else { $asset_queue = 0; } return unless @linked_assets or ($Ticket->CurrentUserHasRight("ModifyTicket") and $asset_queue); my $assets = RT::Assets->new( $session{CurrentUser} ); $assets->OrderBy( FIELD => "Name", ORDER => "ASC" ); if ( @linked_assets ) { $assets->Limit( FIELD => "id", OPERATOR => "IN", VALUE => \@linked_assets, ); } my $Format = RT->Config->Get("AssetSummaryFormat") || q[ '__Name__/TITLE:Name', Description, Status, Catalog, ]; $m->callback( CallbackName => 'ModifyCollection', Ticket => $Ticket, Assets => $assets, Format => \$Format, ); <&| /Widgets/TitleBox, title => loc('Assets'), class => 'ticket-assets', title_class => "inverse", &>
      /Ticket/Display.html" method="POST" enctype="multipart/form-data"> % $m->callback( CallbackName => "Start", Ticket => $Ticket, Assets => $assets );
      % my $display_path = $session{'CurrentUser'}->Privileged ? 'Asset' : 'SelfService/Asset'; % while (my $asset = $assets->Next) {
      <& /Elements/ShowRecord, Object => $asset, Format => $Format, TrustFormat => 1, &> % $m->callback( CallbackName => "BeforeTickets", Ticket => $Ticket, Asset => $asset ); <%perl> if ($ShowRelatedTickets) { my %search = ( Query => "id != '@{[$Ticket->id]}' AND LinkedTo = 'asset:@{[$asset->id]}'", OrderBy => "LastUpdated", Order => "DESC", ); my $url = RT->Config->Get("WebPath") . "/Search/Results.html?" . $m->comp("/Elements/QueryString", %search); % } % $m->callback( CallbackName => "PerAsset", Ticket => $Ticket, Asset => $asset );
      % }
      % if ($Ticket->CurrentUserHasRight("ModifyTicket")) {
      % } % $m->callback( CallbackName => "End", Ticket => $Ticket, Assets => $assets );
      rt-5.0.1/share/html/Ticket/Elements/AddWatchers000644 000765 000024 00000012172 14005011336 022165 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % if ($ShowLabel) {

      <&|/l&>Add new watchers:
      % } % if ($Users and $Users->Count) {
      <&|/l&>Type
      <&|/l&>Username
      % while (my $u = $Users->Next ) {
      <&/Elements/SelectWatcherType, Name => "Ticket-AddWatcher-Principal-". $u->PrincipalId, Queue => $Ticket->QueueObj, &>
      <& '/Elements/ShowUser', User => $u, style=>'verbose' &>
      % } % } % if ($Groups and $Groups->Count) {
      <&|/l&>Type
      <&|/l&>Group
      % while (my $g = $Groups->Next ) {
      <& /Elements/SelectWatcherType, Name => "Ticket-AddWatcher-Principal-".$g->PrincipalId, Queue => $Ticket->QueueObj, &>
      <%$g->Name%> (<%$g->Description%>)
      % } % }
      <&|/l&>Type
      <&|/l&>Email
      % my $counter = 4; % for my $email (@extras) { % $counter++;
      <&/Elements/SelectWatcherType, Name => "WatcherTypeEmail".$counter, Queue => $Ticket->QueueObj &>
      <%$email->format%>
      % } % for my $i (1 .. 3) {
      <&/Elements/SelectWatcherType, Name => "WatcherTypeEmail" . $i, Queue => $Ticket->QueueObj &>
      <& /Elements/EmailInput, Name => 'WatcherAddressEmail' . $i, Size => '20' &>
      % } <%INIT> my ($Users, $Groups); if ($UserString) { $Users = RT::Users->new($session{'CurrentUser'}); $Users->Limit(FIELD => $UserField, VALUE => $UserString, OPERATOR => $UserOp, CASESENSITIVE => 0); $Users->LimitToPrivileged if $PrivilegedOnly; } if ($GroupString) { $Groups = RT::Groups->new($session{'CurrentUser'}); $Groups->LimitToUserDefinedGroups; $Groups->Limit(FIELD => $GroupField, VALUE => $GroupString, OPERATOR => $GroupOp, CASESENSITIVE => 0); } my @extras; for my $addr ( values %{$Ticket->TransactionAddresses} ) { my $is_watcher; for my $type ( qw/Owner Requestor Cc AdminCc/ ) { if ($Ticket->IsWatcher( Email => $addr->address, Type => $type )) { $is_watcher = 1; last; } } push @extras, $addr unless $is_watcher; } <%ARGS> $ShowLabel => 1 $UserField => 'Name' $UserOp => '=' $UserString => undef $GroupField => 'Name' $GroupOp => '=' $GroupString => undef $PrivilegedOnly => undef $Ticket => undef rt-5.0.1/share/html/Ticket/Elements/PopupTimerLink000644 000765 000024 00000004564 14005011336 022724 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $id <%INIT> my $url = RT->Config->Get('WebPath') . "/Helpers/TicketTimer?id=" . $id; my $alt = loc('Open Timer'); rt-5.0.1/share/html/Ticket/Elements/ShowRequestorTicketsActive000644 000765 000024 00000004411 14005011336 025306 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& ShowRequestorTickets, %ARGS, Description => loc('active'), conditions => $conditions, Rows => $Rows &> <%INIT> unless ( @$conditions ) { push @$conditions, { cond => "Status = '__Active__'" }; } <%ARGS> $Requestor => undef $conditions => [] $Rows => 10 rt-5.0.1/share/html/Ticket/Elements/ShowRequestor000644 000765 000024 00000020137 14005011336 022626 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % if ($ShowTickets) { % } <&| /Widgets/TitleBox, title_raw => loc("More about the requestors"), class => 'ticket-info-requestor fullwidth' &>
      % while ( my $requestor = $people->Next ) {
      %# Additional information about this user. Empty by default. % $m->callback( requestor => $requestor, %ARGS, CallbackName => 'AboutThisUser' ); <& ShowRequestorExtraInfo, Requestor => $requestor &> % if ( $ShowComments ) {
      <&|/l&>Comments about this user:
      <% ($requestor->Comments || loc("No comment entered about this user")) %>
      % } % $m->callback( requestor => $requestor, %ARGS, CallbackName => 'AfterComments' ); % if ( $ShowTickets ) {
      % $index = 1;
      % for my $status (@$status_order) { % if ( $status eq $DefaultTicketsTab ) {
      <& $TicketTemplate, Requestor => $requestor &> % } else {
      % $index++;
      <&|/l&>Loading...
      % }
      % }
      % } % my $grouplimit = RT->Config->Get('MoreAboutRequestorGroupsLimit'); % if ( $ShowGroups and defined $grouplimit ) {
      <&|/l&>Groups this user belongs to % if ( $session{CurrentUser}->HasRight( Right => 'AdminUsers', Object => $RT::System ) && % $session{CurrentUser}->HasRight( Right => 'ShowConfigTab', Object =>$RT::System ) ) { [<&|/l&>Edit] % }
      <& /Elements/ShowMemberships, UserObj => $requestor, Limit => $grouplimit &>
      % } %# end of individual requestor details
      % } %# end of requestors loop % $m->callback( %ARGS, CallbackName => 'AfterRequestors' );
      <%INIT> my $show_privileged = RT->Config->Get('ShowMoreAboutPrivilegedUsers'); my $people = $Ticket->Requestors->UserMembersObj; $people->LimitToUnprivileged unless $show_privileged; my $count = $people->Count; return unless $count; my $has_right_adminusers = $session{'CurrentUser'}->HasRight( Object => $RT::System, Right => 'AdminUsers' ); $has_right_adminusers &&= $session{'CurrentUser'}->HasRight( Object => $RT::System, Right => 'ShowConfigTab' ); # Ticket list tabs my $selected = -1; $DefaultTicketsTab ||= RT->Config->Get('MoreAboutRequestorTicketList', $session{CurrentUser}) || 'Active'; my $status_link_text = {Active => loc('Active Tickets'), Inactive => loc('Inactive Tickets'), All => loc('All Tickets')}; my $status_order = [qw/Active Inactive All/]; $m->callback( CallbackName => 'AddStatus', status_link_text => \$status_link_text, status_order => \$status_order ); $ShowTickets = 0 if $DefaultTicketsTab eq 'None'; my $TicketTemplate; if ($ShowTickets) { for (0 .. (@$status_order - 1)) { if ( $status_order->[$_] eq $DefaultTicketsTab ) { $selected = $_; last; } } $TicketTemplate = "ShowRequestorTickets$DefaultTicketsTab"; $TicketTemplate = "ShowRequestorTicketsActive" unless RT::Interface::Web->ComponentPathIsSafe($TicketTemplate) and $m->comp_exists($TicketTemplate); } <%ARGS> $Ticket=>undef $DefaultTicketsTab => undef $ShowComments => 1 $ShowTickets => 1 $ShowGroups => 1 $Title => 'More about [_1]' rt-5.0.1/share/html/Ticket/Elements/ShowPeople000644 000765 000024 00000011606 14005011336 022062 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <&|/l&>Owner:
      % my $owner = $Ticket->OwnerObj;
      <& /Elements/ShowUser, User => $owner, Ticket => $Ticket &> <& /Elements/ShowUserEmailFrequency, User => $owner, Ticket => $Ticket &> % $m->callback( User => $owner, Ticket => $Ticket, %ARGS, CallbackName => 'AboutThisUser' );
      % my $single_roles = $Ticket->QueueObj->CustomRoles; % $single_roles->LimitToSingleValue; % my @hidden = $Ticket->QueueObj->HiddenCustomRoleIDsForURL; % $single_roles->Limit(FIELD => 'id', OPERATOR => 'NOT IN', VALUE => \@hidden) if @hidden; % $m->callback( CustomRoles => $single_roles, SingleRoles => 1, Ticket => $Ticket, %ARGS, CallbackName => 'ModifyCustomRoles' ); % while (my $role = $single_roles->Next) { % my $group = $Ticket->RoleGroup($role->GroupType); % my $users = $group->UserMembersObj( Recursively => 0 ); % $users->{find_disabled_rows} = 1; %# $users can be empty for tickets created before the custom role is added to the queue, %# so fall back to nobody % my $user = $users->First || RT->Nobody;
      <% $role->Name %>:
      <& /Elements/ShowUser, User => $user, Ticket => $Ticket &>
      % }
      <&|/l&>Requestors:
      <& ShowGroupMembers, Group => $Ticket->Requestors, Ticket => $Ticket &>
      <&|/l&>Cc:
      <& ShowGroupMembers, Group => $Ticket->Cc, Ticket => $Ticket &>
      <&|/l&>AdminCc:
      <& ShowGroupMembers, Group => $Ticket->AdminCc, Ticket => $Ticket &>
      % my $multi_roles = $Ticket->QueueObj->CustomRoles; % $multi_roles->LimitToMultipleValue; % $multi_roles->Limit(FIELD => 'id', OPERATOR => 'NOT IN', VALUE => \@hidden) if @hidden; % $m->callback( CustomRoles => $multi_roles, SingleRoles => 0, Ticket => $Ticket, %ARGS, CallbackName => 'ModifyCustomRoles' ); % while (my $role = $multi_roles->Next) {
      <% $role->Name %>:
      <& ShowGroupMembers, Group => $Ticket->RoleGroup($role->GroupType), Ticket => $Ticket &>
      % } <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Grouping => 'People', Table => 0 &>
      <%ARGS> $Ticket => undef rt-5.0.1/share/html/Ticket/Elements/EditDates000644 000765 000024 00000011500 14005011336 021634 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <&|/l&>Starts:
      <& /Elements/SelectDate, menu_prefix => 'Starts', current => 0, Default => $TicketObj->StartsObj->Unix ? $TicketObj->StartsObj->ISO(Timezone => 'user') : '', Object => $TicketObj, ARGSRef => \%ARGS, &>
      (<% $TicketObj->StartsObj->AsString %>)
      <&|/l&>Started:
      <& /Elements/SelectDate, menu_prefix => 'Started', current => 0, Default => $TicketObj->StartedObj->Unix ? $TicketObj->StartedObj->ISO(Timezone => 'user') : '', Object => $TicketObj, ARGSRef => \%ARGS, &>
      (<%$TicketObj->StartedObj->AsString %>)
      <&|/l&>Last Contact:
      <& /Elements/SelectDate, menu_prefix => 'Told', current => 0, Default => $TicketObj->ToldObj->Unix ? $TicketObj->ToldObj->ISO(Timezone => 'user') : '', Object => $TicketObj, ARGSRef => \%ARGS, &>
      (<% $TicketObj->ToldObj->AsString %>)
      <&|/l&>Due:
      <& /Elements/SelectDate, menu_prefix => 'Due', current => 0, Default => $TicketObj->DueObj->Unix ? $TicketObj->DueObj->ISO(Timezone => 'user') : '', Object => $TicketObj, ARGSRef => \%ARGS, &>
      (<% $TicketObj->DueObj->AsString %>)
      <& /Elements/EditCustomFields, Object => $TicketObj, Grouping => 'Dates', InTable => 1 &> % $m->callback( %ARGS, CallbackName => 'EndOfList', Ticket => $TicketObj );
      <%ARGS> $TicketObj => undef rt-5.0.1/share/html/Ticket/Elements/ShowGroupMembers000644 000765 000024 00000005040 14005011336 023240 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# Released under the terms of version 2 of the GNU Public License <%init> my $post_user = sub { my $user = shift; $m->comp("/Elements/ShowUserEmailFrequency", User => $user, Ticket => $Ticket); $m->callback( User => $user, Ticket => $Ticket, %ARGS, CallbackName => 'AboutThisUser', CallbackPage => '/Ticket/Elements/ShowGroupMembers' ); }; $m->comp("/Elements/ShowPrincipal", Object => $Group, Separator => "
      ", PostUser => $post_user, Link => $Link); <%ARGS> $Group => undef $Ticket => undef $Link => 1 rt-5.0.1/share/html/Ticket/Elements/ShowRequestorExtraInfo000644 000765 000024 00000004237 14005011336 024451 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /User/Elements/UserInfo, User => $Requestor, FormatConfig => 'MoreAboutRequestorExtraInfo', ClassPrefix => 'more-about-requestor' &> <%ARGS> $Requestor => undef rt-5.0.1/share/html/Ticket/Elements/SelectStatus000644 000765 000024 00000005624 14005011336 022423 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/SelectStatus, %ARGS, Statuses => \@Statuses, Object => $TicketObj || $QueueObj, Lifecycles => \@Lifecycles, Type => 'ticket', &> <%INIT> my @Lifecycles; for my $id (keys %Queues) { my $queue = RT::Queue->new($session{'CurrentUser'}); $queue->Load($id); push @Lifecycles, $queue->LifecycleObj if $queue->id; } if ($TicketObj) { $ARGS{DefaultLabel} = loc("[_1] (Unchanged)", loc($TicketObj->Status)); if ($DefaultFromArgs and $DECODED_ARGS->{Status}) { $ARGS{Default} = $DECODED_ARGS->{Status}; } elsif (defined $ARGS{Default}) { $ARGS{Default} = undef if $TicketObj->Status eq $ARGS{Default}; } } elsif ($QueueObj) { $ARGS{DefaultValue} = 0; $ARGS{Default} ||= $DECODED_ARGS->{Status} || $QueueObj->LifecycleObj->DefaultOnCreate; } <%ARGS> $DefaultFromArgs => 1, @Statuses => () $TicketObj => undef $QueueObj => undef %Queues => () rt-5.0.1/share/html/Ticket/Elements/ClickToShowHistory000644 000765 000024 00000005172 14005011336 023551 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <& /Widgets/TitleBoxStart, title => loc('History') &> <% loc('Show ticket history') %> <& /Widgets/TitleBoxEnd &>
      <%ARGS> $Ticket <%INIT> my %params = %ARGS; delete $params{Ticket}; my $query = $m->comp('/Elements/QueryString', %params, id => $Ticket->id ); my $url = RT->Config->Get('WebPath')."/Helpers/TicketHistory?$query"; my $display = RT->Config->Get('WebPath')."/Ticket/Display.html?ForceShowHistory=1;$query"; rt-5.0.1/share/html/Ticket/Elements/DelayShowHistory000644 000765 000024 00000005747 14005011336 023267 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <& /Widgets/TitleBoxStart, title => loc('History') &> <&|/l&>Loading... <& /Widgets/TitleBoxEnd &>
      <%ARGS> $Ticket <%INIT> my %params = %ARGS; delete $params{Ticket}; my $url = JSON( RT->Config->Get('WebPath') . "/Helpers/TicketHistory?". $m->comp('/Elements/QueryString', %params, id => $Ticket->id ) ); rt-5.0.1/share/html/Ticket/Elements/ShowTime000644 000765 000024 00000004353 14005011336 021535 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % if ($minutes < 60) { <&|/l, $minutes &>[quant,_1,minute,minutes] % } else { <&|/l, sprintf("%.2f",$minutes / 60), $minutes &>[quant,_1,hour,hours] ([quant,_2,minute,minutes]) % } <%init> $minutes ||= 0; <%ARGS> $minutes rt-5.0.1/share/html/Ticket/Elements/EditMerge000644 000765 000024 00000005676 14005011336 021654 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <&|/l&>Warning: merging is a non-reversible action! Enter a single ticket number to be merged into.
      <&|/l&>Merge into:
      <%INIT> my @excludes; if ( $Ticket ) { $Name ||= $Ticket->id . '-MergeInto'; @excludes = $Ticket->id; } elsif ( $Tickets ) { $Name ||= 'Ticket-MergeInto'; while ( my $ticket = $Tickets->Next ) { push @excludes, $ticket->id; } } $Default ||= $ARGS{$Name}; <%ARGS> $Ticket => undef $Tickets => undef $Name => '' $Default => '' $MergeTextClass => 'ticket-merge-position' $LabelStyle => 'col-2' $ValueStyle => 'col-4' rt-5.0.1/share/html/Ticket/Elements/EditPeopleInline000644 000765 000024 00000014422 14005011336 023165 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <&|/l&>Current watchers:
      <&|/l&>Owner:
      <& /Elements/SelectOwner, Name => 'Owner', QueueObj => $Ticket->QueueObj, TicketObj => $Ticket, Default => $Ticket->OwnerObj->Id, DefaultValue => 0&>
      % my @role_fields; % my $single_roles = $Ticket->QueueObj->CustomRoles; % $single_roles->LimitToSingleValue; % my @hidden = $Ticket->QueueObj->HiddenCustomRoleIDsForURL('/Ticket/ModifyPeople.html'); % $single_roles->Limit(FIELD => 'id', OPERATOR => 'NOT IN', VALUE => \@hidden) if @hidden; % $m->callback( CustomRoles => $single_roles, SingleRoles => 1, Ticket => $Ticket, %ARGS, CallbackName => 'ModifyCustomRoles', CallbackPage => '/Ticket/ModifyPeople.html' ); % while (my $role = $single_roles->Next) {
      <% $role->Name %>: % if ( my $hint = $role->EntryHint ) { % }
      <& /Elements/SingleUserRoleInput, role => $role, Ticket => $Ticket &>
      % } % if ($Ticket->Requestors->MembersObj->Count) {
      <& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->Requestors &>
      % } % if ($Ticket->Cc->MembersObj->Count) {
      <& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->Cc &>
      % } % if ($Ticket->AdminCc->MembersObj->Count) {
      <& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->AdminCc &>
      % } % my $multi_roles = $Ticket->QueueObj->CustomRoles; % $multi_roles->LimitToMultipleValue; % $multi_roles->Limit(FIELD => 'id', OPERATOR => 'NOT IN', VALUE => \@hidden) if @hidden; % $m->callback( CustomRoles => $multi_roles, SingleRoles => 0, Ticket => $Ticket, %ARGS, CallbackName => 'ModifyCustomRoles', CallbackPage => '/Ticket/ModifyPeople.html' ); % while (my $role = $multi_roles->Next) { % my $group = $Ticket->RoleGroup($role->GroupType); % if ($group->Id && $group->MembersObj->Count) {
      <& EditWatchers, TicketObj => $Ticket, Watchers => $group &>
      % } % }
      <&|/l&>Add new watchers:
      <& AddWatchers, Ticket => $Ticket, ShowLabel => 0 &> <& /Elements/EditCustomFields, Object => $Ticket, Grouping => 'People', InTable => 1 &> <%ARGS> $Ticket => undef rt-5.0.1/share/html/Ticket/Elements/ShowQueue000644 000765 000024 00000006207 14005011336 021723 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % if ( $Wrap ) { % } % if ( $query ) { <% $label %> % } else { <% $label %> % } % if ( $Wrap ) { % } <%ARGS> $Ticket => undef $QueueObj $Escape => 1 $Wrap => undef <%INIT> my $label = $QueueObj->Name; my $query; if ( $Ticket and $Ticket->CurrentUserHasRight('SeeQueue') ) { # Grab the queue name anyway if the current user can # see the queue based on his role for this ticket $label = $QueueObj->__Value('Name'); if ( $session{CurrentUser}->Privileged ) { my $queue_name_parameter = $label; $queue_name_parameter =~ s/(['\\])/\\$1/g; #' $query = "Queue = '$queue_name_parameter' AND Status = '__Active__'"; } } $label = '#'. $QueueObj->id unless defined $label && length $label; # Ticket create page uses this component to get queue name to generate page # title, which will be escaped in /Elements/Header later, so we don't need to # escape it here, otherwise the queue name will be wrongly escaped twice. if ( !$Escape ) { $m->out($label); return; } rt-5.0.1/share/html/Ticket/Elements/ShowPriority000644 000765 000024 00000005677 14005011336 022472 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % if ($use_numeric) { <% $Ticket->Priority %>/<% $Ticket->FinalPriority || ''%> % } else { % my $current = $Ticket->PriorityAsString || ''; % my $final = $Ticket->FinalPriorityAsString || ''; <% loc($current) %>/\ <% loc($final) %> % } <%ARGS> $Ticket => undef <%INIT> my $use_numeric = 1; if ( RT->Config->Get('EnablePriorityAsString') ) { my %config = RT->Config->Get('PriorityAsString'); my $queue_name = $Ticket->QueueObj->__Value('Name'); # Skip ACL check $use_numeric = 0 if exists $config{$queue_name} ? $config{$queue_name} : $config{Default}; } my $CSSClass = sub { my $value = shift; return '' unless defined $value; $value =~ s/[^A-Za-z0-9_-]/_/g; return $value; }; rt-5.0.1/share/html/Ticket/Elements/EditBasics000644 000765 000024 00000015260 14005011336 022007 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $TicketObj => undef $QueueObj => undef @fields => () $InTable => 0 %defaults => () $ExcludeCustomRoles => 0 $ExcludeOwner => 0 <%INIT> if ($TicketObj) { $QueueObj ||= $TicketObj->QueueObj; } unless ( @fields ) { my $subject = $defaults{'Subject'} || $TicketObj->Subject; @fields = ( { name => 'Subject', html => '', }, { name => 'Status', comp => '/Ticket/Elements/SelectStatus', args => { Name => 'Status', Default => $defaults{Status}, DefaultFromArgs => 0, TicketObj => $TicketObj, }, }, { name => 'Queue', comp => '/Elements/SelectQueue', args => { Name => 'Queue', Default => $defaults{'Queue'} || $QueueObj->Id, ShowNullOption => 0, } }, { name => 'Owner', comp => '/Elements/SelectOwner', args => { Name => 'Owner', QueueObj => $QueueObj, TicketObj => $TicketObj, Default => $defaults{'Owner'} || $TicketObj->OwnerObj->Id, DefaultValue => 0, } }, { special => 'roles' }, $QueueObj->SLADisabled ? () : ( { name => 'SLA', comp => '/Elements/SelectSLA', args => { Name => 'SLA', Default => $defaults{SLA}, DefaultFromArgs => 0, TicketObj => $TicketObj, }, }), # Time Estimated, Worked, and Left ( map { (my $field = $_) =~ s/ //g; { name => $_, comp => '/Elements/EditTimeValue', args => { Name => $field, Default => $defaults{$field} || $TicketObj->$field, } } } ('Time Estimated', 'Time Worked', 'Time Left') ), # Priority and Final Priority ( map { (my $field = $_) =~ s/ //g; { name => $_, comp => '/Elements/SelectPriority', args => { Name => $field, Default => $defaults{$field} || $TicketObj->$field, QueueObj => $TicketObj->QueueObj, } } } ('Priority', 'Final Priority') ), ); } my @role_fields; unless ($ExcludeCustomRoles) { my $roles = $QueueObj->CustomRoles; $roles->LimitToSingleValue; my @hidden = $QueueObj->HiddenCustomRoleIDsForURL; $roles->Limit(FIELD => 'id', OPERATOR => 'NOT IN', VALUE => \@hidden) if @hidden; $m->callback( CallbackName => 'ModifyCustomRoles', %ARGS, CustomRoles => $roles); while (my $role = $roles->Next) { push @role_fields, { name => $role->Name, hint => $role->EntryHint, comp => '/Elements/SingleUserRoleInput', args => { role => $role, Ticket => $TicketObj, Default => $defaults{$role->GroupType}, } }; } } if ($ExcludeOwner) { @fields = grep { ($_->{name}||'') ne 'Owner' } @fields; } # inflate the marker for custom roles into the field specs for each one @fields = map { ($_->{special}||'') eq 'roles' ? @role_fields : $_ } @fields; $m->callback( CallbackName => 'MassageFields', %ARGS, TicketObj => $TicketObj, Fields => \@fields ); # Process the field list, skipping if html is provided and running the # components otherwise for my $field (@fields) { next if defined $field->{'html'}; if ( $field->{'comp'} ) { $field->{'html'} = $m->scomp($field->{'comp'}, %{$field->{'args'} || {}}); } } % unless ($InTable) {
      % } % for my $field (@fields) { %# Prefer input name as css class, e.g. "FinalPriority" instead of "Final_Priority"
      <% loc($field->{'name'}) %>: % if ( my $hint = $field->{hint} ) { % }
      <% $field->{'html'} |n %>
      % } % $m->callback( CallbackName => 'EndOfList', TicketObj => $TicketObj, %ARGS, Fields => \@fields ); % unless ($InTable) {
      % } rt-5.0.1/share/html/Ticket/Elements/ShowUpdateStatus000644 000765 000024 00000006454 14005011336 023271 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <&| /Widgets/TitleBox, title => loc('New messages'), title_href => "#txn-". $txn->id &>
      <%ARGS> $Ticket $DisplayPath => $session{'CurrentUser'}->Privileged ? 'Ticket' : 'SelfService' <%INIT> return unless (RT->Config->Get( 'ShowUnreadMessageNotifications', $session{'CurrentUser'})); my $txn = $Ticket->SeenUpTo or return; rt-5.0.1/share/html/Ticket/Elements/ShowLinkedQueues000644 000765 000024 00000013454 14005011336 023237 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%PERL> foreach my $queues ( @{ $portlet_config{ $queue } } ) { foreach my $queue_name ( keys %{ $queues } ) { my $queue_obj = RT::Queue->new( $session{ CurrentUser } ); my ( $ret ) = $queue_obj->Load( $queue_name ); unless ( $ret ) { RT::Logger->error( "Couldn't load queue $queue_name" ); next; } my $link_types = $queues->{$queue_name}; my $query = "Queue = '$queue_name'"; my $ticket_id = $TicketObj->id; if ( grep { lc $_ eq 'all' } @$link_types ) { $query .= " AND ( LinkedTo = $ticket_id OR LinkedFrom = $ticket_id )"; } else { my @link_relations = map { $_ . " = $ticket_id" } @$link_types; my $link_query = join( ' OR ', @link_relations ); if ($link_query) { $query .= ' AND ( ' . $link_query . ' )'; } else { $query = 'id=0'; } } # create an identifiable class name for the linked queue portlet so # we can specifically target it apart from ticket-info-links. my $linked_queue_class = 'linked-queue'; my $query_string = $m->comp( '/Elements/QueryString', Query => $query ); my $title_href = RT->Config->Get( 'WebPath' ) . "/Search/Results.html?$query_string"; my $title_class = 'inverse'; my $class = 'ticket-info-links' . ' ' . $linked_queue_class; my $titleright_raw = ''; $m->callback( CallbackName => 'MassageTitleBox', ARGSRef => \%ARGS, title => \$queue_name, title_href => \$title_href, titleright_raw => \$titleright_raw, title_class => \$title_class, class => \$class, ); <&| /Widgets/TitleBox, title => $queue_name, title_href => $title_href, titleright_raw => $titleright_raw, title_class => $title_class, class => $class, content_class => 'linked-queue-portlet', &> <%PERL> my @queries = map { "$query AND $_" } q{Status = '__Active__'}, q{Status = '__Inactive__'}; my @empty_messages = ( loc( '(No active tickets)' ), loc( '(No inactive tickets)' ) ); $m->callback( CallbackName => 'MassageQueries', ARGSRef => \%ARGS, Queue => $queue_name, Queries => \@queries, EmptyMessages => \@empty_messages, ); my $format = ( exists RT->Config->Get('LinkedQueuePortletFormats')->{$queue_name} ? RT->Config->Get('LinkedQueuePortletFormats')->{$queue_name} : RT->Config->Get('LinkedQueuePortletFormats')->{'Default'} ); my $i = 0; for my $query ( @queries ) { $i++; my $empty_message = shift @empty_messages; my $order_by = $OrderBy; my $rows = $Rows; $m->callback( CallbackName => 'MassageSearchArgs', ARGSRef => \%ARGS, Queue => $queue_name, Query => $query, Format => \$format, OrderBy => \$order_by, Rows => \$rows, ); my $tickets = RT::Tickets->new($session{CurrentUser}); $tickets->FromSQL($query); if ( $tickets->Count ) { <& /Elements/CollectionList, %ARGS, %$DECODED_ARGS, Class => 'RT::Tickets', Query => $query, Format => $format, OrderBy => $order_by, Rows => $rows, ShowHeader => 0, Page => $DECODED_ARGS->{"Page-$queue_name-$i"} || 1, PageParam => "Page-$queue_name-$i", PassArguments => [ keys %$DECODED_ARGS ], &> % } else {
      <% $empty_message %>
      % } % } % } %} <%INIT> my %portlet_config = RT->Config->Get( 'LinkedQueuePortlets' ); return unless %portlet_config; my $queue = $TicketObj->QueueObj->Name; return unless $portlet_config{ $queue }; <%ARGS> $TicketObj $OrderBy => 'Due' $Rows => 8 rt-5.0.1/share/html/Ticket/Elements/ShowBasics000644 000765 000024 00000013430 14005011336 022037 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <&|/l&>Id:
      <%$Ticket->Id %>
      <&|/l&>Status:
      <% loc($Ticket->Status) %>
      % if ( !$Ticket->QueueObj->SLADisabled ) {
      <&|/l&>SLA:
      <% loc($Ticket->SLA) %>
      % } % if ($show_time_worked) { % if ($Ticket->TimeEstimated) {
      <&|/l&>Estimated:
      <& ShowTime, minutes => $Ticket->TimeEstimated &>
      % } % $m->callback( %ARGS, CallbackName => 'AfterTimeEstimated', TicketObj => $Ticket ); % if ($Ticket->TimeWorked) {
      <&|/l&>Worked:
      <& ShowTime, minutes => $Ticket->TimeWorked &>
      % } % my $totalTimeWorked = 0; % if (RT->Config->Get('DisplayTotalTimeWorked') && ($totalTimeWorked = $Ticket->TotalTimeWorked)) {
      <&|/l&>Total Time Worked:
      <& ShowTime, minutes => $totalTimeWorked &>
      % } % if ( keys %$time_worked ) {
      <&|/l&>Users:
      % for my $user ( keys %$time_worked ) {
      <% $user %>: <& /Ticket/Elements/ShowTime, minutes => $time_worked->{$user} &>
      % }
      % } % $m->callback( %ARGS, CallbackName => 'AfterTimeWorked', TicketObj => $Ticket ); % if ($Ticket->TimeLeft) {
      <&|/l&>Left:
      <& ShowTime, minutes => $Ticket->TimeLeft &>
      % } % } % $m->callback( %ARGS, CallbackName => 'AfterTimeLeft', TicketObj => $Ticket );
      <&|/l&>Priority:
      <& ShowPriority, Ticket => $Ticket &>
      % $m->callback( %ARGS, CallbackName => 'AfterPriority', TicketObj => $Ticket ); %# This will check SeeQueue at the ticket role level, queue level, and global level % if ($Ticket->CurrentUserHasRight('SeeQueue')) {
      <&|/l&>Queue:
      <& ShowQueue, Ticket => $Ticket, QueueObj => $Ticket->QueueObj &>
      % } % $m->callback( %ARGS, CallbackName => 'AfterQueue', TicketObj => $Ticket ); <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Grouping => 'Basics', Table => 0 &> % if ($UngroupedCFs) { <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Grouping => '', Table => 0 &> % } % $m->callback( %ARGS, CallbackName => 'EndOfList', TicketObj => $Ticket );
      % $m->callback( %ARGS, CallbackName => 'AfterTable', TicketObj => $Ticket ); <%ARGS> $Ticket => undef $UngroupedCFs => 0 <%init> my $time_worked; my $show_time_worked = $Ticket->CurrentUserCanSeeTime; if ( $show_time_worked ) { if (RT->Config->Get('DisplayTotalTimeWorked')) { $time_worked = $Ticket->TotalTimeWorkedPerUser; } else { $time_worked = $Ticket->TimeWorkedPerUser; } } rt-5.0.1/share/html/Ticket/Elements/ShowSummary000644 000765 000024 00000030306 14005011336 022271 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      % $m->callback( %ARGS, CallbackName => 'LeftColumnTop' ); <%PERL> my $modify_url = RT->Config->Get('WebPath')."/Ticket/Modify.html?id=".$Ticket->Id; my $modify_inline = '' . qq{} . '' . ''; my $modify_basics = sprintf( $modify_inline, $m->interp->apply_escapes( $modify_url, 'h' ) ); my $modify_behavior = $InlineEdit ? ($inline_edit_behavior{Basics} || $inline_edit_behavior{_default} || 'link') : 'hide'; <&| /Widgets/TitleBox, title => loc('The Basics'), (($can_modify || $can_modify_cf) ? (title_href => $modify_url) : ()), (($can_modify || $can_modify_cf) && $modify_behavior =~ /^(link|click)$/ ? (titleright_raw => $modify_basics) : ()), class => (join " ", 'ticket-info-basics', ($modify_behavior eq 'always' ? 'editing' : ())), data => { 'inline-edit-behavior' => $modify_behavior }, &> % unless ($modify_behavior eq 'always') {
      <& /Ticket/Elements/ShowBasics, Ticket => $Ticket &>
      % } % if ($modify_behavior ne 'hide') {
      <& /Ticket/Elements/EditBasics, TicketObj => $Ticket, InTable => 1, ExcludeOwner => 1, ExcludeCustomRoles => 1 &> <& /Elements/EditCustomFields, Object => $Ticket, Grouping => 'Basics', InTable => 1 &>
      % } % $m->callback( %ARGS, CallbackName => 'AfterBasics' ); <& /Elements/ShowCustomFieldCustomGroupings, Object => $Ticket, title_href => ($can_modify || $can_modify_cf) ? RT->Config->Get('WebPath')."/Ticket/Modify.html" : "", InlineEdit => ($can_modify || $can_modify_cf) ? $InlineEdit : 0, &> <%PERL> my $people_url = RT->Config->Get('WebPath')."/Ticket/ModifyPeople.html?id=".$Ticket->Id; my $people_inline = sprintf( $modify_inline, $m->interp->apply_escapes( $people_url, 'h' ) ); my $people_behavior = $InlineEdit ? ($inline_edit_behavior{People} || $inline_edit_behavior{_default} || 'link') : 'hide'; <&| /Widgets/TitleBox, title => loc('People'), (($can_modify || $can_modify_owner || $can_modify_people) ? (title_href => RT->Config->Get('WebPath')."/Ticket/ModifyPeople.html?id=".$Ticket->Id) : ()), class => (join " ", 'ticket-info-people', ($people_behavior eq 'always' ? 'editing' : ())), (($can_modify || $can_modify_owner || $can_modify_people) && $people_behavior =~ /^(link|click)$/ ? (titleright_raw => $people_inline) : ()), data => { 'inline-edit-behavior' => $people_behavior }, &> % unless ($people_behavior eq 'always') {
      <& /Ticket/Elements/ShowPeople, Ticket => $Ticket &>
      % } % if ($people_behavior ne 'hide') {
      <& /Ticket/Elements/EditPeopleInline, Ticket => $Ticket &>
      % } % $m->callback( %ARGS, CallbackName => 'AfterPeople' ); <& /Ticket/Elements/ShowAttachments, Ticket => $Ticket, Attachments => $Attachments, Count => RT->Config->Get('AttachmentListCount') &> % $m->callback( %ARGS, CallbackName => 'AfterAttachments' ); <& /Ticket/Elements/ShowRequestor, Ticket => $Ticket &> % $m->callback( %ARGS, CallbackName => 'LeftColumn' );
      % $m->callback( %ARGS, CallbackName => 'RightColumnTop' ); % if ( RT->Config->Get('EnableReminders') ) { <&|/Widgets/TitleBox, title => loc("Reminders"), title_href => RT->Config->Get('WebPath')."/Ticket/Reminders.html?id=".$Ticket->Id, class => 'ticket-info-reminders fullwidth', &>
      <& /Ticket/Elements/Reminders, Ticket => $Ticket, ShowCompleted => 0 &>
      % } % $m->callback( %ARGS, CallbackName => 'AfterReminders' ); <%PERL> my $dates_url = RT->Config->Get('WebPath')."/Ticket/ModifyDates.html?id=".$Ticket->Id; my $dates_inline = sprintf( $modify_inline, $m->interp->apply_escapes( $dates_url, 'h' ) ); my $dates_behavior = $InlineEdit ? ($inline_edit_behavior{Dates} || $inline_edit_behavior{_default} || 'link') : 'hide'; <&| /Widgets/TitleBox, title => loc("Dates"), ($can_modify ? (title_href => $dates_url) : ()), class => (join " ", 'ticket-info-dates', ($dates_behavior eq 'always' ? 'editing' : ())), ($can_modify && $dates_behavior =~ /^(link|click)$/ ? (titleright_raw => $dates_inline) : ()), data => { 'inline-edit-behavior' => $dates_behavior }, &> % unless ($dates_behavior eq 'always') {
      <& /Ticket/Elements/ShowDates, Ticket => $Ticket &>
      % } % if ($dates_behavior ne 'hide') {
      <& /Ticket/Elements/EditDates, TicketObj => $Ticket &>
      % } % $m->callback( %ARGS, CallbackName => 'AfterDates' ); % my (@extra); % push @extra, titleright_raw => ''.loc('Graph').'' unless RT->Config->Get('DisableGraphViz'); <& /Ticket/Elements/ShowLinkedQueues, TicketObj => $Ticket, &> <& /Ticket/Elements/ShowAssets, Ticket => $Ticket &> <%PERL> my $links_url = RT->Config->Get('WebPath')."/Ticket/ModifyLinks.html?id=".$Ticket->Id; my $links_inline = sprintf( $modify_inline, $m->interp->apply_escapes( $links_url, 'h' ) ); my $links_behavior = $InlineEdit ? ($inline_edit_behavior{Links} || $inline_edit_behavior{_default} || 'link') : 'hide'; my $alt = loc('Graph ticket links'); my $links_graph = ''; my $links_titleright = join ' ', ($can_modify && $links_behavior =~ /^(link|click)$/ ? ($links_inline) : ()), (RT->Config->Get('DisableGraphViz') ? () : $links_graph); push @extra, (titleright_raw => $links_titleright) if $links_titleright; % $m->callback( %ARGS, CallbackName => 'LinksExtra', extra => \@extra ); <&| /Widgets/TitleBox, title => loc('Links'), ($can_modify ? (title_href => $links_url) : ()), class => (join " ", 'ticket-info-links', ($links_behavior eq 'always' ? 'editing' : ())), data => { 'inline-edit-behavior' => $links_behavior }, ($can_modify ? (title_href => RT->Config->Get('WebPath')."/Ticket/ModifyLinks.html?id=".$Ticket->Id) : ()), @extra, &> % unless ($links_behavior eq 'always') {
      <& /Elements/ShowLinks, Object => $Ticket &>
      % } % if ($links_behavior ne 'hide') {
      <& /Elements/EditLinks, Object => $Ticket, TwoColumn => 0 &>

      <&|/l&>Merge

      <& /Ticket/Elements/EditMerge, Ticket => $Ticket, LabelStyle => 'col-3', ValueStyle => 'col-9', MergeTextClass => '', %ARGS &>
      % } % $m->callback( %ARGS, CallbackName => 'RightColumn' );
      <%ARGS> $Ticket => undef $Attachments => undef $InlineEdit => 0 <%INIT> my $can_modify = $Ticket->CurrentUserHasRight('ModifyTicket'); my $can_modify_cf = $Ticket->CurrentUserHasRight('ModifyCustomField'); my ($can_modify_owner) = $Ticket->CurrentUserCanSetOwner(); my $can_modify_people = $Ticket->CurrentUserHasRight('Watch') || $Ticket->CurrentUserHasRight('WatchAsAdminCc'); my $edit_label = $m->interp->apply_escapes( loc("Edit"), 'h' ); my $cancel_label = $m->interp->apply_escapes( loc("Cancel"), 'h' ); my %inline_edit_behavior; if (RT->Config->Get('InlineEditPanelBehavior')) { %inline_edit_behavior = %{ RT->Config->Get('InlineEditPanelBehavior')->{'RT::Ticket'} || {} }; } rt-5.0.1/share/html/Ticket/Elements/EditWatchers000644 000765 000024 00000006426 14005011336 022367 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
        %# Print out a placeholder if there are none. % if ( !$Watchers->id || $Members->Count == 0 ) {
      • <&|/l&>none
      • % } else { % while ( my $watcher = $Members->Next ) { % my $member = $watcher->MemberObj->Object;
      • % my $name = 'Ticket-DeleteWatcher-Type-' . $Watchers->Name . '-Principal-' . $watcher->MemberId;
      • % } % }
      <%INIT> my $Members = $Watchers->MembersObj; <%ARGS> $TicketObj => undef $Watchers => undef rt-5.0.1/share/html/Ticket/Elements/EditTransactionCustomFields000644 000765 000024 00000006762 14005011336 025421 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $m->callback( CallbackName => 'BeforeTransactionCustomFields', TicketObj => $TicketObj, QueueObj => $QueueObj, InTable => $InTable ); % if ( !$InTable ) {
      % } % if ($CustomFields->Count) { % while (my $CF = $CustomFields->Next()) { % next unless $CF->CurrentUserHasRight('ModifyCustomField');
      <% $CF->Name %>: % if ( $CF->EntryHint ) { % }
      <& /Elements/EditCustomField, %ARGS, CustomField => $CF, Object => RT::Transaction->new( $session{'CurrentUser'} ), &> % if (my $msg = $m->notes('InvalidField-' . $CF->Id)) {
      <% $msg %> % }
      % } % } % if ( !$InTable ) {
      % } % $m->callback( CallbackName => 'AfterTransactionCustomFields', TicketObj => $TicketObj, QueueObj => $QueueObj, InTable => $InTable ); <%INIT> my $CustomFields; if ($TicketObj) { $CustomFields = $TicketObj->TransactionCustomFields(); } else { $CustomFields = $QueueObj->TicketTransactionCustomFields(); } $m->callback( CallbackName => 'MassageTransactionCustomFields', CustomFields => $CustomFields, InTable => $InTable ); <%ARGS> $TicketObj => undef $QueueObj => undef $InTable => 0 $LabelCols => 3 $ValueCols => 9 rt-5.0.1/share/html/Ticket/Elements/ScrollShowHistory000644 000765 000024 00000016657 14005011336 023471 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Ticket <%INIT> my %params = %ARGS; delete $params{Ticket}; my $url = RT->Config->Get('WebPath') . "/Helpers/TicketHistoryPage?" . $m->comp('/Elements/QueryString', %params, id => $Ticket->id ); my %extra_args = map { $_ => $ARGS{$_} // 1 } qw/ShowDisplayModes ShowTitle ScrollShowHistory/; $extra_args{ShowHeaders} = $ARGS{ShowHeaders}; $extra_args{ReverseTxns} = $ARGS{ReverseTxns}; $m->callback( CallbackName => 'ExtraShowHistoryArguments', Ticket => $Ticket, ExtraArgs => \%extra_args ); my $oldestTransactionsFirst; if ( $ARGS{ReverseTxns} ) { $oldestTransactionsFirst = $ARGS{ReverseTxns} eq 'ASC' ? 1 : 0; } else { $oldestTransactionsFirst = RT->Config->Get("OldestTransactionsFirst", $session{CurrentUser}); } <& /Elements/ShowHistoryHeader, Object => $Ticket, %extra_args, &>
      % if ($extra_args{ShowDisplayModes} or $extra_args{ShowTitle} or $extra_args{ScrollShowHistory} ) { <& /Widgets/TitleBoxEnd &> % }
      rt-5.0.1/share/html/Ticket/Elements/ShowRequestorTickets000644 000765 000024 00000004752 14005011336 024162 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /User/Elements/TicketList, User => $Requestor, conditions => $conditions, Rows => $Rows, Title => $Title, WatcherTypes => ['Requestor'], Format => => RT->Config->Get('MoreAboutRequestorTicketListFormat'), &> <%INIT> my $Title = loc("This user's [_1] highest priority [_2] tickets", $Rows, $Description ); $m->callback( CallbackName => 'ModifyTitle', %ARGS, Title => \$Title ); <%ARGS> $Requestor => undef $conditions $Rows => 10 $Description => '' rt-5.0.1/share/html/Ticket/Elements/Reminders000644 000765 000024 00000022666 14005011336 021735 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $Ticket => undef $id => undef $ShowCompleted => 0 $Edit => 0 $ShowSave => 1 <%init> $Ticket = LoadTicket($id) if ($id); my $resolve_status = $Ticket->LifecycleObj->ReminderStatusOnResolve; my $count_reminders = RT::Reminders->new($session{'CurrentUser'}); $count_reminders->Ticket($Ticket->id); my $count_tickets = $count_reminders->Collection; if (!$ShowCompleted) { # XXX: don't break encapsulation if we can avoid it $count_tickets->FromSQL(q{Type = "reminder" AND RefersTo = "} . $Ticket->id . qq{" AND Status != "$resolve_status" }); } my $has_reminders = $count_tickets->Count; # We've made changes, let's reload our search my $reminder_collection = $count_reminders->Collection; % my $editable = 0; % if ($has_reminders) {
      % if ( $Edit ) { <&|/l&>Reminders % } else {
      <&|/l&>Reminder
      <&|/l&>Due
      <&|/l&>Owner
      % }
      % my $i = 0; % while ( my $reminder = $reminder_collection->Next ) {
      % $i++; % if ( $reminder->Status eq $resolve_status && !$ShowCompleted ) { % $i++; % } % else { % $editable = 1 if !$editable && $reminder->CurrentUserHasRight( 'ModifyTicket' ); % if ($Edit) { <& SELF:EditEntry, Reminder => $reminder, Ticket => $Ticket, Index => $i &> % } else { <& SELF:ShowEntry, Reminder => $reminder, Ticket => $Ticket, Index => $i &> % } % }
      % } % if ( $editable ) {
      <&|/l&>(Check box to complete)
      % }
      % } else { %# we must always include resolved reminders due to the browser %# checkbox-with-false-value issue % while ( my $reminder = $reminder_collection->Next ) { % if ( $reminder->Status eq $resolve_status && !$ShowCompleted ) { % } % } % } % if (lc $Ticket->Status ne "deleted" and $Ticket->QueueObj->CurrentUserHasRight('CreateTicket') and $Ticket->CurrentUserHasRight('ModifyTicket') ) { <& SELF:NewReminder, Ticket => $Ticket &> % $editable = 1; % } % if ( $editable && $ShowSave ) {
      % } <%method NewReminder> <%args> $Ticket
      <&|/l&>New reminder:
      <&|/l&>Subject:
      <&|/l&>Owner:
      <& /Elements/SelectOwner, Name => 'NewReminder-Owner', QueueObj => $Ticket->QueueObj, Default=>$session{'CurrentUser'}->id, DefaultValue => 0 &>
      <&|/l&>Due:
      <& /Elements/SelectDate, Name => "NewReminder-Due", Default => "" &>
      <%method EditEntry> <%args> $Reminder $Ticket $Index
      % unless ( $Reminder->CurrentUserHasRight('ModifyTicket') ) { Status eq $Reminder->LifecycleObj->ReminderStatusOnResolve ? 1 : 0 %> /> % }
      Status eq $Reminder->LifecycleObj->ReminderStatusOnResolve ? 'checked="checked"' : '' |n %> % unless ( $Reminder->CurrentUserHasRight('ModifyTicket') ) { disabled="disabled" % } />
      <&|/l&>Subject:
      CurrentUserHasRight('ModifyTicket') ) { readonly="readonly" % } />
      <&|/l&>Owner:
      <& /Elements/SelectOwner, Name => 'Reminder-Owner-'.$Reminder->id, QueueObj => $Ticket->QueueObj, Default => $Reminder->Owner, DefaultValue => 0 &>
      <&|/l&>Due:
      % if ( $Reminder->CurrentUserHasRight('ModifyTicket') ) {
      <& /Elements/SelectDate, Name => 'Reminder-Due-'.$Reminder->id &>
      (<% $Reminder->DueObj->AsString %>)
      % }
      <%method ShowEntry> <%args> $Reminder $Ticket $Index % my $dueobj = $Reminder->DueObj; % my $overdue = $dueobj->IsSet && $dueobj->Diff < 0 ? 1 : 0;
      % unless ( $Reminder->CurrentUserHasRight('ModifyTicket') ) {
      Status eq $Reminder->LifecycleObj->ReminderStatusOnResolve ? 1 : 0 %> />
      % }
      Status eq $Reminder->LifecycleObj->ReminderStatusOnResolve ? 'checked="checked"' : '' |n %> % unless ( $Reminder->CurrentUserHasRight('ModifyTicket') ) { disabled="disabled" % } />
      <% $overdue ? '' : '' |n %><% $dueobj->AgeAsString || loc('Not set') %><% $overdue ? '' : '' |n %>
      <& /Elements/ShowUser, User => $Reminder->OwnerObj &>
      rt-5.0.1/share/html/Ticket/Elements/ShowRequestorTicketsAll000644 000765 000024 00000004202 14005011336 024601 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& ShowRequestorTickets, %ARGS, conditions => $conditions, Rows => $Rows &> <%ARGS> $Requestor => undef $conditions => [] $Rows => 10 rt-5.0.1/share/html/Ticket/Elements/ShowDependencyStatus000644 000765 000024 00000005614 14005011336 024122 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      % if ($approvals) { % if ($approvals == 1) { <&|/l&>Pending approval. % } else { <&|/l, $approvals &>Pending [quant,_1,approval,approvals]. % } <&|/l&>This ticket cannot be resolved until it is approved. % } else { <&|/l, $depends &>Pending [quant,_1,ticket,tickets]. <&|/l, $depends &>This ticket cannot be resolved until its [numerate,_1,dependency is,dependencies are] resolved. % }
      <%args> $Ticket <%init> # normal tickets my $deps = $Ticket->UnresolvedDependencies; $deps->LimitType( VALUE => 'ticket' ); my $depends = $deps->Count || 0; # approvals $deps = $Ticket->UnresolvedDependencies; $deps->LimitType( VALUE => 'approval' ); my $approvals = $deps->Count || 0; return unless $depends or $approvals; rt-5.0.1/share/html/Ticket/Attachment/dhandler000644 000765 000024 00000010207 14005011336 022066 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%perl> my ( $ticket, $trans, $attach, $filename ); my $arg = $m->dhandler_arg; # get rest of path if ( $arg =~ m{^(\d+)/(\d+)} ) { $trans = $1; $attach = $2; } else { Abort("Corrupted attachment URL.", Code => HTTP::Status::HTTP_BAD_REQUEST); } my $AttachmentObj = RT::Attachment->new( $session{'CurrentUser'} ); $AttachmentObj->Load($attach) || Abort("Attachment '$attach' could not be loaded", Code => HTTP::Status::HTTP_NOT_FOUND); unless ( $AttachmentObj->id ) { Abort("Bad attachment id. Couldn't find attachment '$attach'\n", Code => HTTP::Status::HTTP_NOT_FOUND); } unless ( $AttachmentObj->TransactionId() == $trans ) { Abort("Bad transaction number for attachment. $trans should be". $AttachmentObj->TransactionId() . "\n", Code => HTTP::Status::HTTP_NOT_FOUND); } my $content = $AttachmentObj->OriginalContent; my $content_type = $AttachmentObj->ContentType || 'text/plain'; my $attachment_regex = qr{^(image/svg\+xml|application/pdf)}i; if ( RT->Config->Get('AlwaysDownloadAttachments') || ($content_type =~ $attachment_regex) ) { $r->headers_out->{'Content-Disposition'} = "attachment"; } elsif ( !RT->Config->Get('TrustHTMLAttachments') ) { my $text_plain_regex = qr{^(text/html|application/xhtml\+xml|text/xml|application/xml)}i; $content_type = 'text/plain' if ( $content_type =~ $text_plain_regex ); } elsif (lc $content_type eq 'text/html') { # If we're trusting and serving HTML for display not download, try to do # inline rewriting to be extra helpful. my $count = RT::Interface::Web::RewriteInlineImages( Content => \$content, Attachment => $AttachmentObj, ); RT->Logger->debug("Rewrote $count CID images when displaying original HTML attachment #$attach"); } my $enc = $AttachmentObj->OriginalEncoding || 'utf-8'; my $iana = Encode::find_encoding($enc); $iana = $iana ? $iana->mime_name : $enc; require MIME::Types; my $mimetype = MIME::Types->new->type($content_type); unless ( $mimetype && $mimetype->isBinary ) { $content_type .= ";charset=$iana"; } $r->content_type($content_type); $m->clear_buffer(); $m->out($content); $m->abort; <%attr> AutoFlush => 0 rt-5.0.1/share/html/Ticket/Attachment/WithHeaders/000755 000765 000024 00000000000 14005011336 022571 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Ticket/Attachment/WithHeaders/dhandler000644 000765 000024 00000005737 14005011336 024311 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%perl> # we don't need transaction id my ($id) = $m->dhandler_arg =~ /^(\d+)/; unless ( $id ) { # wrong url format Abort("Corrupted attachment URL", Code => HTTP::Status::HTTP_BAD_REQUEST); } my $AttachmentObj = RT::Attachment->new( $session{'CurrentUser'} ); $AttachmentObj->Load( $id ); unless ( $AttachmentObj->id ) { Abort("Couldn't load attachment #$id", Code => HTTP::Status::HTTP_NOT_FOUND); } my $content_type = 'text/plain'; my $enc = $AttachmentObj->OriginalEncoding || 'utf-8'; my $iana = Encode::find_encoding($enc); $iana = $iana ? $iana->mime_name : $enc; $content_type .= ";charset=$iana"; # XXX: should we check handle html here and integrate headers into html? $r->content_type( $content_type ); $m->clear_buffer; $m->out( $AttachmentObj->EncodedHeaders( $enc ) ); $m->out( "\n\n" ); $m->out( $AttachmentObj->OriginalContent ); $m->abort; <%attr> AutoFlush => 0 rt-5.0.1/share/html/Dashboards/index.html000644 000765 000024 00000004202 14005011336 021114 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => loc("Dashboards") &> <& /Elements/Tabs &> <& /Elements/ListActions &> <& /Dashboards/Elements/ShowDashboards &> rt-5.0.1/share/html/Dashboards/dhandler000644 000765 000024 00000004176 14005011336 020635 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> if ($m->dhandler_arg =~ /^(\d+)/) { $m->comp('/Dashboards/Render.html', id => $1, %ARGS); } else { $m->decline; } rt-5.0.1/share/html/Dashboards/Modify.html000644 000765 000024 00000013677 14005011336 021254 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => $title &> <& /Elements/Tabs &> <& /Elements/ListActions, actions => \@results &>
      %unless ($Dashboard->Id) { % } else { % } <&|/Widgets/TitleBox, title => loc('Basics') &>
      <&|/l&>Name:
      <&|/l&>Privacy:
      <& /Dashboards/Elements/SelectPrivacy, Name => "Privacy", Objects => \@privacies, Default => $Dashboard->Privacy &>
      <& /Elements/Submit, Name => 'Save', Label =>( $Create ? loc('Create') : loc('Save Changes') ) &>
      % if ($Dashboard->Id && $can_delete) {
      <& /Elements/Submit, Name => 'Delete', Label => loc('Delete') &>
      % }
      <%INIT> my ($title, @results); my $tried_create = 0; # user went directly to Modify.html $Create = 1 if !$id; my $redirect_to ='/Dashboards/Modify.html'; use RT::Dashboard; my $Dashboard = RT::Dashboard->new($session{'CurrentUser'}); my $method = $Create ? 'ObjectsForCreating' : 'ObjectsForModifying'; my @privacies = $Dashboard->$method; Abort(loc("Permission Denied"), Code => HTTP::Status::HTTP_FORBIDDEN) if @privacies == 0; if ($Create) { $title = loc("Create a new dashboard"); } else { if ($id eq 'new') { $tried_create = 1; my ($val, $msg) = $Dashboard->Save( Name => $ARGS{'Name'}, Privacy => $ARGS{'Privacy'}, ); if (!$val) { Abort(loc("Dashboard could not be created: [_1]", $msg)); } push @results, $msg; $id = $Dashboard->Id; if (!$Dashboard->id || ! $Dashboard->CurrentUserCanSee) { $redirect_to='/Dashboards/index.html'; } } else { my ($ok, $msg) = $Dashboard->LoadById($id); unless ($ok) { RT::Logger->error("Unable to load dashboard with $id: $msg"); Abort(loc("Could not load dashboard [_1]", $id), Code => HTTP::Status::HTTP_NOT_FOUND); } } if ($id) { $title = loc("Modify the dashboard [_1]", $Dashboard->Name); } # If the create failed else { $Create = 1; $title = loc("Create a new dashboard"); } } if (!$Create && !$tried_create && $id && $ARGS{'Save'}) { my ($ok, $msg) = $Dashboard->Update(Privacy => $ARGS{'Privacy'}, Name => $ARGS{'Name'}); if ($ok) { push @results, loc("Dashboard [_1] updated", $Dashboard->Name); } else { push @results, loc("Dashboard [_1] could not be updated: [_2]", $Dashboard->Name, $msg); } } my $can_delete = $Dashboard->CurrentUserCanDelete; if (!$Create && !$tried_create && $id && $ARGS{'Delete'}) { if (!$can_delete) { Abort(loc("Couldn't delete dashboard [_1]: Permission Denied", $id), Code => HTTP::Status::HTTP_FORBIDDEN); } my ($ok, $msg) = $Dashboard->Delete(); if (!$ok) { Abort(loc("Couldn't delete dashboard [_1]: [_2]", $id, $msg), Code => HTTP::Status::HTTP_BAD_REQUEST); } push @results, $msg; $redirect_to = '/Dashboards/index.html'; } # This code does automatic redirection if any updates happen. MaybeRedirectForResults( Actions => \@results, Path => $redirect_to, Arguments => { id => $id }, ); <%ARGS> $Create => undef $Name => undef $id => '' unless defined $id $Delete => undef rt-5.0.1/share/html/Dashboards/Queries.html000644 000765 000024 00000020212 14005011336 021421 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => $title &> <& /Elements/Tabs &> <& /Elements/ListActions, actions => \@results &>
      <& /Widgets/SearchSelection, pane_name => \%pane_name, sections => \@sections, selected => \%selected, filters => \@filters, &> <& /Elements/Submit, Name => "UpdateSearches", Label => loc('Save') &>
      <%INIT> my @results; # Don't permit someone to supply "self_service_dashboard=1" on the URL line if ($m->request_path ne '/Admin/Global/SelfServiceHomePage.html') { $self_service_dashboard = 0; } my $class = $self_service_dashboard ? 'RT::Dashboard::SelfService' : 'RT::Dashboard'; $class->require; my $Dashboard = $class->new($session{'CurrentUser'}); my ($ok, $msg) = $Dashboard->LoadById($id); unless ($ok) { RT::Logger->error("Unable to load dashboard with $id: $msg"); Abort(loc("Could not load dashboard [_1]", $id), Code => HTTP::Status::HTTP_NOT_FOUND); } my $title; if ($self_service_dashboard) { $title = loc("Modify the self-service home page"); } else { $title = loc("Modify the content of dashboard [_1]", $Dashboard->Name); } my @sections; my %item_for; my @components; if ($self_service_dashboard) { @components = map { type => "component", name => $_, label => loc($_) }, @{RT->Config->Get('SelfServicePageComponents') || []}; } else { @components = map { type => "component", name => $_, label => loc($_) }, @{RT->Config->Get('HomepageComponents')}; } $item_for{ $_->{type} }{ $_->{name} } = $_ for @components; push @sections, { id => 'components', label => loc("Components"), items => \@components, }; my $sys = RT::System->new($session{'CurrentUser'}); my @objs = ($sys); push @objs, RT::SavedSearch->new( $session{CurrentUser} )->ObjectsForLoading if $session{'CurrentUser'}->HasRight( Right => 'LoadSavedSearch', Object => $RT::System ); for my $object (@objs) { my @items; my $object_id = ref($object) . '-' . $object->Id; # saved searches and charts for ($m->comp("/Search/Elements/SearchesForObject", Object => $object)) { my ($desc, $loc_desc, $search) = @$_; my $SearchType = 'Ticket'; if ((ref($search->Content)||'') eq 'HASH') { $SearchType = $search->Content->{'SearchType'} if $search->Content->{'SearchType'}; } else { $RT::Logger->debug("Search ".$search->id." ($desc) appears to have no Content"); } my $item; my $oid = $object_id.'-SavedSearch-'.$search->Id; $item = { type => 'saved', name => $oid, search_type => $SearchType, label => $loc_desc }; my $setting = RT::SavedSearch->new($session{CurrentUser}); $setting->Load($object, $search->Id); $item->{possibly_hidden} = !$setting->IsVisibleTo($Dashboard->Privacy); $item_for{ $item->{type} }{ $item->{name} } = $item; push @items, $item; } for my $dashboard ($m->comp("/Dashboards/Elements/DashboardsForObject", Object => $object, Flat => 1)) { # Users *can* set up mutually recursive dashboards, but don't make it # THIS easy for them to shoot themselves in the foot. next if $dashboard->Id == $Dashboard->id; my $name = 'dashboard-' . $dashboard->Id . '-' . $dashboard->Privacy; my $item = { type => 'dashboard', name => $name, label => $dashboard->Name }; $item->{possibly_hidden} = !$dashboard->IsVisibleTo($Dashboard->Privacy); $item_for{ $item->{type} }{ $item->{name} } = $item; push @items, $item; } my $label = $object eq $sys ? loc('System') : $object->isa('RT::Group') ? $object->Label : $object->Name; push @sections, { id => $object_id, label => $label, items => [ sort { lc($a->{label}) cmp lc($b->{label}) } @items ], }; } my %pane_name = ( 'body' => loc('Body'), 'sidebar' => loc('Sidebar'), ); my %selected; do { my $panes = $Dashboard->Panes; for my $pane (keys %$panes) { my @items; for my $saved (@{ $panes->{$pane} }) { my $item; if ($saved->{portlet_type} eq 'component') { $item = $item_for{ $saved->{portlet_type} }{ $saved->{component} }; } elsif ($saved->{portlet_type} eq 'search') { my $name = join '-', $saved->{privacy}, 'SavedSearch', $saved->{id}; $item = $item_for{saved}{$name}; } else { my $type = $saved->{portlet_type}; my $name = join '-', $type, $saved->{id}, $saved->{privacy}; $item = $item_for{$type}{$name}; } if ($item) { push @items, $item; } else { push @results, loc('Unable to find [_1] [_2]', $saved->{portlet_type}, $saved->{description}); } } $selected{$pane} = \@items; } }; my @filters = ( [ 'component' => loc('Components') ], [ 'dashboard' => loc('Dashboards') ], [ 'ticket' => loc('Tickets') ], [ 'chart' => loc('Charts') ], ); $m->callback( CallbackName => 'Default', pane_name => \%pane_name, sections => \@sections, selected => \%selected, filters => \@filters, ); if ( $ARGS{UpdateSearches} ) { $ARGS{dashboard_id} = $id; $ARGS{self_service_dashboard} = $self_service_dashboard; my ($ok, $msg) = UpdateDashboard( \%ARGS, \%item_for ); if ($self_service_dashboard) { push @results, $ok ? loc('Self-Service home page updated') : $msg; } else { push @results, $ok ? loc('Dashboard updated') : $msg; } my $path; my $args; if ($self_service_dashboard) { $path = '/Admin/Global/SelfServiceHomePage.html'; $args = { }; } else { $path = '/Dashboards/Queries.html'; $args = { id => $id }; } MaybeRedirectForResults( Actions => \@results, Path => $path, Arguments => $args, ); } <%ARGS> $id => '' unless defined $id $self_service_dashboard => 0 rt-5.0.1/share/html/Dashboards/Elements/000755 000765 000024 00000000000 14005011336 020675 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Dashboards/Render.html000644 000765 000024 00000014274 14005011336 021236 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => $title, ShowBar => $Preview, Refresh => $Refresh, &> % if ($Preview) { <& /Elements/Tabs &> % } <& /Elements/ListActions, actions => \@results &> % # honor the chosen language for just the dashboard content % my $original_handle; % if ($SubscriptionObj->id) { % if (my $lang = $SubscriptionObj->SubValue('Language')) { % $original_handle = $session{'CurrentUser'}->{'LangHandle'}; % $session{'CurrentUser'}->{'LangHandle'} = RT::I18N->get_handle($lang); % } % } % $m->callback(CallbackName => 'BeforeTable', Dashboard => $Dashboard, show_cb => $show_cb);
      % $m->callback(CallbackName => 'BeforePanes', Dashboard => $Dashboard, show_cb => $show_cb); % my $body = $show_cb->('body'); % my $sidebar = $show_cb->('sidebar');
      <% $body |n %>
      % if ( $sidebar =~ /\S/ ) {
      <% $sidebar |n %>
      % } % $m->callback(CallbackName => 'AfterPanes', Dashboard => $Dashboard, show_cb => $show_cb);
      % $m->callback(CallbackName => 'AfterTable', Dashboard => $Dashboard, show_cb => $show_cb); % if (!$Preview) { % my $edit = RT->Config->Get('WebPath') . '/Dashboards/Modify.html?id='.$id; % my $subscription = RT->Config->Get('WebPath') . '/Dashboards/Subscription.html?id='.$id;

      <&|/l, $edit, $subscription &>You may edit this dashboard and your subscription to it in RT.

      %# We disable autohandlers when mailing (!$Preview) so /Elements/Footer isn't %# run for us. Tidy up the end of the HTML. We don't use /Elements/Tabs (and %# hence PageLayout) so we don't need to close any other tags. % } % # restore the original language for anything else on the page % if ($original_handle) { % $session{'CurrentUser'}->{'LangHandle'} = $original_handle; % } <%INIT> my @results; my $skip_create = 0; # Don't permit someone to supply "self_service_dashboard=1" directly # on the URL line if ($m->request_path !~ m{^/SelfService/}) { $self_service_dashboard = 0; } $m->callback(ARGSRef => \%ARGS, results => \@results, CallbackName => 'Initial', skip_create => \$skip_create); my $class = $self_service_dashboard ? 'RT::Dashboard::SelfService' : 'RT::Dashboard'; $class->require; my $Dashboard = $class->new($session{'CurrentUser'}); my ($ok, $msg) = $Dashboard->LoadById($id); unless ($ok) { RT::Logger->error("Unable to load dashboard with $id: $msg"); Abort(loc("Could not load dashboard [_1]", $id), Code => HTTP::Status::HTTP_NOT_FOUND); } my $path = '/Dashboards/' . $Dashboard->id . '/' . $Dashboard->Name; unless ( $skip_create ) { push @results, ProcessQuickCreate( Path => $path, ARGSRef => \%ARGS ); } my $SubscriptionObj = RT::Attribute->new($session{'CurrentUser'}); my $rows; # try to load the subscription to this id to get a better idea of number of rows for my $sub ($session{'CurrentUser'}->UserObj->Attributes->Named('Subscription')) { next unless $sub->SubValue('DashboardId') == $id; $SubscriptionObj = $sub; $rows = $SubscriptionObj->SubValue('Rows'); last; } # otherwise honor their search preferences.. otherwise 50 rows # $rows == 0 means unlimited, which we don't want to ignore from above unless (defined($rows)) { my $prefs = $session{'CurrentUser'}->UserObj->Preferences("SearchDisplay") || {}; $rows = defined($prefs->{'RowsPerPage'}) ? $prefs->{'RowsPerPage'} : 50; } my $title; if ($self_service_dashboard) { $title = loc('Self-Service Dashboard'); } else { $title = loc('[_1] Dashboard', $Dashboard->Name); } my $show_cb = sub { my $pane = shift; return $m->scomp('Elements/ShowPortlet/dashboard', Portlet => $Dashboard, Rows => $rows, Preview => $Preview, Dashboard => $Dashboard, Pane => $pane, Depth => 0, HasResults => $HasResults, ); }; my $Refresh = $Preview ? $session{'home_refresh_interval'} || RT->Config->Get('HomePageRefreshInterval', $session{'CurrentUser'}) : 0; <%ARGS> $id => undef $Preview => 1 $HasResults => undef $self_service_dashboard => 0 rt-5.0.1/share/html/Dashboards/Subscription.html000644 000765 000024 00000042067 14005011336 022504 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => $title &> <& /Elements/Tabs &> <& /Elements/ListActions, actions => \@results &>
      <&| /Widgets/TitleBox, title => loc('Dashboard') &>
      <&|/l&>Dashboard:
      <% $Dashboard->Name %>
      <&|/l&>Queries:
      % my @portlets = grep { defined } $Dashboard->Portlets; % if (!@portlets) { (<&|/l&>none) % } else {
        % for my $portlet (@portlets) {
      1. <% loc( RT::SavedSearch->EscapeDescription($portlet->{description}), $fields{'Rows'}) %>
      2. % }
      % }
      <&| /Widgets/TitleBox, title => loc('Subscription') &>
      <&|/l&>Frequency:
      class="custom-control-input">
      % for my $day ( qw/Monday Tuesday Wednesday Thursday Friday Saturday Sunday/ ) {
      >
      % }
      class="custom-control-input">
      <&|/l&>every
      <&|/l&>weeks
      class="custom-control-input">
      class="custom-control-input">
      <&|/l&>Hour:
      (<%$timezone%>)
      <&|/l&>Language:
      <& /Elements/SelectLang, Name => 'Language', Default => $fields{'Language'}, ShowNullOption => 1, &>
      <&|/l&>Rows:
      >
      % $m->callback( %ARGS, CallbackName => 'SubscriptionFormEnd', FieldsRef => \%fields, % SubscriptionObj => $SubscriptionObj, DashboardObj => $Dashboard ); <&| /Widgets/TitleBox, title => loc('Recipients') &> <& Elements/SubscriptionRecipients, UserField => $UserField, UserString => $UserString, UserOp => $UserOp, GroupString => $GroupString, GroupOp => $GroupOp, GroupField => $GroupField, Recipients => $fields{Recipients}, IsFirstSubscription => $SubscriptionObj ? 0 : 1 &>
      % if ($SubscriptionObj) { <& /Elements/Submit, Name => "Save", Label => loc('Save Changes') &> % } else { <& /Elements/Submit, Name => "Save", Label => loc('Subscribe') &> % }
      <%INIT> use List::MoreUtils 'uniq'; my ($title, @results); my $Loaded = 0; my $timezone = $session{'CurrentUser'}->UserObj->Timezone || RT->Config->Get('Timezone'); use RT::Dashboard; my $Dashboard = RT::Dashboard->new($session{'CurrentUser'}); my ($ok, $msg) = $Dashboard->LoadById($id); unless ($ok) { RT::Logger->error("Unable to load dashboard with $id: $msg"); Abort(loc("Could not load dashboard [_1]", $id), Code => HTTP::Status::HTTP_NOT_FOUND); } my $SubscriptionObj = $Dashboard->Subscription; $id = $SubscriptionObj ? $SubscriptionObj->SubValue('DashboardId') : $ARGS{'id'}; my %fields = ( DashboardId => $id, Frequency => 'daily', Monday => 1, Tuesday => 1, Wednesday => 1, Thursday => 1, Friday => 1, Saturday => 0, Sunday => 0, Hour => '06:00', Dow => 'Monday', Dom => 1, Rows => 20, Recipients => { Users => [], Groups => [] }, Fow => 1, Counter => 0, Language => '', SuppressIfEmpty => 0, ); $m->callback( %ARGS, CallbackName => 'SubscriptionFields', FieldsRef => \%fields, SubscriptionObj => $SubscriptionObj, DashboardObj => $Dashboard); # update any fields with the values from the subscription object if ($SubscriptionObj) { for my $field (keys %fields) { $fields{$field} = $SubscriptionObj->SubValue($field); } } # finally, update any fields with arguments passed in by the user for my $field (keys %fields) { next if $field eq 'DashboardId'; # but this one is immutable $fields{$field} = $ARGS{$field} if defined($ARGS{$field}) || $ARGS{$field.'-Magic'}; } $m->callback( %ARGS, CallbackName => 'MassageSubscriptionFields', FieldsRef => \%fields, SubscriptionObj => $SubscriptionObj, DashboardObj => $Dashboard); # this'll be defined on submit if (defined $ARGS{Save}) { # update recipients for my $key (keys %ARGS) { my $val = $ARGS{$key}; if ( $key =~ /^Dashboard-Subscription-Email-\d+$/ && $val ) { my @recipients = @{ $fields{Recipients}->{Users} }; for ( RT::EmailParser->ParseEmailAddress( $val ) ) { my $email = $_->address; my $user = RT::User->new(RT->SystemUser); ($ok, $msg) = $user->LoadOrCreateByEmail( EmailAddress => $email, Comments => 'Autocreated when added as a dashboard subscription recipient', ); unless ($ok) { push @results, loc("Could not add [_1] as a recipient: [_2]", $email, $msg); next; } my $is_prev_recipient = grep { $_ == $user->id } @recipients; next if $is_prev_recipient; push @recipients, $user->id; push @results, loc("[_1] added to dashboard subscription recipients", $email); } @{ $fields{Recipients}->{Users} } = uniq @recipients; } elsif ($key =~ /^Dashboard-Subscription-(Users|Groups)-(\d+)$/) { my ($mode, $type, $id) = ('', $1, $2); my @recipients = @{ $fields{Recipients}->{$type} }; # find out proper value for user/group checkbox my $add_keep_recipient = ref $ARGS{$key} eq 'ARRAY' ? grep { $_ } @{ $ARGS{$key} } : $ARGS{$key}; my $record; # hold user/group object if ($type eq 'Users') { my $user = RT::User->new($session{CurrentUser}); $user->Load( $id ); $record = $user; } elsif ($type eq 'Groups') { my $group = RT::Group->new($session{CurrentUser}); $group->Load( $id ); $record = $group; } my $is_prev_recipient = grep { $_ == $id } @recipients; if ($add_keep_recipient and not $is_prev_recipient) { # Add User/Group push @recipients, $id; push @results, loc("[_1] added to dashboard subscription recipients", $record->Name); } elsif (not $add_keep_recipient and $is_prev_recipient) { # Remove User/Group @recipients = grep { $_ != $id } @recipients; push @results, loc("[_1] removed from dashboard subscription recipients", $record->Name); } @{ $fields{Recipients}->{$type} } = uniq @recipients; } } # update if ($SubscriptionObj) { $id = delete $fields{'DashboardId'}; # immutable ($ok, $msg) = $SubscriptionObj->SetSubValues(%fields); $fields{'DashboardId'} = $id; $msg = loc("Subscription updated") if $ok; push @results, $msg; } # create else { Abort(loc("Unable to subscribe to dashboard [_1]: Permission Denied", $id)) unless $Dashboard->CurrentUserCanSubscribe; $SubscriptionObj = RT::Attribute->new($session{CurrentUser}); ($ok, $msg) = $SubscriptionObj->Create( Name => 'Subscription', Description => 'Subscription to dashboard ' . $id, ContentType => 'storable', Object => $session{'CurrentUser'}->UserObj, Content => \%fields, ); if ($ok) { push @results, loc("Subscribed to dashboard [_1]", $Dashboard->Name); } else { push @results, loc('Subscription could not be created: [_1]', $msg); } } push @results, loc("Warning: This dashboard has no recipients") unless @{ $fields{Recipients}->{Users} } || @{ $fields{Recipients}->{Groups} }; } elsif (defined $ARGS{OnlySearchForPeople}) { $GroupString = undef; $GroupField = undef; $GroupOp = undef; } elsif (defined $ARGS{OnlySearchForGroup}) { $UserString = undef; $UserField = undef; $UserOp = undef; } if ($SubscriptionObj) { $title = loc("Modify the subscription to dashboard [_1]", $Dashboard->Name); } else { $title = loc("Subscribe to dashboard [_1]", $Dashboard->Name); } <%ARGS> $id => undef $Frequency => undef $Hour => undef $Dow => undef $Dom => undef $Rows => undef $Recipient => undef $Language => undef $UserField => undef $UserOp => undef $UserString => undef $GroupField => undef $GroupOp => undef $GroupString => undef rt-5.0.1/share/html/Dashboards/Elements/SelectPrivacy000644 000765 000024 00000005241 14005011336 023377 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> @Objects => undef $Name => undef $Default => undef rt-5.0.1/share/html/Dashboards/Elements/DashboardsForObject000644 000765 000024 00000006203 14005011336 024471 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $Object => undef $User => $session{'CurrentUser'} $Flat => 0 <%init> # Returns a hash of dashboards associated on $Object # or, if $Flat, a simple list use RT::Dashboard; my %dashboards; my @flat_dashboards; my $privacy = RT::Dashboard->_build_privacy($Object); while (my $attr = $Object->Attributes->Next) { if ($attr->Name =~ /^Dashboard\b/) { my $dashboard = RT::Dashboard->new($User); my ($ok, $msg) = $dashboard->Load($privacy, $attr->id); if (!$ok) { $RT::Logger->debug("Unable to load dashboard $ok (privacy $privacy): $msg"); next; } if ($Flat) { push @flat_dashboards, $dashboard; } else { if ($Object->isa('RT::System')) { push @{ $dashboards{system} }, $dashboard; } elsif ($Object->isa('RT::User')) { push @{ $dashboards{personal} }, $dashboard; } elsif ($Object->isa('RT::Group')) { push @{ $dashboards{group}{$Object->Name} }, $dashboard; } } } } return $Flat ? @flat_dashboards : \%dashboards; rt-5.0.1/share/html/Dashboards/Elements/ShowDashboards000644 000765 000024 00000005551 14005011336 023541 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % foreach my $Object (@Objects) { % my $Dashboards = RT::Dashboards->new($session{CurrentUser}); % $Dashboards->LimitToPrivacy(join('-',ref($Object),$Object->Id)); % my $title; % if (ref $Object eq 'RT::User' && $Object->Id == $session{CurrentUser}->Id) { % $title = loc("My dashboards"); % } else { % $title = loc("[_1]'s dashboards",$Object->Name); % } % $title =~ s/([\\'])/\\$1/g; % $title = $m->interp->apply_escapes($title, 'h'); % $Dashboards->SortDashboards(); <& /Elements/CollectionList, %ARGS, Format => qq{'__Name__/TITLE:$title', __Subscription__}, Collection => $Dashboards, &> % } <%init> use RT::Dashboards; my @Objects = RT::Dashboard->new($session{CurrentUser})->ObjectsForLoading(IncludeSuperuserGroups => $IncludeSuperuserGroups); <%args> $IncludeSuperuserGroups => 1 rt-5.0.1/share/html/Dashboards/Elements/ListOfDashboards000644 000765 000024 00000006065 14005011336 024022 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> # put the list of dashboards into the navigation use RT::Dashboard; my @objs = RT::Dashboard->new($User)->ObjectsForLoading( IncludeSuperuserGroups => $IncludeSuperuserGroups ); my %dashboard_map; for my $object (@objs) { my $new_dashboards = $m->comp("/Dashboards/Elements/DashboardsForObject", Object => $object, User => $User ); push @{ $dashboard_map{$_} }, @{ $new_dashboards->{$_} || [] } for qw/personal system/; push @{ $dashboard_map{group}{$_} }, @{ $new_dashboards->{group}{$_} } for keys %{ $new_dashboards->{group} || {} }; } my @dashboards = ( (sort { $a->Id <=> $b->Id } @{ $dashboard_map{personal} || [] }), (sort { $a->Id <=> $b->Id } @{ $dashboard_map{system} || [] }), map { sort { $a->Id <=> $b->Id } @{ $dashboard_map{group}{$_} } } keys %{ $dashboard_map{group} || {} }, ); $m->callback(%ARGS, dashboards => \@dashboards, CallbackName => 'ModifyDashboards'); return @dashboards; <%args> $User => $session{CurrentUser} $IncludeSuperuserGroups => 1 rt-5.0.1/share/html/Dashboards/Elements/Deleted000644 000765 000024 00000004551 14005011336 022173 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> @searches % if (@searches) { <&| /Widgets/TitleBox, title => loc('Deleted queries') &>

      <% loc("The following queries have been deleted and each will be removed from the dashboard once its pane is updated.") %>

        % for (@searches) {
      • <% loc('[_1] (from pane [_2])', ($_->{description} || $_->{name}), $_->{pane}) %>
      • % }
      % } rt-5.0.1/share/html/Dashboards/Elements/SubscriptionRecipients000644 000765 000024 00000023133 14005011336 025334 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $UserField => 'Name' $UserOp => '=' $UserString => undef $PrivilegedOnly => undef $GroupField => 'Name' $GroupOp => '=' $GroupString => undef $Recipients => undef $IsFirstSubscription => undef <%INIT> my ($recipients_users, $recipients_groups); my ($Users, $Groups); if ($Recipients) { $recipients_users = $Recipients->{Users}; $recipients_groups = $Recipients->{Groups}; } if ($UserString) { $Users = RT::Users->new($session{'CurrentUser'}); $Users->Limit(FIELD => $UserField, VALUE => $UserString, OPERATOR => $UserOp, CASESENSITIVE => 0); $Users->LimitToPrivileged if $PrivilegedOnly; my $excluded_users = [ $session{'CurrentUser'}->id ]; push @$excluded_users, @{ $recipients_users || [] }; $Users->Limit(FIELD => 'id', VALUE => $excluded_users, OPERATOR => 'NOT IN'); } if ($GroupString) { $Groups = RT::Groups->new($session{'CurrentUser'}); $Groups->LimitToUserDefinedGroups; $Groups->Limit(FIELD => $GroupField, VALUE => $GroupString, OPERATOR => $GroupOp, CASESENSITIVE => 0); $Groups->Limit(FIELD => 'id', VALUE => $recipients_groups, OPERATOR => 'NOT IN') if @{ $recipients_groups || [] }; }
      <&|/l&>Find people whose
      % if ($UserString) { <& /Elements/SelectUsers, UserString => $UserString, UserField => $UserField, UserOp => $UserOp &> % } else { <& /Elements/SelectUsers &> % }

      <&|/l&>Find groups whose
      % if ($GroupString) { <& /Elements/SelectGroups, GroupString => $GroupString, GroupField => $GroupField, GroupOp => $GroupOp &> % } else { <& /Elements/SelectGroups &> % }

      <&|/l&>Add New Recipients:
      % if ( $Users && $Users->Count ) {
      <&|/l&>User
      % while ( my $u = $Users->Next ) {
      % } % } % if ( $Groups && $Groups->Count ) {
      <&|/l&>Group
      % while ( my $g = $Groups->Next ) {
      % } % }
      <&|/l&>Email
      % for my $i (1 .. 3) {
      <& /Elements/EmailInput, Name => 'Dashboard-Subscription-Email-' . $i, Size => '20' &>
      % }
      <&|/l&>Current Recipients:
        % my $current_user_id = $session{CurrentUser}->id; % my $current_user_subscribed = $recipients_users && grep { $_ == $current_user_id } @$recipients_users;
      • >
      • % for my $user_id (@{ $recipients_users || [] }) { % next if $user_id == $session{'CurrentUser'}->id; # already listed current user % my $user = RT::User->new( $session{'CurrentUser'} ); % $user->Load($user_id); % next unless $user->id and !$user->Disabled;
      • % } % for my $group_id (@{ $recipients_groups || [] }) { % my $group = RT::Group->new( $session{'CurrentUser'} ); % $group->Load($group_id); % next unless $group->id and !$group->Disabled;
      • % }
      rt-5.0.1/share/html/Dashboards/Elements/ShowPortlet/000755 000765 000024 00000000000 14005011336 023167 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Dashboards/Elements/HiddenSearches000644 000765 000024 00000005254 14005011336 023477 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> @searches $Dashboard <%init> # eliminate deleted searches (id=0) because they confuse this logic @searches = grep { $_->Id } @searches; return if @searches == 0; my @display; for my $search (@searches) { if ($search->Name eq 'SavedSearch') { push @display, $search->Description; } elsif ($search->Name =~ m/^Search - (.*)/) { push @display, $1; } else { push @display, $search->Name; } } <&| /Widgets/TitleBox, title => loc('Possible hidden searches') &>

      <% loc("The following queries may not be visible to all users who can see this dashboard.") %>

        % for (@display) {
      • <% $_ %>
      • % }
      rt-5.0.1/share/html/Dashboards/Elements/ShowPortlet/component000644 000765 000024 00000005660 14005011336 025123 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $Dashboard $Portlet $Rows => 20 $Preview => 0 $HasResults <%init> my $full_path = $Portlet->{path}; (my $path = $full_path) =~ s{^/Elements/}{}; my $allowed; if ($m->request_path =~ m{/SelfService/}) { $allowed = grep { $_ eq $path } @{RT->Config->Get('SelfServicePageComponents') || []}; } else { $allowed = grep { $_ eq $path } @{RT->Config->Get('HomepageComponents')}; } % if (!$allowed) { % $m->out( $m->interp->apply_escapes( loc("Invalid portlet [_1]", $path), "h" ) ); % RT->Logger->info("Invalid portlet $path found on dashboard #" . $Dashboard->Id); % if ($path eq 'QueueList' && grep { $_ eq 'Quicksearch' } @{RT->Config->Get('HomepageComponents')}) { % RT->Logger->warning("You may need to replace the component 'Quicksearch' in the HomepageComponents config with 'QueueList'. See the UPGRADING-4.4 document."); % } % } else { % $m->comp($full_path, HasResults => $HasResults); % } rt-5.0.1/share/html/Dashboards/Elements/ShowPortlet/search000644 000765 000024 00000004503 14005011336 024361 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $Dashboard $Portlet $Rows => 20 $Preview => 0 $HasResults <%init> my @for_showsearch = $Dashboard->ShowSearchName($Portlet); <& /Elements/ShowSearch, @for_showsearch, Override => { Rows => $Rows }, hideable => $Preview, ShowCustomize => $Preview, HasResults => $HasResults, &> rt-5.0.1/share/html/Dashboards/Elements/ShowPortlet/dashboard000644 000765 000024 00000005547 14005011336 025054 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $Dashboard $Pane $Portlet $Rows => 20 $Preview => 0 $Depth => 0 $HasResults <%init> my $current_dashboard; if (blessed($Portlet) && $Portlet->isa('RT::Dashboard')) { $current_dashboard = $Portlet; } else { $current_dashboard = RT::Dashboard->new($session{CurrentUser}); my ($ok, $msg) = $current_dashboard->LoadById($Portlet->{id}); if (!$ok) { $m->out($msg); return; } } my @panes = @{ $current_dashboard->Panes->{$Pane} || [] }; Abort("Possible recursive dashboard detected.") if $Depth > 8; <%perl> for my $portlet (@panes) { $m->comp($portlet->{portlet_type}, Portlet => $portlet, Rows => $Rows, Preview => $Preview, Dashboard => $current_dashboard, Pane => $Pane, Depth => $Depth + 1, HasResults => $HasResults ); } rt-5.0.1/share/html/Download/CustomFieldValue/000755 000765 000024 00000000000 14005011336 022031 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Download/CustomFieldValue/dhandler000644 000765 000024 00000005506 14005011336 023543 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%perl> my $id; my $arg = $m->dhandler_arg; # get rest of path if ($arg =~ /^(\d+)\//) { $id = $1; } else { Abort("Corrupted customfieldvalue URL."); } my $OCFV = RT::ObjectCustomFieldValue->new($session{'CurrentUser'}); $OCFV->Load($id) || Abort("OCFV '$id' could not be loaded"); unless ($OCFV->id) { Abort("Bad OCFV id. Couldn't find OCFV '$id'\n"); } my $content_type = $OCFV->ContentType || 'text/plain; charset=utf-8'; if (RT->Config->Get('AlwaysDownloadAttachments')) { $r->headers_out->{'Content-Disposition'} = "attachment"; } elsif (!RT->Config->Get('TrustHTMLAttachments')) { $content_type = 'text/plain; charset=utf-8' if ($content_type =~ /^text\/html/i); } $r->content_type( $content_type ); $m->clear_buffer(); $m->out($OCFV->LargeContent); $m->abort; <%attr> AutoFlush => 0 rt-5.0.1/share/html/Elements/SelectOwnerAutocomplete000644 000765 000024 00000005435 14005011336 023371 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Name => undef $Objects => [] $Default => 0 $ValueAttribute => 'Name' $TicketObj => undef <%INIT> $ValueAttribute = 'Name' unless $ValueAttribute =~ /^(?:id|Name)$/; my $value = ''; if ( $Default and not $Default =~ /\D/ ) { my $user = RT::User->new( $session{'CurrentUser'} ); $user->Load($Default); $value = $user->$ValueAttribute; } elsif (defined $TicketObj) { $value = $TicketObj->OwnerObj->$ValueAttribute; } # Map to a string of RT::Ticket-1|RT::Queue-5|... my $limit = join '|', map { join '-', ref($_), ($_->id || '') } @$Objects; rt-5.0.1/share/html/Elements/EditLinks000644 000765 000024 00000012111 14005011336 020430 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}

      <&|/l&>Current Links

      <& ShowRelationLabel, Object => $Object, Label => loc('Depends on').':', Relation => 'DependsOn' &>
      % while (my $link = $Object->DependsOn->Next) {
      <& EditLink, Link => $link, Mode => 'Target' &>
      % }
      <& ShowRelationLabel, Object => $Object, Label => loc('Depended on by').':', Relation => 'DependedOnBy' &>
      % while (my $link = $Object->DependedOnBy->Next) {
      <& EditLink, Link => $link, Mode => 'Base' &>
      % }
      <& ShowRelationLabel, Object => $Object, Label => loc('Parents').':', Relation => 'Parents' &>
      % while (my $link = $Object->MemberOf->Next) {
      <& EditLink, Link => $link, Mode => 'Target' &>
      % }
      <& ShowRelationLabel, Object => $Object, Label => loc('Children').':', Relation => 'Children' &>
      % while (my $link = $Object->Members->Next) {
      <& EditLink, Link => $link, Mode => 'Base' &>
      % }
      <& ShowRelationLabel, Object => $Object, Label => loc('Refers to').':', Relation => 'RefersTo' &>
      % while (my $link = $Object->RefersTo->Next) {
      <& EditLink, Link => $link, Mode => 'Target' &>
      %}
      <& ShowRelationLabel, Object => $Object, Label => loc('Referred to by').':', Relation => 'ReferredToBy' &>
      % while (my $link = $Object->ReferredToBy->Next) {
      <& EditLink, Link => $link, Mode => 'Base' &>
      % }
      <&|/l&>(Check box to delete)

      <&|/l&>New Links

      <& AddLinks, %ARGS &>
      <%ARGS> $Object => undef $TwoColumn => 1 rt-5.0.1/share/html/Elements/Tabs000644 000765 000024 00000005174 14005011336 017446 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/PageLayout, show_menu => $show_menu &> <%INIT> my $request_path = $HTML::Mason::Commands::r->path_info; $request_path =~ s!/{2,}!/!g; my $top = Menu(); my $widgets = PageWidgets(); my $page = PageMenu(); use RT::Interface::Web::MenuBuilder; if ( $request_path !~ m{^/SelfService/} ) { RT::Interface::Web::MenuBuilder::BuildMainNav( $request_path, $top, $widgets, $page, %ARGS ); } else { RT::Interface::Web::MenuBuilder::BuildSelfServiceNav( $request_path, $top, $widgets, $page, %ARGS ); } <%ARGS> $show_menu => 1 $QueryString => '' $QueryArgs => {} rt-5.0.1/share/html/Elements/EditTimeValue000644 000765 000024 00000005342 14005011336 021253 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <& /Elements/SelectTimeUnits, Name => $UnitName, Default => $InUnits &>
      <%ARGS> $Default => '' $Name => '' $ValueName => '' $UnitName => '' $InUnits => '' <%INIT> $ValueName ||= $Name; $UnitName ||= ($Name||$ValueName) . '-TimeUnits'; $InUnits ||= $m->request_args->{ $UnitName }; $InUnits ||= RT->Config->Get('DefaultTimeUnitsToHours', $session{'CurrentUser'}) ? 'hours' : 'minutes'; if ($Default && $InUnits eq 'hours') { # 0+ here is to remove the ending 0s $Default = 0 + sprintf '%.2f', $Default / 60; } rt-5.0.1/share/html/Elements/EditCustomFieldSelect000644 000765 000024 00000017350 14005011336 022740 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# Build up the set of cascading select boxes as "guides" %# each one limits the options of the final one a bit %# (perhaps by tweaking the .display style?) % my $selected = 0; % my @category; % my $out = $m->scomp('SELF:options', %ARGS, SelectedRef => \$selected, CategoryRef => \@category); % if ( $RenderType eq 'List' ) {
      BasedOnObj->id) { data-cascade-based-on-name="<% $BasedOnName || $NamePrefix . $CustomField->BasedOnObj->id . '-Value' |n %>" % } > % if ( $checktype eq 'radio' ) { % if ( $show_empty_option ) {
      />
      % } % } % my $CFVs = CachedCustomFieldValues($CustomField); % while ( my $value = $CFVs->Next ) { % my $content = $value->Name; % my $labelid = "$name-". $value->id;
      />
      % }
      % } else { % if (@category) { %# this hidden select is to supply a full list of values, %# see filter_cascade_select() in js/cascaded.js % } % my $size = ($Rows && ( $Multiple || !@category || $RenderType eq 'Select box')) ? $Rows : 1; % } <%init> # Handle render types $RenderType ||= $CustomField->RenderType; if ( $RenderType eq 'Dropdown' ) { # Turn it into a dropdown $Rows = 0; } # Process scalar values for Default if ( $Default and !@Default ){ push @Default, $Default; } my ($checktype, $name); if ( $MaxValues == 1 and $RenderType eq 'List' ) { ($checktype, $name) = ('radio', $Name || $NamePrefix . $CustomField->Id . '-Value'); } else { ($checktype, $name) = ('checkbox', $Name || $NamePrefix . $CustomField->Id . '-Values'); } @Default = grep defined && length, @Default; if ( !@Default && $Values ) { @Default = map $_->Content, @{ $Values->ItemsArrayRef }; } my %default = map {lc $_ => 1} @Default; my $show_empty_option; if ( exists $ARGS{ShowEmptyOption} ) { $show_empty_option = $ARGS{ShowEmptyOption}; } else { if ( $CustomField->MatchPattern('') ) { $show_empty_option = 1; } elsif ( $CustomField->SupportDefaultValues ) { my ( $on ) = grep { $_->isa( $CustomField->RecordClassFromLookupType ) } $CustomField->ACLEquivalenceObjects; my $default_values = $CustomField->DefaultValues( Object => $on || RT->System ); $show_empty_option = 1 unless defined $default_values && length $default_values; } } my $use_chosen = CachedCustomFieldValues($CustomField)->Count >= 10 ? 1 : 0; $m->callback( CallbackName => 'Chosen', UseChosen => \$use_chosen, CustomField => $CustomField ); # it's weird to see "(no value) X" in the input when selecting multiple values $show_empty_option = 0 if $use_chosen && $Multiple; <%ARGS> $Object => undef $CustomField => undef $NamePrefix => undef $Name => undef $BasedOnName => undef @Default => () $Default => undef $Values => undef $Multiple => 0 $Rows => undef $HideCategory => 0 $RenderType => undef $MaxValues => 1 <%METHOD options> % @Default = grep defined && length, @Default; % # $Values->HasEntry is too slow here % if ( !@Default && $Values ) { % @Default = map $_->Content, @{$Values->ItemsArrayRef}; % } % $_ = lc $_ foreach @Default; % my $selected; % my $CFVs = CachedCustomFieldValues($CustomField); % my @levels; % while ( my $value = $CFVs->Next ) { % my $name = $value->Name; % my $category = $value->Category || ''; % my $level = (split /:/, $category, 2)[0] || ''; % while (@levels) { % if ($levels[-1] eq $level) { % $level = ''; % last; % } elsif (index($level, $levels[-1]) != 0) { % $m->out(''); % pop @levels; % } else { % last; % } % } % if ( length $level ) { % push @$CategoryRef, [0+@levels, $level]; % push @levels, $level; % } % } % for (@levels) { % } <%ARGS> $CustomField => undef @Default => () $Values => undef $SelectedRef => undef $CategoryRef => undef rt-5.0.1/share/html/Elements/ShowPrincipal000644 000765 000024 00000005643 14005011336 021340 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# Released under the terms of version 2 of the GNU Public License <%args> $Object $PostUser => undef $Separator => ", " $Link => 1 <%init> if ($Object->isa("RT::Group")) { # Link the users (non-recursively) my @ret = map {$m->scomp("ShowPrincipal", Object => $_->[1], PostUser => $PostUser, Link => $Link)} sort {$a->[0] cmp $b->[0]} map {+[($_->EmailAddress||''), $_]} @{ $Object->UserMembersObj( Recursively => 0 )->ItemsArrayRef }; # Link to the group summary page my $href = RT->Config->Get("WebPath") . "/Group/Summary.html?id="; push @ret, sort map { "id . "\">" . loc("Group:") . " " . $_->Name . "" } @{ $Object->GroupMembersObj( Recursively => 0)->ItemsArrayRef }; $m->out( join($Separator, @ret) ); } else { $m->comp("/Elements/ShowUser", User => $Object, Link => $Link); $m->out( $PostUser->($Object) ) if $PostUser; } rt-5.0.1/share/html/Elements/LoginRedirectWarning000644 000765 000024 00000005351 14005011336 022632 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $next => undef <%init> return unless $next; my $destination = RT::Interface::Web::FetchNextPage($next); return unless ref $destination and $destination->{'HasSideEffects'}; my $consequence = RT::Interface::Web::PotentialPageAction($destination->{'url'}) || loc("perform actions"); $consequence = $m->interp->apply_escapes($consequence => "h");

      <&|/l&>After logging in you'll be sent to your original destination: <% $destination->{'url'} %> <&|/l_unsafe, "$consequence" &>which may [_1] on your behalf.

      <&|/l&>If this is not what you expect, leave this page now without logging in.

      rt-5.0.1/share/html/Elements/ColumnMap000644 000765 000024 00000025666 14005011336 020460 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Class => 'RT__Ticket' $Name $Attr => undef <%ONCE> use Scalar::Util; my ($COLUMN_MAP, $WCOLUMN_MAP); $WCOLUMN_MAP = $COLUMN_MAP = { id => { attribute => 'id', title => '#', # loc align => 'right', value => sub { return $_[0]->id } }, Created => { attribute => 'Created', title => 'Created', # loc value => sub { return $_[0]->CreatedObj->AsString } }, CreatedRelative => { attribute => 'Created', title => 'Created', # loc value => sub { return $_[0]->CreatedObj->AgeAsString } }, CreatedBy => { attribute => 'Creator', title => 'Created By', # loc value => sub { return $_[0]->CreatorObj->Name } }, LastUpdated => { attribute => 'LastUpdated', title => 'Last Updated', # loc value => sub { return $_[0]->LastUpdatedObj->AsString } }, LastUpdatedRelative => { attribute => 'LastUpdated', title => 'Last Updated', # loc value => sub { return $_[0]->LastUpdatedObj->AgeAsString } }, LastUpdatedBy => { attribute => 'LastUpdatedBy', title => 'Last Updated By', # loc value => sub { return $_[0]->LastUpdatedByObj->Name } }, CustomField => { attribute => sub { return shift @_ }, title => sub { return pop @_ }, value => sub { my $self = $WCOLUMN_MAP->{CustomField}; my $cf = $self->{load}->(@_); return unless $cf->Id; return $self->{render}->( $cf, $cf->ValuesForObject($_[0])->ItemsArrayRef ); }, load => sub { # Cache the CF object on a per-request basis, to avoid # having to load it for every row my $key = join("-","CF", $_[0]->CustomFieldLookupType, $_[0]->CustomFieldLookupId, $_[-1]); my $cf = $m->notes($key); unless ($cf) { $cf = $_[0]->LoadCustomFieldByIdentifier($_[-1]); RT->Logger->debug("Unable to load $_[-1] for ".$_[0]->CustomFieldLookupType." ".$_[0]->CustomFieldLookupId) unless $cf->Id; $m->notes($key, $cf); } return $cf; }, render => sub { my ($cf, $ocfvs) = @_; my $comp = $m->comp_exists("/Elements/ShowCustomField".$cf->Type) ? "/Elements/ShowCustomField".$cf->Type : undef; my @values = map { $comp ? \($m->scomp( $comp, Object => $_ )) : $_->Content } @$ocfvs; if (@values > 1) { for my $value (splice @values) { push @values, \"
    • ", $value, \"
    • \n"; } @values = (\"
        ", @values, \"
      "); } return @values; }, edit => sub { my $self = $WCOLUMN_MAP->{CustomField}; my $cf = $self->{load}->(@_); return unless $cf->Id; # uploading files should be done on the modify page return if $cf->Type =~ /^(?:Binary|Image)$/; return \($m->scomp('/Elements/EditCustomField', CustomField => $cf, Object => $_[0])) }, }, CustomRole => { attribute => sub { return shift @_ }, title => sub { return pop @_ }, value => sub { my $self = $WCOLUMN_MAP->{CustomRole}; my $role = $self->{load}->(@_); return unless $role->Id; return \($m->scomp("/Elements/ShowPrincipal", Object => $_[0]->RoleGroup($role->GroupType) ) ); }, load => sub { my $role_name = pop; # Cache the role object on a per-request basis, to avoid # having to load it for every row my $key = "RT::CustomRole-" . $role_name; my $role_obj = $m->notes($key); if (!$role_obj) { $role_obj = RT::CustomRole->new($_[0]->CurrentUser); $role_obj->Load($role_name); RT->Logger->notice("Unable to load custom role $role_name") unless $role_obj->Id; $m->notes($key, $role_obj); } return $role_obj; }, edit => sub { my $self = $WCOLUMN_MAP->{CustomRole}; my $role = $self->{load}->(@_); return unless $role->Id; if ($role->SingleValue) { return \($m->scomp("/Elements/SingleUserRoleInput", role => $role, Ticket => $_[0])); } else { return undef; } }, }, CheckBox => { title => sub { my $name = $_[1] || 'SelectedTickets'; my $checked = $DECODED_ARGS->{ $name .'All' }? 'checked="checked"': ''; return \qq{}; }, value => sub { my $id = $_[0]->id; my $name = $_[2] || 'SelectedTickets'; return \qq{} if $DECODED_ARGS->{ $name . 'All'}; my $arg = $DECODED_ARGS->{ $name }; my $checked = ''; if ( $arg && ref $arg ) { $checked = 'checked="checked"' if grep $_ == $id, grep { defined and length } @$arg; } elsif ( $arg ) { $checked = 'checked="checked"' if $arg == $id; } return \qq{} }, }, RadioButton => { title => \' ', value => sub { my $id = $_[0]->id; my $name = $_[2] || 'SelectedTicket'; my $arg = $DECODED_ARGS->{ $name }; my $checked = ''; $checked = 'checked="checked"' if $arg && $arg == $id; return \qq{}; }, }, (map { my $value = RT->Config->Get($_); $_ => { value => sub { return \$value } }; } qw(WebPath WebBaseURL WebURL)), WebRequestPath => { value => sub { substr( $m->request_path, 1 ) } }, WebRequestPathDir => { value => sub { substr( $m->request_comp->dir_path, 1 ) } }, WebHomePath => { value => sub { my $path = RT->Config->Get("WebPath"); if (not $session{CurrentUser}->Privileged) { $path .= "/SelfService"; } return \$path; }, }, CurrentUser => { value => sub { $session{CurrentUser}->id } }, CurrentUserName => { value => sub { $session{CurrentUser}->Name } }, }; $COLUMN_MAP->{'CF'} = $COLUMN_MAP->{'CustomField'}; # Add a CustomFieldView column for custom fields, but with editing disabled $COLUMN_MAP->{'CustomFieldView'} = {}; # We copy all keys from CF to CustomFieldView except for "edit" foreach my $key ( keys( %{ $COLUMN_MAP->{'CF'} } ) ) { next if $key eq 'edit'; $COLUMN_MAP->{'CustomFieldView'}->{$key} = $COLUMN_MAP->{'CF'}->{$key}; } Scalar::Util::weaken($WCOLUMN_MAP); my $ROLE_MAP = {}; <%INIT> $m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'Once', CallbackOnce => 1 ); my $generic_with_roles; # Add in roles my $RecordClass = $Class; $RecordClass =~ s/_/:/g; if ($RecordClass->DOES("RT::Record::Role::Roles")) { unless ($ROLE_MAP->{$RecordClass}) { # UserDefined => 1 is handled by the CustomRole mapping for my $role ($RecordClass->Roles(UserDefined => 0)) { my $attrs = $RecordClass->Role($role); $ROLE_MAP->{$RecordClass}{$role} = { title => $role, attribute => $attrs->{Column} || "$role.EmailAddress", value => sub { return \($m->scomp("/Elements/ShowPrincipal", Object => $_[0]->RoleGroup($role) ) ) }, }; $ROLE_MAP->{$RecordClass}{$role . "s"} = $ROLE_MAP->{$RecordClass}{$role} unless $attrs->{Single}; } } $generic_with_roles = { %{$COLUMN_MAP}, %{$ROLE_MAP->{$RecordClass}} }; } else { $generic_with_roles = { %{$COLUMN_MAP} }; } $m->callback( COLUMN_MAP => $generic_with_roles ); # first deal with class specific things if (RT::Interface::Web->ComponentPathIsSafe($Class) and $m->comp_exists("/Elements/$Class/ColumnMap")) { my $class_map = $m->comp("/Elements/$Class/ColumnMap", Attr => $Attr, Name => $Name, GenericMap => $generic_with_roles ); return $class_map if defined $class_map; } return GetColumnMapEntry( Map => $generic_with_roles, Name => $Name, Attribute => $Attr ); rt-5.0.1/share/html/Elements/EditCustomFieldAutocomplete000644 000765 000024 00000010004 14005011336 024147 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % if ( $Multiple ) { <%INIT> my $name = $Name || $NamePrefix . $CustomField->Id . ( $Multiple ? '-Values' : '-Value' ); if ( $Default && !$Multiple ) { $Default =~ s/\s*\r*\n\s*/ /g; } if ( $Multiple and $Values ) { $Default = ''; while (my $value = $Values->Next ) { $Default .= $value->Content ."\n"; } } my $Context = ""; if ($CustomField->ContextObject) { $Context .= "ContextId=" . $CustomField->ContextObject->Id . "&"; $Context .= "ContextType=". ref($CustomField->ContextObject) . "&"; } <%ARGS> $CustomField => undef $NamePrefix => undef $Name => undef $Default => undef $Values => undef $Multiple => undef $Rows => undef $Cols => undef rt-5.0.1/share/html/Elements/RT__Class/000755 000765 000024 00000000000 14005011336 020434 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/QuickCreate000644 000765 000024 00000010130 14005011336 020741 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <&| /Widgets/TitleBox, title => loc('Quick ticket creation') &>
      callback(CallbackName => 'InFormElement'); >
      <&|/l&>Subject:
      <&|/l&>Queue:
      <& /Elements/SelectNewTicketQueue, Name => 'Queue', Default => $args->{Queue} &>
      <&|/l&>Owner:
      <&|/l&>Requestors:
      <& /Elements/EmailInput, Name => 'Requestors', Size => '40', Default => $args->{Requestors} || $session{CurrentUser}->EmailAddress, AutocompleteMultiple => 1 &>
      <&|/l&>Content:
      <& /Elements/Submit, Name => 'SubmitTicket', Label => loc('Create') &>
      <%INIT> my $args = delete $session{QuickCreate} || {}; rt-5.0.1/share/html/Elements/EditLink000644 000765 000024 00000006026 14005011336 020255 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      % if ( $Mode eq 'Target' ) { % } else { % }
      <%INIT> my $ModeObj = $Mode . 'Obj'; return if UNIVERSAL::isa($Link->$ModeObj, 'RT::Article') && $Link->$ModeObj->Disabled; # Skip reminders return if $Mode eq 'Base' && $Link->Type eq 'RefersTo' && UNIVERSAL::isa($Link->BaseObj, 'RT::Ticket') && $Link->BaseObj->__Value('Type') eq 'reminder'; <%ARGS> $Link $Mode rt-5.0.1/share/html/Elements/EditCustomField000644 000765 000024 00000013463 14005011336 021601 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> my $Type = $CustomField->Type; unless ( $Type ) { # if we can't see the type, all hell will break loose. $RT::Logger->error( "Custom field #". $CustomField->id ." has empty type" ); return; } my $Values; if ( $Object ) { $Grouping =~ s/\W//g if $Grouping; if ( $Object->Id ) { $Values = $Object->CustomFieldValues( $CustomField->id ); $Values->Columns( qw( id CustomField ObjectType ObjectId Disabled Content ContentType ContentEncoding SortOrder Creator Created LastUpdatedBy LastUpdated ) ); # Don't take care of $Values if there isn't values inside undef ( $Values ) unless ( $Values->Count ); } } my $Name; if ( !$NamePrefix ) { $Name = GetCustomFieldInputName(Object => $Object, CustomField => $CustomField, Grouping => $Grouping ); } # Always fill $Default with submited values if it's empty if ( ( !defined $Default || !length $Default ) && $DefaultsFromTopArguments ) { my %TOP = %$DECODED_ARGS; $Default = $TOP{ $Name } if $Name; # check both -Values and -Value for back compatibility if ( $NamePrefix ) { $Default //= $TOP{ $NamePrefix . $CustomField->Id . '-Values' } // $TOP{ $NamePrefix . $CustomField->Id . '-Value' }; } else { my $prefix = GetCustomFieldInputNamePrefix(Object => $Object, CustomField => $CustomField, Grouping => $Grouping ); $Default //= $TOP{ $prefix . 'Values' } // $TOP{ $prefix . 'Value' }; } } if ( (!$Object || !$Object->id) && ( !defined $Default || !length $Default ) && $CustomField->SupportDefaultValues ) { my ( $on ) = grep {$_->isa($CustomField->RecordClassFromLookupType)} $CustomField->ACLEquivalenceObjects; $Default = $CustomField->DefaultValues(Object => $on || RT->System); } my $MaxValues = $CustomField->MaxValues; if ($MaxValues == 1 && $Values) { # what exactly is this doing? Without the "unless" it breaks RTFM # transaction extraction into articles. unless ( $Default ) { if ( $Values->First ) { if ( $CustomField->Type eq 'DateTime' ) { my $date = RT::Date->new($session{CurrentUser}); $date->Set(Format => 'ISO', Value => $Values->First->Content); $Default = $date->ISO(Timezone => 'user'); } else { $Default = $Values->First->Content; } } else { $Default = ''; } } $Values->GotoFirstItem; } # The "Magic" hidden input causes RT to know that we were trying to edit the field, even if # we don't see a value later, since browsers aren't compelled to submit empty form fields $m->out("\n".''."\n"); my $EditComponent = "EditCustomField$Type"; $m->callback( %ARGS, CallbackName => 'EditComponentName', Name => \$EditComponent, CustomField => $CustomField, Object => $Object, Rows => \$Rows, Cols => \$Cols); $EditComponent = "EditCustomField$Type" unless $m->comp_exists($EditComponent); return $m->comp( $EditComponent, %ARGS, Rows => $Rows, Cols => $Cols, Default => $Default, Object => $Object, Values => $Values, MaxValues => $MaxValues, Multiple => ($MaxValues != 1), NamePrefix => $NamePrefix, CustomField => $CustomField, Name => $Name, $CustomField->BasedOn && $Name ? ( BasedOnName => GetCustomFieldInputName(Object => $Object, CustomField => $CustomField->BasedOnObj, Grouping => $Grouping) ) : (), ); <%ARGS> $Grouping => undef $Object => undef $CustomField => undef $NamePrefix => undef $Rows => 5 $Cols => 15 $Default => undef $DefaultsFromTopArguments => 1, rt-5.0.1/share/html/Elements/RT__Queue/000755 000765 000024 00000000000 14005011336 020453 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/EditCustomFieldBinary000644 000765 000024 00000006467 14005011336 022754 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % while ( $Values and my $value = $Values->Next ) { %# XXX - let user download the file(s) here?
      % } % if ($MaxValues && $Values && $Values->Count >= $MaxValues ) {
      <&|/l&>Reached maximum number, so new values will override old ones.
      % }
      <%INIT> my $name = $Name || $NamePrefix . $CustomField->Id . '-Upload'; my $delete_name = $name; $delete_name =~ s!-Upload$!-DeleteValueIds!; <%ARGS> $Object => undef $CustomField => undef $NamePrefix => undef $Name => undef $Default => undef $Values => undef $MaxValues => undef rt-5.0.1/share/html/Elements/Footer000644 000765 000024 00000006603 14005011336 020011 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# End of div#body from /Elements/PageLayout
      % $m->callback( %ARGS ); % if ($Debug >= 2 ) { % require Data::Dumper; % my $d = Data::Dumper->new([\%ARGS], [qw(%ARGS)]);
      <%$d->Dump() %>
      
      % }
      <%ARGS> $Debug => 0 $Menu => 1 rt-5.0.1/share/html/Elements/SelectLang000644 000765 000024 00000005776 14005011336 020606 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $ShowNullOption => 1 $Name => undef $Verbose => undef $Default => 0 <%ONCE> use I18N::LangTags::List; my (@lang, %lang_to_desc); foreach my $lang (map { s/:://; s/_/-/g; $_ } grep { /^\w+::$/ } keys %RT::I18N::) { next if $lang =~ /i-default|en-us/; my $desc = I18N::LangTags::List::name($lang); next unless ($desc); $desc =~ s/(.*) (.*)/$2 ($1)/ unless ( $desc =~ /.* \(.*\)/ ); $lang_to_desc{$lang} = $desc; } @lang = sort { $lang_to_desc{$a} cmp $lang_to_desc{$b} } keys %lang_to_desc; rt-5.0.1/share/html/Elements/RefreshHomepage000644 000765 000024 00000004707 14005011336 021622 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&|/Widgets/TitleBox, title => loc('Refresh')&>
      <& /Elements/Refresh, Name => 'HomeRefreshInterval', Default => $session{'home_refresh_interval'}||RT->Config->Get('HomePageRefreshInterval', $session{'CurrentUser'}) &>
      <& /Elements/Submit, Label => loc('Go!') &>
      rt-5.0.1/share/html/Elements/SelectCustomFieldValue000644 000765 000024 00000006634 14005011336 023132 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $m->callback( Name => $Name, CustomField => $CustomField, Default => \$Default ); % $Default = "" unless defined $Default; % if ($CustomField->Type =~ /Select/i) { % my $values = CachedCustomFieldValues($CustomField); % } elsif ($CustomField->Type =~ /^Date(Time)?$/) { <& /Elements/SelectDate, ShowTime => ($1 ? 1 : 0), Name => $Name, Value => $Default &> % } elsif ( $CustomField->Type eq 'Autocomplete' ) { % } else { % } <%args> $Name => undef $CustomField => undef $Default => undef rt-5.0.1/share/html/Elements/QueueSummaryByLifecycle000644 000765 000024 00000011424 14005011336 023325 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%perl> foreach my $lifecycle ( map $lifecycle{$_}, sort keys %lifecycle ) { my @cur_statuses = grep $lifecycle->IsValid($_), @statuses; next unless @cur_statuses;
      % for my $status ( @cur_statuses ) { % } <%PERL> my $i = 0; for my $queue (@$queues) { next if lc($queue->{Lifecycle} || '') ne lc $lifecycle->Name; $i++; % for my $status (@cur_statuses) { % } % }
      <&|/l&>Queue<% loc($status) %>
      <% $queue->{Name} %> <% $data->{$queue->{Id}}->{lc $status} || '-' %>
      % } <%INIT> my $build_search_link = sub { my ($queue_name, $extra_query) = @_; $queue_name =~ s/(['\\])/\\$1/g; #' return RT->Config->Get('WebPath') . "/Search/Results.html?Query=" . $m->interp->apply_escapes("Queue = '$queue_name' AND $extra_query", 'u'); }; my $link_all = sub { my ($queue) = @_; return $build_search_link->($queue->{Name}, "Status = '__Active__'"); }; my $link_status = sub { my ($queue, $status) = @_; $status =~ s{(['\\])}{\\$1}g; return $build_search_link->($queue->{Name}, "Status = '$status'"); }; $m->callback( CallbackName => 'LinkBuilders', build_search_link => \$build_search_link, link_all => \$link_all, link_status => \$link_status, ); my %lifecycle; for my $queue (@$queues) { my $cycle = RT::Lifecycle->Load( Name => $queue->{'Lifecycle'} ); RT::Logger->error('Unable to load lifecycle for ' . $queue->{'Lifecycle'}) unless $cycle; $lifecycle{ lc $cycle->Name } = $cycle; } my @statuses; my %seen; foreach my $set ( 'initial', 'active' ) { foreach my $lifecycle ( map $lifecycle{$_}, sort keys %lifecycle ) { push @statuses, grep !$seen{ lc $_ }++, $lifecycle->Valid($set); } } my $data = {}; my $statuses = {}; use RT::Report::Tickets; my $report = RT::Report::Tickets->new( RT->SystemUser ); my $query = "(Status = '__Active__') 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 ) { $data->{ $entry->__Value("Queue") }->{ $entry->__Value("Status") } = $entry->__Value('Id'); $statuses->{ $entry->__Value("Status") } = 1; } <%ARGS> $queues => undef # Arrayref of hashes with cached queue info rt-5.0.1/share/html/Elements/CreateTicket000644 000765 000024 00000006017 14005011336 021121 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      callback(CallbackName => 'InFormElement'); > % if ($IncludeExplanation) {

      <&|/l&>Select a queue for your new ticket.

      % } % my $button_start = ''; % my $queue_selector = $m->scomp('/Elements/SelectNewTicketQueue', AutoSubmit => 1, SendTo => $SendTo, Placeholder => loc('Queue'), Hyperlink => $Hyperlink ); % if ($Hyperlink) { <% $queue_selector |n %> % }
      <&|/l_unsafe, $button_start, $button_end &>[_1]Create new ticket[_2]
      <&|/l_unsafe, $button_start, $button_end &>[_1]Create[_2]
      <&|/l_unsafe, $button_start, $button_end &>[_1]+[_2]
      % $m->callback(CallbackName => 'BeforeFormEnd');
      <%ARGS> $SendTo => '/Ticket/Create.html', $IncludeExplanation => 0 $Hyperlink => undef rt-5.0.1/share/html/Elements/SelectOwner000644 000765 000024 00000005700 14005011336 021002 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& "SelectOwner$Widget", %ARGS, Objects => \@objects &> <%INIT> my $Widget; if ( !$QueueObj && !$TicketObj && RT->Config->Get('AutocompleteOwnersForSearch') ) { $Widget = 'Autocomplete'; } $Widget ||= RT->Config->Get('AutocompleteOwners', $session{'CurrentUser'}) ? 'Autocomplete' : 'Dropdown'; my @objects; if ($TicketObj) { @objects = ($TicketObj); } elsif ($QueueObj) { @objects = ($QueueObj); } elsif (%Queues) { for my $name (keys %Queues) { my $q = RT::Queue->new($session{'CurrentUser'}); $q->Load($name); push @objects, $q; } } else { # Let's check rights on an empty queue object. that will do a search # for any queue. my $queue = RT::Queue->new( $session{'CurrentUser'} ); push( @objects, $queue ); } $m->callback( %ARGS, objects => \@objects, CallbackName => 'UpdateObjectList' ); <%ARGS> $TicketObj => undef $QueueObj => undef %Queues => () rt-5.0.1/share/html/Elements/MessageBox000644 000765 000024 00000013071 14005011336 020605 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % } % else { <% $Default || '' %><% $message %><% $signature %> % } % $m->callback( %ARGS, CallbackName => 'AfterTextArea' ); % if (!$SuppressAttachmentWarning) { % $m->comp('/Elements/AttachmentWarning', QuotedMessage => $message, Signature => $signature, %ARGS); % } % if ($Type eq 'text/html') { " /> % } <%INIT> my $message = ''; if ( $QuoteTransaction ) { my $transaction = RT::Transaction->new( $session{'CurrentUser'} ); $transaction->Load( $QuoteTransaction ); $message = $transaction->Content( Quote => 1, Type => $Type ); } my $signature = $session{'CurrentUser'}->UserObj->Signature // ""; if ( $IncludeSignature and $signature =~ /\S/ ) { $signature =~ s/\n*$//; if ($Type eq 'text/html') { $signature =~ s/&/&/g; $signature =~ s//>/g; $signature =~ s/"/"/g; # "//; $signature =~ s/'/'/g; # '//; $signature =~ s{\n}{
      }g; $signature = "

      -- 
      $signature

      "; } else { $signature = "\n\n-- \n". $signature . "\n"; } if ($message =~ /\S/) { if (RT->Config->Get('SignatureAboveQuote', $session{CurrentUser})) { $signature .= $Type eq 'text/html' ? "
      " : "\n"; } else { $signature = ($Type eq 'text/html' ? "" : "\n") . $signature; } } } else { $signature = ''; } my $article_id; if ( $IncludeDefaultArticle && defined $QueueObj && $QueueObj->Id ) { # Load a default article $article_id = $QueueObj->DefaultValue('Article') if $QueueObj->DefaultValue('Article'); } else { # Load from the page, if provided $article_id = $ARGS{'IncludeArticleId'} if $ARGS{'IncludeArticleId'}; } # wrap="something" seems to really break IE + richtext my $wrap_type = $Type eq 'text/html' ? '' : 'wrap="soft"'; # If there's no cols specified, we want to set the width to 100% in CSS my $width_attr; if ($Width) { $width_attr = 'cols'; } else { $width_attr = 'style'; $Width = 'width: 100%'; } <%ARGS> $QuoteTransaction => undef $Name => 'Content' $Default => '' $Width => RT->Config->Get('MessageBoxWidth', $session{'CurrentUser'} ) $Height => RT->Config->Get('MessageBoxHeight', $session{'CurrentUser'} ) || 15 $IncludeSignature => RT->Config->Get('MessageBoxIncludeSignature'); $IncludeArticle => 1; $Type => RT->Config->Get('MessageBoxRichText', $session{'CurrentUser'}) ? 'text/html' : 'text/plain'; $SuppressAttachmentWarning => 0 $Placeholder => loc('Type your message here') $IncludeDefaultArticle => 0 # Preload a default article based on queue settings $QueueObj => undef rt-5.0.1/share/html/Elements/RT__Article/000755 000765 000024 00000000000 14005011336 020752 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/Section000644 000765 000024 00000004047 14005011336 020157 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}

      <%$title%>

      <%ARGS> $title => undef rt-5.0.1/share/html/Elements/SelectResultsPerPage000644 000765 000024 00000004711 14005011336 022616 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# TODO: Better default handling <%INIT> my @values = qw(0 10 25 50 100); my @labels = (loc('Unlimited'), qw(10 25 50 100)); $Default = 50 unless defined $Default && $Default =~ /^\d+$/; <%ARGS> $Name => undef $Default => 50 rt-5.0.1/share/html/Elements/Framekiller000644 000765 000024 00000004754 14005011336 021015 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % if ( RT->Config->Get('Framebusting') ) { %# This is defeatable. The current best known implemention uses CSS to hide %# the content and JS to re-show it, but that fails poorly for clients that %# don't run JS. % } rt-5.0.1/share/html/Elements/ShowHistoryPage000644 000765 000024 00000014324 14005011336 021651 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Object $Transactions => $Object->SortedTransactions $Attachments => $Object->Attachments( WithHeaders => 1 ) $AttachmentContent => $Object->TextAttachments $ShowHeaders => 0 $PathPrefix => '' <%INIT> my $trans_content = {}; my $trans_attachments = {}; if ( $ARGS{'ReverseTxns'} ) { $Transactions = $Object->SortedTransactions($ARGS{'ReverseTxns'}); } for my $content (@{$AttachmentContent->ItemsArrayRef()}) { $trans_content->{$content->TransactionId}->{$content->Id} = $content; } for my $attachment (@{$Attachments->ItemsArrayRef()}) { my $tmp = $trans_attachments->{ $attachment->TransactionId } ||= {}; push @{ $tmp->{ $attachment->Parent || 0 } ||= [] }, $attachment; } { my %tmp = ( AttachmentPath => 'Attachment', UpdatePath => 'Update.html', ForwardPath => 'Forward.html', EmailRecordPath => 'ShowEmailRecord.html', EncryptionPath => 'Crypt.html', ); my $prefix = $ARGS{PathPrefix}||''; while ( my ($arg, $path) = each %tmp ) { next if defined $ARGS{ $arg }; $ARGS{ $arg } = $prefix.$path; } } my $HasTxnCFs = ($Object->can("TransactionCustomFields") and $Object->TransactionCustomFields->Count); <%perl> my $i = 1; while ( my $Transaction = $Transactions->Next ) { my $skip = 0; # Skip display of SetWatcher transactions for ticket Owner groups. Owner # was a single member role group and denormalized into a column well before # the generic role group handling and transactions came about. For # tickets, we rely on rendering ownership changes using the Set-Owner # transaction. For all other record types, or even potential ticket single # role groups which aren't Owner, we use SetWatcher to render history and # skip the Set transactions. This complication is necessary to avoid # creating backdated transactions on upgrade which normalize to one type or # another. # # These conditions assumes ticket Owner is a single-member denormalized # role group, which is safe since that is unlikely to ever change in the # future. if ($Object->isa("RT::Ticket") and ($Transaction->Field || '') eq "Owner") { $skip = 1 if $Transaction->Type eq "SetWatcher"; } else { $skip = 1 if $Transaction->Type eq "Set" and $Transaction->Field and $Object->DOES("RT::Record::Role::Roles") and $Object->HasRole( $Transaction->Field ) and $Object->RoleGroup( $Transaction->Field )->SingleMemberRoleGroupColumn; } # Skip Time Worked fields if user is unprivileged and # HideTimeFieldsFromUnprivilegedUsers is set. $skip = 1 if $Object->isa("RT::Ticket") and not $Object->CurrentUserCanSeeTime and ($Transaction->Field || '') =~ /^Time(?:Estimated|Worked|Left)$/; $skip = 1 if $m->request_path =~ m{^/SelfService/} and RT::Config->Get('SelfServiceCorrespondenceOnly') and ($Transaction->Type ne "Correspond" && $Transaction->Type ne "Create"); $m->callback( %ARGS, Transaction => $Transaction, skip => \$skip, CallbackName => 'SkipTransaction', ); next if $skip; # ARGS is first because we're clobbering the "Attachments" parameter $m->comp( 'ShowTransaction', %ARGS, Object => $Object, Transaction => $Transaction, ShowHeaders => $ShowHeaders, RowNum => $i, Attachments => $trans_attachments->{$Transaction->id} || {}, AttachmentContent => $trans_content, HasTxnCFs => $HasTxnCFs, ); # manually flush the content buffer after each txn, # so the user sees some update $m->flush_buffer; $i++; } # For scroll, it's possible that all the transactions in this page were # skipped for some reasons and there are still pages left, so we need to # inform AJAX request that it's not done yet. if ( $i == 1 and RT->Config->Get( "ShowHistory", $session{'CurrentUser'} ) eq 'scroll' and my $txn = $Transactions->Last ) { $m->out( q{\n} ); } rt-5.0.1/share/html/Elements/SelectCustomDateRangeField000644 000765 000024 00000004627 14005011336 023710 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Name => '' $Default => '' $ObjectType => 'RT::Ticket' rt-5.0.1/share/html/Elements/EditPassword000644 000765 000024 00000006017 14005011336 021162 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % unless ( $cond{'CanSet'} ) { <% $cond{'Reason'} %>
      % } else { % if ( $cond{'RequireCurrent'} ) {
      <&|/l, $session{'CurrentUser'}->Name()&>[_1]'s current password:
      % }
      <&|/l&>New password:
      <&|/l&>Retype Password:
      % } <%ARGS> $User @Name => qw(CurrentPass NewPass1 NewPass2) <%INIT> my %cond = $User->CurrentUserRequireToSetPassword; rt-5.0.1/share/html/Elements/GotoUser000644 000765 000024 00000005552 14005011336 020324 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <%ARGS> $Default => '' $Cols => 4 rt-5.0.1/share/html/Elements/SelectAttachmentField000644 000765 000024 00000005073 14005011336 022747 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Class => 'RT::Tickets' $Name => 'AttachmentField' rt-5.0.1/share/html/Elements/ShowCustomFields000644 000765 000024 00000014064 14005011336 022015 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $m->callback( CallbackName => 'BeforeCustomFields', Object => $Object, % Grouping => $Grouping, ARGSRef => \%ARGS, CustomFields => $CustomFields, Table => $Table ); % if ($Table) {
      % } % while ( my $CustomField = $CustomFields->Next ) { % my $Values = $Object->CustomFieldValues( $CustomField->Id ); % my $count = $Values->Count; % next if $HideEmpty and not $count; % my $CustomFieldName = $CustomField->Name; %#The following substitution replaces all non-ID_Continue characters with a dash character. The ID_Continue Unicode property was chosen because it (combined with ID_Start) is meant for variable names. ID_Continue includes characters suitable for use in CSS-class names (even non-Latin ones, to support non-English custom field names) and excludes syntactic characters that are not (such as whitespace characters). % $CustomFieldName =~ s/\P{ID_Continue}+/-/g; % my @classes = ( % 'custom-field', % 'custom-field-'.$CustomField->id, % 'custom-field-'.$CustomFieldName, % ); % push @classes, 'unset-field' if not $count; % $m->callback( CallbackName => 'ModifyFieldClasses', CustomField => $CustomField, % Object => $Object, Classes => \@classes, Grouping => $Grouping );
      <% $CustomField->Name %>:
      % unless ( $count ) { <&|/l&>(no value) % } elsif ( $count == 1 ) { % $print_value->( $CustomField, $Values->First ); % } else {
        % while ( my $Value = $Values->Next ) {
      • % $print_value->( $CustomField, $Value );
      • % }
      % }
      % $m->callback( CallbackName => 'AfterCustomFieldValue', CustomField => $CustomField, % Object => $Object, Grouping => $Grouping, Table => $Table );
      % } % if ($Table) {
      % } % $m->callback( CallbackName => 'AfterCustomFields', Object => $Object, % Grouping => $Grouping, ARGSRef => \%ARGS, Table => $Table ); <%INIT> $m->callback( %ARGS, CallbackName => 'MassageCustomFields', Object => $Object, CustomFields => $CustomFields, Table => $Table, ); $CustomFields->LimitToGrouping( $Object => $Grouping ) if defined $Grouping; # don't print anything if there is no custom fields return unless $CustomFields->First; $CustomFields->GotoFirstItem; my $print_value = sub { my ($cf, $value) = @_; my $linked = $value->LinkValueTo; if ( defined $linked && length $linked ) { my $linked = $m->interp->apply_escapes( $linked, 'h' ); $m->out(''); } my $comp = "ShowCustomField". $cf->Type; $m->callback( CallbackName => 'ShowComponentName', Name => \$comp, CustomField => $cf, Object => $Object, Table => $Table, ); if ( $m->comp_exists( $comp ) ) { $m->comp( $comp, Object => $value ); } else { $m->out( $m->interp->apply_escapes( $value->Content, 'h' ) ); } $m->out('') if defined $linked && length $linked; # This section automatically populates a div with the "IncludeContentForValue" for this custom # field if it's been defined if ( $cf->IncludeContentForValue ) { my $vid = $value->id; $m->out( '\n} ); $m->out( qq{\n} ); } }; <%ARGS> $Object => undef $CustomFields => $Object->CustomFields $Grouping => undef $Table => 1 $HideEmpty => 0 rt-5.0.1/share/html/Elements/AuthToken/000755 000765 000024 00000000000 14005011336 020525 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/Submit000644 000765 000024 00000010415 14005011336 020012 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      % if ($CheckAll) { % } % if ($ClearAll) { % } % if ($Reset) { % }
      % if ( $Back ) { % } % if ( $Back ) {
      <%$BackCaption%> <% $BackName ? qq[ name="$BackName"] : '' | n %> value="<%$BackLabel%>" class="button btn btn-primary form-control" />
      % }
      <%ARGS> $color => undef $Caption => '' $AlternateCaption => undef $AlternateLabel => undef $Label => loc('Submit') $Name => undef $CheckAll => undef $CheckAllLabel => loc('Check All') $ClearAll => undef $ClearAllLabel => loc('Clear All') $CheckboxName => '' $CheckboxNameRegex => '' $Back => undef $BackName => 'Back' $BackLabel => loc('Back') $BackCaption => '' $BackOnClick => undef $OnClick => undef $Reset => undef $ResetLabel => loc('Reset') $SubmitId => undef $id => undef <%init> my $match; if (length $CheckboxName) { $match = $m->interp->apply_escapes($CheckboxName,'j'); } elsif (length $CheckboxNameRegex) { $match = $CheckboxNameRegex; } else { $match = q{''}; } rt-5.0.1/share/html/Elements/EditCustomDateRanges000644 000765 000024 00000012364 14005011336 022572 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %# TODO selectpicker options exceeding the table are invisible in .table-responsive %#
      % my $i = 0; % if ( keys %CustomDateRanges ) { % my $id = 0; % for my $name ( sort keys %CustomDateRanges ) { % $i++; % my %date_range_spec = $ObjectType->_ParseCustomDateRangeSpec($name, $CustomDateRanges{$name}); % $id++; % } % } % for ( 1 .. 3 ) { % $i++; % }
      <&|/l&>Name <&|/l&>From <&|/l&>From Value
      if Unset
      <&|/l&>To <&|/l&>To Value
      if Unset
      <&|/l&>Business
      Hours?
      <&|/l&>Delete
      <& /Elements/SelectCustomDateRangeField, Name => "$id-from", Default => $date_range_spec{from} &> <& /Elements/SelectCustomDateRangeField, Name => "$id-from_fallback", Default => $date_range_spec{from_fallback} &> <& /Elements/SelectCustomDateRangeField, Name => "$id-to", Default => $date_range_spec{to} &> <& /Elements/SelectCustomDateRangeField, Name => "$id-to_fallback", Default => $date_range_spec{to_fallback} &>
      <& /Elements/SelectCustomDateRangeField, Name => 'from' &> <& /Elements/SelectCustomDateRangeField, Name => 'from_fallback' &> <& /Elements/SelectCustomDateRangeField, Name => 'to' &> <& /Elements/SelectCustomDateRangeField, Name => 'to_fallback' &>
      <%ARGS> %CustomDateRanges => () $ObjectType => 'RT::Ticket' rt-5.0.1/share/html/Elements/CollectionList000644 000765 000024 00000017620 14005011336 021503 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> if (!$Collection) { $Collection = $Class->new( $session{'CurrentUser'} ); if ( $Class eq 'RT::Transactions' ) { my @limits = ( "ObjectType = '$ObjectType'", $Query ? "($Query)" : () ); if ( $ObjectType eq 'RT::Ticket' ) { unshift @limits, "TicketType = 'ticket'"; } $Query = join ' AND ', @limits; } $Collection->FromSQL($Query); } elsif (!$Collection && $Class eq 'RT::Assets') { $Collection = RT::Assets->new( $session{'CurrentUser'} ); $Collection->FromSQL($Query); } # flip HasResults from undef to 0 to indicate there was a search, so # dashboard mail can be suppressed if there are no results $$HasResults = 0 if $HasResults && !defined($$HasResults); $TotalFound = $Collection->CountAll() unless defined $TotalFound; return '' if !$TotalFound && !$ShowEmpty; if ( $Rows ) { if ( $TotalFound <= $Rows ) { $Page = 1; } else { my $MaxPage = int( $TotalFound / $Rows ) + ( $TotalFound % $Rows ? 1 : 0 ); $Page = $MaxPage if $Page > $MaxPage; } } # XXX: ->{'order_by'} is hacky, but there is no way to check if # collection is ordered or not if ( @OrderBy && ($AllowSorting || $PreferOrderBy || !$Collection->{'order_by'}) ) { if ( $OrderBy[0] =~ /\|/ ) { @OrderBy = split /\|/, $OrderBy[0]; @Order = split /\|/,$Order[0]; } @OrderBy = grep length, @OrderBy; $Collection->OrderByCols( map { { FIELD => $OrderBy[$_], ORDER => $Order[$_] } } ( 0 .. $#OrderBy ) ); } $Collection->RowsPerPage( $Rows ) if $Rows; $Page = 1 unless $Page && $Page > 0; # workaround problems with $Page = '' or undef $Collection->GotoPage( $Page - 1 ); # SB uses page 0 as the first page # DisplayFormat lets us use a "temporary" format for display, while # still using our original format for next/prev page links. # bulk update uses this feature to add checkboxes $DisplayFormat ||= $Format; # Scrub the html of the format string to remove any potential nasties. $Format = $m->comp('/Elements/ScrubHTML', Content => $Format); $DisplayFormat = $m->comp('/Elements/ScrubHTML', Content => $DisplayFormat); my @Format = $m->comp('/Elements/CollectionAsTable/ParseFormat', Format => $DisplayFormat); # Find the maximum number of items in any row, so we can pad the table. my ($maxitems, $item) = (0, 0); foreach my $col (@Format) { $item++; if ( $col->{title} && ($col->{title} eq 'NEWLINE') ) { $item = 0; } else { $maxitems = $item if $item > $maxitems; } } $Class ||= $Collection->ColumnMapClassName; $InlineEdit = 0 unless $Collection->isa('RT::Tickets'); $m->out('
      '); $m->out('out(' class="table ' . ($Collection->isa('RT::Tickets') ? 'ticket-list' : 'collection') . ($InlineEdit ? ' inline-edit' : '') . ' collection-as-table"'); $m->out(' data-display-format="' . $m->interp->apply_escapes($DisplayFormat, 'h') . '"'); $m->out(' data-max-items="' . $maxitems . '"'); $m->out(' data-class="' . $Collection->RecordClass . '"'); $m->out('>'); if ( $ShowCols ) { $m->out(''); $m->out('') for 1 .. $maxitems; $m->out(''); } if ( $ShowHeader ) { $m->comp('/Elements/CollectionAsTable/Header', %ARGS, Class => $Class, Format => \@Format, FormatString => $Format, Order => \@Order, OrderBy => \@OrderBy, Rows => $Rows, Page => $Page, AllowSorting => $AllowSorting, BaseURL => $BaseURL, GenericQueryArgs => $GenericQueryArgs, maxitems => $maxitems, PassArguments => \@PassArguments, ); } my ($i, $column_map) = (0, {}); while ( my $record = $Collection->Next ) { # Every ten rows, flush the buffer and put something on the page. $m->flush_buffer unless ++$i % 10; my $warning = 0; my $Classes = ''; $m->callback( CallbackName => 'EachRow', Record => $record, Warning => \$warning, Classes => \$Classes, Format => \@Format, ); $m->comp('/Elements/CollectionAsTable/Row', i => $i, Format => \@Format, record => $record, maxitems => $maxitems, ColumnMap => $column_map, Class => $Class, Warning => $warning, Classes => $Classes, InlineEdit => $InlineEdit, ); $$HasResults++ if $HasResults; } $m->out('
      '); $m->out('
      '); if ( $Rows && $ShowNavigation && $TotalFound > $Rows ) { my $oddRows = ($TotalFound && $TotalFound % $Rows == 0 )? 0 : 1; my $pages = int( $TotalFound / $Rows ) + $oddRows; $pages = 1 if $pages < 1; my %query_args = map { $_ => $ARGS{$_} } @PassArguments; $m->comp( '/Elements/CollectionListPaging', BaseURL => $BaseURL, Rows => $Rows, TotalFound => $TotalFound, CurrentPage => $Page, Pages => $pages, PageParam => $PageParam, URLParams => \%query_args ); } <%ARGS> $Class => '' $Collection => undef $TotalFound => undef $Format => undef $DisplayFormat => undef @Order => () @OrderBy => () $GenericQueryArgs => undef $Rows => undef $Page => 1 $PageParam => 'Page' $Title => loc('Ticket Search') $BaseURL => RT->Config->Get('WebPath') . $m->request_comp->path .'?' @PassArguments => qw( Query Format Rows Page Order OrderBy Class ObjectType ) $AllowSorting => 0 # Make headers in table links that will resort results $PreferOrderBy => 0 # Prefer the passed-in @OrderBy to the collection default $ShowNavigation => 1 $ShowHeader => 1 $ShowCols => 1 $ShowEmpty => 0 $InlineEdit => RT->Config->Get('InlineEdit', $session{CurrentUser}) $Query => 0 $HasResults => undef $ObjectType => $Class eq 'RT::Transactions' ? 'RT::Ticket' : '' rt-5.0.1/share/html/Elements/ShowArticleCustomFields000644 000765 000024 00000011560 14005011336 023317 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $m->callback( CallbackName => 'BeforeCustomFields', Object => $Object, % ARGSRef => \%ARGS, CustomFields => $CustomFields);
      % while ( my $CustomField = $CustomFields->Next ) { % my $Values = $Object->CustomFieldValues( $CustomField->Id ); % my $count = $Values->Count; % next if $HideEmpty and not $count; % my $CustomFieldName = $CustomField->Name; %#The following substitution replaces all non-ID_Continue characters with a dash character. The ID_Continue Unicode property was chosen because it (combined with ID_Start) is meant for variable names. ID_Continue includes characters suitable for use in CSS-class names (even non-Latin ones, to support non-English custom field names) and excludes syntactic characters that are not (such as whitespace characters). % $CustomFieldName =~ s/\P{ID_Continue}+/-/g; % my @classes = ( % 'custom-field', % 'custom-field-'.$CustomField->id, % 'custom-field-'.$CustomFieldName, % 'article-custom-field', % ); % push @classes, 'unset-field' if not $count; % $m->callback( CallbackName => 'ModifyFieldClasses', CustomField => $CustomField, % Object => $Object, Classes => \@classes );
      % if ($HideFieldNames->{$CustomField->id}) {
      %} else {
      <% $CustomField->Name %>:
      % } % unless ( $count ) { <&|/l&>(no value) % } elsif ( $count == 1 ) { % $print_value->( $CustomField, $Values->First ); % } else {
        % while ( my $Value = $Values->Next ) {
      • % $print_value->( $CustomField, $Value );
      • % }
      % }
      % $m->callback( CallbackName => 'AfterCustomFieldValue', CustomField => $CustomField, % Object => $Object );
      % }
      % $m->callback( CallbackName => 'AfterCustomFields', Object => $Object, % ARGSRef => \%ARGS ); <%INIT> $m->callback( %ARGS, CallbackName => 'MassageCustomFields', Object => $Object, CustomFields => $CustomFields, ); # don't print anything if there are no custom fields return unless $CustomFields->First; $CustomFields->GotoFirstItem; my $print_value = sub { my ($cf, $value) = @_; my $comp = "ShowCustomField". $cf->Type; $m->callback( CallbackName => 'ShowComponentName', Name => \$comp, CustomField => $cf, Object => $Object, ); if ( $m->comp_exists( $comp ) ) { $m->comp( $comp, Object => $value ); } else { $m->out( $m->interp->apply_escapes( $value->Content, 'h' ) ); } }; <%ARGS> $Object => undef $CustomFields => $Object->CustomFields $HideEmpty => 0 $HideFieldNames => {} rt-5.0.1/share/html/Elements/RT__SavedSearch/000755 000765 000024 00000000000 14005011336 021557 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/ShowCustomDateRanges000644 000765 000024 00000006415 14005011336 022625 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      % my $i = 0; % for my $name ( sort keys %CustomDateRanges ) { % $i++; % my %date_range_spec = $ObjectType->_ParseCustomDateRangeSpec($name, $CustomDateRanges{$name}); % }
      <&|/l&>Name <&|/l&>From <&|/l&>From Value
      if Unset
      <&|/l&>To <&|/l&>To Value
      if Unset
      <&|/l&>Business
      Hours?
      <% $name %><% $date_range_spec{from} %> <% $date_range_spec{from_fallback} || '' %> <% $date_range_spec{to} %> <% $date_range_spec{to_fallback} || '' %> <% $date_range_spec{business_time} ? loc('Yes') : loc('No') %>
      <%ARGS> %CustomDateRanges => () $ObjectType => 'RT::Ticket' rt-5.0.1/share/html/Elements/ValidateCustomFields000644 000765 000024 00000012056 14005011336 022625 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> my ($valid, @res) = (1, ()); $CustomFields->GotoFirstItem; my $CFArgs = _ParseObjectCustomFieldArgs( $ARGSRef )->{ref($Object)}{$Object->Id || 0} || {}; while ( my $CF = $CustomFields->Next ) { my $submitted = $CFArgs->{$CF->Id}; # Pick the first grouping $submitted = $submitted ? $submitted->{(keys %$submitted)[0]} : {}; # If we don't have a value and we don't see the Magic, then we're not # submitting a field. next if not $ValidateUnsubmitted and not exists $submitted->{"Value"} and not exists $submitted->{"Upload"} and not exists $submitted->{"Values"} and not $submitted->{"Values-Magic"}; # We only validate Single Combos -- multis can never be user input next if $submitted->{"Values-Magic"} and exists $submitted->{"Values"} and ref $submitted->{"Values"}; $m->notes(('Field-' . $CF->Id) => $submitted->{Values} // $submitted->{Value}); my @values = _NormalizeObjectCustomFieldValue( CustomField => $CF, Value => ($submitted->{Values} // $submitted->{Value} // $submitted->{Upload}), ); if ($CF->Type =~ /^Date(?:Time)?$/) { @values = grep { my $DateObj = RT::Date->new ( $session{'CurrentUser'} ); $DateObj->Set( Format => 'unknown', Value => $_, ($CF->Type eq "Date" ? (Timezone => 'utc') : ()) ); $DateObj->IsSet } @values; } push @values, '' unless @values; for my $value( @values ) { if ($value) { my $ref = { Content => $value }; my ($ok, $msg) = $CF->_CanonicalizeValue( $ref ); unless ($ok) { $m->notes( ( 'InvalidField-' . $CF->Id ) => $msg ); push @res, $CF->Name .': '. $msg; $valid = 0; } } if (!$CF->MatchPattern($value)) { my $msg = loc("Input must match [_1]", $CF->FriendlyPattern); $m->notes( ('InvalidField-' . $CF->Id) => $msg ); push @res, $CF->Name .': '. $msg; $valid = 0; } if ($CF->UniqueValues) { my $existing = RT::ObjectCustomFieldValues->new(RT->SystemUser); $existing->LimitToCustomField($CF->Id); $existing->LimitToEnabled; $existing->Limit(FIELD => 'ObjectType', VALUE => ref($Object)); $existing->Limit(FIELD => 'ObjectId', VALUE => $Object->id, OPERATOR => '!='); $existing->Limit( FIELD => 'Content', VALUE => $value, ); while (my $ocfv = $existing->Next) { my $msg = loc("That is not a unique value"); $m->notes( ('InvalidField-' . $CF->Id) => $msg ); push @res, $CF->Name .': '. $msg; $valid = 0; last; } } } } $m->notes('ValidFields', $valid); return wantarray? ($valid, @res): $valid; <%ARGS> $Object => RT::Ticket->new( $session{'CurrentUser'}) $CustomFields $ARGSRef $ValidateUnsubmitted => 0 rt-5.0.1/share/html/Elements/GotoTicket000644 000765 000024 00000004301 14005011336 020620 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
       
      rt-5.0.1/share/html/Elements/AttachmentWarning000644 000765 000024 00000004420 14005011336 022164 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <% $QuotedMessage %> <% $Signature %>
      <&|/l&>It looks like you may have forgotten to add an attachment.
      <%ARGS> $QuotedMessage => '' $Signature => '' rt-5.0.1/share/html/Elements/FoldStanzaJS000644 000765 000024 00000004257 14005011336 021060 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <%loc('Show quoted text')%>
      rt-5.0.1/share/html/Elements/PersonalQuickbar000644 000765 000024 00000004423 14005011336 022016 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Prefs => '/Prefs/Other.html'
      <&|/l&>Skip Menu | % unless ($session{'CurrentUser'}->Id) { <&|/l&>Not logged in. % } % $m->callback( %ARGS );
      rt-5.0.1/share/html/Elements/EditCustomFieldDate000644 000765 000024 00000005202 14005011336 022367 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % my $name = $Name || $NamePrefix.$CustomField->Id.'-Values';
      <& /Elements/SelectDate, Name => $name, Default => $Default, current => 0, ShowTime => 0 &>
      (<%$DateObj->AsString(Time => 0, Timezone => 'utc')%>)
      <%INIT> my $DateObj = RT::Date->new ( $session{'CurrentUser'} ); $DateObj->Set( Format => 'unknown', Value => $Default, Timezone => 'utc' ); <%ARGS> $Object => undef $CustomField => undef $NamePrefix => undef $Default => undef $Values => undef $MaxValues => 1 $Name => undef rt-5.0.1/share/html/Elements/BulkLinks000644 000765 000024 00000016174 14005011336 020455 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}

      <&|/l&>Current Links

      <&|/l&>Depends on:
      % if ( $hash{DependsOn} ) { % for my $link ( values %{$hash{DependsOn}} ) { <& EditLink, Link => $link, Mode => 'Target' &> % } }
      <&|/l&>Depended on by:
      % if ( $hash{DependedOnBy} ) { % for my $link ( values %{$hash{DependedOnBy}} ) { <& EditLink, Link => $link, Mode => 'Base' &> % } }
      <&|/l&>Parents:
      % if ( $hash{MemberOf} ) { % for my $link ( values %{$hash{MemberOf}} ) { <& EditLink, Link => $link, Mode => 'Target' &> % } }
      <&|/l&>Children:
      % if ( $hash{Members} ) { % for my $link ( values %{$hash{Members}} ) { <& EditLink, Link => $link, Mode => 'Base' &> % } }
      <&|/l&>Refers to:
      % if ( $hash{RefersTo} ) { % for my $link ( values %{$hash{RefersTo}} ) { <& EditLink, Link => $link, Mode => 'Target' &> % } }
      <&|/l&>Referred to by:
      % if ( $hash{ReferredToBy} ) { % for my $link ( values %{$hash{ReferredToBy}} ) { <& EditLink, Link => $link, Mode => 'Base' &> % } }
      <&|/l&>(Check box to delete)

      <&|/l&>New Links

      <&|/l&>Enter tickets or URIs to link to. Separate multiple entries with spaces.
      <&|/l&>Depends on:
      <&|/l&>Depended on by:
      <&|/l&>Parents:
      <&|/l&>Children:
      <&|/l&>Refers to:
      <&|/l&>Referred to by:
      <%ARGS> $Collection <%INIT> my @types = qw/DependsOn DependedOnBy Members MemberOf RefersTo ReferredToBy/; my $record_type = $Collection->RecordClass; $record_type =~ s/^RT:://; $record_type =~ s/::/-/g; my %hash; if ( $Collection->Count ) { my $first_record = $Collection->Next; # we only show current links that exist on all the records for my $type ( @types ) { my $target_or_base = $type =~ /DependsOn|MemberOf|RefersTo/ ? 'Target' : 'Base'; my $links = $first_record->$type; while ( my $link = $links->Next ) { $hash{$type}{$link->$target_or_base} = $link; } } while ( my $record = $Collection->Next ) { for my $type ( @types ) { my $target_or_base = $type =~ /DependsOn|MemberOf|RefersTo/ ? 'Target' : 'Base'; # if $hash{$type} is empty, no need to check any more next unless $hash{$type} && keys %{$hash{$type}}; my %exists; while ( my $link = $record->$type->Next ) { $exists{$link->$target_or_base}++; } for ( keys %{$hash{$type}} ) { delete $hash{$type}{$_} unless $exists{$_}; } } } } rt-5.0.1/share/html/Elements/RT__User/000755 000765 000024 00000000000 14005011336 020305 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/Crypt/000755 000765 000024 00000000000 14005011336 017724 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/HeaderJavascript000644 000765 000024 00000005162 14005011336 021771 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $focus => undef $onload => undef % for my $jsfile ( @js_files ) { % } <%INIT> my @js_files; if ( RT->Config->Get('DevelMode') ) { @js_files = map { $_ =~ m{^/} ? $_ : "/static/js/$_" } RT::Interface::Web->JSFiles(); } else { my $key = RT::Interface::Web::SquishedJS()->Key; @js_files = "/NoAuth/js/squished-$key.js"; } rt-5.0.1/share/html/Elements/EditCustomFieldIPAddressRange000644 000765 000024 00000004070 14005011336 024307 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> return $m->comp( 'EditCustomFieldFreeform', %ARGS ); rt-5.0.1/share/html/Elements/Refresh000644 000765 000024 00000005110 14005011336 020141 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Name => undef $Default => 0 rt-5.0.1/share/html/Elements/ShowMemberships000644 000765 000024 00000006365 14005011336 021677 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
        % while ( my $GroupMember = $GroupMembers->Next ) { % my $Group = RT::Group->new($session{'CurrentUser'}); % $Group->Load($GroupMember->GroupId) or next; % if ($Group->Domain eq 'UserDefined') {
      • <% $Group->Label %>
      • % } elsif ($Group->Domain eq 'SystemInternal') {
      • <% $Group->Label %>
      • % } % }
      <%INIT> my $GroupMembers = RT::GroupMembers->new($session{'CurrentUser'}); $GroupMembers->Limit( FIELD => 'MemberId', VALUE => $UserObj->Id ); my $alias = $GroupMembers->Join( TYPE => 'left', ALIAS1 => 'main', FIELD1 => 'GroupId', TABLE2 => 'Groups', FIELD2 => 'id' ); $GroupMembers->Limit( ALIAS => $alias, FIELD => 'Domain', OPERATOR => '=', VALUE => 'SystemInternal', CASESENSITIVE => 0, ); $GroupMembers->Limit( ALIAS => $alias, FIELD => 'Domain', OPERATOR => '=', VALUE => 'UserDefined', CASESENSITIVE => 0, ); $GroupMembers->OrderByCols( { ALIAS => $alias, FIELD => 'Domain' }, { ALIAS => $alias, FIELD => 'Name' }, ); $GroupMembers->RowsPerPage($Limit) if $Limit; <%ARGS> $UserObj $Limit => undef rt-5.0.1/share/html/Elements/RT__Template/000755 000765 000024 00000000000 14005011336 021142 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/ShowMessageStanza000644 000765 000024 00000015324 14005011336 022161 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> my $plain_text_mono = RT->Config->Get( 'PlainTextMono', $session{'CurrentUser'} ); my $Depth = 0; my $object = $Transaction ? $Transaction->Object : undef; my $print_content = sub { my $ref = shift; return unless defined $$ref && length $$ref; $m->callback( content => $ref, %ARGS ); if ( $ContentType eq 'text/plain' ) { $m->comp( '/Elements/MakeClicky', content => $ref, object => $object, %ARGS ); if ( defined $$ref && !$plain_text_mono ) { $$ref =~ s{(\r?\n)}{
      }g; } } else { if ( defined $$ref ) { $$ref =~ s/^[\r\n]+//g; } } $m->out($$ref); }; $m->out( '
      ' ); if ( ref $Message ) { my @stack; my $para = ''; my $i = 0; AGAIN: foreach ( ; $i < @$Message; $i++ ) { my $stanza = $Message->[$i]; if ( ref $stanza eq "HASH" ) { # Fix message stanza nesting for Outlook's quoting styles if ( $stanza->{raw} and not $stanza->{_outlooked} and $stanza->{raw} =~ /^ # start of an internal line \s* # optional whitespace (?: -{3,} # at least three hyphens \s* # whitespace varies between Outlook versions # don't trigger on PGP signed message or signature blocks (?!(?:BEGIN|END)\s+PGP) \w # at least one word character [\w\s]{3,}? # the rest of the word(s), totalling at least 5 characters, # loose to get different languages \w # at least one ending word character \s* # whitespace varies between Outlook versions -{3,} # at least three hyphens again | _{6,} # OR: six or more underscores ) \s*$ # optional whitespace until the end of the line /xm ) { # There's content before the quoted message, but in the # same stanza. Break it out! if ( my $start = $-[0] ) { my %preceding = %$stanza; # We don't process $stanza->{text} because we don't use it # and it isn't given to us by HTML::Quoted. If we ever # need to, we can process it the same way as 'raw'. $preceding{raw} = substr($stanza->{raw}, 0, $start, ''); # Replace the current stanza with the two we just created splice @$Message, $i, 1, \%preceding, $stanza; # Try it again from the top now that we've rejiggered our # stanzas. We'll process the Outlook stanza again, and hit # the else below this time. redo; } else { # Nest the current stanza and everything that follows $stanza->{_outlooked}++; $stanza = $Message->[ $i ] = [ splice @$Message, $i ]; } } else { $para .= ( defined $stanza->{raw} ? $stanza->{raw} : '' )."\n"; } } next unless ref $stanza eq "ARRAY"; $print_content->( \$para ); $para = ''; $Depth++; push @stack, [ $Message, $i + 1 ]; ( $Message, $i ) = ( $stanza, -1 ); if ( $Depth == 1 ) { $m->comp('FoldStanzaJS'); } my @classes = ('message-stanza'); push @classes, $Depth == 1 ? 'closed' : 'open'; $m->out( '
      ' ); } if ( length $para ) { $print_content->( \$para ); $para = ''; } if (@stack) { ( $Message, $i ) = @{ pop @stack }; $Depth--; $m->out('
      '); goto AGAIN; } } else { $print_content->( \$Message ); } $m->out('
      '); <%ARGS> $Message => undef $Transaction => undef $ContentType => 'text/plain' rt-5.0.1/share/html/Elements/SelectQueueAutocomplete000644 000765 000024 00000005420 14005011336 023355 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $Name => 'Queue' $Default => 0 $Class => 'select-queue field' $Placeholder => '' $ShowAllQueues => 1 $CheckQueueRight => 'CreateTicket' $AutoSubmit => 0 $Return => 'Name' <%init> my $DefaultQueue = RT::Queue->new($session{'CurrentUser'}); $DefaultQueue->Load( $Default ); undef $CheckQueueRight if $ShowAllQueues; " class="<% $Class %>" data-autocomplete="Queues" placeholder="<% $Placeholder %>" data-autocomplete-checkright="<% $CheckQueueRight %>" data-autocomplete-return="<% $Return %>" <% $AutoSubmit ? 'data-autocomplete-autosubmit=1' : '' %> /> rt-5.0.1/share/html/Elements/SelectMatch000644 000765 000024 00000005600 14005011336 020743 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Name => undef $Like => loc('matches') $NotLike => loc("doesn't match") $True => loc('is') $False => loc("isn't") $Default => undef <%INIT> my $TrueDefault = ''; my $FalseDefault=''; my $LikeDefault=''; my $NotLikeDefault =''; if ($Default && $Default =~ /false|!=/i) { $FalseDefault = qq[ selected="selected"]; } elsif ($Default && $Default =~ /true|=/i) { $TrueDefault = qq[ selected="selected"]; } elsif ($Default && $Default =~ /notlike|NOT LIKE/i) { $NotLikeDefault = qq[ selected="selected"]; } else { $LikeDefault = qq[ selected="selected"]; } rt-5.0.1/share/html/Elements/EditCustomFieldCustomGroupings000644 000765 000024 00000005427 14005011336 024673 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % foreach my $group ( @Groupings ) { <&| /Widgets/TitleBox, title => $group? loc($group) : loc('Custom Fields'), class => $css_class .' '. ($group? CSSClass("$css_class-$group") : ''), id => ($group ? CSSClass("$css_class-$group") : $css_class), hide_empty => 1, %$TitleBoxARGS, &> % $ARGS{CustomFields} = $CustomFieldGenerator->() if $CustomFieldGenerator; <& EditCustomFields, %ARGS, Object => $Object, Grouping => $group &> % } <%ARGS> $Object $CustomFieldGenerator => undef, @Groupings => (RT::CustomField->CustomGroupings( $Object ), '') <%INIT> my $css_class = lc(ref($Object)||$Object); $css_class =~ s/^rt:://; $css_class =~ s/::/-/g; $css_class = CSSClass($css_class); $css_class .= '-info-cfs'; my $TitleBoxARGS = delete $ARGS{TitleBoxARGS} || {}; rt-5.0.1/share/html/Elements/EditCustomFields000644 000765 000024 00000010657 14005011336 021766 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $m->callback( CallbackName => 'BeforeCustomFields', Object => $Object, % Grouping => $Grouping, ARGSRef => \%ARGS, CustomFields => $CustomFields); % if (@CustomFields) { % if ( !$InTable ) {
      % } % for my $CustomField ( @CustomFields ) { % my $Type = $CustomField->Type || 'Unknown';
      <% $CustomField->Name %>: % if ( $CustomField->EntryHint ) { % }
      % my $default = $m->notes('Field-' . $CustomField->Id); % $default ||= $ARGS{"CustomField-". $CustomField->Id }; <& /Elements/EditCustomField, %ARGS, CustomField => $CustomField, Default => $default, Object => $Object, &> % if (my $msg = $m->notes('InvalidField-' . $CustomField->Id)) {
      <% $msg %> % } elsif ($ShowHints and $CustomField->FriendlyPattern) {
      <&|/l, $CustomField->FriendlyPattern &>Input must match [_1] % }
      % $m->callback( CallbackName => 'AfterCustomFieldValue', CustomField => $CustomField, Object => $Object, Grouping => $Grouping );
      % } % if ( !$InTable ) {
      % } % } % $m->callback( CallbackName => 'AfterCustomFields', Object => $Object, % Grouping => $Grouping, ARGSRef => \%ARGS ); <%INIT> $CustomFields ||= $Object->CustomFields; $CustomFields->{include_set_initial} = 1 if $ForCreation; $CustomFields->LimitToGrouping( $Object => $Grouping ) if defined $Grouping; $m->callback( %ARGS, CallbackName => 'MassageCustomFields', CustomFields => $CustomFields, Object => $Object, ShowHintsRef => \$ShowHints, InTableRef => \$InTable ); $CustomFields->GotoFirstItem; my @CustomFields; while ( my $CustomField = $CustomFields->Next ) { next unless $CustomField->CurrentUserHasRight('ModifyCustomField') || ($ForCreation && $CustomField->CurrentUserHasRight('SetInitialCustomField')); push @CustomFields, $CustomField; } <%ARGS> $Object $CustomFields => undef $Grouping => undef $InTable => 0 $LabelCols => 3 $ValueCols => 9 $ShowHints => 1 $ForCreation => 0 rt-5.0.1/share/html/Elements/CollectionListPaging000644 000765 000024 00000006642 14005011336 022633 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $BaseURL => undef $Rows => undef $TotalFound => undef $CurrentPage => undef $Pages => undef $URLParams => undef $PageParam => 'Page' <%INIT> $BaseURL = $m->interp->apply_escapes($BaseURL, 'h'); $m->out(qq{
      }); if ($Pages == 1) { $m->out(loc('Page 1 of 1')); } else{ $m->out(loc('Page') . ' '); use Data::Page; use Data::Page::Pageset; my $pager = Data::Page->new($TotalFound, $Rows, $CurrentPage); my $pageset = Data::Page::Pageset->new($pager); for my $chunk ( $pageset->total_pagesets ) { $m->out(qq{}); if ( $chunk->is_current ) { for my $number ( $chunk->first .. $chunk->last ) { if ( $number == $CurrentPage ) { $m->out(qq{$number }); } else { my $qs = $m->interp->apply_escapes( $m->comp('/Elements/QueryString', %$URLParams, $PageParam => $number ), 'h', ); $m->out(qq{$number }); } } } else { my $qs = $m->interp->apply_escapes( $m->comp('/Elements/QueryString', %$URLParams, $PageParam => $chunk->first, ), 'h', ); $m->out(qq{$chunk}); } $m->out(qq{}); } } $m->out(qq{
      }); rt-5.0.1/share/html/Elements/MySupportQueues000644 000765 000024 00000005406 14005011336 021725 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&|/Widgets/TitleBox, title => loc("Queues I'm an AdminCc for"), bodyclass => "" &> <& /Elements/QueueSummaryByStatus, queues => \@queues, &> <%INIT> my $Queues = RT::Queues->new( $session{'CurrentUser'} ); $Queues->UnLimit(); $m->callback( CallbackName => 'SQLFilter', Queues => $Queues ); my @queues; foreach my $queue ( @{ $Queues->ItemsArrayRef } ){ next unless $queue->IsAdminCc($session{'CurrentUser'}->Id); if ( $queue->Id ) { push @queues, { Id => $queue->Id, Name => $queue->Name, Description => $queue->_Accessible("Description" => "read") ? $queue->Description : undef, Lifecycle => $queue->_Accessible("Lifecycle" => "read") ? $queue->Lifecycle : undef, }; } } rt-5.0.1/share/html/Elements/ListActions000644 000765 000024 00000006402 14005011336 021004 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $m->out($$_) for grep {ref $_} @actions; % if (grep {not ref $_} @actions) {
      <&| /Widgets/TitleBox, title => loc('Results'), content_class => 'list-actions', %{$titlebox || {}} &>
        % foreach my $action (grep {not ref $_} @actions) {
      • <%$action%>
      • % }
      % } <%init> # backward compatibility, don't use array in new code, but use keyed hash if ( ref( $session{'Actions'} ) eq 'ARRAY' ) { unshift @actions, @{ delete $session{'Actions'} }; $session{'i'}++; } if ( ref( $session{'Actions'}{''} ) eq 'ARRAY' ) { unshift @actions, @{ delete $session{'Actions'}{''} }; $session{'i'}++; } my $actions_pointer = $DECODED_ARGS->{'results'}; if ($actions_pointer && ref( $session{'Actions'}->{$actions_pointer} ) eq 'ARRAY' ) { unshift @actions, @{ delete $session{'Actions'}->{$actions_pointer} }; $session{'i'}++; } # XXX: run callbacks per row really crazy idea @actions = grep $_, grep { my $skip; $m->callback( %ARGS, row => \$_, skip => \$skip, CallbackName => 'ModifyRow', ); !$skip; } grep $_, @actions; return unless @actions; <%ARGS> $titlebox => {} @actions => undef rt-5.0.1/share/html/Elements/SelfServiceShowArticles000644 000765 000024 00000006540 14005011336 023315 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&| /Widgets/TitleBox, title => $title, class => 'article-listing', content_class => 'mx-auto' &>
      % while (my $article = $articles->Next) {
      <%$article->Name || loc('(no name)')%>
      <% $article->Summary %>
      % }
      <%INIT> my $articles = RT::Articles->new( $session{'CurrentUser'} ); # By default article right check is not at SQL level, which could lead # to empty or wrong article counts. Here we check right beforehand to # get rid of it. my $right = 'ShowArticle'; if ( $session{'CurrentUser'}->HasRight( Right => $right, Object => $RT::System ) ) { $articles->UnLimit; } else { my $classes = RT::Classes->new( $session{'CurrentUser'} ); $classes->UnLimit; $articles->Limit( FIELD => 'Class', VALUE => [ map { $_->Id } grep { $_->CurrentUserHasRight($right) } @{ $classes->ItemsArrayRef } ], OPERATOR => 'IN', ); } $articles->OrderBy(FIELD => $orderby_field, ORDER => $sortorder); $articles->RowsPerPage($rows); <%ARGS> $orderby_field => 'LastUpdated' $sortorder => 'Desc' $rows => 5 $title => loc("[_1] Newest Articles", $rows) rt-5.0.1/share/html/Elements/ShowSearch000644 000765 000024 00000017170 14005011336 020622 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % my $alt = loc('Edit'); <&|/Widgets/TitleBox, title => loc(RT::SavedSearch->EscapeDescription($search->Description), $ProcessedSearchArg->{'Rows'}), title_raw => $title_raw, title_href => $query_link_url.$QueryString, titleright_raw => $customize ? qq[] : '', titleright_href => $customize, hideable => $hideable, class => 'fullwidth' &> <& $query_display_component, hideable => $hideable, %$ProcessedSearchArg, ShowNavigation => 0, Class => $class, HasResults => $HasResults, PreferOrderBy => 1 &> <%init> my $search; my $user = $session{'CurrentUser'}->UserObj; my $SearchArg; my $customize; my $query_display_component = '/Elements/CollectionList'; my $query_link_url = RT->Config->Get('WebPath').'/Search/Results.html'; my $class = 'RT::Tickets'; if ($SavedSearch) { my ( $container_object, $search_id ) = _parse_saved_search($SavedSearch); unless ( $container_object ) { $m->out(loc("Either you have no rights to view saved search [_1] or identifier is incorrect", $m->interp->apply_escapes($SavedSearch, 'h'))); return; } $search = RT::Attribute->new( $session{'CurrentUser'} ); $search->Load($search_id); unless ( $search->Id && ref( $SearchArg = $search->Content ) eq 'HASH' ) { $m->out(loc("Saved search [_1] not found", $m->interp->apply_escapes($SavedSearch, 'h'))) unless $IgnoreMissing; return; } $SearchArg->{'SavedSearchId'} ||= $SavedSearch; $SearchArg->{'SearchType'} ||= 'Ticket'; if ( $SearchArg->{SearchType} eq 'Transaction' ) { $class = $SearchArg->{Class} = 'RT::Transactions'; $customize = RT->Config->Get('WebPath') . '/Search/Build.html?' . $m->comp( '/Elements/QueryString', SavedSearchLoad => $SavedSearch, Class => 'RT::Transactions' ); $ShowCount = RT->Config->Get('TransactionShowSearchResultCount')->{'RT::Ticket'}; } elsif ( $SearchArg->{SearchType} eq 'Asset' ) { $class = $SearchArg->{Class} = 'RT::Assets'; $customize = RT->Config->Get('WebPath') . '/Search/Build.html?' . $m->comp( '/Elements/QueryString', SavedSearchLoad => $SavedSearch, Class => 'RT::Assets' ); $ShowCount = RT->Config->Get('AssetShowSearchResultCount'); } elsif ( $SearchArg->{SearchType} ne 'Ticket' ) { # XXX: dispatch to different handler here $query_display_component = '/Search/Elements/' . $SearchArg->{SearchType}; $query_link_url = RT->Config->Get('WebPath') . "/Search/$SearchArg->{SearchType}.html"; $ShowCount = 0; } elsif ($ShowCustomize) { $customize = RT->Config->Get('WebPath') . '/Search/Build.html?' . $m->comp( '/Elements/QueryString', SavedSearchLoad => $SavedSearch ); } } else { ($search) = RT::System->new( $session{'CurrentUser'} ) ->Attributes->Named( 'Search - ' . $Name ); unless ( $search && $search->Id ) { my (@custom_searches) = RT::System->new( $session{'CurrentUser'} )->Attributes->Named('SavedSearch'); foreach my $custom (@custom_searches) { if ($custom->Description eq $Name) { $search = $custom; last } } unless ($search && $search->id) { $m->out(loc("Predefined search [_1] not found", $m->interp->apply_escapes($Name, 'h'))); return; } } $SearchArg = $user->Preferences( $search, $search->Content ); if ($ShowCustomize) { $customize = RT->Config->Get('WebPath') . '/Prefs/Search.html?' . $m->comp( '/Elements/QueryString', name => ref($search) . '-' . $search->Id ); } } # ProcessedSearchArg is a search with overridings, but for link we use # orginal search's poperties my $ProcessedSearchArg = $SearchArg; $ProcessedSearchArg = { %$SearchArg, %Override } if keys %Override; $m->callback( %ARGS, CallbackName => 'ModifySearch', OriginalSearch => $SearchArg, Search => $ProcessedSearchArg, ); foreach ( $SearchArg, $ProcessedSearchArg ) { $_->{'Format'} ||= ''; $_->{'Query'} ||= ''; # extract-message-catalog would "$1", so we avoid quotes for loc calls $_->{'Format'} =~ s/__loc\(["']?(\w+)["']?\)__/my $f = "$1"; loc($f)/ge; } my $QueryString = '?' . $m->comp( '/Elements/QueryString', %$SearchArg ); my $title_raw; if ($ShowCount) { my $collection = $class->new( $session{'CurrentUser'} ); my $query; if ( $class eq 'RT::Transactions' ) { $query = join ' AND ', "ObjectType = '$ProcessedSearchArg->{ObjectType}'", $ProcessedSearchArg->{Query} ? "($ProcessedSearchArg->{Query})" : (); } else { $query = $ProcessedSearchArg->{Query}; } $collection->FromSQL($query); my $count = $collection->CountAll(); my $title; if ( $class eq 'RT::Transactions' ) { $title = loc('(Found [quant,_1,transaction,transactions])', $count); } elsif ( $class eq 'RT::Assets' ) { $title = loc('(Found [quant,_1,asset,assets])', $count); } else { $title = loc('(Found [quant,_1,ticket,tickets])', $count); } $title_raw = '' . $title . ''; # don't repeat the search in CollectionList $ProcessedSearchArg->{Collection} = $collection; $ProcessedSearchArg->{TotalFound} = $count; } <%ARGS> $Name => undef $SavedSearch => undef %Override => () $IgnoreMissing => undef $hideable => 1 $ShowCustomize => 1 $ShowCount => RT->Config->Get('ShowSearchResultCount') $HasResults => undef rt-5.0.1/share/html/Elements/RT__Transaction/000755 000765 000024 00000000000 14005011336 021654 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/EditCustomFieldCombobox000644 000765 000024 00000005657 14005011336 023300 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % while ($Values and my $value = $Values->Next and $Multiple) {
      % } % (!$Multiple or !$MaxValues or !$Values or $Values->Count < $MaxValues) or return; <& /Widgets/ComboBox, Name => $name, Default => $Default, Rows => $Rows, Class => "CF-".$CustomField->id."-Edit", Values => [map {$_->Name} @{CachedCustomFieldValues($CustomField)->ItemsArrayRef}], &> <%INIT> my $name = $Name || $NamePrefix . $CustomField->Id . '-Value'; my $delete_name = $name; $delete_name =~ s!-Value$!-DeleteValueIds!; <%ARGS> $Object => undef $CustomField => undef $NamePrefix => undef $Default => undef $Values => undef $Multiple => 0 $Rows => undef $MaxValues => undef $Name => undef rt-5.0.1/share/html/Elements/SimpleSearch000644 000765 000024 00000004626 14005011336 021135 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> my $value = defined $DECODED_ARGS->{q} ? $DECODED_ARGS->{q} : ''; <%ARGS> $SendTo => '/Search/Simple.html' $Placeholder => loc('Search') rt-5.0.1/share/html/Elements/EditCustomFieldWikitext000644 000765 000024 00000005505 14005011336 023330 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % while ($Values and my $value = $Values->Next ) {
      % } % if (!$MaxValues or !$Values or $Values->Count < $MaxValues) { % } <%INIT> # XXX - MultiValue textarea is for now outlawed. $MaxValues = 1; my $name = $Name || $NamePrefix . $CustomField->Id . '-Values'; <%ARGS> $Object => undef $CustomField => undef $NamePrefix => undef $Name => undef $Default => undef $Values => undef $MaxValues => undef $Cols $Rows rt-5.0.1/share/html/Elements/ShowCustomFieldWikitext000644 000765 000024 00000005105 14005011336 023357 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%$wiki_content|n%> <%init> my $content = $Object->LargeContent || $Object->Content; $content = $m->comp('/Elements/ScrubHTML', Content => $content); my $base = $Object->Object->WikiBase; my %wiki_args = ( extended => 1, absolute_links => 1, implicit_links => RT->Config->Get('WikiImplicitLinks'), prefix => $base, ); $m->callback( CallbackName => 'WikiFormatArgs', ARGSRef => \%ARGS, WikiArgsRef => \%wiki_args, ContentRef => \$content); use Text::WikiFormat; my $wiki_content = Text::WikiFormat::format( $content."\n" , {}, { %wiki_args } ); <%ARGS> $Object rt-5.0.1/share/html/Elements/SelectObject000644 000765 000024 00000010555 14005011336 021122 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % if ($Lite) { % my $d = $ObjectType->new($session{'CurrentUser'}); % $d->Load($Default); % } % elsif ($Hyperlink) { % } % else { % } <%args> $ObjectType $CheckRight => undef $ShowNullOption => 1 $ShowAll => 1 $Name => undef $Verbose => undef $NamedValues => 0 $DefaultLabel => "-" $Default => 0 $Lite => 0 $OnChange => undef $Multiple => 0 $Size => 6 $Class => "" $CacheNeedsUpdate => undef $Hyperlink => undef $AccessKey => undef <%init> $ObjectType = "RT::$ObjectType" unless $ObjectType =~ /::/; $Class ||= "select-" . CSSClass("\L$1") if $ObjectType =~ /RT::(.+)$/; my $cache_key; if ( not $Lite ) { $cache_key = SetObjectSessionCache( ObjectType => $ObjectType, CheckRight => $CheckRight, ShowAll => $ShowAll, Default => $Default, CacheNeedsUpdate => $CacheNeedsUpdate, ); } my $default_entry; if ( $Default && !$Lite ) { my $object = $ObjectType->new( $session{'CurrentUser'} ); $object->Load( $Default ); if ( $object->id && !$session{$cache_key}{id}{ $object->id } ) { $default_entry = { Id => $object->id, Name => '#' . $object->id, Description => '#' . $object->id, }; } } rt-5.0.1/share/html/Elements/MyAdminQueues000644 000765 000024 00000004536 14005011336 021304 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&|/Widgets/TitleBox, title => loc("Queues I administer"), bodyclass => "" &> <& /Elements/QueueSummaryByStatus, queues => $session{$cache_key}{objects}, &> <%INIT> my $cache_key = SetObjectSessionCache( ObjectType => 'RT::Queue', CheckRight => 'AdminQueue', ShowAll => 0, CacheNeedsUpdate => RT->System->QueueCacheNeedsUpdate, ); rt-5.0.1/share/html/Elements/SelectTimeUnits000644 000765 000024 00000004771 14005011336 021640 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> $Name .= '-TimeUnits' unless $Name =~ /-TimeUnits$/io; <%ARGS> $Name => '' $Default => RT->Config->Get('DefaultTimeUnitsToHours', $session{'CurrentUser'}) ? 'hours' : 'minutes' rt-5.0.1/share/html/Elements/Checkbox000644 000765 000024 00000004431 14005011336 020276 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} /> <%ARGS> $Name => undef $Default => undef $True => undef $False => undef $IsChecked => undef <%INIT> $IsChecked = ($Default && $Default =~ /checked/i) ? ' checked="checked" ' : ""; 1; rt-5.0.1/share/html/Elements/Lifecycle/000755 000765 000024 00000000000 14005011336 020522 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/EditCustomFieldFreeform000644 000765 000024 00000005632 14005011336 023266 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % my $name = $Name || $NamePrefix . $CustomField->Id . ( $Multiple ? '-Values' : '-Value' ); % if ($Multiple) { % } else { % } <%INIT> if ( $Multiple and $Values ) { $Default = join "\n", map $_->Content, @{ $Values->ItemsArrayRef }; } unless ( $Multiple ) { $Default =~ s/\s*\n+\s*/ /g if $Default; } <%ARGS> $Object => undef $CustomField => undef $NamePrefix => undef $Name => undef $Default => undef $Values => undef $Multiple => undef $Cols $Rows rt-5.0.1/share/html/Elements/SelectSLA000644 000765 000024 00000005221 14005011336 020325 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> $Default ||= $DECODED_ARGS->{SLA} if $DefaultFromArgs; $Default ||= $TicketObj->SLA if $TicketObj; <%ARGS> $DefaultFromArgs => 1 $TicketObj => undef $QueueObj => undef $Name => 'SLA' $Default => undef $DefaultValue => 1 $DefaultLabel => '-' rt-5.0.1/share/html/Elements/RT__CustomField/000755 000765 000024 00000000000 14005011336 021605 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/SelectIPRelation000644 000765 000024 00000004613 14005011336 021720 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Name => undef @Options => ( loc('is'), loc("isn't"), loc('less than'), loc('greater than')) @Values => ('=', '!=', '<', '>') $Default => '' rt-5.0.1/share/html/Elements/RT__ScripCondition/000755 000765 000024 00000000000 14005011336 022316 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/ShowCustomFieldDate000644 000765 000024 00000004422 14005011336 022425 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> my $content = $Object->Content; my $DateObj = RT::Date->new ( $session{'CurrentUser'} ); $DateObj->Set( Format => 'unknown', Value => $content, Timezone => 'utc' ); $content = $DateObj->AsString(Time => 0, Timezone => 'utc'); <%$content|n%> <%ARGS> $Object rt-5.0.1/share/html/Elements/SingleUserRoleInput000644 000765 000024 00000005301 14005011336 022467 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/EmailInput, Name => $role->GroupType, Size => $Size, ($ShowPlaceholder ? (Placeholder => loc(RT->Nobody->Name)) : ()), Default => $Default, Autocomplete => 1, AutocompleteReturn => "Name", AutocompleteNobody => 1, &> <%INIT> if (!defined($Default)) { if (!$User && $Ticket) { my $group = $Ticket->RoleGroup($role->GroupType); my $users = $group->UserMembersObj( Recursively => 0 ); $users->{find_disabled_rows} = 1; $User = $users->First; } $Default = (!$User || $User->Id == RT->Nobody->id ? "" : $User->Name); } <%ARGS> $role $Size => undef $Default => undef $User => undef $Ticket => undef $ShowPlaceholder => 1 rt-5.0.1/share/html/Elements/SetupSessionCookie000644 000765 000024 00000004261 14005011336 022347 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> return if $m->is_subrequest; # avoid reentrancy, as suggested by masonbook RT::Interface::Web::LoadSessionFromCookie(); return (); <%ARGS> $SessionCookie => undef rt-5.0.1/share/html/Elements/SelectPriority000644 000765 000024 00000010262 14005011336 021530 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Name => 'Priority' $Default => '' $QueueObj => undef %Queues => () $ValueAsString => undef <%INIT> use List::MoreUtils 'uniq'; if ( RT->Config->Get('EnablePriorityAsString') ) { my %config = RT->Config->Get('PriorityAsString'); my @names; if ($QueueObj) { push @names, $QueueObj->__Value('Name'); # Skip ACL check } elsif (%Queues) { for my $id ( keys %Queues ) { my $queue = RT::Queue->new( $session{'CurrentUser'} ); $queue->Load($id); if ( $queue->Id ) { push @names, $queue->__Value('Name'); # Skip ACL check } } } if ( @names ) { @names = uniq map { exists $config{$_} ? $_ : 'Default' } @names; } else { @names = keys %config; } @names = sort { lc $a cmp lc $b } @names; my $use_numeric; for my $name (@names) { if ( !$config{$name} ) { RT->Logger->debug("PriorityAsString for Queue $name is disabled, skipping"); $use_numeric = 1; last; } } my %map; my %options; for my $name ( @names ) { my $value = $config{$name}; my @list; if ( ref $value eq 'ARRAY' ) { @list = @$value; } elsif ( ref $value eq 'HASH' ) { @list = map { $_ => $value->{$_} } sort { $value->{$a} <=> $value->{$b} } keys %$value; } while ( my $label = shift @list ) { my $option = { Label => $label, Value => shift @list }; push @{ $options{$name} }, $option; $map{$label} //= $option->{Value}; if ( $ValueAsString && $map{$label} != $option->{Value} ) { $ValueAsString = 0; } } } if ($ValueAsString) { for my $name ( keys %options ) { for my $option ( @{ $options{$name} } ) { $option->{Value} = $option->{Label}; } } } return $m->comp( "/Elements/SelectPriorityAsString", %ARGS, Options => \%options ) unless $use_numeric; } $Default = '' unless defined $Default; rt-5.0.1/share/html/Elements/EmailInput000644 000765 000024 00000007754 14005011336 020632 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % if ($EntryHint) {
      <% loc($EntryHint) %> % } <%INIT> my @options; my @items; if ($AutocompleteMultiple) { my $get_autocomplete_option = sub { my $term = shift; return unless $term =~ /\S/; my $json = $m->scomp( '/Helpers/Autocomplete/Users', term => $term, max => 1, $AutocompleteReturn ? ( return => $AutocompleteReturn ) : (), abort => 0, ); if ($json) { if ( my $parsed = JSON::from_json($json) ) { return $parsed->[0] || (); } } return; }; for my $email ( @$Options ) { if ( my $option = $get_autocomplete_option->($email) ) { push @options, $option; } } for my $email ( split '\s*,\s*', $Default || '' ) { if ( my $option = $get_autocomplete_option->($email) ) { push @options, $option; push @items, $option->{value}; } } } <%ARGS> $Name $Size => 40 $Default => '' $Autocomplete => 1 $AutocompleteMultiple => 0 $AutocompleteReturn => '' $AutocompleteNobody => 0 $AutocompleteSystem => 0 $EntryHint => '' $Placeholder => '' $Options => [] rt-5.0.1/share/html/Elements/ShowCustomFieldBinary000644 000765 000024 00000004403 14005011336 022773 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % if (my $url = RT->System->ExternalStorageURLFor($Object)) { % } else { % } <% $Object->Content %> <%ARGS> $Object => undef rt-5.0.1/share/html/Elements/DoAuth000644 000765 000024 00000005573 14005011336 017744 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> # return as quickly as possible if the user is logged in return if $session{CurrentUser} && $session{'CurrentUser'}->id; # It's important to nab the next page from the session before we # potentially blow the session away below. my $next = $session{'NextPage'}->{ $ARGS{'next'} || "" }; $next = $next->{'url'} if ref $next; my ($val,$msg) = RT::Authen::ExternalAuth::DoAuth(\%session,$user,$pass); $RT::Logger->debug("Autohandler called ExternalAuth. Response: ($val, $msg)"); if ( $val ) { $m->callback( %ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler', RedirectTo => \$next ); } # Redirect to the relevant page if the above succeeded RT::Interface::Web::Redirect( $next ) if $val and $next and $m->request_comp->path eq '/NoAuth/Login.html'; # this component should never generate content return; <%ARGS> $user => undef $pass => undef rt-5.0.1/share/html/Elements/ShowUser000644 000765 000024 00000006332 14005011336 020331 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> # $User is an RT::User object # $Address is Email::Address object my $display = RT::User->Format( User => $User, Address => $Address, CurrentUser => $session{CurrentUser}, Format => $style, ); # RT::User->Format does this itself, but we want to make sure we have a $User # if at all possible for the rest of our code below. if ($Address and not $User) { $User = RT::User->new( $session{CurrentUser} ); $User->LoadByEmail( $Address->address ); undef $User unless $User->id; } my %system_user = ( RT->Nobody->id => 1, RT->SystemUser->id => 1, ); $m->callback( ARGSRef => \%ARGS, User => $User, Address => $Address, display => \$display, system_user => \%system_user, CallbackName => 'Modify', ); <%ARGS> $User => undef $Address => undef $style => undef $Link => 1 id ? 'data-user-id="'.$User->id.'"' : "" |n %>>\ % if ($Link and $User and $User->id and not $system_user{$User->id} and $session{CurrentUser}->Privileged) { /User/Summary.html?id=<% $User->id %>">\ <% $display %>\ \ % } else { <% $display %>\ % } \ rt-5.0.1/share/html/Elements/ShowHistoryHeader000644 000765 000024 00000013123 14005011336 022161 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Object $ShowHeaders => 0 $ShowTitle => 1 $ShowDisplayModes => 1 $ScrollShowHistory => 0 $SingleTransaction => 0 <%INIT> my $record_type = $Object->RecordType; my $histid = "\L$record_type\E-" . $Object->id . "-history"; my $reverse_txns = $ARGS{'ReverseTxns'}; if ( $reverse_txns ) { # If we got something, reverse it for the link $reverse_txns = $reverse_txns eq 'ASC' ? 'DESC' : 'ASC'; } else { # Default the link to the opposite of the config setting # Oldest Txns first is ASC, so reverse it for this option default $reverse_txns = RT->Config->Get("OldestTransactionsFirst", $session{'CurrentUser'}) ? 'DESC' : 'ASC'; }
      <%perl> if ( $ShowDisplayModes or $ShowTitle or $ScrollShowHistory ) { my $title = $ShowTitle ? loc('History') : ' '; my @elements; if ( $ScrollShowHistory ) { push( @elements, qq{} . qq{} . loc('Load all history') . qq{} . qq{} ); } if ( $ShowDisplayModes ) { if ( RT->Config->Get( 'QuoteFolding', $session{CurrentUser} ) ) { my $open_all = $m->interp->apply_escapes( loc("Show all quoted text"), 'j' ); my $open_html = $m->interp->apply_escapes( loc("Show all quoted text"), 'h' ); my $close_all = $m->interp->apply_escapes( loc("Hide all quoted text"), 'j' ); push( @elements, qq{$open_html} ); } if ($ShowHeaders) { push( @elements, qq{} . loc("Show brief headers") . qq{} ); } else { push( @elements, qq{} . loc("Show full headers") . qq{} ); } } # Don't need to reverse history when showing a single transaction unless ( $SingleTransaction ) { push( @elements, qq{} . loc("Reverse history order") . qq{} ); } my $titleright; if ( @elements ) { # build the new link my $alt = loc('Options'); $titleright = qq{}; } % $m->callback( CallbackName => 'BeforeTitle', %ARGS, title => \$title, titleright => \$titleright ); <& /Widgets/TitleBoxStart, title => $title, titleright_raw => $titleright, class => 'fullwidth' &> % }
      rt-5.0.1/share/html/Elements/SelectCustomFieldOperator000644 000765 000024 00000004705 14005011336 023646 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Name => undef @Options => ( loc('matches'), loc("doesn't match"), loc('is'), loc("isn't"), loc('less than'), loc('greater than')) @Values => ('LIKE', 'NOT LIKE', '=', '!=', '<', '>') $Default => '' rt-5.0.1/share/html/Elements/MakeClicky000644 000765 000024 00000013337 14005011336 020571 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ONCE> use Regexp::Common qw(URI); my $escaper = sub { my $content = shift; RT::Interface::Web::EscapeHTML( \$content ); return $content; }; my %actions = ( default => sub { my %args = @_; return $escaper->($args{value}); }, url => sub { my %args = @_; my $post = ""; $post = ")" if $args{value} !~ /\(/ and $args{value} =~ s/\)$//; $args{value} = $escaper->($args{value}) unless $args{html}; my $result = qq{[}. loc('Open URL') .qq{]}; return $args{value} . qq{ $result$post}; }, url_overwrite => sub { my %args = @_; my $post = ""; $post = ")" if $args{value} !~ /\(/ and $args{value} =~ s/\)$//; $args{value} = $escaper->($args{value}) unless $args{html}; my $result = qq{$args{value}}; return qq{$result$post}; }, ); my @types = ( { name => "httpurl", regex => qr/$RE{URI}{HTTP}{-keep}{-scheme => 'https?'}(?:#[^\s<]+)?(? "url", }, { name => "httpurl_overwrite", regex => qr/$RE{URI}{HTTP}{-keep}{-scheme => 'https?'}(?:#[^\s<]+)?(? "url_overwrite", }, ); my $handle = sub { my %args = @_; for my $rec( @types ) { return $rec->{action}->( %args, all_matches => [ $args{value}, $1, $2, $3, $4, $5, $6, $7, $8, $9 ], ) if $args{value} =~ $rec->{regex}; } }; my $cache; # only defined via callback # Hook to add more Clicky types # XXX Have to have Page argument, as Mason gets caller wrong in Callback? # This happens as we are in <%ONCE> block $m->callback( CallbackPage => "/Elements/MakeClicky", types => \@types, actions => \%actions, handle => \$handle, cache => \$cache, ); # Filter my %active; $active{$_}++ for RT->Config->Get('Active_MakeClicky'); @types = grep $active{$_->{name}}, @types; # Build up the whole match my $regexp = join "|", map $_->{regex}, @types; # Make sure we have a default $actions{default} ||= sub {}; # Anchor the regexes and look up the actions foreach my $type ( @types ) { $type->{regex} = qr/^$type->{regex}$/; $type->{action} = $actions{$type->{action}} || $actions{default}; } <%ARGS> $content => undef $html => undef <%INIT> return unless defined $$content; if ( defined $cache ) { my $cached_content = $cache->(fetch => $content); if ( $cached_content ) { RT->Logger->debug("Found MakeClicky cache"); $$content = $cached_content; return; } } unless ( $regexp ) { RT::Interface::Web::EscapeHTML( $content ) unless $html; return; } my $pos = 0; while ( $$content =~ /($regexp)/gsio ) { my $match = $1; next if $` =~ /\w+=(?:"|")$/; my $skipped_len = pos($$content) - $pos - length($match); if ( $skipped_len > 0 ) { my $plain; if ( $html ) { $plain = substr( $$content, $pos, $skipped_len ); } else { $plain = $escaper->( substr( $$content, $pos, $skipped_len ) ) } substr( $$content, $pos, $skipped_len ) = $plain; $pos += length($plain); } my $plain = $handle->( %ARGS, value => $match, all_matches => [ $1, $2, $3, $4, $5, $6, $7, $8, $9 ], ); substr( $$content, $pos, length($match) ) = $plain; pos($$content) = ( $pos += length($plain) ); } substr( $$content, $pos ) = $escaper->( substr( $$content, $pos ) ) unless ($pos == length $$content) || $html; pos($$content) = 0; $cache->(store => $content) if defined $cache; rt-5.0.1/share/html/Elements/ShowLinks000644 000765 000024 00000012531 14005011336 020471 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      % for my $type (@display) {
      <& ShowRelationLabel, Object => $Object, Label => $labels{$type}.':', Relation => $type &> % if ($clone{$type}) { (<% loc('Create') %>) % }
      <& ShowLinksOfType, Object => $Object, Type => $type, Recurse => ($type eq 'Members') &>
      % }
      % # Allow people to add more rows to the table % $m->callback( %ARGS ); <& /Elements/ShowCustomFields, Object => $Object, Grouping => 'Links', Table => 0 &> % if ($Object->isa('RT::Ticket')) {
      " name="SpawnLinkedTicket">
      <&|/l&>Create new
      <&|/l&>Ticket in
      <& /Elements/SelectNewTicketQueue, Name => 'CloneQueue' &>
      % } <%INIT> my @display = qw(DependsOn DependedOnBy MemberOf Members RefersTo ReferredToBy); $m->callback( %ARGS, CallbackName => 'ChangeDisplay', display => \@display ); my %labels = ( DependsOn => loc('Depends on'), DependedOnBy => loc('Depended on by'), MemberOf => loc('Parents'), Members => loc('Children'), RefersTo => loc('Refers to'), ReferredToBy => loc('Referred to by'), ); my %clone; if ( $Object->isa("RT::Ticket") and $Object->QueueObj->CurrentUserHasRight('CreateTicket')) { my $id = $Object->id; my $path = RT->Config->Get('WebPath') . '/Ticket/Create.html?Queue=' . $Object->Queue . '&CloneTicket=' . $id; for my $relation (@display) { my $mode = $RT::Link::TYPEMAP{$relation}->{Mode}; my $type = $RT::Link::TYPEMAP{$relation}->{Type}; my $field = $mode eq 'Base' ? 'new-' . $type : $type . '-new'; my @copy = ($id); # Canonicalized type captures both directions if ($type eq "RefersTo") { my $other = "Local" . $mode; push @copy, map { $_->$other() } @{ $Object->$relation->ItemsArrayRef }; } $clone{$relation} = "$path&$field=" . join('%20', grep { $_ } @copy); } } <%ARGS> $Object rt-5.0.1/share/html/Elements/SelfServiceTopArticles000644 000765 000024 00000004175 14005011336 023141 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/SelfServiceShowArticles, orderby_field => 'SortOrder', sortorder => 'ASC', title => loc('[_1] Top Articles', 5), rows => 5 &> rt-5.0.1/share/html/Elements/ShowHistory000644 000765 000024 00000005276 14005011336 021062 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/ShowHistoryHeader, %ARGS &> % $m->callback( %ARGS, Object => $Object, CallbackName => 'BeforeTransactions' ); <& /Elements/ShowHistoryPage, %ARGS &>
      % if ($ShowDisplayModes or $ShowTitle) { <& /Widgets/TitleBoxEnd &> % }
      <%ARGS> $Object $Transactions => $Object->SortedTransactions $Attachments => $Object->Attachments( WithHeaders => 1 ) $AttachmentContent => $Object->TextAttachments $ShowHeaders => 0 $ShowTitle => 1 $ShowDisplayModes => 1 $PathPrefix => '' rt-5.0.1/share/html/Elements/JavascriptConfig000644 000765 000024 00000007242 14005011336 022007 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> my $Config = {}; $Config->{$_} = RT->Config->Get( $_, $session{CurrentUser} ) for qw(rtname WebPath MessageBoxRichText MessageBoxRichTextHeight MaxAttachmentSize WebDefaultStylesheet); my $CurrentUser = {}; if ($session{CurrentUser} and $session{CurrentUser}->id) { $CurrentUser->{$_} = $session{CurrentUser}->$_ for qw(id Name EmailAddress RealName); $CurrentUser->{Privileged} = $session{CurrentUser}->Privileged ? JSON::true : JSON::false; $Config->{WebHomePath} = RT->Config->Get("WebPath") . (!$session{CurrentUser}->Privileged ? "/SelfService" : ""); } my $Catalog = { quote_in_filename => "Filenames with double quotes can not be uploaded.", #loc attachment_warning_regex => "\\b(re)?attach", #loc shortcut_help_error => "Unable to open shortcut help. Reason:", #loc error => "Error", #loc check => "Check", #loc remove => "Remove", #loc loading => "Loading...", #loc try_again => "Try again", #loc no_results => "No results", # loc contains => "Contains", # loc lower_disabled => "disabled", # loc history_scroll_error => "Could not load ticket history. Reason:", #loc unclip => "Show all", #loc clip => "Show less", #loc show_details => "Show Details", #loc hide_details => "Hide Details", #loc }; $_ = loc($_) for values %$Catalog; $m->callback( CallbackName => "Data", CurrentUser => $CurrentUser, Config => $Config, Catalog => $Catalog, ); rt-5.0.1/share/html/Elements/GotoGroup000644 000765 000024 00000005533 14005011336 020501 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <%ARGS> $Default => '' rt-5.0.1/share/html/Elements/RT__Asset/000755 000765 000024 00000000000 14005011336 020446 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/SelectStatus000644 000765 000024 00000012223 14005011336 021171 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> ### XXX: no cover for Tools/MyDay.html my %statuses_by_lifecycle; if ( @Statuses ) { $statuses_by_lifecycle{''} = \@Statuses; } else { if ( $Object ) { my $lifecycle = $Object->LifecycleObj; if ($Object->_Accessible("Status", "read")) { my $current = $Object->Status; my @status; push @status, $current; my %has = (); foreach my $next ( $lifecycle->Transitions( $current ) ) { my $check = $lifecycle->CheckRight( $current => $next ); $has{ $check } = $Object->CurrentUserHasRight( $check ) unless exists $has{ $check }; push @status, $next if $has{ $check }; } $statuses_by_lifecycle{$lifecycle->Name} = \@status; } else { $statuses_by_lifecycle{$lifecycle->Name} = [ $lifecycle->Transitions('') ]; } } for my $lifecycle ( @Lifecycles ) { $statuses_by_lifecycle{$lifecycle->Name} ||= [ $lifecycle->Valid ]; } if (not keys %statuses_by_lifecycle) { for my $lifecycle (map { RT::Lifecycle->Load(Type => $Type, Name => $_) } RT::Lifecycle->List($Type)) { $statuses_by_lifecycle{$lifecycle->Name} = [ $lifecycle->Valid ]; } } } if (keys %statuses_by_lifecycle) { my %simplified; my $key = sub { join "\0", sort @{$_[0]}; }; for my $name (sort keys %statuses_by_lifecycle) { my $matched; my $statuses = $statuses_by_lifecycle{$name}; for my $simple (sort keys %simplified) { if ($key->($statuses) eq $key->($simplified{$simple})) { # Statuses are the same, join 'em! $simplified{"$simple, $name"} = delete $simplified{$simple}; $matched++; last; } } unless ($matched) { $simplified{$name} = $statuses; } } %statuses_by_lifecycle = %simplified; } my $group_by_lifecycle = keys %statuses_by_lifecycle > 1; <%ARGS> $Name => undef $Type => 'ticket' @Statuses => () $Object => undef, @Lifecycles => (), $Default => '' $SkipDeleted => 0 $DefaultValue => 1 $DefaultLabel => "-" $Multiple => 0 $Size => 6 $ShowActiveInactive => 0 rt-5.0.1/share/html/Elements/ListMenu000644 000765 000024 00000006077 14005011336 020320 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $menu $show_children => undef
        % for my $child ($menu->children) {
      • <% $show_link->( $child ) |n %>
        % if ( my $description = $child->description ) { <% $description %>\ % }
      • % if ($show_children && $child->children) { <& /Elements/ListMenu, menu => $child &> % } % }
      <%INIT> my $web_path = RT->Config->Get('WebPath'); my $interp = $m->interp; my $show_link = sub { my $e = shift; my $res = ''; if ( $e->path) { $res .= 'path or $e->path =~ m{^\w+:/}) ? $e->path : $web_path . $e->path; $res .= ' href="'. $interp->apply_escapes($url, 'h') .'"' if $url; if ( $e->target ) { $res .= ' target="'. $interp->apply_escapes( $e->target, 'h' ) .'"'; } $res .= '>'; } my $title = $e->title; $title = $interp->apply_escapes( $title, 'h' ); $res .= $title; if ( $e->path) { $res .= ''; } return $res; }; rt-5.0.1/share/html/Elements/QueueList000644 000765 000024 00000005602 14005011336 020471 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % my $alt = loc('Edit');
      <&|/Widgets/TitleBox, title => loc("Queue list"), class => "fullwidth", bodyclass => "", titleright_raw => qq[], titleright_href => RT->Config->Get('WebPath').'/Prefs/QueueList.html', &> <& $comp, queues => $session{$cache_key}{objects}, &>
      <%INIT> my $unwanted = $session{'CurrentUser'}->UserObj->Preferences('QueueList', {}); my $comp = $SplitByLifecycle? '/Elements/QueueSummaryByLifecycle' : '/Elements/QueueSummaryByStatus'; my $cache_key = SetObjectSessionCache( ObjectType => 'RT::Queue', CheckRight => 'ShowTicket', ShowAll => 0, CacheNeedsUpdate => RT->System->QueueCacheNeedsUpdate, Exclude => $unwanted, ); <%ARGS> $SplitByLifecycle => 1 rt-5.0.1/share/html/Elements/ShowLink000644 000765 000024 00000005402 14005011336 020305 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % my $member = $URI->Object; % if (blessed($member) and $member->isa("RT::Ticket") and $member->CurrentUserHasRight('ShowTicket')) { % my $class = $member->QueueObj->IsInactiveStatus($member->Status) % ? 'ticket-inactive' % : 'ticket-active'; % $class .= ' '.CSSClass($member->Status); <%$member->Id%>: <%$member->Subject || ''%> [<% loc($member->Status) %>] (<& /Elements/ShowUser, User => $member->OwnerObj &>) % } else { <%$URI->AsString%> % } <%ARGS> $URI => undef <%INIT> my $href = $URI->AsHREF; if ( $URI->IsLocal ) { my $base = RT->Config->Get('WebBaseURL'); # URI->rel doesn't contain the leading '/' $href = '/' . URI->new($href)->rel($base); } rt-5.0.1/share/html/Elements/SelectGroups000644 000765 000024 00000006023 14005011336 021166 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <& /Elements/SelectMatch, Name => $SelectOpName, Default => $GroupOp &>
      <%INIT> my $CFs = RT::CustomFields->new($session{'CurrentUser'}); $CFs->LimitToChildType('RT::Group'); $CFs->OrderBy( FIELD => 'Name' ); <%ARGS> $GroupField => '' $GroupOp => '' $GroupString => '' $SelectFieldName => 'GroupField' $SelectOpName => 'GroupOp' $InputStringName => 'GroupString' rt-5.0.1/share/html/Elements/SelectTimezone000644 000765 000024 00000005137 14005011336 021506 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ONCE> use DateTime; my @names = DateTime::TimeZone->all_names; my %label; my $dt = DateTime->now; for ( @names ) { $dt->set_time_zone( $_ ); $label{$_} = $_ . ' ' . $dt->strftime('%z'); } <%ARGS> $ShowNullOption => 1 $Name => undef $Default => 0 rt-5.0.1/share/html/Elements/CryptStatus000644 000765 000024 00000016212 14005011336 021055 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Message $WarnUnsigned => undef $Reverify => 1 <%INIT> my @runs; my $needs_unsigned_warning = $WarnUnsigned; my @protocols = RT::Crypt->EnabledProtocols; my $re_protocols = join '|', map "\Q$_\E", @protocols; foreach ( $Message->SplitHeaders ) { if ( s/^X-RT-($re_protocols)-Status:\s*//io ) { push @runs, [ $1, RT::Crypt->ParseStatus( Protocol => "$1", Status => $_ ) ]; } $needs_unsigned_warning = 0 if /^X-RT-Incoming-Signature:/; # if this is not set, then the email is generated by RT, and so we don't # need "email is unsigned" warnings $needs_unsigned_warning = 0 if not /^Received:/; } return unless @runs or $needs_unsigned_warning; my $reverify_cb = sub { my $top = shift; my $txn = $top->TransactionObj; unless ( $txn && $txn->id ) { return (0, "Couldn't get transaction of attachment #". $top->id); } my $attachments = $txn->Attachments->Clone; $attachments->Limit( FIELD => 'ContentType', VALUE => 'application/x-rt-original-message' ); my $original = $attachments->First; unless ( $original ) { return (0, "Couldn't find attachment with original email of transaction #". $txn->id); } my $parser = RT::EmailParser->new(); $parser->SmartParseMIMEEntityFromScalar( Message => $original->Content, Decode => 0, Exact => 1, ); my $entity = $parser->Entity; unless ( $entity ) { return (0, "Couldn't parse content of attachment #". $original->id); } my @res = RT::Crypt->VerifyDecrypt( Entity => $entity ); return (0, "Content of attachment #". $original->id ." is not signed and/or encrypted") unless @res; $top->DelHeader("X-RT-$_-Status") for RT::Crypt->Protocols; $top->AddHeader(map { ("X-RT-". $_->{Protocol} ."-Status" => $_->{'status'} ) } @res); $top->DelHeader("X-RT-Privacy"); my %protocols; $protocols{$_->{Protocol}}++ for @res; $top->AddHeader('X-RT-Privacy' => $_ ) for sort keys %protocols; $top->DelHeader('X-RT-Incoming-Signature'); my @status = RT::Crypt->ParseStatus( Protocol => $res[0]{'Protocol'}, Status => $res[0]{'status'}, ); for ( @status ) { if ( $_->{'Operation'} eq 'Verify' && $_->{'Status'} eq 'DONE' ) { $top->AddHeader( 'X-RT-Incoming-Signature' => $_->{'UserString'} ); $needs_unsigned_warning = 0; } } return (1, "Reverified original message"); }; my @messages; foreach my $run ( @runs ) { my $protocol = shift @$run; $protocol = $RT::Crypt::PROTOCOLS{lc $protocol}; foreach my $line ( @$run ) { if ( $line->{'Operation'} eq 'KeyCheck' ) { next unless $Reverify; # if a public key was missing during verification then we want try again next unless $line->{'KeyType'} eq 'public' && $line->{'Status'} eq 'MISSING'; # but only if we have key my %key = RT::Crypt->GetPublicKeyInfo( Protocol => $protocol, Key => $line->{'Key'} ); if ( $key{'info'} ) { my ($status, $msg) = $reverify_cb->($Message); unless ($status) { $RT::Logger->error($msg); } else { return $m->comp('SELF', %ARGS, Reverify => 0); } } else { push @messages, { Tag => $protocol, Classes => [qw/keycheck bad/], Value => $m->interp->apply_escapes( loc( "Public key '0x[_1]' is required to verify signature", $line->{'Key'} ), 'h'), }; } } elsif ( $line->{'Operation'} eq 'PassphraseCheck' ) { next if $line->{'Status'} eq 'DONE'; push @messages, { Tag => $protocol, Classes => ['passphrasecheck', lc $line->{Status}], Value => $m->interp->apply_escapes( loc( $line->{'Message'} ), 'h'), }; } elsif ( $line->{'Operation'} eq 'Decrypt' ) { push @messages, { Tag => $protocol, Classes => ['decrypt', lc $line->{Status}], Value => $m->interp->apply_escapes( loc( $line->{'Message'} ), 'h'), }; } elsif ( $line->{'Operation'} eq 'Verify' ) { push @messages, { Tag => $protocol, Classes => ['verify', lc $line->{Status}, 'trust-'.($line->{Trust} || 'UNKNOWN')], Value => $m->interp->apply_escapes( loc( $line->{'Message'} ), 'h'), }; } else { next if $line->{'Status'} eq 'DONE'; push @messages, { Tag => $protocol, Classes => [lc $line->{Operation}, lc $line->{Status}], Value => $m->interp->apply_escapes( loc( $line->{'Message'} ), 'h'), } } } } push @messages, { Tag => "Signing", Classes => ['verify', 'bad'], Value => loc('Warning! This is NOT signed!') } if $needs_unsigned_warning; return unless @messages; my %seen; @messages = grep !$seen{$_->{Value}}++, @messages; return @messages; rt-5.0.1/share/html/Elements/MyReminders000644 000765 000024 00000004504 14005011336 021007 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&|/Widgets/TitleBox, class => 'reminders fullwidth', title => loc("My reminders"), title_href => RT->Config->Get('WebPath') . '/Tools/MyReminders.html' &> <& /Elements/ShowReminders, HasResults => $HasResults &> <%init> return unless RT->Config->Get('EnableReminders'); <%ARGS> $HasResults => undef rt-5.0.1/share/html/Elements/WidgetBar000644 000765 000024 00000004252 14005011336 020421 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % return unless ($menu); % for my $child ($menu->children) { % if (defined $child->raw_html) { <% $child->raw_html |n %> % } else { <% $child->title %>\ % } % } <%ARGS> $menu rt-5.0.1/share/html/Elements/CSRF000644 000765 000024 00000006252 14005011336 017310 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => loc('Possible cross-site request forgery') &> <& /Elements/Tabs &>

      <&|/l&>Possible cross-site request forgery

      % my $strong_start = ""; % my $strong_end = "";

      <&|/l_unsafe, $strong_start, $strong_end, $Reason, $action &>RT has detected a possible [_1]cross-site request forgery[_2] for this request, because [_3]. A malicious attacker may be trying to [_1][_4][_2] on your behalf. If you did not initiate this request, then you should alert your security team.

      % my $start = qq||; % my $end = qq||;

      <&|/l_unsafe, $escaped_path, $action, $start, $end &>If you really intended to visit [_1] and [_2], then [_3]click here to resume your request[_4].

      <& /Elements/Footer, %ARGS &> % $m->abort; <%ARGS> $OriginalURL => '' $Reason => '' $Token => '' <%INIT> my $escaped_path = $m->interp->apply_escapes($OriginalURL, 'h'); $escaped_path = "$escaped_path"; my $url_with_token = URI->new($OriginalURL); $url_with_token->query_form([CSRF_Token => $Token]); my $action = RT::Interface::Web::PotentialPageAction($OriginalURL) || loc("perform actions"); rt-5.0.1/share/html/Elements/SavedSearches000644 000765 000024 00000006076 14005011336 021277 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&|/Widgets/TitleBox, title => loc('Saved Searches') &> % foreach my $Object (@Objects) { % my $SavedSearches = RT::SavedSearches->new($session{CurrentUser}); % $SavedSearches->LimitToPrivacy(join('-',ref($Object),$Object->Id),'Ticket'); % my $title; % if (ref $Object eq 'RT::User' && $Object->Id == $session{CurrentUser}->Id) { % $title = loc("My saved searches"); % } else { % $title = loc("[_1]'s saved searches",$Object->Name); % } % $title = $m->interp->apply_escapes($title, 'h'); <& /Elements/CollectionList, %ARGS, Class => 'RT::SavedSearch', Format => qq{'__Name__/TITLE:$title'}, Collection => $SavedSearches, PassArguments => [qw(Format Name id)], &> % } <%init> my @Objects = RT::SavedSearch->new($session{CurrentUser})->ObjectsForLoading; push @Objects, RT::System->new( $session{'CurrentUser'} ) if $session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'SuperUser' ); <%ARGS> $user_attrs => undef rt-5.0.1/share/html/Elements/SelectArticle000644 000765 000024 00000006600 14005011336 021273 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % if ( $autocomplete ) { <& "SelectArticleAutocomplete", QueueObj => $QueueObj, Default => $Default, Name => $Name &> % } else { % } <%INIT> my $articles = RT::Articles->new( $session{'CurrentUser'} ); $articles->LimitAppliedClasses( Queue => $QueueObj ); my $dropdown_limit = RT->Config->Get( 'DropdownMenuLimit' ) || 50; $m->callback( CallbackName => 'ModifyDropdownLimit', DropdownLimit => \$dropdown_limit ); my $autocomplete = $articles->Count > $dropdown_limit ? 1 : 0; if ( $Default and $Default =~ /^\d+$/ ){ # We got an id, look up the name my $default_article = RT::Article->new($session{'CurrentUser'}); my ($ret, $msg) = $default_article->Load( $Default ); if ($ret) { $Default = $default_article->Name; } else { RT::Logger->error("Unable to load article $Default: $msg"); } } $Default //= ''; <%ARGS> $QueueObj $Name => 'IncludeArticleId' $Default => '' $AutoSubmit => 0 $IncludeSummary => 1 rt-5.0.1/share/html/Elements/Logo000644 000765 000024 00000006507 14005011336 017456 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      % if ($user_logo) { <%loc($ARGS{'LogoAltText'}||RT->Config->Get('LogoAltText'))%> % } else { <%loc($ARGS{'LogoAltText'}||RT->Config->Get('LogoAltText'))%> % } % if ( $ShowName ) { <% $Name || loc("RT for [_1]", RT->Config->Get('rtname')) %> % }
      <%INIT> if ( exists $ARGS{'show_name'} ) { $RT::Logger->warning('show_name argument was renamed, use ShowName'); $ShowName = delete $ARGS{'show_name'}; } my $user_logo = blessed $RT::System ? $RT::System->FirstAttribute('UserLogo') : undef; # If we have the attribute, but no content, we don't really have a user logo if ($user_logo) { my $content = $user_logo->Content; undef $user_logo unless ref $content eq 'HASH' and defined $content->{'data'}; } if ($OnlyCustom and not $user_logo and ($ARGS{LogoURL}||RT->Config->Get('LogoURL')) =~ /request-tracker-logo\.svg$/) { return; } <%ARGS> $ShowName => 0 $OnlyCustom => 0 $Name => undef $id => 'logo' rt-5.0.1/share/html/Elements/EditCustomFieldDateTime000644 000765 000024 00000005146 14005011336 023215 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % my $name = $Name || $NamePrefix.$CustomField->Id.'-Values';
      <& /Elements/SelectDate, Name => $name, Default => $Default, current => 0 &>
      (<%$DateObj->AsString%>)
      <%INIT> my $DateObj = RT::Date->new ( $session{'CurrentUser'} ); $DateObj->Set( Format => $Format, Value => $Default ); <%ARGS> $Object => undef $CustomField => undef $NamePrefix => undef $Default => undef $Values => undef $MaxValues => 1 $Name => undef $Format => 'unknown' rt-5.0.1/share/html/Elements/ShowCustomFieldDateTime000644 000765 000024 00000004334 14005011336 023246 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> my $content = $Object->Content; my $DateObj = RT::Date->new ( $session{'CurrentUser'} ); $DateObj->Set( Format => 'ISO', Value => $content ); $content = $DateObj->AsString; <%$content|n%> <%ARGS> $Object rt-5.0.1/share/html/Elements/ShowMessageHeaders000644 000765 000024 00000010102 14005011336 022261 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % if ( @headers ) { % foreach my $header (@headers) { % }
      <% $header->{'Tag'} %>: <% $header->{'Value'} | n %>
      % $m->callback( CallbackName => 'AfterHeaders', message => $Message ); % } <%INIT> my @headers; foreach my $field( RT->Config->Get('ShowBccHeader')? $Message->_SplitHeaders : $Message->SplitHeaders ) { my ($tag, $value) = split /:/, $field, 2; next unless $tag && $value; push @headers, { Tag => $tag, Value => $value }; } my %display_headers = map { lc($_) => 1 } @DisplayHeaders; $m->callback( message => $Message, headers => \@headers, display_headers => \%display_headers, ); unless ( $display_headers{'_all'} ) { @headers = grep $display_headers{ lc $_->{'Tag'} }, @headers; } my $object = $Message->TransactionObj->Object; foreach my $f (@headers) { $m->comp('/Elements/MakeClicky', content => \$f->{'Value'}, object => $object, %ARGS); if ($f->{'Tag'} eq 'RT-Attach') { $f->{'Value'} =~ s/(?:^\s*|\s*$)//g; # Blat in the filename and linkify my $att = RT::Attachment->new( $session{'CurrentUser'} ); $att->Load($f->{'Value'}); next unless $att->Id and $att->TransactionObj->CurrentUserCanSee; $f->{'Value'} = sprintf '%s', RT->Config->Get('WebPath'), $att->TransactionObj->Id, $att->Id, $m->interp->apply_escapes($att->Filename, qw(u h)), $m->interp->apply_escapes($att->Filename, 'h'); } } unshift @headers, $m->comp( 'CryptStatus', Message => $Message, WarnUnsigned => $WarnUnsigned ); $m->callback( CallbackName => 'BeforeLocalization', headers => \@headers, ); if ( $Localize ) { $_->{'Tag'} = loc($_->{'Tag'}) foreach @headers; } <%ARGS> $WarnUnsigned => 0 $Message => undef $Localize => 1 @DisplayHeaders => ('_all') rt-5.0.1/share/html/Elements/Dashboards000644 000765 000024 00000004707 14005011336 020630 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % my $alt = loc('Edit'); <&|/Widgets/TitleBox, title => loc('Dashboards'), title_href => RT->Config->Get('WebPath').'/Dashboards/index.html', titleright_raw => qq[], titleright_href => RT->Config->Get('WebPath').'/Dashboards/index.html', &> <& /Dashboards/Elements/ShowDashboards, IncludeSuperuserGroups => 0 &> rt-5.0.1/share/html/Elements/RT__Dashboard/000755 000765 000024 00000000000 14005011336 021256 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/BulkCustomFields000644 000765 000024 00000013045 14005011336 021770 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <&|/l&>Name
      <&|/l&>Add values
      <&|/l&>Delete values
      % my $i = 0; % while (my $cf = $CustomFields->Next) {
      <% $cf->Name %>: % if ( $cf->EntryHint ) { % }
      % my $rows = 5; % my $cf_id = $cf->id; % my @add = (NamePrefix => 'Bulk-Add-CustomField-', CustomField => $cf, Rows => $rows, % MaxValues => $cf->MaxValues, Multiple => ($cf->MaxValues == 1 ? 0 : 1) , Cols => 25, % Default => $ARGS{"Bulk-Add-CustomField-$cf_id-Values"} || $ARGS{"Bulk-Add-CustomField-$cf_id-Value"}, ); % my @del = (NamePrefix => 'Bulk-Delete-CustomField-', CustomField => $cf, % MaxValues => 0, Rows => $rows, Multiple => 1, Cols => 25, % Default => $ARGS{"Bulk-Delete-CustomField-$cf_id-Values"} || $ARGS{"Bulk-Delete-CustomField-$cf_id-Value"}, ); % if ($cf->Type eq 'Select') {
      <& /Elements/EditCustomFieldSelect, @add &>
      <& /Elements/EditCustomFieldSelect, @del &> % } elsif ($cf->Type eq 'Combobox') {
      <& /Elements/EditCustomFieldCombobox, @add &>
      <& /Elements/EditCustomFieldCombobox, @del &> % } elsif ($cf->Type eq 'Freeform') {
      <& /Elements/EditCustomFieldFreeform, @add &>
      <& /Elements/EditCustomFieldFreeform, @del &> % } elsif ($cf->Type eq 'Text') {
      <& /Elements/EditCustomFieldText, @add &> % } elsif ($cf->Type eq 'Wikitext') {
      <& /Elements/EditCustomFieldWikitext, @add &> % } elsif ($cf->Type eq 'Date') {
      <& /Elements/EditCustomFieldDate, @add &>
      <& /Elements/EditCustomFieldDate, @del &> % } elsif ($cf->Type eq 'DateTime') { % # Pass datemanip format to prevent another tz date conversion
      <& /Elements/EditCustomFieldDateTime, @add, Default => undef, Format => 'datemanip' &>
      <& /Elements/EditCustomFieldDateTime, @del, Default => undef, Format => 'datemanip' &> % } elsif ($cf->Type eq 'Autocomplete') {
      <& /Elements/EditCustomFieldAutocomplete, @add &>
      <& /Elements/EditCustomFieldAutocomplete, @del &> % } else {
      <&|/l&>(Unsupported custom field type)
      % $RT::Logger->info("Unknown CustomField type: " . $cf->Type); % next % }
      % }
      <%ARGS> $CustomFields <%INIT> return unless $CustomFields->Count; rt-5.0.1/share/html/Elements/SelectUsers000644 000765 000024 00000006427 14005011336 021020 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <& /Elements/SelectMatch, Name => $SelectOpName, Default => $UserOp &>
      <%INIT> my $CFs = RT::CustomFields->new($session{'CurrentUser'}); $CFs->LimitToChildType('RT::User'); $CFs->OrderBy( FIELD => 'Name' ); my @fields = RT::User->BasicColumns; if ( $Fields and ref $Fields eq 'ARRAY' ) { if ( ref $Fields->[0] eq 'ARRAY' ) { @fields = @$Fields; } else { # make the name equal the label @fields = [ @$Fields, @$Fields ]; } } <%ARGS> $UserField => '' $UserOp => '' $UserString => '' $Fields => undef $SelectFieldName => 'UserField' $SelectOpName => 'UserOp' $InputStringName => 'UserString' rt-5.0.1/share/html/Elements/RT__Catalog/000755 000765 000024 00000000000 14005011336 020741 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/QueryString000644 000765 000024 00000004733 14005011336 021051 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> my @params; for my $key (sort keys %ARGS) { my $value = $ARGS{$key}; next unless defined $value; $key = $m->interp->apply_escapes( $key, 'u' ); if( UNIVERSAL::isa( $value, 'ARRAY' ) ) { push @params, map $key ."=". $m->interp->apply_escapes( $_, 'u' ), map defined $_? $_ : '', @$value; } else { push @params, $key ."=". $m->interp->apply_escapes($value, 'u'); } } return join '&', @params; rt-5.0.1/share/html/Elements/SelfServiceNewestArticles000644 000765 000024 00000004032 14005011336 023634 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/SelfServiceShowArticles &> rt-5.0.1/share/html/Elements/Menu000644 000765 000024 00000004150 14005011336 017452 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $menu $id => undef $toplevel => 1 $parent_id => '' $depth => 0 <%INIT> RenderMenu( %ARGS ); rt-5.0.1/share/html/Elements/CollectionAsTable/000755 000765 000024 00000000000 14005011336 022152 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/LoginHelp000644 000765 000024 00000004345 14005011336 020435 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> my $source = RT->Config->Meta('OwnerEmail')->{Source}; return unless $source->{SiteConfig} or $source->{Extension}; rt-5.0.1/share/html/Elements/RT__Ticket/000755 000765 000024 00000000000 14005011336 020612 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/SelectQueue000644 000765 000024 00000005125 14005011336 020775 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} %if (RT->Config->Get("AutocompleteQueues", $session{'CurrentUser'})) { <& SelectQueueAutocomplete, %ARGS, &> %} else { <& SelectObject, %ARGS, ObjectType => "Queue", CheckRight => $CheckQueueRight, ShowAll => $ShowAllQueues, CacheNeedsUpdate => RT->System->QueueCacheNeedsUpdate, &> %} <%args> $CheckQueueRight => 'CreateTicket' $ShowAllQueues => 1 $AutoSubmit => 0 <%init> $ARGS{OnChange} = "jQuery(this).closest('form').find('input[name=QueueChanged]').val(1);"; $ARGS{OnChange} .= "jQuery(this).closest('form').submit();" if $AutoSubmit; rt-5.0.1/share/html/Elements/FindAsset000644 000765 000024 00000004331 14005011336 020427 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&|/Widgets/TitleBox, title => loc('Find an asset') &>
      rt-5.0.1/share/html/Elements/ShowRelationLabel000644 000765 000024 00000005357 14005011336 022136 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % if ($SearchURL) { <% $Label %> % } else { <% $Label %> % } <%INIT> my $typemap = $RT::Link::TYPEMAP{$Relation}; my $search_mode = $typemap->{Mode}; my $search_type = $typemap->{Type}; my $search_relation = $RT::Link::DIRMAP{$search_type}->{$search_mode}; my $SearchURL; if ($Object and $Object->id) { my $id = $Object->id; if ($Object->isa("RT::Ticket")) { $SearchURL = RT->Config->Get('WebPath') . '/Search/Results.html?' . $m->comp('/Elements/QueryString', Query => "$search_relation = $id"); } } $m->callback( CallbackName => "ModifySearchURL", SearchURL => \$SearchURL, ARGSRef => \%ARGS, ); <%ARGS> $Object => undef $Label $Relation rt-5.0.1/share/html/Elements/FindUser000644 000765 000024 00000004117 14005011336 020270 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&|/Widgets/TitleBox, title => loc('Find a user')&> <& /Elements/GotoUser, Cols => 9 &> rt-5.0.1/share/html/Elements/SelectOwnerDropdown000644 000765 000024 00000011140 14005011336 022512 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> my %user_uniq_hash; my $isSU = $session{CurrentUser} ->HasRight( Right => 'SuperUser', Object => $RT::System ); foreach my $object (@$Objects) { my $Users = RT::Users->new( $session{CurrentUser} ); $Users->LimitToPrivileged; $Users->WhoHaveRight( Right => 'OwnTicket', Object => $object, IncludeSystemRights => 1, IncludeSuperusers => $isSU ); while ( my $User = $Users->Next() ) { $user_uniq_hash{ $User->Id() } = $User; } } my $dropdown_limit = RT->Config->Get( 'DropdownMenuLimit' ) || 50; $m->callback( CallbackName => 'ModifyDropdownLimit', DropdownLimit => \$dropdown_limit ); if (keys(%user_uniq_hash) > $dropdown_limit ) { if ($Objects->[0]->id) { my $desc = $Objects->[0]->RecordType." ".$Objects->[0]->id; RT->Logger->notice("More than $dropdown_limit possible Owners found for $desc; switching to autocompleter. See the \$AutocompleteOwners configuration option"); } $m->comp("/Elements/SelectOwnerAutocomplete", %ARGS); return; } $m->callback( CallbackName => 'ModifyOwnerListRaw', ARGSRef => \%ARGS, UserHashRef => \%user_uniq_hash, DefaultRef => \$Default, Objects => $Objects ); if ($Default && $Default != RT->Nobody->id && !$user_uniq_hash{$Default}) { $user_uniq_hash{$Default} = RT::User->new($session{CurrentUser}); $user_uniq_hash{$Default}->Load($Default); } $Default = 0 unless defined $Default && $Default =~ /^\d+$/; my @formatednames = sort {lc $a->[1] cmp lc $b->[1]} map {[$_, $_->Format]} grep { $_->id != RT->Nobody->id } values %user_uniq_hash; my $nobody_user = RT::User->new( $session{CurrentUser} ); $nobody_user->Load( RT->Nobody->id ); my $nobody = [$nobody_user, $nobody_user->Format]; unshift @formatednames, $nobody; $m->callback( CallbackName => 'ModifyOwnerListSorted', ARGSRef => \%ARGS, NamesRef => \@formatednames, DefaultRef => \$Default, Objects => $Objects ); <%ARGS> $Name => undef $Objects => [] $Default => 0 $DefaultValue => 1 $DefaultLabel => "-" $ValueAttribute => 'id' rt-5.0.1/share/html/Elements/ScrubHTML000644 000765 000024 00000004102 14005011336 020306 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> return ScrubHTML($Content); <%args> $Content => undef rt-5.0.1/share/html/Elements/SelectDateRelation000644 000765 000024 00000004442 14005011336 022265 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Name => undef $Default => undef $Before => loc('before') $On => loc('on') $After => loc('after') rt-5.0.1/share/html/Elements/ShortcutHelp000644 000765 000024 00000014431 14005011336 021175 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $show_bulk_update => 0 $show_search => 0 $show_ticket_reply => 0 $show_ticket_comment => 0 rt-5.0.1/share/html/Elements/FindGroup000644 000765 000024 00000004106 14005011336 020444 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <&|/Widgets/TitleBox, title => loc('Find a group')&> <& /Elements/GotoGroup &> rt-5.0.1/share/html/Elements/MyRT000644 000765 000024 00000010256 14005011336 017405 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $m->callback( ARGSRef => \%ARGS, CallbackName => 'BeforeTable' );
      % $show_cb->($_) foreach @$body;
      % if ( $sidebar ) {
      % $show_cb->($_) foreach @$sidebar;
      % }
      % $m->callback( ARGSRef => \%ARGS, CallbackName => 'AfterTable' ); <%INIT> my %allowed_components = map {$_ => 1} @{RT->Config->Get('HomepageComponents')}; my $user = $session{'CurrentUser'}->UserObj; unless ( $Portlets ) { my ($defaults) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings'); $Portlets = $user->Preferences( HomepageSettings => $defaults ? $defaults->Content : {} ); } $m->callback( CallbackName => 'MassagePortlets', Portlets => $Portlets ); my ($body, $sidebar) = @{$Portlets}{qw(body sidebar)}; unless( $body && @$body ) { $body = $sidebar || []; $sidebar = undef; } $sidebar = undef unless $sidebar && @$sidebar; my $Rows = $user->Preferences( 'SummaryRows', ( RT->Config->Get('DefaultSummaryRows') || 10 ) ); my $show_cb = sub { my $entry = shift; my $type = $entry->{type}; my $name = $entry->{'name'}; if ( $type eq 'component' ) { if (!$allowed_components{$name}) { $m->out( $m->interp->apply_escapes( loc("Invalid portlet [_1]", $name), "h" ) ); RT->Logger->info("Invalid portlet $name found on user " . $user->Name . "'s homepage"); if ($name eq 'QueueList' && $allowed_components{Quicksearch}) { RT->Logger->warning("You may need to replace the component 'Quicksearch' in the HomepageComponents config with 'QueueList'. See the UPGRADING-4.4 document."); } } else { $m->comp( $name, %{ $entry->{arguments} || {} } ); } } elsif ( $type eq 'system' ) { $m->comp( '/Elements/ShowSearch', Name => $name, Override => { Rows => $Rows } ); } elsif ( $type eq 'saved' ) { $m->comp( '/Elements/ShowSearch', SavedSearch => $name, Override => { Rows => $Rows } ); } else { $RT::Logger->error("unknown portlet type '$type'"); } }; <%ARGS> $Portlets => undef rt-5.0.1/share/html/Elements/SelectDate000644 000765 000024 00000006072 14005011336 020570 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $m->callback( %ARGS, Name => $Name, CallbackName => 'BeforeDateInput', Object => $Object, ARGSRef => $ARGSRef, ShowTimeRef => \$ShowTime, ); % $m->callback( %ARGS, Name => $Name, CallbackName => 'AfterDateInput', Object => $Object, ARGSRef => $ARGSRef, ); <%init> unless ((defined $Default) or ($current <= 0)) { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($current); $Default = sprintf("%04d-%02d-%02d %02d:%02d", $year+1900,$mon+1,$mday, $hour,$min); } $Default ||= ''; unless ($Name) { $Name = $menu_prefix. "_Date"; } $id = $Name if !defined($id); <%args> $ShowTime => 1 $menu_prefix => '' $current => time $Default => '' $Name => undef $Size => 16 $Object => undef $ARGSRef => undef $id => undef rt-5.0.1/share/html/Elements/PageLayout000644 000765 000024 00000006022 14005011336 020620 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % if ($m->comp_exists($beforenav_plugin) ) { <& $beforenav_plugin &> % } % if ( $show_menu ) { % }
      <& /Elements/WidgetBar, menu => PageWidgets() &>
      % if ($m->comp_exists($aftermenu_plugin) ) { <& $aftermenu_plugin &> % } % # Close div for rt-header-container started in Header
      % $m->callback( %ARGS, CallbackName => 'BeforeBody' ); % $m->flush_buffer(); # we've got the page laid out, let's flush the buffer; <%ARGS> $title => $m->callers(-1)->path $show_menu => 1 <%init> my $style = $session{'CurrentUser'} ? $session{'CurrentUser'}->Stylesheet : RT->Config->Get('WebDefaultStylesheet'); my $beforenav_plugin = "/NoAuth/css/".$style."/BeforeNav"; my $aftermenu_plugin = "/NoAuth/css/".$style."/AfterMenus"; rt-5.0.1/share/html/Elements/ShowCustomFieldCustomGroupings000644 000765 000024 00000012706 14005011336 024724 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%perl> for my $group ( @Groupings ) { my $CustomFields = $Object->CustomFields; $m->callback( Table => 1, # default is true %ARGS, CallbackPage => '/Elements/ShowCustomFields', CallbackName => 'MassageCustomFields', Object => $Object, CustomFields => $CustomFields, ); $CustomFields->LimitToGrouping( $Object => $group ); next unless $CustomFields->First; my $modify_url = $title_href ? "$title_href?id=".$Object->id.($group?";Grouping=".$m->interp->apply_escapes($group,'u')."#".CSSClass("$css_class-$group") : "#".$css_class) : undef; my $modify_inline = '' . qq{} . '' . ''; my $modify_behavior = $InlineEdit ? ($inline_edit_behavior{$group} || $inline_edit_behavior{_default} || 'link') : 'hide'; my @class = $css_class; push @class, CSSClass("$css_class-$group") if $group; push @class, 'editing' if $modify_behavior eq 'always'; my %grouping_args = ( title => $group? loc($group) : loc('Custom Fields'), class => (join " ", @class), hide_empty => 1, title_href => $modify_url, ($modify_behavior =~ /^(link|click)$/ ? (titleright_raw => $modify_inline) : ()), data => { 'inline-edit-behavior' => $modify_behavior }, %$TitleBoxARGS, ); $m->callback( CallbackName => 'TitleBox', Object => $Object, Grouping => $group, ARGSRef => \%grouping_args ); <&| /Widgets/TitleBox, %grouping_args &> % unless ($modify_behavior eq 'always') {
      <& ShowCustomFields, %ARGS, Object => $Object, Grouping => $group &>
      % } % if ($modify_behavior ne 'hide') {
      <& /Elements/EditCustomFields, Object => $Object, Grouping => $group, InTable => 0 &>
      % } % } <%ARGS> $Object $title_href => "" $InlineEdit => 0 @Groupings => () $ActionURL => RT->Config->Get('WebPath')."/Ticket/Display.html" <%INIT> my $css_class = lc(ref($Object)||$Object); $css_class =~ s/^rt:://; $css_class =~ s/::/-/g; $css_class = CSSClass($css_class); $css_class .= '-info-cfs'; my $TitleBoxARGS = delete $ARGS{TitleBoxARGS} || {}; $InlineEdit = 0 unless $Object->isa('RT::Ticket'); my %inline_edit_behavior; if ( my $config = RT->Config->Get('InlineEditPanelBehavior') ) { %inline_edit_behavior = %{ $config->{ RT::CustomField->_GroupingClass($Object) } || $config->{'RT::Ticket'} || {} }; } my $edit_label = $m->interp->apply_escapes( loc("Edit"), 'h' ); my $cancel_label = $m->interp->apply_escapes( loc("Cancel"), 'h' ); @Groupings = (RT::CustomField->CustomGroupings( $Object ), '') unless @Groupings; rt-5.0.1/share/html/Elements/EditCustomFieldText000644 000765 000024 00000005533 14005011336 022445 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % while ($Values and my $value = $Values->Next ) {
      % } % if (!$MaxValues or !$Values or $Values->Count < $MaxValues) { % } <%INIT> # XXX - MultiValue textarea is for now outlawed. $MaxValues = 1; my $name = $Name || $NamePrefix . $CustomField->Id . '-Values'; <%ARGS> $Object => undef $CustomField => undef $NamePrefix => '' $Name => undef $Default => undef $Values => undef $MaxValues => undef $Cols $Rows rt-5.0.1/share/html/Elements/ShowLinksOfType000644 000765 000024 00000007467 14005011336 021634 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
        % for my $link (@not_tickets, @active, @inactive) {
      • <& ShowLink, URI => $link->$ModeURI &> <%perl> next unless $Recurse; my $ToObj = $link->$ModeObj; next if $ToObj and $checked->{$ToObj->id}; if ($depth <= $MaxDepth) { <& ShowLinksOfType, %ARGS, Object => $ToObj, depth => ($depth + 1), checked => $checked &> % }
      • % }
      <%INIT> return unless $Object; unless ($RT::Link::TYPEMAP{$Type}) { RT->Logger->error("Unknown link Type '$ARGS{Type}'"); return; } unless ($Object->can($Type)) { RT->Logger->error("Don't know how to fetch links of '$Type' for object '$Object'"); return; } my $links = $Object->$Type; return unless $links->Count; return if $checked->{$Object->id}; $checked->{$Object->id} = 1; my $mode = $RT::Link::TYPEMAP{$Type}->{'Mode'}; my $ModeURI = "${mode}URI"; my $ModeObj = "${mode}Obj"; # Filter and bucket my (@active, @inactive, @not_tickets); while (my $link = $links->Next) { my $ToObj = $link->$ModeObj; next if UNIVERSAL::isa($ToObj,'RT::Article') && $ToObj->Disabled; if ($ToObj and $ToObj->isa('RT::Ticket')) { next if $Type eq "ReferredToBy" and $ToObj->__Value('Type') eq 'reminder'; if ( $ToObj->QueueObj->IsInactiveStatus( $ToObj->Status ) ) { push @inactive, $link; } else { push @active, $link; } } else { push @not_tickets, $link; } } $m->callback( CallbackName => "Init", ARGSRef => \%ARGS, Object => $Object, $Type => $Type, $Recurse => \$Recurse, $MaxDepth => \$MaxDepth, active => \@active, inactive => \@inactive, not_tickets => \@not_tickets, ); <%ARGS> $Object => undef $Type $Recurse => 0 $MaxDepth => 7 $depth => 1 $checked => {} rt-5.0.1/share/html/Elements/AddLinks000644 000765 000024 00000012610 14005011336 020237 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%args> $Object => undef $CustomFields => undef $ARGSRef => $DECODED_ARGS <%init> my $id = ($Object and $Object->id) ? $Object->id : "new"; my $exclude = qq| data-autocomplete="Tickets" data-autocomplete-multiple="1"|; $exclude .= qq| data-autocomplete-exclude="$id"| if $Object->id; % if (ref($Object) eq 'RT::Ticket') { <&|/l&>Enter tickets or URIs to link tickets to. Separate multiple entries with spaces.
      <&|/l&>You may enter links to Articles as "a:###", where ### represents the number of the Article.
      <&|/l&>Enter links to assets as "asset:###", where ### represents the asset ID.
      <&|/l&>Enter links to groups as "group:###", where ### represents the group ID.
      <&|/l&>Enter links to users as "user:###", where ### represents the user ID. % $m->callback( CallbackName => 'ExtraLinkInstructions' );

      % } elsif (ref($Object) eq 'RT::Queue') { <&|/l&>Enter queues or URIs to link queues to. Separate multiple entries with spaces.
      % } else { <&|/l&>Enter objects or URIs to link objects to. Separate multiple entries with spaces.
      % }
      <& ShowRelationLabel, Object => $Object, Label => loc('Depends on').':', Relation => 'DependsOn' &>
      " <% $exclude |n%>/>
      <& ShowRelationLabel, Object => $Object, Label => loc('Depended on by').':', Relation => 'DependedOnBy' &>
      " <% $exclude |n%>/>
      <& ShowRelationLabel, Object => $Object, Label => loc('Parents').':', Relation => 'Parents' &>
      " <% $exclude |n%>/>
      <& ShowRelationLabel, Object => $Object, Label => loc('Children').':', Relation => 'Children' &>
      " <% $exclude |n%>/>
      <& ShowRelationLabel, Object => $Object, Label => loc('Refers to').':', Relation => 'RefersTo' &>
      " <% $exclude |n%>/>
      <& ShowRelationLabel, Object => $Object, Label => loc('Referred to by').':', Relation => 'ReferredToBy' &>
      " <% $exclude |n%>/>
      <& /Elements/EditCustomFields, Object => $Object, Grouping => 'Links', InTable => 1, ($CustomFields ? (CustomFields => $CustomFields) : ()), &> % $m->callback( CallbackName => 'NewLink' );
      rt-5.0.1/share/html/Elements/ShowUserEmailFrequency000644 000765 000024 00000004231 14005011336 023157 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} (<% loc($frequency) %>) <%INIT> my $frequency = $User->EmailFrequency( Ticket => $Ticket ); return unless $frequency; <%ARGS> $User $Ticket => undef rt-5.0.1/share/html/Elements/EditCustomFieldIPAddress000644 000765 000024 00000004070 14005011336 023332 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> return $m->comp( 'EditCustomFieldFreeform', %ARGS ); rt-5.0.1/share/html/Elements/MyAssets000644 000765 000024 00000004256 14005011336 020325 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /User/Elements/AssetList, User => $session{'CurrentUser'}->UserObj, Roles => [qw(HeldBy)], Title => loc('My Assets'), HasResults => $HasResults &> <%ARGS> $HasResults => undef rt-5.0.1/share/html/Elements/ShowReminders000644 000765 000024 00000010077 14005011336 021344 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % if ( $reminders->Count ) { <%PERL> my $i =0; while ( my $reminder = $reminders->Next ) { $i++; my $dueobj = $reminder->DueObj; my $overdue = $dueobj->IsSet && $dueobj->Diff < 0 ? 1 : 0; my $targets = RT::Tickets->new($session{'CurrentUser'}); $targets->{'allow_deleted_search'} = 1; $targets->FromSQL( "ReferredToBy = " . $reminder->id ); if ( my $ticket= $targets->First ) { $$HasResults++ if $HasResults; % } else { % } % }
      <&|/l&>Reminder <&|/l&>Due <&|/l&>Ticket
      <% $reminder->Subject %> <% $overdue ? '' : '' |n %><% $dueobj->AgeAsString || loc('Not set') %><% $overdue ? '' : '' |n %> #<% $ticket->Id %>: <% $ticket->Subject %>
      Couldn't find Ticket for reminder <% $reminder->id %>. Please contact administrator.
      % } <%INIT> my $reminders = RT::Tickets->new($session{'CurrentUser'}); my $tsql = 'Type = "reminder"' . ' AND ( Owner = "Nobody" OR Owner ="' . $session{'CurrentUser'}->id . '")' . ' AND Status = "__Active__"'; $tsql .= ' AND ( Due < "now" OR Due IS NULL )' if $OnlyOverdue; $reminders->FromSQL($tsql); $reminders->OrderBy( FIELD => 'Due', ORDER => 'ASC' ); # flip HasResults from undef to 0 to indicate there was a search, so # dashboard mail can be suppressed if there are no results $$HasResults = 0 if $HasResults && !defined($$HasResults); <%ARGS> $OnlyOverdue => 0 $HasResults => undef rt-5.0.1/share/html/Elements/RT__CustomRole/000755 000765 000024 00000000000 14005011336 021463 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/Error000644 000765 000024 00000005370 14005011336 017644 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $m->callback( %ARGS, error => $error ); % unless ($SuppressHeader) { <& /Elements/Header, Title => $Title &> <& /Elements/Tabs &> % } <& /Elements/ListActions, actions => $Actions &>
      <%$Why%>
      <%$Details%>
      <& /Elements/Footer &> % $m->abort; <%args> $Actions => [] $Details => '' $Title => loc("RT Error") $Why => loc("the calling component did not specify why"), $SuppressHeader => 0, <%INIT> my $error = $Why; $error .= " ($Details)" if defined $Details && length $Details; $RT::Logger->error( $error ); if ( $session{'REST'} ) { $r->content_type('text/plain; charset=utf-8'); $m->out( "Error: " . $Why . "\n" ); $m->out( $Details . "\n" ) if defined $Details && length $Details; $m->abort(); } rt-5.0.1/share/html/Elements/ShowTransaction000644 000765 000024 00000025761 14005011336 021707 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      <%PERL> $m->comp('/Elements/ShowCustomFields', Object => $Transaction, HideEmpty => 1 ) if $HasTxnCFs; $m->comp( 'ShowTransactionAttachments', %ARGS, Parent => 0 ) if $ShowBody;
      % if ($Transaction->Type eq 'CustomField' && $Transaction->Field ) { % my ($old, $new); % my $cf = RT::CustomField->new( $session{CurrentUser} ); % $cf->SetContextObject( $Transaction->Object ); % $cf->Load( $Transaction->Field ); % if ($cf->Id && $cf->Type =~ /text/i) { % $old = $Transaction->OldValue // loc('(no value)'); % $old = $m->comp('/Elements/ScrubHTML', Content => $old); % $old =~ s|\n|
      |g; % $new = $Transaction->NewValue // loc('(no value)'); % $new = $m->comp('/Elements/ScrubHTML', Content => $new); % $new =~ s|\n|
      |g; % } % } % $m->callback( %ARGS, Transaction => $Transaction, CallbackName => 'AfterContent' );
      <%ARGS> $Transaction $Object => $Transaction->Object $Attachments => undef $AttachmentContent => undef $HasTxnCFs => 1 $ShowBody => 1 $ShowActions => 1 $RowNum => 1 $DisplayPath => undef $AttachmentPath => undef $UpdatePath => undef $ForwardPath => undef $EncryptionPath => undef $EmailRecordPath => undef <%ONCE> <%INIT> my $record_type = $Object->RecordType; my $type_class = $Object->ClassifyTransaction( $Transaction ); $m->callback( CallbackName => 'MassageTypeClass', Transaction => $Transaction, TypeClassRef => \$type_class, ARGSRef => \%ARGS, ); my @classes = ( "transaction", "$record_type-transaction", $type_class, ($RowNum % 2 ? 'odd' : 'even') ); my $desc = $Transaction->BriefDescriptionAsHTML; if ( $Object->id != $Transaction->ObjectId ) { # merged objects $desc = join " - ", $m->interp->apply_escapes( loc("[_1] #[_2]:", loc($record_type), $Transaction->ObjectId), 'h'), $desc; } my $date = $Transaction->CreatedAsString; my $time = ''; $time = loc('[quant,_1,minute,minutes]', $Transaction->TimeTaken) if $Transaction->TimeTaken; if ( $ShowBody && !$Attachments ) { $ARGS{'Attachments'} = $Attachments = {}; my $attachments = $Transaction->Attachments( WithHeaders => 1 ); push @{ $Attachments->{ $_->Parent || 0 } ||= [] }, $_ foreach @{ $attachments->ItemsArrayRef }; } my @actions = (); my $txn_type = $Transaction->Type; if ( $txn_type =~ /EmailRecord$/ ) { my $alt = loc('Show email contents'); push @actions, { title => qq[], target => '_blank', path => $EmailRecordPath .'?id='. $Object->id .'&Transaction='. $Transaction->id .'&Attachment='. ( $Attachments->{0}[0] && $Attachments->{0}[0]->id ), } if $EmailRecordPath; $ShowBody = 0; } elsif ($txn_type eq 'CustomField' && $Transaction->Field) { my $cf = RT::CustomField->new( $session{CurrentUser} ); $cf->SetContextObject( $Transaction->Object ); $cf->Load( $Transaction->Field ); if ($cf->Id && $cf->Type =~ /text/i) { push @actions, { class => 'toggle-txn-details', title => loc('Show Details'), path => '#' }; } } # If the transaction has anything attached to it at all elsif ( %$Attachments && $ShowActions ) { my %has_right = map { $_ => RT::ACE->CanonicalizeRightName( $_ . $record_type ) } qw(Modify CommentOn ReplyTo); $has_right{'Forward'} = RT::ACE->CanonicalizeRightName('ForwardMessage'); my $can_modify = $has_right{'Modify'} && $Object->CurrentUserHasRight( $has_right{'Modify'} ); if ( $UpdatePath && $has_right{'ReplyTo'} && ( $can_modify || $Object->CurrentUserHasRight( $has_right{'ReplyTo'} ) ) ) { my $alt = loc('Reply'); push @actions, { class => "reply-link", title => qq[], path => $UpdatePath .'?id='. $Object->id .'&QuoteTransaction='. $Transaction->id .'&Action=Respond' , }; } if ( $UpdatePath && $has_right{'CommentOn'} && ( $can_modify || $Object->CurrentUserHasRight( $has_right{'CommentOn'} ) ) ) { my $alt = loc('Comment'); push @actions, { class => "comment-link", title => qq[], path => $UpdatePath .'?id='. $Object->id .'&QuoteTransaction='. $Transaction->id .'&Action=Comment' , }; } if ( $ForwardPath && $has_right{'Forward'} && $Object->CurrentUserHasRight( $has_right{'Forward'} ) ) { my $alt = loc('Forward'); push @actions, { class => "forward-link", title => qq[], path => $ForwardPath .'?id='. $Object->id .'&QuoteTransaction='. $Transaction->id , }; } if ( $EncryptionPath && $can_modify && RT->Config->Get('Crypt')->{'Enable'} && RT->Config->Get('Crypt')->{'AllowEncryptDataInDB'} ) { my $alt = loc('Encrypt/Decrypt'); push @actions, { class => "encryption-link", title => qq[], path => $EncryptionPath .'?id='. $Transaction->id .'&QuoteTransaction='. $Transaction->id , }; } } my $CreatorObj = $Transaction->CreatorObj; $m->callback( %ARGS, Transaction => $Transaction, Object => $Object, Classes => \@classes, Actions => \@actions, Created => \$date, TimeTaken => \$time, Description => \$desc, ShowBody => \$ShowBody, CreatorObj => \$CreatorObj, ); my $actions = ''; if ( @actions ) { my $i = $m->interp; foreach my $a ( @actions ) { $a = '{'target'} ? ' target="'. $i->apply_escapes( $a->{'target'}, 'h' ) .'"' : '' ) . ($a->{'class'} ? ' class="'. $i->apply_escapes( $a->{'class'}, 'h' ) .'"' : '' ) .'>'. $a->{'title'} .'' ; } $actions = join ' ', @actions; } # make date unbreakable $date = $m->interp->apply_escapes( $date, 'h' ); $date =~ s/\s/ /g; rt-5.0.1/share/html/Elements/ShowTransactionAttachments000644 000765 000024 00000031034 14005011336 024071 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%PERL> # Find all the attachments which have parent $Parent # For each of these attachments foreach my $message ( @{ $Attachments->{ $Parent || 0 } || [] } ) { $m->comp( 'ShowMessageHeaders', WarnUnsigned => $WarnUnsigned, Message => $message, DisplayHeaders => \@DisplayHeaders, ); my $name = defined $message->Filename && length $message->Filename ? $message->Filename : ''; my $should_render_download = $message->ContentLength || $name; $m->callback(CallbackName => 'BeforeAttachment', ARGSRef => \%ARGS, Object => $Object, Transaction => $Transaction, Attachment => $message, Name => $name, ShouldRenderDownload => \$should_render_download); if ($should_render_download) {
      % if (my $url = RT->System->ExternalStorageURLFor($message)) { ContentType, $message->FriendlyContentLength ); alt="<% $download_alt %>" data-toggle="tooltip" data-placement="bottom" data-original-title="<% $download_alt %>"> <% $name %> % } % else { # view source and view source headers, without filename or size % my $view_source_alt = loc( 'View source' ); > % if ( $DownloadableHeaders && ! length $name && $message->ContentType =~ /text/ ) { % my $download_with_headers_alt = loc('View source with headers'); % } % } % $m->callback(CallbackName => 'AfterDownloadLinks', ARGSRef => \%ARGS, Object => $Object, Transaction => $Transaction, Attachment => $message);
      % } %# If there is sub-messages, open a dedicated div % if ( $Attachments->{ $message->id } ) {
      % } else {
      % } <%PERL> $render_attachment->( $message ); $m->comp( $m->current_comp, %ARGS, Parent => $message->id, ParentObj => $message, displayed_inline => $displayed_inline, );
      % } <%ARGS> $Transaction $Object => $Transaction->Object $ShowHeaders => 0 $DownloadableHeaders => 1 $AttachmentPath => undef $Attachments => {} $AttachmentContent => {} $Parent => 0 $ParentObj => undef $WarnUnsigned => 0 # Keep track of CID images we display inline $displayed_inline => {} <%INIT> my @DisplayHeaders=qw(_all); if ( $Transaction->Type =~ /EmailRecord$/ ) { @DisplayHeaders = qw(To Cc Bcc); } # If the transaction has anything attached to it at all elsif (!$ShowHeaders) { @DisplayHeaders = qw(To From RT-Send-Cc Cc Bcc RT-Attach Date Subject); push @DisplayHeaders, 'RT-Send-Bcc' if RT->Config->Get('ShowBccHeader'); } $m->callback(CallbackName => 'MassageDisplayHeaders', DisplayHeaders => \@DisplayHeaders, Transaction => $Transaction, ShowHeaders => $ShowHeaders); 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'); if ( $disposition && $disposition =~ /^\s*attachment/i ) { $disposition = 'attachment'; } else { $disposition = 'inline'; } # If it's text if ( $content_type =~ m{^(text|message)/} ) { my $max_size = RT->Config->Get( 'MaxInlineBody', $session{'CurrentUser'} ); if ( $disposition ne 'inline' ) { $m->out('

      '. loc( 'Message body is not shown because sender requested not to inline it.' ) .'

      '); return; } elsif ( length $name && RT->Config->Get('SuppressInlineTextFiles', $session{'CurrentUser'} ) ) { $m->out('

      '. loc( 'Text file is not shown because it is disabled in preferences.' ) .'

      '); return; } elsif ( $max_size && $message->ContentLength > $max_size ) { $m->out('

      '. loc( 'Message body is not shown because it is too large.' ) .'

      '); return; } if ( # it's a toplevel object !$ParentObj # or its parent isn't a multipart alternative || ( $ParentObj->ContentType !~ m{^multipart/(?:alternative|related)$}i ) # or it's of our prefered alterative type || ( ( RT->Config->Get('PreferRichText', $session{CurrentUser}) && ( $content_type =~ m{^text/(?:html|enriched)$} ) ) || ( !RT->Config->Get('PreferRichText', $session{CurrentUser}) && ( $content_type !~ m{^text/(?:html|enriched)$} ) ) ) ) { my $content; # If we've cached the content, use it from there if (my $x = $AttachmentContent->{ $Transaction->id }->{$message->id}) { $content = $x->Content; } else { $content = $message->Content; } $RT::Logger->debug( "Rendering attachment #". $message->id ." of '$content_type' type" ); my $skip_quote_folding; $m->callback( CallbackName => 'ModifyContent', ARGSRef => \%ARGS, Object => $Object, Transaction => $Transaction, Content => \$content, SkipQuoteFolding => \$skip_quote_folding, ); # if it's a text/html clean the body and show it if ( $content_type eq 'text/html' ) { $content = $m->comp( '/Elements/ScrubHTML', Content => $content ); if (RT->Config->Get('ShowTransactionImages')) { my @rewritten = RT::Interface::Web::RewriteInlineImages( Content => \$content, Attachment => $message, # Not technically correct to search all parts of the # MIME structure, but it saves having to go to the # database again and is unlikely to break display. Related => [ map { @$_ } values %$Attachments ], AttachmentPath => $AttachmentPath, ); $displayed_inline->{$_}++ for @rewritten; } $m->comp( '/Elements/MakeClicky', content => \$content, html => 1, object => $Object, ); if ( !$skip_quote_folding && !length $name && RT->Config->Get( 'QuoteFolding', $session{CurrentUser} ) ) { eval { require HTML::Quoted; $content = HTML::Quoted->extract($content) }; if ($@) { RT->Logger->error( "HTML::Quoted couldn't process attachment #@{[$message->id]}: $@." . " This is a bug, please report it to rt-bugs\@bestpractical.com."); } } $m->comp( 'ShowMessageStanza', Message => $content, Transaction => $Transaction, ContentType => 'text/html', ); } elsif ( $content_type eq 'text/enriched' ) { $content = $m->comp( '/Elements/ScrubHTML', Content => $content ); $m->out( $content ); } # It's a text type we don't have special handling for else { if ( !$skip_quote_folding && !length $name && RT->Config->Get( 'QuoteFolding', $session{CurrentUser} ) ) { eval { require Text::Quoted; Text::Quoted::set_quote_characters(undef); $content = Text::Quoted::extract($content); }; if ($@) { RT->Logger->error( "Text::Quoted couldn't process attachment #@{[$message->id]}: $@." . " This is a bug, please report it to rt-bugs\@bestpractical.com."); } } $m->comp( 'ShowMessageStanza', Message => $content, Transaction => $Transaction, ContentType => 'text/plain', ); } } } # if it's an image, show it as an image elsif ( $content_type =~ m{^image/} ) { if (not RT->Config->Get('ShowTransactionImages')) { $m->out('

      '. loc( 'Image not shown because display is disabled in system configuration.' ) .'

      '); return; } elsif ( $displayed_inline->{$message->Id} ) { $m->out('

      '. loc( 'Image displayed inline above' ) .'

      '); return; } elsif ( $disposition ne 'inline' ) { $m->out('

      '. loc( 'Image not shown because sender requested not to inline it.' ) .'

      '); return; } my $filename = length $name ? $name : loc('(untitled)'); my $efilename = $m->interp->apply_escapes( $filename, 'h' ); my $url = RT->System->ExternalStorageURLFor($message) || $AttachmentPath .'/'. $Transaction->Id .'/'. $message->Id .'/' . $m->interp->apply_escapes( $filename, 'u', 'h' ); $m->out( qq{$efilename} ); } elsif ( $message->ContentLength && $message->ContentLength > 0 ) { $m->out( '

      ' . loc( 'Message body not shown because it is not plain text.' ) . '

      ' ); } }; rt-5.0.1/share/html/Elements/SelectLifecycle000644 000765 000024 00000005072 14005011336 021611 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Name => '' $Default => '' $DefaultValue => 1 $DefaultLabel => '-' @Lifecycles => RT::Lifecycle->List rt-5.0.1/share/html/Elements/QueueSummaryByStatus000644 000765 000024 00000011201 14005011336 022702 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      % for my $status ( @statuses ) { % } <%PERL> my $i = 0; for my $queue (@$queues) { $i++; my $lifecycle = $lifecycle{ lc $queue->{'Lifecycle'} }; <%perl> for my $status (@statuses) { if ( $lifecycle->IsValid( $status ) ) { % } else { % } % } % }
      <&|/l&>Queue<% loc($status) %>
      <% $queue->{Name} %> <% $data->{$queue->{Id}}->{lc $status} || '-' %> -
      <%INIT> my $build_search_link = sub { my ($queue_name, $extra_query) = @_; $queue_name =~ s/(['\\])/\\$1/g; #' return RT->Config->Get('WebPath') . "/Search/Results.html?Query=" . $m->interp->apply_escapes("Queue = '$queue_name' AND $extra_query", 'u'); }; my $link_all = sub { my ($queue) = @_; return $build_search_link->($queue->{Name}, "Status = '__Active__'"); }; my $link_status = sub { my ($queue, $status) = @_; $status =~ s{(['\\])}{\\$1}g; return $build_search_link->($queue->{Name}, "Status = '$status'"); }; $m->callback( CallbackName => 'LinkBuilders', build_search_link => \$build_search_link, link_all => \$link_all, link_status => \$link_status, ); my %lifecycle; for my $queue (@$queues) { my $cycle = RT::Lifecycle->Load( Name => $queue->{'Lifecycle'} ); RT::Logger->error('Unable to load lifecycle for ' . $queue->{'Lifecycle'}) unless $cycle; $lifecycle{ lc $cycle->Name } = $cycle; } my @statuses; my %seen; foreach my $set ( 'initial', 'active' ) { foreach my $lifecycle ( map $lifecycle{$_}, sort keys %lifecycle ) { push @statuses, grep !$seen{ lc $_ }++, $lifecycle->Valid($set); } } my $data = {}; my $statuses = {}; use RT::Report::Tickets; my $report = RT::Report::Tickets->new( RT->SystemUser ); my $query = "(Status = '__Active__') 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 ) { $data->{ $entry->__Value("Queue") }->{ $entry->__Value("Status") } = $entry->__Value('id'); $statuses->{ $entry->__Value("Status") } = 1; } <%ARGS> $queues => undef rt-5.0.1/share/html/Elements/ShowCustomFieldImage000644 000765 000024 00000004542 14005011336 022575 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <% $Object->Content %>
      <%ARGS> $Object <%INIT> my $url = RT->System->ExternalStorageURLFor($Object) || RT->Config->Get('WebPath') . "/Download/CustomFieldValue/".$Object->Id.'/'.$m->interp->apply_escapes($Object->Content, 'u'); rt-5.0.1/share/html/Elements/ShowCustomFieldText000644 000765 000024 00000004303 14005011336 022472 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%init> my $content = $Object->LargeContent || $Object->Content; $content = $m->comp('/Elements/ScrubHTML', Content => $content); $content =~ s|\n|
      |g; <%$content|n%> <%ARGS> $Object rt-5.0.1/share/html/Elements/EditCustomFieldImage000644 000765 000024 00000006115 14005011336 022540 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % while ($Values and my $value = $Values->Next ) {
      % } % if ($MaxValues && $Values && $Values->Count >= $MaxValues ) {
      <&|/l&>Reached maximum number, so new values will override old ones.
      % }
      <%INIT> my $name = $Name || $NamePrefix . $CustomField->Id . '-Upload'; my $delete_name = $name; $delete_name =~ s!-Upload$!-DeleteValueIds!; <%ARGS> $Object => undef $CustomField => undef $NamePrefix => undef $Name => undef $Default => undef $Values => undef $MaxValues => undef rt-5.0.1/share/html/Elements/QueriesAsComment000644 000765 000024 00000004536 14005011336 022002 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%INIT> return unless RT->Config->Get('StatementLog') && $session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'SuperUser'); rt-5.0.1/share/html/Elements/RT__ScripAction/000755 000765 000024 00000000000 14005011336 021605 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Elements/TSVExport000644 000765 000024 00000010312 14005011336 020421 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <%ARGS> $Class => undef $Collection $Format $PreserveNewLines => 0 $Filename => undef <%ONCE> my $no_html = HTML::Scrubber->new( deny => '*' ); <%INIT> require HTML::Entities; $Class ||= $Collection->ColumnMapClassName; $r->content_type('application/vnd.ms-excel'); $r->header_out( 'Content-disposition' => "attachment; filename=$Filename" ) if $Filename; my $DisplayFormat = $m->comp('/Elements/ScrubHTML', Content => $Format); my @Format = $m->comp('/Elements/CollectionAsTable/ParseFormat', Format => $DisplayFormat); my @columns; my $should_loc = { map { $_ => 1 } qw(Status) }; my $col_entry = sub { my $col = shift; # in tsv output, "#" is often a comment character but we use it for "id" delete $col->{title} if $col->{title} and $col->{title} =~ /^\s*#\s*$/; return { header => loc($col->{title} || $col->{attribute}), map => $m->comp( "/Elements/ColumnMap", Name => $col->{attribute}, Attr => 'value', Class => $Class, ), should_loc => $should_loc->{$col->{attribute}}, } }; if ($PreserveNewLines) { my $col = []; push @columns, $col; for (@Format) { if ($_->{title} eq 'NEWLINE') { $col = []; push @columns, $col; } else { push @$col, $col_entry->($_); } } } else { push @columns, [map { $_->{attribute} ? $col_entry->($_) : () } @Format]; } for (@columns) { $m->out(join("\t", map { $_->{header} } @$_)."\n"); } my $i = 0; my $ii = 0; while (my $row = $Collection->Next) { for my $col (@columns) { $m->out(join("\t", map { my $val = ProcessColumnMapValue($_->{map}, Arguments => [$row, $ii++], Escape => 0); $val = loc($val) if $_->{should_loc}; # remove tabs from all field values, they screw up the tsv $val = '' unless defined $val; $val =~ s/(?:\n|\r)+/ /g; $val =~ s{\t}{ }g; $val = $no_html->scrub($val); $val = HTML::Entities::decode_entities($val); $val; } @$col)."\n"); } $m->flush_buffer unless ++$i % 10; } $m->abort(); rt-5.0.1/share/html/Elements/Login000644 000765 000024 00000010110 14005011336 017607 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} % $m->callback( %ARGS, CallbackName => 'Header' ); <& /Elements/Header, Title => loc('Login'), Focus => '#user', RichText => 0 &> % # Close div for rt-header-container started in Header
      <& /Widgets/TitleBoxEnd &>
      <%ARGS> $id => undef <%INIT> Abort( loc('No transaction specified') ) unless $id; my $txn = LoadTransaction($id); Abort( loc('No permission to view transaction'), Code => HTTP::Status::HTTP_FORBIDDEN ) unless $txn->CurrentUserCanSee; rt-5.0.1/share/html/Prefs/DashboardsInMenu.html000644 000765 000024 00000017275 14005011336 022216 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => $title &> <& /Elements/Tabs &> <& /Elements/ListActions, actions => \@results &>
      <& /Widgets/SearchSelection, pane_name => { dashboard => loc('Dashboards in menu') }, sections => [{ id => 'dashboards', label => loc("Dashboards"), items => \@dashboard_components, }], selected => \%selected, filters => [], &> <& /Elements/Submit, Name => "UpdateSearches", Label => loc('Save') &>
      <&|/Widgets/TitleBox, title => loc("Reset dashboards in menu"), class => "mx-auto max-width-lg" &>
      <& /Widgets/SearchSelection, pane_name => { report => loc('Reports in menu') }, sections => [{ id => 'reports', label => loc("Reports"), items => \@report_components, }], selected => \%selected, filters => [], &> <& /Elements/Submit, Name => "UpdateSearches", Label => loc('Save') &>
      <&|/Widgets/TitleBox, title => loc("Reset reports in menu"), class => "mx-auto max-width-lg" &>
      <%INIT> my @results; my $title = loc("Customize").' '.loc("reports menu"); my $user = $session{'CurrentUser'}->UserObj; if ( $ARGS{ResetDashboards} ) { # Empty DashboardsInMenu pref means to use system default. my ($ok, $msg) = $user->SetPreferences('DashboardsInMenu', {}); push @results, $ok ? loc('Preferences saved.') : $msg; delete $session{'dashboards_in_menu'}; } if ( $ARGS{ResetReports} ) { if ( $user->Preferences('ReportsInMenu') ) { # Empty ReportsInMenu pref means empty, not to use default, # thus we need to delete preference instead. my ( $ok, $msg ) = $user->DeletePreferences('ReportsInMenu'); push @results, $ok ? loc('Preferences saved.') : $msg; delete $session{'reports_in_menu'}; } } my @dashboard_components = map {{ type => 'dashboard', id => $_->id, name => $_->Name, label => $_->Name }} $m->comp("/Dashboards/Elements/ListOfDashboards", IncludeSuperuserGroups => 0 ); my @report_components = map { {%$_, name => $_->{'title'}, type => 'report', label => $_->{'title'} } } @{ ListOfReports() }; if ($ARGS{UpdateSearches}) { if ( $ARGS{'dashboard_id'} eq 'DashboardsInMenu' ) { my @dashboard_ids; my $dashboard_names = ref $ARGS{'dashboard'} eq 'ARRAY' ? $ARGS{'dashboard'} : [$ARGS{'dashboard'}]; foreach my $name ( @{$dashboard_names} ) { next unless $name; $name =~ s/dashboard-//; my $attribute = RT::Attribute->new( $session{'CurrentUser'} ); my ($ret, $msg) = $attribute->LoadByCols( Name => 'Dashboard', Description => $name ); if ( $ret ) { push @dashboard_ids, $attribute->Id; } else { RT::Logger->error( "Could not load dashboard: $msg" ); push @results, "Could not load dashboard $name"; } } my ( $ok, $msg ) = $user->SetPreferences( $ARGS{'dashboard_id'}, { 'dashboards' => \@dashboard_ids } ); push @results, $ok ? loc('Preferences saved for dashboards in menu.') : $msg; delete $session{'dashboards_in_menu'}; } else { my $report_names = ref $ARGS{'report'} eq 'ARRAY' ? $ARGS{'report'} : [$ARGS{'report'}]; my @ret; foreach my $report ( @report_components ) { last unless $report_names->[0]; push @ret, $report if grep { $_ =~ /report-$report->{'name'}/ } @{$report_names}; } my ( $ok, $msg ) = $user->SetPreferences( $ARGS{'dashboard_id'}, \@ret ); push @results, $ok ? loc('Preferences saved for reports in menu.') : $msg; delete $session{'reports_in_menu'}; } } my %selected; my ($default_dashboards) = RT::System->new( $session{'CurrentUser'} )->Attributes->Named('DashboardsInMenu'); my $dashboard_pref = $user->Preferences( 'DashboardsInMenu', $default_dashboards ? $default_dashboards->Content : () ); # prefs returns an array of Dashboard ID's my $selected_dashboard_ids = $dashboard_pref->{'dashboards'} || []; my @selected_dashboards; foreach my $id ( @{$selected_dashboard_ids} ) { my $attribute = RT::Attribute->new( RT->SystemUser ); my ($ret, $msg) = $attribute->Load( $id ); unless ( $ret ) { RT::Logger->error( "Could not load dashboard: $id" ); next; } push @selected_dashboards, { id => $id, name => $attribute->Description, label => $attribute->Description, type => 'dashboard' }; } $selected{'dashboard'} = \@selected_dashboards; my $report_pref = $user->Preferences('ReportsInMenu'); unless ($report_pref) { my ($defaults) = RT::System->new( $session{'CurrentUser'} )->Attributes->Named('ReportsInMenu'); $report_pref = $defaults ? $defaults->Content : []; } my @report_pref_sanitized = map { {%$_, name => $_->{'title'}, type => 'report', label => $_->{'title'} } } @{ $report_pref }; $selected{'report'} = \@report_pref_sanitized || []; rt-5.0.1/share/html/Prefs/AuthTokens.html000644 000765 000024 00000004476 14005011336 021114 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => loc('My authentication tokens') &> <& /Elements/Tabs &> <& /Elements/ListActions, actions => \@results &> <& /Elements/AuthToken/List, %ARGS, Owner => $session{'CurrentUser'}->Id &> <%INIT> my @results = ProcessAuthToken(ARGSRef => \%ARGS); MaybeRedirectForResults( Actions => \@results ); rt-5.0.1/share/html/Prefs/CustomDateRanges.html000644 000765 000024 00000007400 14005011336 022225 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => loc('Custom Date Ranges') &> <& /Elements/Tabs &> <& /Elements/ListActions, actions => \@results &> <&|/Widgets/TitleBox, title => loc('System Custom Date Ranges'), class => 'mx-auto max-width-xl' &> % if ( keys %{$system_config->{'RT::Ticket'}} ) { <& /Elements/ShowCustomDateRanges, CustomDateRanges => $system_config->{'RT::Ticket'} || {}, ObjectType => 'RT::Ticket' &> % } % else {

      <&|/l&>No system custom date ranges

      % } <&|/Widgets/TitleBox, title => loc('Other Users Custom Date Ranges'), class => 'mx-auto max-width-xl' &> % if ( keys %{$user_config->{'RT::Ticket'}} ) { <& /Elements/ShowCustomDateRanges, CustomDateRanges => $user_config->{'RT::Ticket'} || {}, ObjectType => 'RT::Ticket' &> % } % else {

      <&|/l&>No other users custom date ranges

      % }
      <&|/Widgets/TitleBox, title => loc('My Custom Date Ranges'), class => 'mx-auto max-width-xl' &> <& /Elements/EditCustomDateRanges, CustomDateRanges => $content->{'RT::Ticket'} || {}, ObjectType => 'RT::Ticket' &> <& /Elements/Submit, Name => 'Save', Label => loc('Save Changes') &>
      <%INIT> my $system_config = { 'RT::Ticket' => { RT::Ticket->CustomDateRanges( ExcludeUsers => 1 ) } }; my $user_config = { 'RT::Ticket' => { RT::Ticket->CustomDateRanges( ExcludeSystem => 1, ExcludeUser => $session{CurrentUser}->Id ) } }; my $content = $session{CurrentUser}->Preferences('CustomDateRanges'); my @results; if ($Save) { push @results, ProcessCustomDateRanges( ARGSRef => \%ARGS, UserPreference => 1 ); } MaybeRedirectForResults( Actions => \@results, Path => '/Prefs/CustomDateRanges.html', ); <%ARGS> $Save => undef rt-5.0.1/share/html/Prefs/MyRT.html000644 000765 000024 00000016576 14005011336 017666 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => $title &> <& /Elements/Tabs &> <& /Elements/ListActions, actions => \@results &>
      <& /Widgets/SearchSelection, pane_name => \%pane_name, sections => \@sections, selected => \%selected, filters => \@filters, &> <& /Elements/Submit, Name => "UpdateSearches", Label => loc('Save') &>
      <&|/Widgets/TitleBox, title => loc('Options'), bodyclass => "", class => "mx-auto max-width-lg" &>
      <&|/l&>Rows per box:
      <&|/Widgets/TitleBox, title => loc("Reset RT at a glance"), class => "mx-auto max-width-lg" &>
      <%INIT> my @results; my $title = loc("Customize").' '.loc("RT at a glance"); my $user = $session{'CurrentUser'}->UserObj; if ( $ARGS{'UpdateSummaryRows'} ) { unless ( $ARGS{SummaryRows} && int $ARGS{SummaryRows} > 0 ) { push @results, loc ("Illegal '[_1]' preference value.", loc('summary rows')); $ARGS{SummaryRows} = 0; } else { my ($ok, $msg) = $user->SetPreferences( 'SummaryRows', int $ARGS{SummaryRows} ); push @results, $ok ? loc('Preferences saved for [_1].', loc('summary rows')) : $msg; } } $ARGS{'SummaryRows'} ||= $user->Preferences('SummaryRows', RT->Config->Get('DefaultSummaryRows')); if ($ARGS{Reset}) { for my $pref_name ('HomepageSettings', 'SummaryRows') { next unless $user->Preferences($pref_name); my ($ok, $msg) = $user->DeletePreferences($pref_name); push @results, $msg unless $ok; } push @results, loc('Preferences saved.') unless @results; } my $portlets = $user->Preferences('HomepageSettings'); unless ($portlets) { my ($defaults) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings'); $portlets = $defaults ? $defaults->Content : {}; } my @sections; my %item_for; my @components = map { type => "component", name => $_, label => loc($_) }, @{RT->Config->Get('HomepageComponents')}; $item_for{ $_->{type} }{ $_->{name} } = $_ for @components; push @sections, { id => 'components', label => loc("Components"), items => \@components, }; my $sys = RT::System->new($session{'CurrentUser'}); my @objs = ($sys); push @objs, RT::SavedSearch->new( $session{CurrentUser} )->ObjectsForLoading if $session{'CurrentUser'}->HasRight( Right => 'LoadSavedSearch', Object => $RT::System ); for my $object (@objs) { my @items; my $object_id = ref($object) . '-' . $object->Id; for ($m->comp("/Search/Elements/SearchesForObject", Object => $object)) { my ($desc, $loc_desc, $search) = @$_; my $SearchType = 'Ticket'; if ((ref($search->Content)||'') eq 'HASH') { $SearchType = $search->Content->{'SearchType'} if $search->Content->{'SearchType'}; } else { $RT::Logger->debug("Search ".$search->id." ($desc) appears to have no Content"); } my $item; if ($object eq $sys && $SearchType eq 'Ticket') { $item = { type => 'system', name => $desc, label => $loc_desc }; } else { my $oid = $object_id.'-SavedSearch-'.$search->Id; $item = { type => 'saved', name => $oid, search_type => $SearchType, label => $loc_desc }; } $item_for{ $item->{type} }{ $item->{name} } = $item; push @items, $item; } my $label = $object eq $sys ? loc('System') : $object->isa('RT::Group') ? $object->Label : $object->Name; push @sections, { id => $object_id, label => $label, items => [ sort { lc($a->{label}) cmp lc($b->{label}) } @items ], }; } my %selected; for my $pane (keys %$portlets) { my @items; for my $saved (@{ $portlets->{$pane} }) { my $item = $item_for{ $saved->{type} }{ $saved->{name} }; if ($item) { push @items, $item; } else { push @results, loc('Unable to find [_1] [_2]', $saved->{type}, $saved->{name}); } } $selected{$pane} = \@items; } my %pane_name = ( 'body' => loc('Body'), 'sidebar' => loc('Sidebar'), ); my @filters = ( [ 'component' => loc('Components') ], [ 'ticket' => loc('Tickets') ], [ 'chart' => loc('Charts') ], ); $m->callback( CallbackName => 'Default', pane_name => \%pane_name, sections => \@sections, selected => \%selected, filters => \@filters, ); if ($ARGS{UpdateSearches}) { my ($ok, $msg) = UpdateDashboard( \%ARGS, \%item_for ); push @results, $ok ? loc('Preferences saved.') : $msg; MaybeRedirectForResults( Actions => \@results, Path => "/Prefs/MyRT.html", ); } rt-5.0.1/share/html/Prefs/Elements/000755 000765 000024 00000000000 14005011336 017702 5ustar00sunnavystaff000000 000000 rt-5.0.1/share/html/Prefs/QueueList.html000644 000765 000024 00000010570 14005011336 020737 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => $title &> <& /Elements/Tabs &> <& /Elements/ListActions, actions => \@actions &>

      <&|/l&>Select queues to be displayed on the "RT at a glance" page

        % for my $queue (@queues) {
      • {$queue->Name}) { checked="checked" % } />
      • % }
      <& /Elements/Submit, Caption => loc("Save Changes"), Label => loc('Save'), Name => 'Save', Reset => 1, CheckAll => 1, ClearAll => 1, CheckboxNameRegex => '/^Want-/', &>
      <%INIT> my @actions; my $title = loc("Customize").' '.loc("Queue list"); my $user = $session{'CurrentUser'}->UserObj; my $unwanted = $user->Preferences('QueueList', {}); my $Queues = RT::Queues->new($session{'CurrentUser'}); $Queues->UnLimit; my $right = 'ShowTicket'; $m->callback( CallbackName => 'ModifyQueues', Queues => \$Queues, Right => \$right, Unwanted => $unwanted, ); my @queues = grep { $right ? $_->CurrentUserHasRight($right) : 1 } @{$Queues->ItemsArrayRef}; if ($ARGS{'Save'}) { for my $queue (@queues) { if ($ARGS{"Want-".$queue->Name}) { delete $unwanted->{$queue->Name}; } else { ++$unwanted->{$queue->Name}; } } my ($ok, $msg) = $user->SetPreferences('QueueList', $unwanted); push @actions, $ok ? loc('Preferences saved.') : $msg; # Clear queue caches if ( $ok ){ # Clear for 'CreateTicket' my $cache_key = GetObjectSessionCacheKey( ObjectType => 'RT::Queue', CheckRight => 'CreateTicket', ShowAll => 0 ); delete $session{$cache_key}; # Clear for 'ShowTicket' $cache_key = GetObjectSessionCacheKey( ObjectType => 'RT::Queue', CheckRight => 'ShowTicket', ShowAll => 0 ); delete $session{$cache_key}; } } rt-5.0.1/share/html/Prefs/AboutMe.html000644 000765 000024 00000011473 14005011336 020356 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title=>loc("Preferences") &> <& /Elements/Tabs &> <& /Elements/ListActions, actions => \@results &> <& Elements/EditAboutMe, UserObj => $UserObj, PasswordName => [ qw(CurrentPass Pass1 Pass2) ] &> % $m->callback( %ARGS, UserObj => $UserObj, CallbackName => 'FormEnd' ); <%INIT> my $UserObj = RT::User->new( $session{'CurrentUser'} ); $UserObj->Load($id) if $id; $UserObj->Load($Name) if $Name && !$UserObj->id; unless ( $UserObj->id ) { Abort(loc("Couldn't load user #[_1] or user '[_2]'", $id, $Name)) if $id && $Name; Abort(loc("Couldn't load user #[_1]", $id)) if $id; Abort(loc("Couldn't load user '[_1]'", $Name)) if $Name; Abort(loc("Couldn't load user")); } $id = $UserObj->id; my @results; if ( $ARGS{'ResetAuthToken'} ) { my ($status, $msg) = $UserObj->GenerateAuthToken; push @results, $msg; } else { my @fields = qw( Name Comments Signature EmailAddress FreeformContactInfo Organization RealName NickName Lang Gecos HomePhone WorkPhone MobilePhone PagerPhone Address1 Address2 City State Zip Country Timezone ); $m->callback( CallbackName => 'UpdateLogic', fields => \@fields, results => \@results, UserObj => $UserObj, ARGSRef => \%ARGS, ); push @results, UpdateRecordObject ( AttributesRef => \@fields, Object => $UserObj, ARGSRef => \%ARGS, ); push @results, ProcessObjectCustomFieldUpdates( ARGSRef => \%ARGS, Object => $UserObj ); # Deal with special fields: Privileged, Enabled, and Password if ( $SetPrivileged and $Privileged != $UserObj->Privileged ) { my ($code, $msg) = $UserObj->SetPrivileged( $Privileged ); push @results, loc('Privileged status: [_1]', loc_fuzzy($msg)); } my %password_cond = $UserObj->CurrentUserRequireToSetPassword; if (defined $Pass1 && length $Pass1 ) { my ($status, $msg) = $UserObj->SafeSetPassword( Current => $CurrentPass, New => $Pass1, Confirmation => $Pass2, ); push @results, loc("Password: [_1]", $msg); } } MaybeRedirectForResults( Actions => \@results, ); <%ARGS> $id => $session{'CurrentUser'}->Id $Name => undef $Comments => undef $Signature => undef $EmailAddress => undef $FreeformContactInfo => undef $Organization => undef $RealName => undef $NickName => undef $Privileged => undef $SetPrivileged => undef $Enabled => undef $SetEnabled => undef $Lang => undef $Gecos => undef $HomePhone => undef $WorkPhone => undef $MobilePhone => undef $PagerPhone => undef $Address1 => undef $Address2 => undef $City => undef $State => undef $Zip => undef $Country => undef $CurrentPass => undef $Pass1 => undef $Pass2 => undef $Create=> undef rt-5.0.1/share/html/Prefs/Other.html000644 000765 000024 00000010376 14005011336 020104 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => $title &> <& /Elements/Tabs &> <& /Elements/ListActions, actions => \@results &>
      % 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, %{ $m->comp('/Widgets/FinalizeWidgetArguments', WidgetArguments => $meta->{'WidgetArguments'} ) }, Name => $option, DefaultValue => scalar RT->Config->Get( $option ), CurrentValue => $preferences->{ $option }, &> % } % } % if ( RT->Config->Get('Crypt')->{'Enable'} ) { <&|/Widgets/TitleBox, title => loc( 'Cryptography' ) &> <&|/l&>Preferred key: <& /Elements/Crypt/SelectKeyForEncryption, EmailAddress => $UserObj->EmailAddress, Default => $UserObj->PreferredKey &> % }
      <& /Elements/Submit, Name => 'Update', Label => loc('Save Changes') &>
      <%INIT> my @results; my $title = loc("Preferences"); my $UserObj = $session{'CurrentUser'}->UserObj; my $preferences = $UserObj->Preferences( $RT::System ); if (defined($PreferredKey) and (not $UserObj->FirstAttribute('PreferredKey') or $PreferredKey ne $UserObj->FirstAttribute('PreferredKey')->Content)) { my ($code, $msg) = $UserObj->SetAttribute(Name => 'PreferredKey', Content => $PreferredKey); push @results, loc('Preferred Key: [_1]', $msg) unless $code; } if ( $Update ) { $preferences ||= {}; $m->comp( '/Widgets/BulkProcess', Meta => { map { $_ => RT->Config->Meta($_) } RT->Config->Options }, Store => $preferences, Types => [RT->Config->Options], Default => 1, Arguments => \%ARGS, DefaultValue => { map { $_ => RT->Config->Get($_) } RT->Config->Options }, ); my ($ok, $msg) = $UserObj->SetPreferences( $RT::System, $preferences ); push @results, $ok ? loc("Preferences saved.") : $msg; } <%ARGS> $Update => 0, $User => undef, $PreferredKey => undef, rt-5.0.1/share/html/Prefs/Search.html000644 000765 000024 00000011764 14005011336 020232 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => $title &> <& /Elements/Tabs &> <& /Elements/ListActions, actions => \@actions &> % if ($session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'SuperUser')) {

      <&|/l&>You can also edit the predefined search itself: <% $search->Name %>

      % }
      <& /Search/Elements/DisplayOptions, %$SearchArg, %ARGS, AvailableColumns => $AvailableColumns, CurrentFormat => $CurrentFormat &>
      <& /Elements/Submit, Caption => loc("Save Changes"), Label => loc('Save'), Name => 'Save'&>
      <&|/Widgets/TitleBox, title => loc("Reset") &>
      <%INIT> my @actions; my $title = loc("Customize").' '; my @fields = qw(Format Order OrderBy RowsPerPage); $ARGS{name} ||= ''; my ($class, $id) = ( $ARGS{name} =~ m/^(.*)-(\d+)$/ ); Abort('No search specified') unless defined $class and $class eq 'RT::Attribute'; my $search = $class->new ($session{'CurrentUser'}); $search->LoadById ($id); # If we are resetting prefs, do so before attempting to load them if ($ARGS{'Reset'}) { my ($ok, $msg) = $session{'CurrentUser'}->UserObj->DeletePreferences($ARGS{name}); push @actions, $ok ? loc('Preferences reset.') : $msg; } $title .= loc (RT::SavedSearch->EscapeDescription($search->Description), loc ('"N"')); my $user = $session{'CurrentUser'}->UserObj; my $SearchArg = $user->Preferences($search, $search->Content); $ARGS{Order} = (ref $ARGS{Order} ? join('|',grep {/\S/} @{$ARGS{Order}}) : $ARGS{Order}); $ARGS{OrderBy} = (ref $ARGS{OrderBy} ? join('|',grep {/\S/} @{$ARGS{OrderBy}}) : $ARGS{OrderBy}); for (@fields) { $ARGS{$_} = $SearchArg->{$_} unless defined $ARGS{$_}; } $ARGS{'Order'} = join '|', grep defined && /\S/, (ref $ARGS{'Order'})? @{$ARGS{'Order'}}: $ARGS{'Order'}; $ARGS{'OrderBy'} = join '|', grep defined && /\S/, (ref $ARGS{'OrderBy'})? @{$ARGS{'OrderBy'}}: $ARGS{'OrderBy'}; my ( $AvailableColumns, $CurrentFormat ); ( $ARGS{Format}, $AvailableColumns, $CurrentFormat ) = $m->comp( '/Search/Elements/BuildFormatString', %ARGS ); if ($ARGS{'Save'}) { my $hash = {map { $_ => $ARGS{$_}} @fields}; my ($ok, $msg) = $user->SetPreferences($search, $hash); push @actions, $ok ? loc('Preferences saved.') : $msg; } rt-5.0.1/share/html/Prefs/SearchOptions.html000644 000765 000024 00000011013 14005011336 021571 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}} <& /Elements/Header, Title => loc("Search Preferences") &> <& /Elements/Tabs &> <& /Elements/ListActions, actions => \@actions &>
      <& /Search/Elements/DisplayOptions, %ARGS, Format=> $Format, AvailableColumns => $AvailableColumns, CurrentFormat => $CurrentFormat, RowsPerPage => $RowsPerPage, OrderBy => $OrderBy, Order => $Order &>
      <& /Elements/Submit, Name => 'SavePreferences', Label => loc('Save Changes') &>
      % if ($session{'CurrentUser'}->UserObj->Preferences("SearchDisplay")) { <& /Elements/Submit, Name => 'Reset', Label => loc('Restore Default Preferences')&> % }
      <%INIT> my @actions; # If we're saving search preferences, do that now $Order = join '|', grep defined && /\S/, (ref $Order)? @{$Order}: $Order; $OrderBy = join '|', grep defined && /\S/, (ref $OrderBy)? @{$OrderBy}: $OrderBy; $Order = (ref $Order ? join('|',grep {/\S/} @{$Order}) : $Order); $OrderBy = (ref $OrderBy ? join('|',grep {/\S/} @{$OrderBy}) : $OrderBy); if ($ARGS{'SavePreferences'}) { my ($ok, $msg) = $session{'CurrentUser'}->UserObj->SetPreferences("SearchDisplay", { Format => $Format, Order => $Order, OrderBy => $OrderBy, RowsPerPage => $RowsPerPage, }); push @actions, $ok ? loc("Preferences saved.") : $msg; } # If we're restoring default preferences, delete the user's changes if ($Reset) { my ($ok, $msg) = $session{'CurrentUser'}->UserObj->DeletePreferences("SearchDisplay"); push @actions, $ok ? loc("Default preferences restored.") : $msg; } # Read from user preferences my $prefs = $session{'CurrentUser'}->UserObj->Preferences("SearchDisplay") || {}; $Format ||= $prefs->{'Format'}; $Order ||= ($prefs->{'Order'} || RT->Config->Get('DefaultSearchResultOrder')); $OrderBy ||= ($prefs->{'OrderBy'} || RT->Config->Get('DefaultSearchResultOrderBy')); ($RowsPerPage = defined( $prefs->{'RowsPerPage'} ) ? $prefs->{'RowsPerPage'} : 50) unless defined ($RowsPerPage); my ( $AvailableColumns, $CurrentFormat ); ( $Format, $AvailableColumns, $CurrentFormat ) = $m->comp( '/Search/Elements/BuildFormatString', %ARGS, Format => $Format ); <%ARGS> $Format => undef $Description => undef $Order => undef $OrderBy => undef $RowsPerPage => undef $Reset => undef rt-5.0.1/share/html/Prefs/Elements/EditAboutMe000644 000765 000024 00000023231 14005011336 021770 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      %### Left column ###
      <&| /Widgets/TitleBox, title => loc('Identity'), id => "user-prefs-identity" &>
      <&|/l&>Email:
      <&|/l&>Real Name:
      <&|/l&>Nickname:
      <&|/l&>Language:
      <& /Elements/SelectLang, Name => 'Lang', Default => $UserObj->Lang &>
      <&|/l&>Timezone:
      <& /Elements/SelectTimezone, Name => 'Timezone', Default => $UserObj->Timezone &>
      <& /Elements/EditCustomFields, Object => $UserObj, Grouping => 'Identity' &>
      <&| /Widgets/TitleBox, title => loc('Phone numbers'), id => "user-prefs-phone" &>
      <&|/l&>Residence:
      <&|/l&>Work:
      <&|/l&>Mobile:
      <&|/l&>Pager:
      <& /Elements/EditCustomFields, Object => $UserObj, Grouping => 'Phones' &>
      % $m->callback( %ARGS, UserObj => $UserObj, CallbackName => 'FormLeftColumn' ); %### End left column ###
      %### Right column ###
      <&| /Widgets/TitleBox, title => loc( $AccessControlName ), id => "user-prefs-access-control" &> % if ( $UserObj->__Value('Password') ne '*NO-PASSWORD*' ) { <& /Elements/EditPassword, User => $UserObj, Name => $PasswordName, &> % } <& /Elements/EditCustomFields, Object => $UserObj, Grouping => 'Access control' &> % my $AdminUser = $session{'CurrentUser'}->HasRight( Object => RT->System, Right => 'AdminUsers' ); <&| /Widgets/TitleBox, title => loc('Location'), id => "user-prefs-location" &>
      <&|/l&>Organization:
      %if ( $AdminUser ) { %} else { <%$UserObj->Organization || ''%> %}
      <&|/l&>Address1:
      <&|/l&>Address2:
      <&|/l&>City:
      <&|/l&>State:
      <&|/l&>Zip:
      <&|/l&>Country:
      <& /Elements/EditCustomFields, Object => $UserObj, Grouping => 'Location' &>
      % $m->callback( %ARGS, UserObj => $UserObj, CallbackName => 'FormRightColumn' ); %### End right column ###
      %if ($UserObj->Privileged) {
      <&| /Widgets/TitleBox, title => loc('Signature'), id => "user-prefs-signature" &>
      % } <& /Elements/EditCustomFieldCustomGroupings, Object => $UserObj &>
      <& /Elements/Submit, Label => loc('Save Preferences') &>
      %if ( $AdminUser ) { <&| /Widgets/TitleBox, title => loc('Secret authentication token'), id => "user-prefs-feeds" &> <&|/l&>All iCal feeds embed a secret token which authorizes you. If the URL for one of your iCal feeds was exposed to the outside world, you can get a new secret, breaking all existing iCal feeds, below. <& /Elements/Submit, Label => loc('Reset secret authentication token'), Name => "ResetAuthToken", id => "ResetAuthTokenContainer" &> %} <%ARGS> $UserObj $PasswordName $AccessControlName => 'Access control' rt-5.0.1/share/html/Prefs/Elements/ShowAboutMe000644 000765 000024 00000016320 14005011336 022024 0ustar00sunnavystaff000000 000000 %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC %# %# %# (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 }}}
      %### Left column ###
      <&| /Widgets/TitleBox, title => loc('Identity'), id => "user-prefs-identity" &>
      <&|/l&>Email:
      <%$UserObj->EmailAddress%>
      <&|/l&>Real Name:
      <%$UserObj->RealName%>
      <&|/l&>Nickname:
      <%$UserObj->NickName || ''%>
      <&|/l&>Language:
      % if ( $UserObj->Lang ) { <&|/l, $lang &>[_1] % } else { <&|/l&>System Default (<% I18N::LangTags::List::name($session{CurrentUser}->LanguageHandle->language_tag) %>) % }
      <&|/l&>Timezone:
      % if ( $UserObj->Timezone ) { <%$UserObj->Timezone%> % } else { <&|/l&>System Default (<% RT->Config->Get('Timezone') %>) % }
      <& /Elements/ShowCustomFields, Object => $UserObj, Grouping => 'Identity' &>
      <&| /Widgets/TitleBox, title => loc('Phone numbers'), id => "user-prefs-phone" &>
      <&|/l&>Residence:
      <%$UserObj->HomePhone || ''%>
      <&|/l&>Work:
      <%$UserObj->WorkPhone || ''%>
      <&|/l&>Mobile:
      <%$UserObj->MobilePhone || ''%>
      <&|/l&>Pager:
      <%$UserObj->PagerPhone || ''%>
      <& /Elements/ShowCustomFields, Object => $UserObj, Grouping => 'Phones' &>
      %### Right column ###
      <&| /Widgets/TitleBox, title => loc('Location'), id => "user-prefs-location" &>
      <&|/l&>Organization:
      <%$UserObj->Organization || ''%>
      <&|/l&>Address1:
      <%$UserObj->Address1 || ''%>
      <&|/l&>Address2:
      <%$UserObj->Address2 || ''%>
      <&|/l&>City:
      <%$UserObj->City || ''%>
      <&|/l&>State:
      <%$UserObj->State || ''%>
      <&|/l&>Zip:
      <%$UserObj->Zip || ''%>
      <&|/l&>Country:
      <%$UserObj->Country || ''%>
      <& /Elements/ShowCustomFields, Object => $UserObj, Grouping => 'Location' &>
      <& /Elements/ShowCustomFieldCustomGroupings, Object => $UserObj &>
      %### End right column ###
      <%INIT> use I18N::LangTags::List; my $lang = I18N::LangTags::List::name( $UserObj->Lang ); <%ARGS> $UserObj rt-5.0.1/lib/RT.pm000644 000765 000024 00000071047 14005011336 014443 0ustar00sunnavystaff000000 000000 # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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; use 5.010; package RT; use Encode (); use File::Spec (); use Cwd (); use Scalar::Util qw(blessed); use UNIVERSAL::require; use vars qw($Config $System $SystemUser $Nobody $Handle $Logger $_Privileged $_Unprivileged $_INSTALL_MODE); use vars qw($BasePath $EtcPath $BinPath $SbinPath $VarPath $FontPath $LexiconPath $StaticPath $PluginPath $LocalPath $LocalEtcPath $LocalLibPath $LocalLexiconPath $LocalStaticPath $LocalPluginPath $MasonComponentRoot $MasonLocalComponentRoot $MasonDataDir $MasonSessionDir); # Set Email::Address module var before anything else loads. # This avoids an algorithmic complexity denial of service vulnerability. # See T#157608 and CVE-2015-7686 for more information. $Email::Address::COMMENT_NEST_LEVEL = 1; RT->LoadGeneratedData(); =head1 NAME RT - Request Tracker =head1 SYNOPSIS A fully featured request tracker package. This documentation describes the point-of-entry for RT's Perl API. To learn more about what RT is and what it can do for you, visit L. =head1 DESCRIPTION =head2 INITIALIZATION If you're using RT's Perl libraries, you need to initialize RT before using any of the modules. You have the option of handling the timing of config loading and the actual init sequence yourself with: use RT; BEGIN { RT->LoadConfig; RT->Init; } or you can let RT do it all: use RT -init; This second method is particular useful when writing one-liners to interact with RT: perl -MRT=-init -e '...' The first method is necessary if you need to delay or conditionalize initialization or if you want to fiddle with C<< RT->Config >> between loading the config files and initializing the RT environment. =cut { my $DID_IMPORT_INIT; sub import { my $class = shift; my $action = shift || ''; if ($action eq "-init" and not $DID_IMPORT_INIT) { $class->LoadConfig; $class->Init; $DID_IMPORT_INIT = 1; } } } =head2 LoadConfig Load RT's config file. First, the site configuration file (F) is loaded, in order to establish overall site settings like hostname and name of RT instance. Then, the core configuration file (F) 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 is used; it only sets values if they have not been set already. =cut sub LoadConfig { require RT::Config; $Config = RT::Config->new; $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; It cannot be fixed in a PostLoadCheck, as # they are run after logging is enabled. unless ( File::Spec->file_name_is_absolute( $Config->Get('LogDir') ) ) { $Config->Set( LogDir => File::Spec->catfile( $BasePath, $Config->Get('LogDir') ) ); } return $Config; } =head2 Init L, L, L, L, and L. =cut sub Init { shift if @_%2; # code is inconsistent about calling as method my %args = (@_); CheckPerlRequirements(); InitPluginPaths(); #Get a database connection ConnectToDatabase(); InitSystemObjects(); InitClasses(%args); RT->Config->LoadConfigFromDatabase() unless $args{SkipConfigurations}; InitLogging(); ProcessPreInitMessages(); InitPlugins(); _BuildTableAttributes(); RT::I18N->Init; RT::CustomRoles->RegisterRoles unless $args{SkipCustomRoles}; RT->Config->PostLoadCheck; RT::Lifecycle->FillCache; } =head2 ConnectToDatabase Get a database connection. See also L. =cut sub ConnectToDatabase { require RT::Handle; $Handle = RT::Handle->new unless $Handle; $Handle->Connect; return $Handle; } =head2 InitLogging Create the Logger object and set up signal handlers. =cut sub InitLogging { # 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 ) { # preload UTF-8 encoding so that Encode:encode doesn't fail to load # as part of throwing an exception Encode::encode("UTF-8",""); $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); # Encode to bytes, so we don't send wide characters $p{message} = Encode::encode("UTF-8", $p{message}); $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); # Encode to bytes, so we don't send wide characters $p{message} = Encode::encode("UTF-8", $p{message}); $p{message} =~ s/(?:\r*\n)+$//; if ($p{level} eq 'debug') { return "[$$] $p{message} ($filename:$line)\n"; } else { return "[$$] $p{message}\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('LogToSTDERR') ) { require Log::Dispatch::Screen; $RT::Logger->add( Log::Dispatch::Screen->new ( name => 'screen', min_level => $Config->Get('LogToSTDERR'), 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(); } # Some messages may have been logged before the logger was available. # Output them here. sub ProcessPreInitMessages { foreach my $message ( @RT::Config::PreInitLoggerMessages ){ RT->Logger->debug($message); } } sub InitSignalHandlers { # 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). $SIG{__WARN__} = sub { # use 'goto &foo' syntax to hide ANON sub from stack 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 { eval {require 5.010_001}; if ($@) { die sprintf "RT requires Perl v5.10.1 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. Usually 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::CustomRoles; require RT::ObjectCustomRoles; require RT::Attributes; require RT::Dashboard; require RT::Approval; require RT::Lifecycle; require RT::Link; require RT::Links; require RT::Article; require RT::Articles; require RT::Class; require RT::Classes; require RT::ObjectClass; require RT::ObjectClasses; require RT::ObjectTopic; require RT::ObjectTopics; require RT::Topic; require RT::Topics; require RT::Link; require RT::Links; require RT::Catalog; require RT::Catalogs; require RT::Asset; require RT::Assets; require RT::CustomFieldValues::Canonicalizer; require RT::Configuration; require RT::Configurations; require RT::REST2; require RT::Authen::Token; _BuildTableAttributes(); if ( $args{'Heavy'} ) { # load scrips' modules my $scrips = RT::Scrips->new(RT->SystemUser); 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') ) { $class->require or $RT::Logger->error( "Class '$class' is listed in CustomFieldValuesSources option" ." in the config, but we failed to load it:\n$@\n" ); } } } sub _BuildTableAttributes { # 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" # without this, we also can never call _ClassAccessible, because we # won't have filled RT::Record::_TABLE_ATTR $_->_BuildTableAttributes foreach qw( RT::Ticket RT::Transaction RT::Attachment RT::User RT::Principal RT::Template RT::Queue RT::ScripAction RT::ScripCondition RT::Scrip RT::ObjectScrip RT::Group RT::GroupMember RT::CustomField RT::CustomFieldValue RT::ObjectCustomField RT::ObjectCustomFieldValue RT::Attribute RT::ACE RT::Article RT::Class RT::Link RT::ObjectClass RT::ObjectTopic RT::Topic RT::Asset RT::Catalog RT::CustomRole RT::ObjectCustomRole ); } =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 = RT::CurrentUser->new; $SystemUser->LoadByName('RT_System'); #RT's "nobody user" is a genuine database user. its ID lives here. $Nobody = RT::CurrentUser->new; $Nobody->LoadByName('Nobody'); require RT::System; $System = RT::System->new( $SystemUser ); } =head1 CLASS METHODS =head2 Config Returns the current L, but note that you must L first otherwise this method returns undef. Method can be called as class method. =cut sub Config { return $Config || shift->LoadConfig(); } =head2 DatabaseHandle Returns the current L. See also L. =cut sub DatabaseHandle { return $Handle } =head2 Logger Returns the logger. See also L. =cut sub Logger { return $Logger } =head2 System Returns the current L. See also L. =cut sub System { return $System } =head2 SystemUser Returns the system user's object, it's object of L class that represents the system. See also L. =cut sub SystemUser { return $SystemUser } =head2 Nobody Returns object of Nobody. It's object of L class that represents a user who can own ticket and nothing else. See also L. =cut sub Nobody { return $Nobody } sub PrivilegedUsers { if (!$_Privileged) { $_Privileged = RT::Group->new(RT->SystemUser); $_Privileged->LoadSystemInternalGroup('Privileged'); } return $_Privileged; } sub UnprivilegedUsers { if (!$_Unprivileged) { $_Unprivileged = RT::Group->new(RT->SystemUser); $_Unprivileged->LoadSystemInternalGroup('Unprivileged'); } return $_Unprivileged; } =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 sub Plugins { state @PLUGINS; state $DID_INIT = 0; my $self = shift; unless ($DID_INIT) { $self->InitPluginPaths; @PLUGINS = $self->InitPlugins; $DID_INIT++; } return [@PLUGINS]; } =head2 PluginDirs Takes an optional subdir (e.g. po, lib, etc.) and returns a list of directories from plugins where that subdirectory exists. This code does not check plugin names, plugin validitity, or load plugins (see L) in any way, and requires that RT's configuration have been already loaded. =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. In case F 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) { my $realpath = Cwd::realpath($_); next unless defined $realpath; if ( $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 Initialize all Plugins found in the RT configuration file, setting up their lib and L component roots. =cut our %CORED_PLUGINS = ( 'RT::Extension::SLA' => '4.4', 'RT::Extension::ExternalStorage' => '4.4', 'RT::Extension::Assets' => '4.4', 'RT::Authen::ExternalAuth' => '4.4', 'RT::Extension::LDAPImport' => '4.4', 'RT::Extension::SpawnLinkedTicketInQueue' => '4.4', 'RT::Extension::ParentTimeWorked' => '4.4', 'RT::Extension::FutureMailgate' => '4.4', 'RT::Extension::AdminConditionsAndActions' => '4.4.2', 'RT::Extension::RightsInspector' => '5.0', 'RT::Extension::ConfigInDatabase' => '5.0', 'RT::Extension::CustomRole::Visibility' => '5.0', 'RT::Extension::PriorityAsString' => '5.0', 'RT::Extension::AssetSQL' => '5.0', 'RT::Extension::LifecycleUI' => '5.0', 'RT::Extension::REST2' => '5.0', 'RT::Authen::Token' => '5.0', 'RT::Extension::QuoteSelection' => 5.0, 'RT::Extension::FormattedTransactions' => '5.0.1', ); sub InitPlugins { my $self = shift; my @plugins; require RT::Plugin; foreach my $plugin (grep $_, RT->Config->Get('Plugins')) { if ( $CORED_PLUGINS{$plugin} ) { RT->Logger->warning( "$plugin has been cored since RT $CORED_PLUGINS{$plugin}, please check the upgrade document for more details" ); } $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 (@_) { my ($integrity, $state, $msg) = RT::Handle->CheckIntegrity; if ($_[0] and $integrity) { # Trying to turn install mode on but we have a good DB! require Carp; $RT::Logger->error( Carp::longmess("Something tried to turn on InstallMode but we have DB integrity!") ); } else { $_INSTALL_MODE = shift; if($_INSTALL_MODE) { require RT::CurrentUser; $SystemUser = RT::CurrentUser->new(); } } } return $_INSTALL_MODE; } sub LoadGeneratedData { my $class = shift; my $pm_path = ( File::Spec->splitpath( $INC{'RT.pm'} ) )[1]; $pm_path = File::Spec->rel2abs( $pm_path ); require "$pm_path/RT/Generated.pm" || die "Couldn't load RT::Generated: $@"; $class->CanonicalizeGeneratedPaths(); } sub CanonicalizeGeneratedPaths { my $class = shift; 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 the source dir(where we configure RT) be the # BasePath instead of the one specified by --prefix 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 StaticPath LocalEtcPath LocalLibPath LexiconPath LocalLexiconPath PluginPath FontPath LocalPluginPath LocalStaticPath 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); } } } =head2 AddJavaScript Helper method to add JS files to the C<@JSFiles> config at runtime. To add files, you can add the following line to your extension's main C<.pm> file: RT->AddJavaScript( 'foo.js', 'bar.js' ); Files are expected to be in a static root in a F directory, such as F in your extension or F for local overlays. =cut sub AddJavaScript { my $self = shift; my @old = RT->Config->Get('JSFiles'); RT->Config->Set( 'JSFiles', @old, @_ ); return RT->Config->Get('JSFiles'); } =head2 AddStyleSheets Helper method to add CSS files to the C<@CSSFiles> config at runtime. To add files, you can add the following line to your extension's main C<.pm> file: RT->AddStyleSheets( 'foo.css', 'bar.css' ); Files are expected to be in a static root in a F directory, such as F in your extension or F for local overlays. =cut sub AddStyleSheets { my $self = shift; my @old = RT->Config->Get('CSSFiles'); RT->Config->Set( 'CSSFiles', @old, @_ ); return RT->Config->Get('CSSFiles'); } =head2 JavaScript helper method of RT->Config->Get('JSFiles') =cut sub JavaScript { return RT->Config->Get('JSFiles'); } =head2 StyleSheets helper method of RT->Config->Get('CSSFiles') =cut sub StyleSheets { return RT->Config->Get('CSSFiles'); } =head2 Deprecated Notes that a particular call path is deprecated, and will be removed in a particular release. Puts a warning in the logs indicating such, along with a stack trace. Optional arguments include: =over =item Remove The release which is slated to remove the method or component =item Instead A suggestion of what to use in place of the deprecated API =item Arguments Used if not the entire method is being removed, merely a manner of calling it; names the arguments which are deprecated. =item Message Overrides the auto-built phrasing of C with a custom message. =item Detail Provides more context (e.g. callback paths) after the Message but before the Stack =item Object An L object to print the class and numeric id of. Useful if the admin will need to hunt down a particular object to fix the deprecation warning. =back =cut sub Deprecated { my $class = shift; my %args = ( Arguments => undef, Remove => undef, Instead => undef, Message => undef, Detail => undef, Stack => 1, LogLevel => "warn", @_, ); my ($function) = (caller(1))[3]; my $stack; if ($function eq "HTML::Mason::Commands::__ANON__") { eval { HTML::Mason::Exception->throw() }; my $error = $@; my $info = $error->analyze_error; $function = "Mason component ".$info->{frames}[0]->filename; $stack = join("\n", map { sprintf("\t[%s:%d]", $_->filename, $_->line) } @{$info->{frames}}); } else { $function = "function $function"; $stack = Carp::longmess(); } $stack =~ s/^.*?\n//; # Strip off call to ->Deprecated my $msg; if ($args{Message}) { $msg = $args{Message}; } elsif ($args{Arguments}) { $msg = "Calling $function with $args{Arguments} is deprecated"; } else { $msg = "The $function is deprecated"; } $msg .= ", and will be removed in RT $args{Remove}" if $args{Remove}; $msg .= "."; $msg .= " You should use $args{Instead} instead." if $args{Instead}; $msg .= sprintf " Object: %s #%d.", blessed($args{Object}), $args{Object}->id if $args{Object}; $msg .= "\n$args{Detail}\n" if $args{Detail}; $msg .= " Call stack:\n$stack" if $args{Stack}; my $loglevel = $args{LogLevel}; RT->Logger->$loglevel($msg); } =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, start a discussion in the RT Developers category on the community forum at L or send email to sales@bestpractical.com for professional assistance. =head1 SEE ALSO L L =cut require RT::Base; RT::Base->_ImportOverlays(); 1; rt-5.0.1/lib/RT/000755 000765 000024 00000000000 14005021226 014073 5ustar00sunnavystaff000000 000000 rt-5.0.1/lib/RT/System.pm000644 000765 000024 00000034712 14005011336 015725 0ustar00sunnavystaff000000 000000 # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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 }}} =head1 NAME RT::System =head1 DESCRIPTION RT::System is a simple global object used as a focal point for things that are system-wide. It works sort of like an RT::Record, except it's really a single object that has an id of "1" when instantiated. This gets used by the ACL system so that you can have rights for the scope "RT::System" In the future, there will probably be other API goodness encapsulated here. =cut package RT::System; use strict; use warnings; use base qw/RT::Record/; use Role::Basic 'with'; with "RT::Record::Role::Roles", "RT::Record::Role::Rights" => { -excludes => [qw/AvailableRights RightCategories/] }; use RT::ACL; use RT::ACE; use Data::GUID; __PACKAGE__->AddRight( Admin => SuperUser => 'Do anything and everything'); # loc __PACKAGE__->AddRight( Staff => ShowUserHistory => 'Show history of public user properties'); # loc __PACKAGE__->AddRight( Admin => AdminUsers => 'Create, modify and delete users'); # loc __PACKAGE__->AddRight( Admin => AdminCustomRoles => 'Create, modify and delete custom roles'); # loc __PACKAGE__->AddRight( Staff => ModifySelf => "Modify one's own RT account"); # loc __PACKAGE__->AddRight( Staff => ShowArticlesMenu => 'Show Articles menu'); # loc __PACKAGE__->AddRight( Admin => ShowConfigTab => 'Show Admin menu'); # loc __PACKAGE__->AddRight( Admin => ShowApprovalsTab => 'Show Approvals tab'); # loc __PACKAGE__->AddRight( Staff => ShowAssetsMenu => 'Show Assets menu'); # loc __PACKAGE__->AddRight( Staff => ShowGlobalTemplates => 'Show global templates'); # loc __PACKAGE__->AddRight( General => LoadSavedSearch => 'Allow loading of saved searches'); # loc __PACKAGE__->AddRight( General => CreateSavedSearch => 'Allow creation of saved searches'); # loc __PACKAGE__->AddRight( Admin => ExecuteCode => 'Allow writing Perl code in templates, scrips, etc'); # loc __PACKAGE__->AddRight( General => SeeSelfServiceGroupTicket => 'See tickets for other group members in SelfService' ); # loc __PACKAGE__->AddRight( Staff => ShowSearchAdvanced => 'Show search "Advanced" menu' ); # loc __PACKAGE__->AddRight( Staff => ShowSearchBulkUpdate => 'Show search "Bulk Update" menu' ); # loc =head2 AvailableRights Returns a hashref of available rights for this object. The keys are the right names and the values are a description of what the rights do. This method as well returns rights of other RT objects, like L or L, to allow users to apply those rights globally. If an L is passed as the first argument, the available rights will be limited to ones which make sense for the principal. Currently only role groups are supported and rights announced by object types to which the role group doesn't apply are not returned. =cut sub AvailableRights { my $self = shift; my $principal = shift; my $class = ref($self) || $self; my @rights; if ($principal and $principal->IsRoleGroup) { my $role = $principal->Object->Name; for my $class (keys %RT::ACE::RIGHTS) { next unless $class->DOES('RT::Record::Role::Roles') and $class->HasRole($role) and $class ne "RT::System"; push @rights, values %{ $RT::ACE::RIGHTS{$class} }; } } else { @rights = map {values %{$_}} values %RT::ACE::RIGHTS; } my %rights; $rights{$_->{Name}} = $_->{Description} for @rights; delete $rights{ExecuteCode} if RT->Config->Get('DisallowExecuteCode'); return \%rights; } =head2 RightCategories Returns a hashref where the keys are rights for this type of object and the values are the category (General, Staff, Admin) the right falls into. =cut sub RightCategories { my $self = shift; my $class = ref($self) || $self; my %rights; $rights{$_->{Name}} = $_->{Category} for map {values %{$_}} values %RT::ACE::RIGHTS; return \%rights; } sub _Init { my $self = shift; $self->SUPER::_Init (@_) if @_ && $_[0]; } =head2 id Returns RT::System's id. It's 1. =cut *Id = \&id; sub id { return 1 } sub UID { return "RT::System" } =head2 Load Since this object is pretending to be an RT::Record, we need a load method. It does nothing =cut sub Load { return 1 } sub Name { return 'RT System' } sub __Set { return 0 } sub __Value { return 0 } sub Create { return 0 } sub Delete { return 0 } sub SubjectTag { my $self = shift; my $queue = shift; return $queue->SubjectTag if $queue; my $queues = RT::Queues->new( $self->CurrentUser ); $queues->Limit( FIELD => 'SubjectTag', OPERATOR => 'IS NOT', VALUE => 'NULL' ); return $queues->DistinctFieldValues('SubjectTag'); } =head2 QueueCacheNeedsUpdate ( 1 ) Attribute to decide when SelectQueue needs to flush the list of queues and retrieve new ones. Set when queues are created, enabled/disabled and on certain acl changes. Should also better understand group management. If passed a true value, will update the attribute to be the current time. =cut sub QueueCacheNeedsUpdate { my $self = shift; my $update = shift; if ($update) { return $self->SetAttribute(Name => 'QueueCacheNeedsUpdate', Content => time); } else { my $cache = $self->FirstAttribute('QueueCacheNeedsUpdate'); return (defined $cache ? $cache->Content : 0 ); } } =head2 CustomRoleCacheNeedsUpdate ( 1 ) Attribute to decide when we need to flush the list of custom roles and re-register any changes. Set when roles are created, enabled/disabled, etc. If passed a true value, will update the attribute to be the current time. =cut sub CustomRoleCacheNeedsUpdate { my $self = shift; my $update = shift; if ($update) { return $self->SetAttribute(Name => 'CustomRoleCacheNeedsUpdate', Content => time); } else { my $cache = $self->FirstAttribute('CustomRoleCacheNeedsUpdate'); return (defined $cache ? $cache->Content : 0 ); } } =head2 ConfigCacheNeedsUpdate ( 1 ) Attribute to decide when we need to flush the database settings and re-register any changes. Set when settings are created, enabled/disabled, etc. If passed a true value, will update the attribute to be the current time. =cut sub ConfigCacheNeedsUpdate { my $self = shift; my $time = shift; if ($time) { return $self->SetAttribute(Name => 'ConfigCacheNeedsUpdate', Content => $time); } else { my $cache = $self->FirstAttribute('ConfigCacheNeedsUpdate'); return (defined $cache ? $cache->Content : 0 ); } } # This needs to be in RT::System as RT::Interface::Web and RT::Interface::Email both use this my $lifecycle_cache_time = time; sub MaybeRebuildLifecycleCache { my $needs_update = RT->System->LifecycleCacheNeedsUpdate; if ( $needs_update > $lifecycle_cache_time ) { RT::Lifecycle->FillCache; $lifecycle_cache_time = $needs_update; } } =head2 LifecycleCacheNeedsUpdate ( 1 ) Attribute to decide when we need to flush the list of lifecycles and re-register any changes. This is needed for the lifecycle UI editor. If passed a true value, will update the attribute to be the current time. =cut sub LifecycleCacheNeedsUpdate { my $self = shift; my $update = shift; if ($update) { return $self->SetAttribute(Name => 'LifecycleCacheNeedsUpdate', Content => time); } else { my $cache = $self->FirstAttribute('LifecycleCacheNeedsUpdate'); return (defined $cache ? $cache->Content : 0); } } =head2 AddUpgradeHistory package, data Adds an entry to the upgrade history database. The package can be either C for core RT upgrades, or the fully qualified name of a plugin. The data must be a hash reference. =cut sub AddUpgradeHistory { my $self = shift; my $package = shift; my $data = shift; $data->{timestamp} ||= time; $data->{rt_version} ||= $RT::VERSION; my $upgrade_history_attr = $self->FirstAttribute('UpgradeHistory'); my $upgrade_history = $upgrade_history_attr ? $upgrade_history_attr->Content : {}; push @{ $upgrade_history->{$package} }, $data; $self->SetAttribute( Name => 'UpgradeHistory', Content => $upgrade_history, ); } =head2 UpgradeHistory [package] Returns the entries of RT's upgrade history. If a package is specified, the list of upgrades for that package will be returned. Otherwise a hash reference of C<< package => [upgrades] >> will be returned. =cut sub UpgradeHistory { my $self = shift; my $package = shift; my $upgrade_history_attr = $self->FirstAttribute('UpgradeHistory'); my $upgrade_history = $upgrade_history_attr ? $upgrade_history_attr->Content : {}; if ($package) { return @{ $upgrade_history->{$package} || [] }; } return $upgrade_history; } sub ParsedUpgradeHistory { my $self = shift; my $package = shift; my $version_status = "Current version: "; if ( $package eq 'RT' ){ $version_status .= $RT::VERSION; } elsif ( grep {/$package/} @{RT->Config->Get('Plugins')} ) { no strict 'refs'; $version_status .= ${ $package . '::VERSION' }; } else { $version_status = "Not currently loaded"; } my %ids; my @lines; my @events = $self->UpgradeHistory( $package ); for my $event (@events) { if ($event->{stage} eq 'before' or (($event->{action}||'') eq 'insert' and not $event->{full_id})) { if (not $event->{full_id}) { # For upgrade done in the 4.1 series without GUIDs if (($event->{type}||'') eq 'full upgrade') { $event->{full_id} = $event->{individual_id} = Data::GUID->new->as_string; } else { $event->{individual_id} = Data::GUID->new->as_string; $event->{full_id} = (@lines ? $lines[-1]{full_id} : Data::GUID->new->as_string); } $event->{return_value} = [1] if $event->{stage} eq 'after'; } if ($ids{$event->{full_id}}) { my $kids = $ids{$event->{full_id}}{sub_events} ||= []; # Stitch non-"upgrade"s beneath the previous "upgrade" if ( @{$kids} and $event->{action} ne 'upgrade' and $kids->[-1]{action} eq 'upgrade') { push @{ $kids->[-1]{sub_events} }, $event; } else { push @{ $kids }, $event; } } else { push @lines, $event; } $ids{$event->{individual_id}} = $event; } elsif ($event->{stage} eq 'after') { if (not $event->{individual_id}) { if (($event->{type}||'') eq 'full upgrade') { $lines[-1]{end} = $event->{timestamp} if @lines; } elsif (($event->{type}||'') eq 'individual upgrade') { $lines[-1]{sub_events}[-1]{end} = $event->{timestamp} if @lines and @{ $lines[-1]{sub_events} }; } } elsif ($ids{$event->{individual_id}}) { my $end = $event; $event = $ids{$event->{individual_id}}; $event->{end} = $end->{timestamp}; $end->{return_value} = [ split ', ', $end->{return_value}, 2 ] if $end->{return_value} and not ref $end->{return_value}; $event->{return_value} = $end->{return_value}; $event->{content} ||= $end->{content}; } } } return ($version_status, @lines); } =head2 ExternalStorage Accessor for the storage engine selected by L. Will be undefined if external storage is not configured. =cut sub ExternalStorage { my $self = shift; if (@_) { $self->{ExternalStorage} = shift; } return $self->{ExternalStorage}; } =head2 ExternalStorageURLFor object Returns a URL for direct linking to an L engine. Will return C if external storage is not configured, or if direct linking is disabled in config (C<$ExternalStorageDirectLink>), or if the external storage engine doesn't support hyperlinking (as in L), or finally, if the object is for whatever reason not present in external storage. =cut sub ExternalStorageURLFor { my $self = shift; my $Object = shift; # external storage not configured return undef if !$self->ExternalStorage; # external storage direct links disabled return undef if !RT->Config->Get('ExternalStorageDirectLink'); return undef unless $Object->ContentEncoding eq 'external'; return $self->ExternalStorage->DownloadURLFor($Object); } RT::Base->_ImportOverlays(); 1; rt-5.0.1/lib/RT/Generated.pm000644 000765 000024 00000005700 14005021226 016331 0ustar00sunnavystaff000000 000000 # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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 }}} package RT; use warnings; use strict; our $VERSION = '5.0.1'; our ($MAJOR_VERSION, $MINOR_VERSION, $REVISION) = $VERSION =~ /^(\d)\.(\d)\.(\d+)/; $BasePath = '/opt/rt5'; $EtcPath = 'etc'; $BinPath = 'bin'; $SbinPath = 'sbin'; $VarPath = 'var'; $FontPath = 'share/fonts'; $LexiconPath = 'share/po'; $StaticPath = 'share/static'; $PluginPath = 'plugins'; $LocalPath = 'local'; $LocalEtcPath = 'local/etc'; $LocalLibPath = 'local/lib'; $LocalLexiconPath = 'local/po'; $LocalStaticPath = 'local/static'; $LocalPluginPath = 'local/plugins'; # $MasonComponentRoot is where your rt instance keeps its mason html files $MasonComponentRoot = 'share/html'; # $MasonLocalComponentRoot is where your rt instance keeps its site-local # mason html files. $MasonLocalComponentRoot = 'local/html'; # $MasonDataDir Where mason keeps its datafiles $MasonDataDir = 'var/mason_data'; # RT needs to put session data (for preserving state between connections # via the web interface) $MasonSessionDir = 'var/session_data'; 1; rt-5.0.1/lib/RT/PlackRunner.pm000644 000765 000024 00000013057 14005011336 016664 0ustar00sunnavystaff000000 000000 # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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 warnings; use strict; package RT::PlackRunner; use base 'Plack::Runner'; sub new { my $class = shift; return $class->SUPER::new( default_middleware => 0, @_ ); } sub parse_options { my $self = shift; my @args = @_; # handle "rt-server 8888" for back-compat, but complain about it if (@args && $args[0] =~ m/^\d+$/) { warn "Deprecated: please run $0 --port $ARGV[0] instead\n"; unshift @args, '--port'; } $self->SUPER::parse_options(@args); $self->{app} ||= $self->app; $self->{server} ||= $self->loader->guess; my %args = @{$self->{options}}; $self->{webpath} ||= $args{webpath} if $args{webpath}; if ($self->{server} eq "FCGI") { # We deal with the possible failure modes of this in ->run } elsif ($args{port}) { $self->{explicit_port} = 1; my $old_app = $self->{app}; $self->{app} = sub { my $env = shift; $env->{'rt.explicit_port'} = $args{port}; $old_app->($env, @_); }; } else { $self->set_options(port => (RT->Config->Get('WebPort') || '8080')); } } # Don't assume port 5000 with no port or socket supplied; this allows # the WebPort default to kick in (above), and also to provide useful # error messages when starting FCGI without any options. sub mangle_host_port_socket { my $self = shift; my ($host, $port, $socket, @listen) = @_; return $self->SUPER::mangle_host_port_socket(@_) if @listen or $port or $socket; return host => $host, port => $port, socket => $socket, @listen ? (listen => \@listen) : (); } sub app { require RT::Interface::Web::Handler; my $app = RT::Interface::Web::Handler->PSGIApp; if ($ENV{RT_TESTING}) { my $screen_logger = $RT::Logger->remove('screen'); require Log::Dispatch::Perl; $RT::Logger->add( Log::Dispatch::Perl->new( name => 'rttest', min_level => $screen_logger->min_level, action => { error => 'warn', critical => 'warn' } ) ); require Plack::Middleware::Test::StashWarnings; $app = Plack::Middleware::Test::StashWarnings->wrap($app); } return $app; } sub run { my $self = shift; my %args = @{$self->{options}}; # Plack::Handler::FCGI has its own catch for this, but doesn't # notice that listen is an empty list, and we can also provide a # better error message. if ($self->{server} eq "FCGI" and not -S STDIN and not @{$args{listen} || []}) { print STDERR "STDIN is not a socket, and no --listen, --socket, or --port provided\n"; exit 1; } if ( $self->{webpath} ){ require Plack::App::URLMap; my $urlmap = Plack::App::URLMap->new; $urlmap->map($self->{webpath} => $self->{app}); $self->{app} = $urlmap->to_app; } eval { $self->SUPER::run(@_) }; my $err = $@; exit 0 unless $err; if ( $err =~ /listen/ ) { print STDERR < EOF if ($self->{explicit_port}) { print STDERR "Please check your system configuration or choose another port\n\n"; } exit 1; } else { die "Something went wrong while trying to run RT's standalone web server:\n\t" . $err; } } 1; rt-5.0.1/lib/RT/Ruleset.pm000644 000765 000024 00000005157 14005011336 016065 0ustar00sunnavystaff000000 000000 # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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 }}} package RT::Ruleset; use strict; use warnings; use base 'Class::Accessor::Fast'; __PACKAGE__->mk_accessors(qw(Name Rules)); my @RULE_SETS; sub FindAllRules { my ($class, %args) = @_; return [ grep { $_->Prepare } map { $_->new(CurrentUser => RT->SystemUser, %args) } grep { $_->_Stage eq $args{Stage} } map { @{$_->Rules} } @RULE_SETS ]; } sub CommitRules { my ($class, $rules) = @_; $_->Commit for @$rules; } sub Add { my ($class, %args) = @_; for (@{$args{Rules}}) { $_->require or die $UNIVERSAL::require::ERROR; } push @RULE_SETS, $class->new(\%args); } RT::Base->_ImportOverlays(); 1; rt-5.0.1/lib/RT/Ticket.pm000644 000765 000024 00000316466 14005011336 015675 0ustar00sunnavystaff000000 000000 # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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 }}} =head1 SYNOPSIS use RT::Ticket; my $ticket = RT::Ticket->new($CurrentUser); $ticket->Load($ticket_id); =head1 DESCRIPTION This module lets you manipulate RT's ticket object. =head1 METHODS =cut package RT::Ticket; use strict; use warnings; use base 'RT::Record'; use 5.010; use Role::Basic 'with'; # SetStatus and _SetStatus are reimplemented below (using other pieces of the # role) to deal with ACLs, moving tickets between queues, and automatically # setting dates. with "RT::Record::Role::Status" => { -excludes => [qw(SetStatus _SetStatus)] }, "RT::Record::Role::Links", "RT::Record::Role::Roles"; use RT::Queue; use RT::User; use RT::Record; use RT::Link; use RT::Links; use RT::Date; use RT::CustomFields; use RT::Tickets; use RT::Transactions; use RT::Reminders; use RT::URI::fsck_com_rt; use RT::URI; use RT::SLA; use MIME::Entity; use Devel::GlobalDestruction; sub LifecycleColumn { "Queue" } my %ROLES = ( # name => description Owner => 'The owner of a ticket', # loc_pair Requestor => 'The requestor of a ticket', # loc_pair Cc => 'The CC of a ticket', # loc_pair AdminCc => 'The administrative CC of a ticket', # loc_pair ); for my $role (sort keys %ROLES) { RT::Ticket->RegisterRole( Name => $role, EquivClasses => ['RT::Queue'], ( $role eq "Owner" ? ( Column => "Owner") : () ), ( $role !~ /Cc/ ? ( ACLOnlyInEquiv => 1) : () ), ); } our %MERGE_CACHE = ( effective => {}, merged => {}, ); =head2 Load Takes a single argument. This can be a ticket id, ticket alias or local ticket uri. If the ticket can't be loaded, returns undef. Otherwise, returns the ticket id. =cut sub Load { my $self = shift; my $id = shift; $id = '' unless defined $id; # TODO: modify this routine to look at EffectiveId and # do the recursive load thing. be careful to cache all # the interim tickets we try so we don't loop forever. unless ( $id =~ /^\d+$/ ) { $RT::Logger->debug("Tried to load a bogus ticket id: '$id'"); return (undef); } $id = $MERGE_CACHE{'effective'}{ $id } if $MERGE_CACHE{'effective'}{ $id }; my ($ticketid, $msg) = $self->LoadById( $id ); unless ( $self->Id ) { $RT::Logger->debug("$self tried to load a bogus ticket: $id"); return (undef); } #If we're merged, resolve the merge. if ( $self->EffectiveId && $self->EffectiveId != $self->Id ) { $RT::Logger->debug( "We found a merged ticket. " . $self->id ."/". $self->EffectiveId ); my $real_id = $self->Load( $self->EffectiveId ); $MERGE_CACHE{'effective'}{ $id } = $real_id; return $real_id; } #Ok. we're loaded. lets get outa here. return $self->Id; } =head2 Create (ARGS) Arguments: ARGS is a hash of named parameters. Valid parameters are: id Queue - Either a Queue object or a Queue Name Requestor - A reference to a list of email addresses or RT user Names Cc - A reference to a list of email addresses or Names AdminCc - A reference to a list of email addresses or Names SquelchMailTo - A reference to a list of email addresses - who should this ticket not mail Type -- The ticket's type. ignore this for now Owner -- This ticket's owner. either an RT::User object or this user's id Subject -- A string describing the subject of the ticket Priority -- an integer from 0 to 99 InitialPriority -- an integer from 0 to 99 FinalPriority -- an integer from 0 to 99 Status -- any valid status for Queue's Lifecycle, otherwises uses on_create from Lifecycle default TimeEstimated -- an integer. estimated time for this task in minutes TimeWorked -- an integer. time worked so far in minutes TimeLeft -- an integer. time remaining in minutes Starts -- an ISO date describing the ticket's start date and time in GMT Due -- an ISO date describing the ticket's due date and time in GMT MIMEObj -- a MIME::Entity object with the content of the initial ticket request. CustomField- -- a scalar or array of values for the customfield with the id Ticket links can be set up during create by passing the link type as a hask key and the ticket id to be linked to as a value (or a URI when linking to other objects). Multiple links of the same type can be created by passing an array ref. For example: Parents => 45, DependsOn => [ 15, 22 ], RefersTo => 'http://www.bestpractical.com', Supported link types are C, C, C, C, C and C. Also, C is alias for C and C and C are aliases for C. Returns: TICKETID, Transaction Object, Error Message =cut sub Create { my $self = shift; my %args = ( id => undef, EffectiveId => undef, Queue => undef, Requestor => undef, Cc => undef, AdminCc => undef, SquelchMailTo => undef, TransSquelchMailTo => undef, Type => 'ticket', Owner => undef, Subject => '', InitialPriority => undef, FinalPriority => undef, Priority => undef, Status => undef, TimeWorked => "0", TimeLeft => 0, TimeEstimated => 0, Due => undef, Starts => undef, Started => undef, Resolved => undef, SLA => undef, MIMEObj => undef, _RecordTransaction => 1, @_ ); my ($ErrStr, @non_fatal_errors); my $QueueObj = RT::Queue->new( RT->SystemUser ); if ( ref $args{'Queue'} eq 'RT::Queue' ) { $QueueObj->Load( $args{'Queue'}->Id ); } elsif ( $args{'Queue'} ) { $QueueObj->Load( $args{'Queue'} ); } else { $RT::Logger->debug("'". ( $args{'Queue'} ||''). "' not a recognised queue object." ); } #Can't create a ticket without a queue. unless ( $QueueObj->Id ) { $RT::Logger->debug("$self No queue given for ticket creation."); return ( 0, 0, $self->loc('Could not create ticket. Queue not set') ); } #Now that we have a queue, Check the ACLS unless ( $self->CurrentUser->HasRight( Right => 'CreateTicket', Object => $QueueObj ) and $QueueObj->Disabled != 1 ) { return ( 0, 0, $self->loc( "No permission to create tickets in the queue '[_1]'", $QueueObj->Name)); } my $cycle = $QueueObj->LifecycleObj; unless ( defined $args{'Status'} && length $args{'Status'} ) { $args{'Status'} = $cycle->DefaultOnCreate; } $args{'Status'} = lc $args{'Status'}; unless ( $cycle->IsValid( $args{'Status'} ) ) { return ( 0, 0, $self->loc("Status '[_1]' isn't a valid status for tickets in this queue.", $self->loc($args{'Status'})) ); } unless ( $cycle->IsTransition( '' => $args{'Status'} ) ) { return ( 0, 0, $self->loc("New tickets can not have status '[_1]' in this queue.", $self->loc($args{'Status'})) ); } #Since we have a queue, we can set queue defaults #Initial Priority # If there's no queue default initial priority and it's not set, set it to 0 $args{'InitialPriority'} = $QueueObj->DefaultValue('InitialPriority') || 0 unless defined $args{'InitialPriority'}; #Final priority # If there's no queue default final priority and it's not set, set it to 0 $args{'FinalPriority'} = $QueueObj->DefaultValue('FinalPriority') || 0 unless defined $args{'FinalPriority'}; # Priority may have changed from InitialPriority, for the case # where we're importing tickets (eg, from an older RT version.) $args{'Priority'} = $args{'InitialPriority'} unless defined $args{'Priority'}; # Dates my $Now = RT::Date->new( $self->CurrentUser ); $Now->SetToNow(); #TODO we should see what sort of due date we're getting, rather + # than assuming it's in ISO format. #Set the due date. if we didn't get fed one, use the queue default due in my $Due = RT::Date->new( $self->CurrentUser ); if ( defined $args{'Due'} ) { $Due->Set( Format => 'ISO', Value => $args{'Due'} ); } elsif ( my $default = $QueueObj->DefaultValue('Due') ) { $Due->Set( Format => 'unknown', Value => $default ); } my $Starts = RT::Date->new( $self->CurrentUser ); if ( defined $args{'Starts'} ) { $Starts->Set( Format => 'ISO', Value => $args{'Starts'} ); } elsif ( my $default = $QueueObj->DefaultValue('Starts') ) { $Starts->Set( Format => 'unknown', Value => $default ); } my $Started = RT::Date->new( $self->CurrentUser ); if ( defined $args{'Started'} ) { $Started->Set( Format => 'ISO', Value => $args{'Started'} ); } # If the status is not an initial status, set the started date elsif ( !$cycle->IsInitial($args{'Status'}) ) { $Started->Set( Format => 'ISO', Value => $Now->ISO ); } my $Resolved = RT::Date->new( $self->CurrentUser ); if ( defined $args{'Resolved'} ) { $Resolved->Set( Format => 'ISO', Value => $args{'Resolved'} ); } #If the status is an inactive status, set the resolved date elsif ( $cycle->IsInactive( $args{'Status'} ) ) { $RT::Logger->debug( "Got a ". $args{'Status'} ."(inactive) ticket with undefined resolved date. Setting to now." ); $Resolved->Set( Format => 'ISO', Value => $Now->ISO ); } # Dealing with time fields $args{'TimeEstimated'} = 0 unless defined $args{'TimeEstimated'}; $args{'TimeWorked'} = 0 unless defined $args{'TimeWorked'}; $args{'TimeLeft'} = 0 unless defined $args{'TimeLeft'}; # Figure out users for roles my $roles = {}; push @non_fatal_errors, $QueueObj->_ResolveRoles( $roles, %args ); $args{'Type'} = lc $args{'Type'} if $args{'Type'} =~ /^(ticket|approval|reminder)$/i; $args{'Subject'} =~ s/\n//g; $RT::Handle->BeginTransaction(); my %params = ( Queue => $QueueObj->Id, Subject => $args{'Subject'}, InitialPriority => $args{'InitialPriority'}, FinalPriority => $args{'FinalPriority'}, Priority => $args{'Priority'}, Status => $args{'Status'}, TimeWorked => $args{'TimeWorked'}, TimeEstimated => $args{'TimeEstimated'}, TimeLeft => $args{'TimeLeft'}, Type => $args{'Type'}, Created => $Now->ISO, Starts => $Starts->ISO, Started => $Started->ISO, Resolved => $Resolved->ISO, Due => $Due->ISO, $args{ 'Type' } eq 'ticket' ? ( SLA => $args{ SLA } || RT::SLA->GetDefaultServiceLevel( Queue => $QueueObj ), ) : (), ); # Parameters passed in during an import that we probably don't want to touch, otherwise foreach my $attr (qw(id Creator Created LastUpdated LastUpdatedBy)) { $params{$attr} = $args{$attr} if $args{$attr}; } # Delete null integer parameters foreach my $attr (qw(TimeWorked TimeLeft TimeEstimated InitialPriority FinalPriority)) { delete $params{$attr} unless ( exists $params{$attr} && $params{$attr} ); } # Delete the time worked if we're counting it in the transaction delete $params{'TimeWorked'} if $args{'_RecordTransaction'}; my ($id,$ticket_message) = $self->SUPER::Create( %params ); unless ($id) { $RT::Logger->crit( "Couldn't create a ticket: " . $ticket_message ); $RT::Handle->Rollback(); return ( 0, 0, $self->loc("Ticket could not be created due to an internal error") ); } #Set the ticket's effective ID now that we've created it. my ( $val, $msg ) = $self->__Set( Field => 'EffectiveId', Value => ( $args{'EffectiveId'} || $id ) ); unless ( $val ) { $RT::Logger->crit("Couldn't set EffectiveId: $msg"); $RT::Handle->Rollback; return ( 0, 0, $self->loc("Ticket could not be created due to an internal error") ); } # Create (empty) role groups my $create_groups_ret = $self->_CreateRoleGroups(); unless ($create_groups_ret) { $RT::Logger->crit( "Couldn't create ticket groups for ticket " . $self->Id . ". aborting Ticket creation." ); $RT::Handle->Rollback(); return ( 0, 0, $self->loc("Ticket could not be created due to an internal error") ); } # Codify what it takes to add each kind of group my $always_ok = sub { 1 }; my %acls = ( (map { $_ => $always_ok } $QueueObj->Roles), AdminCc => sub { my $principal = shift; return 1 if $self->CurrentUserHasRight('ModifyTicket'); return unless $self->CurrentUserHasRight("WatchAsAdminCc"); return unless $principal->id == $self->CurrentUser->PrincipalId; return 1; }, Owner => sub { my $principal = shift; return 1 if $principal->id == RT->Nobody->PrincipalId; return $principal->HasRight( Object => $self, Right => 'OwnTicket' ); }, ); # Populate up the role groups. This call modifies $roles. push @non_fatal_errors, $self->_AddRolesOnCreate( $roles, %acls ); # Squelching if ($args{'SquelchMailTo'}) { my @squelch = ref( $args{'SquelchMailTo'} ) ? @{ $args{'SquelchMailTo'} } : $args{'SquelchMailTo'}; $self->_SquelchMailTo( @squelch ); } # Add all the custom fields foreach my $arg ( keys %args ) { next unless $arg =~ /^CustomField-(\d+)$/i; my $cfid = $1; my $cf = $self->LoadCustomFieldByIdentifier($cfid); $cf->{include_set_initial} = 1; next unless $cf->ObjectTypeFromLookupType($cf->__Value('LookupType'))->isa(ref $self); foreach my $value ( UNIVERSAL::isa( $args{$arg} => 'ARRAY' ) ? @{ $args{$arg} } : ( $args{$arg} ) ) { next if $self->CustomFieldValueIsEmpty( Field => $cf, Value => $value, ); # Allow passing in uploaded LargeContent etc by hash reference my ($status, $msg) = $self->_AddCustomFieldValue( (UNIVERSAL::isa( $value => 'HASH' ) ? %$value : (Value => $value) ), Field => $cfid, RecordTransaction => 0, ForCreation => 1, ); push @non_fatal_errors, $msg unless $status; } } my ( $status, @msgs ) = $self->AddCustomFieldDefaultValues; push @non_fatal_errors, @msgs unless $status; # Deal with setting up links # TODO: Adding link may fire scrips on other end and those scrips # could create transactions on this ticket before 'Create' transaction. # # We should implement different lifecycle: record 'Create' transaction, # create links and only then fire create transaction's scrips. # # Ideal variant: add all links without firing scrips, record create # transaction and only then fire scrips on the other ends of links. # # //RUZ push @non_fatal_errors, $self->_AddLinksOnCreate(\%args, { Silent => !$args{'_RecordTransaction'} || ($self->Type || '') eq 'reminder', }); # Try to add roles once more. push @non_fatal_errors, $self->_AddRolesOnCreate( $roles, %acls ); # Anything left is failure of ACLs; Cc and Requestor have no ACLs, # so we don't bother checking them. if (@{ $roles->{Owner} }) { my $owner = $roles->{Owner}[0]->Object; $RT::Logger->warning( "User " . $owner->Name . "(" . $owner->id . ") was proposed as a ticket owner but has no rights to own " . "tickets in " . $QueueObj->Name ); push @non_fatal_errors, $self->loc( "Owner '[_1]' does not have rights to own this ticket.", $owner->Name ); } for my $principal (@{ $roles->{AdminCc} }) { push @non_fatal_errors, $self->loc( "No rights to add '[_1]' as an AdminCc on this ticket", $principal->Object->Name ); } if ( $args{'_RecordTransaction'} ) { # Add a transaction for the create my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction( Type => "Create", TimeTaken => $args{'TimeWorked'}, MIMEObj => $args{'MIMEObj'}, SquelchMailTo => $args{'TransSquelchMailTo'}, ); if ( $self->Id && $Trans ) { $TransObj->UpdateCustomFields( %args ); $RT::Logger->info( "Ticket " . $self->Id . " created in queue '" . $QueueObj->Name . "' by " . $self->CurrentUser->Name ); $ErrStr = $self->loc( "Ticket [_1] created in queue '[_2]'", $self->Id, $QueueObj->Name ); $ErrStr = join( "\n", $ErrStr, @non_fatal_errors ); } else { $RT::Handle->Rollback(); $ErrStr = join( "\n", $ErrStr, @non_fatal_errors ); $RT::Logger->error("Ticket couldn't be created: $ErrStr"); return ( 0, 0, $self->loc( "Ticket could not be created due to an internal error")); } $RT::Handle->Commit(); return ( $self->Id, $TransObj->Id, $ErrStr ); } else { # Not going to record a transaction $RT::Handle->Commit(); $ErrStr = $self->loc( "Ticket [_1] created in queue '[_2]'", $self->Id, $QueueObj->Name ); $ErrStr = join( "\n", $ErrStr, @non_fatal_errors ); return ( $self->Id, 0, $ErrStr ); } } sub SetType { my $self = shift; my $value = shift; # Force lowercase on internal RT types $value = lc $value if $value =~ /^(ticket|approval|reminder)$/i; return $self->_Set(Field => 'Type', Value => $value, @_); } =head2 OwnerGroup A constructor which returns an RT::Group object containing the owner of this ticket. =cut sub OwnerGroup { my $self = shift; return $self->RoleGroup( 'Owner' ); } sub _HasModifyWatcherRight { my $self = shift; my ($type, $principal) = @_; # ModifyTicket works in any case return 1 if $self->CurrentUserHasRight('ModifyTicket'); # If the watcher isn't the current user then the current user has no right return 0 unless $self->CurrentUser->PrincipalId == $principal->id; # If it's an AdminCc and they have 'WatchAsAdminCc', they can modify return 1 if $type eq 'AdminCc' and $self->CurrentUserHasRight('WatchAsAdminCc'); # If it's a Requestor or Cc and they have 'Watch', they can modify return 1 if ($type eq "Cc" or $type eq 'Requestor') and $self->CurrentUserHasRight('Watch'); # Unknown type, so default to denied. return 0; } =head2 AddWatcher Applies access control checking, then calls L. Additionally, C is accepted as an alternative argument name for C. Returns a tuple of (status, message). =cut sub AddWatcher { my $self = shift; my %args = ( Type => undef, PrincipalId => undef, Email => undef, @_ ); $args{User} ||= delete $args{Email}; my ($principal, $msg) = $self->CanonicalizePrincipal(%args); if (!$principal) { return (0, $msg); } my $original_user; my $group = $self->RoleGroup( $args{Type} ); if ($group->id && $group->SingleMemberRoleGroup) { my $users = $group->UserMembersObj( Recursively => 0 ); $users->{find_disabled_rows} = 1; $original_user = $users->First; if ($original_user->PrincipalId == $principal->Id) { return 1; } } else { $original_user = RT->Nobody; } ((my $ok), $msg) = $self->AddRoleMember( Principal => $principal, ACL => sub { $self->_HasModifyWatcherRight( @_ ) }, Type => $args{Type}, InsideTransaction => 1, ); return ( 0, $msg) unless $ok; # reload group in case it was lazily created $group = $self->RoleGroup( $args{Type} ); if ($group->SingleMemberRoleGroup) { return ( 1, $self->loc( "[_1] changed from [_2] to [_3]", $group->Label, $original_user->Name, $principal->Object->Name ) ); } else { return ( 1, $self->loc('Added [_1] as [_2] for this ticket', $principal->Object->Name, $group->Label) ); } } =head2 DeleteWatcher Applies access control checking, then calls L. Additionally, C is accepted as an alternative argument name for C. Returns a tuple of (status, message). =cut sub DeleteWatcher { my $self = shift; my %args = ( Type => undef, PrincipalId => undef, Email => undef, @_ ); $args{ACL} = sub { $self->_HasModifyWatcherRight( @_ ) }; $args{User} ||= delete $args{Email}; my ($principal, $msg) = $self->DeleteRoleMember( %args ); return ( 0, $msg ) unless $principal; my $group = $self->RoleGroup( $args{Type} ); return ( 1, $self->loc( "[_1] is no longer [_2] for this ticket", $principal->Object->Name, $group->Label ) ); } =head2 SquelchMailTo ADDRESSES Takes a list of email addresses to never email about updates to this ticket. Subsequent calls to this method add, rather than replace, the list of squelched addresses. Returns an array of the L objects for this ticket's 'SquelchMailTo' attributes. =cut sub SquelchMailTo { my $self = shift; if (@_) { unless ( $self->CurrentUserHasRight('ModifyTicket') ) { return (); } } else { unless ( $self->CurrentUserHasRight('ShowTicket') ) { return (); } } return $self->_SquelchMailTo(@_); } sub _SquelchMailTo { my $self = shift; while (@_) { my $attr = shift; $self->AddAttribute( Name => 'SquelchMailTo', Content => $attr ) unless grep { $_->Content eq $attr } $self->Attributes->Named('SquelchMailTo'); } my @attributes = $self->Attributes->Named('SquelchMailTo'); return (@attributes); } =head2 UnsquelchMailTo ADDRESS Takes an address and removes it from this ticket's "SquelchMailTo" list. If an address appears multiple times, each instance is removed. Returns a tuple of (status, message) =cut sub UnsquelchMailTo { my $self = shift; my $address = shift; unless ( $self->CurrentUserHasRight('ModifyTicket') ) { return ( 0, $self->loc("Permission Denied") ); } my ($val, $msg) = $self->Attributes->DeleteEntry ( Name => 'SquelchMailTo', Content => $address); return ($val, $msg); } =head2 RequestorAddresses B String: All Ticket Requestor email addresses as a string. =cut sub RequestorAddresses { my $self = shift; return $self->RoleAddresses('Requestor'); } =head2 AdminCcAddresses returns String: All Ticket AdminCc email addresses as a string =cut sub AdminCcAddresses { my $self = shift; return $self->RoleAddresses('AdminCc'); } =head2 CcAddresses returns String: All Ticket Ccs as a string of email addresses =cut sub CcAddresses { my $self = shift; return $self->RoleAddresses('Cc'); } =head2 RoleAddresses Takes a role name and returns a string of all the email addresses for users in that role =cut sub RoleAddresses { my $self = shift; my $role = shift; unless ( $self->CurrentUserHasRight('ShowTicket') ) { return undef; } return ( $self->RoleGroup($role)->MemberEmailAddressesAsString); } =head2 Requestor Takes nothing. Returns this ticket's Requestors as an RT::Group object =cut sub Requestor { my $self = shift; return RT::Group->new($self->CurrentUser) unless $self->CurrentUserHasRight('ShowTicket'); return $self->RoleGroup( 'Requestor' ); } sub Requestors { my $self = shift; return $self->Requestor; } =head2 Cc Takes nothing. Returns an RT::Group object which contains this ticket's Ccs. If the user doesn't have "ShowTicket" permission, returns an empty group =cut sub Cc { my $self = shift; return RT::Group->new($self->CurrentUser) unless $self->CurrentUserHasRight('ShowTicket'); return $self->RoleGroup( 'Cc' ); } =head2 AdminCc Takes nothing. Returns an RT::Group object which contains this ticket's AdminCcs. If the user doesn't have "ShowTicket" permission, returns an empty group =cut sub AdminCc { my $self = shift; return RT::Group->new($self->CurrentUser) unless $self->CurrentUserHasRight('ShowTicket'); return $self->RoleGroup( 'AdminCc' ); } # a generic routine to be called by IsRequestor, IsCc and IsAdminCc =head2 IsWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID, Email => EMAIL } Takes a param hash with the attributes Type and either PrincipalId or Email Type is one of Requestor, Cc, AdminCc and Owner PrincipalId is an RT::Principal id, and Email is an email address. Returns true if the specified principal (or the one corresponding to the specified address) is a member of the group Type for this ticket. XX TODO: This should be Memoized. =cut sub IsWatcher { my $self = shift; my %args = ( Type => 'Requestor', PrincipalId => undef, Email => undef, @_ ); # Load the relevant group. my $group = $self->RoleGroup( $args{'Type'} ); # Find the relevant principal. if (!$args{PrincipalId} && $args{Email}) { # Look up the specified user. my $user = RT::User->new($self->CurrentUser); $user->LoadByEmail($args{Email}); if ($user->Id) { $args{PrincipalId} = $user->PrincipalId; } else { # A non-existent user can't be a group member. return 0; } } # Ask if it has the member in question return $group->HasMember( $args{'PrincipalId'} ); } =head2 IsRequestor PRINCIPAL_ID Takes an L id. Returns true if the principal is a requestor of the current ticket. =cut sub IsRequestor { my $self = shift; my $person = shift; return ( $self->IsWatcher( Type => 'Requestor', PrincipalId => $person ) ); }; =head2 IsCc PRINCIPAL_ID Takes an RT::Principal id. Returns true if the principal is a Cc of the current ticket. =cut sub IsCc { my $self = shift; my $cc = shift; return ( $self->IsWatcher( Type => 'Cc', PrincipalId => $cc ) ); } =head2 IsAdminCc PRINCIPAL_ID Takes an RT::Principal id. Returns true if the principal is an AdminCc of the current ticket. =cut sub IsAdminCc { my $self = shift; my $person = shift; return ( $self->IsWatcher( Type => 'AdminCc', PrincipalId => $person ) ); } =head2 IsOwner Takes an RT::User object. Returns true if that user is this ticket's owner. returns undef otherwise =cut sub IsOwner { my $self = shift; my $person = shift; # no ACL check since this is used in acl decisions # unless ($self->CurrentUserHasRight('ShowTicket')) { # return(undef); # } #Tickets won't yet have owners when they're being created. unless ( $self->OwnerObj->id ) { return (undef); } if ( $person->id == $self->OwnerObj->id ) { return (1); } else { return (undef); } } =head2 TransactionAddresses Returns a composite hashref of the results of L for all this ticket's Create, Comment or Correspond transactions. The keys are stringified email addresses. Each value is an L object. NOTE: For performance reasons, this method might want to skip transactions and go straight for attachments. But to make that work right, we're going to need to go and walk around the access control in Attachment.pm's sub _Value. =cut sub TransactionAddresses { my $self = shift; my $txns = $self->Transactions; my %addresses = (); my $attachments = RT::Attachments->new( $self->CurrentUser ); $attachments->LimitByTicket( $self->id ); $attachments->Columns( qw( id Headers TransactionId)); # If $TreatAttachedEmailAsFiles is set, don't parse child attachments # for email addresses. if ( RT->Config->Get('TreatAttachedEmailAsFiles') ){ $attachments->Limit( FIELD => 'Parent', VALUE => 0, ); } $attachments->Limit( ALIAS => $attachments->TransactionAlias, FIELD => 'Type', OPERATOR => 'IN', VALUE => [ qw(Create Comment Correspond) ], ); while ( my $att = $attachments->Next ) { foreach my $addrlist ( values %{$att->Addresses } ) { foreach my $addr (@$addrlist) { $addr->address( lc $addr->address ); # force lower-case # Skip addresses without a phrase (things that are just raw addresses) if we have a phrase next if ( $addresses{ $addr->address } && $addresses{ $addr->address }->phrase && not $addr->phrase ); # skips "comment-only" addresses next unless ( $addr->address ); $addresses{ $addr->address } = $addr; } } } return \%addresses; } sub ValidateQueue { my $self = shift; my $Value = shift; if ( !$Value ) { $RT::Logger->warning( " RT:::Queue::ValidateQueue called with a null value. this isn't ok."); return (1); } my $QueueObj = RT::Queue->new( $self->CurrentUser ); my $id = $QueueObj->Load($Value); if ($id) { return (1); } else { return (undef); } } sub SetQueue { my $self = shift; my $value = shift; unless ( $self->CurrentUserHasRight('ModifyTicket') ) { return ( 0, $self->loc("Permission Denied") ); } my ($ok, $msg, $status) = $self->_SetLifecycleColumn( Value => $value, RequireRight => "CreateTicket" ); if ($ok) { # Clear the queue object cache; $self->{_queue_obj} = undef; my $queue = $self->QueueObj; # Untake the ticket if we have no permissions in the new queue unless ($self->OwnerObj->HasRight( Right => 'OwnTicket', Object => $queue )) { my $clone = RT::Ticket->new( RT->SystemUser ); $clone->Load( $self->Id ); unless ( $clone->Id ) { return ( 0, $self->loc("Couldn't load copy of ticket #[_1].", $self->Id) ); } my ($status, $msg) = $clone->SetOwner( RT->Nobody->Id, 'Force' ); $RT::Logger->error("Couldn't set owner on queue change: $msg") unless $status; } # On queue change, change queue for reminders too my $reminder_collection = $self->Reminders->Collection; while ( my $reminder = $reminder_collection->Next ) { my ($status, $msg) = $reminder->_Set( Field => 'Queue', Value => $queue->Id(), RecordTransaction => 0 ); $RT::Logger->error('Queue change failed for reminder #' . $reminder->Id . ': ' . $msg) unless $status; } # Pick up any changes made by the clones above $self->Load( $self->id ); RT->Logger->error("Unable to reload ticket #" . $self->id) unless $self->id; } return ($ok, $msg); } =head2 QueueObj Takes nothing. returns this ticket's queue object =cut sub QueueObj { my $self = shift; if(!$self->{_queue_obj} || ! $self->{_queue_obj}->id) { $self->{_queue_obj} = RT::Queue->new( $self->CurrentUser ); #We call __Value so that we can avoid the ACL decision and some deep recursion my ($result) = $self->{_queue_obj}->Load( $self->__Value('Queue') ); } return ($self->{_queue_obj}); } sub Subject { my $self = shift; my $subject = $self->_Value( 'Subject' ); return $subject if defined $subject; if ( RT->Config->Get( 'DatabaseType' ) eq 'Oracle' && $self->CurrentUserHasRight( 'ShowTicket' ) ) { # Oracle treats empty strings as NULL, so it returns undef for empty subjects. # Since '' is the default Subject value, returning '' is more correct. return ''; } else { return undef; } } sub SetSubject { my $self = shift; my $value = shift; $value =~ s/\n//g; return $self->_Set( Field => 'Subject', Value => $value ); } =head2 SubjectTag Takes nothing. Returns SubjectTag for this ticket. Includes queue's subject tag or rtname if that is not set, ticket id and brackets, for example: [support.example.com #123456] =cut sub SubjectTag { my $self = shift; return '[' . ($self->QueueObj->SubjectTag || RT->Config->Get('rtname')) .' #'. $self->id .']' ; } =head2 DueObj Returns an RT::Date object containing this ticket's due date =cut sub DueObj { my $self = shift; my $time = RT::Date->new( $self->CurrentUser ); # -1 is RT::Date slang for never if ( my $due = $self->Due ) { $time->Set( Format => 'sql', Value => $due ); } else { $time->Set( Format => 'unix', Value => -1 ); } return $time; } =head2 ResolvedObj Returns an RT::Date object of this ticket's 'resolved' time. =cut sub ResolvedObj { my $self = shift; my $time = RT::Date->new( $self->CurrentUser ); $time->Set( Format => 'sql', Value => $self->Resolved ); return $time; } =head2 FirstActiveStatus Returns the first active 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 L, for instance. =cut sub FirstActiveStatus { my $self = shift; my $lifecycle = $self->LifecycleObj; my $status = $self->Status; my @active = $lifecycle->Active; # no change if no active statuses in the lifecycle return undef unless @active; # no change if the ticket is already has first status from the list of active return undef if lc $status eq lc $active[0]; my ($next) = grep $lifecycle->IsActive($_), $lifecycle->Transitions($status); 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 L, for instance. =cut sub FirstInactiveStatus { my $self = shift; my $lifecycle = $self->LifecycleObj; 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 Returns a transaction id and a message The client calls "Start" to note that the project was started on the date in $date. A null date means "now" =cut sub SetStarted { my $self = shift; my $time = shift || 0; unless ( $self->CurrentUserHasRight('ModifyTicket') ) { return ( 0, $self->loc("Permission Denied") ); } #We create a date object to catch date weirdness my $time_obj = RT::Date->new( $self->CurrentUser() ); if ( $time ) { $time_obj->Set( Format => 'ISO', Value => $time ); } else { $time_obj->SetToNow(); } return ( $self->_Set( Field => 'Started', Value => $time_obj->ISO ) ); } =head2 StartedObj Returns an RT::Date object which contains this ticket's 'Started' time. =cut sub StartedObj { my $self = shift; my $time = RT::Date->new( $self->CurrentUser ); $time->Set( Format => 'sql', Value => $self->Started ); return $time; } =head2 StartsObj Returns an RT::Date object which contains this ticket's 'Starts' time. =cut sub StartsObj { my $self = shift; my $time = RT::Date->new( $self->CurrentUser ); $time->Set( Format => 'sql', Value => $self->Starts ); return $time; } =head2 ToldObj Returns an RT::Date object which contains this ticket's 'Told' time. =cut sub ToldObj { my $self = shift; my $time = RT::Date->new( $self->CurrentUser ); $time->Set( Format => 'sql', Value => $self->Told ); return $time; } sub _DurationAsString { my $self = shift; my $value = shift; return "" unless $value; if ($value < 60) { return $self->loc("[quant,_1,minute,minutes]", $value); } else { my $h = sprintf("%.2f", $value / 60 ); return $self->loc("[quant,_1,hour,hours] ([quant,_2,minute,minutes])", $h, $value); } } =head2 TimeWorkedAsString Returns the amount of time worked on this ticket as a text string. =cut sub TimeWorkedAsString { my $self = shift; return $self->_DurationAsString( $self->TimeWorked ); } =head2 TimeLeftAsString Returns the amount of time left on this ticket as a text string. =cut sub TimeLeftAsString { my $self = shift; return $self->_DurationAsString( $self->TimeLeft ); } =head2 TimeEstimatedAsString Returns the amount of time estimated on this ticket as a text string. =cut sub TimeEstimatedAsString { my $self = shift; return $self->_DurationAsString( $self->TimeEstimated ); } =head2 TotalTimeWorked Returns the amount of time worked on this ticket and all child tickets =cut sub TotalTimeWorked { my $self = shift; my $seen = shift || {}; my $time = $self->TimeWorked; my $links = $self->Members; LINK: while (my $link = $links->Next) { my $obj = $link->BaseObj; next LINK unless $obj && UNIVERSAL::isa($obj,'RT::Ticket'); next LINK if $seen->{$obj->Id}; $seen->{ $obj->Id } = 1; $time += $obj->TotalTimeWorked($seen); } return $time; } =head2 TotalTimeWorkedAsString Returns the amount of time worked on this ticket and all its children as a formatted duration string =cut sub TotalTimeWorkedAsString { my $self = shift; return $self->_DurationAsString( $self->TotalTimeWorked ); } =head2 TimeWorkedPerUser Returns a hash of user id to the amount of time worked on this ticket for that user =cut sub TimeWorkedPerUser { my $self = shift; my %time_worked; my $transactions = $self->Transactions; $transactions->Limit( FIELD => 'TimeTaken', VALUE => 0, OPERATOR => '!=', ); while ( my $txn = $transactions->Next ) { $time_worked{ $txn->CreatorObj->Name } += $txn->TimeTaken; } return \%time_worked; } =head2 TotalTimeWorkedPerUser Returns the amount of time worked on this ticket and all child tickets calculated per user =cut sub TotalTimeWorkedPerUser { my $self = shift; my $seen = shift || {}; my $time = $self->TimeWorkedPerUser; my $links = $self->Members; LINK: while (my $link = $links->Next) { my $obj = $link->BaseObj; next LINK unless $obj && UNIVERSAL::isa($obj,'RT::Ticket'); next LINK if $seen->{$obj->Id}; $seen->{ $obj->Id } = 1; my $child_time = $obj->TotalTimeWorkedPerUser($seen); for my $user_id (keys %$child_time) { $time->{$user_id} += $child_time->{$user_id}; } } return $time; } =head2 Comment Comment on this ticket. Takes a hash with the following attributes: If MIMEObj is undefined, Content will be used to build a MIME::Entity for this comment. MIMEObj, TimeTaken, CcMessageTo, BccMessageTo, Content Returns: Transaction id, Error Message, Transaction Object (note the different order from Create()!) =cut sub Comment { my $self = shift; my %args = ( CcMessageTo => undef, BccMessageTo => undef, MIMEObj => undef, Content => undef, TimeTaken => 0, @_ ); unless ( ( $self->CurrentUserHasRight('CommentOnTicket') ) or ( $self->CurrentUserHasRight('ModifyTicket') ) ) { return ( 0, $self->loc("Permission Denied"), undef ); } $args{'NoteType'} = 'Comment'; $RT::Handle->BeginTransaction(); my @results = $self->_RecordNote(%args); if ( not $results[0] ) { $RT::Handle->Rollback(); } else { $RT::Handle->Commit; } return(@results); } =head2 Correspond Correspond on this ticket. Takes a hashref with the following attributes: MIMEObj, TimeTaken, CcMessageTo, BccMessageTo, Content if there's no MIMEObj, Content is used to build a MIME::Entity object Returns: Transaction id, Error Message, Transaction Object (note the different order from Create()!) =cut sub Correspond { my $self = shift; my %args = ( CcMessageTo => undef, BccMessageTo => undef, MIMEObj => undef, Content => undef, TimeTaken => 0, @_ ); unless ( ( $self->CurrentUserHasRight('ReplyToTicket') ) or ( $self->CurrentUserHasRight('ModifyTicket') ) ) { return ( 0, $self->loc("Permission Denied"), undef ); } $args{'NoteType'} = 'Correspond'; $RT::Handle->BeginTransaction(); my @results = $self->_RecordNote(%args); unless ( $results[0] ) { $RT::Handle->Rollback(); return @results; } #Set the last told date to now if this isn't mail from the requestor. #TODO: Note that this will wrongly ack mail from any non-requestor as a "told" unless ( $self->IsRequestor($self->CurrentUser->id) ) { my %squelch; $squelch{$_}++ for map {$_->Content} $self->SquelchMailTo, $results[2]->SquelchMailTo; $self->_SetTold if grep {not $squelch{$_}} $self->Requestors->MemberEmailAddresses; } $RT::Handle->Commit; return (@results); } =head2 _RecordNote the meat of both comment and correspond. Performs no access control checks. hence, dangerous. =cut sub _RecordNote { my $self = shift; my %args = ( CcMessageTo => undef, BccMessageTo => undef, Encrypt => undef, Sign => undef, MIMEObj => undef, Content => undef, NoteType => 'Correspond', TimeTaken => 0, SquelchMailTo => undef, AttachExisting => [], @_ ); unless ( $args{'MIMEObj'} || $args{'Content'} ) { return ( 0, $self->loc("No message attached"), undef ); } unless ( $args{'MIMEObj'} ) { my $data = ref $args{'Content'}? $args{'Content'} : [ $args{'Content'} ]; $args{'MIMEObj'} = MIME::Entity->build( Type => "text/plain", Charset => "UTF-8", Data => [ map {Encode::encode("UTF-8", $_)} @{$data} ], ); } $args{'MIMEObj'}->head->replace('X-RT-Interface' => 'API') unless $args{'MIMEObj'}->head->get('X-RT-Interface'); # convert text parts into utf-8 RT::I18N::SetMIMEEntityToUTF8( $args{'MIMEObj'} ); # Set the magic RT headers which include existing attachments on this note if ($args{'AttachExisting'}) { $args{'AttachExisting'} = [$args{'AttachExisting'}] if not ref $args{'AttachExisting'} eq 'ARRAY'; for my $attach (@{$args{'AttachExisting'}}) { next if $attach =~ /\D/; $args{'MIMEObj'}->head->add( 'RT-Attach' => $attach ); } } # If we've been passed in CcMessageTo and BccMessageTo fields, # add them to the mime object for passing on to the transaction handler # The "NotifyOtherRecipients" scripAction will look for RT-Send-Cc: and # RT-Send-Bcc: headers foreach my $type (qw/Cc Bcc/) { if ( defined $args{ $type . 'MessageTo' } ) { my $addresses = join ', ', ( map { RT::User->CanonicalizeEmailAddress( $_->address ) } Email::Address->parse( $args{ $type . 'MessageTo' } ) ); $args{'MIMEObj'}->head->replace( 'RT-Send-' . $type, Encode::encode( "UTF-8", $addresses ) ); } } foreach my $argument (qw(Encrypt Sign)) { $args{'MIMEObj'}->head->replace( "X-RT-$argument" => $args{ $argument } ? 1 : 0 ) if defined $args{ $argument }; } # If this is from an external source, we need to come up with its # internal Message-ID now, so all emails sent because of this # message have a common Message-ID my $org = RT->Config->Get('Organization'); my $msgid = Encode::decode( "UTF-8", $args{'MIMEObj'}->head->get('Message-ID') ); unless (defined $msgid && $msgid =~ /<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@\Q$org\E>/) { $args{'MIMEObj'}->head->replace( 'RT-Message-ID' => Encode::encode( "UTF-8", RT::Interface::Email::GenMessageId( Ticket => $self ) ) ); } #Record the correspondence (write the transaction) my ( $Trans, $msg, $TransObj ) = $self->_NewTransaction( Type => $args{'NoteType'}, Data => ( Encode::decode( "UTF-8", $args{'MIMEObj'}->head->get('Subject') ) || 'No Subject' ), TimeTaken => $args{'TimeTaken'}, MIMEObj => $args{'MIMEObj'}, SquelchMailTo => $args{'SquelchMailTo'}, ); unless ($Trans) { $RT::Logger->err("$self couldn't init a transaction $msg"); return ( $Trans, $self->loc("Message could not be recorded"), undef ); } if ($args{NoteType} eq "Comment") { $msg = $self->loc("Comments added"); } else { $msg = $self->loc("Correspondence added"); } return ( $Trans, $msg, $TransObj ); } =head2 Atomic Takes one argument, a subroutine reference. Starts a transaction, taking a write lock on this ticket object, and runs the subroutine in the context of that transaction. Commits the transaction at the end of the block. Returns whatever the subroutine returns. If the subroutine explicitly calls L or L, this function respects that, and will skip is usual commit step. If the subroutine dies, this function will abort the transaction (unless it is already aborted or committed, per above), and will re-die with the error. This method should be used to lock, and operate atomically on, all ticket changes via the UI (e.g. L). =cut sub Atomic { my $self = shift; my ($subref) = @_; my $has_id = defined $self->id; $RT::Handle->BeginTransaction; my $depth = $RT::Handle->TransactionDepth; $self->LockForUpdate if $has_id; $self->Load( $self->id ) if $has_id; my $context = wantarray; my @ret; local $@; eval { if ($context) { @ret = $subref->(); } elsif (defined $context) { @ret = scalar $subref->(); } else { $subref->(); } }; if ($@) { $RT::Handle->Rollback if $RT::Handle->TransactionDepth == $depth; die $@; } if ($RT::Handle->TransactionDepth == $depth) { $self->ApplyTransactionBatch; $RT::Handle->Commit; } return $context ? @ret : $ret[0]; } =head2 DryRun Takes one argument, a subroutine reference. Like L, starts a transaction and obtains a write lock on this ticket object, running the subroutine in the context of that transaction. In contrast to L, the transaction is B. As such, the body of the function should not call L or L, as that would break this method's ability to inspect the entire transaction. The return value of the subroutine reference is ignored. Returns the set of L objects that would have resulted from running the body of the transaction. =cut sub DryRun { my $self = shift; my ($subref) = @_; my @transactions; my $has_id = defined $self->id; $RT::Handle->BeginTransaction(); { # Getting nested "commit"s inside this rollback is fine local %DBIx::SearchBuilder::Handle::TRANSROLLBACK; local $self->{DryRun} = \@transactions; # Get a write lock for this whole transaction $self->LockForUpdate if $has_id; eval { $subref->() }; warn "Error is $@" if $@; $self->ApplyTransactionBatch; } @transactions = grep {$_} @transactions; $RT::Handle->Rollback(); return wantarray ? @transactions : $transactions[0]; } sub _Links { my $self = shift; #TODO: Field isn't the right thing here. but I ahave no idea what mnemonic --- #tobias meant by $f my $field = shift; my $type = shift || ""; my $cache_key = "$field$type"; return $self->{ $cache_key } if $self->{ $cache_key }; my $links = $self->{ $cache_key } = RT::Links->new( $self->CurrentUser ); unless ( $self->CurrentUserHasRight('ShowTicket') ) { $links->Limit( FIELD => 'id', VALUE => 0, SUBCLAUSE => 'acl' ); return $links; } # Maybe this ticket is a merge ticket my $limit_on = 'Local'. $field; # at least to myself $links->Limit( FIELD => $limit_on, OPERATOR => 'IN', VALUE => [ $self->id, $self->Merged ], ); $links->Limit( FIELD => 'Type', VALUE => $type, ) if $type; return $links; } =head2 MergeInto MergeInto take the id of the ticket to merge this ticket into. =cut sub MergeInto { my $self = shift; my $ticket_id = shift; unless ( $self->CurrentUserHasRight('ModifyTicket') ) { return ( 0, $self->loc("Permission Denied") ); } # Load up the new ticket. my $MergeInto = RT::Ticket->new($self->CurrentUser); $MergeInto->Load($ticket_id); # make sure it exists. unless ( $MergeInto->Id ) { return ( 0, $self->loc("New ticket doesn't exist") ); } # Can't merge into yourself if ( $MergeInto->Id == $self->Id ) { return ( 0, $self->loc("Can't merge a ticket into itself") ); } # Only tickets can be merged unless ($MergeInto->Type eq 'ticket' && $self->Type eq 'ticket'){ return(0, $self->loc("Only tickets can be merged")); } # Make sure the current user can modify the new ticket. unless ( $MergeInto->CurrentUserHasRight('ModifyTicket') ) { return ( 0, $self->loc("Permission Denied") ); } delete $MERGE_CACHE{'effective'}{ $self->id }; delete @{ $MERGE_CACHE{'merged'} }{ $ticket_id, $MergeInto->id, $self->id }; $RT::Handle->BeginTransaction(); my ($ok, $msg) = $self->_MergeInto( $MergeInto ); $RT::Handle->Commit() if $ok; return ($ok, $msg); } sub _MergeInto { my $self = shift; my $MergeInto = shift; # We use EffectiveId here even though it duplicates information from # the links table becasue of the massive performance hit we'd take # by trying to do a separate database query for merge info everytime # loaded a ticket. #update this ticket's effective id to the new ticket's id. my ( $id_val, $id_msg ) = $self->__Set( Field => 'EffectiveId', Value => $MergeInto->Id() ); unless ($id_val) { $RT::Handle->Rollback(); return ( 0, $self->loc("Merge failed. Couldn't set EffectiveId") ); } ( $id_val, $id_msg ) = $self->__Set( Field => 'IsMerged', Value => 1 ); unless ($id_val) { $RT::Handle->Rollback(); return ( 0, $self->loc("Merge failed. Couldn't set IsMerged") ); } # update all the links that point to that old ticket my $old_links_to = RT::Links->new($self->CurrentUser); $old_links_to->Limit(FIELD => 'Target', VALUE => $self->URI); my %old_seen; while (my $link = $old_links_to->Next) { if (exists $old_seen{$link->Base."-".$link->Type}) { $link->Delete; } elsif ($link->Base eq $MergeInto->URI) { $link->Delete; } else { # First, make sure the link doesn't already exist. then move it over. my $tmp = RT::Link->new(RT->SystemUser); $tmp->LoadByCols(Base => $link->Base, Type => $link->Type, LocalTarget => $MergeInto->id); if ($tmp->id) { $link->Delete; } else { $link->SetTarget($MergeInto->URI); $link->SetLocalTarget($MergeInto->id); } $old_seen{$link->Base."-".$link->Type} =1; } } my $old_links_from = RT::Links->new($self->CurrentUser); $old_links_from->Limit(FIELD => 'Base', VALUE => $self->URI); while (my $link = $old_links_from->Next) { if (exists $old_seen{$link->Type."-".$link->Target}) { $link->Delete; } if ($link->Target eq $MergeInto->URI) { $link->Delete; } else { # First, make sure the link doesn't already exist. then move it over. my $tmp = RT::Link->new(RT->SystemUser); $tmp->LoadByCols(Target => $link->Target, Type => $link->Type, LocalBase => $MergeInto->id); if ($tmp->id) { $link->Delete; } else { $link->SetBase($MergeInto->URI); $link->SetLocalBase($MergeInto->id); $old_seen{$link->Type."-".$link->Target} =1; } } } # Update time fields foreach my $type (qw(TimeEstimated TimeWorked TimeLeft)) { $MergeInto->_Set( Field => $type, Value => ( $MergeInto->$type() || 0 ) + ( $self->$type() || 0 ), RecordTransaction => 0, ); } # add all of this ticket's watchers to that ticket. for my $role ($self->Roles) { my $group = $self->RoleGroup($role); next unless $group->Id; # e.g. lazily-created custom role groups next if $group->SingleMemberRoleGroup; my $people = $group->MembersObj; while ( my $watcher = $people->Next ) { my ($val, $msg) = $MergeInto->AddRoleMember( Type => $role, Silent => 1, PrincipalId => $watcher->MemberId, InsideTransaction => 1, ); unless ($val) { $RT::Logger->debug($msg); } } } #find all of the tickets that were merged into this ticket. my $old_mergees = RT::Tickets->new( $self->CurrentUser ); $old_mergees->Limit( FIELD => 'EffectiveId', OPERATOR => '=', VALUE => $self->Id ); # update their EffectiveId fields to the new ticket's id while ( my $ticket = $old_mergees->Next() ) { my ( $val, $msg ) = $ticket->__Set( Field => 'EffectiveId', Value => $MergeInto->Id() ); } #make a new link: this ticket is merged into that other ticket. $self->AddLink( Type => 'MergedInto', Target => $MergeInto->Id()); $MergeInto->_SetLastUpdated; return ( 1, $self->loc("Merge Successful") ); } =head2 Merged Returns list of tickets' ids that's been merged into this ticket. =cut sub Merged { my $self = shift; my $id = $self->id; return @{ $MERGE_CACHE{'merged'}{ $id } } if $MERGE_CACHE{'merged'}{ $id }; my $mergees = RT::Tickets->new( $self->CurrentUser ); $mergees->LimitField( FIELD => 'EffectiveId', VALUE => $id, ); $mergees->LimitField( FIELD => 'id', OPERATOR => '!=', VALUE => $id, ); return @{ $MERGE_CACHE{'merged'}{ $id } ||= [] } = map $_->id, @{ $mergees->ItemsArrayRef || [] }; } =head2 OwnerObj Takes nothing and returns an RT::User object of this ticket's owner =cut sub OwnerObj { my $self = shift; #If this gets ACLed, we lose on a rights check in User.pm and #get deep recursion. if we need ACLs here, we need #an equiv without ACLs my $owner = RT::User->new( $self->CurrentUser ); $owner->Load( $self->__Value('Owner') ); #Return the owner object return ($owner); } =head2 OwnerAsString Returns the owner's email address =cut sub OwnerAsString { my $self = shift; return ( $self->OwnerObj->EmailAddress ); } =head2 SetOwner Takes two arguments: the Id or Name of the owner and (optionally) the type of the SetOwner Transaction. It defaults to 'Set'. 'Steal' is also a valid option. =cut sub SetOwner { my $self = shift; my $NewOwner = shift; my $Type = shift || "Set"; return $self->Atomic(sub{ my $OldOwnerObj = $self->OwnerObj; my $NewOwnerObj = RT::User->new( $self->CurrentUser ); $NewOwnerObj->Load( $NewOwner ); my ( $val, $msg ) = $self->CurrentUserCanSetOwner( NewOwnerObj => $NewOwnerObj, Type => $Type ); return ( $val, $msg ) unless $val; ($val, $msg ) = $self->OwnerGroup->_AddMember( PrincipalId => $NewOwnerObj->PrincipalId, InsideTransaction => 1, Object => $self, ); unless ($val) { $RT::Handle->Rollback; return ( 0, $self->loc("Could not change owner: [_1]", $msg) ); } $msg = $self->loc( "Owner changed from [_1] to [_2]", $OldOwnerObj->Name, $NewOwnerObj->Name ); return ($val, $msg); }); } =head2 CurrentUserCanSetOwner Confirm the current user can set the owner of the current ticket. There are several different rights to manage owner changes and this method evaluates these rights, guided by parameters provided. This method evaluates these rights in the context of the state of the current ticket. For example, it evaluates Take for tickets that are owned by Nobody because that is the context appropriate for the TakeTicket right. If you need to strictly test a user for a right, use HasRight to check for the right directly. For some custom types of owner changes (C and C), it also verifies that those actions are possible given the current ticket owner. =head3 Rights to Set Owner The current user can set or change the Owner field in the following cases: =over =item * ReassignTicket unconditionally grants the right to set the owner to any user who has OwnTicket. This can be used to break an Owner lock held by another user (see below) and can be a convenient right for managers or administrators who need to assign tickets without necessarily owning them. =item * ModifyTicket grants the right to set the owner to any user who has OwnTicket, provided the ticket is currently owned by the current user or is not owned (owned by Nobody). (See the details on the Force parameter below for exceptions to this.) =item * If the ticket is currently not owned (owned by Nobody), TakeTicket is sufficient to set the owner to yourself (but not an arbitrary person), but only if you have OwnTicket. It is thus a subset of the possible changes provided by ModifyTicket. This exists to allow granting TakeTicket freely, and the broader ModifyTicket only to Owners. =item * If the ticket is currently owned by someone who is not you or Nobody, StealTicket is sufficient to set the owner to yourself, but only if you have OwnTicket. This is hence non-overlapping with the changes provided by ModifyTicket, and is used to break a lock held by another user. =back =head3 Parameters This method returns ($result, $message) with $result containing true or false indicating if the current user can set owner and $message containing a message, typically in the case of a false response. If called with no parameters, this method determines if the current user could set the owner of the current ticket given any permutation of the rights described above. This can be useful when determining whether to make owner-setting options available in the GUI. This method accepts the following parameters as a paramshash: =over =item C Optional; an L object representing the proposed new owner of the ticket. =item C Optional; the type of set owner operation. Valid values are C, C, or C. Note that if the type is C, this method will return false if the current user is already the owner; similarly, it will return false for C if the ticket has no owner or the owner is the current user. =back As noted above, there are exceptions to the standard ticket-based rights described here. The Force option allows for these and is used when moving tickets between queues, for reminders (because the full owner rights system is too complex for them), and optionally during bulk update. =cut sub CurrentUserCanSetOwner { my $self = shift; my %args = ( Type => '', @_); my $OldOwnerObj = $self->OwnerObj; $args{NewOwnerObj} ||= $self->CurrentUser->UserObj if $args{Type} eq "Take" or $args{Type} eq "Steal"; # Confirm rights for new owner if we got one if ( $args{'NewOwnerObj'} ){ my ($ok, $message) = $self->_NewOwnerCanOwnTicket($args{'NewOwnerObj'}, $OldOwnerObj); return ($ok, $message) if not $ok; } # ReassignTicket allows you to SetOwner, but we also need to check ticket's # current owner for Take and Steal Types return ( 1, undef ) if $self->CurrentUserHasRight('ReassignTicket') && $args{Type} ne 'Take' && $args{Type} ne 'Steal'; # Ticket is unowned if ( $OldOwnerObj->Id == RT->Nobody->Id ) { # Steal is not applicable for unowned tickets. if ( $args{'Type'} eq 'Steal' ){ return ( 0, $self->loc("You can only steal a ticket owned by someone else") ) } # Can set owner to yourself with ModifyTicket, ReassignTicket, # or TakeTicket; in all of these cases, OwnTicket is checked by # _NewOwnerCanOwnTicket above. if ( $args{'Type'} eq 'Take' or ( $args{'NewOwnerObj'} and $args{'NewOwnerObj'}->id == $self->CurrentUser->id )) { unless ( $self->CurrentUserHasRight('ModifyTicket') or $self->CurrentUserHasRight('ReassignTicket') or $self->CurrentUserHasRight('TakeTicket') ) { return ( 0, $self->loc("Permission Denied") ); } } else { # Nobody -> someone else requires ModifyTicket or ReassignTicket unless ( $self->CurrentUserHasRight('ModifyTicket') or $self->CurrentUserHasRight('ReassignTicket') ) { return ( 0, $self->loc("Permission Denied") ); } } } # Ticket is owned by someone else # Can set owner to yourself with ModifyTicket or StealTicket # and OwnTicket. elsif ( $OldOwnerObj->Id != RT->Nobody->Id && $OldOwnerObj->Id != $self->CurrentUser->id ) { unless ( $self->CurrentUserHasRight('ModifyTicket') || $self->CurrentUserHasRight('ReassignTicket') || $self->CurrentUserHasRight('StealTicket') ) { return ( 0, $self->loc("Permission Denied") ) } if ( $args{'Type'} eq 'Steal' || $args{'Type'} eq 'Force' ){ return ( 1, undef ) if $self->CurrentUserHasRight('OwnTicket'); return ( 0, $self->loc("Permission Denied") ); } # Not a steal or force if ( $args{'Type'} eq 'Take' or ( $args{'NewOwnerObj'} and $args{'NewOwnerObj'}->id == $self->CurrentUser->id )) { return ( 0, $self->loc("You can only take tickets that are unowned") ); } unless ( $self->CurrentUserHasRight('ReassignTicket') ) { return ( 0, $self->loc( "You can only reassign tickets that you own or that are unowned")); } } # You own the ticket # Untake falls through to here, so we don't need to explicitly handle that Type else { if ( $args{'Type'} eq 'Take' || $args{'Type'} eq 'Steal' ) { return ( 0, $self->loc("You already own this ticket") ); } unless ( $self->CurrentUserHasRight('ModifyTicket') || $self->CurrentUserHasRight('ReassignTicket') ) { return ( 0, $self->loc("Permission Denied") ); } } return ( 1, undef ); } # Verify the proposed new owner can own the ticket. sub _NewOwnerCanOwnTicket { my $self = shift; my $NewOwnerObj = shift; my $OldOwnerObj = shift; unless ( $NewOwnerObj->Id ) { return ( 0, $self->loc("That user does not exist") ); } # The proposed new owner can't own the ticket if ( !$NewOwnerObj->HasRight( Right => 'OwnTicket', Object => $self ) ){ return ( 0, $self->loc("That user may not own tickets in that queue") ); } # Ticket's current owner is the same as the new owner, nothing to do elsif ( $NewOwnerObj->Id == $OldOwnerObj->Id ) { return ( 0, $self->loc("That user already owns that ticket") ); } return (1, undef); } =head2 Take A convenince method to set the ticket's owner to the current user =cut sub Take { my $self = shift; return ( $self->SetOwner( $self->CurrentUser->Id, 'Take' ) ); } =head2 Untake Convenience method to set the owner to 'nobody' if the current user is the owner. =cut sub Untake { my $self = shift; return ( $self->SetOwner( RT->Nobody->UserObj->Id, 'Untake' ) ); } =head2 Steal A convenience method to change the owner of the current ticket to the current user. Even if it's owned by another user. =cut sub Steal { my $self = shift; if ( $self->IsOwner( $self->CurrentUser ) ) { return ( 0, $self->loc("You already own this ticket") ); } else { return ( $self->SetOwner( $self->CurrentUser->Id, 'Steal' ) ); } } =head2 SetStatus STATUS Set this ticket's status. Alternatively, you can pass in a list of named parameters (Status => STATUS, Force => FORCE, SetStarted => SETSTARTED ). If FORCE is true, ignore unresolved dependencies and force a status change. if SETSTARTED is true (it's the default value), set Started to current datetime if Started is not set and the status is changed from initial to not initial. =cut sub SetStatus { my $self = shift; my %args; if (@_ == 1) { $args{Status} = shift; } else { %args = (@_); } # this only allows us to SetStarted, not we must SetStarted. # this option was added for rtir initially $args{SetStarted} = 1 unless exists $args{SetStarted}; my ($valid, $msg) = $self->ValidateStatusChange($args{Status}); return ($valid, $msg) unless $valid; my $lifecycle = $self->LifecycleObj; if ( !$args{Force} && !$lifecycle->IsInactive($self->Status) && $lifecycle->IsInactive($args{Status}) && $self->HasUnresolvedDependencies ) { return ( 0, $self->loc('That ticket has unresolved dependencies') ); } return $self->_SetStatus( Status => $args{Status}, SetStarted => $args{SetStarted}, ); } sub _SetStatus { my $self = shift; my %args = ( Status => undef, SetStarted => 1, RecordTransaction => 1, Lifecycle => $self->LifecycleObj, @_, ); $args{Status} = lc $args{Status} if defined $args{Status}; $args{NewLifecycle} ||= $args{Lifecycle}; my $now = RT::Date->new( $self->CurrentUser ); $now->SetToNow(); my $raw_started = RT::Date->new(RT->SystemUser); $raw_started->Set(Format => 'ISO', Value => $self->__Value('Started')); my $old = $self->__Value('Status'); # If we're changing the status from new, record that we've started if ( $args{SetStarted} && $args{Lifecycle}->IsInitial($old) && !$args{NewLifecycle}->IsInitial($args{Status}) && !$raw_started->IsSet) { # Set the Started time to "now" $self->_Set( Field => 'Started', Value => $now->ISO, RecordTransaction => 0 ); } # When we close a ticket, set the 'Resolved' attribute to now. # It's misnamed, but that's just historical. if ( $args{NewLifecycle}->IsInactive($args{Status}) ) { $self->_Set( Field => 'Resolved', Value => $now->ISO, RecordTransaction => 0, ); } # Actually update the status my ($val, $msg)= $self->_Set( Field => 'Status', Value => $args{Status}, TimeTaken => 0, CheckACL => 0, TransactionType => 'Status', RecordTransaction => $args{RecordTransaction}, ); return ($val, $msg); } sub SetTimeWorked { my $self = shift; my $value = shift; my $taken = ($value||0) - ($self->__Value('TimeWorked')||0); return $self->_Set( Field => 'TimeWorked', Value => $value, TimeTaken => $taken, ); } =head2 Delete Takes no arguments. Marks this ticket for garbage collection =cut sub Delete { my $self = shift; unless ( $self->LifecycleObj->IsValid('deleted') ) { return (0, $self->loc('Delete operation is disabled by lifecycle configuration') ); #loc } return ( $self->SetStatus('deleted') ); } =head2 SetTold ISO [TIMETAKEN] Updates the told and records a transaction =cut sub SetTold { my $self = shift; my $told; $told = shift if (@_); my $timetaken = shift || 0; unless ( $self->CurrentUserHasRight('ModifyTicket') ) { return ( 0, $self->loc("Permission Denied") ); } my $datetold = RT::Date->new( $self->CurrentUser ); if ($told) { $datetold->Set( Format => 'iso', Value => $told ); } else { $datetold->SetToNow(); } return ( $self->_Set( Field => 'Told', Value => $datetold->ISO, TimeTaken => $timetaken, TransactionType => 'Told' ) ); } =head2 _SetTold Updates the told without a transaction or acl check. Useful when we're sending replies. =cut sub _SetTold { my $self = shift; my $now = RT::Date->new( $self->CurrentUser ); $now->SetToNow(); #use __Set to get no ACLs ;) return ( $self->__Set( Field => 'Told', Value => $now->ISO ) ); } =head2 SeenUpTo Returns the first comment/correspond transaction not seen by current user. In list context returns the first not-seen comment/correspond transaction and also the total number of such not-seen transactions. =cut sub SeenUpTo { my $self = shift; my $uid = $self->CurrentUser->id; my $attr = $self->FirstAttribute( "User-". $uid ."-SeenUpTo" ); if ( $attr && $attr->Content gt $self->LastUpdated ) { return wantarray ? ( undef, 0 ) : undef; } my $txns = $self->Transactions; $txns->Limit( FIELD => 'Type', VALUE => 'Comment' ); $txns->Limit( FIELD => 'Type', VALUE => 'Correspond' ); $txns->Limit( FIELD => 'Creator', OPERATOR => '!=', VALUE => $uid ); $txns->Limit( FIELD => 'Created', OPERATOR => '>', VALUE => $attr->Content ) if $attr; my $next_unread_txn = $txns->First; return wantarray ? ( $next_unread_txn, $txns->Count ) : $next_unread_txn; } =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 Returns an array reference of all transactions created on this ticket during this ticket object's lifetime or since last application of a batch, or undef if there were none. Only works when the C config option is set to true. =cut sub TransactionBatch { my $self = shift; return $self->{_TransactionBatch}; } =head2 ApplyTransactionBatch Applies scrips on the current batch of transactions and shinks it. Usually batch is applied when object is destroyed, but in some cases it's too late. =cut sub ApplyTransactionBatch { my $self = shift; my $batch = $self->TransactionBatch; return unless $batch && @$batch; $self->_ApplyTransactionBatch; $self->{_TransactionBatch} = []; } 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; my $types = join ',', grep !$seen{$_}++, grep defined, map $_->__Value('Type'), grep defined, @{$batch}; require RT::Scrips; my $scrips = RT::Scrips->new(RT->SystemUser); $scrips->Prepare( Stage => 'TransactionBatch', TicketObj => $self, TransactionObj => $batch->[0], Type => $types, ); # Entry point of the rule system my $rules = RT::Ruleset->FindAllRules( Stage => 'TransactionBatch', TicketObj => $self, TransactionObj => $batch->[0], Type => $types, ); if ($self->{DryRun}) { my $fake_txn = RT::Transaction->new( $self->CurrentUser ); $fake_txn->{scrips} = $scrips; $fake_txn->{rules} = $rules; push @{$self->{DryRun}}, $fake_txn; } else { $scrips->Commit; RT::Ruleset->CommitRules($rules); } } sub DESTROY { my $self = shift; # DESTROY methods need to localize $@, or it may unset it. This # causes $m->abort to not bubble all of the way up. See perlbug # http://rt.perl.org/rt3/Ticket/Display.html?id=17650 local $@; # The following line eliminates reentrancy. # It protects against the fact that perl doesn't deal gracefully # when an object's refcount is changed in its destructor. return if $self->{_Destroyed}++; if (in_global_destruction()) { unless ($ENV{'HARNESS_ACTIVE'}) { warn "Too late to safely run transaction-batch scrips!" ." This is typically caused by using ticket objects" ." at the top-level of a script which uses the RT API." ." Be sure to explicitly undef such ticket objects," ." or put them inside of a lexical scope."; } return; } return $self->ApplyTransactionBatch; } sub _OverlayAccessible { { EffectiveId => { 'read' => 1, 'write' => 1, 'public' => 1 }, Queue => { 'read' => 1, 'write' => 1 }, Requestors => { 'read' => 1, 'write' => 1 }, Owner => { 'read' => 1, 'write' => 1 }, Subject => { 'read' => 1, 'write' => 1 }, InitialPriority => { 'read' => 1, 'write' => 1 }, FinalPriority => { 'read' => 1, 'write' => 1 }, Priority => { 'read' => 1, 'write' => 1 }, Status => { 'read' => 1, 'write' => 1 }, TimeEstimated => { 'read' => 1, 'write' => 1 }, TimeWorked => { 'read' => 1, 'write' => 1 }, TimeLeft => { 'read' => 1, 'write' => 1 }, Told => { 'read' => 1, 'write' => 1 }, Resolved => { 'read' => 1 }, Type => { 'read' => 1 }, Starts => { 'read' => 1, 'write' => 1 }, Started => { 'read' => 1, 'write' => 1 }, Due => { 'read' => 1, 'write' => 1 }, Creator => { 'read' => 1, 'auto' => 1 }, Created => { 'read' => 1, 'auto' => 1 }, LastUpdatedBy => { 'read' => 1, 'auto' => 1 }, LastUpdated => { 'read' => 1, 'auto' => 1 } }; } sub _Set { my $self = shift; my %args = ( Field => undef, Value => undef, TimeTaken => 0, RecordTransaction => 1, CheckACL => 1, TransactionType => 'Set', @_ ); if ($args{'CheckACL'}) { unless ( $self->CurrentUserHasRight('ModifyTicket')) { return ( 0, $self->loc("Permission Denied")); } } # Avoid ACL loops using _Value my $Old = $self->SUPER::_Value($args{'Field'}); # Set the new value my ( $ret, $msg ) = $self->SUPER::_Set( Field => $args{'Field'}, Value => $args{'Value'} ); return ( 0, $msg ) unless $ret; return ( $ret, $msg ) unless $args{'RecordTransaction'}; my $trans; ( $ret, $msg, $trans ) = $self->_NewTransaction( Type => $args{'TransactionType'}, Field => $args{'Field'}, NewValue => $args{'Value'}, OldValue => $Old, TimeTaken => $args{'TimeTaken'}, ); # Ensure that we can read the transaction, even if the change # just made the ticket unreadable to us $trans->{ _object_is_readable } = 1; return ( $ret, scalar $trans->BriefDescription, $trans ); } =head2 _Value Takes the name of a table column. Returns its value as a string, if the user passes an ACL check =cut sub _Value { my $self = shift; my $field = shift; #if the field is public, return it. if ( $self->_Accessible( $field, 'public' ) ) { #$RT::Logger->debug("Skipping ACL check for $field"); return ( $self->SUPER::_Value($field) ); } #If the current user doesn't have ACLs, don't let em at it. unless ( $self->CurrentUserHasRight('ShowTicket') ) { return (undef); } return ( $self->SUPER::_Value($field) ); } =head2 Attachments Customization of L for tickets. =cut sub Attachments { my $self = shift; my %args = ( WithHeaders => 0, WithContent => 0, @_ ); my $res = RT::Attachments->new( $self->CurrentUser ); unless ( $self->CurrentUserHasRight('ShowTicket') ) { $res->Limit( SUBCLAUSE => 'acl', FIELD => 'id', VALUE => 0, ENTRYAGGREGATOR => 'AND' ); return $res; } my @columns = grep { not /^(Headers|Content)$/ } RT::Attachment->ReadableAttributes; push @columns, 'Headers' if $args{'WithHeaders'}; push @columns, 'Content' if $args{'WithContent'}; $res->Columns( @columns ); my $txn_alias = $res->TransactionAlias; $res->Limit( ALIAS => $txn_alias, FIELD => 'ObjectType', VALUE => ref($self), ); my $ticket_alias = $res->Join( ALIAS1 => $txn_alias, FIELD1 => 'ObjectId', TABLE2 => 'Tickets', FIELD2 => 'id', ); $res->Limit( ALIAS => $ticket_alias, FIELD => 'EffectiveId', VALUE => $self->id, ); return $res; } =head2 TextAttachments Customization of L for tickets. =cut sub TextAttachments { my $self = shift; my $res = $self->SUPER::TextAttachments( @_ ); unless ( $self->CurrentUserHasRight('ShowTicketComments') ) { # if the user may not see comments do not return them $res->Limit( SUBCLAUSE => 'ACL', ALIAS => $res->TransactionAlias, FIELD => 'Type', OPERATOR => '!=', VALUE => 'Comment', ); } return $res; } =head2 _UpdateTimeTaken This routine will increment the timeworked counter. it should only be called from _NewTransaction =cut sub _UpdateTimeTaken { my $self = shift; my $Minutes = shift; my %rest = @_; if ( my $txn = $rest{'Transaction'} ) { return if $txn->__Value('Type') eq 'Set' && $txn->__Value('Field') eq 'TimeWorked'; } my $Total = $self->__Value("TimeWorked"); $Total = ( $Total || 0 ) + ( $Minutes || 0 ); $self->_Set( Field => "TimeWorked", Value => $Total, RecordTransaction => 0, CheckACL => 0, ); return ($Total); } =head2 CurrentUserCanSee Returns true if the current user can see the ticket, using ShowTicket =cut sub CurrentUserCanSee { my $self = shift; my ($what, $txn) = @_; return 0 unless $self->CurrentUserHasRight('ShowTicket'); return 1 if $what ne "Transaction"; # If it's a comment, we need to be extra special careful my $type = $txn->__Value('Type'); if ( $type eq 'Comment' ) { unless ( $self->CurrentUserHasRight('ShowTicketComments') ) { return 0; } } elsif ( $type eq 'CommentEmailRecord' ) { unless ( $self->CurrentUserHasRight('ShowTicketComments') && $self->CurrentUserHasRight('ShowOutgoingEmail') ) { return 0; } } elsif ( $type eq 'EmailRecord' ) { unless ( $self->CurrentUserHasRight('ShowOutgoingEmail') ) { return 0; } } return 1; } =head2 Reminders Return the Reminders object for this ticket. (It's an RT::Reminders object.) It isn't acutally a searchbuilder collection itself. =cut sub Reminders { my $self = shift; unless ($self->{'__reminders'}) { $self->{'__reminders'} = RT::Reminders->new($self->CurrentUser); $self->{'__reminders'}->Ticket($self->id); } return $self->{'__reminders'}; } =head2 Transactions Returns an RT::Transactions object of all transactions on this ticket =cut sub Transactions { my $self = shift; my $transactions = RT::Transactions->new( $self->CurrentUser ); #If the user has no rights, return an empty object if ( $self->CurrentUserHasRight('ShowTicket') ) { $transactions->LimitToTicket($self->id); # if the user may not see comments do not return them unless ( $self->CurrentUserHasRight('ShowTicketComments') ) { $transactions->Limit( SUBCLAUSE => 'acl', FIELD => 'Type', OPERATOR => '!=', VALUE => "Comment" ); $transactions->Limit( SUBCLAUSE => 'acl', FIELD => 'Type', OPERATOR => '!=', VALUE => "CommentEmailRecord", ENTRYAGGREGATOR => 'AND' ); } } else { $transactions->Limit( SUBCLAUSE => 'acl', FIELD => 'id', VALUE => 0, ENTRYAGGREGATOR => 'AND' ); } return ($transactions); } =head2 TransactionCustomFields Returns the custom fields that transactions on tickets will have. =cut sub TransactionCustomFields { my $self = shift; my $cfs = $self->QueueObj->TicketTransactionCustomFields; $cfs->SetContextObject( $self ); return $cfs; } =head2 LoadCustomFieldByIdentifier Finds and returns the custom field of the given name for the ticket, overriding L to look for queue-specific CFs before global ones. =cut sub LoadCustomFieldByIdentifier { my $self = shift; my $field = shift; return $self->SUPER::LoadCustomFieldByIdentifier($field) if ref $field or $field =~ /^\d+$/; my $cf = RT::CustomField->new( $self->CurrentUser ); $cf->SetContextObject( $self ); $cf->LoadByName( Name => $field, LookupType => $self->CustomFieldLookupType, ObjectId => $self->Queue, IncludeGlobal => 1, ); return $cf; } =head2 CustomFieldLookupType Returns the RT::Ticket lookup type, which can be passed to RT::CustomField->Create() via the 'LookupType' hash key. =cut sub CustomFieldLookupType { "RT::Queue-RT::Ticket"; } =head2 ACLEquivalenceObjects This method returns a list of objects for which a user's rights also apply to this ticket. Generally, this is only the ticket's queue, but some RT extensions may make other objects available too. This method is called from L. =cut sub ACLEquivalenceObjects { my $self = shift; return $self->QueueObj; } =head2 ModifyLinkRight =cut sub ModifyLinkRight { "ModifyTicket" } =head2 Forward Transaction => undef, To => '', Cc => '', Bcc => '' Forwards transaction with all attachments as 'message/rfc822'. =cut sub Forward { my $self = shift; my %args = ( Transaction => undef, Subject => '', To => '', Cc => '', Bcc => '', Content => '', ContentType => 'text/plain', @_ ); unless ( $self->CurrentUserHasRight('ForwardMessage') ) { return ( 0, $self->loc("Permission Denied") ); } $args{$_} = join ", ", map { $_->format } RT::EmailParser->ParseEmailAddress( $args{$_} || '' ) for qw(To Cc Bcc); return (0, $self->loc("Can't forward: no valid email addresses specified") ) unless grep {length $args{$_}} qw/To Cc Bcc/; my $mime = MIME::Entity->build( Type => $args{ContentType}, Data => Encode::encode( "UTF-8", $args{Content} ), ); $mime->head->replace( $_ => Encode::encode('UTF-8',$args{$_} ) ) for grep defined $args{$_}, qw(Subject To Cc Bcc); $mime->head->replace( From => Encode::encode( 'UTF-8', RT::Interface::Email::GetForwardFrom( Transaction => $args{Transaction}, Ticket => $self, ) ) ); for my $argument (qw(Encrypt Sign)) { if ( defined $args{ $argument } ) { $mime->head->replace( "X-RT-$argument" => $args{$argument} ? 1 : 0 ); } } my ( $ret, $msg ) = $self->_NewTransaction( $args{Transaction} ? ( Type => 'Forward Transaction', Field => $args{Transaction}->id, ) : ( Type => 'Forward Ticket', Field => $self->id, ), Data => join( ', ', grep { length } $args{To}, $args{Cc}, $args{Bcc} ), MIMEObj => $mime, ); unless ($ret) { $RT::Logger->error("Failed to create transaction: $msg"); } return ( $ret, $self->loc('Message recorded') ); } =head2 CurrentUserCanSeeTime Returns true if the current user can see time worked, estimated, left =cut sub CurrentUserCanSeeTime { my $self = shift; return $self->CurrentUser->Privileged || !RT->Config->Get('HideTimeFieldsFromUnprivilegedUsers'); } sub _DateForCustomDateRangeField { my $self = shift; my $field = shift or return; state $alias = { 'lastcontact' => 'told' }; return $self->SUPER::_DateForCustomDateRangeField( $alias->{lc $field} || $field, @_); } sub _CustomDateRangeFieldParser { my $self = shift; return $self->SUPER::_CustomDateRangeFieldParser . '|' . qr{LastContact}i; } sub Table {'Tickets'} =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 EffectiveId Returns the current value of EffectiveId. (In the database, EffectiveId is stored as int(11).) =head2 SetEffectiveId VALUE Set EffectiveId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, EffectiveId will be stored as a int(11).) =cut =head2 Queue Returns the current value of Queue. (In the database, Queue is stored as int(11).) =head2 SetQueue VALUE Set Queue to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Queue will be stored as a int(11).) =cut =head2 Type Returns the current value of Type. (In the database, Type is stored as varchar(16).) =head2 SetType VALUE Set Type to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Type will be stored as a varchar(16).) =cut =head2 Owner Returns the current value of Owner. (In the database, Owner is stored as int(11).) =head2 SetOwner VALUE Set Owner to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Owner will be stored as a int(11).) =cut =head2 Subject Returns the current value of Subject. (In the database, Subject is stored as varchar(200).) =head2 SetSubject VALUE Set Subject to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Subject will be stored as a varchar(200).) =cut =head2 InitialPriority Returns the current value of InitialPriority. (In the database, InitialPriority is stored as int(11).) =head2 SetInitialPriority VALUE Set InitialPriority to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, InitialPriority will be stored as a int(11).) =cut =head2 FinalPriority Returns the current value of FinalPriority. (In the database, FinalPriority is stored as int(11).) =head2 SetFinalPriority VALUE Set FinalPriority to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, FinalPriority will be stored as a int(11).) =cut =head2 Priority Returns the current value of Priority. (In the database, Priority is stored as int(11).) =head2 SetPriority VALUE Set Priority to VALUE. Accepts numeric and string values for priority. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Priority will be stored as a int(11).) =cut sub SetPriority { my $self = shift; my $priority = shift; my $number; if ( $priority =~ /^\d+$/ ) { # Already a digit $number = $priority; } else { # Try to load a digit from the string $number = $self->_PriorityAsNumber($priority); } return $self->_Set( Field => 'Priority', Value => $number || 0 ); } =head2 TimeEstimated Returns the current value of TimeEstimated. (In the database, TimeEstimated is stored as int(11).) =head2 SetTimeEstimated VALUE Set TimeEstimated to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, TimeEstimated will be stored as a int(11).) =cut =head2 TimeWorked Returns the current value of TimeWorked. (In the database, TimeWorked is stored as int(11).) =head2 SetTimeWorked VALUE Set TimeWorked to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, TimeWorked will be stored as a int(11).) =cut =head2 Status Returns the current value of Status. (In the database, Status is stored as varchar(64).) =head2 SetStatus VALUE Set Status to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Status will be stored as a varchar(64).) =cut =head2 TimeLeft Returns the current value of TimeLeft. (In the database, TimeLeft is stored as int(11).) =head2 SetTimeLeft VALUE Set TimeLeft to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, TimeLeft will be stored as a int(11).) =cut =head2 Told Returns the current value of Told. (In the database, Told is stored as datetime.) =head2 SetTold VALUE Set Told to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Told will be stored as a datetime.) =cut =head2 Starts Returns the current value of Starts. (In the database, Starts is stored as datetime.) =head2 SetStarts VALUE Set Starts to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Starts will be stored as a datetime.) =cut =head2 Started Returns the current value of Started. (In the database, Started is stored as datetime.) =head2 SetStarted VALUE Set Started to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Started will be stored as a datetime.) =cut =head2 Due Returns the current value of Due. (In the database, Due is stored as datetime.) =head2 SetDue VALUE Set Due to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Due will be stored as a datetime.) =cut =head2 Resolved Returns the current value of Resolved. (In the database, Resolved is stored as datetime.) =head2 SetResolved VALUE Set Resolved to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Resolved will be stored as a datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, EffectiveId => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, IsMerged => {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => undef}, Queue => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Type => {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, Owner => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Subject => {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => '[no subject]'}, InitialPriority => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, FinalPriority => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Priority => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, TimeEstimated => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, TimeWorked => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Status => {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''}, SLA => {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''}, TimeLeft => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Told => {read => 1, write => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, Starts => {read => 1, write => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, Started => {read => 1, write => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, Due => {read => 1, write => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, Resolved => {read => 1, write => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); # Links my $links = RT::Links->new( $self->CurrentUser ); $links->Limit( SUBCLAUSE => "either", FIELD => $_, VALUE => $self->URI, ENTRYAGGREGATOR => 'OR' ) for qw/Base Target/; $deps->Add( in => $links ); # Tickets which were merged in my $objs = RT::Tickets->new( $self->CurrentUser ); $objs->Limit( FIELD => 'EffectiveId', VALUE => $self->Id ); $objs->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => $self->Id ); $deps->Add( in => $objs ); # Ticket role groups( Owner, Requestors, Cc, AdminCc ) $objs = RT::Groups->new( $self->CurrentUser ); $objs->Limit( FIELD => 'Domain', VALUE => 'RT::Ticket-Role', CASESENSITIVE => 0 ); $objs->Limit( FIELD => 'Instance', VALUE => $self->Id ); $deps->Add( in => $objs ); # Queue $deps->Add( out => $self->QueueObj ); # Owner $deps->Add( out => $self->OwnerObj ); } sub __DependsOn { my $self = shift; my %args = ( Shredder => undef, Dependencies => undef, @_, ); my $deps = $args{'Dependencies'}; my $list = []; # Tickets which were merged in my $objs = RT::Tickets->new( $self->CurrentUser ); $objs->{'allow_deleted_search'} = 1; $objs->Limit( FIELD => 'EffectiveId', VALUE => $self->Id ); $objs->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => $self->Id ); push( @$list, $objs ); # Ticket role groups( Owner, Requestors, Cc, AdminCc ) $objs = RT::Groups->new( $self->CurrentUser ); $objs->Limit( FIELD => 'Domain', VALUE => 'RT::Ticket-Role', CASESENSITIVE => 0 ); $objs->Limit( FIELD => 'Instance', VALUE => $self->Id ); push( @$list, $objs ); #TODO: Users, Queues if we wish export tool $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON, TargetObjects => $list, Shredder => $args{'Shredder'} ); return $self->SUPER::__DependsOn( %args ); } sub Serialize { my $self = shift; my %args = (@_); my %store = $self->SUPER::Serialize(@_); my $obj = RT::Ticket->new( RT->SystemUser ); $obj->Load( $store{EffectiveId} ); $store{EffectiveId} = \($obj->UID); return %store; } sub PriorityAsString { my $self = shift; return $self->_PriorityAsString( $self->Priority ); } sub InitialPriorityAsString { my $self = shift; return $self->_PriorityAsString( $self->InitialPriority ); } sub FinalPriorityAsString { my $self = shift; return $self->_PriorityAsString( $self->FinalPriority ); } sub _PriorityAsString { my $self = shift; my $priority = shift; my $queue_name = shift || $self->QueueObj->__Value('Name'); # Skip ACL check return undef unless defined $priority && length $priority && RT->Config->Get('EnablePriorityAsString'); my $map_ref = $self->GetPriorityAsStringMapping($queue_name); return undef unless $map_ref; # Count from high down to low until we find one that our number is # greater than or equal to. foreach my $label ( sort { $map_ref->{$b} <=> $map_ref->{$a} } keys %$map_ref ) { return $label if $priority >= $map_ref->{$label}; } return "unknown"; } sub _PriorityAsNumber { my $self = shift; my $priority = shift; my $queue_name = shift || $self->QueueObj->__Value('Name'); # Skip ACL check return undef unless defined $priority && length $priority && RT->Config->Get('EnablePriorityAsString'); my $map_ref = $self->GetPriorityAsStringMapping($queue_name); return undef unless $map_ref; return $map_ref->{$priority} if exists $map_ref->{$priority}; return undef; } sub GetPriorityAsStringMapping { my $self = shift; my $queue_name = shift || $self->QueueObj->__Value('Name'); # Skip ACL check my %config = RT->Config->Get('PriorityAsString'); my $value = ( exists $config{$queue_name} ? $config{$queue_name} : $config{Default} ) or return undef; my %map; if ( ref $value eq 'ARRAY' ) { %map = @$value; } elsif ( ref $value eq 'HASH' ) { %map = %$value; } else { RT->Logger->warning("Invalid PriorityAsString value: $value"); } return \%map; } RT::Base->_ImportOverlays(); 1; rt-5.0.1/lib/RT/Pod/000755 000765 000024 00000000000 14005011336 014616 5ustar00sunnavystaff000000 000000 rt-5.0.1/lib/RT/CachedGroupMember.pm000644 000765 000024 00000032211 14005011336 017745 0ustar00sunnavystaff000000 000000 # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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 }}} package RT::CachedGroupMember; use strict; use warnings; use base 'RT::Record'; sub Table {'CachedGroupMembers'} =head1 NAME RT::CachedGroupMember =head1 SYNOPSIS use RT::CachedGroupMember; =head1 DESCRIPTION =head1 METHODS =cut # {{ Create =head2 Create PARAMHASH Create takes a hash of values and creates a row in the database: 'Group' is the "top level" group we're building the cache for. This is an RT::Principal object 'Member' is the RT::Principal of the user or group we're adding to the cache. 'ImmediateParent' is the RT::Principal of the group that this principal belongs to to get here int(11) 'Via' is an internal reference to CachedGroupMembers->Id of the "parent" record of this cached group member. It should be empty if this member is a "direct" member of this group. (In that case, it will be set to this cached group member's id after creation) This routine should _only_ be called by GroupMember->Create =cut sub Create { my $self = shift; my %args = ( Group => '', Member => '', ImmediateParent => '', Via => '0', Disabled => '0', @_ ); unless ( $args{'Member'} && UNIVERSAL::isa( $args{'Member'}, 'RT::Principal' ) && $args{'Member'}->Id ) { $RT::Logger->debug("$self->Create: bogus Member argument"); } unless ( $args{'Group'} && UNIVERSAL::isa( $args{'Group'}, 'RT::Principal' ) && $args{'Group'}->Id ) { $RT::Logger->debug("$self->Create: bogus Group argument"); } unless ( $args{'ImmediateParent'} && UNIVERSAL::isa( $args{'ImmediateParent'}, 'RT::Principal' ) && $args{'ImmediateParent'}->Id ) { $RT::Logger->debug("$self->Create: bogus ImmediateParent argument"); } # If the parent group for this group member is disabled, it's disabled too, along with all its children if ( $args{'ImmediateParent'}->Disabled ) { $args{'Disabled'} = $args{'ImmediateParent'}->Disabled; } my $id = $self->SUPER::Create( GroupId => $args{'Group'}->Id, MemberId => $args{'Member'}->Id, ImmediateParentId => $args{'ImmediateParent'}->Id, Disabled => $args{'Disabled'}, Via => $args{'Via'}, ); unless ($id) { $RT::Logger->warning( "Couldn't create " . $args{'Member'} . " as a cached member of " . $args{'Group'}->Id . " via " . $args{'Via'} ); return (undef); #this will percolate up and bail out of the transaction } if ( $self->__Value('Via') == 0 ) { my ( $vid, $vmsg ) = $self->__Set( Field => 'Via', Value => $id ); unless ($vid) { $RT::Logger->warning( "Due to a via error, couldn't create " . $args{'Member'} . " as a cached member of " . $args{'Group'}->Id . " via " . $args{'Via'} ); return (undef) ; #this will percolate up and bail out of the transaction } } return $id if $args{'Member'}->id == $args{'Group'}->id; if ( $args{'Member'}->IsGroup() ) { my $GroupMembers = $args{'Member'}->Object->MembersObj(); while ( my $member = $GroupMembers->Next() ) { my $cached_member = RT::CachedGroupMember->new( $self->CurrentUser ); my $c_id = $cached_member->Create( Group => $args{'Group'}, Member => $member->MemberObj, ImmediateParent => $args{'Member'}, Disabled => $args{'Disabled'}, Via => $id ); unless ($c_id) { return (undef); #percolate the error upwards. # the caller will log an error and abort the transaction } } } return ($id); } =head2 Delete Deletes the current CachedGroupMember from the group it's in and cascades the delete to all submembers. =cut sub Delete { my $self = shift; my $member = $self->MemberObj(); if ( $member->IsGroup ) { my $deletable = RT::CachedGroupMembers->new( $self->CurrentUser ); $deletable->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => $self->id ); $deletable->Limit( FIELD => 'Via', OPERATOR => '=', VALUE => $self->id ); while ( my $kid = $deletable->Next ) { my ($ok, $msg) = $kid->Delete(); unless ($ok) { $RT::Logger->error( "Couldn't delete CachedGroupMember " . $kid->Id ); return ($ok, $msg); } } } my ($ok, $msg) = $self->SUPER::Delete(); $RT::Logger->error( "Couldn't delete CachedGroupMember " . $self->Id ) unless $ok; return ($ok, $msg); } =head2 SetDisabled SetDisableds the current CachedGroupMember from the group it's in and cascades the SetDisabled to all submembers. This routine could be completely excised if mysql supported foreign keys with cascading SetDisableds. =cut sub SetDisabled { my $self = shift; my $val = shift; # if it's already disabled, we're good. return (1) if ( $self->__Value('Disabled') == $val); my $err = $self->_Set(Field => 'Disabled', Value => $val); my ($retval, $msg) = $err->as_array(); unless ($retval) { $RT::Logger->error( "Couldn't SetDisabled CachedGroupMember " . $self->Id .": $msg"); return ($err); } my $member = $self->MemberObj(); if ( $member->IsGroup ) { my $deletable = RT::CachedGroupMembers->new( $self->CurrentUser ); $deletable->Limit( FIELD => 'Via', OPERATOR => '=', VALUE => $self->id ); $deletable->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => $self->id ); while ( my $kid = $deletable->Next ) { my $kid_err = $kid->SetDisabled($val ); unless ($kid_err) { $RT::Logger->error( "Couldn't SetDisabled CachedGroupMember " . $kid->Id ); return ($kid_err); } } } return ($err); } =head2 GroupObj Returns the RT::Principal object for this group Group =cut sub GroupObj { my $self = shift; my $principal = RT::Principal->new( $self->CurrentUser ); $principal->Load( $self->GroupId ); return ($principal); } =head2 ImmediateParentObj Returns the RT::Principal object for this group ImmediateParent =cut sub ImmediateParentObj { my $self = shift; my $principal = RT::Principal->new( $self->CurrentUser ); $principal->Load( $self->ImmediateParentId ); return ($principal); } =head2 MemberObj Returns the RT::Principal object for this group member =cut sub MemberObj { my $self = shift; my $principal = RT::Principal->new( $self->CurrentUser ); $principal->Load( $self->MemberId ); return ($principal); } # }}} =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 GroupId Returns the current value of GroupId. (In the database, GroupId is stored as int(11).) =head2 SetGroupId VALUE Set GroupId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, GroupId will be stored as a int(11).) =cut =head2 MemberId Returns the current value of MemberId. (In the database, MemberId is stored as int(11).) =head2 SetMemberId VALUE Set MemberId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, MemberId will be stored as a int(11).) =cut =head2 Via Returns the current value of Via. (In the database, Via is stored as int(11).) =head2 SetVia VALUE Set Via to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Via will be stored as a int(11).) =cut =head2 ImmediateParentId Returns the current value of ImmediateParentId. (In the database, ImmediateParentId is stored as int(11).) =head2 SetImmediateParentId VALUE Set ImmediateParentId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ImmediateParentId will be stored as a int(11).) =cut =head2 Disabled Returns the current value of Disabled. (In the database, Disabled is stored as smallint(6).) =head2 SetDisabled VALUE Set Disabled to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Disabled will be stored as a smallint(6).) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, GroupId => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, MemberId => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Via => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, ImmediateParentId => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Disabled => {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'}, } }; sub Serialize { die "CachedGroupMembers should never be serialized"; } sub __DependsOn { my $self = shift; my %args = ( Shredder => undef, Dependencies => undef, @_, ); my $deps = $args{'Dependencies'}; my $list = []; # deep memebership my $objs = RT::CachedGroupMembers->new( $self->CurrentUser ); $objs->Limit( FIELD => 'Via', VALUE => $self->Id ); $objs->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => $self->Id ); push( @$list, $objs ); # principal lost group membership and lost some rights which he could delegate to # some body # XXX: Here is problem cause HasMemberRecursively would return true allways # cause we didn't delete anything yet. :( # if pricipal is not member anymore(could be via other groups) then proceed if( $self->GroupObj->Object->HasMemberRecursively( $self->MemberObj ) ) { my $acl = RT::ACL->new( $self->CurrentUser ); $acl->LimitToPrincipal( Id => $self->GroupId ); } $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON, TargetObjects => $list, Shredder => $args{'Shredder'} ); return $self->SUPER::__DependsOn( %args ); } RT::Base->_ImportOverlays(); 1; rt-5.0.1/lib/RT/User.pm000644 000765 000024 00000253553 14005011336 015365 0ustar00sunnavystaff000000 000000 # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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 }}} =head1 NAME RT::User - RT User object =head1 SYNOPSIS use RT::User; =head1 DESCRIPTION Object to operate on a single RT user record. =head1 METHODS =cut package RT::User; use strict; use warnings; use Scalar::Util qw(blessed); use base 'RT::Record'; sub Table {'Users'} use Digest::SHA; use Digest::MD5; use Crypt::Eksblowfish::Bcrypt qw(); use RT::Principals; use RT::ACE; use RT::Interface::Email; use Text::Password::Pronounceable; use RT::Util; sub _OverlayAccessible { { Name => { public => 1, admin => 1 }, # loc_left_pair Password => { read => 0 }, EmailAddress => { public => 1 }, # loc_left_pair Organization => { public => 1, admin => 1 }, # loc_left_pair RealName => { public => 1 }, # loc_left_pair NickName => { public => 1 }, # loc_left_pair Lang => { public => 1 }, # loc_left_pair Gecos => { public => 1, admin => 1 }, # loc_left_pair SMIMECertificate => { public => 1, admin => 1 }, # loc_left_pair City => { public => 1 }, # loc_left_pair Country => { public => 1 }, # loc_left_pair Timezone => { public => 1 }, # loc_left_pair } } =head2 Create { PARAMHASH } Create accepts all core RT::User fields (Name, EmailAddress, etc.) and user custom fields in the form UserCF.Foo where Foo is the name of the custom field. my ($ret, $msg) = $user->Create( Name => 'mycroft', EmailAddress => 'mycroft@example.com', UserCF.Relationship => 'Brother' ); =cut sub Create { my $self = shift; my %args = ( Privileged => 0, Disabled => 0, EmailAddress => '', _RecordTransaction => 1, @_ # get the real argumentlist ); # remove the value so it does not cripple SUPER::Create my $record_transaction = delete $args{'_RecordTransaction'}; #Check the ACL unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) { return ( 0, $self->loc('Permission Denied') ); } unless ($self->CanonicalizeUserInfo(\%args)) { return ( 0, $self->loc("Could not set user info") ); } $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'}); # if the user doesn't have a name defined, set it to the email address $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'}); my $privileged = delete $args{'Privileged'}; if ($args{'CryptedPassword'} ) { $args{'Password'} = $args{'CryptedPassword'}; delete $args{'CryptedPassword'}; } elsif ( !$args{'Password'} ) { $args{'Password'} = '*NO-PASSWORD*'; } else { my ($ok, $msg) = $self->ValidatePassword($args{'Password'}); return ($ok, $msg) if !$ok; $args{'Password'} = $self->_GeneratePassword($args{'Password'}); } #TODO Specify some sensible defaults. unless ( $args{'Name'} ) { return ( 0, $self->loc("Must specify 'Name' attribute") ); } my ( $val, $msg ) = $self->ValidateName( $args{'Name'} ); return ( 0, $msg ) unless $val; ( $val, $msg ) = $self->ValidateEmailAddress( $args{'EmailAddress'} ); return ( 0, $msg ) unless ($val); $RT::Handle->BeginTransaction(); # Groups deal with principal ids, rather than user ids. # When creating this user, set up a principal Id for it. my $principal = RT::Principal->new($self->CurrentUser); my $principal_id = $principal->Create(PrincipalType => 'User', Disabled => $args{'Disabled'}); # If we couldn't create a principal Id, get the fuck out. unless ($principal_id) { $RT::Handle->Rollback(); $RT::Logger->crit("Couldn't create a Principal on new user create."); $RT::Logger->crit("Strange things are afoot at the circle K"); return ( 0, $self->loc('Could not create user') ); } delete $args{'Disabled'}; $self->SUPER::Create( id => $principal_id, map { $_ => $args{$_} } grep { !/^(?:User)?CF\./ } keys %args ); my $id = $self->Id; #If the create failed. unless ($id) { $RT::Handle->Rollback(); $RT::Logger->error("Could not create a new user - " .join('-', %args)); return ( 0, $self->loc('Could not create user') ); } # Handle any user CFs $self->UpdateObjectCustomFieldValues( %args ); my $aclstash = RT::Group->new($self->CurrentUser); my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal); unless ($stash_id) { $RT::Handle->Rollback(); $RT::Logger->crit("Couldn't stash the user in groupmembers"); return ( 0, $self->loc('Could not create user') ); } my $everyone = RT::Group->new($self->CurrentUser); $everyone->LoadSystemInternalGroup('Everyone'); unless ($everyone->id) { $RT::Logger->crit("Could not load Everyone group on user creation."); $RT::Handle->Rollback(); return ( 0, $self->loc('Could not create user') ); } my ($everyone_id, $everyone_msg) = $everyone->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId); unless ($everyone_id) { $RT::Logger->crit("Could not add user to Everyone group on user creation."); $RT::Logger->crit($everyone_msg); $RT::Handle->Rollback(); return ( 0, $self->loc('Could not create user') ); } my $access_class = RT::Group->new($self->CurrentUser); if ($privileged) { $access_class->LoadSystemInternalGroup('Privileged'); } else { $access_class->LoadSystemInternalGroup('Unprivileged'); } unless ($access_class->id) { $RT::Logger->crit("Could not load Privileged or Unprivileged group on user creation"); $RT::Handle->Rollback(); return ( 0, $self->loc('Could not create user') ); } my ($ac_id, $ac_msg) = $access_class->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId); unless ($ac_id) { $RT::Logger->crit("Could not add user to Privileged or Unprivileged group on user creation. Aborted"); $RT::Logger->crit($ac_msg); $RT::Handle->Rollback(); return ( 0, $self->loc('Could not create user') ); } if ( $record_transaction ) { $self->_NewTransaction( Type => "Create" ); } $RT::Handle->Commit; return ( $id, $self->loc('User created') ); } =head2 UpdateObjectCustomFieldValues Set User CFs from incoming args in the form UserCF.Foo. =cut sub UpdateObjectCustomFieldValues { my $self = shift; my %args = @_; foreach my $rtfield ( sort keys %args ) { next unless $rtfield =~ /^UserCF\.(.+)$/i; my $cf_name = $1; my $value = $args{$rtfield}; $value = '' unless defined $value; my $current = $self->FirstCustomFieldValue($cf_name); $current = '' unless defined $current; if ( not length $current and not length $value ) { $RT::Logger->debug("\tCF.$cf_name\tskipping, no value provided"); next; } elsif ( $current eq $value ) { $RT::Logger->debug("\tCF.$cf_name\tunchanged => $value"); next; } $current = 'unset' unless length $current; $RT::Logger->debug("\tCF.$cf_name\t$current => $value"); my ( $ok, $msg ) = $self->AddCustomFieldValue( Field => $cf_name, Value => $value ); $RT::Logger->error( $self->Name . ": Couldn't add value '$value' for '$cf_name': $msg" ) unless $ok; } return; } =head2 ValidateName STRING Returns either (0, "failure reason") or 1 depending on whether the given name is valid. =cut sub ValidateName { my $self = shift; my $name = shift; return ( 0, $self->loc('empty name') ) unless defined $name && length $name; my $TempUser = RT::User->new( RT->SystemUser ); $TempUser->Load($name); if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) ) { return ( 0, $self->loc('Name in use') ); } else { return 1; } } =head2 GenerateAnonymousName Generate a random username proceeded by 'anon_' and then a random string, Returns the AnonymousName string. =cut sub GenerateAnonymousName { my $self = shift; my $name; do { $name = 'anon_' . Digest::MD5::md5_hex( time . {} . rand() ); } while !$self->ValidateName($name); return $name; } =head2 AnonymizeUser { ClearCustomfields => 1|0 } Remove all personal identifying information on the user record, but keep the user record alive. Additionally replace the username with an anonymous name. Submit ClearCustomfields in a paramhash, if true all customfield values applied to the user record will be cleared. =cut sub AnonymizeUser { my $self = shift; my %args = ( ClearCustomFields => undef, @_, ); my %skip_clear = map { $_ => 1 } qw/Name Password AuthToken/; my @user_identifying_info = grep { !$skip_clear{$_} && $self->_Accessible( $_, 'write' ) } keys %{ $self->_CoreAccessible() }; $RT::Handle->BeginTransaction(); # Remove identifying user information from record foreach my $attr (@user_identifying_info) { if ( defined $self->$attr && length $self->$attr ) { my $method = 'Set' . $attr; my ( $ret, $msg ) = $self->$method(''); unless ($ret) { RT::Logger->error( "Could not clear user $attr: " . $msg ); $RT::Handle->Rollback(); return ( $ret, $self->loc( "Couldn't clear user [_1]", $self->loc($attr) ) ); } } } # Do not do anything if password is already unset if ( $self->HasPassword ) { my ( $ret, $msg ) = $self->_Set( Field => 'Password', Value => '*NO-PASSWORD*' ); unless ($ret) { RT::Logger->error("Could not clear user password: $msg"); $RT::Handle->Rollback(); return ( $ret, "Could not clear user Password" ); } } # Generate the random anon username my ( $ret, $msg ) = $self->SetName( $self->GenerateAnonymousName ); unless ($ret) { RT::Logger->error( "Could not anonymize user Name: " . $msg ); $RT::Handle->Rollback(); return ( $ret, $self->loc( "Could not anonymize user [_1]", $self->loc('Name') ) ); } # Clear AuthToken if ( $self->_Value('AuthToken') ) { my ( $ret, $msg ) = $self->SetAuthToken(''); unless ($ret) { RT::Logger->error( "Could not clear user AuthToken: " . $msg ); $RT::Handle->Rollback(); return ( $ret, $self->loc( "Couldn't clear user [_1]", $self->loc('AuthToken') ) ); } } # Remove user customfield values if ( $args{'ClearCustomFields'} ) { my $cfs = RT::CustomFields->new( RT->SystemUser ); $cfs->LimitToLookupType('RT::User'); while ( my $cf = $cfs->Next ) { my $ocfvs = $self->CustomFieldValues($cf); while ( my $ocfv = $ocfvs->Next ) { my ( $ret, $msg ) = $ocfv->Delete; unless ($ret) { RT::Logger->error( "Could not delete ocfv #" . $ocfv->id . ": $msg" ); $RT::Handle->Rollback(); return ( $ret, $self->loc( "Could not clear user custom field [_1]", $cf->Name ) ); } } } } $RT::Handle->Commit(); return ( 1, $self->loc('User successfully anonymized') ); } =head2 ValidatePassword STRING Returns either (0, "failure reason") or 1 depending on whether the given password is valid. =cut sub ValidatePassword { my $self = shift; my $password = shift; if ( length($password) < RT->Config->Get('MinimumPasswordLength') ) { return ( 0, $self->loc("Password needs to be at least [quant,_1,character,characters] long", RT->Config->Get('MinimumPasswordLength')) ); } return 1; } =head2 SetPrivileged BOOL If passed a true value, makes this user a member of the "Privileged" PseudoGroup. Otherwise, makes this user a member of the "Unprivileged" pseudogroup. Returns a standard RT tuple of (val, msg); =cut sub SetPrivileged { my $self = shift; my $val = shift; #Check the ACL unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) { return ( 0, $self->loc('Permission Denied') ); } $self->_SetPrivileged($val); } sub _SetPrivileged { my $self = shift; my $val = shift; my $priv = RT::Group->new($self->CurrentUser); $priv->LoadSystemInternalGroup('Privileged'); unless ($priv->Id) { $RT::Logger->crit("Could not find Privileged pseudogroup"); return(0,$self->loc("Failed to find 'Privileged' users pseudogroup.")); } my $unpriv = RT::Group->new($self->CurrentUser); $unpriv->LoadSystemInternalGroup('Unprivileged'); unless ($unpriv->Id) { $RT::Logger->crit("Could not find unprivileged pseudogroup"); return(0,$self->loc("Failed to find 'Unprivileged' users pseudogroup")); } my $principal = $self->PrincipalId; if ($val) { if ($priv->HasMember($principal)) { #$RT::Logger->debug("That user is already privileged"); return (0,$self->loc("That user is already privileged")); } if ($unpriv->HasMember($principal)) { $unpriv->_DeleteMember($principal); } else { # if we had layered transactions, life would be good # sadly, we have to just go ahead, even if something # bogus happened $RT::Logger->crit("User ".$self->Id." is neither privileged nor ". "unprivileged. something is drastically wrong."); } my ($status, $msg) = $priv->_AddMember( InsideTransaction => 1, PrincipalId => $principal); if ($status) { $self->_NewTransaction( Type => 'Set', Field => 'Privileged', NewValue => 1, OldValue => 0, ); return (1, $self->loc("That user is now privileged")); } else { return (0, $msg); } } else { if ($unpriv->HasMember($principal)) { #$RT::Logger->debug("That user is already unprivileged"); return (0,$self->loc("That user is already unprivileged")); } if ($priv->HasMember($principal)) { $priv->_DeleteMember( $principal ); } else { # if we had layered transactions, life would be good # sadly, we have to just go ahead, even if something # bogus happened $RT::Logger->crit("User ".$self->Id." is neither privileged nor ". "unprivileged. something is drastically wrong."); } my ($status, $msg) = $unpriv->_AddMember( InsideTransaction => 1, PrincipalId => $principal); if ($status) { $self->_NewTransaction( Type => 'Set', Field => 'Privileged', NewValue => 0, OldValue => 1, ); return (1, $self->loc("That user is now unprivileged")); } else { return (0, $msg); } } } =head2 Privileged Returns true if this user is privileged. Returns undef otherwise. =cut sub Privileged { my $self = shift; if ( RT->PrivilegedUsers->HasMember( $self->id ) ) { return(1); } else { return(undef); } } #create a user without validating _any_ data. #To be used only on database init. # We can't localize here because it's before we _have_ a loc framework sub _BootstrapCreate { my $self = shift; my %args = (@_); $args{'Password'} = '*NO-PASSWORD*'; $RT::Handle->BeginTransaction(); # Groups deal with principal ids, rather than user ids. # When creating this user, set up a principal Id for it. my $principal = RT::Principal->new($self->CurrentUser); my $principal_id = $principal->Create(PrincipalType => 'User'); # If we couldn't create a principal Id, get the fuck out. unless ($principal_id) { $RT::Handle->Rollback(); $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K"); return ( 0, 'Could not create user' ); } $self->SUPER::Create(id => $principal_id, %args); my $id = $self->Id; #If the create failed. unless ($id) { $RT::Handle->Rollback(); return ( 0, 'Could not create user' ) ; #never loc this } my $aclstash = RT::Group->new($self->CurrentUser); my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal); unless ($stash_id) { $RT::Handle->Rollback(); $RT::Logger->crit("Couldn't stash the user in groupmembers"); return ( 0, $self->loc('Could not create user') ); } $RT::Handle->Commit(); return ( $id, 'User created' ); } sub Delete { my $self = shift; return ( 0, $self->loc('Deleting this object would violate referential integrity') ); } =head2 Load Load a user object from the database. Takes a single argument. If the argument is numerical, load by the column 'id'. If a user object or its subclass passed then loads the same user by id. Otherwise, load by the "Name" column which is the user's textual username. =cut sub Load { my $self = shift; my $identifier = shift || return undef; if ( $identifier !~ /\D/ ) { return $self->SUPER::LoadById( $identifier ); } elsif ( UNIVERSAL::isa( $identifier, 'RT::User' ) ) { return $self->SUPER::LoadById( $identifier->Id ); } else { return $self->LoadByCol( "Name", $identifier ); } } =head2 LoadByEmail Tries to load this user object from the database by the user's email address. =cut sub LoadByEmail { my $self = shift; my $address = shift; # Never load an empty address as an email address. unless ($address) { return (undef); } $address = $self->CanonicalizeEmailAddress($address); #$RT::Logger->debug("Trying to load an email address: $address"); return $self->LoadByCol( "EmailAddress", $address ); } =head2 LoadOrCreateByEmail ADDRESS Attempts to find a user who has the provided email address. If that fails, creates an unprivileged user with the provided email address and loads them. Address can be provided either as L object or string which is parsed using the module. Returns a tuple of the user's id and a status message. 0 will be returned in place of the user's id in case of failure. =cut sub LoadOrCreateByEmail { my $self = shift; my %create; if (@_ > 1) { %create = (@_); } elsif ( UNIVERSAL::isa( $_[0] => 'Email::Address' ) ) { @create{'EmailAddress','RealName'} = ($_[0]->address, $_[0]->phrase); } else { my ($addr) = RT::EmailParser->ParseEmailAddress( $_[0] ); @create{'EmailAddress','RealName'} = $addr ? ($addr->address, $addr->phrase) : (undef, undef); } $self->LoadByEmail( $create{EmailAddress} ); $self->Load( $create{EmailAddress} ) unless $self->Id; return wantarray ? ($self->Id, $self->loc("User loaded")) : $self->Id if $self->Id; $create{Name} ||= $create{EmailAddress}; $create{Privileged} ||= 0; $create{Comments} //= 'Autocreated when added as a watcher'; my ($val, $message) = $self->Create( %create ); return wantarray ? ($self->Id, $self->loc("User loaded")) : $self->Id if $self->Id; # Deal with the race condition of two account creations at once $self->LoadByEmail( $create{EmailAddress} ); unless ( $self->Id ) { sleep 5; $self->LoadByEmail( $create{EmailAddress} ); } if ( $self->Id ) { $RT::Logger->error("Recovered from creation failure due to race condition"); return wantarray ? ($self->Id, $self->loc("User loaded")) : $self->Id; } else { $RT::Logger->crit("Failed to create user $create{EmailAddress}: $message"); return wantarray ? (0, $message) : 0 unless $self->id; } } =head2 ValidateEmailAddress ADDRESS Returns true if the email address entered is not in use by another user or is undef or ''. Returns false if it's in use. =cut sub ValidateEmailAddress { my $self = shift; my $Value = shift; # if the email address is null, it's always valid return (1) if ( !$Value || $Value eq "" ); if ( RT->Config->Get('ValidateUserEmailAddresses') ) { # We only allow one valid email address my @addresses = Email::Address->parse($Value); return ( 0, $self->loc('Invalid syntax for email address') ) unless ( ( scalar (@addresses) == 1 ) && ( $addresses[0]->address ) ); } my $TempUser = RT::User->new(RT->SystemUser); $TempUser->LoadByEmail($Value); if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) ) { # if we found a user with that address # it's invalid to set this user's address to it return ( 0, $self->loc('Email address in use') ); } else { #it's a valid email address return (1); } } =head2 SetName Check to make sure someone else isn't using this name already =cut sub SetName { my $self = shift; my $Value = shift; my ( $val, $message ) = $self->ValidateName($Value); if ($val) { return $self->_Set( Field => 'Name', Value => $Value ); } else { return ( 0, $message ); } } =head2 SetEmailAddress Check to make sure someone else isn't using this email address already so that a better email address can be returned =cut sub SetEmailAddress { my $self = shift; my $Value = shift; $Value = '' unless defined $Value; my ($val, $message) = $self->ValidateEmailAddress( $Value ); if ( $val ) { return $self->_Set( Field => 'EmailAddress', Value => $Value ); } else { return ( 0, $message ) } } =head2 EmailFrequency Takes optional Ticket argument in paramhash. Returns a string, suitable for localization, describing any notable properties about email delivery to the user. This includes lack of email address, ticket-level squelching (if C is provided in the paramhash), or user email delivery preferences. Returns the empty string if there are no notable properties. =cut sub EmailFrequency { my $self = shift; my %args = ( Ticket => undef, @_ ); return '' unless $self->id && $self->id != RT->Nobody->id && $self->id != RT->SystemUser->id; return 'no email address set' # loc unless my $email = $self->EmailAddress; return 'email disabled for ticket' # loc if $args{'Ticket'} && grep lc $email eq lc $_->Content, $args{'Ticket'}->SquelchMailTo; my $frequency = RT->Config->Get( 'EmailFrequency', $self ) || ''; return 'receives daily digests' # loc if $frequency =~ /daily/i; return 'receives weekly digests' # loc if $frequency =~ /weekly/i; return 'email delivery suspended' # loc if $frequency =~ /suspend/i; return ''; } =head2 CanonicalizeEmailAddress ADDRESS CanonicalizeEmailAddress converts email addresses into canonical form. it takes one email address in and returns the proper canonical form. You can dump whatever your proper local config is in here. Note that it may be called as a static method; in this case the first argument is class name not an object. =cut sub CanonicalizeEmailAddress { my $self = shift; my $email = shift; # Example: the following rule would treat all email # coming from a subdomain as coming from second level domain # foo.com if ( my $match = RT->Config->Get('CanonicalizeEmailAddressMatch') and my $replace = RT->Config->Get('CanonicalizeEmailAddressReplace') ) { $email =~ s/$match/$replace/gi; } return ($email); } =head2 CanonicalizeUserInfo HASH of ARGS CanonicalizeUserInfo can convert all User->Create options. it takes a hashref of all the params sent to User->Create and returns that same hash, by default nothing is done. If external auth is enabled CanonicalizeUserInfoFromExternalAuth is called. This function is intended to allow users to have their info looked up via an outside source and modified upon creation. =cut sub CanonicalizeUserInfo { my $self = shift; my $args = shift; if ( my $config = RT->Config->Get('ExternalInfoPriority') ) { if ( ref $config && @$config ) { return $self->CanonicalizeUserInfoFromExternalAuth( $args ); } } return 1; # fall back to old RT::User::CanonicalizeUserInfo } =head2 CanonicalizeUserInfoFromExternalAuth Convert an ldap entry in to fields that can be used by RT as specified by the C configuration in the C<$ExternalSettings> variable for L. =cut sub CanonicalizeUserInfoFromExternalAuth { # Careful, this $args hashref was given to RT::User::CanonicalizeUserInfo and # then transparently passed on to this function. The whole purpose is to update # the original hash as whatever passed it to RT::User is expecting to continue its # code with an update args hash. my $UserObj = shift; my $args = shift; my $found = 0; my %params = (Name => undef, EmailAddress => undef, RealName => undef); $RT::Logger->debug( (caller(0))[3], "called by", caller, "with:", join(", ", map {sprintf("%s: %s", $_, ($args->{$_} ? $args->{$_} : ''))} sort(keys(%$args)))); # Get the list of defined external services my @info_services = @{ RT->Config->Get('ExternalInfoPriority') }; # For each external service... foreach my $service (@info_services) { $RT::Logger->debug( "Attempting to get user info using this external service:", $service); # Get the config for the service so that we know what attrs we can canonicalize my $config = RT->Config->Get('ExternalSettings')->{$service}; # For each attr we've been told to canonicalize in the match list foreach my $rt_attr (@{$config->{'attr_match_list'}}) { # Jump to the next attr in $args if this one isn't in the attr_match_list $RT::Logger->debug( "Attempting to use this canonicalization key:",$rt_attr); unless( ($args->{$rt_attr} // '') =~ /\S/ ) { $RT::Logger->debug("No value provided for RT user attribute $rt_attr"); next; } # Else, use it as a canonicalization key and lookup the user info my $key = $config->{'attr_map'}->{$rt_attr}; my $value = $args->{$rt_attr}; # Check to see that the key being asked for is defined in the config's attr_map my $valid = 0; my ($attr_key, $attr_value); my $attr_map = $config->{'attr_map'}; while (($attr_key, $attr_value) = each %$attr_map) { $valid = 1 if ($key eq $attr_value); } unless ($valid){ $RT::Logger->debug( "This key (", $key, "is not a valid attribute key (", $service, ")"); next; } # Use an if/elsif structure to do a lookup with any custom code needed # for any given type of external service, or die if no code exists for # the service requested. if($config->{'type'} eq 'ldap'){ ($found, %params) = RT::Authen::ExternalAuth::LDAP::CanonicalizeUserInfo($service,$key,$value); } elsif ($config->{'type'} eq 'db') { ($found, %params) = RT::Authen::ExternalAuth::DBI::CanonicalizeUserInfo($service,$key,$value); } # Don't Check any more attributes last if $found; } # Don't Check any more services last if $found; } # If found, Canonicalize Email Address and # update the args hash that we were given the hashref for if ($found) { # It's important that we always have a canonical email address if ($params{'EmailAddress'}) { $params{'EmailAddress'} = $UserObj->CanonicalizeEmailAddress($params{'EmailAddress'}); } %$args = (%$args, %params); } else { $RT::Logger->debug("No record found in configured external sources"); } $RT::Logger->info( (caller(0))[3], "returning", join(", ", map {sprintf("%s: %s", $_, ($args->{$_} ? $args->{$_} : ''))} sort(keys(%$args)))); ### HACK: The config var below is to overcome the (IMO) bug in ### RT::User::Create() which expects this function to always ### return true or rejects the user for creation. This should be ### a different config var (CreateUncanonicalizedUsers) and ### should be honored in RT::User::Create() return($found || RT->Config->Get('AutoCreateNonExternalUsers')); } =head2 Password and authentication related functions =head3 SetRandomPassword Takes no arguments. Returns a status code and a new password or an error message. If the status is 1, the second value returned is the new password. If the status is anything else, the new value returned is the error code. =cut sub SetRandomPassword { my $self = shift; unless ( $self->CurrentUserCanModify('Password') ) { return ( 0, $self->loc("Permission Denied") ); } my $min = ( RT->Config->Get('MinimumPasswordLength') > 6 ? RT->Config->Get('MinimumPasswordLength') : 6); my $max = ( RT->Config->Get('MinimumPasswordLength') > 8 ? RT->Config->Get('MinimumPasswordLength') : 8); my $pass = $self->GenerateRandomPassword( $min, $max) ; # If we have "notify user on my ( $val, $msg ) = $self->SetPassword($pass); #If we got an error return the error. return ( 0, $msg ) unless ($val); #Otherwise, we changed the password, lets return it. return ( 1, $pass ); } =head3 ResetPassword Returns status, [ERROR or new password]. Resets this user's password to a randomly generated pronouncable password and emails them, using a global template called "PasswordChange". This function is currently unused in the UI, but available for local scripts. =cut sub ResetPassword { my $self = shift; unless ( $self->CurrentUserCanModify('Password') ) { return ( 0, $self->loc("Permission Denied") ); } my ( $status, $pass ) = $self->SetRandomPassword(); unless ($status) { return ( 0, "$pass" ); } my $ret = RT::Interface::Email::SendEmailUsingTemplate( To => $self->EmailAddress, Template => 'PasswordChange', Arguments => { NewPassword => $pass, }, ); if ($ret) { return ( 1, $self->loc('New password notification sent') ); } else { return ( 0, $self->loc('Notification could not be sent') ); } } =head3 GenerateRandomPassword MIN_LEN and MAX_LEN Returns a random password between MIN_LEN and MAX_LEN characters long. =cut sub GenerateRandomPassword { my $self = shift; # just to drop it return Text::Password::Pronounceable->generate(@_); } sub SafeSetPassword { my $self = shift; my %args = ( Current => undef, New => undef, Confirmation => undef, @_, ); return (1) unless defined $args{'New'} && length $args{'New'}; my %cond = $self->CurrentUserRequireToSetPassword; unless ( $cond{'CanSet'} ) { return (0, $self->loc('You can not set password.') .' '. $cond{'Reason'} ); } my $error = ''; if ( $cond{'RequireCurrent'} && !$self->CurrentUser->IsPassword($args{'Current'}) ) { if ( defined $args{'Current'} && length $args{'Current'} ) { $error = $self->loc("Please enter your current password correctly."); } else { $error = $self->loc("Please enter your current password."); } } elsif ( $args{'New'} ne $args{'Confirmation'} ) { $error = $self->loc("Passwords do not match."); } if ( $error ) { $error .= ' '. $self->loc('Password has not been set.'); return (0, $error); } return $self->SetPassword( $args{'New'} ); } =head3 SetPassword Takes a string. Checks the string's length and sets this user's password to that string. =cut sub SetPassword { my $self = shift; my $password = shift; unless ( $self->CurrentUserCanModify('Password') ) { return ( 0, $self->loc('Password: Permission Denied') ); } if ( !$password ) { return ( 0, $self->loc("No password set") ); } else { my ($val, $msg) = $self->ValidatePassword($password); return ($val, $msg) if !$val; my $new = !$self->HasPassword; $password = $self->_GeneratePassword($password); ( $val, $msg ) = $self->_Set(Field => 'Password', Value => $password); if ($val) { return ( 1, $self->loc("Password set") ) if $new; return ( 1, $self->loc("Password changed") ); } else { return ( $val, $msg ); } } } sub _GeneratePassword_bcrypt { my $self = shift; my ($password, @rest) = @_; my $salt; my $rounds; if (@rest) { # The first split is the number of rounds $rounds = $rest[0]; # The salt is the first 22 characters, b64 encoded usign the # special bcrypt base64. $salt = Crypt::Eksblowfish::Bcrypt::de_base64( substr($rest[1], 0, 22) ); } else { $rounds = RT->Config->Get('BcryptCost'); # Generate a random 16-octet base64 salt $salt = ""; $salt .= pack("C", int rand(256)) for 1..16; } my $hash = Crypt::Eksblowfish::Bcrypt::bcrypt_hash({ key_nul => 1, cost => $rounds, salt => $salt, }, Digest::SHA::sha512( Encode::encode( 'UTF-8', $password) ) ); return join("!", "", "bcrypt", sprintf("%02d", $rounds), Crypt::Eksblowfish::Bcrypt::en_base64( $salt ). Crypt::Eksblowfish::Bcrypt::en_base64( $hash ) ); } sub _GeneratePassword_sha512 { my $self = shift; my ($password, $salt) = @_; # Generate a 16-character base64 salt unless ($salt) { $salt = ""; $salt .= ("a".."z", "A".."Z","0".."9", "+", "/")[rand 64] for 1..16; } my $sha = Digest::SHA->new(512); $sha->add($salt); $sha->add(Encode::encode( 'UTF-8', $password)); return join("!", "", "sha512", $salt, $sha->b64digest); } =head3 _GeneratePassword PASSWORD [, SALT] Returns a string to store in the database. This string takes the form: !method!salt!hash By default, the method is currently C. =cut sub _GeneratePassword { my $self = shift; return $self->_GeneratePassword_bcrypt(@_); } =head3 HasPassword Returns true if the user has a valid password, otherwise returns false. =cut sub HasPassword { my $self = shift; my $pwd = $self->__Value('Password'); return undef if !defined $pwd || $pwd eq '' || $pwd eq '*NO-PASSWORD*'; return 1; } =head3 IsPassword Returns true if the passed in value is this user's password. Returns undef otherwise. =cut sub IsPassword { my $self = shift; my $value = shift; #TODO there isn't any apparent way to legitimately ACL this # RT does not allow null passwords if ( ( !defined($value) ) or ( $value eq '' ) ) { return (undef); } if ( $self->PrincipalObj->Disabled ) { $RT::Logger->info( "Disabled user " . $self->Name . " tried to log in" ); return (undef); } unless ($self->HasPassword) { return(undef); } my $stored = $self->__Value('Password'); if ($stored =~ /^!/) { # If it's a new-style (>= RT 4.0) password, it starts with a '!' my (undef, $method, @rest) = split /!/, $stored; if ($method eq "bcrypt") { return 0 unless RT::Util::constant_time_eq( $self->_GeneratePassword_bcrypt($value, @rest), $stored ); # Upgrade to a larger number of rounds if necessary return 1 unless $rest[0] < RT->Config->Get('BcryptCost'); } elsif ($method eq "sha512") { return 0 unless RT::Util::constant_time_eq( $self->_GeneratePassword_sha512($value, @rest), $stored ); } else { $RT::Logger->warn("Unknown hash method $method"); return 0; } } elsif (length $stored == 40) { # The truncated SHA256(salt,MD5(passwd)) form from 2010/12 is 40 characters long my $hash = MIME::Base64::decode_base64($stored); # Decoding yields 30 byes; first 4 are the salt, the rest are substr(SHA256,0,26) my $salt = substr($hash, 0, 4, ""); return 0 unless RT::Util::constant_time_eq( substr(Digest::SHA::sha256($salt . Digest::MD5::md5(Encode::encode( "UTF-8", $value))), 0, 26), $hash, 1 ); } elsif (length $stored == 32) { # Hex nonsalted-md5 return 0 unless RT::Util::constant_time_eq( Digest::MD5::md5_hex(Encode::encode( "UTF-8", $value)), $stored ); } elsif (length $stored == 22) { # Base64 nonsalted-md5 return 0 unless RT::Util::constant_time_eq( Digest::MD5::md5_base64(Encode::encode( "UTF-8", $value)), $stored ); } elsif (length $stored == 13) { # crypt() output return 0 unless RT::Util::constant_time_eq( crypt(Encode::encode( "UTF-8", $value), $stored), $stored ); } else { $RT::Logger->warning("Unknown password form"); return 0; } # We got here by validating successfully, but with a legacy # password form. Update to the most recent form. my $obj = $self->isa("RT::CurrentUser") ? $self->UserObj : $self; $obj->_Set(Field => 'Password', Value => $self->_GeneratePassword($value) ); return 1; } sub CurrentUserRequireToSetPassword { my $self = shift; my %res = ( CanSet => 1, Reason => '', RequireCurrent => 1, ); if ( RT->Config->Get('WebRemoteUserAuth') && !RT->Config->Get('WebFallbackToRTLogin') ) { $res{'CanSet'} = 0; $res{'Reason'} = $self->loc("External authentication enabled."); } elsif ( !$self->CurrentUser->HasPassword ) { if ( $self->CurrentUser->id == ($self->id||0) ) { # don't require current password if user has no $res{'RequireCurrent'} = 0; } else { $res{'CanSet'} = 0; $res{'Reason'} = $self->loc("Your password is not set."); } } return %res; } =head3 AuthToken Returns an authentication string associated with the user. This string can be used to generate passwordless URLs to integrate RT with services and programms like callendar managers, rss readers and other. =cut sub AuthToken { my $self = shift; my $secret = $self->_Value( AuthToken => @_ ); return $secret if $secret; $secret = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16); my $tmp = RT::User->new( RT->SystemUser ); $tmp->Load( $self->id ); my ($status, $msg) = $tmp->SetAuthToken( $secret ); unless ( $status ) { $RT::Logger->error( "Couldn't set auth token: $msg" ); return undef; } return $secret; } =head3 GenerateAuthToken Generate a random authentication string for the user. =cut sub GenerateAuthToken { my $self = shift; my $token = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16); return $self->SetAuthToken( $token ); } =head3 GenerateAuthString Takes a string and returns back a hex hash string. Later you can use this pair to make sure it's generated by this user using L =cut sub GenerateAuthString { my $self = shift; my $protect = shift; my $str = Encode::encode( "UTF-8", $self->AuthToken . $protect ); return substr(Digest::MD5::md5_hex($str),0,16); } =head3 ValidateAuthString Takes auth string and protected string. Returns true if protected string has been protected by user's L. See also L. =cut sub ValidateAuthString { my $self = shift; my $auth_string_to_validate = shift; my $protected = shift; my $str = Encode::encode( "UTF-8", $self->AuthToken . $protected ); my $valid_auth_string = substr(Digest::MD5::md5_hex($str),0,16); return RT::Util::constant_time_eq( $auth_string_to_validate, $valid_auth_string ); } =head2 SetDisabled Toggles the user's disabled flag. If this flag is set, all password checks for this user will fail. All ACL checks for this user will fail. The user will appear in no user listings. =cut sub SetDisabled { my $self = shift; my $val = shift; unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) { return (0, $self->loc('Permission Denied')); } $RT::Handle->BeginTransaction(); my ($status, $msg) = $self->PrincipalObj->SetDisabled($val); unless ($status) { $RT::Handle->Rollback(); $RT::Logger->warning(sprintf("Couldn't %s user %s", ($val == 1) ? "disable" : "enable", $self->PrincipalObj->Id)); return ($status, $msg); } $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" ); $RT::Handle->Commit(); if ( $val == 1 ) { return (1, $self->loc("User disabled")); } else { return (1, $self->loc("User enabled")); } } =head2 Disabled Returns true if user is disabled or false otherwise =cut sub Disabled { my $self = shift; return $self->PrincipalObj->Disabled(@_); } =head2 PrincipalObj Returns the principal object for this user. returns an empty RT::Principal if there's no principal object matching this user. The response is cached. PrincipalObj should never ever change. =cut sub PrincipalObj { my $self = shift; unless ( $self->id ) { $RT::Logger->error("Couldn't get principal for an empty user"); return undef; } if ( !$self->{_principal_obj} ) { my $obj = RT::Principal->new( $self->CurrentUser ); $obj->LoadById( $self->id ); if (! $obj->id ) { $RT::Logger->crit( 'No principal for user #' . $self->id ); return undef; } elsif ( $obj->PrincipalType ne 'User' ) { $RT::Logger->crit( 'User #' . $self->id . ' has principal of ' . $obj->PrincipalType . ' type' ); return undef; } $self->{_principal_obj} = $obj; } return $self->{_principal_obj}; } =head2 PrincipalId Returns this user's PrincipalId =cut sub PrincipalId { my $self = shift; return $self->Id; } =head2 HasGroupRight Takes a paramhash which can contain these items: GroupObj => RT::Group or Group => integer Right => 'Right' Returns 1 if this user has the right specified in the paramhash for the Group passed in. Returns undef if they don't. =cut sub HasGroupRight { my $self = shift; my %args = ( GroupObj => undef, Group => undef, Right => undef, @_ ); if ( defined $args{'Group'} ) { $args{'GroupObj'} = RT::Group->new( $self->CurrentUser ); $args{'GroupObj'}->Load( $args{'Group'} ); } # Validate and load up the GroupId unless ( ( defined $args{'GroupObj'} ) and ( $args{'GroupObj'}->Id ) ) { return undef; } # Figure out whether a user has the right we're asking about. my $retval = $self->HasRight( Object => $args{'GroupObj'}, Right => $args{'Right'}, ); return ($retval); } =head2 OwnGroups Returns a group collection object containing the groups of which this user is a member. =cut sub OwnGroups { my $self = shift; my $groups = RT::Groups->new($self->CurrentUser); $groups->LimitToUserDefinedGroups; $groups->WithMember( PrincipalId => $self->Id, Recursively => 1 ); return $groups; } =head2 HasRight Shim around PrincipalObj->HasRight. See L. =cut sub HasRight { my $self = shift; return $self->PrincipalObj->HasRight(@_); } =head2 CurrentUserCanSee [FIELD] Returns true if the current user can see the user, based on if it is public, ourself, or we have AdminUsers =cut sub CurrentUserCanSee { my $self = shift; my ($what, $txn) = @_; # If it's a public property, fine return 1 if $self->_Accessible( $what, 'public' ); # Users can see all of their own properties return 1 if defined($self->Id) and $self->CurrentUser->Id == $self->Id; # If the user has the admin users right, that's also enough return 1 if $self->CurrentUserHasRight( 'AdminUsers' ); # Transactions of public properties are visible to users with ShowUserHistory if ($what eq "Transaction" and $self->CurrentUserHasRight( 'ShowUserHistory' )) { my $type = $txn->__Value('Type'); my $field = $txn->__Value('Field'); return 1 if $type eq "Set" and $self->CurrentUserCanSee($field, $txn); # RT::Transaction->CurrentUserCanSee deals with ensuring we meet # the ACLs on CFs, so allow them here return 1 if $type eq "CustomField"; } return 0; } =head2 CurrentUserCanModify RIGHT If the user has rights for this object, either because he has 'AdminUsers' or (if he's trying to edit himself and the right isn't an admin right) 'ModifySelf', return 1. otherwise, return undef. =cut sub CurrentUserCanModify { my $self = shift; my $field = shift; if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) { return (1); } #If the field is marked as an "administrators only" field, # don't let the user touch it. elsif ( $self->_Accessible( $field, 'admin' ) ) { return (undef); } #If the current user is trying to modify themselves elsif ( ( $self->id == $self->CurrentUser->id ) and ( $self->CurrentUser->HasRight(Right => 'ModifySelf', Object => $RT::System) ) ) { return (1); } #If we don't have a good reason to grant them rights to modify # by now, they lose else { return (undef); } } =head2 CurrentUserHasRight Takes a single argument. returns 1 if $Self->CurrentUser has the requested right. returns undef otherwise =cut sub CurrentUserHasRight { my $self = shift; my $right = shift; return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) ); } sub _PrefName { my $name = shift; if (ref $name) { $name = ref($name).'-'.$name->Id; } return 'Pref-'. $name; } =head2 Preferences NAME/OBJ DEFAULT Obtain user preferences associated with given object or name. Returns DEFAULT if no preferences found. If DEFAULT is a hashref, override the entries with user preferences. =cut sub Preferences { my $self = shift; my $name = _PrefName(shift); my $default = shift; my ($attr) = $self->Attributes->Named( $name ); my $content = $attr ? $attr->Content : undef; unless ( ref $content eq 'HASH' ) { return defined $content ? $content : $default; } if (ref $default eq 'HASH') { for (keys %$default) { exists $content->{$_} or $content->{$_} = $default->{$_}; } } elsif (defined $default) { $RT::Logger->error("Preferences $name for user #".$self->Id." is hash but default is not"); } return $content; } =head2 SetPreferences NAME/OBJ VALUE Set user preferences associated with given object or name. =cut sub SetPreferences { my $self = shift; my $name = _PrefName( shift ); my $value = shift; return (0, $self->loc("No permission to set preferences")) unless $self->CurrentUserCanModify('Preferences'); my ($attr) = $self->Attributes->Named( $name ); if ( $attr ) { my ($ok, $msg) = $attr->SetContent( $value ); return (1, "No updates made") if $msg eq "That is already the current value"; return ($ok, $msg); } else { return $self->AddAttribute( Name => $name, Content => $value ); } } =head2 DeletePreferences NAME/OBJ VALUE Delete user preferences associated with given object or name. =cut sub DeletePreferences { my $self = shift; my $name = _PrefName( shift ); return (0, $self->loc("No permission to set preferences")) unless $self->CurrentUserCanModify('Preferences'); my ($attr) = $self->DeleteAttribute( $name ); return (0, $self->loc("Preferences were not found")) unless $attr; return 1; } =head2 Stylesheet Returns a list of valid stylesheets take from preferences. =cut sub Stylesheet { my $self = shift; my $style = RT->Config->Get('WebDefaultStylesheet', $self->CurrentUser); if (RT::Interface::Web->ComponentPathIsSafe($style)) { for my $root (RT::Interface::Web->StaticRoots) { if (-d "$root/css/$style") { return $style } } } # Fall back to the system stylesheet. return RT->Config->Get('WebDefaultStylesheet'); } =head2 WatchedQueues ROLE_LIST Returns a RT::Queues object containing every queue watched by the user. Takes a list of roles which is some subset of ('Cc', 'AdminCc'). Defaults to: $user->WatchedQueues('Cc', 'AdminCc'); =cut sub WatchedQueues { my $self = shift; my @roles = @_ ? @_ : ('Cc', 'AdminCc'); $RT::Logger->debug('WatcheQueues got user ' . $self->Name); my $watched_queues = RT::Queues->new($self->CurrentUser); my $group_alias = $watched_queues->Join( ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'Groups', FIELD2 => 'Instance', ); $watched_queues->Limit( ALIAS => $group_alias, FIELD => 'Domain', VALUE => 'RT::Queue-Role', ENTRYAGGREGATOR => 'AND', CASESENSITIVE => 0, ); if (grep { $_ eq 'Cc' } @roles) { $watched_queues->Limit( SUBCLAUSE => 'LimitToWatchers', ALIAS => $group_alias, FIELD => 'Name', VALUE => 'Cc', ENTRYAGGREGATOR => 'OR', ); } if (grep { $_ eq 'AdminCc' } @roles) { $watched_queues->Limit( SUBCLAUSE => 'LimitToWatchers', ALIAS => $group_alias, FIELD => 'Name', VALUE => 'AdminCc', ENTRYAGGREGATOR => 'OR', ); } my $queues_alias = $watched_queues->Join( ALIAS1 => $group_alias, FIELD1 => 'id', TABLE2 => 'CachedGroupMembers', FIELD2 => 'GroupId', ); $watched_queues->Limit( ALIAS => $queues_alias, FIELD => 'MemberId', VALUE => $self->PrincipalId, ); $watched_queues->Limit( ALIAS => $queues_alias, FIELD => 'Disabled', VALUE => 0, ); $RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues"); return $watched_queues; } sub _Set { my $self = shift; my %args = ( Field => undef, Value => undef, TransactionType => 'Set', RecordTransaction => 1, @_ ); # Nobody is allowed to futz with RT_System or Nobody if ( ($self->Id == RT->SystemUser->Id ) || ($self->Id == RT->Nobody->Id)) { return ( 0, $self->loc("Can not modify system users") ); } unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) { return ( 0, $self->loc("Permission Denied") ); } my $Old = $self->SUPER::_Value("$args{'Field'}"); my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'}, Value => $args{'Value'} ); #If we can't actually set the field to the value, don't record # a transaction. instead, get out of here. if ( $ret == 0 ) { return ( 0, $msg ); } if ( $args{'RecordTransaction'} == 1 ) { if ($args{'Field'} eq "Password") { $args{'Value'} = $Old = '********'; } my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction( Type => $args{'TransactionType'}, Field => $args{'Field'}, NewValue => $args{'Value'}, OldValue => $Old, TimeTaken => $args{'TimeTaken'}, ); return ( $Trans, scalar $TransObj->BriefDescription ); } else { return ( $ret, $msg ); } } =head2 _Value Takes the name of a table column. Returns its value as a string, if the user passes an ACL check =cut sub _Value { my $self = shift; my $field = shift; # Defer to the abstraction above to know if the field can be read return $self->SUPER::_Value($field) if $self->CurrentUserCanSee($field); return undef; } =head2 FriendlyName Return the friendly name =cut sub FriendlyName { my $self = shift; return $self->RealName if defined $self->RealName and length $self->RealName; return $self->Name; } =head2 Format Class or object method. Returns a string describing a user in the current user's preferred format. May be invoked in three ways: $UserObj->Format; RT::User->Format( User => $UserObj ); # same as above RT::User->Format( Address => $AddressObj, CurrentUser => $CurrentUserObj ); Possible arguments are: =over =item User An L object representing the user to format. Preferred to Address. =item Address An L object representing the user address to format. Address will be used to lookup an L if possible. =item CurrentUser Required when Format is called as a class method with an Address argument. Otherwise, this argument is ignored in preference to the CurrentUser of the involved L object. =item Format Specifies the format to use, overriding any set from the config or current user's preferences. =back =cut sub Format { my $self = shift; my %args = ( User => undef, Address => undef, CurrentUser => undef, Format => undef, @_ ); if (blessed($self) and $self->id) { @args{"User", "CurrentUser"} = ($self, $self->CurrentUser); } elsif ($args{User} and $args{User}->id) { $args{CurrentUser} = $args{User}->CurrentUser; } elsif ($args{Address} and $args{CurrentUser}) { $args{User} = RT::User->new( $args{CurrentUser} ); $args{User}->LoadByEmail( $args{Address}->address ); if ($args{User}->id) { delete $args{Address}; } else { delete $args{User}; } } else { RT->Logger->warning("Invalid arguments to RT::User->Format at @{[join '/', caller]}"); return ""; } $args{Format} ||= RT->Config->Get("UsernameFormat", $args{CurrentUser}); $args{Format} =~ s/[^A-Za-z0-9_]+//g; my $method = "_FormatUser" . ucfirst lc $args{Format}; my $formatter = $self->can($method); unless ($formatter) { RT->Logger->error( "Either system config or user #" . $args{CurrentUser}->id . " picked UsernameFormat $args{Format}, but RT::User->$method doesn't exist" ); $formatter = $self->can("_FormatUserRole"); } return $formatter->( $self, map { $_ => $args{$_} } qw(User Address) ); } sub _FormatUserRole { my $self = shift; my %args = @_; my $user = $args{User}; return $self->_FormatUserVerbose(@_) unless $user and $user->Privileged; my $name = $user->Name; $name .= " (".$user->RealName.")" if $user->RealName and lc $user->RealName ne lc $user->Name; return $name; } sub _FormatUserConcise { my $self = shift; my %args = @_; return $args{User} ? $args{User}->FriendlyName : $args{Address}->address; } sub _FormatUserVerbose { my $self = shift; my %args = @_; my ($user, $address) = @args{"User", "Address"}; my $email = ''; my $phrase = ''; my $comment = ''; if ($user) { $email = $user->EmailAddress || ''; $phrase = $user->RealName if $user->RealName and lc $user->RealName ne lc $email; $comment = $user->Name if lc $user->Name ne lc $email; } else { ($email, $phrase, $comment) = (map { $address->$_ } "address", "phrase", "comment"); } return join " ", grep { $_ } ($phrase || $comment || ''), ($email ? "<$email>" : ""); } =head2 PreferredKey Returns the preferred key of the user. If none is set, then this will query GPG and set the preferred key to the maximally trusted key found (and then return it). Returns C if no preferred key can be found. =cut sub PreferredKey { my $self = shift; return undef unless RT->Config->Get('GnuPG')->{'Enable'}; if ( ($self->CurrentUser->Id != $self->Id ) && !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) { return undef; } my $prefkey = $self->FirstAttribute('PreferredKey'); return $prefkey->Content if $prefkey; # we don't have a preferred key for this user, so now we must query GPG my %res = RT::Crypt->GetKeysForEncryption($self->EmailAddress); return undef unless defined $res{'info'}; my @keys = @{ $res{'info'} }; return undef if @keys == 0; if (@keys == 1) { $prefkey = $keys[0]->{'Fingerprint'}; } else { # prefer the maximally trusted key @keys = sort { $b->{'TrustLevel'} <=> $a->{'TrustLevel'} } @keys; $prefkey = $keys[0]->{'Fingerprint'}; } $self->SetAttribute(Name => 'PreferredKey', Content => $prefkey); return $prefkey; } sub PrivateKey { my $self = shift; #If the user wants to see their own values, let them. #If the user is an admin, let them. #Otherwwise, don't let them. # if ( ($self->CurrentUser->Id != $self->Id ) && !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) { return undef; } my $key = $self->FirstAttribute('PrivateKey') or return undef; return $key->Content; } sub SetPrivateKey { my $self = shift; my $key = shift; # Users should not be able to change their own PrivateKey values unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) { return (0, $self->loc("Permission Denied")); } unless ( $key ) { my ($status, $msg) = $self->DeleteAttribute('PrivateKey'); unless ( $status ) { $RT::Logger->error( "Couldn't delete attribute: $msg" ); return ($status, $self->loc("Couldn't unset private key")); } return ($status, $self->loc("Unset private key")); } # check that it's really private key { my %tmp = RT::Crypt->GetKeysForSigning( Signer => $key, Protocol => 'GnuPG' ); return (0, $self->loc("No such key or it's not suitable for signing")) if $tmp{'exit_code'} || !$tmp{'info'}; } my ($status, $msg) = $self->SetAttribute( Name => 'PrivateKey', Content => $key, ); return ($status, $self->loc("Couldn't set private key")) unless $status; return ($status, $self->loc("Set private key")); } sub SetLang { my $self = shift; my ($lang) = @_; unless ($self->CurrentUserCanModify('Lang')) { return (0, $self->loc("Permission Denied")); } # Local hack to cause the result message to be in the _new_ language # if we're updating ourselves $self->CurrentUser->{LangHandle} = RT::I18N->get_handle( $lang ) if $self->CurrentUser->id == $self->id; return $self->_Set( Field => 'Lang', Value => $lang ); } sub BasicColumns { ( [ Name => 'Username' ], [ EmailAddress => 'Email' ], [ RealName => 'Name' ], [ Organization => 'Organization' ], ); } =head2 Bookmarks Returns an unordered list of IDs representing the user's bookmarked tickets. =cut sub Bookmarks { my $self = shift; my $bookmarks = $self->FirstAttribute('Bookmarks'); return if !$bookmarks; $bookmarks = $bookmarks->Content; return if !$bookmarks; return keys %$bookmarks; } =head2 HasBookmark TICKET Returns whether the provided ticket is bookmarked by the user. =cut sub HasBookmark { my $self = shift; my $ticket = shift; my $id = $ticket->id; # maintain bookmarks across merges my @ids = ($id, $ticket->Merged); my $bookmarks = $self->FirstAttribute('Bookmarks'); $bookmarks = $bookmarks ? $bookmarks->Content : {}; my @bookmarked = grep { $bookmarks->{ $_ } } @ids; return @bookmarked ? 1 : 0; } =head2 ToggleBookmark TICKET Toggles whether the provided ticket is bookmarked by the user. =cut sub ToggleBookmark { my $self = shift; my $ticket = shift; my $id = $ticket->id; # maintain bookmarks across merges my @ids = ($id, $ticket->Merged); my $bookmarks = $self->FirstAttribute('Bookmarks'); $bookmarks = $bookmarks ? $bookmarks->Content : {}; my $is_bookmarked; if ( grep { $bookmarks->{ $_ } } @ids ) { delete $bookmarks->{ $_ } foreach @ids; $is_bookmarked = 0; } else { $bookmarks->{ $id } = 1; $is_bookmarked = 1; } $self->SetAttribute( Name => 'Bookmarks', Content => $bookmarks, ); return $is_bookmarked; } =head2 RecentlyViewedTickets TICKET Returns a list of hashrefs { id => , subject => } ) ordered by recently viewed first. If a ticket cannot be loaded (eg because it has been shredded) or is duplicated (eg because 2 tickets on the list have been merged), it is not returned and the user's RecentlyViewedTickets list is updated =cut sub RecentlyViewedTickets { my $self = shift; my $recentlyViewedTickets = $self->FirstAttribute( 'RecentlyViewedTickets' ); my $content = $recentlyViewedTickets ? $recentlyViewedTickets->Content : undef; my @storedList = $content ? @$content : (); # [ [ , ], ...] my @toDisplay = (); # [ { id => , title => }, ... ] if ( @storedList ) { my @currentList; # same as @storedList, without deleted tickets my $updateStoredList; # set to 1 if a ticket does not load my %seen; # used to check that we don't have duplicates (in case of merges) foreach my $ticketRef ( @storedList ) { my ($ticketId, $timestamp) = @$ticketRef; my $ticket = RT::Ticket->new( $self->CurrentUser ); $ticket->Load( $ticketId ); my $realId= $ticket->Id; # can be undef or changed in case of merge if ( $ticket->Id && ! $seen{$realId} ) { push @toDisplay, { id => $ticket->Id, subject => $ticket->Subject }; push @currentList, [ $ticketId, $timestamp ]; $seen{$realId}++; } else { # non existent ticket, do not add to @currentList, list needs to be updated $updateStoredList = 1; } } if ( $updateStoredList ) { $self->SetAttribute( Name => 'RecentlyViewedTickets', Content => \@currentList, ); } } return @toDisplay; } =head2 AddRecentlyViewedTicket TICKET Takes an RT::Ticket object and adds it to the current user's RecentlyViewedTickets =cut sub AddRecentlyViewedTicket { my $self = shift; my $ticket = shift; my $maxCount = 10; #The max number of tickets to keep #Nothing to do without a ticket return unless $ticket->Id; my @recentTickets; my $content = $self->FirstAttribute('RecentlyViewedTickets'); $content = $content ? $content->Content : []; if (defined $content) { @recentTickets = @$content; } my @tickets; for (@recentTickets) { my ($ticketId, $timestamp) = @$_; #Skip the ticket if it exists in recents already if ($ticketId != $ticket->Id) { push @tickets, $_; # -1 is for the new ticket that will be added last if @tickets >= $maxCount - 1; } } #Add the new ticket unshift @tickets, [$ticket->Id, time()]; $self->SetAttribute( Name => 'RecentlyViewedTickets', Content => \@tickets, ); } =head2 Create PARAMHASH Create takes a hash of values and creates a row in the database: varchar(200) 'Name'. varbinary(256) 'Password'. varchar(16) 'AuthToken'. text 'Comments'. text 'Signature'. varchar(120) 'EmailAddress'. text 'FreeformContactInfo'. varchar(200) 'Organization'. varchar(120) 'RealName'. varchar(16) 'NickName'. varchar(16) 'Lang'. varchar(16) 'Gecos'. varchar(30) 'HomePhone'. varchar(30) 'WorkPhone'. varchar(30) 'MobilePhone'. varchar(30) 'PagerPhone'. varchar(200) 'Address1'. varchar(200) 'Address2'. varchar(100) 'City'. varchar(100) 'State'. varchar(16) 'Zip'. varchar(50) 'Country'. varchar(50) 'Timezone'. =cut =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 Name Returns the current value of Name. (In the database, Name is stored as varchar(200).) =head2 SetName VALUE Set Name to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Name will be stored as a varchar(200).) =cut =head2 Password Returns the current value of Password. (In the database, Password is stored as varchar(256).) =head2 SetPassword VALUE Set Password to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Password will be stored as a varchar(256).) =cut =head2 AuthToken Returns the current value of AuthToken. (In the database, AuthToken is stored as varchar(16).) =head2 SetAuthToken VALUE Set AuthToken to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, AuthToken will be stored as a varchar(16).) =cut =head2 Comments Returns the current value of Comments. (In the database, Comments is stored as text.) =head2 SetComments VALUE Set Comments to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Comments will be stored as a text.) =cut =head2 Signature Returns the current value of Signature. (In the database, Signature is stored as text.) =head2 SetSignature VALUE Set Signature to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Signature will be stored as a text.) =cut =head2 EmailAddress Returns the current value of EmailAddress. (In the database, EmailAddress is stored as varchar(120).) =head2 SetEmailAddress VALUE Set EmailAddress to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, EmailAddress will be stored as a varchar(120).) =cut =head2 FreeformContactInfo Returns the current value of FreeformContactInfo. (In the database, FreeformContactInfo is stored as text.) =head2 SetFreeformContactInfo VALUE Set FreeformContactInfo to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, FreeformContactInfo will be stored as a text.) =cut =head2 Organization Returns the current value of Organization. (In the database, Organization is stored as varchar(200).) =head2 SetOrganization VALUE Set Organization to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Organization will be stored as a varchar(200).) =cut =head2 RealName Returns the current value of RealName. (In the database, RealName is stored as varchar(120).) =head2 SetRealName VALUE Set RealName to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, RealName will be stored as a varchar(120).) =cut =head2 NickName Returns the current value of NickName. (In the database, NickName is stored as varchar(16).) =head2 SetNickName VALUE Set NickName to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, NickName will be stored as a varchar(16).) =cut =head2 Lang Returns the current value of Lang. (In the database, Lang is stored as varchar(16).) =head2 SetLang VALUE Set Lang to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Lang will be stored as a varchar(16).) =cut =head2 Gecos Returns the current value of Gecos. (In the database, Gecos is stored as varchar(16).) =head2 SetGecos VALUE Set Gecos to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Gecos will be stored as a varchar(16).) =cut =head2 HomePhone Returns the current value of HomePhone. (In the database, HomePhone is stored as varchar(30).) =head2 SetHomePhone VALUE Set HomePhone to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, HomePhone will be stored as a varchar(30).) =cut =head2 WorkPhone Returns the current value of WorkPhone. (In the database, WorkPhone is stored as varchar(30).) =head2 SetWorkPhone VALUE Set WorkPhone to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, WorkPhone will be stored as a varchar(30).) =cut =head2 MobilePhone Returns the current value of MobilePhone. (In the database, MobilePhone is stored as varchar(30).) =head2 SetMobilePhone VALUE Set MobilePhone to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, MobilePhone will be stored as a varchar(30).) =cut =head2 PagerPhone Returns the current value of PagerPhone. (In the database, PagerPhone is stored as varchar(30).) =head2 SetPagerPhone VALUE Set PagerPhone to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, PagerPhone will be stored as a varchar(30).) =cut =head2 Address1 Returns the current value of Address1. (In the database, Address1 is stored as varchar(200).) =head2 SetAddress1 VALUE Set Address1 to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Address1 will be stored as a varchar(200).) =cut =head2 Address2 Returns the current value of Address2. (In the database, Address2 is stored as varchar(200).) =head2 SetAddress2 VALUE Set Address2 to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Address2 will be stored as a varchar(200).) =cut =head2 City Returns the current value of City. (In the database, City is stored as varchar(100).) =head2 SetCity VALUE Set City to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, City will be stored as a varchar(100).) =cut =head2 State Returns the current value of State. (In the database, State is stored as varchar(100).) =head2 SetState VALUE Set State to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, State will be stored as a varchar(100).) =cut =head2 Zip Returns the current value of Zip. (In the database, Zip is stored as varchar(16).) =head2 SetZip VALUE Set Zip to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Zip will be stored as a varchar(16).) =cut =head2 Country Returns the current value of Country. (In the database, Country is stored as varchar(50).) =head2 SetCountry VALUE Set Country to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Country will be stored as a varchar(50).) =cut =head2 Timezone Returns the current value of Timezone. (In the database, Timezone is stored as varchar(50).) =head2 SetTimezone VALUE Set Timezone to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Timezone will be stored as a varchar(50).) =cut =head2 SMIMECertificate Returns the current value of SMIMECertificate. (In the database, SMIMECertificate is stored as text.) =head2 SetSMIMECertificate VALUE Set SMIMECertificate to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, SMIMECertificate will be stored as a text.) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Name => {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, Password => {read => 1, write => 1, sql_type => 12, length => 256, is_blob => 0, is_numeric => 0, type => 'varchar(256)', default => ''}, AuthToken => {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, Comments => {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, Signature => {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, EmailAddress => {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''}, FreeformContactInfo => {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, Organization => {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, RealName => {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''}, NickName => {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, Lang => {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, Gecos => {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, HomePhone => {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''}, WorkPhone => {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''}, MobilePhone => {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''}, PagerPhone => {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''}, Address1 => {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, Address2 => {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, City => {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''}, State => {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''}, Zip => {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, Country => {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''}, Timezone => {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''}, SMIMECertificate => {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; sub UID { my $self = shift; return undef unless defined $self->Name; return "@{[ref $self]}-@{[$self->Name]}"; } sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); # ACL equivalence group my $objs = RT::Groups->new( $self->CurrentUser ); $objs->Limit( FIELD => 'Domain', VALUE => 'ACLEquivalence', CASESENSITIVE => 0 ); $objs->Limit( FIELD => 'Instance', VALUE => $self->Id ); $deps->Add( in => $objs ); # Memberships in SystemInternal groups $objs = RT::GroupMembers->new( $self->CurrentUser ); $objs->Limit( FIELD => 'MemberId', VALUE => $self->Id ); my $groups = $objs->Join( ALIAS1 => 'main', FIELD1 => 'GroupId', TABLE2 => 'Groups', FIELD2 => 'id', ); $objs->Limit( ALIAS => $groups, FIELD => 'Domain', VALUE => 'SystemInternal', CASESENSITIVE => 0 ); $deps->Add( in => $objs ); # XXX: This ignores the myriad of "in" references from the Creator # and LastUpdatedBy columns. } sub __DependsOn { my $self = shift; my %args = ( Shredder => undef, Dependencies => undef, @_, ); my $deps = $args{'Dependencies'}; my $list = []; # Principal $deps->_PushDependency( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON | RT::Shredder::Constants::WIPE_AFTER, TargetObject => $self->PrincipalObj, Shredder => $args{'Shredder'} ); # ACL equivalence group # don't use LoadACLEquivalenceGroup cause it may not exists any more my $objs = RT::Groups->new( $self->CurrentUser ); $objs->Limit( FIELD => 'Domain', VALUE => 'ACLEquivalence', CASESENSITIVE => 0 ); $objs->Limit( FIELD => 'Instance', VALUE => $self->Id ); push( @$list, $objs ); # Cleanup user's membership $objs = RT::GroupMembers->new( $self->CurrentUser ); $objs->Limit( FIELD => 'MemberId', VALUE => $self->Id ); push( @$list, $objs ); # Cleanup user's membership transactions $objs = RT::Transactions->new( $self->CurrentUser ); $objs->Limit( FIELD => 'Type', OPERATOR => 'IN', VALUE => ['AddMember', 'DeleteMember'] ); $objs->Limit( FIELD => 'Field', VALUE => $self->PrincipalObj->id, ENTRYAGGREGATOR => 'AND' ); push( @$list, $objs ); $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON, TargetObjects => $list, Shredder => $args{'Shredder'} ); # TODO: Almost all objects has Creator, LastUpdatedBy and etc. fields # which are references on users(Principal actualy) my @OBJECTS = qw( ACL Articles Attachments Attributes CachedGroupMembers Classes CustomFieldValues CustomFields GroupMembers Groups Links ObjectClasses ObjectCustomFieldValues ObjectCustomFields ObjectScrips Principals Queues ScripActions ScripConditions Scrips Templates Tickets Transactions Users ); my @var_objs; foreach( @OBJECTS ) { my $class = "RT::$_"; foreach my $method ( qw(Creator LastUpdatedBy) ) { my $objs = $class->new( $self->CurrentUser ); next unless $objs->RecordClass->_Accessible( $method => 'read' ); $objs->Limit( FIELD => $method, VALUE => $self->id ); push @var_objs, $objs; } } $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON | RT::Shredder::Constants::VARIABLE, TargetObjects => \@var_objs, Shredder => $args{'Shredder'} ); return $self->SUPER::__DependsOn( %args ); } sub BeforeWipeout { my $self = shift; if( $self->Name =~ /^(RT_System|Nobody)$/ ) { RT::Shredder::Exception::Info->throw('SystemObject'); } return $self->SUPER::BeforeWipeout( @_ ); } sub Serialize { my $self = shift; return ( Disabled => $self->PrincipalObj->Disabled, Principal => $self->PrincipalObj->UID, PrincipalId => $self->PrincipalObj->Id, $self->SUPER::Serialize(@_), ); } sub PreInflate { my $class = shift; my ($importer, $uid, $data) = @_; my $principal_uid = delete $data->{Principal}; my $principal_id = delete $data->{PrincipalId}; my $disabled = delete $data->{Disabled}; my $obj = RT::User->new( RT->SystemUser ); $obj->LoadByCols( Name => $data->{Name} ); $obj->LoadByEmail( $data->{EmailAddress} ) unless $obj->Id; if ($obj->Id) { # User already exists -- merge # XXX: We might be merging a privileged user into an unpriv one, # in which case we should probably promote the unpriv user to # being privileged. Of course, we don't know if the user being # imported is privileged yet, as its group memberships show up # later in the stream... $importer->MergeValues($obj, $data); $importer->SkipTransactions( $uid ); # Mark both the principal and the user object as resolved $importer->Resolve( $principal_uid, ref($obj->PrincipalObj), $obj->PrincipalObj->Id ); $importer->Resolve( $uid => ref($obj) => $obj->Id ); return; } # Create a principal first, so we know what ID to use my $principal = RT::Principal->new( RT->SystemUser ); my ($id) = $principal->Create( PrincipalType => 'User', Disabled => $disabled, ); # Now we have a principal id, set the id for the user record $data->{id} = $id; $importer->Resolve( $principal_uid => ref($principal), $id ); $data->{id} = $id; return $class->SUPER::PreInflate( $importer, $uid, $data ); } sub PostInflate { my $self = shift; RT->InitSystemObjects if $self->Name eq "RT_System"; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Principal.pm������������������������������������������������������������������������000644 �000765 �000024 �00000054173 14005011336 016365� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} # package RT::Principal; use strict; use warnings; use base 'RT::Record'; sub Table {'Principals'} use RT; use RT::Group; use RT::User; # Set up the ACL cache on startup our $_ACL_CACHE; InvalidateACLCache(); require RT::ACE; RT::ACE->RegisterCacheHandler(sub { RT::Principal->InvalidateACLCache() }); =head2 IsGroup Returns true if this principal is a group. Returns undef, otherwise =cut sub IsGroup { my $self = shift; if ( defined $self->PrincipalType && $self->PrincipalType eq 'Group' ) { return 1; } return undef; } =head2 IsRoleGroup Returns true if this principal is a role group. Returns undef, otherwise. =cut sub IsRoleGroup { my $self = shift; return ($self->IsGroup and $self->Object->RoleClass) ? 1 : undef; } =head2 IsUser Returns true if this principal is a User. Returns undef, otherwise =cut sub IsUser { my $self = shift; if ($self->PrincipalType eq 'User') { return(1); } else { return undef; } } =head2 Object Returns the user or group associated with this principal =cut sub Object { my $self = shift; unless ( $self->{'object'} ) { if ( $self->IsUser ) { $self->{'object'} = RT::User->new($self->CurrentUser); } elsif ( $self->IsGroup ) { $self->{'object'} = RT::Group->new($self->CurrentUser); } else { $RT::Logger->crit("Found a principal (".$self->Id.") that was neither a user nor a group"); return(undef); } $self->{'object'}->Load( $self->id ); } return ($self->{'object'}); } =head2 DisplayName Returns the relevant display name for this principal =cut sub DisplayName { my $self = shift; return undef unless $self->Object; # If this principal is an ACLEquivalence group, return the user name return $self->Object->InstanceObj->Name if ($self->Object->Domain eq 'ACLEquivalence'); # Otherwise, show the group name return $self->Object->Label; } =head2 GrantRight { Right => RIGHTNAME, Object => undef } A helper function which calls RT::ACE->Create Returns a tuple of (STATUS, MESSAGE); If the call succeeded, STATUS is true. Otherwise it's false. =cut sub GrantRight { my $self = shift; my %args = ( Right => undef, Object => undef, @_ ); return (0, "Permission Denied") if $args{'Right'} eq 'ExecuteCode' and RT->Config->Get('DisallowExecuteCode'); #ACL check handled in ACE.pm my $ace = RT::ACE->new( $self->CurrentUser ); my $type = $self->_GetPrincipalTypeForACL(); # If it's a user, we really want to grant the right to their # user equivalence group my ($id, $msg) = $ace->Create( RightName => $args{'Right'}, Object => $args{'Object'}, PrincipalType => $type, PrincipalId => $self->Id, ); return ($id, $msg); } =head2 RevokeRight { Right => "RightName", Object => "object" } Delete a right that a user has Returns a tuple of (STATUS, MESSAGE); If the call succeeded, STATUS is true. Otherwise it's false. =cut sub RevokeRight { my $self = shift; my %args = ( Right => undef, Object => undef, @_ ); #if we haven't specified any sort of right, we're talking about a global right if (!defined $args{'Object'} && !defined $args{'ObjectId'} && !defined $args{'ObjectType'}) { $args{'Object'} = $RT::System; } #ACL check handled in ACE.pm my $type = $self->_GetPrincipalTypeForACL(); my $ace = RT::ACE->new( $self->CurrentUser ); my ($status, $msg) = $ace->LoadByValues( RightName => $args{'Right'}, Object => $args{'Object'}, PrincipalType => $type, PrincipalId => $self->Id ); if ( not $status and $msg =~ /Invalid right/ ) { $RT::Logger->warn("Tried to revoke the invalid right '$args{Right}', ignoring it."); return (1); } return ($status, $msg) unless $status; my $right = $ace->RightName; ($status, $msg) = $ace->Delete; return ($status, $msg); } =head2 HasRight (Right => 'right' Object => undef) Checks to see whether this principal has the right "Right" for the Object specified. This takes the params: =over 4 =item Right name of a right =item Object an RT style object (->id will get its id) =back Returns 1 if a matching ACE was found. Returns undef if no ACE was found. Use L</HasRights> to fill a fast cache, especially if you're going to check many different rights with the same principal and object. =cut sub HasRight { my $self = shift; my %args = ( Right => undef, Object => undef, EquivObjects => undef, @_, ); # RT's SystemUser always has all rights if ( $self->id == RT->SystemUser->id ) { return 1; } if ( my $right = RT::ACE->CanonicalizeRightName( $args{'Right'} ) ) { $args{'Right'} = $right; } else { $RT::Logger->error( "Invalid right. Couldn't canonicalize right '$args{'Right'}'"); return undef; } return undef if $args{'Right'} eq 'ExecuteCode' and RT->Config->Get('DisallowExecuteCode'); $args{'EquivObjects'} = [ @{ $args{'EquivObjects'} } ] if $args{'EquivObjects'}; if ( $self->__Value('Disabled') ) { $RT::Logger->debug( "Disabled User #" . $self->id . " failed access check for " . $args{'Right'} ); return (undef); } if ( eval { $args{'Object'}->id } ) { push @{ $args{'EquivObjects'} }, $args{'Object'}; } else { $RT::Logger->crit("HasRight called with no valid object"); return (undef); } { my $cached = $_ACL_CACHE->{ $self->id .';:;'. ref($args{'Object'}) .'-'. $args{'Object'}->id }; return $cached->{'SuperUser'} || $cached->{ $args{'Right'} } if $cached; } unshift @{ $args{'EquivObjects'} }, $args{'Object'}->ACLEquivalenceObjects; unshift @{ $args{'EquivObjects'} }, $RT::System; # If we've cached a win or loss for this lookup say so # Construct a hashkeys to cache decisions: # 1) full_hashkey - key for any result and for full combination of uid, right and objects # 2) short_hashkey - one key for each object to store positive results only, it applies # only to direct group rights and partly to role rights my $full_hashkey = join (";:;", $self->id, $args{'Right'}); foreach ( @{ $args{'EquivObjects'} } ) { my $ref_id = $self->_ReferenceId($_); $full_hashkey .= ";:;".$ref_id; my $short_hashkey = join(";:;", $self->id, $args{'Right'}, $ref_id); my $cached_answer = $_ACL_CACHE->{ $short_hashkey }; return $cached_answer > 0 if defined $cached_answer; } { my $cached_answer = $_ACL_CACHE->{ $full_hashkey }; return $cached_answer > 0 if defined $cached_answer; } my ( $hitcount, $via_obj ) = $self->_HasRight(%args); $_ACL_CACHE->{ $full_hashkey } = $hitcount ? 1 : -1; $_ACL_CACHE->{ join ';:;', $self->id, $args{'Right'}, $via_obj } = 1 if $via_obj && $hitcount; return ($hitcount); } =head2 HasRights Returns a hash reference with all rights this principal has on an object. Takes Object as a named argument. Main use case of this method is the following: $ticket->CurrentUser->PrincipalObj->HasRights( Object => $ticket ); ... $ticket->CurrentUserHasRight('A'); ... $ticket->CurrentUserHasRight('Z'); Results are cached and the cache is used in this and, as well, in L</HasRight> method speeding it up. Don't use hash reference returned by this method directly for rights checks as it's more complicated then it seems, especially considering config options like 'DisallowExecuteCode'. =cut sub HasRights { my $self = shift; my %args = ( Object => undef, EquivObjects => undef, @_ ); return {} if $self->__Value('Disabled'); my $object = $args{'Object'}; unless ( eval { $object->id } ) { $RT::Logger->crit("HasRights called with no valid object"); } my $cache_key = $self->id .';:;'. ref($object) .'-'. $object->id; my $cached = $_ACL_CACHE->{ $cache_key }; return $cached if $cached; push @{ $args{'EquivObjects'} }, $object; unshift @{ $args{'EquivObjects'} }, $args{'Object'}->ACLEquivalenceObjects; unshift @{ $args{'EquivObjects'} }, $RT::System; my %res = (); { my $query = "SELECT DISTINCT ACL.RightName " . $self->_HasGroupRightQuery( EquivObjects => $args{'EquivObjects'} ); my $rights = $RT::Handle->dbh->selectcol_arrayref($query); unless ($rights) { $RT::Logger->warning( $RT::Handle->dbh->errstr ); return (); } $res{$_} = 1 foreach @$rights; } my $roles; { my $query = "SELECT DISTINCT Groups.Name " . $self->_HasRoleRightQuery( EquivObjects => $args{'EquivObjects'} ); $roles = $RT::Handle->dbh->selectcol_arrayref($query); unless ($roles) { $RT::Logger->warning( $RT::Handle->dbh->errstr ); return (); } } if ( @$roles ) { my $query = "SELECT DISTINCT ACL.RightName " . $self->_RolesWithRightQuery( EquivObjects => $args{'EquivObjects'} ) . ' AND ('. join( ' OR ', map "PrincipalType = '$_'", @$roles ) .')' ; my $rights = $RT::Handle->dbh->selectcol_arrayref($query); unless ($rights) { $RT::Logger->warning( $RT::Handle->dbh->errstr ); return (); } $res{$_} = 1 foreach @$rights; } delete $res{'ExecuteCode'} if RT->Config->Get('DisallowExecuteCode'); $_ACL_CACHE->{ $cache_key } = \%res; return \%res; } =head2 _HasRight Low level HasRight implementation, use HasRight method instead. =cut sub _HasRight { my $self = shift; { my ( $hit, @other ) = $self->_HasGroupRight(@_); return ( $hit, @other ) if $hit; } { my ( $hit, @other ) = $self->_HasRoleRight(@_); return ( $hit, @other ) if $hit; } return (0); } # this method handles role rights partly in situations # where user plays role X on an object and as well the right is # assigned to this role X of the object, for example right CommentOnTicket # is granted to Cc role of a queue and user is in cc list of the queue sub _HasGroupRight { my $self = shift; my %args = ( Right => undef, EquivObjects => [], @_ ); my $query = "SELECT ACL.id, ACL.ObjectType, ACL.ObjectId " . $self->_HasGroupRightQuery( %args ); $self->_Handle->ApplyLimits( \$query, 1 ); my ( $hit, $obj, $id ) = $self->_Handle->FetchResult($query); return (0) unless $hit; $obj .= "-$id" if $id; return ( 1, $obj ); } sub _HasGroupRightQuery { my $self = shift; my %args = ( Right => undef, EquivObjects => [], @_ ); my $query = "FROM ACL, Principals, CachedGroupMembers WHERE " # Never find disabled groups. . "Principals.id = ACL.PrincipalId " . "AND Principals.PrincipalType = 'Group' " . "AND Principals.Disabled = 0 " # See if the principal is a member of the group recursively or _is the rightholder_ # never find recursively disabled group members # also, check to see if the right is being granted _directly_ to this principal, # as is the case when we want to look up group rights . "AND CachedGroupMembers.GroupId = ACL.PrincipalId " . "AND CachedGroupMembers.GroupId = Principals.id " . "AND CachedGroupMembers.MemberId = ". $self->Id . " " . "AND CachedGroupMembers.Disabled = 0 "; my @clauses; foreach my $obj ( @{ $args{'EquivObjects'} } ) { my $type = ref($obj) || $obj; my $clause = "ACL.ObjectType = '$type'"; if ( defined eval { $obj->id } ) { # it might be 0 $clause .= " AND ACL.ObjectId = " . $obj->id; } push @clauses, "($clause)"; } if (@clauses) { $query .= " AND (" . join( ' OR ', @clauses ) . ")"; } if ( my $right = $args{'Right'} ) { # Only find superuser or rights with the name $right $query .= " AND (ACL.RightName = 'SuperUser' " . ( $right ne 'SuperUser' ? "OR ACL.RightName = '$right'" : '' ) . ") "; } return $query; } sub _HasRoleRight { my $self = shift; my %args = ( Right => undef, EquivObjects => [], @_ ); my @roles = $self->RolesWithRight(%args); return 0 unless @roles; my $query = "SELECT Groups.id " . $self->_HasRoleRightQuery( %args, Roles => \@roles ); $self->_Handle->ApplyLimits( \$query, 1 ); my ($hit) = $self->_Handle->FetchResult($query); return (1) if $hit; return 0; } sub _HasRoleRightQuery { my $self = shift; my %args = ( Right => undef, EquivObjects => [], Roles => undef, @_ ); my $query = " FROM Groups, Principals, CachedGroupMembers WHERE " # Never find disabled things . "Principals.Disabled = 0 " . "AND CachedGroupMembers.Disabled = 0 " # We always grant rights to Groups . "AND Principals.id = Groups.id " . "AND Principals.PrincipalType = 'Group' " # See if the principal is a member of the group recursively or _is the rightholder_ # never find recursively disabled group members # also, check to see if the right is being granted _directly_ to this principal, # as is the case when we want to look up group rights . "AND Principals.id = CachedGroupMembers.GroupId " . "AND CachedGroupMembers.MemberId = " . $self->Id . " " ; if ( $args{'Roles'} ) { $query .= "AND (" . join( ' OR ', map $RT::Handle->__MakeClauseCaseInsensitive('Groups.Name', '=', "'$_'"), @{ $args{'Roles'} } ) . ")"; } my @object_clauses = RT::Users->_RoleClauses( Groups => @{ $args{'EquivObjects'} } ); $query .= " AND (" . join( ' OR ', @object_clauses ) . ")"; return $query; } =head2 RolesWithRight Returns list with names of roles that have right on set of objects. Takes Right, EquiveObjects, IncludeSystemRights and IncludeSuperusers arguments. IncludeSystemRights is true by default, rights granted systemwide are ignored when IncludeSystemRights is set to a false value. IncludeSuperusers is true by default, SuperUser right is not checked if it's set to a false value. =cut sub RolesWithRight { my $self = shift; my %args = ( Right => undef, IncludeSystemRights => 1, IncludeSuperusers => 1, EquivObjects => [], @_ ); return () if $args{'Right'} eq 'ExecuteCode' and RT->Config->Get('DisallowExecuteCode'); my $query = "SELECT DISTINCT PrincipalType " . $self->_RolesWithRightQuery( %args ); my $roles = $RT::Handle->dbh->selectcol_arrayref($query); unless ($roles) { $RT::Logger->warning( $RT::Handle->dbh->errstr ); return (); } return @$roles; } sub _RolesWithRightQuery { my $self = shift; my %args = ( Right => undef, IncludeSystemRights => 1, IncludeSuperusers => 1, EquivObjects => [], @_ ); my $query = " FROM ACL WHERE" # we need only roles . " PrincipalType != 'Group'"; if ( my $right = $args{'Right'} ) { $query .= # Only find superuser or rights with the requested right " AND ( RightName = '$right' " # Check SuperUser if we were asked to . ( $args{'IncludeSuperusers'} ? "OR RightName = 'SuperUser' " : '' ) . ")"; } # skip rights granted on system level if we were asked to unless ( $args{'IncludeSystemRights'} ) { $query .= " AND ObjectType != 'RT::System'"; } my (@object_clauses); foreach my $obj ( @{ $args{'EquivObjects'} } ) { my $type = ref($obj) ? ref($obj) : $obj; my $object_clause = "ObjectType = '$type'"; if ( my $id = eval { $obj->id } ) { $object_clause .= " AND ObjectId = $id"; } push @object_clauses, "($object_clause)"; } # find ACLs that are related to our objects only $query .= " AND (" . join( ' OR ', @object_clauses ) . ")" if @object_clauses; return $query; } =head2 InvalidateACLCache Cleans out and reinitializes the user rights cache =cut sub InvalidateACLCache { $_ACL_CACHE = {} } =head2 _GetPrincipalTypeForACL Gets the principal type. if it's a user, it's a user. if it's a role group and it has a Type, return that. if it has no type, return group. =cut sub _GetPrincipalTypeForACL { my $self = shift; if ($self->IsRoleGroup) { return $self->Object->Name; } else { return $self->PrincipalType; } } =head2 _ReferenceId Returns a list uniquely representing an object or normal scalar. For a scalar, its string value is returned. For an object that has an id() method which returns a value, its class name and id are returned as a string separated by a "-". For an object that has an id() method which returns false, its class name is returned. =cut sub _ReferenceId { my $self = shift; my $scalar = shift; my $id = eval { $scalar->id }; if ($@) { return $scalar; } elsif ($id) { return ref($scalar) . "-" . $id; } else { return ref($scalar); } } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 PrincipalType Returns the current value of PrincipalType. (In the database, PrincipalType is stored as varchar(16).) =head2 SetPrincipalType VALUE Set PrincipalType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, PrincipalType will be stored as a varchar(16).) =cut =head2 Disabled Returns the current value of Disabled. (In the database, Disabled is stored as smallint(6).) =head2 SetDisabled VALUE Set Disabled to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Disabled will be stored as a smallint(6).) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, PrincipalType => {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, Disabled => {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'}, } }; sub __DependsOn { my $self = shift; my %args = ( Shredder => undef, Dependencies => undef, @_, ); my $deps = $args{'Dependencies'}; my $list = []; # Group or User # Could be wiped allready my $obj = $self->Object; if( defined $obj->id ) { push( @$list, $obj ); } # Access Control List my $objs = RT::ACL->new( $self->CurrentUser ); $objs->Limit( FIELD => 'PrincipalId', OPERATOR => '=', VALUE => $self->Id ); push( @$list, $objs ); # AddWatcher/DelWatcher txns foreach my $type ( qw(AddWatcher DelWatcher) ) { my $objs = RT::Transactions->new( $self->CurrentUser ); $objs->Limit( FIELD => $type =~ /Add/? 'NewValue': 'OldValue', VALUE => $self->Id ); $objs->Limit( FIELD => 'Type', VALUE => $type ); push( @$list, $objs ); } $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON, TargetObjects => $list, Shredder => $args{'Shredder'} ); return $self->SUPER::__DependsOn( %args ); } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Configuration.pm��������������������������������������������������������������������000644 �000765 �000024 �00000033761 14005011336 017253� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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; use 5.10.1; package RT::Configuration; use base 'RT::Record'; use JSON (); =head1 NAME RT::Configuration - Represents a config setting =cut =head1 METHODS =head2 Create PARAMHASH Create takes a hash of values and creates a row in the database. Available keys are: =over 4 =item Name Must be unique. =item Content If you provide a reference, we will automatically serialize the data structure using L<Data::Dumper>. Otherwise any string is passed through as-is. =item ContentType Currently handles C<perl> or C<application/json>. =back Returns a tuple of (status, msg) on failure and (id, msg) on success. Also automatically propagates this config change to all server processes. =cut sub Create { my $self = shift; my %args = ( Name => '', Content => '', ContentType => '', @_, ); return (0, $self->loc("Permission Denied")) unless $self->CurrentUserHasRight('SuperUser'); if ( $args{'Name'} ) { my ( $ok, $msg ) = $self->ValidateName( $args{'Name'} ); unless ($ok) { return ($ok, $msg); } } else { return ( 0, $self->loc("Must specify 'Name' attribute") ); } $RT::Handle->BeginTransaction; my ( $id, $msg ) = $self->_Create(%args); unless ($id) { $RT::Handle->Rollback; return ($id, $msg); } my ($content, $error) = $self->Content; unless (defined($content) && length($content)) { $content = $self->loc('(no value)'); } my ( $Trans, $tx_msg, $TransObj ) = $self->_NewTransaction( Type => 'SetConfig', Field => $self->Name, ObjectType => 'RT::Configuration', ObjectId => $self->id, ReferenceType => ref($self), NewReference => $self->id, ); unless ($Trans) { $RT::Handle->Rollback; return (0, $self->loc("Setting [_1] to [_2] failed: [_3]", $args{Name}, $content, $tx_msg)); } $RT::Handle->Commit; RT->Config->ApplyConfigChangeToAllServerProcesses; my $old_value = RT->Config->Get($args{Name}); if ( ref $old_value ) { $old_value = $self->_SerializeContent($old_value); } RT->Logger->info($self->CurrentUser->Name . " changed " . $args{Name}); return ( $id, $self->loc( '[_1] changed from "[_2]" to "[_3]"', $self->Name, $old_value // '', $content // '' ) ); } =head2 CurrentUserCanSee Returns true if the current user can see the database setting =cut sub CurrentUserCanSee { my $self = shift; return $self->CurrentUserHasRight('SuperUser'); } =head2 Load Load a setting from the database. Takes a single argument. If the argument is numerical, load by the column 'id'. Otherwise, load by the "Name" column. =cut sub Load { my $self = shift; my $identifier = shift || return undef; if ( $identifier !~ /\D/ ) { return $self->SUPER::LoadById( $identifier ); } else { return $self->LoadByCol( "Name", $identifier ); } } =head2 SetName Not permitted =cut sub SetName { my $self = shift; return (0, $self->loc("Permission Denied")); } =head2 ValidateName Returns either (0, "failure reason") or 1 depending on whether the given name is valid. =cut sub ValidateName { my $self = shift; my $name = shift; return ( 0, $self->loc('empty name') ) unless defined $name && length $name; my $TempSetting = RT::Configuration->new( RT->SystemUser ); $TempSetting->LoadByCols(Name => $name, Disabled => 0); if ( $TempSetting->id && ( !$self->id || $TempSetting->id != $self->id ) ) { return ( 0, $self->loc('Name in use') ); } else { return 1; } } =head2 Delete Checks ACL, and on success propagates this config change to all server processes. =cut sub Delete { my $self = shift; return (0, $self->loc("Permission Denied")) unless $self->CurrentUserCanSee; $RT::Handle->BeginTransaction; my ( $ok, $msg ) = $self->SetDisabled( 1 ); unless ($ok) { $RT::Handle->Rollback; return ($ok, $msg); } my ( $Trans, $tx_msg, $TransObj ) = $self->_NewTransaction( Type => 'DeleteConfig', Field => $self->Name, ObjectType => 'RT::Configuration', ObjectId => $self->Id, ReferenceType => ref($self), OldReference => $self->id, ); unless ($Trans) { $RT::Handle->Rollback(); return ( 0, $self->loc( "Deleting [_1] failed: [_2]", $self->Name, $tx_msg ) ); } $RT::Handle->Commit; RT->Config->ApplyConfigChangeToAllServerProcesses; RT->Logger->info($self->CurrentUser->Name . " removed database setting for " . $self->Name); return ($ok, $self->loc("Database setting removed.")); } =head2 DecodedContent Returns a pair of this setting's content and any error. =cut sub DecodedContent { my $self = shift; # Here we call _Value to run the ACL check. my $content = $self->_Value('Content'); my $type = $self->__Value('ContentType') || ''; if ($type eq 'perl') { return $self->_DeserializeContent($content); } elsif ($type eq 'application/json') { return $self->_DeJSONContent($content); } return ($content, ""); } =head2 SetContent =cut sub SetContent { my $self = shift; my $raw_value = shift; my $content_type = shift || ''; return (0, $self->loc("Permission Denied")) unless $self->CurrentUserCanSee; my ( $ok, $msg ) = $self->ValidateContent( Content => $raw_value ); return ( 0, $msg ) unless $ok; my $value = $raw_value; if (ref $value) { $value = $self->_SerializeContent($value, $self->Name); $content_type = 'perl'; } if ($self->Content eq $value) { return (0, $self->loc("[_1] update: Nothing changed", ucfirst($self->Name))); } $RT::Handle->BeginTransaction; ( $ok, $msg ) = $self->SetDisabled( 1 ); unless ($ok) { $RT::Handle->Rollback; return ($ok, $msg); } my ($old_value, $error) = $self->Content; my $old_id = $self->id; my ( $new_id, $new_msg ) = $self->_Create( Name => $self->Name, Content => $raw_value, ContentType => $content_type, ); unless ($new_id) { $RT::Handle->Rollback; return (0, $self->loc("Setting [_1] to [_2] failed: [_3]", $self->Name, $value, $new_msg)); } unless (defined($value) && length($value)) { $value = $self->loc('(no value)'); } my ( $Trans, $tx_msg, $TransObj ) = $self->_NewTransaction( Type => 'SetConfig', Field => $self->Name, ObjectType => 'RT::Configuration', ObjectId => $new_id, ReferenceType => ref($self), OldReference => $old_id, NewReference => $new_id, ); unless ($Trans) { $RT::Handle->Rollback(); return (0, $self->loc("Setting [_1] to [_2] failed: [_3]", $self->Name, $value, $tx_msg)); } $RT::Handle->Commit; RT->Config->ApplyConfigChangeToAllServerProcesses; RT->Logger->info($self->CurrentUser->Name . " changed " . $self->Name); unless (defined($old_value) && length($old_value)) { $old_value = $self->loc('(no value)'); } return( 1, $self->loc('[_1] changed from "[_2]" to "[_3]"', $self->Name, $old_value // '', $value // '') ); } =head2 ValidateContent Returns either (0, "failure reason") or 1 depending on whether the given content is valid. =cut sub ValidateContent { my $self = shift; my %args = @_ == 1 ? ( Content => @_ ) : @_; $args{Name} ||= $self->Name; # Validate methods are automatically called on Create by RT::Record. # Sadly we have to skip that because it doesn't pass other field values, # which we need here, as content type depends on the config name. # We need to explicitly call Validate ourselves instead. return 1 unless $args{Name}; my $meta = RT->Config->Meta( $args{Name} ); if ( my $type = $meta->{Type} ) { if ( ( $type eq 'ARRAY' && ref $args{Content} ne 'ARRAY' ) || ( $type eq 'HASH' && ref $args{Content} ne 'HASH' ) ) { return ( 0, $self->loc( 'Invalid value for [_1], should be of type [_2]', $args{Name}, $type ) ); } } return ( 1, $self->loc('Content valid') ); } =head1 PRIVATE METHODS Documented for internal use only, do not call these from outside RT::Configuration itself. =head2 _Create Checks that the field being created/updated is not immutable, before calling C<SUPER::Create> to save changes in a new row, returning id of new row on success and 0, and message on failure. =cut sub _Create { my $self = shift; my %args = ( Name => '', Content => '', ContentType => '', @_ ); my $meta = RT->Config->Meta($args{'Name'}); if ($meta->{Immutable}) { return ( 0, $self->loc("You cannot update [_1] using database config; you must edit your site config", $args{'Name'}) ); } if ( ref( $args{'Content'} ) ) { $args{'Content'} = $self->_SerializeContent( $args{'Content'}, $args{'Name'} ); $args{'ContentType'} = 'perl'; } my ( $id, $msg ) = $self->SUPER::Create( map { $_ => $args{$_} } qw(Name Content ContentType), ); unless ($id) { return (0, $self->loc("Setting [_1] to [_2] failed: [_3]", $args{Name}, $args{Content}, $msg)); } return ($id, $msg); } =head2 _Set Checks if the current user has I<SuperUser> before calling C<SUPER::_Set>, and then propagates this config change to all server processes. =cut sub _Set { my $self = shift; my %args = ( Field => undef, Value => undef, @_ ); return (0, $self->loc("Permission Denied")) unless $self->CurrentUserCanSee; my ($ok, $msg) = $self->SUPER::_Set(@_); RT->Config->ApplyConfigChangeToAllServerProcesses; return ($ok, $msg); } =head2 _Value Checks L</CurrentUserCanSee> before calling C<SUPER::_Value>. =cut sub _Value { my $self = shift; return unless $self->CurrentUserCanSee; return $self->SUPER::_Value(@_); } sub _SerializeContent { my $self = shift; my $content = shift; require Data::Dumper; local $Data::Dumper::Terse = 1; local $Data::Dumper::Sortkeys = 1; my $frozen = Data::Dumper::Dumper($content); chomp $frozen; return $frozen; } sub _DeserializeContent { my $self = shift; my $content = shift; my $thawed = eval "$content"; if (my $error = $@) { $RT::Logger->error("Perl deserialization of database setting " . $self->Name . " failed: $error"); return (undef, $self->loc("Perl deserialization of database setting [_1] failed: [_2]", $self->Name, $error)); } return $thawed; } sub _DeJSONContent { my $self = shift; my $content = shift; my $thawed = eval { JSON::from_json($content) }; if (my $error = $@) { $RT::Logger->error("JSON deserialization of database setting " . $self->Name . " failed: $error"); return (undef, $self->loc("JSON deserialization of database setting [_1] failed: [_2]", $self->Name, $error)); } return $thawed; } sub Table { "Configurations" } sub _CoreAccessible { { id => { read => 1, type => 'int(11)', default => '' }, Name => { read => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, Content => { read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'blob', default => ''}, ContentType => { read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, Disabled => { read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'}, Creator => { read => 1, type => 'int(11)', default => '0', auto => 1 }, Created => { read => 1, type => 'datetime', default => '', auto => 1 }, LastUpdatedBy => { read => 1, type => 'int(11)', default => '0', auto => 1 }, LastUpdated => { read => 1, type => 'datetime', default => '', auto => 1 }, } } 1; ���������������rt-5.0.1/lib/RT/Tickets.pm��������������������������������������������������������������������������000644 �000765 �000024 �00000265066 14005011336 016057� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Tickets - A collection of Ticket objects =head1 SYNOPSIS use RT::Tickets; my $tickets = RT::Tickets->new($CurrentUser); =head1 DESCRIPTION A collection of RT::Tickets. =head1 METHODS =cut package RT::Tickets; use strict; use warnings; use base 'RT::SearchBuilder'; use Role::Basic 'with'; with 'RT::SearchBuilder::Role::Roles'; use Scalar::Util qw/blessed/; use 5.010; use RT::Ticket; use RT::SQL; sub Table { 'Tickets'} use RT::CustomFields; use RT::CustomRoles; __PACKAGE__->RegisterCustomFieldJoin(@$_) for [ "RT::Transaction" => sub { $_[0]->JoinTransactions } ], [ "RT::Queue" => sub { # XXX: Could avoid join and use main.Queue with some refactoring? return $_[0]->{_sql_aliases}{queues} ||= $_[0]->Join( ALIAS1 => 'main', FIELD1 => 'Queue', TABLE2 => 'Queues', FIELD2 => 'id', ); } ]; # Configuration Tables: # FIELD_METADATA is a mapping of searchable Field name, to Type, and other # metadata. our %FIELD_METADATA = ( Status => [ 'STRING', ], #loc_left_pair SLA => [ 'STRING', ], #loc_left_pair Queue => [ 'QUEUE' ], #loc_left_pair Type => [ 'ENUM', ], #loc_left_pair Creator => [ 'ENUM' => 'User', ], #loc_left_pair LastUpdatedBy => [ 'ENUM' => 'User', ], #loc_left_pair Owner => [ 'WATCHERFIELD' => 'Owner', ], #loc_left_pair EffectiveId => [ 'INT', ], #loc_left_pair id => [ 'ID', ], #loc_left_pair InitialPriority => [ 'INT', ], #loc_left_pair FinalPriority => [ 'INT', ], #loc_left_pair Priority => [ 'INT', ], #loc_left_pair TimeLeft => [ 'INT', ], #loc_left_pair TimeWorked => [ 'INT', ], #loc_left_pair TimeEstimated => [ 'INT', ], #loc_left_pair Linked => [ 'LINK' ], #loc_left_pair LinkedTo => [ 'LINK' => 'To' ], #loc_left_pair LinkedFrom => [ 'LINK' => 'From' ], #loc_left_pair MemberOf => [ 'LINK' => To => 'MemberOf', ], #loc_left_pair DependsOn => [ 'LINK' => To => 'DependsOn', ], #loc_left_pair RefersTo => [ 'LINK' => To => 'RefersTo', ], #loc_left_pair HasMember => [ 'LINK' => From => 'MemberOf', ], #loc_left_pair DependentOn => [ 'LINK' => From => 'DependsOn', ], #loc_left_pair DependedOnBy => [ 'LINK' => From => 'DependsOn', ], #loc_left_pair ReferredToBy => [ 'LINK' => From => 'RefersTo', ], #loc_left_pair Told => [ 'DATE' => 'Told', ], #loc_left_pair Starts => [ 'DATE' => 'Starts', ], #loc_left_pair Started => [ 'DATE' => 'Started', ], #loc_left_pair Due => [ 'DATE' => 'Due', ], #loc_left_pair Resolved => [ 'DATE' => 'Resolved', ], #loc_left_pair LastUpdated => [ 'DATE' => 'LastUpdated', ], #loc_left_pair Created => [ 'DATE' => 'Created', ], #loc_left_pair Subject => [ 'STRING', ], #loc_left_pair Content => [ 'TRANSCONTENT', ], #loc_left_pair ContentType => [ 'TRANSFIELD', ], #loc_left_pair Filename => [ 'TRANSFIELD', ], #loc_left_pair TransactionDate => [ 'TRANSDATE', ], #loc_left_pair Requestor => [ 'WATCHERFIELD' => 'Requestor', ], #loc_left_pair Requestors => [ 'WATCHERFIELD' => 'Requestor', ], #loc_left_pair Cc => [ 'WATCHERFIELD' => 'Cc', ], #loc_left_pair AdminCc => [ 'WATCHERFIELD' => 'AdminCc', ], #loc_left_pair Watcher => [ 'WATCHERFIELD', ], #loc_left_pair QueueCc => [ 'WATCHERFIELD' => 'Cc' => 'Queue', ], #loc_left_pair QueueAdminCc => [ 'WATCHERFIELD' => 'AdminCc' => 'Queue', ], #loc_left_pair QueueWatcher => [ 'WATCHERFIELD' => undef => 'Queue', ], #loc_left_pair CustomRole => [ 'WATCHERFIELD' ], # loc_left_pair CustomFieldValue => [ 'CUSTOMFIELD' => 'Ticket' ], #loc_left_pair CustomField => [ 'CUSTOMFIELD' => 'Ticket' ], #loc_left_pair CF => [ 'CUSTOMFIELD' => 'Ticket' ], #loc_left_pair TxnCF => [ 'CUSTOMFIELD' => 'Transaction' ], #loc_left_pair TransactionCF => [ 'CUSTOMFIELD' => 'Transaction' ], #loc_left_pair QueueCF => [ 'CUSTOMFIELD' => 'Queue' ], #loc_left_pair Lifecycle => [ 'LIFECYCLE' ], #loc_left_pair Updated => [ 'TRANSDATE', ], #loc_left_pair UpdatedBy => [ 'TRANSCREATOR', ], #loc_left_pair OwnerGroup => [ 'MEMBERSHIPFIELD' => 'Owner', ], #loc_left_pair RequestorGroup => [ 'MEMBERSHIPFIELD' => 'Requestor', ], #loc_left_pair CCGroup => [ 'MEMBERSHIPFIELD' => 'Cc', ], #loc_left_pair AdminCCGroup => [ 'MEMBERSHIPFIELD' => 'AdminCc', ], #loc_left_pair WatcherGroup => [ 'MEMBERSHIPFIELD', ], #loc_left_pair HasAttribute => [ 'HASATTRIBUTE', 1 ], HasNoAttribute => [ 'HASATTRIBUTE', 0 ], ); # Lower Case version of FIELDS, for case insensitivity our %LOWER_CASE_FIELDS = map { ( lc($_) => $_ ) } (keys %FIELD_METADATA); our %SEARCHABLE_SUBFIELDS = ( User => [qw( EmailAddress Name RealName Nickname Organization Address1 Address2 City State Zip Country WorkPhone HomePhone MobilePhone PagerPhone id )], ); # Mapping of Field Type to Function our %dispatch = ( ENUM => \&_EnumLimit, INT => \&_IntLimit, ID => \&_IdLimit, LINK => \&_LinkLimit, DATE => \&_DateLimit, STRING => \&_StringLimit, QUEUE => \&_QueueLimit, TRANSFIELD => \&_TransLimit, TRANSCONTENT => \&_TransContentLimit, TRANSDATE => \&_TransDateLimit, TRANSCREATOR => \&_TransCreatorLimit, WATCHERFIELD => \&_WatcherLimit, MEMBERSHIPFIELD => \&_WatcherMembershipLimit, CUSTOMFIELD => \&_CustomFieldLimit, HASATTRIBUTE => \&_HasAttributeLimit, LIFECYCLE => \&_LifecycleLimit, ); # Default EntryAggregator per type # if you specify OP, you must specify all valid OPs my %DefaultEA = ( INT => 'AND', ENUM => { '=' => 'OR', '!=' => 'AND' }, DATE => { 'IS' => 'OR', 'IS NOT' => 'OR', '=' => 'OR', '>=' => 'AND', '<=' => 'AND', '>' => 'AND', '<' => 'AND' }, STRING => { '=' => 'OR', '!=' => 'AND', 'LIKE' => 'AND', 'NOT LIKE' => 'AND' }, QUEUE => { '=' => 'OR', '!=' => 'AND', 'LIKE' => 'OR', 'NOT LIKE' => 'AND' }, TRANSFIELD => 'AND', TRANSDATE => 'AND', LINK => 'OR', LINKFIELD => 'AND', TARGET => 'AND', BASE => 'AND', WATCHERFIELD => { '=' => 'OR', '!=' => 'AND', 'LIKE' => 'OR', 'NOT LIKE' => 'AND' }, HASATTRIBUTE => { '=' => 'AND', '!=' => 'AND', }, CUSTOMFIELD => 'OR', ); sub FIELDS { return \%FIELD_METADATA } our @SORTFIELDS = qw(id Status Queue Subject Owner Created Due Starts Started Told Resolved LastUpdated Priority TimeWorked TimeLeft); =head2 SortFields Returns the list of fields that lists of tickets can easily be sorted by =cut sub SortFields { my $self = shift; return (@SORTFIELDS); } # BEGIN SQL STUFF ********************************* sub CleanSlate { my $self = shift; $self->SUPER::CleanSlate( @_ ); delete $self->{$_} foreach qw( _sql_cf_alias _sql_group_members_aliases _sql_object_cfv_alias _sql_role_group_aliases _sql_trattachalias _sql_u_watchers_alias_for_sort _sql_u_watchers_aliases _sql_current_user_can_see_applied ); } =head1 Limit Helper Routines These routines are the targets of a dispatch table depending on the type of field. They all share the same signature: my ($self,$field,$op,$value,@rest) = @_; The values in @rest should be suitable for passing directly to DBIx::SearchBuilder::Limit. Essentially they are an expanded/broken out (and much simplified) version of what ProcessRestrictions used to do. They're also much more clearly delineated by the TYPE of field being processed. =head2 _IdLimit Handle ID field. =cut sub _IdLimit { my ( $sb, $field, $op, $value, @rest ) = @_; if ( $value eq '__Bookmarked__' ) { return $sb->_BookmarkLimit( $field, $op, $value, @rest ); } else { return $sb->_IntLimit( $field, $op, $value, @rest ); } } sub _BookmarkLimit { my ( $sb, $field, $op, $value, @rest ) = @_; die "Invalid operator $op for __Bookmarked__ search on $field" unless $op =~ /^(=|!=)$/; my @bookmarks = $sb->CurrentUser->UserObj->Bookmarks; return $sb->Limit( FIELD => $field, OPERATOR => $op, VALUE => 0, @rest, ) unless @bookmarks; # as bookmarked tickets can be merged we have to use a join # but it should be pretty lightweight my $tickets_alias = $sb->Join( TYPE => 'LEFT', ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'Tickets', FIELD2 => 'EffectiveId', ); $op = $op eq '='? 'IN': 'NOT IN'; $sb->Limit( ALIAS => $tickets_alias, FIELD => 'id', OPERATOR => $op, VALUE => [ @bookmarks ], @rest, ); } =head2 _EnumLimit Handle Fields which are limited to certain values, and potentially need to be looked up from another class. This subroutine actually handles two different kinds of fields. For some the user is responsible for limiting the values. (i.e. Status, Type). For others, the value specified by the user will be looked by via specified class. Meta Data: name of class to lookup in (Optional) =cut sub _EnumLimit { my ( $sb, $field, $op, $value, @rest ) = @_; # SQL::Statement changes != to <>. (Can we remove this now?) $op = "!=" if $op eq "<>"; die "Invalid Operation: $op for $field" unless $op eq "=" or $op eq "!="; my $meta = $FIELD_METADATA{$field}; if ( defined $meta->[1] && defined $value && $value !~ /^\d+$/ ) { my $class = "RT::" . $meta->[1]; my $o = $class->new( $sb->CurrentUser ); $o->Load($value); $value = $o->Id || 0; } elsif ( $field eq "Type" ) { $value = lc $value if $value =~ /^(ticket|approval|reminder)$/i; } $sb->Limit( FIELD => $field, VALUE => $value, OPERATOR => $op, @rest, ); } =head2 _IntLimit Handle fields where the values are limited to integers. (For example, Priority, TimeWorked.) Meta Data: None =cut sub _IntLimit { my ( $sb, $field, $op, $value, @rest ) = @_; my $is_a_like = $op =~ /MATCHES|ENDSWITH|STARTSWITH|LIKE/i; # We want to support <id LIKE '1%'> for ticket autocomplete, # but we need to explicitly typecast on Postgres if ( $is_a_like && RT->Config->Get('DatabaseType') eq 'Pg' ) { return $sb->Limit( FUNCTION => "CAST(main.$field AS TEXT)", OPERATOR => $op, VALUE => $value, @rest, ); } $sb->Limit( FIELD => $field, VALUE => $value, OPERATOR => $op, @rest, ); } =head2 _LinkLimit Handle fields which deal with links between tickets. (MemberOf, DependsOn) Meta Data: 1: Direction (From, To) 2: Link Type (MemberOf, DependsOn, RefersTo) =cut sub _LinkLimit { my ( $sb, $field, $op, $value, @rest ) = @_; my $meta = $FIELD_METADATA{$field}; die "Invalid Operator $op for $field" unless $op =~ /^(=|!=|IS|IS NOT)$/io; my $is_negative = 0; if ( $op eq '!=' || $op =~ /\bNOT\b/i ) { $is_negative = 1; } my $is_null = 0; $is_null = 1 if !$value || $value =~ /^null$/io; my $direction = $meta->[1] || ''; my ($matchfield, $linkfield) = ('', ''); if ( $direction eq 'To' ) { ($matchfield, $linkfield) = ("Target", "Base"); } elsif ( $direction eq 'From' ) { ($matchfield, $linkfield) = ("Base", "Target"); } elsif ( $direction ) { die "Invalid link direction '$direction' for $field\n"; } else { $sb->_OpenParen; $sb->_LinkLimit( 'LinkedTo', $op, $value, @rest ); $sb->_LinkLimit( 'LinkedFrom', $op, $value, @rest, ENTRYAGGREGATOR => (($is_negative && $is_null) || (!$is_null && !$is_negative))? 'OR': 'AND', ); $sb->_CloseParen; return; } my $is_local = 1; if ( $is_null ) { $op = ($op =~ /^(=|IS)$/i)? 'IS': 'IS NOT'; } elsif ( $value =~ /\D/ ) { $value = RT::URI->new( $sb->CurrentUser )->CanonicalizeURI( $value ); $is_local = 0; } $matchfield = "Local$matchfield" if $is_local; #For doing a left join to find "unlinked tickets" we want to generate a query that looks like this # SELECT main.* FROM Tickets main # LEFT JOIN Links Links_1 ON ( (Links_1.Type = 'MemberOf') # AND(main.id = Links_1.LocalTarget)) # WHERE Links_1.LocalBase IS NULL; if ( $is_null ) { my $linkalias = $sb->Join( TYPE => 'LEFT', ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'Links', FIELD2 => 'Local' . $linkfield ); $sb->Limit( LEFTJOIN => $linkalias, FIELD => 'Type', OPERATOR => '=', VALUE => $meta->[2], ) if $meta->[2]; $sb->Limit( @rest, ALIAS => $linkalias, FIELD => $matchfield, OPERATOR => $op, VALUE => 'NULL', QUOTEVALUE => 0, ); } else { my $linkalias = $sb->Join( TYPE => 'LEFT', ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'Links', FIELD2 => 'Local' . $linkfield ); $sb->Limit( LEFTJOIN => $linkalias, FIELD => 'Type', OPERATOR => '=', VALUE => $meta->[2], ) if $meta->[2]; $sb->Limit( LEFTJOIN => $linkalias, FIELD => $matchfield, OPERATOR => '=', VALUE => $value, ); $sb->Limit( @rest, ALIAS => $linkalias, FIELD => $matchfield, OPERATOR => $is_negative? 'IS': 'IS NOT', VALUE => 'NULL', QUOTEVALUE => 0, ); } } =head2 _DateLimit Handle date fields. (Created, LastTold..) Meta Data: 1: type of link. (Probably not necessary.) =cut sub _DateLimit { my ( $sb, $field, $op, $value, %rest ) = @_; die "Invalid Date Op: $op" unless $op =~ /^(=|>|<|>=|<=|IS(\s+NOT)?)$/i; my $meta = $FIELD_METADATA{$field}; die "Incorrect Meta Data for $field" unless ( defined $meta->[1] ); if ( $op =~ /^(IS(\s+NOT)?)$/i) { return $sb->Limit( FUNCTION => $sb->NotSetDateToNullFunction, FIELD => $meta->[1], OPERATOR => $op, VALUE => "NULL", %rest, ); } if ( my $subkey = $rest{SUBKEY} ) { if ( $subkey eq 'DayOfWeek' && $op !~ /IS/i && $value =~ /[^0-9]/ ) { for ( my $i = 0; $i < @RT::Date::DAYS_OF_WEEK; $i++ ) { # Use a case-insensitive regex for better matching across # locales since we don't have fc() and lc() is worse. Really # we should be doing Unicode normalization too, but we don't do # that elsewhere in RT. # # XXX I18N: Replace the regex with fc() once we're guaranteed 5.16. next unless lc $RT::Date::DAYS_OF_WEEK[ $i ] eq lc $value or $sb->CurrentUser->loc($RT::Date::DAYS_OF_WEEK[ $i ]) =~ /^\Q$value\E$/i; $value = $i; last; } return $sb->Limit( FIELD => 'id', VALUE => 0, %rest ) if $value =~ /[^0-9]/; } elsif ( $subkey eq 'Month' && $op !~ /IS/i && $value =~ /[^0-9]/ ) { for ( my $i = 0; $i < @RT::Date::MONTHS; $i++ ) { # Use a case-insensitive regex for better matching across # locales since we don't have fc() and lc() is worse. Really # we should be doing Unicode normalization too, but we don't do # that elsewhere in RT. # # XXX I18N: Replace the regex with fc() once we're guaranteed 5.16. next unless lc $RT::Date::MONTHS[ $i ] eq lc $value or $sb->CurrentUser->loc($RT::Date::MONTHS[ $i ]) =~ /^\Q$value\E$/i; $value = $i + 1; last; } return $sb->Limit( FIELD => 'id', VALUE => 0, %rest ) if $value =~ /[^0-9]/; } my $tz; if ( RT->Config->Get('ChartsTimezonesInDB') ) { my $to = $sb->CurrentUser->UserObj->Timezone || RT->Config->Get('Timezone'); $tz = { From => 'UTC', To => $to } if $to && lc $to ne 'utc'; } # $subkey is validated by DateTimeFunction my $function = $RT::Handle->DateTimeFunction( Type => $subkey, Field => $sb->NotSetDateToNullFunction, Timezone => $tz, ); return $sb->Limit( FUNCTION => $function, FIELD => $meta->[1], OPERATOR => $op, VALUE => $value, %rest, ); } my $date = RT::Date->new( $sb->CurrentUser ); $date->Set( Format => 'unknown', Value => $value ); if ( $op eq "=" ) { # if we're specifying =, that means we want everything on a # particular single day. in the database, we need to check for > # and < the edges of that day. $date->SetToMidnight( Timezone => 'server' ); my $daystart = $date->ISO; $date->AddDay; my $dayend = $date->ISO; $sb->_OpenParen; $sb->Limit( FIELD => $meta->[1], OPERATOR => ">=", VALUE => $daystart, %rest, ); $sb->Limit( FIELD => $meta->[1], OPERATOR => "<", VALUE => $dayend, %rest, ENTRYAGGREGATOR => 'AND', ); $sb->_CloseParen; } else { $sb->Limit( FUNCTION => $sb->NotSetDateToNullFunction, FIELD => $meta->[1], OPERATOR => $op, VALUE => $date->ISO, %rest, ); } } =head2 _StringLimit Handle simple fields which are just strings. (Subject,Type) Meta Data: None =cut sub _StringLimit { my ( $sb, $field, $op, $value, @rest ) = @_; # FIXME: # Valid Operators: # =, !=, LIKE, NOT LIKE if ( RT->Config->Get('DatabaseType') eq 'Oracle' && (!defined $value || !length $value) && lc($op) ne 'is' && lc($op) ne 'is not' ) { if ($op eq '!=' || $op =~ /^NOT\s/i) { $op = 'IS NOT'; } else { $op = 'IS'; } $value = 'NULL'; } if ($field eq "Status") { $value = lc $value; } $sb->Limit( FIELD => $field, OPERATOR => $op, VALUE => $value, CASESENSITIVE => 0, @rest, ); } =head2 _QueueLimit Handle Queue field supporting both "is" and "match". Input should be a queue name or a partial string. =cut sub _QueueLimit { my ($sb, $field, $op, $value, @rest ) = @_; if ($op eq 'LIKE' || $op eq 'NOT LIKE') { my $alias = $sb->{_sql_aliases}{queues} ||= $sb->Join( ALIAS1 => 'main', FIELD1 => 'Queue', TABLE2 => 'Queues', FIELD2 => 'id', ); return $sb->Limit( ALIAS => $alias, FIELD => 'Name', OPERATOR => $op, VALUE => $value, CASESENSITIVE => 0, @rest, ); } my $o = RT::Queue->new( $sb->CurrentUser ); $o->Load($value); $value = $o->Id || 0; $sb->Limit( FIELD => $field, OPERATOR => $op, VALUE => $value, CASESENSITIVE => 0, @rest, ); } =head2 _TransDateLimit Handle fields limiting based on Transaction Date. The inpupt value must be in a format parseable by Time::ParseDate Meta Data: None =cut # This routine should really be factored into translimit. sub _TransDateLimit { my ( $sb, $field, $op, $value, @rest ) = @_; # See the comments for TransLimit, they apply here too my $txn_alias = $sb->JoinTransactions; my $date = RT::Date->new( $sb->CurrentUser ); $date->Set( Format => 'unknown', Value => $value ); $sb->_OpenParen; if ( $op eq "=" ) { # if we're specifying =, that means we want everything on a # particular single day. in the database, we need to check for > # and < the edges of that day. $date->SetToMidnight( Timezone => 'server' ); my $daystart = $date->ISO; $date->AddDay; my $dayend = $date->ISO; $sb->Limit( ALIAS => $txn_alias, FIELD => 'Created', OPERATOR => ">=", VALUE => $daystart, @rest ); $sb->Limit( ALIAS => $txn_alias, FIELD => 'Created', OPERATOR => "<=", VALUE => $dayend, @rest, ENTRYAGGREGATOR => 'AND', ); } # not searching for a single day else { #Search for the right field $sb->Limit( ALIAS => $txn_alias, FIELD => 'Created', OPERATOR => $op, VALUE => $date->ISO, @rest ); } $sb->_CloseParen; } sub _TransCreatorLimit { my ( $sb, $field, $op, $value, @rest ) = @_; $op = "!=" if $op eq "<>"; die "Invalid Operation: $op for $field" unless $op eq "=" or $op eq "!="; # See the comments for TransLimit, they apply here too my $txn_alias = $sb->JoinTransactions; if ( defined $value && $value !~ /^\d+$/ ) { my $u = RT::User->new( $sb->CurrentUser ); $u->Load($value); $value = $u->id || 0; } $sb->Limit( ALIAS => $txn_alias, FIELD => 'Creator', OPERATOR => $op, VALUE => $value, @rest ); } =head2 _TransLimit Limit based on the ContentType or the Filename of a transaction. =cut sub _TransLimit { my ( $self, $field, $op, $value, %rest ) = @_; my $txn_alias = $self->JoinTransactions; unless ( defined $self->{_sql_trattachalias} ) { $self->{_sql_trattachalias} = $self->Join( TYPE => 'LEFT', # not all txns have an attachment ALIAS1 => $txn_alias, FIELD1 => 'id', TABLE2 => 'Attachments', FIELD2 => 'TransactionId', ); } $self->Limit( %rest, ALIAS => $self->{_sql_trattachalias}, FIELD => $field, OPERATOR => $op, VALUE => $value, CASESENSITIVE => 0, ); } =head2 _TransContentLimit Limit based on the Content of a transaction. =cut sub _TransContentLimit { # Content search # If only this was this simple. We've got to do something # complicated here: #Basically, we want to make sure that the limits apply to #the same attachment, rather than just another attachment #for the same ticket, no matter how many clauses we lump #on. # In the SQL, we might have # (( Content = foo ) or ( Content = bar AND Content = baz )) # The AND group should share the same Alias. # Actually, maybe it doesn't matter. We use the same alias and it # works itself out? (er.. different.) # Steal more from _ProcessRestrictions # FIXME: Maybe look at the previous FooLimit call, and if it was a # TransLimit and EntryAggregator == AND, reuse the Aliases? # Or better - store the aliases on a per subclause basis - since # those are going to be the things we want to relate to each other, # anyway. # maybe we should not allow certain kinds of aggregation of these # clauses and do a psuedo regex instead? - the problem is getting # them all into the same subclause when you have (A op B op C) - the # way they get parsed in the tree they're in different subclauses. my ( $self, $field, $op, $value, %rest ) = @_; $field = 'Content' if $field =~ /\W/; my $config = RT->Config->Get('FullTextSearch') || {}; unless ( $config->{'Enable'} ) { $self->Limit( %rest, FIELD => 'id', VALUE => 0 ); return; } my $txn_alias = $self->JoinTransactions; unless ( defined $self->{_sql_trattachalias} ) { $self->{_sql_trattachalias} = $self->Join( TYPE => 'LEFT', # not all txns have an attachment ALIAS1 => $txn_alias, FIELD1 => 'id', TABLE2 => 'Attachments', FIELD2 => 'TransactionId', ); } $self->_OpenParen; if ( $config->{'Indexed'} ) { my $db_type = RT->Config->Get('DatabaseType'); my $alias; if ( $config->{'Table'} and $config->{'Table'} ne "Attachments") { $alias = $self->{'_sql_aliases'}{'full_text'} ||= $self->Join( TYPE => 'LEFT', ALIAS1 => $self->{'_sql_trattachalias'}, FIELD1 => 'id', TABLE2 => $config->{'Table'}, FIELD2 => 'id', ); } else { $alias = $self->{'_sql_trattachalias'}; } #XXX: handle negative searches my $index = $config->{'Column'}; if ( $db_type eq 'Oracle' ) { my $dbh = $RT::Handle->dbh; my $alias = $self->{_sql_trattachalias}; $self->Limit( %rest, FUNCTION => "CONTAINS( $alias.$field, ".$dbh->quote($value) .")", OPERATOR => '>', VALUE => 0, QUOTEVALUE => 0, CASESENSITIVE => 1, ); # this is required to trick DBIx::SB's LEFT JOINS optimizer # into deciding that join is redundant as it is $self->Limit( ENTRYAGGREGATOR => 'AND', ALIAS => $self->{_sql_trattachalias}, FIELD => 'Content', OPERATOR => 'IS NOT', VALUE => 'NULL', ); } elsif ( $db_type eq 'Pg' ) { my $dbh = $RT::Handle->dbh; $self->Limit( %rest, ALIAS => $alias, FIELD => $index, OPERATOR => '@@', VALUE => 'plainto_tsquery('. $dbh->quote($value) .')', QUOTEVALUE => 0, ); } elsif ( $db_type eq 'mysql' and not $config->{Sphinx}) { my $dbh = $RT::Handle->dbh; $self->Limit( %rest, FUNCTION => "MATCH($alias.Content)", OPERATOR => 'AGAINST', VALUE => "(". $dbh->quote($value) ." IN BOOLEAN MODE)", QUOTEVALUE => 0, ); # As with Oracle, above, this forces the LEFT JOINs into # JOINS, which allows the FULLTEXT index to be used. # Orthogonally, the IS NOT NULL clause also helps the # optimizer decide to use the index. $self->Limit( ENTRYAGGREGATOR => 'AND', ALIAS => $alias, FIELD => "Content", OPERATOR => 'IS NOT', VALUE => 'NULL', QUOTEVALUE => 0, ); } elsif ( $db_type eq 'mysql' ) { # XXX: We could theoretically skip the join to Attachments, # and have Sphinx simply index and group by the TicketId, # and join Ticket.id to that attribute, which would be much # more efficient -- however, this is only a possibility if # there are no other transaction limits. # This is a special character. Note that \ does not escape # itself (in Sphinx 2.1.0, at least), so 'foo\;bar' becoming # 'foo\\;bar' is not a vulnerability, and is still parsed as # "foo, \, ;, then bar". Happily, the default mode is # "all", meaning that boolean operators are not special. $value =~ s/;/\\;/g; my $max = $config->{'MaxMatches'}; $self->Limit( %rest, ALIAS => $alias, FIELD => 'query', OPERATOR => '=', VALUE => "$value;limit=$max;maxmatches=$max", ); } } else { $self->Limit( %rest, ALIAS => $txn_alias, FIELD => 'Type', OPERATOR => 'NOT IN', VALUE => ['EmailRecord', 'CommentEmailRecord'], ); $self->Limit( ENTRYAGGREGATOR => 'AND', ALIAS => $self->{_sql_trattachalias}, FIELD => $field, OPERATOR => $op, VALUE => $value, CASESENSITIVE => 0, ); } if ( RT->Config->Get('DontSearchFileAttachments') ) { $self->Limit( ENTRYAGGREGATOR => 'AND', ALIAS => $self->{_sql_trattachalias}, FIELD => 'Filename', OPERATOR => 'IS', VALUE => 'NULL', ); } $self->_CloseParen; } =head2 _CustomRoleDecipher Try and turn a custom role descriptor (e.g. C<CustomRole.{Engineer}>) into (role, column, original name). =cut sub _CustomRoleDecipher { my ($self, $string) = @_; my ($field, $column) = ($string =~ /^\{(.+)\}(?:\.(\w+))?$/); my $role; if ( $field =~ /\D/ ) { my $roles = RT::CustomRoles->new( $self->CurrentUser ); $roles->Limit( FIELD => 'Name', VALUE => $field, CASESENSITIVE => 0 ); # custom roles are named uniquely, but just in case there are # multiple matches, bail out as we don't know which one to use $role = $roles->First; if ( $role ) { $role = undef if $roles->Next; } } else { $role = RT::CustomRole->new( $self->CurrentUser ); $role->Load( $field ); } return ($role, $column, $field); } =head2 _WatcherLimit Handle watcher limits. (Requestor, CC, etc..) Meta Data: 1: Field to query on =cut sub _WatcherLimit { my $self = shift; my $field = shift; my $op = shift; my $value = shift; my %rest = (@_); my $meta = $FIELD_METADATA{ $field }; my $type = $meta->[1] || ''; my $class = $meta->[2] || 'Ticket'; my $column = $rest{SUBKEY}; if ($field eq 'CustomRole') { my ($role, $col, $original_name) = $self->_CustomRoleDecipher( $column ); $column = $col || 'id'; $type = $role ? $role->GroupType : $original_name; } # Bail if the subfield is not allowed if ( $column and not grep { $_ eq $column } @{$SEARCHABLE_SUBFIELDS{'User'}}) { die "Invalid watcher subfield: '$column'"; } $self->RoleLimit( TYPE => $type, CLASS => "RT::$class", FIELD => $column, OPERATOR => $op, VALUE => $value, SUBCLAUSE => "ticketsql", %rest, ); } =head2 _WatcherMembershipLimit Handle watcher membership limits, i.e. whether the watcher belongs to a specific group or not. Meta Data: 1: Role to query on =cut sub _WatcherMembershipLimit { my ( $self, $field, $op, $value, %rest ) = @_; # we don't support anything but '=' die "Invalid $field Op: $op" unless $op =~ /^=$/; unless ( $value =~ /^\d+$/ ) { my $group = RT::Group->new( $self->CurrentUser ); $group->LoadUserDefinedGroup( $value ); $value = $group->id || 0; } my $meta = $FIELD_METADATA{$field}; my $type = $meta->[1] || ''; my ($members_alias, $members_column); if ( $type eq 'Owner' ) { ($members_alias, $members_column) = ('main', 'Owner'); } else { (undef, undef, $members_alias) = $self->_WatcherJoin( New => 1, Name => $type ); $members_column = 'id'; } my $cgm_alias = $self->Join( ALIAS1 => $members_alias, FIELD1 => $members_column, TABLE2 => 'CachedGroupMembers', FIELD2 => 'MemberId', ); $self->Limit( LEFTJOIN => $cgm_alias, ALIAS => $cgm_alias, FIELD => 'Disabled', VALUE => 0, ); $self->Limit( ALIAS => $cgm_alias, FIELD => 'GroupId', VALUE => $value, OPERATOR => $op, %rest, ); } =head2 _CustomFieldDecipher Try and turn a CF descriptor into (cfid, cfname) object pair. Takes an optional second parameter of the CF LookupType, defaults to Ticket CFs. =cut sub _CustomFieldDecipher { my ($self, $string, $lookuptype) = @_; $lookuptype ||= $self->_SingularClass->CustomFieldLookupType; my ($object, $field, $column) = ($string =~ /^(?:(.+?)\.)?\{(.+)\}(?:\.(Content|LargeContent))?$/); $field ||= ($string =~ /^\{(.*?)\}$/)[0] || $string; my ($cf, $applied_to); if ( $object ) { my $record_class = RT::CustomField->RecordClassFromLookupType($lookuptype); $applied_to = $record_class->new( $self->CurrentUser ); $applied_to->Load( $object ); if ( $applied_to->id ) { RT->Logger->debug("Limiting to CFs identified by '$field' applied to $record_class #@{[$applied_to->id]} (loaded via '$object')"); } else { RT->Logger->warning("$record_class '$object' doesn't exist, parsed from '$string'"); $object = 0; undef $applied_to; } } if ( $field =~ /\D/ ) { $object ||= ''; my $cfs = RT::CustomFields->new( $self->CurrentUser ); $cfs->Limit( FIELD => 'Name', VALUE => $field, CASESENSITIVE => 0 ); $cfs->LimitToLookupType($lookuptype); if ($applied_to) { $cfs->SetContextObject($applied_to); $cfs->LimitToObjectId($applied_to->id); } # if there is more then one field the current user can # see with the same name then we shouldn't return cf object # as we don't know which one to use $cf = $cfs->First; if ( $cf ) { $cf = undef if $cfs->Next; } } else { $cf = RT::CustomField->new( $self->CurrentUser ); $cf->Load( $field ); $cf->SetContextObject($applied_to) if $cf->id and $applied_to; } return ($object, $field, $cf, $column); } =head2 _CustomFieldLimit Limit based on CustomFields Meta Data: none =cut sub _CustomFieldLimit { my ( $self, $_field, $op, $value, %rest ) = @_; my $meta = $FIELD_METADATA{ $_field }; my $class = $meta->[1] || 'Ticket'; my $type = "RT::$class"->CustomFieldLookupType; my $field = $rest{'SUBKEY'} || die "No field specified"; # For our sanity, we can only limit on one object at a time my ($object, $cfid, $cf, $column); ($object, $field, $cf, $column) = $self->_CustomFieldDecipher( $field, $type ); $self->_LimitCustomField( %rest, LOOKUPTYPE => $type, CUSTOMFIELD => $cf || $field, KEY => $cf ? $cf->id : "$type-$object.$field", OPERATOR => $op, VALUE => $value, COLUMN => $column, SUBCLAUSE => "ticketsql", ); } sub _CustomFieldJoinByName { my $self = shift; my ($ObjectAlias, $cf, $type) = @_; my ($ocfvalias, $CFs, $ocfalias) = $self->SUPER::_CustomFieldJoinByName(@_); $self->Limit( LEFTJOIN => $ocfalias, ENTRYAGGREGATOR => 'OR', FIELD => 'ObjectId', VALUE => 'main.Queue', QUOTEVALUE => 0, ); return ($ocfvalias, $CFs, $ocfalias); } sub _HasAttributeLimit { my ( $self, $field, $op, $value, %rest ) = @_; my $alias = $self->Join( TYPE => 'LEFT', ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'Attributes', FIELD2 => 'ObjectId', ); $self->Limit( LEFTJOIN => $alias, FIELD => 'ObjectType', VALUE => 'RT::Ticket', ENTRYAGGREGATOR => 'AND' ); $self->Limit( LEFTJOIN => $alias, FIELD => 'Name', OPERATOR => $op, VALUE => $value, ENTRYAGGREGATOR => 'AND' ); $self->Limit( %rest, ALIAS => $alias, FIELD => 'id', OPERATOR => $FIELD_METADATA{$field}->[1]? 'IS NOT': 'IS', VALUE => 'NULL', QUOTEVALUE => 0, ); } sub _LifecycleLimit { my ( $self, $field, $op, $value, %rest ) = @_; die "Invalid Operator $op for $field" if $op =~ /^(IS|IS NOT)$/io; my $queue = $self->{_sql_aliases}{queues} ||= $_[0]->Join( ALIAS1 => 'main', FIELD1 => 'Queue', TABLE2 => 'Queues', FIELD2 => 'id', ); $self->Limit( ALIAS => $queue, FIELD => 'Lifecycle', OPERATOR => $op, VALUE => $value, %rest, ); } # End Helper Functions # End of SQL Stuff ------------------------------------------------- =head2 OrderByCols ARRAY A modified version of the OrderBy method which automatically joins where C<ALIAS> is set to the name of a watcher type. =cut sub OrderByCols { my $self = shift; my @args = @_; my $clause; my @res = (); my $order = 0; foreach my $row (@args) { if ( $row->{ALIAS} ) { push @res, $row; next; } if ( $row->{FIELD} !~ /\./ ) { my $meta = $FIELD_METADATA{ $row->{FIELD} }; unless ( $meta ) { push @res, $row; next; } if ( $meta->[0] eq 'QUEUE' ) { my $alias = $self->Join( TYPE => 'LEFT', ALIAS1 => 'main', FIELD1 => $row->{'FIELD'}, TABLE2 => 'Queues', FIELD2 => 'id', ); push @res, { %$row, ALIAS => $alias, FIELD => "Name", CASESENSITIVE => 0 }; } elsif ( ( $meta->[0] eq 'ENUM' && ($meta->[1]||'') eq 'User' ) || ( $meta->[0] eq 'WATCHERFIELD' && ($meta->[1]||'') eq 'Owner' ) ) { my $alias = $self->Join( TYPE => 'LEFT', ALIAS1 => 'main', FIELD1 => $row->{'FIELD'}, TABLE2 => 'Users', FIELD2 => 'id', ); push @res, { %$row, ALIAS => $alias, FIELD => "Name", CASESENSITIVE => 0 }; } else { push @res, $row; } next; } my ( $field, $subkey ) = split /\./, $row->{FIELD}, 2; my $meta = $FIELD_METADATA{$field}; if ( defined $meta->[0] && $meta->[0] eq 'WATCHERFIELD' ) { my $type = $meta->[1] || ''; my $class = $meta->[2] || 'Ticket'; my $column = $subkey; if ($field eq 'CustomRole') { my ($role, $col, $original_name) = $self->_CustomRoleDecipher( $column ); $column = $col || 'id'; $type = $role ? $role->GroupType : $original_name; } # cache alias as we want to use one alias per watcher type for sorting my $cache_key = join "-", $type, $class; my $users = $self->{_sql_u_watchers_alias_for_sort}{ $cache_key }; unless ( $users ) { $self->{_sql_u_watchers_alias_for_sort}{ $cache_key } = $users = ( $self->_WatcherJoin( Name => $type, Class => "RT::" . $class ) )[2]; } push @res, { %$row, ALIAS => $users, FIELD => $column }; } elsif ( defined $meta->[0] && $meta->[0] eq 'CUSTOMFIELD' ) { my ($object, $field, $cf, $column) = $self->_CustomFieldDecipher( $subkey ); my $cfkey = $cf ? $cf->id : "$object.$field"; push @res, $self->_OrderByCF( $row, $cfkey, ($cf || $field) ); } elsif ( $field eq "Custom" && $subkey eq "Ownership") { # PAW logic is "reversed" my $order = "ASC"; if (exists $row->{ORDER} ) { my $o = $row->{ORDER}; delete $row->{ORDER}; $order = "DESC" if $o =~ /asc/i; } # Ticket.Owner 1 0 X # Unowned Tickets 0 1 X # Else 0 0 X foreach my $uid ( $self->CurrentUser->Id, RT->Nobody->Id ) { if ( RT->Config->Get('DatabaseType') eq 'Oracle' ) { my $f = ($row->{'ALIAS'} || 'main') .'.Owner'; push @res, { %$row, FIELD => undef, ALIAS => '', FUNCTION => "CASE WHEN $f=$uid THEN 1 ELSE 0 END", ORDER => $order }; } else { push @res, { %$row, FIELD => undef, FUNCTION => "Owner=$uid", ORDER => $order }; } } push @res, { %$row, FIELD => "Priority", ORDER => $order } ; } else { push @res, $row; } } return $self->SUPER::OrderByCols(@res); } sub _OpenParen { $_[0]->SUPER::_OpenParen( $_[1] || 'ticketsql' ); } sub _CloseParen { $_[0]->SUPER::_CloseParen( $_[1] || 'ticketsql' ); } sub Limit { my $self = shift; my %args = @_; $self->{'must_redo_search'} = 1; delete $self->{'raw_rows'}; delete $self->{'count_all'}; if ($self->{'using_restrictions'}) { RT->Deprecated( Message => "Mixing old-style LimitFoo methods with Limit is deprecated" ); $self->LimitField(@_); } $args{SUBCLAUSE} ||= "ticketsql" if $self->{parsing_ticketsql} and not $args{LEFTJOIN}; $self->{_sql_looking_at}{ lc $args{FIELD} } = 1 if $args{FIELD} and (not $args{ALIAS} or $args{ALIAS} eq "main"); $self->SUPER::Limit(%args); } =head2 LimitField Takes a paramhash with the fields FIELD, OPERATOR, VALUE and DESCRIPTION Generally best called from LimitFoo methods =cut sub LimitField { my $self = shift; my %args = ( FIELD => undef, OPERATOR => '=', VALUE => undef, DESCRIPTION => undef, @_ ); $args{'DESCRIPTION'} = $self->loc( "[_1] [_2] [_3]", $args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'} ) if ( !defined $args{'DESCRIPTION'} ); if ($self->_isLimited > 1) { RT->Deprecated( Message => "Mixing old-style LimitFoo methods with Limit is deprecated" ); } $self->{using_restrictions} = 1; my $index = $self->_NextIndex; # make the TicketRestrictions hash the equivalent of whatever we just passed in; %{ $self->{'TicketRestrictions'}{$index} } = %args; $self->{'RecalcTicketLimits'} = 1; return ($index); } =head2 LimitQueue LimitQueue takes a paramhash with the fields OPERATOR and VALUE. OPERATOR is one of = or !=. (It defaults to =). VALUE is a queue id or Name. =cut sub LimitQueue { my $self = shift; my %args = ( VALUE => undef, OPERATOR => '=', @_ ); #TODO VALUE should also take queue objects if ( defined $args{'VALUE'} && $args{'VALUE'} !~ /^\d+$/ ) { my $queue = RT::Queue->new( $self->CurrentUser ); $queue->Load( $args{'VALUE'} ); $args{'VALUE'} = $queue->Id; } # What if they pass in an Id? Check for isNum() and convert to # string. #TODO check for a valid queue here $self->LimitField( FIELD => 'Queue', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => join( ' ', $self->loc('Queue'), $args{'OPERATOR'}, $args{'VALUE'}, ), ); } =head2 LimitStatus Takes a paramhash with the fields OPERATOR and VALUE. OPERATOR is one of = or !=. VALUE is a status. RT adds Status != 'deleted' until object has allow_deleted_search internal property set. $tickets->{'allow_deleted_search'} = 1; $tickets->LimitStatus( VALUE => 'deleted' ); =cut sub LimitStatus { my $self = shift; my %args = ( OPERATOR => '=', @_ ); $self->LimitField( FIELD => 'Status', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => join( ' ', $self->loc('Status'), $args{'OPERATOR'}, $self->loc( $args{'VALUE'} ) ), ); } =head2 LimitToActiveStatus Limits the status to L<RT::Queue/ActiveStatusArray> TODO: make this respect lifecycles for the queues associated with the search =cut sub LimitToActiveStatus { my $self = shift; my @active = RT::Queue->ActiveStatusArray(); for my $active (@active) { $self->LimitStatus( VALUE => $active, ); } } =head2 LimitToInactiveStatus Limits the status to L<RT::Queue/InactiveStatusArray> TODO: make this respect lifecycles for the queues associated with the search =cut sub LimitToInactiveStatus { my $self = shift; my @active = RT::Queue->InactiveStatusArray(); for my $active (@active) { $self->LimitStatus( VALUE => $active, ); } } =head2 IgnoreType If called, this search will not automatically limit the set of results found to tickets of type "Ticket". Tickets of other types, such as "project" and "approval" will be found. =cut sub IgnoreType { my $self = shift; # Instead of faking a Limit that later gets ignored, fake up the # fact that we're already looking at type, so that the check in # FromSQL goes down the right branch # $self->LimitType(VALUE => '__any'); $self->{_sql_looking_at}{type} = 1; } =head2 LimitType Takes a paramhash with the fields OPERATOR and VALUE. OPERATOR is one of = or !=, it defaults to "=". VALUE is a string to search for in the type of the ticket. =cut sub LimitType { my $self = shift; my %args = ( OPERATOR => '=', VALUE => undef, @_ ); $self->LimitField( FIELD => 'Type', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => join( ' ', $self->loc('Type'), $args{'OPERATOR'}, $args{'VALUE'}, ), ); } =head2 LimitSubject Takes a paramhash with the fields OPERATOR and VALUE. OPERATOR is one of = or !=. VALUE is a string to search for in the subject of the ticket. =cut sub LimitSubject { my $self = shift; my %args = (@_); $self->LimitField( FIELD => 'Subject', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => join( ' ', $self->loc('Subject'), $args{'OPERATOR'}, $args{'VALUE'}, ), ); } # Things that can be > < = != =head2 LimitId Takes a paramhash with the fields OPERATOR and VALUE. OPERATOR is one of =, >, < or !=. VALUE is a ticket Id to search for =cut sub LimitId { my $self = shift; my %args = ( OPERATOR => '=', @_ ); $self->LimitField( FIELD => 'id', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => join( ' ', $self->loc('Id'), $args{'OPERATOR'}, $args{'VALUE'}, ), ); } =head2 LimitPriority Takes a paramhash with the fields OPERATOR and VALUE. OPERATOR is one of =, >, < or !=. VALUE is a value to match the ticket's priority against =cut sub LimitPriority { my $self = shift; my %args = (@_); $self->LimitField( FIELD => 'Priority', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => join( ' ', $self->loc('Priority'), $args{'OPERATOR'}, $args{'VALUE'}, ), ); } =head2 LimitInitialPriority Takes a paramhash with the fields OPERATOR and VALUE. OPERATOR is one of =, >, < or !=. VALUE is a value to match the ticket's initial priority against =cut sub LimitInitialPriority { my $self = shift; my %args = (@_); $self->LimitField( FIELD => 'InitialPriority', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => join( ' ', $self->loc('Initial Priority'), $args{'OPERATOR'}, $args{'VALUE'}, ), ); } =head2 LimitFinalPriority Takes a paramhash with the fields OPERATOR and VALUE. OPERATOR is one of =, >, < or !=. VALUE is a value to match the ticket's final priority against =cut sub LimitFinalPriority { my $self = shift; my %args = (@_); $self->LimitField( FIELD => 'FinalPriority', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => join( ' ', $self->loc('Final Priority'), $args{'OPERATOR'}, $args{'VALUE'}, ), ); } =head2 LimitTimeWorked Takes a paramhash with the fields OPERATOR and VALUE. OPERATOR is one of =, >, < or !=. VALUE is a value to match the ticket's TimeWorked attribute =cut sub LimitTimeWorked { my $self = shift; my %args = (@_); $self->LimitField( FIELD => 'TimeWorked', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => join( ' ', $self->loc('Time Worked'), $args{'OPERATOR'}, $args{'VALUE'}, ), ); } =head2 LimitTimeLeft Takes a paramhash with the fields OPERATOR and VALUE. OPERATOR is one of =, >, < or !=. VALUE is a value to match the ticket's TimeLeft attribute =cut sub LimitTimeLeft { my $self = shift; my %args = (@_); $self->LimitField( FIELD => 'TimeLeft', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => join( ' ', $self->loc('Time Left'), $args{'OPERATOR'}, $args{'VALUE'}, ), ); } =head2 LimitContent Takes a paramhash with the fields OPERATOR and VALUE. OPERATOR is one of =, LIKE, NOT LIKE or !=. VALUE is a string to search for in the body of the ticket =cut sub LimitContent { my $self = shift; my %args = (@_); $self->LimitField( FIELD => 'Content', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => join( ' ', $self->loc('Ticket content'), $args{'OPERATOR'}, $args{'VALUE'}, ), ); } =head2 LimitFilename Takes a paramhash with the fields OPERATOR and VALUE. OPERATOR is one of =, LIKE, NOT LIKE or !=. VALUE is a string to search for in the body of the ticket =cut sub LimitFilename { my $self = shift; my %args = (@_); $self->LimitField( FIELD => 'Filename', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => join( ' ', $self->loc('Attachment filename'), $args{'OPERATOR'}, $args{'VALUE'}, ), ); } =head2 LimitContentType Takes a paramhash with the fields OPERATOR and VALUE. OPERATOR is one of =, LIKE, NOT LIKE or !=. VALUE is a content type to search ticket attachments for =cut sub LimitContentType { my $self = shift; my %args = (@_); $self->LimitField( FIELD => 'ContentType', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => join( ' ', $self->loc('Ticket content type'), $args{'OPERATOR'}, $args{'VALUE'}, ), ); } =head2 LimitOwner Takes a paramhash with the fields OPERATOR and VALUE. OPERATOR is one of = or !=. VALUE is a user id. =cut sub LimitOwner { my $self = shift; my %args = ( OPERATOR => '=', @_ ); my $owner = RT::User->new( $self->CurrentUser ); $owner->Load( $args{'VALUE'} ); # FIXME: check for a valid $owner $self->LimitField( FIELD => 'Owner', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => join( ' ', $self->loc('Owner'), $args{'OPERATOR'}, $owner->Name(), ), ); } =head2 LimitWatcher Takes a paramhash with the fields OPERATOR, TYPE and VALUE. OPERATOR is one of =, LIKE, NOT LIKE or !=. VALUE is a value to match the ticket's watcher email addresses against TYPE is the sort of watchers you want to match against. Leave it undef if you want to search all of them =cut sub LimitWatcher { my $self = shift; my %args = ( OPERATOR => '=', VALUE => undef, TYPE => undef, @_ ); #build us up a description my ( $watcher_type, $desc ); if ( $args{'TYPE'} ) { $watcher_type = $args{'TYPE'}; } else { $watcher_type = "Watcher"; } $self->LimitField( FIELD => $watcher_type, VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, TYPE => $args{'TYPE'}, DESCRIPTION => join( ' ', $self->loc($watcher_type), $args{'OPERATOR'}, $args{'VALUE'}, ), ); } =head2 LimitLinkedTo LimitLinkedTo takes a paramhash with two fields: TYPE and TARGET TYPE limits the sort of link we want to search on TYPE = { RefersTo, MemberOf, DependsOn } TARGET is the id or URI of the TARGET of the link =cut sub LimitLinkedTo { my $self = shift; my %args = ( TARGET => undef, TYPE => undef, OPERATOR => '=', @_ ); $self->LimitField( FIELD => 'LinkedTo', BASE => undef, TARGET => $args{'TARGET'}, TYPE => $args{'TYPE'}, DESCRIPTION => $self->loc( "Tickets [_1] by [_2]", $self->loc( $args{'TYPE'} ), $args{'TARGET'} ), OPERATOR => $args{'OPERATOR'}, ); } =head2 LimitLinkedFrom LimitLinkedFrom takes a paramhash with two fields: TYPE and BASE TYPE limits the sort of link we want to search on BASE is the id or URI of the BASE of the link =cut sub LimitLinkedFrom { my $self = shift; my %args = ( BASE => undef, TYPE => undef, OPERATOR => '=', @_ ); # translate RT2 From/To naming to RT3 TicketSQL naming my %fromToMap = qw(DependsOn DependentOn MemberOf HasMember RefersTo ReferredToBy); my $type = $args{'TYPE'}; $type = $fromToMap{$type} if exists( $fromToMap{$type} ); $self->LimitField( FIELD => 'LinkedTo', TARGET => undef, BASE => $args{'BASE'}, TYPE => $type, DESCRIPTION => $self->loc( "Tickets [_1] [_2]", $self->loc( $args{'TYPE'} ), $args{'BASE'}, ), OPERATOR => $args{'OPERATOR'}, ); } sub LimitMemberOf { my $self = shift; my $ticket_id = shift; return $self->LimitLinkedTo( @_, TARGET => $ticket_id, TYPE => 'MemberOf', ); } sub LimitHasMember { my $self = shift; my $ticket_id = shift; return $self->LimitLinkedFrom( @_, BASE => "$ticket_id", TYPE => 'HasMember', ); } sub LimitDependsOn { my $self = shift; my $ticket_id = shift; return $self->LimitLinkedTo( @_, TARGET => $ticket_id, TYPE => 'DependsOn', ); } sub LimitDependedOnBy { my $self = shift; my $ticket_id = shift; return $self->LimitLinkedFrom( @_, BASE => $ticket_id, TYPE => 'DependentOn', ); } sub LimitRefersTo { my $self = shift; my $ticket_id = shift; return $self->LimitLinkedTo( @_, TARGET => $ticket_id, TYPE => 'RefersTo', ); } sub LimitReferredToBy { my $self = shift; my $ticket_id = shift; return $self->LimitLinkedFrom( @_, BASE => $ticket_id, TYPE => 'ReferredToBy', ); } =head2 LimitDate (FIELD => 'DateField', OPERATOR => $oper, VALUE => $ISODate) Takes a paramhash with the fields FIELD OPERATOR and VALUE. OPERATOR is one of > or < VALUE is a date and time in ISO format in GMT FIELD is one of Starts, Started, Told, Created, Resolved, LastUpdated There are also helper functions of the form LimitFIELD that eliminate the need to pass in a FIELD argument. =cut sub LimitDate { my $self = shift; my %args = ( FIELD => undef, VALUE => undef, OPERATOR => undef, @_ ); #Set the description if we didn't get handed it above unless ( $args{'DESCRIPTION'} ) { $args{'DESCRIPTION'} = $args{'FIELD'} . " " . $args{'OPERATOR'} . " " . $args{'VALUE'} . " GMT"; } $self->LimitField(%args); } sub LimitCreated { my $self = shift; $self->LimitDate( FIELD => 'Created', @_ ); } sub LimitDue { my $self = shift; $self->LimitDate( FIELD => 'Due', @_ ); } sub LimitStarts { my $self = shift; $self->LimitDate( FIELD => 'Starts', @_ ); } sub LimitStarted { my $self = shift; $self->LimitDate( FIELD => 'Started', @_ ); } sub LimitResolved { my $self = shift; $self->LimitDate( FIELD => 'Resolved', @_ ); } sub LimitTold { my $self = shift; $self->LimitDate( FIELD => 'Told', @_ ); } sub LimitLastUpdated { my $self = shift; $self->LimitDate( FIELD => 'LastUpdated', @_ ); } # =head2 LimitTransactionDate (OPERATOR => $oper, VALUE => $ISODate) Takes a paramhash with the fields FIELD OPERATOR and VALUE. OPERATOR is one of > or < VALUE is a date and time in ISO format in GMT =cut sub LimitTransactionDate { my $self = shift; my %args = ( FIELD => 'TransactionDate', VALUE => undef, OPERATOR => undef, @_ ); # <20021217042756.GK28744@pallas.fsck.com> # "Kill It" - Jesse. #Set the description if we didn't get handed it above unless ( $args{'DESCRIPTION'} ) { $args{'DESCRIPTION'} = $args{'FIELD'} . " " . $args{'OPERATOR'} . " " . $args{'VALUE'} . " GMT"; } $self->LimitField(%args); } =head2 LimitCustomField Takes a paramhash of key/value pairs with the following keys: =over 4 =item CUSTOMFIELD - CustomField name or id. If a name is passed, an additional parameter QUEUE may also be passed to distinguish the custom field. =item OPERATOR - The usual Limit operators =item VALUE - The value to compare against =back =cut sub LimitCustomField { my $self = shift; my %args = ( VALUE => undef, CUSTOMFIELD => undef, OPERATOR => '=', DESCRIPTION => undef, FIELD => 'CustomFieldValue', QUOTEVALUE => 1, @_ ); my $CF = RT::CustomField->new( $self->CurrentUser ); if ( $args{CUSTOMFIELD} =~ /^\d+$/ ) { $CF->Load( $args{CUSTOMFIELD} ); } else { $CF->LoadByName( Name => $args{CUSTOMFIELD}, LookupType => RT::Ticket->CustomFieldLookupType, ObjectId => $args{QUEUE}, ); $args{CUSTOMFIELD} = $CF->Id; } #If we are looking to compare with a null value. if ( $args{'OPERATOR'} =~ /^is$/i ) { $args{'DESCRIPTION'} ||= $self->loc( "Custom field [_1] has no value.", $CF->Name ); } elsif ( $args{'OPERATOR'} =~ /^is not$/i ) { $args{'DESCRIPTION'} ||= $self->loc( "Custom field [_1] has a value.", $CF->Name ); } # if we're not looking to compare with a null value else { $args{'DESCRIPTION'} ||= $self->loc( "Custom field [_1] [_2] [_3]", $CF->Name, $args{OPERATOR}, $args{VALUE} ); } if ( defined $args{'QUEUE'} && $args{'QUEUE'} =~ /\D/ ) { my $QueueObj = RT::Queue->new( $self->CurrentUser ); $QueueObj->Load( $args{'QUEUE'} ); $args{'QUEUE'} = $QueueObj->Id; } delete $args{'QUEUE'} unless defined $args{'QUEUE'} && length $args{'QUEUE'}; my @rest; @rest = ( ENTRYAGGREGATOR => 'AND' ) if ( $CF->Type eq 'SelectMultiple' ); $self->LimitField( VALUE => $args{VALUE}, FIELD => "CF" .(defined $args{'QUEUE'}? ".$args{'QUEUE'}" : '' ) .".{" . $CF->Name . "}", OPERATOR => $args{OPERATOR}, CUSTOMFIELD => 1, @rest, ); $self->{'RecalcTicketLimits'} = 1; } =head2 _NextIndex Keep track of the counter for the array of restrictions =cut sub _NextIndex { my $self = shift; return ( $self->{'restriction_index'}++ ); } sub _Init { my $self = shift; $self->{'table'} = "Tickets"; $self->{'RecalcTicketLimits'} = 1; $self->{'restriction_index'} = 1; $self->{'primary_key'} = "id"; delete $self->{'items_array'}; delete $self->{'item_map'}; delete $self->{'columns_to_display'}; $self->SUPER::_Init(@_); $self->_InitSQL(); } sub _InitSQL { my $self = shift; # Private Member Variables (which should get cleaned) $self->{'_sql_transalias'} = undef; $self->{'_sql_trattachalias'} = undef; $self->{'_sql_cf_alias'} = undef; $self->{'_sql_object_cfv_alias'} = undef; $self->{'_sql_watcher_join_users_alias'} = undef; $self->{'_sql_query'} = ''; $self->{'_sql_looking_at'} = {}; } sub Count { my $self = shift; $self->_ProcessRestrictions() if ( $self->{'RecalcTicketLimits'} == 1 ); return ( $self->SUPER::Count() ); } sub CountAll { my $self = shift; $self->_ProcessRestrictions() if ( $self->{'RecalcTicketLimits'} == 1 ); return ( $self->SUPER::CountAll() ); } =head2 ItemsArrayRef Returns a reference to the set of all items found in this search =cut sub ItemsArrayRef { my $self = shift; return $self->{'items_array'} if $self->{'items_array'}; my $placeholder = $self->_ItemsCounter; $self->GotoFirstItem(); while ( my $item = $self->Next ) { push( @{ $self->{'items_array'} }, $item ); } $self->GotoItem($placeholder); $self->{'items_array'} = $self->ItemsOrderBy( $self->{'items_array'} ); return $self->{'items_array'}; } sub ItemsArrayRefWindow { my $self = shift; my $window = shift; my @old = ($self->_ItemsCounter, $self->RowsPerPage, $self->FirstRow+1); $self->RowsPerPage( $window ); $self->FirstRow(1); $self->GotoFirstItem; my @res; while ( my $item = $self->Next ) { push @res, $item; } $self->RowsPerPage( $old[1] ); $self->FirstRow( $old[2] ); $self->GotoItem( $old[0] ); return \@res; } sub Next { my $self = shift; $self->_ProcessRestrictions() if ( $self->{'RecalcTicketLimits'} == 1 ); my $Ticket = $self->SUPER::Next; return $Ticket unless $Ticket; if ( $Ticket->__Value('Status') eq 'deleted' && !$self->{'allow_deleted_search'} ) { return $self->Next; } elsif ( RT->Config->Get('UseSQLForACLChecks') ) { # if we found a ticket with this option enabled then # all tickets we found are ACLed, cache this fact my $key = join ";:;", $self->CurrentUser->id, 'ShowTicket', 'RT::Ticket-'. $Ticket->id; $RT::Principal::_ACL_CACHE->{ $key } = 1; return $Ticket; } elsif ( $Ticket->CurrentUserHasRight('ShowTicket') ) { # has rights return $Ticket; } else { # If the user doesn't have the right to show this ticket return $self->Next; } } sub _DoSearch { my $self = shift; $self->CurrentUserCanSee if RT->Config->Get('UseSQLForACLChecks'); return $self->SUPER::_DoSearch( @_ ); } sub _DoCount { my $self = shift; $self->CurrentUserCanSee if RT->Config->Get('UseSQLForACLChecks'); return $self->SUPER::_DoCount( @_ ); } sub _RolesCanSee { my $self = shift; my $cache_key = 'RolesHasRight;:;ShowTicket'; if ( my $cached = $RT::Principal::_ACL_CACHE->{ $cache_key } ) { return %$cached; } my $ACL = RT::ACL->new( RT->SystemUser ); $ACL->Limit( FIELD => 'RightName', VALUE => 'ShowTicket' ); $ACL->Limit( FIELD => 'PrincipalType', OPERATOR => '!=', VALUE => 'Group' ); my $principal_alias = $ACL->Join( ALIAS1 => 'main', FIELD1 => 'PrincipalId', TABLE2 => 'Principals', FIELD2 => 'id', ); $ACL->Limit( ALIAS => $principal_alias, FIELD => 'Disabled', VALUE => 0 ); my %res = (); foreach my $ACE ( @{ $ACL->ItemsArrayRef } ) { my $role = $ACE->__Value('PrincipalType'); my $type = $ACE->__Value('ObjectType'); if ( $type eq 'RT::System' ) { $res{ $role } = 1; } elsif ( $type eq 'RT::Queue' ) { next if $res{ $role } && !ref $res{ $role }; push @{ $res{ $role } ||= [] }, $ACE->__Value('ObjectId'); } else { $RT::Logger->error('ShowTicket right is granted on unsupported object'); } } $RT::Principal::_ACL_CACHE->{ $cache_key } = \%res; return %res; } sub _DirectlyCanSeeIn { my $self = shift; my $id = $self->CurrentUser->id; my $cache_key = 'User-'. $id .';:;ShowTicket;:;DirectlyCanSeeIn'; if ( my $cached = $RT::Principal::_ACL_CACHE->{ $cache_key } ) { return @$cached; } my $ACL = RT::ACL->new( RT->SystemUser ); $ACL->Limit( FIELD => 'RightName', VALUE => 'ShowTicket' ); my $principal_alias = $ACL->Join( ALIAS1 => 'main', FIELD1 => 'PrincipalId', TABLE2 => 'Principals', FIELD2 => 'id', ); $ACL->Limit( ALIAS => $principal_alias, FIELD => 'Disabled', VALUE => 0 ); my $cgm_alias = $ACL->Join( ALIAS1 => 'main', FIELD1 => 'PrincipalId', TABLE2 => 'CachedGroupMembers', FIELD2 => 'GroupId', ); $ACL->Limit( ALIAS => $cgm_alias, FIELD => 'MemberId', VALUE => $id ); $ACL->Limit( ALIAS => $cgm_alias, FIELD => 'Disabled', VALUE => 0 ); my @res = (); foreach my $ACE ( @{ $ACL->ItemsArrayRef } ) { my $type = $ACE->__Value('ObjectType'); if ( $type eq 'RT::System' ) { # If user is direct member of a group that has the right # on the system then he can see any ticket $RT::Principal::_ACL_CACHE->{ $cache_key } = [-1]; return (-1); } elsif ( $type eq 'RT::Queue' ) { push @res, $ACE->__Value('ObjectId'); } else { $RT::Logger->error('ShowTicket right is granted on unsupported object'); } } $RT::Principal::_ACL_CACHE->{ $cache_key } = \@res; return @res; } sub CurrentUserCanSee { my $self = shift; return if $self->{'_sql_current_user_can_see_applied'}; return $self->{'_sql_current_user_can_see_applied'} = 1 if $self->CurrentUser->UserObj->HasRight( Right => 'SuperUser', Object => $RT::System ); local $self->{using_restrictions}; my $id = $self->CurrentUser->id; # directly can see in all queues then we have nothing to do my @direct_queues = $self->_DirectlyCanSeeIn; return $self->{'_sql_current_user_can_see_applied'} = 1 if @direct_queues && $direct_queues[0] == -1; my %roles = $self->_RolesCanSee; { my %skip = map { $_ => 1 } @direct_queues; foreach my $role ( keys %roles ) { next unless ref $roles{ $role }; my @queues = grep !$skip{$_}, @{ $roles{ $role } }; if ( @queues ) { $roles{ $role } = \@queues; } else { delete $roles{ $role }; } } } # there is no global watchers, only queues and tickes, if at # some point we will add global roles then it's gonna blow # the idea here is that if the right is set globaly for a role # and user plays this role for a queue directly not a ticket # then we have to check in advance if ( my @tmp = grep $_ ne 'Owner' && !ref $roles{ $_ }, keys %roles ) { my $groups = RT::Groups->new( RT->SystemUser ); $groups->Limit( FIELD => 'Domain', VALUE => 'RT::Queue-Role', CASESENSITIVE => 0 ); $groups->Limit( FIELD => 'Name', FUNCTION => 'LOWER(?)', OPERATOR => 'IN', VALUE => [ map {lc $_} @tmp ], CASESENSITIVE => 1, ); my $principal_alias = $groups->Join( ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'Principals', FIELD2 => 'id', ); $groups->Limit( ALIAS => $principal_alias, FIELD => 'Disabled', VALUE => 0 ); my $cgm_alias = $groups->Join( ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'CachedGroupMembers', FIELD2 => 'GroupId', ); $groups->Limit( ALIAS => $cgm_alias, FIELD => 'MemberId', VALUE => $id ); $groups->Limit( ALIAS => $cgm_alias, FIELD => 'Disabled', VALUE => 0 ); while ( my $group = $groups->Next ) { push @direct_queues, $group->Instance; } } unless ( @direct_queues || keys %roles ) { $self->Limit( SUBCLAUSE => 'ACL', ALIAS => 'main', FIELD => 'id', VALUE => 0, ENTRYAGGREGATOR => 'AND', ); return $self->{'_sql_current_user_can_see_applied'} = 1; } { my $join_roles = keys %roles; $join_roles = 0 if $join_roles == 1 && $roles{'Owner'}; my ($role_group_alias, $cgm_alias); if ( $join_roles ) { $role_group_alias = $self->_RoleGroupsJoin( New => 1 ); $cgm_alias = $self->_GroupMembersJoin( GroupsAlias => $role_group_alias ); $self->Limit( LEFTJOIN => $cgm_alias, FIELD => 'MemberId', OPERATOR => '=', VALUE => $id, ); } my $limit_queues = sub { my $ea = shift; my @queues = @_; return unless @queues; $self->Limit( SUBCLAUSE => 'ACL', ALIAS => 'main', FIELD => 'Queue', OPERATOR => 'IN', VALUE => [ @queues ], ENTRYAGGREGATOR => $ea, ); return 1; }; $self->SUPER::_OpenParen('ACL'); my $ea = 'AND'; $ea = 'OR' if $limit_queues->( $ea, @direct_queues ); while ( my ($role, $queues) = each %roles ) { $self->SUPER::_OpenParen('ACL'); if ( $role eq 'Owner' ) { $self->Limit( SUBCLAUSE => 'ACL', FIELD => 'Owner', VALUE => $id, ENTRYAGGREGATOR => $ea, ); } else { $self->Limit( SUBCLAUSE => 'ACL', ALIAS => $cgm_alias, FIELD => 'MemberId', OPERATOR => 'IS NOT', VALUE => 'NULL', QUOTEVALUE => 0, ENTRYAGGREGATOR => $ea, ); $self->Limit( SUBCLAUSE => 'ACL', ALIAS => $role_group_alias, FIELD => 'Name', VALUE => $role, ENTRYAGGREGATOR => 'AND', CASESENSITIVE => 0, ); } $limit_queues->( 'AND', @$queues ) if ref $queues; $ea = 'OR' if $ea eq 'AND'; $self->SUPER::_CloseParen('ACL'); } $self->SUPER::_CloseParen('ACL'); } return $self->{'_sql_current_user_can_see_applied'} = 1; } =head2 ClearRestrictions Removes all restrictions irretrievably =cut sub ClearRestrictions { my $self = shift; delete $self->{'TicketRestrictions'}; $self->{_sql_looking_at} = {}; $self->{'RecalcTicketLimits'} = 1; } # Convert a set of oldstyle SB Restrictions to Clauses for RQL sub _RestrictionsToClauses { my $self = shift; my %clause; foreach my $row ( keys %{ $self->{'TicketRestrictions'} } ) { my $restriction = $self->{'TicketRestrictions'}{$row}; # We need to reimplement the subclause aggregation that SearchBuilder does. # Default Subclause is ALIAS.FIELD, and default ALIAS is 'main', # Then SB AND's the different Subclauses together. # So, we want to group things into Subclauses, convert them to # SQL, and then join them with the appropriate DefaultEA. # Then join each subclause group with AND. my $field = $restriction->{'FIELD'}; my $realfield = $field; # CustomFields fake up a fieldname, so # we need to figure that out # One special case # Rewrite LinkedTo meta field to the real field if ( $field =~ /LinkedTo/ ) { $realfield = $field = $restriction->{'TYPE'}; } # Two special case # Handle subkey fields with a different real field if ( $field =~ /^(\w+)\./ ) { $realfield = $1; } die "I don't know about $field yet" unless ( exists $FIELD_METADATA{$realfield} or $restriction->{CUSTOMFIELD} ); my $type = $FIELD_METADATA{$realfield}->[0]; my $op = $restriction->{'OPERATOR'}; my $value = ( grep {defined} map { $restriction->{$_} } qw(VALUE TICKET BASE TARGET) )[0]; # this performs the moral equivalent of defined or/dor/C<//>, # without the short circuiting.You need to use a 'defined or' # type thing instead of just checking for truth values, because # VALUE could be 0.(i.e. "false") # You could also use this, but I find it less aesthetic: # (although it does short circuit) #( defined $restriction->{'VALUE'}? $restriction->{VALUE} : # defined $restriction->{'TICKET'} ? # $restriction->{TICKET} : # defined $restriction->{'BASE'} ? # $restriction->{BASE} : # defined $restriction->{'TARGET'} ? # $restriction->{TARGET} ) my $ea = $restriction->{ENTRYAGGREGATOR} || $DefaultEA{$type} || "AND"; if ( ref $ea ) { die "Invalid operator $op for $field ($type)" unless exists $ea->{$op}; $ea = $ea->{$op}; } # Each CustomField should be put into a different Clause so they # are ANDed together. if ( $restriction->{CUSTOMFIELD} ) { $realfield = $field; } exists $clause{$realfield} or $clause{$realfield} = []; # Escape Quotes $field =~ s!(['\\])!\\$1!g; $value =~ s!(['\\])!\\$1!g; my $data = [ $ea, $type, $field, $op, $value ]; # here is where we store extra data, say if it's a keyword or # something. (I.e. "TYPE SPECIFIC STUFF") if (lc $ea eq 'none') { $clause{$realfield} = [ $data ]; } else { push @{ $clause{$realfield} }, $data; } } return \%clause; } =head2 ClausesToSQL =cut sub ClausesToSQL { my $self = shift; my $clauses = shift; my @sql; for my $f (keys %{$clauses}) { my $sql; my $first = 1; # Build SQL from the data hash for my $data ( @{ $clauses->{$f} } ) { $sql .= $data->[0] unless $first; $first=0; # ENTRYAGGREGATOR $sql .= " '". $data->[2] . "' "; # FIELD $sql .= $data->[3] . " "; # OPERATOR $sql .= "'". $data->[4] . "' "; # VALUE } push @sql, " ( " . $sql . " ) "; } return join("AND",@sql); } sub _ProcessRestrictions { my $self = shift; delete $self->{'items_array'}; delete $self->{'item_map'}; delete $self->{'raw_rows'}; delete $self->{'count_all'}; my $sql = $self->Query; if ( !$sql || $self->{'RecalcTicketLimits'} ) { local $self->{using_restrictions}; # "Restrictions to Clauses Branch\n"; my $clauseRef = eval { $self->_RestrictionsToClauses; }; if ($@) { $RT::Logger->error( "RestrictionsToClauses: " . $@ ); $self->FromSQL(""); } else { $sql = $self->ClausesToSQL($clauseRef); $self->FromSQL($sql) if $sql; } } $self->{'RecalcTicketLimits'} = 0; } =head2 _BuildItemMap Build up a L</ItemMap> of first/last/next/prev items, so that we can display search nav quickly. =cut sub _BuildItemMap { my $self = shift; my $window = RT->Config->Get('TicketsItemMapSize'); $self->{'item_map'} = {}; my $items = $self->ItemsArrayRefWindow( $window ); return unless $items && @$items; my $prev = 0; $self->{'item_map'}{'first'} = $items->[0]->EffectiveId; for ( my $i = 0; $i < @$items; $i++ ) { my $item = $items->[$i]; my $id = $item->EffectiveId; $self->{'item_map'}{$id}{'defined'} = 1; $self->{'item_map'}{$id}{'prev'} = $prev; $self->{'item_map'}{$id}{'next'} = $items->[$i+1]->EffectiveId if $items->[$i+1]; $prev = $id; } $self->{'item_map'}{'last'} = $prev if !$window || @$items < $window; } =head2 ItemMap Returns an a map of all items found by this search. The map is a hash of the form: { first => <first ticket id found>, last => <last ticket id found or undef>, <ticket id> => { prev => <the ticket id found before>, next => <the ticket id found after>, }, <ticket id> => { prev => ..., next => ..., }, } =cut sub ItemMap { my $self = shift; $self->_BuildItemMap unless $self->{'item_map'}; return $self->{'item_map'}; } =head2 PrepForSerialization You don't want to serialize a big tickets object, as the {items} hash will be instantly invalid _and_ eat lots of space =cut sub PrepForSerialization { my $self = shift; delete $self->{'items'}; delete $self->{'items_array'}; $self->RedoSearch(); } =head1 FLAGS RT::Tickets supports several flags which alter search behavior: allow_deleted_search (Otherwise never show deleted tickets in search results) These flags are set by calling $tickets->{'flagname'} = 1; BUG: There should be an API for this =cut =head2 FromSQL Convert a RT-SQL string into a set of SearchBuilder restrictions. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =cut sub _parser { my ($self,$string) = @_; require RT::Interface::Web::QueryBuilder::Tree; my $tree = RT::Interface::Web::QueryBuilder::Tree->new; my @results = $tree->ParseSQL( Query => $string, CurrentUser => $self->CurrentUser, ); die join "; ", map { ref $_ eq 'ARRAY' ? $_->[ 0 ] : $_ } @results if @results; my ( $active_status_node, $inactive_status_node ); my $escape_quotes = sub { my $text = shift; $text =~ s{(['\\])}{\\$1}g; return $text; }; $tree->traverse( sub { my $node = shift; return unless $node->isLeaf and $node->getNodeValue; my ($key, $subkey, $meta, $op, $value, $bundle) = @{$node->getNodeValue}{qw/Key Subkey Meta Op Value Bundle/}; return unless $key eq "Status" && $value =~ /^(?:__(?:in)?active__)$/i; my $parent = $node->getParent; my $index = $node->getIndex; if ( ( lc $value eq '__inactive__' && $op eq '=' ) || ( lc $value eq '__active__' && $op eq '!=' ) ) { unless ( $inactive_status_node ) { my %lifecycle = map { $_ => $RT::Lifecycle::LIFECYCLES{ $_ }{ inactive } } grep { @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ inactive } || [] } } grep { $_ ne '__maps__' && $RT::Lifecycle::LIFECYCLES_CACHE{ $_ }{ type } eq 'ticket' } keys %RT::Lifecycle::LIFECYCLES; return unless %lifecycle; my $sql; if ( keys %lifecycle == 1 ) { $sql = join ' OR ', map { qq{ Status = '$_' } } map { $escape_quotes->($_) } map { @$_ } values %lifecycle; } else { my @inactive_sql; for my $name ( keys %lifecycle ) { my $escaped_name = $escape_quotes->($name); my $inactive_sql = qq{Lifecycle = '$escaped_name'} . ' AND (' . join( ' OR ', map { qq{ Status = '$_' } } map { $escape_quotes->($_) } @{ $lifecycle{ $name } } ) . ')'; push @inactive_sql, qq{($inactive_sql)}; } $sql = join ' OR ', @inactive_sql; } $inactive_status_node = RT::Interface::Web::QueryBuilder::Tree->new; $inactive_status_node->ParseSQL( Query => $sql, CurrentUser => $self->CurrentUser, ); } $parent->removeChild( $node ); $parent->insertChild( $index, $inactive_status_node ); } else { unless ( $active_status_node ) { my %lifecycle = map { $_ => [ @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ initial } || [] }, @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ active } || [] }, ] } grep { @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ initial } || [] } || @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ active } || [] } } grep { $_ ne '__maps__' && $RT::Lifecycle::LIFECYCLES_CACHE{ $_ }{ type } eq 'ticket' } keys %RT::Lifecycle::LIFECYCLES; return unless %lifecycle; my $sql; if ( keys %lifecycle == 1 ) { $sql = join ' OR ', map { qq{ Status = '$_' } } map { $escape_quotes->($_) } map { @$_ } values %lifecycle; } else { my @active_sql; for my $name ( keys %lifecycle ) { my $escaped_name = $escape_quotes->($name); my $active_sql = qq{Lifecycle = '$escaped_name'} . ' AND (' . join( ' OR ', map { qq{ Status = '$_' } } map { $escape_quotes->($_) } @{ $lifecycle{ $name } } ) . ')'; push @active_sql, qq{($active_sql)}; } $sql = join ' OR ', @active_sql; } $active_status_node = RT::Interface::Web::QueryBuilder::Tree->new; $active_status_node->ParseSQL( Query => $sql, CurrentUser => $self->CurrentUser, ); } $parent->removeChild( $node ); $parent->insertChild( $index, $active_status_node ); } } ); if ( RT->Config->Get('EnablePriorityAsString') ) { my $queues = $tree->GetReferencedQueues( CurrentUser => $self->CurrentUser ); my %config = RT->Config->Get('PriorityAsString'); my @names; if (%$queues) { for my $id ( keys %$queues ) { my $queue = RT::Queue->new( $self->CurrentUser ); $queue->Load($id); if ( $queue->Id ) { push @names, $queue->__Value('Name'); # Skip ACL check } } } else { @names = keys %config; } my %map; for my $name (@names) { if ( my $value = exists $config{$name} ? $config{$name} : $config{Default} ) { my %hash = ref $value eq 'ARRAY' ? @$value : %$value; for my $label ( keys %hash ) { $map{lc $label} //= $hash{$label}; } } } $tree->traverse( sub { my $node = shift; return unless $node->isLeaf; my $value = $node->getNodeValue; if ( $value->{Key} =~ /^(?:Initial|Final)?Priority$/i ) { $value->{Value} = $map{ lc $value->{Value} } if defined $map{ lc $value->{Value} }; } } ); } # Perform an optimization pass looking for watcher bundling $tree->traverse( sub { my $node = shift; return if $node->isLeaf; return unless ($node->getNodeValue||'') eq "OR"; my %refs; my @kids = grep {$_->{Meta}[0] eq "WATCHERFIELD"} map {$_->getNodeValue} grep {$_->isLeaf} $node->getAllChildren; for (@kids) { my $node = $_; my ($key, $subkey, $op) = @{$node}{qw/Key Subkey Op/}; next if $node->{Meta}[1] and RT::Ticket->Role($node->{Meta}[1])->{Column}; next if $op =~ /^!=$|\bNOT\b/i; next if $op =~ /^IS( NOT)?$/i and not $subkey; $node->{Bundle} = $refs{$node->{Meta}[1] || ''} ||= []; } } ); my $ea = ''; $tree->traverse( sub { my $node = shift; $ea = $node->getParent->getNodeValue if $node->getIndex > 0; return $self->_OpenParen unless $node->isLeaf; my ($key, $subkey, $meta, $op, $value, $bundle) = @{$node->getNodeValue}{qw/Key Subkey Meta Op Value Bundle/}; # normalize key and get class (type) my $class = $meta->[0]; # replace __CurrentUser__ with id $value = $self->CurrentUser->id if $value eq '__CurrentUser__'; # replace __CurrentUserName__ with the username $value = $self->CurrentUser->Name if $value eq '__CurrentUserName__'; my $sub = $dispatch{ $class } or die "No dispatch method for class '$class'"; # A reference to @res may be pushed onto $sub_tree{$key} from # above, and we fill it here. $sub->( $self, $key, $op, $value, ENTRYAGGREGATOR => $ea, SUBKEY => $subkey, BUNDLE => $bundle, ); }, sub { my $node = shift; return $self->_CloseParen unless $node->isLeaf; } ); } sub FromSQL { my ($self,$query) = @_; { # preserve first_row and show_rows across the CleanSlate local ($self->{'first_row'}, $self->{'show_rows'}, $self->{_sql_looking_at}); $self->CleanSlate; $self->_InitSQL(); } return (1, $self->loc("No Query")) unless $query; $self->{_sql_query} = $query; eval { local $self->{parsing_ticketsql} = 1; $self->_parser( $query ); }; if ( $@ ) { my $error = "$@"; $RT::Logger->error("Couldn't parse query: $error"); return (0, $error); } # We only want to look at EffectiveId's (mostly) for these searches. unless ( $self->{_sql_looking_at}{effectiveid} ) { # instead of EffectiveId = id we do IsMerged IS NULL $self->Limit( FIELD => 'IsMerged', OPERATOR => 'IS', VALUE => 'NULL', ENTRYAGGREGATOR => 'AND', QUOTEVALUE => 0, ); } unless ( $self->{_sql_looking_at}{type} ) { $self->Limit( FIELD => 'Type', VALUE => 'ticket' ); } # We don't want deleted tickets unless 'allow_deleted_search' is set unless( $self->{'allow_deleted_search'} ) { $self->Limit( FIELD => 'Status', OPERATOR => '!=', VALUE => 'deleted', ); } # set SB's dirty flag $self->{'must_redo_search'} = 1; $self->{'RecalcTicketLimits'} = 0; return (1, $self->loc("Valid Query")); } =head2 Query Returns the last string passed to L</FromSQL>. =cut sub Query { my $self = shift; return $self->{_sql_query}; } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/��������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 015774� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Queue.pm����������������������������������������������������������������������������000644 �000765 �000024 �00000106253 14005011336 015525� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Queue - an RT Queue object =head1 SYNOPSIS use RT::Queue; =head1 DESCRIPTION An RT queue object. =head1 METHODS =cut package RT::Queue; use strict; use warnings; use base 'RT::Record'; use Role::Basic 'with'; with "RT::Record::Role::Lifecycle", "RT::Record::Role::Links" => { -excludes => ["_AddLinksOnCreate"] }, "RT::Record::Role::Roles", "RT::Record::Role::Rights"; sub Table {'Queues'} sub LifecycleType { "ticket" } sub ModifyLinkRight { "AdminQueue" } require RT::ACE; RT::ACE->RegisterCacheHandler(sub { my %args = ( Action => "", RightName => "", @_ ); return unless $args{Action} =~ /^(Grant|Revoke)$/i and $args{RightName} =~ /^(SeeQueue|CreateTicket|AdminQueue|ShowTicket|SuperUser)$/; RT->System->QueueCacheNeedsUpdate(1); }); use RT::Groups; use RT::CustomRoles; use RT::ACL; use RT::Interface::Email; __PACKAGE__->AddRight( General => SeeQueue => 'View queue' ); # loc __PACKAGE__->AddRight( Admin => AdminQueue => 'Create, modify and delete queue' ); # loc __PACKAGE__->AddRight( Admin => ShowACL => 'Display Access Control List' ); # loc __PACKAGE__->AddRight( Admin => ModifyACL => 'Create, modify and delete Access Control List entries' ); # loc __PACKAGE__->AddRight( Admin => ModifyQueueWatchers => 'Modify queue watchers' ); # loc __PACKAGE__->AddRight( General => SeeCustomField => 'View custom field values' ); # loc __PACKAGE__->AddRight( Staff => ModifyCustomField => 'Modify custom field values' ); # loc __PACKAGE__->AddRight( Staff => SetInitialCustomField => 'Add custom field values only at object creation time'); # loc __PACKAGE__->AddRight( Admin => AssignCustomFields => 'Assign and remove queue custom fields' ); # loc __PACKAGE__->AddRight( Admin => ModifyTemplate => 'Modify Scrip templates' ); # loc __PACKAGE__->AddRight( Admin => ShowTemplate => 'View Scrip templates' ); # loc __PACKAGE__->AddRight( Admin => ModifyScrips => 'Modify Scrips' ); # loc __PACKAGE__->AddRight( Admin => ShowScrips => 'View Scrips' ); # loc __PACKAGE__->AddRight( General => ShowTicket => 'View ticket summaries' ); # loc __PACKAGE__->AddRight( Staff => ShowTicketComments => 'View ticket private commentary' ); # loc __PACKAGE__->AddRight( Staff => ShowOutgoingEmail => 'View exact outgoing email messages and their recipients' ); # loc __PACKAGE__->AddRight( General => Watch => 'Sign up as a ticket Requestor or ticket or queue Cc' ); # loc __PACKAGE__->AddRight( Staff => WatchAsAdminCc => 'Sign up as a ticket or queue AdminCc' ); # loc __PACKAGE__->AddRight( General => CreateTicket => 'Create tickets' ); # loc __PACKAGE__->AddRight( General => ReplyToTicket => 'Reply to tickets' ); # loc __PACKAGE__->AddRight( General => CommentOnTicket => 'Comment on tickets' ); # loc __PACKAGE__->AddRight( Staff => OwnTicket => 'Own tickets' ); # loc __PACKAGE__->AddRight( Staff => ModifyTicket => 'Modify tickets' ); # loc __PACKAGE__->AddRight( Staff => DeleteTicket => 'Delete tickets' ); # loc __PACKAGE__->AddRight( Staff => TakeTicket => 'Take tickets' ); # loc __PACKAGE__->AddRight( Staff => StealTicket => 'Steal tickets' ); # loc __PACKAGE__->AddRight( Staff => ReassignTicket => 'Modify ticket owner on owned tickets' ); # loc __PACKAGE__->AddRight( Staff => ForwardMessage => 'Forward messages outside of RT' ); # loc =head2 Create(ARGS) Arguments: ARGS is a hash of named parameters. Valid parameters are: Name (required) Description CorrespondAddress CommentAddress If you pass the ACL check, it creates the queue and returns its queue id. =cut sub Create { my $self = shift; my %args = ( Name => undef, Description => '', CorrespondAddress => '', CommentAddress => '', Lifecycle => 'default', SubjectTag => undef, Sign => undef, SignAuto => undef, Encrypt => undef, SLA => undef, _RecordTransaction => 1, @_ ); unless ( $self->CurrentUser->HasRight(Right => 'AdminQueue', Object => $RT::System) ) { #Check them ACLs return ( 0, $self->loc("No permission to create queues") ); } { my ($val, $msg) = $self->_ValidateName( $args{'Name'} ); return ($val, $msg) unless $val; } $args{'Lifecycle'} ||= 'default'; return ( 0, $self->loc('[_1] is not a valid lifecycle', $args{'Lifecycle'} ) ) unless $self->ValidateLifecycle( $args{'Lifecycle'} ); my %attrs = map {$_ => 1} $self->ReadableAttributes; #TODO better input validation $RT::Handle->BeginTransaction(); my $id = $self->SUPER::Create( map { $_ => $args{$_} } grep exists $args{$_}, keys %attrs ); unless ($id) { $RT::Handle->Rollback(); return ( 0, $self->loc('Queue could not be created') ); } my $create_ret = $self->_CreateRoleGroups(); unless ($create_ret) { $RT::Handle->Rollback(); return ( 0, $self->loc('Queue could not be created') ); } if ( $args{'_RecordTransaction'} ) { $self->_NewTransaction( Type => "Create" ); } $RT::Handle->Commit; for my $attr (qw/Sign SignAuto Encrypt SLA/) { next unless defined $args{$attr}; my $set = "Set" . $attr; my ($status, $msg) = $self->$set( $args{$attr} ); $RT::Logger->error("Couldn't set attribute '$attr': $msg") unless $status; } RT->System->QueueCacheNeedsUpdate(1); return ( $id, $self->loc("Queue created") ); } sub Delete { my $self = shift; return ( 0, $self->loc('Deleting this object would break referential integrity') ); } =head2 Load Takes either a numerical id or a textual Name and loads the specified queue. =cut sub Load { my $self = shift; my $identifier = shift; if ( !$identifier ) { return (undef); } if ( $identifier =~ /^(\d+)$/ ) { $self->SUPER::LoadById($identifier); } else { $self->LoadByCols( Name => $identifier ); } return ( $self->Id ); } =head2 ValidateName NAME Takes a queue name. Returns true if it's an ok name for a new queue. Returns undef if there's already a queue by that name. =cut sub ValidateName { my $self = shift; my $name = shift; my ($ok, $msg) = $self->_ValidateName($name); return $ok ? 1 : 0; } sub _ValidateName { my $self = shift; my $name = shift; return (undef, "Queue name is required") unless length $name; # Validate via the superclass first # Case: short circuit if it's an integer so we don't have # fale negatives when loading a temp queue unless ( my $q = $self->SUPER::ValidateName($name) ) { return ($q, $self->loc("'[_1]' is not a valid name.", $name)); } my $tempqueue = RT::Queue->new(RT->SystemUser); $tempqueue->Load($name); #If this queue exists, return undef if ( $tempqueue->Name() && ( !$self->id || $tempqueue->id != $self->id ) ) { return (undef, $self->loc("Queue already exists") ); } return (1); } =head2 SetSign =cut sub Sign { my $self = shift; my $value = shift; return undef unless $self->CurrentUserHasRight('SeeQueue'); my $attr = $self->FirstAttribute('Sign') or return 0; return $attr->Content; } sub SetSign { my $self = shift; my $value = shift; return ( 0, $self->loc('Permission Denied') ) unless $self->CurrentUserHasRight('AdminQueue'); my ($status, $msg) = $self->SetAttribute( Name => 'Sign', Description => 'Sign outgoing messages by default', Content => $value, ); return ($status, $msg) unless $status; my ( undef, undef, $TransObj ) = $self->_NewTransaction( Field => 'Signing', #loc Type => $value ? "Enabled" : "Disabled" ); return ($status, scalar $TransObj->BriefDescription); } sub SignAuto { my $self = shift; my $value = shift; return undef unless $self->CurrentUserHasRight('SeeQueue'); my $attr = $self->FirstAttribute('SignAuto') or return 0; return $attr->Content; } sub SetSignAuto { my $self = shift; my $value = shift; return ( 0, $self->loc('Permission Denied') ) unless $self->CurrentUserHasRight('AdminQueue'); my ($status, $msg) = $self->SetAttribute( Name => 'SignAuto', Description => 'Sign auto-generated outgoing messages', Content => $value, ); return ($status, $msg) unless $status; my ( undef, undef, $TransObj ) = $self->_NewTransaction( Field => 'AutoSigning', #loc Type => $value ? "Enabled" : "Disabled" ); return ($status, scalar $TransObj->BriefDescription); } sub Encrypt { my $self = shift; my $value = shift; return undef unless $self->CurrentUserHasRight('SeeQueue'); my $attr = $self->FirstAttribute('Encrypt') or return 0; return $attr->Content; } sub SetEncrypt { my $self = shift; my $value = shift; return ( 0, $self->loc('Permission Denied') ) unless $self->CurrentUserHasRight('AdminQueue'); my ($status, $msg) = $self->SetAttribute( Name => 'Encrypt', Description => 'Encrypt outgoing messages by default', Content => $value, ); return ($status, $msg) unless $status; my ( undef, undef, $TransObj ) = $self->_NewTransaction( Field => 'Encrypting', #loc Type => $value ? "Enabled" : "Disabled" ); return ($status, scalar $TransObj->BriefDescription); } =head2 Templates Returns an RT::Templates object of all of this queue's templates. =cut sub Templates { my $self = shift; my $templates = RT::Templates->new( $self->CurrentUser ); if ( $self->CurrentUserHasRight('ShowTemplate') ) { $templates->LimitToQueue( $self->id ); } return ($templates); } =head2 CustomField NAME Load the Ticket Custom Field applied to this Queue named NAME. Does not load Global custom fields. =cut sub CustomField { my $self = shift; my $name = shift; my $cf = RT::CustomField->new($self->CurrentUser); $cf->LoadByName( Name => $name, LookupType => RT::Ticket->CustomFieldLookupType, ObjectId => $self->id, ); return ($cf); } =head2 TicketCustomFields Returns an L<RT::CustomFields> object containing all global and queue-specific B<ticket> custom fields. =cut sub TicketCustomFields { my $self = shift; my $cfs = RT::CustomFields->new( $self->CurrentUser ); if ( $self->CurrentUserHasRight('SeeQueue') ) { $cfs->SetContextObject( $self ); $cfs->LimitToGlobalOrObjectId( $self->Id ); $cfs->LimitToLookupType( 'RT::Queue-RT::Ticket' ); $cfs->ApplySortOrder; } return ($cfs); } =head2 TicketTransactionCustomFields Returns an L<RT::CustomFields> object containing all global and queue-specific B<transaction> custom fields. =cut sub TicketTransactionCustomFields { my $self = shift; my $cfs = RT::CustomFields->new( $self->CurrentUser ); if ( $self->CurrentUserHasRight('SeeQueue') ) { $cfs->SetContextObject( $self ); $cfs->LimitToGlobalOrObjectId( $self->Id ); $cfs->LimitToLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' ); $cfs->ApplySortOrder; } return ($cfs); } =head2 CustomRoles Returns an L<RT::CustomRoles> object containing all queue-specific roles. =cut sub CustomRoles { my $self = shift; my $roles = RT::CustomRoles->new( $self->CurrentUser ); if ( $self->CurrentUserHasRight('SeeQueue') ) { $roles->LimitToObjectId( $self->Id ); $roles->ApplySortOrder; } else { $roles->Limit( FIELD => 'id', VALUE => 0, SUBCLAUSE => 'ACL' ); } return ($roles); } =head2 ManageableRoleGroupTypes Returns a list of the names of the various role group types for Queues, excluding ones used only for ACLs such as Requestor and Owner. If you want them, see L</Roles>. =cut sub ManageableRoleGroupTypes { shift->Roles( ACLOnly => 0 ) } =head2 IsManageableRoleGroupType Returns whether the passed-in type is a manageable role group type. =cut sub IsManageableRoleGroupType { my $self = shift; my $type = shift; return grep { $type eq $_ } $self->ManageableRoleGroupTypes; } sub _HasModifyWatcherRight { my $self = shift; my ($type, $principal) = @_; # ModifyQueueWatchers works in any case return 1 if $self->CurrentUserHasRight('ModifyQueueWatchers'); # If the watcher isn't the current user then the current user has no right return 0 unless $self->CurrentUser->PrincipalId == $principal->id; # If it's an AdminCc and they have 'WatchAsAdminCc', they can modify return 1 if $type eq 'AdminCc' and $self->CurrentUserHasRight('WatchAsAdminCc'); # If it's a Requestor or Cc and they have 'Watch', they can modify return 1 if ($type eq "Cc" or $type eq 'Requestor') and $self->CurrentUserHasRight('Watch'); # Unknown type, so default to denied. return 0; } =head2 AddWatcher Applies access control checking, then calls L<RT::Record::Role::Roles/AddRoleMember>. Additionally, C<Email> is accepted as an alternative argument name for C<User>. Returns a tuple of (status, message). =cut sub AddWatcher { my $self = shift; my %args = ( Type => undef, PrincipalId => undef, Email => undef, @_ ); $args{ACL} = sub { $self->_HasModifyWatcherRight( @_ ) }; $args{User} ||= delete $args{Email}; my ($principal, $msg) = $self->AddRoleMember( %args ); return ( 0, $msg) unless $principal; my $group = $self->RoleGroup( $args{Type} ); return ( 1, $self->loc("Added [_1] as [_2] for this queue", $principal->Object->Name, $group->Label )); } =head2 DeleteWatcher Applies access control checking, then calls L<RT::Record::Role::Roles/DeleteRoleMember>. Additionally, C<Email> is accepted as an alternative argument name for C<User>. Returns a tuple of (status, message). =cut sub DeleteWatcher { my $self = shift; my %args = ( Type => undef, PrincipalId => undef, Email => undef, @_ ); $args{ACL} = sub { $self->_HasModifyWatcherRight( @_ ) }; $args{User} ||= delete $args{Email}; my ($principal, $msg) = $self->DeleteRoleMember( %args ); return ( 0, $msg) unless $principal; my $group = $self->RoleGroup( $args{Type} ); return ( 1, $self->loc("[_1] is no longer [_2] for this queue", $principal->Object->Name, $group->Label )); } =head2 AdminCcAddresses returns String: All queue AdminCc email addresses as a string =cut sub AdminCcAddresses { my $self = shift; unless ( $self->CurrentUserHasRight('SeeQueue') ) { return undef; } return ( $self->AdminCc->MemberEmailAddressesAsString ) } =head2 CcAddresses returns String: All queue Ccs as a string of email addresses =cut sub CcAddresses { my $self = shift; unless ( $self->CurrentUserHasRight('SeeQueue') ) { return undef; } return ( $self->Cc->MemberEmailAddressesAsString); } =head2 Cc Takes nothing. Returns an RT::Group object which contains this Queue's Ccs. If the user doesn't have "ShowQueue" permission, returns an empty group =cut sub Cc { my $self = shift; return $self->RoleGroup( 'Cc', CheckRight => 'SeeQueue' ); } =head2 AdminCc Takes nothing. Returns an RT::Group object which contains this Queue's AdminCcs. If the user doesn't have "ShowQueue" permission, returns an empty group =cut sub AdminCc { my $self = shift; return $self->RoleGroup( 'AdminCc', CheckRight => 'SeeQueue' ); } # a generic routine to be called by IsRequestor, IsCc and IsAdminCc =head2 IsWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID } Takes a param hash with the attributes Type and PrincipalId Type is one of Requestor, Cc, AdminCc and Owner PrincipalId is an RT::Principal id Returns true if that principal is a member of the group Type for this queue =cut sub IsWatcher { my $self = shift; my %args = ( Type => 'Cc', PrincipalId => undef, @_ ); # Load the relevant group. my $group = $self->RoleGroup( $args{'Type'} ); # Ask if it has the member in question my $principal = RT::Principal->new($self->CurrentUser); $principal->Load($args{'PrincipalId'}); unless ($principal->Id) { return (undef); } return ($group->HasMemberRecursively($principal)); } =head2 IsCc PRINCIPAL_ID Takes an RT::Principal id. Returns true if the principal is a requestor of the current queue. =cut sub IsCc { my $self = shift; my $cc = shift; return ( $self->IsWatcher( Type => 'Cc', PrincipalId => $cc ) ); } =head2 IsAdminCc PRINCIPAL_ID Takes an RT::Principal id. Returns true if the principal is a requestor of the current queue. =cut sub IsAdminCc { my $self = shift; my $person = shift; return ( $self->IsWatcher( Type => 'AdminCc', PrincipalId => $person ) ); } sub _Set { my $self = shift; my %args = ( Field => undef, Value => undef, TransactionType => 'Set', RecordTransaction => 1, @_ ); unless ( $self->CurrentUserHasRight('AdminQueue') ) { return ( 0, $self->loc('Permission Denied') ); } my $Old = $self->SUPER::_Value("$args{'Field'}"); my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'}, Value => $args{'Value'}, ); if ( $ret == 0 ) { return ( 0, $msg ); } RT->System->QueueCacheNeedsUpdate(1); if ( $args{'RecordTransaction'} == 1 ) { if ($args{'Field'} eq 'Disabled') { $args{'TransactionType'} = ($args{'Value'} == 1) ? "Disabled" : "Enabled"; delete $args{'Field'}; } my ( undef, undef, $TransObj ) = $self->_NewTransaction( Type => $args{'TransactionType'}, Field => $args{'Field'}, NewValue => $args{'Value'}, OldValue => $Old, TimeTaken => $args{'TimeTaken'}, ); } return ( $ret, $msg ); } sub Lifecycle { my $self = shift; my $context_obj = shift; if ( $context_obj && $context_obj->QueueObj->Id eq $self->Id && $context_obj->CurrentUserHasRight('SeeQueue') ) { return ( $self->__Value('Lifecycle') ); } return ( $self->_Value('Lifecycle') ); } sub _Value { my $self = shift; unless ( $self->CurrentUserHasRight('SeeQueue') ) { return (undef); } return ( $self->__Value(@_) ); } =head2 CurrentUserCanSee Returns true if the current user can see the queue, using SeeQueue =cut sub CurrentUserCanSee { my $self = shift; return $self->CurrentUserHasRight('SeeQueue'); } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 Name Returns the current value of Name. (In the database, Name is stored as varchar(200).) =head2 SetName VALUE Set Name to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Name will be stored as a varchar(200).) =cut =head2 Description Returns the current value of Description. (In the database, Description is stored as varchar(255).) =head2 SetDescription VALUE Set Description to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Description will be stored as a varchar(255).) =cut =head2 CorrespondAddress Returns the current value of CorrespondAddress. (In the database, CorrespondAddress is stored as varchar(120).) =head2 SetCorrespondAddress VALUE Set CorrespondAddress to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, CorrespondAddress will be stored as a varchar(120).) =cut =head2 CommentAddress Returns the current value of CommentAddress. (In the database, CommentAddress is stored as varchar(120).) =head2 SetCommentAddress VALUE Set CommentAddress to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, CommentAddress will be stored as a varchar(120).) =cut =head2 Lifecycle [CONTEXT_OBJ] Returns the current value of Lifecycle. Provide an optional ticket object as context to check role-level rights in addition to queue-level rights for SeeQueue. (In the database, Lifecycle is stored as varchar(32).) =head2 SetLifecycle VALUE Set Lifecycle to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Lifecycle will be stored as a varchar(32).) =cut =head2 SubjectTag Returns the current value of SubjectTag. (In the database, SubjectTag is stored as varchar(120).) =head2 SetSubjectTag VALUE Set SubjectTag to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, SubjectTag will be stored as a varchar(120).) =cut =head2 SortOrder Returns the current value of SortOrder. (In the database, SortOrder is stored as int(11).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut =head2 Disabled Returns the current value of Disabled. (In the database, Disabled is stored as smallint(6).) =head2 SetDisabled VALUE Set Disabled to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Disabled will be stored as a smallint(6).) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Name => {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, Description => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, CorrespondAddress => {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''}, CommentAddress => {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''}, SubjectTag => {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''}, Lifecycle => {read => 1, write => 1, sql_type => 12, length => 32, is_blob => 0, is_numeric => 0, type => 'varchar(32)', default => 'default'}, SortOrder => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, SLADisabled => {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '1'}, Disabled => {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); # Queue role groups( Cc, AdminCc ) my $objs = RT::Groups->new( $self->CurrentUser ); $objs->Limit( FIELD => 'Domain', VALUE => 'RT::Queue-Role', CASESENSITIVE => 0 ); $objs->Limit( FIELD => 'Instance', VALUE => $self->Id ); $deps->Add( in => $objs ); # Scrips $objs = RT::ObjectScrips->new( $self->CurrentUser ); $objs->Limit( FIELD => 'ObjectId', OPERATOR => '=', VALUE => $self->id, ENTRYAGGREGATOR => 'OR' ); $objs->Limit( FIELD => 'ObjectId', OPERATOR => '=', VALUE => 0, ENTRYAGGREGATOR => 'OR' ); $deps->Add( in => $objs ); # Templates (global ones have already been dealt with) $objs = RT::Templates->new( $self->CurrentUser ); $objs->Limit( FIELD => 'Queue', VALUE => $self->Id); $deps->Add( in => $objs ); # Custom Fields on things _in_ this queue (CFs on the queue itself # have already been dealt with) $objs = RT::ObjectCustomFields->new( $self->CurrentUser ); $objs->Limit( FIELD => 'ObjectId', OPERATOR => '=', VALUE => $self->id, ENTRYAGGREGATOR => 'OR' ); $objs->Limit( FIELD => 'ObjectId', OPERATOR => '=', VALUE => 0, ENTRYAGGREGATOR => 'OR' ); my $cfs = $objs->Join( ALIAS1 => 'main', FIELD1 => 'CustomField', TABLE2 => 'CustomFields', FIELD2 => 'id', ); $objs->Limit( ALIAS => $cfs, FIELD => 'LookupType', OPERATOR => 'STARTSWITH', VALUE => 'RT::Queue-' ); $deps->Add( in => $objs ); # Tickets (skipped early as an optimization) if ($walker->{FollowTickets} || !defined($walker->{FollowTickets})) { $objs = RT::Tickets->new( $self->CurrentUser ); $objs->Limit( FIELD => "Queue", VALUE => $self->Id ); $objs->{allow_deleted_search} = 1; $deps->Add( in => $objs ); } # Object Custom Roles $objs = RT::ObjectCustomRoles->new( $self->CurrentUser ); $objs->LimitToObjectId($self->Id); $deps->Add( in => $objs ); } sub __DependsOn { my $self = shift; my %args = ( Shredder => undef, Dependencies => undef, @_, ); my $deps = $args{'Dependencies'}; my $list = []; # Tickets my $objs = RT::Tickets->new( $self->CurrentUser ); $objs->{'allow_deleted_search'} = 1; $objs->Limit( FIELD => 'Queue', VALUE => $self->Id ); push( @$list, $objs ); # Queue role groups( Cc, AdminCc ) $objs = RT::Groups->new( $self->CurrentUser ); $objs->Limit( FIELD => 'Domain', VALUE => 'RT::Queue-Role', CASESENSITIVE => 0 ); $objs->Limit( FIELD => 'Instance', VALUE => $self->Id ); push( @$list, $objs ); # Scrips $objs = RT::ObjectScrips->new( $self->CurrentUser ); $objs->LimitToObjectId( $self->id ); push( @$list, $objs ); # Templates $objs = $self->Templates; push( @$list, $objs ); # Object Custom Fields $objs = RT::ObjectCustomFields->new( $self->CurrentUser ); $objs->LimitToObjectId( $self->id ); push( @$list, $objs ); # Object Custom Roles $objs = RT::ObjectCustomRoles->new( $self->CurrentUser ); $objs->LimitToObjectId($self->Id); push( @$list, $objs ); $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON, TargetObjects => $list, Shredder => $args{'Shredder'} ); return $self->SUPER::__DependsOn( %args ); } sub PreInflate { my $class = shift; my ($importer, $uid, $data) = @_; $class->SUPER::PreInflate( $importer, $uid, $data ); $data->{Name} = $importer->Qualify($data->{Name}) if $data->{Name} ne "___Approvals"; return if $importer->MergeBy( "Name", $class, $uid, $data ); return 1; } sub DefaultValue { my $self = shift; my $field = shift; my $attr = $self->FirstAttribute('DefaultValues'); return undef unless $attr && $attr->Content; return $attr->Content->{$field}; } sub SetDefaultValue { my $self = shift; my %args = ( Name => undef, Value => undef, @_ ); my $field = shift; my $attr = $self->FirstAttribute('DefaultValues'); my ($old_value, $old_content, $new_value); if ( $attr && $attr->Content ) { $old_content = $attr->Content; $old_value = $old_content->{$args{Name}}; } unless ( defined $old_value && length $old_value ) { $old_value = $self->loc('(no value)'); } $new_value = $args{Value}; unless ( defined $new_value && length $new_value ) { $new_value = $self->loc( '(no value)' ); } if ( $args{Name} eq 'Article' && $args{Value} ) { my $article = RT::Article->new($self->CurrentUser); my ($ret, $msg); if ( $args{Value} =~ /^\d+$/ ) { ($ret, $msg) = $article->Load( $args{Value} ); } else { ($ret, $msg) = $article->LoadByCols( Name => $args{Value} ); } return ($ret, $msg ) unless $ret; $args{Value} = $article->Id; $new_value = $article->Name; } if ( $args{Name} eq 'Article' && $old_value =~ /^\d+$/ ) { my $article = RT::Article->new($self->CurrentUser); my ($ret, $msg) = $article->Load( $old_value ); if ($ret) { $old_value = $article->Name; } } return 1 if $new_value eq $old_value; my ($ret, $msg) = $self->SetAttribute( Name => 'DefaultValues', Content => { %{ $old_content || {} }, $args{Name} => $args{Value}, }, ); if ( $args{Name} =~ /Priority/ && RT->Config->Get('EnablePriorityAsString') ) { if ( $old_value ne $self->loc('(no value)') ) { my $str = RT::Ticket->_PriorityAsString( $old_value, $self->Name ); $old_value = $self->loc($str) if $str; } if ( $new_value ne $self->loc('(no value)') ) { my $str = RT::Ticket->_PriorityAsString( $new_value, $self->Name ); $new_value = $self->loc($str) if $str; } } if ( $ret ) { return ( $ret, $self->loc( 'Default value of [_1] changed from [_2] to [_3]', $args{Name}, $old_value, $new_value ) ); } else { return ( $ret, $self->loc( "Can't change default value of [_1] from [_2] to [_3]: [_4]", $args{Name}, $old_value, $new_value, $msg ) ); } } sub SLA { my $self = shift; my $value = shift; return undef unless $self->CurrentUserHasRight('SeeQueue'); my $attr = $self->FirstAttribute('SLA') or return undef; return $attr->Content; } sub SetSLA { my $self = shift; my $value = shift; return ( 0, $self->loc('Permission Denied') ) unless $self->CurrentUserHasRight('AdminQueue'); my ($status, $msg) = $self->SetAttribute( Name => 'SLA', Description => 'Default Queue SLA', Content => $value, ); return ($status, $msg) unless $status; return ($status, $self->loc("Queue's default service level has been changed")); } sub InitialPriority { my $self = shift; RT->Deprecated( Instead => "DefaultValue('InitialPriority')", Remove => '4.6' ); return $self->DefaultValue('InitialPriority'); } sub FinalPriority { my $self = shift; RT->Deprecated( Instead => "DefaultValue('FinalPriority')", Remove => '4.6' ); return $self->DefaultValue('FinalPriority'); } sub DefaultDueIn { my $self = shift; RT->Deprecated( Instead => "DefaultValue('Due')", Remove => '4.6' ); # DefaultDueIn used to be a number of days; so if the DefaultValue is, # say, "3 days" then return 3 my $due = $self->DefaultValue('Due'); if (defined($due) && $due =~ /^(\d+) days?$/i) { return $1; } return $due; } sub SetInitialPriority { my $self = shift; my $value = shift; RT->Deprecated( Instead => "SetDefaultValue", Remove => '4.6' ); return $self->SetDefaultValue( Name => 'InitialPriority', Value => $value, ); } sub SetFinalPriority { my $self = shift; my $value = shift; RT->Deprecated( Instead => "SetDefaultValue", Remove => '4.6' ); return $self->SetDefaultValue( Name => 'FinalPriority', Value => $value, ); } sub SetDefaultDueIn { my $self = shift; my $value = shift; # DefaultDueIn used to be a number of days; so if we're setting to, # say, "3" then add the word "days" to match the way the new # DefaultValues works $value .= " days" if defined($value) && $value =~ /^\d+$/; RT->Deprecated( Instead => "SetDefaultValue", Remove => '4.6' ); return $self->SetDefaultValue( Name => 'Due', Value => $value, ); } sub HiddenCustomRoleIDsForURL { my $self = shift; my $url = shift; my $roles = $self->CustomRoles; my @ids; while (my $role = $roles->Next) { push @ids, $role->Id if $role->IsHiddenForURL($url); } return @ids; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Link.pm�����������������������������������������������������������������������������000644 �000765 �000024 �00000041022 14005011336 015326� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Link - an RT Link object =head1 SYNOPSIS use RT::Link; =head1 DESCRIPTION This module should never be called directly by client code. it's an internal module which should only be accessed through exported APIs in Ticket other similar objects. =cut package RT::Link; use strict; use warnings; use base 'RT::Record'; sub Table {'Links'} use Carp; use RT::URI; use List::Util 'first'; use List::MoreUtils 'uniq'; # Helper tables for links mapping to make it easier # to build and parse links between objects. our %TYPEMAP = ( MemberOf => { Type => 'MemberOf', Mode => 'Target', Display => 0 }, Parents => { Type => 'MemberOf', Mode => 'Target', Display => 1 }, Parent => { Type => 'MemberOf', Mode => 'Target', Display => 0 }, Members => { Type => 'MemberOf', Mode => 'Base', Display => 0 }, Member => { Type => 'MemberOf', Mode => 'Base', Display => 0 }, Children => { Type => 'MemberOf', Mode => 'Base', Display => 1 }, Child => { Type => 'MemberOf', Mode => 'Base', Display => 0 }, HasMember => { Type => 'MemberOf', Mode => 'Base', Display => 0 }, RefersTo => { Type => 'RefersTo', Mode => 'Target', Display => 1 }, ReferredToBy => { Type => 'RefersTo', Mode => 'Base', Display => 1 }, DependsOn => { Type => 'DependsOn', Mode => 'Target', Display => 1 }, DependedOnBy => { Type => 'DependsOn', Mode => 'Base', Display => 1 }, MergedInto => { Type => 'MergedInto', Mode => 'Target', Display => 1 }, ); our %DIRMAP = ( MemberOf => { Base => 'MemberOf', Target => 'HasMember' }, RefersTo => { Base => 'RefersTo', Target => 'ReferredToBy' }, DependsOn => { Base => 'DependsOn', Target => 'DependedOnBy' }, MergedInto => { Base => 'MergedInto', Target => 'MergedInto' }, ); __PACKAGE__->_BuildDisplayAs; my %DISPLAY_AS; sub _BuildDisplayAs { %DISPLAY_AS = (); foreach my $in_db ( uniq map { $_->{Type} } values %TYPEMAP ) { foreach my $mode (qw(Base Target)) { $DISPLAY_AS{$in_db}{$mode} = first { $TYPEMAP{$_}{Display} && $TYPEMAP{$_}{Type} eq $in_db && $TYPEMAP{$_}{Mode} eq $mode } keys %TYPEMAP; } } } =head1 CLASS METHODS =head2 DisplayTypes Returns a list of the standard link Types for display, including directional variants but not aliases. =cut sub DisplayTypes { sort { $a cmp $b } uniq grep { defined } map { values %$_ } values %DISPLAY_AS } =head1 METHODS =head2 Create PARAMHASH Create a new link object. Takes 'Base', 'Target' and 'Type'. Returns undef on failure or a Link Id on success. =cut sub Create { my $self = shift; my %args = ( Base => undef, Target => undef, Type => undef, @_ ); my $base = RT::URI->new( $self->CurrentUser ); unless ($base->FromURI( $args{'Base'} )) { my $msg = $self->loc("Couldn't resolve base '[_1]' into a URI.", $args{'Base'}); $RT::Logger->warning( "$self $msg" ); return wantarray ? (undef, $msg) : undef; } my $target = RT::URI->new( $self->CurrentUser ); unless ($target->FromURI( $args{'Target'} )) { my $msg = $self->loc("Couldn't resolve target '[_1]' into a URI.", $args{'Target'}); $RT::Logger->warning( "$self $msg" ); return wantarray ? (undef, $msg) : undef; } my $base_id = 0; my $target_id = 0; if ( $base->IsLocal ) { my $object = $base->Object; unless (UNIVERSAL::can($object, 'Id')) { return (undef, $self->loc("[_1] appears to be a local object, but can't be found in the database", $args{'Base'})); } $base_id = $object->Id if UNIVERSAL::isa($object, 'RT::Ticket'); } if ( $target->IsLocal ) { my $object = $target->Object; unless (UNIVERSAL::can($object, 'Id')) { return (undef, $self->loc("[_1] appears to be a local object, but can't be found in the database", $args{'Target'})); } $target_id = $object->Id if UNIVERSAL::isa($object, 'RT::Ticket'); } # We don't want references to ourself if ( $base->URI eq $target->URI ) { return ( 0, $self->loc("Can't link a ticket to itself") ); } # }}} my ( $id, $msg ) = $self->SUPER::Create( Base => $base->URI, Target => $target->URI, LocalBase => $base_id, LocalTarget => $target_id, Type => $args{'Type'} ); return ( $id, $msg ); } # sub LoadByParams =head2 LoadByParams Load an RT::Link object from the database. Takes three parameters Base => undef, Target => undef, Type =>undef Base and Target are expected to be integers which refer to Tickets or URIs Type is the link type =cut sub LoadByParams { my $self = shift; my %args = ( Base => undef, Target => undef, Type => undef, @_ ); my $base = RT::URI->new($self->CurrentUser); $base->FromURI( $args{'Base'} ) or return wantarray ? (0, $self->loc("Couldn't parse Base URI: [_1]", $args{Base})) : 0; my $target = RT::URI->new($self->CurrentUser); $target->FromURI( $args{'Target'} ) or return wantarray ? (0, $self->loc("Couldn't parse Target URI: [_1]", $args{Target})) : 0; my ( $id, $msg ) = $self->LoadByCols( Base => $base->URI, Type => $args{'Type'}, Target => $target->URI ); unless ($id) { return wantarray ? ( 0, $self->loc("Couldn't load link: [_1]", $msg) ) : 0; } else { return wantarray ? ($id, $msg) : $id; } } =head2 Load Load an RT::Link object from the database. Takes one parameter, the id of an entry in the links table. =cut sub Load { my $self = shift; my $identifier = shift; if ( $identifier !~ /^\d+$/ ) { return wantarray ? ( 0, $self->loc("That's not a numerical id") ) : 0; } else { my ( $id, $msg ) = $self->LoadById($identifier); unless ( $self->Id ) { return wantarray ? ( 0, $self->loc("Couldn't load link") ) : 0; } return wantarray ? ( $id, $msg ) : $id; } } =head2 TargetURI returns an RT::URI object for the "Target" of this link. =cut sub TargetURI { my $self = shift; my $URI = RT::URI->new($self->CurrentUser); $URI->FromURI($self->Target); return ($URI); } =head2 TargetObj =cut sub TargetObj { my $self = shift; return $self->TargetURI->Object; } =head2 BaseURI returns an RT::URI object for the "Base" of this link. =cut sub BaseURI { my $self = shift; my $URI = RT::URI->new($self->CurrentUser); $URI->FromURI($self->Base); return ($URI); } =head2 BaseObj =cut sub BaseObj { my $self = shift; return $self->BaseURI->Object; } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 Base Returns the current value of Base. (In the database, Base is stored as varchar(240).) =head2 SetBase VALUE Set Base to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Base will be stored as a varchar(240).) =cut =head2 Target Returns the current value of Target. (In the database, Target is stored as varchar(240).) =head2 SetTarget VALUE Set Target to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Target will be stored as a varchar(240).) =cut =head2 Type Returns the current value of Type. (In the database, Type is stored as varchar(20).) =head2 SetType VALUE Set Type to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Type will be stored as a varchar(20).) =cut =head2 LocalTarget Returns the current value of LocalTarget. (In the database, LocalTarget is stored as int(11).) =head2 SetLocalTarget VALUE Set LocalTarget to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, LocalTarget will be stored as a int(11).) =cut =head2 LocalBase Returns the current value of LocalBase. (In the database, LocalBase is stored as int(11).) =head2 SetLocalBase VALUE Set LocalBase to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, LocalBase will be stored as a int(11).) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Base => {read => 1, write => 1, sql_type => 12, length => 240, is_blob => 0, is_numeric => 0, type => 'varchar(240)', default => ''}, Target => {read => 1, write => 1, sql_type => 12, length => 240, is_blob => 0, is_numeric => 0, type => 'varchar(240)', default => ''}, Type => {read => 1, write => 1, sql_type => 12, length => 20, is_blob => 0, is_numeric => 0, type => 'varchar(20)', default => ''}, LocalTarget => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LocalBase => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); $deps->Add( out => $self->BaseObj ) if $self->BaseObj and $self->BaseObj->id; $deps->Add( out => $self->TargetObj ) if $self->TargetObj and $self->TargetObj->id; } sub __DependsOn { my $self = shift; my %args = ( Shredder => undef, Dependencies => undef, @_, ); my $deps = $args{'Dependencies'}; my $list = []; # AddLink transactions my $map = { %RT::Link::TYPEMAP }; my $link_meta = $map->{ $self->Type }; unless ( $link_meta && $link_meta->{'Mode'} && $link_meta->{'Type'} ) { RT::Shredder::Exception->throw( 'Wrong link link_meta, no record for '. $self->Type ); } if ( $self->BaseURI->IsLocal ) { my $objs = $self->BaseObj->Transactions; $objs->Limit( FIELD => 'Type', OPERATOR => '=', VALUE => 'AddLink', ); $objs->Limit( FIELD => 'NewValue', VALUE => $self->Target ); while ( my ($k, $v) = each %$map ) { next unless $v->{'Type'} eq $link_meta->{'Type'}; next unless $v->{'Mode'} eq $link_meta->{'Mode'}; $objs->Limit( FIELD => 'Field', VALUE => $k ); } push( @$list, $objs ); } my %reverse = ( Base => 'Target', Target => 'Base' ); if ( $self->TargetURI->IsLocal ) { my $objs = $self->TargetObj->Transactions; $objs->Limit( FIELD => 'Type', OPERATOR => '=', VALUE => 'AddLink', ); $objs->Limit( FIELD => 'NewValue', VALUE => $self->Base ); while ( my ($k, $v) = each %$map ) { next unless $v->{'Type'} eq $link_meta->{'Type'}; next unless $v->{'Mode'} eq $reverse{ $link_meta->{'Mode'} }; $objs->Limit( FIELD => 'Field', VALUE => $k ); } push( @$list, $objs ); } $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON|RT::Shredder::Constants::WIPE_AFTER, TargetObjects => $list, Shredder => $args{'Shredder'} ); return $self->SUPER::__DependsOn( %args ); } sub Serialize { my $self = shift; my %args = (@_); my %store = $self->SUPER::Serialize(@_); delete $store{LocalBase} if $store{Base}; delete $store{LocalTarget} if $store{Target}; for my $dir (qw/Base Target/) { my $uri = $self->${\($dir.'URI')}; my $object = $self->${\($dir.'Obj')}; if ($uri->IsLocal) { if ($args{serializer}->Observe(object => $object)) { # no action needed; the object is being migrated } elsif ($args{serializer}{HyperlinkUnmigrated}) { # object is not being migrated; hyperlinkify $store{$dir} = $uri->AsHREF; } else { # object is not being migrated and hyperlinks not desired, # so drop this RT::Link altogether return; } } } return %store; } sub PreInflate { my $class = shift; my ($importer, $uid, $data) = @_; for my $dir (qw/Base Target/) { my $uid_ref = $data->{$dir}; next unless $uid_ref and ref $uid_ref; my $to_uid = ${ $uid_ref }; my $obj = $importer->LookupObj( $to_uid ); if ($obj) { $data->{$dir} = $obj->URI; $data->{"Local$dir"} = $obj->Id if $obj->isa("RT::Ticket"); } else { $data->{$dir} = ""; $importer->Postpone( for => $to_uid, uid => $uid, uri => $dir, column => ($to_uid =~ /RT::Ticket/ ? "Local$dir" : undef), ); } } return $class->SUPER::PreInflate( $importer, $uid, $data ); } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/LDAPImport.pm�����������������������������������������������������������������������000644 �000765 �000024 �00000135743 14005011336 016362� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::LDAPImport; use warnings; use strict; use base qw(Class::Accessor); __PACKAGE__->mk_accessors(qw(_ldap _group _users)); use Carp; use Net::LDAP; use Net::LDAP::Util qw(escape_filter_value); use Net::LDAP::Control::Paged; use Net::LDAP::Constant qw(LDAP_CONTROL_PAGED); use Data::Dumper; =head1 NAME RT::LDAPImport - Import Users from an LDAP store =head1 SYNOPSIS In C<RT_SiteConfig.pm>: Set($LDAPHost,'my.ldap.host'); Set($LDAPOptions, [ port => 636, scheme => 'ldaps', raw => qr/(\;binary)/, version => 3, verify => 'required', cafile => '/certificate-file/path' ]); Set($LDAPUser,'me'); Set($LDAPPassword,'mypass'); Set($LDAPBase, 'ou=People,o=Our Place'); Set($LDAPFilter, '(&(cn = users))'); Set($LDAPMapping, {Name => 'uid', # required EmailAddress => 'mail', RealName => 'cn', WorkPhone => 'telephoneNumber', Organization => 'departmentName'}); # If you want to sync Groups from LDAP into RT Set($LDAPGroupBase, 'ou=Groups,o=Our Place'); Set($LDAPGroupFilter, '(&(cn = Groups))'); Set($LDAPGroupMapping, {Name => 'cn', Member_Attr => 'member', Member_Attr_Value => 'dn' }); Running the import: # Run a test import /opt/rt5/sbin/rt-ldapimport --verbose > ldapimport.debug 2>&1 # Run for real, possibly put in cron /opt/rt5/sbin/rt-ldapimport --import =head1 CONFIGURATION All of the configuration for the importer goes in your F<RT_SiteConfig.pm> file. Some of these values pass through to L<Net::LDAP> so you can check there for valid values and more advanced options. =over =item C<< Set($LDAPHost,'our.ldap.host'); >> Hostname or ldap(s):// uri: =item C<< Set($LDAPOptions, [ port => 636 ]); >> This allows you to pass any options supported by the L<Net::LDAP> new method. =item C<< Set($LDAPUser, 'uid=foo,ou=users,dc=example,dc=com'); >> Your LDAP username or DN. If unset, we'll attempt an anonymous bind. =item C<< Set($LDAPPassword, 'ldap pass'); >> Your LDAP password. =item C<< Set($LDAPBase, 'ou=People,o=Our Place'); >> Base object to search from. =item C<< Set($LDAPFilter, '(&(cn = users))'); >> The LDAP search filter to apply (in this case, find all the users). =item C<< Set($LDAPMapping... >> Set($LDAPMapping, {Name => 'uid', EmailAddress => 'mail', RealName => 'cn', WorkPhone => 'telephoneNumber', Organization => 'departmentName'}); This provides the mapping of attributes in RT to attribute(s) in LDAP. Only Name is required for RT. The values in the mapping (i.e. the LDAP fields, the right hand side) can be one of the following: =over 4 =item an attribute LDAP attribute to use. Only first value is used if attribute is multivalue. For example: EmailAddress => 'mail', =item an array reference The LDAP attributes can also be an arrayref of LDAP fields, for example: WorkPhone => [qw/CompanyPhone Extension/] which will be concatenated together with a space. First values of each attribute are used in case they have multiple values. =item a subroutine reference The LDAP attribute can also be a subroutine reference that does mapping, for example: YYY => sub { my %args = @_; my @values = grep defined && length, $args{ldap_entry}->get_value('XXX'); return @values; }, The subroutine should return value or list of values. The following arguments are passed into the function in a hash: =over 4 =item self Instance of this class. =item ldap_entry L<Net::LDAP::Entry> instance that is currently mapped. =item import Boolean value indicating whether it's import or a dry run. If it's dry run (import is false) then function shouldn't change anything. =item mapping Hash reference with the currently processed mapping, eg. C<$LDAPMapping>. =item rt_field and ldap_field The currently processed key and value from the mapping. =item result Hash reference with results of completed mappings for this ldap entry. This should be used to inject that are not in the mapping, not to inspect. Mapping is processed in literal order of the keys. =back =back The keys in the mapping (i.e. the RT fields, the left hand side) may be a user custom field name prefixed with C<UserCF.>, for example C<< 'UserCF.Employee Number' => 'employeeId' >>. Note that this only B<adds> values at the moment, which on single value CFs will remove any old value first. Multiple value CFs may behave not quite how you expect. If the attribute no longer exists on a user in LDAP, it will be cleared on the RT side as well. You may also prefix any RT custom field name with C<CF.> inside your mapping to add available values to a Select custom field. This effectively takes user attributes in LDAP and adds the values as selectable options in a CF. It does B<not> set a CF value on any RT object (User, Ticket, Queue, etc). You might use this to populate a ticket Location CF with all the locations of your users so that tickets can be associated with the locations in use. =item C<< Set($LDAPCreatePrivileged, 1); >> By default users are created as Unprivileged, but you can change this by setting C<$LDAPCreatePrivileged> to 1. =item C<< Set($LDAPGroupName,'My Imported Users'); >> The RT Group new and updated users belong to. By default, all users added or updated by the importer will belong to the 'Imported from LDAP' group. =item C<< Set($LDAPSkipAutogeneratedGroup, 1); >> Set this to true to prevent users from being automatically added to the group configured by C<$LDAPGroupName>. =item C<< Set($LDAPUpdateUsers, 1); >> By default, existing users are skipped. If you turn on LDAPUpdateUsers, we will clobber existing data with data from LDAP. =item C<< Set($LDAPUpdateOnly, 1); >> By default, we create users who don't exist in RT but do match your LDAP filter and obey C<$LDAPUpdateUsers> for existing users. This setting updates existing users, overriding C<$LDAPUpdateUsers>, but won't create new users who are found in LDAP but not in RT. =item C<< Set($LDAPGroupBase, 'ou=Groups,o=Our Place'); >> Where to search for groups to import. =item C<< Set($LDAPGroupFilter, '(&(cn = Groups))'); >> The search filter to apply. =item C<< Set($LDAPGroupMapping... >> Set($LDAPGroupMapping, {Name => 'cn', Member_Attr => 'member', Member_Attr_Value => 'dn' }); A mapping of RT attributes to LDAP attributes to identify group members. Name will become the name of the group in RT, in this case pulling from the cn attribute on the LDAP group record returned. Everything besides C<Member_Attr_Value> is processed according to rules described in documentation for C<$LDAPMapping> option, so value can be array or code reference besides scalar. C<Member_Attr> is the field in the LDAP group record the importer should look at for group members. These values (there may be multiple members) will then be compared to the RT user name, which came from the LDAP user record. See F<t/ldapimport/group-callbacks.t> for a complex example of using a code reference as value of this option. C<Member_Attr_Value>, which defaults to 'dn', specifies where on the LDAP user record the importer should look to compare the member value. A match between the member field on the group record and this identifier (dn or other LDAP field) on a user record means the user will be added to that group in RT. C<id> is the field in LDAP group record that uniquely identifies the group. This is optional and shouldn't be equal to mapping for Name field. Group names in RT must be distinct and you don't need another unique identifier in common situation. However, when you rename a group in LDAP, without this option set properly you end up with two groups in RT. You can provide a C<Description> key which will be added as the group description in RT. The default description is 'Imported from LDAP'. =item C<< Set($LDAPImportGroupMembers, 1); >> When disabled, the default, LDAP group import expects that all LDAP members already exist as RT users. Often the user import stage, which happens before groups, is used to create and/or update group members by using an C<$LDAPFilter> which includes a C<memberOf> attribute. When enabled, by setting to C<1>, LDAP group members are explicitly imported before membership is synced with RT. This enables groups-only configurations to also import group members without specifying a potentially long and complex C<$LDAPFilter> using C<memberOf>. It's particularly handy when C<memberOf> isn't available on user entries. Note that C<$LDAPFilter> still applies when this option is enabled, so some group members may be filtered out from the import. =item C<< Set($LDAPSizeLimit, 1000); >> You can set this value if your LDAP server has result size limits. =back =head1 Mapping Groups Between RT and LDAP If you are using the importer, you likely want to manage access via LDAP by putting people in groups like 'DBAs' and 'IT Support', but also have groups for other non-RT related things. In this case, you won't want to create all of your LDAP groups in RT. To limit the groups that get mirrored, construct your C<$LDAPGroupFilter> as an OR (|) with all of the RT groups you want to mirror from LDAP. For example: Set($LDAPGroupBase, 'OU=Groups,OU=Company,DC=COM'); Set($LDAPGroupFilter, '(|(CN=DBAs)(CN=IT Support))'); The importer will then import only the groups that match. In this case, import means: =over =item * Verifying the group is in AD; =item * Creating the group in RT if it doesn't exist; =item * Populating the group with the members identified in AD; =back The import script will also issue a warning if a user isn't found in RT, but this should only happen when testing. When running with --import on, users are created before groups are processed, so all users (group members) should exist unless there are inconsistencies in your LDAP configuration. =head1 Running the Import Executing C<rt-ldapimport> will run a test that connects to your LDAP server and prints out a list of the users found. To see more about these users, and to see more general debug information, include the C<--verbose> flag. That debug information is also sent to the RT log with the debug level. Errors are logged to the screen and to the RT log. Executing C<rt-ldapimport> with the C<--import> flag will cause it to import users into your RT database. It is recommended that you make a database backup before doing this. If your filters aren't set properly this could create a lot of users or groups in your RT instance. =head1 LDAP Filters The L<ldapsearch|http://www.openldap.org/software/man.cgi?query=ldapsearch&manpath=OpenLDAP+2.0-Release> utility in openldap can be very helpful while refining your filters. =head1 METHODS =head2 connect_ldap Relies on the config variables C<$LDAPHost>, C<$LDAPOptions>, C<$LDAPUser>, and C<$LDAPPassword> being set in your RT Config files. Set($LDAPHost,'my.ldap.host'); Set($LDAPOptions, [ port => 636 ]); Set($LDAPUSER,'me'); Set($LDAPPassword,'mypass'); LDAPUser and LDAPPassword can be blank, which will cause an anonymous bind. LDAPHost can be a hostname or an ldap:// ldaps:// uri. =cut sub connect_ldap { my $self = shift; $RT::LDAPOptions = [] unless $RT::LDAPOptions; my $ldap = Net::LDAP->new($RT::LDAPHost, @$RT::LDAPOptions); $RT::Logger->debug("connecting to $RT::LDAPHost"); unless ($ldap) { $RT::Logger->error("Can't connect to $RT::LDAPHost $@"); return; } my $msg; if ($RT::LDAPUser) { $RT::Logger->debug("binding as $RT::LDAPUser"); $msg = $ldap->bind($RT::LDAPUser, password => $RT::LDAPPassword); } else { $RT::Logger->debug("binding anonymously"); $msg = $ldap->bind; } if ($msg->code) { $RT::Logger->error("LDAP bind failed " . $msg->error); return; } $self->_ldap($ldap); return $ldap; } =head2 run_user_search Set up the appropriate arguments for a listing of users. =cut sub run_user_search { my $self = shift; $self->_run_search( base => $RT::LDAPBase, filter => $RT::LDAPFilter ); } =head2 _run_search Executes a search using the provided base and filter. Will connect to LDAP server using C<connect_ldap>. Returns an array of L<Net::LDAP::Entry> objects, possibly consolidated from multiple LDAP pages. =cut sub _run_search { my $self = shift; my $ldap = $self->_ldap||$self->connect_ldap; my %args = @_; unless ($ldap) { $RT::Logger->error("fetching an LDAP connection failed"); return; } my %search = ( base => $args{base}, filter => $args{filter}, scope => ($args{scope} || 'sub'), ); my (@results, $page, $cookie); if ($RT::LDAPSizeLimit) { $page = Net::LDAP::Control::Paged->new( size => $RT::LDAPSizeLimit, critical => 1 ); $search{control} = $page; } LOOP: { # Start where we left off $page->cookie($cookie) if $page and $cookie; $RT::Logger->debug("searching with: " . join(' ', map { "$_ => '$search{$_}'" } sort keys %search)); my $result = $ldap->search( %search ); if ($result->code) { $RT::Logger->error("LDAP search failed " . $result->error); last; } push @results, $result->entries; # Short circuit early if we're done last if not $result->count or $result->count < ($RT::LDAPSizeLimit || 0); if ($page) { if (my $control = $result->control( LDAP_CONTROL_PAGED )) { $cookie = $control->cookie; } else { $RT::Logger->error("LDAP search didn't return a paging control"); last; } } redo if $cookie; } # Let the server know we're abandoning the search if we errored out if ($cookie) { $RT::Logger->debug("Informing the LDAP server we're done with the result set"); $page->cookie($cookie); $page->size(0); $ldap->search( %search ); } $RT::Logger->debug("search found ".scalar @results." objects"); return @results; } =head2 import_users import => 1|0 Takes the results of the search from run_search and maps attributes from LDAP into C<RT::User> attributes using C<$LDAPMapping>. Creates RT users if they don't already exist. With no arguments, only prints debugging information. Pass C<--import> to actually change data. C<$LDAPMapping>> should be set in your C<RT_SiteConfig.pm> file and look like this. Set($LDAPMapping, { RTUserField => LDAPField, RTUserField => LDAPField }); RTUserField is the name of a field on an C<RT::User> object LDAPField can be a simple scalar and that attribute will be looked up in LDAP. It can also be an arrayref, in which case each of the elements will be evaluated in turn. Scalars will be looked up in LDAP and concatenated together with a single space. If the value is a sub reference, it will be executed. The sub should return a scalar, which will be examined. If it is a scalar, the value will be looked up in LDAP. If it is an arrayref, the values will be concatenated together with a single space. By default users are created as Unprivileged, but you can change this by setting C<$LDAPCreatePrivileged> to 1. =cut sub import_users { my $self = shift; my %args = @_; $self->_users({}); my @results = $self->run_user_search; return $self->_import_users( %args, users => \@results ); } sub _import_users { my $self = shift; my %args = @_; my $users = $args{users}; unless ( @$users ) { $RT::Logger->debug("No users found, no import"); $self->disconnect_ldap; return; } my $mapping = $RT::LDAPMapping; return unless $self->_check_ldap_mapping( mapping => $mapping ); my $done = 0; my $count = scalar @$users; while (my $entry = shift @$users) { my $user = $self->_build_user_object( ldap_entry => $entry ); $self->_import_user( user => $user, ldap_entry => $entry, import => $args{import} ); $done++; $RT::Logger->debug("Imported $done/$count users"); } return 1; } =head2 _import_user We have found a user to attempt to import; returns the L<RT::User> object if it was found (or created), C<undef> if not. =cut sub _import_user { my $self = shift; my %args = @_; unless ( $args{user}{Name} ) { $RT::Logger->warn("No Name or Emailaddress for user, skipping ".Dumper($args{user})); return; } if ( $args{user}{Name} =~ /^[0-9]+$/) { $RT::Logger->debug("Skipping user '$args{user}{Name}', as it is numeric"); return; } $RT::Logger->debug("Processing user $args{user}{Name}"); $self->_cache_user( %args ); $args{user} = $self->create_rt_user( %args ); return unless $args{user}; $self->add_user_to_group( %args ); $self->add_custom_field_value( %args ); $self->update_object_custom_field_values( %args, object => $args{user} ); return $args{user}; } =head2 _cache_user ldap_entry => Net::LDAP::Entry, [user => { ... }] Adds the user to a global cache which is used when importing groups later. Optionally takes a second argument which is a user data object returned by _build_user_object. If not given, _cache_user will call _build_user_object itself. Returns the user Name. =cut sub _cache_user { my $self = shift; my %args = (@_); my $user = $args{user} || $self->_build_user_object( ldap_entry => $args{ldap_entry} ); $self->_users({}) if not defined $self->_users; my $group_map = $RT::LDAPGroupMapping || {}; my $member_attr_val = $group_map->{Member_Attr_Value} || 'dn'; my $membership_key = lc $member_attr_val eq 'dn' ? $args{ldap_entry}->dn : $args{ldap_entry}->get_value($member_attr_val); # Fallback to the DN if the user record doesn't have a value unless (defined $membership_key) { $membership_key = $args{ldap_entry}->dn; $RT::Logger->warn("User attribute '$member_attr_val' has no value for '$membership_key'; falling back to DN"); } return $self->_users->{lc $membership_key} = $user->{Name}; } sub _show_user_info { my $self = shift; my %args = @_; my $user = $args{user}; my $rt_user = $args{rt_user}; $RT::Logger->debug( "\tRT Field\tRT Value -> LDAP Value" ); foreach my $key (sort keys %$user) { my $old_value; if ($rt_user) { eval { $old_value = $rt_user->$key() }; if ($user->{$key} && defined $old_value && $old_value eq $user->{$key}) { $old_value = 'unchanged'; } } $old_value ||= 'unset'; $RT::Logger->debug( "\t$key\t$old_value => $user->{$key}" ); } #$RT::Logger->debug(Dumper($user)); } =head2 _check_ldap_mapping Returns true is there is an C<LDAPMapping> configured, returns false, logs an error and disconnects from ldap if there is no mapping. =cut sub _check_ldap_mapping { my $self = shift; my %args = @_; my $mapping = $args{mapping}; my @rtfields = keys %{$mapping}; unless ( @rtfields ) { $RT::Logger->error("No mapping found, can't import"); $self->disconnect_ldap; return; } return 1; } =head2 _build_user_object Utility method which wraps C<_build_object> to provide sane defaults for building users. It also tries to ensure a Name exists in the returned object. =cut sub _build_user_object { my $self = shift; my $user = $self->_build_object( skip => qr/(?i)^(?:User)?CF\./, mapping => $RT::LDAPMapping, @_ ); $user->{Name} ||= $user->{EmailAddress}; return $user; } =head2 _build_object Internal method - a wrapper around L</_parse_ldap_mapping> that flattens results turning every value into a scalar. The following: [ [$first_value1, ... ], [$first_value2], $scalar_value, ] Turns into: "$first_value1 $first_value2 $scalar_value" Arguments are just passed into L</_parse_ldap_mapping>. =cut sub _build_object { my $self = shift; my %args = @_; my $res = $self->_parse_ldap_mapping( %args ); foreach my $value ( values %$res ) { @$value = map { ref $_ eq 'ARRAY'? $_->[0] : $_ } @$value; $value = join ' ', grep defined && length, @$value; } return $res; } =head3 _parse_ldap_mapping Internal helper method that maps an LDAP entry to a hash according to passed arguments. Takes named arguments: =over 4 =item ldap_entry L<Net::LDAP::Entry> instance that should be mapped. =item only Optional regular expression. If passed then only matching entries in the mapping will be processed. =item skip Optional regular expression. If passed then matching entries in the mapping will be skipped. =item mapping Hash that defines how to map. Key defines position in the result. Value can be one of the following: If we're passed a scalar or an array reference then value is: [ [value1_of_attr1, value2_of_attr1], [value1_of_attr2, value2_of_attr2], ] If we're passed a subroutine reference as value or as an element of array, it executes the code and returned list is pushed into results array: [ @result_of_function, ] All arguments are passed into the subroutine as well as a few more. See more in description of C<$LDAPMapping> option. =back Returns hash reference with results, each value is an array with elements either scalars or arrays as described above. =cut sub _parse_ldap_mapping { my $self = shift; my %args = @_; my $mapping = $args{mapping}; my %res; foreach my $rtfield ( sort keys %$mapping ) { next if $args{'skip'} && $rtfield =~ $args{'skip'}; next if $args{'only'} && $rtfield !~ $args{'only'}; my $ldap_field = $mapping->{$rtfield}; my @list = grep defined && length, ref $ldap_field eq 'ARRAY'? @$ldap_field : ($ldap_field); unless (@list) { $RT::Logger->error("Invalid LDAP mapping for $rtfield, no defined fields"); next; } my @values; foreach my $e (@list) { if (ref $e eq 'CODE') { push @values, $e->( %args, self => $self, rt_field => $rtfield, ldap_field => $ldap_field, result => \%res, ); } elsif (ref $e) { $RT::Logger->error("Invalid type of LDAP mapping for $rtfield, value is $e"); next; } else { # XXX: get_value asref returns undef if there is no such field on # the entry, should we warn? push @values, grep defined, $args{'ldap_entry'}->get_value( $e, asref => 1 ); } } $res{ $rtfield } = \@values; } return \%res; } =head2 create_rt_user Takes a hashref of args to pass to C<RT::User::Create> Will try loading the user and will only create a new user if it can't find an existing user with the C<Name> or C<EmailAddress> arg passed in. If the C<$LDAPUpdateUsers> variable is true, data in RT will be clobbered with data in LDAP. Otherwise we will skip to the next user. If C<$LDAPUpdateOnly> is true, we will not create new users but we will update existing ones. =cut sub create_rt_user { my $self = shift; my %args = @_; my $user = $args{user}; my $user_obj = $self->_load_rt_user(%args); if ($user_obj->Id) { my $message = "User $user->{Name} already exists as ".$user_obj->Id; if ($RT::LDAPUpdateUsers || $RT::LDAPUpdateOnly) { $RT::Logger->debug("$message, updating their data"); if ($args{import}) { my @results = $user_obj->Update( ARGSRef => $user, AttributesRef => [keys %$user] ); $RT::Logger->debug(join("\n",@results)||'no change'); } else { $RT::Logger->debug("Found existing user $user->{Name} to update"); $self->_show_user_info( %args, rt_user => $user_obj ); } } else { $RT::Logger->debug("$message, skipping"); } } else { if ( $RT::LDAPUpdateOnly ) { $RT::Logger->debug("User $user->{Name} doesn't exist in RT, skipping"); return; } else { if ($args{import}) { my ($val, $msg) = $user_obj->Create( %$user, Privileged => $RT::LDAPCreatePrivileged ? 1 : 0 ); unless ($val) { $RT::Logger->error("couldn't create user_obj for $user->{Name}: $msg"); return; } $RT::Logger->debug("Created user for $user->{Name} with id ".$user_obj->Id); } else { $RT::Logger->debug( "Found new user $user->{Name} to create in RT" ); $self->_show_user_info( %args ); return; } } } unless ($user_obj->Id) { $RT::Logger->error("We couldn't find or create $user->{Name}. This should never happen"); } return $user_obj; } sub _load_rt_user { my $self = shift; my %args = @_; my $user = $args{user}; my $user_obj = RT::User->new($RT::SystemUser); $user_obj->Load( $user->{Name} ); unless ($user_obj->Id) { $user_obj->LoadByEmail( $user->{EmailAddress} ); } return $user_obj; } =head2 add_user_to_group Adds new users to the group specified in the C<$LDAPGroupName> variable (defaults to 'Imported from LDAP'). You can avoid this if you set C<$LDAPSkipAutogeneratedGroup>. =cut sub add_user_to_group { my $self = shift; my %args = @_; my $user = $args{user}; return if $RT::LDAPSkipAutogeneratedGroup; my $group = $self->_group||$self->setup_group; my $principal = $user->PrincipalObj; if ($group->HasMember($principal)) { $RT::Logger->debug($user->Name . " already a member of " . $group->Name); return; } if ($args{import}) { my ($status, $msg) = $group->AddMember($principal->Id); if ($status) { $RT::Logger->debug("Added ".$user->Name." to ".$group->Name." [$msg]"); } else { $RT::Logger->error("Couldn't add ".$user->Name." to ".$group->Name." [$msg]"); } return $status; } else { $RT::Logger->debug("Would add to ".$group->Name); return; } } =head2 setup_group Pulls the C<$LDAPGroupName> object out of the DB or creates it if we need to do so. =cut sub setup_group { my $self = shift; my $group_name = $RT::LDAPGroupName||'Imported from LDAP'; my $group = RT::Group->new($RT::SystemUser); $group->LoadUserDefinedGroup( $group_name ); unless ($group->Id) { my ($id,$msg) = $group->CreateUserDefinedGroup( Name => $group_name ); unless ($id) { $RT::Logger->error("Can't create group $group_name [$msg]") } } $self->_group($group); } =head3 add_custom_field_value Adds values to a Select (one|many) Custom Field. The Custom Field should already exist, otherwise this will throw an error and not import any data. This could probably use some caching. =cut sub add_custom_field_value { my $self = shift; my %args = @_; my $user = $args{user}; my $data = $self->_build_object( %args, only => qr/^CF\.(.+)$/i, mapping => $RT::LDAPMapping, ); foreach my $rtfield ( keys %$data ) { next unless $rtfield =~ /^CF\.(.+)$/i; my $cf_name = $1; my $cfv_name = $data->{ $rtfield } or next; my $cf = RT::CustomField->new($RT::SystemUser); my ($status, $msg) = $cf->Load($cf_name); unless ($status) { $RT::Logger->error("Couldn't load CF [$cf_name]: $msg"); next; } my $cfv = RT::CustomFieldValue->new($RT::SystemUser); $cfv->LoadByCols( CustomField => $cf->id, Name => $cfv_name ); if ($cfv->id) { $RT::Logger->debug("Custom Field '$cf_name' already has '$cfv_name' for a value"); next; } if ($args{import}) { ($status, $msg) = $cf->AddValue( Name => $cfv_name ); if ($status) { $RT::Logger->debug("Added '$cfv_name' to Custom Field '$cf_name' [$msg]"); } else { $RT::Logger->error("Couldn't add '$cfv_name' to '$cf_name' [$msg]"); } } else { $RT::Logger->debug("Would add '$cfv_name' to Custom Field '$cf_name'"); } } return; } =head3 update_object_custom_field_values Adds CF values to an object (currently only users). The Custom Field should already exist, otherwise this will throw an error and not import any data. Note that this code only B<adds> values at the moment, which on single value CFs will remove any old value first. Multiple value CFs may behave not quite how you expect. =cut sub update_object_custom_field_values { my $self = shift; my %args = @_; my $obj = $args{object}; my $data = $self->_build_object( %args, only => qr/^UserCF\.(.+)$/i, mapping => $RT::LDAPMapping, ); foreach my $rtfield ( sort keys %$data ) { # XXX TODO: accept GroupCF when we call this from group_import too next unless $rtfield =~ /^UserCF\.(.+)$/i; my $cf_name = $1; my $value = $data->{$rtfield}; $value = '' unless defined $value; my $current = $obj->FirstCustomFieldValue($cf_name); $current = '' unless defined $current; if (not length $current and not length $value) { $RT::Logger->debug("\tCF.$cf_name\tskipping, no value in RT and LDAP"); next; } elsif ($current eq $value) { $RT::Logger->debug("\tCF.$cf_name\tunchanged => $value"); next; } $current = 'unset' unless length $current; $RT::Logger->debug("\tCF.$cf_name\t$current => $value"); next unless $args{import}; my ($ok, $msg) = $obj->AddCustomFieldValue( Field => $cf_name, Value => $value ); $RT::Logger->error($obj->Name . ": Couldn't add value '$value' for '$cf_name': $msg") unless $ok; } } =head2 import_groups import => 1|0 Takes the results of the search from C<run_group_search> and maps attributes from LDAP into C<RT::Group> attributes using C<$LDAPGroupMapping>. Creates groups if they don't exist. Removes users from groups if they have been removed from the group on LDAP. With no arguments, only prints debugging information. Pass C<--import> to actually change data. =cut sub import_groups { my $self = shift; my %args = @_; my @results = $self->run_group_search; unless ( @results ) { $RT::Logger->debug("No results found, no group import"); $self->disconnect_ldap; return; } my $mapping = $RT::LDAPGroupMapping; return unless $self->_check_ldap_mapping( mapping => $mapping ); my $done = 0; my $count = scalar @results; while (my $entry = shift @results) { my $group = $self->_parse_ldap_mapping( %args, ldap_entry => $entry, skip => qr/^Member_Attr_Value$/i, mapping => $mapping, ); foreach my $key ( grep !/^Member_Attr/, keys %$group ) { @{ $group->{$key} } = map { ref $_ eq 'ARRAY'? $_->[0] : $_ } @{ $group->{$key} }; $group->{$key} = join ' ', grep defined && length, @{ $group->{$key} }; } @{ $group->{'Member_Attr'} } = map { ref $_ eq 'ARRAY'? @$_ : $_ } @{ $group->{'Member_Attr'} } if $group->{'Member_Attr'}; $group->{Description} ||= 'Imported from LDAP'; unless ( $group->{Name} ) { $RT::Logger->warn("No Name for group, skipping ".Dumper $group); next; } if ( $group->{Name} =~ /^[0-9]+$/) { $RT::Logger->debug("Skipping group '$group->{Name}', as it is numeric"); next; } $self->_import_group( %args, group => $group, ldap_entry => $entry ); $done++; $RT::Logger->debug("Imported $done/$count groups"); } return 1; } =head3 run_group_search Set up the appropriate arguments for a listing of users. =cut sub run_group_search { my $self = shift; unless ($RT::LDAPGroupBase && $RT::LDAPGroupFilter) { $RT::Logger->warn("Not running a group import, configuration not set"); return; } $self->_run_search( base => $RT::LDAPGroupBase, filter => $RT::LDAPGroupFilter ); } =head2 _import_group The user has run us with C<--import>, so bring data in. =cut sub _import_group { my $self = shift; my %args = @_; my $group = $args{group}; my $ldap_entry = $args{ldap_entry}; $RT::Logger->debug("Processing group $group->{Name}"); my ($group_obj, $created) = $self->create_rt_group( %args, group => $group ); return if $args{import} and not $group_obj; $self->add_group_members( %args, name => $group->{Name}, info => $group, group => $group_obj, ldap_entry => $ldap_entry, new => $created, ); # XXX TODO: support OCFVs for groups too return; } =head2 create_rt_group Takes a hashref of args to pass to C<RT::Group::Create> Will try loading the group and will only create a new group if it can't find an existing group with the C<Name> or C<EmailAddress> arg passed in. If C<$LDAPUpdateOnly> is true, we will not create new groups but we will update existing ones. There is currently no way to prevent Group data from being clobbered from LDAP. =cut sub create_rt_group { my $self = shift; my %args = @_; my $group = $args{group}; my $group_obj = $self->find_rt_group(%args); return unless defined $group_obj; $group = { map { $_ => $group->{$_} } qw(id Name Description) }; my $id = delete $group->{'id'}; my $created; if ($group_obj->Id) { if ($args{import}) { $RT::Logger->debug("Group $group->{Name} already exists as ".$group_obj->Id.", updating their data"); my @results = $group_obj->Update( ARGSRef => $group, AttributesRef => [keys %$group] ); $RT::Logger->debug(join("\n",@results)||'no change'); } else { $RT::Logger->debug( "Found existing group $group->{Name} to update" ); $self->_show_group_info( %args, rt_group => $group_obj ); } } else { if ( $RT::LDAPUpdateOnly ) { $RT::Logger->debug("Group $group->{Name} doesn't exist in RT, skipping"); return; } if ($args{import}) { my ($val, $msg) = $group_obj->CreateUserDefinedGroup( %$group ); unless ($val) { $RT::Logger->error("couldn't create group_obj for $group->{Name}: $msg"); return; } $created = $val; $RT::Logger->debug("Created group for $group->{Name} with id ".$group_obj->Id); if ( $id ) { my ($val, $msg) = $group_obj->SetAttribute( Name => 'LDAPImport-gid-'.$id, Content => 1 ); unless ($val) { $RT::Logger->error("couldn't set attribute: $msg"); return; } } } else { $RT::Logger->debug( "Found new group $group->{Name} to create in RT" ); $self->_show_group_info( %args ); return; } } unless ($group_obj->Id) { $RT::Logger->error("We couldn't find or create $group->{Name}. This should never happen"); } return ($group_obj, $created); } =head3 find_rt_group Loads groups by Name and by the specified LDAP id. Attempts to resolve renames and other out-of-sync failures between RT and LDAP. =cut sub find_rt_group { my $self = shift; my %args = @_; my $group = $args{group}; my $group_obj = RT::Group->new($RT::SystemUser); $group_obj->LoadUserDefinedGroup( $group->{Name} ); return $group_obj unless $group->{'id'}; unless ( $group_obj->id ) { $RT::Logger->debug("No group in RT named $group->{Name}. Looking by $group->{id} LDAP id."); $group_obj = $self->find_rt_group_by_ldap_id( $group->{'id'} ); unless ( $group_obj ) { $RT::Logger->debug("No group in RT with LDAP id $group->{id}. Creating a new one."); return RT::Group->new($RT::SystemUser); } $RT::Logger->debug("No group in RT named $group->{Name}, but found group by LDAP id $group->{id}. Renaming the group."); # $group->Update will take care of the name return $group_obj; } my $attr_name = 'LDAPImport-gid-'. $group->{'id'}; my $rt_gid = $group_obj->FirstAttribute( $attr_name ); return $group_obj if $rt_gid; my $other_group = $self->find_rt_group_by_ldap_id( $group->{'id'} ); if ( $other_group ) { $RT::Logger->debug("Group with LDAP id $group->{id} exists, as well as group named $group->{Name}. Renaming both."); } elsif ( grep $_->Name =~ /^LDAPImport-gid-/, @{ $group_obj->Attributes->ItemsArrayRef } ) { $RT::Logger->debug("No group in RT with LDAP id $group->{id}, but group $group->{Name} has id. Renaming the group and creating a new one."); } else { $RT::Logger->debug("No group in RT with LDAP id $group->{id}, but group $group->{Name} exists and has no LDAP id. Assigning the id to the group."); if ( $args{import} ) { my ($status, $msg) = $group_obj->SetAttribute( Name => $attr_name, Content => 1 ); unless ( $status ) { $RT::Logger->error("Couldn't set attribute: $msg"); return undef; } $RT::Logger->debug("Assigned $group->{id} LDAP group id to $group->{Name}"); } else { $RT::Logger->debug( "Group $group->{'Name'} gets LDAP id $group->{id}" ); } return $group_obj; } # rename existing group to move it out of our way { my ($old, $new) = ($group_obj->Name, $group_obj->Name .' (LDAPImport '. time . ')'); if ( $args{import} ) { my ($status, $msg) = $group_obj->SetName( $new ); unless ( $status ) { $RT::Logger->error("Couldn't rename group from $old to $new: $msg"); return undef; } $RT::Logger->debug("Renamed group $old to $new"); } else { $RT::Logger->debug( "Group $old to be renamed to $new" ); } } return $other_group || RT::Group->new($RT::SystemUser); } =head3 find_rt_group_by_ldap_id Loads an RT::Group by the ldap provided id (different from RT's internal group id) =cut sub find_rt_group_by_ldap_id { my $self = shift; my $id = shift; my $groups = RT::Groups->new( RT->SystemUser ); $groups->LimitToUserDefinedGroups; my $attr_alias = $groups->Join( FIELD1 => 'id', TABLE2 => 'Attributes', FIELD2 => 'ObjectId' ); $groups->Limit( ALIAS => $attr_alias, FIELD => 'ObjectType', VALUE => 'RT::Group' ); $groups->Limit( ALIAS => $attr_alias, FIELD => 'Name', VALUE => 'LDAPImport-gid-'. $id ); return $groups->First; } =head3 add_group_members Iterate over the list of values in the C<Member_Attr> LDAP entry. Look up the appropriate username from LDAP. Add those users to the group. Remove members of the RT Group who are no longer members of the LDAP group. =cut sub add_group_members { my $self = shift; my %args = @_; my $group = $args{group}; my $groupname = $args{name}; my $ldap_entry = $args{ldap_entry}; $RT::Logger->debug("Processing group membership for $groupname"); my $members = $args{'info'}{'Member_Attr'}; unless (defined $members) { $RT::Logger->warn("No members found for $groupname in Member_Attr"); return; } if ($RT::LDAPImportGroupMembers) { $RT::Logger->debug("Importing members of group $groupname"); my @entries; my $attr = lc($RT::LDAPGroupMapping->{Member_Attr_Value} || 'dn'); # Lookup each DN's full entry, or... if ($attr eq 'dn') { @entries = grep defined, map { my @results = $self->_run_search( scope => 'base', base => $_, filter => $RT::LDAPFilter, ); $results[0] } @$members; } # ...or find all the entries in a single search by attribute. else { # I wonder if this will run into filter length limits? -trs, 22 Jan 2014 my $members = join "", map { "($attr=" . escape_filter_value($_) . ")" } @$members; @entries = $self->_run_search( base => $RT::LDAPBase, filter => "(&$RT::LDAPFilter(|$members))", ); } $self->_import_users( import => $args{import}, users => \@entries, ) or $RT::Logger->debug("Importing group members failed"); } my %rt_group_members; if ($args{group} and not $args{new}) { my $user_members = $group->UserMembersObj( Recursively => 0); # find members who are Disabled too so we don't try to add them below $user_members->FindAllRows; while ( my $member = $user_members->Next ) { $rt_group_members{$member->Name} = $member; } } elsif (not $args{import}) { $RT::Logger->debug("No group in RT, would create with members:"); } my $users = $self->_users; foreach my $member (@$members) { my $username; if (exists $users->{lc $member}) { next unless $username = $users->{lc $member}; } else { my $attr = lc($RT::LDAPGroupMapping->{Member_Attr_Value} || 'dn'); my $base = $attr eq 'dn' ? $member : $RT::LDAPBase; my $scope = $attr eq 'dn' ? 'base' : 'sub'; my $filter = $attr eq 'dn' ? $RT::LDAPFilter : "(&$RT::LDAPFilter($attr=" . escape_filter_value($member) . "))"; my @results = $self->_run_search( base => $base, scope => $scope, filter => $filter, ); unless ( @results ) { $users->{lc $member} = undef; $RT::Logger->error("No user found for $member who should be a member of $groupname"); next; } my $ldap_user = shift @results; $username = $self->_cache_user( ldap_entry => $ldap_user ); } if ( delete $rt_group_members{$username} ) { $RT::Logger->debug("\t$username\tin RT and LDAP"); next; } $RT::Logger->debug($group ? "\t$username\tin LDAP, adding to RT" : "\t$username"); next unless $args{import}; my $rt_user = RT::User->new($RT::SystemUser); my ($res,$msg) = $rt_user->Load( $username ); unless ($res) { $RT::Logger->warn("Unable to load $username: $msg"); next; } ($res,$msg) = $group->AddMember($rt_user->PrincipalObj->Id); unless ($res) { $RT::Logger->warn("Failed to add $username to $groupname: $msg"); } } for my $username (sort keys %rt_group_members) { $RT::Logger->debug("\t$username\tin RT, not in LDAP, removing"); next unless $args{import}; my ($res,$msg) = $group->DeleteMember($rt_group_members{$username}->PrincipalObj->Id); unless ($res) { $RT::Logger->warn("Failed to remove $username to $groupname: $msg"); } } } =head2 _show_group Show debugging information about the group record we're going to import when the groups reruns us with C<--import>. =cut sub _show_group { my $self = shift; my %args = @_; my $group = $args{group}; my $rt_group = RT::Group->new($RT::SystemUser); $rt_group->LoadUserDefinedGroup( $group->{Name} ); if ( $rt_group->Id ) { $RT::Logger->debug( "Found existing group $group->{Name} to update" ); $self->_show_group_info( %args, rt_group => $rt_group ); } else { $RT::Logger->debug( "Found new group $group->{Name} to create in RT" ); $self->_show_group_info( %args ); } } sub _show_group_info { my $self = shift; my %args = @_; my $group = $args{group}; my $rt_group = $args{rt_group}; $RT::Logger->debug( "\tRT Field\tRT Value -> LDAP Value" ); foreach my $key (sort keys %$group) { my $old_value; if ($rt_group) { eval { $old_value = $rt_group->$key() }; if ($group->{$key} && defined $old_value && $old_value eq $group->{$key}) { $old_value = 'unchanged'; } } $old_value ||= 'unset'; $RT::Logger->debug( "\t$key\t$old_value => $group->{$key}" ); } } =head3 disconnect_ldap Disconnects from the LDAP server. Takes no arguments, returns nothing. =cut sub disconnect_ldap { my $self = shift; my $ldap = $self->_ldap; return unless $ldap; $ldap->unbind; $ldap->disconnect; $self->_ldap(undef); return; } RT::Base->_ImportOverlays(); 1; �����������������������������rt-5.0.1/lib/RT/Catalog.pm��������������������������������������������������������������������������000644 �000765 �000024 �00000036637 14005011336 016023� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Catalog; use base 'RT::Record'; use Role::Basic 'with'; with "RT::Record::Role::Lifecycle", "RT::Record::Role::Roles" => { -rename => { # We provide ACL'd wraps of these. AddRoleMember => "_AddRoleMember", DeleteRoleMember => "_DeleteRoleMember", RoleGroup => "_RoleGroup", }, }, "RT::Record::Role::Rights"; require RT::ACE; =head1 NAME RT::Catalog - A logical set of assets =cut # For the Lifecycle role sub LifecycleType { "asset" } # Setup rights __PACKAGE__->AddRight( General => ShowCatalog => 'See catalogs' ); #loc __PACKAGE__->AddRight( Admin => AdminCatalog => 'Create, modify, and disable catalogs' ); #loc __PACKAGE__->AddRight( General => ShowAsset => 'See assets' ); #loc __PACKAGE__->AddRight( Staff => CreateAsset => 'Create assets' ); #loc __PACKAGE__->AddRight( Staff => ModifyAsset => 'Modify assets' ); #loc __PACKAGE__->AddRight( General => SeeCustomField => 'View custom field values' ); # loc __PACKAGE__->AddRight( Staff => ModifyCustomField => 'Modify custom field values' ); # loc __PACKAGE__->AddRight( Staff => SetInitialCustomField => 'Add custom field values only at object creation time'); # loc RT::ACE->RegisterCacheHandler(sub { my %args = ( Action => "", RightName => "", @_ ); return unless $args{Action} =~ /^(Grant|Revoke)$/i and $args{RightName} =~ /^(ShowCatalog|CreateAsset)$/; RT::Catalog->CacheNeedsUpdate(1); }); =head1 DESCRIPTION Catalogs are for assets what queues are for tickets or classes are for articles. It announces the rights for assets, and rights are granted at the catalog or global level. Asset custom fields are either applied globally to all Catalogs or individually to specific Catalogs. =over 4 =item id =item Name Limited to 255 characters. =item Description Limited to 255 characters. =item Lifecycle =item Disabled =item Creator =item Created =item LastUpdatedBy =item LastUpdated =back All of these are readable through methods of the same name and mutable through methods of the same name with C<Set> prefixed. The last four are automatically managed. =head1 METHODS =head2 Load ID or NAME Loads the specified Catalog into the current object. =cut sub Load { my $self = shift; my $id = shift; return unless $id; if ( $id =~ /\D/ ) { return $self->LoadByCols( Name => $id ); } else { return $self->SUPER::Load($id); } } =head2 Create PARAMHASH Create takes a hash of values and creates a row in the database. Available keys are: =over 4 =item Name =item Description =item Lifecycle =item HeldBy, Contact A single principal ID or array ref of principal IDs to add as members of the respective role groups for the new catalog. User Names and EmailAddresses may also be used, but Groups must be referenced by ID. =item Disabled =back Returns a tuple of (status, msg) on failure and (id, msg, non-fatal errors) on success, where the third value is an array reference of errors that occurred but didn't prevent creation. =cut sub Create { my $self = shift; my %args = ( Name => '', Description => '', Lifecycle => 'assets', HeldBy => undef, Contact => undef, Disabled => 0, @_ ); my @non_fatal_errors; return (0, $self->loc("Permission Denied")) unless $self->CurrentUserHasRight('AdminCatalog'); return (0, $self->loc('Invalid Name (names must be unique and may not be all digits)')) unless $self->ValidateName( $args{'Name'} ); $args{'Lifecycle'} ||= 'assets'; return (0, $self->loc('[_1] is not a valid lifecycle', $args{'Lifecycle'})) unless $self->ValidateLifecycle( $args{'Lifecycle'} ); RT->DatabaseHandle->BeginTransaction(); my ( $id, $msg ) = $self->SUPER::Create( map { $_ => $args{$_} } qw(Name Description Lifecycle Disabled), ); unless ($id) { RT->DatabaseHandle->Rollback(); return (0, $self->loc("Catalog create failed: [_1]", $msg)); } # Create role groups unless ($self->_CreateRoleGroups()) { RT->Logger->error("Couldn't create role groups for catalog ". $self->id); RT->DatabaseHandle->Rollback(); return (0, $self->loc("Couldn't create role groups for catalog")); } # Figure out users for roles my $roles = {}; push @non_fatal_errors, $self->_ResolveRoles( $roles, %args ); push @non_fatal_errors, $self->_AddRolesOnCreate( $roles, map { $_ => sub {1} } $self->Roles ); # Create transaction my ( $txn_id, $txn_msg, $txn ) = $self->_NewTransaction( Type => 'Create' ); unless ($txn_id) { RT->DatabaseHandle->Rollback(); return (0, $self->loc( 'Catalog Create txn failed: [_1]', $txn_msg )); } $self->CacheNeedsUpdate(1); RT->DatabaseHandle->Commit(); return ($id, $self->loc('Catalog #[_1] created: [_2]', $self->id, $args{'Name'}), \@non_fatal_errors); } =head2 ValidateName NAME Requires that Names contain at least one non-digit and doesn't already exist. =cut sub ValidateName { my $self = shift; my $name = shift; return 0 unless defined $name and length $name; return 0 unless $name =~ /\D/; my $catalog = RT::Catalog->new( RT->SystemUser ); $catalog->Load($name); return 0 if $catalog->id; return 1; } =head2 Delete Catalogs may not be deleted. Always returns failure. You should disable the catalog instead using C<< $catalog->SetDisabled(1) >>. =cut sub Delete { my $self = shift; return (0, $self->loc("Catalogs may not be deleted")); } =head2 CurrentUserCanSee Returns true if the current user can see the catalog via the I<ShowCatalog> or I<AdminCatalog> rights. =cut sub CurrentUserCanSee { my $self = shift; return $self->CurrentUserHasRight('ShowCatalog') || $self->CurrentUserHasRight('AdminCatalog'); } =head2 Owner Returns an L<RT::User> object for this catalog's I<Owner> role group. On error, returns undef. =head2 HeldBy Returns an L<RT::Group> object for this catalog's I<HeldBy> role group. The object may be unloaded if permissions aren't satisfied. =head2 Contacts Returns an L<RT::Group> object for this catalog's I<Contact> role group. The object may be unloaded if permissions aren't satisfied. =cut sub Owner { my $self = shift; my $group = $self->RoleGroup("Owner"); return unless $group and $group->id; return $group->UserMembersObj->First; } sub HeldBy { $_[0]->RoleGroup("HeldBy") } sub Contacts { $_[0]->RoleGroup("Contact") } =head2 AddRoleMember Checks I<AdminCatalog> before calling L<RT::Record::Role::Roles/_AddRoleMember>. =cut sub AddRoleMember { my $self = shift; return (0, $self->loc("No permission to modify this catalog")) unless $self->CurrentUserHasRight("AdminCatalog"); return $self->_AddRoleMember(@_); } =head2 DeleteRoleMember Checks I<AdminCatalog> before calling L<RT::Record::Role::Roles/_DeleteRoleMember>. =cut sub DeleteRoleMember { my $self = shift; return (0, $self->loc("No permission to modify this catalog")) unless $self->CurrentUserHasRight("AdminCatalog"); return $self->_DeleteRoleMember(@_); } =head2 RoleGroup An ACL'd version of L<RT::Record::Role::Roles/_RoleGroup>. Checks I<ShowCatalog>. =cut sub RoleGroup { my $self = shift; if ($self->CurrentUserCanSee) { return $self->_RoleGroup(@_); } else { return RT::Group->new( $self->CurrentUser ); } } =head2 AssetCustomFields Returns an L<RT::CustomFields> object containing all global and catalog-specific B<asset> custom fields. =cut sub AssetCustomFields { my $self = shift; my $cfs = RT::CustomFields->new( $self->CurrentUser ); if ($self->CurrentUserCanSee) { $cfs->SetContextObject( $self ); $cfs->LimitToGlobalOrObjectId( $self->Id ); $cfs->LimitToLookupType( RT::Asset->CustomFieldLookupType ); $cfs->ApplySortOrder; } else { $cfs->Limit( FIELD => 'id', VALUE => 0, SUBCLAUSE => 'acl' ); } return ($cfs); } =head1 INTERNAL METHODS =head2 CacheNeedsUpdate Takes zero or one arguments. If a true argument is provided, marks any Catalog caches as needing an update. This happens when catalogs are created, disabled/enabled, or modified. Returns nothing. If no arguments are provided, returns an epoch time that any catalog caches should be newer than. May be called as a class or object method. =cut sub CacheNeedsUpdate { my $class = shift; my $update = shift; if ($update) { RT->System->SetAttribute(Name => 'CatalogCacheNeedsUpdate', Content => time); return; } else { my $attribute = RT->System->FirstAttribute('CatalogCacheNeedsUpdate'); return $attribute ? $attribute->Content : 0; } } =head1 PRIVATE METHODS Documented for internal use only, do not call these from outside RT::Catalog itself. =head2 _Set Checks if the current user can I<AdminCatalog> before calling C<SUPER::_Set> and records a transaction against this object if C<SUPER::_Set> was successful. =cut sub _Set { my $self = shift; my %args = ( Field => undef, Value => undef, @_ ); return (0, $self->loc("Permission Denied")) unless $self->CurrentUserHasRight('AdminCatalog'); my $old = $self->_Value( $args{'Field'} ); my ($ok, $msg) = $self->SUPER::_Set(@_); # Only record the transaction if the _Set worked return ($ok, $msg) unless $ok; my $txn_type = "Set"; if ($args{'Field'} eq "Disabled") { if (not $old and $args{'Value'}) { $txn_type = "Disabled"; } elsif ($old and not $args{'Value'}) { $txn_type = "Enabled"; } } $self->CacheNeedsUpdate(1); my ($txn_id, $txn_msg, $txn) = $self->_NewTransaction( Type => $txn_type, Field => $args{'Field'}, NewValue => $args{'Value'}, OldValue => $old, ); return ($txn_id, scalar $txn->BriefDescription); } =head2 Lifecycle [CONTEXT_OBJ] Returns the current value of Lifecycle. Provide an optional asset object as context to check role-level rights in addition to catalog-level rights for ShowCatalog and AdminCatalog. (In the database, Lifecycle is stored as varchar(32).) =cut sub Lifecycle { my $self = shift; my $context_obj = shift; if ( $context_obj && $context_obj->CatalogObj->Id eq $self->Id && ( $context_obj->CurrentUserHasRight('ShowCatalog') or $context_obj->CurrentUserHasRight('AdminCatalog') ) ) { return ( $self->__Value('Lifecycle') ); } return ( $self->_Value('Lifecycle') ); } =head2 _Value Checks L</CurrentUserCanSee> before calling C<SUPER::_Value>. =cut sub _Value { my $self = shift; return unless $self->CurrentUserCanSee; return $self->SUPER::_Value(@_); } sub Table { "Catalogs" } sub _CoreAccessible { { id => { read => 1, type => 'int(11)', default => '' }, Name => { read => 1, type => 'varchar(255)', default => '', write => 1 }, Description => { read => 1, type => 'varchar(255)', default => '', write => 1 }, Lifecycle => { read => 1, type => 'varchar(32)', default => 'assets', write => 1 }, Disabled => { read => 1, type => 'int(2)', default => '0', write => 1 }, Creator => { read => 1, type => 'int(11)', default => '0', auto => 1 }, Created => { read => 1, type => 'datetime', default => '', auto => 1 }, LastUpdatedBy => { read => 1, type => 'int(11)', default => '0', auto => 1 }, LastUpdated => { read => 1, type => 'datetime', default => '', auto => 1 }, } } sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); # Role groups( HeldBy, Contact) my $objs = RT::Groups->new( $self->CurrentUser ); $objs->Limit( FIELD => 'Domain', VALUE => 'RT::Catalog-Role', CASESENSITIVE => 0 ); $objs->Limit( FIELD => 'Instance', VALUE => $self->Id ); $deps->Add( in => $objs ); # Custom Fields on assets _in_ this catalog $objs = RT::ObjectCustomFields->new( $self->CurrentUser ); $objs->Limit( FIELD => 'ObjectId', OPERATOR => '=', VALUE => $self->id, ENTRYAGGREGATOR => 'OR' ); $objs->Limit( FIELD => 'ObjectId', OPERATOR => '=', VALUE => 0, ENTRYAGGREGATOR => 'OR' ); my $cfs = $objs->Join( ALIAS1 => 'main', FIELD1 => 'CustomField', TABLE2 => 'CustomFields', FIELD2 => 'id', ); $objs->Limit( ALIAS => $cfs, FIELD => 'LookupType', OPERATOR => 'STARTSWITH', VALUE => 'RT::Catalog-' ); $deps->Add( in => $objs ); # Assets $objs = RT::Assets->new( $self->CurrentUser ); $objs->Limit( FIELD => "Catalog", VALUE => $self->Id ); $objs->{allow_deleted_search} = 1; $deps->Add( in => $objs ); } sub PreInflate { my $class = shift; my ( $importer, $uid, $data ) = @_; $class->SUPER::PreInflate( $importer, $uid, $data ); $data->{Name} = $importer->Qualify( $data->{Name} ); return if $importer->MergeBy( "Name", $class, $uid, $data ); return 1; } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Crypt.pm����������������������������������������������������������������������������000644 �000765 �000024 �00000060636 14005011336 015546� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Crypt; use 5.010; =head1 NAME RT::Crypt - encrypt/decrypt and sign/verify subsystem for RT =head1 DESCRIPTION This module provides support for encryption and signing of outgoing messages, as well as the decryption and verification of incoming emails using various encryption standards. Currently, L<GnuPG|RT::Crypt::GnuPG> and L<SMIME|RT::Crypt::SMIME> protocols are supported. =head1 CONFIGURATION You can control the configuration of this subsystem from RT's configuration file. Some options are available via the web interface, but to enable this functionality, you MUST start in the configuration file. For each protocol there is a hash with the same name in the configuration file. This hash controls RT-specific options regarding the protocol. It allows you to enable/disable each facility or change the format of messages; for example, GnuPG uses the following config: Set( %GnuPG, Enable => 1, ... other options ... ); C<Enable> is the only key that is generic for all protocols. A protocol may have additional options to fine-tune behaviour. =head2 %Crypt This config option hash chooses which protocols are decrypted and verified in incoming messages, which protocol is used for outgoing emails, and RT's behaviour on errors during decrypting and verification. RT will provide sane defaults for all of these options. By default, all enabled encryption protocols are decrypted on incoming mail; if you wish to limit this to a subset, you may, via: Set( %Crypt, ... Incoming => ['SMIME'], ... ); RT can currently only use one protocol to encrypt and sign outgoing email; this defaults to the first enabled protocol. You many specify it explicitly via: Set( %Crypt, ... Outgoing => 'GnuPG', ... ); You can allow users to encrypt data in the database by setting the C<AllowEncryptDataInDB> key to a true value; by default, this is disabled. Be aware that users must have rights to see and modify tickets to use this feature. =head2 Per-queue options Using the web interface, it is possible to enable signing and/or encrypting by default. As an administrative user of RT, navigate to the 'Admin' and 'Queues' menus, and select a queue. If at least one encryption protocol is enabled, information concerning available keys will be displayed, as well as options to enable signing and encryption. =head2 Error handling There are several global templates created in the database by default. RT uses these templates to send error messages to users or RT's owner. These templates have an 'Error:' or 'Error to RT owner:' prefix in the name. You can adjust the text of the messages using the web interface. Note that while C<$TicketObj>, C<$TransactionObj> and other variables usually available in RT's templates are not available in these templates, but each is passed alternate data structures can be used to build better messages; see the default templates and descriptions below. You can disable any particular notification by simply deleting the content of a template. Deleting the templates entirely is not suggested, as RT will log error messages when attempting to send mail usign them. =head3 Problems with public keys The 'Error: public key' template is used to inform the user that RT had problems with their public key, and thus will not be able to send encrypted content. There are several reasons why RT might fail to use a key; by default, the actual reason is not sent to the user, but sent to the RT owner using the 'Error to RT owner: public key' template. Possible reasons include "Not Found", "Ambiguous specification", "Wrong key usage", "Key revoked", "Key expired", "No CRL known", "CRL too old", "Policy mismatch", "Not a secret key", "Key not trusted" or "No specific reason given". In the 'Error: public key' template there are a few additional variables available: =over 4 =item $Message - user friendly error message =item $Reason - short reason as listed above =item $Recipient - recipient's identification =item $AddressObj - L<Email::Address> object containing recipient's email address =back As a message may have several invalid recipients, to avoid sending many emails to the RT owner, the system sends one message to the owner, grouped by recipient. In the 'Error to RT owner: public key' template a C<@BadRecipients> array is available where each element is a hash reference that describes one recipient using the same fields as described above: @BadRecipients = ( { Message => '...', Reason => '...', Recipient => '...', ...}, { Message => '...', Reason => '...', Recipient => '...', ...}, ... ) =head3 Private key doesn't exist The 'Error: no private key' template is used to inform the user that they sent an encrypted email to RT, but RT does not have the private key to decrypt it. In this template L<MIME::Entity> object C<$Message> is available, which is the originally received message. =head3 Invalid data The 'Error: bad encrypted data' template is used to inform the user that a message they sent had invalid data, and could not be handled. There are several possible reasons for this error, but most of them are data corruption or absence of expected information. In this template, the C<@Messages> array is available, and will contain a list of error messages. =head1 METHODS =head2 Protocols Returns the complete set of encryption protocols that RT implements; not all may be supported by this installation. =cut our @PROTOCOLS = ('GnuPG', 'SMIME'); our %PROTOCOLS = map { lc $_ => $_ } @PROTOCOLS; sub Protocols { return @PROTOCOLS; } =head2 EnabledProtocols Returns the set of enabled and available encryption protocols. =cut sub EnabledProtocols { my $self = shift; return grep RT->Config->Get($_)->{'Enable'}, $self->Protocols; } =head2 UseForOutgoing Returns the configured outgoing encryption protocol; see L<RT_Config/Crypt>. =cut sub UseForOutgoing { return RT->Config->Get('Crypt')->{'Outgoing'}; } =head2 EnabledOnIncoming Returns the list of encryption protocols that should be used for decryption and verification of incoming email; see L<RT_Config/Crypt>. =cut sub EnabledOnIncoming { return @{ scalar RT->Config->Get('Crypt')->{'Incoming'} }; } =head2 LoadImplementation CLASS Given the name of an encryption implementation (e.g. "GnuPG"), loads the L<RT::Crypt> class associated with it; return the classname on success, and undef on failure. =cut sub LoadImplementation { state %cache; my $proto = $PROTOCOLS{ lc $_[1] } or die "Unknown protocol '$_[1]'"; my $class = 'RT::Crypt::'. $proto; return $cache{ $class } if exists $cache{ $class }; if ($class->require) { return $cache{ $class } = $class; } else { RT->Logger->warn( "Could not load $class: $@" ); return $cache{ $class } = undef; } } =head2 SimpleImplementationCall Protocol => NAME, [...] Examines the caller of this method, and dispatches to the method of the same name on the correct L<RT::Crypt::Role> class based on the provided C<Protocol>. =cut sub SimpleImplementationCall { my $self = shift; my %args = (@_); my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing; my $method = (caller(1))[3]; $method =~ s/.*:://; my %res = $self->LoadImplementation( $protocol )->$method( %args ); $res{'Protocol'} = $protocol if keys %res; return %res; } =head2 FindProtectedParts Entity => MIME::Entity Looks for encrypted or signed parts of the given C<Entity>, using all L</EnabledOnIncoming> encryption protocols. For each node in the MIME hierarchy, L<RT::Crypt::Role/CheckIfProtected> for that L<MIME::Entity> is called on each L</EnabledOnIncoming> protocol. Any multipart nodes not claimed by those protocols are recursed into. Finally, L<RT::Crypt::Role/FindScatteredParts> is called on the top-most entity for each L</EnabledOnIncoming> protocol. Returns a list of hash references; each hash reference is guaranteed to contain a C<Protocol> key describing the protocol of the found part, and a C<Type> which is either C<encrypted> or C<signed>. The remaining keys are protocol-dependent; the hashref will be provided to L</VerifyDecrypt>. =cut sub FindProtectedParts { my $self = shift; my %args = ( Entity => undef, Skip => {}, Scattered => 1, @_ ); my $entity = $args{'Entity'}; return () if $args{'Skip'}{ $entity }; $args{'TopEntity'} ||= $entity; my @protocols = $self->EnabledOnIncoming; foreach my $protocol ( @protocols ) { my $class = $self->LoadImplementation( $protocol ); my %info = $class->CheckIfProtected( TopEntity => $args{'TopEntity'}, Entity => $entity, ); next unless keys %info; $args{'Skip'}{ $entity } = 1; $info{'Protocol'} = $protocol; return \%info; } if ( $entity->effective_type =~ /^multipart\/(?:signed|encrypted)/ ) { # if no module claimed that it supports these types then # we don't dive in and check sub-parts $args{'Skip'}{ $entity } = 1; return (); } my @res; # not protected itself, look inside push @res, $self->FindProtectedParts( %args, Entity => $_, Scattered => 0, ) foreach grep !$args{'Skip'}{$_}, $entity->parts; if ( $args{'Scattered'} ) { my %parent; my $filter; $filter = sub { $parent{$_[0]} = $_[1]; unless ( $_[0]->is_multipart ) { return () if $args{'Skip'}{$_[0]}; return $_[0]; } return map $filter->($_, $_[0]), grep !$args{'Skip'}{$_}, $_[0]->parts; }; my @parts = $filter->($entity); return @res unless @parts; foreach my $protocol ( @protocols ) { my $class = $self->LoadImplementation( $protocol ); my @list = $class->FindScatteredParts( Entity => $args{'TopEntity'}, Parts => \@parts, Parents => \%parent, Skip => $args{'Skip'} ); next unless @list; $_->{'Protocol'} = $protocol foreach @list; push @res, @list; @parts = grep !$args{'Skip'}{$_}, @parts; } } return @res; } =head2 SignEncrypt Entity => ENTITY, [Sign => 1], [Encrypt => 1], [Recipients => ARRAYREF], [Signer => NAME], [Protocol => NAME], [Passphrase => VALUE] Takes a L<MIME::Entity> object, and signs and/or encrypts it using the given C<Protocol>. If not set, C<Recipients> for encryption will be set by examining the C<To>, C<Cc>, and C<Bcc> headers of the MIME entity. If not set, C<Signer> defaults to the C<From> of the MIME entity. C<Passphrase>, if not provided, will be retrieved using L<RT::Crypt::Role/GetPassphrase>. Returns a hash with at least the following keys: =over =item exit_code True if there was an error encrypting or signing. =item message An un-localized error message desribing the problem. =back =cut sub SignEncrypt { my $self = shift; my %args = ( Sign => 1, Encrypt => 1, @_, ); my $entity = $args{'Entity'}; if ( $args{'Sign'} && !defined $args{'Signer'} ) { $args{'Signer'} = $self->UseKeyForSigning || do { my ($addr) = map {Email::Address->parse( Encode::decode( "UTF-8", $_ ) )} $entity->head->get( 'From' ); $addr ? $addr->address : undef }; } if ( $args{'Encrypt'} && !$args{'Recipients'} ) { my %seen; $args{'Recipients'} = [ grep $_ && !$seen{ $_ }++, map $_->address, map Email::Address->parse( Encode::decode("UTF-8", $_ ) ), map $entity->head->get( $_ ), qw(To Cc Bcc) ]; } return $self->SimpleImplementationCall( %args ); } =head2 SignEncryptContent Content => STRINGREF, [Sign => 1], [Encrypt => 1], [Recipients => ARRAYREF], [Signer => NAME], [Protocol => NAME], [Passphrase => VALUE] Signs and/or encrypts a string, which is passed by reference. C<Recipients> defaults to C</UseKeyForSigning>, and C<Recipients> defaults to the global L<RT::Config/CorrespondAddress>. All other arguments and return values are identical to L</SignEncrypt>. =cut sub SignEncryptContent { my $self = shift; my %args = (@_); if ( $args{'Sign'} && !defined $args{'Signer'} ) { $args{'Signer'} = $self->UseKeyForSigning; } if ( $args{'Encrypt'} && !$args{'Recipients'} ) { $args{'Recipients'} = [ RT->Config->Get('CorrespondAddress') ]; } return $self->SimpleImplementationCall( %args ); } =head2 DrySign Signer => KEY Signs a small message with the key, to make sure the key exists and we have a useable passphrase. The Signer argument MUST be a key identifier of the signer: either email address, key id or finger print. Returns a true value if all went well. =cut sub DrySign { my $self = shift; my $mime = MIME::Entity->build( Type => "text/plain", From => 'nobody@localhost', To => 'nobody@localhost', Subject => "dry sign", Data => ['t'], ); my %res = $self->SignEncrypt( @_, Sign => 1, Encrypt => 0, Entity => $mime, ); return $res{exit_code} == 0; } =head2 VerifyDecrypt Entity => ENTITY [, Passphrase => undef ] Locates all protected parts of the L<MIME::Entity> object C<ENTITY>, as found by L</FindProtectedParts>, and calls L<RT::Crypt::Role/VerifyDecrypt> from the appropriate L<RT::Crypt::Role> class on each. C<Passphrase>, if not provided, will be retrieved using L<RT::Crypt::Role/GetPassphrase>. Returns a list of the hash references returned from L<RT::Crypt::Role/VerifyDecrypt>. =cut sub VerifyDecrypt { my $self = shift; my %args = ( Entity => undef, Recursive => 1, @_ ); my @res; my @protected = $self->FindProtectedParts( Entity => $args{'Entity'} ); foreach my $protected ( @protected ) { my %res = $self->SimpleImplementationCall( %args, Protocol => $protected->{'Protocol'}, Info => $protected ); # Let the header be modified so continuations are handled my $modify = $res{status_on}->head->modify; $res{status_on}->head->modify(1); $res{status_on}->head->add( "X-RT-" . $protected->{'Protocol'} . "-Status" => Encode::encode( "UTF-8", $res{'status'} ) ); $res{status_on}->head->modify($modify); push @res, \%res; } push @res, $self->VerifyDecrypt( %args ) if $args{Recursive} and @res and not grep {$_->{'exit_code'}} @res; return @res; } =head2 DecryptContent Protocol => NAME, Content => STRINGREF, [Passphrase => undef] Decrypts the content in the string reference in-place. All other arguments and return values are identical to L</VerifyDecrypt>. =cut sub DecryptContent { return shift->SimpleImplementationCall( @_ ); } =head2 ParseStatus Protocol => NAME, Status => STRING Takes a C<String> describing the status of verification/decryption, usually as stored in a MIME header. Parses it and returns array of hash references, one for each operation. Each hashref contains at least three keys: =over =item Operation The classification of the process whose status is being reported upon. Valid values include C<Sign>, C<Encrypt>, C<Decrypt>, C<Verify>, C<PassphraseCheck>, C<RecipientsCheck> and C<Data>. =item Status Whether the operation was successful; contains C<DONE> on success. Other possible values include C<ERROR>, C<BAD>, or C<MISSING>. =item Message An un-localized user friendly message. =back =cut sub ParseStatus { my $self = shift; my %args = ( Protocol => undef, Status => '', @_ ); return $self->LoadImplementation( $args{'Protocol'} )->ParseStatus( $args{'Status'} ); } =head2 UseKeyForSigning [KEY] Returns or sets the identifier of the key that should be used for signing. Returns the current value when called without arguments; sets the new value when called with one argument and unsets if it's undef. This cache is cleared at the end of every request. =cut sub UseKeyForSigning { my $self = shift; state $key; if ( @_ ) { $key = $_[0]; } return $key; } =head2 UseKeyForEncryption [KEY [, VALUE]] Gets or sets keys to use for encryption. When passed no arguments, clears the cache. When passed just a key, returns the encryption key previously stored for that key. When passed two (or more) keys, stores them associatively. This cache is reset at the end of every request. =cut sub UseKeyForEncryption { my $self = shift; state %key; unless ( @_ ) { %key = (); } elsif ( @_ > 1 ) { %key = (%key, @_); $key{ lc($_) } = delete $key{ $_ } foreach grep lc ne $_, keys %key; } else { return $key{ $_[0] }; } return (); } =head2 GetKeysForEncryption Recipient => EMAIL, Protocol => NAME Returns the list of keys which are suitable for encrypting mail to the given C<Recipient>. Generally this is equivalent to L</GetKeysInfo> with a C<Type> of <private>, but encryption protocols may further limit which keys can be used for encryption, as opposed to signing. =cut sub CheckRecipients { my $self = shift; my @recipients = (@_); my ($status, @issues) = (1, ()); my $trust = sub { 1 }; if ( $self->UseForOutgoing eq 'SMIME' ) { $trust = sub { $_[0]->{'TrustLevel'} > 0 or RT->Config->Get('SMIME')->{AcceptUntrustedCAs} }; } elsif ( $self->UseForOutgoing eq 'GnuPG' ) { $trust = sub { $_[0]->{'TrustLevel'} > 0 }; } my %seen; foreach my $address ( grep !$seen{ lc $_ }++, map $_->address, @recipients ) { my %res = $self->GetKeysForEncryption( Recipient => $address ); if ( $res{'info'} && @{ $res{'info'} } == 1 and $trust->($res{'info'}[0]) ) { # One key, which is trusted, or we can sign with an # untrusted key (aka SMIME with AcceptUntrustedCAs) next; } my $user = RT::User->new( RT->SystemUser ); $user->LoadByEmail( $address ); # it's possible that we have no User record with the email $user = undef unless $user->id; if ( my $fpr = RT::Crypt->UseKeyForEncryption( $address ) ) { if ( $res{'info'} && @{ $res{'info'} } ) { next if grep lc $_->{'Fingerprint'} eq lc $fpr, grep $trust->($_), @{ $res{'info'} }; } $status = 0; my %issue = ( EmailAddress => $address, $user? (User => $user) : (), Keys => undef, ); $issue{'Message'} = "Selected key either is not trusted or doesn't exist anymore."; #loc push @issues, \%issue; next; } my $prefered_key; $prefered_key = $user->PreferredKey if $user; #XXX: prefered key is not yet implemented... # classify errors $status = 0; my %issue = ( EmailAddress => $address, $user? (User => $user) : (), Keys => undef, ); unless ( $res{'info'} && @{ $res{'info'} } ) { # no key $issue{'Message'} = "There is no key suitable for encryption."; #loc } elsif ( @{ $res{'info'} } == 1 && !$res{'info'}[0]{'TrustLevel'} ) { # trust is not set $issue{'Message'} = "There is one suitable key, but trust level is not set."; #loc } else { # multiple keys $issue{'Message'} = "There are several keys suitable for encryption."; #loc } push @issues, \%issue; } return ($status, @issues); } sub GetKeysForEncryption { my $self = shift; my %args = @_%2? (Recipient => @_) : (Protocol => undef, Recipient => undef, @_ ); return $self->SimpleImplementationCall( %args ); } =head2 GetKeysForSigning Signer => EMAIL, Protocol => NAME Returns the list of keys which are suitable for signing mail from the given C<Signer>. Generally this is equivalent to L</GetKeysInfo> with a C<Type> of <private>, but encryption protocols may further limit which keys can be used for signing, as opposed to encryption. =cut sub GetKeysForSigning { my $self = shift; my %args = @_%2? (Signer => @_) : (Protocol => undef, Signer => undef, @_); return $self->SimpleImplementationCall( %args ); } =head2 GetPublicKeyInfo Protocol => NAME, KEY => EMAIL As per L</GetKeyInfo>, but the C<Type> is forced to C<public>. =cut sub GetPublicKeyInfo { return (shift)->GetKeyInfo( @_, Type => 'public' ); } =head2 GetPrivateKeyInfo Protocol => NAME, KEY => EMAIL As per L</GetKeyInfo>, but the C<Type> is forced to C<private>. =cut sub GetPrivateKeyInfo { return (shift)->GetKeyInfo( @_, Type => 'private' ); } =head2 GetKeyInfo Protocol => NAME, Type => ('public'|'private'), KEY => EMAIL As per L</GetKeysInfo>, but only the first matching key is returned in the C<info> value of the result. =cut sub GetKeyInfo { my $self = shift; my %res = $self->GetKeysInfo( @_ ); $res{'info'} = $res{'info'}->[0]; return %res; } =head2 GetKeysInfo Protocol => NAME, Type => ('public'|'private'), Key => EMAIL Looks up information about the public or private keys (as determined by C<Type>) for the email address C<Key>. As each protocol has its own key store, C<Protocol> is also required. If no C<Key> is provided and a true value for C<Force> is given, returns all keys. The return value is a hash containing C<exit_code> and C<message> in the case of failure, or C<info>, which is an array reference of key information. Each key is represented as a hash reference; the keys are protocol-dependent, but will at least contain: =over =item Protocol The name of the protocol of this key =item Created An L<RT::Date> of the date the key was created; undef if unset. =item Expire An L<RT::Date> of the date the key expires; undef if the key does not expire. =item Fingerprint A fingerprint unique to this key =item Formatted A formatted string representation of the key =item User An array reference of associated user data, each of which is a hashref containing at least a C<String> value, which is a C<< Alice Example <alice@example.com> >> style email address. Each may also contain C<Created> and C<Expire> keys, which are L<RT::Date> objects. =back =cut sub GetKeysInfo { my $self = shift; my %args = @_%2 ? (Key => @_) : ( Protocol => undef, Key => undef, @_ ); return $self->SimpleImplementationCall( %args ); } 1; ��������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ObjectTopics.pm���������������������������������������������������������������������000644 �000765 �000024 �00000005330 14005011336 017023� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 warnings; use strict; package RT::ObjectTopics; use base 'RT::SearchBuilder'; sub Table {'ObjectTopics'} # {{{ LimitToTopic =head2 LimitToTopic FIELD Returns values for the topic with Id FIELD =cut sub LimitToTopic { my $self = shift; my $cf = shift; return ($self->Limit( FIELD => 'Topic', VALUE => $cf, OPERATOR => '=')); } # }}} # {{{ LimitToObject =head2 LimitToObject OBJ Returns associations for the given OBJ only =cut sub LimitToObject { my $self = shift; my $object = shift; $self->Limit( FIELD => 'ObjectType', VALUE => ref($object)); $self->Limit( FIELD => 'ObjectId', VALUE => $object->Id); } # }}} RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Scrip.pm����������������������������������������������������������������������������000644 �000765 �000024 �00000075010 14005011336 015515� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Scrip - an RT Scrip object =head1 SYNOPSIS use RT::Scrip; =head1 DESCRIPTION =head1 METHODS =cut package RT::Scrip; use strict; use warnings; use base 'RT::Record'; use RT::Queue; use RT::Template; use RT::ScripCondition; use RT::ScripAction; use RT::Scrips; use RT::ObjectScrip; sub Table {'Scrips'} # {{{ sub Create =head2 Create Creates a new entry in the Scrips table. Takes a paramhash with: Queue => 0, Description => undef, Template => undef, ScripAction => undef, ScripCondition => undef, CustomPrepareCode => undef, CustomCommitCode => undef, CustomIsApplicableCode => undef, Returns (retval, msg); retval is 0 for failure or scrip id. msg is a textual description of what happened. =cut sub Create { my $self = shift; my %args = ( Queue => 0, Template => undef, # name or id ScripAction => 0, # name or id ScripCondition => 0, # name or id Stage => 'TransactionCreate', Description => undef, CustomPrepareCode => undef, CustomCommitCode => undef, CustomIsApplicableCode => undef, @_ ); if ($args{CustomPrepareCode} || $args{CustomCommitCode} || $args{CustomIsApplicableCode}) { unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'ExecuteCode' ) ) { return ( 0, $self->loc('Permission Denied') ); } } unless ( $args{'Queue'} ) { unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'ModifyScrips' ) ) { return ( 0, $self->loc('Permission Denied') ); } $args{'Queue'} = 0; # avoid undef sneaking in } else { my $QueueObj = RT::Queue->new( $self->CurrentUser ); $QueueObj->Load( $args{'Queue'} ); unless ( $QueueObj->id ) { return ( 0, $self->loc('Invalid queue') ); } unless ( $QueueObj->CurrentUserHasRight('ModifyScrips') ) { return ( 0, $self->loc('Permission Denied') ); } $args{'Queue'} = $QueueObj->id; } #TODO +++ validate input return ( 0, $self->loc("Action is mandatory argument") ) unless $args{'ScripAction'}; my $action = RT::ScripAction->new( $self->CurrentUser ); $action->Load( $args{'ScripAction'} ); return ( 0, $self->loc( "Action '[_1]' not found", $args{'ScripAction'} ) ) unless $action->Id; return ( 0, $self->loc("Template is mandatory argument") ) unless $args{'Template'}; my $template = RT::Template->new( $self->CurrentUser ); if ( $args{'Template'} =~ /\D/ ) { $template->LoadByName( Name => $args{'Template'}, Queue => $args{'Queue'} ); return ( 0, $self->loc( "Global template '[_1]' not found", $args{'Template'} ) ) if !$template->Id && !$args{'Queue'}; return ( 0, $self->loc( "Global or queue specific template '[_1]' not found", $args{'Template'} ) ) if !$template->Id; } else { $template->Load( $args{'Template'} ); return ( 0, $self->loc( "Template '[_1]' not found", $args{'Template'} ) ) unless $template->Id; return (0, $self->loc( "Template '[_1]' is not global" )) if !$args{'Queue'} && $template->Queue; return (0, $self->loc( "Template '[_1]' is not global nor queue specific" )) if $args{'Queue'} && $template->Queue && $template->Queue != $args{'Queue'}; } return ( 0, $self->loc("Condition is mandatory argument") ) unless $args{'ScripCondition'}; my $condition = RT::ScripCondition->new( $self->CurrentUser ); $condition->Load( $args{'ScripCondition'} ); return ( 0, $self->loc( "Condition '[_1]' not found", $args{'ScripCondition'} ) ) unless $condition->Id; if ( $args{'Stage'} eq 'Disabled' ) { $RT::Logger->warning("Disabled Stage is deprecated"); $args{'Stage'} = 'TransactionCreate'; $args{'Disabled'} = 1; } $args{'Disabled'} ||= 0; my ( $id, $msg ) = $self->SUPER::Create( Template => $template->Name, ScripCondition => $condition->id, ScripAction => $action->Id, Disabled => $args{'Disabled'}, Description => $args{'Description'}, CustomPrepareCode => $args{'CustomPrepareCode'}, CustomCommitCode => $args{'CustomCommitCode'}, CustomIsApplicableCode => $args{'CustomIsApplicableCode'}, ); return ( $id, $msg ) unless $id; (my $status, $msg) = RT::ObjectScrip->new( $self->CurrentUser )->Add( Scrip => $self, Stage => $args{'Stage'}, ObjectId => $args{'Queue'}, SortOrder => $args{'ObjectSortOrder'}, ); $RT::Logger->error( "Couldn't add scrip: $msg" ) unless $status; return ( $id, $self->loc('Scrip Created') ); } =head2 Delete Delete this object =cut sub Delete { my $self = shift; unless ( $self->CurrentUserHasRight('ModifyScrips') ) { return ( 0, $self->loc('Permission Denied') ); } RT::ObjectScrip->new( $self->CurrentUser )->DeleteAll( Scrip => $self ); return ( $self->SUPER::Delete(@_) ); } sub IsGlobal { return shift->IsAdded(0) } sub IsAdded { my $self = shift; my $record = RT::ObjectScrip->new( $self->CurrentUser ); $record->LoadByCols( Scrip => $self->id, ObjectId => shift || 0 ); return undef unless $record->id; return $record; } sub IsAddedToAny { my $self = shift; my $record = RT::ObjectScrip->new( $self->CurrentUser ); $record->LoadByCols( Scrip => $self->id ); return $record->id ? 1 : 0; } sub AddedTo { my $self = shift; return RT::ObjectScrip->new( $self->CurrentUser ) ->AddedTo( Scrip => $self ); } sub NotAddedTo { my $self = shift; return RT::ObjectScrip->new( $self->CurrentUser ) ->NotAddedTo( Scrip => $self ); } =head2 AddToObject Adds (applies) the current scrip to the provided queue (ObjectId). Accepts a param hash of: =over =item C<ObjectId> Queue name or id. 0 makes the scrip global. =item C<Stage> Stage to run in. Valid stages are TransactionCreate or TransactionBatch. Defaults to TransactionCreate. As of RT 4.2, Disabled is no longer a stage. =item C<Template> Name of global or queue-specific template for the scrip. Use 'Blank' for non-notification scrips. =item C<SortOrder> Number indicating the relative order the scrip should run in. =back Returns (val, message). If val is false, the message contains an error message. =cut sub AddToObject { my $self = shift; my %args = @_%2? (ObjectId => @_) : (@_); # Default Stage explicitly rather than in %args assignment to handle # Stage coming in set to undef. $args{'Stage'} //= 'TransactionCreate'; my $queue; if ( $args{'ObjectId'} ) { $queue = RT::Queue->new( $self->CurrentUser ); $queue->Load( $args{'ObjectId'} ); return (0, $self->loc('Invalid queue')) unless $queue->id; $args{'ObjectId'} = $queue->id; } return ( 0, $self->loc('Permission Denied') ) unless $self->CurrentUser->PrincipalObj->HasRight( Object => $queue || $RT::System, Right => 'ModifyScrips', ) ; my $tname = $self->Template; my $template = RT::Template->new( $self->CurrentUser ); $template->LoadByName( Queue => $queue? $queue->id : 0, Name => $tname ); unless ( $template->id ) { if ( $queue ) { return (0, $self->loc('No template [_1] in queue [_2] or global', $tname, $queue->Name||$queue->id)); } else { return (0, $self->loc('No global template [_1]', $tname)); } } my $rec = RT::ObjectScrip->new( $self->CurrentUser ); return $rec->Add( %args, Scrip => $self ); } =head2 RemoveFromObject Removes the current scrip to the provided queue (ObjectId). Accepts a param hash of: =over =item C<ObjectId> Queue name or id. 0 makes the scrip global. =back Returns (val, message). If val is false, the message contains an error message. =cut sub RemoveFromObject { my $self = shift; my %args = @_%2? (ObjectId => @_) : (@_); my $queue; if ( $args{'ObjectId'} ) { $queue = RT::Queue->new( $self->CurrentUser ); $queue->Load( $args{'ObjectId'} ); return (0, $self->loc('Invalid queue id')) unless $queue->id; } return ( 0, $self->loc('Permission Denied') ) unless $self->CurrentUser->PrincipalObj->HasRight( Object => $queue || $RT::System, Right => 'ModifyScrips', ) ; my $rec = RT::ObjectScrip->new( $self->CurrentUser ); $rec->LoadByCols( Scrip => $self->id, ObjectId => $args{'ObjectId'} ); return (0, $self->loc('Scrip is not added') ) unless $rec->id; return $rec->Delete; } =head2 ActionObj Retuns an RT::Action object with this Scrip's Action =cut sub ActionObj { my $self = shift; unless ( defined $self->{'ScripActionObj'} ) { require RT::ScripAction; $self->{'ScripActionObj'} = RT::ScripAction->new( $self->CurrentUser ); $self->{'ScripActionObj'}->Load( $self->ScripAction ); } return ( $self->{'ScripActionObj'} ); } =head2 ConditionObj Retuns an L<RT::ScripCondition> object with this Scrip's IsApplicable =cut sub ConditionObj { my $self = shift; my $res = RT::ScripCondition->new( $self->CurrentUser ); $res->Load( $self->ScripCondition ); return $res; } =head2 LoadModules Loads scrip's condition and action modules. =cut sub LoadModules { my $self = shift; $self->ConditionObj->LoadCondition; $self->ActionObj->LoadAction; } =head2 TemplateObj Retuns an RT::Template object with this Scrip's Template =cut sub TemplateObj { my $self = shift; my $queue = shift; my $res = RT::Template->new( $self->CurrentUser ); $res->LoadByName( Queue => $queue, Name => $self->Template ); return $res; } =head2 Stage Takes TicketObj named argument and returns scrip's stage when added to ticket's queue. =cut sub Stage { my $self = shift; my %args = ( TicketObj => undef, @_ ); my $queue = $args{'TicketObj'}->Queue; my $rec = RT::ObjectScrip->new( $self->CurrentUser ); $rec->LoadByCols( Scrip => $self->id, ObjectId => $queue ); return $rec->Stage if $rec->id; $rec->LoadByCols( Scrip => $self->id, ObjectId => 0 ); return $rec->Stage if $rec->id; return undef; } =head2 FriendlyStage($Stage) Helper function that returns a localized human-readable version of the C<$Stage> argument. =cut sub FriendlyStage { my ( $class, $stage ) = @_; my $stage_i18n_lookup = { TransactionCreate => 'Normal', # loc TransactionBatch => 'Batch', # loc TransactionBatchDisabled => 'Batch (disabled by config)', # loc }; $stage = 'TransactionBatchDisabled' if $stage eq 'TransactionBatch' and not RT->Config->Get('UseTransactionBatch'); return $stage_i18n_lookup->{$stage}; } =head2 Apply { TicketObj => undef, TransactionObj => undef} This method instantiates the ScripCondition and ScripAction objects for a single execution of this scrip. it then calls the IsApplicable method of the ScripCondition. If that succeeds, it calls the Prepare method of the ScripAction. If that succeeds, it calls the Commit method of the ScripAction. Usually, the ticket and transaction objects passed to this method should be loaded by the SuperUser role =cut # XXX TODO : This code appears to be obsoleted in favor of similar code in Scrips->Apply. # Why is this here? Is it still called? sub Apply { my $self = shift; my %args = ( TicketObj => undef, TransactionObj => undef, @_ ); $RT::Logger->debug("Now applying scrip ".$self->Id . " for transaction ".$args{'TransactionObj'}->id); my $ApplicableTransactionObj = $self->IsApplicable( TicketObj => $args{'TicketObj'}, TransactionObj => $args{'TransactionObj'} ); unless ( $ApplicableTransactionObj ) { return undef; } if ( $ApplicableTransactionObj->id != $args{'TransactionObj'}->id ) { $RT::Logger->debug("Found an applicable transaction ".$ApplicableTransactionObj->Id . " in the same batch with transaction ".$args{'TransactionObj'}->id); } #If it's applicable, prepare and commit it $RT::Logger->debug("Now preparing scrip ".$self->Id . " for transaction ".$ApplicableTransactionObj->id); unless ( $self->Prepare( TicketObj => $args{'TicketObj'}, TransactionObj => $ApplicableTransactionObj ) ) { return undef; } $RT::Logger->debug("Now commiting scrip ".$self->Id . " for transaction ".$ApplicableTransactionObj->id); unless ( $self->Commit( TicketObj => $args{'TicketObj'}, TransactionObj => $ApplicableTransactionObj) ) { return undef; } $RT::Logger->debug("We actually finished scrip ".$self->Id . " for transaction ".$ApplicableTransactionObj->id); return (1); } =head2 IsApplicable Calls the Condition object's IsApplicable method Upon success, returns the applicable Transaction object. Otherwise, undef is returned. If the Scrip is in the TransactionCreate Stage (the usual case), only test the associated Transaction object to see if it is applicable. For Scrips in the TransactionBatch Stage, test all Transaction objects created during the Ticket object's lifetime, and returns the first one that is applicable. =cut sub IsApplicable { my $self = shift; my %args = ( TicketObj => undef, TransactionObj => undef, @_ ); my $return; eval { my @Transactions; my $stage = $self->Stage( TicketObj => $args{'TicketObj'} ); unless ( $stage ) { $RT::Logger->error( "Scrip #". $self->id ." is not applied to" ." queue #". $args{'TicketObj'}->Queue ); return (undef); } elsif ( $stage eq 'TransactionCreate') { # Only look at our current Transaction @Transactions = ( $args{'TransactionObj'} ); } elsif ( $stage eq 'TransactionBatch') { # Look at all Transactions in this Batch @Transactions = @{ $args{'TicketObj'}->TransactionBatch || [] }; } else { $RT::Logger->error( "Unknown Scrip stage: '$stage'" ); return (undef); } my $ConditionObj = $self->ConditionObj; foreach my $TransactionObj ( @Transactions ) { # in TxnBatch stage we can select scrips that are not applicable to all txns my $txn_type = $TransactionObj->Type; next unless( $ConditionObj->ApplicableTransTypes =~ /(?:^|,)(?:Any|\Q$txn_type\E)(?:,|$)/i ); # Load the scrip's Condition object $ConditionObj->LoadCondition( ScripObj => $self, TicketObj => $args{'TicketObj'}, TransactionObj => $TransactionObj, ); if ( $ConditionObj->IsApplicable() ) { # We found an application Transaction -- return it $return = $TransactionObj; last; } } }; if ($@) { $RT::Logger->error( "Scrip IsApplicable " . $self->Id . " died. - " . $@ ); return (undef); } return ($return); } =head2 Prepare Calls the action object's prepare method =cut sub Prepare { my $self = shift; my %args = ( TicketObj => undef, TransactionObj => undef, @_ ); my $return; eval { $self->ActionObj->LoadAction( ScripObj => $self, TicketObj => $args{'TicketObj'}, TransactionObj => $args{'TransactionObj'}, TemplateObj => $self->TemplateObj( $args{'TicketObj'}->Queue ), ); $return = $self->ActionObj->Prepare(); }; if ($@) { $RT::Logger->error( "Scrip Prepare " . $self->Id . " died. - " . $@ ); return (undef); } unless ($return) { } return ($return); } =head2 Commit Calls the action object's commit method =cut sub Commit { my $self = shift; my %args = ( TicketObj => undef, TransactionObj => undef, @_ ); my $return; eval { $return = $self->ActionObj->Commit(); }; #Searchbuilder caching isn't perfectly coherent. got to reload the ticket object, since it # may have changed $args{'TicketObj'}->Load( $args{'TicketObj'}->Id ); if ($@) { $RT::Logger->error( "Scrip Commit " . $self->Id . " died. - " . $@ ); return (undef); } # Not destroying or weakening hte Action and Condition here could cause a # leak return ($return); } # does an acl check and then passes off the call sub _Set { my $self = shift; my %args = ( Field => undef, Value => undef, @_, ); unless ( $self->CurrentUserHasRight('ModifyScrips') ) { $RT::Logger->debug( "CurrentUser can't modify Scrips" ); return ( 0, $self->loc('Permission Denied') ); } if (exists $args{Value}) { if ($args{Field} eq 'CustomIsApplicableCode' || $args{Field} eq 'CustomPrepareCode' || $args{Field} eq 'CustomCommitCode') { unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'ExecuteCode' ) ) { return ( 0, $self->loc('Permission Denied') ); } } elsif ($args{Field} eq 'Queue') { if ($args{Value}) { # moving to another queue my $queue = RT::Queue->new( $self->CurrentUser ); $queue->Load($args{Value}); unless ($queue->Id and $queue->CurrentUserHasRight('ModifyScrips')) { return ( 0, $self->loc('Permission Denied') ); } } else { # moving to global unless ($self->CurrentUser->HasRight( Object => RT->System, Right => 'ModifyScrips' )) { return ( 0, $self->loc('Permission Denied') ); } } } elsif ($args{Field} eq 'Template') { my $template = RT::Template->new( $self->CurrentUser ); $template->Load($args{Value}); unless ($template->Id and $template->CurrentUserCanRead) { return ( 0, $self->loc('Permission Denied') ); } } } return $self->SUPER::_Set(@_); } # does an acl check and then passes off the call sub _Value { my $self = shift; return unless $self->CurrentUserHasRight('ShowScrips'); return $self->__Value(@_); } =head2 ACLEquivalenceObjects Having rights on any of the queues the scrip applies to is equivalent to having rights on the scrip. =cut sub ACLEquivalenceObjects { my $self = shift; return unless $self->id; return @{ $self->AddedTo->ItemsArrayRef }; } =head2 CompileCheck This routine compile-checks the custom prepare, commit, and is-applicable code to see if they are syntactically valid Perl. We eval them in a codeblock to avoid actually executing the code. If one of the fields has a compile error, only the first is reported. Returns an (ok, message) pair. =cut sub CompileCheck { my $self = shift; for my $method (qw/CustomPrepareCode CustomCommitCode CustomIsApplicableCode/) { my $code = $self->$method; next if !defined($code); do { no strict 'vars'; eval "sub { $code \n }"; }; next if !$@; my $error = $@; return (0, $self->loc("Couldn't compile [_1] codeblock '[_2]': [_3]", $method, $code, $error)); } } =head2 SetScripAction =cut sub SetScripAction { my $self = shift; my $value = shift; return ( 0, $self->loc("Action is mandatory argument") ) unless $value; require RT::ScripAction; my $action = RT::ScripAction->new( $self->CurrentUser ); $action->Load($value); return ( 0, $self->loc( "Action '[_1]' not found", $value ) ) unless $action->Id; return $self->_Set( Field => 'ScripAction', Value => $action->Id ); } =head2 SetScripCondition =cut sub SetScripCondition { my $self = shift; my $value = shift; return ( 0, $self->loc("Condition is mandatory argument") ) unless $value; require RT::ScripCondition; my $condition = RT::ScripCondition->new( $self->CurrentUser ); $condition->Load($value); return ( 0, $self->loc( "Condition '[_1]' not found", $value ) ) unless $condition->Id; return $self->_Set( Field => 'ScripCondition', Value => $condition->Id ); } =head2 SetTemplate =cut sub SetTemplate { my $self = shift; my $value = shift; return ( 0, $self->loc("Template is mandatory argument") ) unless $value; require RT::Template; my $template = RT::Template->new( $self->CurrentUser ); $template->Load($value); return ( 0, $self->loc( "Template '[_1]' not found", $value ) ) unless $template->Id; return $self->_Set( Field => 'Template', Value => $template->Name ); } 1; =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 Description Returns the current value of Description. (In the database, Description is stored as varchar(255).) =head2 SetDescription VALUE Set Description to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Description will be stored as a varchar(255).) =cut =head2 ScripCondition Returns the current value of ScripCondition. (In the database, ScripCondition is stored as int(11).) =head2 SetScripCondition VALUE Set ScripCondition to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ScripCondition will be stored as a int(11).) =cut =head2 ScripConditionObj Returns the ScripCondition Object which has the id returned by ScripCondition =cut sub ScripConditionObj { my $self = shift; my $ScripCondition = RT::ScripCondition->new($self->CurrentUser); $ScripCondition->Load($self->__Value('ScripCondition')); return($ScripCondition); } =head2 ScripAction Returns the current value of ScripAction. (In the database, ScripAction is stored as int(11).) =head2 SetScripAction VALUE Set ScripAction to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ScripAction will be stored as a int(11).) =cut =head2 ScripActionObj Returns the ScripAction Object which has the id returned by ScripAction =cut sub ScripActionObj { my $self = shift; my $ScripAction = RT::ScripAction->new($self->CurrentUser); $ScripAction->Load($self->__Value('ScripAction')); return($ScripAction); } =head2 CustomIsApplicableCode Returns the current value of CustomIsApplicableCode. (In the database, CustomIsApplicableCode is stored as text.) =head2 SetCustomIsApplicableCode VALUE Set CustomIsApplicableCode to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, CustomIsApplicableCode will be stored as a text.) =cut =head2 CustomPrepareCode Returns the current value of CustomPrepareCode. (In the database, CustomPrepareCode is stored as text.) =head2 SetCustomPrepareCode VALUE Set CustomPrepareCode to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, CustomPrepareCode will be stored as a text.) =cut =head2 CustomCommitCode Returns the current value of CustomCommitCode. (In the database, CustomCommitCode is stored as text.) =head2 SetCustomCommitCode VALUE Set CustomCommitCode to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, CustomCommitCode will be stored as a text.) =cut =head2 Disabled Returns the current value of Disabled. (In the database, Disabled is stored as smallint(6).) =head2 SetDisabled VALUE Set Disabled to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Disabled will be stored as a smallint(6).) =cut =head2 Template Returns the current value of Template. (In the database, Template is stored as varchar(200).) =head2 SetTemplate VALUE Set Template to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Template will be stored as a varchar(200).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Description => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, ScripCondition => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, ScripAction => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, CustomIsApplicableCode => {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, CustomPrepareCode => {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, CustomCommitCode => {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, Disabled => {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'}, Template => {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => 'Blank'}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); my $applied = RT::ObjectScrips->new( $self->CurrentUser ); $applied->LimitToScrip( $self->id ); $deps->Add( in => $applied ); $deps->Add( out => $self->ScripConditionObj ); $deps->Add( out => $self->ScripActionObj ); my $template = $self->TemplateObj; $deps->Add( out => $template ) if $template->Id; } sub __DependsOn { my $self = shift; my %args = ( Shredder => undef, Dependencies => undef, @_, ); my $deps = $args{'Dependencies'}; my $list = []; my $objs = RT::ObjectScrips->new( $self->CurrentUser ); $objs->LimitToScrip( $self->Id ); push @$list, $objs; $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON, TargetObjects => $list, Shredder => $args{'Shredder'} ); return $self->SUPER::__DependsOn( %args ); } sub Serialize { my $self = shift; my %args = (@_); my %store = $self->SUPER::Serialize(@_); # Store the string, not a reference to the object $store{Template} = $self->Template; return %store; } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Asset.pm����������������������������������������������������������������������������000644 �000765 �000024 �00000042446 14005011336 015523� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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; use 5.10.1; package RT::Asset; use base 'RT::Record'; use Role::Basic "with"; with "RT::Record::Role::Status", "RT::Record::Role::Links", "RT::Record::Role::Roles" => { -rename => { # We provide ACL'd wraps of these. AddRoleMember => "_AddRoleMember", DeleteRoleMember => "_DeleteRoleMember", RoleGroup => "_RoleGroup", }, }; require RT::Catalog; require RT::CustomField; require RT::URI::asset; =head1 NAME RT::Asset - Represents a single asset record =cut sub LifecycleColumn { "Catalog" } # Assets are primarily built on custom fields RT::CustomField->RegisterLookupType( CustomFieldLookupType() => 'Assets' ); RT::CustomField->RegisterBuiltInGroupings( 'RT::Asset' => [qw( Basics Dates People Links )] ); # loc('Owner') # loc('HeldBy') # loc('Contact') for my $role ('Owner', 'HeldBy', 'Contact') { state $i = 1; RT::Asset->RegisterRole( Name => $role, EquivClasses => ["RT::Catalog"], SortOrder => $i++, ( $role eq "Owner" ? ( Single => 1, ACLOnlyInEquiv => 1, ) : () ), ); } =head1 DESCRIPTION An Asset is a small record object upon which zero to many custom fields are applied. The core fields are: =over 4 =item id =item Name Limited to 255 characters. =item Description Limited to 255 characters. =item Catalog =item Status =item Creator =item Created =item LastUpdatedBy =item LastUpdated =back All of these are readable through methods of the same name and mutable through methods of the same name with C<Set> prefixed. The last four are automatically managed. =head1 METHODS =head2 Load ID or NAME Loads the specified Asset into the current object. =cut sub Load { my $self = shift; my $id = shift; return unless $id; if ( $id =~ /\D/ ) { return $self->LoadByCols( Name => $id ); } else { return $self->SUPER::Load($id); } } =head2 Create PARAMHASH Create takes a hash of values and creates a row in the database. Available keys are: =over 4 =item Name =item Description =item Catalog Name or numeric ID =item CustomField-<ID> Sets the value for this asset of the custom field specified by C<< <ID> >>. C<< <ID> >> should be a numeric ID, but may also be a Name if and only if your custom fields have unique names. Without unique names, the behaviour is undefined. =item Status =item Owner, HeldBy, Contact A single principal ID or array ref of principal IDs to add as members of the respective role groups for the new asset. User Names and EmailAddresses may also be used, but Groups must be referenced by ID. =item RefersTo, ReferredToBy, DependsOn, DependedOnBy, Parents, Children, and aliases Any of these link types accept either a single value or arrayref of values parseable by L<RT::URI>. =back Returns a tuple of (status, msg) on failure and (id, msg, non-fatal errors) on success, where the third value is an array reference of errors that occurred but didn't prevent creation. =cut sub Create { my $self = shift; my %args = ( Name => '', Description => '', Catalog => undef, Owner => undef, HeldBy => undef, Contact => undef, Status => undef, @_ ); my @non_fatal_errors; return (0, $self->loc("Invalid Catalog")) unless $self->ValidateCatalog( $args{'Catalog'} ); my $catalog = RT::Catalog->new( $self->CurrentUser ); $catalog->Load($args{'Catalog'}); $args{'Catalog'} = $catalog->id; return (0, $self->loc("Permission Denied")) unless $catalog->CurrentUserHasRight('CreateAsset'); return (0, $self->loc('Invalid Name (names may not be all digits)')) unless $self->ValidateName( $args{'Name'} ); # XXX TODO: This status/lifecycle pattern is duplicated in RT::Ticket and # should be refactored into a role helper. my $cycle = $catalog->LifecycleObj; unless ( defined $args{'Status'} && length $args{'Status'} ) { $args{'Status'} = $cycle->DefaultOnCreate; } $args{'Status'} = lc $args{'Status'}; unless ( $cycle->IsValid( $args{'Status'} ) ) { return ( 0, $self->loc("Status '[_1]' isn't a valid status for assets.", $self->loc($args{'Status'})) ); } unless ( $cycle->IsTransition( '' => $args{'Status'} ) ) { return ( 0, $self->loc("New assets cannot have status '[_1]'.", $self->loc($args{'Status'})) ); } my $roles = {}; my @errors = $self->_ResolveRoles( $roles, %args ); return (0, @errors) if @errors; RT->DatabaseHandle->BeginTransaction(); my ( $id, $msg ) = $self->SUPER::Create( map { $_ => $args{$_} } grep {exists $args{$_}} qw(id Name Description Catalog Status), ); unless ($id) { RT->DatabaseHandle->Rollback(); return (0, $self->loc("Asset create failed: [_1]", $msg)); } # Let users who just created an asset see it until the end of this method. $self->{_object_is_readable} = 1; # Create role groups unless ($self->_CreateRoleGroups()) { RT->Logger->error("Couldn't create role groups for asset ". $self->id); RT->DatabaseHandle->Rollback(); return (0, $self->loc("Couldn't create role groups for asset")); } # Figure out users for roles push @non_fatal_errors, $self->_AddRolesOnCreate( $roles, map { $_ => sub {1} } $self->Roles ); # Add CFs foreach my $key (keys %args) { next unless $key =~ /^CustomField-(.+)$/i; my $cf = $1; my @vals = ref $args{$key} eq 'ARRAY' ? @{ $args{$key} } : $args{$key}; foreach my $value (@vals) { next unless defined $value; my ( $cfid, $cfmsg ) = $self->AddCustomFieldValue( (ref($value) eq 'HASH' ? %$value : (Value => $value)), Field => $cf, RecordTransaction => 0, ForCreation => 1, ); unless ($cfid) { RT->DatabaseHandle->Rollback(); return (0, $self->loc("Couldn't add custom field value on create: [_1]", $cfmsg)); } } } # Add CF default values my ( $status, @msgs ) = $self->AddCustomFieldDefaultValues; push @non_fatal_errors, @msgs unless $status; # Create transaction my ( $txn_id, $txn_msg, $txn ) = $self->_NewTransaction( Type => 'Create' ); unless ($txn_id) { RT->DatabaseHandle->Rollback(); return (0, $self->loc( 'Asset Create txn failed: [_1]', $txn_msg )); } # Add links push @non_fatal_errors, $self->_AddLinksOnCreate(\%args); RT->DatabaseHandle->Commit(); # Let normal ACLs take over. delete $self->{_object_is_readable}; return ($id, $self->loc('Asset #[_1] created: [_2]', $self->id, $args{'Name'}), \@non_fatal_errors); } =head2 ValidateName NAME Requires that Names contain at least one non-digit. Empty names are OK. =cut sub ValidateName { my $self = shift; my $name = shift; return 1 unless defined $name and length $name; return 0 unless $name =~ /\D/; return 1; } =head2 ValidateCatalog Takes a catalog name or ID. Returns true if the catalog exists and is not disabled, otherwise false. =cut sub ValidateCatalog { my $self = shift; my $name = shift; my $catalog = RT::Catalog->new( $self->CurrentUser ); $catalog->Load($name); return 1 if $catalog->id and not $catalog->Disabled; return 0; } =head2 Delete Assets may not be deleted. Always returns failure. You should disable the asset instead with C<< $asset->SetStatus('deleted') >>. =cut sub Delete { my $self = shift; return (0, $self->loc("Assets may not be deleted")); } =head2 CurrentUserHasRight RIGHTNAME Returns true if the current user has the right for this asset, or globally if this is called on an unloaded object. =cut sub CurrentUserHasRight { my $self = shift; my $right = shift; return ( $self->CurrentUser->HasRight( Right => $right, Object => ($self->id ? $self : RT->System), ) ); } =head2 CurrentUserCanSee Returns true if the current user can see the asset, either because they just created it or they have the I<ShowAsset> right. =cut sub CurrentUserCanSee { my $self = shift; return $self->{_object_is_readable} || $self->CurrentUserHasRight('ShowAsset'); } =head2 URI Returns this asset's URI =cut sub URI { my $self = shift; my $uri = RT::URI::asset->new($self->CurrentUser); return $uri->URIForObject($self); } =head2 CatalogObj Returns the L<RT::Catalog> object for this asset's catalog. =cut sub CatalogObj { my $self = shift; my $catalog = RT::Catalog->new($self->CurrentUser); $catalog->Load( $self->__Value("Catalog") ); return $catalog; } =head2 SetCatalog Validates the supplied catalog and updates the column if valid. Transitions Status if necessary. Returns a (status, message) tuple. =cut sub SetCatalog { my $self = shift; my $value = shift; return (0, $self->loc("Permission Denied")) unless $self->CurrentUserHasRight("ModifyAsset"); my ($ok, $msg, $status) = $self->_SetLifecycleColumn( Value => $value, RequireRight => "CreateAsset" ); return ($ok, $msg); } =head2 Owner Returns an L<RT::User> object for this asset's I<Owner> role group. On error, returns undef. =head2 HeldBy Returns an L<RT::Group> object for this asset's I<HeldBy> role group. The object may be unloaded if permissions aren't satisfied. =head2 Contacts Returns an L<RT::Group> object for this asset's I<Contact> role group. The object may be unloaded if permissions aren't satisfied. =cut sub Owner { my $self = shift; my $group = $self->RoleGroup("Owner"); return unless $group and $group->id; return $group->UserMembersObj->First; } sub HeldBy { $_[0]->RoleGroup("HeldBy") } sub Contacts { $_[0]->RoleGroup("Contact") } =head2 AddRoleMember Checks I<ModifyAsset> before calling L<RT::Record::Role::Roles/_AddRoleMember>. =cut sub AddRoleMember { my $self = shift; return (0, $self->loc("No permission to modify this asset")) unless $self->CurrentUserHasRight("ModifyAsset"); return $self->_AddRoleMember(@_); } =head2 DeleteRoleMember Checks I<ModifyAsset> before calling L<RT::Record::Role::Roles/_DeleteRoleMember>. =cut sub DeleteRoleMember { my $self = shift; return (0, $self->loc("No permission to modify this asset")) unless $self->CurrentUserHasRight("ModifyAsset"); return $self->_DeleteRoleMember(@_); } =head2 RoleGroup An ACL'd version of L<RT::Record::Role::Roles/_RoleGroup>. Checks I<ShowAsset>. =cut sub RoleGroup { my $self = shift; if ($self->CurrentUserCanSee) { return $self->_RoleGroup(@_); } else { return RT::Group->new( $self->CurrentUser ); } } =head1 INTERNAL METHODS Public methods, but you shouldn't need to call these unless you're extending Assets. =head2 CustomFieldLookupType =cut sub CustomFieldLookupType { "RT::Catalog-RT::Asset" } =head2 ACLEquivalenceObjects =cut sub ACLEquivalenceObjects { my $self = shift; return $self->CatalogObj; } =head2 ModifyLinkRight =cut # Used for StrictLinkACL and RT::Record::Role::Links. # # Historically StrictLinkACL has only applied between tickets, but # if you care about it enough to turn it on, you probably care when # linking an asset to an asset or an asset to a ticket. sub ModifyLinkRight { "ShowAsset" } =head2 LoadCustomFieldByIdentifier Finds and returns the custom field of the given name for the asset, overriding L<RT::Record/LoadCustomFieldByIdentifier> to look for catalog-specific CFs before global ones. =cut sub LoadCustomFieldByIdentifier { my $self = shift; my $field = shift; return $self->SUPER::LoadCustomFieldByIdentifier($field) if ref $field or $field =~ /^\d+$/; my $cf = RT::CustomField->new( $self->CurrentUser ); $cf->SetContextObject( $self ); $cf->LoadByNameAndCatalog( Name => $field, Catalog => $self->Catalog ); $cf->LoadByNameAndCatalog( Name => $field, Catalog => 0 ) unless $cf->id; return $cf; } =head1 PRIVATE METHODS Documented for internal use only, do not call these from outside RT::Asset itself. =head2 _Set Checks if the current user can I<ModifyAsset> before calling C<SUPER::_Set> and records a transaction against this object if C<SUPER::_Set> was successful. =cut sub _Set { my $self = shift; my %args = ( Field => undef, Value => undef, @_ ); return (0, $self->loc("Permission Denied")) unless $self->CurrentUserHasRight('ModifyAsset'); my $old = $self->_Value( $args{'Field'} ); my ($ok, $msg) = $self->SUPER::_Set(@_); # Only record the transaction if the _Set worked return ($ok, $msg) unless $ok; my $txn_type = $args{Field} eq "Status" ? "Status" : "Set"; my ($txn_id, $txn_msg, $txn) = $self->_NewTransaction( Type => $txn_type, Field => $args{'Field'}, NewValue => $args{'Value'}, OldValue => $old, ); # Ensure that we can read the transaction, even if the change just made # the asset unreadable to us. This is only in effect for the lifetime of # $txn, i.e. as soon as this method returns. $txn->{ _object_is_readable } = 1; return ($txn_id, scalar $txn->BriefDescription); } =head2 _Value Checks L</CurrentUserCanSee> before calling C<SUPER::_Value>. =cut sub _Value { my $self = shift; return unless $self->CurrentUserCanSee; return $self->SUPER::_Value(@_); } sub Table { "Assets" } sub _CoreAccessible { { id => { read => 1, type => 'int(11)', default => '' }, Name => { read => 1, type => 'varchar(255)', default => '', write => 1 }, Status => { read => 1, type => 'varchar(64)', default => '', write => 1 }, Description => { read => 1, type => 'varchar(255)', default => '', write => 1 }, Catalog => { read => 1, type => 'int(11)', default => '0', write => 1 }, Creator => { read => 1, type => 'int(11)', default => '0', auto => 1 }, Created => { read => 1, type => 'datetime', default => '', auto => 1 }, LastUpdatedBy => { read => 1, type => 'int(11)', default => '0', auto => 1 }, LastUpdated => { read => 1, type => 'datetime', default => '', auto => 1 }, } } sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); # Links my $links = RT::Links->new( $self->CurrentUser ); $links->Limit( SUBCLAUSE => "either", FIELD => $_, VALUE => $self->URI, ENTRYAGGREGATOR => 'OR' ) for qw/Base Target/; $deps->Add( in => $links ); # Asset role groups( Owner, HeldBy, Contact ) my $objs = RT::Groups->new( $self->CurrentUser ); $objs->Limit( FIELD => 'Domain', VALUE => 'RT::Asset-Role', CASESENSITIVE => 0 ); $objs->Limit( FIELD => 'Instance', VALUE => $self->Id ); $deps->Add( in => $objs ); # Catalog $deps->Add( out => $self->CatalogObj ); } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Users.pm����������������������������������������������������������������������������000644 �000765 �000024 �00000045314 14005011336 015542� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Users - Collection of RT::User objects =head1 SYNOPSIS use RT::Users; =head1 DESCRIPTION =head1 METHODS =cut package RT::Users; use strict; use warnings; use base 'RT::SearchBuilder'; use RT::User; sub Table { 'Users'} sub _Init { my $self = shift; $self->{'with_disabled_column'} = 1; my @result = $self->SUPER::_Init(@_); # By default, order by name $self->OrderBy( ALIAS => 'main', FIELD => 'Name', ORDER => 'ASC' ); # XXX: should be generalized $self->{'princalias'} = $self->Join( ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'Principals', FIELD2 => 'id' ); $self->Limit( ALIAS => $self->{'princalias'}, FIELD => 'PrincipalType', VALUE => 'User', ); return (@result); } sub OrderByCols { my $self = shift; my @res = (); for my $row (@_) { if (($row->{FIELD}||'') =~ /^CustomField\.\{(.*)\}$/) { my $name = $1 || $2; my $cf = RT::CustomField->new( $self->CurrentUser ); $cf->LoadByName( Name => $name, ObjectType => 'RT::User', ); if ( $cf->id ) { push @res, $self->_OrderByCF( $row, $cf->id, $cf ); } } else { push @res, $row; } } return $self->SUPER::OrderByCols( @res ); } =head2 PrincipalsAlias Returns the string that represents this Users object's primary "Principals" alias. =cut # XXX: should be generalized sub PrincipalsAlias { my $self = shift; return($self->{'princalias'}); } =head2 LimitToEnabled Only find items that haven't been disabled =cut # XXX: should be generalized sub LimitToEnabled { my $self = shift; $self->{'handled_disabled_column'} = 1; $self->Limit( ALIAS => $self->PrincipalsAlias, FIELD => 'Disabled', VALUE => '0', ); } =head2 LimitToDeleted Only find items that have been deleted. =cut sub LimitToDeleted { my $self = shift; $self->{'handled_disabled_column'} = $self->{'find_disabled_rows'} = 1; $self->Limit( ALIAS => $self->PrincipalsAlias, FIELD => 'Disabled', VALUE => 1, ); } =head2 LimitToEmail Takes one argument. an email address. limits the returned set to that email address =cut sub LimitToEmail { my $self = shift; my $addr = shift; $self->Limit( FIELD => 'EmailAddress', VALUE => $addr, CASESENSITIVE => 0 ); } =head2 MemberOfGroup PRINCIPAL_ID takes one argument, a group's principal id. Limits the returned set to members of a given group =cut sub MemberOfGroup { my $self = shift; my $group = shift; return $self->loc("No group specified") if ( !defined $group ); my $groupalias = $self->NewAlias('CachedGroupMembers'); # Join the principal to the groups table $self->Join( ALIAS1 => $self->PrincipalsAlias, FIELD1 => 'id', ALIAS2 => $groupalias, FIELD2 => 'MemberId' ); $self->Limit( ALIAS => $groupalias, FIELD => 'Disabled', VALUE => 0 ); $self->Limit( ALIAS => "$groupalias", FIELD => 'GroupId', VALUE => "$group", OPERATOR => "=" ); } =head2 LimitToPrivileged Limits to users who can be made members of ACLs and groups =cut sub LimitToPrivileged { my $self = shift; $self->MemberOfGroup( RT->PrivilegedUsers->id ); } =head2 LimitToUnprivileged Limits to unprivileged users only =cut sub LimitToUnprivileged { my $self = shift; $self->MemberOfGroup( RT->UnprivilegedUsers->id); } sub Limit { my $self = shift; my %args = @_; $args{'CASESENSITIVE'} = 0 unless exists $args{'CASESENSITIVE'} or $args{'ALIAS'}; return $self->SUPER::Limit( %args ); } =head2 WhoHaveRight { Right => 'name', Object => $rt_object , IncludeSuperusers => undef, IncludeSubgroupMembers => undef, IncludeSystemRights => undef, EquivObjects => [ ] } find all users who the right Right for this group, either individually or as members of groups If passed a queue object, with no id, it will find users who have that right for _any_ queue =cut # XXX: should be generalized sub _JoinGroupMembers { my $self = shift; my %args = ( IncludeSubgroupMembers => 1, @_ ); my $principals = $self->PrincipalsAlias; # The cachedgroupmembers table is used for unrolling group memberships # to allow fast lookups. if we bind to CachedGroupMembers, we'll find # all members of groups recursively. if we don't we'll find only 'direct' # members of the group in question my $group_members; if ( $args{'IncludeSubgroupMembers'} ) { $group_members = $self->NewAlias('CachedGroupMembers'); } else { $group_members = $self->NewAlias('GroupMembers'); } $self->Join( ALIAS1 => $group_members, FIELD1 => 'MemberId', ALIAS2 => $principals, FIELD2 => 'id' ); $self->Limit( ALIAS => $group_members, FIELD => 'Disabled', VALUE => 0, ) if $args{'IncludeSubgroupMembers'}; return $group_members; } # XXX: should be generalized sub _JoinGroups { my $self = shift; my %args = (@_); my $group_members = $self->_JoinGroupMembers( %args ); my $groups = $self->NewAlias('Groups'); $self->Join( ALIAS1 => $groups, FIELD1 => 'id', ALIAS2 => $group_members, FIELD2 => 'GroupId' ); return $groups; } # XXX: should be generalized sub _JoinACL { my $self = shift; my %args = ( Right => undef, IncludeSuperusers => undef, @_, ); if ( $args{'Right'} ) { my $canonic = RT::ACE->CanonicalizeRightName( $args{'Right'} ); unless ( $canonic ) { $RT::Logger->error("Invalid right. Couldn't canonicalize right '$args{'Right'}'"); } else { $args{'Right'} = $canonic; } } my $acl = $self->NewAlias('ACL'); $self->Limit( ALIAS => $acl, FIELD => 'RightName', OPERATOR => ( $args{Right} ? '=' : 'IS NOT' ), VALUE => $args{Right} || 'NULL', ENTRYAGGREGATOR => 'OR' ); if ( $args{'IncludeSuperusers'} and $args{'Right'} ) { $self->Limit( ALIAS => $acl, FIELD => 'RightName', OPERATOR => '=', VALUE => 'SuperUser', ENTRYAGGREGATOR => 'OR' ); } return $acl; } # XXX: should be generalized sub _GetEquivObjects { my $self = shift; my %args = ( Object => undef, IncludeSystemRights => undef, EquivObjects => [ ], @_ ); return () unless $args{'Object'}; my @objects = ($args{'Object'}); if ( UNIVERSAL::isa( $args{'Object'}, 'RT::Ticket' ) ) { # If we're looking at ticket rights, we also want to look at the associated queue rights. # this is a little bit hacky, but basically, now that we've done the ticket roles magic, # we load the queue object and ask all the rest of our questions about the queue. # XXX: This should be abstracted into object itself if( $args{'Object'}->id ) { push @objects, $args{'Object'}->ACLEquivalenceObjects; } else { push @objects, 'RT::Queue'; } } if( $args{'IncludeSystemRights'} ) { push @objects, $RT::System; } push @objects, @{ $args{'EquivObjects'} }; return grep $_, @objects; } # XXX: should be generalized sub WhoHaveRight { my $self = shift; my %args = ( Right => undef, Object => undef, IncludeSystemRights => undef, IncludeSuperusers => undef, IncludeSubgroupMembers => 1, EquivObjects => [ ], @_ ); if ( defined $args{'ObjectType'} || defined $args{'ObjectId'} ) { $RT::Logger->crit( "WhoHaveRight called with the Obsolete ObjectId/ObjectType API"); return (undef); } my $from_role = $self->Clone; $from_role->WhoHaveRoleRight( %args ); my $from_group = $self->Clone; $from_group->WhoHaveGroupRight( %args ); #XXX: DIRTY HACK use DBIx::SearchBuilder::Union; my $union = DBIx::SearchBuilder::Union->new(); $union->add( $from_group ); $union->add( $from_role ); %$self = %$union; bless $self, ref($union); return; } # XXX: should be generalized sub WhoHaveRoleRight { my $self = shift; my %args = ( Right => undef, Object => undef, IncludeSystemRights => undef, IncludeSuperusers => undef, IncludeSubgroupMembers => 1, EquivObjects => [ ], @_ ); my @objects = $self->_GetEquivObjects( %args ); # RT::Principal->RolesWithRight only expects EquivObjects, so we need to # fill it. At the very least it needs $args{Object}, which # _GetEquivObjects above does for us. unshift @{$args{'EquivObjects'}}, @objects; my @roles = RT::Principal->RolesWithRight( %args ); unless ( @roles ) { $self->_AddSubClause( "WhichRole", "(main.id = 0)" ); return; } my $groups = $self->_JoinGroups( %args ); # no system user $self->Limit( ALIAS => $self->PrincipalsAlias, FIELD => 'id', OPERATOR => '!=', VALUE => RT->SystemUser->id ); $self->_AddSubClause( "WhichRole", "(". join( ' OR ', map $RT::Handle->__MakeClauseCaseInsensitive("$groups.Name", '=', "'$_'"), @roles ) .")" ); my @groups_clauses = $self->_RoleClauses( $groups, @objects ); $self->_AddSubClause( "WhichObject", "(". join( ' OR ', @groups_clauses ) .")" ) if @groups_clauses; return; } sub _RoleClauses { my $self = shift; my $groups = shift; my @objects = @_; my @groups_clauses; foreach my $obj ( @objects ) { my $type = ref($obj)? ref($obj): $obj; my $role_clause = $RT::Handle->__MakeClauseCaseInsensitive("$groups.Domain", '=', "'$type-Role'"); if ( my $id = eval { $obj->id } ) { $role_clause .= " AND $groups.Instance = $id"; } push @groups_clauses, "($role_clause)"; } return @groups_clauses; } # XXX: should be generalized sub _JoinGroupMembersForGroupRights { my $self = shift; my %args = (@_); my $group_members = $self->_JoinGroupMembers( %args ); $self->Limit( ALIAS => $args{'ACLAlias'}, FIELD => 'PrincipalId', VALUE => "$group_members.GroupId", QUOTEVALUE => 0, ); return $group_members; } # XXX: should be generalized sub WhoHaveGroupRight { my $self = shift; my %args = ( Right => undef, Object => undef, IncludeSystemRights => undef, IncludeSuperusers => undef, IncludeSubgroupMembers => 1, EquivObjects => [ ], @_ ); # Find only rows where the right granted is # the one we're looking up or _possibly_ superuser my $acl = $self->_JoinACL( %args ); my ($check_objects) = (''); my @objects = $self->_GetEquivObjects( %args ); my %seen; if ( @objects ) { my @object_clauses; foreach my $obj ( @objects ) { my $type = ref($obj)? ref($obj): $obj; my $id = 0; $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id; next if $seen{"$type-$id"}++; my $object_clause = "$acl.ObjectType = '$type'"; $object_clause .= " AND $acl.ObjectId = $id" if $id; push @object_clauses, "($object_clause)"; } $check_objects = join ' OR ', @object_clauses; } else { if( !$args{'IncludeSystemRights'} ) { $check_objects = "($acl.ObjectType != 'RT::System')"; } } $self->_AddSubClause( "WhichObject", "($check_objects)" ); my $group_members = $self->_JoinGroupMembersForGroupRights( %args, ACLAlias => $acl ); # Find only members of groups that have the right. $self->Limit( ALIAS => $acl, FIELD => 'PrincipalType', VALUE => 'Group', ); # no system user $self->Limit( ALIAS => $self->PrincipalsAlias, FIELD => 'id', OPERATOR => '!=', VALUE => RT->SystemUser->id ); return $group_members; } =head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1, IncludeUnprivileged => 0 } Return members who belong to any of the groups passed in the groups whose IDs are included in the Groups arrayref. If IncludeSubgroupMembers is true (default) then members of any group that's a member of one of the passed groups are returned. If it's cleared then only direct member users are returned. If IncludeUnprivileged is false (default) then only privileged members are returned; otherwise either privileged or unprivileged group members may be returned. =cut sub WhoBelongToGroups { my $self = shift; my %args = ( Groups => undef, IncludeSubgroupMembers => 1, IncludeUnprivileged => 0, @_ ); if (!$args{'IncludeUnprivileged'}) { $self->LimitToPrivileged(); } my $group_members = $self->_JoinGroupMembers( %args ); $self->Limit( ALIAS => $group_members, FIELD => 'GroupId', OPERATOR => 'IN', VALUE => [ 0, @{$args{'Groups'}} ], ); } =head2 SimpleSearch Does a 'simple' search of Users against a specified Term. This Term is compared to a number of fields using various types of SQL comparison operators. Ensures that the returned collection of Users will have a value for Return. This method is passed the following. You must specify a Term and a Return. Privileged - Whether or not to limit to Privileged Users (0 or 1) Fields - Hashref of data - defaults to C<$UserSearchFields> emulate that if you want to override Term - String that is in the fields specified by Fields Return - What field on the User you want to be sure isn't empty Exclude - Array reference of ids to exclude Max - What to limit this collection to =cut sub SimpleSearch { my $self = shift; my %args = ( Privileged => 0, Fields => RT->Config->Get('UserSearchFields'), Term => undef, Exclude => [], Return => undef, Max => 10, @_ ); return $self unless defined $args{Return} and defined $args{Term} and length $args{Term}; $self->RowsPerPage( $args{Max} ); $self->LimitToPrivileged() if $args{Privileged}; while (my ($name, $op) = each %{$args{Fields}}) { $op = 'STARTSWITH' unless $op =~ /^(?:LIKE|(?:START|END)SWITH|=|!=)$/i; if ($name =~ /^CF\.(?:\{(.*)}|(.*))$/) { my $cfname = $1 || $2; my $cf = RT::CustomField->new(RT->SystemUser); my ($ok, $msg) = $cf->LoadByName( Name => $cfname, LookupType => 'RT::User'); if ( $ok ) { $self->LimitCustomField( CUSTOMFIELD => $cf->Id, OPERATOR => $op, VALUE => $args{Term}, ENTRYAGGREGATOR => 'OR', SUBCLAUSE => 'autocomplete', ); } else { RT->Logger->warning("Asked to search custom field $name but unable to load a User CF with the name $cfname: $msg"); } } else { $self->Limit( FIELD => $name, OPERATOR => $op, VALUE => $args{Term}, ENTRYAGGREGATOR => 'OR', SUBCLAUSE => 'autocomplete', ); } } # Exclude users we don't want $self->Limit(FIELD => 'id', OPERATOR => 'NOT IN', VALUE => $args{Exclude} ) if @{$args{Exclude}}; if ( RT->Config->Get('DatabaseType') eq 'Oracle' ) { $self->Limit( FIELD => $args{Return}, OPERATOR => 'IS NOT', VALUE => 'NULL', ); } else { $self->Limit( FIELD => $args{Return}, OPERATOR => '!=', VALUE => '' ); $self->Limit( FIELD => $args{Return}, OPERATOR => 'IS NOT', VALUE => 'NULL', ENTRYAGGREGATOR => 'AND' ); } return $self; } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/DependencyWalker/�������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 017320� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ACE.pm������������������������������������������������������������������������������000644 �000765 �000024 �00000052557 14005011336 015040� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 SYNOPSIS use RT::ACE; my $ace = RT::ACE->new($CurrentUser); =head1 DESCRIPTION =head1 METHODS =cut package RT::ACE; use base 'RT::Record'; sub Table {'ACL'} use strict; use warnings; require RT::Principals; require RT::Queues; require RT::Groups; our %RIGHTS; my (@_ACL_CACHE_HANDLERS); =head1 Rights # Queue rights are the sort of queue rights that can only be granted # to real people or groups =cut =head2 LoadByValues PARAMHASH Load an ACE by specifying a paramhash with the following fields: PrincipalId => undef, PrincipalType => undef, RightName => undef, And either: Object => undef, OR ObjectType => undef, ObjectId => undef =cut sub LoadByValues { my $self = shift; my %args = ( PrincipalId => undef, PrincipalType => undef, RightName => undef, Object => undef, ObjectId => undef, ObjectType => undef, @_ ); if ( $args{'RightName'} ) { my $canonic_name = $self->CanonicalizeRightName( $args{'RightName'} ); unless ( $canonic_name ) { return wantarray ? ( 0, $self->loc("Invalid right. Couldn't canonicalize right '[_1]'", $args{'RightName'}) ) : 0; } $args{'RightName'} = $canonic_name; } my $princ_obj; ( $princ_obj, $args{'PrincipalType'} ) = $self->_CanonicalizePrincipal( $args{'PrincipalId'}, $args{'PrincipalType'} ); unless ( $princ_obj->id ) { return wantarray ? ( 0, $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} ) ) : 0; } my ($object, $object_type, $object_id) = $self->_ParseObjectArg( %args ); unless( $object ) { return wantarray ? ( 0, $self->loc("System error. Right not granted.")) : 0; } $self->LoadByCols( PrincipalId => $princ_obj->Id, PrincipalType => $args{'PrincipalType'}, RightName => $args{'RightName'}, ObjectType => $object_type, ObjectId => $object_id); #If we couldn't load it. unless ( $self->Id ) { return wantarray ? ( 0, $self->loc("ACE not found") ) : 0; } # if we could return wantarray ? ( $self->Id, $self->loc("Right Loaded") ) : $self->Id; } =head2 Create <PARAMS> PARAMS is a parameter hash with the following elements: PrincipalId => The id of an RT::Principal object PrincipalType => "User" "Group" or any Role type RightName => the name of a right. in any case Either: Object => An object to create rights for. ususally, an RT::Queue or RT::Group This should always be a DBIx::SearchBuilder::Record subclass OR ObjectType => the type of the object in question (ref ($object)) ObjectId => the id of the object in question $object->Id Returns a tuple of (STATUS, MESSAGE); If the call succeeded, STATUS is true. Otherwise it's false. =cut sub Create { my $self = shift; my %args = ( PrincipalId => undef, PrincipalType => undef, RightName => undef, Object => undef, @_ ); unless ( $args{'RightName'} ) { return ( 0, $self->loc('No right specified') ); } #if we haven't specified any sort of right, we're talking about a global right if (!defined $args{'Object'} && !defined $args{'ObjectId'} && !defined $args{'ObjectType'}) { $args{'Object'} = $RT::System; } ($args{'Object'}, $args{'ObjectType'}, $args{'ObjectId'}) = $self->_ParseObjectArg( %args ); unless( $args{'Object'} ) { return ( 0, $self->loc("System error. Right not granted.") ); } # Validate the principal my $princ_obj; ( $princ_obj, $args{'PrincipalType'} ) = $self->_CanonicalizePrincipal( $args{'PrincipalId'}, $args{'PrincipalType'} ); unless ( $princ_obj->id ) { return ( 0, $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} ) ); } # }}} # Check the ACL if (ref( $args{'Object'}) eq 'RT::Group' ) { unless ( $self->CurrentUser->HasRight( Object => $args{'Object'}, Right => 'AdminGroup' ) ) { return ( 0, $self->loc('Permission Denied') ); } } else { unless ( $self->CurrentUser->HasRight( Object => $args{'Object'}, Right => 'ModifyACL' )) { return ( 0, $self->loc('Permission Denied') ); } } # }}} # Canonicalize and check the right name my $canonic_name = $self->CanonicalizeRightName( $args{'RightName'} ); unless ( $canonic_name ) { return ( 0, $self->loc("Invalid right. Couldn't canonicalize right '[_1]'", $args{'RightName'}) ); } $args{'RightName'} = $canonic_name; #check if it's a valid RightName if ( $args{'Object'}->can('AvailableRights') ) { my $available = $args{'Object'}->AvailableRights($princ_obj); unless ( grep $_ eq $args{'RightName'}, map $self->CanonicalizeRightName( $_ ), keys %$available ) { $RT::Logger->warning( "Couldn't validate right name '$args{'RightName'}'" ." for object of ". ref( $args{'Object'} ) ." class" ); return ( 0, $self->loc('Invalid right') ); } } # }}} # Make sure the right doesn't already exist. $self->LoadByCols( PrincipalId => $princ_obj->id, PrincipalType => $args{'PrincipalType'}, RightName => $args{'RightName'}, ObjectType => $args{'ObjectType'}, ObjectId => $args{'ObjectId'}, ); if ( $self->Id ) { return ( 0, $self->loc('[_1] already has the right [_2] on [_3] [_4]', $princ_obj->DisplayName, $args{'RightName'}, $args{'ObjectType'}, $args{'ObjectId'}) ); } my $id = $self->SUPER::Create( PrincipalId => $princ_obj->id, PrincipalType => $args{'PrincipalType'}, RightName => $args{'RightName'}, ObjectType => ref( $args{'Object'} ), ObjectId => $args{'Object'}->id, ); if ( $id ) { RT::ACE->InvalidateCaches( Action => "Grant", RightName => $self->RightName, ACE => $self, ); return ( $id, $self->loc("Granted right '[_1]' to [_2].", $self->RightName, $princ_obj->DisplayName)); } else { return ( 0, $self->loc('System error. Right not granted.') ); } } =head2 Delete { InsideTransaction => undef} Delete this object. This method should ONLY ever be called from RT::User or RT::Group (or from itself) If this is being called from within a transaction, specify a true value for the parameter InsideTransaction. Really, DBIx::SearchBuilder should use and/or fake subtransactions This routine will also recurse and delete any delegations of this right =cut sub Delete { my $self = shift; unless ( $self->Id ) { return ( 0, $self->loc('Right not loaded.') ); } # A user can delete an ACE if the current user has the right to modify it and it's not a delegated ACE # or if it's a delegated ACE and it was delegated by the current user unless ($self->CurrentUser->HasRight(Right => 'ModifyACL', Object => $self->Object)) { return ( 0, $self->loc('Permission Denied') ); } $self->_Delete(@_); } # Helper for Delete with no ACL check sub _Delete { my $self = shift; my %args = ( InsideTransaction => undef, @_ ); my $InsideTransaction = $args{'InsideTransaction'}; $RT::Handle->BeginTransaction() unless $InsideTransaction; my $right = $self->RightName; my ( $val, $msg ) = $self->SUPER::Delete(@_); if ($val) { RT::ACE->InvalidateCaches( Action => "Revoke", RightName => $right ); $RT::Handle->Commit() unless $InsideTransaction; return ( $val, $self->loc("Revoked right '[_1]' from [_2].", $right, $self->PrincipalObj->DisplayName)); } $RT::Handle->Rollback() unless $InsideTransaction; return ( 0, $self->loc('Right could not be revoked') ); } =head2 _BootstrapCreate Grant a right with no error checking and no ACL. this is _only_ for installation. If you use this routine without the author's explicit written approval, he will hunt you down and make you spend eternity translating mozilla's code into FORTRAN or intercal. If you think you need this routine, you've mistaken. =cut sub _BootstrapCreate { my $self = shift; my %args = (@_); # When bootstrapping, make sure we get the _right_ users if ( $args{'UserId'} ) { my $user = RT::User->new( $self->CurrentUser ); $user->Load( $args{'UserId'} ); delete $args{'UserId'}; $args{'PrincipalId'} = $user->PrincipalId; $args{'PrincipalType'} = 'User'; } my $id = $self->SUPER::Create(%args); if ( $id > 0 ) { return ($id); } else { $RT::Logger->err('System error. right not granted.'); return (undef); } } =head2 InvalidateCaches Calls any registered ACL cache handlers (see L</RegisterCacheHandler>). Usually called from L</Create> and L</Delete>. =cut sub InvalidateCaches { my $class = shift; for my $handler (@_ACL_CACHE_HANDLERS) { next unless ref($handler) eq "CODE"; $handler->(@_); } } =head2 RegisterCacheHandler Class method. Takes a coderef and adds it to the ACL cache handlers. These handlers are called by L</InvalidateCaches>, usually called itself from L</Create> and L</Delete>. The handlers are passed a hash which may contain any (or none) of these optional keys: =over =item Action A string indicating the action that (may have) invalidated the cache. Expected values are currently: =over =item Grant =item Revoke =back However, other values may be passed in the future. =item RightName The (canonicalized) right being granted or revoked. =item ACE The L<RT::ACE> object just created. =back Your handler should be flexible enough to account for additional arguments being passed in the future. =cut sub RegisterCacheHandler { push @_ACL_CACHE_HANDLERS, $_[1]; } sub RightName { my $self = shift; my $val = $self->_Value('RightName'); return $val unless $val; my $available = $self->Object->AvailableRights; foreach my $right ( keys %$available ) { return $right if $val eq $self->CanonicalizeRightName($right); } $RT::Logger->error("Invalid right. Couldn't canonicalize right '$val'"); return $val; } =head2 CanonicalizeRightName <RIGHT> Takes a queue or system right name in any case and returns it in the correct case. If it's not found, will return undef. =cut sub CanonicalizeRightName { my $self = shift; my $name = shift; for my $class (sort keys %RIGHTS) { return $RIGHTS{$class}{ lc $name }{Name} if $RIGHTS{$class}{ lc $name }; } return undef; } =head2 Object If the object this ACE applies to is a queue, returns the queue object. If the object this ACE applies to is a group, returns the group object. If it's the system object, returns undef. If the user has no rights, returns undef. =cut sub Object { my $self = shift; my $appliesto_obj; if ($self->__Value('ObjectType') && $self->__Value('ObjectType')->DOES('RT::Record::Role::Rights') ) { $appliesto_obj = $self->__Value('ObjectType')->new($self->CurrentUser); unless (ref( $appliesto_obj) eq $self->__Value('ObjectType')) { return undef; } $appliesto_obj->Load( $self->__Value('ObjectId') ); return ($appliesto_obj); } else { $RT::Logger->warning( "$self -> Object called for an object " . "of an unknown type:" . $self->__Value('ObjectType') ); return (undef); } } =head2 PrincipalObj Returns the RT::Principal object for this ACE. =cut sub PrincipalObj { my $self = shift; my $princ_obj = RT::Principal->new( $self->CurrentUser ); $princ_obj->Load( $self->__Value('PrincipalId') ); unless ( $princ_obj->Id ) { $RT::Logger->err( "ACE " . $self->Id . " couldn't load its principal object" ); } return ($princ_obj); } sub _Set { my $self = shift; return ( 0, $self->loc("ACEs can only be created and deleted.") ); } sub _Value { my $self = shift; if ( $self->PrincipalObj->IsGroup && $self->PrincipalObj->Object->HasMemberRecursively( $self->CurrentUser->PrincipalObj ) ) { return ( $self->__Value(@_) ); } elsif ( $self->CurrentUser->HasRight(Right => 'ShowACL', Object => $self->Object) ) { return ( $self->__Value(@_) ); } else { return undef; } } =head2 _CanonicalizePrincipal (PrincipalId, PrincipalType) Takes a principal id and a principal type. If the principal is a user, resolves it to the proper acl equivalence group. Returns a tuple of (RT::Principal, PrincipalType) for the principal we really want to work with =cut sub _CanonicalizePrincipal { my $self = shift; my $princ_id = shift; my $princ_type = shift || ''; my $princ_obj = RT::Principal->new(RT->SystemUser); $princ_obj->Load($princ_id); unless ( $princ_obj->Id ) { use Carp; $RT::Logger->crit(Carp::longmess); $RT::Logger->crit("Can't load a principal for id $princ_id"); return ( $princ_obj, undef ); } # Rights never get granted to users. they get granted to their # ACL equivalence groups if ( $princ_type eq 'User' ) { my $equiv_group = RT::Group->new( $self->CurrentUser ); $equiv_group->LoadACLEquivalenceGroup($princ_obj); unless ( $equiv_group->Id ) { $RT::Logger->crit( "No ACL equiv group for princ " . $princ_obj->id ); return ( RT::Principal->new(RT->SystemUser), undef ); } $princ_obj = $equiv_group->PrincipalObj(); $princ_type = 'Group'; } return ( $princ_obj, $princ_type ); } sub _ParseObjectArg { my $self = shift; my %args = ( Object => undef, ObjectId => undef, ObjectType => undef, @_ ); if( $args{'Object'} && ($args{'ObjectId'} || $args{'ObjectType'}) ) { $RT::Logger->crit( "Method called with an ObjectType or an ObjectId and Object args" ); return (); } elsif( $args{'Object'} && ref($args{'Object'}) && !$args{'Object'}->can('id') ) { $RT::Logger->crit( "Method called called Object that has no id method" ); return (); } elsif( $args{'Object'} ) { my $obj = $args{'Object'}; return ($obj, ref $obj, $obj->id); } elsif ( $args{'ObjectType'} ) { my $obj = $args{'ObjectType'}->new( $self->CurrentUser ); $obj->Load( $args{'ObjectId'} ); return ($obj, ref $obj, $obj->id); } else { $RT::Logger->crit( "Method called with wrong args" ); return (); } } # }}} =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 PrincipalType Returns the current value of PrincipalType. (In the database, PrincipalType is stored as varchar(25).) =head2 SetPrincipalType VALUE Set PrincipalType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, PrincipalType will be stored as a varchar(25).) =cut =head2 PrincipalId Returns the current value of PrincipalId. (In the database, PrincipalId is stored as int(11).) =head2 SetPrincipalId VALUE Set PrincipalId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, PrincipalId will be stored as a int(11).) =cut =head2 RightName Returns the current value of RightName. (In the database, RightName is stored as varchar(25).) =head2 SetRightName VALUE Set RightName to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, RightName will be stored as a varchar(25).) =cut =head2 ObjectType Returns the current value of ObjectType. (In the database, ObjectType is stored as varchar(25).) =head2 SetObjectType VALUE Set ObjectType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectType will be stored as a varchar(25).) =cut =head2 ObjectId Returns the current value of ObjectId. (In the database, ObjectId is stored as int(11).) =head2 SetObjectId VALUE Set ObjectId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectId will be stored as a int(11).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, PrincipalType => {read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''}, PrincipalId => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, RightName => {read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''}, ObjectType => {read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''}, ObjectId => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); $deps->Add( out => $self->PrincipalObj->Object ); if ($self->PrincipalObj->Object->Domain eq 'ACLEquivalence') { $deps->Add( out => $self->PrincipalObj->Object->InstanceObj ); } $deps->Add( out => $self->Object ); } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ExternalStorage/��������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 017203� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Record/�����������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 015312� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ObjectCustomFieldValues.pm����������������������������������������������������������000644 �000765 �000024 �00000014554 14005011336 021170� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::ObjectCustomFieldValues; use strict; use warnings; use 5.010; use base 'RT::SearchBuilder'; use RT::ObjectCustomFieldValue; # Set up the OCFV cache for faster comparison on add/update our $_OCFV_CACHE; ClearOCFVCache(); sub Table { 'ObjectCustomFieldValues'} sub _Init { my $self = shift; $self->{'with_disabled_column'} = 1; # By default, order by SortOrder $self->OrderByCols( { ALIAS => 'main', FIELD => 'SortOrder', ORDER => 'ASC' }, { ALIAS => 'main', FIELD => 'id', ORDER => 'ASC' }, ); return ( $self->SUPER::_Init(@_) ); } =head2 ClearOCFVCache Cleans out and reinitializes the OCFV cache =cut sub ClearOCFVCache { $_OCFV_CACHE = {} } # {{{ sub LimitToCustomField =head2 LimitToCustomField FIELD Limits the returned set to values for the custom field with Id FIELD =cut sub LimitToCustomField { my $self = shift; my $cf = shift; return $self->Limit( FIELD => 'CustomField', VALUE => $cf, ); } =head2 LimitToObject OBJECT Limits the returned set to values for the given OBJECT =cut sub LimitToObject { my $self = shift; my $object = shift; $self->Limit( FIELD => 'ObjectType', VALUE => ref($object), ); return $self->Limit( FIELD => 'ObjectId', VALUE => $object->Id, ); } =head2 HasEntry CONTENT LARGE_CONTENT If this collection has an entry with content that eq CONTENT and large content that eq LARGE_CONTENT then returns the entry, otherwise returns undef. =cut sub HasEntry { my $self = shift; my $value = shift; my $large_content = shift; return undef unless defined $value && length $value; my $first = $self->First; return undef unless $first; # No entries to check # Key should be the same for all values of the same ocfv my $ocfv_key = $first->GetOCFVCacheKey; # This cache relieves performance issues when adding large numbers of values # to a CF since each add compares against the full list each time. unless ( $_OCFV_CACHE->{$ocfv_key} ) { # Load the cache with existing values foreach my $item ( @{$self->ItemsArrayRef} ) { push @{$_OCFV_CACHE->{$ocfv_key}}, { 'ObjectId' => $item->Id, 'CustomFieldObj' => $item->CustomFieldObj, 'Content' => $item->_Value('Content'), 'LargeContent' => $item->LargeContent }; } } my %canon_value; my $item_id; foreach my $item ( @{$_OCFV_CACHE->{$ocfv_key}} ) { my $cf = $item->{'CustomFieldObj'}; my $args = $canon_value{ $cf->Type }; if ( !$args ) { $args = { Content => $value, LargeContent => $large_content }; my ($ok, $msg) = $cf->_CanonicalizeValue( $args ); next unless $ok; $canon_value{ $cf->Type } = $args; } if ( $cf->Type eq 'Select' ) { # select is case insensitive $item_id = $item->{'ObjectId'} if lc $item->{'Content'} eq lc $args->{Content}; } else { if ( ($item->{'Content'} // '') eq $args->{Content} ) { if ( defined $item->{'LargeContent'} ) { $item_id = $item->{'ObjectId'} if defined $args->{LargeContent} && $item->{'LargeContent'} eq $args->{LargeContent}; } else { $item_id = $item->{'ObjectId'} unless defined $args->{LargeContent}; } } elsif ( $item->{'LargeContent'} && $args->{Content} ) { $item_id = $item->{'ObjectId'} if ($item->{'LargeContent'} eq $args->{Content}); } } last if $item_id; } if ( $item_id ) { my $ocfv = RT::ObjectCustomFieldValue->new( $self->CurrentUser ); my ($ret, $msg) = $ocfv->Load($item_id); RT::Logger->error("Unable to load object custom field value from id: $item_id $msg") unless $ret; return $ocfv; } else { return undef; } } RT::Base->_ImportOverlays(); # Clear the OCVF cache on exit to release connected RT::Ticket objects. # # Without this, there could be warnings generated like "Too late to safely run # transaction-batch scrips...". You can test this by commenting it out and running # some cf tests, e.g. perl -Ilib t/customfields/enter_one.t END { ClearOCFVCache(); } 1; ����������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/CustomFieldValues.pm����������������������������������������������������������������000644 �000765 �000024 �00000007443 14005011336 020040� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::CustomFieldValues; use strict; use warnings; use base 'RT::SearchBuilder'; use RT::CustomFieldValue; sub Table { 'CustomFieldValues'} sub _Init { my $self = shift; # By default, order by SortOrder $self->OrderByCols( { ALIAS => 'main', FIELD => 'SortOrder', ORDER => 'ASC' }, { ALIAS => 'main', FIELD => 'Name', ORDER => 'ASC' }, { ALIAS => 'main', FIELD => 'id', ORDER => 'ASC' }, ); return ( $self->SUPER::_Init(@_) ); } # {{{ sub LimitToCustomField =head2 LimitToCustomField FIELD Limits the returned set to values for the custom field with Id FIELD =cut sub LimitToCustomField { my $self = shift; my $cf = shift; return $self->Limit( FIELD => 'CustomField', VALUE => $cf, OPERATOR => '=', ); } =head2 SetCustomFieldObject Store the CustomField object which loaded this CustomFieldValues collection. Consumers of CustomFieldValues collection (such as External Custom Fields) can now work out how they were loaded (off a Queue or Ticket or something else) by inspecting the CustomField. =cut sub SetCustomFieldObject { my $self = shift; return $self->{'custom_field'} = shift; } =head2 CustomFieldObject Returns the CustomField object used to load this CustomFieldValues collection. Relies on $CustomField->Values having been called, is not set on manual loads. =cut sub CustomFieldObject { my $self = shift; return $self->{'custom_field'}; } =head2 AddRecord Propagates the CustomField object from the Collection down to individual CustomFieldValue objects. =cut sub AddRecord { my $self = shift; my $CFV = shift; $CFV->SetCustomFieldObj($self->CustomFieldObject); push @{$self->{'items'}}, $CFV; $self->{'rows'}++; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Attributes.pm�����������������������������������������������������������������������000644 �000765 �000024 �00000012402 14005011336 016557� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Attributes - collection of RT::Attribute objects =head1 SYNOPSIS use RT::Attributes; my $Attributes = RT::Attributes->new($CurrentUser); =head1 DESCRIPTION =head1 METHODS =cut package RT::Attributes; use strict; use warnings; use base 'RT::SearchBuilder'; use RT::Attribute; sub Table { 'Attributes'} sub _DoSearch { my $self = shift; $self->SUPER::_DoSearch(); # if _DoSearch doesn't fully succeed, 'must_redo_search' will be true # and call _BuildAccessTable then will result in a deep recursion if ( $self->{'must_redo_search'} ) { $RT::Logger->crit( "_DoSearch is not so successful as it still needs redo search, won't call _BuildAccessTable" ); } else { $self->_BuildAccessTable(); } } sub _BuildAccessTable { my $self = shift; delete $self->{'attr'}; while (my $attr = $self->Next) { push @{$self->{'attr'}->{$attr->Name}}, $attr; } } sub _AttrHash { my $self = shift; $self->_DoSearch if ($self->{'must_redo_search'} && $self->{'is_limited'}); unless ($self->{'attr'}) { $self->{'attr'}->{'__none'} = RT::Attribute->new($self->CurrentUser); } return ($self->{'attr'}); } =head2 Names Returns a list of the Names of all attributes for this object. =cut sub Names { my $self = shift; my @keys = keys %{$self->_AttrHash}; return(@keys); } =head2 Named STRING Returns an array of all the RT::Attribute objects with the name STRING =cut sub Named { my $self = shift; my $name = shift; my @attributes; if ($self->_AttrHash) { @attributes = @{($self->_AttrHash->{$name}||[])}; } return (@attributes); } =head2 DeleteEntry { Name => Content => , id => } Deletes attributes with the matching name and the matching content or id If Content and id are both undefined, delete all attributes with the matching name. =cut sub DeleteEntry { my $self = shift; my %args = ( Name => undef, Content => undef, id => undef, @_ ); my $found = 0; foreach my $attr ( $self->Named( $args{'Name'} ) ) { if ( ( !defined $args{'id'} and !defined $args{'Content'} ) or ( defined $args{'id'} and $attr->id eq $args{'id'} ) or ( defined $args{'Content'} and $attr->Content eq $args{'Content'} ) ) { my ($id, $msg) = $attr->Delete; return ($id, $msg) unless $id; $found = 1; } } return (0, "No entry found") unless $found; $self->RedoSearch; # XXX: above string must work but because of bug in DBIx::SB it doesn't, # to reproduce delete string below and run t/api/attribute-tests.t $self->_DoSearch; return (1, $self->loc('Attribute Deleted')); } =head2 LimitToObject $object Limit the Attributes to rights for the object $object. It needs to be an RT::Record class. =cut sub LimitToObject { my $self = shift; my $obj = shift; unless (eval { $obj->id} ){ return undef; } my $type = $obj->isa("RT::CurrentUser") ? "RT::User" : ref($obj); $self->Limit(FIELD => 'ObjectType', OPERATOR=> '=', VALUE => $type, ENTRYAGGREGATOR => 'OR'); $self->Limit(FIELD => 'ObjectId', OPERATOR=> '=', VALUE => $obj->id, ENTRYAGGREGATOR => 'OR', QUOTEVALUE => 0); } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/RightsInspector.pm������������������������������������������������������������������000644 �000765 �000024 �00000070372 14005011336 017572� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::RightsInspector; use strict; use warnings; # glossary: # inner role - being granted a right by way of ticket role membership # which is treated in a special way in RT. this is because # members of ticket AdminCc group are neither members of # the queue AdminCc group nor the system AdminCc group. # this means we have to do a really gnarly joins to recover # such ACLs. to improve comprehensibility we keep track # of such inner roles then massage the serialized data # afterwards to reference these implicit relationships # principal - the recipient of a privilege; e.g. user or group # object - the scope of the privilege; e.g. queue or system # record - generalization of principal and object since rendering # and whatnot can share code my $PageLimit = 100; sub CurrentUser { return $HTML::Mason::Commands::session{CurrentUser}; } sub _EscapeHTML { my $s = shift; RT::Interface::Web::EscapeHTML(\$s); return $s; } sub _EscapeURI { my $s = shift; RT::Interface::Web::EscapeURI(\$s); return $s; } # used to convert a search term (e.g. "root") into a regex for highlighting # in the UI. potentially useful hook point for implementing say, "ro*t" sub RegexifyTermForHighlight { my $self = shift; my $term = shift || ''; return qr/\Q$term\E/i; } # takes a text label and returns escaped html, highlighted using the search # term(s) sub HighlightTextForSearch { my $self = shift; my $text = shift; my $term = shift; my $re = ref($term) eq 'ARRAY' ? join '|', map { $self->RegexifyTermForHighlight($_) } @$term : $self->RegexifyTermForHighlight($term); # if $term is an arrayref, make sure we qr-ify it # without this, then if $term has no elements, we interpolate $re # as an empty string which causes the regex engine to fall into # an infinite loop $re = qr/$re/ unless ref($re); $text =~ s{ \G # where we left off the previous iteration thanks to /g (.*?) # non-matching text before the match ($re|$) # matching text, or the end of the line (to escape any # text after the last match) }{ _EscapeHTML($1) . (length $2 ? '<span class="match">' . _EscapeHTML($2) . '</span>' : '') }xeg; return $text; # now escaped as html } # takes a serialized result and highlights its labels according to the search # terms sub HighlightSerializedForSearch { my $self = shift; my $serialized = shift; my $args = shift; my $regex_search = shift; # highlight matching terms $serialized->{right_highlighted} = $self->HighlightTextForSearch($serialized->{right}, [split ' ', $args->{right} || '']); for my $key (qw/principal object/) { for my $record ($serialized->{$key}, $serialized->{$key}->{primary_record}) { next if !$record; # if we used a regex search for this record, then highlight the # text that the regex matched if ($regex_search->{$key}) { for my $column (qw/label detail/) { $record->{$column . '_highlighted'} = $self->HighlightTextForSearch($record->{$column}, $args->{$key}); } } # otherwise we used a search like user:root and so we should # highlight just that user completely (but not its parent group) else { $record->{'highlight'} = $record->{primary_record} ? 0 : 1; for my $column (qw/label detail/) { $record->{$column . '_highlighted'} = _EscapeHTML($record->{$column}); } } } } return; } # takes "u:root" "group:37" style specs and returns the RT::Principal sub PrincipalForSpec { my $self = shift; my $type = shift; my $identifier = shift; if ($type =~ /^(g|group)$/i) { my $group = RT::Group->new($self->CurrentUser); if ( $identifier =~ /^\d+$/ ) { $group->LoadByCols( id => $identifier, ); } else { $group->LoadByCols( Domain => 'UserDefined', Name => $identifier, ); } return $group->PrincipalObj if $group->Id; return (0, "Unable to load group $identifier"); } elsif ($type =~ /^(u|user)$/i) { my $user = RT::User->new($self->CurrentUser); my ($ok, $msg) = $user->Load($identifier); return $user->PrincipalObj if $user->Id; return (0, "Unable to load user $identifier"); } else { RT->Logger->debug("Unexpected type '$type'"); } return undef; } # takes "t#1" "queue:General", "asset:37" style specs and returns that object # limited to thinks you can grant rights on sub ObjectForSpec { my $self = shift; my $type = shift; my $identifier = shift; my $record; if ($type =~ /^(t|ticket)$/i) { $record = RT::Ticket->new($self->CurrentUser); } elsif ($type =~ /^(q|queue)$/i) { $record = RT::Queue->new($self->CurrentUser); } elsif ($type =~ /^asset$/i) { $record = RT::Asset->new($self->CurrentUser); } elsif ($type =~ /^catalog$/i) { $record = RT::Catalog->new($self->CurrentUser); } elsif ($type =~ /^(a|article)$/i) { $record = RT::Article->new($self->CurrentUser); } elsif ($type =~ /^class$/i) { $record = RT::Class->new($self->CurrentUser); } elsif ($type =~ /^cf|customfield$/i) { $record = RT::CustomField->new($self->CurrentUser); } elsif ($type =~ /^(g|group)$/i) { return $self->PrincipalForSpec($type, $identifier); } else { RT->Logger->debug("Unexpected type '$type'"); return undef; } $record->Load($identifier); return $record if $record->Id; my $class = ref($record); $class =~ s/^RT:://; return (0, "Unable to load $class '$identifier'"); return undef; } our %ParentMap = ( 'RT::Ticket' => [Queue => 'RT::Queue'], 'RT::Asset' => [Catalog => 'RT::Catalog'], ); # see inner role glossary entry # this has three modes, depending on which parameters are passed # - principal_id but no inner_id: find tickets/assets this principal # has permissions for # - inner_id but no principal_id: find the queue/system permissions that affect # this ticket # - principal and inner_id: find all permissions this principal has on # this "inner" object # there's no analagous query in the RT codebase because it uses a caching approach; # see RT::Tickets::_RolesCanSee sub InnerRoleQuery { my $self = shift; my %args = ( inner_class => '', # RT::Ticket, RT::Asset principal_id => undef, inner_id => undef, right_search => undef, @_, ); my $inner_class = $args{inner_class}; my $principal_id = $args{principal_id}; my $inner_id = $args{inner_id}; my $inner_table = $inner_class->Table; my ($parent_column, $parent_class) = @{ $ParentMap{$inner_class} || [] } or die "No parent mapping specified for $inner_class"; my $parent_table = $parent_class->Table; my @query = qq[ SELECT main.id, MIN(InnerRecords.id) AS example_record, COUNT(InnerRecords.id)-1 AS other_count FROM ACL main JOIN Groups ParentRoles ON main.PrincipalId = ParentRoles.id JOIN $inner_table InnerRecords ON (ParentRoles.Domain = '$parent_class-Role' AND InnerRecords.$parent_column = ParentRoles.Instance) OR ParentRoles.Domain = 'RT::System-Role' JOIN Groups InnerRoles ON InnerRoles.Instance = InnerRecords.Id AND InnerRoles.Name = main.PrincipalType ]; if ($principal_id) { push @query, qq[ JOIN CachedGroupMembers CGM ON CGM.GroupId = InnerRoles.id ]; } push @query, qq[ WHERE ]; if ($args{right_search}) { my $LIKE = RT->Config->Get('DatabaseType') eq 'Pg' ? 'ILIKE' : 'LIKE'; push @query, qq[ ( ]; for my $term (split ' ', $args{right_search}) { my $quoted = $RT::Handle->Quote('%' . $term . '%'); push @query, qq[ main.RightName $LIKE $quoted OR ], } push @query, qq[main.RightName $LIKE 'SuperUser']; push @query, qq[ ) AND ]; } if ($principal_id) { push @query, qq[ CGM.MemberId = $principal_id AND CGM.Disabled = 0 AND ]; } else { #push @query, qq[ # CGM.MemberId = $principal_id AND #]; } push @query, qq[ InnerRecords.id = $inner_id AND ] if $inner_id; push @query, qq[ InnerRoles.Domain = '$inner_class-Role' GROUP BY main.id ]; return join "\n", @query; } # key entry point into this extension; takes a query (principal, object, right) # and produces a list of highlighted results sub Search { my $self = shift; my %args = ( principal => '', object => '', right => '', @_, ); my @results; my $ACL = RT::ACL->new($self->CurrentUser); my $has_search = 0; my %use_regex_search_for = ( principal => 1, object => 1, ); my %primary_records = ( principal => undef, object => undef, ); my %filter_out; my %inner_role; if ($args{right}) { $has_search = 1; push @{ $filter_out{right} }, $2 while $args{right} =~ s/( |^)!(\S+)/$1/; for my $term (split ' ', $args{right}) { $ACL->Limit( FIELD => 'RightName', OPERATOR => 'LIKE', VALUE => $term, CASESENSITIVE => 0, ENTRYAGGREGATOR => 'OR', ); } $ACL->Limit( FIELD => 'RightName', OPERATOR => '=', VALUE => 'SuperUser', ENTRYAGGREGATOR => 'OR', ); } if ($args{object}) { push @{ $filter_out{object} }, $2 while $args{object} =~ s/( |^)!(\S+)/$1/; if (my ($type, $identifier) = $args{object} =~ m{ ^ \s* (t|ticket|q|queue|asset|catalog|a|article|class|g|group|cf|customfield) \s* [:#] \s* (.+?) \s* $ }xi) { my ($record, $msg) = $self->ObjectForSpec($type, $identifier); if (!$record) { return { error => $msg || 'Unable to find row' }; } $has_search = 1; $use_regex_search_for{object} = 0; $primary_records{object} = $record; for my $obj ($record, $record->ACLEquivalenceObjects, RT->System) { $ACL->_OpenParen('object'); $ACL->Limit( SUBCLAUSE => 'object', FIELD => 'ObjectType', OPERATOR => '=', VALUE => ref($obj), ENTRYAGGREGATOR => 'OR', ); $ACL->Limit( SUBCLAUSE => 'object', FIELD => 'ObjectId', OPERATOR => '=', VALUE => $obj->Id, QUOTEVALUE => 0, ENTRYAGGREGATOR => 'AND', ); $ACL->_CloseParen('object'); } } } my $principal_paren = 0; if ($args{principal}) { push @{ $filter_out{principal} }, $2 while $args{principal} =~ s/( |^)!(\S+)/$1/; if (my ($type, $identifier) = $args{principal} =~ m{ ^ \s* (u|user|g|group) \s* [:#] \s* (.+?) \s* $ }xi) { my ($principal, $msg) = $self->PrincipalForSpec($type, $identifier); if (!$principal) { return { error => $msg || 'Unable to find row' }; } $has_search = 1; $use_regex_search_for{principal} = 0; $primary_records{principal} = $principal; my $principal_alias = $ACL->Join( ALIAS1 => 'main', FIELD1 => 'PrincipalId', TABLE2 => 'Principals', FIELD2 => 'id', ); my $cgm_alias = $ACL->Join( ALIAS1 => 'main', FIELD1 => 'PrincipalId', TABLE2 => 'CachedGroupMembers', FIELD2 => 'GroupId', ); $ACL->_OpenParen('principal'); $principal_paren = 1; $ACL->Limit( ALIAS => $cgm_alias, SUBCLAUSE => 'principal', FIELD => 'Disabled', QUOTEVALUE => 0, VALUE => 0, ENTRYAGGREGATOR => 'AND', ); $ACL->Limit( ALIAS => $cgm_alias, SUBCLAUSE => 'principal', FIELD => 'MemberId', VALUE => $principal->Id, QUOTEVALUE => 0, ENTRYAGGREGATOR => 'AND', ); } } # now we need to address the unfortunate fact that ticket role # members are not listed as queue role members. the way we do this # is with a many-join query to map queue roles to ticket roles if ($primary_records{principal} || $primary_records{object}) { for my $inner_class (keys %ParentMap) { next if $primary_records{object} && !$primary_records{object}->isa($inner_class); my $query = $self->InnerRoleQuery( inner_class => $inner_class, principal_id => ($primary_records{principal} ? $primary_records{principal}->Id : undef), inner_id => ($primary_records{object} ? $primary_records{object}->Id : undef), right_search => $args{right}, ); my $sth = $ACL->_Handle->SimpleQuery($query); my @acl_ids; while (my ($acl_id, $record_id, $other_count) = $sth->fetchrow_array) { push @acl_ids, $acl_id; $inner_role{$acl_id} = [$inner_class, $record_id, $other_count]; } if (@acl_ids) { if (!$principal_paren) { $ACL->_OpenParen('principal'); $principal_paren = 1; } $ACL->Limit( SUBCLAUSE => 'principal', FIELD => 'id', OPERATOR => 'IN', VALUE => \@acl_ids, ENTRYAGGREGATOR => 'OR', ); } } } $ACL->_CloseParen('principal') if $principal_paren; if ($args{continueAfter}) { $has_search = 1; $ACL->Limit( FIELD => 'id', OPERATOR => '>', VALUE => int($args{continueAfter}), QUOTEVALUE => 0, ); } $ACL->OrderBy( ALIAS => 'main', FIELD => 'id', ORDER => 'ASC', ); $ACL->UnLimit unless $has_search; $ACL->RowsPerPage($PageLimit); my $continueAfter; ACE: while (my $ACE = $ACL->Next) { $continueAfter = $ACE->Id; my $serialized = $self->SerializeACE($ACE, \%primary_records, \%inner_role); for my $key (keys %filter_out) { for my $term (@{ $filter_out{$key} }) { my $re = qr/\Q$term\E/i; if ($key eq 'right') { next ACE if $serialized->{right} =~ $re; } else { my $record = $serialized->{$key}; next ACE if $record->{class} =~ $re || $record->{id} =~ $re || $record->{label} =~ $re || $record->{detail} =~ $re; } } } KEY: for my $key (qw/principal object/) { # filtering on the serialized record is hacky, but doing the # searching in SQL is absolutely a nonstarter next KEY unless $use_regex_search_for{$key}; if (my $term = $args{$key}) { my $record = $serialized->{$key}; $term =~ s/^\s+//; $term =~ s/\s+$//; my $re = qr/\Q$term\E/i; next KEY if $record->{class} =~ $re || $record->{id} =~ $re || $record->{label} =~ $re || $record->{detail} =~ $re; # no matches next ACE; } } $self->HighlightSerializedForSearch($serialized, \%args, \%use_regex_search_for); push @results, $serialized; } return { results => \@results, continueAfter => $continueAfter, }; } # takes an ACE (singular version of ACL) and produces a JSON-serializable # dictionary for transmitting over the wire sub SerializeACE { my $self = shift; my $ACE = shift; my $primary_records = shift; my $inner_role = shift; my $serialized = { principal => $self->SerializeRecord($ACE->PrincipalObj, $primary_records->{principal}), object => $self->SerializeRecord($ACE->Object, $primary_records->{object}), right => $ACE->RightName, ace => { id => $ACE->Id }, disable_revoke => $self->DisableRevoke($ACE), }; if ($inner_role->{$ACE->Id}) { $self->InjectSerializedWithInnerRoleDetails($serialized, $ACE, $inner_role->{$ACE->Id}, $primary_records); } return $serialized; } # should the "Revoke" button be disabled? by default it is for the two required # system privileges; if such privileges needed to be revoked they can be done # through the ordinary ACL management UI # it is also disabled for SuperUser, otherwise it is too easy to completely hose the system sub DisableRevoke { my $self = shift; my $ACE = shift; my $Principal = $ACE->PrincipalObj; my $Object = $ACE->Object; my $Right = $ACE->RightName; if ($Principal->Object->Domain eq 'ACLEquivalence') { my $User = $Principal->Object->InstanceObj; # identify super user priviledges required for the system to work if ($User->Id == RT->SystemUser->Id && $Object->isa('RT::System') && $Right eq 'SuperUser') { return 1; } if ($User->Id == RT->Nobody->Id && $Object->isa('RT::System') && $Right eq 'OwnTicket') { return 1; } } return 0; } # convert principal to its user/group, custom role group to its custom role, etc sub CanonicalizeRecord { my $self = shift; my $record = shift; return undef unless $record; if ($record->isa('RT::Principal')) { $record = $record->Object; } if ($record->isa('RT::Group')) { if ($record->Domain eq 'ACLEquivalence') { my $principal = RT::Principal->new($record->CurrentUser); $principal->Load($record->Instance); $record = $principal->Object; } elsif ($record->Domain =~ /-Role$/) { my ($id) = $record->Name =~ /^RT::CustomRole-(\d+)$/; if ($id) { my $role = RT::CustomRole->new($record->CurrentUser); $role->Load($id); $record = $role; } } } return $record; } # takes a user, group, ticket, queue, etc and produces a JSON-serializable # dictionary sub SerializeRecord { my $self = shift; my $record = shift; my $primary_record = shift; return undef unless $record; $record = $self->CanonicalizeRecord($record); $primary_record = $self->CanonicalizeRecord($primary_record); undef $primary_record if $primary_record && ref($record) eq ref($primary_record) && $record->Id == $primary_record->Id; my $serialized = { class => ref($record), id => $record->id, label => $self->LabelForRecord($record), detail => $self->DetailForRecord($record), url => $self->URLForRecord($record), disabled => $self->DisabledForRecord($record) ? JSON::true : JSON::false, primary_record => $self->SerializeRecord($primary_record), }; return $serialized; } sub InjectSerializedWithInnerRoleDetails { my $self = shift; my $serialized = shift; my $ACE = shift; my $inner_role = shift; my $primary_records = shift; my $principal = $self->CanonicalizeRecord($ACE->PrincipalObj); my $object = $self->CanonicalizeRecord($ACE->Object); my $primary_principal = $self->CanonicalizeRecord($primary_records->{principal}) || $principal; my $primary_object = $self->CanonicalizeRecord($primary_records->{object}) || $object; if ($principal->isa('RT::Group') || $principal->isa('RT::CustomRole')) { my ($inner_class, $inner_id, $inner_count) = @$inner_role; my $inner_record = $inner_class->new($self->CurrentUser); $inner_record->Load($inner_id); $inner_class =~ s/^RT:://i; my $detail = "$inner_class #$inner_id "; $detail .= $principal->isa('RT::Group') ? 'Role' : 'CustomRole'; $serialized->{principal}{detail} = $detail; $serialized->{principal}{detail_url} = $self->URLForRecord($inner_record); if ($inner_count) { $serialized->{principal}{detail_extra} = $self->CurrentUser->loc("(+[quant,_1,other,others])", $inner_count); if ($inner_class eq 'Ticket' && $primary_principal->isa('RT::User')) { my $query; if ($ACE->Object->isa('RT::Queue')) { my $name = $ACE->Object->Name; $name =~ s/(['\\])/\\$1/g; $query .= "Queue = '$name' AND "; } my $user_name = $primary_principal->Name; $user_name =~ s/(['\\])/\\$1/g; my $role_name = $principal->Name; $role_name =~ s/(['\\])/\\$1/g; my $role_term = $principal->isa('RT::Group') ? $role_name : "CustomRole.{$role_name}"; $query .= "$role_term.Name = '$user_name'"; $serialized->{principal}{detail_extra_url} = RT->Config->Get('WebURL') . 'Search/Results.html?Query=' . _EscapeURI($query); } } } } # primary display label for a record (e.g. user name, ticket subject) sub LabelForRecord { my $self = shift; my $record = shift; if ($record->isa('RT::Ticket')) { return $record->Subject || $self->CurrentUser->loc('(No subject)'); } return $record->Name || $self->CurrentUser->loc('(No name)'); } # boolean indicating whether the record should be labeled as disabled in the UI sub DisabledForRecord { my $self = shift; my $record = shift; if ($record->can('Disabled') || $record->_Accessible('Disabled', 'read')) { return $record->Disabled; } return 0; } # secondary detail information for a record (e.g. ticket #) sub DetailForRecord { my $self = shift; my $record = shift; my $id = $record->Id; return 'Global System' if $record->isa('RT::System'); return 'System User' if $record->isa('RT::User') && ($id == RT->SystemUser->Id || $id == RT->Nobody->Id); # like RT::Group->SelfDescription but without the redundant labels if ($record->isa('RT::Group')) { if ($record->RoleClass) { my $class = $record->RoleClass; $class =~ s/^RT:://i; return "$class Role"; } elsif ($record->Domain eq 'SystemInternal') { return "System Group"; } } my $type = ref($record); $type =~ s/^RT:://; return $type . ' #' . $id; } # most appropriate URL for a record. admin UI preferred, but for objects without # admin UI (such as ticket) then user UI is fine sub URLForRecord { my $self = shift; my $record = shift; my $id = $record->id; if ($record->isa('RT::Queue')) { return RT->Config->Get('WebURL') . 'Admin/Queues/Modify.html?id=' . $id; } elsif ($record->isa('RT::User')) { return undef if $id == RT->SystemUser->id || $id == RT->Nobody->id; return RT->Config->Get('WebURL') . 'Admin/Users/Modify.html?id=' . $id; } elsif ($record->isa('RT::Group')) { if ($record->Domain eq 'UserDefined') { return RT->Config->Get('WebURL') . 'Admin/Groups/Modify.html?id=' . $id; } elsif ($record->Domain eq 'RT::System-Role') { return RT->Config->Get('WebURL') . 'Admin/Global/GroupRights.html#acl-' . $id; } elsif ($record->Domain eq 'RT::Queue-Role') { return RT->Config->Get('WebURL') . 'Admin/Queues/GroupRights.html?id=' . $record->Instance . '#acl-' . $id; } elsif ($record->Domain eq 'RT::Catalog-Role') { return RT->Config->Get('WebURL') . 'Admin/Assets/Catalogs/GroupRights.html?id=' . $record->Instance . '#acl-' . $id; } else { return undef; } } elsif ($record->isa('RT::CustomField')) { return RT->Config->Get('WebURL') . 'Admin/CustomFields/Modify.html?id=' . $id; } elsif ($record->isa('RT::Class')) { return RT->Config->Get('WebURL') . 'Admin/Articles/Classes/Modify.html?id=' . $id; } elsif ($record->isa('RT::Catalog')) { return RT->Config->Get('WebURL') . 'Admin/Assets/Catalogs/Modify.html?id=' . $id; } elsif ($record->isa('RT::CustomRole')) { return RT->Config->Get('WebURL') . 'Admin/CustomRoles/Modify.html?id=' . $id; } elsif ($record->isa('RT::Ticket')) { return RT->Config->Get('WebURL') . 'Ticket/Display.html?id=' . $id; } elsif ($record->isa('RT::Asset')) { return RT->Config->Get('WebURL') . 'Asset/Display.html?id=' . $id; } elsif ($record->isa('RT::Article')) { return RT->Config->Get('WebURL') . 'Articles/Article/Display.html?id=' . $id; } return undef; } 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Config.pm���������������������������������������������������������������������������000644 �000765 �000024 �00000270536 14005011336 015654� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Config; use strict; use warnings; use 5.010; use File::Spec (); use Symbol::Global::Name; use List::MoreUtils 'uniq'; use Clone (); # Store log messages generated before RT::Logger is available our @PreInitLoggerMessages; =head1 NAME RT::Config - RT's config =head1 SYNOPSYS # get config object use RT::Config; my $config = RT::Config->new; $config->LoadConfigs; # get or set option my $rt_web_path = $config->Get('WebPath'); $config->Set(EmailOutputEncoding => 'latin1'); # get config object from RT package use RT; RT->LoadConfig; my $config = RT->Config; =head1 DESCRIPTION C<RT::Config> class provide access to RT's and RT extensions' config files. RT uses two files for site configuring: First file is F<RT_Config.pm> - core config file. This file is shipped with RT distribution and contains default values for all available options. B<You should never edit this file.> Second file is F<RT_SiteConfig.pm> - site config file. You can use it to customize your RT instance. In this file you can override any option listed in core config file. You may also split settings into separate files under the F<etc/RT_SiteConfig.d/> directory. All files ending in C<.pm> will be parsed, in alphabetical order, after F<RT_SiteConfig.pm> is loaded. RT extensions could also provide their config files. Extensions should use F<< <NAME>_Config.pm >> and F<< <NAME>_SiteConfig.pm >> names for config files, where <NAME> is extension name. B<NOTE>: All options from RT's config and extensions' configs are saved in one place and thus extension could override RT's options, but it is not recommended. =cut =head2 %META Hash of Config options that may be user overridable or may require more logic than should live in RT_*Config.pm Keyed by config name, there are several properties that can be set for each config optin: Section - What header this option should be grouped under on the user Preferences page Overridable - Can users change this option SortOrder - Within a Section, how should the options be sorted for display to the user Widget - Mason component path to widget that should be used to display this config option WidgetArguments - An argument hash passed to the WIdget Description - Friendly description to show the user Values - Arrayref of options (for select Widget) ValuesLabel - Hashref, key is the Value from the Values list, value is a user friendly description of the value Callback - subref that receives no arguments. It returns a hashref of items that are added to the rest of the WidgetArguments PostSet - subref passed the RT::Config object and the current and previous setting of the config option. This is called well before much of RT's subsystems are initialized, so what you can do here is pretty limited. It's mostly useful for effecting the value of other config options early. PostLoadCheck - subref passed the RT::Config object and the current setting of the config option. Can make further checks (such as seeing if a library is installed) and then change the setting of this or other options in the Config using the RT::Config option. Obfuscate - subref passed the RT::Config object, current setting of the config option and a user object, can return obfuscated value. it's called in RT->Config->GetObfuscated() =cut our %META; %META = ( # General user overridable options RestrictReferrerLogin => { PostLoadCheck => sub { my $self = shift; if (defined($self->Get('RestrictReferrerLogin'))) { RT::Logger->error("The config option 'RestrictReferrerLogin' is incorrect, and should be 'RestrictLoginReferrer' instead."); } }, }, DefaultQueue => { Section => 'General', Overridable => 1, SortOrder => 1, Widget => '/Widgets/Form/Select', WidgetArguments => { Description => 'Default queue', #loc Default => 1, # allow user to unset it on EditConfig.html Callback => sub { my $ret = { Values => [], ValuesLabel => {}}; my $q = RT::Queues->new($HTML::Mason::Commands::session{'CurrentUser'}); $q->UnLimit; while (my $queue = $q->Next) { next unless $queue->CurrentUserHasRight("CreateTicket"); push @{$ret->{Values}}, $queue->Id; $ret->{ValuesLabel}{$queue->Id} = $queue->Name; } return $ret; }, } }, RememberDefaultQueue => { Section => 'General', Overridable => 1, SortOrder => 2, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Remember default queue' # loc } }, UsernameFormat => { Section => 'General', Overridable => 1, SortOrder => 3, Widget => '/Widgets/Form/Select', WidgetArguments => { Description => 'Username format', # loc Values => [qw(role concise verbose)], ValuesLabel => { role => 'Privileged: usernames; Unprivileged: names and email addresses', # loc concise => 'Short usernames', # loc verbose => 'Name and email address', # loc }, }, }, AutocompleteOwners => { Section => 'General', Overridable => 1, SortOrder => 3.1, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Use autocomplete to find owners?', # loc Hints => 'Replaces the owner dropdowns with textboxes' #loc } }, AutocompleteQueues => { Section => 'General', Overridable => 1, SortOrder => 3.2, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Use autocomplete to find queues?', # loc Hints => 'Replaces the queue dropdowns with textboxes' #loc } }, WebDefaultStylesheet => { Section => 'General', #loc Overridable => 1, SortOrder => 4, Widget => '/Widgets/Form/Select', WidgetArguments => { Description => 'Theme', #loc Callback => sub { state @stylesheets; unless (@stylesheets) { for my $static_path ( RT::Interface::Web->StaticRoots ) { my $css_path = File::Spec->catdir( $static_path, 'css' ); next unless -d $css_path; if ( opendir my $dh, $css_path ) { push @stylesheets, grep { -e File::Spec->catfile( $css_path, $_, 'main.css' ) } readdir $dh; } else { RT->Logger->error("Can't read $css_path: $!"); } } @stylesheets = sort { lc $a cmp lc $b } uniq @stylesheets; } return { Values => \@stylesheets }; }, }, PostLoadCheck => sub { my $self = shift; my $value = $self->Get('WebDefaultStylesheet'); my @roots = RT::Interface::Web->StaticRoots; for my $root (@roots) { return if -d "$root/css/$value"; } $RT::Logger->warning( "The default stylesheet ($value) does not exist in this instance of RT. " . "Defaulting to elevator-light." ); $self->Set('WebDefaultStylesheet', 'elevator-light'); }, }, TimeInICal => { Section => 'General', Overridable => 1, SortOrder => 5, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Include time in iCal feed events?', # loc Hints => 'Formats iCal feed events with date and time' #loc } }, UseSideBySideLayout => { Section => 'Ticket composition', Overridable => 1, SortOrder => 5, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Use a two column layout for create and update forms?' # loc } }, MessageBoxRichText => { Section => 'Ticket composition', Overridable => 1, SortOrder => 5.1, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'WYSIWYG message composer' # loc } }, MessageBoxRichTextHeight => { Section => 'Ticket composition', Overridable => 1, SortOrder => 6, Widget => '/Widgets/Form/Integer', WidgetArguments => { Description => 'WYSIWYG composer height', # loc } }, MessageBoxWidth => { Section => 'Ticket composition', Overridable => 1, SortOrder => 7, Widget => '/Widgets/Form/Integer', WidgetArguments => { Description => 'Message box width', #loc }, }, MessageBoxHeight => { Section => 'Ticket composition', Overridable => 1, SortOrder => 8, Widget => '/Widgets/Form/Integer', WidgetArguments => { Description => 'Message box height', #loc }, }, DefaultTimeUnitsToHours => { Section => 'Ticket composition', #loc Overridable => 1, SortOrder => 9, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Enter time in hours by default', #loc Hints => 'Only for entry, not display', #loc }, }, SignatureAboveQuote => { Section => 'Ticket composition', #loc Overridable => 1, SortOrder => 10, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Place signature above quote', #loc }, }, PreferDropzone => { Section => 'Ticket composition', #loc Overridable => 1, SortOrder => 11, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Use dropzone if available', #loc }, }, RefreshIntervals => { Type => 'ARRAY', PostLoadCheck => sub { my $self = shift; my @intervals = $self->Get('RefreshIntervals'); if (grep { $_ == 0 } @intervals) { $RT::Logger->warning("Please do not include a 0 value in RefreshIntervals, as that default is already added for you."); } }, }, SearchResultsRefreshInterval => { Section => 'General', #loc Overridable => 1, SortOrder => 9, Widget => '/Widgets/Form/Select', WidgetArguments => { Description => 'Search results refresh interval', #loc Callback => sub { my @values = RT->Config->Get('RefreshIntervals'); my %labels = ( 0 => "Don't refresh search results.", # loc ); for my $value (@values) { if ($value % 60 == 0) { $labels{$value} = [ 'Refresh search results every [quant,_1,minute,minutes].', #loc $value / 60 ]; } else { $labels{$value} = [ 'Refresh search results every [quant,_1,second,seconds].', #loc $value ]; } } unshift @values, 0; return { Values => \@values, ValuesLabel => \%labels }; }, }, }, EnableJSChart => { Section => 'General', #loc Overridable => 1, SortOrder => 10, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Use JavaScript to render charts', #loc }, }, JSChartColorScheme => { Section => 'General', #loc Overridable => 1, SortOrder => 11, Widget => '/Widgets/Form/String', WidgetArguments => { Description => 'JavaScript chart color scheme', #loc }, }, # User overridable options for RT at a glance HomePageRefreshInterval => { Section => 'RT at a glance', #loc Overridable => 1, SortOrder => 2, Widget => '/Widgets/Form/Select', WidgetArguments => { Description => 'Home page refresh interval', #loc Callback => sub { my @values = RT->Config->Get('RefreshIntervals'); my %labels = ( 0 => "Don't refresh home page.", # loc ); for my $value (@values) { if ($value % 60 == 0) { $labels{$value} = [ 'Refresh home page every [quant,_1,minute,minutes].', #loc $value / 60 ]; } else { $labels{$value} = [ 'Refresh home page every [quant,_1,second,seconds].', #loc $value ]; } } unshift @values, 0; return { Values => \@values, ValuesLabel => \%labels }; }, }, }, # User overridable options for Ticket displays PreferRichText => { Section => 'Ticket display', # loc Overridable => 1, SortOrder => 0.9, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Display messages in rich text if available', # loc Hints => 'Rich text (HTML) shows formatting such as colored text, bold, italics, and more', # loc }, }, MaxInlineBody => { Section => 'Ticket display', #loc Overridable => 1, SortOrder => 1, Widget => '/Widgets/Form/Integer', WidgetArguments => { Description => 'Maximum inline message length', #loc Hints => "Length in characters; Use '0' to show all messages inline, regardless of length" #loc }, }, OldestTransactionsFirst => { Section => 'Ticket display', Overridable => 1, SortOrder => 2, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Show oldest history first', #loc }, }, ShowHistory => { Section => 'Ticket display', Overridable => 1, SortOrder => 3, Widget => '/Widgets/Form/Select', WidgetArguments => { Description => 'Show history', #loc Values => [qw(delay click always scroll)], ValuesLabel => { delay => "after the rest of the page loads", #loc click => "after clicking a link", #loc always => "immediately", #loc scroll => "as you scroll", #loc }, }, }, ShowUnreadMessageNotifications => { Section => 'Ticket display', Overridable => 1, SortOrder => 4, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Notify me of unread messages', #loc }, }, PlainTextMono => { Section => 'Ticket display', Overridable => 1, SortOrder => 5, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Display plain-text attachments in fixed-width font', #loc Hints => 'Display all plain-text attachments in a monospace font with formatting preserved, but wrapping as needed.', #loc }, }, MoreAboutRequestorTicketList => { Section => 'Ticket display', #loc Overridable => 1, SortOrder => 6, Widget => '/Widgets/Form/Select', WidgetArguments => { Description => '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 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 }, }, }, SimplifiedRecipients => { Section => 'Ticket display', #loc Overridable => 1, SortOrder => 7, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => "Show simplified recipient list on ticket update", #loc }, }, SquelchedRecipients => { Section => 'Ticket display', #loc Overridable => 1, SortOrder => 8, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => "Default to squelching all outgoing email notifications (from web interface) on ticket update", #loc }, }, DisplayTicketAfterQuickCreate => { Section => 'Ticket display', Overridable => 1, SortOrder => 9, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Display ticket after "Quick Create"', #loc }, }, QuoteFolding => { Section => 'Ticket display', Overridable => 1, SortOrder => 10, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Enable quote folding?' # loc } }, HideUnsetFieldsOnDisplay => { Section => 'Ticket display', Overridable => 1, SortOrder => 11, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Hide unset fields?' # loc } }, InlineEdit => { Section => 'Ticket display', Overridable => 1, SortOrder => 12, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Enable inline edit?' # loc } }, InlineEditPanelBehavior => { Type => 'HASH', PostLoadCheck => sub { my $config = shift; # use scalar context intentionally to avoid not a hash error my $behavior = $config->Get('InlineEditPanelBehavior') || {}; unless (ref($behavior) eq 'HASH') { RT->Logger->error("Config option \%InlineEditPanelBehavior is a @{[ref $behavior]} not a HASH; ignoring"); $behavior = {}; } my %valid = map { $_ => 1 } qw/link click always hide/; for my $class (keys %$behavior) { if (ref($behavior->{$class}) eq 'HASH') { for my $panel (keys %{ $behavior->{$class} }) { my $value = $behavior->{$class}{$panel}; if (!$valid{$value}) { RT->Logger->error("Config option \%InlineEditPanelBehavior{$class}{$panel}, which is '$value', must be one of: " . (join ', ', map { "'$_'" } sort keys %valid) . "; ignoring"); delete $behavior->{$class}{$panel}; } } } else { RT->Logger->error("Config option \%InlineEditPanelBehavior{$class} is not a HASH; ignoring"); delete $behavior->{$class}; next; } } $config->Set( InlineEditPanelBehavior => %$behavior ); }, }, # User overridable locale options DateTimeFormat => { Section => 'Locale', #loc Overridable => 1, Widget => '/Widgets/Form/Select', WidgetArguments => { Description => 'Date format', #loc Callback => sub { my $ret = { Values => [], ValuesLabel => {}}; my $date = RT::Date->new($HTML::Mason::Commands::session{'CurrentUser'}); $date->SetToNow; foreach my $value ($date->Formatters) { push @{$ret->{Values}}, $value; $ret->{ValuesLabel}{$value} = $date->Get( Format => $value, Timezone => 'user', ); } return $ret; }, }, }, RTAddressRegexp => { Type => 'SCALAR', Immutable => 1, PostLoadCheck => sub { my $self = shift; my $value = $self->Get('RTAddressRegexp'); if (not $value) { $RT::Logger->debug( 'The RTAddressRegexp option is not set in the config.' .' Not setting this option results in additional SQL queries to' .' check whether each address belongs to RT or not.' .' It is especially important to set this option if RT receives' .' emails on addresses that are not in the database or config.' ); } elsif (ref $value and ref $value eq "Regexp") { # Ensure that the regex is case-insensitive; while the # local part of email addresses is _technically_ # case-sensitive, most MTAs don't treat it as such. $RT::Logger->warning( 'RTAddressRegexp is set to a case-sensitive regular expression.' .' This may lead to mail loops with MTAs which treat the' .' local part as case-insensitive -- which is most of them.' ) if "$value" =~ /^\(\?[a-z]*-([a-z]*):/ and "$1" =~ /i/; } }, }, # User overridable mail options EmailFrequency => { Section => 'Mail', #loc Overridable => 1, Default => 'Individual messages', Widget => '/Widgets/Form/Select', WidgetArguments => { Description => 'Email delivery', #loc Values => [ 'Individual messages', #loc 'Daily digest', #loc 'Weekly digest', #loc 'Suspended' #loc ] } }, NotifyActor => { Section => 'Mail', #loc Overridable => 1, SortOrder => 2, Widget => '/Widgets/Form/Boolean', WidgetArguments => { Description => 'Outgoing mail', #loc Hints => 'Should RT send you mail for ticket updates you make?', #loc } }, # this tends to break extensions that stash links in ticket update pages Organization => { Type => 'SCALAR', Immutable => 1, Widget => '/Widgets/Form/String', PostLoadCheck => sub { my ($self,$value) = @_; $RT::Logger->error("your \$Organization setting ($value) appears to contain whitespace. Please fix this.") if $value =~ /\s/;; }, }, rtname => { Immutable => 1, Widget => '/Widgets/Form/String', }, # Internal config options DatabaseExtraDSN => { Type => 'HASH', Immutable => 1, }, DatabaseAdmin => { Immutable => 1, Widget => '/Widgets/Form/String', }, DatabaseHost => { Immutable => 1, Widget => '/Widgets/Form/String', }, DatabaseName => { Immutable => 1, Widget => '/Widgets/Form/String', }, DatabasePassword => { Immutable => 1, Widget => '/Widgets/Form/String', Obfuscate => sub { my ($config, $sources, $user) = @_; return $user->loc('Password not printed'); }, }, DatabasePort => { Immutable => 1, Widget => '/Widgets/Form/Integer', }, DatabaseRTHost => { Immutable => 1, Widget => '/Widgets/Form/String', }, DatabaseType => { Immutable => 1, Widget => '/Widgets/Form/String', }, DatabaseUser => { Immutable => 1, Widget => '/Widgets/Form/String', }, FullTextSearch => { Type => 'HASH', PostLoadCheck => sub { my $self = shift; my $v = $self->Get('FullTextSearch'); return unless $v->{Enable} and $v->{Indexed}; my $dbtype = $self->Get('DatabaseType'); if ($dbtype eq 'Oracle') { if (not $v->{IndexName}) { $RT::Logger->error("No IndexName set for full-text index; disabling"); $v->{Enable} = $v->{Indexed} = 0; } } elsif ($dbtype eq 'Pg') { my $bad = 0; if (not $v->{'Column'}) { $RT::Logger->error("No Column set for full-text index; disabling"); $v->{Enable} = $v->{Indexed} = 0; } elsif ($v->{'Column'} eq "Content" and (not $v->{'Table'} or $v->{'Table'} eq "Attachments")) { $RT::Logger->error("Column for full-text index is set to Content, not tsvector column; disabling"); $v->{Enable} = $v->{Indexed} = 0; } } elsif ($dbtype eq 'mysql') { if (not $v->{'Table'}) { $RT::Logger->error("No Table set for full-text index; disabling"); $v->{Enable} = $v->{Indexed} = 0; } elsif ($v->{'Table'} eq "Attachments") { $RT::Logger->error("Table for full-text index is set to Attachments, not FTS table; disabling"); $v->{Enable} = $v->{Indexed} = 0; } else { my (undef, $create) = eval { $RT::Handle->dbh->selectrow_array("SHOW CREATE TABLE " . $v->{Table}); }; my ($engine) = ($create||'') =~ /engine=(\S+)/i; if (not $create) { $RT::Logger->error("External table ".$v->{Table}." does not exist"); $v->{Enable} = $v->{Indexed} = 0; } elsif (lc $engine eq "sphinx") { # External Sphinx indexer $v->{Sphinx} = 1; unless ($v->{'MaxMatches'}) { $RT::Logger->warn("No MaxMatches set for full-text index; defaulting to 10000"); $v->{MaxMatches} = 10_000; } } else { # Internal, one-column table $v->{Column} = 'Content'; $v->{Engine} = $engine; } } } else { $RT::Logger->error("Indexed full-text-search not supported for $dbtype"); $v->{Indexed} = 0; } }, }, DisableGraphViz => { Type => 'SCALAR', Widget => '/Widgets/Form/Boolean', PostLoadCheck => sub { my $self = shift; my $value = shift; return if $value; return if GraphViz->require; $RT::Logger->debug("You've enabled GraphViz, but we couldn't load the module: $@"); $self->Set( DisableGraphViz => 1 ); }, }, DisableGD => { Type => 'SCALAR', Widget => '/Widgets/Form/Boolean', PostLoadCheck => sub { my $self = shift; my $value = shift; return if $value; return if GD->require; $RT::Logger->debug("You've enabled GD, but we couldn't load the module: $@"); $self->Set( DisableGD => 1 ); }, }, MailCommand => { Type => 'SCALAR', Widget => '/Widgets/Form/String', PostLoadCheck => sub { my $self = shift; my $value = $self->Get('MailCommand'); return if ref($value) eq "CODE" or $value =~/^(sendmail|sendmailpipe|qmail|testfile|mbox)$/; $RT::Logger->error("Unknown value for \$MailCommand: $value; defaulting to sendmailpipe"); $self->Set( MailCommand => 'sendmailpipe' ); }, }, HTMLFormatter => { Type => 'SCALAR', Widget => '/Widgets/Form/String', PostLoadCheck => sub { RT::Interface::Email->_HTMLFormatter }, }, Plugins => { Immutable => 1, }, RecordBaseClass => { Immutable => 1, Widget => '/Widgets/Form/String', }, WebSessionClass => { Immutable => 1, Widget => '/Widgets/Form/String', }, DevelMode => { Immutable => 1, Widget => '/Widgets/Form/Boolean', }, DisallowExecuteCode => { Immutable => 1, Widget => '/Widgets/Form/Boolean', }, MailPlugins => { Type => 'ARRAY', Immutable => 1, PostLoadCheck => sub { my $self = shift; # Make sure Crypt is post-loaded first $META{Crypt}{'PostLoadCheck'}->( $self, $self->Get( 'Crypt' ) ); RT::Interface::Email::Plugins(Add => ["Authz::Default", "Action::Defaults"]); RT::Interface::Email::Plugins(Add => ["Auth::MailFrom"]) unless RT::Interface::Email::Plugins(Code => 1, Method => "GetCurrentUser"); }, }, Crypt => { Immutable => 1, Invisible => 1, Type => 'HASH', PostLoadCheck => sub { my $self = shift; require RT::Crypt; for my $proto (RT::Crypt->EnabledProtocols) { my $opt = $self->Get($proto); if (not RT::Crypt->LoadImplementation($proto)) { $RT::Logger->error("You enabled $proto, but we couldn't load module RT::Crypt::$proto"); $opt->{'Enable'} = 0; } elsif (not RT::Crypt->LoadImplementation($proto)->Probe) { $opt->{'Enable'} = 0; } elsif ($META{$proto}{'PostLoadCheck'}) { $META{$proto}{'PostLoadCheck'}->( $self, $self->Get( $proto ) ); } } my $opt = $self->Get('Crypt'); my @enabled = RT::Crypt->EnabledProtocols; my %enabled; $enabled{$_} = 1 for @enabled; $opt->{'Enable'} = scalar @enabled; $opt->{'Incoming'} = [ $opt->{'Incoming'} ] if $opt->{'Incoming'} and not ref $opt->{'Incoming'}; if ( $opt->{'Incoming'} && @{ $opt->{'Incoming'} } ) { $RT::Logger->warning("$_ explicitly set as incoming Crypt plugin, but not marked Enabled; removing") for grep {not $enabled{$_}} @{$opt->{'Incoming'}}; $opt->{'Incoming'} = [ grep {$enabled{$_}} @{$opt->{'Incoming'}} ]; } else { $opt->{'Incoming'} = \@enabled; } if ( $opt->{'Outgoing'} ) { if (not $enabled{$opt->{'Outgoing'}}) { $RT::Logger->warning($opt->{'Outgoing'}. " explicitly set as outgoing Crypt plugin, but not marked Enabled; " . (@enabled ? "using $enabled[0]" : "removing")); } $opt->{'Outgoing'} = $enabled[0] unless $enabled{$opt->{'Outgoing'}}; } else { $opt->{'Outgoing'} = $enabled[0]; } }, }, SMIME => { Type => 'HASH', Immutable => 1, Invisible => 1, Obfuscate => sub { my ( $config, $value, $user ) = @_; $value->{Passphrase} = $user->loc('Password not printed'); return $value; }, PostLoadCheck => sub { my $self = shift; my $opt = $self->Get('SMIME'); return unless $opt->{'Enable'}; if (exists $opt->{Keyring}) { unless ( File::Spec->file_name_is_absolute( $opt->{Keyring} ) ) { $opt->{Keyring} = File::Spec->catfile( $RT::BasePath, $opt->{Keyring} ); } unless (-d $opt->{Keyring} and -r _) { $RT::Logger->info( "RT's SMIME libraries couldn't successfully read your". " configured SMIME keyring directory (".$opt->{Keyring} .")."); delete $opt->{Keyring}; } } if (defined $opt->{CAPath}) { if (-d $opt->{CAPath} and -r _) { # directory, all set } elsif (-f $opt->{CAPath} and -r _) { # file, all set } else { $RT::Logger->warn( "RT's SMIME libraries could not read your configured CAPath (".$opt->{CAPath}.")" ); delete $opt->{CAPath}; } } }, }, GnuPG => { Type => 'HASH', Immutable => 1, Invisible => 1, Obfuscate => sub { my ( $config, $value, $user ) = @_; $value->{Passphrase} = $user->loc('Password not printed'); return $value; }, PostLoadCheck => sub { my $self = shift; my $gpg = $self->Get('GnuPG'); return unless $gpg->{'Enable'}; my $gpgopts = $self->Get('GnuPGOptions'); unless ( File::Spec->file_name_is_absolute( $gpgopts->{homedir} ) ) { $gpgopts->{homedir} = File::Spec->catfile( $RT::BasePath, $gpgopts->{homedir} ); } unless (-d $gpgopts->{homedir} && -r _ ) { # no homedir, no gpg $RT::Logger->info( "RT's GnuPG libraries couldn't successfully read your". " configured GnuPG home directory (".$gpgopts->{homedir} ."). GnuPG support has been disabled"); $gpg->{'Enable'} = 0; return; } if ( grep exists $gpg->{$_}, qw(RejectOnMissingPrivateKey RejectOnBadData AllowEncryptDataInDB) ) { $RT::Logger->warning( "The RejectOnMissingPrivateKey, RejectOnBadData and AllowEncryptDataInDB" ." GnuPG options are now properties of the generic Crypt configuration. You" ." should set them there instead." ); delete $gpg->{$_} for qw(RejectOnMissingPrivateKey RejectOnBadData AllowEncryptDataInDB); } } }, GnuPGOptions => { Type => 'HASH', Immutable => 1, Invisible => 1, Obfuscate => sub { my ( $config, $value, $user ) = @_; $value->{passphrase} = $user->loc('Password not printed'); return $value; }, }, ReferrerWhitelist => { Type => 'ARRAY' }, EmailDashboardLanguageOrder => { Type => 'ARRAY' }, CustomFieldValuesCanonicalizers => { Type => 'ARRAY' }, WebPath => { Immutable => 1, Widget => '/Widgets/Form/String', PostLoadCheck => sub { my $self = shift; my $value = shift; # "In most cases, you should leave $WebPath set to '' (an empty value)." return unless $value; # try to catch someone who assumes that you shouldn't leave this empty if ($value eq '/') { $RT::Logger->error("For the WebPath config option, use the empty string instead of /"); return; } # $WebPath requires a leading / but no trailing /, or it can be blank. return if $value =~ m{^/.+[^/]$}; if ($value =~ m{/$}) { $RT::Logger->error("The WebPath config option requires no trailing slash"); } if ($value !~ m{^/}) { $RT::Logger->error("The WebPath config option requires a leading slash"); } }, }, WebDomain => { Immutable => 1, Widget => '/Widgets/Form/String', PostLoadCheck => sub { my $self = shift; my $value = shift; if (!$value) { $RT::Logger->error("You must set the WebDomain config option"); return; } if ($value =~ m{^(\w+://)}) { $RT::Logger->error("The WebDomain config option must not contain a scheme ($1)"); return; } if ($value =~ m{(/.*)}) { $RT::Logger->error("The WebDomain config option must not contain a path ($1)"); return; } if ($value =~ m{:(\d*)}) { $RT::Logger->error("The WebDomain config option must not contain a port ($1)"); return; } }, }, WebPort => { Immutable => 1, Widget => '/Widgets/Form/Integer', PostLoadCheck => sub { my $self = shift; my $value = shift; if (!$value) { $RT::Logger->error("You must set the WebPort config option"); return; } if ($value !~ m{^\d+$}) { $RT::Logger->error("The WebPort config option must be an integer"); } }, }, WebBaseURL => { Immutable => 1, Widget => '/Widgets/Form/String', PostLoadCheck => sub { my $self = shift; my $value = shift; if (!$value) { $RT::Logger->error("You must set the WebBaseURL config option"); return; } if ($value !~ m{^https?://}i) { $RT::Logger->error("The WebBaseURL config option must contain a scheme (http or https)"); } if ($value =~ m{/$}) { $RT::Logger->error("The WebBaseURL config option requires no trailing slash"); } if ($value =~ m{^https?://.+?(/[^/].*)}i) { $RT::Logger->error("The WebBaseURL config option must not contain a path ($1)"); } }, }, WebURL => { Immutable => 1, Widget => '/Widgets/Form/String', PostLoadCheck => sub { my $self = shift; my $value = shift; if (!$value) { $RT::Logger->error("You must set the WebURL config option"); return; } if ($value !~ m{^https?://}i) { $RT::Logger->error("The WebURL config option must contain a scheme (http or https)"); } if ($value !~ m{/$}) { $RT::Logger->error("The WebURL config option requires a trailing slash"); } }, }, EmailInputEncodings => { Type => 'ARRAY', PostLoadCheck => sub { my $self = shift; my $value = $self->Get('EmailInputEncodings'); return unless $value && @$value; my %seen; foreach my $encoding ( grep defined && length, splice @$value ) { next if $seen{ $encoding }; if ( $encoding eq '*' ) { unshift @$value, '*'; next; } my $canonic = Encode::resolve_alias( $encoding ); unless ( $canonic ) { $RT::Logger->warning("Unknown encoding '$encoding' in \@EmailInputEncodings option"); } elsif ( $seen{ $canonic }++ ) { next; } else { push @$value, $canonic; } } }, }, CustomFieldGroupings => { Type => 'HASH', PostLoadCheck => sub { my $config = shift; # use scalar context intentionally to avoid not a hash error my $groups = $config->Get('CustomFieldGroupings') || {}; unless (ref($groups) eq 'HASH') { RT->Logger->error("Config option \%CustomFieldGroupings is a @{[ref $groups]} not a HASH; ignoring"); $groups = {}; } for my $class (keys %$groups) { my @h; if (ref($groups->{$class}) eq 'HASH') { push @h, $_, $groups->{$class}->{$_} for sort {lc($a) cmp lc($b)} keys %{ $groups->{$class} }; } elsif (ref($groups->{$class}) eq 'ARRAY') { @h = @{ $groups->{$class} }; } else { RT->Logger->error("Config option \%CustomFieldGroupings{$class} is not a HASH or ARRAY; ignoring"); delete $groups->{$class}; next; } $groups->{$class} = []; while (@h) { my $group = shift @h; my $ref = shift @h; if (ref($ref) eq 'ARRAY') { push @{$groups->{$class}}, $group => $ref; } else { RT->Logger->error("Config option \%CustomFieldGroupings{$class}{$group} is not an ARRAY; ignoring"); } } } $config->Set( CustomFieldGroupings => %$groups ); }, }, CustomDateRanges => { Type => 'HASH', Widget => '/Widgets/Form/CustomDateRanges', PostLoadCheck => sub { my $config = shift; # use scalar context intentionally to avoid not a hash error my $ranges = $config->Get('CustomDateRanges') || {}; unless (ref($ranges) eq 'HASH') { RT->Logger->error("Config option \%CustomDateRanges is a @{[ref $ranges]} not a HASH"); return; } for my $class (keys %$ranges) { if (ref($ranges->{$class}) eq 'HASH') { for my $name (keys %{ $ranges->{$class} }) { my $spec = $ranges->{$class}{$name}; if (!ref($spec) || ref($spec) eq 'HASH') { # this will produce error messages if parsing fails $class->require; $class->_ParseCustomDateRangeSpec($name, $spec); } else { RT->Logger->error("Config option \%CustomDateRanges{$class}{$name} is not a string or HASH"); } } } else { RT->Logger->error("Config option \%CustomDateRanges{$class} is not a HASH"); } } my %system_config = %$ranges; if ( my $db_config = $config->Get('CustomDateRangesUI') ) { for my $type ( keys %$db_config ) { for my $name ( keys %{ $db_config->{$type} || {} } ) { if ( $system_config{$type}{$name} ) { RT->Logger->warning("$type custom date range $name is defined by config file and db"); } else { $system_config{$name} = $db_config->{$type}{$name}; } } } } for my $type ( keys %system_config ) { my $attributes = RT::Attributes->new( RT->SystemUser ); $attributes->Limit( FIELD => 'Name', VALUE => 'Pref-CustomDateRanges' ); $attributes->Limit( FIELD => 'ObjectType', VALUE => 'RT::User' ); $attributes->OrderBy( FIELD => 'id' ); while ( my $attribute = $attributes->Next ) { if ( my $content = $attribute->Content ) { for my $name ( keys %{ $content->{$type} || {} } ) { if ( $system_config{$type}{$name} ) { RT->Logger->warning( "$type custom date range $name is defined by system and user #" . $attribute->ObjectId ); } } } } } }, }, CustomDateRangesUI => { Type => 'HASH', Widget => '/Widgets/Form/CustomDateRanges', }, ExternalStorage => { Type => 'HASH', PostLoadCheck => sub { my $self = shift; my %hash = $self->Get('ExternalStorage'); return unless keys %hash; require RT::ExternalStorage; my $backend = RT::ExternalStorage::Backend->new(%hash); RT->System->ExternalStorage($backend); }, }, ChartColors => { Type => 'ARRAY', }, LogoImageHeight => { Deprecated => { LogLevel => "info", Message => "The LogoImageHeight configuration option did not affect display, and has been removed; please remove it from your RT_SiteConfig.pm", }, }, LogoImageWidth => { Deprecated => { LogLevel => "info", Message => "The LogoImageWidth configuration option did not affect display, and has been removed; please remove it from your RT_SiteConfig.pm", }, }, ExternalAuth => { Immutable => 1, Widget => '/Widgets/Form/Boolean', }, DisablePasswordForAuthToken => { Widget => '/Widgets/Form/Boolean', }, ExternalSettings => { Immutable => 1, Obfuscate => sub { # Ensure passwords are obfuscated on the System Configuration page my ($config, $sources, $user) = @_; my $msg = $user->loc('Password not printed'); for my $source (values %$sources) { $source->{pass} = $msg; } return $sources; }, PostLoadCheck => sub { my $self = shift; my $settings = shift || {}; $self->EnableExternalAuth() if keys %$settings > 0; my $remove = sub { my ($service) = @_; delete $settings->{$service}; $self->Set( 'ExternalAuthPriority', [ grep { $_ ne $service } @{ $self->Get('ExternalAuthPriority') || [] } ] ); $self->Set( 'ExternalInfoPriority', [ grep { $_ ne $service } @{ $self->Get('ExternalInfoPriority') || [] } ] ); }; for my $service (keys %$settings) { my %conf = %{ $settings->{$service} }; if ($conf{type} !~ /^(ldap|db|cookie)$/) { $RT::Logger->error( "Service '$service' in ExternalInfoPriority is not ldap, db, or cookie; removing." ); $remove->($service); next; } next unless $conf{type} eq 'db'; # Ensure people don't misconfigure DBI auth to point to RT's # Users table; only check server/hostname/table, as # user/pass might be different (root, for instance) no warnings 'uninitialized'; next unless lc $conf{server} eq lc RT->Config->Get('DatabaseHost') and lc $conf{database} eq lc RT->Config->Get('DatabaseName') and lc $conf{table} eq 'users'; $RT::Logger->error( "RT::Authen::ExternalAuth should _not_ be configured with a database auth service ". "that points back to RT's internal Users table. Removing the service '$service'! ". "Please remove it from your config file." ); $remove->($service); } $self->Set( 'ExternalSettings', $settings ); }, }, ExternalAuthPriority => { Immutable => 1, PostLoadCheck => sub { my $self = shift; my @values = @{ shift || [] }; return unless @values or $self->Get('ExternalSettings'); if (not @values) { $RT::Logger->debug("ExternalAuthPriority not defined. Attempting to create based on ExternalSettings"); $self->Set( 'ExternalAuthPriority', \@values ); return; } my %settings; if ( $self->Get('ExternalSettings') ){ %settings = %{ $self->Get('ExternalSettings') }; } else{ $RT::Logger->error("ExternalSettings not defined. ExternalAuth requires the ExternalSettings configuration option to operate properly"); return; } for my $key (grep {not $settings{$_}} @values) { $RT::Logger->error("Removing '$key' from ExternalAuthPriority, as it is not defined in ExternalSettings"); } @values = grep {$settings{$_}} @values; $self->Set( 'ExternalAuthPriority', \@values ); }, }, ExternalInfoPriority => { Immutable => 1, PostLoadCheck => sub { my $self = shift; my @values = @{ shift || [] }; return unless @values or $self->Get('ExternalSettings'); if (not @values) { $RT::Logger->debug("ExternalInfoPriority not defined. User information (including user enabled/disabled) cannot be externally-sourced"); $self->Set( 'ExternalInfoPriority', \@values ); return; } my %settings; if ( $self->Get('ExternalSettings') ){ %settings = %{ $self->Get('ExternalSettings') }; } else{ $RT::Logger->error("ExternalSettings not defined. ExternalAuth requires the ExternalSettings configuration option to operate properly"); return; } for my $key (grep {not $settings{$_}} @values) { $RT::Logger->error("Removing '$key' from ExternalInfoPriority, as it is not defined in ExternalSettings"); } @values = grep {$settings{$_}} @values; for my $key (grep {$settings{$_}{type} eq "cookie"} @values) { $RT::Logger->error("Removing '$key' from ExternalInfoPriority, as cookie authentication cannot be used as an information source"); } @values = grep {$settings{$_}{type} ne "cookie"} @values; $self->Set( 'ExternalInfoPriority', \@values ); }, }, PriorityAsString => { Type => 'HASH', PostLoadCheck => sub { my $self = shift; return unless $self->Get('EnablePriorityAsString'); my $config = $self->Get('PriorityAsString'); my %map; for my $name ( keys %$config ) { if ( my $value = $config->{$name} ) { my @list; if ( ref $value eq 'ARRAY' ) { @list = @$value; } elsif ( ref $value eq 'HASH' ) { @list = %$value; } else { RT->Logger->error("Invalid value for $name in PriorityAsString"); undef $config->{$name}; } while ( my $label = shift @list ) { my $value = shift @list; $map{$label} //= $value; if ( $map{$label} != $value ) { RT->Logger->debug("Priority $label is inconsistent: $map{$label} VS $value"); } } } } unless ( keys %map ) { RT->Logger->debug("No valid PriorityAsString options"); $self->Set( 'EnablePriorityAsString', 0 ); } }, }, ServiceBusinessHours => { Type => 'HASH', PostLoadCheck => sub { my $self = shift; my $config = $self->Get('ServiceBusinessHours'); for my $name (keys %$config) { if ($config->{$name}->{7}) { RT->Logger->error("Config option \%ServiceBusinessHours '$name' erroneously specifies '$config->{$name}->{7}->{Name}' as day 7; Sunday should be specified as day 0."); } } }, }, ServiceAgreements => { Type => 'HASH', }, AssetHideSimpleSearch => { Widget => '/Widgets/Form/Boolean', }, AssetShowSearchResultCount => { Widget => '/Widgets/Form/Boolean', }, AllowUserAutocompleteForUnprivileged => { Widget => '/Widgets/Form/Boolean', }, AlwaysDownloadAttachments => { Widget => '/Widgets/Form/Boolean', }, AmbiguousDayInFuture => { Widget => '/Widgets/Form/Boolean', }, AmbiguousDayInPast => { Widget => '/Widgets/Form/Boolean', }, ApprovalRejectionNotes => { Widget => '/Widgets/Form/Boolean', }, ArticleOnTicketCreate => { Widget => '/Widgets/Form/Boolean', }, AutoCreateNonExternalUsers => { Widget => '/Widgets/Form/Boolean', }, AutocompleteOwnersForSearch => { Widget => '/Widgets/Form/Boolean', }, CanonicalizeRedirectURLs => { Widget => '/Widgets/Form/Boolean', }, CanonicalizeURLsInFeeds => { Widget => '/Widgets/Form/Boolean', }, ChartsTimezonesInDB => { Widget => '/Widgets/Form/Boolean', }, CheckMoreMSMailHeaders => { Widget => '/Widgets/Form/Boolean', }, DateDayBeforeMonth => { Widget => '/Widgets/Form/Boolean', }, DisplayTotalTimeWorked => { Widget => '/Widgets/Form/Boolean', }, DontSearchFileAttachments => { Widget => '/Widgets/Form/Boolean', }, DropLongAttachments => { Widget => '/Widgets/Form/Boolean', }, EditCustomFieldsSingleColumn => { Widget => '/Widgets/Form/Boolean', }, EnableReminders => { Widget => '/Widgets/Form/Boolean', }, EnablePriorityAsString => { Widget => '/Widgets/Form/Boolean', }, ExternalStorageDirectLink => { Widget => '/Widgets/Form/Boolean', }, ForceApprovalsView => { Widget => '/Widgets/Form/Boolean', }, ForwardFromUser => { Widget => '/Widgets/Form/Boolean', }, Framebusting => { Widget => '/Widgets/Form/Boolean', }, HideArticleSearchOnReplyCreate => { Widget => '/Widgets/Form/Boolean', }, HideResolveActionsWithDependencies => { Widget => '/Widgets/Form/Boolean', }, HideTimeFieldsFromUnprivilegedUsers => { Widget => '/Widgets/Form/Boolean', }, LoopsToRTOwner => { Widget => '/Widgets/Form/Boolean', }, MessageBoxIncludeSignature => { Widget => '/Widgets/Form/Boolean', }, MessageBoxIncludeSignatureOnComment => { Widget => '/Widgets/Form/Boolean', }, OnlySearchActiveTicketsInSimpleSearch => { Widget => '/Widgets/Form/Boolean', }, ParseNewMessageForTicketCcs => { Widget => '/Widgets/Form/Boolean', }, PreferDateTimeFormatNatural => { Widget => '/Widgets/Form/Boolean', }, PreviewScripMessages => { Widget => '/Widgets/Form/Boolean', }, RecordOutgoingEmail => { Widget => '/Widgets/Form/Boolean', }, RestrictLoginReferrer => { Widget => '/Widgets/Form/Boolean', }, RestrictReferrer => { Widget => '/Widgets/Form/Boolean', }, SearchResultsAutoRedirect => { Widget => '/Widgets/Form/Boolean', }, SelfServiceUseDashboard => { Widget => '/Widgets/Form/Boolean', }, ShowBccHeader => { Widget => '/Widgets/Form/Boolean', }, ShowEditSystemConfig => { Immutable => 1, Widget => '/Widgets/Form/Boolean', }, ShowEditLifecycleConfig => { Immutable => 1, Widget => '/Widgets/Form/Boolean', }, ShowMoreAboutPrivilegedUsers => { Widget => '/Widgets/Form/Boolean', }, ShowRTPortal => { Widget => '/Widgets/Form/Boolean', }, ShowRemoteImages => { Widget => '/Widgets/Form/Boolean', }, ShowTransactionImages => { Widget => '/Widgets/Form/Boolean', }, StoreLoops => { Widget => '/Widgets/Form/Boolean', }, StrictLinkACL => { Widget => '/Widgets/Form/Boolean', }, SuppressInlineTextFiles => { Widget => '/Widgets/Form/Boolean', }, TreatAttachedEmailAsFiles => { Widget => '/Widgets/Form/Boolean', }, TruncateLongAttachments => { Widget => '/Widgets/Form/Boolean', }, TrustHTMLAttachments => { Widget => '/Widgets/Form/Boolean', }, UseFriendlyFromLine => { Widget => '/Widgets/Form/Boolean', }, UseFriendlyToLine => { Widget => '/Widgets/Form/Boolean', }, UseOriginatorHeader => { Widget => '/Widgets/Form/Boolean', }, UseSQLForACLChecks => { Widget => '/Widgets/Form/Boolean', }, UseTransactionBatch => { Widget => '/Widgets/Form/Boolean', }, ValidateUserEmailAddresses => { Widget => '/Widgets/Form/Boolean', }, WebFallbackToRTLogin => { Widget => '/Widgets/Form/Boolean', }, WebFlushDbCacheEveryRequest => { Widget => '/Widgets/Form/Boolean', }, WebHttpOnlyCookies => { Widget => '/Widgets/Form/Boolean', }, WebRemoteUserAuth => { Widget => '/Widgets/Form/Boolean', }, WebRemoteUserAutocreate => { Widget => '/Widgets/Form/Boolean', }, WebRemoteUserContinuous => { Widget => '/Widgets/Form/Boolean', }, WebRemoteUserGecos => { Widget => '/Widgets/Form/Boolean', }, WebSecureCookies => { Widget => '/Widgets/Form/Boolean', }, WikiImplicitLinks => { Widget => '/Widgets/Form/Boolean', }, HideOneTimeSuggestions => { Widget => '/Widgets/Form/Boolean', }, LinkArticlesOnInclude => { Widget => '/Widgets/Form/Boolean', }, SelfServiceCorrespondenceOnly => { Widget => '/Widgets/Form/Boolean', }, SelfServiceDownloadUserData => { Widget => '/Widgets/Form/Boolean', }, SelfServiceShowGroupTickets => { Widget => '/Widgets/Form/Boolean', }, SelfServiceShowArticleSearch => { Widget => '/Widgets/Form/Boolean', }, ShowSearchResultCount => { Widget => '/Widgets/Form/Boolean', }, AllowGroupAutocompleteForUnprivileged => { Widget => '/Widgets/Form/Boolean', }, AttachmentListCount => { Widget => '/Widgets/Form/Integer', }, AutoLogoff => { Widget => '/Widgets/Form/Integer', }, BcryptCost => { Widget => '/Widgets/Form/Integer', }, DefaultSummaryRows => { Widget => '/Widgets/Form/Integer', }, DropdownMenuLimit => { Widget => '/Widgets/Form/Integer', }, ExternalStorageCutoffSize => { Widget => '/Widgets/Form/Integer', }, LogoutRefresh => { Widget => '/Widgets/Form/Integer', }, MaxAttachmentSize => { Widget => '/Widgets/Form/Integer', }, MaxFulltextAttachmentSize => { Widget => '/Widgets/Form/Integer', }, MinimumPasswordLength => { Widget => '/Widgets/Form/Integer', }, MoreAboutRequestorGroupsLimit => { Widget => '/Widgets/Form/Integer', }, TicketsItemMapSize => { Widget => '/Widgets/Form/Integer', }, AssetDefaultSearchResultOrderBy => { Widget => '/Widgets/Form/String', }, CanonicalizeEmailAddressMatch => { Widget => '/Widgets/Form/String', }, CanonicalizeEmailAddressReplace => { Widget => '/Widgets/Form/String', }, CommentAddress => { Widget => '/Widgets/Form/String', }, CorrespondAddress => { Widget => '/Widgets/Form/String', }, DashboardAddress => { Widget => '/Widgets/Form/String', }, DashboardSubject => { Widget => '/Widgets/Form/String', }, DefaultErrorMailPrecedence => { Widget => '/Widgets/Form/String', }, DefaultMailPrecedence => { Widget => '/Widgets/Form/String', }, DefaultSearchResultOrderBy => { Widget => '/Widgets/Form/String', }, EmailOutputEncoding => { Widget => '/Widgets/Form/String', }, FriendlyFromLineFormat => { Widget => '/Widgets/Form/String', }, FriendlyToLineFormat => { Widget => '/Widgets/Form/String', }, LDAPHost => { Widget => '/Widgets/Form/String', }, LDAPUser => { Widget => '/Widgets/Form/String', }, LDAPPassword => { Widget => '/Widgets/Form/String', Obfuscate => sub { my ($config, $sources, $user) = @_; return $user->loc('Password not printed'); }, }, LDAPBase => { Widget => '/Widgets/Form/String', }, LDAPGroupBase => { Widget => '/Widgets/Form/String', }, LogDir => { Immutable => 1, Widget => '/Widgets/Form/String', }, LogToFileNamed => { Immutable => 1, Widget => '/Widgets/Form/String', }, LogoAltText => { Widget => '/Widgets/Form/String', }, LogoLinkURL => { Widget => '/Widgets/Form/String', }, LogoURL => { Widget => '/Widgets/Form/String', }, OwnerEmail => { Widget => '/Widgets/Form/String', }, QuoteWrapWidth => { Widget => '/Widgets/Form/Integer', }, RedistributeAutoGeneratedMessages => { Widget => '/Widgets/Form/String', }, SelfServiceRequestUpdateQueue => { Widget => '/Widgets/Form/String', }, SendmailArguments => { Widget => '/Widgets/Form/String', }, SendmailBounceArguments => { Widget => '/Widgets/Form/String', }, SendmailPath => { Widget => '/Widgets/Form/String', }, SetOutgoingMailFrom => { Widget => '/Widgets/Form/String', }, Timezone => { Widget => '/Widgets/Form/String', }, VERPPrefix => { Widget => '/Widgets/Form/String', WidgetArguments => { Hints => 'rt-', }, }, VERPDomain => { Widget => '/Widgets/Form/String', WidgetArguments => { Callback => sub { return { Hints => RT->Config->Get( 'Organization') } }, }, }, WebImagesURL => { Widget => '/Widgets/Form/String', }, AssetDefaultSearchResultOrder => { Widget => '/Widgets/Form/Select', WidgetArguments => { Values => [qw(ASC DESC)] }, }, LogToSyslog => { Immutable => 1, Widget => '/Widgets/Form/Select', WidgetArguments => { Values => [qw(debug info notice warning error critical alert emergency)] }, }, LogToSTDERR => { Immutable => 1, Widget => '/Widgets/Form/Select', WidgetArguments => { Values => [qw(debug info notice warning error critical alert emergency)] }, }, LogToFile => { Immutable => 1, Widget => '/Widgets/Form/Select', WidgetArguments => { Values => [qw(debug info notice warning error critical alert emergency)] }, }, LogStackTraces => { Immutable => 1, Widget => '/Widgets/Form/Select', WidgetArguments => { Values => [qw(debug info notice warning error critical alert emergency)] }, }, StatementLog => { Widget => '/Widgets/Form/Select', WidgetArguments => { Values => ['', qw(debug info notice warning error critical alert emergency)] }, }, DefaultCatalog => { Widget => '/Widgets/Form/Select', WidgetArguments => { Description => 'Default catalog', #loc Callback => sub { my $ret = { Values => [], ValuesLabel => {} }; my $c = RT::Catalogs->new( $HTML::Mason::Commands::session{'CurrentUser'} ); $c->UnLimit; while ( my $catalog = $c->Next ) { next unless $catalog->CurrentUserHasRight("CreateAsset"); push @{ $ret->{Values} }, $catalog->Id; $ret->{ValuesLabel}{ $catalog->Id } = $catalog->Name; } return $ret; }, } }, DefaultSearchResultOrder => { Widget => '/Widgets/Form/Select', WidgetArguments => { Values => [qw(ASC DESC)] }, }, LogToSyslogConf => { Immutable => 1, }, ShowMobileSite => { Widget => '/Widgets/Form/Boolean', }, StaticRoots => { Type => 'ARRAY', Immutable => 1, }, EmailSubjectTagRegex => { Immutable => 1, }, ExtractSubjectTagMatch => { Immutable => 1, }, ExtractSubjectTagNoMatch => { Immutable => 1, }, WebNoAuthRegex => { Immutable => 1, }, SelfServiceRegex => { Immutable => 1, }, ); my %OPTIONS = (); my @LOADED_CONFIGS = (); =head1 METHODS =head2 new Object constructor returns new object. Takes no arguments. =cut sub new { my $proto = shift; my $class = ref($proto) ? ref($proto) : $proto; my $self = bless {}, $class; $self->_Init(@_); return $self; } sub _Init { return; } =head2 LoadConfigs Load all configs. First of all load RT's config then load extensions' config files in alphabetical order. Takes no arguments. =cut sub LoadConfigs { my $self = shift; $self->LoadConfig( File => 'RT_Config.pm' ); my @configs = $self->Configs; $self->LoadConfig( File => $_ ) foreach @configs; return; } =head1 LoadConfig Takes param hash with C<File> field. First, the site configuration file is loaded, in order to establish overall site settings like hostname and name of RT instance. Then, the core configuration file is loaded to set fallback values for all settings; it bases some values on settings from the site configuration file. B<Note> that core config file don't change options if site config has set them so to add value to some option instead of overriding you have to copy original value from core config file. =cut sub LoadConfig { my $self = shift; my %args = ( File => '', @_ ); $args{'File'} =~ s/(?<!Site)(?=Config\.pm$)/Site/; if ( $args{'File'} eq 'RT_SiteConfig.pm' ) { my $load = $ENV{RT_SITE_CONFIG} || $args{'File'}; $self->_LoadConfig( %args, File => $load ); # to allow load siteconfig again and again in case it's updated delete $INC{$load}; my $dir = $ENV{RT_SITE_CONFIG_DIR} || "$RT::EtcPath/RT_SiteConfig.d"; my $localdir = $ENV{RT_SITE_CONFIG_DIR} || "$RT::LocalEtcPath/RT_SiteConfig.d"; for my $file ( sort(<$dir/*.pm>), sort(<$localdir/*.pm>) ) { $self->_LoadConfig( %args, File => $file, Site => 1, Extension => '' ); delete $INC{$file}; } } else { $self->_LoadConfig(%args); delete $INC{$args{'File'}}; } $args{'File'} =~ s/Site(?=Config\.pm$)//; $self->_LoadConfig(%args); return 1; } sub _LoadConfig { my $self = shift; my %args = ( File => '', @_ ); my ($is_ext, $is_site); if ( defined $args{Site} && defined $args{Extension} ) { $is_ext = $args{Extension}; $is_site = $args{Site}; } elsif ( $args{'File'} eq ($ENV{RT_SITE_CONFIG}||'') ) { ($is_ext, $is_site) = ('', 1); } else { $is_ext = $args{'File'} =~ /^(?!RT_)(?:(.*)_)(?:Site)?Config/ ? $1 : ''; $is_site = $args{'File'} =~ /SiteConfig/ ? 1 : 0; } eval { package RT; local *Set = sub(\[$@%]@) { my ( $opt_ref, @args ) = @_; my ( $pack, $file, $line ) = caller; return $self->SetFromConfig( Option => $opt_ref, Value => [@args], Package => $pack, File => $file, Line => $line, SiteConfig => $is_site, Extension => $is_ext, ); }; local *Plugin = sub { my (@new_plugins) = @_; @new_plugins = map {s/-/::/g if not /:/; $_} @new_plugins; my ( $pack, $file, $line ) = caller; return $self->SetFromConfig( Option => \@RT::Plugins, Value => [@RT::Plugins, @new_plugins], Package => $pack, File => $file, Line => $line, SiteConfig => $is_site, Extension => $is_ext, ); }; my @etc_dirs = ($RT::LocalEtcPath); push @etc_dirs, RT->PluginDirs('etc') if $is_ext; push @etc_dirs, $RT::EtcPath, @INC; local @INC = @etc_dirs; eval { require $args{'File'} }; if ( $@ && $@ !~ /did not return a true value/ ) { die $@; } }; if ($@) { return 1 if $is_site && $@ =~ /^Can't locate \Q$args{File}/; if ( $is_site || $@ !~ /^Can't locate \Q$args{File}/ ) { die qq{Couldn't load RT config file $args{'File'}:\n\n$@}; } my $username = getpwuid($>); my $group = getgrgid($(); my ( $file_path, $fileuid, $filegid ); foreach ( $RT::LocalEtcPath, $RT::EtcPath, @INC ) { my $tmp = File::Spec->catfile( $_, $args{File} ); ( $fileuid, $filegid ) = ( stat($tmp) )[ 4, 5 ]; if ( defined $fileuid ) { $file_path = $tmp; last; } } unless ($file_path) { die qq{Couldn't load RT config file $args{'File'} as user $username / group $group.\n} . qq{The file couldn't be found in $RT::LocalEtcPath and $RT::EtcPath.\n$@}; } my $message = <<EOF; RT couldn't load RT config file %s as: user: $username group: $group The file is owned by user %s and group %s. This usually means that the user/group your webserver is running as cannot read the file. Be careful not to make the permissions on this file too liberal, because it contains database passwords. You may need to put the webserver user in the appropriate group (%s) or change permissions be able to run succesfully. EOF my $fileusername = getpwuid($fileuid); my $filegroup = getgrgid($filegid); my $errormessage = sprintf( $message, $file_path, $fileusername, $filegroup, $filegroup ); die "$errormessage\n$@"; } else { # Loaded successfully push @LOADED_CONFIGS, { as => $args{'File'}, filename => $INC{ $args{'File'} }, extension => $is_ext, site => $is_site, }; } return 1; } sub PostLoadCheck { my $self = shift; foreach my $o ( grep $META{$_}{'PostLoadCheck'}, $self->Options( Overridable => undef ) ) { $META{$o}->{'PostLoadCheck'}->( $self, $self->Get($o) ); } } =head2 SectionMap A data structure used to breakup the option list into tabs/sections/subsections/options This is done by parsing RT_Config.pm. =cut our $SectionMap = []; our $SectionMapLoaded = 0; # so we only load it once sub LoadSectionMap { my $self = shift; if ($SectionMapLoaded) { return $SectionMap; } my $ConfigFile = "$RT::EtcPath/RT_Config.pm"; require Pod::Simple::HTML; my $PodParser = Pod::Simple::HTML->new(); my $html; $PodParser->output_string( \$html ); $PodParser->parse_file($ConfigFile); my $has_subsection; while ( $html =~ m{<(h[123]|dt)\b[^>]*>(.*?)</\1>}sg ) { my ( $tag, $content ) = ( $1, $2 ); if ( $tag eq 'h1' ) { my ($title) = $content =~ m{<a class='u'\s*name="[^"]*"\s*>([^<]*)</a>}; next if $title =~ /^(?:NAME|DESCRIPTION)$/; push @$SectionMap, { Name => $title, Content => [] }; } elsif (@$SectionMap) { if ( $tag eq 'h2' ) { my ($title) = $content =~ m{<a class='u'\s*name="[^"]*"\s*>([^<]*)</a>}; push @{ $SectionMap->[-1]{Content} }, { Name => $title, Content => [] }; $has_subsection = 0; } elsif ( $tag eq 'h3' ) { my ($title) = $content =~ m{<a class='u'\s*name="[^"]*"\s*>([^<]*)</a>}; push @{ $SectionMap->[-1]{Content}[-1]{Content} }, { Name => $title, Content => [] }; $has_subsection ||= 1; } else { # tag is 'dt' if ( !$has_subsection ) { # Create an empty subsection to keep the same data structure push @{ $SectionMap->[-1]{Content}[-1]{Content} }, { Name => '', Content => [] }; $has_subsection = 1; } # a single item (dt) can document several options, in separate <code> elements my ($name) = $content =~ m{name=".([^"]*)"}; $name =~ s{,_.}{-}g; # e.g. DatabaseHost,_$DatabaseRTHost while ( $content =~ m{<code>(.)([^<]*)</code>}sg ) { my ( $sigil, $option ) = ( $1, $2 ); next unless $sigil =~ m{[\@\%\$]}; # no sigil => this is a value for a select option if ( $META{$option} ) { next if $META{$option}{Invisible}; } push @{ $SectionMap->[-1]{Content}[-1]{Content}[-1]{Content} }, { Name => $option, Help => $name }; } } } } # Remove empty tabs/sections for my $tab (@$SectionMap) { for my $section ( @{ $tab->{Content} } ) { @{ $section->{Content} } = grep { @{ $_->{Content} } } @{ $section->{Content} }; } @{ $tab->{Content} } = grep { @{ $_->{Content} } } @{ $tab->{Content} }; } @$SectionMap = grep { @{ $_->{Content} } } @$SectionMap; $SectionMapLoaded = 1; return $SectionMap; } =head2 Configs Returns list of config files found in local etc, plugins' etc and main etc directories. =cut sub Configs { my $self = shift; my @configs = (); foreach my $path ( $RT::LocalEtcPath, RT->PluginDirs('etc'), $RT::EtcPath ) { my $mask = File::Spec->catfile( $path, "*_Config.pm" ); my @files = glob $mask; @files = grep !/^RT_Config\.pm$/, grep $_ && /^\w+_Config\.pm$/, map { s/^.*[\\\/]//; $_ } @files; push @configs, sort @files; } my %seen; @configs = grep !$seen{$_}++, @configs; return @configs; } =head2 LoadedConfigs Returns a list of hashrefs, one for each config file loaded. The keys of the hashes are: =over 4 =item as Name this config file was loaded as (relative filename usually). =item filename The full path and filename. =item extension The "extension" part of the filename. For example, the file C<RTIR_Config.pm> will have an C<extension> value of C<RTIR>. =item site True if the file is considered a site-level override. For example, C<site> will be false for C<RT_Config.pm> and true for C<RT_SiteConfig.pm>. =back =cut sub LoadedConfigs { # Copy to avoid the caller changing our internal data return map { \%$_ } @LOADED_CONFIGS } =head2 Get Takes name of the option as argument and returns its current value. In the case of a user-overridable option, first checks the user's preferences before looking for site-wide configuration. Returns values from RT_SiteConfig, RT_Config and then the %META hash of configuration variables's "Default" for this config variable, in that order. Returns different things in scalar and array contexts. For scalar options it's not that important, however for arrays and hash it's. In scalar context returns references to arrays and hashes. Use C<scalar> perl's op to force context, especially when you use C<(..., Argument => RT->Config->Get('ArrayOpt'), ...)> as perl's '=>' op doesn't change context of the right hand argument to scalar. Instead use C<(..., Argument => scalar RT->Config->Get('ArrayOpt'), ...)>. It's also important for options that have no default value(no default in F<etc/RT_Config.pm>). If you don't force scalar context then you'll get empty list and all your named args will be messed up. For example C<(arg1 => 1, arg2 => RT->Config->Get('OptionDoesNotExist'), arg3 => 3)> will result in C<(arg1 => 1, arg2 => 'arg3', 3)> what is most probably unexpected, or C<(arg1 => 1, arg2 => RT->Config->Get('ArrayOption'), arg3 => 3)> will result in C<(arg1 => 1, arg2 => 'element of option', 'another_one' => ..., 'arg3', 3)>. =cut sub Get { my ( $self, $name, $user ) = @_; my $res; if ( $user && $user->id && $META{$name}->{'Overridable'} ) { my $prefs = $user->Preferences($RT::System); $res = $prefs->{$name} if $prefs; } $res = $OPTIONS{$name} unless defined $res; $res = $META{$name}->{'Default'} unless defined $res; return $self->_ReturnValue( $res, $META{$name}->{'Type'} || 'SCALAR' ); } =head2 GetObfuscated the same as Get, except it returns Obfuscated value via Obfuscate sub =cut sub GetObfuscated { my $self = shift; my ( $name, $user ) = @_; my $obfuscate = $META{$name}->{Obfuscate}; # we use two Get here is to simplify the logic of the return value # configs need obfuscation are supposed to be less, so won't be too heavy return $self->Get(@_) unless $obfuscate; my $res = Clone::clone( $self->Get( @_ ) ); $res = $obfuscate->( $self, $res, $user && $user->Id ? $user : RT->SystemUser ); return $self->_ReturnValue( $res, $META{$name}->{'Type'} || 'SCALAR' ); } =head2 Set Set option's value to new value. Takes name of the option and new value. Returns old value. The new value should be scalar, array or hash depending on type of the option. If the option is not defined in meta or the default RT config then it is of scalar type. =cut sub Set { my ( $self, $name ) = ( shift, shift ); my $old = $OPTIONS{$name}; my $type = $META{$name}->{'Type'} || 'SCALAR'; if ( $type eq 'ARRAY' ) { $OPTIONS{$name} = [@_]; { no warnings 'once'; no strict 'refs'; @{"RT::$name"} = (@_); } } elsif ( $type eq 'HASH' ) { $OPTIONS{$name} = {@_}; { no warnings 'once'; no strict 'refs'; %{"RT::$name"} = (@_); } } else { $OPTIONS{$name} = shift; {no warnings 'once'; no strict 'refs'; ${"RT::$name"} = $OPTIONS{$name}; } } $META{$name}->{'Type'} = $type; $META{$name}->{'PostSet'}->($self, $OPTIONS{$name}, $old) if $META{$name}->{'PostSet'}; if ($META{$name}->{'Deprecated'}) { my %deprecated = %{$META{$name}->{'Deprecated'}}; my $new_var = $deprecated{Instead} || ''; $self->SetFromConfig( Option => \$new_var, Value => [$OPTIONS{$name}], %{$self->Meta($name)->{'Source'}} ) if $new_var; $META{$name}->{'PostLoadCheck'} ||= sub { RT->Deprecated( Message => "Configuration option $name is deprecated", Stack => 0, %deprecated, ); }; } return $self->_ReturnValue( $old, $type ); } sub _ReturnValue { my ( $self, $res, $type ) = @_; return $res unless wantarray; if ( $type eq 'ARRAY' ) { return @{ $res || [] }; } elsif ( $type eq 'HASH' ) { return %{ $res || {} }; } return $res; } sub SetFromConfig { my $self = shift; my %args = ( Option => undef, Value => [], Package => 'RT', File => '', Line => 0, SiteConfig => 1, Extension => 0, @_ ); unless ( $args{'File'} ) { ( $args{'Package'}, $args{'File'}, $args{'Line'} ) = caller(1); } my $opt = $args{'Option'}; my $type; my $name = Symbol::Global::Name->find($opt); if ($name) { $type = ref $opt; $name =~ s/.*:://; } else { $name = $$opt; $type = $META{$name}->{'Type'} || 'SCALAR'; } # if option is already set we have to check where # it comes from and may be ignore it if ( exists $OPTIONS{$name} ) { if ( $type eq 'HASH' ) { $args{'Value'} = [ @{ $args{'Value'} }, @{ $args{'Value'} }%2? (undef) : (), $self->Get( $name ), ]; } elsif ( $args{'SiteConfig'} && $args{'Extension'} ) { # if it's site config of an extension then it can only # override options that came from its main config if ( $args{'Extension'} ne $META{$name}->{'Source'}{'Extension'} ) { my %source = %{ $META{$name}->{'Source'} }; push @PreInitLoggerMessages, "Change of config option '$name' at $args{'File'} line $args{'Line'} has been ignored." ." This option earlier has been set in $source{'File'} line $source{'Line'}." ." To overide this option use ". ($source{'Extension'}||'RT') ." site config." ; return 1; } } elsif ( !$args{'SiteConfig'} && $META{$name}->{'Source'}{'SiteConfig'} ) { # if it's core config then we can override any option that came from another # core config, but not site config my %source = %{ $META{$name}->{'Source'} }; if ( $source{'Extension'} ne $args{'Extension'} ) { # as a site config is loaded earlier then its base config # then we warn only on different extensions, for example # RTIR's options is set in main site config push @PreInitLoggerMessages, "Change of config option '$name' at $args{'File'} line $args{'Line'} has been ignored." ." It may be ok, but we want you to be aware." ." This option has been set earlier in $source{'File'} line $source{'Line'}." ; } return 1; } } $META{$name}->{'Type'} = $type; foreach (qw(Package File Line SiteConfig Extension Database)) { $META{$name}->{'Source'}->{$_} = $args{$_}; } $self->Set( $name, @{ $args{'Value'} } ); return 1; } =head2 Metadata =head2 Meta =cut sub Meta { return $META{ $_[1] }; } sub Sections { my $self = shift; my %seen; my @sections = sort grep !$seen{$_}++, map $_->{'Section'} || 'General', values %META; return @sections; } sub Options { my $self = shift; my %args = ( Section => undef, Overridable => 1, Sorted => 1, @_ ); my @res = sort keys %META; @res = grep( ( $META{$_}->{'Section'} || 'General' ) eq $args{'Section'}, @res ) if defined $args{'Section'}; if ( defined $args{'Overridable'} ) { @res = grep( ( $META{$_}->{'Overridable'} || 0 ) == $args{'Overridable'}, @res ); } if ( $args{'Sorted'} ) { @res = sort { ($META{$a}->{SortOrder}||9999) <=> ($META{$b}->{SortOrder}||9999) || $a cmp $b } @res; } else { @res = sort { $a cmp $b } @res; } return @res; } =head2 AddOption( Name => '', Section => '', ... ) =cut sub AddOption { my $self = shift; my %args = ( Name => undef, Section => undef, Overridable => 0, SortOrder => undef, Widget => '/Widgets/Form/String', WidgetArguments => {}, @_ ); unless ( $args{Name} ) { $RT::Logger->error("Need Name to add a new config"); return; } unless ( $args{Section} ) { $RT::Logger->error("Need Section to add a new config option"); return; } $META{ delete $args{Name} } = \%args; } =head2 DeleteOption( Name => '' ) =cut sub DeleteOption { my $self = shift; my %args = ( Name => undef, @_ ); if ( $args{Name} ) { delete $META{$args{Name}}; } else { $RT::Logger->error("Need Name to remove a config option"); return; } } =head2 UpdateOption( Name => '' ), Section => '', ... ) =cut sub UpdateOption { my $self = shift; my %args = ( Name => undef, Section => undef, Overridable => undef, SortOrder => undef, Widget => undef, WidgetArguments => undef, @_ ); my $name = delete $args{Name}; unless ( $name ) { $RT::Logger->error("Need Name to update a new config"); return; } unless ( exists $META{$name} ) { $RT::Logger->error("Config $name doesn't exist"); return; } for my $type ( keys %args ) { next unless defined $args{$type}; $META{$name}{$type} = $args{$type}; } return 1; } sub ObjectHasCustomFieldGrouping { my $self = shift; my %args = ( Object => undef, Grouping => undef, @_ ); my $object_type = RT::CustomField->_GroupingClass($args{Object}); my $groupings = RT->Config->Get( 'CustomFieldGroupings' ); return 0 unless $groupings; return 1 if $groupings->{$object_type} && grep { $_ eq $args{Grouping} } @{ $groupings->{$object_type} }; return 0; } # Internal method to activate ExtneralAuth if any ExternalAuth config # options are set. sub EnableExternalAuth { my $self = shift; $self->Set('ExternalAuth', 1); require RT::Authen::ExternalAuth; return; } my $database_config_cache_time = 0; my %original_setting_from_files; my $in_config_change_txn = 0; sub BeginDatabaseConfigChanges { $in_config_change_txn = $in_config_change_txn + 1; } sub EndDatabaseConfigChanges { $in_config_change_txn = $in_config_change_txn - 1; if (!$in_config_change_txn) { shift->ApplyConfigChangeToAllServerProcesses(); } } sub ApplyConfigChangeToAllServerProcesses { my $self = shift; return if $in_config_change_txn; # first apply locally $self->LoadConfigFromDatabase(); # then notify other servers RT->System->ConfigCacheNeedsUpdate($database_config_cache_time); } sub RefreshConfigFromDatabase { my $self = shift; if ($in_config_change_txn) { RT->Logger->error("It appears that there were unbalanced calls to BeginDatabaseConfigChanges with EndDatabaseConfigChanges; this indicates a software fault"); $in_config_change_txn = 0; } if( RT->InstallMode ) { return; } # RT can't load the config in the DB if the DB is not there! my $needs_update = RT->System->ConfigCacheNeedsUpdate; if ($needs_update > $database_config_cache_time) { $self->LoadConfigFromDatabase(); $database_config_cache_time = $needs_update; } } sub LoadConfigFromDatabase { my $self = shift; $database_config_cache_time = time; my $settings = RT::Configurations->new(RT->SystemUser); $settings->LimitToEnabled; my %seen; while (my $setting = $settings->Next) { my $name = $setting->Name; my ($value, $error) = $setting->DecodedContent; next if $error; if (!exists $original_setting_from_files{$name}) { $original_setting_from_files{$name} = [ scalar($self->Get($name)), Clone::clone(scalar($self->Meta($name))), ]; } $seen{$name}++; # are we inadvertantly overriding RT_SiteConfig.pm? my $meta = $META{$name}; if ($meta->{'Source'}) { my %source = %{ $meta->{'Source'} }; if ($source{'SiteConfig'} && $source{'File'} ne 'database') { warn("Change of config option '$name' at $source{File} line $source{Line} has been overridden by the config setting from the database. Please remove it from $source{File} or from the database to avoid confusion."); } } my $type = $meta->{Type} || 'SCALAR'; my $val = $type eq 'ARRAY' ? $value : $type eq 'HASH' ? [ %$value ] : [ $value ]; # hashes combine, but by default previous config settings shadow # later changes, here we want database configs to shadow file ones. if ($type eq 'HASH') { $val = [ $self->Get($name), @$val ]; $self->Set($name, ()); } $self->SetFromConfig( Option => \$name, Value => $val, Package => 'N/A', File => 'database', Line => 'N/A', Database => 1, SiteConfig => 1, ); } # anything that wasn't loaded from the database but has been set in # %original_setting_from_files must have been disabled from the database, # so we want to restore the original setting for my $name (keys %original_setting_from_files) { next if $seen{$name}; my ($value, $meta) = @{ $original_setting_from_files{$name} }; my $type = $meta->{Type} || 'SCALAR'; if ($type eq 'ARRAY') { $self->Set($name, @$value); } elsif ($type eq 'HASH') { $self->Set($name, %$value); } else { $self->Set($name, $value); } %{ $META{$name} } = %$meta; } } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Test/�������������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 015013� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/DependencyWalker.pm�����������������������������������������������������������������000644 �000765 �000024 �00000022724 14005011336 017665� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::DependencyWalker; use strict; use warnings; use RT::DependencyWalker::FindDependencies; use Carp; sub new { my $class = shift; my $self = bless {}, $class; $self->Init(@_); return $self; } sub Init { my $self = shift; my %args = ( First => "top", GC => 0, Page => 100, Progress => undef, MessageHandler => \&Carp::carp, @_ ); $self->{first} = $args{First}; $self->{GC} = $args{GC}; $self->{Page} = $args{Page}; $self->{progress} = $args{Progress}; $self->{msg} = $args{MessageHandler}, $self->{stack} = []; } sub PushObj { my $self = shift; push @{$self->{stack}}, { object => $_ } for @_; } sub Walk { my $self = shift; $self->PushObj( @_ ); # Ensure that RT::Ticket's ->Load doesn't follow a merged ticket to # the ticket it was merged into. no warnings 'redefine'; local *RT::Ticket::Load = sub { my $self = shift; my $id = shift; $self->LoadById( $id ); return $self->Id; }; # When we walk ticket links, find deleted tickets as well local *RT::Links::IsValidLink = sub { my $self = shift; my $link = shift; return unless $link && ref $link && $link->Target && $link->Base; return 1; }; $self->{visited} = {}; $self->{seen} = {}; $self->{gc_count} = 0; my $stack = $self->{stack}; while (@{$stack}) { my %frame = %{ shift @{$stack} }; $self->{top} = []; $self->{replace} = []; $self->{bottom} = []; my $ref = $frame{object}; if ($ref->isa("RT::Record")) { $self->Process(%frame); } else { unless ($ref->{unrolled}) { $ref->FindAllRows; $ref->RowsPerPage( $self->{Page} ); $ref->FirstPage; $ref->{unrolled}++; } my $last; while (my $obj = $ref->DBIx::SearchBuilder::Next) { $last = $obj->Id; $self->Process(%frame, object => $obj ); } if (defined $last) { $self->NextPage($ref => $last); push @{$self->{replace}}, \%frame; } } unshift @{$stack}, @{$self->{replace}}; unshift @{$stack}, @{$self->{top}}; push @{$stack}, @{$self->{bottom}}; if ($self->{GC} > 0 and $self->{gc_count} > $self->{GC}) { $self->{gc_count} = 0; require Time::HiRes; my $start_time = Time::HiRes::time(); $self->{msg}->("Starting GC pass..."); my $start_size = @{$self->{stack}}; @{ $self->{stack} } = grep { $_->{object}->isa("RT::Record") ? not exists $self->{visited}{$_->{uid} ||= $_->{object}->UID} : ( $_->{has_results} ||= do { $_->{object}->FindAllRows; $_->{object}->RowsPerPage(1); $_->{object}->Count; } ) } @{ $self->{stack} }; my $end_time = Time::HiRes::time(); my $end_size = @{$self->{stack}}; my $size = $start_size - $end_size; my $time = $end_time - $start_time; $self->{msg}->( sprintf( "GC -- %d removed, %.2f seconds, %d/s", $size, $time, int($size/$time) ) ); } } $self->{progress}->(undef, 'force') if $self->{progress}; } sub NextPage { my $self = shift; my $collection = shift; $collection->NextPage; } sub Process { my $self = shift; my %args = ( object => undef, direction => undef, from => undef, @_ ); my $obj = $args{object}; return if $obj->isa("RT::System"); my $uid = $obj->UID; unless ($uid) { warn "$args{direction} from $args{from} to $obj is an invalid reference"; return; } $self->{progress}->($obj) if $self->{progress}; if (exists $self->{visited}{$uid}) { # Already visited -- no-op $self->Again(%args); } elsif (exists $obj->{satisfied}) { # All dependencies visited -- time to visit $self->Visit(%args); $self->{visited}{$uid}++; } elsif (exists $self->{seen}{$uid}) { # All of the dependencies are on the stack already. We may not # have gotten to them, but we will eventually. This _may_ be a # cycle, but true cycle detection is too memory-intensive, as it # requires keeping track of the history of how each dep got # added to the stack, all of the way back. $self->ForcedVisit(%args); $self->{visited}{$uid}++; } else { # Nothing known about this previously; add its deps to the # stack, then objects it refers to. return if defined $args{from} and not $self->Observe(%args); my $deps = RT::DependencyWalker::FindDependencies->new; $obj->FindDependencies($self, $deps); # Shove it back for later push @{$self->{replace}}, \%args; if ($self->{first} eq "top") { # Top-first; that is, visit things we point to first, # then deal with us, then deal with things that point to # us. For serialization. $self->PrependDeps( out => $deps, $uid ); $self->AppendDeps( in => $deps, $uid ); } else { # Bottom-first; that is, deal with things that point to # us first, then deal with us, then deal with things we # point to. For removal. $self->PrependDeps( in => $deps, $uid ); $self->AppendDeps( out => $deps, $uid ); } $obj->{satisfied}++; $self->{seen}{$uid}++; $self->{gc_count}++ if $self->{GC} > 0; } } sub Observe { 1 } sub Again {} sub Visit {} sub ForcedVisit { my $self = shift; $self->Visit( @_ ); } sub AppendDeps { my $self = shift; my ($dir, $deps, $from) = @_; for my $obj (@{$deps->{$dir}}) { if (not defined $obj) { warn "$dir from $from contained an invalid reference"; next; } elsif ($obj->isa("RT::Record")) { warn "$dir from $from to $obj is an invalid reference" unless $obj->UID; next if $self->{GC} < 0 and exists $self->{seen}{$obj->UID}; } else { $obj->FindAllRows; if ($self->{GC} < 0) { $obj->RowsPerPage(1); next unless $obj->Count; } } push @{$self->{bottom}}, { object => $obj, direction => $dir, from => $from, }; } } sub PrependDeps { my $self = shift; my ($dir, $deps, $from) = @_; for my $obj (@{$deps->{$dir}}) { if (not defined $obj) { warn "$dir from $from contained an invalid reference"; next; } elsif ($obj->isa("RT::Record")) { warn "$dir from $from to $obj is an invalid reference" unless $obj->UID; next if $self->{GC} < 0 and exists $self->{visited}{$obj->UID}; } else { $obj->FindAllRows; if ($self->{GC} < 0) { $obj->RowsPerPage(1); next unless $obj->Count; } } unshift @{$self->{top}}, { object => $obj, direction => $dir, from => $from, }; } } 1; ��������������������������������������������rt-5.0.1/lib/RT/Configurations.pm�������������������������������������������������������������������000644 �000765 �000024 �00000004734 14005011336 017434� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Configurations; use base 'RT::SearchBuilder'; =head1 NAME RT::Configurations - a collection of L<RT::Configurations> objects =cut sub NewItem { my $self = shift; return RT::Configuration->new( $self->CurrentUser ); } =head2 _Init Sets default ordering by id ascending. =cut sub _Init { my $self = shift; $self->{'with_disabled_column'} = 1; $self->OrderBy( FIELD => 'id', ORDER => 'ASC' ); return $self->SUPER::_Init( @_ ); } sub Table { "Configurations" } 1; ������������������������������������rt-5.0.1/lib/RT/Queues.pm���������������������������������������������������������������������������000644 �000765 �000024 �00000006335 14005011336 015710� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Queues - a collection of RT::Queue objects =head1 SYNOPSIS use RT::Queues; =head1 DESCRIPTION =head1 METHODS =cut package RT::Queues; use strict; use warnings; use base 'RT::SearchBuilder'; use RT::Queue; sub Table { 'Queues'} # {{{ sub _Init sub _Init { my $self = shift; $self->{'with_disabled_column'} = 1; # By default, order by SortOrder, then Name $self->OrderByCols( { ALIAS => 'main', FIELD => 'SortOrder', ORDER => 'ASC', }, { ALIAS => 'main', FIELD => 'Name', ORDER => 'ASC', } ); return ($self->SUPER::_Init(@_)); } sub Limit { my $self = shift; my %args = ( ENTRYAGGREGATOR => 'AND', @_); $self->SUPER::Limit(%args); } =head2 AddRecord Adds a record object to this collection if this user can see. This is used for filtering objects for both Next and ItemsArrayRef. =cut sub AddRecord { my $self = shift; my $Queue = shift; return unless $Queue->CurrentUserHasRight('SeeQueue'); push @{$self->{'items'}}, $Queue; $self->{'rows'}++; } # no need to order here, it's already ordered in _Init sub ItemsOrderBy { my $self = shift; my $items = shift; return $items; } RT::Base->_ImportOverlays(); 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Attribute.pm������������������������������������������������������������������������000644 �000765 �000024 �00000065363 14005011336 016412� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Attribute; use strict; use warnings; use base 'RT::Record'; sub Table {'Attributes'} use Storable qw/nfreeze thaw/; use MIME::Base64; =head1 NAME RT::Attribute_Overlay =head1 Content =cut # the acl map is a map of "name of attribute" and "what right the user must have on the associated object to see/edit it our $ACL_MAP = { SavedSearch => { create => 'EditSavedSearches', update => 'EditSavedSearches', delete => 'EditSavedSearches', display => 'ShowSavedSearches' }, }; # There are a number of attributes that users should be able to modify for themselves, such as saved searches # we could do this with a different set of "update" rights, but that gets very hacky very fast. this is even faster and even # hackier. we're hardcoding that a different set of rights are needed for attributes on oneself our $PERSONAL_ACL_MAP = { SavedSearch => { create => 'ModifySelf', update => 'ModifySelf', delete => 'ModifySelf', display => 'allow' }, }; =head2 LookupObjectRight { ObjectType => undef, ObjectId => undef, Name => undef, Right => { create, update, delete, display } } Returns the right that the user needs to have on this attribute's object to perform the related attribute operation. Returns "allow" if the right is otherwise unspecified. =cut sub LookupObjectRight { my $self = shift; my %args = ( ObjectType => undef, ObjectId => undef, Right => undef, Name => undef, @_); # if it's an attribute on oneself, check the personal acl map if (($args{'ObjectType'} eq 'RT::User') && ($args{'ObjectId'} eq $self->CurrentUser->Id)) { return('allow') unless ($PERSONAL_ACL_MAP->{$args{'Name'}}); return('allow') unless ($PERSONAL_ACL_MAP->{$args{'Name'}}->{$args{'Right'}}); return($PERSONAL_ACL_MAP->{$args{'Name'}}->{$args{'Right'}}); } # otherwise check the main ACL map else { return('allow') unless ($ACL_MAP->{$args{'Name'}}); return('allow') unless ($ACL_MAP->{$args{'Name'}}->{$args{'Right'}}); return($ACL_MAP->{$args{'Name'}}->{$args{'Right'}}); } } =head2 Create PARAMHASH Create takes a hash of values and creates a row in the database: varchar(200) 'Name'. varchar(255) 'Content'. varchar(16) 'ContentType', varchar(64) 'ObjectType'. int(11) 'ObjectId'. You may pass a C<Object> instead of C<ObjectType> and C<ObjectId>. =cut sub Create { my $self = shift; my %args = ( Name => '', Description => '', Content => '', ContentType => '', Object => undef, @_); if ($args{Object} and UNIVERSAL::can($args{Object}, 'Id')) { $args{ObjectType} = $args{Object}->isa("RT::CurrentUser") ? "RT::User" : ref($args{Object}); $args{ObjectId} = $args{Object}->Id; } else { return(0, $self->loc("Required parameter '[_1]' not specified", 'Object')); } # object_right is the right that the user has to have on the object for them to have $right on this attribute my $object_right = $self->LookupObjectRight( Right => 'create', ObjectId => $args{'ObjectId'}, ObjectType => $args{'ObjectType'}, Name => $args{'Name'} ); if ($object_right eq 'deny') { return (0, $self->loc('Permission Denied')); } elsif ($object_right eq 'allow') { # do nothing, we're ok } elsif (!$self->CurrentUser->HasRight( Object => $args{Object}, Right => $object_right)) { return (0, $self->loc('Permission Denied')); } if (ref ($args{'Content'}) ) { eval {$args{'Content'} = $self->_SerializeContent($args{'Content'}); }; if ($@) { return(0, $@); } $args{'ContentType'} = 'storable'; } $self->SUPER::Create( Name => $args{'Name'}, Content => $args{'Content'}, ContentType => $args{'ContentType'}, Description => $args{'Description'}, ObjectType => $args{'ObjectType'}, ObjectId => $args{'ObjectId'}, ); } =head2 LoadByNameAndObject (Object => OBJECT, Name => NAME) Loads the Attribute named NAME for Object OBJECT. =cut sub LoadByNameAndObject { my $self = shift; my %args = ( Object => undef, Name => undef, @_, ); return ( $self->LoadByCols( Name => $args{'Name'}, ObjectType => ref($args{'Object'}), ObjectId => $args{'Object'}->Id, ) ); } =head2 _DeserializeContent DeserializeContent returns this Attribute's "Content" as a hashref. =cut sub _DeserializeContent { my $self = shift; my $content = shift; my $hashref; eval {$hashref = thaw(decode_base64($content))} ; if ($@) { $RT::Logger->error("Deserialization of attribute ".$self->Id. " failed"); } return($hashref); } =head2 Content Returns this attribute's content. If it's a scalar, returns a scalar If it's data structure returns a ref to that data structure. =cut sub Content { my $self = shift; # Here we call _Value to get the ACL check. my $content = $self->_Value('Content'); if ( ($self->__Value('ContentType') || '') eq 'storable') { eval {$content = $self->_DeserializeContent($content); }; if ($@) { $RT::Logger->error("Deserialization of content for attribute ".$self->Id. " failed. Attribute was: ".$content); } } return($content); } sub _SerializeContent { my $self = shift; my $content = shift; return( encode_base64(nfreeze($content))); } sub SetContent { my $self = shift; my $content = shift; # Call __Value to avoid ACL check. if ( ($self->__Value('ContentType')||'') eq 'storable' ) { # We eval the serialization because it will lose on a coderef. $content = eval { $self->_SerializeContent($content) }; if ($@) { $RT::Logger->error("Content couldn't be frozen: $@"); return(0, "Content couldn't be frozen"); } } my ($ok, $msg) = $self->_Set( Field => 'Content', Value => $content ); return ($ok, $self->loc("Attribute updated")) if $ok; return ($ok, $msg); } =head2 SubValue KEY Returns the subvalue for $key. =cut sub SubValue { my $self = shift; my $key = shift; my $values = $self->Content(); return undef unless ref($values); return($values->{$key}); } =head2 DeleteSubValue NAME Deletes the subvalue with the key NAME =cut sub DeleteSubValue { my $self = shift; my $key = shift; my $values = $self->Content(); delete $values->{$key}; $self->SetContent($values); } =head2 DeleteAllSubValues Deletes all subvalues for this attribute =cut sub DeleteAllSubValues { my $self = shift; $self->SetContent({}); } =head2 SetSubValues { } Takes a hash of keys and values and stores them in the content of this attribute. Each key B<replaces> the existing key with the same name Returns a tuple of (status, message) =cut sub SetSubValues { my $self = shift; my %args = (@_); my $values = ($self->Content() || {} ); foreach my $key (keys %args) { $values->{$key} = $args{$key}; } $self->SetContent($values); } sub Object { my $self = shift; my $object_type = $self->__Value('ObjectType'); my $object; eval { $object = $object_type->new($self->CurrentUser) }; unless(UNIVERSAL::isa($object, $object_type)) { $RT::Logger->error("Attribute ".$self->Id." has a bogus object type - $object_type (".$@.")"); return(undef); } $object->Load($self->__Value('ObjectId')); return($object); } sub Delete { my $self = shift; unless ($self->CurrentUserHasRight('delete')) { return (0,$self->loc('Permission Denied')); } return($self->SUPER::Delete(@_)); } sub _Value { my $self = shift; unless ($self->CurrentUserHasRight('display')) { return (0,$self->loc('Permission Denied')); } return($self->SUPER::_Value(@_)); } sub _Set { my $self = shift; unless ($self->CurrentUserHasRight('update')) { return (0,$self->loc('Permission Denied')); } return($self->SUPER::_Set(@_)); } =head2 CurrentUserHasRight One of "display" "update" "delete" or "create" and returns 1 if the user has that right for attributes of this name for this object.Returns undef otherwise. =cut sub CurrentUserHasRight { my $self = shift; my $right = shift; # object_right is the right that the user has to have on the object for them to have $right on this attribute my $object_right = $self->LookupObjectRight( Right => $right, ObjectId => $self->__Value('ObjectId'), ObjectType => $self->__Value('ObjectType'), Name => $self->__Value('Name') ); return (1) if ($object_right eq 'allow'); return (0) if ($object_right eq 'deny'); return(1) if ($self->CurrentUser->HasRight( Object => $self->Object, Right => $object_right)); return(0); } =head1 TODO We should be deserializing the content on load and then never again, rather than at every access =cut =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 Name Returns the current value of Name. (In the database, Name is stored as varchar(255).) =head2 SetName VALUE Set Name to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Name will be stored as a varchar(255).) =cut =head2 Description Returns the current value of Description. (In the database, Description is stored as varchar(255).) =head2 SetDescription VALUE Set Description to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Description will be stored as a varchar(255).) =cut =head2 Content Returns the current value of Content. (In the database, Content is stored as blob.) =head2 SetContent VALUE Set Content to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Content will be stored as a blob.) =cut =head2 ContentType Returns the current value of ContentType. (In the database, ContentType is stored as varchar(16).) =head2 SetContentType VALUE Set ContentType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ContentType will be stored as a varchar(16).) =cut =head2 ObjectType Returns the current value of ObjectType. (In the database, ObjectType is stored as varchar(64).) =head2 SetObjectType VALUE Set ObjectType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectType will be stored as a varchar(64).) =cut =head2 ObjectId Returns the current value of ObjectId. (In the database, ObjectId is stored as int(11).) =head2 SetObjectId VALUE Set ObjectId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectId will be stored as a int(11).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Name => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, Description => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, Content => {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'blob', default => ''}, ContentType => {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, ObjectType => {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''}, ObjectId => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); $deps->Add( out => $self->Object ); # dashboards in menu attribute has dependencies on each of its dashboards if ($self->Name eq RT::User::_PrefName("DashboardsInMenu")) { my $content = $self->Content; for my $pane (values %{ $content || {} }) { for my $dash_id (@$pane) { my $attr = RT::Attribute->new($self->CurrentUser); $attr->LoadById($dash_id); $deps->Add( out => $attr ); } } } # homepage settings attribute has dependencies on each of the searches in it elsif ($self->Name eq RT::User::_PrefName("HomepageSettings")) { my $content = $self->Content; for my $pane (values %{ $content || {} }) { for my $component (@$pane) { # this hairy code mirrors what's in the saved search loader # in /Elements/ShowSearch if ($component->{type} eq 'saved') { if ($component->{name} =~ /^(.*?)-(\d+)-SavedSearch-(\d+)$/) { my $attr = RT::Attribute->new($self->CurrentUser); $attr->LoadById($3); $deps->Add( out => $attr ); } } elsif ($component->{type} eq 'system') { my ($search) = RT::System->new($self->CurrentUser)->Attributes->Named( 'Search - ' . $component->{name} ); unless ( $search && $search->Id ) { my (@custom_searches) = RT::System->new($self->CurrentUser)->Attributes->Named('SavedSearch'); foreach my $custom (@custom_searches) { if ($custom->Description eq $component->{name}) { $search = $custom; last } } } $deps->Add( out => $search ) if $search; } } } } # dashboards have dependencies on all the searches and dashboards they use elsif ($self->Name eq 'Dashboard' || $self->Name eq 'SelfServiceDashboard') { my $content = $self->Content; for my $pane (values %{ $content->{Panes} || {} }) { for my $component (@$pane) { if ($component->{portlet_type} eq 'search' || $component->{portlet_type} eq 'dashboard') { my $attr = RT::Attribute->new($self->CurrentUser); $attr->LoadById($component->{id}); $deps->Add( out => $attr ); } } } } # each subscription depends on its dashboard elsif ($self->Name eq 'Subscription') { my $content = $self->Content; my $attr = RT::Attribute->new($self->CurrentUser); $attr->LoadById($content->{DashboardId}); $deps->Add( out => $attr ); } } sub PreInflate { my $class = shift; my ($importer, $uid, $data) = @_; if ($data->{Object} and ref $data->{Object}) { my $on_uid = ${ $data->{Object} }; # skip attributes of objects we're not inflating # exception: we don't inflate RT->System, but we want RT->System's searches unless ($on_uid eq RT->System->UID && $data->{Name} =~ /Search/) { return if $importer->ShouldSkipTransaction($on_uid); } } return $class->SUPER::PreInflate( $importer, $uid, $data ); } # this method will be called repeatedly to fix up this attribute's contents # (a list of searches, dashboards) during the import process, as the # ordinary dependency resolution system can't quite handle the subtlety # involved (e.g. a user simply declares out-dependencies on all of her # attributes, but those attributes (e.g. dashboards, saved searches, # dashboards in menu preferences) have dependencies amongst themselves). # if this attribute (e.g. a user's dashboard) fails to load an attribute # (e.g. a user's saved search) then it postpones and repeats the postinflate # process again when that user's saved search has been imported # this method updates Content each time through, each time getting closer and # closer to the fully inflated attribute sub PostInflateFixup { my $self = shift; my $importer = shift; my $spec = shift; # decode UIDs to be raw dashboard IDs if ($self->Name eq RT::User::_PrefName("DashboardsInMenu")) { my $content = $self->Content; for my $pane (values %{ $content || {} }) { for (@$pane) { if (ref($_) eq 'SCALAR') { my $attr = $importer->LookupObj($$_); if ($attr) { $_ = $attr->Id; } else { $importer->Postpone( for => $$_, uid => $spec->{uid}, method => 'PostInflateFixup', ); } } } } $self->SetContent($content); } # decode UIDs to be saved searches elsif ($self->Name eq RT::User::_PrefName("HomepageSettings")) { my $content = $self->Content; for my $pane (values %{ $content || {} }) { for (@$pane) { if (ref($_->{uid}) eq 'SCALAR') { my $uid = $_->{uid}; my $attr = $importer->LookupObj($$uid); if ($attr) { if ($_->{type} eq 'saved') { $_->{name} = join '-', $attr->ObjectType, $attr->ObjectId, 'SavedSearch', $attr->id; } # if type is system, name doesn't need to change # if type is anything else, pass it through as is delete $_->{uid}; } else { $importer->Postpone( for => $$uid, uid => $spec->{uid}, method => 'PostInflateFixup', ); } } } } $self->SetContent($content); } elsif ($self->Name eq 'Dashboard') { my $content = $self->Content; for my $pane (values %{ $content->{Panes} || {} }) { for (@$pane) { if (ref($_->{uid}) eq 'SCALAR') { my $uid = $_->{uid}; my $attr = $importer->LookupObj($$uid); if ($attr) { # update with the new id numbers assigned to us $_->{id} = $attr->Id; $_->{privacy} = join '-', $attr->ObjectType, $attr->ObjectId; delete $_->{uid}; } else { $importer->Postpone( for => $$uid, uid => $spec->{uid}, method => 'PostInflateFixup', ); } } } } $self->SetContent($content); } elsif ($self->Name eq 'Subscription') { my $content = $self->Content; if (ref($content->{DashboardId}) eq 'SCALAR') { my $attr = $importer->LookupObj(${ $content->{DashboardId} }); if ($attr) { $content->{DashboardId} = $attr->Id; } else { $importer->Postpone( for => ${ $content->{DashboardId} }, uid => $spec->{uid}, method => 'PostInflateFixup', ); } } $self->SetContent($content); } } sub PostInflate { my $self = shift; my ($importer, $uid) = @_; $self->SUPER::PostInflate( $importer, $uid ); # this method is separate because it needs to be callable multple times, # and we can't guarantee that SUPER::PostInflate can deal with that $self->PostInflateFixup($importer, { uid => $uid }); } sub Serialize { my $self = shift; my %args = (@_); my %store = $self->SUPER::Serialize(@_); # encode raw dashboard IDs to be UIDs if ($store{Name} eq RT::User::_PrefName("DashboardsInMenu")) { my $content = $self->_DeserializeContent($store{Content}); for my $pane (values %{ $content || {} }) { for (@$pane) { my $attr = RT::Attribute->new($self->CurrentUser); $attr->LoadById($_); $_ = \($attr->UID); } } $store{Content} = $self->_SerializeContent($content); } # encode saved searches to be UIDs elsif ($store{Name} eq RT::User::_PrefName("HomepageSettings")) { my $content = $self->_DeserializeContent($store{Content}); for my $pane (values %{ $content || {} }) { for (@$pane) { # this hairy code mirrors what's in the saved search loader # in /Elements/ShowSearch if ($_->{type} eq 'saved') { if ($_->{name} =~ /^(.*?)-(\d+)-SavedSearch-(\d+)$/) { my $attr = RT::Attribute->new($self->CurrentUser); $attr->LoadById($3); $_->{uid} = \($attr->UID); } # if we can't parse the name, just pass it through } elsif ($_->{type} eq 'system') { my ($search) = RT::System->new($self->CurrentUser)->Attributes->Named( 'Search - ' . $_->{name} ); unless ( $search && $search->Id ) { my (@custom_searches) = RT::System->new($self->CurrentUser)->Attributes->Named('SavedSearch'); foreach my $custom (@custom_searches) { if ($custom->Description eq $_->{name}) { $search = $custom; last } } } # if we can't load the search, just pass it through if ($search) { $_->{uid} = \($search->UID); } } # pass through everything else (e.g. component) } } $store{Content} = $self->_SerializeContent($content); } # encode saved searches and dashboards to be UIDs elsif ($store{Name} eq 'Dashboard') { my $content = $self->_DeserializeContent($store{Content}) || {}; for my $pane (values %{ $content->{Panes} || {} }) { for (@$pane) { if ($_->{portlet_type} eq 'search' || $_->{portlet_type} eq 'dashboard') { my $attr = RT::Attribute->new($self->CurrentUser); $attr->LoadById($_->{id}); $_->{uid} = \($attr->UID); } # pass through everything else (e.g. component) } } $store{Content} = $self->_SerializeContent($content); } # encode subscriptions to have dashboard UID elsif ($store{Name} eq 'Subscription') { my $content = $self->_DeserializeContent($store{Content}); my $attr = RT::Attribute->new($self->CurrentUser); $attr->LoadById($content->{DashboardId}); $content->{DashboardId} = \($attr->UID); $store{Content} = $self->_SerializeContent($content); } return %store; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Scrips.pm���������������������������������������������������������������������������000644 �000765 �000024 �00000033006 14005011336 015677� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Scrips - a collection of RT Scrip objects =head1 SYNOPSIS use RT::Scrips; =head1 DESCRIPTION =head1 METHODS =cut package RT::Scrips; use strict; use warnings; use base 'RT::SearchBuilder'; use RT::Scrip; use RT::ObjectScrips; sub Table { 'Scrips'} sub _Init { my $self = shift; $self->{'with_disabled_column'} = 1; return ( $self->SUPER::_Init(@_) ); } =head2 LimitToQueue Takes a queue id (numerical) as its only argument. Makes sure that Scopes it pulls out apply to this queue (or another that you've selected with another call to this method =cut sub LimitToQueue { my $self = shift; my $queue = shift; return unless defined $queue; my $alias = RT::ObjectScrips->new( $self->CurrentUser ) ->JoinTargetToThis( $self ); $self->Limit( ALIAS => $alias, FIELD => 'ObjectId', VALUE => int $queue, ); } =head2 LimitToGlobal Makes sure that Scopes it pulls out apply to all queues (or another that you've selected with another call to this method or LimitToQueue =cut sub LimitToGlobal { my $self = shift; return $self->LimitToQueue(0); } sub LimitToAdded { my $self = shift; return RT::ObjectScrips->new( $self->CurrentUser ) ->LimitTargetToAdded( $self => @_ ); } sub LimitToNotAdded { my $self = shift; return RT::ObjectScrips->new( $self->CurrentUser ) ->LimitTargetToNotAdded( $self => @_ ); } sub LimitByStage { my $self = shift; my %args = @_%2? (Stage => @_) : @_; return unless defined $args{'Stage'}; my $alias = RT::ObjectScrips->new( $self->CurrentUser ) ->JoinTargetToThis( $self, %args ); $self->Limit( ALIAS => $alias, FIELD => 'Stage', VALUE => $args{'Stage'}, ); } =head2 LimitByTemplate Takes a L<RT::Template> object and limits scrips to those that use the template. =cut sub LimitByTemplate { my $self = shift; my $template = shift; $self->Limit( FIELD => 'Template', VALUE => $template->Name ); if ( $template->Queue ) { # if template is local then we are interested in global and # queue specific scrips $self->LimitToQueue( $template->Queue ); $self->LimitToGlobal; } else { # template is global # if every queue has a custom version then there # is no scrip that uses the template { my $queues = RT::Queues->new( RT->SystemUser ); my $alias = $queues->Join( TYPE => 'LEFT', ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'Templates', FIELD2 => 'Queue', ); $queues->Limit( LEFTJOIN => $alias, ALIAS => $alias, FIELD => 'Name', VALUE => $template->Name, ); $queues->Limit( ALIAS => $alias, FIELD => 'id', OPERATOR => 'IS', VALUE => 'NULL', ); return $self->Limit( FIELD => 'id', VALUE => 0 ) unless $queues->Count; } # otherwise it's either a global scrip or application to # a queue with custom version of the template. my $os_alias = RT::ObjectScrips->new( $self->CurrentUser ) ->JoinTargetToThis( $self ); my $tmpl_alias = $self->Join( TYPE => 'LEFT', ALIAS1 => $os_alias, FIELD1 => 'ObjectId', TABLE2 => 'Templates', FIELD2 => 'Queue', ); $self->Limit( LEFTJOIN => $tmpl_alias, ALIAS => $tmpl_alias, FIELD => 'Name', VALUE => $template->Name, ); $self->Limit( LEFTJOIN => $tmpl_alias, ALIAS => $tmpl_alias, FIELD => 'Queue', OPERATOR => '!=', VALUE => 0, ); $self->_OpenParen('UsedBy'); $self->Limit( SUBCLAUSE => 'UsedBy', ALIAS => $os_alias, FIELD => 'ObjectId', VALUE => 0 ); $self->Limit( SUBCLAUSE => 'UsedBy', ALIAS => $tmpl_alias, FIELD => 'id', OPERATOR => 'IS', VALUE => 'NULL', ); $self->_CloseParen('UsedBy'); } } sub ApplySortOrder { my $self = shift; my $order = shift || 'ASC'; $self->OrderByCols( { ALIAS => RT::ObjectScrips->new( $self->CurrentUser ) ->JoinTargetToThis( $self => @_ ) , FIELD => 'SortOrder', ORDER => $order, } ); } =head2 AddRecord Overrides the collection to ensure that only scrips the user can see are returned. =cut sub AddRecord { my $self = shift; my ($record) = @_; return unless $record->CurrentUserHasRight('ShowScrips'); return $self->SUPER::AddRecord( $record ); } =head2 Apply Run through the relevant scrips. Scrips will run in order based on description. (Most common use case is to prepend a number to the description, forcing the scrips to run in ascending alphanumerical order.) =cut sub Apply { my $self = shift; my %args = ( TicketObj => undef, Ticket => undef, Transaction => undef, TransactionObj => undef, Stage => undef, Type => undef, @_ ); $self->Prepare(%args); $self->Commit(); } =head2 Commit Commit all of this object's prepared scrips =cut sub Commit { my $self = shift; foreach my $scrip (@{$self->Prepared}) { $RT::Logger->debug( "Committing scrip #". $scrip->id ." on txn #". $self->{'TransactionObj'}->id ." of ticket #". $self->{'TicketObj'}->id ); $scrip->Commit( TicketObj => $self->{'TicketObj'}, TransactionObj => $self->{'TransactionObj'} ); } } =head2 Prepare Only prepare the scrips, returning an array of the scrips we're interested in in order of preparation, not execution =cut sub Prepare { my $self = shift; my %args = ( TicketObj => undef, Ticket => undef, Transaction => undef, TransactionObj => undef, Stage => undef, Type => undef, @_ ); #We're really going to need a non-acled ticket for the scrips to work $self->_SetupSourceObjects( TicketObj => $args{'TicketObj'}, Ticket => $args{'Ticket'}, TransactionObj => $args{'TransactionObj'}, Transaction => $args{'Transaction'} ); $self->_FindScrips( Stage => $args{'Stage'}, Type => $args{'Type'} ); #Iterate through each script and check it's applicability. while ( my $scrip = $self->Next() ) { unless ( $scrip->IsApplicable( TicketObj => $self->{'TicketObj'}, TransactionObj => $self->{'TransactionObj'} ) ) { $RT::Logger->debug("Skipping Scrip #".$scrip->Id." because it isn't applicable"); next; } #If it's applicable, prepare and commit it unless ( $scrip->Prepare( TicketObj => $self->{'TicketObj'}, TransactionObj => $self->{'TransactionObj'} ) ) { $RT::Logger->debug("Skipping Scrip #".$scrip->Id." because it didn't Prepare"); next; } push @{$self->{'prepared_scrips'}}, $scrip; } return (@{$self->Prepared}); }; =head2 Prepared Returns an arrayref of the scrips this object has prepared =cut sub Prepared { my $self = shift; return ($self->{'prepared_scrips'} || []); } =head2 _SetupSourceObjects { TicketObj , Ticket, Transaction, TransactionObj } Setup a ticket and transaction for this Scrip collection to work with as it runs through the relevant scrips. (Also to figure out which scrips apply) Returns: nothing =cut sub _SetupSourceObjects { my $self = shift; my %args = ( TicketObj => undef, Ticket => undef, Transaction => undef, TransactionObj => undef, @_ ); 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 ); $self->{'TicketObj'}->Load( $args{'Ticket'} ) || $RT::Logger->err("$self couldn't load ticket $args{'Ticket'}"); } if ( ( $self->{'TransactionObj'} = $args{'TransactionObj'} ) ) { $self->{'TransactionObj'}->CurrentUser( $self->CurrentUser ); } else { $self->{'TransactionObj'} = RT::Transaction->new( $self->CurrentUser ); $self->{'TransactionObj'}->Load( $args{'Transaction'} ) || $RT::Logger->err( "$self couldn't load transaction $args{'Transaction'}"); } } =head2 _FindScrips Find only the appropriate scrips for whatever we're doing now. Order them by the SortOrder field from the ObjectScrips table. =cut sub _FindScrips { my $self = shift; my %args = ( Stage => undef, Type => undef, @_ ); $self->LimitToQueue( $self->{'TicketObj'}->QueueObj->Id ); $self->LimitToGlobal; $self->LimitByStage( $args{'Stage'} ); my $ConditionsAlias = $self->Join( ALIAS1 => 'main', FIELD1 => 'ScripCondition', TABLE2 => 'ScripConditions', FIELD2 => 'id', ); #We only want things where the scrip applies to this sort of transaction # TransactionBatch stage can define list of transaction foreach( split /\s*,\s*/, ($args{'Type'} || '') ) { $self->Limit( ALIAS => $ConditionsAlias, FIELD => 'ApplicableTransTypes', OPERATOR => 'LIKE', VALUE => $_, ENTRYAGGREGATOR => 'OR', ) } # Or where the scrip applies to any transaction $self->Limit( ALIAS => $ConditionsAlias, FIELD => 'ApplicableTransTypes', OPERATOR => 'LIKE', VALUE => "Any", ENTRYAGGREGATOR => 'OR', ); $self->ApplySortOrder; # we call Count below, but later we always do search # so just do search and get count from results $self->_DoSearch if $self->{'must_redo_search'}; $RT::Logger->debug( "Found ". $self->Count ." scrips for $args{'Stage'} stage" ." with applicable type(s) $args{'Type'}" ." for txn #".$self->{TransactionObj}->Id ." on ticket #".$self->{TicketObj}->Id ); } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Crypt/������������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 015175� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/CachedGroupMembers.pm���������������������������������������������������������������000644 �000765 �000024 �00000011053 14005011336 020131� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::CachedGroupMembers - a collection of RT::GroupMember objects =head1 SYNOPSIS use RT::CachedGroupMembers; =head1 DESCRIPTION =head1 METHODS =cut package RT::CachedGroupMembers; use strict; use warnings; use base 'RT::SearchBuilder'; use RT::CachedGroupMember; sub Table { 'CachedGroupMembers'} # {{{ LimitToUsers =head2 LimitToUsers Limits this search object to users who are members of this group This is really useful when you want to have your UI separate out groups from users for display purposes =cut sub LimitToUsers { my $self = shift; my $principals = $self->Join( ALIAS1 => 'main', FIELD1 => 'MemberId', TABLE2 => 'Principals', FIELD2 =>'id' ); $self->Limit( ALIAS => $principals, FIELD => 'PrincipalType', VALUE => 'User', ENTRYAGGREGATOR => 'OR', ); } =head2 LimitToGroups Limits this search object to Groups who are members of this group This is really useful when you want to have your UI separate out groups from users for display purposes =cut sub LimitToGroups { my $self = shift; my $principals = $self->Join( ALIAS1 => 'main', FIELD1 => 'MemberId', TABLE2 => 'Principals', FIELD2 =>'id' ); $self->Limit( ALIAS => $principals, FIELD => 'PrincipalType', VALUE => 'Group', ENTRYAGGREGATOR => 'OR', ); } =head2 LimitToMembersOfGroup PRINCIPAL_ID Takes a Principal Id as its only argument. Limits the current search principals which are _directly_ members of the group which has PRINCIPAL_ID as its principal id. =cut sub LimitToMembersOfGroup { my $self = shift; my $group = shift; return ($self->Limit( VALUE => $group, FIELD => 'GroupId', ENTRYAGGREGATOR => 'OR', )); } =head2 LimitToGroupsWithMember PRINCIPAL_ID Takes a Principal Id as its only argument. Limits the current search to groups which contain PRINCIPAL_ID as a member or submember. This function gets used by GroupMember->Create to populate subgroups =cut sub LimitToGroupsWithMember { my $self = shift; my $member = shift; return ($self->Limit( VALUE => $member || '0', FIELD => 'MemberId', ENTRYAGGREGATOR => 'OR', QUOTEVALUE => 0 )); } # }}} RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Shredder.pm�������������������������������������������������������������������������000644 �000765 �000024 �00000057170 14005011336 016204� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Shredder; use strict; use warnings; =head1 NAME RT::Shredder - Permanently wipeout data from RT =head1 SYNOPSIS =head2 CLI rt-shredder --force --plugin 'Tickets=query,Queue="General" and Status="deleted"' =head1 DESCRIPTION RT::Shredder is extension to RT which allows you to permanently wipeout data from the RT database. Shredder supports the wiping of almost all RT objects (Tickets, Transactions, Attachments, Users...). =head2 "Delete" vs "Wipeout" RT uses the term "delete" to mean "deactivate". To avoid confusion, RT::Shredder uses the term "Wipeout" to mean "permanently erase" (or what most people would think of as "delete"). =head2 Why do you want this? Normally in RT, "deleting" an item simply deactivates it and makes it invisible from view. This is done to retain full history and auditability of your tickets. For most RT users this is fine and they have no need of RT::Shredder. But in some large and heavily used RT instances the database can get clogged up with junk, particularly spam. This can slow down searches and bloat the size of the database. For these users, RT::Shredder allows them to completely clear the database of this unwanted junk. An additional use of Shredder is to obliterate sensitive information (passwords, credit card numbers, ...) which might have made their way into RT. =head2 Command line tools (CLI) L<rt-shredder> is a program which allows you to wipe objects from command line or with system tasks scheduler (cron, for example). See also 'rt-shredder --help'. =head2 Web based interface (WebUI) Shredder's WebUI integrates into RT's WebUI. You can find it in the Admin->Tools->Shredder tab. The interface is similar to the CLI and gives you the same functionality. You can find 'Shredder' link at the bottom of tickets search results, so you could wipeout tickets in the way similar to the bulk update. =head1 DATA STORAGE AND BACKUPS Shredder allows you to store data you wiped in files as scripts with SQL commands. =head3 Restoring from backup Should you wipeout something you did not intend to the objects can be restored by using the storage files. These files are a simple set of SQL commands to re-insert your objects into the RT database. 1) Locate the appropriate shredder SQL dump file. In the WebUI, when you use shredder, the path to the dump file is displayed. It also gives the option to download the dump file after each wipeout. Or it can be found in your C<$ShredderStoragePath>. 2) Load the shredder SQL dump into your RT database. The details will be different for each database and RT configuration, consult your database manual and RT config. For example, in MySQL... mysql -u your_rt_user -p your_rt_database < /path/to/rt/var/data/shredder/dump.sql That's it.i This will restore everything you'd deleted during a shredding session when the file had been created. =head1 CONFIGURATION =head2 $DependenciesLimit Shredder stops with an error if the object has more than C<$DependenciesLimit> dependencies. For example: a ticket has 1000 transactions or a transaction has 1000 attachments. This is protection from bugs in shredder from wiping out your whole database, but sometimes when you have big mail loops you may hit it. Defaults to 1000. To change this (for example, to 10000) add the following to your F<RT_SiteConfig.pm>: Set( $DependenciesLimit, 10_000 );> =head2 $ShredderStoragePath Directory containing Shredder backup dumps; defaults to F</opt/rt5/var/data/RT-Shredder> (assuming an /opt/rt5 installation). To change this (for example, to /some/backup/path) add the following to your F<RT_SiteConfig.pm>: Set( $ShredderStoragePath, "/some/backup/path" );> Be sure to specify an absolute path. =head1 Database Indexes We have found that the following indexes significantly speed up shredding on most databases. CREATE INDEX SHREDDER_CGM1 ON CachedGroupMembers(MemberId, GroupId, Disabled); CREATE INDEX SHREDDER_CGM2 ON CachedGroupMembers(ImmediateParentId,MemberId); CREATE INDEX SHREDDER_CGM3 on CachedGroupMembers (Via, Id); CREATE UNIQUE INDEX SHREDDER_GM1 ON GroupMembers(MemberId, GroupId); CREATE INDEX SHREDDER_TXN1 ON Transactions(ReferenceType, OldReference); CREATE INDEX SHREDDER_TXN2 ON Transactions(ReferenceType, NewReference); CREATE INDEX SHREDDER_TXN3 ON Transactions(Type, OldValue); CREATE INDEX SHREDDER_TXN4 ON Transactions(Type, NewValue); CREATE INDEX SHREDDER_ATTACHMENTS1 ON Attachments(Creator); =head1 INFORMATION FOR DEVELOPERS =head2 General API L<RT::Shredder> is an extension to RT which adds shredder methods to RT objects and classes. The API is not well documented yet, but you can find usage examples in L<rt-shredder> and the F<lib/t/regression/shredder/*.t> test files. However, here is a small example that do the same action as in CLI example from L</SYNOPSIS>: use RT::Shredder; RT::Shredder::Init( force => 1 ); my $deleted = RT::Tickets->new( RT->SystemUser ); $deleted->{'allow_deleted_search'} = 1; $deleted->LimitQueue( VALUE => 'general' ); $deleted->LimitStatus( VALUE => 'deleted' ); while( my $t = $deleted->Next ) { $t->Wipeout; } =head2 RT::Shredder class' API L<RT::Shredder> implements interfaces to objects cache, actions on the objects in the cache and backups storage. =cut use File::Spec (); BEGIN { # I can't use 'use lib' here since it breakes tests # because test suite uses old RT::Shredder setup from # RT lib path ### after: push @INC, qw(@RT_LIB_PATH@); use RT::Shredder::Constants; use RT::Shredder::Exceptions; } our @SUPPORTED_OBJECTS = qw( ACE Attachment CachedGroupMember CustomField CustomFieldValue GroupMember Group Link Principal Queue Scrip ScripAction ScripCondition Template ObjectCustomFieldValue Ticket Transaction User ); =head3 GENERIC =head4 Init RT::Shredder::Init( %default_options ); C<RT::Shredder::Init()> should be called before creating an RT::Shredder object. It iniitalizes RT and loads the RT configuration. %default_options are passed to every C<<RT::Shredder->new>> call. =cut our %opt = (); sub Init { %opt = @_; RT::LoadConfig(); RT::Init(); return; } =head4 new my $shredder = RT::Shredder->new(%options); Construct a new RT::Shredder object. There currently are no %options. =cut sub new { my $proto = shift; my $self = bless( {}, ref $proto || $proto ); return $self->_Init( @_ ); } sub _Init { my $self = shift; $self->{'opt'} = { %opt, @_ }; $self->{'cache'} = {}; $self->{'resolver'} = {}; $self->{'dump_plugins'} = []; return $self; } =head4 CastObjectsToRecords( Objects => undef ) Cast objects to the C<RT::Record> objects or its ancesstors. Objects can be passed as SCALAR (format C<< <class>-<id> >>), ARRAY, C<RT::Record> ancesstors or C<RT::SearchBuilder> ancesstor. Most methods that takes C<Objects> argument use this method to cast argument value to list of records. Returns an array of records. For example: my @objs = $shredder->CastObjectsToRecords( Objects => [ # ARRAY reference 'RT::Attachment-10', # SCALAR or SCALAR reference $tickets, # RT::Tickets object (isa RT::SearchBuilder) $user, # RT::User object (isa RT::Record) ], ); =cut sub CastObjectsToRecords { my $self = shift; my %args = ( Objects => undef, @_ ); my @res; my $targets = delete $args{'Objects'}; unless( $targets ) { RT::Shredder::Exception->throw( "Undefined Objects argument" ); } if( UNIVERSAL::isa( $targets, 'RT::SearchBuilder' ) ) { #XXX: try to use ->_DoSearch + ->ItemsArrayRef in feature # like we do in Record with links, but change only when # more tests would be available while( my $tmp = $targets->Next ) { push @res, $tmp }; } elsif ( UNIVERSAL::isa( $targets, 'RT::Record' ) ) { push @res, $targets; } elsif ( UNIVERSAL::isa( $targets, 'ARRAY' ) ) { foreach( @$targets ) { push @res, $self->CastObjectsToRecords( Objects => $_ ); } } elsif ( UNIVERSAL::isa( $targets, 'SCALAR' ) || !ref $targets ) { $targets = $$targets if ref $targets; my ($class, $org, $id); if ($targets =~ /-.*-/) { ($class, $org, $id) = split /-/, $targets; RT::Shredder::Exception->throw( "Can't wipeout remote object $targets" ) unless $org eq RT->Config->Get('Organization'); } else { ($class, $id) = split /-/, $targets; } RT::Shredder::Exception->throw( "Unsupported class $class" ) unless $class =~ /^\w+(::\w+)*$/; $class = 'RT::'. $class unless $class =~ /^RTx?::/i; $class->require or die "Failed to load $class: $@"; my $obj = $class->new( RT->SystemUser ); die "Couldn't construct new '$class' object" unless $obj; $obj->Load( $id ); unless ( $obj->id ) { $RT::Logger->error( "Couldn't load '$class' object with id '$id'" ); RT::Shredder::Exception::Info->throw( 'CouldntLoadObject' ); } if ( $id =~ /^\d+$/ ) { if ( $id ne $obj->Id ) { die 'Loaded object id ' . $obj->Id . " is different from passed id $id"; } } else { if ( $obj->_Accessible( 'Name', 'read' ) && $id ne $obj->Name ) { die 'Loaded object name ' . $obj->Name . " is different from passed name $id"; } } push @res, $obj; } else { RT::Shredder::Exception->throw( "Unsupported type ". ref $targets ); } return @res; } =head3 OBJECTS CACHE =head4 PutObjects( Objects => undef ) Puts objects into cache. Returns array of the cache entries. See C<CastObjectsToRecords> method for supported types of the C<Objects> argument. =cut sub PutObjects { my $self = shift; my %args = ( Objects => undef, @_ ); my @res; for( $self->CastObjectsToRecords( Objects => delete $args{'Objects'} ) ) { push @res, $self->PutObject( %args, Object => $_ ) } return @res; } =head4 PutObject( Object => undef ) Puts record object into cache and returns its cache entry. B<NOTE> that this method support B<only C<RT::Record> object or its ancesstor objects>, if you want put mutliple objects or objects represented by different classes then use C<PutObjects> method instead. =cut sub PutObject { my $self = shift; my %args = ( Object => undef, @_ ); my $obj = $args{'Object'}; unless( UNIVERSAL::isa( $obj, 'RT::Record' ) ) { RT::Shredder::Exception->throw( "Unsupported type '". (ref $obj || $obj || '(undef)')."'" ); } my $str = $obj->UID; return ($self->{'cache'}->{ $str } ||= { State => RT::Shredder::Constants::ON_STACK, Object => $obj } ); } =head4 GetObject, GetState, GetRecord( String => ''| Object => '' ) Returns record object from cache, cache entry state or cache entry accordingly. All three methods takes C<String> (format C<< <class>-<id> >>) or C<Object> argument. C<String> argument has more priority than C<Object> so if it's not empty then methods leave C<Object> argument unchecked. You can read about possible states and their meanings in L<RT::Shredder::Constants> docs. =cut sub _ParseRefStrArgs { my $self = shift; my %args = ( String => '', Object => undef, @_ ); if( $args{'String'} && $args{'Object'} ) { require Carp; Carp::croak( "both String and Object args passed" ); } return $args{'String'} if $args{'String'}; return $args{'Object'}->UID if UNIVERSAL::can($args{'Object'}, 'UID' ); return ''; } sub GetObject { return (shift)->GetRecord( @_ )->{'Object'} } sub GetState { return (shift)->GetRecord( @_ )->{'State'} } sub GetRecord { my $self = shift; my $str = $self->_ParseRefStrArgs( @_ ); return $self->{'cache'}->{ $str }; } =head3 Dependencies resolvers =head4 PutResolver, GetResolvers and ApplyResolvers TODO: These methods have no documentation. =cut sub PutResolver { my $self = shift; my %args = ( BaseClass => '', TargetClass => '', Code => undef, @_, ); unless( UNIVERSAL::isa( $args{'Code'} => 'CODE' ) ) { die "Resolver '$args{Code}' is not code reference"; } my $resolvers = ( ( $self->{'resolver'}->{ $args{'BaseClass'} } ||= {} )->{ $args{'TargetClass'} || '' } ||= [] ); unshift @$resolvers, $args{'Code'}; return; } sub GetResolvers { my $self = shift; my %args = ( BaseClass => '', TargetClass => '', @_, ); my @res; if( $args{'TargetClass'} && exists $self->{'resolver'}->{ $args{'BaseClass'} }->{ $args{'TargetClass'} } ) { push @res, @{ $self->{'resolver'}->{ $args{'BaseClass'} }->{ $args{'TargetClass'} || '' } }; } if( exists $self->{'resolver'}->{ $args{'BaseClass'} }->{ '' } ) { push @res, @{ $self->{'resolver'}->{ $args{'BaseClass'} }->{''} }; } return @res; } sub ApplyResolvers { my $self = shift; my %args = ( Dependency => undef, @_ ); my $dep = $args{'Dependency'}; my @resolvers = $self->GetResolvers( BaseClass => $dep->BaseClass, TargetClass => $dep->TargetClass, ); unless( @resolvers ) { RT::Shredder::Exception::Info->throw( tag => 'NoResolver', error => "Couldn't find resolver for dependency '". $dep->AsString ."'", ); } $_->( Shredder => $self, BaseObject => $dep->BaseObject, TargetObject => $dep->TargetObject, ) foreach @resolvers; return; } sub WipeoutAll { my $self = $_[0]; foreach my $cache_val ( values %{ $self->{'cache'} } ) { next if $cache_val->{'State'} & (RT::Shredder::Constants::WIPED | RT::Shredder::Constants::IN_WIPING); $self->Wipeout( Object => $cache_val->{'Object'} ); } return; } sub Wipeout { my $self = shift; my $mark; eval { die "Couldn't begin transaction" unless $RT::Handle->BeginTransaction; $mark = $self->PushDumpMark or die "Couldn't get dump mark"; $self->_Wipeout( @_ ); $self->PopDumpMark( Mark => $mark ); die "Couldn't commit transaction" unless $RT::Handle->Commit; }; if( $@ ) { my $error = $@; $RT::Handle->Rollback('force'); $self->RollbackDumpTo( Mark => $mark ) if $mark; die $error if RT::Shredder::Exception::Info->caught; die "Couldn't wipeout object: $error"; } return; } sub _Wipeout { my $self = shift; my %args = ( CacheRecord => undef, Object => undef, @_ ); my $record = $args{'CacheRecord'}; $record = $self->PutObject( Object => $args{'Object'} ) unless $record; return if $record->{'State'} & (RT::Shredder::Constants::WIPED | RT::Shredder::Constants::IN_WIPING); $record->{'State'} |= RT::Shredder::Constants::IN_WIPING; my $object = $record->{'Object'}; $self->DumpObject( Object => $object, State => 'before any action' ); unless( $object->BeforeWipeout ) { RT::Shredder::Exception->throw( "BeforeWipeout check returned error" ); } my $deps = $object->Dependencies( Shredder => $self ); $deps->List( WithFlags => RT::Shredder::Constants::DEPENDS_ON | RT::Shredder::Constants::VARIABLE, Callback => sub { $self->ApplyResolvers( Dependency => $_[0] ) }, ); $self->DumpObject( Object => $object, State => 'after resolvers' ); $deps->List( WithFlags => RT::Shredder::Constants::DEPENDS_ON, WithoutFlags => RT::Shredder::Constants::WIPE_AFTER | RT::Shredder::Constants::VARIABLE, Callback => sub { $self->_Wipeout( Object => $_[0]->TargetObject ) }, ); $self->DumpObject( Object => $object, State => 'after wiping dependencies' ); $object->__Wipeout; $record->{'State'} |= RT::Shredder::Constants::WIPED; delete $record->{'Object'}; $self->DumpObject( Object => $object, State => 'after wipeout' ); $deps->List( WithFlags => RT::Shredder::Constants::DEPENDS_ON | RT::Shredder::Constants::WIPE_AFTER, WithoutFlags => RT::Shredder::Constants::VARIABLE, Callback => sub { $self->_Wipeout( Object => $_[0]->TargetObject ) }, ); $self->DumpObject( Object => $object, State => 'after late dependencies' ); return; } =head3 Data storage and backups =head4 GetFileName( FileName => '<ISO DATETIME>-XXXX.sql', FromStorage => 1 ) Takes desired C<FileName> and flag C<FromStorage> then translate file name to absolute path by next rules: * Default value of the C<FileName> option is C<< <ISO DATETIME>-XXXX.sql >>; * if C<FileName> has C<XXXX> (exactly four uppercase C<X> letters) then it would be changed with digits from 0000 to 9999 range, with first one free value; * if C<FileName> has C<%T> then it would be replaced with the current date and time in the C<YYYY-MM-DDTHH:MM:SS> format. Note that using C<%t> may still generate not unique names, using C<XXXX> recomended. * if C<FromStorage> argument is true (default behaviour) then result path would always be relative to C<StoragePath>; * if C<FromStorage> argument is false then result would be relative to the current dir unless it's already absolute path. Returns an absolute path of the file. Examples: # file from storage with default name format my $fname = $shredder->GetFileName; # file from storage with custom name format my $fname = $shredder->GetFileName( FileName => 'shredder-XXXX.backup' ); # file with path relative to the current dir my $fname = $shredder->GetFileName( FromStorage => 0, FileName => 'backups/shredder.sql', ); # file with absolute path my $fname = $shredder->GetFileName( FromStorage => 0, FileName => '/var/backups/shredder-XXXX.sql' ); =cut sub GetFileName { my $self = shift; my %args = ( FileName => '', FromStorage => 1, @_ ); # default value my $file = $args{'FileName'} || '%t-XXXX.sql'; if( $file =~ /\%t/i ) { require POSIX; my $date_time = POSIX::strftime( "%Y%m%dT%H%M%S", gmtime ); $file =~ s/\%t/$date_time/gi; } # convert to absolute path if( $args{'FromStorage'} ) { $file = File::Spec->catfile( $self->StoragePath, $file ); } elsif( !File::Spec->file_name_is_absolute( $file ) ) { $file = File::Spec->rel2abs( $file ); } # check mask if( $file =~ /XXXX[^\/\\]*$/ ) { my( $tmp, $i ) = ( $file, 0 ); do { $i++; $tmp = $file; $tmp =~ s/XXXX([^\/\\]*)$/sprintf("%04d", $i).$1/e; } while( -e $tmp && $i < 9999 ); $file = $tmp; } if( -f $file ) { unless( -w _ ) { die "File '$file' exists, but is read-only"; } } elsif( !-e _ ) { unless( File::Spec->file_name_is_absolute( $file ) ) { $file = File::Spec->rel2abs( $file ); } # check base dir my $dir = File::Spec->join( (File::Spec->splitpath( $file ))[0,1] ); unless( -e $dir && -d _) { die "Base directory '$dir' for file '$file' doesn't exist"; } unless( -w $dir ) { die "Base directory '$dir' is not writable"; } } else { die "'$file' is not regular file"; } return $file; } =head4 StoragePath Returns an absolute path to the storage dir. See L</$ShredderStoragePath>. See also description of the L</GetFileName> method. =cut sub StoragePath { return scalar( RT->Config->Get('ShredderStoragePath') ) || File::Spec->catdir( $RT::VarPath, qw(data RT-Shredder) ); } my %active_dump_state = (); sub AddDumpPlugin { my $self = shift; my %args = ( Object => undef, Name => 'SQLDump', Arguments => undef, @_ ); my $plugin = $args{'Object'}; unless ( $plugin ) { require RT::Shredder::Plugin; $plugin = RT::Shredder::Plugin->new; my( $status, $msg ) = $plugin->LoadByName( $args{'Name'} ); die "Couldn't load dump plugin: $msg\n" unless $status; } die "Plugin is not of correct type" unless lc $plugin->Type eq 'dump'; if ( my $pargs = $args{'Arguments'} ) { my ($status, $msg) = $plugin->TestArgs( %$pargs ); die "Couldn't set plugin args: $msg\n" unless $status; } my @applies_to = $plugin->AppliesToStates; die "Plugin doesn't apply to any state" unless @applies_to; $active_dump_state{ lc $_ } = 1 foreach @applies_to; push @{ $self->{'dump_plugins'} }, $plugin; return $plugin; } sub DumpObject { my $self = shift; my %args = (Object => undef, State => undef, @_); die "No state passed" unless $args{'State'}; return unless $active_dump_state{ lc $args{'State'} }; foreach (@{ $self->{'dump_plugins'} }) { next unless grep lc $args{'State'} eq lc $_, $_->AppliesToStates; my ($state, $msg) = $_->Run( %args ); die "Couldn't run plugin: $msg" unless $state; } return; } { my $mark = 1; # XXX: integer overflows? sub PushDumpMark { my $self = shift; $mark++; foreach (@{ $self->{'dump_plugins'} }) { my ($state, $msg) = $_->PushMark( Mark => $mark ); die "Couldn't push mark: $msg" unless $state; } return $mark; } sub PopDumpMark { my $self = shift; foreach (@{ $self->{'dump_plugins'} }) { my ($state, $msg) = $_->PopMark( @_ ); die "Couldn't pop mark: $msg" unless $state; } return; } sub RollbackDumpTo { my $self = shift; foreach (@{ $self->{'dump_plugins'} }) { my ($state, $msg) = $_->RollbackTo( @_ ); die "Couldn't rollback to mark: $msg" unless $state; } return; } } 1; __END__ =head1 SEE ALSO L<rt-shredder>, L<rt-validator> =cut ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Principals.pm�����������������������������������������������������������������������000644 �000765 �000024 �00000004573 14005011336 016547� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Principals - a collection of RT::Principal objects =head1 SYNOPSIS use RT::Principals; =head1 DESCRIPTION =head1 METHODS =cut package RT::Principals; use strict; use warnings; use base 'RT::SearchBuilder'; use RT::Principal; sub Table { 'Principals'} sub _Init { my $self = shift; $self->{'with_disabled_column'} = 1; return ( $self->SUPER::_Init(@_) ); } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ScripConditions.pm������������������������������������������������������������������000644 �000765 �000024 �00000005320 14005011336 017544� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::ScripConditions - Collection of Action objects =head1 SYNOPSIS use RT::ScripConditions; =head1 DESCRIPTION =head1 METHODS =cut package RT::ScripConditions; use strict; use warnings; use base 'RT::SearchBuilder'; use RT::ScripCondition; sub Table { 'ScripConditions'} sub LimitToType { my $self = shift; my $type = shift; $self->Limit (ENTRYAGGREGATOR => 'OR', FIELD => 'Type', VALUE => "$type") if defined $type; $self->Limit (ENTRYAGGREGATOR => 'OR', FIELD => 'Type', VALUE => "Correspond") if $type eq "Create"; $self->Limit (ENTRYAGGREGATOR => 'OR', FIELD => 'Type', VALUE => 'any'); } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ObjectCustomRoles.pm����������������������������������������������������������������000644 �000765 �000024 �00000006111 14005011336 020037� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::ObjectCustomRoles; use base 'RT::SearchBuilder::AddAndSort'; use RT::CustomRoles; use RT::ObjectCustomRole; =head1 NAME RT::ObjectCustomRole - collection of RT::ObjectCustomRole records =head1 DESCRIPTION Collection of L<RT::ObjectCustomRole> records. Inherits methods from L<RT::SearchBuilder::AddAndSort>. =head1 METHODS =cut =head2 Table Returns name of the table where records are stored. =cut sub Table { 'ObjectCustomRoles'} =head2 LimitToCustomRole Takes the id of an L<RT::CustomRole> object and limits this collection. =cut sub LimitToCustomRole { my $self = shift; my $id = shift; $self->Limit( FIELD => 'CustomRole', VALUE => $id ); } =head2 LimitToObjectId Takes an ObjectId and limits the collection to object custom roles for said object. When called multiple times the ObjectId limits are joined with OR. =cut sub LimitToObjectId { my $self = shift; my $id = shift; $self->Limit( FIELD => 'ObjectId', OPERATOR => '=', VALUE => $id, ENTRYAGGREGATOR => 'OR' ); } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ScripActions.pm���������������������������������������������������������������������000644 �000765 �000024 �00000005471 14005011336 017042� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::ScripActions - Collection of Action objects =head1 SYNOPSIS use RT::ScripActions; =head1 DESCRIPTION =head1 METHODS =cut package RT::ScripActions; use strict; use warnings; use base 'RT::SearchBuilder'; sub Table { 'ScripActions'} sub _Init { my $self = shift; $self->{'table'} = "ScripActions"; $self->{'primary_key'} = "id"; return ( $self->SUPER::_Init(@_)); } sub LimitToType { my $self = shift; my $type = shift; $self->Limit (ENTRYAGGREGATOR => 'OR', FIELD => 'Type', VALUE => "$type") if defined $type; $self->Limit (ENTRYAGGREGATOR => 'OR', FIELD => 'Type', VALUE => "Correspond") if $type eq "Create"; $self->Limit (ENTRYAGGREGATOR => 'OR', FIELD => 'Type', VALUE => 'any'); } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Catalogs.pm�������������������������������������������������������������������������000644 �000765 �000024 �00000006156 14005011336 016177� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Catalogs; use base 'RT::SearchBuilder'; =head1 NAME RT::Catalogs - a collection of L<RT::Catalog> objects =head1 METHODS Only additional methods or overridden behaviour beyond the L<RT::SearchBuilder> (itself a L<DBIx::SearchBuilder>) class are documented below. =head2 Limit Defaults CASESENSITIVE to 0 =cut sub Limit { my $self = shift; my %args = ( CASESENSITIVE => 0, @_ ); $self->SUPER::Limit(%args); } =head1 INTERNAL METHODS Public methods which encapsulate implementation details. You shouldn't need to call these in normal code. =head2 AddRecord Checks the L<RT::Catalog> is readable before adding it to the results =cut sub AddRecord { my $self = shift; my $catalog = shift; return unless $catalog->CurrentUserCanSee; $self->SUPER::AddRecord($catalog, @_); } =head1 PRIVATE METHODS =head2 _Init Sets default ordering by Name ascending. =cut sub _Init { my $self = shift; $self->{'with_disabled_column'} = 1; $self->OrderBy( FIELD => 'Name', ORDER => 'ASC' ); return $self->SUPER::_Init( @_ ); } sub Table { "Catalogs" } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/EmailParser.pm����������������������������������������������������������������������000644 �000765 �000024 �00000051631 14005011336 016644� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::EmailParser; use base qw/RT::Base/; use strict; use warnings; use Email::Address; use MIME::Entity; use MIME::Head; use MIME::Parser; use File::Temp qw/tempdir/; use RT::Util qw(mime_recommended_filename EntityLooksLikeEmailMessage); =head1 NAME RT::EmailParser - helper functions for parsing parts from incoming email messages =head1 SYNOPSIS =head1 DESCRIPTION =head1 METHODS =head2 new Returns a new RT::EmailParser object =cut sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; bless ($self, $class); return $self; } =head2 SmartParseMIMEEntityFromScalar Message => SCALAR_REF [, Decode => BOOL, Exact => BOOL ] } Parse a message stored in a scalar from scalar_ref. =cut sub SmartParseMIMEEntityFromScalar { my $self = shift; my %args = ( Message => undef, Decode => 1, Exact => 0, @_ ); eval { my ( $fh, $temp_file ); for ( 1 .. 10 ) { # on NFS and NTFS, it is possible that tempfile() conflicts # with other processes, causing a race condition. we try to # accommodate this by pausing and retrying. last if ( $fh, $temp_file ) = eval { File::Temp::tempfile( UNLINK => 0 ) }; sleep 1; } if ($fh) { #thank you, windows binmode $fh; $fh->autoflush(1); print $fh $args{'Message'}; close($fh); if ( -f $temp_file ) { my $entity = $self->ParseMIMEEntityFromFile( $temp_file, $args{'Decode'}, $args{'Exact'} ); unlink($temp_file) or RT->Logger->error("Unable to delete temp file $temp_file, error: $!"); return $entity; } } }; #If for some reason we weren't able to parse the message using a temp file # try it with a scalar if ( $@ || !$self->Entity ) { return $self->ParseMIMEEntityFromScalar( $args{'Message'}, $args{'Decode'}, $args{'Exact'} ); } } =head2 ParseMIMEEntityFromSTDIN Parse a message from standard input =cut sub ParseMIMEEntityFromSTDIN { my $self = shift; return $self->ParseMIMEEntityFromFileHandle(\*STDIN, @_); } =head2 ParseMIMEEntityFromScalar $message Takes either a scalar or a reference to a scalar which contains a stringified MIME message. Parses it. Returns true if it wins. Returns false if it loses. =cut sub ParseMIMEEntityFromScalar { my $self = shift; return $self->_ParseMIMEEntity( shift, 'parse_data', @_ ); } =head2 ParseMIMEEntityFromFilehandle *FH Parses a mime entity from a filehandle passed in as an argument =cut sub ParseMIMEEntityFromFileHandle { my $self = shift; return $self->_ParseMIMEEntity( shift, 'parse', @_ ); } =head2 ParseMIMEEntityFromFile Parses a mime entity from a filename passed in as an argument =cut sub ParseMIMEEntityFromFile { my $self = shift; return $self->_ParseMIMEEntity( shift, 'parse_open', @_ ); } sub _ParseMIMEEntity { my $self = shift; my $message = shift; my $method = shift; my $postprocess = (@_ ? shift : 1); my $exact = shift; # Create a new parser object: my $parser = MIME::Parser->new(); $self->_SetupMIMEParser($parser); $parser->decode_bodies(0) if $exact; # TODO: XXX 3.0 we really need to wrap this in an eval { } unless ( $self->{'entity'} = $parser->$method($message) ) { $RT::Logger->crit("Couldn't parse MIME stream and extract the submessages"); # Try again, this time without extracting nested messages $parser->extract_nested_messages(0); unless ( $self->{'entity'} = $parser->$method($message) ) { $RT::Logger->crit("couldn't parse MIME stream"); return ( undef); } } $self->_PostProcessNewEntity if $postprocess; return $self->{'entity'}; } sub _DecodeBodies { my $self = shift; return unless $self->{'entity'}; my @parts = $self->{'entity'}->parts_DFS; $self->_DecodeBody($_) foreach @parts; } sub _DecodeBody { my $self = shift; my $entity = shift; my $old = $entity->bodyhandle or return; return unless $old->is_encoded; require MIME::Decoder; my $encoding = $entity->head->mime_encoding; my $decoder = MIME::Decoder->new($encoding); unless ( $decoder ) { $RT::Logger->error("Couldn't find decoder for '$encoding', switching to binary"); $old->is_encoded(0); return; } require MIME::Body; # XXX: use InCore for now, but later must switch to files my $new = MIME::Body::InCore->new(); $new->binmode(1); $new->is_encoded(0); my $source = $old->open('r') or die "couldn't open body: $!"; my $destination = $new->open('w') or die "couldn't open body: $!"; { local $@; eval { $decoder->decode($source, $destination) }; $RT::Logger->error($@) if $@; } $source->close or die "can't close: $!"; $destination->close or die "can't close: $!"; $entity->bodyhandle( $new ); } =head2 _PostProcessNewEntity cleans up and postprocesses a newly parsed MIME Entity =cut sub _PostProcessNewEntity { my $self = shift; #Now we've got a parsed mime object. _DetectAttachedEmailFiles($self->{'entity'}); # Unfold headers that are have embedded newlines # Better do this before conversion or it will break # with multiline encoded Subject (RFC2047) (fsck.com #5594) $self->Head->unfold; # try to convert text parts into utf-8 charset RT::I18N::SetMIMEEntityToEncoding($self->{'entity'}, 'utf-8'); } =head2 _DetectAttachedEmailFiles Email messages submitted as attachments will be processed as a nested email rather than an attached file. Users may prefer to treat attached emails as normal file attachments and not have them processed. If TreatAttachedEmailAsFiles is selected, treat attached email files as regular file attachments. We do this by checking MIME entities that have email (message/) content types and also have a defined filename, indicating they are attachments. See the extract_nested_messages documentation in in the L<MIME::Parser> module for details on how it deals with nested email messages. =cut sub _DetectAttachedEmailFiles { my $entity = shift; return unless RT->Config->Get('TreatAttachedEmailAsFiles'); my $filename = mime_recommended_filename($entity); # This detection is based on the way MIME::Parser handles email with # extract_nested_messages enabled. if ( EntityLooksLikeEmailMessage($entity) && not defined $entity->bodyhandle && $filename ) { # Fixup message # TODO: Investigate proposing an option upstream in MIME::Parser to avoid the initial parse if ( $entity->parts(0) ){ # Get the headers from the part and write them back to the body. # This will create a file attachment that looks like the file you get if # you "Save As" and email message in your email client. # If we don't save them here, the headers from the attached email will be lost. ( undef, my $filename ) = File::Temp::tempfile( UNLINK => 1 ); my $bodyhandle = MIME::Body::File->new($filename); my $IO = $bodyhandle->open("w") || RT::Logger->error("Unable to open email body: $!"); $IO->print( $entity->parts(0)->as_string ); $IO->close || RT::Logger->error("Unable to close email body: $!"); $entity->parts([]); $entity->bodyhandle($bodyhandle); RT::Logger->debug("Manually setting bodyhandle for attached email file"); } } elsif ( $entity->parts ) { foreach my $part ( $entity->parts ){ _DetectAttachedEmailFiles( $part ); } } return; } =head2 IsRTaddress ADDRESS Takes a single parameter, an email address. Returns true if that address matches the C<RTAddressRegexp> config option. Returns false, otherwise. =cut sub IsRTAddress { my $self = shift; my $address = shift; return undef unless defined($address) and $address =~ /\S/; if ( my $address_re = RT->Config->Get('RTAddressRegexp') ) { return $address =~ /$address_re/i ? 1 : undef; } # we don't warn here, but do in config check if ( my $correspond_address = RT->Config->Get('CorrespondAddress') ) { return 1 if lc $correspond_address eq lc $address; } if ( my $comment_address = RT->Config->Get('CommentAddress') ) { return 1 if lc $comment_address eq lc $address; } my $queue = RT::Queue->new( RT->SystemUser ); $queue->LoadByCols( CorrespondAddress => $address ); return 1 if $queue->id; $queue->LoadByCols( CommentAddress => $address ); return 1 if $queue->id; return undef; } =head2 CullRTAddresses ARRAY Takes a single argument, an array of email addresses. Returns the same array with any IsRTAddress()es weeded out. =cut sub CullRTAddresses { my $self = shift; my @addresses = (@_); return grep { !$self->IsRTAddress($_) } @addresses; } # LookupExternalUserInfo is a site-definable method for synchronizing # incoming users with an external data source. # # This routine takes a tuple of EmailAddress and FriendlyName # EmailAddress is the user's email address, ususally taken from # an email message's From: header. # FriendlyName is a freeform string, ususally taken from the "comment" # portion of an email message's From: header. # # If you define an AutoRejectRequest template, RT will use this # template for the rejection message. =head2 LookupExternalUserInfo LookupExternalUserInfo is a site-definable method for synchronizing incoming users with an external data source. This routine takes a tuple of EmailAddress and FriendlyName EmailAddress is the user's email address, ususally taken from an email message's From: header. FriendlyName is a freeform string, ususally taken from the "comment" portion of an email message's From: header. It returns (FoundInExternalDatabase, ParamHash); FoundInExternalDatabase must be set to 1 before return if the user was found in the external database. ParamHash is a Perl parameter hash which can contain at least the following fields. These fields are used to populate RT's users database when the user is created. EmailAddress is the email address that RT should use for this user. Name is the 'Name' attribute RT should use for this user. 'Name' is used for things like access control and user lookups. RealName is what RT should display as the user's name when displaying 'friendly' names =cut sub LookupExternalUserInfo { my $self = shift; my $EmailAddress = shift; my $RealName = shift; my $FoundInExternalDatabase = 1; my %params; #Name is the RT username you want to use for this user. $params{'Name'} = $EmailAddress; $params{'EmailAddress'} = $EmailAddress; $params{'RealName'} = $RealName; return ($FoundInExternalDatabase, %params); } =head2 Head Return the parsed head from this message =cut sub Head { my $self = shift; return $self->Entity->head; } =head2 Entity Return the parsed Entity from this message =cut sub Entity { my $self = shift; return $self->{'entity'}; } =head2 _SetupMIMEParser $parser A private instance method which sets up a mime parser to do its job =cut ## TODO: Does it make sense storing to disk at all? After all, we ## need to put each msg as an in-core scalar before saving it to ## the database, don't we? ## At the same time, we should make sure that we nuke attachments ## Over max size and return them sub _SetupMIMEParser { my $self = shift; my $parser = shift; # Set up output directory for files; we use $RT::VarPath instead # of File::Spec->tmpdir (e.g., /tmp) beacuse it isn't always # writable. my $tmpdir; if (-w File::Spec->tmpdir) { $tmpdir = File::Temp::tempdir( TMPDIR => 1, CLEANUP => 1 ); } elsif ( -w $RT::VarPath ) { $tmpdir = File::Temp::tempdir( DIR => $RT::VarPath, CLEANUP => 1 ); } else { $RT::Logger->crit("Neither the RT var directory ($RT::VarPath) nor the system tmpdir (@{[File::Spec->tmpdir]}) are writable; falling back to in-memory parsing!"); } #If someone includes a message, extract it $parser->extract_nested_messages(1); $parser->extract_uuencode(1); ### default is false if ($tmpdir) { # If we got a writable tmpdir, write to disk push ( @{ $self->{'AttachmentDirs'} ||= [] }, $tmpdir ); $parser->output_dir($tmpdir); $parser->filer->ignore_filename(1); # Set up the prefix for files with auto-generated names: $parser->output_prefix("part"); # From the MIME::Parser docs: # "Normally, tmpfiles are created when needed during parsing, and destroyed automatically when they go out of scope" # Turns out that the default is to recycle tempfiles # Temp files should never be recycled, especially when running under perl taint checking $parser->tmp_recycling(0) if $parser->can('tmp_recycling'); } else { # Otherwise, fall back to storing it in memory $parser->output_to_core(1); $parser->tmp_to_core(1); $parser->use_inner_files(1); } } =head2 ParseEmailAddress string Returns a list of Email::Address objects Works around the bug that Email::Address 1.889 and earlier doesn't handle local-only email addresses (when users pass in just usernames on the RT system in fields that expect Email Addresses) We don't handle the case of bob, fred@bestpractical.com because we don't want to fail parsing bob, "Falcone, Fred" <fred@bestpractical.com> The next release of Email::Address will have a new method we can use that removes the bandaid =cut use Email::Address::List; sub ParseEmailAddress { my $self = shift; my $address_string = shift; # Some broken mailers send: ""Vincent, Jesse"" <jesse@fsck.com>. Hate $address_string =~ s/\"\"(.*?)\"\"/\"$1\"/g; my @list = Email::Address::List->parse( $address_string, skip_comments => 1, skip_groups => 1, ); my $logger = sub { RT->Logger->error( "Unable to parse an email address from $address_string: ". shift ) }; my @addresses; foreach my $e ( @list ) { if ($e->{'type'} eq 'mailbox') { if ($e->{'not_ascii'}) { $logger->($e->{'value'} ." contains not ASCII values"); next; } push @addresses, $e->{'value'} } elsif ( $e->{'value'} =~ /^\s*(\w+)\s*$/ ) { my $user = RT::User->new( RT->SystemUser ); $user->Load( $1 ); if ($user->id) { push @addresses, Email::Address->new($user->Name, $user->EmailAddress); } else { $logger->($e->{'value'} ." is not a valid email address and is not user name"); } } else { $logger->($e->{'value'} ." is not a valid email address"); } } $self->CleanupAddresses(@addresses); return @addresses; } =head2 CleanupAddresses ARRAY Massages an array of L<Email::Address> objects to make their email addresses more palatable. Currently this strips off surrounding single quotes around C<< ->address >> and B<< modifies the L<Email::Address> objects in-place >>. Returns the list of objects for convienence in C<map>/C<grep> chains. =cut sub CleanupAddresses { my $self = shift; for my $addr (@_) { next unless defined $addr; # Outlook sometimes sends addresses surrounded by single quotes; # clean them all up if ((my $email = $addr->address) =~ s/^'(.+)'$/$1/) { $addr->address($email); } } return @_; } =head2 RescueOutlook Outlook 2007/2010 have a bug when you write an email with the html format. it will send a 'multipart/alternative' with both 'text/plain' and 'text/html' in it. it's cool to have a 'text/plain' part, but the problem is the part is not so right: all the "\n" in your main message will become "\n\n" :/ this method will fix this bug, i.e. replaces "\n\n" to "\n". return 1 if it does find the problem in the entity and get it fixed. =cut sub RescueOutlook { my $self = shift; my $mime = $self->Entity(); return unless $mime && $self->LooksLikeMSEmail($mime); my $text_part; if ( $mime->head->get('Content-Type') =~ m{multipart/mixed} ) { my $first = $mime->parts(0); if ( $first->head->get('Content-Type') =~ m{multipart/alternative} ) { my $inner_first = $first->parts(0); if ( $inner_first->head->get('Content-Type') =~ m{text/plain} ) { $text_part = $inner_first; } } } elsif ( $mime->head->get('Content-Type') =~ m{multipart/alternative} ) { my $first = $mime->parts(0); if ( $first->head->get('Content-Type') =~ m{text/plain} ) { $text_part = $first; } } # Add base64 since we've seen examples of double newlines with # this type too. Need an example of a multi-part base64 to # handle that permutation if it exists. elsif ( ($mime->head->get('Content-Transfer-Encoding')||'') =~ m{base64} ) { $text_part = $mime; # Assuming single part, already decoded. } if ($text_part) { # use the unencoded string my $content = $text_part->bodyhandle->as_string; if ( $content =~ s/\n\n/\n/g ) { # Outlook puts a space on extra newlines, remove it $content =~ s/\ +$//mg; # only write only if we did change the content if ( my $io = $text_part->open("w") ) { $io->print($content); $io->close; $RT::Logger->debug( "Removed extra newlines from MS Outlook message."); return 1; } else { $RT::Logger->error("Can't write to body to fix newlines"); } } } return; } =head1 LooksLikeMSEmail Try to determine if the current email may have come from MS Outlook or gone through Exchange, and therefore may have extra newlines added. =cut sub LooksLikeMSEmail { my $self = shift; my $mime = shift; my $mailer = $mime->head->get('X-Mailer'); # 12.0 is outlook 2007, 14.0 is 2010 return 1 if ( $mailer && $mailer =~ /Microsoft(?:.*?)Outlook 1[2-4]\./ ); if ( RT->Config->Get('CheckMoreMSMailHeaders') ) { # Check for additional headers that might # indicate this came from Outlook or through Exchange. # A sample we received had the headers X-MS-Has-Attach: and # X-MS-Tnef-Correlator: and both had no value. my @tags = $mime->head->tags(); return 1 if grep { /^X-MS-/ } @tags; } return 0; # Doesn't look like MS email. } sub DESTROY { my $self = shift; File::Path::rmtree([@{$self->{'AttachmentDirs'}}],0,1) if $self->{'AttachmentDirs'}; } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Transactions.pm���������������������������������������������������������������������000644 �000765 �000024 �00000103705 14005011336 017110� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Transactions - a collection of RT Transaction objects =head1 SYNOPSIS use RT::Transactions; =head1 DESCRIPTION =head1 METHODS =cut package RT::Transactions; use strict; use warnings; use base 'RT::SearchBuilder'; use RT::Transaction; use 5.010; sub Table { 'Transactions'} # {{{ sub _Init sub _Init { my $self = shift; $self->{'table'} = "Transactions"; $self->{'primary_key'} = "id"; # By default, order by the date of the transaction, rather than ID. $self->OrderByCols( { FIELD => 'Created', ORDER => 'ASC' }, { FIELD => 'id', ORDER => 'ASC' } ); $self->SUPER::_Init(@_); $self->_InitSQL(); } sub _InitSQL { my $self = shift; # Private Member Variables (which should get cleaned) $self->{'_sql_query'} = ''; } =head2 LimitToTicket TICKETID Find only transactions for the ticket whose id is TICKETID. This includes tickets merged into TICKETID. Repeated calls to this method will intelligently limit down to that set of tickets, joined with an OR =cut sub LimitToTicket { my $self = shift; my $tid = shift; unless ( $self->{'tickets_table'} ) { $self->{'tickets_table'} ||= $self->Join( ALIAS1 => 'main', FIELD1 => 'ObjectId', TABLE2 => 'Tickets', FIELD2 => 'id' ); $self->Limit( FIELD => 'ObjectType', VALUE => 'RT::Ticket', ); } $self->Limit( ALIAS => $self->{tickets_table}, FIELD => 'EffectiveId', OPERATOR => '=', ENTRYAGGREGATOR => 'OR', VALUE => $tid, ); } sub AddRecord { my $self = shift; my ($record) = @_; return unless $record->CurrentUserCanSee; return $self->SUPER::AddRecord($record); } our %FIELD_METADATA = ( id => ['INT'], #loc_left_pair ObjectId => ['ID'], #loc_left_pair ObjectType => ['STRING'], #loc_left_pair Creator => [ 'ENUM' => 'User' ], #loc_left_pair TimeTaken => ['INT'], #loc_left_pair Type => ['STRING'], #loc_left_pair Field => ['STRING'], #loc_left_pair OldValue => ['STRING'], #loc_left_pair NewValue => ['STRING'], #loc_left_pair ReferenceType => ['STRING'], #loc_left_pair OldReference => ['STRING'], #loc_left_pair NewReference => ['STRING'], #loc_left_pair Data => ['STRING'], #loc_left_pair Created => [ 'DATE' => 'Created' ], #loc_left_pair Content => ['ATTACHCONTENT'], #loc_left_pair ContentType => ['ATTACHFIELD'], #loc_left_pair Filename => ['ATTACHFIELD'], #loc_left_pair Subject => ['ATTACHFIELD'], #loc_left_pair CustomFieldValue => [ 'CUSTOMFIELD' => 'Transaction' ], #loc_left_pair CustomField => [ 'CUSTOMFIELD' => 'Transaction' ], #loc_left_pair CF => [ 'CUSTOMFIELD' => 'Transaction' ], #loc_left_pair TicketId => ['TICKETFIELD'], #loc_left_pair TicketSubject => ['TICKETFIELD'], #loc_left_pair TicketQueue => ['TICKETFIELD'], #loc_left_pair TicketStatus => ['TICKETFIELD'], #loc_left_pair TicketOwner => ['TICKETFIELD'], #loc_left_pair TicketCreator => ['TICKETFIELD'], #loc_left_pair TicketLastUpdatedBy => ['TICKETFIELD'], #loc_left_pair TicketCreated => ['TICKETFIELD'], #loc_left_pair TicketStarted => ['TICKETFIELD'], #loc_left_pair TicketResolved => ['TICKETFIELD'], #loc_left_pair TicketTold => ['TICKETFIELD'], #loc_left_pair TicketLastUpdated => ['TICKETFIELD'], #loc_left_pair TicketStarts => ['TICKETFIELD'], #loc_left_pair TicketDue => ['TICKETFIELD'], #loc_left_pair TicketPriority => ['TICKETFIELD'], #loc_left_pair TicketInitialPriority => ['TICKETFIELD'], #loc_left_pair TicketFinalPriority => ['TICKETFIELD'], #loc_left_pair TicketType => ['TICKETFIELD'], #loc_left_pair TicketQueueLifecycle => ['TICKETQUEUEFIELD'], #loc_left_pair CustomFieldName => ['CUSTOMFIELDNAME'], #loc_left_pair CFName => ['CUSTOMFIELDNAME'], #loc_left_pair OldCFValue => ['OBJECTCUSTOMFIELDVALUE'], #loc_left_pair NewCFValue => ['OBJECTCUSTOMFIELDVALUE'], #loc_left_pair ); # Lower Case version of FIELDS, for case insensitivity our %LOWER_CASE_FIELDS = map { ( lc($_) => $_ ) } (keys %FIELD_METADATA); our %dispatch = ( INT => \&_IntLimit, ID => \&_IdLimit, ENUM => \&_EnumLimit, DATE => \&_DateLimit, STRING => \&_StringLimit, CUSTOMFIELD => \&_CustomFieldLimit, ATTACHFIELD => \&_AttachLimit, ATTACHCONTENT => \&_AttachContentLimit, TICKETFIELD => \&_TicketLimit, TICKETQUEUEFIELD => \&_TicketQueueLimit, OBJECTCUSTOMFIELDVALUE => \&_ObjectCustomFieldValueLimit, CUSTOMFIELDNAME => \&_CustomFieldNameLimit, ); sub FIELDS { return \%FIELD_METADATA } our @SORTFIELDS = qw(id ObjectId Created); =head2 SortFields Returns the list of fields that lists of transactions can easily be sorted by =cut sub SortFields { my $self = shift; return (@SORTFIELDS); } =head1 Limit Helper Routines These routines are the targets of a dispatch table depending on the type of field. They all share the same signature: my ($self,$field,$op,$value,@rest) = @_; The values in @rest should be suitable for passing directly to DBIx::SearchBuilder::Limit. Essentially they are an expanded/broken out (and much simplified) version of what ProcessRestrictions used to do. They're also much more clearly delineated by the TYPE of field being processed. =head2 _IdLimit Handle ID field. =cut sub _IdLimit { my ( $sb, $field, $op, $value, @rest ) = @_; if ( $value eq '__Bookmarked__' ) { return $sb->_BookmarkLimit( $field, $op, $value, @rest ); } else { return $sb->_IntLimit( $field, $op, $value, @rest ); } } =head2 _EnumLimit Handle Fields which are limited to certain values, and potentially need to be looked up from another class. This subroutine actually handles two different kinds of fields. For some the user is responsible for limiting the values. (i.e. ObjectType). For others, the value specified by the user will be looked by via specified class. Meta Data: name of class to lookup in (Optional) =cut sub _EnumLimit { my ( $sb, $field, $op, $value, @rest ) = @_; # SQL::Statement changes != to <>. (Can we remove this now?) $op = "!=" if $op eq "<>"; die "Invalid Operation: $op for $field" unless $op eq "=" or $op eq "!="; my $meta = $FIELD_METADATA{$field}; if ( defined $meta->[1] && defined $value && $value !~ /^\d+$/ ) { my $class = "RT::" . $meta->[1]; my $o = $class->new( $sb->CurrentUser ); $o->Load($value); $value = $o->Id || 0; } $sb->Limit( FIELD => $field, VALUE => $value, OPERATOR => $op, @rest, ); } =head2 _IntLimit Handle fields where the values are limited to integers. (id) Meta Data: None =cut sub _IntLimit { my ( $sb, $field, $op, $value, @rest ) = @_; my $is_a_like = $op =~ /MATCHES|ENDSWITH|STARTSWITH|LIKE/i; # We want to support <id LIKE '1%'>, but we need to explicitly typecast # on Postgres if ( $is_a_like && RT->Config->Get('DatabaseType') eq 'Pg' ) { return $sb->Limit( FUNCTION => "CAST(main.$field AS TEXT)", OPERATOR => $op, VALUE => $value, @rest, ); } $sb->Limit( FIELD => $field, VALUE => $value, OPERATOR => $op, @rest, ); } =head2 _DateLimit Handle date fields. (Created) Meta Data: 1: type of link. (Probably not necessary.) =cut sub _DateLimit { my ( $sb, $field, $op, $value, %rest ) = @_; die "Invalid Date Op: $op" unless $op =~ /^(=|>|<|>=|<=|IS(\s+NOT)?)$/i; my $meta = $FIELD_METADATA{$field}; die "Incorrect Meta Data for $field" unless ( defined $meta->[1] ); if ( $op =~ /^(IS(\s+NOT)?)$/i) { return $sb->Limit( FUNCTION => $sb->NotSetDateToNullFunction, FIELD => $meta->[1], OPERATOR => $op, VALUE => "NULL", %rest, ); } my $date = RT::Date->new( $sb->CurrentUser ); $date->Set( Format => 'unknown', Value => $value ); if ( $op eq "=" ) { # if we're specifying =, that means we want everything on a # particular single day. in the database, we need to check for > # and < the edges of that day. $date->SetToMidnight( Timezone => 'server' ); my $daystart = $date->ISO; $date->AddDay; my $dayend = $date->ISO; $sb->_OpenParen; $sb->Limit( FIELD => $meta->[1], OPERATOR => ">=", VALUE => $daystart, %rest, ); $sb->Limit( FIELD => $meta->[1], OPERATOR => "<", VALUE => $dayend, %rest, ENTRYAGGREGATOR => 'AND', ); $sb->_CloseParen; } else { $sb->Limit( FUNCTION => $sb->NotSetDateToNullFunction, FIELD => $meta->[1], OPERATOR => $op, VALUE => $date->ISO, %rest, ); } } =head2 _StringLimit Handle simple fields which are just strings. (Type, Field, OldValue, NewValue, ReferenceType) Meta Data: None =cut sub _StringLimit { my ( $sb, $field, $op, $value, @rest ) = @_; # FIXME: # Valid Operators: # =, !=, LIKE, NOT LIKE if ( RT->Config->Get('DatabaseType') eq 'Oracle' && (!defined $value || !length $value) && lc($op) ne 'is' && lc($op) ne 'is not' ) { if ($op eq '!=' || $op =~ /^NOT\s/i) { $op = 'IS NOT'; } else { $op = 'IS'; } $value = 'NULL'; } $sb->Limit( FIELD => $field, OPERATOR => $op, VALUE => $value, CASESENSITIVE => 0, @rest, ); } =head2 _ObjectCustomFieldValueLimit Handle object custom field values. (OldReference, NewReference) Meta Data: None =cut sub _ObjectCustomFieldValueLimit { my ( $self, $field, $op, $value, @rest ) = @_; my $alias_name = $field =~ /new/i ? 'newocfv' : 'oldocfv'; $self->{_sql_aliases}{$alias_name} ||= $self->Join( TYPE => 'LEFT', FIELD1 => $field =~ /new/i ? 'NewReference' : 'OldReference', TABLE2 => 'ObjectCustomFieldValues', FIELD2 => 'id', ); my $value_is_long = ( length( Encode::encode( "UTF-8", $value ) ) > 255 ) ? 1 : 0; $self->Limit( @rest, ALIAS => $self->{_sql_aliases}{$alias_name}, FIELD => $value_is_long ? 'LargeContent' : 'Content', OPERATOR => $op, VALUE => $value, CASESENSITIVE => 0, @rest, ); } =head2 _CustomFieldNameLimit Handle custom field name field. (Field) Meta Data: None =cut sub _CustomFieldNameLimit { my ( $self, $_field, $op, $value, %rest ) = @_; $self->Limit( FIELD => 'Type', OPERATOR => '=', VALUE => 'CustomField', CASESENSITIVE => 0, ENTRYAGGREGATOR => 'AND', ); if ( $value =~ /\D/ ) { my $cfs = RT::CustomFields->new( RT->SystemUser ); $cfs->Limit( FIELD => 'Name', VALUE => $value, CASESENSITIVE => 0, ); $value = [ map { $_->id } @{ $cfs->ItemsArrayRef } ]; $self->Limit( FIELD => 'Field', OPERATOR => $op eq '!=' ? 'NOT IN' : 'IN', VALUE => $value, CASESENSITIVE => 0, ENTRYAGGREGATOR => 'AND', %rest, ); } else { $self->Limit( FIELD => 'Field', OPERATOR => $op, VALUE => $value, CASESENSITIVE => 0, ENTRYAGGREGATOR => 'AND', %rest, ); } } =head2 _CustomFieldDecipher Try and turn a CF descriptor into (cfid, cfname) object pair. Takes an optional second parameter of the CF LookupType, defaults to Ticket CFs. =cut sub _CustomFieldDecipher { my ($self, $string, $lookuptype) = @_; $lookuptype ||= $self->_SingularClass->CustomFieldLookupType; my ($object, $field, $column) = ($string =~ /^(?:(.+?)\.)?\{(.+)\}(?:\.(Content|LargeContent))?$/); $field ||= ($string =~ /^\{(.*?)\}$/)[0] || $string; my ($cf, $applied_to); if ( $object ) { my $record_class = RT::CustomField->RecordClassFromLookupType($lookuptype); $applied_to = $record_class->new( $self->CurrentUser ); $applied_to->Load( $object ); if ( $applied_to->id ) { RT->Logger->debug("Limiting to CFs identified by '$field' applied to $record_class #@{[$applied_to->id]} (loaded via '$object')"); } else { RT->Logger->warning("$record_class '$object' doesn't exist, parsed from '$string'"); $object = 0; undef $applied_to; } } if ( $field =~ /\D/ ) { $object ||= ''; my $cfs = RT::CustomFields->new( $self->CurrentUser ); $cfs->Limit( FIELD => 'Name', VALUE => $field, CASESENSITIVE => 0 ); $cfs->LimitToLookupType($lookuptype); if ($applied_to) { $cfs->SetContextObject($applied_to); $cfs->LimitToObjectId($applied_to->id); } # if there is more then one field the current user can # see with the same name then we shouldn't return cf object # as we don't know which one to use $cf = $cfs->First; if ( $cf ) { $cf = undef if $cfs->Next; } } else { $cf = RT::CustomField->new( $self->CurrentUser ); $cf->Load( $field ); $cf->SetContextObject($applied_to) if $cf->id and $applied_to; } return ($object, $field, $cf, $column); } =head2 _CustomFieldLimit Limit based on CustomFields Meta Data: none =cut sub _CustomFieldLimit { my ( $self, $_field, $op, $value, %rest ) = @_; my $meta = $FIELD_METADATA{ $_field }; my $class = $meta->[1] || 'Transaction'; my $type = "RT::$class"->CustomFieldLookupType; my $field = $rest{'SUBKEY'} || die "No field specified"; # For our sanity, we can only limit on one object at a time my ($object, $cfid, $cf, $column); ($object, $field, $cf, $column) = $self->_CustomFieldDecipher( $field, $type ); $self->_LimitCustomField( %rest, LOOKUPTYPE => $type, CUSTOMFIELD => $cf || $field, KEY => $cf ? $cf->id : "$type-$object.$field", OPERATOR => $op, VALUE => $value, COLUMN => $column, SUBCLAUSE => "txnsql", ); } =head2 _AttachLimit Limit based on the ContentType or the Filename of an attachment. =cut sub _AttachLimit { my ( $self, $field, $op, $value, %rest ) = @_; unless ( defined $self->{_sql_aliases}{attach} ) { $self->{_sql_aliases}{attach} = $self->Join( TYPE => 'LEFT', # not all txns have an attachment FIELD1 => 'id', TABLE2 => 'Attachments', FIELD2 => 'TransactionId', ); } $self->Limit( %rest, ALIAS => $self->{_sql_aliases}{attach}, FIELD => $field, OPERATOR => $op, VALUE => $value, CASESENSITIVE => 0, ); } =head2 _AttachContentLimit Limit based on the Content of a transaction. =cut sub _AttachContentLimit { my ( $self, $field, $op, $value, %rest ) = @_; $field = 'Content' if $field =~ /\W/; my $config = RT->Config->Get('FullTextSearch') || {}; unless ( $config->{'Enable'} ) { $self->Limit( %rest, FIELD => 'id', VALUE => 0 ); return; } unless ( defined $self->{_sql_aliases}{attach} ) { $self->{_sql_aliases}{attach} = $self->Join( TYPE => 'LEFT', # not all txns have an attachment FIELD1 => 'id', TABLE2 => 'Attachments', FIELD2 => 'TransactionId', ); } $self->_OpenParen; if ( $config->{'Indexed'} ) { my $db_type = RT->Config->Get('DatabaseType'); my $alias; if ( $config->{'Table'} and $config->{'Table'} ne "Attachments") { $alias = $self->{'_sql_aliases'}{'full_text'} ||= $self->Join( TYPE => 'LEFT', ALIAS1 => $self->{_sql_aliases}{attach}, FIELD1 => 'id', TABLE2 => $config->{'Table'}, FIELD2 => 'id', ); } else { $alias = $self->{_sql_aliases}{attach}; } #XXX: handle negative searches my $index = $config->{'Column'}; if ( $db_type eq 'Oracle' ) { my $dbh = $RT::Handle->dbh; my $alias = $self->{_sql_aliases}{attach}; $self->Limit( %rest, FUNCTION => "CONTAINS( $alias.$field, ".$dbh->quote($value) .")", OPERATOR => '>', VALUE => 0, QUOTEVALUE => 0, CASESENSITIVE => 1, ); # this is required to trick DBIx::SB's LEFT JOINS optimizer # into deciding that join is redundant as it is $self->Limit( ENTRYAGGREGATOR => 'AND', ALIAS => $self->{_sql_aliases}{attach}, FIELD => 'Content', OPERATOR => 'IS NOT', VALUE => 'NULL', ); } elsif ( $db_type eq 'Pg' ) { my $dbh = $RT::Handle->dbh; $self->Limit( %rest, ALIAS => $alias, FIELD => $index, OPERATOR => '@@', VALUE => 'plainto_tsquery('. $dbh->quote($value) .')', QUOTEVALUE => 0, ); } elsif ( $db_type eq 'mysql' and not $config->{Sphinx}) { my $dbh = $RT::Handle->dbh; $self->Limit( %rest, FUNCTION => "MATCH($alias.Content)", OPERATOR => 'AGAINST', VALUE => "(". $dbh->quote($value) ." IN BOOLEAN MODE)", QUOTEVALUE => 0, ); # As with Oracle, above, this forces the LEFT JOINs into # JOINS, which allows the FULLTEXT index to be used. # Orthogonally, the IS NOT NULL clause also helps the # optimizer decide to use the index. $self->Limit( ENTRYAGGREGATOR => 'AND', ALIAS => $alias, FIELD => "Content", OPERATOR => 'IS NOT', VALUE => 'NULL', QUOTEVALUE => 0, ); } elsif ( $db_type eq 'mysql' ) { # This is a special character. Note that \ does not escape # itself (in Sphinx 2.1.0, at least), so 'foo\;bar' becoming # 'foo\\;bar' is not a vulnerability, and is still parsed as # "foo, \, ;, then bar". Happily, the default mode is # "all", meaning that boolean operators are not special. $value =~ s/;/\\;/g; my $max = $config->{'MaxMatches'}; $self->Limit( %rest, ALIAS => $alias, FIELD => 'query', OPERATOR => '=', VALUE => "$value;limit=$max;maxmatches=$max", ); } } else { # This is the main difference from ticket content search. # For transaction searches, it probably worths keeping emails. # $self->Limit( # %rest, # FIELD => 'Type', # OPERATOR => 'NOT IN', # VALUE => ['EmailRecord', 'CommentEmailRecord'], # ); $self->Limit( ENTRYAGGREGATOR => 'AND', ALIAS => $self->{_sql_aliases}{attach}, FIELD => $field, OPERATOR => $op, VALUE => $value, CASESENSITIVE => 0, ); } if ( RT->Config->Get('DontSearchFileAttachments') ) { $self->Limit( ENTRYAGGREGATOR => 'AND', ALIAS => $self->{_sql_aliases}{attach}, FIELD => 'Filename', OPERATOR => 'IS', VALUE => 'NULL', ); } $self->_CloseParen; } sub _TicketLimit { my ( $self, $field, $op, $value, %rest ) = @_; $field =~ s!^Ticket!!; if ( $field eq 'Queue' && $value =~ /\D/ ) { my $queue = RT::Queue->new($self->CurrentUser); $queue->Load($value); $value = $queue->id if $queue->id; } if ( $field =~ /^(?:Owner|Creator)$/ && $value =~ /\D/ ) { my $user = RT::User->new( $self->CurrentUser ); $user->Load($value); $value = $user->id if $user->id; } $self->Limit( %rest, ALIAS => $self->_JoinTickets, FIELD => $field, OPERATOR => $op, VALUE => $value, CASESENSITIVE => 0, ); } sub _TicketQueueLimit { my ( $self, $field, $op, $value, %rest ) = @_; $field =~ s!^TicketQueue!!; my $queue = $self->{_sql_aliases}{ticket_queues} ||= $_[0]->Join( ALIAS1 => $self->_JoinTickets, FIELD1 => 'Queue', TABLE2 => 'Queues', FIELD2 => 'id', ); $self->Limit( ALIAS => $queue, FIELD => $field, OPERATOR => $op, VALUE => $value, %rest, ); } sub PrepForSerialization { my $self = shift; delete $self->{'items'}; delete $self->{'items_array'}; $self->RedoSearch(); } sub _OpenParen { $_[0]->SUPER::_OpenParen( $_[1] || 'txnsql' ); } sub _CloseParen { $_[0]->SUPER::_CloseParen( $_[1] || 'txnsql' ); } sub Limit { my $self = shift; my %args = @_; $self->{'must_redo_search'} = 1; delete $self->{'raw_rows'}; delete $self->{'count_all'}; $args{SUBCLAUSE} ||= "txnsql" if $self->{parsing_txnsql} and not $args{LEFTJOIN}; $self->SUPER::Limit(%args); } =head2 FromSQL Convert a RT-SQL string into a set of SearchBuilder restrictions. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =cut sub _parser { my ($self,$string) = @_; require RT::Interface::Web::QueryBuilder::Tree; my $tree = RT::Interface::Web::QueryBuilder::Tree->new; my @results = $tree->ParseSQL( Query => $string, CurrentUser => $self->CurrentUser, Class => ref $self || $self, ); die join "; ", map { ref $_ eq 'ARRAY' ? $_->[ 0 ] : $_ } @results if @results; # To handle __Active__ and __InActive__ statuses, copied from # RT::Tickets::_parser with field name updates, i.e. # Lifecycle => TicketQueueLifecycle # Status => TicketStatus my ( $active_status_node, $inactive_status_node ); my $escape_quotes = sub { my $text = shift; $text =~ s{(['\\])}{\\$1}g; return $text; }; $tree->traverse( sub { my $node = shift; return unless $node->isLeaf and $node->getNodeValue; my ($key, $subkey, $meta, $op, $value, $bundle) = @{$node->getNodeValue}{qw/Key Subkey Meta Op Value Bundle/}; return unless $key eq "TicketStatus" && $value =~ /^(?:__(?:in)?active__)$/i; my $parent = $node->getParent; my $index = $node->getIndex; if ( ( lc $value eq '__inactive__' && $op eq '=' ) || ( lc $value eq '__active__' && $op eq '!=' ) ) { unless ( $inactive_status_node ) { my %lifecycle = map { $_ => $RT::Lifecycle::LIFECYCLES{ $_ }{ inactive } } grep { @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ inactive } || [] } } grep { $RT::Lifecycle::LIFECYCLES_CACHE{ $_ }{ type } eq 'ticket' } keys %RT::Lifecycle::LIFECYCLES; return unless %lifecycle; my $sql; if ( keys %lifecycle == 1 ) { $sql = join ' OR ', map { qq{ TicketStatus = '$_' } } map { $escape_quotes->($_) } map { @$_ } values %lifecycle; } else { my @inactive_sql; for my $name ( keys %lifecycle ) { my $escaped_name = $escape_quotes->($name); my $inactive_sql = qq{TicketQueueLifecycle = '$escaped_name'} . ' AND (' . join( ' OR ', map { qq{ TicketStatus = '$_' } } map { $escape_quotes->($_) } @{ $lifecycle{ $name } } ) . ')'; push @inactive_sql, qq{($inactive_sql)}; } $sql = join ' OR ', @inactive_sql; } $inactive_status_node = RT::Interface::Web::QueryBuilder::Tree->new; $inactive_status_node->ParseSQL( Class => ref $self, Query => $sql, CurrentUser => $self->CurrentUser, ); } $parent->removeChild( $node ); $parent->insertChild( $index, $inactive_status_node ); } else { unless ( $active_status_node ) { my %lifecycle = map { $_ => [ @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ initial } || [] }, @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ active } || [] }, ] } grep { @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ initial } || [] } || @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ active } || [] } } grep { $RT::Lifecycle::LIFECYCLES_CACHE{ $_ }{ type } eq 'ticket' } keys %RT::Lifecycle::LIFECYCLES; return unless %lifecycle; my $sql; if ( keys %lifecycle == 1 ) { $sql = join ' OR ', map { qq{ TicketStatus = '$_' } } map { $escape_quotes->($_) } map { @$_ } values %lifecycle; } else { my @active_sql; for my $name ( keys %lifecycle ) { my $escaped_name = $escape_quotes->($name); my $active_sql = qq{TicketQueueLifecycle = '$escaped_name'} . ' AND (' . join( ' OR ', map { qq{ TicketStatus = '$_' } } map { $escape_quotes->($_) } @{ $lifecycle{ $name } } ) . ')'; push @active_sql, qq{($active_sql)}; } $sql = join ' OR ', @active_sql; } $active_status_node = RT::Interface::Web::QueryBuilder::Tree->new; $active_status_node->ParseSQL( Class => ref $self, Query => $sql, CurrentUser => $self->CurrentUser, ); } $parent->removeChild( $node ); $parent->insertChild( $index, $active_status_node ); } } ); if ( RT->Config->Get('EnablePriorityAsString') ) { my $queues = $tree->GetReferencedQueues( CurrentUser => $self->CurrentUser ); my %config = RT->Config->Get('PriorityAsString'); my @names; if (%$queues) { for my $id ( keys %$queues ) { my $queue = RT::Queue->new( $self->CurrentUser ); $queue->Load($id); if ( $queue->Id ) { push @names, $queue->__Value('Name'); # Skip ACL check } } } else { @names = keys %config; } my %map; for my $name (@names) { if ( my $value = exists $config{$name} ? $config{$name} : $config{Default} ) { my %hash = ref $value eq 'ARRAY' ? @$value : %$value; for my $label ( keys %hash ) { $map{lc $label} //= $hash{$label}; } } } $tree->traverse( sub { my $node = shift; return unless $node->isLeaf; my $value = $node->getNodeValue; if ( $value->{Key} =~ /^Ticket(?:Initial|Final)?Priority$/i ) { $value->{Value} = $map{ lc $value->{Value} } if defined $map{ lc $value->{Value} }; } } ); } my $ea = ''; $tree->traverse( sub { my $node = shift; $ea = $node->getParent->getNodeValue if $node->getIndex > 0; return $self->_OpenParen unless $node->isLeaf; my ($key, $subkey, $meta, $op, $value, $bundle) = @{$node->getNodeValue}{qw/Key Subkey Meta Op Value Bundle/}; # normalize key and get class (type) my $class = $meta->[0]; # replace __CurrentUser__ with id $value = $self->CurrentUser->id if $value eq '__CurrentUser__'; # replace __CurrentUserName__ with the username $value = $self->CurrentUser->Name if $value eq '__CurrentUserName__'; my $sub = $dispatch{ $class } or die "No dispatch method for class '$class'"; # A reference to @res may be pushed onto $sub_tree{$key} from # above, and we fill it here. $sub->( $self, $key, $op, $value, ENTRYAGGREGATOR => $ea, SUBKEY => $subkey, BUNDLE => $bundle, ); }, sub { my $node = shift; return $self->_CloseParen unless $node->isLeaf; } ); } sub FromSQL { my ($self,$query) = @_; $self->CleanSlate; $self->_InitSQL; return (1, $self->loc("No Query")) unless $query; $self->{_sql_query} = $query; eval { local $self->{parsing_txnsql} = 1; $self->_parser( $query ); }; if ( $@ ) { my $error = "$@"; $RT::Logger->error("Couldn't parse query: $error"); return (0, $error); } # set SB's dirty flag $self->{'must_redo_search'} = 1; return (1, $self->loc("Valid Query")); } sub _JoinTickets { my $self = shift; unless ( defined $self->{_sql_aliases}{tickets} ) { $self->{_sql_aliases}{tickets} = $self->Join( TYPE => 'LEFT', FIELD1 => 'ObjectId', TABLE2 => 'Tickets', FIELD2 => 'id', ); } return $self->{_sql_aliases}{tickets}; } =head2 Query Returns the last string passed to L</FromSQL>. =cut sub Query { my $self = shift; return $self->{_sql_query}; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������rt-5.0.1/lib/RT/Transaction.pm����������������������������������������������������������������������000644 �000765 �000024 �00000202304 14005011336 016720� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Transaction - RT's transaction object =head1 SYNOPSIS use RT::Transaction; =head1 DESCRIPTION Each RT::Transaction describes an atomic change to a ticket object or an update to an RT::Ticket object. It can have arbitrary MIME attachments. =head1 METHODS =cut package RT::Transaction; use base 'RT::Record'; use strict; use warnings; use vars qw( %_BriefDescriptions $PreferredContentType ); use RT::Attachments; use RT::Scrips; use RT::Ruleset; use HTML::FormatText::WithLinks::AndTables; use HTML::Scrubber; # For EscapeHTML() and decode_entities() require RT::Interface::Web; require HTML::Entities; sub Table {'Transactions'} # {{{ sub Create =head2 Create Create a new transaction. This routine should _never_ be called by anything other than RT::Ticket. It should not be called from client code. Ever. Not ever. If you do this, we will hunt you down and break your kneecaps. Then the unpleasant stuff will start. TODO: Document what gets passed to this =cut sub Create { my $self = shift; my %args = ( id => undef, TimeTaken => 0, Type => 'undefined', Data => '', Field => undef, OldValue => undef, NewValue => undef, MIMEObj => undef, ActivateScrips => 1, DryRun => undef, ObjectType => 'RT::Ticket', ObjectId => 0, ReferenceType => undef, OldReference => undef, NewReference => undef, SquelchMailTo => undef, @_ ); $args{ObjectId} ||= $args{Ticket}; #if we didn't specify a ticket, we need to bail unless ( $args{'ObjectId'} && $args{'ObjectType'}) { return ( 0, $self->loc( "Transaction->Create couldn't, as you didn't specify an object type and id")); } #lets create our transaction my %params = ( Type => $args{'Type'}, Data => $args{'Data'}, Field => $args{'Field'}, OldValue => $args{'OldValue'}, NewValue => $args{'NewValue'}, Created => $args{'Created'}, ObjectType => $args{'ObjectType'}, ObjectId => $args{'ObjectId'}, ReferenceType => $args{'ReferenceType'}, OldReference => $args{'OldReference'}, NewReference => $args{'NewReference'}, ); # Parameters passed in during an import that we probably don't want to touch, otherwise foreach my $attr (qw(id Creator Created LastUpdated TimeTaken LastUpdatedBy)) { $params{$attr} = $args{$attr} if ($args{$attr}); } my $id = $self->SUPER::Create(%params); $self->Load($id); if ( defined $args{'MIMEObj'} ) { my ($id, $msg) = $self->_Attach( $args{'MIMEObj'} ); unless ( $id ) { $RT::Logger->error("Couldn't add attachment: $msg"); return ( 0, $self->loc("Couldn't add attachment") ); } } $self->AddAttribute( Name => 'SquelchMailTo', Content => RT::User->CanonicalizeEmailAddress($_) ) for @{$args{'SquelchMailTo'} || []}; my @return = ( $id, $self->loc("Transaction Created") ); return @return unless $args{'ObjectType'} eq 'RT::Ticket'; # Provide a way to turn off scrips if we need to unless ( $args{'ActivateScrips'} ) { $RT::Logger->debug('Skipping scrips for transaction #' .$self->Id); return @return; } push @{$args{DryRun}}, $self if $args{DryRun}; $self->{'scrips'} = RT::Scrips->new(RT->SystemUser); $RT::Logger->debug('About to prepare scrips for transaction #' .$self->Id); $self->{'scrips'}->Prepare( Stage => 'TransactionCreate', Type => $args{'Type'}, Ticket => $args{'ObjectId'}, Transaction => $self->id, ); # Entry point of the rule system my $ticket = RT::Ticket->new(RT->SystemUser); $ticket->Load($args{'ObjectId'}); my $txn = RT::Transaction->new($RT::SystemUser); $txn->Load($self->id); my $rules = $self->{rules} = RT::Ruleset->FindAllRules( Stage => 'TransactionCreate', Type => $args{'Type'}, TicketObj => $ticket, TransactionObj => $txn, ); unless ($args{DryRun} ) { $RT::Logger->debug('About to commit scrips for transaction #' .$self->Id); $self->{'scrips'}->Commit(); RT::Ruleset->CommitRules($rules); } return @return; } =head2 Scrips Returns the Scrips object for this transaction. This routine is only useful on a freshly created transaction object. Scrips do not get persisted to the database with transactions. =cut sub Scrips { my $self = shift; return($self->{'scrips'}); } =head2 Rules Returns the array of Rule objects for this transaction. This routine is only useful on a freshly created transaction object. Rules do not get persisted to the database with transactions. =cut sub Rules { my $self = shift; return($self->{'rules'}); } =head2 Delete Delete this transaction. Currently DOES NOT CHECK ACLS =cut sub Delete { my $self = shift; $RT::Handle->BeginTransaction(); my $attachments = $self->Attachments; while (my $attachment = $attachments->Next) { my ($id, $msg) = $attachment->Delete(); unless ($id) { $RT::Handle->Rollback(); return($id, $self->loc("System Error: [_1]", $msg)); } } my ($id,$msg) = $self->SUPER::Delete(); unless ($id) { $RT::Handle->Rollback(); return($id, $self->loc("System Error: [_1]", $msg)); } $RT::Handle->Commit(); return ($id,$msg); } =head2 Message Returns the L<RT::Attachments> object which contains the "top-level" object attachment for this transaction. =cut sub Message { my $self = shift; # XXX: Where is ACL check? unless ( defined $self->{'message'} ) { $self->{'message'} = RT::Attachments->new( $self->CurrentUser ); $self->{'message'}->Limit( FIELD => 'TransactionId', VALUE => $self->Id ); $self->{'message'}->ChildrenOf(0); } else { $self->{'message'}->GotoFirstItem; } return $self->{'message'}; } =head2 HasContent Returns whether this transaction has attached mime objects. =cut sub HasContent { my $self = shift; my $type = $PreferredContentType || ''; return !!$self->ContentObj( $type ? ( Type => $type) : () ); } =head2 Content PARAMHASH If this transaction has attached mime objects, returns the body of the first textual part (as defined in RT::I18N::IsTextualContentType). Otherwise, returns the message "This transaction appears to have no content". Takes a paramhash. If the $args{'Quote'} parameter is set, wraps this message at $args{'Wrap'}. $args{'Wrap'} is set from the $QuoteWrapWidth config variable. If $args{'Type'} is set to C<text/html>, this will return an HTML part of the message, if available. Otherwise it looks for a text/plain part. If $args{'Type'} is missing, it defaults to the value of C<$RT::Transaction::PreferredContentType>, if that's missing too, defaults to textual. =cut sub Content { my $self = shift; my %args = ( Type => $PreferredContentType || '', Quote => 0, Wrap => RT->Config->Get('QuoteWrapWidth') || 70, @_ ); my $content; if ( my $content_obj = $self->ContentObj( $args{Type} ? ( Type => $args{Type}) : () ) ) { $content = $content_obj->Content ||''; if ( lc $content_obj->ContentType eq 'text/html' ) { $content =~ s/(?:(<\/div>)|<p>|<br\s*\/?>|<div(\s+class="[^"]+")?>)\s*--\s+<br\s*\/?>.*?$/$1/s if $args{'Quote'}; if ($args{Type} ne 'text/html') { $content = RT::Interface::Email::ConvertHTMLToText($content); } else { # Scrub out <html>, <head>, <meta>, and <body>, and # leave all else untouched. my $scrubber = HTML::Scrubber->new(); $scrubber->rules( html => 0, head => 0, meta => 0, body => 0, ); $scrubber->default( 1 => { '*' => 1 } ); $content = $scrubber->scrub( $content ); } } else { $content =~ s/\n-- \n.*?$//s if $args{'Quote'}; if ($args{Type} eq 'text/html') { # Extremely simple text->html converter $content =~ s/&/&/g; $content =~ s/</</g; $content =~ s/>/>/g; $content = qq|<pre style="white-space: pre-wrap; font-family: monospace;">$content</pre>|; } } } # If all else fails, return a message that we couldn't find any content else { $content = $self->loc('This transaction appears to have no content'); } if ( $args{'Quote'} ) { if ($args{Type} eq 'text/html') { $content = '<div class="gmail_quote">' . $self->QuoteHeader . '<br /><blockquote class="gmail_quote" type="cite">' . $content . '</blockquote></div>'; } else { $content = $self->ApplyQuoteWrap(content => $content, cols => $args{'Wrap'} ); $content = $self->QuoteHeader . "\n$content"; } } return ($content); } =head2 QuoteHeader Returns text prepended to content when transaction is quoted (see C<Quote> argument in L</Content>). By default returns localized "On <date> <user name> wrote:\n". =cut sub QuoteHeader { my $self = shift; return $self->loc("On [_1], [_2] wrote:", $self->CreatedAsString, $self->CreatorObj->Name); } =head2 ApplyQuoteWrap PARAMHASH Wrapper to calculate wrap criteria and apply quote wrapping if needed. =cut sub ApplyQuoteWrap { my $self = shift; my %args = @_; my $content = $args{content}; # What's the longest line like? my $max = 0; foreach ( split ( /\n/, $args{content} ) ) { $max = length if length > $max; } # the addition of 6 here accounts for the extra characters added when quoting # previously quoted text. if ( $max > $args{cols} + 6 ) { require Text::Quoted; require Text::Wrapper; my $structure = Text::Quoted::extract($args{content}); $content = $self->QuoteWrap(content_ref => $structure, cols => $args{cols}, max => $max ); } $content =~ s/^/> /gm; # use regex since string might be multi-line return $content; } =head2 QuoteWrap PARAMHASH Wrap the contents of transactions based on Wrap settings, maintaining the quote character from the original. =cut sub QuoteWrap { my $self = shift; my %args = @_; my $ref = $args{content_ref}; my $final_string; if ( ref $ref eq 'ARRAY' ){ foreach my $array (@$ref){ $final_string .= $self->QuoteWrap(content_ref => $array, cols => $args{cols}, max => $args{max} ); } } elsif ( ref $ref eq 'HASH' ){ return $ref->{quoter} . "\n" if $ref->{empty}; # Blank line my $col = $args{cols} - (length $ref->{quoter}); my $wrapper = Text::Wrapper->new( columns => $col ); # Wrap on individual lines to honor incoming line breaks # Otherwise deliberate separate lines (like a list or a sig) # all get combined incorrectly into single paragraphs. my @lines = split /\n/, $ref->{text}; my $wrap = join '', map { $wrapper->wrap($_) } @lines; my $quoter = $ref->{quoter}; # Only add the space if actually quoting $quoter .= ' ' if length $quoter; $wrap =~ s/^/$quoter/mg; # use regex since string might be multi-line return $wrap; } else{ $RT::Logger->warning("Can't apply quoting with $ref"); return; } return $final_string; } =head2 Addresses Returns a hashref of addresses related to this transaction. See L<RT::Attachment/Addresses> for details. =cut sub Addresses { my $self = shift; if (my $attach = $self->Attachments->First) { return $attach->Addresses; } else { return {}; } } =head2 ContentObj Returns the RT::Attachment object which contains the content for this Transaction =cut sub ContentObj { my $self = shift; my %args = ( Type => $PreferredContentType, Attachment => undef, @_ ); # If we don't have any content, return undef now. # Get the set of toplevel attachments to this transaction. my $Attachment = $args{'Attachment'}; $Attachment ||= $self->Attachments->First; return undef unless ($Attachment); my $Attachments = $self->Attachments; while ( my $Attachment = $Attachments->Next ) { if ( my $content = _FindPreferredContentObj( %args, Attachment => $Attachment ) ) { return $content; } } # If that fails, return the first top-level textual part which has some content. # We probably really want this to become "recurse, looking for the other type of # displayable". For now, this maintains backcompat my $all_parts = $self->Attachments; while ( my $part = $all_parts->Next ) { next unless _IsDisplayableTextualContentType($part->ContentType) && $part->Content; return $part; } return; } sub _FindPreferredContentObj { my %args = @_; my $Attachment = $args{Attachment}; # If we don't have any content, return undef now. return undef unless $Attachment; # If it's a textual part, just return the body. if ( _IsDisplayableTextualContentType($Attachment->ContentType) ) { return ($Attachment); } # If it's a multipart object, first try returning the first part with preferred # MIME type ('text/plain' by default). elsif ( $Attachment->ContentType =~ m|^multipart/mixed|i ) { my $kids = $Attachment->Children; while (my $child = $kids->Next) { my $ret = _FindPreferredContentObj(%args, Attachment => $child); return $ret if ($ret); } } elsif ( $Attachment->ContentType =~ m|^multipart/|i ) { if ( $args{Type} ) { my $plain_parts = $Attachment->Children; $plain_parts->ContentType( VALUE => $args{Type} ); $plain_parts->LimitNotEmpty; # If we actully found a part, return its content if ( my $first = $plain_parts->First ) { return $first; } } else { my $parts = $Attachment->Children; $parts->LimitNotEmpty; # If we actully found a part, return its content while (my $part = $parts->Next) { next unless _IsDisplayableTextualContentType($part->ContentType); return $part; } } } # If this is a message/rfc822 mail, we need to dig into it in order to find # the actual textual content elsif ( $Attachment->ContentType =~ '^message/rfc822' ) { my $children = $Attachment->Children; while ( my $child = $children->Next ) { if ( my $content = _FindPreferredContentObj( %args, Attachment => $child ) ) { return $content; } } } # We found no content. suck return (undef); } =head2 _IsDisplayableTextualContentType We may need to pull this out to another module later, but for now, this is better than RT::I18N::IsTextualContentType because that believes that a message/rfc822 email is displayable, despite it having no content =cut sub _IsDisplayableTextualContentType { my $type = shift; ($type =~ m{^text/(?:plain|html)\b}i) ? 1 : 0; } =head2 Subject If this transaction has attached mime objects, returns the first one's subject Otherwise, returns null =cut sub Subject { my $self = shift; return undef unless my $first = $self->Attachments->First; return $first->Subject; } =head2 Attachments Returns all the RT::Attachment objects which are attached to this transaction. Takes an optional parameter, which is a ContentType that Attachments should be restricted to. =cut sub Attachments { my $self = shift; if ( $self->{'attachments'} ) { $self->{'attachments'}->GotoFirstItem; return $self->{'attachments'}; } $self->{'attachments'} = RT::Attachments->new( $self->CurrentUser ); unless ( $self->CurrentUserCanSee ) { $self->{'attachments'}->Limit(FIELD => 'id', VALUE => '0', SUBCLAUSE => 'acl'); return $self->{'attachments'}; } $self->{'attachments'}->Limit( FIELD => 'TransactionId', VALUE => $self->Id ); # Get the self->{'attachments'} in the order they're put into # the database. Arguably, we should be returning a tree # of self->{'attachments'}, not a set...but no current app seems to need # it. $self->{'attachments'}->OrderBy( FIELD => 'id', ORDER => 'ASC' ); return $self->{'attachments'}; } =head2 _Attach A private method used to attach a mime object to this transaction. =cut sub _Attach { my $self = shift; my $MIMEObject = shift; unless ( defined $MIMEObject ) { $RT::Logger->error("We can't attach a mime object if you don't give us one."); return ( 0, $self->loc("[_1]: no attachment specified", $self) ); } my $Attachment = RT::Attachment->new( $self->CurrentUser ); my ($id, $msg) = $Attachment->Create( TransactionId => $self->Id, Attachment => $MIMEObject ); return ( $Attachment, $msg || $self->loc("Attachment created") ); } sub ContentAsMIME { my $self = shift; # RT::Attachments doesn't limit ACLs as strictly as RT::Transaction does # since it has less information available without looking to it's parent # transaction. Check ACLs here before we go any further. return unless $self->CurrentUserCanSee; my $attachments = RT::Attachments->new( $self->CurrentUser ); $attachments->OrderBy( FIELD => 'id', ORDER => 'ASC' ); $attachments->Limit( FIELD => 'TransactionId', VALUE => $self->id ); $attachments->Limit( FIELD => 'Parent', VALUE => 0 ); $attachments->RowsPerPage(1); my $top = $attachments->First; return unless $top; my $entity = MIME::Entity->build( Type => 'message/rfc822', Description => 'transaction ' . $self->id, Data => $top->ContentAsMIME(Children => 1)->as_string, ); return $entity; } =head2 Description Returns a text string which describes this transaction =cut sub Description { my $self = shift; unless ( $self->CurrentUserCanSee ) { return ( $self->loc("Permission Denied") ); } unless ( defined $self->Type ) { return ( $self->loc("No transaction type specified")); } return $self->loc("[_1] by [_2]", $self->BriefDescription , $self->CreatorObj->Name ); } =head2 BriefDescription Returns a text string which briefly describes this transaction =cut { my $scrubber = HTML::Scrubber->new(default => 0); # deny everything sub BriefDescription { my $self = shift; my $desc = $self->BriefDescriptionAsHTML; $desc = $scrubber->scrub($desc); $desc = HTML::Entities::decode_entities($desc); return $desc; } } =head2 BriefDescriptionAsHTML Returns an HTML string which briefly describes this transaction. =cut sub BriefDescriptionAsHTML { my $self = shift; unless ( $self->CurrentUserCanSee ) { return ( $self->loc("Permission Denied") ); } my ($objecttype, $type, $field) = ($self->ObjectType, $self->Type, $self->Field); unless ( defined $type ) { return $self->loc("No transaction type specified"); } my ($template, @params); my @code = grep { ref eq 'CODE' } map { $_BriefDescriptions{$_} } ( $field ? ("$objecttype-$type-$field", "$type-$field") : () ), "$objecttype-$type", $type; if (@code) { ($template, @params) = $code[0]->($self); } unless ($template) { ($template, @params) = ( "Default: [_1]/[_2] changed from [_3] to [_4]", #loc $type, $field, ( $self->OldValue ? "'" . $self->OldValue . "'" : $self->loc("(no value)") ), ( $self->NewValue ? "'" . $self->NewValue . "'" : $self->loc("(no value)") ), ); } return $self->loc($template, $self->_ProcessReturnValues(@params)); } sub _ProcessReturnValues { my $self = shift; my @values = @_; return map { if (ref eq 'ARRAY') { $_ = join "", $self->_ProcessReturnValues(@$_) } elsif (ref eq 'SCALAR') { $_ = $$_ } else { RT::Interface::Web::EscapeHTML(\$_) } $_ } @values; } sub _FormatPrincipal { my $self = shift; my $principal = shift; if ($principal->IsUser) { return $self->_FormatUser( $principal->Object ); } else { return $self->loc("group [_1]", $principal->Object->Name); } } sub _FormatUser { my $self = shift; my $user = shift; return [ \'<span class="user" data-replace="user" data-user-id="', $user->id, \'">', $user->Format, \'</span>' ]; } sub _CanonicalizeRoleName { my $self = shift; my $role_name = shift; if ($role_name =~ /^RT::CustomRole-(\d+)$/) { my $role = RT::CustomRole->new($self->CurrentUser); $role->Load($1); return $role->Name; } return $self->loc($role_name); } %_BriefDescriptions = ( Create => sub { my $self = shift; return ( "[_1] created", $self->FriendlyObjectType ); #loc() }, Enabled => sub { my $self = shift; return ( "[_1] enabled", $self->Field ? $self->loc($self->Field) : $self->FriendlyObjectType ); #loc() }, Disabled => sub { my $self = shift; return ( "[_1] disabled", $self->Field ? $self->loc($self->Field) : $self->FriendlyObjectType ); #loc() }, Status => sub { my $self = shift; if ( $self->Field eq 'Status' ) { if ( $self->NewValue eq 'deleted' ) { return ( "[_1] deleted", $self->FriendlyObjectType ); #loc() } else { my $canon = $self->Object->DOES("RT::Record::Role::Status") ? sub { $self->Object->LifecycleObj->CanonicalCase(@_) } : sub { return $_[0] }; return ( "Status changed from [_1] to [_2]", "'" . $self->loc( $canon->($self->OldValue) ) . "'", "'" . $self->loc( $canon->($self->NewValue) ) . "'" ); # loc() } } # Generic: my $no_value = $self->loc("(no value)"); return ( "[_1] changed from [_2] to [_3]", $self->Field, ( $self->OldValue ? "'" . $self->OldValue . "'" : $no_value ), "'" . $self->NewValue . "'" ); #loc() }, SystemError => sub { my $self = shift; return $self->Data // ("System error"); #loc() }, AttachmentTruncate => sub { my $self = shift; if ( defined $self->Data ) { return ( "File '[_1]' truncated because its size ([_2] bytes) exceeded configured maximum size setting ([_3] bytes).", $self->Data, $self->OldValue, $self->NewValue ); #loc() } else { return ( "Content truncated because its size ([_1] bytes) exceeded configured maximum size setting ([_2] bytes).", $self->OldValue, $self->NewValue ); #loc() } }, AttachmentDrop => sub { my $self = shift; if ( defined $self->Data ) { return ( "File '[_1]' dropped because its size ([_2] bytes) exceeded configured maximum size setting ([_3] bytes).", $self->Data, $self->OldValue, $self->NewValue ); #loc() } else { return ( "Content dropped because its size ([_1] bytes) exceeded configured maximum size setting ([_2] bytes).", $self->OldValue, $self->NewValue ); #loc() } }, AttachmentError => sub { my $self = shift; if ( defined $self->Data ) { return ( "File '[_1]' insert failed. See error log for details.", $self->Data ); #loc() } else { return ( "Content insert failed. See error log for details." ); #loc() } }, "Forward Transaction" => sub { my $self = shift; my $recipients = join ", ", map { RT::User->Format( Address => $_, CurrentUser => $self->CurrentUser ) } RT::EmailParser->ParseEmailAddress($self->Data); return ( "Forwarded [_3]Transaction #[_1][_4] to [_2]", $self->Field, $recipients, [\'<a href="#txn-', $self->Field, \'">'], \'</a>'); #loc() }, "Forward Ticket" => sub { my $self = shift; my $recipients = join ", ", map { RT::User->Format( Address => $_, CurrentUser => $self->CurrentUser ) } RT::EmailParser->ParseEmailAddress($self->Data); return ( "Forwarded Ticket to [_1]", $recipients ); #loc() }, CommentEmailRecord => sub { my $self = shift; return ("Outgoing email about a comment recorded"); #loc() }, EmailRecord => sub { my $self = shift; return ("Outgoing email recorded"); #loc() }, Correspond => sub { my $self = shift; return ("Correspondence added"); #loc() }, Comment => sub { my $self = shift; return ("Comments added"); #loc() }, CustomField => sub { my $self = shift; my $field = $self->loc('CustomField'); my $cf; if ( $self->Field ) { $cf = RT::CustomField->new( $self->CurrentUser ); $cf->SetContextObject( $self->Object ); $cf->Load( $self->Field ); $field = $cf->Name(); $field = $self->loc('a custom field') if !defined($field); } my $new = $self->NewValue; my $old = $self->OldValue; if ( $cf ) { if ( $cf->Type eq 'DateTime' ) { if ($old) { my $date = RT::Date->new( $self->CurrentUser ); $date->Set( Format => 'ISO', Value => $old ); $old = $date->AsString; } if ($new) { my $date = RT::Date->new( $self->CurrentUser ); $date->Set( Format => 'ISO', Value => $new ); $new = $date->AsString; } } elsif ( $cf->Type eq 'Date' ) { if ($old) { my $date = RT::Date->new( $self->CurrentUser ); $date->Set( Format => 'unknown', Value => $old, Timezone => 'UTC', ); $old = $date->AsString( Time => 0, Timezone => 'UTC' ); } if ($new) { my $date = RT::Date->new( $self->CurrentUser ); $date->Set( Format => 'unknown', Value => $new, Timezone => 'UTC', ); $new = $date->AsString( Time => 0, Timezone => 'UTC' ); } } elsif ( $cf->Type =~ /text/i) { if (!defined($old) || ($old eq '')) { return ( "[_1] added", $field); #loc() } if (!defined($new) || ($new eq '')) { return ( "[_1] deleted", $field); #loc() } else { return ( "[_1] changed", $field); #loc() } } } if ( !defined($old) || $old eq '' ) { return ("[_1] [_2] added", $field, $new); #loc() } elsif ( !defined($new) || $new eq '' ) { return ("[_1] [_2] deleted", $field, $old); #loc() } else { return ("[_1] [_2] changed to [_3]", $field, $old, $new); #loc() } }, Untake => sub { my $self = shift; return ("Untaken"); #loc() }, Take => sub { my $self = shift; return ("Taken"); #loc() }, Force => sub { my $self = shift; my $Old = RT::User->new( $self->CurrentUser ); $Old->Load( $self->OldValue ); my $New = RT::User->new( $self->CurrentUser ); $New->Load( $self->NewValue ); return ("Owner forcibly changed from [_1] to [_2]", map { $self->_FormatUser($_) } $Old, $New); #loc() }, Steal => sub { my $self = shift; my $Old = RT::User->new( $self->CurrentUser ); $Old->Load( $self->OldValue ); return ("Stolen from [_1]", $self->_FormatUser($Old)); #loc() }, Give => sub { my $self = shift; my $New = RT::User->new( $self->CurrentUser ); $New->Load( $self->NewValue ); return ( "Given to [_1]", $self->_FormatUser($New)); #loc() }, AddWatcher => sub { my $self = shift; my $principal = RT::Principal->new($self->CurrentUser); $principal->Load($self->NewValue); return ( "[_1] [_2] added", $self->_CanonicalizeRoleName($self->Field), $self->_FormatPrincipal($principal)); #loc() }, DelWatcher => sub { my $self = shift; my $principal = RT::Principal->new($self->CurrentUser); $principal->Load($self->OldValue); return ( "[_1] [_2] deleted", $self->_CanonicalizeRoleName($self->Field), $self->_FormatPrincipal($principal)); #loc() }, SetWatcher => sub { my $self = shift; my $principal = RT::Principal->new($self->CurrentUser); $principal->Load($self->NewValue); return ( "[_1] set to [_2]", $self->_CanonicalizeRoleName($self->Field), $self->_FormatPrincipal($principal)); #loc() }, Subject => sub { my $self = shift; return ( "Subject changed to [_1]", $self->Data ); #loc() }, AddLink => sub { my $self = shift; my $value; if ( $self->NewValue ) { my $URI = RT::URI->new( $self->CurrentUser ); if ( $URI->FromURI( $self->NewValue ) ) { $value = [ \'<a href="', $URI->AsHREF, \'">', $URI->AsString, \'</a>' ]; } else { $value = $self->NewValue; } if ( $self->Field eq 'DependsOn' ) { return ( "Dependency on [_1] added", $value ); #loc() } elsif ( $self->Field eq 'DependedOnBy' ) { return ( "Dependency by [_1] added", $value ); #loc() } elsif ( $self->Field eq 'RefersTo' ) { return ( "Reference to [_1] added", $value ); #loc() } elsif ( $self->Field eq 'ReferredToBy' ) { return ( "Reference by [_1] added", $value ); #loc() } elsif ( $self->Field eq 'MemberOf' ) { return ( "Membership in [_1] added", $value ); #loc() } elsif ( $self->Field eq 'HasMember' ) { return ( "Member [_1] added", $value ); #loc() } elsif ( $self->Field eq 'MergedInto' ) { return ( "Merged into [_1]", $value ); #loc() } } else { return ( "[_1]", $self->Data ); #loc() } }, DeleteLink => sub { my $self = shift; my $value; if ( $self->OldValue ) { my $URI = RT::URI->new( $self->CurrentUser ); if ( $URI->FromURI( $self->OldValue ) ) { $value = [ \'<a href="', $URI->AsHREF, \'">', $URI->AsString, \'</a>' ]; } else { $value = $self->OldValue; } if ( $self->Field eq 'DependsOn' ) { return ( "Dependency on [_1] deleted", $value ); #loc() } elsif ( $self->Field eq 'DependedOnBy' ) { return ( "Dependency by [_1] deleted", $value ); #loc() } elsif ( $self->Field eq 'RefersTo' ) { return ( "Reference to [_1] deleted", $value ); #loc() } elsif ( $self->Field eq 'ReferredToBy' ) { return ( "Reference by [_1] deleted", $value ); #loc() } elsif ( $self->Field eq 'MemberOf' ) { return ( "Membership in [_1] deleted", $value ); #loc() } elsif ( $self->Field eq 'HasMember' ) { return ( "Member [_1] deleted", $value ); #loc() } } else { return ( "[_1]", $self->Data ); #loc() } }, Told => sub { my $self = shift; if ( $self->Field eq 'Told' ) { my $t1 = RT::Date->new($self->CurrentUser); $t1->Set(Format => 'ISO', Value => $self->NewValue); my $t2 = RT::Date->new($self->CurrentUser); $t2->Set(Format => 'ISO', Value => $self->OldValue); return ( "[_1] changed from [_2] to [_3]", $self->loc($self->Field), $t2->AsString, $t1->AsString ); #loc() } else { return ( "[_1] changed from [_2] to [_3]", $self->loc($self->Field), ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'" ); #loc() } }, Set => sub { my $self = shift; if ( $self->Field eq 'Password' ) { return ('Password changed'); #loc() } elsif ( $self->Field eq 'Queue' ) { my $q1 = RT::Queue->new( $self->CurrentUser ); $q1->Load( $self->OldValue ); my $q2 = RT::Queue->new( $self->CurrentUser ); $q2->Load( $self->NewValue ); return ("[_1] changed from [_2] to [_3]", $self->loc($self->Field), $q1->Name // '#'.$q1->id, $q2->Name // '#'.$q2->id); #loc() } # Write the date/time change at local time: elsif ($self->Field =~ /^(?:Due|Starts|Started|Told)$/) { my $t1 = RT::Date->new($self->CurrentUser); $t1->Set(Format => 'ISO', Value => $self->NewValue); my $t2 = RT::Date->new($self->CurrentUser); $t2->Set(Format => 'ISO', Value => $self->OldValue); return ( "[_1] changed from [_2] to [_3]", $self->loc($self->Field), $t2->AsString, $t1->AsString ); #loc() } elsif ( $self->Field eq 'Owner' ) { my $Old = RT::User->new( $self->CurrentUser ); $Old->Load( $self->OldValue ); my $New = RT::User->new( $self->CurrentUser ); $New->Load( $self->NewValue ); if ( $Old->id == RT->Nobody->id ) { if ( $New->id == $self->Creator ) { return ("Taken"); #loc() } else { return ( "Given to [_1]", $self->_FormatUser($New) ); #loc() } } else { if ( $New->id == $self->Creator ) { return ("Stolen from [_1]", $self->_FormatUser($Old) ); #loc() } elsif ( $Old->id == $self->Creator ) { if ( $New->id == RT->Nobody->id ) { return ("Untaken"); #loc() } else { return ( "Given to [_1]", $self->_FormatUser($New) ); #loc() } } else { return ( "Owner forcibly changed from [_1] to [_2]", map { $self->_FormatUser($_) } $Old, $New ); #loc() } } } elsif ( $self->Field =~ /Priority/ && RT->Config->Get('EnablePriorityAsString') ) { my $object = $self->Object; my ( $old_value, $new_value ); if ( $object->isa('RT::Ticket') ) { $old_value = $object->_PriorityAsString( $self->OldValue ); $new_value = $object->_PriorityAsString( $self->NewValue ); $old_value = $self->loc($old_value) if $old_value; $new_value = $self->loc($new_value) if $new_value; } $old_value //= $self->OldValue; $new_value //= $self->NewValue; return ( "[_1] changed from [_2] to [_3]", $self->loc( $self->Field ), "'$old_value'", "'$new_value'" ); #loc() } else { return ( "[_1] changed from [_2] to [_3]", $self->loc($self->Field), ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")), ($self->NewValue? "'".$self->NewValue ."'" : $self->loc("(no value)"))); #loc() } }, "Set-TimeWorked" => sub { my $self = shift; my $old = $self->OldValue || 0; my $new = $self->NewValue || 0; my $duration = $new - $old; if ($duration < 0) { return ("Adjusted time worked by [quant,_1,minute,minutes]", $duration); # loc() } elsif ($duration < 60) { return ("Worked [quant,_1,minute,minutes]", $duration); # loc() } else { return ("Worked [quant,_1,hour,hours] ([quant,_2,minute,minutes])", sprintf("%.2f", $duration / 60), $duration); # loc() } }, PurgeTransaction => sub { my $self = shift; return ("Transaction [_1] purged", $self->Data); #loc() }, AddReminder => sub { my $self = shift; my $ticket = RT::Ticket->new($self->CurrentUser); $ticket->Load($self->NewValue); if ( $ticket->CurrentUserHasRight('ShowTicket') ) { my $subject = [ \'<a href="', RT->Config->Get('WebPath'), "/Ticket/Reminders.html?id=", $self->ObjectId, "#reminder-", $ticket->id, \'">', $ticket->Subject, \'</a>' ]; return ("Reminder '[_1]' added", $subject); #loc() } else { return ("Reminder added"); #loc() } }, OpenReminder => sub { my $self = shift; my $ticket = RT::Ticket->new($self->CurrentUser); $ticket->Load($self->NewValue); if ( $ticket->CurrentUserHasRight('ShowTicket') ) { my $subject = [ \'<a href="', RT->Config->Get('WebPath'), "/Ticket/Reminders.html?id=", $self->ObjectId, "#reminder-", $ticket->id, \'">', $ticket->Subject, \'</a>' ]; return ("Reminder '[_1]' reopened", $subject); #loc() } else { return ("Reminder reopened"); #loc() } }, ResolveReminder => sub { my $self = shift; my $ticket = RT::Ticket->new($self->CurrentUser); $ticket->Load($self->NewValue); if ( $ticket->CurrentUserHasRight('ShowTicket') ) { my $subject = [ \'<a href="', RT->Config->Get('WebPath'), "/Ticket/Reminders.html?id=", $self->ObjectId, "#reminder-", $ticket->id, \'">', $ticket->Subject, \'</a>' ]; return ("Reminder '[_1]' completed", $subject); #loc() } else { return ("Reminder completed"); #loc() } }, 'RT::Asset-Set-Catalog' => sub { my $self = shift; return ("[_1] changed from [_2] to [_3]", #loc $self->loc($self->Field), map { my $c = RT::Catalog->new($self->CurrentUser); $c->Load($_); $c->Name || $self->loc("~[a hidden catalog~]") } $self->OldValue, $self->NewValue); }, AddMember => sub { my $self = shift; my $principal = RT::Principal->new($self->CurrentUser); $principal->Load($self->Field); if ($principal->IsUser) { return ("Added user '[_1]'", $principal->Object->Name); #loc() } else { return ("Added group '[_1]'", $principal->Object->Name); #loc() } }, DeleteMember => sub { my $self = shift; my $principal = RT::Principal->new($self->CurrentUser); $principal->Load($self->Field); if ($principal->IsUser) { return ("Removed user '[_1]'", $principal->Object->Name); #loc() } else { return ("Removed group '[_1]'", $principal->Object->Name); #loc() } }, AddMembership => sub { my $self = shift; my $principal = RT::Principal->new($self->CurrentUser); $principal->Load($self->Field); return ("Added to group '[_1]'", $principal->Object->Name); #loc() }, DeleteMembership => sub { my $self = shift; my $principal = RT::Principal->new($self->CurrentUser); $principal->Load($self->Field); return ("Removed from group '[_1]'", $principal->Object->Name); #loc() }, Munge => sub { my $self = shift; return "Attachment content modified"; }, SetConfig => sub { my $self = shift; my ($new_value, $old_value); # pull in new value from reference if exists if ( $self->NewReference ) { my $newobj = RT::Configuration->new($self->CurrentUser); $newobj->Load($self->NewReference); $new_value = $newobj->Content; } # pull in old value from reference if exists if ( $self->OldReference ) { my $oldobj = RT::Configuration->new($self->CurrentUser); $oldobj->Load($self->OldReference); $old_value = $oldobj->Content; return ('[_1] changed from "[_2]" to "[_3]"', $self->Field, $old_value // '', $new_value // ''); #loc() } else { return ('[_1] changed to "[_2]"', $self->Field, $new_value // ''); #loc() } }, DeleteConfig => sub { my $self = shift; return ('[_1] deleted"', $self->Field); #loc() } ); =head2 IsInbound Returns true if the creator of the transaction is a requestor of the ticket. Returns false otherwise =cut sub IsInbound { my $self = shift; $self->ObjectType eq 'RT::Ticket' or return undef; return ( $self->TicketObj->IsRequestor( $self->CreatorObj->PrincipalId ) ); } sub _OverlayAccessible { { ObjectType => { public => 1}, ObjectId => { public => 1}, } }; sub _Set { my $self = shift; return ( 0, $self->loc('Transactions are immutable') ); } =head2 _Value Takes the name of a table column. Returns its value as a string, if the user passes an ACL check =cut sub _Value { my $self = shift; my $field = shift; #if the field is public, return it. if ( $self->_Accessible( $field, 'public' ) ) { return $self->SUPER::_Value( $field ); } unless ( $self->CurrentUserCanSee ) { return undef; } return $self->SUPER::_Value( $field ); } =head2 CurrentUserCanSee Returns true if current user has rights to see this particular transaction. This fact depends on type of the transaction, type of an object the transaction is attached to and may be other conditions, so this method is prefered over custom implementations. It always returns true if current user is system user. =cut sub CurrentUserCanSee { my $self = shift; return 1 if $self->CurrentUser->PrincipalObj->Id == RT->SystemUser->Id; # Make sure the user can see the custom field before showing that it changed my $type = $self->__Value('Type'); if ( $type eq 'CustomField' and my $cf_id = $self->__Value('Field') ) { my $cf = RT::CustomField->new( $self->CurrentUser ); $cf->SetContextObject( $self->Object ); $cf->Load( $cf_id ); return 0 unless $cf->CurrentUserCanSee; } # Transactions that might have changed the ->Object's visibility to # the current user are marked readable return 1 if $self->{ _object_is_readable }; # Defer to the object in question return $self->Object->CurrentUserCanSee("Transaction", $self); } sub Ticket { my $self = shift; return $self->ObjectId; } sub TicketObj { my $self = shift; return $self->Object; } sub OldValue { my $self = shift; if ( my $Object = $self->OldReferenceObject ) { return $Object->Content; } else { return $self->_Value('OldValue'); } } sub NewValue { my $self = shift; if ( my $Object = $self->NewReferenceObject ) { return $Object->Content; } else { return $self->_Value('NewValue'); } } sub Object { my $self = shift; my $Object = $self->__Value('ObjectType')->new($self->CurrentUser); $Object->Load($self->__Value('ObjectId')); return $Object; } =head2 NewReferenceObject =head2 OldReferenceObject Returns an object of the class specified by the column C<ReferenceType> and loaded with the id specified by the column C<NewReference> or C<OldReference>. C<ReferenceType> is assumed to be an L<RT::Record> subclass. The object may be unloaded (check C<< $object->id >>) if the reference is corrupt (such as if the referenced record was improperly deleted). Returns undef if either C<ReferenceType> or C<NewReference>/C<OldReference> is false. =cut sub NewReferenceObject { $_[0]->_ReferenceObject("New") } sub OldReferenceObject { $_[0]->_ReferenceObject("Old") } sub _ReferenceObject { my $self = shift; my $which = shift; my $type = $self->__Value("ReferenceType"); my $id = $self->__Value("${which}Reference"); return unless $type and $id; my $object = $type->new($self->CurrentUser); $object->Load( $id ); return $object; } sub FriendlyObjectType { my $self = shift; return $self->loc( $self->Object->RecordType ); } =head2 UpdateCustomFields Takes a hash of: CustomField-C<Id> => Value or: Object-RT::Transaction-CustomField-C<Id> => Value parameters to update this transaction's custom fields. =cut sub UpdateCustomFields { my $self = shift; my %args = @_; foreach my $arg ( keys %args ) { next unless ( $arg =~ /^(?:Object-RT::Transaction--)?CustomField-(\d+)/ ); next if $arg =~ /-Magic$/; my $cfid = $1; my $values = $args{$arg}; my $cf = $self->LoadCustomFieldByIdentifier($cfid); next unless $cf->ObjectTypeFromLookupType($cf->__Value('LookupType'))->isa(ref $self); foreach my $value ( UNIVERSAL::isa( $values, 'ARRAY' ) ? @$values : $values ) { next if $self->CustomFieldValueIsEmpty( Field => $cf, Value => $value, ); $self->_AddCustomFieldValue( Field => $cfid, Value => $value, RecordTransaction => 0, ); } } $self->AddCustomFieldDefaultValues; } =head2 LoadCustomFieldByIdentifier Finds and returns the custom field of the given name for the transaction, overriding L<RT::Record/LoadCustomFieldByIdentifier> to look for queue-specific CFs before global ones. =cut sub LoadCustomFieldByIdentifier { my $self = shift; my $field = shift; return $self->SUPER::LoadCustomFieldByIdentifier($field) if ref $field or $field =~ /^\d+$/; return $self->SUPER::LoadCustomFieldByIdentifier($field) unless UNIVERSAL::can( $self->Object, 'QueueObj' ); my $CFs = RT::CustomFields->new( $self->CurrentUser ); $CFs->SetContextObject( $self->Object ); $CFs->Limit( FIELD => 'Name', VALUE => $field, CASESENSITIVE => 0 ); $CFs->LimitToLookupType($self->CustomFieldLookupType); $CFs->LimitToGlobalOrObjectId($self->Object->QueueObj->id); return $CFs->First || RT::CustomField->new( $self->CurrentUser ); } =head2 CustomFieldLookupType Returns the RT::Transaction lookup type, which can be passed to RT::CustomField->Create() via the 'LookupType' hash key. =cut sub CustomFieldLookupType { "RT::Queue-RT::Ticket-RT::Transaction"; } =head2 SquelchMailTo Similar to Ticket class SquelchMailTo method - returns a list of transaction's squelched addresses. As transactions are immutable, the list of squelched recipients cannot be modified after creation. =cut sub SquelchMailTo { my $self = shift; return () unless $self->CurrentUserCanSee; return $self->Attributes->Named('SquelchMailTo'); } =head2 Recipients Returns the list of email addresses (as L<Email::Address> objects) that this transaction would send mail to. There may be duplicates. =cut sub Recipients { my $self = shift; my @recipients; foreach my $scrip ( @{ $self->Scrips->Prepared } ) { my $action = $scrip->ActionObj->Action; next unless $action->isa('RT::Action::SendEmail'); foreach my $type (qw(To Cc Bcc)) { push @recipients, $action->$type(); } } if ( $self->Rules ) { for my $rule (@{$self->Rules}) { next unless $rule->{hints} && $rule->{hints}{class} eq 'SendEmail'; my $data = $rule->{hints}{recipients}; foreach my $type (qw(To Cc Bcc)) { push @recipients, map {Email::Address->new($_)} @{$data->{$type}}; } } } return @recipients; } =head2 DeferredRecipients($freq, $include_sent ) Takes the following arguments: =over =item * a string to indicate the frequency of digest delivery. Valid values are "daily", "weekly", or "susp". =item * an optional argument which, if true, will return addresses even if this notification has been marked as 'sent' for this transaction. =back Returns an array of users who should now receive the notification that was recorded in this transaction. Returns an empty array if there were no deferred users, or if $include_sent was not specified and the deferred notifications have been sent. =cut sub DeferredRecipients { my $self = shift; my $freq = shift; my $include_sent = @_? shift : 0; my $attr = $self->FirstAttribute('DeferredRecipients'); return () unless ($attr); my $deferred = $attr->Content; return () unless ( ref($deferred) eq 'HASH' && exists $deferred->{$freq} ); # Skip it. for my $user (keys %{$deferred->{$freq}}) { if ($deferred->{$freq}->{$user}->{_sent} && !$include_sent) { delete $deferred->{$freq}->{$user} } } # Now get our users. Easy. return keys %{ $deferred->{$freq} }; } # Transactions don't change. by adding this cache config directive, we don't lose pathalogically on long tickets. sub _CacheConfig { { 'cache_for_sec' => 6000, } } =head2 ACLEquivalenceObjects This method returns a list of objects for which a user's rights also apply to this Transaction. This currently only applies to Transaction Custom Fields on Tickets, so we return the Ticket's Queue and the Ticket. This method is called from L<RT::Principal/HasRight>. =cut sub ACLEquivalenceObjects { my $self = shift; return unless $self->ObjectType eq 'RT::Ticket'; my $object = $self->Object; return $object,$object->QueueObj; } =head2 id Returns the current value of id. (In the database, id is stored as int(19).) =cut =head2 ObjectType Returns the current value of ObjectType. (In the database, ObjectType is stored as varchar(64).) =head2 SetObjectType VALUE Set ObjectType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectType will be stored as a varchar(64).) =cut =head2 ObjectId Returns the current value of ObjectId. (In the database, ObjectId is stored as int(11).) =head2 SetObjectId VALUE Set ObjectId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectId will be stored as a int(11).) =cut =head2 TimeTaken Returns the current value of TimeTaken. (In the database, TimeTaken is stored as int(11).) =head2 SetTimeTaken VALUE Set TimeTaken to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, TimeTaken will be stored as a int(11).) =cut =head2 Type Returns the current value of Type. (In the database, Type is stored as varchar(20).) =head2 SetType VALUE Set Type to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Type will be stored as a varchar(20).) =cut =head2 Field Returns the current value of Field. (In the database, Field is stored as varchar(40).) =head2 SetField VALUE Set Field to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Field will be stored as a varchar(40).) =cut =head2 OldValue Returns the current value of OldValue. (In the database, OldValue is stored as varchar(255).) =head2 SetOldValue VALUE Set OldValue to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, OldValue will be stored as a varchar(255).) =cut =head2 NewValue Returns the current value of NewValue. (In the database, NewValue is stored as varchar(255).) =head2 SetNewValue VALUE Set NewValue to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, NewValue will be stored as a varchar(255).) =cut =head2 ReferenceType Returns the current value of ReferenceType. (In the database, ReferenceType is stored as varchar(255).) =head2 SetReferenceType VALUE Set ReferenceType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ReferenceType will be stored as a varchar(255).) =cut =head2 OldReference Returns the current value of OldReference. (In the database, OldReference is stored as int(11).) =head2 SetOldReference VALUE Set OldReference to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, OldReference will be stored as a int(11).) =cut =head2 NewReference Returns the current value of NewReference. (In the database, NewReference is stored as int(11).) =head2 SetNewReference VALUE Set NewReference to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, NewReference will be stored as a int(11).) =cut =head2 Data Returns the current value of Data. (In the database, Data is stored as varchar(255).) =head2 SetData VALUE Set Data to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Data will be stored as a varchar(255).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(19)', default => ''}, ObjectType => {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''}, ObjectId => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, TimeTaken => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Type => {read => 1, write => 1, sql_type => 12, length => 20, is_blob => 0, is_numeric => 0, type => 'varchar(20)', default => ''}, Field => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, OldValue => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, NewValue => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, ReferenceType => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, OldReference => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, NewReference => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Data => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); $deps->Add( out => $self->Object ); $deps->Add( in => $self->Attachments ); my $type = $self->Type; if ($type eq "CustomField") { my $cf = RT::CustomField->new( RT->SystemUser ); $cf->Load( $self->Field ); $deps->Add( out => $cf ); } elsif ($type =~ /^(Take|Untake|Force|Steal|Give)$/) { for my $field (qw/OldValue NewValue/) { my $user = RT::User->new( RT->SystemUser ); $user->Load( $self->$field ); $deps->Add( out => $user ); } } elsif ($type eq "DelWatcher") { my $principal = RT::Principal->new( RT->SystemUser ); $principal->Load( $self->OldValue ); $deps->Add( out => $principal->Object ); } elsif ($type eq "AddWatcher") { my $principal = RT::Principal->new( RT->SystemUser ); $principal->Load( $self->NewValue ); $deps->Add( out => $principal->Object ); } elsif ($type eq "DeleteLink") { if ($self->OldValue) { my $base = RT::URI->new( $self->CurrentUser ); $base->FromURI( $self->OldValue ); $deps->Add( out => $base->Object ) if $base->Resolver and $base->Object; } } elsif ($type eq "AddLink") { if ($self->NewValue) { my $base = RT::URI->new( $self->CurrentUser ); $base->FromURI( $self->NewValue ); $deps->Add( out => $base->Object ) if $base->Resolver and $base->Object; } } elsif ($type eq "Set" and $self->Field eq "Queue") { for my $field (qw/OldValue NewValue/) { my $queue = RT::Queue->new( RT->SystemUser ); $queue->Load( $self->$field ); $deps->Add( out => $queue ); } } elsif ($type =~ /^(Add|Open|Resolve)Reminder$/) { my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $self->NewValue ); $deps->Add( out => $ticket ); } } sub __DependsOn { my $self = shift; my %args = ( Shredder => undef, Dependencies => undef, @_, ); my $deps = $args{'Dependencies'}; $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON, TargetObjects => $self->Attachments, Shredder => $args{'Shredder'} ); return $self->SUPER::__DependsOn( %args ); } sub Serialize { my $self = shift; my %args = (@_); my %store = $self->SUPER::Serialize(@_); my $type = $store{Type}; if ($type eq "CustomField") { my $cf = RT::CustomField->new( RT->SystemUser ); $cf->Load( $store{Field} ); $store{Field} = \($cf->UID); $store{OldReference} = \($self->OldReferenceObject->UID) if $self->OldReference; $store{NewReference} = \($self->NewReferenceObject->UID) if $self->NewReference; } elsif ($type =~ /^(Take|Untake|Force|Steal|Give)$/) { for my $field (qw/OldValue NewValue/) { my $user = RT::User->new( RT->SystemUser ); $user->Load( $store{$field} ); $store{$field} = \($user->UID); } } elsif ($type eq "DelWatcher") { my $principal = RT::Principal->new( RT->SystemUser ); $principal->Load( $store{OldValue} ); $store{OldValue} = \($principal->UID); } elsif ($type eq "AddWatcher") { my $principal = RT::Principal->new( RT->SystemUser ); $principal->Load( $store{NewValue} ); $store{NewValue} = \($principal->UID); } elsif ($type eq "DeleteLink") { if ($store{OldValue}) { my $base = RT::URI->new( $self->CurrentUser ); $base->FromURI( $store{OldValue} ); if ($base->Resolver && (my $object = $base->Object)) { if ($args{serializer}->Observe(object => $object)) { $store{OldValue} = \($object->UID); } elsif ($args{serializer}{HyperlinkUnmigrated}) { $store{OldValue} = $base->AsHREF; } else { $store{OldValue} = "(not migrated)"; } } } } elsif ($type eq "AddLink") { if ($store{NewValue}) { my $base = RT::URI->new( $self->CurrentUser ); $base->FromURI( $store{NewValue} ); if ($base->Resolver && (my $object = $base->Object)) { if ($args{serializer}->Observe(object => $object)) { $store{NewValue} = \($object->UID); } elsif ($args{serializer}{HyperlinkUnmigrated}) { $store{NewValue} = $base->AsHREF; } else { $store{NewValue} = "(not migrated)"; } } } } elsif ($type eq "Set" and $store{Field} eq "Queue") { for my $field (qw/OldValue NewValue/) { my $queue = RT::Queue->new( RT->SystemUser ); $queue->Load( $store{$field} ); if ($args{serializer}->Observe(object => $queue)) { $store{$field} = \($queue->UID); } else { $store{$field} = "$RT::Organization: " . $queue->Name . " (not migrated)"; } } } elsif ($type =~ /^(Add|Open|Resolve)Reminder$/) { my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $store{NewValue} ); $store{NewValue} = \($ticket->UID); } return %store; } sub PreInflate { my $class = shift; my ($importer, $uid, $data) = @_; if ($data->{Object} and ref $data->{Object}) { my $on_uid = ${ $data->{Object} }; return if $importer->ShouldSkipTransaction($on_uid); } if ($data->{Type} eq "DeleteLink" and ref $data->{OldValue}) { my $uid = ${ $data->{OldValue} }; my $obj = $importer->LookupObj( $uid ); $data->{OldValue} = $obj->URI; } elsif ($data->{Type} eq "AddLink" and ref $data->{NewValue}) { my $uid = ${ $data->{NewValue} }; my $obj = $importer->LookupObj( $uid ); $data->{NewValue} = $obj->URI; } return $class->SUPER::PreInflate( $importer, $uid, $data ); } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Authen/�����������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 015320� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/SavedSearches.pm��������������������������������������������������������������������000644 �000765 �000024 �00000007000 14005011336 017147� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::SavedSearches - a pseudo-collection for SavedSearch objects. =head1 SYNOPSIS use RT::SavedSearches =head1 DESCRIPTION SavedSearches is an object consisting of a number of SavedSearch objects. It works more or less like a DBIx::SearchBuilder collection, although it is not. =head1 METHODS =cut package RT::SavedSearches; use strict; use warnings; use base 'RT::SharedSettings'; use RT::SavedSearch; sub RecordClass { return 'RT::SavedSearch'; } =head2 LimitToPrivacy Takes two argumets: a privacy string, of the format "<class>-<id>", as produced by RT::SavedSearch::Privacy(); and a type string, as produced by RT::SavedSearch::Type(). The SavedSearches object will load the searches belonging to that user or group that are of the type specified. If no type is specified, all the searches belonging to the user/group will be loaded. Repeated calls to the same object should DTRT. =cut sub LimitToPrivacy { my $self = shift; my $privacy = shift; my $type = shift; my $object = $self->_GetObject($privacy); if ($object) { $self->{'objects'} = []; my @search_atts = $object->Attributes->Named('SavedSearch'); foreach my $att (@search_atts) { my $search = RT::SavedSearch->new($self->CurrentUser); $search->Load($privacy, $att->Id); next if $type && $search->Type && $search->Type ne $type; push(@{$self->{'objects'}}, $search); } } else { $RT::Logger->error("Could not load object $privacy"); } } RT::Base->_ImportOverlays(); 1; rt-5.0.1/lib/RT/Dashboards.pm�����������������������������������������������������������������������000644 �000765 �000024 �00000007226 14005011336 016513� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Dashboards - a pseudo-collection for Dashboard objects. =head1 SYNOPSIS use RT::Dashboards =head1 DESCRIPTION Dashboards is an object consisting of a number of Dashboard objects. It works more or less like a DBIx::SearchBuilder collection, although it is not. =head1 METHODS =cut package RT::Dashboards; use strict; use warnings; use base 'RT::SharedSettings'; use RT::Dashboard; sub RecordClass { return 'RT::Dashboard'; } =head2 LimitToPrivacy Takes one argument: a privacy string, of the format "<class>-<id>", as produced by RT::Dashboard::Privacy(). The Dashboards object will load the dashboards belonging to that user or group. Repeated calls to the same object should DTRT. =cut sub LimitToPrivacy { my $self = shift; my $privacy = shift; my $object = $self->_GetObject($privacy); if ($object) { $self->{'objects'} = []; my @dashboard_atts = $object->Attributes->Named('Dashboard'); foreach my $att (@dashboard_atts) { my $dashboard = RT::Dashboard->new($self->CurrentUser); $dashboard->Load($privacy, $att->Id); push(@{$self->{'objects'}}, $dashboard); } } else { $RT::Logger->error("Could not load object $privacy"); } } =head2 SortDashboards Sort the list of dashboards. The default is to sort alphabetically. =cut sub SortDashboards { my $self = shift; # Work directly with the internal data structure since Dashboards # aren't fully backed by a DB table and can't support typical OrderBy, etc. my @sorted = sort { lcfirst($a->Name) cmp lcfirst($b->Name) } @{$self->{'objects'}}; @{$self->{'objects'}} = @sorted; return; } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Generated.pm.in���������������������������������������������������������������������000644 �000765 �000024 �00000006224 14005011336 016741� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT; use warnings; use strict; our $VERSION = '@RT_VERSION_MAJOR@.@RT_VERSION_MINOR@.@RT_VERSION_PATCH@'; our ($MAJOR_VERSION, $MINOR_VERSION, $REVISION) = $VERSION =~ /^(\d)\.(\d)\.(\d+)/; @DATABASE_ENV_PREF@ $BasePath = '@RT_PATH@'; $EtcPath = '@RT_ETC_PATH@'; $BinPath = '@RT_BIN_PATH@'; $SbinPath = '@RT_SBIN_PATH@'; $VarPath = '@RT_VAR_PATH@'; $FontPath = '@RT_FONT_PATH@'; $LexiconPath = '@RT_LEXICON_PATH@'; $StaticPath = '@RT_STATIC_PATH@'; $PluginPath = '@RT_PLUGIN_PATH@'; $LocalPath = '@RT_LOCAL_PATH@'; $LocalEtcPath = '@LOCAL_ETC_PATH@'; $LocalLibPath = '@LOCAL_LIB_PATH@'; $LocalLexiconPath = '@LOCAL_LEXICON_PATH@'; $LocalStaticPath = '@LOCAL_STATIC_PATH@'; $LocalPluginPath = '@LOCAL_PLUGIN_PATH@'; # $MasonComponentRoot is where your rt instance keeps its mason html files $MasonComponentRoot = '@MASON_HTML_PATH@'; # $MasonLocalComponentRoot is where your rt instance keeps its site-local # mason html files. $MasonLocalComponentRoot = '@MASON_LOCAL_HTML_PATH@'; # $MasonDataDir Where mason keeps its datafiles $MasonDataDir = '@MASON_DATA_PATH@'; # RT needs to put session data (for preserving state between connections # via the web interface) $MasonSessionDir = '@MASON_SESSION_PATH@'; 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Util.pm�����������������������������������������������������������������������������000644 �000765 �000024 �00000017624 14005011336 015361� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Util; use strict; use warnings; use base 'Exporter'; our @EXPORT = qw/safe_run_child mime_recommended_filename EntityLooksLikeEmailMessage EmailContentTypes/; use Encode qw/encode/; sub safe_run_child (&) { my $our_pid = $$; # situation here is wierd, running external app # involves fork+exec. At some point after fork, # but before exec (or during) code can die in a # child. Local is no help here as die throws # error out of scope and locals are reset to old # values. Instead we set values, eval code, check pid # on failure and reset values only in our original # process my ($oldv_dbh, $oldv_rth); my $dbh = $RT::Handle ? $RT::Handle->dbh : undef; $oldv_dbh = $dbh->{'InactiveDestroy'} if $dbh; $dbh->{'InactiveDestroy'} = 1 if $dbh; $oldv_rth = $RT::Handle->{'DisconnectHandleOnDestroy'} if $RT::Handle; $RT::Handle->{'DisconnectHandleOnDestroy'} = 0 if $RT::Handle; my ($reader, $writer); pipe( $reader, $writer ); my @res; my $want = wantarray; eval { my $code = shift; local @ENV{ 'LANG', 'LC_ALL' } = ( 'C', 'C' ); unless ( defined $want ) { $code->(); } elsif ( $want ) { @res = $code->(); } else { @res = ( scalar $code->() ); } exit 0 if $our_pid != $$; 1; } or do { my $err = $@; $err =~ s/^Stack:.*$//ms; if ( $our_pid == $$ ) { $dbh->{'InactiveDestroy'} = $oldv_dbh if $dbh; $RT::Handle->{'DisconnectHandleOnDestroy'} = $oldv_rth if $RT::Handle; die "System Error: $err"; } else { print $writer "System Error: $err"; exit 1; } }; close($writer); $reader->blocking(0); my ($response) = $reader->getline; warn $response if $response; $dbh->{'InactiveDestroy'} = $oldv_dbh if $dbh; $RT::Handle->{'DisconnectHandleOnDestroy'} = $oldv_rth if $RT::Handle; return $want? (@res) : $res[0]; } =head2 mime_recommended_filename( MIME::Head|MIME::Entity ) # mimic our own recommended_filename # since MIME-tools 5.501, head->recommended_filename requires the head are # mime encoded, we don't meet this yet. =cut sub mime_recommended_filename { my $head = shift; $head = $head->head if $head->isa('MIME::Entity'); for my $attr_name (qw( content-disposition.filename content-type.name )) { my $value = Encode::decode("UTF-8",$head->mime_attr($attr_name)); if ( defined $value && $value =~ /\S/ ) { return $value; } } return; } sub assert_bytes { my $string = shift; return unless utf8::is_utf8($string); return unless $string =~ /([^\x00-\x7F])/; my $msg; if (ord($1) > 255) { $msg = "Expecting a byte string, but was passed characters"; } else { $msg = "Expecting a byte string, but was possibly passed charcters;" ." if the string is actually bytes, please use utf8::downgrade"; } $RT::Logger->warn($msg, Carp::longmess()); } =head2 C<constant_time_eq($a, $b)> Compares two strings for equality in constant-time. Replacement for the C<eq> operator designed to avoid timing side-channel vulnerabilities. Returns zero or one. This is intended for use in cryptographic subsystems for comparing well-formed data such as hashes - not for direct use with user input or as a general replacement for the C<eq> operator. The two string arguments B<MUST> be of equal length. If the lengths differ, this function will call C<die()>, as proceeding with execution would create a timing vulnerability. Length is defined by characters, not bytes. Strings that should be treated as binary octets rather than Unicode text should pass a true value for the binary flag. This code has been tested to do what it claims. Do not change it without thorough statistical timing analysis to validate the changes. Added to resolve CVE-2017-5361 For more on timing attacks, see this Wikipedia article: B<https://en.wikipedia.org/wiki/Timing_attack> =cut sub constant_time_eq { my ($a, $b, $binary) = @_; my $result = 0; # generic error message avoids potential information leaks my $generic_error = "Cannot compare values"; die $generic_error unless defined $a and defined $b; die $generic_error unless length $a == length $b; die $generic_error if ref($a) or ref($b); for (my $i = 0; $i < length($a); $i++) { my $a_char = substr($a, $i, 1); my $b_char = substr($b, $i, 1); my (@a_octets, @b_octets); if ($binary) { @a_octets = ord($a_char); @b_octets = ord($b_char); } else { # encode() is set to die on malformed @a_octets = unpack("C*", encode('UTF-8', $a_char, Encode::FB_CROAK)); @b_octets = unpack("C*", encode('UTF-8', $b_char, Encode::FB_CROAK)); } die $generic_error if (scalar @a_octets) != (scalar @b_octets); for (my $j = 0; $j < scalar @a_octets; $j++) { $result |= $a_octets[$j] ^ $b_octets[$j]; } } return 0 + not $result; } =head2 EntityLooksLikeEmailMessage( MIME::Entity ) Check MIME type headers for entities that look like email. =cut sub EntityLooksLikeEmailMessage { my $entity = shift; return unless $entity; # Use mime_type instead of effective_type to get the same headers # MIME::Parser used. my $mime_type = $entity->mime_type(); my @email_types = EmailContentTypes(); return 1 if grep { $mime_type eq $_ } @email_types; return 0; } =head2 EmailContentTypes Return MIME types that indicate email messages. =cut sub EmailContentTypes { # This is the same list of MIME types MIME::Parser uses. The partial and # external-body types are unlikely to produce usable attachments, but they # are still recognized as email for the purposes of this function. return ( 'message/rfc822', 'message/partial', 'message/external-body' ); } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Handle.pm���������������������������������������������������������������������������000644 �000765 �000024 �00000306224 14005011336 015634� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Handle - RT's database handle =head1 SYNOPSIS use RT; BEGIN { RT::LoadConfig() }; use RT::Handle; =head1 DESCRIPTION C<RT::Handle> is RT specific wrapper over one of L<DBIx::SearchBuilder::Handle> classes. As RT works with different types of DBs we subclass repsective handler from L<DBIx::SearchBuilder>. Type of the DB is defined by L<RT's DatabaseType config option|RT_Config/DatabaseType>. You B<must> load this module only when the configs have been loaded. =cut package RT::Handle; use strict; use warnings; use File::Spec; use Cwd; =head1 METHODS =head2 FinalizeDatabaseType Sets RT::Handle's superclass to the correct subclass of L<DBIx::SearchBuilder::Handle>, using the C<DatabaseType> configuration. =cut sub FinalizeDatabaseType { my $db_type = RT->Config->Get('DatabaseType'); my $package = "DBIx::SearchBuilder::Handle::$db_type"; $package->require or die "Unable to load DBIx::SearchBuilder database handle for '$db_type'.\n". "Perhaps you've picked an invalid database type or spelled it incorrectly.\n". $@; @RT::Handle::ISA = ($package); # We use COLLATE NOCASE to enforce case insensitivity on the normally # case-sensitive SQLite, LOWER() approach works, but lucks performance # due to absence of functional indexes if ($db_type eq 'SQLite') { no strict 'refs'; no warnings 'redefine'; *DBIx::SearchBuilder::Handle::SQLite::CaseSensitive = sub {0}; } } =head2 Connect Connects to RT's database using credentials and options from the RT config. Takes nothing. =cut sub Connect { my $self = shift; my %args = (@_); my $db_type = RT->Config->Get('DatabaseType'); if ( $db_type eq 'Oracle' ) { $ENV{'NLS_LANG'} = "AMERICAN_AMERICA.AL32UTF8"; $ENV{'NLS_NCHAR'} = "AL32UTF8"; } $self->SUPER::Connect( User => RT->Config->Get('DatabaseUser'), Password => RT->Config->Get('DatabasePassword'), DisconnectHandleOnDestroy => 1, %args, ); if ( $db_type eq 'mysql' ) { # set the character set $self->dbh->do("SET NAMES 'utf8mb4'"); } elsif ( $db_type eq 'Pg' ) { my $version = $self->DatabaseVersion; ($version) = $version =~ /^(\d+\.\d+)/; $self->dbh->do("SET bytea_output = 'escape'") if $version >= 9.0; } $self->dbh->{'LongReadLen'} = RT->Config->Get('MaxAttachmentSize'); } =head2 BuildDSN Build the DSN for the RT database. Doesn't take any parameters, draws all that from the config. =cut sub BuildDSN { my $self = shift; # Unless the database port is a positive integer, we really don't want to pass it. my $db_port = RT->Config->Get('DatabasePort'); $db_port = undef unless (defined $db_port && $db_port =~ /^(\d+)$/); my $db_host = RT->Config->Get('DatabaseHost'); $db_host = undef unless $db_host; my $db_name = RT->Config->Get('DatabaseName'); my $db_type = RT->Config->Get('DatabaseType'); $db_name = File::Spec->catfile($RT::VarPath, $db_name) if $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name); my %args = ( Host => $db_host, Database => $db_name, Port => $db_port, Driver => $db_type, ); if ( $db_type eq 'Oracle' && $db_host ) { $args{'SID'} = delete $args{'Database'}; } $self->SUPER::BuildDSN( %args ); if (RT->Config->Get('DatabaseExtraDSN')) { my %extra = RT->Config->Get('DatabaseExtraDSN'); $self->{'dsn'} .= ";$_=$extra{$_}" for sort keys %extra; } return $self->{'dsn'}; } =head2 DSN Returns the DSN for this handle. In order to get correct value you must build DSN first, see L</BuildDSN>. This is method can be called as class method, in this case creates temporary handle object, L</BuildDSN builds DSN> and returns it. =cut sub DSN { my $self = shift; return $self->SUPER::DSN if ref $self; my $handle = $self->new; $handle->BuildDSN; return $handle->DSN; } =head2 SystemDSN Returns a DSN suitable for database creates and drops and user creates and drops. Gets RT's DSN first (see L<DSN>) and then change it according to requirements of a database system RT's using. =cut sub SystemDSN { my $self = shift; my $db_name = RT->Config->Get('DatabaseName'); my $db_type = RT->Config->Get('DatabaseType'); my $dsn = $self->DSN; if ( $db_type eq 'mysql' ) { # with mysql, you want to connect sans database to funge things $dsn =~ s/dbname=\Q$db_name//; } elsif ( $db_type eq 'Pg' ) { # with postgres, you want to connect to template1 database $dsn =~ s/dbname=\Q$db_name/dbname=template1/; } return $dsn; } =head2 Database compatibility and integrity checks =cut sub CheckIntegrity { my $self = shift; unless ($RT::Handle and $RT::Handle->dbh) { local $@; unless ( eval { RT::ConnectToDatabase(); 1 } ) { return (0, 'no connection', "$@"); } } require RT::CurrentUser; my $test_user = RT::CurrentUser->new; $test_user->Load('RT_System'); unless ( $test_user->id ) { return (0, 'no system user', "Couldn't find RT_System user in the DB '". $RT::Handle->DSN ."'"); } $test_user = RT::CurrentUser->new; $test_user->Load('Nobody'); unless ( $test_user->id ) { return (0, 'no nobody user', "Couldn't find Nobody user in the DB '". $RT::Handle->DSN ."'"); } return 1; } sub CheckCompatibility { my $self = shift; my $dbh = shift; my $state = shift || 'post'; my $db_type = RT->Config->Get('DatabaseType'); if ( $db_type eq "mysql" ) { # Check which version we're running my $version = ($dbh->selectrow_array("show variables like 'version'"))[1]; return (0, "couldn't get version of the mysql server") unless $version; # MySQL and MariaDB are both 'mysql' type. # the minimum version supported is MySQL 5.7.7 / MariaDB 10.2.5 # the version string for MariaDB includes "MariaDB" in Debian/RedHat my $is_mariadb = $version =~ m{mariadb}i ? 1 : 0; my $mysql_min_version = '5.7.7'; # so index sizes allow for VARCHAR(255) fields my $mariadb_min_version = '10.2.5'; # uses innodb by default return ( 0, "RT 5.0.0 is unsupported on MySQL versions before $mysql_min_version. Your version is $version.") if !$is_mariadb && cmp_version( $version, $mysql_min_version ) < 0; return ( 0, "RT 5.0.0 is unsupported on MariaDB versions before $mariadb_min_version. Your version is $version.") if $is_mariadb && cmp_version( $version, $mariadb_min_version ) < 0; # MySQL must have InnoDB support local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $innodb = lc($dbh->selectall_hashref("SHOW ENGINES", "engine")->{InnoDB}{support} || "no"); if ( $innodb eq "no" ) { return (0, "RT requires that MySQL be compiled with InnoDB table support.\n". "See <http://dev.mysql.com/doc/mysql/en/innodb-storage-engine.html>\n". "and check that there are no 'skip-innodb' lines in your my.cnf."); } elsif ( $innodb eq "disabled" ) { return (0, "RT requires that MySQL InnoDB table support be enabled.\n". "Remove the 'skip-innodb' or 'innodb = OFF' line from your my.cnf file, restart MySQL, and try again.\n"); } if ( $state eq 'post' ) { my $show_table = sub { $dbh->selectrow_arrayref("SHOW CREATE TABLE $_[0]")->[1] }; unless ( $show_table->("Tickets") =~ /(?:ENGINE|TYPE)\s*=\s*InnoDB/i ) { return (0, "RT requires that all its tables be of InnoDB type. Upgrade RT tables."); } unless ( $show_table->("Attachments") =~ /\bContent\b[^,]*BLOB/i ) { return (0, "RT since version 3.8 has new schema for MySQL versions after 4.1.0\n" ."Follow instructions in the UPGRADING.mysql file."); } } if ($state =~ /^(create|post)$/) { my $show_var = sub { $dbh->selectrow_arrayref("SHOW VARIABLES LIKE ?",{},$_[0])->[1] }; my $max_packet = $show_var->("max_allowed_packet"); if ($max_packet <= (5 * 1024 * 1024)) { $max_packet = sprintf("%.1fM", $max_packet/1024/1024); warn "max_allowed_packet is set to $max_packet, which limits the maximum attachment or email size that RT can process. Consider adjusting MySQL's max_allowed_packet setting.\n"; } } } return (1) } sub CheckSphinxSE { my $self = shift; my $dbh = $RT::Handle->dbh; local $dbh->{'RaiseError'} = 0; local $dbh->{'PrintError'} = 0; my $has = ($dbh->selectrow_array("show variables like 'have_sphinx'"))[1]; $has ||= ($dbh->selectrow_array( "select 'yes' from INFORMATION_SCHEMA.PLUGINS where PLUGIN_NAME = 'sphinx' AND PLUGIN_STATUS='active'" ))[0]; return 0 unless lc($has||'') eq "yes"; return 1; } =head2 Database maintanance =head3 CreateDatabase $DBH Creates a new database. This method can be used as class method. Takes DBI handle. Many database systems require special handle to allow you to create a new database, so you have to use L<SystemDSN> method during connection. Fetches type and name of the DB from the config. =cut sub CreateDatabase { my $self = shift; my $dbh = shift or return (0, "No DBI handle provided"); my $db_type = RT->Config->Get('DatabaseType'); my $db_name = RT->Config->Get('DatabaseName'); my $status; if ( $db_type eq 'SQLite' ) { return (1, 'Skipped as SQLite doesn\'t need any action'); } elsif ( $db_type eq 'Oracle' ) { my $db_user = RT->Config->Get('DatabaseUser'); my $db_pass = RT->Config->Get('DatabasePassword'); $status = $dbh->do( "CREATE USER $db_user IDENTIFIED BY $db_pass" ." default tablespace USERS" ." temporary tablespace TEMP" ." quota unlimited on USERS" ); unless ( $status ) { return $status, "Couldn't create user $db_user identified by $db_pass." ."\nError: ". $dbh->errstr; } $status = $dbh->do( "GRANT connect, resource TO $db_user" ); unless ( $status ) { return $status, "Couldn't grant connect and resource to $db_user." ."\nError: ". $dbh->errstr; } return (1, "Created user $db_user. All RT's objects should be in his schema."); } elsif ( $db_type eq 'Pg' ) { $status = $dbh->do("CREATE DATABASE $db_name WITH ENCODING='UNICODE' TEMPLATE template0"); } elsif ( $db_type eq 'mysql' ) { $status = $dbh->do("CREATE DATABASE `$db_name` DEFAULT CHARACTER SET utf8"); } else { $status = $dbh->do("CREATE DATABASE $db_name"); } return ($status, $DBI::errstr); } =head3 DropDatabase $DBH Drops RT's database. This method can be used as class method. Takes DBI handle as first argument. Many database systems require a special handle to allow you to drop a database, so you may have to use L<SystemDSN> when acquiring the DBI handle. Fetches the type and name of the database from the config. =cut sub DropDatabase { my $self = shift; my $dbh = shift or return (0, "No DBI handle provided"); my $db_type = RT->Config->Get('DatabaseType'); my $db_name = RT->Config->Get('DatabaseName'); if ( $db_type eq 'Oracle' ) { my $db_user = RT->Config->Get('DatabaseUser'); my $status = $dbh->do( "DROP USER $db_user CASCADE" ); unless ( $status ) { return 0, "Couldn't drop user $db_user." ."\nError: ". $dbh->errstr; } return (1, "Successfully dropped user '$db_user' with his schema."); } elsif ( $db_type eq 'SQLite' ) { my $path = $db_name; $path = "$RT::VarPath/$path" unless substr($path, 0, 1) eq '/'; unlink $path or return (0, "Couldn't remove '$path': $!"); return (1); } elsif ( $db_type eq 'mysql' ) { $dbh->do("DROP DATABASE `$db_name`") or return (0, $DBI::errstr); } else { $dbh->do("DROP DATABASE ". $db_name) or return (0, $DBI::errstr); } return (1); } =head2 InsertACL =cut sub InsertACL { my $self = shift; my $dbh = shift; my $base_path = shift || $RT::EtcPath; my $db_type = RT->Config->Get('DatabaseType'); return (1) if $db_type eq 'SQLite'; $dbh = $self->dbh if !$dbh && ref $self; return (0, "No DBI handle provided") unless $dbh; return (0, "'$base_path' doesn't exist") unless -e $base_path; my $path; if ( -d $base_path ) { $path = File::Spec->catfile( $base_path, "acl.$db_type"); $path = $self->GetVersionFile($dbh, $path); $path = File::Spec->catfile( $base_path, "acl") unless $path && -e $path; return (0, "Couldn't find ACLs for $db_type") unless -e $path; } else { $path = $base_path; } # Get the full path since . is no longer in @INC after perl 5.24 $path = Cwd::abs_path($path); local *acl; do $path || return (0, "Couldn't load ACLs: " . $@); my @acl = acl($dbh); foreach my $statement (@acl) { my $sth = $dbh->prepare($statement) or return (0, "Couldn't prepare SQL query:\n $statement\n\nERROR: ". $dbh->errstr); unless ( $sth->execute ) { return (0, "Couldn't run SQL query:\n $statement\n\nERROR: ". $sth->errstr); } } return (1); } =head2 InsertSchema =cut sub InsertSchema { my $self = shift; my $dbh = shift; my $base_path = (shift || $RT::EtcPath); $dbh = $self->dbh if !$dbh && ref $self; return (0, "No DBI handle provided") unless $dbh; my $db_type = RT->Config->Get('DatabaseType'); my $file; if ( -d $base_path ) { $file = $base_path . "/schema." . $db_type; } else { $file = $base_path; } $file = $self->GetVersionFile( $dbh, $file ); unless ( $file ) { return (0, "Couldn't find schema file(s) '$file*'"); } unless ( -f $file && -r $file ) { return (0, "File '$file' doesn't exist or couldn't be read"); } my (@schema); open( my $fh_schema, '<', $file ) or die $!; my $has_local = 0; open( my $fh_schema_local, "<" . $self->GetVersionFile( $dbh, $RT::LocalEtcPath . "/schema." . $db_type )) and $has_local = 1; my $statement = ""; foreach my $line ( <$fh_schema>, ($_ = ';;'), $has_local? <$fh_schema_local>: () ) { $line =~ s/\#.*//g; $line =~ s/--.*//g; $statement .= $line; if ( $line =~ /;(\s*)$/ ) { $statement =~ s/;(\s*)$//g; push @schema, $statement; $statement = ""; } } close $fh_schema; close $fh_schema_local; if ( $db_type eq 'Oracle' ) { my $db_user = RT->Config->Get('DatabaseUser'); my $status = $dbh->do( "ALTER SESSION SET CURRENT_SCHEMA=$db_user" ); unless ( $status ) { return $status, "Couldn't set current schema to $db_user." ."\nError: ". $dbh->errstr; } } local $SIG{__WARN__} = sub {}; my $is_local = 0; $dbh->begin_work or return (0, "Couldn't begin transaction: ". $dbh->errstr); foreach my $statement (@schema) { if ( $statement =~ /^\s*;$/ ) { $is_local = 1; next; } my $sth = $dbh->prepare($statement) or return (0, "Couldn't prepare SQL query:\n$statement\n\nERROR: ". $dbh->errstr); unless ( $sth->execute or $is_local ) { return (0, "Couldn't run SQL query:\n$statement\n\nERROR: ". $sth->errstr); } } $dbh->commit or return (0, "Couldn't commit transaction: ". $dbh->errstr); return (1); } sub InsertIndexes { my $self = shift; my $dbh = shift; my $base_path = shift || $RT::EtcPath; my $db_type = RT->Config->Get('DatabaseType'); $dbh = $self->dbh if !$dbh && ref $self; return (0, "No DBI handle provided") unless $dbh; return (0, "'$base_path' doesn't exist") unless -e $base_path; my $path; if ( -d $base_path ) { $path = File::Spec->catfile( $base_path, "indexes"); return (0, "Couldn't find indexes file") unless -e $path; } else { $path = $base_path; } if ( $db_type eq 'Oracle' ) { my $db_user = RT->Config->Get('DatabaseUser'); my $status = $dbh->do( "ALTER SESSION SET CURRENT_SCHEMA=$db_user" ); unless ( $status ) { return $status, "Couldn't set current schema to $db_user." ."\nError: ". $dbh->errstr; } } # Get the full path since . is no longer in @INC after perl 5.24 $path = Cwd::abs_path($path); local $@; eval { require $path; 1 } or return (0, "Couldn't execute '$path': " . $@); return (1); } =head1 GetVersionFile Takes base name of the file as argument, scans for <base name>-<version> named files and returns file name with closest version to the version of the RT DB. =cut sub GetVersionFile { my $self = shift; my $dbh = shift; my $base_name = shift; my $db_version = ref $self ? $self->DatabaseVersion : do { my $tmp = RT::Handle->new; $tmp->dbh($dbh); $tmp->DatabaseVersion; }; require File::Glob; my @files = File::Glob::bsd_glob("$base_name*"); return '' unless @files; my %version = map { $_ =~ /\.\w+-([-\w\.]+)$/; ($1||0) => $_ } @files; my $version; foreach ( reverse sort cmp_version keys %version ) { if ( cmp_version( $db_version, $_ ) >= 0 ) { $version = $_; last; } } return defined $version? $version{ $version } : undef; } { my %word = ( a => -4, alpha => -4, b => -3, beta => -3, pre => -2, rc => -1, head => 9999, ); sub cmp_version($$) { my ($a, $b) = (@_); my @a = grep defined, map { /^[0-9]+$/? $_ : /^[a-zA-Z]+$/? $word{$_}|| -10 : undef } split /([^0-9]+)/, $a; my @b = grep defined, map { /^[0-9]+$/? $_ : /^[a-zA-Z]+$/? $word{$_}|| -10 : undef } split /([^0-9]+)/, $b; @a > @b ? push @b, (0) x (@a-@b) : push @a, (0) x (@b-@a); for ( my $i = 0; $i < @a; $i++ ) { return $a[$i] <=> $b[$i] if $a[$i] <=> $b[$i]; } return 0; } sub version_words { return keys %word; } } =head2 InsertInitialData Inserts system objects into RT's DB, like system user or 'nobody', internal groups and other records required. However, this method doesn't insert any real users like 'root' and you have to use InsertData or another way to do that. Takes no arguments. Returns status and message tuple. It's safe to call this method even if those objects already exist. =cut sub InsertInitialData { my $self = shift; my @warns; # avoid trying to canonicalize system users through ExternalAuth no warnings 'redefine'; local *RT::User::CanonicalizeUserInfo = sub { 1 }; # create RT_System user and grant him rights { require RT::CurrentUser; my $test_user = RT::User->new( RT::CurrentUser->new() ); $test_user->Load('RT_System'); if ( $test_user->id ) { push @warns, "Found system user in the DB."; } else { my $user = RT::User->new( RT::CurrentUser->new() ); my ( $val, $msg ) = $user->_BootstrapCreate( Name => 'RT_System', RealName => 'The RT System itself', Comments => 'Do not delete or modify this user. ' . 'It is integral to RT\'s internal database structures', Creator => '1', LastUpdatedBy => '1', ); return ($val, $msg) unless $val; } DBIx::SearchBuilder::Record::Cachable->FlushCache; } # init RT::SystemUser and RT::System objects RT::InitSystemObjects(); unless ( RT->SystemUser->id ) { return (0, "Couldn't load system user"); } # grant SuperUser right to system user { my $test_ace = RT::ACE->new( RT->SystemUser ); $test_ace->LoadByCols( PrincipalId => ACLEquivGroupId( RT->SystemUser->Id ), PrincipalType => 'Group', RightName => 'SuperUser', ObjectType => 'RT::System', ObjectId => 1, ); if ( $test_ace->id ) { push @warns, "System user has global SuperUser right."; } else { my $ace = RT::ACE->new( RT->SystemUser ); my ( $val, $msg ) = $ace->_BootstrapCreate( PrincipalId => ACLEquivGroupId( RT->SystemUser->Id ), PrincipalType => 'Group', RightName => 'SuperUser', ObjectType => 'RT::System', ObjectId => 1, ); return ($val, $msg) unless $val; } DBIx::SearchBuilder::Record::Cachable->FlushCache; } # system groups # $self->loc('Everyone'); # For the string extractor to get a string to localize # $self->loc('Privileged'); # For the string extractor to get a string to localize # $self->loc('Unprivileged'); # For the string extractor to get a string to localize foreach my $name (qw(Everyone Privileged Unprivileged)) { my $group = RT::Group->new( RT->SystemUser ); $group->LoadSystemInternalGroup( $name ); if ( $group->id ) { push @warns, "System group '$name' already exists."; next; } $group = RT::Group->new( RT->SystemUser ); my ( $val, $msg ) = $group->_Create( Domain => 'SystemInternal', Description => 'Pseudogroup for internal use', # loc Name => $name, Instance => '', ); return ($val, $msg) unless $val; } # nobody { my $user = RT::User->new( RT->SystemUser ); $user->Load('Nobody'); if ( $user->id ) { push @warns, "Found 'Nobody' user in the DB."; } else { my ( $val, $msg ) = $user->Create( Name => 'Nobody', RealName => 'Nobody in particular', Comments => 'Do not delete or modify this user. It is integral ' .'to RT\'s internal data structures', Privileged => 0, ); return ($val, $msg) unless $val; } if ( $user->HasRight( Right => 'OwnTicket', Object => $RT::System ) ) { push @warns, "User 'Nobody' has global OwnTicket right."; } else { my ( $val, $msg ) = $user->PrincipalObj->GrantRight( Right => 'OwnTicket', Object => $RT::System, ); return ($val, $msg) unless $val; } } # rerun to get init Nobody as well RT::InitSystemObjects(); # system role groups foreach my $name (qw(Owner Requestor Cc AdminCc)) { my $group = RT->System->RoleGroup( $name ); if ( $group->id ) { push @warns, "System role '$name' already exists."; next; } $group = RT::Group->new( RT->SystemUser ); my ( $val, $msg ) = $group->CreateRoleGroup( Name => $name, Object => RT->System, Description => 'SystemRolegroup for internal use', # loc InsideTransaction => 0, ); return ($val, $msg) unless $val; } # assets role groups foreach my $name (RT::Asset->Roles) { next if $name eq "Owner"; my $group = RT->System->RoleGroup( $name ); if ( $group->id ) { push @warns, "Assets role '$name' already exists."; next; } $group = RT::Group->new( RT->SystemUser ); my ($val, $msg) = $group->CreateRoleGroup( Object => RT->System, Name => $name, InsideTransaction => 0, ); return ($val, $msg) unless $val; } push @warns, "You appear to have a functional RT database." if @warns; return (1, join "\n", @warns); } =head2 InsertData Load some sort of data into the database, takes path to a file. =cut sub InsertData { my $self = shift; my $datafile = shift; my $root_password = shift; my %args = ( disconnect_after => 1, admin_dbh => undef, @_ ); # Slurp in stuff to insert from the datafile. Possible things to go in here:- our (@Groups, @Users, @Members, @ACL, @Queues, @Classes, @ScripActions, @ScripConditions, @Templates, @CustomFields, @CustomRoles, @Scrips, @Attributes, @Initial, @Final, @Catalogs, @Assets, @Articles, @OCFVs, @Topics, @ObjectTopics); local (@Groups, @Users, @Members, @ACL, @Queues, @Classes, @ScripActions, @ScripConditions, @Templates, @CustomFields, @CustomRoles, @Scrips, @Attributes, @Initial, @Final, @Catalogs, @Assets, @Articles, @OCFVs, @Topics, @ObjectTopics); local $@; $RT::Logger->debug("Going to load '$datafile' data file"); my $datafile_content = do { local $/; open (my $f, '<:encoding(UTF-8)', $datafile) or die "Cannot open initialdata file '$datafile' for read: $@"; <$f>; }; my $format_handler; my $handlers = RT->Config->Get('InitialdataFormatHandlers'); foreach my $handler_candidate (@$handlers) { next if $handler_candidate eq 'perl'; $handler_candidate->require or die "Config option InitialdataFormatHandlers lists '$handler_candidate', but it failed to load:\n$@\n"; if ($handler_candidate->CanLoad($datafile_content)) { $RT::Logger->debug("Initialdata file '$datafile' can be loaded by $handler_candidate"); $format_handler = $handler_candidate; last; } else { $RT::Logger->debug("Initialdata file '$datafile' can not be loaded by $handler_candidate"); } } if ( $format_handler ) { $format_handler->Load( $datafile_content, { Groups => \@Groups, Users => \@Users, Members => \@Members, ACL => \@ACL, Queues => \@Queues, Classes => \@Classes, ScripActions => \@ScripActions, ScripConditions => \@ScripConditions, Templates => \@Templates, CustomFields => \@CustomFields, CustomRoles => \@CustomRoles, Scrips => \@Scrips, Attributes => \@Attributes, Initial => \@Initial, Final => \@Final, Catalogs => \@Catalogs, Assets => \@Assets, Articles => \@Articles, OCFVs => \@OCFVs, Topics => \@Topics, ObjectTopics => \@ObjectTopics, }, ) or return (0, "Couldn't load data from '$datafile' for import:\n\nERROR:" . $@); } if ( !$format_handler and grep(/^perl$/, @$handlers) ) { # Use perl-style initialdata # Note: eval of perl initialdata should only be done once # Get the full path since . is no longer in @INC after perl 5.24 $datafile = Cwd::abs_path($datafile); eval { require $datafile } or return (0, "Couldn't load data from '$datafile':\nERROR:" . $@ . "\n\nDo you have the correct initialdata handler in RT_Config for this type of file?"); } if ( @Initial ) { $RT::Logger->debug("Running initial actions..."); foreach ( @Initial ) { local $@; eval { $_->( admin_dbh => $args{admin_dbh} ); 1 } or return (0, "One of initial functions failed: $@"); } $RT::Logger->debug("Done."); } if ( @Groups ) { $RT::Logger->debug("Creating groups..."); foreach my $item (@Groups) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::Group', $item, { CustomFields => \@OCFVs, Attributes => \@Attributes } ); next; } my $attributes = delete $item->{ Attributes }; my $ocfvs = delete $item->{ CustomFields }; my $new_entry = RT::Group->new( RT->SystemUser ); $item->{'Domain'} ||= 'UserDefined'; my $member_of = delete $item->{'MemberOf'}; my $members = delete $item->{'Members'}; my ( $return, $msg ) = $new_entry->_Create(%$item); unless ( $return ) { $RT::Logger->error( $msg ); next; } else { $RT::Logger->debug($return ."."); $_->{Object} = $new_entry for @{$attributes || []}; push @Attributes, @{$attributes || []}; $_->{Object} = $new_entry for @{$ocfvs || []}; push @OCFVs, @{$ocfvs || []}; } if ( $member_of ) { $member_of = [ $member_of ] unless ref $member_of eq 'ARRAY'; foreach( @$member_of ) { my $parent = RT::Group->new(RT->SystemUser); if ( ref $_ eq 'HASH' ) { $parent->LoadByCols( %$_ ); } elsif ( !ref $_ ) { $parent->LoadUserDefinedGroup( $_ ); } else { $RT::Logger->error( "(Error: wrong format of MemberOf field." ." Should be name of user defined group or" ." hash reference with 'column => value' pairs." ." Use array reference to add to multiple groups)" ); next; } unless ( $parent->Id ) { $RT::Logger->error("(Error: couldn't load group to add member)"); next; } my ( $return, $msg ) = $parent->AddMember( $new_entry->Id ); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return ."." ); } } } push @Members, map { +{Group => $new_entry->id, Class => "RT::User", Name => $_} } @{ $members->{Users} || [] }; push @Members, map { +{Group => $new_entry->id, Class => "RT::Group", Name => $_} } @{ $members->{Groups} || [] }; } $RT::Logger->debug("done."); } if ( @Users ) { $RT::Logger->debug("Creating users..."); foreach my $item (@Users) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::User', $item, { CustomFields => \@OCFVs, Attributes => \@Attributes } ); next; } my $member_of = delete $item->{'MemberOf'}; if ( $item->{'Name'} eq 'root' && $root_password ) { $item->{'Password'} = $root_password; } my $attributes = delete $item->{ Attributes }; my $ocfvs = delete $item->{ CustomFields }; no warnings 'redefine'; local *RT::User::CanonicalizeUserInfo = sub { 1 } if delete $item->{ SkipCanonicalize }; my $new_entry = RT::User->new( RT->SystemUser ); my ( $return, $msg ) = $new_entry->Create(%$item); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return ."." ); $_->{Object} = $new_entry for @{$attributes || []}; push @Attributes, @{$attributes || []}; $_->{Object} = $new_entry for @{$ocfvs || []}; push @OCFVs, @{$ocfvs || []}; } if ( $member_of ) { $member_of = [ $member_of ] unless ref $member_of eq 'ARRAY'; foreach( @$member_of ) { my $parent = RT::Group->new($RT::SystemUser); if ( ref $_ eq 'HASH' ) { $parent->LoadByCols( %$_ ); } elsif ( !ref $_ ) { $parent->LoadUserDefinedGroup( $_ ); } else { $RT::Logger->error( "(Error: wrong format of MemberOf field." ." Should be name of user defined group or" ." hash reference with 'column => value' pairs." ." Use array reference to add to multiple groups)" ); next; } unless ( $parent->Id ) { $RT::Logger->error("(Error: couldn't load group to add member)"); next; } my ( $return, $msg ) = $parent->AddMember( $new_entry->Id ); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return ."." ); } } } } $RT::Logger->debug("done."); } if ( @Queues ) { $RT::Logger->debug("Creating queues..."); for my $item (@Queues) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::Queue', $item, { CustomFields => \@OCFVs, Attributes => \@Attributes } ); next; } my $attributes = delete $item->{ Attributes }; my $ocfvs = delete $item->{ CustomFields }; my $new_entry = RT::Queue->new(RT->SystemUser); my ( $return, $msg ) = $new_entry->Create(%$item); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return ."." ); $_->{Object} = $new_entry for @{$attributes || []}; push @Attributes, @{$attributes || []}; $_->{Object} = $new_entry for @{$ocfvs || []}; push @OCFVs, @{$ocfvs || []}; } } $RT::Logger->debug("done."); } if ( @Classes ) { $RT::Logger->debug("Creating classes..."); for my $item (@Classes) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::Class', $item, { Attributes => \@Attributes } ); next; } my $attributes = delete $item->{ Attributes }; # Back-compat for the old "Queue" argument if ( exists $item->{'Queue'} ) { $item->{'ApplyTo'} = delete $item->{'Queue'}; } my $apply_to = (delete $item->{'ApplyTo'}) || 0; $apply_to = [ $apply_to ] unless ref $apply_to; my $new_entry = RT::Class->new(RT->SystemUser); my ( $return, $msg ) = $new_entry->Create(%$item); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return ."." ); for my $name ( @{ $apply_to } ) { if ( !$name ) { ( $return, $msg) = $new_entry->AddToObject( RT::Queue->new(RT->SystemUser) ); $RT::Logger->error( $msg ) unless $return; } else { my $queue = RT::Queue->new( RT->SystemUser ); $queue->Load( $name ); if ( $queue->id ) { ( $return, $msg) = $new_entry->AddToObject( $queue ); $RT::Logger->error( $msg ) unless $return; } else { $RT::Logger->error( "Could not find RT::Queue $name to apply " . $new_entry->Name . " to" ); } } } $_->{Object} = $new_entry for @{$attributes || []}; push @Attributes, @{$attributes || []}; } } $RT::Logger->debug("done."); } if ( @Catalogs ) { $RT::Logger->debug("Creating Catalogs..."); for my $item (@Catalogs) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::Catalog', $item, { Attributes => \@Attributes } ); next; } my $attributes = delete $item->{ Attributes }; my $new_entry = RT::Catalog->new(RT->SystemUser); my ( $return, $msg ) = $new_entry->Create(%$item); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return ."." ); } $_->{Object} = $new_entry for @{$attributes || []}; push @Attributes, @{$attributes || []}; } $RT::Logger->debug("done."); } if ( @Topics ) { $RT::Logger->debug("Creating Topics..."); for my $item ( @Topics ) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::Topic', $item ); next; } my $obj = delete $item->{Object}; if ( ref $obj eq 'CODE' ) { $obj = $obj->(); } elsif ( !ref $obj ) { my $class = RT::Class->new(RT->SystemUser); $class->Load($obj); if ( $class->id ) { $obj = $class; } } if ( !$obj ) { $RT::Logger->error( "Invalid class $obj" ); next; } if ( $item->{Parent} && $item->{Parent} =~ /\D/ ) { my $topic = RT::Topic->new( RT->SystemUser ); $topic->LoadByCols( Name => $item->{Parent} ); if ( $topic->id ) { $item->{Parent} = $topic->id; } else { $RT::Logger->error( "Invalid parent $item->{Parent}" ); next; } } my $new_entry = RT::Topic->new( RT->SystemUser ); my ( $return, $msg ) = $new_entry->Create( %$item, ObjectType => ref $obj, ObjectId => $obj->id ); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return . "." ); } } $RT::Logger->debug("done."); } if ( @Assets ) { $RT::Logger->debug("Creating Assets..."); for my $item (@Assets) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::Asset', $item, { CustomFields => \@OCFVs, Attributes => \@Attributes } ); next; } my $attributes = delete $item->{ Attributes }; my $ocfvs = delete $item->{ CustomFields }; my $new_entry = RT::Asset->new(RT->SystemUser); my ( $return, $msg ) = $new_entry->Create(%$item); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return ."." ); } $_->{Object} = $new_entry for @{$attributes || []}; push @Attributes, @{$attributes || []}; $_->{Object} = $new_entry for @{$ocfvs || []}; push @OCFVs, @{$ocfvs || []}; } $RT::Logger->debug("done."); } if ( @Articles ) { $RT::Logger->debug("Creating Articles..."); for my $item (@Articles) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::Article', $item, { CustomFields => \@OCFVs, Attributes => \@Attributes, ObjectTopics => \@ObjectTopics } ); next; } my $attributes = delete $item->{ Attributes }; my $ocfvs = delete $item->{ CustomFields }; my $object_topics = delete $item->{ Topics }; my $new_entry = RT::Article->new(RT->SystemUser); my ( $return, $msg ) = $new_entry->Create(%$item); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return ."." ); } $_->{Object} = $new_entry for @{$attributes || []}; push @Attributes, @{$attributes || []}; $_->{Object} = $new_entry for @{$ocfvs || []}; push @OCFVs, @{$ocfvs || []}; $_->{Object} = $new_entry for @{$object_topics || []}; push @ObjectTopics, @{$object_topics || []}; } $RT::Logger->debug("done."); } if ( @CustomFields ) { $RT::Logger->debug("Creating custom fields..."); my @deferred_BasedOn; for my $item ( @CustomFields ) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::CustomField', $item, { Attributes => \@Attributes } ); next; } my $attributes = delete $item->{ Attributes }; my $new_entry = RT::CustomField->new( RT->SystemUser ); my $values = delete $item->{'Values'}; # Back-compat for the old "Queue" argument if ( exists $item->{'Queue'} ) { $item->{'LookupType'} ||= 'RT::Queue-RT::Ticket'; $RT::Logger->warn("Queue provided for non-ticket custom field") unless $item->{'LookupType'} =~ /^RT::Queue-/; $item->{'ApplyTo'} = delete $item->{'Queue'}; } my $apply_to = delete $item->{'ApplyTo'}; if ( $item->{'BasedOn'} ) { if ( $item->{'BasedOn'} =~ /^\d+$/) { # Already have an ID -- should be fine } elsif ( $item->{'LookupType'} ) { my $basedon = RT::CustomField->new($RT::SystemUser); my ($ok, $msg ) = $basedon->LoadByCols( Name => $item->{'BasedOn'}, LookupType => $item->{'LookupType'}, Disabled => 0 ); if ($ok) { $item->{'BasedOn'} = $basedon->Id; } else { push @deferred_BasedOn, [$new_entry, delete $item->{'BasedOn'}]; } } else { $RT::Logger->error("Unable to load CF $item->{BasedOn} because no LookupType was specified. Skipping BasedOn"); delete $item->{'BasedOn'}; } } my ( $return, $msg ) = $new_entry->Create(%$item); unless( $return ) { $RT::Logger->error( $msg ); next; } foreach my $value ( @{$values} ) { ( $return, $msg ) = $new_entry->AddValue(%$value); $RT::Logger->error( $msg ) unless $return; } my $class = $new_entry->RecordClassFromLookupType; if ($class) { $apply_to = [ $apply_to ] unless ref $apply_to; for my $name ( @{ $apply_to } ) { my $ocf = RT::ObjectCustomField->new(RT->SystemUser); # global CF if (!$name) { ( $return, $msg ) = $ocf->Create( CustomField => $new_entry->Id, ); $RT::Logger->error( $msg ) unless $return and $ocf->Id; } else { if ($new_entry->IsOnlyGlobal) { $RT::Logger->warn("ApplyTo '$name' provided for global custom field ".$new_entry->Name ); } my $obj = $class->new(RT->SystemUser); $obj->Load($name); if ( $obj->Id ) { ( $return, $msg ) = $ocf->Create( CustomField => $new_entry->Id, ObjectId => $obj->Id, ); $RT::Logger->error( $msg ) unless $return and $ocf->Id; } else { $RT::Logger->error("Could not find $class $name to apply ".$new_entry->Name." to" ); } } } } $_->{Object} = $new_entry for @{$attributes || []}; push @Attributes, @{$attributes || []}; } for ( @deferred_BasedOn ) { my ($cf, $name) = @$_; my $basedon = RT::CustomField->new($RT::SystemUser); my ($ok, $msg ) = $basedon->LoadByCols( Name => $name, LookupType => $cf->LookupType, Disabled => 0, ); if ($ok) { ($ok, $msg) = $cf->SetBasedOn($basedon->Id); $RT::Logger->error("Unable to set $name as a " . $cf->LookupType . " BasedOn CF: $msg") if !$ok; } else { $RT::Logger->error("Unable to load $name as a " . $cf->LookupType . " CF. Skipping BasedOn: $msg"); } } $RT::Logger->debug("done."); } if ( @CustomRoles ) { $RT::Logger->debug("Creating custom roles..."); for my $item ( @CustomRoles ) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::CustomRole', $item, { Attributes => \@Attributes } ); next; } my $attributes = delete $item->{ Attributes }; my $apply_to = delete $item->{'ApplyTo'}; my $new_entry = RT::CustomRole->new( RT->SystemUser ); my ( $ok, $msg ) = $new_entry->Create(%$item, Disabled => 0); if (!$ok) { $RT::Logger->error($msg); next; } if ($apply_to) { $apply_to = [ $apply_to ] unless ref $apply_to; for my $name ( @{ $apply_to } ) { my ($ok, $msg) = $new_entry->AddToObject($name); $RT::Logger->error( $msg ) if !$ok; } } $_->{Object} = $new_entry for @{$attributes || []}; push @Attributes, @{$attributes || []}; if ( $item->{Disabled} ) { ( $ok, $msg ) = $new_entry->SetDisabled( $item->{Disabled} ); if ( !$ok ) { $RT::Logger->error( "Couldn't set Disabled to custom role $item->{Name}: $msg" ); } } } $RT::Logger->debug("done."); } if ( @Members ) { $RT::Logger->debug("Adding users and groups to groups..."); for my $item (@Members) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::GroupMember', $item ); next; } my $name = delete $item->{Group}; my $domain = delete $item->{GroupDomain} || 'UserDefined'; my $instance = delete $item->{GroupInstance}; if ($domain =~ /^(.+)-Role$/) { my $class = $1; if (!$class->DOES("RT::Record::Role::Roles")) { RT->Logger->error("Invalid group domain '$domain' for group $name; skipping adding membership"); next; } my $object = $class->new(RT->SystemUser); $object->Load($instance); $instance = $object->Id; } my $group = RT::Group->new(RT->SystemUser); $group->LoadByCols( $name =~ /\D/ ? ( Name => $name ) : ( id => $name ), Domain => $domain, (defined $instance ? (Instance => $instance) : ()), ); unless ($group->Id) { RT->Logger->error("Unable to find $domain group '$name' to add members to"); next; } my $class = delete $item->{Class} || 'RT::User'; my $member = $class->new( RT->SystemUser ); $item->{Domain} = 'UserDefined' if $member->isa("RT::Group"); $member->LoadByCols( %$item ); unless ($member->Id) { RT->Logger->error("Unable to find $class '".($item->{id} || $item->{Name})."' to add to ".$group->Name); next; } my ( $return, $msg) = $group->AddMember( $member->PrincipalObj->Id ); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return ."." ); } } } if ( @ACL ) { $RT::Logger->debug("Creating ACL..."); for my $item (@ACL) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::ACE', $item ); next; } my ($princ, $object); # Global rights or Queue rights? if ( $item->{'CF'} ) { $object = RT::CustomField->new( RT->SystemUser ); my @columns = ( Name => $item->{'CF'} ); push @columns, LookupType => $item->{'LookupType'} if $item->{'LookupType'}; push @columns, ObjectId => $item->{'ObjectId'} if $item->{'ObjectId'}; push @columns, Queue => $item->{'Queue'} if $item->{'Queue'} and not ref $item->{'Queue'}; my ($ok, $msg) = $object->LoadByName( @columns ); unless ( $ok ) { RT->Logger->error("Unable to load CF ".$item->{CF}.": $msg"); next; } } elsif ( $item->{'Queue'} ) { $object = RT::Queue->new(RT->SystemUser); my ($ok, $msg) = $object->Load( $item->{'Queue'} ); unless ( $ok ) { RT->Logger->error("Unable to load queue ".$item->{Queue}.": $msg"); next; } } elsif ( $item->{'Group'} || ($item->{ObjectType}||'') eq 'RT::Group') { my $name = $item->{'Group'} || $item->{ObjectId}; $object = RT::Group->new(RT->SystemUser); my ($ok, $msg) = $object->LoadUserDefinedGroup($name); unless ( $ok ) { RT->Logger->error("Unable to load user-defined group $name: $msg"); next; } } elsif ( $item->{ObjectType} and $item->{ObjectId}) { $object = $item->{ObjectType}->new(RT->SystemUser); my ($ok, $msg) = $object->Load( $item->{ObjectId} ); unless ( $ok ) { RT->Logger->error("Unable to load ".$item->{ObjectType}." ".$item->{ObjectId}.": $msg"); next; } } else { $object = $RT::System; } # Group rights or user rights? if ( $item->{'GroupDomain'} ) { if (my $role_name = delete $item->{CustomRole}) { my $role = RT::CustomRole->new(RT->SystemUser); $role->Load($role_name); $item->{'GroupType'} = $role->GroupType; } $princ = RT::Group->new(RT->SystemUser); if ( $item->{'GroupDomain'} eq 'UserDefined' ) { $princ->LoadUserDefinedGroup( $item->{'GroupId'} ); } elsif ( $item->{'GroupDomain'} eq 'SystemInternal' ) { $princ->LoadSystemInternalGroup( $item->{'GroupType'} ); } elsif ( $item->{'GroupDomain'} eq 'RT::System-Role' ) { $princ->LoadRoleGroup( Object => RT->System, Name => $item->{'GroupType'} ); } elsif ( $item->{'GroupDomain'} =~ /-Role$/ ) { $princ->LoadRoleGroup( Object => $object, Name => $item->{'GroupType'} ); } else { $princ->Load( $item->{'GroupId'} ); } unless ( $princ->Id ) { RT->Logger->error("Unable to load Group: GroupDomain => $item->{GroupDomain}, GroupId => $item->{GroupId}, Queue => $item->{Queue}"); next; } } else { $princ = RT::User->new(RT->SystemUser); my ($ok, $msg) = $princ->Load( $item->{'UserId'} ); unless ( $ok ) { RT->Logger->error("Unable to load user: $item->{UserId} : $msg"); next; } } $item->{Right} = delete $item->{RightName} if $item->{RightName}; # Grant it my @rights = ref($item->{'Right'}) eq 'ARRAY' ? @{$item->{'Right'}} : $item->{'Right'}; foreach my $right ( @rights ) { my ( $return, $msg ) = $princ->PrincipalObj->GrantRight( Right => $right, Object => $object ); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return ."." ); } } } $RT::Logger->debug("done."); } if ( @ScripActions ) { $RT::Logger->debug("Creating ScripActions..."); for my $item (@ScripActions) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::ScripAction', $item ); next; } my $new_entry = RT::ScripAction->new(RT->SystemUser); my ( $return, $msg ) = $new_entry->Create(%$item); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return ."." ); } } $RT::Logger->debug("done."); } if ( @ScripConditions ) { $RT::Logger->debug("Creating ScripConditions..."); for my $item (@ScripConditions) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::ScripCondition', $item ); next; } my $new_entry = RT::ScripCondition->new(RT->SystemUser); my ( $return, $msg ) = $new_entry->Create(%$item); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return ."." ); } } $RT::Logger->debug("done."); } if ( @Templates ) { $RT::Logger->debug("Creating templates..."); for my $item (@Templates) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::Template', $item ); next; } my $new_entry = RT::Template->new(RT->SystemUser); my ( $return, $msg ) = $new_entry->Create(%$item); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return ."." ); } } $RT::Logger->debug("done."); } if ( @Scrips ) { $RT::Logger->debug("Creating scrips..."); for my $item (@Scrips) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::Scrip', $item ); next; } my $new_entry = RT::Scrip->new(RT->SystemUser); my @queues = ref $item->{'Queue'} eq 'ARRAY' ? @{ $item->{'Queue'} } : ($item->{'Queue'}) if $item->{'Queue'}; if (!@queues) { push @queues, 0 unless $item->{'NoAutoGlobal'}; } my $remove_global = (delete $item->{'NoAutoGlobal'}) && !@queues; my %args = %$item; $args{Queue} = shift @queues; if (ref($args{Queue})) { # transform ScripObject->Create API into Scrip->Create API $args{Queue}{Queue} = delete $args{Queue}{ObjectId}; $args{Queue}{ObjectSortOrder} = delete $args{Queue}{SortOrder}; %args = ( %args, %{ $args{Queue} }, ); } my ( $return, $msg ) = $new_entry->Create(%args); unless ( $return ) { $RT::Logger->error( $msg ); next; } else { $RT::Logger->debug( $return ."." ); } if ($remove_global) { my ($return, $msg) = $new_entry->RemoveFromObject(ObjectId => 0); $RT::Logger->error( "Couldn't unapply scrip globally: $msg" ) unless $return; } foreach my $q ( @queues ) { my %args = ( Stage => $item->{'Stage'}, (ref($q) ? %$q : (ObjectId => $q)), ); my ($return, $msg) = $new_entry->AddToObject(%args); $RT::Logger->error( "Couldn't apply scrip to $args{ObjectId}: $msg" ) unless $return; } } $RT::Logger->debug("done."); } if ( @ObjectTopics ) { $RT::Logger->debug( "Creating ObjectTopics..." ); for my $item ( @ObjectTopics ) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::ObjectTopic', $item ); next; } my $obj = delete $item->{Object}; if ( ref $obj eq 'CODE' ) { $obj = $obj->(); } my $topic = RT::Topic->new( RT->SystemUser ); if ( $item->{Topic} =~ /\D/ ) { $topic->LoadByCols( Name => $item->{Topic} ); } else { $topic->Load( $item->{Topic} ); } if ( !$topic->id ) { $RT::Logger->error( "Couldn't load topic $item->{Topic}" ); next; } $item->{Topic} = $topic->id; my ( $return, $msg ) = $obj->AddTopic( %$item ); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return . "." ); } } $RT::Logger->debug( "done." ); } if ( @OCFVs ) { $RT::Logger->debug("Creating ObjectCustomFieldValues..."); for my $item (@OCFVs) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::ObjectCustomFieldValue', $item ); next; } my $obj = delete $item->{Object}; if ( ref $obj eq 'CODE' ) { $obj = $obj->(); } $item->{Field} = delete $item->{CustomField} if $item->{CustomField}; $item->{Value} = delete $item->{Content} if $item->{Content}; $self->_CanonilizeObjectCustomFieldValue( $item ); my ( $return, $msg ) = $obj->AddCustomFieldValue (%$item); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return ."." ); } } $RT::Logger->debug("done."); } if ( @Attributes ) { $RT::Logger->debug("Creating attributes..."); my $sys = RT::System->new(RT->SystemUser); my %order = ( 'Dashboard' => 1, 'HomepageSettings' => 1, 'Pref-DashboardsInMenu' => 2, 'Subscription' => 2, ); for my $item ( sort { ( $order{ $a->{Name} } || 0 ) <=> ( $order{ $b->{Name} } || 0 ) } @Attributes ) { if ( $item->{_Original} ) { $self->_UpdateOrDeleteObject( 'RT::Attribute', $item ); next; } my $obj = delete $item->{Object}; if ( ref $obj eq 'CODE' ) { $obj = $obj->(); } elsif ( $obj && !ref $obj ) { if ( $obj eq 'RT::System' ) { $obj = RT->System; } elsif ( my $class = delete $item->{ObjectType} ) { my $id = $obj; $obj = $class->new( RT->SystemUser ); if ( $class eq 'RT::Group') { $obj->LoadUserDefinedGroup( $id ); } else { $obj->Load( $id ); } if ( !$obj->id ) { $RT::Logger->error( "Failed to load object $class-$id" ); next; } } else { $RT::Logger->error( "Invalid object $obj" ); next; } } $obj ||= $sys; $self->_CanonilizeAttributeContent($item); my ( $return, $msg ) = $obj->AddAttribute (%$item); unless ( $return ) { $RT::Logger->error( $msg ); } else { $RT::Logger->debug( $return ."." ); } } $RT::Logger->debug("done."); } if ( @Final ) { $RT::Logger->debug("Running final actions..."); for ( @Final ) { local $@; eval { $_->( admin_dbh => $args{admin_dbh} ); }; $RT::Logger->error( "Failed to run one of final actions: $@" ) if $@; } $RT::Logger->debug("done."); } # XXX: This disconnect doesn't really belong here; it's a relict from when # this method was extracted from rt-setup-database. However, too much # depends on it to change without significant testing. At the very least, # we can provide a way to skip the side-effect. if ( $args{disconnect_after} ) { my $db_type = RT->Config->Get('DatabaseType'); $RT::Handle->Disconnect() unless $db_type eq 'SQLite'; } $RT::Logger->debug("Done setting up database content."); # TODO is it ok to return 1 here? If so, the previous codes in this sub # should return (0, $msg) if error happens instead of just warning. # anyway, we need to return something here to tell if everything is ok return( 1, 'Done inserting data' ); } =head2 ACLEquivGroupId Given a userid, return that user's acl equivalence group =cut sub ACLEquivGroupId { my $id = shift; my $cu = RT->SystemUser; unless ( $cu ) { require RT::CurrentUser; $cu = RT::CurrentUser->new; $cu->LoadByName('RT_System'); warn "Couldn't load RT_System user" unless $cu->id; } my $equiv_group = RT::Group->new( $cu ); $equiv_group->LoadACLEquivalenceGroup( $id ); return $equiv_group->Id; } =head2 QueryHistory Returns the SQL query history associated with this handle. The top level array represents a lists of request. Each request is a hash with metadata about the request (such as the URL) and a list of queries. You'll probably not be using this. =cut sub QueryHistory { my $self = shift; return $self->{QueryHistory}; } =head2 AddRequestToHistory Adds a web request to the query history. It must be a hash with keys Path (a string) and Queries (an array reference of arrays, where elements are time, sql, bind parameters, and duration). =cut sub AddRequestToHistory { my $self = shift; my $request = shift; push @{ $self->{QueryHistory} }, $request; } =head2 Quote Returns the parameter quoted by DBI. B<You almost certainly do not need this.> Use bind parameters (C<?>) instead. This is used only outside the scope of interacting with the database. =cut sub Quote { my $self = shift; my $value = shift; return $self->dbh->quote($value); } =head2 FillIn Takes a SQL query and an array reference of bind parameters and fills in the query's C<?> parameters. =cut sub FillIn { my $self = shift; my $sql = shift; my $bind = shift; my $b = 0; # is this regex sufficient? $sql =~ s{\?}{$self->Quote($bind->[$b++])}eg; return $sql; } sub Indexes { my $self = shift; my %res; my $db_type = RT->Config->Get('DatabaseType'); my $dbh = $self->dbh; my $list; if ( $db_type eq 'mysql' ) { $list = $dbh->selectall_arrayref( 'select distinct table_name, index_name from information_schema.statistics where table_schema = ?', undef, scalar RT->Config->Get('DatabaseName') ); } elsif ( $db_type eq 'Pg' ) { $list = $dbh->selectall_arrayref( 'select tablename, indexname from pg_indexes', undef, ); } elsif ( $db_type eq 'SQLite' ) { $list = $dbh->selectall_arrayref( 'select tbl_name, name from sqlite_master where type = ?', undef, 'index' ); } elsif ( $db_type eq 'Oracle' ) { $list = $dbh->selectall_arrayref( 'select table_name, index_name from all_indexes where index_name NOT LIKE ? AND lower(Owner) = ?', undef, 'SYS_%$$', lc RT->Config->Get('DatabaseUser'), ); } else { die "Not implemented"; } push @{ $res{ lc $_->[0] } ||= [] }, lc $_->[1] foreach @$list; return %res; } sub IndexesThatBeginWith { my $self = shift; my %args = (Table => undef, Columns => [], @_); my %indexes = $self->Indexes; my @check = @{ $args{'Columns'} }; my @list; foreach my $index ( @{ $indexes{ lc $args{'Table'} } || [] } ) { my %info = $self->IndexInfo( Table => $args{'Table'}, Name => $index ); next if @{ $info{'Columns'} } < @check; my $check = join ',', @check; next if join( ',', @{ $info{'Columns'} } ) !~ /^\Q$check\E(?:,|$)/i; push @list, \%info; } return sort { @{ $a->{'Columns'} } <=> @{ $b->{'Columns'} } } @list; } sub IndexInfo { my $self = shift; my %args = (Table => undef, Name => undef, @_); my $db_type = RT->Config->Get('DatabaseType'); my $dbh = $self->dbh; my %res = ( Table => lc $args{'Table'}, Name => lc $args{'Name'}, ); if ( $db_type eq 'mysql' ) { my $list = $dbh->selectall_arrayref( 'select NON_UNIQUE, COLUMN_NAME, SUB_PART from information_schema.statistics where table_schema = ? AND LOWER(table_name) = ? AND index_name = ? ORDER BY SEQ_IN_INDEX', undef, scalar RT->Config->Get('DatabaseName'), lc $args{'Table'}, $args{'Name'}, ); return () unless $list && @$list; $res{'Unique'} = $list->[0][0]? 0 : 1; $res{'Functional'} = 0; $res{'Columns'} = [ map $_->[1], @$list ]; } elsif ( $db_type eq 'Pg' ) { my $index = $dbh->selectrow_hashref( 'select ix.*, pg_get_expr(ix.indexprs, ix.indrelid) as functions from pg_class t, pg_class i, pg_index ix where t.relname ilike ? and t.relkind = ? and i.relname ilike ? and ix.indrelid = t.oid and ix.indexrelid = i.oid ', undef, $args{'Table'}, 'r', $args{'Name'}, ); return () unless $index && keys %$index; $res{'Unique'} = $index->{'indisunique'}; $res{'Functional'} = (grep $_ == 0, split ' ', $index->{'indkey'})? 1 : 0; $res{'Columns'} = [ map int($_), split ' ', $index->{'indkey'} ]; my $columns = $dbh->selectall_hashref( 'select a.attnum, a.attname from pg_attribute a where a.attrelid = ?', 'attnum', undef, $index->{'indrelid'} ); if ($index->{'functions'}) { # XXX: this is good enough for us $index->{'functions'} = [ split /,\s+/, $index->{'functions'} ]; } foreach my $e ( @{ $res{'Columns'} } ) { if (exists $columns->{$e} ) { $e = $columns->{$e}{'attname'}; } elsif ( !$e ) { $e = shift @{ $index->{'functions'} }; } } foreach my $column ( @{$res{'Columns'}} ) { next unless $column =~ s/^lower\( \s* \(? (\w+) \)? (?:::text)? \s* \)$/$1/ix; $res{'CaseInsensitive'}{ lc $1 } = 1; } } elsif ( $db_type eq 'SQLite' ) { my $list = $dbh->selectall_arrayref("pragma index_info('$args{'Name'}')"); return () unless $list && @$list; $res{'Functional'} = 0; $res{'Columns'} = [ map $_->[2], @$list ]; $list = $dbh->selectall_arrayref("pragma index_list('$args{'Table'}')"); $res{'Unique'} = (grep lc $_->[1] eq lc $args{'Name'}, @$list)[0][2]? 1 : 0; } elsif ( $db_type eq 'Oracle' ) { my $index = $dbh->selectrow_arrayref( 'select uniqueness, funcidx_status from all_indexes where lower(table_name) = ? AND lower(index_name) = ? AND LOWER(Owner) = ?', undef, lc $args{'Table'}, lc $args{'Name'}, lc RT->Config->Get('DatabaseUser'), ); return () unless $index && @$index; $res{'Unique'} = $index->[0] eq 'UNIQUE'? 1 : 0; $res{'Functional'} = $index->[1] ? 1 : 0; my %columns = map @$_, @{ $dbh->selectall_arrayref( 'select column_position, column_name from all_ind_columns where lower(table_name) = ? AND lower(index_name) = ? AND LOWER(index_owner) = ?', undef, lc $args{'Table'}, lc $args{'Name'}, lc RT->Config->Get('DatabaseUser'), ) }; $columns{ $_->[0] } = $_->[1] foreach @{ $dbh->selectall_arrayref( 'select column_position, column_expression from all_ind_expressions where lower(table_name) = ? AND lower(index_name) = ? AND LOWER(index_owner) = ?', undef, lc $args{'Table'}, lc $args{'Name'}, lc RT->Config->Get('DatabaseUser'), ) }; $res{'Columns'} = [ map $columns{$_}, sort { $a <=> $b } keys %columns ]; foreach my $column ( @{$res{'Columns'}} ) { next unless $column =~ s/^lower\( \s* " (\w+) " \s* \)$/$1/ix; $res{'CaseInsensitive'}{ lc $1 } = 1; } } else { die "Not implemented"; } $_ = lc $_ foreach @{ $res{'Columns'} }; return %res; } sub DropIndex { my $self = shift; my %args = (Table => undef, Name => undef, @_); my $db_type = RT->Config->Get('DatabaseType'); my $dbh = $self->dbh; local $dbh->{'PrintError'} = 0; local $dbh->{'RaiseError'} = 0; my $res; if ( $db_type eq 'mysql' ) { $args{'Table'} = $self->_CanonicTableNameMysql( $args{'Table'} ); $res = $dbh->do( 'drop index '. $dbh->quote_identifier($args{'Name'}) ." on $args{'Table'}", ); } elsif ( $db_type eq 'Pg' ) { $res = $dbh->do("drop index $args{'Name'} CASCADE"); } elsif ( $db_type eq 'SQLite' ) { $res = $dbh->do("drop index $args{'Name'}"); } elsif ( $db_type eq 'Oracle' ) { my $user = RT->Config->Get('DatabaseUser'); # Check if it has constraints associated with it my ($constraint) = $dbh->selectrow_arrayref( 'SELECT constraint_name, table_name FROM all_constraints WHERE LOWER(owner) = ? AND LOWER(index_name) = ?', undef, lc $user, lc $args{'Name'} ); if ($constraint) { my ($constraint_name, $table) = @{$constraint}; $res = $dbh->do("ALTER TABLE $user.$table DROP CONSTRAINT $constraint_name"); } else { $res = $dbh->do("DROP INDEX $user.$args{'Name'}"); } } else { die "Not implemented"; } my $desc = $self->IndexDescription( %args ); return ($res, $res? "Dropped $desc" : "Couldn't drop $desc: ". $dbh->errstr); } sub _CanonicTableNameMysql { my $self = shift; my $table = shift; return $table unless $table; # table name can be case sensitivity in DDL # use LOWER to workaround mysql "bug" return ($self->dbh->selectrow_array( 'SELECT table_name FROM information_schema.tables WHERE table_schema = ? AND LOWER(table_name) = ?', undef, scalar RT->Config->Get('DatabaseName'), lc $table ))[0] || $table; } sub DropIndexIfExists { my $self = shift; my %args = (Table => undef, Name => undef, @_); my %indexes = $self->Indexes; return (1, ucfirst($self->IndexDescription( %args )) ." doesn't exists") unless grep $_ eq lc $args{'Name'}, @{ $indexes{ lc $args{'Table'} } || []}; return $self->DropIndex(%args); } sub CreateIndex { my $self = shift; my %args = ( Table => undef, Name => undef, Columns => [], CaseInsensitive => {}, @_ ); $args{'Table'} = $self->_CanonicTableNameMysql( $args{'Table'} ) if RT->Config->Get('DatabaseType') eq 'mysql'; my $name = $args{'Name'}; unless ( $name ) { my %indexes = $self->Indexes; %indexes = map { $_ => 1 } @{ $indexes{ lc $args{'Table'} } || [] }; my $i = 1; $i++ while $indexes{ lc($args{'Table'}).$i }; $name = lc($args{'Table'}).$i; } my @columns = @{ $args{'Columns'} }; if ( $self->CaseSensitive ) { foreach my $column ( @columns ) { next unless $args{'CaseInsensitive'}{ lc $column }; $column = "LOWER($column)"; } } my $sql = "CREATE" . ($args{'Unique'}? ' UNIQUE' : '') ." INDEX $name ON $args{'Table'}" ."(". join( ', ', @columns ) .")" ; my $res = $self->dbh->do( $sql ); unless ( $res ) { return ( undef, "Failed to create ". $self->IndexDescription( %args ) ." (sql: $sql): ". $self->dbh->errstr ); } return ($name, "Created ". $self->IndexDescription( %args ) ); } sub IndexDescription { my $self = shift; my %args = (@_); my $desc = ($args{'Unique'}? 'unique ' : '') .'index' . ($args{'Name'}? " $args{'Name'}" : '') . ( @{$args{'Columns'}||[]}? " (" . join(', ', @{$args{'Columns'}}) . (@{$args{'Optional'}||[]}? '['. join(', ', '', @{$args{'Optional'}}).']' : '' ) .")" : '' ) . ($args{'Table'}? " on $args{'Table'}" : '') ; return $desc; } sub MakeSureIndexExists { my $self = shift; my %args = ( Table => undef, Columns => [], Optional => [], @_ ); my @list = $self->IndexesThatBeginWith( Table => $args{'Table'}, Columns => [@{$args{'Columns'}}, @{$args{'Optional'}}], ); if (@list) { RT->Logger->debug( ucfirst $self->IndexDescription( Table => $args{'Table'}, Columns => [@{$args{'Columns'}}, @{$args{'Optional'}}], ). ' exists.' ); return; } @list = $self->IndexesThatBeginWith( Table => $args{'Table'}, Columns => $args{'Columns'}, ); if ( !@list ) { my ($status, $msg) = $self->CreateIndex( Table => $args{'Table'}, Columns => [@{$args{'Columns'}}, @{$args{'Optional'}}], ); my $method = $status ? 'debug' : 'warning'; RT->Logger->$method($msg); } else { RT->Logger->info( ucfirst $self->IndexDescription( %{$list[0]} ) .' exists, you may consider replacing it with ' . $self->IndexDescription( Table => $args{'Table'}, Columns => [@{$args{'Columns'}}, @{$args{'Optional'}}], ) ); } } sub DropIndexesThatArePrefix { my $self = shift; my %args = ( Table => undef, Columns => [], @_ ); my @list = $self->IndexesThatBeginWith( Table => $args{'Table'}, Columns => [$args{'Columns'}[0]], ); my $checking = join ',', map lc $_, @{ $args{'Columns'} }, ''; foreach my $i ( splice @list ) { my $columns = join ',', @{ $i->{'Columns'} }, ''; next unless $checking =~ /^\Q$columns/i; push @list, $i; } pop @list; foreach my $i ( @list ) { my ($status, $msg) = $self->DropIndex( Table => $i->{'Table'}, Name => $i->{'Name'}, ); my $method = $status ? 'debug' : 'warning'; RT->Logger->$method($msg); } } # log a mason stack trace instead of a Carp::longmess because it's less painful # and uses mason component paths properly sub _LogSQLStatement { my $self = shift; my $statement = shift; my $duration = shift; my @bind = @_; require HTML::Mason::Exceptions; push @{$self->{'StatementLog'}} , ([Time::HiRes::time(), $statement, [@bind], $duration, HTML::Mason::Exception->new->as_string]); } # helper in a few cases where we do SQL by hand sub __MakeClauseCaseInsensitive { my $self = shift; return join ' ', @_ unless $self->CaseSensitive; my ($field, $op, $value) = $self->_MakeClauseCaseInsensitive(@_); return "$field $op $value"; } sub _TableNames { my $self = shift; my $dbh = shift || $self->dbh; { local $@; if ( $dbh->{Driver}->{Name} eq 'Pg' && $dbh->{'pg_server_version'} >= 90200 && !eval { DBD::Pg->VERSION('2.19.3'); 1 } ) { die "You're using PostgreSQL 9.2 or newer. You have to upgrade DBD::Pg module to 2.19.3 or newer: $@"; } } my @res; my $sth = $dbh->table_info( '', undef, undef, "'TABLE'"); while ( my $table = $sth->fetchrow_hashref ) { push @res, $table->{TABLE_NAME} || $table->{table_name}; } return @res; } sub _UpdateOrDeleteObject { my $self = shift; my $class = shift; my $values = shift; my $refs = shift; if ( delete $values->{_Updated} ) { return $self->_UpdateObject( $class, $values, $refs ); } elsif ( delete $values->{_Deleted} ) { return $self->_DeleteObject( $class, $values, $refs ); } else { RT->Logger->error( "Unknown action in $class" ); return; } } sub _UpdateObject { my $self = shift; my $class = shift; my $values = shift; my $refs = shift; my $object = $self->_LoadObject($class, $values); return unless $object; my $original = delete $values->{_Original}; for my $type ( qw/Attributes CustomFields Topics/ ) { if ( my $items = delete $values->{$type} ) { if ( $type eq 'Attributes' ) { for my $item ( @$items ) { my $attributes = $object->Attributes; $attributes->Limit( FIELD => 'Name', VALUE => $item->{Name} ); $attributes->Limit( FIELD => 'Description', VALUE => $item->{Description} ); if ( my $attribute = $attributes->First ) { $item->{_Updated} = 1; $item->{_Original} = { Name => $item->{Name}, Description => $item->{Description}, ObjectType => $class, ObjectId => $object->Name, }; } } } elsif ( $type eq 'CustomFields' ) { my @new_items; my ( %old, %new ); for my $item ( @$items ) { $self->_CanonilizeObjectCustomFieldValue( $item ); push @{ $new{ $item->{CustomField} } }, $item; } for my $name ( keys %new ) { my $cf = $object->LoadCustomFieldByIdentifier( $name ); if ( $cf->id ) { $old{$name} = $object->CustomFieldValues( $cf ); } } for my $item ( @$items ) { next if $old{ $item->{CustomField} } && $old{ $item->{CustomField} }->HasEntry( $item->{Content}, $item->{LargeContent} ); push @new_items, $item; } for my $name ( keys %old ) { my $ocfvs = $old{$name}; while ( my $ocfv = $ocfvs->Next ) { next if grep { ( $ocfv->Content // '' ) eq ( $_->{Content} // '' ) && ( $ocfv->LargeContent // '' ) eq ( $_->{LargeContent} // '' ) } @{ $new{$name} }; my ( $ret, $msg ) = $ocfv->Delete(); unless ( $ret ) { RT->Logger->error( "Couldn't delete ocfv#" . $ocfv->id . ": $msg" ); } } } @$items = @new_items; } elsif ( $type eq 'Topics' ) { my @new_items; my %old = map { $_->Topic->Name => 1 } @{ $object->Topics->ItemsArrayRef || [] }; my %new = map { $_->{Topic} => 1 } @$items; for my $item ( @$items ) { next if $old{ $item->{Topic} }; push @new_items, $item; } for my $name ( keys %old ) { next if $new{$name}; my $topic = RT::Topic->new( RT->SystemUser ); $topic->Load( $name ); if ( $topic->id ) { my ( $ret, $msg ) = $object->DeleteTopic( Topic => $topic->id ); unless ( $ret ) { RT->Logger->error( "Couldn't delete topic#" . $topic->id . "from object#" . $object->id . ": $msg" ); } } else { RT->Logger->error( "Couldn't load topic $name" ); } } @$items = @new_items; } for my $item ( @$items ) { $item->{Object} = $object; } if ( $refs->{$type} ) { push @{ $refs->{$type} }, @$items; } else { RT->Logger->error( "$class doesn't support $type in initialdata" ); } } } for my $field ( sort { $a eq 'ApplyTo' || $b eq 'ApplyTo' ? 1 : 0 } keys %$values ) { if ( $class eq 'RT::Attribute' ) { if ( $field eq 'Content' ) { $self->_CanonilizeAttributeContent( $values ); } } my $value = $values->{$field}; if ( $class eq 'RT::CustomField' ) { if ( $field eq 'ApplyTo' ) { my %current; my %new; my $ocfs = RT::ObjectCustomFields->new(RT->SystemUser); $ocfs->LimitToCustomField($object->id); while ( my $ocf = $ocfs->Next ) { if ( $ocf->ObjectId == 0 ) { $current{0} = 1; } else { my $added = $object->RecordClassFromLookupType->new( RT->SystemUser ); $current{$ocf->ObjectId} = 1; } } for my $item ( @{ $value || [] } ) { if ( $item eq 0 ) { $new{0} = 1; } else { my $added = $object->RecordClassFromLookupType->new( RT->SystemUser ); $added->Load($item); if ( $added->id ) { $new{$added->id} = 1; } } } for my $id ( keys %current ) { next if $new{$id}; my $ocf = RT::ObjectCustomField->new(RT->SystemUser); $ocf->LoadByCols( CustomField => $object->id, ObjectId => $id ); if ( $ocf->id ) { my ( $ret, $msg) = $ocf->Delete; if ( !$ret ) { RT->Logger->error( "Couldn't delete ObjectCustomField #" . $ocf->id . ": $msg" ); } } } for my $id ( keys %new ) { next if $current{$id}; my $ocf = RT::ObjectCustomField->new( RT->SystemUser ); $ocf->LoadByCols( CustomField => $object->id, ObjectId => $id ); if ( !$ocf->id ) { my ( $ret, $msg ) = $ocf->Create( CustomField => $object->id, ObjectId => $id ); if ( !$ret ) { RT->Logger->error( "Couldn't create ObjectCustomField for CustomField #" . $object->id . " and ObjectId #$id: $msg" ); } } } next; } elsif ( $field eq 'Values' ) { my %current; my %new; my $cfvs = $object->Values; # Only support core cfvs for now next unless $cfvs->isa( 'RT::CustomFieldValues' ); while ( my $cfv = $cfvs->Next ) { $current{ $cfv->Name } = $cfv; } for my $item ( @{ $value || [] } ) { $new{ $item->{Name} } = $item; } for my $name ( keys %current ) { if ( $new{$name} ) { for my $column ( qw/Description Category SortOrder/ ) { if ( ( $current{$name}->$column // '' ) ne ( $new{$name}{$column} // '' ) ) { my ( $ret, $msg ) = $current{$name}->_Set( Field => $column, Value => $new{$name}{$column} ); unless ( $ret ) { RT->Logger->error( "Couldn't update CustomFieldValue#" . $current{$name}->id . " $column to $new{$name}{$column}: $msg" ); next; } } } } else { my ( $ret, $msg ) = $current{$name}->Delete; if ( !$ret ) { RT->Logger->error( "Couldn't delete CustomFieldValue #" . $current{$name}->id . ": $msg" ); } } } for my $name ( keys %new ) { next if $current{$name}; my ( $ret, $msg ) = $object->AddValue( map { $_ => $new{$name}{$_} } qw/Name Description Category SortOrder/ ); if ( !$ret ) { RT->Logger->error( "Couldn't create CustomFieldValue $new{$name}{Name} to CustomField #" . $object->id . ": $msg" ); } } next; } } next unless $object->can( $field ) || $object->_Accessible( $field, 'read' ); my $old_value = $object->can( $field ) ? $object->$field : $object->_Value( $field ); if ( ( $old_value // '' ) ne ( $values->{$field} // '' ) ) { my $set_method = "Set$field"; if ( $object->can( $set_method ) || $object->_Accessible( $field, 'write' ) ) { my ( $ret, $msg ); if ( $object->can( $set_method ) ) { ( $ret, $msg ) = $object->$set_method( $values->{$field} ); } elsif ( $object->_Accessible( $field, 'write' ) ) { ( $ret, $msg ) = $object->_Set( Field => $field, Value => $value ); } unless ( $ret ) { RT->Logger->error( "Couldn't update $class#" . $object->id . " $field to $value: $msg" ); next; } } else { RT->Logger->error( "Couldn't update $field for $class" ); } } } } sub _DeleteObject { my $self = shift; my $class = shift; my $values = shift; my $object = $self->_LoadObject( $class, $values ); return unless $object; my ( $return, $msg ) = $object->Delete(); unless ( $return ) { $RT::Logger->error( $msg ); } } sub _LoadObject { my $self = shift; my $class = shift; my $values = shift; if ( $class eq 'RT::System' ) { return RT->System; } my $object = $class->new( RT->SystemUser ); if ( !ref $values ) { if ( $class eq 'RT::Group' ) { $object->LoadUserDefinedGroup( $values ); } else { $object->Load( $values ); } return $object->id ? $object : undef; } if ( $class eq 'RT::ACE' ) { my ( $principal_id, $principal_type ); if ( $values->{_Original}{UserId} ) { my $user = RT::User->new(RT->SystemUser); $user->Load( $values->{_Original}{UserId} ); if ( $user->id ) { $principal_id = $user->PrincipalId; $principal_type = 'User'; } else { RT->Logger->error( "Couldn't load user $values->{_Original}{UserId}" ); return; } } elsif ( $values->{_Original}{GroupId} ) { my $group = RT::Group->new(RT->SystemUser); $group->LoadByCols( Domain => $values->{_Original}{GroupDomain}, Name => $values->{_Original}{GroupId} ); if ( $group->id ) { $principal_id = $group->PrincipalId; $principal_type = 'Group'; } else { RT->Logger->error( "Couldn't load group $values->{_Original}{UserId}" ); return; } } else { RT->Logger->error( "Invalid principal type in $class" ); return; } $object->LoadByValues( PrincipalId => $principal_id, PrincipalType => $principal_type, map { $_ => $values->{_Original}{$_} } grep { $values->{_Original}{$_} } qw/ObjectType ObjectId RightName/, ); } elsif ( $class eq 'RT::GroupMember' ) { my $group = RT::Group->new( RT->SystemUser ); $group->LoadUserDefinedGroup( $values->{_Original}{Group} ); unless ( $group->id ) { RT->Logger->error( "Couldn't load group $values->{_Original}{Group}" ); return; } my $member; if ( $values->{_Original}{Class} eq 'RT::User' ) { $member = RT::User->new( RT->SystemUser ); $member->Load( $values->{_Original}{Name} ); unless ( $member->id ) { RT->Logger->error( "Couldn't load user $values->{_Original}{Name}" ); return; } } else { $member = RT::Group->new( RT->SystemUser ); $member->LoadUserDefinedGroup( $values->{_Original}{Name} ); unless ( $member->id ) { RT->Logger->error( "Couldn't load group $values->{_Original}{Name}" ); return; } } $object->LoadByCols( Group => $group->PrincipalObj, Member => $member->PrincipalObj ); } elsif ( $class eq 'RT::Template' ) { $object->LoadByName( Name => $values->{_Original}{Name}, Queue => $values->{_Original}{Queue} ); } elsif ( $class eq 'RT::Scrip' ) { $object->LoadByCols( Description => $values->{_Original}{Description} ); } else { if ( $class eq 'RT::Group' ) { $object->LoadByCols( Domain => $values->{Domain}, Name => $values->{_Original}{Name} ); } elsif ( $class eq 'RT::Attribute' ) { my $obj = $values->{_Original}{Object}; if ( $obj eq 'RT::System' ) { $obj = RT->System; } elsif ( my $class = $values->{_Original}{ObjectType} ) { my $id = $obj; $obj = $class->new( RT->SystemUser ); if ( $class eq 'RT::Group' ) { $obj->LoadUserDefinedGroup( $id ); } else { $obj->Load( $id ); } if ( !$obj->id ) { $RT::Logger->error( "Failed to load object $class-$id" ); return; } } else { $RT::Logger->error( "Invalid object $obj" ); return; } my $attributes = $obj->Attributes; $attributes->Limit( FIELD => 'Name', VALUE => $values->{_Original}{Name} ); $attributes->Limit( FIELD => 'Description', VALUE => $values->{_Original}{Description} ); if ( my $attribute = $attributes->First ) { $object = $attribute; } } else { $object->Load( $values->{_Original}{Name} ); } } return $object->id ? $object : undef; } sub _CanonilizeAttributeContent { my $self = shift; my $item = shift or return; if ( $item->{Name} =~ /HomepageSettings$/ ) { my $content = $item->{Content}; for my $type ( qw/body sidebar/ ) { if ( $content->{$type} && ref $content->{$type} eq 'ARRAY' ) { for my $entry ( @{ $content->{$type} } ) { if ( $entry->{ObjectType} && $entry->{ObjectId} && $entry->{Description} ) { if ( my $object = $self->_LoadObject( $entry->{ObjectType}, $entry->{ObjectId} ) ) { my $attributes = $object->Attributes; $attributes->Limit( FIELD => 'Name', VALUE => 'SavedSearch' ); $attributes->Limit( FIELD => 'Description', VALUE => $entry->{Description} ); if ( my $attribute = $attributes->First ) { $entry->{name} = ref( $object ) . '-' . $object->Id . '-SavedSearch-' . $attribute->Id; } } delete $entry->{$_} for qw/ObjectType ObjectId Description/; } } } } } elsif ( $item->{Name} eq 'Dashboard' ) { my $content = $item->{Content}{Panes}; for my $type ( qw/body sidebar/ ) { if ( $content->{$type} && ref $content->{$type} eq 'ARRAY' ) { for my $entry ( @{ $content->{$type} } ) { if ( $entry->{ObjectType} && $entry->{ObjectId} && $entry->{Description} ) { if ( my $object = $self->_LoadObject( $entry->{ObjectType}, $entry->{ObjectId} ) ) { my $attributes = $object->Attributes; $attributes->Limit( FIELD => 'Name', VALUE => 'SavedSearch' ); $attributes->Limit( FIELD => 'Description', VALUE => $entry->{Description} ); if ( my $attribute = $attributes->First ) { $entry->{id} = $attribute->id; $entry->{privacy} = ref( $object ) . '-' . $object->Id; } } delete $entry->{$_} for qw/ObjectType ObjectId Description/; } } } } } elsif ( $item->{Name} eq 'Pref-DashboardsInMenu' ) { my @dashboards; for my $entry ( @{ $item->{Content}{dashboards} } ) { if ( $entry->{ObjectType} && $entry->{ObjectId} && $entry->{Description} ) { if ( my $object = $self->_LoadObject( $entry->{ObjectType}, $entry->{ObjectId} ) ) { my $attributes = $object->Attributes; $attributes->Limit( FIELD => 'Name', VALUE => 'Dashboard' ); $attributes->Limit( FIELD => 'Description', VALUE => $entry->{Description} ); if ( my $attribute = $attributes->First ) { push @dashboards, $attribute->id; } } } } $item->{Content}{dashboards} = \@dashboards; } elsif ( $item->{Name} eq 'Subscription' ) { my $entry = $item->{Content}{DashboardId}; if ( $entry->{ObjectType} && $entry->{ObjectId} && $entry->{Description} ) { if ( my $object = $self->_LoadObject( $entry->{ObjectType}, $entry->{ObjectId} ) ) { my $attributes = $object->Attributes; $attributes->Limit( FIELD => 'Name', VALUE => 'Dashboard' ); $attributes->Limit( FIELD => 'Description', VALUE => $entry->{Description} ); if ( my $attribute = $attributes->First ) { $item->{Content}{DashboardId} = $attribute->Id; $item->{Description} = 'Subscription to dashboard ' . $attribute->Id; } } } } } sub _CanonilizeObjectCustomFieldValue { my $self = shift; my $item = shift; if ( $item->{LargeContent} ) { if ( ( $item->{ContentEncoding} // '' ) eq 'base64' ) { $item->{LargeContent} = MIME::Base64::decode_base64( $item->{LargeContent} ); delete $item->{ContentEncoding}; } else { $item->{LargeContent} = Encode::encode( 'UTF-8', $item->{'LargeContent'} ) if utf8::is_utf8( $item->{LargeContent} ); } } } __PACKAGE__->FinalizeDatabaseType; RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Graph/������������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 015135� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action.pm���������������������������������������������������������������������������000644 �000765 �000024 �00000010326 14005011336 015651� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Action - a generic baseclass for RT Actions =head1 SYNOPSIS use RT::Action; =head1 DESCRIPTION =head1 METHODS =cut package RT::Action; use strict; use warnings; use Scalar::Util; use base qw/RT::Base/; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; bless ($self, $class); $self->_Init(@_); return $self; } sub _Init { my $self = shift; my %args = ( Argument => undef, CurrentUser => undef, ScripActionObj => undef, ScripObj => undef, TemplateObj => undef, TicketObj => undef, TransactionObj => undef, Type => undef, @_ ); $self->{'Argument'} = $args{'Argument'}; $self->CurrentUser( $args{'CurrentUser'}); $self->{'ScripActionObj'} = $args{'ScripActionObj'}; $self->{'ScripObj'} = $args{'ScripObj'}; $self->{'TemplateObj'} = $args{'TemplateObj'}; $self->{'TicketObj'} = $args{'TicketObj'}; $self->{'TransactionObj'} = $args{'TransactionObj'}; $self->{'Type'} = $args{'Type'}; Scalar::Util::weaken($self->{'ScripActionObj'}); Scalar::Util::weaken($self->{'ScripObj'}); Scalar::Util::weaken($self->{'TemplateObj'}); Scalar::Util::weaken($self->{'TicketObj'}); Scalar::Util::weaken($self->{'TransactionObj'}); } # Access Scripwide data sub Argument { my $self = shift; return($self->{'Argument'}); } sub TicketObj { my $self = shift; return($self->{'TicketObj'}); } sub TransactionObj { my $self = shift; return($self->{'TransactionObj'}); } sub TemplateObj { my $self = shift; return($self->{'TemplateObj'}); } sub ScripObj { my $self = shift; return($self->{'ScripObj'}); } sub ScripActionObj { my $self = shift; return($self->{'ScripActionObj'}); } sub Type { my $self = shift; return($self->{'Type'}); } # Scrip methods #Do what we need to do and send it out. sub Commit { my $self = shift; return(0, $self->loc("Commit Stubbed")); } #What does this type of Action does sub Describe { my $self = shift; return $self->loc("No description for [_1]", ref $self); } #Parse the templates, get things ready to go. sub Prepare { my $self = shift; return (0, $self->loc("Prepare Stubbed")); } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Lifecycle/��������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 015773� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ObjectCustomField.pm����������������������������������������������������������������000644 �000765 �000024 �00000015430 14005011336 020002� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::ObjectCustomField; use base 'RT::Record::AddAndSort'; use RT::CustomField; use RT::ObjectCustomFields; sub Table {'ObjectCustomFields'} sub ObjectCollectionClass { my $self = shift; my %args = (@_); return $args{'CustomField'}->CollectionClassFromLookupType; } # XXX: Where is ACL check when we create a record? =head2 CustomFieldObj Returns the CustomField Object which has the id returned by CustomField =cut sub CustomFieldObj { my $self = shift; my $id = shift || $self->CustomField; # To find out the proper context object to load the CF with, we need # data from the CF -- namely, the record class. Go find that as the # system user first. my $system_CF = RT::CustomField->new( RT->SystemUser ); $system_CF->Load( $id ); my $class = $system_CF->RecordClassFromLookupType; my $obj = $class->new( $self->CurrentUser ); $obj->Load( $self->ObjectId ); my $CF = RT::CustomField->new( $self->CurrentUser ); $CF->SetContextObject( $obj ); $CF->Load( $id ); return $CF; } sub Neighbors { my $self = shift; my %args = @_; my $res = $self->CollectionClass->new( $self->CurrentUser ); $res->LimitToLookupType( ($args{'CustomField'} || $self->CustomFieldObj)->LookupType ); return $res; } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 CustomField Returns the current value of CustomField. (In the database, CustomField is stored as int(11).) =head2 SetCustomField VALUE Set CustomField to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, CustomField will be stored as a int(11).) =cut =head2 ObjectId Returns the current value of ObjectId. (In the database, ObjectId is stored as int(11).) =head2 SetObjectId VALUE Set ObjectId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectId will be stored as a int(11).) =cut =head2 SortOrder Returns the current value of SortOrder. (In the database, SortOrder is stored as int(11).) =head2 SetSortOrder VALUE Set SortOrder to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, SortOrder will be stored as a int(11).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, CustomField => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, ObjectId => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, SortOrder => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); $deps->Add( out => $self->CustomFieldObj ); if ($self->ObjectId) { my $class = $self->CustomFieldObj->RecordClassFromLookupType; my $obj = $class->new( $self->CurrentUser ); $obj->Load( $self->ObjectId ); $deps->Add( out => $obj ); } } sub Serialize { my $self = shift; my %args = (@_); my %store = $self->SUPER::Serialize(@_); if ($store{ObjectId}) { my $class = $self->CustomFieldObj->RecordClassFromLookupType; my $obj = $class->new( RT->SystemUser ); $obj->Load( $store{ObjectId} ); $store{ObjectId} = \($obj->UID); } return %store; } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Reminders.pm������������������������������������������������������������������������000644 �000765 �000024 �00000012053 14005011336 016363� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Reminders; use strict; use warnings; use base 'RT::Base'; sub new { my $class = shift; my $self = {}; bless $self, $class; $self->CurrentUser(@_); return($self); } sub Ticket { my $self = shift; $self->{'_ticket'} = shift if (@_); return ($self->{'_ticket'}); } sub TicketObj { my $self = shift; unless ($self->{'_ticketobj'}) { $self->{'_ticketobj'} = RT::Ticket->new($self->CurrentUser); $self->{'_ticketobj'}->Load($self->Ticket); } return $self->{'_ticketobj'}; } =head2 Collection Returns an RT::Tickets object containing reminders for this object's "Ticket" =cut sub Collection { my $self = shift; my $col = RT::Tickets->new($self->CurrentUser); my $query = 'Type = "reminder" AND RefersTo = "'.$self->Ticket.'"'; $col->FromSQL($query); $col->OrderByCols( { FIELD => 'Due' }, { FIELD => 'id' } ); return($col); } =head2 Add Add a reminder for this ticket. Takes Subject Owner Due =cut sub Add { my $self = shift; my %args = ( Subject => undef, Owner => undef, Due => undef, @_ ); my $ticket = RT::Ticket->new($self->CurrentUser); $ticket->Load($self->Ticket); if ( !$ticket->id ) { return ( 0, $self->loc( "Failed to load ticket [_1]", $self->Ticket ) ); } if ( lc $ticket->Status eq 'deleted' ) { return ( 0, $self->loc("Can't link to a deleted ticket") ); } return ( 0, $self->loc('Permission Denied') ) unless $self->CurrentUser->HasRight( Right => 'CreateTicket', Object => $self->TicketObj->QueueObj, ) && $self->CurrentUser->HasRight( Right => 'ModifyTicket', Object => $self->TicketObj, ); my $reminder = RT::Ticket->new($self->CurrentUser); # the 2nd return value is txn id, which is useless here my ( $status, undef, $msg ) = $reminder->Create( Subject => $args{'Subject'}, Owner => $args{'Owner'}, Due => $args{'Due'}, RefersTo => $self->Ticket, Type => 'reminder', Queue => $self->TicketObj->Queue, Status => $self->TicketObj->QueueObj->LifecycleObj->ReminderStatusOnOpen, ); $self->TicketObj->_NewTransaction( Type => 'AddReminder', Field => 'RT::Ticket', NewValue => $reminder->id ) if $status; return ( $status, $msg ); } sub Open { my $self = shift; my $reminder = shift; my ( $status, $msg ) = $reminder->SetStatus( $reminder->LifecycleObj->ReminderStatusOnOpen ); $self->TicketObj->_NewTransaction( Type => 'OpenReminder', Field => 'RT::Ticket', NewValue => $reminder->id ) if $status; return ( $status, $msg ); } sub Resolve { my $self = shift; my $reminder = shift; my ( $status, $msg ) = $reminder->SetStatus( $reminder->LifecycleObj->ReminderStatusOnResolve ); $self->TicketObj->_NewTransaction( Type => 'ResolveReminder', Field => 'RT::Ticket', NewValue => $reminder->id ) if $status; return ( $status, $msg ); } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Groups.pm���������������������������������������������������������������������������000644 �000765 �000024 �00000036402 14005011336 015716� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Groups - a collection of RT::Group objects =head1 SYNOPSIS use RT::Groups; my $groups = RT::Groups->new($CurrentUser); $groups->UnLimit(); while (my $group = $groups->Next()) { print $group->Id ." is a group id\n"; } =head1 DESCRIPTION =head1 METHODS =cut package RT::Groups; use strict; use warnings; use base 'RT::SearchBuilder'; sub Table { 'Groups'} use RT::Group; use RT::Users; # XXX: below some code is marked as subject to generalize in Groups, Users classes. # RUZ suggest name Principals::Generic or Principals::Base as abstract class, but # Jesse wants something that doesn't imply it's a Principals.pm subclass. # See comments below for candidats. sub _Init { my $self = shift; $self->{'with_disabled_column'} = 1; my @result = $self->SUPER::_Init(@_); $self->OrderBy( ALIAS => 'main', FIELD => 'Name', ORDER => 'ASC'); # XXX: this code should be generalized $self->{'princalias'} = $self->Join( ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'Principals', FIELD2 => 'id' ); # even if this condition is useless and ids in the Groups table # only match principals with type 'Group' this could speed up # searches in some DBs. $self->Limit( ALIAS => $self->{'princalias'}, FIELD => 'PrincipalType', VALUE => 'Group', ); return (@result); } =head2 PrincipalsAlias Returns the string that represents this Users object's primary "Principals" alias. =cut # XXX: should be generalized, code duplication sub PrincipalsAlias { my $self = shift; return($self->{'princalias'}); } =head2 LimitToSystemInternalGroups Return only SystemInternal Groups, such as "privileged" "unprivileged" and "everyone" =cut sub LimitToSystemInternalGroups { my $self = shift; $self->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'SystemInternal', CASESENSITIVE => 0 ); # All system internal groups have the same instance. No reason to limit down further #$self->Limit(FIELD => 'Instance', OPERATOR => '=', VALUE => '0'); } =head2 LimitToUserDefinedGroups Return only UserDefined Groups =cut sub LimitToUserDefinedGroups { my $self = shift; $self->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'UserDefined', CASESENSITIVE => 0 ); # All user-defined groups have the same instance. No reason to limit down further #$self->Limit(FIELD => 'Instance', OPERATOR => '=', VALUE => ''); } =head2 LimitToRolesForObject OBJECT Limits the set of groups to role groups specifically for the object in question based on the object's class and ID. If the object has no ID, the roles are not limited by group C<Instance>. That is, calling this method on an unloaded object will find all role groups for that class of object. Replaces L</LimitToRolesForQueue>, L</LimitToRolesForTicket>, and L</LimitToRolesForSystem>. =cut sub LimitToRolesForObject { my $self = shift; my $object = shift; $self->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => ref($object) . "-Role", CASESENSITIVE => 0 ); $self->Limit(FIELD => 'Instance', OPERATOR => '=', VALUE => $object->id); } =head2 WithMember {PrincipalId => PRINCIPAL_ID, Recursively => undef} Limits the set of groups returned to groups which have Principal PRINCIPAL_ID as a member. Returns the alias used for the join. =cut sub WithMember { my $self = shift; my %args = ( PrincipalId => undef, Recursively => undef, @_); my $members = $self->Join( ALIAS1 => 'main', FIELD1 => 'id', $args{'Recursively'} ? (TABLE2 => 'CachedGroupMembers') # (GroupId, MemberId) is unique in GM table : (TABLE2 => 'GroupMembers', DISTINCT => 1) , FIELD2 => 'GroupId', ); $self->Limit(ALIAS => $members, FIELD => 'MemberId', OPERATOR => '=', VALUE => $args{'PrincipalId'}); $self->Limit(ALIAS => $members, FIELD => 'Disabled', VALUE => 0) if $args{'Recursively'}; return $members; } sub WithCurrentUser { my $self = shift; $self->{with_current_user} = 1; return $self->WithMember( PrincipalId => $self->CurrentUser->PrincipalId, Recursively => 1, ); } sub WithoutMember { my $self = shift; my %args = ( PrincipalId => undef, Recursively => undef, @_ ); my $members = $args{'Recursively'} ? 'CachedGroupMembers' : 'GroupMembers'; my $members_alias = $self->Join( TYPE => 'LEFT', FIELD1 => 'id', TABLE2 => $members, FIELD2 => 'GroupId', DISTINCT => $members eq 'GroupMembers', ); $self->Limit( LEFTJOIN => $members_alias, ALIAS => $members_alias, FIELD => 'MemberId', OPERATOR => '=', VALUE => $args{'PrincipalId'}, ); $self->Limit( LEFTJOIN => $members_alias, ALIAS => $members_alias, FIELD => 'Disabled', VALUE => 0 ) if $args{'Recursively'}; $self->Limit( ALIAS => $members_alias, FIELD => 'MemberId', OPERATOR => 'IS', VALUE => 'NULL', QUOTEVALUE => 0, ); } =head2 WithRight { Right => RIGHTNAME, Object => RT::Record, IncludeSystemRights => 1, IncludeSuperusers => 0, EquivObjects => [ ] } Find all groups which have RIGHTNAME for RT::Record. Optionally include global rights and superusers. By default, include the global rights, but not the superusers. =cut #XXX: should be generilized sub WithRight { my $self = shift; my %args = ( Right => undef, Object => => undef, IncludeSystemRights => 1, IncludeSuperusers => undef, IncludeSubgroupMembers => 0, EquivObjects => [ ], @_ ); my $from_role = $self->Clone; $from_role->WithRoleRight( %args ); my $from_group = $self->Clone; $from_group->WithGroupRight( %args ); #XXX: DIRTY HACK use DBIx::SearchBuilder::Union; my $union = DBIx::SearchBuilder::Union->new(); $union->add($from_role); $union->add($from_group); %$self = %$union; bless $self, ref($union); return; } #XXX: methods are active aliases to Users class to prevent code duplication # should be generalized sub _JoinGroups { my $self = shift; my %args = (@_); return 'main' unless $args{'IncludeSubgroupMembers'}; return $self->RT::Users::_JoinGroups( %args ); } sub _JoinGroupMembers { my $self = shift; my %args = (@_); return 'main' unless $args{'IncludeSubgroupMembers'}; return $self->RT::Users::_JoinGroupMembers( %args ); } sub _JoinGroupMembersForGroupRights { my $self = shift; my %args = (@_); my $group_members = $self->_JoinGroupMembers( %args ); unless( $group_members eq 'main' ) { return $self->RT::Users::_JoinGroupMembersForGroupRights( %args ); } $self->Limit( ALIAS => $args{'ACLAlias'}, FIELD => 'PrincipalId', VALUE => "main.id", QUOTEVALUE => 0, ); } sub _JoinACL { return (shift)->RT::Users::_JoinACL( @_ ) } sub _RoleClauses { return (shift)->RT::Users::_RoleClauses( @_ ) } sub _WhoHaveRoleRightSplitted { return (shift)->RT::Users::_WhoHaveRoleRightSplitted( @_ ) } sub _GetEquivObjects { return (shift)->RT::Users::_GetEquivObjects( @_ ) } sub WithGroupRight { return (shift)->RT::Users::WhoHaveGroupRight( @_ ) } sub WithRoleRight { return (shift)->RT::Users::WhoHaveRoleRight( @_ ) } sub ForWhichCurrentUserHasRight { my $self = shift; my %args = ( Right => undef, IncludeSuperusers => undef, @_, ); # Non-disabled groups... $self->LimitToEnabled; # ...which are the target object of an ACL with that right, or # where the target is the system object (a global right) my $acl = $self->_JoinACL( %args ); $self->_AddSubClause( ACLObjects => "( (main.id = $acl.ObjectId AND $acl.ObjectType = 'RT::Group')" . " OR $acl.ObjectType = 'RT::System')"); # ...and where that right is granted to any group.. my $member = $self->Join( ALIAS1 => $acl, FIELD1 => 'PrincipalId', TABLE2 => 'CachedGroupMembers', FIELD2 => 'GroupId', ); $self->Limit( ALIAS => $member, FIELD => 'Disabled', VALUE => '0', ); # ...with the current user in it $self->Limit( ALIAS => $member, FIELD => 'MemberId', VALUE => $self->CurrentUser->Id, ); return; } =head2 LimitToEnabled Only find items that haven't been disabled =cut sub LimitToEnabled { my $self = shift; $self->{'handled_disabled_column'} = 1; $self->Limit( ALIAS => $self->PrincipalsAlias, FIELD => 'Disabled', VALUE => '0', ); } =head2 LimitToDeleted Only find items that have been deleted. =cut sub LimitToDeleted { my $self = shift; $self->{'handled_disabled_column'} = $self->{'find_disabled_rows'} = 1; $self->Limit( ALIAS => $self->PrincipalsAlias, FIELD => 'Disabled', VALUE => 1, ); } sub AddRecord { my $self = shift; my ($record) = @_; # If we've explicitly limited to groups the user is a member of (for # dashboard or savedsearch privacy objects), skip the ACL. return unless $self->{with_current_user} or $record->CurrentUserHasRight('SeeGroup'); return $self->SUPER::AddRecord( $record ); } sub _DoSearch { my $self = shift; #unless we really want to find disabled rows, make sure we're only finding enabled ones. unless($self->{'find_disabled_rows'}) { $self->LimitToEnabled(); } return($self->SUPER::_DoSearch(@_)); } =head2 SimpleSearch Does a 'simple' search of Groups against a specified Term. This Term is compared to a number of fields using various types of SQL comparison operators. Ensures that the returned collection of Groups will have a value for Return. This method is passed the following. You must specify a Term and a Return. Fields - Hashref of data - defaults to C<$GroupSearchFields> emulate that if you want to override Term - String that is in the fields specified by Fields Return - What field on the User you want to be sure isn't empty Exclude - Array reference of ids to exclude Max - Size to limit this collection to =cut sub SimpleSearch { my $self = shift; my %args = ( Fields => RT->Config->Get('GroupSearchFields'), Term => undef, Exclude => [], Return => 'Name', Max => 10, @_ ); return $self unless defined $args{Return} and defined $args{Term} and length $args{Term}; $self->RowsPerPage( $args{Max} ); $self->LimitToUserDefinedGroups(); while (my ($name, $op) = each %{$args{Fields}}) { $op = 'STARTSWITH' unless $op =~ /^(?:LIKE|(?:START|END)SWITH|=|!=)$/i; if ($name =~ /^CF\.(?:\{(.*)}|(.*))$/) { my $cfname = $1 || $2; my $cf = RT::CustomField->new(RT->SystemUser); my ($ok, $msg) = $cf->LoadByName( Name => $cfname, LookupType => 'RT::Group'); if ( $ok ) { $self->LimitCustomField( CUSTOMFIELD => $cf->Id, OPERATOR => $op, VALUE => $args{Term}, ENTRYAGGREGATOR => 'OR', SUBCLAUSE => 'autocomplete', CASESENSITIVE => 0, ); } else { RT->Logger->warning("Asked to search custom field $name but unable to load a Group CF with the name $cfname: $msg"); } } elsif ($name eq 'id' and $op =~ /(?:LIKE|(?:START|END)SWITH)$/i) { $self->Limit( FUNCTION => "CAST( main.$name AS TEXT )", OPERATOR => $op, VALUE => $args{Term}, ENTRYAGGREGATOR => 'OR', SUBCLAUSE => 'autocomplete', CASESENSITIVE => 0, ) if $args{Term} =~ /^\d+$/; } else { $self->Limit( FIELD => $name, OPERATOR => $op, VALUE => $args{Term}, ENTRYAGGREGATOR => 'OR', SUBCLAUSE => 'autocomplete', CASESENSITIVE => 0, ) unless $args{Term} =~ /\D/ and $name eq 'id'; } } # Exclude groups we don't want $self->Limit(FIELD => 'id', OPERATOR => 'NOT IN', VALUE => $args{Exclude} ) if @{$args{Exclude}}; if ( RT->Config->Get('DatabaseType') eq 'Oracle' ) { $self->Limit( FIELD => $args{Return}, OPERATOR => 'IS NOT', VALUE => 'NULL', ); } else { $self->Limit( FIELD => $args{Return}, OPERATOR => '!=', VALUE => '', CASESENSITIVE => 0, ); $self->Limit( FIELD => $args{Return}, OPERATOR => 'IS NOT', VALUE => 'NULL', ENTRYAGGREGATOR => 'AND', CASESENSITIVE => 0, ); } return $self; } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Test.pm�����������������������������������������������������������������������������000644 �000765 �000024 �00000147743 14005011336 015371� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Test; use strict; use warnings; BEGIN { $^W = 1 }; use base 'Test::More'; BEGIN { # Warn about role consumers overriding role methods so we catch it in tests. $ENV{PERL_ROLE_OVERRIDE_WARN} = 1; } # We use the Test::NoWarnings catching and reporting functionality, but need to # wrap it in our own special handler because of the warn handler installed via # RT->InitLogging(). require Test::NoWarnings; my $Test_NoWarnings_Catcher = $SIG{__WARN__}; my $check_warnings_in_end = 1; use Socket; use File::Temp qw(tempfile); use File::Path qw(mkpath); use File::Spec; use File::Which qw(); use Scalar::Util qw(blessed); our @EXPORT = qw(is_empty diag parse_mail works fails plan done_testing); my %tmp = ( directory => undef, config => { RT => undef, apache => undef, }, mailbox => undef, ); my %rttest_opt; =head1 NAME RT::Test - RT Testing =head1 NOTES =head2 COVERAGE To run the rt test suite with coverage support, install L<Devel::Cover> and run: make test RT_DBA_USER=.. RT_DBA_PASSWORD=.. HARNESS_PERL_SWITCHES=-MDevel::Cover cover -ignore_re '^var/mason_data/' -ignore_re '^t/' The coverage tests have DevelMode turned off, and have C<named_component_subs> enabled for L<HTML::Mason> to avoid an optimizer problem in Perl that hides the top-level optree from L<Devel::Cover>. =cut our $port; our @SERVERS; my @ports; # keep track of all the random ports we used BEGIN { delete $ENV{$_} for qw/LANGUAGE LC_ALL LC_MESSAGES LANG/; $ENV{LANG} = "C"; }; sub import { my $class = shift; my %args = @_; %rttest_opt = %args; $rttest_opt{'nodb'} = $args{'nodb'} = 1 if $^C; # Spit out a plan (if we got one) *before* we load modules if ( $args{'tests'} ) { plan( tests => $args{'tests'} ) unless $args{'tests'} eq 'no_declare'; } elsif ( exists $args{'tests'} ) { # do nothing if they say "tests => undef" - let them make the plan } elsif ( $args{'skip_all'} ) { plan(skip_all => $args{'skip_all'}); } else { $class->builder->no_plan unless $class->builder->has_plan; } push @{ $args{'plugins'} ||= [] }, @{ $args{'requires'} } if $args{'requires'}; push @{ $args{'plugins'} ||= [] }, $args{'testing'} if $args{'testing'}; push @{ $args{'plugins'} ||= [] }, split " ", $ENV{RT_TEST_PLUGINS} if $ENV{RT_TEST_PLUGINS}; $class->bootstrap_tempdir; $port = $class->find_idle_port; $class->bootstrap_plugins_paths( %args ); $class->bootstrap_config( %args ); use RT; RT::LoadConfig; RT::InitPluginPaths(); RT::InitClasses(); RT::I18N->Init(); $class->set_config_wrapper; $class->bootstrap_db( %args ); __reconnect_rt() unless $args{nodb}; __init_logging(); RT->Plugins; RT->Config->PostLoadCheck; $class->encode_output; my $screen_logger = $RT::Logger->remove( 'screen' ); require Log::Dispatch::Perl; $RT::Logger->add( Log::Dispatch::Perl->new ( name => 'rttest', min_level => $screen_logger->min_level, action => { error => 'warn', critical => 'warn' } ) ); # XXX: this should really be totally isolated environment so we # can parallelize and be sane mkpath [ $RT::MasonSessionDir ] if RT->Config->Get('DatabaseType'); my $level = 1; while ( my ($package) = caller($level-1) ) { last unless $package =~ /Test/; $level++; } # By default we test HTML templates, but text templates are # available on request if ( $args{'text_templates'} ) { $class->switch_templates_ok('text'); } Test::More->export_to_level($level); Test::NoWarnings->export_to_level($level); # Blow away symbols we redefine to avoid warnings. # better than "no warnings 'redefine'" because we might accidentally # suppress a mistaken redefinition no strict 'refs'; delete ${ caller($level) . '::' }{diag}; delete ${ caller($level) . '::' }{plan}; delete ${ caller($level) . '::' }{done_testing}; __PACKAGE__->export_to_level($level); } sub is_empty($;$) { my ($v, $d) = shift; local $Test::Builder::Level = $Test::Builder::Level + 1; return Test::More::ok(1, $d) unless defined $v; return Test::More::ok(1, $d) unless length $v; return Test::More::is($v, '', $d); } my $created_new_db; # have we created new db? mainly for parallel testing sub db_requires_no_dba { my $self = shift; my $db_type = RT->Config->Get('DatabaseType'); return 1 if $db_type eq 'SQLite'; } sub find_idle_port { my $class = shift; my %ports; # Determine which ports are in use use Fcntl qw(:DEFAULT :flock); my $portfile = "$tmp{'directory'}/../ports"; sysopen(PORTS, $portfile, O_RDWR|O_CREAT) or die "Can't write to ports file $portfile: $!"; flock(PORTS, LOCK_EX) or die "Can't write-lock ports file $portfile: $!"; $ports{$_}++ for split ' ', join("",<PORTS>); # Pick a random port, checking that the port isn't in our in-use # list, and that something isn't already listening there. my $port; { $port = 1024 + int rand(10_000) + $$ % 1024; redo if $ports{$port}; # There is a race condition in here, where some non-RT::Test # process claims the port after we check here but before our # server binds. However, since we mostly care about race # conditions with ourselves under high concurrency, this is # generally good enough. my $paddr = sockaddr_in( $port, inet_aton('localhost') ); socket( SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp') ) or die "socket: $!"; if ( connect( SOCK, $paddr ) ) { close(SOCK); redo; } close(SOCK); } $ports{$port}++; # Write back out the in-use ports seek(PORTS, 0, 0); truncate(PORTS, 0); print PORTS "$_\n" for sort {$a <=> $b} keys %ports; close(PORTS) or die "Can't close ports file: $!"; push @ports, $port; return $port; } sub bootstrap_tempdir { my $self = shift; my ($test_dir, $test_file) = ('t', ''); if (File::Spec->rel2abs($0) =~ m{(?:^|[\\/])(x?t)[/\\](.*)}) { $test_dir = $1; $test_file = "$2-"; $test_file =~ s{[/\\]}{-}g; } my $dir_name = File::Spec->rel2abs("$test_dir/tmp"); mkpath( $dir_name ); return $tmp{'directory'} = File::Temp->newdir( "${test_file}XXXXXXXX", DIR => $dir_name ); } sub bootstrap_config { my $self = shift; my %args = @_; $tmp{'config'}{'RT'} = File::Spec->catfile( "$tmp{'directory'}", 'RT_SiteConfig.pm' ); open( my $config, '>', $tmp{'config'}{'RT'} ) or die "Couldn't open $tmp{'config'}{'RT'}: $!"; my $dbname = $ENV{RT_TEST_PARALLEL}? "rt5test_$port" : "rt5test"; print $config qq{ Set( \$WebDomain, "localhost"); Set( \$WebPort, $port); Set( \$WebPath, ""); Set( \@LexiconLanguages, qw(en zh_TW zh_CN fr ja)); Set( \$RTAddressRegexp , qr/^bad_re_that_doesnt_match\$/i); Set( \$ShowHistory, "always"); }; if ( $ENV{'RT_TEST_DB_SID'} ) { # oracle case print $config "Set( \$DatabaseName , '$ENV{'RT_TEST_DB_SID'}' );\n"; print $config "Set( \$DatabaseUser , '$dbname');\n"; } else { print $config "Set( \$DatabaseName , '$dbname');\n"; print $config "Set( \$DatabaseUser , 'u${dbname}');\n"; } if ( $ENV{'RT_TEST_DB_HOST'} ) { print $config "Set( \$DatabaseHost , '$ENV{'RT_TEST_DB_HOST'}');\n"; } if ( $ENV{'RT_TEST_RT_HOST'} ) { # Used to add rights for test users in the DB when testing mysql/mariadb print $config "Set( \$DatabaseRTHost , '$ENV{'RT_TEST_RT_HOST'}');\n"; } if ( $args{'plugins'} ) { print $config "Set( \@Plugins, qw(". join( ' ', @{ $args{'plugins'} } ) .") );\n"; my $plugin_data = File::Spec->rel2abs("t/data/plugins"); print $config qq[\$RT::PluginPath = "$plugin_data";\n]; } if ( $INC{'Devel/Cover.pm'} ) { print $config "Set( \$DevelMode, 0 );\n"; } elsif ( $ENV{RT_TEST_DEVEL} ) { print $config "Set( \$DevelMode, 1 );\n"; } else { print $config "Set( \$DevelMode, 0 );\n"; } $self->bootstrap_logging( $config ); # set mail catcher my $mail_catcher = $tmp{'mailbox'} = File::Spec->catfile( $tmp{'directory'}->dirname, 'mailbox.eml' ); print $config <<END; Set( \$MailCommand, sub { my \$MIME = shift; open( my \$handle, '>>', '$mail_catcher' ) or die "Unable to open '$mail_catcher' for appending: \$!"; \$MIME->print(\$handle); print \$handle "%% split me! %%\n"; close \$handle; } ); END $self->bootstrap_more_config($config, \%args); print $config $args{'config'} if $args{'config'}; print $config "\n1;\n"; $ENV{'RT_SITE_CONFIG'} = $tmp{'config'}{'RT'}; $ENV{'RT_SITE_CONFIG_DIR'} = '/dev/null'; close $config; return $config; } sub bootstrap_more_config { } sub bootstrap_logging { my $self = shift; my $config = shift; # prepare file for logging $tmp{'log'}{'RT'} = File::Spec->catfile( "$tmp{'directory'}", 'rt.debug.log' ); open( my $fh, '>', $tmp{'log'}{'RT'} ) or die "Couldn't open $tmp{'config'}{'RT'}: $!"; # make world writable so apache under different user # can write into it chmod 0666, $tmp{'log'}{'RT'}; print $config <<END; Set( \$LogToSyslog , undef); Set( \$LogToSTDERR , "warning"); Set( \$LogToFile, 'debug' ); Set( \$LogDir, q{$tmp{'directory'}} ); Set( \$LogToFileNamed, 'rt.debug.log' ); END } sub set_config_wrapper { my $self = shift; my $old_sub = \&RT::Config::Set; no warnings 'redefine'; *RT::Config::WriteSet = sub { my ($self, $name) = @_; my $type = $RT::Config::META{$name}->{'Type'} || 'SCALAR'; my %sigils = ( HASH => '%', ARRAY => '@', SCALAR => '$', ); my $sigil = $sigils{$type} || $sigils{'SCALAR'}; open( my $fh, '<', $tmp{'config'}{'RT'} ) or die "Couldn't open config file: $!"; my @lines; while (<$fh>) { if (not @lines or /^Set\(/) { push @lines, $_; } else { $lines[-1] .= $_; } } close $fh; # Traim trailing newlines and "1;" $lines[-1] =~ s/(^1;\n|^\n)*\Z//m; # Remove any previous definitions of this var @lines = grep {not /^Set\(\s*\Q$sigil$name\E\b/} @lines; # Format the new value for output require Data::Dumper; local $Data::Dumper::Terse = 1; local $Data::Dumper::Deparse = 1; my $dump = Data::Dumper::Dumper([@_[2 .. $#_]]); $dump =~ s/;?\s+\Z//; push @lines, "Set( ${sigil}${name}, \@{". $dump ."});\n"; push @lines, "\n1;\n"; # Re-write the configuration file open( $fh, '>', $tmp{'config'}{'RT'} ) or die "Couldn't open config file: $!"; print $fh $_ for @lines; close $fh; if ( @SERVERS ) { warn "you're changing config option in a test file" ." when server is active"; } return $old_sub->(@_); }; *RT::Config::Set = sub { # Determine if the caller is either from a test script, or # from helper functions called by test script to alter # configuration that should be written. This is necessary # because some extensions (RTIR, for example) temporarily swap # configuration values out and back in Mason during requests. my @caller = caller(1); # preserve list context @caller = caller(0) unless @caller; return RT::Config::WriteSet(@_) if ($caller[1]||'') =~ /\.t$/; return $old_sub->(@_); }; } sub encode_output { my $builder = Test::More->builder; binmode $builder->output, ":encoding(utf8)"; binmode $builder->failure_output, ":encoding(utf8)"; binmode $builder->todo_output, ":encoding(utf8)"; } sub bootstrap_db { my $self = shift; my %args = @_; unless (defined $ENV{'RT_DBA_USER'} && defined $ENV{'RT_DBA_PASSWORD'}) { Test::More::BAIL_OUT( "RT_DBA_USER and RT_DBA_PASSWORD environment variables need" ." to be set in order to run 'make test'" ) unless $self->db_requires_no_dba; } require RT::Handle; if (my $forceopt = $ENV{RT_TEST_FORCE_OPT}) { Test::More::diag "forcing $forceopt"; $args{$forceopt}=1; } # Short-circuit the rest of ourselves if we don't want a db if ($args{nodb}) { __drop_database(); return; } my $db_type = RT->Config->Get('DatabaseType'); if ($db_type eq "SQLite") { RT->Config->WriteSet( DatabaseName => File::Spec->catfile( $self->temp_directory, "rt5test" ) ); } __create_database(); __reconnect_rt('as dba'); $RT::Handle->InsertSchema; $RT::Handle->InsertACL unless $db_type eq 'Oracle'; __init_logging(); __reconnect_rt(); $RT::Handle->InsertInitialData unless $args{noinitialdata}; $RT::Handle->InsertData( $RT::EtcPath . "/initialdata" ) unless $args{noinitialdata} or $args{nodata}; $self->bootstrap_plugins_db( %args ); } sub bootstrap_plugins_paths { my $self = shift; my %args = @_; return unless $args{'plugins'}; my @plugins = @{ $args{'plugins'} }; my $cwd; if ( $args{'testing'} ) { require Cwd; $cwd = Cwd::getcwd(); } require RT::Plugin; my $old_func = \&RT::Plugin::_BasePath; no warnings 'redefine'; *RT::Plugin::_BasePath = sub { my $name = $_[0]->{'name'}; return $cwd if $args{'testing'} && $name eq $args{'testing'}; if ( grep $name eq $_, @plugins ) { my $variants = join "(?:|::|-|_)", map "\Q$_\E", split /::/, $name; my ($path) = map $ENV{$_}, grep /^RT_TEST_PLUGIN_(?:$variants).*_ROOT$/i, keys %ENV; return $path if $path; } return $old_func->(@_); }; } sub bootstrap_plugins_db { my $self = shift; my %args = @_; return unless $args{'plugins'}; require File::Spec; my @plugins = @{ $args{'plugins'} }; foreach my $name ( @plugins ) { my $plugin = RT::Plugin->new( name => $name ); Test::More::diag( "Initializing DB for the $name plugin" ) if $ENV{'TEST_VERBOSE'}; my $etc_path = $plugin->Path('etc'); Test::More::diag( "etc path of the plugin is '$etc_path'" ) if $ENV{'TEST_VERBOSE'}; unless ( -e $etc_path ) { # We can't tell if the plugin has no data, or we screwed up the etc/ path Test::More::ok(1, "There is no etc dir: no schema" ); Test::More::ok(1, "There is no etc dir: no ACLs" ); Test::More::ok(1, "There is no etc dir: no data" ); next; } __reconnect_rt('as dba'); { # schema my ($ret, $msg) = $RT::Handle->InsertSchema( undef, $etc_path ); Test::More::ok($ret || $msg =~ /^Couldn't find schema/, "Created schema: ".($msg||'')); } { # ACLs my ($ret, $msg) = $RT::Handle->InsertACL( undef, $etc_path ); Test::More::ok($ret || $msg =~ /^Couldn't find ACLs/, "Created ACL: ".($msg||'')); } # data my $data_file = File::Spec->catfile( $etc_path, 'initialdata' ); if ( -e $data_file ) { __reconnect_rt(); my ($ret, $msg) = $RT::Handle->InsertData( $data_file );; Test::More::ok($ret, "Inserted data".($msg||'')); } else { Test::More::ok(1, "There is no data file" ); } } __reconnect_rt(); } sub _get_dbh { my ($dsn, $user, $pass) = @_; if ( $dsn =~ /Oracle/i ) { $ENV{'NLS_LANG'} = "AMERICAN_AMERICA.AL32UTF8"; $ENV{'NLS_NCHAR'} = "AL32UTF8"; } my $dbh = DBI->connect( $dsn, $user, $pass, { RaiseError => 0, PrintError => 1 }, ); unless ( $dbh ) { my $msg = "Failed to connect to $dsn as user '$user': ". $DBI::errstr; print STDERR $msg; exit -1; } return $dbh; } sub __create_database { my %args = ( # already dropped db in parallel tests, need to do so for other cases. DropDatabase => $ENV{RT_TEST_PARALLEL} ? 0 : 1, @_, ); # bootstrap with dba cred my $dbh = _get_dbh( RT::Handle->SystemDSN, $ENV{RT_DBA_USER}, $ENV{RT_DBA_PASSWORD} ); if ($args{DropDatabase}) { __drop_database( $dbh ); } RT::Handle->CreateDatabase( $dbh ); $dbh->disconnect; $created_new_db++; } sub __drop_database { my $dbh = shift; # Pg doesn't like if you issue a DROP DATABASE while still connected # it's still may fail if web-server is out there and holding a connection __disconnect_rt(); my $my_dbh = $dbh? 0 : 1; $dbh ||= _get_dbh( RT::Handle->SystemDSN, $ENV{RT_DBA_USER}, $ENV{RT_DBA_PASSWORD} ); # We ignore errors intentionally by not checking the return value of # DropDatabase below, so let's also suppress DBI's printing of errors when # we overzealously drop. local $dbh->{PrintError} = 0; local $dbh->{PrintWarn} = 0; RT::Handle->DropDatabase( $dbh ); $dbh->disconnect if $my_dbh; } sub __reconnect_rt { my $as_dba = shift; __disconnect_rt(); # look at %DBIHandle and $PrevHandle in DBIx::SB::Handle for explanation $RT::Handle = RT::Handle->new; $RT::Handle->dbh( undef ); $RT::Handle->Connect( $as_dba ? (User => $ENV{RT_DBA_USER}, Password => $ENV{RT_DBA_PASSWORD}) : () ); $RT::Handle->PrintError; $RT::Handle->dbh->{PrintError} = 1; return $RT::Handle->dbh; } sub __disconnect_rt { # look at %DBIHandle and $PrevHandle in DBIx::SB::Handle for explanation $RT::Handle->dbh->disconnect if $RT::Handle and $RT::Handle->dbh; %DBIx::SearchBuilder::Handle::DBIHandle = (); $DBIx::SearchBuilder::Handle::PrevHandle = undef; $RT::Handle = undef; delete $RT::System->{attributes}; DBIx::SearchBuilder::Record::Cachable->FlushCache if DBIx::SearchBuilder::Record::Cachable->can("FlushCache"); } sub __init_logging { my $filter; { # We use local to ensure that the $filter we grab is from InitLogging # and not the handler generated by a previous call to this function # itself. local $SIG{__WARN__}; RT::InitLogging(); $filter = $SIG{__WARN__}; } $SIG{__WARN__} = sub { $filter->(@_) if $filter; # Avoid reporting this anonymous call frame as the source of the warning. goto &$Test_NoWarnings_Catcher; }; } =head1 UTILITIES =head2 load_or_create_user =cut sub load_or_create_user { my $self = shift; my %args = ( Privileged => 1, Disabled => 0, @_ ); my $MemberOf = delete $args{'MemberOf'}; $MemberOf = [ $MemberOf ] if defined $MemberOf && !ref $MemberOf; $MemberOf ||= []; my $obj = RT::User->new( RT->SystemUser ); if ( $args{'Name'} ) { $obj->LoadByCols( Name => $args{'Name'} ); } elsif ( $args{'EmailAddress'} ) { $obj->LoadByCols( EmailAddress => $args{'EmailAddress'} ); } else { die "Name or EmailAddress is required"; } if ( $obj->id ) { # cool $obj->SetPrivileged( $args{'Privileged'} || 0 ) if ($args{'Privileged'}||0) != ($obj->Privileged||0); $obj->SetDisabled( $args{'Disabled'} || 0 ) if ($args{'Disabled'}||0) != ($obj->Disabled||0); } else { my ($val, $msg) = $obj->Create( %args ); die "$msg" unless $val; } # clean group membership { require RT::GroupMembers; my $gms = RT::GroupMembers->new( RT->SystemUser ); my $groups_alias = $gms->Join( FIELD1 => 'GroupId', TABLE2 => 'Groups', FIELD2 => 'id', ); $gms->Limit( ALIAS => $groups_alias, FIELD => 'Domain', VALUE => 'UserDefined', CASESENSITIVE => 0, ); $gms->Limit( FIELD => 'MemberId', VALUE => $obj->id ); while ( my $group_member_record = $gms->Next ) { $group_member_record->Delete; } } # add new user to groups foreach ( @$MemberOf ) { my $group = RT::Group->new( RT::SystemUser() ); $group->LoadUserDefinedGroup( $_ ); die "couldn't load group '$_'" unless $group->id; $group->AddMember( $obj->id ); } return $obj; } sub load_or_create_group { my $self = shift; my $name = shift; my %args = (@_); my $group = RT::Group->new( RT->SystemUser ); $group->LoadUserDefinedGroup( $name ); unless ( $group->id ) { my ($id, $msg) = $group->CreateUserDefinedGroup( Name => $name, ); die "$msg" unless $id; } if ( $args{Members} ) { my $cur = $group->MembersObj; while ( my $entry = $cur->Next ) { my ($status, $msg) = $entry->Delete; die "$msg" unless $status; } foreach my $new ( @{ $args{Members} } ) { my ($status, $msg) = $group->AddMember( ref($new)? $new->id : $new, ); die "$msg" unless $status; } } return $group; } =head2 load_or_create_queue =cut sub load_or_create_queue { my $self = shift; my %args = ( Disabled => 0, @_ ); my $obj = RT::Queue->new( RT->SystemUser ); if ( $args{'Name'} ) { $obj->LoadByCols( Name => $args{'Name'} ); } else { die "Name is required"; } unless ( $obj->id ) { my ($val, $msg) = $obj->Create( %args ); die "$msg" unless $val; } else { my @fields = qw(CorrespondAddress CommentAddress SLADisabled); foreach my $field ( @fields ) { next unless exists $args{ $field }; next if $args{ $field } eq ($obj->$field || ''); no warnings 'uninitialized'; my $method = 'Set'. $field; my ($val, $msg) = $obj->$method( $args{ $field } ); die "$msg" unless $val; } } return $obj; } sub delete_queue_watchers { my $self = shift; my @queues = @_; foreach my $q ( @queues ) { foreach my $t (qw(Cc AdminCc) ) { $q->DeleteWatcher( Type => $t, PrincipalId => $_->MemberId ) foreach @{ $q->$t()->MembersObj->ItemsArrayRef }; } } } sub create_tickets { local $Test::Builder::Level = $Test::Builder::Level + 1; my $self = shift; my $defaults = shift; my @data = @_; @data = sort { rand(100) <=> rand(100) } @data if delete $defaults->{'RandomOrder'}; $defaults->{'Queue'} ||= 'General'; my @res = (); while ( @data ) { my %args = %{ shift @data }; $args{$_} = $res[ $args{$_} ]->id foreach grep $args{ $_ }, keys %RT::Link::TYPEMAP; push @res, $self->create_ticket( %$defaults, %args ); } return @res; } sub create_ticket { local $Test::Builder::Level = $Test::Builder::Level + 1; my $self = shift; my %args = @_; if ( blessed $args{'Queue'} ) { $args{Queue} = $args{'Queue'}->id; } elsif ($args{Queue} && $args{Queue} =~ /\D/) { my $queue = RT::Queue->new(RT->SystemUser); if (my $id = $queue->Load($args{Queue}) ) { $args{Queue} = $id; } else { die ("Error: Invalid queue $args{Queue}"); } } if ( my $content = delete $args{'Content'} ) { $args{'MIMEObj'} = MIME::Entity->build( From => Encode::encode( "UTF-8", $args{'Requestor'} ), Subject => RT::Interface::Email::EncodeToMIME( String => $args{'Subject'} ), Type => (defined $args{ContentType} ? $args{ContentType} : "text/plain"), Charset => "UTF-8", Data => Encode::encode( "UTF-8", $content ), ); } if ( my $cfs = delete $args{'CustomFields'} ) { my $q = RT::Queue->new( RT->SystemUser ); $q->Load( $args{'Queue'} ); while ( my ($k, $v) = each %$cfs ) { my $cf = $q->CustomField( $k ); unless ($cf->id) { RT->Logger->error("Couldn't load custom field $k"); next; } $args{'CustomField-'. $cf->id} = $v; } } my $ticket = RT::Ticket->new( RT->SystemUser ); my ( $id, undef, $msg ) = $ticket->Create( %args ); Test::More::ok( $id, "ticket created" ) or Test::More::diag("error: $msg"); # hackish, but simpler if ( $args{'LastUpdatedBy'} ) { $ticket->__Set( Field => 'LastUpdatedBy', Value => $args{'LastUpdatedBy'} ); } for my $field ( keys %args ) { #TODO check links and watchers if ( $field =~ /CustomField-(\d+)/ ) { my $cf = $1; my $got = join ',', sort map $_->Content, @{ $ticket->CustomFieldValues($cf)->ItemsArrayRef }; my $expected = ref $args{$field} ? join( ',', sort @{ $args{$field} } ) : $args{$field}; Test::More::is( $got, $expected, 'correct CF values' ); } else { next if ref $args{$field}; next unless $ticket->can($field) or $ticket->_Accessible($field,"read"); next if ref $ticket->$field(); Test::More::is( $ticket->$field(), $args{$field}, "$field is correct" ); } } return $ticket; } sub delete_tickets { my $self = shift; my $query = shift; my $tickets = RT::Tickets->new( RT->SystemUser ); if ( $query ) { $tickets->FromSQL( $query ); } else { $tickets->UnLimit; } while ( my $ticket = $tickets->Next ) { $ticket->Delete; } } =head2 load_or_create_custom_field =cut sub load_or_create_custom_field { my $self = shift; my %args = ( Disabled => 0, @_ ); my $obj = RT::CustomField->new( RT->SystemUser ); if ( $args{'Name'} ) { $obj->LoadByName( Name => $args{'Name'}, LookupType => RT::Ticket->CustomFieldLookupType, ObjectId => $args{'Queue'}, ); } else { die "Name is required"; } unless ( $obj->id ) { my ($val, $msg) = $obj->Create( %args ); die "$msg" unless $val; } return $obj; } sub last_ticket { my $self = shift; my $current = shift; $current = $current ? RT::CurrentUser->new($current) : RT->SystemUser; my $tickets = RT::Tickets->new( $current ); $tickets->OrderBy( FIELD => 'id', ORDER => 'DESC' ); $tickets->Limit( FIELD => 'id', OPERATOR => '>', VALUE => '0' ); $tickets->RowsPerPage( 1 ); return $tickets->First; } sub store_rights { my $self = shift; require RT::ACE; # fake construction RT::ACE->new( RT->SystemUser ); my @fields = keys %{ RT::ACE->_ClassAccessible }; require RT::ACL; my $acl = RT::ACL->new( RT->SystemUser ); $acl->Limit( FIELD => 'RightName', OPERATOR => '!=', VALUE => 'SuperUser' ); my @res; while ( my $ace = $acl->Next ) { my $obj = $ace->PrincipalObj->Object; if ( $obj->isa('RT::Group') && $obj->Domain eq 'ACLEquivalence' && $obj->Instance == RT->Nobody->id ) { next; } my %tmp = (); foreach my $field( @fields ) { $tmp{ $field } = $ace->__Value( $field ); } push @res, \%tmp; } return @res; } sub restore_rights { my $self = shift; my @entries = @_; foreach my $entry ( @entries ) { my $ace = RT::ACE->new( RT->SystemUser ); my ($status, $msg) = $ace->RT::Record::Create( %$entry ); unless ( $status ) { Test::More::diag "couldn't create a record: $msg"; } } } sub set_rights { my $self = shift; require RT::ACL; my $acl = RT::ACL->new( RT->SystemUser ); $acl->Limit( FIELD => 'RightName', OPERATOR => '!=', VALUE => 'SuperUser' ); while ( my $ace = $acl->Next ) { my $obj = $ace->PrincipalObj->Object; if ( $obj->isa('RT::Group') && $obj->Domain eq 'ACLEquivalence' && $obj->Instance == RT->Nobody->id ) { next; } $ace->Delete; } return $self->add_rights( @_ ); } sub add_rights { my $self = shift; my @list = ref $_[0]? @_: @_? { @_ }: (); require RT::ACL; foreach my $e (@list) { my $principal = delete $e->{'Principal'}; unless ( ref $principal ) { if ( $principal =~ /^(everyone|(?:un)?privileged)$/i ) { $principal = RT::Group->new( RT->SystemUser ); $principal->LoadSystemInternalGroup($1); } else { my $type = $principal; $principal = RT::Group->new( RT->SystemUser ); $principal->LoadRoleGroup( Object => ($e->{'Object'} || RT->System), Name => $type ); } die "Principal is not an object nor the name of a system or role group" unless $principal->id; } unless ( $principal->isa('RT::Principal') ) { if ( $principal->can('PrincipalObj') ) { $principal = $principal->PrincipalObj; } } my @rights = ref $e->{'Right'}? @{ $e->{'Right'} }: ($e->{'Right'}); foreach my $right ( @rights ) { my ($status, $msg) = $principal->GrantRight( %$e, Right => $right ); $RT::Logger->debug($msg); } } return 1; } =head2 switch_templates_to TYPE This runs /opt/rt5/etc/upgrade/switch-templates-to in order to change the templates from HTML to text or vice versa. TYPE is the type to switch to, either C<html> or C<text>. =cut sub switch_templates_to { my $self = shift; my $type = shift; return $self->run_and_capture( command => "$RT::EtcPath/upgrade/switch-templates-to", args => $type, ); } =head2 switch_templates_ok TYPE Calls L<switch_template_to> and tests the return values. =cut sub switch_templates_ok { my $self = shift; my $type = shift; my ($exit, $output) = $self->switch_templates_to($type); if ($exit >> 8) { Test::More::fail("Switched templates to $type cleanly"); diag("**** $RT::EtcPath/upgrade/switch-templates-to exited with ".($exit >> 8).":\n$output"); } else { Test::More::pass("Switched templates to $type cleanly"); } return ($exit, $output); } sub run_mailgate { my $self = shift; require RT::Test::Web; my %args = ( url => RT::Test::Web->rt_base_url, message => '', action => 'correspond', queue => 'General', debug => 1, command => $RT::BinPath .'/rt-mailgate', @_ ); my $message = delete $args{'message'}; $args{after_open} = sub { my $child_in = shift; if ( UNIVERSAL::isa($message, 'MIME::Entity') ) { $message->print( $child_in ); } else { print $child_in $message; } }; $self->run_and_capture(%args); } sub run_and_capture { my $self = shift; my %args = @_; my $after_open = delete $args{after_open}; my $cmd = delete $args{'command'}; die "Couldn't find command ($cmd)" unless -f $cmd; $cmd .= ' --debug' if delete $args{'debug'}; my $args = delete $args{'args'}; while( my ($k,$v) = each %args ) { next unless $v; $cmd .= " --$k '$v'"; } $cmd .= " $args" if defined $args; $cmd .= ' 2>&1'; DBIx::SearchBuilder::Record::Cachable->FlushCache; require IPC::Open2; my ($child_out, $child_in); my $pid = IPC::Open2::open2($child_out, $child_in, $cmd); $after_open->($child_in, $child_out) if $after_open; close $child_in; my $result = do { local $/; <$child_out> }; close $child_out; waitpid $pid, 0; return ($?, $result); } sub send_via_mailgate_and_http { my $self = shift; my $message = shift; my %args = (@_); my ($status, $gate_result) = $self->run_mailgate( message => $message, %args ); my $id; unless ( $status >> 8 ) { ($id) = ($gate_result =~ /Ticket:\s*(\d+)/i); unless ( $id ) { Test::More::diag "Couldn't find ticket id in text:\n$gate_result" if $ENV{'TEST_VERBOSE'}; } } else { Test::More::diag "Mailgate output:\n$gate_result" if $ENV{'TEST_VERBOSE'}; } return ($status, $id); } sub send_via_mailgate { my $self = shift; my $message = shift; my %args = ( action => 'correspond', queue => 'General', @_ ); if ( UNIVERSAL::isa( $message, 'MIME::Entity' ) ) { $message = $message->as_string; } my ( $status, $error_message, $ticket ) = RT::Interface::Email::Gateway( {%args, message => $message} ); # Invert the status to act like a syscall; failing return code is 1, # and it will be right-shifted before being examined. $status = ($status == 1) ? 0 : ($status == -75) ? (-75 << 8) : (1 << 8); return ( $status, $ticket ? $ticket->id : 0 ); } sub open_mailgate_ok { local $Test::Builder::Level = $Test::Builder::Level + 1; my $class = shift; my $baseurl = shift; my $queue = shift || 'general'; my $action = shift || 'correspond'; Test::More::ok(open(my $mail, '|-', "$RT::BinPath/rt-mailgate --url $baseurl --queue $queue --action $action"), "Opened the mailgate - $!"); return $mail; } sub close_mailgate_ok { local $Test::Builder::Level = $Test::Builder::Level + 1; my $class = shift; my $mail = shift; close $mail; Test::More::is ($? >> 8, 0, "The mail gateway exited normally. yay"); } sub mailsent_ok { local $Test::Builder::Level = $Test::Builder::Level + 1; my $class = shift; my $expected = shift; my $mailsent = scalar grep /\S/, split /%% split me! %%\n/, RT::Test->file_content( $tmp{'mailbox'}, 'unlink' => 0, noexist => 1 ); Test::More::is( $mailsent, $expected, "The number of mail sent ($expected) matches. yay" ); } sub fetch_caught_mails { my $self = shift; return grep /\S/, split /%% split me! %%\n/, RT::Test->file_content( $tmp{'mailbox'}, 'unlink' => 1, noexist => 1 ); } sub clean_caught_mails { unlink $tmp{'mailbox'}; } sub run_validator { my $self = shift; my %args = (check => 1, resolve => 0, force => 1, timeout => 0, @_ ); my $cmd = "$RT::SbinPath/rt-validator"; die "Couldn't find $cmd command" unless -f $cmd; my $timeout = delete $args{timeout}; while( my ($k,$v) = each %args ) { next unless $v; $cmd .= " --$k '$v'"; } $cmd .= ' 2>&1'; require IPC::Open2; my ($child_out, $child_in); my $pid = IPC::Open2::open2($child_out, $child_in, $cmd); close $child_in; local $SIG{ALRM} = sub { kill KILL => $pid; die "Timeout!" }; alarm $timeout if $timeout; my $result = eval { local $/; <$child_out> }; warn $@ if $@; close $child_out; waitpid $pid, 0; alarm 0; DBIx::SearchBuilder::Record::Cachable->FlushCache if $args{'resolve'}; return ($?, $result); } sub db_is_valid { local $Test::Builder::Level = $Test::Builder::Level + 1; my $self = shift; my ($ecode, $res) = $self->run_validator; Test::More::is( $ecode, 0, 'no invalid records' ) or Test::More::diag "errors:\n$res"; } =head2 object_scrips_are Takes an L<RT::Scrip> object or ID as the first argument and an arrayref of L<RT::Queue> objects and/or Queue IDs as the second argument. The scrip's applications (L<RT::ObjectScrip> records) are tested to ensure they exactly match the arrayref. An optional third arrayref may be passed to enumerate and test the queues the scrip is B<not> added to. This is most useful for testing the API returns the correct results. =cut sub object_scrips_are { local $Test::Builder::Level = $Test::Builder::Level + 1; my $self = shift; my $scrip = shift; my $to = shift || []; my $not_to = shift; unless (blessed($scrip)) { my $id = $scrip; $scrip = RT::Scrip->new( RT->SystemUser ); $scrip->Load($id); } $to = [ map { blessed($_) ? $_->id : $_ } @$to ]; Test::More::ok($scrip->IsAdded($_), "added to queue $_" ) foreach @$to; Test::More::is_deeply( [sort map $_->id, @{ $scrip->AddedTo->ItemsArrayRef }], [sort grep $_, @$to ], 'correct list of added to queues', ); if ($not_to) { $not_to = [ map { blessed($_) ? $_->id : $_ } @$not_to ]; Test::More::ok(!$scrip->IsAdded($_), "not added to queue $_" ) foreach @$not_to; Test::More::is_deeply( [sort map $_->id, @{ $scrip->NotAddedTo->ItemsArrayRef }], [sort grep $_, @$not_to ], 'correct list of not added to queues', ); } } =head2 get_relocatable_dir Takes a path relative to the location of the test file that is being run and returns a path that takes the invocation path into account. e.g. C<RT::Test::get_relocatable_dir(File::Spec->updir(), 'data', 'emails')> Parent directory traversals (C<..> or File::Spec->updir()) are naively canonicalized based on the test file path (C<$0>) so that symlinks aren't followed. This is the exact opposite behaviour of most filesystems and is considered "wrong", however it is necessary for some subsets of tests which are symlinked into the testing tree. =cut sub get_relocatable_dir { my @directories = File::Spec->splitdir( File::Spec->rel2abs((File::Spec->splitpath($0))[1]) ); push @directories, File::Spec->splitdir($_) for @_; my @clean; for (@directories) { if ($_ eq "..") { pop @clean } elsif ($_ ne ".") { push @clean, $_ } } return File::Spec->catdir(@clean); } =head2 get_relocatable_file Same as get_relocatable_dir, but takes a file and a path instead of just a path. e.g. RT::Test::get_relocatable_file('test-email', (File::Spec->updir(), 'data', 'emails')) =cut sub get_relocatable_file { my $file = shift; return File::Spec->catfile(get_relocatable_dir(@_), $file); } sub find_relocatable_path { my @path = @_; # A simple strategy to find e.g., t/data/gnupg/keys, from the dir # where test file lives. We try up to 3 directories up my $path = File::Spec->catfile( @path ); for my $up ( 0 .. 2 ) { my $p = get_relocatable_dir($path); return $p if -e $p; $path = File::Spec->catfile( File::Spec->updir(), $path ); } return undef; } sub get_abs_relocatable_dir { (my $volume, my $directories, my $file) = File::Spec->splitpath($0); if (File::Spec->file_name_is_absolute($directories)) { return File::Spec->catdir($directories, @_); } else { return File::Spec->catdir(Cwd->getcwd(), $directories, @_); } } sub gnupg_homedir { my $self = shift; File::Temp->newdir( DIR => $tmp{directory}, CLEANUP => 0, ); } sub import_gnupg_key { my $self = shift; my $key = shift; my $type = shift || 'secret'; $key =~ s/\@/-at-/g; $key .= ".$type.key"; my $path = find_relocatable_path( 'data', 'gnupg', 'keys' ); die "can't find the dir where gnupg keys are stored" unless $path; return RT::Crypt::GnuPG->ImportKey( RT::Test->file_content( [ $path, $key ] ) ); } sub lsign_gnupg_key { my $self = shift; my $key = shift; return RT::Crypt::GnuPG->CallGnuPG( Command => '--lsign-key', CommandArgs => [$key], Callback => sub { my %handle = @_; while ( my $str = readline $handle{'status'} ) { if ( $str =~ /^\[GNUPG:\]\s*GET_BOOL sign_uid\..*/ ) { print { $handle{'command'} } "y\n"; } } }, ); } sub trust_gnupg_key { my $self = shift; my $key = shift; return RT::Crypt::GnuPG->CallGnuPG( Command => '--edit-key', CommandArgs => [$key], Callback => sub { my %handle = @_; my $done = 0; while ( my $str = readline $handle{'status'} ) { if ( $str =~ /^\[GNUPG:\]\s*\QGET_LINE keyedit.prompt/ ) { if ( $done ) { print { $handle{'command'} } "quit\n"; } else { print { $handle{'command'} } "trust\n"; } } elsif ( $str =~ /^\[GNUPG:\]\s*\QGET_LINE edit_ownertrust.value/ ) { print { $handle{'command'} } "5\n"; } elsif ( $str =~ /^\[GNUPG:\]\s*\QGET_BOOL edit_ownertrust.set_ultimate.okay/ ) { print { $handle{'command'} } "y\n"; $done = 1; } } }, ); } sub started_ok { my $self = shift; require RT::Test::Web; if ($rttest_opt{nodb} and not $rttest_opt{server_ok}) { die "You are trying to use a test web server without a database. " ."You may want noinitialdata => 1 instead. " ."Pass server_ok => 1 if you know what you're doing."; } $ENV{'RT_TEST_WEB_HANDLER'} = undef if $rttest_opt{actual_server} && ($ENV{'RT_TEST_WEB_HANDLER'}||'') eq 'inline'; $ENV{'RT_TEST_WEB_HANDLER'} ||= 'plack'; my $which = $ENV{'RT_TEST_WEB_HANDLER'}; my ($server, $variant) = split /\+/, $which, 2; my $function = 'start_'. $server .'_server'; unless ( $self->can($function) ) { die "Don't know how to start server '$server'"; } return $self->$function( variant => $variant, @_ ); } sub test_app { my $self = shift; my %server_opt = @_; my $app; my $warnings = ""; open( my $warn_fh, ">", \$warnings ); local *STDERR = $warn_fh; if ($server_opt{variant} and $server_opt{variant} eq 'rt-server') { $app = do { my $file = "$RT::SbinPath/rt-server"; my $psgi = do $file; unless ($psgi) { die "Couldn't parse $file: $@" if $@; die "Couldn't do $file: $!" unless defined $psgi; die "Couldn't run $file" unless $psgi; } $psgi; }; } else { require RT::Interface::Web::Handler; $app = RT::Interface::Web::Handler->PSGIApp; } require Plack::Middleware::Test::StashWarnings; my $stashwarnings = Plack::Middleware::Test::StashWarnings->new( $ENV{'RT_TEST_WEB_HANDLER'} && $ENV{'RT_TEST_WEB_HANDLER'} eq 'inline' ? ( verbose => 0 ) : () ); $app = $stashwarnings->wrap($app); if ($server_opt{basic_auth}) { require Plack::Middleware::Auth::Basic; $app = Plack::Middleware::Auth::Basic->wrap( $app, authenticator => $server_opt{basic_auth} eq 'anon' ? sub { 1 } : sub { my ($username, $password) = @_; return $username eq 'root' && $password eq 'password'; } ); } close $warn_fh; $stashwarnings->add_warning( $warnings ) if $warnings; return $app; } sub start_plack_server { local $Test::Builder::Level = $Test::Builder::Level + 1; my $self = shift; require Plack::Loader; my $plack_server = Plack::Loader->load ('Standalone', port => $port, server_ready => sub { kill 'USR1' => getppid(); }); # We are expecting a USR1 from the child process after it's ready # to listen. We set this up _before_ we fork to avoid race # conditions. my $handled; local $SIG{USR1} = sub { $handled = 1}; __disconnect_rt(); my $pid = fork(); die "failed to fork" unless defined $pid; if ($pid) { sleep 15 unless $handled; Test::More::diag "did not get expected USR1 for test server readiness" unless $handled; push @SERVERS, $pid; my $Tester = Test::Builder->new; $Tester->ok(1, "started plack server ok"); __reconnect_rt() unless $rttest_opt{nodb}; return ("http://localhost:$port", RT::Test::Web->new); } require POSIX; POSIX::setsid() or die "Can't start a new session: $!"; # stick this in a scope so that when $app is garbage collected, # StashWarnings can complain about unhandled warnings do { $plack_server->run($self->test_app(@_)); }; exit; } our $TEST_APP; sub start_inline_server { local $Test::Builder::Level = $Test::Builder::Level + 1; my $self = shift; require Test::WWW::Mechanize::PSGI; unshift @RT::Test::Web::ISA, 'Test::WWW::Mechanize::PSGI'; # Clear out squished CSS and JS cache, since it's retained across # servers, since it's in-process RT::Interface::Web->ClearSquished; require RT::Interface::Web::Request; RT::Interface::Web::Request->clear_callback_cache; Test::More::ok(1, "psgi test server ok"); $TEST_APP = $self->test_app(@_); return ("http://localhost:$port", RT::Test::Web->new); } sub start_apache_server { local $Test::Builder::Level = $Test::Builder::Level + 1; my $self = shift; my %server_opt = @_; $server_opt{variant} ||= 'mod_perl'; $ENV{RT_TEST_WEB_HANDLER} = "apache+$server_opt{variant}"; require RT::Test::Apache; my $pid = RT::Test::Apache->start_server( %server_opt, port => $port, tmp => \%tmp ); push @SERVERS, $pid; my $url = RT->Config->Get('WebURL'); $url =~ s!/$!!; return ($url, RT::Test::Web->new); } sub stop_server { my $self = shift; my $in_end = shift; return unless @SERVERS; kill 'TERM', @SERVERS; foreach my $pid (@SERVERS) { if ($ENV{RT_TEST_WEB_HANDLER} =~ /^apache/) { my $count = 0; while ( kill 0, $pid ) { sleep 1; last if $count++ >= 100; } # Give it a final shot and leave it. # It's more important to not hang the tests kill 'KILL', $pid if kill 0, $pid; } else { waitpid $pid, 0; } } @SERVERS = (); } sub temp_directory { return $tmp{'directory'}; } sub file_content { my $self = shift; my $path = shift; my %args = @_; $path = File::Spec->catfile( @$path ) if ref $path eq 'ARRAY'; open( my $fh, "<:raw", $path ) or do { warn "couldn't open file '$path': $!" unless $args{noexist}; return '' }; my $content = do { local $/; <$fh> }; close $fh; unlink $path if $args{'unlink'}; return $content; } sub find_executable { my ( $self, $exe ) = @_; return File::Which::which( $exe ); } sub diag { return unless $ENV{RT_TEST_VERBOSE} || $ENV{TEST_VERBOSE}; goto \&Test::More::diag; } sub parse_mail { my $mail = shift; require RT::EmailParser; my $parser = RT::EmailParser->new; $parser->ParseMIMEEntityFromScalar( $mail ); my $entity = $parser->Entity; $entity->{__store_link_to_object_to_avoid_early_cleanup} = $parser; return $entity; } sub works { Test::More::ok($_[0], $_[1] || 'This works'); } sub fails { Test::More::ok(!$_[0], $_[1] || 'This should fail'); } sub plan { my ($cmd, @args) = @_; my $builder = RT::Test->builder; if ($cmd eq "skip_all") { $check_warnings_in_end = 0; } elsif ($cmd eq "tests") { # Increment the test count for the warnings check $args[0]++; } $builder->plan($cmd, @args); } sub done_testing { my $builder = RT::Test->builder; Test::NoWarnings::had_no_warnings(); $check_warnings_in_end = 0; if ($RT::Test::Web::INSTANCES) { my $cleanup = RT::Test::Web->new; undef $RT::Test::Web::INSTANCES; $cleanup->no_warnings_ok; } $builder->done_testing(@_); } # Some utilities must only run one process at a time, so they check # for other running processes and quit if another is found. In parallel # test mode, this can cause test failures if two tests happen to run at # the same time. # This test helper checks for a running process, and if found sleeps and # tries again for a short time rather than immediately failing. sub run_singleton_command { my $self = shift; my $command = shift; my @args = @_; my $dir = "$tmp{'directory'}/../singleton"; mkdir $dir unless -e $dir; my $flag = $command; $flag =~ s!/!-!g; $flag = "$dir/$flag"; for ( 1 .. 100 ) { if ( -e $flag ) { sleep 1; } else { open my $fh, '>', $flag or die $!; close $fh; last; } } my $ret = !system( $command, @args ); unlink $flag; return $ret; } END { my $Test = RT::Test->builder; return if $Test->{Original_Pid} != $$; # we are in END block and should protect our exit code # so calls below may call system or kill that clobbers $? local $?; Test::NoWarnings::had_no_warnings() if $check_warnings_in_end; RT::Test->stop_server(1); # not success if ( !$Test->is_passing ) { $tmp{'directory'}->unlink_on_destroy(0); Test::More::diag( "Some tests failed or we bailed out, tmp directory" ." '$tmp{directory}' is not cleaned" ); } if ( $ENV{RT_TEST_PARALLEL} && $created_new_db ) { __drop_database(); } # Drop our port from t/tmp/ports; do this after dropping the # database, as our port lock is also a lock on the database name. if (@ports) { my %ports; my $portfile = "$tmp{'directory'}/../ports"; sysopen(PORTS, $portfile, O_RDWR|O_CREAT) or die "Can't write to ports file $portfile: $!"; flock(PORTS, LOCK_EX) or die "Can't write-lock ports file $portfile: $!"; $ports{$_}++ for split ' ', join("",<PORTS>); delete $ports{$_} for @ports; seek(PORTS, 0, 0); truncate(PORTS, 0); print PORTS "$_\n" for sort {$a <=> $b} keys %ports; close(PORTS) or die "Can't close ports file: $!"; } } { # ease the used only once warning no warnings; no strict 'refs'; %{'RT::I18N::en_us::Lexicon'}; %{'Win32::Locale::Lexicon'}; } 1; �����������������������������rt-5.0.1/lib/RT/ExternalStorage.pm������������������������������������������������������������������000644 �000765 �000024 �00000011003 14005011336 017534� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 warnings; use strict; package RT::ExternalStorage; require RT::ExternalStorage::Backend; =head1 NAME RT::ExternalStorage - Store attachments outside the database =head1 SYNOPSIS Set(%ExternalStorage, Type => 'Disk', Path => '/opt/rt5/var/attachments', ); =head1 DESCRIPTION By default, RT stores attachments in the database. ExternalStorage moves all attachments that RT does not need efficient access to (which include textual content and images) to outside of the database. This may either be on local disk, or to a cloud storage solution. This decreases the size of RT's database, in turn decreasing the burden of backing up RT's database, at the cost of adding additional locations which must be configured or backed up. Attachment storage paths are calculated based on file contents; this provides de-duplication. The files are initially stored in the database when RT receives them; this guarantees that the user does not need to wait for the file to be transferred to disk or to the cloud, and makes it durable to transient failures of cloud connectivity. The provided C<sbin/rt-externalize-attachments> script, to be run regularly via cron, takes care of moving attachments out of the database at a later time. =head1 SETUP =head2 Edit F</opt/rt5/etc/RT_SiteConfig.pm> You will need to configure the C<%ExternalStorage> option, depending on how and where you want your data stored. RT comes with a number of possible storage backends; see the documentation in each for necessary configuration details: =over =item L<RT::ExternalStorage::Disk> =item L<RT::ExternalStorage::Dropbox> =item L<RT::ExternalStorage::AmazonS3> =back =head2 Restart your webserver Restarting the webserver before the next step (extracting existing attachments) is important to ensure that files remain available as they are extracted. =head2 Extract existing attachments Run C<sbin/rt-externalize-attachments>; this may take some time, depending on the existing size of the database. This task may be safely cancelled and re-run to resume. =head2 Schedule attachments extraction Schedule C<sbin/rt-externalize-attachments> to run at regular intervals via cron. For instance, the following F</etc/cron.d/rt> entry will run it daily, which may be good to concentrate network or disk usage to times when RT is less in use: 0 0 * * * root /opt/rt5/sbin/rt-externalize-attachments =head1 CAVEATS This feature is not currently compatible with RT's C<shredder> tool; attachments which are shredded will not be removed from external storage. =cut RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Links.pm����������������������������������������������������������������������������000644 �000765 �000024 �00000010464 14005011336 015517� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Links - A collection of Link objects =head1 SYNOPSIS use RT::Links; my $links = RT::Links->new($CurrentUser); =head1 DESCRIPTION =head1 METHODS =cut package RT::Links; use strict; use warnings; use base 'RT::SearchBuilder'; use RT::Link; sub Table { 'Links'} use RT::URI; sub Limit { my $self = shift; my %args = ( ENTRYAGGREGATOR => 'AND', OPERATOR => '=', @_); # If we're limiting by target, order by base # (Order by the thing that's changing) if ( ($args{'FIELD'} eq 'Target') or ($args{'FIELD'} eq 'LocalTarget') ) { $self->OrderByCols( { ALIAS => 'main', FIELD => 'LocalBase', ORDER => 'ASC' }, { ALIAS => 'main', FIELD => 'Base', ORDER => 'ASC' }, ); } elsif ( ($args{'FIELD'} eq 'Base') or ($args{'FIELD'} eq 'LocalBase') ) { $self->OrderByCols( { ALIAS => 'main', FIELD => 'LocalTarget', ORDER => 'ASC' }, { ALIAS => 'main', FIELD => 'Target', ORDER => 'ASC' }, ); } $self->SUPER::Limit(%args); } =head2 LimitRefersTo URI find all things that refer to URI =cut sub LimitRefersTo { my $self = shift; my $URI = shift; $self->Limit(FIELD => 'Type', VALUE => 'RefersTo'); $self->Limit(FIELD => 'Target', VALUE => $URI); } =head2 LimitReferredToBy URI find all things that URI refers to =cut sub LimitReferredToBy { my $self = shift; my $URI = shift; $self->Limit(FIELD => 'Type', VALUE => 'RefersTo'); $self->Limit(FIELD => 'Base', VALUE => $URI); } # }}} sub AddRecord { my $self = shift; my $record = shift; return unless $self->IsValidLink($record); push @{$self->{'items'}}, $record; } =head2 IsValidLink if linked to a local ticket and is deleted, then the link is invalid. =cut sub IsValidLink { my $self = shift; my $link = shift; return unless $link && ref $link && $link->Target && $link->Base; # Skip links to local objects thast are deleted return if $link->TargetURI->IsLocal && ( UNIVERSAL::isa( $link->TargetObj, "RT::Ticket" ) && $link->TargetObj->__Value('status') eq "deleted" || UNIVERSAL::isa( $link->BaseObj, "RT::Ticket" ) && $link->BaseObj->__Value('status') eq "deleted" ); return 1; } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ObjectScrip.pm����������������������������������������������������������������������000644 �000765 �000024 �00000016541 14005011336 016650� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::ObjectScrip; use base 'RT::Record::AddAndSort'; use RT::Scrip; use RT::ObjectScrips; use Scalar::Util 'blessed'; =head1 NAME RT::ObjectScrip - record representing addition of a scrip to a queue =head1 DESCRIPTION This record is created if you want to add a scrip to a queue or globally. Inherits methods from L<RT::Record::AddAndSort>. For most operations it's better to use methods in L<RT::Scrip>. =head1 METHODS =head2 Table Returns table name for records of this class. =cut sub Table {'ObjectScrips'} =head2 ObjectCollectionClass Returns class name of collection of records scrips can be added to. Now it's only L<RT::Queue>, so 'RT::Queues' is returned. =cut sub ObjectCollectionClass {'RT::Queues'} =head2 ScripObj Returns the Scrip Object which has the id returned by Scrip =cut sub ScripObj { my $self = shift; my $id = shift || $self->Scrip; my $obj = RT::Scrip->new( $self->CurrentUser ); $obj->Load( $id ); return $obj; } =head2 Neighbors Stage splits scrips into neighborhoods. See L<RT::Record::AddAndSort/Neighbors and Siblings>. =cut sub Neighbors { my $self = shift; my %args = @_; my $res = $self->CollectionClass->new( $self->CurrentUser ); $res->Limit( FIELD => 'Stage', VALUE => $args{'Stage'} || $self->Stage ); return $res; } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 Scrip Returns the current value of Scrip. (In the database, Scrip is stored as int(11).) =head2 FriendlyStage Returns a localized human-readable version of the stage. =cut sub FriendlyStage { my $self = shift; my $scrip_class = blessed($self->ScripObj); return $scrip_class->FriendlyStage($self->Stage); } =head2 SetScrip VALUE Set Scrip to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Scrip will be stored as a int(11).) =head2 Stage Returns the current value of Stage. (In the database, Stage is stored as varchar(32).) =head2 SetStage VALUE Set Stage to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Stage will be stored as a varchar(32).) =head2 ObjectId Returns the current value of ObjectId. (In the database, ObjectId is stored as int(11).) =head2 SetObjectId VALUE Set ObjectId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectId will be stored as a int(11).) =cut =head2 SortOrder Returns the current value of SortOrder. (In the database, SortOrder is stored as int(11).) =head2 SetSortOrder VALUE Set SortOrder to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, SortOrder will be stored as a int(11).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Scrip => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Stage => {read => 1, write => 1, sql_type => 12, length => 32, is_blob => 0, is_numeric => 0, type => 'varchar(32)', default => 'TransactionCreate'}, ObjectId => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, SortOrder => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); $deps->Add( out => $self->ScripObj ); if ($self->ObjectId) { my $obj = RT::Queue->new( $self->CurrentUser ); $obj->Load( $self->ObjectId ); $deps->Add( out => $obj ); $deps->Add( out => $self->ScripObj->TemplateObj($obj->Id) ); } } sub Serialize { my $self = shift; my %args = (@_); my %store = $self->SUPER::Serialize(@_); if ($store{ObjectId}) { my $obj = RT::Queue->new( RT->SystemUser ); $obj->Load( $store{ObjectId} ); $store{ObjectId} = \($obj->UID); } return %store; } RT::Base->_ImportOverlays(); 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/SearchBuilder/����������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 016610� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ObjectClasses.pm��������������������������������������������������������������������000644 �000765 �000024 �00000004555 14005011336 017167� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::ObjectClasses; use base 'RT::SearchBuilder'; sub Table {'ObjectClasses'} =head2 LimitToClass Takes a Class id and limits this collection to ObjectClasses that reference it. =cut sub LimitToClass { my $self = shift; my $id = shift; return $self->Limit( FIELD => 'Class', VALUE => $id ); } sub _SingularClass { "RT::ObjectClass" } RT::Base->_ImportOverlays(); 1; ���������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ScripAction.pm����������������������������������������������������������������������000644 �000765 �000024 �00000022241 14005011336 016651� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::ScripAction - RT Action object =head1 DESCRIPTION This module should never be called directly by client code. it's an internal module which should only be accessed through exported APIs in other modules. =cut package RT::ScripAction; use strict; use warnings; use base 'RT::Record'; sub Table {'ScripActions'} use RT::Template; sub _Accessible { my $self = shift; my %Cols = ( Name => 'read', Description => 'read', ExecModule => 'read', Argument => 'read', Creator => 'read/auto', Created => 'read/auto', LastUpdatedBy => 'read/auto', LastUpdated => 'read/auto' ); return($self->SUPER::_Accessible(@_, %Cols)); } =head1 METHODS =head2 Create Takes a hash. Creates a new Action entry. =cut sub Create { my $self = shift; #TODO check these args and do smart things. return($self->SUPER::Create(@_)); } sub Delete { my $self = shift; unless ( $self->CurrentUser->HasRight( Object => RT->System, Right => 'ModifyScrips' ) ) { return ( 0, $self->loc('Permission Denied') ); } my $scrips = RT::Scrips->new( RT->SystemUser ); $scrips->Limit( FIELD => 'ScripAction', VALUE => $self->id ); if ( $scrips->Count ) { return ( 0, $self->loc('Action is in use') ); } return $self->SUPER::Delete(@_); } sub UsedBy { my $self = shift; my $scrips = RT::Scrips->new( $self->CurrentUser ); $scrips->Limit( FIELD => 'ScripAction', VALUE => $self->Id ); return $scrips; } =head2 Load IDENTIFIER Loads an action by its Name. Returns: Id, Error Message =cut sub Load { my $self = shift; my $identifier = shift; if (!$identifier) { return wantarray ? (0, $self->loc('Input error')) : 0; } my ($ok, $msg); if ($identifier !~ /\D/) { ($ok, $msg) = $self->SUPER::Load($identifier); } else { ($ok, $msg) = $self->LoadByCol('Name', $identifier); } return wantarray ? ($ok, $msg) : $ok; } =head2 LoadAction HASH Takes a hash consisting of TicketObj and TransactionObj. Loads an RT::Action:: module. =cut sub LoadAction { my $self = shift; my %args = ( TransactionObj => undef, TicketObj => undef, ScripObj => undef, @_ ); # XXX: this whole block goes with TemplateObj method unless ( @_ && exists $args{'TemplateObj'} ) { local $self->{_TicketObj} = $args{TicketObj}; $args{'TemplateObj'} = $self->TemplateObj; } else { $self->{'TemplateObj'} = $args{'TemplateObj'}; } my $module = $self->ExecModule; my $type = 'RT::Action::' . $module; $type->require or die "Require of $type action module failed.\n$@\n"; return $self->{'Action'} = $type->new( %args, Argument => $self->Argument, CurrentUser => $self->CurrentUser, ScripActionObj => $self, ); } sub Prepare { my $self = shift; $self->{_Message_ID} = 0; return $self->Action->Prepare( @_ ); } sub Commit { my $self = shift; return $self->Action->Commit( @_ ); } sub Describe { my $self = shift; return $self->Action->Describe( @_ ); } =head2 Action Return the actual RT::Action object for this scrip. =cut sub Action { my $self = shift; return $self->{'Action'}; } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =head2 Name Returns the current value of Name. (In the database, Name is stored as varchar(200).) =head2 SetName VALUE Set Name to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Name will be stored as a varchar(200).) =head2 Description Returns the current value of Description. (In the database, Description is stored as varchar(255).) =head2 SetDescription VALUE Set Description to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Description will be stored as a varchar(255).) =head2 ExecModule Returns the current value of ExecModule. (In the database, ExecModule is stored as varchar(60).) =head2 SetExecModule VALUE Set ExecModule to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ExecModule will be stored as a varchar(60).) =head2 Argument Returns the current value of Argument. (In the database, Argument is stored as varbinary(255).) =head2 SetArgument VALUE Set Argument to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Argument will be stored as a varbinary(255).) =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Name => {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, Description => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, ExecModule => {read => 1, write => 1, sql_type => 12, length => 60, is_blob => 0, is_numeric => 0, type => 'varchar(60)', default => ''}, Argument => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varbinary(255)', default => ''}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; sub PreInflate { my $class = shift; my ($importer, $uid, $data) = @_; $class->SUPER::PreInflate( $importer, $uid, $data ); return not $importer->SkipBy( "Name", $class, $uid, $data ); } sub __DependsOn { my $self = shift; my %args = ( Shredder => undef, Dependencies => undef, @_, ); my $deps = $args{'Dependencies'}; # Scrips my $objs = RT::Scrips->new( $self->CurrentUser ); $objs->Limit( FIELD => 'ScripAction', VALUE => $self->Id ); $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON, TargetObjects => $objs, Shredder => $args{'Shredder'} ); return $self->SUPER::__DependsOn( %args ); } RT::Base->_ImportOverlays(); 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/CurrentUser.pm����������������������������������������������������������������������000644 �000765 �000024 �00000016327 14005011336 016724� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::CurrentUser - an RT object representing the current user =head1 SYNOPSIS use RT::CurrentUser; # load my $current_user = RT::CurrentUser->new; $current_user->Load(...); # or my $current_user = RT::CurrentUser->new( $user_obj ); # or my $current_user = RT::CurrentUser->new( $address || $name || $id ); # manipulation $current_user->UserObj->SetName('new_name'); =head1 DESCRIPTION B<Read-only> subclass of L<RT::User> class. Used to define the current user. You should pass an instance of this class to constructors of many RT classes, then the instance used to check ACLs and localize strings. =head1 METHODS See also L<RT::User> for a list of methods this class has. =head2 new Returns new CurrentUser object. Unlike all other classes of RT it takes either subclass of C<RT::User> class object or scalar value that is passed to Load method. =cut package RT::CurrentUser; use strict; use warnings; use base qw/RT::User/; use RT::I18N; #The basic idea here is that $self->CurrentUser is always supposed # to be a CurrentUser object. but that's hard to do when we're trying to load # the CurrentUser object sub _Init { my $self = shift; my $User = shift; $self->{'table'} = "Users"; if ( defined $User ) { if ( UNIVERSAL::isa( $User, 'RT::User' ) ) { $self->LoadById( $User->id ); } elsif ( ref $User ) { $RT::Logger->crit( "RT::CurrentUser->new() called with a bogus argument: $User"); } else { $self->Load( $User ); } } $self->_BuildTableAttributes; } =head2 Create, Delete and Set* As stated above it's a subclass of L<RT::User>, but this class is read-only and calls to these methods are illegal. Return 'permission denied' message and log an error. =cut sub Create { my $self = shift; $RT::Logger->error('RT::CurrentUser is read-only, RT::User for manipulation'); return (0, $self->loc('Permission Denied')); } sub Delete { my $self = shift; $RT::Logger->error('RT::CurrentUser is read-only, RT::User for manipulation'); return (0, $self->loc('Permission Denied')); } sub _Set { my $self = shift; $RT::Logger->error('RT::CurrentUser is read-only, RT::User for manipulation'); return (0, $self->loc('Permission Denied')); } =head2 UserObj Returns the L<RT::User> object associated with this CurrentUser object. =cut sub UserObj { my $self = shift; my $user = RT::User->new( $self ); unless ( $user->LoadById( $self->Id ) ) { $RT::Logger->error("Couldn't load " . $self->Id . " from the users database."); } return $user; } sub _CoreAccessible { { Name => { 'read' => 1 }, Gecos => { 'read' => 1 }, RealName => { 'read' => 1 }, Lang => { 'read' => 1 }, Password => { 'read' => 0, 'write' => 0 }, EmailAddress => { 'read' => 1, 'write' => 0 } }; } =head2 LoadByGecos Loads a User into this CurrentUser object. Takes a unix username as its only argument. =cut sub LoadByGecos { my $self = shift; return $self->LoadByCol( "Gecos", shift ); } =head2 LoadByName Loads a User into this CurrentUser object. Takes a Name. =cut sub LoadByName { my $self = shift; return $self->LoadByCol( "Name", shift ); } =head2 LanguageHandle Returns this current user's langauge handle. Should take a language specification. but currently doesn't =cut sub LanguageHandle { my $self = shift; if ( !defined $self->{'LangHandle'} || !UNIVERSAL::can( $self->{'LangHandle'}, 'maketext' ) || @_ ) { if ( my $lang = $self->Lang ) { push @_, $lang; } elsif ( $self->id && ($self->id == (RT->SystemUser->id||0) || $self->id == (RT->Nobody->id||0)) ) { # don't use ENV magic for system users push @_, 'en'; } elsif ($HTML::Mason::Commands::m) { # Detect from the HTTP header. require I18N::LangTags::Detect; push @_, I18N::LangTags::Detect->http_accept_langs( RT::Interface::Web::RequestENV('HTTP_ACCEPT_LANGUAGE') ); } $self->{'LangHandle'} = RT::I18N->get_handle(@_); } # Fall back to english. unless ( $self->{'LangHandle'} ) { die "We couldn't get a dictionary. Ne mogu naidti slovar. No puedo encontrar dictionario."; } return $self->{'LangHandle'}; } sub loc { my $self = shift; return '' if !defined $_[0] || $_[0] eq ''; my $handle = $self->LanguageHandle; if (@_ == 1) { # If we have no [_1] replacements, and the key does not appear # in the lexicon, unescape (using ~) and return it verbatim, as # an optimization. my $unescaped = $_[0]; $unescaped =~ s!~(.)!$1!g; return $unescaped unless grep exists $_->{$_[0]}, @{ $handle->_lex_refs }; } return $handle->maketext(@_); } sub loc_fuzzy { my $self = shift; return '' if !defined $_[0] || $_[0] eq ''; return $self->LanguageHandle->maketext_fuzzy( @_ ); } =head2 CurrentUser Return the current currentuser object =cut sub CurrentUser { return shift; } sub CustomFieldLookupType { return "RT::User"; } RT::Base->_ImportOverlays(); 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/CustomRole.pm�����������������������������������������������������������������������000644 �000765 �000024 �00000050312 14005011336 016527� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::CustomRole; use base 'RT::Record'; use RT::CustomRoles; use RT::ObjectCustomRole; =head1 NAME RT::CustomRole - user-defined role groups =head1 DESCRIPTION =head1 METHODS =head2 Table Returns table name for records of this class =cut sub Table {'CustomRoles'} =head2 Create PARAMHASH Create takes a hash of values and creates a row in the database: varchar(200) 'Name'. varchar(255) 'Description'. int(11) 'MaxValues'. varchar(255) 'EntryHint'. smallint(6) 'Disabled'. =cut sub Create { my $self = shift; my %args = ( Name => '', Description => '', MaxValues => 0, EntryHint => '', Disabled => 0, @_, ); unless ( $self->CurrentUser->HasRight(Object => $RT::System, Right => 'AdminCustomRoles') ) { return (0, $self->loc('Permission Denied')); } { my ($val, $msg) = $self->_ValidateName( $args{'Name'} ); return ($val, $msg) unless $val; } $args{'Disabled'} ||= 0; $args{'MaxValues'} = int $args{'MaxValues'}; $RT::Handle->BeginTransaction; my ($ok, $msg) = $self->SUPER::Create( Name => $args{'Name'}, Description => $args{'Description'}, MaxValues => $args{'MaxValues'}, EntryHint => $args{'EntryHint'}, Disabled => $args{'Disabled'}, ); unless ($ok) { $RT::Handle->Rollback; $RT::Logger->error("Couldn't create CustomRole: $msg"); return(undef); } # registration needs to happen before creating the system role group, # otherwise its validation that you're creating a group from # a valid role will fail $self->_RegisterAsRole; RT->System->CustomRoleCacheNeedsUpdate(1); # create a system role group for assigning rights on a global level # to members of this role my $system_group = RT::Group->new( RT->SystemUser ); ($ok, $msg) = $system_group->CreateRoleGroup( Name => $self->GroupType, Object => RT->System, Description => 'SystemRolegroup for internal use', # loc InsideTransaction => 1, ); unless ($ok) { $RT::Handle->Rollback; $RT::Logger->error("Couldn't create system custom role group: $msg"); return(undef); } $RT::Handle->Commit; return ($ok, $msg); } sub _RegisterAsRole { my $self = shift; my $id = $self->Id; RT::Ticket->RegisterRole( Name => $self->GroupType, EquivClasses => ['RT::Queue'], Single => $self->SingleValue, UserDefined => 1, # multi-value roles can have queue-level members, # single-value roles cannot (just like Owner) ACLOnlyInEquiv => $self->SingleValue, # only create role groups for tickets in queues which # have this custom role applied CreateGroupPredicate => sub { my %args = @_; my $object = $args{Object}; my $role = RT::CustomRole->new(RT->SystemUser); $role->Load($id); if ($object->isa('RT::Queue')) { # there's no way to apply the custom # role to a queue before that queue is created return 0; } elsif ($object->isa('RT::Ticket')) { # see if the role has been applied to the ticket's queue # need to walk around ACLs because of the common case of # (e.g. Everyone) having the CreateTicket right but not # ShowTicket return $role->IsAdded($object->__Value('Queue')); } return 0; }, # custom roles can apply to only a subset of queues AppliesToObjectPredicate => sub { my $object = shift; # reload the role to avoid capturing $self across requests my $role = RT::CustomRole->new(RT->SystemUser); $role->Load($id); return 0 if $role->Disabled; # all roles are also available on RT::System for granting rights if ($object->isa('RT::System')) { return 1; } # for callers not specific to any queue, e.g. ColumnMap if (!ref($object)) { return 1; } if ( $object->isa('RT::Ticket') || $object->isa('RT::Queue') ) { return 0 unless $object->CurrentUserHasRight('SeeQueue'); # custom roles apply to queues, so canonicalize a ticket # into its queue if ( $object->isa('RT::Ticket') ) { $object = $object->QueueObj; } return $role->IsAdded( $object->Id ); } return 0; }, LabelGenerator => sub { my $object = shift; # reload the role to avoid capturing $self across requests my $role = RT::CustomRole->new(RT->SystemUser); $role->Load($id); return $role->Name; }, ); } sub _UnregisterAsRole { my $self = shift; RT::Ticket->UnregisterRole($self->GroupType); } =head2 Load ID/NAME Load a custom role. If the value handed in is an integer, load by ID. Otherwise, load by name. =cut sub Load { my $self = shift; my $id = shift || ''; if ( $id =~ /^\d+$/ ) { return $self->SUPER::Load( $id ); } else { return $self->LoadByCols( Name => $id ); } } =head2 ValidateName NAME Takes a custom role name. Returns true if it's an ok name for a new custom role. Returns undef if there's already a role by that name. =cut sub ValidateName { my $self = shift; my $name = shift; my ($ok, $msg) = $self->_ValidateName($name); return $ok ? 1 : 0; } sub _ValidateName { my $self = shift; my $name = shift; return (undef, "Role name is required") unless length $name; # Validate via the superclass first unless ( my $ok = $self->SUPER::ValidateName($name) ) { return ($ok, $self->loc("'[_1]' is not a valid name.", $name)); } # These roles are builtin, so avoid any potential confusion if ($name =~ m{^( cc | admin[ ]?cc | requestors? | owner ) $}xi) { return (undef, $self->loc("Role already exists") ); } my $temp = RT::CustomRole->new(RT->SystemUser); $temp->LoadByCols(Name => $name); if ( $temp->Name && $temp->id != ($self->id||0)) { return (undef, $self->loc("Role already exists") ); } return (1); } =head2 Delete Delete this object. You should Disable instead. =cut sub Delete { my $self = shift; unless ( $self->CurrentUserHasRight('AdminCustomRoles') ) { return ( 0, $self->loc('Permission Denied') ); } RT::ObjectCustomRole->new( $self->CurrentUser )->DeleteAll( CustomRole => $self ); $self->_UnregisterAsRole; RT->System->CustomRoleCacheNeedsUpdate(1); return ( $self->SUPER::Delete(@_) ); } =head2 IsAdded Takes an object id and returns a boolean indicating whether the custom role applies to that object =cut sub IsAdded { my $self = shift; my $record = RT::ObjectCustomRole->new( $self->CurrentUser ); $record->LoadByCols( CustomRole => $self->id, ObjectId => shift ); return undef unless $record->id; return $record; } =head2 IsAddedToAny Returns a boolean of whether this custom role has been applied to any objects =cut sub IsAddedToAny { my $self = shift; my $record = RT::ObjectCustomRole->new( $self->CurrentUser ); $record->LoadByCols( CustomRole => $self->id ); return $record->id ? 1 : 0; } =head2 AddedTo Returns a collection of objects this custom role is applied to =cut sub AddedTo { my $self = shift; return RT::ObjectCustomRole->new( $self->CurrentUser ) ->AddedTo( CustomRole => $self ); } =head2 NotAddedTo Returns a collection of objects this custom role is not applied to =cut sub NotAddedTo { my $self = shift; return RT::ObjectCustomRole->new( $self->CurrentUser ) ->NotAddedTo( CustomRole => $self ); } =head2 AddToObject Adds (applies) this custom role to the provided queue (ObjectId). Accepts a param hash of: =over =item C<ObjectId> Queue name or id. =item C<SortOrder> Number indicating the relative order of the custom role =back Returns (val, message). If val is false, the message contains an error message. =cut sub AddToObject { my $self = shift; my %args = @_%2? (ObjectId => @_) : (@_); my $queue = RT::Queue->new( $self->CurrentUser ); $queue->Load( $args{'ObjectId'} ); return (0, $self->loc('Invalid queue')) unless $queue->id; $args{'ObjectId'} = $queue->id; return ( 0, $self->loc('Permission Denied') ) unless $queue->CurrentUserHasRight('AdminCustomRoles'); my $rec = RT::ObjectCustomRole->new( $self->CurrentUser ); my ( $status, $add ) = $rec->Add( %args, CustomRole => $self ); my $msg = $self->loc("[_1] added to queue [_2]", $self->Name, $queue->Name) if $status; return ( $add, $msg ); } =head2 RemoveFromObject Removes this custom role from the provided queue (ObjectId). Accepts a param hash of: =over =item C<ObjectId> Queue name or id. =back Returns (val, message). If val is false, the message contains an error message. =cut sub RemoveFromObject { my $self = shift; my %args = @_%2? (ObjectId => @_) : (@_); my $queue = RT::Queue->new( $self->CurrentUser ); $queue->Load( $args{'ObjectId'} ); return (0, $self->loc('Invalid queue id')) unless $queue->id; return ( 0, $self->loc('Permission Denied') ) unless $queue->CurrentUserHasRight('AdminCustomRoles'); my $rec = RT::ObjectCustomRole->new( $self->CurrentUser ); $rec->LoadByCols( CustomRole => $self->id, ObjectId => $args{'ObjectId'} ); return (0, $self->loc('Custom role is not added') ) unless $rec->id; my ( $status, $delete ) = $rec->Delete; my $msg = $self->loc("[_1] removed from queue [_2]", $self->Name, $queue->Name) if $status; return ( $delete, $msg ); } =head2 SingleValue Returns true if this custom role accepts only a single member. Returns false if it accepts multiple members. =cut sub SingleValue { my $self = shift; if (($self->MaxValues||0) == 1) { return 1; } else { return undef; } } =head2 UnlimitedValues Returns true if this custom role accepts multiple members. Returns false if it accepts only a single member. =cut sub UnlimitedValues { my $self = shift; if (($self->MaxValues||0) == 0) { return 1; } else { return undef; } } =head2 GroupType The C<Name> that groups for this custom role will have. =cut sub GroupType { my $self = shift; return 'RT::CustomRole-' . $self->id; } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 Name Returns the current value of Name. (In the database, Name is stored as varchar(200).) =head2 SetName VALUE Set Name to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Name will be stored as a varchar(200).) =cut =head2 Description Returns the current value of Description. (In the database, Description is stored as varchar(255).) =head2 SetDescription VALUE Set Description to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Description will be stored as a varchar(255).) =cut =head2 MaxValues Returns the current value of MaxValues. (In the database, MaxValues is stored as int(11).) =head2 SetMaxValues VALUE Set MaxValues to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, MaxValues will be stored as a int(11).) =cut sub SetMaxValues { my $self = shift; my $value = shift; my ($ok, $msg) = $self->_Set( Field => 'MaxValues', Value => $value ); # update single/multi value declaration $self->_RegisterAsRole; RT->System->CustomRoleCacheNeedsUpdate(1); return ($ok, $msg); } =head2 EntryHint Returns the current value of EntryHint. (In the database, EntryHint is stored as varchar(255).) =head2 SetEntryHint VALUE Set EntryHint to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, EntryHint will be stored as a varchar(255).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut =head2 Disabled Returns the current value of Disabled. (In the database, Disabled is stored as smallint(6).) =head2 SetDisabled VALUE Set Disabled to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Disabled will be stored as a smallint(6).) =cut sub _SetGroupsDisabledForQueue { my $self = shift; my $value = shift; my $queue = shift; # set disabled on the queue group my $queue_group = RT::Group->new($self->CurrentUser); $queue_group->LoadRoleGroup( Name => $self->GroupType, Object => $queue, ); if (!$queue_group->Id) { $RT::Handle->Rollback; $RT::Logger->error("Couldn't find role group for " . $self->GroupType . " on queue " . $queue->Id); return(undef); } my ($ok, $msg) = $queue_group->SetDisabled($value); unless ($ok) { $RT::Handle->Rollback; $RT::Logger->error("Couldn't SetDisabled($value) on role group: $msg"); return(undef); } # disable each existant ticket group my $ticket_groups = RT::Groups->new($self->CurrentUser); if ($value) { $ticket_groups->LimitToEnabled; } else { $ticket_groups->LimitToDeleted; } $ticket_groups->Limit(FIELD => 'Domain', OPERATOR => 'LIKE', VALUE => "RT::Ticket-Role", CASESENSITIVE => 0 ); $ticket_groups->Limit(FIELD => 'Name', OPERATOR => '=', VALUE => $self->GroupType, CASESENSITIVE => 0); my $tickets = $ticket_groups->Join( ALIAS1 => 'main', FIELD1 => 'Instance', TABLE2 => 'Tickets', FIELD2 => 'Id', ); $ticket_groups->Limit( ALIAS => $tickets, FIELD => 'Queue', VALUE => $queue->Id, ); while (my $ticket_group = $ticket_groups->Next) { my ($ok, $msg) = $ticket_group->SetDisabled($value); unless ($ok) { $RT::Handle->Rollback; $RT::Logger->error("Couldn't SetDisabled($value) ticket role group: $msg"); return(undef); } } } sub SetDisabled { my $self = shift; my $value = shift; $RT::Handle->BeginTransaction(); my ($ok, $msg) = $self->_Set( Field => 'Disabled', Value => $value ); unless ($ok) { $RT::Handle->Rollback(); $RT::Logger->warning("Couldn't ".(($value == 0) ? "enable" : "disable")." custom role ".$self->Name.": $msg"); return ($ok, $msg); } # we can't unconditionally re-enable all role groups because # if you add a role to queues A and B, add users and privileges and # tickets on both, remove the role from B, disable the role, then re-enable # the role, we shouldn't re-enable B because it's still removed my $queues = $self->AddedTo; while (my $queue = $queues->Next) { $self->_SetGroupsDisabledForQueue($value, $queue); } $RT::Handle->Commit(); if ( $value == 0 ) { return (1, $self->loc("Custom role enabled")); } else { return (1, $self->loc("Custom role disabled")); } } sub HiddenForURLs { my $self = shift; my $attr = $self->FirstAttribute('HiddenForURLs'); return {} if !$attr; return $attr->Content; } sub SetHiddenForURLs { my $self = shift; my $hidden = shift; unless ( $self->CurrentUser->HasRight(Object => $RT::System, Right => 'AdminCustomRoles') ) { return (0, $self->loc('Permission Denied')); } return $self->SetAttribute( Name => 'HiddenForURLs', Content => $hidden, ); } sub IsHiddenForURL { my $self = shift; my $url = shift; my $current_url = $HTML::Mason::Commands::r->path_info; $current_url =~ s!/{2,}!/!g; $url //= $current_url; return $self->HiddenForURLs->{$url}; } sub _Set { my $self = shift; unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AdminCustomRoles' ) ) { return ( 0, $self->loc('Permission Denied') ); } return $self->SUPER::_Set(@_); } sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Name => {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, Description => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, MaxValues => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, EntryHint => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, Disabled => {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'}, } }; RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/StyleGuide.pod����������������������������������������������������������������������000644 �000765 �000024 �00000071116 14005011336 016664� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������=head1 NAME RT::StyleGuide - RT Style Guide =head1 CAVEATS This file is somewhat out of date; L<hacking> takes precedence over it. =head1 INTRODUCTION All code and documentation that is submitted to be included in the RT distribution should follow the style in this document. This is not to try to stifle your creativity, but to make life easier for everybody who has to work with your code, and to aid those who are not quite sure how to do something. These conventions below apply to perl modules, web programs, and command-line programs, specifically, but also might apply to some degree to any Perl code written for use in RT. Note that these are all guidelines, not unbreakable rules. If you have a really good need to break one of the rules herein, however, then it is best to first start a discussion in the RT Developers category on the community forum at L<https://forum.bestpractical.com>. Note that with much of this document, it is not so much the Right Way as it is Our Way. We need to have conventions in order to make life easier for everyone. So don't gripe, and just follow it, because you didn't get a good grade in "Plays Well With Others" in kindergarten and you want to make up for it now. We don't always follow this guide. We are making changes throughout our code to be in line with it. But just because we didn't do it yet, that is no excuse. Do it anyway. :-) This document is subject to change at the whims of the core RT team. We hope to add any significant changes at the bottom of the document. =head1 CODING PRINCIPLES =head2 Perl Version We code everything to Perl 5.10.1 or higher. =head2 Documentation All modules will be documented using the POD examples in the module boilerplate. The function, purpose, use of the module will be explained, and each public API will be documented with name, description, inputs, outputs, side effects, etc. If an array or hash reference is returned, document the size of the array (including what each element is, as appropriate) and name each key in the hash. For complex data structures, map out the structure as appropriate (e.g., name each field returned for each column from a DB call; yes, this means you shouldn't use "SELECT *", which you shouldn't use anyway). Also document what kind of data returned values are. Is it an integer, a block of HTML, a boolean? All command-line program options will be documented using the boilerplate code for command-line programs, which doesn't yet exist. Each available function, switch, etc. should be documented, along with a statement of function, purpose, use of the program. Do not use the same options as another program, for a different purpose. All web templates should be documented with a statement of function, purpose, and use in a mason comment block. Any external documents, and documentation for command-line programs and modules, should be written in POD, where appropriate. From there, they can be translated to many formats with the various pod2* translators. Read the perlpod manpage before writing any POD, because although POD is not difficult, it is not what most people are used to. It is not a regular markup language; it is just a way to make easy documentation for translating to other formats. Read, and understand, the perlpod manpage, and ask us or someone else who knows if you have any questions. =head2 Version Our distribution versions use tuples, where the first number is the major revision, the second number is the version, and third number is the subversion. Odd-numbered versions are development versions. Examples: 1.0.0 First release of RT 1 1.0.1 Second release of RT 1.0 1.0.10 etc. 1.1.0 First development release of RT 1.2 (or 2.0) 2.0.0 First release of RT 2 Versions may end in "rc" and a number if they are release candidates: 2.0.0rc1 First release candiate for real 2.0.0 =head2 Comments All code should be self-documenting as much as possible. Only include necessary comments. Use names like "$ticket_count", so you don't need to do something like: # ticket count my $tc = 0; Include any comments that are, or might be, necessary in order for someone else to understand the code. Sometimes a simple one-line comment is good to explain what the purpose of the following code is for. Sometimes each line needs to be commented because of a complex algorithm. Read Kernighan & Pike's I<Practice of Programming> about commenting. Good stuff, Maynard. =head2 Warnings and Strict All code must compile and run cleanly with "use strict" enabled and the perl "-w" (warnings) option on. If you must do something that -w or strict complains about, there are workarounds, but the chances that you really need to do it that way are remote. =head2 Lexical Variables Use only lexical variables, except for special global variables ($VERSION, %ENV, @ISA, $!, etc.) or very special circumstances (see %HTML::Mason::Commands::session ). Global variables for regular use are never appropriate. When necessary, "declare" globals with "use vars" or "our()". A lexical variable is created with my(). A global variable is pre-existing (if it is a special variable), or it pops into existence when it is used. local() is used to tell perl to assign a temporary value to a variable. This should only be used with special variables, like $/, or in special circumstances. If you must assign to any global variable, consider whether or not you should use local(). local() may also be used on elements of arrays and hashes, though there is seldom a need to do it, and you shouldn't. =head2 Pass by Reference Arrays and hashes should be passed to and from functions by reference only. Note that a list and an array are NOT the same thing. This is perfectly fine: return($user, $form, $constants); An exception might be a temporary array of discrete arguments: my @return = ($user, $form); push @return, $constants if $flag; return @return; Although, usually, this is better (faster, easier to read, etc.): if ($flag) { return($user, $form, $constants); } else { return($user, $form); } We need to talk about Class::ReturnValue here. =head2 Method parameters If a method takes exactly one mandatory argument, the argument should be passed in a straightforward manner: my $self = shift; my $id = shift; In all other cases, the method needs to take named parameters, usually using a C<%args> hash to store them: my $self = shift; my %args = ( Name => undef, Description => undef, @_ ); You may specify defaults to those named parameters instead of using C<undef> above, as long as it is documented as such. It is worth noting that the existing RT codebase had not followed this style perfectly; we are trying to fix it without breaking existing APIs. =head2 Tests Modules should provide test code, with documentation on how to use it. Test::More makes it easy to create tests. Any code you write should have a testsuite. Any code you alter should have a test suite. If a patch comes in without tests, there is something wrong. When altering code, you must run the test harness before submitting a patch or committing code to the repository. "make test" will run the test suite. =head2 STDIN/STDOUT Always report errors using $RT::Logger. It's a Log::Dispatch object. Unlike message meant for the user, log messages are not to be internationalized. There are several different levels ($RT::Logger methods) of logging: =over 4 =item debug Used for messages only needed during system debugging. =item info Should be used to describe "system-critical" events which aren't errors. Examples: creating users, deleting users, creating tickets, creating queues, sending email (message id, time, recipients), recieving mail, changing passwords, changing access control, superuser logins) =item error Used for RT-generated failures during execution. =item crit Should be used for messages when an action can not be completed due to some error condition beyond our control. =back In the web UI and modules, never print directly to STDERR. Do not print directly to STDOUT, unless you need to print directly to the user's console. In command-line programs, feel free to print to STDERR and STDOUT as needed for direct console communication. But for actual error reporting, use the logging API. =head2 System Calls Always check return values from system calls, including open(), close(), mkdir(), or anything else that talks directly to the system. Perl built-in system calls return the error in $!; some functions in modules might return an error in $@ or some other way, so read the module's documentation if you don't know. Always do something, even if it is just calling $RT::Logger->warning(), when the return value is not what you'd expect. =head1 STYLE Much of the style section is taken from the perlsyle manpage. We make some changes to it here, but it wouldn't be a bad idea to read that document, too. =head2 Terminology =over 4 =item function vs. sub(routine) vs. method Just because it is the Perl Way (not necessarily right for all languages, but the documented terminology in the perl documentation), "method" should be used only to refer to a subroutine that are object methods or class methods; that is, these are functions that are used with OOP that always take either an object or a class as the first argument. Regular subroutines, ones that are not object or class methods, are functions. Class methods that create and return an object are optionally called constructors. =item Users "users" are normally users of RT, the ones hitting the site; if using it in any other context, specify. "system users" are user names on the operating system. "database users" are the user names in the database server. None of these needs to be capitalized. =back =head2 Names Don't use single-character variables, except as iterator variables. Don't use two-character variables just to spite us over the above rule. Constants are in all caps; these are variables whose value will I<never> change during the course of the program. $Minimum = 10; # wrong $MAXIMUM = 50; # right Other variables are lowercase, with underscores separating the words. They words used should, in general, form a noun (usually singular), unless the variable is a flag used to denote some action that should be taken, in which case they should be verbs (or gerunds, as appropriate) describing that action. $thisVar = 'foo'; # wrong $this_var = 'foo'; # right $work_hard = 1; # right, verb, boolean flag $running_fast = 0; # right, gerund, boolean flag Arrays and hashes should be plural nouns, whether as regular arrays and hashes or array and hash references. Do not name references with "ref" or the data type in the name. @stories = (1, 2, 3); # right $comment_ref = [4, 5, 6]; # wrong $comments = [4, 5, 6]; # right $comment = $comments->[0]; # right Make the name descriptive. Don't use variables like "$sc" when you could call it "$story_count". See L<"Comments">. There are several variables in RT that are used throughout the code, that you should use in your code. Do not use these variable names for anything other than how they are normally used, and do not use any other variable names in their place. Some of these are: $self # first named argument in object method Subroutines (except for special cases, like AUTOLOAD and simple accessors) begin with a verb, with words following to complete the action. Accessors don't start with "Get" if they're just the name of the attribute. Accessors which return an object should end with the suffix Obj. This section needs clarification for RT. Words begin with a capital letter. They should as clearly as possible describe the activity to be peformed, and the data to be returned. Load(); # good LoadByName(); # good LoadById(); # good Subroutines beginning with C<_> are special: they are not to be used outside the current object. There is not to be enforced by the code itself, but by someone very big and very scary. For large for() loops, do not use $_, but name the variable. Do not use $_ (or assume it) except for when it is absolutely clear what is going on, or when it is required (such as with map() and grep()). for (@list) { print; # OK; everyone knows this one print uc; # wrong; few people know this print uc $_; # better } Note that the special variable C<_> I<should> be used when possible. It is a placeholder that can be passed to stat() and the file test operators, that saves perl a trip to re-stat the file. In the example below, using C<$file> over for each file test, instead of C<_> for subsequent uses, is a performance hit. You should be careful that the last-tested file is what you think it is, though. if (-d $file) { # $file is a directory # ... } elsif (-l _) { # $file is a symlink # ... } Package names begin with a capital letter in each word, followed by lower case letters (for the most part). Multiple words should be StudlyCapped. RT::User # good RT::Database::MySQL # proper name RT::Display::Provider # good RT::CustomField # not so good, but OK Plugin modules should begin with "RT::Extension::", followed by the name of the plugin. =head1 Code formatting When in doubt, use perltidy; RT includes a F<.perltidyrc>. =head2 Indents and Blank Space All indents should be four spaces; hard tabs are forbidden. No space before a semicolon that closes a statement. foo(@bar) ; # wrong foo(@bar); # right Line up corresponding items vertically. my $foo = 1; my $bar = 2; my $xyzzy = 3; open(FILE, $fh) or die $!; open(FILE2, $fh2) or die $!; $rot13 =~ tr[abcedfghijklmnopqrstuvwxyz] [nopqrstuvwxyzabcdefghijklm]; # note we use a-mn-z instead of a-z, # for readability $rot13 =~ tr[a-mn-z] [n-za-m]; Put blank lines between groups of code that do different things. Put blank lines after your variable declarations. Put a blank line before a final return() statement. Put a blank line following a block (and before, with the exception of comment lines). An example: # this is my function! sub foo { my $val = shift; my $obj = new Constructor; my($var1, $var2); $obj->SetFoo($val); $var1 = $obj->Foo(); return($val); } print 1; =head2 Parentheses For control structures, there is a space between the keyword and opening parenthesis. For functions, there is not. for(@list) # wrong for (@list) # right my ($ref) # wrong my($ref) # right Be careful about list vs. scalar context with parentheses! my @array = ('a', 'b', 'c'); my($first_element) = @array; # a my($first_element) = ('a', 'b', 'c'); # a my $element_count = @array; # 3 my $last_element = ('a', 'b', 'c'); # c Always include parentheses after functions, even if there are no arguments. There are some exceptions, such as list operators (like print) and unary operators (like undef, delete, uc). There is no space inside the parentheses, unless it is needed for readability. for ( map { [ $_, 1 ] } @list ) # OK for ( @list ) # not really OK, not horrible On multi-line expressions, match up the closing parenthesis with either the opening statement, or the opening parenthesis, whichever works best. Examples: @list = qw( bar baz ); # right if ($foo && $bar && $baz && $buz && $xyzzy) { print $foo; } Whether or not there is space following a closing parenthesis is dependent on what it is that follows. print foo(@bar), baz(@buz) if $xyzzy; Note also that parentheses around single-statement control expressions, as in C<if $xyzzy>, are optional (and discouraged) C<if> it is I<absolutely> clear -- to a programmer -- what is going on. There is absolutely no need for parentheses around C<$xyzzy> above, so leaving them out enhances readability. Use your best discretion. Better to include them, if there is any question. The same essentially goes for perl's built-in functions, when there is nothing confusing about what is going on (for example, there is only one function call in the statement, or the function call is separated by a flow control operator). User-supplied functions must always include parentheses. print 1, 2, 3; # good delete $hash{key} if isAnon($uid); # good However, if there is any possible confusion at all, then include the parentheses. Remember the words of Larry Wall in the perlstyle manpage: When in doubt, parenthesize. At the very least it will let some poor schmuck bounce on the % key in vi. Even if you aren't in doubt, consider the mental welfare of the person who has to maintain the code after you, and who will probably put parens in the wrong place. So leave them out when it is absoutely clear to a programmer, but if there is any question, leave them in. =head2 Braces (This is about control braces, not hash/data structure braces.) There is always a space befor the opening brace. while (<$fh>){ # wrong while (<$fh>) { # right A one-line block may be put on one line, and the semicolon may be omitted. for (@list) { print } Otherwise, finish each statement with a semicolon, put the keyword and opening curly on the first line, and the ending curly lined up with the keyword at the end. for (@list) { print; smell(); } Generally, we prefer "cuddled elses": if ($foo) { print; } else { die; } =head2 Operators Put space around most operators. The primary exception is the for aesthetics; e.g., sometimes the space around "**" is ommitted, and there is never a space before a ",", but always after. print $x , $y; # wrong print $x, $y; # right $x = 2 >> 1; # good $y = 2**2; # ok Note that "&&" and "||" have a higher precedence than "and" and "or". Other than that, they are exactly the same. It is best to use the lower precedence version for control, and the higher for testing/returning values. Examples: $bool = $flag1 or $flag2; # WRONG (doesn't work) $value = $foo || $bar; # right open(FILE, $file) or die $!; $true = foo($bar) && baz($buz); foo($bar) and baz($buz); Note that "and" is seldom ever used, because the statement above is better written using "if": baz($buz) if foo($bar); Most of the time, the confusion between and/&&, or/|| can be alleviated by using parentheses. If you want to leave off the parentheses then you I<must> use the proper operator. But if you use parentheses -- and normally, you should, if there is any question at all -- then it doesn't matter which you use. Use whichever is most readable and aesthetically pleasing to you at the time, and be consistent within your block of code. Break long lines AFTER operators, except for ".", "and", "or", "&&", "||". Try to keep the two parts to a binary operator (an operator that has two operands) together when possible. print "foo" . "bar" . "baz" . "buz"; # wrong print "foo" . "bar" . "baz" . "buz"; # right print $foo unless $x == 3 && $y == 4 && $z == 5; # wrong print $foo unless $x == 3 && $y == 4 && $z == 5; # right =head2 Other Put space around a complex subscript inside the brackets or braces. $foo{$bar{baz}{buz}}; # OK $foo{ $bar{baz}{buz} }; # better In general, use single-quotes around literals, and double-quotes when the text needs to be interpolated. It is OK to omit quotes around names in braces and when using the => operator, but be careful not to use a name that doubles as a function; in that case, quote. $what{'time'}{it}{is} = time(); When making compound statements, put the primary action first. open(FILE, $fh) or die $!; # right die $! unless open(FILE, $fh); # wrong print "Starting\n" if $verbose; # right $verbose && print "Starting\n"; # wrong Use here-docs instead of repeated print statements. print <<EOT; This is a whole bunch of text. I like it. I don't need to worry about messing with lots of print statements and lining them up. EOT Just remember that unless you put single quotes around your here-doc token (<<'EOT'), the text will be interpolated, so escape any "$" or "@" as needed. =head1 INTERNATIONALIZATION =head2 String extraction styleguide =over 4 =item Web templates Templates should use the /l filtering component to call the localisation framework The string Foo! Should become <&|/l&>Foo!</&> All newlines should be removed from localized strings, to make it easy to grep the codebase for strings to be localized The string Foo Bar Baz Should become <&|/l&>Foo Bar Baz</&> Variable subsititutions should be moved to Locale::MakeText format The string Hello, <%$name %> should become <&|/l, $name &>Hello, [_1]</&> Multiple variables work just like single variables The string You found <%$num%> tickets in queue <%$queue%> should become <&|/l, $num, $queue &>You found [_1] tickets in queue [_2]</&> When subcomponents are called in the middle of a phrase, they need to be escaped too: The string <input type="submit" value="New ticket in"> <& /Elements/SelectNewTicketQueue&> should become <&|/l, $m->scomp('/Elements/SelectNewTicketQueue')&><input type="submit" value="New ticket in"> [_1]</&> The string <& /Widgets/TitleBoxStart, width=> "40%", titleright => "RT $RT::VERSION for RT->Config->Get('rtname')", title => 'Login' &> should become <& /Widgets/TitleBoxStart, width=> "40%", titleright => loc("RT [_1] for [_2]",$RT::VERSION, RT->Config->Get('rtname')), title => loc('Login'), &> =item Library code Within RT's core code, every module has a localization handle available through the 'loc' method: The code return ( $id, "Queue created" ); should become return ( $id, $self->loc("Queue created") ); When returning or localizing a single string, the "extra" set of parenthesis () should be omitted. The code return ("Subject changed to ". $self->Data ); should become return $self->loc( "Subject changed to [_1]", $self->Data ); It is important not to localize the names of rights or statuses within RT's core, as there is logic that depends on them as string identifiers. The proper place to localize these values is when they're presented for display in the web or commandline interfaces. =back =head1 CODING PRCEDURE This is for new programs, modules, specific APIs, or anything else. =over 4 =item Create a topic in RT Developers on the Forum We may know of a better way to approach the problem, or know of an existing way to deal with it, or know someone else is working on it. This is mostly informal, but a fairly complete explanation for the need and use of the code should be provided. =item Present specs in RT Developers The complete proposed API should be submitted for discussion. For web and command-line programs, present the functionality and interface (op codes, command-line switches, etc.). The best way to do this is to take the documentation portion of the boilerplate and fill it in. You can make changes later if necessary, but fill it in as much as you can. =item Prepare for code review When you are done, the code will undergo a code review by a member of the core team, or someone picked by the core team. This is not to belittle you (that's just a nice side effect), it is to make sure that you understand your code, that we understand your code, that it won't break other code, that it follows the documentation and existing proposal. It is to check for possible optimizations or better ways of doing it. Note that all code is expected to follow the coding principles and style guide contained in this document. =item Finish it up After the code is done (possibly going through multiple code reviews), submit your updates as a pull request on GitHub. If you don't have a GitHub account, you can generate patches and send email to rt-bugs@bestpractical.com which will create a ticket in our public issue tracker at L<https://issues.bestpractical.com>. =back =head1 BUG REPORTS, PATCHES Use rt-bugs@bestpractical.com for I<any> bug that is not being fixed immediately. If it is not in RT, there is a good chance it will not be dealt with. Send patches to rt-bugs@bestpractical.com, too. Use C<diff -u> for patches. =head1 SCHEMA DESIGN RT uses a convention to denote the foreign key status in its tables. The rule of thumb is: =over 4 =item When it references to another table, always use the table name For example, the C<Template> field in the C<Scrips> table refers to the C<Id> of the same-named C<Template> table. =item Otherwise, always use the C<Id> suffix For example, the C<ObjectId> field in the C<ACL> table can refer to any object, so it has the C<Id> suffix. =back There are some legacy fields that did not follow this rule, namely C<ACL.PrincipalId>, C<GroupMembers.GroupId> and C<Attachments.TransactionId>, but new tables are expected to be consistent. =head1 EXTENDING RT CLASSES =head2 The Overlay mechanism RT's classes allow "overlay" methods to be placed into files named F<Filename_Vendor.pm> and F<Filename_Local.pm>. _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations. These overlay files can contain new subs or subs to replace existing subs in this module. Each of these files should begin with the line: no warnings qw(redefine); so that perl does not kick and scream when you redefine a subroutine or variable in your overlay. Some common ways that overlays are used: =head3 Adding Methods Create a file named F<Classname_Local.pm> as appropriate (like F<User_Local.pm>) in F<rt_base_dir/local/lib/RT/>. no warnings qw(redefine); sub MyNewMethod { my $self = shift; ... } 1; =head3 Modifying Methods Create a file with a F<_Local.pm> suffix as appropriate in the same F<local/lib> tree mentioned above. Copy the method you need to modify from the original RT version of the file and paste it into your local version. Then modify the code to behave the way you need it to. When changing code in this way, make note of incoming values and especially return values since other code likely expects the existing method to return values a certain way. When copying code for modification, do not place the C<_ImportOverlays> call from the original at the bottom of your modified file. no warnings qw(redefine); sub ExistingMethod { ... existing RT code ... my special changes ... existing RT code } 1; =head3 Hooking Methods Set up your local file the same way as in B<Modifying Methods> described above. You'll need to save the original method call before you redfine it, and then call it at the head or tail of your code: no warnings qw(redefine); # This should be the same class we are overlaying here my $original_method = \&RT::Class::MethodToHook; sub MethodToHook { my $self = shift; ... ... my special code ... # Call the original method at the tail of our extra code: &$original_method( $args ); } 1; B<Remember!> If you modify existing core RT methods, you will need to update your local modifications when you upgrade the base RT code so that it matches. =head1 TO DO Talk about DBIx::SearchBuilder Talk about mason component style cascading style sheets Talk about adding a new translation Talk more about logging ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Rule.pm�����������������������������������������������������������������������������000644 �000765 �000024 �00000007322 14005011336 015345� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Rule; use strict; use warnings; use base 'RT::Action'; use constant _Stage => 'TransactionCreate'; use constant _Queue => undef; sub Prepare { my $self = shift; if ( $self->_Queue ) { my $queue = RT::Queue->new( RT->SystemUser ); $queue->Load( $self->TicketObj->__Value('Queue') ); if ( $queue->Name ne $self->_Queue ) { return (0); } return 1; } } sub Commit { my $self = shift; return(0, $self->loc("Commit Stubbed")); } sub Describe { my $self = shift; return $self->loc( $self->Description ); } sub OnStatusChange { my ($self, $value) = @_; $self->TransactionObj->Type eq 'Status' and $self->TransactionObj->Field eq 'Status' and $self->TransactionObj->NewValue eq $value } sub RunScripAction { my ($self, $scrip_action, $template, %args) = @_; my $ScripAction = RT::ScripAction->new($self->CurrentUser); $ScripAction->Load($scrip_action) or die ; unless (ref($template)) { # XXX: load per-queue template # $template->LoadQueueTemplate( Queue => ..., ) || $template->LoadGlobalTemplate(...) my $t = RT::Template->new($self->CurrentUser); $t->Load($template) or die; $template = $t; } my $action = $ScripAction->LoadAction( TransactionObj => $self->TransactionObj, TicketObj => $self->TicketObj, TemplateObj => $template, %args, ); $action->{'ScripObj'} = RT::Scrip->new($self->CurrentUser); # Stub. sendemail action really wants a scripobj available $action->Prepare or return; $action->Commit; } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/SharedSettings.pm�������������������������������������������������������������������000644 �000765 �000024 �00000007501 14005011336 017364� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::SharedSettings - a pseudo-collection for SharedSetting objects. =head1 SYNOPSIS use RT::SharedSettings =head1 DESCRIPTION SharedSettings is an object consisting of a number of SharedSetting objects. It works more or less like a DBIx::SearchBuilder collection, although it is not. =head1 METHODS =cut package RT::SharedSettings; use strict; use warnings; use base 'RT::Base'; use RT::SharedSetting; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; bless ($self, $class); $self->CurrentUser(@_); $self->{'idx'} = 0; $self->{'objects'} = []; return $self; } ### Accessor methods =head2 Next Returns the next object in the collection. =cut sub Next { my $self = shift; my $search = $self->{'objects'}->[$self->{'idx'}]; if ($search) { $self->{'idx'}++; } else { # We have run out of objects; reset the counter. $self->{'idx'} = 0; } return $search; } =head2 Count Returns the number of search objects found. =cut sub Count { my $self = shift; return scalar @{$self->{'objects'}}; } =head2 CountAll Returns the number of search objects found =cut sub CountAll { my $self = shift; return $self->Count; } =head2 GotoPage Act more like a normal L<DBIx::SearchBuilder> collection. Moves the internal index around =cut sub GotoPage { my $self = shift; $self->{idx} = shift; } =head2 ColumnMapClassName Equivalent to L<RT::SearchBuilder/ColumnMapClassName> =cut sub ColumnMapClassName { return shift->RecordClass->ColumnMapClassName } ### Internal methods # _GetObject: helper routine to load the correct object whose parameters # have been passed. sub _GetObject { my $self = shift; my $privacy = shift; return $self->RecordClass->new($self->CurrentUser)->_GetObject($privacy); } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Search/�����������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 015301� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/CustomFieldValues/������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 017472� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/SharedSetting.pm��������������������������������������������������������������������000644 �000765 �000024 �00000034515 14005011336 017206� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::SharedSetting - an API for settings that belong to an RT::User or RT::Group =head1 SYNOPSIS use RT::SharedSetting; =head1 DESCRIPTION A RT::SharedSetting is an object that can belong to an L<RT::User> or an <RT::Group>. It consists of an ID, a name, and some arbitrary data. =cut package RT::SharedSetting; use strict; use warnings; use base qw/RT::Base/; use RT::Attribute; use Scalar::Util 'blessed'; =head1 METHODS =head2 new Returns a new L<RT::SharedSetting> object. Takes the current user, see also L<RT::Base>. =cut sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; $self->{'Id'} = 0; bless ($self, $class); $self->CurrentUser(@_); return $self; } =head2 Load Takes a privacy specification and a shared-setting ID. Loads the given object ID if it belongs to the stated user or group. Calls the L</PostLoad> method on success for any further initialization. Returns a tuple of status and message, where status is true on success. =cut sub Load { my $self = shift; my ($privacy, $id) = @_; my $object = $self->_GetObject($privacy); if ($object) { $self->{'Attribute'} = RT::Attribute->new($self->CurrentUser); $self->{'Attribute'}->Load( $id ); if ($self->{'Attribute'}->Id) { $self->{'Id'} = $self->{'Attribute'}->Id; $self->{'Privacy'} = $privacy; $self->PostLoad(); return wantarray ? (0, $self->loc("Permission Denied")) : 0 unless $self->CurrentUserCanSee; my ($ok, $msg) = $self->PostLoadValidate; return wantarray ? ($ok, $msg) : $ok if !$ok; return wantarray ? (1, $self->loc("Loaded [_1] [_2]", $self->ObjectName, $self->Name)) : 1; } else { $RT::Logger->error("Could not load attribute " . $id . " for object " . $privacy); return wantarray ? (0, $self->loc("Failed to load [_1] [_2]", $self->ObjectName, $id)) : 0; } } else { $RT::Logger->warning("Could not load object $privacy when loading " . $self->ObjectName); return wantarray ? (0, $self->loc("Could not load object for [_1]", $privacy)) : 0; } } =head2 LoadById First loads up the L<RT::Attribute> for this shared setting by ID, then calls L</Load> with the correct parameters. Returns a tuple of status and message, where status is true on success. =cut sub LoadById { my $self = shift; my $id = shift; my $attr = RT::Attribute->new($self->CurrentUser); my ($ok, $msg) = $attr->LoadById($id); if (!$ok) { return wantarray ? (0, $self->loc("Failed to load [_1] [_2]: [_3]", $self->ObjectName, $id, $msg)) : 0; } my $privacy = $self->_build_privacy($attr->ObjectType, $attr->ObjectId); return wantarray ? (0, $self->loc("Bad privacy for attribute [_1]", $id)) : 0 if !$privacy; return $self->Load($privacy, $id); } =head2 PostLoad Called after a successful L</Load>. =cut sub PostLoad { } =head2 PostLoadValidate Called just before returning success from L</Load>; may be used to validate that the record is correct. This method is expected to return a (ok, msg) pair. =cut sub PostLoadValidate { return 1; } =head2 Save Creates a new shared setting. Takes a privacy, a name, and any other arguments. Saves the given parameters to the appropriate user/group object, and loads the resulting object. Arguments are passed to the L</SaveAttribute> method, which does the actual update. Returns a tuple of status and message, where status is true on success. Defaults are: Privacy: CurrentUser only Name: "new (ObjectName)" =cut sub Save { my $self = shift; my %args = ( 'Privacy' => 'RT::User-' . $self->CurrentUser->Id, 'Name' => "new " . $self->ObjectName, @_, ); my $privacy = $args{'Privacy'}; my $name = $args{'Name'}, my $object = $self->_GetObject($privacy); return (0, $self->loc("Failed to load object for [_1]", $privacy)) unless $object; return (0, $self->loc("Permission Denied")) unless $self->CurrentUserCanCreate($privacy); my ($att_id, $att_msg) = $self->SaveAttribute($object, \%args); if ($att_id) { $self->{'Attribute'} = RT::Attribute->new($self->CurrentUser); $self->{'Attribute'}->Load( $att_id ); $self->{'Id'} = $att_id; $self->{'Privacy'} = $privacy; return ( 1, $self->loc( "Saved [_1] [_2]", $self->loc( $self->ObjectName ), $name ) ); } else { $RT::Logger->error($self->ObjectName . " save failure: $att_msg"); return ( 0, $self->loc("Failed to create [_1] attribute", $self->loc( $self->ObjectName ) ) ); } } =head2 SaveAttribute An empty method for subclassing. Called from L</Save> method. =cut sub SaveAttribute { } =head2 Update Updates the parameters of an existing shared setting. Any arguments are passed to the L</UpdateAttribute> method. Returns a tuple of status and message, where status is true on success. =cut sub Update { my $self = shift; my %args = @_; return(0, $self->loc("No [_1] loaded", $self->ObjectName)) unless $self->Id; return(0, $self->loc("Could not load [_1] attribute", $self->ObjectName)) unless $self->{'Attribute'}->Id; return (0, $self->loc("Permission Denied")) unless $self->CurrentUserCanModify; my ($status, $msg) = $self->UpdateAttribute(\%args); return (1, $self->loc("[_1] update: Nothing changed", ucfirst($self->ObjectName))) if !defined $msg; # prevent useless warnings return (1, $self->loc("[_1] updated"), ucfirst($self->ObjectName)) if $msg =~ /That is already the current value/; return ($status, $self->loc("[_1] update: [_2]", ucfirst($self->ObjectName), $msg)); } =head2 UpdateAttribute An empty method for subclassing. Called from L</Update> method. =cut sub UpdateAttribute { } =head2 Delete Deletes the existing shared setting. Returns a tuple of status and message, where status is true upon success. =cut sub Delete { my $self = shift; return (0, $self->loc("Permission Denied")) unless $self->CurrentUserCanDelete; my ($status, $msg) = $self->{'Attribute'}->Delete; $self->CurrentUser->ClearAttributes; # force the current user's attribute cache to be cleaned up if ($status) { return (1, $self->loc("Deleted [_1]", $self->ObjectName)); } else { return (0, $self->loc("Delete failed: [_1]", $msg)); } } ### Accessor methods =head2 Name Returns the name of this shared setting. =cut sub Name { my $self = shift; return unless ref($self->{'Attribute'}) eq 'RT::Attribute'; return $self->{'Attribute'}->Description(); } =head2 Id Returns the numerical ID of this shared setting. =cut sub Id { my $self = shift; return $self->{'Id'}; } *id = \&Id; =head2 Privacy Returns the principal object to whom this shared setting belongs, in a string "<class>-<id>", e.g. "RT::Group-16". =cut sub Privacy { my $self = shift; return $self->{'Privacy'}; } =head2 GetParameter Returns the given named parameter of the setting. =cut sub GetParameter { my $self = shift; my $param = shift; return unless ref($self->{'Attribute'}) eq 'RT::Attribute'; return $self->{'Attribute'}->SubValue($param); } =head2 IsVisibleTo Privacy Returns true if the setting is visible to all principals of the given privacy. This does not deal with ACLs, this only looks at membership. =cut sub IsVisibleTo { my $self = shift; my $to = shift; my $privacy = $self->Privacy || ''; # if the privacies are the same, then they can be seen. this handles # a personal setting being visible to that user. return 1 if $privacy eq $to; # If the setting is systemwide, then any user can see it. return 1 if $privacy =~ /^RT::System/; # Only privacies that are RT::System can be seen by everyone. return 0 if $to =~ /^RT::System/; # If the setting is group-wide... if ($privacy =~ /^RT::Group-(\d+)$/) { my $setting_group = RT::Group->new($self->CurrentUser); $setting_group->Load($1); if ($to =~ /-(\d+)$/) { my $to_id = $1; # then any principal that is a member of the setting's group can see # the setting return $setting_group->HasMemberRecursively($to_id); } } return 0; } sub CurrentUserCanSee { 1 } sub CurrentUserCanCreate { 1 } sub CurrentUserCanModify { 1 } sub CurrentUserCanDelete { 1 } =head2 ColumnMapClassName Equivalent to L<RT::Record/ColumnMapClassName> =cut sub ColumnMapClassName { return RT::Record::ColumnMapClassName(shift) } ### Internal methods # _GetObject: helper routine to load the correct object whose parameters # have been passed. sub _GetObject { my $self = shift; my $privacy = shift; # short circuit: if they pass the object we want anyway, just return it if (blessed($privacy) && $privacy->isa('RT::Record')) { return $privacy; } my ($obj_type, $obj_id) = split(/\-/, ($privacy || '')); unless ($obj_type && $obj_id) { $privacy = '(undef)' if !defined($privacy); $RT::Logger->debug("Invalid privacy string '$privacy'"); return undef; } my $object = $self->_load_privacy_object($obj_type, $obj_id); unless (ref($object) eq $obj_type) { $RT::Logger->error("Could not load object of type $obj_type with ID $obj_id, got object of type " . (ref($object) || 'undef')); return undef; } # Do not allow the loading of a user object other than the current # user, or of a group object of which the current user is not a member. if ($obj_type eq 'RT::User' && $object->Id != $self->CurrentUser->UserObj->Id) { $RT::Logger->debug("Permission denied for user other than self"); return undef; } if ( $obj_type eq 'RT::Group' && !$object->HasMemberRecursively($self->CurrentUser->PrincipalObj) && !$self->CurrentUser->HasRight( Object => $RT::System, Right => 'SuperUser' ) ) { $RT::Logger->debug("Permission denied, ".$self->CurrentUser->Name. " is not a member of group"); return undef; } return $object; } sub _load_privacy_object { my ($self, $obj_type, $obj_id) = @_; if ( $obj_type eq 'RT::User' ) { if ( $obj_id == $self->CurrentUser->Id ) { return $self->CurrentUser->UserObj; } else { $RT::Logger->warning("User #". $self->CurrentUser->Id ." tried to load container user #". $obj_id); return undef; } } elsif ($obj_type eq 'RT::Group') { my $group = RT::Group->new($self->CurrentUser); $group->Load($obj_id); return $group; } elsif ($obj_type eq 'RT::System') { return RT::System->new($self->CurrentUser); } $RT::Logger->error( "Tried to load a ". $self->ObjectName ." belonging to an $obj_type, which is neither a user nor a group" ); return undef; } sub _build_privacy { my ($self, $obj_type, $obj_id) = @_; # allow passing in just an object to find its privacy string if (ref($obj_type)) { my $Object = $obj_type; return $Object->isa('RT::User') ? 'RT::User-' . $Object->Id : $Object->isa('RT::Group') ? 'RT::Group-' . $Object->Id : $Object->isa('RT::System') ? 'RT::System-' . $Object->Id : undef; } return undef unless ($obj_type); # undef workaround return $obj_type eq 'RT::User' ? "$obj_type-$obj_id" : $obj_type eq 'RT::Group' ? "$obj_type-$obj_id" : $obj_type eq 'RT::System' ? "$obj_type-$obj_id" : undef; } =head2 ObjectsForLoading Returns a list of objects that can be used to load this shared setting. It is ACL checked. =cut sub ObjectsForLoading { my $self = shift; return grep { $self->CurrentUserCanSee($_) } $self->_PrivacyObjects; } =head2 ObjectsForCreating Returns a list of objects that can be used to create this shared setting. It is ACL checked. =cut sub ObjectsForCreating { my $self = shift; return grep { $self->CurrentUserCanCreate($_) } $self->_PrivacyObjects; } =head2 ObjectsForModifying Returns a list of objects that can be used to modify this shared setting. It is ACL checked. =cut sub ObjectsForModifying { my $self = shift; return grep { $self->CurrentUserCanModify($_) } $self->_PrivacyObjects; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Topics.pm���������������������������������������������������������������������������000644 �000765 �000024 �00000005746 14005011336 015707� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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; no warnings qw(redefine); package RT::Topics; use base 'RT::SearchBuilder'; sub Table {'Topics'} # {{{ LimitToObject =head2 LimitToObject OBJ Find all Topics hung off of the given Object =cut sub LimitToObject { my $self = shift; my $object = shift; my $subclause = "limittoobject"; $self->_OpenParen($subclause); $self->Limit(FIELD => 'ObjectId', VALUE => $object->Id, SUBCLAUSE => $subclause); $self->Limit(FIELD => 'ObjectType', VALUE => ref($object), SUBCLAUSE => $subclause, ENTRYAGGREGATOR => 'AND'); $self->_CloseParen($subclause); } # }}} # {{{ LimitToKids =head2 LimitToKids TOPIC Find all Topics which are immediate children of Id TOPIC. Note this does not do the recursive query of their kids, etc. =cut sub LimitToKids { my $self = shift; my $topic = shift; $self->Limit(FIELD => 'Parent', VALUE => $topic); } # }}} RT::Base->_ImportOverlays(); 1; ��������������������������rt-5.0.1/lib/RT/Condition.pm������������������������������������������������������������������������000644 �000765 �000024 �00000011035 14005011336 016360� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Condition - generic baseclass for scrip condition; =head1 SYNOPSIS use RT::Condition; my $foo = RT::Condition->new( TransactionObj => $tr, TicketObj => $ti, ScripObj => $scr, Argument => $arg, Type => $type); if ($foo->IsApplicable) { # do something } =head1 DESCRIPTION =head1 METHODS =cut package RT::Condition; use strict; use warnings; use base qw/RT::Base/; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; bless ($self, $class); $self->_Init(@_); return $self; } sub _Init { my $self = shift; my %args = ( TransactionObj => undef, TicketObj => undef, ScripObj => undef, TemplateObj => undef, Argument => undef, ApplicableTransTypes => undef, CurrentUser => undef, @_ ); $self->{'Argument'} = $args{'Argument'}; $self->{'ScripObj'} = $args{'ScripObj'}; $self->{'TicketObj'} = $args{'TicketObj'}; $self->{'TransactionObj'} = $args{'TransactionObj'}; $self->{'ApplicableTransTypes'} = $args{'ApplicableTransTypes'}; $self->CurrentUser($args{'CurrentUser'}); } # Access Scripwide data =head2 Argument Return the optional argument associated with this ScripCondition =cut sub Argument { my $self = shift; return($self->{'Argument'}); } =head2 TicketObj Return the ticket object we're talking about =cut sub TicketObj { my $self = shift; return($self->{'TicketObj'}); } =head2 ScripObj Return the Scrip object we're talking about =cut sub ScripObj { my $self = shift; return($self->{'ScripObj'}); } =head2 TransactionObj Return the transaction object we're talking about =cut sub TransactionObj { my $self = shift; return($self->{'TransactionObj'}); } =head2 Type =cut sub ApplicableTransTypes { my $self = shift; return($self->{'ApplicableTransTypes'}); } # Scrip methods #What does this type of Action does sub Describe { my $self = shift; return ($self->loc("No description for [_1]", ref $self)); } #Parse the templates, get things ready to go. #If this rule applies to this transaction, return true. sub IsApplicable { my $self = shift; return(undef); } sub DESTROY { my $self = shift; # We need to clean up all the references that might maybe get # oddly circular $self->{'TemplateObj'} =undef $self->{'TicketObj'} = undef; $self->{'TransactionObj'} = undef; $self->{'ScripObj'} = undef; } RT::Base->_ImportOverlays(); 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Dashboard/��������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 015763� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Attachment.pm�����������������������������������������������������������������������000644 �000765 �000024 �00000110643 14005011336 016527� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 SYNOPSIS use RT::Attachment; =head1 DESCRIPTION This module should never be instantiated directly by client code. it's an internal module which should only be instantiated through exported APIs in Ticket, Queue and other similar objects. =head1 METHODS =cut package RT::Attachment; use base 'RT::Record'; sub Table {'Attachments'} use strict; use warnings; use RT::Transaction; use MIME::Base64; use MIME::QuotedPrint; use MIME::Body; use RT::Util 'mime_recommended_filename'; use URI; sub _OverlayAccessible { { TransactionId => { 'read'=>1, 'public'=>1, 'write' => 0 }, MessageId => { 'read'=>1, 'write' => 0 }, Parent => { 'read'=>1, 'write' => 0 }, ContentType => { 'read'=>1, 'write' => 0 }, Subject => { 'read'=>1, 'write' => 0 }, Content => { 'read'=>1, 'write' => 0 }, ContentEncoding => { 'read'=>1, 'write' => 0 }, Headers => { 'read'=>1, 'write' => 0 }, Filename => { 'read'=>1, 'write' => 0 }, Creator => { 'read'=>1, 'auto'=>1, }, Created => { 'read'=>1, 'auto'=>1, }, }; } =head2 Create Create a new attachment. Takes a paramhash: 'Attachment' Should be a single MIME body with optional subparts 'Parent' is an optional id of the parent attachment 'TransactionId' is the mandatory id of the transaction this attachment is associated with.; =cut sub Create { my $self = shift; my %args = ( id => 0, TransactionId => 0, Parent => 0, Attachment => undef, @_ ); # For ease of reference my $Attachment = $args{'Attachment'}; # if we didn't specify a ticket, we need to bail unless ( $args{'TransactionId'} ) { $RT::Logger->crit( "RT::Attachment->Create couldn't, as you didn't specify a transaction" ); return (0); } # If we possibly can, collapse it to a singlepart $Attachment->make_singlepart; my $head = $Attachment->head; # Get the subject my $Subject = Encode::decode( 'UTF-8', $head->get( 'subject' ) ); $Subject = '' unless defined $Subject; chomp $Subject; #Get the Message-ID my $MessageId = Encode::decode( "UTF-8", $head->get( 'Message-ID' ) ); defined($MessageId) or $MessageId = ''; chomp ($MessageId); $MessageId =~ s/^<(.*?)>$/$1/o; #Get the filename my $Filename = mime_recommended_filename($Attachment); # remove path part. $Filename =~ s!.*/!! if $Filename; my $content; unless ( $head->get('Content-Length') ) { my $length = 0; $length = length $Attachment->bodyhandle->as_string if defined $Attachment->bodyhandle; $head->replace( 'Content-Length' => Encode::encode( "UTF-8", $length ) ); } $head = $head->as_string; # MIME::Head doesn't support perl strings well and can return # octets which later will be double encoded in low-level code $head = Encode::decode( 'UTF-8', $head ); # If a message has no bodyhandle, that means that it has subparts (or appears to) # and we should act accordingly. unless ( defined $Attachment->bodyhandle ) { my ($id) = $self->SUPER::Create( TransactionId => $args{'TransactionId'}, Parent => $args{'Parent'}, ContentType => $Attachment->mime_type, Headers => $head, MessageId => $MessageId, Subject => $Subject, ); unless ($id) { $RT::Logger->crit("Attachment insert failed - ". $RT::Handle->dbh->errstr); my $txn = RT::Transaction->new($self->CurrentUser); $txn->Load($args{'TransactionId'}); if ( $txn->id ) { $txn->Object->_NewTransaction( Type => 'AttachmentError', ActivateScrips => 0, Data => $Filename ); } return ($id); } foreach my $part ( $Attachment->parts ) { my $SubAttachment = RT::Attachment->new( $self->CurrentUser ); my ($id) = $SubAttachment->Create( TransactionId => $args{'TransactionId'}, Parent => $id, Attachment => $part, ); unless ($id) { $RT::Logger->crit("Attachment insert failed: ". $RT::Handle->dbh->errstr); return ($id); } } return ($id); } #If it's not multipart else { my ( $encoding, $type, $note_args ); ( $encoding, $content, $type, $Filename, $note_args ) = $self->_EncodeLOB( $Attachment->bodyhandle->as_string, $Attachment->mime_type, $Filename, ); my $id = $self->SUPER::Create( TransactionId => $args{'TransactionId'}, ContentType => $type, ContentEncoding => $encoding, Parent => $args{'Parent'}, Headers => $head, Subject => $Subject, Content => $content, Filename => $Filename, MessageId => $MessageId, ); if ($id) { if ($note_args) { $self->TransactionObj->Object->_NewTransaction( %$note_args ); } } else { $RT::Logger->crit("Attachment insert failed: ". $RT::Handle->dbh->errstr); my $txn = RT::Transaction->new($self->CurrentUser); $txn->Load($args{'TransactionId'}); if ( $txn->id ) { $txn->Object->_NewTransaction( Type => 'AttachmentError', ActivateScrips => 0, Data => $Filename ); } } return $id; } } =head2 TransactionObj Returns the transaction object asscoiated with this attachment. =cut sub TransactionObj { my $self = shift; unless ( $self->{_TransactionObj} ) { $self->{_TransactionObj} = RT::Transaction->new( $self->CurrentUser ); $self->{_TransactionObj}->Load( $self->TransactionId ); } unless ($self->{_TransactionObj}->Id) { $RT::Logger->crit( "Attachment ". $self->id ." can't find transaction ". $self->TransactionId ." which it is ostensibly part of. That's bad"); } return $self->{_TransactionObj}; } =head2 ParentObj Returns a parent's L<RT::Attachment> object if this attachment has a parent, otherwise returns undef. =cut sub ParentObj { my $self = shift; return undef unless $self->Parent; my $parent = RT::Attachment->new( $self->CurrentUser ); $parent->LoadById( $self->Parent ); return $parent; } =head2 Closest Takes a MIME type as a string or regex. Returns an L<RT::Attachment> object for the nearest containing part with a matching L</ContentType>. Strings must match exactly and all matches are done case insensitively. Strings ending in a C</> must only match the first part of the MIME type. For example: # Find the nearest multipart/* container my $container = $attachment->Closest("multipart/"); Returns undef if no such object is found. =cut sub Closest { my $self = shift; my $type = shift; my $part = $self->ParentObj or return undef; $type = qr/^\Q$type\E$/ unless ref $type eq "REGEX"; while (lc($part->ContentType) !~ $type) { $part = $part->ParentObj or last; } return ($part and $part->id) ? $part : undef; } =head2 Children Returns an L<RT::Attachments> object which is preloaded with all attachments objects with this attachment's Id as their C<Parent>. =cut sub Children { my $self = shift; my $kids = RT::Attachments->new( $self->CurrentUser ); $kids->ChildrenOf( $self->Id ); return($kids); } =head2 Siblings Returns an L<RT::Attachments> object containing all the attachments sharing the same immediate parent as the current object, excluding the current attachment itself. If the current attachment is a top-level part (i.e. Parent == 0) then a guaranteed empty L<RT::Attachments> object is returned. =cut sub Siblings { my $self = shift; my $siblings = RT::Attachments->new( $self->CurrentUser ); if ($self->Parent) { $siblings->ChildrenOf( $self->Parent ); $siblings->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => $self->Id ); } else { # Ensure emptiness $siblings->Limit( SUBCLAUSE => 'empty', FIELD => 'id', VALUE => 0 ); } return $siblings; } =head2 Content Returns the attachment's content. if it's base64 encoded, decode it before returning it. =cut sub Content { my $self = shift; return $self->_DecodeLOB( $self->GetHeader('Content-Type'), # Includes charset, unlike ->ContentType $self->ContentEncoding, $self->_Value('Content', decode_utf8 => 0), ); } =head2 OriginalContent Returns the attachment's content as octets before RT's mangling. Generally this just means restoring text content back to its original encoding. If the attachment has a C<message/*> Content-Type, its children attachments are reconstructed and returned as a string. =cut sub OriginalContent { my $self = shift; # message/* content types represent raw messages. Since we break them # apart when they come in, we'll reconstruct their child attachments when # you ask for the OriginalContent of the message/ part. if ($self->IsMessageContentType) { # There shouldn't be more than one "subpart" to a message/* attachment my $child = $self->Children->First; return $self->Content unless $child and $child->id; return $child->ContentAsMIME(Children => 1)->as_string; } return $self->Content unless RT::I18N::IsTextualContentType($self->ContentType); my $content = $self->_DecodeLOB( "application/octet-stream", # Force _DecodeLOB to not decode to characters $self->ContentEncoding, $self->_Value('Content', decode_utf8 => 0), ); my $entity = MIME::Entity->new(); $entity->head->add("Content-Type", $self->GetHeader("Content-Type")); $entity->bodyhandle( MIME::Body::Scalar->new( $content ) ); my $from = RT::I18N::_FindOrGuessCharset($entity); $from = 'utf-8' if not $from or not Encode::find_encoding($from); my $to = RT::I18N::_CanonicalizeCharset( $self->OriginalEncoding || 'utf-8' ); local $@; eval { Encode::from_to($content, $from => $to) }; if ($@) { $RT::Logger->error("Could not convert attachment from $from to $to: ".$@); } return $content; } =head2 OriginalEncoding Returns the attachment's original encoding. =cut sub OriginalEncoding { my $self = shift; return $self->GetHeader('X-RT-Original-Encoding'); } =head2 ContentLength Returns length of L</Content> in bytes. =cut sub ContentLength { my $self = shift; return undef unless $self->TransactionObj->CurrentUserCanSee; my $len = $self->GetHeader('Content-Length'); unless ( defined $len ) { use bytes; no warnings 'uninitialized'; $len = length($self->Content) || 0; $self->SetHeader('Content-Length' => $len); } return $len; } =head2 FriendlyContentLength Returns L</ContentLength> in bytes, kilobytes, or megabytes as most appropriate. The size is suffixed with C<MiB>, C<KiB>, or C<B> and the returned string is localized. Returns the empty string if the L</ContentLength> is 0 or undefined. =cut sub FriendlyContentLength { my $self = shift; my $size = $self->ContentLength; return '' unless $size; my $res = ''; if ( $size > 1024*1024 ) { $res = $self->loc( "[_1]MiB", int( $size / 1024 / 102.4 ) / 10 ); } elsif ( $size > 1024 ) { $res = $self->loc( "[_1]KiB", int( $size / 102.4 ) / 10 ); } else { $res = $self->loc( "[_1]B", $size ); } return $res; } =head2 ContentAsMIME [Children => 1] Returns MIME entity built from this attachment. If the optional parameter C<Children> is set to a true value, the children are recursively added to the entity. =cut sub _EncodeHeaderToMIME { my ( $self, $header_name, $header_val ) = @_; if ($header_name =~ /^Content-/i) { my $params = MIME::Field::ParamVal->parse_params($header_val); $header_val = delete $params->{'_'}; foreach my $key ( sort keys %$params ) { my $value = $params->{$key}; if ( $value =~ /[^\x00-\x7f]/ ) { # check for non-ASCII $value = q{UTF-8''} . URI->new( Encode::encode('UTF-8', $value) ); $value =~ s/(["\\])/\\$1/g; $header_val .= qq{; ${key}*="$value"}; } else { $header_val .= qq{; $key="$value"}; } } } elsif ( $header_name =~ /^(?:Resent-)?(?:To|From|B?Cc|Sender|Reply-To)$/i ) { my @addresses = RT::EmailParser->ParseEmailAddress( $header_val ); foreach my $address ( @addresses ) { foreach my $field (qw(phrase comment)) { my $v = $address->$field() or next; $v = RT::Interface::Email::EncodeToMIME( String => $v ); $address->$field($v); } } $header_val = join ', ', map $_->format, @addresses; } else { $header_val = RT::Interface::Email::EncodeToMIME( String => $header_val ); } return $header_val; } sub ContentAsMIME { my $self = shift; my %opts = ( Children => 0, @_ ); my $entity = MIME::Entity->new(); foreach my $header ($self->SplitHeaders) { my ($h_key, $h_val) = split /:/, $header, 2; $entity->head->add( $h_key, $self->_EncodeHeaderToMIME($h_key, $h_val) ); } if ($entity->is_multipart) { if ($opts{'Children'} and not $self->IsMessageContentType) { my $children = $self->Children; while (my $child = $children->Next) { $entity->add_part( $child->ContentAsMIME(%opts) ); } } } else { # since we want to return original content, let's use original encoding $entity->head->mime_attr( "Content-Type.charset" => $self->OriginalEncoding ) if $self->OriginalEncoding; $entity->bodyhandle( MIME::Body::Scalar->new( $self->OriginalContent ) ); } return $entity; } =head2 IsMessageContentType Returns a boolean indicating if the Content-Type of this attachment is a C<message/> subtype. =cut sub IsMessageContentType { my $self = shift; return $self->ContentType =~ m{^\s*message/}i ? 1 : 0; } =head2 Addresses Returns a hashref of all addresses related to this attachment. The keys of the hash are C<From>, C<To>, C<Cc>, C<Bcc>, C<RT-Send-Cc> and C<RT-Send-Bcc>. The values are references to lists of L<Email::Address> objects. =cut our @ADDRESS_HEADERS = qw(From To Cc Bcc RT-Send-Cc RT-Send-Bcc); sub Addresses { my $self = shift; my %data = (); my $current_user_address = lc($self->CurrentUser->EmailAddress || ''); foreach my $hdr (@ADDRESS_HEADERS) { my @Addresses; my $line = $self->GetHeader($hdr); foreach my $AddrObj ( Email::Address->parse( $line )) { my $address = $AddrObj->address; $address = lc RT::User->CanonicalizeEmailAddress($address); next if $current_user_address eq $address; next if RT::EmailParser->IsRTAddress($address); push @Addresses, $AddrObj ; } $data{$hdr} = \@Addresses; } return \%data; } =head2 NiceHeaders Returns a multi-line string of the To, From, Cc, Date and Subject headers. =cut sub NiceHeaders { my $self = shift; my $hdrs = ""; my @hdrs = $self->_SplitHeaders; while (my $str = shift @hdrs) { next unless $str =~ /^(To|From|RT-Send-Cc|Cc|Bcc|Date|Subject):/i; $hdrs .= $str . "\n"; $hdrs .= shift( @hdrs ) . "\n" while ($hdrs[0] =~ /^[ \t]+/); } return $hdrs; } =head2 Headers Returns this object's headers as a string. This method specifically removes the RT-Send-Bcc: header, so as to never reveal to whom RT sent a Bcc. We need to record the RT-Send-Cc and RT-Send-Bcc values so that we can actually send out mail. The mailing rules are separated from the ticket update code by an abstraction barrier that makes it impossible to pass this data directly. =cut sub Headers { return join("\n", $_[0]->SplitHeaders); } =head2 EncodedHeaders Takes encoding as argument and returns the attachment's headers as octets in encoded using the encoding. This is not protection using quoted printable or base64 encoding. =cut sub EncodedHeaders { my $self = shift; my $encoding = shift || 'utf8'; # Require Encode::HanExtra to handle more encodings it supports. # The regex is based on the names documented in Encode::HanExtra. if ( $encoding =~ /^(?:big5(?:-1984|-2003|ext|plus)|cccii|cns11643-[1-7f]|euc-tw|gb18030|unisys(?:-sosi(?:1|2))?)$/ ) { unless ( Encode::HanExtra->require ) { RT->Logger->error("Need Encode::HanExtra to handle $encoding"); } } return Encode::encode( $encoding, $self->Headers ); } =head2 GetHeader $TAG Returns the value of the B<first> header Tag as a string. This bypasses the weeding out done in Headers() above. =cut sub GetHeader { my $self = shift; my $tag = shift; foreach my $line ($self->_SplitHeaders) { next unless $line =~ /^\Q$tag\E:\s+(.*)$/si; #if we find the header, return its value return ($1); } # we found no header. return an empty string return undef; } =head2 GetAllHeaders $TAG Returns a list of all values for the the given header tag, in the order they appear. =cut sub GetAllHeaders { my $self = shift; my $tag = shift; my @values = (); foreach my $line ($self->_SplitHeaders) { next unless $line =~ /^\Q$tag\E:\s+(.*)$/si; push @values, $1; } return @values; } =head2 DelHeader $TAG Delete a field from the attachment's headers. =cut sub DelHeader { my $self = shift; my $tag = shift; my $newheader = ''; foreach my $line ($self->_SplitHeaders) { next if $line =~ /^\Q$tag\E:\s+/i; $newheader .= "$line\n"; } return $self->__Set( Field => 'Headers', Value => $newheader); } =head2 AddHeader $TAG, $VALUE, ... Add one or many fields to the attachment's headers. =cut sub AddHeader { my $self = shift; my $newheader = $self->__Value( 'Headers' ); while ( my ($tag, $value) = splice @_, 0, 2 ) { $value = $self->_CanonicalizeHeaderValue($value); $newheader .= "$tag: $value\n"; } return $self->__Set( Field => 'Headers', Value => $newheader); } =head2 SetHeader ( 'Tag', 'Value' ) Replace or add a Header to the attachment's headers. =cut sub SetHeader { my $self = shift; my $tag = shift; my $value = $self->_CanonicalizeHeaderValue(shift); my $replaced = 0; my $newheader = ''; foreach my $line ( $self->_SplitHeaders ) { if ( $line =~ /^\Q$tag\E:\s+/i ) { # replace first instance, skip all the rest unless ($replaced) { $newheader .= "$tag: $value\n"; $replaced = 1; } } else { $newheader .= "$line\n"; } } $newheader .= "$tag: $value\n" unless $replaced; $self->__Set( Field => 'Headers', Value => $newheader); } =head2 ReplaceHeaders ( Search => 'SEARCH', Replacement => 'Replacement' ) Search the attachments table's Header column for the search string provided. When a match is found call the SetHeader() method on the header with the match, either set the header to empty or a replacement value. =cut sub ReplaceHeaders { my $self = shift; my %args = ( Search => undef, Replacement => '', @_, ); return ( 0, $self->loc('No Search string provided') ) unless $args{Search}; my $updated; foreach my $header ( $self->SplitHeaders ) { my ( $tag, $value ) = split /:/, $header, 2; if ( $value =~ s/\Q$args{Search}\E/$args{Replacement}/ig ) { my ( $ret, $msg ) = $self->SetHeader( $tag, $value ); if ( $ret ) { $updated ||= 1; } else { RT::Logger->error("Could not set header: $tag to $value: $msg"); return ( $ret, $msg ); } } } if ( $updated ) { return ( 1, $self->loc('Headers cleared') ); } else { return ( 0, $self->loc('No header matches found') ); } } =head2 ReplaceContent ( Search => 'SEARCH', Replacement => 'Replacement' ) Search the attachments table's Content column for the search string provided. When a match is found either replace it with the provided replacement string or an empty string. =cut sub ReplaceContent { my $self = shift; my %args = ( Search => undef, Replacement => '', @_, ); return ( 0, $self->loc('No search string provided') ) unless $args{Search}; my $content = $self->Content; if ( $content && $content =~ s/\Q$args{Search}\E/$args{Replacement}/ig ) { my ( $encoding, $encoded_content, undef, undef, $note_args ) = $self->_EncodeLOB( Encode::encode( 'UTF-8', $content ) ); $RT::Handle->BeginTransaction; if ($note_args) { $self->TransactionObj->Object->_NewTransaction(%$note_args); } my ( $ret, $msg ) = $self->_Set( Field => 'Content', Value => $encoded_content ); unless ($ret) { $RT::Handle->Rollback; return ( $ret, $msg ); } if ( ( $self->ContentEncoding // '' ) ne $encoding ) { my ( $ret, $msg ) = $self->_Set( Field => 'ContentEncoding', Value => $encoding ); unless ($ret) { $RT::Handle->Rollback; return ( $ret, $msg ); } } $RT::Handle->Commit; return ( $ret, 'Content replaced' ); } return ( 0, $self->loc('No content matches found') ); } sub _CanonicalizeHeaderValue { my $self = shift; my $value = shift; $value = '' unless defined $value; $value =~ s/\s+$//s; $value =~ s/\r*\n/\n /g; return $value; } =head2 SplitHeaders Returns an array of this attachment object's headers, with one header per array entry. Multiple lines are folded. B<Never> returns C<RT-Send-Bcc> field. =cut sub SplitHeaders { my $self = shift; return (grep !/^RT-Send-Bcc/i, $self->_SplitHeaders(@_) ); } =head2 _SplitHeaders Returns an array of this attachment object's headers, with one header per array entry. multiple lines are folded. =cut sub _SplitHeaders { my $self = shift; my $headers = (shift || $self->_Value('Headers')); my @headers; # XXX TODO: splitting on \n\w is _wrong_ as it treats \n[ as a valid # continuation, which it isn't. The correct split pattern, per RFC 2822, # is /\n(?=[^ \t]|\z)/. That is, only "\n " or "\n\t" is a valid # continuation. Older values of X-RT-GnuPG-Status contain invalid # continuations and rely on this bogus split pattern, however, so it is # left as-is for now. for (split(/\n(?=\w|\z)/,$headers)) { push @headers, $_; } return(@headers); } sub Encrypt { my $self = shift; my $txn = $self->TransactionObj; return (0, $self->loc('Permission Denied')) unless $txn->CurrentUserCanSee; return (0, $self->loc('Permission Denied')) unless $txn->TicketObj->CurrentUserHasRight('ModifyTicket'); return (0, $self->loc('Cryptography is disabled')) unless RT->Config->Get('Crypt')->{'Enable'}; return (0, $self->loc('Attachments encryption is disabled')) unless RT->Config->Get('Crypt')->{'AllowEncryptDataInDB'}; my $type = $self->ContentType; if ( $type =~ /^x-application-rt\/[^-]+-encrypted/i ) { return (1, $self->loc('Already encrypted')); } elsif ( $type =~ /^multipart\//i ) { return (1, $self->loc('No need to encrypt')); } my $queue = $txn->TicketObj->QueueObj; my $encrypt_for; foreach my $address ( grep $_, $queue->CorrespondAddress, $queue->CommentAddress, RT->Config->Get('CorrespondAddress'), RT->Config->Get('CommentAddress'), ) { my %res = RT::Crypt->GetKeysInfo( Key => $address, Type => 'private' ); next if $res{'exit_code'} || !$res{'info'}; %res = RT::Crypt->GetKeysForEncryption( $address ); next if $res{'exit_code'} || !$res{'info'}; $encrypt_for = $address; } unless ( $encrypt_for ) { return (0, $self->loc('No key suitable for encryption')); } my $content = $self->Content; my %res = RT::Crypt->SignEncryptContent( Content => \$content, Sign => 0, Encrypt => 1, Recipients => [ $encrypt_for ], ); if ( $res{'exit_code'} ) { return (0, $self->loc('Encryption error; contact the administrator')); } my ($status, $msg) = $self->__Set( Field => 'Content', Value => $content ); unless ( $status ) { return ($status, $self->loc("Couldn't replace content with encrypted data: [_1]", $msg)); } $type = qq{x-application-rt\/$res{'Protocol'}-encrypted; original-type="$type"}; $self->__Set( Field => 'ContentType', Value => $type ); $self->SetHeader( 'Content-Type' => $type ); return (1, $self->loc('Successfuly encrypted data')); } sub Decrypt { my $self = shift; my $txn = $self->TransactionObj; return (0, $self->loc('Permission Denied')) unless $txn->CurrentUserCanSee; return (0, $self->loc('Permission Denied')) unless $txn->TicketObj->CurrentUserHasRight('ModifyTicket'); return (0, $self->loc('Cryptography is disabled')) unless RT->Config->Get('Crypt')->{'Enable'}; my $type = $self->ContentType; my $protocol; if ( $type =~ /^x-application-rt\/([^-]+)-encrypted/i ) { $protocol = $1; $protocol =~ s/gpg/gnupg/; # backwards compatibility ($type) = ($type =~ /original-type="(.*)"/i); $type ||= 'application/octet-stream'; } else { return (1, $self->loc('Is not encrypted')); } my $queue = $txn->TicketObj->QueueObj; my @addresses = $queue->CorrespondAddress, $queue->CommentAddress, RT->Config->Get('CorrespondAddress'), RT->Config->Get('CommentAddress') ; my $content = $self->Content; my %res = RT::Crypt->DecryptContent( Protocol => $protocol, Content => \$content, Recipients => \@addresses, ); if ( $res{'exit_code'} ) { return (0, $self->loc('Decryption error; contact the administrator')); } my ($status, $msg) = $self->__Set( Field => 'Content', Value => $content ); unless ( $status ) { return ($status, $self->loc("Couldn't replace content with decrypted data: [_1]", $msg)); } $self->__Set( Field => 'ContentType', Value => $type ); $self->SetHeader( 'Content-Type' => $type ); return (1, $self->loc('Successfuly decrypted data')); } =head2 _Value Takes the name of a table column. Returns its value as a string, if the user passes an ACL check =cut sub _Value { my $self = shift; my $field = shift; #if the field is public, return it. if ( $self->_Accessible( $field, 'public' ) ) { return ( $self->__Value( $field, @_ ) ); } return undef unless $self->TransactionObj->CurrentUserCanSee; return $self->__Value( $field, @_ ); } # Attachments don't change; by adding this cache config directive, # we don't lose pathalogically on long tickets. sub _CacheConfig { { 'cache_for_sec' => 180, } } =head2 id Returns the current value of id. (In the database, id is stored as int(19).) =cut =head2 TransactionId Returns the current value of TransactionId. (In the database, TransactionId is stored as int(19).) =head2 SetTransactionId VALUE Set TransactionId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, TransactionId will be stored as a int(19).) =cut =head2 Parent Returns the current value of Parent. (In the database, Parent is stored as int(19).) =head2 SetParent VALUE Set Parent to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Parent will be stored as a int(19).) =cut =head2 MessageId Returns the current value of MessageId. (In the database, MessageId is stored as varchar(160).) =head2 SetMessageId VALUE Set MessageId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, MessageId will be stored as a varchar(160).) =cut =head2 Subject Returns the current value of Subject. (In the database, Subject is stored as varchar(255).) =head2 SetSubject VALUE Set Subject to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Subject will be stored as a varchar(255).) =cut =head2 Filename Returns the current value of Filename. (In the database, Filename is stored as varchar(255).) =head2 SetFilename VALUE Set Filename to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Filename will be stored as a varchar(255).) =cut =head2 ContentType Returns the current value of ContentType. (In the database, ContentType is stored as varchar(80).) =head2 SetContentType VALUE Set ContentType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ContentType will be stored as a varchar(80).) =cut =head2 ContentEncoding Returns the current value of ContentEncoding. (In the database, ContentEncoding is stored as varchar(80).) =head2 SetContentEncoding VALUE Set ContentEncoding to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ContentEncoding will be stored as a varchar(80).) =cut =head2 Content Returns the current value of Content. (In the database, Content is stored as longblob.) =head2 SetContent VALUE Set Content to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Content will be stored as a longblob.) =cut =head2 Headers Returns the current value of Headers. (In the database, Headers is stored as longtext.) =head2 SetHeaders VALUE Set Headers to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Headers will be stored as a longtext.) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(19)', default => ''}, TransactionId => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(19)', default => ''}, Parent => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(19)', default => '0'}, MessageId => {read => 1, write => 1, sql_type => 12, length => 160, is_blob => 0, is_numeric => 0, type => 'varchar(160)', default => ''}, Subject => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, Filename => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, ContentType => {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''}, ContentEncoding => {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''}, Content => {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longblob', default => ''}, Headers => {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longtext', default => ''}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); $deps->Add( out => $self->TransactionObj ); } sub __DependsOn { my $self = shift; my %args = ( Shredder => undef, Dependencies => undef, @_, ); my $deps = $args{'Dependencies'}; my $list = []; # Nested attachments my $objs = RT::Attachments->new( $self->CurrentUser ); $objs->Limit( FIELD => 'Parent', OPERATOR => '=', VALUE => $self->Id ); $objs->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => $self->Id ); push( @$list, $objs ); $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON, TargetObjects => $list, Shredder => $args{'Shredder'} ); return $self->SUPER::__DependsOn( %args ); } sub ShouldStoreExternally { my $self = shift; my $type = $self->ContentType; my $length = $self->ContentLength; if ($type =~ m{^multipart/}) { return (0, "attachment is multipart"); } elsif ($length == 0) { return (0, "zero length"); } elsif ($type =~ m{^(text|message)/}) { # If textual, we only store externally if it's _large_ return 1 if $length > RT->Config->Get('ExternalStorageCutoffSize'); return (0, "text length ($length) does not exceed ExternalStorageCutoffSize (" . RT->Config->Get('ExternalStorageCutoffSize') . ")"); } elsif ($type =~ m{^image/}) { # Ditto images, which may be displayed inline return 1 if $length > RT->Config->Get('ExternalStorageCutoffSize'); return (0, "image size ($length) does not exceed ExternalStorageCutoffSize (" . RT->Config->Get('ExternalStorageCutoffSize') . ")"); } else { return 1; } } sub ExternalStoreDigest { my $self = shift; return undef if $self->ContentEncoding ne 'external'; return $self->_Value('Content'); } RT::Base->_ImportOverlays(); 1; ���������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/CustomFields.pm���������������������������������������������������������������������000644 �000765 �000024 �00000026575 14005011336 017052� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::CustomFields - a collection of RT CustomField objects =head1 SYNOPSIS use RT::CustomFields; =head1 DESCRIPTION =head1 METHODS =cut package RT::CustomFields; use strict; use warnings; use base 'RT::SearchBuilder'; use RT::CustomField; sub Table { 'CustomFields'} sub _Init { my $self = shift; # By default, order by SortOrder $self->OrderByCols( { ALIAS => 'main', FIELD => 'SortOrder', ORDER => 'ASC' }, { ALIAS => 'main', FIELD => 'Name', ORDER => 'ASC' }, { ALIAS => 'main', FIELD => 'id', ORDER => 'ASC' }, ); $self->{'with_disabled_column'} = 1; return ( $self->SUPER::_Init(@_) ); } =head2 LimitToGrouping Limits this collection object to custom fields which appear under a specified grouping by calling L</Limit> for each CF name as appropriate. Requires an L<RT::Record> object or class name as the first argument and accepts a grouping name as the second. If the grouping name is false (usually via the empty string), limits to custom fields which appear in no grouping. I<Caveat:> While the record object or class name is used to find the available groupings, no automatic limit is placed on the lookup type of the custom fields. It's highly suggested you limit the collection by queue or another lookup type first. This is already done for you if you're creating the collection via the L</CustomFields> method on an L<RT::Record> object. =cut sub LimitToGrouping { my $self = shift; my $obj = shift; my $grouping = shift; my $grouping_class = $self->NewItem->_GroupingClass($obj); my $config = RT->Config->Get('CustomFieldGroupings'); $config = {} unless ref($config) eq 'HASH'; $config = $config->{$grouping_class} || []; my %h = ref $config eq "ARRAY" ? @{$config} : %{$config}; if ( $grouping ) { my $list = $h{$grouping}; unless ( $list and ref($list) eq 'ARRAY' and @$list ) { return $self->Limit( FIELD => 'id', VALUE => 0, ENTRYAGGREGATOR => 'AND' ); } $self->Limit( FIELD => 'Name', FUNCTION => 'LOWER(?)', OPERATOR => 'IN', VALUE => [map {lc $_} @{$list}], CASESENSITIVE => 1, ); } else { my @list = map {@$_} grep defined && ref($_) eq 'ARRAY', values %h; return unless @list; $self->Limit( FIELD => 'Name', FUNCTION => 'LOWER(?)', OPERATOR => 'NOT IN', VALUE => [ map {lc $_} @list ], CASESENSITIVE => 1, ); } return; } =head2 LimitToLookupType Takes LookupType and limits collection. =cut sub LimitToLookupType { my $self = shift; my $lookup = shift; $self->Limit( FIELD => 'LookupType', VALUE => "$lookup" ); } =head2 LimitToChildType Takes partial LookupType and limits collection to records where LookupType is equal or ends with the value. =cut sub LimitToChildType { my $self = shift; my $lookup = shift; $self->Limit( FIELD => 'LookupType', VALUE => "$lookup" ); $self->Limit( FIELD => 'LookupType', ENDSWITH => "$lookup" ); } =head2 LimitToParentType Takes partial LookupType and limits collection to records where LookupType is equal or starts with the value. =cut sub LimitToParentType { my $self = shift; my $lookup = shift; $self->Limit( FIELD => 'LookupType', VALUE => "$lookup" ); $self->Limit( FIELD => 'LookupType', STARTSWITH => "$lookup" ); } =head2 LimitToObjectId Takes an ObjectId and limits the collection to CFs applied to said object. When called multiple times the ObjectId limits are joined with OR. =cut sub LimitToObjectId { my $self = shift; my $id = shift; $self->Limit( ALIAS => $self->_OCFAlias, FIELD => 'ObjectId', OPERATOR => '=', VALUE => $id || 0, ENTRYAGGREGATOR => 'OR' ); } =head2 LimitToGlobalOrObjectId Takes list of object IDs and limits collection to custom fields that are added to these objects or globally. =cut sub LimitToGlobalOrObjectId { my $self = shift; my $global_only = 1; foreach my $id (@_) { $self->LimitToObjectId($id); $global_only = 0 if $id; } $self->LimitToObjectId(0) unless $global_only; } =head2 LimitToNotAdded Takes either list of object ids or nothing. Limits collection to custom fields to listed objects or any corespondingly. Use zero to mean global. =cut sub LimitToNotAdded { my $self = shift; return RT::ObjectCustomFields->new( $self->CurrentUser ) ->LimitTargetToNotAdded( $self => @_ ); } =head2 LimitToAdded Limits collection to custom fields to listed objects or any corespondingly. Use zero to mean global. =cut sub LimitToAdded { my $self = shift; return RT::ObjectCustomFields->new( $self->CurrentUser ) ->LimitTargetToAdded( $self => @_ ); } =head2 LimitToGlobalOrQueue QUEUEID Limits the set of custom fields found to global custom fields or those tied to the queue C<QUEUEID>, similar to L</LimitToGlobalOrObjectId>. Note that this will cause the collection to only return ticket CFs. =cut sub LimitToGlobalOrQueue { my $self = shift; my $queue = shift; $self->LimitToGlobalOrObjectId( $queue ); $self->LimitToLookupType( 'RT::Queue-RT::Ticket' ); } =head2 LimitToQueue QUEUEID Takes a numeric C<QUEUEID>, and limits the Custom Field collection to those only applied directly to it; this limit is OR'd with other L</LimitToQueue> and L</LimitToGlobal> limits. Note that this will cause the collection to only return ticket CFs. =cut sub LimitToQueue { my $self = shift; my $queue = shift; $self->Limit (ALIAS => $self->_OCFAlias, ENTRYAGGREGATOR => 'OR', FIELD => 'ObjectId', VALUE => "$queue") if defined $queue; $self->LimitToLookupType( 'RT::Queue-RT::Ticket' ); } =head2 LimitToGlobal Limits the Custom Field collection to global ticket CFs; this limit is OR'd with L</LimitToQueue> limits. Note that this will cause the collection to only return ticket CFs. =cut sub LimitToGlobal { my $self = shift; $self->Limit (ALIAS => $self->_OCFAlias, ENTRYAGGREGATOR => 'OR', FIELD => 'ObjectId', VALUE => 0); $self->LimitToLookupType( 'RT::Queue-RT::Ticket' ); } =head2 LimitToDefaultValuesSupportedTypes Limits the Custom Field collection to ones of which types support default values. =cut sub LimitToDefaultValuesSupportedTypes { my $self = shift; $self->Limit( FIELD => 'Type', VALUE => 'Binary', OPERATOR => '!=', ENTRYAGGREGATOR => 'AND' ); $self->Limit( FIELD => 'Type', VALUE => 'Image', OPERATOR => '!=', ENTRYAGGREGATOR => 'AND' ); return $self; } =head2 ApplySortOrder Sort custom fields according to thier order application to objects. It's expected that collection contains only records of one L<RT::CustomField/LookupType> and applied to one object or globally (L</LimitToGlobalOrObjectId>), otherwise sorting makes no sense. =cut sub ApplySortOrder { my $self = shift; my $order = shift || 'ASC'; $self->OrderByCols( { ALIAS => $self->_OCFAlias, FIELD => 'SortOrder', ORDER => $order, } ); } =head2 ContextObject Returns context object for this collection of custom fields, but only if it's defined. =cut sub ContextObject { my $self = shift; return $self->{'context_object'}; } =head2 SetContextObject Sets context object for this collection of custom fields. =cut sub SetContextObject { my $self = shift; return $self->{'context_object'} = shift; } sub _OCFAlias { my $self = shift; return RT::ObjectCustomFields->new( $self->CurrentUser ) ->JoinTargetToThis( $self => @_ ); } =head2 AddRecord Overrides the collection to ensure that only custom fields the user can see are returned; also propagates down the L</ContextObject>. =cut sub AddRecord { my $self = shift; my ($record) = @_; $record->SetContextObject( $self->ContextObject ); $record->{include_set_initial} = $self->{include_set_initial}; return unless $record->CurrentUserCanSee; return $self->SUPER::AddRecord( $record ); } =head2 NewItem Returns an empty new RT::CustomField item Overrides <RT::SearchBuilder/NewItem> to make sure </ContextObject> is inherited. =cut sub NewItem { my $self = shift; my $res = RT::CustomField->new($self->CurrentUser); $res->SetContextObject($self->ContextObject); return $res; } =head2 LimitToCatalog Takes a numeric L<RT::Catalog> ID. Limits the L<RT::CustomFields> collection to only those fields applied directly to the specified catalog. This limit is OR'd with other L</LimitToCatalog> and L<RT::CustomFields/LimitToObjectId> calls. Note that this will cause the collection to only return asset CFs. =cut sub LimitToCatalog { my $self = shift; my $catalog = shift; $self->Limit (ALIAS => $self->_OCFAlias, ENTRYAGGREGATOR => 'OR', FIELD => 'ObjectId', VALUE => "$catalog") if defined $catalog; $self->LimitToLookupType( RT::Asset->CustomFieldLookupType ); $self->ApplySortOrder; unless ($self->ContextObject) { my $obj = RT::Catalog->new( $self->CurrentUser ); $obj->Load( $catalog ); $self->SetContextObject( $obj ); } } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ACL.pm������������������������������������������������������������������������������000644 �000765 �000024 �00000014341 14005011336 015034� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::ACL - collection of RT ACE objects =head1 SYNOPSIS use RT::ACL; my $ACL = RT::ACL->new($CurrentUser); =head1 DESCRIPTION =head1 METHODS =cut package RT::ACL; use base 'RT::SearchBuilder'; use RT::ACE; sub Table { 'ACL'} use strict; use warnings; =head2 Next Hand out the next ACE that was found =cut =head2 LimitToObject $object Limit the ACL to rights for the object $object. It needs to be an RT::Record class. =cut sub LimitToObject { my $self = shift; my $obj = shift; my $obj_type = ref($obj)||$obj; my $obj_id = eval { $obj->id}; my $object_clause = 'possible_objects'; $self->_OpenParen($object_clause); $self->Limit( SUBCLAUSE => $object_clause, FIELD => 'ObjectType', OPERATOR => '=', VALUE => (ref($obj)||$obj), ENTRYAGGREGATOR => 'OR' # That "OR" applies to the separate objects we're searching on, not "Type Or ID" ); if ($obj_id) { $self->Limit( SUBCLAUSE => $object_clause, FIELD => 'ObjectId', OPERATOR => '=', VALUE => $obj_id, ENTRYAGGREGATOR => 'AND', QUOTEVALUE => 0 ); } $self->_CloseParen($object_clause); } =head2 LimitToPrincipal { Type => undef, Id => undef, IncludeGroupMembership => undef } Limit the ACL to the principal with PrincipalId Id and PrincipalType Type Id is not optional. Type is. if IncludeGroupMembership => 1 is specified, ACEs which apply to the principal due to group membership will be included in the resultset. =cut sub LimitToPrincipal { my $self = shift; my %args = ( Type => undef, Id => undef, IncludeGroupMembership => undef, @_ ); if ( $args{'IncludeGroupMembership'} ) { my $cgm = $self->NewAlias('CachedGroupMembers'); $self->Join( ALIAS1 => 'main', FIELD1 => 'PrincipalId', ALIAS2 => $cgm, FIELD2 => 'GroupId' ); $self->Limit( ALIAS => $cgm, FIELD => 'Disabled', VALUE => 0 ); $self->Limit( ALIAS => $cgm, FIELD => 'MemberId', OPERATOR => '=', VALUE => $args{'Id'}, ENTRYAGGREGATOR => 'OR' ); } else { if ( defined $args{'Type'} ) { $self->Limit( FIELD => 'PrincipalType', OPERATOR => '=', VALUE => $args{'Type'}, ENTRYAGGREGATOR => 'OR' ); } # if the principal id points to a user, we really want to point # to their ACL equivalence group. The machinations we're going through # lead me to start to suspect that we really want users and groups # to just be the same table. or _maybe_ that we want an object db. my $princ = RT::Principal->new( RT->SystemUser ); $princ->Load( $args{'Id'} ); if ( $princ->PrincipalType eq 'User' ) { my $group = RT::Group->new( RT->SystemUser ); $group->LoadACLEquivalenceGroup($princ); $args{'Id'} = $group->PrincipalId; } $self->Limit( FIELD => 'PrincipalId', OPERATOR => '=', VALUE => $args{'Id'}, ENTRYAGGREGATOR => 'OR' ); } } sub AddRecord { my $self = shift; my ($record) = @_; # Short-circuit having to load up the ->Object return $self->SUPER::AddRecord( $record ) if $record->CurrentUser->PrincipalObj->Id == RT->SystemUser->Id; my $obj = $record->Object; return unless $self->CurrentUser->HasRight( Right => 'ShowACL', Object => $obj ) or $self->CurrentUser->HasRight( Right => 'ModifyACL', Object => $obj ); return $self->SUPER::AddRecord( $record ); } # The singular of ACL is ACE. sub _SingularClass { "RT::ACE" } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Shredder/���������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 015634� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/�����������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 015311� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Dashboard.pm������������������������������������������������������������������������000644 �000765 �000024 �00000030746 14005011336 016333� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Dashboard - an API for saving and retrieving dashboards =head1 SYNOPSIS use RT::Dashboard =head1 DESCRIPTION Dashboard is an object that can belong to either an RT::User or an RT::Group. It consists of an ID, a name, and a number of saved searches and portlets. =head1 METHODS =cut package RT::Dashboard; use strict; use warnings; use base qw/RT::SharedSetting/; use RT::SavedSearch; use RT::System; 'RT::System'->AddRight( Staff => SubscribeDashboard => 'Subscribe to dashboards'); # loc 'RT::System'->AddRight( General => SeeDashboard => 'View system dashboards'); # loc 'RT::System'->AddRight( Admin => CreateDashboard => 'Create system dashboards'); # loc 'RT::System'->AddRight( Admin => ModifyDashboard => 'Modify system dashboards'); # loc 'RT::System'->AddRight( Admin => DeleteDashboard => 'Delete system dashboards'); # loc 'RT::System'->AddRight( Staff => SeeOwnDashboard => 'View personal dashboards'); # loc 'RT::System'->AddRight( Staff => CreateOwnDashboard => 'Create personal dashboards'); # loc 'RT::System'->AddRight( Staff => ModifyOwnDashboard => 'Modify personal dashboards'); # loc 'RT::System'->AddRight( Staff => DeleteOwnDashboard => 'Delete personal dashboards'); # loc =head2 ObjectName An object of this class is called "dashboard" =cut sub ObjectName { "dashboard" } # loc sub SaveAttribute { my $self = shift; my $object = shift; my $args = shift; return $object->AddAttribute( 'Name' => 'Dashboard', 'Description' => $args->{'Name'}, 'Content' => {Panes => $args->{'Panes'}}, ); } sub UpdateAttribute { my $self = shift; my $args = shift; my ($status, $msg) = (1, undef); if (defined $args->{'Panes'}) { ($status, $msg) = $self->{'Attribute'}->SetSubValues( Panes => $args->{'Panes'}, ); } if ($status && $args->{'Name'}) { ($status, $msg) = $self->{'Attribute'}->SetDescription($args->{'Name'}) unless $self->Name eq $args->{'Name'}; } if ($status && $args->{'Privacy'}) { my ($new_obj_type, $new_obj_id) = split /-/, $args->{'Privacy'}; my ($obj_type, $obj_id) = split /-/, $self->Privacy; my $attr = $self->{'Attribute'}; if ($new_obj_type ne $obj_type) { ($status, $msg) = $attr->SetObjectType($new_obj_type); } if ($status && $new_obj_id != $obj_id ) { ($status, $msg) = $attr->SetObjectId($new_obj_id); } $self->{'Privacy'} = $args->{'Privacy'} if $status; } return ($status, $msg); } =head2 PostLoadValidate Ensure that the ID corresponds to an actual dashboard object, since it's all attributes under the hood. =cut sub PostLoadValidate { my $self = shift; return (0, "Invalid object type") unless $self->{'Attribute'}->Name eq 'Dashboard'; return 1; } =head2 Panes Returns a hashref of pane name to portlets =cut sub Panes { my $self = shift; return unless ref($self->{'Attribute'}) eq 'RT::Attribute'; return $self->{'Attribute'}->SubValue('Panes') || {}; } =head2 Portlets Returns the list of this dashboard's portlets, each a hashref with key C<portlet_type> being C<search> or C<component>. =cut sub Portlets { my $self = shift; return map { @$_ } values %{ $self->Panes }; } =head2 Dashboards Returns a list of loaded sub-dashboards =cut sub Dashboards { my $self = shift; return map { my $search = RT::Dashboard->new($self->CurrentUser); $search->LoadById($_->{id}); $search } grep { $_->{portlet_type} eq 'dashboard' } $self->Portlets; } =head2 Searches Returns a list of loaded saved searches =cut sub Searches { my $self = shift; return map { my $search = RT::SavedSearch->new($self->CurrentUser); $search->Load($_->{privacy}, $_->{id}); $search } grep { $_->{portlet_type} eq 'search' } $self->Portlets; } =head2 ShowSearchName Portlet Returns an array for one saved search, suitable for passing to /Elements/ShowSearch. =cut sub ShowSearchName { my $self = shift; my $portlet = shift; if ($portlet->{privacy} eq 'RT::System') { return Name => $portlet->{description}; } return SavedSearch => join('-', $portlet->{privacy}, 'SavedSearch', $portlet->{id}); } =head2 PossibleHiddenSearches This will return a list of saved searches that are potentially not visible by all users for whom the dashboard is visible. You may pass in a privacy to use instead of the dashboard's privacy. =cut sub PossibleHiddenSearches { my $self = shift; my $privacy = shift || $self->Privacy; return grep { !$_->IsVisibleTo($privacy) } $self->Searches, $self->Dashboards; } # _PrivacyObjects: returns a list of objects that can be used to load # dashboards from. You probably want to use the wrapper methods like # ObjectsForLoading, ObjectsForCreating, etc. sub _PrivacyObjects { my $self = shift; my @objects; my $CurrentUser = $self->CurrentUser; push @objects, $CurrentUser->UserObj; my $groups = RT::Groups->new($CurrentUser); $groups->LimitToUserDefinedGroups; $groups->WithCurrentUser; push @objects, @{ $groups->ItemsArrayRef }; push @objects, RT::System->new($CurrentUser); return @objects; } # ACLs sub _CurrentUserCan { my $self = shift; my $privacy = shift || $self->Privacy; my %args = @_; if (!defined($privacy)) { $RT::Logger->debug("No privacy provided to $self->_CurrentUserCan"); return 0; } my $object = $self->_GetObject($privacy); return 0 unless $object; my $level; if ($object->isa('RT::User')) { $level = 'Own' } elsif ($object->isa('RT::Group')) { $level = 'Group' } elsif ($object->isa('RT::System')) { $level = '' } else { $RT::Logger->error("Unknown object $object from privacy $privacy"); return 0; } # users are mildly special-cased, since we actually have to check that # the user is operating on himself if ($object->isa('RT::User')) { return 0 unless $object->Id == $self->CurrentUser->Id; } my $right = $args{FullRight} || join('', $args{Right}, $level, 'Dashboard'); # all rights, except group rights, are global $object = $RT::System unless $object->isa('RT::Group'); return $self->CurrentUser->HasRight( Right => $right, Object => $object, ); } sub CurrentUserCanSee { my $self = shift; my $privacy = shift; $self->_CurrentUserCan($privacy, Right => 'See'); } sub CurrentUserCanCreate { my $self = shift; my $privacy = shift; $self->_CurrentUserCan($privacy, Right => 'Create'); } sub CurrentUserCanModify { my $self = shift; my $privacy = shift; $self->_CurrentUserCan($privacy, Right => 'Modify'); } sub CurrentUserCanDelete { my $self = shift; my $privacy = shift; $self->_CurrentUserCan($privacy, Right => 'Delete'); } sub CurrentUserCanSubscribe { my $self = shift; my $privacy = shift; $self->_CurrentUserCan($privacy, FullRight => 'SubscribeDashboard'); } =head2 Subscription Returns the L<RT::Attribute> representing the current user's subscription to this dashboard if there is one; otherwise, returns C<undef>. =cut sub Subscription { my $self = shift; # no subscription to unloaded dashboards return unless $self->id; for my $sub ($self->CurrentUser->UserObj->Attributes->Named('Subscription')) { return $sub if $sub->SubValue('DashboardId') == $self->id; } return; } sub ObjectsForLoading { my $self = shift; my %args = ( IncludeSuperuserGroups => 1, @_ ); my @objects; # If you've been granted the SeeOwnDashboard global right (which you # could have by way of global user right or global group right), you # get to see your own dashboards my $CurrentUser = $self->CurrentUser; push @objects, $CurrentUser->UserObj if $CurrentUser->HasRight(Object => $RT::System, Right => 'SeeOwnDashboard'); # Find groups for which: (a) you are a member of the group, and (b) # you have been granted SeeGroupDashboard on (by any means), and (c) # have at least one dashboard my $groups = RT::Groups->new($CurrentUser); $groups->LimitToUserDefinedGroups; $groups->ForWhichCurrentUserHasRight( Right => 'SeeGroupDashboard', IncludeSuperusers => $args{IncludeSuperuserGroups}, ); $groups->WithCurrentUser; my $attrs = $groups->Join( ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'Attributes', FIELD2 => 'ObjectId', ); $groups->Limit( ALIAS => $attrs, FIELD => 'ObjectType', VALUE => 'RT::Group', ); $groups->Limit( ALIAS => $attrs, FIELD => 'Name', VALUE => 'Dashboard', ); push @objects, @{ $groups->ItemsArrayRef }; # Finally, if you have been granted the SeeDashboard right (which # you could have by way of global user right or global group right), # you can see system dashboards. push @objects, RT::System->new($CurrentUser) if $CurrentUser->HasRight(Object => $RT::System, Right => 'SeeDashboard'); return @objects; } sub CurrentUserCanCreateAny { my $self = shift; my @objects; my $CurrentUser = $self->CurrentUser; return 1 if $CurrentUser->HasRight(Object => $RT::System, Right => 'CreateOwnDashboard'); my $groups = RT::Groups->new($CurrentUser); $groups->LimitToUserDefinedGroups; $groups->ForWhichCurrentUserHasRight( Right => 'CreateGroupDashboard', IncludeSuperusers => 1, ); return 1 if $groups->Count; return 1 if $CurrentUser->HasRight(Object => $RT::System, Right => 'CreateDashboard'); 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; ��������������������������rt-5.0.1/lib/RT/Templates.pm������������������������������������������������������������������������000644 �000765 �000024 �00000006676 14005011336 016407� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Templates - a collection of RT Template objects =head1 SYNOPSIS use RT::Templates; =head1 DESCRIPTION =head1 METHODS =cut package RT::Templates; use strict; use warnings; use base 'RT::SearchBuilder'; use RT::Template; sub Table { 'Templates'} =head2 LimitToNotInQueue Takes a queue id # and limits the returned set of templates to those which aren't that queue's templates. =cut sub LimitToNotInQueue { my $self = shift; my $queue_id = shift; $self->Limit(FIELD => 'Queue', VALUE => "$queue_id", OPERATOR => '!=' ); } =head2 LimitToGlobal Takes no arguments. Limits the returned set to "Global" templates which can be used with any queue. =cut sub LimitToGlobal { my $self = shift; $self->Limit(FIELD => 'Queue', VALUE => "0", OPERATOR => '=' ); } =head2 LimitToQueue Takes a queue id # and limits the returned set of templates to that queue's templates =cut sub LimitToQueue { my $self = shift; my $queue_id = shift; $self->Limit(FIELD => 'Queue', VALUE => "$queue_id", OPERATOR => '=' ); } =head2 AddRecord Overrides the collection to ensure that only templates the user can see are returned. =cut sub AddRecord { my $self = shift; my ($record) = @_; return unless $record->CurrentUserCanRead; return $self->SUPER::AddRecord( $record ); } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������rt-5.0.1/lib/RT/SavedSearch.pm����������������������������������������������������������������������000644 �000765 �000024 �00000014423 14005011336 016626� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::SavedSearch - an API for saving and retrieving search form values. =head1 SYNOPSIS use RT::SavedSearch =head1 DESCRIPTION SavedSearch is an object based on L<RT::SharedSetting> that can belong to either an L<RT::User> or an L<RT::Group>. It consists of an ID, a description, and a number of search parameters. =cut package RT::SavedSearch; use strict; use warnings; use base qw/RT::SharedSetting/; =head1 METHODS =head2 ObjectName An object of this class is called "search" =cut sub ObjectName { "search" } # loc sub PostLoad { my $self = shift; $self->{'Type'} = $self->{'Attribute'}->SubValue('SearchType'); } sub SaveAttribute { my $self = shift; my $object = shift; my $args = shift; my $params = $args->{'SearchParams'}; $params->{'SearchType'} = $args->{'Type'} || 'Ticket'; return $object->AddAttribute( 'Name' => 'SavedSearch', 'Description' => $args->{'Name'}, 'Content' => $params, ); } sub UpdateAttribute { my $self = shift; my $args = shift; my $params = $args->{'SearchParams'} || {}; my ($status, $msg) = $self->{'Attribute'}->SetSubValues(%$params); if ($status && $args->{'Name'}) { ($status, $msg) = $self->{'Attribute'}->SetDescription($args->{'Name'}); } return ($status, $msg); } =head2 RT::SavedSearch->EscapeDescription STRING This is a class method because system-level saved searches aren't true C<RT::SavedSearch> objects but direct C<RT::Attribute> objects. Returns C<STRING> with all square brackets except those in C<[_1]> escaped, ready for passing as the first argument to C<loc()>. =cut sub EscapeDescription { my $self = shift; my $desc = shift; if ($desc) { # We only use [_1] in saved search descriptions, so let's escape other "[" # and "]" unless they are escaped already. $desc =~ s/(?<!~)\[(?!_1\])/~[/g; $desc =~ s/(?<!~)(?<!\[_1)\]/~]/g; } return $desc; } =head2 Type Returns the type of this search, e.g. 'Ticket'. Useful for denoting the saved searches that are relevant to a particular search page. =cut sub Type { my $self = shift; return $self->{'Type'}; } ### Internal methods # _PrivacyObjects: returns a list of objects that can be used to load, create, # etc. saved searches from. You probably want to use the wrapper methods like # ObjectsForLoading, ObjectsForCreating, etc. sub _PrivacyObjects { my $self = shift; my ($has_attr) = @_; my $CurrentUser = $self->CurrentUser; my $groups = RT::Groups->new($CurrentUser); $groups->LimitToUserDefinedGroups; $groups->WithCurrentUser; if ($has_attr) { my $attrs = $groups->Join( ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'Attributes', FIELD2 => 'ObjectId', ); $groups->Limit( ALIAS => $attrs, FIELD => 'ObjectType', VALUE => 'RT::Group', ); $groups->Limit( ALIAS => $attrs, FIELD => 'Name', VALUE => $has_attr, ); } return ( $CurrentUser->UserObj, @{ $groups->ItemsArrayRef() } ); } sub ObjectsForLoading { my $self = shift; return grep { $self->CurrentUserCanSee($_) } $self->_PrivacyObjects( "SavedSearch" ); } =head2 ObjectsForCreating In the context of the current user, load a list of objects that could have searches saved under, including the current user and groups. This method considers both rights and group membership when creating the list of objects for saved searches. =cut sub ObjectsForCreating { my $self = shift; my @objects = $self->_PrivacyObjects( ); my @create_objects; foreach my $object ( @objects ) { # Users need ModifySelf to save personal searches if ( ref $object && ref $object eq 'RT::User' && $self->CurrentUser->HasRight( Right => 'ModifySelf', Object => $object ) ) { push @create_objects, $object; } # On groups, the EditSavedSearches right manages create and edit if ( ref $object && ref $object eq 'RT::Group' && $self->CurrentUser->HasRight( Right => 'EditSavedSearches', Object => $object ) ) { push @create_objects, $object; } } return @create_objects; } RT::Base->_ImportOverlays(); 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/GroupMember.pm����������������������������������������������������������������������000644 �000765 �000024 �00000037574 14005011336 016676� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::GroupMember - a member of an RT Group =head1 SYNOPSIS RT::GroupMember should never be called directly. It should ONLY only be accessed through the helper functions in RT::Group; If you're operating on an RT::GroupMember object yourself, you B<ARE> doing something wrong. =head1 DESCRIPTION =head1 METHODS =cut package RT::GroupMember; use strict; use warnings; use base 'RT::Record'; sub Table {'GroupMembers'} use RT::CachedGroupMembers; =head2 Create { Group => undef, Member => undef } Add a Principal to the group Group. if the Principal is a group, automatically inserts all members of the principal into the cached members table recursively down. Both Group and Member are expected to be RT::Principal objects =cut sub _InsertCGM { my $self = shift; my $cached_member = RT::CachedGroupMember->new( $self->CurrentUser ); my $cached_id = $cached_member->Create( Member => $self->MemberObj, Group => $self->GroupObj, ImmediateParent => $self->GroupObj, Via => '0' ); #When adding a member to a group, we need to go back #and popuplate the CachedGroupMembers of all the groups that group is part of . my $cgm = RT::CachedGroupMembers->new( $self->CurrentUser ); # find things which have the current group as a member. # $group is an RT::Principal for the group. $cgm->LimitToGroupsWithMember( $self->GroupId ); $cgm->Limit( SUBCLAUSE => 'filter', # dont't mess up with prev condition FIELD => 'MemberId', OPERATOR => '!=', VALUE => 'main.GroupId', QUOTEVALUE => 0, ENTRYAGGREGATOR => 'AND', ); while ( my $parent_member = $cgm->Next ) { my $parent_id = $parent_member->MemberId; my $via = $parent_member->Id; my $group_id = $parent_member->GroupId; my $other_cached_member = RT::CachedGroupMember->new( $self->CurrentUser ); my $other_cached_id = $other_cached_member->Create( Member => $self->MemberObj, Group => $parent_member->GroupObj, ImmediateParent => $parent_member->MemberObj, Via => $parent_member->Id ); unless ($other_cached_id) { $RT::Logger->err( "Couldn't add " . $self->MemberId . " as a submember of a supergroup" ); return; } } return $cached_id; } sub Create { my $self = shift; my %args = ( Group => undef, Member => undef, InsideTransaction => undef, @_ ); unless ($args{'Group'} && UNIVERSAL::isa($args{'Group'}, 'RT::Principal') && $args{'Group'}->Id ) { $RT::Logger->warning("GroupMember::Create called with a bogus Group arg"); return (undef); } unless($args{'Group'}->IsGroup) { $RT::Logger->warning("Someone tried to add a member to a user instead of a group"); return (undef); } unless ($args{'Member'} && UNIVERSAL::isa($args{'Member'}, 'RT::Principal') && $args{'Member'}->Id) { $RT::Logger->warning("GroupMember::Create called with a bogus Principal arg"); return (undef); } #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. # TODO what about the groups key cache? RT::Principal->InvalidateACLCache(); $RT::Handle->BeginTransaction() unless ($args{'InsideTransaction'}); # We really need to make sure we don't add any members to this group # that contain the group itself. that would, um, suck. # (and recurse infinitely) Later, we can add code to check this in the # cache and bail so we can support cycling directed graphs if ($args{'Member'}->IsGroup) { my $member_object = $args{'Member'}->Object; if ($member_object->HasMemberRecursively($args{'Group'})) { $RT::Logger->debug("Adding that group would create a loop"); $RT::Handle->Rollback() unless ($args{'InsideTransaction'}); return(undef); } elsif ( $args{'Member'}->Id == $args{'Group'}->Id) { $RT::Logger->debug("Can't add a group to itself"); $RT::Handle->Rollback() unless ($args{'InsideTransaction'}); return(undef); } } my $id = $self->SUPER::Create( GroupId => $args{'Group'}->Id, MemberId => $args{'Member'}->Id ); unless ($id) { $RT::Handle->Rollback() unless ($args{'InsideTransaction'}); return (undef); } my $clone = RT::GroupMember->new( $self->CurrentUser ); $clone->Load( $id ); my $cached_id = $clone->_InsertCGM; unless ($cached_id) { $RT::Handle->Rollback() unless ($args{'InsideTransaction'}); return (undef); } $RT::Handle->Commit() unless ($args{'InsideTransaction'}); return ($id); } =head2 _StashUser PRINCIPAL Create { Group => undef, Member => undef } Creates an entry in the groupmembers table, which lists a user as a member of himself. This makes ACL checks a whole bunch easier. This happens once on user create and never ever gets yanked out. PRINCIPAL is expected to be an RT::Principal object for a user This routine expects to be called inside a transaction by RT::User->Create =cut sub _StashUser { my $self = shift; my %args = ( Group => undef, Member => undef, @_ ); #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. # TODO what about the groups key cache? RT::Principal->InvalidateACLCache(); # We really need to make sure we don't add any members to this group # that contain the group itself. that would, um, suck. # (and recurse infinitely) Later, we can add code to check this in the # cache and bail so we can support cycling directed graphs my $id = $self->SUPER::Create( GroupId => $args{'Group'}->Id, MemberId => $args{'Member'}->Id, ); unless ($id) { return (undef); } my $cached_member = RT::CachedGroupMember->new( $self->CurrentUser ); my $cached_id = $cached_member->Create( Member => $args{'Member'}, Group => $args{'Group'}, ImmediateParent => $args{'Group'}, Via => '0' ); unless ($cached_id) { return (undef); } return ($id); } =head2 Delete Takes no arguments. deletes the currently loaded member from the group in question. =cut sub Delete { my $self = shift; $RT::Handle->BeginTransaction(); # Find all occurrences of this member as a member of this group # in the cache and nuke them, recursively. # The following code will delete all Cached Group members # where this member's group is _not_ the primary group # (Ie if we're deleting C as a member of B, and B happens to be # a member of A, will delete C as a member of A without touching # C as a member of B my $cached_submembers = RT::CachedGroupMembers->new( $self->CurrentUser ); $cached_submembers->Limit( FIELD => 'MemberId', OPERATOR => '=', VALUE => $self->MemberObj->Id ); $cached_submembers->Limit( FIELD => 'ImmediateParentId', OPERATOR => '=', VALUE => $self->GroupObj->Id ); while ( my $item_to_del = $cached_submembers->Next() ) { my ($ok, $msg) = $item_to_del->Delete(); unless ($ok) { $RT::Handle->Rollback(); return ($ok, $msg); } } my ($ok, $msg) = $self->SUPER::Delete(); unless ($ok) { $RT::Logger->error("Couldn't delete GroupMember ".$self->Id); $RT::Handle->Rollback(); return ($ok, $msg); } #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. # TODO what about the groups key cache? RT::Principal->InvalidateACLCache(); $RT::Handle->Commit(); return ($ok, $msg); } =head2 MemberObj Returns an RT::Principal object for the Principal specified by $self->MemberId =cut sub MemberObj { my $self = shift; unless ( defined( $self->{'Member_obj'} ) ) { $self->{'Member_obj'} = RT::Principal->new( $self->CurrentUser ); $self->{'Member_obj'}->Load( $self->MemberId ) if ($self->MemberId); } return ( $self->{'Member_obj'} ); } =head2 GroupObj Returns an RT::Principal object for the Group specified in $self->GroupId =cut sub GroupObj { my $self = shift; unless ( defined( $self->{'Group_obj'} ) ) { $self->{'Group_obj'} = RT::Principal->new( $self->CurrentUser ); $self->{'Group_obj'}->Load( $self->GroupId ); } return ( $self->{'Group_obj'} ); } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 GroupId Returns the current value of GroupId. (In the database, GroupId is stored as int(11).) =head2 SetGroupId VALUE Set GroupId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, GroupId will be stored as a int(11).) =cut =head2 MemberId Returns the current value of MemberId. (In the database, MemberId is stored as int(11).) =head2 SetMemberId VALUE Set MemberId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, MemberId will be stored as a int(11).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, GroupId => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, MemberId => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); $deps->Add( out => $self->GroupObj->Object ); $deps->Add( out => $self->MemberObj->Object ); } sub __DependsOn { my $self = shift; my %args = ( Shredder => undef, Dependencies => undef, @_, ); my $deps = $args{'Dependencies'}; my $list = []; my $objs = RT::CachedGroupMembers->new( $self->CurrentUser ); $objs->Limit( FIELD => 'MemberId', VALUE => $self->MemberId ); $objs->Limit( FIELD => 'ImmediateParentId', VALUE => $self->GroupId ); push( @$list, $objs ); $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON, TargetObjects => $list, Shredder => $args{'Shredder'} ); my $group = $self->GroupObj->Object; # XXX: If we delete member of the ticket owner role group then we should also # fix ticket object, but only if we don't plan to delete group itself! unless( ($group->Name || '') eq 'Owner' && ($group->Domain || '') eq 'RT::Ticket-Role' ) { return $self->SUPER::__DependsOn( %args ); } # we don't delete group, so we have to fix Ticket and Group $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON | RT::Shredder::Constants::VARIABLE, TargetObjects => $group, Shredder => $args{'Shredder'} ); $args{'Shredder'}->PutResolver( BaseClass => ref $self, TargetClass => ref $group, Code => sub { my %args = (@_); my $group = $args{'TargetObject'}; return if $args{'Shredder'}->GetState( Object => $group ) & (RT::Shredder::Constants::WIPED|RT::Shredder::Constants::IN_WIPING); return unless ($group->Name || '') eq 'Owner'; return unless ($group->Domain || '') eq 'RT::Ticket-Role'; return if $group->MembersObj->Count > 1; my $group_member = $args{'BaseObject'}; if( $group_member->MemberObj->id == RT->Nobody->id ) { RT::Shredder::Exception->throw( "Couldn't delete Nobody from owners role group" ); } my( $status, $msg ) = $group->AddMember( RT->Nobody->id ); RT::Shredder::Exception->throw( $msg ) unless $status; return; }, ); return $self->SUPER::__DependsOn( %args ); } sub PreInflate { my $class = shift; my ($importer, $uid, $data) = @_; $class->SUPER::PreInflate( $importer, $uid, $data ); my $obj = RT::GroupMember->new( RT->SystemUser ); $obj->LoadByCols( GroupId => $data->{GroupId}, MemberId => $data->{MemberId}, ); if ($obj->id) { $importer->Resolve( $uid => ref($obj) => $obj->Id ); return; } return 1; } sub PostInflate { my $self = shift; $self->_InsertCGM; } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Migrate/����������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 015464� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Condition/��������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 016022� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Attachments.pm����������������������������������������������������������������������000644 �000765 �000024 �00000024136 14005011336 016713� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Attachments - a collection of RT::Attachment objects =head1 SYNOPSIS use RT::Attachments; =head1 DESCRIPTION This module should never be called directly by client code. it's an internal module which should only be accessed through exported APIs in Ticket, Queue and other similar objects. =head1 METHODS =cut package RT::Attachments; use strict; use warnings; use base 'RT::SearchBuilder'; use RT::Attachment; sub Table { 'Attachments'} use RT::Attachment; sub _Init { my $self = shift; $self->{'table'} = "Attachments"; $self->{'primary_key'} = "id"; $self->OrderBy( FIELD => 'id', ORDER => 'ASC', ); return $self->SUPER::_Init( @_ ); } sub CleanSlate { my $self = shift; delete $self->{_sql_transaction_alias}; return $self->SUPER::CleanSlate( @_ ); } =head2 TransactionAlias Returns alias for transactions table with applied join condition. Always return the same alias, so if you want to build some complex or recursive joining then you have to create new alias youself. =cut sub TransactionAlias { my $self = shift; return $self->{'_sql_transaction_alias'} if $self->{'_sql_transaction_alias'}; return $self->{'_sql_transaction_alias'} = $self->Join( ALIAS1 => 'main', FIELD1 => 'TransactionId', TABLE2 => 'Transactions', FIELD2 => 'id', ); } =head2 ContentType (VALUE => 'text/plain', ENTRYAGGREGATOR => 'OR', OPERATOR => '=' ) Limit result set to attachments of ContentType 'TYPE'... =cut sub ContentType { my $self = shift; my %args = ( VALUE => 'text/plain', OPERATOR => '=', ENTRYAGGREGATOR => 'OR', @_ ); return $self->Limit ( %args, FIELD => 'ContentType' ); } =head2 ChildrenOf ID Limit result set to children of Attachment ID =cut sub ChildrenOf { my $self = shift; my $attachment = shift; return $self->Limit( FIELD => 'Parent', VALUE => $attachment ); } =head2 LimitNotEmpty Limit result set to attachments with not empty content. =cut sub LimitNotEmpty { my $self = shift; $self->Limit( ENTRYAGGREGATOR => 'AND', FIELD => 'Content', OPERATOR => 'IS NOT', VALUE => 'NULL', QUOTEVALUE => 0, ); # http://rt3.fsck.com/Ticket/Display.html?id=12483 if ( RT->Config->Get('DatabaseType') ne 'Oracle' ) { $self->Limit( ENTRYAGGREGATOR => 'AND', FIELD => 'Content', OPERATOR => '!=', VALUE => '', ); } return; } =head2 LimitHasFilename Limit result set to attachments with not empty filename. =cut sub LimitHasFilename { my $self = shift; $self->Limit( ENTRYAGGREGATOR => 'AND', FIELD => 'Filename', OPERATOR => 'IS NOT', VALUE => 'NULL', QUOTEVALUE => 0, ); if ( RT->Config->Get('DatabaseType') ne 'Oracle' ) { $self->Limit( ENTRYAGGREGATOR => 'AND', FIELD => 'Filename', OPERATOR => '!=', VALUE => '', ); } return; } =head2 LimitByTicket $ticket_id Limit result set to attachments of a ticket. =cut sub LimitByTicket { my $self = shift; my $tid = shift; my $transactions = $self->TransactionAlias; $self->Limit( ENTRYAGGREGATOR => 'AND', ALIAS => $transactions, FIELD => 'ObjectType', VALUE => 'RT::Ticket', ); my $tickets = $self->Join( ALIAS1 => $transactions, FIELD1 => 'ObjectId', TABLE2 => 'Tickets', FIELD2 => 'id', ); $self->Limit( ENTRYAGGREGATOR => 'AND', ALIAS => $tickets, FIELD => 'EffectiveId', VALUE => $tid, ); return; } sub AddRecord { my $self = shift; my ($record) = @_; return unless $record->TransactionObj->CurrentUserCanSee; return $self->SUPER::AddRecord( $record ); } =head2 ReplaceAttachments ( Search => 'SEARCH', Replacement => 'Replacement', Header => 1, Content => 1 ) Provide a search string to search the attachments table for, by default the Headers and Content columns will both be searched for matches. =cut sub ReplaceAttachments { my $self = shift; my %args = ( Search => undef, Replacement => '', Headers => 1, Content => 1, FilterBySearchString => 1, @_, ); return ( 0, $self->loc('Provide a search string to search on') ) unless $args{Search}; my %munged; my $create_munge_txn = sub { my $ticket = shift; if ( !$munged{ $ticket->id } ) { my ( $ret, $msg ) = $ticket->_NewTransaction( Type => "Munge" ); if ($ret) { $munged{ $ticket->id } = 1; } else { RT::Logger->error($msg); } } }; my $attachments = $self->Clone; if ( $args{FilterBySearchString} ) { $attachments->Limit( FIELD => 'ContentEncoding', VALUE => 'none', SUBCLAUSE => 'Encoding', ); $attachments->Limit( FIELD => 'ContentEncoding', OPERATOR => 'IS', VALUE => 'NULL', SUBCLAUSE => 'Encoding', ); # For QP encoding, if encoded string is equal to the decoded # version, then SQL search will also work. # # Adding "\n" is to avoid trailing "=" in QP encoding if ( MIME::QuotedPrint::encode( Encode::encode( 'UTF-8', "$args{Search}\n" ) ) eq Encode::encode( 'UTF-8', "$args{Search}\n" ) ) { $attachments->Limit( FIELD => 'ContentEncoding', VALUE => 'quoted-printable', SUBCLAUSE => 'Encoding', ); } } if ( $args{Headers} ) { my $atts = $attachments->Clone; if ( $args{FilterBySearchString} ) { $atts->Limit( FIELD => 'Headers', OPERATOR => 'LIKE', VALUE => $args{Search}, ); } $atts->Limit( FIELD => 'ContentType', OPERATOR => 'IN', VALUE => [ RT::Util::EmailContentTypes(), 'text/plain', 'text/html' ], SUBCLAUSE => 'Types', ); $atts->Limit( FIELD => 'ContentType', OPERATOR => 'STARTSWITH', VALUE => 'multipart/', SUBCLAUSE => 'Types', ENTRYAGGREGATOR => 'OR', ); while ( my $att = $atts->Next ) { my ( $ret, $msg ) = $att->ReplaceHeaders( Search => $args{Search}, Replacement => $args{Replacement}, ); if ( $ret ) { $create_munge_txn->( $att->TransactionObj->TicketObj ); } else { RT::Logger->debug($msg); } } } if ( $args{Content} ) { my $atts = $attachments->Clone; if ( $args{FilterBySearchString} ) { $atts->Limit( FIELD => 'Content', OPERATOR => 'LIKE', VALUE => $args{Search}, SUBCLAUSE => 'Content', ); } $atts->Limit( FIELD => 'ContentType', OPERATOR => 'IN', VALUE => [ 'text/plain', 'text/html' ], ); while ( my $att = $atts->Next ) { my ( $ret, $msg ) = $att->ReplaceContent( Search => $args{Search}, Replacement => $args{Replacement}, ); if ( $ret ) { $create_munge_txn->( $att->TransactionObj->TicketObj ); } else { RT::Logger->debug($msg); } } } my $count = scalar keys %munged; return wantarray ? ( 1, $self->loc( "Updated [quant,_1,ticket's,tickets'] attachment content", $count ) ) : $count; } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Installer.pm������������������������������������������������������������������������000644 �000765 �000024 �00000024670 14005011336 016400� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Installer; use strict; use warnings; use DateTime; require UNIVERSAL::require; my %Meta = ( DatabaseType => { Widget => '/Widgets/Form/Select', WidgetArguments => { Description => 'Database type', # loc Values => [ grep { my $m = 'DBD::' . $_; $m->require ? 1 : 0 } qw/mysql Pg SQLite Oracle/ ], ValuesLabel => { mysql => 'MySQL', #loc Pg => 'PostgreSQL', #loc SQLite => 'SQLite', #loc Oracle => 'Oracle', #loc }, }, }, DatabaseHost => { Widget => '/Widgets/Form/String', WidgetArguments => { Description => 'Database host', #loc Default => 1, DefaultLabel => "Keep 'localhost' if you're not sure. Leave blank to connect locally over a socket", #loc Hints => "The domain name of your database server (like 'db.example.com').", #loc }, }, DatabasePort => { Widget => '/Widgets/Form/Integer', WidgetArguments => { Description => 'Database port', #loc Default => 1, DefaultLabel => 'Leave empty to use the default value for your database', #loc }, }, DatabaseName => { Widget => '/Widgets/Form/String', WidgetArguments => { Description => 'Database name', #loc }, }, DatabaseAdmin => { SkipWrite => 1, Widget => '/Widgets/Form/String', WidgetArguments => { Default => 1, Hints => "Leave this alone to use the default dba username for your database type", #loc Description => 'DBA username', # loc DefaultLabel => '', }, }, DatabaseAdminPassword => { SkipWrite => 1, Widget => '/Widgets/Form/String', WidgetArguments => { Description => 'DBA password', #loc DefaultLabel => "The DBA's database password",#loc Type => 'password', Hints => "You must provide the dba's password so we can create the RT database and user.", }, }, DatabaseUser => { Widget => '/Widgets/Form/String', WidgetArguments => { Description => 'Database username for RT', #loc Hints => 'RT will connect to the database using this user. It will be created for you.', #loc }, }, DatabasePassword => { Widget => '/Widgets/Form/String', WidgetArguments => { Description => 'Database password for RT', #loc Type => 'password', Hints => 'The password RT should use to connect to the database.', }, }, rtname => { Widget => '/Widgets/Form/String', WidgetArguments => { Description => 'Site name', #loc Hints => 'RT will use this string to uniquely identify your installation and looks for it in the subject of emails to decide what ticket a message applies to. We recommend that you set this to your internet domain. (ex: example.com)' #loc }, }, MinimumPasswordLength => { Widget => '/Widgets/Form/Integer', WidgetArguments => { Description => 'Minimum password length', #loc }, }, Password => { SkipWrite => 1, Widget => '/Widgets/Form/String', WidgetArguments => { Description => 'Administrative password', #loc Hints => 'RT will create a user called "root" and set this as their password', #loc Type => 'password', }, }, OwnerEmail => { Widget => '/Widgets/Form/String', WidgetArguments => { Description => 'RT Administrator Email', #loc Hints => "When RT can't handle an email message, where should it be forwarded?", #loc }, }, CommentAddress => { Widget => '/Widgets/Form/String', WidgetArguments => { Description => 'Comment address', #loc Hints => 'the default addresses that will be listed in From: and Reply-To: headers of comment mail.' #loc }, }, CorrespondAddress => { Widget => '/Widgets/Form/String', WidgetArguments => { Description => 'Correspond address', #loc Hints => 'the default addresses that will be listed in From: and Reply-To: headers of correspondence mail.' #loc }, }, SendmailPath => { Widget => '/Widgets/Form/String', WidgetArguments => { Hints => 'Where to find your sendmail binary.', #loc Description => 'Path to sendmail', #loc }, }, Timezone => { Widget => '/Widgets/Form/Select', WidgetArguments => { Description => 'Timezone', #loc Callback => sub { my $ret; $ret->{Values} = ['', DateTime::TimeZone->all_names]; my $dt = DateTime->now; for my $tz ( DateTime::TimeZone->all_names ) { $dt->set_time_zone( $tz ); $ret->{ValuesLabel}{$tz} = $tz . ' ' . $dt->strftime('%z'); } $ret->{ValuesLabel}{''} = 'System Default'; #loc return $ret; }, }, }, WebDomain => { Widget => '/Widgets/Form/String', WidgetArguments => { Description => 'Domain name', #loc Hints => "Don't include http://, just something like 'localhost', 'rt.example.com'", #loc }, }, WebPort => { Widget => '/Widgets/Form/Integer', WidgetArguments => { Description => 'Web port', #loc Hints => 'which port your web server will listen to, e.g. 8080', #loc }, }, ); sub Meta { my $class = shift; my $type = shift; return $Meta{$type} if $type; return \%Meta; } sub CurrentValue { my $class = shift; my $type = shift; $type = $class if !ref $class && $class && $class ne 'RT::Installer'; return undef unless $type; return $RT::Installer && exists $RT::Installer->{InstallConfig}{$type} ? $RT::Installer->{InstallConfig}{$type} : scalar RT->Config->Get($type); } sub CurrentValues { my $class = shift; my @types = @_; push @types, $class if !ref $class && $class && $class ne 'RT::Installer'; return { map { $_ => CurrentValue($_) } @types }; } sub ConfigFile { require File::Spec; return $ENV{RT_SITE_CONFIG} || File::Spec->catfile( $RT::EtcPath, 'RT_SiteConfig.pm' ); } sub SaveConfig { my $class = shift; my $file = $class->ConfigFile; my $content; { local $/; open( my $fh, '<', $file ) or die $!; $content = <$fh>; $content =~ s/^\s*1;\s*$//m; } # make organization the same as rtname $RT::Installer->{InstallConfig}{Organization} = $RT::Installer->{InstallConfig}{rtname}; if ( open my $fh, '>', $file ) { for ( sort keys %{ $RT::Installer->{InstallConfig} } ) { # we don't want to store root's password in config. next if $class->Meta($_) and $class->Meta($_)->{SkipWrite}; $RT::Installer->{InstallConfig}{$_} = '' unless defined $RT::Installer->{InstallConfig}{$_}; # remove obsolete settings we'll add later $content =~ s/^\s* Set \s* \( \s* \$$_ .*$//xm; my $value = $RT::Installer->{InstallConfig}{$_}; $value =~ s/(['\\])/\\$1/g; $content .= "Set( \$$_, '$value' );\n"; } $content .= "1;\n"; print $fh $content; close $fh; return ( 1, "Successfully saved configuration to $file." ); } return ( 0, "Cannot save configuration to $file: $!" ); } =head1 NAME RT::Installer - RT's Installer =head1 SYNOPSYS use RT::Installer; my $meta = RT::Installer->Meta; =head1 DESCRIPTION C<RT::Installer> class provides access to RT Installer Meta =cut RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������rt-5.0.1/lib/RT/Search.pm���������������������������������������������������������������������������000644 �000765 �000024 �00000006430 14005011336 015642� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Search - generic baseclass for searches; =head1 SYNOPSIS use RT::Search; my $tickets = RT::Tickets->new($CurrentUser); my $foo = RT::Search->new(Argument => $arg, TicketsObj => $tickets); $foo->Prepare(); while ( my $ticket = $foo->Next ) { # Do something with each ticket we've found } =head1 DESCRIPTION =head1 METHODS =cut package RT::Search; use strict; use warnings; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; bless( $self, $class ); $self->_Init(@_); return $self; } sub _Init { my $self = shift; my %args = ( TicketsObj => undef, Argument => undef, @_ ); $self->{'TicketsObj'} = $args{'TicketsObj'}; $self->{'Argument'} = $args{'Argument'}; } =head2 Argument Return the optional argument associated with this Search =cut sub Argument { my $self = shift; return ( $self->{'Argument'} ); } =head2 TicketsObj Return the Tickets object passed into this search =cut sub TicketsObj { my $self = shift; return ( $self->{'TicketsObj'} ); } sub Describe { my $self = shift; return ( $self->loc( "No description for [_1]", ref $self ) ); } sub Prepare { my $self = shift; return (1); } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Squish/�����������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 015350� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ObjectScrips.pm���������������������������������������������������������������������000644 �000765 �000024 �00000005163 14005011336 017031� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::ObjectScrips; use base 'RT::SearchBuilder::AddAndSort'; use RT::Scrips; use RT::ObjectScrip; =head1 NAME RT::ObjectScrips - collection of RT::ObjectScrip records =head1 DESCRIPTION Collection of L<RT::ObjectScrip> records. Inherits methods from L<RT::SearchBuilder::AddAndSort>. =head1 METHODS =cut =head2 Table Returns name of the table where records are stored. =cut sub Table { 'ObjectScrips'} =head2 LimitToScrip Takes id of a L<RT::Scrip> object and limits this collection. =cut sub LimitToScrip { my $self = shift; my $id = shift; $self->Limit( FIELD => 'Scrip', VALUE => $id ); } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Articles.pm�������������������������������������������������������������������������000644 �000765 �000024 �00000057274 14005011336 016217� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Articles; use base 'RT::SearchBuilder'; sub Table {'Articles'} sub _Init { my $self = shift; $self->{'with_disabled_column'} = 1; $self->OrderByCols( { FIELD => 'SortOrder', ORDER => 'ASC' }, { FIELD => 'Name', ORDER => 'ASC' }, ); return $self->SUPER::_Init( @_ ); } =head2 AddRecord Overrides the collection to ensure that only Articles the user can see are returned. =cut sub AddRecord { my $self = shift; my ($record) = @_; return unless $record->CurrentUserHasRight('ShowArticle'); return $self->SUPER::AddRecord( $record ); } =head2 Limit { FIELD => undef, OPERATOR => '=', VALUE => 'undef'} Limit the result set. See DBIx::SearchBuilder docs In addition to the "normal" stuff, value can be an array. =cut sub Limit { my $self = shift; my %ARGS = ( OPERATOR => '=', @_ ); if ( ref( $ARGS{'VALUE'} ) ) { my @values = $ARGS{'VALUE'}; delete $ARGS{'VALUE'}; foreach my $v (@values) { $self->SUPER::Limit( %ARGS, VALUE => $v ); } } else { $self->SUPER::Limit(%ARGS); } } =head2 LimitName { OPERATOR => 'LIKE', VALUE => undef } Find all articles with Name fields which satisfy OPERATOR for VALUE =cut sub LimitName { my $self = shift; my %args = ( FIELD => 'Name', OPERATOR => 'LIKE', CASESENSITIVE => 0, VALUE => undef, @_ ); $self->Limit(%args); } =head2 LimitSummary { OPERATOR => 'LIKE', VALUE => undef } Find all articles with summary fields which satisfy OPERATOR for VALUE =cut sub LimitSummary { my $self = shift; my %args = ( FIELD => 'Summary', OPERATOR => 'LIKE', CASESENSITIVE => 0, VALUE => undef, @_ ); $self->Limit(%args); } sub LimitCreated { my $self = shift; my %args = ( FIELD => 'Created', OPERATOR => undef, VALUE => undef, @_ ); $self->Limit(%args); } sub LimitCreatedBy { my $self = shift; my %args = ( FIELD => 'CreatedBy', OPERATOR => '=', VALUE => undef, @_ ); $self->Limit(%args); } sub LimitUpdated { my $self = shift; my %args = ( FIELD => 'Updated', OPERATOR => undef, VALUE => undef, @_ ); $self->Limit(%args); } sub LimitUpdatedBy { my $self = shift; my %args = ( FIELD => 'UpdatedBy', OPERATOR => '=', VALUE => undef, @_ ); $self->Limit(%args); } # {{{ LimitToParent ID =head2 LimitToParent ID Limit the returned set of articles to articles which are children of article ID. This does not recurse. =cut sub LimitToParent { my $self = shift; my $parent = shift; $self->Limit( FIELD => 'Parent', OPERATOR => '=', VALUE => $parent ); } # }}} # {{{ LimitCustomField =head2 LimitCustomField HASH Limit the result set to articles which have or do not have the custom field value listed, using a left join to catch things where no rows match. HASH needs the following fields: FIELD (A custom field id) or undef for any custom field ENTRYAGGREGATOR => (AND, OR) OPERATOR ('=', 'LIKE', '!=', 'NOT LIKE') VALUE ( a single scalar value or a list of possible values to be concatenated with ENTRYAGGREGATOR) The subclause that the LIMIT statement(s) should be done in can also be passed in with a SUBCLAUSE parameter. =cut sub LimitCustomField { my $self = shift; my %args = ( FIELD => undef, ENTRYAGGREGATOR => 'OR', OPERATOR => '=', QUOTEVALUE => 1, VALUE => undef, SUBCLAUSE => undef, @_ ); my $value = $args{'VALUE'}; # XXX: this work in a different way than RT return unless $value; #strip out total blank wildcards my $ObjectValuesAlias = $self->Join( TYPE => 'left', ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'ObjectCustomFieldValues', FIELD2 => 'ObjectId', EXPRESSION => 'main.id' ); $self->Limit( LEFTJOIN => $ObjectValuesAlias, FIELD => 'Disabled', VALUE => '0' ); if ( $args{'FIELD'} ) { my $field_id; if (UNIVERSAL::isa($args{'FIELD'} ,'RT::CustomField')) { $field_id = $args{'FIELD'}->id; } elsif($args{'FIELD'} =~ /^\d+$/) { $field_id = $args{'FIELD'}; } if ($field_id) { $self->Limit( LEFTJOIN => $ObjectValuesAlias, FIELD => 'CustomField', VALUE => $field_id, ENTRYAGGREGATOR => 'AND'); # Could convert the above to a non-left join and also enable the thing below # $self->SUPER::Limit( ALIAS => $ObjectValuesAlias, # FIELD => 'CustomField', # OPERATOR => 'IS', # VALUE => 'NULL', # QUOTEVALUE => 0, # ENTRYAGGREGATOR => 'OR',); } else { # Search for things by name if the cf was specced by name. my $fields = $self->NewAlias('CustomFields'); $self->Join( TYPE => 'left', ALIAS1 => $ObjectValuesAlias , FIELD1 => 'CustomField', ALIAS2 => $fields, FIELD2=> 'id'); $self->Limit( ALIAS => $fields, FIELD => 'Name', VALUE => $args{'FIELD'}, ENTRYAGGREGATOR => 'OR', CASESENSITIVE => 0); $self->Limit( ALIAS => $fields, FIELD => 'LookupType', VALUE => RT::Article->new($RT::SystemUser)->CustomFieldLookupType() ); } } # If we're trying to find articles where a custom field value # doesn't match something, be sure to find things where it's null # basically, we do a left join on the value being applicable to # the article and then we turn around and make sure that it's # actually null in practise # TODO this should deal with starts with and ends with my $fix_op = sub { my $op = shift; return $op unless RT->Config->Get('DatabaseType') eq 'Oracle'; return 'MATCHES' if $op eq '='; return 'NOT MATCHES' if $op eq '!='; return $op; }; my $clause = $args{'SUBCLAUSE'} || $ObjectValuesAlias; if ( $args{'OPERATOR'} eq '!=' || $args{'OPERATOR'} =~ /^not like$/i ) { my $op; if ( $args{'OPERATOR'} eq '!=' ) { $op = "="; } elsif ( $args{'OPERATOR'} =~ /^not like$/i ) { $op = 'LIKE'; } $self->SUPER::Limit( LEFTJOIN => $ObjectValuesAlias, FIELD => 'Content', OPERATOR => $op, VALUE => $value, QUOTEVALUE => $args{'QUOTEVALUE'}, ENTRYAGGREGATOR => 'AND', #$args{'ENTRYAGGREGATOR'}, SUBCLAUSE => $clause, CASESENSITIVE => 0, ); $self->SUPER::Limit( ALIAS => $ObjectValuesAlias, FIELD => 'Content', OPERATOR => 'IS', VALUE => 'NULL', QUOTEVALUE => 0, ENTRYAGGREGATOR => 'AND', SUBCLAUSE => $clause, ); } else { $self->SUPER::Limit( ALIAS => $ObjectValuesAlias, FIELD => 'LargeContent', OPERATOR => $fix_op->($args{'OPERATOR'}), VALUE => $value, QUOTEVALUE => $args{'QUOTEVALUE'}, ENTRYAGGREGATOR => $args{'ENTRYAGGREGATOR'}, SUBCLAUSE => $clause, CASESENSITIVE => 0, ); $self->SUPER::Limit( ALIAS => $ObjectValuesAlias, FIELD => 'Content', OPERATOR => $args{'OPERATOR'}, VALUE => $value, QUOTEVALUE => $args{'QUOTEVALUE'}, ENTRYAGGREGATOR => $args{'ENTRYAGGREGATOR'}, SUBCLAUSE => $clause, CASESENSITIVE => 0, ); } } # }}} # {{{ LimitTopics sub LimitTopics { my $self = shift; my @topics = @_; return unless @topics; my $topics = $self->NewAlias('ObjectTopics'); $self->Limit( ALIAS => $topics, FIELD => 'Topic', OPERATOR => 'IN', VALUE => [ @topics ], ); $self->Limit( ALIAS => $topics, FIELD => 'ObjectType', VALUE => 'RT::Article', ); $self->Join( ALIAS1 => 'main', FIELD1 => 'id', ALIAS2 => $topics, FIELD2 => 'ObjectId', ); } # }}} # {{{ LimitRefersTo URI =head2 LimitRefersTo URI Limit the result set to only articles which are referred to by the URI passed in. =cut sub LimitRefersTo { my $self = shift; my $uri = shift; my $uri_obj = RT::URI->new($self->CurrentUser); $uri_obj->FromURI($uri); my $links = $self->NewAlias('Links'); $self->Limit( ALIAS => $links, FIELD => 'Target', VALUE => $uri_obj->URI ); $self->Join( ALIAS1 => 'main', FIELD1 => 'URI', ALIAS2 => $links, FIELD2 => 'Base' ); } # }}} # {{{ LimitReferredToBy URI =head2 LimitReferredToBy URI Limit the result set to only articles which are referred to by the URI passed in. =cut sub LimitReferredToBy { my $self = shift; my $uri = shift; my $uri_obj = RT::URI->new($self->CurrentUser); $uri_obj->FromURI($uri); my $links = $self->NewAlias('Links'); $self->Limit( ALIAS => $links, FIELD => 'Base', VALUE => $uri_obj->URI ); $self->Join( ALIAS1 => 'main', FIELD1 => 'URI', ALIAS2 => $links, FIELD2 => 'Target' ); } # }}} =head2 LimitAppliedClasses Queue => QueueObj Takes a Queue and limits articles returned to classes which are applied to that Queue Accepts either a Queue obj or a Queue id =cut sub LimitAppliedClasses { my $self = shift; my %args = @_; unless (ref $args{Queue} || $args{Queue} =~/^[0-9]+$/) { $RT::Logger->error("Not a valid Queue: $args{Queue}"); return; } my $queue = ( ref $args{Queue} ? $args{Queue}->Id : $args{Queue} ); my $oc_alias = $self->Join( ALIAS1 => 'main', FIELD1 => 'Class', TABLE2 => 'ObjectClasses', FIELD2 => 'Class' ); my $subclause = "possibleobjectclasses"; $self->_OpenParen($subclause); $self->Limit( ALIAS => $oc_alias, FIELD => 'ObjectId', VALUE => $queue, SUBCLAUSE => $subclause, ENTRYAGGREGATOR => 'OR' ); $self->Limit( ALIAS => $oc_alias, FIELD => 'ObjectType', VALUE => 'RT::Queue', SUBCLAUSE => $subclause, ENTRYAGGREGATOR => 'AND' ); $self->_CloseParen($subclause); $self->_OpenParen($subclause); $self->Limit( ALIAS => $oc_alias, FIELD => 'ObjectId', VALUE => 0, SUBCLAUSE => $subclause, ENTRYAGGREGATOR => 'OR' ); $self->Limit( ALIAS => $oc_alias, FIELD => 'ObjectType', VALUE => 'RT::System', SUBCLAUSE => $subclause, ENTRYAGGREGATOR => 'AND' ); $self->_CloseParen($subclause); return $self; } sub Search { my $self = shift; my %args = @_; my $customfields = $args{CustomFields} || RT::CustomFields->new( $self->CurrentUser ); my $dates = $args{Dates} || {}; my $order_by = $args{OrderBy}; my $order = $args{Order}; if ( $args{'q'} ) { $self->Limit( FIELD => 'Name', SUBCLAUSE => 'NameOrSummary', OPERATOR => 'LIKE', ENTRYAGGREGATOR => 'OR', CASESENSITIVE => 0, VALUE => $args{'q'} ); $self->Limit( FIELD => 'Summary', SUBCLAUSE => 'NameOrSummary', OPERATOR => 'LIKE', ENTRYAGGREGATOR => 'OR', CASESENSITIVE => 0, VALUE => $args{'q'} ); } foreach my $date (qw(Created< Created> LastUpdated< LastUpdated>)) { next unless ( $args{$date} ); my $date_obj = RT::Date->new( $self->CurrentUser ); $date_obj->Set( Format => 'unknown', Value => $args{$date} ); $dates->{$date} = $date_obj; if ( $date =~ /^(.*?)<$/i ) { $self->Limit( FIELD => $1, OPERATOR => "<=", ENTRYAGGREGATOR => "AND", VALUE => $date_obj->ISO ); } if ( $date =~ /^(.*?)>$/i ) { $self->Limit( FIELD => $1, OPERATOR => ">=", ENTRYAGGREGATOR => "AND", VALUE => $date_obj->ISO ); } } if ($args{'RefersTo'}) { foreach my $link ( split( /\s+/, $args{'RefersTo'} ) ) { next unless ($link); $self->LimitRefersTo($link); } } if ($args{'ReferredToBy'}) { foreach my $link ( split( /\s+/, $args{'ReferredToBy'} ) ) { next unless ($link); $self->LimitReferredToBy($link); } } if ( $args{'Topics'} ) { my @Topics = ( ref $args{'Topics'} eq 'ARRAY' ) ? @{ $args{'Topics'} } : ( $args{'Topics'} ); @Topics = map { split } @Topics; if ( $args{'ExpandTopics'} ) { my %topics; while (@Topics) { my $id = shift @Topics; next if $topics{$id}; my $Topics = RT::Topics->new( $self->CurrentUser ); $Topics->Limit( FIELD => 'Parent', VALUE => $id ); push @Topics, $_->Id while $_ = $Topics->Next; $topics{$id}++; } @Topics = keys %topics; $args{'Topics'} = \@Topics; } $self->LimitTopics(@Topics); } my %cfs; $customfields->LimitToLookupType( RT::Article->new( $self->CurrentUser ) ->CustomFieldLookupType ); if ( $args{'Class'} ) { my @Classes = ( ref $args{'Class'} eq 'ARRAY' ) ? @{ $args{'Class'} } : ( $args{'Class'} ); foreach my $class (@Classes) { $customfields->LimitToGlobalOrObjectId($class); } } else { $customfields->LimitToGlobalOrObjectId(); } while ( my $cf = $customfields->Next ) { $cfs{ $cf->Name } = $cf->Id; } # reset the iterator because we use this to build the UI $customfields->GotoFirstItem; foreach my $field ( keys %cfs ) { my @MatchLike = ( ref $args{ $field . "~" } eq 'ARRAY' ) ? @{ $args{ $field . "~" } } : ( $args{ $field . "~" } ); my @NoMatchLike = ( ref $args{ $field . "!~" } eq 'ARRAY' ) ? @{ $args{ $field . "!~" } } : ( $args{ $field . "!~" } ); my @Match = ( ref $args{$field} eq 'ARRAY' ) ? @{ $args{$field} } : ( $args{$field} ); my @NoMatch = ( ref $args{ $field . "!" } eq 'ARRAY' ) ? @{ $args{ $field . "!" } } : ( $args{ $field . "!" } ); foreach my $val (@MatchLike) { next unless $val; push @Match, "~" . $val; } foreach my $val (@NoMatchLike) { next unless $val; push @NoMatch, "~" . $val; } foreach my $value (@Match) { next unless $value; my $op; if ( $value =~ /^~(.*)$/ ) { $value = "%$1%"; $op = 'LIKE'; } else { $op = '='; } $self->LimitCustomField( FIELD => $cfs{$field}, VALUE => $value, CASESENSITIVE => 0, ENTRYAGGREGATOR => 'OR', OPERATOR => $op ); } foreach my $value (@NoMatch) { next unless $value; my $op; if ( $value =~ /^~(.*)$/ ) { $value = "%$1%"; $op = 'NOT LIKE'; } else { $op = '!='; } $self->LimitCustomField( FIELD => $cfs{$field}, VALUE => $value, CASESENSITIVE => 0, ENTRYAGGREGATOR => 'OR', OPERATOR => $op ); } } ### Searches for any field if ( $args{'Article~'} ) { $self->LimitCustomField( VALUE => $args{'Article~'}, ENTRYAGGREGATOR => 'OR', OPERATOR => 'LIKE', CASESENSITIVE => 0, SUBCLAUSE => 'SearchAll' ); $self->Limit( SUBCLAUSE => 'SearchAll', FIELD => "Name", VALUE => $args{'Article~'}, ENTRYAGGREGATOR => 'OR', CASESENSITIVE => 0, OPERATOR => 'LIKE' ); $self->Limit( SUBCLAUSE => 'SearchAll', FIELD => "Summary", VALUE => $args{'Article~'}, ENTRYAGGREGATOR => 'OR', CASESENSITIVE => 0, OPERATOR => 'LIKE' ); } if ( $args{'Article!~'} ) { $self->LimitCustomField( VALUE => $args{'Article!~'}, OPERATOR => 'NOT LIKE', CASESENSITIVE => 0, SUBCLAUSE => 'SearchAll' ); $self->Limit( SUBCLAUSE => 'SearchAll', FIELD => "Name", VALUE => $args{'Article!~'}, ENTRYAGGREGATOR => 'AND', CASESENSITIVE => 0, OPERATOR => 'NOT LIKE' ); $self->Limit( SUBCLAUSE => 'SearchAll', FIELD => "Summary", VALUE => $args{'Article!~'}, ENTRYAGGREGATOR => 'AND', CASESENSITIVE => 0, OPERATOR => 'NOT LIKE' ); } foreach my $field (qw(Name Summary Class)) { my @MatchLike = ( ref $args{ $field . "~" } eq 'ARRAY' ) ? @{ $args{ $field . "~" } } : ( $args{ $field . "~" } ); my @NoMatchLike = ( ref $args{ $field . "!~" } eq 'ARRAY' ) ? @{ $args{ $field . "!~" } } : ( $args{ $field . "!~" } ); my @Match = ( ref $args{$field} eq 'ARRAY' ) ? @{ $args{$field} } : ( $args{$field} ); my @NoMatch = ( ref $args{ $field . "!" } eq 'ARRAY' ) ? @{ $args{ $field . "!" } } : ( $args{ $field . "!" } ); foreach my $val (@MatchLike) { next unless $val; push @Match, "~" . $val; } foreach my $val (@NoMatchLike) { next unless $val; push @NoMatch, "~" . $val; } my $op; foreach my $value (@Match) { if ( $value && $value =~ /^~(.*)$/ ) { $value = "%$1%"; $op = 'LIKE'; } else { $op = '='; } # preprocess Classes, so we can search on class if ( $field eq 'Class' && $value ) { my $class = RT::Class->new($RT::SystemUser); $class->Load($value); $value = $class->Id; } # now that we've pruned the value, get out if it's different. next unless $value; $self->Limit( SUBCLAUSE => $field . 'Match', FIELD => $field, OPERATOR => $op, CASESENSITIVE => 0, VALUE => $value, ENTRYAGGREGATOR => 'OR' ); } foreach my $value (@NoMatch) { # preprocess Classes, so we can search on class if ( $value && $value =~ /^~(.*)/ ) { $value = "%$1%"; $op = 'NOT LIKE'; } else { $op = '!='; } if ( $field eq 'Class' ) { my $class = RT::Class->new($RT::SystemUser); $class->Load($value); $value = $class->Id; } # now that we've pruned the value, get out if it's different. next unless $value; $self->Limit( SUBCLAUSE => $field . 'NoMatch', OPERATOR => $op, VALUE => $value, CASESENSITIVE => 0, FIELD => $field, ENTRYAGGREGATOR => 'AND' ); } } if ($order_by && @$order_by) { if ( $order_by->[0] && $order_by->[0] =~ /\|/ ) { @$order_by = split '|', $order_by->[0]; @$order = split '|', $order->[0]; } my @tmp = map { { FIELD => $order_by->[$_], ORDER => $order->[$_] } } 0 .. $#{$order_by}; $self->OrderByCols(@tmp); } return 1; } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/I18N.pm�����������������������������������������������������������������������������000644 �000765 �000024 �00000055576 14005011336 015133� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::I18N - a base class for localization of RT =cut package RT::I18N; use strict; use warnings; use Cwd (); use Locale::Maketext 1.04; use Locale::Maketext::Lexicon 0.25; use base 'Locale::Maketext::Fuzzy'; use MIME::Entity; use MIME::Head; use File::Glob; use Encode::HanExtra; use Encode::Guess; use Encode::Detect::Detector; # I decree that this project's first language is English. our %Lexicon = ( 'TEST_STRING' => 'Concrete Mixer', '__Content-Type' => 'text/plain; charset=utf-8', '_AUTO' => 1, # That means that lookup failures can't happen -- if we get as far # as looking for something in this lexicon, and we don't find it, # then automagically set $Lexicon{$key} = $key, before possibly # compiling it. # The exception is keys that start with "_" -- they aren't auto-makeable. ); # End of lexicon. =head2 Init Initializes the lexicons used for localization. =cut sub Init { my @lang = RT->Config->Get('LexiconLanguages'); @lang = ('*') unless @lang; # load default functions require substr(Cwd::abs_path(__FILE__), 0, -3) . '/i_default.pm'; # Load language-specific functions foreach my $file ( File::Glob::bsd_glob(substr(Cwd::abs_path(__FILE__), 0, -3) . "/*.pm") ) { my ($lang) = ($file =~ /([^\\\/]+?)\.pm$/); next if $lang eq 'Extract'; # Avoid loading non-language utility module next unless grep $_ eq '*' || $_ eq $lang, @lang; require $file; } my %import; foreach my $l ( @lang ) { $import{$l} = [ Gettext => $RT::LexiconPath."/$l.po", ]; push @{ $import{$l} }, map {(Gettext => "$_/$l.po")} RT->PluginDirs('po'); push @{ $import{$l} }, (Gettext => $RT::LocalLexiconPath."/*/$l.po", Gettext => $RT::LocalLexiconPath."/$l.po"); } # Acquire all .po files and iterate them into lexicons Locale::Maketext::Lexicon->import({ _decode => 1, %import }); return 1; } sub LoadLexicons { no strict 'refs'; foreach my $k (keys %{RT::I18N::} ) { next if $k eq 'main::'; next unless index($k, '::', -2) >= 0; next unless exists ${ 'RT::I18N::'. $k }{'Lexicon'}; my $lex = *{ ${'RT::I18N::'. $k }{'Lexicon'} }{HASH}; # run fetch to force load my $tmp = $lex->{'foo'}; # XXX: untie may fail with "untie attempted # while 1 inner references still exist" # TODO: untie that has to lower fetch impact # untie %$lex if tied %$lex; } } =head2 encoding Returns the encoding of the current lexicon, as yanked out of __ContentType's "charset" field. If it can't find anything, it returns 'ISO-8859-1' =cut sub encoding { 'utf-8' } =head2 SetMIMEEntityToUTF8 $entity An utility function which will try to convert entity body into utf8. It's now a wrap-up of SetMIMEEntityToEncoding($entity, 'utf-8'). =cut sub SetMIMEEntityToUTF8 { RT::I18N::SetMIMEEntityToEncoding(shift, 'utf-8'); } =head2 IsTextualContentType $type An utility function that determines whether $type is I<textual>, meaning that it can sensibly be converted to Unicode text. Currently, it returns true iff $type matches this regular expression (case-insensitively): ^(?:text/(?:plain|html)|message/rfc822)\b =cut sub IsTextualContentType { my $type = shift; ($type =~ m{^(?:text/(?:plain|html)|message/rfc822)\b}i) ? 1 : 0; } =head2 SetMIMEEntityToEncoding Entity => ENTITY, Encoding => ENCODING, PreserveWords => BOOL, IsOut => BOOL An utility function which will try to convert entity body into specified charset encoding (encoded as octets, *not* unicode-strings). It will iterate all the entities in $entity, and try to convert each one into specified charset if whose Content-Type is 'text/plain'. If PreserveWords is true, values in mime head will be decoded.(default is false) Incoming and outgoing mails are handled differently, if IsOut is true(default is false), it'll be treated as outgoing mail, otherwise incomding mail: incoming mail: 1) find encoding 2) if found then try to convert to utf-8 in croak mode, return if success 3) guess encoding 4) if guessed differently then try to convert to utf-8 in croak mode, return if success 5) mark part as application/octet-stream instead of falling back to any encoding outgoing mail: 1) find encoding 2) if didn't find then do nothing, send as is, let MUA deal with it 3) if found then try to convert it to outgoing encoding in croak mode, return if success 4) do nothing otherwise, keep original encoding This function doesn't return anything meaningful. =cut sub SetMIMEEntityToEncoding { my ( $entity, $enc, $preserve_words, $is_out ); if ( @_ <= 3 ) { ( $entity, $enc, $preserve_words ) = @_; } else { my %args = ( Entity => undef, Encoding => undef, PreserveWords => undef, IsOut => undef, @_, ); $entity = $args{Entity}; $enc = $args{Encoding}; $preserve_words = $args{PreserveWords}; $is_out = $args{IsOut}; } unless ( $entity && $enc ) { RT->Logger->error("Missing Entity or Encoding arguments"); return; } # do the same for parts first of all SetMIMEEntityToEncoding( Entity => $_, Encoding => $enc, PreserveWords => $preserve_words, IsOut => $is_out, ) foreach $entity->parts; my $head = $entity->head; my $charset = _FindOrGuessCharset($entity); if ( $charset ) { unless( Encode::find_encoding($charset) ) { $RT::Logger->warning("Encoding '$charset' is not supported"); $charset = undef; } } unless ( $charset ) { $head->replace( "X-RT-Original-Content-Type" => $head->mime_attr('Content-Type') ); $head->mime_attr('Content-Type' => 'application/octet-stream'); return; } SetMIMEHeadToEncoding( Head => $head, From => _FindOrGuessCharset( $entity, 1 ), To => $enc, PreserveWords => $preserve_words, IsOut => $is_out, ); # If this is a textual entity, we'd need to preserve its original encoding $head->replace( "X-RT-Original-Encoding" => Encode::encode( "UTF-8", $charset ) ) if $head->mime_attr('content-type.charset') or IsTextualContentType($head->mime_type); return unless IsTextualContentType($head->mime_type); my $body = $entity->bodyhandle; if ( $body && ($enc ne $charset || $enc =~ /^utf-?8(?:-strict)?$/i) ) { my $string = $body->as_string or return; RT::Util::assert_bytes($string); $RT::Logger->debug( "Converting '$charset' to '$enc' for " . $head->mime_type . " - " . ( Encode::decode("UTF-8",$head->get('subject')) || 'Subjectless message' ) ); my $orig_string = $string; ( my $success, $string ) = EncodeFromToWithCroak( $orig_string, $charset => $enc ); if ( !$success ) { return if $is_out; my $error = $string; my $guess = _GuessCharset($orig_string); if ( $guess && $guess ne $charset ) { $RT::Logger->error( "Encoding error: " . $error . " falling back to Guess($guess) => $enc" ); ( $success, $string ) = EncodeFromToWithCroak( $orig_string, $guess, $enc ); $error = $string unless $success; } if ( !$success ) { $RT::Logger->error( "Encoding error: " . $error . " falling back to application/octet-stream" ); $head->mime_attr( "content-type" => 'application/octet-stream' ); return; } } my $new_body = MIME::Body::InCore->new($string); # set up the new entity $head->mime_attr( "content-type" => 'text/plain' ) unless ( $head->mime_attr("content-type") ); $head->mime_attr( "content-type.charset" => $enc ); $entity->bodyhandle($new_body); } } =head2 DecodeMIMEWordsToUTF8 $raw An utility method which mimics MIME::Words::decode_mimewords, but only limited functionality. Despite its name, this function returns the bytes of the string, in UTF-8. =cut sub DecodeMIMEWordsToUTF8 { my $str = shift; return DecodeMIMEWordsToEncoding($str, 'utf-8', @_); } sub DecodeMIMEWordsToEncoding { my $str = shift; my $to_charset = _CanonicalizeCharset(shift); my $field = shift || ''; $RT::Logger->warning( "DecodeMIMEWordsToEncoding was called without field name." ."It's known to cause troubles with decoding fields properly." ) unless $field; # XXX TODO: RT doesn't currently do the right thing with mime-encoded headers # We _should_ be preserving them encoded until after parsing is completed and # THEN undo the mime-encoding. # # This routine should be translating the existing mimeencoding to utf8 but leaving # things encoded. # # It's legal for headers to contain mime-encoded commas and semicolons which # should not be treated as address separators. (Encoding == quoting here) # # until this is fixed, we must escape any string containing a comma or semicolon # this is only a bandaid # Some _other_ MUAs encode quotes _already_, and double quotes # confuse us a lot, so only quote it if it isn't quoted # already. # handle filename*=ISO-8859-1''%74%E9%73%74%2E%74%78%74, parameter value # continuations, and similar syntax from RFC 2231 if ($field =~ /^Content-/i) { # This concatenates continued parameters and normalizes encoded params # to QB encoded-words which we handle below my $params = MIME::Field::ParamVal->parse_params($str); foreach my $v ( values %$params ) { $v = _DecodeMIMEWordsToEncoding( $v, $to_charset ); # de-quote in case those were hidden inside encoded part $v =~ s/\\(.)/$1/g if $v =~ s/^"(.*)"$/$1/; } $str = bless({}, 'MIME::Field::ParamVal')->set($params)->stringify; } elsif ( $field =~ /^(?:Resent-)?(?:To|From|B?Cc|Sender|Reply-To)$/i ) { my @addresses = RT::EmailParser->ParseEmailAddress( $str ); foreach my $address ( @addresses ) { foreach my $field (qw(phrase comment)) { my $v = $address->$field() or next; $v = _DecodeMIMEWordsToEncoding( $v, $to_charset ); if ( $field eq 'phrase' ) { # de-quote in case quoted value were hidden inside encoded part $v =~ s/\\(.)/$1/g if $v =~ s/^"(.*)"$/$1/; } $address->$field($v); } } $str = join ', ', map $_->format, @addresses; } else { $str = _DecodeMIMEWordsToEncoding( $str, $to_charset ); } # We might have \n without trailing whitespace, which will result in # invalid headers. $str =~ s/\n//g; return ($str) } sub _DecodeMIMEWordsToEncoding { my $str = shift; my $to_charset = shift; # Pre-parse by removing all whitespace between encoded words my $encoded_word = qr/ =\? # =? ([^?]+?) # charset (?:\*[^?]+)? # optional '*language' \? # ? ([QqBb]) # encoding \? # ? ([^?]+) # encoded string \?= # ?= /x; $str =~ s/($encoded_word)\s+(?=$encoded_word)/$1/g; # Also merge quoted-printable sections together, in case multiple # octets of a single encoded character were split between chunks. # Though not valid according to RFC 2047, this has been seen in the # wild. 1 while $str =~ s/(=\?[^?]+\?[Qq]\?)([^?]+)\?=\1([^?]+)\?=/$1$2$3?=/i; # XXX TODO: use decode('MIME-Header', ...) and Encode::Alias to replace our # custom MIME word decoding and charset canonicalization. We can't do this # until we parse before decode, instead of the other way around. my @list = $str =~ m/(.*?) # prefix $encoded_word ([^=]*) # trailing /xgcs; return $str unless @list; # add everything that hasn't matched to the end of the latest # string in array this happen when we have 'key="=?encoded?="; key="plain"' $list[-1] .= substr($str, pos $str); $str = ''; while (@list) { my ($prefix, $charset, $encoding, $enc_str, $trailing) = splice @list, 0, 5; $charset = _CanonicalizeCharset($charset); $encoding = lc $encoding; if ( $encoding eq 'q' ) { use MIME::QuotedPrint; $enc_str =~ tr/_/ /; # RFC 2047, 4.2 (2) $enc_str = decode_qp($enc_str); } elsif ( $encoding eq 'b' ) { use MIME::Base64; $enc_str = decode_base64($enc_str); } else { $RT::Logger->warning("Incorrect encoding '$encoding' in '$str', " ."only Q(uoted-printable) and B(ase64) are supported"); } # now we have got a decoded subject, try to convert into the encoding if ( $charset ne $to_charset || $charset =~ /^utf-?8(?:-strict)?$/i ) { if ( Encode::find_encoding($charset) ) { Encode::from_to( $enc_str, $charset, $to_charset ); } else { $RT::Logger->warning("Charset '$charset' is not supported"); $enc_str =~ s/[^[:print:]]/\357\277\275/g; Encode::from_to( $enc_str, 'UTF-8', $to_charset ) unless $to_charset eq 'utf-8'; } } $str .= $prefix . $enc_str . $trailing; } return ($str) } =head2 _FindOrGuessCharset MIME::Entity, $head_only When handed a MIME::Entity will first attempt to read what charset the message is encoded in. Failing that, will use Encode::Guess to try to figure it out If $head_only is true, only guesses charset for head parts. This is because header's encoding (e.g. filename="...") may be different from that of body's. =cut sub _FindOrGuessCharset { my $entity = shift; my $head_only = shift; my $head = $entity->head; if ( my $charset = $head->mime_attr("content-type.charset") ) { return _CanonicalizeCharset($charset); } if ( !$head_only and $head->mime_type =~ m{^text/} ) { my $body = $entity->bodyhandle or return; return _GuessCharset( $body->as_string ); } else { # potentially binary data -- don't guess the body return _GuessCharset( $head->as_string ); } } =head2 _GuessCharset STRING Use L<Encode::Guess> and L<Encode::Detect::Detector> to try to figure it out the string's encoding. =cut sub _GuessCharset { my $fallback = _CanonicalizeCharset('iso-8859-1'); # if $_[0] is null/empty, we don't guess its encoding return $fallback unless defined $_[0] && length $_[0]; my @encodings = RT->Config->Get('EmailInputEncodings'); unless ( @encodings ) { $RT::Logger->warning("No EmailInputEncodings set, fallback to $fallback"); return $fallback; } if ( $encodings[0] eq '*' ) { shift @encodings; my $charset = Encode::Detect::Detector::detect( $_[0] ); if ( $charset ) { $RT::Logger->debug("Encode::Detect::Detector guessed encoding: $charset"); return _CanonicalizeCharset( Encode::resolve_alias( $charset ) ); } else { $RT::Logger->debug("Encode::Detect::Detector failed to guess encoding"); } } unless ( @encodings ) { $RT::Logger->warning("No EmailInputEncodings set except '*', fallback to $fallback"); return $fallback; } Encode::Guess->set_suspects( @encodings ); my $decoder = Encode::Guess->guess( $_[0] ); unless ( defined $decoder ) { $RT::Logger->warning("Encode::Guess failed: decoder is undefined; fallback to $fallback"); return $fallback; } if ( ref $decoder ) { my $charset = $decoder->name; $RT::Logger->debug("Encode::Guess guessed encoding: $charset"); return _CanonicalizeCharset( $charset ); } elsif ($decoder =~ /(\S+ or .+)/) { my %matched = map { $_ => 1 } split(/ or /, $1); return 'utf-8' if $matched{'utf8'}; # one and only normalization foreach my $suspect (RT->Config->Get('EmailInputEncodings')) { next unless $matched{$suspect}; $RT::Logger->debug("Encode::Guess ambiguous ($decoder); using $suspect"); return _CanonicalizeCharset( $suspect ); } } else { $RT::Logger->warning("Encode::Guess failed: $decoder; fallback to $fallback"); } return $fallback; } =head2 _CanonicalizeCharset NAME canonicalize charset, return lowercase version. special cases are: gb2312 => gbk, utf8 => utf-8 =cut sub _CanonicalizeCharset { my $charset = lc shift; return $charset unless $charset; # Canonicalize aliases if they're known if (my $canonical = Encode::resolve_alias($charset)) { $charset = $canonical; } if ( $charset eq 'utf8' || $charset eq 'utf-8-strict' ) { return 'utf-8'; } elsif ( $charset eq 'euc-cn' ) { # gbk is superset of gb2312/euc-cn so it's safe return 'gbk'; } else { return $charset; } } =head2 SetMIMEHeadToEncoding MIMEHead => HEAD, From => OLD_ENCODING, To => NEW_Encoding, PreserveWords => BOOL, IsOut => BOOL Converts a MIME Head from one encoding to another. This totally violates the RFC. We should never need this. But, Surprise!, MUAs are badly broken and do this kind of stuff all the time =cut sub SetMIMEHeadToEncoding { my ( $head, $charset, $enc, $preserve_words, $is_out ); if ( @_ <= 4 ) { ( $head, $charset, $enc, $preserve_words ) = @_; } else { my %args = ( Head => undef, From => undef, To => undef, PreserveWords => undef, IsOut => undef, @_, ); $head = $args{Head}; $charset = $args{From}; $enc = $args{To}; $preserve_words = $args{PreserveWords}; $is_out = $args{IsOut}; } unless ( $head && $charset && $enc ) { RT->Logger->error( "Missing Head or From or To arguments"); return; } $charset = _CanonicalizeCharset($charset); $enc = _CanonicalizeCharset($enc); return if $charset eq $enc and $preserve_words; RT::Util::assert_bytes( $head->as_string ); foreach my $tag ( $head->tags ) { next unless $tag; # seen in wild: headers with no name my @values = $head->get_all($tag); $head->delete($tag); foreach my $value (@values) { if ( $charset ne $enc || $enc =~ /^utf-?8(?:-strict)?$/i ) { my $orig_value = $value; ( my $success, $value ) = EncodeFromToWithCroak( $orig_value, $charset => $enc ); if ( !$success ) { my $error = $value; if ($is_out) { $value = $orig_value; $head->add( $tag, $value ); next; } my $guess = _GuessCharset($orig_value); if ( $guess && $guess ne $charset ) { $RT::Logger->error( "Encoding error: " . $error . " falling back to Guess($guess) => $enc" ); ( $success, $value ) = EncodeFromToWithCroak( $orig_value, $guess, $enc ); $error = $value unless $success; } if ( !$success ) { $RT::Logger->error( "Encoding error: " . $error . " forcing conversion to $charset => $enc" ); $value = $orig_value; Encode::from_to( $value, $charset => $enc ); } } } $value = DecodeMIMEWordsToEncoding( $value, $enc, $tag ) unless $preserve_words; # We intentionally add a leading space when re-adding the # header; Mail::Header strips it before storing, but it # serves to prevent it from "helpfully" canonicalizing # $head->add("Subject", "Subject: foo") into the same as # $head->add("Subject", "foo"); $head->add( $tag, " " . $value ); } } } =head2 EncodeFromToWithCroak $string, $from, $to Try to encode string from encoding $from to encoding $to in croak mode return (1, $encoded_string) if success, otherwise (0, $error) =cut sub EncodeFromToWithCroak { my $string = shift; my $from = shift; my $to = shift; eval { no warnings 'utf8'; $string = Encode::encode( $to, Encode::decode( $from, $string ), Encode::FB_CROAK ); }; return $@ ? ( 0, $@ ) : ( 1, $string ); } RT::Base->_ImportOverlays(); 1; # End of module. ����������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Topic.pm����������������������������������������������������������������������������000644 �000765 �000024 �00000017723 14005011336 015522� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 warnings; use strict; package RT::Topic; use base 'RT::Record'; sub Table {'Topics'} # {{{ Create =head2 Create PARAMHASH Create takes a hash of values and creates a row in the database: int(11) 'Parent'. varchar(255) 'Name'. varchar(255) 'Description'. varchar(64) 'ObjectType'. int(11) 'ObjectId'. =cut sub Create { my $self = shift; my %args = ( Parent => '', Name => '', Description => '', ObjectType => '', ObjectId => '0', @_); my $obj = $RT::System; if ($args{ObjectId}) { $obj = $args{ObjectType}->new($self->CurrentUser); $obj->Load($args{ObjectId}); $obj = $RT::System unless $obj->id; } return ( 0, $self->loc("Permission Denied")) unless ( $self->CurrentUser->HasRight( Right => "AdminTopics", Object => $obj, EquivObjects => [ $RT::System, $obj ], ) ); $self->SUPER::Create(@_); } # }}} # {{{ Delete =head2 Delete Deletes this topic, reparenting all sub-topics to this one's parent. =cut sub Delete { my $self = shift; unless ( $self->CurrentUserHasRight('AdminTopics') ) { return ( 0, $self->loc("Permission Denied") ); } my $kids = RT::Topics->new($self->CurrentUser); $kids->LimitToKids($self->Id); while (my $topic = $kids->Next) { $topic->setParent($self->Parent); } $self->SUPER::Delete(@_); return (0, "Topic deleted"); } # }}} # {{{ DeleteAll =head2 DeleteAll Deletes this topic, and all of its descendants. =cut sub DeleteAll { my $self = shift; unless ( $self->CurrentUserHasRight('AdminTopics') ) { return ( 0, $self->loc("Permission Denied") ); } $self->SUPER::Delete(@_); my $kids = RT::Topics->new($self->CurrentUser); $kids->LimitToKids($self->Id); while (my $topic = $kids->Next) { $topic->DeleteAll; } return (0, "Topic tree deleted"); } # }}} # {{{ ParentObj =head2 ParentObj Returns the parent Topic of this one. =cut sub ParentObj { my $self = shift; my $id = $self->Parent; my $obj = RT::Topic->new($self->CurrentUser); $obj->Load($id); return $obj; } # }}} # {{{ Children =head2 Children Returns a Topics object containing this topic's children, sorted by Topic.Name. =cut sub Children { my $self = shift; unless ($self->{'Children'}) { $self->{'Children'} = RT::Topics->new($self->CurrentUser); $self->{'Children'}->Limit('FIELD' => 'Parent', 'VALUE' => $self->Id); $self->{'Children'}->OrderBy('FIELD' => 'Name'); } return $self->{'Children'}; } # {{{ _Set =head2 _Set Intercept attempts to modify the Topic so we can apply ACLs =cut sub _Set { my $self = shift; unless ( $self->CurrentUserHasRight('AdminTopics') ) { return ( 0, $self->loc("Permission Denied") ); } $self->SUPER::_Set(@_); } # }}} =head2 ACLEquivalenceObjects Rights on the topic are inherited from the object it is a topic on. =cut sub ACLEquivalenceObjects { my $self = shift; return unless $self->id and $self->ObjectId; return $self->Object; } sub Object { my $self = shift; my $Object = $self->__Value('ObjectType')->new( $self->CurrentUser ); $Object->Load( $self->__Value('ObjectId') ); return $Object; } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 Parent Returns the current value of Parent. (In the database, Parent is stored as int(11).) =head2 SetParent VALUE Set Parent to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Parent will be stored as a int(11).) =cut =head2 Name Returns the current value of Name. (In the database, Name is stored as varchar(255).) =head2 SetName VALUE Set Name to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Name will be stored as a varchar(255).) =cut =head2 Description Returns the current value of Description. (In the database, Description is stored as varchar(255).) =head2 SetDescription VALUE Set Description to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Description will be stored as a varchar(255).) =cut =head2 ObjectType Returns the current value of ObjectType. (In the database, ObjectType is stored as varchar(64).) =head2 SetObjectType VALUE Set ObjectType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectType will be stored as a varchar(64).) =cut =head2 ObjectId Returns the current value of ObjectId. (In the database, ObjectId is stored as int(11).) =head2 SetObjectId VALUE Set ObjectId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectId will be stored as a int(11).) =cut sub _CoreAccessible { { id => {read => 1, type => 'int(11)', default => ''}, Parent => {read => 1, write => 1, type => 'int(11)', default => ''}, Name => {read => 1, write => 1, type => 'varchar(255)', default => ''}, Description => {read => 1, write => 1, type => 'varchar(255)', default => ''}, ObjectType => {read => 1, write => 1, type => 'varchar(64)', default => ''}, ObjectId => {read => 1, write => 1, type => 'int(11)', default => '0'}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); $deps->Add( out => $self->ParentObj ) if $self->ParentObj->Id; $deps->Add( in => $self->Children ); $deps->Add( out => $self->Object ); } RT::Base->_ImportOverlays(); 1; ���������������������������������������������rt-5.0.1/lib/RT/Record.pm���������������������������������������������������������������������������000644 �000765 �000024 �00000252267 14005011336 015666� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Record - Base class for RT record objects =head1 SYNOPSIS =head1 DESCRIPTION =head1 METHODS =cut package RT::Record; use strict; use warnings; use RT; use base RT->Config->Get('RecordBaseClass'); use base 'RT::Base'; require RT::Date; require RT::User; require RT::Attributes; require RT::Transactions; require RT::Link; use RT::Shredder::Dependencies; use RT::Shredder::Constants; use RT::Shredder::Exceptions; our $_TABLE_ATTR = { }; sub _Init { my $self = shift; $self->_BuildTableAttributes unless ($_TABLE_ATTR->{ref($self)}); $self->CurrentUser(@_); } =head2 _PrimaryKeys The primary keys for RT classes is 'id' =cut sub _PrimaryKeys { return ['id'] } # short circuit many, many thousands of calls from searchbuilder sub _PrimaryKey { 'id' } =head2 Id Override L<DBIx::SearchBuilder/Id> to avoid a few lookups RT doesn't do on a very common codepath C<id> is an alias to C<Id> and is the preferred way to call this method. =cut sub Id { return shift->{'values'}->{id}; } *id = \&Id; =head2 Delete Delete this record object from the database. =cut sub Delete { my $self = shift; my ($rv) = $self->SUPER::Delete; if ($rv) { return ($rv, $self->loc("Object deleted")); } else { return (0, $self->loc("Object could not be deleted")); } } =head2 RecordType Returns a string which is this record's type. It's not localized and by default last part (everything after last ::) of class name is returned. =cut sub RecordType { my $res = ref($_[0]) || $_[0]; $res =~ s/.*:://; return $res; } =head2 Attributes Return this object's attributes as an RT::Attributes object =cut sub Attributes { my $self = shift; unless ($self->{'attributes'}) { $self->{'attributes'} = RT::Attributes->new($self->CurrentUser); $self->{'attributes'}->LimitToObject($self); $self->{'attributes'}->OrderByCols({FIELD => 'id'}); } return ($self->{'attributes'}); } =head2 AddAttribute { Name, Description, Content } Adds a new attribute for this object. =cut sub AddAttribute { my $self = shift; my %args = ( Name => undef, Description => undef, Content => undef, @_ ); my $attr = RT::Attribute->new( $self->CurrentUser ); my ( $id, $msg ) = $attr->Create( Object => $self, Name => $args{'Name'}, Description => $args{'Description'}, Content => $args{'Content'} ); # XXX TODO: Why won't RedoSearch work here? $self->Attributes->_DoSearch; return ($id, $msg); } =head2 SetAttribute { Name, Description, Content } Like AddAttribute, but replaces all existing attributes with the same Name. =cut sub SetAttribute { my $self = shift; my %args = ( Name => undef, Description => undef, Content => undef, @_ ); my @AttributeObjs = $self->Attributes->Named( $args{'Name'} ) or return $self->AddAttribute( %args ); my $AttributeObj = pop( @AttributeObjs ); $_->Delete foreach @AttributeObjs; $AttributeObj->SetDescription( $args{'Description'} ); $AttributeObj->SetContent( $args{'Content'} ); $self->Attributes->RedoSearch; return 1; } =head2 DeleteAttribute NAME Deletes all attributes with the matching name for this object. =cut sub DeleteAttribute { my $self = shift; my $name = shift; my ($val,$msg) = $self->Attributes->DeleteEntry( Name => $name ); $self->ClearAttributes; return ($val,$msg); } =head2 FirstAttribute NAME Returns the first attribute with the matching name for this object (as an L<RT::Attribute> object), or C<undef> if no such attributes exist. If there is more than one attribute with the matching name on the object, the first value that was set is returned. =cut sub FirstAttribute { my $self = shift; my $name = shift; return ($self->Attributes->Named( $name ))[0]; } sub ClearAttributes { my $self = shift; delete $self->{'attributes'}; } sub _Handle { return $RT::Handle } =head2 Create PARAMHASH Takes a PARAMHASH of Column -> Value pairs. If any Column has a Validate$PARAMNAME subroutine defined and the value provided doesn't pass validation, this routine returns an error. If this object's table has any of the following atetributes defined as 'Auto', this routine will automatically fill in their values. =over =item Created =item Creator =item LastUpdated =item LastUpdatedBy =back =cut sub Create { my $self = shift; my %attribs = (@_); foreach my $key ( keys %attribs ) { if (my $method = $self->can("Validate$key")) { if (! $method->( $self, $attribs{$key} ) ) { if (wantarray) { return ( 0, $self->loc('Invalid value for [_1]', $key) ); } else { return (0); } } } } my ($sec,$min,$hour,$mday,$mon,$year,$wday,$ydaym,$isdst,$offset) = gmtime(); my $now_iso = sprintf("%04d-%02d-%02d %02d:%02d:%02d", ($year+1900), ($mon+1), $mday, $hour, $min, $sec); $attribs{'Created'} = $now_iso if ( $self->_Accessible( 'Created', 'auto' ) && !$attribs{'Created'}); if ($self->_Accessible( 'Creator', 'auto' ) && !$attribs{'Creator'}) { $attribs{'Creator'} = $self->CurrentUser->id || '0'; } $attribs{'LastUpdated'} = $now_iso if ( $self->_Accessible( 'LastUpdated', 'auto' ) && !$attribs{'LastUpdated'}); $attribs{'LastUpdatedBy'} = $self->CurrentUser->id || '0' if ( $self->_Accessible( 'LastUpdatedBy', 'auto' ) && !$attribs{'LastUpdatedBy'}); my $id = $self->SUPER::Create(%attribs); if ( UNIVERSAL::isa( $id, 'Class::ReturnValue' ) ) { if ( $id->errno ) { if (wantarray) { return ( 0, $self->loc( "Internal Error: [_1]", $id->{error_message} ) ); } else { return (0); } } } # If the object was created in the database, # load it up now, so we're sure we get what the database # has. Arguably, this should not be necessary, but there # isn't much we can do about it. unless ($id) { if (wantarray) { return ( $id, $self->loc('Object could not be created') ); } else { return ($id); } } if (UNIVERSAL::isa('errno',$id)) { return(undef); } $self->Load($id) if ($id); if (wantarray) { return ( $id, $self->loc('Object created') ); } else { return ($id); } } =head2 LoadByCols Override DBIx::SearchBuilder::LoadByCols to do case-insensitive loads if the DB is case sensitive =cut sub LoadByCols { my $self = shift; # We don't want to hang onto this $self->ClearAttributes; unless ( $self->_Handle->CaseSensitive ) { my ( $ret, $msg ) = $self->SUPER::LoadByCols( @_ ); return wantarray ? ( $ret, $msg ) : $ret; } # If this database is case sensitive we need to uncase objects for # explicit loading my %hash = (@_); foreach my $key ( keys %hash ) { # If we've been passed an empty value, we can't do the lookup. # We don't need to explicitly downcase integers or an id. if ( $key ne 'id' && defined $hash{ $key } && $hash{ $key } !~ /^\d+$/ ) { my ($op, $val, $func); ($key, $op, $val, $func) = $self->_Handle->_MakeClauseCaseInsensitive( $key, '=', delete $hash{ $key } ); $hash{$key}->{operator} = $op; $hash{$key}->{value} = $val; $hash{$key}->{function} = $func; } } my ( $ret, $msg ) = $self->SUPER::LoadByCols( %hash ); return wantarray ? ( $ret, $msg ) : $ret; } # There is room for optimizations in most of those subs: sub LastUpdatedObj { my $self = shift; my $obj = RT::Date->new( $self->CurrentUser ); $obj->Set( Format => 'sql', Value => $self->LastUpdated ); return $obj; } sub CreatedObj { my $self = shift; my $obj = RT::Date->new( $self->CurrentUser ); $obj->Set( Format => 'sql', Value => $self->Created ); return $obj; } sub LastUpdatedAsString { my $self = shift; if ( $self->LastUpdated ) { return ( $self->LastUpdatedObj->AsString() ); } else { return "never"; } } sub CreatedAsString { my $self = shift; return ( $self->CreatedObj->AsString() ); } sub _Set { my $self = shift; my %args = ( Field => undef, Value => undef, IsSQL => undef, @_ ); #if the user is trying to modify the record # TODO: document _why_ this code is here if ( ( !defined( $args{'Field'} ) ) || ( !defined( $args{'Value'} ) ) ) { $args{'Value'} = 0; } my $old_val = $self->__Value($args{'Field'}); $self->_SetLastUpdated(); my $ret = $self->SUPER::_Set( Field => $args{'Field'}, Value => $args{'Value'}, IsSQL => $args{'IsSQL'} ); my ($status, $msg) = $ret->as_array(); # @values has two values, a status code and a message. # $ret is a Class::ReturnValue object. as such, in a boolean context, it's a bool # we want to change the standard "success" message if ($status) { if ($self->SQLType( $args{'Field'}) =~ /text/) { $msg = $self->loc( "[_1] updated", $self->loc( $args{'Field'} ), ); } else { $msg = $self->loc( "[_1] changed from [_2] to [_3]", $self->loc( $args{'Field'} ), ( $old_val ? '"' . $old_val . '"' : $self->loc("(no value)") ), '"' . $self->__Value( $args{'Field'}) . '"', ); } } else { $msg = $self->CurrentUser->loc_fuzzy($msg); } return wantarray ? ($status, $msg) : $ret; } =head2 _SetLastUpdated This routine updates the LastUpdated and LastUpdatedBy columns of the row in question It takes no options. Arguably, this is a bug =cut sub _SetLastUpdated { my $self = shift; my $now = RT::Date->new( $self->CurrentUser ); $now->SetToNow(); if ( $self->_Accessible( 'LastUpdated', 'auto' ) ) { my ( $msg, $val ) = $self->__Set( Field => 'LastUpdated', Value => $now->ISO ); } if ( $self->_Accessible( 'LastUpdatedBy', 'auto' ) ) { my ( $msg, $val ) = $self->__Set( Field => 'LastUpdatedBy', Value => $self->CurrentUser->id ); } } =head2 CreatorObj Returns an RT::User object with the RT account of the creator of this row =cut sub CreatorObj { my $self = shift; unless ( exists $self->{'CreatorObj'} ) { $self->{'CreatorObj'} = RT::User->new( $self->CurrentUser ); $self->{'CreatorObj'}->Load( $self->Creator ); } return ( $self->{'CreatorObj'} ); } =head2 LastUpdatedByObj Returns an RT::User object of the last user to touch this object =cut sub LastUpdatedByObj { my $self = shift; unless ( exists $self->{LastUpdatedByObj} ) { $self->{'LastUpdatedByObj'} = RT::User->new( $self->CurrentUser ); $self->{'LastUpdatedByObj'}->Load( $self->LastUpdatedBy ); } return $self->{'LastUpdatedByObj'}; } =head2 URI Returns this record's URI =cut sub URI { my $self = shift; my $uri = RT::URI::fsck_com_rt->new($self->CurrentUser); return($uri->URIForObject($self)); } =head2 ValidateName NAME Validate the name of the record we're creating. Mostly, just make sure it's not a numeric ID, which is invalid for Name =cut sub ValidateName { my $self = shift; my $value = shift; if (defined $value && $value=~ /^\d+$/) { return(0); } else { return(1); } } =head2 SQLType attribute return the SQL type for the attribute 'attribute' as stored in _ClassAccessible =cut sub SQLType { my $self = shift; my $field = shift; return ($self->_Accessible($field, 'type')); } sub __Value { my $self = shift; my $field = shift; my %args = ( decode_utf8 => 1, @_ ); unless ($field) { $RT::Logger->error("__Value called with undef field"); } my $value = $self->SUPER::__Value($field); return $value if ref $value; return undef if (!defined $value); # Pg returns character columns as character strings; mysql and # sqlite return them as bytes. While mysql can be made to return # characters, using the mysql_enable_utf8 flag, the "Content" column # is bytes on mysql and characters on Postgres, making true # consistency impossible. if ( $args{'decode_utf8'} ) { if ( !utf8::is_utf8($value) ) { # mysql/sqlite utf8::decode($value); } } else { if ( utf8::is_utf8($value) ) { utf8::encode($value); } } return $value; } # Set up defaults for DBIx::SearchBuilder::Record::Cachable sub _CacheConfig { { 'cache_for_sec' => 30, } } sub _BuildTableAttributes { my $self = shift; my $class = ref($self) || $self; my $attributes; if ( UNIVERSAL::can( $self, '_CoreAccessible' ) ) { $attributes = $self->_CoreAccessible(); } elsif ( UNIVERSAL::can( $self, '_ClassAccessible' ) ) { $attributes = $self->_ClassAccessible(); } foreach my $column (keys %$attributes) { foreach my $attr ( keys %{ $attributes->{$column} } ) { $_TABLE_ATTR->{$class}->{$column}->{$attr} = $attributes->{$column}->{$attr}; } } foreach my $method ( qw(_OverlayAccessible _VendorAccessible _LocalAccessible) ) { next unless UNIVERSAL::can( $self, $method ); $attributes = $self->$method(); foreach my $column ( keys %$attributes ) { foreach my $attr ( keys %{ $attributes->{$column} } ) { $_TABLE_ATTR->{$class}->{$column}->{$attr} = $attributes->{$column}->{$attr}; } } } } =head2 _ClassAccessible Overrides the "core" _ClassAccessible using $_TABLE_ATTR. Behaves identical to the version in DBIx::SearchBuilder::Record =cut sub _ClassAccessible { my $self = shift; return $_TABLE_ATTR->{ref($self) || $self}; } =head2 _Accessible COLUMN ATTRIBUTE returns the value of ATTRIBUTE for COLUMN =cut sub _Accessible { my $self = shift; my $column = shift; my $attribute = lc(shift); my $class = ref($self) || $self; $class->_BuildTableAttributes unless ($_TABLE_ATTR->{$class}); return 0 unless defined ($_TABLE_ATTR->{$class}->{$column}); return $_TABLE_ATTR->{$class}->{$column}->{$attribute} || 0; } =head2 _EncodeLOB BODY MIME_TYPE FILENAME Takes a potentially large attachment. Returns (ContentEncoding, EncodedBody, MimeType, Filename, NoteArgs) based on system configuration and selected database. Returns a custom (short) text/plain message if DropLongAttachments causes an attachment to not be stored. Encodes your data as base64 or Quoted-Printable as needed based on your Databases's restrictions and the UTF-8ness of the data being passed in. Since we are storing in columns marked UTF8, we must ensure that binary data is encoded on databases which are strict. This function expects to receive an octet string in order to properly evaluate and encode it. It will return an octet string. NoteArgs is currently used to indicate caller that the message is too long and is truncated or dropped. It's a hashref which is expected to be passed to L<RT::Record/_NewTransaction>. =cut sub _EncodeLOB { my $self = shift; my $Body = shift; my $MIMEType = shift || ''; my $Filename = shift; my $ContentEncoding = 'none'; my $note_args; RT::Util::assert_bytes( $Body ); #get the max attachment length from RT my $MaxSize = RT->Config->Get('MaxAttachmentSize'); #if the current attachment contains nulls and the #database doesn't support embedded nulls if ( ( !$RT::Handle->BinarySafeBLOBs ) && ( $Body =~ /\x00/ ) ) { # set a flag telling us to mimencode the attachment $ContentEncoding = 'base64'; #cut the max attchment size by 25% (for mime-encoding overhead. $RT::Logger->debug("Max size is $MaxSize"); $MaxSize = $MaxSize * 3 / 4; # Some databases (postgres) can't handle non-utf8 data } elsif ( !$RT::Handle->BinarySafeBLOBs && $Body =~ /\P{ASCII}/ && !Encode::is_utf8( $Body, 1 ) ) { $ContentEncoding = 'quoted-printable'; } #if the attachment is larger than the maximum size if ( ($MaxSize) and ( $MaxSize < length($Body) ) ) { my $size = length $Body; # if we're supposed to truncate large attachments if (RT->Config->Get('TruncateLongAttachments')) { $RT::Logger->info("$self: Truncated an attachment of size $size"); # truncate the attachment to that length. $Body = substr( $Body, 0, $MaxSize ); $note_args = { Type => 'AttachmentTruncate', Data => $Filename, OldValue => $size, NewValue => $MaxSize, ActivateScrips => 0, }; } # elsif we're supposed to drop large attachments on the floor, elsif (RT->Config->Get('DropLongAttachments')) { # drop the attachment on the floor $RT::Logger->info( "$self: Dropped an attachment of size $size" ); $RT::Logger->info( "It started: " . substr( $Body, 0, 60 ) ); $note_args = { Type => 'AttachmentDrop', Data => $Filename, OldValue => $size, NewValue => $MaxSize, ActivateScrips => 0, }; $Filename .= ".txt" if $Filename && $Filename !~ /\.txt$/; return ("none", "Large attachment dropped", "text/plain", $Filename, $note_args ); } } # if we need to mimencode the attachment if ( $ContentEncoding eq 'base64' ) { # base64 encode the attachment $Body = MIME::Base64::encode_base64($Body); } elsif ($ContentEncoding eq 'quoted-printable') { $Body = MIME::QuotedPrint::encode($Body); } return ($ContentEncoding, $Body, $MIMEType, $Filename, $note_args ); } =head2 _DecodeLOB C<ContentType>, C<ContentEncoding>, C<Content> This function reverses the effects of L</_EncodeLOB>, by unpacking the data, provided as bytes (not characters!), from the database. This data may also be Base64 or Quoted-Printable encoded, as given by C<Content-Encoding>. This encoding layer exists because the underlying database column is "text", which rejects non-UTF-8 byte sequences. Alternatively, if the data lives in external storage, it will be read (or downloaded) and returned. This function differs in one important way from being the inverse of L</_EncodeLOB>: for textual data (as judged via L<RT::I18N/IsTextualContentType> applied to the given C<ContentType>), C<_DecodeLOB> returns character strings, not bytes. The character set used in decoding is taken from the C<ContentType>, or UTF-8 if not provided; however, for all textual content inserted by current code, the character set used for storage is always UTF-8. This decoding step is done using L<Encode>'s PERLQQ filter, which replaces invalid byte sequences with C<\x{HH}>. This mirrors how data from query parameters are parsed in L<RT::Interface::Web/DecodeARGS>. Since RT is now strict about the bytes it inserts, substitution characters should only be needed for data inserted by older versions of RT, or for C<ContentType>s which are now believed to be textual, but were not considered so on insertion (and thus not transcoded). =cut sub _DecodeLOB { my $self = shift; my $ContentType = shift || ''; my $ContentEncoding = shift || 'none'; my $Content = shift; RT::Util::assert_bytes( $Content ); if ( $ContentEncoding eq 'base64' ) { $Content = MIME::Base64::decode_base64($Content); } elsif ( $ContentEncoding eq 'quoted-printable' ) { $Content = MIME::QuotedPrint::decode($Content); } elsif ( $ContentEncoding eq 'external' ) { my $Digest = $Content; my $Storage = RT->System->ExternalStorage; unless ($Storage) { RT->Logger->error( "Failed to load $Content; external storage not configured" ); return (""); }; ($Content, my $msg) = $Storage->Get( $Digest ); unless (defined $Content) { RT->Logger->error( "Failed to load $Digest from external storage: $msg" ); return (""); } } elsif ( $ContentEncoding && $ContentEncoding ne 'none' ) { return ( $self->loc( "Unknown ContentEncoding [_1]", $ContentEncoding ) ); } if ( RT::I18N::IsTextualContentType($ContentType) ) { my $entity = MIME::Entity->new(); $entity->head->add("Content-Type", $ContentType); $entity->bodyhandle( MIME::Body::Scalar->new( $Content ) ); my $charset = RT::I18N::_FindOrGuessCharset($entity); $charset = 'utf-8' if not $charset or not Encode::find_encoding($charset); $Content = Encode::decode($charset,$Content,Encode::FB_PERLQQ); } return ($Content); } =head2 Update ARGSHASH Updates fields on an object for you using the proper Set methods, skipping unchanged values. ARGSRef => a hashref of attributes => value for the update AttributesRef => an arrayref of keys in ARGSRef that should be updated AttributePrefix => a prefix that should be added to the attributes in AttributesRef when looking up values in ARGSRef Bare attributes are tried before prefixed attributes Returns a list of localized results of the update =cut sub Update { my $self = shift; my %args = ( ARGSRef => undef, AttributesRef => undef, AttributePrefix => undef, @_ ); my $attributes = $args{'AttributesRef'}; my $ARGSRef = $args{'ARGSRef'}; my %new_values; # gather all new values foreach my $attribute (@$attributes) { my $value; if ( defined $ARGSRef->{$attribute} ) { $value = $ARGSRef->{$attribute}; } elsif ( defined( $args{'AttributePrefix'} ) && defined( $ARGSRef->{ $args{'AttributePrefix'} . "-" . $attribute } ) ) { $value = $ARGSRef->{ $args{'AttributePrefix'} . "-" . $attribute }; } else { next; } $value =~ s/\r\n/\n/gs; my $truncated_value = $self->TruncateValue($attribute, $value); # If Queue is 'General', we want to resolve the queue name for # the object. # This is in an eval block because $object might not exist. # and might not have a Name method. But "can" won't find autoloaded # items. If it fails, we don't care do { no warnings "uninitialized"; if ( $attribute ne 'Lifecycle' ) { local $@; my $name = eval { my $object = $attribute . "Obj"; $self->$object->Name; }; unless ($@) { next if $name eq $value || $name eq ( $value || 0 ); } } next if $truncated_value eq $self->$attribute(); next if ( $truncated_value || 0 ) eq $self->$attribute(); }; $new_values{$attribute} = $value; } return $self->_UpdateAttributes( Attributes => $attributes, NewValues => \%new_values, ); } sub _UpdateAttributes { my $self = shift; my %args = ( Attributes => [], NewValues => {}, @_, ); my @results; foreach my $attribute (@{ $args{Attributes} }) { next if !exists($args{NewValues}{$attribute}); my $value = $args{NewValues}{$attribute}; my $method = "Set$attribute"; my ( $code, $msg ) = $self->$method($value); my ($prefix) = ref($self) =~ /RT(?:.*)::(\w+)/; # Default to $id, but use name if we can get it. my $label = $self->id; $label = $self->Name if (UNIVERSAL::can($self,'Name')); # this requires model names to be loc'ed. =for loc "Ticket" # loc "User" # loc "Group" # loc "Queue" # loc =cut push @results, $self->loc( $prefix ) . " $label: ". $msg; =for loc "[_1] could not be set to [_2].", # loc "That is already the current value", # loc "No value sent to _Set!", # loc "Illegal value for [_1]", # loc "The new value has been set.", # loc "No column specified", # loc "Immutable field", # loc "Nonexistant field?", # loc "Invalid data", # loc "Couldn't find row", # loc "Missing a primary key?: [_1]", # loc "Found Object", # loc =cut } return @results; } =head2 Members This returns an RT::Links object which references all the tickets which are 'MembersOf' this ticket =cut sub Members { my $self = shift; return ( $self->_Links( 'Target', 'MemberOf' ) ); } =head2 MemberOf This returns an RT::Links object which references all the tickets that this ticket is a 'MemberOf' =cut sub MemberOf { my $self = shift; return ( $self->_Links( 'Base', 'MemberOf' ) ); } =head2 RefersTo This returns an RT::Links object which shows all references for which this ticket is a base =cut sub RefersTo { my $self = shift; return ( $self->_Links( 'Base', 'RefersTo' ) ); } =head2 ReferredToBy This returns an L<RT::Links> object which shows all references for which this ticket is a target =cut sub ReferredToBy { my $self = shift; return ( $self->_Links( 'Target', 'RefersTo' ) ); } =head2 DependedOnBy This returns an RT::Links object which references all the tickets that depend on this one =cut sub DependedOnBy { my $self = shift; return ( $self->_Links( 'Target', 'DependsOn' ) ); } =head2 HasUnresolvedDependencies Takes a paramhash of Type (default to '__any'). Returns the number of unresolved dependencies, if $self->UnresolvedDependencies returns an object with one or more members of that type. Returns false otherwise. =cut sub HasUnresolvedDependencies { my $self = shift; my %args = ( Type => undef, @_ ); my $deps = $self->UnresolvedDependencies; if ($args{Type}) { $deps->LimitType( VALUE => $args{Type} ); } else { $deps->IgnoreType; } if ($deps->Count > 0) { return $deps->Count; } else { return (undef); } } =head2 UnresolvedDependencies Returns an RT::Tickets object of tickets which this ticket depends on and which have a status of new, open or stalled. (That list comes from RT::Queue->ActiveStatusArray =cut sub UnresolvedDependencies { my $self = shift; my $deps = RT::Tickets->new($self->CurrentUser); $deps->LimitToActiveStatus; $deps->LimitDependedOnBy($self->Id); return($deps); } =head2 AllDependedOnBy Returns an array of RT::Ticket objects which (directly or indirectly) depends on this ticket; takes an optional 'Type' argument in the param hash, which will limit returned tickets to that type, as well as cause tickets with that type to serve as 'leaf' nodes that stops the recursive dependency search. =cut sub AllDependedOnBy { my $self = shift; return $self->_AllLinkedTickets( LinkType => 'DependsOn', Direction => 'Target', @_ ); } =head2 AllDependsOn Returns an array of RT::Ticket objects which this ticket (directly or indirectly) depends on; takes an optional 'Type' argument in the param hash, which will limit returned tickets to that type, as well as cause tickets with that type to serve as 'leaf' nodes that stops the recursive dependency search. =cut sub AllDependsOn { my $self = shift; return $self->_AllLinkedTickets( LinkType => 'DependsOn', Direction => 'Base', @_ ); } sub _AllLinkedTickets { my $self = shift; my %args = ( LinkType => undef, Direction => undef, Type => undef, _found => {}, _top => 1, @_ ); my $dep = $self->_Links( $args{Direction}, $args{LinkType}); while (my $link = $dep->Next()) { my $uri = $args{Direction} eq 'Target' ? $link->BaseURI : $link->TargetURI; next unless ($uri->IsLocal()); my $obj = $args{Direction} eq 'Target' ? $link->BaseObj : $link->TargetObj; next if $args{_found}{$obj->Id}; if (!$args{Type}) { $args{_found}{$obj->Id} = $obj; $obj->_AllLinkedTickets( %args, _top => 0 ); } elsif ($obj->Type and $obj->Type eq $args{Type}) { $args{_found}{$obj->Id} = $obj; } else { $obj->_AllLinkedTickets( %args, _top => 0 ); } } if ($args{_top}) { return map { $args{_found}{$_} } sort keys %{$args{_found}}; } else { return 1; } } =head2 DependsOn This returns an RT::Links object which references all the tickets that this ticket depends on =cut sub DependsOn { my $self = shift; return ( $self->_Links( 'Base', 'DependsOn' ) ); } =head2 Links DIRECTION [TYPE] Return links (L<RT::Links>) to/from this object. DIRECTION is either 'Base' or 'Target'. TYPE is a type of links to return, it can be omitted to get links of any type. =cut sub Links { shift->_Links(@_) } sub _Links { my $self = shift; #TODO: Field isn't the right thing here. but I ahave no idea what mnemonic --- #tobias meant by $f my $field = shift; my $type = shift || ""; unless ( $self->{"$field$type"} ) { $self->{"$field$type"} = RT::Links->new( $self->CurrentUser ); # at least to myself $self->{"$field$type"}->Limit( FIELD => $field, VALUE => $self->URI, ENTRYAGGREGATOR => 'OR' ); $self->{"$field$type"}->Limit( FIELD => 'Type', VALUE => $type ) if ($type); } return ( $self->{"$field$type"} ); } =head2 FormatType Takes a Type and returns a string that is more human readable. =cut sub FormatType{ my $self = shift; my %args = ( Type => '', @_ ); $args{Type} =~ s/([A-Z])/" " . lc $1/ge; $args{Type} =~ s/^\s+//; return $args{Type}; } =head2 FormatLink Takes either a Target or a Base and returns a string of human friendly text. =cut sub FormatLink { my $self = shift; my %args = ( Object => undef, FallBack => '', @_ ); my $text = "URI " . $args{FallBack}; if ($args{Object} && $args{Object}->isa("RT::Ticket")) { $text = "Ticket " . $args{Object}->id; } return $text; } =head2 _AddLink Takes a paramhash of Type and one of Base or Target. Adds that link to this object. If Silent is true then no transactions will be recorded. You can individually control transactions on both base and target and with SilentBase and SilentTarget respectively. By default both transactions are created. If the link destination is a local object and does the L<RT::Record::Role::Status> role, this method ensures object Status is not "deleted". Linking to deleted objects is forbidden. If the link destination (i.e. not C<$self>) is a local object and the C<$StrictLinkACL> option is enabled, this method checks the appropriate right on the destination object (if any, as returned by the L</ModifyLinkRight> method). B<< The subclass is expected to check the appropriate right on the source object (i.e. C<$self>) before calling this method. >> This allows a different right to be used on the source object during creation, for example. Returns a tuple of (link ID, message, flag if link already existed). =cut sub _AddLink { my $self = shift; my %args = ( Target => '', Base => '', Type => '', Silent => undef, Silent => undef, SilentBase => undef, SilentTarget => undef, @_ ); # Remote_link is the URI of the object that is not this ticket my $remote_link; my $direction; if ( $args{'Base'} and $args{'Target'} ) { $RT::Logger->debug( "$self tried to create a link. both base and target were specified" ); return ( 0, $self->loc("Can't specify both base and target") ); } elsif ( $args{'Base'} ) { $args{'Target'} = $self->URI(); $remote_link = $args{'Base'}; $direction = 'Target'; } elsif ( $args{'Target'} ) { $args{'Base'} = $self->URI(); $remote_link = $args{'Target'}; $direction = 'Base'; } else { return ( 0, $self->loc('Either base or target must be specified') ); } my $remote_uri = RT::URI->new( $self->CurrentUser ); if ($remote_uri->FromURI( $remote_link )) { my $remote_obj = $remote_uri->IsLocal ? $remote_uri->Object : undef; if ($remote_obj and $remote_obj->id) { # Enforce the remote end of StrictLinkACL if (RT->Config->Get("StrictLinkACL")) { my $right = $remote_obj->ModifyLinkRight; return (0, $self->loc("Permission denied")) if $right and not $self->CurrentUser->HasRight( Right => $right, Object => $remote_obj ); } # Prevent linking to deleted objects if ($remote_obj->DOES("RT::Record::Role::Status") and $remote_obj->Status eq "deleted") { return (0, $self->loc("Linking to a deleted [_1] is not allowed", $self->loc(lc($remote_obj->RecordType)))); } } } else { return (0, $self->loc("Couldn't resolve '[_1]' into a link.", $remote_link)); } # Check if the link already exists - we don't want duplicates my $old_link = RT::Link->new( $self->CurrentUser ); $old_link->LoadByParams( Base => $args{'Base'}, Type => $args{'Type'}, Target => $args{'Target'} ); if ( $old_link->Id ) { $RT::Logger->debug("$self Somebody tried to duplicate a link"); return ( $old_link->id, $self->loc("Link already exists"), 1 ); } if ( $args{'Type'} =~ /^(?:DependsOn|MemberOf)$/ ) { my @tickets = $self->_AllLinkedTickets( LinkType => $args{'Type'}, Direction => $direction eq 'Target' ? 'Base' : 'Target', ); if ( grep { $_->id == ( $direction eq 'Target' ? $args{'Base'} : $args{'Target'} ) } @tickets ) { return ( 0, $self->loc("Refused to add link which would create a circular relationship") ); } } # Storing the link in the DB. my $link = RT::Link->new( $self->CurrentUser ); my ($linkid, $linkmsg) = $link->Create( Target => $args{Target}, Base => $args{Base}, Type => $args{Type} ); unless ($linkid) { $RT::Logger->error("Link could not be created: ".$linkmsg); return ( 0, $self->loc("Link could not be created: [_1]", $linkmsg) ); } my $basetext = $self->FormatLink(Object => $link->BaseObj, FallBack => $args{Base}); my $targettext = $self->FormatLink(Object => $link->TargetObj, FallBack => $args{Target}); my $typetext = $self->FormatType(Type => $args{Type}); my $TransString = "$basetext $typetext $targettext."; # No transactions for you! return ($linkid, $TransString) if $args{'Silent'}; my $opposite_direction = $direction eq 'Target' ? 'Base': 'Target'; # Some transactions? unless ( $args{ 'Silent'. $direction } ) { my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction( Type => 'AddLink', Field => $RT::Link::DIRMAP{$args{'Type'}}->{$direction}, NewValue => $remote_uri->URI || $remote_link, TimeTaken => 0 ); $RT::Logger->error("Couldn't create transaction: $Msg") unless $Trans; } if ( !$args{"Silent$opposite_direction"} && $remote_uri->IsLocal ) { my $OtherObj = $remote_uri->Object; my ( $val, $msg ) = $OtherObj->_NewTransaction( Type => 'AddLink', Field => $RT::Link::DIRMAP{$args{'Type'}}->{$opposite_direction}, NewValue => $self->URI, TimeTaken => 0, ); $RT::Logger->error("Couldn't create transaction: $msg") unless $val; } return ($linkid, $TransString); } =head2 _DeleteLink Takes a paramhash of Type and one of Base or Target. Removes that link from this object. If Silent is true then no transactions will be recorded. You can individually control transactions on both base and target and with SilentBase and SilentTarget respectively. By default both transactions are created. If the link destination (i.e. not C<$self>) is a local object and the C<$StrictLinkACL> option is enabled, this method checks the appropriate right on the destination object (if any, as returned by the L</ModifyLinkRight> method). B<< The subclass is expected to check the appropriate right on the source object (i.e. C<$self>) before calling this method. >> Returns a tuple of (status flag, message). =cut sub _DeleteLink { my $self = shift; my %args = ( Base => undef, Target => undef, Type => undef, Silent => undef, SilentBase => undef, SilentTarget => undef, @_ ); # We want one of base and target. We don't care which but we only want _one_. my $direction; my $remote_link; if ( $args{'Base'} and $args{'Target'} ) { $RT::Logger->debug("$self ->_DeleteLink. got both Base and Target"); return ( 0, $self->loc("Can't specify both base and target") ); } elsif ( $args{'Base'} ) { $args{'Target'} = $self->URI(); $remote_link = $args{'Base'}; $direction = 'Target'; } elsif ( $args{'Target'} ) { $args{'Base'} = $self->URI(); $remote_link = $args{'Target'}; $direction = 'Base'; } else { $RT::Logger->error("Base or Target must be specified"); return ( 0, $self->loc('Either base or target must be specified') ); } my $remote_uri = RT::URI->new( $self->CurrentUser ); if ($remote_uri->FromURI( $remote_link )) { # Enforce the remote end of StrictLinkACL my $remote_obj = $remote_uri->IsLocal ? $remote_uri->Object : undef; if ($remote_obj and $remote_obj->id and RT->Config->Get("StrictLinkACL")) { my $right = $remote_obj->ModifyLinkRight; return (0, $self->loc("Permission denied")) if $right and not $self->CurrentUser->HasRight( Right => $right, Object => $remote_obj ); } } else { return (0, $self->loc("Couldn't resolve '[_1]' into a link.", $remote_link)); } my $link = RT::Link->new( $self->CurrentUser ); $RT::Logger->debug( "Trying to load link: " . $args{'Base'} . " " . $args{'Type'} . " " . $args{'Target'} ); $link->LoadByParams( Base => $args{'Base'}, Type => $args{'Type'}, Target => $args{'Target'} ); unless ($link->id) { $RT::Logger->debug("Couldn't find that link"); return ( 0, $self->loc("Link not found") ); } my $basetext = $self->FormatLink(Object => $link->BaseObj, FallBack => $args{Base}); my $targettext = $self->FormatLink(Object => $link->TargetObj, FallBack => $args{Target}); my $typetext = $self->FormatType(Type => $args{Type}); my $TransString = "$basetext no longer $typetext $targettext."; my ($ok, $msg) = $link->Delete(); unless ($ok) { RT->Logger->error("Link could not be deleted: $msg"); return ( 0, $self->loc("Link could not be deleted: [_1]", $msg) ); } # No transactions for you! return (1, $TransString) if $args{'Silent'}; my $opposite_direction = $direction eq 'Target' ? 'Base': 'Target'; # Some transactions? unless ( $args{ 'Silent'. $direction } ) { my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction( Type => 'DeleteLink', Field => $RT::Link::DIRMAP{$args{'Type'}}->{$direction}, OldValue => $remote_uri->URI || $remote_link, TimeTaken => 0 ); $RT::Logger->error("Couldn't create transaction: $Msg") unless $Trans; } if ( !$args{"Silent$opposite_direction"} && $remote_uri->IsLocal ) { my $OtherObj = $remote_uri->Object; my ( $val, $msg ) = $OtherObj->_NewTransaction( Type => 'DeleteLink', Field => $RT::Link::DIRMAP{$args{'Type'}}->{$opposite_direction}, OldValue => $self->URI, TimeTaken => 0, ); $RT::Logger->error("Couldn't create transaction: $msg") unless $val; } return (1, $TransString); } =head1 LockForUpdate In a database transaction, gains an exclusive lock on the row, to prevent race conditions. On SQLite, this is a "RESERVED" lock on the entire database. =cut sub LockForUpdate { my $self = shift; my $pk = $self->_PrimaryKey; my $id = @_ ? $_[0] : $self->$pk; $self->_expire if $self->isa("DBIx::SearchBuilder::Record::Cachable"); if (RT->Config->Get('DatabaseType') eq "SQLite") { # SQLite does DB-level locking, upgrading the transaction to # "RESERVED" on the first UPDATE/INSERT/DELETE. Do a no-op # UPDATE to force the upgade. return RT->DatabaseHandle->dbh->do( "UPDATE " .$self->Table. " SET $pk = $pk WHERE 1 = 0"); } else { return $self->_LoadFromSQL( "SELECT * FROM ".$self->Table ." WHERE $pk = ? FOR UPDATE", $id, ); } } =head2 _NewTransaction PARAMHASH Private function to create a new RT::Transaction object for this ticket update =cut sub _NewTransaction { my $self = shift; my %args = ( TimeTaken => undef, Type => undef, OldValue => undef, NewValue => undef, OldReference => undef, NewReference => undef, ReferenceType => undef, Data => undef, Field => undef, MIMEObj => undef, ActivateScrips => 1, SquelchMailTo => undef, @_ ); my $in_txn = RT->DatabaseHandle->TransactionDepth; RT->DatabaseHandle->BeginTransaction unless $in_txn; $self->LockForUpdate; my $old_ref = $args{'OldReference'}; my $new_ref = $args{'NewReference'}; my $ref_type = $args{'ReferenceType'}; if ($old_ref or $new_ref) { $ref_type ||= ref($old_ref) || ref($new_ref); if (!$ref_type) { $RT::Logger->error("Reference type not specified for transaction"); return; } $old_ref = $old_ref->Id if ref($old_ref); $new_ref = $new_ref->Id if ref($new_ref); } require RT::Transaction; my $trans = RT::Transaction->new( $self->CurrentUser ); my ( $transaction, $msg ) = $trans->Create( ObjectId => $self->Id, ObjectType => ref($self), TimeTaken => $args{'TimeTaken'}, Type => $args{'Type'}, Data => $args{'Data'}, Field => $args{'Field'}, NewValue => $args{'NewValue'}, OldValue => $args{'OldValue'}, NewReference => $new_ref, OldReference => $old_ref, ReferenceType => $ref_type, MIMEObj => $args{'MIMEObj'}, ActivateScrips => $args{'ActivateScrips'}, DryRun => $self->{DryRun}, SquelchMailTo => $args{'SquelchMailTo'} || $self->{TransSquelchMailTo}, ); # Rationalize the object since we may have done things to it during the caching. $self->Load($self->Id); $RT::Logger->warning($msg) unless $transaction; $self->_SetLastUpdated; if ( defined $args{'TimeTaken'} and $self->can('_UpdateTimeTaken')) { $self->_UpdateTimeTaken( $args{'TimeTaken'}, Transaction => $trans ); } if ( RT->Config->Get('UseTransactionBatch') and $transaction ) { push @{$self->{_TransactionBatch}}, $trans; } RT->DatabaseHandle->Commit unless $in_txn; return ( $transaction, $msg, $trans ); } =head2 Transactions Returns an L<RT::Transactions> object of all transactions on this record object =cut sub Transactions { my $self = shift; my $transactions = RT::Transactions->new( $self->CurrentUser ); $transactions->Limit( FIELD => 'ObjectId', VALUE => $self->id, ); $transactions->Limit( FIELD => 'ObjectType', VALUE => ref($self), ); return $transactions; } =head2 SortedTransactions Returns the result of L</Transactions> ordered per the I<OldestTransactionsFirst> preference/option. Pass an optional value 'ASC' or 'DESC' to force a specific order. =cut sub SortedTransactions { my $self = shift; my $order = shift || 0; my $txns = $self->Transactions; if ( $order && ( $order eq 'ASC' || $order eq 'DESC' ) ) { # Use provided value } else { $order = RT->Config->Get("OldestTransactionsFirst", $self->CurrentUser) ? 'ASC' : 'DESC'; } $txns->OrderByCols( { FIELD => 'Created', ORDER => $order }, { FIELD => 'id', ORDER => $order }, ); return $txns; } our %TRANSACTION_CLASSIFICATION = ( Create => 'message', Correspond => 'message', Comment => 'message', AddWatcher => 'people', DelWatcher => 'people', Take => 'people', Untake => 'people', Force => 'people', Steal => 'people', Give => 'people', AddLink => 'links', DeleteLink => 'links', Status => 'basics', Set => { __default => 'basics', map( { $_ => 'dates' } qw( Told Starts Started Due LastUpdated Created LastUpdated ) ), map( { $_ => 'people' } qw( Owner Creator LastUpdatedBy ) ), }, CustomField => 'cfs', SystemError => 'error', AttachmentTruncate => 'attachment-truncate', AttachmentDrop => 'attachment-drop', AttachmentError => 'error', __default => 'other', ); sub ClassifyTransaction { my $self = shift; my $txn = shift; my $type = $txn->Type; my $res = $TRANSACTION_CLASSIFICATION{ $type }; return $res || $TRANSACTION_CLASSIFICATION{ '__default' } unless ref $res; return $res->{ $txn->Field } || $res->{'__default'} || $TRANSACTION_CLASSIFICATION{ '__default' }; } =head2 Attachments Returns an L<RT::Attachments> object of all attachments on this record object (for all its L</Transactions>). By default Content and Headers of attachments are not fetched right away from database. Use C<WithContent> and C<WithHeaders> options to override this. =cut sub Attachments { my $self = shift; my %args = ( WithHeaders => 0, WithContent => 0, @_ ); my @columns = grep { not /^(Headers|Content)$/ } RT::Attachment->ReadableAttributes; push @columns, 'Headers' if $args{'WithHeaders'}; push @columns, 'Content' if $args{'WithContent'}; my $res = RT::Attachments->new( $self->CurrentUser ); $res->Columns( @columns ); my $txn_alias = $res->TransactionAlias; $res->Limit( ALIAS => $txn_alias, FIELD => 'ObjectType', VALUE => ref($self), ); $res->Limit( ALIAS => $txn_alias, FIELD => 'ObjectId', VALUE => $self->id, ); return $res; } =head2 TextAttachments Returns an L<RT::Attachments> object of all attachments, like L<Attachments>, but only those that are text. By default Content and Headers are fetched. Use C<WithContent> and C<WithHeaders> options to override this. =cut sub TextAttachments { my $self = shift; my $res = $self->Attachments( WithHeaders => 1, WithContent => 1, @_ ); $res->Limit( FIELD => 'ContentType', OPERATOR => '=', VALUE => 'text/plain'); $res->Limit( FIELD => 'ContentType', OPERATOR => 'STARTSWITH', VALUE => 'message/'); $res->Limit( FIELD => 'ContentType', OPERATOR => '=', VALUE => 'text'); $res->Limit( FIELD => 'Filename', OPERATOR => 'IS', VALUE => 'NULL') if RT->Config->Get( 'SuppressInlineTextFiles', $self->CurrentUser ); return $res; } sub CustomFields { my $self = shift; my $cfs = RT::CustomFields->new( $self->CurrentUser ); $cfs->SetContextObject( $self ); # XXX handle multiple types properly $cfs->LimitToLookupType( $self->CustomFieldLookupType ); $cfs->LimitToGlobalOrObjectId( $self->CustomFieldLookupId ); $cfs->ApplySortOrder; return $cfs; } # TODO: This _only_ works for RT::Foo classes. it doesn't work, for # example, for RT::IR::Foo classes. sub CustomFieldLookupId { my $self = shift; my $lookup = shift || $self->CustomFieldLookupType; my @classes = ($lookup =~ /RT::(\w+)-/g); # Work on "RT::Queue", for instance return $self->Id unless @classes; my $object = $self; # Save a ->Load call by not calling ->FooObj->Id, just ->Foo my $final = shift @classes; foreach my $class (reverse @classes) { my $method = "${class}Obj"; $object = $object->$method; } my $id = $object->$final; unless (defined $id) { my $method = "${final}Obj"; $id = $object->$method->Id; } return $id; } =head2 CustomFieldLookupType Returns the path RT uses to figure out which custom fields apply to this object. =cut sub CustomFieldLookupType { my $self = shift; return ref($self) || $self; } =head2 AddCustomFieldValue { Field => FIELD, Value => VALUE } VALUE should be a string. FIELD can be any identifier of a CustomField supported by L</LoadCustomFieldByIdentifier> method. Adds VALUE as a value of CustomField FIELD. If this is a single-value custom field, deletes the old value. If VALUE is not a valid value for the custom field, returns (0, 'Error message' ) otherwise, returns ($id, 'Success Message') where $id is ID of created L<ObjectCustomFieldValue> object. =cut sub AddCustomFieldValue { my $self = shift; $self->_AddCustomFieldValue(@_); } sub _AddCustomFieldValue { my $self = shift; my %args = ( Field => undef, Value => undef, LargeContent => undef, ContentType => undef, RecordTransaction => 1, ForCreation => 0, @_ ); my $cf = $self->LoadCustomFieldByIdentifier($args{'Field'}); $cf->{include_set_initial} = 1 if $args{'ForCreation'}; unless ( $cf->Id ) { return ( 0, $self->loc( "Custom field [_1] not found", $args{'Field'} ) ); } my $OCFs = $self->CustomFields; $OCFs->Limit( FIELD => 'id', VALUE => $cf->Id ); unless ( $OCFs->Count ) { return ( 0, $self->loc( "Custom field [_1] does not apply to this object", ref $args{'Field'} ? $args{'Field'}->id : $args{'Field'} ) ); } # empty string is not correct value of any CF, so undef it foreach ( qw(Value LargeContent) ) { $args{ $_ } = undef if defined $args{ $_ } && !length $args{ $_ }; } unless ( $cf->ValidateValue( $args{'Value'} ) ) { return ( 0, $self->loc("Invalid value for custom field") ); } # If the custom field only accepts a certain # of values, delete the existing # value and record a "changed from foo to bar" transaction unless ( $cf->UnlimitedValues ) { # Load up a ObjectCustomFieldValues object for this custom field and this ticket my $values = $cf->ValuesForObject($self); # We need to whack any old values here. In most cases, the custom field should # only have one value to delete. In the pathalogical case, this custom field # used to be a multiple and we have many values to whack.... my $cf_values = $values->Count; if ( $cf_values > $cf->MaxValues ) { my $i = 0; #We want to delete all but the max we can currently have , so we can then # execute the same code to "change" the value from old to new while ( my $value = $values->Next ) { $i++; if ( $i < $cf_values ) { my ( $val, $msg ) = $cf->DeleteValueForObject( Object => $self, Id => $value->id, ); unless ($val) { return ( 0, $msg ); } my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction( Type => 'CustomField', Field => $cf->Id, OldReference => $value, ); } } $values->RedoSearch if $i; # redo search if have deleted at least one value } if ( my $entry = $values->HasEntry($args{'Value'}, $args{'LargeContent'}) ) { return $entry->id; } my $old_value = $values->First; my $old_content; $old_content = $old_value->Content if $old_value; my ( $new_value_id, $value_msg ) = $cf->AddValueForObject( Object => $self, Content => $args{'Value'}, LargeContent => $args{'LargeContent'}, ContentType => $args{'ContentType'}, ForCreation => $args{'ForCreation'}, ); unless ( $new_value_id ) { return ( 0, $self->loc( "Could not add new custom field value: [_1]", $value_msg ) ); } my $new_value = RT::ObjectCustomFieldValue->new( $self->CurrentUser ); $new_value->{include_set_initial} = 1 if $args{'ForCreation'}; $new_value->Load( $new_value_id ); if ( $args{'RecordTransaction'} ) { my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction( Type => 'CustomField', Field => $cf->Id, OldReference => $old_value, NewReference => $new_value, ); } my $new_content = $new_value->Content; # For datetime, we need to display them in "human" format in result message #XXX TODO how about date without time? if ($cf->Type eq 'DateTime') { my $DateObj = RT::Date->new( $self->CurrentUser ); $DateObj->Set( Format => 'ISO', Value => $new_content, ); $new_content = $DateObj->AsString; if ( defined $old_content && length $old_content ) { $DateObj->Set( Format => 'ISO', Value => $old_content, ); $old_content = $DateObj->AsString; } } unless ( defined $old_content && length $old_content ) { return ( $new_value_id, $self->loc( "[_1] [_2] added", $cf->Name, $new_content )); } elsif ( !defined $new_content || !length $new_content ) { return ( $new_value_id, $self->loc( "[_1] [_2] deleted", $cf->Name, $old_content ) ); } else { return ( $new_value_id, $self->loc( "[_1] [_2] changed to [_3]", $cf->Name, $old_content, $new_content)); } } # otherwise, just add a new value and record "new value added" else { my $values = $cf->ValuesForObject($self); if ( my $entry = $values->HasEntry($args{'Value'}, $args{'LargeContent'}) ) { return $entry->id; } my ($new_value_id, $msg) = $cf->AddValueForObject( Object => $self, Content => $args{'Value'}, LargeContent => $args{'LargeContent'}, ContentType => $args{'ContentType'}, ForCreation => $args{'ForCreation'}, ); unless ( $new_value_id ) { return ( 0, $self->loc( "Could not add new custom field value: [_1]", $msg ) ); } if ( $args{'RecordTransaction'} ) { my ( $tid, $msg ) = $self->_NewTransaction( Type => 'CustomField', Field => $cf->Id, NewReference => $new_value_id, ReferenceType => 'RT::ObjectCustomFieldValue', ); unless ( $tid ) { return ( 0, $self->loc( "Couldn't create a transaction: [_1]", $msg ) ); } } return ( $new_value_id, $self->loc( "[_1] added as a value for [_2]", $args{'Value'}, $cf->Name ) ); } } =head2 AddCustomFieldDefaultValues Add default values to object's empty custom fields. =cut sub AddCustomFieldDefaultValues { my $self = shift; my $cfs = $self->CustomFields; my @msgs; while ( my $cf = $cfs->Next ) { next if $self->CustomFieldValues($cf->id)->Count || !$cf->SupportDefaultValues; my ( $on ) = grep { $_->isa( $cf->RecordClassFromLookupType ) } $cf->ACLEquivalenceObjects; my $values = $cf->DefaultValues( Object => $on || RT->System ); foreach my $value ( UNIVERSAL::isa( $values => 'ARRAY' ) ? @$values : $values ) { next if $self->CustomFieldValueIsEmpty( Field => $cf, Value => $value, ); my ( $status, $msg ) = $self->_AddCustomFieldValue( Field => $cf->id, Value => $value, RecordTransaction => 0, ); push @msgs, $msg unless $status; } } return ( 0, @msgs ) if @msgs; return 1; } =head2 CustomFieldValueIsEmpty { Field => FIELD, Value => VALUE } Check if the custom field value is empty. Some custom fields could have other special empty values, e.g. "1970-01-01" is empty for Date cf Return 1 if it is empty, 0 otherwise. =cut sub CustomFieldValueIsEmpty { my $self = shift; my %args = ( Field => undef, Value => undef, @_ ); my $value = $args{Value}; return 1 unless defined $value && length $value; my $cf = ref($args{'Field'}) ? $args{'Field'} : $self->LoadCustomFieldByIdentifier( $args{'Field'} ); if ($cf) { if ( $cf->__Value('Type') =~ /^Date(?:Time)?$/ ) { my $DateObj = RT::Date->new( $self->CurrentUser ); $DateObj->Set( Format => 'unknown', Value => $value, $cf->Type eq 'Date' ? ( Timezone => 'UTC' ) : (), ); return 1 unless $DateObj->IsSet; } } return 0; } =head2 DeleteCustomFieldValue { Field => FIELD, Value => VALUE } Deletes VALUE as a value of CustomField FIELD. VALUE can be a string, a CustomFieldValue or a ObjectCustomFieldValue. If VALUE is not a valid value for the custom field, returns (0, 'Error message' ) otherwise, returns (1, 'Success Message') =cut sub DeleteCustomFieldValue { my $self = shift; my %args = ( Field => undef, Value => undef, ValueId => undef, @_ ); my $cf = $self->LoadCustomFieldByIdentifier($args{'Field'}); unless ( $cf->Id ) { return ( 0, $self->loc( "Custom field [_1] not found", $args{'Field'} ) ); } my ( $val, $msg ) = $cf->DeleteValueForObject( Object => $self, Id => $args{'ValueId'}, Content => $args{'Value'}, ); unless ($val) { return ( 0, $msg ); } my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction( Type => 'CustomField', Field => $cf->Id, OldReference => $val, ReferenceType => 'RT::ObjectCustomFieldValue', ); unless ($TransactionId) { return ( 0, $self->loc( "Couldn't create a transaction: [_1]", $Msg ) ); } my $old_value = $TransactionObj->OldValue; # For datetime, we need to display them in "human" format in result message if ( $cf->Type eq 'DateTime' ) { my $DateObj = RT::Date->new( $self->CurrentUser ); $DateObj->Set( Format => 'ISO', Value => $old_value, ); $old_value = $DateObj->AsString; } return ( $TransactionId, $self->loc( "[_1] is no longer a value for custom field [_2]", $old_value, $cf->Name ) ); } =head2 FirstCustomFieldValue FIELD Return the content of the first value of CustomField FIELD for this ticket Takes a field id or name =cut sub FirstCustomFieldValue { my $self = shift; my $field = shift; my $values = $self->CustomFieldValues( $field ); return undef unless my $first = $values->First; return $first->Content; } =head2 CustomFieldValuesAsString FIELD Return the content of the CustomField FIELD for this ticket. If this is a multi-value custom field, values will be joined with newlines. Takes a field id or name as the first argument Takes an optional Separator => "," second and third argument if you want to join the values using something other than a newline =cut sub CustomFieldValuesAsString { my $self = shift; my $field = shift; my %args = @_; my $separator = $args{Separator} || "\n"; my $values = $self->CustomFieldValues( $field ); return join ($separator, grep { defined $_ } map { $_->Content } @{$values->ItemsArrayRef}); } =head2 CustomFieldValues FIELD Return a ObjectCustomFieldValues object of all values of the CustomField whose id or Name is FIELD for this record. Returns an RT::ObjectCustomFieldValues object =cut sub CustomFieldValues { my $self = shift; my $field = shift; if ( $field ) { my $cf = $self->LoadCustomFieldByIdentifier( $field ); # we were asked to search on a custom field we couldn't find unless ( $cf->id ) { $RT::Logger->warning("Couldn't load custom field by '$field' identifier"); return RT::ObjectCustomFieldValues->new( $self->CurrentUser ); } return ( $cf->ValuesForObject($self) ); } # we're not limiting to a specific custom field; my $ocfs = RT::ObjectCustomFieldValues->new( $self->CurrentUser ); $ocfs->LimitToObject( $self ); return $ocfs; } =head2 LoadCustomFieldByIdentifier IDENTIFER Find the custom field has id or name IDENTIFIER for this object. If no valid field is found, returns an empty RT::CustomField object. =cut sub LoadCustomFieldByIdentifier { my $self = shift; my $field = shift; my $cf; if ( UNIVERSAL::isa( $field, "RT::CustomField" ) ) { $cf = RT::CustomField->new($self->CurrentUser); $cf->SetContextObject( $self ); $cf->LoadById( $field->id ); } elsif ($field =~ /^\d+$/) { $cf = RT::CustomField->new($self->CurrentUser); $cf->SetContextObject( $self ); $cf->LoadById($field); } else { my $cfs = $self->CustomFields($self->CurrentUser); $cfs->SetContextObject( $self ); $cfs->Limit(FIELD => 'Name', VALUE => $field, CASESENSITIVE => 0); $cf = $cfs->First || RT::CustomField->new($self->CurrentUser); } return $cf; } sub ACLEquivalenceObjects { } =head2 HasRight Takes a paramhash with the attributes 'Right' and 'Principal' 'Right' is a ticket-scoped textual right from RT::ACE 'Principal' is an RT::User object Returns 1 if the principal has the right. Returns undef if not. =cut sub HasRight { my $self = shift; my %args = ( Right => undef, Principal => undef, @_ ); $args{Principal} ||= $self->CurrentUser->PrincipalObj; return $args{'Principal'}->HasRight( Object => $self->Id ? $self : $RT::System, Right => $args{'Right'} ); } sub CurrentUserHasRight { my $self = shift; return $self->HasRight( Right => @_ ); } sub ModifyLinkRight { } =head2 ColumnMapClassName ColumnMap needs a massaged collection class name to load the correct list display. Equivalent to L<RT::SearchBuilder/ColumnMapClassName>, but provided for a record instead of a collection. Returns a string. May be called as a package method. =cut sub ColumnMapClassName { my $self = shift; my $Class = ref($self) || $self; $Class =~ s/:/_/g; return $Class; } sub BasicColumns { } sub WikiBase { return RT->Config->Get('WebPath'). "/index.html?q="; } # Matches one field in "field - field" style range specs. Subclasses # that can participate in custom date ranges should override this method # to match their additional date fields. Be sure to call this superclass # method to get "now", datetime columns and CF parsing. sub _CustomDateRangeFieldParser { my $self = shift; my $regex = qr{ now | cf\. (?: \{ .*? \} | \S+ ) }xi; for my $column ( keys %{ $_TABLE_ATTR->{ ref $self || $self} } ) { my $entry = $_TABLE_ATTR->{ ref $self || $self}{$column}; next unless $entry->{read} && ( $entry->{type} // '' ) eq 'datetime'; $regex .= '|' . qr{$column}i; } return $regex; } # Returns an RT::Date instantiated with this record's value for the parsed # field name. Includes the $range_name parameter only for diagnostics. # Subclasses should override this to instantiate the fields they added in # _CustomDateRangeFieldParser. sub _DateForCustomDateRangeField { my $self = shift; my $field = shift; my $range_name = shift; my $date = RT::Date->new($self->CurrentUser); if (lc($field) eq 'now') { $date->Set(Format => 'unix', Value => time); } elsif ($field =~ m{^ cf\. (?: \{ (.*?) \} | (\S+) ) $}xi) { my $name = $1 || $2; my $value = $self->FirstCustomFieldValue($name); if (!$value) { # no CF value for this record, so bail out return; } $date->Set(Format => 'unknown', Value => $value, Timezone => 'UTC'); } else { if ( my ($column) = grep { lc $field eq lc $_ } keys %{ $_TABLE_ATTR->{ ref $self || $self } } ) { my $method = $column . 'Obj'; if ( $self->can($method) ) { $date = $self->$method; } else { RT->Logger->error( "Missing $method in " . ref $self ); return; } } else { RT->Logger->error("Unable to parse '$field' as a field name in CustomDateRanges '$range_name'"); return; } } return $date; } # Parses custom date range spec and returns a hash containing parsed info. # Returns the empty list if there's an error. sub _ParseCustomDateRangeSpec { my $self = shift; my $name = shift; my $spec = shift; my $calculation; my $format; if (ref($spec)) { $calculation = $spec->{value} || join( ' - ', $spec->{to}, $spec->{from} ); $format = $spec->{format}; } else { $calculation = $spec; } if (!$calculation || ref($calculation)) { RT->Logger->error("CustomDateRanges '$name' 'value' must be a string"); return; } if ($format && ref($format) ne 'CODE') { RT->Logger->error("CustomDateRanges '$name' 'format' must be a CODE reference"); return; } # class-specific matcher for now, created, CF.{foo bar}, CF.baz, etc. my $field_parser = $self->_CustomDateRangeFieldParser; # regex parses "field - field" (intentionally very strict) my $calculation_parser = qr{ ^ ($field_parser) # to field name \s+ - \s+ # space, operator, more space ($field_parser) # from field name $ }x; my @matches = $calculation =~ $calculation_parser; if (!@matches) { RT->Logger->error("Unable to parse '$calculation' as a calculated value in CustomDateRanges '$name'"); return; } if ( ref $spec ) { for my $type ( qw/from to/ ) { if ( $spec->{"${type}_fallback"} && $spec->{"${type}_fallback"} !~ /^$field_parser$/ ) { RT->Logger->error( "Invalid ${type}_fallback field: " . $spec->{"${type}_fallback"} ); return; } } } my %date_range_spec = ( from => $matches[1], to => $matches[0], ref $spec ? %$spec : () ); return %date_range_spec; } =head2 CustomDateRange name, spec Takes a L<RT_Config/%CustomDateRanges>-style spec string and its name (for diagnostics). Returns a localized string evaluating the calculation. If either date is unset, or anything fails to parse, this returns C<undef>. =cut sub CustomDateRange { my $self = shift; my $name = shift; my $spec = shift; my %date_range_spec = $self->_ParseCustomDateRangeSpec($name, $spec); # parse failed; render no value return unless $date_range_spec{from} && $date_range_spec{to}; my $end_dt = $self->_DateForCustomDateRangeField($date_range_spec{to}, $name); my $start_dt = $self->_DateForCustomDateRangeField($date_range_spec{from}, $name); unless ( $start_dt && $start_dt->IsSet ) { if ( ref $spec && $date_range_spec{from_fallback} ) { $start_dt = $self->_DateForCustomDateRangeField( $date_range_spec{from_fallback}, $name ); } } unless ( $end_dt && $end_dt->IsSet ) { if ( ref $spec && $date_range_spec{to_fallback} ) { $end_dt = $self->_DateForCustomDateRangeField( $date_range_spec{to_fallback}, $name ); } } # RT::Date instantiation failed; render no value return unless $start_dt && $start_dt->IsSet && $end_dt && $end_dt->IsSet; my $duration; if ( $date_range_spec{business_time} ) { my $schedule; my $timezone; # Prefer the schedule/timezone specified in %ServiceAgreements for current object if ( $self->isa('RT::Ticket') && !$self->QueueObj->SLADisabled && $self->SLA ) { if ( my $config = RT->Config->Get('ServiceAgreements') ) { if ( ref( $config->{QueueDefault}{ $self->QueueObj->Name } ) eq 'HASH' ) { $timezone = $config->{QueueDefault}{ $self->QueueObj->Name }{Timezone}; } # Each SLA could have its own schedule and timezone if ( my $agreement = $config->{Levels}{ $self->SLA } ) { $schedule = $agreement->{BusinessHours}; $timezone ||= $agreement->{Timezone}; } } } $timezone ||= RT->Config->Get('Timezone'); $schedule ||= 'Default'; { local $ENV{'TZ'} = $ENV{'TZ'}; if ( $timezone ne ( $ENV{'TZ'} || '' ) ) { $ENV{'TZ'} = $timezone; require POSIX; POSIX::tzset(); } my $bhours = RT::SLA->BusinessHours($schedule); $duration = $bhours->between( $start_dt->Unix <= $end_dt->Unix ? ( $start_dt->Unix, $end_dt->Unix ) : ( $end_dt->Unix, $start_dt->Unix ) ); $duration *= -1 if $start_dt->Unix > $end_dt->Unix; } if ( $timezone ne ( $ENV{'TZ'} || '' ) ) { POSIX::tzset(); } } $duration //= $end_dt->Diff($start_dt); # _ParseCustomDateRangeSpec guarantees $format is a coderef if ($date_range_spec{format}) { return $date_range_spec{format}->($duration, $end_dt, $start_dt, $self); } else { my $max_unit = $date_range_spec{business_time} ? 'hour' : 'year'; # "x days ago" is strongly suggestive of comparing with the current # time; but if we're comparing two arbitrary times, "x days prior" # reads better if ($duration < 0) { $duration *= -1; return $self->loc('[_1] prior', $end_dt->DurationAsString($duration, MaxUnit => $max_unit)); } else { return $end_dt->DurationAsString($duration, MaxUnit => $max_unit); } } } =head2 CustomDateRanges Return all of the custom date ranges of current class. =cut sub CustomDateRanges { my $self = shift; my %args = ( Type => undef, ExcludeSystem => undef, ExcludeUsers => undef, ExcludeUser => undef, @_, ); my $type = $args{Type} || ref $self || $self,; my %ranges; if ( !$args{ExcludeSystem} ) { if ( my $config = RT->Config->Get('CustomDateRanges') ) { for my $name ( keys %{ $config->{$type} || {} } ) { $ranges{$name} ||= $config->{$type}{$name}; } } if ( my $db_config = RT->Config->Get('CustomDateRangesUI') ) { for my $name ( keys %{ $db_config->{$type} || {} } ) { $ranges{$name} ||= $db_config->{$type}{$name}; } } } if ( !$args{ExcludeUsers} ) { my $attributes = RT::Attributes->new( RT->SystemUser ); $attributes->Limit( FIELD => 'Name', VALUE => 'Pref-CustomDateRanges' ); $attributes->Limit( FIELD => 'ObjectType', VALUE => 'RT::User' ); if ( $args{ExcludeUser} ) { $attributes->Limit( FIELD => 'Creator', OPERATOR => '!=', VALUE => $args{ExcludeUser} ); } $attributes->OrderBy( FIELD => 'id' ); while ( my $attribute = $attributes->Next ) { if ( my $content = $attribute->Content ) { for my $name ( keys %{ $content->{$type} || {} } ) { $ranges{$name} ||= $content->{$type}{$name}; } } } } return %ranges; } =head2 CustomDateRangeFields Return all of the fields custom date range could use for current class. =cut sub CustomDateRangeFields { my $self = shift; my $type = ref $self || $self; my @fields = 'now'; for my $column ( keys %{ $_TABLE_ATTR->{ ref $self || $self } } ) { my $entry = $_TABLE_ATTR->{ ref $self || $self }{$column}; next unless $entry->{read} && ( $entry->{type} // '' ) eq 'datetime'; push @fields, $column; } my $cfs = RT::CustomFields->new( ref $self ? $self->CurrentUser : RT->SystemUser ); $cfs->Limit( FIELD => 'Type', VALUE => [ 'Date', 'DateTime' ], OPERATOR => 'IN' ); while ( my $cf = $cfs->Next ) { push @fields, 'CF.{' . $cf->Name . '}'; } return sort { lc $a cmp lc $b } @fields; } sub UID { my $self = shift; return undef unless defined $self->Id; return "@{[ref $self]}-$RT::Organization-@{[$self->Id]}"; } sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; for my $col (qw/Creator LastUpdatedBy/) { if ( $self->_Accessible( $col, 'read' ) ) { next unless $self->$col; my $obj = RT::Principal->new( $self->CurrentUser ); $obj->Load( $self->$col ); $deps->Add( out => $obj->Object ); } } # Object attributes, we have to check on every object my $objs = $self->Attributes; $deps->Add( in => $objs ); # Transactions if ( $self->isa("RT::Ticket") or $self->isa("RT::User") or $self->isa("RT::Group") or $self->isa("RT::Article") or $self->isa("RT::Asset") or $self->isa("RT::Catalog") or $self->isa("RT::Queue") ) { $objs = RT::Transactions->new( $self->CurrentUser ); $objs->Limit( FIELD => 'ObjectType', VALUE => ref $self ); $objs->Limit( FIELD => 'ObjectId', VALUE => $self->id ); $deps->Add( in => $objs ); } # Object custom field values if (( $self->isa("RT::Transaction") or $self->isa("RT::Ticket") or $self->isa("RT::User") or $self->isa("RT::Group") or $self->isa("RT::Asset") or $self->isa("RT::Queue") or $self->isa("RT::Article") ) and $self->can("CustomFieldValues") ) { $objs = $self->CustomFieldValues; # Actually OCFVs $objs->{find_disabled_rows} = 1; $deps->Add( in => $objs ); } # ACE records if ( $self->isa("RT::Group") or $self->isa("RT::Class") or $self->isa("RT::Queue") or $self->isa("RT::CustomField") ) { $objs = RT::ACL->new( $self->CurrentUser ); $objs->LimitToObject( $self ); $deps->Add( in => $objs ); } } sub Serialize { my $self = shift; my %args = ( Methods => {}, UIDs => 1, @_, ); my %methods = ( Creator => "CreatorObj", LastUpdatedBy => "LastUpdatedByObj", %{ $args{Methods} || {} }, ); my %values = %{$self->{values}}; my %ca = %{ $self->_ClassAccessible }; my @cols = grep {exists $values{lc $_} and defined $values{lc $_}} keys %ca; my %store; $store{$_} = $values{lc $_} for @cols; $store{id} = $values{id}; # Explicitly necessary in some cases # Un-apply the _transfer_ encoding, but don't mess with the octets # themselves. Calling ->Content directly would, in some cases, # decode from some mostly-unknown character set -- which reversing # on the far end would be complicated. if ($ca{ContentEncoding} and $ca{ContentType}) { my ($content_col) = grep {exists $ca{$_}} qw/LargeContent Content/; $store{$content_col} = $self->_DecodeLOB( "application/octet-stream", # Lie so that we get bytes, not characters $self->ContentEncoding, $self->_Value( $content_col, decode_utf8 => 0 ) ); delete $store{ContentEncoding}; } return %store unless $args{UIDs}; # Use FooObj to turn Foo into a reference to the UID for my $col ( grep {$store{$_}} @cols ) { my $method = $methods{$col}; if (not $method) { $method = $col; $method =~ s/(Id)?$/Obj/; } next unless $self->can($method); my $obj = $self->$method; next unless $obj and $obj->isa("RT::Record"); $store{$col} = \($obj->UID); } # Anything on an object should get the UID stored instead if ($store{ObjectType} and $store{ObjectId} and $self->can("Object")) { delete $store{$_} for qw/ObjectType ObjectId/; $store{Object} = \($self->Object->UID); } return %store; } sub PreInflate { my $class = shift; my ($importer, $uid, $data) = @_; my $ca = $class->_ClassAccessible; my %ca = %{ $ca }; if ($ca{ContentEncoding} and $ca{ContentType}) { my ($content_col) = grep {exists $ca{$_}} qw/LargeContent Content/; if (defined $data->{$content_col}) { my ($ContentEncoding, $Content) = $class->_EncodeLOB( $data->{$content_col}, $data->{ContentType}, ); $data->{ContentEncoding} = $ContentEncoding; $data->{$content_col} = $Content; } } if ($data->{Object} and not $ca{Object}) { my $ref_uid = ${ delete $data->{Object} }; my $ref = $importer->Lookup( $ref_uid ); if ($ref) { my ($class, $id) = @{$ref}; $data->{ObjectId} = $id; $data->{ObjectType} = $class; } else { $data->{ObjectId} = 0; $data->{ObjectType} = ""; $importer->Postpone( for => $ref_uid, uid => $uid, column => "ObjectId", classcolumn => "ObjectType", ); } } for my $col (keys %{$data}) { if (ref $data->{$col}) { my $ref_uid = ${ $data->{$col} }; my $ref = $importer->Lookup( $ref_uid ); if ($ref) { my (undef, $id) = @{$ref}; $data->{$col} = $id; } else { $data->{$col} = 0; $importer->Postpone( for => $ref_uid, uid => $uid, column => $col, ); } } } return 1; } sub PostInflate { } =head2 _AsInsertQuery Returns INSERT query string that duplicates current record and can be used to insert record back into DB after delete. =cut sub _AsInsertQuery { my $self = shift; my $dbh = $RT::Handle->dbh; my $res = "INSERT INTO ". $dbh->quote_identifier( $self->Table ); my $values = $self->{'values'}; $res .= "(". join( ",", map { $dbh->quote_identifier( $_ ) } sort keys %$values ) .")"; $res .= " VALUES"; $res .= "(". join( ",", map { $dbh->quote( $values->{$_} ) } sort keys %$values ) .")"; $res .= ";"; return $res; } sub BeforeWipeout { return 1 } =head2 Dependencies Returns L<RT::Shredder::Dependencies> object. =cut sub Dependencies { my $self = shift; my %args = ( Shredder => undef, Flags => RT::Shredder::Constants::DEPENDS_ON, @_, ); unless( $self->id ) { RT::Shredder::Exception->throw('Object is not loaded'); } my $deps = RT::Shredder::Dependencies->new(); if( $args{'Flags'} & RT::Shredder::Constants::DEPENDS_ON ) { $self->__DependsOn( %args, Dependencies => $deps ); } return $deps; } sub __DependsOn { my $self = shift; my %args = ( Shredder => undef, Dependencies => undef, @_, ); my $deps = $args{'Dependencies'}; my $list = []; # Object custom field values my $objs = $self->CustomFieldValues; $objs->{'find_disabled_rows'} = 1; push( @$list, $objs ); # Object attributes $objs = $self->Attributes; push( @$list, $objs ); # Transactions $objs = RT::Transactions->new( $self->CurrentUser ); $objs->Limit( FIELD => 'ObjectType', VALUE => ref $self ); $objs->Limit( FIELD => 'ObjectId', VALUE => $self->id ); push( @$list, $objs ); if ( $self->isa( 'RT::CustomField' ) ) { $objs = RT::Transactions->new( $self->CurrentUser ); $objs->Limit( FIELD => 'Type', VALUE => 'CustomField' ); $objs->Limit( FIELD => 'Field', VALUE => $self->id ); push( @$list, $objs ); } # Links if ( $self->can('Links') ) { # make sure we don't skip any record no warnings 'redefine'; local *RT::Links::IsValidLink = sub { 1 }; foreach ( qw(Base Target) ) { my $objs = $self->Links( $_ ); $objs->_DoSearch; push @$list, $objs->ItemsArrayRef; } } # ACE records $objs = RT::ACL->new( $self->CurrentUser ); $objs->LimitToObject( $self ); push( @$list, $objs ); $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON, TargetObjects => $list, Shredder => $args{'Shredder'} ); return; } # implement proxy method because some RT classes # override Delete method sub __Wipeout { my $self = shift; my $msg = $self->UID ." wiped out"; $self->SUPER::Delete; $RT::Logger->info( $msg ); return; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Plugin.pm���������������������������������������������������������������������������000644 �000765 �000024 �00000007262 14005011336 015677� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Plugin; use File::ShareDir; =head1 NAME RT::Plugin =head1 METHODS =head2 new Instantiate a new L<RT::Plugin> object. Takes a paramhash. currently the only key it cares about is 'name', the name of this plugin. =cut sub new { my $class = shift; my $args ={@_}; my $self = bless $args, $class; return $self; } =head2 Name Returns a human-readable name for this plugin. =cut sub Name { my $self = shift; return $self->{name}; } =head2 Version Returns the extension version. =cut sub Version { my $self = shift; no strict 'refs'; return ${$self->Name . '::VERSION'}; } =head2 Path Takes a name of sub directory and returns its full path, for example: my $plugin_etc_dir = $plugin->Path('etc'); See also L</ComponentRoot>, L</StaticDir>, L</PoDir> and other shortcut methods. =cut sub Path { my $self = shift; my $subdir = shift; my $res = $self->_BasePath; $res .= "/$subdir" if defined $subdir && length $subdir; return $res; } sub _BasePath { my $self = shift; my $base = $self->{'name'}; $base =~ s/::/-/g; my $local_base = $RT::LocalPluginPath."/".$base; my $base_base = $RT::PluginPath."/".$base; return -d $local_base ? $local_base : $base_base; } =head2 ComponentRoot Returns the directory this plugin has installed its L<HTML::Mason> templates into =cut sub ComponentRoot { return $_[0]->Path('html') } =head2 StaticDir Returns the directory this plugin has installed its static files into =cut sub StaticDir { return $_[0]->Path('static') } =head2 PoDir Returns the directory this plugin has installed its message catalogs into. =cut sub PoDir { return $_[0]->Path('po') } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Template.pm�������������������������������������������������������������������������000644 �000765 �000024 �00000071622 14005011336 016215� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} # Portions Copyright 2000 Tobias Brox <tobix@cpan.org> =head1 NAME RT::Template - RT's template object =head1 SYNOPSIS use RT::Template; =head1 DESCRIPTION =head1 METHODS =cut package RT::Template; use strict; use warnings; use base 'RT::Record'; use RT::Queue; use Text::Template; use MIME::Entity; use MIME::Parser; use Scalar::Util 'blessed'; use RT::Interface::Web; sub _Accessible { my $self = shift; my %Cols = ( id => 'read', Name => 'read/write', Description => 'read/write', Type => 'read/write', #Type is one of Perl or Simple Content => 'read/write', Queue => 'read/write', Creator => 'read/auto', Created => 'read/auto', LastUpdatedBy => 'read/auto', LastUpdated => 'read/auto' ); return $self->SUPER::_Accessible( @_, %Cols ); } sub _Set { my $self = shift; my %args = ( Field => undef, Value => undef, @_, ); unless ( $self->CurrentUserHasQueueRight('ModifyTemplate') ) { return ( 0, $self->loc('Permission Denied') ); } if (exists $args{Value}) { if ($args{Field} eq 'Queue') { if ($args{Value}) { # moving to another queue my $queue = RT::Queue->new( $self->CurrentUser ); $queue->Load($args{Value}); unless ($queue->Id and $queue->CurrentUserHasRight('ModifyTemplate')) { return ( 0, $self->loc('Permission Denied') ); } } else { # moving to global unless ($self->CurrentUser->HasRight( Object => RT->System, Right => 'ModifyTemplate' )) { return ( 0, $self->loc('Permission Denied') ); } } } } return $self->SUPER::_Set( @_ ); } =head2 _Value Takes the name of a table column. Returns its value as a string, if the user passes an ACL check, otherwise returns undef. =cut sub _Value { my $self = shift; unless ( $self->CurrentUserCanRead() ) { return undef; } return $self->__Value( @_ ); } =head2 Load <identifier> Load a template, either by number or by name. Note that loading templates by name using this method B<is ambiguous>. Several queues may have template with the same name and as well global template with the same name may exist. Use L</LoadByName>, L</LoadGlobalTemplate> or L<LoadQueueTemplate> to get precise result. =cut sub Load { my $self = shift; my $identifier = shift; return undef unless $identifier; if ( $identifier =~ /\D/ ) { return $self->LoadByCol( 'Name', $identifier ); } return $self->LoadById( $identifier ); } =head2 LoadByName Takes Name and Queue arguments. Tries to load queue specific template first, then global. If Queue argument is omitted then global template is tried, not template with the name in any queue. =cut sub LoadByName { my $self = shift; my %args = ( Queue => undef, Name => undef, @_ ); my $queue = $args{'Queue'}; if ( blessed $queue ) { $queue = $queue->id; } elsif ( defined $queue and $queue =~ /\D/ ) { my $tmp = RT::Queue->new( $self->CurrentUser ); $tmp->Load($queue); $queue = $tmp->id; } return $self->LoadGlobalTemplate( $args{'Name'} ) unless $queue; $self->LoadQueueTemplate( Queue => $queue, Name => $args{'Name'} ); return $self->id if $self->id; return $self->LoadGlobalTemplate( $args{'Name'} ); } =head2 LoadGlobalTemplate NAME Load the global template with the name NAME =cut sub LoadGlobalTemplate { my $self = shift; my $name = shift; return ( $self->LoadQueueTemplate( Queue => 0, Name => $name ) ); } =head2 LoadQueueTemplate (Queue => QUEUEID, Name => NAME) Loads the Queue template named NAME for Queue QUEUE. Note that this method doesn't load a global template with the same name if template in the queue doesn't exist. Use L</LoadByName>. =cut sub LoadQueueTemplate { my $self = shift; my %args = ( Queue => undef, Name => undef, @_ ); return ( $self->LoadByCols( Name => $args{'Name'}, Queue => $args{'Queue'} ) ); } =head2 Create Takes a paramhash of Content, Queue, Name and Description. Name should be a unique string identifying this Template. Description and Content should be the template's title and content. Queue should be 0 for a global template and the queue # for a queue-specific template. Returns the Template's id # if the create was successful. Returns undef for unknown database failure. =cut sub Create { my $self = shift; my %args = ( Content => undef, Queue => 0, Description => '[no description]', Type => 'Perl', Name => undef, @_ ); if ( $args{Type} eq 'Perl' && !$self->CurrentUser->HasRight(Right => 'ExecuteCode', Object => $RT::System) ) { return ( undef, $self->loc('Permission Denied') ); } unless ( $args{'Queue'} ) { unless ( $self->CurrentUser->HasRight(Right =>'ModifyTemplate', Object => $RT::System) ) { return ( undef, $self->loc('Permission Denied') ); } $args{'Queue'} = 0; } else { my $QueueObj = RT::Queue->new( $self->CurrentUser ); $QueueObj->Load( $args{'Queue'} ) || return ( undef, $self->loc('Invalid queue') ); unless ( $QueueObj->CurrentUserHasRight('ModifyTemplate') ) { return ( undef, $self->loc('Permission Denied') ); } $args{'Queue'} = $QueueObj->Id; } return ( undef, $self->loc('Name is required') ) unless $args{Name}; { my $tmp = $self->new( RT->SystemUser ); $tmp->LoadByCols( Name => $args{'Name'}, Queue => $args{'Queue'} ); return ( undef, $self->loc('A Template with that name already exists') ) if $tmp->id; } my ( $result, $msg ) = $self->SUPER::Create( Content => $args{'Content'}, Queue => $args{'Queue'}, Description => $args{'Description'}, Name => $args{'Name'}, Type => $args{'Type'}, ); if ( wantarray ) { return ( $result, $msg ); } else { return ( $result ); } } =head2 Delete Delete this template. =cut sub Delete { my $self = shift; unless ( $self->CurrentUserHasQueueRight('ModifyTemplate') ) { return ( 0, $self->loc('Permission Denied') ); } if ( !$self->IsOverride && $self->UsedBy->Count ) { return ( 0, $self->loc('Template is in use') ); } return ( $self->SUPER::Delete(@_) ); } =head2 UsedBy Returns L<RT::Scrips> limitted to scrips that use this template. Takes into account that template can be overriden in a queue. =cut sub UsedBy { my $self = shift; my $scrips = RT::Scrips->new( $self->CurrentUser ); $scrips->LimitByTemplate( $self ); return $scrips; } =head2 IsEmpty Returns true value if content of the template is empty, otherwise returns false. =cut sub IsEmpty { my $self = shift; my $content = $self->Content; return 0 if defined $content && length $content; return 1; } =head2 IsOverride Returns true if it's queue specific template and there is global template with the same name. =cut sub IsOverride { my $self = shift; return 0 unless $self->Queue; my $template = RT::Template->new( $self->CurrentUser ); $template->LoadGlobalTemplate( $self->Name ); return $template->id; } =head2 MIMEObj Returns L<MIME::Entity> object parsed using L</Parse> method. Returns undef if last call to L</Parse> failed or never be called. Note that content of the template is characters, but the contents of all L<MIME::Entity> objects (including the one returned by this function, are bytes in UTF-8. =cut sub MIMEObj { my $self = shift; return ( $self->{'MIMEObj'} ); } =head2 Parse This routine performs L<Text::Template> parsing on the template and then imports the results into a L<MIME::Entity> so we can really use it. Use L</MIMEObj> method to get the L<MIME::Entity> object. Takes a hash containing Argument, TicketObj, and TransactionObj and other arguments that will be available in the template's code. TicketObj and TransactionObj are not mandatory, but highly recommended. It returns a tuple of (val, message). If val is false, the message contains an error message. =cut sub Parse { my $self = shift; my ($rv, $msg); if (not $self->IsEmpty and $self->Content =~ m{^Content-Type:\s+text/html\b}im) { local $RT::Transaction::PreferredContentType = 'text/html'; ($rv, $msg) = $self->_Parse(@_); } else { ($rv, $msg) = $self->_Parse(@_); } return ($rv, $msg) unless $rv; my $mime_type = $self->MIMEObj->mime_type; if (defined $mime_type and $mime_type eq 'text/html') { $self->_DowngradeFromHTML(@_); } return ($rv, $msg); } sub _Parse { my $self = shift; # clear prev MIME object $self->{'MIMEObj'} = undef; #We're passing in whatever we were passed. it's destined for _ParseContent my ($content, $msg) = $self->_ParseContent(@_); return ( 0, $msg ) unless defined $content && length $content; if ( $content =~ /^\S/s && $content !~ /^\S+:/ ) { $RT::Logger->error( "Template #". $self->id ." has leading line that doesn't" ." look like header field, if you don't want to override" ." any headers and don't want to see this error message" ." then leave first line of the template empty" ); $content = "\n".$content; } my $parser = MIME::Parser->new(); $parser->output_to_core(1); $parser->tmp_to_core(1); $parser->use_inner_files(1); ### Should we forgive normally-fatal errors? $parser->ignore_errors(1); # Always provide bytes, not characters, to MIME objects $content = Encode::encode( 'UTF-8', $content ); $self->{'MIMEObj'} = eval { $parser->parse_data( \$content ) }; if ( my $error = $@ || $parser->last_error ) { $RT::Logger->error( "$error" ); return ( 0, $error ); } # Unfold all headers $self->{'MIMEObj'}->head->unfold; $self->{'MIMEObj'}->head->modify(1); return ( 1, $self->loc("Template parsed") ); } # Perform Template substitutions on the template sub _ParseContent { my $self = shift; my %args = ( Argument => undef, TicketObj => undef, TransactionObj => undef, @_ ); unless ( $self->CurrentUserCanRead() ) { return (undef, $self->loc("Permission Denied")); } if ( $self->IsEmpty ) { return ( undef, $self->loc("Template is empty") ); } my $content = $self->SUPER::_Value('Content'); $args{'Ticket'} = delete $args{'TicketObj'} if $args{'TicketObj'}; $args{'Transaction'} = delete $args{'TransactionObj'} if $args{'TransactionObj'}; $args{'Requestor'} = eval { $args{'Ticket'}->Requestors->UserMembersObj->First->Name } if $args{'Ticket'}; $args{'rtname'} = RT->Config->Get('rtname'); if ( $args{'Ticket'} ) { my $t = $args{'Ticket'}; # avoid memory leak $args{'loc'} = sub { $t->loc(@_) }; } else { $args{'loc'} = sub { $self->loc(@_) }; } $args{'EscapeURI'} = sub { my $str = shift; RT::Interface::Web::EscapeURI( \$str ); return $str; }; $args{'EscapeHTML'} = sub { my $str = shift; RT::Interface::Web::EscapeHTML( \$str ); return $str; }; if ($self->Type eq 'Perl') { return $self->_ParseContentPerl( Content => $content, TemplateArgs => \%args, ); } else { return $self->_ParseContentSimple( Content => $content, TemplateArgs => \%args, ); } } # uses Text::Template for Perl templates sub _ParseContentPerl { my $self = shift; my %args = ( Content => undef, TemplateArgs => {}, @_, ); foreach my $key ( keys %{ $args{TemplateArgs} } ) { my $val = $args{TemplateArgs}{ $key }; next unless ref $val; next if ref($val) =~ /^(ARRAY|HASH|SCALAR|CODE)$/; $args{TemplateArgs}{ $key } = \$val; } my $template = Text::Template->new( TYPE => 'STRING', SOURCE => $args{Content}, ); my ($ok) = $template->compile; unless ($ok) { $RT::Logger->error("Template parsing error in @{[$self->Name]} (#@{[$self->id]}): $Text::Template::ERROR"); return ( undef, $self->loc('Template parsing error: [_1]', $Text::Template::ERROR) ); } my $is_broken = 0; my $retval = $template->fill_in( HASH => $args{TemplateArgs}, BROKEN => sub { my (%args) = @_; $RT::Logger->error("Template parsing error: $args{error}") unless $args{error} =~ /^Died at /; # ignore intentional die() $is_broken++; return undef; }, ); return ( undef, $self->loc('Template parsing error') ) if $is_broken; return ($retval); } sub _ParseContentSimple { my $self = shift; my %args = ( Content => undef, TemplateArgs => {}, @_, ); $self->_MassageSimpleTemplateArgs(%args); my $template = Text::Template->new( TYPE => 'STRING', SOURCE => $args{Content}, ); my ($ok) = $template->compile; return ( undef, $self->loc('Template parsing error: [_1]', $Text::Template::ERROR) ) if !$ok; # copied from Text::Template::fill_in and refactored to be simple variable # interpolation my $fi_r = ''; foreach my $fi_item (@{$template->{SOURCE}}) { my ($fi_type, $fi_text, $fi_lineno) = @$fi_item; if ($fi_type eq 'TEXT') { $fi_r .= $fi_text; } elsif ($fi_type eq 'PROG') { my $fi_res; my $original_fi_text = $fi_text; # strip surrounding whitespace for simpler regexes $fi_text =~ s/^\s+//; $fi_text =~ s/\s+$//; # if the codeblock is a simple $Variable lookup, use the value from # the TemplateArgs hash... if (my ($var) = $fi_text =~ /^\$(\w+)$/) { if (exists $args{TemplateArgs}{$var}) { $fi_res = $args{TemplateArgs}{$var}; } } # if there was no substitution then just reinsert the codeblock if (!defined $fi_res) { $fi_res = "{$original_fi_text}"; } # If the value of the filled-in text really was undef, # change it to an explicit empty string to avoid undefined # value warnings later. $fi_res = '' unless defined $fi_res; $fi_r .= $fi_res; } } return $fi_r; } sub _MassageSimpleTemplateArgs { my $self = shift; my %args = ( TemplateArgs => {}, @_, ); my $template_args = $args{TemplateArgs}; if (my $ticket = $template_args->{Ticket}) { for my $column (qw/Id Subject Type InitialPriority FinalPriority Priority TimeEstimated TimeWorked Status TimeLeft Told Starts Started Due Resolved RequestorAddresses AdminCcAddresses CcAddresses/) { $template_args->{"Ticket".$column} = $ticket->$column; } $template_args->{"TicketQueueId"} = $ticket->Queue; $template_args->{"TicketQueueName"} = $ticket->QueueObj->Name; $template_args->{"TicketOwnerId"} = $ticket->Owner; $template_args->{"TicketOwnerName"} = $ticket->OwnerObj->Name; $template_args->{"TicketOwnerEmailAddress"} = $ticket->OwnerObj->EmailAddress; my $cfs = $ticket->CustomFields; while (my $cf = $cfs->Next) { my $simple = $cf->Name; $simple =~ s/\W//g; $template_args->{"TicketCF" . $simple} = $ticket->CustomFieldValuesAsString($cf->Name); } } if (my $txn = $template_args->{Transaction}) { for my $column (qw/Id TimeTaken Type Field OldValue NewValue Data Content Subject Description BriefDescription/) { $template_args->{"Transaction".$column} = $txn->$column; } my $cfs = $txn->CustomFields; while (my $cf = $cfs->Next) { my $simple = $cf->Name; $simple =~ s/\W//g; $template_args->{"TransactionCF" . $simple} = $txn->CustomFieldValuesAsString($cf->Name); } } } sub _DowngradeFromHTML { my $self = shift; my $orig_entity = $self->MIMEObj; my $new_entity = $orig_entity->dup; # this will fail badly if we go away from InCore parsing # We're going to make this multipart/alternative below, so clear out the Subject # header copied from the original when we dup'd above. # Alternative parts should have just formatting headers like Content-Type # so maybe we should clear all, but staying conservative for now. if ( $new_entity->head->get('Subject') ) { $new_entity->head->delete('Subject'); } $new_entity->head->mime_attr( "Content-Type" => 'text/plain' ); $new_entity->head->mime_attr( "Content-Type.charset" => 'utf-8' ); $orig_entity->head->mime_attr( "Content-Type" => 'text/html' ); $orig_entity->head->mime_attr( "Content-Type.charset" => 'utf-8' ); my $body = $new_entity->bodyhandle->as_string; $body = Encode::decode( "UTF-8", $body ); my $html = RT::Interface::Email::ConvertHTMLToText( $body ); $html = Encode::encode( "UTF-8", $html ); return unless defined $html; $new_entity->bodyhandle(MIME::Body::InCore->new( \$html )); $orig_entity->make_multipart('alternative', Force => 1); $orig_entity->add_part($new_entity, 0); # plain comes before html $self->{MIMEObj} = $orig_entity; return; } =head2 CurrentUserHasQueueRight Helper function to call the template's queue's CurrentUserHasQueueRight with the passed in args. =cut sub CurrentUserHasQueueRight { my $self = shift; return ( $self->QueueObj->CurrentUserHasRight(@_) ); } =head2 SetQueue Changing queue is not implemented. =cut sub SetQueue { my $self = shift; return ( undef, $self->loc('Changing queue is not implemented') ); } =head2 SetName Change name of the template. =cut sub SetName { my $self = shift; my $value = shift; return ( undef, $self->loc('Name is required') ) unless $value; return $self->_Set( Field => 'Name', Value => $value ) if lc($self->Name) eq lc($value); my $tmp = $self->new( RT->SystemUser ); $tmp->LoadByCols( Name => $value, Queue => $self->Queue ); return ( undef, $self->loc('A Template with that name already exists') ) if $tmp->id; return $self->_Set( Field => 'Name', Value => $value ); } =head2 SetType If setting Type to Perl, require the ExecuteCode right. =cut sub SetType { my $self = shift; my $NewType = shift; if ($NewType eq 'Perl' && !$self->CurrentUser->HasRight(Right => 'ExecuteCode', Object => $RT::System)) { return ( undef, $self->loc('Permission Denied') ); } return $self->_Set( Field => 'Type', Value => $NewType ); } =head2 SetContent If changing content and the type is Perl, require the ExecuteCode right. =cut sub SetContent { my $self = shift; my $NewContent = shift; if ($self->Type eq 'Perl' && !$self->CurrentUser->HasRight(Right => 'ExecuteCode', Object => $RT::System)) { return ( undef, $self->loc('Permission Denied') ); } return $self->_Set( Field => 'Content', Value => $NewContent ); } sub _UpdateAttributes { my $self = shift; my %args = ( NewValues => {}, @_, ); my $type = $args{NewValues}{Type} || $self->Type; # forbid updating content when the (possibly new) value of Type is Perl if ($type eq 'Perl' && exists $args{NewValues}{Content}) { if (!$self->CurrentUser->HasRight(Right => 'ExecuteCode', Object => $RT::System)) { return $self->loc('Permission Denied'); } } return $self->SUPER::_UpdateAttributes(%args); } =head2 CompileCheck If the template's Type is Perl, then compile check all the codeblocks to see if they are syntactically valid. We eval them in a codeblock to avoid actually executing the code. Returns an (ok, message) pair. =cut sub CompileCheck { my $self = shift; return (1, $self->loc("Template does not include Perl code")) unless $self->Type eq 'Perl'; my $content = $self->Content; $content = '' if !defined($content); my $template = Text::Template->new( TYPE => 'STRING', SOURCE => $content, ); my ($ok) = $template->compile; return ( undef, $self->loc('Template parsing error: [_1]', $Text::Template::ERROR) ) if !$ok; # copied from Text::Template::fill_in and refactored to be compile checks foreach my $fi_item (@{$template->{SOURCE}}) { my ($fi_type, $fi_text, $fi_lineno) = @$fi_item; next unless $fi_type eq 'PROG'; do { no strict 'vars'; eval "sub { $fi_text }"; }; next if !$@; my $error = $@; # provide a (hopefully) useful line number for the error, but clean up # all the other extraneous garbage $error =~ s/\(eval \d+\) line (\d+).*/"template line " . ($1+$fi_lineno-1)/es; return (0, $self->loc("Couldn't compile template codeblock '[_1]': [_2]", $fi_text, $error)); } return (1, $self->loc("Template compiles")); } =head2 CurrentUserCanRead =cut sub CurrentUserCanRead { my $self =shift; if ($self->__Value('Queue')) { my $queue = RT::Queue->new( RT->SystemUser ); $queue->Load( $self->__Value('Queue')); return 1 if $self->CurrentUser->HasRight( Right => 'ShowTemplate', Object => $queue ); } else { return 1 if $self->CurrentUser->HasRight( Right => 'ShowGlobalTemplates', Object => $RT::System ); return 1 if $self->CurrentUser->HasRight( Right => 'ShowTemplate', Object => $RT::System ); } return; } 1; sub Table {'Templates'} =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 Queue Returns the current value of Queue. (In the database, Queue is stored as int(11).) =head2 SetQueue VALUE Set Queue to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Queue will be stored as a int(11).) =cut =head2 QueueObj Returns the Queue Object which has the id returned by Queue =cut sub QueueObj { my $self = shift; my $Queue = RT::Queue->new($self->CurrentUser); $Queue->Load($self->__Value('Queue')); return($Queue); } =head2 Name Returns the current value of Name. (In the database, Name is stored as varchar(200).) =head2 SetName VALUE Set Name to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Name will be stored as a varchar(200).) =cut =head2 Description Returns the current value of Description. (In the database, Description is stored as varchar(255).) =head2 SetDescription VALUE Set Description to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Description will be stored as a varchar(255).) =cut =head2 Type Returns the current value of Type. (In the database, Type is stored as varchar(16).) =head2 SetType VALUE Set Type to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Type will be stored as a varchar(16).) =cut =head2 Content Returns the current value of Content. (In the database, Content is stored as text.) =head2 SetContent VALUE Set Content to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Content will be stored as a text.) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Queue => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Name => {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, Description => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, Type => {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, Content => {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); $deps->Add( out => $self->QueueObj ) if $self->QueueObj->Id; } sub __DependsOn { my $self = shift; my %args = ( Shredder => undef, Dependencies => undef, @_, ); my $deps = $args{'Dependencies'}; my $list = []; # Scrips push( @$list, $self->UsedBy ); $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON, TargetObjects => $list, Shredder => $args{'Shredder'}, ); return $self->SUPER::__DependsOn( %args ); } sub PreInflate { my $class = shift; my ($importer, $uid, $data) = @_; $class->SUPER::PreInflate( $importer, $uid, $data ); my $obj = RT::Template->new( RT->SystemUser ); if ($data->{Queue} == 0) { $obj->LoadGlobalTemplate( $data->{Name} ); } else { $obj->LoadQueueTemplate( Queue => $data->{Queue}, Name => $data->{Name} ); } if ($obj->Id) { $importer->Resolve( $uid => ref($obj) => $obj->Id ); return; } return 1; } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Approval.pm�������������������������������������������������������������������������000644 �000765 �000024 �00000004412 14005011336 016217� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Approval; use strict; use warnings; use RT::Ruleset; RT::Ruleset->Add( Name => 'Approval', Rules => [ 'RT::Approval::Rule::NewPending', 'RT::Approval::Rule::Rejected', 'RT::Approval::Rule::Passed', 'RT::Approval::Rule::Created', ]); RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Group.pm����������������������������������������������������������������������������000644 �000765 �000024 �00000137220 14005011336 015533� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} # Released under the terms of version 2 of the GNU Public License =head1 NAME RT::Group - RT's group object =head1 SYNOPSIS use RT::Group; my $group = RT::Group->new($CurrentUser); =head1 DESCRIPTION An RT group object. =cut package RT::Group; use strict; use warnings; use base 'RT::Record'; use Role::Basic 'with'; with "RT::Record::Role::Rights", "RT::Record::Role::Links"; sub Table {'Groups'} use RT::Users; use RT::GroupMembers; use RT::Principals; use RT::ACL; use RT::CustomRole; __PACKAGE__->AddRight( Admin => AdminGroup => 'Modify group metadata or delete group'); # loc __PACKAGE__->AddRight( Admin => AdminGroupMembership => 'Modify group membership roster'); # loc __PACKAGE__->AddRight( Staff => ModifyOwnMembership => 'Join or leave group'); # loc __PACKAGE__->AddRight( Admin => EditSavedSearches => 'Create, modify and delete saved searches'); # loc __PACKAGE__->AddRight( Staff => ShowSavedSearches => 'View saved searches'); # loc __PACKAGE__->AddRight( Staff => SeeGroup => 'View group'); # loc __PACKAGE__->AddRight( Staff => SeeGroupDashboard => 'View group dashboards'); # loc __PACKAGE__->AddRight( Admin => CreateGroupDashboard => 'Create group dashboards'); # loc __PACKAGE__->AddRight( Admin => ModifyGroupDashboard => 'Modify group dashboards'); # loc __PACKAGE__->AddRight( Admin => DeleteGroupDashboard => 'Delete group dashboards'); # loc __PACKAGE__->AddRight( Staff => ModifyGroupLinks => 'Modify group links' ); # loc =head1 METHODS =head2 SelfDescription Returns a user-readable description of what this group is for and what it's named. =cut sub SelfDescription { my $self = shift; if ($self->Domain eq 'ACLEquivalence') { my $user = RT::Principal->new($self->CurrentUser); $user->Load($self->Instance); return $self->loc("user [_1]",$user->Object->Name); } elsif ($self->Domain eq 'UserDefined') { return $self->loc("group '[_1]'",$self->Name); } elsif ($self->Domain eq 'RT::System-Role') { return $self->loc("system [_1]",$self->Name); } elsif ($self->Domain eq 'RT::Queue-Role') { my $queue = RT::Queue->new($self->CurrentUser); $queue->Load($self->Instance); return $self->loc("queue [_1] [_2]",$queue->Name, $self->Name); } elsif ($self->Domain eq 'RT::Ticket-Role') { return $self->loc("ticket #[_1] [_2]",$self->Instance, $self->Name); } elsif ($self->RoleClass) { my $class = lc $self->RoleClass; $class =~ s/^RT:://i; return $self->loc("[_1] #[_2] [_3]", $self->loc($class), $self->Instance, $self->Name); } elsif ($self->Domain eq 'SystemInternal') { return $self->loc("system group '[_1]'",$self->Name); } else { return $self->loc("undescribed group [_1]",$self->Id); } } =head2 Load ID Load a group object from the database. Takes a single argument. If the argument is numerical, load by the column 'id'. Otherwise, complain and return. =cut sub Load { my $self = shift; my $identifier = shift || return undef; if ( $identifier !~ /\D/ ) { $self->SUPER::LoadById($identifier); } else { $RT::Logger->crit("Group -> Load called with a bogus argument"); return undef; } } =head2 LoadUserDefinedGroup NAME Loads a system group from the database. The only argument is the group's name. =cut sub LoadUserDefinedGroup { my $self = shift; my $identifier = shift; if ( $identifier =~ /^\d+$/ ) { return $self->LoadByCols( Domain => 'UserDefined', id => $identifier, ); } else { return $self->LoadByCols( Domain => 'UserDefined', Name => $identifier, ); } } =head2 LoadACLEquivalenceGroup PRINCIPAL Loads a user's acl equivalence group. Takes a principal object or its ID. ACL equivalnce groups are used to simplify the acl system. Each user has one group that only he is a member of. Rights granted to the user are actually granted to that group. This greatly simplifies ACL checks. While this results in a somewhat more complex setup when creating users and granting ACLs, it _greatly_ simplifies acl checks. =cut sub LoadACLEquivalenceGroup { my $self = shift; my $principal = shift; $principal = $principal->id if ref $principal; return $self->LoadByCols( Domain => 'ACLEquivalence', Name => 'UserEquiv', Instance => $principal, ); } =head2 LoadSystemInternalGroup NAME Loads a Pseudo group from the database. The only argument is the group's name. =cut sub LoadSystemInternalGroup { my $self = shift; my $identifier = shift; return $self->LoadByCols( Domain => 'SystemInternal', Name => $identifier, ); } =head2 LoadRoleGroup Takes a paramhash of Object and Name and attempts to load the suitable role group for said object. =cut sub LoadRoleGroup { my $self = shift; my %args = ( Object => undef, Name => undef, @_ ); my $object = delete $args{Object}; return wantarray ? (0, $self->loc("Object passed is not loaded")) : 0 unless $object->id; # Translate Object to Domain + Instance $args{Domain} = ref($object) . "-Role"; $args{Instance} = $object->id; return $self->LoadByCols(%args); } sub LoadByCols { my $self = shift; my %args = ( @_ ); return $self->SUPER::LoadByCols( %args ); } =head2 Create You need to specify what sort of group you're creating by calling one of the other Create_____ routines. =cut sub Create { my $self = shift; $RT::Logger->crit("Someone called RT::Group->Create. this method does not exist. someone's being evil"); return(0,$self->loc('Permission Denied')); } =head2 _Create Takes a paramhash with named arguments: Name, Description. Returns a tuple of (Id, Message). If id is 0, the create failed =cut sub _Create { my $self = shift; my %args = ( Name => undef, Description => undef, Domain => undef, Instance => '0', InsideTransaction => undef, _RecordTransaction => 1, @_ ); if ($args{'Domain'}) { # Enforce uniqueness on user defined group names if ($args{'Domain'} eq 'UserDefined') { my ($ok, $msg) = $self->_ValidateUserDefinedName($args{'Name'}); return ($ok, $msg) if not $ok; } # Enforce uniqueness on SystemInternal and system role groups if ($args{'Domain'} eq 'SystemInternal' || $args{'Domain'} eq 'RT::System-Role') { my ($ok, $msg) = $self->_ValidateNameForDomain($args{'Name'}, $args{'Domain'}); return ($ok, $msg) if not $ok; } } $RT::Handle->BeginTransaction() unless ($args{'InsideTransaction'}); # Groups deal with principal ids, rather than user ids. # When creating this group, set up a principal Id for it. my $principal = RT::Principal->new( $self->CurrentUser ); my $principal_id = $principal->Create( PrincipalType => 'Group', ); $self->SUPER::Create( id => $principal_id, Name => $args{'Name'}, Description => $args{'Description'}, Domain => $args{'Domain'}, Instance => ($args{'Instance'} || '0') ); my $id = $self->Id; unless ($id) { $RT::Handle->Rollback() unless ($args{'InsideTransaction'}); return ( 0, $self->loc('Could not create group') ); } # If we couldn't create a principal Id, get the fuck out. unless ($principal_id) { $RT::Handle->Rollback() unless ($args{'InsideTransaction'}); $RT::Logger->crit( "Couldn't create a Principal on new user create. Strange things are afoot at the circle K" ); return ( 0, $self->loc('Could not create group') ); } # Now we make the group a member of itself as a cached group member # this needs to exist so that group ACL checks don't fall over. # you're checking CachedGroupMembers to see if the principal in question # is a member of the principal the rights have been granted too # in the ordinary case, this would fail badly because it would recurse and add all the members of this group as # cached members. thankfully, we're creating the group now...so it has no members. my $cgm = RT::CachedGroupMember->new($self->CurrentUser); $cgm->Create(Group =>$self->PrincipalObj, Member => $self->PrincipalObj, ImmediateParent => $self->PrincipalObj); if ( $args{'_RecordTransaction'} ) { $self->_NewTransaction( Type => "Create" ); } $RT::Handle->Commit() unless ($args{'InsideTransaction'}); return ( $id, $self->loc("Group created") ); } =head2 CreateUserDefinedGroup { Name => "name", Description => "Description"} A helper subroutine which creates a system group Returns a tuple of (Id, Message). If id is 0, the create failed =cut sub CreateUserDefinedGroup { my $self = shift; unless ( $self->CurrentUserHasRight('AdminGroup') ) { $RT::Logger->warning( $self->CurrentUser->Name . " Tried to create a group without permission." ); return ( 0, $self->loc('Permission Denied') ); } return($self->_Create( Domain => 'UserDefined', Instance => '', @_)); } =head2 ValidateName VALUE Enforces unique user defined group names when updating =cut sub ValidateName { my ($self, $value) = @_; if ($self->Domain and $self->Domain eq 'UserDefined') { my ($ok, $msg) = $self->_ValidateUserDefinedName($value); # It's really too bad we can't pass along the actual error return 0 if not $ok; } return $self->SUPER::ValidateName($value); } =head2 _ValidateNameForDomain VALUE DOMAIN Returns true if the group name isn't in use in the same domain, false otherwise. =cut sub _ValidateNameForDomain { my ($self, $value, $domain) = @_; return (0, 'Name is required') unless length $value; my $dupcheck = RT::Group->new(RT->SystemUser); if ($domain eq 'UserDefined') { $dupcheck->LoadUserDefinedGroup($value); } else { $dupcheck->LoadByCols(Domain => $domain, Name => $value); } if ( $dupcheck->id && ( !$self->id || $self->id != $dupcheck->id ) ) { return ( 0, $self->loc( "Group name '[_1]' is already in use", $value ) ); } return 1; } =head2 _ValidateUserDefinedName VALUE Returns true if the user defined group name isn't in use, false otherwise. =cut sub _ValidateUserDefinedName { my ($self, $value) = @_; return $self->_ValidateNameForDomain($value, 'UserDefined'); } =head2 _CreateACLEquivalenceGroup { Principal } A helper subroutine which creates a group containing only an individual user. This gets used by the ACL system to check rights. Yes, it denormalizes the data, but that's ok, as we totally win on performance. Returns a tuple of (Id, Message). If id is 0, the create failed =cut sub _CreateACLEquivalenceGroup { my $self = shift; my $princ = shift; my $id = $self->_Create( Domain => 'ACLEquivalence', Name => 'UserEquiv', Description => 'ACL equiv. for user '.$princ->Object->Id, Instance => $princ->Id, InsideTransaction => 1, _RecordTransaction => 0 ); unless ($id) { $RT::Logger->crit("Couldn't create ACL equivalence group"); return undef; } # We use stashuser so we don't get transactions inside transactions # and so we bypass all sorts of cruft we don't need my $aclstash = RT::GroupMember->new($self->CurrentUser); my ($stash_id, $add_msg) = $aclstash->_StashUser(Group => $self->PrincipalObj, Member => $princ); unless ($stash_id) { $RT::Logger->crit("Couldn't add the user to his own acl equivalence group:".$add_msg); # We call super delete so we don't get acl checked. $self->SUPER::Delete(); return(undef); } return ($id); } =head2 CreateRoleGroup A convenience method for creating a role group on an object. This method expects to be called from B<inside of a database transaction>! If you're calling it outside of one, you B<MUST> pass a false value for InsideTransaction. Takes a paramhash of: =over 4 =item Name Required. RT's core role types are C<Requestor>, C<Cc>, C<AdminCc>, and C<Owner>. Extensions may add their own. =item Object Optional. The object on which this role applies, used to set Domain and Instance automatically. =item Domain Optional. The class on which this role applies, with C<-Role> appended. RT's supported core role group domains are C<RT::Ticket-Role>, C<RT::Queue-Role>, and C<RT::System-Role>. Not required if you pass an Object. =item Instance Optional. The numeric ID of the object (of the class encoded in Domain) on which this role applies. If Domain is C<RT::System-Role>, Instance should be C<1>. Not required if you pass an Object. =item InsideTransaction Optional. Defaults to true in expectation of usual call sites. If you call this method while not inside a transaction, you C<MUST> pass a false value for this parameter. =back You must pass either an Object or both Domain and Instance. Returns a tuple of (id, Message). If id is false, the create failed and Message should contain an error string. =cut sub CreateRoleGroup { my $self = shift; my %args = ( Instance => undef, Name => undef, Domain => undef, Object => undef, InsideTransaction => 1, @_ ); # Translate Object to Domain + Instance my $object = delete $args{Object}; if ( $object ) { $args{Domain} = ref($object) . "-Role"; $args{Instance} = $object->id; } unless ($args{Instance}) { return ( 0, $self->loc("An Instance must be provided") ); } unless ($self->ValidateRoleGroup(%args)) { return ( 0, $self->loc("Invalid Group Name and Domain") ); } my %create = map { $_ => $args{$_} } qw(Domain Instance Name); my $duplicate = RT::Group->new( RT->SystemUser ); $duplicate->LoadByCols( %create ); if ($duplicate->id) { return ( 0, $self->loc("Role group exists already") ); } my ($id, $msg) = $self->_Create( InsideTransaction => $args{InsideTransaction}, %create, ); if ($self->SingleMemberRoleGroup) { $self->_AddMember( PrincipalId => RT->Nobody->Id, InsideTransaction => $args{InsideTransaction}, RecordTransaction => 0, Object => $object, ); } return ($id, $msg); } sub RoleClass { my $self = shift; my $domain = shift || $self->Domain; return unless $domain =~ /^(.+)-Role$/; return unless $1->DOES("RT::Record::Role::Roles"); return $1; } =head2 ValidateRoleGroup Takes a param hash containing Domain and Type which are expected to be values passed into L</CreateRoleGroup>. Returns true if the specified Type is a registered role on the specified Domain. Otherwise returns false. =cut sub ValidateRoleGroup { my $self = shift; my %args = (@_); return 0 unless $args{Domain} and $args{'Name'}; my $class = $self->RoleClass($args{Domain}); return 0 unless $class; return $class->HasRole($args{'Name'}); } =head2 SingleMemberRoleGroup =cut sub SingleMemberRoleGroup { my $self = shift; my $class = $self->RoleClass; return unless $class; return $class->Role($self->Name)->{Single}; } sub SingleMemberRoleGroupColumn { my $self = shift; my ($class) = $self->Domain =~ /^(.+)-Role$/; return unless $class; my $role = $class->Role($self->Name); return unless $role->{Class} eq $class; return $role->{Column}; } sub RoleGroupObject { my $self = shift; my ($class) = $self->Domain =~ /^(.+)-Role$/; return unless $class; my $obj = $class->new( $self->CurrentUser ); $obj->Load( $self->Instance ); return $obj; } sub SetName { my $self = shift; my $value = shift; my ($status, $msg) = $self->_Set( Field => 'Name', Value => $value ); return ($status, $msg); } =head2 Delete Delete this object =cut sub Delete { my $self = shift; unless ( $self->CurrentUserHasRight('AdminGroup') ) { return ( 0, 'Permission Denied' ); } $RT::Logger->crit("Deleting groups violates referential integrity until we go through and fix this"); # TODO XXX # Remove the principal object # Remove this group from anything it's a member of. # Remove all cached members of this group # Remove any rights granted to this group # remove any rights delegated by way of this group return ( $self->SUPER::Delete(@_) ); } =head2 SetDisabled BOOL If passed a positive value, this group will be disabled. No rights it commutes or grants will be honored. It will not appear in most group listings. This routine finds all the cached group members that are members of this group (recursively) and disables them. =cut # }}} sub SetDisabled { my $self = shift; my $val = shift; unless ( $self->CurrentUserHasRight('AdminGroup') ) { return (0, $self->loc('Permission Denied')); } $RT::Handle->BeginTransaction(); $self->PrincipalObj->SetDisabled($val); # Find all occurrences of this member as a member of this group # in the cache and nuke them, recursively. # The following code will delete all Cached Group members # where this member's group is _not_ the primary group # (Ie if we're deleting C as a member of B, and B happens to be # a member of A, will delete C as a member of A without touching # C as a member of B my $cached_submembers = RT::CachedGroupMembers->new( $self->CurrentUser ); $cached_submembers->Limit( FIELD => 'ImmediateParentId', OPERATOR => '=', VALUE => $self->Id); #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. # TODO what about the groups key cache? RT::Principal->InvalidateACLCache(); while ( my $item = $cached_submembers->Next() ) { my $del_err = $item->SetDisabled($val); unless ($del_err) { $RT::Handle->Rollback(); $RT::Logger->warning("Couldn't disable cached group submember ".$item->Id); return (undef); } } $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" ); $RT::Handle->Commit(); if ( $val == 1 ) { return (1, $self->loc("Group disabled")); } else { return (1, $self->loc("Group enabled")); } } sub Disabled { my $self = shift; $self->PrincipalObj->Disabled(@_); } =head2 DeepMembersObj Returns an RT::CachedGroupMembers object of this group's members, including all members of subgroups. =cut sub DeepMembersObj { my $self = shift; my $members_obj = RT::CachedGroupMembers->new( $self->CurrentUser ); #If we don't have rights, don't include any results # TODO XXX WHY IS THERE NO ACL CHECK HERE? $members_obj->LimitToMembersOfGroup( $self->PrincipalId ); return ( $members_obj ); } =head2 MembersObj Returns an RT::GroupMembers object of this group's direct members. =cut sub MembersObj { my $self = shift; my $members_obj = RT::GroupMembers->new( $self->CurrentUser ); #If we don't have rights, don't include any results # TODO XXX WHY IS THERE NO ACL CHECK HERE? $members_obj->LimitToMembersOfGroup( $self->PrincipalId ); return ( $members_obj ); } =head2 GroupMembersObj [Recursively => 1] Returns an L<RT::Groups> object of this group's members. By default returns groups including all subgroups, but could be changed with C<Recursively> named argument. B<Note> that groups are not filtered by type and result may contain as well system groups and others. =cut sub GroupMembersObj { my $self = shift; my %args = ( Recursively => 1, @_ ); my $groups = RT::Groups->new( $self->CurrentUser ); my $members_table = $args{'Recursively'}? 'CachedGroupMembers': 'GroupMembers'; my $members_alias = $groups->NewAlias( $members_table ); $groups->Join( ALIAS1 => $members_alias, FIELD1 => 'MemberId', ALIAS2 => $groups->PrincipalsAlias, FIELD2 => 'id', ); $groups->Limit( ALIAS => $members_alias, FIELD => 'GroupId', VALUE => $self->PrincipalId, ); $groups->Limit( ALIAS => $members_alias, FIELD => 'Disabled', VALUE => 0, ) if $args{'Recursively'}; return $groups; } =head2 UserMembersObj Returns an L<RT::Users> object of this group's members, by default returns users including all members of subgroups, but could be changed with C<Recursively> named argument. =cut sub UserMembersObj { my $self = shift; my %args = ( Recursively => 1, @_ ); #If we don't have rights, don't include any results # TODO XXX WHY IS THERE NO ACL CHECK HERE? my $members_table = $args{'Recursively'}? 'CachedGroupMembers': 'GroupMembers'; my $users = RT::Users->new($self->CurrentUser); my $members_alias = $users->NewAlias( $members_table ); $users->Join( ALIAS1 => $members_alias, FIELD1 => 'MemberId', ALIAS2 => $users->PrincipalsAlias, FIELD2 => 'id', ); $users->Limit( ALIAS => $members_alias, FIELD => 'GroupId', VALUE => $self->PrincipalId, ); $users->Limit( ALIAS => $members_alias, FIELD => 'Disabled', VALUE => 0, ) if $args{'Recursively'}; return ( $users); } =head2 MemberEmailAddresses Returns an array of the email addresses of all of this group's members =cut sub MemberEmailAddresses { my $self = shift; return sort grep defined && length, map $_->EmailAddress, @{ $self->UserMembersObj->ItemsArrayRef }; } =head2 MemberEmailAddressesAsString Returns a comma delimited string of the email addresses of all users who are members of this group. =cut sub MemberEmailAddressesAsString { my $self = shift; return (join(', ', $self->MemberEmailAddresses)); } =head2 AddMember PRINCIPAL_ID AddMember adds a principal to this group. It takes a single principal id. Returns a two value array. the first value is true on successful addition or 0 on failure. The second value is a textual status msg. =cut sub AddMember { my $self = shift; my $new_member = shift; # We should only allow membership changes if the user has the right # to modify group membership or the user is the principal in question # and the user has the right to modify his own membership unless ( ($new_member == $self->CurrentUser->PrincipalId && $self->CurrentUserHasRight('ModifyOwnMembership') ) || $self->CurrentUserHasRight('AdminGroupMembership') ) { #User has no permission to be doing this return ( 0, $self->loc("Permission Denied") ); } $self->_AddMember(PrincipalId => $new_member); } # A helper subroutine for AddMember that bypasses the ACL checks # this should _ONLY_ ever be called from Ticket/Queue AddWatcher # when we want to deal with groups according to queue rights # In the dim future, this will all get factored out and life # will get better # takes a paramhash of { PrincipalId => undef, InsideTransaction } sub _AddMember { my $self = shift; my %args = ( PrincipalId => undef, InsideTransaction => undef, RecordTransaction => 1, @_); # RecordSetTransaction is used by _DeleteMember to get one txn but not the other $args{RecordSetTransaction} = $args{RecordTransaction} unless exists $args{RecordSetTransaction}; my $new_member = $args{'PrincipalId'}; unless ($self->Id) { $RT::Logger->crit("Attempting to add a member to a group which wasn't loaded. 'oops'"); return(0, $self->loc("Group not found")); } my $new_member_obj = RT::Principal->new( $self->CurrentUser ); $new_member_obj->Load($new_member); unless ( $new_member_obj->Id ) { $RT::Logger->debug("Couldn't find that principal"); return ( 0, $self->loc("Couldn't find that principal") ); } if ( $self->HasMember( $new_member_obj ) ) { #User is already a member of this group. no need to add it return ( 0, $self->loc("Group already has member: [_1]", $new_member_obj->Object->Name) ); } if ( $new_member_obj->IsGroup && $new_member_obj->Object->HasMemberRecursively($self->PrincipalObj) ) { #This group can't be made to be a member of itself return ( 0, $self->loc("Groups can't be members of their members")); } my @purge; push @purge, @{$self->MembersObj->ItemsArrayRef} if $self->SingleMemberRoleGroup; my $member_object = RT::GroupMember->new( $self->CurrentUser ); my $id = $member_object->Create( Member => $new_member_obj, Group => $self->PrincipalObj, InsideTransaction => $args{'InsideTransaction'} ); return(0, $self->loc("Couldn't add member to group")) unless $id; # Purge all previous members (we're a single member role group) my $old_member_id; for my $member (@purge) { my $old_member = $member->MemberId; my ($ok, $msg) = $member->Delete(); return(0, $self->loc("Couldn't remove previous member: [_1]", $msg)) unless $ok; # We remove all members in this loop, but there should only ever be one # member. Keep track of the last one successfully removed for the # SetWatcher transaction below. $old_member_id = $old_member; } # Update the column if (my $col = $self->SingleMemberRoleGroupColumn) { my $obj = $args{Object} || $self->RoleGroupObject; my ($ok, $msg) = $obj->_Set( Field => $col, Value => $new_member_obj->Id, CheckACL => 0, # don't check acl RecordTransaction => $args{'RecordSetTransaction'}, ); return (0, $self->loc("Could not update column [_1]: [_2]", $col, $msg)) unless $ok; } # Record transactions for UserDefined groups if ($args{RecordTransaction} && $self->Domain eq 'UserDefined') { $new_member_obj->Object->_NewTransaction( Type => 'AddMembership', Field => $self->PrincipalObj->id, ); $self->_NewTransaction( Type => 'AddMember', Field => $new_member, ); } # Record an Add/SetWatcher txn on the object if we're a role group if ($args{RecordTransaction} and $self->RoleClass) { my $obj = $args{Object} || $self->RoleGroupObject; if ($self->SingleMemberRoleGroup) { $obj->_NewTransaction( Type => 'SetWatcher', OldValue => $old_member_id, NewValue => $new_member_obj->Id, Field => $self->Name, ); } else { $obj->_NewTransaction( Type => 'AddWatcher', # use "watcher" for history's sake NewValue => $new_member_obj->Id, Field => $self->Name, ); } } return (1, $self->loc("[_1] set to [_2]", $self->loc($self->Name), $new_member_obj->Object->Name) ) if $self->SingleMemberRoleGroup; return ( 1, $self->loc("Member added: [_1]", $new_member_obj->Object->Name) ); } =head2 HasMember RT::Principal|id Takes an L<RT::Principal> object or its id returns a GroupMember Id if that user is a member of this group. Returns undef if the user isn't a member of the group or if the current user doesn't have permission to find out. Arguably, it should differentiate between ACL failure and non membership. =cut sub HasMember { my $self = shift; my $principal = shift; my $id; if ( UNIVERSAL::isa($principal,'RT::Principal') ) { $id = $principal->id; } elsif ( $principal =~ /^\d+$/ ) { $id = $principal; } else { $RT::Logger->error("Group::HasMember was called with an argument that". " isn't an RT::Principal or id. It's ".($principal||'(undefined)')); return(undef); } return undef unless $id; my $member_obj = RT::GroupMember->new( $self->CurrentUser ); $member_obj->LoadByCols( MemberId => $id, GroupId => $self->PrincipalId ); if ( my $member_id = $member_obj->id ) { return $member_id; } else { return (undef); } } =head2 HasMemberRecursively RT::Principal|id Takes an L<RT::Principal> object or its id and returns true if that user is a member of this group. Returns undef if the user isn't a member of the group or if the current user doesn't have permission to find out. Arguably, it should differentiate between ACL failure and non membership. =cut sub HasMemberRecursively { my $self = shift; my $principal = shift; my $id; if ( UNIVERSAL::isa($principal,'RT::Principal') ) { $id = $principal->id; } elsif ( $principal =~ /^\d+$/ ) { $id = $principal; } else { $RT::Logger->error("Group::HasMemberRecursively was called with an argument that". " isn't an RT::Principal or id. It's $principal"); return(undef); } return undef unless $id; my $member_obj = RT::CachedGroupMember->new( $self->CurrentUser ); $member_obj->LoadByCols( MemberId => $id, GroupId => $self->PrincipalId ); if ( my $member_id = $member_obj->id ) { return $member_id; } else { return (undef); } } =head2 DeleteMember PRINCIPAL_ID Takes the principal id of a current user or group. If the current user has apropriate rights, removes that GroupMember from this group. Returns a two value array. the first value is true on successful addition or 0 on failure. The second value is a textual status msg. Optionally takes a hash of key value flags, such as RecordTransaction. =cut sub DeleteMember { my $self = shift; my $member_id = shift; # We should only allow membership changes if the user has the right # to modify group membership or the user is the principal in question # and the user has the right to modify his own membership unless ( (($member_id == $self->CurrentUser->PrincipalId) && $self->CurrentUserHasRight('ModifyOwnMembership') ) || $self->CurrentUserHasRight('AdminGroupMembership') ) { #User has no permission to be doing this return ( 0, $self->loc("Permission Denied") ); } $self->_DeleteMember($member_id, @_); } # A helper subroutine for DeleteMember that bypasses the ACL checks. sub _DeleteMember { my $self = shift; my $member_id = shift; my %args = ( RecordTransaction => 1, @_, ); my $member_obj = RT::GroupMember->new( $self->CurrentUser ); $member_obj->LoadByCols( MemberId => $member_id, GroupId => $self->PrincipalId, ); # If we couldn't load it, return undef. unless ( $member_obj->Id() ) { $RT::Logger->debug("Group has no member with that id"); return ( 0, $self->loc( "Group has no such member" )); } # Now that we've checked ACLs and sanity, delete the groupmember my ($ok, $msg) = $member_obj->Delete(); return ( 0, $self->loc("Member not deleted" )) unless $ok; if ($self->RoleClass) { my %txn = ( OldValue => $member_id, Field => $self->Name, ); if ($self->SingleMemberRoleGroup) { # _AddMember creates the Set-Owner txn (for example) but # we handle the SetWatcher-Owner txn below. $self->_AddMember( PrincipalId => RT->Nobody->Id, RecordTransaction => 0, RecordSetTransaction => $args{RecordTransaction}, ); $txn{Type} = "SetWatcher"; $txn{NewValue} = RT->Nobody->id; } else { $txn{Type} = "DelWatcher"; } if ($args{RecordTransaction}) { my $obj = $args{Object} || $self->RoleGroupObject; $obj->_NewTransaction(%txn); } } # Record transactions for UserDefined groups if ($args{RecordTransaction} && $self->Domain eq 'UserDefined') { $member_obj->MemberObj->Object->_NewTransaction( Type => 'DeleteMembership', Field => $self->PrincipalObj->id, ); $self->_NewTransaction( Type => 'DeleteMember', Field => $member_id, ); } return ( $ok, $self->loc("Member deleted") ); } sub _Set { my $self = shift; my %args = ( Field => undef, Value => undef, TransactionType => 'Set', RecordTransaction => 1, @_ ); unless ( $self->CurrentUserHasRight('AdminGroup') ) { return ( 0, $self->loc('Permission Denied') ); } my $Old = $self->SUPER::_Value("$args{'Field'}"); my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'}, Value => $args{'Value'} ); #If we can't actually set the field to the value, don't record # a transaction. instead, get out of here. if ( $ret == 0 ) { return ( 0, $msg ); } if ( $args{'RecordTransaction'} == 1 ) { my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction( Type => $args{'TransactionType'}, Field => $args{'Field'}, NewValue => $args{'Value'}, OldValue => $Old, TimeTaken => $args{'TimeTaken'}, ); return ( $Trans, scalar $TransObj->Description ); } else { return ( $ret, $msg ); } } =head2 CurrentUserCanSee Always returns 1; unfortunately, for historical reasons, users have always been able to examine groups they have indirect access to, even if they do not have SeeGroup explicitly. =cut sub CurrentUserCanSee { my $self = shift; return 1; } =head2 PrincipalObj Returns the principal object for this user. returns an empty RT::Principal if there's no principal object matching this user. The response is cached. PrincipalObj should never ever change. =cut sub PrincipalObj { my $self = shift; my $res = RT::Principal->new( $self->CurrentUser ); $res->Load( $self->id ); return $res; } =head2 PrincipalId Returns this user's PrincipalId =cut sub PrincipalId { my $self = shift; return $self->Id; } sub InstanceObj { my $self = shift; my $class; if ( $self->Domain eq 'ACLEquivalence' ) { $class = "RT::User"; } elsif ($self->Domain eq 'RT::Queue-Role') { $class = "RT::Queue"; } elsif ($self->Domain eq 'RT::Ticket-Role') { $class = "RT::Ticket"; } elsif ($self->Domain eq 'RT::Asset-Role') { $class = "RT::Asset"; } return unless $class; my $obj = $class->new( $self->CurrentUser ); $obj->Load( $self->Instance ); return $obj; } sub BasicColumns { ( [ Name => 'Name' ], [ Description => 'Description' ], ); } =head2 Label Returns the group name suitable for displaying to end users. Override this instead of L</Name>, which is used internally. =cut sub Label { my $self = shift; # don't loc user-defined group names if ($self->Domain eq 'UserDefined') { return $self->Name; } if (my $role = $self->_CustomRoleObj) { # don't loc user-defined role names return $role->Name; } return $self->loc($self->Name); } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 Name Returns the current value of Name. (In the database, Name is stored as varchar(200).) =head2 SetName VALUE Set Name to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Name will be stored as a varchar(200).) =cut =head2 Description Returns the current value of Description. (In the database, Description is stored as varchar(255).) =head2 SetDescription VALUE Set Description to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Description will be stored as a varchar(255).) =cut =head2 Domain Returns the current value of Domain. (In the database, Domain is stored as varchar(64).) =head2 SetDomain VALUE Set Domain to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Domain will be stored as a varchar(64).) =cut =head2 Instance Returns the current value of Instance. (In the database, Instance is stored as int(11).) =head2 SetInstance VALUE Set Instance to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Instance will be stored as a int(11).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Name => {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, Description => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, Domain => {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''}, Instance => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); my $instance = $self->InstanceObj; $deps->Add( out => $instance ) if $instance; my $custom_role = $self->_CustomRoleObj; $deps->Add( out => $custom_role ) if $custom_role; # Group members records, unless we're a system group if ($self->Domain ne "SystemInternal") { my $objs = RT::GroupMembers->new( $self->CurrentUser ); $objs->LimitToMembersOfGroup( $self->PrincipalId ); $deps->Add( in => $objs ); } # Group member records group belongs to my $objs = RT::GroupMembers->new( $self->CurrentUser ); $objs->Limit( FIELD => 'MemberId', VALUE => $self->PrincipalId ); $deps->Add( in => $objs ); } sub __DependsOn { my $self = shift; my %args = ( Shredder => undef, Dependencies => undef, @_, ); my $deps = $args{'Dependencies'}; my $list = []; # User is inconsistent without own Equivalence group if( $self->Domain eq 'ACLEquivalence' ) { # delete user entry after ACL equiv group # in other case we will get deep recursion my $objs = RT::User->new($self->CurrentUser); $objs->Load( $self->Instance ); $deps->_PushDependency( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON | RT::Shredder::Constants::WIPE_AFTER, TargetObject => $objs, Shredder => $args{'Shredder'} ); } # Principal $deps->_PushDependency( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON | RT::Shredder::Constants::WIPE_AFTER, TargetObject => $self->PrincipalObj, Shredder => $args{'Shredder'} ); # Group members records my $objs = RT::GroupMembers->new( $self->CurrentUser ); $objs->LimitToMembersOfGroup( $self->PrincipalId ); push( @$list, $objs ); # Group member records group belongs to $objs = RT::GroupMembers->new( $self->CurrentUser ); $objs->Limit( VALUE => $self->PrincipalId, FIELD => 'MemberId', ENTRYAGGREGATOR => 'OR', QUOTEVALUE => 0 ); push( @$list, $objs ); # Cached group members records push( @$list, $self->DeepMembersObj ); # Cached group member records group belongs to $objs = RT::GroupMembers->new( $self->CurrentUser ); $objs->Limit( VALUE => $self->PrincipalId, FIELD => 'MemberId', ENTRYAGGREGATOR => 'OR', QUOTEVALUE => 0 ); push( @$list, $objs ); # Cleanup group's membership transactions $objs = RT::Transactions->new( $self->CurrentUser ); $objs->Limit( FIELD => 'Type', OPERATOR => 'IN', VALUE => ['AddMember', 'DeleteMember'] ); $objs->Limit( FIELD => 'Field', VALUE => $self->PrincipalObj->id, ENTRYAGGREGATOR => 'AND' ); push( @$list, $objs ); $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON, TargetObjects => $list, Shredder => $args{'Shredder'} ); return $self->SUPER::__DependsOn( %args ); } sub BeforeWipeout { my $self = shift; if( $self->Domain eq 'SystemInternal' ) { RT::Shredder::Exception::Info->throw('SystemObject'); } return $self->SUPER::BeforeWipeout( @_ ); } sub Serialize { my $self = shift; my %args = (@_); my %store = $self->SUPER::Serialize(@_); my $instance = $self->InstanceObj; $store{Instance} = \($instance->UID) if $instance; $store{Disabled} = $self->PrincipalObj->Disabled; $store{Principal} = $self->PrincipalObj->UID; $store{PrincipalId} = $self->PrincipalObj->Id; if (my $role = $self->_CustomRoleObj) { $store{Name} = \($role->UID); } return %store; } sub PreInflate { my $class = shift; my ($importer, $uid, $data) = @_; my $principal_uid = delete $data->{Principal}; my $principal_id = delete $data->{PrincipalId}; my $disabled = delete $data->{Disabled}; if (ref($data->{Name})) { my $role = $importer->LookupObj(${ $data->{Name} }); $data->{Name} = $role->GroupType; } # Inflate refs into their IDs $class->SUPER::PreInflate( $importer, $uid, $data ); # Factored out code, in case we find an existing version of this group my $obj = RT::Group->new( RT->SystemUser ); my $duplicated = sub { $importer->SkipTransactions( $uid ); $importer->Resolve( $principal_uid, ref($obj->PrincipalObj), $obj->PrincipalObj->Id ); $importer->Resolve( $uid => ref($obj), $obj->Id ); return; }; # Go looking for the pre-existing version of it if ($data->{Domain} eq "ACLEquivalence") { $obj->LoadACLEquivalenceGroup( $data->{Instance} ); return $duplicated->() if $obj->Id; # Update description for the new ID $data->{Description} = 'ACL equiv. for user '.$data->{Instance}; } elsif ($data->{Domain} eq "UserDefined") { $data->{Name} = $importer->Qualify($data->{Name}); $obj->LoadUserDefinedGroup( $data->{Name} ); if ($obj->Id) { $importer->MergeValues($obj, $data); return $duplicated->(); } } elsif ($data->{Domain} =~ /^(SystemInternal|RT::System-Role)$/) { $obj->LoadByCols( Domain => $data->{Domain}, Name => $data->{Name} ); return $duplicated->() if $obj->Id; } elsif ($data->{Domain} eq "RT::Queue-Role") { my $queue = RT::Queue->new( RT->SystemUser ); $queue->Load( $data->{Instance} ); $obj->LoadRoleGroup( Object => $queue, Name => $data->{Name} ); return $duplicated->() if $obj->Id; } my $principal = RT::Principal->new( RT->SystemUser ); my ($id) = $principal->Create( PrincipalType => 'Group', Disabled => $disabled, ); # Now we have a principal id, set the id for the group record $data->{id} = $id; $importer->Resolve( $principal_uid => ref($principal), $id ); $data->{id} = $id; return 1; } sub PostInflate { my $self = shift; my $cgm = RT::CachedGroupMember->new($self->CurrentUser); $cgm->Create( Group => $self->PrincipalObj, Member => $self->PrincipalObj, ImmediateParent => $self->PrincipalObj ); } # If this group represents the members of a custom role, then return # the RT::CustomRole object. Otherwise, return undef sub _CustomRoleObj { my $self = shift; if ($self->Domain =~ /-Role$/) { my ($id) = $self->Name =~ /^RT::CustomRole-(\d+)$/; if ($id) { my $role = RT::CustomRole->new($self->CurrentUser); $role->Load($id); return $role; } } return; } sub ModifyLinkRight {'ModifyGroupLinks'} =head2 URI Returns this group's URI =cut sub URI { my $self = shift; require RT::URI::group; my $uri = RT::URI::group->new($self->CurrentUser); return $uri->URIForObject($self); } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/SearchBuilder.pm��������������������������������������������������������������������000644 �000765 �000024 �00000100361 14005011336 017147� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::SearchBuilder - a baseclass for RT collection objects =head1 SYNOPSIS =head1 DESCRIPTION =head1 METHODS =cut package RT::SearchBuilder; use strict; use warnings; use 5.010; use base qw(DBIx::SearchBuilder RT::Base); use RT::Base; use DBIx::SearchBuilder "1.40"; use Scalar::Util qw/blessed/; sub _Init { my $self = shift; $self->{'user'} = shift; unless(defined($self->CurrentUser)) { use Carp; Carp::confess("$self was created without a CurrentUser"); $RT::Logger->err("$self was created without a CurrentUser"); return(0); } $self->SUPER::_Init( 'Handle' => $RT::Handle); } sub _Handle { return $RT::Handle } sub CleanSlate { my $self = shift; $self->{'_sql_aliases'} = {}; delete $self->{'handled_disabled_column'}; delete $self->{'find_disabled_rows'}; return $self->SUPER::CleanSlate(@_); } sub Join { my $self = shift; my %args = @_; $args{'DISTINCT'} = 1 if !exists $args{'DISTINCT'} && $args{'TABLE2'} && lc($args{'FIELD2'}||'') eq 'id'; return $self->SUPER::Join( %args ); } sub JoinTransactions { my $self = shift; my %args = ( New => 0, @_ ); return $self->{'_sql_aliases'}{'transactions'} if !$args{'New'} && $self->{'_sql_aliases'}{'transactions'}; my $alias = $self->Join( ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'Transactions', FIELD2 => 'ObjectId', ); # NewItem is necessary here because of RT::Report::Tickets and RT::Report::Tickets::Entry my $item = $self->NewItem; my $object_type = $item->can('ObjectType') ? $item->ObjectType : ref $item; $self->RT::SearchBuilder::Limit( LEFTJOIN => $alias, FIELD => 'ObjectType', VALUE => $object_type, ); $self->{'_sql_aliases'}{'transactions'} = $alias unless $args{'New'}; return $alias; } sub _OrderByCF { my $self = shift; my ($row, $cfkey, $cf) = @_; $cfkey .= ".ordering" if !blessed($cf) || ($cf->MaxValues||0) != 1; my ($ocfvs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf ); # this is described in _LimitCustomField $self->Limit( ALIAS => $CFs, FIELD => 'Name', OPERATOR => 'IS NOT', VALUE => 'NULL', ENTRYAGGREGATOR => 'AND', SUBCLAUSE => ".ordering", ) if $CFs; my $CFvs = $self->Join( TYPE => 'LEFT', ALIAS1 => $ocfvs, FIELD1 => 'CustomField', TABLE2 => 'CustomFieldValues', FIELD2 => 'CustomField', ); $self->Limit( LEFTJOIN => $CFvs, FIELD => 'Name', QUOTEVALUE => 0, VALUE => "$ocfvs.Content", ENTRYAGGREGATOR => 'AND' ); return { %$row, ALIAS => $CFvs, FIELD => 'SortOrder' }, { %$row, ALIAS => $ocfvs, FIELD => 'Content' }; } sub OrderByCols { my $self = shift; my @sort; for my $s (@_) { next if defined $s->{FIELD} and $s->{FIELD} =~ /\W/; $s->{FIELD} = $s->{FUNCTION} if $s->{FUNCTION}; push @sort, $s; } return $self->SUPER::OrderByCols( @sort ); } # If we're setting RowsPerPage or FirstRow, ensure we get a natural number or undef. sub RowsPerPage { my $self = shift; return if @_ and defined $_[0] and $_[0] =~ /\D/; return $self->SUPER::RowsPerPage(@_); } sub FirstRow { my $self = shift; return if @_ and defined $_[0] and $_[0] =~ /\D/; return $self->SUPER::FirstRow(@_); } =head2 LimitToEnabled Only find items that haven't been disabled =cut sub LimitToEnabled { my $self = shift; $self->{'handled_disabled_column'} = 1; $self->Limit( FIELD => 'Disabled', VALUE => '0' ); } =head2 LimitToDeleted Only find items that have been deleted. =cut sub LimitToDeleted { my $self = shift; $self->{'handled_disabled_column'} = $self->{'find_disabled_rows'} = 1; $self->Limit( FIELD => 'Disabled', VALUE => '1' ); } =head2 FindAllRows Find all matching rows, regardless of whether they are disabled or not =cut sub FindAllRows { shift->{'find_disabled_rows'} = 1; } =head2 LimitCustomField Takes a paramhash of key/value pairs with the following keys: =over 4 =item CUSTOMFIELD - CustomField id. Optional =item OPERATOR - The usual Limit operators =item VALUE - The value to compare against =back =cut sub _SingularClass { my $self = shift; my $class = ref($self) || $self; $class =~ s/s$// or die "Cannot deduce SingularClass for $class"; return $class; } =head2 RecordClass Returns class name of records in this collection. This generic implementation just strips trailing 's'. =cut sub RecordClass { $_[0]->_SingularClass } =head2 RegisterCustomFieldJoin Takes a pair of arguments, the first a class name and the second a callback function. The class will be used to call L<RT::Record/CustomFieldLookupType>. The callback will be called when limiting a collection of the caller's class by a CF of the passed class's lookup type. The callback is passed a single argument, the current collection object (C<$self>). An example from L<RT::Tickets>: __PACKAGE__->RegisterCustomFieldJoin( "RT::Transaction" => sub { $_[0]->JoinTransactions } ); Returns true on success, undef on failure. =cut sub RegisterCustomFieldJoin { my $class = shift; my ($type, $callback) = @_; $type = $type->CustomFieldLookupType if $type; die "Unknown LookupType '$type'" unless $type and grep { $_ eq $type } RT::CustomField->LookupTypes; die "Custom field join callbacks must be CODE references" unless ref($callback) eq 'CODE'; warn "Another custom field join callback is already registered for '$type'" if $class->_JOINS_FOR_LOOKUP_TYPES->{$type}; # Stash the callback on ourselves $class->_JOINS_FOR_LOOKUP_TYPES->{ $type } = $callback; return 1; } =head2 _JoinForLookupType Takes an L<RT::CustomField> LookupType and joins this collection as appropriate to reach the object records to which LookupType applies. The object records will be of the class returned by L<RT::CustomField/ObjectTypeFromLookupType>. Returns the join alias suitable for further limiting against object properties. Returns undef on failure. Used by L</_CustomFieldJoin>. =cut sub _JoinForLookupType { my $self = shift; my $type = shift or return; # Convenience shortcut so that classes don't need to register a handler # for their native lookup type return "main" if $type eq $self->RecordClass->CustomFieldLookupType and grep { $_ eq $type } RT::CustomField->LookupTypes; my $JOINS = $self->_JOINS_FOR_LOOKUP_TYPES; return $JOINS->{$type}->($self) if ref $JOINS->{$type} eq 'CODE'; return; } sub _JOINS_FOR_LOOKUP_TYPES { my $class = blessed($_[0]) || $_[0]; state %JOINS; return $JOINS{$class} ||= {}; } =head2 _CustomFieldJoin Factor out the Join of custom fields so we can use it for sorting too =cut sub _CustomFieldJoin { my ($self, $cfkey, $cf, $type) = @_; $type ||= $self->RecordClass->CustomFieldLookupType; # Perform one Join per CustomField if ( $self->{_sql_object_cfv_alias}{$cfkey} || $self->{_sql_cf_alias}{$cfkey} ) { return ( $self->{_sql_object_cfv_alias}{$cfkey}, $self->{_sql_cf_alias}{$cfkey} ); } my $ObjectAlias = $self->_JoinForLookupType($type) or die "We don't know how to join for LookupType $type"; my ($ocfvalias, $CFs); if ( blessed($cf) ) { $ocfvalias = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join( TYPE => 'LEFT', ALIAS1 => $ObjectAlias, FIELD1 => 'id', TABLE2 => 'ObjectCustomFieldValues', FIELD2 => 'ObjectId', $cf->SingleValue? (DISTINCT => 1) : (), ); $self->Limit( LEFTJOIN => $ocfvalias, FIELD => 'CustomField', VALUE => $cf->Disabled ? 0 : $cf->id, ENTRYAGGREGATOR => 'AND' ); } else { ($ocfvalias, $CFs) = $self->_CustomFieldJoinByName( $ObjectAlias, $cf, $type ); $self->{_sql_cf_alias}{$cfkey} = $CFs; $self->{_sql_object_cfv_alias}{$cfkey} = $ocfvalias; } $self->Limit( LEFTJOIN => $ocfvalias, FIELD => 'ObjectType', VALUE => RT::CustomField->ObjectTypeFromLookupType($type), ENTRYAGGREGATOR => 'AND' ); $self->Limit( LEFTJOIN => $ocfvalias, FIELD => 'Disabled', OPERATOR => '=', VALUE => '0', ENTRYAGGREGATOR => 'AND' ); return ($ocfvalias, $CFs); } sub _CustomFieldJoinByName { my $self = shift; my ($ObjectAlias, $cf, $type) = @_; my $ocfalias = $self->Join( TYPE => 'LEFT', EXPRESSION => q|'0'|, TABLE2 => 'ObjectCustomFields', FIELD2 => 'ObjectId', ); my $CFs = $self->Join( TYPE => 'LEFT', ALIAS1 => $ocfalias, FIELD1 => 'CustomField', TABLE2 => 'CustomFields', FIELD2 => 'id', ); $self->Limit( LEFTJOIN => $CFs, ENTRYAGGREGATOR => 'AND', FIELD => 'LookupType', VALUE => $type, ); $self->Limit( LEFTJOIN => $CFs, ENTRYAGGREGATOR => 'AND', FIELD => 'Name', CASESENSITIVE => 0, VALUE => $cf, ); $self->Limit( LEFTJOIN => $CFs, ENTRYAGGREGATOR => 'AND', FIELD => 'Disabled', VALUE => 0, ); my $ocfvalias = $self->Join( TYPE => 'LEFT', ALIAS1 => $CFs, FIELD1 => 'id', TABLE2 => 'ObjectCustomFieldValues', FIELD2 => 'CustomField', ); $self->Limit( LEFTJOIN => $ocfvalias, FIELD => 'ObjectId', VALUE => "$ObjectAlias.id", QUOTEVALUE => 0, ENTRYAGGREGATOR => 'AND', ); return ($ocfvalias, $CFs, $ocfalias); } sub LimitCustomField { my $self = shift; return $self->_LimitCustomField( @_ ); } use Regexp::Common qw(RE_net_IPv4); use Regexp::Common::net::CIDR; sub _LimitCustomField { my $self = shift; my %args = ( VALUE => undef, CUSTOMFIELD => undef, OPERATOR => '=', KEY => undef, PREPARSE => 1, @_ ); my $op = delete $args{OPERATOR}; my $value = delete $args{VALUE}; my $ltype = delete $args{LOOKUPTYPE} || $self->RecordClass->CustomFieldLookupType; my $cf = delete $args{CUSTOMFIELD}; my $column = delete $args{COLUMN}; my $cfkey = delete $args{KEY}; if (blessed($cf) and $cf->id) { $cfkey ||= $cf->id; # Make sure we can really see $cf unless ( $cf->CurrentUserHasRight('SeeCustomField') ) { my $obj = RT::CustomField->new( RT->SystemUser ); $obj->Load( $cf->id ); $cf = $obj; } } elsif ($cf =~ /^\d+$/) { # Intentionally load as the system user, so we can build better # queries; this is necessary as we don't have a context object # which might grant the user rights to see the CF. This object # is only used to inspect the properties of the CF itself. my $obj = RT::CustomField->new( RT->SystemUser ); $obj->Load($cf); if ($obj->id) { $cf = $obj; $cfkey ||= $cf->id; } else { $cfkey ||= "$ltype-$cf"; } } else { # Resolve CF by name for better queries, like the above block. my $cfs = RT::CustomFields->new( RT->SystemUser ); $cfs->LimitToLookupType($ltype); $cfs->Limit( FIELD => 'Name', VALUE => $cf, CASESENSITIVE => 0, ); if ( $cfs->Count == 1 ) { $cf = $cfs->Next; $cfkey ||= $cf->id; } else { $cfkey ||= "$ltype-$cf"; } } $args{SUBCLAUSE} ||= "cf-$cfkey"; my $fix_op = sub { return @_ unless RT->Config->Get('DatabaseType') eq 'Oracle'; my %args = @_; return %args unless $args{'FIELD'} eq 'LargeContent'; my $op = $args{'OPERATOR'}; if ( $op eq '=' ) { $args{'OPERATOR'} = 'MATCHES'; } elsif ( $op eq '!=' ) { $args{'OPERATOR'} = 'NOT MATCHES'; } elsif ( $op =~ /^[<>]=?$/ ) { $args{'FUNCTION'} = "TO_CHAR( $args{'ALIAS'}.LargeContent )"; } return %args; }; # Special Limit (we can exit early) # IS NULL and IS NOT NULL checks if ( $op =~ /^IS( NOT)?$/i ) { my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf, $ltype ); $self->_OpenParen( $args{SUBCLAUSE} ); $self->Limit( %args, ALIAS => $ocfvalias, FIELD => ($column || 'id'), OPERATOR => $op, VALUE => $value, ); # See below for an explanation of this limit $self->Limit( ALIAS => $CFs, FIELD => 'Name', OPERATOR => 'IS NOT', VALUE => 'NULL', ENTRYAGGREGATOR => 'AND', SUBCLAUSE => $args{SUBCLAUSE}, ) if $CFs; $self->_CloseParen( $args{SUBCLAUSE} ); return; } ########## Content pre-parsing if we know things about the CF if ( blessed($cf) and delete $args{PREPARSE} ) { my $type = $cf->Type; if ( $type eq 'IPAddress' ) { my $parsed = RT::ObjectCustomFieldValue->ParseIP($value); if ($parsed) { $value = $parsed; } else { $RT::Logger->warn("$value is not a valid IPAddress"); } } elsif ( $type eq 'IPAddressRange' ) { my ( $start_ip, $end_ip ) = RT::ObjectCustomFieldValue->ParseIPRange($value); if ( $start_ip && $end_ip ) { if ( $op =~ /^<=?$/ ) { $value = $start_ip; } elsif ($op =~ /^>=?$/ ) { $value = $end_ip; } else { $value = join '-', $start_ip, $end_ip; } } else { $RT::Logger->warn("$value is not a valid IPAddressRange"); } # Recurse if they want a range comparison if ( $op !~ /^[<>]=?$/ ) { my ($start_ip, $end_ip) = split /-/, $value; $self->_OpenParen( $args{SUBCLAUSE} ); # Ideally we would limit >= 000.000.000.000 and <= # 255.255.255.255 so DB optimizers could use better # estimations and scan less rows, but this breaks with IPv6. if ( $op !~ /NOT|!=|<>/i ) { # positive equation $self->_LimitCustomField( %args, OPERATOR => '<=', VALUE => $end_ip, LOOKUPTYPE => $ltype, CUSTOMFIELD => $cf, COLUMN => 'Content', PREPARSE => 0, ); $self->_LimitCustomField( %args, OPERATOR => '>=', VALUE => $start_ip, LOOKUPTYPE => $ltype, CUSTOMFIELD => $cf, COLUMN => 'LargeContent', ENTRYAGGREGATOR => 'AND', PREPARSE => 0, ); } else { # negative equation $self->_LimitCustomField( %args, OPERATOR => '>', VALUE => $end_ip, LOOKUPTYPE => $ltype, CUSTOMFIELD => $cf, COLUMN => 'Content', PREPARSE => 0, ); $self->_LimitCustomField( %args, OPERATOR => '<', VALUE => $start_ip, LOOKUPTYPE => $ltype, CUSTOMFIELD => $cf, COLUMN => 'LargeContent', ENTRYAGGREGATOR => 'OR', PREPARSE => 0, ); } $self->_CloseParen( $args{SUBCLAUSE} ); return; } } elsif ( $type =~ /^Date(?:Time)?$/ ) { my $date = RT::Date->new( $self->CurrentUser ); $date->Set( Format => 'unknown', Value => $value ); if ( $date->IsSet ) { if ( $type eq 'Date' # Heuristics to determine if a date, and not # a datetime, was entered: || $value =~ /^\s*(?:today|tomorrow|yesterday)\s*$/i || ( $value !~ /midnight|\d+:\d+:\d+/i && $date->Time( Timezone => 'user' ) eq '00:00:00' ) ) { $value = $date->Date( Timezone => 'user' ); } else { $value = $date->DateTime; } } else { $RT::Logger->warn("$value is not a valid date string"); } # Recurse if day equality is being checked on a datetime if ( $type eq 'DateTime' and $op eq '=' && $value !~ /:/ ) { my $date = RT::Date->new( $self->CurrentUser ); $date->Set( Format => 'unknown', Value => $value ); my $daystart = $date->ISO; $date->AddDay; my $dayend = $date->ISO; $self->_OpenParen( $args{SUBCLAUSE} ); $self->_LimitCustomField( %args, OPERATOR => ">=", VALUE => $daystart, LOOKUPTYPE => $ltype, CUSTOMFIELD => $cf, COLUMN => 'Content', ENTRYAGGREGATOR => 'AND', PREPARSE => 0, ); $self->_LimitCustomField( %args, OPERATOR => "<", VALUE => $dayend, LOOKUPTYPE => $ltype, CUSTOMFIELD => $cf, COLUMN => 'Content', ENTRYAGGREGATOR => 'AND', PREPARSE => 0, ); $self->_CloseParen( $args{SUBCLAUSE} ); return; } } } ########## Limits my $single_value = !blessed($cf) || $cf->SingleValue; my $negative_op = ($op eq '!=' || $op =~ /\bNOT\b/i); my $value_is_long = (length( Encode::encode( "UTF-8", $value)) > 255) ? 1 : 0; $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++ if not $single_value and $op =~ /^(!?=|(NOT )?LIKE)$/i; my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf, $ltype ); # A negative limit on a multi-value CF means _none_ of the values # are the given value if ( $negative_op and not $single_value ) { # Reverse the limit we apply to the join, and check IS NULL $op =~ s/!|NOT\s+//i; # Ideally we would check both Content and LargeContent here, as # the positive searches do below -- however, we cannot place # complex limits inside LEFTJOINs due to searchbuilder # limitations. Guessing which to check based on the value's # string length is sufficient for !=, but sadly insufficient for # NOT LIKE checks, giving false positives. $column ||= $value_is_long ? 'LargeContent' : 'Content'; $self->Limit( $fix_op->( LEFTJOIN => $ocfvalias, ALIAS => $ocfvalias, FIELD => $column, OPERATOR => $op, VALUE => $value, CASESENSITIVE => 0, ) ); $self->Limit( %args, ALIAS => $ocfvalias, FIELD => 'id', OPERATOR => 'IS', VALUE => 'NULL', ); return; } # If column is defined, then we just search it that, with no magic if ( $column ) { $self->_OpenParen( $args{SUBCLAUSE} ); $self->Limit( $fix_op->( %args, ALIAS => $ocfvalias, FIELD => $column, OPERATOR => $op, VALUE => $value, CASESENSITIVE => 0, ) ); $self->Limit( ALIAS => $ocfvalias, FIELD => $column, OPERATOR => 'IS', VALUE => 'NULL', ENTRYAGGREGATOR => 'OR', SUBCLAUSE => $args{SUBCLAUSE}, ) if $negative_op; $self->_CloseParen( $args{SUBCLAUSE} ); return; } $self->_OpenParen( $args{SUBCLAUSE} ); # For negative_op "OR it is null" clause $self->_OpenParen( $args{SUBCLAUSE} ); # NAME IS NOT NULL clause $self->_OpenParen( $args{SUBCLAUSE} ); # Check Content / LargeContent if ($value_is_long and $op eq "=") { # Doesn't matter what Content contains, as it cannot match the # too-long value; we just look in LargeContent, below. } elsif ($value_is_long and $op =~ /^(!=|<>)$/) { # If Content is non-null, that's a valid way to _not_ contain the too-long value. $self->Limit( %args, ALIAS => $ocfvalias, FIELD => 'Content', OPERATOR => 'IS NOT', VALUE => 'NULL', ); } else { # Otherwise, go looking at the Content $self->Limit( %args, ALIAS => $ocfvalias, FIELD => 'Content', OPERATOR => $op, VALUE => $value, CASESENSITIVE => 0, ); } if (!$value_is_long and $op eq "=") { # Doesn't matter what LargeContent contains, as it cannot match # the short value. } elsif (!$value_is_long and $op =~ /^(!=|<>)$/) { # If LargeContent is non-null, that's a valid way to _not_ # contain the too-short value. $self->Limit( %args, ALIAS => $ocfvalias, FIELD => 'LargeContent', OPERATOR => 'IS NOT', VALUE => 'NULL', ENTRYAGGREGATOR => 'OR', ); } else { $self->_OpenParen( $args{SUBCLAUSE} ); # LargeContent check $self->_OpenParen( $args{SUBCLAUSE} ); # Content is null? $self->Limit( ALIAS => $ocfvalias, FIELD => 'Content', OPERATOR => '=', VALUE => '', ENTRYAGGREGATOR => 'OR', SUBCLAUSE => $args{SUBCLAUSE}, ); $self->Limit( ALIAS => $ocfvalias, FIELD => 'Content', OPERATOR => 'IS', VALUE => 'NULL', ENTRYAGGREGATOR => 'OR', SUBCLAUSE => $args{SUBCLAUSE}, ); $self->_CloseParen( $args{SUBCLAUSE} ); # Content is null? $self->Limit( $fix_op->( ALIAS => $ocfvalias, FIELD => 'LargeContent', OPERATOR => $op, VALUE => $value, ENTRYAGGREGATOR => 'AND', SUBCLAUSE => $args{SUBCLAUSE}, CASESENSITIVE => 0, ) ); $self->_CloseParen( $args{SUBCLAUSE} ); # LargeContent check } $self->_CloseParen( $args{SUBCLAUSE} ); # Check Content/LargeContent # XXX: if we join via CustomFields table then # because of order of left joins we get NULLs in # CF table and then get nulls for those records # in OCFVs table what result in wrong results # as decifer method now tries to load a CF then # we fall into this situation only when there # are more than one CF with the name in the DB. # the same thing applies to order by call. # TODO: reorder joins T <- OCFVs <- CFs <- OCFs if # we want treat IS NULL as (not applies or has # no value) $self->Limit( ALIAS => $CFs, FIELD => 'Name', OPERATOR => 'IS NOT', VALUE => 'NULL', ENTRYAGGREGATOR => 'AND', SUBCLAUSE => $args{SUBCLAUSE}, ) if $CFs; $self->_CloseParen( $args{SUBCLAUSE} ); # Name IS NOT NULL clause # If we were looking for != or NOT LIKE, we need to include the # possibility that the row had no value. $self->Limit( ALIAS => $ocfvalias, FIELD => 'id', OPERATOR => 'IS', VALUE => 'NULL', ENTRYAGGREGATOR => 'OR', SUBCLAUSE => $args{SUBCLAUSE}, ) if $negative_op; $self->_CloseParen( $args{SUBCLAUSE} ); # negative_op clause } =head2 Limit PARAMHASH This Limit sub calls SUPER::Limit, but defaults "CASESENSITIVE" to 1, thus making sure that by default lots of things don't do extra work trying to match lower(colname) agaist lc($val); We also force VALUE to C<NULL> when the OPERATOR is C<IS> or C<IS NOT>. This ensures that we don't pass invalid SQL to the database or allow SQL injection attacks when we pass through user specified values. =cut my %check_case_sensitivity = ( groups => { 'name' => 1, domain => 1 }, queues => { 'name' => 1 }, users => { 'name' => 1, emailaddress => 1 }, customfields => { 'name' => 1 }, ); my %deprecated = ( ); sub Limit { my $self = shift; my %ARGS = ( OPERATOR => '=', @_, ); # We use the same regex here that DBIx::SearchBuilder uses to exclude # values from quoting if ( $ARGS{'OPERATOR'} =~ /IS/i ) { # Don't pass anything but NULL for IS and IS NOT $ARGS{'VALUE'} = 'NULL'; } if (($ARGS{FIELD}||'') =~ /\W/ or $ARGS{OPERATOR} !~ /^(=|<|>|!=|<>|<=|>= |(NOT\s*)?LIKE |(NOT\s*)?(STARTS|ENDS)WITH |(NOT\s*)?MATCHES |IS(\s*NOT)? |(NOT\s*)?IN |\@\@ |AGAINST)$/ix) { $RT::Logger->crit("Possible SQL injection attack: $ARGS{FIELD} $ARGS{OPERATOR}"); %ARGS = ( %ARGS, FIELD => 'id', OPERATOR => '<', VALUE => '0', ); } my $table; ($table) = $ARGS{'ALIAS'} && $ARGS{'ALIAS'} ne 'main' ? ($ARGS{'ALIAS'} =~ /^(.*)_\d+$/) : $self->Table ; if ( $table and $ARGS{FIELD} and my $instead = $deprecated{ lc $table }{ lc $ARGS{'FIELD'} } ) { RT->Deprecated( Message => "$table.$ARGS{'FIELD'} column is deprecated", Instead => $instead, Remove => '5.2' ); } unless ( exists $ARGS{CASESENSITIVE} or (exists $ARGS{QUOTEVALUE} and not $ARGS{QUOTEVALUE}) ) { if ( $ARGS{FIELD} and $ARGS{'OPERATOR'} !~ /IS/i && $table && $check_case_sensitivity{ lc $table }{ lc $ARGS{'FIELD'} } ) { RT->Logger->warning( "Case sensitive search by $table.$ARGS{'FIELD'}" ." at ". (caller)[1] . " line ". (caller)[2] ); } $ARGS{'CASESENSITIVE'} = 1; } return $self->SUPER::Limit( %ARGS ); } =head2 ItemsOrderBy If it has a SortOrder attribute, sort the array by SortOrder. Otherwise, if it has a "Name" attribute, sort alphabetically by Name Otherwise, just give up and return it in the order it came from the db. =cut sub ItemsOrderBy { my $self = shift; my $items = shift; if ($self->RecordClass->_Accessible('SortOrder','read')) { $items = [ sort { $a->SortOrder <=> $b->SortOrder } @{$items} ]; } elsif ($self->RecordClass->_Accessible('Name','read')) { $items = [ sort { lc($a->Name) cmp lc($b->Name) } @{$items} ]; } return $items; } =head2 ItemsArrayRef Return this object's ItemsArray, in the order that ItemsOrderBy sorts it. =cut sub ItemsArrayRef { my $self = shift; return $self->ItemsOrderBy($self->SUPER::ItemsArrayRef()); } # make sure that Disabled rows never get seen unless # we're explicitly trying to see them. sub _DoSearch { my $self = shift; if ( $self->{'with_disabled_column'} && !$self->{'handled_disabled_column'} && !$self->{'find_disabled_rows'} ) { $self->LimitToEnabled; } return $self->SUPER::_DoSearch(@_); } sub _DoCount { my $self = shift; if ( $self->{'with_disabled_column'} && !$self->{'handled_disabled_column'} && !$self->{'find_disabled_rows'} ) { $self->LimitToEnabled; } return $self->SUPER::_DoCount(@_); } =head2 ColumnMapClassName ColumnMap needs a Collection name to load the correct list display. Depluralization is hard, so provide an easy way to correct the naive algorithm that this code uses. =cut sub ColumnMapClassName { my $self = shift; my $Class = $self->_SingularClass; $Class =~ s/:/_/g; return $Class; } =head2 NewItem Returns a new item based on L</RecordClass> using the current user. =cut sub NewItem { my $self = shift; return $self->RecordClass->new($self->CurrentUser); } =head2 NotSetDateToNullFunction Takes a paramhash with an optional FIELD key whose value is the name of a date column. If no FIELD is provided, a literal C<?> placeholder is used so the caller can fill in the field later. Returns a SQL function which evaluates to C<NULL> if the FIELD is set to the Unix epoch; otherwise it evaluates to FIELD. This is useful because RT currently stores unset dates as a Unix epoch timestamp instead of NULL, but NULLs are often more desireable. =cut sub NotSetDateToNullFunction { my $self = shift; my %args = ( FIELD => undef, @_ ); my $res = "CASE WHEN ? BETWEEN '1969-12-31 11:59:59' AND '1970-01-01 12:00:01' THEN NULL ELSE ? END"; if ( $args{FIELD} ) { $res = $self->CombineFunctionWithField( %args, FUNCTION => $res ); } return $res; } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/CustomFieldValue.pm�����������������������������������������������������������������000644 �000765 �000024 �00000021306 14005011336 017647� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::CustomFieldValue; no warnings qw/redefine/; use base 'RT::Record'; use RT::CustomField; sub Table {'CustomFieldValues'} =head2 ValidateName Override the default ValidateName method that stops custom field values from being integers. =cut sub Create { my $self = shift; my %args = ( CustomField => 0, Name => '', Description => '', SortOrder => 0, Category => '', @_, ); my $cf_id = ref $args{'CustomField'}? $args{'CustomField'}->id: $args{'CustomField'}; my $cf = RT::CustomField->new( $self->CurrentUser ); $cf->Load( $cf_id ); unless ( $cf->id ) { return (0, $self->loc("Couldn't load Custom Field #[_1]", $cf_id)); } unless ( $cf->CurrentUserHasRight('AdminCustomField') || $cf->CurrentUserHasRight('AdminCustomFieldValues') ) { return (0, $self->loc('Permission Denied')); } my ($id, $msg) = $self->SUPER::Create( CustomField => $cf_id, map { $_ => $args{$_} } qw(Name Description SortOrder Category) ); return ($id, $msg); } sub ValidateName { return defined $_[1] && length $_[1]; }; sub Delete { my $self = shift; my ( $ret, $msg ) = $self->SUPER::Delete; $self->CustomFieldObj->CleanupDefaultValues; return ( $ret, $msg ); } sub _Set { my $self = shift; my %args = @_; my $cf_id = $self->CustomField; my $cf = RT::CustomField->new( $self->CurrentUser ); $cf->Load( $cf_id ); unless ( $cf->id ) { return (0, $self->loc("Couldn't load Custom Field #[_1]", $cf_id)); } unless ($cf->CurrentUserHasRight('AdminCustomField') || $cf->CurrentUserHasRight('AdminCustomFieldValues')) { return (0, $self->loc('Permission Denied')); } my ($ret, $msg) = $self->SUPER::_Set( @_ ); if ( $args{Field} eq 'Name' ) { $cf->CleanupDefaultValues; } return ($ret, $msg); } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 CustomField Returns the current value of CustomField. (In the database, CustomField is stored as int(11).) =head2 SetCustomField VALUE Set CustomField to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, CustomField will be stored as a int(11).) =head2 SetCustomFieldObj Store the CustomField object which loaded this CustomFieldValue. Passed down from the CustomFieldValues collection in AddRecord. This object will be transparently returned from CustomFieldObj rather than loading from the database. =cut sub SetCustomFieldObj { my $self = shift; return $self->{'custom_field'} = shift; } =head2 CustomFieldObj If a CustomField object was stored using SetCustomFieldObj and it is the same CustomField stored in the CustomField column, then the stored CustomField object (likely passed down from CustomField->Values) will be returned. Otherwise returns the CustomField Object which has the id returned by CustomField =cut sub CustomFieldObj { my $self = shift; return $self->{custom_field} if $self->{custom_field} and $self->{custom_field}->id == $self->__Value('CustomField'); my $CustomField = RT::CustomField->new($self->CurrentUser); $CustomField->Load($self->__Value('CustomField')); return($CustomField); } =head2 Name Returns the current value of Name. (In the database, Name is stored as varchar(200).) =head2 SetName VALUE Set Name to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Name will be stored as a varchar(200).) =cut =head2 Description Returns the current value of Description. (In the database, Description is stored as varchar(255).) =head2 SetDescription VALUE Set Description to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Description will be stored as a varchar(255).) =cut =head2 SortOrder Returns the current value of SortOrder. (In the database, SortOrder is stored as int(11).) =head2 SetSortOrder VALUE Set SortOrder to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, SortOrder will be stored as a int(11).) =cut =head2 Category Returns the current value of Category. (In the database, Category is stored as varchar(255).) =head2 SetCategory VALUE Set Category to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Category will be stored as a varchar(255).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, CustomField => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Name => {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, Description => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, SortOrder => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Category => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); $deps->Add( out => $self->CustomFieldObj ); } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/AuthToken.pm������������������������������������������������������������������������000644 �000765 �000024 �00000022245 14005011336 016341� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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; use 5.10.1; package RT::AuthToken; use base 'RT::Record'; require RT::User; require RT::Util; use Digest::SHA 'sha512_hex'; =head1 NAME RT::AuthToken - Represents an authentication token for a user =cut =head1 METHODS =head2 Create PARAMHASH Create takes a hash of values and creates a row in the database. Available keys are: =over 4 =item Owner The user ID for whom this token will authenticate. If it's not the AuthToken object's CurrentUser, then the AdminUsers permission is required. =item Description A human-readable description of what this token will be used for. =back Returns a tuple of (status, msg) on failure and (id, msg, authstring) on success. Note that this is the only time the authstring will be directly readable (as it is stored in the database hashed like a password, so use this opportunity to capture it. =cut sub Create { my $self = shift; my %args = ( Owner => undef, Description => '', @_, ); return (0, $self->loc("Permission Denied")) unless $self->CurrentUserHasRight('ManageAuthTokens'); return (0, $self->loc("Owner required")) unless $args{Owner}; return (0, $self->loc("Permission Denied")) unless $args{Owner} == $self->CurrentUser->Id || $self->CurrentUserHasRight('AdminUsers'); my $token = $self->_GenerateToken; my ( $id, $msg ) = $self->SUPER::Create( Token => $self->_CryptToken($token), map { $_ => $args{$_} } grep {exists $args{$_}} qw(Owner Description), ); unless ($id) { return (0, $self->loc("Authentication token create failed: [_1]", $msg)); } my $authstring = $self->_BuildAuthString($self->Owner, $token); return ($id, $self->loc('Authentication token created'), $authstring); } =head2 CurrentUserCanSee Returns true if the current user can see the AuthToken =cut sub CurrentUserCanSee { my $self = shift; return 0 unless $self->CurrentUserHasRight('ManageAuthTokens'); return 0 unless $self->__Value('Owner') == $self->CurrentUser->Id || $self->CurrentUserHasRight('AdminUsers'); return 1; } =head2 SetOwner Not permitted =cut sub SetOwner { my $self = shift; return (0, $self->loc("Permission Denied")); } =head2 SetToken Not permitted =cut sub SetToken { my $self = shift; return (0, $self->loc("Permission Denied")); } =head2 Delete Checks ACL =cut sub Delete { my $self = shift; return (0, $self->loc("Permission Denied")) unless $self->CurrentUserCanSee; my ($ok, $msg) = $self->SUPER::Delete(@_); return ($ok, $self->loc("Authentication token revoked.")) if $ok; return ($ok, $msg); } =head2 UpdateLastUsed Sets the "last used" time, without touching "last updated" =cut sub UpdateLastUsed { my $self = shift; my $now = RT::Date->new( $self->CurrentUser ); $now->SetToNow; return $self->__Set( Field => 'LastUsed', Value => $now->ISO, ); } =head2 ParseAuthString AUTHSTRING Class method that takes as input an authstring and provides a tuple of (user id, token) on success, or the empty list on failure. =cut sub ParseAuthString { my $class = shift; my $input = shift; my ($version) = $input =~ s/^([0-9]+)-// or return; if ($version == 1) { my ($user_id, $token) = $input =~ /^([0-9]+)-([0-9a-f]{32})$/i or return; return ($user_id, $token); } return; } =head2 IsToken Analogous to L<RT::User/IsPassword>, without all of the legacy password forms. =cut sub IsToken { my $self = shift; my $value = shift; my $stored = $self->__Value('Token'); # If it's a new-style (>= RT 4.0) password, it starts with a '!' my (undef, $method, @rest) = split /!/, $stored; if ($method eq "bcrypt") { if (RT::Util->can('constant_time_eq')) { return 0 unless RT::Util::constant_time_eq( $self->_CryptToken_bcrypt($value, @rest), $stored, ); } else { return 0 unless $self->_CryptToken_bcrypt($value, @rest) eq $stored; } # Upgrade to a larger number of rounds if necessary return 1 unless $rest[0] < RT->Config->Get('BcryptCost'); } else { $RT::Logger->warn("Unknown hash method $method"); return 0; } # We got here by validating successfully, but with a legacy # password form. Update to the most recent form. $self->_Set(Field => 'Token', Value => $self->_CryptToken($value)); return 1; } =head2 LastUsedObj L</LastUsed> as an L<RT::Date> object. =cut sub LastUsedObj { my $self = shift; my $date = RT::Date->new($self->CurrentUser); $date->Set(Format => 'sql', Value => $self->LastUsed); return $date; } =head1 PRIVATE METHODS Documented for internal use only, do not call these from outside RT::AuthToken itself. =head2 _Set Checks if the current user can I<ManageAuthTokens> before calling C<SUPER::_Set>. =cut sub _Set { my $self = shift; my %args = ( Field => undef, Value => undef, @_ ); return (0, $self->loc("Permission Denied")) unless $self->CurrentUserCanSee; return $self->SUPER::_Set(@_); } =head2 _Value Checks L</CurrentUserCanSee> before calling C<SUPER::_Value>. =cut sub _Value { my $self = shift; return unless $self->CurrentUserCanSee; return $self->SUPER::_Value(@_); } =head2 _GenerateToken Generates an unpredictable auth token =cut sub _GenerateToken { my $class = shift; require Time::HiRes; my $input = join '', Time::HiRes::time(), # subsecond-precision time {}, # unpredictable memory address rand(); # RNG my $digest = sha512_hex($input); return substr($digest, 0, 32); } =head2 _BuildAuthString Takes a user id and token and provides an authstring for use in place of a (username, password) combo. =cut sub _BuildAuthString { my $self = shift; my $version = 1; my $userid = shift; my $token = shift; return $version . '-' . $userid . '-' . $token; } sub _CryptToken_bcrypt { my $self = shift; return $self->CurrentUser->UserObj->_GeneratePassword_bcrypt(@_); } sub _CryptToken { my $self = shift; return $self->_CryptToken_bcrypt(@_); } sub Table { "AuthTokens" } sub _CoreAccessible { { id => { read => 1, type => 'int(11)', default => '' }, Owner => { read => 1, type => 'int(11)', default => '0' }, Token => { read => 1, sql_type => 12, length => 256, is_blob => 0, is_numeric => 0, type => 'varchar(256)', default => ''}, Description => { read => 1, type => 'varchar(255)', default => '', write => 1 }, LastUsed => { read => 1, type => 'datetime', default => '', write => 1 }, Creator => { read => 1, type => 'int(11)', default => '0', auto => 1 }, Created => { read => 1, type => 'datetime', default => '', auto => 1 }, LastUpdatedBy => { read => 1, type => 'int(11)', default => '0', auto => 1 }, LastUpdated => { read => 1, type => 'datetime', default => '', auto => 1 }, } } 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Class.pm����������������������������������������������������������������������������000644 �000765 �000024 �00000035257 14005011336 015513� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Class; use strict; use warnings; use base 'RT::Record'; use RT::System; use RT::CustomFields; use RT::ACL; use RT::Articles; use RT::ObjectClass; use RT::ObjectClasses; use Role::Basic 'with'; with "RT::Record::Role::Rights"; sub Table {'Classes'} =head2 Load IDENTIFIER Loads a class, either by name or by id =cut sub Load { my $self = shift; my $id = shift ; return unless $id; if ( $id =~ /^\d+$/ ) { $self->SUPER::Load($id); } else { $self->LoadByCols( Name => $id ); } } __PACKAGE__->AddRight( Staff => SeeClass => 'See that this class exists'); # loc __PACKAGE__->AddRight( Staff => CreateArticle => 'Create articles in this class'); # loc __PACKAGE__->AddRight( General => ShowArticle => 'See articles in this class'); # loc __PACKAGE__->AddRight( Staff => ShowArticleHistory => 'See changes to articles in this class'); # loc __PACKAGE__->AddRight( General => SeeCustomField => 'View custom field values' ); # loc __PACKAGE__->AddRight( Staff => ModifyArticle => 'Modify articles in this class'); # loc __PACKAGE__->AddRight( Staff => ModifyArticleTopics => 'Modify topics for articles in this class'); # loc __PACKAGE__->AddRight( Staff => ModifyCustomField => 'Modify custom field values' ); # loc __PACKAGE__->AddRight( Staff => SetInitialCustomField => 'Add custom field values only at object creation time'); # loc __PACKAGE__->AddRight( Admin => AdminClass => 'Modify metadata and custom fields for this class'); # loc __PACKAGE__->AddRight( Admin => AdminTopics => 'Modify topic hierarchy associated with this class'); # loc __PACKAGE__->AddRight( Admin => ShowACL => 'Display Access Control List'); # loc __PACKAGE__->AddRight( Admin => ModifyACL => 'Create, modify and delete Access Control List entries'); # loc __PACKAGE__->AddRight( Staff => DisableArticle => 'Disable articles in this class'); # loc # {{{ Create =head2 Create PARAMHASH Create takes a hash of values and creates a row in the database: varchar(255) 'Name'. varchar(255) 'Description'. int(11) 'SortOrder'. =cut sub Create { my $self = shift; my %args = ( Name => '', Description => '', SortOrder => '0', @_ ); unless ( $self->CurrentUser->HasRight( Right => 'AdminClass', Object => $RT::System ) ) { return ( 0, $self->loc('Permission Denied') ); } $self->SUPER::Create( Name => $args{'Name'}, Description => $args{'Description'}, SortOrder => $args{'SortOrder'}, ); } sub ValidateName { my $self = shift; my $newval = shift; return undef unless ($newval); my $obj = RT::Class->new($RT::SystemUser); $obj->Load($newval); return undef if $obj->id && ( !$self->id || $self->id != $obj->id ); return $self->SUPER::ValidateName($newval); } # }}} # }}} # {{{ ACCESS CONTROL # {{{ sub _Set sub _Set { my $self = shift; unless ( $self->CurrentUserHasRight('AdminClass') ) { return ( 0, $self->loc('Permission Denied') ); } return ( $self->SUPER::_Set(@_) ); } # }}} # {{{ sub _Value sub _Value { my $self = shift; unless ( $self->CurrentUserHasRight('SeeClass') ) { return (undef); } return ( $self->__Value(@_) ); } # }}} sub ArticleCustomFields { my $self = shift; my $cfs = RT::CustomFields->new( $self->CurrentUser ); if ( $self->CurrentUserHasRight('SeeClass') ) { $cfs->SetContextObject( $self ); $cfs->LimitToGlobalOrObjectId( $self->Id ); $cfs->LimitToLookupType( RT::Article->CustomFieldLookupType ); $cfs->ApplySortOrder; } return ($cfs); } =head1 AppliedTo Returns collection of Queues this Class is applied to. Doesn't takes into account if object is applied globally. =cut sub AppliedTo { my $self = shift; my ($res, $ocfs_alias) = $self->_AppliedTo; return $res unless $res; $res->Limit( ALIAS => $ocfs_alias, FIELD => 'id', OPERATOR => 'IS NOT', VALUE => 'NULL', ); return $res; } =head1 NotAppliedTo Returns collection of Queues this Class is not applied to. Doesn't takes into account if object is applied globally. =cut sub NotAppliedTo { my $self = shift; my ($res, $ocfs_alias) = $self->_AppliedTo; return $res unless $res; $res->Limit( ALIAS => $ocfs_alias, FIELD => 'id', OPERATOR => 'IS', VALUE => 'NULL', ); return $res; } sub _AppliedTo { my $self = shift; my $res = RT::Queues->new( $self->CurrentUser ); $res->OrderBy( FIELD => 'Name' ); my $ocfs_alias = $res->Join( TYPE => 'LEFT', ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'ObjectClasses', FIELD2 => 'ObjectId', ); $res->Limit( LEFTJOIN => $ocfs_alias, ALIAS => $ocfs_alias, FIELD => 'Class', VALUE => $self->id, ); return ($res, $ocfs_alias); } =head2 IsApplied Takes object id and returns corresponding L<RT::ObjectClass> record if this Class is applied to the object. Use 0 to check if Class is applied globally. =cut sub IsApplied { my $self = shift; my $id = shift; return unless defined $id; my $oc = RT::ObjectClass->new( $self->CurrentUser ); $oc->LoadByCols( Class=> $self->id, ObjectId => $id, ObjectType => ( $id ? 'RT::Queue' : 'RT::System' )); return undef unless $oc->id; return $oc; } =head2 AddToObject OBJECT Apply this Class to a single object, to start with we support Queues Takes an object =cut sub AddToObject { my $self = shift; my $object = shift; my $id = $object->Id || 0; unless ( $object->CurrentUserHasRight('AdminClass') ) { return ( 0, $self->loc('Permission Denied') ); } my $queue = RT::Queue->new( $self->CurrentUser ); if ( $id ) { my ($ok, $msg) = $queue->Load( $id ); unless ($ok) { return ( 0, $self->loc('Invalid Queue, unable to apply Class: [_1]',$msg ) ); } } if ( $self->IsApplied( $id ) ) { return ( 0, $self->loc("Class is already applied to [_1]",$queue->Name) ); } if ( $id ) { # applying locally return (0, $self->loc("Class is already applied Globally") ) if $self->IsApplied( 0 ); } else { my $applied = RT::ObjectClasses->new( $self->CurrentUser ); $applied->LimitToClass( $self->id ); while ( my $record = $applied->Next ) { $record->Delete; } } my $oc = RT::ObjectClass->new( $self->CurrentUser ); my ( $oid, $msg ) = $oc->Create( ObjectId => $id, Class => $self->id, ObjectType => ( $id ? 'RT::Queue' : 'RT::System' ), ); return ( $oid, $msg ); } =head2 RemoveFromObject OBJECT Remove this class from a single queue object =cut sub RemoveFromObject { my $self = shift; my $object = shift; my $id = $object->Id || 0; unless ( $object->CurrentUserHasRight('AdminClass') ) { return ( 0, $self->loc('Permission Denied') ); } my $ocf = $self->IsApplied( $id ); unless ( $ocf ) { return ( 0, $self->loc("This class does not apply to that object") ); } # XXX: Delete doesn't return anything my ( $oid, $msg ) = $ocf->Delete; return ( $oid, $msg ); } sub SubjectOverride { my $self = shift; my $override = $self->FirstAttribute('SubjectOverride'); return $override ? $override->Content : 0; } sub SetSubjectOverride { my $self = shift; my $override = shift; if ( $override == $self->SubjectOverride ) { return (0, "SubjectOverride is already set to that"); } my $cf = RT::CustomField->new($self->CurrentUser); $cf->Load($override); if ( $override ) { my ($ok, $msg) = $self->SetAttribute( Name => 'SubjectOverride', Content => $override ); return ($ok, $ok ? $self->loc('Added Subject Override: [_1]', $cf->Name) : $self->loc('Unable to add Subject Override: [_1] [_2]', $cf->Name, $msg)); } else { my ($ok, $msg) = $self->DeleteAttribute('SubjectOverride'); return ($ok, $ok ? $self->loc('Removed Subject Override') : $self->loc('Unable to add Subject Override: [_1] [_2]', $cf->Name, $msg)); } } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 Name Returns the current value of Name. (In the database, Name is stored as varchar(255).) =head2 SetName VALUE Set Name to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Name will be stored as a varchar(255).) =cut =head2 Description Returns the current value of Description. (In the database, Description is stored as varchar(255).) =head2 SetDescription VALUE Set Description to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Description will be stored as a varchar(255).) =cut =head2 SortOrder Returns the current value of SortOrder. (In the database, SortOrder is stored as int(11).) =head2 SetSortOrder VALUE Set SortOrder to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, SortOrder will be stored as a int(11).) =cut =head2 Disabled Returns the current value of Disabled. (In the database, Disabled is stored as int(2).) =head2 SetDisabled VALUE Set Disabled to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Disabled will be stored as a int(2).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, type => 'int(11)', default => ''}, Name => {read => 1, write => 1, type => 'varchar(255)', default => ''}, Description => {read => 1, write => 1, type => 'varchar(255)', default => ''}, SortOrder => {read => 1, write => 1, type => 'int(11)', default => '0'}, Disabled => {read => 1, write => 1, type => 'int(2)', default => '0'}, Creator => {read => 1, auto => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, type => 'datetime', default => ''}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); my $articles = RT::Articles->new( $self->CurrentUser ); $articles->Limit( FIELD => "Class", VALUE => $self->Id ); $deps->Add( in => $articles ); my $topics = RT::Topics->new( $self->CurrentUser ); $topics->LimitToObject( $self ); $deps->Add( in => $topics ); my $objectclasses = RT::ObjectClasses->new( $self->CurrentUser ); $objectclasses->LimitToClass( $self->Id ); $deps->Add( in => $objectclasses ); # Custom Fields on things _in_ this class (CFs on the class itself # have already been dealt with) my $ocfs = RT::ObjectCustomFields->new( $self->CurrentUser ); $ocfs->Limit( FIELD => 'ObjectId', OPERATOR => '=', VALUE => $self->id, ENTRYAGGREGATOR => 'OR' ); $ocfs->Limit( FIELD => 'ObjectId', OPERATOR => '=', VALUE => 0, ENTRYAGGREGATOR => 'OR' ); my $cfs = $ocfs->Join( ALIAS1 => 'main', FIELD1 => 'CustomField', TABLE2 => 'CustomFields', FIELD2 => 'id', ); $ocfs->Limit( ALIAS => $cfs, FIELD => 'LookupType', OPERATOR => 'STARTSWITH', VALUE => 'RT::Class-' ); $deps->Add( in => $ocfs ); } sub PreInflate { my $class = shift; my ($importer, $uid, $data) = @_; $class->SUPER::PreInflate( $importer, $uid, $data ); return if $importer->MergeBy( "Name", $class, $uid, $data ); return 1; } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Assets.pm���������������������������������������������������������������������������000644 �000765 �000024 �00000154332 14005011336 015704� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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; use 5.010; package RT::Assets; use base 'RT::SearchBuilder'; use Role::Basic "with"; with "RT::SearchBuilder::Role::Roles" => { -rename => {RoleLimit => '_RoleLimit'}}; use Scalar::Util qw/blessed/; # Configuration Tables: # FIELD_METADATA is a mapping of searchable Field name, to Type, and other # metadata. our %FIELD_METADATA = ( id => [ 'ID', ], #loc_left_pair Name => [ 'STRING', ], #loc_left_pair Description => [ 'STRING', ], #loc_left_pair Status => [ 'STRING', ], #loc_left_pair Catalog => [ 'ENUM' => 'Catalog', ], #loc_left_pair LastUpdated => [ 'DATE' => 'LastUpdated', ], #loc_left_pair Created => [ 'DATE' => 'Created', ], #loc_left_pair Linked => [ 'LINK' ], #loc_left_pair LinkedTo => [ 'LINK' => 'To' ], #loc_left_pair LinkedFrom => [ 'LINK' => 'From' ], #loc_left_pair MemberOf => [ 'LINK' => To => 'MemberOf', ], #loc_left_pair DependsOn => [ 'LINK' => To => 'DependsOn', ], #loc_left_pair RefersTo => [ 'LINK' => To => 'RefersTo', ], #loc_left_pair HasMember => [ 'LINK' => From => 'MemberOf', ], #loc_left_pair DependentOn => [ 'LINK' => From => 'DependsOn', ], #loc_left_pair DependedOnBy => [ 'LINK' => From => 'DependsOn', ], #loc_left_pair ReferredToBy => [ 'LINK' => From => 'RefersTo', ], #loc_left_pair Owner => [ 'WATCHERFIELD' => 'Owner', ], #loc_left_pair OwnerGroup => [ 'MEMBERSHIPFIELD' => 'Owner', ], #loc_left_pair HeldBy => [ 'WATCHERFIELD' => 'HeldBy', ], #loc_left_pair HeldByGroup => [ 'MEMBERSHIPFIELD' => 'HeldBy', ], #loc_left_pair Contact => [ 'WATCHERFIELD' => 'Contact', ], #loc_left_pair ContactGroup => [ 'MEMBERSHIPFIELD' => 'Contact', ], #loc_left_pair CustomFieldValue => [ 'CUSTOMFIELD' => 'Asset' ], #loc_left_pair CustomField => [ 'CUSTOMFIELD' => 'Asset' ], #loc_left_pair CF => [ 'CUSTOMFIELD' => 'Asset' ], #loc_left_pair Lifecycle => [ 'LIFECYCLE' ], #loc_left_pair ); # Lower Case version of FIELDS, for case insensitivity our %LOWER_CASE_FIELDS = map { ( lc($_) => $_ ) } (keys %FIELD_METADATA); our %SEARCHABLE_SUBFIELDS = ( User => [qw( EmailAddress Name RealName Nickname Organization Address1 Address2 City State Zip Country WorkPhone HomePhone MobilePhone PagerPhone id )], ); # Mapping of Field Type to Function our %dispatch = ( ENUM => \&_EnumLimit, INT => \&_IntLimit, ID => \&_IdLimit, LINK => \&_LinkLimit, DATE => \&_DateLimit, STRING => \&_StringLimit, WATCHERFIELD => \&_WatcherLimit, MEMBERSHIPFIELD => \&_WatcherMembershipLimit, CUSTOMFIELD => \&_CustomFieldLimit, LIFECYCLE => \&_LifecycleLimit, # HASATTRIBUTE => \&_HasAttributeLimit, ); # Default EntryAggregator per type # if you specify OP, you must specify all valid OPs my %DefaultEA = ( INT => 'AND', ENUM => { '=' => 'OR', '!=' => 'AND' }, DATE => { 'IS' => 'OR', 'IS NOT' => 'OR', '=' => 'OR', '>=' => 'AND', '<=' => 'AND', '>' => 'AND', '<' => 'AND' }, STRING => { '=' => 'OR', '!=' => 'AND', 'LIKE' => 'AND', 'NOT LIKE' => 'AND' }, LINK => 'OR', LINKFIELD => 'AND', TARGET => 'AND', BASE => 'AND', WATCHERFIELD => { '=' => 'OR', '!=' => 'AND', 'LIKE' => 'OR', 'NOT LIKE' => 'AND' }, HASATTRIBUTE => { '=' => 'AND', '!=' => 'AND', }, CUSTOMFIELD => 'OR', ); sub FIELDS { return \%FIELD_METADATA } =head1 NAME RT::Assets - a collection of L<RT::Asset> objects =head1 METHODS Only additional methods or overridden behaviour beyond the L<RT::SearchBuilder> (itself a L<DBIx::SearchBuilder>) class are documented below. =cut sub Count { my $self = shift; $self->_ProcessRestrictions() if ( $self->{'RecalcAssetLimits'} == 1 ); return ( $self->SUPER::Count() ); } sub CountAll { my $self = shift; $self->_ProcessRestrictions() if ( $self->{'RecalcAssetLimits'} == 1 ); return ( $self->SUPER::CountAll() ); } sub ItemsArrayRef { my $self = shift; return $self->{'items_array'} if $self->{'items_array'}; my $placeholder = $self->_ItemsCounter; $self->GotoFirstItem(); while ( my $item = $self->Next ) { push( @{ $self->{'items_array'} }, $item ); } $self->GotoItem($placeholder); $self->{'items_array'} ||= []; $self->{'items_array'} = $self->ItemsOrderBy( $self->{'items_array'} ); return $self->{'items_array'}; } sub ItemsArrayRefWindow { my $self = shift; my $window = shift; my @old = ($self->_ItemsCounter, $self->RowsPerPage, $self->FirstRow+1); $self->RowsPerPage( $window ); $self->FirstRow(1); $self->GotoFirstItem; my @res; while ( my $item = $self->Next ) { push @res, $item; } $self->RowsPerPage( $old[1] ); $self->FirstRow( $old[2] ); $self->GotoItem( $old[0] ); return \@res; } sub Next { my $self = shift; $self->_ProcessRestrictions() if ( $self->{'RecalcAssetLimits'} == 1 ); my $Asset = $self->SUPER::Next; return $Asset unless $Asset; if ( $Asset->__Value('Status') eq 'deleted' && !$self->{'allow_deleted_search'} ) { return $self->Next; } elsif ( RT->Config->Get('UseSQLForACLChecks') ) { # if we found an asset with this option enabled then # all assets we found are ACLed, cache this fact my $key = join ";:;", $self->CurrentUser->id, 'ShowAsset', 'RT::Asset-'. $Asset->id; $RT::Principal::_ACL_CACHE->{ $key } = 1; return $Asset; } elsif ( $Asset->CurrentUserHasRight('ShowAsset') ) { # has rights return $Asset; } else { # If the user doesn't have the right to show this asset return $self->Next; } } =head2 LimitToActiveStatus =cut sub LimitToActiveStatus { my $self = shift; $self->Limit( FIELD => 'Status', VALUE => $_ ) for RT::Catalog->LifecycleObj->Valid('initial', 'active'); } =head2 LimitCatalog Limit Catalog =cut sub LimitCatalog { my $self = shift; my %args = ( FIELD => 'Catalog', OPERATOR => '=', @_ ); if ( $args{OPERATOR} eq '=' ) { $self->{Catalog} = $args{VALUE}; } $self->SUPER::Limit(%args); } =head2 Limit Defaults CASESENSITIVE to 0 =cut sub Limit { my $self = shift; my %args = ( CASESENSITIVE => 0, @_ ); $self->{'must_redo_search'} = 1; delete $self->{'raw_rows'}; delete $self->{'count_all'}; if ($self->{'using_restrictions'}) { RT->Deprecated( Message => "Mixing old-style LimitFoo methods with Limit is deprecated" ); $self->LimitField(@_); } $args{SUBCLAUSE} ||= "assetsql" if $self->{parsing_assetsql} and not $args{LEFTJOIN}; $self->{_sql_looking_at}{ lc $args{FIELD} } = 1 if $args{FIELD} and (not $args{ALIAS} or $args{ALIAS} eq "main"); $self->SUPER::Limit(%args); } =head2 RoleLimit Re-uses the underlying JOIN, if possible. =cut sub RoleLimit { my $self = shift; my %args = ( TYPE => '', SUBCLAUSE => '', OPERATOR => '=', @_ ); my $key = "role-join-".join("-",map {$args{$_}//''} qw/SUBCLAUSE TYPE OPERATOR/); my @ret = $self->_RoleLimit(%args, BUNDLE => $self->{$key} ); $self->{$key} = \@ret; } =head2 LimitField Takes a paramhash with the fields FIELD, OPERATOR, VALUE and DESCRIPTION Generally best called from LimitFoo methods =cut sub LimitField { my $self = shift; my %args = ( FIELD => undef, OPERATOR => '=', VALUE => undef, DESCRIPTION => undef, @_ ); $args{'DESCRIPTION'} = $self->loc( "[_1] [_2] [_3]", $args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'} ) if ( !defined $args{'DESCRIPTION'} ); if ($self->_isLimited > 1) { RT->Deprecated( Message => "Mixing old-style LimitFoo methods with Limit is deprecated" ); } $self->{using_restrictions} = 1; my $index = $self->_NextIndex; # make the TicketRestrictions hash the equivalent of whatever we just passed in; %{ $self->{'TicketRestrictions'}{$index} } = %args; $self->{'RecalcTicketLimits'} = 1; return ($index); } =head1 INTERNAL METHODS Public methods which encapsulate implementation details. You shouldn't need to call these in normal code. =head2 AddRecord Checks the L<RT::Asset> is readable before adding it to the results =cut sub AddRecord { my $self = shift; my $asset = shift; return unless $asset->CurrentUserCanSee; return if $asset->__Value('Status') eq 'deleted' and not $self->{'allow_deleted_search'}; $self->SUPER::AddRecord($asset, @_); } =head1 PRIVATE METHODS =head2 _Init Sets default ordering by Name ascending. =cut sub _Init { my $self = shift; $self->{'table'} = "Assets"; $self->{'RecalcAssetLimits'} = 1; $self->{'restriction_index'} = 1; $self->{'primary_key'} = "id"; delete $self->{'items_array'}; delete $self->{'item_map'}; delete $self->{'columns_to_display'}; $self->OrderBy( FIELD => 'Name', ORDER => 'ASC' ); $self->SUPER::_Init(@_); $self->_InitSQL(); } sub _InitSQL { my $self = shift; # Private Member Variables (which should get cleaned) $self->{'_sql_cf_alias'} = undef; $self->{'_sql_object_cfv_alias'} = undef; $self->{'_sql_watcher_join_users_alias'} = undef; $self->{'_sql_query'} = ''; $self->{'_sql_looking_at'} = {}; } sub SimpleSearch { my $self = shift; my %args = ( Fields => RT->Config->Get('AssetSearchFields'), Catalog => RT->Config->Get('DefaultCatalog'), Term => undef, @_ ); # XXX: We only search a single catalog so that we can map CF names # to their ids, as searching CFs by CF name is rather complicated # and currently fails in odd ways. Such a mapping obviously assumes # that names are unique within the catalog, but ids are also # allowable as well. my $catalog; if (ref $args{Catalog}) { $catalog = $args{Catalog}; } else { $catalog = RT::Catalog->new( $self->CurrentUser ); $catalog->Load( $args{Catalog} ); } my %cfs; my $cfs = $catalog->AssetCustomFields; while (my $customfield = $cfs->Next) { $cfs{$customfield->id} = $cfs{$customfield->Name} = $customfield; } $self->LimitCatalog( VALUE => $catalog->id ); while (my ($name, $op) = each %{$args{Fields}}) { $op = 'STARTSWITH' unless $op =~ /^(?:LIKE|(?:START|END)SWITH|=|!=)$/i; if ($name =~ /^CF\.(?:\{(.*)}|(.*))$/) { my $cfname = $1 || $2; $self->LimitCustomField( CUSTOMFIELD => $cfs{$cfname}, OPERATOR => $op, VALUE => $args{Term}, ENTRYAGGREGATOR => 'OR', SUBCLAUSE => 'autocomplete', ) if $cfs{$cfname}; } elsif ($name eq 'id' and $op =~ /(?:LIKE|(?:START|END)SWITH)$/i) { $self->Limit( FUNCTION => "CAST( main.$name AS TEXT )", OPERATOR => $op, VALUE => $args{Term}, ENTRYAGGREGATOR => 'OR', SUBCLAUSE => 'autocomplete', ) if $args{Term} =~ /^\d+$/; } else { $self->Limit( FIELD => $name, OPERATOR => $op, VALUE => $args{Term}, ENTRYAGGREGATOR => 'OR', SUBCLAUSE => 'autocomplete', ) unless $args{Term} =~ /\D/ and $name eq 'id'; } } return $self; } sub OrderByCols { my $self = shift; my @res = (); my $class = $self->_RoleGroupClass; for my $row (@_) { if ($row->{FIELD} =~ /^(?:CF|CustomField)\.(?:\{(.*)\}|(.*))$/) { my $name = $1 || $2; my $cf = RT::CustomField->new( $self->CurrentUser ); $cf->LoadByNameAndCatalog( Name => $name, Catalog => $self->{'Catalog'}, ); if ( $cf->id ) { push @res, $self->_OrderByCF( $row, $cf->id, $cf ); } } elsif ($row->{FIELD} =~ /^(\w+)(?:\.(\w+))?$/) { my ($role, $subkey) = ($1, $2); if ($class->HasRole($role)) { $self->{_order_by_role}{ $role } ||= ( $self->_WatcherJoin( Name => $role, Class => $class) )[2]; push @res, { %$row, ALIAS => $self->{_order_by_role}{ $role }, FIELD => $subkey || 'EmailAddress', }; } else { push @res, $row; } } else { push @res, $row; } } return $self->SUPER::OrderByCols( @res ); } =head2 _DoSearch =head2 _DoCount Limits to non-deleted assets unless the C<allow_deleted_search> flag is set. =cut sub _DoSearch { my $self = shift; $self->Limit( FIELD => 'Status', OPERATOR => '!=', VALUE => 'deleted', SUBCLAUSE => "not_deleted" ) unless $self->{ 'allow_deleted_search' }; $self->CurrentUserCanSee if RT->Config->Get('UseSQLForACLChecks'); return $self->SUPER::_DoSearch( @_ ); } sub _DoCount { my $self = shift; $self->Limit( FIELD => 'Status', OPERATOR => '!=', VALUE => 'deleted', SUBCLAUSE => "not_deleted" ) unless $self->{ 'allow_deleted_search' }; $self->CurrentUserCanSee if RT->Config->Get('UseSQLForACLChecks'); return $self->SUPER::_DoCount( @_ ); } sub _RolesCanSee { my $self = shift; my $cache_key = 'RolesHasRight;:;ShowAsset'; if ( my $cached = $RT::Principal::_ACL_CACHE->{ $cache_key } ) { return %$cached; } my $ACL = RT::ACL->new( RT->SystemUser ); $ACL->Limit( FIELD => 'RightName', VALUE => 'ShowAsset' ); $ACL->Limit( FIELD => 'PrincipalType', OPERATOR => '!=', VALUE => 'Group' ); my $principal_alias = $ACL->Join( ALIAS1 => 'main', FIELD1 => 'PrincipalId', TABLE2 => 'Principals', FIELD2 => 'id', ); $ACL->Limit( ALIAS => $principal_alias, FIELD => 'Disabled', VALUE => 0 ); my %res = (); foreach my $ACE ( @{ $ACL->ItemsArrayRef } ) { my $role = $ACE->__Value('PrincipalType'); my $type = $ACE->__Value('ObjectType'); if ( $type eq 'RT::System' ) { $res{ $role } = 1; } elsif ( $type eq 'RT::Catalog' ) { next if $res{ $role } && !ref $res{ $role }; push @{ $res{ $role } ||= [] }, $ACE->__Value('ObjectId'); } else { $RT::Logger->error('ShowAsset right is granted on unsupported object'); } } $RT::Principal::_ACL_CACHE->{ $cache_key } = \%res; return %res; } sub _DirectlyCanSeeIn { my $self = shift; my $id = $self->CurrentUser->id; my $cache_key = 'User-'. $id .';:;ShowAsset;:;DirectlyCanSeeIn'; if ( my $cached = $RT::Principal::_ACL_CACHE->{ $cache_key } ) { return @$cached; } my $ACL = RT::ACL->new( RT->SystemUser ); $ACL->Limit( FIELD => 'RightName', VALUE => 'ShowAsset' ); my $principal_alias = $ACL->Join( ALIAS1 => 'main', FIELD1 => 'PrincipalId', TABLE2 => 'Principals', FIELD2 => 'id', ); $ACL->Limit( ALIAS => $principal_alias, FIELD => 'Disabled', VALUE => 0 ); my $cgm_alias = $ACL->Join( ALIAS1 => 'main', FIELD1 => 'PrincipalId', TABLE2 => 'CachedGroupMembers', FIELD2 => 'GroupId', ); $ACL->Limit( ALIAS => $cgm_alias, FIELD => 'MemberId', VALUE => $id ); $ACL->Limit( ALIAS => $cgm_alias, FIELD => 'Disabled', VALUE => 0 ); my @res = (); foreach my $ACE ( @{ $ACL->ItemsArrayRef } ) { my $type = $ACE->__Value('ObjectType'); if ( $type eq 'RT::System' ) { # If user is direct member of a group that has the right # on the system then he can see any asset $RT::Principal::_ACL_CACHE->{ $cache_key } = [-1]; return (-1); } elsif ( $type eq 'RT::Catalog' ) { push @res, $ACE->__Value('ObjectId'); } else { $RT::Logger->error('ShowAsset right is granted on unsupported object'); } } $RT::Principal::_ACL_CACHE->{ $cache_key } = \@res; return @res; } sub CurrentUserCanSee { my $self = shift; return if $self->{'_sql_current_user_can_see_applied'}; return $self->{'_sql_current_user_can_see_applied'} = 1 if $self->CurrentUser->UserObj->HasRight( Right => 'SuperUser', Object => $RT::System ); local $self->{using_restrictions}; my $id = $self->CurrentUser->id; # directly can see in all catalogs then we have nothing to do my @direct_catalogs = $self->_DirectlyCanSeeIn; return $self->{'_sql_current_user_can_see_applied'} = 1 if @direct_catalogs && $direct_catalogs[0] == -1; my %roles = $self->_RolesCanSee; { my %skip = map { $_ => 1 } @direct_catalogs; foreach my $role ( keys %roles ) { next unless ref $roles{ $role }; my @catalogs = grep !$skip{$_}, @{ $roles{ $role } }; if ( @catalogs ) { $roles{ $role } = \@catalogs; } else { delete $roles{ $role }; } } } # there is no global watchers, only catalogs and tickes, if at # some point we will add global roles then it's gonna blow # the idea here is that if the right is set globaly for a role # and user plays this role for a catalog directly not a ticket # then we have to check in advance if ( my @tmp = grep !ref $roles{ $_ }, keys %roles ) { my $groups = RT::Groups->new( RT->SystemUser ); $groups->Limit( FIELD => 'Domain', VALUE => 'RT::Catalog-Role', CASESENSITIVE => 0 ); $groups->Limit( FIELD => 'Name', FUNCTION => 'LOWER(?)', OPERATOR => 'IN', VALUE => [ map {lc $_} @tmp ], CASESENSITIVE => 1, ); my $principal_alias = $groups->Join( ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'Principals', FIELD2 => 'id', ); $groups->Limit( ALIAS => $principal_alias, FIELD => 'Disabled', VALUE => 0 ); my $cgm_alias = $groups->Join( ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'CachedGroupMembers', FIELD2 => 'GroupId', ); $groups->Limit( ALIAS => $cgm_alias, FIELD => 'MemberId', VALUE => $id ); $groups->Limit( ALIAS => $cgm_alias, FIELD => 'Disabled', VALUE => 0 ); while ( my $group = $groups->Next ) { push @direct_catalogs, $group->Instance; } } unless ( @direct_catalogs || keys %roles ) { $self->Limit( SUBCLAUSE => 'ACL', ALIAS => 'main', FIELD => 'id', VALUE => 0, ENTRYAGGREGATOR => 'AND', ); return $self->{'_sql_current_user_can_see_applied'} = 1; } { my $join_roles = keys %roles; my ($role_group_alias, $cgm_alias); if ( $join_roles ) { $role_group_alias = $self->_RoleGroupsJoin( New => 1 ); $cgm_alias = $self->_GroupMembersJoin( GroupsAlias => $role_group_alias ); $self->Limit( LEFTJOIN => $cgm_alias, FIELD => 'MemberId', OPERATOR => '=', VALUE => $id, ); } my $limit_catalogs = sub { my $ea = shift; my @catalogs = @_; return unless @catalogs; $self->Limit( SUBCLAUSE => 'ACL', ALIAS => 'main', FIELD => 'Catalog', OPERATOR => 'IN', VALUE => [ @catalogs ], ENTRYAGGREGATOR => $ea, ); return 1; }; $self->SUPER::_OpenParen('ACL'); my $ea = 'AND'; $ea = 'OR' if $limit_catalogs->( $ea, @direct_catalogs ); while ( my ($role, $catalogs) = each %roles ) { $self->SUPER::_OpenParen('ACL'); $self->Limit( SUBCLAUSE => 'ACL', ALIAS => $cgm_alias, FIELD => 'MemberId', OPERATOR => 'IS NOT', VALUE => 'NULL', QUOTEVALUE => 0, ENTRYAGGREGATOR => $ea, ); $self->Limit( SUBCLAUSE => 'ACL', ALIAS => $role_group_alias, FIELD => 'Name', VALUE => $role, ENTRYAGGREGATOR => 'AND', CASESENSITIVE => 0, ); $limit_catalogs->( 'AND', @$catalogs ) if ref $catalogs; $ea = 'OR' if $ea eq 'AND'; $self->SUPER::_CloseParen('ACL'); } $self->SUPER::_CloseParen('ACL'); } return $self->{'_sql_current_user_can_see_applied'} = 1; } sub _OpenParen { $_[0]->SUPER::_OpenParen( $_[1] || 'assetsql' ); } sub _CloseParen { $_[0]->SUPER::_CloseParen( $_[1] || 'assetsql' ); } sub Table { "Assets" } # BEGIN SQL STUFF ********************************* sub CleanSlate { my $self = shift; $self->SUPER::CleanSlate( @_ ); delete $self->{$_} foreach qw( _sql_cf_alias _sql_group_members_aliases _sql_object_cfv_alias _sql_role_group_aliases _sql_u_watchers_alias_for_sort _sql_u_watchers_aliases _sql_current_user_can_see_applied ); } =head1 Limit Helper Routines These routines are the targets of a dispatch table depending on the type of field. They all share the same signature: my ($self,$field,$op,$value,@rest) = @_; The values in @rest should be suitable for passing directly to DBIx::SearchBuilder::Limit. Essentially they are an expanded/broken out (and much simplified) version of what ProcessRestrictions used to do. They're also much more clearly delineated by the TYPE of field being processed. =head2 _IdLimit Handle ID field. =cut sub _IdLimit { my ( $sb, $field, $op, $value, @rest ) = @_; return $sb->_IntLimit( $field, $op, $value, @rest ); } =head2 _EnumLimit Handle Fields which are limited to certain values, and potentially need to be looked up from another class. This subroutine actually handles two different kinds of fields. For some the user is responsible for limiting the values. (i.e. Status, Type). For others, the value specified by the user will be looked by via specified class. Meta Data: name of class to lookup in (Optional) =cut sub _EnumLimit { my ( $sb, $field, $op, $value, @rest ) = @_; # SQL::Statement changes != to <>. (Can we remove this now?) $op = "!=" if $op eq "<>"; die "Invalid Operation: $op for $field" unless $op eq "=" or $op eq "!="; my $meta = $FIELD_METADATA{$field}; if ( defined $meta->[1] && defined $value && $value !~ /^\d+$/ ) { my $class = "RT::" . $meta->[1]; my $o = $class->new( $sb->CurrentUser ); $o->Load($value); $value = $o->Id || 0; } $sb->Limit( FIELD => $field, VALUE => $value, OPERATOR => $op, @rest, ); } =head2 _IntLimit Handle fields where the values are limited to integers. (For example, Priority, TimeWorked.) Meta Data: None =cut sub _IntLimit { my ( $sb, $field, $op, $value, @rest ) = @_; my $is_a_like = $op =~ /MATCHES|ENDSWITH|STARTSWITH|LIKE/i; # We want to support <id LIKE '1%'> for asset autocomplete, # but we need to explicitly typecast on Postgres if ( $is_a_like && RT->Config->Get('DatabaseType') eq 'Pg' ) { return $sb->Limit( FUNCTION => "CAST(main.$field AS TEXT)", OPERATOR => $op, VALUE => $value, @rest, ); } $sb->Limit( FIELD => $field, VALUE => $value, OPERATOR => $op, @rest, ); } =head2 _LinkLimit Handle fields which deal with links between assets. (MemberOf, DependsOn) Meta Data: 1: Direction (From, To) 2: Link Type (MemberOf, DependsOn, RefersTo) =cut sub _LinkLimit { my ( $sb, $field, $op, $value, @rest ) = @_; my $meta = $FIELD_METADATA{$field}; die "Invalid Operator $op for $field" unless $op =~ /^(=|!=|IS|IS NOT)$/io; my $is_negative = 0; if ( $op eq '!=' || $op =~ /\bNOT\b/i ) { $is_negative = 1; } my $is_null = 0; $is_null = 1 if !$value || $value =~ /^null$/io; my $direction = $meta->[1] || ''; my ($matchfield, $linkfield) = ('', ''); if ( $direction eq 'To' ) { ($matchfield, $linkfield) = ("Target", "Base"); } elsif ( $direction eq 'From' ) { ($matchfield, $linkfield) = ("Base", "Target"); } elsif ( $direction ) { die "Invalid link direction '$direction' for $field\n"; } else { $sb->_OpenParen; $sb->_LinkLimit( 'LinkedTo', $op, $value, @rest ); $sb->_LinkLimit( 'LinkedFrom', $op, $value, @rest, ENTRYAGGREGATOR => (($is_negative && $is_null) || (!$is_null && !$is_negative))? 'OR': 'AND', ); $sb->_CloseParen; return; } my $is_local = 1; if ( $is_null ) { $op = ($op =~ /^(=|IS)$/i)? 'IS': 'IS NOT'; } elsif ( $value =~ /\D/ ) { $value = RT::URI->new( $sb->CurrentUser )->CanonicalizeURI( $value ); $is_local = 0; } $matchfield = "Local$matchfield" if $is_local; #For doing a left join to find "unlinked assets" we want to generate a query that looks like this # SELECT main.* FROM Assets main # LEFT JOIN Links Links_1 ON ( (Links_1.Type = 'MemberOf') # AND(main.id = Links_1.LocalTarget)) # WHERE Links_1.LocalBase IS NULL; my $join_expression; my $local_prefix = RT::URI::asset->new( RT->SystemUser )->LocalURIPrefix . '/'; if ( RT->Config->Get('DatabaseType') eq 'SQLite' ) { $join_expression = qq{'$local_prefix' || main.id}; } else { $join_expression = qq{CONCAT( '$local_prefix', main.id )};; } if ( $is_null ) { my $linkalias = $sb->Join( TYPE => 'LEFT', ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'Links', FIELD2 => $linkfield, EXPRESSION => $join_expression, ); $sb->Limit( LEFTJOIN => $linkalias, FIELD => 'Type', OPERATOR => '=', VALUE => $meta->[2], ) if $meta->[2]; $sb->Limit( @rest, ALIAS => $linkalias, FIELD => $matchfield, OPERATOR => $op, VALUE => 'NULL', QUOTEVALUE => 0, ); } else { my $linkalias = $sb->Join( TYPE => 'LEFT', ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'Links', FIELD2 => $linkfield, EXPRESSION => $join_expression, ); $sb->Limit( LEFTJOIN => $linkalias, FIELD => 'Type', OPERATOR => '=', VALUE => $meta->[2], ) if $meta->[2]; $sb->Limit( LEFTJOIN => $linkalias, FIELD => $matchfield, OPERATOR => '=', VALUE => $value, ); $sb->Limit( @rest, ALIAS => $linkalias, FIELD => $matchfield, OPERATOR => $is_negative? 'IS': 'IS NOT', VALUE => 'NULL', QUOTEVALUE => 0, ); } } =head2 _DateLimit Handle date fields. (Created, LastTold..) Meta Data: 1: type of link. (Probably not necessary.) =cut sub _DateLimit { my ( $sb, $field, $op, $value, %rest ) = @_; die "Invalid Date Op: $op" unless $op =~ /^(=|>|<|>=|<=|IS(\s+NOT)?)$/i; my $meta = $FIELD_METADATA{$field}; die "Incorrect Meta Data for $field" unless ( defined $meta->[1] ); if ( $op =~ /^(IS(\s+NOT)?)$/i) { return $sb->Limit( FUNCTION => $sb->NotSetDateToNullFunction, FIELD => $meta->[1], OPERATOR => $op, VALUE => "NULL", %rest, ); } if ( my $subkey = $rest{SUBKEY} ) { if ( $subkey eq 'DayOfWeek' && $op !~ /IS/i && $value =~ /[^0-9]/ ) { for ( my $i = 0; $i < @RT::Date::DAYS_OF_WEEK; $i++ ) { # Use a case-insensitive regex for better matching across # locales since we don't have fc() and lc() is worse. Really # we should be doing Unicode normalization too, but we don't do # that elsewhere in RT. # # XXX I18N: Replace the regex with fc() once we're guaranteed 5.16. next unless lc $RT::Date::DAYS_OF_WEEK[ $i ] eq lc $value or $sb->CurrentUser->loc($RT::Date::DAYS_OF_WEEK[ $i ]) =~ /^\Q$value\E$/i; $value = $i; last; } return $sb->Limit( FIELD => 'id', VALUE => 0, %rest ) if $value =~ /[^0-9]/; } elsif ( $subkey eq 'Month' && $op !~ /IS/i && $value =~ /[^0-9]/ ) { for ( my $i = 0; $i < @RT::Date::MONTHS; $i++ ) { # Use a case-insensitive regex for better matching across # locales since we don't have fc() and lc() is worse. Really # we should be doing Unicode normalization too, but we don't do # that elsewhere in RT. # # XXX I18N: Replace the regex with fc() once we're guaranteed 5.16. next unless lc $RT::Date::MONTHS[ $i ] eq lc $value or $sb->CurrentUser->loc($RT::Date::MONTHS[ $i ]) =~ /^\Q$value\E$/i; $value = $i + 1; last; } return $sb->Limit( FIELD => 'id', VALUE => 0, %rest ) if $value =~ /[^0-9]/; } my $tz; if ( RT->Config->Get('ChartsTimezonesInDB') ) { my $to = $sb->CurrentUser->UserObj->Timezone || RT->Config->Get('Timezone'); $tz = { From => 'UTC', To => $to } if $to && lc $to ne 'utc'; } # $subkey is validated by DateTimeFunction my $function = $RT::Handle->DateTimeFunction( Type => $subkey, Field => $sb->NotSetDateToNullFunction, Timezone => $tz, ); return $sb->Limit( FUNCTION => $function, FIELD => $meta->[1], OPERATOR => $op, VALUE => $value, %rest, ); } my $date = RT::Date->new( $sb->CurrentUser ); $date->Set( Format => 'unknown', Value => $value ); if ( $op eq "=" ) { # if we're specifying =, that means we want everything on a # particular single day. in the database, we need to check for > # and < the edges of that day. $date->SetToMidnight( Timezone => 'server' ); my $daystart = $date->ISO; $date->AddDay; my $dayend = $date->ISO; $sb->_OpenParen; $sb->Limit( FIELD => $meta->[1], OPERATOR => ">=", VALUE => $daystart, %rest, ); $sb->Limit( FIELD => $meta->[1], OPERATOR => "<", VALUE => $dayend, %rest, ENTRYAGGREGATOR => 'AND', ); $sb->_CloseParen; } else { $sb->Limit( FUNCTION => $sb->NotSetDateToNullFunction, FIELD => $meta->[1], OPERATOR => $op, VALUE => $date->ISO, %rest, ); } } =head2 _StringLimit Handle simple fields which are just strings. (Subject,Type) Meta Data: None =cut sub _StringLimit { my ( $sb, $field, $op, $value, @rest ) = @_; # FIXME: # Valid Operators: # =, !=, LIKE, NOT LIKE if ( RT->Config->Get('DatabaseType') eq 'Oracle' && (!defined $value || !length $value) && lc($op) ne 'is' && lc($op) ne 'is not' ) { if ($op eq '!=' || $op =~ /^NOT\s/i) { $op = 'IS NOT'; } else { $op = 'IS'; } $value = 'NULL'; } if ($field eq "Status") { $value = lc $value; } $sb->Limit( FIELD => $field, OPERATOR => $op, VALUE => $value, CASESENSITIVE => 0, @rest, ); } =head2 _WatcherLimit Handle watcher limits. (Requestor, CC, etc..) Meta Data: 1: Field to query on =cut sub _WatcherLimit { my $self = shift; my $field = shift; my $op = shift; my $value = shift; my %rest = (@_); my $meta = $FIELD_METADATA{ $field }; my $type = $meta->[1] || ''; my $class = $meta->[2] || 'Asset'; # Bail if the subfield is not allowed if ( $rest{SUBKEY} and not grep { $_ eq $rest{SUBKEY} } @{$SEARCHABLE_SUBFIELDS{'User'}}) { die "Invalid watcher subfield: '$rest{SUBKEY}'"; } $self->RoleLimit( TYPE => $type, CLASS => "RT::$class", FIELD => $rest{SUBKEY}, OPERATOR => $op, VALUE => $value, SUBCLAUSE => "assetsql", %rest, ); } =head2 _WatcherMembershipLimit Handle watcher membership limits, i.e. whether the watcher belongs to a specific group or not. Meta Data: 1: Role to query on =cut sub _WatcherMembershipLimit { my ( $self, $field, $op, $value, %rest ) = @_; # we don't support anything but '=' die "Invalid $field Op: $op" unless $op =~ /^=$/; unless ( $value =~ /^\d+$/ ) { my $group = RT::Group->new( $self->CurrentUser ); $group->LoadUserDefinedGroup( $value ); $value = $group->id || 0; } my $meta = $FIELD_METADATA{$field}; my $type = $meta->[1] || ''; (undef, undef, my $members_alias) = $self->_WatcherJoin( New => 1, Name => $type ); my $members_column = 'id'; my $cgm_alias = $self->Join( ALIAS1 => $members_alias, FIELD1 => $members_column, TABLE2 => 'CachedGroupMembers', FIELD2 => 'MemberId', ); $self->Limit( LEFTJOIN => $cgm_alias, ALIAS => $cgm_alias, FIELD => 'Disabled', VALUE => 0, ); $self->Limit( ALIAS => $cgm_alias, FIELD => 'GroupId', VALUE => $value, OPERATOR => $op, %rest, ); } =head2 _CustomFieldDecipher Try and turn a CF descriptor into (cfid, cfname) object pair. Takes an optional second parameter of the CF LookupType, defaults to Asset CFs. =cut sub _CustomFieldDecipher { my ($self, $string, $lookuptype) = @_; $lookuptype ||= $self->_SingularClass->CustomFieldLookupType; my ($object, $field, $column) = ($string =~ /^(?:(.+?)\.)?\{(.+)\}(?:\.(Content|LargeContent))?$/); $field ||= ($string =~ /^\{(.*?)\}$/)[0] || $string; my ($cf, $applied_to); if ( $object ) { my $record_class = RT::CustomField->RecordClassFromLookupType($lookuptype); $applied_to = $record_class->new( $self->CurrentUser ); $applied_to->Load( $object ); if ( $applied_to->id ) { RT->Logger->debug("Limiting to CFs identified by '$field' applied to $record_class #@{[$applied_to->id]} (loaded via '$object')"); } else { RT->Logger->warning("$record_class '$object' doesn't exist, parsed from '$string'"); $object = 0; undef $applied_to; } } if ( $field =~ /\D/ ) { $object ||= ''; my $cfs = RT::CustomFields->new( $self->CurrentUser ); $cfs->Limit( FIELD => 'Name', VALUE => $field, CASESENSITIVE => 0 ); $cfs->LimitToLookupType($lookuptype); if ($applied_to) { $cfs->SetContextObject($applied_to); $cfs->LimitToObjectId($applied_to->id); } # if there is more then one field the current user can # see with the same name then we shouldn't return cf object # as we don't know which one to use $cf = $cfs->First; if ( $cf ) { $cf = undef if $cfs->Next; } else { # find the cf without ACL # this is because current _CustomFieldJoinByName has a bug that # can't search correctly with negative cf ops :/ my $cfs = RT::CustomFields->new( RT->SystemUser ); $cfs->Limit( FIELD => 'Name', VALUE => $field, CASESENSITIVE => 0 ); $cfs->LimitToLookupType( $lookuptype ); if ( $applied_to ) { $cfs->SetContextObject( $applied_to ); $cfs->LimitToObjectId( $applied_to->id ); } $cf = $cfs->First unless $cfs->Count > 1; } } else { $cf = RT::CustomField->new( $self->CurrentUser ); $cf->Load( $field ); $cf->SetContextObject($applied_to) if $cf->id and $applied_to; } return ($object, $field, $cf, $column); } =head2 _CustomFieldLimit Limit based on CustomFields Meta Data: none =cut sub _CustomFieldLimit { my ( $self, $_field, $op, $value, %rest ) = @_; my $meta = $FIELD_METADATA{ $_field }; my $class = $meta->[1] || 'Asset'; my $type = "RT::$class"->CustomFieldLookupType; my $field = $rest{'SUBKEY'} || die "No field specified"; # For our sanity, we can only limit on one object at a time my ($object, $cfid, $cf, $column); ($object, $field, $cf, $column) = $self->_CustomFieldDecipher( $field, $type ); $self->_LimitCustomField( %rest, LOOKUPTYPE => $type, CUSTOMFIELD => $cf || $field, KEY => $cf ? $cf->id : "$type-$object.$field", OPERATOR => $op, VALUE => $value, COLUMN => $column, SUBCLAUSE => "assetsql", ); } sub _CustomFieldJoinByName { my $self = shift; my ($ObjectAlias, $cf, $type) = @_; my ($ocfvalias, $CFs, $ocfalias) = $self->SUPER::_CustomFieldJoinByName(@_); $self->Limit( LEFTJOIN => $ocfalias, ENTRYAGGREGATOR => 'OR', FIELD => 'ObjectId', VALUE => 'main.Catalog', QUOTEVALUE => 0, ); return ($ocfvalias, $CFs, $ocfalias); } sub _LifecycleLimit { my ( $self, $field, $op, $value, %rest ) = @_; die "Invalid Operator $op for $field" if $op =~ /^(IS|IS NOT)$/io; my $catalog = $self->{_sql_aliases}{catalogs} ||= $_[0]->Join( ALIAS1 => 'main', FIELD1 => 'Catalog', TABLE2 => 'Catalogs', FIELD2 => 'id', ); $self->Limit( ALIAS => $catalog, FIELD => 'Lifecycle', OPERATOR => $op, VALUE => $value, %rest, ); } =head2 PrepForSerialization You don't want to serialize a big assets object, as the {items} hash will be instantly invalid _and_ eat lots of space =cut sub PrepForSerialization { my $self = shift; delete $self->{'items'}; delete $self->{'items_array'}; $self->RedoSearch(); } =head2 FromSQL Convert a RT-SQL string into a set of SearchBuilder restrictions. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =cut sub _parser { my ($self,$string) = @_; require RT::Interface::Web::QueryBuilder::Tree; my $tree = RT::Interface::Web::QueryBuilder::Tree->new; $tree->ParseSQL( Class => 'RT::Assets', Query => $string, CurrentUser => $self->CurrentUser, ); my $escape_quotes = sub { my $text = shift; $text =~ s{(['\\])}{\\$1}g; return $text; }; my ( $active_status_node, $inactive_status_node ); $tree->traverse( sub { my $node = shift; return unless $node->isLeaf and $node->getNodeValue; my ($key, $subkey, $meta, $op, $value, $bundle) = @{$node->getNodeValue}{qw/Key Subkey Meta Op Value Bundle/}; return unless $key eq "Status" && $value =~ /^(?:__(?:in)?active__)$/i; my $parent = $node->getParent; my $index = $node->getIndex; if ( ( lc $value eq '__inactive__' && $op eq '=' ) || ( lc $value eq '__active__' && $op eq '!=' ) ) { unless ( $inactive_status_node ) { my %lifecycle = map { $_ => $RT::Lifecycle::LIFECYCLES{ $_ }{ inactive } } grep { @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ inactive } || [] } } grep { $_ ne '__maps__' && $RT::Lifecycle::LIFECYCLES_CACHE{ $_ }{ type } eq 'asset' } keys %RT::Lifecycle::LIFECYCLES; return unless %lifecycle; my $sql; if ( keys %lifecycle == 1 ) { $sql = join ' OR ', map { qq{ Status = '$_' } } map { $escape_quotes->($_) } map { @$_ } values %lifecycle; } else { my @inactive_sql; for my $name ( keys %lifecycle ) { my $escaped_name = $escape_quotes->($name); my $inactive_sql = qq{Lifecycle = '$escaped_name'} . ' AND (' . join( ' OR ', map { qq{ Status = '$_' } } map { $escape_quotes->($_) } @{ $lifecycle{ $name } } ) . ')'; push @inactive_sql, qq{($inactive_sql)}; } $sql = join ' OR ', @inactive_sql; } $inactive_status_node = RT::Interface::Web::QueryBuilder::Tree->new; $inactive_status_node->ParseSQL( Class => 'RT::Assets', Query => $sql, CurrentUser => $self->CurrentUser, ); } $parent->removeChild( $node ); $parent->insertChild( $index, $inactive_status_node ); } else { unless ( $active_status_node ) { my %lifecycle = map { $_ => [ @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ initial } || [] }, @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ active } || [] }, ] } grep { @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ initial } || [] } || @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ active } || [] } } grep { $_ ne '__maps__' && $RT::Lifecycle::LIFECYCLES_CACHE{ $_ }{ type } eq 'asset' } keys %RT::Lifecycle::LIFECYCLES; return unless %lifecycle; my $sql; if ( keys %lifecycle == 1 ) { $sql = join ' OR ', map { qq{ Status = '$_' } } map { $escape_quotes->($_) } map { @$_ } values %lifecycle; } else { my @active_sql; for my $name ( keys %lifecycle ) { my $escaped_name = $escape_quotes->($name); my $active_sql = qq{Lifecycle = '$escaped_name'} . ' AND (' . join( ' OR ', map { qq{ Status = '$_' } } map { $escape_quotes->($_) } @{ $lifecycle{ $name } } ) . ')'; push @active_sql, qq{($active_sql)}; } $sql = join ' OR ', @active_sql; } $active_status_node = RT::Interface::Web::QueryBuilder::Tree->new; $active_status_node->ParseSQL( Class => 'RT::Assets', Query => $sql, CurrentUser => $self->CurrentUser, ); } $parent->removeChild( $node ); $parent->insertChild( $index, $active_status_node ); } } ); # Perform an optimization pass looking for watcher bundling $tree->traverse( sub { my $node = shift; return if $node->isLeaf; return unless ($node->getNodeValue||'') eq "OR"; my %refs; my @kids = grep {$_->{Meta}[0] eq "WATCHERFIELD"} map {$_->getNodeValue} grep {$_->isLeaf} $node->getAllChildren; for (@kids) { my $node = $_; my ($key, $subkey, $op) = @{$node}{qw/Key Subkey Op/}; next if $node->{Meta}[1] and RT::Asset->Role($node->{Meta}[1])->{Column}; next if $op =~ /^!=$|\bNOT\b/i; next if $op =~ /^IS( NOT)?$/i and not $subkey; $node->{Bundle} = $refs{$node->{Meta}[1] || ''} ||= []; } } ); my $ea = ''; $tree->traverse( sub { my $node = shift; $ea = $node->getParent->getNodeValue if $node->getIndex > 0; return $self->_OpenParen unless $node->isLeaf; my ($key, $subkey, $meta, $op, $value, $bundle) = @{$node->getNodeValue}{qw/Key Subkey Meta Op Value Bundle/}; # normalize key and get class (type) my $class = $meta->[0]; # replace __CurrentUser__ with id $value = $self->CurrentUser->id if $value eq '__CurrentUser__'; my $sub = $dispatch{ $class } or die "No dispatch method for class '$class'"; # A reference to @res may be pushed onto $sub_tree{$key} from # above, and we fill it here. $sub->( $self, $key, $op, $value, ENTRYAGGREGATOR => $ea, SUBKEY => $subkey, BUNDLE => $bundle, ); }, sub { my $node = shift; return $self->_CloseParen unless $node->isLeaf; } ); } sub FromSQL { my ($self,$query) = @_; { # preserve first_row and show_rows across the CleanSlate local ($self->{'first_row'}, $self->{'show_rows'}, $self->{_sql_looking_at}); $self->CleanSlate; $self->_InitSQL(); } return (1, $self->loc("No Query")) unless $query; $self->{_sql_query} = $query; eval { local $self->{parsing_assetsql} = 1; $self->_parser( $query ); }; if ( $@ ) { my $error = "$@"; $RT::Logger->error("Couldn't parse query: $error"); return (0, $error); } # We don't want deleted tickets unless 'allow_deleted_search' is set unless( $self->{'allow_deleted_search'} ) { $self->Limit( FIELD => 'Status', OPERATOR => '!=', VALUE => 'deleted', ); } # set SB's dirty flag $self->{'must_redo_search'} = 1; $self->{'RecalcAssetLimits'} = 0; return (1, $self->loc("Valid Query")); } =head2 Query Returns the last string passed to L</FromSQL>. =cut sub Query { my $self = shift; return $self->{_sql_query}; } =head2 ClearRestrictions Removes all restrictions irretrievably =cut sub ClearRestrictions { my $self = shift; delete $self->{'AssetRestrictions'}; $self->{_sql_looking_at} = {}; $self->{'RecalcAssetLimits'} = 1; } # Convert a set of oldstyle SB Restrictions to Clauses for RQL sub _RestrictionsToClauses { my $self = shift; my %clause; foreach my $row ( keys %{ $self->{'AssetRestrictions'} } ) { my $restriction = $self->{'AssetRestrictions'}{$row}; # We need to reimplement the subclause aggregation that SearchBuilder does. # Default Subclause is ALIAS.FIELD, and default ALIAS is 'main', # Then SB AND's the different Subclauses together. # So, we want to group things into Subclauses, convert them to # SQL, and then join them with the appropriate DefaultEA. # Then join each subclause group with AND. my $field = $restriction->{'FIELD'}; my $realfield = $field; # CustomFields fake up a fieldname, so # we need to figure that out # One special case # Rewrite LinkedTo meta field to the real field if ( $field =~ /LinkedTo/ ) { $realfield = $field = $restriction->{'TYPE'}; } # Two special case # Handle subkey fields with a different real field if ( $field =~ /^(\w+)\./ ) { $realfield = $1; } die "I don't know about $field yet" unless ( exists $FIELD_METADATA{$realfield} or $restriction->{CUSTOMFIELD} ); my $type = $FIELD_METADATA{$realfield}->[0]; my $op = $restriction->{'OPERATOR'}; my $value = ( grep {defined} map { $restriction->{$_} } qw(VALUE TICKET BASE TARGET) )[0]; # this performs the moral equivalent of defined or/dor/C<//>, # without the short circuiting.You need to use a 'defined or' # type thing instead of just checking for truth values, because # VALUE could be 0.(i.e. "false") # You could also use this, but I find it less aesthetic: # (although it does short circuit) #( defined $restriction->{'VALUE'}? $restriction->{VALUE} : # defined $restriction->{'TICKET'} ? # $restriction->{TICKET} : # defined $restriction->{'BASE'} ? # $restriction->{BASE} : # defined $restriction->{'TARGET'} ? # $restriction->{TARGET} ) my $ea = $restriction->{ENTRYAGGREGATOR} || $DefaultEA{$type} || "AND"; if ( ref $ea ) { die "Invalid operator $op for $field ($type)" unless exists $ea->{$op}; $ea = $ea->{$op}; } # Each CustomField should be put into a different Clause so they # are ANDed together. if ( $restriction->{CUSTOMFIELD} ) { $realfield = $field; } exists $clause{$realfield} or $clause{$realfield} = []; # Escape Quotes $field =~ s!(['\\])!\\$1!g; $value =~ s!(['\\])!\\$1!g; my $data = [ $ea, $type, $field, $op, $value ]; # here is where we store extra data, say if it's a keyword or # something. (I.e. "TYPE SPECIFIC STUFF") if (lc $ea eq 'none') { $clause{$realfield} = [ $data ]; } else { push @{ $clause{$realfield} }, $data; } } return \%clause; } =head2 ClausesToSQL =cut sub ClausesToSQL { my $self = shift; my $clauses = shift; my @sql; for my $f (keys %{$clauses}) { my $sql; my $first = 1; # Build SQL from the data hash for my $data ( @{ $clauses->{$f} } ) { $sql .= $data->[0] unless $first; $first=0; # ENTRYAGGREGATOR $sql .= " '". $data->[2] . "' "; # FIELD $sql .= $data->[3] . " "; # OPERATOR $sql .= "'". $data->[4] . "' "; # VALUE } push @sql, " ( " . $sql . " ) "; } return join("AND",@sql); } sub _ProcessRestrictions { my $self = shift; delete $self->{'items_array'}; delete $self->{'item_map'}; delete $self->{'raw_rows'}; delete $self->{'count_all'}; my $sql = $self->Query; if ( !$sql || $self->{'RecalcAssetLimits'} ) { local $self->{using_restrictions}; # "Restrictions to Clauses Branch\n"; my $clauseRef = eval { $self->_RestrictionsToClauses; }; if ($@) { $RT::Logger->error( "RestrictionsToClauses: " . $@ ); $self->FromSQL(""); } else { $sql = $self->ClausesToSQL($clauseRef); $self->FromSQL($sql) if $sql; } } $self->{'RecalcAssetLimits'} = 0; } 1; RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Lifecycle.pm������������������������������������������������������������������������000644 �000765 �000024 �00000077235 14005011336 016347� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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; use Storable (); package RT::Lifecycle; use List::MoreUtils 'uniq'; our %LIFECYCLES; our %LIFECYCLES_CACHE; our %LIFECYCLES_TYPES; # cache structure: # { # lifecycle_x => { # '' => [...], # all valid in lifecycle # initial => [...], # active => [...], # inactive => [...], # transitions => { # status_x => [status_next1, status_next2,...], # }, # rights => { # 'status_y -> status_y' => 'right', # .... # } # actions => [ # { from => 'a', to => 'b', label => '...', update => '...' }, # .... # ] # } # } =head1 NAME RT::Lifecycle - class to access and manipulate lifecycles =head1 DESCRIPTION A lifecycle is a list of statuses that a ticket can have. There are three groups of statuses: initial, active and inactive. A lifecycle also defines possible transitions between statuses. For example, in the 'default' lifecycle, you may only change status from 'stalled' to 'open'. It is also possible to define user-interface labels and the action a user should perform during a transition. For example, the "open -> stalled" transition would have a 'Stall' label and the action would be Comment. The action only defines what form is showed to the user, but actually performing the action is not required. The user can leave the comment box empty yet still Stall a ticket. Finally, the user can also just use the Basics or Jumbo form to change the status with the usual dropdown. =head1 METHODS =head2 new Simple constructor, takes no arguments. =cut sub new { my $proto = shift; my $self = bless {}, ref($proto) || $proto; $self->FillCache unless keys %LIFECYCLES_CACHE; return $self; } =head2 Load Name => I<NAME>, Type => I<TYPE> Takes a name of the lifecycle and loads it. If only a Type is provided, loads the global lifecycle with statuses from all named lifecycles of that type. Can be called as class method, returns a new object, for example: my $lifecycle = RT::Lifecycle->Load( Name => 'default'); Returns an object which may be a subclass of L<RT::Lifecycle> (L<RT::Lifecycle::Ticket>, for example) depending on the type of the lifecycle in question. =cut sub Load { my $self = shift; return $self->new->Load( @_ ) unless ref $self; unshift @_, Type => "ticket", "Name" if @_ % 2; my %args = ( Type => "ticket", Name => '', @_, ); if (defined $args{Name} and exists $LIFECYCLES_CACHE{ $args{Name} }) { $self->{'name'} = $args{Name}; $self->{'data'} = $LIFECYCLES_CACHE{ $args{Name} }; $self->{'type'} = $args{Type}; my $found_type = $self->{'data'}{'type'}; warn "Found type of $found_type ne $args{Type}" if $found_type ne $args{Type}; } elsif (not $args{Name} and exists $LIFECYCLES_TYPES{ $args{Type} }) { $self->{'data'} = $LIFECYCLES_TYPES{ $args{Type} }; $self->{'type'} = $args{Type}; } else { return undef; } my $class = "RT::Lifecycle::".ucfirst($args{Type}); bless $self, $class if $class->require; return $self; } =head2 List List available lifecycles. This list omits RT's default approvals lifecycle. Takes: An optional parameter for lifecycle types other than tickets. Defaults to 'ticket'. Returns: A sorted list of available lifecycles. =cut sub List { my $self = shift; my $for = shift || 'ticket'; return grep { $_ ne 'approvals' } $self->ListAll( $for ); } =head2 ListAll Returns a list of all lifecycles, including approvals. Takes: An optional parameter for lifecycle types other than tickets. Defaults to 'ticket'. Returns: A sorted list of all available lifecycles. =cut sub ListAll { my $self = shift; my $for = shift || 'ticket'; $self->FillCache unless keys %LIFECYCLES_CACHE; return sort grep {$LIFECYCLES_CACHE{$_}{type} eq $for && !$LIFECYCLES_CACHE{$_}{disabled}} grep $_ ne '__maps__', keys %LIFECYCLES_CACHE; } =head2 Name Returns name of the loaded lifecycle. =cut sub Name { return $_[0]->{'name'} } =head2 Type Returns the type of the loaded lifecycle. =cut sub Type { return $_[0]->{'type'} } =head2 Getting statuses and validating. Methods to get statuses in different sets or validating them. =head3 Valid Returns an array of all valid statuses for the current lifecycle. Statuses are not sorted alphabetically, instead initial goes first, then active and then inactive. Takes optional list of status types, from 'initial', 'active' or 'inactive'. For example: $lifecycle->Valid('initial', 'active'); =cut sub Valid { my $self = shift; my @types = @_; unless ( @types ) { return @{ $self->{'data'}{''} || [] }; } my @res; push @res, @{ $self->{'data'}{ $_ } || [] } foreach @types; return @res; } =head3 IsValid Takes a status and returns true if value is a valid status for the current lifecycle. Otherwise, returns false. Takes optional list of status types after the status, so it's possible check validity in particular sets, for example: # returns true if status is valid and from initial or active set $lifecycle->IsValid('some_status', 'initial', 'active'); See also </valid>. =cut sub IsValid { my $self = shift; my $value = shift or return 0; return 1 if grep lc($_) eq lc($value), $self->Valid( @_ ); return 0; } =head3 StatusType Takes a status and returns its type, one of 'initial', 'active' or 'inactive'. =cut sub StatusType { my $self = shift; my $status = shift; foreach my $type ( qw(initial active inactive) ) { return $type if $self->IsValid( $status, $type ); } return ''; } =head3 Initial Returns an array of all initial statuses for the current lifecycle. =cut sub Initial { my $self = shift; return $self->Valid('initial'); } =head3 IsInitial Takes a status and returns true if value is a valid initial status. Otherwise, returns false. =cut sub IsInitial { my $self = shift; my $value = shift or return 0; return 1 if grep lc($_) eq lc($value), $self->Valid('initial'); return 0; } =head3 Active Returns an array of all active statuses for this lifecycle. =cut sub Active { my $self = shift; return $self->Valid('active'); } =head3 IsActive Takes a value and returns true if value is a valid active status. Otherwise, returns false. =cut sub IsActive { my $self = shift; my $value = shift or return 0; return 1 if grep lc($_) eq lc($value), $self->Valid('active'); return 0; } =head3 Inactive Returns an array of all inactive statuses for this lifecycle. =cut sub Inactive { my $self = shift; return $self->Valid('inactive'); } =head3 IsInactive Takes a value and returns true if value is a valid inactive status. Otherwise, returns false. =cut sub IsInactive { my $self = shift; my $value = shift or return 0; return 1 if grep lc($_) eq lc($value), $self->Valid('inactive'); return 0; } =head2 Default statuses In some cases when status is not provided a default values should be used. =head3 DefaultStatus Takes a situation name and returns value. Name should be spelled following spelling in the RT config file. =cut sub DefaultStatus { my $self = shift; my $situation = shift; return $self->{data}{defaults}{ $situation }; } =head3 DefaultOnCreate Returns the status that should be used by default when ticket is created. =cut sub DefaultOnCreate { my $self = shift; return $self->DefaultStatus('on_create'); } =head2 Transitions, rights, labels and actions. =head3 Transitions Takes status and returns list of statuses it can be changed to. Is status is empty or undefined then returns list of statuses for a new ticket. If argument is ommitted then returns a hash with all possible transitions in the following format: status_x => [ next_status, next_status, ... ], status_y => [ next_status, next_status, ... ], =cut sub Transitions { my $self = shift; return %{ $self->{'data'}{'transitions'} || {} } unless @_; my $status = shift || ''; return @{ $self->{'data'}{'transitions'}{ lc $status } || [] }; } =head1 IsTransition Takes two statuses (from -> to) and returns true if it's valid transition and false otherwise. =cut sub IsTransition { my $self = shift; my $from = shift; my $to = shift or return 0; return 1 if grep lc($_) eq lc($to), $self->Transitions($from); return 0; } =head3 CheckRight Takes two statuses (from -> to) and returns the right that should be checked on the ticket. =cut sub CheckRight { my $self = shift; my $from = lc shift; my $to = lc shift; if ( my $rights = $self->{'data'}{'rights'} ) { my $check = $rights->{ $from .' -> '. $to } || $rights->{ '* -> '. $to } || $rights->{ $from .' -> *' } || $rights->{ '* -> *' }; return $check if $check; } return $to eq 'deleted' ? 'DeleteTicket' : 'ModifyTicket'; } =head3 RightsDescription [TYPE] Returns hash with description of rights that are defined for particular transitions. =cut sub RightsDescription { my $self = shift; my $type = shift; $self->FillCache unless keys %LIFECYCLES_CACHE; my %tmp; foreach my $lifecycle ( values %LIFECYCLES_CACHE ) { next unless exists $lifecycle->{'rights'}; next if $type and $lifecycle->{type} ne $type; while ( my ($transition, $right) = each %{ $lifecycle->{'rights'} } ) { push @{ $tmp{ $right } ||=[] }, $transition; } } my %res; while ( my ($right, $transitions) = each %tmp ) { my (@from, @to); foreach ( @$transitions ) { ($from[@from], $to[@to]) = split / -> /, $_; } my $description = 'Change status' . ( (grep $_ eq '*', @from)? '' : ' from '. join ', ', @from ) . ( (grep $_ eq '*', @to )? '' : ' to '. join ', ', @to ); $res{ $right } = $description; } return %res; } =head3 Actions Takes a status and returns list of defined actions for the status. Each element in the list is a hash reference with the following key/value pairs: =over 4 =item from - either the status or * =item to - next status =item label - label of the action =item update - 'Respond', 'Comment' or '' (empty string) =back =cut sub Actions { my $self = shift; my $from = shift || return (); $from = lc $from; $self->FillCache unless keys %LIFECYCLES_CACHE; my @res = grep lc $_->{'from'} eq $from || ( $_->{'from'} eq '*' && lc $_->{'to'} ne $from ), @{ $self->{'data'}{'actions'} }; # skip '* -> x' if there is '$from -> x' my @temp = @res; # Create a copy for the inner grep since we modify in the loop foreach my $e ( grep $_->{'from'} eq '*', @res ) { $e = undef if grep $_->{'from'} ne '*' && $_->{'to'} eq $e->{'to'}, @temp; } return grep defined, @res; } =head2 Moving tickets between lifecycles =head3 MoveMap Takes lifecycle as a name string or an object and returns a hash reference with move map from this cycle to provided. =cut sub MoveMap { my $from = shift; # self my $to = shift; $to = RT::Lifecycle->Load( Name => $to, Type => $from->Type ) unless ref $to; return $LIFECYCLES{'__maps__'}{ $from->Name .' -> '. $to->Name } || {}; } =head3 HasMoveMap Takes a lifecycle as a name string or an object and returns true if move map defined for move from this cycle to provided. =cut sub HasMoveMap { my $self = shift; my $map = $self->MoveMap( @_ ); return 0 unless $map && keys %$map; return 0 unless grep defined && length, values %$map; return 1; } =head3 NoMoveMaps Takes no arguments and returns hash with pairs that has no move maps. =cut sub NoMoveMaps { my $self = shift; my $type = $self->Type; my @list = $self->List( $type ); my @res; foreach my $from ( @list ) { foreach my $to ( @list ) { next if $from eq $to; push @res, $from, $to unless RT::Lifecycle->Load( Name => $from, Type => $type )->HasMoveMap( $to ); } } return @res; } =head2 Localization =head3 ForLocalization A class method that takes no arguments and returns list of strings that require translation. =cut sub ForLocalization { my $self = shift; $self->FillCache unless keys %LIFECYCLES_CACHE; my @res = (); push @res, @{$_->{''}} for values %LIFECYCLES_TYPES; foreach my $lifecycle ( values %LIFECYCLES ) { push @res, grep defined && length, map $_->{'label'}, grep ref($_), @{ $lifecycle->{'actions'} || [] }; } push @res, $self->RightsDescription; my %seen; return grep !$seen{lc $_}++, @res; } sub loc { return RT->SystemUser->loc( @_ ) } sub CanonicalCase { my $self = shift; my ($status) = @_; return undef unless defined $status; return($self->{data}{canonical_case}{lc $status} || lc $status); } sub FillCache { my $self = shift; my $map = RT->Config->Get('Lifecycles') or return; { my @lifecycles; # if users are upgrading from 3.* where we don't have lifecycle column yet, # this could die. we also don't want to frighten them by the errors out eval { local $RT::Logger = Log::Dispatch->new; @lifecycles = grep { defined } RT::Queues->new( RT->SystemUser )->DistinctFieldValues( 'Lifecycle' ); }; unless ( $@ ) { for my $name ( @lifecycles ) { unless ( $map->{$name} ) { warn "Lifecycle $name is missing in %Lifecycles config"; } } } } %LIFECYCLES_CACHE = %LIFECYCLES = %$map; $_ = { %$_ } foreach values %LIFECYCLES_CACHE; foreach my $name ( keys %LIFECYCLES_CACHE ) { next if $name eq "__maps__"; my $lifecycle = $LIFECYCLES_CACHE{$name}; my $type = $lifecycle->{type} ||= 'ticket'; $LIFECYCLES_TYPES{$type} ||= { '' => [], initial => [], active => [], inactive => [], actions => [], }; my ( $ret, @warnings ) = $self->ValidateLifecycle(Lifecycle => $lifecycle, Name => $name); unless ( $ret ) { warn $_ for @warnings; } my @statuses; foreach my $category ( qw(initial active inactive) ) { for my $status (@{ $lifecycle->{ $category } || [] }) { push @{ $LIFECYCLES_TYPES{$type}{$category} }, $status; push @statuses, $status; } } # Lower-case for consistency # ->{actions} are handled below for my $state (keys %{ $lifecycle->{defaults} || {} }) { my $status = $lifecycle->{defaults}{$state}; $lifecycle->{defaults}{$state} = $lifecycle->{canonical_case}{lc $status} || lc $status; } unless ( $lifecycle->{defaults} && $lifecycle->{defaults}{on_create} && $lifecycle->{canonical_case}{ lc $lifecycle->{defaults}{on_create} } ) { $lifecycle->{defaults}{on_create} = $lifecycle->{initial}[0]; } for my $from (keys %{ $lifecycle->{transitions} || {} }) { for my $status ( @{delete($lifecycle->{transitions}{$from}) || []} ) { push @{ $lifecycle->{transitions}{lc $from} }, $lifecycle->{canonical_case}{lc $status} || lc $status; } } for my $schema (keys %{ $lifecycle->{rights} || {} }) { my ($from, $to) = split /\s*->\s*/, $schema, 2; unless ($from and $to) { next; } $lifecycle->{rights}{lc($from) . " -> " .lc($to)} = delete $lifecycle->{rights}{$schema}; } my %seen; @statuses = grep !$seen{ lc $_ }++, @statuses; $lifecycle->{''} = \@statuses; unless ( $lifecycle->{'transitions'}{''} ) { $lifecycle->{'transitions'}{''} = [ grep lc $_ ne 'deleted', @statuses ]; } my @actions; if ( ref $lifecycle->{'actions'} eq 'HASH' ) { foreach my $k ( sort keys %{ $lifecycle->{'actions'} } ) { push @actions, $k, $lifecycle->{'actions'}{ $k }; } } elsif ( ref $lifecycle->{'actions'} eq 'ARRAY' ) { @actions = @{ $lifecycle->{'actions'} }; } $lifecycle->{'actions'} = []; while ( my ($transition, $info) = splice @actions, 0, 2 ) { my ($from, $to) = split /\s*->\s*/, $transition, 2; unless ($from and $to) { next; } push @{ $lifecycle->{'actions'} }, { %$info, from => ($lifecycle->{canonical_case}{lc $from} || lc $from), to => ($lifecycle->{canonical_case}{lc $to} || lc $to), }; } } my ( $ret, @warnings ) = $self->ValidateLifecycleMaps(); unless ( $ret ) { warn $_ for @warnings; } # Lower-case the transition maps for my $mapname (keys %{ $LIFECYCLES_CACHE{'__maps__'} || {} }) { my ($from, $to) = split /\s*->\s*/, $mapname, 2; unless ($from and $to) { next; } my $map = delete $LIFECYCLES_CACHE{'__maps__'}{$mapname}; $LIFECYCLES_CACHE{'__maps__'}{"$from -> $to"} = $map; for my $status (keys %{ $map }) { $map->{lc $status} = lc delete $map->{$status}; } } for my $type (keys %LIFECYCLES_TYPES) { for my $category ( qw(initial active inactive), '' ) { my %seen; @{ $LIFECYCLES_TYPES{$type}{$category} } = grep !$seen{ lc $_ }++, @{ $LIFECYCLES_TYPES{$type}{$category} }; push @{ $LIFECYCLES_TYPES{$type}{''} }, @{ $LIFECYCLES_TYPES{$type}{$category} } if $category; } my $class = "RT::Lifecycle::".ucfirst($type); $class->RegisterRights if $class->require and $class->can("RegisterRights"); } return; } sub _CloneLifecycleMaps { my $class = shift; my $maps = shift; my $name = shift; my $clone = shift; for my $key (keys %$maps) { my $map = $maps->{$key}; next unless $key =~ s/^ \Q$clone\E \s+ -> \s+/$name -> /x || $key =~ s/\s+ -> \s+ \Q$clone\E $/ -> $name/x; $maps->{$key} = Storable::dclone($map); } my $CloneObj = RT::Lifecycle->new; $CloneObj->Load($clone); my %map = map { $_ => $_ } $CloneObj->Valid; $maps->{"$name -> $clone"} = { %map }; $maps->{"$clone -> $name"} = { %map }; } sub _SaveLifecycles { my $class = shift; my $lifecycles = shift; my $CurrentUser = shift; my $setting = RT::Configuration->new($CurrentUser); $setting->LoadByCols(Name => 'Lifecycles', Disabled => 0); if ($setting->Id) { my ($ok, $msg) = $setting->SetContent($lifecycles); return ($ok, $msg) if !$ok; } else { my ($ok, $msg) = $setting->Create( Name => 'Lifecycles', Content => $lifecycles, ); return ($ok, $msg) if !$ok; } RT->System->LifecycleCacheNeedsUpdate(1); return 1; } sub _CreateLifecycle { my $class = shift; my %args = @_; my $CurrentUser = $args{CurrentUser}; my $lifecycles = RT->Config->Get('Lifecycles'); my $lifecycle; if ($args{Clone}) { $lifecycle = Storable::dclone($lifecycles->{ $args{Clone} }); $class->_CloneLifecycleMaps( $lifecycles->{__maps__}, $args{Name}, $args{Clone}, ); } else { $lifecycle = { type => $args{Type} }; } $lifecycles->{$args{Name}} = $lifecycle; my ($ok, $msg) = $class->_SaveLifecycles($lifecycles, $CurrentUser); return ($ok, $msg) if !$ok; return (1, $CurrentUser->loc("Lifecycle [_1] created", $args{Name})); } =head2 CreateLifecycle( CurrentUser => undef, Name => undef, Type => undef, Clone => undef ) Create a lifecycle. To clone from an existing lifecycle, pass its Name to Clone. Returns (STATUS, MESSAGE). STATUS is true if succeeded, otherwise false. =cut sub CreateLifecycle { my $class = shift; my %args = ( CurrentUser => undef, Name => undef, Type => undef, Clone => undef, @_, ); my $CurrentUser = $args{CurrentUser}; my $Name = $args{Name}; my $Type = $args{Type}; my $Clone = $args{Clone}; return (0, $CurrentUser->loc("Lifecycle Name required")) unless length $Name; return (0, $CurrentUser->loc("Lifecycle Type required")) unless length $Type; return (0, $CurrentUser->loc("Invalid lifecycle type '[_1]'", $Type)) unless $RT::Lifecycle::LIFECYCLES_TYPES{$Type}; if (length $Clone) { return (0, $CurrentUser->loc("Invalid '[_1]' lifecycle '[_2]'", $Type, $Clone)) unless grep { $_ eq $Clone } RT::Lifecycle->ListAll($Type); } return (0, $CurrentUser->loc("'[_1]' lifecycle '[_2]' already exists", $Type, $Name)) if grep { $_ eq $Name } RT::Lifecycle->ListAll($Type); return $class->_CreateLifecycle(%args); } =head2 UpdateLifecycle( CurrentUser => undef, LifecycleObj => undef, NewConfig => undef, Maps => undef ) Update passed lifecycle to the new configuration. Returns (STATUS, MESSAGE). STATUS is true if succeeded, otherwise false. =cut sub UpdateLifecycle { my $class = shift; my %args = ( CurrentUser => undef, LifecycleObj => undef, NewConfig => undef, Maps => undef, @_, ); my $CurrentUser = $args{CurrentUser}; my $name = $args{LifecycleObj}->Name; my $lifecycles = RT->Config->Get('Lifecycles'); $lifecycles->{$name} = $args{NewConfig}; if ( $args{Maps} ) { %{ $lifecycles->{__maps__} } = ( %{ $lifecycles->{__maps__} || {} }, %{ $args{Maps} }, ); } my ($ok, $msg) = $class->_SaveLifecycles($lifecycles, $CurrentUser); return ($ok, $msg) if !$ok; return (1, $CurrentUser->loc("Lifecycle [_1] updated", $name)); } =head2 UpdateMaps( CurrentUser => undef, Maps => undef ) Update lifecycle maps. Returns (STATUS, MESSAGE). STATUS is true if succeeded, otherwise false. =cut sub UpdateMaps { my $class = shift; my %args = ( CurrentUser => undef, Maps => undef, @_, ); my $CurrentUser = $args{CurrentUser}; my $lifecycles = RT->Config->Get('Lifecycles'); %{ $lifecycles->{__maps__} } = ( %{ $lifecycles->{__maps__} || {} }, %{ $args{Maps} }, ); my ($ok, $msg) = $class->_SaveLifecycles($lifecycles, $CurrentUser); return ($ok, $msg) if !$ok; return (1, $CurrentUser->loc("Lifecycle mappings updated")); } =head2 ValidateLifecycle( CurrentUser => undef, Lifecycle => undef, Name => undef ) Validate passed Lifecycle data structure. Returns (STATUS, MESSAGE). STATUS is true if succeeded, otherwise false. =cut sub ValidateLifecycle { my $self = shift; my %args = ( CurrentUser => undef, Lifecycle => undef, Name => undef, @_, ); my $current_user = $args{CurrentUser} || RT->SystemUser; my $name = $args{Name} || $self->Name; my $lifecycle = $args{Lifecycle} or return ( 0, $current_user->loc('lifecycle undefined') ); my @warnings; my $type = $lifecycle->{type} ||= 'ticket'; $lifecycle->{canonical_case} = {}; foreach my $category (qw(initial active inactive)) { for my $status ( @{ $lifecycle->{$category} || [] } ) { if ( exists $lifecycle->{canonical_case}{ lc $status } ) { push @warnings, $current_user->loc( "Duplicate status [_1] in lifecycle [_2]", lc $status, $name ); } else { $lifecycle->{canonical_case}{ lc $status } = $status; } } } # Lower-case for consistency # ->{actions} are handled below for my $state ( keys %{ $lifecycle->{defaults} || {} } ) { my $status = $lifecycle->{defaults}{$state}; push @warnings, $current_user->loc( "Nonexistant status [_1] in default states in [_2] lifecycle", lc $status, $name ) unless $lifecycle->{canonical_case}{ lc $status }; } for my $from ( keys %{ $lifecycle->{transitions} || {} } ) { push @warnings, $current_user->loc( "Nonexistant status [_1] in transitions in [_2] lifecycle", lc $from, $name ) unless $from eq '' || $lifecycle->{canonical_case}{ lc $from }; for my $status ( @{ ( $lifecycle->{transitions}{$from} ) || [] } ) { push @warnings, $current_user->loc( "Nonexistant status [_1] in transitions in [_2] lifecycle", lc $status, $name ) unless $lifecycle->{canonical_case}{ lc $status }; } } for my $schema ( keys %{ $lifecycle->{rights} || {} } ) { my ( $from, $to ) = split /\s*->\s*/, $schema, 2; unless ( $from and $to ) { push @warnings, $current_user->loc( "Invalid right transition [_1] in [_2] lifecycle", $schema, $name ); next; } push @warnings, $current_user->loc( "Nonexistant status [_1] in right transition in [_2] lifecycle", lc $from, $name ) unless $from eq '*' or $lifecycle->{canonical_case}{ lc $from }; push @warnings, $current_user->loc( "Nonexistant status [_1] in right transition in [_2] lifecycle", lc $to, $name ) unless $to eq '*' || $lifecycle->{canonical_case}{ lc $to }; push @warnings, $current_user->loc( "Invalid right name ([_1]) in [_2] lifecycle; right names must be ASCII", $lifecycle->{rights}{$schema}, $name ) if $lifecycle->{rights}{$schema} =~ /\P{ASCII}/; push @warnings, $current_user ->loc( "Invalid right name ([_1]) in [_2] lifecycle; right names must be <= 25 characters", $lifecycle->{rights}{$schema}, $name ) if length( $lifecycle->{rights}{$schema} ) > 25; } my @actions; if ( ref $lifecycle->{'actions'} eq 'HASH' ) { foreach my $k ( sort keys %{ $lifecycle->{'actions'} } ) { push @actions, $k, $lifecycle->{'actions'}{$k}; } } elsif ( ref $lifecycle->{'actions'} eq 'ARRAY' ) { @actions = @{ $lifecycle->{'actions'} }; } while ( my ( $transition, $info ) = splice @actions, 0, 2 ) { my ( $from, $to ) = split /\s*->\s*/, $transition, 2; unless ( $from and $to ) { push @warnings, $current_user->loc( "Invalid action status change [_1], in [_2] lifecycle", $transition, $name ); next; } push @warnings, $current_user->loc( "Nonexistant status [_1] in action in [_2] lifecycle", lc $from, $name ) unless $from eq '*' or $lifecycle->{canonical_case}{ lc $from }; push @warnings, $current_user->loc( "Nonexistant status [_1] in action in [_2] lifecycle", lc $to, $name ) unless $to eq '*' or $lifecycle->{canonical_case}{ lc $to }; } return @warnings ? ( 0, uniq @warnings ) : 1; } =head2 ValidateLifecycleMaps( CurrentUser => undef ) Validate lifecycle Maps. Returns (STATUS, MESSAGES). STATUS is true if succeeded, otherwise false. =cut sub ValidateLifecycleMaps { my $self = shift; my %args = ( CurrentUser => undef, @_, ); my $current_user = $args{CurrentUser} || RT->SystemUser; my @warnings; for my $mapname ( keys %{ $LIFECYCLES_CACHE{'__maps__'} || {} } ) { my ( $from, $to ) = split /\s*->\s*/, $mapname, 2; unless ( $from and $to ) { push @warnings, $current_user->loc( "Invalid lifecycle mapping [_1]", $mapname ); next; } push @warnings, $current_user->loc( "Nonexistant lifecycle [_1] in [_2] lifecycle map", $from, $mapname ) unless $LIFECYCLES_CACHE{$from}; push @warnings, $current_user->loc( "Nonexistant lifecycle [_1] in [_2] lifecycle map", $to, $mapname ) unless $LIFECYCLES_CACHE{$to}; my $map = $LIFECYCLES_CACHE{'__maps__'}{$mapname}; for my $status ( keys %{$map} ) { push @warnings, $current_user->loc( "Nonexistant status [_1] in [_2] in [_3] lifecycle map", lc $status, $from, $mapname ) if $LIFECYCLES_CACHE{$from} && !$LIFECYCLES_CACHE{$from}{canonical_case}{ lc $status }; push @warnings, $current_user->loc( "Nonexistant status [_1] in [_2] in [_3] lifecycle map", lc $map->{$status}, $to, $mapname ) if $LIFECYCLES_CACHE{$to} && !$LIFECYCLES_CACHE{$to}{canonical_case}{ lc $map->{$status} }; } } return @warnings ? ( 0, uniq @warnings ) : 1; } =head2 UpdateLifecycleLayout( CurrentUser => undef, LifecycleObj => undef, NewLayout => undef ) Update lifecycle's web admin layout. Returns (STATUS, MESSAGE). STATUS is true if succeeded, otherwise false. =cut sub UpdateLifecycleLayout { my $class = shift; my %args = ( CurrentUser => undef, LifecycleObj => undef, NewLayout => undef, @_, ); my $name = $args{LifecycleObj}->Name; my $setting = RT::Configuration->new( $args{CurrentUser} ); $setting->LoadByCols( Name => "LifecycleLayout-$name", Disabled => 0 ); if ( $setting->Id ) { my ( $ok, $msg ); if ( $args{NewLayout} ) { ( $ok, $msg ) = $setting->SetContent( $args{NewLayout} ); } else { ( $ok, $msg ) = $setting->SetDisabled(1); } return ( $ok, $msg ) if !$ok; } elsif ( $args{NewLayout} ) { my ( $ok, $msg ) = $setting->Create( Name => "LifecycleLayout-$name", Content => $args{NewLayout}, ); return ( $ok, $msg ) if !$ok; } else { return ( 0, $args{CurrentUser}->loc('That is already the current value') ); } return 1; } 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/SLA.pm������������������������������������������������������������������������������000644 �000765 �000024 �00000020505 14005011336 015053� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::SLA; =head1 NAME RT::SLA - Service Level Agreements for RT =head1 DESCRIPTION Automated due dates using service levels. =cut sub BusinessHours { my $self = shift; my $name = shift || 'Default'; require Business::Hours; my $res = new Business::Hours; my %config = RT->Config->Get('ServiceBusinessHours'); $res->business_hours(%{ $config{$name} }) if $config{$name}; return $res; } sub Agreement { my $self = shift; my %args = ( Level => undef, Type => 'Response', Time => undef, Ticket => undef, Queue => undef, @_ ); my %config = RT->Config->Get('ServiceAgreements'); my $meta = $config{'Levels'}{ $args{'Level'} }; return undef unless $meta; if ( exists $meta->{'StartImmediately'} || !defined $meta->{'Starts'} ) { $meta->{'Starts'} = { delete $meta->{'StartImmediately'} ? ( ) : ( BusinessMinutes => 0 ) , }; } return undef unless $meta->{ $args{'Type'} }; my %res; if ( ref $meta->{ $args{'Type'} } ) { %res = %{ $meta->{ $args{'Type'} } }; } elsif ( $meta->{ $args{'Type'} } =~ /^\d+$/ ) { %res = ( BusinessMinutes => $meta->{ $args{'Type'} } ); } else { $RT::Logger->error("Levels of SLA should be either number or hash ref"); return undef; } if ( $args{'Ticket'} && $res{'IgnoreOnStatuses'} ) { my $status = $args{'Ticket'}->Status; return undef if grep $_ eq $status, @{$res{'IgnoreOnStatuses'}}; } $res{'OutOfHours'} = $meta->{'OutOfHours'}{ $args{'Type'} }; $args{'Queue'} ||= $args{'Ticket'}->QueueObj if $args{'Ticket'}; if ( $args{'Queue'} && ref $config{'QueueDefault'}{ $args{'Queue'}->Name } ) { $res{'Timezone'} = $config{'QueueDefault'}{ $args{'Queue'}->Name }{'Timezone'}; } $res{'Timezone'} ||= $meta->{'Timezone'} || $RT::Timezone; $res{'BusinessHours'} = $meta->{'BusinessHours'}; return \%res; } sub Due { my $self = shift; return $self->CalculateTime( @_ ); } sub Starts { my $self = shift; return $self->CalculateTime( @_, Type => 'Starts' ); } sub CalculateTime { my $self = shift; my %args = (@_); my $agreement = $args{'Agreement'} || $self->Agreement( @_ ); return undef unless $agreement and ref $agreement eq 'HASH'; my $res = $args{'Time'}; my $ok = eval { local $ENV{'TZ'} = $ENV{'TZ'}; if ( $agreement->{'Timezone'} && $agreement->{'Timezone'} ne ($ENV{'TZ'}||'') ) { $ENV{'TZ'} = $agreement->{'Timezone'}; require POSIX; POSIX::tzset(); } my $bhours = $self->BusinessHours( $agreement->{'BusinessHours'} ); if ( $agreement->{'OutOfHours'} && $bhours->first_after( $res ) != $res ) { foreach ( qw(RealMinutes BusinessMinutes) ) { next unless my $mod = $agreement->{'OutOfHours'}{ $_ }; ($agreement->{ $_ } ||= 0) += $mod; } } if ( $args{ Ticket } && $agreement->{ IgnoreOnStatuses } && $agreement->{ ExcludeTimeOnIgnoredStatuses } ) { my $txns = RT::Transactions->new( RT->SystemUser ); $txns->LimitToTicket($args{Ticket}->id); $txns->Limit( FIELD => 'Field', VALUE => 'Status', ); my $date = RT::Date->new( RT->SystemUser ); $date->Set( Value => $args{ Time } ); $txns->Limit( FIELD => 'Created', OPERATOR => '>=', VALUE => $date->ISO( Timezone => 'UTC' ), ); my $last_time = $args{ Time }; while ( my $txn = $txns->Next ) { if ( grep( { $txn->OldValue eq $_ } @{ $agreement->{ IgnoreOnStatuses } } ) ) { if ( !grep( { $txn->NewValue eq $_ } @{ $agreement->{ IgnoreOnStatuses } } ) ) { if ( defined $agreement->{ 'BusinessMinutes' } ) { # re-init $bhours to make sure we don't have a cached start/end, # so the time here is not outside the calculated business hours my $bhours = $self->BusinessHours( $agreement->{ 'BusinessHours' } ); my $time = $bhours->between( $last_time, $txn->CreatedObj->Unix ); if ( $time > 0 ) { $res = $bhours->add_seconds( $res, $time ); } } else { my $time = $txn->CreatedObj->Unix - $last_time; $res += $time; } $last_time = $txn->CreatedObj->Unix; } } else { $last_time = $txn->CreatedObj->Unix; } } } if ( defined $agreement->{'BusinessMinutes'} ) { if ( $agreement->{'BusinessMinutes'} ) { $res = $bhours->add_seconds( $res, 60 * $agreement->{'BusinessMinutes'}, ); } else { $res = $bhours->first_after( $res ); } } $res += 60 * $agreement->{'RealMinutes'} if defined $agreement->{'RealMinutes'}; 1; }; POSIX::tzset() if $agreement->{'Timezone'} && $agreement->{'Timezone'} ne ($ENV{'TZ'}||''); die $@ unless $ok; return $res; } sub GetDefaultServiceLevel { my $self = shift; my %args = (Ticket => undef, Queue => undef, @_); unless ( $args{'Queue'} || $args{'Ticket'} ) { $args{'Ticket'} = $self->TicketObj if $self->can('TicketObj'); } if ( !$args{'Queue'} && $args{'Ticket'} ) { $args{'Queue'} = $args{'Ticket'}->QueueObj; } my %config = RT->Config->Get('ServiceAgreements'); if ( $args{'Queue'} ) { return undef if $args{Queue}->SLADisabled; return $args{'Queue'}->SLA if $args{'Queue'}->SLA; if ( $config{'QueueDefault'} && ( my $info = $config{'QueueDefault'}{ $args{'Queue'}->Name } )) { return $info unless ref $info; return $info->{'Level'} || $config{'Default'}; } } return $config{'Default'}; } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/URI.pm������������������������������������������������������������������������������000644 �000765 �000024 �00000014372 14005011336 015100� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::URI; use strict; use warnings; use base 'RT::Base'; use RT::URI::base; use Carp; =head1 NAME RT::URI =head1 DESCRIPTION This class provides a base class for URIs, such as those handled by RT::Link objects. =head1 API =cut =head2 new Create a new RT::URI object. =cut sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; bless( $self, $class ); $self->CurrentUser(@_); 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> Given a local object, such as an RT::Ticket or an RT::Article, this routine will return a URI for the local object =cut sub FromObject { my $self = shift; my $obj = shift; return undef unless $obj->can('URI'); return $self->FromURI($obj->URI); } =head2 FromURI <URI> Returns a local object id for this content. You are expected to know what sort of object this is the Id of Returns true if everything is ok, otherwise false =cut sub FromURI { my $self = shift; my $uri = shift; return undef unless ($uri); my $scheme; # Special case: integers passed in as URIs must be ticket ids if ($uri =~ /^(\d+)$/) { $scheme = "fsck.com-rt"; } elsif ($uri =~ /^((?!javascript|data)(?:\w|\.|-)+?):/i) { $scheme = $1; } else { $self->{resolver} = RT::URI::base->new( $self->CurrentUser ); # clear resolver $RT::Logger->warning("Could not determine a URI scheme for $uri"); return (undef); } # load up a resolver object for this scheme $self->_GetResolver($scheme); unless ($self->Resolver->ParseURI($uri)) { $RT::Logger->warning( "Resolver " . ref( $self->Resolver ) . " could not parse $uri, maybe Organization config was changed?" ); $self->{resolver} = RT::URI::base->new( $self->CurrentUser ); # clear resolver return (undef); } return(1); } =head2 _GetResolver <scheme> Gets an RT URI resolver for the scheme <scheme>. Falls back to a null resolver. RT::URI::base. =cut sub _GetResolver { my $self = shift; my $scheme = shift; $scheme =~ s/(\.|-)/_/g; my $resolver; eval " require RT::URI::$scheme; \$resolver = RT::URI::$scheme->new(\$self->CurrentUser); "; if ($resolver) { $self->{'resolver'} = $resolver; } else { RT->Logger->warning("Failed to create new resolver object for scheme '$scheme': $@") if $@ !~ m{Can't locate RT/URI/\Q$scheme\E}; $self->{'resolver'} = RT::URI::base->new($self->CurrentUser); } } =head2 Scheme Returns a local object id for this content. You are expected to know what sort of object this is the Id of =cut sub Scheme { my $self = shift; return ($self->Resolver->Scheme); } =head2 URI Returns a local object id for this content. You are expected to know what sort of object this is the Id of =cut sub URI { my $self = shift; return ($self->Resolver->URI); } =head2 Object Returns a local object for this content. This will usually be an RT::Ticket or somesuch =cut sub Object { my $self = shift; return($self->Resolver->Object); } =head2 IsLocal Returns a local object for this content. This will usually be an RT::Ticket or somesuch =cut sub IsLocal { my $self = shift; return $self->Resolver->IsLocal; } =head2 AsHREF =cut sub AsHREF { my $self = shift; return $self->Resolver->HREF; } =head2 Resolver Returns this URI's URI resolver object =cut sub Resolver { my $self =shift; return ($self->{'resolver'}); } =head2 AsString Returns a friendly display form of the object if Local, or the full URI =cut sub AsString { my $self = shift; return $self->Resolver->AsString; } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Report/�����������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 015347� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2.pm����������������������������������������������������������������������������000644 �000765 �000024 �00000132251 14005011336 015275� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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; use 5.010001; package RT::REST2; our $REST_PATH = '/REST/2.0'; use Plack::Builder; use RT::REST2::Dispatcher; =encoding utf-8 =head1 NAME RT::REST2 - RT REST API v. 2.0 under /REST/2.0/ =head1 USAGE =head2 Tutorial To make it easier to authenticate to REST2, we recommend also using L<RT::Authen::Token>. Visit "Logged in as ___" -> Settings -> Auth Tokens. Create an Auth Token, give it any description (such as "REST2 with curl"). Make note of the authentication token it provides to you. For other authentication options see the section L<Authentication Methods> below. =head3 Authentication Run the following in a terminal, filling in XX_TOKEN_XX from the auth token above and XX_RT_URL_XX with the URL for your RT instance. curl -H 'Authorization: token XX_TOKEN_XX' 'XX_RT_URL_XX/REST/2.0/queues/all' This does an authenticated request (using the C<Authorization> HTTP header with type C<token>) for all of the queues you can see. You should see a response, typical of search results, like this: { "total" : 1, "count" : 1, "page" : 1, "pages" : 1, "per_page" : 20, "items" : [ { "type" : "queue", "id" : "1", "_url" : "XX_RT_URL_XX/REST/2.0/queue/1" } ] } This format is JSON, which is a format for which many programming languages provide libraries for parsing and generating. (If you instead see a response like C<{"message":"Unauthorized"}> that indicates RT couldn't process your authentication token successfully; make sure the word "token" appears between "Authorization:" and the auth token that RT provided to you) =head3 Following Links You can request one of the provided C<_url>s to get more information about that queue. curl -H 'Authorization: token XX_TOKEN_XX' 'XX_QUEUE_URL_XX' This will give a lot of information, like so: { "id" : 1, "Name" : "General", "Description" : "The default queue", "Lifecycle" : "default", ... "CustomFields" : {}, "_hyperlinks" : [ { "id" : "1", "ref" : "self", "type" : "queue", "_url" : "XX_RT_URL_XX/REST/2.0/queue/1" }, { "ref" : "history", "_url" : "XX_RT_URL_XX/REST/2.0/queue/1/history" }, { "ref" : "create", "type" : "ticket", "_url" : "XX_RT_URL_XX/REST/2.0/ticket?Queue=1" } ], } Of particular note is the C<_hyperlinks> key, which gives you a list of related resources to examine (following the L<https://en.wikipedia.org/wiki/HATEOAS> principle). For example an entry with a C<ref> of C<history> lets you examine the transaction log for a record. You can implement your REST API client knowing that any other hypermedia link with a C<ref> of C<history> has the same meaning, regardless of whether it's the history of a queue, ticket, asset, etc. Another C<ref> you'll see in C<_hyperlinks> is C<create>, with a C<type> of C<ticket>. This of course gives you the URL to create tickets I<in this queue>. Importantly, if your user does I<not> have the C<CreateTicket> permission in this queue, then REST2 would simply not include this hyperlink in its response to your request. This allows you to dynamically adapt your client's behavior to its presence or absence, just like the web version of RT does. =head3 Creating Tickets Let's use the C<_url> from the C<create> hyperlink with type C<ticket>. To create a ticket is a bit more involved, since it requires providing a different HTTP verb (C<POST> instead of C<GET>), a C<Content-Type> header (to tell REST2 that your content is JSON instead of, say, XML), and the fields for your new ticket such as Subject. Here is the curl invocation, wrapped to multiple lines for readability. curl -X POST -H "Content-Type: application/json" -d '{ "Subject": "hello world" }' -H 'Authorization: token XX_TOKEN_XX' 'XX_TICKET_CREATE_URL_XX' If successful, that will provide output like so: { "_url" : "XX_RT_URL_XX/REST/2.0/ticket/20", "type" : "ticket", "id" : "20" } (REST2 also produces the status code of C<201 Created> with a C<Location> header of the new ticket, which you may choose to use instead of the JSON response) We can fetch that C<_url> to continue working with this newly-created ticket. Request the ticket like so (make sure to include the C<-i> flag to see response's HTTP headers). curl -i -H 'Authorization: token XX_TOKEN_XX' 'XX_TICKET_URL_XX' You'll first see that there are many hyperlinks for tickets, including one for each Lifecycle action you can perform, history, comment, correspond, etc. Again these adapt to whether you have the appropriate permissions to do these actions. Additionally you'll see an C<ETag> header for this record, which can be used for conflict avoidance (L<https://en.wikipedia.org/wiki/HTTP_ETag>). We'll first try updating this ticket with an I<invalid> C<ETag> to see what happens. =head3 Updating Tickets For updating tickets we use the C<PUT> verb, but otherwise it looks much like a ticket creation. curl -X PUT -H "Content-Type: application/json" -H "If-Match: invalid-etag" -d '{ "Subject": "trial update" }' -H 'Authorization: token XX_TOKEN_XX' 'XX_TICKET_URL_XX' You'll get an error response like C<{"message":"Precondition Failed"}> and a status code of 412. If you examine the ticket, you'll also see that its Subject was not changed. This is because the C<If-Match> header advises the server to make changes I<if and only if> the ticket's C<ETag> matches what you provide. Since it differed, the server refused the request and made no changes. Now, try the same request by replacing the value "invalid-etag" in the C<If-Match> request header with the real C<ETag> you'd received when you requested the ticket previously. You'll then get a JSON response like: ["Ticket 1: Subject changed from 'hello world' to 'trial update'"] which is a list of messages meant for displaying to an end-user. If you C<GET> the ticket again, you'll observe that the C<ETag> header now has a different value, indicating that the ticket itself has changed. This means if you were to retry the C<PUT> update with the previous (at the time, expected) C<ETag> you would instead be rejected by the server with Precondition Failed. You can use C<ETag> and C<If-Match> headers to avoid race conditions such as two people updating a ticket at the same time. Depending on the sophistication of your client, you may be able to automatically retry the change by incorporating the changes made on the server (for example adding time worked can be automatically be recalculated). You may of course choose to ignore the C<ETag> header and not provide C<If-Match> in your requests; RT doesn't require its use. =head3 Replying/Commenting Tickets You can reply to or comment a ticket by C<POST>ing to C<_url> from the C<correspond> or C<comment> hyperlinks that were returned when fetching the ticket. curl -X POST -H "Content-Type: application/json" -d '{ "Subject" : "response", "Content" : "What is your <em>issue</em>?", "ContentType": "text/html", "TimeTaken" : "1" }' -H 'Authorization: token XX_TOKEN_XX' 'XX_TICKET_URL_XX'/correspond Replying or commenting a ticket is quite similar to a ticket creation: you send a C<POST> request, with data encoded in C<JSON>. The difference lies in the properties of the JSON data object you can pass: =over 4 =item C<Subject> The subject of your response/comment, optional =item C<Content> The content of your response/comment, mandatory unless there is a non empty C<Attachments> property to add at least one attachment to the ticket (see L<Add Attachments> section below). =item C<ContentType> The MIME content type of your response/comment, typically C<text/plain> or C</text/html>, mandatory unless there is a non empty C<Attachments> property to add at least one attachment to the ticket (see L<Add Attachments> section below). =item C<TimeTaken> The time, in minutes, you've taken to work on your response/comment, optional. =back =head3 Add Attachments You can attach any binary or text file to a ticket via create, correspond, or comment by adding an C<Attachments> property in the JSON object. The value should be a JSON array where each item represents a file you want to attach. Each item is a JSON object with the following properties: =over 4 =item C<FileName> The name of the file to attach to your response/comment, mandatory. =item C<FileType> The MIME type of the file to attach to your response/comment, mandatory. =item C<FileContent> The content, I<encoded in C<MIME Base64>> of the file to attach to your response/comment, mandatory. =back The reason why you should encode the content of any file to C<MIME Base64> is that a JSON string value should be a sequence of zero or more Unicode characters. C<MIME Base64> is a binary-to-text encoding scheme widely used (for eg. by web browser) to send binary data when text data is required. Most popular language have C<MIME Base64> libraries that you can use to encode the content of your attached files (see L<MIME::Base64> for C<Perl>). Note that even text files should be C<MIME Base64> encoded to be passed in the C<FileContent> property. Here's a Perl example to send an image and a plain text file attached to a comment: #!/usr/bin/perl use strict; use warnings; use LWP::UserAgent; use JSON; use MIME::Base64; use Data::Dumper; my $url = 'http://rt.local/REST/2.0/ticket/1/comment'; my $img_path = '/tmp/my_image.png'; my $img_content; open my $img_fh, '<', $img_path or die "Cannot read $img_path: $!\n"; { local $/; $img_content = <$img_fh>; } close $img_fh; $img_content = MIME::Base64::encode_base64($img_content); my $txt_path = '~/.bashrc'; my $txt_content; open my $txt_fh, '<', glob($txt_path) or die "Cannot read $txt_path: $!\n"; { local $/; $txt_content = <$txt_fh>; } close $txt_fh; $txt_content = MIME::Base64::encode_base64($txt_content); my $json = JSON->new->utf8; my $payload = { Content => '<p>I want <b>two</b> <em>attachments</em></p>', ContentType => 'text/html', Subject => 'Attachments in JSON Array', Attachments => [ { FileName => 'my_image.png', FileType => 'image/png', FileContent => $img_content, }, { FileName => '.bashrc', FileType => 'text/plain', FileContent => $txt_content, }, ], }; my $req = HTTP::Request->new(POST => $url); $req->header('Authorization' => 'token 6-66-66666666666666666666666666666666'); $req->header('Content-Type' => 'application/json' ); $req->header('Accept' => 'application/json' ); $req->content($json->encode($payload)); my $ua = LWP::UserAgent->new; my $res = $ua->request($req); print Dumper($json->decode($res->content)) . "\n"; Encoding the content of attachments file in C<MIME Base64> has the drawback of adding some processing overhead and to increase the sent data size by around 33%. RT's REST2 API provides another way to attach any binary or text file to your response or comment by C<POST>ing, instead of a JSON request, a C<multipart/form-data> request. This kind of request is similar to what the browser sends when you add attachments in RT's reply or comment form. As its name suggests, a C<multipart/form-data> request message contains a series of parts, each representing a form field. To reply to or comment a ticket, the request has to include a field named C<JSON>, which, as previously, is a JSON object with C<Subject>, C<Content>, C<ContentType>, C<TimeTaken> properties. Files can then be attached by specifying a field named C<Attachments> for each of them, with the content of the file as value and the appropriate MIME type. The curl invocation is quite straightforward: curl -X POST -H "Content-Type: multipart/form-data" -F 'JSON={ "Subject" : "Attachments in multipart/form-data", "Content" : "<p>I want <b>two</b> <em>attachments</em></p>", "ContentType": "text/html", "TimeTaken" : "1" };type=application/json' -F 'Attachments=@/tmp/my_image.png;type=image/png' -F 'Attachments=@/tmp/.bashrc;type=text/plain' -H 'Authorization: token XX_TOKEN_XX' 'XX_TICKET_URL_XX'/comment =head3 Summary RT's REST2 API provides the tools you need to build robust and dynamic integrations. Tools like C<ETag>/C<If-Match> allow you to avoid conflicts such as two people taking a ticket at the same time. Using JSON for all data interchange avoids problems caused by parsing text. Hypermedia links inform your client application of what the user has the ability to do. Careful readers will see that, other than our initial entry into the system, we did not I<generate> any URLs. We only I<followed> links, just like you do when browsing a website on your computer. We've better decoupled the client's implementation from the server's REST API. Additionally, this system lets you be informed of new capabilities in the form of additional hyperlinks. Using these tools and principles, REST2 will help you build rich, robust, and powerful integrations with the other applications and services that your team uses. =head2 Endpoints Currently provided endpoints under C</REST/2.0/> are described below. Wherever possible please consider using C<_hyperlinks> hypermedia controls available in response bodies rather than hardcoding URLs. For simplicity, the examples below omit the extra options to curl for SSL like --cacert. =head3 Tickets GET /tickets?query=<TicketSQL> search for tickets using TicketSQL GET /tickets?simple=1;query=<simple search query> search for tickets using simple search syntax POST /tickets search for tickets with the 'query' and optional 'simple' parameters POST /ticket create a ticket; provide JSON content GET /ticket/:id retrieve a ticket PUT /ticket/:id update a ticket's metadata; provide JSON content PUT /ticket/:id/take PUT /ticket/:id/untake PUT /ticket/:id/steal take, untake, or steal the ticket DELETE /ticket/:id set status to deleted POST /ticket/:id/correspond POST /ticket/:id/comment add a reply or comment to the ticket GET /ticket/:id/history retrieve list of transactions for ticket POST /tickets/bulk create multiple tickets; provide JSON content(array of hashes) PUT /tickets/bulk update multiple tickets' metadata; provide JSON content(array of hashes) =head3 Ticket Examples Below are some examples using the endpoints above. # Create a ticket, setting some custom fields curl -X POST -H "Content-Type: application/json" -u 'root:password' -d '{ "Queue": "General", "Subject": "Create ticket test", "Requestor": "user1@example.com", "Cc": "user2@example.com", "Content": "Testing a create", "CustomFields": {"Severity": "Low"}}' 'https://myrt.com/REST/2.0/ticket' # Update a ticket, with a custom field update curl -X PUT -H "Content-Type: application/json" -u 'root:password' -d '{ "Subject": "Update test", "CustomFields": {"Severity": "High"}}' 'https://myrt.com/REST/2.0/ticket/6' # Update a ticket, with links update curl -X PUT -H "Content-Type: application/json" -u 'root:password' -d '{ "DependsOn": [2, 3], "ReferredToBy": 1 }' 'https://myrt.com/REST/2.0/ticket/6' curl -X PUT -H "Content-Type: application/json" -u 'root:password' -d '{ "AddDependsOn": [4, 5], "DeleteReferredToBy": 1 }' 'https://myrt.com/REST/2.0/ticket/6' # Merge a ticket into another curl -X PUT -H "Content-Type: application/json" -u 'root:password' -d '{ "MergeInto": 3 }' 'https://myrt.com/REST/2.0/ticket/6' # Take a ticket curl -X PUT -H "Content-Type: application/json" -u 'root:password' 'https://myrt.com/REST/2.0/ticket/6/take' # Untake a ticket curl -X PUT -H "Content-Type: application/json" -u 'root:password' 'https://myrt.com/REST/2.0/ticket/6/untake' # Steal a ticket curl -X PUT -H "Content-Type: application/json" -u 'root:password' 'https://myrt.com/REST/2.0/ticket/6/steal' # Correspond a ticket curl -X POST -H "Content-Type: application/json" -u 'root:password' -d '{ "Content": "Testing a correspondence", "ContentType": "text/plain" }' 'https://myrt.com/REST/2.0/ticket/6/correspond' # Correspond a ticket with a transaction custom field curl -X POST -H "Content-Type: application/json" -u 'root:password' -d '{ "Content": "Testing a correspondence", "ContentType": "text/plain", "TxnCustomFields": {"MyField": "custom field value"} }' 'https://myrt.com/REST/2.0/ticket/6/correspond' # Comment on a ticket curl -X POST -H "Content-Type: text/plain" -u 'root:password' -d 'Testing a comment' 'https://myrt.com/REST/2.0/ticket/6/comment' # Comment on a ticket with custom field update curl -X POST -H "Content-Type: application/json" -u 'root:password' -d '{ "Content": "Testing a comment", "ContentType": "text/plain", "CustomFields": {"Severity": "High"} }' 'https://myrt.com/REST/2.0/ticket/6/comment' =head3 Ticket Fields The following describes some of the values you can send when creating and updating tickets as shown in the examples above. =over 4 =item Ticket Links As shown above, you can update links on a ticket with a C<PUT> and passing the link relationship you want to create. The available keys are Parent, Child, RefersTo, ReferredToBy, DependsOn, and DependedOnBy. These correspond with the standard link types on a ticket. The value can be a single ticket id or an array of ticket ids. The indicated link relationship will be set to the value passed, adding or removing as needed. You can specifically add or remove a link by prepending C<Add> or C<Delete> to the link type, like C<AddParent> or C<DeleteParent>. These versions also accept a single ticket id or an array. =back =head3 Transactions GET /transactions?query=<JSON> POST /transactions search for transactions using L</JSON searches> syntax GET /ticket/:id/history GET /queue/:id/history GET /queue/:name/history GET /asset/:id/history GET /user/:id/history GET /user/:name/history GET /group/:id/history get transactions for record GET /transaction/:id retrieve a transaction =head3 Attachments and Messages GET /attachments?query=<JSON> POST /attachments search for attachments using L</JSON searches> syntax GET /transaction/:id/attachments get attachments for transaction GET /ticket/:id/attachments get attachments associated with a ticket GET /attachment/:id retrieve an attachment. Note that the C<Content> field contains the base64-encoded representation of the raw content. =head3 Image and Binary Object Custom Field Values GET /download/cf/:id retrieve an image or a binary file as an object custom field value =head3 Queues GET /queues/all retrieve list of all queues you can see GET /queues?query=<JSON> POST /queues search for queues using L</JSON searches> syntax POST /queue create a queue; provide JSON content GET /queue/:id GET /queue/:name retrieve a queue by numeric id or name PUT /queue/:id PUT /queue/:name update a queue's metadata; provide JSON content DELETE /queue/:id DELETE /queue/:name disable queue GET /queue/:id/history GET /queue/:name/history retrieve list of transactions for queue =head3 Assets GET /assets?query=<JSON> POST /assets search for assets using L</JSON searches> syntax POST /asset create an asset; provide JSON content GET /asset/:id retrieve an asset PUT /asset/:id update an asset's metadata; provide JSON content DELETE /asset/:id set status to deleted GET /asset/:id/history retrieve list of transactions for asset =head3 Assets Examples Below are some examples using the endpoints above. # Create an Asset curl -X POST -H "Content-Type: application/json" -u 'root:password' -d '{"Name" : "Asset From Rest", "Catalog" : "General assets", "Content" : "Some content"}' 'https://myrt.com/REST/2.0/asset' # Search Assets curl -X POST -H "Content-Type: application/json" -u 'root:password' -d '[{ "field" : "id", "operator" : ">=", "value" : 0 }]' 'https://myrt.com/REST/2.0/assets' =head3 Catalogs GET /catalogs/all retrieve list of all catalogs you can see GET /catalogs?query=<JSON> POST /catalogs search for catalogs using L</JSON searches> syntax POST /catalog create a catalog; provide JSON content GET /catalog/:id GET /catalog/:name retrieve a catalog by numeric id or name PUT /catalog/:id PUT /catalog/:name update a catalog's metadata; provide JSON content DELETE /catalog/:id DELETE /catalog/:name disable catalog =head3 Articles GET /articles?query=<JSON> POST /articles search for articles using L</JSON searches> syntax POST /article create an article; provide JSON content GET /article/:id retrieve an article PUT /article/:id update an article's metadata; provide JSON content DELETE /article/:id set status to deleted GET /article/:id/history retrieve list of transactions for article =head3 Classes GET /classes/all retrieve list of all classes you can see GET /classes?query=<JSON> POST /classes search for classes using L</JSON searches> syntax POST /class create a class; provide JSON content GET /class/:id GET /class/:name retrieve a class by numeric id or name PUT /class/:id PUT /class/:name update a class's metadata; provide JSON content DELETE /class/:id DELETE /class/:name disable class =head3 Users GET /users?query=<JSON> POST /users search for users using L</JSON searches> syntax POST /user create a user; provide JSON content GET /user/:id GET /user/:name retrieve a user by numeric id or username (including its memberships and whether it is disabled) PUT /user/:id PUT /user/:name update a user's metadata (including its Disabled status); provide JSON content DELETE /user/:id DELETE /user/:name disable user GET /user/:id/history GET /user/:name/history retrieve list of transactions for user =head3 Groups GET /groups?query=<JSON> POST /groups search for groups using L</JSON searches> syntax POST /group create a (user defined) group; provide JSON content GET /group/:id retrieve a group (including its members and whether it is disabled) PUT /group/:id update a groups's metadata (including its Disabled status); provide JSON content DELETE /group/:id disable group GET /group/:id/history retrieve list of transactions for group =head3 User Memberships GET /user/:id/groups GET /user/:name/groups retrieve list of groups which a user is a member of PUT /user/:id/groups PUT /user/:name/groups add a user to groups; provide a JSON array of groups ids DELETE /user/:id/group/:id DELETE /user/:name/group/:id remove a user from a group DELETE /user/:id/groups DELETE /user/:name/groups remove a user from all groups =head3 Group Members GET /group/:id/members retrieve list of direct members of a group GET /group/:id/members?recursively=1 retrieve list of direct and recursive members of a group GET /group/:id/members?users=0 retrieve list of direct group members of a group GET /group/:id/members?users=0&recursively=1 retrieve list of direct and recursive group members of a group GET /group/:id/members?groups=0 retrieve list of direct user members of a group GET /group/:id/members?groups=0&recursively=1 retrieve list of direct and recursive user members of a group PUT /group/:id/members add members to a group; provide a JSON array of principal ids DELETE /group/:id/member/:id remove a member from a group DELETE /group/:id/members remove all members from a group =head3 Custom Fields GET /customfields?query=<JSON> POST /customfields search for custom fields using L</JSON searches> syntax POST /customfield create a customfield; provide JSON content GET /catalog/:id/customfields?query=<JSON> POST /catalog/:id/customfields search for custom fields attached to a catalog using L</JSON searches> syntax GET /class/:id/customfields?query=<JSON> POST /class/:id/customfields search for custom fields attached to a class using L</JSON searches> syntax GET /queue/:id/customfields?query=<JSON> POST /queue/:id/customfields search for custom fields attached to a queue using L</JSON searches> syntax GET /customfield/:id retrieve a custom field, with values if type is Select GET /customfield/:id?category=<category name> retrieve a custom field, with values filtered by category if type is Select PUT /customfield/:id update a custom field's metadata; provide JSON content DELETE /customfield/:id disable customfield =head3 Custom Field Values GET /customfield/:id/values?query=<JSON> POST /customfield/:id/values search for values of a custom field using L</JSON searches> syntax POST /customfield/:id/value add a value to a custom field; provide JSON content GET /customfield/:id/value/:id retrieve a value of a custom field PUT /customfield/:id/value/:id update a value of a custom field; provide JSON content DELETE /customfield/:id/value/:id remove a value from a custom field =head3 Custom Roles GET /customroles?query=<JSON> POST /customroles search for custom roles using L</JSON searches> syntax GET /customrole/:id retrieve a custom role =head3 Miscellaneous GET / produces this documentation GET /rt produces system information =head2 JSON searches Some resources accept a basic JSON structure as the search conditions which specifies one or more fields to limit on (using specified operators and values). An example: curl -si -u user:pass https://rt.example.com/REST/2.0/queues -XPOST --data-binary ' [ { "field": "Name", "operator": "LIKE", "value": "Engineering" }, { "field": "Lifecycle", "value": "helpdesk" } ] ' The JSON payload must be an array of hashes with the keys C<field> and C<value> and optionally C<operator>. Results can be sorted by using multiple query parameter arguments C<orderby> and C<order>. Each C<orderby> query parameter specify a field to be used for sorting results. If the request includes more than one C<orderby> query parameter, results are sorted according to corresponding fields in the same order than they are specified. For instance, if you want to sort results according to creation date and then by id (in case of some items have the same creation date), your request should specify C<?orderby=Created&orderby=id>. By default, results are sorted in ascending order. To sort results in descending order, you should use C<order=DESC> query parameter. Any other value for C<order> query parameter will be treated as C<order=ASC>, for ascending order. The order of the C<order> query parameters should be the same as the C<orderby> query parameters. Therefore, if you specify two fields to sort the results (with two C<orderby> parameters) and you want to sort the second field by descending order, you should also explicitely specify C<order=ASC> for the first field: C<orderby=Created&order=ASC&orderby=id&order=DESC>. C<orderby> and C<order> query parameters are supported in both JSON and TicketSQL searches. The same C<field> is specified more than one time to express more than one condition on this field. For example: [ { "field": "id", "operator": ">", "value": $min }, { "field": "id", "operator": "<", "value": $max } ] By default, RT will aggregate these conditions with an C<OR>, except for when searching queues, where an C<AND> is applied. If you want to search for multiple conditions on the same field aggregated with an C<AND> (or an C<OR> for queues), you can specify C<entry_aggregator> keys in corresponding hashes: [ { "field": "id", "operator": ">", "value": $min }, { "field": "id", "operator": "<", "value": $max, "entry_aggregator": "AND" } ] Results are returned in L<the format described below|/"Example of plural resources (collections)">. =head2 Example of plural resources (collections) Resources which represent a collection of other resources use the following standard JSON format: { "count" : 20, "page" : 1, "pages" : 191, "per_page" : 20, "next_page" : "<collection path>?page=2" "total" : 3810, "items" : [ { … }, { … }, … ] } Each item is nearly the same representation used when an individual resource is requested. =head2 Object Custom Field Values When creating (via C<POST>) or updating (via C<PUT>) a resource which has some custom fields attached to, you can specify the value(s) for these customfields in the C<CustomFields> property of the JSON object parameter. The C<CustomFields> property should be a JSON object, with each property being the custom field identifier or name. If the custom field can have only one value, you just have to speciy the value as JSON string for this custom field. If the customfield can have several value, you have to specify a JSON array of each value you want for this custom field. "CustomFields": { "XX_SINGLE_CF_ID_XX" : "My Single Value", "XX_MULTI_VALUE_CF_ID": [ "My First Value", "My Second Value" ] } Note that for a multi-value custom field, you have to specify all the values for this custom field. Therefore if the customfield for this resource already has some values, the existing values must be including in your update request if you want to keep them (and add some new values). Conversely, if you want to delete some existing values, do not include them in your update request (including only values you wan to keep). The following example deletes "My Second Value" from the previous example: "CustomFields": { "XX_MULTI_VALUE_CF_ID": [ "My First Value" ] } To delete a single-value custom field, set its value to JSON C<null> (C<undef> in Perl): "CustomFields": { "XX_SINGLE_CF_ID_XX" : null } New values for Image and Binary custom fields can be set by specifying a JSON object as value for the custom field identifier or name with the following properties: =over 4 =item C<FileName> The name of the file to attach, mandatory. =item C<FileType> The MIME type of the file to attach, mandatory. =item C<FileContent> The content, I<encoded in C<MIME Base64>> of the file to attach, mandatory. =back The reason why you should encode the content of the image or binary file to C<MIME Base64> is that a JSON string value should be a sequence of zero or more Unicode characters. C<MIME Base64> is a binary-to-text encoding scheme widely used (for eg. by web browser) to send binary data when text data is required. Most popular language have C<MIME Base64> libraries that you can use to encode the content of your attached files (see L<MIME::Base64> for C<Perl>). Note that even text files should be C<MIME Base64> encoded to be passed in the C<FileContent> property. "CustomFields": { "XX_SINGLE_IMAGE_OR_BINARY_CF_ID_XX" : { "FileName" : "image.png", "FileType" : "image/png", "FileContent": "XX_BASE_64_STRING_XX" }, "XX_MULTI_VALUE_IMAGE_OR_BINARY_CF_ID": [ { "FileName" : "another_image.png", "FileType" : "image/png", "FileContent": "XX_BASE_64_STRING_XX" }, { "FileName" : "hello_world.txt", "FileType" : "text/plain", "FileContent": "SGVsbG8gV29ybGQh" } ] } Encoding the content of image or binary files in C<MIME Base64> has the drawback of adding some processing overhead and to increase the sent data size by around 33%. RT's REST2 API provides another way to upload image or binary files as custom field alues by sending, instead of a JSON request, a C<multipart/form-data> request. This kind of request is similar to what the browser sends when you upload a file in RT's ticket creation or update forms. As its name suggests, a C<multipart/form-data> request message contains a series of parts, each representing a form field. To create or update a ticket with image or binary file, the C<multipart/form-data> request has to include a field named C<JSON>, which, as previously, is a JSON object with C<Queue>, C<Subject>, C<Content>, C<ContentType>, etc. properties. But instead of specifying each custom field value as a JSON object with C<FileName>, C<FileType> and C<FileContent> properties, each custom field value should be a JSON object with C<UploadField>. You can choose anything you want for this field name, except I<Attachments>, which should be reserved for attaching files to a response or a comment to a ticket. Files can then be attached by specifying a field named as specified in the C<CustomFields> property for each of them, with the content of the file as value and the appropriate MIME type. Here is an exemple of a curl invocation, wrapped to multiple lines for readability, to create a ticket with a multipart/request to upload some image or binary files as custom fields values. curl -X POST -H "Content-Type: multipart/form-data" -F 'JSON={ "Queue" : "General", "Subject" : "hello world", "Content" : "That <em>damned</em> printer is out of order <b>again</b>!", "ContentType": "text/html", "CustomFields" : { "XX_SINGLE_IMAGE_OR_BINARY_CF_ID_XX" => { "UploadField": "FILE_1", "XX_MULTI_VALUE_IMAGE_OR_BINARY_CF_ID" => [ { "UploadField": "FILE_2" }, { "UploadField": "FILE_3" } ] } };type=application/json' -F 'FILE_1=@/tmp/image.png;type=image/png' -F 'FILE_2=@/tmp/another_image.png;type=image/png' -F 'FILE_3=@/etc/cups/cupsd.conf;type=text/plain' -H 'Authorization: token XX_TOKEN_XX' 'XX_RT_URL_XX'/tickets If you want to delete some existing values from a multi-value image or binary custom field, you can just pass the existing filename as value for the custom field identifier or name, no need to upload again the content of the file. The following example will delete the text file and keep the image upload in previous example: "CustomFields": { "XX_MULTI_VALUE_IMAGE_OR_BINARY_CF_ID": [ "image.png" ] } To download an image or binary file which is the custom field value of a resource, you just have to make a C<GET> request to the entry point returned for the corresponding custom field when fetching this resource, and it will return the content of the file as an octet string: curl -i -H 'Authorization: token XX_TOKEN_XX' 'XX_TICKET_URL_XX' { […] "XX_IMAGE_OR_BINARY_CF_ID_XX" : [ { "content_type" : "image/png", "filename" : "image.png", "_url" : "XX_RT_URL_XX/REST/2.0/download/cf/XX_IMAGE_OR_BINARY_OCFV_ID_XX" } ], […] }, curl -i -H 'Authorization: token XX_TOKEN_XX' 'XX_RT_URL_XX/REST/2.0/download/cf/XX_IMAGE_OR_BINARY_OCFV_ID_XX' > file.png =head2 Paging All plural resources (such as C</tickets>) require pagination, controlled by the query parameters C<page> and C<per_page>. The default page size is 20 items, but it may be increased up to 100 (or decreased if desired). Page numbers start at 1. The number of pages is returned, and if there is a next or previous page, then the URL for that page is returned in the next_page and prev_page variables respectively. It is up to you to store the required JSON to pass with the following page request. =head2 Disabled items By default, only enabled objects are returned. To include disabled objects you can specify C<find_disabled_rows=1> as a query parameter. =head2 Fields When fetching search results you can include additional fields by adding a query parameter C<fields> which is a comma seperated list of fields to include. You must use the camel case version of the name as included in the results for the actual item. You can use additional fields parameters to expand child blocks, for example (line wrapping inserted for readability): XX_RT_URL_XX/REST/2.0/tickets ?fields=Owner,Status,Created,Subject,Queue,CustomFields &fields[Queue]=Name,Description Says that in the result set for tickets, the extra fields for Owner, Status, Created, Subject, Queue and CustomFields should be included. But in addition, for the Queue block, also include Name and Description. The results would be similar to this (only one ticket is displayed in this example): "items" : [ { "Subject" : "Sample Ticket", "id" : "2", "type" : "ticket", "Owner" : { "id" : "root", "_url" : "XX_RT_URL_XX/REST/2.0/user/root", "type" : "user" }, "_url" : "XX_RT_URL_XX/REST/2.0/ticket/2", "Status" : "resolved", "Created" : "2018-06-29:10:25Z", "Queue" : { "id" : "1", "type" : "queue", "Name" : "General", "Description" : "The default queue", "_url" : "XX_RT_URL_XX/REST/2.0/queue/1" }, "CustomFields" : [ { "id" : "1", "type" : "customfield", "_url" : "XX_RT_URL_XX/REST/2.0/customfield/1", "name" : "My Custom Field", "values" : [ "CustomField value" }, } ] } { … }, … ], If the user performing the query doesn't have rights to view the record (or sub record), then the empty string will be returned. For single object URLs like /ticket/:id, as it already contains all the fields by default, parameter "fields" is not needed, but you can still use additional fields parameters to expand child blocks: XX_RT_URL_XX/REST/2.0/ticket/1?fields[Queue]=Name,Description =head2 Authentication Methods Authentication should B<always> be done over HTTPS/SSL for security. You should only serve up the C</REST/2.0/> endpoint over SSL. =head3 Basic Auth Authentication may use internal RT usernames and passwords, provided via HTTP Basic auth. Most HTTP libraries already have a way of providing basic auth credentials when making requests. Using curl, for example: curl -u 'username:password' /path/to/REST/2.0 =head3 Token Auth You may use the L<RT::Authen::Token> extension to authenticate to the REST 2 API. Once you've acquired an authentication token in the web interface, specify the C<Authorization> header with a value of "token" like so: curl -H 'Authorization: token …' /path/to/REST/2.0 If the library or application you're using does not support specifying additional HTTP headers, you may also pass the authentication token as a query parameter like so: curl /path/to/REST/2.0?token=… =head3 Cookie Auth Finally, you may reuse an existing cookie from an ordinary web session to authenticate against REST2. This is primarily intended for interacting with REST2 via JavaScript in the browser. Other REST consumers are advised to use the alternatives above. =head2 Conditional requests (If-Modified-Since, If-Match) You can take advantage of the C<Last-Modified> headers returned by most single resource endpoints. Add a C<If-Modified-Since> header to your requests for the same resource, using the most recent C<Last-Modified> value seen, and the API may respond with a 304 Not Modified. You can also use HEAD requests to check for updates without receiving the actual content when there is a newer version. You may also add an C<If-Unmodified-Since> header to your updates to tell the server to refuse updates if the record had been changed since you last retrieved it. C<ETag>, C<If-Match>, and C<If-None-Match> work similarly to C<Last-Modified>, C<If-Modified-Since>, and C<If-Unmodified-Since>, except that they don't use a timestamp, which has its own set of tradeoffs. C<ETag> is an opaque value, so it has no meaning to consumers (unlike timestamps). However, timestamps have the disadvantage of having a resolution of seconds, so two updates happening in the same second would produce incorrect results, whereas C<ETag> does not suffer from that problem. =head2 Status codes The REST API uses the full range of HTTP status codes, and your client should handle them appropriately. =cut # XXX TODO: API doc sub to_psgi_app { my $self = shift; my $res = $self->to_app(@_); return Plack::Util::response_cb($res, sub { my $res = shift; $self->CleanupRequest; }); } sub to_app { my $class = shift; return builder { enable '+RT::REST2::Middleware::ErrorAsJSON'; enable '+RT::REST2::Middleware::Log'; enable '+RT::REST2::Middleware::Auth'; RT::REST2::Dispatcher->to_psgi_app; }; } sub base_path { RT->Config->Get('WebPath') . $REST_PATH } sub base_uri { RT->Config->Get('WebBaseURL') . shift->base_path } # Called by RT::Interface::Web::Handler->PSGIApp sub PSGIWrap { my ($class, $app) = @_; return builder { mount $REST_PATH => $class->to_app; mount '/' => $app; }; } sub CleanupRequest { if ( $RT::Handle && $RT::Handle->TransactionDepth ) { $RT::Handle->ForceRollback; $RT::Logger->crit( "Transaction not committed. Usually indicates a software fault." . "Data loss may have occurred" ); } # Clean out the ACL cache. the performance impact should be marginal. # Consistency is imprived, too. RT::Principal->InvalidateACLCache(); DBIx::SearchBuilder::Record::Cachable->FlushCache if ( RT->Config->Get('WebFlushDbCacheEveryRequest') and UNIVERSAL::can( 'DBIx::SearchBuilder::Record::Cachable' => 'FlushCache' ) ); } 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/CustomRoles.pm����������������������������������������������������������������������000644 �000765 �000024 �00000010403 14005011336 016707� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::CustomRoles; use base 'RT::SearchBuilder'; use RT::CustomRole; =head1 NAME RT::CustomRoles - collection of RT::CustomRole records =head1 DESCRIPTION Collection of L<RT::CustomRole> records. Inherits methods from L<RT::SearchBuilder>. =head1 METHODS =cut =head2 Table Returns name of the table where records are stored. =cut sub Table { 'CustomRoles'} sub _Init { my $self = shift; $self->{'with_disabled_column'} = 1; return ( $self->SUPER::_Init(@_) ); } sub _ObjectCustomRoleAlias { my $self = shift; return RT::ObjectCustomRoles->new( $self->CurrentUser ) ->JoinTargetToThis( $self => @_ ); } =head2 RegisterRoles This declares all (enabled) custom roles to the L<RT::Record::Role::Roles> subsystem, suitable for system startup. =cut sub RegisterRoles { my $class = shift; my $roles = $class->new(RT->SystemUser); $roles->UnLimit; while (my $role = $roles->Next) { $role->_RegisterAsRole; } } =head2 LimitToObjectId Takes an ObjectId and limits the collection to custom roles applied to said object. When called multiple times the ObjectId limits are joined with OR. =cut sub LimitToObjectId { my $self = shift; my $id = shift; $self->Limit( ALIAS => $self->_ObjectCustomRoleAlias, FIELD => 'ObjectId', OPERATOR => '=', VALUE => $id, ENTRYAGGREGATOR => 'OR' ); } =head2 LimitToSingleValue Limits the list of custom roles to only those that take a single value. =cut sub LimitToSingleValue { my $self = shift; $self->Limit( FIELD => 'MaxValues', OPERATOR => '=', VALUE => 1, ); } =head2 LimitToMultipleValue Limits the list of custom roles to only those that take multiple values. =cut sub LimitToMultipleValue { my $self = shift; $self->Limit( FIELD => 'MaxValues', OPERATOR => '=', VALUE => 0, ); } =head2 ApplySortOrder Sort custom roles according to the order provided by the object custom roles. =cut sub ApplySortOrder { my $self = shift; my $order = shift || 'ASC'; $self->OrderByCols( { ALIAS => $self->_ObjectCustomRoleAlias, FIELD => 'SortOrder', ORDER => $order, } ); } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ScripCondition.pm�������������������������������������������������������������������000644 �000765 �000024 �00000023427 14005011336 017371� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::ScripCondition - RT scrip conditional =head1 SYNOPSIS use RT::ScripCondition; =head1 DESCRIPTION This module should never be called directly by client code. it's an internal module which should only be accessed through exported APIs in other modules. =head1 METHODS =cut package RT::ScripCondition; use strict; use warnings; use base 'RT::Record'; sub Table {'ScripConditions'} sub _Accessible { my $self = shift; my %Cols = ( Name => 'read', Description => 'read', ApplicableTransTypes => 'read', ExecModule => 'read', Argument => 'read', Creator => 'read/auto', Created => 'read/auto', LastUpdatedBy => 'read/auto', LastUpdated => 'read/auto' ); return($self->SUPER::_Accessible(@_, %Cols)); } =head2 Create Takes a hash. Creates a new Condition entry. should be better documented. =cut sub Create { my $self = shift; return($self->SUPER::Create(@_)); } =head2 Delete No API available for deleting things just yet. =cut sub Delete { my $self = shift; unless ( $self->CurrentUser->HasRight( Object => RT->System, Right => 'ModifyScrips' ) ) { return ( 0, $self->loc('Permission Denied') ); } my $scrips = RT::Scrips->new( RT->SystemUser ); $scrips->Limit( FIELD => 'ScripCondition', VALUE => $self->id ); if ( $scrips->Count ) { return ( 0, $self->loc('Condition is in use') ); } return $self->SUPER::Delete(@_); } sub UsedBy { my $self = shift; my $scrips = RT::Scrips->new( $self->CurrentUser ); $scrips->Limit( FIELD => 'ScripCondition', VALUE => $self->Id ); return $scrips; } =head2 Load IDENTIFIER Loads a condition takes a name or ScripCondition id. =cut sub Load { my $self = shift; my $identifier = shift; unless (defined $identifier) { return (undef); } if ($identifier !~ /\D/) { return ($self->SUPER::LoadById($identifier)); } else { return ($self->LoadByCol('Name', $identifier)); } } =head2 LoadCondition HASH takes a hash which has the following elements: TransactionObj and TicketObj. Loads the Condition module in question. =cut sub LoadCondition { my $self = shift; my %args = ( TransactionObj => undef, TicketObj => undef, @_ ); my $module = $self->ExecModule; my $type = 'RT::Condition::' . $module; $type->require or die "Require of $type condition module failed.\n$@\n"; return $self->{'Condition'} = $type->new( ScripConditionObj => $self, TicketObj => $args{'TicketObj'}, ScripObj => $args{'ScripObj'}, TransactionObj => $args{'TransactionObj'}, Argument => $self->Argument, ApplicableTransTypes => $self->ApplicableTransTypes, CurrentUser => $self->CurrentUser ); } =head2 Describe Helper method to call the condition module's Describe method. =cut sub Describe { my $self = shift; return ($self->{'Condition'}->Describe()); } =head2 IsApplicable Helper method to call the condition module's IsApplicable method. =cut sub IsApplicable { my $self = shift; return ($self->{'Condition'}->IsApplicable()); } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 Name Returns the current value of Name. (In the database, Name is stored as varchar(200).) =head2 SetName VALUE Set Name to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Name will be stored as a varchar(200).) =cut =head2 Description Returns the current value of Description. (In the database, Description is stored as varchar(255).) =head2 SetDescription VALUE Set Description to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Description will be stored as a varchar(255).) =cut =head2 ExecModule Returns the current value of ExecModule. (In the database, ExecModule is stored as varchar(60).) =head2 SetExecModule VALUE Set ExecModule to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ExecModule will be stored as a varchar(60).) =cut =head2 Argument Returns the current value of Argument. (In the database, Argument is stored as varbinary(255).) =head2 SetArgument VALUE Set Argument to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Argument will be stored as a varbinary(255).) =cut =head2 ApplicableTransTypes Returns the current value of ApplicableTransTypes. (In the database, ApplicableTransTypes is stored as varchar(60).) =head2 SetApplicableTransTypes VALUE Set ApplicableTransTypes to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ApplicableTransTypes will be stored as a varchar(60).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Name => {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, Description => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, ExecModule => {read => 1, write => 1, sql_type => 12, length => 60, is_blob => 0, is_numeric => 0, type => 'varchar(60)', default => ''}, Argument => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varbinary(255)', default => ''}, ApplicableTransTypes => {read => 1, write => 1, sql_type => 12, length => 60, is_blob => 0, is_numeric => 0, type => 'varchar(60)', default => ''}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; sub PreInflate { my $class = shift; my ($importer, $uid, $data) = @_; $class->SUPER::PreInflate( $importer, $uid, $data ); return not $importer->SkipBy( "Name", $class, $uid, $data ); } sub __DependsOn { my $self = shift; my %args = ( Shredder => undef, Dependencies => undef, @_, ); my $deps = $args{'Dependencies'}; # Scrips my $objs = RT::Scrips->new( $self->CurrentUser ); $objs->Limit( FIELD => 'ScripCondition', VALUE => $self->Id ); $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON, TargetObjects => $objs, Shredder => $args{'Shredder'} ); return $self->SUPER::__DependsOn( %args ); } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Classes.pm��������������������������������������������������������������������������000644 �000765 �000024 �00000005022 14005011336 016026� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Classes; use base 'RT::SearchBuilder'; sub Table {'Classes'} =head2 _Init =cut sub _Init { my $self = shift; $self->{'with_disabled_column'} = 1; return ($self->SUPER::_Init(@_)); } =head2 AddRecord Overrides the collection to ensure that only Classes the user can see are returned. =cut sub AddRecord { my $self = shift; my ($record) = @_; return unless $record->CurrentUserHasRight('SeeClass'); return $self->SUPER::AddRecord( $record ); } sub _SingularClass { "RT::Class" } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/GroupMembers.pm���������������������������������������������������������������������000644 �000765 �000024 �00000010124 14005011336 017037� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::GroupMembers - a collection of RT::GroupMember objects =head1 SYNOPSIS use RT::GroupMembers; =head1 DESCRIPTION =head1 METHODS =cut package RT::GroupMembers; use strict; use warnings; use base 'RT::SearchBuilder'; use RT::GroupMember; sub Table { 'GroupMembers'} sub _Init { my $self = shift; $self->OrderBy( FIELD => 'id', ORDER => 'ASC' ); return $self->SUPER::_Init( @_ ); } =head2 LimitToUsers Limits this search object to users who are members of this group. This is really useful when you want to have your UI separate out groups from users for display purposes =cut sub LimitToUsers { my $self = shift; my $principals = $self->Join( ALIAS1 => 'main', FIELD1 => 'MemberId', TABLE2 => 'Principals', FIELD2 =>'id' ); $self->Limit( ALIAS => $principals, FIELD => 'PrincipalType', VALUE => 'User', ENTRYAGGREGATOR => 'OR', ); } =head2 LimitToGroups Limits this search object to Groups who are members of this group. This is really useful when you want to have your UI separate out groups from users for display purposes =cut sub LimitToGroups { my $self = shift; my $principals = $self->Join( ALIAS1 => 'main', FIELD1 => 'MemberId', TABLE2 => 'Principals', FIELD2 =>'id' ); $self->Limit( ALIAS => $principals, FIELD => 'PrincipalType', VALUE => 'Group', ENTRYAGGREGATOR => 'OR', ); } =head2 LimitToMembersOfGroup PRINCIPAL_ID Takes a Principal Id as its only argument. Limits the current search principals which are _directly_ members of the group which has PRINCIPAL_ID as its principal id. =cut sub LimitToMembersOfGroup { my $self = shift; my $group = shift; return ($self->Limit( VALUE => $group, FIELD => 'GroupId', ENTRYAGGREGATOR => 'OR', QUOTEVALUE => 0 )); } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/I18N/�������������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 014553� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ObjectCustomRole.pm�����������������������������������������������������������������000644 �000765 �000024 �00000020677 14005011336 017671� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::ObjectCustomRole; use base 'RT::Record::AddAndSort'; use RT::CustomRole; use RT::ObjectCustomRoles; =head1 NAME RT::ObjectCustomRole - record representing addition of a custom role to a queue =head1 DESCRIPTION This record is created if you want to add a custom role to a queue. Inherits methods from L<RT::Record::AddAndSort>. For most operations it's better to use methods in L<RT::CustomRole>. =head1 METHODS =head2 Table Returns table name for records of this class. =cut sub Table {'ObjectCustomRoles'} =head2 ObjectCollectionClass Returns class name of collection of records custom roles can be added to. Now it's only L<RT::Queue>, so 'RT::Queues' is returned. =cut sub ObjectCollectionClass {'RT::Queues'} =head2 CustomRoleObj Returns the L<RT::CustomRole> object with the id returned by L</CustomRole> =cut sub CustomRoleObj { my $self = shift; my $id = shift || $self->CustomRole; my $obj = RT::CustomRole->new( $self->CurrentUser ); $obj->Load( $id ); return $obj; } =head2 QueueObj Returns the L<RT::Queue> object which this ObjectCustomRole is added to =cut sub QueueObj { my $self = shift; my $queue = RT::Queue->new($self->CurrentUser); $queue->Load($self->ObjectId); return $queue; } =head2 Add Adds the custom role to the queue and creates (or re-enables) that queue's role group. =cut sub Add { my $self = shift; $RT::Handle->BeginTransaction; my ($ok, $msg) = $self->SUPER::Add(@_); unless ($ok) { $RT::Handle->Rollback; $RT::Logger->error("Couldn't add ObjectCustomRole: $msg"); return(undef); } my $queue = $self->QueueObj; my $role = $self->CustomRoleObj; # see if we already have this role group (which can happen if you # add a role to a queue, remove it, then add it back in) my $existing = RT::Group->new($self->CurrentUser); $existing->LoadRoleGroup( Name => $role->GroupType, Object => $queue, ); if ($existing->Id) { # there already was a role group for this queue, which means # this was previously added, then removed, and is now being re-added, # which means we have to re-enable the queue group and all the # ticket groups $role->_SetGroupsDisabledForQueue(0, $queue); } else { my $group = RT::Group->new($self->CurrentUser); my ($ok, $msg) = $group->CreateRoleGroup( Name => $role->GroupType, Object => $queue, ); unless ($ok) { $RT::Handle->Rollback; $RT::Logger->error("Couldn't create a role group: $msg"); return(undef); } } $RT::Handle->Commit; return ($ok, $msg); } =head2 Delete Removes the custom role from the queue and disables that queue's role group. =cut sub Delete { my $self = shift; $RT::Handle->BeginTransaction; $self->CustomRoleObj->_SetGroupsDisabledForQueue(1, $self->QueueObj); # remove the ObjectCustomRole record my ($ok, $msg) = $self->SUPER::Delete(@_); unless ($ok) { $RT::Handle->Rollback; $RT::Logger->error("Couldn't add ObjectCustomRole: $msg"); return(undef); } $RT::Handle->Commit; return ($ok, $msg); } sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); $deps->Add( out => $self->CustomRoleObj ); $deps->Add( out => $self->QueueObj ); } sub Serialize { my $self = shift; my %args = (@_); my %store = $self->SUPER::Serialize(@_); if ($store{ObjectId}) { my $obj = RT::Queue->new( RT->SystemUser ); $obj->Load( $store{ObjectId} ); $store{ObjectId} = \($obj->UID); } return %store; } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 CustomRole Returns the current value of CustomRole. (In the database, CustomRole is stored as int(11).) =head2 SetCustomRole VALUE Set CustomRole to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, CustomRole will be stored as a int(11).) =cut =head2 ObjectId Returns the current value of ObjectId. (In the database, ObjectId is stored as int(11).) =head2 SetObjectId VALUE Set ObjectId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectId will be stored as a int(11).) =cut =head2 SortOrder Returns the current value of SortOrder. (In the database, SortOrder is stored as int(11).) =head2 SetSortOrder VALUE Set SortOrder to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, SortOrder will be stored as a int(11).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, CustomRole => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, ObjectId => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, SortOrder => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������rt-5.0.1/lib/RT/SQL.pm������������������������������������������������������������������������������000644 �000765 �000024 �00000016267 14005011336 015105� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::SQL; use strict; use warnings; # States use constant VALUE => 1; use constant AGGREG => 2; use constant OP => 4; use constant OPEN_PAREN => 8; use constant CLOSE_PAREN => 16; use constant KEYWORD => 32; my @tokens = qw[VALUE AGGREGATOR OPERATOR OPEN_PAREN CLOSE_PAREN KEYWORD]; use Regexp::Common qw /delimited/; my $re_aggreg = qr[(?i:AND|OR)]; my $re_delim = qr[$RE{delimited}{-delim=>qq{\'\"}}]; my $re_value = qr[[+-]?\d+|(?i:NULL)|$re_delim]; my $re_keyword = qr[[{}\w\.]+|$re_delim]; my $re_op = qr[=|!=|>=|<=|>|<|(?i:IS NOT)|(?i:IS)|(?i:NOT LIKE)|(?i:LIKE)|(?i:NOT STARTSWITH)|(?i:STARTSWITH)|(?i:NOT ENDSWITH)|(?i:ENDSWITH)]; # long to short my $re_open_paren = qr[\(]; my $re_close_paren = qr[\)]; sub ParseToArray { my ($string) = shift; my ($tree, $node, @pnodes); $node = $tree = []; my %callback; $callback{'OpenParen'} = sub { push @pnodes, $node; $node = []; push @{ $pnodes[-1] }, $node }; $callback{'CloseParen'} = sub { $node = pop @pnodes }; $callback{'EntryAggregator'} = sub { push @$node, $_[0] }; $callback{'Condition'} = sub { push @$node, { key => $_[0], op => $_[1], value => $_[2] } }; Parse($string, \%callback); return $tree; } sub Parse { my ($string, $cb) = @_; my $loc = sub {HTML::Mason::Commands::loc(@_)}; $string = '' unless defined $string; my $want = KEYWORD | OPEN_PAREN; my $last = 0; my $depth = 0; my ($key,$op,$value) = ("","",""); # order of matches in the RE is important.. op should come early, # because it has spaces in it. otherwise "NOT LIKE" might be parsed # as a keyword or value. while ($string =~ /( $re_aggreg |$re_op |$re_keyword |$re_value |$re_open_paren |$re_close_paren )/iogx ) { my $match = $1; # Highest priority is last my $current = 0; $current = OP if ($want & OP) && $match =~ /^$re_op$/io; $current = VALUE if ($want & VALUE) && $match =~ /^$re_value$/io; $current = KEYWORD if ($want & KEYWORD) && $match =~ /^$re_keyword$/io; $current = AGGREG if ($want & AGGREG) && $match =~ /^$re_aggreg$/io; $current = OPEN_PAREN if ($want & OPEN_PAREN) && $match =~ /^$re_open_paren$/io; $current = CLOSE_PAREN if ($want & CLOSE_PAREN) && $match =~ /^$re_close_paren$/io; unless ($current && $want & $current) { my $tmp = substr($string, 0, pos($string)- length($match)); $tmp .= '>'. $match .'<--here'. substr($string, pos($string)); my $msg = $loc->("Wrong query, expecting a [_1] in '[_2]'", _BitmaskToString($want), $tmp); return $cb->{'Error'}->( $msg ) if $cb->{'Error'}; die $msg; } # State Machine: # Parens are highest priority if ( $current & OPEN_PAREN ) { $cb->{'OpenParen'}->(); $depth++; $want = KEYWORD | OPEN_PAREN; } elsif ( $current & CLOSE_PAREN ) { $cb->{'CloseParen'}->(); $depth--; $want = AGGREG; $want |= CLOSE_PAREN if $depth; } elsif ( $current & AGGREG ) { $cb->{'EntryAggregator'}->( $match ); $want = KEYWORD | OPEN_PAREN; } elsif ( $current & KEYWORD ) { $key = $match; $want = OP; } elsif ( $current & OP ) { $op = $match; $want = VALUE; } elsif ( $current & VALUE ) { $value = $match; # Remove surrounding quotes and unescape escaped # characters from $key, $match for ( $key, $value ) { if ( /$re_delim/o ) { substr($_,0,1) = ""; substr($_,-1,1) = ""; } s!\\(.)!$1!g; } $cb->{'Condition'}->( $key, $op, $value ); ($key,$op,$value) = ("","",""); $want = AGGREG; $want |= CLOSE_PAREN if $depth; } else { my $msg = $loc->("Query parser is lost"); return $cb->{'Error'}->( $msg ) if $cb->{'Error'}; die $msg; } $last = $current; } # while unless( !$last || $last & (CLOSE_PAREN | VALUE) ) { my $msg = $loc->("Incomplete query, last element ([_1]) is not close paren or value in '[_2]'", _BitmaskToString($last), $string); return $cb->{'Error'}->( $msg ) if $cb->{'Error'}; die $msg; } if( $depth ) { my $msg = $loc->("Incomplete query, [quant,_1,unclosed paren,unclosed parens] in '[_2]'", $depth, $string); return $cb->{'Error'}->( $msg ) if $cb->{'Error'}; die $msg; } } sub _BitmaskToString { my $mask = shift; my @res; for( my $i = 0; $i<@tokens; $i++ ) { next unless $mask & (1<<$i); push @res, $tokens[$i]; } my $tmp = join ', ', splice @res, 0, -1; unshift @res, $tmp if $tmp; return join ' or ', @res; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/URI/��������������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 014533� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Squish.pm���������������������������������������������������������������������������000644 �000765 �000024 �00000006246 14005011336 015716� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 SYNOPSIS =head1 DESCRIPTION base class of RT::Squish::JS and RT::Squish::CSS =head1 METHODS =cut use strict; use warnings; package RT::Squish; use base 'Class::Accessor::Fast'; __PACKAGE__->mk_accessors(qw/Content Key ModifiedTime ModifiedTimeString/); use Digest::MD5 'md5_hex'; use HTTP::Date; =head2 new (ARGS) ARGS is a hash of named parameters. Valid parameters are: Name - name for this object =cut sub new { my $class = shift; my %args = @_; my $self = \%args; bless $self, $class; my $content = $self->Squish; $self->Content($content); $self->Key( md5_hex $content ); $self->ModifiedTime( time() ); $self->ModifiedTimeString( HTTP::Date::time2str( $self->ModifiedTime ) ); return $self; } =head2 Squish virtual method which does nothing, you need to implement this method in subclasses. =cut sub Squish { $RT::Logger->warn( "you need to implement this method in subclasses" ); return 1; } =head2 Content squished content =head2 Key md5 of the squished content =head2 ModifiedTime created time of squished content, i.e. seconds since 00:00:00 UTC, January 1, 1970 =head2 ModifiedTimeString created time of squished content, with HTTP::Date format =cut 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Article.pm��������������������������������������������������������������������������000644 �000765 �000024 �00000045067 14005011336 016031� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Article; use base 'RT::Record'; use Role::Basic 'with'; with "RT::Record::Role::Links" => { -excludes => ["AddLink", "_AddLinksOnCreate"] }; use RT::Articles; use RT::ObjectTopics; use RT::Classes; use RT::Links; use RT::CustomFields; use RT::URI::fsck_com_article; use RT::Transactions; sub Table {'Articles'} # This object takes custom fields use RT::CustomField; RT::CustomField->RegisterLookupType( CustomFieldLookupType() => 'Articles' ); #loc # {{{ Create =head2 Create PARAMHASH Create takes a hash of values and creates a row in the database: varchar(200) 'Name'. varchar(200) 'Summary'. int(11) 'Content'. Class ID 'Class' A paramhash called 'CustomFields', which contains arrays of values for each custom field you want to fill in. Arrays aRe ordered. =cut sub Create { my $self = shift; my %args = ( Name => '', Summary => '', SortOrder => 0, Class => '0', CustomFields => {}, Links => {}, Topics => [], Disabled => 0, @_ ); my $class = RT::Class->new( $self->CurrentUser ); $class->Load( $args{'Class'} ); unless ( $class->Id ) { return ( 0, $self->loc('Invalid Class') ); } unless ( $class->CurrentUserHasRight('CreateArticle') ) { return ( 0, $self->loc("Permission Denied") ); } return ( undef, $self->loc('Name in use') ) unless $self->ValidateName( $args{'Name'} ); $RT::Handle->BeginTransaction(); my ( $id, $msg ) = $self->SUPER::Create( Name => $args{'Name'}, Class => $class->Id, Summary => $args{'Summary'}, SortOrder => $args{'SortOrder'}, Disabled => $args{'Disabled'}, ); unless ($id) { $RT::Handle->Rollback(); return ( undef, $msg ); } # {{{ Add custom fields foreach my $key ( keys %args ) { next unless ( $key =~ /CustomField-(.*)$/ ); my $cf = $1; my @vals = ref( $args{$key} ) eq 'ARRAY' ? @{ $args{$key} } : ( $args{$key} ); foreach my $value (@vals) { next if $self->CustomFieldValueIsEmpty( Field => $cf, Value => $value, ); my ( $cfid, $cfmsg ) = $self->_AddCustomFieldValue( (UNIVERSAL::isa( $value => 'HASH' ) ? %$value : (Value => $value) ), Field => $cf, RecordTransaction => 0, ForCreation => 1, ); unless ($cfid) { $RT::Handle->Rollback(); return ( undef, $cfmsg ); } } } # }}} # {{{ Add topics foreach my $topic ( @{ $args{Topics} } ) { my ( $cfid, $cfmsg ) = $self->AddTopic( Topic => $topic ); unless ($cfid) { $RT::Handle->Rollback(); return ( undef, $cfmsg ); } } # }}} # {{{ Add relationships foreach my $type ( keys %args ) { next unless ( $type =~ /^(RefersTo-new|new-RefersTo)$/ ); my @vals = ref( $args{$type} ) eq 'ARRAY' ? @{ $args{$type} } : ( $args{$type} ); foreach my $val (@vals) { my ( $base, $target ); if ( $type =~ /^new-(.*)$/ ) { $type = $1; $base = undef; $target = $val; } elsif ( $type =~ /^(.*)-new$/ ) { $type = $1; $base = $val; $target = undef; } my ( $linkid, $linkmsg ) = $self->AddLink( Type => $type, Target => $target, Base => $base, RecordTransaction => 0 ); unless ($linkid) { $RT::Handle->Rollback(); return ( undef, $linkmsg ); } } } # }}} # We override the URI lookup. the whole reason # we have a URI column is so that joins on the links table # aren't expensive and stupid $self->__Set( Field => 'URI', Value => $self->URI ); my ( $txn_id, $txn_msg, $txn ) = $self->_NewTransaction( Type => 'Create' ); unless ($txn_id) { $RT::Handle->Rollback(); return ( undef, $self->loc( 'Internal error: [_1]', $txn_msg ) ); } $RT::Handle->Commit(); return ( $id, $self->loc('Article [_1] created',$self->id )); } # }}} # {{{ ValidateName =head2 ValidateName NAME Takes a string name. Returns true if that name isn't in use by another article Empty names are permitted. =cut sub ValidateName { my $self = shift; my $name = shift; if ( !$name ) { return (1); } my $temp = RT::Article->new($RT::SystemUser); $temp->LoadByCols( Name => $name ); if ( $temp->id && (!$self->id || ($temp->id != $self->id ))) { return (undef); } return (1); } # }}} # {{{ Delete =head2 Delete This does not remove from the database; it merely sets the Disabled bit. =cut sub Delete { my $self = shift; return $self->SetDisabled(1); } # }}} # {{{ Children =head2 Children Returns an RT::Articles object which contains all articles which have this article as their parent. This routine will not recurse and will not find grandchildren, great-grandchildren, uncles, aunts, nephews or any other such thing. =cut sub Children { my $self = shift; my $kids = RT::Articles->new( $self->CurrentUser ); unless ( $self->CurrentUserHasRight('ShowArticle') ) { $kids->LimitToParent( $self->Id ); } return ($kids); } # }}} # {{{ sub AddLink =head2 AddLink Takes a paramhash of Type and one of Base or Target. Adds that link to this article. Prevents the use of plain numbers to avoid confusing behaviour. =cut sub AddLink { my $self = shift; my %args = ( Target => '', Base => '', Type => '', Silent => undef, @_ ); unless ( $self->CurrentUserHasRight('ModifyArticle') ) { return ( 0, $self->loc("Permission Denied") ); } # Disallow parsing of plain numbers in article links. If they are # allowed, they default to being tickets instead of articles, which # is counterintuitive. if ( $args{'Target'} && $args{'Target'} =~ /^\d+$/ || $args{'Base'} && $args{'Base'} =~ /^\d+$/ ) { return ( 0, $self->loc("Cannot add link to plain number") ); } $self->_AddLink(%args); } sub URI { my $self = shift; unless ( $self->CurrentUserHasRight('ShowArticle') ) { return $self->loc("Permission Denied"); } my $uri = RT::URI::fsck_com_article->new( $self->CurrentUser ); return ( $uri->URIForObject($self) ); } # }}} # {{{ sub URIObj =head2 URIObj Returns this article's URI =cut sub URIObj { my $self = shift; my $uri = RT::URI->new( $self->CurrentUser ); if ( $self->CurrentUserHasRight('ShowArticle') ) { $uri->FromObject($self); } return ($uri); } # }}} # }}} # {{{ Topics # {{{ Topics sub Topics { my $self = shift; my $topics = RT::ObjectTopics->new( $self->CurrentUser ); if ( $self->CurrentUserHasRight('ShowArticle') ) { $topics->LimitToObject($self); } return $topics; } # }}} # {{{ AddTopic sub AddTopic { my $self = shift; my %args = (@_); unless ( $self->CurrentUserHasRight('ModifyArticleTopics') ) { return ( 0, $self->loc("Permission Denied") ); } my $t = RT::ObjectTopic->new( $self->CurrentUser ); my ($tid) = $t->Create( Topic => $args{'Topic'}, ObjectType => ref($self), ObjectId => $self->Id ); if ($tid) { return ( $tid, $self->loc("Topic membership added") ); } else { return ( 0, $self->loc("Unable to add topic membership") ); } } # }}} sub DeleteTopic { my $self = shift; my %args = (@_); unless ( $self->CurrentUserHasRight('ModifyArticleTopics') ) { return ( 0, $self->loc("Permission Denied") ); } my $t = RT::ObjectTopic->new( $self->CurrentUser ); $t->LoadByCols( Topic => $args{'Topic'}, ObjectId => $self->Id, ObjectType => ref($self) ); if ( $t->Id ) { my ($ok, $msg) = $t->Delete; unless ($ok) { return ( undef, $self->loc( "Unable to delete topic membership in [_1]", $t->TopicObj->Name ) ); } else { return ( 1, $self->loc("Topic membership removed") ); } } else { return ( undef, $self->loc( "Couldn't load topic membership while trying to delete it") ); } } =head2 CurrentUserCanSee Returns true if the current user can see the article, using ShowArticle =cut sub CurrentUserCanSee { my $self = shift; return $self->CurrentUserHasRight('ShowArticle'); } # }}} # {{{ _Set =head2 _Set { Field => undef, Value => undef Internal helper method to record a transaction as we update some core field of the article =cut sub _Set { my $self = shift; my %args = ( Field => undef, Value => undef, @_ ); if ( $args{Field} eq 'Disabled' ) { unless ( $self->CurrentUserHasRight( 'DisableArticle' ) ) { return ( 0, $self->loc( "Permission Denied" ) ); } } else { unless ( $self->CurrentUserHasRight( 'ModifyArticle' ) ) { return ( 0, $self->loc( "Permission Denied" ) ); } } $self->_NewTransaction( Type => 'Set', Field => $args{'Field'}, NewValue => $args{'Value'}, OldValue => $self->__Value( $args{'Field'} ) ); return ( $self->SUPER::_Set(%args) ); } =head2 _Value PARAM Return "PARAM" for this object. if the current user doesn't have rights, returns undef =cut sub _Value { my $self = shift; my $arg = shift; unless ( ( $arg eq 'Class' ) || ( $self->CurrentUserHasRight('ShowArticle') ) ) { return (undef); } return $self->SUPER::_Value($arg); } # }}} sub CustomFieldLookupType { "RT::Class-RT::Article"; } sub ACLEquivalenceObjects { my $self = shift; return $self->ClassObj; } sub ModifyLinkRight { "ModifyArticle" } =head2 LoadByInclude Field Value Takes the name of a form field from "Include Article" and the value submitted by the browser and attempts to load an Article. This handles Articles included by searching, by the Name and via the hotlist. If you optionaly pass an id as the Queue argument, this will check that the Article's Class is applied to that Queue. =cut sub LoadByInclude { my $self = shift; RT->Deprecated( Remove => '5.2' ); my %args = @_; my $Field = $args{Field}; my $Value = $args{Value}; my $Queue = $args{Queue}; return unless $Field; my ($ok, $msg); if ( $Field eq 'Articles-Include-Article' && $Value ) { ($ok, $msg) = $self->Load( $Value ); } elsif ( $Field =~ /^Articles-Include-Article-(\d+)$/ ) { ($ok, $msg) = $self->Load( $1 ); } elsif ( $Field =~ /^Articles-Include-Article-Named/ && $Value ) { if ( $Value =~ /\D/ ) { ($ok, $msg) = $self->LoadByCols( Name => $Value ); } else { ($ok, $msg) = $self->LoadByCols( id => $Value ); } } unless ($ok) { # load failed, don't check Class return wantarray ? ($ok, $msg) : $ok; } unless ($Queue) { # we haven't requested extra sanity checking return wantarray ? ($ok, $msg) : $ok; } # ensure that this article is available for the Queue we're # operating under. my $class = $self->ClassObj; unless ($class->IsApplied(0) || $class->IsApplied($Queue)) { $self->LoadById(0); return wantarray ? (0, $self->loc("The Class of the Article identified by [_1] is not applied to the current Queue",$Value)) : 0; } return wantarray ? ($ok, $msg) : $ok; } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 Name Returns the current value of Name. (In the database, Name is stored as varchar(255).) =head2 SetName VALUE Set Name to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Name will be stored as a varchar(255).) =cut =head2 Summary Returns the current value of Summary. (In the database, Summary is stored as varchar(255).) =head2 SetSummary VALUE Set Summary to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Summary will be stored as a varchar(255).) =cut =head2 SortOrder Returns the current value of SortOrder. (In the database, SortOrder is stored as int(11).) =head2 SetSortOrder VALUE Set SortOrder to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, SortOrder will be stored as a int(11).) =cut =head2 Class Returns the current value of Class. (In the database, Class is stored as int(11).) =head2 SetClass VALUE Set Class to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Class will be stored as a int(11).) =cut =head2 ClassObj Returns the Class Object which has the id returned by Class =cut sub ClassObj { my $self = shift; my $Class = RT::Class->new($self->CurrentUser); $Class->Load($self->Class()); return($Class); } =head2 Parent Returns the current value of Parent. (In the database, Parent is stored as int(11).) =head2 SetParent VALUE Set Parent to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Parent will be stored as a int(11).) =cut =head2 URI Returns the current value of URI. (In the database, URI is stored as varchar(255).) =head2 SetURI VALUE Set URI to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, URI will be stored as a varchar(255).) =cut =head2 Disabled Returns the current value of Disabled. (In the database, Disabled is stored as int(2).) =head2 SetDisabled VALUE Set Disabled to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Disabled will be stored as a int(2).) =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, type => 'int(11)', default => ''}, Name => {read => 1, write => 1, type => 'varchar(255)', default => ''}, Summary => {read => 1, write => 1, type => 'varchar(255)', default => ''}, SortOrder => {read => 1, write => 1, type => 'int(11)', default => '0', is_numeric => 1}, Class => {read => 1, write => 1, type => 'int(11)', default => '0'}, Parent => {read => 1, write => 1, type => 'int(11)', default => '0'}, URI => {read => 1, write => 1, type => 'varchar(255)', default => ''}, Disabled => {read => 1, write => 1, type => 'int(2)', default => '0'}, Creator => {read => 1, auto => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, type => 'datetime', default => ''}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); # Links my $links = RT::Links->new( $self->CurrentUser ); $links->Limit( SUBCLAUSE => "either", FIELD => $_, VALUE => $self->URI, ENTRYAGGREGATOR => 'OR' ) for qw/Base Target/; $deps->Add( in => $links ); $deps->Add( out => $self->ClassObj ); $deps->Add( in => $self->Topics ); } sub PostInflate { my $self = shift; $self->__Set( Field => 'URI', Value => $self->URI ); } RT::Base->_ImportOverlays(); 1; 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ObjectClass.pm����������������������������������������������������������������������000644 �000765 �000024 �00000013227 14005011336 016633� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::ObjectClass; use base 'RT::Record'; sub Table {'ObjectClasses'} sub Create { my $self = shift; my %args = ( Class => '0', ObjectType => '', ObjectId => '0', @_); unless ( $self->CurrentUser->HasRight( Right => 'AdminClass', Object => $RT::System ) ) { return ( 0, $self->loc('Permission Denied') ); } $self->SUPER::Create( Class => $args{'Class'}, ObjectType => $args{'ObjectType'}, ObjectId => $args{'ObjectId'}, ); } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 Class Returns the current value of Class. (In the database, Class is stored as int(11).) =head2 SetClass VALUE Set Class to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Class will be stored as a int(11).) =cut =head2 ClassObj Returns the Class Object which has the id returned by Class =cut sub ClassObj { my $self = shift; my $Class = RT::Class->new($self->CurrentUser); $Class->Load($self->Class()); return($Class); } =head2 ObjectType Returns the current value of ObjectType. (In the database, ObjectType is stored as varchar(255).) =head2 SetObjectType VALUE Set ObjectType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectType will be stored as a varchar(255).) =cut =head2 ObjectId Returns the current value of ObjectId. (In the database, ObjectId is stored as int(11).) =head2 SetObjectId VALUE Set ObjectId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectId will be stored as a int(11).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut sub _CoreAccessible { { id => {read => 1, type => 'int(11)', default => ''}, Class => {read => 1, write => 1, type => 'int(11)', default => '0'}, ObjectType => {read => 1, write => 1, type => 'varchar(255)', default => ''}, ObjectId => {read => 1, write => 1, type => 'int(11)', default => '0'}, Creator => {read => 1, auto => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, type => 'datetime', default => ''}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); $deps->Add( out => $self->ClassObj ); my $obj = $self->ObjectType->new( $self->CurrentUser ); $obj->Load( $self->ObjectId ); $deps->Add( out => $obj ); } sub Serialize { my $self = shift; my %args = (@_); my %store = $self->SUPER::Serialize(@_); if ($store{ObjectId}) { my $obj = $self->ObjectType->new( RT->SystemUser ); $obj->Load( $store{ObjectId} ); $store{ObjectId} = \($obj->UID); } return %store; } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ObjectCustomFieldValue.pm�����������������������������������������������������������000644 �000765 �000024 �00000052405 14005011336 021002� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::ObjectCustomFieldValue; use 5.010; use strict; use warnings; use base 'RT::Record'; use RT::Interface::Web; use Regexp::Common qw(RE_net_IPv4); use Regexp::IPv6 qw($IPv6_re); use Regexp::Common::net::CIDR; require Net::CIDR; # Allow the empty IPv6 address $IPv6_re = qr/(?:$IPv6_re|::)/; use RT::CustomField; sub Table {'ObjectCustomFieldValues'} sub Create { my $self = shift; my %args = ( CustomField => 0, ObjectType => '', ObjectId => 0, Disabled => 0, Content => '', LargeContent => undef, ContentType => '', ContentEncoding => '', @_, ); my $cf = RT::CustomField->new( $self->CurrentUser ); $cf->Load( $args{CustomField} ); my ($val, $msg) = $cf->_CanonicalizeValue(\%args); return ($val, $msg) unless $val; my $encoded = Encode::encode("UTF-8", $args{'Content'}); if ( defined $args{'Content'} && length( $encoded ) > 255 ) { if ( defined $args{'LargeContent'} && length $args{'LargeContent'} ) { $RT::Logger->error("Content is longer than 255 bytes and LargeContent specified"); } else { # _EncodeLOB, and thus LargeContent, takes bytes; Content is # in characters. Encode it; this may replace illegal # codepoints (e.g. \x{FDD0}) with \x{FFFD}. $args{'LargeContent'} = Encode::encode("UTF-8",$args{'Content'}); $args{'Content'} = undef; $args{'ContentType'} ||= 'text/plain'; } } ( $args{'ContentEncoding'}, $args{'LargeContent'} ) = $self->_EncodeLOB( $args{'LargeContent'}, $args{'ContentType'} ) if defined $args{'LargeContent'}; ( my $id, $msg ) = $self->SUPER::Create( CustomField => $args{'CustomField'}, ObjectType => $args{'ObjectType'}, ObjectId => $args{'ObjectId'}, Disabled => $args{'Disabled'}, Content => $args{'Content'}, LargeContent => $args{'LargeContent'}, ContentType => $args{'ContentType'}, ContentEncoding => $args{'ContentEncoding'}, ); if ( $id ) { my $new_value = RT::ObjectCustomFieldValue->new( $self->CurrentUser ); $new_value->Load( $id ); my $ocfv_key = $new_value->GetOCFVCacheKey(); if ( $RT::ObjectCustomFieldValues::_OCFV_CACHE->{$ocfv_key} ) { push @{ $RT::ObjectCustomFieldValues::_OCFV_CACHE->{$ocfv_key} }, { 'ObjectId' => $new_value->Id, 'CustomFieldObj' => $new_value->CustomFieldObj, 'Content' => $new_value->_Value('Content'), 'LargeContent' => $new_value->LargeContent, }; } } return wantarray ? ( $id, $msg ) : $id; } sub LargeContent { my $self = shift; return $self->_DecodeLOB( $self->ContentType, $self->ContentEncoding, $self->_Value( 'LargeContent', decode_utf8 => 0 ) ); } =head2 LoadByCols =cut sub LoadByCols { my $self = shift; my %args = (@_); my $cf; if ( $args{CustomField} ) { $cf = RT::CustomField->new( $self->CurrentUser ); $cf->Load( $args{CustomField} ); my ($ok, $msg) = $cf->_CanonicalizeValue(\%args); return ($ok, $msg) unless $ok; } return $self->SUPER::LoadByCols(%args); } =head2 LoadByTicketContentAndCustomField { Ticket => TICKET, CustomField => CUSTOMFIELD, Content => CONTENT } Loads a custom field value by Ticket, Content and which CustomField it's tied to =cut sub LoadByTicketContentAndCustomField { my $self = shift; my %args = ( Ticket => undef, CustomField => undef, Content => undef, @_ ); return $self->LoadByCols( Content => $args{'Content'}, CustomField => $args{'CustomField'}, ObjectType => 'RT::Ticket', ObjectId => $args{'Ticket'}, Disabled => 0 ); } sub LoadByObjectContentAndCustomField { my $self = shift; my %args = ( Object => undef, CustomField => undef, Content => undef, @_ ); my $obj = $args{'Object'} or return; return $self->LoadByCols( Content => $args{'Content'}, CustomField => $args{'CustomField'}, ObjectType => ref($obj), ObjectId => $obj->Id, Disabled => 0 ); } =head2 CustomFieldObj Returns the CustomField Object which has the id returned by CustomField =cut sub CustomFieldObj { my $self = shift; my $CustomField = RT::CustomField->new( $self->CurrentUser ); $CustomField->SetContextObject( $self->Object ); $CustomField->Load( $self->__Value('CustomField') ); return $CustomField; } =head2 Content Return this custom field's content. If there's no "regular" content, try "LargeContent" =cut my $re_ip_sunit = qr/[0-1][0-9][0-9]|2[0-4][0-9]|25[0-5]/; my $re_ip_serialized = qr/$re_ip_sunit(?:\.$re_ip_sunit){3}/; sub Content { my $self = shift; my $cf = $self->CustomFieldObj; $cf->{include_set_initial} = $self->{include_set_initial}; return undef unless $cf->CurrentUserCanSee; my $content = $self->_Value('Content'); if ( $cf->Type eq 'IPAddress' || $cf->Type eq 'IPAddressRange' ) { require Net::IP; if ( $content =~ /^\s*($re_ip_serialized)\s*$/o ) { $content = sprintf "%d.%d.%d.%d", split /\./, $1; } if ( $content =~ /^\s*($IPv6_re)\s*$/o ) { $content = Net::IP::ip_compress_address($1, 6); } return $content if $cf->Type eq 'IPAddress'; my $large_content = $self->__Value('LargeContent'); if ( $large_content =~ /^\s*($re_ip_serialized)\s*$/o ) { my $eIP = sprintf "%d.%d.%d.%d", split /\./, $1; if ( $content eq $eIP ) { return $content; } else { return $content . "-" . $eIP; } } elsif ( $large_content =~ /^\s*($IPv6_re)\s*$/o ) { my $eIP = Net::IP::ip_compress_address($1, 6); if ( $content eq $eIP ) { return $content; } else { return $content . "-" . $eIP; } } else { return $content; } } if ( !(defined $content && length $content) && $self->ContentType && $self->ContentType eq 'text/plain' ) { return $self->LargeContent; } else { return $content; } } =head2 Object Returns the object this value applies to =cut sub Object { my $self = shift; my $Object = $self->__Value('ObjectType')->new( $self->CurrentUser ); $Object->LoadById( $self->__Value('ObjectId') ); return $Object; } =head2 Delete Disable this value. Used to remove "current" values from records while leaving them in the history. =cut sub Delete { my $self = shift; my ( $ret, $msg ) = $self->SetDisabled( 1 ); if ( $ret ) { my $ocfv_key = $self->GetOCFVCacheKey(); if ( $RT::ObjectCustomFieldValues::_OCFV_CACHE->{$ocfv_key} ) { @{ $RT::ObjectCustomFieldValues::_OCFV_CACHE->{$ocfv_key} } = grep { $_->{'ObjectId'} != $self->Id } @{ $RT::ObjectCustomFieldValues::_OCFV_CACHE->{$ocfv_key} }; } } return wantarray ? ( $ret, $msg ) : $ret; } =head2 _FillInTemplateURL URL Takes a URL containing placeholders and returns the URL as filled in for this ObjectCustomFieldValue. The values for the placeholders will be URI-escaped. Available placeholders: =over =item __id__ The id of the object in question. =item __CustomField__ The value of this custom field for the object in question. =item __WebDomain__, __WebPort__, __WebPath__, __WebBaseURL__ and __WebURL__ The value of the config option. =back =cut { my %placeholders = ( id => { value => sub { $_[0]->ObjectId }, escape => 1 }, CustomField => { value => sub { $_[0]->Content }, escape => 1 }, WebDomain => { value => sub { RT->Config->Get('WebDomain') } }, WebPort => { value => sub { RT->Config->Get('WebPort') } }, WebPath => { value => sub { RT->Config->Get('WebPath') } }, WebBaseURL => { value => sub { RT->Config->Get('WebBaseURL') } }, WebURL => { value => sub { RT->Config->Get('WebURL') } }, ); sub _FillInTemplateURL { my $self = shift; my $url = shift; return undef unless defined $url && length $url; # special case, whole value should be an URL if ( $url =~ /^__CustomField__/ ) { my $value = $self->Content; $value //= ''; # protect from potentially malicious URLs if ( $value =~ /^\s*(?:javascript|data):/i ) { my $object = $self->Object; $RT::Logger->error( "Potentially dangerous URL type in custom field '". $self->CustomFieldObj->Name ."'" ." on ". ref($object) ." #". $object->id ); return undef; } $url =~ s/^__CustomField__/$value/; } # default value, uri-escape for my $key (keys %placeholders) { $url =~ s{__${key}__}{ my $value = $placeholders{$key}{'value'}->( $self ); $value //= ''; RT::Interface::Web::EscapeURI(\$value) if $placeholders{$key}{'escape'}; $value }gxe; } return $url; } } =head2 ValueLinkURL Returns a filled in URL template for this ObjectCustomFieldValue, suitable for constructing a hyperlink in RT's webui. Returns undef if this custom field doesn't have a LinkValueTo =cut sub LinkValueTo { my $self = shift; return $self->_FillInTemplateURL($self->CustomFieldObj->LinkValueTo); } =head2 ValueIncludeURL Returns a filled in URL template for this ObjectCustomFieldValue, suitable for constructing a hyperlink in RT's webui. Returns undef if this custom field doesn't have a IncludeContentForValue =cut sub IncludeContentForValue { my $self = shift; return $self->_FillInTemplateURL($self->CustomFieldObj->IncludeContentForValue); } sub ParseIPRange { my $self = shift; my $value = shift or return; $value = lc $value; $value =~ s!^\s+!!; $value =~ s!\s+$!!; if ( $value =~ /^$RE{net}{CIDR}{IPv4}{-keep}$/go ) { my $cidr = join( '.', map $_||0, (split /\./, $1)[0..3] ) ."/$2"; $value = (Net::CIDR::cidr2range( $cidr ))[0] || $value; } elsif ( $value =~ /^$IPv6_re(?:\/\d+)?$/o ) { $value = (Net::CIDR::cidr2range( $value ))[0] || $value; } my ($sIP, $eIP); if ( $value =~ /^($RE{net}{IPv4})$/o ) { $sIP = $eIP = sprintf "%03d.%03d.%03d.%03d", split /\./, $1; } elsif ( $value =~ /^($RE{net}{IPv4})-($RE{net}{IPv4})$/o ) { $sIP = sprintf "%03d.%03d.%03d.%03d", split /\./, $1; $eIP = sprintf "%03d.%03d.%03d.%03d", split /\./, $2; } elsif ( $value =~ /^($IPv6_re)$/o ) { $sIP = $self->ParseIP( $1 ); $eIP = $sIP; } elsif ( $value =~ /^($IPv6_re)-($IPv6_re)$/o ) { ($sIP, $eIP) = ( $1, $2 ); $sIP = $self->ParseIP( $sIP ); $eIP = $self->ParseIP( $eIP ); } else { return; } ($sIP, $eIP) = ($eIP, $sIP) if $sIP gt $eIP; return $sIP, $eIP; } sub ParseIP { my $self = shift; my $value = shift or return; $value = lc $value; $value =~ s!^\s+!!; $value =~ s!\s+$!!; if ( $value =~ /^($RE{net}{IPv4})$/o ) { return sprintf "%03d.%03d.%03d.%03d", split /\./, $1; } elsif ( $value =~ /^$IPv6_re$/o ) { # up_fields are before '::' # low_fields are after '::' but without v4 # v4_fields are the v4 my ( @up_fields, @low_fields, @v4_fields ); my $v6; if ( $value =~ /(.*:)(\d+\..*)/ ) { ( $v6, my $v4 ) = ( $1, $2 ); chop $v6 unless $v6 =~ /::$/; while ( $v4 =~ /(\d+)\.(\d+)/g ) { push @v4_fields, sprintf '%.2x%.2x', $1, $2; } } else { $v6 = $value; } my ( $up, $low ); if ( $v6 =~ /::/ ) { ( $up, $low ) = split /::/, $v6; } else { $up = $v6; } @up_fields = split /:/, $up; @low_fields = split /:/, $low if $low; my @zero_fields = ('0000') x ( 8 - @v4_fields - @up_fields - @low_fields ); my @fields = ( @up_fields, @zero_fields, @low_fields, @v4_fields ); return join ':', map { sprintf "%.4x", hex "0x$_" } @fields; } return; } =head2 GetOCFVCacheKey Get the OCFV cache key for this object =cut sub GetOCFVCacheKey { my $self = shift; my $ocfv_key = "CustomField-" . $self->CustomField . '-ObjectType-' . $self->ObjectType . '-ObjectId-' . $self->ObjectId; return $ocfv_key; } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 CustomField Returns the current value of CustomField. (In the database, CustomField is stored as int(11).) =head2 SetCustomField VALUE Set CustomField to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, CustomField will be stored as a int(11).) =cut =head2 ObjectType Returns the current value of ObjectType. (In the database, ObjectType is stored as varchar(255).) =head2 SetObjectType VALUE Set ObjectType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectType will be stored as a varchar(255).) =cut =head2 ObjectId Returns the current value of ObjectId. (In the database, ObjectId is stored as int(19).) =head2 SetObjectId VALUE Set ObjectId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectId will be stored as a int(19).) =cut =head2 SortOrder Returns the current value of SortOrder. (In the database, SortOrder is stored as int(11).) =head2 SetSortOrder VALUE Set SortOrder to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, SortOrder will be stored as a int(11).) =cut =head2 Content Returns the current value of Content. (In the database, Content is stored as varchar(255).) =head2 SetContent VALUE Set Content to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Content will be stored as a varchar(255).) =cut =head2 LargeContent Returns the current value of LargeContent. (In the database, LargeContent is stored as longblob.) =head2 SetLargeContent VALUE Set LargeContent to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, LargeContent will be stored as a longblob.) =cut =head2 ContentType Returns the current value of ContentType. (In the database, ContentType is stored as varchar(80).) =head2 SetContentType VALUE Set ContentType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ContentType will be stored as a varchar(80).) =cut =head2 ContentEncoding Returns the current value of ContentEncoding. (In the database, ContentEncoding is stored as varchar(80).) =head2 SetContentEncoding VALUE Set ContentEncoding to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ContentEncoding will be stored as a varchar(80).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut =head2 Disabled Returns the current value of Disabled. (In the database, Disabled is stored as smallint(6).) =head2 SetDisabled VALUE Set Disabled to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Disabled will be stored as a smallint(6).) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, CustomField => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, ObjectType => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, ObjectId => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(19)', default => ''}, SortOrder => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Content => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, LargeContent => {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longblob', default => ''}, ContentType => {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''}, ContentEncoding => {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, Disabled => {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); $deps->Add( out => $self->CustomFieldObj ); $deps->Add( out => $self->Object ); } sub ShouldStoreExternally { my $self = shift; my $type = $self->CustomFieldObj->Type; my $length = length($self->LargeContent || ''); return (0, "zero length") if $length == 0; return 1 if $type eq "Binary"; if ($type eq "Image") { # We only store externally if it's _large_ return 1 if $length > RT->Config->Get('ExternalStorageCutoffSize'); return (0, "image size ($length) does not exceed ExternalStorageCutoffSize (" . RT->Config->Get('ExternalStorageCutoffSize') . ")"); } return (0, "Only custom fields of type Binary or Image go into external storage (not $type)"); } sub ExternalStoreDigest { my $self = shift; return undef if $self->ContentEncoding ne 'external'; return $self->_Value( 'LargeContent' ); } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ObjectCustomFields.pm���������������������������������������������������������������000644 �000765 �000024 �00000006517 14005011336 020173� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::ObjectCustomFields; use base 'RT::SearchBuilder::AddAndSort'; use RT::CustomField; use RT::ObjectCustomField; sub Table { 'ObjectCustomFields'} sub LimitToCustomField { my $self = shift; my $id = shift; $self->Limit( FIELD => 'CustomField', VALUE => $id ); } sub LimitToLookupType { my $self = shift; my $lookup = shift; $self->{'_cfs_alias'} ||= $self->Join( ALIAS1 => 'main', FIELD1 => 'CustomField', TABLE2 => 'CustomFields', FIELD2 => 'id', ); $self->Limit( ALIAS => $self->{'_cfs_alias'}, FIELD => 'LookupType', OPERATOR => '=', VALUE => $lookup, ); } sub HasEntryForCustomField { my $self = shift; my $id = shift; my @items = grep {$_->CustomField == $id } @{$self->ItemsArrayRef}; if ($#items > 1) { die "$self HasEntry had a list with more than one of $id in it. this can never happen"; } if ($#items == -1 ) { return undef; } else { return ($items[0]); } } sub _DoSearch { my $self = shift; if ($self->{'_cfs_alias'}) { $self->Limit( ALIAS => $self->{'_cfs_alias'}, FIELD => 'Disabled', OPERATOR => '!=', VALUE => 1); } $self->SUPER::_DoSearch() } RT::Base->_ImportOverlays(); 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/CustomField.pm����������������������������������������������������������������������000644 �000765 �000024 �00000233455 14005011336 016664� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::CustomField; use strict; use warnings; use 5.010; use Scalar::Util 'blessed'; use base 'RT::Record'; use Role::Basic 'with'; with "RT::Record::Role::Rights"; sub Table {'CustomFields'} use Scalar::Util qw(blessed); use RT::CustomFieldValues; use RT::ObjectCustomFields; use RT::ObjectCustomFieldValues; our %FieldTypes = ( Select => { sort_order => 10, selection_type => 1, canonicalizes => 0, labels => [ 'Select multiple values', # loc 'Select one value', # loc 'Select up to [quant,_1,value,values]', # loc ], render_types => { multiple => [ # Default is the first one 'Select box', # loc 'List', # loc ], single => [ 'Dropdown', # loc 'Select box', # loc 'List', # loc ] }, }, Freeform => { sort_order => 20, selection_type => 0, canonicalizes => 1, labels => [ 'Enter multiple values', # loc 'Enter one value', # loc 'Enter up to [quant,_1,value,values]', # loc ] }, Text => { sort_order => 30, selection_type => 0, canonicalizes => 1, labels => [ 'Fill in multiple text areas', # loc 'Fill in one text area', # loc 'Fill in up to [quant,_1,text area,text areas]', # loc ] }, Wikitext => { sort_order => 40, selection_type => 0, canonicalizes => 1, labels => [ 'Fill in multiple wikitext areas', # loc 'Fill in one wikitext area', # loc 'Fill in up to [quant,_1,wikitext area,wikitext areas]', # loc ] }, Image => { sort_order => 50, selection_type => 0, canonicalizes => 0, labels => [ 'Upload multiple images', # loc 'Upload one image', # loc 'Upload up to [quant,_1,image,images]', # loc ] }, Binary => { sort_order => 60, selection_type => 0, canonicalizes => 0, labels => [ 'Upload multiple files', # loc 'Upload one file', # loc 'Upload up to [quant,_1,file,files]', # loc ] }, Combobox => { sort_order => 70, selection_type => 1, canonicalizes => 1, labels => [ 'Combobox: Select or enter multiple values', # loc 'Combobox: Select or enter one value', # loc 'Combobox: Select or enter up to [quant,_1,value,values]', # loc ] }, Autocomplete => { sort_order => 80, selection_type => 1, canonicalizes => 1, labels => [ 'Enter multiple values with autocompletion', # loc 'Enter one value with autocompletion', # loc 'Enter up to [quant,_1,value,values] with autocompletion', # loc ] }, Date => { sort_order => 90, selection_type => 0, canonicalizes => 0, labels => [ 'Select multiple dates', # loc 'Select date', # loc 'Select up to [quant,_1,date,dates]', # loc ] }, DateTime => { sort_order => 100, selection_type => 0, canonicalizes => 0, labels => [ 'Select multiple datetimes', # loc 'Select datetime', # loc 'Select up to [quant,_1,datetime,datetimes]', # loc ] }, IPAddress => { sort_order => 110, selection_type => 0, canonicalizes => 0, labels => [ 'Enter multiple IP addresses', # loc 'Enter one IP address', # loc 'Enter up to [quant,_1,IP address,IP addresses]', # loc ] }, IPAddressRange => { sort_order => 120, selection_type => 0, canonicalizes => 0, labels => [ 'Enter multiple IP address ranges', # loc 'Enter one IP address range', # loc 'Enter up to [quant,_1,IP address range,IP address ranges]', # loc ] }, ); my %BUILTIN_GROUPINGS; my %FRIENDLY_LOOKUP_TYPES = (); __PACKAGE__->RegisterLookupType( 'RT::Queue-RT::Ticket' => "Tickets", ); #loc __PACKAGE__->RegisterLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", ); #loc __PACKAGE__->RegisterLookupType( 'RT::User' => "Users", ); #loc __PACKAGE__->RegisterLookupType( 'RT::Queue' => "Queues", ); #loc __PACKAGE__->RegisterLookupType( 'RT::Group' => "Groups", ); #loc __PACKAGE__->RegisterBuiltInGroupings( 'RT::Ticket' => [ qw(Basics Dates Links People) ], 'RT::User' => [ 'Identity', 'Access control', 'Location', 'Phones' ], 'RT::Group' => [ 'Basics' ], ); __PACKAGE__->AddRight( General => SeeCustomField => 'View custom fields'); # loc __PACKAGE__->AddRight( Admin => AdminCustomField => 'Create, modify and delete custom fields'); # loc __PACKAGE__->AddRight( Admin => AdminCustomFieldValues => 'Create, modify and delete custom fields values'); # loc __PACKAGE__->AddRight( Staff => ModifyCustomField => 'Add, modify and delete custom field values for objects'); # loc __PACKAGE__->AddRight( Staff => SetInitialCustomField => 'Add custom field values only at object creation time'); # loc =head1 NAME RT::CustomField_Overlay - overlay for RT::CustomField =head1 DESCRIPTION =head1 'CORE' METHODS =head2 Create PARAMHASH Create takes a hash of values and creates a row in the database: varchar(200) 'Name'. varchar(200) 'Type'. int(11) 'MaxValues'. varchar(255) 'Pattern'. varchar(255) 'Description'. int(11) 'SortOrder'. varchar(255) 'LookupType'. varchar(255) 'EntryHint'. smallint(6) 'Disabled'. C<LookupType> is generally the result of either C<RT::Ticket->CustomFieldLookupType> or C<RT::Transaction->CustomFieldLookupType>. =cut sub Create { my $self = shift; my %args = ( Name => '', Type => '', MaxValues => 0, Pattern => '', Description => '', Disabled => 0, SortOrder => 0, LookupType => '', LinkValueTo => '', IncludeContentForValue => '', EntryHint => undef, UniqueValues => 0, CanonicalizeClass => undef, @_, ); unless ( $self->CurrentUser->HasRight(Object => $RT::System, Right => 'AdminCustomField') ) { return (0, $self->loc('Permission Denied')); } if ( $args{TypeComposite} ) { @args{'Type', 'MaxValues'} = split(/-/, $args{TypeComposite}, 2); } elsif ( $args{Type} =~ s/(?:(Single)|Multiple)$// ) { # old style Type string $args{'MaxValues'} = $1 ? 1 : 0; } $args{'MaxValues'} = int $args{'MaxValues'}; if ( !exists $args{'Queue'}) { # do nothing -- things below are strictly backward compat } elsif ( ! $args{'Queue'} ) { unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AssignCustomFields') ) { return ( 0, $self->loc('Permission Denied') ); } $args{'LookupType'} = 'RT::Queue-RT::Ticket'; } else { my $queue = RT::Queue->new($self->CurrentUser); $queue->Load($args{'Queue'}); unless ($queue->Id) { return (0, $self->loc("Queue not found")); } unless ( $queue->CurrentUserHasRight('AssignCustomFields') ) { return ( 0, $self->loc('Permission Denied') ); } $args{'LookupType'} = 'RT::Queue-RT::Ticket'; $args{'Queue'} = $queue->Id; } my ($ok, $msg) = $self->_IsValidRegex( $args{'Pattern'} ); return (0, $self->loc("Invalid pattern: [_1]", $msg)) unless $ok; if ( $args{'MaxValues'} != 1 && $args{'Type'} =~ /(text|combobox)$/i ) { $RT::Logger->debug("Support for 'multiple' Texts or Comboboxes is not implemented"); $args{'MaxValues'} = 1; } if ( $args{'RenderType'} ||= undef ) { my $composite = join '-', @args{'Type', 'MaxValues'}; return (0, $self->loc("This custom field has no Render Types")) unless $self->HasRenderTypes( $composite ); if ( $args{'RenderType'} eq $self->DefaultRenderType( $composite ) ) { $args{'RenderType'} = undef; } else { return (0, $self->loc("Invalid Render Type") ) unless grep $_ eq $args{'RenderType'}, $self->RenderTypes( $composite ); } } $args{'ValuesClass'} = undef if ($args{'ValuesClass'} || '') eq 'RT::CustomFieldValues'; if ( $args{'ValuesClass'} ||= undef ) { return (0, $self->loc("This Custom Field can not have list of values")) unless $self->IsSelectionType( $args{'Type'} ); unless ( $self->ValidateValuesClass( $args{'ValuesClass'} ) ) { return (0, $self->loc("Invalid Custom Field values source")); } } if ( $args{'CanonicalizeClass'} ||= undef ) { return (0, $self->loc("This custom field can not have a canonicalizer")) unless $self->IsCanonicalizeType( $args{'Type'} ); unless ( $self->ValidateCanonicalizeClass( $args{'CanonicalizeClass'} ) ) { return (0, $self->loc("Invalid custom field values canonicalizer")); } } $args{'Disabled'} ||= 0; (my $rv, $msg) = $self->SUPER::Create( Name => $args{'Name'}, Type => $args{'Type'}, RenderType => $args{'RenderType'}, MaxValues => $args{'MaxValues'}, Pattern => $args{'Pattern'}, BasedOn => $args{'BasedOn'}, ValuesClass => $args{'ValuesClass'}, Description => $args{'Description'}, Disabled => $args{'Disabled'}, SortOrder => $args{'SortOrder'}, LookupType => $args{'LookupType'}, UniqueValues => $args{'UniqueValues'}, CanonicalizeClass => $args{'CanonicalizeClass'}, ); if ($rv) { if ( exists $args{'LinkValueTo'}) { $self->SetLinkValueTo($args{'LinkValueTo'}); } $self->SetEntryHint( $args{EntryHint} // $self->FriendlyType ); if ( exists $args{'IncludeContentForValue'}) { $self->SetIncludeContentForValue($args{'IncludeContentForValue'}); } return ($rv, $msg) unless exists $args{'Queue'}; # Compat code -- create a new ObjectCustomField mapping my $OCF = RT::ObjectCustomField->new( $self->CurrentUser ); $OCF->Create( CustomField => $self->Id, ObjectId => $args{'Queue'}, ); } return ($rv, $msg); } =head2 Load ID/NAME Load a custom field. If the value handed in is an integer, load by custom field ID. Otherwise, Load by name. =cut sub Load { my $self = shift; my $id = shift || ''; if ( $id =~ /^\d+$/ ) { return $self->SUPER::Load( $id ); } else { return $self->LoadByName( Name => $id ); } } =head2 LoadByName Name => C<NAME>, [...] Loads the Custom field named NAME. As other optional parameters, takes: =over =item LookupType => C<LOOKUPTYPE> The type of Custom Field to look for; while this parameter is not required, it is highly suggested, or you may not find the Custom Field you are expecting. It should be passed a C<LookupType> such as L<RT::Ticket/CustomFieldLookupType> or L<RT::User/CustomFieldLookupType>. =item ObjectType => C<CLASS> The class of object that the custom field is applied to. This can be intuited from the provided C<LookupType>. =item ObjectId => C<ID> limits the custom field search to one applied to the relevant id. For example, if a C<LookupType> of C<< RT::Ticket->CustomFieldLookupType >> is used, this is which Queue the CF must be applied to. Pass 0 to only search custom fields that are applied globally. =item IncludeDisabled => C<BOOLEAN> Whether it should return Disabled custom fields if they match; defaults to on, though non-Disabled custom fields are returned preferentially. =item IncludeGlobal => C<BOOLEAN> Whether to also search global custom fields, even if a value is provided for C<ObjectId>; defaults to off. Non-global custom fields are returned preferentially. =back For backwards compatibility, a value passed for C<Queue> is equivalent to specifying a C<LookupType> of L<RT::Ticket/CustomFieldLookupType>, and a C<ObjectId> of the value passed as C<Queue>. If multiple custom fields match the above constraints, the first according to C<SortOrder> will be returned; ties are broken by C<id>, lowest-first. =head2 LoadNameAndQueue =head2 LoadByNameAndQueue Deprecated alternate names for L</LoadByName>. =cut # Compatibility for API change after 3.0 beta 1 *LoadNameAndQueue = \&LoadByName; # Change after 3.4 beta. *LoadByNameAndQueue = \&LoadByName; sub LoadByName { my $self = shift; my %args = ( Name => undef, LookupType => undef, ObjectType => undef, ObjectId => undef, IncludeDisabled => 1, IncludeGlobal => 0, # Back-compat Queue => undef, @_, ); unless ( defined $args{'Name'} && length $args{'Name'} ) { $RT::Logger->error("Couldn't load Custom Field without Name"); return wantarray ? (0, $self->loc("No name provided")) : 0; } if ( defined $args{'Queue'} ) { # Set a LookupType for backcompat, otherwise we'll calculate # one of RT::Queue from your ContextObj. Older code was relying # on us defaulting to RT::Queue-RT::Ticket in old LimitToQueue call. $args{LookupType} ||= 'RT::Queue-RT::Ticket'; $args{ObjectId} //= delete $args{Queue}; } # Default the ObjectType to the top category of the LookupType; it's # what the CFs are assigned on. $args{ObjectType} ||= $1 if $args{LookupType} and $args{LookupType} =~ /^([^-]+)/; # Resolve the ObjectId/ObjectType; this is necessary to properly # limit ObjectId, and also possibly useful to set a ContextObj if we # are currently lacking one. It is not strictly necessary if we # have a context object and were passed a numeric ObjectId, but it # cannot hurt to verify its sanity. Skip if we have a false # ObjectId, which means "global", or if we lack an ObjectType if ($args{ObjectId} and $args{ObjectType}) { my ($obj, $ok, $msg); eval { $obj = $args{ObjectType}->new( $self->CurrentUser ); ($ok, $msg) = $obj->Load( $args{ObjectId} ); }; if ($ok) { $args{ObjectId} = $obj->id; $self->SetContextObject( $obj ) unless $self->ContextObject; } else { $RT::Logger->warning("Failed to load $args{ObjectType} '$args{ObjectId}'"); if ($args{IncludeGlobal}) { # Fall back to acting like we were only asked about the # global case $args{ObjectId} = 0; } else { # If they didn't also want global results, there's no # point in searching; abort return wantarray ? (0, $self->loc("Not found")) : 0; } } } elsif (not $args{ObjectType} and $args{ObjectId}) { # If we skipped out on the above due to lack of ObjectType, make # sure we clear out ObjectId of anything lingering $RT::Logger->warning("No LookupType or ObjectType passed; ignoring ObjectId"); delete $args{ObjectId}; } my $CFs = RT::CustomFields->new( $self->CurrentUser ); $CFs->SetContextObject( $self->ContextObject ); my $field = $args{'Name'} =~ /\D/? 'Name' : 'id'; $CFs->Limit( FIELD => $field, VALUE => $args{'Name'}, CASESENSITIVE => 0); # The context object may be a ticket, for example, as context for a # queue CF. The valid lookup types are thus the entire set of # ACLEquivalenceObjects for the context object. $args{LookupType} ||= [ map {$_->CustomFieldLookupType} ($self->ContextObject, $self->ContextObject->ACLEquivalenceObjects) ] if $self->ContextObject; # Apply LookupType limits $args{LookupType} = [ $args{LookupType} ] if $args{LookupType} and not ref($args{LookupType}); $CFs->Limit( FIELD => "LookupType", OPERATOR => "IN", VALUE => $args{LookupType} ) if $args{LookupType}; # Default to by SortOrder and id; this mirrors the standard ordering # of RT::CustomFields (minus the Name, which is guaranteed to be # fixed) my @order = ( { FIELD => 'SortOrder', ORDER => 'ASC' }, { FIELD => 'id', ORDER => 'ASC' }, ); if (defined $args{ObjectId}) { # The join to OCFs is distinct -- either we have a global # application or an objectid match, but never both. Even if # this were not the case, we care only for the first row. my $ocfs = $CFs->_OCFAlias( Distinct => 1); if ($args{IncludeGlobal}) { $CFs->Limit( ALIAS => $ocfs, FIELD => 'ObjectId', OPERATOR => 'IN', VALUE => [ $args{ObjectId}, 0 ], ); # Find the queue-specific first unshift @order, { ALIAS => $ocfs, FIELD => "ObjectId", ORDER => "DESC" }; } else { $CFs->Limit( ALIAS => $ocfs, FIELD => 'ObjectId', VALUE => $args{ObjectId}, ); } } if ($args{IncludeDisabled}) { # Load disabled fields, but return them only as a last resort. # This goes at the front of @order, as we prefer the # non-disabled global CF to the disabled Queue-specific CF. $CFs->FindAllRows; unshift @order, { FIELD => "Disabled", ORDER => 'ASC' }; } # Apply the above orderings $CFs->OrderByCols( @order ); # We only want one entry. $CFs->RowsPerPage(1); # version before 3.8 just returns 0, so we need to test if wantarray to be # backward compatible. return wantarray ? (0, $self->loc("Not found")) : 0 unless my $first = $CFs->First; return $self->LoadById( $first->id ); } =head2 Custom field values =head3 Values FIELD Return a object (collection) of all acceptable values for this Custom Field. Class of the object can vary and depends on the return value of the C<ValuesClass> method. =cut *ValuesObj = \&Values; sub Values { my $self = shift; my $class = $self->ValuesClass; if ( $class ne 'RT::CustomFieldValues') { $class->require or die "Can't load $class: $@"; } my $cf_values = $class->new( $self->CurrentUser ); $cf_values->SetCustomFieldObject( $self ); # if the user has no rights, return an empty object if ( $self->id && $self->CurrentUserCanSee ) { $cf_values->LimitToCustomField( $self->Id ); } else { $cf_values->Limit( FIELD => 'id', VALUE => 0, SUBCLAUSE => 'acl' ); } return ($cf_values); } =head3 AddValue HASH Create a new value for this CustomField. Takes a paramhash containing the elements Name, Description and SortOrder =cut sub AddValue { my $self = shift; my %args = @_; unless ($self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues')) { return (0, $self->loc('Permission Denied')); } # allow zero value if ( !defined $args{'Name'} || $args{'Name'} eq '' ) { return (0, $self->loc("Can't add a custom field value without a name")); } my $newval = RT::CustomFieldValue->new( $self->CurrentUser ); return $newval->Create( %args, CustomField => $self->Id ); } =head3 DeleteValue ID Deletes a value from this custom field by id. Does not remove this value for any article which has had it selected =cut sub DeleteValue { my $self = shift; my $id = shift; unless ( $self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues') ) { return (0, $self->loc('Permission Denied')); } my $val_to_del = RT::CustomFieldValue->new( $self->CurrentUser ); $val_to_del->Load( $id ); unless ( $val_to_del->Id ) { return (0, $self->loc("Couldn't find that value")); } unless ( $val_to_del->CustomField == $self->Id ) { return (0, $self->loc("That is not a value for this custom field")); } my ($ok, $msg) = $val_to_del->Delete; unless ( $ok ) { return (0, $self->loc("Custom field value could not be deleted")); } return ($ok, $self->loc("Custom field value deleted")); } =head2 ValidateQueue Queue Make sure that the name specified is valid =cut sub ValidateName { my $self = shift; my $value = shift; return 0 unless length $value; return $self->SUPER::ValidateName($value); } =head2 ValidateQueue Queue Make sure that the queue specified is a valid queue name =cut sub ValidateQueue { my $self = shift; my $id = shift; return undef unless defined $id; # 0 means "Global" null would _not_ be ok. return 1 if $id eq '0'; my $q = RT::Queue->new( RT->SystemUser ); $q->Load( $id ); return undef unless $q->id; return 1; } =head2 Types Retuns an array of the types of CustomField that are supported =cut sub Types { return (sort {(($FieldTypes{$a}{sort_order}||999) <=> ($FieldTypes{$b}{sort_order}||999)) or ($a cmp $b)} keys %FieldTypes); } =head2 IsSelectionType Returns a boolean value indicating whether the C<Values> method makes sense to this Custom Field. =cut sub IsSelectionType { my $self = shift; my $type = @_ ? shift : $self->Type; return undef unless $type; return $FieldTypes{$type}->{selection_type}; } =head2 IsCanonicalizeType Returns a boolean value indicating whether the type of this custom field permits using a canonicalizer. =cut sub IsCanonicalizeType { my $self = shift; my $type = @_ ? shift : $self->Type; return undef unless $type; return $FieldTypes{$type}->{canonicalizes}; } =head2 IsExternalValues =cut sub IsExternalValues { my $self = shift; return 0 unless $self->IsSelectionType( @_ ); return $self->ValuesClass eq 'RT::CustomFieldValues'? 0 : 1; } sub ValuesClass { my $self = shift; return $self->_Value( ValuesClass => @_ ) || 'RT::CustomFieldValues'; } =head2 SetValuesClass CLASS Writer method for the ValuesClass field; validates that the custom field can use a ValuesClass, and that the provided ValuesClass passes L</ValidateValuesClass>. =cut sub SetValuesClass { my $self = shift; my $class = shift || 'RT::CustomFieldValues'; if ( $class eq 'RT::CustomFieldValues' ) { return $self->_Set( Field => 'ValuesClass', Value => undef, @_ ); } return (0, $self->loc("This Custom Field can not have list of values")) unless $self->IsSelectionType; unless ( $self->ValidateValuesClass( $class ) ) { return (0, $self->loc("Invalid Custom Field values source")); } return $self->_Set( Field => 'ValuesClass', Value => $class, @_ ); } =head2 ValidateValuesClass CLASS Validates a potential ValuesClass value; the ValuesClass may be C<undef> or the string C<"RT::CustomFieldValues"> (both of which make this custom field use the ordinary values implementation), or a class name in the listed in the L<RT_Config/@CustomFieldValuesSources> setting. Returns true if valid; false if invalid. =cut sub ValidateValuesClass { my $self = shift; my $class = shift; return 1 if !$class || $class eq 'RT::CustomFieldValues'; return 1 if grep $class eq $_, RT->Config->Get('CustomFieldValuesSources'); return undef; } =head2 SetCanonicalizeClass CLASS Writer method for the CanonicalizeClass field; validates that the custom field can use a CanonicalizeClass, and that the provided CanonicalizeClass passes L</ValidateCanonicalizeClass>. =cut sub SetCanonicalizeClass { my $self = shift; my $class = shift; if ( !$class ) { return $self->_Set( Field => 'CanonicalizeClass', Value => undef, @_ ); } return (0, $self->loc("This custom field can not have a canonicalizer")) unless $self->IsCanonicalizeType; unless ( $self->ValidateCanonicalizeClass( $class ) ) { return (0, $self->loc("Invalid custom field values canonicalizer")); } return $self->_Set( Field => 'CanonicalizeClass', Value => $class, @_ ); } =head2 ValidateCanonicalizeClass CLASS Validates a potential CanonicalizeClass value; the CanonicalizeClass may be C<undef> (which make this custom field use no special canonicalization), or a class name in the listed in the L<RT_Config/@CustomFieldValuesCanonicalizers> setting. Returns true if valid; false if invalid. =cut sub ValidateCanonicalizeClass { my $self = shift; my $class = shift; return 1 if !$class; return 1 if grep $class eq $_, RT->Config->Get('CustomFieldValuesCanonicalizers'); return undef; } =head2 FriendlyType [TYPE, MAX_VALUES] Returns a localized human-readable version of the custom field type. If a custom field type is specified as the parameter, the friendly type for that type will be returned =cut sub FriendlyType { my $self = shift; my $type = @_ ? shift : $self->Type; my $max = @_ ? shift : $self->MaxValues; $max = 0 unless $max; if (my $friendly_type = $FieldTypes{$type}->{labels}->[$max>2 ? 2 : $max]) { return ( $self->loc( $friendly_type, $max ) ); } else { return ( $self->loc( $type ) ); } } sub FriendlyTypeComposite { my $self = shift; my $composite = shift || $self->TypeComposite; return $self->FriendlyType(split(/-/, $composite, 2)); } =head2 ValidateType TYPE Takes a single string. returns true if that string is a value type of custom field =cut sub ValidateType { my $self = shift; my $type = shift; if ( $FieldTypes{$type} ) { return 1; } else { return undef; } } sub SetType { my $self = shift; my $type = shift; my $need_to_update_hint; $need_to_update_hint = 1 if $self->EntryHint && $self->EntryHint eq $self->FriendlyType; my ( $ret, $msg ) = $self->_Set( Field => 'Type', Value => $type ); $self->SetEntryHint($self->FriendlyType) if $need_to_update_hint && $ret; return ( $ret, $msg ); } =head2 SetPattern STRING Takes a single string representing a regular expression. Performs basic validation on that regex, and sets the C<Pattern> field for the CF if it is valid. =cut sub SetPattern { my $self = shift; my $regex = shift; my ($ok, $msg) = $self->_IsValidRegex($regex); if ($ok) { return $self->_Set(Field => 'Pattern', Value => $regex); } else { return (0, $self->loc("Invalid pattern: [_1]", $msg)); } } =head2 _IsValidRegex(Str $regex) returns (Bool $success, Str $msg) Tests if the string contains an invalid regex. =cut sub _IsValidRegex { my $self = shift; my $regex = shift or return (1, 'valid'); local $^W; local $@; local $SIG{__DIE__} = sub { 1 }; local $SIG{__WARN__} = sub { 1 }; if (eval { qr/$regex/; 1 }) { return (1, 'valid'); } my $err = $@; $err =~ s{[,;].*}{}; # strip debug info from error chomp $err; return (0, $err); } =head2 SingleValue Returns true if this CustomField only accepts a single value. Returns false if it accepts multiple values =cut sub SingleValue { my $self = shift; if (($self->MaxValues||0) == 1) { return 1; } else { return undef; } } sub UnlimitedValues { my $self = shift; if (($self->MaxValues||0) == 0) { return 1; } else { return undef; } } =head2 ACLEquivalenceObjects Returns list of objects via which users can get rights on this custom field. For custom fields these objects can be set using L<ContextObject|/"ContextObject and SetContextObject">. =cut sub ACLEquivalenceObjects { my $self = shift; my $ctx = $self->ContextObject or return; return ($ctx, $ctx->ACLEquivalenceObjects); } =head2 ContextObject and SetContextObject Set or get a context for this object. It can be ticket, queue or another object this CF added to. Used for ACL control, for example SeeCustomField can be granted on queue level to allow people to see all fields added to the queue. =cut sub SetContextObject { my $self = shift; return $self->{'context_object'} = shift; } sub ContextObject { my $self = shift; return $self->{'context_object'}; } sub ValidContextType { my $self = shift; my $class = shift; my %valid; $valid{$_}++ for split '-', $self->LookupType; delete $valid{'RT::Transaction'}; return $valid{$class}; } =head2 LoadContextObject Takes an Id for a Context Object and loads the right kind of RT::Object for this particular Custom Field (based on the LookupType) and returns it. This is a good way to ensure you don't try to use a Queue as a Context Object on a User Custom Field. =cut sub LoadContextObject { my $self = shift; my $type = shift; my $contextid = shift; unless ( $self->ValidContextType($type) ) { RT->Logger->debug("Invalid ContextType $type for Custom Field ".$self->Id); return; } my $context_object = $type->new( $self->CurrentUser ); my ($id, $msg) = $context_object->LoadById( $contextid ); unless ( $id ) { RT->Logger->debug("Invalid ContextObject id: $msg"); return; } return $context_object; } =head2 ValidateContextObject Ensure that a given ContextObject applies to this Custom Field. For custom fields that are assigned to Queues or to Classes, this checks that the Custom Field is actually added to that object. For Global Custom Fields, it returns true as long as the Object is of the right type, because you may be using your permissions on a given Queue of Class to see a Global CF. For CFs that are only added globally, you don't need a ContextObject. =cut sub ValidateContextObject { my $self = shift; my $object = shift; return 1 if $self->IsGlobal; # global only custom fields don't have objects # that should be used as context objects. return if $self->IsOnlyGlobal; # Otherwise, make sure we weren't passed a user object that we're # supposed to treat as a queue. return unless $self->ValidContextType(ref $object); # Check that it is added correctly my ($added_to) = grep {ref($_) eq $self->RecordClassFromLookupType} ($object, $object->ACLEquivalenceObjects); return unless $added_to; return $self->IsAdded($added_to->id); } sub _Set { my $self = shift; my %args = @_; unless ( $self->CurrentUserHasRight('AdminCustomField') ) { return ( 0, $self->loc('Permission Denied') ); } my ($ret, $msg) = $self->SUPER::_Set( @_ ); if ( $args{Field} =~ /^(?:MaxValues|Type|LookupType|ValuesClass|CanonicalizeClass)$/ ) { $self->CleanupDefaultValues; } return ($ret, $msg); } =head2 _Value Takes the name of a table column. Returns its value as a string, if the user passes an ACL check =cut sub _Value { my $self = shift; return undef unless $self->id; # we need to do the rights check unless ( $self->CurrentUserCanSee ) { $RT::Logger->debug( "Permission denied. User #". $self->CurrentUser->id ." has no SeeCustomField right on CF #". $self->id ); return (undef); } return $self->__Value( @_ ); } =head2 SetDisabled Takes a boolean. 1 will cause this custom field to no longer be avaialble for objects. 0 will re-enable this field. =cut sub SetDisabled { my $self = shift; my $val = shift; my ($status, $msg) = $self->_Set(Field => 'Disabled', Value => $val); unless ($status) { return ($status, $msg); } if ( $val == 1 ) { return (1, $self->loc("Disabled")); } else { return (1, $self->loc("Enabled")); } } =head2 SetTypeComposite Set this custom field's type and maximum values as a composite value =cut sub SetTypeComposite { my $self = shift; my $composite = shift; my $old = $self->TypeComposite; my ($type, $max_values) = split(/-/, $composite, 2); if ( $type ne $self->Type ) { my ($status, $msg) = $self->SetType( $type ); return ($status, $msg) unless $status; } if ( ($max_values || 0) != ($self->MaxValues || 0) ) { my ($status, $msg) = $self->SetMaxValues( $max_values ); return ($status, $msg) unless $status; } my $render = $self->RenderType; if ( $render and not grep { $_ eq $render } $self->RenderTypes ) { # We switched types and our render type is no longer valid, so unset it # and use the default $self->SetRenderType( undef ); } return 1, $self->loc( "Type changed from '[_1]' to '[_2]'", $self->FriendlyTypeComposite( $old ), $self->FriendlyTypeComposite( $composite ), ); } =head2 TypeComposite Returns a composite value composed of this object's type and maximum values =cut sub TypeComposite { my $self = shift; return join '-', ($self->Type || ''), ($self->MaxValues || 0); } =head2 TypeComposites Returns an array of all possible composite values for custom fields. =cut sub TypeComposites { my $self = shift; return grep !/(?:[Tt]ext|Combobox|Date|DateTime)-0/, map { ("$_-1", "$_-0") } $self->Types; } =head2 RenderType Returns the type of form widget to render for this custom field. Currently this only affects fields which return true for L</HasRenderTypes>. =cut sub RenderType { my $self = shift; return '' unless $self->HasRenderTypes; return $self->_Value( 'RenderType', @_ ) || $self->DefaultRenderType; } =head2 SetRenderType TYPE Sets this custom field's render type. =cut sub SetRenderType { my $self = shift; my $type = shift; return (0, $self->loc("This custom field has no Render Types")) unless $self->HasRenderTypes; if ( !$type || $type eq $self->DefaultRenderType ) { return $self->_Set( Field => 'RenderType', Value => undef, @_ ); } if ( not grep { $_ eq $type } $self->RenderTypes ) { return (0, $self->loc("Invalid Render Type for custom field of type [_1]", $self->FriendlyType)); } return $self->_Set( Field => 'RenderType', Value => $type, @_ ); } =head2 DefaultRenderType [TYPE COMPOSITE] Returns the default render type for this custom field's type or the TYPE COMPOSITE specified as an argument. =cut sub DefaultRenderType { my $self = shift; my $composite = @_ ? shift : $self->TypeComposite; my ($type, $max) = split /-/, $composite, 2; return unless $type and $self->HasRenderTypes($composite); return $FieldTypes{$type}->{render_types}->{ $max == 1 ? 'single' : 'multiple' }[0]; } =head2 HasRenderTypes [TYPE_COMPOSITE] Returns a boolean value indicating whether the L</RenderTypes> and L</RenderType> methods make sense for this custom field. Currently true only for type C<Select>. =cut sub HasRenderTypes { my $self = shift; my ($type, $max) = split /-/, (@_ ? shift : $self->TypeComposite), 2; return undef unless $type; return defined $FieldTypes{$type}->{render_types} ->{ $max == 1 ? 'single' : 'multiple' }; } =head2 RenderTypes [TYPE COMPOSITE] Returns the valid render types for this custom field's type or the TYPE COMPOSITE specified as an argument. =cut sub RenderTypes { my $self = shift; my $composite = @_ ? shift : $self->TypeComposite; my ($type, $max) = split /-/, $composite, 2; return unless $type and $self->HasRenderTypes($composite); return @{$FieldTypes{$type}->{render_types}->{ $max == 1 ? 'single' : 'multiple' }}; } =head2 SetLookupType Autrijus: care to doc how LookupTypes work? =cut sub SetLookupType { my $self = shift; my $lookup = shift; if ( $lookup ne $self->LookupType ) { # Okay... We need to invalidate our existing relationships RT::ObjectCustomField->new($self->CurrentUser)->DeleteAll( CustomField => $self ); } return $self->_Set(Field => 'LookupType', Value =>$lookup); } =head2 LookupTypes Returns an array of LookupTypes available =cut sub LookupTypes { my $self = shift; return sort keys %FRIENDLY_LOOKUP_TYPES; } =head2 FriendlyLookupType Returns a localized description of the type of this custom field =cut sub FriendlyLookupType { my $self = shift; my $lookup = shift || $self->LookupType; return ($self->loc( $FRIENDLY_LOOKUP_TYPES{$lookup} )) if defined $FRIENDLY_LOOKUP_TYPES{$lookup}; my @types = map { s/^RT::// ? $self->loc($_) : $_ } grep { defined and length } split( /-/, $lookup ) or return; state $LocStrings = [ "[_1] objects", # loc "[_1]'s [_2] objects", # loc "[_1]'s [_2]'s [_3] objects", # loc ]; return ( $self->loc( $LocStrings->[$#types], @types ) ); } =head1 RecordClassFromLookupType Returns the type of Object referred to by ObjectCustomFields' ObjectId column Optionally takes a LookupType to use instead of using the value on the loaded record. In this case, the method may be called on the class instead of an object. =cut sub RecordClassFromLookupType { my $self = shift; my $type = shift || $self->LookupType; my ($class) = ($type =~ /^([^-]+)/); unless ( $class ) { if (blessed($self) and $self->LookupType eq $type) { $RT::Logger->error( "Custom Field #". $self->id ." has incorrect LookupType '$type'" ); } else { RT->Logger->error("Invalid LookupType passed as argument: $type"); } return undef; } return $class; } =head1 ObjectTypeFromLookupType Returns the ObjectType used in ObjectCustomFieldValues rows for this CF Optionally takes a LookupType to use instead of using the value on the loaded record. In this case, the method may be called on the class instead of an object. =cut sub ObjectTypeFromLookupType { my $self = shift; my $type = shift || $self->LookupType; my ($class) = ($type =~ /([^-]+)$/); unless ( $class ) { if (blessed($self) and $self->LookupType eq $type) { $RT::Logger->error( "Custom Field #". $self->id ." has incorrect LookupType '$type'" ); } else { RT->Logger->error("Invalid LookupType passed as argument: $type"); } return undef; } return $class; } sub CollectionClassFromLookupType { my $self = shift; my $record_class = shift || $self->RecordClassFromLookupType; return undef unless $record_class; my $collection_class; if ( UNIVERSAL::can($record_class.'Collection', 'new') ) { $collection_class = $record_class.'Collection'; } elsif ( UNIVERSAL::can($record_class.'es', 'new') ) { $collection_class = $record_class.'es'; } elsif ( UNIVERSAL::can($record_class.'s', 'new') ) { $collection_class = $record_class.'s'; } else { $RT::Logger->error("Can not find a collection class for record class '$record_class'"); return undef; } return $collection_class; } =head2 Groupings Returns a (sorted and lowercased) list of the groupings in which this custom field appears. If called on a loaded object, the returned list is limited to groupings which apply to the record class this CF applies to (L</RecordClassFromLookupType>). If passed a loaded object or a class name, the returned list is limited to groupings which apply to the class of the object or the specified class. If called on an unloaded object, all potential groupings are returned. =cut sub Groupings { my $self = shift; my $record_class = $self->_GroupingClass(shift); my $config = RT->Config->Get('CustomFieldGroupings'); $config = {} unless ref($config) eq 'HASH'; my @groups; if ( $record_class ) { push @groups, sort {lc($a) cmp lc($b)} keys %{ $BUILTIN_GROUPINGS{$record_class} || {} }; if ( ref($config->{$record_class} ||= []) eq "ARRAY") { my @order = @{ $config->{$record_class} }; while (@order) { push @groups, shift(@order); shift(@order); } } else { @groups = sort {lc($a) cmp lc($b)} keys %{ $config->{$record_class} }; } } else { my %all = (%$config, %BUILTIN_GROUPINGS); @groups = sort {lc($a) cmp lc($b)} map {$self->Groupings($_)} grep {$_} keys(%all); } my %seen; return grep defined && length && !$seen{lc $_}++, @groups; } =head2 CustomGroupings Identical to L</Groupings> but filters out built-in groupings from the the returned list. =cut sub CustomGroupings { my $self = shift; my $record_class = $self->_GroupingClass(shift); return grep !$BUILTIN_GROUPINGS{$record_class}{$_}, $self->Groupings( $record_class ); } sub _GroupingClass { my $self = shift; my $record = shift; my $record_class = ref($record) || $record || ''; $record_class = $self->RecordClassFromLookupType if !$record_class and blessed($self) and $self->id; return $record_class; } =head2 RegisterBuiltInGroupings Registers groupings to be considered a fundamental part of RT, either via use in core RT or via an extension. These groupings must be rendered explicitly in Mason by specific calls to F</Elements/ShowCustomFields> and F</Elements/EditCustomFields>. They will not show up automatically on normal display pages like configured custom groupings. Takes a set of key-value pairs of class names (valid L<RT::Record> subclasses) and array refs of grouping names to consider built-in. If a class already contains built-in groupings (such as L<RT::Ticket> and L<RT::User>), new groupings are appended. =cut sub RegisterBuiltInGroupings { my $self = shift; my %new = @_; while (my ($k,$v) = each %new) { $v = [$v] unless ref($v) eq 'ARRAY'; $BUILTIN_GROUPINGS{$k} = { %{$BUILTIN_GROUPINGS{$k} || {}}, map { $_ => 1 } @$v }; } $BUILTIN_GROUPINGS{''} = { map { %$_ } values %BUILTIN_GROUPINGS }; } =head1 IsOnlyGlobal Certain custom fields (users, groups) should only be added globally; codify that set here for reference. =cut sub IsOnlyGlobal { my $self = shift; return ($self->LookupType =~ /^RT::(?:Group|User)/io); } =head1 AddedTo Returns collection with objects this custom field is added to. Class of the collection depends on L</LookupType>. See all L</NotAddedTo> . Doesn't takes into account if object is added globally. =cut sub AddedTo { my $self = shift; return RT::ObjectCustomField->new( $self->CurrentUser ) ->AddedTo( CustomField => $self ); } =head1 NotAddedTo Returns collection with objects this custom field is not added to. Class of the collection depends on L</LookupType>. See all L</AddedTo> . Doesn't take into account if the object is added globally. =cut sub NotAddedTo { my $self = shift; return RT::ObjectCustomField->new( $self->CurrentUser ) ->NotAddedTo( CustomField => $self ); } =head2 IsAdded Takes object id and returns corresponding L<RT::ObjectCustomField> record if this custom field is added to the object. Use 0 to check if custom field is added globally. =cut sub IsAdded { my $self = shift; my $id = shift; my $ocf = RT::ObjectCustomField->new( $self->CurrentUser ); $ocf->LoadByCols( CustomField => $self->id, ObjectId => $id || 0 ); return undef unless $ocf->id; return $ocf; } sub IsGlobal { return shift->IsAdded(0) } =head2 IsAddedToAny Returns true if custom field is applied to any object. =cut sub IsAddedToAny { my $self = shift; my $id = shift; my $ocf = RT::ObjectCustomField->new( $self->CurrentUser ); $ocf->LoadByCols( CustomField => $self->id ); return $ocf->id ? 1 : 0; } =head2 AddToObject OBJECT Add this custom field as a custom field for a single object, such as a queue or group. Takes an object =cut sub AddToObject { my $self = shift; my $object = shift; my $id = $object->Id || 0; unless (index($self->LookupType, ref($object)) == 0) { return ( 0, $self->loc('Lookup type mismatch') ); } unless ( $object->CurrentUserHasRight('AssignCustomFields') ) { return ( 0, $self->loc('Permission Denied') ); } my $ocf = RT::ObjectCustomField->new( $self->CurrentUser ); my $oid = $ocf->Add( CustomField => $self->id, ObjectId => $id, ); my $msg; # If object has no id, it represents all objects if ($object->id) { $msg = $self->loc( 'Added custom field [_1] to [_2].', $self->Name, $object->Name ); } else { $msg = $self->loc( 'Globally added custom field [_1].', $self->Name ); } return ( $oid, $msg ); } =head2 RemoveFromObject OBJECT Remove this custom field for a single object, such as a queue or group. Takes an object =cut sub RemoveFromObject { my $self = shift; my $object = shift; my $id = $object->Id || 0; unless (index($self->LookupType, ref($object)) == 0) { return ( 0, $self->loc('Object type mismatch') ); } unless ( $object->CurrentUserHasRight('AssignCustomFields') ) { return ( 0, $self->loc('Permission Denied') ); } my $ocf = $self->IsAdded( $id ); unless ( $ocf ) { return ( 0, $self->loc("This custom field cannot be added to that object") ); } my ($ok, $msg) = $ocf->Delete; return ($ok, $msg) unless $ok; # If object has no id, it represents all objects if ($object->id) { return (1, $self->loc( 'Removed custom field [_1] from [_2].', $self->Name, $object->Name ) ); } else { return (1, $self->loc( 'Globally removed custom field [_1].', $self->Name ) ); } } =head2 AddValueForObject HASH Adds a custom field value for a record object of some kind. Takes a param hash of Required: Object Content Optional: LargeContent ContentType =cut sub AddValueForObject { my $self = shift; my %args = ( Object => undef, Content => undef, LargeContent => undef, ContentType => undef, ForCreation => 0, @_ ); my $obj = $args{'Object'} or return ( 0, $self->loc('Invalid object') ); unless ( $self->CurrentUserHasRight('ModifyCustomField') || ($args{ForCreation} && $self->CurrentUserHasRight('SetInitialCustomField')) ) { return ( 0, $self->loc('Permission Denied') ); } unless ( $self->MatchPattern($args{'Content'}) ) { return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) ); } $RT::Handle->BeginTransaction; if ( $self->MaxValues ) { my $current_values = $self->ValuesForObject($obj); # (The +1 is for the new value we're adding) my $extra_values = ( $current_values->Count + 1 ) - $self->MaxValues; # Could have a negative value if MaxValues is greater than count if ( $extra_values > 0 ) { # If we have a set of current values and we've gone over the maximum # allowed number of values, we'll need to delete some to make room. # which former values are blown away is not guaranteed while ($extra_values) { my $extra_item = $current_values->Next; unless ( $extra_item && $extra_item->id ) { $RT::Logger->crit( "We were just asked to delete " ."a custom field value that doesn't exist!" ); $RT::Handle->Rollback(); return (undef); } $extra_item->Delete; $extra_values--; } } } if ($self->UniqueValues) { my $class = $self->CollectionClassFromLookupType($self->ObjectTypeFromLookupType); my $collection = $class->new(RT->SystemUser); $collection->LimitCustomField(CUSTOMFIELD => $self->Id, OPERATOR => '=', VALUE => $args{'LargeContent'} // $args{'Content'}); if ($collection->Count) { $RT::Logger->debug( "Non-unique custom field value for CF #" . $self->Id ." with object custom field value " . $collection->First->Id ); $RT::Handle->Rollback(); return ( 0, $self->loc('That is not a unique value') ); } } my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser ); my ($val, $msg) = $newval->Create( ObjectType => ref($obj), ObjectId => $obj->Id, Content => $args{'Content'}, LargeContent => $args{'LargeContent'}, ContentType => $args{'ContentType'}, CustomField => $self->Id ); unless ($val) { $RT::Handle->Rollback(); return ($val, $self->loc("Couldn't create record: [_1]", $msg)); } $RT::Handle->Commit(); return ($val); } sub _CanonicalizeValue { my $self = shift; my $args = shift; my $type = $self->__Value('Type'); return 1 unless $type; $self->_CanonicalizeValueWithCanonicalizer($args); my $method = '_CanonicalizeValue'. $type; return 1 unless $self->can($method); $self->$method($args); } sub _CanonicalizeValueWithCanonicalizer { my $self = shift; my $args = shift; my $class = $self->__Value('CanonicalizeClass') or return 1; $class->require or die "Can't load $class: $@"; my $canonicalizer = $class->new($self->CurrentUser); $args->{'Content'} = $canonicalizer->CanonicalizeValue( CustomField => $self, Content => $args->{'Content'}, ); return 1; } sub _CanonicalizeValueDateTime { my $self = shift; my $args = shift; my $DateObj = RT::Date->new( $self->CurrentUser ); $DateObj->Set( Format => 'unknown', Value => $args->{'Content'} ); $args->{'Content'} = $DateObj->ISO; return 1; } # For date, we need to store Content as ISO date sub _CanonicalizeValueDate { my $self = shift; my $args = shift; # in case user input date with time, let's omit it by setting timezone # to utc so "hour" won't affect "day" my $DateObj = RT::Date->new( $self->CurrentUser ); $DateObj->Set( Format => 'unknown', Value => $args->{'Content'}, ); $args->{'Content'} = $DateObj->Date( Timezone => 'user' ); return 1; } sub _CanonicalizeValueIPAddress { my $self = shift; my $args = shift; $args->{Content} = RT::ObjectCustomFieldValue->ParseIP( $args->{Content} ); return (0, $self->loc("Content is not a valid IP address")) unless $args->{Content}; return 1; } sub _CanonicalizeValueIPAddressRange { my $self = shift; my $args = shift; my $content = $args->{Content}; $content .= "-".$args->{LargeContent} if $args->{LargeContent}; ($args->{Content}, $args->{LargeContent}) = RT::ObjectCustomFieldValue->ParseIPRange( $content ); $args->{ContentType} = 'text/plain'; return (0, $self->loc("Content is not a valid IP address range")) unless $args->{Content}; return 1; } =head2 MatchPattern STRING Tests the incoming string against the Pattern of this custom field object and returns a boolean; returns true if the Pattern is empty. =cut sub MatchPattern { my $self = shift; my $regex = $self->Pattern or return 1; return (( defined $_[0] ? $_[0] : '') =~ $regex); } =head2 FriendlyPattern Prettify the pattern of this custom field, by taking the text in C<(?#text)> and localizing it. =cut sub FriendlyPattern { my $self = shift; my $regex = $self->Pattern; return '' unless length $regex; if ( $regex =~ /\(\?#([^)]*)\)/ ) { return '[' . $self->loc($1) . ']'; } else { return $regex; } } =head2 DeleteValueForObject HASH Deletes a custom field value for a ticket. Takes a param hash of Object and Content Returns a tuple of (STATUS, MESSAGE). If the call succeeded, the STATUS is true. otherwise it's false =cut sub DeleteValueForObject { my $self = shift; my %args = ( Object => undef, Content => undef, Id => undef, @_ ); unless ($self->CurrentUserHasRight('ModifyCustomField')) { return (0, $self->loc('Permission Denied')); } my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser); if (my $id = $args{'Id'}) { $oldval->Load($id); } unless ($oldval->id) { $oldval->LoadByObjectContentAndCustomField( Object => $args{'Object'}, Content => $args{'Content'}, CustomField => $self->Id, ); } # check to make sure we found it unless ($oldval->Id) { return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name)); } # for single-value fields, we need to validate that empty string is a valid value for it if ( $self->SingleValue and not $self->MatchPattern( '' ) ) { return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) ); } # delete it my ($ok, $msg) = $oldval->Delete(); unless ($ok) { return(0, $self->loc("Custom field value could not be deleted")); } return($oldval->Id, $self->loc("Custom field value deleted")); } =head2 ValuesForObject OBJECT Return an L<RT::ObjectCustomFieldValues> object containing all of this custom field's values for OBJECT =cut sub ValuesForObject { my $self = shift; my $object = shift; my $values = RT::ObjectCustomFieldValues->new($self->CurrentUser); unless ($self->id and $self->CurrentUserCanSee) { # Return an empty object if they have no rights to see $values->Limit( FIELD => "id", VALUE => 0, SUBCLAUSE => "ACL" ); return ($values); } $values->LimitToCustomField($self->Id); $values->LimitToObject($object); return ($values); } =head2 CurrentUserCanSee If the user has SeeCustomField they can see this custom field and its details. Otherwise, if the user has SetInitialCustomField and this is being used in a "create" context, then they can see this custom field and its details. This allows you to set up custom fields that are only visible on create pages and are then inaccessible. =cut sub CurrentUserCanSee { my $self = shift; return 1 if $self->CurrentUserHasRight('SeeCustomField'); return 1 if $self->{include_set_initial} && $self->CurrentUserHasRight('SetInitialCustomField'); return 0; } =head2 RegisterLookupType LOOKUPTYPE FRIENDLYNAME Tell RT that a certain object accepts custom fields via a lookup type and provide a friendly name for such CFs. Examples: 'RT::Queue-RT::Ticket' => "Tickets", # loc 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", # loc 'RT::User' => "Users", # loc 'RT::Group' => "Groups", # loc 'RT::Queue' => "Queues", # loc This is a class method. =cut sub RegisterLookupType { my $self = shift; my $path = shift; my $friendly_name = shift; $FRIENDLY_LOOKUP_TYPES{$path} = $friendly_name; } =head2 IncludeContentForValue [VALUE] (and SetIncludeContentForValue) Gets or sets the C<IncludeContentForValue> for this custom field. RT uses this field to automatically include content into the user's browser as they display records with custom fields in RT. =cut sub SetIncludeContentForValue { shift->IncludeContentForValue(@_); } sub IncludeContentForValue{ my $self = shift; $self->_URLTemplate('IncludeContentForValue', @_); } =head2 LinkValueTo [VALUE] (and SetLinkValueTo) Gets or sets the C<LinkValueTo> for this custom field. RT uses this field to make custom field values into hyperlinks in the user's browser as they display records with custom fields in RT. =cut sub SetLinkValueTo { shift->LinkValueTo(@_); } sub LinkValueTo { my $self = shift; $self->_URLTemplate('LinkValueTo', @_); } =head2 _URLTemplate NAME [VALUE] With one argument, returns the _URLTemplate named C<NAME>, but only if the current user has the right to see this custom field. With two arguments, attemptes to set the relevant template value. =cut sub _URLTemplate { my $self = shift; my $template_name = shift; if (@_) { my $value = shift; unless ( $self->CurrentUserHasRight('AdminCustomField') ) { return ( 0, $self->loc('Permission Denied') ); } if (length $value and defined $value) { $self->SetAttribute( Name => $template_name, Content => $value ); } else { $self->DeleteAttribute( $template_name ); } return ( 1, $self->loc('Updated') ); } else { unless ( $self->id && $self->CurrentUserCanSee ) { return (undef); } my ($attr) = $self->Attributes->Named($template_name); return undef unless $attr; return $attr->Content; } } sub SetBasedOn { my $self = shift; my $value = shift; return $self->_Set( Field => 'BasedOn', Value => $value, @_ ) unless defined $value and length $value; my $cf = RT::CustomField->new( $self->CurrentUser ); $cf->SetContextObject( $self->ContextObject ); $cf->Load( ref $value ? $value->id : $value ); return (0, "Permission Denied") unless $cf->id && $cf->CurrentUserCanSee; # XXX: Remove this restriction once we support lists and cascaded selects if ( $self->RenderType =~ /List/ ) { return (0, $self->loc("We can't currently render as a List when basing categories on another custom field. Please use another render type.")); } return $self->_Set( Field => 'BasedOn', Value => $value, @_ ) } sub BasedOnObj { my $self = shift; my $obj = RT::CustomField->new( $self->CurrentUser ); $obj->SetContextObject( $self->ContextObject ); if ( $self->BasedOn ) { $obj->Load( $self->BasedOn ); } return $obj; } sub SupportDefaultValues { my $self = shift; return 0 unless $self->id; return 0 unless $self->LookupType =~ /RT::(?:Ticket|Transaction|Asset)$/; return $self->Type !~ /^(?:Image|Binary)$/; } sub DefaultValues { my $self = shift; my %args = ( Object => RT->System, @_, ); my $attr = $args{Object}->FirstAttribute('CustomFieldDefaultValues'); my $values; $values = $attr->Content->{$self->id} if $attr && $attr->Content; return $values if defined $values; if ( !$args{Object}->isa( 'RT::System' ) ) { my $system_attr = RT::System->FirstAttribute( 'CustomFieldDefaultValues' ); $values = $system_attr->Content->{$self->id} if $system_attr && $system_attr->Content; return $values if defined $values; } return undef; } sub SetDefaultValues { my $self = shift; my %args = ( Object => RT->System, Values => undef, @_, ); my $attr = $args{Object}->FirstAttribute( 'CustomFieldDefaultValues' ); my ( $old_values, $old_content, $new_values ); if ( $attr && $attr->Content ) { $old_content = $attr->Content; $old_values = $old_content->{ $self->id }; } if ( !$args{Object}->isa( 'RT::System' ) && !defined $old_values ) { my $system_attr = RT::System->FirstAttribute( 'CustomFieldDefaultValues' ); if ( $system_attr && $system_attr->Content ) { $old_values = $system_attr->Content->{ $self->id }; } } if ( defined $old_values && length $old_values ) { $old_values = join ', ', @$old_values if ref $old_values eq 'ARRAY'; } $new_values = $args{Values}; if ( defined $new_values && length $new_values ) { $new_values = join ', ', @$new_values if ref $new_values eq 'ARRAY'; } return 1 if ( $new_values // '' ) eq ( $old_values // '' ); my ($ret, $msg) = $args{Object}->SetAttribute( Name => 'CustomFieldDefaultValues', Content => { %{ $old_content || {} }, $self->id => $args{Values}, }, ); $old_values = $self->loc('(no value)') unless defined $old_values && length $old_values; $new_values = $self->loc( '(no value)' ) unless defined $new_values && length $new_values; if ( $ret ) { return ( $ret, $self->loc( 'Default values changed from [_1] to [_2]', $old_values, $new_values ) ); } else { return ( $ret, $self->loc( "Can't change default values from [_1] to [_2]: [_3]", $old_values, $new_values, $msg ) ); } } sub CleanupDefaultValues { my $self = shift; my $attrs = RT::Attributes->new( $self->CurrentUser ); $attrs->Limit( FIELD => 'Name', VALUE => 'CustomFieldDefaultValues' ); my @values; if ( $self->Type eq 'Select' ) { # Select has a limited list valid values, we need to exclude invalid ones @values = map { $_->Name } @{ $self->Values->ItemsArrayRef || [] }; } while ( my $attr = $attrs->Next ) { my $content = $attr->Content; next unless $content; my $changed; if ( $self->SupportDefaultValues ) { if ( $self->MaxValues == 1 && ref $content->{ $self->id } eq 'ARRAY' ) { $content->{ $self->id } = $content->{ $self->id }[ 0 ]; $changed = 1; } my $default_values = $content->{ $self->id }; if ( $default_values ) { if ( $self->Type eq 'Select' ) { if ( ref $default_values ne 'ARRAY' && $default_values =~ /\n/ ) { # e.g. multiple values Freeform cf has 2 default values: foo and "bar", # the values will be stored as "foo\nbar". so we need to convert it to ARRAY for Select cf. # this could happen when we change a Freeform cf into a Select one $default_values = [ split /\s*\n+\s*/, $default_values ]; $content->{ $self->id } = $default_values; $changed = 1; } if ( ref $default_values eq 'ARRAY' ) { my @new_defaults; for my $default ( @$default_values ) { if ( grep { $_ eq $default } @values ) { push @new_defaults, $default; } else { $changed = 1; } } $content->{ $self->id } = \@new_defaults if $changed; } elsif ( !grep { $_ eq $default_values } @values ) { delete $content->{ $self->id }; $changed = 1; } } else { # ARRAY default values only happen for Select cf. we need to convert it to a scalar for other cfs. # this could happen when we change a Select cf into a Freeform one if ( ref $default_values eq 'ARRAY' ) { $content->{ $self->id } = join "\n", @$default_values; $changed = 1; } if ($self->MaxValues == 1) { my $args = { Content => $default_values }; $self->_CanonicalizeValueWithCanonicalizer($args); if ($args->{Content} ne $default_values) { $content->{ $self->id } = $default_values; $changed = 1; } } else { my @new_values; my $multi_changed = 0; for my $value (split /\s*\n+\s*/, $default_values) { my $args = { Content => $value }; $self->_CanonicalizeValueWithCanonicalizer($args); push @new_values, $args->{Content}; $multi_changed = 1 if $args->{Content} ne $value; } if ($multi_changed) { $content->{ $self->id } = join "\n", @new_values; $changed = 1; } } } } } else { if ( exists $content->{ $self->id } ) { delete $content->{ $self->id }; $changed = 1; } } $attr->SetContent( $content ) if $changed; } } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 Name Returns the current value of Name. (In the database, Name is stored as varchar(200).) =head2 SetName VALUE Set Name to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Name will be stored as a varchar(200).) =cut =head2 Type Returns the current value of Type. (In the database, Type is stored as varchar(200).) =head2 SetType VALUE Set Type to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Type will be stored as a varchar(200).) =cut =head2 RenderType Returns the current value of RenderType. (In the database, RenderType is stored as varchar(64).) =head2 SetRenderType VALUE Set RenderType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, RenderType will be stored as a varchar(64).) =cut =head2 MaxValues Returns the current value of MaxValues. (In the database, MaxValues is stored as int(11).) =head2 SetMaxValues VALUE Set MaxValues to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, MaxValues will be stored as a int(11).) =cut =head2 Pattern Returns the current value of Pattern. (In the database, Pattern is stored as text.) =head2 SetPattern VALUE Set Pattern to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Pattern will be stored as a text.) =cut =head2 BasedOn Returns the current value of BasedOn. (In the database, BasedOn is stored as int(11).) =head2 SetBasedOn VALUE Set BasedOn to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, BasedOn will be stored as a int(11).) =cut =head2 Description Returns the current value of Description. (In the database, Description is stored as varchar(255).) =head2 SetDescription VALUE Set Description to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Description will be stored as a varchar(255).) =cut =head2 SortOrder Returns the current value of SortOrder. (In the database, SortOrder is stored as int(11).) =head2 SetSortOrder VALUE Set SortOrder to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, SortOrder will be stored as a int(11).) =cut =head2 LookupType Returns the current value of LookupType. (In the database, LookupType is stored as varchar(255).) =head2 SetLookupType VALUE Set LookupType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, LookupType will be stored as a varchar(255).) =cut =head2 SetEntryHint VALUE Set EntryHint to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, EntryHint will be stored as a varchar(255).) =cut =head2 Creator Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut =head2 Created Returns the current value of Created. (In the database, Created is stored as datetime.) =cut =head2 LastUpdatedBy Returns the current value of LastUpdatedBy. (In the database, LastUpdatedBy is stored as int(11).) =cut =head2 LastUpdated Returns the current value of LastUpdated. (In the database, LastUpdated is stored as datetime.) =cut =head2 Disabled Returns the current value of Disabled. (In the database, Disabled is stored as smallint(6).) =head2 SetDisabled VALUE Set Disabled to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Disabled will be stored as a smallint(6).) =cut sub _CoreAccessible { { id => {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Name => {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, Type => {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, RenderType => {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''}, MaxValues => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Pattern => {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, ValuesClass => {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''}, BasedOn => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Description => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, SortOrder => {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LookupType => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, EntryHint => {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => undef }, UniqueValues => {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'}, CanonicalizeClass => {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''}, Creator => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, Disabled => {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); $deps->Add( out => $self->BasedOnObj ) if $self->BasedOnObj->id; my $applied = RT::ObjectCustomFields->new( $self->CurrentUser ); $applied->LimitToCustomField( $self->id ); $deps->Add( in => $applied ); $deps->Add( in => $self->Values ) if $self->ValuesClass eq "RT::CustomFieldValues"; } sub __DependsOn { my $self = shift; my %args = ( Shredder => undef, Dependencies => undef, @_, ); my $deps = $args{'Dependencies'}; my $list = []; # Custom field values push( @$list, $self->Values ); # Applications of this CF my $applied = RT::ObjectCustomFields->new( $self->CurrentUser ); $applied->LimitToCustomField( $self->Id ); push @$list, $applied; # Ticket custom field values my $objs = RT::ObjectCustomFieldValues->new( $self->CurrentUser ); $objs->LimitToCustomField( $self->Id ); push( @$list, $objs ); $deps->_PushDependencies( BaseObject => $self, Flags => RT::Shredder::Constants::DEPENDS_ON, TargetObjects => $list, Shredder => $args{'Shredder'} ); return $self->SUPER::__DependsOn( %args ); } =head2 LoadByNameAndCatalog Loads the described asset custom field, if one is found, into the current object. This method only consults custom fields applied to L<RT::Catalog> for L<RT::Asset> objects. Takes a hash with the keys: =over =item Name A L<RT::CustomField> ID or Name which applies to L<assets|RT::Asset>. =item Catalog Optional. An L<RT::Catalog> ID or Name. =back If Catalog is specified, only a custom field added to that Catalog will be loaded. If Catalog is C<0>, only global asset custom fields will be loaded. If no Catalog is specified, all asset custom fields are searched including global and catalog-specific CFs. Please note that this method may load a Disabled custom field if no others matching the same criteria are found. Enabled CFs are preferentially loaded. =cut # To someday be merged into RT::CustomField::LoadByName sub LoadByNameAndCatalog { my $self = shift; my %args = ( Catalog => undef, Name => undef, @_, ); unless ( defined $args{'Name'} && length $args{'Name'} ) { $RT::Logger->error("Couldn't load Custom Field without Name"); return wantarray ? (0, $self->loc("No name provided")) : 0; } # if we're looking for a catalog by name, make it a number if ( defined $args{'Catalog'} && ($args{'Catalog'} =~ /\D/ || !$self->ContextObject) ) { my $CatalogObj = RT::Catalog->new( $self->CurrentUser ); my ($ok, $msg) = $CatalogObj->Load( $args{'Catalog'} ); if ( $ok ){ $args{'Catalog'} = $CatalogObj->Id; } elsif ($args{'Catalog'}) { RT::Logger->error("Unable to load catalog " . $args{'Catalog'} . $msg); return (0, $msg); } $self->SetContextObject( $CatalogObj ) unless $self->ContextObject; } my $CFs = RT::CustomFields->new( $self->CurrentUser ); $CFs->SetContextObject( $self->ContextObject ); my $field = $args{'Name'} =~ /\D/? 'Name' : 'id'; $CFs->Limit( FIELD => $field, VALUE => $args{'Name'}, CASESENSITIVE => 0); # Limit to catalog, if provided. This will also limit to RT::Asset types. $CFs->LimitToCatalog( $args{'Catalog'} ); # When loading by name, we _can_ load disabled fields, but prefer # non-disabled fields. $CFs->FindAllRows; $CFs->OrderByCols( { FIELD => "Disabled", ORDER => 'ASC' }, ); # We only want one entry. $CFs->RowsPerPage(1); return (0, $self->loc("Not found")) unless my $first = $CFs->First; return $self->LoadById( $first->id ); } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/������������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 014733� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Initialdata/������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 016317� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Base.pm�����������������������������������������������������������������������������000644 �000765 �000024 �00000011334 14005011336 015306� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Base; use Carp (); use Scalar::Util (); use strict; use warnings; use vars qw(@EXPORT); @EXPORT=qw(loc CurrentUser); =head1 NAME RT::Base =head1 SYNOPSIS =head1 DESCRIPTION =head1 FUNCTIONS =cut =head2 CurrentUser If called with an argument, sets the current user to that user object. This will affect ACL decisions, etc. The argument can be either L<RT::CurrentUser> or L<RT::User> object. Returns the current user object of L<RT::CurrentUser> class. =cut sub CurrentUser { my $self = shift; if (@_) { $self->{'original_user'} = $self->{'user'}; my $current_user = $_[0]; if ( ref $current_user eq 'RT::User' ) { $self->{'user'} = RT::CurrentUser->new; $self->{'user'}->Load( $current_user->id ); } else { $self->{'user'} = $current_user; } # We need to weaken the CurrentUser ($self->{'user'}) reference # if the object in question is the currentuser object. # This avoids memory leaks. Scalar::Util::weaken($self->{'user'}) if ref $self->{'user'} && $self->{'user'} == $self; } return ( $self->{'user'} ); } sub OriginalUser { my $self = shift; if (@_) { $self->{'original_user'} = shift; Scalar::Util::weaken($self->{'original_user'}) if (ref($self->{'original_user'}) && $self->{'original_user'} == $self ); } return ( $self->{'original_user'} || $self->{'user'} ); } =head2 loc LOC_STRING l is a method which takes a loc string to this object's CurrentUser->LanguageHandle for localization. you call it like this: $self->loc("I have [quant,_1,concrete mixer,concrete mixers].", 6); In english, this would return: I have 6 concrete mixers. =cut sub loc { my $self = shift; if (my $user = $self->OriginalUser) { return $user->loc(@_); } else { Carp::confess("No currentuser"); return ("Critical error:$self has no CurrentUser", $self); } } sub loc_fuzzy { my $self = shift; if (my $user = $self->OriginalUser) { return $user->loc_fuzzy(@_); } else { Carp::confess("No currentuser"); return ("Critical error:$self has no CurrentUser", $self); } } =head2 _ImportOverlays C<_ImportOverlays> is an internal method used to modify or add functionality to existing RT code. For more on how to use overlays with RT, please see the documentation in L<RT::StyleGuide>. =cut sub _ImportOverlays { my $class = shift; my ($package,undef,undef) = caller(); $package =~ s|::|/|g; for my $type (qw(Overlay Vendor Local)) { my $filename = $package."_".$type.".pm"; eval { require $filename }; die $@ if ($@ && $@ !~ m{^Can't locate $filename}); } } __PACKAGE__->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Date.pm�����������������������������������������������������������������������������000644 �000765 �000024 �00000111473 14005011336 015316� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Date - a simple Object Oriented date. =head1 SYNOPSIS use RT::Date =head1 DESCRIPTION RT Date is a simple Date Object designed to be speedy and easy for RT to use. The fact that it assumes that a time of 0 means "never" is probably a bug. =head1 METHODS =cut package RT::Date; use strict; use warnings; use base qw/RT::Base/; use DateTime; use Time::Local; use POSIX qw(tzset); use vars qw($MINUTE $HOUR $DAY $WEEK $MONTH $YEAR); $MINUTE = 60; $HOUR = 60 * $MINUTE; $DAY = 24 * $HOUR; $WEEK = 7 * $DAY; $MONTH = 30.4375 * $DAY; $YEAR = 365.25 * $DAY; our @MONTHS = ( 'Jan', # loc 'Feb', # loc 'Mar', # loc 'Apr', # loc 'May', # loc 'Jun', # loc 'Jul', # loc 'Aug', # loc 'Sep', # loc 'Oct', # loc 'Nov', # loc 'Dec', # loc ); our @DAYS_OF_WEEK = ( 'Sun', # loc 'Mon', # loc 'Tue', # loc 'Wed', # loc 'Thu', # loc 'Fri', # loc 'Sat', # loc ); our @FORMATTERS = ( 'DefaultFormat', # loc 'ISO', # loc 'W3CDTF', # loc 'RFC2822', # loc 'RFC2616', # loc 'iCal', # loc 'LocalizedDateTime', # loc ); =head2 new Object constructor takes one argument C<RT::CurrentUser> object. =cut sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; bless ($self, $class); $self->CurrentUser(@_); $self->Unix(0); return $self; } =head2 Set Takes a param hash with the fields C<Format>, C<Value> and C<Timezone>. If $args->{'Format'} is 'unix', takes the number of seconds since the epoch. If $args->{'Format'} is ISO, tries to parse an ISO date. If $args->{'Format'} is 'unknown', require Time::ParseDate and make it figure things out. This is a heavyweight operation that should never be called from within RT's core. But it's really useful for something like the textbox date entry where we let the user do whatever they want. If $args->{'Value'} is 0, assumes you mean never. =cut sub Set { my $self = shift; my %args = ( Format => 'unix', Value => time, Timezone => 'user', @_ ); return $self->Unix(0) unless $args{'Value'} && $args{'Value'} =~ /\S/; my $format = lc $args{'Format'}; if ( $format eq 'unix' ) { return $self->Unix( $args{'Value'} ); } elsif ( ($format eq 'sql' || $format eq 'iso') && $args{'Value'} =~ /^(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/ ) { local $@; my $u = eval { Time::Local::timegm($6, $5, $4, $3, $2-1, $1) } || 0; $RT::Logger->warning("Invalid date $args{'Value'}: $@") if $@ && !$u; return $self->Unix( $u > 0 ? $u : 0 ); } elsif ( $format =~ /^(sql|datemanip|iso)$/ ) { $args{'Value'} =~ s!/!-!g; if ( ( $args{'Value'} =~ /^(\d{4})?(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/ ) || ( $args{'Value'} =~ /^(\d{4})?(\d\d)(\d\d)(\d\d):(\d\d):(\d\d)$/ ) || ( $args{'Value'} =~ /^(?:(\d{4})-)?(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/ ) || ( $args{'Value'} =~ /^(?:(\d{4})-)?(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)\+00$/ ) ) { my ($year, $mon, $mday, $hours, $min, $sec) = ($1, $2, $3, $4, $5, $6); # use current year if string has no value $year ||= (localtime time)[5] + 1900; #timegm expects month as 0->11 $mon--; #now that we've parsed it, deal with the case where everything was 0 return $self->Unix(0) if $mon < 0 || $mon > 11; my $tz = lc $args{'Format'} eq 'datemanip'? 'user': 'utc'; $self->Unix( $self->Timelocal( $tz, $sec, $min, $hours, $mday, $mon, $year ) ); $self->Unix(0) unless $self->Unix > 0; } else { $RT::Logger->warning( "Couldn't parse date '$args{'Value'}' as a $args{'Format'} format" ); return $self->Unix(0); } } elsif ( $format eq 'unknown' ) { if ( RT->Config->Get('PreferDateTimeFormatNatural') ) { return $self->Unix( $self->ParseByDateTimeFormatNatural(%args) || $self->ParseByTimeParseDate(%args) || 0 ); } else { return $self->Unix( $self->ParseByTimeParseDate(%args) || $self->ParseByDateTimeFormatNatural(%args) || 0 ); } } else { $RT::Logger->error( "Unknown Date format: $args{'Format'}\n" ); return $self->Unix(0); } return $self->Unix; } =head2 ParseByTimeParseDate Parse date using Time::ParseDate. return undef if it fails to parse, otherwise return epoch time. =cut sub ParseByTimeParseDate { my $self = shift; my %args = @_; require Time::ParseDate; # the module supports only legacy timezones like PDT or EST... # so we parse date as GMT and later apply offset, this only # should be applied to absolute times, so compensate shift in NOW my $now = time; $now += ($self->Localtime( $args{Timezone}, $now ))[9]; my ($date, $error) = Time::ParseDate::parsedate( $args{'Value'}, GMT => 1, NOW => $now, UK => RT->Config->Get('DateDayBeforeMonth'), PREFER_PAST => RT->Config->Get('AmbiguousDayInPast'), PREFER_FUTURE => RT->Config->Get('AmbiguousDayInFuture'), ); unless ( defined $date ) { $RT::Logger->warning( "Couldn't parse date '$args{'Value'}' by Time::ParseDate" ); return undef; } # apply timezone offset $date -= ($self->Localtime( $args{Timezone}, $date ))[9]; $RT::Logger->debug( "RT::Date used Time::ParseDate to make '$args{'Value'}' $date\n" ); return $date; } =head2 ParseByDateTimeFormatNatural Parse date using DateTime::Format::Natural. return undef if it fails to parse, otherwise return epoch time. =cut sub ParseByDateTimeFormatNatural { my $self = shift; my %args = @_; require DateTime::Format::Natural; my $parser = DateTime::Format::Natural->new( prefer_future => RT->Config->Get('AmbiguousDayInPast') ? 0 : RT->Config->Get('AmbiguousDayInFuture'), time_zone => $self->Timezone($args{Timezone}), ); my ($dt) = eval { $parser->parse_datetime($args{Value}) }; if ( !$@ && $parser->success && $dt ) { my $date = $dt->epoch; $RT::Logger->debug( "RT::Date used DateTime::Format::Natural to make '$args{'Value'}' $date\n" ); return $date; } else { $RT::Logger->warning( "Couldn't parse date '$args{'Value'}' by DateTime::Format::Natural" ); return undef; } } =head2 SetToNow Set the object's time to the current time. Takes no arguments and returns unix time. =cut sub SetToNow { return $_[0]->Unix(time); } =head2 SetToMidnight [Timezone => 'utc'] Sets the date to midnight (at the beginning of the day). Returns the unixtime at midnight. Arguments: =over 4 =item Timezone Timezone context C<user>, C<server> or C<UTC>. See also L</Timezone>. =back =cut sub SetToMidnight { my $self = shift; my %args = ( Timezone => '', @_ ); my $new = $self->Timelocal( $args{'Timezone'}, 0,0,0,($self->Localtime( $args{'Timezone'} ))[3..9] ); return $self->Unix( $new ); } =head2 Diff Takes either an C<RT::Date> object or the date in unixtime format as a string, if nothing is specified uses the current time. Returns the differnce between the time in the current object and that time as a number of seconds. Returns C<undef> if any of two compared values is incorrect or not set. =cut sub Diff { my $self = shift; my $other = shift; $other = time unless defined $other; if ( UNIVERSAL::isa( $other, 'RT::Date' ) ) { $other = $other->Unix; } return undef unless $other=~ /^\d+$/ && $other > 0; my $unix = $self->Unix; return undef unless $unix > 0; return $unix - $other; } =head2 DiffAsString Takes either an C<RT::Date> object or the date in unixtime format as a string, if nothing is specified uses the current time. Returns the differnce between C<$self> and that time as a number of seconds as a localized string fit for human consumption. Returns empty string if any of two compared values is incorrect or not set. =cut sub DiffAsString { my $self = shift; my $diff = $self->Diff( @_ ); return '' unless defined $diff; return $self->DurationAsString( $diff ); } =head2 DurationAsString Takes a number of seconds. Returns a localized string describing that duration. Takes optional named arguments: =over 4 =item * Show How many elements to show, how precise it should be. Default is 1, most vague variant. =item * Short Turn on short notation with one character units, for example "3M 2d 1m 10s". =item * MinUnit The min unit to use. Default is 'second'. =item * MaxUnit The max unit to use. Default is 'year'. =item * Unit The unit to use. This implies showing "< ..." or "> ..." when the value exceeds boundary. E.g. when it's set to "hour", 24*3600*2 seconds will show as "> 24 hours" instead of "2 days" or "48 hours". =back =cut sub DurationAsString { my $self = shift; my $duration = int shift; my %args = ( Show => 1, Short => 0, MinUnit => 'second', MaxUnit => 'year', @_ ); unless ( $duration || $args{Unit} ) { return $args{Short}? $self->loc("0s") : $self->loc("0 seconds"); } my $negative; $negative = 1 if $duration < 0; $duration = abs $duration; my @res; my $coef = 2; my $i = 0; my %skip; my @units = qw/second minute hour day week month year/; for my $unit (@units) { last if $args{MinUnit} =~ /$unit/i; $skip{$unit} = 1; } if ( $args{Unit} ) { my ( $locstr, $unit ); if ( $args{Unit} eq 'minute' ) { if ( $duration < $MINUTE ) { if ( $args{Short} ) { $locstr = '< [_1]m'; # loc } else { $locstr = '< [_1] minute'; # loc } $unit = 1; } elsif ( $duration > $coef * $HOUR ) { if ( $args{Short} ) { $locstr = '> [_1]m'; # loc } else { $locstr = '> [_1] minutes'; # loc } $unit = $coef * 60; } } elsif ( $args{Unit} eq 'hour' ) { if ( $duration < $HOUR ) { if ( $args{Short} ) { $locstr = '< [_1]h'; # loc } else { $locstr = '< [_1] hour'; # loc } $unit = 1; } elsif ( $duration > $coef * $DAY ) { if ( $args{Short} ) { $locstr = '> [_1]h'; # loc } else { $locstr = '> [_1] hours'; # loc } $unit = $coef * 24; } } elsif ( $args{Unit} eq 'day' ) { if ( $duration < $DAY ) { if ( $args{Short} ) { $locstr = '< [_1]d'; # loc } else { $locstr = '< [_1] day'; # loc } $unit = 1; } elsif ( $duration > $coef * $WEEK ) { if ( $args{Short} ) { $locstr = '> [_1]d'; # loc } else { $locstr = '> [_1] days'; # loc } $unit = $coef * 7; } } elsif ( $args{Unit} eq 'week' ) { if ( $duration < $WEEK ) { if ( $args{Short} ) { $locstr = '< [_1]W'; # loc } else { $locstr = '< [_1] week'; # loc } $unit = 1; } elsif ( $duration > $coef * 4 * $WEEK ) { if ( $args{Short} ) { $locstr = '> [_1]W'; # loc } else { $locstr = '> [_1] weeks'; # loc } $unit = $coef * 4; } } elsif ( $args{Unit} eq 'month' ) { if ( $duration < $MONTH ) { if ( $args{Short} ) { $locstr = '< [_1]M'; # loc } else { $locstr = '< [_1] month'; # loc } $unit = 1; } elsif ( $duration > $coef * 12 * $MONTH ) { if ( $args{Short} ) { $locstr = '> [_1]M'; # loc } else { $locstr = '> [_1] months'; # loc } $unit = $coef * 12; } } elsif ( $args{Unit} eq 'year' ) { if ( $duration < $YEAR ) { if ( $args{Short} ) { $locstr = '< [_1]Y'; # loc } else { $locstr = '< [_1] years'; # loc } $unit = 1; } elsif ( $duration > $coef * 100 * $YEAR ) { if ( $args{Short} ) { $locstr = '> [_1]Y'; # loc } else { $locstr = '> [_1] years'; # loc } $unit = $coef * 100; } } if ($locstr) { push @res, $self->loc($locstr, $unit); $duration = 0; } } while ( $duration > 0 && ++$i <= $args{'Show'} ) { my ($locstr, $unit); if ( !$skip{second} && ( $duration < $MINUTE || $args{MaxUnit} =~ /second/i ) ) { $locstr = $args{Short} ? '[_1]s' # loc : '[quant,_1,second,seconds]'; # loc $unit = 1; } elsif ( !$skip{minute} && ( $duration < ( $coef * $HOUR ) || $args{MaxUnit} =~ /minute/i ) ) { $locstr = $args{Short} ? '[_1]m' # loc : '[quant,_1,minute,minutes]'; # loc $unit = $MINUTE; } elsif ( !$skip{hour} && ( $duration < ( $coef * $DAY ) || $args{MaxUnit} =~ /hour/i) ) { $locstr = $args{Short} ? '[_1]h' # loc : '[quant,_1,hour,hours]'; # loc $unit = $HOUR; } elsif ( !$skip{day} && ( $duration < ( $coef * $WEEK ) || $args{MaxUnit} =~ /day/i ) ) { $locstr = $args{Short} ? '[_1]d' # loc : '[quant,_1,day,days]'; # loc $unit = $DAY; } elsif ( !$skip{week} && ( $duration < ( $coef * $MONTH ) || $args{MaxUnit} =~ /week/i) ) { $locstr = $args{Short} ? '[_1]W' # loc : '[quant,_1,week,weeks]'; # loc $unit = $WEEK; } elsif ( !$skip{month} && ( $duration < $YEAR || $args{MaxUnit} =~ /month/i ) ) { $locstr = $args{Short} ? '[_1]M' # loc : '[quant,_1,month,months]'; # loc $unit = $MONTH; } else { $locstr = $args{Short} ? '[_1]Y' # loc : '[quant,_1,year,years]'; # loc $unit = $YEAR; } my $value = int( $duration / $unit + ($i < $args{'Show'}? 0 : 0.5) ); $duration -= int( $value * $unit ); push @res, $self->loc($locstr, $value); $coef = 1; } if ( $negative ) { return $self->loc( "[_1] ago", join ' ', @res ); } else { return join ' ', @res; } } =head2 AgeAsString Takes nothing. Returns a string that's the difference between the time in the object and now. =cut sub AgeAsString { return $_[0]->DiffAsString } =head2 AsString Returns the object's time as a localized string with curent user's preferred format and timezone. If the current user didn't choose preferred format then system wide setting is used or L</DefaultFormat> if the latter is not specified. See config option C<DateTimeFormat>. =cut sub AsString { my $self = shift; my %args = (@_); return $self->loc("Not set") unless $self->IsSet; my $format = RT->Config->Get( 'DateTimeFormat', $self->CurrentUser ) || 'DefaultFormat'; $format = { Format => $format } unless ref $format; %args = (%$format, %args); return $self->Get( Timezone => 'user', %args ); } =head2 GetWeekday DAY Takes an integer day of week and returns a localized string for that day of week. Valid values are from range 0-6, Note that B<0 is sunday>. =cut sub GetWeekday { my $self = shift; my $dow = shift; return $self->loc($DAYS_OF_WEEK[$dow]) if $DAYS_OF_WEEK[$dow]; return ''; } =head2 GetMonth MONTH Takes an integer month and returns a localized string for that month. Valid values are from from range 0-11. =cut sub GetMonth { my $self = shift; my $mon = shift; return $self->loc($MONTHS[$mon]) if $MONTHS[$mon]; return ''; } =head2 AddSeconds SECONDS Takes a number of seconds and returns the new unix time. Negative value can be used to substract seconds. =cut sub AddSeconds { my $self = shift; my $delta = shift or return $self->Unix; $self->Set(Format => 'unix', Value => ($self->Unix + $delta)); return ($self->Unix); } =head2 AddDays [DAYS] Adds C<24 hours * DAYS> to the current time. Adds one day when no argument is specified. Negative value can be used to substract days. Returns new unix time. =cut sub AddDays { my $self = shift; my $days = shift; $days = 1 unless defined $days; return $self->AddSeconds( $days * $DAY ); } =head2 AddDay Adds 24 hours to the current time. Returns new unix time. =cut sub AddDay { return $_[0]->AddSeconds($DAY) } =head2 Unix [unixtime] Optionally takes a date in unix seconds since the epoch format. Returns the number of seconds since the epoch =cut sub Unix { my $self = shift; if (@_) { my $time = int(shift || 0); if ($time < 0) { RT->Logger->notice("Passed a unix time less than 0, forcing to 0: [$time]"); $time = 0; } $self->{'time'} = int $time; } return $self->{'time'}; } =head2 DateTime Alias for L</Get> method. Arguments C<Date> and C<Time> are fixed to true values, other arguments could be used as described in L</Get>. =cut sub DateTime { my $self = shift; unless (defined $self) { use Carp; Carp::confess("undefined $self"); } return $self->Get( @_, Date => 1, Time => 1 ); } =head2 Date Takes Format argument which allows you choose date formatter. Pass throught other arguments to the formatter method. Returns the object's formatted date. Default formatter is ISO. =cut sub Date { my $self = shift; return $self->Get( @_, Date => 1, Time => 0 ); } =head2 Time =cut sub Time { my $self = shift; return $self->Get( @_, Date => 0, Time => 1 ); } =head2 Get Returns a formatted and localized string that represents the time of the current object. =cut sub Get { my $self = shift; my %args = (Format => 'ISO', @_); my $formatter = $args{'Format'}; unless ( $self->ValidFormatter($formatter) ) { RT->Logger->warning("Invalid date formatter '$formatter', falling back to ISO"); $formatter = 'ISO'; } $formatter = 'ISO' unless $self->can($formatter); return $self->$formatter( %args ); } =head2 Output formatters Fomatter is a method that returns date and time in different configurable format. Each method takes several arguments: =over 1 =item Date =item Time =item Timezone - Timezone context C<server>, C<user> or C<UTC> =back Formatters may also add own arguments to the list, for example in RFC2822 format day of time in output is optional so it understands boolean argument C<DayOfTime>. =head3 Formatters Returns an array of available formatters. =cut sub Formatters { my $self = shift; return @FORMATTERS; } =head3 ValidFormatter FORMAT Returns a true value if C<FORMAT> is a known formatter. Otherwise returns false. =cut sub ValidFormatter { my $self = shift; my $format = shift; return (grep { $_ eq $format } $self->Formatters and $self->can($format)) ? 1 : 0; } =head3 DefaultFormat =cut sub DefaultFormat { my $self = shift; my %args = ( Date => 1, Time => 1, Timezone => '', Seconds => 1, @_, ); # 0 1 2 3 4 5 6 7 8 9 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$ydaym,$isdst,$offset) = $self->Localtime($args{'Timezone'}); $wday = $self->GetWeekday($wday); $mon = $self->GetMonth($mon); $_ = sprintf "%02d", $_ foreach $mday, $hour, $min, $sec; if( $args{'Date'} && !$args{'Time'} ) { return $self->loc('[_1] [_2] [_3] [_4]', $wday,$mon,$mday,$year); } elsif( !$args{'Date'} && $args{'Time'} ) { if( $args{'Seconds'} ) { return $self->loc('[_1]:[_2]:[_3]', $hour,$min,$sec); } else { return $self->loc('[_1]:[_2]', $hour,$min); } } else { if( $args{'Seconds'} ) { return $self->loc('[_1] [_2] [_3] [_4]:[_5]:[_6] [_7]', $wday,$mon,$mday,$hour,$min,$sec,$year); } else { return $self->loc('[_1] [_2] [_3] [_4]:[_5] [_6]', $wday,$mon,$mday,$hour,$min,$year); } } } =head2 LocaleObj Returns the L<DateTime::Locale> object representing the current user's locale. =cut sub LocaleObj { my $self = shift; my $lang = $self->CurrentUser->UserObj->Lang; unless ($lang) { require I18N::LangTags::Detect; $lang = ( I18N::LangTags::Detect::detect(), 'en' )[0]; } return DateTime::Locale->load($lang); } =head3 LocalizedDateTime Returns date and time as string, with user localization. Supports arguments: C<DateFormat> and C<TimeFormat> which may contains date and time format as specified in L<DateTime::Locale> (default to C<date_format_full> and C<time_format_medium>), C<AbbrDay> and C<AbbrMonth> which may be set to 0 if you want full Day/Month names instead of abbreviated ones. =cut sub LocalizedDateTime { my $self = shift; my %args = ( Date => 1, Time => 1, Timezone => '', DateFormat => '', TimeFormat => '', AbbrDay => 1, AbbrMonth => 1, @_, ); my $dt = $self->DateTimeObj(%args); # Require valid names for the format methods my $date_format = $args{DateFormat} =~ /^\w+$/ ? $args{DateFormat} : 'date_format_full'; my $time_format = $args{TimeFormat} =~ /^\w+$/ ? $args{TimeFormat} : 'time_format_medium'; my $formatter = $self->LocaleObj; $date_format = $formatter->$date_format; $time_format = $formatter->$time_format; $date_format =~ s/EEEE/EEE/g if ( $args{'AbbrDay'} ); $date_format =~ s/MMMM/MMM/g if ( $args{'AbbrMonth'} ); if ( $args{'Date'} && !$args{'Time'} ) { return $dt->format_cldr($date_format); } elsif ( !$args{'Date'} && $args{'Time'} ) { return $dt->format_cldr($time_format); } else { return $dt->format_cldr($date_format) . " " . $dt->format_cldr($time_format); } } =head3 ISO Returns the object's date in ISO format C<YYYY-MM-DD mm:hh:ss>. ISO format is locale-independent, but adding timezone offset info is not implemented yet. Supports arguments: C<Timezone>, C<Date>, C<Time> and C<Seconds>. See L</Output formatters> for description of arguments. =cut sub ISO { my $self = shift; my %args = ( Date => 1, Time => 1, Timezone => '', Seconds => 1, @_, ); # 0 1 2 3 4 5 6 7 8 9 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$ydaym,$isdst,$offset) = $self->Localtime($args{'Timezone'}); #the month needs incrementing, as gmtime returns 0-11 $mon++; my $res = ''; $res .= sprintf("%04d-%02d-%02d", $year, $mon, $mday) if $args{'Date'}; $res .= sprintf(' %02d:%02d', $hour, $min) if $args{'Time'}; $res .= sprintf(':%02d', $sec) if $args{'Time'} && $args{'Seconds'}; $res =~ s/^\s+//; return $res; } =head3 W3CDTF Returns the object's date and time in W3C date time format (L<http://www.w3.org/TR/NOTE-datetime>). Format is locale-independent and is close enough to ISO, but note that date part is B<not optional> and output string has timezone offset mark in C<[+-]hh:mm> format. Supports arguments: C<Timezone>, C<Time> and C<Seconds>. See L</Output formatters> for description of arguments. =cut sub W3CDTF { my $self = shift; my %args = ( Time => 1, Timezone => '', Seconds => 1, @_, Date => 1, ); # 0 1 2 3 4 5 6 7 8 9 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$ydaym,$isdst,$offset) = $self->Localtime( $args{'Timezone'} ); #the month needs incrementing, as gmtime returns 0-11 $mon++; my $res = ''; $res .= sprintf("%04d-%02d-%02d", $year, $mon, $mday); if ( $args{'Time'} ) { $res .= sprintf('T%02d:%02d', $hour, $min); $res .= sprintf(':%02d', $sec) if $args{'Seconds'}; if ( $offset ) { $res .= sprintf "%s%02d:%02d", $self->_SplitOffset( $offset ); } else { $res .= 'Z'; } } return $res; }; =head3 RFC2822 (MIME) Returns the object's date and time in RFC2822 format, for example C<Sun, 06 Nov 1994 08:49:37 +0000>. Format is locale-independent as required by RFC. Time part always has timezone offset in digits with sign prefix. Supports arguments: C<Timezone>, C<Date>, C<Time>, C<DayOfWeek> and C<Seconds>. See L</Output formatters> for description of arguments. =cut sub RFC2822 { my $self = shift; my %args = ( Date => 1, Time => 1, Timezone => '', DayOfWeek => 1, Seconds => 1, @_, ); # 0 1 2 3 4 5 6 7 8 9 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$ydaym,$isdst,$offset) = $self->Localtime($args{'Timezone'}); my ($date, $time) = ('',''); $date .= "$DAYS_OF_WEEK[$wday], " if $args{'DayOfWeek'} && $args{'Date'}; $date .= sprintf("%02d %s %04d", $mday, $MONTHS[$mon], $year) if $args{'Date'}; if ( $args{'Time'} ) { $time .= sprintf("%02d:%02d", $hour, $min); $time .= sprintf(":%02d", $sec) if $args{'Seconds'}; $time .= sprintf " %s%02d%02d", $self->_SplitOffset( $offset ); } return join ' ', grep $_, ($date, $time); } =head3 RFC2616 (HTTP) Returns the object's date and time in RFC2616 (HTTP/1.1) format, for example C<Sun, 06 Nov 1994 08:49:37 GMT>. While the RFC describes version 1.1 of HTTP, but the same form date can be used in version 1.0. Format is fixed-length, locale-independent and always represented in GMT which makes it quite useless for users, but any date in HTTP transfers must be presented using this format. HTTP-date = rfc1123 | ... rfc1123 = wkday "," SP date SP time SP "GMT" date = 2DIGIT SP month SP 4DIGIT ; day month year (e.g., 02 Jun 1982) time = 2DIGIT ":" 2DIGIT ":" 2DIGIT ; 00:00:00 - 23:59:59 wkday = "Mon" | "Tue" | "Wed" | "Thu" | "Fri" | "Sat" | "Sun" month = "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" | "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec" Supports arguments: C<Date> and C<Time>, but you should use them only for some personal reasons, RFC2616 doesn't define any optional parts. See L</Output formatters> for description of arguments. =cut sub RFC2616 { my $self = shift; my %args = ( Date => 1, Time => 1, @_, Timezone => 'utc', Seconds => 1, DayOfWeek => 1, ); my $res = $self->RFC2822( %args ); $res =~ s/\s*[+-]\d\d\d\d$/ GMT/ if $args{'Time'}; return $res; } =head4 iCal Returns the object's date and time in iCalendar format. If only date requested then user's timezone is used, otherwise it's UTC. Supports arguments: C<Date> and C<Time>. See L</Output formatters> for description of arguments. =cut sub iCal { my $self = shift; my %args = ( Date => 1, Time => 1, @_, ); my $res; if ( $args{'Date'} && !$args{'Time'} ) { my (undef, undef, undef, $mday, $mon, $year) = $self->Localtime( 'user' ); $res = sprintf( '%04d%02d%02d', $year, $mon+1, $mday ); } elsif ( !$args{'Date'} && $args{'Time'} ) { my ($sec, $min, $hour) = $self->Localtime( 'utc' ); $res = sprintf( 'T%02d%02d%02dZ', $hour, $min, $sec ); } else { my ($sec, $min, $hour, $mday, $mon, $year) = $self->Localtime( 'utc' ); $res = sprintf( '%04d%02d%02dT%02d%02d%02dZ', $year, $mon+1, $mday, $hour, $min, $sec ); } return $res; } # it's been added by mistake in 3.8.0 sub iCalDate { return (shift)->iCal( Time => 0, @_ ) } sub _SplitOffset { my ($self, $offset) = @_; my $sign = $offset < 0? '-': '+'; $offset = int( (abs $offset) / 60 + 0.001 ); my $mins = $offset % 60; my $hours = int( $offset/60 + 0.001 ); return $sign, $hours, $mins; } =head2 Timezones handling =head3 Localtime $context [$time] Takes one mandatory argument C<$context>, which determines whether we want "user local", "system" or "UTC" time. Also, takes optional argument unix C<$time>, default value is the current unix time. Returns object's date and time in the format provided by perl's builtin functions C<localtime> and C<gmtime> with two exceptions: =over =item 1) "Year" is a four-digit year, rather than "years since 1900" =item 2) The last element of the array returned is C<offset>, which represents timezone offset against C<UTC> in seconds. =back =cut sub Localtime { my $self = shift; my $tz = $self->Timezone(shift); my $unix = shift || $self->Unix; $unix = 0 unless $unix >= 0; my @local; if ($tz eq 'UTC') { @local = gmtime($unix); } else { { local $ENV{'TZ'} = $tz; ## Using POSIX::tzset fixes a bug where the TZ environment variable ## is cached. POSIX::tzset(); @local = localtime($unix); } POSIX::tzset(); # return back previous value } $local[5] += 1900; # change year to 4+ digits format my $offset = Time::Local::timegm_nocheck(@local) - $unix; return @local, $offset; } =head3 Timelocal $context @time Takes argument C<$context>, which determines whether we should treat C<@time> as "user local", "system" or "UTC" time. C<@time> is array returned by L</Localtime> functions. Only first six elements are mandatory - $sec, $min, $hour, $mday, $mon and $year. You may pass $wday, $yday and $isdst, these are ignored. If you pass C<$offset> as ninth argument, it's used instead of C<$context>. It's done such way as code C<< $self->Timelocal('utc', $self->Localtime('server')) >> doesn't make much sense and most probably would produce unexpected results, so the method ignores 'utc' context and uses the offset returned by the L</Localtime> method. =cut sub Timelocal { my $self = shift; my $tz = shift; if ( defined $_[9] ) { return timegm(@_[0..5]) - $_[9]; } else { $tz = $self->Timezone( $tz ); if ( $tz eq 'UTC' ) { return Time::Local::timegm(@_[0..5]); } else { my $rv; { local $ENV{'TZ'} = $tz; ## Using POSIX::tzset fixes a bug where the TZ environment variable ## is cached. POSIX::tzset(); $rv = Time::Local::timelocal(@_[0..5]); }; POSIX::tzset(); # switch back to previouse value return $rv; } } } =head3 Timezone $context Returns the timezone name for the specified context. C<$context> should be one of these values: =over =item C<user> The current user's Timezone value will be returned. =item C<server> The value of the C<Timezone> RT config option will be returned. =back For any other value of C<$context>, or if the specified context has no defined timezone, C<UTC> is returned. =cut sub Timezone { my $self = shift; if (@_ == 0) { Carp::carp 'RT::Date->Timezone requires a context argument'; return undef; } my $context = lc(shift); my $tz; if( $context eq 'user' ) { $tz = $self->CurrentUser->UserObj->Timezone; } elsif( $context eq 'server') { $tz = RT->Config->Get('Timezone'); } else { $tz = 'UTC'; } $tz ||= RT->Config->Get('Timezone') || 'UTC'; $tz = 'UTC' if lc $tz eq 'gmt'; return $tz; } =head3 IsSet Returns true if this Date is set in the database, otherwise returns a false value. This avoids needing to compare to 1970-01-01 in any of your code. =cut sub IsSet { my $self = shift; return $self->Unix ? 1 : 0; } =head3 DateTimeObj [Timezone => 'utc'] Returns an L<DateTime> object representing the same time as this RT::Date. The DateTime object's locale is set up to match the user's language. Modifying this DateTime object will not change the corresponding RT::Date, and vice versa. =cut sub DateTimeObj { my $self = shift; my %args = ( Timezone => '', @_, ); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$ydaym,$isdst,$offset) = $self->Localtime($args{'Timezone'}); $mon++; return DateTime::->new( locale => $self->LocaleObj, time_zone => $self->Timezone($args{'Timezone'}), year => $year, month => $mon, day => $mday, hour => $hour, minute => $min, second => $sec, nanosecond => 0, ); } =head3 Strftime FORMAT, [Timezone => 'user'] Stringify the RT::Date according to the specified format. See L<DateTime/strftime Patterns>. =cut sub Strftime { my $self = shift; my $format = shift; my %args = ( Timezone => 'user', @_, ); my $dt = $self->DateTimeObj(%args); return $dt->strftime($format); } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Migrate.pm��������������������������������������������������������������������������000644 �000765 �000024 �00000013401 14005011336 016021� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Migrate; use strict; use warnings; use Time::HiRes qw//; sub format_time { my $time = shift; my $s = ""; $s .= int($time/60/60)."hr " if $time > 60*60; $s .= int(($time % (60*60))/60)."min " if $time > 60; $s .= int($time % 60)."s" if $time < 60*60; return $s; } sub progress_bar { my %args = ( label => "", now => 0, max => 1, cols => 80, char => "=", @_, ); $args{now} ||= 0; my $fraction = $args{max} ? $args{now} / $args{max} : 0; my $max_width = $args{cols} - 35; my $bar_width = int($max_width * $fraction); return sprintf "%25s |%-" . $max_width . "s| %3d%%\n", $args{label}, $args{char} x $bar_width, $fraction*100; } sub progress { my %args = ( top => sub { print "\n\n" }, bottom => sub {}, every => 3, bars => [qw/Ticket Asset Transaction Attachment User Group/], counts => sub {}, max => {}, @_, ); my $max_objects = 0; $max_objects += $_ for values %{ $args{max} }; my $last_time; my $start; my $left; my $offset; return sub { my $obj = shift; my $force = shift; my $now = Time::HiRes::time(); return if defined $last_time and $now - $last_time <= $args{every} and not $force; $start = $now unless $start; $last_time = $now; my $elapsed = $now - $start; # Determine terminal size print `clear`; my ($cols, $rows) = (80, 25); eval { require Term::ReadKey; ($cols, $rows) = Term::ReadKey::GetTerminalSize(); }; $cols -= 1; $args{top}->($elapsed, $rows, $cols); my %counts = $args{counts}->(); for my $class (map {"RT::$_"} @{$args{bars}}) { my $display; if ( $class eq 'RT::ACE' ) { $display = 'ACL:'; } else { $display = $class; my $suffix = UNIVERSAL::can( $class . 'es', 'new' ) ? 'es' : 's'; $display =~ s/^RT::(.*)/@{[$1]}$suffix:/; } print progress_bar( label => $display, now => $counts{$class}, max => $args{max}{$class}, cols => $cols, ); } my $total = 0; $total += $_ for map {$counts{$_}} grep {exists $args{max}{$_}} keys %counts; $offset = $total unless defined $offset; print "\n", progress_bar( label => "Total", now => $total, max => $max_objects, cols => $cols, char => "#", ); # Time estimates my $fraction = $max_objects ? ($total - $offset)/($max_objects - $offset) : 0; if ($fraction > 0.03) { if (defined $left) { $left = 0.75 * $left + 0.25 * ($elapsed / $fraction - $elapsed); } else { $left = ($elapsed / $fraction - $elapsed); } } print "\n"; printf "%25s %s\n", "Elapsed time:", format_time($elapsed); printf "%25s %s\n", "Estimated left:", (defined $left) ? format_time($left) : "-"; $args{bottom}->($elapsed, $rows, $cols); } } sub setup_logging { my ($dir, $file) = @_; RT->Config->Set(LogToSTDERR => 'warning'); RT->Config->Set(LogToFile => 'warning'); RT->Config->Set(LogDir => $dir); RT->Config->Set(LogToFileNamed => $file); RT->Config->Set(LogStackTraces => 'error'); undef $RT::Logger; RT->InitLogging(); my $logger = $RT::Logger->output('file') || $RT::Logger->output("rtlog"); return $logger ? $logger->{filename} : undef; } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Approval/���������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 015660� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/AuthTokens.pm�����������������������������������������������������������������������000644 �000765 �000024 �00000005147 14005011336 016526� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::AuthTokens; use base 'RT::SearchBuilder'; =head1 NAME RT::AuthTokens - a collection of L<RT::AuthToken> objects =cut =head2 LimitOwner Limit Owner =cut sub LimitOwner { my $self = shift; my %args = ( FIELD => 'Owner', OPERATOR => '=', @_ ); $self->SUPER::Limit(%args); } sub NewItem { my $self = shift; return RT::AuthToken->new( $self->CurrentUser ); } =head2 _Init Sets default ordering by id ascending. =cut sub _Init { my $self = shift; $self->OrderBy( FIELD => 'id', ORDER => 'ASC' ); return $self->SUPER::_Init( @_ ); } sub Table { "AuthTokens" } 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ObjectTopic.pm����������������������������������������������������������������������000644 �000765 �000024 �00000012034 14005011336 016637� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::ObjectTopic =head1 SYNOPSIS =head1 DESCRIPTION =head1 METHODS =cut package RT::ObjectTopic; use strict; use warnings; no warnings 'redefine'; use base qw( RT::Record ); use RT::Topic; sub _Init { my $self = shift; $self->Table('ObjectTopics'); $self->SUPER::_Init(@_); } =head2 Create PARAMHASH Create takes a hash of values and creates a row in the database: int(11) 'Topic'. varchar(64) 'ObjectType'. int(11) 'ObjectId'. =cut sub Create { my $self = shift; my %args = ( Topic => '0', ObjectType => '', ObjectId => '0', @_); $self->SUPER::Create( Topic => $args{'Topic'}, ObjectType => $args{'ObjectType'}, ObjectId => $args{'ObjectId'}, ); } =head2 id Returns the current value of id. (In the database, id is stored as int(11).) =cut =head2 Topic Returns the current value of Topic. (In the database, Topic is stored as int(11).) =head2 SetTopic VALUE Set Topic to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, Topic will be stored as a int(11).) =cut =head2 TopicObj Returns the Topic Object which has the id returned by Topic =cut sub TopicObj { my $self = shift; my $Topic = RT::Topic->new($self->CurrentUser); $Topic->Load($self->Topic()); return($Topic); } =head2 ObjectType Returns the current value of ObjectType. (In the database, ObjectType is stored as varchar(64).) =head2 SetObjectType VALUE Set ObjectType to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectType will be stored as a varchar(64).) =cut =head2 ObjectId Returns the current value of ObjectId. (In the database, ObjectId is stored as int(11).) =head2 SetObjectId VALUE Set ObjectId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, ObjectId will be stored as a int(11).) =cut sub _CoreAccessible { { id => {read => 1, type => 'int(11)', default => ''}, Topic => {read => 1, write => 1, type => 'int(11)', default => '0'}, ObjectType => {read => 1, write => 1, type => 'varchar(64)', default => ''}, ObjectId => {read => 1, write => 1, type => 'int(11)', default => '0'}, } }; sub FindDependencies { my $self = shift; my ($walker, $deps) = @_; $self->SUPER::FindDependencies($walker, $deps); $deps->Add( out => $self->TopicObj ); my $obj = $self->ObjectType->new( $self->CurrentUser ); $obj->Load( $self->ObjectId ); $deps->Add( out => $obj ); } sub Serialize { my $self = shift; my %args = (@_); my %store = $self->SUPER::Serialize(@_); if ($store{ObjectId}) { my $obj = $self->ObjectType->new( RT->SystemUser ); $obj->Load( $store{ObjectId} ); $store{ObjectId} = \($obj->UID); } return %store; } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Approval/Rule.pm��������������������������������������������������������������������000644 �000765 �000024 �00000005066 14005011336 017134� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Approval::Rule; use strict; use warnings; use base 'RT::Rule'; use constant _Queue => '___Approvals'; sub Prepare { my $self = shift; return unless $self->SUPER::Prepare(); if (($self->TicketObj->__Value('Type')||'') eq 'approval') { return 1; } else { return undef } } sub GetTemplate { my ($self, $template_name, %args) = @_; my $template = RT::Template->new($self->CurrentUser); $template->Load($template_name) or return; my ($result, $msg) = $template->Parse(%args); # XXX: error handling return $template; } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Approval/Rule/����������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 016567� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Approval/Rule/Rejected.pm�����������������������������������������������������������000644 �000765 �000024 �00000010250 14005011336 020650� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Approval::Rule::Rejected; use strict; use warnings; use base 'RT::Approval::Rule'; use constant Description => "If an approval is rejected, reject the original and delete pending approvals"; # loc sub Prepare { my $self = shift; return unless $self->SUPER::Prepare(); return (0) unless $self->OnStatusChange('rejected') or $self->OnStatusChange('deleted') } sub Commit { # XXX: from custom prepare code my $self = shift; if ( my ($rejected) = $self->TicketObj->AllDependedOnBy( Type => 'ticket' ) ) { my $note = $self->GetNotes; my $template = $self->GetTemplate('Approval Rejected', TicketObj => $rejected, Approval => $self->TicketObj, Approver => $self->TransactionObj->CreatorObj, Notes => $note); $rejected->Correspond( MIMEObj => $template->MIMEObj ); $rejected->SetStatus( Status => $rejected->LifecycleObj->DefaultStatus('denied') || 'rejected', Force => 1, ); } my $links = $self->TicketObj->DependedOnBy; foreach my $link ( @{ $links->ItemsArrayRef } ) { my $obj = $link->BaseObj; if ( $obj->QueueObj->IsActiveStatus( $obj->Status ) ) { if ( $obj->Type eq 'approval' ) { $obj->SetStatus( Status => 'deleted', Force => 1, ); } } } $links = $self->TicketObj->DependsOn; foreach my $link ( @{ $links->ItemsArrayRef } ) { my $obj = $link->TargetObj; if ( $obj->QueueObj->IsActiveStatus( $obj->Status ) ) { $obj->SetStatus( Status => 'deleted', Force => 1, ); } } } sub GetNotes { my $self = shift; my $note = ''; if ( RT->Config->Get('ApprovalRejectionNotes') ) { my $t = $self->TicketObj->Transactions; while ( my $o = $t->Next ) { next unless $o->Type eq 'Correspond'; $note .= $o->Content . "\n" if $o->ContentObj; } } return $note; } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Approval/Rule/Created.pm������������������������������������������������������������000644 �000765 �000024 �00000004741 14005011336 020502� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Approval::Rule::Created; use strict; use warnings; use base 'RT::Approval::Rule'; use constant _Stage => 'TransactionBatch'; use constant Description => 'Change Approval ticket to open status'; #loc sub Prepare { my $self = shift; return unless $self->SUPER::Prepare(); $self->TransactionObj->Type eq 'Create' && !$self->TicketObj->HasUnresolvedDependencies( Type => 'approval' ); } sub Commit { my $self = shift; $self->RunScripAction('Open Tickets' => 'Blank'); } RT::Base->_ImportOverlays(); 1; �������������������������������rt-5.0.1/lib/RT/Approval/Rule/Passed.pm�������������������������������������������������������������000644 �000765 �000024 �00000007745 14005011336 020361� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Approval::Rule::Passed; use strict; use warnings; use base 'RT::Approval::Rule'; use constant Description => "Notify Owner of their ticket has been approved by some or all approvers"; # loc sub Prepare { my $self = shift; return unless $self->SUPER::Prepare(); $self->OnStatusChange('resolved'); } sub Commit { my $self = shift; my $note = $self->GetNotes; my ($top) = $self->TicketObj->AllDependedOnBy( Type => 'ticket' ); my $links = $self->TicketObj->DependedOnBy; while ( my $link = $links->Next ) { my $obj = $link->BaseObj; next unless $obj->Type eq 'approval'; for my $other ($obj->AllDependsOn( Type => 'approval' )) { if ( $other->QueueObj->IsActiveStatus( $other->Status ) ) { $other->__Set( Field => 'Status', Value => 'deleted', ); } } $obj->SetStatus( Status => $obj->FirstActiveStatus, Force => 1 ) if $obj->FirstActiveStatus; } my $passed = !$top->HasUnresolvedDependencies( Type => 'approval' ); my $template = $self->GetTemplate( $passed ? 'All Approvals Passed' : 'Approval Passed', TicketObj => $top, Approval => $self->TicketObj, Approver => $self->TransactionObj->CreatorObj, Notes => $note, ) or die; $top->Correspond( MIMEObj => $template->MIMEObj ); if ($passed) { my $new_status = $top->LifecycleObj->DefaultStatus('approved') || 'open'; if ( $new_status ne $top->Status ) { $top->SetStatus( $new_status ); } $self->RunScripAction('Notify Owner', 'Approval Ready for Owner', TicketObj => $top); } return; } sub GetNotes { my $self = shift; my $t = $self->TicketObj->Transactions; my $note = ''; while ( my $o = $t->Next ) { next unless $o->Type eq 'Correspond'; $note .= $o->Content . "\n" if $o->ContentObj; } return $note; } RT::Base->_ImportOverlays(); 1; ���������������������������rt-5.0.1/lib/RT/Approval/Rule/NewPending.pm���������������������������������������������������������000644 �000765 �000024 �00000006756 14005011336 021201� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Approval::Rule::NewPending; use strict; use warnings; use base 'RT::Approval::Rule'; use constant Description => "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"; # loc sub Prepare { my $self = shift; return unless $self->SUPER::Prepare(); $self->OnStatusChange('open') and eval { $T::Approving = ($self->TicketObj->AllDependedOnBy( Type => 'ticket' ))[0] } } sub Commit { my $self = shift; my ($top) = $self->TicketObj->AllDependedOnBy( Type => 'ticket' ); my $t = $self->TicketObj->Transactions; my $to; while ( my $o = $t->Next ) { $to = $o, last if $o->Type eq 'Create'; } # XXX: this makes the owner incorrect so notify owner won't work # local $self->{TicketObj} = $top; # first txn entry of the approval ticket local $self->{TransactionObj} = $to; $self->RunScripAction('Notify Owner and AdminCcs', 'New Pending Approval', @_); return; # this generates more correct content of the message, but not sure # if ccmessageto is the right way to do this. my $template = $self->GetTemplate('New Pending Approval', TicketObj => $top, TransactionObj => $to) or return; my ( $result, $msg ) = $template->Parse( TicketObj => $top, ); $self->TicketObj->Comment( CcMessageTo => $self->TicketObj->OwnerObj->EmailAddress, MIMEObj => $template->MIMEObj ); } RT::Base->_ImportOverlays(); 1; ������������������rt-5.0.1/lib/RT/Initialdata/JSON.pm�����������������������������������������������������������������000644 �000765 �000024 �00000010307 14005011336 017427� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Initialdata::JSON - Support for JSON-format initialdata files =head1 DESCRIPTION RT supports pluggable parsers for initialdata in different source formats. This module supports JSON. Perl-based initialdata files can contain not just data, but also perl code that executes when they are processed. The JSON format is purely for data serialization and does not support code sections. Files used with this handler must be UTF-8 encoded text containing a valid JSON structure. See http://json.org for JSON syntax specifications. =cut package RT::Initialdata::JSON; use strict; use warnings; use JSON; =head2 C<CanLoad($json)> This is called by base RT to determine if an initialdata file is whatever type is associated with this module. It must return true or false. Takes one arg, the content of the file to check. =cut sub CanLoad { my $self = shift; my $json = shift; return 0 unless $json; my $parsed; eval { $parsed = JSON->new->decode($json) }; return 0 if ($@); return 1; } =head2 C<Load($data, \@Var, ...)> This is the main routine called when initialdata file handlers are enabled. It is passed the file contents and refs to the arrays that will be populated from the file. If the file parsing fails, due to invalid JSON (generally indicating that the file is actually a perl initialdata file), the sub will return false. =cut sub Load { my ($self, $json, $vars) = @_; return 0 unless $json; my $parsed; eval { $parsed = JSON->new->decode($json) }; if ($@) { RT::Logger->debug("Could not parse initialdata as JSON: ($@)"); return 0; } RT::Logger->info("JSON initialdata has unsupported 'Initial' or 'Final'; ignoring.") if ($parsed->{Initial} or $parsed->{Final}); foreach (keys %$vars) { # Explicitly skip Initial and Final, because we don't want any code or # data that will be eval'd like these keys will be. next if /Initial/ or /Final/; next unless $parsed->{$_}; die "JSON initialdata error: The key named $_ must have a value of type array, but does not." unless ref $parsed->{$_} eq 'ARRAY'; no strict 'refs'; @{$vars->{$_}} = @{$parsed->{$_}}; } return 1; } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Middleware/�������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 017010� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Dispatcher.pm�����������������������������������������������������������������000644 �000765 �000024 �00000007704 14005011336 017367� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Dispatcher; use strict; use warnings; use Moose; use Web::Machine; use Path::Dispatcher; use Plack::Request; use List::MoreUtils 'uniq'; use Module::Pluggable ( search_path => ['RT::REST2::Resource'], sub_name => '_resource_classes', require => 1, max_depth => 5, ); has _dispatcher => ( is => 'ro', isa => 'Path::Dispatcher', builder => '_build_dispatcher', ); sub _build_dispatcher { my $self = shift; my $dispatcher = Path::Dispatcher->new; for my $resource_class ($self->_resource_classes) { if ($resource_class->can('dispatch_rules')) { my @rules = $resource_class->dispatch_rules; for my $rule (@rules) { $rule->{_rest2_resource} = $resource_class; $dispatcher->add_rule($rule); } } } return $dispatcher; } sub to_psgi_app { my $class = shift; my $self = $class->new; return sub { my $env = shift; RT::ConnectToDatabase(); my $dispatch = $self->_dispatcher->dispatch($env->{PATH_INFO}); return [404, ['Content-Type' => 'text/plain'], 'Not Found'] if !$dispatch->has_matches; my @matches = $dispatch->matches; my @matched_resources = uniq map { $_->rule->{_rest2_resource} } @matches; if (@matched_resources > 1) { RT->Logger->error("Path $env->{PATH_INFO} erroneously matched " . scalar(@matched_resources) . " resources: " . (join ', ', @matched_resources) . ". Refusing to dispatch."); return [500, ['Content-Type' => 'text/plain'], 'Internal Server Error'] } my $match = shift @matches; my $rule = $match->rule; my $resource = $rule->{_rest2_resource}; my $args = $rule->block ? $match->run(Plack::Request->new($env)) : {}; my $machine = Web::Machine->new( resource => $resource, resource_args => [%$args], ); return $machine->call($env); }; } 1; ������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Util.pm�����������������������������������������������������������������������000644 �000765 �000024 �00000036432 14005011336 016216� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Util; use strict; use warnings; use JSON (); use Scalar::Util qw( blessed ); use List::MoreUtils 'uniq'; use Sub::Exporter -setup => { exports => [qw[ looks_like_uid expand_uid expand_uri serialize_record deserialize_record error_as_json record_type record_class escape_uri query_string custom_fields_for format_datetime update_custom_fields process_uploads ]] }; sub looks_like_uid { my $value = shift; return 0 unless ref $value eq 'HASH'; return 0 unless $value->{type} and $value->{id} and $value->{_url}; return 1; } sub expand_uid { my $uid = shift; $uid = $$uid if ref $uid eq 'SCALAR'; return if not defined $uid; my $Organization = RT->Config->Get('Organization'); my ($class, $id) = $uid =~ /^([\w:]+)(?:-\Q$Organization\E)?-(.+)$/g; return unless $class and $id; $class =~ s/^RT:://; $class = lc $class; return { type => $class, id => $id, _url => RT::REST2->base_uri . "/$class/$id", }; } sub expand_uri { my $uri = shift; return { type => 'external', _url => $uri, }; } sub format_datetime { my $sql = shift; my $date = RT::Date->new( RT->SystemUser ); $date->Set( Format => 'sql', Value => $sql ); return $date->W3CDTF( Timezone => 'UTC' ); } sub serialize_record { my $record = shift; my %data = $record->Serialize(@_); no warnings 'redefine'; local *RT::Deprecated = sub { # don't trigger deprecation warnings for $record->$column below # such as RT::Group->Type on 4.2 }; for my $column (grep !ref($data{$_}), keys %data) { if ($record->_Accessible($column => "read")) { # Replace values via the Perl API for consistency, access control, # and utf-8 handling. $data{$column} = $record->$column; # Promote raw SQL dates to a standard format if ($record->_Accessible($column => "type") =~ /(datetime|timestamp)/i) { $data{$column} = format_datetime( $data{$column} ); } } else { delete $data{$column}; } } # Add available values for Select RT::CustomField if (ref($record) eq 'RT::CustomField' && $record->Type eq 'Select') { my $values = $record->Values; while (my $val = $values->Next) { my $category = $record->BasedOn ? $val->Category : ''; if (exists $data{Values}) { push @{$data{Values}}, {name => $val->Name, category => $category}; } else { $data{Values} = [{name => $val->Name, category => $category}]; } } } # Replace UIDs with object placeholders for my $uid (grep ref eq 'SCALAR', values %data) { $uid = expand_uid($uid); } # Include role members, if applicable if ($record->DOES("RT::Record::Role::Roles")) { for my $role ($record->Roles(ACLOnly => 0)) { my $members = $data{$role} = []; my $group = $record->RoleGroup($role); if ( !$group->Id ) { $data{$role} = expand_uid( RT->Nobody->UserObj->UID ) if $record->_ROLES->{$role}{Single}; next; } my $gm = $group->MembersObj; while ($_ = $gm->Next) { push @$members, expand_uid($_->MemberObj->Object->UID); } # Avoid the extra array ref for single member roles $data{$role} = shift @$members if $group->SingleMemberRoleGroup; } } if (my $cfs = custom_fields_for($record)) { my %values; while (my $cf = $cfs->Next) { if (! defined $values{$cf->Id}) { $values{$cf->Id} = { %{ expand_uid($cf->UID) }, name => $cf->Name, values => [], }; } my $ocfvs = $cf->ValuesForObject( $record ); my $type = $cf->Type; while (my $ocfv = $ocfvs->Next) { my $content = $ocfv->Content; if ($type eq 'DateTime') { $content = format_datetime($content); } elsif ($type eq 'Image' or $type eq 'Binary') { $content = { content_type => $ocfv->ContentType, filename => $content, _url => RT::REST2->base_uri . "/download/cf/" . $ocfv->id, }; } push @{ $values{$cf->Id}{values} }, $content; } } push @{ $data{CustomFields} }, values %values; } return \%data; } sub deserialize_record { my $record = shift; my $data = shift; my $does_roles = $record->DOES("RT::Record::Role::Roles"); # Sanitize input for the Perl API for my $field (sort keys %$data) { my $skip_regex = join '|', 'CustomFields', 'Attachments', $record->DOES("RT::Record::Role::Links") ? ( sort keys %RT::Link::TYPEMAP ) : (); next if $field =~ /$skip_regex/; my $value = $data->{$field}; next unless ref $value; if (looks_like_uid($value)) { # Deconstruct UIDs back into simple foreign key IDs, assuming it # points to the same record type (class). $data->{$field} = $value->{id} || 0; } elsif ($does_roles and ($field =~ /^RT::CustomRole-\d+$/ or $record->HasRole($field))) { my @members = ref $value eq 'ARRAY' ? @$value : $value; for my $member (@members) { $member = $member->{id} || 0 if looks_like_uid($member); } $data->{$field} = \@members; } else { RT->Logger->debug("Received unknown value via JSON for field $field: ".ref($value)); delete $data->{$field}; } } return $data; } sub error_as_json { my $response = shift; my $return = shift; my $body = JSON::encode_json({ message => join "", @_ }); $response->content_type( "application/json; charset=utf-8" ); $response->content_length( length $body ); $response->body( $body ); return $return; } sub record_type { my $object = shift; my ($type) = blessed($object) =~ /::(\w+)$/; return $type; } sub record_class { my $type = record_type(shift); return "RT::$type"; } sub escape_uri { my $uri = shift; RT::Interface::Web::EscapeURI(\$uri); return $uri; } sub query_string { my %args = @_; my @params; for my $key (sort keys %args) { my $value = $args{$key}; next unless defined $value; $key = escape_uri($key); if (UNIVERSAL::isa($value, 'ARRAY')) { push @params, map $key ."=". escape_uri($_), map defined $_ ? $_ : '', @$value; } else { push @params, $key . "=" . escape_uri($value); } } return join '&', @params; } sub custom_fields_for { my $record = shift; # no role yet, but we have registered lookup types my %registered_type = map {; $_ => 1 } RT::CustomField->LookupTypes; if ($registered_type{$record->CustomFieldLookupType}) { # see $HasTxnCFs in /Elements/ShowHistoryPage; seems like it's working # around a bug in RT::Transaction->CustomFieldLookupId if ($record->isa('RT::Transaction')) { my $object = $record->Object; if ($object->can('TransactionCustomFields') && $object->TransactionCustomFields->Count) { return $object->TransactionCustomFields; } } else { return $record->CustomFields; } } return; } sub update_custom_fields { my $record = shift; my $data = shift; my @results; foreach my $cfid (keys %{ $data }) { my $val = $data->{$cfid}; my $cf = $record->LoadCustomFieldByIdentifier($cfid); next unless $cf->ObjectTypeFromLookupType($cf->__Value('LookupType'))->isa(ref $record); if ($cf->SingleValue) { my %args; my $old_val = $record->FirstCustomFieldValue($cfid); if (!defined $val && $old_val) { my ($ok, $msg) = $record->DeleteCustomFieldValue( Field => $cf, Value => $old_val, ); push @results, $msg; next; } elsif (ref($val) eq 'ARRAY') { $val = $val->[0]; } elsif (ref($val) eq 'HASH' && $cf->Type =~ /^(?:Image|Binary)$/) { my @required_fields; foreach my $field ('FileName', 'FileType', 'FileContent') { unless ($val->{$field}) { push @required_fields, "$field is a required field for Image/Binary ObjectCustomFieldValue"; } } if (@required_fields) { push @results, @required_fields; next; } $args{ContentType} = delete $val->{FileType}; $args{LargeContent} = MIME::Base64::decode_base64(delete $val->{FileContent}); $val = delete $val->{FileName}; } elsif (ref($val)) { die "Invalid value type for CustomField $cfid"; } my ($ok, $msg) = $record->AddCustomFieldValue( Field => $cf, Value => $val, %args, ); push @results, $msg; } else { my %count; my @vals = ref($val) eq 'ARRAY' ? @$val : $val; my @content_vals; my %args; for my $value (@vals) { if (ref($value) eq 'HASH' && $cf->Type =~ /^(?:Image|Binary)$/) { my @required_fields; foreach my $field ('FileName', 'FileType', 'FileContent') { unless ($value->{$field}) { push @required_fields, "$field is a required field for Image/Binary ObjectCustomFieldValue"; } } if (@required_fields) { push @results, @required_fields; next; } my $key = delete $value->{FileName}; $args{$key}->{ContentType} = delete $value->{FileType}; $args{$key}->{LargeContent} = MIME::Base64::decode_base64(delete $value->{FileContent}); $count{$key}++; push @content_vals, $key; } elsif (ref($value)) { die "Invalid value type for CustomField $cfid"; } else { $count{$value}++; } } @vals = @content_vals if @content_vals; my $ocfvs = $cf->ValuesForObject( $record ); my %ocfv_id; while (my $ocfv = $ocfvs->Next) { my $content = $ocfv->Content; $count{$content}--; push @{ $ocfv_id{$content} }, $ocfv->Id; } # we want to provide a stable order, so first go by the order # provided in the argument list, and then for any custom fields # that are being removed, remove in sorted order for my $key (uniq(@vals, sort keys %count)) { my $count = $count{$key}; if ($count == 0) { # new == old, no change needed } elsif ($count > 0) { # new > old, need to add new while ($count-- > 0) { my ($ok, $msg) = $record->AddCustomFieldValue( Field => $cf, Value => $key, $args{$key} ? %{$args{$key}} : (), ); push @results, $msg; } } elsif ($count < 0) { # old > new, need to remove old while ($count++ < 0) { my $id = shift @{ $ocfv_id{$key} }; my ($ok, $msg) = $record->DeleteCustomFieldValue( Field => $cf, ValueId => $id, ); push @results, $msg; } } } } } return @results; } sub process_uploads { my @attachments = @_; my @ret; foreach my $attachment (@attachments) { open my $filehandle, '<', $attachment->tempname; if ( defined $filehandle && length $filehandle ) { my ( @content, $buffer ); while ( read( $filehandle, $buffer, 72 * 57 ) ) { push @content, MIME::Base64::encode_base64($buffer); } close $filehandle; push @ret, { FileName => $attachment->filename, FileType => $attachment->headers->{'content-type'}, FileContent => join( "\n", @content ), }; } } return @ret; } 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource.pm�������������������������������������������������������������������000644 �000765 �000024 �00000012054 14005011336 017062� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource; use strict; use warnings; use Moose; use MooseX::NonMoose; use namespace::autoclean; use RT::REST2::Util qw(expand_uid format_datetime custom_fields_for); extends 'Web::Machine::Resource'; has 'current_user' => ( is => 'ro', isa => 'RT::CurrentUser', required => 1, lazy_build => 1, ); # XXX TODO: real sessions sub _build_current_user { $_[0]->request->env->{"rt.current_user"} || RT::CurrentUser->new; } # Used in Serialize to allow additional fields to be selected ala JSON API on: # http://jsonapi.org/examples/ sub expand_field { my $self = shift; my $item = shift; my $field = shift; my $param_prefix = shift || 'fields'; my $result; if ($field eq 'CustomFields') { if (my $cfs = custom_fields_for($item)) { my %values; while (my $cf = $cfs->Next) { if (! defined $values{$cf->Id}) { $values{$cf->Id} = { %{ expand_uid($cf->UID) }, name => $cf->Name, values => [], }; } my $ocfvs = $cf->ValuesForObject($item); my $type = $cf->Type; while ( my $ocfv = $ocfvs->Next ) { my $content = $ocfv->Content; if ( $type eq 'DateTime' ) { $content = format_datetime($content); } elsif ( $type eq 'Image' or $type eq 'Binary' ) { $content = { content_type => $ocfv->ContentType, filename => $content, _url => RT::REST2->base_uri . "/download/cf/" . $ocfv->id, }; } push @{ $values{ $cf->Id }{values} }, $content; } } push @{ $result }, values %values if %values; } } elsif ($field eq 'ContentLength' && $item->can('ContentLength')) { $result = $item->ContentLength; } elsif ($item->can('_Accessible') && $item->_Accessible($field => 'read')) { # RT::Record derived object, so we can check access permissions. if ($item->_Accessible($field => 'type') =~ /(datetime|timestamp)/i) { $result = format_datetime($item->$field); } elsif ($item->can($field . 'Obj')) { my $method = $field . 'Obj'; my $obj = $item->$method; if ( $obj->can('UID') and $result = expand_uid( $obj->UID ) ) { my $param_field = $param_prefix . '[' . $field . ']'; my @subfields = split( /,/, $self->request->param($param_field) || '' ); for my $subfield (@subfields) { my $subfield_result = $self->expand_field( $obj, $subfield, $param_field ); $result->{$subfield} = $subfield_result if defined $subfield_result; } } } $result //= $item->$field; } return $result // ''; } __PACKAGE__->meta->make_immutable; 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/���������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 016522� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Ticket.pm������������������������������������������������������������000644 �000765 �000024 �00000016375 14005011336 020317� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Ticket; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Record'; with ( 'RT::REST2::Resource::Record::Readable', 'RT::REST2::Resource::Record::Hypermedia' => { -alias => { hypermedia_links => '_default_hypermedia_links' } }, 'RT::REST2::Resource::Record::Deletable', 'RT::REST2::Resource::Record::Writable' => { -alias => { create_record => '_create_record', update_record => '_update_record' } }, ); has 'action' => ( is => 'ro', isa => 'Str', ); sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/ticket/?$}, block => sub { { record_class => 'RT::Ticket' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/ticket/(\d+)/?$}, block => sub { { record_class => 'RT::Ticket', record_id => shift->pos(1) } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/ticket/(\d+)/(take|untake|steal)$}, block => sub { { record_class => 'RT::Ticket', record_id => $_[0]->pos(1), action => $_[0]->pos(2) } }, ), } sub create_record { my $self = shift; my $data = shift; return (\400, "Could not create ticket. Queue not set") if !$data->{Queue}; my $queue = RT::Queue->new(RT->SystemUser); $queue->Load($data->{Queue}); return (\400, "Unable to find queue") if !$queue->Id; return (\403, $self->record->loc("No permission to create tickets in the queue '[_1]'", $queue->Name)) unless $self->record->CurrentUser->HasRight( Right => 'CreateTicket', Object => $queue, ) and $queue->Disabled != 1; if ( defined $data->{Content} || defined $data->{Attachments} ) { $data->{MIMEObj} = HTML::Mason::Commands::MakeMIMEEntity( Interface => 'REST', Subject => $data->{Subject}, Body => delete $data->{Content}, Type => delete $data->{ContentType} || 'text/plain', ); if ( defined $data->{Attachments} ) { return (\400, "Attachments must be an array") unless ref($data->{Attachments}) eq 'ARRAY'; foreach my $attachment (@{$data->{Attachments}}) { return (\400, "Each element of Attachments must be a hash") unless ref($attachment) eq 'HASH'; foreach my $field (qw(FileName FileType FileContent)) { return (\400, "Field $field is required for each attachment in Attachments") unless $attachment->{$field}; } $data->{MIMEObj}->attach( Type => $attachment->{FileType}, Filename => $attachment->{FileName}, Data => MIME::Base64::decode_base64($attachment->{FileContent})); } delete $data->{Attachments}; } } my ($ok, $txn, $msg) = $self->_create_record($data); return ($ok, $msg); } sub update_record { my $self = shift; my $data = shift; my @results; if ( my $action = $self->action ) { my $method = ucfirst $action; my ( $ok, $msg ) = $self->record->$method(); push @results, $msg; } push @results, $self->_update_record($data); if ( my $ticket_id = delete $data->{MergeInto} ) { my ( $ok, $msg ) = $self->record->MergeInto($ticket_id); push @results, $msg; } return @results; } sub forbidden { my $self = shift; return 0 unless $self->record->id; return !$self->record->CurrentUserHasRight('ShowTicket'); } sub lifecycle_hypermedia_links { my $self = shift; my $self_link = $self->_self_link; my $ticket = $self->record; my @links; # lifecycle actions my $lifecycle = $ticket->LifecycleObj; my $current = $ticket->Status; my $hide_resolve_with_deps = RT->Config->Get('HideResolveActionsWithDependencies') && $ticket->HasUnresolvedDependencies; for my $info ( $lifecycle->Actions($current) ) { my $next = $info->{'to'}; next unless $lifecycle->IsTransition( $current => $next ); my $check = $lifecycle->CheckRight( $current => $next ); next unless $ticket->CurrentUserHasRight($check); next if $hide_resolve_with_deps && $lifecycle->IsInactive($next) && !$lifecycle->IsInactive($current); my $url = $self_link->{_url}; $url .= '/correspond' if ($info->{update}||'') eq 'Respond'; $url .= '/comment' if ($info->{update}||'') eq 'Comment'; push @links, { %$info, label => $self->current_user->loc($info->{'label'} || ucfirst($next)), ref => 'lifecycle', _url => $url, }; } return @links; } sub hypermedia_links { my $self = shift; my $self_link = $self->_self_link; my $links = $self->_default_hypermedia_links(@_); my $ticket = $self->record; push @$links, $self->_transaction_history_link; push @$links, { ref => 'correspond', _url => $self_link->{_url} . '/correspond', } if $ticket->CurrentUserHasRight('ReplyToTicket'); push @$links, { ref => 'comment', _url => $self_link->{_url} . '/comment', } if $ticket->CurrentUserHasRight('CommentOnTicket'); push @$links, $self->lifecycle_hypermedia_links; return $links; } __PACKAGE__->meta->make_immutable; 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/User.pm��������������������������������������������������������������000644 �000765 �000024 �00000011657 14005011336 020010� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::User; use strict; use warnings; use Moose; use namespace::autoclean; use RT::REST2::Util qw(expand_uid); extends 'RT::REST2::Resource::Record'; with ( 'RT::REST2::Resource::Record::Readable', 'RT::REST2::Resource::Record::DeletableByDisabling', 'RT::REST2::Resource::Record::Writable', 'RT::REST2::Resource::Record::Hypermedia' => { -alias => { hypermedia_links => '_default_hypermedia_links' } }, ); sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/user/?$}, block => sub { { record_class => 'RT::User' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/user/(\d+)/?$}, block => sub { { record_class => 'RT::User', record_id => shift->pos(1) } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/user/([^/]+)/?$}, block => sub { my ($match, $req) = @_; my $user = RT::User->new($req->env->{"rt.current_user"}); $user->Load($match->pos(1)); return { record => $user }; }, ), } around 'serialize' => sub { my $orig = shift; my $self = shift; my $data = $self->$orig(@_); $data->{Privileged} = $self->record->Privileged ? 1 : 0; unless ( $self->record->CurrentUserHasRight("AdminUsers") or $self->record->id == $self->current_user->id ) { my $summary = { map { $_ => $data->{$_} } ( '_hyperlinks', 'Privileged', split( /\s*,\s*/, RT->Config->Get('UserSummaryExtraInfo') ) ) }; return $summary; } $data->{Disabled} = $self->record->PrincipalObj->Disabled; $data->{Memberships} = [ map { expand_uid($_->UID) } @{ $self->record->OwnGroups->ItemsArrayRef } ]; return $data; }; sub forbidden { my $self = shift; return 0 if not $self->record->id; return 0 if $self->record->id == $self->current_user->id; return 0 if $self->current_user->Privileged; return 1; } sub hypermedia_links { my $self = shift; my $links = $self->_default_hypermedia_links(@_); if ( $self->record->CurrentUserHasRight('ShowUserHistory') or $self->record->CurrentUserHasRight("AdminUsers") or $self->record->id == $self->current_user->id ) { push @$links, $self->_transaction_history_link; } # TODO Use the same right in UserGroups.pm. # Maybe loose it a bit so user can see groups? if (( $self->current_user->HasRight( Right => "ModifyOwnMembership", Object => RT->System, ) && $self->current_user->id == $self->record->id ) || $self->current_user->HasRight( Right => 'AdminGroupMembership', Object => RT->System ) ) { my $id = $self->record->id; push @$links, { ref => 'memberships', _url => RT::REST2->base_uri . "/user/$id/groups", }; } return $links; } __PACKAGE__->meta->make_immutable; 1; ���������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Tickets.pm�����������������������������������������������������������000644 �000765 �000024 �00000007350 14005011336 020473� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Tickets; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Collection'; with 'RT::REST2::Resource::Collection::ProcessPOSTasGET'; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/tickets/?$}, block => sub { { collection_class => 'RT::Tickets' } }, ) } use Encode qw( decode_utf8 ); use RT::REST2::Util qw( error_as_json ); use RT::Search::Simple; has 'query' => ( is => 'ro', isa => 'Str', required => 1, lazy_build => 1, ); sub _build_query { my $self = shift; my $query = decode_utf8($self->request->param('query') || ""); if ($self->request->param('simple') and $query) { # XXX TODO: Note that "normal" ModifyQuery callback isn't invoked # XXX TODO: Special-casing of "#NNN" isn't used my $search = RT::Search::Simple->new( Argument => $query, TicketsObj => $self->collection, ); $query = $search->QueryToSQL; } return $query; } sub allowed_methods { [ 'GET', 'HEAD', 'POST' ] } sub limit_collection { my $self = shift; my ($ok, $msg) = $self->collection->FromSQL( $self->query ); return error_as_json( $self->response, 0, $msg ) unless $ok; my @orderby_cols; my @orders = $self->request->param('order'); foreach my $orderby ($self->request->param('orderby')) { $orderby = decode_utf8($orderby); my $order = shift @orders || 'ASC'; $order = uc(decode_utf8($order)); $order = 'ASC' unless $order eq 'DESC'; push @orderby_cols, {FIELD => $orderby, ORDER => $order}; } $self->collection->OrderByCols(@orderby_cols) if @orderby_cols; return 1; } __PACKAGE__->meta->make_immutable; 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Queue.pm�������������������������������������������������������������000644 �000765 �000024 �00000011037 14005011336 020146� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Queue; use strict; use warnings; use Moose; use namespace::autoclean; use RT::REST2::Util qw(expand_uid); extends 'RT::REST2::Resource::Record'; with ( 'RT::REST2::Resource::Record::Readable', 'RT::REST2::Resource::Record::Hypermedia' => { -alias => { hypermedia_links => '_default_hypermedia_links' } }, 'RT::REST2::Resource::Record::DeletableByDisabling', 'RT::REST2::Resource::Record::Writable', ); sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/queue/?$}, block => sub { { record_class => 'RT::Queue' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/queue/(\d+)/?$}, block => sub { { record_class => 'RT::Queue', record_id => shift->pos(1) } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/queue/([^/]+)/?$}, block => sub { my ($match, $req) = @_; my $queue = RT::Queue->new($req->env->{"rt.current_user"}); $queue->Load($match->pos(1)); return { record => $queue }; }, ), } sub hypermedia_links { my $self = shift; my $links = $self->_default_hypermedia_links(@_); my $queue = $self->record; push @$links, $self->_transaction_history_link; push @$links, { ref => 'create', type => 'ticket', _url => RT::REST2->base_uri . '/ticket?Queue=' . $queue->Id, } if $queue->CurrentUserHasRight('CreateTicket'); return $links; } around 'serialize' => sub { my $orig = shift; my $self = shift; my $data = $self->$orig(@_); # Load Ticket Custom Fields for this queue if ( my $ticket_cfs = $self->record->TicketCustomFields ) { my @values; while (my $cf = $ticket_cfs->Next) { my $entry = expand_uid($cf->UID); my $content = { %$entry, ref => 'customfield', name => $cf->Name, }; push @values, $content; } $data->{TicketCustomFields} = \@values; } # Load Transaction custom fields for this queue if ( my $ticket_cfs = $self->record->TicketTransactionCustomFields ) { my @values; while (my $cf = $ticket_cfs->Next) { my $entry = expand_uid($cf->UID); my $content = { %$entry, ref => 'customfield', name => $cf->Name, }; push @values, $content; } $data->{TicketTransactionCustomFields} = \@values; } return $data; }; __PACKAGE__->meta->make_immutable; 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Catalog.pm�����������������������������������������������������������000644 �000765 �000024 �00000006616 14005011336 020443� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Catalog; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Record'; with ( 'RT::REST2::Resource::Record::Readable', 'RT::REST2::Resource::Record::Hypermedia' => { -alias => { hypermedia_links => '_default_hypermedia_links' } }, 'RT::REST2::Resource::Record::DeletableByDisabling', 'RT::REST2::Resource::Record::Writable', ); sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/catalog/?$}, block => sub { { record_class => 'RT::Catalog' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/catalog/(\d+)/?$}, block => sub { { record_class => 'RT::Catalog', record_id => shift->pos(1) } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/catalog/([^/]+)/?$}, block => sub { my ($match, $req) = @_; my $catalog = RT::Catalog->new($req->env->{"rt.current_user"}); $catalog->Load($match->pos(1)); return { record => $catalog }; }, ), } sub hypermedia_links { my $self = shift; my $links = $self->_default_hypermedia_links(@_); my $catalog = $self->record; push @$links, { ref => 'create', type => 'asset', _url => RT::REST2->base_uri . '/asset?Catalog=' . $catalog->Id, } if $catalog->CurrentUserHasRight('CreateAsset'); return $links; } __PACKAGE__->meta->make_immutable; 1; ������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Collection/����������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 020615� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Role/����������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 017423� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Asset.pm�������������������������������������������������������������000644 �000765 �000024 �00000010655 14005011336 020146� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Asset; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Record'; with ( 'RT::REST2::Resource::Record::Readable', 'RT::REST2::Resource::Record::Hypermedia' => { -alias => { hypermedia_links => '_default_hypermedia_links' } }, 'RT::REST2::Resource::Record::Deletable', 'RT::REST2::Resource::Record::Writable' => { -alias => { create_record => '_create_record' } }, ); sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/asset/?$}, block => sub { { record_class => 'RT::Asset' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/asset/(\d+)/?$}, block => sub { { record_class => 'RT::Asset', record_id => shift->pos(1) } }, ) } sub create_record { my $self = shift; my $data = shift; return (\400, "Invalid Catalog") if !$data->{Catalog}; my $catalog = RT::Catalog->new($self->record->CurrentUser); $catalog->Load($data->{Catalog}); return (\400, "Invalid Catalog") if !$catalog->Id; return (\403, $self->record->loc("Permission Denied", $catalog->Name)) unless $catalog->CurrentUserHasRight('CreateAsset'); return $self->_create_record($data); } sub forbidden { my $self = shift; return 0 unless $self->record->id; return !$self->record->CurrentUserHasRight('ShowAsset'); } sub lifecycle_hypermedia_links { my $self = shift; my $self_link = $self->_self_link; my $asset = $self->record; my @links; # lifecycle actions my $lifecycle = $asset->LifecycleObj; my $current = $asset->Status; for my $info ( $lifecycle->Actions($current) ) { my $next = $info->{'to'}; next unless $lifecycle->IsTransition( $current => $next ); my $check = $lifecycle->CheckRight( $current => $next ); next unless $asset->CurrentUserHasRight($check); my $url = $self_link->{_url}; push @links, { %$info, label => $self->current_user->loc($info->{'label'} || ucfirst($next)), ref => 'lifecycle', _url => $url, }; } return @links; } sub hypermedia_links { my $self = shift; my $self_link = $self->_self_link; my $links = $self->_default_hypermedia_links(@_); my $asset = $self->record; push @$links, $self->_transaction_history_link; push @$links, $self->lifecycle_hypermedia_links; return $links; } __PACKAGE__->meta->make_immutable; 1; �����������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Users.pm�������������������������������������������������������������000644 �000765 �000024 �00000005624 14005011336 020170� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Users; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Collection'; with 'RT::REST2::Resource::Collection::QueryByJSON'; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/users/?$}, block => sub { { collection_class => 'RT::Users' } }, ), } sub searchable_fields { my $self = shift; my $class = $self->collection->RecordClass; my @fields; if ($self->current_user->HasRight( Right => "AdminUsers", Object => RT->System, )) { @fields = grep { $class->_Accessible($_ => "public") } $class->ReadableAttributes; } else { @fields = split(/\s*\,\s*/, RT->Config->Get('UserSummaryExtraInfo')); } return @fields } sub forbidden { my $self = shift; return 0 if $self->current_user->Privileged; return 1; } __PACKAGE__->meta->make_immutable; 1; ������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Record/��������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 017740� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/CustomFieldValues.pm�������������������������������������������������000644 �000765 �000024 �00000007401 14005011336 022460� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::CustomFieldValues; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Collection'; with 'RT::REST2::Resource::Collection::QueryByJSON'; has 'customfield' => ( is => 'ro', isa => 'RT::CustomField', ); sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/customfield/(\d+)/values/?$}, block => sub { my ($match, $req) = @_; my $cf_id = $match->pos(1); my $cf = RT::CustomField->new($req->env->{"rt.current_user"}); $cf->Load($cf_id); my $values = $cf->Values; return { customfield => $cf, collection => $values } }, ) } sub forbidden { my $self = shift; my $method = $self->request->method; if ($method eq 'GET') { return !$self->customfield->CurrentUserHasRight('SeeCustomField'); } else { return !($self->customfield->CurrentUserHasRight('AdminCustomField') ||$self->customfield->CurrentUserHasRight('AdminCustomFieldValues')); } } sub serialize { my $self = shift; my $collection = $self->collection; my $cf = $self->customfield; my @results; while (my $item = $collection->Next) { my $result = { type => 'customfieldvalue', id => $item->id, name => $item->Name, _url => RT::REST2->base_uri . "/customfield/" . $cf->id . '/value/' . $item->id, }; push @results, $result; } return { count => scalar(@results) + 0, total => $collection->CountAll + 0, per_page => $collection->RowsPerPage + 0, page => ($collection->FirstRow / $collection->RowsPerPage) + 1, items => \@results, }; } __PACKAGE__->meta->make_immutable; 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Queues.pm������������������������������������������������������������000644 �000765 �000024 �00000005213 14005011336 020330� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Queues; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Collection'; with 'RT::REST2::Resource::Collection::QueryByJSON'; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/queues/?$}, block => sub { { collection_class => 'RT::Queues' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/queues/all/?$}, block => sub { my ($match, $req) = @_; my $queues = RT::Queues->new($req->env->{"rt.current_user"}); $queues->UnLimit; return { collection => $queues }; }, ), } __PACKAGE__->meta->make_immutable; 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Catalogs.pm����������������������������������������������������������000644 �000765 �000024 �00000005233 14005011336 020620� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Catalogs; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Collection'; with 'RT::REST2::Resource::Collection::QueryByJSON'; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/catalogs/?$}, block => sub { { collection_class => 'RT::Catalogs' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/catalogs/all/?$}, block => sub { my ($match, $req) = @_; my $catalogs = RT::Catalogs->new($req->env->{"rt.current_user"}); $catalogs->UnLimit; return { collection => $catalogs }; }, ), } __PACKAGE__->meta->make_immutable; 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Transactions.pm������������������������������������������������������000644 �000765 �000024 �00000006654 14005011336 021543� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Transactions; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Collection'; with 'RT::REST2::Resource::Collection::QueryByJSON'; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/transactions/?$}, block => sub { { collection_class => 'RT::Transactions' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/(ticket|queue|asset|user|group|article)/(\d+)/history/?$}, block => sub { my ($match, $req) = @_; my ($class, $id) = ($match->pos(1), $match->pos(2)); my $package = 'RT::' . ucfirst $class; my $record = $package->new( $req->env->{"rt.current_user"} ); $record->Load($id); return { collection => $record->Transactions }; }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/(queue|user)/([^/]+)/history/?$}, block => sub { my ($match, $req) = @_; my ($class, $id) = ($match->pos(1), $match->pos(2)); my $record; if ($class eq 'queue') { $record = RT::Queue->new($req->env->{"rt.current_user"}); } elsif ($class eq 'user') { $record = RT::User->new($req->env->{"rt.current_user"}); } $record->Load($id); return { collection => $record->Transactions }; }, ) } __PACKAGE__->meta->make_immutable; 1; ������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Transaction.pm�������������������������������������������������������000644 �000765 �000024 �00000006031 14005011336 021345� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Transaction; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Record'; with 'RT::REST2::Resource::Record::Readable', 'RT::REST2::Resource::Record::Hypermedia' => { -alias => { hypermedia_links => '_default_hypermedia_links' } }; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/transaction/?$}, block => sub { { record_class => 'RT::Transaction' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/transaction/(\d+)/?$}, block => sub { { record_class => 'RT::Transaction', record_id => shift->pos(1) } }, ) } sub hypermedia_links { my $self = shift; my $links = $self->_default_hypermedia_links(@_); my $attachments = $self->record->Attachments; while (my $attachment = $attachments->Next) { my $id = $attachment->Id; push @$links, { ref => 'attachment', _url => RT::REST2->base_uri . "/attachment/$id", }; } return $links; } __PACKAGE__->meta->make_immutable; 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/RT.pm����������������������������������������������������������������000644 �000765 �000024 �00000005104 14005011336 017405� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::RT; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource'; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/rt?$}, ); } sub charsets_provided { [ 'utf-8' ] } sub default_charset { 'utf-8' } sub allowed_methods { ['GET'] } sub content_types_provided { [{ 'application/json' => 'to_json' }] } sub to_json { my $self = shift; return JSON::to_json({ Version => $RT::VERSION, Plugins => [ RT->Config->Get('Plugins') ], }, { pretty => 1 }); } __PACKAGE__->meta->make_immutable; 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Groups.pm������������������������������������������������������������000644 �000765 �000024 �00000004533 14005011336 020344� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Groups; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Collection'; with 'RT::REST2::Resource::Collection::QueryByJSON'; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/groups/?$}, block => sub { { collection_class => 'RT::Groups' } }, ), } __PACKAGE__->meta->make_immutable; 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Message.pm�����������������������������������������������������������000644 �000765 �000024 �00000017040 14005011336 020446� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Message; use strict; use warnings; use Moose; use namespace::autoclean; use MIME::Base64; extends 'RT::REST2::Resource'; use RT::REST2::Util qw( error_as_json update_custom_fields process_uploads ); sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/ticket/(\d+)/(correspond|comment)$}, block => sub { my ($match, $req) = @_; my $ticket = RT::Ticket->new($req->env->{"rt.current_user"}); $ticket->Load($match->pos(1)); return { record => $ticket, type => $match->pos(2) }, }, ); } has record => ( is => 'ro', isa => 'RT::Record', required => 1, ); has type => ( is => 'ro', isa => 'Str', required => 1, ); has created_transaction => ( is => 'rw', isa => 'RT::Transaction', ); sub post_is_create { 1 } sub create_path_after_handler { 1 } sub allowed_methods { ['POST'] } sub charsets_provided { [ 'utf-8' ] } sub default_charset { 'utf-8' } sub content_types_provided { [ { 'application/json' => sub {} } ] } sub content_types_accepted { [ { 'text/plain' => 'add_message' }, { 'text/html' => 'add_message' }, { 'application/json' => 'from_json' }, { 'multipart/form-data' => 'from_multipart' } ] } sub from_multipart { my $self = shift; my $json_str = $self->request->parameters->{JSON}; return error_as_json( $self->response, \400, "JSON is a required field for multipart/form-data") unless $json_str; my $json = JSON::decode_json($json_str); if ( my @attachments = $self->request->upload('Attachments') ) { $json->{Attachments} = [ process_uploads(@attachments) ]; } return $self->from_json($json); } sub from_json { my $self = shift; my $body = shift || JSON::decode_json( $self->request->content ); if ($body->{Attachments}) { foreach my $attachment (@{$body->{Attachments}}) { foreach my $field ('FileName', 'FileType', 'FileContent') { return error_as_json( $self->response, \400, "$field is a required field for each attachment in Attachments") unless $attachment->{$field}; } } $body->{NoContent} = 1 unless $body->{Content}; } if (!$body->{NoContent} && !$body->{ContentType}) { return error_as_json( $self->response, \400, "ContentType is a required field for application/json"); } $self->add_message(%$body); } sub add_message { my $self = shift; my %args = @_; my @results; my $MIME = HTML::Mason::Commands::MakeMIMEEntity( Interface => 'REST', $args{NoContent} ? () : (Body => $args{Content} || $self->request->content), Type => $args{ContentType} || $self->request->content_type, Subject => $args{Subject}, ); # Process attachments foreach my $attachment (@{$args{Attachments}}) { $MIME->attach( Type => $attachment->{FileType}, Filename => $attachment->{FileName}, Data => MIME::Base64::decode_base64($attachment->{FileContent}), ); } my ( $Trans, $msg, $TransObj ); if ($self->type eq 'correspond') { ( $Trans, $msg, $TransObj ) = $self->record->Correspond( MIMEObj => $MIME, TimeTaken => ($args{TimeTaken} || 0), ); } elsif ($self->type eq 'comment') { ( $Trans, $msg, $TransObj ) = $self->record->Comment( MIMEObj => $MIME, TimeTaken => ($args{TimeTaken} || 0), ); } else { return \400; } if (!$Trans) { return error_as_json( $self->response, \400, $msg || "Message failed for unknown reason"); } push @results, $msg; push @results, update_custom_fields($self->record, $args{CustomFields}); push @results, $self->_update_txn_custom_fields( $TransObj, $args{TxnCustomFields} || $args{TransactionCustomFields} ); $self->created_transaction($TransObj); $self->response->body(JSON::to_json(\@results, { pretty => 1 })); return 1; } sub _update_txn_custom_fields { my $self = shift; my $TransObj = shift; my $TxnCustomFields = shift; my @results; # generate a hash suitable for UpdateCustomFields # ie the keys are the "full names" of the custom fields my %txn_custom_fields; foreach my $cf_name ( keys %{$TxnCustomFields} ) { my $cf_obj = $TransObj->LoadCustomFieldByIdentifier($cf_name); unless ( $cf_obj and $cf_obj->Id ) { RT->Logger->error( "Unable to load transaction custom field: $cf_name" ); push @results, "Unable to load transaction custom field: $cf_name"; next; } my $txn_input_name = RT::Interface::Web::GetCustomFieldInputName( CustomField => $cf_obj, Grouping => undef ); $txn_custom_fields{$txn_input_name} = $TxnCustomFields->{$cf_name}; } # UpdateCustomFields currently doesn't return messages on updates # Stub it out for now. my @return = $TransObj->UpdateCustomFields( %txn_custom_fields ); if ( keys %txn_custom_fields ) { # Simulate return messages until we get real results if ( @return && $return[0] == 1 ) { push @results, 'Custom fields updated'; } } return @results; } sub create_path { my $self = shift; my $id = $self->created_transaction->Id; return "/transaction/$id"; } __PACKAGE__->meta->make_immutable; 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/CustomRole.pm��������������������������������������������������������000644 �000765 �000024 �00000005075 14005011336 021163� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::CustomRole; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Record'; with 'RT::REST2::Resource::Record::Readable', 'RT::REST2::Resource::Record::Hypermedia'; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/customrole/?$}, block => sub { { record_class => 'RT::CustomRole' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/customrole/(\d+)/?$}, block => sub { { record_class => 'RT::CustomRole', record_id => shift->pos(1) } }, ) } __PACKAGE__->meta->make_immutable; 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Attachment.pm��������������������������������������������������������000644 �000765 �000024 �00000006451 14005011336 021156� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Attachment; use strict; use warnings; use MIME::Base64; use Encode; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Record'; with 'RT::REST2::Resource::Record::Readable', 'RT::REST2::Resource::Record::Hypermedia'; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/attachment/?$}, block => sub { { record_class => 'RT::Attachment' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/attachment/(\d+)/?$}, block => sub { { record_class => 'RT::Attachment', record_id => shift->pos(1) } }, ) } # Tweak serialize to base-64-encode Content around 'serialize' => sub { my ($orig, $self) = @_; my $data = $self->$orig(@_); return $data unless defined $data->{Content}; # Encode as UTF-8 if it's an internal Perl Unicode string, or if it # contains wide characters. If the raw data does indeed contain # wide characters, encode_base64 will die anyway, so encoding # seems like a safer choice. if (utf8::is_utf8($data->{Content}) || $data->{Content} =~ /[^\x00-\xFF]/) { # Encode internal Perl string to UTF-8 $data->{Content} = encode('UTF-8', $data->{Content}, Encode::FB_PERLQQ); } $data->{Content} = encode_base64($data->{Content}); return $data; }; __PACKAGE__->meta->make_immutable; 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/CustomFields.pm������������������������������������������������������000644 �000765 �000024 �00000006503 14005011336 021465� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::CustomFields; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Collection'; with 'RT::REST2::Resource::Collection::QueryByJSON'; has 'object_applied_to' => ( is => 'ro', required => 0, ); sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/customfields/?$}, block => sub { { collection_class => 'RT::CustomFields' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/(catalog|class|queue)/(\d+)/customfields/?$}, block => sub { my ($match, $req) = @_; my $object_type = 'RT::'. ucfirst($match->pos(1)); my $object_id = $match->pos(2); my $object_applied_to = $object_type->new($req->env->{"rt.current_user"}); $object_applied_to->Load($object_id); return {object_applied_to => $object_applied_to, collection_class => 'RT::CustomFields'}; }, ), } after 'limit_collection' => sub { my $self = shift; my $collection = $self->collection; my $object = $self->object_applied_to; if ($object && $object->id) { $collection->Limit(ENTRYAGGREGATOR => "AND", FIELD => 'LookupType', OPERATOR => 'STARTSWITH', VALUE => ref($object)); $collection->LimitToGlobalOrObjectId($object->id); } return 1; }; __PACKAGE__->meta->make_immutable; 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Attachments.pm�������������������������������������������������������000644 �000765 �000024 �00000007432 14005011336 021341� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Attachments; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Collection'; with 'RT::REST2::Resource::Collection::QueryByJSON'; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/attachments/?$}, block => sub { { collection_class => 'RT::Attachments' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/transaction/(\d+)/attachments/?$}, block => sub { my ($match, $req) = @_; my $txn = RT::Transaction->new($req->env->{"rt.current_user"}); $txn->Load($match->pos(1)); return { collection => $txn->Attachments }; }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/ticket/(\d+)/attachments/?$}, block => sub { my ($match, $req) = @_; return _get_ticket_attachments($match, $req); }, ), } # Get a collection of attachments associated with a ticket This code # was put into a subroutine as it was a little long to put inline # above and maintain readability. sub _get_ticket_attachments { my ($match, $req) = @_; my $ticket = RT::Ticket->new($req->env->{"rt.current_user"}); my $id = $ticket->Load($match->pos(1)); my $attachments = RT::Attachments->new($req->env->{"rt.current_user"}); # Return empty list if no such ticket return { collection => $attachments } unless $id; # Explicitly check for permission to see the ticket. # If we do not do that, we leak the total number of attachments # even though the actual attachments themselves are not shown. return { collection => $attachments } unless $ticket->CurrentUserHasRight('ShowTicket'); $attachments->LimitByTicket($id); return { collection => $attachments }; } __PACKAGE__->meta->make_immutable; 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Articles.pm����������������������������������������������������������000644 �000765 �000024 �00000004537 14005011336 020637� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Articles; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Collection'; with 'RT::REST2::Resource::Collection::QueryByJSON'; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/articles/?$}, block => sub { { collection_class => 'RT::Articles' } }, ) } __PACKAGE__->meta->make_immutable; 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/TicketsBulk.pm�������������������������������������������������������000644 �000765 �000024 �00000010302 14005011336 021300� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::TicketsBulk; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource'; with 'RT::REST2::Resource::Role::RequestBodyIsJSON' => { type => 'ARRAY' }; use RT::REST2::Util qw(expand_uid); use RT::REST2::Resource::Ticket; use JSON (); sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/tickets/bulk/?$} ); } sub post_is_create { 1 } sub create_path { '/tickets/bulk' } sub charsets_provided { [ 'utf-8' ] } sub default_charset { 'utf-8' } sub allowed_methods { [ 'PUT', 'POST' ] } sub content_types_provided { [ { 'application/json' => sub {} } ] } sub content_types_accepted { [ { 'application/json' => 'from_json' } ] } sub from_json { my $self = shift; my $params = JSON::decode_json( $self->request->content ); my $method = $self->request->method; my @results; if ( $method eq 'PUT' ) { for my $param ( @$params ) { my $id = delete $param->{id}; if ( $id && $id =~ /^\d+$/ ) { my $resource = RT::REST2::Resource::Ticket->new( request => $self->request, response => $self->response, record_class => 'RT::Ticket', record_id => $id, ); if ( $resource->resource_exists ) { push @results, [ $id, $resource->update_record( $param ) ]; next; } } push @results, [ $id, 'Resource does not exist' ]; } } else { for my $param ( @$params ) { my $resource = RT::REST2::Resource::Ticket->new( request => $self->request, response => $self->response, record_class => 'RT::Ticket', ); my ( $ok, $msg ) = $resource->create_record( $param ); if ( ref( $ok ) || !$ok ) { push @results, { message => $msg || "Create failed for unknown reason" }; } else { push @results, expand_uid( $resource->record->UID ); } } } $self->response->body( JSON::encode_json( \@results ) ); return; } __PACKAGE__->meta->make_immutable; 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Record.pm������������������������������������������������������������000644 �000765 �000024 �00000007574 14005011336 020313� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Record; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource'; use Web::Machine::Util qw( create_date ); use RT::REST2::Util qw( record_type ); has 'record_class' => ( is => 'ro', isa => 'ClassName', ); has 'record_id' => ( is => 'ro', isa => 'Int', ); has 'record' => ( is => 'ro', isa => 'RT::Record', required => 1, lazy_build => 1, ); sub _build_record { my $self = shift; my $class = $self->record_class; my $id = $self->record_id; $class->require; my $record = $class->new( $self->current_user ); $record->Load($id) if $id; return $record; } sub base_uri { my $self = shift; my $base = RT::REST2->base_uri; my $type = lc record_type($self); return join '/', $base, $type; } sub resource_exists { $_[0]->record->id } sub forbidden { my $self = shift; return 0 unless $self->record->id; my $can_see = $self->record->can("CurrentUserCanSee"); return 1 if $can_see and not $self->record->$can_see(); return 0; } sub last_modified { my $self = shift; return unless $self->record->_Accessible("LastUpdated" => "read"); my $updated = $self->record->LastUpdatedObj->RFC2616 or return; return create_date($updated); } sub allowed_methods { my $self = shift; my @ok; push @ok, 'GET', 'HEAD' if $self->DOES("RT::REST2::Resource::Record::Readable"); push @ok, 'DELETE' if $self->DOES("RT::REST2::Resource::Record::Deletable"); push @ok, 'PUT', 'POST' if $self->DOES("RT::REST2::Resource::Record::Writable"); return \@ok; } sub finish_request { my $self = shift; # Ensure the record object is destroyed before the request finishes, for # any cleanup that may need to happen (i.e. TransactionBatch). $self->clear_record; return $self->SUPER::finish_request(@_); } __PACKAGE__->meta->make_immutable; 1; ������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Collection.pm��������������������������������������������������������000644 �000765 �000024 �00000013674 14005011336 021166� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Collection; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource'; use Scalar::Util qw( blessed ); use Web::Machine::FSM::States qw( is_status_code ); use Module::Runtime qw( require_module ); use RT::REST2::Util qw( serialize_record expand_uid format_datetime ); use POSIX qw( ceil ); has 'collection_class' => ( is => 'ro', isa => 'ClassName', ); has 'collection' => ( is => 'ro', isa => 'RT::SearchBuilder', required => 1, lazy_build => 1, ); sub _build_collection { my $self = shift; my $collection = $self->collection_class->new( $self->current_user ); return $collection; } sub setup_paging { my $self = shift; my $per_page = $self->request->param('per_page') || 20; if ( $per_page !~ /^\d+$/ ) { $per_page = 20 } elsif ( $per_page == 0 ) { $per_page = 20 } elsif ( $per_page > 100 ) { $per_page = 100 } $self->collection->RowsPerPage($per_page); my $page = $self->request->param('page') || 1; if ( $page !~ /^\d+$/ ) { $page = 1 } elsif ( $page == 0 ) { $page = 1 } $self->collection->GotoPage($page - 1); } sub limit_collection { my $self = shift; my $collection = $self->collection; $collection->{'find_disabled_rows'} = 1 if $self->request->param('find_disabled_rows'); return 1; } sub search { my $self = shift; $self->setup_paging; return $self->limit_collection; } sub serialize { my $self = shift; my $collection = $self->collection; my @results; my @fields = defined $self->request->param('fields') ? split(/,/, $self->request->param('fields')) : (); while (my $item = $collection->Next) { my $result = expand_uid( $item->UID ); # Allow selection of desired fields if ($result) { for my $field (@fields) { my $field_result = $self->expand_field($item, $field); $result->{$field} = $field_result if defined $field_result; } } push @results, $result; } my %results = ( count => scalar(@results) + 0, total => $collection->CountAll + 0, per_page => $collection->RowsPerPage + 0, page => ($collection->FirstRow / $collection->RowsPerPage) + 1, items => \@results, ); my $uri = $self->request->uri; my @query_form = $uri->query_form; # find page and if it is set, delete it and its value. for my $i (0..$#query_form) { if ($query_form[$i] eq 'page') { delete @query_form[$i, $i + 1]; last; } } $results{pages} = ceil($results{total} / $results{per_page}); if ($results{page} < $results{pages}) { my $page = $results{page} + 1; $uri->query_form( @query_form, page => $results{page} + 1 ); $results{next_page} = $uri->as_string; }; if ($results{page} > 1) { # If we're beyond the last page, set prev_page as the last page # available, otherwise, the previous page. $uri->query_form( @query_form, page => ($results{page} > $results{pages} ? $results{pages} : $results{page} - 1) ); $results{prev_page} = $uri->as_string; }; return \%results; } # XXX TODO: Bulk update via DELETE/PUT on a collection resource? sub charsets_provided { [ 'utf-8' ] } sub default_charset { 'utf-8' } sub content_types_provided { [ { 'application/json' => 'to_json' }, ] } sub to_json { my $self = shift; my $status = $self->search; return $status if is_status_code($status); return \400 unless $status; return JSON::to_json($self->serialize, { pretty => 1 }); } sub finish_request { my $self = shift; # Ensure the collection object is destroyed before the request finishes, for # any cleanup that may need to happen (i.e. TransactionBatch). $self->clear_collection; return $self->SUPER::finish_request(@_); } __PACKAGE__->meta->make_immutable; 1; ��������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Group.pm�������������������������������������������������������������000644 �000765 �000024 �00000010275 14005011336 020161� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Group; use strict; use warnings; use Moose; use namespace::autoclean; use RT::REST2::Util qw(expand_uid); extends 'RT::REST2::Resource::Record'; with 'RT::REST2::Resource::Record::Readable' => { -alias => { serialize => '_default_serialize' } }, 'RT::REST2::Resource::Record::DeletableByDisabling', => { -alias => { delete_resource => '_delete_resource' } }, 'RT::REST2::Resource::Record::Writable', => { -alias => { create_record => '_create_record' } }, 'RT::REST2::Resource::Record::Hypermedia' => { -alias => { hypermedia_links => '_default_hypermedia_links' } }; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/group/?$}, block => sub { { record_class => 'RT::Group' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/group/(\d+)/?$}, block => sub { { record_class => 'RT::Group', record_id => shift->pos(1) } }, ) } sub serialize { my $self = shift; my $data = $self->_default_serialize(@_); $data->{Members} = [ map { expand_uid($_->MemberObj->Object->UID) } @{ $self->record->MembersObj->ItemsArrayRef } ]; $data->{Disabled} = $self->record->PrincipalObj->Disabled; return $data; } sub hypermedia_links { my $self = shift; my $links = $self->_default_hypermedia_links(@_); push @$links, $self->_transaction_history_link; my $id = $self->record->id; push @$links, { ref => 'members', _url => RT::REST2->base_uri . "/group/$id/members", }; return $links; } sub create_record { my $self = shift; my $data = shift; return (\403, $self->record->loc("Permission Denied")) unless $self->current_user->HasRight( Right => "AdminGroup", Object => RT->System, ); return $self->_create_record($data); } sub delete_resource { my $self = shift; return (\403, $self->record->loc("Permission Denied")) unless $self->record->CurrentUserHasRight('AdminGroup'); return $self->_delete_resource; } sub forbidden { my $self = shift; return 0 unless $self->record->id; return !$self->record->CurrentUserHasRight('SeeGroup'); } __PACKAGE__->meta->make_immutable; 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/CustomFieldValue.pm��������������������������������������������������000644 �000765 �000024 �00000011115 14005011336 022272� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::CustomFieldValue; use strict; use warnings; use Moose; use namespace::autoclean; use RT::REST2::Util qw(expand_uid); extends 'RT::REST2::Resource::Record'; with 'RT::REST2::Resource::Record::Readable', 'RT::REST2::Resource::Record::Hypermedia', 'RT::REST2::Resource::Record::Deletable', 'RT::REST2::Resource::Record::Writable'; has 'customfield' => ( is => 'ro', isa => 'RT::CustomField', ); sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/customfield/(\d+)/value/?$}, block => sub { my ($match, $req) = @_; my $cf_id = $match->pos(1); my $cf = RT::CustomField->new($req->env->{"rt.current_user"}); $cf->Load($cf_id); return { record_class => 'RT::CustomFieldValue', customfield => $cf } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/customfield/(\d+)/value/(\d+)/?$}, block => sub { my ($match, $req) = @_; my $cf_id = $match->pos(1); my $cf = RT::CustomField->new($req->env->{"rt.current_user"}); $cf->Load($cf_id); return { record_class => 'RT::CustomFieldValue', record_id => shift->pos(2), customfield => $cf } }, ) } sub forbidden { my $self = shift; my $method = $self->request->method; if ($method eq 'GET') { return !$self->customfield->CurrentUserHasRight('SeeCustomField'); } else { return !($self->customfield->CurrentUserHasRight('AdminCustomField') ||$self->customfield->CurrentUserHasRight('AdminCustomFieldValues')); } } sub create_record { my $self = shift; my $data = shift; my ($ok, $msg) = $self->customfield->AddValue(%$data); $self->record->Load($ok) if $ok; return ($ok, $msg); } sub delete_resource { my $self = shift; my ($ok, $msg) = $self->customfield->DeleteValue($self->record->id); return $ok; } sub hypermedia_links { my $self = shift; my $record = $self->record; my $cf = $self->customfield; my $class = blessed($record); $class =~ s/^RT:://; $class = lc $class; my $id = $record->id; my $cf_class = blessed($cf); $cf_class =~ s/^RT:://; $cf_class = lc $cf_class; my $cf_id = $cf->id; my $cf_entry = expand_uid($cf->UID); my $links = [ { ref => 'self', type => $class, id => $id, _url => RT::REST2->base_uri . "/$cf_class/$cf_id/$class/$id", }, { %$cf_entry, ref => 'customfield', }, ]; return $links; } __PACKAGE__->meta->make_immutable; 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Class.pm�������������������������������������������������������������000644 �000765 �000024 �00000007774 14005011336 020144� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Class; use strict; use warnings; use Moose; use namespace::autoclean; use RT::REST2::Util qw(expand_uid); extends 'RT::REST2::Resource::Record'; with ( 'RT::REST2::Resource::Record::Readable', 'RT::REST2::Resource::Record::Hypermedia' => { -alias => { hypermedia_links => '_default_hypermedia_links' } }, 'RT::REST2::Resource::Record::DeletableByDisabling', 'RT::REST2::Resource::Record::Writable', ); sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/class/?$}, block => sub { { record_class => 'RT::Class' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/class/(\d+)/?$}, block => sub { { record_class => 'RT::Class', record_id => shift->pos(1) } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/class/([^/]+)/?$}, block => sub { my ($match, $req) = @_; my $class = RT::Class->new($req->env->{"rt.current_user"}); $class->Load($match->pos(1)); return { record => $class }; }, ), } sub hypermedia_links { my $self = shift; my $links = $self->_default_hypermedia_links(@_); my $class = $self->record; push @$links, { ref => 'create', type => 'article', _url => RT::REST2->base_uri . '/article?Class=' . $class->Id, } if $class->CurrentUserHasRight('CreateArticle'); return $links; } around 'serialize' => sub { my $orig = shift; my $self = shift; my $data = $self->$orig(@_); # Load Article Custom Fields for this class if ( my $article_cfs = $self->record->ArticleCustomFields ) { my @values; while (my $cf = $article_cfs->Next) { my $entry = expand_uid($cf->UID); my $content = { %$entry, ref => 'customfield', name => $cf->Name, }; push @values, $content; } $data->{ArticleCustomFields} = \@values; } return $data; }; __PACKAGE__->meta->make_immutable; 1; ����rt-5.0.1/lib/RT/REST2/Resource/Assets.pm������������������������������������������������������������000644 �000765 �000024 �00000004531 14005011336 020325� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Assets; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Collection'; with 'RT::REST2::Resource::Collection::QueryByJSON'; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/assets/?$}, block => sub { { collection_class => 'RT::Assets' } }, ) } __PACKAGE__->meta->make_immutable; 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/UserGroups.pm��������������������������������������������������������000644 �000765 �000024 �00000012437 14005011336 021205� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::UserGroups; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Collection'; with 'RT::REST2::Resource::Role::RequestBodyIsJSON' => {type => 'ARRAY'}; has 'user' => ( is => 'ro', isa => 'RT::User', ); sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/user/([^/]+)/groups/?$}, block => sub { my ($match, $req) = @_; my $user_id = $match->pos(1); my $user = RT::User->new($req->env->{"rt.current_user"}); $user->Load($user_id); return {user => $user, collection => $user->OwnGroups}; }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/user/([^/]+)/group/(\d+)/?$}, block => sub { my ($match, $req) = @_; my $user_id = $match->pos(1); my $group_id = $match->pos(2) || ''; my $user = RT::User->new($req->env->{"rt.current_user"}); $user->Load($user_id); my $collection = $user->OwnGroups(); $collection->Limit(FIELD => 'id', VALUE => $group_id); return {user => $user, collection => $collection}; }, ), } sub forbidden { my $self = shift; return 0 if ($self->current_user->HasRight( Right => "ModifyOwnMembership", Object => RT->System, ) && $self->current_user->id == $self->user->id) || $self->current_user->HasRight( Right => 'AdminGroupMembership', Object => RT->System); return 1; } sub serialize { my $self = shift; my $collection = $self->collection; my @results; while (my $item = $collection->Next) { my $result = { type => 'group', id => $item->id, _url => RT::REST2->base_uri . "/group/" . $item->id, }; push @results, $result; } return { count => scalar(@results) + 0, total => $collection->CountAll + 0, per_page => $collection->RowsPerPage + 0, page => ($collection->FirstRow / $collection->RowsPerPage) + 1, items => \@results, }; } sub allowed_methods { my @ok = ('GET', 'HEAD', 'DELETE', 'PUT'); return \@ok; } sub content_types_accepted {[{'application/json' => 'from_json'}]} sub delete_resource { my $self = shift; my $collection = $self->collection; while (my $group = $collection->Next) { $RT::Logger->info('Delete user ' . $self->user->Name . ' from group '.$group->id); $group->DeleteMember($self->user->id); } return 1; } sub from_json { my $self = shift; my $params = JSON::decode_json($self->request->content); my $user = $self->user; my $method = $self->request->method; my @results; if ($method eq 'PUT') { for my $param (@$params) { if ($param =~ /^\d+$/) { my $group = RT::Group->new($self->request->env->{"rt.current_user"}); $group->Load($param); push @results, $group->AddMember($user->id); } else { push @results, [0, 'You should provide group id for each group user should be added']; } } } $self->response->body(JSON::encode_json(\@results)); return; } __PACKAGE__->meta->make_immutable; 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/CustomRoles.pm�������������������������������������������������������000644 �000765 �000024 �00000004552 14005011336 021345� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::CustomRoles; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Collection'; with 'RT::REST2::Resource::Collection::QueryByJSON'; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/customroles/?$}, block => sub { { collection_class => 'RT::CustomRoles' } }, ), } __PACKAGE__->meta->make_immutable; 1; ������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Classes.pm�����������������������������������������������������������000644 �000765 �000024 �00000005223 14005011336 020457� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Classes; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Collection'; with 'RT::REST2::Resource::Collection::QueryByJSON'; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/classes/?$}, block => sub { { collection_class => 'RT::Classes' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/classes/all/?$}, block => sub { my ($match, $req) = @_; my $classes = RT::Classes->new($req->env->{"rt.current_user"}); $classes->UnLimit; return { collection => $classes }; }, ), } __PACKAGE__->meta->make_immutable; 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/GroupMembers.pm������������������������������������������������������000644 �000765 �000024 �00000014740 14005011336 021475� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::GroupMembers; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Collection'; with 'RT::REST2::Resource::Role::RequestBodyIsJSON' => {type => 'ARRAY'}; has 'group' => ( is => 'ro', ); sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/group/(\d+)/members/?$}, block => sub { my ($match, $req) = @_; my $group_id = $match->pos(1); my $group = RT::Group->new($req->env->{"rt.current_user"}); $group->Load($group_id); my $collection; my $recursively = $req->parameters->{recursively} // 0; my $users = $req->parameters->{users} // 1; my $groups = $req->parameters->{groups} // 1; if ( $users && $groups ) { if ( $recursively ) { $collection = $group->DeepMembersObj; } else { $collection = $group->MembersObj; } } elsif ( $users ) { $collection = $group->UserMembersObj(Recursively => $recursively); } elsif ( $groups ) { $collection = $group->GroupMembersObj(Recursively => $recursively); } else { $collection = RT::GroupMembers->new( $req->env->{"rt.current_user"} ); $collection->Limit(FIELD => 'id', VALUE => 0); } return {group => $group, collection => $collection}; }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/group/(\d+)/member/(\d+)/?$}, block => sub { my ($match, $req) = @_; my $group_id = $match->pos(1); my $member_id = $match->pos(2) || ''; my $group = RT::Group->new($req->env->{"rt.current_user"}); $group->Load($group_id); my $collection = $group->MembersObj; $collection->Limit(FIELD => 'MemberId', VALUE => $member_id); return {group => $group, collection => $collection}; }, ), } sub forbidden { my $self = shift; return 0 unless $self->group->id; return !$self->group->CurrentUserHasRight('AdminGroupMembership'); return 1; } sub serialize { my $self = shift; my $collection = $self->collection; my @results; while (my $item = $collection->Next) { my ($id, $class); if (ref $item eq 'RT::GroupMember' || ref $item eq 'RT::CachedGroupMember') { my $principal = $item->MemberObj; $class = $principal->IsGroup ? 'group' : 'user'; $id = $principal->id; } elsif (ref $item eq 'RT::Group') { $class = 'group'; $id = $item->id; } elsif (ref $item eq 'RT::User') { $class = 'user'; $id = $item->id; } else { next; } my $result = { type => $class, id => $id, _url => RT::REST2->base_uri . "/$class/$id", }; push @results, $result; } return { count => scalar(@results) + 0, total => $collection->CountAll, per_page => $collection->RowsPerPage + 0, page => ($collection->FirstRow / $collection->RowsPerPage) + 1, items => \@results, }; } sub allowed_methods { my @ok = ('GET', 'HEAD', 'DELETE', 'PUT'); return \@ok; } sub content_types_accepted {[{'application/json' => 'from_json'}]} sub delete_resource { my $self = shift; my $collection = $self->collection; while (my $group_member = $collection->Next) { $RT::Logger->info('Delete ' . ($group_member->MemberObj->IsGroup ? 'group' : 'user') . ' ' . $group_member->MemberId . ' from group '.$group_member->GroupId); $group_member->GroupObj->Object->DeleteMember($group_member->MemberId); } return 1; } sub from_json { my $self = shift; my $params = JSON::decode_json($self->request->content); my $group = $self->group; my $method = $self->request->method; my @results; if ($method eq 'PUT') { for my $param (@$params) { if ($param =~ /^\d+$/) { my ($ret, $msg) = $group->AddMember($param); push @results, $msg; } else { push @results, 'You should provide principal id for each member to add'; } } } $self->response->body(JSON::encode_json(\@results)); return; } __PACKAGE__->meta->make_immutable; 1; ��������������������������������rt-5.0.1/lib/RT/REST2/Resource/Article.pm�����������������������������������������������������������000644 �000765 �000024 �00000007212 14005011336 020445� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Article; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Record'; with ( 'RT::REST2::Resource::Record::Readable', 'RT::REST2::Resource::Record::Hypermedia' => { -alias => { hypermedia_links => '_default_hypermedia_links' } }, 'RT::REST2::Resource::Record::Deletable', 'RT::REST2::Resource::Record::Writable' => { -alias => { create_record => '_create_record' } }, ); sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/article/?$}, block => sub { { record_class => 'RT::Article' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/article/(\d+)/?$}, block => sub { { record_class => 'RT::Article', record_id => shift->pos(1) } }, ) } sub create_record { my $self = shift; my $data = shift; return (\400, "Invalid Class") if !$data->{Class}; my $class = RT::Class->new(RT->SystemUser); $class->Load($data->{Class}); return (\400, "Invalid Class") if !$class->Id; return ( \403, $self->record->loc("Permission Denied") ) unless $self->record->CurrentUser->HasRight( Right => 'CreateArticle', Object => $class, ) and $class->Disabled != 1; my ($ok, $txn, $msg) = $self->_create_record($data); return ($ok, $msg); } sub forbidden { my $self = shift; return 0 unless $self->record->id; return !$self->record->CurrentUserHasRight('ShowArticle'); } sub hypermedia_links { my $self = shift; my $links = $self->_default_hypermedia_links(@_); push @$links, $self->_transaction_history_link; return $links; } __PACKAGE__->meta->make_immutable; 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/ObjectCustomFieldValue.pm��������������������������������������������000644 �000765 �000024 �00000007067 14005011336 023434� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::ObjectCustomFieldValue; use strict; use warnings; use Moose; use namespace::autoclean; use RT::REST2::Util qw( error_as_json ); extends 'RT::REST2::Resource::Record'; with 'RT::REST2::Resource::Record::WithETag'; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/download/cf/(\d+)/?$}, block => sub { { record_class => 'RT::ObjectCustomFieldValue', record_id => shift->pos(1) } }, ) } sub allowed_methods { ['GET', 'HEAD'] } sub content_types_provided { my $self = shift; { [ {$self->record->ContentType || 'text/plain; charset=utf-8' => 'to_binary'} ] }; } sub forbidden { my $self = shift; return 0 unless $self->record->id; return !$self->record->CurrentUserHasRight('SeeCustomField'); } sub to_binary { my $self = shift; unless ($self->record->CustomFieldObj->Type =~ /^(?:Image|Binary)$/) { return error_as_json( $self->response, \400, "Only Image and Binary CustomFields can be downloaded"); } my $content_type = $self->record->ContentType || 'text/plain; charset=utf-8'; if (RT->Config->Get('AlwaysDownloadAttachments')) { $self->response->headers_out->{'Content-Disposition'} = "attachment"; } elsif (!RT->Config->Get('TrustHTMLAttachments')) { $content_type = 'text/plain; charset=utf-8' if ($content_type =~ /^text\/html/i); } $self->response->content_type($content_type); my $content = $self->record->LargeContent; $self->response->content_length(length $content); $self->response->body($content); } __PACKAGE__->meta->make_immutable; 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/CustomField.pm�������������������������������������������������������000644 �000765 �000024 �00000010134 14005011336 021275� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::CustomField; use strict; use warnings; use Moose; use namespace::autoclean; extends 'RT::REST2::Resource::Record'; with 'RT::REST2::Resource::Record::Readable', => { -alias => { serialize => '_default_serialize' } }, 'RT::REST2::Resource::Record::Hypermedia' => { -alias => { hypermedia_links => '_default_hypermedia_links' } }, 'RT::REST2::Resource::Record::DeletableByDisabling', 'RT::REST2::Resource::Record::Writable'; sub dispatch_rules { Path::Dispatcher::Rule::Regex->new( regex => qr{^/customfield/?$}, block => sub { { record_class => 'RT::CustomField' } }, ), Path::Dispatcher::Rule::Regex->new( regex => qr{^/customfield/(\d+)/?$}, block => sub { { record_class => 'RT::CustomField', record_id => shift->pos(1) } }, ) } sub serialize { my $self = shift; my $data = $self->_default_serialize(@_); if ($data->{Values}) { if ($self->record->BasedOn && defined $self->request->param('category')) { my $category = $self->request->param('category') || ''; @{ $data->{Values} } = grep { ( $_->{category} // '' ) eq $category } @{ $data->{Values} }; } @{$data->{Values}} = map {$_->{name}} @{$data->{Values}}; } return $data; } sub forbidden { my $self = shift; my $method = $self->request->method; if ($self->record->id) { if ($method eq 'GET') { return !$self->record->CurrentUserHasRight('SeeCustomField'); } else { return !($self->record->CurrentUserHasRight('SeeCustomField') && $self->record->CurrentUserHasRight('AdminCustomField')); } } else { return !$self->current_user->HasRight(Right => "AdminCustomField", Object => RT->System); } return 0; } sub hypermedia_links { my $self = shift; my $links = $self->_default_hypermedia_links(@_); if ($self->record->IsSelectionType) { push @$links, { ref => 'customfieldvalues', _url => RT::REST2->base_uri . "/customfield/" . $self->record->id . "/values", }; } return $links; } __PACKAGE__->meta->make_immutable; 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Record/Deletable.pm��������������������������������������������������000644 �000765 �000024 �00000004643 14005011336 022166� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Record::Deletable; use strict; use warnings; use Moose::Role; use namespace::autoclean; requires 'record'; requires 'record_class'; sub delete_resource { my $self = shift; my ($ok, $msg) = $self->record->Delete; RT->Logger->debug("Failed to delete ", $self->record_class, " #", $self->record->id, ": $msg") unless $ok; # XXX TODO: why can't I return a status code here? only true/false is available. return $ok; } 1; ���������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Record/DeletableByDisabling.pm���������������������������������������000644 �000765 �000024 �00000004544 14005011336 024276� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Record::DeletableByDisabling; use strict; use warnings; use Moose::Role; use namespace::autoclean; with 'RT::REST2::Resource::Record::Deletable'; sub delete_resource { my $self = shift; my ($ok, $msg) = $self->record->SetDisabled(1); RT->Logger->debug("Failed to disable ", $self->record_class, " #", $self->record->id, ": $msg") unless $ok; return $ok; } 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Record/WithETag.pm���������������������������������������������������000644 �000765 �000024 �00000005517 14005011336 021762� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Record::WithETag; use strict; use warnings; use Moose::Role; use namespace::autoclean; requires 'record'; sub last_modified { my $self = shift; return unless $self->record->_Accessible("LastUpdated" => "read"); return $self->record->LastUpdatedObj->W3CDTF( Timezone => 'UTC' ); } sub generate_etag { my $self = shift; my $record = $self->record; if ($record->can('Transactions')) { my $txns = $record->Transactions; $txns->OrderByCols({ FIELD => 'id', ORDER => 'DESC' }); # $txns->Count could be inaccurate because of ACL, # checking ->First directly is more reliable. if ( my $first = $txns->First ) { return $first->Id; } } # fall back to last-modified time, which is commonly accepted even # though there's a risk of race condition return $self->last_modified; } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Record/Hypermedia.pm�������������������������������������������������000644 �000765 �000024 �00000011620 14005011336 022365� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Record::Hypermedia; use strict; use warnings; use Moose::Role; use namespace::autoclean; use RT::REST2::Util qw(expand_uid expand_uri custom_fields_for); use JSON qw(to_json); sub hypermedia_links { my $self = shift; return [ $self->_self_link, $self->_rtlink_links, $self->_customfield_links, $self->_customrole_links ]; } sub _self_link { my $self = shift; my $record = $self->record; my $class = blessed($record); $class =~ s/^RT:://; $class = lc $class; my $id = $record->id; return { ref => 'self', type => $class, id => $id, _url => RT::REST2->base_uri . "/$class/$id", }; } sub _transaction_history_link { my $self = shift; my $self_link = $self->_self_link; return { ref => 'history', _url => $self_link->{_url} . '/history', }; } my %link_refs = ( DependsOn => 'depends-on', DependedOnBy => 'depended-on-by', MemberOf => 'parent', Members => 'child', RefersTo => 'refers-to', ReferredToBy => 'referred-to-by', ); sub _rtlink_links { my $self = shift; my $record = $self->record; my @links; for my $relation (keys %link_refs) { my $ref = $link_refs{$relation}; my $mode = $RT::Link::TYPEMAP{$relation}{Mode}; my $type = $RT::Link::TYPEMAP{$relation}{Type}; my $method = $mode . "Obj"; my $links = $record->$relation; while (my $link = $links->Next) { my $entry; if ( $link->LocalTarget and $link->LocalBase ){ # Internal links $entry = expand_uid($link->$method->UID); } else { # Links to external URLs $entry = expand_uri($link->$mode); } push @links, { %$entry, ref => $ref, }; } } return @links; } sub _customfield_links { my $self = shift; my $record = $self->record; my @links; if (my $cfs = custom_fields_for($record)) { while (my $cf = $cfs->Next) { my $entry = expand_uid($cf->UID); push @links, { %$entry, ref => 'customfield', name => $cf->Name, }; } } return @links; } sub _customrole_links { my $self = shift; my $record = $self->record; my @links; return unless $record->DOES('RT::Record::Role::Roles'); for my $role ($record->Roles(UserDefined => 1)) { if ($role =~ /^RT::CustomRole-(\d+)$/) { my $cr = RT::CustomRole->new($record->CurrentUser); $cr->Load($1); if ($cr->Id) { my $entry = expand_uid($cr->UID); push @links, { %$entry, group_type => $cr->GroupType, ref => 'customrole', }; } } } return @links; } 1; ����������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Record/Readable.pm���������������������������������������������������000644 �000765 �000024 �00000006614 14005011336 022004� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Record::Readable; use strict; use warnings; use Moose::Role; use namespace::autoclean; requires 'record'; requires 'record_class'; requires 'current_user'; requires 'base_uri'; with 'RT::REST2::Resource::Record::WithETag'; use JSON (); use RT::REST2::Util qw( serialize_record ); use Scalar::Util qw( blessed ); sub serialize { my $self = shift; my $record = $self->record; my $data = serialize_record($record); for my $field (keys %$data) { my $result = $data->{$field}; if ($record->can($field . 'Obj')) { my $method = $field . 'Obj'; my $obj = $record->$method; my $param_field = "fields[$field]"; my @subfields = split(/,/, $self->request->param($param_field) || ''); for my $subfield (@subfields) { my $subfield_result = $self->expand_field($obj, $subfield, $param_field); $result->{$subfield} = $subfield_result if defined $subfield_result; } } $data->{$field} = $result; } if ($self->does('RT::REST2::Resource::Record::Hypermedia')) { $data->{_hyperlinks} = $self->hypermedia_links; } return $data; } sub charsets_provided { [ 'utf-8' ] } sub default_charset { 'utf-8' } sub content_types_provided { [ { 'application/json' => 'to_json' }, ] } sub to_json { my $self = shift; return JSON::to_json($self->serialize, { pretty => 1 }); } 1; ��������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Record/Writable.pm���������������������������������������������������000644 �000765 �000024 �00000041071 14005011336 022052� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Record::Writable; use strict; use warnings; use Moose::Role; use namespace::autoclean; use JSON (); use RT::REST2::Util qw( deserialize_record error_as_json expand_uid update_custom_fields process_uploads ); use List::MoreUtils 'uniq'; with 'RT::REST2::Resource::Role::RequestBodyIsJSON' => { type => 'HASH' }; requires 'record'; requires 'record_class'; sub post_is_create { 1 } sub allow_missing_post { 1 } sub create_path_after_handler { 1 } sub create_path { $_[0]->record->id || undef } sub content_types_accepted { [ {'application/json' => 'from_json'}, { 'multipart/form-data' => 'from_multipart' } ] } sub from_multipart { my $self = shift; my $json_str = $self->request->parameters->{JSON}; return error_as_json( $self->response, \400, "Json is a required field for multipart/form-data") unless $json_str; my $json = JSON::decode_json($json_str); my $cfs = delete $json->{CustomFields}; if ($cfs) { foreach my $id (keys %$cfs) { my $value = delete $cfs->{$id}; if (ref($value) eq 'ARRAY') { my @values; foreach my $single_value (@$value) { if ( ref $single_value eq 'HASH' && ( my $field_name = $single_value->{UploadField} ) ) { if ( my $file = $self->request->upload($field_name) ) { push @values, process_uploads($file); } } else { push @values, $single_value; } } $cfs->{$id} = \@values; } elsif ( ref $value eq 'HASH' && ( my $field_name = $value->{UploadField} ) ) { if ( my $file = $self->request->upload($field_name) ) { ( $cfs->{$id} ) = process_uploads($file); } } else { $cfs->{$id} = $value; } } $json->{CustomFields} = $cfs; } if ( my @attachments = $self->request->upload('Attachments') ) { $json->{Attachments} = [ process_uploads(@attachments) ]; } return $self->from_json($json); } sub from_json { my $self = shift; my $params = shift; if ( !$params ) { if ( my $content = $self->request->content ) { $params = JSON::decode_json($content); } else { $params = {}; } } %$params = ( %$params, %{ $self->request->query_parameters->mixed }, ); my $data = deserialize_record( $self->record, $params, ); my $method = $self->request->method; return $method eq 'PUT' ? $self->update_resource($data) : $method eq 'POST' ? $self->create_resource($data) : \501 ; } sub update_record { my $self = shift; my $data = shift; my @results = $self->record->Update( ARGSRef => $data, AttributesRef => [ $self->record->WritableAttributes ], ); push @results, update_custom_fields($self->record, $data->{CustomFields}); push @results, $self->_update_role_members($data); push @results, $self->_update_links($data); push @results, $self->_update_disabled($data->{Disabled}) unless grep { $_ eq 'Disabled' } $self->record->WritableAttributes; push @results, $self->_update_privileged($data->{Privileged}) unless grep { $_ eq 'Privileged' } $self->record->WritableAttributes; # XXX TODO: Figure out how to return success/failure? Core RT::Record's # ->Update will need to be replaced or improved. return @results; } sub _update_role_members { my $self = shift; my $data = shift; my $record = $self->record; return unless $record->DOES('RT::Record::Role::Roles'); my @results; foreach my $role ($record->Roles) { next unless exists $data->{$role}; # special case: RT::Ticket->Update already handles Owner for us next if $role eq 'Owner' && $record->isa('RT::Ticket'); my $val = $data->{$role}; if ($record->Role($role)->{Single}) { if (ref($val) eq 'ARRAY') { $val = $val->[0]; } elsif (ref($val)) { die "Invalid value type for role $role"; } my ($ok, $msg); if ($record->can('AddWatcher')) { ($ok, $msg) = $record->AddWatcher( Type => $role, User => $val, ); } else { ($ok, $msg) = $record->AddRoleMember( Type => $role, User => $val, ); } push @results, $msg; } else { my %count; my @vals; for (ref($val) eq 'ARRAY' ? @$val : $val) { my ($principal_id, $msg); if (/^\d+$/) { $principal_id = $_; } elsif ($record->can('CanonicalizePrincipal')) { ((my $principal), $msg) = $record->CanonicalizePrincipal(User => $_); $principal_id = $principal->Id; } else { my $user = RT::User->new($record->CurrentUser); if (/@/) { ((my $ok), $msg) = $user->LoadOrCreateByEmail( $_ ); } else { ((my $ok), $msg) = $user->Load( $_ ); } $principal_id = $user->PrincipalId; } if (!$principal_id) { push @results, $msg; next; } push @vals, $principal_id; $count{$principal_id}++; } my $group = $record->RoleGroup($role); my $members = $group->MembersObj; while (my $member = $members->Next) { $count{$member->MemberId}--; } # RT::Ticket has specialized methods my $add_method = $record->can('AddWatcher') ? 'AddWatcher' : 'AddRoleMember'; my $del_method = $record->can('DeleteWatcher') ? 'DeleteWatcher' : 'DeleteRoleMember'; # we want to provide a stable order, so first go by the order # provided in the argument list, and then for any role members # that are being removed, remove in sorted order for my $id (uniq(@vals, sort keys %count)) { my $count = $count{$id}; if ($count == 0) { # new == old, no change needed } elsif ($count > 0) { # new > old, need to add new while ($count-- > 0) { my ($ok, $msg) = $record->$add_method( Type => $role, PrincipalId => $id, ); push @results, $msg; } } elsif ($count < 0) { # old > new, need to remove old while ($count++ < 0) { my ($ok, $msg) = $record->$del_method( Type => $role, PrincipalId => $id, ); push @results, $msg; } } } } } return @results; } sub _update_links { my $self = shift; my $data = shift; my $record = $self->record; return unless $record->DOES('RT::Record::Role::Links'); my @results; for my $name ( grep { $_ ne 'MergedInto' } sort keys %RT::Link::TYPEMAP ) { my $mode = $RT::Link::TYPEMAP{$name}{Mode}; my $type = $RT::Link::TYPEMAP{$name}{Type}; if ( $data->{$name} ) { my $links = $record->Links( $mode eq 'Base' ? 'Target' : 'Base', $type ); my %current; while ( my $link = $links->Next ) { my $uri_method = $mode . 'URI'; my $uri = $link->$uri_method; if ( $uri->IsLocal ) { my $local_method = "Local$mode"; $current{ $link->$local_method } = 1; } else { $current{ $link->$mode } = 1; } } for my $value ( ref $data->{$name} eq 'ARRAY' ? @{ $data->{$name} } : $data->{$name} ) { if ( $current{$value} ) { delete $current{$value}; } else { my ( $ok, $msg ) = $record->AddLink( $mode => $value, Type => $type, ); push @results, $msg; } } for my $value ( sort keys %current ) { my ( $ok, $msg ) = $record->DeleteLink( $mode => $value, Type => $type, ); push @results, $msg; } } else { for my $action (qw/Add Delete/) { my $arg = "$action$name"; next unless $data->{$arg}; for my $value ( ref $data->{$arg} eq 'ARRAY' ? @{ $data->{$arg} } : $data->{$arg} ) { my $method = $action . 'Link'; my ( $ok, $msg ) = $record->$method( $mode => $value, Type => $type, ); push @results, $msg; } } } } return @results; } sub _update_disabled { my $self = shift; my $data = shift; my @results; my $record = $self->record; return unless defined $data and $data =~ /^[01]$/; return unless $record->can('SetDisabled'); my ($ok, $msg) = $record->SetDisabled($data); push @results, $msg; return @results; } sub _update_privileged { my $self = shift; my $data = shift; my @results; my $record = $self->record; return unless defined $data and $data =~ /^[01]$/; return unless $record->can('SetPrivileged'); my ($ok, $msg) = $record->SetPrivileged($data); push @results, $msg; return @results; } sub update_resource { my $self = shift; my $data = shift; if (not $self->resource_exists) { return error_as_json( $self->response, \404, "Resource does not exist; use POST to create"); } my @results = $self->update_record($data); $self->response->body( JSON::encode_json(\@results) ); return; } sub create_record { my $self = shift; my $data = shift; my $record = $self->record; my %args = %$data; my $cfs = delete $args{CustomFields}; # Lookup CustomFields by name. if ($cfs) { foreach my $id (keys(%$cfs)) { my $value = delete $cfs->{$id}; if ( ref($value) eq 'HASH' ) { foreach my $field ( 'FileName', 'FileType', 'FileContent' ) { return ( 0, 0, "$field is a required field for Image/Binary ObjectCustomFieldValue" ) unless $value->{$field}; } $value->{Value} = delete $value->{FileName}; $value->{ContentType} = delete $value->{FileType}; $value->{LargeContent} = MIME::Base64::decode_base64( delete $value->{FileContent} ); } elsif ( ref($value) eq 'ARRAY' ) { my $i = 0; foreach my $single_value (@$value) { if ( ref($single_value) eq 'HASH' ) { foreach my $field ( 'FileName', 'FileType', 'FileContent' ) { return ( 0, 0, "$field is a required field for Image/Binary ObjectCustomFieldValue" ) unless $single_value->{$field}; } $single_value->{Value} = delete $single_value->{FileName}; $single_value->{ContentType} = delete $single_value->{FileType}; $single_value->{LargeContent} = MIME::Base64::decode_base64( delete $single_value->{FileContent} ); $value->[$i] = $single_value; } $i++; } } $cfs->{$id} = $value; if ($id !~ /^\d+$/) { my $cf = $record->LoadCustomFieldByIdentifier($id); if ($cf->Id) { $cfs->{$cf->Id} = $cfs->{$id}; delete $cfs->{$id}; } else { # I would really like to return an error message, but, how? # RT appears to treat missing permission to a CF or # non-existance of a CF as a non-fatal error. RT->Logger->error( $record->loc( "Custom field [_1] not found", $id ) ); } } } } # if a record class handles CFs in ->Create, use it (so it doesn't generate # spurious transactions and interfere with default values, etc). Otherwise, # add OCFVs after ->Create if ($record->isa('RT::Ticket') || $record->isa('RT::Asset') || $record->isa('RT::Article') ) { if ($cfs) { while (my ($id, $value) = each(%$cfs)) { delete $cfs->{$id}; $args{"CustomField-$id"} = $value; } } } my $method = $record->isa('RT::Group') ? 'CreateUserDefinedGroup' : 'Create'; my ($ok, @rest) = $record->$method(%args); if ($ok && $cfs) { update_custom_fields($record, $cfs); } return ($ok, @rest); } sub create_resource { my $self = shift; my $data = shift; if ($self->resource_exists) { return error_as_json( $self->response, \409, "Resource already exists; use PUT to update"); } my ($ok, $msg) = $self->create_record($data); if (ref($ok)) { return error_as_json( $self->response, $ok, $msg || "Create failed for unknown reason"); } elsif ($ok) { my $response = $self->response; my $body = JSON::encode_json(expand_uid($self->record->UID)); $response->content_type( "application/json; charset=utf-8" ); $response->content_length( length $body ); $response->body( $body ); return; } else { return error_as_json( $self->response, \400, $msg || "Create failed for unknown reason"); } } 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Role/RequestBodyIsJSON.pm��������������������������������������������000644 �000765 �000024 �00000006324 14005011336 023222� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Role::RequestBodyIsJSON; use strict; use warnings; use MooseX::Role::Parameterized; use namespace::autoclean; use JSON (); use RT::REST2::Util qw( error_as_json ); use Moose::Util::TypeConstraints qw( enum ); parameter 'type' => ( isa => enum([qw(ARRAY HASH)]), default => 'HASH', ); role { my $P = shift; around 'malformed_request' => sub { my $orig = shift; my $self = shift; my $malformed = $self->$orig(@_); return $malformed if $malformed; my $request = $self->request; return 0 unless $request->method =~ /^(PUT|POST)$/; return 0 unless $request->header('Content-Type') =~ /^application\/json/; return 0 unless $request->content; # allow empty content my $json = eval { JSON::from_json($request->content) }; if ($@ or not $json) { my $error = $@; $error =~ s/ at \S+? line \d+\.?$//; error_as_json($self->response, undef, "JSON parse error: $error"); return 1; } elsif (ref $json ne $P->type) { error_as_json($self->response, undef, "JSON object must be a ", $P->type); return 1; } else { return 0; } }; }; 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Collection/ProcessPOSTasGET.pm���������������������������������������000644 �000765 �000024 �00000004546 14005011336 024174� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Collection::ProcessPOSTasGET; use strict; use warnings; use Moose::Role; use namespace::autoclean; use Web::Machine::FSM::States qw( is_status_code ); requires 'to_json'; sub process_post { my $self = shift; my $json = $self->to_json; unless (is_status_code($json)) { $self->response->body( $json ); return 1; } else { return $json; } } 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Resource/Collection/QueryByJSON.pm��������������������������������������������000644 �000765 �000024 �00000010065 14005011336 023247� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Resource::Collection::QueryByJSON; use strict; use warnings; use Moose::Role; use namespace::autoclean; use JSON (); with ( 'RT::REST2::Resource::Collection::ProcessPOSTasGET', 'RT::REST2::Resource::Role::RequestBodyIsJSON' => { type => 'ARRAY' }, ); requires 'collection'; has 'query' => ( is => 'ro', isa => 'ArrayRef[HashRef]', required => 1, lazy_build => 1, ); sub _build_query { my $self = shift; my $content = $self->request->method eq 'GET' ? $self->request->param('query') : $self->request->content; return $content ? JSON::decode_json($content) : []; } sub allowed_methods { [ 'GET', 'POST' ] } sub searchable_fields { $_[0]->collection->RecordClass->ReadableAttributes } sub limit_collection { my $self = shift; my $collection = $self->collection; my $query = $self->query; my @fields = $self->searchable_fields; my %searchable = map {; $_ => 1 } @fields; $collection->{'find_disabled_rows'} = 1 if $self->request->param('find_disabled_rows'); for my $limit (@$query) { next unless $limit->{field} and $searchable{$limit->{field}} and defined $limit->{value}; $collection->Limit( FIELD => $limit->{field}, VALUE => $limit->{value}, ( $limit->{operator} ? (OPERATOR => $limit->{operator}) : () ), CASESENSITIVE => ($limit->{case_sensitive} || 0), ( $limit->{entry_aggregator} ? (ENTRYAGGREGATOR => $limit->{entry_aggregator}) : () ), ); } my @orderby_cols; my @orders = $self->request->param('order'); foreach my $orderby ($self->request->param('orderby')) { my $order = shift @orders || 'ASC'; $order = uc($order); $order = 'ASC' unless $order eq 'DESC'; push @orderby_cols, {FIELD => $orderby, ORDER => $order}; } $self->collection->OrderByCols(@orderby_cols) if @orderby_cols; return 1; } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Middleware/Auth.pm������������������������������������������������������������000644 �000765 �000024 �00000012027 14005011336 020251� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Middleware::Auth; use strict; use warnings; use base 'Plack::Middleware'; our @auth_priority = qw( login_from_cookie login_from_authtoken login_from_basicauth ); sub call { my ($self, $env) = @_; RT::ConnectToDatabase(); for my $method (@auth_priority) { last if $env->{'rt.current_user'} = $self->$method($env); } if ($env->{'rt.current_user'}) { return $self->app->($env); } else { return $self->unauthorized($env); } } sub login_from_cookie { my ($self, $env) = @_; # allow reusing authentication from the ordinary web UI so that # among other things our JS can use REST2 if ($env->{HTTP_COOKIE}) { no warnings 'redefine'; # this is foul but LoadSessionFromCookie doesn't have a hook for # saying "look up cookie in my $env". this beats duplicating # LoadSessionFromCookie local *RT::Interface::Web::RequestENV = sub { return $env->{$_[0]} } if RT::Handle::cmp_version($RT::VERSION, '4.4.0') >= 0; # similar but for 4.2 local %ENV = %$env if RT::Handle::cmp_version($RT::VERSION, '4.4.0') < 0; local *HTML::Mason::Commands::session; RT::Interface::Web::LoadSessionFromCookie(); if (RT::Interface::Web::_UserLoggedIn) { return $HTML::Mason::Commands::session{CurrentUser}; } } return; } sub login_from_authtoken { my ($self, $env) = @_; # needs RT::Authen::Token extension return unless RT::AuthToken->can('Create'); # Authorization: token 1-14-abcdef header my ($authstring) = ($env->{HTTP_AUTHORIZATION}||'') =~ /^token (.*)$/i; # or ?token=1-14-abcdef query parameter $authstring ||= Plack::Request->new($env)->parameters->{token}; if ($authstring) { my ($user_obj, $token) = RT::Authen::Token->UserForAuthString($authstring); return $user_obj; } return; } sub login_from_basicauth { my ($self, $env) = @_; require MIME::Base64; if (($env->{HTTP_AUTHORIZATION}||'') =~ /^basic (.*)$/i) { my($user, $pass) = split /:/, (MIME::Base64::decode($1) || ":"), 2; my $cu = RT::CurrentUser->new; $cu->Load($user); if ($cu->id and $cu->IsPassword($pass)) { return $cu; } else { RT->Logger->info("Failed login for $user"); return; } } return; } sub _looks_like_browser { my $self = shift; my $env = shift; return 1 if $env->{HTTP_COOKIE}; return 1 if $env->{HTTP_USER_AGENT} =~ /Mozilla/; return 0; } sub unauthorized { my $self = shift; my $env = shift; if ($self->_looks_like_browser($env)) { my $url = RT->Config->Get('WebPath') . '/'; return [ 302, [ 'Location' => $url ], [ "Login required" ], ]; } else { my $body = 'Authorization required'; return [ 401, [ 'Content-Type' => 'text/plain', 'Content-Length' => length $body ], [ $body ], ]; } } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Middleware/Log.pm�������������������������������������������������������������000644 �000765 �000024 �00000004435 14005011336 020075� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Middleware::Log; use strict; use warnings; use base 'Plack::Middleware'; sub call { my ( $self, $env ) = @_; # XXX TODO: logging of SQL queries in RT's framework for doing so $env->{'psgix.logger'} = sub { my $what = shift; RT->Logger->log(%$what); }; return $self->app->($env); } 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/REST2/Middleware/ErrorAsJSON.pm�����������������������������������������������������000644 �000765 �000024 �00000005457 14005011336 021430� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::REST2::Middleware::ErrorAsJSON; use strict; use warnings; use base 'Plack::Middleware'; use Plack::Util; use HTTP::Status qw(is_error status_message); use RT::REST2::Util 'error_as_json'; sub call { my ( $self, $env ) = @_; my $res = $self->app->($env); return Plack::Util::response_cb($res, sub { my $psgi_res = shift; my $status_code = $psgi_res->[0]; my $headers = $psgi_res->[1]; my $content_type = Plack::Util::header_get($headers, 'content-type'); my $is_json = $content_type && $content_type =~ m/json/i; if ( is_error($status_code) && !$is_json ) { my $plack_res = Plack::Response->new($status_code, $headers); error_as_json($plack_res, undef, status_message($status_code)); @$psgi_res = @{ $plack_res->finalize }; } return; }); } 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/URI/user.pm�������������������������������������������������������������������������000644 �000765 �000024 �00000012455 14005011336 016056� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::URI::user; use base qw/RT::URI::base/; require RT::User; =head1 NAME RT::URI::user - Internal URIs for linking to an L<RT::User> =head1 DESCRIPTION This class should rarely be used directly, but via L<RT::URI> instead. Represents, parses, and generates internal RT URIs such as: user:42 user://example.com/42 These URIs are used to link between objects in RT such as referencing an RT user record from a ticket in the Links section. =head1 METHODS Much of the interface below is dictated by L<RT::URI> and L<RT::URI::base>. =head2 Scheme Return the URI scheme for groups =cut sub Scheme { "user" } =head2 LocalURIPrefix Returns the site-specific prefix for a local group URI =cut sub LocalURIPrefix { my $self = shift; return $self->Scheme . "://" . RT->Config->Get('Organization'); } =head2 IsLocal Returns a true value, the grouup ID, if this object represents a local group, undef otherwise. =cut sub IsLocal { my $self = shift; my $prefix = $self->LocalURIPrefix; return $1 if $self->{uri} =~ qr!^\Q$prefix\E/(\d+)!i; return undef; } =head2 URIForObject RT::Group Returns the URI for a local L<RT::Group> object =cut sub URIForObject { my $self = shift; my $obj = shift; return $self->LocalURIPrefix . '/' . $obj->Id; } =head2 ParseURI URI Primarily used by L<RT::URI> to set internal state. Figures out from an C<user:> URI whether it refers to a local user and the user ID. Returns the user ID if local, otherwise returns false. =cut sub ParseURI { my $self = shift; my $uri = shift; my $scheme = $self->Scheme; # canonicalize "42" and "user:42" -> user://example.com/42 if ($uri =~ /^(?:\Q$scheme\E:)?(\d+)$/i) { my $user_obj = RT::User->new( $self->CurrentUser ); my ($ret, $msg) = $user_obj->Load($1); if ( $ret ) { $self->{'uri'} = $user_obj->URI; $self->{'object'} = $user_obj; } else { RT::Logger->error("Unable to load user for id: $1: $msg"); return; } } else { $self->{'uri'} = $uri; } my $user = RT::User->new( $self->CurrentUser ); if ( my $id = $self->IsLocal ) { $user->Load($id); if ($user->id) { $self->{'object'} = $user; } else { RT->Logger->error("Can't load User #$id by URI '$uri'"); return; } } return $user->id; } =head2 Object Returns the object for this URI, if it's local. Otherwise returns undef. =cut sub Object { my $self = shift; return $self->{'object'}; } =head2 HREF If this is a local group, return an HTTP URL for it. Otherwise, return its URI. =cut sub HREF { my $self = shift; if ($self->IsLocal and $self->Object) { return RT->Config->Get('WebURL') . "User/Summary.html?id=" . $self->Object->Id; } else { return $self->URI; } } =head2 AsString Returns a description of this object =cut sub AsString { my $self = shift; if ($self->IsLocal and $self->Object) { my $object = $self->Object; if ( $object->Name ) { return $self->loc('[_1] (User #[_2])', $object->Name, $object->id); } else { return $self->loc('User #[_1]', $object->id); } } else { return $self->SUPER::AsString(@_); } } 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/URI/asset.pm������������������������������������������������������������������������000644 �000765 �000024 �00000012621 14005011336 016212� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::URI::asset; use base qw/RT::URI::base/; require RT::Asset; =head1 NAME RT::URI::asset - Internal URIs for linking to an L<RT::Asset> =head1 DESCRIPTION This class should rarely be used directly, but via L<RT::URI> instead. Represents, parses, and generates internal RT URIs such as: asset:42 asset://example.com/42 These URIs are used to link between objects in RT such as associating an asset with a ticket or an asset with another asset. =head1 METHODS Much of the interface below is dictated by L<RT::URI> and L<RT::URI::base>. =head2 Scheme Return the URI scheme for assets =cut sub Scheme { "asset" } =head2 LocalURIPrefix Returns the site-specific prefix for a local asset URI =cut sub LocalURIPrefix { my $self = shift; return $self->Scheme . "://" . RT->Config->Get('Organization'); } =head2 IsLocal Returns a true value, the asset ID, if this object represents a local asset, undef otherwise. =cut sub IsLocal { my $self = shift; my $prefix = $self->LocalURIPrefix; return $1 if $self->{uri} =~ qr!^\Q$prefix\E/(\d+)!i; return undef; } =head2 URIForObject RT::Asset Returns the URI for a local L<RT::Asset> object =cut sub URIForObject { my $self = shift; my $obj = shift; return $self->LocalURIPrefix . '/' . $obj->Id; } =head2 ParseURI URI Primarily used by L<RT::URI> to set internal state. Figures out from an C<asset:> URI whether it refers to a local asset and the asset ID. Returns the asset ID if local, otherwise returns false. =cut sub ParseURI { my $self = shift; my $uri = shift; my $scheme = $self->Scheme; # canonicalize "42" and "asset:42" -> asset://example.com/42 if ($uri =~ /^(?:\Q$scheme\E:)?(\d+)$/i) { my $asset_obj = RT::Asset->new( $self->CurrentUser ); my ($ret, $msg) = $asset_obj->Load($1); if ( $ret ) { $self->{'uri'} = $asset_obj->URI; $self->{'object'} = $asset_obj; } else { RT::Logger->error("Unable to load asset for id: $1: $msg"); return; } } else { $self->{'uri'} = $uri; } my $asset = RT::Asset->new( $self->CurrentUser ); if ( my $id = $self->IsLocal ) { $asset->Load($id); if ($asset->id) { $self->{'object'} = $asset; } else { RT->Logger->error("Can't load Asset #$id by URI '$uri'"); return; } } return $asset->id; } =head2 Object Returns the object for this URI, if it's local. Otherwise returns undef. =cut sub Object { my $self = shift; return $self->{'object'}; } =head2 HREF If this is a local asset, return an HTTP URL for it. Otherwise, return its URI. =cut sub HREF { my $self = shift; if ($self->IsLocal and $self->Object) { return RT->Config->Get('WebURL') . ( $self->CurrentUser->Privileged ? "" : "SelfService/" ) . "Asset/Display.html?id=" . $self->Object->Id; } else { return $self->URI; } } =head2 AsString Returns a description of this object =cut sub AsString { my $self = shift; if ($self->IsLocal and $self->Object) { my $object = $self->Object; if ( $object->Name ) { return $self->loc('Asset #[_1]: [_2]', $object->id, $object->Name); } else { return $self->loc('Asset #[_1]', $object->id); } } else { return $self->SUPER::AsString(@_); } } 1; ���������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/URI/a.pm����������������������������������������������������������������������������000644 �000765 �000024 �00000005153 14005011336 015315� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::URI::a; use strict; use warnings; use base qw/RT::URI::fsck_com_article/; use RT::Article; my $scheme = "a"; =head2 ParseURI URI When handed an a: URI, figures out if it is an article. =cut sub ParseURI { my $self = shift; my $uri = shift; # "a:<articlenum>" # Pass this off to fsck_com_article, which is equipped to deal with # articles after stripping off the a: prefix. if ($uri =~ /^$scheme:(\d+)/) { my $value = $1; return $self->SUPER::ParseURI($value); } else { $self->{'uri'} = $uri; return undef; } } =head2 Scheme Return the URI scheme =cut sub Scheme { return $scheme; } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/URI/fsck_com_rt.pm������������������������������������������������������������������000644 �000765 �000024 �00000013411 14005011336 017362� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::URI::fsck_com_rt; use base 'RT::URI::base'; =head2 LocalURIPrefix Returns the prefix for a local URI. =cut sub LocalURIPrefix { my $self = shift; my $prefix = $self->Scheme. "://". RT->Config->Get('Organization'); return ($prefix); } =head2 ObjectType =cut sub ObjectType { my $self = shift; my $object = shift || $self->Object; my $type = 'ticket'; if (ref($object) && (ref($object) ne 'RT::Ticket')) { $type = ref($object); } return ($type); } =head2 URIForObject RT::Record Returns the RT URI for a local RT::Record object =cut sub URIForObject { my $self = shift; my $obj = shift; return ($self->LocalURIPrefix ."/". $self->ObjectType($obj) ."/". $obj->Id); } =head2 ParseURI URI When handed an fsck.com-rt: URI, figures out things like whether its a local record and what its ID is =cut sub ParseURI { my $self = shift; my $uri = shift; if ( $uri =~ /^\d+$/ ) { use RT::Ticket; my $ticket = RT::Ticket->new( $self->CurrentUser ); $ticket->Load( $uri ); $self->{'uri'} = $ticket->URI; $self->{'object'} = $ticket; return ($ticket->id); } else { $self->{'uri'} = $uri; } #If it's a local URI, load the ticket object and return its URI if ( $self->IsLocal ) { my $local_uri_prefix = $self->LocalURIPrefix; if ( $self->{'uri'} =~ /^\Q$local_uri_prefix\E\/(.*?)\/(\d+)$/i ) { my $type = $1; my $id = $2; if ( $type eq 'ticket' ) { $type = 'RT::Ticket' } # We can instantiate any RT::Record subtype. but not anything else if ( UNIVERSAL::isa( $type, 'RT::Record' ) ) { my $record = $type->new( $self->CurrentUser ); $record->Load($id); if ( $record->Id ) { $self->{'object'} = $record; return ( $record->Id ); } } } } return undef; } =head2 IsLocal Returns true if this URI is for a local ticket. Returns undef otherwise. =cut sub IsLocal { my $self = shift; my $local_uri_prefix = $self->LocalURIPrefix; if ( $self->{'uri'} =~ /^\Q$local_uri_prefix/i ) { return 1; } else { return undef; } } =head2 Object Returns the object for this URI, if it's local. Otherwise returns undef. =cut sub Object { my $self = shift; return ($self->{'object'}); } =head2 Scheme Return the URI scheme for RT records =cut sub Scheme { my $self = shift; return "fsck.com-rt"; } =head2 HREF If this is a local ticket, return an HTTP url to it. Otherwise, return its URI =cut sub HREF { my $self = shift; return $self->URI unless $self->IsLocal; my $obj = $self->Object; if ( $obj && $self->ObjectType eq 'ticket' ) { return RT->Config->Get('WebURL') ."Ticket/Display.html?id=". $obj->id; } return $self->URI; } =head2 AsString Returns either a localized string C<#23: Subject> for tickets, C<ObjectType #13: Name> for other object types (not really used), or the full URI if the object is not local. =cut sub AsString { my $self = shift; if ($self->IsLocal && ( my $object = $self->Object )) { if ($object->isa('RT::Ticket')) { return $self->loc("#[_1]: [_2]", $object->Id, $object->Subject || ''); } else { my $name = $object->_Accessible('Name', 'read') ? $object->Name : undef; if ( defined $name and length $name ) { return $self->loc("[_1] #[_2]: [_3]", $self->ObjectType, $object->Id, $name); } else { return $self->loc("[_1] #[_2]", $self->ObjectType, $object->Id); } } } else { return $self->URI; } } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/URI/group.pm������������������������������������������������������������������������000644 �000765 �000024 �00000012650 14005011336 016231� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::URI::group; use base qw/RT::URI::base/; require RT::Group; =head1 NAME RT::URI::group - Internal URIs for linking to an L<RT::Group> =head1 DESCRIPTION This class should rarely be used directly, but via L<RT::URI> instead. Represents, parses, and generates internal RT URIs such as: group:42 group://example.com/42 These URIs are used to link between objects in RT such as associating a group with another group. =head1 METHODS Much of the interface below is dictated by L<RT::URI> and L<RT::URI::base>. =head2 Scheme Return the URI scheme for groups =cut sub Scheme { "group" } =head2 LocalURIPrefix Returns the site-specific prefix for a local group URI =cut sub LocalURIPrefix { my $self = shift; return $self->Scheme . "://" . RT->Config->Get('Organization'); } =head2 IsLocal Returns a true value, the grouup ID, if this object represents a local group, undef otherwise. =cut sub IsLocal { my $self = shift; my $prefix = $self->LocalURIPrefix; return $1 if $self->{uri} =~ qr!^\Q$prefix\E/(\d+)!i; return undef; } =head2 URIForObject RT::Group Returns the URI for a local L<RT::Group> object =cut sub URIForObject { my $self = shift; my $obj = shift; return $self->LocalURIPrefix . '/' . $obj->Id; } =head2 ParseURI URI Primarily used by L<RT::URI> to set internal state. Figures out from an C<group:> URI whether it refers to a local group and the group ID. Returns the group ID if local, otherwise returns false. =cut sub ParseURI { my $self = shift; my $uri = shift; my $scheme = $self->Scheme; # canonicalize "42" and "group:42" -> group://example.com/42 if ($uri =~ /^(?:\Q$scheme\E:)?(\d+)$/i) { my $group_obj = RT::Group->new( $self->CurrentUser ); my ($ret, $msg) = $group_obj->Load($1); if ( $ret ) { $self->{'uri'} = $group_obj->URI; $self->{'object'} = $group_obj; } else { RT::Logger->error("Unable to load group for id: $1: $msg"); return; } } else { $self->{'uri'} = $uri; } my $group = RT::Group->new( $self->CurrentUser ); if ( my $id = $self->IsLocal ) { $group->Load($id); if ($group->id) { $self->{'object'} = $group; } else { RT->Logger->error("Can't load Group #$id by URI '$uri'"); return; } } return $group->id; } =head2 Object Returns the object for this URI, if it's local. Otherwise returns undef. =cut sub Object { my $self = shift; return $self->{'object'}; } =head2 HREF If this is a local group, return an HTTP URL for it. Otherwise, return its URI. =cut sub HREF { my $self = shift; if ($self->IsLocal and $self->Object) { return RT->Config->Get('WebURL') # . ( $self->CurrentUser->Privileged ? "" : "SelfService/" ) # . "Admin/Groups/Modify.html?id=" . "Group/Summary.html?id=" . $self->Object->Id; } else { return $self->URI; } } =head2 AsString Returns a description of this object =cut sub AsString { my $self = shift; if ($self->IsLocal and $self->Object) { my $object = $self->Object; if ( $object->Name ) { return $self->loc('[_1] (Group #[_2])', $object->Name, $object->id); } else { return $self->loc('Group #[_1]', $object->id); } } else { return $self->SUPER::AsString(@_); } } 1; ����������������������������������������������������������������������������������������rt-5.0.1/lib/RT/URI/t.pm����������������������������������������������������������������������������000644 �000765 �000024 �00000004771 14005011336 015345� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::URI::t; use base 'RT::URI::fsck_com_rt'; =head1 NAME RT::URI::t - aliad for RT::URI::fsck_com_rt that supports 't:12345' URIs =head2 ParseURI URI When handed an t: URI, figures out if it is an RT ticket. This is an alternate short form of specifying a full ticket URI. =cut sub ParseURI { my $self = shift; my $uri = shift; # Pass this off to fsck_com_rt, which is equipped to deal with # tickets after stripping off the t: prefix. $uri =~ s/^t://; return $self->SUPER::ParseURI($uri); } 1; �������rt-5.0.1/lib/RT/URI/fsck_com_article.pm�������������������������������������������������������������000644 �000765 �000024 �00000012460 14005011336 020363� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::URI::fsck_com_article; use strict; use warnings; no warnings 'redefine'; use base qw/RT::URI::base/; use RT::Article; =head2 LocalURIPrefix Returns the prefix for a local article URI =cut sub LocalURIPrefix { my $self = shift; my $prefix = $self->Scheme. "://". RT->Config->Get('Organization'); return ($prefix); } =head2 URIForObject RT::article Returns the RT URI for a local RT::article object =cut sub URIForObject { my $self = shift; my $obj = shift; return ($self->LocalURIPrefix . "/article/" . $obj->Id); } =head2 ParseObject $ArticleObj When handed an L<RT::Article> object, figure out its URI =cut =head2 ParseURI URI When handed an fsck.com-article URI, figures out things like whether its a local article and what its ID is =cut sub ParseURI { my $self = shift; my $uri = shift; my $article; if ($uri =~ /^(\d+)$/) { $article = RT::Article->new($self->CurrentUser); $article->Load($uri); $self->{'uri'} = $article->URI; } else { $self->{'uri'} = $uri; } #If it's a local URI, load the article object and return its URI if ( $self->IsLocal) { my $local_uri_prefix = $self->LocalURIPrefix; if ($self->{'uri'} =~ /^$local_uri_prefix\/article\/(\d+)$/) { my $id = $1; $article = RT::Article->new( $self->CurrentUser ); my ($ret, $msg) = $article->Load($id); #If we couldn't find a article, return undef. unless ( $article and $article->Id ) { # We got an id, but couldn't load it, so warn that it may # have been deleted. RT::Logger->warning("Unable to load article for id $id. It may" . " have been deleted: $msg"); return undef; } } else { return undef; } } #If we couldn't find a article, return undef. unless ( $article and $article->Id ) { return undef; } $self->{'object'} = $article; return ($article->Id); } =head2 IsLocal Returns true if this URI is for a local article. Returns undef otherwise. =cut sub IsLocal { my $self = shift; my $local_uri_prefix = $self->LocalURIPrefix; if ($self->{'uri'} =~ /^$local_uri_prefix/) { return 1; } else { return undef; } } =head2 Object Returns the object for this URI, if it's local. Otherwise returns undef. =cut sub Object { my $self = shift; return ($self->{'object'}); } =head2 Scheme Return the URI scheme for RT articles =cut sub Scheme { my $self = shift; return "fsck.com-article"; } =head2 HREF If this is a local article, return an HTTP url to it. Otherwise, return its URI =cut sub HREF { my $self = shift; if ($self->IsLocal && $self->Object) { return ( RT->Config->Get('WebURL') . "Articles/Article/Display.html?id=".$self->Object->Id); } else { return ($self->URI); } } =head2 AsString Return "Article 23" =cut sub AsString { my $self = shift; if ($self->IsLocal && ( my $object = $self->Object )) { if ( $object->Name ) { return $self->loc('Article #[_1]: [_2]', $object->id, $object->Name); } else { return $self->loc('Article #[_1]', $object->id); } } else { return $self->SUPER::AsString(@_); } } 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/URI/base.pm�������������������������������������������������������������������������000644 �000765 �000024 �00000006426 14005011336 016013� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::URI::base; use strict; use warnings; use base qw(RT::Base); =head1 NAME RT::URI::base =head1 DESCRIPTION A baseclass (and fallback) RT::URI handler. Every URI handler needs to handle the API presented here =cut =head1 API =head2 new Create a new URI handler =cut sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; bless( $self, $class ); $self->CurrentUser(@_); return ($self); } sub ParseObject { my $self = shift; my $obj = shift; $self->{'uri'} = "unknown-object:".ref($obj); } sub ParseURI { my $self = shift; my $uri = shift; if ($uri =~ /^(.*?):/) { $self->{'scheme'} = $1; } $self->{'uri'} = $uri; } sub Object { my $self = shift; return undef; } sub URI { my $self = shift; return($self->{'uri'}); } sub Scheme { my $self = shift; return($self->{'scheme'}); } sub HREF { my $self = shift; return($self->{'href'} || $self->{'uri'}); } sub IsLocal { my $self = shift; return undef; } =head2 AsString Return a "pretty" string representing the URI object. This is meant to be used like this: % $re = $uri->Resolver; <A HREF="<% $re->HREF %>"><% $re->AsString %></A> =cut sub AsString { my $self = shift; return $self->URI; } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/I18N/ru.pm��������������������������������������������������������������������������000644 �000765 �000024 �00000005113 14005011336 015537� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::I18N::ru; use base 'RT::I18N'; use strict; use warnings; sub quant { my($handle, $num, @forms) = @_; return $num unless @forms; return $forms[3] if !$num && $forms[3]; return $handle->numf($num) .' '. $handle->numerate($num, @forms); } sub numerate { my($handle, $n, @forms) = @_; my $form = 0; if ( $n%10 == 1 && $n%100 != 11 ) { $form = 0; } elsif ( $n%10 >= 2 && $n%10 <= 4 && ($n%100 < 10 || $n%100 >= 20) ) { $form = 1; } else { $form = 2; } return $forms[$form] || (grep defined, @forms)[0]; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/I18N/fr.pm��������������������������������������������������������������������������000644 �000765 �000024 �00000004456 14005011336 015531� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::I18N::fr; use base 'RT::I18N'; use strict; use warnings; sub numf { my ($handle, $num) = @_[0,1]; my $fr_num = $handle->SUPER::numf($num); # French prefer to print 1000 as 1(nbsp)000 rather than 1,000 $fr_num =~ tr<.,><,\x{A0}>; return $fr_num; } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/I18N/cs.pm��������������������������������������������������������������������������000644 �000765 �000024 �00000006227 14005011336 015525� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::I18N::cs; use base 'RT::I18N'; use strict; use warnings; # # CZECH TRANSLATORS COMMENTS see Locale::Maketext::TPJ13 # Obecne parametry musi byt docela slozite (v pripade Slavistickych jazyku) # typu pocet, slovo, pad a rod # #pad 1., rod muzsky: #0 krecku #1 krecek #2..4 krecci #5.. krecku (nehodi se zde resit pravidlo mod 1,2,3,4 krom mod 11,12,13,14) # #0 kabatu #1 kabat #2..4 kabaty #5 kabatu # # => Vyplati se udelat quant s parametry typu pocet, slovo1, slovo2..4, slovo5 a slovo0 # sub quant { my($handle, $num, @forms) = @_; return $num if @forms == 0; # what should this mean? return $forms[3] if @forms > 3 and $num == 0; # special zeroth case # Normal case: # Note that the formatting of $num is preserved. return( $handle->numf($num) . ' ' . $handle->numerate($num, @forms) ); } sub numerate { # return this lexical item in a form appropriate to this number my($handle, $num, @forms) = @_; return '' unless @forms; my $fallback = (grep defined, @forms)[0]; return $forms[0] // $fallback if $num == 1; return $forms[1] // $fallback if $num > 1 and $num < 5; return $forms[2] // $fallback; } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/I18N/i_default.pm�������������������������������������������������������������������000644 �000765 �000024 �00000010200 14005011336 017036� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::I18N::i_default; use base 'RT::I18N'; RT::Base->_ImportOverlays(); 1; __END__ This class just zero-derives from the project base class, which is English for this project. i-default is "English at least". It wouldn't be a bad idea to make our i-default messages be English plus, say, French -- i-default is meant to /contain/ English, not be /just/ English. If you have all your English messages in Whatever::en and all your French messages in Whatever::fr, it would be straightforward to define Whatever::i_default's as a subclass of Whatever::en, but for every case where a key gets you a string (as opposed to a coderef) from %Whatever::en::Lexicon and %Whatever::fr::Lexicon, you could make %Whatever::i_default::Lexicon be the concatenation of them both. So: "file '[_1]' not found.\n" and "fichier '[_1]' non trouve\n" could make for an %Whatever::i_default::Lexicon entry of "file '[_1]' not found\nfichier '[_1]' non trouve.\n". There may be entries, however, where that is undesirable. And in any case, it's not feasable once you have an _AUTO lexicon in the mix, as wo do here. RFC 2277 says: 4.5. Default Language When human-readable text must be presented in a context where the sender has no knowledge of the recipient's language preferences (such as login failures or E-mailed warnings, or prior to language negotiation), text SHOULD be presented in Default Language. Default Language is assigned the tag "i-default" according to the procedures of RFC 1766. It is not a specific language, but rather identifies the condition where the language preferences of the user cannot be established. Messages in Default Language MUST be understandable by an English- speaking person, since English is the language which, worldwide, the greatest number of people will be able to get adequate help in interpreting when working with computers. Note that negotiating English is NOT the same as Default Language; Default Language is an emergency measure in otherwise unmanageable situations. In many cases, using only English text is reasonable; in some cases, the English text may be augumented by text in other languages. ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/I18N/Extract.pm���������������������������������������������������������������������000644 �000765 �000024 �00000020475 14005011336 016533� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::I18N::Extract; use strict; use warnings; use Regexp::Common; use File::Spec; use File::Find; use Locale::PO; sub new { return bless { results => {}, errors => [], }, shift; } sub all { my $self = shift; my $merged = sub { $self->from($File::Find::name) }; File::Find::find( { wanted => $merged, no_chdir => 1, follow => 1 }, grep {-d $_} qw(bin sbin lib share/html html etc), ); return $self->results; } sub valid_to_extract { my $self = shift; my ($file) = @_; return unless -f $file; return if $file eq "lib/RT/StyleGuide.pod"; return if $file eq "lib/RT/I18N/Extract.pm"; return if $file =~ m{/[\.#][^/]*$} or $file =~ /\.bak$/; return if -f "$file.in"; return 1; } sub from { my $self = shift; my ($file) = (@_); return unless $self->valid_to_extract($file); my $fh; unless (open $fh, '<', $file) { push @{$self->{errors}}, "$file:0: Cannot open for reading: $!"; return; } my $contents = do { local $/; <$fh> }; close $fh; # Provide the non-.in filename for the rest of error reporting and # POT file needs, as the .in file will not exist if looking in the # installed tree. $file =~ s/\.in$//; my %seen; my $line; my $_add = sub { my ($maybe_quoted, $key, $vars) = @_; $vars = '' unless defined $vars; $seen{$line}++; if ($maybe_quoted and $key =~ s/^(['"])(.*)\1$/$2/) { my $quote = $1; $key =~ s/\\(['"\\])/$1/g; if ($quote eq '"') { if ($key =~ /([\$\@]\w+)/) { push @{$self->{errors}}, "$file:$line: Interpolated variable '$1' in \"$key\""; } if ($key =~ /\\n/) { push @{$self->{errors}}, "$file:$line: Embedded newline in \"$key\""; } } } if ($key =~ /^\s/m || $key =~ /\s$/m) { push @{$self->{errors}}, "$file:$line: Extraneous whitespace in '$key'"; } $vars =~ tr/\n\r//d; push @{ $self->{results}{$key} }, [ $file, $line, $vars ]; }; my $add = sub {$_add->(1, @_)}; my $add_noquotes = sub {$_add->(0, @_)}; my $extract = sub { my ($regex, $run) = @_; $line = 1; pos($contents) = 0; while ($contents =~ m!\G.*?$regex!sg) { my $match = substr($contents,$-[0],$+[0]-$-[0]); $line += ( $match =~ tr/\n/\n/ ); $run->(); } }; my $ws = qr{[ ]*}; my $punct = qr{[ \{\}\)\],;]*}; my $quoted = $RE{delimited}{-delim=>q{'"}}; # Mason filter: <&|/l&>...</&> and <&|/l_unsafe&>...</&> $extract->(qr! <&\|/l(?:_unsafe)?(.*?)&> (.*?) </&> !sox, sub { my ($key, $vars) = ($2, $1); if ($key =~ m! (<([%&]) .*? \2>) !sox) { push @{$self->{errors}}, "$file:$line: Mason content within loc: '$1'"; } $add_noquotes->($key, $vars); }); # Localization function: loc(...) $extract->(qr! \b loc ( $RE{balanced}{-parens=>'()'} ) !sox, sub { # Re-parse what was in the parens for the string and optional arguments return unless "$1" =~ m! \( \s* ($quoted) (.*?) \s* \) $ !sox; $add->($1, $2); }); # Comment-based mark: "..." # loc $extract->(qr! ($quoted) # Quoted string $punct $ws \# $ws loc $ws $ !smox, sub { $add->($1); }); # Comment-based mark for list to loc(): ("...", $foo, $bar) # loc() $extract->(qr! ( $RE{balanced}{-parens=>'()'} ) $punct $ws \# $ws loc \(\) $ws $ !smox, sub { # Re-parse what was in the parens for the string and optional arguments return unless "$1" =~ m! \( \s* ($quoted) (.*?) \s* \) $ !sox; $add->($1, $2); }); # Comment-based qw mark: "qw(...)" # loc_qw $extract->(qr! qw \( ([^)]+) \) $punct $ws \# $ws loc_qw $ws $ !smox, sub { $add_noquotes->($_) for split ' ', $1; }); # Comment-based left pair mark: "..." => ... # loc_left_pair $extract->(qr! (\w+|$quoted) \s* => [^#\n]+? $ws \# $ws loc_left_pair $ws $ !smox, sub { $add->($1); }); # Comment-based pair mark: "..." => "..." # loc_pair $extract->(qr! (\w+|$quoted) \s* => \s* ($quoted) $punct $ws \# $ws loc_pair $ws $ !smox, sub { $add->($1); $add->($2); }); # Specific key foo => "...", #loc{foo} $extract->(qr! (\w+|$quoted) \s* => \s* ($quoted) (?-s: .*? ) \# $ws loc\{\1\} # More lax about what matches before the # $ws $ !smox, sub { $add->($2); }); # Check for ones we missed $extract->(qr! \# $ws ( loc ( _\w+ | \(\) | {(\w+|$quoted)} )? ) $ws $ !smox, sub { return if $seen{$line}; push @{$self->{errors}}, "$file:$line: Localization comment '$1' did not match"; }); } sub results { my $self = shift; my %PO; for my $str ( sort keys %{$self->{results}} ) { my $entry = $self->{results}{$str}; my $escape = sub { $_ = shift; s/\b_(\d+)/%$1/; $_ }; $str =~ s/((?<!~)(?:~~)*)\[_(\d+)\]/$1%$2/g; $str =~ s/((?<!~)(?:~~)*)\[([A-Za-z#*]\w*),([^\]]+)\]/"$1%$2(".$escape->($3).")"/eg; $str =~ s/~([\[\]])/$1/g; my $po = Locale::PO->new(-msgid => $str, -msgstr => ""); $po->reference( join ( ' ', sort map $_->[0].":".$_->[1], @{ $entry } ) ); my %seen; my @vars; foreach my $find ( sort { $a->[2] cmp $b->[2] } grep { $_->[2] } @{ $entry } ) { my ( $file, $line, $var ) = @{$find}; $var =~ s/^\s*,\s*//; $var =~ s/\s*$//; push @vars, "($var)" unless $seen{$var}++; } $po->automatic( join( "\n", @vars) ); $PO{$po->msgid} = $po; } return %PO; } sub errors { my $self = shift; return @{$self->{errors}}; } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/I18N/de.pm��������������������������������������������������������������������������000644 �000765 �000024 �00000004127 14005011336 015505� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::I18N::de; use base 'RT::I18N'; sub init { $_[0]->{numf_comma} = 1; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Report/Tickets/���������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 016755� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Report/Tickets.pm�������������������������������������������������������������������000644 �000765 �000024 �00000152572 14005011336 017327� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Report::Tickets; use base qw/RT::Tickets/; use RT::Report::Tickets::Entry; use strict; use warnings; use 5.010; use Scalar::Util qw(weaken); __PACKAGE__->RegisterCustomFieldJoin(@$_) for [ "RT::Transaction" => sub { $_[0]->JoinTransactions } ], [ "RT::Queue" => sub { # XXX: Could avoid join and use main.Queue with some refactoring? return $_[0]->{_sql_aliases}{queues} ||= $_[0]->Join( ALIAS1 => 'main', FIELD1 => 'Queue', TABLE2 => 'Queues', FIELD2 => 'id', ); } ]; our @GROUPINGS = ( Status => 'Enum', #loc_left_pair Queue => 'Queue', #loc_left_pair InitialPriority => 'Priority', #loc_left_pair FinalPriority => 'Priority', #loc_left_pair Priority => 'Priority', #loc_left_pair Owner => 'User', #loc_left_pair Creator => 'User', #loc_left_pair LastUpdatedBy => 'User', #loc_left_pair Requestor => 'Watcher', #loc_left_pair Cc => 'Watcher', #loc_left_pair AdminCc => 'Watcher', #loc_left_pair Watcher => 'Watcher', #loc_left_pair Created => 'Date', #loc_left_pair Starts => 'Date', #loc_left_pair Started => 'Date', #loc_left_pair Resolved => 'Date', #loc_left_pair Due => 'Date', #loc_left_pair Told => 'Date', #loc_left_pair LastUpdated => 'Date', #loc_left_pair CF => 'CustomField', #loc_left_pair SLA => 'Enum', #loc_left_pair ); our %GROUPINGS; our %GROUPINGS_META = ( Queue => { Display => sub { my $self = shift; my %args = (@_); my $queue = RT::Queue->new( $self->CurrentUser ); $queue->Load( $args{'VALUE'} ); return $queue->Name; }, Localize => 1, }, Priority => { Sort => 'numeric raw', }, User => { SubFields => [grep RT::User->_Accessible($_, "public"), qw( Name RealName NickName EmailAddress Organization Lang City Country Timezone )], Function => 'GenerateUserFunction', }, Watcher => { SubFields => [grep RT::User->_Accessible($_, "public"), qw( Name RealName NickName EmailAddress Organization Lang City Country Timezone )], Function => 'GenerateWatcherFunction', }, Date => { SubFields => [qw( Time Hourly Hour Date Daily DayOfWeek Day DayOfMonth DayOfYear Month Monthly Year Annually WeekOfYear )], # loc_qw StrftimeFormat => { Time => '%T', Hourly => '%Y-%m-%d %H', Hour => '%H', Date => '%F', Daily => '%F', DayOfWeek => '%w', Day => '%F', DayOfMonth => '%d', DayOfYear => '%j', Month => '%m', Monthly => '%Y-%m', Year => '%Y', Annually => '%Y', WeekOfYear => '%W', }, Function => 'GenerateDateFunction', Display => sub { my $self = shift; my %args = (@_); my $raw = $args{'VALUE'}; return $raw unless defined $raw; if ( $args{'SUBKEY'} eq 'DayOfWeek' ) { return $self->loc($RT::Date::DAYS_OF_WEEK[ int $raw ]); } elsif ( $args{'SUBKEY'} eq 'Month' ) { return $self->loc($RT::Date::MONTHS[ int($raw) - 1 ]); } return $raw; }, Sort => 'raw', }, CustomField => { SubFields => sub { my $self = shift; my $args = shift; my $queues = $args->{'Queues'}; if ( !$queues && $args->{'Query'} ) { require RT::Interface::Web::QueryBuilder::Tree; my $tree = RT::Interface::Web::QueryBuilder::Tree->new('AND'); $tree->ParseSQL( Query => $args->{'Query'}, CurrentUser => $self->CurrentUser ); $queues = $args->{'Queues'} = $tree->GetReferencedQueues( CurrentUser => $self->CurrentUser ); } return () unless $queues; my @res; my $CustomFields = RT::CustomFields->new( $self->CurrentUser ); foreach my $id (keys %$queues) { my $queue = RT::Queue->new( $self->CurrentUser ); $queue->Load($id); next unless $queue->id; $CustomFields->LimitToQueue($queue->id); } $CustomFields->LimitToGlobal; while ( my $CustomField = $CustomFields->Next ) { push @res, ["Custom field", $CustomField->Name], "CF.{". $CustomField->id ."}"; } return @res; }, Function => 'GenerateCustomFieldFunction', Label => sub { my $self = shift; my %args = (@_); my ($cf) = ( $args{'SUBKEY'} =~ /^\{(.*)\}$/ ); if ( $cf =~ /^\d+$/ ) { my $obj = RT::CustomField->new( $self->CurrentUser ); $obj->Load( $cf ); $cf = $obj->Name; } return 'Custom field [_1]', $cf; }, }, Enum => { Localize => 1, }, Duration => { SubFields => [ qw/Default Hour Day Week Month Year/ ], Localize => 1, Short => 0, Show => 1, Sort => 'duration', }, DurationInBusinessHours => { SubFields => [ qw/Default Hour/ ], Localize => 1, Short => 0, Show => 1, Sort => 'duration', }, ); # loc'able strings below generated with (s/loq/loc/): # perl -MRT=-init -MRT::Report::Tickets -E 'say qq{\# loq("$_->[0]")} while $_ = splice @RT::Report::Tickets::STATISTICS, 0, 2' # # loc("Ticket count") # loc("Summary of time worked") # loc("Total time worked") # loc("Average time worked") # loc("Minimum time worked") # loc("Maximum time worked") # loc("Summary of time estimated") # loc("Total time estimated") # loc("Average time estimated") # loc("Minimum time estimated") # loc("Maximum time estimated") # loc("Summary of time left") # loc("Total time left") # loc("Average time left") # loc("Minimum time left") # loc("Maximum time left") # loc("Summary of Created to Started") # loc("Total Created to Started") # loc("Average Created to Started") # loc("Minimum Created to Started") # loc("Maximum Created to Started") # loc("Summary of Created to Resolved") # loc("Total Created to Resolved") # loc("Average Created to Resolved") # loc("Minimum Created to Resolved") # loc("Maximum Created to Resolved") # loc("Summary of Created to LastUpdated") # loc("Total Created to LastUpdated") # loc("Average Created to LastUpdated") # loc("Minimum Created to LastUpdated") # loc("Maximum Created to LastUpdated") # loc("Summary of Starts to Started") # loc("Total Starts to Started") # loc("Average Starts to Started") # loc("Minimum Starts to Started") # loc("Maximum Starts to Started") # loc("Summary of Due to Resolved") # loc("Total Due to Resolved") # loc("Average Due to Resolved") # loc("Minimum Due to Resolved") # loc("Maximum Due to Resolved") # loc("Summary of Started to Resolved") # loc("Total Started to Resolved") # loc("Average Started to Resolved") # loc("Minimum Started to Resolved") # loc("Maximum Started to Resolved") our @STATISTICS = ( COUNT => ['Ticket count', 'Count', 'id'], ); foreach my $field (qw(TimeWorked TimeEstimated TimeLeft)) { my $friendly = lc join ' ', split /(?<=[a-z])(?=[A-Z])/, $field; push @STATISTICS, ( "ALL($field)" => ["Summary of $friendly", 'TimeAll', $field ], "SUM($field)" => ["Total $friendly", 'Time', 'SUM', $field ], "AVG($field)" => ["Average $friendly", 'Time', 'AVG', $field ], "MIN($field)" => ["Minimum $friendly", 'Time', 'MIN', $field ], "MAX($field)" => ["Maximum $friendly", 'Time', 'MAX', $field ], ); } foreach my $pair ( 'Created to Started', 'Created to Resolved', 'Created to LastUpdated', 'Starts to Started', 'Due to Resolved', 'Started to Resolved', ) { my ($from, $to) = split / to /, $pair; push @STATISTICS, ( "ALL($pair)" => ["Summary of $pair", 'DateTimeIntervalAll', $from, $to ], "SUM($pair)" => ["Total $pair", 'DateTimeInterval', 'SUM', $from, $to ], "AVG($pair)" => ["Average $pair", 'DateTimeInterval', 'AVG', $from, $to ], "MIN($pair)" => ["Minimum $pair", 'DateTimeInterval', 'MIN', $from, $to ], "MAX($pair)" => ["Maximum $pair", 'DateTimeInterval', 'MAX', $from, $to ], ); push @GROUPINGS, $pair => 'Duration'; my %extra_info = ( business_time => 1 ); if ( keys %{RT->Config->Get('ServiceBusinessHours')} ) { my $business_pair = "$pair(Business Hours)"; push @STATISTICS, ( "ALL($business_pair)" => ["Summary of $business_pair", 'DateTimeIntervalAll', $from, $to, \%extra_info ], "SUM($business_pair)" => ["Total $business_pair", 'DateTimeInterval', 'SUM', $from, $to, \%extra_info ], "AVG($business_pair)" => ["Average $business_pair", 'DateTimeInterval', 'AVG', $from, $to, \%extra_info ], "MIN($business_pair)" => ["Minimum $business_pair", 'DateTimeInterval', 'MIN', $from, $to, \%extra_info ], "MAX($business_pair)" => ["Maximum $business_pair", 'DateTimeInterval', 'MAX', $from, $to, \%extra_info ], ); push @GROUPINGS, $business_pair => 'DurationInBusinessHours'; } } our %STATISTICS; our %STATISTICS_META = ( Count => { Function => sub { my $self = shift; my $field = shift || 'id'; return ( FUNCTION => 'COUNT', FIELD => 'id' ); }, }, Simple => { Function => sub { my $self = shift; my ($function, $field) = @_; return (FUNCTION => $function, FIELD => $field); }, }, Time => { Function => sub { my $self = shift; my ($function, $field) = @_; return (FUNCTION => "$function(?)*60", FIELD => $field); }, Display => 'DurationAsString', }, TimeAll => { SubValues => sub { return ('Minimum', 'Average', 'Maximum', 'Total') }, Function => sub { my $self = shift; my $field = shift; return ( Minimum => { FUNCTION => "MIN(?)*60", FIELD => $field }, Average => { FUNCTION => "AVG(?)*60", FIELD => $field }, Maximum => { FUNCTION => "MAX(?)*60", FIELD => $field }, Total => { FUNCTION => "SUM(?)*60", FIELD => $field }, ); }, Display => 'DurationAsString', }, DateTimeInterval => { Function => sub { my $self = shift; my ($function, $from, $to) = @_; my $interval = $self->_Handle->DateTimeIntervalFunction( From => { FUNCTION => $self->NotSetDateToNullFunction( FIELD => $from ) }, To => { FUNCTION => $self->NotSetDateToNullFunction( FIELD => $to ) }, ); return (FUNCTION => "$function($interval)"); }, Display => 'DurationAsString', }, DateTimeIntervalAll => { SubValues => sub { return ('Minimum', 'Average', 'Maximum', 'Total') }, Function => sub { my $self = shift; my ($from, $to) = @_; my $interval = $self->_Handle->DateTimeIntervalFunction( From => { FUNCTION => $self->NotSetDateToNullFunction( FIELD => $from ) }, To => { FUNCTION => $self->NotSetDateToNullFunction( FIELD => $to ) }, ); return ( Minimum => { FUNCTION => "MIN($interval)" }, Average => { FUNCTION => "AVG($interval)" }, Maximum => { FUNCTION => "MAX($interval)" }, Total => { FUNCTION => "SUM($interval)" }, ); }, Display => 'DurationAsString', }, CustomDateRange => { Display => 'DurationAsString', Function => sub {}, # Placeholder to use the same DateTimeInterval handling }, CustomDateRangeAll => { SubValues => sub { return ('Minimum', 'Average', 'Maximum', 'Total') }, Function => sub { my $self = shift; # To use the same DateTimeIntervalAll handling, not real SQL return ( Minimum => { FUNCTION => "MIN" }, Average => { FUNCTION => "AVG" }, Maximum => { FUNCTION => "MAX" }, Total => { FUNCTION => "SUM" }, ); }, Display => 'DurationAsString', }, ); sub Groupings { my $self = shift; my %args = (@_); my @fields; my @tmp = @GROUPINGS; while ( my ($field, $type) = splice @tmp, 0, 2 ) { my $meta = $GROUPINGS_META{ $type } || {}; unless ( $meta->{'SubFields'} ) { push @fields, [$field, $field], $field; } elsif ( ref( $meta->{'SubFields'} ) eq 'ARRAY' ) { push @fields, map { ([$field, $_], "$field.$_") } @{ $meta->{'SubFields'} }; } elsif ( my $code = $self->FindImplementationCode( $meta->{'SubFields'} ) ) { push @fields, $code->( $self, \%args ); } else { $RT::Logger->error( "$type has unsupported SubFields." ." Not an array, a method name or a code reference" ); } } return @fields; } sub IsValidGrouping { my $self = shift; my %args = (@_); return 0 unless $args{'GroupBy'}; my ($key, $subkey) = split /\./, $args{'GroupBy'}, 2; %GROUPINGS = @GROUPINGS unless keys %GROUPINGS; my $type = $GROUPINGS{$key}; return 0 unless $type; return 1 unless $subkey; my $meta = $GROUPINGS_META{ $type } || {}; unless ( $meta->{'SubFields'} ) { return 0; } elsif ( ref( $meta->{'SubFields'} ) eq 'ARRAY' ) { return 1 if grep $_ eq $subkey, @{ $meta->{'SubFields'} }; } elsif ( my $code = $self->FindImplementationCode( $meta->{'SubFields'}, 'silent' ) ) { return 1 if grep $_ eq "$key.$subkey", $code->( $self, \%args ); } return 0; } sub Statistics { my $self = shift; return map { ref($_)? $_->[0] : $_ } @STATISTICS; } sub Label { my $self = shift; my $column = shift; my $info = $self->ColumnInfo( $column ); unless ( $info ) { $RT::Logger->error("Unknown column '$column'"); return $self->CurrentUser->loc('(Incorrect data)'); } if ( $info->{'META'}{'Label'} ) { my $code = $self->FindImplementationCode( $info->{'META'}{'Label'} ); return $self->CurrentUser->loc( $code->( $self, %$info ) ) if $code; } my $res = ''; if ( $info->{'TYPE'} eq 'statistic' ) { $res = $info->{'INFO'}[0]; } else { $res = join ' ', grep defined && length, @{ $info }{'KEY', 'SUBKEY'}; } return $self->CurrentUser->loc( $res ); } sub ColumnInfo { my $self = shift; my $column = shift; return $self->{'column_info'}{$column}; } sub ColumnsList { my $self = shift; return sort { $self->{'column_info'}{$a}{'POSITION'} <=> $self->{'column_info'}{$b}{'POSITION'} } keys %{ $self->{'column_info'} || {} }; } sub SetupGroupings { my $self = shift; my %args = ( Query => undef, GroupBy => undef, Function => undef, @_ ); $self->FromSQL( $args{'Query'} ) if $args{'Query'}; # Apply ACL checks $self->CurrentUserCanSee if RT->Config->Get('UseSQLForACLChecks'); # See if our query is distinct if (not $self->{'joins_are_distinct'} and $self->_isJoined) { # If it isn't, we need to do this in two stages -- first, find # the distinct matching tickets (with no group by), then search # within the matching tickets grouped by what is wanted. my @match = (0); $self->Columns( 'id' ); while (my $row = $self->Next) { push @match, $row->id; } # Replace the query with one that matches precisely those # tickets, with no joins. We then mark it as having been ACL'd, # since it was by dint of being in the search results above $self->CleanSlate; while ( @match > 1000 ) { my @batch = splice( @match, 0, 1000 ); $self->Limit( FIELD => 'Id', OPERATOR => 'IN', VALUE => \@batch ); } $self->Limit( FIELD => 'Id', OPERATOR => 'IN', VALUE => \@match ); $self->{'_sql_current_user_can_see_applied'} = 1 } %GROUPINGS = @GROUPINGS unless keys %GROUPINGS; my $i = 0; my @group_by = grep defined && length, ref( $args{'GroupBy'} )? @{ $args{'GroupBy'} } : ($args{'GroupBy'}); @group_by = ('Status') unless @group_by; foreach my $e ( splice @group_by ) { unless ($self->IsValidGrouping( Query => $args{Query}, GroupBy => $e )) { RT->Logger->error("'$e' is not a valid grouping for reports; skipping"); next; } my ($key, $subkey) = split /\./, $e, 2; $e = { $self->_FieldToFunction( KEY => $key, SUBKEY => $subkey ) }; $e->{'TYPE'} = 'grouping'; $e->{'INFO'} = $GROUPINGS{ $key }; $e->{'META'} = $GROUPINGS_META{ $e->{'INFO'} }; $e->{'POSITION'} = $i++; push @group_by, $e; } $self->GroupBy( map { { ALIAS => $_->{'ALIAS'}, FIELD => $_->{'FIELD'}, FUNCTION => $_->{'FUNCTION'}, } } @group_by ); my %res = (Groups => [], Functions => []); my %column_info; foreach my $group_by ( @group_by ) { $group_by->{'NAME'} = $self->Column( %$group_by ); $column_info{ $group_by->{'NAME'} } = $group_by; push @{ $res{'Groups'} }, $group_by->{'NAME'}; } %STATISTICS = @STATISTICS unless keys %STATISTICS; my @function = grep defined && length, ref( $args{'Function'} )? @{ $args{'Function'} } : ($args{'Function'}); push @function, 'COUNT' unless @function; foreach my $e ( @function ) { $e = { TYPE => 'statistic', KEY => $e, INFO => $STATISTICS{ $e }, META => $STATISTICS_META{ $STATISTICS{ $e }[1] }, POSITION => $i++, }; unless ( $e->{'INFO'} && $e->{'META'} ) { $RT::Logger->error("'". $e->{'KEY'} ."' is not valid statistic for report"); $e->{'FUNCTION'} = 'NULL'; $e->{'NAME'} = $self->Column( FUNCTION => 'NULL' ); } elsif ( $e->{'META'}{'Function'} ) { my $code = $self->FindImplementationCode( $e->{'META'}{'Function'} ); unless ( $code ) { $e->{'FUNCTION'} = 'NULL'; $e->{'NAME'} = $self->Column( FUNCTION => 'NULL' ); } elsif ( $e->{'META'}{'SubValues'} ) { my %tmp = $code->( $self, @{ $e->{INFO} }[2 .. $#{$e->{INFO}}] ); $e->{'NAME'} = 'postfunction'. $self->{'postfunctions'}++; while ( my ($k, $v) = each %tmp ) { $e->{'MAP'}{ $k }{'NAME'} = $self->Column( %$v ); @{ $e->{'MAP'}{ $k } }{'FUNCTION', 'ALIAS', 'FIELD'} = @{ $v }{'FUNCTION', 'ALIAS', 'FIELD'}; } } else { my %tmp = $code->( $self, @{ $e->{INFO} }[2 .. $#{$e->{INFO}}] ); $e->{'NAME'} = $self->Column( %tmp ); @{ $e }{'FUNCTION', 'ALIAS', 'FIELD'} = @tmp{'FUNCTION', 'ALIAS', 'FIELD'}; } } elsif ( $e->{'META'}{'Calculate'} ) { $e->{'NAME'} = 'postfunction'. $self->{'postfunctions'}++; } push @{ $res{'Functions'} }, $e->{'NAME'}; $column_info{ $e->{'NAME'} } = $e; } $self->{'column_info'} = \%column_info; if ($args{Query} && ( grep( { $_->{INFO} =~ /Duration|CustomDateRange/ } map { $column_info{$_} } @{ $res{Groups} } ) || grep( { $_->{TYPE} eq 'statistic' && ref $_->{INFO} && $_->{INFO}[1] =~ /CustomDateRange/ } values %column_info ) || grep( { $_->{TYPE} eq 'statistic' && ref $_->{INFO} && ref $_->{INFO}[-1] && $_->{INFO}[-1]{business_time} } values %column_info ) ) ) { # Need to do the groupby/calculation at Perl level $self->{_query} = $args{'Query'}; } else { delete $self->{_query}; } return %res; } =head2 _DoSearch Subclass _DoSearch from our parent so we can go through and add in empty columns if it makes sense =cut sub _DoSearch { my $self = shift; # When groupby/calculation can't be done at SQL level, do it at Perl level if ( $self->{_query} ) { my $tickets = RT::Tickets->new( $self->CurrentUser ); $tickets->FromSQL( $self->{_query} ); my @groups = grep { $_->{TYPE} eq 'grouping' } map { $self->ColumnInfo($_) } $self->ColumnsList; my %info; while ( my $ticket = $tickets->Next ) { my @keys; my $max = 1; for my $group ( @groups ) { my $value; if ( $ticket->_Accessible($group->{KEY}, 'read' )) { if ( $group->{SUBKEY} ) { my $method = "$group->{KEY}Obj"; if ( my $obj = $ticket->$method ) { if ( $group->{INFO} eq 'Date' ) { if ( $obj->Unix > 0 ) { $value = $obj->Strftime( $GROUPINGS_META{Date}{StrftimeFormat}{ $group->{SUBKEY} }, Timezone => 'user' ); } else { $value = $self->loc('(no value)') } } else { $value = $obj->_Value($group->{SUBKEY}); } $value //= $self->loc('(no value)'); } } $value //= $ticket->_Value( $group->{KEY} ) // $self->loc('(no value)'); } elsif ( $group->{INFO} eq 'Watcher' ) { my @values; if ( $ticket->can($group->{KEY}) ) { my $method = $group->{KEY}; push @values, @{$ticket->$method->UserMembersObj->ItemsArrayRef}; } elsif ( $group->{KEY} eq 'Watcher' ) { push @values, @{$ticket->$_->UserMembersObj->ItemsArrayRef} for /Requestor Cc AdminCc/; } else { RT->Logger->error("Unsupported group by $group->{KEY}"); next; } @values = map { $_->_Value( $group->{SUBKEY} || 'Name' ) } @values; @values = $self->loc('(no value)') unless @values; $value = \@values; } elsif ( $group->{INFO} eq 'CustomField' ) { my ($id) = $group->{SUBKEY} =~ /{(\d+)}/; my $values = $ticket->CustomFieldValues($id); if ( $values->Count ) { $value = [ map { $_->Content } @{ $values->ItemsArrayRef } ]; } else { $value = $self->loc('(no value)'); } } elsif ( $group->{INFO} =~ /^Duration(InBusinessHours)?/ ) { my $business_time = $1; if ( $group->{FIELD} =~ /^(\w+) to (\w+)(\(Business Hours\))?$/ ) { my $start = $1; my $end = $2; my $start_method = $start . 'Obj'; my $end_method = $end . 'Obj'; if ( $ticket->$end_method->Unix > 0 && $ticket->$start_method->Unix > 0 ) { my $seconds; if ($business_time) { $seconds = $ticket->CustomDateRange( '', { value => "$end - $start", business_time => 1, format => sub { $_[0] }, } ); } else { $seconds = $ticket->$end_method->Unix - $ticket->$start_method->Unix; } if ( $group->{SUBKEY} eq 'Default' ) { $value = RT::Date->new( $self->CurrentUser )->DurationAsString( $seconds, Show => $group->{META}{Show}, Short => $group->{META}{Short}, MaxUnit => $business_time ? 'hour' : 'year', ); } else { $value = RT::Date->new( $self->CurrentUser )->DurationAsString( $seconds, Show => $group->{META}{Show} // 3, Short => $group->{META}{Short} // 1, MaxUnit => lc $group->{SUBKEY}, MinUnit => lc $group->{SUBKEY}, Unit => lc $group->{SUBKEY}, ); } } } else { my %ranges = RT::Ticket->CustomDateRanges; if ( my $spec = $ranges{$group->{FIELD}} ) { if ( $group->{SUBKEY} eq 'Default' ) { $value = $ticket->CustomDateRange( $group->{FIELD}, $spec ); } else { my $seconds = $ticket->CustomDateRange( $group->{FIELD}, { ref $spec ? %$spec : ( value => $spec ), format => sub { $_[0] } } ); if ( defined $seconds ) { $value = RT::Date->new( $self->CurrentUser )->DurationAsString( $seconds, Show => $group->{META}{Show} // 3, Short => $group->{META}{Short} // 1, MaxUnit => lc $group->{SUBKEY}, MinUnit => lc $group->{SUBKEY}, Unit => lc $group->{SUBKEY}, ); } } } } $value //= $self->loc('(no value)'); } else { RT->Logger->error("Unsupported group by $group->{KEY}"); next; } push @keys, $value; } # @keys could contain arrayrefs, so we need to expand it. # e.g. "open", [ "root", "foo" ], "General" ) # will be expanded to: # "open", "root", "General" # "open", "foo", "General" my @all_keys; for my $key (@keys) { if ( ref $key eq 'ARRAY' ) { if (@all_keys) { my @new_all_keys; for my $keys ( @all_keys ) { push @new_all_keys, [ @$keys, $_ ] for @$key; } @all_keys = @new_all_keys; } else { push @all_keys, [$_] for @$key; } } else { if (@all_keys) { @all_keys = map { [ @$_, $key ] } @all_keys; } else { push @all_keys, [$key]; } } } my @fields = grep { $_->{TYPE} eq 'statistic' } map { $self->ColumnInfo($_) } $self->ColumnsList; while ( my $field = shift @fields ) { for my $keys (@all_keys) { my $key = join ';;;', @$keys; if ( $field->{NAME} =~ /^id/ && $field->{FUNCTION} eq 'COUNT' ) { $info{$key}{ $field->{NAME} }++; } elsif ( $field->{NAME} =~ /^postfunction/ ) { if ( $field->{MAP} ) { my ($meta_type) = $field->{INFO}[1] =~ /^(\w+)All$/; for my $item ( values %{ $field->{MAP} } ) { push @fields, { NAME => $item->{NAME}, FIELD => $item->{FIELD}, INFO => [ '', $meta_type, $item->{FUNCTION} =~ /^(\w+)/ ? $1 : '', @{ $field->{INFO} }[ 2 .. $#{ $field->{INFO} } ], ], }; } } } elsif ( $field->{INFO}[1] eq 'Time' ) { if ( $field->{NAME} =~ /^(TimeWorked|TimeEstimated|TimeLeft)$/ ) { my $method = $1; my $type = $field->{INFO}[2]; my $name = lc $field->{NAME}; $info{$key}{$name} = $self->_CalculateTime( $type, $ticket->$method * 60, $info{$key}{$name} ) || 0; } else { RT->Logger->error("Unsupported field $field->{NAME}"); } } elsif ( $field->{INFO}[1] eq 'DateTimeInterval' ) { my ( undef, undef, $type, $start, $end, $extra_info ) = @{ $field->{INFO} }; my $name = lc $field->{NAME}; $info{$key}{$name} ||= 0; my $start_method = $start . 'Obj'; my $end_method = $end . 'Obj'; next unless $ticket->$end_method->Unix > 0 && $ticket->$start_method->Unix > 0; my $value; if ($extra_info->{business_time}) { $value = $ticket->CustomDateRange( '', { value => "$end - $start", business_time => 1, format => sub { return $_[0] }, } ); } else { $value = $ticket->$end_method->Unix - $ticket->$start_method->Unix; } $info{$key}{$name} = $self->_CalculateTime( $type, $value, $info{$key}{$name} ); } elsif ( $field->{INFO}[1] eq 'CustomDateRange' ) { my ( undef, undef, $type, $range_name ) = @{ $field->{INFO} }; my $name = lc $field->{NAME}; $info{$key}{$name} ||= 0; my $value; my %ranges = RT::Ticket->CustomDateRanges; if ( my $spec = $ranges{$range_name} ) { $value = $ticket->CustomDateRange( $range_name, { ref $spec eq 'HASH' ? %$spec : ( value => $spec ), format => sub { $_[0] }, } ); } $info{$key}{$name} = $self->_CalculateTime( $type, $value, $info{$key}{$name} ); } else { RT->Logger->error("Unsupported field $field->{INFO}[1]"); } } } for my $keys (@all_keys) { my $key = join ';;;', @$keys; push @{ $info{$key}{ids} }, $ticket->id; } } # Make generated results real SB results for my $key ( keys %info ) { my @keys = split /;;;/, $key; my $row; for my $group ( @groups ) { $row->{lc $group->{NAME}} = shift @keys; } for my $field ( keys %{ $info{$key} } ) { my $value = $info{$key}{$field}; if ( ref $value eq 'HASH' && $value->{calculate} ) { $row->{$field} = $value->{calculate}->($value); } else { $row->{$field} = $info{$key}{$field}; } } my $item = $self->NewItem(); $item->LoadFromHash($row); $self->AddRecord($item); } $self->{must_redo_search} = 0; $self->{is_limited} = 1; $self->PostProcessRecords; return; } $self->SUPER::_DoSearch( @_ ); if ( $self->{'must_redo_search'} ) { $RT::Logger->crit( "_DoSearch is not so successful as it still needs redo search, won't call AddEmptyRows" ); } else { $self->PostProcessRecords; } } =head2 _FieldToFunction FIELD Returns a tuple of the field or a database function to allow grouping on that field. =cut sub _FieldToFunction { my $self = shift; my %args = (@_); $args{'FIELD'} ||= $args{'KEY'}; my $meta = $GROUPINGS_META{ $GROUPINGS{ $args{'KEY'} } }; return ('FUNCTION' => 'NULL') unless $meta; return %args unless $meta->{'Function'}; my $code = $self->FindImplementationCode( $meta->{'Function'} ); return ('FUNCTION' => 'NULL') unless $code; return $code->( $self, %args ); } # Gotta skip over RT::Tickets->Next, since it does all sorts of crazy magic we # don't want. sub Next { my $self = shift; $self->RT::SearchBuilder::Next(@_); } sub NewItem { my $self = shift; my $res = RT::Report::Tickets::Entry->new($self->CurrentUser); $res->{'report'} = $self; weaken $res->{'report'}; return $res; } # This is necessary since normally NewItem (above) is used to intuit the # correct class. However, since we're abusing a subclass, it's incorrect. sub _RoleGroupClass { "RT::Ticket" } sub _SingularClass { "RT::Report::Tickets::Entry" } sub SortEntries { my $self = shift; $self->_DoSearch if $self->{'must_redo_search'}; return unless $self->{'items'} && @{ $self->{'items'} }; my @groups = grep $_->{'TYPE'} eq 'grouping', map $self->ColumnInfo($_), $self->ColumnsList; return unless @groups; my @SORT_OPS; my $by_multiple = sub ($$) { for my $f ( @SORT_OPS ) { my $r = $f->($_[0], $_[1]); return $r if $r; } }; my @data = map [$_], @{ $self->{'items'} }; for ( my $i = 0; $i < @groups; $i++ ) { my $group_by = $groups[$i]; my $idx = $i+1; my $order = $group_by->{'META'}{Sort} || 'label'; my $method = $order =~ /label$/ ? 'LabelValue' : 'RawValue'; unless ($order =~ /^numeric/) { # Traverse the values being used for labels. # If they all look like numbers or undef, flag for a numeric sort. my $looks_like_number = 1; foreach my $item (@data){ my $label = $item->[0]->$method($group_by->{'NAME'}); $looks_like_number = 0 unless (not defined $label) or Scalar::Util::looks_like_number( $label ); } $order = "numeric $order" if $looks_like_number; } if ( $order eq 'label' ) { push @SORT_OPS, sub { $_[0][$idx] cmp $_[1][$idx] }; $method = 'LabelValue'; } elsif ( $order eq 'numeric label' ) { my $nv = $self->loc("(no value)"); # Sort the (no value) elements first, by comparing for them # first, and falling back to a numeric sort on all other # values. push @SORT_OPS, sub { (($_[0][$idx] ne $nv) <=> ($_[1][$idx] ne $nv)) || ( $_[0][$idx] <=> $_[1][$idx] ) }; $method = 'LabelValue'; } elsif ( $order eq 'raw' ) { push @SORT_OPS, sub { ($_[0][$idx]//'') cmp ($_[1][$idx]//'') }; $method = 'RawValue'; } elsif ( $order eq 'numeric raw' ) { push @SORT_OPS, sub { $_[0][$idx] <=> $_[1][$idx] }; $method = 'RawValue'; } elsif ( $order eq 'duration' ) { push @SORT_OPS, sub { $_[0][$idx] <=> $_[1][$idx] }; $method = 'DurationValue'; } else { $RT::Logger->error("Unknown sorting function '$order'"); next; } $_->[$idx] = $_->[0]->$method( $group_by->{'NAME'} ) for @data; } $self->{'items'} = [ map $_->[0], sort $by_multiple @data ]; } sub PostProcessRecords { my $self = shift; my $info = $self->{'column_info'}; foreach my $column ( values %$info ) { next unless $column->{'TYPE'} eq 'statistic'; if ( $column->{'META'}{'Calculate'} ) { $self->CalculatePostFunction( $column ); } elsif ( $column->{'META'}{'SubValues'} ) { $self->MapSubValues( $column ); } } } sub CalculatePostFunction { my $self = shift; my $info = shift; my $code = $self->FindImplementationCode( $info->{'META'}{'Calculate'} ); unless ( $code ) { # TODO: fill in undefs return; } my $column = $info->{'NAME'}; my $base_query = $self->Query; foreach my $item ( @{ $self->{'items'} } ) { $item->{'values'}{ lc $column } = $code->( $self, Query => join( ' AND ', map "($_)", grep defined && length, $base_query, $item->Query, ), ); $item->{'fetched'}{ lc $column } = 1; } } sub MapSubValues { my $self = shift; my $info = shift; my $to = $info->{'NAME'}; my $map = $info->{'MAP'}; foreach my $item ( @{ $self->{'items'} } ) { my $dst = $item->{'values'}{ lc $to } = { }; while (my ($k, $v) = each %{ $map } ) { $dst->{ $k } = delete $item->{'values'}{ lc $v->{'NAME'} }; # This mirrors the logic in RT::Record::__Value When that # ceases tp use the UTF-8 flag as a character/byte # distinction from the database, this can as well. utf8::decode( $dst->{ $k } ) if defined $dst->{ $k } and not utf8::is_utf8( $dst->{ $k } ); delete $item->{'fetched'}{ lc $v->{'NAME'} }; } $item->{'fetched'}{ lc $to } = 1; } } sub GenerateDateFunction { my $self = shift; my %args = @_; my $tz; if ( RT->Config->Get('ChartsTimezonesInDB') ) { my $to = $self->CurrentUser->UserObj->Timezone || RT->Config->Get('Timezone'); $tz = { From => 'UTC', To => $to } if $to && lc $to ne 'utc'; } $args{'FUNCTION'} = $RT::Handle->DateTimeFunction( Type => $args{'SUBKEY'}, Field => $self->NotSetDateToNullFunction, Timezone => $tz, ); return %args; } sub GenerateCustomFieldFunction { my $self = shift; my %args = @_; my ($name) = ( $args{'SUBKEY'} =~ /^\{(.*)\}$/ ); my $cf = RT::CustomField->new( $self->CurrentUser ); $cf->Load($name); unless ( $cf->id ) { $RT::Logger->error("Couldn't load CustomField #$name"); @args{qw(FUNCTION FIELD)} = ('NULL', undef); } else { my ($ticket_cf_alias, $cf_alias) = $self->_CustomFieldJoin($cf->id, $cf); @args{qw(ALIAS FIELD)} = ($ticket_cf_alias, 'Content'); } return %args; } sub GenerateUserFunction { my $self = shift; my %args = @_; my $column = $args{'SUBKEY'} || 'Name'; my $u_alias = $self->{"_sql_report_$args{FIELD}_users_$column"} ||= $self->Join( TYPE => 'LEFT', ALIAS1 => 'main', FIELD1 => $args{'FIELD'}, TABLE2 => 'Users', FIELD2 => 'id', ); @args{qw(ALIAS FIELD)} = ($u_alias, $column); return %args; } sub GenerateWatcherFunction { my $self = shift; my %args = @_; my $type = $args{'FIELD'}; $type = '' if $type eq 'Watcher'; my $column = $args{'SUBKEY'} || 'Name'; my $u_alias = $self->{"_sql_report_watcher_users_alias_$type"}; unless ( $u_alias ) { my ($g_alias, $gm_alias); ($g_alias, $gm_alias, $u_alias) = $self->_WatcherJoin( Name => $type ); $self->{"_sql_report_watcher_users_alias_$type"} = $u_alias; } @args{qw(ALIAS FIELD)} = ($u_alias, $column); return %args; } sub DurationAsString { my $self = shift; my %args = @_; my $v = $args{'VALUE'}; my $max_unit = $args{INFO} && ref $args{INFO}[-1] && $args{INFO}[-1]{business_time} ? 'hour' : 'year'; unless ( ref $v ) { return $self->loc("(no value)") unless defined $v && length $v; return RT::Date->new( $self->CurrentUser )->DurationAsString( $v, Show => 3, Short => 1, MaxUnit => $max_unit, ); } my $date = RT::Date->new( $self->CurrentUser ); my %res = %$v; foreach my $e ( values %res ) { $e = $date->DurationAsString( $e, Short => 1, Show => 3, MaxUnit => $max_unit ) if defined $e && length $e; $e = $self->loc("(no value)") unless defined $e && length $e; } return \%res; } sub LabelValueCode { my $self = shift; my $name = shift; my $display = $self->ColumnInfo( $name )->{'META'}{'Display'}; return undef unless $display; return $self->FindImplementationCode( $display ); } sub FindImplementationCode { my $self = shift; my $value = shift; my $silent = shift; my $code; unless ( $value ) { $RT::Logger->error("Value is not defined. Should be method name or code reference") unless $silent; return undef; } elsif ( !ref $value ) { $code = $self->can( $value ); unless ( $code ) { $RT::Logger->error("No method $value in ". (ref $self || $self) ." class" ) unless $silent; return undef; } } elsif ( ref( $value ) eq 'CODE' ) { $code = $value; } else { $RT::Logger->error("$value is not method name or code reference") unless $silent; return undef; } return $code; } sub Serialize { my $self = shift; my %clone = %$self; # current user, handle and column_info delete @clone{'user', 'DBIxHandle', 'column_info'}; $clone{'items'} = [ map $_->{'values'}, @{ $clone{'items'} || [] } ]; $clone{'column_info'} = {}; while ( my ($k, $v) = each %{ $self->{'column_info'} } ) { $clone{'column_info'}{$k} = { %$v }; delete $clone{'column_info'}{$k}{'META'}; } return \%clone; } sub Deserialize { my $self = shift; my $data = shift; $self->CleanSlate; %$self = (%$self, %$data); $self->{'items'} = [ map { my $r = $self->NewItem; $r->LoadFromHash( $_ ); $r } @{ $self->{'items'} } ]; foreach my $e ( values %{ $self->{column_info} } ) { $e->{'META'} = $e->{'TYPE'} eq 'grouping' ? $GROUPINGS_META{ $e->{'INFO'} } : $STATISTICS_META{ $e->{'INFO'}[1] } } } sub FormatTable { my $self = shift; my %columns = @_; my (@head, @body, @footer); @head = ({ cells => []}); foreach my $column ( @{ $columns{'Groups'} } ) { push @{ $head[0]{'cells'} }, { type => 'head', value => $self->Label( $column ) }; } my $i = 0; while ( my $entry = $self->Next ) { $body[ $i ] = { even => ($i+1)%2, cells => [] }; $i++; } @footer = ({ even => ++$i%2, cells => []}); my $g = 0; foreach my $column ( @{ $columns{'Groups'} } ) { $i = 0; my $last; while ( my $entry = $self->Next ) { my $value = $entry->LabelValue( $column ); if ( !$last || $last->{'value'} ne $value ) { push @{ $body[ $i++ ]{'cells'} }, $last = { type => 'label', value => $value }; $last->{even} = $g++ % 2 unless $column eq $columns{'Groups'}[-1]; } else { $i++; $last->{rowspan} = ($last->{rowspan}||1) + 1; } } } push @{ $footer[0]{'cells'} }, { type => 'label', value => $self->loc('Total'), colspan => scalar @{ $columns{'Groups'} }, }; my $pick_color = do { my @colors = RT->Config->Get("ChartColors"); sub { $colors[ $_[0] % @colors - 1 ] } }; my $function_count = 0; foreach my $column ( @{ $columns{'Functions'} } ) { $i = 0; my $info = $self->ColumnInfo( $column ); my @subs = (''); if ( $info->{'META'}{'SubValues'} ) { @subs = $self->FindImplementationCode( $info->{'META'}{'SubValues'} )->( $self ); } my %total; unless ( $info->{'META'}{'NoTotals'} ) { while ( my $entry = $self->Next ) { my $raw = $entry->RawValue( $column ) || {}; $raw = { '' => $raw } unless ref $raw; $total{ $_ } += $raw->{ $_ } foreach grep $raw->{$_}, @subs; } @subs = grep $total{$_}, @subs unless $info->{'META'}{'NoHideEmpty'}; } my $label = $self->Label( $column ); unless (@subs) { while ( my $entry = $self->Next ) { push @{ $body[ $i++ ]{'cells'} }, { type => 'value', value => undef, query => $entry->Query, }; } push @{ $head[0]{'cells'} }, { type => 'head', value => $label, rowspan => scalar @head, color => $pick_color->(++$function_count), }; push @{ $footer[0]{'cells'} }, { type => 'value', value => undef }; next; } if ( @subs > 1 && @head == 1 ) { $_->{rowspan} = 2 foreach @{ $head[0]{'cells'} }; } if ( @subs == 1 ) { push @{ $head[0]{'cells'} }, { type => 'head', value => $label, rowspan => scalar @head, color => $pick_color->(++$function_count), }; } else { push @{ $head[0]{'cells'} }, { type => 'head', value => $label, colspan => scalar @subs }; push @{ $head[1]{'cells'} }, { type => 'head', value => $_, color => $pick_color->(++$function_count) } foreach @subs; } while ( my $entry = $self->Next ) { my $query = $entry->Query; my $value = $entry->LabelValue( $column ) || {}; $value = { '' => $value } unless ref $value; foreach my $e ( @subs ) { push @{ $body[ $i ]{'cells'} }, { type => 'value', value => $value->{ $e }, query => $query, }; } $i++; } unless ( $info->{'META'}{'NoTotals'} ) { my $total_code = $self->LabelValueCode( $column ); foreach my $e ( @subs ) { my $total = $total{ $e }; $total = $total_code->( $self, %$info, VALUE => $total ) if $total_code; push @{ $footer[0]{'cells'} }, { type => 'value', value => $total }; } } else { foreach my $e ( @subs ) { push @{ $footer[0]{'cells'} }, { type => 'value', value => undef }; } } } return thead => \@head, tbody => \@body, tfoot => \@footer; } sub _CalculateTime { my $self = shift; my ( $type, $value, $current ) = @_; return $current unless defined $value; if ( $type eq 'SUM' ) { $current += $value; } elsif ( $type eq 'AVG' ) { $current ||= {}; $current->{total} += $value; $current->{count}++; $current->{calculate} ||= sub { my $item = shift; return sprintf '%.0f', $item->{total} / $item->{count}; }; } elsif ( $type eq 'MAX' ) { $current = $value unless $current && $current > $value; } elsif ( $type eq 'MIN' ) { $current = $value unless $current && $current < $value; } else { RT->Logger->error("Unsupported type $type"); } return $current; } sub new { my $self = shift; $self->_SetupCustomDateRanges; return $self->SUPER::new(@_); } sub _SetupCustomDateRanges { my $self = shift; my %names; # Remove old custom date range groupings for my $field ( grep {ref} @STATISTICS ) { if ( $field->[1] && $field->[1] eq 'CustomDateRangeAll' ) { $names{ $field->[2] } = 1; } } my ( @new_groupings, @new_statistics ); while (@GROUPINGS) { my $name = shift @GROUPINGS; my $type = shift @GROUPINGS; if ( !$names{$name} ) { push @new_groupings, $name, $type; } } while (@STATISTICS) { my $key = shift @STATISTICS; my $info = shift @STATISTICS; my ($name) = $key =~ /^(?:ALL|SUM|AVG|MIN|MAX)\((.+)\)$/; unless ( $name && $names{$name} ) { push @new_statistics, $key, $info; } } # Add new ones my %ranges = RT::Ticket->CustomDateRanges; for my $name ( sort keys %ranges ) { my %extra_info; my $spec = $ranges{$name}; if ( ref $spec && $spec->{business_time} ) { $extra_info{business_time} = 1; } push @new_groupings, $name => $extra_info{business_time} ? 'DurationInBusinessHours' : 'Duration'; push @new_statistics, ( "ALL($name)" => [ "Summary of $name", 'CustomDateRangeAll', $name, \%extra_info ], "SUM($name)" => [ "Total $name", 'CustomDateRange', 'SUM', $name, \%extra_info ], "AVG($name)" => [ "Average $name", 'CustomDateRange', 'AVG', $name, \%extra_info ], "MIN($name)" => [ "Minimum $name", 'CustomDateRange', 'MIN', $name, \%extra_info ], "MAX($name)" => [ "Maximum $name", 'CustomDateRange', 'MAX', $name, \%extra_info ], ); } @GROUPINGS = @new_groupings; @STATISTICS = @new_statistics; %GROUPINGS = %STATISTICS = (); return 1; } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Report/Tickets/Entry.pm�������������������������������������������������������������000644 �000765 �000024 �00000013610 14005011336 020415� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Report::Tickets::Entry; use warnings; use strict; use base qw/RT::Record/; # XXX TODO: how the heck do we acl a report? sub CurrentUserHasRight {1} =head2 LabelValue If you're pulling a value out of this collection and using it as a label, you may want the "cleaned up" version. This includes scrubbing 1970 dates and ensuring that dates are in local not DB timezones. =cut sub LabelValue { my $self = shift; my $name = shift; my $raw = $self->RawValue( $name, @_ ); if ( my $code = $self->Report->LabelValueCode( $name ) ) { $raw = $code->( $self, %{ $self->Report->ColumnInfo( $name ) }, VALUE => $raw ); return $self->loc('(no value)') unless defined $raw && length $raw; return $raw; } unless ( ref $raw ) { return $self->loc('(no value)') unless defined $raw && length $raw; return $self->loc($raw) if $self->Report->ColumnInfo( $name )->{'META'}{'Localize'}; return $raw; } else { my $loc = $self->Report->ColumnInfo( $name )->{'META'}{'Localize'}; my %res = %$raw; if ( $loc ) { $res{ $self->loc($_) } = delete $res{ $_ } foreach keys %res; $_ = $self->loc($_) foreach values %res; } $_ = $self->loc('(no value)') foreach grep !defined || !length, values %res; return \%res; } } sub RawValue { return (shift)->__Value( @_ ); } sub ObjectType { return 'RT::Ticket'; } sub CustomFieldLookupType { RT::Ticket->CustomFieldLookupType } sub Query { my $self = shift; if ( my $ids = $self->{values}{ids} ) { return join ' OR ', map "id=$_", @$ids; } my @parts; foreach my $column ( $self->Report->ColumnsList ) { my $info = $self->Report->ColumnInfo( $column ); next unless $info->{'TYPE'} eq 'grouping'; my $custom = $info->{'META'}{'Query'}; if ( $custom and my $code = $self->Report->FindImplementationCode( $custom ) ) { push @parts, $code->( $self, COLUMN => $column, %$info ); } else { my $field = join '.', grep $_, $info->{KEY}, $info->{SUBKEY}; my $value = $self->RawValue( $column ); my $op = '='; if ( defined $value ) { unless ( $value =~ /^\d+$/ ) { $value =~ s/(['\\])/\\$1/g; $value = "'$value'"; } } else { ($op, $value) = ('IS', 'NULL'); } unless ( $field =~ /^[{}\w\.]+$/ ) { $field =~ s/(['\\])/\\$1/g; $field = "'$field'"; } push @parts, "$field $op $value"; } } return () unless @parts; return join ' AND ', map "($_)", grep defined && length, @parts; } sub Report { return $_[0]->{'report'}; } sub DurationValue { my $self = shift; my $value = $self->__Value(@_); return 0 unless $value; my $number; my $unit; if ( $value =~ /([\d,]+)(?:s| second)/ ) { $number = $1; $unit = 1; } elsif ( $value =~ /([\d,]+)(?:m| minute)/ ) { $number = $1; $unit = $RT::Date::MINUTE; } elsif ( $value =~ /([\d,]+)(?:h| hour)/ ) { $number = $1; $unit = $RT::Date::HOUR; } elsif ( $value =~ /([\d,]+)(?:d| day)/ ) { $number = $1; $unit = $RT::Date::DAY; } elsif ( $value =~ /([\d,]+)(?:W| week)/ ) { $number = $1; $unit = $RT::Date::WEEK; } elsif ( $value =~ /([\d,]+)(?:M| month)/ ) { $number = $1; $unit = $RT::Date::MONTH; } elsif ( $value =~ /([\d,]+)(?:Y| year)/ ) { $number = $1; $unit = $RT::Date::YEAR; } else { return -.1; # Mark "(no value)" as -1 so it comes before 0 } $number =~ s!,!!g; my $seconds = $number * $unit; if ( $value =~ /([<|>])/ ) { $seconds += $1 eq '<' ? -1 : 1; } return $seconds; } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Squish/JS.pm������������������������������������������������������������������������000644 �000765 �000024 �00000006055 14005011336 016230� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 SYNOPSIS use RT::Squish::JS; my $squish = RT::Squish::JS->new(); =head1 DESCRIPTION This module lets you create squished content of js files. =head1 METHODS =cut use strict; use warnings; package RT::Squish::JS; use base 'RT::Squish'; use JavaScript::Minifier::XS (); =head2 Squish not only concatenate files, but also minify them =cut sub Squish { my $self = shift; my $content = ""; for my $file ( RT::Interface::Web->JSFiles ) { my $uri = $file =~ m{^/} ? $file : "/static/js/$file"; my $res = RT::Interface::Web::Handler->GetStatic($uri); if ($res->is_success) { if ( $file =~ /\.min\.js$/ ) { $content .= $res->decoded_content . "\n"; } else { $content .= $self->Filter($res->decoded_content) . "\n"; } } else { RT->Logger->error("Unable to fetch $uri for JS Squishing: " . $res->status_line); next; } } return $content; } sub Filter { my ( $self, $content ) = @_; return JavaScript::Minifier::XS::minify($content); } 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Squish/CSS.pm�����������������������������������������������������������������������000644 �000765 �000024 �00000005274 14005011336 016346� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 SYNOPSIS use RT::Squish::CSS; my $squish = RT::Squish::CSS->new( Style => 'elevator-light'); =head1 DESCRIPTION This module lets you create squished content of css files. =head1 METHODS =cut use strict; use warnings; package RT::Squish::CSS; use base 'RT::Squish', 'CSS::Squish'; use CSS::Minifier::XS (); __PACKAGE__->mk_accessors(qw/Style/); =head2 Squish use CSS::Squish to squish css =cut sub Squish { my $self = shift; my $style = $self->Style; return $self->Filter( $self->concatenate( "$style/main.css", RT->Config->Get('CSSFiles') ) ); } sub roots { map { "$_/css" } RT::Interface::Web->StaticRoots } sub Filter { my ( $self, $content ) = @_; return CSS::Minifier::XS::minify($content); } 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Condition/QueueChange.pm������������������������������������������������������������000644 �000765 �000024 �00000004425 14005011336 020557� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Condition::QueueChange; use base 'RT::Condition'; use strict; use warnings; =head2 IsApplicable If the queue has changed. =cut sub IsApplicable { my $self = shift; if ($self->TransactionObj->Field eq 'Queue') { return(1); } else { return(undef); } } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Condition/PriorityExceeds.pm��������������������������������������������������������000644 �000765 �000024 �00000004460 14005011336 021506� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Condition::PriorityExceeds; use base 'RT::Condition'; use strict; use warnings; =head2 IsApplicable If the priority exceeds the argument value =cut sub IsApplicable { my $self = shift; if ($self->TicketObj->Priority > $self->Argument) { return(1); } else { return(undef); } } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Condition/ReopenTicket.pm�����������������������������������������������������������000644 �000765 �000024 �00000005415 14005011336 020761� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Condition::ReopenTicket; use strict; use warnings; use base 'RT::Condition'; =head2 IsApplicable If the ticket was repopened, ie status was changed from any inactive status to an active. See F<RT_Config.pm> for C<ActiveStatuses> and C<InactiveStatuses> options. =cut sub IsApplicable { my $self = shift; my $txn = $self->TransactionObj; return 0 unless $txn->Type eq "Status" || ( $txn->Type eq "Set" && $txn->Field eq "Status" ); my $queue = $self->TicketObj->QueueObj; return 0 unless $queue->IsInactiveStatus( $txn->OldValue ); return 0 unless $queue->IsActiveStatus( $txn->NewValue ); $RT::Logger->debug("Condition 'On Reopen' triggered " ."for ticket #". $self->TicketObj->id ." transaction #". $txn->id ); return 1; } RT::Base->_ImportOverlays(); 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Condition/AnyTransaction.pm���������������������������������������������������������000644 �000765 �000024 �00000004325 14005011336 021321� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Condition::AnyTransaction; use base 'RT::Condition'; use strict; use warnings; =head2 IsApplicable This happens on every transaction. it's always applicable =cut sub IsApplicable { my $self = shift; return(1); } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Condition/TimeWorkedChange.pm�������������������������������������������������������000644 �000765 �000024 �00000005074 14005011336 021546� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Condition::TimeWorkedChange; use base 'RT::Condition'; =head1 NAME RT::Condition::TimeWorkedChange - RT's scrip condition that fires when the TimeWorked field has a value at form submission. =head1 DESCRIPTION This condition is true when the transaction has a TimeTaken value or the TimeWorked field is being updated. =cut sub IsApplicable { my $self = shift; my $txn = $self->TransactionObj; return 1 if $txn->TimeTaken; return 1 if $txn->Type eq 'Set' && $txn->Field eq 'TimeWorked' && ( $txn->NewValue - $txn->OldValue ); return 0; } 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Condition/SLA_RequireDueSet.pm������������������������������������������������������000644 �000765 �000024 �00000005606 14005011336 021614� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Condition::SLA_RequireDueSet; use base qw(RT::Condition::SLA); =head1 NAME RT::Condition::SLA_RequireDueSet - checks if Due date require update =head1 DESCRIPTION Checks if Due date require update. This should be done when we create a ticket and it has service level value or when we set service level. =cut sub IsApplicable { my $self = shift; return 0 unless $self->SLAIsApplied; return 0 if $self->TicketObj->QueueObj->SLADisabled; my $type = $self->TransactionObj->Type; if ( $type eq 'Create' || $type eq 'Correspond' ) { return 1 if $self->TicketObj->SLA; return 0; } elsif ( $type eq 'Status' || ($type eq 'Set' && $self->TransactionObj->Field eq 'Status') ) { return 1 if $self->TicketObj->SLA; return 0; } elsif ( $type eq 'SLA' || ($type eq 'Set' && $self->TransactionObj->Field eq 'SLA') ) { return 1; } return 0; } 1; ��������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Condition/OwnerChange.pm������������������������������������������������������������000644 �000765 �000024 �00000005246 14005011336 020567� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Condition::OwnerChange; use base 'RT::Condition'; use strict; use warnings; =head2 IsApplicable If we're changing the owner return true, otherwise return false =cut sub IsApplicable { my $self = shift; return unless ( $self->TransactionObj->Field || '' ) eq 'Owner'; # For tickets, there is both a Set txn (for the column) and a # SetWatcher txn (for the group); we fire on the former for # historical consistency. Non-ticket objects will not have a # denormalized Owner column, and thus need fire on the SetWatcher. return if $self->TransactionObj->Type eq "SetWatcher" and $self->TransactionObj->ObjectType eq "RT::Ticket"; return(1); } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Condition/BeforeDue.pm��������������������������������������������������������������000644 �000765 �000024 �00000006055 14005011336 020226� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Condition::BeforeDue =head1 DESCRIPTION Returns true if the ticket we're operating on is within the amount of time defined by the passed in argument. The passed in value is a date in the format "1d2h3m4s" for 1 day and 2 hours and 3 minutes and 4 seconds. Single units can also be passed such as 1d for just one day. =cut package RT::Condition::BeforeDue; use base 'RT::Condition'; use RT::Date; use strict; use warnings; sub IsApplicable { my $self = shift; # Parse date string. Format is "1d2h3m4s" for 1 day and 2 hours # and 3 minutes and 4 seconds. my %e; foreach (qw(d h m s)) { my @vals = $self->Argument =~ m/(\d+)$_/i; $e{$_} = pop @vals || 0; } my $elapse = $e{'d'} * 24*60*60 + $e{'h'} * 60*60 + $e{'m'} * 60 + $e{'s'}; my $cur = RT::Date->new( RT->SystemUser ); $cur->SetToNow(); my $due = $self->TicketObj->DueObj; return (undef) unless $due->IsSet; my $diff = $due->Diff($cur); if ( $diff >= 0 and $diff <= $elapse ) { return(1); } else { return(undef); } } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Condition/PriorityChange.pm���������������������������������������������������������000644 �000765 �000024 �00000004517 14005011336 021316� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Condition::PriorityChange; use base 'RT::Condition'; use strict; use warnings; =head2 IsApplicable If the argument passed in is equivalent to the new value of the Priority Obj =cut sub IsApplicable { my $self = shift; if ($self->TransactionObj->Field eq 'Priority') { return(1); } else { return(undef); } } RT::Base->_ImportOverlays(); 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Condition/SLA_RequireStartsSet.pm���������������������������������������������������000644 �000765 �000024 �00000004651 14005011336 022356� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Condition::SLA_RequireStartsSet; use base qw(RT::Condition::SLA); =head1 NAME RT::Condition::SLA_RequireStartsSet - checks if Starts date is not set =head1 DESCRIPTION Applies if Starts date is not set for the ticket. =cut sub IsApplicable { my $self = shift; return 0 if $self->TicketObj->StartsObj->Unix > 0; return 0 if $self->TicketObj->QueueObj->SLADisabled; return 0 unless $self->TicketObj->SLA; return 1; } 1; ���������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Condition/CloseTicket.pm������������������������������������������������������������000644 �000765 �000024 �00000005165 14005011336 020600� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Condition::CloseTicket; use strict; use warnings; use base 'RT::Condition'; =head2 IsApplicable If the ticket was closed, ie status was changed from any active status to an inactive. See F<RT_Config.pm> for C<ActiveStatuses> and C<InactiveStatuses> options. =cut sub IsApplicable { my $self = shift; my $txn = $self->TransactionObj; return 0 unless $txn->Type eq "Status" || ( $txn->Type eq "Set" && $txn->Field eq "Status" ); my $queue = $self->TicketObj->QueueObj; return 0 unless $queue->IsActiveStatus( $txn->OldValue ); return 0 unless $queue->IsInactiveStatus( $txn->NewValue ); return 1; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Condition/UserDefined.pm������������������������������������������������������������000644 �000765 �000024 �00000004650 14005011336 020562� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Condition::UserDefined; use base 'RT::Condition'; use strict; use warnings; =head2 IsApplicable This happens on every transaction. it's always applicable =cut sub IsApplicable { my $self = shift; local $@; my $retval = eval $self->ScripObj->CustomIsApplicableCode; if ($@) { $RT::Logger->error("Scrip ".$self->ScripObj->Id. " IsApplicable failed: ".$@); return (undef); } return ($retval); } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Condition/Overdue.pm����������������������������������������������������������������000644 �000765 �000024 �00000004712 14005011336 017775� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Condition::Overdue =head1 DESCRIPTION Returns true if the ticket we're operating on is overdue =cut package RT::Condition::Overdue; use base 'RT::Condition'; use strict; use warnings; =head2 IsApplicable If the due date is before "now" return true =cut sub IsApplicable { my $self = shift; if ($self->TicketObj->DueObj->IsSet and $self->TicketObj->DueObj->Unix < time()) { return(1); } else { return(undef); } } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������rt-5.0.1/lib/RT/Condition/SLA.pm��������������������������������������������������������������������000644 �000765 �000024 �00000004135 14005011336 017002� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Condition::SLA; use base qw(RT::SLA RT::Condition); =head1 SLAIsApplied =cut sub SLAIsApplied { return 1 } 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Condition/StatusChange.pm�����������������������������������������������������������000644 �000765 �000024 �00000011006 14005011336 020747� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Condition::StatusChange; use base 'RT::Condition'; use strict; use warnings; =head2 DESCRIPTION This condition check passes if the current transaction is a status change. The argument can be used to apply additional conditions on old and new values. If argument is empty then the check passes for any change of the status field. If argument is equal to new value then check is passed. This is behavior is close to RT 3.8 and older. For example, setting the argument to 'resolved' means 'fire scrip when status changed from any to resolved'. The following extended format is supported: old: comma separated list; new: comma separated list For example: old: open; new: resolved You can omit old or new part, for example: old: open new: resolved You can specify multiple values, for example: old: new, open; new: resolved, rejected Status sets ('initial', 'active' or 'inactive') can be used, for example: old: active; new: inactive old: initial, active; new: resolved =cut sub IsApplicable { my $self = shift; my $txn = $self->TransactionObj; my ($type, $field) = ($txn->Type, $txn->Field); return 0 unless $type eq 'Status' || ($type eq 'Set' && $field eq 'Status'); my $argument = $self->Argument; return 1 unless $argument; my $new = $txn->NewValue || ''; return 1 if $argument eq $new; # let's parse argument my ($old_must_be, $new_must_be) = ('', ''); if ( $argument =~ /^\s*old:\s*(.*);\s*new:\s*(.*)\s*$/i ) { ($old_must_be, $new_must_be) = ($1, $2); } elsif ( $argument =~ /^\s*new:\s*(.*)\s*$/i ) { $new_must_be = $1; } elsif ( $argument =~ /^\s*old:\s*(.*)\s*$/i ) { $old_must_be = $1; } else { $RT::Logger->error("Argument '$argument' is incorrect.") unless RT::Lifecycle->Load(Type => 'ticket')->IsValid( $argument ); return 0; } my $lifecycle = $self->TicketObj->LifecycleObj; if ( $new_must_be ) { return 0 unless grep lc($new) eq lc($_), map {m/^(initial|active|inactive)$/i? $lifecycle->Valid(lc $_): $_ } grep defined && length, map { s/^\s+//; s/\s+$//; $_ } split /,/, $new_must_be; } if ( $old_must_be ) { my $old = lc($txn->OldValue || ''); return 0 unless grep $old eq lc($_), map {m/^(initial|active|inactive)$/i? $lifecycle->Valid(lc $_): $_ } grep defined && length, map { s/^\s+//; s/\s+$//; $_ } split /,/, $old_must_be; } return 1; } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Migrate/Serializer/�����������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 017575� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Migrate/Importer/�������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 017265� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Migrate/Importer.pm�����������������������������������������������������������������000644 �000765 �000024 �00000034345 14005011336 017634� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Migrate::Importer; use strict; use warnings; use Storable qw//; use File::Spec; use Carp qw/carp/; sub new { my $class = shift; my $self = bless {}, $class; $self->Init(@_); return $self; } sub Init { my $self = shift; my %args = ( OriginalId => undef, Progress => undef, Statefile => undef, DumpObjects => undef, HandleError => undef, ExcludeOrganization => undef, @_, ); # Should we attempt to preserve record IDs as they are created? $self->{OriginalId} = $args{OriginalId}; $self->{ExcludeOrganization} = $args{ExcludeOrganization}; $self->{Progress} = $args{Progress}; $self->{HandleError} = sub { 0 }; $self->{HandleError} = $args{HandleError} if $args{HandleError} and ref $args{HandleError} eq 'CODE'; if ($args{DumpObjects}) { require Data::Dumper; $self->{DumpObjects} = { map { $_ => 1 } @{$args{DumpObjects}} }; } # Objects we've created $self->{UIDs} = {}; # Columns we need to update when an object is later created $self->{Pending} = {}; # Objects missing from the source database before serialization $self->{Invalid} = []; # What we created $self->{ObjectCount} = {}; # To know what global CFs need to be unglobal'd and applied to what $self->{NewQueues} = []; $self->{NewCFs} = []; } sub Metadata { my $self = shift; return $self->{Metadata}; } sub LoadMetadata { my $self = shift; my ($data) = @_; return if $self->{Metadata}; $self->{Metadata} = $data; die "Incompatible format version: ".$data->{Format} if $data->{Format} ne "0.8"; $self->{Organization} = $data->{Organization}; $self->{Clone} = $data->{Clone}; $self->{Incremental} = $data->{Incremental}; $self->{Files} = $data->{Files} if $data->{Final}; } sub InitStream { my $self = shift; die "Stream initialized after objects have been recieved!" if keys %{ $self->{UIDs} }; die "Cloning does not support importing the Original Id separately\n" if $self->{OriginalId} and $self->{Clone}; die "RT already contains data; overwriting will not work\n" if ($self->{Clone} and not $self->{Incremental}) and RT->SystemUser->Id; # Basic facts of life, as a safety net $self->Resolve( RT->System->UID => ref RT->System, RT->System->Id ); $self->SkipTransactions( RT->System->UID ); if ($self->{OriginalId}) { # Where to shove the original ticket ID my $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName( Name => $self->{OriginalId}, LookupType => RT::Ticket->CustomFieldLookupType, ObjectId => 0 ); unless ($cf->Id) { warn "Failed to find global CF named $self->{OriginalId} -- creating one"; $cf->Create( Queue => 0, Name => $self->{OriginalId}, Type => 'FreeformSingle', ); } } } sub Resolve { my $self = shift; my ($uid, $class, $id) = @_; $self->{UIDs}{$uid} = [ $class, $id ]; return unless $self->{Pending}{$uid}; for my $ref (@{$self->{Pending}{$uid}}) { my ($pclass, $pid) = @{ $self->Lookup( $ref->{uid} ) }; my $obj = $pclass->new( RT->SystemUser ); $obj->LoadByCols( Id => $pid ); $obj->__Set( Field => $ref->{column}, Value => $id, ) if defined $ref->{column}; $obj->__Set( Field => $ref->{classcolumn}, Value => $class, ) if defined $ref->{classcolumn}; $obj->__Set( Field => $ref->{uri}, Value => $self->LookupObj($uid)->URI, ) if defined $ref->{uri}; if (my $method = $ref->{method}) { $obj->$method($self, $ref, $class, $id); } } delete $self->{Pending}{$uid}; } sub Lookup { my $self = shift; my ($uid) = @_; unless (defined $uid) { carp "Tried to lookup an undefined UID"; return; } return $self->{UIDs}{$uid}; } sub LookupObj { my $self = shift; my ($uid) = @_; my $ref = $self->Lookup( $uid ); return unless $ref; my ($class, $id) = @{ $ref }; my $obj = $class->new( RT->SystemUser ); $obj->Load( $id ); return $obj; } sub Postpone { my $self = shift; my %args = ( for => undef, uid => undef, column => undef, classcolumn => undef, uri => undef, @_, ); my $uid = delete $args{for}; if (defined $uid) { push @{$self->{Pending}{$uid}}, \%args; } else { push @{$self->{Invalid}}, \%args; } } sub SkipTransactions { my $self = shift; my ($uid) = @_; return if $self->{Clone}; $self->{SkipTransactions}{$uid} = 1; } sub ShouldSkipTransaction { my $self = shift; my ($uid) = @_; return exists $self->{SkipTransactions}{$uid}; } sub MergeValues { my $self = shift; my ($obj, $data) = @_; for my $col (keys %{$data}) { next if defined $obj->__Value($col) and length $obj->__Value($col); next unless defined $data->{$col} and length $data->{$col}; if (ref $data->{$col}) { my $uid = ${ $data->{$col} }; my $ref = $self->Lookup( $uid ); if ($ref) { $data->{$col} = $ref->[1]; } else { $self->Postpone( for => $obj->UID, uid => $uid, column => $col, ); next; } } $obj->__Set( Field => $col, Value => $data->{$col} ); } } sub SkipBy { my $self = shift; my ($column, $class, $uid, $data) = @_; my $obj = $class->new( RT->SystemUser ); $obj->Load( $data->{$column} ); return unless $obj->Id; $self->SkipTransactions( $uid ); $self->Resolve( $uid => $class => $obj->Id ); return $obj; } sub MergeBy { my $self = shift; my ($column, $class, $uid, $data) = @_; my $obj = $self->SkipBy(@_); return unless $obj; $self->MergeValues( $obj, $data ); return 1; } sub Qualify { my $self = shift; my ($string) = @_; return $string if $self->{Clone}; return $string if not defined $self->{Organization}; return $string if $self->{ExcludeOrganization}; return $string if $self->{Organization} eq $RT::Organization; return $self->{Organization}.": $string"; } sub Create { my $self = shift; my ($class, $uid, $data) = @_; # Use a simpler pre-inflation if we're cloning if ($self->{Clone}) { $class->RT::Record::PreInflate( $self, $uid, $data ); } else { # Non-cloning always wants to make its own id delete $data->{id}; return unless $class->PreInflate( $self, $uid, $data ); } my $obj = $class->new( RT->SystemUser ); # Unlike MySQL and Oracle, Pg stores UTF-8 strings, without this, data # could be be wrongly encoded on Pg. if ( RT->Config->Get( 'DatabaseType' ) eq 'Pg' ) { for my $field ( keys %$data ) { if ( $data->{$field} && !utf8::is_utf8( $data->{$field} ) ) { # Make sure decoded data is valid UTF-8, otherwise Pg won't insert my $decoded; eval { local $SIG{__DIE__}; # don't exit importer for errors happen here $decoded = Encode::decode( 'UTF-8', $data->{$field}, Encode::FB_CROAK ); }; if ( $@ ) { warn "$uid contains invalid UTF-8 data in $field: $@, will store encoded string instead\n" . Data::Dumper::Dumper( $data ) . "\n"; } else { $data->{$field} = $decoded; } } } } my ($id, $msg) = eval { # catch and rethrow on the outside so we can provide more info local $SIG{__DIE__}; $obj->DBIx::SearchBuilder::Record::Create( %{$data} ); }; if (not $id or $@) { $msg ||= ''; # avoid undef my $err = "Failed to create $uid: $msg $@\n" . Data::Dumper::Dumper($data) . "\n"; if (not $self->{HandleError}->($self, $err)) { die $err; } else { return; } } $self->{ObjectCount}{$class}++; $self->Resolve( $uid => $class, $id ); # Load it back to get real values into the columns $obj = $class->new( RT->SystemUser ); $obj->Load( $id ); $obj->PostInflate( $self, $uid ); return $obj; } sub ReadStream { my $self = shift; my ($fh) = @_; no warnings 'redefine'; local *RT::Ticket::Load = sub { my $self = shift; my $id = shift; $self->LoadById( $id ); return $self->Id; }; my $loaded = Storable::fd_retrieve($fh); # Metadata is stored at the start of the stream as a hashref if (ref $loaded eq "HASH") { $self->LoadMetadata( $loaded ); $self->InitStream; return; } my ($class, $uid, $data) = @{$loaded}; if ($self->{Incremental}) { my $obj = $class->new( RT->SystemUser ); $obj->Load( $data->{id} ); if (not $uid) { # undef $uid means "delete it" $obj->Delete; $self->{ObjectCount}{$class}++; } elsif ( $obj->Id ) { # If it exists, update it $class->RT::Record::PreInflate( $self, $uid, $data ); $obj->__Set( Field => $_, Value => $data->{$_} ) for keys %{ $data }; $self->{ObjectCount}{$class}++; } else { # Otherwise, make it $obj = $self->Create( $class, $uid, $data ); } $self->{Progress}->($obj) if $obj and $self->{Progress}; return; } elsif ($self->{Clone}) { my $obj = $self->Create( $class, $uid, $data ); $self->{Progress}->($obj) if $obj and $self->{Progress}; return; } # If it's a queue, store its ID away, as we'll need to know # it to split global CFs into non-global across those # fields. We do this before inflating, so that queues which # got merged still get the CFs applied push @{$self->{NewQueues}}, $uid if $class eq "RT::Queue"; my $origid = $data->{id}; my $obj = $self->Create( $class, $uid, $data ); return unless $obj; # If it's a ticket, we might need to create a # TicketCustomField for the previous ID if ($class eq "RT::Ticket" and $self->{OriginalId}) { my $value = $self->{ExcludeOrganization} ? $origid : $self->Organization . ":$origid"; my ($id, $msg) = $obj->AddCustomFieldValue( Field => $self->{OriginalId}, Value => $value, RecordTransaction => 0, ); warn "Failed to add custom field to $uid: $msg" unless $id; } # If it's a CF, we don't know yet if it's global (the OCF # hasn't been created yet) to store away the CF for later # inspection push @{$self->{NewCFs}}, $uid if $class eq "RT::CustomField" and $obj->LookupType =~ /^RT::Queue/; $self->{Progress}->($obj) if $self->{Progress}; } sub CloseStream { my $self = shift; $self->{Progress}->(undef, 'force') if $self->{Progress}; return if $self->{Clone}; # Take global CFs which we made and make them un-global my @queues = grep {$_} map {$self->LookupObj( $_ )} @{$self->{NewQueues}}; for my $obj (map {$self->LookupObj( $_ )} @{$self->{NewCFs}}) { my $ocf = $obj->IsGlobal or next; $ocf->Delete; $obj->AddToObject( $_ ) for @queues; } $self->{NewQueues} = []; $self->{NewCFs} = []; } sub ObjectCount { my $self = shift; return %{ $self->{ObjectCount} }; } sub Missing { my $self = shift; return wantarray ? sort keys %{ $self->{Pending} } : keys %{ $self->{Pending} }; } sub Invalid { my $self = shift; return wantarray ? sort { $a->{uid} cmp $b->{uid} } @{ $self->{Invalid} } : $self->{Invalid}; } sub Organization { my $self = shift; return $self->{Organization}; } sub Progress { my $self = shift; return defined $self->{Progress} unless @_; return $self->{Progress} = $_[0]; } 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Migrate/Serializer.pm���������������������������������������������������������������000644 �000765 �000024 �00000043772 14005011336 020150� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Migrate::Serializer; use strict; use warnings; use base 'RT::DependencyWalker'; sub cmp_version($$) { RT::Handle::cmp_version($_[0],$_[1]) }; use RT::Migrate::Incremental; use RT::Migrate::Serializer::IncrementalRecord; use RT::Migrate::Serializer::IncrementalRecords; use List::MoreUtils 'none'; sub Init { my $self = shift; my %args = ( AllUsers => 1, AllGroups => 1, FollowDeleted => 1, FollowDisabled => 1, FollowScrips => 0, FollowTickets => 1, FollowTransactions => 1, FollowACL => 0, FollowAssets => 1, Clone => 0, Incremental => 0, Verbose => 1, @_, ); $self->{Verbose} = delete $args{Verbose}; $self->{$_} = delete $args{$_} for qw/ AllUsers AllGroups FollowDeleted FollowDisabled FollowScrips FollowTickets FollowTransactions FollowAssets FollowACL Queues CustomFields HyperlinkUnmigrated Clone Incremental /; $self->{Clone} = 1 if $self->{Incremental}; $self->SUPER::Init(@_, First => "top"); # Keep track of the number of each type of object written out $self->{ObjectCount} = {}; if ($self->{Clone}) { $self->PushAll; } else { $self->PushBasics; } } sub Metadata { my $self = shift; # Determine the highest upgrade step that we run my @versions = ($RT::VERSION, keys %RT::Migrate::Incremental::UPGRADES); my ($max) = reverse sort cmp_version @versions; # we don't want to run upgrades to 4.2.x if we're running # the serializier on an 4.0 instance. $max = $RT::VERSION unless $self->{Incremental}; return { Format => "0.8", VersionFrom => $RT::VERSION, Version => $max, Organization => $RT::Organization, Clone => $self->{Clone}, Incremental => $self->{Incremental}, ObjectCount => { $self->ObjectCount }, @_, }, } sub PushAll { my $self = shift; # To keep unique constraints happy, we need to remove old records # before we insert new ones. This fixes the case where a # GroupMember was deleted and re-added (with a new id, but the same # membership). if ($self->{Incremental}) { my $removed = RT::Migrate::Serializer::IncrementalRecords->new( RT->SystemUser ); $removed->Limit( FIELD => "UpdateType", VALUE => 3 ); $removed->OrderBy( FIELD => 'id' ); $self->PushObj( $removed ); } # XXX: This is sadly not sufficient to deal with the general case of # non-id unique constraints, such as queue names. If queues A and B # existed, and B->C and A->B renames were done, these will be # serialized with A->B first, which will fail because there already # exists a B. # Principals first; while we don't serialize these separately during # normal dependency walking (we fold them into users and groups), # having them separate during cloning makes logic simpler. $self->PushCollections(qw(Principals)); # Users and groups $self->PushCollections(qw(Users Groups GroupMembers)); # Tickets $self->PushCollections(qw(Queues Tickets Transactions Attachments Links)); # Articles $self->PushCollections(qw(Articles), map { ($_, "Object$_") } qw(Classes Topics)); # Custom Roles $self->PushCollections(qw(CustomRoles ObjectCustomRoles)); # Assets $self->PushCollections(qw(Catalogs Assets)); # Custom Fields if (RT::ObjectCustomFields->require) { $self->PushCollections(map { ($_, "Object$_") } qw(CustomFields CustomFieldValues)); } elsif (RT::TicketCustomFieldValues->require) { $self->PushCollections(qw(CustomFields CustomFieldValues TicketCustomFieldValues)); } # ACLs $self->PushCollections(qw(ACL)); # Scrips $self->PushCollections(qw(Scrips ObjectScrips ScripActions ScripConditions Templates)); # Attributes $self->PushCollections(qw(Attributes)); } sub PushCollections { my $self = shift; for my $type (@_) { my $class = "RT::\u$type"; $class->require or next; my $collection = $class->new( RT->SystemUser ); $collection->FindAllRows if $self->{FollowDisabled}; $collection->CleanSlate; # some collections (like groups and users) join in _Init $collection->UnLimit; $collection->OrderBy( FIELD => 'id' ); if ($self->{Clone}) { if ($collection->isa('RT::Tickets')) { $collection->{allow_deleted_search} = 1; $collection->IgnoreType; # looking_at_type } elsif ($collection->isa('RT::Assets')) { $collection->{allow_deleted_search} = 1; } elsif ($collection->isa('RT::ObjectCustomFieldValues')) { # FindAllRows (find_disabled_rows) isn't used by OCFVs $collection->{find_disabled_rows} = 1; } if ($self->{Incremental}) { my $alias = $collection->Join( ALIAS1 => "main", FIELD1 => "id", TABLE2 => "IncrementalRecords", FIELD2 => "ObjectId", ); $collection->DBIx::SearchBuilder::Limit( ALIAS => $alias, FIELD => "ObjectType", VALUE => ref($collection->NewItem), ); } } $self->PushObj( $collection ); } } sub PushBasics { my $self = shift; # System users for my $name (qw/RT_System root nobody/) { my $user = RT::User->new( RT->SystemUser ); my ($id, $msg) = $user->Load( $name ); warn "No '$name' user found: $msg" unless $id; $self->PushObj( $user ) if $id; } # System groups foreach my $name (qw(Everyone Privileged Unprivileged)) { my $group = RT::Group->new( RT->SystemUser ); my ($id, $msg) = $group->LoadSystemInternalGroup( $name ); warn "No '$name' group found: $msg" unless $id; $self->PushObj( $group ) if $id; } # System role groups my $systemroles = RT::Groups->new( RT->SystemUser ); $systemroles->LimitToRolesForObject( RT->System ); $self->PushObj( $systemroles ); # CFs on Users, Groups, Queues my $cfs = RT::CustomFields->new( RT->SystemUser ); $cfs->Limit( FIELD => 'LookupType', OPERATOR => 'IN', VALUE => [ qw/RT::User RT::Group RT::Queue/ ], ); if ($self->{CustomFields}) { $cfs->Limit(FIELD => 'id', OPERATOR => 'IN', VALUE => $self->{CustomFields}); } $self->PushObj( $cfs ); # Global attributes my $attributes = RT::Attributes->new( RT->SystemUser ); $attributes->LimitToObject( $RT::System ); $self->PushObj( $attributes ); # Global ACLs if ($self->{FollowACL}) { my $acls = RT::ACL->new( RT->SystemUser ); $acls->LimitToObject( $RT::System ); $self->PushObj( $acls ); } # Global scrips if ($self->{FollowScrips}) { my $scrips = RT::Scrips->new( RT->SystemUser ); $scrips->LimitToGlobal; my $templates = RT::Templates->new( RT->SystemUser ); $templates->LimitToGlobal; $self->PushObj( $scrips, $templates ); $self->PushCollections(qw(ScripActions ScripConditions)); } if ($self->{AllUsers}) { my $users = RT::Users->new( RT->SystemUser ); $users->LimitToPrivileged; $self->PushObj( $users ); } if ($self->{AllGroups}) { my $groups = RT::Groups->new( RT->SystemUser ); $groups->LimitToUserDefinedGroups; $self->PushObj( $groups ); } if (RT::Articles->require) { $self->PushCollections(qw(Topics Classes)); } if ($self->{Queues}) { my $queues = RT::Queues->new(RT->SystemUser); $queues->Limit(FIELD => 'id', OPERATOR => 'IN', VALUE => $self->{Queues}); $self->PushObj($queues); } else { $self->PushCollections(qw(Queues)); } $self->PushCollections(qw(Catalogs)); } sub InitStream { my $self = shift; my $meta = $self->Metadata; $self->WriteMetadata($meta); return unless cmp_version($meta->{VersionFrom}, $meta->{Version}) < 0; my %transforms; for my $v (sort cmp_version keys %RT::Migrate::Incremental::UPGRADES) { for my $ref (keys %{$RT::Migrate::Incremental::UPGRADES{$v}}) { push @{$transforms{$ref}}, $RT::Migrate::Incremental::UPGRADES{$v}{$ref}; } } for my $ref (keys %transforms) { # XXX Does not correctly deal with updates of $classref, which # should technically apply all later transforms of the _new_ # class. This is not relevant in the current upgrades, as # RT::ObjectCustomFieldValues do not have interesting later # upgrades if you start from 3.2 (which does # RT::TicketCustomFieldValues -> RT::ObjectCustomFieldValues) $self->{Transform}{$ref} = sub { my ($dat, $classref) = @_; my @extra; for my $c (@{$transforms{$ref}}) { push @extra, $c->($dat, $classref); return @extra if not $$classref; } return @extra; }; } } sub NextPage { my $self = shift; my ($collection, $last) = @_; $last ||= 0; if ($self->{Clone}) { # Clone provides guaranteed ordering by id and with no other id limits # worry about trampling # Use DBIx::SearchBuilder::Limit explicitly to avoid shenanigans in RT::Tickets $collection->DBIx::SearchBuilder::Limit( FIELD => 'id', OPERATOR => '>', VALUE => $last, ENTRYAGGREGATOR => 'none', # replaces last limit on this field ); } else { # XXX TODO: this could dig around inside the collection to see how it's # limited and do the faster paging above under other conditions. $self->SUPER::NextPage(@_); } } sub Process { my $self = shift; my %args = ( object => undef, @_ ); my $obj = $args{object}; my $uid = $obj->UID; # Skip all dependency walking if we're cloning; go straight to # visiting them. if ($self->{Clone} and $uid) { return if $obj->isa("RT::System"); $self->{progress}->($obj) if $self->{progress}; return $self->Visit(%args); } if (!$self->{FollowDisabled}) { return if ($obj->can('Disabled') || $obj->_Accessible('Disabled', 'read')) && $obj->Disabled # Disabled for OCFV means "old value" which we want to keep # in the history && !$obj->isa('RT::ObjectCustomFieldValue'); if ($obj->isa('RT::ACE')) { my $principal = $obj->PrincipalObj; return if $principal->Disabled; # [issues.bestpractical.com #32662] return if $principal->Object->Domain eq 'ACLEquivalence' && (!$principal->Object->InstanceObj->Id || $principal->Object->InstanceObj->Disabled); return if !$obj->Object->isa('RT::System') && $obj->Object->Disabled; } } return $self->SUPER::Process( @_ ); } sub StackSize { my $self = shift; return scalar @{$self->{stack}}; } sub ObjectCount { my $self = shift; return %{ $self->{ObjectCount} }; } sub Observe { my $self = shift; my %args = ( object => undef, direction => undef, from => undef, @_ ); my $obj = $args{object}; my $from = $args{from}; if ($obj->isa("RT::Ticket")) { return 0 if $obj->Status eq "deleted" and not $self->{FollowDeleted}; my $queue = $obj->Queue; return 0 if $self->{Queues} && none { $queue == $_ } @{ $self->{Queues} }; return $self->{FollowTickets}; } elsif ($obj->isa("RT::Queue")) { my $id = $obj->Id; return 0 if $self->{Queues} && none { $id == $_ } @{ $self->{Queues} }; return 1; } elsif ($obj->isa("RT::CustomField")) { my $id = $obj->Id; return 0 if $self->{CustomFields} && none { $id == $_ } @{ $self->{CustomFields} }; return 1; } elsif ($obj->isa("RT::ObjectCustomFieldValue")) { my $id = $obj->CustomField; return 0 if $self->{CustomFields} && none { $id == $_ } @{ $self->{CustomFields} }; return 1; } elsif ($obj->isa("RT::ObjectCustomField")) { my $id = $obj->CustomField; return 0 if $self->{CustomFields} && none { $id == $_ } @{ $self->{CustomFields} }; return 1; } elsif ($obj->isa("RT::Asset")) { return 0 if $obj->Status eq "deleted" and not $self->{FollowDeleted}; return $self->{FollowAssets}; } elsif ($obj->isa("RT::ACE")) { if (!$self->{FollowDisabled}) { my $principal = $obj->PrincipalObj; return 0 if $principal->Disabled; # [issues.bestpractical.com #32662] return if $principal->Object->Domain eq 'ACLEquivalence' && (!$principal->Object->InstanceObj->Id || $principal->Object->InstanceObj->Disabled); return 0 if !$obj->Object->isa('RT::System') && $obj->Object->Disabled; } return $self->{FollowACL}; } elsif ($obj->isa("RT::Transaction")) { return $self->{FollowTransactions}; } elsif ($obj->isa("RT::Scrip") or $obj->isa("RT::Template") or $obj->isa("RT::ObjectScrip")) { return $self->{FollowScrips}; } elsif ($obj->isa("RT::GroupMember")) { my $grp = $obj->GroupObj->Object; if ($grp->Domain =~ /^RT::(Queue|Ticket)-Role$/) { return 0 unless $grp->UID eq $from; } elsif ($grp->Domain eq "SystemInternal") { return 0 if $grp->UID eq $from; } if (!$self->{FollowDisabled}) { return 0 if $grp->Disabled || $obj->MemberObj->Disabled; } } return 1; } sub Visit { my $self = shift; my %args = ( object => undef, @_ ); # Serialize it my $obj = $args{object}; warn "Writing ".$obj->UID."\n" if $self->{Verbose}; my @store; if ($obj->isa("RT::Migrate::Serializer::IncrementalRecord")) { # These are stand-ins for record removals my $class = $obj->ObjectType; my %data = ( id => $obj->ObjectId ); # -class is used for transforms when dropping a record if ($self->{Transform}{"-$class"}) { $self->{Transform}{"-$class"}->(\%data,\$class) } @store = ( $class, undef, \%data, ); } elsif ($self->{Clone}) { # Short-circuit and get Just The Basics, Sir if we're cloning my $class = ref($obj); my $uid = $obj->UID; my %data = $obj->RT::Record::Serialize( UIDs => 0 ); # +class is used when seeing a record of one class might insert # a separate record into the stream if ($self->{Transform}{"+$class"}) { my @extra = $self->{Transform}{"+$class"}->(\%data,\$class); for my $e (@extra) { $self->WriteRecord($e); $self->{ObjectCount}{$e->[0]}++; } } # Upgrade the record if necessary if ($self->{Transform}{$class}) { $self->{Transform}{$class}->(\%data,\$class); } # Transforms set $class to undef to drop the record return unless $class; @store = ( $class, $uid, \%data, ); } else { my %serialized = $obj->Serialize(serializer => $self); return unless %serialized; @store = ( ref($obj), $obj->UID, \%serialized, ); } $self->WriteRecord(\@store); $self->{ObjectCount}{$store[0]}++; } 1; ������rt-5.0.1/lib/RT/Migrate/Incremental.pm��������������������������������������������������������������000644 �000765 �000024 �00000054036 14005011336 020273� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Migrate::Incremental; use strict; use warnings; require Storable; require MIME::Base64; our %UPGRADES = ( '3.3.0' => { 'RT::Transaction' => sub { my ($ref) = @_; $ref->{ObjectType} = 'RT::Ticket'; $ref->{ObjectId} = delete $ref->{Ticket}; delete $ref->{EffectiveTicket}; }, 'RT::TicketCustomFieldValue' => sub { my ($ref, $classref) = @_; $$classref = "RT::ObjectCustomFieldValue"; $ref->{ObjectType} = 'RT::Ticket'; $ref->{ObjectId} = delete $ref->{Ticket}; }, '-RT::TicketCustomFieldValue' => sub { my ($ref, $classref) = @_; $$classref = "RT::ObjectCustomFieldValue"; }, 'RT::CustomField' => sub { my ($ref) = @_; $ref->{MaxValues} = 0 if $ref->{Type} =~ /Multiple$/; $ref->{MaxValues} = 1 if $ref->{Type} =~ /Single$/; $ref->{Type} = 'Select' if $ref->{Type} =~ /^Select/; $ref->{Type} = 'Freeform' if $ref->{Type} =~ /^Freeform/; $ref->{LookupType} = 'RT::Queue-RT::Ticket'; delete $ref->{Queue}; }, '+RT::CustomField' => sub { my ($ref) = @_; return [ "RT::ObjectCustomField" => rand(1), { id => undef, CustomField => $ref->{id}, ObjectId => $ref->{Queue}, SortOrder => $ref->{SortOrder}, Creator => $ref->{Creator}, LastUpdatedBy => $ref->{LastUpdatedBy}, } ]; } }, '3.3.11' => { 'RT::ObjectCustomFieldValue' => sub { my ($ref) = @_; $ref->{Disabled} = not delete $ref->{Current}; }, }, '3.7.19' => { 'RT::Scrip' => sub { my ($ref) = @_; return if defined $ref->{Description} and length $ref->{Description}; my $scrip = RT::Scrip->new( $RT::SystemUser ); $scrip->Load( $ref->{id} ); my $condition = $scrip->ConditionObj->Name || $scrip->ConditionObj->Description || ('On Condition #'. $scrip->Condition); my $action = $scrip->ActionObj->Name || $scrip->ActionObj->Description || ('Run Action #'. $scrip->Action); $ref->{Description} = join ' ', $condition, $action; }, }, # XXX BrandedQueues # XXX iCal '3.8.2' => { 'RT::Template' => sub { my ($ref) = @_; return unless $ref->{Queue}; my $queue = RT::Queue->new( $RT::SystemUser ); $queue->Load( $ref->{Queue} ); return unless $queue->Id and $queue->Name eq "___Approvals"; $ref->{Name} = "[OLD] ".$ref->{Name}; }, 'RT::Attribute' => sub { my ($ref) = @_; return unless $ref->{Name} eq "Dashboard"; my $v = eval { Storable::thaw(MIME::Base64::decode_base64($ref->{Content})) }; return unless $v and exists $v->{Searches}; $v->{Panes} = { body => [ map { my ($privacy, $id, $desc) = @$_; +{ portlet_type => 'search', privacy => $privacy, id => $id, description => $desc, pane => 'body', } } @{ delete $v->{Searches} } ], }; $ref->{Content} = MIME::Base64::encode_base64( Storable::nfreeze($v) ); }, 'RT::Scrip' => sub { my ($ref, $classref) = @_; return unless $ref->{Queue}; my $queue = RT::Queue->new( $RT::SystemUser ); $queue->Load( $ref->{Queue} ); return unless $queue->Id and $queue->Name eq "___Approvals"; $$classref = undef; }, }, '3.8.3' => { 'RT::ScripAction' => sub { my ($ref) = @_; return unless ($ref->{Argument}||"") eq "All"; if ($ref->{ExecModule} eq "Notify") { $ref->{Name} = 'Notify Owner, Requestors, Ccs and AdminCcs'; $ref->{Description} = 'Send mail to owner and all watchers'; } elsif ($ref->{ExecModule} eq "NotifyAsComment") { $ref->{Name} = 'Notify Owner, Requestors, Ccs and AdminCcs as Comment'; $ref->{Description} = 'Send mail to owner and all watchers as a "comment"'; } }, }, '3.8.4' => { 'RT::ScripAction' => sub { my ($ref) = @_; return unless $ref->{ExecModule} eq "NotifyGroup" or $ref->{ExecModule} eq "NotifyGroupAsComment"; my $argument = $ref->{Argument}; if ( my $struct = eval { Storable::thaw( $argument ) } ) { my @res; foreach my $r ( @{ $struct } ) { my $obj; next unless $r->{'Type'}; if( lc $r->{'Type'} eq 'user' ) { $obj = RT::User->new( $RT::SystemUser ); } elsif ( lc $r->{'Type'} eq 'group' ) { $obj = RT::Group->new( $RT::SystemUser ); } else { next; } $obj->Load( $r->{'Instance'} ); next unless $obj->id ; push @res, $obj->id; } $ref->{Argument} = join ",", @res; } else { $ref->{Argument} = join ",", grep length, split /[^0-9]+/, $argument; } }, }, '3.8.8' => { 'RT::ObjectCustomField' => sub { # XXX Removing OCFs applied both global and non-global # XXX Fixing SortOrder on OCFs }, }, '3.8.9' => { 'RT::Link' => sub { my ($ref) = @_; my $prefix = RT::URI::fsck_com_rt->LocalURIPrefix . '/ticket/'; for my $dir (qw(Target Base)) { next unless $ref->{$dir} =~ /^$prefix(.*)/; next unless int($1) eq $1; next if $ref->{'Local'.$dir}; $ref->{'Local'.$dir} = $1; } }, 'RT::Template' => sub { my ($ref) = @_; return unless $ref->{Name} =~ /^(All Approvals Passed|Approval Passed|Approval Rejected)$/; my $queue = RT::Queue->new( $RT::SystemUser ); $queue->Load( $ref->{Queue} ); return unless $queue->Id and $queue->Name eq "___Approvals"; $ref->{Content} =~ s!(?<=Your ticket has been (?:approved|rejected) by \{ eval \{ )\$Approval->OwnerObj->Name!\$Approver->Name!; }, }, '3.9.1' => { 'RT::Template' => sub { my ($ref) = @_; $ref->{Type} = 'Perl'; }, # XXX: Add ExecuteCode to principals that currently have ModifyTemplate or ModifyScrips }, '3.9.2' => { 'RT::ACE' => sub { my ($ref, $classref) = @_; $$classref = undef if $ref->{DelegatedBy} > 0 or $ref->{DelegatedFrom} > 0; }, 'RT::GroupMember' => sub { my ($ref, $classref) = @_; my $group = RT::Group->new( $RT::SystemUser ); $group->Load( $ref->{GroupId} ); $$classref = undef if $group->Domain eq "Personal"; }, 'RT::Group' => sub { my ($ref, $classref) = @_; $$classref = undef if $ref->{Domain} eq "Personal"; }, 'RT::Principal' => sub { my ($ref, $classref) = @_; return unless $ref->{PrincipalType} eq "Group"; my $group = RT::Group->new( $RT::SystemUser ); $group->Load( $ref->{ObjectId} ); $$classref = undef if $group->Domain eq "Personal"; }, }, '3.9.3' => { 'RT::ACE' => sub { my ($ref) = @_; delete $ref->{DelegatedBy}; delete $ref->{DelegatedFrom}; }, }, '3.9.5' => { 'RT::CustomFieldValue' => sub { my ($ref) = @_; my $attr = RT::Attribute->new( $RT::SystemUser ); $attr->LoadByCols( ObjectType => "RT::CustomFieldValue", ObjectId => $ref->{Id}, Name => "Category", ); $ref->{Category} = $attr->Content if $attr->id; }, 'RT::Attribute' => sub { my ($ref, $classref) = @_; $$classref = undef if $ref->{Name} eq "Category" and $ref->{ObjectType} eq "RT::CustomFieldValue"; }, }, '3.9.7' => { 'RT::User' => sub { my ($ref) = @_; my $attr = RT::Attribute->new( $RT::SystemUser ); $attr->LoadByCols( ObjectType => "RT::User", ObjectId => $ref->{id}, Name => "AuthToken", ); $ref->{AuthToken} = $attr->Content if $attr->id; }, 'RT::CustomField' => sub { my ($ref) = @_; for my $name (qw/RenderType BasedOn ValuesClass/) { my $attr = RT::Attribute->new( $RT::SystemUser ); $attr->LoadByCols( ObjectType => "RT::CustomField", ObjectId => $ref->{id}, Name => $name, ); $ref->{$name} = $attr->Content if $attr->id; } }, 'RT::Queue' => sub { my ($ref) = @_; my $attr = RT::Attribute->new( ObjectType => "RT::System", ObjectId => 1, Name => "BrandedSubjectTag", );; return unless $attr->id; my $map = $attr->Content || {}; return unless $map->{$ref->{id}}; $ref->{SubjectTag} = $map->{$ref->{id}}; }, 'RT::Attribute' => sub { my ($ref, $classref) = @_; if ($ref->{ObjectType} eq "RT::User" and $ref->{Name} eq "AuthToken") { $$classref = undef; } elsif ($ref->{ObjectType} eq "RT::CustomField" and $ref->{Name} eq "RenderType") { $$classref = undef; } elsif ($ref->{ObjectType} eq "RT::CustomField" and $ref->{Name} eq "BasedOn") { $$classref = undef; } elsif ($ref->{ObjectType} eq "RT::CustomField" and $ref->{Name} eq "ValuesClass") { $$classref = undef; } elsif ($ref->{ObjectType} eq "RT::System" and $ref->{Name} eq "BrandedSubjectTag") { $$classref = undef; } }, }, '3.9.8' => { # XXX RTFM => Articles }, '4.0.0rc7' => { 'RT::Queue' => sub { my ($ref) = @_; return unless $ref->{Name} eq '___Approvals'; $ref->{Lifecycle} = "approvals"; }, }, '4.0.1' => { 'RT::ACE' => sub { my ($ref, $classref) = @_; my $group = RT::Group->new( $RT::SystemUser ); $group->LoadByCols( id => $ref->{PrincipalId}, Domain => "Personal", ); $$classref = undef if $group->id; $$classref = undef if $ref->{RightName} =~ /^(AdminOwnPersonalGroups|AdminAllPersonalGroups|DelegateRights)$/; $$classref = undef if $ref->{RightName} =~ /^(RejectTicket|ModifyTicketStatus)$/; }, }, '4.0.4' => { 'RT::Template' => sub { my ($ref) = @_; $ref->{Type} ||= 'Perl'; }, }, '4.0.6' => { 'RT::Transaction' => sub { my ($ref) = @_; return unless $ref->{ObjectType} eq "RT::User" and $ref->{Field} eq "Password"; $ref->{OldValue} = $ref->{NewValue} = '********'; }, }, '4.0.9' => { 'RT::Queue' => sub { my ($ref) = @_; $ref->{Lifecycle} ||= 'default'; }, }, '4.0.19' => { 'RT::CustomField' => sub { my ($ref) = @_; $ref->{LookupType} = 'RT::Class-RT::Article' if $ref->{LookupType} eq 'RT::FM::Class-RT::FM::Article'; }, 'RT::ObjectCustomFieldValue' => sub { my ($ref) = @_; $ref->{ObjectType} = 'RT::Article' if $ref->{ObjectType} eq 'RT::FM::Article'; }, }, '4.1.0' => { 'RT::Attribute' => sub { my ($ref) = @_; return unless $ref->{Name} eq "HomepageSettings"; my $v = eval { Storable::thaw(MIME::Base64::decode_base64($ref->{Content})) }; return if not $v or $v->{sidebar}; $v->{sidebar} = delete $v->{summary}; $ref->{Content} = MIME::Base64::encode_base64( Storable::nfreeze($v) ); }, }, '4.1.1' => { '+RT::Scrip' => sub { my ($ref) = @_; my $new = [ "RT::ObjectScrip" => rand(1), { id => undef, Scrip => $ref->{id}, Stage => delete $ref->{Stage}, ObjectId => delete $ref->{Queue}, Creator => $ref->{Creator}, Created => $ref->{Created}, LastUpdatedBy => $ref->{LastUpdatedBy}, LastUpdated => $ref->{LastUpdated}, } ]; if ( $new->[2]{Stage} eq "Disabled" ) { $ref->{Disabled} = 1; $new->[2]{Stage} = "TransactionCreate"; } else { $ref->{Disabled} = 0; } # XXX SortOrder return $new; }, }, '4.1.4' => { 'RT::Group' => sub { my ($ref) = @_; $ref->{Instance} = 1 if $ref->{Domain} eq "RT::System-Role" and $ref->{Instance} = 0; }, # XXX Invalid rights }, '4.1.5' => { 'RT::Scrip' => sub { my ($ref) = @_; my $template = RT::Template->new( $RT::SystemUser ); $template->Load( $ref->{Template} ); $ref->{Template} = $template->id ? $template->Name : 'Blank'; }, }, '4.1.6' => { 'RT::Attribute' => sub { my ($ref) = @_; return unless $ref->{Name} eq RT::User::_PrefName( RT->System ) and $ref->{ObjectType} eq "RT::User"; my $v = eval { Storable::thaw(MIME::Base64::decode_base64($ref->{Content})) }; return if not $v or $v->{ShowHistory}; $v->{ShowHistory} = delete $v->{DeferTransactionLoading} ? "click" : "delay"; $ref->{Content} = MIME::Base64::encode_base64( Storable::nfreeze($v) ); }, }, '4.1.7' => { 'RT::Transaction' => sub { my ($ref) = @_; return unless $ref->{ObjectType} eq 'RT::Ticket' and $ref->{Type} eq 'Set' and $ref->{Field} eq 'TimeWorked'; $ref->{TimeTaken} = $ref->{NewValue} - $ref->{OldValue}; }, }, '4.1.8' => { 'RT::Ticket' => sub { my ($ref) = @_; $ref->{IsMerged} = 1 if $ref->{id} != $ref->{EffectiveId}; }, }, '4.1.10' => { 'RT::ObjectcustomFieldValue' => sub { my ($ref) = @_; $ref->{Content} = undef if defined $ref->{LargeContent} and defined $ref->{Content} and $ref->{Content} eq ''; }, }, '4.1.11' => { 'RT::CustomField' => sub { my ($ref) = @_; delete $ref->{Repeated}; }, }, '4.1.13' => { 'RT::Group' => sub { my ($ref) = @_; $ref->{Name} = $ref->{Type} if $ref->{Domain} =~ /^(ACLEquivalence|SystemInternal|.*-Role)$/; }, }, '4.1.14' => { 'RT::Scrip' => sub { my ($ref) = @_; delete $ref->{ConditionRules}; delete $ref->{ActionRules}; }, }, '4.1.17' => { 'RT::Attribute' => sub { my ($ref) = @_; return unless $ref->{Name} eq 'SavedSearch'; my $v = eval { Storable::thaw(MIME::Base64::decode_base64($ref->{Content})) }; return unless $v and ref $v and ($v->{SearchType}||'') eq 'Chart'; # Switch from PrimaryGroupBy to GroupBy name # Switch from "CreatedMonthly" to "Created.Monthly" $v->{GroupBy} ||= [delete $v->{PrimaryGroupBy}]; for (@{$v->{GroupBy}}) { next if /\./; s/(?<=[a-z])(?=[A-Z])/./; } $ref->{Content} = MIME::Base64::encode_base64( Storable::nfreeze($v) ); }, }, '4.1.19' => { 'RT::Template' => sub { my ($ref) = @_; delete $ref->{Language}; delete $ref->{TranslationOf}; }, }, '4.1.20' => { 'RT::Template' => sub { my ($ref) = @_; if ($ref->{Name} eq 'Forward') { $ref->{Description} = 'Forwarded message'; if ( $ref->{Content} =~ m/^\n*This is (a )?forward of transaction #\{\s*\$Transaction->id\s*\} of (a )?ticket #\{\s*\$Ticket->id\s*\}\n*$/ ) { $ref->{Content} = q{ { $ForwardTransaction->Content =~ /\S/ ? $ForwardTransaction->Content : "This is a forward of transaction #".$Transaction->id." of ticket #". $Ticket->id } }; } else { RT->Logger->error('Current "Forward" template is not the default version, please check docs/UPGRADING-4.2'); } } elsif ($ref->{Name} eq 'Forward Ticket') { $ref->{Description} = 'Forwarded ticket message'; if ( $ref->{Content} eq q{ This is a forward of ticket #{ $Ticket->id } } ) { $ref->{Content} = q{ { $ForwardTransaction->Content =~ /\S/ ? $ForwardTransaction->Content : "This is a forward of ticket #". $Ticket->id } }; } else { RT->Logger->error('Current "Forward Ticket" template is not the default version, please check docs/UPGRADING-4.2'); } } }, }, '4.1.21' => { # XXX User dashboards }, '4.1.22' => { 'RT::Template' => sub { my ($ref) = @_; return unless $ref->{Name} eq 'Error: bad GnuPG data'; $ref->{Name} = 'Error: bad encrypted data'; $ref->{Description} = 'Inform user that a message he sent has invalid encryption data'; $ref->{Content} =~ s/GnuPG signature/signature/g; }, # XXX SMIME keys 'RT::Attribute' => sub { my ($ref, $classref) = @_; if ($ref->{ObjectType} eq "RT::User" and $ref->{Name} eq "SMIMEKeyNotAfter") { $$classref = undef; } }, }, '4.2.1' => { 'RT::Attribute' => sub { my ($ref, $classref) = @_; if ($ref->{ObjectType} eq "RT::System" and $ref->{Name} eq "BrandedSubjectTag") { $$classref = undef; } }, }, '4.2.2' => { 'RT::CustomField' => sub { my ($ref) = @_; $ref->{LookupType} = 'RT::Class-RT::Article' if $ref->{LookupType} eq 'RT::FM::Class-RT::FM::Article'; }, 'RT::ObjectCustomFieldValue' => sub { my ($ref) = @_; $ref->{ObjectType} = 'RT::Article' if $ref->{ObjectType} eq 'RT::FM::Article'; }, }, '4.3.1' => { 'RT::CustomField' => sub { my ($ref) = @_; $ref->{EntryHint} //= RT::CustomField->FriendlyType( $ref->{Type}, $ref->{MaxValues} ); }, }, ); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Migrate/Importer/File.pm������������������������������������������������������������000644 �000765 �000024 �00000014732 14005011336 020511� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Migrate::Importer::File; use strict; use warnings; use base qw(RT::Migrate::Importer); sub Init { my $self = shift; my %args = ( Directory => undef, Resume => undef, @_ ); # Directory is required die "Directory is required" unless $args{Directory}; die "Invalid path $args{Directory}" unless -d $args{Directory}; $self->{Directory} = $args{Directory}; # Load metadata, if present if (-e "$args{Directory}/rt-serialized") { my $dat = eval { Storable::retrieve("$args{Directory}/rt-serialized"); } or die "Failed to load metadata" . ($@ ? ": $@" : ""); $self->LoadMetadata($dat); } # Support resuming $self->{Statefile} = $args{Statefile} || "$args{Directory}/partial-import"; unlink $self->{Statefile} if -f $self->{Statefile} and not $args{Resume}; return $self->SUPER::Init(@_); } sub Import { my $self = shift; my $dir = $self->{Directory}; if ($self->{Metadata} and $self->{Metadata}{Files}) { $self->{Files} = [ map {s|^.*/|$dir/|;$_} @{$self->{Metadata}{Files}} ]; } else { $self->{Files} = [ <$dir/*.dat> ]; } $self->{Files} = [ map {File::Spec->rel2abs($_)} @{ $self->{Files} } ]; $self->RestoreState( $self->{Statefile} ); local $SIG{ INT } = sub { $self->{INT} = 1 }; local $SIG{__DIE__} = sub { warn "\n", @_; $self->SaveState; exit 1 }; $self->{Progress}->(undef) if $self->{Progress}; while (@{$self->{Files}}) { $self->{Filename} = shift @{$self->{Files}}; open(my $fh, "<", $self->{Filename}) or die "Can't read $self->{Filename}: $!"; if ($self->{Seek}) { seek($fh, $self->{Seek}, 0) or die "Can't seek to $self->{Seek} in $self->{Filename}"; $self->{Seek} = undef; } while (not eof($fh)) { $self->{Position} = tell($fh); # Stop when we're at a good stopping point die "Caught interrupt, quitting.\n" if $self->{INT}; $self->ReadStream( $fh ); } } $self->CloseStream; # Return creation counts return $self->ObjectCount; } sub List { my $self = shift; my $dir = $self->{Directory}; my %found = ( "RT::System" => 1 ); my @files = ($self->{Metadata} and $self->{Metadata}{Files}) ? @{ $self->{Metadata}{Files} } : <$dir/*.dat>; @files = map {File::Spec->rel2abs($_)} @files; for my $filename (@files) { open(my $fh, "<", $filename) or die "Can't read $filename: $!"; while (not eof($fh)) { my $loaded = Storable::fd_retrieve($fh); if (ref $loaded eq "HASH") { $self->LoadMetadata( $loaded ); next; } if ($self->{DumpObjects}) { print STDERR Data::Dumper::Dumper($loaded), "\n" if $self->{DumpObjects}{ $loaded->[0] }; } my ($class, $uid, $data) = @{$loaded}; $self->{ObjectCount}{$class}++; $found{$uid} = 1; delete $self->{Pending}{$uid}; for (grep {ref $data->{$_}} keys %{$data}) { my $uid_ref = ${ $data->{$_} }; unless (defined $uid_ref) { push @{ $self->{Invalid} }, { uid => $uid, column => $_ }; next; } next if $found{$uid_ref}; next if $uid_ref =~ /^RT::Principal-/; push @{$self->{Pending}{$uid_ref} ||= []}, {uid => $uid}; } } } return $self->ObjectCount; } sub RestoreState { my $self = shift; my ($statefile) = @_; return unless $statefile && -f $statefile; my $state = Storable::retrieve( $self->{Statefile} ); $self->{$_} = $state->{$_} for keys %{$state}; unlink $self->{Statefile}; print STDERR "Resuming partial import...\n"; sleep 2; return 1; } sub SaveState { my $self = shift; my %data; unshift @{$self->{Files}}, $self->{Filename}; $self->{Seek} = $self->{Position}; $data{$_} = $self->{$_} for qw/Filename Seek Position Files Organization ObjectCount NewQueues NewCFs SkipTransactions Pending Invalid UIDs OriginalId ExcludeOrganization Clone /; Storable::nstore(\%data, $self->{Statefile}); print STDERR <<EOT; Importer state has been written to the file: $self->{Statefile} It may be possible to resume the import by re-running rt-importer. EOT } 1; ��������������������������������������rt-5.0.1/lib/RT/Migrate/Serializer/File.pm����������������������������������������������������������000644 �000765 �000024 �00000011607 14005011336 021017� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Migrate::Serializer::File; use strict; use warnings; use Storable qw//; use base 'RT::Migrate::Serializer'; sub Init { my $self = shift; my %args = ( Directory => undef, Force => undef, MaxFileSize => 32, @_, ); # Set up the output directory we'll be writing to my ($y,$m,$d) = (localtime)[5,4,3]; $args{Directory} = $RT::Organization . sprintf(":%d-%02d-%02d",$y+1900,$m+1,$d) unless defined $args{Directory}; system("rm", "-rf", $args{Directory}) if $args{Force}; die "Output directory $args{Directory} already exists" if -d $args{Directory}; mkdir $args{Directory} or die "Can't create output directory $args{Directory}: $!\n"; $self->{Directory} = delete $args{Directory}; # How many megabytes each chunk should be, approximitely $self->{MaxFileSize} = delete $args{MaxFileSize}; # Which file we're writing to $self->{FileCount} = 1; $self->SUPER::Init(@_); } sub Metadata { my $self = shift; return $self->SUPER::Metadata( Files => [ $self->Files ], @_, ) } sub Export { my $self = shift; # Set up our output file $self->OpenFile; # Write the initial metadata $self->InitStream; # Walk the objects $self->Walk( @_ ); # Close everything back up $self->CloseFile; # Write the summary file Storable::nstore( $self->Metadata( Final => 1 ), $self->Directory . "/rt-serialized" ); return $self->ObjectCount; } sub Visit { my $self = shift; # Rotate if we get too big my $maxsize = 1024 * 1024 * $self->{MaxFileSize}; $self->RotateFile if tell($self->{Filehandle}) > $maxsize; # Serialize it $self->SUPER::Visit( @_ ); } sub Files { my $self = shift; return @{ $self->{Files} }; } sub Filename { my $self = shift; return sprintf( "%s/%03d.dat", $self->{Directory}, $self->{FileCount} ); } sub Directory { my $self = shift; return $self->{Directory}; } sub OpenFile { my $self = shift; open($self->{Filehandle}, ">", $self->Filename) or die "Can't write to file @{[$self->Filename]}: $!"; push @{$self->{Files}}, $self->Filename; } sub CloseFile { my $self = shift; close($self->{Filehandle}) or die "Can't close @{[$self->Filename]}: $!"; $self->{FileCount}++; } sub RotateFile { my $self = shift; $self->CloseFile; $self->OpenFile; } sub WriteMetadata { my $self = shift; my $meta = shift; $! = 0; Storable::nstore_fd( $meta, $self->{Filehandle} ); die "Failed to write metadata: $!" if $!; } sub WriteRecord { my $self = shift; my $record = shift; # Write it out; nstore_fd doesn't trap failures to write, so we have # to; by clearing $! and checking it afterwards. $! = 0; Storable::nstore_fd($record, $self->{Filehandle}); die "Failed to write: $!" if $!; } 1; �������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Migrate/Serializer/IncrementalRecords.pm��������������������������������������������000644 �000765 �000024 �00000004557 14005011336 023731� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Migrate::Serializer::IncrementalRecords; use base qw/RT::SearchBuilder/; use strict; use warnings; sub _Init { my $self = shift; $self->{'table'} = 'IncrementalRecords'; $self->{'primary_key'} = 'id'; return ( $self->SUPER::_Init(@_) ); } sub Table {'IncrementalRecords'} sub NewItem { my $self = shift; return(RT::Migrate::Serializer::IncrementalRecord->new($self->CurrentUser)); } 1; �������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Migrate/Serializer/IncrementalRecord.pm���������������������������������������������000644 �000765 �000024 �00000005141 14005011336 023534� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Migrate::Serializer::IncrementalRecord; use base qw/RT::Record/; use strict; use warnings; sub Table {'IncrementalRecords'} sub _CoreAccessible { return { id => { read => 1 }, ObjectType => { read => 1 }, ObjectId => { read => 1 }, UpdateType => { read => 1 }, AlteredAt => { read => 1 }, }; }; 1; __END__ CREATE TABLE IncrementalRecords ( id INTEGER NOT NULL AUTO_INCREMENT, ObjectType VARCHAR(50) NOT NULL, ObjectId INTEGER NOT NULL, UpdateType TINYINT NOT NULL, AlteredAt TIMESTAMP NOT NULL, PRIMARY KEY(ObjectType, ObjectId), UNIQUE KEY(id), KEY(UpdateType) ); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Migrate/Serializer/JSON.pm����������������������������������������������������������000644 �000765 �000024 �00000060221 14005011336 020705� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Migrate::Serializer::JSON; use strict; use warnings; use JSON qw//; use List::MoreUtils 'uniq'; use base 'RT::Migrate::Serializer'; sub Init { my $self = shift; my %args = ( Directory => undef, Force => undef, FollowTickets => 0, FollowTransactions => 0, FollowScrips => 1, FollowACL => 1, @_, ); # Set up the output directory we'll be writing to my ($y,$m,$d) = (localtime)[5,4,3]; $args{Directory} = $RT::Organization . sprintf(":%d-%02d-%02d",$y+1900,$m+1,$d) unless defined $args{Directory}; system("rm", "-rf", $args{Directory}) if $args{Force}; die "Output directory $args{Directory} already exists" if -d $args{Directory}; mkdir $args{Directory} or die "Can't create output directory $args{Directory}: $!\n"; $self->{Directory} = delete $args{Directory}; $self->{Records} = {}; $self->{Sync} = $args{Sync}; $self->SUPER::Init(%args); } sub Export { my $self = shift; # Write the initial metadata $self->InitStream; # Walk the objects $self->Walk( @_ ); # Set up our output file $self->OpenFile; # Write out the initialdata $self->WriteFile; # Close everything back up $self->CloseFile; return $self->ObjectCount; } sub Files { my $self = shift; return ($self->Filename); } sub Filename { my $self = shift; return sprintf( "%s/initialdata.json", $self->{Directory}, ); } sub Directory { my $self = shift; return $self->{Directory}; } sub Observe { my $self = shift; my %args = @_; my $obj = $args{object}; if ($obj->isa("RT::Group")) { return 0 if $obj->Domain eq 'ACLEquivalence'; } if ($obj->isa("RT::GroupMember")) { my $domain = $obj->GroupObj->Object->Domain; return 0 if $domain eq 'ACLEquivalence' || $domain eq 'SystemInternal'; } return $self->SUPER::Observe(%args); } sub PushBasics { my $self = shift; $self->SUPER::PushBasics(@_); # we want to include all CFs, scrips, etc, not just the reachable ones $self->PushCollections(qw(CustomFields CustomRoles)); $self->PushCollections(qw(Scrips)) if $self->{FollowScrips}; } sub JSON { my $self = shift; $self->{JSON} ||= JSON->new->pretty->canonical; $self->{JSON} = $self->{JSON}->utf8 if RT->Config->Get('DatabaseType') =~ /Pg|Oracle/; return $self->{JSON}; } sub OpenFile { my $self = shift; open($self->{Filehandle}, ">", $self->Filename) or die "Can't write to file @{[$self->Filename]}: $!"; } sub CloseFile { my $self = shift; close($self->{Filehandle}) or die "Can't close @{[$self->Filename]}: $!"; } sub WriteMetadata { my $self = shift; my $meta = shift; # no need to write metadata return; } sub WriteRecord { my $self = shift; my $record = shift; $self->{Records}{ $record->[0] }{ $record->[1] } = $record->[2]; } my %initialdataType = ( ACE => 'ACL', Class => 'Classes', GroupMember => 'Members', ); sub _GetObjectByRef { my $self = shift; my $ref = shift; $ref = $$ref if ref($ref) eq 'SCALAR'; return RT->System if $ref eq 'RT::System'; return RT->SystemUser if $ref eq 'RT::User-RT_System'; my ($class, $id) = $ref =~ /^([\w:]+)-.*-(\d+)$/ or do { warn "Unable to canonicalize ref '$ref'"; return undef }; my $obj = $class->new(RT->SystemUser); $obj->Load($id); return $obj; } sub _GetSerializedByRef { my $self = shift; my $ref = shift; $ref = $$ref if ref($ref) eq 'SCALAR'; my ($class) = $ref =~ /^([\w:]+)-/ or return undef; return $self->{Records}{$class}{$ref}; } sub CanonicalizeReference { my $self = shift; my $ref = ${ shift(@_) }; my $context = shift; my $for_key = shift; my $record = $self->_GetSerializedByRef($ref) or return $ref; return $record->{Name} || $ref; } sub _CanonicalizeManyToMany { my $self = shift; my %args = ( object_class => '', object_primary_ref => '', object_sorter => '', primary_class => '', primary_key => 'ApplyTo', add_to_primary => undef, sort_uniq => 0, delete_empty => 0, finalize => undef, canonicalize_object => sub { $_->{ObjectId} }, @_, ); my $object_class = $args{object_class}; my $object_primary_ref = $args{object_primary_ref}; my $object_sorter = $args{object_sorter}; my $primary_class = $args{primary_class}; my $primary_key = $args{primary_key}; my $add_to_primary = $args{add_to_primary}; my $sort_uniq = $args{sort_uniq}; my $delete_empty = $args{delete_empty}; my $finalize = $args{finalize}; my $canonicalize_object = $args{canonicalize_object}; my $primary_records = $self->{Records}{$primary_class}; if (my $objects = delete $self->{Records}{$object_class}) { for my $object (values %$objects) { my $primary = $primary_records->{ ${ $object->{$object_primary_ref} } }; push @{ $primary->{$primary_key} }, $object; } for my $primary (values %$primary_records) { @{ $primary->{$primary_key} } = grep defined, map &$canonicalize_object, sort { ($a->{SortOrder}||0) <=> ($b->{SortOrder}||0) || ($object_sorter ? $a->{$object_sorter} cmp $b->{$object_sorter} : 0) } @{ $primary->{$primary_key} || [] }; if ($sort_uniq) { @{ $primary->{$primary_key} } = uniq sort @{ $primary->{$primary_key} }; } if ($delete_empty) { delete $primary->{$primary_key} if !@{ $primary->{$primary_key} }; } if ($finalize) { $finalize->($primary); } if (ref($add_to_primary) eq 'CODE') { $add_to_primary->($primary); } } } } sub CanonicalizeACLs { my $self = shift; for my $ace (values %{ $self->{Records}{'RT::ACE'} }) { delete $ace->{PrincipalType}; my $principal = $self->_GetObjectByRef(delete $ace->{PrincipalId}); my $object = $self->_GetObjectByRef(delete $ace->{Object}); if ($principal->IsGroup) { my $group = $principal->Object; my $domain = $group->Domain; if ($domain eq 'ACLEquivalence') { $ace->{UserId} = $group->InstanceObj->Name; } else { $ace->{GroupDomain} = $domain; if ($domain eq 'UserDefined') { $ace->{GroupId} = $group->Name; } if ($domain eq 'SystemInternal' || $domain =~ /-Role$/) { $ace->{GroupType} = $group->Name; } } } else { $ace->{UserId} = $principal->Object->Name; } unless ($object->isa('RT::System')) { $ace->{ObjectType} = ref($object); $ace->{ObjectId} = \($object->UID); } } } sub CanonicalizeUsers { my $self = shift; for my $user (values %{ $self->{Records}{'RT::User'} }) { delete $user->{Principal}; delete $user->{PrincipalId}; delete $user->{Password}; delete $user->{AuthToken}; my $object = RT::User->new(RT->SystemUser); $object->Load($user->{id}); for my $key (keys %$user) { my $value = $user->{$key}; delete $user->{$key} if !defined($value) || !length($value); } $user->{Privileged} = $object->Privileged ? JSON::true : JSON::false; } } sub CanonicalizeGroups { my $self = shift; my $records = $self->{Records}{'RT::Group'}; for my $id (keys %$records) { my $group = $records->{$id}; # no need to serialize this because role groups are automatically # created; but we can't exclude this in ->Observe because then we # lose out on the group members if ($group->{Domain} =~ /-Role$/) { delete $records->{$id}; next; } delete $group->{Principal}; delete $group->{PrincipalId}; delete $group->{Instance} if $group->{Domain} eq 'UserDefined' || $group->{Domain} eq 'SystemInternal'; } } sub CanonicalizeGroupMembers { my $self = shift; my $records = $self->{Records}{'RT::GroupMember'}; for my $id (keys %$records) { my $record = $records->{$id}; my $group = $self->_GetObjectByRef(delete $record->{GroupId})->Object; my $member = $self->_GetObjectByRef(delete $record->{MemberId})->Object; my $domain = $group->Domain; # no need to explicitly include a Nobody member if ($member->isa('RT::User') && $member->Name eq 'Nobody' && $group->SingleMemberRoleGroup) { delete $records->{$id}; next; } $record->{Group} = $group->Name; $record->{GroupDomain} = $domain unless $domain eq 'UserDefined'; $record->{GroupInstance} = \($group->InstanceObj->UID) if $domain =~ /-Role$/; $record->{Class} = ref($member); $record->{Name} = $member->Name; } } sub CanonicalizeCustomFields { my $self = shift; for my $record (values %{ $self->{Records}{'RT::CustomField'} }) { delete $record->{Pattern} if ($record->{Pattern}||'') eq ""; delete $record->{UniqueValues} if !$record->{UniqueValues}; } } sub CanonicalizeObjectCustomFieldValues { my $self = shift; my $records = delete $self->{Records}{'RT::ObjectCustomFieldValue'}; for my $id ( sort { $records->{$a}{id} <=> $records->{$b}{id} } keys %$records ) { my $record = $records->{$id}; if ($record->{Disabled} && !$self->{FollowDisabled}) { delete $records->{$id}; next; } my $object = $self->_GetSerializedByRef(delete $record->{Object}); my $cf = $self->_GetSerializedByRef(delete $record->{CustomField}); next unless $cf && $cf->{Name}; # disabled CF on live object $record->{CustomField} = $cf->{Name}; if ( $cf->{Type} =~ /^(?:Binary|Image)$/ && $record->{LargeContent} ) { $record->{ContentEncoding} = 'base64'; $record->{LargeContent} = MIME::Base64::encode_base64($record->{LargeContent} ); } delete $record->{id} unless $self->{Sync}; push @{ $object->{CustomFields} }, $record; } } sub CanonicalizeObjectTopics { my $self = shift; my $records = delete $self->{Records}{'RT::ObjectTopic'}; for my $id ( sort { $records->{$a}{id} <=> $records->{$b}{id} } keys %$records ) { my $record = $records->{$id}; my $object = $self->_GetSerializedByRef(delete $record->{ObjectId}); my $topic = $self->_GetSerializedByRef(delete $record->{Topic}); $record->{Topic} = $topic->{Name}; delete $record->{id} unless $self->{Sync}; delete $record->{ObjectType}; # unnecessary as it's always RT::Article for now push @{ $object->{Topics} }, $record; } } sub CanonicalizeArticles { my $self = shift; for my $record (values %{ $self->{Records}{'RT::Article'} }) { delete $record->{URI}; } } sub CanonicalizeAttributes { my $self = shift; for my $record (values %{ $self->{Records}{'RT::Attribute'} }) { if ( ( $record->{ContentType} // '' ) eq 'storable' ) { eval { $record->{Content} = RT::Attribute->_DeserializeContent( $record->{Content} ) }; if ( $@ ) { $RT::Logger->error( "Deserialization of content for attribute $record->{id} failed. Attribute was: $record->{Content}" ); } else { if ( $record->{Name} =~ /HomepageSettings$/ ) { my $content = $record->{Content}; for my $type ( qw/body sidebar/ ) { if ( $content->{$type} && ref $content->{$type} eq 'ARRAY' ) { for my $item ( @{ $content->{$type} } ) { if ( $item->{type} eq 'saved' && $item->{name} =~ /RT::\w+-\d+-SavedSearch-(\d+)/ ) { my $id = $1; my $attribute = RT::Attribute->new( RT->SystemUser ); $attribute->Load( $id ); if ( $attribute->id ) { $item->{ObjectType} = $attribute->ObjectType; $item->{ObjectId} = $attribute->Object->Name; $item->{Description} = $attribute->Description; delete $item->{name}; } } delete $item->{uid}; } } } } elsif ( $record->{Name} eq 'Dashboard' ) { my $content = $record->{Content}{Panes}; for my $type ( qw/body sidebar/ ) { if ( $content->{$type} && ref $content->{$type} eq 'ARRAY' ) { for my $item ( @{ $content->{$type} } ) { if ( my $id = $item->{id} ) { my $attribute = RT::Attribute->new( RT->SystemUser ); $attribute->Load( $id ); if ( $attribute->id ) { $item->{ObjectType} = $attribute->ObjectType; $item->{ObjectId} = $attribute->Object->Name; $item->{Description} = $attribute->Description; delete $item->{$_} for qw/id privacy/; } } delete $item->{uid}; } } } } elsif ( $record->{Name} eq 'Pref-DashboardsInMenu' ) { my @dashboards; for my $item ( @{ $record->{Content}{dashboards} } ) { if ( ref $item eq 'SCALAR' && $$item =~ /(\d+)$/ ) { my $id = $1; my $attribute = RT::Attribute->new( RT->SystemUser ); $attribute->Load( $id ); my $dashboard = {}; if ( $attribute->id ) { $dashboard->{ObjectType} = $attribute->ObjectType; $dashboard->{ObjectId} = $attribute->Object->Name; $dashboard->{Description} = $attribute->Description; } push @dashboards, $dashboard; } } $record->{Content}{dashboards} = \@dashboards; } elsif ( $record->{Name} eq 'Subscription' ) { my $dashboard_id = $record->{Content}{DashboardId}; if ( ref $dashboard_id eq 'SCALAR' && $$dashboard_id =~ /(\d+)$/ ) { my $id = $1; my $attribute = RT::Attribute->new( RT->SystemUser ); $attribute->Load( $id ); if ( $attribute->Id ) { $record->{Content}{DashboardId} = { ObjectType => $attribute->ObjectType, ObjectId => $attribute->Object->Name, Description => $attribute->Description, }; } } } } } if ( ${$record->{Object}} =~ /^(RT::\w+)-/ ) { $record->{ObjectType} = $1; } } for my $key ( keys %{ $self->{Records}{'RT::Attribute'} } ) { delete $self->{Records}{'RT::Attribute'}{$key} if $self->{Records}{'RT::Attribute'}{$key}{Name} =~ /^(?:UpgradeHistory|QueueCacheNeedsUpdate|CatalogCacheNeedsUpdate|CustomRoleCacheNeedsUpdate|RecentlyViewedTickets)$/; } } sub CanonicalizeObjects { my $self = shift; $self->_CanonicalizeManyToMany( object_class => 'RT::ObjectCustomField', object_primary_ref => 'CustomField', primary_class => 'RT::CustomField', sort_uniq => 1, canonicalize_object => sub { my $id = $_->{ObjectId}; return $id if !ref($id); my $serialized = $self->_GetSerializedByRef($id); return $serialized ? $serialized->{Name} : undef; }, ); $self->_CanonicalizeManyToMany( object_class => 'RT::CustomFieldValue', object_primary_ref => 'CustomField', object_sorter => 'Name', primary_class => 'RT::CustomField', primary_key => 'Values', delete_empty => 1, canonicalize_object => sub { my %object = %$_; return if $object{Disabled} && !$self->{FollowDisabled}; delete $object{CustomField}; delete $object{id} unless $self->{Sync}; delete $object{Category} if !length($object{Category}); delete $object{Description} if !length($object{Description}); return \%object; }, ); $self->_CanonicalizeManyToMany( object_class => 'RT::ObjectClass', object_primary_ref => 'Class', primary_class => 'RT::Class', sort_uniq => 1, canonicalize_object => sub { my $id = $_->{ObjectId}; return $id if !ref($id); my $serialized = $self->_GetSerializedByRef($id); return $serialized ? $serialized->{Name} : undef; }, ); $self->_CanonicalizeManyToMany( object_class => 'RT::ObjectCustomRole', object_primary_ref => 'CustomRole', primary_class => 'RT::CustomRole', sort_uniq => 1, canonicalize_object => sub { my $id = $_->{ObjectId}; return $id if !ref($id); my $serialized = $self->_GetSerializedByRef($id); return $serialized ? $serialized->{Name} : undef; }, ); $self->_CanonicalizeManyToMany( object_class => 'RT::ObjectScrip', object_primary_ref => 'Scrip', primary_class => 'RT::Scrip', primary_key => 'Queue', add_to_primary => sub { my $primary = shift; $primary->{NoAutoGlobal} = 1 if @{ $primary->{Queue} || [] } == 0; }, canonicalize_object => sub { my %object = %$_; delete $object{Scrip}; delete $object{id} unless $self->{Sync}; if (ref($_->{ObjectId})) { my $serialized = $self->_GetSerializedByRef($_->{ObjectId}); return undef if !$serialized; $object{ObjectId} = $serialized->{Name}; } return \%object; }, finalize => sub { my $scrip = shift; @{ $scrip->{Queue} } = sort { $a->{ObjectId} cmp $b->{ObjectId} } @{ $scrip->{Queue} }; }, ); } # Exclude critical system objects that should already be present in the importing side sub ShouldExcludeObject { my $self = shift; my $class = shift; my $id = shift; my $record = shift; if ($class eq 'RT::User') { return 1 if $record->{Name} eq 'RT_System' || $record->{Name} eq 'Nobody'; } elsif ($class eq 'RT::ACE') { return 1 if ($record->{UserId}||'') eq 'Nobody' && $record->{RightName} eq 'OwnTicket'; return 1 if ($record->{UserId}||'') eq 'RT_System' && $record->{RightName} eq 'SuperUser'; } elsif ($class eq 'RT::Group') { return 1 if $record->{Domain} eq 'RT::System-Role' || $record->{Domain} eq 'SystemInternal'; } elsif ($class eq 'RT::Queue') { return 1 if $record->{Name} eq '___Approvals'; } elsif ($class eq 'RT::GroupMember') { return 1 if $record->{Group} eq 'Owner' && $record->{GroupDomain} =~ /-Role$/ && $record->{Class} eq 'RT::User' && $record->{Name} eq 'Nobody'; } return 0; } sub WriteFile { my $self = shift; my %output; for my $record (map { values %$_ } values %{ $self->{Records} }) { delete @$record{qw/Creator Created LastUpdated LastUpdatedBy/}; } $self->CanonicalizeObjects; $self->CanonicalizeObjectCustomFieldValues; $self->CanonicalizeObjectTopics; $self->CanonicalizeACLs; $self->CanonicalizeUsers; $self->CanonicalizeGroups; $self->CanonicalizeGroupMembers; $self->CanonicalizeCustomFields; $self->CanonicalizeArticles; $self->CanonicalizeAttributes; my $all_records = $self->{Records}; my $sync = $self->{Sync}; for my $intype (keys %$all_records) { my $outtype = $intype; $outtype =~ s/^RT:://; $outtype = $initialdataType{$outtype} || ($outtype . 's'); my $records = $all_records->{$intype}; # sort by database id then serializer id for stability for my $id (sort { ($records->{$a}{id} || 0) <=> ($records->{$b}{id} || 0) || $a cmp $b } keys %$records) { my $record = $records->{$id}; next if $self->ShouldExcludeObject($intype, $id, $record); for my $key (keys %$record) { if (ref($record->{$key}) eq 'SCALAR') { $record->{$key} = $self->CanonicalizeReference($record->{$key}, $record, $key); } } delete $record->{id} unless $sync; delete $record->{Disabled} if !$record->{Disabled}; push @{ $output{$outtype} }, $record; } } print { $self->{Filehandle} } $self->JSON->encode(\%output); } 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/NotifyOwnerOrAdminCc.pm������������������������������������������������������000644 �000765 �000024 �00000004644 14005011336 021662� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Action::NotifyOwnerOrAdminCc; use strict; use warnings; use base qw(RT::Action::Notify); use Email::Address; =head1 Notify Owner or AdminCc If the owner of this ticket is Nobody, notify the AdminCcs. Otherwise, only notify the Owner. =cut sub Argument { my $self = shift; my $ticket = $self->TicketObj; if ($ticket->Owner == RT->Nobody->id) { return 'AdminCc'; } else { return 'Owner'; } } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/RecordComment.pm�������������������������������������������������������������000644 �000765 �000024 �00000007055 14005011336 020417� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Action::RecordComment; use base 'RT::Action'; use strict; use warnings; =head1 NAME RT::Action::RecordComment - An Action which can be used from an external tool, or in any situation where a ticket transaction has not been started, to make a comment on the ticket. =head1 SYNOPSIS my $action_obj = RT::Action::RecordComment->new( 'TicketObj' => $ticket_obj, 'TemplateObj' => $template_obj, ); my $result = $action_obj->Prepare(); $action_obj->Commit() if $result; =head1 METHODS =head2 Prepare Check for the existence of a Transaction. If a Transaction already exists, and is of type "Comment" or "Correspond", abort because that will give us a loop. =cut sub Prepare { my $self = shift; if (defined $self->{'TransactionObj'} && $self->{'TransactionObj'}->Type =~ /^(Comment|Correspond)$/) { return undef; } return 1; } =head2 Commit Create a Transaction by calling the ticket's Comment method on our parsed Template, which may have an RT-Send-Cc or RT-Send-Bcc header. The Transaction will be of type Comment. This Transaction can then be used by the scrips that actually send the email. =cut sub Commit { my $self = shift; $self->CreateTransaction(); } sub CreateTransaction { my $self = shift; my ($result, $msg) = $self->{'TemplateObj'}->Parse( TicketObj => $self->{'TicketObj'}); return undef unless $result; my ($trans, $desc, $transaction) = $self->{'TicketObj'}->Comment( MIMEObj => $self->TemplateObj->MIMEObj); $self->{'TransactionObj'} = $transaction; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/AutoOpenInactive.pm����������������������������������������������������������000644 �000765 �000024 �00000006436 14005011336 021075� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Action::AutoOpenInactive; use strict; use warnings; use base qw(RT::Action); =head1 DESCRIPTION This action automatically moves an inactive ticket to an active status. Status is not changed if there is no active statuses in the lifecycle. Status is not changed if message's head has field C<RT-Control> with C<no-autoopen> substring. Status is set to the first possible active status. If the ticket's status is C<Resolved> then RT finds all possible transitions from C<Resolved> status and selects first one that results in the ticket having an active status. =cut sub Prepare { my $self = shift; my $ticket = $self->TicketObj; return 0 if $ticket->LifecycleObj->IsActive( $ticket->Status ); if ( my $msg = $self->TransactionObj->Message->First ) { return 0 if ( $msg->GetHeader('RT-Control') || '' ) =~ /\bno-autoopen\b/i; } my $next = $ticket->FirstActiveStatus; return 0 unless defined $next; $self->{'set_status_to'} = $next; return 1; } sub Commit { my $self = shift; return 1 unless my $new_status = $self->{'set_status_to'}; my ($val, $msg) = $self->TicketObj->SetStatus( $new_status ); unless ( $val ) { $RT::Logger->error( "Couldn't auto-open-inactive ticket: ". $msg ); return 0; } return 1; } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/NotifyAsComment.pm�����������������������������������������������������������000644 �000765 �000024 �00000004674 14005011336 020741� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Action::NotifyAsComment; use strict; use warnings; use base qw(RT::Action::Notify); =head2 SetReturnAddress Tell SendEmail that this message should come out as a comment. Calls SUPER::SetReturnAddress. =cut sub SetReturnAddress { my $self = shift; # Tell RT::Action::SendEmail that this should come # from the relevant comment email address. $self->{'comment'} = 1; return $self->SUPER::SetReturnAddress( @_, is_comment => 1 ); } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������rt-5.0.1/lib/RT/Action/NotifyGroup.pm���������������������������������������������������������������000644 �000765 �000024 �00000013120 14005011336 020131� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Action::NotifyGroup - RT Action that sends notifications to groups and/or users =head1 DESCRIPTION RT action module that allow you to notify particular groups and/or users. Distribution is shipped with C<rt-email-group-admin> script that is command line tool for managing NotifyGroup scrip actions. For more more info see its documentation. =cut package RT::Action::NotifyGroup; use strict; use warnings; use base qw(RT::Action::Notify); require RT::User; require RT::Group; =head1 METHODS =head2 SetRecipients Sets the recipients of this message to Groups and/or Users. Respects RT's NotifyActor configuration. To send email to the selected recipients regardless of RT's NotifyActor configuration, include AlwaysNotifyActor in the list of arguments. Or to always suppress email to the selected recipients regardless of RT's NotifyActor configuration, include NeverNotifyActor in the list of arguments. =cut sub SetRecipients { my $self = shift; my $arg = $self->Argument; foreach( $self->__SplitArg( $arg ) ) { $self->_HandleArgument( $_ ); } $self->{'seen_ueas'} = {}; return 1; } sub _HandleArgument { my $self = shift; my $instance = shift; return if $instance eq 'AlwaysNotifyActor' || $instance eq 'NeverNotifyActor'; if ( $instance !~ /\D/ ) { my $obj = RT::Principal->new( $self->CurrentUser ); $obj->Load( $instance ); return $self->_HandlePrincipal( $obj ); } my $group = RT::Group->new( $self->CurrentUser ); $group->LoadUserDefinedGroup( $instance ); # to check disabled and so on return $self->_HandlePrincipal( $group->PrincipalObj ) if $group->id; require Email::Address; my $user = RT::User->new( $self->CurrentUser ); if ( $instance =~ /^$Email::Address::addr_spec$/ ) { $user->LoadByEmail( $instance ); return $self->__PushUserAddress( $instance ) unless $user->id; } else { $user->Load( $instance ); } return $self->_HandlePrincipal( $user->PrincipalObj ) if $user->id; $RT::Logger->error( "'$instance' is not principal id, group name, user name," ." user email address or any email address" ); return; } sub _HandlePrincipal { my $self = shift; my $obj = shift; unless( $obj->id ) { $RT::Logger->error( "Couldn't load principal #$obj" ); return; } if( $obj->Disabled ) { $RT::Logger->info( "Principal #$obj is disabled => skip" ); return; } if( !$obj->PrincipalType ) { $RT::Logger->crit( "Principal #$obj has empty type" ); } elsif( lc $obj->PrincipalType eq 'user' ) { $self->__HandleUserArgument( $obj->Object ); } elsif( lc $obj->PrincipalType eq 'group' ) { $self->__HandleGroupArgument( $obj->Object ); } else { $RT::Logger->info( "Principal #$obj has unsupported type" ); } return; } sub __HandleUserArgument { my $self = shift; my $obj = shift; my $uea = $obj->EmailAddress; unless( $uea ) { $RT::Logger->warning( "User #". $obj->id ." has no email address" ); return; } $self->__PushUserAddress( $uea ); } sub __HandleGroupArgument { my $self = shift; my $obj = shift; my $members = $obj->UserMembersObj; while( my $m = $members->Next ) { $self->__HandleUserArgument( $m ); } } sub __SplitArg { return grep length, map {s/^\s+//; s/\s+$//; $_} split /,/, $_[1]; } sub __PushUserAddress { my $self = shift; my $uea = shift; push @{ $self->{'To'} }, $uea unless $self->{'seen_ueas'}{ $uea }++; return; } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/EscalatePriority.pm����������������������������������������������������������000644 �000765 �000024 �00000016176 14005011336 021145� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Action::EscalatePriority =head1 DESCRIPTION EscalatePriority is a ScripAction which is NOT intended to be called per transaction. It's intended to be called by an RT escalation tool. One such tool is called rt-crontool and is located in $RTHOME/bin (see C<rt-crontool -h> for more details) EsclatePriority uses the following formula to change a ticket's priority: Priority = Priority + (( FinalPriority - Priority ) / ( DueDate-Today)) Unless the duedate is past, in which case priority gets bumped straight to final priority. In this way, priority is either increased or decreased toward the final priority as the ticket heads toward its due date. Alternately, if you don't set a due date, the Priority will be incremented by 1 until it reaches the Final Priority. If a ticket without a due date has a Priority greater than Final Priority, it will be decremented by 1. =head2 CONFIGURATION EsclatePriority's behavior can be controlled by two options: =over 4 =item RecordTransaction If true (the default), the action casuses a transaction on the ticket when it is escalated. If false, the action updates the priority without running scrips or recording a transaction. =item UpdateLastUpdated If true (the default), the action updates the LastUpdated field when the ticket is escalated. You cannot set C<UpdateLastUpdated> to false unless C<RecordTransaction> is also false. =back To use these with C<rt-crontool>, specify them with C<--action-arg>: --action-arg "RecordTransaction: 0, UpdateLastUpdated: 0" =cut package RT::Action::EscalatePriority; use base 'RT::Action'; use strict; use warnings; #Do what we need to do and send it out. #What does this type of Action does sub Describe { my $self = shift; return (ref $self . " will move a ticket's priority toward its final priority."); } sub Prepare { my $self = shift; if ($self->TicketObj->Priority() == $self->TicketObj->FinalPriority()) { # no update necessary. return 0; } #compute the number of days until the ticket is due my $due = $self->TicketObj->DueObj(); # If we don't have a due date, adjust the priority by one # until we hit the final priority if (not $due->IsSet) { if ( $self->TicketObj->Priority > $self->TicketObj->FinalPriority ){ $self->{'prio'} = ($self->TicketObj->Priority - 1); return 1; } elsif ( $self->TicketObj->Priority < $self->TicketObj->FinalPriority ){ $self->{'prio'} = ($self->TicketObj->Priority + 1); return 1; } # otherwise the priority is at the final priority. we don't need to # Continue else { return 0; } } # we've got a due date. now there are other things we should do else { my $diff_in_seconds = $due->Diff(time()); my $diff_in_days = int( $diff_in_seconds / 86400); #if we haven't hit the due date yet if ($diff_in_days > 0 ) { # compute the difference between the current priority and the # final priority my $prio_delta = $self->TicketObj->FinalPriority() - $self->TicketObj->Priority; my $inc_priority_by = int( $prio_delta / $diff_in_days ); #set the ticket's priority to that amount $self->{'prio'} = $self->TicketObj->Priority + $inc_priority_by; } #if $days is less than 1, set priority to final_priority else { $self->{'prio'} = $self->TicketObj->FinalPriority(); } } return 1; } sub Commit { my $self = shift; my $new_value = $self->{'prio'}; return 1 unless defined $new_value; my $ticket = $self->TicketObj; return 1 if $ticket->Priority == $new_value; # Overide defaults from argument my($record, $update) = (1, 1); { my $arg = $self->Argument || ''; if ( $arg =~ /RecordTransaction:\s*(\d+)/i ) { $record = $1; $RT::Logger->debug("Overrode RecordTransaction: $record"); } if ( $arg =~ /UpdateLastUpdated:\s*(\d+)/i ) { $update = $1; $RT::Logger->debug("Overrode UpdateLastUpdated: $update"); } # If creating a transaction, we have to update lastupdated $update = 1 if $record; } $RT::Logger->debug( 'Escalating priority of ticket #'. $ticket->Id .' from '. $ticket->Priority .' to '. $new_value .' and'. ($record? '': ' do not') .' record a transaction' .' and'. ($update? '': ' do not') .' touch last updated field' ); my ($val, $msg); unless ( $record ) { unless ( $update ) { ( $val, $msg ) = $ticket->__Set( Field => 'Priority', Value => $new_value, ); } else { ( $val, $msg ) = $ticket->_Set( Field => 'Priority', Value => $new_value, RecordTransaction => 0, ); } } else { ($val, $msg) = $ticket->SetPriority($new_value); } unless ($val) { $RT::Logger->error( "Couldn't set new priority value: $msg"); return (0, $msg); } return 1; } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/CreateTickets.pm�������������������������������������������������������������000644 �000765 �000024 �00000116711 14005011336 020410� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Action::CreateTickets; use base 'RT::Action'; use strict; use warnings; use MIME::Entity; use RT::Link; =head1 NAME RT::Action::CreateTickets - Create one or more tickets according to an externally supplied template =head1 SYNOPSIS ===Create-Ticket: codereview Subject: Code review for {$Tickets{'TOP'}->Subject} Depended-On-By: TOP Content: Someone has created a ticket. you should review and approve it, so they can finish their work ENDOFCONTENT =head1 DESCRIPTION The CreateTickets ScripAction allows you to create automated workflows in RT, creating new tickets in response to actions and conditions from other tickets. =head2 Format CreateTickets uses the RT template configured in the scrip as a template for an ordered set of tickets to create. The basic format is as follows: ===Create-Ticket: identifier Param: Value Param2: Value Param3: Value Content: Blah blah blah ENDOFCONTENT ===Create-Ticket: id2 Param: Value Content: Blah ENDOFCONTENT As shown, you can put one or more C<===Create-Ticket:> sections in a template. Each C<===Create-Ticket:> section is evaluated as its own L<Text::Template> object, which means that you can embed snippets of Perl inside the L<Text::Template> using C<{}> delimiters, but that such sections absolutely can not span a C<===Create-Ticket:> boundary. Note that each C<Value> must come right after the C<Param> on the same line. The C<Content:> param can extend over multiple lines, but the text of the first line must start right after C<Content:>. Don't try to start your C<Content:> section with a newline. After each ticket is created, it's stuffed into a hash called C<%Tickets> making it available during the creation of other tickets during the same ScripAction. The hash key for each ticket is C<create-[identifier]>, where C<[identifier]> is the value you put after C<===Create-Ticket:>. The hash is prepopulated with the ticket which triggered the ScripAction as C<$Tickets{'TOP'}>. You can also access that ticket using the shorthand C<TOP>. A simple example: ===Create-Ticket: codereview Subject: Code review for {$Tickets{'TOP'}->Subject} Depended-On-By: TOP Content: Someone has created a ticket. you should review and approve it, so they can finish their work ENDOFCONTENT A convoluted example: ===Create-Ticket: approval { # Find out who the administrators of the group called "HR" # of which the creator of this ticket is a member my $name = "HR"; my $groups = RT::Groups->new(RT->SystemUser); $groups->LimitToUserDefinedGroups(); $groups->Limit(FIELD => "Name", OPERATOR => "=", VALUE => $name, CASESENSITIVE => 0); $groups->WithMember($TransactionObj->CreatorObj->Id); my $groupid = $groups->First->Id; my $adminccs = RT::Users->new(RT->SystemUser); $adminccs->WhoHaveRight( Right => "AdminGroup", Object =>$groups->First, IncludeSystemRights => undef, IncludeSuperusers => 0, IncludeSubgroupMembers => 0, ); our @admins; while (my $admin = $adminccs->Next) { push (@admins, $admin->EmailAddress); } } Queue: ___Approvals Type: approval AdminCc: {join ("\nAdminCc: ",@admins) } Depended-On-By: TOP Refers-To: TOP Subject: Approval for ticket: {$Tickets{"TOP"}->Id} - {$Tickets{"TOP"}->Subject} Due: {time + 86400} Content-Type: text/plain Content: Your approval is requested for the ticket {$Tickets{"TOP"}->Id}: {$Tickets{"TOP"}->Subject} Blah Blah ENDOFCONTENT ===Create-Ticket: two Subject: Manager approval Type: approval Depended-On-By: TOP Refers-To: {$Tickets{"create-approval"}->Id} Queue: ___Approvals Content-Type: text/plain Content: Your approval is requred for this ticket, too. ENDOFCONTENT As shown above, you can include a block with Perl code to set up some values for the new tickets. If you want to access a variable in the template section after the block, you must scope it with C<our> rather than C<my>. Just as with other RT templates, you can also include Perl code in the template sections using C<{}>. =head2 Acceptable Fields A complete list of acceptable fields: * Queue => Name or id# of a queue Subject => A text string ! Status => A valid status. Defaults to 'new' Due => Dates can be specified in seconds since the epoch to be handled literally or in a semi-free textual format which RT will attempt to parse. Starts => Started => Resolved => Owner => Username or id of an RT user who can and should own this ticket; forces the owner if necessary + Requestor => Email address + Cc => Email address + AdminCc => Email address + RequestorGroup => Group name + CcGroup => Group name + AdminCcGroup => Group name TimeWorked => TimeEstimated => TimeLeft => InitialPriority => FinalPriority => Type => +! DependsOn => +! DependedOnBy => +! RefersTo => +! ReferredToBy => +! Members => +! MemberOf => Content => Content. Can extend to multiple lines. Everything within a template after a Content: header is treated as content until we hit a line containing only ENDOFCONTENT ContentType => the content-type of the Content field. Defaults to 'text/plain' UpdateType => 'correspond' or 'comment'; used in conjunction with 'content' if this is an update. Defaults to 'correspond' CustomField-<id#> => custom field value CF-name => custom field value CustomField-name => custom field value Fields marked with an C<*> are required. Fields marked with a C<+> may have multiple values, simply by repeating the fieldname on a new line with an additional value. Fields marked with a C<!> have processing postponed until after all tickets in the same actions are created. Except for C<Status>, those fields can also take a ticket name within the same action (i.e. the identifiers after C<===Create-Ticket:>), instead of raw ticket ID numbers. When parsed, field names are converted to lowercase and have hyphens stripped. C<Refers-To>, C<RefersTo>, C<refersto>, C<refers-to> and C<r-e-f-er-s-tO> will all be treated as the same thing. =head1 METHODS =cut #Do what we need to do and send it out. sub Commit { my $self = shift; # Create all the tickets we care about return (1) unless $self->TicketObj->Type eq 'ticket'; $self->CreateByTemplate( $self->TicketObj ); $self->UpdateByTemplate( $self->TicketObj ); return (1); } sub Prepare { my $self = shift; unless ( $self->TemplateObj ) { $RT::Logger->warning("No template object handed to $self"); } unless ( $self->TransactionObj ) { $RT::Logger->warning("No transaction object handed to $self"); } unless ( $self->TicketObj ) { $RT::Logger->warning("No ticket object handed to $self"); } my $active = 0; if ( $self->TemplateObj->Type eq 'Perl' ) { $active = 1; } else { RT->Logger->info(sprintf( "Template #%d is type %s. You most likely want to use a Perl template instead.", $self->TemplateObj->id, $self->TemplateObj->Type )); } $self->Parse( Content => $self->TemplateObj->Content, _ActiveContent => $active, ); return 1; } sub CreateByTemplate { my $self = shift; my $top = shift; $RT::Logger->debug("In CreateByTemplate"); my @results; # XXX: cargo cult programming that works. i'll be back. local %T::Tickets = %T::Tickets; local $T::TOP = $T::TOP; local $T::ID = $T::ID; $T::Tickets{'TOP'} = $T::TOP = $top if $top; local $T::TransactionObj = $self->TransactionObj; my $ticketargs; my ( @links, @postponed ); foreach my $template_id ( @{ $self->{'create_tickets'} } ) { $RT::Logger->debug("Workflow: processing $template_id of $T::TOP") if $T::TOP; $T::ID = $template_id; @T::AllID = @{ $self->{'create_tickets'} }; ( $T::Tickets{$template_id}, $ticketargs ) = $self->ParseLines( $template_id, \@links, \@postponed ); # Now we have a %args to work with. # Make sure we have at least the minimum set of # reasonable data and do our thang my ( $id, $transid, $msg ) = $T::Tickets{$template_id}->Create(%$ticketargs); foreach my $res ( split( '\n', $msg ) ) { push @results, $T::Tickets{$template_id} ->loc( "Ticket [_1]", $T::Tickets{$template_id}->Id ) . ': ' . $res; } if ( !$id ) { if ( $self->TicketObj ) { $msg = "Couldn't create related ticket $template_id for " . $self->TicketObj->Id . " " . $msg; } else { $msg = "Couldn't create ticket $template_id " . $msg; } $RT::Logger->error($msg); next; } $RT::Logger->debug("Assigned $template_id with $id"); } $self->PostProcess( \@links, \@postponed ); return @results; } sub UpdateByTemplate { my $self = shift; my $top = shift; # XXX: cargo cult programming that works. i'll be back. my @results; local %T::Tickets = %T::Tickets; local $T::ID = $T::ID; my $ticketargs; my ( @links, @postponed ); foreach my $template_id ( @{ $self->{'update_tickets'} } ) { $RT::Logger->debug("Update Workflow: processing $template_id"); $T::ID = $template_id; @T::AllID = @{ $self->{'update_tickets'} }; ( $T::Tickets{$template_id}, $ticketargs ) = $self->ParseLines( $template_id, \@links, \@postponed ); # Now we have a %args to work with. # Make sure we have at least the minimum set of # reasonable data and do our thang my @attribs = qw( Subject FinalPriority Priority TimeEstimated TimeWorked TimeLeft Status Queue Due Starts Started Resolved ); my $id = $template_id; $id =~ s/update-(\d+).*/$1/; my ($loaded, $msg) = $T::Tickets{$template_id}->LoadById($id); unless ( $loaded ) { $RT::Logger->error("Couldn't update ticket $template_id: " . $msg); push @results, $self->loc( "Couldn't load ticket '[_1]'", $id ); next; } my $current = $self->GetBaseTemplate( $T::Tickets{$template_id} ); $template_id =~ m/^update-(.*)/; my $base_id = "base-$1"; my $base = $self->{'templates'}->{$base_id}; if ($base) { $base =~ s/\r//g; $base =~ s/\n+$//; $current =~ s/\n+$//; # If we have no base template, set what we can. if ( $base ne $current ) { push @results, "Could not update ticket " . $T::Tickets{$template_id}->Id . ": Ticket has changed"; next; } } push @results, $T::Tickets{$template_id}->Update( AttributesRef => \@attribs, ARGSRef => $ticketargs ); if ( $ticketargs->{'Owner'} ) { ($id, $msg) = $T::Tickets{$template_id}->SetOwner($ticketargs->{'Owner'}, "Force"); push @results, $msg unless $msg eq $self->loc("That user already owns that ticket"); } push @results, $self->UpdateWatchers( $T::Tickets{$template_id}, $ticketargs ); push @results, $self->UpdateCustomFields( $T::Tickets{$template_id}, $ticketargs ); next unless $ticketargs->{'MIMEObj'}; if ( $ticketargs->{'UpdateType'} =~ /^(private|comment)$/i ) { my ( $Transaction, $Description, $Object ) = $T::Tickets{$template_id}->Comment( BccMessageTo => $ticketargs->{'Bcc'}, MIMEObj => $ticketargs->{'MIMEObj'}, TimeTaken => $ticketargs->{'TimeWorked'} ); push( @results, $T::Tickets{$template_id} ->loc( "Ticket [_1]", $T::Tickets{$template_id}->id ) . ': ' . $Description ); } elsif ( $ticketargs->{'UpdateType'} =~ /^(public|response|correspond)$/i ) { my ( $Transaction, $Description, $Object ) = $T::Tickets{$template_id}->Correspond( BccMessageTo => $ticketargs->{'Bcc'}, MIMEObj => $ticketargs->{'MIMEObj'}, TimeTaken => $ticketargs->{'TimeWorked'} ); push( @results, $T::Tickets{$template_id} ->loc( "Ticket [_1]", $T::Tickets{$template_id}->id ) . ': ' . $Description ); } else { push( @results, $T::Tickets{$template_id}->loc( "Update type was neither correspondence nor comment.") . " " . $T::Tickets{$template_id}->loc("Update not recorded.") ); } } $self->PostProcess( \@links, \@postponed ); return @results; } =head2 Parse Takes (in order) template content, a default queue, a default requestor, and active (a boolean flag). Parses a template in the template content, defaulting queue and requestor if unspecified in the template to the values provided as arguments. If the active flag is true, then we'll use L<Text::Template> to parse the templates, allowing you to embed active Perl in your templates. =cut sub Parse { my $self = shift; my %args = ( Content => undef, Queue => undef, Requestor => undef, _ActiveContent => undef, @_ ); if ( $args{'_ActiveContent'} ) { $self->{'UsePerlTextTemplate'} = 1; } else { $self->{'UsePerlTextTemplate'} = 0; } if ( substr( $args{'Content'}, 0, 3 ) eq '===' ) { $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}"); } } =head2 _ParseMultilineTemplate Parses mulitline templates. Things like: ===Create-Ticket: ... Takes the same arguments as L</Parse>. =cut sub _ParseMultilineTemplate { my $self = shift; my %args = (@_); my $template_id; my ( $queue, $requestor ); $RT::Logger->debug("Line: ==="); foreach my $line ( split( /\n/, $args{'Content'} ) ) { $line =~ s/\r$//; $RT::Logger->debug( "Line: $line" ); if ( $line =~ /^===/ ) { if ( $template_id && !$queue && $args{'Queue'} ) { $self->{'templates'}->{$template_id} .= "Queue: $args{'Queue'}\n"; } if ( $template_id && !$requestor && $args{'Requestor'} ) { $self->{'templates'}->{$template_id} .= "Requestor: $args{'Requestor'}\n"; } $queue = 0; $requestor = 0; } if ( $line =~ /^===Create-Ticket: (.*)$/ ) { $template_id = "create-$1"; $RT::Logger->debug("**** Create ticket: $template_id"); push @{ $self->{'create_tickets'} }, $template_id; } elsif ( $line =~ /^===Update-Ticket: (.*)$/ ) { $template_id = "update-$1"; $RT::Logger->debug("**** Update ticket: $template_id"); push @{ $self->{'update_tickets'} }, $template_id; } elsif ( $line =~ /^===Base-Ticket: (.*)$/ ) { $template_id = "base-$1"; $RT::Logger->debug("**** Base ticket: $template_id"); push @{ $self->{'base_tickets'} }, $template_id; } elsif ( $line =~ /^===#.*$/ ) { # a comment next; } else { if ( $line =~ /^Queue:(.*)/i ) { $queue = 1; my $value = $1; $value =~ s/^\s//; $value =~ s/\s$//; if ( !$value && $args{'Queue'} ) { $value = $args{'Queue'}; $line = "Queue: $value"; } } if ( $line =~ /^Requestors?:(.*)/i ) { $requestor = 1; my $value = $1; $value =~ s/^\s//; $value =~ s/\s$//; if ( !$value && $args{'Requestor'} ) { $value = $args{'Requestor'}; $line = "Requestor: $value"; } } $self->{'templates'}->{$template_id} .= $line . "\n"; } } if ( $template_id && !$queue && $args{'Queue'} ) { $self->{'templates'}->{$template_id} .= "Queue: $args{'Queue'}\n"; } } sub ParseLines { my $self = shift; my $template_id = shift; my $links = shift; my $postponed = shift; my $content = $self->{'templates'}->{$template_id}; if ( $self->{'UsePerlTextTemplate'} ) { $RT::Logger->debug( "Workflow: evaluating\n$self->{templates}{$template_id}"); my $template = Text::Template->new( TYPE => 'STRING', SOURCE => $content ); my $err; $content = $template->fill_in( PACKAGE => 'T', BROKEN => sub { $err = {@_}->{error}; } ); $RT::Logger->debug("Workflow: yielding $content"); if ($err) { $RT::Logger->error( "Ticket creation failed: " . $err ); next; } } my $TicketObj ||= RT::Ticket->new( $self->CurrentUser ); my %args; my %original_tags; my @lines = ( split( /\n/, $content ) ); while ( defined( my $line = shift @lines ) ) { if ( $line =~ /^(.*?):(?:\s+)(.*?)(?:\s*)$/ ) { my $value = $2; my $original_tag = $1; my $tag = lc($original_tag); $tag =~ s/-//g; $tag =~ s/^(requestor|cc|admincc)s?$/$1/i; $original_tags{$tag} = $original_tag; if ( ref( $args{$tag} ) ) { #If it's an array, we want to push the value push @{ $args{$tag} }, $value; } elsif ( defined( $args{$tag} ) ) { #if we're about to get a second value, make it an array $args{$tag} = [ $args{$tag}, $value ]; } else { #if there's nothing there, just set the value $args{$tag} = $value; } if ( $tag =~ /^content$/i ) { #just build up the content # convert it to an array $args{$tag} = defined($value) ? [ $value . "\n" ] : []; while ( defined( my $l = shift @lines ) ) { last if ( $l =~ /^ENDOFCONTENT\s*$/ ); push @{ $args{'content'} }, $l . "\n"; } } else { # if it's not content, strip leading and trailing spaces if ( $args{$tag} ) { $args{$tag} =~ s/^\s+//g; $args{$tag} =~ s/\s+$//g; } if ( ($tag =~ /^(requestor|cc|admincc)(group)?$/i or grep {lc $_ eq $tag} keys %RT::Link::TYPEMAP) and $args{$tag} =~ /,/ ) { $args{$tag} = [ split /,\s*/, $args{$tag} ]; } } } } foreach my $date (qw(due starts started resolved)) { my $dateobj = RT::Date->new( $self->CurrentUser ); next unless $args{$date}; if ( $args{$date} =~ /^\d+$/ ) { $dateobj->Set( Format => 'unix', Value => $args{$date} ); } else { eval { $dateobj->Set( Format => 'iso', Value => $args{$date} ); }; if ($@ or not $dateobj->IsSet) { $dateobj->Set( Format => 'unknown', Value => $args{$date} ); } } $args{$date} = $dateobj->ISO; } foreach my $role (qw(requestor cc admincc)) { next unless my $value = $args{ $role . 'group' }; my $group = RT::Group->new( $self->CurrentUser ); $group->LoadUserDefinedGroup( $value ); unless ( $group->id ) { $RT::Logger->error("Couldn't load group '$value'"); next; } $args{ $role } = $args{ $role } ? [$args{ $role }] : [] unless ref $args{ $role }; push @{ $args{ $role } }, $group->PrincipalObj->id; } $args{'requestor'} ||= $self->TicketObj->Requestors->MemberEmailAddresses if $self->TicketObj; $args{'type'} ||= 'ticket'; my %ticketargs = ( Queue => $args{'queue'}, Subject => $args{'subject'}, Status => $args{'status'} || 'new', Due => $args{'due'}, Starts => $args{'starts'}, Started => $args{'started'}, Resolved => $args{'resolved'}, Owner => $args{'owner'}, Requestor => $args{'requestor'}, Cc => $args{'cc'}, AdminCc => $args{'admincc'}, TimeWorked => $args{'timeworked'}, TimeEstimated => $args{'timeestimated'}, TimeLeft => $args{'timeleft'}, InitialPriority => $args{'initialpriority'} || 0, FinalPriority => $args{'finalpriority'} || 0, SquelchMailTo => $args{'squelchmailto'}, Type => $args{'type'}, ); if ( $args{content} ) { my $mimeobj = MIME::Entity->build( Type => $args{'contenttype'} || 'text/plain', Charset => 'UTF-8', Data => [ map {Encode::encode( "UTF-8", $_ )} @{$args{'content'}} ], ); $ticketargs{MIMEObj} = $mimeobj; $ticketargs{UpdateType} = $args{'updatetype'} || 'correspond'; } foreach my $tag ( keys(%args) ) { # if the tag was added later, skip it my $orig_tag = $original_tags{$tag} or next; if ( $orig_tag =~ /^customfield-?(\d+)$/i ) { $ticketargs{ "CustomField-" . $1 } = $args{$tag}; } elsif ( $orig_tag =~ /^(?:customfield|cf)-?(.+)$/i ) { my $cf = RT::CustomField->new( $self->CurrentUser ); $cf->LoadByName( Name => $1, LookupType => RT::Ticket->CustomFieldLookupType, ObjectId => $ticketargs{Queue}, IncludeGlobal => 1, ); next unless $cf->id; $ticketargs{ "CustomField-" . $cf->id } = $args{$tag}; } elsif ($orig_tag) { my $cf = RT::CustomField->new( $self->CurrentUser ); $cf->LoadByName( Name => $orig_tag, LookupType => RT::Ticket->CustomFieldLookupType, ObjectId => $ticketargs{Queue}, IncludeGlobal => 1, ); next unless $cf->id; $ticketargs{ "CustomField-" . $cf->id } = $args{$tag}; } } $self->GetDeferred( \%args, $template_id, $links, $postponed ); return $TicketObj, \%ticketargs; } =head2 _ParseXSVTemplate Parses a tab or comma delimited template. Should only ever be called by L</Parse>. =cut sub _ParseXSVTemplate { my $self = shift; my %args = (@_); use Regexp::Common qw(delimited); my($first, $content) = split(/\r?\n/, $args{'Content'}, 2); my $delimiter; if ( $first =~ /\t/ ) { $delimiter = "\t"; } else { $delimiter = ','; } my @fields = split( /$delimiter/, $first ); my $delimiter_re = qr[$delimiter]; my $justquoted = qr[$RE{quoted}]; # Used to generate automatic template ids my $autoid = 1; LINE: while ($content) { $content =~ s/^(\s*\r?\n)+//; # Keep track of Queue and Requestor, so we can provide defaults my $queue; my $requestor; # The template for this line my $template; # What column we're on my $i = 0; # If the last iteration was the end of the line my $EOL = 0; # The template id my $template_id; COLUMN: while (not $EOL and length $content and $content =~ s/^($justquoted|.*?)($delimiter_re|$)//smix) { $EOL = not $2; # Strip off quotes, if they exist my $value = $1; if ( $value =~ /^$RE{delimited}{-delim=>qq{\'\"}}$/ ) { substr( $value, 0, 1 ) = ""; substr( $value, -1, 1 ) = ""; } # What column is this? my $field = $fields[$i++]; next COLUMN unless $field =~ /\S/; $field =~ s/^\s//; $field =~ s/\s$//; if ( $field =~ /^id$/i ) { # Special case if this is the ID column if ( $value =~ /^\d+$/ ) { $template_id = 'update-' . $value; push @{ $self->{'update_tickets'} }, $template_id; } elsif ( $value =~ /^#base-(\d+)$/ ) { $template_id = 'base-' . $1; push @{ $self->{'base_tickets'} }, $template_id; } elsif ( $value =~ /\S/ ) { $template_id = 'create-' . $value; push @{ $self->{'create_tickets'} }, $template_id; } } else { # Some translations if ( $field =~ /^Body$/i || $field =~ /^Data$/i || $field =~ /^Message$/i ) { $field = 'Content'; } elsif ( $field =~ /^Summary$/i ) { $field = 'Subject'; } elsif ( $field =~ /^Queue$/i ) { # Note that we found a queue $queue = 1; $value ||= $args{'Queue'}; } elsif ( $field =~ /^Requestors?$/i ) { $field = 'Requestor'; # Remove plural # Note that we found a requestor $requestor = 1; $value ||= $args{'Requestor'}; } # Tack onto the end of the template $template .= $field . ": "; $template .= (defined $value ? $value : ""); $template .= "\n"; $template .= "ENDOFCONTENT\n" if $field =~ /^Content$/i; } } # Ignore blank lines next unless $template; # If we didn't find a queue of requestor, tack on the defaults if ( !$queue && $args{'Queue'} ) { $template .= "Queue: $args{'Queue'}\n"; } if ( !$requestor && $args{'Requestor'} ) { $template .= "Requestor: $args{'Requestor'}\n"; } # If we never found an ID, come up with one unless ($template_id) { $autoid++ while exists $self->{'templates'}->{"create-auto-$autoid"}; $template_id = "create-auto-$autoid"; # Also, it's a ticket to create push @{ $self->{'create_tickets'} }, $template_id; } # Save the template we generated $self->{'templates'}->{$template_id} = $template; } } sub GetDeferred { my $self = shift; my $args = shift; my $id = shift; my $links = shift; my $postponed = shift; # Unify the aliases for child/parent $args->{$_} = [$args->{$_}] for grep {$args->{$_} and not ref $args->{$_}} qw/members hasmember memberof/; push @{$args->{'children'}}, @{delete $args->{'members'}} if $args->{'members'}; push @{$args->{'children'}}, @{delete $args->{'hasmember'}} if $args->{'hasmember'}; push @{$args->{'parents'}}, @{delete $args->{'memberof'}} if $args->{'memberof'}; # Deferred processing push @$links, ( $id, { DependsOn => $args->{'dependson'}, DependedOnBy => $args->{'dependedonby'}, RefersTo => $args->{'refersto'}, ReferredToBy => $args->{'referredtoby'}, Children => $args->{'children'}, Parents => $args->{'parents'}, } ); push @$postponed, ( # Status is postponed so we don't violate dependencies $id, { Status => $args->{'status'}, } ); } sub GetUpdateTemplate { my $self = shift; my $t = shift; my $string; $string .= "Queue: " . $t->QueueObj->Name . "\n"; $string .= "Subject: " . $t->Subject . "\n"; $string .= "Status: " . $t->Status . "\n"; $string .= "UpdateType: correspond\n"; $string .= "Content: \n"; $string .= "ENDOFCONTENT\n"; $string .= "Due: " . $t->DueObj->AsString . "\n"; $string .= "Starts: " . $t->StartsObj->AsString . "\n"; $string .= "Started: " . $t->StartedObj->AsString . "\n"; $string .= "Resolved: " . $t->ResolvedObj->AsString . "\n"; $string .= "Owner: " . $t->OwnerObj->Name . "\n"; $string .= "Requestor: " . $t->RequestorAddresses . "\n"; $string .= "Cc: " . $t->CcAddresses . "\n"; $string .= "AdminCc: " . $t->AdminCcAddresses . "\n"; $string .= "TimeWorked: " . $t->TimeWorked . "\n"; $string .= "TimeEstimated: " . $t->TimeEstimated . "\n"; $string .= "TimeLeft: " . $t->TimeLeft . "\n"; $string .= "InitialPriority: " . $t->Priority . "\n"; $string .= "FinalPriority: " . $t->FinalPriority . "\n"; foreach my $type ( RT::Link->DisplayTypes ) { $string .= "$type: "; my $mode = $RT::Link::TYPEMAP{$type}->{Mode}; my $method = $RT::Link::TYPEMAP{$type}->{Type}; my $links = ''; while ( my $link = $t->$method->Next ) { $links .= ", " if $links; my $object = $mode . "Obj"; my $member = $link->$object; $links .= $member->Id if $member; } $string .= $links; $string .= "\n"; } return $string; } sub GetBaseTemplate { my $self = shift; my $t = shift; my $string; $string .= "Queue: " . $t->Queue . "\n"; $string .= "Subject: " . $t->Subject . "\n"; $string .= "Status: " . $t->Status . "\n"; $string .= "Due: " . $t->DueObj->Unix . "\n"; $string .= "Starts: " . $t->StartsObj->Unix . "\n"; $string .= "Started: " . $t->StartedObj->Unix . "\n"; $string .= "Resolved: " . $t->ResolvedObj->Unix . "\n"; $string .= "Owner: " . $t->Owner . "\n"; $string .= "Requestor: " . $t->RequestorAddresses . "\n"; $string .= "Cc: " . $t->CcAddresses . "\n"; $string .= "AdminCc: " . $t->AdminCcAddresses . "\n"; $string .= "TimeWorked: " . $t->TimeWorked . "\n"; $string .= "TimeEstimated: " . $t->TimeEstimated . "\n"; $string .= "TimeLeft: " . $t->TimeLeft . "\n"; $string .= "InitialPriority: " . $t->Priority . "\n"; $string .= "FinalPriority: " . $t->FinalPriority . "\n"; return $string; } sub GetCreateTemplate { my $self = shift; my $string; $string .= "Queue: General\n"; $string .= "Subject: \n"; $string .= "Status: new\n"; $string .= "Content: \n"; $string .= "ENDOFCONTENT\n"; $string .= "Due: \n"; $string .= "Starts: \n"; $string .= "Started: \n"; $string .= "Resolved: \n"; $string .= "Owner: \n"; $string .= "Requestor: \n"; $string .= "Cc: \n"; $string .= "AdminCc:\n"; $string .= "TimeWorked: \n"; $string .= "TimeEstimated: \n"; $string .= "TimeLeft: \n"; $string .= "InitialPriority: \n"; $string .= "FinalPriority: \n"; foreach my $type ( RT::Link->DisplayTypes ) { $string .= "$type: \n"; } return $string; } sub UpdateWatchers { my $self = shift; my $ticket = shift; my $args = shift; my @results; foreach my $type (qw(Requestor Cc AdminCc)) { my $method = $type . 'Addresses'; my $oldaddr = $ticket->$method; # Skip unless we have a defined field next unless defined $args->{$type}; my $newaddr = $args->{$type}; my @old = split( /,\s*/, $oldaddr ); my @new; for (ref $newaddr ? @{$newaddr} : split( /,\s*/, $newaddr )) { # Sometimes these are email addresses, sometimes they're # users. Try to guess which is which, as we want to deal # with email addresses if at all possible. if (/^\S+@\S+$/) { push @new, $_; } else { # It doesn't look like an email address. Try to load it. my $user = RT::User->new($self->CurrentUser); $user->Load($_); if ($user->Id) { push @new, $user->EmailAddress; } else { push @new, $_; } } } my %oldhash = map { $_ => 1 } @old; my %newhash = map { $_ => 1 } @new; my @add = grep( !defined $oldhash{$_}, @new ); my @delete = grep( !defined $newhash{$_}, @old ); foreach (@add) { my ( $val, $msg ) = $ticket->AddWatcher( Type => $type, Email => $_ ); push @results, $ticket->loc( "Ticket [_1]", $ticket->Id ) . ': ' . $msg; } foreach (@delete) { my ( $val, $msg ) = $ticket->DeleteWatcher( Type => $type, Email => $_ ); push @results, $ticket->loc( "Ticket [_1]", $ticket->Id ) . ': ' . $msg; } } return @results; } sub UpdateCustomFields { my $self = shift; my $ticket = shift; my $args = shift; my @results; foreach my $arg (keys %{$args}) { next unless $arg =~ /^CustomField-(\d+)$/; my $cf = $1; my $CustomFieldObj = RT::CustomField->new($self->CurrentUser); $CustomFieldObj->SetContextObject( $ticket ); $CustomFieldObj->LoadById($cf); my @values; if ($CustomFieldObj->Type =~ /text/i) { # Both Text and Wikitext @values = ($args->{$arg}); } else { @values = split /\n/, $args->{$arg}; } if ( ($CustomFieldObj->Type eq 'Freeform' && ! $CustomFieldObj->SingleValue) || $CustomFieldObj->Type =~ /text/i) { foreach my $val (@values) { $val =~ s/\r//g; } } foreach my $value (@values) { next if $ticket->CustomFieldValueIsEmpty( Field => $CustomFieldObj, Value => $value, ); my ( $val, $msg ) = $ticket->AddCustomFieldValue( Field => $cf, Value => $value ); push ( @results, $msg ); } } return @results; } sub PostProcess { my $self = shift; my $links = shift; my $postponed = shift; # postprocessing: add links while ( my $template_id = shift(@$links) ) { my $ticket = $T::Tickets{$template_id}; $RT::Logger->debug( "Handling links for " . $ticket->Id ); my %args = %{ shift(@$links) }; foreach my $type ( keys %RT::Link::TYPEMAP ) { next unless ( defined $args{$type} ); foreach my $link ( ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) ) { next unless $link; if ( $link =~ /^TOP$/i ) { $RT::Logger->debug( "Building $type link for $link: " . $T::Tickets{TOP}->Id ); $link = $T::Tickets{TOP}->Id; } elsif ( $link !~ m/^\d+$/ ) { my $key = "create-$link"; if ( !exists $T::Tickets{$key} ) { $RT::Logger->debug( "Skipping $type link for $key (non-existent)"); next; } $RT::Logger->debug( "Building $type link for $link: " . $T::Tickets{$key}->Id ); $link = $T::Tickets{$key}->Id; } else { $RT::Logger->debug("Building $type link for $link"); } my ( $wval, $wmsg ) = $ticket->AddLink( Type => $RT::Link::TYPEMAP{$type}->{'Type'}, $RT::Link::TYPEMAP{$type}->{'Mode'} => $link, Silent => 1 ); $RT::Logger->warning("AddLink thru $link failed: $wmsg") unless $wval; # push @non_fatal_errors, $wmsg unless ($wval); } } } # postponed actions -- Status only, currently while ( my $template_id = shift(@$postponed) ) { my $ticket = $T::Tickets{$template_id}; $RT::Logger->debug( "Handling postponed actions for " . $ticket->id ); my %args = %{ shift(@$postponed) }; $ticket->SetStatus( $args{Status} ) if defined $args{Status}; } } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������rt-5.0.1/lib/RT/Action/OpenOnStarted.pm�������������������������������������������������������������000644 �000765 �000024 �00000005465 14005011336 020406� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Action::OpenOnStarted =head1 DESCRIPTION OpenOnStarted is a ScripAction which sets a ticket status to open when the ticket is given a Started value. Before this commit, this functionality used to happen in RT::Ticket::SetStarted which made the functionality the policy for setting started. Moving the functionality to a scrip allows for it to be disabled if it is not desired. =cut package RT::Action::OpenOnStarted; use base 'RT::Action'; use strict; use warnings; sub Prepare { my $self = shift; return 0 unless $self->TransactionObj->Type eq "Set"; return 0 unless $self->TransactionObj->Field eq "Started"; return 1; } sub Commit { my $self = shift; my $ticket = $self->TicketObj; my $next = $ticket->FirstActiveStatus; $ticket->SetStatus( $next ) if defined $next; return 1; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/NotifyGroupAsComment.pm������������������������������������������������������000644 �000765 �000024 �00000005026 14005011336 021746� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Action::NotifyGroupAsComment - RT Action that sends notifications to groups and/or users as comment =head1 DESCRIPTION This is subclass of L<RT::Action::NotifyGroup> that send comments instead of replies. See C<rt-email-group-admin> and L<RT::Action::NotifyGroup> docs for more info. =cut package RT::Action::NotifyGroupAsComment; use strict; use warnings; use base qw(RT::Action::NotifyGroup); sub SetReturnAddress { my $self = shift; $self->{'comment'} = 1; return $self->SUPER::SetReturnAddress( @_, is_comment => 1 ); } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/SendEmail.pm�����������������������������������������������������������������000644 �000765 �000024 �00000111771 14005011336 017520� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} # Portions Copyright 2000 Tobias Brox <tobix@cpan.org> package RT::Action::SendEmail; use strict; use warnings; use base qw(RT::Action); use RT::EmailParser; use RT::Interface::Email; use Email::Address; use List::MoreUtils qw(uniq); our @EMAIL_RECIPIENT_HEADERS = qw(To Cc Bcc); =head1 NAME RT::Action::SendEmail - An Action which users can use to send mail or can subclassed for more specialized mail sending behavior. RT::Action::AutoReply is a good example subclass. =head1 SYNOPSIS use base 'RT::Action::SendEmail'; =head1 DESCRIPTION Basically, you create another module RT::Action::YourAction which ISA RT::Action::SendEmail. =head1 METHODS =head2 CleanSlate Cleans class-wide options, like L</AttachTickets>. =cut sub CleanSlate { my $self = shift; $self->AttachTickets(undef); } =head2 Commit Sends the prepared message and writes outgoing record into DB if the feature is activated in the config. =cut sub Commit { my $self = shift; return abs $self->SendMessage( $self->TemplateObj->MIMEObj ) unless RT->Config->Get('RecordOutgoingEmail'); $self->DeferDigestRecipients(); my $message = $self->TemplateObj->MIMEObj; my $orig_message; $orig_message = $message->dup if RT::Interface::Email::WillSignEncrypt( Attachment => $self->TransactionObj->Attachments->First, Ticket => $self->TicketObj, ); my ($ret) = $self->SendMessage($message); return abs( $ret ) if $ret <= 0; if ($orig_message) { $message->attach( Type => 'application/x-rt-original-message', Disposition => 'inline', Data => $orig_message->as_string, ); } $self->RecordOutgoingMailTransaction($message); $self->RecordDeferredRecipients(); return 1; } =head2 Prepare Builds an outgoing email we're going to send using scrip's template. =cut sub Prepare { my $self = shift; unless ( $self->TemplateObj->MIMEObj ) { my ( $result, $message ) = $self->TemplateObj->Parse( Argument => $self->Argument, TicketObj => $self->TicketObj, TransactionObj => $self->TransactionObj ); if ( !$result ) { return (undef); } } my $MIMEObj = $self->TemplateObj->MIMEObj; # Header $self->SetRTSpecialHeaders(); my %seen; foreach my $type (@EMAIL_RECIPIENT_HEADERS) { @{ $self->{$type} } = grep defined && length && !$seen{ lc $_ }++, @{ $self->{$type} }; } $self->RemoveInappropriateRecipients(); # Go add all the Tos, Ccs and Bccs that we need to to the message to # make it happy, but only if we actually have values in those arrays. # TODO: We should be pulling the recipients out of the template and shove them into To, Cc and Bcc for my $header (@EMAIL_RECIPIENT_HEADERS) { $self->SetHeader( $header, join( ', ', @{ $self->{$header} } ) ) if (!$MIMEObj->head->get($header) && $self->{$header} && @{ $self->{$header} } ); } # PseudoTo (fake to headers) shouldn't get matched for message recipients. # If we don't have any 'To' header (but do have other recipients), drop in # the pseudo-to header. $self->SetHeader( 'To', join( ', ', @{ $self->{'PseudoTo'} } ) ) if $self->{'PseudoTo'} && @{ $self->{'PseudoTo'} } && !$MIMEObj->head->get('To') && ( $MIMEObj->head->get('Cc') or $MIMEObj->head->get('Bcc') ); # For security reasons, we only send out textual mails. foreach my $part ( grep !$_->is_multipart, $MIMEObj->parts_DFS ) { my $type = $part->mime_type || 'text/plain'; $type = 'text/plain' unless RT::I18N::IsTextualContentType($type); $part->head->mime_attr( "Content-Type" => $type ); # utf-8 here is for _FindOrGuessCharset in I18N.pm # it's not the final charset/encoding sent $part->head->mime_attr( "Content-Type.charset" => 'utf-8' ); } RT::I18N::SetMIMEEntityToEncoding( Entity => $MIMEObj, Encoding => RT->Config->Get('EmailOutputEncoding'), PreserveWords => 1, IsOut => 1, ); # Build up a MIME::Entity that looks like the original message. $self->AddAttachments if ( $MIMEObj->head->get('RT-Attach-Message') && ( $MIMEObj->head->get('RT-Attach-Message') !~ /^(n|no|0|off|false)$/i ) ); $self->AddTickets; my $attachment = $self->TransactionObj->Attachments->First; if ($attachment && !( $attachment->GetHeader('X-RT-Encrypt') || $self->TicketObj->QueueObj->Encrypt ) ) { $attachment->SetHeader( 'X-RT-Encrypt' => 1 ) if ( $attachment->GetHeader("X-RT-Incoming-Encryption") || '' ) eq 'Success'; } return 1; } =head2 To Returns an array of L<Email::Address> objects containing all the To: recipients for this notification =cut sub To { my $self = shift; return ( $self->AddressesFromHeader('To') ); } =head2 Cc Returns an array of L<Email::Address> objects containing all the Cc: recipients for this notification =cut sub Cc { my $self = shift; return ( $self->AddressesFromHeader('Cc') ); } =head2 Bcc Returns an array of L<Email::Address> objects containing all the Bcc: recipients for this notification =cut sub Bcc { my $self = shift; return ( $self->AddressesFromHeader('Bcc') ); } sub AddressesFromHeader { my $self = shift; my $field = shift; my $header = Encode::decode("UTF-8",$self->TemplateObj->MIMEObj->head->get($field)); my @addresses = Email::Address->parse($header); return (@addresses); } =head2 SendMessage MIMEObj sends the message using RT's preferred API. TODO: Break this out to a separate module =cut sub SendMessage { # DO NOT SHIFT @_ in this subroutine. It breaks Hook::LexWrap's # ability to pass @_ to a 'post' routine. my ( $self, $MIMEObj ) = @_; my $msgid = Encode::decode( "UTF-8", $MIMEObj->head->get('Message-ID') ); chomp $msgid; $self->ScripActionObj->{_Message_ID}++; $RT::Logger->info( $msgid . " #" . $self->TicketObj->id . "/" . $self->TransactionObj->id . " - Scrip " . ($self->ScripObj->id || '#rule'). " " . ( $self->ScripObj->Description || '' ) ); my $status = RT::Interface::Email::SendEmail( Entity => $MIMEObj, Ticket => $self->TicketObj, Transaction => $self->TransactionObj, ); return $status unless ($status > 0 || exists $self->{'Deferred'}); my $success = $msgid . " sent "; foreach (@EMAIL_RECIPIENT_HEADERS) { my $recipients = Encode::decode( "UTF-8", $MIMEObj->head->get($_) ); $success .= " $_: " . $recipients if $recipients; } if( exists $self->{'Deferred'} ) { for (qw(daily weekly susp)) { $success .= "\nBatched email $_ for: ". join(", ", keys %{ $self->{'Deferred'}{ $_ } } ) if exists $self->{'Deferred'}{ $_ }; } } $success =~ s/\n//g; $RT::Logger->info($success); return (1); } =head2 AttachableFromTransaction Function (not method) that takes an L<RT::Transaction> and returns an L<RT::Attachments> collection of attachments suitable for attaching to an email. =cut sub AttachableFromTransaction { my $txn = shift; my $attachments = RT::Attachments->new( RT->SystemUser ); $attachments->Limit( FIELD => 'TransactionId', VALUE => $txn->Id ); # Don't attach anything blank $attachments->LimitNotEmpty; $attachments->OrderBy( FIELD => 'id' ); # We want to make sure that we don't include the attachment that's # being used as the "Content" of this message" unless that attachment's # content type is not like text/... my $transaction_content_obj = $txn->ContentObj; if ( $transaction_content_obj && $transaction_content_obj->ContentType =~ m{text/}i ) { # If this was part of a multipart/alternative, skip all of the kids my $parent = $transaction_content_obj->ParentObj; if ($parent and $parent->Id and $parent->ContentType eq "multipart/alternative") { $attachments->Limit( ENTRYAGGREGATOR => 'AND', FIELD => 'parent', OPERATOR => '!=', VALUE => $parent->Id, ); } else { $attachments->Limit( ENTRYAGGREGATOR => 'AND', FIELD => 'id', OPERATOR => '!=', VALUE => $transaction_content_obj->Id, ); } } return $attachments; } =head2 AddAttachments Takes any attachments to this transaction and attaches them to the message we're building. =cut sub AddAttachments { my $self = shift; my $MIMEObj = $self->TemplateObj->MIMEObj; $MIMEObj->head->delete('RT-Attach-Message'); my $attachments = AttachableFromTransaction($self->TransactionObj); # attach any of this transaction's attachments my $seen_attachment = 0; while ( my $attach = $attachments->Next ) { if ( !$seen_attachment ) { $MIMEObj->make_multipart( 'mixed', Force => 1 ); $seen_attachment = 1; } $self->AddAttachment($attach); } # attach any attachments requested by the transaction or template # that aren't part of the transaction itself $self->AddAttachmentsFromHeaders; } =head2 AddAttachmentsFromHeaders Add attachments requested by the transaction or template that aren't part of the transaction itself. This inspects C<RT-Attach> headers, which are expected to contain an L<RT::Attachment> ID that the transaction's creator can See. L<RT::Ticket->_RecordNote> accepts an C<AttachExisting> argument which sets C<RT-Attach> headers appropriately on Comment/Correspond. =cut sub AddAttachmentsFromHeaders { my $self = shift; my $email = $self->TemplateObj->MIMEObj; # Add the RT-Attach headers from the transaction to the email if (my $attachment = $self->TransactionObj->Attachments->First) { for my $id ($attachment->GetAllHeaders('RT-Attach')) { $email->head->add('RT-Attach' => $id); } } # Take all RT-Attach headers and add the attachments to the outgoing mail for my $id (uniq $email->head->get_all('RT-Attach')) { $id =~ s/(?:^\s*|\s*$)//g; my $attach = RT::Attachment->new( $self->TransactionObj->CreatorObj ); $attach->Load($id); next unless $attach->Id and $attach->TransactionObj->CurrentUserCanSee; $email->make_multipart( 'mixed', Force => 1 ) unless $email->effective_type eq 'multipart/mixed'; $self->AddAttachment($attach, $email); } } =head2 AddAttachment $attachment Takes one attachment object of L<RT::Attachment> class and attaches it to the message we're building. =cut sub AddAttachment { my $self = shift; my $attach = shift; my $MIMEObj = shift || $self->TemplateObj->MIMEObj; # $attach->TransactionObj may not always be $self->TransactionObj return unless $attach->Id and $attach->TransactionObj->CurrentUserCanSee; # ->attach expects just the disposition type; extract it if we have the header # or default to "attachment" my $disp = ($attach->GetHeader('Content-Disposition') || '') =~ /^\s*(inline|attachment)/i ? $1 : "attachment"; $MIMEObj->attach( Type => $attach->ContentType, Charset => $attach->OriginalEncoding, Data => $attach->OriginalContent, Disposition => $disp, Filename => $self->MIMEEncodeString( $attach->Filename ), Id => $attach->GetHeader('Content-ID'), 'RT-Attachment:' => $self->TicketObj->Id . "/" . $self->TransactionObj->Id . "/" . $attach->id, Encoding => '-SUGGEST', ); } =head2 AttachTickets [@IDs] Returns or set list of ticket's IDs that should be attached to an outgoing message. B<Note> this method works as a class method and setup things global, so you have to clean list by passing undef as argument. =cut { my $list = []; sub AttachTickets { my $self = shift; $list = [ grep defined, @_ ] if @_; return @$list; } } =head2 AddTickets Attaches tickets to the current message, list of tickets' ids get from L</AttachTickets> method. =cut sub AddTickets { my $self = shift; $self->AddTicket($_) foreach $self->AttachTickets; return; } =head2 AddTicket $ID Attaches a ticket with ID to the message. Each ticket is attached as multipart entity and all its messages and attachments are attached as sub entities in order of creation, but only if transaction type is Create or Correspond. =cut sub AddTicket { my $self = shift; my $tid = shift; my $attachs = RT::Attachments->new( $self->TransactionObj->CreatorObj ); my $txn_alias = $attachs->TransactionAlias; $attachs->Limit( ALIAS => $txn_alias, FIELD => 'Type', OPERATOR => 'IN', VALUE => [qw(Create Correspond)], ); $attachs->LimitByTicket($tid); $attachs->LimitNotEmpty; $attachs->OrderBy( FIELD => 'Created' ); my $ticket_mime = MIME::Entity->build( Type => 'multipart/mixed', Top => 0, Description => "ticket #$tid", ); while ( my $attachment = $attachs->Next ) { $self->AddAttachment( $attachment, $ticket_mime ); } if ( $ticket_mime->parts ) { my $email_mime = $self->TemplateObj->MIMEObj; $email_mime->make_multipart( 'mixed', Force => 1 ) unless $email_mime->effective_type eq 'multipart/mixed'; $email_mime->add_part($ticket_mime); } return; } =head2 RecordOutgoingMailTransaction MIMEObj Record a transaction in RT with this outgoing message for future record-keeping purposes =cut sub RecordOutgoingMailTransaction { my $self = shift; my $MIMEObj = shift; my @parts = $MIMEObj->parts; my @attachments; my @keep; foreach my $part (@parts) { my $attach = $part->head->get('RT-Attachment'); if ($attach) { $RT::Logger->debug( "We found an attachment. we want to not record it."); push @attachments, $attach; } else { $RT::Logger->debug("We found a part. we want to record it."); push @keep, $part; } } $MIMEObj->parts( \@keep ); foreach my $attachment (@attachments) { $MIMEObj->head->add( 'RT-Attachment', $attachment ); } RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, 'utf-8', 'mime_words_ok' ); my $transaction = RT::Transaction->new( $self->TransactionObj->CurrentUser ); # XXX: TODO -> Record attachments as references to things in the attachments table, maybe. my $type; if ( $self->TransactionObj->Type eq 'Comment' ) { $type = 'CommentEmailRecord'; } else { $type = 'EmailRecord'; } my $msgid = Encode::decode( "UTF-8", $MIMEObj->head->get('Message-ID') ); chomp $msgid; my ( $id, $msg ) = $transaction->Create( Ticket => $self->TicketObj->Id, Type => $type, Data => $msgid, MIMEObj => $MIMEObj, ActivateScrips => 0 ); if ($id) { $self->{'OutgoingMailTransaction'} = $id; } else { $RT::Logger->warning( "Could not record outgoing message transaction: $msg"); } return $id; } =head2 SetRTSpecialHeaders This routine adds all the random headers that RT wants in a mail message that don't matter much to anybody else. =cut sub SetRTSpecialHeaders { my $self = shift; $self->SetSubject(); $self->SetSubjectToken(); $self->SetHeaderAsEncoding( 'Subject', RT->Config->Get('EmailOutputEncoding') ) if ( RT->Config->Get('EmailOutputEncoding') ); $self->SetReturnAddress(); $self->SetReferencesHeaders(); unless ( $self->TemplateObj->MIMEObj->head->get('Message-ID') ) { # Get Message-ID for this txn my $msgid = ""; if ( my $msg = $self->TransactionObj->Message->First ) { $msgid = $msg->GetHeader("RT-Message-ID") || $msg->GetHeader("Message-ID"); } # If there is one, and we can parse it, then base our Message-ID on it if ( $msgid and $msgid =~ s/<(rt-.*?-\d+-\d+)\.(\d+)-\d+-\d+\@\QRT->Config->Get('Organization')\E>$/ "<$1." . $self->TicketObj->id . "-" . $self->ScripObj->id . "-" . $self->ScripActionObj->{_Message_ID} . "@" . RT->Config->Get('Organization') . ">"/eg and $2 == $self->TicketObj->id ) { $self->SetHeader( "Message-ID" => $msgid ); } else { $self->SetHeader( 'Message-ID' => RT::Interface::Email::GenMessageId( Ticket => $self->TicketObj, Scrip => $self->ScripObj, ScripAction => $self->ScripActionObj ), ); } } $self->SetHeader( 'X-RT-Loop-Prevention', RT->Config->Get('rtname') ); $self->SetHeader( 'X-RT-Ticket', RT->Config->Get('rtname') . " #" . $self->TicketObj->id() ); $self->SetHeader( 'X-Managed-by', "RT $RT::VERSION (http://www.bestpractical.com/rt/)" ); # XXX, TODO: use /ShowUser/ShowUserEntry(or something like that) when it would be # refactored into user's method. if ( my $email = $self->TransactionObj->CreatorObj->EmailAddress and ! defined $self->TemplateObj->MIMEObj->head->get("RT-Originator") and RT->Config->Get('UseOriginatorHeader') ) { $self->SetHeader( 'X-RT-Originator', $email ); } } sub DeferDigestRecipients { my $self = shift; $RT::Logger->debug( "Calling SetRecipientDigests for transaction " . $self->TransactionObj . ", id " . $self->TransactionObj->id ); # The digest attribute will be an array of notifications that need to # be sent for this transaction. The array will have the following # format for its objects. # $digest_hash -> {daily|weekly|susp} -> address -> {To|Cc|Bcc} # -> sent -> {true|false} # The "sent" flag will be used by the cron job to indicate that it has # run on this transaction. # In a perfect world we might move this hash construction to the # extension module itself. my $digest_hash = {}; foreach my $mailfield (@EMAIL_RECIPIENT_HEADERS) { # If we have a "PseudoTo", the "To" contains it, so we don't need to access it next if ( ( $self->{'PseudoTo'} && @{ $self->{'PseudoTo'} } ) && ( $mailfield eq 'To' ) ); $RT::Logger->debug( "Working on mailfield $mailfield; recipients are " . join( ',', @{ $self->{$mailfield} } ) ); # Store the 'daily digest' folk in an array. my ( @send_now, @daily_digest, @weekly_digest, @suspended ); # Have to get the list of addresses directly from the MIME header # at this point. $RT::Logger->debug( Encode::decode( "UTF-8", $self->TemplateObj->MIMEObj->head->as_string ) ); foreach my $rcpt ( map { $_->address } $self->AddressesFromHeader($mailfield) ) { next unless $rcpt; my $user_obj = RT::User->new(RT->SystemUser); $user_obj->LoadByEmail($rcpt); if ( ! $user_obj->id ) { # If there's an email address in here without an associated # RT user, pass it on through. $RT::Logger->debug( "User $rcpt is not associated with an RT user object. Send mail."); push( @send_now, $rcpt ); next; } my $mailpref = RT->Config->Get( 'EmailFrequency', $user_obj ) || ''; $RT::Logger->debug( "Got user mail preference '$mailpref' for user $rcpt"); if ( $mailpref =~ /daily/i ) { push( @daily_digest, $rcpt ) } elsif ( $mailpref =~ /weekly/i ) { push( @weekly_digest, $rcpt ) } elsif ( $mailpref =~ /suspend/i ) { push( @suspended, $rcpt ) } else { push( @send_now, $rcpt ) } } # Reset the relevant mail field. $RT::Logger->debug( "Removing deferred recipients from $mailfield: line"); if (@send_now) { $self->SetHeader( $mailfield, join( ', ', @send_now ) ); } else { # No recipients! Remove the header. $self->TemplateObj->MIMEObj->head->delete($mailfield); } # Push the deferred addresses into the appropriate field in # our attribute hash, with the appropriate mail header. $RT::Logger->debug( "Setting deferred recipients for attribute creation"); $digest_hash->{'daily'}->{$_} = {'header' => $mailfield , _sent => 0} for (@daily_digest); $digest_hash->{'weekly'}->{$_} ={'header' => $mailfield, _sent => 0} for (@weekly_digest); $digest_hash->{'susp'}->{$_} = {'header' => $mailfield, _sent =>0 } for (@suspended); } if ( scalar keys %$digest_hash ) { # Save the hash so that we can add it as an attribute to the # outgoing email transaction. $self->{'Deferred'} = $digest_hash; } else { $RT::Logger->debug( "No recipients found for deferred delivery on " . "transaction #" . $self->TransactionObj->id ); } } sub RecordDeferredRecipients { my $self = shift; return unless exists $self->{'Deferred'}; my $txn_id = $self->{'OutgoingMailTransaction'}; return unless $txn_id; my $txn_obj = RT::Transaction->new( $self->CurrentUser ); $txn_obj->Load( $txn_id ); my( $ret, $msg ) = $txn_obj->AddAttribute( Name => 'DeferredRecipients', Content => $self->{'Deferred'} ); $RT::Logger->warning( "Unable to add deferred recipients to outgoing transaction: $msg" ) unless $ret; return ($ret,$msg); } =head2 SquelchMailTo Returns list of the addresses to squelch on this transaction. =cut sub SquelchMailTo { my $self = shift; return map $_->Content, $self->TransactionObj->SquelchMailTo; } =head2 RemoveInappropriateRecipients Remove addresses that are RT addresses or that are on this transaction's blacklist =cut my %squelch_reasons = ( 'not privileged' => "because autogenerated messages are configured to only be sent to privileged users (RedistributeAutoGeneratedMessages)", 'squelch:attachment' => "by RT-Squelch-Replies-To header in the incoming message", 'squelch:transaction' => "by notification checkboxes for this transaction", 'squelch:ticket' => "by notification checkboxes on this ticket's People page", ); sub RemoveInappropriateRecipients { my $self = shift; my %blacklist = (); # If there are no recipients, don't try to send the message. # If the transaction has content and has the header RT-Squelch-Replies-To my $msgid = Encode::decode( "UTF-8", $self->TemplateObj->MIMEObj->head->get('Message-Id') ); chomp $msgid; if ( my $attachment = $self->TransactionObj->Attachments->First ) { if ( $attachment->GetHeader('RT-DetectedAutoGenerated') ) { # What do we want to do with this? It's probably (?) a bounce # caused by one of the watcher addresses being broken. # Default ("true") is to redistribute, for historical reasons. my $redistribute = RT->Config->Get('RedistributeAutoGeneratedMessages'); if ( !$redistribute ) { # Don't send to any watchers. @{ $self->{$_} } = () for (@EMAIL_RECIPIENT_HEADERS); $RT::Logger->info( $msgid . " The incoming message was autogenerated. " . "Not redistributing this message based on site configuration." ); } elsif ( $redistribute eq 'privileged' ) { # Only send to "privileged" watchers. foreach my $type (@EMAIL_RECIPIENT_HEADERS) { foreach my $addr ( @{ $self->{$type} } ) { my $user = RT::User->new(RT->SystemUser); $user->LoadByEmail($addr); $blacklist{ $addr } ||= 'not privileged' unless $user->id && $user->Privileged; } } $RT::Logger->info( $msgid . " The incoming message was autogenerated. " . "Not redistributing this message to unprivileged users based on site configuration." ); } } if ( my $squelch = $attachment->GetHeader('RT-Squelch-Replies-To') ) { $blacklist{ $_->address } ||= 'squelch:attachment' foreach Email::Address->parse( $squelch ); } } # Let's grab the SquelchMailTo attributes and push those entries # into the blacklisted $blacklist{ $_->Content } ||= 'squelch:transaction' foreach $self->TransactionObj->SquelchMailTo; $blacklist{ $_->Content } ||= 'squelch:ticket' foreach $self->TicketObj->SquelchMailTo; # canonicalize emails foreach my $address ( keys %blacklist ) { my $reason = delete $blacklist{ $address }; $blacklist{ lc $_ } = $reason foreach map RT::User->CanonicalizeEmailAddress( $_->address ), Email::Address->parse( $address ); } $self->RecipientFilter( Callback => sub { return unless RT::EmailParser->IsRTAddress( $_[0] ); return "$_[0] appears to point to this RT instance. Skipping"; }, All => 1, ); $self->RecipientFilter( Callback => sub { return unless $blacklist{ lc $_[0] }; return "$_[0] is blacklisted $squelch_reasons{ $blacklist{ lc $_[0] } }. Skipping"; }, ); # Cycle through the people we're sending to and pull out anyone that meets any of the callbacks for my $type (@EMAIL_RECIPIENT_HEADERS) { my @addrs; ADDRESS: for my $addr ( @{ $self->{$type} } ) { for my $filter ( map {$_->{Callback}} @{$self->{RecipientFilter}} ) { my $skip = $filter->($addr); next unless $skip; $RT::Logger->info( "$msgid $skip" ); next ADDRESS; } push @addrs, $addr; } NOSQUELCH_ADDRESS: for my $addr ( @{ $self->{NoSquelch}{$type} } ) { for my $filter ( map {$_->{Callback}} grep {$_->{All}} @{$self->{RecipientFilter}} ) { my $skip = $filter->($addr); next unless $skip; $RT::Logger->info( "$msgid $skip" ); next NOSQUELCH_ADDRESS; } push @addrs, $addr; } @{ $self->{$type} } = @addrs; } } =head2 RecipientFilter Callback => SUB, [All => 1] Registers a filter to be applied to addresses by L<RemoveInappropriateRecipients>. The C<Callback> will be called with one address at a time, and should return false if the address should receive mail, or a message explaining why it should not be. Passing a true value for C<All> will cause the filter to also be applied to NoSquelch (one-time Cc and Bcc) recipients as well. =cut sub RecipientFilter { my $self = shift; push @{ $self->{RecipientFilter}}, {@_}; } =head2 SetReturnAddress is_comment => BOOLEAN Calculate and set From and Reply-To headers based on the is_comment flag. =cut sub SetReturnAddress { my $self = shift; my %args = ( is_comment => 0, friendly_name => undef, @_ ); # From and Reply-To # $args{is_comment} should be set if the comment address is to be used. my $replyto; if ( $args{'is_comment'} ) { $replyto = $self->TicketObj->QueueObj->CommentAddress || RT->Config->Get('CommentAddress'); } else { $replyto = $self->TicketObj->QueueObj->CorrespondAddress || RT->Config->Get('CorrespondAddress'); } unless ( $self->TemplateObj->MIMEObj->head->get('From') ) { $self->SetFrom( %args, From => $replyto ); } unless ( $self->TemplateObj->MIMEObj->head->get('Reply-To') ) { $self->SetHeader( 'Reply-To', "$replyto" ); } } =head2 SetFrom ( From => emailaddress ) Set the From: address for outgoing email =cut sub SetFrom { my $self = shift; my %args = @_; if ( RT->Config->Get('UseFriendlyFromLine') ) { my $friendly_name = $self->GetFriendlyName(%args); $self->SetHeader( 'From', sprintf( RT->Config->Get('FriendlyFromLineFormat'), $self->MIMEEncodeString( $friendly_name, RT->Config->Get('EmailOutputEncoding') ), $args{From} ), ); } else { $self->SetHeader( 'From', $args{From} ); } } =head2 GetFriendlyName Calculate the proper Friendly Name based on the creator of the transaction =cut sub GetFriendlyName { my $self = shift; my %args = ( is_comment => 0, friendly_name => '', @_ ); my $friendly_name = $args{friendly_name}; unless ( $friendly_name ) { $friendly_name = $self->TransactionObj->CreatorObj->FriendlyName; if ( $friendly_name =~ /^"(.*)"$/ ) { # a quoted string $friendly_name = $1; } } $friendly_name =~ s/"/\\"/g; return $friendly_name; } =head2 SetHeader FIELD, VALUE Set the FIELD of the current MIME object into VALUE, which should be in characters, not bytes. Returns the new header, in bytes. =cut sub SetHeader { my $self = shift; my $field = shift; my $val = shift; chomp $val; chomp $field; my $head = $self->TemplateObj->MIMEObj->head; $head->fold_length( $field, 10000 ); $head->replace( $field, Encode::encode( "UTF-8", $val ) ); return $head->get($field); } =head2 SetSubject This routine sets the subject. it does not add the rt tag. That gets done elsewhere If subject is already defined via template, it uses that. otherwise, it tries to get the transaction's subject. =cut sub SetSubject { my $self = shift; my $subject; if ( $self->TemplateObj->MIMEObj->head->get('Subject') ) { return (); } # don't use Transaction->Attachments because it caches # and anything which later calls ->Attachments will be hurt # by our RowsPerPage() call. caching is hard. my $message = RT::Attachments->new( $self->CurrentUser ); $message->Limit( FIELD => 'TransactionId', VALUE => $self->TransactionObj->id); $message->OrderBy( FIELD => 'id', ORDER => 'ASC' ); $message->RowsPerPage(1); if ( $self->{'Subject'} ) { $subject = $self->{'Subject'}; } elsif ( my $first = $message->First ) { my $tmp = $first->GetHeader('Subject'); $subject = defined $tmp ? $tmp : $self->TicketObj->Subject; } else { $subject = $self->TicketObj->Subject; } $subject = '' unless defined $subject; chomp $subject; $subject =~ s/(\r\n|\n|\s)/ /g; $self->SetHeader( 'Subject', $subject ); } =head2 SetSubjectToken This routine fixes the RT tag in the subject. It's unlikely that you want to overwrite this. =cut sub SetSubjectToken { my $self = shift; my $head = $self->TemplateObj->MIMEObj->head; $self->SetHeader( Subject => RT::Interface::Email::AddSubjectTag( Encode::decode( "UTF-8", $head->get('Subject') ), $self->TicketObj, ), ); } =head2 SetReferencesHeaders Set References and In-Reply-To headers for this message. =cut sub SetReferencesHeaders { my $self = shift; my $top = $self->TransactionObj->Message->First; unless ( $top ) { $self->SetHeader( References => $self->PseudoReference ); return (undef); } my @in_reply_to = split( /\s+/m, $top->GetHeader('In-Reply-To') || '' ); my @references = split( /\s+/m, $top->GetHeader('References') || '' ); my @msgid = split( /\s+/m, $top->GetHeader('Message-ID') || '' ); # There are two main cases -- this transaction was created with # the RT Web UI, and hence we want to *not* append its Message-ID # to the References and In-Reply-To. OR it came from an outside # source, and we should treat it as per the RFC my $org = RT->Config->Get('Organization'); if ( "@msgid" =~ /<(rt-.*?-\d+-\d+)\.(\d+)-0-0\@\Q$org\E>/ ) { # Make all references which are internal be to version which we # have sent out for ( @references, @in_reply_to ) { s/<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@\Q$org\E>$/ "<$1." . $self->TicketObj->id . "-" . $self->ScripObj->id . "-" . $self->ScripActionObj->{_Message_ID} . "@" . $org . ">"/eg } # In reply to whatever the internal message was in reply to $self->SetHeader( 'In-Reply-To', join( " ", (@in_reply_to) ) ); # Default the references to whatever we're in reply to @references = @in_reply_to unless @references; # References are unchanged from internal } else { # In reply to that message $self->SetHeader( 'In-Reply-To', join( " ", (@msgid) ) ); # Default the references to whatever we're in reply to @references = @in_reply_to unless @references; # Push that message onto the end of the references push @references, @msgid; } # Push pseudo-ref to the front my $pseudo_ref = $self->PseudoReference; @references = ( $pseudo_ref, grep { $_ ne $pseudo_ref } @references ); # If there are more than 10 references headers, remove all but the # first four and the last six (Gotta keep this from growing # forever) splice( @references, 4, -6 ) if ( $#references >= 10 ); # Add on the references $self->SetHeader( 'References', join( " ", @references ) ); $self->TemplateObj->MIMEObj->head->fold_length( 'References', 80 ); } =head2 PseudoReference Returns a fake Message-ID: header for the ticket to allow a base level of threading =cut sub PseudoReference { my $self = shift; return RT::Interface::Email::PseudoReference( $self->TicketObj ); } =head2 SetHeaderAsEncoding($field_name, $charset_encoding) This routine converts the field into specified charset encoding, then applies the MIME-Header transfer encoding. =cut sub SetHeaderAsEncoding { my $self = shift; my ( $field, $enc ) = ( shift, shift ); my $head = $self->TemplateObj->MIMEObj->head; my $value = Encode::decode("UTF-8", $head->get( $field )); $value = $self->MIMEEncodeString( $value, $enc ); # Returns bytes $head->replace( $field, $value ); } =head2 MIMEEncodeString Takes a perl string and optional encoding pass it over L<RT::Interface::Email/EncodeToMIME>. Basicly encode a string using B encoding according to RFC2047, returning bytes. =cut sub MIMEEncodeString { my $self = shift; return RT::Interface::Email::EncodeToMIME( String => $_[0], Charset => $_[1] ); } RT::Base->_ImportOverlays(); 1; �������rt-5.0.1/lib/RT/Action/RecordCorrespondence.pm������������������������������������������������������000644 �000765 �000024 �00000007124 14005011336 021763� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Action::RecordCorrespondence; use base 'RT::Action'; use strict; use warnings; =head1 NAME RT::Action::RecordCorrespondence - An Action which can be used from an external tool, or in any situation where a ticket transaction has not been started, to create a correspondence on the ticket. =head1 SYNOPSIS my $action_obj = RT::Action::RecordCorrespondence->new( 'TicketObj' => $ticket_obj, 'TemplateObj' => $template_obj, ); my $result = $action_obj->Prepare(); $action_obj->Commit() if $result; =head1 METHODS =head2 Prepare Check for the existence of a Transaction. If a Transaction already exists, and is of type "Comment" or "Correspond", abort because that will give us a loop. =cut sub Prepare { my $self = shift; if (defined $self->{'TransactionObj'} && $self->{'TransactionObj'}->Type =~ /^(Comment|Correspond)$/) { return undef; } return 1; } =head2 Commit Create a Transaction by calling the ticket's Correspond method on our parsed Template, which may have an RT-Send-Cc or RT-Send-Bcc header. The Transaction will be of type Correspond. This Transaction can then be used by the scrips that actually send the email. =cut sub Commit { my $self = shift; $self->CreateTransaction(); } sub CreateTransaction { my $self = shift; my ($result, $msg) = $self->{'TemplateObj'}->Parse( TicketObj => $self->{'TicketObj'}); return undef unless $result; my ($trans, $desc, $transaction) = $self->{'TicketObj'}->Correspond( MIMEObj => $self->TemplateObj->MIMEObj); $self->{'TransactionObj'} = $transaction; } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/Notify.pm��������������������������������������������������������������������000644 �000765 �000024 �00000021464 14005011336 017126� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} # package RT::Action::Notify; use strict; use warnings; use base qw(RT::Action::SendEmail); use Email::Address; =head2 Prepare Set up the relevant recipients, then call our parent. =cut sub Prepare { my $self = shift; $self->SetRecipients(); $self->SUPER::Prepare(); } =head2 SetRecipients Sets the recipients of this message to Owner, Requestor, AdminCc, Cc or All. Explicitly B<does not> notify the creator of the transaction by default. =cut sub SetRecipients { my $self = shift; my $ticket = $self->TicketObj; my $arg = $self->Argument; $arg =~ s/\bAll\b/Owner,Requestor,AdminCc,Cc/; my ( @To, @PseudoTo, @Cc, @Bcc ); if ( $arg =~ /\bRequestor\b/ ) { push @To, $ticket->Requestors->MemberEmailAddresses; } # custom role syntax: gives: # name (undef, role name, Cc) # RT::CustomRole-# (role id, undef, Cc) # name/To (undef, role name, To) # RT::CustomRole-#/To (role id, undef, To) # name/Cc (undef, role name, Cc) # RT::CustomRole-#/Cc (role id, undef, Cc) # name/Bcc (undef, role name, Bcc) # RT::CustomRole-#/Bcc (role id, undef, Bcc) # this has to happen early because adding To addresses affects how Cc # is handled my $custom_role_re = qr! ( # $1 match everything for error reporting # word boundary \b # then RT::CustomRole-# or a role name (?: RT::CustomRole-(\d+) # $2 role id | ( \w+ ) # $3 role name ) # then, optionally, a type after a slash (?: / (To | Cc | Bcc) # $4 type )? # finally another word boundary, either from # the end of role identifier or from the end of type \b ) !x; while ($arg =~ m/$custom_role_re/g) { my ($argument, $role_id, $name, $type) = ($1, $2, $3, $4); my $role; if ($name) { # skip anything that is a core Notify argument next if $name eq 'All' || $name eq 'Owner' || $name eq 'Requestor' || $name eq 'AdminCc' || $name eq 'Cc' || $name eq 'OtherRecipients' || $name eq 'AlwaysNotifyActor' || $name eq 'NeverNotifyActor'; my $roles = RT::CustomRoles->new( $self->CurrentUser ); $roles->Limit( FIELD => 'Name', VALUE => $name, CASESENSITIVE => 0 ); # custom roles are named uniquely, but just in case there are # multiple matches, bail out as we don't know which one to use $role = $roles->First; if ( $role ) { $role = undef if $roles->Next; } } else { $role = RT::CustomRole->new( $self->CurrentUser ); $role->Load( $role_id ); } unless ($role && $role->id) { $RT::Logger->debug("Unable to load custom role from scrip action argument '$argument'"); next; } my @role_members = ( $ticket->RoleGroup($role->GroupType)->MemberEmailAddresses, $ticket->QueueObj->RoleGroup($role->GroupType)->MemberEmailAddresses, ); if (!$type || $type eq 'Cc') { push @Cc, @role_members; } elsif ($type eq 'Bcc') { push @Bcc, @role_members; } elsif ($type eq 'To') { push @To, @role_members; } } if ( $arg =~ /\bCc\b/ ) { #If we have a To, make the Ccs, Ccs, otherwise, promote them to To if (@To) { push ( @Cc, $ticket->Cc->MemberEmailAddresses ); push ( @Cc, $ticket->QueueObj->Cc->MemberEmailAddresses ); } else { push ( @Cc, $ticket->Cc->MemberEmailAddresses ); push ( @To, $ticket->QueueObj->Cc->MemberEmailAddresses ); } } if ( $arg =~ /\bOwner\b/ && $ticket->OwnerObj->id != RT->Nobody->id && $ticket->OwnerObj->EmailAddress && not $ticket->OwnerObj->Disabled ) { # If we're not sending to Ccs or requestors, # then the Owner can be the To. if (@To) { push ( @Bcc, $ticket->OwnerObj->EmailAddress ); } else { push ( @To, $ticket->OwnerObj->EmailAddress ); } } if ( $arg =~ /\bAdminCc\b/ ) { push ( @Bcc, $ticket->AdminCc->MemberEmailAddresses ); push ( @Bcc, $ticket->QueueObj->AdminCc->MemberEmailAddresses ); } if ( RT->Config->Get('UseFriendlyToLine') ) { unless (@To) { push @PseudoTo, sprintf RT->Config->Get('FriendlyToLineFormat'), $arg, $ticket->id; } } @{ $self->{'To'} } = @To; @{ $self->{'Cc'} } = @Cc; @{ $self->{'Bcc'} } = @Bcc; @{ $self->{'PseudoTo'} } = @PseudoTo; if ( $arg =~ /\bOtherRecipients\b/ ) { if ( my $attachment = $self->TransactionObj->Attachments->First ) { push @{ $self->{'NoSquelch'}{'Cc'} ||= [] }, map $_->address, Email::Address->parse( $attachment->GetHeader('RT-Send-Cc') ); push @{ $self->{'NoSquelch'}{'Bcc'} ||= [] }, map $_->address, Email::Address->parse( $attachment->GetHeader('RT-Send-Bcc') ); } } } =head2 RemoveInappropriateRecipients Remove transaction creator as appropriate for the NotifyActor setting. To send email to the selected recipients regardless of RT's NotifyActor configuration, include AlwaysNotifyActor in the list of arguments. Or to always suppress email to the selected recipients regardless of RT's NotifyActor configuration, include NeverNotifyActor in the list of arguments. =cut sub RemoveInappropriateRecipients { my $self = shift; my $creatorObj = $self->TransactionObj->CreatorObj; my $creator = $creatorObj->EmailAddress() || ''; my $TransactionCurrentUser = RT::CurrentUser->new; $TransactionCurrentUser->LoadByName($creatorObj->Name); $self->RecipientFilter( Callback => sub { return unless lc $_[0] eq lc $creator; return "not sending to $creator, creator of the transaction, due to NotifyActor setting"; }, ) if $self->Argument =~ /\bNeverNotifyActor\b/ || (!RT->Config->Get('NotifyActor',$TransactionCurrentUser) && $self->Argument !~ /\bAlwaysNotifyActor\b/); $self->SUPER::RemoveInappropriateRecipients(); } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/UserDefined.pm���������������������������������������������������������������000644 �000765 �000024 �00000005302 14005011336 020044� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Action::UserDefined; use base 'RT::Action'; use strict; use warnings; =head2 Prepare This happens on every transaction. it's always applicable =cut sub Prepare { my $self = shift; my $retval = eval $self->ScripObj->CustomPrepareCode; if ($@) { $RT::Logger->error("Scrip ".$self->ScripObj->Id. " Prepare failed: ".$@); return (undef); } return ($retval); } =head2 Commit This happens on every transaction. it's always applicable =cut sub Commit { my $self = shift; my $retval = eval $self->ScripObj->CustomCommitCode; if ($@) { $RT::Logger->error("Scrip ".$self->ScripObj->Id. " Commit failed: ".$@); return (undef); } return ($retval); } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/SetCustomFieldToNow.pm�������������������������������������������������������000644 �000765 �000024 �00000005177 14005011336 021542� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Action::SetCustomFieldToNow; use base 'RT::Action'; use strict; use warnings; sub Describe { my $self = shift; return (ref $self . " will set the value of a custom field, provided as the Argument, to the current datetime."); } sub Prepare { # nothing to prepare return 1; } sub Commit { my $self = shift; my $DateObj = RT::Date->new($self->TicketObj->CurrentUser); $DateObj->SetToNow; my $value = $DateObj->ISO(Timezone => 'user'); my ($ret, $msg) = $self->TicketObj->AddCustomFieldValue(Field => $self->Argument, Value => $value); unless ($ret) { RT->Logger->error($msg); } return $ret; } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/ExtractSubjectTag.pm���������������������������������������������������������000644 �000765 �000024 �00000010323 14005011336 021234� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Action::ExtractSubjectTag =head1 DESCRIPTION ExtractSubjectTag is a ScripAction which allows ticket bonding between two RT instances or between RT and other Ticket systems like Siebel or Remedy. By default this ScripAction is set up to run on every transaction on every Correspondence. One can configure this ScripActions behaviour by changing the global C<$ExtractSubjectTagMatch> in C<RT_Config.pm>. If a transaction's subject matches this regexp, we append the match tag to the ticket's current subject. This helps ensure that further communication on the ticket will include the remote system's subject tag. If you modify this code, be careful not to remove the code where it ensures that it only examines remote systems' tags. =head1 EXAMPLE As an example, Siebel will set their subject tag to something like: B<[SR ID:1-554]> To record this tag in the local ticket's subject, we need to change ExtractSubjectTagMatch to something like: Set($ExtractSubjectTagMatch, qr/\[[^\]]+[#:][0-9-]+\]/); =cut package RT::Action::ExtractSubjectTag; use base 'RT::Action'; use strict; use warnings; sub Describe { my $self = shift; return ( ref $self ); } sub Prepare { return (1); } sub Commit { my $self = shift; my $Transaction = $self->TransactionObj; my $FirstAttachment = $Transaction->Attachments->First; return 1 unless $FirstAttachment; my $TransactionSubject = $FirstAttachment->Subject; return 1 unless $TransactionSubject; my $Ticket = $self->TicketObj; my $TicketSubject = $self->TicketObj->Subject; my $origTicketSubject = $TicketSubject; my $match = RT->Config->Get('ExtractSubjectTagMatch'); my $nomatch = RT->Config->Get('ExtractSubjectTagNoMatch'); TAGLIST: while ( $TransactionSubject =~ /($match)/g ) { my $tag = $1; next if $tag =~ /$nomatch/; foreach my $subject_tag ( RT->System->SubjectTag ) { if ($tag =~ /\[\Q$subject_tag\E\s+\#(\d+)\s*\]/) { next TAGLIST; } } $TicketSubject .= " $tag" unless ( $TicketSubject =~ /\Q$tag\E/ ); } $self->TicketObj->SetSubject($TicketSubject) if ( $TicketSubject ne $origTicketSubject ); return (1); } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/SetPriority.pm���������������������������������������������������������������000644 �000765 �000024 �00000004625 14005011336 020153� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Action::SetPriority; use base 'RT::Action'; use strict; use warnings; #Do what we need to do and send it out. #What does this type of Action does sub Describe { my $self = shift; return (ref $self . " will set a ticket's priority to the argument provided."); } sub Prepare { # nothing to prepare return 1; } sub Commit { my $self = shift; $self->TicketObj->SetPriority($self->Argument); } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/SLA_SetDue.pm����������������������������������������������������������������000644 �000765 �000024 �00000011203 14005011336 017534� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Action::SLA_SetDue; use base qw(RT::Action::SLA); =head2 Prepare Checks if the ticket has service level defined. =cut sub Prepare { my $self = shift; unless ( $self->TicketObj->SLA ) { $RT::Logger->error('SLA::SetDue scrip has been applied to ticket #' . $self->TicketObj->id . ' that has no SLA defined'); return 0; } return 1; } =head2 Commit Set the Due date accordingly to SLA. =cut sub Commit { my $self = shift; my $ticket = $self->TicketObj; my $txn = $self->TransactionObj; my $level = $ticket->SLA; my ($last_reply, $is_outside) = $self->LastEffectiveAct; $RT::Logger->debug( 'Last effective '. ($is_outside? '':'non-') .'outside actors\' reply' .' to ticket #'. $ticket->id .' is txn #'. $last_reply->id ); my $response_due = $self->Due( Ticket => $ticket, Level => $level, Type => $is_outside? 'Response' : 'KeepInLoop', Time => $last_reply->CreatedObj->Unix, ); my $resolve_due = $self->Due( Ticket => $ticket, Level => $level, Type => 'Resolve', Time => $ticket->CreatedObj->Unix, ); my $due; $due = $response_due if defined $response_due; $due = $resolve_due unless defined $due; $due = $resolve_due if defined $due && defined $resolve_due && $resolve_due < $due; return $self->SetDateField( Due => $due ); } sub IsOutsideActor { my $self = shift; my $txn = shift || $self->TransactionObj; my $actor = $txn->CreatorObj->PrincipalObj; # owner is always treated as inside actor return 0 if $actor->id == $self->TicketObj->Owner; if ( RT->Config->Get('ServiceAgreements')->{'AssumeOutsideActor'} ) { # All non-admincc users are outside actors return 0 if $self->TicketObj ->AdminCc->HasMemberRecursively( $actor ) or $self->TicketObj->QueueObj->AdminCc->HasMemberRecursively( $actor ); return 1; } else { # Only requestors are outside actors return 1 if $self->TicketObj->Requestors->HasMemberRecursively( $actor ); return 0; } } sub LastEffectiveAct { my $self = shift; my $txns = $self->TicketObj->Transactions; $txns->Limit( FIELD => 'Type', VALUE => 'Correspond' ); $txns->Limit( FIELD => 'Type', VALUE => 'Create' ); $txns->OrderByCols( { FIELD => 'Created', ORDER => 'DESC' }, { FIELD => 'id', ORDER => 'DESC' }, ); my $res; while ( my $txn = $txns->Next ) { unless ( $self->IsOutsideActor( $txn ) ) { last if $res; return ($txn); } $res = $txn; } return ($res, 1); } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/SLA_SetStarts.pm�������������������������������������������������������������000644 �000765 �000024 �00000005651 14005011336 020311� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Action::SLA_SetStarts; use base qw(RT::Action::SLA); =head1 NAME RT::Action::SLA_SetStarts - set starts date field of a ticket according to SLA =head1 DESCRIPTION Look up the SLA of the ticket and set the Starts date accordingly. Nothing happens if the ticket has no SLA defined. Note that this action doesn't check if Starts field is set already, so you can use it to set the field in a force mode or can protect field using a condition that checks value of Starts. =cut sub Prepare { return 1 } sub Commit { my $self = shift; my $ticket = $self->TicketObj; my $level = $ticket->SLA; unless ( $level ) { $RT::Logger->debug('Ticket #'. $ticket->id .' has no service level defined, skip setting Starts'); return 1; } my $starts = $self->Starts( Ticket => $ticket, Level => $level, Time => $ticket->CreatedObj->Unix, ); return $self->SetDateField( Starts => $starts ); } 1; ���������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/LinearEscalate.pm������������������������������������������������������������000644 �000765 �000024 �00000021241 14005011336 020523� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Action::LinearEscalate - will move a ticket's priority toward its final priority. =head1 This vs. RT::Action::EscalatePriority This action doesn't change priority if due date is not set. This action honor the Starts date. This action can apply changes silently. This action can replace EscalatePriority completly. If you want to tickets that have been created without Due date then you can add scrip that sets default due date. For example a week then priorities of your tickets will escalate linearly during the week from intial value towards final. =head1 This vs. LinearEscalate from the CPAN This action is an integration of the module from the CPAN into RT's core that's happened in RT 3.8. If you're upgrading from 3.6 and have been using module from the CPAN with old version of RT then you should uninstall it and use this one. However, this action doesn't support control over config. Read </CONFIGURATION> to find out ways to deal with it. =head1 DESCRIPTION LinearEscalate is a ScripAction that will move a ticket's priority from its initial priority to its final priority linearly as the ticket approaches its due date. It's intended to be called by an RT escalation tool. One such tool is called rt-crontool and is located in $RTHOME/bin (see C<rt-crontool -h> for more details). =head1 USAGE Once the ScripAction is installed, the following script in "cron" will get tickets to where they need to be: rt-crontool --search RT::Search::FromSQL --search-arg \ "(Status='new' OR Status='open' OR Status = 'stalled')" \ --action RT::Action::LinearEscalate The Starts date is associated with intial ticket's priority or the Created field if the former is not set. End of interval is the Due date. Tickets without due date B<are not updated>. =head1 CONFIGURATION Initial and Final priorities are controlled by queue's options and can be defined using the web UI via Admin tab. This action should handle correctly situations when initial priority is greater than final. LinearEscalate's behavior can be controlled by two options: =over 4 =item RecordTransaction - defaults to false and if option is true then causes the tool to create a transaction on the ticket when it is escalated. =item UpdateLastUpdated - which defaults to true and updates the LastUpdated field when the ticket is escalated, otherwise don't touch anything. =back You cannot set "UpdateLastUpdated" to false unless "RecordTransaction" is also false. Well, you can, but we'll just ignore you. You can set this options using either in F<RT_SiteConfig.pm>, as action argument in call to the rt-crontool or in DB if you want to use the action in scrips. From a shell you can use the following command: rt-crontool --search RT::Search::FromSQL --search-arg \ "(Status='new' OR Status='open' OR Status = 'stalled')" \ --action RT::Action::LinearEscalate \ --action-arg "RecordTransaction: 1" This ScripAction uses RT's internal _Set or __Set calls to set ticket priority without running scrips or recording a transaction on each update, if it's been said to. =cut package RT::Action::LinearEscalate; use strict; use warnings; use base qw(RT::Action); #Do what we need to do and send it out. #What does this type of Action does sub Describe { my $self = shift; my $class = ref($self) || $self; return "$class will move a ticket's priority toward its final priority."; } sub Prepare { my $self = shift; my $ticket = $self->TicketObj; unless ( $ticket->DueObj->IsSet ) { $RT::Logger->debug('Due is not set. Not escalating.'); return 1; } my $priority_range = ($ticket->FinalPriority ||0) - ($ticket->InitialPriority ||0); unless ( $priority_range ) { $RT::Logger->debug('Final and Initial priorities are equal. Not escalating.'); return 1; } if ( $ticket->Priority >= $ticket->FinalPriority && $priority_range > 0 ) { $RT::Logger->debug('Current priority is greater than final. Not escalating.'); return 1; } elsif ( $ticket->Priority <= $ticket->FinalPriority && $priority_range < 0 ) { $RT::Logger->debug('Current priority is lower than final. Not escalating.'); return 1; } # TODO: compute the number of business days until the ticket is due # now we know we have a due date. for every day that passes, # increment priority according to the formula my $starts = $ticket->StartsObj->IsSet ? $ticket->StartsObj->Unix : $ticket->CreatedObj->Unix; my $now = time; # do nothing if we didn't reach starts or created date if ( $starts > $now ) { $RT::Logger->debug('Starts(Created) is in future. Not escalating.'); return 1; } my $due = $ticket->DueObj->Unix; $due = $starts + 1 if $due <= $starts; # +1 to avoid div by zero my $percent_complete = ($now-$starts)/($due - $starts); my $new_priority = int($percent_complete * $priority_range) + ($ticket->InitialPriority || 0); $new_priority = $ticket->FinalPriority if $new_priority > $ticket->FinalPriority; $self->{'new_priority'} = $new_priority; return 1; } sub Commit { my $self = shift; my $new_value = $self->{'new_priority'}; return 1 unless defined $new_value; my $ticket = $self->TicketObj; # if the priority hasn't changed do nothing return 1 if $ticket->Priority == $new_value; # override defaults from argument my ($record, $update) = (0, 1); { my $arg = $self->Argument || ''; if ( $arg =~ /RecordTransaction:\s*(\d+)/i ) { $record = $1; $RT::Logger->debug("Overrode RecordTransaction: $record"); } if ( $arg =~ /UpdateLastUpdated:\s*(\d+)/i ) { $update = $1; $RT::Logger->debug("Overrode UpdateLastUpdated: $update"); } $update = 1 if $record; } $RT::Logger->debug( 'Linearly escalating priority of ticket #'. $ticket->Id .' from '. $ticket->Priority .' to '. $new_value .' and'. ($record? '': ' do not') .' record a transaction' .' and'. ($update? '': ' do not') .' touch last updated field' ); my ( $val, $msg ); unless ( $record ) { unless ( $update ) { ( $val, $msg ) = $ticket->__Set( Field => 'Priority', Value => $new_value, ); } else { ( $val, $msg ) = $ticket->_Set( Field => 'Priority', Value => $new_value, RecordTransaction => 0, ); } } else { ( $val, $msg ) = $ticket->SetPriority( $new_value ); } unless ($val) { $RT::Logger->error( "Couldn't set new priority value: $msg" ); return (0, $msg); } return 1; } RT::Base->_ImportOverlays(); 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/AddPriority.pm���������������������������������������������������������������000644 �000765 �000024 �00000004517 14005011336 020110� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Action::AddPriority; use base 'RT::Action'; use strict; use warnings; sub Describe { my $self = shift; return (ref $self . " will increment a ticket's priority by the argument provided."); } sub Prepare { return 1; } sub Commit { my $self = shift; $self->TicketObj->SetPriority($self->TicketObj->Priority + $self->Argument); } RT::Base->_ImportOverlays(); 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/SLA.pm�����������������������������������������������������������������������000644 �000765 �000024 �00000006024 14005011336 016270� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Action::SLA; use base qw(RT::SLA RT::Action); =head1 NAME RT::Action::SLA - base class for all actions in the extension =head1 DESCRIPTION It's not a real action, but container for subclassing which provide help methods for other actions. =head1 METHODS =head2 SetDateField NAME VALUE Sets specified ticket's date field to the value, doesn't update if field is set already. VALUE is unix time. =cut sub SetDateField { my $self = shift; my ($type, $value) = (@_); my $ticket = $self->TicketObj; my $method = $type .'Obj'; if ( defined $value ) { return 1 if $ticket->$method->Unix == $value; } else { return 1 if $ticket->$method->Unix <= 0; } my $date = RT::Date->new( $RT::SystemUser ); $date->Set( Format => 'unix', Value => $value ); $method = 'Set'. $type; return 1 if $ticket->$type eq $date->ISO; my ($status, $msg) = $ticket->$method( $date->ISO ); unless ( $status ) { $RT::Logger->error("Couldn't set $type date: $msg"); return 0; } return 1; } 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/SetStatus.pm�����������������������������������������������������������������000644 �000765 �000024 �00000011303 14005011336 017604� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Action::SetStatus; use strict; use warnings; use base qw(RT::Action); =head1 NAME RT::Action::SetStatus - RT's scrip action to set status of a ticket =head1 DESCRIPTION This action changes status to a new value according to the rules in L</ARGUMENT>. Status is not changed if the transition is invalid or another error occurs. All issues are logged at apropriate levels. =head1 ARGUMENT Argument can be one of the following: =over 4 =item status literally Status is changed from the current value to a new defined by the argument, but only if it's valid status and allowed by transitions of the current lifecycle, for example: * The current status is 'stalled' * Argument of this action is 'open' * The only possible transition in the scheam from 'stalled' is 'open' * Status is changed However, in the example above Status is not changed if argument is anything else as it's just not allowed by the lifecycle. =item 'initial', 'active' or 'inactive' Status is changed from the current value to first possible 'initial', 'active' or 'inactive' correspondingly. First possible value is figured according to transitions to the target set, for example: * The current status is 'open' * Argument of this action is 'inactive' * Possible transitions from 'open' are 'resolved', 'rejected' or 'deleted' * Status is changed to 'resolved' =back =cut sub Prepare { my $self = shift; my $ticket = $self->TicketObj; my $lifecycle = $ticket->LifecycleObj; my $status = $ticket->Status; my $argument = $self->Argument; unless ( $argument ) { $RT::Logger->error("Argument is mandatory for SetStatus action"); return 0; } my $next = ''; if ( $argument =~ /^(initial|active|inactive)$/i ) { my $method = 'Is'. ucfirst lc $argument; ($next) = grep $lifecycle->$method($_), $lifecycle->Transitions($status); unless ( $next ) { $RT::Logger->info("No transition from '$status' to $argument set"); return 1; } } elsif ( $lifecycle->IsValid( $argument ) ) { unless ( $lifecycle->IsTransition( $status => $argument ) ) { $RT::Logger->warning("Transition '$status -> $argument' is not valid"); return 1; } $next = $argument; } else { $RT::Logger->error("Argument for SetStatus action is not valid status or one of set"); return 0; } $self->{'set_status_to'} = $next; return 1; } sub Commit { my $self = shift; return 1 unless my $new_status = $self->{'set_status_to'}; my ($val, $msg) = $self->TicketObj->SetStatus( $new_status ); unless ( $val ) { $RT::Logger->error( "Couldn't set status: ". $msg ); return 0; } return 1; } 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/Autoreply.pm�����������������������������������������������������������������000644 �000765 �000024 �00000006226 14005011336 017641� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Action::Autoreply; use strict; use warnings; use base qw(RT::Action::SendEmail); =head2 Prepare Set up the relevant recipients, then call our parent. =cut sub Prepare { my $self = shift; $self->SetRecipients(); $self->SUPER::Prepare(); } =head2 SetRecipients Sets the recipients of this message to this ticket's Requestor. =cut sub SetRecipients { my $self=shift; push(@{$self->{'To'}}, $self->TicketObj->Requestors->MemberEmailAddresses); return(1); } =head2 SetReturnAddress Set this message's return address to the apropriate queue address =cut sub SetReturnAddress { my $self = shift; my $friendly_name; if (RT->Config->Get('UseFriendlyFromLine')) { $friendly_name = $self->TicketObj->QueueObj->Description || $self->TicketObj->QueueObj->Name; } $self->SUPER::SetReturnAddress( @_, friendly_name => $friendly_name ); } =head2 SetRTSpecialHeaders Set the C<Auto-Generated> header to C<auto-replied>, in accordance with RFC3834. =cut sub SetRTSpecialHeaders { my $self = shift; $self->SUPER::SetRTSpecialHeaders(@_); $self->SetHeader( 'Auto-Submitted', 'auto-replied' ); } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/UpdateParentTimeWorked.pm����������������������������������������������������000644 �000765 �000024 �00000012145 14005011336 022241� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Action::UpdateParentTimeWorked; use base 'RT::Action'; =head1 NAME RT::Action::UpdateParentTimeWorked - RT's scrip action to set/update the time worked on a parent ticket when a child ticket's TimeWorked is added to. =head1 DESCRIPTION This action is used as an action for the 'On TimeWorked Change' condition. When it fires it finds a ticket's parent tickets and increments the Time Worked value on those tickets along with the built-in behavior of incrementing Time Worked on the current ticket. =head2 Important Notes on Operation There are some important details related to the use of this scrip for time tracking that you should take into account when using it. =over =item * Parent and child time entries are combined on the parent This is the intended function of the scrip, but since the parent ticket has only one Time Worked value, it is difficult to differentiate time recorded directly on the parent ticket from time added from child tickets. If you record time only on child tickets, this is typically not an issue. If you record time on the parent ticket in addition to child tickets, it can make it difficult to pull out the time specifically logged on the parent. =item * A ticket must be linked as a child when the time is recorded For this scrip to work properly, a ticket must be linked to the parent as a child before time is entered. If you link a child ticket to a parent and it already has time recorded, that time will not be added to the parent. Similarly, if you remove a child ticket link from a parent, any previously recorded time will remain in the parent's Time Worked value. If you want the time removed, you must subtract it manually. =back RT has a feature called C<$DisplayTotalTimeWorked> that you can activate in your C<RT_SiteConfig.pm> file. This feature dynamically calculates time worked on child tickets and shows it on the parent. This solves the issues above because it doesn't modify the parent's Time Worked value and it will update if children tickets are added or removed. =cut sub Prepare { my $self = shift; my $ticket = $self->TicketObj; return 0 unless $ticket->MemberOf->Count; return 1; } sub Commit { my $self = shift; my $ticket = $self->TicketObj; my $txn = $self->TransactionObj; my $parents = $ticket->MemberOf; my $time_worked = $txn->TimeTaken || ( $txn->NewValue - $txn->OldValue ); while ( my $parent = $parents->Next ) { my $parent_ticket = $parent->TargetObj; my $original_actor = RT::CurrentUser->new( $txn->Creator ); my $actor_parent_ticket = RT::Ticket->new( $original_actor ); $actor_parent_ticket->Load( $parent_ticket->Id ); unless ( $actor_parent_ticket->Id ) { RT->Logger->error("Unable to load ".$parent_ticket->Id." as ".$txn->Creator->Name); return 0; } my ( $ret, $msg ) = $actor_parent_ticket->_Set( Field => 'TimeWorked', Value => $parent_ticket->TimeWorked + $time_worked, ); unless ($ret) { RT->Logger->error( "Failed to update parent ticket's TimeWorked: $msg"); } } } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/SendForward.pm���������������������������������������������������������������000644 �000765 �000024 �00000010147 14005011336 020070� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} # package RT::Action::SendForward; use strict; use warnings; use base qw(RT::Action::SendEmail); use Email::Address; =head2 Prepare =cut sub Prepare { my $self = shift; my $txn = $self->TransactionObj; if ( $txn->Type eq 'Forward Transaction' ) { my $forwarded_txn = RT::Transaction->new( $self->CurrentUser ); $forwarded_txn->Load( $txn->Field ); $self->{ForwardedTransactionObj} = $forwarded_txn; } my ( $result, $message ) = $self->TemplateObj->Parse( Argument => $self->Argument, Ticket => $self->TicketObj, Transaction => $self->ForwardedTransactionObj, ForwardTransaction => $self->TransactionObj, ); if ( !$result ) { return (undef); } my $mime = $self->TemplateObj->MIMEObj; $mime->make_multipart unless $mime->is_multipart; my $entity; if ( $txn->Type eq 'Forward Transaction' ) { $entity = $self->ForwardedTransactionObj->ContentAsMIME; } else { my $txns = $self->TicketObj->Transactions; $txns->Limit( FIELD => 'Type', OPERATOR => 'IN', VALUE => [qw(Create Correspond)], ); $entity = MIME::Entity->build( Type => 'multipart/mixed', Description => 'forwarded ticket', ); $entity->add_part($_) foreach map $_->ContentAsMIME, @{ $txns->ItemsArrayRef }; } $mime->add_part($entity); my $txn_attachment = $self->TransactionObj->Attachments->First; for my $header (qw/From To Cc Bcc/) { if ( $txn_attachment->GetHeader( $header ) ) { $mime->head->replace( $header => Encode::encode( "UTF-8", $txn_attachment->GetHeader($header) ) ); } } if ( RT->Config->Get('ForwardFromUser') ) { $mime->head->replace( 'X-RT-Sign' => 0 ); } $self->SUPER::Prepare(); } sub SetSubjectToken { my $self = shift; return if RT->Config->Get('ForwardFromUser'); $self->SUPER::SetSubjectToken(@_); } sub ForwardedTransactionObj { my $self = shift; return $self->{'ForwardedTransactionObj'}; } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Action/AutoOpen.pm������������������������������������������������������������������000644 �000765 �000024 �00000007500 14005011336 017403� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Action::AutoOpen; use strict; use warnings; use base qw(RT::Action); =head1 DESCRIPTION This action automatically moves a ticket to an active status. Status is not changed if there is no active statuses in the lifecycle. Status is not changed if the current status is first active for ticket's status lifecycle. For example if ticket's status is 'processing' and active statuses are 'processing', 'on hold' and 'waiting' then status is not changed, but for ticket with status 'on hold' other rules are checked. Status is not changed if it's in the list of C<initial> statuses for the current scheme and creator of the current transaction is one of the ticket's requestors. Status is not changed if message's head has field C<RT-Control> with C<no-autoopen> substring. Status is set to the first possible active status. If the ticket's status is C<Stalled> then RT finds all possible transitions from C<Stalled> status and selects first one that results in the ticket having an active status. =cut sub Prepare { my $self = shift; my $ticket = $self->TicketObj; my $next = $ticket->FirstActiveStatus; return 1 unless defined $next; # no change if the ticket is in initial status and the message is a mail # from a requestor return 1 if $ticket->LifecycleObj->IsInitial($ticket->Status) && $self->TransactionObj->IsInbound; if ( my $msg = $self->TransactionObj->Message->First ) { return 1 if ($msg->GetHeader('RT-Control') || '') =~ /\bno-autoopen\b/i; } $self->{'set_status_to'} = $next; return 1; } sub Commit { my $self = shift; return 1 unless my $new_status = $self->{'set_status_to'}; my ($val, $msg) = $self->TicketObj->SetStatus( $new_status ); unless ( $val ) { $RT::Logger->error( "Couldn't auto-open ticket: ". $msg ); return 0; } return 1; } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Shredder/Constants.pm���������������������������������������������������������������000644 �000765 �000024 �00000007035 14005011336 020153� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Shredder::Constants; use strict; use warnings; =head1 NAME RT::Shredder::Constants - RT::Shredder constants that is used to mark state of RT objects. =head1 DESCRIPTION This module contains two group of bit constants. First group is group of flags which are used to clarify dependecies between objects, and second group is states of RT objects in Shredder cache. =head1 FLAGS =head2 DEPENDS_ON Targets that has such dependency flag set should be wiped out with base object. =head2 WIPE_AFTER If dependency has such flag then target object would be wiped only after base object. You should mark dependencies with this flag if two objects depends on each other, for example Group and Principal have such relationship, this mean Group depends on Principal record and that Principal record depends on the same Group record. Other examples: User and Principal, User and its ACL equivalence group. =head2 VARIABLE This flag is used to mark dependencies that can be resolved with changing value in target object. For example ticket can be created by user we can change this reference when we delete user. =cut use constant { DEPENDS_ON => 0x001, WIPE_AFTER => 0x002, VARIABLE => 0x004, }; =head1 STATES =head2 ON_STACK Default state of object in Shredder cache that means that object is loaded and placed into cache. =head2 WIPED Objects with this state are not exist any more in DB, but perl object is still in memory. This state is used to be shure that delete query is called once. =cut use constant { ON_STACK => 0x000, IN_WIPING => 0x010, WIPED => 0x020, }; 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Shredder/Plugin/��������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 017072� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Shredder/POD.pm���������������������������������������������������������������������000644 �000765 �000024 �00000013646 14005011336 016626� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Shredder::POD; use strict; use warnings; use Pod::Select; use Pod::PlainText; sub plugin_html { my ($file, $out_fh) = @_; my $parser = RT::Shredder::POD::HTML->new; $parser->select('SYNOPSIS', 'ARGUMENTS', 'USAGE'); $parser->parse_from_file( $file, $out_fh ); return; } sub plugin_cli { my ($file, $out_fh, $no_name) = @_; local @Pod::PlainText::ISA = ('Pod::Select', @Pod::PlainText::ISA); my $parser = Pod::PlainText->new(); $parser->select('SYNOPSIS', 'ARGUMENTS', 'USAGE'); $parser->add_selection('NAME') unless $no_name; $parser->parse_from_file( $file, $out_fh ); return; } sub shredder_cli { my ($file, $out_fh) = @_; local @Pod::PlainText::ISA = ('Pod::Select', @Pod::PlainText::ISA); my $parser = Pod::PlainText->new(); $parser->select('NAME', 'SYNOPSIS', 'USAGE', 'OPTIONS'); $parser->parse_from_file( $file, $out_fh ); return; } # Extract the help foer each argument from the plugin POD # they must be on a =head2 line in the ARGUMENTS section of the POD # the return value is a hashref: # keys are the argument names, # values are hash_refs: { name => <ucfirst argument name>, # type => <from the head line>, # help => <first paragraph from the POD> # } sub arguments_help { my ($file) = @_; my $text; open( my $io_handle, ">:scalar", \$text ) or die "Can't open scalar for write: $!"; my $parser = RT::Shredder::POD::HTML->new; $parser->select('ARGUMENTS'); $parser->parse_from_file( $file, $io_handle ); my $arguments_help = {}; while( $text=~ m{<h4[^>]*> # argument description starts with an h4 title \s*(\S*) # argument name ($1) \s*-\s* ([^<]*) # argument type ($2) </h4>\s* (?:<p[^>]*>\s* (.*?) # help: the first paragraph of the POD ($3) (?=</p>) )? }gsx ) { my( $arg, $arg_name, $type, $help)= ( lc( $1), $1, $2, $3 || ''); $arguments_help->{$arg}= { name => $arg_name, type => $type, help => $help }; } return $arguments_help; } 1; package RT::Shredder::POD::HTML; use base qw(Pod::Select); sub command { my( $self, $command, $paragraph, $line_num ) = @_; my $tag; # =head1 => h3, =head2 => h4 if ($command =~ /^head(\d+)$/) { my $h_level = $1 + 2; $tag = "h$h_level"; } my $out_fh = $self->output_handle(); my $expansion = $self->interpolate($paragraph, $line_num); $expansion =~ s/^\s+|\s+$//; $expansion = lc( $expansion ); $expansion = ucfirst( $expansion ); print $out_fh "<$tag class=\"rt-general-header1\">" if $tag eq 'h3'; print $out_fh "<$tag class=\"rt-general-header2\">" if $tag eq 'h4'; print $out_fh $expansion; print $out_fh "</$tag>" if $tag; print $out_fh "\n"; return; } sub verbatim { my ($self, $paragraph, $line_num) = @_; my $out_fh = $self->output_handle(); print $out_fh "<pre class=\"rt-general-paragraph\">"; print $out_fh $paragraph; print $out_fh "</pre>"; print $out_fh "\n"; return; } sub textblock { my ($self, $paragraph, $line_num) = @_; my $out_fh = $self->output_handle(); my $expansion = $self->interpolate($paragraph, $line_num); $expansion =~ s/^\s+|\s+$//; print $out_fh "<p class=\"rt-general-paragraph\">"; print $out_fh $expansion; print $out_fh "</p>"; print $out_fh "\n"; return; } sub interior_sequence { my ($self, $seq_command, $seq_argument) = @_; ## Expand an interior sequence; sample actions might be: return "<b>$seq_argument</b>" if $seq_command eq 'B'; return "<i>$seq_argument</i>" if $seq_command eq 'I'; return "<tt>$seq_argument</tt>" if $seq_command eq 'C'; return "<span class=\"pod-sequence-$seq_command\">$seq_argument</span>"; } 1; ������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Shredder/Exceptions.pm��������������������������������������������������������������000644 �000765 �000024 �00000007274 14005011336 020325� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Shredder::Exception; use warnings; use strict; use Exception::Class; use base qw(Exception::Class::Base); BEGIN { __PACKAGE__->NoRefs(0); } #sub NoRefs { return 0 } sub show_trace { return 1 } package RT::Shredder::Exception::Info; use base qw(RT::Shredder::Exception); my %DESCRIPTION = ( DependenciesLimit => <<END, Dependencies list has reached its limit. See \$RT::DependenciesLimit in RT::Shredder docs. END SystemObject => <<END, System object was selected for deletion, shredder couldn't do that because system would be unusable then. END CouldntLoadObject => <<END, Shredder couldn't load object. Most likely it's not a fatal error. Perhaps you've used the Objects plugin and asked to delete an object that doesn't exist in the system. If you think that your request was correct and it's a problem of the Shredder then you can get a full error message from RT log files and send a bug report. END NoResolver => <<END, Shredder has found a dependency that it cannot automatically resolve, so the requested object was not removed. Some plugins do not automatically shred dependent objects for safety, but you may be able to shred the dependent objects directly using other plugins. The documentation for this plugin may have more information. END ); sub Fields { return ((shift)->SUPER::Fields(@_), 'tag') } sub tag { return (shift)->{'tag'} } sub full_message { my $self = shift; my $error = $self->message; if ( my $tag = $self->tag ) { my $message = $DESCRIPTION{ $self->tag } || ''; warn "Tag '$tag' doesn't exist" unless $message; $message .= "\nAdditional info:\n$error" if $error; return $message; } return $DESCRIPTION{$error} || $error; } sub show_trace { return 0 } 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Shredder/Plugin.pm������������������������������������������������������������������000644 �000765 �000024 �00000015437 14005011336 017442� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Shredder::Plugin; use strict; use warnings FATAL => 'all'; use File::Spec (); =head1 NAME RT::Shredder::Plugin - interface to access shredder plugins =head1 SYNOPSIS use RT::Shredder::Plugin; # get list of the plugins my %plugins = RT::Shredder::Plugin->List; # load plugin by name my $plugin = RT::Shredder::Plugin->new; my( $status, $msg ) = $plugin->LoadByName( 'Tickets' ); unless( $status ) { print STDERR "Couldn't load plugin 'Tickets': $msg\n"; exit(1); } # load plugin by preformatted string my $plugin = RT::Shredder::Plugin->new; my( $status, $msg ) = $plugin->LoadByString( 'Tickets=status,deleted' ); unless( $status ) { print STDERR "Couldn't load plugin: $msg\n"; exit(1); } =head1 METHODS =head2 new Object constructor, returns new object. Takes optional hash as arguments, it's not required and this class doesn't use it, but plugins could define some arguments and can handle them after your've load it. =cut sub new { my $proto = shift; my $self = bless( {}, ref $proto || $proto ); $self->_Init( @_ ); return $self; } sub _Init { my $self = shift; my %args = ( @_ ); $self->{'opt'} = \%args; return; } =head2 List Returns hash with names of the available plugins as keys and path to library files as values. Method has no arguments. Can be used as class method too. Takes optional argument C<type> and leaves in the result hash only plugins of that type. =cut sub List { my $self = shift; my $type = shift; my @files; foreach my $root( @INC ) { my $mask = File::Spec->catfile( $root, qw(RT Shredder Plugin *.pm) ); push @files, glob $mask; } my %res; for my $f (reverse @files) { $res{$1} = $f if $f =~ /([^\\\/]+)\.pm$/; } return %res unless $type; delete $res{'Base'}; foreach my $name( keys %res ) { my $class = join '::', qw(RT Shredder Plugin), $name; unless( $class->require ) { delete $res{ $name }; next; } next if lc $class->Type eq lc $type; delete $res{ $name }; } return %res; } =head2 LoadByName Takes name of the plugin as first argument, loads plugin, creates new plugin object and reblesses self into plugin if all steps were successfuly finished, then you don't need to create new object for the plugin. Other arguments are sent to the constructor of the plugin (method new.) Returns C<$status> and C<$message>. On errors status is C<false> value. In scalar context, returns $status only. =cut sub LoadByName { my $self = shift; my $name = shift or return (0, "Name not specified"); $name =~ /^\w+(::\w+)*$/ or return (0, "Invalid plugin name"); my $plugin = "RT::Shredder::Plugin::$name"; $plugin->require or return( 0, "Failed to load $plugin" ); return wantarray ? ( 0, "Plugin '$plugin' has no method new") : 0 unless $plugin->can('new'); my $obj = eval { $plugin->new( @_ ) }; return wantarray ? ( 0, $@ ) : 0 if $@; return wantarray ? ( 0, 'constructor returned empty object' ) : 0 unless $obj; $self->Rebless( $obj ); return wantarray ? ( 1, "successfuly load plugin" ) : 1; } =head2 LoadByString Takes formatted string as first argument and which is used to load plugin. The format of the string is <plugin name>[=<arg>,<val>[;<arg>,<val>]...] exactly like in the L<rt-shredder> script. All other arguments are sent to the plugins constructor. Method does the same things as C<LoadByName>, but also checks if the plugin supports arguments and values are correct, so you can C<Run> specified plugin immediatly. Returns list with C<$status> and C<$message>. On errors status is C<false>. =cut sub LoadByString { my $self = shift; my ($plugin, $args) = split /=/, ( shift || '' ), 2; my ($status, $msg) = $self->LoadByName( $plugin, @_ ); return( $status, $msg ) unless $status; my %args; foreach( split /\s*;\s*/, ( $args || '' ) ) { my( $k,$v ) = split /\s*,\s*/, ( $_ || '' ), 2; unless( $args{$k} ) { $args{$k} = $v; next; } $args{$k} = [ $args{$k} ] unless UNIVERSAL::isa( $args{ $k }, 'ARRAY'); push @{ $args{$k} }, $v; } ($status, $msg) = $self->HasSupportForArgs( keys %args ); return( $status, $msg ) unless $status; ($status, $msg) = $self->TestArgs( %args ); return( $status, $msg ) unless $status; return( 1, "successfuly load plugin" ); } =head2 Rebless Instance method that takes one object as argument and rebless the current object into into class of the argument and copy data of the former. Returns nothing. Method is used by C<Load*> methods to automaticaly rebless C<RT::Shredder::Plugin> object into class of the loaded plugin. =cut sub Rebless { my( $self, $obj ) = @_; bless( $self, ref $obj ); %{$self} = %{$obj}; return; } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Shredder/Dependency.pm��������������������������������������������������������������000644 �000765 �000024 �00000006603 14005011336 020255� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Shredder::Dependency; use strict; use warnings; use RT::Shredder::Constants; use RT::Shredder::Exceptions; my %FlagDescs = ( RT::Shredder::Constants::DEPENDS_ON, 'depends on', RT::Shredder::Constants::VARIABLE, 'resolvable dependency', RT::Shredder::Constants::WIPE_AFTER, 'delete after', ); sub new { my $proto = shift; my $self = bless( {}, ref $proto || $proto ); $self->Set( @_ ); return $self; } sub Set { my $self = shift; my %args = ( Flags => RT::Shredder::Constants::DEPENDS_ON, @_ ); my @keys = qw(Flags BaseObject TargetObject); @$self{ @keys } = @args{ @keys }; return; } sub AsString { my $self = shift; my $res = $self->BaseObject->UID; $res .= " ". $self->FlagsAsString; $res .= " ". $self->TargetObject->UID; return $res; } sub Flags { return $_[0]->{'Flags'} } sub FlagsAsString { my $self = shift; my @res = (); foreach ( sort keys %FlagDescs ) { if( $self->Flags() & $_ ) { push( @res, $FlagDescs{ $_ } ); } } push @res, 'no flags' unless( @res ); return "(" . join( ',', @res ) . ")"; } sub BaseObject { return $_[0]->{'BaseObject'} } sub TargetObject { return $_[0]->{'TargetObject'} } sub Object { return shift()->{ ({@_})->{Type}. "Object" } } sub TargetClass { return ref $_[0]->{'TargetObject'} } sub BaseClass { return ref $_[0]->{'BaseObject'} } sub Class { return ref shift()->Object( @_ ) } 1; �����������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Shredder/Dependencies.pm������������������������������������������������������������000644 �000765 �000024 �00000010365 14005011336 020565� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Shredder::Dependencies; use strict; use warnings; use RT::Shredder::Exceptions; use RT::Shredder::Constants; use RT::Shredder::Dependency; use RT::Record; =head1 METHODS =head2 new Creates new empty collection of dependecies. =cut sub new { my $proto = shift; my $self = bless( {}, ref $proto || $proto ); $self->{'list'} = []; return $self; } =head2 _PushDependencies Put in objects into collection. Takes BaseObject - any supported object of RT::Record subclass; Flags - flags that describe relationship between target and base objects; TargetObjects - any of RT::SearchBuilder or RT::Record subclassed objects or array ref on list of this objects; Shredder - RT::Shredder object. SeeAlso: _PushDependecy, RT::Shredder::Dependency =cut sub _PushDependencies { my $self = shift; my %args = ( TargetObjects => undef, Shredder => undef, @_ ); my @objs = $args{'Shredder'}->CastObjectsToRecords( Objects => delete $args{'TargetObjects'} ); $self->_PushDependency( %args, TargetObject => $_ ) foreach @objs; return; } sub _PushDependency { my $self = shift; my %args = ( BaseObject => undef, Flags => undef, TargetObject => undef, Shredder => undef, @_ ); my $rec = $args{'Shredder'}->PutObject( Object => $args{'TargetObject'} ); return if $rec->{'State'} & RT::Shredder::Constants::WIPED; # there is no object anymore push @{ $self->{'list'} }, RT::Shredder::Dependency->new( BaseObject => $args{'BaseObject'}, Flags => $args{'Flags'}, TargetObject => $rec->{'Object'}, ); if( scalar @{ $self->{'list'} } > ( $RT::DependenciesLimit || 1000 ) ) { RT::Shredder::Exception::Info->throw( 'DependenciesLimit' ); } return; } =head2 List =cut sub List { my $self = shift; my %args = ( WithFlags => undef, WithoutFlags => undef, Callback => undef, @_ ); my $wflags = delete $args{'WithFlags'}; my $woflags = delete $args{'WithoutFlags'}; return map $args{'Callback'}? $args{'Callback'}->($_): $_, grep !defined( $wflags ) || ($_->Flags & $wflags) == $wflags, grep !defined( $woflags ) || !($_->Flags & $woflags), @{ $self->{'list'} }; } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Shredder/Plugin/SQLDump.pm����������������������������������������������������������000644 �000765 �000024 �00000006152 14005011336 020721� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Shredder::Plugin::SQLDump; use strict; use warnings; use base qw(RT::Shredder::Plugin::Base::Dump); use RT::Shredder; sub AppliesToStates { return 'after wiping dependencies' } sub SupportArgs { my $self = shift; return $self->SUPER::SupportArgs, qw(file_name from_storage); } sub TestArgs { my $self = shift; my %args = @_; $args{'from_storage'} = 1 unless defined $args{'from_storage'}; my $file = $args{'file_name'} = RT::Shredder->GetFileName( FileName => $args{'file_name'}, FromStorage => delete $args{'from_storage'}, ); open $args{'file_handle'}, ">:raw", $file or return (0, "Couldn't open '$file' for write: $!"); return $self->SUPER::TestArgs( %args ); } sub FileName { return $_[0]->{'opt'}{'file_name'} } sub FileHandle { return $_[0]->{'opt'}{'file_handle'} } sub Run { my $self = shift; return (0, 'no handle') unless my $fh = $self->{'opt'}{'file_handle'}; my %args = ( Object => undef, @_ ); my $query = $args{'Object'}->_AsInsertQuery; $query .= "\n" unless $query =~ /\n$/; return 1 if print $fh $query; return (0, "Couldn't write to filehandle"); } 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Shredder/Plugin/Tickets.pm����������������������������������������������������������000644 �000765 �000024 �00000011774 14005011336 021050� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Shredder::Plugin::Tickets; use strict; use warnings FATAL => 'all'; use base qw(RT::Shredder::Plugin::Base::Search); =head1 NAME RT::Shredder::Plugin::Tickets - search plugin for wiping tickets. =head1 ARGUMENTS =head2 query - query string Search tickets with query string. Examples: Queue = 'my queue' AND ( Status = 'deleted' OR Status = 'rejected' ) LastUpdated < '2003-12-31 23:59:59' B<Hint:> You can construct query with the query builder in RT's web interface and then open advanced page and copy query string. Arguments C<queue>, C<status> and C<updated_before> have been dropped as you can easy make the same search with the C<query> option. See examples above. =head2 with_linked - boolean Deletes all tickets that are linked to tickets that match L<query>. =head2 apply_query_to_linked - boolean Delete linked tickets only if those too match L<query>. See also L<with_linked>. =cut sub SupportArgs { return $_[0]->SUPER::SupportArgs, qw(query with_linked apply_query_to_linked) } # used to genrate checkboxes instead of text fields in the web interface sub ArgIsBoolean { my( $self, $arg ) = @_; my %boolean_atts = map { $_ => 1 } qw( with_linked apply_query_to_linked ); return $boolean_atts{$arg}; } sub TestArgs { my $self = shift; my %args = @_; my $queue; if( $args{'query'} ) { my $objs = RT::Tickets->new( RT->SystemUser ); $objs->{'allow_deleted_search'} = 1; my ($status, $msg) = $objs->FromSQL( $args{'query'} ); return( 0, "Bad query argument, error: $msg" ) unless $status; $self->{'opt'}{'objects'} = $objs; } $args{'with_linked'} = 1 if $args{'apply_query_to_linked'}; return $self->SUPER::TestArgs( %args ); } sub Run { my $self = shift; my $objs = $self->{'opt'}{'objects'} or return (1, undef); $objs->OrderByCols( { FIELD => 'id', ORDER => 'ASC' } ); unless ( $self->{'opt'}{'with_linked'} ) { if( $self->{'opt'}{'limit'} ) { $objs->RowsPerPage( $self->{'opt'}{'limit'} ); } return (1, $objs); } my (@top, @linked, %seen); $self->FetchNext($objs, 1); while ( my $obj = $self->FetchNext( $objs ) ) { next if $seen{ $obj->id }++; push @linked, $self->GetLinked( Object => $obj, Seen => \%seen ); push @top, $obj; last if $self->{'opt'}{'limit'} && @top >= $self->{'opt'}{'limit'}; } return (1, @top, @linked); } sub GetLinked { my $self = shift; my %arg = @_; my @res = (); my $query = 'Linked = '. $arg{'Object'}->id; if ( $self->{'opt'}{'apply_query_to_linked'} ) { $query .= " AND ( ". $self->{'opt'}{'query'} ." )"; } my $objs = RT::Tickets->new( RT->SystemUser ); $objs->{'allow_deleted_search'} = 1; $objs->FromSQL( $query ); $self->FetchNext( $objs, 1 ); while ( my $linked_obj = $self->FetchNext( $objs ) ) { next if $arg{'Seen'}->{ $linked_obj->id }++; push @res, $self->GetLinked( %arg, Object => $linked_obj ); push @res, $linked_obj; } return @res; } 1; ����rt-5.0.1/lib/RT/Shredder/Plugin/Users.pm������������������������������������������������������������000644 �000765 �000024 �00000027716 14005011336 020546� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Shredder::Plugin::Users; use strict; use warnings FATAL => 'all'; use base qw(RT::Shredder::Plugin::Base::Search); =head1 NAME RT::Shredder::Plugin::Users - search plugin for wiping users. =head1 SYNOPSIS This shredder plugin removes user records from the RT system. Since users can be connected to many other objects in RT, these connections must be resolved in some way before removing a user record. RT will not automatically shred linked objects (i.e., tickets, transactions, attachments, etc.) when attempting to shred a user. RT provides the following options for handling linked objects. =head2 Replace User This option locates all records currently referenced by the user you wish to shred and replaces the reference with a reference to the user record you provide. This can be another real user or a placeholder user. Once these references are updated, the original user can be shredded. You can use this option with the replace_relations argument. =head2 Delete Objects First If you wish to completely remove all records associated with a user, you can perform searches and remove objects using other shredder plugins first. For example, if the user is a requestor on one or more tickets, first use the Tickets shredder plugin and search for "Requestor.EmailAddress = 'user1@example.com'". Once all associated objects are removed, you may then use the users plugin to shred the user record. If you receive an error when trying to shred a user, it's possible it is still reference by other objects in RT. =head1 ARGUMENTS =head2 status - string Status argument allow you to limit result set to C<disabled>, C<enabled> or C<any> users. B<< Default value is C<disabled>. >> =head2 name - mask User name mask. =head2 email - mask Email address mask. =head2 member_of - group identifier Using this option users that are members of a particular group can be selected for deletion. Identifier is name of user defined group or id of a group, as well C<Privileged> or C<unprivileged> can used to select people from system groups. =head2 not_member_of - group identifier Like member_of, but selects users who are not members of the provided group. =head2 replace_relations - user identifier When you delete a user there could be minor links to them in the RT database. This option allow you to replace these links with links to the new user. The replaceable links are Creator and LastUpdatedBy, but NOT any watcher roles. This means that if the user is a watcher(Requestor, Owner, Cc or AdminCc) of the ticket or queue then the link would be deleted. This argument could be a user id or name. =head2 no_tickets - boolean If true then plugin looks for users who are not watchers (Owners, Requestors, Ccs or AdminCcs) of any ticket. Before RT 3.8.5, users who were watchers of deleted tickets B<will be deleted> when this option was enabled. Decision has been made that it's not correct and you should either shred these deleted tickets, change watchers or explicitly delete user by name or email. Note that found users still B<may have relations> with other objects, for example via Creator or LastUpdatedBy fields, and you most probably want to use C<replace_relations> option. =head2 no_ticket_transactions - boolean If true then plugin looks for users who have created no ticket transactions. This is especially useful after wiping out tickets. Note that found users still B<may have relations> with other objects, for example via Creator or LastUpdatedBy fields, and you most probably want to use C<replace_relations> option. =cut sub SupportArgs { return ( $_[0]->SUPER::SupportArgs, qw( status name email member_of not_member_of replace_relations no_tickets no_ticket_transactions ) ); } # used to genrate checkboxes instead of text fields in the web interface sub ArgIsBoolean { my( $self, $arg ) = @_; my %boolean_atts = map { $_ => 1 } qw( no_tickets no_ticket_transactions ); return $boolean_atts{$arg}; } sub TestArgs { my $self = shift; my %args = @_; if( $args{'status'} ) { unless( $args{'status'} =~ /^(disabled|enabled|any)$/i ) { return (0, "Status '$args{'status'}' is unsupported."); } } else { $args{'status'} = 'disabled'; } if( $args{'email'} ) { $args{'email'} = $self->ConvertMaskToSQL( $args{'email'} ); } if( $args{'name'} ) { $args{'name'} = $self->ConvertMaskToSQL( $args{'name'} ); } if( $args{'member_of'} or $args{'not_member_of'} ) { foreach my $group_option ( qw(member_of not_member_of) ){ next unless $args{$group_option}; my $group = RT::Group->new( RT->SystemUser ); if ( $args{$group_option} =~ /^(Everyone|Privileged|Unprivileged)$/i ) { $group->LoadSystemInternalGroup( $args{$group_option} ); } else { $group->LoadUserDefinedGroup( $args{$group_option} ); } unless ( $group->id ) { return (0, "Couldn't load group '$args{$group_option}'" ); } $args{$group_option} = $group->id; } } if( $args{'replace_relations'} ) { my $uid = $args{'replace_relations'}; # XXX: it's possible that SystemUser is not available my $user = RT::User->new( RT->SystemUser ); $user->Load( $uid ); unless( $user->id ) { return (0, "Couldn't load user '$uid'" ); } $args{'replace_relations'} = $user->id; } return $self->SUPER::TestArgs( %args ); } sub Run { my $self = shift; my %args = ( Shredder => undef, @_ ); my $objs = RT::Users->new( RT->SystemUser ); # XXX: we want preload only things we need, but later while # logging we need all data, TODO envestigate this # $objs->Columns(qw(id Name EmailAddress Lang Timezone # Creator Created LastUpdated LastUpdatedBy)); if( my $s = $self->{'opt'}{'status'} ) { if( $s eq 'any' ) { $objs->FindAllRows; } elsif( $s eq 'disabled' ) { $objs->LimitToDeleted; } else { $objs->LimitToEnabled; } } if( $self->{'opt'}{'email'} ) { $objs->Limit( FIELD => 'EmailAddress', OPERATOR => 'MATCHES', VALUE => $self->{'opt'}{'email'}, ); } if( $self->{'opt'}{'name'} ) { $objs->Limit( FIELD => 'Name', OPERATOR => 'MATCHES', VALUE => $self->{'opt'}{'name'}, CASESENSITIVE => 0, ); } if( $self->{'opt'}{'member_of'} ) { $objs->MemberOfGroup( $self->{'opt'}{'member_of'} ); } my @filter; if( $self->{'opt'}{'not_member_of'} ) { push @filter, $self->FilterNotMemberOfGroup( Shredder => $args{'Shredder'}, GroupId => $self->{'opt'}{'not_member_of'}, ); } if( $self->{'opt'}{'no_tickets'} ) { push @filter, $self->FilterWithoutTickets( Shredder => $args{'Shredder'}, ); } if( $self->{'opt'}{'no_ticket_transactions'} ) { push @filter, $self->FilterWithoutTicketTransactions( Shredder => $args{'Shredder'}, ); } if (@filter) { $self->FetchNext( $objs, 'init' ); my @res; USER: while ( my $user = $self->FetchNext( $objs ) ) { for my $filter (@filter) { next USER unless $filter->($user); } push @res, $user; last if $self->{'opt'}{'limit'} && @res >= $self->{'opt'}{'limit'}; } $objs = \@res; } elsif ( $self->{'opt'}{'limit'} ) { $objs->RowsPerPage( $self->{'opt'}{'limit'} ); } return (1, $objs); } sub SetResolvers { my $self = shift; my %args = ( Shredder => undef, @_ ); if( $self->{'opt'}{'replace_relations'} ) { my $uid = $self->{'opt'}{'replace_relations'}; my $resolver = sub { my %args = (@_); my $t = $args{'TargetObject'}; foreach my $method ( qw(Creator LastUpdatedBy) ) { next unless $t->_Accessible( $method => 'read' ); $t->__Set( Field => $method, Value => $uid ); } }; $args{'Shredder'}->PutResolver( BaseClass => 'RT::User', Code => $resolver ); } return (1); } sub FilterNotMemberOfGroup { my $self = shift; my %args = ( Shredder => undef, GroupId => undef, @_, ); my $group = RT::Group->new(RT->SystemUser); $group->Load($args{'GroupId'}); return sub { my $user = shift; not $group->HasMemberRecursively($user->id); }; } sub FilterWithoutTickets { my $self = shift; my %args = ( Shredder => undef, Objects => undef, @_, ); return sub { my $user = shift; $self->_WithoutTickets( $user ) }; } sub _WithoutTickets { my ($self, $user) = @_; return unless $user and $user->Id; my $tickets = RT::Tickets->new( RT->SystemUser ); $tickets->{'allow_deleted_search'} = 1; $tickets->FromSQL( 'Watcher.id = '. $user->id ); # we could use the Count method which counts all records # that match, but we really want to know only that # at least one record exists, so this is faster $tickets->RowsPerPage(1); return !$tickets->First; } sub FilterWithoutTicketTransactions { my $self = shift; my %args = ( Shredder => undef, Objects => undef, @_, ); return sub { my $user = shift; $self->_WithoutTicketTransactions( $user ) }; } sub _WithoutTicketTransactions { my ($self, $user) = @_; return unless $user and $user->Id; my $txns = RT::Transactions->new( RT->SystemUser ); $txns->Limit(FIELD => 'ObjectType', VALUE => 'RT::Ticket'); $txns->Limit(FIELD => 'Creator', VALUE => $user->Id); # we could use the Count method which counts all records # that match, but we really want to know only that # at least one record exists, so this is faster $txns->RowsPerPage(1); return !$txns->First; } 1; ��������������������������������������������������rt-5.0.1/lib/RT/Shredder/Plugin/Summary.pm����������������������������������������������������������000644 �000765 �000024 �00000012754 14005011336 021076� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Shredder::Plugin::Summary; use strict; use warnings FATAL => 'all'; use base qw(RT::Shredder::Plugin::SQLDump); sub AppliesToStates { return 'before any action' } sub TestArgs { my $self = shift; my %args = (file_name => '', @_); unless( $args{'file_name'} ) { require POSIX; $args{'file_name'} = POSIX::strftime( "summary-%Y%m%dT%H%M%S.XXXX.txt", gmtime ); } return $self->SUPER::TestArgs( %args ); } sub Run { my $self = shift; my %args = ( Object => undef, @_ ); my $class = ref $args{'Object'}; $class =~ s/^RT:://; $class =~ s/:://g; my $method = 'WriteDown'. $class; $method = 'WriteDownDefault' unless $self->can($method); return $self->$method( %args ); } my %skip_refs_to = (); sub WriteDownDefault { my $self = shift; my %args = ( Object => undef, @_ ); return $self->_WriteDownHash( $args{'Object'}, $self->_MakeHash( $args{'Object'} ), ); } # TODO: cover other objects # ACE.pm # Attachment.pm # CustomField.pm # CustomFieldValue.pm # GroupMember.pm # Group.pm # Link.pm # ObjectCustomFieldValue.pm # Principal.pm # Queue.pm # Ticket.pm # User.pm # ScripAction.pm - works fine with defaults # ScripCondition.pm - works fine with defaults # Template.pm - works fine with defaults sub WriteDownCachedGroupMember { return 1 } sub WriteDownPrincipal { return 1 } sub WriteDownGroup { my $self = shift; my %args = ( Object => undef, @_ ); if ( $args{'Object'}->RoleClass ) { return $skip_refs_to{ $args{'Object'}->UID } = 1; } return $self->WriteDownDefault( %args ); } sub WriteDownTransaction { my $self = shift; my %args = ( Object => undef, @_ ); my $props = $self->_MakeHash( $args{'Object'} ); $props->{'Object'} = delete $props->{'ObjectType'}; $props->{'Object'} .= '-'. delete $props->{'ObjectId'} if $props->{'ObjectId'}; return 1 if $skip_refs_to{ $props->{'Object'} }; delete $props->{$_} foreach grep !defined $props->{$_} || $props->{$_} eq '', keys %$props; return $self->_WriteDownHash( $args{'Object'}, $props ); } sub WriteDownScrip { my $self = shift; my %args = ( Object => undef, @_ ); my $props = $self->_MakeHash( $args{'Object'} ); $props->{'Action'} = $args{'Object'}->ActionObj->Name; $props->{'Condition'} = $args{'Object'}->ConditionObj->Name; $props->{'Template'} = $args{'Object'}->Template; $props->{'Queue'} = $args{'Object'}->QueueObj->Name || 'global'; return $self->_WriteDownHash( $args{'Object'}, $props ); } sub _MakeHash { my ($self, $obj) = @_; my $hash = $self->__MakeHash( $obj ); foreach (grep exists $hash->{$_}, qw(Creator LastUpdatedBy)) { my $method = $_ .'Obj'; my $u = $obj->$method(); $hash->{ $_ } = $u->EmailAddress || $u->Name || $u->UID; } return $hash; } sub __MakeHash { my ($self, $obj) = @_; my %hash; $hash{ $_ } = $obj->$_() foreach sort keys %{ $obj->_ClassAccessible }; return \%hash; } sub _WriteDownHash { my ($self, $obj, $hash) = @_; return (0, 'no handle') unless my $fh = $self->{'opt'}{'file_handle'}; print $fh "=== ". $obj->UID ." ===\n" or return (0, "Couldn't write to filehandle"); foreach my $key( sort keys %$hash ) { my $val = $hash->{ $key }; next unless defined $val; $val =~ s/\n/\n /g; print $fh $key .': '. $val ."\n" or return (0, "Couldn't write to filehandle"); } print $fh "\n" or return (0, "Couldn't write to filehandle"); return 1; } 1; ��������������������rt-5.0.1/lib/RT/Shredder/Plugin/Attachments.pm������������������������������������������������������000644 �000765 �000024 �00000011153 14005011336 021704� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Shredder::Plugin::Attachments; use strict; use warnings FATAL => 'all'; use base qw(RT::Shredder::Plugin::Base::Search); =head1 NAME RT::Shredder::Plugin::Attachments - search plugin for wiping attachments. =head1 ARGUMENTS =head2 files_only - boolean value Search only file attachments. =head2 file - mask Search files with specific file name only. Example: '*.xl?' or '*.gif' =head2 longer - attachment content size Search attachments which content is longer than specified. You can use trailing 'K' or 'M' character to specify size in kilobytes or megabytes. =cut sub SupportArgs { return $_[0]->SUPER::SupportArgs, qw(files_only file longer) } # used to generate checkboxes instead of text fields in the web interface sub ArgIsBoolean { my( $self, $arg ) = @_; my %boolean_atts = map { $_ => 1 } qw( files_only ); return $boolean_atts{$arg}; } sub TestArgs { my $self = shift; my %args = @_; my $queue; if( $args{'file'} ) { unless( $args{'file'} =~ /^[\w\. *?]+$/) { return( 0, "Files mask '$args{file}' has invalid characters" ); } $args{'file'} = $self->ConvertMaskToSQL( $args{'file'} ); } if( $args{'longer'} ) { unless( $args{'longer'} =~ /^\d+\s*[mk]?$/i ) { return( 0, "Invalid file size argument '$args{longer}'" ); } } return $self->SUPER::TestArgs( %args ); } sub Run { my $self = shift; my @conditions = (); my @values = (); if( $self->{'opt'}{'file'} ) { my $mask = $self->{'opt'}{'file'}; push @conditions, "( Filename LIKE ? )"; push @values, $mask; } if( $self->{'opt'}{'files_only'} ) { push @conditions, "( LENGTH(Filename) > 0 )"; } if( $self->{'opt'}{'longer'} ) { my $size = $self->{'opt'}{'longer'}; $size =~ s/([mk])//i; $size *= 1024 if $1 && lc $1 eq 'k'; $size *= 1024*1024 if $1 && lc $1 eq 'm'; push @conditions, "( LENGTH(Content) > ? )"; push @values, $size; } return (0, "At least one condition should be provided" ) unless @conditions; my $query = "SELECT id FROM Attachments WHERE ". join ' AND ', @conditions; if( $self->{'opt'}{'limit'} ) { $RT::Handle->ApplyLimits( \$query, $self->{'opt'}{'limit'} ); } my $sth = $RT::Handle->SimpleQuery( $query, @values ); return (0, "Internal error: '$sth'. Please send bug report.") unless $sth; my @objs; while( my $row = $sth->fetchrow_arrayref ) { push @objs, $row->[0]; } return (0, "Internal error: '". $sth->err ."'. Please send bug report.") if $sth->err; @objs = map {"RT::Attachment-$_"} @objs; return (1, @objs); } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Shredder/Plugin/Objects.pm����������������������������������������������������������000644 �000765 �000024 �00000006224 14005011336 021025� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Shredder::Plugin::Objects; use strict; use warnings FATAL => 'all'; use base qw(RT::Shredder::Plugin::Base::Search); use RT::Shredder; =head1 NAME RT::Shredder::Plugin::Objects - search plugin for wiping any selected object. =head1 ARGUMENTS This plugin searches an RT object you want, so you can use the object name as argument and id as value, for example if you want select ticket #123 then from CLI you write next command: rt-shredder --plugin 'Objects=Ticket,123' =cut sub SupportArgs { return $_[0]->SUPER::SupportArgs, @RT::Shredder::SUPPORTED_OBJECTS; } sub TestArgs { my $self = shift; my %args = @_; my @strings; foreach my $name( @RT::Shredder::SUPPORTED_OBJECTS ) { next unless $args{$name}; my $list = $args{$name}; $list = [$list] unless UNIVERSAL::isa( $list, 'ARRAY' ); push @strings, map "RT::$name\-$_", @$list; } my @objs = RT::Shredder->CastObjectsToRecords( Objects => \@strings ); my @res = $self->SUPER::TestArgs( %args ); $self->{'opt'}->{'objects'} = \@objs; return (@res); } sub Run { my $self = shift; my %args = ( Shredder => undef, @_ ); return (1, @{$self->{'opt'}->{'objects'}}); } 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Shredder/Plugin/Base/���������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 017744� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Shredder/Plugin/Base.pm�������������������������������������������������������������000644 �000765 �000024 �00000011445 14005011336 020307� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Shredder::Plugin::Base; use strict; use warnings FATAL => 'all'; =head1 NAME RT::Shredder::Plugin::Base - base class for Shredder plugins. =cut sub new { my $proto = shift; my $self = bless( {}, ref $proto || $proto ); return $self->_Init( @_ ); } sub _Init { my $self = shift; $self->{'opt'} = { @_ }; return $self; } =head1 USAGE =head2 masks If any argument is marked with keyword C<mask> then it means that this argument support two special characters: 1) C<*> matches any non empty sequence of the characters. For example C<*@example.com> will match any email address in C<example.com> domain. 2) C<?> matches exactly one character. For example C<????> will match any string four characters long. =head1 METHODS =head2 for subclassing in plugins =head3 Type - is not supported yet See F<Todo> for more info. =cut sub Type { return '' } =head3 SupportArgs Takes nothing. Returns list of the supported plugin arguments. Base class returns list of the arguments which all classes B<must> support. =cut sub SupportArgs { return () } =head3 ArgIsBoolean Takes the name of an argument. Returns true if the argument is a boolean. Used to display checkboxes in the web interface. =cut sub ArgIsBoolean { return; } =head3 HasSupportForArgs Takes a list of argument names. Returns true if all arguments are supported by plugin and returns C<(0, $msg)> in other case. =cut sub HasSupportForArgs { my $self = shift; my @args = @_; my @unsupported = (); foreach my $a( @args ) { push @unsupported, $a unless grep $_ eq $a, $self->SupportArgs; } return( 0, "Plugin doesn't support argument(s): @unsupported" ) if @unsupported; return( 1 ); } =head3 TestArgs Takes hash with arguments and thier values and returns true if all values pass testing otherwise returns C<(0, $msg)>. Stores arguments hash in C<$self->{'opt'}>, you can access this hash from C<Run> method. Method should be subclassed if plugin support non standard arguments. =cut sub TestArgs { my $self = shift; my %args = @_; if ( $self->{'opt'} ) { $self->{'opt'} = { %{$self->{'opt'}}, %args }; } else { $self->{'opt'} = \%args; } return 1; } =head3 Run Takes no arguments. Executes plugin and return C<(1, @objs)> on success or C<(0, $msg)> if error had happenned. Method B<must> be subclassed, this class always returns error. Method B<must> be called only after C<TestArgs> method in other case values of the arguments are not available. =cut sub Run { return (0, "This is abstract plugin, you couldn't use it directly") } =head2 utils =head3 ConvertMaskToSQL Takes one argument - mask with C<*> and C<?> chars and return mask SQL chars. =cut sub ConvertMaskToSQL { my $self = shift; my $mask = shift || ''; $mask =~ s/\*/%/g; $mask =~ s/\?/_/g; return $mask; } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Shredder/Plugin/Base/Dump.pm��������������������������������������������������������000644 �000765 �000024 �00000004504 14005011336 021212� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Shredder::Plugin::Base::Dump; use strict; use warnings FATAL => 'all'; use base qw(RT::Shredder::Plugin::Base); =head1 NAME RT::Shredder::Plugin::Base - base class for Shredder plugins. =cut sub Type { return 'dump' } sub AppliesToStates { return () } sub SupportArgs { return () } sub PushMark { return 1 } sub PopMark { return 1 } sub RollbackTo { return 1 } 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Shredder/Plugin/Base/Search.pm������������������������������������������������������000644 �000765 �000024 �00000007532 14005011336 021516� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Shredder::Plugin::Base::Search; use strict; use warnings FATAL => 'all'; use base qw(RT::Shredder::Plugin::Base); =head1 NAME RT::Shredder::Plugin::Base - base class for Shredder plugins. =cut sub Type { return 'search' } =head1 ARGUMENTS Arguments which all plugins support. =head2 limit - unsigned integer Allow you to limit search results. B<< Default value is C<10> >>. =head1 METHODS =cut sub SupportArgs { my %seen; my @args = sort grep $_ && !$seen{$_}, shift->SUPER::SupportArgs(@_), qw(limit); return @args; } sub TestArgs { my $self = shift; my %args = @_; if( defined $args{'limit'} && $args{'limit'} ne '' ) { my $limit = $args{'limit'}; $limit =~ s/[^0-9]//g; unless( $args{'limit'} eq $limit ) { return( 0, "'limit' should be an unsigned integer"); } $args{'limit'} = $limit; } else { $args{'limit'} = 10; } return $self->SUPER::TestArgs( %args ); } sub SetResolvers { return 1 } =head2 FetchNext $collection [, $init] Returns next object in collection as method L<RT::SearchBuilder/Next>, but doesn't stop on page boundaries. When method is called with true C<$init> arg it enables pages on collection and selects first page. Main purpose of this method is to avoid loading of whole collection into memory as RT does by default when pager is not used. This method init paging on the collection, but doesn't stop when reach page end. Example: $plugin->FetchNext( $tickets, 'init' ); while( my $ticket = $plugin->FetchNext( $tickets ) ) { ... } =cut use constant PAGE_SIZE => 100; sub FetchNext { my ($self, $objs, $init) = @_; if ( $init ) { $objs->RowsPerPage( PAGE_SIZE ); $objs->FirstPage; return; } my $obj = $objs->Next; return $obj if $obj; $objs->NextPage; return $objs->Next; } 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Dashboard/SelfService.pm������������������������������������������������������������000644 �000765 �000024 �00000005730 14005011336 020540� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Dashboard::SelfService - dashboard for the Self-Service Home Page =head1 SYNOPSIS See RT::Dashboard =cut package RT::Dashboard::SelfService; use strict; use warnings; use base qw/RT::Dashboard/; =head2 ObjectName An object of this class is called "selfservicedashboard" =cut sub ObjectName { "selfservicedashboard" } # loc =head2 PostLoadValidate Ensure that the ID corresponds to an actual dashboard object, since it's all attributes under the hood. =cut sub PostLoadValidate { my $self = shift; return (0, "Invalid object type") unless $self->{'Attribute'}->Name eq 'SelfServiceDashboard'; return 1; } sub SaveAttribute { my $self = shift; my $object = shift; my $args = shift; return $object->AddAttribute( 'Name' => 'SelfServiceDashboard', 'Description' => $args->{'Name'}, 'Content' => {Panes => $args->{'Panes'}}, ); } # All users can use SelfServiceDashboard sub CurrentUserCanSee { return 1 } RT::Base->_ImportOverlays(); 1; ����������������������������������������rt-5.0.1/lib/RT/Dashboard/Mailer.pm�����������������������������������������������������������������000644 �000765 �000024 �00000055032 14005011336 017537� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Dashboard::Mailer; use strict; use warnings; use HTML::Mason; use HTML::RewriteAttributes::Links; use HTML::RewriteAttributes::Resources; use MIME::Types; use POSIX 'tzset'; use RT::Dashboard; use RT::Interface::Web::Handler; use RT::Interface::Web; use File::Temp 'tempdir'; use HTML::Scrubber; use URI::QueryParam; use List::MoreUtils 'uniq'; sub MailDashboards { my $self = shift; my %args = ( All => 0, DryRun => 0, Time => time, @_, ); $RT::Logger->debug("Using time $args{Time} for dashboard generation"); my $from = $self->GetFrom(); $RT::Logger->debug("Sending email from $from"); # look through each user for her subscriptions my $Users = RT::Users->new(RT->SystemUser); $Users->LimitToPrivileged; $Users->LimitToEnabled; while (defined(my $user = $Users->Next)) { my ($hour, $dow, $dom) = HourDowDomIn($args{Time}, $user->Timezone || RT->Config->Get('Timezone')); $hour .= ':00'; $RT::Logger->debug("Checking ".$user->Name."'s subscriptions: hour $hour, dow $dow, dom $dom"); my $currentuser = RT::CurrentUser->new; $currentuser->LoadByName($user->Name); my $subscriber_lang = $user->Lang; # look through this user's subscriptions, are any supposed to be generated # right now? for my $subscription ($user->Attributes->Named('Subscription')) { next unless $self->IsSubscriptionReady( %args, Subscription => $subscription, User => $user, LocalTime => [$hour, $dow, $dom], ); my $recipients = $subscription->SubValue('Recipients'); my $recipients_users = $recipients->{Users}; my $recipients_groups = $recipients->{Groups}; my @emails; my %recipient_language; # add users' emails to email list for my $user_id (@{ $recipients_users || [] }) { my $user = RT::User->new(RT->SystemUser); $user->Load($user_id); next unless $user->id and !$user->Disabled; push @emails, $user->EmailAddress; $recipient_language{$user->EmailAddress} = $user->Lang; } # add emails for every group's members for my $group_id (@{ $recipients_groups || [] }) { my $group = RT::Group->new(RT->SystemUser); $group->Load($group_id); next unless $group->id; my $users = $group->UserMembersObj; while (my $user = $users->Next) { push @emails, $user->EmailAddress; $recipient_language{$user->EmailAddress} = $user->Lang; } } my $email_success = 0; for my $email (uniq @emails) { eval { my $lang; for my $langkey (RT->Config->Get('EmailDashboardLanguageOrder')) { if ($langkey eq '_subscription') { if ($lang = $subscription->SubValue('Language')) { $RT::Logger->debug("Using subscription's specified language '$lang'"); last; } } elsif ($langkey eq '_recipient') { if ($lang = $recipient_language{$email}) { $RT::Logger->debug("Using recipient's preferred language '$lang'"); last; } } elsif ($langkey eq '_subscriber') { if ($lang = $subscriber_lang) { $RT::Logger->debug("Using subscriber's preferred language '$lang'"); last; } } else { # specific language name $lang = $langkey; $RT::Logger->debug("Using EmailDashboardLanguageOrder fallback language '$lang'"); last; } } # use English as the absolute fallback. Though the config # lets you specify a site-specific fallback, it also lets # you not specify a fallback, and we don't want to # accidentally reuse whatever language the previous # recipient happened to have if (!$lang) { $RT::Logger->debug("Using RT's fallback language 'en'. You may specify a different fallback language in your config with EmailDashboardLanguageOrder."); $lang = 'en'; } $currentuser->{'LangHandle'} = RT::I18N->get_handle($lang); $self->SendDashboard( %args, CurrentUser => $currentuser, Email => $email, Subscription => $subscription, From => $from, ) }; if ( $@ ) { $RT::Logger->error("Caught exception: $@"); } else { $email_success = 1; } } if ($email_success) { my $counter = $subscription->SubValue('Counter') || 0; $subscription->SetSubValues(Counter => $counter + 1) unless $args{DryRun}; } } } } sub IsSubscriptionReady { my $self = shift; my %args = ( All => 0, Subscription => undef, User => undef, LocalTime => [0, 0, 0], @_, ); return 1 if $args{All}; my $subscription = $args{Subscription}; my $counter = $subscription->SubValue('Counter') || 0; my $sub_frequency = $subscription->SubValue('Frequency'); my $sub_hour = $subscription->SubValue('Hour'); my $sub_dow = $subscription->SubValue('Dow'); my $sub_dom = $subscription->SubValue('Dom'); my $sub_fow = $subscription->SubValue('Fow') || 1; my $log_frequency = $sub_frequency; if ($log_frequency eq 'daily') { my $days = join ' ', grep { $subscription->SubValue($_) } qw/Monday Tuesday Wednesday Thursday Friday Saturday Sunday/; $log_frequency = "$log_frequency ($days)"; } my ($hour, $dow, $dom) = @{ $args{LocalTime} }; $RT::Logger->debug("Checking against subscription " . $subscription->Id . " for " . $args{User}->Name . " with frequency $log_frequency, hour $sub_hour, dow $sub_dow, dom $sub_dom, fow $sub_fow, counter $counter"); return 0 if $sub_frequency eq 'never'; # correct hour? return 0 if $sub_hour ne $hour; if ($sub_frequency eq 'daily') { return $subscription->SubValue($dow) ? 1 : 0; } if ($sub_frequency eq 'weekly') { # correct day of week? return 0 if $sub_dow ne $dow; # does it match the "every N weeks" clause? return 1 if $counter % $sub_fow == 0; $subscription->SetSubValues(Counter => $counter + 1) unless $args{DryRun}; return 0; } # if monthly, correct day of month? if ($sub_frequency eq 'monthly') { return $sub_dom == $dom; } $RT::Logger->debug("Invalid subscription frequency $sub_frequency for " . $args{User}->Name); # unknown frequency type, bail out return 0; } sub GetFrom { RT->Config->Get('DashboardAddress') || RT->Config->Get('OwnerEmail') } sub SendDashboard { my $self = shift; my %args = ( CurrentUser => undef, Email => undef, Subscription => undef, DryRun => 0, @_, ); my $currentuser = $args{CurrentUser}; my $subscription = $args{Subscription}; my $rows = $subscription->SubValue('Rows'); my $DashboardId = $subscription->SubValue('DashboardId'); my $dashboard = RT::Dashboard->new($currentuser); my ($ok, $msg) = $dashboard->LoadById($DashboardId); # failed to load dashboard. perhaps it was deleted or it changed privacy if (!$ok) { $RT::Logger->warning("Unable to load dashboard $DashboardId of subscription ".$subscription->Id." for user ".$currentuser->Name.": $msg"); return $self->ObsoleteSubscription( %args, Subscription => $subscription, ); } $RT::Logger->debug('Generating dashboard "'.$dashboard->Name.'" for user "'.$currentuser->Name.'":'); if ($args{DryRun}) { print << "SUMMARY"; Dashboard: @{[ $dashboard->Name ]} Subscription Owner: @{[ $currentuser->Name ]} Recipient: <$args{Email}> SUMMARY return; } local $HTML::Mason::Commands::session{CurrentUser} = $currentuser; local $HTML::Mason::Commands::r = RT::Dashboard::FakeRequest->new; my $HasResults = undef; my $content = RunComponent( '/Dashboards/Render.html', id => $dashboard->Id, Preview => 0, HasResults => \$HasResults, ); if ($subscription->SubValue('SuppressIfEmpty')) { # undef means there were no searches, so we should still send it (it's just portlets) # 0 means there was at least one search and none had any result, so we should suppress it if (defined($HasResults) && !$HasResults) { $RT::Logger->debug("Not sending because there are no results and the subscription has SuppressIfEmpty"); return; } } if ( RT->Config->Get('EmailDashboardRemove') ) { for ( RT->Config->Get('EmailDashboardRemove') ) { $content =~ s/$_//g; } } $content = ScrubContent($content); $RT::Logger->debug("Got ".length($content)." characters of output."); $content = HTML::RewriteAttributes::Links->rewrite( $content, RT->Config->Get('WebURL') . 'Dashboards/Render.html', ); $self->EmailDashboard( %args, Dashboard => $dashboard, Content => $content, ); } sub ObsoleteSubscription { my $self = shift; my %args = ( From => undef, To => undef, Subscription => undef, CurrentUser => undef, @_, ); my $subscription = $args{Subscription}; my $ok = RT::Interface::Email::SendEmailUsingTemplate( From => $args{From}, To => $args{Email}, Template => 'Error: Missing dashboard', Arguments => { SubscriptionObj => $subscription, }, ExtraHeaders => { 'X-RT-Dashboard-Subscription-Id' => $subscription->Id, 'X-RT-Dashboard-Id' => $subscription->SubValue('DashboardId'), }, ); # only delete the subscription if the email looks like it went through if ($ok) { my ($deleted, $msg) = $subscription->Delete(); if ($deleted) { $RT::Logger->debug("Deleted an obsolete subscription: $msg"); } else { $RT::Logger->warning("Unable to delete an obsolete subscription: $msg"); } } else { $RT::Logger->warning("Unable to notify ".$args{CurrentUser}->Name." of an obsolete subscription"); } } sub EmailDashboard { my $self = shift; my %args = ( CurrentUser => undef, Email => undef, Dashboard => undef, Subscription => undef, Content => undef, @_, ); my $subscription = $args{Subscription}; my $dashboard = $args{Dashboard}; my $currentuser = $args{CurrentUser}; my $email = $args{Email}; my $frequency = $subscription->SubValue('Frequency'); my %frequency_lookup = ( 'daily' => 'Daily', # loc 'weekly' => 'Weekly', # loc 'monthly' => 'Monthly', # loc 'never' => 'Never', # loc ); my $frequency_display = $frequency_lookup{$frequency} || $frequency; my $subject = sprintf '[%s] ' . RT->Config->Get('DashboardSubject'), RT->Config->Get('rtname'), $currentuser->loc($frequency_display), $dashboard->Name; my $entity = $self->BuildEmail( %args, To => $email, Subject => $subject, ); $entity->head->replace('X-RT-Dashboard-Id', $dashboard->Id); $entity->head->replace('X-RT-Dashboard-Subscription-Id', $subscription->Id); $RT::Logger->debug('Mailing dashboard "'.$dashboard->Name.'" to user '.$currentuser->Name." <$email>"); my $ok = RT::Interface::Email::SendEmail( %{ RT->Config->Get('Crypt')->{'Dashboards'} || {} }, Entity => $entity, ); if (!$ok) { $RT::Logger->error("Failed to email dashboard to user ".$currentuser->Name." <$email>"); return; } $RT::Logger->debug("Done sending dashboard to ".$currentuser->Name." <$email>"); } sub BuildEmail { my $self = shift; my %args = ( Content => undef, From => undef, To => undef, Subject => undef, @_, ); my @parts; my %cid_of; my $content = HTML::RewriteAttributes::Resources->rewrite($args{Content}, sub { my $uri = shift; # already attached this object return "cid:$cid_of{$uri}" if $cid_of{$uri}; my ($data, $filename, $mimetype, $encoding) = GetResource($uri); return $uri unless defined $data; $cid_of{$uri} = time() . $$ . int(rand(1e6)); # Encode textual data in UTF-8, and downgrade (treat # codepoints as codepoints, and ensure the UTF-8 flag is # off) everything else. my @extra; if ( $mimetype =~ m{text/} ) { $data = Encode::encode( "UTF-8", $data ); @extra = ( Charset => "UTF-8" ); } else { utf8::downgrade( $data, 1 ) or $RT::Logger->warning("downgrade $data failed"); } push @parts, MIME::Entity->build( Top => 0, Data => $data, Type => $mimetype, Encoding => $encoding, Disposition => 'inline', Name => RT::Interface::Email::EncodeToMIME( String => $filename ), 'Content-Id' => $cid_of{$uri}, @extra, ); return "cid:$cid_of{$uri}"; }, inline_css => sub { my $uri = shift; my ($content) = GetResource($uri); return defined $content ? $content : ""; }, inline_imports => 1, ); my $entity = MIME::Entity->build( From => Encode::encode("UTF-8", $args{From}), To => Encode::encode("UTF-8", $args{To}), Subject => RT::Interface::Email::EncodeToMIME( String => $args{Subject} ), Type => "multipart/mixed", ); $entity->attach( Type => 'text/html', Charset => 'UTF-8', Data => Encode::encode("UTF-8", $content), Disposition => 'inline', Encoding => "base64", ); for my $part (@parts) { $entity->add_part($part); } $entity->make_singlepart; return $entity; } { my $mason; my $outbuf = ''; my $data_dir = ''; sub _mason { unless ($mason) { $RT::Logger->debug("Creating Mason object."); # user may not have permissions on the data directory, so create a # new one $data_dir = tempdir(CLEANUP => 1); $mason = HTML::Mason::Interp->new( RT::Interface::Web::Handler->DefaultHandlerArgs, out_method => \$outbuf, autohandler_name => '', # disable forced login and more data_dir => $data_dir, ); $mason->set_escape( h => \&RT::Interface::Web::EscapeHTML ); $mason->set_escape( u => \&RT::Interface::Web::EscapeURI ); $mason->set_escape( j => \&RT::Interface::Web::EscapeJS ); } return $mason; } sub RunComponent { _mason->exec(@_); my $ret = $outbuf; $outbuf = ''; return $ret; } } { my $scrubber; sub _scrubber { unless ($scrubber) { $scrubber = HTML::Scrubber->new; # Allow everything by default, except JS attributes ... $scrubber->default( 1 => { '*' => 1, map { ("on$_" => 0) } qw(blur change click dblclick error focus keydown keypress keyup load mousedown mousemove mouseout mouseover mouseup reset select submit unload) } ); # ... and <script>s $scrubber->deny('script'); } return $scrubber; } sub ScrubContent { my $content = shift; return _scrubber->scrub($content); } } { my %cache; sub HourDowDomIn { my $now = shift; my $tz = shift; my $key = "$now $tz"; return @{$cache{$key}} if exists $cache{$key}; my ($hour, $dow, $dom); { local $ENV{'TZ'} = $tz; ## Using POSIX::tzset fixes a bug where the TZ environment variable ## is cached. tzset(); (undef, undef, $hour, $dom, undef, undef, $dow) = localtime($now); } tzset(); # return back previous value $hour = "0$hour" if length($hour) == 1; $dow = (qw/Sunday Monday Tuesday Wednesday Thursday Friday Saturday/)[$dow]; return @{$cache{$key}} = ($hour, $dow, $dom); } } sub GetResource { my $uri = URI->new(shift); my ($content, $content_type, $filename, $mimetype, $encoding); # Avoid trying to inline any remote URIs. We absolutified all URIs # using WebURL in SendDashboard() above, so choose the simpler match on # that rather than testing a bunch of URI accessors. my $WebURL = RT->Config->Get("WebURL"); return unless $uri =~ /^\Q$WebURL/; $RT::Logger->debug("Getting resource $uri"); # strip out the equivalent of WebURL, so we start at the correct / my $path = $uri->path; my $webpath = RT->Config->Get('WebPath'); $path =~ s/^\Q$webpath//; # add a leading / if needed $path = "/$path" unless $path =~ m{^/}; # Try the static handler first for non-Mason CSS, JS, etc. my $res = RT::Interface::Web::Handler->GetStatic($path); if ($res->is_success) { RT->Logger->debug("Fetched '$path' from the static handler"); $content = $res->decoded_content; $content_type = $res->headers->content_type; } else { # Try it through Mason instead... $HTML::Mason::Commands::r->path_info($path); # grab the query arguments my %args = map { $_ => [ map {Encode::decode("UTF-8",$_)} $uri->query_param($_) ] } $uri->query_param; # Convert empty and single element arrayrefs to a non-ref scalar @$_ < 2 and $_ = $_->[0] for values %args; $RT::Logger->debug("Running component '$path'"); $content = RunComponent($path, %args); $content_type = $HTML::Mason::Commands::r->content_type; } # guess at the filename from the component name $filename = $1 if $path =~ m{^.*/(.*?)$}; # the rest of this was taken from Email::MIME::CreateHTML::Resolver::LWP ($mimetype, $encoding) = MIME::Types::by_suffix($filename); if ($content_type) { $mimetype = $content_type; # strip down to just a MIME type $mimetype = $1 if $mimetype =~ /(\S+);\s*charset=(.*)$/; } #If all else fails then some conservative and general-purpose defaults are: $mimetype ||= 'application/octet-stream'; $encoding ||= 'base64'; $RT::Logger->debug("Resource $uri: length=".length($content)." filename='$filename' mimetype='$mimetype', encoding='$encoding'"); return ($content, $filename, $mimetype, $encoding); } { package RT::Dashboard::FakeRequest; sub new { bless {}, shift } sub header_out { return undef } sub headers_out { wantarray ? () : {} } sub err_headers_out { wantarray ? () : {} } sub content_type { my $self = shift; $self->{content_type} = shift if @_; return $self->{content_type}; } sub path_info { my $self = shift; $self->{path_info} = shift if @_; return $self->{path_info}; } } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/CustomFieldValues/Canonicalizer/����������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 022253� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/CustomFieldValues/Groups.pm���������������������������������������������������������000644 �000765 �000024 �00000006434 14005011336 021316� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::CustomFieldValues::Groups; use strict; use warnings; use base qw(RT::CustomFieldValues::External); =head1 NAME RT::CustomFieldValues::Groups - Provide RT's groups as a dynamic list of CF values =head1 SYNOPSIS To use as a source of CF values, add the following to your F<RT_SiteConfig.pm> and restart RT. # In RT_SiteConfig.pm Set( @CustomFieldValuesSources, "RT::CustomFieldValues::Groups" ); Then visit the modify CF page in the RT admin configuration. =head1 METHODS Most methods are inherited from L<RT::CustomFieldValues::External>, except the ones below. =head2 SourceDescription Returns a brief string describing this data source. =cut sub SourceDescription { return 'RT user defined groups'; } =head2 ExternalValues Returns an arrayref containing a hashref for each possible value in this data source, where the value name is the group name. =cut sub ExternalValues { my $self = shift; my @res; my $i = 0; my $groups = RT::Groups->new( $self->CurrentUser ); $groups->LimitToUserDefinedGroups; $groups->OrderByCols( { FIELD => 'Name' } ); while( my $group = $groups->Next ) { push @res, { name => $group->Name, description => $group->Description, sortorder => $i++, }; } return \@res; } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/CustomFieldValues/External.pm�������������������������������������������������������000644 �000765 �000024 �00000015105 14005011336 021614� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::CustomFieldValues::External; use strict; use warnings; use base qw(RT::CustomFieldValues); =head1 NAME RT::CustomFieldValues::External - Pull possible values for a custom field from an arbitrary external data source. =head1 SYNOPSIS Custom field value lists can be produced by creating a class that inherits from C<RT::CustomFieldValues::External>, and overloading C<SourceDescription> and C<ExternalValues>. See L<RT::CustomFieldValues::Groups> for a simple example. =head1 DESCRIPTION Subclasses should implement the following methods: =head2 SourceDescription This method should return a string describing the data source; this is the identifier by which the user will see the dropdown. =head2 ExternalValues This method should return an array reference of hash references. The hash references must contain a key for C<name> and can optionally contain keys for C<description>, C<sortorder>, and C<category>. If supplying a category, you must also set the category the custom field is based on in the custom field configuration page. =head1 SEE ALSO F<docs/extending/external_custom_fields.pod> =cut sub _Init { my $self = shift; $self->Table( '' ); return ( $self->SUPER::_Init(@_) ); } sub CleanSlate { my $self = shift; delete $self->{ $_ } foreach qw( __external_cf __external_cf_limits ); return $self->SUPER::CleanSlate(@_); } sub _ClonedAttributes { my $self = shift; return qw( __external_cf __external_cf_limits ), $self->SUPER::_ClonedAttributes; } sub Limit { my $self = shift; my %args = (@_); push @{ $self->{'__external_cf_limits'} ||= [] }, { %args, CALLBACK => $self->__BuildLimitCheck( %args ), }; return $self->SUPER::Limit( %args ); } sub __BuildLimitCheck { my ($self, %args) = (@_); return undef unless $args{'FIELD'} =~ /^(?:Name|Description)$/; my $condition = $args{VALUE}; my $op = $args{'OPERATOR'} || '='; my $field = $args{FIELD}; return sub { my $record = shift; my $value = $record->$field; return 0 unless defined $value; if ($op eq "=") { return 0 unless $value eq $condition; } elsif ($op eq "!=" or $op eq "<>") { return 0 unless $value ne $condition; } elsif (uc($op) eq "LIKE") { return 0 unless $value =~ /\Q$condition\E/i; } elsif (uc($op) eq "NOT LIKE") { return 0 unless $value !~ /\Q$condition\E/i; } else { return 0; } return 1; }; } sub __BuildAggregatorsCheck { my $self = shift; my @cbs = grep {$_->{CALLBACK}} @{ $self->{'__external_cf_limits'} }; return undef unless @cbs; my %h = ( OR => sub { defined $_[0] ? ($_[0] || $_[1]) : $_[1] }, AND => sub { defined $_[0] ? ($_[0] && $_[1]) : $_[1] }, ); return sub { my ($sb, $record) = @_; my $ok; for my $limit ( @cbs ) { $ok = $h{$limit->{ENTRYAGGREGATOR} || 'OR'}->( $ok, $limit->{CALLBACK}->($record), ); } return $ok; }; } sub _DoSearch { my $self = shift; delete $self->{'items'}; my %defaults = ( id => 1, name => '', customfield => $self->{'__external_cf'}, sortorder => 0, description => '', category => undef, creator => RT->SystemUser->id, created => undef, lastupdatedby => RT->SystemUser->id, lastupdated => undef, ); my $i = 0; my $check = $self->__BuildAggregatorsCheck; foreach( $self->_SortValues( @{ $self->ExternalValues } ) ) { my $value = $self->NewItem; $value->LoadFromHash( { %defaults, %$_ } ); next if $check && !$check->( $self, $value ); $self->AddRecord( $value ); last if $self->RowsPerPage and ++$i >= $self->RowsPerPage; } $self->{'must_redo_search'} = 0; return $self->_RecordCount; } sub _SortValues { my $self = shift; my @items = @_; no warnings 'uninitialized'; return sort { $a->{sortorder} <=> $b->{sortorder} || lc($a->{name}) cmp lc($b->{name}) } @items; } sub _DoCount { my $self = shift; my $count; $count = $self->_DoSearch if $self->{'must_redo_search'}; $count = $self->_RecordCount unless defined $count; return $self->{'count_all'} = $self->{'raw_rows'} = $count; } sub LimitToCustomField { my $self = shift; $self->{'__external_cf'} = $_[0]; return $self->SUPER::LimitToCustomField( @_ ); } sub _SingularClass { "RT::CustomFieldValue" } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/CustomFieldValues/Canonicalizer.pm��������������������������������������������������000644 �000765 �000024 �00000007330 14005011336 022614� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::CustomFieldValues::Canonicalizer; use strict; use warnings; use base 'RT::Base'; =head1 NAME RT::CustomFieldValues::Canonicalizer - base class for custom field value canonicalizers =head1 SYNOPSIS =head1 DESCRIPTION This class is the base class for custom field value canonicalizers. To implement a new canonicalizer, you must create a new class that subclasses this class. Your subclass must implement the method L</CanonicalizeValue> as documented below. You should also implement the method L</Description> which is the label shown to users. Finally, add the new class name to L<RT_Config/@CustomFieldValuesCanonicalizers>. See L<RT::CustomFieldValues::Canonicalizer::Uppercase> for a complete example. =head2 new The object constructor takes one argument: L<RT::CurrentUser> object. =cut sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; bless ($self, $class); $self->CurrentUser(@_); return $self; } =head2 CanonicalizeValue Receives a parameter hash including C<CustomField> (an L<RT::CustomField> object) and C<Content> (a string of user-provided content). You may also access C<< $self->CurrentUser >> in case you need the user's language or locale. This method is expected to return the canonicalized C<Content>. =cut sub CanonicalizeValue { my $self = shift; die "Subclass " . ref($self) . " of " . __PACKAGE__ . " does not implement required method CanonicalizeValue"; } =head2 Description A class method that returns the human-friendly name for this canonicalizer which appears in the admin UI. By default it is the class name, which is not so human friendly. You should override this in your subclass. =cut sub Description { my $class = shift; return $class; } RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/CustomFieldValues/Canonicalizer/Uppercase.pm����������������������������������������000644 �000765 �000024 �00000005131 14005011336 024540� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::CustomFieldValues::Canonicalizer::Uppercase; use strict; use warnings; use base qw(RT::CustomFieldValues::Canonicalizer); =encoding utf-8 =head1 NAME RT::CustomFieldValues::Canonicalizer::Uppercase - uppercase custom field values =head1 DESCRIPTION This canonicalizer adjusts the custom field value to have all uppercase characters. It is Unicode-aware, so for example "ω" will become "Ω". =cut sub CanonicalizeValue { my $self = shift; my %args = ( CustomField => undef, Content => undef, @_, ); return uc $args{Content}; } sub Description { "Uppercase" } RT::Base->_ImportOverlays(); 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/CustomFieldValues/Canonicalizer/Lowercase.pm����������������������������������������000644 �000765 �000024 �00000005131 14005011336 024535� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::CustomFieldValues::Canonicalizer::Lowercase; use strict; use warnings; use base qw(RT::CustomFieldValues::Canonicalizer); =encoding utf-8 =head1 NAME RT::CustomFieldValues::Canonicalizer::Lowercase - lowercase custom field values =head1 DESCRIPTION This canonicalizer adjusts the custom field value to have all lowercase characters. It is Unicode-aware, so for example "Ω" will become "ω". =cut sub CanonicalizeValue { my $self = shift; my %args = ( CustomField => undef, Content => undef, @_, ); return lc $args{Content}; } sub Description { "Lowercase" } RT::Base->_ImportOverlays(); 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Search/Simple.pm��������������������������������������������������������������������000644 �000765 �000024 �00000022617 14005011336 017100� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Search::Simple =head1 SYNOPSIS =head1 DESCRIPTION Use the argument passed in as a simple set of keywords =head1 METHODS =cut package RT::Search::Simple; use strict; use warnings; use base qw(RT::Search); use Regexp::Common qw/delimited/; # Only a subset of limit types AND themselves together. "queue:foo # queue:bar" is an OR, but "subject:foo subject:bar" is an AND our %AND = ( default => 1, content => 1, subject => 1, ); sub _Init { my $self = shift; my %args = @_; $self->{'Queues'} = delete( $args{'Queues'} ) || []; $self->SUPER::_Init(%args); } sub Describe { my $self = shift; return ( $self->loc( "Keyword and intuition-based searching", ref $self ) ); } sub Prepare { my $self = shift; my $tql = $self->QueryToSQL( $self->Argument ); $RT::Logger->debug($tql); $self->TicketsObj->FromSQL($tql); return (1); } sub QueryToSQL { my $self = shift; my $query = shift || $self->Argument; my %limits; $query =~ s/^\s*//; while ($query =~ /^\S/) { if ($query =~ s/^ (?: (\w+) # A straight word (?:\. # With an optional .foo ($RE{delimited}{-delim=>q['"]} |[\w-]+ # Allow \w + dashes ) # Which could be ."foo bar", too )? ) : # Followed by a colon ($RE{delimited}{-delim=>q['"]} |\S+ ) # And a possibly-quoted foo:"bar baz" \s*//ix) { my ($type, $extra, $value) = ($1, $2, $3); ($value, my ($quoted)) = $self->Unquote($value); $extra = $self->Unquote($extra) if defined $extra; $self->Dispatch(\%limits, $type, $value, $quoted, $extra); } elsif ($query =~ s/^($RE{delimited}{-delim=>q['"]}|\S+)\s*//) { # If there's no colon, it's just a word or quoted string my($val, $quoted) = $self->Unquote($1); $self->Dispatch(\%limits, $self->GuessType($val, $quoted), $val, $quoted); } } $self->Finalize(\%limits); my @clauses; for my $subclause (sort keys %limits) { next unless @{$limits{$subclause}}; my $op = $AND{lc $subclause} ? "AND" : "OR"; push @clauses, "( ".join(" $op ", @{$limits{$subclause}})." )"; } return join " AND ", @clauses; } sub Dispatch { my $self = shift; my ($limits, $type, $contents, $quoted, $extra) = @_; $contents =~ s/(['\\])/\\$1/g; $extra =~ s/(['\\])/\\$1/g if defined $extra; my $method = "Handle" . ucfirst(lc($type)); $method = "HandleDefault" unless $self->can($method); my ($key, @tsql) = $self->$method($contents, $quoted, $extra); push @{$limits->{$key}}, @tsql; } sub Unquote { # Given a word or quoted string, unquote it if it is quoted, # removing escaped quotes. my $self = shift; my ($token) = @_; if ($token =~ /^$RE{delimited}{-delim=>q['"]}{-keep}$/) { my $quote = $2 || $5; my $value = $3 || $6; $value =~ s/\\(\\|$quote)/$1/g; return wantarray ? ($value, 1) : $value; } else { return wantarray ? ($token, 0) : $token; } } sub Finalize { my $self = shift; my ($limits) = @_; # Assume that numbers were actually "default"s if we have other limits if ($limits->{id} and keys %{$limits} > 1) { my $values = delete $limits->{id}; for my $value (@{$values}) { $value =~ /(\d+)/ or next; my ($key, @tsql) = $self->HandleDefault($1); push @{$limits->{$key}}, @tsql; } } # Apply default "active status" limit if we don't have any status # limits ourselves, and we're not limited by id if (not $limits->{status} and not $limits->{id} and RT::Config->Get('OnlySearchActiveTicketsInSimpleSearch', $self->TicketsObj->CurrentUser)) { $limits->{status} = ["Status = '__Active__'"]; } # Respect the "only search these queues" limit if we didn't # specify any queues ourselves if (not $limits->{queue} and not $limits->{id}) { for my $queue ( @{ $self->{'Queues'} } ) { my $QueueObj = RT::Queue->new( $self->TicketsObj->CurrentUser ); next unless $QueueObj->Load($queue); my $name = $QueueObj->Name; $name =~ s/(['\\])/\\$1/g; push @{$limits->{queue}}, "Queue = '$name'"; } } } our @GUESS = ( [ 10 => sub { return "default" if $_[1] } ], [ 20 => sub { return "id" if /^#?\d+$/ } ], [ 30 => sub { return "requestor" if /\w+@\w+/} ], [ 35 => sub { return "domain" if /^@\w+/} ], [ 40 => sub { return "status" if RT::Queue->new( $_[2] )->IsValidStatus( $_ ) }], [ 40 => sub { return "status" if /^((in)?active|any)$/i } ], [ 50 => sub { my $q = RT::Queue->new( $_[2] ); return "queue" if $q->Load($_) and $q->Id and not $q->Disabled }], [ 60 => sub { my $u = RT::User->new( $_[2] ); return "owner" if $u->Load($_) and $u->Id and $u->Privileged }], [ 70 => sub { return "owner" if $_ eq "me" } ], ); sub GuessType { my $self = shift; my ($val, $quoted) = @_; my $cu = $self->TicketsObj->CurrentUser; for my $sub (map $_->[1], sort {$a->[0] <=> $b->[0]} @GUESS) { local $_ = $val; my $ret = $sub->($val, $quoted, $cu); return $ret if $ret; } 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 { my $fts = RT->Config->Get('FullTextSearch'); if ($fts->{Enable} and $fts->{Indexed}) { return default => "Content LIKE '$_[1]'"; } else { return default => "Subject LIKE '$_[1]'"; } } sub HandleSubject { return subject => "Subject LIKE '$_[1]'"; } sub HandleFulltext { return content => "Content LIKE '$_[1]'"; } sub HandleContent { return content => "Content LIKE '$_[1]'"; } sub HandleId { $_[1] =~ s/^#//; return id => "Id = $_[1]"; } sub HandleStatus { if ($_[1] =~ /^active$/i and !$_[2]) { return status => "Status = '__Active__'"; } elsif ($_[1] =~ /^inactive$/i and !$_[2]) { return status => "Status = '__Inactive__'"; } elsif ($_[1] =~ /^any$/i and !$_[2]) { return 'status'; } else { return status => "Status = '$_[1]'"; } } sub HandleOwner { 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]'"; } sub HandleRequestor { return requestor => "Requestor STARTSWITH '$_[1]'"; } sub HandleDomain { $_[1] =~ s/^@?/@/; return requestor => "Requestor ENDSWITH '$_[1]'"; } sub HandleQueue { return queue => "Queue = '$_[1]'"; } sub HandleQ { return queue => "Queue = '$_[1]'"; } sub HandleCf { return "cf.$_[3]" => "'CF.{$_[3]}' LIKE '$_[1]'"; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Search/FromSQL.pm�������������������������������������������������������������������000644 �000765 �000024 �00000005332 14005011336 017125� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Search::FromSQL =head1 SYNOPSIS =head1 DESCRIPTION Find all tickets described by the SQL statement passed as an argument =head1 METHODS =cut package RT::Search::FromSQL; use strict; use warnings; use base qw(RT::Search); =head2 Describe Returns a localized string describing the module's function. =cut sub Describe { my $self = shift; return ($self->loc("TicketSQL search module", ref $self)); } =head2 Prepare The meat of the module. Runs a search on its Tickets object, using the SQL string described in its Argument object. The Tickets object is reduced to those tickets matching the SQL query. =cut sub Prepare { my $self = shift; $self->TicketsObj->FromSQL($self->Argument); return(1); } RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Search/ActiveTicketsInQueue.pm������������������������������������������������������000644 �000765 �000024 �00000004770 14005011336 021705� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Search::ActiveTicketsInQueue =head1 SYNOPSIS =head1 DESCRIPTION Find all active tickets in the queue named in the argument passed in =head1 METHODS =cut package RT::Search::ActiveTicketsInQueue; use strict; use warnings; use base qw(RT::Search); sub Describe { my $self = shift; return ($self->loc("No description for [_1]", ref $self)); } sub Prepare { my $self = shift; $self->TicketsObj->LimitQueue(VALUE => $self->Argument); $self->TicketsObj->LimitToActiveStatus; return(1); } RT::Base->_ImportOverlays(); 1; ��������rt-5.0.1/lib/RT/SearchBuilder/Role/�����������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 017511� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/SearchBuilder/Role.pm���������������������������������������������������������������000644 �000765 �000024 �00000004775 14005011336 020064� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::SearchBuilder::Role; use Role::Basic; =head1 NAME RT::SearchBuilder::Role - Common requirements for roles which are consumed by collections =head1 DESCRIPTION Various L<RT::SearchBuilder> (and by inheritance L<DBIx::SearchBuilder>) methods are required by this role. It provides no methods on its own but is simply a contract for other roles to require (usually under the I<RT::SearchBuilder::Role::> namespace). =cut requires $_ for qw( Join Limit NewItem CurrentUser _OpenParen _CloseParen ); 1; ���rt-5.0.1/lib/RT/SearchBuilder/AddAndSort.pm���������������������������������������������������������000644 �000765 �000024 �00000013333 14005011336 021134� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::SearchBuilder::AddAndSort; use base 'RT::SearchBuilder'; =head1 NAME RT::SearchBuilder::AddAndSort - base class for 'add and sort' collections =head1 DESCRIPTION Base class for collections where records can be added to objects with order. See also L<RT::Record::AddAndSort>. Used by L<RT::ObjectScrips> and L<RT::ObjectCustomFields>. As it's about sorting then collection is sorted by SortOrder field. =head1 METHODS =cut sub _Init { my $self = shift; # By default, order by SortOrder $self->OrderByCols( { ALIAS => 'main', FIELD => 'SortOrder', ORDER => 'ASC' }, { ALIAS => 'main', FIELD => 'id', ORDER => 'ASC' }, ); return $self->SUPER::_Init(@_); } =head2 LimitToObjectId Takes id of an object and limits collection. =cut sub LimitToObjectId { my $self = shift; my $id = shift || 0; $self->Limit( FIELD => 'ObjectId', VALUE => $id ); } =head1 METHODS FOR TARGETS Rather than implementing a base class for targets (L<RT::Scrip>, L<RT::CustomField>) and its collections. This class provides class methods to limit target collections. =head2 LimitTargetToNotAdded Takes a collection object and optional list of object ids. Limits the collection to records not added to listed objects or if the list is empty then any object. Use 0 (zero) to mean global. =cut sub LimitTargetToNotAdded { my $self = shift; my $collection = shift; my @ids = @_; my $alias = $self->JoinTargetToAdded($collection => @ids); $collection->Limit( ENTRYAGGREGATOR => 'AND', ALIAS => $alias, FIELD => 'id', OPERATOR => 'IS', VALUE => 'NULL', ); return $alias; } =head2 LimitTargetToAdded L</LimitTargetToNotAdded> with reverse meaning. Takes the same arguments. =cut sub LimitTargetToAdded { my $self = shift; my $collection = shift; my @ids = @_; my $alias = $self->JoinTargetToAdded($collection => @ids); $collection->Limit( ENTRYAGGREGATOR => 'AND', ALIAS => $alias, FIELD => 'id', OPERATOR => 'IS NOT', VALUE => 'NULL', ); return $alias; } =head2 JoinTargetToAdded Joins collection to this table using left join, limits joined table by ids if those are provided. Returns alias of the joined table. Join is cached and re-used for multiple calls. =cut sub JoinTargetToAdded { my $self = shift; my $collection = shift; my @ids = @_; my $alias = $self->JoinTargetToThis( $collection, New => 0, Left => 1 ); return $alias unless @ids; # XXX: we need different EA in join clause, but DBIx::SB # doesn't support them, use IN (X) instead my $dbh = $self->_Handle->dbh; $collection->Limit( LEFTJOIN => $alias, ALIAS => $alias, FIELD => 'ObjectId', OPERATOR => 'IN', VALUE => [ @ids ], ); return $alias; } =head2 JoinTargetToThis Joins target collection to this table using TargetField. Takes New and Left arguments. Use New to avoid caching and re-using this join. Use Left to create LEFT JOIN rather than inner. =cut sub JoinTargetToThis { my $self = shift; my $collection = shift; my %args = ( New => 0, Left => 0, Distinct => 0, @_ ); my $table = $self->Table; my $key = "_sql_${table}_alias"; return $collection->{ $key } if $collection->{ $key } && !$args{'New'}; my $alias = $collection->Join( $args{'Left'} ? (TYPE => 'LEFT') : (), ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => $table, FIELD2 => $self->RecordClass->TargetField, DISTINCT => $args{Distinct}, ); return $alias if $args{'New'}; return $collection->{ $key } = $alias; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/SearchBuilder/Role/Roles.pm���������������������������������������������������������000644 �000765 �000024 �00000031431 14005011336 021135� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::SearchBuilder::Role::Roles; use Role::Basic; use Scalar::Util qw(blessed); =head1 NAME RT::Record::Role::Roles - Common methods for records which "watchers" or "roles" =head1 REQUIRES =head2 L<RT::SearchBuilder::Role> =cut with 'RT::SearchBuilder::Role'; require RT::System; require RT::Principal; require RT::Group; require RT::User; require RT::EmailParser; =head1 PROVIDES =head2 _RoleGroupClass Returns the class name on which role searches should be based. This relates to the internal L<RT::Group/Domain> and distinguishes between roles on the objects being searched and their counterpart roles on containing classes. For example, limiting on L<RT::Queue> roles while searching for L<RT::Ticket>s. The default implementation is: $self->RecordClass which is the class that this collection object searches and instatiates objects for. If you're doing something hinky, you may need to override this method. =cut sub _RoleGroupClass { my $self = shift; return $self->RecordClass; } sub _RoleGroupsJoin { my $self = shift; my %args = (New => 0, Class => '', Name => '', @_); $args{'Class'} ||= $self->_RoleGroupClass; my $name = $args{'Name'}; return $self->{'_sql_role_group_aliases'}{ $args{'Class'} .'-'. $name } if $self->{'_sql_role_group_aliases'}{ $args{'Class'} .'-'. $name } && !$args{'New'}; # If we're looking at a role group on a class that "contains" this record # (i.e. roles on queues for tickets), then we assume that the current # record has a column named after the containing class (i.e. # Tickets.Queue). my $instance = $self->_RoleGroupClass eq $args{Class} ? "id" : $args{Class}; $instance =~ s/^RT:://; # Watcher groups are no longer always created for each record, so we now use left join. # Previously (before 4.4) this used an inner join. my $groups = $self->Join( TYPE => 'left', ALIAS1 => 'main', FIELD1 => $instance, TABLE2 => 'Groups', FIELD2 => 'Instance', ENTRYAGGREGATOR => 'AND', DISTINCT => !!$args{'Type'}, ); $self->Limit( LEFTJOIN => $groups, ALIAS => $groups, FIELD => 'Domain', VALUE => $args{'Class'} .'-Role', CASESENSITIVE => 0, ); $self->Limit( LEFTJOIN => $groups, ALIAS => $groups, FIELD => 'Name', VALUE => $name, CASESENSITIVE => 0, ) if $name; $self->{'_sql_role_group_aliases'}{ $args{'Class'} .'-'. $name } = $groups unless $args{'New'}; return $groups; } sub _GroupMembersJoin { my $self = shift; my %args = (New => 1, GroupsAlias => undef, Left => 1, @_); return $self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} } if $self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} } && !$args{'New'}; my $alias = $self->Join( $args{'Left'} ? (TYPE => 'LEFT') : (), ALIAS1 => $args{'GroupsAlias'}, FIELD1 => 'id', TABLE2 => 'CachedGroupMembers', FIELD2 => 'GroupId', ENTRYAGGREGATOR => 'AND', ); $self->Limit( LEFTJOIN => $alias, ALIAS => $alias, FIELD => 'Disabled', VALUE => 0, ); $self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} } = $alias unless $args{'New'}; return $alias; } =head2 _WatcherJoin Helper function which provides joins to a watchers table both for limits and for ordering. =cut sub _WatcherJoin { my $self = shift; my $groups = $self->_RoleGroupsJoin(@_); my $group_members = $self->_GroupMembersJoin( GroupsAlias => $groups ); # XXX: work around, we must hide groups that # are members of the role group we search in, # otherwise them result in wrong NULLs in Users # table and break ordering. Now, we know that # RT doesn't allow to add groups as members of the # ticket roles, so we just hide entries in CGM table # with MemberId == GroupId from results $self->Limit( LEFTJOIN => $group_members, FIELD => 'GroupId', OPERATOR => '!=', VALUE => "$group_members.MemberId", QUOTEVALUE => 0, ); my $users = $self->Join( TYPE => 'LEFT', ALIAS1 => $group_members, FIELD1 => 'MemberId', TABLE2 => 'Users', FIELD2 => 'id', ); return ($groups, $group_members, $users); } sub RoleLimit { my $self = shift; my %args = ( TYPE => '', CLASS => '', FIELD => undef, OPERATOR => '=', VALUE => undef, @_ ); my $class = $args{CLASS} || $self->_RoleGroupClass; $args{FIELD} ||= 'id' if $args{VALUE} =~ /^\d+$/; my $type = delete $args{TYPE}; if ($type and not $class->HasRole($type)) { RT->Logger->warn("RoleLimit called with invalid role $type for $class"); return; } my $column = $type ? $class->Role($type)->{Column} : undef; # if it's equality op and search by Email or Name then we can preload user # we do it to help some DBs better estimate number of rows and get better plans if ( $args{OPERATOR} =~ /^!?=$/ && (!$args{FIELD} || $args{FIELD} eq 'Name' || $args{FIELD} eq 'EmailAddress') ) { my $o = RT::User->new( $self->CurrentUser ); my $method = !$args{FIELD} ? ($column ? 'Load' : 'LoadByEmail') : $args{FIELD} eq 'EmailAddress' ? 'LoadByEmail': 'Load'; $o->$method( $args{VALUE} ); $args{FIELD} = 'id'; $args{VALUE} = $o->id || 0; } if ( $column and $args{FIELD} and $args{FIELD} eq 'id' ) { $self->Limit( %args, FIELD => $column, ); return; } $args{FIELD} ||= 'EmailAddress'; my ($groups, $group_members, $users); if ( $args{'BUNDLE'} and @{$args{'BUNDLE'}}) { ($groups, $group_members, $users) = @{ $args{'BUNDLE'} }; } else { $groups = $self->_RoleGroupsJoin( Name => $type, Class => $class, New => !$type ); } $self->_OpenParen( $args{SUBCLAUSE} ) if $args{SUBCLAUSE}; if ( $args{OPERATOR} =~ /^IS(?: NOT)?$/i ) { # is [not] empty case $group_members ||= $self->_GroupMembersJoin( GroupsAlias => $groups ); # to avoid joining the table Users into the query, we just join GM # and make sure we don't match records where group is member of itself $self->Limit( LEFTJOIN => $group_members, FIELD => 'GroupId', OPERATOR => '!=', VALUE => "$group_members.MemberId", QUOTEVALUE => 0, ); $self->Limit( %args, ALIAS => $group_members, FIELD => 'GroupId', OPERATOR => $args{OPERATOR}, VALUE => $args{VALUE}, ); } elsif ( $args{OPERATOR} =~ /^!=$|^NOT\s+/i ) { # negative condition case # reverse op $args{OPERATOR} =~ s/!|NOT\s+//i; # XXX: we have no way to build correct "Watcher.X != 'Y'" when condition # "X = 'Y'" matches more then one user so we try to fetch two records and # do the right thing when there is only one exist and semi-working solution # otherwise. my $users_obj = RT::Users->new( $self->CurrentUser ); $users_obj->Limit( FIELD => $args{FIELD}, OPERATOR => $args{OPERATOR}, VALUE => $args{VALUE}, ); $users_obj->OrderBy; $users_obj->RowsPerPage(2); my @users = @{ $users_obj->ItemsArrayRef }; $group_members ||= $self->_GroupMembersJoin( GroupsAlias => $groups ); if ( @users <= 1 ) { my $uid = 0; $uid = $users[0]->id if @users; $self->Limit( LEFTJOIN => $group_members, ALIAS => $group_members, FIELD => 'MemberId', VALUE => $uid, ); $self->Limit( %args, ALIAS => $group_members, FIELD => 'id', OPERATOR => 'IS', VALUE => 'NULL', ); } else { $self->Limit( LEFTJOIN => $group_members, FIELD => 'GroupId', OPERATOR => '!=', VALUE => "$group_members.MemberId", QUOTEVALUE => 0, ); $users ||= $self->Join( TYPE => 'LEFT', ALIAS1 => $group_members, FIELD1 => 'MemberId', TABLE2 => 'Users', FIELD2 => 'id', ); $self->Limit( LEFTJOIN => $users, ALIAS => $users, FIELD => $args{FIELD}, OPERATOR => $args{OPERATOR}, VALUE => $args{VALUE}, CASESENSITIVE => 0, ); $self->Limit( %args, ALIAS => $users, FIELD => 'id', OPERATOR => 'IS', VALUE => 'NULL', ); } } else { # positive condition case $group_members ||= $self->_GroupMembersJoin( GroupsAlias => $groups, New => 1, Left => 0 ); if ($args{FIELD} eq "id") { # Save a left join to Users, if possible $self->Limit( %args, ALIAS => $group_members, FIELD => "MemberId", OPERATOR => $args{OPERATOR}, VALUE => $args{VALUE}, CASESENSITIVE => 0, ); } else { $users ||= $self->Join( TYPE => 'LEFT', ALIAS1 => $group_members, FIELD1 => 'MemberId', TABLE2 => 'Users', FIELD2 => 'id', ); $self->Limit( %args, ALIAS => $users, FIELD => $args{FIELD}, OPERATOR => $args{OPERATOR}, VALUE => $args{VALUE}, CASESENSITIVE => 0, ); } } $self->_CloseParen( $args{SUBCLAUSE} ) if $args{SUBCLAUSE}; if ($args{BUNDLE} and not @{$args{BUNDLE}}) { @{$args{BUNDLE}} = ($groups, $group_members, $users); } return ($groups, $group_members, $users); } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Lifecycle/Ticket.pm�����������������������������������������������������������������000644 �000765 �000024 �00000006252 14005011336 017561� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Lifecycle::Ticket; use base qw(RT::Lifecycle); =head2 Queues Returns L<RT::Queues> collection with queues that use this lifecycle. =cut sub Queues { my $self = shift; require RT::Queues; my $queues = RT::Queues->new( RT->SystemUser ); $queues->Limit( FIELD => 'Lifecycle', VALUE => $self->Name ); return $queues; } =head3 ReminderStatusOnOpen Returns the status that should be used when reminders are opened. =cut sub ReminderStatusOnOpen { my $self = shift; return $self->DefaultStatus('reminder_on_open') || 'open'; } =head3 ReminderStatusOnResolve Returns the status that should be used when reminders are resolved. =cut sub ReminderStatusOnResolve { my $self = shift; return $self->DefaultStatus('reminder_on_resolve') || 'resolved'; } =head2 RegisterRights Ticket lifecycle rights are registered (and thus grantable) at the queue level. =cut sub RegisterRights { my $self = shift; my %rights = $self->RightsDescription( 'ticket' ); require RT::ACE; while ( my ($right, $description) = each %rights ) { next if RT::ACE->CanonicalizeRightName( $right ); RT::Queue->AddRight( Status => $right => $description ); } } 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Lifecycle/Asset.pm������������������������������������������������������������������000644 �000765 �000024 �00000005431 14005011336 017413� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Lifecycle::Asset; __PACKAGE__->RegisterRights; use base qw(RT::Lifecycle); =head2 Catalogs Returns L<RT::Catalogs> collection with catalogs that use this lifecycle. =cut sub Catalogs { my $self = shift; require RT::Catalogs; my $catalogs = RT::Catalogs->new( RT->SystemUser ); $catalogs->Limit( FIELD => 'Lifecycle', VALUE => $self->Name ); return $catalogs; } =head2 RegisterRights Asset lifecycle rights are registered (and thus grantable) at the catalog level. =cut sub RegisterRights { my $self = shift; my %rights = $self->RightsDescription( 'asset' ); require RT::ACE; while ( my ($right, $description) = each %rights ) { next if RT::ACE->CanonicalizeRightName( $right ); RT::Catalog->AddRight( Status => $right => $description ); } } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Graph/Tickets.pm��������������������������������������������������������������������000644 �000765 �000024 �00000032510 14005011336 017102� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Graph::Tickets; use strict; use warnings; =head1 NAME RT::Graph::Tickets - view relations between tickets as graphs =cut unless ($RT::DisableGraphViz) { require GraphViz; GraphViz->import; } our %ticket_status_style = ( new => { fontcolor => '#FF0000', fontsize => 10 }, open => { fontcolor => '#000000', fontsize => 10 }, stalled => { fontcolor => '#DAA520', fontsize => 10 }, resolved => { fontcolor => '#00FF00', fontsize => 10 }, rejected => { fontcolor => '#808080', fontsize => 10 }, deleted => { fontcolor => '#A9A9A9', fontsize => 10 }, ); our %link_style = ( MemberOf => { style => 'solid' }, DependsOn => { style => 'dashed' }, RefersTo => { style => 'dotted' }, ); # We don't use qw() because perl complains about "possible attempt to put comments in qw() list" our @fill_colors = split ' ',<<EOT; #0000FF #8A2BE2 #A52A2A #DEB887 #5F9EA0 #7FFF00 #D2691E #FF7F50 #6495ED #FFF8DC #DC143C #00FFFF #00008B #008B8B #B8860B #A9A9A9 #A9A9A9 #006400 #BDB76B #8B008B #556B2F #FF8C00 #9932CC #8B0000 #E9967A #8FBC8F #483D8B #2F4F4F #2F4F4F #00CED1 #9400D3 #FF1493 #00BFFF #696969 #696969 #1E90FF #B22222 #FFFAF0 #228B22 #FF00FF #DCDCDC #F8F8FF #FFD700 #DAA520 #808080 #808080 #008000 #ADFF2F #F0FFF0 #FF69B4 #CD5C5C #4B0082 #FFFFF0 #F0E68C #E6E6FA #FFF0F5 #7CFC00 #FFFACD #ADD8E6 #F08080 #E0FFFF #FAFAD2 #D3D3D3 #D3D3D3 #90EE90 #FFB6C1 #FFA07A #20B2AA #87CEFA #778899 #778899 #B0C4DE #FFFFE0 #00FF00 #32CD32 #FAF0E6 #FF00FF #800000 #66CDAA #0000CD #BA55D3 #9370D8 #3CB371 #7B68EE #00FA9A #48D1CC #C71585 #191970 #F5FFFA #FFE4E1 #FFE4B5 #FFDEAD #000080 #FDF5E6 #808000 #6B8E23 #FFA500 #FF4500 #DA70D6 #EEE8AA #98FB98 #AFEEEE #D87093 #FFEFD5 #FFDAB9 #CD853F #FFC0CB #DDA0DD #B0E0E6 #800080 #FF0000 #BC8F8F #4169E1 #8B4513 #FA8072 #F4A460 #2E8B57 #FFF5EE #A0522D #C0C0C0 #87CEEB #6A5ACD #708090 #708090 #FFFAFA #00FF7F #4682B4 #D2B48C #008080 #D8BFD8 #FF6347 #40E0D0 #EE82EE #F5DEB3 #FFFF00 #9ACD32 EOT sub gv_escape($) { my $value = shift; $value =~ s{(?=["\\])}{\\}g; return $value; } sub loc { return HTML::Mason::Commands::loc(@_) }; our (%fill_cache, @available_colors) = (); our %property_cb = ( Queue => sub { return $_[0]->QueueObj->Name || $_[0]->Queue }, Status => sub { return loc($_[0]->Status) }, CF => sub { my $values = $_[0]->CustomFieldValues( $_[1] ); return join ', ', map $_->Content, @{ $values->ItemsArrayRef }; }, ); foreach my $field (qw(Subject TimeLeft TimeWorked TimeEstimated)) { $property_cb{ $field } = sub { return $_[0]->$field }, } foreach my $field (qw(Creator LastUpdatedBy Owner)) { $property_cb{ $field } = sub { my $method = $field .'Obj'; return $_[0]->$method->Name; }; } foreach my $field (qw(Requestor Cc AdminCc)) { $property_cb{ $field."s" } = sub { my $method = $field .'Addresses'; return $_[0]->$method; }; } foreach my $field (qw(Told Starts Started Due Resolved LastUpdated Created)) { $property_cb{ $field } = sub { my $method = $field .'Obj'; return $_[0]->$method->AsString; }; } foreach my $field (qw(Members DependedOnBy ReferredToBy)) { $property_cb{ $field } = sub { my $links = $_[0]->$field; my @string; while ( my $link = $links->Next ) { next if UNIVERSAL::isa($link->BaseObj, 'RT::Article') && $link->BaseObj->Disabled; next if $field eq 'ReferredToBy' && UNIVERSAL::isa($link->BaseObj, 'RT::Ticket') && $link->BaseObj->Type eq 'reminder'; my $prefix = UNIVERSAL::isa( $link->BaseObj, 'RT::Ticket' ) ? '' : UNIVERSAL::isa( $link->BaseObj, 'RT::Article' ) ? 'a:' : $link->BaseURI->Scheme . ':'; push @string, $prefix . $link->BaseObj->id; } return join ', ', @string; }; } foreach my $field (qw(MemberOf DependsOn RefersTo)) { $property_cb{ $field } = sub { my $links = $_[0]->$field; my @string; while ( my $link = $links->Next ) { next if UNIVERSAL::isa($link->TargetObj, 'RT::Article') && $link->TargetObj->Disabled; my $prefix = UNIVERSAL::isa( $link->TargetObj, 'RT::Ticket' ) ? '' : UNIVERSAL::isa( $link->TargetObj, 'RT::Article' ) ? 'a:' : $link->TargetURI->Scheme . ':'; push @string, $prefix . $link->TargetObj->id; } return join ', ', @string; }; } sub TicketProperties { my $self = shift; my $user = shift; my @res = ( Basics => [qw(Subject Status Queue TimeLeft TimeWorked TimeEstimated)], # loc_qw People => [qw(Owner Requestors Ccs AdminCcs Creator LastUpdatedBy)], # loc_qw Dates => [qw(Created Starts Started Due Resolved Told LastUpdated)], # loc_qw Links => [qw(MemberOf Members DependsOn DependedOnBy RefersTo ReferredToBy)], # loc_qw ); my $cfs = RT::CustomFields->new( $user ); $cfs->LimitToLookupType('RT::Queue-RT::Ticket'); $cfs->OrderBy( FIELD => 'Name' ); my ($first, %seen) = (1); while ( my $cf = $cfs->Next ) { next if $seen{ lc $cf->Name }++; next if $cf->Type eq 'Image'; if ( $first ) { push @res, 'Custom Fields', # loc []; $first = 0; } push @{ $res[-1] }, 'CF.{'. $cf->Name .'}'; } return @res; } sub _SplitProperty { my $self = shift; my $property = shift; my ($key, @subkeys) = split /\./, $property; foreach ( grep /^{.*}$/, @subkeys ) { s/^{//; s/}$//; } return $key, @subkeys; } sub _PropertiesToFields { my $self = shift; my %args = ( Ticket => undef, Graph => undef, CurrentDepth => 1, @_ ); my @properties; if ( my $tmp = $args{ 'Level-'. $args{'CurrentDepth'} .'-Properties' } ) { @properties = ref $tmp? @$tmp : ($tmp); } my @fields; foreach my $property( @properties ) { my ($key, @subkeys) = $self->_SplitProperty( $property ); unless ( $property_cb{ $key } ) { $RT::Logger->error("Couldn't find property handler for '$key' and '@subkeys' subkeys"); next; } my $label = $key eq 'CF' ? $subkeys[0] : loc($key); push @fields, $label .': '. $property_cb{ $key }->( $args{'Ticket'}, @subkeys ); } return @fields; } sub AddTicket { my $self = shift; my %args = ( Ticket => undef, Properties => [], Graph => undef, CurrentDepth => 1, @_ ); my %node_style = ( style => 'filled,rounded', %{ $ticket_status_style{ $args{'Ticket'}->Status } || {} }, URL => $RT::WebPath .'/Ticket/Display.html?id='. $args{'Ticket'}->id, tooltip => gv_escape( $args{'Ticket'}->Subject || '#'. $args{'Ticket'}->id ), ); my @fields = $self->_PropertiesToFields( %args ); if ( @fields ) { unshift @fields, $args{'Ticket'}->id; my $label = join ' | ', map { s/(?=[{}|><])/\\/g; $_ } @fields; $label = "{ $label }" if ($args{'Direction'} || 'TB') =~ /^(?:TB|BT)$/; $node_style{'label'} = gv_escape( $label ); $node_style{'shape'} = 'record'; } if ( $args{'FillUsing'} ) { my ($key, @subkeys) = $self->_SplitProperty( $args{'FillUsing'} ); my $value; if ( $property_cb{ $key } ) { $value = $property_cb{ $key }->( $args{'Ticket'}, @subkeys ); } else { $RT::Logger->error("Couldn't find property callback for '$key'"); } if ( defined $value && length $value && $value =~ /\S/ ) { my $fill = $fill_cache{ $value }; $fill = $fill_cache{ $value } = shift @available_colors unless $fill; if ( $fill ) { $node_style{'fillcolor'} = $fill; $node_style{'style'} ||= ''; $node_style{'style'} = join ',', split( ',', $node_style{'style'} ), 'filled' unless $node_style{'style'} =~ /\bfilled\b/; } } } $args{'Graph'}->add_node( $args{'Ticket'}->id, %node_style ); } sub TicketLinks { my $self = shift; my %args = ( Ticket => undef, Graph => undef, Direction => 'TB', Seen => undef, SeenEdge => undef, LeadingLink => 'Members', ShowLinks => [], MaxDepth => 0, CurrentDepth => 1, ShowLinkDescriptions => 0, @_ ); my %valid_links = map { $_ => 1 } qw(Members MemberOf RefersTo ReferredToBy DependsOn DependedOnBy); # Validate our link types $args{ShowLinks} = [ grep { $valid_links{$_} } @{$args{ShowLinks}} ]; $args{LeadingLink} = 'Members' unless $valid_links{ $args{LeadingLink} }; unless ( $args{'Graph'} ) { $args{'Graph'} = GraphViz->new( name => 'ticket_links_'. $args{'Ticket'}->id, bgcolor => "transparent", # TODO: patch GraphViz to support all posible RDs rankdir => ($args{'Direction'} || "TB") eq "LR", node => { shape => 'box', style => 'filled,rounded', fillcolor => 'white' }, ); %fill_cache = (); @available_colors = @fill_colors; } $args{'Seen'} ||= {}; if ( $args{'Seen'}{ $args{'Ticket'}->id } && $args{'Seen'}{ $args{'Ticket'}->id } <= $args{'CurrentDepth'} ) { return $args{'Graph'}; } elsif ( ! defined $args{'Seen'}{ $args{'Ticket'}->id } ) { $self->AddTicket( %args ); } $args{'Seen'}{ $args{'Ticket'}->id } = $args{'CurrentDepth'}; return $args{'Graph'} if $args{'MaxDepth'} && $args{'CurrentDepth'} >= $args{'MaxDepth'}; $args{'SeenEdge'} ||= {}; my $show_link_descriptions = $args{'ShowLinkDescriptions'} && RT::Link->can('Description'); foreach my $type ( $args{'LeadingLink'}, @{ $args{'ShowLinks'} } ) { my $links = $args{'Ticket'}->$type(); $links->GotoFirstItem; while ( my $link = $links->Next ) { next if $args{'SeenEdge'}{ $link->id }++; my $target = $link->TargetObj; next unless $target && $target->isa('RT::Ticket'); my $base = $link->BaseObj; next unless $base && $base->isa('RT::Ticket'); my $next = $target->id == $args{'Ticket'}->id? $base : $target; $self->TicketLinks( %args, Ticket => $next, $type eq $args{'LeadingLink'} ? ( CurrentDepth => $args{'CurrentDepth'} + 1 ) : ( MaxDepth => $args{'CurrentDepth'} + 1, CurrentDepth => $args{'CurrentDepth'} + 1 ), ); my $desc; $desc = $link->Description if $show_link_descriptions; $args{'Graph'}->add_edge( # we revers order of member links to get better layout $link->Type eq 'MemberOf' ? ($target->id => $base->id, dir => 'back') : ($base->id => $target->id), %{ $link_style{ $link->Type } || {} }, $desc? (label => gv_escape $desc): (), ); } } return $args{'Graph'}; } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Authen/ExternalAuth/����������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 017724� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Authen/Token.pm���������������������������������������������������������������������000644 �000765 �000024 �00000010523 14005011336 016737� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Authen::Token; use strict; use warnings; use RT::System; 'RT::System'->AddRight(Staff => ManageAuthTokens => 'Manage authentication tokens'); # loc use RT::AuthToken; use RT::AuthTokens; sub UserForAuthString { my $self = shift; my $authstring = shift; my $user = shift; my ($user_id, $cleartext_token) = RT::AuthToken->ParseAuthString($authstring); return unless $user_id; my $user_obj = RT::CurrentUser->new; $user_obj->Load($user_id); return if !$user_obj->Id || $user_obj->Disabled; if (length $user) { my $check_user = RT::CurrentUser->new; $check_user->Load($user); return unless $check_user->Id && $user_obj->Id == $check_user->Id; } my $tokens = RT::AuthTokens->new(RT->SystemUser); $tokens->LimitOwner(VALUE => $user_id); while (my $token = $tokens->Next) { if ($token->IsToken($cleartext_token)) { $token->UpdateLastUsed; return ($user_obj, $token); } } return; } =head1 NAME RT-Authen-Token - token-based authentication =head1 DESCRIPTION Allow for users to generate and login with authentication tokens. Users with the C<ManageAuthTokens> permission will see a new "Auth Tokens" menu item under "Logged in as ____" -> Settings. On that page they will be able to generate new tokens and modify or revoke existing tokens. Once you have an authentication token, you may use it in place of a password to log into RT. (Additionally, L<REST2> allows for using auth tokens with the C<Authorization: token> HTTP header.) One common use case is to use an authentication token as an application-specific password, so that you may revoke that application's access without disturbing other applications. You also need not change your password, since the application never received it. If you have the C<AdminUsers> permission, along with C<ManageAuthTokens>, you may generate, modify, and revoke tokens for other users as well by visiting Admin -> Users -> Select -> (user) -> Auth Tokens. Authentication tokens are stored securely (hashed and salted) in the database just like passwords, and so cannot be recovered after they are generated. =head2 Update your Apache configuration If you are running RT under Apache, add the following directive to your RT Apache configuration to allow RT to access the Authorization header. SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 =cut 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Authen/ExternalAuth.pm��������������������������������������������������������������000644 �000765 �000024 �00000066714 14005011336 020300� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Authen::ExternalAuth; =head1 NAME RT::Authen::ExternalAuth - RT Authentication using External Sources =head1 DESCRIPTION This module provides the ability to authenticate RT users against one or more external data sources at once. It will also allow information about that user to be loaded from the same, or any other available, source as well as allowing multple redundant servers for each method. The functionality currently supports authentication and information from LDAP via the Net::LDAP module, and from any data source that an installed DBI driver is available for. It is also possible to use cookies set by an alternate application for Single Sign-On (SSO) with that application. For example, you may integrate RT with your own website login system so that once users log in to your website, they will be automagically logged in to RT when they access it. =head1 CONFIGURATION C<RT::Authen::ExternalAuth> provides a lot of flexibility with many configuration options. The following describes these configuration options, and provides a complete example. As with all RT configuration, you set these values in C<RT_SiteConfig.pm> or for RT 4.4 or later in a custom configuration file in the directory C<RT_SiteConfig.d>. =over 4 =item C<$ExternalAuthPriority> The order in which the services defined in L</$ExternalSettings> should be used to authenticate users. Once the user has been authenticated by one service, the rest are skipped. You should remove services you don't use. For example, if you're only using C<My_LDAP>, remove C<My_MySQL> and C<My_SSO_Cookie>. Set($ExternalAuthPriority, [ 'My_LDAP', 'My_MySQL', 'My_SSO_Cookie' ] ); =item C<$ExternalInfoPriority> When multiple auth services are available, this value defines the order in which the services defined in L</$ExternalSettings> should be used to get information about users. This includes C<RealName>, telephone numbers etc, but also whether or not the user should be considered disabled. Once a user record is found, no more services are checked. You CANNOT use a SSO cookie to retrieve information. You should remove services you don't use, but you must define at least one service. Set($ExternalInfoPriority, [ 'My_LDAP', 'My_MySQL', ] ); =item C<$AutoCreateNonExternalUsers> If this is set to 1, then users should be autocreated by RT as internal users if they fail to authenticate from an external service. This is useful if you have users outside your organization who might interface with RT, perhaps by sending email to a support email address. =item C<$ExternalSettings> These are the full settings for each external service as a hash of hashes. Note that you may have as many external services as you wish. They will be checked in the order specified in L</$ExternalAuthPriority> and L</$ExternalInfoPriority> directives above. The outer structure is a key with the authentication option (name of external source). The value is a hash reference with configuration keys and values, for example: Set($ExternalSettings, { My_LDAP => { type => 'ldap', ... other options ... }, My_MySQL => { type => 'db', ... other options ... }, ... other sources ... } ); As shown above, each description should have 'type' defined. The following types are supported: =over 4 =item ldap Authenticate against and sync information with LDAP servers. See L<RT::Authen::ExternalAuth::LDAP> for details. =item db Authenticate against and sync information with external RDBMS, supported by Perl's L<DBI> interface. See L<RT::Authen::ExternalAuth::DBI> for details. =item cookie Authenticate by cookie. See L<RT::Authen::ExternalAuth::DBI::Cookie> for details. =back See the modules noted above for configuration options specific to each type. The following apply to all types. =over 4 =item attr_match_list The list of RT attributes that uniquely identify a user. These values are used, in order, to find users in the selected authentication source. Each value specified here must have a mapping in the L</attr_map> section below. You can remove values you don't expect to match, but we recommend using C<Name> and C<EmailAddress> at a minimum. For example: 'attr_match_list' => [ 'Name', 'EmailAddress', ], You should not use items that can map to multiple users (such as a C<RealName> or building name). =item attr_map Mapping of RT attributes on to attributes in the external source. For example, an LDAP mapping might look like: 'attr_map' => { 'Name' => 'sAMAccountName', 'EmailAddress' => 'mail', 'Organization' => 'physicalDeliveryOfficeName', 'RealName' => 'cn', ... }, The values in the mapping (i.e. the fields in external source, the right hand side) can be one of the following: =over 4 =item an attribute/field External field to use. Only first value is used if field is multivalue(could happen in LDAP). For example: EmailAddress => 'mail', =item an array reference The LDAP attributes can also be an arrayref of external fields, for example: WorkPhone => [qw/CompanyPhone Extension/] which will be concatenated together with a space. First values of each field are used in case they have multiple values. =item a subroutine reference The external field can also be a subroutine reference that does mapping. E.g. YYY => sub { my %args = @_; return 'XXX' unless $args{external_entry}; my @values = grep defined && length, $args{external_entry}->get_value('XXX'); return @values; }, The following arguments are passed into the function in a hash: =over 4 =item external_entry For type "ldap", it's an instance of L<Net::LDAP::Entry>. For type "db", it's a hashref of the result row. =item mapping Hash reference of the attr_map value. =item rt_field and external_field The currently processed key and value from the mapping. =back The subroutine is called in 2 modes: when called with external_entry specified, it should return value or list of values, otherwise, it should return the external field list it depends on, so RT could retrieve them at the beginning. =back The keys in the mapping (i.e. the RT fields, the left hand side) may be a user custom field name prefixed with C<UserCF.>, for example C<< 'UserCF.Employee Number' => 'employeeId' >>. Note that this only B<adds> values at the moment, which on single value CFs will remove any old value first. Multiple value CFs may behave not quite how you expect. If the attribute no longer exists on a user in external source, it will be cleared on the RT side as well. You may also prefix any RT custom field name with C<CF.> inside your mapping to add available values to a Select custom field. This effectively takes user attributes in external source and adds the values as selectable options in a CF. It does B<not> set a CF value on any RT object (User, Ticket, Queue, etc). You might use this to populate a ticket Location CF with all the locations of your users so that tickets can be associated with the locations in use. =back =back =head2 Example # Use the below LDAP source for both authentication, as well as user # information Set( $ExternalAuthPriority, ["My_LDAP"] ); Set( $ExternalInfoPriority, ["My_LDAP"] ); # Make users created from LDAP Privileged Set( $UserAutocreateDefaultsOnLogin, { Privileged => 1 } ); # Users should still be autocreated by RT as internal users if they # fail to exist in an external service; this is so requestors (who # are not in LDAP) can still be created when they email in. Set($AutoCreateNonExternalUsers, 1); # Minimal LDAP configuration; see RT::Authen::ExternalAuth::LDAP for # further details and examples Set($ExternalSettings, { 'My_LDAP' => { 'type' => 'ldap', 'server' => 'ldap.example.com', # By not passing 'user' and 'pass' we are using an anonymous # bind, which some servers to not allow 'base' => 'ou=Staff,dc=example,dc=com', 'filter' => '(objectClass=inetOrgPerson)', # Users are allowed to log in via email address or account # name 'attr_match_list' => [ 'Name', 'EmailAddress', ], # Import the following properties of the user from LDAP upon # login 'attr_map' => { 'Name' => 'sAMAccountName', 'EmailAddress' => 'mail', 'RealName' => 'cn', 'WorkPhone' => 'telephoneNumber', 'Address1' => 'streetAddress', 'City' => 'l', 'State' => 'st', 'Zip' => 'postalCode', 'Country' => 'co', }, }, } ); =cut use RT::Authen::ExternalAuth::LDAP; use RT::Authen::ExternalAuth::DBI; use warnings; use strict; sub DoAuth { my ($session,$given_user,$given_pass) = @_; # Get the prioritised list of external authentication services my @auth_services = @{ RT->Config->Get('ExternalAuthPriority') }; my $settings = RT->Config->Get('ExternalSettings'); return (0, "ExternalAuthPriority not defined, please check your configuration file.") unless @auth_services; # This may be used by single sign-on (SSO) authentication mechanisms for bypassing a password check. my $success = 0; # Should have checked if user is already logged in before calling this function, # but just in case, we'll check too. return (0, "User already logged in!") if ($session->{'CurrentUser'} && $session->{'CurrentUser'}->Id); # For each of those services.. foreach my $service (@auth_services) { # Get the full configuration for that service as a hashref my $config = $settings->{$service}; $RT::Logger->debug( "Attempting to use external auth service:", $service); # $username will be the final username we decide to check # This will not necessarily be $given_user my $username = undef; ############################################################# ####################### SSO Check ########################### ############################################################# if ($config->{'type'} eq 'cookie') { # Currently, Cookie authentication is our only SSO method $username = RT::Authen::ExternalAuth::DBI::GetCookieAuth($config); } ############################################################# # If $username is defined, we have a good SSO $username and can # safely bypass the password checking later on; primarily because # it's VERY unlikely we even have a password to check if an SSO succeeded. my $pass_bypass = 0; if(defined($username)) { $RT::Logger->debug("Pass not going to be checked, attempting SSO"); $pass_bypass = 1; } else { # SSO failed and no $user was passed for a login attempt # We only don't return here because the next iteration could be an SSO attempt unless(defined($given_user)) { $RT::Logger->debug("SSO Failed and no user to test with. Nexting"); next; } # We don't have an SSO login, so we will be using the credentials given # on RT's login page to do our authentication. $username = $given_user; # Don't continue unless the service works. # next unless RT::Authen::ExternalAuth::TestConnection($config); # Don't continue unless the $username exists in the external service $RT::Logger->debug("Calling UserExists with \$username ($username) and \$service ($service)"); next unless RT::Authen::ExternalAuth::UserExists($username, $service); } #################################################################### ########## Load / Auto-Create ###################################### #################################################################### # We are now sure that we're talking about a valid RT user. # If the user already exists, load up their info. If they don't # then we need to create the user in RT. # Does user already exist internally to RT? $session->{'CurrentUser'} = RT::CurrentUser->new(); $session->{'CurrentUser'}->Load($username); # Unless we have loaded a valid user with a UserID create one. unless ($session->{'CurrentUser'}->Id) { my $UserObj = RT::User->new($RT::SystemUser); my $create = RT->Config->Get('UserAutocreateDefaultsOnLogin') || RT->Config->Get('AutoCreate'); my ($val, $msg) = $UserObj->Create(%{ref($create) ? $create : {}}, Name => $username, Gecos => $username, ); unless ($val) { $RT::Logger->error( "Couldn't create user $username: $msg" ); next; } $RT::Logger->info( "Autocreated external user", $UserObj->Name, "(", $UserObj->Id, ")"); $RT::Logger->debug("Loading new user (", $username, ") into current session"); $session->{'CurrentUser'}->Load($username); } #################################################################### ########## Authentication ########################################## #################################################################### # If we successfully used an SSO service, then authentication # succeeded. If we didn't then, success is determined by a password # test. $success = 0; if($pass_bypass) { $RT::Logger->debug("Password check bypassed due to SSO method being in use"); $success = 1; } else { $RT::Logger->debug("Password validation required for service - Executing..."); $success = RT::Authen::ExternalAuth::GetAuth($service,$username,$given_pass); } $RT::Logger->debug("Password Validation Check Result: ",$success); # If the password check succeeded then this is our authoritative service # and we proceed to user information update and login. last if $success; } # If we got here and don't have a user loaded we must have failed to # get a full, valid user from an authoritative external source. unless ($session->{'CurrentUser'} && $session->{'CurrentUser'}->Id) { $session->{'CurrentUser'} = RT::CurrentUser->new; return (0, "No User"); } unless($success) { $session->{'CurrentUser'} = RT::CurrentUser->new; return (0, "Password Invalid"); } # Otherwise we succeeded. $RT::Logger->debug("Authentication successful. Now updating user information and attempting login."); #################################################################################################### ############################### The following is auth-method agnostic ############################## #################################################################################################### # If we STILL have a completely valid RT user to play with... # and therefore password has been validated... if ($session->{'CurrentUser'} && $session->{'CurrentUser'}->Id) { # Even if we have JUST created the user in RT, we are going to # reload their information from an external source. This allows us # to be sure that the user the cookie gave us really does exist in # the database, but more importantly, UpdateFromExternal will check # whether the user is disabled or not which we have not been able to # do during auto-create # These are not currently used, but may be used in the future. my $info_updated = 0; my $info_updated_msg = "User info not updated"; if ( @{ RT->Config->Get('ExternalInfoPriority') } ) { # Note that UpdateUserInfo does not care how we authenticated the user # It will look up user info from whatever is specified in $RT::ExternalInfoPriority ($info_updated,$info_updated_msg) = RT::Authen::ExternalAuth::UpdateUserInfo($session->{'CurrentUser'}->Name); } # Now that we definitely have up-to-date user information, # if the user is disabled, kick them out. Now! if ($session->{'CurrentUser'}->UserObj->Disabled) { $session->{'CurrentUser'} = RT::CurrentUser->new; return (0, "User account disabled, login denied"); } } # If we **STILL** have a full user and the session hasn't already been deleted # This If/Else is logically unnecessary, but it doesn't hurt to leave it here # just in case. Especially to be a double-check to future modifications. if ($session->{'CurrentUser'} && $session->{'CurrentUser'}->Id) { $RT::Logger->info( "Successful login for", $session->{'CurrentUser'}->Name, "from", ( RT::Interface::Web::RequestENV('REMOTE_ADDR') || 'UNKNOWN') ); # Do not delete the session. User stays logged in and # autohandler will not check the password again my $cu = $session->{CurrentUser}; RT::Interface::Web::InstantiateNewSession(); $session->{CurrentUser} = $cu; } else { # Make SURE the session is purged to an empty user. $session->{'CurrentUser'} = RT::CurrentUser->new; return (0, "Failed to authenticate externally"); # This will cause autohandler to request IsPassword # which will in turn call IsExternalPassword } return (1, "Successful login"); } sub UpdateUserInfo { my $username = shift; # Prepare for the worst... my $found = 0; my $updated = 0; my $msg = "User NOT updated"; my $user_disabled = RT::Authen::ExternalAuth::UserDisabled($username); my $UserObj = RT::User->new(RT->SystemUser); $UserObj->Load($username); # If user is disabled, set the RT::Principal to disabled and return out of the function. # I think it's a waste of time and energy to update a user's information if they are disabled # and it could be a security risk if they've updated their external information with some # carefully concocted code to try to break RT - worst case scenario, but they have been # denied access after all, don't take any chances. # If someone gives me a good enough reason to do it, # then I'll update all the info for disabled users if ($user_disabled) { unless ( $UserObj->Disabled ) { # Make sure principal is disabled in RT my ($val, $message) = $UserObj->SetDisabled(1); # Log what has happened $RT::Logger->info("User marked as DISABLED (", $username, ") per External Service", "($val, $message)\n"); $msg = "User Disabled"; } return ($updated, $msg); } # Make sure principal is not disabled in RT if ( $UserObj->Disabled ) { my ($val, $message) = $UserObj->SetDisabled(0); unless ( $val ) { $RT::Logger->error("Failed to enable user ($username) per External Service: ".($message||'')); return ($updated, "Failed to enable"); } $RT::Logger->info("User ($username) was disabled, marked as ENABLED ", "per External Service", "($val, $message)\n"); } # Update their info from external service using the username as the lookup key # CanonicalizeUserInfo will work out for itself which service to use # Passing it a service instead could break other RT code my %args = (Name => $username); $UserObj->CanonicalizeUserInfo(\%args); # For each piece of information returned by CanonicalizeUserInfo, # run the Set method for that piece of info to change it for the user my @results = $UserObj->Update( ARGSRef => \%args, AttributesRef => [ grep { !/^(?:User)?CF\./ } keys %args ], ); $RT::Logger->debug("UPDATED user $username: $_") for @results; AddCustomFieldValue( %args ); $UserObj->UpdateObjectCustomFieldValues( %args ); # Confirm update success $updated = 1; $RT::Logger->debug( "UPDATED user (", $username, ") from External Service\n"); $msg = 'User updated'; return ($updated, $msg); } sub GetAuth { # Request a username/password check from the specified service # This is only valid for non-SSO services. my ($service,$username,$password) = @_; my $success = 0; # Get the full configuration for that service as a hashref my $config = RT->Config->Get('ExternalSettings')->{$service}; # And then act accordingly depending on what type of service it is. # Right now, there is only code for DBI and LDAP non-SSO services if ($config->{'type'} eq 'db') { $success = RT::Authen::ExternalAuth::DBI::GetAuth($service,$username,$password); $RT::Logger->debug("DBI password validation result:",$success); } elsif ($config->{'type'} eq 'ldap') { $success = RT::Authen::ExternalAuth::LDAP::GetAuth($service,$username,$password); $RT::Logger->debug("LDAP password validation result:",$success); } return $success; } sub UserExists { # Request a username/password check from the specified service # This is only valid for non-SSO services. my ($username,$service) = @_; my $success = 0; # Get the full configuration for that service as a hashref my $config = RT->Config->Get('ExternalSettings')->{$service}; # And then act accordingly depending on what type of service it is. # Right now, there is only code for DBI and LDAP non-SSO services if ($config->{'type'} eq 'db') { $success = RT::Authen::ExternalAuth::DBI::UserExists($username,$service); } elsif ($config->{'type'} eq 'ldap') { $success = RT::Authen::ExternalAuth::LDAP::UserExists($username,$service); } return $success; } sub UserDisabled { my $username = shift; my $user_disabled = 0; my @info_services = @{ RT->Config->Get('ExternalInfoPriority') }; # For each named service in the list # Check to see if the user is found in the external service # If not found, jump to next service # If found, check to see if user is considered disabled by the service # Then update the user's info in RT and return foreach my $service (@info_services) { # Get the external config for this service as a hashref my $config = RT->Config->Get('ExternalSettings')->{$service}; # If it's a DBI config: if ($config->{'type'} eq 'db') { unless(RT::Authen::ExternalAuth::DBI::UserExists($username,$service)) { $RT::Logger->debug("User (", $username, ") doesn't exist in service (", $service, ") - Cannot update information - Skipping..."); next; } $user_disabled = RT::Authen::ExternalAuth::DBI::UserDisabled($username,$service); } elsif ($config->{'type'} eq 'ldap') { unless(RT::Authen::ExternalAuth::LDAP::UserExists($username,$service)) { $RT::Logger->debug("User (", $username, ") doesn't exist in service (", $service, ") - Cannot update information - Skipping..."); next; } $user_disabled = RT::Authen::ExternalAuth::LDAP::UserDisabled($username,$service); } } return $user_disabled; } sub AddCustomFieldValue { my %args = @_; foreach my $rtfield ( keys %args ) { next unless $rtfield =~ /^CF\.(.+)$/i; my $cf_name = $1; my $cfv_name = $args{$rtfield} or next; my $cf = RT::CustomField->new($RT::SystemUser); my ( $status, $msg ) = $cf->Load($cf_name); unless ($status) { $RT::Logger->error("Couldn't load CF [$cf_name]: $msg"); next; } my $cfv = RT::CustomFieldValue->new($RT::SystemUser); $cfv->LoadByCols( CustomField => $cf->id, Name => $cfv_name ); if ( $cfv->id ) { $RT::Logger->debug("Custom Field '$cf_name' already has '$cfv_name' for a value"); next; } ( $status, $msg ) = $cf->AddValue( Name => $cfv_name ); if ($status) { $RT::Logger->debug("Added '$cfv_name' to Custom Field '$cf_name' [$msg]"); } else { $RT::Logger->error("Couldn't add '$cfv_name' to '$cf_name' [$msg]"); } } return; } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������rt-5.0.1/lib/RT/Authen/ExternalAuth/DBI.pm����������������������������������������������������������000644 �000765 �000024 �00000061607 14005011336 020672� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Authen::ExternalAuth::DBI; use DBI; use RT::Authen::ExternalAuth::DBI::Cookie; use RT::Util; use List::MoreUtils 'uniq'; use warnings; use strict; =head1 NAME RT::Authen::ExternalAuth::DBI - External database source for RT authentication =head1 DESCRIPTION Provides the database implementation for L<RT::Authen::ExternalAuth>. =head1 SYNOPSIS Set($ExternalSettings, { 'My_MySQL' => { 'type' => 'db', 'dbi_driver' => 'DBI_DRIVER', 'server' => 'server.domain.tld', 'port' => 'DB_PORT', 'user' => 'DB_USER', 'pass' => 'DB_PASS', 'database' => 'DB_NAME', 'table' => 'USERS_TABLE', 'u_field' => 'username', 'p_field' => 'password', # Example of custom hashed password check # (See below for security concerns with this implementation) #'p_check' => sub { # my ($hash_from_db, $password) = @_; # return $hash_from_db eq function($password); #}, 'p_enc_pkg' => 'Crypt::MySQL', 'p_enc_sub' => 'password', 'p_salt' => 'SALT', 'd_field' => 'disabled', 'd_values' => ['0'], 'attr_match_list' => [ 'Gecos', 'Name', ], 'attr_map' => { 'Name' => 'username', 'EmailAddress' => 'email', 'Gecos' => 'userID', }, }, } ); =head1 CONFIGURATION DBI-specific options are described here. Shared options are described in L<RT::Authen::ExternalAuth>. The example in the L</SYNOPSIS> lists all available options and they are described below. See the L<DBI> module for details on debugging connection issues. =over 4 =item dbi_driver The name of the Perl DBI driver to use (e.g. mysql, Pg, SQLite). =item server The server hosting the database. =item port The port to use to connect on (e.g. 3306). =item user The database user for the connection. =item pass The password for the database user. =item database The database name. =item table The database table containing the user information to check against. =item u_field The field in the table that holds usernames =item p_field The field in the table that holds passwords =item p_check Optional. An anonymous subroutine definition used to check the (presumably hashed) passed from the database with the password entered by the user logging in. The subroutine should return true on success and false on failure. The configuration options C<p_enc_pkg> and C<p_enc_sub> will be ignored when C<p_check> is defined. An example, where C<FooBar()> is some external hashing function: p_check => sub { my ($hash_from_db, $password) = @_; return $hash_from_db eq FooBar($password); }, Importantly, the C<p_check> subroutine allows for arbitrarily complex password checking unlike C<p_enc_pkg> and C<p_enc_sub>. Please note, the use of the C<eq> operator in the C<p_check> example above introduces a timing sidechannel vulnerability. (It was left there for clarity of the example.) There is a comparison function available in RT that is hardened against timing attacks. The comparison from the above example could be re-written with it like this: p_check => sub { my ($hash_from_db, $password) = @_; return RT::Util::constant_time_eq($hash_from_db, FooBar($password)); }, =item p_enc_pkg, p_enc_sub The Perl package and subroutine used to encrypt passwords from the database. For example, if the passwords are stored using the MySQL v3.23 "PASSWORD" function, then you will need the L<Crypt::MySQL> C<password> function, but for the MySQL4+ password you will need L<Crypt::MySQL>'s C<password41>. Alternatively, you could use L<Digest::MD5> C<md5_hex> or any other encryption subroutine you can load in your Perl installation. =item p_salt If p_enc_sub takes a salt as a second parameter then set it here. =item d_field, d_values The field and values in the table that determines if a user should be disabled. For example, if the field is 'user_status' and the values are ['0','1','2','disabled'] then the user will be disabled if their user_status is set to '0','1','2' or the string 'disabled'. Otherwise, they will be considered enabled. =back =cut sub GetAuth { my ($service, $username, $password) = @_; my $config = RT->Config->Get('ExternalSettings')->{$service}; $RT::Logger->debug( "Trying external auth service:",$service); my $db_table = $config->{'table'}; my $db_u_field = $config->{'u_field'}; my $db_p_field = $config->{'p_field'}; my $db_p_check = $config->{'p_check'}; my $db_p_enc_pkg = $config->{'p_enc_pkg'}; my $db_p_enc_sub = $config->{'p_enc_sub'}; my $db_p_salt = $config->{'p_salt'}; # Set SQL query and bind parameters my $query = "SELECT $db_u_field,$db_p_field FROM $db_table WHERE $db_u_field=?"; my @params = ($username); # Uncomment this to trace basic DBI information and drop it in a log for debugging # DBI->trace(1,'/tmp/dbi.log'); # Get DBI handle object (DBH), do SQL query, kill DBH my $dbh = _GetBoundDBIObj($config); return 0 unless $dbh; my $results_hashref = $dbh->selectall_hashref($query,$db_u_field,{},@params); $dbh->disconnect(); my $num_users_returned = scalar keys %$results_hashref; if($num_users_returned != 1) { # FAIL # FAIL because more than one user returned. Users MUST be unique! if ((scalar keys %$results_hashref) > 1) { $RT::Logger->info( $service, "AUTH FAILED", $username, "More than one user with that username!"); } # FAIL because no users returned. Users MUST exist! if ((scalar keys %$results_hashref) < 1) { $RT::Logger->info( $service, "AUTH FAILED", $username, "User not found in database!"); } # Drop out to next external authentication service return 0; } # Get the user's password from the database query result my $pass_from_db = $results_hashref->{$username}->{$db_p_field}; if ( $db_p_check ) { unless ( ref $db_p_check eq 'CODE' ) { $RT::Logger->error( "p_check for $service is not a code" ); return 0; } my $check = 0; local $@; eval { $check = $db_p_check->( $pass_from_db, $password ); 1; } or do { $RT::Logger->error( "p_check for $service failed: $@" ); return 0; }; unless ( $check ) { $RT::Logger->info( "$service AUTH FAILED for $username: Password Incorrect (via p_check)" ); } else { $RT::Logger->info( (caller(0))[3], "External Auth OK (", $service, "):", $username); } return $check; } # This is the encryption package & subroutine passed in by the config file $RT::Logger->debug( "Encryption Package:", $db_p_enc_pkg); $RT::Logger->debug( "Encryption Subroutine:", $db_p_enc_sub); # Use config info to auto-load the perl package needed for password encryption # Jump to next external authentication service on failure $db_p_enc_pkg->require or do { $RT::Logger->error("AUTH FAILED, Couldn't Load Password Encryption Package. Error: $@"); return 0; }; my $encrypt = $db_p_enc_pkg->can($db_p_enc_sub); if (defined($encrypt)) { # If the package given can perform the subroutine given, then use it to compare the # password given with the password pulled from the database. # Jump to the next external authentication service if they don't match if(defined($db_p_salt)) { $RT::Logger->debug("Using salt:",$db_p_salt); unless (RT::Util::constant_time_eq(${encrypt}->($password,$db_p_salt), $pass_from_db)) { $RT::Logger->info( $service, "AUTH FAILED", $username, "Password Incorrect"); return 0; } } else { unless (RT::Util::constant_time_eq(${encrypt}->($password), $pass_from_db)) { $RT::Logger->info( $service, "AUTH FAILED", $username, "Password Incorrect"); return 0; } } } else { # If the encryption package can't perform the request subroutine, # dump an error and jump to the next external authentication service. $RT::Logger->error($service, "AUTH FAILED", "The encryption package you gave me (", $db_p_enc_pkg, ") does not support the encryption method you specified (", $db_p_enc_sub, ")"); return 0; } # Any other checks you want to add? Add them here. # If we've survived to this point, we're good. $RT::Logger->info( (caller(0))[3], "External Auth OK (", $service, "):", $username); return 1; } sub CanonicalizeUserInfo { my ($service, $key, $value) = @_; my $found = 0; my %params = (Name => undef, EmailAddress => undef, RealName => undef); # Load the config my $config = RT->Config->Get('ExternalSettings')->{$service}; # Figure out what's what my $table = $config->{'table'}; unless ($table) { $RT::Logger->critical( (caller(0))[3], "No table given"); # Drop out to the next external information service return ($found, %params); } unless ($key && $value){ $RT::Logger->critical( (caller(0))[3], " Nothing to look-up given"); # Drop out to the next external information service return ($found, %params); } # "where" refers to WHERE section of SQL query my ($where_key,$where_value) = ("@{[ $key ]}",$value); # Get the list of unique attrs we need my @attrs; for my $field ( values %{ $config->{'attr_map'} } ) { if ( ref $field eq 'CODE' ) { push @attrs, $field->(); } elsif ( ref $field eq 'ARRAY' ) { push @attrs, @$field; } else { push @attrs, $field; } } my $fields = join(',', uniq grep defined && length, @attrs); my $query = "SELECT $fields FROM $table WHERE $where_key=?"; my @bind_params = ($where_value); # Uncomment this to trace basic DBI throughput in a log # DBI->trace(1,'/tmp/dbi.log'); my $dbh = _GetBoundDBIObj($config); my $results_hashref = $dbh->selectall_hashref($query,$key,{},@bind_params); $dbh->disconnect(); if ((scalar keys %$results_hashref) != 1) { # If returned users <> 1, we have no single unique user, so prepare to die my $death_msg; if ((scalar keys %$results_hashref) == 0) { # If no user... $death_msg = "No User Found in External Database!"; } else { # If more than one user... $death_msg = "More than one user found in External Database with that unique identifier!"; } # Log the death $RT::Logger->info( (caller(0))[3], "INFO CHECK FAILED", "Key: $key", "Value: $value", $death_msg); # $found remains as 0 # Drop out to next external information service return ($found, %params); } # We haven't dropped out, so DB search must have succeeded with # exactly 1 result. Get the result and set $found to 1 my $result = $results_hashref->{$value}; # Use the result to populate %params for every key we're given in the config foreach my $key (keys(%{$config->{'attr_map'}})) { my $external_field = $config->{'attr_map'}{$key}; my @list = grep defined && length, ref $external_field eq 'ARRAY' ? @$external_field : ($external_field); unless (@list) { $RT::Logger->error("Invalid attr mapping for $key, no defined fields"); next; } my @values; foreach my $e (@list) { if ( ref $e eq 'CODE' ) { push @values, $e->( external_entry => $result, mapping => $config->{'attr_map'}, rt_field => $key, external_field => $external_field, ); } elsif ( ref $e ) { $RT::Logger->error("Invalid type of attr mapping for $key, value is $e"); next; } else { push @values, $result->{$e}; } } $params{$key} = join ' ', grep defined && length, @values; } $found = 1; return ($found, %params); } sub UserExists { my ($username,$service) = @_; my $config = RT->Config->Get('ExternalSettings')->{$service}; my $table = $config->{'table'}; my $u_field = $config->{'u_field'}; my $query = "SELECT $u_field FROM $table WHERE $u_field=?"; my @bind_params = ($username); # Uncomment this to do a basic trace on DBI information and log it # DBI->trace(1,'/tmp/dbi.log'); # Get DBI Object, do the query, disconnect my $dbh = _GetBoundDBIObj($config); my $results_hashref = $dbh->selectall_hashref($query,$u_field,{},@bind_params); $dbh->disconnect(); my $num_of_results = scalar keys %$results_hashref; if ($num_of_results > 1) { # If more than one result returned, die because we the username field should be unique! $RT::Logger->debug( "Disable Check Failed :: (", $service, ")", $username, "More than one user with that username!"); return 0; } elsif ($num_of_results < 1) { # If 0 or negative integer, no user found or major failure $RT::Logger->debug( "Disable Check Failed :: (", $service, ")", $username, "User not found"); return 0; } # Number of results is exactly one, so we found the user we were looking for return 1; } sub UserDisabled { my ($username,$service) = @_; # FIRST, check that the user exists in the DBI service unless(UserExists($username,$service)) { $RT::Logger->debug("User (",$username,") doesn't exist! - Assuming not disabled for the purposes of disable checking"); return 0; } # Get the necessary config info my $config = RT->Config->Get('ExternalSettings')->{$service}; my $table = $config->{'table'}; my $u_field = $config->{'u_field'}; my $disable_field = $config->{'d_field'}; my $disable_values_list = $config->{'d_values'}; unless ($disable_field) { # If we don't know how to check for disabled users, consider them all enabled. $RT::Logger->debug("No d_field specified for this DBI service (", $service, "), so considering all users enabled"); return 0; } my $query = "SELECT $u_field,$disable_field FROM $table WHERE $u_field=?"; my @bind_params = ($username); # Uncomment this to do a basic trace on DBI information and log it # DBI->trace(1,'/tmp/dbi.log'); # Get DBI Object, do the query, disconnect my $dbh = _GetBoundDBIObj($config); my $results_hashref = $dbh->selectall_hashref($query,$u_field,{},@bind_params); $dbh->disconnect(); my $num_of_results = scalar keys %$results_hashref; if ($num_of_results > 1) { # If more than one result returned, die because we the username field should be unique! $RT::Logger->debug( "Disable Check Failed :: (", $service, ")", $username, "More than one user with that username! - Assuming not disabled"); # Drop out to next service for an info check return 0; } elsif ($num_of_results < 1) { # If 0 or negative integer, no user found or major failure $RT::Logger->debug( "Disable Check Failed :: (", $service, ")", $username, "User not found - Assuming not disabled"); # Drop out to next service for an info check return 0; } else { # otherwise all should be well # $user_db_disable_value = The value for "disabled" returned from the DB my $user_db_disable_value = $results_hashref->{$username}->{$disable_field}; # For each of the values in the (list of values that we consider to mean the user is disabled).. foreach my $disable_value (@{$disable_values_list}){ $RT::Logger->debug( "DB Disable Check:", "User's Val is $user_db_disable_value,", "Checking against: $disable_value"); # If the value from the DB matches a value from the list, the user is disabled. if ($user_db_disable_value eq $disable_value) { return 1; } } # If we've not returned yet, the user can't be disabled return 0; } $RT::Logger->crit("It is seriously not possible to run this code.. what the hell did you do?!"); return 0; } sub GetCookieAuth { $RT::Logger->debug( (caller(0))[3], "Checking Browser Cookies for an Authenticated User"); # Get our cookie and database info... my $config = shift; my $username = undef; my $cookie_name = $config->{'name'}; my $cookie_value = RT::Authen::ExternalAuth::DBI::Cookie::GetCookieVal($cookie_name); unless($cookie_value){ return $username; } # The table mapping usernames to the Username Match Key my $u_table = $config->{'u_table'}; # The username field in that table my $u_field = $config->{'u_field'}; # The field that contains the Username Match Key my $u_match_key = $config->{'u_match_key'}; # The table mapping cookie values to the Cookie Match Key my $c_table = $config->{'c_table'}; # The cookie field in that table - The same as the cookie name if unspecified my $c_field = $config->{'c_field'}; # The field that connects the Cookie Match Key my $c_match_key = $config->{'c_match_key'}; # These are random characters to assign as table aliases in SQL # It saves a lot of garbled code later on my $u_table_alias = "u"; my $c_table_alias = "c"; # $tables will be passed straight into the SQL query # I don't see this as a security issue as only the admin may modify the config file anyway my $tables; # If the tables are the same, then the aliases should be the same # and the match key becomes irrelevant. Ensure this all works out # fine by setting both sides the same. In either case, set an # appropriate value for $tables. if ($u_table eq $c_table) { $u_table_alias = $c_table_alias; $u_match_key = $c_match_key; $tables = "$c_table $c_table_alias"; } else { $tables = "$c_table $c_table_alias, $u_table $u_table_alias"; } my $select_fields = "$u_table_alias.$u_field"; my $where_statement = "$c_table_alias.$c_field = ? AND $c_table_alias.$c_match_key = $u_table_alias.$u_match_key"; my $query = "SELECT $select_fields FROM $tables WHERE $where_statement"; my @params = ($cookie_value); # Use this if you need to debug the DBI SQL process # DBI->trace(1,'/tmp/dbi.log'); my $dbh = _GetBoundDBIObj(RT->Config->Get('ExternalSettings')->{$config->{'db_service_name'}}); my $query_result_arrayref = $dbh->selectall_arrayref($query,{},@params); $dbh->disconnect(); # The log messages say it all here... my $num_rows = scalar @$query_result_arrayref; if ($num_rows < 1) { $RT::Logger->info( "AUTH FAILED", $cookie_name, "Cookie value not found in database.", "User passed an authentication token they were not given by us!", "Is this nefarious activity?"); } elsif ($num_rows > 1) { $RT::Logger->error( "AUTH FAILED", $cookie_name, "Cookie's value is duplicated in the database! This should not happen!!"); } else { $username = $query_result_arrayref->[0][0]; } if ($username) { $RT::Logger->debug( "User (", $username, ") was authenticated by a browser cookie"); } else { $RT::Logger->debug( "No user was authenticated by browser cookie"); } return $username; } # {{{ sub _GetBoundDBIObj sub _GetBoundDBIObj { # Config as hashref. my $config = shift; # Extract the relevant information from the config. my $db_server = $config->{'server'}; my $db_user = $config->{'user'}; my $db_pass = $config->{'pass'}; my $db_database = $config->{'database'}; my $db_port = $config->{'port'}; my $dbi_driver = $config->{'dbi_driver'}; # Use config to create a DSN line for the DBI connection my $dsn; if ( $dbi_driver eq 'SQLite' ) { $dsn = "dbi:$dbi_driver:$db_database"; } else { $dsn = "dbi:$dbi_driver:database=$db_database;host=$db_server;port=$db_port"; } # Now let's get connected my $dbh = DBI->connect($dsn, $db_user, $db_pass,{RaiseError => 1, AutoCommit => 0 }) or die $DBI::errstr; # If we didn't die, return the DBI object handle # and hope it's treated sensibly and correctly # destroyed by the calling code return $dbh; } # }}} RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Authen/ExternalAuth/LDAP.pm���������������������������������������������������������000644 �000765 �000024 �00000063501 14005011336 021007� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Authen::ExternalAuth::LDAP; use Net::LDAP qw(LDAP_SUCCESS LDAP_PARTIAL_RESULTS); use Net::LDAP::Util qw(ldap_error_name escape_filter_value); use Net::LDAP::Filter; use warnings; use strict; =head1 NAME RT::Authen::ExternalAuth::LDAP - LDAP source for RT authentication =head1 DESCRIPTION Provides the LDAP implementation for L<RT::Authen::ExternalAuth>. =head1 SYNOPSIS Set($ExternalSettings, { # AN EXAMPLE LDAP SERVICE 'My_LDAP' => { 'type' => 'ldap', 'server' => 'server.domain.tld', 'user' => 'rt_ldap_username', 'pass' => 'rt_ldap_password', 'base' => 'ou=Organisational Unit,dc=domain,dc=TLD', 'filter' => '(FILTER_STRING)', 'd_filter' => '(FILTER_STRING)', 'group' => 'GROUP_NAME', 'group_attr' => 'GROUP_ATTR', 'tls' => { verify => "require", capath => "/path/to/ca.pem" }, 'net_ldap_args' => [ version => 3 ], 'attr_match_list' => [ 'Name', 'EmailAddress', ], 'attr_map' => { 'Name' => 'sAMAccountName', 'EmailAddress' => 'mail', 'Organization' => 'physicalDeliveryOfficeName', 'RealName' => 'cn', 'Gecos' => 'sAMAccountName', 'WorkPhone' => 'telephoneNumber', 'Address1' => 'streetAddress', 'City' => 'l', 'State' => 'st', 'Zip' => 'postalCode', 'Country' => 'co' }, }, } ); =head1 CONFIGURATION LDAP-specific options are described here. Shared options are described in L<RT::Authen::ExternalAuth>. The example in the L</SYNOPSIS> lists all available options and they are described below. Note that many of these values are specific to LDAP, so you should consult your LDAP documentation for details. =over 4 =item server The server hosting the LDAP or AD service. =item user, pass The username and password RT should use to connect to the LDAP server. If you can bind to your LDAP server anonymously you may be able to omit these options. Many servers do not allow anonymous binds, or restrict what information they can see or how much information they can retrieve. If your server does not allow anonymous binds then you must have a service account created for this component to function. =item base The LDAP search base. =item filter The filter to use to match RT users. You B<must> specify it and it B<must> be a valid LDAP filter encased in parentheses. For example: filter => '(objectClass=*)', =item d_filter The filter that will only match disabled users. Optional. B<Must> be a valid LDAP filter encased in parentheses. For example with Active Directory the following can be used: d_filter => '(userAccountControl:1.2.840.113556.1.4.803:=2)' =item group Does authentication depend on group membership? What group name? =item group_attr What is the attribute for the group object that determines membership? =item group_scope What is the scope of the group search? C<base>, C<one> or C<sub>. Optional; defaults to C<base>, which is good enough for most cases. C<sub> is appropriate when you have nested groups. =item group_attr_value What is the attribute of the user entry that should be matched against group_attr above? Optional; defaults to C<dn>. =item tls Should we try to use TLS to encrypt connections? Either a scalar, for simple enabling, or a hash of values to pass to L<Net::LDAP/start_tls>. By default, L<Net::LDAP> does B<no> certificate validation! To validate certificates, pass: tls => { verify => 'require', cafile => "/etc/ssl/certs/ca.pem", # Path CA file }, =item net_ldap_args What other args should be passed to Net::LDAP->new($host,@args)? =back =cut sub GetAuth { my ($service, $username, $password) = @_; my $config = RT->Config->Get('ExternalSettings')->{$service}; $RT::Logger->debug( "Trying external auth service:",$service); my $base = $config->{'base'}; my $filter = $config->{'filter'}; my $group = $config->{'group'}; my $group_attr = $config->{'group_attr'}; my $group_attr_val = $config->{'group_attr_value'} || 'dn'; my $group_scope = $config->{'group_scope'} || 'base'; my $attr_map = $config->{'attr_map'}; my @attrs = ('dn'); # Make sure we fetch the user attribute we'll need for the group check push @attrs, $group_attr_val unless lc $group_attr_val eq 'dn'; # Empty parentheses as filters cause Net::LDAP to barf. # We take care of this by using Net::LDAP::Filter, but # there's no harm in fixing this right now. undef $filter if defined $filter and $filter eq "()"; # Now let's get connected my $ldap = _GetBoundLdapObj($config); return 0 unless ($ldap); $filter = Net::LDAP::Filter->new( '(&(' . $attr_map->{'Name'} . '=' . escape_filter_value($username) . ')' . $filter . ')' ); $RT::Logger->debug( "LDAP Search === ", "Base:", $base, "== Filter:", $filter->as_string, "== Attrs:", join(',',@attrs)); my $ldap_msg = $ldap->search( base => $base, filter => $filter, attrs => \@attrs); unless ($ldap_msg->code == LDAP_SUCCESS || $ldap_msg->code == LDAP_PARTIAL_RESULTS) { $RT::Logger->debug( "search for", $filter->as_string, "failed:", ldap_error_name($ldap_msg->code), $ldap_msg->code); # Didn't even get a partial result - jump straight to the next external auth service return 0; } unless ($ldap_msg->count == 1) { $RT::Logger->info( $service, "AUTH FAILED:", $username, "User not found or more than one user found"); # We got no user, or too many users.. jump straight to the next external auth service return 0; } my $ldap_entry = $ldap_msg->first_entry; my $ldap_dn = $ldap_entry->dn; $RT::Logger->debug( "Found LDAP DN:", $ldap_dn); # THIS bind determines success or failure on the password. $ldap_msg = $ldap->bind($ldap_dn, password => $password); unless ($ldap_msg->code == LDAP_SUCCESS) { $RT::Logger->info( $service, "AUTH FAILED", $username, "(can't bind:", ldap_error_name($ldap_msg->code), $ldap_msg->code, ")"); # Could not bind to the LDAP server as the user we found with the password # we were given, therefore the password must be wrong so we fail and # jump straight to the next external auth service return 0; } # The user is authenticated ok, but is there an LDAP Group to check? if ($group) { my $group_val = lc $group_attr_val eq 'dn' ? $ldap_dn : $ldap_entry->get_value($group_attr_val); # Fallback to the DN if the user record doesn't have a value unless (defined $group_val) { $group_val = $ldap_dn; $RT::Logger->debug("Attribute '$group_attr_val' has no value; falling back to '$group_val'"); } # We only need the dn for the actual group since all we care about is existence @attrs = qw(dn); $filter = Net::LDAP::Filter->new("(${group_attr}=" . escape_filter_value($group_val) . ")"); $RT::Logger->debug( "LDAP Search === ", "Base:", $group, "== Scope:", $group_scope, "== Filter:", $filter->as_string, "== Attrs:", join(',',@attrs)); $ldap_msg = $ldap->search( base => $group, filter => $filter, attrs => \@attrs, scope => $group_scope); # And the user isn't a member: unless ($ldap_msg->code == LDAP_SUCCESS || $ldap_msg->code == LDAP_PARTIAL_RESULTS) { $RT::Logger->critical( "Search for", $filter->as_string, "failed:", ldap_error_name($ldap_msg->code), $ldap_msg->code); # Fail auth - jump to next external auth service return 0; } unless ($ldap_msg->count == 1) { $RT::Logger->debug( "LDAP group membership check returned", $ldap_msg->count, "results" ); $RT::Logger->info( $service, "AUTH FAILED:", $username); # Fail auth - jump to next external auth service return 0; } } # Any other checks you want to add? Add them here. # If we've survived to this point, we're good. $RT::Logger->info( (caller(0))[3], "External Auth OK (", $service, "):", $username); return 1; } sub CanonicalizeUserInfo { my ($service, $key, $value) = @_; my $found = 0; my %params = (Name => undef, EmailAddress => undef, RealName => undef); # Load the config my $config = RT->Config->Get('ExternalSettings')->{$service}; # Figure out what's what my $base = $config->{'base'}; my $filter = $config->{'filter'}; # Get the list of unique attrs we need my @attrs; for my $field ( values %{ $config->{'attr_map'} } ) { if ( ref $field eq 'CODE' ) { push @attrs, $field->(); } elsif ( ref $field eq 'ARRAY' ) { push @attrs, @$field; } else { push @attrs, $field; } } # This is a bit confusing and probably broken. Something to revisit.. my $filter_addition = ($key && $value) ? "(". $key . "=". escape_filter_value($value) .")" : ""; if(defined($filter) && ($filter ne "()")) { $filter = Net::LDAP::Filter->new( "(&" . $filter . $filter_addition . ")" ); } else { $RT::Logger->debug( "LDAP Filter invalid or not present."); } unless (defined($base)) { $RT::Logger->critical( (caller(0))[3], "LDAP baseDN not defined"); # Drop out to the next external information service return ($found, %params); } # Get a Net::LDAP object based on the config we provide my $ldap = _GetBoundLdapObj($config); # Jump to the next external information service if we can't get one, # errors should be logged by _GetBoundLdapObj so we don't have to. return ($found, %params) unless ($ldap); # Do a search for them in LDAP $RT::Logger->debug( "LDAP Search === ", "Base:", $base, "== Filter:", $filter->as_string, "== Attrs:", join(',',@attrs)); my $ldap_msg = $ldap->search(base => $base, filter => $filter, attrs => \@attrs); # If we didn't get at LEAST a partial result, just die now. if ($ldap_msg->code != LDAP_SUCCESS and $ldap_msg->code != LDAP_PARTIAL_RESULTS) { $RT::Logger->critical( (caller(0))[3], ": Search for ", $filter->as_string, " failed: ", ldap_error_name($ldap_msg->code), $ldap_msg->code); # $found remains as 0 # Drop out to the next external information service $ldap_msg = $ldap->unbind(); if ($ldap_msg->code != LDAP_SUCCESS) { $RT::Logger->critical( (caller(0))[3], ": Could not unbind: ", ldap_error_name($ldap_msg->code), $ldap_msg->code); } undef $ldap; undef $ldap_msg; return ($found, %params); } else { # If there's only one match, we're good; more than one and # we don't know which is the right one so we skip it. if ($ldap_msg->count == 1) { my $entry = $ldap_msg->first_entry(); foreach my $key (keys(%{$config->{'attr_map'}})) { # XXX TODO: This legacy code wants to be removed since modern # configs will always fall through to the else and the logic is # weird even if you do have the old config. if ($RT::LdapAttrMap and $RT::LdapAttrMap->{$key} eq 'dn') { $params{$key} = $entry->dn(); } else { my $external_field = $config->{'attr_map'}{$key}; my @list = grep defined && length, ref $external_field eq 'ARRAY' ? @$external_field : ($external_field); unless (@list) { $RT::Logger->error("Invalid LDAP mapping for $key, no defined fields"); next; } my @values; foreach my $e (@list) { if ( ref $e eq 'CODE' ) { push @values, $e->( external_entry => $entry, mapping => $config->{'attr_map'}, rt_field => $key, external_field => $external_field, ); } elsif ( ref $e ) { $RT::Logger->error("Invalid type of LDAP mapping for $key, value is $e"); next; } else { push @values, $entry->get_value( $e, asref => 1 ); } } # Use the first value if multiple values are set in ldap @values = map { ref $_ eq 'ARRAY' ? $_->[0] : $_ } grep defined, @values; $params{$key} = join ' ', grep defined && length, @values; } } $found = 1; } else { # Drop out to the next external information service $ldap_msg = $ldap->unbind(); if ($ldap_msg->code != LDAP_SUCCESS) { $RT::Logger->critical( (caller(0))[3], ": Could not unbind: ", ldap_error_name($ldap_msg->code), $ldap_msg->code); } undef $ldap; undef $ldap_msg; return ($found, %params); } } $ldap_msg = $ldap->unbind(); if ($ldap_msg->code != LDAP_SUCCESS) { $RT::Logger->critical( (caller(0))[3], ": Could not unbind: ", ldap_error_name($ldap_msg->code), $ldap_msg->code); } undef $ldap; undef $ldap_msg; return ($found, %params); } sub UserExists { my ($username,$service) = @_; $RT::Logger->debug("UserExists params:\nusername: $username , service: $service"); my $config = RT->Config->Get('ExternalSettings')->{$service}; my $base = $config->{'base'}; my $filter = $config->{'filter'}; # While LDAP filters must be surrounded by parentheses, an empty set # of parentheses is an invalid filter and will cause failure # This shouldn't matter since we are now using Net::LDAP::Filter below, # but there's no harm in doing this to be sure undef $filter if defined $filter and $filter eq "()"; if (defined($config->{'attr_map'}->{'Name'})) { # Construct the complex filter $filter = Net::LDAP::Filter->new( '(&' . $filter . '(' . $config->{'attr_map'}->{'Name'} . '=' . escape_filter_value($username) . '))' ); } my $ldap = _GetBoundLdapObj($config); return unless $ldap; my @attrs = values(%{$config->{'attr_map'}}); # Check that the user exists in the LDAP service $RT::Logger->debug( "LDAP Search === ", "Base:", $base, "== Filter:", ($filter ? $filter->as_string : ''), "== Attrs:", join(',',@attrs)); my $user_found = $ldap->search( base => $base, filter => $filter, attrs => \@attrs); if($user_found->count < 1) { # If 0 or negative integer, no user found or major failure $RT::Logger->debug( "User Check Failed :: (", $service, ")", $username, "User not found"); return 0; } elsif ($user_found->count > 1) { # If more than one result returned, die because we the username field should be unique! $RT::Logger->debug( "User Check Failed :: (", $service, ")", $username, "More than one user with that username!"); return 0; } undef $user_found; # If we havent returned now, there must be a valid user. return 1; } sub UserDisabled { my ($username,$service) = @_; # FIRST, check that the user exists in the LDAP service unless(UserExists($username,$service)) { $RT::Logger->debug("User (",$username,") doesn't exist! - Assuming not disabled for the purposes of disable checking"); return 0; } my $config = RT->Config->Get('ExternalSettings')->{$service}; my $base = $config->{'base'}; my $filter = $config->{'filter'}; my $d_filter = $config->{'d_filter'}; my $search_filter; # While LDAP filters must be surrounded by parentheses, an empty set # of parentheses is an invalid filter and will cause failure # This shouldn't matter since we are now using Net::LDAP::Filter below, # but there's no harm in doing this to be sure undef $filter if defined $filter and $filter eq "()"; undef $d_filter if defined $d_filter and $d_filter eq "()"; unless ($d_filter) { # If we don't know how to check for disabled users, consider them all enabled. $RT::Logger->debug("No d_filter specified for this LDAP service (", $service, "), so considering all users enabled"); return 0; } if (defined($config->{'attr_map'}->{'Name'})) { # Construct the complex filter $search_filter = Net::LDAP::Filter->new( '(&' . $filter . $d_filter . '(' . $config->{'attr_map'}->{'Name'} . '=' . escape_filter_value($username) . '))' ); } else { $RT::Logger->debug("You haven't specified an LDAP attribute to match the RT \"Name\" attribute for this service (", $service, "), so it's impossible look up the disabled status of this user (", $username, ") so I'm just going to assume the user is not disabled"); return 0; } my $ldap = _GetBoundLdapObj($config); next unless $ldap; # We only need the UID for confirmation now, # the other information would waste time and bandwidth my @attrs = ('uid'); $RT::Logger->debug( "LDAP Search === ", "Base:", $base, "== Filter:", ($search_filter ? $search_filter->as_string : ''), "== Attrs:", join(',',@attrs)); my $disabled_users = $ldap->search(base => $base, filter => $search_filter, attrs => \@attrs); # If ANY results are returned, # we are going to assume the user should be disabled if ($disabled_users->count) { undef $disabled_users; return 1; } else { undef $disabled_users; return 0; } } # {{{ sub _GetBoundLdapObj sub _GetBoundLdapObj { # Config as hashref my $config = shift; # Figure out what's what my $ldap_server = $config->{'server'}; my $ldap_user = $config->{'user'}; my $ldap_pass = $config->{'pass'}; my $ldap_tls = $config->{'tls'}; $ldap_tls = $ldap_tls ? {} : undef unless ref $ldap_tls; my $ldap_args = $config->{'net_ldap_args'}; my $ldap = new Net::LDAP($ldap_server, @$ldap_args); unless ($ldap) { $RT::Logger->critical( (caller(0))[3], ": Cannot connect to", $ldap_server); return undef; } if ($ldap_tls) { # Thanks to David Narayan for the fault tolerance bits eval { $ldap->start_tls( %{$ldap_tls} ); }; if ($@) { $RT::Logger->critical( (caller(0))[3], "Can't start TLS: ", $@); return; } } my $msg = undef; if (($ldap_user) and ($ldap_pass)) { $msg = $ldap->bind($ldap_user, password => $ldap_pass); } elsif (($ldap_user) and ( ! $ldap_pass)) { $msg = $ldap->bind($ldap_user); } else { $msg = $ldap->bind; } unless ($msg->code == LDAP_SUCCESS) { $RT::Logger->critical( (caller(0))[3], "Can't bind:", ldap_error_name($msg->code), $msg->code); return undef; } else { return $ldap; } } # }}} RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Authen/ExternalAuth/DBI/������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 020322� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Authen/ExternalAuth/DBI/Cookie.pm���������������������������������������������������000644 �000765 �000024 �00000010507 14005011336 022074� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Authen::ExternalAuth::DBI::Cookie; use CGI::Cookie; use warnings; use strict; =head1 NAME RT::Authen::ExternalAuth::DBI::Cookie - Database-backed, cookie SSO source for RT authentication =head1 DESCRIPTION Provides the Cookie implementation for L<RT::Authen::ExternalAuth>. =head1 SYNOPSIS Set($ExternalSettings, { # An example SSO cookie service 'My_SSO_Cookie' => { 'type' => 'cookie', 'name' => 'loginCookieValue', 'u_table' => 'users', 'u_field' => 'username', 'u_match_key' => 'userID', 'c_table' => 'login_cookie', 'c_field' => 'loginCookieValue', 'c_match_key' => 'loginCookieUserID', 'db_service_name' => 'My_MySQL' }, 'My_MySQL' => { ... }, } ); =head1 CONFIGURATION Cookie-specific options are described here. Shared options are described in L<RT::Authen::ExternalAuth::DBI>. The example in the L</SYNOPSIS> lists all available options and they are described below. =over 4 =item name The name of the cookie to be used. =item u_table The users table. =item u_field The username field in the users table. =item u_match_key The field in the users table that uniquely identifies a user and also exists in the cookies table. See c_match_key below. =item c_table The cookies table. =item c_field The field that stores cookie values. =item c_match_key The field in the cookies table that uniquely identifies a user and also exists in the users table. See u_match_key above. =item db_service_name The DB service in this configuration to use to lookup the cookie information. See L<RT::Authen::ExternalAuth::DBI>. =back =cut # {{{ sub GetCookieVal sub GetCookieVal { # The name of the cookie my $cookie_name = shift; my $cookie_value; # Pull in all cookies from browser within our cookie domain my %cookies = CGI::Cookie->fetch(); # If the cookie is set, get the value, if it's not set, get out now! if (defined $cookies{$cookie_name}) { $cookie_value = $cookies{$cookie_name}->value; $RT::Logger->debug( "Cookie Found", ":: $cookie_name"); } else { $RT::Logger->debug( "Cookie Not Found"); } return $cookie_value; } # }}} RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Crypt/GnuPG.pm����������������������������������������������������������������������000644 �000765 �000024 �00000173325 14005011336 016526� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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; use 5.010; package RT::Crypt::GnuPG; use Role::Basic 'with'; with 'RT::Crypt::Role'; use IO::Handle; use File::Which qw(); use RT::Crypt::GnuPG::CRLFHandle; use GnuPG::Interface; use RT::EmailParser (); use RT::Util 'safe_run_child', 'mime_recommended_filename'; use URI::Escape; =head1 NAME RT::Crypt::GnuPG - GNU Privacy Guard encryption/decryption/verification/signing =head1 DESCRIPTION This module provides support for encryption and signing of outgoing messages using GnuPG, as well as the decryption and verification of incoming email. =head1 CONFIGURATION There are two reveant configuration options, both of which are hashes: C<GnuPG> and C<GnuPGOptions>. The first one controls RT specific options; it enables you to enable/disable the GPG protocol or change the format of messages. The second one is a hash with options which are passed to the C<gnupg> utility. You can use it to define a keyserver, enable auto-retrieval of keys, or set almost any option which C<gnupg> supports on your system. =head2 %GnuPG =head3 Enabling GnuPG Set to true value to enable this subsystem: Set( %GnuPG, Enable => 1, ... other options ... ); =head3 Format of outgoing messages The format of outgoing messages can be controlled using the C<OutgoingMessagesFormat> option in the RT config: Set( %GnuPG, ... other options ... OutgoingMessagesFormat => 'RFC', ... other options ... ); or Set( %GnuPG, ... other options ... OutgoingMessagesFormat => 'Inline', ... other options ... ); The two formats for GPG mail are as follows: =over =item RFC This format, the default, is also known as GPG/MIME, and is described in RFC3156 and RFC1847. The technique described in these RFCs is well supported by many mail user agents (MUA); however, some older MUAs only support inline signatures and encryption. =item Inline This format doesn't take advantage of MIME, but some mail clients do not support GPG/MIME. In general, this format is discouraged because modern mail clients typically do not support it well. Text parts are signed using clear-text signatures. For each attachment, the signature is attached separately as a file with a '.sig' extension added to the filename. Encryption of text parts is implemented using inline format, while other parts are replaced with attachments with the filename extension '.pgp'. =back =head3 Passphrases Passphrases for keys may be set by passing C<Passphrase>. It may be set to a scalar (to use for all keys), an anonymous function, or a hash (to look up by address). If the hash is used, the '' key is used as a default. =head2 %GnuPGOptions Use this hash to set additional options of the 'gnupg' program. The only options which are diallowed are options which alter the output format or attempt to run commands; thiss includes C<--sign>, C<--list-options>, etc. Some GnuPG options take arguments, while others take none. (Such as C<--use-agent>). For options without specific value use C<undef> as hash value. To disable these options, you may comment them out or delete them from the hash: Set(%GnuPGOptions, 'option-with-value' => 'value', 'enabled-option-without-value' => undef, # 'commented-option' => 'value or undef', ); B<NOTE> that options may contain the '-' character and such options B<MUST> be quoted, otherwise you will see the quite cryptic error C<gpg: Invalid option "--0">. Common options include: =over =item --homedir The GnuPG home directory where the keyrings are stored; by default it is set to F</opt/rt5/var/data/gpg>. You can manage this data with the 'gpg' commandline utility using the GNUPGHOME environment variable or C<--homedir> option. Other utilities may be used as well. In a standard installation, access to this directory should be granted to the web server user which is running RT's web interface; however, if you are running cronjobs or other utilities that access RT directly via API, and may generate encrypted/signed notifications, then the users you execute these scripts under must have access too. Be aware that granting access to the directory to many users makes the keys less secure -- and some features, such as auto-import of keys, may not be available if directory permissions are too permissive. To enable these features and suppress warnings about permissions on the directory, add the C<--no-permission-warning> option to C<GnuPGOptions>. =item --digest-algo This option is required when the C<RFC> format for outgoing messages is used. RT defaults to 'SHA1' by default, but you may wish to override it. C<gnupng --version> will list the algorithms supported by your C<gnupg> installation under 'hash functions'; these generally include MD5, SHA1, RIPEMD160, and SHA256. =item --use-agent This option lets you use GPG Agent to cache the passphrase of secret keys. See L<http://www.gnupg.org/documentation/manuals/gnupg/Invoking-GPG_002dAGENT.html> for information about GPG Agent. =item --passphrase This option lets you set the passphrase of RT's key directly. This option is special in that it is not passed directly to GPG; rather, it is put into a file that GPG then reads (which is more secure). The downside is that anyone who has read access to your RT_SiteConfig.pm file can see the passphrase -- thus we recommend the --use-agent option whenever possible. =item other Read C<man gpg> to get list of all options this program supports. =back =head2 Per-queue options Using the web interface it's possible to enable signing and/or encrypting by default. As an administrative user of RT, open 'Admin' then 'Queues', and select a queue. On the page you can see information about the queue's keys at the bottom and two checkboxes to choose default actions. As well, encryption is enabled for autoreplies and other notifications when an encypted message enters system via mailgate interface even if queue's option is disabled. =head2 Encrypting to untrusted keys Due to limitations of GnuPG, it's impossible to encrypt to an untrusted key, unless 'always trust' mode is enabled. =head1 FOR DEVELOPERS =head2 Documentation and references =over =item RFC1847 Security Multiparts for MIME: Multipart/Signed and Multipart/Encrypted. Describes generic MIME security framework, "mulitpart/signed" and "multipart/encrypted" MIME types. =item RFC3156 MIME Security with Pretty Good Privacy (PGP), updates RFC2015. =back =cut # gnupg options supported by GnuPG::Interface # other otions should be handled via extra_args argument my %supported_opt = map { $_ => 1 } qw( always_trust armor batch comment compress_algo default_key encrypt_to extra_args force_v3_sigs homedir logger_fd no_greeting no_options no_verbose openpgp options passphrase_fd quiet recipients rfc1991 status_fd textmode verbose ); # DEV WARNING: always pass all STD* handles to GnuPG interface even if we don't # need them, just pass 'IO::Handle->new()' and then close it after safe_run_child. # we don't want to leak anything into FCGI/Apache/MP handles, this break things. # So code should look like: # my $handles = GnuPG::Handles->new( # stdin => ($handle{'stdin'} = IO::Handle->new()), # stdout => ($handle{'stdout'} = IO::Handle->new()), # stderr => ($handle{'stderr'} = IO::Handle->new()), # ... # ); sub CallGnuPG { my $self = shift; my %args = ( Options => undef, Signer => undef, Recipients => [], Passphrase => undef, Command => undef, CommandArgs => [], Content => undef, Handles => {}, Direct => undef, Output => undef, @_ ); my %handle = %{$args{Handles}}; my ($handles, $handle_list) = _make_gpg_handles( %handle ); $handles->options( $_ )->{'direct'} = 1 for @{$args{Direct} || [keys %handle] }; %handle = %$handle_list; my $content = $args{Content}; my $command = $args{Command}; my %GnuPGOptions = RT->Config->Get('GnuPGOptions'); my %opt = ( 'digest-algo' => 'SHA1', 'cert-digest-algo' => 'SHA256', %GnuPGOptions, %{ $args{Options} || {} }, ); my $gnupg = GnuPG::Interface->new; $gnupg->call( $self->GnuPGPath ); $gnupg->options->hash_init( _PrepareGnuPGOptions( %opt ), ); $gnupg->options->armor( 1 ); $gnupg->options->meta_interactive( 0 ); $gnupg->options->default_key( $args{Signer} ) if defined $args{Signer}; my %seen; $gnupg->options->push_recipients( $_ ) for map { RT::Crypt->UseKeyForEncryption($_) || $_ } grep { !$seen{ $_ }++ } @{ $args{Recipients} || [] }; $args{Passphrase} = $GnuPGOptions{passphrase} unless defined $args{'Passphrase'}; $args{Passphrase} = $self->GetPassphrase( Address => $args{Signer} ) unless defined $args{'Passphrase'}; $gnupg->passphrase( $args{'Passphrase'} ) if defined $args{Passphrase}; eval { local $SIG{'CHLD'} = 'DEFAULT'; my $pid = safe_run_child { if ($command =~ /^--/) { $gnupg->wrap_call( handles => $handles, commands => [$command], command_args => $args{CommandArgs}, ); } else { $gnupg->$command( handles => $handles, command_args => $args{CommandArgs}, ); } }; { local $SIG{'PIPE'} = 'IGNORE'; if (Scalar::Util::blessed($content) and $content->can("print")) { $content->print( $handle{'stdin'} ); } elsif (ref($content) eq "SCALAR") { $handle{'stdin'}->print( ${ $content } ); } elsif (defined $content) { $handle{'stdin'}->print( $content ); } close $handle{'stdin'} or die "Can't close gnupg input handle: $!"; $args{Callback}->(%handle) if $args{Callback}; } waitpid $pid, 0; }; my $err = $@; if ($args{Output}) { push @{$args{Output}}, readline $handle{stdout}; if (not close $handle{stdout}) { $err ||= "Can't close gnupg output handle: $!"; } } my %res; $res{'exit_code'} = $?; foreach ( qw(stderr logger status) ) { $res{$_} = do { local $/ = undef; readline $handle{$_} }; delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s; if (not close $handle{$_}) { $err ||= "Can't close gnupg $_ handle: $!"; } } $RT::Logger->debug( $res{'status'} ) if $res{'status'}; $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'}; $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?; if ( $err || $res{'exit_code'} ) { $res{'message'} = $err? $err : "gpg exited with error code ". ($res{'exit_code'} >> 8); } return %res; } sub SignEncrypt { my $self = shift; my $format = lc RT->Config->Get('GnuPG')->{'OutgoingMessagesFormat'} || 'RFC'; if ( $format eq 'inline' ) { return $self->SignEncryptInline( @_ ); } else { return $self->SignEncryptRFC3156( @_ ); } } sub SignEncryptRFC3156 { my $self = shift; my %args = ( Entity => undef, Sign => 1, Signer => undef, Passphrase => undef, Encrypt => 1, Recipients => undef, @_ ); my $entity = $args{'Entity'}; my %res; if ( $args{'Sign'} && !$args{'Encrypt'} ) { # required by RFC3156(Ch. 5) and RFC1847(Ch. 2.1) foreach ( grep !$_->is_multipart, $entity->parts_DFS ) { my $tenc = $_->head->mime_encoding; unless ( $tenc =~ m/^(?:7bit|quoted-printable|base64)$/i ) { $_->head->mime_attr( 'Content-Transfer-Encoding' => $_->effective_type =~ m{^text/}? 'quoted-printable': 'base64' ); } } $entity->make_multipart( 'mixed', Force => 1 ); my @signature; # We use RT::Crypt::GnuPG::CRLFHandle to canonicalize the # MIME::Entity output to use \r\n instead of \n for its newlines %res = $self->CallGnuPG( Signer => $args{'Signer'}, Command => "detach_sign", Handles => { stdin => RT::Crypt::GnuPG::CRLFHandle->new }, Direct => [], Passphrase => $args{'Passphrase'}, Content => $entity->parts(0), Output => \@signature, ); return %res if $res{message}; # setup RFC1847(Ch.2.1) requirements my $protocol = 'application/pgp-signature'; my $algo = RT->Config->Get('GnuPGOptions')->{'digest-algo'} || 'SHA1'; $entity->head->mime_attr( 'Content-Type' => 'multipart/signed' ); $entity->head->mime_attr( 'Content-Type.protocol' => $protocol ); $entity->head->mime_attr( 'Content-Type.micalg' => 'pgp-'. lc $algo ); $entity->attach( Type => $protocol, Disposition => 'inline', Data => \@signature, Encoding => '7bit', ); } if ( $args{'Encrypt'} ) { my @recipients = map $_->address, map Email::Address->parse( Encode::decode( "UTF-8", $_ ) ), map $entity->head->get( $_ ), qw(To Cc Bcc); my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 ); binmode $tmp_fh, ':raw'; $entity->make_multipart( 'mixed', Force => 1 ); %res = $self->CallGnuPG( Signer => $args{'Signer'}, Recipients => \@recipients, Command => ( $args{'Sign'} ? "sign_and_encrypt" : "encrypt" ), Handles => { stdout => $tmp_fh }, Passphrase => $args{'Passphrase'}, Content => $entity->parts(0), ); return %res if $res{message}; my $protocol = 'application/pgp-encrypted'; $entity->parts([]); $entity->head->mime_attr( 'Content-Type' => 'multipart/encrypted' ); $entity->head->mime_attr( 'Content-Type.protocol' => $protocol ); $entity->attach( Type => $protocol, Disposition => 'inline', Data => ['Version: 1',''], Encoding => '7bit', ); $entity->attach( Type => 'application/octet-stream', Disposition => 'inline', Path => $tmp_fn, Filename => '', Encoding => '7bit', ); $entity->parts(-1)->bodyhandle->{'_dirty_hack_to_save_a_ref_tmp_fh'} = $tmp_fh; } return %res; } sub SignEncryptInline { my $self = shift; my %args = ( @_ ); my $entity = $args{'Entity'}; my %res; $entity->make_singlepart; if ( $entity->is_multipart ) { foreach ( $entity->parts ) { %res = $self->SignEncryptInline( @_, Entity => $_ ); return %res if $res{'exit_code'}; } return %res; } return $self->_SignEncryptTextInline( @_ ) if $entity->effective_type =~ /^text\//i; return $self->_SignEncryptAttachmentInline( @_ ); } sub _SignEncryptTextInline { my $self = shift; my %args = ( Entity => undef, Sign => 1, Signer => undef, Passphrase => undef, Encrypt => 1, Recipients => undef, @_ ); return unless $args{'Sign'} || $args{'Encrypt'}; my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 ); binmode $tmp_fh, ':raw'; my $entity = $args{'Entity'}; my %res = $self->CallGnuPG( Signer => $args{'Signer'}, Recipients => $args{'Recipients'}, Command => ( $args{'Sign'} && $args{'Encrypt'} ? 'sign_and_encrypt' : ( $args{'Sign'} ? 'clearsign' : 'encrypt' ) ), Handles => { stdout => $tmp_fh }, Passphrase => $args{'Passphrase'}, Content => $entity->bodyhandle, ); return %res if $res{message}; $entity->bodyhandle( MIME::Body::File->new( $tmp_fn) ); $entity->{'__store_tmp_handle_to_avoid_early_cleanup'} = $tmp_fh; return %res; } sub _SignEncryptAttachmentInline { my $self = shift; my %args = ( Entity => undef, Sign => 1, Signer => undef, Passphrase => undef, Encrypt => 1, Recipients => undef, @_ ); return unless $args{'Sign'} || $args{'Encrypt'}; my $entity = $args{'Entity'}; my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 ); binmode $tmp_fh, ':raw'; my %res = $self->CallGnuPG( Signer => $args{'Signer'}, Recipients => $args{'Recipients'}, Command => ( $args{'Sign'} && $args{'Encrypt'} ? 'sign_and_encrypt' : ( $args{'Sign'} ? 'detach_sign' : 'encrypt' ) ), Handles => { stdout => $tmp_fh }, Passphrase => $args{'Passphrase'}, Content => $entity->bodyhandle, ); return %res if $res{message}; my $filename = mime_recommended_filename( $entity ) || 'no_name'; if ( $args{'Sign'} && !$args{'Encrypt'} ) { $entity->make_multipart; $entity->attach( Type => 'application/octet-stream', Path => $tmp_fn, Filename => "$filename.sig", Disposition => 'attachment', ); } else { $entity->bodyhandle(MIME::Body::File->new( $tmp_fn) ); $entity->effective_type('application/octet-stream'); $entity->head->mime_attr( $_ => "$filename.pgp" ) foreach (qw(Content-Type.name Content-Disposition.filename)); } $entity->{'__store_tmp_handle_to_avoid_early_cleanup'} = $tmp_fh; return %res; } sub SignEncryptContent { my $self = shift; my %args = ( Content => undef, Sign => 1, Signer => undef, Passphrase => undef, Encrypt => 1, Recipients => undef, @_ ); return unless $args{'Sign'} || $args{'Encrypt'}; my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 ); binmode $tmp_fh, ':raw'; my %res = $self->CallGnuPG( Signer => $args{'Signer'}, Recipients => $args{'Recipients'}, Command => ( $args{'Sign'} && $args{'Encrypt'} ? 'sign_and_encrypt' : ( $args{'Sign'} ? 'clearsign' : 'encrypt' ) ), Handles => { stdout => $tmp_fh }, Passphrase => $args{'Passphrase'}, Content => $args{'Content'}, ); return %res if $res{message}; ${ $args{'Content'} } = ''; seek $tmp_fh, 0, 0; while (1) { my $status = read $tmp_fh, my $buf, 4*1024; unless ( defined $status ) { $RT::Logger->crit( "couldn't read message: $!" ); } elsif ( !$status ) { last; } ${ $args{'Content'} } .= $buf; } return %res; } sub CheckIfProtected { my $self = shift; my %args = ( Entity => undef, @_ ); my $entity = $args{'Entity'}; # we check inline PGP block later in another sub return () unless $entity->is_multipart; # RFC3156, multipart/{signed,encrypted} my $type = $entity->effective_type; return () unless $type =~ /^multipart\/(?:encrypted|signed)$/; unless ( $entity->parts == 2 ) { $RT::Logger->error( "Encrypted or signed entity must has two subparts. Skipped" ); return (); } my $protocol = $entity->head->mime_attr( 'Content-Type.protocol' ); unless ( $protocol ) { # if protocol is not set then we can check second part for PGP message $RT::Logger->error( "Entity is '$type', but has no protocol defined. Checking for PGP part" ); my $protected = $self->_CheckIfProtectedInline( $entity->parts(1), 1 ); return () unless $protected; if ( $protected eq 'signature' ) { $RT::Logger->debug("Found part signed according to RFC3156"); return ( Type => 'signed', Format => 'RFC3156', Top => $entity, Data => $entity->parts(0), Signature => $entity->parts(1), ); } else { $RT::Logger->debug("Found part encrypted according to RFC3156"); return ( Type => 'encrypted', Format => 'RFC3156', Top => $entity, Data => $entity->parts(1), Info => $entity->parts(0), ); } } elsif ( $type eq 'multipart/encrypted' ) { unless ( $protocol eq 'application/pgp-encrypted' ) { $RT::Logger->info( "Skipping protocol '$protocol', only 'application/pgp-encrypted' is supported" ); return (); } $RT::Logger->debug("Found part encrypted according to RFC3156"); return ( Type => 'encrypted', Format => 'RFC3156', Top => $entity, Data => $entity->parts(1), Info => $entity->parts(0), ); } else { unless ( $protocol eq 'application/pgp-signature' ) { $RT::Logger->info( "Skipping protocol '$protocol', only 'application/pgp-signature' is supported" ); return (); } $RT::Logger->debug("Found part signed according to RFC3156"); return ( Type => 'signed', Format => 'RFC3156', Top => $entity, Data => $entity->parts(0), Signature => $entity->parts(1), ); } return (); } sub FindScatteredParts { my $self = shift; my %args = ( Parts => [], Skip => {}, @_ ); my @res; my @parts = @{ $args{'Parts'} }; # attachments signed with signature in another part { my @file_indices; for (my $i = 0; $i < @parts; $i++ ) { my $part = $parts[ $i ]; # we can not associate a signature within an attachment # without file names my $fname = $part->head->recommended_filename; next unless $fname; my $type = $part->effective_type; if ( $type eq 'application/pgp-signature' ) { push @file_indices, $i; } elsif ( $type eq 'application/octet-stream' && $fname =~ /\.sig$/i ) { push @file_indices, $i; } } foreach my $i ( @file_indices ) { my $sig_part = $parts[ $i ]; my $sig_name = $sig_part->head->recommended_filename; my ($file_name) = $sig_name =~ /^(.*?)(?:\.sig)?$/; my ($data_part_idx) = grep $file_name eq ($parts[$_]->head->recommended_filename||''), grep $sig_part ne $parts[$_], 0 .. @parts - 1; unless ( defined $data_part_idx ) { $RT::Logger->error("Found $sig_name attachment, but didn't find $file_name"); next; } my $data_part_in = $parts[ $data_part_idx ]; $RT::Logger->debug("Found signature (in '$sig_name') of attachment '$file_name'"); $args{'Skip'}{$data_part_in} = 1; $args{'Skip'}{$sig_part} = 1; push @res, { Type => 'signed', Format => 'Attachment', Top => $args{'Parents'}{$sig_part}, Data => $data_part_in, Signature => $sig_part, }; } } my $file_extension_regex = join '|', @{ RT->Config->Get('GnuPG')->{FileExtensions} }; # attachments with inline encryption foreach my $part ( @parts ) { next if $args{'Skip'}{$part}; my $fname = $part->head->recommended_filename || ''; next unless $fname =~ /\.(?:$file_extension_regex)$/; $RT::Logger->debug("Found encrypted attachment '$fname'"); $args{'Skip'}{$part} = 1; push @res, { Type => 'encrypted', Format => 'Attachment', Data => $part, }; } # inline PGP block foreach my $part ( @parts ) { next if $args{'Skip'}{$part}; my $type = $self->_CheckIfProtectedInline( $part ); next unless $type; my $file = ($part->head->recommended_filename||'') =~ /\.(?:$file_extension_regex)$/; $args{'Skip'}{$part} = 1; push @res, { Type => $type, Format => !$file || $type eq 'signed'? 'Inline' : 'Attachment', Data => $part, }; } return @res; } sub _CheckIfProtectedInline { my $self = shift; my $entity = shift; my $check_for_signature = shift || 0; my $io = $entity->open('r'); unless ( $io ) { $RT::Logger->warning( "Entity of type ". $entity->effective_type ." has no body" ); return ''; } # Deal with "partitioned" PGP mail, which (contrary to common # sense) unnecessarily applies a base64 transfer encoding to PGP # mail (whose content is already base64-encoded). if ( $entity->bodyhandle->is_encoded and $entity->head->mime_encoding ) { my $decoder = MIME::Decoder->new( $entity->head->mime_encoding ); if ($decoder) { local $@; eval { my $buf = ''; open my $fh, '>', \$buf or die "Couldn't open scalar for writing: $!"; binmode $fh, ":raw"; $decoder->decode($io, $fh); close $fh or die "Couldn't close scalar: $!"; open $fh, '<', \$buf or die "Couldn't re-open scalar for reading: $!"; binmode $fh, ":raw"; $io = $fh; 1; } or do { $RT::Logger->error("Couldn't decode body: $@"); } } } while ( defined($_ = $io->getline) ) { if ( /^-----BEGIN PGP (SIGNED )?MESSAGE-----\s*$/ ) { return $1? 'signed': 'encrypted'; } elsif ( $check_for_signature && !/^-----BEGIN PGP SIGNATURE-----\s*$/ ) { return 'signature'; } } $io->close; return ''; } sub VerifyDecrypt { my $self = shift; my %args = ( Info => undef, @_ ); my %res; my $item = $args{'Info'}; my $status_on; if ( $item->{'Type'} eq 'signed' ) { if ( $item->{'Format'} eq 'RFC3156' ) { %res = $self->VerifyRFC3156( %$item ); $status_on = $item->{'Top'}; } elsif ( $item->{'Format'} eq 'Inline' ) { %res = $self->VerifyInline( %$item ); $status_on = $item->{'Data'}; } elsif ( $item->{'Format'} eq 'Attachment' ) { %res = $self->VerifyAttachment( %$item ); $status_on = $item->{'Data'}; } else { die "Unknown format '".$item->{'Format'} . "' of GnuPG signed part"; } } elsif ( $item->{'Type'} eq 'encrypted' ) { if ( $item->{'Format'} eq 'RFC3156' ) { %res = $self->DecryptRFC3156( %$item ); $status_on = $item->{'Top'}; } elsif ( $item->{'Format'} eq 'Inline' ) { %res = $self->DecryptInline( %$item ); $status_on = $item->{'Data'}; } elsif ( $item->{'Format'} eq 'Attachment' ) { %res = $self->DecryptAttachment( %$item ); $status_on = $item->{'Data'}; } else { die "Unknown format '".$item->{'Format'} . "' of GnuPG encrypted part"; } } else { die "Unknown type '".$item->{'Type'} . "' of protected item"; } return (%res, status_on => $status_on); } sub VerifyInline { return (shift)->DecryptInline( @_ ) } sub VerifyAttachment { my $self = shift; my %args = ( Data => undef, Signature => undef, @_ ); foreach ( $args{'Data'}, $args{'Signature'} ) { next unless $_->bodyhandle->is_encoded; require RT::EmailParser; RT::EmailParser->_DecodeBody($_); } my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 ); binmode $tmp_fh, ':raw'; $args{'Data'}->bodyhandle->print( $tmp_fh ); $tmp_fh->flush; my %res = $self->CallGnuPG( Command => "verify", CommandArgs => [ '-', $tmp_fn ], Passphrase => $args{'Passphrase'}, Content => $args{'Signature'}->bodyhandle, ); $args{'Top'}->parts( [ grep "$_" ne $args{'Signature'}, $args{'Top'}->parts ] ); $args{'Top'}->make_singlepart; return %res; } sub VerifyRFC3156 { my $self = shift; my %args = ( Data => undef, Signature => undef, @_ ); my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 ); binmode $tmp_fh, ':raw:eol(CRLF?)'; $args{'Data'}->print( $tmp_fh ); $tmp_fh->flush; my %res = $self->CallGnuPG( Command => "verify", CommandArgs => [ '-', $tmp_fn ], Passphrase => $args{'Passphrase'}, Content => $args{'Signature'}->bodyhandle, ); $args{'Top'}->parts( [ $args{'Data'} ] ); $args{'Top'}->make_singlepart; return %res; } sub DecryptRFC3156 { my $self = shift; my %args = ( Data => undef, Info => undef, Top => undef, Passphrase => undef, @_ ); if ( $args{'Data'}->bodyhandle->is_encoded ) { require RT::EmailParser; RT::EmailParser->_DecodeBody($args{'Data'}); } my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 ); binmode $tmp_fh, ':raw'; my %res = $self->CallGnuPG( Command => "decrypt", Handles => { stdout => $tmp_fh }, Passphrase => $args{'Passphrase'}, Content => $args{'Data'}->bodyhandle, ); # if the decryption is fine but the signature is bad, then without this # status check we lose the decrypted text # XXX: add argument to the function to control this check delete $res{'message'} if $res{'status'} =~ /DECRYPTION_OKAY/; return %res if $res{message}; seek $tmp_fh, 0, 0; my $parser = RT::EmailParser->new(); my $decrypted = $parser->ParseMIMEEntityFromFileHandle( $tmp_fh, 0 ); $decrypted->{'__store_link_to_object_to_avoid_early_cleanup'} = $parser; $args{'Top'}->parts( [$decrypted] ); $args{'Top'}->make_singlepart; return %res; } sub DecryptInline { my $self = shift; my %args = ( Data => undef, Passphrase => undef, @_ ); if ( $args{'Data'}->bodyhandle->is_encoded ) { require RT::EmailParser; RT::EmailParser->_DecodeBody($args{'Data'}); } my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 ); binmode $tmp_fh, ':raw'; my $io = $args{'Data'}->open('r'); unless ( $io ) { die "Entity has no body, never should happen"; } my %res; my ($had_literal, $in_block) = ('', 0); my ($block_fh, $block_fn) = File::Temp::tempfile( UNLINK => 1 ); binmode $block_fh, ':raw'; while ( defined(my $str = $io->getline) ) { if ( $in_block && $str =~ /^-----END PGP (?:MESSAGE|SIGNATURE)-----\s*$/ ) { print $block_fh $str; $in_block--; next if $in_block > 0; seek $block_fh, 0, 0; my ($res_fh, $res_fn, $embedded_fn); ($res_fh, $res_fn, $embedded_fn, %res) = $self->_DecryptInlineBlock( %args, BlockHandle => $block_fh, ); return %res unless $res_fh; print $tmp_fh "-----BEGIN OF PGP PROTECTED PART-----\n" if $had_literal; while (my $buf = <$res_fh> ) { print $tmp_fh $buf; } print $tmp_fh "-----END OF PART-----\n" if $had_literal; ($block_fh, $block_fn) = File::Temp::tempfile( UNLINK => 1 ); binmode $block_fh, ':raw'; $in_block = 0; } elsif ( $str =~ /^-----BEGIN PGP (SIGNED )?MESSAGE-----\s*$/ ) { $in_block++; print $block_fh $str; } elsif ( $in_block ) { print $block_fh $str; } else { print $tmp_fh $str; $had_literal = 1 if /\S/s; } } $io->close; if ( $in_block ) { # we're still in a block, this not bad not good. let's try to # decrypt what we have, it can be just missing -----END PGP... seek $block_fh, 0, 0; my ($res_fh, $res_fn, $embedded_fn); ($res_fh, $res_fn, $embedded_fn, %res) = $self->_DecryptInlineBlock( %args, BlockHandle => $block_fh, ); return %res unless $res_fh; print $tmp_fh "-----BEGIN OF PGP PROTECTED PART-----\n" if $had_literal; while (my $buf = <$res_fh> ) { print $tmp_fh $buf; } print $tmp_fh "-----END OF PART-----\n" if $had_literal; } seek $tmp_fh, 0, 0; $args{'Data'}->bodyhandle(MIME::Body::File->new( $tmp_fn )); $args{'Data'}->{'__store_tmp_handle_to_avoid_early_cleanup'} = $tmp_fh; return %res; } sub _DecryptInlineBlock { my $self = shift; my %args = ( BlockHandle => undef, Passphrase => undef, @_ ); my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 ); binmode $tmp_fh, ':raw'; my %res = $self->CallGnuPG( Command => "decrypt", Handles => { stdout => $tmp_fh, stdin => $args{'BlockHandle'} }, Passphrase => $args{'Passphrase'}, ); my $embedded_fn = $self->_EmbeddedFilename( $res{'status'} ); # if the decryption is fine but the signature is bad, then without this # status check we lose the decrypted text # XXX: add argument to the function to control this check delete $res{'message'} if $res{'status'} =~ /DECRYPTION_OKAY/; return (undef, undef, $embedded_fn, %res) if $res{message}; seek $tmp_fh, 0, 0; return ($tmp_fh, $tmp_fn, $embedded_fn, %res); } # Encyrpted attachments have their filename obfuscated, and the original is # in the encrypted section. This returns the original filename returned in # the status-fd filehandle after decryption is done. sub _EmbeddedFilename { my $self = shift; my $status = shift; my ($embedded_fn) = ( $status =~ /^\[GNUPG:\] PLAINTEXT \d+ \d+ (\S+)$/m ); $RT::Logger->debug("Embedded filename is $embedded_fn") if $embedded_fn; return uri_unescape($embedded_fn); } sub DecryptAttachment { my $self = shift; my %args = ( Data => undef, Passphrase => undef, @_ ); if ( $args{'Data'}->bodyhandle->is_encoded ) { require RT::EmailParser; RT::EmailParser->_DecodeBody($args{'Data'}); } my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 ); binmode $tmp_fh, ':raw'; $args{'Data'}->bodyhandle->print( $tmp_fh ); seek $tmp_fh, 0, 0; my ($res_fh, $res_fn, $embedded_fn, %res) = $self->_DecryptInlineBlock( %args, BlockHandle => $tmp_fh, ); return %res unless $res_fh; $args{'Data'}->bodyhandle(MIME::Body::File->new($res_fn) ); $args{'Data'}->{'__store_tmp_handle_to_avoid_early_cleanup'} = $res_fh; my $head = $args{'Data'}->head; if ($head->recommended_filename =~ /^PGPexch\.htm/i) { # Special-case for handling old-style PGP plugin for Outlook style # attachements of HTML content. For more, see: # http://www.ietf.org/mail-archive/web/openpgp/current/msg01811.html $head->mime_attr( "Content-Type" => 'text/html' ); $head->mime_attr( "Content-Disposition" => "inline" ); } elsif ($head->recommended_filename =~ /^PGPexch\.rtf/i) { # Special-case for handling old-style PGP plugin for Outlook style # attachements of RTF content. These remain as attachments and are # not to be inlined; RTF is for Outlook only. $head->mime_attr( "Content-Type" => 'text/rtf' ); } else { # TODO # The original Content-Type of the MIME part describes the PGP-encoded # data, and not the file inside it. There is no way to know the type, # so we just use octet-stream. # Note, some clients may send .asc files (encryped) as text/plain. # (MIME::Detect::Type could be useful here) $head->mime_attr( "Content-Type" => 'application/octet-stream' ); } my $filename = $embedded_fn || $head->recommended_filename; my $file_extension_regex = join '|', @{ RT->Config->Get('GnuPG')->{FileExtensions} }; $filename =~ s/\.(?:$file_extension_regex)$//i; $head->mime_attr( $_ => $filename ) foreach (qw(Content-Type.name Content-Disposition.filename)); return %res; } sub DecryptContent { my $self = shift; my %args = ( Content => undef, Passphrase => undef, @_ ); my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 ); binmode $tmp_fh, ':raw'; my %res = $self->CallGnuPG( Command => "decrypt", Handles => { stdout => $tmp_fh }, Passphrase => $args{'Passphrase'}, Content => $args{'Content'}, ); # if the decryption is fine but the signature is bad, then without this # status check we lose the decrypted text # XXX: add argument to the function to control this check delete $res{'message'} if $res{'status'} =~ /DECRYPTION_OKAY/; return %res if $res{'message'}; ${ $args{'Content'} } = ''; seek $tmp_fh, 0, 0; while (1) { my $status = read $tmp_fh, my $buf, 4*1024; unless ( defined $status ) { $RT::Logger->crit( "couldn't read message: $!" ); } elsif ( !$status ) { last; } ${ $args{'Content'} } .= $buf; } return %res; } my %REASON_CODE_TO_TEXT = ( NODATA => { 1 => "No armored data", 2 => "Expected a packet, but did not found one", 3 => "Invalid packet found", 4 => "Signature expected, but not found", }, INV_RECP => { 0 => "No specific reason given", 1 => "Not Found", 2 => "Ambigious specification", 3 => "Wrong key usage", 4 => "Key revoked", 5 => "Key expired", 6 => "No CRL known", 7 => "CRL too old", 8 => "Policy mismatch", 9 => "Not a secret key", 10 => "Key not trusted", }, ERRSIG => { 0 => 'not specified', 4 => 'unknown algorithm', 9 => 'missing public key', }, ); sub ReasonCodeToText { my $keyword = shift; my $code = shift; return $REASON_CODE_TO_TEXT{ $keyword }{ $code } if exists $REASON_CODE_TO_TEXT{ $keyword }{ $code }; return 'unknown'; } my %simple_keyword = ( NO_RECP => { Operation => 'RecipientsCheck', Status => 'ERROR', Message => 'No recipients', }, UNEXPECTED => { Operation => 'Data', Status => 'ERROR', Message => 'Unexpected data has been encountered', }, BADARMOR => { Operation => 'Data', Status => 'ERROR', Message => 'The ASCII armor is corrupted', }, ); # keywords we parse my %parse_keyword = map { $_ => 1 } qw( USERID_HINT SIG_CREATED GOODSIG BADSIG ERRSIG END_ENCRYPTION DECRYPTION_FAILED DECRYPTION_OKAY BAD_PASSPHRASE GOOD_PASSPHRASE NO_SECKEY NO_PUBKEY NO_RECP INV_RECP NODATA UNEXPECTED FAILURE ); # keywords we ignore without any messages as we parse them using other # keywords as starting point or just ignore as they are useless for us my %ignore_keyword = map { $_ => 1 } qw( NEED_PASSPHRASE MISSING_PASSPHRASE BEGIN_SIGNING PLAINTEXT PLAINTEXT_LENGTH BEGIN_ENCRYPTION SIG_ID VALIDSIG ENC_TO BEGIN_DECRYPTION END_DECRYPTION GOODMDC TRUST_UNDEFINED TRUST_NEVER TRUST_MARGINAL TRUST_FULLY TRUST_ULTIMATE DECRYPTION_INFO KEY_CONSIDERED DECRYPTION_KEY NEWSIG PINENTRY_LAUNCHED IMPORT_OK DECRYPTION_COMPLIANCE_MODE PROGRESS INV_SGNR ); sub ParseStatus { my $self = shift; my $status = shift; return () unless $status; my @status; while ( $status =~ /\[GNUPG:\]\s*(.*?)(?=\[GNUPG:\]|\z)/igms ) { push @status, $1; $status[-1] =~ s/\s+/ /g; $status[-1] =~ s/\s+$//; } $status = join "\n", @status; study $status; my @res; my (%user_hint, $latest_user_main_key); for ( my $i = 0; $i < @status; $i++ ) { my $line = $status[$i]; my ($keyword, $args) = ($line =~ /^(\S+)\s*(.*)$/s); if ( $simple_keyword{ $keyword } ) { push @res, $simple_keyword{ $keyword }; $res[-1]->{'Keyword'} = $keyword; next; } unless ( $parse_keyword{ $keyword } ) { $RT::Logger->warning("Skipped $keyword") unless $ignore_keyword{ $keyword }; next; } if ( $keyword eq 'USERID_HINT' ) { my %tmp = _ParseUserHint($status, $line); $latest_user_main_key = $tmp{'MainKey'}; if ( $user_hint{ $tmp{'MainKey'} } ) { while ( my ($k, $v) = each %tmp ) { $user_hint{ $tmp{'MainKey'} }->{$k} = $v; } } else { $user_hint{ $tmp{'MainKey'} } = \%tmp; } next; } elsif ( $keyword eq 'BAD_PASSPHRASE' || $keyword eq 'GOOD_PASSPHRASE' ) { my $key_id = $args; my %res = ( Operation => 'PassphraseCheck', Status => $keyword eq 'BAD_PASSPHRASE'? 'BAD' : 'DONE', Key => $key_id, ); $res{'Status'} = 'MISSING' if $status[ $i - 1 ] =~ /^MISSING_PASSPHRASE/; foreach my $line ( reverse @status[ 0 .. $i-1 ] ) { next unless $line =~ /^NEED_PASSPHRASE\s+(\S+)\s+(\S+)\s+(\S+)/; next if $key_id && $2 ne $key_id; @res{'MainKey', 'Key', 'KeyType'} = ($1, $2, $3); last; } $res{'Message'} = ucfirst( lc( $res{'Status'} eq 'DONE'? 'GOOD': $res{'Status'} ) ) .' passphrase'; $res{'User'} = ( $user_hint{ $res{'MainKey'} } ||= {} ) if $res{'MainKey'}; if ( exists $res{'User'}->{'EmailAddress'} ) { $res{'Message'} .= ' for '. $res{'User'}->{'EmailAddress'}; } else { $res{'Message'} .= " for '0x$key_id'"; } push @res, \%res; } elsif ( $keyword eq 'END_ENCRYPTION' ) { my %res = ( Operation => 'Encrypt', Status => 'DONE', Message => 'Data has been encrypted', ); foreach my $line ( reverse @status[ 0 .. $i-1 ] ) { next unless $line =~ /^BEGIN_ENCRYPTION\s+(\S+)\s+(\S+)/; @res{'MdcMethod', 'SymAlgo'} = ($1, $2); last; } push @res, \%res; } elsif ( $keyword eq 'DECRYPTION_FAILED' || $keyword eq 'DECRYPTION_OKAY' ) { my %res = ( Operation => 'Decrypt' ); @res{'Status', 'Message'} = $keyword eq 'DECRYPTION_FAILED' ? ('ERROR', 'Decryption failed') : ('DONE', 'Decryption process succeeded'); foreach my $line ( reverse @status[ 0 .. $i-1 ] ) { next unless $line =~ /^ENC_TO\s+(\S+)\s+(\S+)\s+(\S+)/; my ($key, $alg, $key_length) = ($1, $2, $3); my %encrypted_to = ( Message => "The message is encrypted to '0x$key'", User => ( $user_hint{ $key } ||= {} ), Key => $key, KeyLength => $key_length, Algorithm => $alg, ); push @{ $res{'EncryptedTo'} ||= [] }, \%encrypted_to; } push @res, \%res; } elsif ( $keyword eq 'NO_SECKEY' || $keyword eq 'NO_PUBKEY' ) { my ($key) = split /\s+/, $args; my $type = $keyword eq 'NO_SECKEY'? 'secret': 'public'; my %res = ( Operation => 'KeyCheck', Status => 'MISSING', Message => ucfirst( $type ) ." key '0x$key' is not available", Key => $key, KeyType => $type, ); $res{'User'} = ( $user_hint{ $key } ||= {} ); $res{'User'}{ ucfirst( $type ). 'KeyMissing' } = 1; push @res, \%res; } # GOODSIG, BADSIG, VALIDSIG, TRUST_* elsif ( $keyword eq 'GOODSIG' ) { my %res = ( Operation => 'Verify', Status => 'DONE', Message => 'The signature is good', ); @res{qw(Key UserString)} = split /\s+/, $args, 2; $res{'Message'} .= ', signed by '. $res{'UserString'}; foreach my $line ( @status[ $i .. $#status ] ) { next unless $line =~ /^TRUST_(\S+)/; $res{'Trust'} = $1; last; } $res{'Message'} .= ', trust level is '. lc( $res{'Trust'} || 'unknown'); foreach my $line ( @status[ $i .. $#status ] ) { next unless $line =~ /^VALIDSIG\s+(.*)/; @res{ qw( Fingerprint CreationDate Timestamp ExpireTimestamp Version Reserved PubkeyAlgo HashAlgo Class PKFingerprint Other ) } = split /\s+/, $1, 10; last; } push @res, \%res; } elsif ( $keyword eq 'BADSIG' ) { my %res = ( Operation => 'Verify', Status => 'BAD', Message => 'The signature has not been verified okay', ); @res{qw(Key UserString)} = split /\s+/, $args, 2; push @res, \%res; } elsif ( $keyword eq 'ERRSIG' ) { my %res = ( Operation => 'Verify', Status => 'ERROR', Message => 'Not possible to check the signature', ); @res{qw(Key PubkeyAlgo HashAlgo Class Timestamp ReasonCode Other)} = split /\s+/, $args, 7; $res{'Reason'} = ReasonCodeToText( $keyword, $res{'ReasonCode'} ); $res{'Message'} .= ", the reason is ". $res{'Reason'}; push @res, \%res; } elsif ( $keyword eq 'SIG_CREATED' ) { # SIG_CREATED <type> <pubkey algo> <hash algo> <class> <timestamp> <key fpr> my @props = split /\s+/, $args; push @res, { Operation => 'Sign', Status => 'DONE', Message => "Signed message", Type => $props[0], PubKeyAlgo => $props[1], HashKeyAlgo => $props[2], Class => $props[3], Timestamp => $props[4], KeyFingerprint => $props[5], ( defined $latest_user_main_key ? ( User => $user_hint{$latest_user_main_key} ) : () ) }; if ($latest_user_main_key) { $res[-1]->{Message} .= ' by '. $user_hint{ $latest_user_main_key }->{'EmailAddress'} if $user_hint{ $latest_user_main_key }; } } elsif ( $keyword eq 'INV_RECP' ) { my ($rcode, $recipient) = split /\s+/, $args, 2; my $reason = ReasonCodeToText( $keyword, $rcode ); push @res, { Operation => 'RecipientsCheck', Status => 'ERROR', Message => "Recipient '$recipient' is unusable, the reason is '$reason'", Recipient => $recipient, ReasonCode => $rcode, Reason => $reason, }; } elsif ( $keyword eq 'NODATA' ) { my $rcode = (split /\s+/, $args)[0]; my $reason = ReasonCodeToText( $keyword, $rcode ); push @res, { Operation => 'Data', Status => 'ERROR', Message => "No data has been found. The reason is '$reason'", ReasonCode => $rcode, Reason => $reason, }; } elsif ( $keyword eq 'FAILURE' ) { # FAILURE encrypt 167772218 my ($op, $rcode) = split /\s+/, $args; my $reason = ReasonCodeToText( $keyword, $rcode ); push @res, { Operation => ucfirst($op), Status => 'ERROR', Message => "Failed to $op", ReasonCode => $rcode, Reason => $reason, }; } else { $RT::Logger->warning("Keyword $keyword is unknown : status line is $line"); next; } $res[-1]{'Keyword'} = $keyword if @res && !$res[-1]{'Keyword'}; } return @res; } sub _ParseUserHint { my ($status, $hint) = (@_); my ($main_key_id, $user_str) = ($hint =~ /^USERID_HINT\s+(\S+)\s+(.*)$/); return () unless $main_key_id; return ( MainKey => $main_key_id, String => $user_str, EmailAddress => (map $_->address, Email::Address->parse( $user_str ))[0], ); } sub _PrepareGnuPGOptions { my %opt = @_; my %res = map { lc $_ => $opt{ $_ } } grep $supported_opt{ lc $_ }, keys %opt; $res{'extra_args'} ||= []; foreach my $o ( grep !$supported_opt{ lc $_ }, keys %opt ) { push @{ $res{'extra_args'} }, '--'. lc $o; push @{ $res{'extra_args'} }, $opt{ $o } if defined $opt{ $o }; } return %res; } sub GetKeysForEncryption { my $self = shift; my %args = (Recipient => undef, @_); my %res = $self->GetKeysInfo( Key => delete $args{'Recipient'}, %args, Type => 'public' ); return %res if $res{'exit_code'}; return %res unless $res{'info'}; foreach my $key ( splice @{ $res{'info'} } ) { # skip disabled keys next if $key->{'Capabilities'} =~ /D/; # skip keys not suitable for encryption next unless $key->{'Capabilities'} =~ /e/i; # skip disabled, expired, revoked and keys with no trust, # but leave keys with unknown trust level next if $key->{'TrustLevel'} < 0; push @{ $res{'info'} }, $key; } delete $res{'info'} unless @{ $res{'info'} }; return %res; } sub GetKeysForSigning { my $self = shift; my %args = (Signer => undef, @_); return $self->GetKeysInfo( Key => delete $args{'Signer'}, %args, Type => 'private' ); } sub GetKeysInfo { my $self = shift; my %args = ( Key => undef, Type => 'public', Force => 0, @_ ); my $email = $args{'Key'}; my $type = $args{'Type'}; unless ( $email ) { return (exit_code => 0) unless $args{'Force'}; } my @info; my $method = $type eq 'private'? 'list_secret_keys': 'list_public_keys'; my %res = $self->CallGnuPG( Options => { 'with-colons' => undef, # parseable format 'fingerprint' => undef, # show fingerprint 'fixed-list-mode' => undef, # don't merge uid with keys }, Command => $method, ( $email ? (CommandArgs => ['--', $email]) : () ), Output => \@info, ); # Asking for a non-existent key is not an error if ($res{message} and $res{logger} =~ /(secret key not available|public key not found)/) { delete $res{exit_code}; delete $res{message}; } return %res if $res{'message'}; @info = $self->ParseKeysInfo( @info ); $res{'info'} = \@info; for my $key (@{$res{info}}) { $key->{Formatted} = join("; ", map {$_->{String}} @{$key->{User}}) . " (".substr($key->{Fingerprint}, -8) . ")"; } return %res; } sub ParseKeysInfo { my $self = shift; my @lines = @_; my %gpg_opt = RT->Config->Get('GnuPGOptions'); my @res = (); foreach my $line( @lines ) { chomp $line; my $tag; ($tag, $line) = split /:/, $line, 2; if ( $tag eq 'pub' ) { my %info; @info{ qw( TrustChar KeyLength Algorithm Key Created Expire Empty OwnerTrustChar Empty Empty Capabilities Other ) } = split /:/, $line, 12; # workaround gnupg's wierd behaviour, --list-keys command report calculated trust levels # for any model except 'always', so you can change models and see changes, but not for 'always' # we try to handle it in a simple way - we set ultimate trust for any key with trust # level >= 0 if trust model is 'always' my $always_trust; $always_trust = 1 if exists $gpg_opt{'always-trust'}; $always_trust = 1 if exists $gpg_opt{'trust-model'} && $gpg_opt{'trust-model'} eq 'always'; @info{qw(Trust TrustTerse TrustLevel)} = _ConvertTrustChar( $info{'TrustChar'} ); if ( $always_trust && $info{'TrustLevel'} >= 0 ) { @info{qw(Trust TrustTerse TrustLevel)} = _ConvertTrustChar( 'u' ); } @info{qw(OwnerTrust OwnerTrustTerse OwnerTrustLevel)} = _ConvertTrustChar( $info{'OwnerTrustChar'} ); $info{ $_ } = $self->ParseDate( $info{ $_ } ) foreach qw(Created Expire); push @res, \%info; } elsif ( $tag eq 'sec' ) { my %info; @info{ qw( Empty KeyLength Algorithm Key Created Expire Empty OwnerTrustChar Empty Empty Capabilities Other ) } = split /:/, $line, 12; @info{qw(OwnerTrust OwnerTrustTerse OwnerTrustLevel)} = _ConvertTrustChar( $info{'OwnerTrustChar'} ); $info{ $_ } = $self->ParseDate( $info{ $_ } ) foreach qw(Created Expire); push @res, \%info; } elsif ( $tag eq 'uid' ) { my %info; @info{ qw(Trust Created Expire String) } = (split /:/, $line)[0,4,5,8]; $info{ $_ } = $self->ParseDate( $info{ $_ } ) foreach qw(Created Expire); push @{ $res[-1]{'User'} ||= [] }, \%info; } elsif ( $tag eq 'fpr' ) { $res[-1]{'Fingerprint'} ||= (split /:/, $line, 10)[8]; } } return @res; } { my %verbose = ( # deprecated d => [ "The key has been disabled", #loc "key disabled", #loc "-2" ], r => [ "The key has been revoked", #loc "key revoked", #loc -3, ], e => [ "The key has expired", #loc "key expired", #loc '-4', ], n => [ "Don't trust this key at all", #loc 'none', #loc -1, ], #gpupg docs says that '-' and 'q' may safely be treated as the same value '-' => [ 'Unknown (no trust value assigned)', #loc 'not set', 0, ], q => [ 'Unknown (no trust value assigned)', #loc 'not set', 0, ], o => [ 'Unknown (this value is new to the system)', #loc 'unknown', 0, ], m => [ "There is marginal trust in this key", #loc 'marginal', #loc 1, ], f => [ "The key is fully trusted", #loc 'full', #loc 2, ], u => [ "The key is ultimately trusted", #loc 'ultimate', #loc 3, ], ); sub _ConvertTrustChar { my $value = shift; return @{ $verbose{'-'} } unless $value; $value = substr $value, 0, 1; return @{ $verbose{ $value } || $verbose{'o'} }; } } sub DeleteKey { my $self = shift; my $key = shift; return $self->CallGnuPG( Command => "--delete-secret-and-public-key", CommandArgs => ["--", $key], Callback => sub { my %handle = @_; while ( my $str = readline $handle{'status'} ) { if ( $str =~ /^\[GNUPG:\]\s*GET_BOOL delete_key\..*/ ) { print { $handle{'command'} } "y\n"; } } }, ); } sub ImportKey { my $self = shift; my $key = shift; return $self->CallGnuPG( Command => "import_keys", Content => $key, ); } sub GnuPGPath { state $cache = RT->Config->Get('GnuPG')->{'GnuPG'}; $cache = $_[1] if @_ > 1; return $cache; } sub Probe { my $self = shift; my $gnupg = GnuPG::Interface->new; my $bin = $self->GnuPGPath(); unless ($bin) { $RT::Logger->warning( "No gpg path set; GnuPG support has been disabled. ". "Check the 'GnuPG' configuration in %GnuPG"); return 0; } if ($bin =~ m{^/}) { unless (-f $bin and -x _) { $RT::Logger->warning( "Invalid gpg path $bin; GnuPG support has been disabled. ". "Check the 'GnuPG' configuration in %GnuPG"); return 0; } } else { local $ENV{PATH} = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' unless defined $ENV{PATH}; my $path = File::Which::which( $bin ); unless ($path) { $RT::Logger->warning( "Can't find gpg binary '$bin' in PATH ($ENV{PATH}); GnuPG support has been disabled. ". "You may need to specify a full path to gpg via the 'GnuPG' configuration in %GnuPG"); return 0; } $self->GnuPGPath( $bin = $path ); } $gnupg->call( $bin ); $gnupg->options->hash_init( _PrepareGnuPGOptions( RT->Config->Get('GnuPGOptions') ) ); $gnupg->options->meta_interactive( 0 ); my ($handles, $handle_list) = _make_gpg_handles(); my %handle = %$handle_list; local $@ = undef; eval { local $SIG{'CHLD'} = 'DEFAULT'; my $pid = safe_run_child { $gnupg->wrap_call( commands => ['--version' ], handles => $handles ) }; close $handle{'stdin'} or die "Can't close gnupg input handle: $!"; waitpid $pid, 0; }; if ( $@ ) { $RT::Logger->warning( "RT's GnuPG libraries couldn't successfully execute gpg.". " GnuPG support has been disabled"); $RT::Logger->debug( "Probe for GPG failed." ." Couldn't run `gpg --version`: ". $@ ); return 0; } # on some systems gpg exits with code 2, but still 100% functional, # it's general error system error or incorrect command, command is correct, # but there is no way to get actuall error if ( $? && ($? >> 8) != 2 ) { my $msg = "Probe for GPG failed." ." Process exited with code ". ($? >> 8) . ($? & 127 ? (" as recieved signal ". ($? & 127)) : '') . "."; foreach ( qw(stderr logger status) ) { my $tmp = do { local $/ = undef; readline $handle{$_} }; next unless $tmp && $tmp =~ /\S/s; close $handle{$_} or $tmp .= "\nFailed to close: $!"; $msg .= "\n$_:\n$tmp\n"; } $RT::Logger->warning( "RT's GnuPG libraries couldn't successfully execute gpg.". " GnuPG support has been disabled"); $RT::Logger->debug( $msg ); return 0; } return 1; } sub _make_gpg_handles { my %handle_map = (@_); $handle_map{$_} = IO::Handle->new foreach grep !defined $handle_map{$_}, qw(stdin stdout stderr logger status command); my $handles = GnuPG::Handles->new(%handle_map); return ($handles, \%handle_map); } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Crypt/Role.pm�����������������������������������������������������������������������000644 �000765 �000024 �00000016154 14005011336 016443� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Crypt::Role; use Role::Basic; =head1 NAME RT::Crypt::Role - Common requirements for encryption implementations =head1 METHODS =head2 Probe This routine is called only if the protocol is enabled, and should return true if all binaries required by the protocol are installed. It should produce any warnings necessary to describe any issues it encounters. =cut requires 'Probe'; =head2 GetPassphrase Address => ADDRESS Returns the passphrase for the given address. It looks at the relevant configuration option for the encryption protocol (e.g. L<RT_Config/GnuPG> for GnuPG), and examines the Passphrase key. It it does not exist, returns the empty string. If it is a scalar, it returns that value. If it is an anonymous subroutine, it calls it. If it is a hash, it looks up the address (using '' as a fallback key). =cut sub GetPassphrase { my $self = shift; my %args = ( Address => undef, @_ ); my $class = ref($self) || $self; $class =~ s/^RT::Crypt:://; my $config = RT->Config->Get($class)->{Passphrase}; return '' unless defined $config; if (not ref $config) { return $config; } elsif (ref $config eq "HASH") { return $config->{$args{Address}} || $config->{''}; } elsif (ref $config eq "CODE") { return $config->( @_ ); } else { warn "Unknown Passphrase type for $class: ".ref($config); } } =head2 SignEncrypt Entity => MIME::Entity, [ Encrypt => 1, Sign => 1, ... ] Signs and/or encrypts a MIME entity. All arguments and return values are identical to L<RT::Crypt/SignEncrypt>, with the omission of C<Protocol>. =cut requires 'SignEncrypt'; =head2 SignEncryptContent Content => STRINGREF, [ Encrypt => 1, Sign => 1, ... ] Signs and/or encrypts a string, which is passed by reference. All arguments and return values are identical to L<RT::Crypt/SignEncryptContent>, with the omission of C<Protocol>. =cut requires 'SignEncryptContent'; =head2 VerifyDecrypt Info => HASHREF, [ Passphrase => undef ] The C<Info> key is a hashref as returned from L</FindScatteredParts> or L</CheckIfProtected>. This method should alter the mime objects in-place as necessary during signing and decryption. Returns a hash with at least the following keys: =over =item exit_code True if there was an error encrypting or signing. =item message An un-localized error message desribing the problem. =back =cut requires 'VerifyDecrypt'; =head2 DecryptContent Content => STRINGREF, [ Passphrase => undef ] Decrypts the content in the string reference in-place. All arguments and return values are identical to L<RT::Crypt/DecryptContent>, with the omission of C<Protocol>. =cut requires 'DecryptContent'; =head2 ParseStatus STRING Takes a string describing the status of verification/decryption, usually as stored in a MIME header. Parses and returns it as described in L<RT::Crypt/ParseStatus>. =cut requires 'ParseStatus'; =head2 FindScatteredParts Parts => ARRAYREF, Parents => HASHREF, Skip => HASHREF Passed the list of unclaimed L<MIME::Entity> objects in C<Parts>, this method should examine them as a whole to determine if there are any that could not be claimed by the single-entity-at-a-time L</CheckIfProtected> method. This is generally only necessary in the case of signatures manually attached in parallel, and the like. If found, the relevant entities should be inserted into C<Skip> with a true value, to signify to other encryption protols that they have been claimed. The method should return a list of hash references, each containing a C<Type> key which is either C<signed> or C<encrypted>. The remaining keys are protocol-dependent; the hashref will be provided to L</VerifyDecrypt>. =cut requires 'FindScatteredParts'; =head2 CheckIfProtected Entity => MIME::Entity Examines the provided L<MIME::Entity>, and returns an empty list if it is not signed or encrypted using the protocol. If it is, returns a hash reference containing a C<Type> which is either C<encrypted> or C<signed>. The remaining keys are protocol-dependent; the hashref will be provided to L</VerifyDecrypt>. =cut requires 'CheckIfProtected'; =head2 GetKeysInfo Type => ('public'|'private'), Key => EMAIL Returns a list of keys matching the email C<Key>, as described in L<RT::Crypt/GetKeysInfo>. =cut requires 'GetKeysInfo'; =head2 GetKeysForEncryption Recipient => EMAIL Returns a list of keys suitable for encryption, as described in L<RT::Crypt/GetKeysForEncryption>. =cut requires 'GetKeysForEncryption'; =head2 GetKeysForSigning Signer => EMAIL Returns a list of keys suitable for encryption, as described in L<RT::Crypt/GetKeysForSigning>. =cut requires 'GetKeysForSigning'; =head2 ParseDate STRING Takes a string, and parses and returns a L<RT::Date>; if the string is purely numeric, assumes is a epoch timestamp. =cut sub ParseDate { my $self = shift; my $value = shift; # never return $value unless $value; require RT::Date; my $obj = RT::Date->new( RT->SystemUser ); # unix time if ( $value =~ /^\d+$/ ) { $obj->Set( Value => $value ); } else { $obj->Set( Format => 'unknown', Value => $value, Timezone => 'utc' ); } return $obj; } 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Crypt/SMIME.pm����������������������������������������������������������������������000644 �000765 �000024 �00000076405 14005011336 016421� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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; use 5.010; package RT::Crypt::SMIME; use Role::Basic 'with'; with 'RT::Crypt::Role'; use RT::Crypt; use File::Which qw(); use IPC::Run3 0.036 'run3'; use RT::Util 'safe_run_child'; use Crypt::X509; use String::ShellQuote 'shell_quote'; =head1 NAME RT::Crypt::SMIME - encrypt/decrypt and sign/verify email messages with the SMIME =head1 CONFIGURATION You should start from reading L<RT::Crypt>. =head2 %SMIME Set( %SMIME, Enable => 1, OpenSSL => '/usr/bin/openssl', Keyring => '/opt/rt5/var/data/smime', CAPath => '/opt/rt5/var/data/smime/signing-ca.pem', Passphrase => { 'queue.address@example.com' => 'passphrase', '' => 'fallback', }, ); =head3 OpenSSL Path to openssl executable. =head3 Keyring Path to directory with keys and certificates for queues. Key and certificates should be stored in a PEM file named, e.g., F<email.address@example.com.pem>. See L</Keyring configuration>. =head3 CAPath C<CAPath> should be set to either a PEM-formatted certificate of a single signing certificate authority, or a directory of such (including hash symlinks as created by the openssl tool C<c_rehash>). Only SMIME certificates signed by these certificate authorities will be treated as valid signatures. If left unset (and C<AcceptUntrustedCAs> is unset, as it is by default), no signatures will be marked as valid! =head3 AcceptUntrustedCAs Allows arbitrary SMIME certificates, no matter their signing entities. Such mails will be marked as untrusted, but signed; C<CAPath> will be used to mark which mails are signed by trusted certificate authorities. This configuration is generally insecure, as it allows the possibility of accepting forged mail signed by an untrusted certificate authority. Setting this option also allows encryption to users with certificates created by untrusted CAs. =head3 Passphrase C<Passphrase> may be set to a scalar (to use for all keys), an anonymous function, or a hash (to look up by address). If the hash is used, the '' key is used as a default. =head2 Keyring configuration RT looks for keys in the directory configured in the L</Keyring> option of the L<RT_Config/%SMIME>. While public certificates are also stored on users, private SSL keys are only loaded from disk. Keys and certificates should be concatenated, in in PEM format, in files named C<email.address@example.com.pem>, for example. These files need be readable by the web server user which is running RT's web interface; however, if you are running cronjobs or other utilities that access RT directly via API, and may generate encrypted/signed notifications, then the users you execute these scripts under must have access too. The keyring on disk will be checked before the user with the email address is examined. If the file exists, it will be used in preference to the certificate on the user. =cut sub OpenSSLPath { state $cache = RT->Config->Get('SMIME')->{'OpenSSL'}; $cache = $_[1] if @_ > 1; return $cache; } sub Probe { my $self = shift; my $bin = $self->OpenSSLPath(); unless ($bin) { $RT::Logger->warning( "No openssl path set; SMIME support has been disabled. ". "Check the 'OpenSSL' configuration in %OpenSSL"); return 0; } if ($bin =~ m{^/}) { unless (-f $bin and -x _) { $RT::Logger->warning( "Invalid openssl path $bin; SMIME support has been disabled. ". "Check the 'OpenSSL' configuration in %OpenSSL"); return 0; } } else { local $ENV{PATH} = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' unless defined $ENV{PATH}; my $path = File::Which::which( $bin ); unless ($path) { $RT::Logger->warning( "Can't find openssl binary '$bin' in PATH ($ENV{PATH}); SMIME support has been disabled. ". "You may need to specify a full path to opensssl via the 'OpenSSL' configuration in %OpenSSL"); return 0; } $self->OpenSSLPath( $bin = $path ); } { my ($buf, $err) = ('', ''); local $SIG{'CHLD'} = 'DEFAULT'; safe_run_child { run3( [$bin, "list-standard-commands"], \undef, \$buf, \$err ) }; if ($err && $err =~ /Invalid command/) { ($buf, $err) = ('', ''); safe_run_child { run3( [$bin, "list", "-commands"], \undef, \$buf, \$err ) }; } if ($? or $err) { $RT::Logger->warning( "RT's SMIME libraries couldn't successfully execute openssl.". " SMIME support has been disabled") ; return; } elsif ($buf !~ /\bsmime\b/) { $RT::Logger->warning( "openssl does not include smime support.". " SMIME support has been disabled"); return; } else { return 1; } } } sub SignEncrypt { my $self = shift; my %args = ( Entity => undef, Sign => 1, Signer => undef, Passphrase => undef, Encrypt => 1, Recipients => undef, @_ ); my $entity = $args{'Entity'}; if ( $args{'Encrypt'} ) { my %seen; $args{'Recipients'} = [ grep !$seen{$_}++, map $_->address, map Email::Address->parse(Encode::decode("UTF-8",$_)), grep defined && length, map $entity->head->get($_), qw(To Cc Bcc) ]; } $entity->make_multipart('mixed', Force => 1); my ($buf, %res) = $self->_SignEncrypt( %args, Content => \$entity->parts(0)->stringify, ); unless ( $buf ) { $entity->make_singlepart; return %res; } my $tmpdir = File::Temp::tempdir( TMPDIR => 1, CLEANUP => 1 ); my $parser = MIME::Parser->new(); $parser->output_dir($tmpdir); my $newmime = $parser->parse_data($$buf); # Work around https://rt.cpan.org/Public/Bug/Display.html?id=87835 for my $part (grep {$_->is_multipart and $_->preamble and @{$_->preamble}} $newmime->parts_DFS) { $part->preamble->[-1] .= "\n" if $part->preamble->[-1] =~ /\r$/; } $entity->parts([$newmime]); $entity->make_singlepart; return %res; } sub SignEncryptContent { my $self = shift; my %args = ( Content => undef, @_ ); my ($buf, %res) = $self->_SignEncrypt(%args); ${ $args{'Content'} } = $$buf if $buf; return %res; } sub _SignEncrypt { my $self = shift; my %args = ( Content => undef, Sign => 1, Signer => undef, Passphrase => undef, Encrypt => 1, Recipients => [], @_ ); my %res = (exit_code => 0, status => ''); my @keys; if ( $args{'Encrypt'} ) { my @addresses = @{ $args{'Recipients'} }; foreach my $address ( @addresses ) { $RT::Logger->debug( "Considering encrypting message to " . $address ); my %key_info = $self->GetKeysInfo( Key => $address ); unless ( defined $key_info{'info'} ) { $res{'exit_code'} = 1; my $reason = 'Key not found'; $res{'status'} .= $self->FormatStatus({ Operation => "RecipientsCheck", Status => "ERROR", Message => "Recipient '$address' is unusable, the reason is '$reason'", Recipient => $address, Reason => $reason, }); next; } if ( not $key_info{'info'}[0]{'Expire'} ) { # we continue here as it's most probably a problem with the key, # so later during encryption we'll get verbose errors $RT::Logger->error( "Trying to send an encrypted message to ". $address .", but we couldn't get expiration date of the key." ); } elsif ( $key_info{'info'}[0]{'Expire'}->Diff( time ) < 0 ) { $res{'exit_code'} = 1; my $reason = 'Key expired'; $res{'status'} .= $self->FormatStatus({ Operation => "RecipientsCheck", Status => "ERROR", Message => "Recipient '$address' is unusable, the reason is '$reason'", Recipient => $address, Reason => $reason, }); next; } push @keys, $key_info{'info'}[0]{'Content'}; } } return (undef, %res) if $res{'exit_code'}; my $opts = RT->Config->Get('SMIME'); my @commands; if ( $args{'Sign'} ) { my $file = $self->CheckKeyring( Key => $args{'Signer'} ); unless ($file) { $res{'status'} .= $self->FormatStatus({ Operation => "KeyCheck", Status => "MISSING", Message => "Secret key for $args{Signer} is not available", Key => $args{Signer}, KeyType => "secret", }); $res{exit_code} = 1; return (undef, %res); } $args{'Passphrase'} = $self->GetPassphrase( Address => $args{'Signer'} ) unless defined $args{'Passphrase'}; push @commands, [ $self->OpenSSLPath, qw(smime -sign), -signer => $file, -inkey => $file, (defined $args{'Passphrase'} && length $args{'Passphrase'}) ? (qw(-passin env:SMIME_PASS)) : (), ]; } if ( $args{'Encrypt'} ) { foreach my $key ( @keys ) { my $key_file = File::Temp->new; print $key_file $key; close $key_file; $key = $key_file; } push @commands, [ $self->OpenSSLPath, qw(smime -encrypt -des3), map { $_->filename } @keys ]; } my $buf = ${ $args{'Content'} }; for my $command (@commands) { my ($out, $err) = ('', ''); { local $ENV{'SMIME_PASS'} = $args{'Passphrase'}; local $SIG{'CHLD'} = 'DEFAULT'; safe_run_child { run3( $command, \$buf, \$out, \$err ) }; } $RT::Logger->debug( "openssl stderr: " . $err ) if length $err; # copy output from the first command to the second command # similar to the pipe we used to use to pipe signing -> encryption # Using the pipe forced us to invoke the shell, this avoids any use of shell. $buf = $out; } if ($buf) { $res{'status'} .= $self->FormatStatus({ Operation => "Sign", Status => "DONE", Message => "Signed message", }) if $args{'Sign'}; $res{'status'} .= $self->FormatStatus({ Operation => "Encrypt", Status => "DONE", Message => "Data has been encrypted", }) if $args{'Encrypt'}; } return (\$buf, %res); } sub VerifyDecrypt { my $self = shift; my %args = ( Info => undef, @_ ); my %res; my $item = $args{'Info'}; if ( $item->{'Type'} eq 'signed' ) { %res = $self->Verify( %$item ); } elsif ( $item->{'Type'} eq 'encrypted' ) { %res = $self->Decrypt( %args, %$item ); } else { die "Unknown type '". $item->{'Type'} ."' of protected item"; } return (%res, status_on => $item->{'Data'}); } sub Verify { my $self = shift; my %args = (Data => undef, @_ ); my $msg = $args{'Data'}->as_string; my %res; my $buf; my $keyfh = File::Temp->new; { local $SIG{CHLD} = 'DEFAULT'; my $cmd = [ $self->OpenSSLPath, qw(smime -verify -noverify), '-signer', $keyfh->filename, ]; safe_run_child { run3( $cmd, \$msg, \$buf, \$res{'stderr'} ) }; $res{'exit_code'} = $?; } if ( $res{'exit_code'} ) { if ($res{stderr} =~ /(signature|digest) failure/) { $res{'message'} = "Validation failed"; $res{'status'} = $self->FormatStatus({ Operation => "Verify", Status => "BAD", Message => "The signature did not verify", }); } else { $res{'message'} = "openssl exited with error code ". ($? >> 8) ." and error: $res{stderr}"; $res{'status'} = $self->FormatStatus({ Operation => "Verify", Status => "ERROR", Message => "There was an error verifying: $res{stderr}", }); $RT::Logger->error($res{'message'}); } return %res; } my $signer; if ( my $key = do { $keyfh->seek(0, 0); local $/; readline $keyfh } ) {{ my %info = $self->GetCertificateInfo( Certificate => $key ); $signer = $info{info}[0]; last unless $signer and $signer->{User}[0]{String}; unless ( $info{info}[0]{TrustLevel} > 0 or RT->Config->Get('SMIME')->{AcceptUntrustedCAs}) { # We don't trust it; give it the finger $res{exit_code} = 1; $res{'message'} = "Validation failed"; $res{'status'} = $self->FormatStatus({ Operation => "Verify", Status => "BAD", Message => "The signing CA was not trusted", UserString => $signer->{User}[0]{String}, Trust => "NONE", }); return %res; } my ($address) = Email::Address->parse($signer->{User}[0]{String}); my $user = RT::User->new( $RT::SystemUser ); $user->LoadOrCreateByEmail( EmailAddress => $address->address, RealName => $address->phrase, Comments => 'Autocreated during SMIME parsing', ); my $current_key = $user->SMIMECertificate; last if $current_key && $current_key eq $key; # Never over-write existing keys with untrusted ones. last if $current_key and not $info{info}[0]{TrustLevel} > 0; my ($status, $msg) = $user->SetSMIMECertificate( $key ); $RT::Logger->error("Couldn't set SMIME certificate for user #". $user->id .": $msg") unless $status; }} my $res_entity = _extract_msg_from_buf( \$buf ); unless ( $res_entity ) { $res{'exit_code'} = 1; $res{'message'} = "verified message, but couldn't parse result"; $res{'status'} = $self->FormatStatus({ Operation => "Verify", Status => "DONE", Message => "The signature is good, unknown signer", Trust => "UNKNOWN", }); return %res; } $res_entity->make_multipart( 'mixed', Force => 1 ); $args{'Data'}->make_multipart( 'mixed', Force => 1 ); $args{'Data'}->parts([ $res_entity->parts ]); $args{'Data'}->make_singlepart; $res{'status'} = $self->FormatStatus({ Operation => "Verify", Status => "DONE", Message => "The signature is good, signed by ".$signer->{User}[0]{String}.", trust is ".$signer->{TrustTerse}, UserString => $signer->{User}[0]{String}, Trust => uc($signer->{TrustTerse}), }); return %res; } sub Decrypt { my $self = shift; my %args = (Data => undef, Queue => undef, @_ ); my $msg = $args{'Data'}->as_string; push @{ $args{'Recipients'} ||= [] }, $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'), $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress') ; my ($buf, %res) = $self->_Decrypt( %args, Content => \$args{'Data'}->as_string ); return %res unless $buf; my $res_entity = _extract_msg_from_buf( $buf ); $res_entity->make_multipart( 'mixed', Force => 1 ); # Work around https://rt.cpan.org/Public/Bug/Display.html?id=87835 for my $part (grep {$_->is_multipart and $_->preamble and @{$_->preamble}} $res_entity->parts_DFS) { $part->preamble->[-1] .= "\n" if $part->preamble->[-1] =~ /\r$/; } $args{'Data'}->make_multipart( 'mixed', Force => 1 ); $args{'Data'}->parts([ $res_entity->parts ]); $args{'Data'}->make_singlepart; return %res; } sub DecryptContent { my $self = shift; my %args = ( Content => undef, @_ ); my ($buf, %res) = $self->_Decrypt( %args ); ${ $args{'Content'} } = $$buf if $buf; return %res; } sub _Decrypt { my $self = shift; my %args = (Content => undef, @_ ); my %seen; my @addresses = grep !$seen{lc $_}++, map $_->address, map Email::Address->parse($_), grep length && defined, @{$args{'Recipients'}}; my ($buf, $encrypted_to, %res); foreach my $address ( @addresses ) { my $file = $self->CheckKeyring( Key => $address ); unless ( $file ) { my $keyring = RT->Config->Get('SMIME')->{'Keyring'}; $RT::Logger->debug("No key found for $address in $keyring directory"); next; } local $ENV{SMIME_PASS} = $self->GetPassphrase( Address => $address ); local $SIG{CHLD} = 'DEFAULT'; my $cmd = [ $self->OpenSSLPath, qw(smime -decrypt), -recip => $file, (defined $ENV{'SMIME_PASS'} && length $ENV{'SMIME_PASS'}) ? (qw(-passin env:SMIME_PASS)) : (), ]; safe_run_child { run3( $cmd, $args{'Content'}, \$buf, \$res{'stderr'} ) }; unless ( $? ) { $encrypted_to = $address; $RT::Logger->debug("Message encrypted for $encrypted_to"); last; } if ( index($res{'stderr'}, 'no recipient matches key') >= 0 ) { $RT::Logger->debug("Although we have a key for $address, it is not the one that encrypted this message"); next; } $res{'exit_code'} = $?; $res{'message'} = "openssl exited with error code ". ($? >> 8) ." and error: $res{stderr}"; $RT::Logger->error( $res{'message'} ); $res{'status'} = $self->FormatStatus({ Operation => 'Decrypt', Status => 'ERROR', Message => 'Decryption failed', EncryptedTo => $address, }); return (undef, %res); } unless ( $encrypted_to ) { $RT::Logger->error("Couldn't find SMIME key for addresses: ". join ', ', @addresses); $res{'exit_code'} = 1; $res{'status'} = $self->FormatStatus({ Operation => 'KeyCheck', Status => 'MISSING', Message => "Secret key is not available", KeyType => 'secret', }); return (undef, %res); } $res{'status'} = $self->FormatStatus({ Operation => 'Decrypt', Status => 'DONE', Message => 'Decryption process succeeded', EncryptedTo => $encrypted_to, }); return (\$buf, %res); } sub FormatStatus { my $self = shift; my @status = @_; my $res = ''; foreach ( @status ) { while ( my ($k, $v) = each %$_ ) { $res .= "[SMIME:]". $k .": ". $v ."\n"; } $res .= "[SMIME:]\n"; } return $res; } sub ParseStatus { my $self = shift; my $status = shift; return () unless $status; my @status = split /\s*(?:\[SMIME:\]\s*){2}/, $status; foreach my $block ( grep length, @status ) { chomp $block; $block = { map { s/^\s+//; s/\s+$//; $_ } map split(/:/, $_, 2), split /\s*\[SMIME:\]/, $block }; } foreach my $block ( grep $_->{'EncryptedTo'}, @status ) { $block->{'EncryptedTo'} = [{ EmailAddress => $block->{'EncryptedTo'}, }]; } return @status; } sub _extract_msg_from_buf { my $buf = shift; my $rtparser = RT::EmailParser->new(); my $parser = MIME::Parser->new(); $rtparser->_SetupMIMEParser($parser); $parser->decode_bodies(0); $parser->output_to_core(1); unless ( $rtparser->{'entity'} = $parser->parse_data($$buf) ) { $RT::Logger->crit("Couldn't parse MIME stream and extract the submessages"); # Try again, this time without extracting nested messages $parser->extract_nested_messages(0); unless ( $rtparser->{'entity'} = $parser->parse_data($$buf) ) { $RT::Logger->crit("couldn't parse MIME stream"); return (undef); } } return $rtparser->Entity; } sub FindScatteredParts { return () } sub CheckIfProtected { my $self = shift; my %args = ( Entity => undef, @_ ); my $entity = $args{'Entity'}; my $type = $entity->effective_type; if ( $type =~ m{^application/(?:x-)?pkcs7-mime$} || $type eq 'application/octet-stream' ) { # RFC3851 ch.3.9 variant 1 and 3 my $security_type; my $smime_type = $entity->head->mime_attr('Content-Type.smime-type'); if ( $smime_type ) { # it's optional according to RFC3851 if ( $smime_type eq 'enveloped-data' ) { $security_type = 'encrypted'; } elsif ( $smime_type eq 'signed-data' ) { $security_type = 'signed'; } elsif ( $smime_type eq 'certs-only' ) { $security_type = 'certificate management'; } elsif ( $smime_type eq 'compressed-data' ) { $security_type = 'compressed'; } else { $security_type = $smime_type; } } unless ( $security_type ) { my $fname = $entity->head->recommended_filename || ''; if ( $fname =~ /\.p7([czsm])$/ ) { my $type_char = $1; if ( $type_char eq 'm' ) { # RFC3851, ch3.4.2 # it can be both encrypted and signed $security_type = 'encrypted'; } elsif ( $type_char eq 's' ) { # RFC3851, ch3.4.3, multipart/signed, XXX we should never be here # unless message is changed by some gateway $security_type = 'signed'; } elsif ( $type_char eq 'c' ) { # RFC3851, ch3.7 $security_type = 'certificate management'; } elsif ( $type_char eq 'z' ) { # RFC3851, ch3.5 $security_type = 'compressed'; } } } return () unless $security_type; my %res = ( Type => $security_type, Format => 'RFC3851', Data => $entity, ); if ( $security_type eq 'encrypted' ) { my $top = $args{'TopEntity'}->head; $res{'Recipients'} = [map {Encode::decode("UTF-8", $_)} grep defined && length, map $top->get($_), 'To', 'Cc']; } return %res; } elsif ( $type eq 'multipart/signed' ) { # RFC3156, multipart/signed # RFC3851, ch.3.9 variant 2 unless ( $entity->parts == 2 ) { $RT::Logger->error( "Encrypted or signed entity must has two subparts. Skipped" ); return (); } my $protocol = $entity->head->mime_attr( 'Content-Type.protocol' ); unless ( $protocol ) { $RT::Logger->error( "Entity is '$type', but has no protocol defined. Skipped" ); return (); } unless ( $protocol =~ m{^application/(x-)?pkcs7-signature$} ) { $RT::Logger->info( "Skipping protocol '$protocol', only 'application/x-pkcs7-signature' is supported" ); return (); } $RT::Logger->debug("Found part signed according to RFC3156"); return ( Type => 'signed', Format => 'RFC3156', Data => $entity, ); } return (); } sub GetKeysForEncryption { my $self = shift; my %args = (Recipient => undef, @_); my $recipient = delete $args{'Recipient'}; my %res = $self->GetKeysInfo( Key => $recipient, %args, Type => 'public' ); return %res unless $res{'info'}; foreach my $key ( splice @{ $res{'info'} } ) { if ( not $key->{'Expire'} ) { # we continue here as it's most probably a problem with the key, # so later during encryption we'll get verbose errors $RT::Logger->error( "Trying to send an encrypted message to ". $recipient .", but we couldn't get expiration date of the key." ); } elsif ( $key->{'Expire'}->Diff( time ) < 0 ) { $RT::Logger->info( "Trying to send an encrypted message to ". $recipient .", but ignoring expired key " . $key->{Fingerprint} ); next; } push @{ $res{'info'} }, $key; } delete $res{'info'} unless @{ $res{'info'} }; return %res; } sub GetKeysForSigning { my $self = shift; my %args = (Signer => undef, @_); return $self->GetKeysInfo( Key => delete $args{'Signer'}, %args, Type => 'private' ); } sub GetKeysInfo { my $self = shift; my %args = ( Key => undef, Type => 'public', Force => 0, @_ ); my $email = $args{'Key'}; unless ( $email ) { return (exit_code => 0); # unless $args{'Force'}; } my $key = $self->GetKeyContent( %args ); return (exit_code => 0) unless $key; return $self->GetCertificateInfo( Certificate => $key ); } sub GetKeyContent { my $self = shift; my %args = ( Key => undef, @_ ); my $key; if ( my $file = $self->CheckKeyring( %args ) ) { open my $fh, '<:raw', $file or die "Couldn't open file '$file': $!"; $key = do { local $/; readline $fh }; close $fh; } else { my $user = RT::User->new( RT->SystemUser ); $user->LoadByEmail( $args{'Key'} ); $key = $user->SMIMECertificate if $user->id; } return $key; } sub CheckKeyring { my $self = shift; my %args = ( Key => undef, @_, ); my $keyring = RT->Config->Get('SMIME')->{'Keyring'}; return undef unless $keyring; my $file = File::Spec->catfile( $keyring, $args{'Key'} .'.pem' ); return undef unless -f $file; return $file; } sub GetCertificateInfo { my $self = shift; my %args = ( Certificate => undef, @_, ); if ($args{Certificate} =~ /^-----BEGIN \s+ CERTIFICATE----- \s* $ (.*?) ^-----END \s+ CERTIFICATE----- \s* $/smx) { $args{Certificate} = MIME::Base64::decode_base64($1); } my $cert = Crypt::X509->new( cert => $args{Certificate} ); return ( exit_code => 1, stderr => $cert->error ) if $cert->error; my %USER_MAP = ( Country => 'country', StateOrProvince => 'state', Organization => 'org', OrganizationUnit => 'ou', Name => 'cn', EmailAddress => 'email', ); my $canonicalize = sub { my $type = shift; my %data; for (keys %USER_MAP) { my $method = $type . "_" . $USER_MAP{$_}; $data{$_} = $cert->$method if $cert->can($method); } $data{String} = Email::Address->new( @data{'Name', 'EmailAddress'} )->format if $data{EmailAddress}; return \%data; }; my $PEM = "-----BEGIN CERTIFICATE-----\n" . MIME::Base64::encode_base64( $args{Certificate} ) . "-----END CERTIFICATE-----\n"; my %res = ( exit_code => 0, info => [ { Content => $PEM, Fingerprint => Digest::SHA::sha1_hex($args{Certificate}), 'Serial Number' => $cert->serial, Created => $self->ParseDate( $cert->not_before ), Expire => $self->ParseDate( $cert->not_after ), Version => sprintf("%d (0x%x)",hex($cert->version || 0)+1, hex($cert->version || 0)), Issuer => [ $canonicalize->( 'issuer' ) ], User => [ $canonicalize->( 'subject' ) ], } ], stderr => '' ); # Check the validity my $ca = RT->Config->Get('SMIME')->{'CAPath'}; if ($ca) { my @ca_verify; if (-d $ca) { @ca_verify = ('-CApath', $ca); } elsif (-f $ca) { @ca_verify = ('-CAfile', $ca); } local $SIG{CHLD} = 'DEFAULT'; my $cmd = [ $self->OpenSSLPath, 'verify', @ca_verify, ]; my $buf = ''; safe_run_child { run3( $cmd, \$PEM, \$buf, \$res{stderr} ) }; if ($buf =~ /^stdin: OK$/) { $res{info}[0]{Trust} = "Signed by trusted CA $res{info}[0]{Issuer}[0]{String}"; $res{info}[0]{TrustTerse} = "full"; $res{info}[0]{TrustLevel} = 2; } elsif ($? == 0 or ($? >> 8) == 2) { $res{info}[0]{Trust} = "UNTRUSTED signing CA $res{info}[0]{Issuer}[0]{String}"; $res{info}[0]{TrustTerse} = "none"; $res{info}[0]{TrustLevel} = -1; } else { $res{exit_code} = $?; $res{message} = "openssl exited with error code ". ($? >> 8) ." and stout: $buf"; $res{info}[0]{Trust} = "unknown (openssl failed)"; $res{info}[0]{TrustTerse} = "unknown"; $res{info}[0]{TrustLevel} = 0; } } else { $res{info}[0]{Trust} = "unknown (no CAPath set)"; $res{info}[0]{TrustTerse} = "unknown"; $res{info}[0]{TrustLevel} = 0; } $res{info}[0]{Formatted} = $res{info}[0]{User}[0]{String} . " (issued by $res{info}[0]{Issuer}[0]{String})"; return %res; } 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Crypt/GnuPG/������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 016155� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Crypt/GnuPG/CRLFHandle.pm�����������������������������������������������������������000644 �000765 �000024 �00000005311 14005011336 020355� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Crypt::GnuPG::CRLFHandle; use strict; use warnings; use base qw(IO::Handle); # https://metacpan.org/module/MIME::Tools#Fuzzing-of-CRLF-and-newline-when-encoding-composing # means that the output of $entity->print contains lines terminated by # "\n"; however, signatures are generated off of the "correct" form of # the MIME entity, which uses "\r\n" as the newline separator. This # class, used only when generating signatures, transparently munges "\n" # newlines into "\r\n" newlines such that the generated signature is # correct for the "\r\n"-newline version of the MIME entity which will # eventually be sent over the wire. sub print { my ($self, @args) = (@_); s/\r*\n/\x0D\x0A/g foreach @args; return $self->SUPER::print( @args ); } 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Test/Web.pm�������������������������������������������������������������������������000644 �000765 �000024 �00000033343 14005011336 016074� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Test::Web; use strict; use warnings; use base qw(Test::WWW::Mechanize); use MIME::Base64 qw//; use Encode 'encode_utf8'; use Storable 'thaw'; use HTTP::Status qw(); BEGIN { require RT::Test; } require Test::More; $RT::Test::Web::INSTANCES = undef; sub new { my ($class, @args) = @_; push @args, app => $RT::Test::TEST_APP if $RT::Test::TEST_APP; my $self = $class->SUPER::new(@args); $self->cookie_jar(HTTP::Cookies->new); # Clear our caches of anything that the server process may have done $self->add_handler( response_done => sub { RT::Record->FlushCache; }, ) if RT::Record->can( "FlushCache" ); $RT::Test::Web::INSTANCES++; return $self; } sub clone { my $self = shift; $RT::Test::Web::INSTANCES++ if defined $RT::Test::Web::INSTANCES; return $self->SUPER::clone(); } sub get_ok { my $self = shift; my $url = shift; if ( $url =~ s!^/!! ) { $url = $self->rt_base_url . $url; } local $Test::Builder::Level = $Test::Builder::Level + 1; my $rv = $self->SUPER::get_ok($url, @_); Test::More::diag( "Couldn't get $url" ) unless $rv; return $rv; } sub rt_base_url { return $RT::Test::existing_server if $RT::Test::existing_server; return "http://localhost:" . RT->Config->Get('WebPort') . RT->Config->Get('WebPath') . "/"; } sub login { my $self = shift; my $user = shift || 'root'; my $pass = shift || 'password'; my %args = @_; $self->logout if $args{logout}; my $url = $self->rt_base_url; $self->get($url . "?user=$user;pass=$pass"); return 0 unless $self->logged_in_as($user); unless ( $self->content =~ m/Logout/i ) { Test::More::diag("error: page has no Logout"); return 0; } return 1; } sub logged_in_as { my $self = shift; my $user = shift || ''; unless ( $self->status == HTTP::Status::HTTP_OK ) { Test::More::diag( "error: status is ". $self->status ); return 0; } RT::Interface::Web::EscapeHTML(\$user); unless ( $self->content =~ m{<span class="current-user">\Q$user\E</span>}i ) { Test::More::diag("Page has no user name"); return 0; } return 1; } sub logout { my $self = shift; my $url = $self->rt_base_url; $self->get($url); Test::More::diag( "error: status is ". $self->status ) unless $self->status == HTTP::Status::HTTP_OK; if ( $self->content =~ /Logout/i ) { $self->follow_link( text => 'Logout' ); Test::More::diag( "error: status is ". $self->status ." when tried to logout" ) unless $self->status == HTTP::Status::HTTP_OK; } else { return 1; } $self->get($url); if ( $self->content =~ /Logout/i ) { Test::More::diag( "error: couldn't logout" ); return 0; } return 1; } sub goto_ticket { my $self = shift; my $id = shift; my $view = shift || 'Display'; my $status = shift || HTTP::Status::HTTP_OK; unless ( $id && int $id ) { Test::More::diag( "error: wrong id ". defined $id? $id : '(undef)' ); return 0; } my $url = $self->rt_base_url; $url .= "Ticket/${ view }.html?id=$id"; $self->get($url); unless ( $self->status == $status ) { Test::More::diag( "error: status is ". $self->status ); return 0; } return 1; } sub goto_create_ticket { my $self = shift; my $queue = shift; my $id; if ( ref $queue ) { $id = $queue->id; } elsif ( $queue =~ /^\d+$/ ) { $id = $queue; } else { my $queue_obj = RT::Queue->new(RT->SystemUser); my ($ok, $msg) = $queue_obj->Load($queue); die "Unable to load queue '$queue': $msg" if !$ok; $id = $queue_obj->id; } $self->get($self->rt_base_url . 'Ticket/Create.html?Queue='.$id); return 1; } sub get_warnings { my $self = shift; local $Test::Builder::Level = $Test::Builder::Level + 1; # We clone here so that when we fetch warnings, we don't disrupt the state # of the test's mech. If we reuse the original mech then you can't # test warnings immediately after fetching page XYZ, then fill out # forms on XYZ. This is because the most recently fetched page has changed # from XYZ to /__test_warnings, which has no form. my $clone = $self->clone; return unless $clone->get_ok('/__test_warnings'); return @{ thaw $clone->content }; } sub warning_like { my $self = shift; my $re = shift; my $name = shift; local $Test::Builder::Level = $Test::Builder::Level + 1; my @warnings = $self->get_warnings; if (@warnings == 0) { Test::More::fail("no warnings emitted; expected 1"); return 0; } elsif (@warnings > 1) { Test::More::fail(scalar(@warnings) . " warnings emitted; expected 1"); for (@warnings) { Test::More::diag("got warning: $_"); } return 0; } return Test::More::like($warnings[0], $re, $name); } sub next_warning_like { my $self = shift; my $re = shift; my $name = shift; local $Test::Builder::Level = $Test::Builder::Level + 1; if (@{ $self->{stashed_server_warnings} || [] } == 0) { my @warnings = $self->get_warnings; if (@warnings == 0) { Test::More::fail("no warnings emitted; expected 1"); return 0; } $self->{stashed_server_warnings} = \@warnings; } my $warning = shift @{ $self->{stashed_server_warnings} }; return Test::More::like($warning, $re, $name); } sub no_warnings_ok { my $self = shift; my $name = shift || "no warnings emitted"; local $Test::Builder::Level = $Test::Builder::Level + 1; my @warnings = $self->get_warnings; Test::More::is(@warnings, 0, $name); for (@warnings) { Test::More::diag("got warning: $_"); } return @warnings == 0 ? 1 : 0; } sub no_leftover_warnings_ok { my $self = shift; my $name = shift || "no leftover warnings"; local $Test::Builder::Level = $Test::Builder::Level + 1; # we clear the warnings because we don't want to break later tests # in case there *are* leftover warnings my @warnings = splice @{ $self->{stashed_server_warnings} || [] }; Test::More::is(@warnings, 0, $name); for (@warnings) { Test::More::diag("leftover warning: $_"); } return @warnings == 0 ? 1 : 0; } sub ticket_status { my $self = shift; my $id = shift; $self->display_ticket( $id); my ($got) = ($self->content =~ m{Status:\s*</div>\s*<div.*?>\s*<span.*?>\s*([\w ]+?)\s*</span>}ism); unless ( $got ) { Test::More::diag("Error: couldn't find status value on the page, may be regexp problem"); } return $got; } sub ticket_status_is { my $self = shift; my $id = shift; my $status = shift; my $desc = shift || "Status of the ticket #$id is '$status'"; local $Test::Builder::Level = $Test::Builder::Level + 1; return Test::More::is($self->ticket_status( $id), $status, $desc); } sub get_ticket_id { my $self = shift; my $content = $self->content; my $id = 0; if ($content =~ /.*Ticket (\d+) created.*/g) { $id = $1; } elsif ($content =~ /.*No permission to view newly created ticket #(\d+).*/g) { Test::More::diag("\nNo permissions to view the ticket.\n") if($ENV{'TEST_VERBOSE'}); $id = $1; } return $id; } sub set_custom_field { my $self = shift; my $queue = shift; my $cf_name = shift; my $val = shift; my $field_name = $self->custom_field_input( $queue, $cf_name ) or return 0; $self->field($field_name, $val); return 1; } sub custom_field_input { my $self = shift; my $queue = shift; my $cf_name = shift; my $cf_obj = RT::CustomField->new( $RT::SystemUser ); $cf_obj->LoadByName( Name => $cf_name, LookupType => RT::Ticket->CustomFieldLookupType, ObjectId => $queue, ); unless ( $cf_obj->id ) { Test::More::diag("Can not load custom field '$cf_name' in queue '$queue'"); return undef; } my $cf_id = $cf_obj->id; my ($res) = grep /^Object-RT::Ticket-\d*-CustomField(?::\w+)?-$cf_id-Values?$/, map $_->name, $self->current_form->inputs; unless ( $res ) { Test::More::diag("Can not find input for custom field '$cf_name' #$cf_id"); return undef; } return $res; } sub value_name { my $self = shift; my $field = shift; my $input = $self->current_form->find_input( $field ) or return undef; my @names = $input->value_names; return $input->value unless @names; my @values = $input->possible_values; for ( my $i = 0; $i < @values; $i++ ) { return $names[ $i ] if $values[ $i ] eq $input->value; } return undef; } sub check_links { my $self = shift; my %args = @_; my %has = map {$_ => 1} @{ $args{'has'} }; my %has_no = map {$_ => 1} @{ $args{'has_no'} }; local $Test::Builder::Level = $Test::Builder::Level + 1; my @found; my @links = $self->followable_links; foreach my $text ( grep defined && length, map $_->text, @links ) { push @found, $text if $has_no{ $text }; delete $has{ $text }; } if ( @found || keys %has ) { Test::More::ok( 0, "expected links" ); Test::More::diag( "didn't expect, but found: ". join ', ', map "'$_'", @found ) if @found; Test::More::diag( "didn't find, but expected: ". join ', ', map "'$_'", keys %has ) if keys %has; return 0; } return Test::More::ok( 1, "expected links" ); } sub auth { my $self = shift; $self->default_header( $self->auth_header(@_) ); } sub auth_header { my $self = shift; return Authorization => "Basic " . MIME::Base64::encode( join(":", @_) ); } sub dom { my $self = shift; Carp::croak("Can not get DOM, not HTML repsone") unless $self->is_html; require Mojo::DOM; return Mojo::DOM->new( $self->content ); } # override content_* and text_* methods in Test::Mech to dump the content # on failure, to speed investigation for my $method_name (qw/ content_is content_contains content_lacks content_like content_unlike text_contains text_lacks text_like text_unlike /) { my $super_method = __PACKAGE__->SUPER::can($method_name); my $implementation = sub { local $Test::Builder::Level = $Test::Builder::Level + 1; my $self = shift; my $ok = $self->$super_method(@_); if (!$ok) { my $dir = RT::Test->temp_directory; my ($name) = $self->uri->path =~ m{/([^/]+)$}; $name ||= 'index.html'; my $file = $dir . '/' . RT::Test->builder->current_test . '-' . $name; open my $handle, '>', $file or die $!; print $handle encode_utf8($self->content) or die $!; close $handle or die $!; Test::More::diag("Dumped failing test page content to $file"); } return $ok; }; no strict 'refs'; *{$method_name} = $implementation; } sub DESTROY { my $self = shift; if (defined $RT::Test::Web::INSTANCES) { $RT::Test::Web::INSTANCES--; if ($RT::Test::Web::INSTANCES == 0 ) { # Ordering matters -- clean out INSTANCES before we check # warnings, so the clone therein sees that we've already begun # cleanups. undef $RT::Test::Web::INSTANCES; $self->no_warnings_ok; } } } END { return if RT::Test->builder->{Original_Pid} != $$; if (defined $RT::Test::Web::INSTANCES and $RT::Test::Web::INSTANCES == 0 ) { # Ordering matters -- clean out INSTANCES after the `new` # bumps it up to 1. my $cleanup = RT::Test::Web->new; undef $RT::Test::Web::INSTANCES; $cleanup->no_warnings_ok; } } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Test/Shredder.pm��������������������������������������������������������������������000644 �000765 �000024 �00000020423 14005011336 017112� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Test::Shredder; use base 'RT::Test'; require File::Copy; require Cwd; =head1 DESCRIPTION RT::Shredder test suite utilities =head1 TESTING Since RT:Shredder 0.01_03 we have a test suite. You can run tests and see if everything works as expected before you try shredder on your actual data. Tests also help in the development process. The test suite uses SQLite databases to store data in individual files, so you could sun tests on your production servers without risking damage to your production data. You'll want to run the test suite almost every time you install or update the shredder distribution, especialy if you have local customizations of the DB schema and/or RT code. Tests are one thing you can write even if you don't know much perl, but want to learn more about RT's internals. New tests are very welcome. =head2 WRITING TESTS The shredder distribution has several files to help write new tests. t/shredder/utils.pl - this file, utilities t/00skeleton.t - skeleteton .t file for new tests All tests follow this algorithm: require "t/shredder/utils.pl"; # plug in utilities init_db(); # create new tmp RT DB and init RT API # create RT data you want to be always in the RT DB # ... create_savepoint('mysp'); # create DB savepoint # create data you want delete with shredder # ... # run shredder on the objects you've created # ... # check that shredder deletes things you want # this command will compare savepoint DB with current cmp_deeply( dump_current_and_savepoint('mysp'), "current DB equal to savepoint"); # then you can create another object and delete it, then check again Savepoints are named and you can create two or more savepoints. =cut sub import { my $class = shift; $class->SUPER::import(@_, tests => undef ); RT::Test::plan( skip_all => 'Shredder tests only work on SQLite' ) unless RT->Config->Get('DatabaseType') eq 'SQLite'; my %args = @_; RT::Test::plan( tests => $args{'tests'} ) if $args{tests}; $class->export_to_level(1); } =head1 FUNCTIONS =head2 DATABASES =head3 db_name Returns the absolute file path to the current DB. It is C<<RT::Test->temp_directory . "rt5test" >>. =cut sub db_name { return RT->Config->Get("DatabaseName") } =head3 connect_sqlite Returns connected DBI DB handle. Takes path to sqlite db. =cut sub connect_sqlite { my $self = shift; return DBI->connect("dbi:SQLite:dbname=". shift, "", ""); } =head2 SHREDDER =head3 shredder_new Creates and returns a new RT::Shredder object. =cut sub shredder_new { my $self = shift; require RT::Shredder; my $obj = RT::Shredder->new; my $file = File::Spec->catfile( $self->temp_directory, 'dump.XXXX.sql' ); $obj->AddDumpPlugin( Arguments => { file_name => $file, from_storage => 0, } ); return $obj; } =head2 SAVEPOINTS =head3 savepoint_name Returns the absolute path to the named savepoint DB file. Takes one argument - savepoint name, by default C<sp>. =cut sub savepoint_name { my $self = shift; my $name = shift || 'default'; return File::Spec->catfile( $self->temp_directory, "sp.$name.db" ); } =head3 create_savepoint Creates savepoint DB from the current DB. Takes name of the savepoint as argument. =head3 restore_savepoint Restores current DB to savepoint state. Takes name of the savepoint as argument. =cut sub create_savepoint { my $self = shift; return $self->__cp_db( $self->db_name => $self->savepoint_name( shift ) ); } sub restore_savepoint { my $self = shift; return $self->__cp_db( $self->savepoint_name( shift ) => $self->db_name ); } sub __cp_db { my $self = shift; my( $orig, $dest ) = @_; RT::Test::__disconnect_rt(); File::Copy::copy( $orig, $dest ) or die "Couldn't copy '$orig' => '$dest': $!"; RT::Test::__reconnect_rt(); return; } =head2 DUMPS =head3 dump_sqlite Returns DB dump as a complex hash structure: { TableName => { #id => { lc_field => 'value', } } } Takes named argument C<CleanDates>. If true, clean all date fields from dump. True by default. =cut sub dump_sqlite { my $self = shift; my $dbh = shift; my %args = ( CleanDates => 1, @_ ); my $old_fhkn = $dbh->{'FetchHashKeyName'}; $dbh->{'FetchHashKeyName'} = 'NAME_lc'; my @tables = $RT::Handle->_TableNames( $dbh ); my $res = {}; foreach my $t( @tables ) { next if lc($t) eq 'sessions'; $res->{$t} = $dbh->selectall_hashref( "SELECT * FROM $t". $self->dump_sqlite_exceptions($t), 'id' ); $self->clean_dates( $res->{$t} ) if $args{'CleanDates'}; die $DBI::err if $DBI::err; } $dbh->{'FetchHashKeyName'} = $old_fhkn; return $res; } =head3 dump_sqlite_exceptions If there are parts of the DB which can change from creating and deleting a queue, skip them when doing the comparison. One example is the global queue cache attribute on RT::System which will be updated on Queue creation and can't be rolled back by the shredder. It may actually make sense for Shredder to be updating this at some point in the future. =cut sub dump_sqlite_exceptions { my $self = shift; my $table = shift; my $special_wheres = { attributes => " WHERE Name != 'QueueCacheNeedsUpdate'" }; return $special_wheres->{lc $table}||''; } =head3 dump_current_and_savepoint Returns dump of the current DB and of the named savepoint. Takes one argument - savepoint name. =cut sub dump_current_and_savepoint { my $self = shift; my $orig = $self->savepoint_name( shift ); die "Couldn't find savepoint file" unless -f $orig && -r _; my $odbh = $self->connect_sqlite( $orig ); return ( $self->dump_sqlite( $RT::Handle->dbh, @_ ), $self->dump_sqlite( $odbh, @_ ) ); } =head3 dump_savepoint_and_current Returns the same data as C<dump_current_and_savepoint> function, but in reversed order. =cut sub dump_savepoint_and_current { return reverse (shift)->dump_current_and_savepoint(@_) } sub clean_dates { my $self = shift; my $h = shift; my $date_re = qr/^\d\d\d\d\-\d\d\-\d\d\s*\d\d\:\d\d(\:\d\d)?$/i; foreach my $id ( keys %{ $h } ) { next unless $h->{ $id }; foreach ( keys %{ $h->{ $id } } ) { delete $h->{$id}{$_} if $h->{$id}{$_} && $h->{$id}{$_} =~ /$date_re/; } } } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Test/Apache.pm����������������������������������������������������������������������000644 �000765 �000024 �00000022400 14005011336 016530� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Test::Apache; use strict; use warnings; my %MODULES = ( '2.2' => { "mod_perl" => [qw(authz_host env alias perl)], "fastcgi" => [qw(authz_host env alias mime fastcgi)], }, '2.4' => { "mod_perl" => [qw(mpm_worker authz_core authn_core authz_host env alias perl)], "fastcgi" => [qw(mpm_worker authz_core authn_core authz_host env alias mime fastcgi)], }, ); my $apache_module_prefix = $ENV{RT_TEST_APACHE_MODULES}; my $apxs = $ENV{RT_TEST_APXS} || RT::Test->find_executable('apxs') || RT::Test->find_executable('apxs2'); if ($apxs and not $apache_module_prefix) { $apache_module_prefix = `$apxs -q LIBEXECDIR`; chomp $apache_module_prefix; } $apache_module_prefix ||= 'modules'; sub basic_auth { my $self = shift; my $passwd = File::Spec->rel2abs( File::Spec->catfile( 't', 'data', 'configs', 'passwords' ) ); return <<"EOT"; AuthType Basic AuthName "restricted area" AuthUserFile $passwd Require user root EOT } sub basic_auth_anon { my $self = shift; return <<"EOT"; AuthType Basic AuthName "restricted area" AuthBasicProvider anon Anonymous * Anonymous_NoUserID On Anonymous_MustGiveEmail Off Anonymous_VerifyEmail Off Require valid-user EOT } sub start_server { my ($self, %config) = @_; my %tmp = %{$config{tmp}}; my %info = $self->apache_server_info( %config ); RT::Test::diag(do { open( my $fh, '<', $tmp{'config'}{'RT'} ) or die $!; local $/; <$fh> }); my $tmpl = File::Spec->rel2abs( File::Spec->catfile( 't', 'data', 'configs', 'apache'. $info{'version'} .'+'. $config{variant} .'.conf' ) ); my %opt = ( listen => $config{port}, server_root => $info{'HTTPD_ROOT'} || $ENV{'HTTPD_ROOT'} || Test::More::BAIL_OUT("Couldn't figure out server root"), document_root => $RT::MasonComponentRoot, tmp_dir => "$tmp{'directory'}", rt_bin_path => $RT::BinPath, rt_sbin_path => $RT::SbinPath, rt_site_config => $ENV{'RT_SITE_CONFIG'}, load_modules => $info{load_modules}, ); if (not $config{basic_auth}) { $opt{basic_auth} = ""; } elsif ($config{basic_auth} eq 'anon') { $opt{basic_auth} = $self->basic_auth_anon; } else { $opt{basic_auth} = $self->basic_auth; } foreach (qw(log pid lock)) { $opt{$_ .'_file'} = File::Spec->catfile( "$tmp{'directory'}", "apache.$_" ); } $tmp{'config'}{'apache'} = File::Spec->catfile( "$tmp{'directory'}", "apache.conf" ); $self->process_in_file( in => $tmpl, out => $tmp{'config'}{'apache'}, options => \%opt, ); $self->fork_exec($info{'executable'}, '-f', $tmp{'config'}{'apache'}); my $pid = do { my $tries = 15; while ( !-s $opt{'pid_file'} ) { $tries--; last unless $tries; sleep 1; } my $pid_fh; unless (-e $opt{'pid_file'} and open($pid_fh, '<', $opt{'pid_file'})) { Test::More::BAIL_OUT("Couldn't start apache server, no pid file (unknown error)") unless -e $opt{log_file}; open my $log, "<", $opt{log_file}; my $error = do {local $/; <$log>}; close $log; $RT::Logger->error($error) if $error; Test::More::BAIL_OUT("Couldn't start apache server!"); } my $pid = <$pid_fh>; chomp $pid; $pid; }; Test::More::ok($pid, "Started apache server #$pid"); return $pid; } sub apache_server_info { my $self = shift; my %res = @_; my $bin = $res{'executable'} = $ENV{'RT_TEST_APACHE'} || $self->find_apache_server || Test::More::BAIL_OUT("Couldn't find apache server, use RT_TEST_APACHE"); Test::More::BAIL_OUT( "Couldn't find apache modules directory (set APXS= or RT_TEST_APACHE_MODULES=)" ) unless -d $apache_module_prefix; RT::Test::diag("Using '$bin' apache executable for testing"); my $info = `$bin -v`; ($res{'version'}) = ($info =~ m{Server\s+version:\s+Apache/(\d+\.\d+)\.}); Test::More::BAIL_OUT( "Couldn't figure out version of the server" ) unless $res{'version'}; $res{'modules'} = [ map {s/^\s+//; s/\s+$//; $_} grep $_ !~ /Compiled in modules/i, split /\r*\n/, `$bin -l` ]; Test::More::BAIL_OUT( "Unsupported apache version $res{version}" ) unless exists $MODULES{$res{version}}; Test::More::BAIL_OUT( "Unsupported apache variant $res{variant}" ) unless exists $MODULES{$res{version}}{$res{variant}}; my @mlist = @{$MODULES{$res{version}}{$res{variant}}}; if ($res{basic_auth}) { push @mlist, "auth_basic", "authz_user"; push @mlist, $res{basic_auth} eq 'anon' ? "authn_anon" : "authn_file"; } $res{'load_modules'} = ''; foreach my $mod ( @mlist ) { next if grep $_ =~ /^(mod_|)$mod\.c$/, @{ $res{'modules'} }; my $so_file = $apache_module_prefix."/mod_".$mod.".so"; Test::More::BAIL_OUT( "Couldn't load $mod module (expected in $so_file)" ) unless -f $so_file; $res{'load_modules'} .= "LoadModule ${mod}_module $so_file\n"; } # Apache 2.4 wants to fully-parse a config file when running -V, # because the MPM is no longer compiled-in. Provide a trivial one. require File::Temp; my $tmp = File::Temp->new; my ($mpm) = grep {/^mpm_/} @{$MODULES{$res{version}}{$res{variant}}}; print $tmp "LoadModule ${mpm}_module $apache_module_prefix/mod_${mpm}.so\n" if $mpm; print $tmp "ErrorLog /dev/null\n"; print $tmp "TransferLog /dev/null\n"; close $tmp; $info = `$res{executable} -V -f $tmp`; my %opts = ($info =~ m/^\s*-D\s+([A-Z_]+?)(?:="(.*)")$/mg); %res = (%res, %opts); return %res; } sub find_apache_server { my $self = shift; return $_ foreach grep defined, map RT::Test->find_executable($_), qw(httpd apache apache2 apache1); return undef; } sub apache_mpm_type { my $self = shift; my $apache = $self->find_apache_server; my $out = `$apache -l`; if ( $out =~ /^\s*(worker|prefork|event|itk)\.c\s*$/m ) { return $1; } return "worker"; } sub fork_exec { my $self = shift; RT::Test::__disconnect_rt(); my $pid = fork; unless ( defined $pid ) { die "cannot fork: $!"; } elsif ( !$pid ) { exec @_; die "can't exec `". join(' ', @_) ."` program: $!"; } else { RT::Test::__reconnect_rt(); return $pid; } } sub process_in_file { my $self = shift; my %args = ( in => undef, options => undef, @_ ); my $text = RT::Test->file_content( $args{'in'} ); while ( my ($opt) = ($text =~ /\%\%(.+?)\%\%/) ) { my $value = $args{'options'}{ lc $opt }; die "no value for $opt" unless defined $value; $text =~ s/\%\%\Q$opt\E\%\%/$value/g; } my ($out_fh, $out_conf); unless ( $args{'out'} ) { ($out_fh, $out_conf) = tempfile(); } else { $out_conf = $args{'out'}; open( $out_fh, '>', $out_conf ) or die "couldn't open '$out_conf': $!"; } print $out_fh $text; seek $out_fh, 0, 0; return ($out_fh, $out_conf); } 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Test/GnuPG.pm�����������������������������������������������������������������������000644 �000765 �000024 �00000031457 14005011336 016343� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Test::GnuPG; use strict; use warnings; use Test::More; use base qw(RT::Test); use File::Temp qw(tempdir); use IPC::Run3 'run3'; use File::Copy; use 5.010; our @EXPORT = qw(create_a_ticket update_ticket cleanup_headers set_queue_crypt_options check_text_emails send_email_and_check_transaction create_and_test_outgoing_emails ); sub import { my $class = shift; my %args = @_; my $t = $class->builder; RT::Test::plan( skip_all => 'ENV SKIP_GPG_TESTS is set to true.' ) if $ENV{'SKIP_GPG_TESTS'}; RT::Test::plan( skip_all => 'GnuPG required.' ) unless GnuPG::Interface->require; RT::Test::plan( skip_all => 'gpg executable is required.' ) unless RT::Test->find_executable('gpg'); $class->SUPER::import(%args); return $class->export_to_level(1) if $^C; RT::Test::diag "GnuPG --homedir " . RT->Config->Get('GnuPGOptions')->{'homedir'}; $class->set_rights( Principal => 'Everyone', Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ReplyToTicket', 'ModifyTicket'], ); $class->export_to_level(1); } sub bootstrap_more_config { my $self = shift; my $handle = shift; my $args = shift; $self->SUPER::bootstrap_more_config($handle, $args, @_); my %gnupg_options = ( 'no-permission-warning' => undef, $args->{gnupg_options} ? %{ $args->{gnupg_options} } : (), ); $gnupg_options{homedir} ||= new_homedir(); my $conf = File::Spec->catfile( $gnupg_options{homedir}, 'gpg.conf' ); if ( gnupg_version() >= 2 ) { open my $fh, '>', $conf or die $!; print $fh "pinentry-mode loopback\n"; close $fh; } else { unlink $conf if -e $conf; } use Data::Dumper; local $Data::Dumper::Terse = 1; # "{...}" instead of "$VAR1 = {...};" my $dumped_gnupg_options = Dumper(\%gnupg_options); print $handle qq{ Set(\%GnuPG, ( Enable => 1, OutgoingMessagesFormat => 'RFC', )); Set(\%GnuPGOptions => \%{ $dumped_gnupg_options }); }; } sub create_a_ticket { my $queue = shift; my $mail = shift; my $m = shift; my %args = (@_); RT::Test->clean_caught_mails; $m->goto_create_ticket( $queue ); $m->form_name('TicketCreate'); $m->field( Subject => 'test' ); $m->field( Requestors => 'rt-test@example.com' ); $m->field( Content => 'Some content' ); foreach ( qw(Sign Encrypt) ) { if ( $args{ $_ } ) { $m->tick( $_ => 1 ); } else { $m->untick( $_ => 1 ); } } $m->click('SubmitTicket'); is $m->status, 200, "request successful"; $m->content_lacks("unable to sign outgoing email messages"); my @mail = RT::Test->fetch_caught_mails; check_text_emails(\%args, @mail ); categorize_emails($mail, \%args, @mail ); } sub update_ticket { my $tid = shift; my $mail = shift; my $m = shift; my %args = (@_); RT::Test->clean_caught_mails; $m->get( $m->rt_base_url . "/Ticket/Update.html?Action=Respond&id=$tid" ); $m->form_number(3); $m->field( UpdateContent => 'Some content' ); foreach ( qw(Sign Encrypt) ) { if ( $args{ $_ } ) { $m->tick( $_ => 1 ); } else { $m->untick( $_ => 1 ); } } $m->click('SubmitTicket'); is $m->status, 200, "request successful"; $m->content_contains("Correspondence added", 'Correspondence added') or diag $m->content; my @mail = RT::Test->fetch_caught_mails; check_text_emails(\%args, @mail ); categorize_emails($mail, \%args, @mail ); } sub categorize_emails { my $mail = shift; my $args = shift; my @mail = @_; if ( $args->{'Sign'} && $args->{'Encrypt'} ) { push @{ $mail->{'signed_encrypted'} }, @mail; } elsif ( $args->{'Sign'} ) { push @{ $mail->{'signed'} }, @mail; } elsif ( $args->{'Encrypt'} ) { push @{ $mail->{'encrypted'} }, @mail; } else { push @{ $mail->{'plain'} }, @mail; } } sub check_text_emails { my %args = %{ shift @_ }; my @mail = @_; ok scalar @mail, "got some mail"; for my $mail (@mail) { for my $type ('email', 'attachment') { next if $type eq 'attachment' && !$args{'Attachment'}; my $content = $type eq 'email' ? "Some content" : $args{Attachment}; if ( $args{'Encrypt'} ) { unlike $mail, qr/$content/, "outgoing $type is not in plaintext"; my $entity = RT::Test::parse_mail($mail); my @res = RT::Crypt->VerifyDecrypt(Entity => $entity); like $res[0]{'status'}, qr/DECRYPTION_OKAY/, "Decrypts OK"; like $entity->as_string, qr/$content/, "outgoing decrypts to contain $type content"; } else { like $mail, qr/$content/, "outgoing $type was not encrypted"; } next unless $type eq 'email'; if ( $args{'Sign'} && $args{'Encrypt'} ) { like $mail, qr/BEGIN PGP MESSAGE/, 'outgoing email was signed'; } elsif ( $args{'Sign'} ) { like $mail, qr/SIGNATURE/, 'outgoing email was signed'; } else { unlike $mail, qr/SIGNATURE/, 'outgoing email was not signed'; } } } } sub cleanup_headers { my $mail = shift; # strip id from subject to create new ticket $mail =~ s/^(Subject:)\s*\[.*?\s+#\d+\]\s*/$1 /m; # strip several headers foreach my $field ( qw(Message-ID RT-Originator RT-Ticket X-RT-Loop-Prevention) ) { $mail =~ s/^$field:.*?\n(?! |\t)//gmsi; } return $mail; } sub set_queue_crypt_options { my $queue = shift; my %args = @_; $queue->SetEncrypt($args{'Encrypt'}); $queue->SetSign($args{'Sign'}); } sub send_email_and_check_transaction { my $mail = shift; my $type = shift; my ( $status, $id ) = RT::Test->send_via_mailgate($mail); is( $status >> 8, 0, "The mail gateway exited normally" ); ok( $id, "got id of a newly created ticket - $id" ); my $tick = RT::Ticket->new( RT->SystemUser ); $tick->Load($id); ok( $tick->id, "loaded ticket #$id" ); my $txn = $tick->Transactions->First; my ( $msg, @attachments ) = @{ $txn->Attachments->ItemsArrayRef }; if ( $attachments[0] ) { like $attachments[0]->Content, qr/Some content/, "RT's mail includes copy of ticket text"; } else { like $msg->Content, qr/Some content/, "RT's mail includes copy of ticket text"; } if ( $type eq 'plain' ) { ok !$msg->GetHeader('X-RT-Privacy'), "RT's outgoing mail has no crypto"; is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted', "RT's outgoing mail looks not encrypted"; ok !$msg->GetHeader('X-RT-Incoming-Signature'), "RT's outgoing mail looks not signed"; } elsif ( $type eq 'signed' ) { is $msg->GetHeader('X-RT-Privacy'), 'GnuPG', "RT's outgoing mail has crypto"; is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted', "RT's outgoing mail looks not encrypted"; like $msg->GetHeader('X-RT-Incoming-Signature'), qr/<rt-recipient\@example.com>/, "RT's outgoing mail looks signed"; } elsif ( $type eq 'encrypted' ) { is $msg->GetHeader('X-RT-Privacy'), 'GnuPG', "RT's outgoing mail has crypto"; is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success', "RT's outgoing mail looks encrypted"; ok !$msg->GetHeader('X-RT-Incoming-Signature'), "RT's outgoing mail looks not signed"; } elsif ( $type eq 'signed_encrypted' ) { is $msg->GetHeader('X-RT-Privacy'), 'GnuPG', "RT's outgoing mail has crypto"; is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success', "RT's outgoing mail looks encrypted"; like $msg->GetHeader('X-RT-Incoming-Signature'), qr/<rt-recipient\@example.com>/, "RT's outgoing mail looks signed"; } else { die "unknown type: $type"; } } sub create_and_test_outgoing_emails { my $queue = shift; my $m = shift; my @variants = ( {}, { Sign => 1 }, { Encrypt => 1 }, { Sign => 1, Encrypt => 1 }, ); # collect emails my %mail; # create a ticket for each combination foreach my $ticket_set (@variants) { create_a_ticket( $queue, \%mail, $m, %$ticket_set ); } my $tid; { my $ticket = RT::Ticket->new( RT->SystemUser ); ($tid) = $ticket->Create( Subject => 'test', Queue => $queue->id, Requestor => 'rt-test@example.com', ); ok $tid, 'ticket created'; } # again for each combination add a reply message foreach my $ticket_set (@variants) { update_ticket( $tid, \%mail, $m, %$ticket_set ); } # ------------------------------------------------------------------------------ # now delete all keys from the keyring and put back secret/pub pair for rt-test@ # and only public key for rt-recipient@ so we can verify signatures and decrypt # like we are on another side recieve emails # ------------------------------------------------------------------------------ unlink $_ foreach glob( RT->Config->Get('GnuPGOptions')->{'homedir'} . "/*" ); RT::Test->import_gnupg_key( 'rt-recipient@example.com', 'public' ); RT::Test->import_gnupg_key('rt-test@example.com'); $queue = RT::Test->load_or_create_queue( Name => 'Regression', CorrespondAddress => 'rt-test@example.com', CommentAddress => 'rt-test@example.com', ); ok $queue && $queue->id, 'changed props of the queue'; for my $type ( keys %mail ) { for my $mail ( map cleanup_headers($_), @{ $mail{$type} } ) { send_email_and_check_transaction( $mail, $type ); } } } sub gnupg_version { GnuPG::Interface->require or return; require version; state $gnupg_version = version->parse(GnuPG::Interface->new->version); } sub new_homedir { my $source = shift; return if $ENV{'SKIP_GPG_TESTS'} || !RT::Test->find_executable('gpg') || !GnuPG::Interface->require; my $dir = tempdir(); if ($source) { opendir my $dh, $source or die $!; for my $file ( grep {/\.gpg$/} readdir $dh ) { copy( File::Spec->catfile( $source, $file ), File::Spec->catfile( $dir, $file ) ) or die $!; } closedir $dh; if ( gnupg_version() >= 2 ) { # Do the data migration run3( [ 'gpg', '--homedir', $dir, '--list-secret-keys' ], \undef, \undef, \undef ); } } return $dir; } END { if ( RT::Test->builder()->has_plan && gnupg_version() >= 2 ) { system( 'gpgconf', '--homedir', RT->Config->Get('GnuPGOptions')->{homedir}, '--quiet', '--kill', 'gpg-agent' ) && warn $!; } } 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Test/ExternalStorage.pm�������������������������������������������������������������000644 �000765 �000024 �00000004752 14005011336 020470� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Test::ExternalStorage; use strict; use warnings; use base qw(RT::Test); use File::Spec; use File::Path 'mkpath'; sub attachments_dir { my $dir = File::Spec->catdir( RT::Test->temp_directory, qw(attachments) ); mkpath($dir); return $dir; } sub bootstrap_more_config { my $self = shift; my $handle = shift; my $args = shift; $self->SUPER::bootstrap_more_config($handle, $args, @_); my $dir = $self->attachments_dir; print $handle qq|Set( %ExternalStorage, Type => 'Disk', Path => '$dir' );\n|; } 1; ����������������������rt-5.0.1/lib/RT/Test/SMIME.pm�����������������������������������������������������������������������000644 �000765 �000024 �00000011207 14005011336 016224� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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; use 5.010; package RT::Test::SMIME; use Test::More; use base qw(RT::Test); use File::Temp qw(tempdir); sub import { my $class = shift; my %args = @_; my $t = $class->builder; RT::Test::plan( skip_all => 'openssl executable is required.' ) unless RT::Test->find_executable('openssl'); require RT::Crypt; $class->SUPER::import(%args); $class->set_rights( Principal => 'Everyone', Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ReplyToTicket', 'ModifyTicket'], ); $class->export_to_level(1); } sub bootstrap_more_config { my $self = shift; my $handle = shift; my $args = shift; $self->SUPER::bootstrap_more_config($handle, $args, @_); my $openssl = $self->find_executable('openssl'); my $keyring = $self->keyring_path; mkdir($keyring); my $ca = $self->key_path("demoCA", "cacert.pem"); print $handle qq{ Set(\%GnuPG, Enable => 0); Set(\%SMIME => Enable => 1, Passphrase => { 'root\@example.com' => '123456', 'sender\@example.com' => '123456', }, OpenSSL => q{$openssl}, Keyring => q{$keyring}, CAPath => q{$ca}, ); }; } sub keyring_path { return File::Spec->catfile( RT::Test->temp_directory, "smime" ); } sub key_path { my $self = shift; my $keys = RT::Test::get_abs_relocatable_dir( (File::Spec->updir()) x 2, qw(data smime keys), ); return File::Spec->catfile( $keys => @_ ), } sub mail_set_path { my $self = shift; return RT::Test::get_abs_relocatable_dir( (File::Spec->updir()) x 2, qw(data smime mails), ); } sub import_key { my $self = shift; my $key = shift; my $user = shift; my $path = RT::Test::find_relocatable_path( 'data', 'smime', 'keys' ); die "can't find the dir where smime keys are stored" unless $path; my $keyring = RT->Config->Get('SMIME')->{'Keyring'}; die "SMIME keyring '$keyring' doesn't exist" unless $keyring && -e $keyring; $key .= ".pem" unless $key =~ /\.(pem|crt|key)$/; my $content = RT::Test->file_content( [ $path, $key ] ); if ( $user ) { my ($status, $msg) = $user->SetSMIMECertificate( $content ); die "Couldn't set CF: $msg" unless $status; } else { my $keyring = RT->Config->Get('SMIME')->{'Keyring'}; die "SMIME keyring '$keyring' doesn't exist" unless $keyring && -e $keyring; open my $fh, '>:raw', File::Spec->catfile($keyring, $key) or die "can't open file: $!"; print $fh $content; close $fh; } return; } 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Test/Assets.pm����������������������������������������������������������������������000644 �000765 �000024 �00000013500 14005011336 016612� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Test::Assets; use base 'RT::Test'; our @EXPORT = qw(create_catalog create_asset create_assets create_cf apply_cfs assetsql); sub import { my $class = shift; my %args = @_; $class->SUPER::import( %args ); __PACKAGE__->export_to_level(1); } sub diag { Test::More::diag(@_) if $ENV{TEST_VERBOSE}; } sub create_catalog { my %info = @_; my $catalog = RT::Catalog->new( RT->SystemUser ); my ($id, $msg) = $catalog->Create( %info ); if ($id) { diag("Created catalog #$id: " . $catalog->Name); return $catalog; } else { my $spec = join "/", map { "$_=$info{$_}" } keys %info; RT->Logger->error("Failed to create catalog ($spec): $msg"); return; } } sub load_or_create_catalog { my $self = shift; my %args = ( Disabled => 0, @_ ); my $obj = RT::Catalog->new( RT->SystemUser ); if ( $args{'Name'} ) { $obj->LoadByCols( Name => $args{'Name'} ); } else { die "Name is required"; } unless ( $obj->id ) { my ($val, $msg) = $obj->Create( %args ); die "$msg" unless $val; } return $obj; } sub create_asset { my %info = @_; my $asset = RT::Asset->new( RT->SystemUser ); my ($id, $msg) = $asset->Create( %info ); if ($id) { diag("Created asset #$id: " . $asset->Name); return $asset; } else { my $spec = join "/", map { "$_=$info{$_}" } keys %info; RT->Logger->error("Failed to create asset ($spec): $msg"); return; } } sub create_assets { my $error = 0; for my $info (@_) { create_asset(%$info) or $error++; } return not $error; } sub create_cf { my %args = ( Name => "Test Asset CF ".($$ + rand(1024)), Type => "FreeformSingle", LookupType => RT::Asset->CustomFieldLookupType, @_, ); my $cf = RT::CustomField->new( RT->SystemUser ); my ($ok, $msg) = $cf->Create(%args); RT->Logger->error("Can't create CF: $msg") unless $ok; return $cf; } sub apply_cfs { my $success = 1; for my $cf (@_) { my ($ok, $msg) = $cf->AddToObject( RT::Catalog->new(RT->SystemUser) ); if (not $ok) { RT->Logger->error("Couldn't apply CF: $msg"); $success = 0; } } return $success; } sub last_asset { my $self = shift; my $current = shift; $current = $current ? RT::CurrentUser->new($current) : RT->SystemUser; my $assets = RT::Assets->new( $current ); $assets->OrderBy( FIELD => 'id', ORDER => 'DESC' ); $assets->Limit( FIELD => 'id', OPERATOR => '>', VALUE => '0' ); $assets->RowsPerPage( 1 ); return $assets->First; } sub assetsql { local $Test::Builder::Level = $Test::Builder::Level + 1; my $options = shift; my @expected = @_; my $currentuser = RT->SystemUser; my $sql; if (ref($options)) { $sql = delete $options->{sql}; $currentuser = delete $options->{CurrentUser} if $options->{CurrentUser}; die "Unexpected options: " . join ', ', keys %$options if keys %$options; } else { $sql = $options; } my $count = scalar @expected; my $assets = RT::Assets->new($currentuser); $assets->FromSQL($sql); $assets->OrderBy( FIELD => 'Name', ORDER => 'ASC' ); Test::More::is($assets->Count, $count, "number of assets from [$sql]"); my $i = 0; while (my $asset = $assets->Next) { my $expected = shift @expected; if (!$expected) { Test::More::fail("got more assets (" . $asset->Name . ") than expected from [$sql]"); next; } ++$i; Test::More::is($asset->Name, $expected->Name, "asset ($i/$count) from [$sql]"); } while (my $expected = shift @expected) { Test::More::fail("got fewer assets than expected (" . $expected->Name . ") from [$sql]"); } } 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Test/Email.pm�����������������������������������������������������������������������000644 �000765 �000024 �00000007633 14005011336 016411� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 warnings; use strict; package RT::Test::Email; use Test::More; use Test::Email; use Email::Abstract; use base 'Exporter'; our @EXPORT = qw(mail_ok); =head1 NAME RT::Test::Email - =head1 SYNOPSIS use RT::Test::Email; mail_ok { # ... code } { from => 'admin@localhost', body => qr('hello') }, { from => 'admin@localhost', body => qr('hello again') }; # ... more code # XXX: not yet mail_sent_ok { from => 'admin@localhost', body => qr('hello') }; # you should expect all mails by the end of the test =head1 DESCRIPTION This is a test helper module for RT, allowing you to expect mail notification generated during the block or the test. =cut sub mail_ok (&@) { my $code = shift; $code->(); local $Test::Builder::Level = $Test::Builder::Level + 1; my @msgs = RT::Test->fetch_caught_mails; is(@msgs, @_, "Sent exactly " . @_ . " emails"); for my $spec (@_) { my $msg = shift @msgs or ok(0, 'Expecting message but none found.'), next; $msg =~ s/^\s*//gs; # XXX: for some reasons, message from template has leading newline # XXX: use Test::Email directly? my $te = Email::Abstract->new($msg)->cast('MIME::Entity'); bless $te, 'Test::Email'; $te->ok($spec, "email matched"); my $Test = Test::More->builder; if (!($Test->summary)[$Test->current_test-1]) { diag $te->as_string; } } RT::Test->clean_caught_mails; } END { my $Test = Test::More->builder; # Such a hack -- try to detect if this is a forked copy and don't # do cleanup in that case. return if $Test->{Original_Pid} != $$; my @mail = RT::Test->fetch_caught_mails; if (scalar @mail) { diag ((scalar @mail)." uncaught notification email at end of test: "); diag "From: @{[ $_->header('From' ) ]}, Subject: @{[ $_->header('Subject') ]}" for map { Email::Abstract->new($_)->cast('Email::Simple') } @mail; die; } } 1; �����������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Test/REST2.pm�����������������������������������������������������������������������000644 �000765 �000024 �00000011214 14005011336 016207� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Test::REST2; use strict; use warnings; use base 'RT::Test'; use Test::WWW::Mechanize::PSGI; sub import { my $class = shift; my %args = @_; $class->SUPER::import( %args ); __PACKAGE__->export_to_level(1); } =pod sub import { my $class = shift; my %args = @_; $args{'requires'} ||= []; if ( $args{'testing'} ) { unshift @{ $args{'requires'} }, 'RT::REST2'; } else { $args{'testing'} = 'RT::REST2'; } $class->SUPER::import( %args ); $class->export_to_level(1); require RT::REST2; } =cut sub mech { RT::Test::REST2::Mechanize->new } { my $u; sub authorization_header { $u = _create_user() unless ($u && $u->id); return 'Basic dGVzdDpwYXNzd29yZA=='; } sub user { $u = _create_user() unless ($u && $u->id); return $u; } sub _create_user { my $u = RT::User->new( RT->SystemUser ); $u->Create( Name => 'test', Password => 'password', Privileged => 1, ); return $u; } } { package RT::Test::REST2::Mechanize; use parent 'Test::WWW::Mechanize::PSGI'; use JSON; my $json = JSON->new->utf8; sub new { my $class = shift; my %args = ( app => RT::REST2->PSGIWrap(sub { die "Requested non-REST path" }), @_, ); return $class->SUPER::new(%args); } sub hypermedia_ref { my ($self, $ref) = @_; my $json = $self->json_response; my @matches = grep { $_->{ref} eq $ref } @{ $json->{_hyperlinks} }; Test::More::is(@matches, 1, "got one match for hypermedia with ref '$ref'") or return; return $matches[0]; } sub url_for_hypermedia { my ($self, $ref) = @_; return $self->hypermedia_ref($ref)->{_url}; } sub post_json { my ($self, $url, $payload, %headers) = @_; $self->post( $url, Content => $json->encode($payload), 'Content-Type' => 'application/json; charset=utf-8', %headers, ); } sub put_json { my ($self, $url, $payload, %headers) = @_; $self->put( $url, $payload ? ( Content => $json->encode($payload) ) : (), 'Content-Type' => 'application/json; charset=utf-8', %headers, ); } sub json_response { local $Test::Builder::Level = $Test::Builder::Level + 1; my $self = shift; my $res = $self->response; local $main::TODO; Test::More::like($res->header('content-type'), qr{^application/json(?:; charset="?utf-8"?)?$}); return $json->decode($res->content); } } 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Record/Role/������������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 016213� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Record/Role.pm����������������������������������������������������������������������000644 �000765 �000024 �00000004744 14005011336 016562� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Record::Role; use Role::Basic; =head1 NAME RT::Record::Role - Common requirements for roles which are consumed by records =head1 DESCRIPTION Various L<RT::Record> (and by inheritance L<DBIx::SearchBuilder::Record>) methods are required by this role. It provides no methods on its own but is simply a contract for other roles to require (usually under the I<RT::Record::Role::> namespace). =cut requires $_ for qw( id loc CurrentUser _Set _Accessible _NewTransaction ); 1; ����������������������������rt-5.0.1/lib/RT/Record/AddAndSort.pm����������������������������������������������������������������000644 �000765 �000024 �00000040275 14005011336 017643� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Record::AddAndSort; use base 'RT::Record'; =head1 NAME RT::Record::AddAndSort - base class for records that can be added and sorted =head1 DESCRIPTION Base class for L<RT::ObjectCustomField> and L<RT::ObjectScrip> that unifies application of L<RT::CustomField>s and L<RT::Scrip>s to various objects. Also, deals with order of the records. =head1 METHODS =head2 Meta information =head3 CollectionClass Returns class representing collection for this record class. Basicly adds 's' at the end. Should be overriden if default doesn't work. For example returns L<RT::ObjectCustomFields> when called on L<RT::ObjectCustomField>. =cut sub CollectionClass { return (ref($_[0]) || $_[0]).'s'; } =head3 TargetField Returns name of the field in the table where id of object we add is stored. By default deletes everything up to '::Object' from class name. This method allows to use friendlier argument names and methods. For example returns 'Scrip' for L<RT::ObjectScrip>. =cut sub TargetField { my $class = ref($_[0]) || $_[0]; $class =~ s/.*::Object// or return undef; return $class; } =head3 ObjectCollectionClass Takes an object under L</TargetField> name and should return class name representing collection the object can be added to. Must be overriden by sub classes. See L<RT::ObjectScrip/ObjectCollectionClass> and L<RT::ObjectCustomField/CollectionClass>. =cut sub ObjectCollectionClass { die "should be subclassed" } =head2 Manipulation =head3 Create Takes 'ObjectId' with id of an object we can be added to, object we can add to under L</TargetField> name, Disabled and SortOrder. This method doesn't create duplicates. If record already exists then it's not created, but loaded instead. Note that nothing is updated if record exist. If SortOrder is not defined then it's calculated to place new record last. If it's provided then it's caller's duty to make sure it is correct value. Example: my $ocf = RT::ObjectCustomField->new( RT->SystemUser ); my ($id, $msg) = $ocf->Create( CustomField => 1, ObjectId => 0 ); See L</Add> which has more error checks. Also, L<RT::Scrip> and L<RT::CustomField> have more appropriate methods that B<should be> prefered over calling this directly. =cut sub Create { my $self = shift; my %args = ( ObjectId => 0, SortOrder => undef, @_ ); my $tfield = $self->TargetField; my $target = $self->TargetObj( $args{ $tfield } ); unless ( $target->id ) { $RT::Logger->error("Couldn't load ". ref($target) ." '$args{$tfield}'"); return 0; } my $exist = $self->new($self->CurrentUser); $exist->LoadByCols( ObjectId => $args{'ObjectId'}, $tfield => $target->id ); if ( $exist->id ) { $self->Load( $exist->id ); return $self->id; } unless ( defined $args{'SortOrder'} ) { $args{'SortOrder'} = $self->NextSortOrder( %args, $tfield => $target, ); } return $self->SUPER::Create( %args, $tfield => $target->id, ); } =head3 Add Helper method that wraps L</Create> and does more checks to make sure result is consistent. Doesn't allow adding a record to an object if the record is already global. Removes record from particular objects when asked to add the record globally. =cut sub Add { my $self = shift; my %args = (@_); my $field = $self->TargetField; my $tid = $args{ $field }; $tid = $tid->id if ref $tid; $tid ||= $self->TargetObj->id; my $oid = $args{'ObjectId'}; $oid = $oid->id if ref $oid; $oid ||= 0; if ( $self->IsAdded( $tid => $oid ) ) { return ( 0, $self->loc("Is already added to the object") ); } if ( $oid ) { # adding locally return (0, $self->loc("Couldn't add as it's global already") ) if $self->IsAdded( $tid => 0 ); } else { $self->DeleteAll( $field => $tid ); } return $self->Create( %args, $field => $tid, ObjectId => $oid, ); } sub IsAdded { my $self = shift; my ($tid, $oid) = @_; my $record = $self->new( $self->CurrentUser ); $record->LoadByCols( $self->TargetField => $tid, ObjectId => $oid ); return $record->id; } =head3 AddedTo Returns collection with objects target of this record is added to. Class of the collection depends on L</ObjectCollectionClass>. See all L</NotAddedTo>. For example returns L<RT::Queues> collection if the target is L<RT::Scrip>. Returns empty collection if target is added globally. =cut sub AddedTo { my $self = shift; my ($res, $alias) = $self->_AddedTo( @_ ); return $res unless $res; $res->Limit( ALIAS => $alias, FIELD => 'id', OPERATOR => 'IS NOT', VALUE => 'NULL', ); return $res; } =head3 NotAddedTo Returns collection with objects target of this record is not added to. Class of the collection depends on L</ObjectCollectionClass>. See all L</AddedTo>. Returns empty collection if target is added globally. =cut sub NotAddedTo { my $self = shift; my ($res, $alias) = $self->_AddedTo( @_ ); return $res unless $res; $res->Limit( ALIAS => $alias, FIELD => 'id', OPERATOR => 'IS', VALUE => 'NULL', ); return $res; } sub _AddedTo { my $self = shift; my %args = (@_); my $field = $self->TargetField; my $target = $args{ $field } || $self->TargetObj; my ($class) = $self->ObjectCollectionClass( $field => $target ); return undef unless $class; my $res = $class->new( $self->CurrentUser ); # If target added to a Group, only display user-defined groups $res->LimitToUserDefinedGroups if $class eq 'RT::Groups'; $res->OrderBy( FIELD => 'Name' ); my $alias = $res->Join( TYPE => 'LEFT', ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => $self->Table, FIELD2 => 'ObjectId', ); $res->Limit( LEFTJOIN => $alias, ALIAS => $alias, FIELD => $field, VALUE => $target->id, ); return ($res, $alias); } =head3 Delete Deletes this record. =cut sub Delete { my $self = shift; return $self->SUPER::Delete if $self->IsSortOrderShared; # Move everything below us up my $siblings = $self->Neighbors; $siblings->Limit( FIELD => 'SortOrder', OPERATOR => '>=', VALUE => $self->SortOrder ); $siblings->OrderBy( FIELD => 'SortOrder', ORDER => 'ASC' ); foreach my $record ( @{ $siblings->ItemsArrayRef } ) { $record->SetSortOrder($record->SortOrder - 1); } return $self->SUPER::Delete; } =head3 DeleteAll Helper method to delete all applications for one target (Scrip, CustomField, ...). Target can be provided in arguments. If it's not then L</TargetObj> is used. $object_scrip->DeleteAll; $object_scrip->DeleteAll( Scrip => $scrip ); =cut sub DeleteAll { my $self = shift; my %args = (@_); my $field = $self->TargetField; my $id = $args{ $field }; $id = $id->id if ref $id; $id ||= $self->TargetObj->id; my $list = $self->CollectionClass->new( $self->CurrentUser ); $list->Limit( FIELD => $field, VALUE => $id ); $_->Delete foreach @{ $list->ItemsArrayRef }; } =head3 MoveUp Moves record up. =cut sub MoveUp { return shift->Move( Up => @_ ) } =head3 MoveDown Moves record down. =cut sub MoveDown { return shift->Move( Down => @_ ) } =head3 Move Takes 'up' or 'down'. One method that implements L</MoveUp> and L</MoveDown>. =cut sub Move { my $self = shift; my $dir = lc(shift || 'up'); my %meta; if ( $dir eq 'down' ) { %meta = qw( next_op > next_order ASC prev_op <= diff +1 ); } else { %meta = qw( next_op < next_order DESC prev_op >= diff -1 ); } my $siblings = $self->Siblings; $siblings->Limit( FIELD => 'SortOrder', OPERATOR => $meta{'next_op'}, VALUE => $self->SortOrder ); $siblings->OrderBy( FIELD => 'SortOrder', ORDER => $meta{'next_order'} ); my @next = ($siblings->Next, $siblings->Next); unless ($next[0]) { return $dir eq 'down' ? (0, "Can not move down. It's already at the bottom") : (0, "Can not move up. It's already at the top") ; } my ($new_sort_order, $move); unless ( $self->ObjectId ) { # moving global, it can not share sort order, so just move it # on place of next global and move everything in between one number $new_sort_order = $next[0]->SortOrder; $move = $self->Neighbors; $move->Limit( FIELD => 'SortOrder', OPERATOR => $meta{'next_op'}, VALUE => $self->SortOrder, ); $move->Limit( FIELD => 'SortOrder', OPERATOR => $meta{'prev_op'}, VALUE => $next[0]->SortOrder, ENTRYAGGREGATOR => 'AND', ); } elsif ( $next[0]->ObjectId == $self->ObjectId ) { # moving two locals, just swap them, they should follow 'so = so+/-1' rule $new_sort_order = $next[0]->SortOrder; $move = $next[0]; } else { # moving local behind global unless ( $self->IsSortOrderShared ) { # not shared SO allows us to swap $new_sort_order = $next[0]->SortOrder; $move = $next[0]; } elsif ( $next[1] ) { # more records there and shared SO, we have to move everything $new_sort_order = $next[0]->SortOrder; $move = $self->Neighbors; $move->Limit( FIELD => 'SortOrder', OPERATOR => $meta{prev_op}, VALUE => $next[0]->SortOrder, ); } else { # shared SO and place after is free, so just jump $new_sort_order = $next[0]->SortOrder + $meta{'diff'}; } } if ( $move ) { foreach my $record ( $move->isa('RT::Record')? ($move) : @{ $move->ItemsArrayRef } ) { my ($status, $msg) = $record->SetSortOrder( $record->SortOrder - $meta{'diff'} ); return (0, "Couldn't move: $msg") unless $status; } } my ($status, $msg) = $self->SetSortOrder( $new_sort_order ); unless ( $status ) { return (0, "Couldn't move: $msg"); } return (1,"Moved"); } =head2 Accessors, instrospection and traversing. =head3 TargetObj Returns target object of this record. Returns L<RT::Scrip> object for L<RT::ObjectScrip>. =cut sub TargetObj { my $self = shift; my $id = shift; my $method = $self->TargetField .'Obj'; return $self->$method( $id ); } =head3 NextSortOrder Returns next available SortOrder value in the L<neighborhood|/Neighbors>. Pass arguments to L</Neighbors> and can take optional ObjectId argument, calls ObjectId if it's not provided. =cut sub NextSortOrder { my $self = shift; my %args = (@_); my $oid = $args{'ObjectId'}; $oid = $self->ObjectId unless defined $oid; $oid ||= 0; my $neighbors = $self->Neighbors( %args ); if ( $oid ) { $neighbors->LimitToObjectId( $oid ); $neighbors->LimitToObjectId( 0 ); } elsif ( !$neighbors->_isLimited ) { $neighbors->UnLimit; } $neighbors->OrderBy( FIELD => 'SortOrder', ORDER => 'DESC' ); return 0 unless my $first = $neighbors->First; return $first->SortOrder + 1; } =head3 IsSortOrderShared Returns true if this record shares SortOrder value with a L<neighbor|/Neighbors>. =cut sub IsSortOrderShared { my $self = shift; return 0 unless $self->ObjectId; my $neighbors = $self->Neighbors; $neighbors->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => $self->id ); $neighbors->Limit( FIELD => 'SortOrder', VALUE => $self->SortOrder ); return $neighbors->Count; } =head2 Neighbors and Siblings These two methods should only be understood by developers who wants to implement new classes of records that can be added to other records and sorted. Main purpose is to maintain SortOrder values. Let's take a look at custom fields. A custom field can be created for tickets, queues, transactions, users... Custom fields created for tickets can be added globally or to particular set of queues. Custom fields for tickets are neighbors. Neighbor custom fields added to the same objects are siblings. Custom fields added globally are sibling to all neighbors. For scrips Stage defines neighborhood. Let's look at the three scrips in create stage S1, S2 and S3, queues Q1 and Q2 and G for global. S1@Q1, S3@Q2 0 S2@G 1 S1@Q2 2 Above table says that S2 is added globally, S1 is added to Q1 and executed before S2 in this queue, also S1 is added to Q1, but exectued after S2 in this queue, S3 is only added to Q2 and executed before S2 and S1. Siblings are scrips added to an object including globally added or only globally added. In our example there are three different collection of siblings: (S2) - global, (S1, S2) for Q1, (S3, S2, S1) for Q2. Sort order can be shared between neighbors, but can not be shared between siblings. Here is what happens with sort order if we move S1@Q2 one position up: S3@Q2 0 S1@Q1, S1@Q2 1 S2@G 2 One position more: S1@Q2 0 S1@Q1, S3@Q2 1 S2@G 2 Hopefuly it's enough to understand how it works. Targets from different neighborhood can not be sorted against each other. =head3 Neighbors Returns collection of records of this class with all neighbors. By default all possible targets are neighbors. Takes the same arguments as L</Create> method. If arguments are not passed then uses the current record. See L</Neighbors and Siblings> for detailed description. See L<RT::ObjectCustomField/Neighbors> for example. =cut sub Neighbors { my $self = shift; return $self->CollectionClass->new( $self->CurrentUser ); } =head3 Siblings Returns collection of records of this class with siblings. Takes the same arguments as L</Neighbors>. Siblings is subset of L</Neighbors>. =cut sub Siblings { my $self = shift; my %args = @_; my $oid = $args{'ObjectId'}; $oid = $self->ObjectId unless defined $oid; $oid ||= 0; my $res = $self->Neighbors( %args ); $res->LimitToObjectId( $oid ); $res->LimitToObjectId( 0 ) if $oid; return $res; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Record/Role/Roles.pm����������������������������������������������������������������000644 �000765 �000024 �00000053757 14005011336 017656� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Record::Role::Roles; use Role::Basic; use Scalar::Util qw(blessed); =head1 NAME RT::Record::Role::Roles - Common methods for records which "watchers" or "roles" =head1 REQUIRES =head2 L<RT::Record::Role> =cut with 'RT::Record::Role'; require RT::System; require RT::Principal; require RT::Group; require RT::User; require RT::EmailParser; =head1 PROVIDES =head2 RegisterRole Registers an RT role which applies to this class for role-based access control. Arguments: =over 4 =item Name Required. The role name (i.e. Requestor, Owner, AdminCc, etc). =item EquivClasses Optional. Array ref of classes through which this role percolates up to L<RT::System>. You can think of this list as: map { ref } $record_object->ACLEquivalenceObjects; You should not include L<RT::System> itself in this list. Simply calls RegisterRole on each equivalent class. =item Single Optional. A true value indicates that this role may only contain a single user as a member at any given time. When adding a new member to a Single role, any existing member will be removed. If all members are removed, L<RT/Nobody> is added automatically. =item Column Optional, implies Single. Specifies a column on the announcing class into which the single role member's user ID is denormalized. The column will be kept updated automatically as the role member changes. This is used, for example, for ticket owners and makes searching simpler (among other benefits). =item ACLOnly Optional. A true value indicates this role is only used for ACLs and should not be populated with members. This flag is advisory only, and the Perl API still allows members to be added to ACLOnly roles. =item ACLOnlyInEquiv Optional. Automatically sets the ACLOnly flag for all EquivClasses, but not the announcing class. =item SortOrder Optional. A numeric value indicating the position of this role when sorted ascending with other roles in a list. Roles with the same sort order are ordered alphabetically by name within themselves. =item UserDefined Optional. A true value indicates that this role was created by the user and as such is not managed by the core codebase or an extension. =item CreateGroupPredicate Optional. A subroutine whose return value indicates whether the group for this role should be created as part of L</_CreateRoleGroups>. When this subroutine is not provided, the group will be created. The same parameters that will be passed to L<RT::Group/CreateRoleGroup> are passed to your predicate (including C<Object>) =item AppliesToObjectPredicate Optional. A subroutine which decides whether a specific object in the class has the role or not. =item LabelGenerator Optional. A subroutine which returns the name of the role as suitable for displaying to the end user. Will receive as an argument a specific object. =back =cut sub RegisterRole { my $self = shift; my $class = ref($self) || $self; my %role = ( Name => undef, EquivClasses => [], SortOrder => 0, UserDefined => 0, CreateGroupPredicate => undef, AppliesToObjectPredicate => undef, LabelGenerator => undef, @_ ); return unless $role{Name}; # Keep track of the class this role came from originally $role{ Class } ||= $class; # Some groups are limited to a single user $role{ Single } = 1 if $role{Column}; # Stash the role on ourself $class->_ROLES->{ $role{Name} } = { %role }; # Register it with any equivalent classes... my $equiv = delete $role{EquivClasses} || []; # ... and globally unless we ARE global unless ($class eq "RT::System") { push @$equiv, "RT::System"; } # ... marked as "for ACLs only" if flagged as such by the announcing class $role{ACLOnly} = 1 if delete $role{ACLOnlyInEquiv}; $_->RegisterRole(%role) for @$equiv; # XXX TODO: Register which classes have roles on them somewhere? return 1; } =head2 UnregisterRole Removes an RT role which applies to this class for role-based access control. Any roles on equivalent classes (via EquivClasses passed to L</RegisterRole>) are also unregistered. Takes a role name as the sole argument. B<Use this carefully:> Objects created after a role is unregistered will not have an associated L<RT::Group> for the removed role. If you later decide to stop unregistering the role, operations on those objects created in the meantime will fail when trying to interact with the missing role groups. B<Unregistering a role may break code which assumes the role exists.> =cut sub UnregisterRole { my $self = shift; my $class = ref($self) || $self; my $name = shift or return; my $role = delete $self->_ROLES->{$name} or return; $_->UnregisterRole($name) for "RT::System", reverse @{$role->{EquivClasses}}; } =head2 Role Takes a role name; returns a hashref describing the role. This hashref contains the same attributes used to register the role (see L</RegisterRole>), as well as some extras, including: =over =item Class The original class which announced the role. This is set automatically by L</RegisterRole> and is the same across all EquivClasses. =back Returns an empty hashref if the role doesn't exist. =cut sub Role { my $self = shift; my $type = shift; return {} unless $self->HasRole( $type ); return \%{ $self->_ROLES->{$type} }; } =head2 Roles Returns a list of role names registered for this object, sorted ascending by SortOrder and then alphabetically by name. Optionally takes a hash specifying attributes the returned roles must possess or lack. Testing is done on a simple truthy basis and the actual values of the role attributes and arguments you pass are not compared string-wise or numerically; they must simply evaluate to the same truthiness. For example: # Return role names which are not only for ACL purposes $object->Roles( ACLOnly => 0 ); # Return role names which are denormalized into a column; note that the # role's Column attribute contains a string. $object->Roles( Column => 1 ); =cut sub Roles { my $self = shift; my %attr = @_; return map { $_->[0] } sort { $a->[1]{SortOrder} <=> $b->[1]{SortOrder} or $a->[0] cmp $b->[0] } grep { my $ok = 1; for my $k (keys %attr) { $ok = 0, last if $attr{$k} xor $_->[1]{$k}; } $ok } grep { !$_->[1]{AppliesToObjectPredicate} or $_->[1]{AppliesToObjectPredicate}->($self) } map { [ $_, $self->_ROLES->{$_} ] } keys %{ $self->_ROLES }; } { my %ROLES; sub _ROLES { my $class = ref($_[0]) || $_[0]; return $ROLES{$class} ||= {}; } } =head2 HasRole Returns true if the name provided is a registered role for this class. Otherwise returns false. =cut sub HasRole { my $self = shift; my $type = shift; return scalar grep { $type eq $_ } $self->Roles; } =head2 RoleGroup Expects a role name as the first parameter which is used to load the L<RT::Group> for the specified role on this record. Returns an unloaded L<RT::Group> object on failure. =cut sub RoleGroup { my $self = shift; my $name = shift; my %args = @_; my $group = RT::Group->new( $self->CurrentUser ); if ($args{CheckRight}) { return $group if !$self->CurrentUserHasRight($args{CheckRight}); } if ($self->HasRole($name)) { $group->LoadRoleGroup( Object => $self, Name => $name, ); } return $group; } =head2 CanonicalizePrincipal Takes some description of a principal (see below) and returns the corresponding L<RT::Principal>. C<Type>, as in role name, is a required parameter for producing error messages. =over 4 =item Principal The L<RT::Principal> if you've already got it. =item PrincipalId The ID of the L<RT::Principal> object. =item User The Name or EmailAddress of an L<RT::User>. If an email address is given, but a user matching it cannot be found, a new user will be created. =item Group The Name of an L<RT::Group>. =back =cut sub CanonicalizePrincipal { my $self = shift; my %args = (@_); return (0, $self->loc("One, and only one, of Principal/PrincipalId/User/Group is required")) if 1 != grep { $_ } @args{qw/Principal PrincipalId User Group/}; if ($args{Principal}) { return $args{Principal}; } elsif ($args{PrincipalId}) { # Check the PrincipalId for loops my $principal = RT::Principal->new( $self->CurrentUser ); $principal->Load($args{'PrincipalId'}); if ( $principal->id and $principal->IsUser and my $email = $principal->Object->EmailAddress ) { return (0, $self->loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop", $email, $self->loc($args{Type}))) if RT::EmailParser->IsRTAddress( $email ); } } else { if ($args{User}) { my $name = delete $args{User}; # Sanity check the address return (0, $self->loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop", $name, $self->loc($args{Type}) )) if RT::EmailParser->IsRTAddress( $name ); # Create as the SystemUser, not the current user my $user = RT::User->new(RT->SystemUser); my ($ok, $msg); if ($name =~ /@/) { ($ok, $msg) = $user->LoadOrCreateByEmail( $name ); } else { ($ok, $msg) = $user->Load( $name ); } unless ($user->Id) { # If we can't find this watcher, we need to bail. $RT::Logger->error("Could not load or create a user '$name' to add as a watcher: $msg"); return (0, $self->loc("Could not find or create user '[_1]'", $name)); } $args{PrincipalId} = $user->PrincipalId; } elsif ($args{Group}) { my $name = delete $args{Group}; my $group = RT::Group->new( $self->CurrentUser ); $group->LoadUserDefinedGroup($name); unless ($group->id) { $RT::Logger->error("Could not load group '$name' to add as a watcher"); return (0, $self->loc("Could not find group '[_1]'", $name)); } $args{PrincipalId} = $group->PrincipalObj->id; } } my $principal = RT::Principal->new( $self->CurrentUser ); $principal->Load( $args{PrincipalId} ); return $principal; } =head2 AddRoleMember Adds the described L<RT::Principal> to the specified role group for this record. Takes a set of key-value pairs: =over 4 =item Principal, PrincipalId, User, or Group Required. Canonicalized through L</CanonicalizePrincipal>. =item Type Required. One of the valid roles for this record, as returned by L</Roles>. =item ACL Optional. A subroutine reference which will be passed the role type and principal being added. If it returns false, the method will fail with a status of "Permission denied". =back Returns a tuple of (principal object which was added, message). =cut sub AddRoleMember { my $self = shift; my %args = (@_); my ($principal, $msg) = $self->CanonicalizePrincipal(%args); return (0, $msg) if !$principal; my $type = delete $args{Type}; return (0, $self->loc("That role is invalid for this object")) unless $type and $self->HasRole($type); my $acl = delete $args{ACL}; return (0, $self->loc("Permission denied")) if $acl and not $acl->($type => $principal); my $group = $self->RoleGroup( $type ); if (!$group->id) { $group = $self->_CreateRoleGroup($type); if (!$group || !$group->id) { return (0, $self->loc("Role group '[_1]' not found", $type)); } } return (0, $self->loc('[_1] is already [_2]', $principal->Object->Name, $group->Label) ) if $group->HasMember( $principal ); return (0, $self->loc('[_1] cannot be a group', $group->Label) ) if $group->SingleMemberRoleGroup and $principal->IsGroup; ( (my $ok), $msg ) = $group->_AddMember( %args, PrincipalId => $principal->Id, RecordTransaction => !$args{Silent} ); unless ($ok) { $RT::Logger->error("Failed to add principal ".$principal->Id." as a member of group ".$group->Id.": ".$msg); return ( 0, $self->loc('Could not make [_1] a [_2]', $principal->Object->Name, $group->Label) ); } return ($principal, $msg); } =head2 DeleteRoleMember Removes the specified L<RT::Principal> from the specified role group for this record. Takes a set of key-value pairs: =over 4 =item PrincipalId Optional. The ID of the L<RT::Principal> object to remove. =item User Optional. The Name or EmailAddress of an L<RT::User> to use as the principal =item Type Required. One of the valid roles for this record, as returned by L</Roles>. =item ACL Optional. A subroutine reference which will be passed the role type and principal being removed. If it returns false, the method will fail with a status of "Permission denied". =back One, and only one, of I<PrincipalId> or I<User> is required. Returns a tuple of (principal object that was removed, message). =cut sub DeleteRoleMember { my $self = shift; my %args = (@_); return (0, $self->loc("That role is invalid for this object")) unless $args{Type} and $self->HasRole($args{Type}); if ($args{User}) { my $user = RT::User->new( $self->CurrentUser ); $user->LoadByEmail( $args{User} ); $user->Load( $args{User} ) unless $user->id; return (0, $self->loc("Could not load user '[_1]'", $args{User}) ) unless $user->id; $args{PrincipalId} = $user->PrincipalId; } return (0, $self->loc("No valid PrincipalId")) unless $args{PrincipalId}; my $principal = RT::Principal->new( $self->CurrentUser ); $principal->Load( $args{PrincipalId} ); my $acl = delete $args{ACL}; return (0, $self->loc("Permission denied")) if $acl and not $acl->($args{Type} => $principal); my $group = $self->RoleGroup( $args{Type} ); return (0, $self->loc("Role group '[_1]' not found", $args{Type})) unless $group->id; return ( 0, $self->loc( '[_1] is not a [_2]', $principal->Object->Name, $self->loc($args{Type}) ) ) unless $group->HasMember($principal); my ($ok, $msg) = $group->_DeleteMember($args{PrincipalId}, RecordTransaction => !$args{Silent}); unless ($ok) { $RT::Logger->error("Failed to remove $args{PrincipalId} as a member of group ".$group->Id.": ".$msg); return ( 0, $self->loc('Could not remove [_1] as a [_2]', $principal->Object->Name, $self->loc($args{Type})) ); } return ($principal, $msg); } sub _ResolveRoles { my $self = shift; my ($roles, %args) = (@_); my @errors; for my $role ($self->Roles) { if ($self->_ROLES->{$role}{Single}) { # Default to nobody if unspecified my $value = $args{$role} || RT->Nobody; $value = $value->[0] if ref $value eq 'ARRAY'; if (Scalar::Util::blessed($value) and $value->isa("RT::User")) { # Accept a user; it may not be loaded, which we catch below $roles->{$role} = $value->PrincipalObj; } else { # Try loading by id, name, then email. If all fail, catch that below my $user = RT::User->new( $self->CurrentUser ); $user->Load( $value ); # XXX: LoadOrCreateByEmail ? $user->LoadByEmail( $value ) unless $user->id; $roles->{$role} = $user->PrincipalObj; } unless (Scalar::Util::blessed($roles->{$role}) and $roles->{$role}->id) { push @errors, $self->loc("Invalid value for [_1]",$self->loc($role)); $roles->{$role} = RT->Nobody->PrincipalObj; } # For consistency, we always return an arrayref $roles->{$role} = [ $roles->{$role} ]; } else { $roles->{$role} = []; my @values = ref $args{ $role } ? @{ $args{$role} } : ($args{$role}); for my $value (grep {defined} @values) { if ( $value =~ /^\d+$/ ) { # This implicitly allows groups, if passed by id. my $principal = RT::Principal->new( $self->CurrentUser ); my ($ok, $msg) = $principal->Load( $value ); if ($ok) { push @{ $roles->{$role} }, $principal; } else { push @errors, $self->loc("Couldn't load principal: [_1]", $msg); } } else { my @addresses = RT::EmailParser->ParseEmailAddress( $value ); for my $address ( @addresses ) { my $user = RT::User->new( RT->SystemUser ); my ($id, $msg) = $user->LoadOrCreateByEmail( $address ); if ( $id ) { # Load it back as us, not as the system # user, to be completely safe. $user = RT::User->new( $self->CurrentUser ); $user->Load( $id ); push @{ $roles->{$role} }, $user->PrincipalObj; } else { push @errors, $self->loc("Couldn't load or create user: [_1]", $msg); } } } } } } return (@errors); } sub _CreateRoleGroup { my $self = shift; my $name = shift; my %args = ( @_, ); my $role = $self->Role($name); my %create = ( Name => $name, Object => $self, %args, ); return (0) if $role->{CreateGroupPredicate} && !$role->{CreateGroupPredicate}->(%create); my $type_obj = RT::Group->new($self->CurrentUser); my ($id, $msg) = $type_obj->CreateRoleGroup(%create); unless ($id) { $RT::Logger->error("Couldn't create a role group of type '$name' for ".ref($self)." ". $self->id.": ".$msg); return(undef); } return $type_obj; } sub _CreateRoleGroups { my $self = shift; my %args = (@_); for my $name ($self->Roles) { my ($ok) = $self->_CreateRoleGroup($name, %args); return(undef) if !$ok; } return(1); } sub _AddRolesOnCreate { my $self = shift; my ($roles, %acls) = @_; my @errors; { my $changed = 0; for my $role (keys %{$roles}) { my $group = $self->RoleGroup($role); my @left; for my $principal (@{$roles->{$role}}) { if ($acls{$role}->($principal)) { next if $group->HasMember($principal); my ($ok, $msg) = $group->_AddMember( PrincipalId => $principal->id, InsideTransaction => 1, RecordTransaction => 0, Object => $self, ); push @errors, $self->loc("Couldn't set [_1] watcher: [_2]", $role, $msg) unless $ok; $changed++; } else { push @left, $principal; } } $roles->{$role} = [ @left ]; } redo if $changed; } return @errors; } =head2 LabelForRole Returns a label suitable for displaying the passed-in role to an end user. =cut sub LabelForRole { my $self = shift; my $name = shift; my $role = $self->Role($name); if ($role->{LabelGenerator}) { return $role->{LabelGenerator}->($self); } return $role->{Name}; } 1; �����������������rt-5.0.1/lib/RT/Record/Role/Links.pm����������������������������������������������������������������000644 �000765 �000024 �00000011626 14005011336 017637� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Record::Role::Links; use Role::Basic; =head1 NAME RT::Record::Role::Links - Common methods for records which handle links =head1 REQUIRES =head2 L<RT::Record::Role> =head2 _AddLink Usually provided by L<RT::Record/_AddLink>. =head2 _DeleteLink Usually provided by L<RT::Record/_DeleteLink>. =head2 ModifyLinkRight The right name to check in L<AddLink> and L<DeleteLink>. =head2 CurrentUserHasRight =cut with 'RT::Record::Role'; requires '_AddLink'; requires '_DeleteLink'; requires 'ModifyLinkRight'; requires 'CurrentUserHasRight'; =head1 PROVIDES =head2 _AddLinksOnCreate Calls _AddLink (usually L<RT::Record/_AddLink>) for all valid link types and aliases found in the hash. Refer to L<RT::Link/%TYPEMAP> for details of link types. Key values may be a single URI or an arrayref of URIs. Takes two hashrefs. The first is the argument hash provided to the consuming class's Create method. The second is optional and contains extra arguments to pass to _AddLink. By default records a transaction on the link's destination object (if any), but not on the origin object. Returns an array of localized error messages, if any. =cut sub _AddLinksOnCreate { my $self = shift; my %args = %{shift || {}}; my %AddLink = %{shift || {}}; my @results; foreach my $type ( keys %RT::Link::TYPEMAP ) { next unless defined $args{$type}; my $links = $args{$type}; $links = [$links] unless ref $links; for my $link (@$links) { my $typemap = $RT::Link::TYPEMAP{$type}; my $opposite_mode = $typemap->{Mode} eq "Base" ? "Target" : "Base"; my ($ok, $msg) = $self->_AddLink( Type => $typemap->{Type}, $typemap->{Mode} => $link, "Silent$opposite_mode" => 1, %AddLink, ); push @results, $self->loc("Unable to add [_1] link: [_2]", $self->loc($type), $msg) unless $ok; } } return @results; } =head2 AddLink Takes a paramhash of Type and one of Base or Target. Adds that link to this record. Refer to L<RT::Record/_AddLink> for full documentation. This method implements permissions and ticket validity checks before calling into L<RT::Record> (usually). =cut sub AddLink { my $self = shift; return (0, $self->loc("Permission Denied")) unless $self->CurrentUserHasRight($self->ModifyLinkRight); return $self->_AddLink(@_); } =head2 DeleteLink Takes a paramhash of Type and one of Base or Target. Removes that link from the record. Refer to L<RT::Record/_DeleteLink> for full documentation. This method implements permission checks before calling into L<RT::Record> (usually). =cut sub DeleteLink { my $self = shift; return (0, $self->loc("Permission Denied")) unless $self->CurrentUserHasRight($self->ModifyLinkRight); return $self->_DeleteLink(@_); } 1; ����������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Record/Role/Rights.pm���������������������������������������������������������������000644 �000765 �000024 �00000007170 14005011336 020016� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Record::Role::Rights; use Role::Basic; use Scalar::Util qw(blessed); =head1 NAME RT::Record::Role::Rights - Common methods for records which can provide rights =head1 DESCRIPTION =head1 REQUIRES =head2 L<RT::Record::Role> =cut with 'RT::Record::Role'; =head1 PROVIDES =cut =head2 AddRight C<CATEGORY>, C<RIGHT>, C<DESCRIPTION> Adds the given rights to the list of possible rights. This method should be called during server startup, not at runtime. =cut sub AddRight { my $class = shift; $class = ref($class) || $class; my ($category, $name, $description) = @_; require RT::ACE; if (exists $RT::ACE::RIGHTS{$class}{lc $name}) { warn "Duplicate right '$name' found"; return; } $RT::ACE::RIGHTS{$class}{lc $name} = { Name => $name, Category => $category, Description => $description, }; } =head2 AvailableRights Returns a hashref of available rights for this object. The keys are the right names and the values are a description of what the rights do. =cut sub AvailableRights { my $self = shift; my $class = ref($self) || $self; my %rights; $rights{$_->{Name}} = $_->{Description} for values %{$RT::ACE::RIGHTS{$class} || {} }; return \%rights; } =head2 RightCategories Returns a hashref where the keys are rights for this type of object and the values are the category (General, Staff, Admin) the right falls into. =cut sub RightCategories { my $self = shift; my $class = ref($self) || $self; my %rights; $rights{$_->{Name}} = $_->{Category} for values %{ $RT::ACE::RIGHTS{$class} || {} }; return \%rights; } 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Record/Role/Lifecycle.pm������������������������������������������������������������000644 �000765 �000024 �00000013505 14005011336 020454� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Record::Role::Lifecycle; use Role::Basic; use Scalar::Util qw(blessed); =head1 NAME RT::Record::Role::Lifecycle - Common methods for records which have a Lifecycle column =head1 REQUIRES =head2 L<RT::Record::Role> =head2 LifecycleType Used as a role parameter. Must return a string of the type of lifecycles the record consumes, i.e. I<ticket> for L<RT::Queue>. =head2 Lifecycle A Lifecycle method which returns a lifecycle name is required. Currently unenforced at compile-time due to poor interactions with L<DBIx::SearchBuilder::Record/AUTOLOAD>. You'll hit run-time errors if this method isn't available in consuming classes, however. =cut with 'RT::Record::Role'; requires 'LifecycleType'; # XXX: can't require column methods due to DBIx::SB::Record's AUTOLOAD #requires 'Lifecycle'; =head1 PROVIDES =head2 LifecycleObj [CONTEXT_OBJ] Returns an L<RT::Lifecycle> object for this record's C<Lifecycle>. If called as a class method, returns an L<RT::Lifecycle> object which is an aggregation of all lifecycles of the appropriate type. Pass an additional L<CONTEXT_OBJ> to check rights at the objects context. =cut sub LifecycleObj { my $self = shift; my $context_obj = shift || undef; my $type = $self->LifecycleType; my $fallback = $self->_Accessible( Lifecycle => "default" ); unless (blessed($self) and $self->id) { return RT::Lifecycle->Load( Type => $type ); } my $name = $self->Lifecycle($context_obj); if ( !$name ) { RT::Logger->debug('Failing back to default lifecycle value'); $name = $fallback; } my $res = RT::Lifecycle->Load( Name => $name, Type => $type ); unless ( $res ) { RT->Logger->error( sprintf "Lifecycle '%s' of type %s for %s #%d doesn't exist", $name, $type, ref($self), $self->id); return RT::Lifecycle->Load( Name => $fallback, Type => $type ); } return $res; } =head2 SetLifecycle Validates that the specified lifecycle exists before updating the record. Takes a lifecycle name. =cut sub SetLifecycle { my $self = shift; my $value = shift || $self->_Accessible( Lifecycle => "default" ); return (0, $self->loc('[_1] is not a valid lifecycle', $value)) unless $self->ValidateLifecycle($value); return $self->_Set( Field => 'Lifecycle', Value => $value, @_ ); } =head2 ValidateLifecycle Takes a lifecycle name. Returns true if it's an OK name and such lifecycle is configured. Returns false otherwise. =cut sub ValidateLifecycle { my $self = shift; my $value = shift; return unless $value; return unless RT::Lifecycle->Load( Name => $value, Type => $self->LifecycleType ); return 1; } =head2 ActiveStatusArray Returns an array of all ActiveStatuses for the lifecycle =cut sub ActiveStatusArray { my $self = shift; return $self->LifecycleObj->Valid('initial', 'active'); } =head2 InactiveStatusArray Returns an array of all InactiveStatuses for the lifecycle =cut sub InactiveStatusArray { my $self = shift; return $self->LifecycleObj->Inactive; } =head2 StatusArray Returns an array of all statuses for the lifecycle =cut sub StatusArray { my $self = shift; return $self->LifecycleObj->Valid( @_ ); } =head2 IsValidStatus Takes a status. Returns true if STATUS is a valid status. Otherwise, returns 0. =cut sub IsValidStatus { my $self = shift; return $self->LifecycleObj->IsValid( shift ); } =head2 IsActiveStatus Takes a status. Returns true if STATUS is a Active status. Otherwise, returns 0 =cut sub IsActiveStatus { my $self = shift; return $self->LifecycleObj->IsValid( shift, 'initial', 'active'); } =head2 IsInactiveStatus Takes a status. Returns true if STATUS is a Inactive status. Otherwise, returns 0 =cut sub IsInactiveStatus { my $self = shift; return $self->LifecycleObj->IsInactive( shift ); } 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Record/Role/Status.pm���������������������������������������������������������������000644 �000765 �000024 �00000023540 14005011336 020040� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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::Record::Role::Status; use Role::Basic; use Scalar::Util qw(blessed); =head1 NAME RT::Record::Role::Status - Common methods for records which have a Status column =head1 DESCRIPTION Lifecycles are generally set on container records, and Statuses on records which belong to one of those containers. L<RT::Record::Role::Lifecycle> handles the containers with the I<Lifecycle> column. This role is for the records with a I<Status> column within those containers. It includes convenience methods for grabbing an L<RT::Lifecycle> object as well setters for validating I<Status> and the column which points to the container object. =head1 REQUIRES =head2 L<RT::Record::Role> =head2 LifecycleColumn Used as a role parameter. Must return a string of the column name which points to the container object that consumes L<RT::Record::Role::Lifecycle> (or conforms to it). The resulting string is used to construct two method names: as-is to fetch the column value and suffixed with "Obj" to fetch the object. =head2 Status A Status method which returns a lifecycle name is required. Currently unenforced at compile-time due to poor interactions with L<DBIx::SearchBuilder::Record/AUTOLOAD>. You'll hit run-time errors if this method isn't available in consuming classes, however. =cut with 'RT::Record::Role'; requires 'LifecycleColumn'; =head1 PROVIDES =head2 Status Returns the Status for this record, in the canonical casing. =cut sub Status { my $self = shift; my $value = $self->_Value( 'Status' ); my $lifecycle = $self->LifecycleObj; return $value unless $lifecycle; return $lifecycle->CanonicalCase( $value ); } =head2 LifecycleObj Returns an L<RT::Lifecycle> object for this record's C<Lifecycle>. If called as a class method, returns an L<RT::Lifecycle> object which is an aggregation of all lifecycles of the appropriate type. =cut sub LifecycleObj { my $self = shift; my $obj = $self->LifecycleColumn . "Obj"; return $self->$obj->LifecycleObj($self); } =head2 Lifecycle Returns the L<RT::Lifecycle/Name> of this record's L</LifecycleObj>. =cut sub Lifecycle { my $self = shift; return $self->LifecycleObj->Name; } =head2 ValidateStatus Takes a status. Returns true if that status is a valid status for this record, otherwise returns false. =cut sub ValidateStatus { my $self = shift; return $self->LifecycleObj->IsValid(@_); } =head2 ValidateStatusChange Validates the new status with the current lifecycle. Returns a tuple of (OK, message). Expected to be called from this role's L</SetStatus> or the consuming class' equivalent. =cut sub ValidateStatusChange { my $self = shift; my $new = shift; my $old = $self->Status; my $lifecycle = $self->LifecycleObj; unless ( $lifecycle->IsValid( $new ) ) { return (0, $self->loc("Status '[_1]' isn't a valid status for this [_2].", $self->loc($new), $self->loc($lifecycle->Type))); } unless ( $lifecycle->IsTransition( $old => $new ) ) { return (0, $self->loc("You can't change status from '[_1]' to '[_2]'.", $self->loc($old), $self->loc($new))); } my $check_right = $lifecycle->CheckRight( $old => $new ); unless ( $self->CurrentUser->HasRight( Right => $check_right, Object => $self ) ) { return ( 0, $self->loc('Permission Denied') ); } return 1; } =head2 SetStatus Validates the status transition before updating the Status column. This method may want to be overridden by a more specific method in the consuming class. =cut sub SetStatus { my $self = shift; my $new = shift; my ($valid, $error) = $self->ValidateStatusChange($new); return ($valid, $error) unless $valid; return $self->_SetStatus( Status => $new ); } =head2 _SetStatus Sets the Status column without validating the change. Intended to be used as-is by methods provided by the role, or overridden in the consuming class to take additional action. For example, L<RT::Ticket/_SetStatus> sets the Started and Resolved dates on the ticket as necessary. Takes a paramhash where the only required key is Status. Other keys may include Lifecycle and NewLifecycle when called from L</_SetLifecycleColumn>, which may assist consuming classes. NewLifecycle defaults to Lifecycle if not provided; this indicates the lifecycle isn't changing. =cut sub _SetStatus { my $self = shift; my %args = ( Status => undef, Lifecycle => $self->LifecycleObj, @_, ); $args{Status} = lc $args{Status} if defined $args{Status}; $args{NewLifecycle} ||= $args{Lifecycle}; return $self->_Set( Field => 'Status', Value => $args{Status}, ); } =head2 _SetLifecycleColumn Validates and updates the column named by L</LifecycleColumn>. The Status column is also updated if necessary (via lifecycle transition maps). On success, returns a tuple of (1, I<message>, I<new status>) where I<new status> is the status that was transitioned to, if any. On failure, returns (0, I<error message>). Takes a paramhash with keys I<Value> and (optionally) I<RequireRight>. I<RequireRight> is a right name which the current user must have on the new L</LifecycleColumn> object in order for the method to succeed. This method is expected to be used from within another method such as L<RT::Ticket/SetQueue>. =cut sub _SetLifecycleColumn { my $self = shift; my %args = @_; my $column = $self->LifecycleColumn; my $column_obj = "${column}Obj"; my $current = $self->$column_obj; my $class = blessed($current); my $new = $class->new( $self->CurrentUser ); $new->Load($args{Value}); return (0, $self->loc("[_1] [_2] does not exist", $self->loc($column), $args{Value})) unless $new->id; my $name = eval { $current->Name } || $current->id; return (0, $self->loc("[_1] [_2] is disabled", $self->loc($column), $name)) if $new->Disabled; return (0, $self->loc("[_1] is already set to [_2]", $self->loc($column), $name)) if $new->id == $current->id; return (0, $self->loc("Permission Denied")) if $args{RequireRight} and not $self->CurrentUser->HasRight( Right => $args{RequireRight}, Object => $new, ); my $new_status; my $old_lifecycle = $current->LifecycleObj; my $new_lifecycle = $new->LifecycleObj; if ( $old_lifecycle->Name ne $new_lifecycle->Name ) { unless ( $old_lifecycle->HasMoveMap( $new_lifecycle ) ) { return ( 0, $self->loc("There is no mapping for statuses between lifecycle [_1] and [_2]. Contact your system administrator.", $old_lifecycle->Name, $new_lifecycle->Name) ); } $new_status = $old_lifecycle->MoveMap( $new_lifecycle )->{ lc $self->Status }; return ( 0, $self->loc("Mapping between lifecycle [_1] and [_2] is incomplete. Contact your system administrator.", $old_lifecycle->Name, $new_lifecycle->Name) ) unless $new_status; } my ($ok, $msg) = $self->_Set( Field => $column, Value => $new->id ); if ($ok) { if ( $new_status and $new_status ne $self->Status ) { my $as_system = blessed($self)->new( RT->SystemUser ); $as_system->Load( $self->Id ); unless ( $as_system->Id ) { return ( 0, $self->loc("Couldn't load copy of [_1] #[_2]", blessed($self), $self->Id) ); } my ($val, $msg) = $as_system->_SetStatus( Lifecycle => $old_lifecycle, NewLifecycle => $new_lifecycle, Status => $new_status, ); if ($val) { # Pick up the change made by the clone above $self->Load( $self->id ); } else { RT->Logger->error("Status change to $new_status failed on $column change: $msg"); undef $new_status; } } return (1, $msg, $new_status); } else { return (0, $msg); } } 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ExternalStorage/AmazonS3.pm���������������������������������������������������������000644 �000765 �000024 �00000023104 14005011336 021174� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 warnings; use strict; package RT::ExternalStorage::AmazonS3; use Role::Basic qw/with/; with 'RT::ExternalStorage::Backend'; sub S3 { my $self = shift; if (@_) { $self->{S3} = shift; } return $self->{S3}; } sub Bucket { my $self = shift; return $self->{Bucket}; } sub AccessKeyId { my $self = shift; return $self->{AccessKeyId}; } sub SecretAccessKey { my $self = shift; return $self->{SecretAccessKey}; } sub BucketObj { my $self = shift; return $self->S3->bucket($self->Bucket); } sub Init { my $self = shift; if (not Amazon::S3->require) { RT->Logger->error("Required module Amazon::S3 is not installed"); return; } for my $key (qw/AccessKeyId SecretAccessKey Bucket/) { if (not $self->$key) { RT->Logger->error("Required option '$key' not provided for AmazonS3 external storage. See the documentation for " . __PACKAGE__ . " for setting up this integration."); return; } } my %args = ( aws_access_key_id => $self->AccessKeyId, aws_secret_access_key => $self->SecretAccessKey, retry => 1, ); $args{host} = $self->{Host} if $self->{Host}; my $S3 = Amazon::S3->new(\%args); $self->S3($S3); my $buckets = $S3->bucket( $self->Bucket ); unless ( $buckets ) { RT->Logger->error("Can't list buckets of AmazonS3: ".$S3->errstr); return; } my @buckets = $buckets->{buckets} ? @{$buckets->{buckets}} : ($buckets); unless ( grep {$_->bucket eq $self->Bucket} @buckets ) { my $ok = $S3->add_bucket( { bucket => $self->Bucket, acl_short => 'private', } ); if ($ok) { RT->Logger->debug("Created new bucket '".$self->Bucket."' on AmazonS3"); } else { RT->Logger->error("Can't create new bucket '".$self->Bucket."' on AmazonS3: ".$S3->errstr); return; } } return $self; } sub Get { my $self = shift; my ($sha) = @_; my $ok = $self->BucketObj->get_key( $sha ); return (undef, "Could not retrieve from AmazonS3:" . $self->S3->errstr) unless $ok; return ($ok->{value}); } sub Store { my $self = shift; my ($sha, $content, $attachment) = @_; # No-op if the path exists already return ($sha) if $self->BucketObj->head_key( $sha ); # Without content_type, S3 can guess wrong and cause attachments downloaded # via a link to have a content type of binary/octet-stream $self->BucketObj->add_key( $sha => $content, { content_type => $attachment->ContentType } ) or return (undef, "Failed to write to AmazonS3: " . $self->S3->errstr); return ($sha); } sub DownloadURLFor { my $self = shift; my $object = shift; my $column = $object->isa('RT::Attachment') ? 'Content' : 'LargeContent'; my $digest = $object->__Value($column); # "If you make a request to the http://BUCKET.s3.amazonaws.com # endpoint, the DNS has sufficient information to route your request # directly to the region where your bucket resides." return "https://" . $self->Bucket . ".s3.amazonaws.com/" . $digest; } =head1 NAME RT::ExternalStorage::AmazonS3 - Store files in Amazon's S3 cloud =head1 SYNOPSIS Set(%ExternalStorage, Type => 'AmazonS3', AccessKeyId => '...', SecretAccessKey => '...', Bucket => '...', ); =head1 DESCRIPTION This storage option places attachments in the S3 cloud file storage service. The files are de-duplicated when they are saved; as such, if the same file appears in multiple transactions, only one copy will be stored in S3. Files in S3 B<must not be modified or removed>; doing so may cause internal inconsistency. It is also important to ensure that the S3 account used maintains sufficient funds for your RT's B<storage and bandwidth> needs. =head1 SETUP In order to use this storage type, you must grant RT access to your S3 account. =over =item 1. Log into Amazon S3, L<https://aws.amazon.com/s3/>, as the account you wish to store files under. =item 2. Navigate to "Security Credentials" under your account name in the menu bar. =item 3. Open the "Access Keys" pane. =item 4. Click "Create New Access Key". =item 5. Copy the provided values for Access Key ID and Secret Access Key into your F<RT_SiteConfig.pm> file: Set(%ExternalStorage, Type => 'AmazonS3', AccessKeyId => '...', # Put Access Key ID between quotes SecretAccessKey => '...', # Put Secret Access Key between quotes Bucket => '...', ); =item 6. Set up a Bucket for RT to use. You can either create and configure it in the S3 web interface, or let RT create one itself. Either way, tell RT what bucket name to use in your F<RT_SiteConfig.pm> file: Set(%ExternalStorage, Type => 'AmazonS3', AccessKeyId => '...', SecretAccessKey => '...', Bucket => '...', # Put bucket name between quotes ); =item 7. You may specify a C<Host> option in C<Set(%ExternalStorage, ...);> to connect to an endpoint other than L<Amazon::S3>'s default of C<s3.amazonaws.com>. =back =head2 Direct Linking This storage engine supports direct linking. This means that RT can link I<directly> to S3 when listing attachments, showing image previews, etc. This relieves some bandwidth pressure from RT because ordinarily it would have to download each attachment from S3 to be able to serve it. To enable direct linking you must first make all content in your bucket publicly viewable. B<Beware that this could have serious implications for billing and privacy>. RT cannot enforce its access controls for content on S3. This is tempered somewhat by the fact that users must be able to guess the SHA-256 digest of the file to be able to access it. But there is nothing stopping someone from tweeting a URL to a file hosted on your S3. These concerns do not arise when using an RT-mediated link to S3, since RT uses an access key to upload to and download from S3. To make all content in an S3 bucket publicly viewable, navigate to the bucket in the S3 web UI. Select the "Properties" tab and inside "Permissions" there is a button to "Add bucket policy". Paste the following content in the provided textbox: { "Version": "2008-10-17", "Statement": [ { "Sid": "AllowPublicRead", "Effect": "Allow", "Principal": { "AWS": "*" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::BUCKET/*" } ] } Replace C<BUCKET> with the bucket name that is used by your RT instance. Finally, set C<$ExternalStorageDirectLink> to 1 in your F<RT_SiteConfig.pm> file: Set($ExternalStorageDirectLink, 1); =head1 TROUBLESHOOTING =head2 Issues Connecting to the Amazon Bucket Here are some things to check if you receive errors connecting to Amazon S3. =over =item * Double check all of the configuration parameters, including the bucket name. Remember to restart the server after changing values for RT to load new settings. =item * If you manually created a bucket, make sure it is in your default region. Trying to access a bucket in a different region may result in 400 errors. =item * Check the permissions on the bucket and make sure they are sufficient for the user RT is connecting as to upload and access files. If you are using the direct link option, you will need to open permissions further for users to access the attachment via the direct link. =back =cut RT::Base->_ImportOverlays(); 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ExternalStorage/Disk.pm�������������������������������������������������������������000644 �000765 �000024 �00000011144 14005011336 020434� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 warnings; use strict; package RT::ExternalStorage::Disk; use File::Path qw//; use Role::Basic qw/with/; with 'RT::ExternalStorage::Backend'; sub Path { my $self = shift; return $self->{Path}; } sub Init { my $self = shift; if (not $self->Path) { RT->Logger->error("Required option 'Path' not provided for Disk external storage."); return; } elsif (not -e $self->Path) { RT->Logger->error("Path provided for Disk external storage (".$self->Path.") does not exist"); return; } return $self; } sub IsWriteable { my $self = shift; if (not -w $self->Path) { return (undef, "Path provided for local storage (".$self->Path.") is not writable"); } return (1); } sub Get { my $self = shift; my ($sha) = @_; $sha =~ m{^(...)(...)(.*)}; my $path = $self->Path . "/$1/$2/$3"; return (undef, "File does not exist") unless -e $path; open(my $fh, "<", $path) or return (undef, "Cannot read file on disk: $!"); my $content = do {local $/; <$fh>}; $content = "" unless defined $content; close $fh; return ($content); } sub Store { my $self = shift; my ($sha, $content, $attachment) = @_; # fan out to avoid one gigantic directory which slows down all file access $sha =~ m{^(...)(...)(.*)}; my $dir = $self->Path . "/$1/$2"; my $path = "$dir/$3"; return ($sha) if -f $path; File::Path::make_path($dir, {error => \my $err}); return (undef, "Making directory failed") if @{$err}; open( my $fh, ">:raw", $path ) or return (undef, "Cannot write file on disk: $!"); print $fh $content or return (undef, "Cannot write file to disk: $!"); close $fh or return (undef, "Cannot write file to disk: $!"); return ($sha); } sub DownloadURLFor { return; } =head1 NAME RT::ExternalStorage::Disk - On-disk storage of attachments =head1 SYNOPSIS Set(%ExternalStorage, Type => 'Disk', Path => '/opt/rt5/var/attachments', ); =head1 DESCRIPTION This storage option places attachments on disk under the given C<Path>, uncompressed. The files are de-duplicated when they are saved; as such, if the same file appears in multiple transactions, only one copy will be stored on disk. The C<Path> must be readable by the web server, and writable by the C<sbin/rt-externalize-attachments> script. Because the majority of the attachments are in the filesystem, a simple database backup is thus incomplete. It is B<extremely important> that I<backups include the on-disk attachments directory>. Files also C<must not be modified or removed>; doing so may cause internal inconsistency. =cut RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ExternalStorage/Backend.pm����������������������������������������������������������000644 �000765 �000024 �00000005631 14005011336 021075� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 warnings; use strict; package RT::ExternalStorage::Backend; use Role::Basic; requires 'Init'; requires 'Get'; requires 'Store'; requires 'DownloadURLFor'; sub new { my $class = shift; my %args = @_; $class = delete $args{Type}; if (not $class) { RT->Logger->error("No storage engine type provided"); return undef; } elsif ($class->require) { # no action needed; $class was loaded } else { my $long = "RT::ExternalStorage::$class"; if ($long->require) { $class = $long; } else { RT->Logger->error("Can't load external storage engine $class: $@"); return undef; } } unless ($class->DOES("RT::ExternalStorage::Backend")) { RT->Logger->error("External storage engine $class doesn't implement RT::ExternalStorage::Backend"); return undef; } my $self = bless \%args, $class; $self->Init; } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/ExternalStorage/Dropbox.pm����������������������������������������������������������000644 �000765 �000024 �00000012454 14005011336 021164� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 warnings; use strict; package RT::ExternalStorage::Dropbox; use Role::Basic qw/with/; with 'RT::ExternalStorage::Backend'; sub Dropbox { my $self = shift; if (@_) { $self->{Dropbox} = shift; } return $self->{Dropbox}; } sub AccessToken { my $self = shift; return $self->{AccessToken}; } sub Init { my $self = shift; if (not File::Dropbox->require) { RT->Logger->error("Required module File::Dropbox is not installed"); return; } elsif (not $self->AccessToken) { RT->Logger->error("Required option 'AccessToken' not provided for Dropbox external storage. See the documentation for " . __PACKAGE__ . " for setting up this integration."); return; } my $dropbox = File::Dropbox->new( oauth2 => 1, access_token => $self->AccessToken, root => 'sandbox', furlopts => { timeout => 60 }, ); $self->Dropbox($dropbox); return $self; } sub Get { my $self = shift; my ($sha) = @_; my $dropbox = $self->Dropbox; open( $dropbox, "<", $sha) or return (undef, "Failed to retrieve file from dropbox: $!"); my $content = do {local $/; <$dropbox>}; close $dropbox; return ($content); } sub Store { my $self = shift; my ($sha, $content, $attachment) = @_; my $dropbox = $self->Dropbox; # No-op if the path exists already. This forces a metadata read. return ($sha) if open( $dropbox, "<", $sha); open( $dropbox, ">", $sha ) or return (undef, "Open for write on dropbox failed: $!"); print $dropbox $content or return (undef, "Write to dropbox failed: $!"); close $dropbox or return (undef, "Flush to dropbox failed: $!"); return ($sha); } sub DownloadURLFor { return; } =head1 NAME RT::ExternalStorage::Dropbox - Store files in the Dropbox cloud =head1 SYNOPSIS Set(%ExternalStorage, Type => 'Dropbox', AccessToken => '...', ); =head1 DESCRIPTION This storage option places attachments in the Dropbox shared file service. The files are de-duplicated when they are saved; as such, if the same file appears in multiple transactions, only one copy will be stored in Dropbox. Files in Dropbox B<must not be modified or removed>; doing so may cause internal inconsistency. It is also important to ensure that the Dropbox account used has sufficient space for the attachments, and to monitor its space usage. =head1 SETUP In order to use this storage type, a new application must be registered with Dropbox: =over =item 1. Log into Dropbox as the user you wish to store files as. =item 2. Click C<Create app> on L<https://www.dropbox.com/developers/apps> =item 3. Choose B<Dropbox API app> as the type of app. =item 4. Choose B<App Folder> for the Type of Access; your application only needs access to files it creates. =item 5. Enter a descriptive name -- C<Request Tracker files> is fine. =item 6. Check the checkbox for B<Terms and Conditions> (if present) and then click C<Create App>. =item 7. Under C<Generated access token>, click the C<Generate> button. =item 8. Copy the provided value into your F<RT_SiteConfig.pm> file as the C<AccessToken>: Set(%ExternalStorage, Type => 'Dropbox', AccessToken => '...', # Replace the value here, between the quotes ); =back =cut RT::Base->_ImportOverlays(); 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/DependencyWalker/FindDependencies.pm������������������������������������������������000644 �000765 �000024 �00000004305 14005011336 023047� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::DependencyWalker::FindDependencies; use strict; use warnings; sub new { my $class = shift; return bless {out => [], in => []}, $class; } sub Add { my $self = shift; my ($dir, $obj) = @_; push @{$self->{$dir}}, $obj; } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Web.pm��������������������������������������������������������������������000644 �000765 �000024 �00000501521 14005011336 017053� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} ## Portions Copyright 2000 Tobias Brox <tobix@fsck.com> ## This is a library of static subs to be used by the Mason web ## interface to RT =head1 NAME RT::Interface::Web =cut use strict; use warnings; use 5.010; package RT::Interface::Web; use RT::SavedSearches; use RT::CustomRoles; use URI qw(); use RT::Interface::Web::Menu; use RT::Interface::Web::Session; use RT::Interface::Web::Scrubber; use Digest::MD5 (); use List::MoreUtils qw(); use JSON qw(); use Plack::Util; use HTTP::Status qw(); =head2 SquishedCSS $style =cut my %SQUISHED_CSS; sub SquishedCSS { my $style = shift or die "need name"; return $SQUISHED_CSS{$style} if $SQUISHED_CSS{$style}; require RT::Squish::CSS; my $css = RT::Squish::CSS->new( Style => $style ); $SQUISHED_CSS{ $css->Style } = $css; return $css; } =head2 SquishedJS =cut my $SQUISHED_JS; sub SquishedJS { return $SQUISHED_JS if $SQUISHED_JS; require RT::Squish::JS; my $js = RT::Squish::JS->new(); $SQUISHED_JS = $js; return $js; } =head2 JSFiles =cut sub JSFiles { return qw{ jquery-3.4.1.min.js jquery_noconflict.js jquery-ui.min.js jquery-ui-timepicker-addon.js jquery-ui-patch-datepicker.js jquery.cookie.js selectize.min.js popper.min.js bootstrap.min.js bootstrap-select.min.js bootstrap-combobox.js i18n.js util.js autocomplete.js superfish.min.js jquery.supposition.js chosen.jquery.min.js history-folding.js cascaded.js forms.js event-registration.js late.js mousetrap.min.js keyboard-shortcuts.js assets.js /static/RichText/ckeditor.min.js dropzone.min.js quoteselection.js fontawesome.min.js rights-inspector.js Chart.min.js chartjs-plugin-colorschemes.min.js jquery.jgrowl.min.js }, RT->Config->Get('JSFiles'); } =head2 ClearSquished Removes the cached CSS and JS entries, forcing them to be regenerated on next use. =cut sub ClearSquished { undef $SQUISHED_JS; %SQUISHED_CSS = (); } =head2 EscapeHTML SCALARREF does a css-busting but minimalist escaping of whatever html you're passing in. =cut sub EscapeHTML { my $ref = shift; return unless defined $$ref; $$ref =~ s/&/&/g; $$ref =~ s/</</g; $$ref =~ s/>/>/g; $$ref =~ s/\(/(/g; $$ref =~ s/\)/)/g; $$ref =~ s/"/"/g; $$ref =~ s/'/'/g; } =head2 EscapeURI SCALARREF Escapes URI component according to RFC2396 =cut sub EscapeURI { my $ref = shift; return unless defined $$ref; use bytes; $$ref =~ s/([^a-zA-Z0-9_.!~*'()-])/uc sprintf("%%%02X", ord($1))/eg; } =head2 EncodeJSON SCALAR Encodes the SCALAR to JSON and returns a JSON Unicode (B<not> UTF-8) string. SCALAR may be a simple value or a reference. =cut sub EncodeJSON { my $s = JSON::to_json(shift, { allow_nonref => 1 }); $s =~ s{/}{\\/}g; return $s; } sub _encode_surrogates { my $uni = $_[0] - 0x10000; return ($uni / 0x400 + 0xD800, $uni % 0x400 + 0xDC00); } sub EscapeJS { my $ref = shift; return unless defined $$ref; $$ref = "'" . join('', map { chr($_) =~ /[a-zA-Z0-9]/ ? chr($_) : $_ <= 255 ? sprintf("\\x%02X", $_) : $_ <= 65535 ? sprintf("\\u%04X", $_) : sprintf("\\u%X\\u%X", _encode_surrogates($_)) } unpack('U*', $$ref)) . "'"; } =head2 WebCanonicalizeInfo(); Different web servers set different environmental varibles. This function must return something suitable for REMOTE_USER. By default, just downcase REMOTE_USER env =cut sub WebCanonicalizeInfo { return RequestENV('REMOTE_USER') ? lc RequestENV('REMOTE_USER') : RequestENV('REMOTE_USER'); } =head2 WebRemoteUserAutocreateInfo($user); Returns a hash of user attributes, used when WebRemoteUserAutocreate is set. =cut sub WebRemoteUserAutocreateInfo { my $user = shift; my %user_info; # default to making Privileged users, even if they specify # some other default Attributes if ( !$RT::UserAutocreateDefaultsOnLogin || ( ref($RT::UserAutocreateDefaultsOnLogin) && not exists $RT::UserAutocreateDefaultsOnLogin->{Privileged} ) ) { $user_info{'Privileged'} = 1; } # Populate fields with information from Unix /etc/passwd my ( $comments, $realname ) = ( getpwnam($user) )[ 5, 6 ]; $user_info{'Comments'} = $comments if defined $comments; $user_info{'RealName'} = $realname if defined $realname; # and return the wad of stuff return {%user_info}; } sub HandleRequest { my $ARGS = shift; if (RT->Config->Get('DevelMode')) { require Module::Refresh; Module::Refresh->refresh; } RT->Config->RefreshConfigFromDatabase(); $HTML::Mason::Commands::r->content_type("text/html; charset=utf-8"); $HTML::Mason::Commands::m->{'rt_base_time'} = [ Time::HiRes::gettimeofday() ]; # Roll back any dangling transactions from a previous failed connection $RT::Handle->ForceRollback() if $RT::Handle and $RT::Handle->TransactionDepth; MaybeEnableSQLStatementLog(); # avoid reentrancy, as suggested by masonbook local *HTML::Mason::Commands::session unless $HTML::Mason::Commands::m->is_subrequest; $HTML::Mason::Commands::m->autoflush( $HTML::Mason::Commands::m->request_comp->attr('AutoFlush') ) if ( $HTML::Mason::Commands::m->request_comp->attr_exists('AutoFlush') ); ValidateWebConfig(); DecodeARGS($ARGS); local $HTML::Mason::Commands::DECODED_ARGS = $ARGS; PreprocessTimeUpdates($ARGS); InitializeMenu(); MaybeShowInstallModePage(); MaybeRebuildCustomRolesCache(); RT->System->MaybeRebuildLifecycleCache(); $HTML::Mason::Commands::m->comp( '/Elements/SetupSessionCookie', %$ARGS ); SendSessionCookie(); if ( _UserLoggedIn() ) { # make user info up to date $HTML::Mason::Commands::session{'CurrentUser'} ->Load( $HTML::Mason::Commands::session{'CurrentUser'}->id ); undef $HTML::Mason::Commands::session{'CurrentUser'}->{'LangHandle'}; } else { $HTML::Mason::Commands::session{'CurrentUser'} = RT::CurrentUser->new(); } # attempt external auth $HTML::Mason::Commands::m->comp( '/Elements/DoAuth', %$ARGS ) if @{ RT->Config->Get( 'ExternalAuthPriority' ) || [] }; # Process session-related callbacks before any auth attempts $HTML::Mason::Commands::m->callback( %$ARGS, CallbackName => 'Session', CallbackPage => '/autohandler' ); MaybeRejectPrivateComponentRequest(); MaybeShowNoAuthPage($ARGS); AttemptExternalAuth($ARGS) if RT->Config->Get('WebRemoteUserContinuous') or not _UserLoggedIn(); _ForceLogout() unless _UserLoggedIn(); # attempt external auth $HTML::Mason::Commands::m->comp( '/Elements/DoAuth', %$ARGS ) if @{ RT->Config->Get( 'ExternalAuthPriority' ) || [] }; AttemptTokenAuthentication($ARGS) unless _UserLoggedIn(); # Process per-page authentication callbacks $HTML::Mason::Commands::m->callback( %$ARGS, CallbackName => 'Auth', CallbackPage => '/autohandler' ); if ( $ARGS->{'NotMobile'} ) { $HTML::Mason::Commands::session{'NotMobile'} = 1; } unless ( _UserLoggedIn() ) { _ForceLogout(); # Authenticate if the user is trying to login via user/pass query args my ($authed, $msg) = AttemptPasswordAuthentication($ARGS); unless ($authed) { my $m = $HTML::Mason::Commands::m; # REST urls get a special 401 response if ($m->request_comp->path =~ m{^/REST/\d+\.\d+/}) { $HTML::Mason::Commands::r->content_type("text/plain; charset=utf-8"); $m->error_format("text"); $m->out("RT/$RT::VERSION 401 Credentials required\n"); $m->out("\n$msg\n") if $msg; $m->abort; } # Specially handle /index.html and /m/index.html so that we get a nicer URL elsif ( $m->request_comp->path =~ m{^(/m)?/index\.html$} ) { my $mobile = $1 ? 1 : 0; my $next = SetNextPage($ARGS); $m->comp('/NoAuth/Login.html', next => $next, actions => [$msg], mobile => $mobile); $m->abort; } else { TangentForLogin($ARGS, results => ($msg ? LoginError($msg) : undef)); } } } MaybeShowInterstitialCSRFPage($ARGS); # now it applies not only to home page, but any dashboard that can be used as a workspace $HTML::Mason::Commands::session{'home_refresh_interval'} = $ARGS->{'HomeRefreshInterval'} if ( $ARGS->{'HomeRefreshInterval'} ); # Process per-page global callbacks $HTML::Mason::Commands::m->callback( %$ARGS, CallbackName => 'Default', CallbackPage => '/autohandler' ); ShowRequestedPage($ARGS); LogRecordedSQLStatements(RequestData => { Path => $HTML::Mason::Commands::m->request_path, }); # Process per-page final cleanup callbacks $HTML::Mason::Commands::m->callback( %$ARGS, CallbackName => 'Final', CallbackPage => '/autohandler' ); $HTML::Mason::Commands::m->comp( '/Elements/Footer', %$ARGS ); } sub _ForceLogout { delete $HTML::Mason::Commands::session{'CurrentUser'}; } sub _UserLoggedIn { if ( $HTML::Mason::Commands::session{CurrentUser} && $HTML::Mason::Commands::session{'CurrentUser'}->id ) { return 1; } else { return undef; } } =head2 LoginError ERROR Pushes a login error into the Actions session store and returns the hash key. =cut sub LoginError { my $new = shift; my $key = Digest::MD5::md5_hex( rand(1024) ); push @{ $HTML::Mason::Commands::session{"Actions"}->{$key} ||= [] }, $new; $HTML::Mason::Commands::session{'i'}++; return $key; } =head2 SetNextPage ARGSRef [PATH] Intuits and stashes the next page in the sesssion hash. If PATH is specified, uses that instead of the value of L<IntuitNextPage()>. Returns the hash value. =cut sub SetNextPage { my $ARGS = shift; my $next = $_[0] ? $_[0] : IntuitNextPage(); my $hash = Digest::MD5::md5_hex($next . $$ . rand(1024)); my $page = { url => $next }; # If an explicit URL was passed and we didn't IntuitNextPage, then # IsPossibleCSRF below is almost certainly unrelated to the actual # destination. Currently explicit next pages aren't used in RT, but the # API is available. if (not $_[0] and RT->Config->Get("RestrictReferrer")) { # This isn't really CSRF, but the CSRF heuristics are useful for catching # requests which may have unintended side-effects. my ($is_csrf, $msg, @loc) = IsPossibleCSRF($ARGS); if ($is_csrf) { RT->Logger->notice( "Marking original destination as having side-effects before redirecting for login.\n" ."Request: $next\n" ."Reason: " . HTML::Mason::Commands::loc($msg, @loc) ); $page->{'HasSideEffects'} = [$msg, @loc]; } } $HTML::Mason::Commands::session{'NextPage'}->{$hash} = $page; $HTML::Mason::Commands::session{'i'}++; return $hash; } =head2 FetchNextPage HASHKEY Returns the stashed next page hashref for the given hash. =cut sub FetchNextPage { my $hash = shift || ""; return $HTML::Mason::Commands::session{'NextPage'}->{$hash}; } =head2 RemoveNextPage HASHKEY Removes the stashed next page for the given hash and returns it. =cut sub RemoveNextPage { my $hash = shift || ""; return delete $HTML::Mason::Commands::session{'NextPage'}->{$hash}; } =head2 TangentForLogin ARGSRef [HASH] Redirects to C</NoAuth/Login.html>, setting the value of L<IntuitNextPage> as the next page. Takes a hashref of request %ARGS as the first parameter. Optionally takes all other parameters as a hash which is dumped into query params. =cut sub TangentForLogin { my $login = TangentForLoginURL(@_); Redirect( RT->Config->Get('WebBaseURL') . $login ); } =head2 TangentForLoginURL [HASH] Returns a URL suitable for tangenting for login. Optionally takes a hash which is dumped into query params. =cut sub TangentForLoginURL { my $ARGS = shift; my $hash = SetNextPage($ARGS); my %query = (@_, next => $hash); $query{mobile} = 1 if $HTML::Mason::Commands::m->request_comp->path =~ m{^/m(/|$)}; my $login = RT->Config->Get('WebPath') . '/NoAuth/Login.html?'; $login .= $HTML::Mason::Commands::m->comp('/Elements/QueryString', %query); return $login; } =head2 TangentForLoginWithError ERROR Localizes the passed error message, stashes it with L<LoginError> and then calls L<TangentForLogin> with the appropriate results key. =cut sub TangentForLoginWithError { my $ARGS = shift; my $key = LoginError(HTML::Mason::Commands::loc(@_)); TangentForLogin( $ARGS, results => $key ); } =head2 IntuitNextPage Attempt to figure out the path to which we should return the user after a tangent. The current request URL is used, or failing that, the C<WebURL> configuration variable. =cut sub IntuitNextPage { my $req_uri; # This includes any query parameters. Redirect will take care of making # it an absolute URL. if (RequestENV('REQUEST_URI')) { $req_uri = RequestENV('REQUEST_URI'); # collapse multiple leading slashes so the first part doesn't look like # a hostname of a schema-less URI $req_uri =~ s{^/+}{/}; } my $next = defined $req_uri ? $req_uri : RT->Config->Get('WebURL'); # sanitize $next my $uri = URI->new($next); # You get undef scheme with a relative uri like "/Search/Build.html" unless (!defined($uri->scheme) || $uri->scheme eq 'http' || $uri->scheme eq 'https') { $next = RT->Config->Get('WebURL'); } # Make sure we're logging in to the same domain # You can get an undef authority with a relative uri like "index.html" my $uri_base_url = URI->new(RT->Config->Get('WebBaseURL')); unless (!defined($uri->authority) || $uri->authority eq $uri_base_url->authority) { $next = RT->Config->Get('WebURL'); } return $next; } =head2 MaybeShowInstallModePage This function, called exclusively by RT's autohandler, dispatches a request to RT's Installation workflow, only if Install Mode is enabled in the configuration file. If it serves a page, it stops mason processing. Otherwise, mason just keeps running through the autohandler =cut sub MaybeShowInstallModePage { return unless RT->InstallMode; my $m = $HTML::Mason::Commands::m; if ( $m->base_comp->path =~ RT->Config->Get('WebNoAuthRegex') ) { $m->call_next(); } elsif ( $m->request_comp->path !~ m{^(/+)Install/} ) { RT::Interface::Web::Redirect( RT->Config->Get('WebURL') . "Install/index.html" ); } else { $m->call_next(); } $m->abort(); } =head2 MaybeShowNoAuthPage \%ARGS This function, called exclusively by RT's autohandler, dispatches a request to the page a user requested (but only if it matches the "noauth" regex. If it serves a page, it stops mason processing. Otherwise, mason just keeps running through the autohandler =cut sub MaybeShowNoAuthPage { my $ARGS = shift; my $m = $HTML::Mason::Commands::m; return unless $m->base_comp->path =~ RT->Config->Get('WebNoAuthRegex'); # Don't show the login page to logged in users Redirect(RT->Config->Get('WebURL')) if $m->base_comp->path eq '/NoAuth/Login.html' and _UserLoggedIn(); # If it's a noauth file, don't ask for auth. $m->comp( { base_comp => $m->request_comp }, $m->fetch_next, %$ARGS ); $m->abort; } =head2 MaybeRejectPrivateComponentRequest This function will reject calls to private components, like those under C</Elements>. If the requested path is a private component then we will abort with a C<403> error. =cut sub MaybeRejectPrivateComponentRequest { my $m = $HTML::Mason::Commands::m; my $path = $m->request_comp->path; # We do not check for dhandler here, because requesting our dhandlers # directly is okay. Mason will invoke the dhandler with a dhandler_arg of # 'dhandler'. if ($path =~ m{ / # leading slash ( Elements | _elements | # mobile UI Callbacks | Widgets | autohandler | # requesting this directly is suspicious l (_unsafe)? ) # loc component ( $ | / ) # trailing slash or end of path }xi) { $m->abort(403); } return; } sub InitializeMenu { $HTML::Mason::Commands::m->notes('menu', RT::Interface::Web::Menu->new()); $HTML::Mason::Commands::m->notes('page-menu', RT::Interface::Web::Menu->new()); $HTML::Mason::Commands::m->notes('page-widgets', RT::Interface::Web::Menu->new()); } =head2 ShowRequestedPage \%ARGS This function, called exclusively by RT's autohandler, dispatches a request to the page a user requested (making sure that unpriviled users can only see self-service pages. =cut sub ShowRequestedPage { my $ARGS = shift; my $m = $HTML::Mason::Commands::m; # Ensure that the cookie that we send is up-to-date, in case the # session-id has been modified in any way SendSessionCookie(); # precache all system level rights for the current user $HTML::Mason::Commands::session{CurrentUser}->PrincipalObj->HasRights( Object => RT->System ); if ( $HTML::Mason::Commands::r->path_info =~ m{^(/+)User/Prefs.html} ) { RT->Deprecated( Message => '/User/Prefs.html is deprecated', Instead => "/Prefs/AboutMe.html", Stack => 0, ); RT::Interface::Web::Redirect( RT->Config->Get('WebURL') . 'Prefs/AboutMe.html' ); } # If the user isn't privileged, they can only see SelfService unless ( $HTML::Mason::Commands::session{'CurrentUser'}->Privileged ) { # if the user is trying to access a ticket, redirect them 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'} ); } # otherwise, drop the user at the SelfService default page elsif ( $m->base_comp->path !~ RT->Config->Get('SelfServiceRegex') ) { RT::Interface::Web::Redirect( RT->Config->Get('WebURL') . "SelfService/" ); } # if user is in SelfService dir let him do anything else { $m->comp( { base_comp => $m->request_comp }, $m->fetch_next, %$ARGS ); } } else { $m->comp( { base_comp => $m->request_comp }, $m->fetch_next, %$ARGS ); } } sub AttemptExternalAuth { my $ARGS = shift; return unless ( RT->Config->Get('WebRemoteUserAuth') ); my $user = $ARGS->{user}; my $m = $HTML::Mason::Commands::m; my $logged_in_external_user = _UserLoggedIn() && $HTML::Mason::Commands::session{'WebExternallyAuthed'}; # If RT is configured for external auth, let's go through and get REMOTE_USER # Do we actually have a REMOTE_USER or equivalent? We only check auth if # 1) we have no logged in user, or 2) we have a user who is externally # authed. If we have a logged in user who is internally authed, don't # check remote user otherwise we may log them out. if (RT::Interface::Web::WebCanonicalizeInfo() and (not _UserLoggedIn() or $logged_in_external_user) ) { $user = RT::Interface::Web::WebCanonicalizeInfo(); my $load_method = RT->Config->Get('WebRemoteUserGecos') ? 'LoadByGecos' : 'Load'; my $next = RemoveNextPage($ARGS->{'next'}); $next = $next->{'url'} if ref $next; InstantiateNewSession() unless _UserLoggedIn; $HTML::Mason::Commands::session{'CurrentUser'} = RT::CurrentUser->new(); $HTML::Mason::Commands::session{'CurrentUser'}->$load_method($user); if ( RT->Config->Get('WebRemoteUserAutocreate') and not _UserLoggedIn() ) { # Create users on-the-fly my $UserObj = RT::User->new(RT->SystemUser); my ( $val, $msg ) = $UserObj->Create( %{ ref RT->Config->Get('UserAutocreateDefaultsOnLogin') ? RT->Config->Get('UserAutocreateDefaultsOnLogin') : {} }, Name => $user, Gecos => $user, ); if ($val) { # now get user specific information, to better create our user. my $new_user_info = RT::Interface::Web::WebRemoteUserAutocreateInfo($user); # set the attributes that have been defined. foreach my $attribute ( $UserObj->WritableAttributes, qw(Privileged Disabled) ) { $m->callback( Attribute => $attribute, User => $user, UserInfo => $new_user_info, CallbackName => 'NewUser', CallbackPage => '/autohandler' ); my $method = "Set$attribute"; $UserObj->$method( $new_user_info->{$attribute} ) if defined $new_user_info->{$attribute}; } $HTML::Mason::Commands::session{'CurrentUser'}->Load($user); } else { RT->Logger->error("Couldn't auto-create user '$user' when attempting WebRemoteUser: $msg"); AbortExternalAuth( Error => "UserAutocreateDefaultsOnLogin" ); } } if ( _UserLoggedIn() ) { $HTML::Mason::Commands::session{'WebExternallyAuthed'} = 1; $m->callback( %$ARGS, CallbackName => 'ExternalAuthSuccessfulLogin', CallbackPage => '/autohandler' ); # It is possible that we did a redirect to the login page, # if the external auth allows lack of auth through with no # REMOTE_USER set, instead of forcing a "permission # denied" message. Honor the $next. Redirect($next) if $next; # Unlike AttemptPasswordAuthentication below, we do not # force a redirect to / if $next is not set -- otherwise, # straight-up external auth would always redirect to / # when you first hit it. } else { # Couldn't auth with the REMOTE_USER provided because an RT # user doesn't exist and we're configured not to create one. RT->Logger->error("Couldn't find internal user for '$user' when attempting WebRemoteUser and RT is not configured for auto-creation. Refer to `perldoc $RT::BasePath/docs/authentication.pod` if you want to allow auto-creation."); AbortExternalAuth( Error => "NoInternalUser", User => $user, ); } } elsif ($logged_in_external_user) { # The logged in external user was deauthed by the auth system and we # should kick them out. AbortExternalAuth( Error => "Deauthorized" ); } elsif (not RT->Config->Get('WebFallbackToRTLogin')) { # Abort if we don't want to fallback internally AbortExternalAuth( Error => "NoRemoteUser" ); } } sub AbortExternalAuth { my %args = @_; my $error = $args{Error} ? "/Errors/WebRemoteUser/$args{Error}" : undef; my $m = $HTML::Mason::Commands::m; my $r = $HTML::Mason::Commands::r; _ForceLogout(); # Clear the decks, not that we should have partial content. $m->clear_buffer; $r->status(403); $m->comp($error, %args) if $error and $m->comp_exists($error); # Return a 403 Forbidden or we may fallback to a login page with no form $m->abort(403); } sub AttemptPasswordAuthentication { my $ARGS = shift; return unless defined $ARGS->{user} && defined $ARGS->{pass}; my $user_obj = RT::CurrentUser->new(); $user_obj->Load( $ARGS->{user} ); my $m = $HTML::Mason::Commands::m; my $remote_addr = RequestENV('REMOTE_ADDR'); unless ( $user_obj->id && $user_obj->IsPassword( $ARGS->{pass} ) ) { $RT::Logger->error("FAILED LOGIN for @{[$ARGS->{user}]} from $remote_addr"); $m->callback( %$ARGS, CallbackName => 'FailedLogin', CallbackPage => '/autohandler' ); return (0, HTML::Mason::Commands::loc('Your username or password is incorrect')); } else { $RT::Logger->info("Successful login for @{[$ARGS->{user}]} from $remote_addr"); # It's important to nab the next page from the session before we blow # the session away my $next = RemoveNextPage($ARGS->{'next'}); $next = $next->{'url'} if ref $next; InstantiateNewSession(); $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj; $m->callback( %$ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler', RedirectTo => \$next ); # Really the only time we don't want to redirect here is if we were # passed user and pass as query params in the URL. if ($next) { Redirect($next); } elsif ($ARGS->{'next'}) { # Invalid hash, but still wants to go somewhere, take them to / Redirect(RT->Config->Get('WebURL')); } return (1, HTML::Mason::Commands::loc('Logged in')); } } sub AttemptTokenAuthentication { my $ARGS = shift; my ($pass, $user) = ('', ''); if ((RequestENV('HTTP_AUTHORIZATION')||'') =~ /^token (.*)$/i) { $pass ||= $1; my ($user_obj, $token) = RT::Authen::Token->UserForAuthString($pass, $user); if ( $user_obj ) { # log in my $remote_addr = RequestENV('REMOTE_ADDR'); $RT::Logger->info("Successful login for @{[$user_obj->Name]} from $remote_addr using authentication token #@{[$token->Id]} (\"@{[$token->Description]}\")"); # It's important to nab the next page from the session before we blow # the session away my $next = RT::Interface::Web::RemoveNextPage($ARGS->{'next'}); $next = $next->{'url'} if ref $next; RT::Interface::Web::InstantiateNewSession(); $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj; # Really the only time we don't want to redirect here is if we were # passed user and pass as query params in the URL. if ($next) { RT::Interface::Web::Redirect($next); } elsif ($ARGS->{'next'}) { # Invalid hash, but still wants to go somewhere, take them to / RT::Interface::Web::Redirect(RT->Config->Get('WebURL')); } } } } =head2 LoadSessionFromCookie Load or setup a session cookie for the current user. =cut sub _SessionCookieName { my $cookiename = "RT_SID_" . RT->Config->Get('rtname'); $cookiename .= "." . RequestENV('SERVER_PORT') if RequestENV('SERVER_PORT'); return $cookiename; } sub LoadSessionFromCookie { my %cookies = CGI::Cookie->parse(RequestENV('HTTP_COOKIE')); my $cookiename = _SessionCookieName(); my $SessionCookie = ( $cookies{$cookiename} ? $cookies{$cookiename}->value : undef ); tie %HTML::Mason::Commands::session, 'RT::Interface::Web::Session', $SessionCookie; unless ( $SessionCookie && $HTML::Mason::Commands::session{'_session_id'} eq $SessionCookie ) { InstantiateNewSession(); } if ( int RT->Config->Get('AutoLogoff') ) { my $now = int( time / 60 ); my $last_update = $HTML::Mason::Commands::session{'_session_last_update'} || 0; if ( $last_update && ( $now - $last_update - RT->Config->Get('AutoLogoff') ) > 0 ) { InstantiateNewSession(); } # save session on each request when AutoLogoff is turned on $HTML::Mason::Commands::session{'_session_last_update'} = $now if $now != $last_update; } } sub InstantiateNewSession { tied(%HTML::Mason::Commands::session)->delete if tied(%HTML::Mason::Commands::session); tie %HTML::Mason::Commands::session, 'RT::Interface::Web::Session', undef; SendSessionCookie(); } sub SendSessionCookie { my $cookie = CGI::Cookie->new( -name => _SessionCookieName(), -value => $HTML::Mason::Commands::session{_session_id}, -path => RT->Config->Get('WebPath'), -secure => ( RT->Config->Get('WebSecureCookies') ? 1 : 0 ), -httponly => ( RT->Config->Get('WebHttpOnlyCookies') ? 1 : 0 ), ); $HTML::Mason::Commands::r->err_headers_out->{'Set-Cookie'} = $cookie->as_string; } =head2 GetWebURLFromRequest People may use different web urls instead of C<$WebURL> in config. Return the web url current user is using. =cut sub GetWebURLFromRequest { my $uri = URI->new( RT->Config->Get('WebURL') ); $uri->scheme(RequestENV('psgi.url_scheme') || 'http'); # [rt3.fsck.com #12716] Apache recommends use of $SERVER_HOST $uri->host( RequestENV('SERVER_HOST') || RequestENV('HTTP_HOST') || RequestENV('SERVER_NAME') ); $uri->port( RequestENV('SERVER_PORT') ); return "$uri"; # stringify to be consistent with WebURL in config } =head2 Redirect URL This routine tells the current user's browser to redirect to URL. Additionally, it unties the user's currently active session, helping to avoid A bug in Apache::Session 1.81 and earlier which clobbers sessions if we try to use a cached DBI statement handle twice at the same time. =cut sub Redirect { my $redir_to = shift; untie $HTML::Mason::Commands::session; my $uri = URI->new($redir_to); my $server_uri = URI->new( RT->Config->Get('WebURL') ); # Make relative URIs absolute from the server host and scheme $uri->scheme($server_uri->scheme) if not defined $uri->scheme; if (not defined $uri->host) { $uri->host($server_uri->host); $uri->port($server_uri->port); } # If the user is coming in via a non-canonical # hostname, don't redirect them to the canonical host, # it will just upset them (and invalidate their credentials) # don't do this if $RT::CanonicalizeRedirectURLs is true if ( !RT->Config->Get('CanonicalizeRedirectURLs') && $uri->host eq $server_uri->host && $uri->port eq $server_uri->port ) { my $env_uri = URI->new(GetWebURLFromRequest()); $uri->scheme($env_uri->scheme); $uri->host($env_uri->host); $uri->port($env_uri->port); } # not sure why, but on some systems without this call mason doesn't # set status to 302, but 200 instead and people see blank pages $HTML::Mason::Commands::r->status(302); # Perlbal expects a status message, but Mason's default redirect status # doesn't provide one. See also rt.cpan.org #36689. $HTML::Mason::Commands::m->redirect( $uri->canonical, "302 Found" ); $HTML::Mason::Commands::m->abort; } =head2 GetStaticHeaders return an arrayref of Headers (currently, Cache-Control and Expires). =cut sub GetStaticHeaders { my %args = @_; my $Visibility = 'private'; if ( ! defined $args{Time} ) { $args{Time} = 0; } elsif ( $args{Time} eq 'no-cache' ) { $args{Time} = 0; } elsif ( $args{Time} eq 'forever' ) { $args{Time} = 30 * 24 * 60 * 60; $Visibility = 'public'; } my $CacheControl = $args{Time} ? sprintf "max-age=%d, %s", $args{Time}, $Visibility : 'no-cache' ; my $expires = RT::Date->new(RT->SystemUser); $expires->SetToNow; $expires->AddSeconds( $args{Time} ) if $args{Time}; return [ Expires => $expires->RFC2616, 'Cache-Control' => $CacheControl, ]; } =head2 CacheControlExpiresHeaders set both Cache-Control and Expires http headers =cut sub CacheControlExpiresHeaders { Plack::Util::header_iter( GetStaticHeaders(@_), sub { my ( $key, $val ) = @_; $HTML::Mason::Commands::r->headers_out->{$key} = $val; } ); } =head2 StaticFileHeaders Send the browser a few headers to try to get it to (somewhat agressively) cache RT's static Javascript and CSS files. This routine could really use _accurate_ heuristics. (XXX TODO) =cut sub StaticFileHeaders { # remove any cookie headers -- if it is cached publicly, it # shouldn't include anyone's cookie! delete $HTML::Mason::Commands::r->err_headers_out->{'Set-Cookie'}; # Expire things in a month. CacheControlExpiresHeaders( Time => 'forever' ); } =head2 ComponentPathIsSafe PATH Takes C<PATH> and returns a boolean indicating that the user-specified partial component path is safe. Currently "safe" means that the path does not start with a dot (C<.>), does not contain a slash-dot C</.>, and does not contain any nulls. =cut sub ComponentPathIsSafe { my $self = shift; my $path = shift; return($path !~ m{(?:^|/)\.} and $path !~ m{\0}); } =head2 PathIsSafe Takes a C<< Path => path >> and returns a boolean indicating that the path is safely within RT's control or not. The path I<must> be relative. This function does not consult the filesystem at all; it is merely a logical sanity checking of the path. This explicitly does not handle symlinks; if you have symlinks in RT's webroot pointing outside of it, then we assume you know what you are doing. =cut sub PathIsSafe { my $self = shift; my %args = @_; my $path = $args{Path}; # Get File::Spec to clean up extra /s, ./, etc my $cleaned_up = File::Spec->canonpath($path); if (!defined($cleaned_up)) { $RT::Logger->info("Rejecting path that canonpath doesn't understand: $path"); return 0; } # Forbid too many ..s. We can't just sum then check because # "../foo/bar/baz" should be illegal even though it has more # downdirs than updirs. So as soon as we get a negative score # (which means "breaking out" of the top level) we reject the path. my @components = split '/', $cleaned_up; my $score = 0; for my $component (@components) { if ($component eq '..') { $score--; if ($score < 0) { $RT::Logger->info("Rejecting unsafe path: $path"); return 0; } } elsif ($component eq '.' || $component eq '') { # these two have no effect on $score } else { $score++; } } return 1; } =head2 SendStaticFile Takes a File => path and a Type => Content-type If Type isn't provided and File is an image, it will figure out a sane Content-type, otherwise it will send application/octet-stream Will set caching headers using StaticFileHeaders =cut sub SendStaticFile { my $self = shift; my %args = @_; my $file = $args{File}; my $type = $args{Type}; my $relfile = $args{RelativeFile}; if (defined($relfile) && !$self->PathIsSafe(Path => $relfile)) { $HTML::Mason::Commands::r->status(400); $HTML::Mason::Commands::m->abort; } $self->StaticFileHeaders(); unless ($type) { if ( $file =~ /\.(gif|png|jpe?g)$/i ) { $type = "image/$1"; $type =~ s/jpg/jpeg/gi; } $type ||= "application/octet-stream"; } $HTML::Mason::Commands::r->content_type($type); open( my $fh, '<', $file ) or die "couldn't open file: $!"; binmode($fh); { local $/ = \16384; $HTML::Mason::Commands::m->out($_) while (<$fh>); $HTML::Mason::Commands::m->flush_buffer; } close $fh; } sub MobileClient { my $self = shift; return undef unless RT->Config->Get('ShowMobileSite'); if ((RequestENV('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; } } sub StripContent { my %args = @_; my $content = $args{Content}; return '' unless $content; # Make the content have no 'weird' newlines in it $content =~ s/\r+\n/\n/g; my $return_content = $content; my $html = $args{ContentType} && $args{ContentType} eq "text/html"; my $sigonly = $args{StripSignature}; # massage content to easily detect if there's any real content $content =~ s/\s+//g; # yes! remove all the spaces if ( $html ) { # remove html version of spaces and newlines $content =~ s! !!g; $content =~ s!<br/?>!!g; } # Filter empty content when type is text/html return '' if $html && $content !~ /\S/; # If we aren't supposed to strip the sig, just bail now. return $return_content unless $sigonly; # Find the signature my $sig = $args{'CurrentUser'}->UserObj->Signature || ''; $sig =~ s/\s+//g; # Check for plaintext sig return '' if not $html and $content =~ /^(--)?\Q$sig\E$/; # Check for html-formatted sig; we don't use EscapeHTML here # because we want to precisely match the escapting that FCKEditor # uses. $sig =~ s/&/&/g; $sig =~ s/</</g; $sig =~ s/>/>/g; $sig =~ s/"/"/g; $sig =~ s/'/'/g; return '' if $html and $content =~ m{^(?:<p>)?(--)?\Q$sig\E(?:</p>)?$}s; # Pass it through return $return_content; } sub DecodeARGS { my $ARGS = shift; # Later in the code we use # $m->comp( { base_comp => $m->request_comp }, $m->fetch_next, %ARGS ); # instead of $m->call_next to avoid problems with UTF8 keys in # arguments. Specifically, the call_next method pass through # original arguments, which are still the encoded bytes, not # characters. "{ base_comp => $m->request_comp }" is copied from # mason's source to get the same results as we get from call_next # method; this feature is not documented. %{$ARGS} = map { # if they've passed multiple values, they'll be an array. if they've # passed just one, a scalar whatever they are, mark them as utf8 my $type = ref($_); ( !$type ) ? Encode::decode( 'UTF-8', $_, Encode::FB_PERLQQ ) : ( $type eq 'ARRAY' ) ? [ map { ref($_) ? $_ : Encode::decode( 'UTF-8', $_, Encode::FB_PERLQQ ) } @$_ ] : ( $type eq 'HASH' ) ? { map { ref($_) ? $_ : Encode::decode( 'UTF-8', $_, Encode::FB_PERLQQ ) } %$_ } : $_ } %$ARGS; } sub PreprocessTimeUpdates { my $ARGS = shift; # This code canonicalizes time inputs in hours into minutes foreach my $field ( keys %$ARGS ) { next unless $field =~ /^(.*)-TimeUnits$/i && $ARGS->{$1}; my $local = $1; $ARGS->{$local} =~ s{\b (?: (\d+) \s+ )? (\d+)/(\d+) \b} {($1 || 0) + $3 ? $2 / $3 : 0}xe; if ( $ARGS->{$field} && $ARGS->{$field} =~ /hours/i ) { $ARGS->{$local} *= 60; } delete $ARGS->{$field}; } } sub MaybeEnableSQLStatementLog { my $log_sql_statements = RT->Config->Get('StatementLog'); if ($log_sql_statements) { $RT::Handle->ClearSQLStatementLog; $RT::Handle->LogSQLStatements(1); } } my $role_cache_time = time; sub MaybeRebuildCustomRolesCache { my $needs_update = RT->System->CustomRoleCacheNeedsUpdate; if ($needs_update > $role_cache_time) { RT::CustomRoles->RegisterRoles; $role_cache_time = $needs_update; } } sub LogRecordedSQLStatements { my %args = @_; my $log_sql_statements = RT->Config->Get('StatementLog'); return unless ($log_sql_statements); my @log = $RT::Handle->SQLStatementLog; $RT::Handle->ClearSQLStatementLog; $RT::Handle->AddRequestToHistory({ %{ $args{RequestData} }, Queries => \@log, }); for my $stmt (@log) { my ( $time, $sql, $bind, $duration ) = @{$stmt}; my @bind; if ( ref $bind ) { @bind = @{$bind}; } else { # Older DBIx-SB $duration = $bind; } $RT::Logger->log( level => $log_sql_statements, message => "SQL(" . sprintf( "%.6f", $duration ) . "s): $sql;" . ( @bind ? " [ bound values: @{[map{ defined $_ ? qq|'$_'| : 'undef'} @bind]} ]" : "" ) ); } } my $_has_validated_web_config = 0; sub ValidateWebConfig { my $self = shift; # do this once per server instance, not once per request return if $_has_validated_web_config; $_has_validated_web_config = 1; my $port = RequestENV('SERVER_PORT'); my $host = RequestENV('HTTP_X_FORWARDED_HOST') || RequestENV('HTTP_X_FORWARDED_SERVER') || RequestENV('HTTP_HOST') || RequestENV('SERVER_NAME'); ($host, $port) = ($1, $2) if $host =~ /^(.*?):(\d+)$/; if ( $port != RT->Config->Get('WebPort') and not RequestENV('rt.explicit_port')) { $RT::Logger->warn("The requested port ($port) does NOT match the configured WebPort ($RT::WebPort). " ."Perhaps you should Set(\$WebPort, $port); in RT_SiteConfig.pm, " ."otherwise your internal hyperlinks may be broken."); } if ( $host ne RT->Config->Get('WebDomain') ) { $RT::Logger->warn("The requested host ($host) does NOT match the configured WebDomain ($RT::WebDomain). " ."Perhaps you should Set(\$WebDomain, '$host'); in RT_SiteConfig.pm, " ."otherwise your internal hyperlinks may be broken."); } # Unfortunately, there is no reliable way to get the _path_ that was # requested at the proxy level; simply disable this warning if we're # proxied and there's a mismatch. my $proxied = RequestENV('HTTP_X_FORWARDED_HOST') || RequestENV('HTTP_X_FORWARDED_SERVER'); if (RequestENV('SCRIPT_NAME') ne RT->Config->Get('WebPath') and not $proxied) { $RT::Logger->warn("The requested path ('" . RequestENV('SCRIPT_NAME') . "') does NOT match the configured WebPath ($RT::WebPath). " ."Perhaps you should Set(\$WebPath, '" . RequestENV('SCRIPT_NAME') . "' in RT_SiteConfig.pm, " ."otherwise your internal hyperlinks may be broken."); } } sub ComponentRoots { my $self = shift; my %args = ( Names => 0, @_ ); my @roots; if (defined $HTML::Mason::Commands::m) { @roots = $HTML::Mason::Commands::m->interp->comp_root_array; } else { @roots = ( [ local => $RT::MasonLocalComponentRoot ], (map {[ "plugin-".$_->Name => $_->ComponentRoot ]} @{RT->Plugins}), [ standard => $RT::MasonComponentRoot ] ); } @roots = map { $_->[1] } @roots unless $args{Names}; return @roots; } sub StaticRoots { my $self = shift; my @static = ( $RT::LocalStaticPath, (map { $_->StaticDir } @{RT->Plugins}), $RT::StaticPath, ); return grep { $_ and -d $_ } @static; } our %IS_WHITELISTED_COMPONENT = ( # The RSS feed embeds an auth token in the path, but query # information for the search. Because it's a straight-up read, in # addition to embedding its own auth, it's fine. '/NoAuth/rss/dhandler' => 1, # 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 chart) or bookmark a result page. '/Search/Results.html' => 1, '/Search/Simple.html' => 1, '/m/tickets/search' => 1, '/Search/Chart.html' => 1, '/User/Search.html' => 1, # This page takes Attachment and Transaction argument to figure # out what to show, but it's read only and will deny information if you # don't have ShowOutgoingEmail. '/Ticket/ShowEmailRecord.html' => 1, ); # Whitelist arguments that do not indicate an effectful request. our @GLOBAL_WHITELISTED_ARGS = ( # For example, "id" is acceptable because that is how RT retrieves a # record. 'id', # If they have a results= from MaybeRedirectForResults, that's also fine. 'results', # The homepage refresh, which uses the Refresh header, doesn't send # a referer in most browsers; whitelist the one parameter it reloads # with, HomeRefreshInterval, which is safe 'HomeRefreshInterval', # The NotMobile flag is fine for any page; it's only used to toggle a flag # in the session related to which interface you get. 'NotMobile', ); our %WHITELISTED_COMPONENT_ARGS = ( # SavedSearchLoad - This happens when you middle-(or ⌘ )-click "Edit" for a saved search on # the homepage. It's not going to do any damage # NewQuery - This is simply to clear the search query '/Search/Build.html' => ['SavedSearchLoad','NewQuery'], # Happens if you try and reply to a message in the ticket history or click a number # of options on a tickets Action menu '/Ticket/Update.html' => ['QuoteTransaction', 'Action', 'DefaultStatus'], # Action->Extract Article on a ticket's menu '/Articles/Article/ExtractIntoClass.html' => ['Ticket'], # Only affects display '/Ticket/Display.html' => ['HideUnsetFields'], '/Admin/Tools/RightsInspector.html' => ['Principal', 'Object', 'Right'], '/Helpers/RightsInspector/Search' => ['principal', 'object', 'right', 'continueAfter'], ); # Components which are blacklisted from automatic, argument-based whitelisting. # These pages are not idempotent when called with just an id. our %IS_BLACKLISTED_COMPONENT = ( # Takes only id and toggles bookmark state '/Helpers/Toggle/TicketBookmark' => 1, ); sub IsCompCSRFWhitelisted { my $comp = shift; my $ARGS = shift; return 1 if $IS_WHITELISTED_COMPONENT{$comp}; my %args = %{ $ARGS }; # If the user specifies a *correct* user and pass then they are # golden. This acts on the presumption that external forms may # hardcode a username and password -- if a malicious attacker knew # both already, CSRF is the least of your problems. my $AllowLoginCSRF = not RT->Config->Get('RestrictLoginReferrer'); if ($AllowLoginCSRF and defined($args{user}) and defined($args{pass})) { my $user_obj = RT::CurrentUser->new(); $user_obj->Load($args{user}); return 1 if $user_obj->id && $user_obj->IsPassword($args{pass}); delete $args{user}; delete $args{pass}; } # Some pages aren't idempotent even with safe args like id; blacklist # them from the automatic whitelisting below. return 0 if $IS_BLACKLISTED_COMPONENT{$comp}; if ( my %csrf_config = RT->Config->Get('ReferrerComponents') ) { if (exists $csrf_config{$comp}) { my $value = $csrf_config{$comp}; if ( ref $value eq 'ARRAY' ) { delete $args{$_} for @$value; return %args ? 0 : 1; } else { return $value ? 1 : 0; } } } return AreCompCSRFParametersWhitelisted($comp, \%args); } sub AreCompCSRFParametersWhitelisted { my $sub = shift; my $ARGS = shift; my %leftover_args = %{ $ARGS }; # Join global whitelist and component-specific whitelist my @whitelisted_args = (@GLOBAL_WHITELISTED_ARGS, @{ $WHITELISTED_COMPONENT_ARGS{$sub} || [] }); for my $arg (@whitelisted_args) { delete $leftover_args{$arg}; } # If there are no arguments, then it's likely to be an idempotent # request, which are not susceptible to CSRF return !%leftover_args; } sub IsRefererCSRFWhitelisted { my $referer = _NormalizeHost(shift); my $base_url = _NormalizeHost(RT->Config->Get('WebBaseURL')); $base_url = $base_url->host_port; my $configs; for my $config ( $base_url, RT->Config->Get('ReferrerWhitelist') ) { push @$configs,$config; 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); } =head3 _NormalizeHost Takes a URI and creates a URI object that's been normalized to handle common problems such as localhost vs 127.0.0.1 =cut sub _NormalizeHost { my $uri= URI->new(shift); $uri->host('127.0.0.1') if $uri->host eq 'localhost'; return $uri; } sub IsPossibleCSRF { my $ARGS = shift; # If first request on this session is to a REST endpoint, then # whitelist the REST endpoints -- and explicitly deny non-REST # endpoints. We do this because using a REST cookie in a browser # would open the user to CSRF attacks to the REST endpoints. my $path = $HTML::Mason::Commands::r->path_info; $HTML::Mason::Commands::session{'REST'} = $path =~ m{^/+REST/\d+\.\d+(/|$)} unless defined $HTML::Mason::Commands::session{'REST'}; if ($HTML::Mason::Commands::session{'REST'}) { return 0 if $path =~ m{^/+REST/\d+\.\d+(/|$)}; my $why = <<EOT; This login session belongs to a REST client, and cannot be used to access non-REST interfaces of RT for security reasons. EOT my $details = <<EOT; Please log out and back in to obtain a session for normal browsing. If you understand the security implications, disabling RT's CSRF protection will remove this restriction. EOT chomp $details; HTML::Mason::Commands::Abort( $why, Details => $details ); } return 0 if IsCompCSRFWhitelisted( $HTML::Mason::Commands::m->request_comp->path, $ARGS ); # if there is no Referer header then assume the worst return (1, "your browser did not supply a Referrer header", # loc ) if !RequestENV('HTTP_REFERER'); my ($whitelisted, $browser, $configs) = IsRefererCSRFWhitelisted(RequestENV('HTTP_REFERER')); return 0 if $whitelisted; if ( @$configs > 1 ) { return (1, "the Referrer header supplied by your browser ([_1]) is not allowed by RT's configured hostname ([_2]) or whitelisted hosts ([_3])", # loc $browser->host_port, shift @$configs, join(', ', @$configs) ); } return (1, "the Referrer header supplied by your browser ([_1]) is not allowed by RT's configured hostname ([_2])", # loc $browser->host_port, $configs->[0]); } sub ExpandCSRFToken { my $ARGS = shift; my $token = delete $ARGS->{CSRF_Token}; return unless $token; my $data = $HTML::Mason::Commands::session{'CSRF'}{$token}; return unless $data; return unless $data->{path} eq $HTML::Mason::Commands::r->path_info; my $user = $HTML::Mason::Commands::session{'CurrentUser'}->UserObj; return unless $user->ValidateAuthString( $data->{auth}, $token ); %{$ARGS} = %{$data->{args}}; $HTML::Mason::Commands::DECODED_ARGS = $ARGS; # We explicitly stored file attachments with the request, but not in # the session yet, as that would itself be an attack. Put them into # the session now, so they'll be visible. if ($data->{attach}) { my $filename = $data->{attach}{filename}; my $mime = $data->{attach}{mime}; $HTML::Mason::Commands::session{'Attachments'}{$ARGS->{'Token'}||''}{$filename} = $mime; } return 1; } sub StoreRequestToken { my $ARGS = shift; my $token = Digest::MD5::md5_hex(time . {} . $$ . rand(1024)); my $user = $HTML::Mason::Commands::session{'CurrentUser'}->UserObj; my $data = { auth => $user->GenerateAuthString( $token ), path => $HTML::Mason::Commands::r->path_info, args => $ARGS, }; if ($ARGS->{Attach}) { my $attachment = HTML::Mason::Commands::MakeMIMEEntity( AttachmentFieldName => 'Attach' ); my $file_path = delete $ARGS->{'Attach'}; # This needs to be decoded because the value is a reference; # hence it was not decoded along with all of the standard # arguments in DecodeARGS $data->{attach} = { filename => Encode::decode("UTF-8", "$file_path"), mime => $attachment, }; } $HTML::Mason::Commands::session{'CSRF'}->{$token} = $data; $HTML::Mason::Commands::session{'i'}++; return $token; } sub MaybeShowInterstitialCSRFPage { my $ARGS = shift; return unless RT->Config->Get('RestrictReferrer'); # Deal with the form token provided by the interstitial, which lets # browsers which never set referer headers still use RT, if # painfully. This blows values into ARGS return if ExpandCSRFToken($ARGS); my ($is_csrf, $msg, @loc) = IsPossibleCSRF($ARGS); return if !$is_csrf; $RT::Logger->notice("Possible CSRF: ".RT::CurrentUser->new->loc($msg, @loc)); my $token = StoreRequestToken($ARGS); $HTML::Mason::Commands::m->comp( '/Elements/CSRF', OriginalURL => RT->Config->Get('WebBaseURL') . RT->Config->Get('WebPath') . $HTML::Mason::Commands::r->path_info, Reason => HTML::Mason::Commands::loc( $msg, @loc ), Token => $token, ); # Calls abort, never gets here } our @POTENTIAL_PAGE_ACTIONS = ( qr'/Ticket/Create.html' => "create a ticket", # loc qr'/Ticket/' => "update a ticket", # loc qr'/Admin/' => "modify RT's configuration", # loc qr'/Approval/' => "update an approval", # loc qr'/Articles/' => "update an article", # loc qr'/Dashboards/' => "modify a dashboard", # loc qr'/m/ticket/' => "update a ticket", # loc qr'Prefs' => "modify your preferences", # loc qr'/Search/' => "modify or access a search", # loc qr'/SelfService/Create' => "create a ticket", # loc qr'/SelfService/' => "update a ticket", # loc ); sub PotentialPageAction { my $page = shift; my @potentials = @POTENTIAL_PAGE_ACTIONS; while (my ($pattern, $result) = splice @potentials, 0, 2) { return HTML::Mason::Commands::loc($result) if $page =~ $pattern; } return ""; } =head2 RewriteInlineImages PARAMHASH Turns C<< <img src="cid:..."> >> elements in HTML into working images pointing back to RT's stored copy. Takes the following parameters: =over 4 =item Content Scalar ref of the HTML content to rewrite. Modified in place to support the most common use-case. =item Attachment The L<RT::Attachment> object from which the Content originates. =item Related (optional) Array ref of related L<RT::Attachment> objects to use for C<Content-ID> matching. Defaults to the result of the C<Siblings> method on the passed Attachment. =item AttachmentPath (optional) The base path to use when rewriting C<src> attributes. Defaults to C< $WebPath/Ticket/Attachment > =back In scalar context, returns the number of elements rewritten. In list content, returns the attachments IDs referred to by the rewritten <img> elements, in the order found. There may be duplicates. =cut sub RewriteInlineImages { my %args = ( Content => undef, Attachment => undef, Related => undef, AttachmentPath => RT->Config->Get('WebPath')."/Ticket/Attachment", @_ ); return unless defined $args{Content} and ref $args{Content} eq 'SCALAR' and defined $args{Attachment}; my $related_part = $args{Attachment}->Closest("multipart/related") or return; $args{Related} ||= $related_part->Children->ItemsArrayRef; return unless @{$args{Related}}; my $content = $args{'Content'}; my @rewritten; require HTML::RewriteAttributes::Resources; $$content = HTML::RewriteAttributes::Resources->rewrite($$content, sub { my $cid = shift; my %meta = @_; return $cid unless lc $meta{tag} eq 'img' and lc $meta{attr} eq 'src' and $cid =~ s/^cid://i; for my $attach (@{$args{Related}}) { if (($attach->GetHeader('Content-ID') || '') =~ /^(<)?\Q$cid\E(?(1)>)$/) { push @rewritten, $attach->Id; return "$args{AttachmentPath}/" . $attach->TransactionId . '/' . $attach->Id; } } # No attachments means this is a bogus CID. Just pass it through. RT->Logger->debug(qq[Found bogus inline image src="cid:$cid"]); return "cid:$cid"; }); return @rewritten; } =head2 GetCustomFieldInputName(CustomField => $cf_object, Object => $object, Grouping => $grouping_name) Returns the standard custom field input name; this is complementary to L</_ParseObjectCustomFieldArgs>. Takes the following arguments: =over =item CustomField => I<L<RT::CustomField> object> Required. =item Object => I<object> The object that the custom field is applied to; optional. If omitted, defaults to a new object of the appropriate class for the custom field. =item Grouping => I<CF grouping> The grouping that the custom field is being rendered in. Groupings allow a custom field to appear in more than one location per form. =back =cut sub GetCustomFieldInputName { my %args = ( CustomField => undef, Object => undef, Grouping => undef, @_, ); my $name = GetCustomFieldInputNamePrefix(%args); if ( $args{CustomField}->Type eq 'Select' ) { if ( $args{CustomField}->RenderType eq 'List' and $args{CustomField}->SingleValue ) { $name .= 'Value'; } else { $name .= 'Values'; } } elsif ( $args{CustomField}->Type =~ /^(?:Binary|Image)$/ ) { $name .= 'Upload'; } elsif ( $args{CustomField}->Type =~ /^(?:Date|DateTime|Text|Wikitext)$/ ) { $name .= 'Values'; } else { if ( $args{CustomField}->SingleValue ) { $name .= 'Value'; } else { $name .= 'Values'; } } return $name; } =head2 GetCustomFieldInputNamePrefix(CustomField => $cf_object, Object => $object, Grouping => $grouping_name) Returns the standard custom field input name prefix(without "Value" or alike suffix) =cut sub GetCustomFieldInputNamePrefix { my %args = ( CustomField => undef, Object => undef, Grouping => undef, @_, ); my $prefix = join '-', 'Object', ref( $args{Object} ) || $args{CustomField}->ObjectTypeFromLookupType, ( $args{Object} && $args{Object}->id ? $args{Object}->id : '' ), 'CustomField' . ( $args{Grouping} ? ":$args{Grouping}" : '' ), $args{CustomField}->id, ''; return $prefix; } sub RequestENV { my $name = shift; my $env = $HTML::Mason::Commands::m->cgi_object->env; return $name ? $env->{$name} : $env; } sub ClientIsIE { # IE 11.0 dropped "MSIE", so we can't use that alone return RequestENV('HTTP_USER_AGENT') =~ m{MSIE|Trident/} ? 1 : 0; } package HTML::Mason::Commands; use vars qw/$r $m %session/; use Scalar::Util qw(blessed); sub Menu { return $HTML::Mason::Commands::m->notes('menu'); } sub SearchResultsPageMenu { return $HTML::Mason::Commands::m->notes('search-results-page-menu'); } sub PageMenu { return $HTML::Mason::Commands::m->notes('page-menu'); } sub PageWidgets { return $HTML::Mason::Commands::m->notes('page-widgets'); } sub RenderMenu { my %args = (toplevel => 1, parent_id => '', depth => 0, @_); return unless $args{'menu'}; my ($menu, $depth, $toplevel, $id, $parent_id) = @args{qw(menu depth toplevel id parent_id)}; my $interp = $m->interp; my $web_path = RT->Config->Get('WebPath'); my $res = ''; $res .= ' ' x $depth; $res .= '<ul'; $res .= ' id="'. $interp->apply_escapes($id, 'h') .'"' if $id; my $class = $args{class} // ''; $class .= ' toplevel' if $toplevel; $res .= " class='$class'"; $res .= ">\n"; for my $child ($menu->children) { $res .= ' 'x ($depth+1); my $item_id = lc(($parent_id? "$parent_id-" : "") .$child->key); $item_id =~ s/\s/-/g; my $eitem_id = $interp->apply_escapes($item_id, 'h'); $res .= qq{<li id="li-$eitem_id"}; my @classes; push @classes, 'has-children' if $child->has_children; push @classes, 'active' if $child->active; $res .= ' class="'. join( ' ', @classes ) .'"' if @classes; $res .= '>'; if ( my $tmp = $child->raw_html ) { $res .= $tmp; } else { $res .= qq{<a id="$eitem_id" class="menu-item}; if ( $tmp = $child->class ) { $res .= ' '. $interp->apply_escapes($tmp, 'h'); } $res .= ' btn' if ( defined $id && ( $id eq 'page-menu' || $id eq 'search-results-page-menu' ) ); $res .= '"'; my $path = $child->path; my $url = (not $path or $path =~ m{^\w+:/}) ? $path : $web_path . $path; $url ||= "#"; $res .= ' href="'. $interp->apply_escapes($url, 'h') .'"'; if ( $tmp = $child->target ) { $res .= ' target="'. $interp->apply_escapes($tmp, 'h') .'"' } if ($child->attributes) { for my $key (keys %{$child->attributes}) { my ($name, $value) = map { $interp->apply_escapes($_, 'h') } $key, $child->attributes->{$key}; $res .= " $name=\"$value\""; } } $res .= '>'; if ( $child->escape_title ) { $res .= $interp->apply_escapes($child->title, 'h'); } else { $res .= $child->title; } $res .= '</a>'; } if ( $child->has_children ) { $res .= "\n"; $res .= RenderMenu( menu => $child, toplevel => 0, parent_id => $item_id, depth => $depth+1, return => 1, ); $res .= "\n"; $res .= ' ' x ($depth+1); } $res .= "</li>\n"; } $res .= ' ' x $depth; $res .= '</ul>'; return $res if $args{'return'}; $m->print($res); return ''; } =head2 loc ARRAY loc is a nice clean global routine which calls $session{'CurrentUser'}->loc() with whatever it's called with. If there is no $session{'CurrentUser'}, it creates a temporary user, so we have something to get a localisation handle through =cut sub loc { if ( $session{'CurrentUser'} && UNIVERSAL::can( $session{'CurrentUser'}, 'loc' ) ) { return ( $session{'CurrentUser'}->loc(@_) ); } elsif ( my $u = eval { RT::CurrentUser->new(); } ) { return ( $u->loc(@_) ); } else { # pathetic case -- SystemUser is gone. return $_[0]; } } =head2 loc_fuzzy STRING loc_fuzzy is for handling localizations of messages that may already contain interpolated variables, typically returned from libraries outside RT's control. It takes the message string and extracts the variable array automatically by matching against the candidate entries inside the lexicon file. =cut sub loc_fuzzy { my $msg = shift; if ( $session{'CurrentUser'} && UNIVERSAL::can( $session{'CurrentUser'}, 'loc' ) ) { return ( $session{'CurrentUser'}->loc_fuzzy($msg) ); } else { my $u = RT::CurrentUser->new( RT->SystemUser->Id ); return ( $u->loc_fuzzy($msg) ); } } # Error - calls Error and aborts sub Abort { my $why = shift; my %args = @_; $args{Code} //= HTTP::Status::HTTP_OK; $r->headers_out->{'Status'} = $args{Code} . ' ' . HTTP::Status::status_message($args{Code}); if ( $session{'ErrorDocument'} && $session{'ErrorDocumentType'} ) { $r->content_type( $session{'ErrorDocumentType'} ); $m->comp( $session{'ErrorDocument'}, Why => $why, %args ); $m->abort; } else { $m->comp( "/Elements/Error", Why => $why, %args ); $m->abort; } } sub MaybeRedirectForResults { my %args = ( Path => $HTML::Mason::Commands::m->request_comp->path, Arguments => {}, Anchor => undef, Actions => undef, Force => 0, @_ ); my $has_actions = $args{'Actions'} && grep( defined, @{ $args{'Actions'} } ); return unless $has_actions || $args{'Force'}; my %arguments = %{ $args{'Arguments'} }; if ( $has_actions ) { my $key = Digest::MD5::md5_hex( rand(1024) ); push @{ $session{"Actions"}{ $key } ||= [] }, @{ $args{'Actions'} }; $session{'i'}++; $arguments{'results'} = $key; } $args{'Path'} =~ s!^/+!!; my $url = RT->Config->Get('WebURL') . $args{Path}; if ( keys %arguments ) { $url .= '?'. $m->comp( '/Elements/QueryString', %arguments ); } if ( $args{'Anchor'} ) { $url .= "#". $args{'Anchor'}; } return RT::Interface::Web::Redirect($url); } =head2 MaybeRedirectToApproval Path => 'path', Whitelist => REGEX, ARGSRef => HASHREF If the ticket specified by C<< $ARGSRef->{id} >> is an approval ticket, redirect to the approvals display page, preserving any arguments. C<Path>s matching C<Whitelist> are let through. This is a no-op if the C<ForceApprovalsView> option isn't enabled. =cut sub MaybeRedirectToApproval { my %args = ( Path => $HTML::Mason::Commands::m->request_comp->path, ARGSRef => {}, Whitelist => undef, @_ ); return unless RT::Interface::Web::RequestENV('REQUEST_METHOD') eq 'GET'; my $id = $args{ARGSRef}->{id}; if ( $id and RT->Config->Get('ForceApprovalsView') and not $args{Path} =~ /$args{Whitelist}/) { my $ticket = RT::Ticket->new( $session{'CurrentUser'} ); $ticket->Load($id); if ($ticket and $ticket->id and lc($ticket->Type) eq 'approval') { MaybeRedirectForResults( Path => "/Approvals/Display.html", Force => 1, Anchor => $args{ARGSRef}->{Anchor}, Arguments => $args{ARGSRef}, ); } } } =head2 CreateTicket ARGS Create a new ticket, using Mason's %ARGS. returns @results. =cut sub CreateTicket { my %ARGS = (@_); my (@Actions); my $current_user = $session{'CurrentUser'}; my $Ticket = delete $ARGS{TicketObj} || RT::Ticket->new( $current_user ); my $Queue = RT::Queue->new( $current_user ); unless ( $Queue->Load( $ARGS{'Queue'} ) ) { Abort('Queue not found', Code => HTTP::Status::HTTP_NOT_FOUND); } unless ( $Queue->CurrentUserHasRight('CreateTicket') ) { Abort('You have no permission to create tickets in that queue.', Code => HTTP::Status::HTTP_FORBIDDEN); } my $due; if ( defined $ARGS{'Due'} and $ARGS{'Due'} =~ /\S/ ) { $due = RT::Date->new( $current_user ); $due->Set( Format => 'unknown', Value => $ARGS{'Due'} ); } my $starts; if ( defined $ARGS{'Starts'} and $ARGS{'Starts'} =~ /\S/ ) { $starts = RT::Date->new( $current_user ); $starts->Set( Format => 'unknown', Value => $ARGS{'Starts'} ); } my $sigless = RT::Interface::Web::StripContent( Content => $ARGS{Content}, ContentType => $ARGS{ContentType}, StripSignature => 1, CurrentUser => $current_user, ); my $date_now = RT::Date->new( $current_user ); $date_now->SetToNow; my $MIMEObj = MakeMIMEEntity( Subject => $ARGS{'Subject'}, From => $ARGS{'From'} || $current_user->EmailAddress, To => $ARGS{'To'} || $Queue->CorrespondAddress || RT->Config->Get('CorrespondAddress'), Cc => $ARGS{'Cc'}, Date => $date_now->RFC2822(Timezone => 'user'), Body => $sigless, Type => $ARGS{'ContentType'}, Interface => RT::Interface::Web::MobileClient() ? 'Mobile' : 'Web', ); my @attachments; if ( my $tmp = $session{'Attachments'}{ $ARGS{'Token'} || '' } ) { push @attachments, grep $_, map $tmp->{$_}, sort keys %$tmp; delete $session{'Attachments'}{ $ARGS{'Token'} || '' } unless $ARGS{'KeepAttachments'} or $Ticket->{DryRun}; $session{'Attachments'} = $session{'Attachments'} if @attachments; } if ( $ARGS{'Attachments'} ) { push @attachments, grep $_, map $ARGS{Attachments}->{$_}, sort keys %{ $ARGS{'Attachments'} }; } if ( @attachments ) { $MIMEObj->make_multipart; $MIMEObj->add_part( $_ ) foreach @attachments; } for my $argument (qw(Encrypt Sign)) { if ( defined $ARGS{ $argument } ) { $MIMEObj->head->replace( "X-RT-$argument" => $ARGS{$argument} ? 1 : 0 ); } } my %create_args = ( Type => $ARGS{'Type'} || 'ticket', Queue => $ARGS{'Queue'}, SLA => $ARGS{'SLA'}, InitialPriority => $ARGS{'InitialPriority'}, FinalPriority => $ARGS{'FinalPriority'}, TimeLeft => $ARGS{'TimeLeft'}, TimeEstimated => $ARGS{'TimeEstimated'}, TimeWorked => $ARGS{'TimeWorked'}, Subject => $ARGS{'Subject'}, Status => $ARGS{'Status'}, Due => $due ? $due->ISO : undef, Starts => $starts ? $starts->ISO : undef, MIMEObj => $MIMEObj, SquelchMailTo => $ARGS{'SquelchMailTo'}, TransSquelchMailTo => $ARGS{'TransSquelchMailTo'}, (map { $_ => $ARGS{$_} } $Queue->Roles), # note: name change Requestor => $ARGS{'Requestors'}, ); my @txn_squelch; foreach my $type (qw(Requestor Cc AdminCc)) { push @txn_squelch, map $_->address, Email::Address->parse( $create_args{$type} ) if grep $_ eq $type || $_ eq ( $type . 's' ), @{ $ARGS{'SkipNotification'} || [] }; } foreach my $role (grep { /^RT::CustomRole-\d+$/ } @{ $ARGS{'SkipNotification'} || [] }) { push @txn_squelch, map $_->address, Email::Address->parse( $create_args{$role} ); } push @{$create_args{TransSquelchMailTo}}, @txn_squelch; if ( $ARGS{'AttachTickets'} ) { require RT::Action::SendEmail; RT::Action::SendEmail->AttachTickets( RT::Action::SendEmail->AttachTickets, ref $ARGS{'AttachTickets'} ? @{ $ARGS{'AttachTickets'} } : ( $ARGS{'AttachTickets'} ) ); } my %cfs = ProcessObjectCustomFieldUpdatesForCreate( ARGSRef => \%ARGS, ContextObject => $Queue, ); my %links = ProcessLinksForCreate( ARGSRef => \%ARGS ); my ( $id, $Trans, $ErrMsg ) = $Ticket->Create(%create_args, %links, %cfs); unless ($id) { Abort($ErrMsg); } push( @Actions, split( "\n", $ErrMsg ) ); unless ( $Ticket->CurrentUserHasRight('ShowTicket') ) { Abort( "No permission to view newly created ticket #" . $Ticket->id . ".", Code => HTTP::Status::HTTP_FORBIDDEN ); } return ( $Ticket, @Actions ); } =head2 LoadTicket id Takes a ticket id as its only variable. if it's handed an array, it takes the first value. Returns an RT::Ticket object as the current user. =cut sub LoadTicket { my $id = shift; if ( ref($id) eq "ARRAY" ) { $id = $id->[0]; } unless ($id) { Abort("No ticket specified", Code => HTTP::Status::HTTP_BAD_REQUEST); } my $Ticket = RT::Ticket->new( $session{'CurrentUser'} ); $Ticket->Load($id); unless ( $Ticket->id ) { Abort("Could not load ticket $id", Code => HTTP::Status::HTTP_NOT_FOUND); } return $Ticket; } =head2 ProcessUpdateMessage Takes paramhash with fields ARGSRef, TicketObj and SkipSignatureOnly. Don't write message if it only contains current user's signature and SkipSignatureOnly argument is true. Function anyway adds attachments and updates time worked field even if skips message. The default value is true. =cut sub ProcessUpdateMessage { my %args = ( ARGSRef => undef, TicketObj => undef, SkipSignatureOnly => 1, @_ ); my @attachments; if ( my $tmp = $session{'Attachments'}{ $args{'ARGSRef'}{'Token'} || '' } ) { push @attachments, grep $_, map $tmp->{$_}, sort keys %$tmp; delete $session{'Attachments'}{ $args{'ARGSRef'}{'Token'} || '' } unless $args{'KeepAttachments'} or ($args{TicketObj} and $args{TicketObj}{DryRun}); $session{'Attachments'} = $session{'Attachments'} if @attachments; } if ( $args{ARGSRef}{'UpdateAttachments'} ) { push @attachments, grep $_, map $args{ARGSRef}->{UpdateAttachments}{$_}, sort keys %{ $args{ARGSRef}->{'UpdateAttachments'} }; } # Strip the signature $args{ARGSRef}->{UpdateContent} = RT::Interface::Web::StripContent( Content => $args{ARGSRef}->{UpdateContent}, ContentType => $args{ARGSRef}->{UpdateContentType}, StripSignature => $args{SkipSignatureOnly}, CurrentUser => $args{'TicketObj'}->CurrentUser, ); # If, after stripping the signature, we have no message, move the # UpdateTimeWorked into adjusted TimeWorked, so that a later # ProcessBasics can deal -- then bail out. if ( not @attachments and not $args{ARGSRef}->{'AttachTickets'} and not length $args{ARGSRef}->{'UpdateContent'} ) { if ( $args{ARGSRef}->{'UpdateTimeWorked'} ) { $args{ARGSRef}->{TimeWorked} = $args{TicketObj}->TimeWorked + delete $args{ARGSRef}->{'UpdateTimeWorked'}; } return; } if ( ($args{ARGSRef}->{'UpdateSubject'}||'') eq ($args{'TicketObj'}->Subject || '') ) { $args{ARGSRef}->{'UpdateSubject'} = undef; } my $Message = MakeMIMEEntity( Subject => $args{ARGSRef}->{'UpdateSubject'}, Body => $args{ARGSRef}->{'UpdateContent'}, Type => $args{ARGSRef}->{'UpdateContentType'}, Interface => RT::Interface::Web::MobileClient() ? 'Mobile' : 'Web', ); $Message->head->replace( 'Message-ID' => Encode::encode( "UTF-8", RT::Interface::Email::GenMessageId( Ticket => $args{'TicketObj'} ) ) ); my $old_txn = RT::Transaction->new( $session{'CurrentUser'} ); if ( $args{ARGSRef}->{'QuoteTransaction'} ) { $old_txn->Load( $args{ARGSRef}->{'QuoteTransaction'} ); } else { $old_txn = $args{TicketObj}->Transactions->First(); } if ( my $msg = $old_txn->Message->First ) { RT::Interface::Email::SetInReplyTo( Message => $Message, InReplyTo => $msg, Ticket => $args{'TicketObj'}, ); } if ( @attachments ) { $Message->make_multipart; $Message->add_part( $_ ) foreach @attachments; } if ( $args{ARGSRef}->{'AttachTickets'} ) { require RT::Action::SendEmail; RT::Action::SendEmail->AttachTickets( RT::Action::SendEmail->AttachTickets, ref $args{ARGSRef}->{'AttachTickets'} ? @{ $args{ARGSRef}->{'AttachTickets'} } : ( $args{ARGSRef}->{'AttachTickets'} ) ); } my %message_args = ( Sign => $args{ARGSRef}->{'Sign'}, Encrypt => $args{ARGSRef}->{'Encrypt'}, MIMEObj => $Message, TimeTaken => $args{ARGSRef}->{'UpdateTimeWorked'}, AttachExisting => $args{ARGSRef}->{'AttachExisting'}, ); _ProcessUpdateMessageRecipients( MessageArgs => \%message_args, %args, ); my @results; if ( $args{ARGSRef}->{'UpdateType'} =~ /^(private|public)$/ ) { my ( $Transaction, $Description, $Object ) = $args{TicketObj}->Comment(%message_args); push( @results, $Description ); $Object->UpdateCustomFields( %{ $args{ARGSRef} } ) if $Object; } elsif ( $args{ARGSRef}->{'UpdateType'} eq 'response' ) { my ( $Transaction, $Description, $Object ) = $args{TicketObj}->Correspond(%message_args); push( @results, $Description ); $Object->UpdateCustomFields( %{ $args{ARGSRef} } ) if $Object; } else { push( @results, loc("Update type was neither correspondence nor comment.") . " " . loc("Update not recorded.") ); } return @results; } sub _ProcessUpdateMessageRecipients { my %args = ( ARGSRef => undef, TicketObj => undef, MessageArgs => undef, @_, ); my $bcc = $args{ARGSRef}->{'UpdateBcc'}; my $cc = $args{ARGSRef}->{'UpdateCc'}; my $message_args = $args{MessageArgs}; $message_args->{CcMessageTo} = $cc; $message_args->{BccMessageTo} = $bcc; my @txn_squelch; foreach my $type (qw(Cc AdminCc)) { if (grep $_ eq $type || $_ eq ( $type . 's' ), @{ $args{ARGSRef}->{'SkipNotification'} || [] }) { push @txn_squelch, map $_->address, Email::Address->parse( $message_args->{$type} ); push @txn_squelch, $args{TicketObj}->$type->MemberEmailAddresses; push @txn_squelch, $args{TicketObj}->QueueObj->$type->MemberEmailAddresses; } } for my $role (grep { /^RT::CustomRole-\d+$/ } @{ $args{ARGSRef}->{'SkipNotification'} || [] }) { push @txn_squelch, map $_->address, Email::Address->parse( $message_args->{$role} ); push @txn_squelch, $args{TicketObj}->RoleGroup($role)->MemberEmailAddresses; push @txn_squelch, $args{TicketObj}->QueueObj->RoleGroup($role)->MemberEmailAddresses; } if (grep $_ eq 'Requestor' || $_ eq 'Requestors', @{ $args{ARGSRef}->{'SkipNotification'} || [] }) { push @txn_squelch, map $_->address, Email::Address->parse( $message_args->{Requestor} ); push @txn_squelch, $args{TicketObj}->Requestors->MemberEmailAddresses; } push @txn_squelch, @{$args{ARGSRef}{SquelchMailTo}} if $args{ARGSRef}{SquelchMailTo}; $message_args->{SquelchMailTo} = \@txn_squelch if @txn_squelch; $args{TicketObj}->{TransSquelchMailTo} ||= $message_args->{'SquelchMailTo'}; unless ( $args{'ARGSRef'}->{'UpdateIgnoreAddressCheckboxes'} ) { foreach my $key ( keys %{ $args{ARGSRef} } ) { next unless $key =~ /^Update(Cc|Bcc)-(.*)$/; my $var = ucfirst($1) . 'MessageTo'; my $value = $2; if ( $message_args->{$var} ) { $message_args->{$var} .= ", $value"; } else { $message_args->{$var} = $value; } } } } sub ProcessAttachments { my %args = ( ARGSRef => {}, Token => '', # For back-compatibility, CheckSize is not enabled by default. But for # callers that mean to check returned values, it's safe to enable. CheckSize => wantarray ? 1 : 0, @_ ); my $token = $args{'ARGSRef'}{'Token'} ||= $args{'Token'} ||= Digest::MD5::md5_hex( rand(1024) ); my $update_session = 0; # deal with deleting uploaded attachments if ( my $del = $args{'ARGSRef'}{'DeleteAttach'} ) { delete $session{'Attachments'}{ $token }{ $_ } foreach ref $del? @$del : ($del); $update_session = 1; } # store the uploaded attachment in session my $new = $args{'ARGSRef'}{'Attach'}; if ( defined $new && length $new ) { my $attachment = MakeMIMEEntity( AttachmentFieldName => 'Attach' ); # This needs to be decoded because the value is a reference; # hence it was not decoded along with all of the standard # arguments in DecodeARGS my $file_path = Encode::decode( "UTF-8", "$new"); if ( $args{CheckSize} and my $max_size = RT->Config->Get( 'MaxAttachmentSize' ) ) { my $content = $attachment->bodyhandle->as_string; # The same encoding overhead as in Record.pm $max_size *= 3 / 4 if !$RT::Handle->BinarySafeBLOBs && $content =~ /\x00/; if ( length $content > $max_size ) { my $file_name = ( File::Spec->splitpath( $file_path ) )[ 2 ]; return ( 0, loc( "File '[_1]' size([_2] bytes) exceeds limit([_3] bytes)", $file_name, length $content, $max_size ) ); } } $session{'Attachments'}{ $token }{ $file_path } = $attachment; $update_session = 1; } $session{'Attachments'} = $session{'Attachments'} if $update_session; return 1; } =head2 MakeMIMEEntity PARAMHASH Takes a paramhash Subject, Body and AttachmentFieldName. Also takes Form, Cc and Type as optional paramhash keys. Returns a MIME::Entity. =cut sub MakeMIMEEntity { #TODO document what else this takes. my %args = ( Subject => undef, From => undef, Cc => undef, Body => undef, AttachmentFieldName => undef, Type => undef, Interface => 'API', @_, ); my $Message = MIME::Entity->build( Type => 'multipart/mixed', "Message-Id" => Encode::encode( "UTF-8", RT::Interface::Email::GenMessageId ), "X-RT-Interface" => $args{Interface}, map { $_ => Encode::encode( "UTF-8", $args{ $_} ) } grep defined $args{$_}, qw(Subject From Cc To Date) ); if ( defined $args{'Body'} && length $args{'Body'} ) { # Make the update content have no 'weird' newlines in it $args{'Body'} =~ s/\r\n/\n/gs; $Message->attach( Type => $args{'Type'} || 'text/plain', Charset => 'UTF-8', Data => Encode::encode( "UTF-8", $args{'Body'} ), ); } if ( $args{'AttachmentFieldName'} ) { my $cgi_object = $m->cgi_object; my $filehandle = $cgi_object->upload( $args{'AttachmentFieldName'} ); if ( defined $filehandle && length $filehandle ) { my ( @content, $buffer ); while ( my $bytesread = read( $filehandle, $buffer, 4096 ) ) { push @content, $buffer; } my $uploadinfo = $cgi_object->uploadInfo($filehandle); my $filename = Encode::decode("UTF-8","$filehandle"); $filename =~ s{^.*[\\/]}{}; $Message->attach( Type => $uploadinfo->{'Content-Type'}, Filename => Encode::encode("UTF-8",$filename), Data => \@content, # Bytes, as read directly from the file, above ); if ( !$args{'Subject'} && !( defined $args{'Body'} && length $args{'Body'} ) ) { $Message->head->replace( 'Subject' => Encode::encode( "UTF-8", $filename ) ); } # Attachment parts really shouldn't get a Message-ID or "interface" $Message->head->delete('Message-ID'); $Message->head->delete('X-RT-Interface'); } } $Message->make_singlepart; RT::I18N::SetMIMEEntityToUTF8($Message); # convert text parts into utf-8 return ($Message); } =head2 ParseDateToISO Takes a date in an arbitrary format. Returns an ISO date and time in GMT =cut sub ParseDateToISO { my $date = shift; my $date_obj = RT::Date->new( $session{'CurrentUser'} ); $date_obj->Set( Format => 'unknown', Value => $date ); return ( $date_obj->ISO ); } sub ProcessACLChanges { my $ARGSref = shift; #XXX: why don't we get ARGSref like in other Process* subs? my @results; foreach my $arg ( keys %$ARGSref ) { next unless ( $arg =~ /^(GrantRight|RevokeRight)-(\d+)-(.+?)-(\d+)$/ ); my ( $method, $principal_id, $object_type, $object_id ) = ( $1, $2, $3, $4 ); my @rights; if ( UNIVERSAL::isa( $ARGSref->{$arg}, 'ARRAY' ) ) { @rights = @{ $ARGSref->{$arg} }; } else { @rights = $ARGSref->{$arg}; } @rights = grep $_, @rights; next unless @rights; my $principal = RT::Principal->new( $session{'CurrentUser'} ); $principal->Load($principal_id); my $obj; if ( $object_type eq 'RT::System' ) { $obj = $RT::System; } elsif ( $object_type->DOES('RT::Record::Role::Rights') ) { $obj = $object_type->new( $session{'CurrentUser'} ); $obj->Load($object_id); unless ( $obj->id ) { $RT::Logger->error("couldn't load $object_type #$object_id"); next; } } else { $RT::Logger->error("object type '$object_type' is incorrect"); push( @results, loc("System Error") . ': ' . loc( "Rights could not be granted for [_1]", $object_type ) ); next; } foreach my $right (@rights) { my ( $val, $msg ) = $principal->$method( Object => $obj, Right => $right ); push( @results, $msg ); } } return (@results); } =head2 ProcessACLs ProcessACLs expects values from a series of checkboxes that describe the full set of rights a principal should have on an object. It expects form inputs with names like SetRights-PrincipalId-ObjType-ObjId instead of with the prefixes Grant/RevokeRight. Each input should be an array listing the rights the principal should have, and ProcessACLs will modify the current rights to match. Additionally, the previously unused CheckACL input listing PrincipalId-ObjType-ObjId is now used to catch cases when all the rights are removed from a principal and as such no SetRights input is submitted. =cut sub ProcessACLs { my $ARGSref = shift; my (%state, @results); my $CheckACL = $ARGSref->{'CheckACL'}; my @check = grep { defined } (ref $CheckACL eq 'ARRAY' ? @$CheckACL : $CheckACL); # Check if we want to grant rights to a previously rights-less user for my $type (qw(user group)) { my $principal = _ParseACLNewPrincipal($ARGSref, $type) or next; unless ($principal->PrincipalId) { push @results, loc("Couldn't load the specified principal"); next; } my $principal_id = $principal->PrincipalId; # Turn our addprincipal rights spec into a real one for my $arg (keys %$ARGSref) { next unless $arg =~ /^SetRights-addprincipal-(.+?-\d+)$/; my $tuple = "$principal_id-$1"; my $key = "SetRights-$tuple"; # If we have it already, that's odd, but merge them if (grep { $_ eq $tuple } @check) { $ARGSref->{$key} = [ (ref $ARGSref->{$key} eq 'ARRAY' ? @{$ARGSref->{$key}} : $ARGSref->{$key}), (ref $ARGSref->{$arg} eq 'ARRAY' ? @{$ARGSref->{$arg}} : $ARGSref->{$arg}), ]; } else { $ARGSref->{$key} = $ARGSref->{$arg}; push @check, $tuple; } } } # Build our rights state for each Principal-Object tuple foreach my $arg ( keys %$ARGSref ) { next unless $arg =~ /^SetRights-(\d+-.+?-\d+)$/; my $tuple = $1; my $value = $ARGSref->{$arg}; my @rights = grep { $_ } (ref $value eq 'ARRAY' ? @$value : $value); next unless @rights; $state{$tuple} = { map { $_ => 1 } @rights }; } foreach my $tuple (List::MoreUtils::uniq @check) { next unless $tuple =~ /^(\d+)-(.+?)-(\d+)$/; my ( $principal_id, $object_type, $object_id ) = ( $1, $2, $3 ); my $principal = RT::Principal->new( $session{'CurrentUser'} ); $principal->Load($principal_id); my $obj; if ( $object_type eq 'RT::System' ) { $obj = $RT::System; } elsif ( $object_type->DOES('RT::Record::Role::Rights') ) { $obj = $object_type->new( $session{'CurrentUser'} ); $obj->Load($object_id); unless ( $obj->id ) { $RT::Logger->error("couldn't load $object_type #$object_id"); next; } } else { $RT::Logger->error("object type '$object_type' is incorrect"); push( @results, loc("System Error") . ': ' . loc( "Rights could not be granted for [_1]", $object_type ) ); next; } my $acls = RT::ACL->new($session{'CurrentUser'}); $acls->LimitToObject( $obj ); $acls->LimitToPrincipal( Id => $principal_id ); while ( my $ace = $acls->Next ) { my $right = $ace->RightName; # Has right and should have right next if delete $state{$tuple}->{$right}; # Has right and shouldn't have right my ($val, $msg) = $principal->RevokeRight( Object => $obj, Right => $right ); push @results, $msg; } # For everything left, they don't have the right but they should for my $right (keys %{ $state{$tuple} || {} }) { delete $state{$tuple}->{$right}; my ($val, $msg) = $principal->GrantRight( Object => $obj, Right => $right ); push @results, $msg; } # Check our state for leftovers if ( keys %{ $state{$tuple} || {} } ) { my $missed = join '|', %{$state{$tuple} || {}}; $RT::Logger->warn( "Uh-oh, it looks like we somehow missed a right in " ."ProcessACLs. Here's what was leftover: $missed" ); } } return (@results); } =head2 _ParseACLNewPrincipal Takes a hashref of C<%ARGS> and a principal type (C<user> or C<group>). Looks for the presence of rights being added on a principal of the specified type, and returns undef if no new principal is being granted rights. Otherwise loads up an L<RT::User> or L<RT::Group> object and returns it. Note that the object may not be successfully loaded, and you should check C<->id> yourself. =cut sub _ParseACLNewPrincipal { my $ARGSref = shift; my $type = lc shift; my $key = "AddPrincipalForRights-$type"; return unless $ARGSref->{$key}; my $principal; if ( $type eq 'user' ) { $principal = RT::User->new( $session{'CurrentUser'} ); $principal->LoadByCol( Name => $ARGSref->{$key} ); } elsif ( $type eq 'group' ) { $principal = RT::Group->new( $session{'CurrentUser'} ); $principal->LoadUserDefinedGroup( $ARGSref->{$key} ); } return $principal; } =head2 UpdateRecordObj ( ARGSRef => \%ARGS, Object => RT::Record, AttributesRef => \@attribs) @attribs is a list of ticket fields to check and update if they differ from the B<Object>'s current values. ARGSRef is a ref to HTML::Mason's %ARGS. Returns an array of success/failure messages =cut sub UpdateRecordObject { my %args = ( ARGSRef => undef, AttributesRef => undef, Object => undef, AttributePrefix => undef, @_ ); my $Object = $args{'Object'}; my @results = $Object->Update( AttributesRef => $args{'AttributesRef'}, ARGSRef => $args{'ARGSRef'}, AttributePrefix => $args{'AttributePrefix'}, ); return (@results); } sub ProcessCustomFieldUpdates { my %args = ( CustomFieldObj => undef, ARGSRef => undef, @_ ); my $Object = $args{'CustomFieldObj'}; my $ARGSRef = $args{'ARGSRef'}; my @attribs = qw(Name Type Description Queue SortOrder); my @results = UpdateRecordObject( AttributesRef => \@attribs, Object => $Object, ARGSRef => $ARGSRef ); my $prefix = "CustomField-" . $Object->Id; if ( $ARGSRef->{"$prefix-AddValue-Name"} ) { my ( $addval, $addmsg ) = $Object->AddValue( Name => $ARGSRef->{"$prefix-AddValue-Name"}, Description => $ARGSRef->{"$prefix-AddValue-Description"}, SortOrder => $ARGSRef->{"$prefix-AddValue-SortOrder"}, ); push( @results, $addmsg ); } my @delete_values = ( ref $ARGSRef->{"$prefix-DeleteValue"} eq 'ARRAY' ) ? @{ $ARGSRef->{"$prefix-DeleteValue"} } : ( $ARGSRef->{"$prefix-DeleteValue"} ); foreach my $id (@delete_values) { next unless defined $id; my ( $err, $msg ) = $Object->DeleteValue($id); push( @results, $msg ); } my $vals = $Object->Values(); while ( my $cfv = $vals->Next() ) { if ( my $so = $ARGSRef->{ "$prefix-SortOrder" . $cfv->Id } ) { if ( $cfv->SortOrder != $so ) { my ( $err, $msg ) = $cfv->SetSortOrder($so); push( @results, $msg ); } } } return (@results); } =head2 ProcessTicketBasics ( TicketObj => $Ticket, ARGSRef => \%ARGS ); Returns an array of results messages. =cut sub ProcessTicketBasics { my %args = ( TicketObj => undef, ARGSRef => undef, @_ ); my $TicketObj = $args{'TicketObj'}; my $ARGSRef = $args{'ARGSRef'}; my $OrigOwner = $TicketObj->Owner; # Set basic fields my @attribs = qw( Subject FinalPriority Priority TimeEstimated TimeLeft Type Status Queue SLA ); # Canonicalize Queue and Owner to their IDs if they aren't numeric for my $field (qw(Queue Owner)) { if ( $ARGSRef->{$field} and ( $ARGSRef->{$field} !~ /^(\d+)$/ ) ) { my $class = $field eq 'Owner' ? "RT::User" : "RT::$field"; my $temp = $class->new(RT->SystemUser); $temp->Load( $ARGSRef->{$field} ); if ( $temp->id ) { $ARGSRef->{$field} = $temp->id; } } } # Status isn't a field that can be set to a null value. # RT core complains if you try delete $ARGSRef->{'Status'} unless $ARGSRef->{'Status'}; my @results = UpdateRecordObject( AttributesRef => \@attribs, Object => $TicketObj, ARGSRef => $ARGSRef, ); if ( defined($ARGSRef->{'TimeWorked'}) && ($ARGSRef->{'TimeWorked'} || 0) != $TicketObj->TimeWorked ) { my ( $val, $msg, $txn ) = $TicketObj->SetTimeWorked( $ARGSRef->{'TimeWorked'} ); push( @results, $msg ); $txn->UpdateCustomFields( %$ARGSRef) if $txn; } # We special case owner changing, so we can use ForceOwnerChange if ( $ARGSRef->{'Owner'} && $ARGSRef->{'Owner'} !~ /\D/ && ( $OrigOwner != $ARGSRef->{'Owner'} ) ) { my ($ChownType); if ( $ARGSRef->{'ForceOwnerChange'} ) { $ChownType = "Force"; } else { $ChownType = "Set"; } my ( $val, $msg ) = $TicketObj->SetOwner( $ARGSRef->{'Owner'}, $ChownType ); push( @results, $msg ); } # }}} return (@results); } sub ProcessTicketReminders { my %args = ( TicketObj => undef, ARGSRef => undef, @_ ); my $Ticket = $args{'TicketObj'}; my $args = $args{'ARGSRef'}; my @results; my $reminder_collection = $Ticket->Reminders->Collection; if ( $args->{'update-reminders'} ) { while ( my $reminder = $reminder_collection->Next ) { my $resolve_status = $reminder->LifecycleObj->ReminderStatusOnResolve; my ( $status, $msg, $old_subject, @subresults ); if ( $reminder->Status ne $resolve_status && $args->{ 'Complete-Reminder-' . $reminder->id } ) { ( $status, $msg ) = $Ticket->Reminders->Resolve($reminder); push @subresults, $msg; } elsif ( $reminder->Status eq $resolve_status && !$args->{ 'Complete-Reminder-' . $reminder->id } ) { ( $status, $msg ) = $Ticket->Reminders->Open($reminder); push @subresults, $msg; } if ( exists( $args->{ 'Reminder-Subject-' . $reminder->id } ) && ( $reminder->Subject ne $args->{ 'Reminder-Subject-' . $reminder->id } ) ) { $old_subject = $reminder->Subject; ( $status, $msg ) = $reminder->SetSubject( $args->{ 'Reminder-Subject-' . $reminder->id } ); push @subresults, $msg; } if ( exists( $args->{ 'Reminder-Owner-' . $reminder->id } ) && ( $reminder->Owner != $args->{ 'Reminder-Owner-' . $reminder->id } ) ) { ( $status, $msg ) = $reminder->SetOwner( $args->{ 'Reminder-Owner-' . $reminder->id }, "Force" ); push @subresults, $msg; } if ( exists( $args->{ 'Reminder-Due-' . $reminder->id } ) && $args->{ 'Reminder-Due-' . $reminder->id } ne '' ) { my $DateObj = RT::Date->new( $session{'CurrentUser'} ); my $due = $args->{ 'Reminder-Due-' . $reminder->id }; $DateObj->Set( Format => 'unknown', Value => $due, ); if ( $DateObj->Unix != $reminder->DueObj->Unix ) { ( $status, $msg ) = $reminder->SetDue( $DateObj->ISO ); } else { $msg = loc( "invalid due date: [_1]", $due ); } push @subresults, $msg; } push @results, map { loc( "Reminder '[_1]': [_2]", $old_subject || $reminder->Subject, $_ ) } @subresults; } } if ( $args->{'NewReminder-Subject'} ) { my $due_obj = RT::Date->new( $session{'CurrentUser'} ); $due_obj->Set( Format => 'unknown', Value => $args->{'NewReminder-Due'} ); my ( $status, $msg ) = $Ticket->Reminders->Add( Subject => $args->{'NewReminder-Subject'}, Owner => $args->{'NewReminder-Owner'}, Due => $due_obj->ISO ); if ( $status ) { push @results, loc( "Reminder '[_1]': [_2]", $args->{'NewReminder-Subject'}, loc("Created") ) } else { push @results, $msg; } } return @results; } sub _ValidateConsistentCustomFieldValues { my $cf = shift; my $args = shift; my $ok = 1; my @groupings = sort keys %$args; return ($ok, undef) unless @groupings; my $default_grouping = $groupings[0]; # Default to use if multiple are submitted if (@groupings > 1) { # Check for consistency, in case of JS fail for my $key (qw/AddValue Value Values DeleteValues DeleteValueIds/) { my $base = $args->{$groupings[0]}{$key}; $base = [ $base ] unless ref $base; for my $grouping (@groupings[1..$#groupings]) { my $other = $args->{$grouping}{$key}; $other = [ $other ] unless ref $other; next unless grep {$_} List::MoreUtils::pairwise { no warnings qw(uninitialized); $a ne $b } @{$base}, @{$other}; RT::Logger->warn("CF $cf submitted with multiple differing values"); $ok = 0; } } } return ($ok, $default_grouping); } sub ProcessObjectCustomFieldUpdates { my %args = @_; my $ARGSRef = $args{'ARGSRef'}; my @results; # Build up a list of objects that we want to work with my %custom_fields_to_mod = _ParseObjectCustomFieldArgs($ARGSRef); # For each of those objects foreach my $class ( keys %custom_fields_to_mod ) { foreach my $id ( keys %{ $custom_fields_to_mod{$class} } ) { my $Object = $args{'Object'}; $Object = $class->new( $session{'CurrentUser'} ) unless $Object && ref $Object eq $class; # skip if we have no object to update next unless $id || $Object->id; $Object->Load($id) unless ( $Object->id || 0 ) == $id; unless ( $Object->id ) { $RT::Logger->warning("Couldn't load object $class #$id"); next; } foreach my $cf ( keys %{ $custom_fields_to_mod{$class}{$id} } ) { my $CustomFieldObj = RT::CustomField->new( $session{'CurrentUser'} ); $CustomFieldObj->SetContextObject($Object); $CustomFieldObj->LoadById($cf); unless ( $CustomFieldObj->id ) { $RT::Logger->warning("Couldn't load custom field #$cf"); next; } # In the case of inconsistent CFV submission, # we'll get the 1st grouping in the hash, alphabetically my ($ret, $grouping) = _ValidateConsistentCustomFieldValues($cf, $custom_fields_to_mod{$class}{$id}{$cf}); push @results, _ProcessObjectCustomFieldUpdates( Prefix => GetCustomFieldInputNamePrefix( Object => $Object, CustomField => $CustomFieldObj, Grouping => $grouping, ), Object => $Object, CustomField => $CustomFieldObj, ARGS => $custom_fields_to_mod{$class}{$id}{$cf}{ $grouping }, ); } } } return @results; } sub _ParseObjectCustomFieldArgs { my $ARGSRef = shift || {}; my %args = ( IncludeBulkUpdate => 0, @_, ); my %custom_fields_to_mod; foreach my $arg ( keys %$ARGSRef ) { # format: Object-<object class>-<object id>-CustomField[:<grouping>]-<CF id>-<commands> # you can use GetCustomFieldInputName to generate the complement input name # or if IncludeBulkUpdate: Bulk-<Add or Delete>-CustomField[:<grouping>]-<CF id>-<commands> next unless $arg =~ /^Object-([\w:]+)-(\d*)-CustomField(?::(\w+))?-(\d+)-(.*)$/ || ($args{IncludeBulkUpdate} && $arg =~ /^Bulk-(?:Add|Delete)-()()CustomField(?::(\w+))?-(\d+)-(.*)$/); # need two empty groups because we must consume $1 and $2 with empty # class and ID # For each of those objects, find out what custom fields we want to work with. # Class ID CF grouping command $custom_fields_to_mod{$1}{ $2 || 0 }{$4}{$3 || ''}{$5} = $ARGSRef->{$arg}; } return wantarray ? %custom_fields_to_mod : \%custom_fields_to_mod; } sub _ProcessObjectCustomFieldUpdates { my %args = @_; my $cf = $args{'CustomField'}; my $cf_type = $cf->Type || ''; # Remove blank Values since the magic field will take care of this. Sometimes # the browser gives you a blank value which causes CFs to be processed twice if ( defined $args{'ARGS'}->{'Values'} && !length $args{'ARGS'}->{'Values'} && ($args{'ARGS'}->{'Values-Magic'}) ) { delete $args{'ARGS'}->{'Values'}; } my @results; foreach my $arg ( keys %{ $args{'ARGS'} } ) { # skip category argument next if $arg =~ /-Category$/; # since http won't pass in a form element with a null value, we need # to fake it if ( $arg =~ /-Magic$/ ) { # We don't care about the magic, if there's really a values element; next if defined $args{'ARGS'}->{'Value'} && length $args{'ARGS'}->{'Value'}; next if defined $args{'ARGS'}->{'Values'} && length $args{'ARGS'}->{'Values'}; # "Empty" values does not mean anything for Image and Binary fields next if $cf_type =~ /^(?:Image|Binary)$/; $arg = 'Values'; $args{'ARGS'}->{'Values'} = undef; } my @values = _NormalizeObjectCustomFieldValue( CustomField => $cf, Param => $args{'Prefix'} . $arg, Value => $args{'ARGS'}->{$arg} ); # "Empty" values still don't mean anything for Image and Binary fields next if $cf_type =~ /^(?:Image|Binary)$/ and not @values; if ( $arg eq 'AddValue' || $arg eq 'Value' ) { foreach my $value (@values) { next if $args{'Object'}->CustomFieldValueIsEmpty( Field => $cf, Value => $value, ); my ( $val, $msg ) = $args{'Object'}->AddCustomFieldValue( Field => $cf->id, Value => $value ); push( @results, $msg ); } } elsif ( $arg eq 'Upload' ) { my ( $val, $msg ) = $args{'Object'}->AddCustomFieldValue( %{$values[0]}, Field => $cf, ); push( @results, $msg ); } elsif ( $arg eq 'DeleteValues' ) { foreach my $value (@values) { my ( $val, $msg ) = $args{'Object'}->DeleteCustomFieldValue( Field => $cf, Value => $value, ); push( @results, $msg ); } } elsif ( $arg eq 'DeleteValueIds' ) { foreach my $value (@values) { my ( $val, $msg ) = $args{'Object'}->DeleteCustomFieldValue( Field => $cf, ValueId => $value, ); push( @results, $msg ); } } elsif ( $arg eq 'Values' ) { my $cf_values = $args{'Object'}->CustomFieldValues( $cf->id ); my %values_hash; foreach my $value (@values) { my $value_in_db = $value; if ( $cf->Type eq 'DateTime' ) { my $date = RT::Date->new($session{CurrentUser}); $date->Set(Format => 'unknown', Value => $value); $value_in_db = $date->ISO; } if ( my $entry = $cf_values->HasEntry($value_in_db) ) { $values_hash{ $entry->id } = 1; next; } next if $args{'Object'}->CustomFieldValueIsEmpty( Field => $cf, Value => $value, ); my ( $val, $msg ) = $args{'Object'}->AddCustomFieldValue( Field => $cf, Value => $value ); push( @results, $msg ); $values_hash{$val} = 1 if $val; } $cf_values->RedoSearch; while ( my $cf_value = $cf_values->Next ) { next if $values_hash{ $cf_value->id }; my ( $val, $msg ) = $args{'Object'}->DeleteCustomFieldValue( Field => $cf, ValueId => $cf_value->id ); push( @results, $msg ); } } else { push( @results, loc("User asked for an unknown update type for custom field [_1] for [_2] object #[_3]", $cf->Name, ref $args{'Object'}, $args{'Object'}->id ) ); } } return @results; } sub ProcessObjectCustomFieldUpdatesForCreate { my %args = ( ARGSRef => {}, ContextObject => undef, @_ ); my $context = $args{'ContextObject'}; my %parsed; my %custom_fields = _ParseObjectCustomFieldArgs( $args{'ARGSRef'} ); for my $class (keys %custom_fields) { # we're only interested in new objects, so only look at $id == 0 for my $cfid (keys %{ $custom_fields{$class}{0} || {} }) { my $cf = RT::CustomField->new( $session{'CurrentUser'} ); $cf->{include_set_initial} = 1; if ($context) { my $system_cf = RT::CustomField->new( RT->SystemUser ); $system_cf->LoadById($cfid); if ($system_cf->ValidateContextObject($context)) { $cf->SetContextObject($context); } else { RT->Logger->error( sprintf "Invalid context object %s (%d) for CF %d; skipping CF", ref $context, $context->id, $system_cf->id ); next; } } $cf->LoadById($cfid); unless ($cf->id) { RT->Logger->warning("Couldn't load custom field #$cfid"); next; } my @groupings = sort keys %{ $custom_fields{$class}{0}{$cfid} }; if (@groupings > 1) { # Check for consistency, in case of JS fail for my $key (qw/AddValue Value Values DeleteValues DeleteValueIds/) { warn "CF $cfid submitted with multiple differing $key" if grep {($custom_fields{$class}{0}{$cfid}{$_}{$key} || '') ne ($custom_fields{$class}{0}{$cfid}{$groupings[0]}{$key} || '')} @groupings; } # We'll just be picking the 1st grouping in the hash, alphabetically } my @values; my $name_prefix = GetCustomFieldInputNamePrefix( CustomField => $cf, Grouping => $groupings[0], ); while (my ($arg, $value) = each %{ $custom_fields{$class}{0}{$cfid}{$groupings[0]} }) { # Values-Magic doesn't matter on create; no previous values are being removed # Category is irrelevant for the actual value next if $arg =~ /-Magic$/ or $arg =~ /-Category$/; push @values, _NormalizeObjectCustomFieldValue( CustomField => $cf, Param => $name_prefix . $arg, Value => $value, ); } if (@values) { if ( $class eq 'RT::Transaction' ) { $parsed{"Object-RT::Transaction--CustomField-$cfid"} = \@values; } else { $parsed{"CustomField-$cfid"} = \@values if @values; } } } } return wantarray ? %parsed : \%parsed; } sub _NormalizeObjectCustomFieldValue { my %args = ( Param => "", @_ ); my $cf_type = $args{CustomField}->Type; my @values = (); if ( ref $args{'Value'} eq 'ARRAY' ) { @values = @{ $args{'Value'} }; } elsif ( $cf_type =~ /text/i ) { # Both Text and Wikitext @values = ( $args{'Value'} ); } else { @values = split /\r*\n/, $args{'Value'} if defined $args{'Value'}; } @values = grep length, map { s/\r+\n/\n/g; s/^\s+//; s/\s+$//; $_; } grep defined, @values; if ($args{'Param'} =~ /-Upload$/ and $cf_type =~ /^(Image|Binary)$/) { @values = _UploadedFile( $args{'Param'} ) || (); } return @values; } =head2 ProcessTicketWatchers ( TicketObj => $Ticket, ARGSRef => \%ARGS ); Returns an array of results messages. =cut sub ProcessTicketWatchers { my %args = ( TicketObj => undef, ARGSRef => undef, @_ ); my (@results); my $Ticket = $args{'TicketObj'}; my $ARGSRef = $args{'ARGSRef'}; # Munge watchers foreach my $key ( keys %$ARGSRef ) { # Delete deletable watchers if ( $key =~ /^Ticket-DeleteWatcher-Type-(.*)-Principal-(\d+)$/ ) { my ( $code, $msg ) = $Ticket->DeleteWatcher( PrincipalId => $2, Type => $1 ); push @results, $msg; } # Delete watchers in the simple style demanded by the bulk manipulator elsif ( $key =~ /^Delete(Requestor|Cc|AdminCc|RT::CustomRole-\d+)$/ ) { my ( $code, $msg ) = $Ticket->DeleteWatcher( Email => $ARGSRef->{$key}, Type => $1 ); push @results, $msg; } # Add new watchers by email address elsif ( ( $ARGSRef->{$key} || '' ) =~ /^(?:AdminCc|Cc|Requestor|RT::CustomRole-\d+)$/ and $key =~ /^WatcherTypeEmail(\d*)$/ ) { #They're in this order because otherwise $1 gets clobbered :/ my ( $code, $msg ) = $Ticket->AddWatcher( Type => $ARGSRef->{$key}, Email => $ARGSRef->{ "WatcherAddressEmail" . $1 } ); push @results, $msg; } #Add requestors in the simple style demanded by the bulk manipulator elsif ( $key =~ /^Add(Requestor|Cc|AdminCc|RT::CustomRole-\d+)$/ ) { my ( $code, $msg ) = $Ticket->AddWatcher( Type => $1, Email => $ARGSRef->{$key} ); push @results, $msg; } # Add new watchers by owner elsif ( $key =~ /^Ticket-AddWatcher-Principal-(\d*)$/ ) { my $principal_id = $1; my $form = $ARGSRef->{$key}; foreach my $value ( ref($form) ? @{$form} : ($form) ) { next unless $value =~ /^(?:AdminCc|Cc|Requestor|RT::CustomRole-\d+)$/i; my ( $code, $msg ) = $Ticket->AddWatcher( Type => $value, PrincipalId => $principal_id ); push @results, $msg; } } # Single-user custom roles elsif ( $key =~ /^RT::CustomRole-(\d*)$/ ) { # clearing the field sets value to nobody my $user = $ARGSRef->{$key} || RT->Nobody; my ( $code, $msg ) = $Ticket->AddWatcher( Type => $key, User => $user, ); push @results, $msg; } } return (@results); } =head2 ProcessTicketDates ( TicketObj => $Ticket, ARGSRef => \%ARGS ); Returns an array of results messages. =cut sub ProcessTicketDates { my %args = ( TicketObj => undef, ARGSRef => undef, @_ ); my $Ticket = $args{'TicketObj'}; my $ARGSRef = $args{'ARGSRef'}; my (@results); # Set date fields my @date_fields = qw( Told Starts Started Due ); #Run through each field in this list. update the value if apropriate foreach my $field (@date_fields) { next unless exists $ARGSRef->{ $field . '_Date' }; my $obj = $field . "Obj"; my $method = "Set$field"; if ( $ARGSRef->{ $field . '_Date' } eq '' ) { if ( $Ticket->$obj->IsSet ) { my ( $code, $msg ) = $Ticket->$method( '1970-01-01 00:00:00' ); push @results, $msg; } } else { my $DateObj = RT::Date->new( $session{'CurrentUser'} ); $DateObj->Set( Format => 'unknown', Value => $ARGSRef->{ $field . '_Date' } ); if ( $DateObj->Unix != $Ticket->$obj()->Unix() ) { my ( $code, $msg ) = $Ticket->$method( $DateObj->ISO ); push @results, $msg; } } } # }}} return (@results); } =head2 ProcessTicketLinks ( TicketObj => $Ticket, ARGSRef => \%ARGS ); Returns an array of results messages. =cut sub ProcessTicketLinks { my %args = ( TicketObj => undef, TicketId => undef, ARGSRef => undef, @_ ); my $Ticket = $args{'TicketObj'}; my $TicketId = $args{'TicketId'} || $Ticket->Id; my $ARGSRef = $args{'ARGSRef'}; my (@results) = ProcessRecordLinks( %args, RecordObj => $Ticket, RecordId => $TicketId, ARGSRef => $ARGSRef, ); #Merge if we need to my $input = $TicketId .'-MergeInto'; if ( $ARGSRef->{ $input } ) { $ARGSRef->{ $input } =~ s/\s+//g; my ( $val, $msg ) = $Ticket->MergeInto( $ARGSRef->{ $input } ); push @results, $msg; } return (@results); } sub ProcessRecordLinks { my %args = ( RecordObj => undef, RecordId => undef, ARGSRef => undef, @_ ); my $Record = $args{'RecordObj'}; my $RecordId = $args{'RecordId'} || $Record->Id; my $ARGSRef = $args{'ARGSRef'}; my (@results); # Delete links that are gone gone gone. foreach my $arg ( keys %$ARGSRef ) { if ( $arg =~ /DeleteLink-(.*?)-(DependsOn|MemberOf|RefersTo)-(.*)$/ ) { my $base = $1; my $type = $2; my $target = $3; my ( $val, $msg ) = $Record->DeleteLink( Base => $base, Type => $type, Target => $target ); push @results, $msg; } } my @linktypes = qw( DependsOn MemberOf RefersTo ); foreach my $linktype (@linktypes) { my $input = $RecordId .'-'. $linktype; if ( $ARGSRef->{ $input } ) { $ARGSRef->{ $input } = join( ' ', @{ $ARGSRef->{ $input } } ) if ref $ARGSRef->{ $input }; for my $luri ( split( / /, $ARGSRef->{ $input } ) ) { next unless $luri; $luri =~ s/\s+$//; # Strip trailing whitespace my ( $val, $msg ) = $Record->AddLink( Target => $luri, Type => $linktype ); push @results, $msg; } } $input = $linktype .'-'. $RecordId; if ( $ARGSRef->{ $input } ) { $ARGSRef->{ $input } = join( ' ', @{ $ARGSRef->{ $input } } ) if ref $ARGSRef->{ $input }; for my $luri ( split( / /, $ARGSRef->{ $input } ) ) { next unless $luri; my ( $val, $msg ) = $Record->AddLink( Base => $luri, Type => $linktype ); push @results, $msg; } } } return (@results); } =head2 ProcessLinksForCreate Takes a hash with a single key, C<ARGSRef>, the value of which is a hashref to C<%ARGS>. Converts and returns submitted args in the form of C<new-LINKTYPE> and C<LINKTYPE-new> into their appropriate directional link types. For example, C<new-DependsOn> becomes C<DependsOn> and C<DependsOn-new> becomes C<DependedOnBy>. The incoming arg values are split on whitespace and normalized into arrayrefs before being returned. Primarily used by object creation pages for transforming incoming form inputs from F</Elements/EditLinks> into arguments appropriate for individual record Create methods. Returns a hashref in scalar context and a hash in list context. =cut sub ProcessLinksForCreate { my %args = @_; my %links; foreach my $type ( keys %RT::Link::DIRMAP ) { for ([Base => "new-$type"], [Target => "$type-new"]) { my ($direction, $key) = @$_; next unless $args{ARGSRef}->{$key}; $links{ $RT::Link::DIRMAP{$type}->{$direction} } = [ grep $_, split ' ', $args{ARGSRef}->{$key} ]; } } return wantarray ? %links : \%links; } =head2 ProcessTransactionSquelching Takes a hashref of the submitted form arguments, C<%ARGS>. Returns a hash of squelched addresses. =cut sub ProcessTransactionSquelching { my $args = shift; my %checked = map { $_ => 1 } grep { defined } ( ref $args->{'TxnSendMailTo'} eq "ARRAY" ? @{$args->{'TxnSendMailTo'}} : defined $args->{'TxnSendMailTo'} ? ($args->{'TxnSendMailTo'}) : () ); for my $type ( qw/Cc Bcc/ ) { next unless $args->{"Update$type"}; for my $addr ( Email::Address->parse( $args->{"Update$type"} ) ) { $checked{$addr->address} ||= 1; } } my %squelched = map { $_ => 1 } grep { not $checked{$_} } split /,/, ($args->{'TxnRecipients'}||''); return %squelched; } sub ProcessRecordBulkCustomFields { my %args = (RecordObj => undef, ARGSRef => {}, @_); my $ARGSRef = $args{'ARGSRef'}; my %data; my @results; foreach my $key ( keys %$ARGSRef ) { next unless $key =~ /^Bulk-(Add|Delete)-CustomField-(\d+)-(.*)$/; my ($op, $cfid, $rest) = ($1, $2, $3); next if $rest =~ /-Category$/; my $res = $data{$cfid} ||= {}; unless (keys %$res) { my $cf = RT::CustomField->new( $session{'CurrentUser'} ); $cf->Load( $cfid ); next unless $cf->Id; $res->{'cf'} = $cf; } if ( $op eq 'Delete' && $rest eq 'AllValues' ) { $res->{'DeleteAll'} = $ARGSRef->{$key}; next; } my @values = _NormalizeObjectCustomFieldValue( CustomField => $res->{'cf'}, Value => $ARGSRef->{$key}, Param => $key, ); next unless @values; $res->{$op} = \@values; } while ( my ($cfid, $data) = each %data ) { my $current_values = $args{'RecordObj'}->CustomFieldValues( $cfid ); # just add one value for fields with single value if ( $data->{'Add'} && $data->{'cf'}->MaxValues == 1 ) { next if $current_values->HasEntry($data->{Add}[-1]); my ( $id, $msg ) = $args{'RecordObj'}->AddCustomFieldValue( Field => $cfid, Value => $data->{'Add'}[-1], ); push @results, $msg; next; } if ( $data->{'DeleteAll'} ) { while ( my $value = $current_values->Next ) { my ( $id, $msg ) = $args{'RecordObj'}->DeleteCustomFieldValue( Field => $cfid, ValueId => $value->id, ); push @results, $msg; } } foreach my $value ( @{ $data->{'Delete'} || [] } ) { my $entry = $current_values->HasEntry($value); next unless $entry; my ( $id, $msg ) = $args{'RecordObj'}->DeleteCustomFieldValue( Field => $cfid, ValueId => $entry->id, ); push @results, $msg; } foreach my $value ( @{ $data->{'Add'} || [] } ) { next if $current_values->HasEntry($value); next if $args{'RecordObj'}->CustomFieldValueIsEmpty( Field => $cfid, Value => $value, ); my ( $id, $msg ) = $args{'RecordObj'}->AddCustomFieldValue( Field => $cfid, Value => $value ); push @results, $msg; } } return @results; } =head2 _UploadedFile ( $arg ); Takes a CGI parameter name; if a file is uploaded under that name, return a hash reference suitable for AddCustomFieldValue's use: C<( Value => $filename, LargeContent => $content, ContentType => $type )>. Returns C<undef> if no files were uploaded in the C<$arg> field. =cut sub _UploadedFile { my $arg = shift; my $cgi_object = $m->cgi_object; my $fh = $cgi_object->upload($arg) or return undef; my $upload_info = $cgi_object->uploadInfo($fh); my $filename = "$fh"; $filename =~ s#^.*[\\/]##; binmode($fh); return { Value => $filename, LargeContent => do { local $/; scalar <$fh> }, ContentType => $upload_info->{'Content-Type'}, }; } sub GetColumnMapEntry { my %args = ( Map => {}, Name => '', Attribute => undef, @_ ); # deal with the simplest thing first if ( $args{'Map'}{ $args{'Name'} } ) { return $args{'Map'}{ $args{'Name'} }{ $args{'Attribute'} }; } # complex things elsif ( my ( $mainkey, $subkey ) = $args{'Name'} =~ /^(.*?)\.(.+)$/ ) { $subkey =~ s/^\{(.*)\}$/$1/; return undef unless $args{'Map'}->{$mainkey}; return $args{'Map'}{$mainkey}{ $args{'Attribute'} } unless ref $args{'Map'}{$mainkey}{ $args{'Attribute'} } eq 'CODE'; return sub { $args{'Map'}{$mainkey}{ $args{'Attribute'} }->( @_, $subkey ) }; } return undef; } sub ProcessColumnMapValue { my $value = shift; my %args = ( Arguments => [], Escape => 1, @_ ); if ( ref $value ) { if ( UNIVERSAL::isa( $value, 'CODE' ) ) { my @tmp = $value->( @{ $args{'Arguments'} } ); return ProcessColumnMapValue( ( @tmp > 1 ? \@tmp : $tmp[0] ), %args ); } elsif ( UNIVERSAL::isa( $value, 'ARRAY' ) ) { return join '', map ProcessColumnMapValue( $_, %args ), @$value; } elsif ( UNIVERSAL::isa( $value, 'SCALAR' ) ) { return $$value; } } else { if ($args{'Escape'}) { $value = $m->interp->apply_escapes( $value, 'h' ); $value =~ s/\n/<br>/g if defined $value; } return $value; } } sub ProcessQuickCreate { my %params = @_; my %ARGS = %{ $params{ARGSRef} }; my $path = $params{Path}; my @results; if ( $ARGS{'QuickCreate'} ) { my $QueueObj = RT::Queue->new($session{'CurrentUser'}); $QueueObj->Load($ARGS{Queue}) or Abort(loc("Queue could not be loaded.")); my $CFs = $QueueObj->TicketCustomFields; my ($ValidCFs, @msg) = $m->comp( '/Elements/ValidateCustomFields', CustomFields => $CFs, ARGSRef => \%ARGS, ValidateUnsubmitted => 1, ); my $created; if ( $ValidCFs ) { my ($t, $msg) = CreateTicket( Queue => $ARGS{'Queue'}, Owner => $ARGS{'Owner'}, Status => $ARGS{'Status'}, Requestors => $ARGS{'Requestors'}, Content => $ARGS{'Content'}, Subject => $ARGS{'Subject'}, ); push @results, $msg; if ( $t && $t->Id ) { $created = 1; if ( RT->Config->Get('DisplayTicketAfterQuickCreate', $session{'CurrentUser'}) ) { MaybeRedirectForResults( Actions => \@results, Path => '/Ticket/Display.html', Arguments => { id => $t->Id }, ); } } } else { push @results, loc("Can't quickly create ticket in queue [_1] because custom fields are required. Please finish by using the normal ticket creation page.", $QueueObj->Name); push @results, @msg; MaybeRedirectForResults( Actions => \@results, Path => "/Ticket/Create.html", Arguments => { (map { $_ => $ARGS{$_} } qw(Queue Owner Status Content Subject)), Requestors => $ARGS{Requestors}, # From is set above when CFs are OK, but not here since # we're not calling CreateTicket() directly. The proper # place to set a default for From, if desired in the # future, is in CreateTicket() itself, or at least # /Ticket/Display.html (which processes # /Ticket/Create.html). From is rarely used overall. }, ); } $session{QuickCreate} = \%ARGS unless $created; MaybeRedirectForResults( Actions => \@results, Path => $path, ); } return @results; } =head2 GetPrincipalsMap OBJECT, CATEGORIES Returns an array suitable for passing to /Admin/Elements/EditRights with the principal collections mapped from the categories given. The return value is an array of arrays, where the inner arrays are like: [ 'Category name' => $CollectionObj => 'DisplayColumn' => 1 ] The last value is a boolean determining if the value of DisplayColumn should be loc()-ed before display. =cut sub GetPrincipalsMap { my $object = shift; my @map; for (@_) { if (/System/) { my $system = RT::Groups->new($session{'CurrentUser'}); $system->LimitToSystemInternalGroups(); $system->OrderBy( FIELD => 'Name', ORDER => 'ASC' ); push @map, [ 'System' => $system, # loc_left_pair 'Name' => 1, ]; } elsif (/Groups/) { my $groups = RT::Groups->new($session{'CurrentUser'}); $groups->LimitToUserDefinedGroups(); $groups->OrderBy( FIELD => 'Name', ORDER => 'ASC' ); # Only show groups who have rights granted on this object $groups->WithGroupRight( Right => '', Object => $object, IncludeSystemRights => 0, IncludeSubgroupMembers => 0, ); push @map, [ 'User Groups' => $groups, # loc_left_pair 'Label' => 0 ]; } elsif (/Roles/) { my $roles = RT::Groups->new($session{'CurrentUser'}); if ($object->isa("RT::CustomField")) { # If we're a custom field, show the global roles for our LookupType. my $class = $object->RecordClassFromLookupType; if ($class and $class->DOES("RT::Record::Role::Roles")) { $roles->LimitToRolesForObject(RT->System); $roles->Limit( FIELD => "Name", FUNCTION => 'LOWER(?)', OPERATOR => "IN", VALUE => [ map {lc $_} $class->Roles ], CASESENSITIVE => 1, ); } else { # No roles to show; so show nothing undef $roles; } } else { $roles->LimitToRolesForObject($object); } if ($roles) { $roles->OrderBy( FIELD => 'Name', ORDER => 'ASC' ); push @map, [ 'Roles' => $roles, # loc_left_pair 'Label' => 0 ]; } } elsif (/Users/) { my $Users = RT->PrivilegedUsers->UserMembersObj(); $Users->OrderBy( FIELD => 'Name', ORDER => 'ASC' ); # Only show users who have rights granted on this object my $group_members = $Users->WhoHaveGroupRight( Right => '', Object => $object, IncludeSystemRights => 0, IncludeSubgroupMembers => 0, ); # Limit to UserEquiv groups my $groups = $Users->Join( ALIAS1 => $group_members, FIELD1 => 'GroupId', TABLE2 => 'Groups', FIELD2 => 'id', ); $Users->Limit( ALIAS => $groups, FIELD => 'Domain', VALUE => 'ACLEquivalence', CASESENSITIVE => 0 ); $Users->Limit( ALIAS => $groups, FIELD => 'Name', VALUE => 'UserEquiv', CASESENSITIVE => 0 ); push @map, [ 'Users' => $Users, # loc_left_pair 'Format' => 0 ]; } } return @map; } sub LoadCatalog { my $id = shift or Abort(loc("No catalog specified.")); my $catalog = RT::Catalog->new( $session{CurrentUser} ); $catalog->Load($id); Abort(loc("Unable to find catalog [_1]", $id)) unless $catalog->id; Abort(loc("You don't have permission to view this catalog.")) unless $catalog->CurrentUserCanSee; return $catalog; } sub LoadAsset { my $id = shift or Abort(loc("No asset ID specified.")); my $asset = RT::Asset->new( $session{CurrentUser} ); $asset->Load($id); Abort(loc("Unable to find asset #[_1]", $id)) unless $asset->id; Abort(loc("You don't have permission to view this asset.")) unless $asset->CurrentUserCanSee; return $asset; } sub ProcessAssetRoleMembers { my $object = shift; my %ARGS = (@_); my @results; for my $arg (keys %ARGS) { if ($arg =~ /^Add(User|Group)RoleMember$/) { next unless $ARGS{$arg} and $ARGS{"$arg-Role"}; my ($ok, $msg) = $object->AddRoleMember( Type => $ARGS{"$arg-Role"}, $1 => $ARGS{$arg}, ); push @results, $msg; } elsif ($arg =~ /^SetRoleMember-(.+)$/) { my $role = $1; my $group = $object->RoleGroup($role); next unless $group->id and $group->SingleMemberRoleGroup; next if $ARGS{$arg} eq $group->UserMembersObj->First->Name; my ($ok, $msg) = $object->AddRoleMember( Type => $role, User => $ARGS{$arg} || 'Nobody', ); push @results, $msg; } elsif ($arg =~ /^(Add|Remove)RoleMember-(.+)$/) { my $role = $2; my $method = $1 eq 'Add'? 'AddRoleMember' : 'DeleteRoleMember'; my $is = 'User'; if ( ($ARGS{"$arg-Type"}||'') =~ /^(User|Group)$/ ) { $is = $1; } my ($ok, $msg) = $object->$method( Type => $role, ($ARGS{$arg} =~ /\D/ ? ($is => $ARGS{$arg}) : (PrincipalId => $ARGS{$arg}) ), ); push @results, $msg; } elsif ($arg =~ /^RemoveAllRoleMembers-(.+)$/) { my $role = $1; my $group = $object->RoleGroup($role); next unless $group->id; my $gms = $group->MembersObj; while ( my $gm = $gms->Next ) { my ($ok, $msg) = $object->DeleteRoleMember( Type => $role, PrincipalId => $gm->MemberId, ); push @results, $msg; } } } return @results; } # If provided a catalog, load it and return the object. # If no catalog is passed, load the first active catalog. sub LoadDefaultCatalog { my $catalog = shift; my $catalog_obj = RT::Catalog->new($session{CurrentUser}); if ( $catalog ){ $catalog_obj->Load($catalog); RT::Logger->error("Unable to load catalog: " . $catalog) unless $catalog_obj->Id; } elsif ( $session{'DefaultCatalog'} ){ $catalog_obj->Load($session{'DefaultCatalog'}); RT::Logger->error("Unable to load remembered catalog: " . $session{'DefaultCatalog'}) unless $catalog_obj->Id; } elsif ( RT->Config->Get("DefaultCatalog") ){ $catalog_obj->Load( RT->Config->Get("DefaultCatalog") ); RT::Logger->error("Unable to load default catalog: " . RT->Config->Get("DefaultCatalog")) unless $catalog_obj->Id; } else { # If no catalog, default to the first active catalog my $catalogs = RT::Catalogs->new($session{CurrentUser}); $catalogs->UnLimit; my $candidate = $catalogs->First; $catalog_obj = $candidate if $candidate; RT::Logger->error("No active catalogs.") unless $catalog_obj and $catalog_obj->Id; } return $catalog_obj; } sub ProcessAssetsSearchArguments { my %args = ( Catalog => undef, Assets => undef, ARGSRef => undef, @_ ); my $ARGSRef = $args{'ARGSRef'}; my @PassArguments; if ($ARGSRef->{q}) { if ($ARGSRef->{q} =~ /^\d+$/) { my $asset = RT::Asset->new( $session{CurrentUser} ); $asset->Load( $ARGSRef->{q} ); RT::Interface::Web::Redirect( RT->Config->Get('WebURL')."Asset/Display.html?id=".$ARGSRef->{q} ) if $asset->id; } $args{'Assets'}->SimpleSearch( Term => $ARGSRef->{q}, Catalog => $args{Catalog} ); push @PassArguments, "q"; } elsif ( $ARGSRef->{'SearchAssets'} ){ for my $key (keys %$ARGSRef) { my $value = ref $ARGSRef->{$key} ? $ARGSRef->{$key}[0] : $ARGSRef->{$key}; next unless defined $value and length $value; my $orig_key = $key; my $negative = ($key =~ s/^!// ? 1 : 0); if ($key =~ /^(Name|Description)$/) { $args{'Assets'}->Limit( FIELD => $key, OPERATOR => ($negative ? 'NOT LIKE' : 'LIKE'), VALUE => $value, ENTRYAGGREGATOR => "AND", ); } elsif ($key eq 'Catalog') { $args{'Assets'}->LimitCatalog( OPERATOR => ($negative ? '!=' : '='), VALUE => $value, ENTRYAGGREGATOR => "AND", ); } elsif ($key eq 'Status') { $args{'Assets'}->Limit( FIELD => $key, OPERATOR => ($negative ? '!=' : '='), VALUE => $value, ENTRYAGGREGATOR => "AND", ); } elsif ($key =~ /^Role\.(.+)/) { my $role = $1; $args{'Assets'}->RoleLimit( TYPE => $role, FIELD => $_, OPERATOR => ($negative ? '!=' : '='), VALUE => $value, SUBCLAUSE => $role, ENTRYAGGREGATOR => ($negative ? "AND" : "OR"), CASESENSITIVE => 0, ) for qw/EmailAddress Name/; } elsif ($key =~ /^CF\.\{(.+?)\}$/ or $key =~ /^CF\.(.*)/) { my $cf = RT::Asset->new( $session{CurrentUser} ) ->LoadCustomFieldByIdentifier( $1 ); next unless $cf->id; if ( $value eq 'NULL' ) { $args{'Assets'}->LimitCustomField( CUSTOMFIELD => $cf->Id, OPERATOR => ($negative ? "IS NOT" : "IS"), VALUE => 'NULL', QUOTEVALUE => 0, ENTRYAGGREGATOR => "AND", ); } else { $args{'Assets'}->LimitCustomField( CUSTOMFIELD => $cf->Id, OPERATOR => ($negative ? "NOT LIKE" : "LIKE"), VALUE => $value, ENTRYAGGREGATOR => "AND", ); } } else { next; } push @PassArguments, $orig_key; } push @PassArguments, 'SearchAssets'; } if ( !$ARGSRef->{Format} ) { my $Format = RT->Config->Get('AssetSimpleSearchFormat'); $Format = $Format->{$args{'Catalog'}->id} || $Format->{$args{'Catalog'}->Name} || $Format->{''} if ref $Format; $ARGSRef->{Format} = $Format || q[ '<b><a href="__WebPath__/Asset/Display.html?id=__id__">__id__</a></b>/TITLE:#', '<b><a href="__WebPath__/Asset/Display.html?id=__id__">__Name__</a></b>/TITLE:Name', Description, Status, ]; } $ARGSRef->{OrderBy} ||= 'id'; push @PassArguments, qw/OrderBy Order Page Format/; return ( OrderBy => 'id', Order => 'ASC', Rows => 50, (map { $_ => $ARGSRef->{$_} } grep { defined $ARGSRef->{$_} } @PassArguments), PassArguments => \@PassArguments, ); } =head3 SetObjectSessionCache Convenience method to stash per-user query results in the user session. This is used for rights-intensive queries that change infrequently, such as generating the list of queues a user has access to. The method handles populating the session cache and clearing it based on CacheNeedsUpdate. It returns the cache key so callers can use $session directly after it has been created or updated. Parameters: =over =item * ObjectType, required, the object for which to fetch values =item * CheckRight, the right to check for the current user in the query =item * ShowAll, boolean, ignores the rights check =item * Default, for dropdowns, a default selected value =item * CacheNeedsUpdate, date indicating when an update happened requiring a cache clear =item * Exclude, hashref ({ Name => 1 }) of object Names to exclude from the cache =back =cut sub SetObjectSessionCache { my %args = ( CheckRight => undef, ShowAll => 1, Default => 0, CacheNeedsUpdate => undef, Exclude => undef, @_ ); my $ObjectType = $args{'ObjectType'}; $ObjectType = "RT::$ObjectType" unless $ObjectType =~ /::/; my $CheckRight = $args{'CheckRight'}; my $ShowAll = $args{'ShowAll'}; my $CacheNeedsUpdate = $args{'CacheNeedsUpdate'}; my $cache_key = GetObjectSessionCacheKey( ObjectType => $ObjectType, CheckRight => $CheckRight, ShowAll => $ShowAll ); if ( defined $session{$cache_key} && !$session{$cache_key}{id} ) { delete $session{$cache_key}; } if ( defined $session{$cache_key} && ref $session{$cache_key} eq 'ARRAY') { delete $session{$cache_key}; } if ( defined $session{$cache_key} && defined $CacheNeedsUpdate && $session{$cache_key}{lastupdated} <= $CacheNeedsUpdate ) { delete $session{$cache_key}; } if ( not defined $session{$cache_key} ) { my $collection = "${ObjectType}s"->new($session{'CurrentUser'}); $collection->UnLimit; $HTML::Mason::Commands::m->callback( CallbackName => 'ModifyCollection', CallbackPage => '/Elements/Quicksearch', ARGSRef => \%args, Collection => $collection, ObjectType => $ObjectType ); $session{$cache_key}{id} = {}; while (my $object = $collection->Next) { if ($ShowAll or not $CheckRight or $session{CurrentUser}->HasRight( Object => $object, Right => $CheckRight )) { next if $args{'Exclude'} and exists $args{'Exclude'}->{$object->Name}; push @{$session{$cache_key}{objects}}, { Id => $object->Id, Name => $object->Name, Description => $object->_Accessible("Description" => "read") ? $object->Description : undef, Lifecycle => $object->_Accessible("Lifecycle" => "read") ? $object->Lifecycle : undef, }; $session{$cache_key}{id}{ $object->id } = 1; } } $session{$cache_key}{lastupdated} = time(); } return $cache_key; } sub GetObjectSessionCacheKey { my %args = ( CurrentUser => undef, ObjectType => '', CheckRight => '', ShowAll => 1, @_ ); my $cache_key = join "---", "SelectObject", $args{'ObjectType'}, $session{'CurrentUser'}->Id, $args{'CheckRight'}, $args{'ShowAll'}; return $cache_key; } =head2 _load_container_object ( $type, $id ); Instantiate container object for saving searches. =cut sub _load_container_object { my ( $obj_type, $obj_id ) = @_; return RT::SavedSearch->new( $session{'CurrentUser'} )->_load_privacy_object( $obj_type, $obj_id ); } =head2 _parse_saved_search ( $arg ); Given a serialization string for saved search, and returns the container object and the search id. =cut sub _parse_saved_search { my $spec = shift; return unless $spec; if ( $spec !~ /^(.*?)-(\d+)-SavedSearch-(\d+)$/ ) { return; } my $obj_type = $1; my $obj_id = $2; my $search_id = $3; return ( _load_container_object( $obj_type, $obj_id ), $search_id ); } =head2 ScrubHTML content Removes unsafe and undesired HTML from the passed content =cut sub ScrubHTML { state $scrubber = RT::Interface::Web::Scrubber->new; return $scrubber->scrub(@_); } =head2 JSON Redispatches to L<RT::Interface::Web/EncodeJSON> =cut sub JSON { RT::Interface::Web::EncodeJSON(@_); } sub CSSClass { my $value = shift; return '' unless defined $value; $value =~ s/[^A-Za-z0-9_-]/_/g; return $value; } sub GetCustomFieldInputName { RT::Interface::Web::GetCustomFieldInputName(@_); } sub GetCustomFieldInputNamePrefix { RT::Interface::Web::GetCustomFieldInputNamePrefix(@_); } =head2 LoadTransaction id Takes a transaction id as its only variable. if it's handed an array, it takes the first value. Returns an RT::Transaction object as the current user. =cut sub LoadTransaction { my $id = shift; if ( ref($id) eq "ARRAY" ) { $id = $id->[0]; } unless ($id) { Abort( loc('No transaction specified'), Code => HTTP::Status::HTTP_BAD_REQUEST ); } my $Transaction = RT::Transaction->new( $session{'CurrentUser'} ); $Transaction->Load($id); unless ( $Transaction->id ) { Abort( loc( 'Could not load transaction #[_1]', $id ), Code => HTTP::Status::HTTP_NOT_FOUND ); } return $Transaction; } =head2 GetDefaultQueue Processes global and user-level configuration options to find the default queue for the current user. Accepts no arguments, returns the ID of the default queue, if found, or undef. =cut sub GetDefaultQueue { my $queue; # RememberDefaultQueue tracks the last queue used by this user, if set. if ( $session{'DefaultQueue'} && RT->Config->Get( "RememberDefaultQueue", $session{'CurrentUser'} ) ) { $queue = $session{'DefaultQueue'}; } else { $queue = RT->Config->Get( "DefaultQueue", $session{'CurrentUser'} ); } return $queue; } =head2 UpdateDashboard Update global and user-level dashboard preferences. For arguments, takes submitted args from the page and a hashref of available items. Gets additional information for submitted items from the hashref of available items, since the args can't contain all information about the item. =cut sub UpdateDashboard { my $args = shift; my $available_items = shift; my $id = $args->{dashboard_id}; my $data = { "dashboard_id" => $id, "panes" => { "body" => [], "sidebar" => [] } }; foreach my $arg (qw{ body sidebar }) { my $pane = $arg; my $values = $args->{$pane}; next unless $values; # force value to an arrayref so we can handle both single and multiple members of each pane. $values = [$values] unless ref $values; foreach my $value ( @{$values} ) { $value =~ m/^(\w+)-(.+)$/i; my $type = $1; my $name = $2; push @{ $data->{panes}->{$pane} }, { type => $type, name => $name }; } } my ( $ok, $msg ); if ( $id eq 'MyRT' ) { my $user = $session{CurrentUser}; if ( my $user_id = $args->{user_id} ) { my $UserObj = RT::User->new( $session{'CurrentUser'} ); ( $ok, $msg ) = $UserObj->Load($user_id); return ( $ok, $msg ) unless $ok; return ( $ok, $msg ) = $UserObj->SetPreferences( 'HomepageSettings', $data->{panes} ); } elsif ( $args->{is_global} ) { my $sys = RT::System->new( $session{'CurrentUser'} ); my ($default_portlets) = $sys->Attributes->Named('HomepageSettings'); return ( $ok, $msg ) = $default_portlets->SetContent( $data->{panes} ); } else { return ( $ok, $msg ) = $user->SetPreferences( 'HomepageSettings', $data->{panes} ); } } else { my $class = $args->{self_service_dashboard} ? 'RT::Dashboard::SelfService' : 'RT::Dashboard'; my $Dashboard = $class->new( $session{'CurrentUser'} ); ( $ok, $msg ) = $Dashboard->LoadById($id); # report error at the bottom return ( $ok, $msg ) unless $ok && $Dashboard->Id; my $content; for my $pane_name ( keys %{ $data->{panes} } ) { my @pane; for my $item ( @{ $data->{panes}{$pane_name} } ) { my %saved; $saved{pane} = $pane_name; $saved{portlet_type} = $item->{type}; $saved{description} = $available_items->{ $item->{type} }{ $item->{name} }{label}; if ( $item->{type} eq 'component' ) { $saved{component} = $item->{name}; # Absolute paths stay absolute, relative paths go into # /Elements. This way, extensions that add portlets work. my $path = $item->{name}; $path = "/Elements/$path" if substr( $path, 0, 1 ) ne '/'; $saved{path} = $path; } elsif ( $item->{type} eq 'saved' ) { $saved{portlet_type} = 'search'; $item->{searchType} = $available_items->{ $item->{type} }{ $item->{name} }{search_type} if exists $available_items->{ $item->{type} }{ $item->{name} }{search_type}; my $type = $item->{searchType}; $type = 'Saved Search' if !$type || $type eq 'Ticket'; $saved{description} = loc($type) . ': ' . $saved{description}; $item->{searchId} = $available_items->{ $item->{type} }{ $item->{name} }{search_id} if exists $available_items->{ $item->{type} }{ $item->{name} }{search_id}; my ( $obj_type, $obj_id, undef, $search_id ) = split '-', $item->{name}; $saved{privacy} = "$obj_type-$obj_id"; $saved{id} = $search_id; } elsif ( $item->{type} eq 'dashboard' ) { my ( undef, $dashboard_id, $obj_type, $obj_id ) = split '-', $item->{name}; $saved{privacy} = "$obj_type-$obj_id"; $saved{id} = $dashboard_id; $saved{description} = loc('Dashboard') . ': ' . $saved{description}; } push @pane, \%saved; } $content->{$pane_name} = \@pane; } return ( $ok, $msg ) = $Dashboard->Update( Panes => $content ); } } =head2 ListOfReports Returns the list of reports registered with RT. =cut sub ListOfReports { # TODO: Make this a dynamic list generated by loading files in the Reports # directory my $list_of_reports = [ { id => 'resolvedbyowner', title => 'Resolved by owner', # loc path => '/Reports/ResolvedByOwner.html', }, { id => 'resolvedindaterange', title => 'Resolved in date range', # loc path => '/Reports/ResolvedByDates.html', }, { id => 'createdindaterange', title => 'Created in a date range', # loc path => '/Reports/CreatedByDates.html', }, ]; return $list_of_reports; } =head2 ProcessCustomDateRanges ARGSRef => ARGSREF, UserPreference => 0|1 For system database configuration, it adds corresponding arguments to the passed ARGSRef, and the following code on EditConfig.html page will do the real update job. For user preference, it updates attributes accordingly. Returns an array of results messages. =cut sub ProcessCustomDateRanges { my %args = ( ARGSRef => undef, UserPreference => 0, @_ ); my $args_ref = $args{ARGSRef}; my ( $config, $content ); if ( $args{UserPreference} ) { $config = { 'RT::Ticket' => { RT::Ticket->CustomDateRanges( ExcludeUser => $session{CurrentUser}->Id ) } }; $content = $session{CurrentUser}->Preferences('CustomDateRanges'); # SetPreferences also checks rights, we short-circuit to avoid # returning misleading messages. return ( 0, loc("No permission to set preferences") ) unless $session{CurrentUser}->CurrentUserCanModify('Preferences'); } else { $config = RT->Config->Get('CustomDateRanges'); my $db_config = RT::Configuration->new( $session{CurrentUser} ); $db_config->LoadByCols( Name => 'CustomDateRangesUI', Disabled => 0 ); $content = $db_config->_DeserializeContent( $db_config->Content ) if $db_config->id; } my @results; my %label = ( from => 'From', # loc to => 'To', # loc from_fallback => 'From Value if Unset', # loc to_fallback => 'To Value if Unset', # loc ); my $need_save; if ($content) { my @current_names = sort keys %{ $content->{'RT::Ticket'} }; for my $id ( 0 .. $#current_names ) { my $current_name = $current_names[$id]; my $spec = $content->{'RT::Ticket'}{$current_name}; my $name = $args_ref->{"$id-name"}; if ( $args_ref->{"$id-Delete"} ) { delete $content->{'RT::Ticket'}{$current_name}; push @results, loc( 'Deleted [_1]', $current_name ); $need_save ||= 1; next; } if ( $config && $config->{'RT::Ticket'}{$name} ) { push @results, loc( "[_1] already exists", $name ); next; } my $updated; for my $field (qw/from from_fallback to to_fallback/) { next if ( $spec->{$field} // '' ) eq $args_ref->{"$id-$field"}; if (( $args_ref->{"$id-$field"} && RT::Ticket->_ParseCustomDateRangeSpec( $name, join ' - ', 'now', $args_ref->{"$id-$field"} ) ) || ( !$args_ref->{"$id-$field"} && $field =~ /fallback/ ) ) { $spec->{$field} = $args_ref->{"$id-$field"}; $updated ||= 1; } else { push @results, loc( 'Invalid [_1] for [_2]', loc( $label{$field} ), $name ); next; } } if ( $spec->{business_time} != $args_ref->{"$id-business_time"} ) { $spec->{business_time} = $args_ref->{"$id-business_time"}; $updated ||= 1; } $content->{'RT::Ticket'}{$name} = $spec; if ( $name ne $current_name ) { delete $content->{'RT::Ticket'}{$current_name}; $updated ||= 1; } if ($updated) { push @results, loc( 'Updated [_1]', $name ); $need_save ||= 1; } } } if ( $args_ref->{name} ) { for my $field (qw/from from_fallback to to_fallback business_time/) { $args_ref->{$field} = [ $args_ref->{$field} ] unless ref $args_ref->{$field}; } my $i = 0; for my $name ( @{ $args_ref->{name} } ) { if ($name) { if ( $config && $config->{'RT::Ticket'}{$name} || $content && $content->{'RT::Ticket'}{$name} ) { push @results, loc( "[_1] already exists", $name ); $i++; next; } } else { $i++; next; } my $spec = { business_time => $args_ref->{business_time}[$i] }; for my $field (qw/from from_fallback to to_fallback/) { if (( $args_ref->{$field}[$i] && RT::Ticket->_ParseCustomDateRangeSpec( $name, join ' - ', 'now', $args_ref->{$field}[$i] ) ) || ( !$args_ref->{$field}[$i] && $field =~ /fallback/ ) ) { $spec->{$field} = $args_ref->{$field}[$i]; } else { push @results, loc( 'Invalid [_1] for [_2]', loc($field), $name ); $i++; next; } } $content->{'RT::Ticket'}{$name} = $spec; push @results, loc( 'Created [_1]', $name ); $need_save ||= 1; $i++; } } if ($need_save) { if ( $args{UserPreference} ) { my ( $ret, $msg ); if ( keys %{$content->{'RT::Ticket'}} ) { ( $ret, $msg ) = $session{CurrentUser}->SetPreferences( 'CustomDateRanges', $content ); } else { ( $ret, $msg ) = $session{CurrentUser}->DeletePreferences( 'CustomDateRanges' ); } unless ($ret) { RT->Logger->error($msg); push @results, $msg; } } else { $args_ref->{'CustomDateRangesUI-Current'} = ''; # EditConfig.html needs this to update CustomDateRangesUI $args_ref->{CustomDateRangesUI} = $content; } } return @results; } =head2 ProcessAuthToken ARGSRef => ARGSREF Returns an array of results messages. =cut sub ProcessAuthToken { my %args = ( ARGSRef => undef, @_ ); my $args_ref = $args{ARGSRef}; my @results; my $token = RT::AuthToken->new( $session{CurrentUser} ); if ( $args_ref->{Create} ) { # Don't require password for systems with some form of federated auth # or if configured to not require a password my %res = $session{'CurrentUser'}->CurrentUserRequireToSetPassword(); my $require_password = 1; if ( RT->Config->Get('DisablePasswordForAuthToken') or not $res{'CanSet'}) { $require_password = 0; } if ( !length( $args_ref->{Description} ) ) { push @results, loc("Description cannot be blank."); } elsif ( $require_password && !length( $args_ref->{Password} ) ) { push @results, loc("Please enter your current password."); } elsif ( $require_password && !$session{CurrentUser}->IsPassword( $args_ref->{Password} ) ) { push @results, loc("Please enter your current password correctly."); } else { my ( $ok, $msg, $auth_string ) = $token->Create( Owner => $args_ref->{Owner}, Description => $args_ref->{Description}, ); if ($ok) { push @results, $msg; push @results, loc( '"[_1]" is your new authentication token. Treat it carefully like a password. Please save it now because you cannot access it again.', $auth_string ); } else { push @results, loc('Unable to create a new authentication token. Contact your RT administrator.'); RT->Logger->error('Unable to create authentication token: ' . $msg); } } } elsif ( $args_ref->{Update} || $args_ref->{Revoke} ) { $token->Load( $args_ref->{Token} ); if ( $token->Id ) { if ( $args_ref->{Update} ) { if ( length( $args_ref->{Description} ) ) { if ( $args_ref->{Description} ne $token->Description ) { my ( $ok, $msg ) = $token->SetDescription( $args_ref->{Description} ); push @results, $msg; } } else { push @results, loc("Description cannot be blank."); } } elsif ( $args_ref->{Revoke} ) { my ( $ok, $msg ) = $token->Delete; push @results, $msg; } } else { push @results, loc("Could not find token: [_1]", $args_ref->{Token}); } } return @results; } =head3 CachedCustomFieldValues FIELD Similar to FIELD->Values, but caches the return value of FIELD->Values in $m->notes in anticipation of it being used again. =cut sub CachedCustomFieldValues { my $cf = shift; my $key = 'CF-' . $cf->Id . '-Values'; if ($m->notes($key)) { # Reset the iterator so we always start from the beginning $m->notes($key)->GotoFirstItem; return $m->notes($key); } # Wasn't in the cache; grab it and cache it. $m->notes($key, $cf->Values); return $m->notes($key); } package RT::Interface::Web; RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/REST.pm�������������������������������������������������������������������000644 �000765 �000024 �00000030151 14005011336 017107� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Interface::REST; use LWP::MediaTypes qw(guess_media_type); use strict; use warnings; use RT; use base 'Exporter'; our @EXPORT = qw(expand_list form_parse form_compose vpush vsplit process_attachments); sub custom_field_spec { my $self = shift; my $capture = shift; my $CF_name = '[^,]+'; $CF_name = '(' . $CF_name . ')' if $capture; my $new_style = 'CF\.\{'.$CF_name.'\}'; my $old_style = 'C(?:ustom)?F(?:ield)?-'.$CF_name; return '(?i:' . join('|', $new_style, $old_style) . ')'; } sub field_spec { my $self = shift; my $capture = shift; my $field = '[a-z][a-z0-9_-]*'; $field = '(' . $field . ')' if $capture; my $custom_field = __PACKAGE__->custom_field_spec($capture); return '(?i:' . join('|', $field, $custom_field) . ')'; } # WARN: this code is duplicated in bin/rt.in, # change both functions at once sub expand_list { my ($list) = @_; my @elts; foreach (split /\s*,\s*/, $list) { push @elts, /^(\d+)-(\d+)$/? ($1..$2): $_; } return map $_->[0], # schwartzian transform sort { defined $a->[1] && defined $b->[1]? # both numbers $a->[1] <=> $b->[1] :!defined $a->[1] && !defined $b->[1]? # both letters $a->[2] cmp $b->[2] # mix, number must be first :defined $a->[1]? -1: 1 } map [ $_, (defined( /^(\d+)$/ )? $1: undef), lc($_) ], @elts; } # Returns a reference to an array of parsed forms. sub form_parse { my $state = 0; my @forms = (); my @lines = split /\n/, $_[0]; my ($c, $o, $k, $e) = ("", [], {}, ""); my $field = __PACKAGE__->field_spec; LINE: while (@lines) { my $line = shift @lines; next LINE if $line eq ''; if ($line eq '--') { # We reached the end of one form. We'll ignore it if it was # empty, and store it otherwise, errors and all. if ($e || $c || @$o) { push @forms, [ $c, $o, $k, $e ]; $c = ""; $o = []; $k = {}; $e = ""; } $state = 0; } elsif ($state != -1) { if ($state == 0 && $line =~ /^#/) { # Read an optional block of comments (only) at the start # of the form. $state = 1; $c = $line; while (@lines && $lines[0] =~ /^#/) { $c .= "\n".shift @lines; } $c .= "\n"; } elsif ($state <= 1 && $line =~ /^($field):(?:\s+(.*))?$/i) { # Read a field: value specification. my $f = $1; my @v = ($2); $v[0] = '' unless defined $v[0]; # Read continuation lines, if any. while (@lines && ($lines[0] eq '' || $lines[0] =~ /^\s+/)) { push @v, shift @lines; } pop @v while (@v && $v[-1] eq ''); # Strip longest common leading indent from text. my $ws = (""); foreach my $ls (map {/^(\s+)/} @v[1..$#v]) { $ws = $ls if (!$ws || length($ls) < length($ws)); } s/^$ws// foreach @v; shift @v while (@v && $v[0] eq ''); push(@$o, $f) unless exists $k->{$f}; vpush($k, $f, join("\n", @v)); $state = 1; } elsif ($line =~ /^#/) { # We've found a syntax error, so we'll reconstruct the # form parsed thus far, and add an error marker. (>>) $state = -1; $e = form_compose([[ "", $o, $k, "" ]]); $e.= $line =~ /^>>/ ? "$line\n" : ">> $line\n"; } } else { # We saw a syntax error earlier, so we'll accumulate the # contents of this form until the end. $e .= "$line\n"; } } push(@forms, [ $c, $o, $k, $e ]) if ($e || $c || @$o); foreach my $l (keys %$k) { $k->{$l} = vsplit($k->{$l}) if (ref $k->{$l} eq 'ARRAY'); } return \@forms; } # Returns text representing a set of forms. sub form_compose { my ($forms) = @_; my (@text); foreach my $form (@$forms) { my ($c, $o, $k, $e) = @$form; my $text = ""; if ($c) { $c =~ s/\n*$/\n/; $text = "$c\n"; } if ($e) { $text .= $e; } elsif ($o) { my (@lines); foreach my $key (@$o) { my ($line, $sp); my @values = (ref $k->{$key} eq 'ARRAY') ? @{ $k->{$key} } : $k->{$key}; $sp = " "x(length("$key: ")); $sp = " "x4 if length($sp) > 16; foreach my $v (@values) { $v = '' unless defined $v; if ( $v =~ /\n/) { $v =~ s/^/$sp/gm; $v =~ s/^$sp//; if ($line) { push @lines, "$line\n\n"; $line = ""; } elsif (@lines && $lines[-1] !~ /\n\n$/) { $lines[-1] .= "\n"; } push @lines, "$key: $v\n\n"; } elsif ($line && length($line)+length($v)-rindex($line, "\n") >= 70) { $line .= ",\n$sp$v"; } else { $line = $line ? "$line, $v" : "$key: $v"; } } $line = "$key:" unless @values; if ($line) { if ($line =~ /\n/) { if (@lines && $lines[-1] !~ /\n\n$/) { $lines[-1] .= "\n"; } $line .= "\n"; } push @lines, "$line\n"; } } $text .= join "", @lines; } else { chomp $text; } push @text, $text; } return join "\n--\n\n", @text; } # Add a value to a (possibly multi-valued) hash key. sub vpush { my ($hash, $key, $val) = @_; my @val = ref $val eq 'ARRAY' ? @$val : $val; if (exists $hash->{$key}) { unless (ref $hash->{$key} eq 'ARRAY') { my @v = $hash->{$key} ne '' ? $hash->{$key} : (); $hash->{$key} = \@v; } push @{ $hash->{$key} }, @val; } else { $hash->{$key} = $val; } } # "Normalise" a hash key that's known to be multi-valued. sub vsplit { my ($val, $strip) = @_; my @words; my @values = map {split /\n/} (ref $val eq 'ARRAY' ? @$val : $val); foreach my $line (@values) { while ($line =~ /\S/) { $line =~ s/^ \s* # Trim leading whitespace (?: (") # Quoted string ((?>[^\\"]*(?:\\.[^\\"]*)*))" | (') # Single-quoted string ((?>[^\\']*(?:\\.[^\\']*)*))' | q\{(.*?)\} # A perl-ish q{} string; this does # no paren balancing, however, and # only exists for back-compat | (.*?) # Anything else, until the next comma ) \s* # Trim trailing whitespace (?: \Z # Finish at end-of-line | , # Or a comma ) //xs or last; # There should be no way this match # fails, but add a failsafe to # prevent infinite-looping if it # somehow does. my ($quote, $quoted) = ($1 ? ($1, $2) : $3 ? ($3, $4) : ('', $5 || $6)); # Only unquote the quote character, or the backslash -- and # only if we were originally quoted.. if ($5) { $quoted =~ s/([\\'])/\\$1/g; $quote = "'"; } if ($strip) { $quoted =~ s/\\([\\$quote])/$1/g if $quote; push @words, $quoted; } else { push @words, "$quote$quoted$quote"; } } } return \@words; } sub process_attachments { my $entity = shift; my @list = @_; return 1 unless @list; my $m = $HTML::Mason::Commands::m; my $cgi = $m->cgi_object; my $i = 1; foreach my $e ( @list ) { my $fh = $cgi->upload("attachment_$i"); return (0, "No attachment for $e") unless $fh; local $/=undef; my $file = $e; $file =~ s#^.*[\\/]##; my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 ); my $buf; while (sysread($fh, $buf, 8192)) { syswrite($tmp_fh, $buf); } my $info = $cgi->uploadInfo($fh); # If Content-ID exists for attachment then we need multipart/related # to be able to refer to this Content-Id in core of mime message if($info->{'Content-ID'}) { $entity->head->set('Content-Type', 'multipart/related'); } my $new_entity = $entity->attach( Path => $tmp_fn, Type => $info->{'Content-Type'} || guess_media_type($tmp_fn), Filename => $file, Disposition => $info->{'Content-Disposition'} || "attachment", 'Content-ID' => $info->{'Content-ID'}, ); $new_entity->bodyhandle->{'_dirty_hack_to_save_a_ref_tmp_fh'} = $tmp_fh; $i++; } return (1); } RT::Base->_ImportOverlays(); 1; =head1 NAME RT::Interface::REST - helper functions for the REST interface. =head1 SYNOPSIS Only the REST should use this module. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Web/����������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 016511� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/CLI.pm��������������������������������������������������������������������000644 �000765 �000024 �00000013276 14005011336 016752� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Interface::CLI; use strict; use warnings; use RT::Base; use base 'Exporter'; our @EXPORT_OK = qw(CleanEnv GetCurrentUser debug loc Init); =head1 NAME RT::Interface::CLI - helper functions for creating a commandline RT interface =head1 SYNOPSIS use lib "/opt/rt5/local/lib", "/opt/rt5/lib"; use RT::Interface::CLI qw(GetCurrentUser Init loc); # Process command-line arguments, load the configuration, and connect # to the database Init(); # Get the current user all loaded my $CurrentUser = GetCurrentUser(); print loc('Hello!'); # Synonym of $CurrentUser->loc('Hello!'); =head1 DESCRIPTION =head1 METHODS =cut { my $CurrentUser; # shared betwen GetCurrentUser and loc =head2 GetCurrentUser Figures out the uid of the current user and returns an RT::CurrentUser object loaded with that user. if the current user isn't found, returns a copy of RT::Nobody. =cut sub GetCurrentUser { require RT::CurrentUser; #Instantiate a user object my $Gecos= (getpwuid($<))[0]; #If the current user is 0, then RT will assume that the User object #is that of the currentuser. $CurrentUser = RT::CurrentUser->new(); $CurrentUser->LoadByGecos($Gecos); unless ($CurrentUser->Id) { $RT::Logger->error("No user with a GECOS (unix login) of '$Gecos' was found."); } return($CurrentUser); } =head2 loc Synonym of $CurrentUser->loc(). =cut sub loc { die "No current user yet" unless $CurrentUser ||= RT::CurrentUser->new; return $CurrentUser->loc(@_); } } sub ShowHelp { my $self = shift; my %args = @_; require Pod::Usage; Pod::Usage::pod2usage( -message => $args{'Message'}, -exitval => $args{'ExitValue'} || 0, -verbose => 99, -sections => $args{'Sections'} || ($args{'ExitValue'} ? 'NAME|USAGE' : 'NAME|USAGE|OPTIONS|DESCRIPTION' ), ); } =head2 Init A shim for L<Getopt::Long/GetOptions> which automatically adds a C<--help> option if it is not supplied. It then calls L<RT/LoadConfig> and L<RT/Init>. It sets the C<LogToSTDERR> setting to C<warning>, to ensure that the user sees all relevant warnings. It also adds C<--quiet> and C<--verbose> options, which adjust the C<LogToSTDERR> value to C<error> or C<debug>, respectively. If C<debug> is provided as a parameter, it added as an alias for C<--verbose>. =cut sub Init { require Getopt::Long; require Pod::Usage; my %exists; my @args; my $hash; if (ref $_[0]) { $hash = shift(@_); for (@_) { m/^([a-zA-Z0-9-]+)/; $exists{$1}++; push @args, $_ => \($hash->{$1}); } } else { $hash = {}; @args = @_; while (@_) { my $key = shift(@_); $exists{$key}++; shift(@_); } } push @args, "help|h!" => \($hash->{help}) unless $exists{help}; push @args, "verbose|v!" => \($hash->{verbose}) unless $exists{verbose}; push @args, "debug!" => \($hash->{verbose}) if $exists{debug}; push @args, "quiet|q!" => \($hash->{quiet}) unless $exists{quiet}; my $ok = Getopt::Long::GetOptions( @args ); Pod::Usage::pod2usage(1) if not $ok and not defined wantarray; return unless $ok; Pod::Usage::pod2usage({ verbose => 2}) if not $exists{help} and $hash->{help}; require RT; RT::LoadConfig(); if (not $exists{quiet} and $hash->{quiet}) { RT->Config->Set(LogToSTDERR => "error"); } elsif (not $exists{verbose} and $hash->{verbose}) { RT->Config->Set(LogToSTDERR => "debug"); } else { RT->Config->Set(LogToSTDERR => "warning"); } RT::Init(); $| = 1; return $ok; } RT::Base->_ImportOverlays(); 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Email.pm������������������������������������������������������������������000644 �000765 �000024 �00000144161 14005011336 017370� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Interface::Email; use strict; use warnings; use 5.010; use RT::Interface::Email::Crypt; use Email::Address; use MIME::Entity; use RT::EmailParser; use File::Temp; use Mail::Mailer (); use Text::ParseWords qw/shellwords/; use RT::Util 'safe_run_child'; use File::Spec; use MIME::Words (); use Scope::Upper qw/unwind HERE/; use 5.010; =head1 NAME RT::Interface::Email - helper functions for parsing and sending email =head1 METHODS =head2 RECEIVING MAIL =head3 Gateway ARGSREF Takes parameters: =over =item C<action> A C<-> separated list of actions to run. Standard actions, as detailed in L<bin/rt-mailgate>, are C<comment> and C<correspond>. The L<RT::Interface::Email::Action::Take> and L<RT::Interface::Email::Action::Resolve> plugins can be added to L<RT_Config/@MailPlugins> to provide C<take> and C<resolve> actions, respectively. =item C<queue> The queue that tickets should be created in, if no ticket id is found on the message. Can be either a name or an id; defaults to 1. =item C<message> The content of the message, as obtained from the MTA. =item C<ticket> Optional; this ticket id overrides any ticket number derived from the subject. =back Secrypts and verifies the message, decodes the transfer encoding, determines the user that the mail was sent from, and performs the given actions. Returns a list of C<(status, message, ticket)>. The C<status> is -75 for a temporary failure (to be retried later bt the MTA), 0 for a permanent failure which did not result in a ticket, and 1 for a ticket that was found and acted on. =cut my $SCOPE; sub TMPFAIL { unwind (-75, $_[0], undef, => $SCOPE) } sub FAILURE { unwind ( 0, $_[0], $_[1], => $SCOPE) } sub SUCCESS { unwind ( 1, "Success", $_[0], => $SCOPE) } sub Gateway { my $argsref = shift; my %args = ( action => 'correspond', queue => '1', ticket => undef, message => undef, %$argsref ); RT->Config->RefreshConfigFromDatabase(); RT->System->MaybeRebuildLifecycleCache(); # Set the scope to return from with TMPFAIL/FAILURE/SUCCESS $SCOPE = HERE; # Validate the actions my @actions = grep $_, split /-/, $args{action}; for my $action (@actions) { TMPFAIL( "Invalid 'action' parameter $action for queue $args{queue}" ) unless Plugins(Method => "Handle" . ucfirst($action)); } my $parser = RT::EmailParser->new(); $parser->SmartParseMIMEEntityFromScalar( Message => $args{'message'}, Decode => 0, Exact => 1, ); my $Message = $parser->Entity(); unless ($Message) { MailError( Subject => "RT Bounce: Unparseable message", Explanation => "RT couldn't process the message below", Attach => $args{'message'}, FAILURE => 1, ); } #Set up a queue object my $SystemQueueObj = RT::Queue->new( RT->SystemUser ); $SystemQueueObj->Load( $args{'queue'} ); for my $Code ( Plugins(Method => "BeforeDecrypt") ) { $Code->( Message => $Message, RawMessageRef => \$args{'message'}, Queue => $SystemQueueObj, Actions => \@actions, ); } RT::Interface::Email::Crypt::VerifyDecrypt( Message => $Message, RawMessageRef => \$args{'message'}, Queue => $SystemQueueObj, ); for my $Code ( Plugins(Method => "BeforeDecode") ) { $Code->( Message => $Message, RawMessageRef => \$args{'message'}, Queue => $SystemQueueObj, Actions => \@actions, ); } $parser->_DecodeBodies; $parser->RescueOutlook; $parser->_PostProcessNewEntity; my $head = $Message->head; my $From = Encode::decode( "UTF-8", $head->get("From") ); chomp $From if defined $From; #Pull apart the subject line my $Subject = Encode::decode( "UTF-8", $head->get('Subject') || ''); chomp $Subject; # Lets check for mail loops of various sorts. my $ErrorsTo = ParseErrorsToAddressFromHead( $head ); $ErrorsTo = RT->Config->Get('OwnerEmail') if IsMachineGeneratedMail( Message => $Message, Subject => $Subject, ); # Make all errors from here on out bounce back to $ErrorsTo my $bare_MailError = \&MailError; no warnings 'redefine'; local *MailError = sub { $bare_MailError->(To => $ErrorsTo, MIMEObj => $Message, @_) }; $args{'ticket'} ||= ExtractTicketId( $Message ); my $SystemTicket = RT::Ticket->new( RT->SystemUser ); $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ; # We can safely have no queue of we have a known-good ticket TMPFAIL("RT couldn't find the queue: " . $args{'queue'}) unless $SystemTicket->id || $SystemQueueObj->id; my $CurrentUser = GetCurrentUser( Message => $Message, RawMessageRef => \$args{message}, Ticket => $SystemTicket, Queue => $SystemQueueObj, ); # We only care about ACLs on the _first_ action, as later actions # may have gotten rights by the time they happen. CheckACL( Action => $actions[0], Message => $Message, CurrentUser => $CurrentUser, Ticket => $SystemTicket, Queue => $SystemQueueObj, ); $head->replace('X-RT-Interface' => 'Email'); my $Ticket = RT::Ticket->new($CurrentUser); $Ticket->Load( $SystemTicket->Id ); for my $action (@actions) { HandleAction( Action => $action, Subject => $Subject, Message => $Message, CurrentUser => $CurrentUser, Ticket => $Ticket, TicketId => $args{ticket}, Queue => $SystemQueueObj, ); } SUCCESS( $Ticket ); } =head3 Plugins Method => C<name>, Code => 0 Returns the list of subroutine references for the given method C<name> from the configured L<RT_Config/@MailPlugins>. If C<Code> is passed a true value, includes anonymous subroutines found in C<@MailPlugins>. =cut sub Plugins { my %args = ( Add => undef, Code => 0, Method => undef, @_ ); state $INIT; state @PLUGINS; if ($args{Add} or !$INIT) { my @mail_plugins = $INIT ? () : RT->Config->Get('MailPlugins'); push @mail_plugins, @{$args{Add}} if $args{Add}; foreach my $plugin (@mail_plugins) { if ( ref($plugin) eq "CODE" ) { push @PLUGINS, $plugin; } elsif ( !ref $plugin ) { my $Class = $plugin; $Class = "RT::Interface::Email::" . $Class unless $Class =~ /^RT::/; $Class->require or do { $RT::Logger->error("Couldn't load $Class: $@"); next }; unless ( $Class->DOES( "RT::Interface::Email::Role" ) ) { $RT::Logger->crit( "$Class does not implement RT::Interface::Email::Role. Mail plugins from RT 4.2 and earlier are not forward-compatible with RT 4.4."); next; } push @PLUGINS, $Class; } else { $RT::Logger->crit( "$plugin - is not class name or code reference"); } } $INIT = 1; } my @list = @PLUGINS; @list = grep {not ref} @list unless $args{Code}; @list = grep {$_} map {ref $_ ? $_ : $_->can($args{Method})} @list if $args{Method}; return @list; } =head3 GetCurrentUser Message => C<message>, Ticket => C<ticket>, Queue => C<queue> Dispatches to the C<@MailPlugins> to find one the provides C<GetCurrentUser> that recognizes the current user. Mail plugins are tried one at a time, and stops after the first to return a current user. Anonymous subroutine references found in C<@MailPlugins> are treated as C<GetCurrentUser> methods. The default GetCurrentUser authenticator simply looks at the From: address, and loads or creates a user accordingly; see L<RT::Interface::Email::Auth::MailFrom>. Returns the current user; on failure of any plugin to do so, stops processing with a permanent failure and sends a generic "Permission Denied" mail to the user. =cut sub GetCurrentUser { my %args = ( Message => undef, RawMessageRef => undef, Ticket => undef, Queue => undef, @_, ); # Since this needs loading, no matter what for my $Code ( Plugins(Code => 1, Method => "GetCurrentUser") ) { my $CurrentUser = $Code->( Message => $args{Message}, RawMessageRef => $args{RawMessageRef}, Ticket => $args{Ticket}, Queue => $args{Queue}, ); return $CurrentUser if $CurrentUser and $CurrentUser->id; } # None of the GetCurrentUser plugins found a user. This is # rare; some non-Auth::MailFrom authentication plugin which # doesn't always return a current user? MailError( Subject => "Permission Denied", Explanation => "You do not have permission to communicate with RT", FAILURE => 1, ); } =head3 CheckACL Action => C<action>, CurrentUser => C<user>, Ticket => C<ticket>, Queue => C<queue> Checks that the currentuser can perform a particular action. While RT's standard permission controls apply, this allows a better error message, or more limited restrictions on the email gateway. Each plugin in C<@MailPlugins> which provides C<CheckACL> is given a chance to allow the action. If any returns a true value, it short-circuits all later plugins. Note that plugins may short-circuit and abort with failure of their own accord. Aborts processing, sending a "Permission Denied" mail to the user with the last plugin's failure message, on failure. =cut sub CheckACL { my %args = ( Action => undef, Message => undef, CurrentUser => undef, Ticket => undef, Queue => undef, @_, ); for my $Code ( Plugins( Method => "CheckACL" ) ) { return if $Code->( Message => $args{Message}, CurrentUser => $args{CurrentUser}, Action => $args{Action}, Ticket => $args{Ticket}, Queue => $args{Queue}, ); } # Nobody said yes, and nobody said FAILURE; fail closed MailError( Subject => "Permission Denied", Explanation => "You have no permission to $args{Action}", FAILURE => 1, ); } =head3 HandleAction Action => C<action>, Message => C<message>, Ticket => C<ticket>, Queue => C<queue> Dispatches to the first plugin in C<@MailPlugins> which provides a C<HandleFoo> where C<Foo> is C<ucfirst(action)>. =cut sub HandleAction { my %args = ( Action => undef, Subject => undef, Message => undef, Ticket => undef, TicketId => undef, Queue => undef, @_ ); my $action = delete $args{Action}; my ($code) = Plugins(Method => "Handle" . ucfirst($action)); TMPFAIL( "Invalid 'action' parameter $action for queue ".$args{Queue}->Name ) unless $code; $code->(%args); } =head3 ParseSenderAddressFromHead HEAD Takes a L<MIME::Header> object. Returns a list of (email address, friendly name, errors) where the address and name are the first address found in C<Reply-To>, C<From>, or C<Sender>. A list of error messages may be returned even when an address is found, since it could be a parse error for another (checked earlier) sender field. In this case, the errors aren't fatal, but may be useful to investigate the parse failure. =cut sub ParseSenderAddressFromHead { my $head = shift; my @errors; # Accumulate any errors foreach my $header ( 'Reply-To', 'From', 'Sender' ) { my $addr_line = Encode::decode( "UTF-8", $head->get($header) ) || next; my ($addr) = RT::EmailParser->ParseEmailAddress( $addr_line ); return ($addr->address, $addr->phrase, @errors) if $addr; chomp $addr_line; push @errors, "$header: $addr_line"; } return (undef, undef, @errors); } =head3 ParseErrorsToAddressFromHead HEAD Takes a L<MIME::Header> object. Returns the first email address found in C<Return-path>, C<Errors-To>, C<Reply-To>, C<From>, or C<Sender>. =cut sub ParseErrorsToAddressFromHead { my $head = shift; foreach my $header ( 'Errors-To', 'Reply-To', 'From', 'Sender' ) { my $value = Encode::decode( "UTF-8", $head->get($header) ); next unless $value; my ( $email ) = RT::EmailParser->ParseEmailAddress($value); return $email->address if $email; } } =head3 IsMachineGeneratedMail Message => C<message> Checks if the mail is machine-generated (via a bounce, mail headers, =cut sub IsMachineGeneratedMail { my %args = ( Message => undef, Subject => undef, @_ ); my $head = $args{'Message'}->head; my $IsAutoGenerated = CheckForAutoGenerated($head); my $IsALoop = CheckForLoops($head); my $owner_mail = RT->Config->Get('OwnerEmail'); # Don't let the user stuff the RT-Squelch-Replies-To header. $head->delete('RT-Squelch-Replies-To'); # If the message is autogenerated, we need to know, so we can not # send mail to the sender return unless $IsAutoGenerated || $IsALoop; # Warn someone if it's a loop, before we drop it on the ground if ($IsALoop) { my $MessageId = Encode::decode( "UTF-8", $head->get('Message-ID') ); $RT::Logger->crit("RT Received mail ($MessageId) from itself."); #Should we mail it to RTOwner? if ( RT->Config->Get('LoopsToRTOwner') ) { MailError( To => $owner_mail, Subject => "RT Bounce: ".$args{'Subject'}, Explanation => "RT thinks this message may be a bounce", ); } #Do we actually want to store it? FAILURE( "Message is a bounce" ) unless RT->Config->Get('StoreLoops'); } # Squelch replies to the sender, and also leave a clue to # allow us to squelch ALL outbound messages. This way we # can punt the logic of "what to do when we get a bounce" # to the scrip. We might want to notify nobody. Or just # the RT Owner. Or maybe all Privileged watchers. my ( $Sender ) = ParseSenderAddressFromHead($head); $head->replace( 'RT-Squelch-Replies-To', Encode::encode("UTF-8", $Sender ) ); $head->replace( 'RT-DetectedAutoGenerated', 'true' ); return 1; } =head3 CheckForLoops HEAD Takes a L<MIME::Head> object and returns true if the message was sent by this RT instance, by checking the C<X-RT-Loop-Prevention> header. =cut sub CheckForLoops { my $head = shift; # If this instance of RT sent it our, we don't want to take it in my $RTLoop = Encode::decode( "UTF-8", $head->get("X-RT-Loop-Prevention") || "" ); chomp ($RTLoop); # remove that newline if ( $RTLoop eq RT->Config->Get('rtname') ) { return 1; } # TODO: We might not trap the case where RT instance A sends a mail # to RT instance B which sends a mail to ... return undef; } =head3 CheckForAutoGenerated HEAD Takes a HEAD object of L<MIME::Head> class and returns true if message is autogenerated. Checks C<Precedence>, RFC3834 C<Auto-Submitted>, and C<X-FC-Machinegenerated> fields of the head in tests. =cut sub CheckForAutoGenerated { my $head = shift; # Bounces, via return-path my $ReturnPath = $head->get("Return-path") || ""; return 1 if $ReturnPath =~ /<>/; # Bounces, via mailer-daemon or postmaster my ( $From ) = ParseSenderAddressFromHead($head); return 1 if defined $From and $From =~ /^mailer-daemon\@/i; return 1 if defined $From and $From =~ /^postmaster\@/i; return 1 if defined $From and $From eq ""; # Bulk or junk messages are auto-generated return 1 if grep {/^(bulk|junk)/i} $head->get_all("Precedence"); # Per RFC3834, any Auto-Submitted header which is not "no" means # it is auto-generated. my $AutoSubmitted = $head->get("Auto-Submitted") || ""; return 1 if length $AutoSubmitted and $AutoSubmitted ne "no"; # First Class mailer uses this as a clue. my $FCJunk = $head->get("X-FC-Machinegenerated") || ""; return 1 if $FCJunk =~ /^true/i; return 0; } =head3 ExtractTicketId Passed a L<MIME::Entity> object, and returns a either ticket id or undef to signal 'new ticket'. This is a great entry point if you need to customize how ticket ids are handled for your site. L<RT::Extension::RepliesToResolved> demonstrates one possible use for this extension. If the Subject of the L<MIME::Entity> is modified, the updated subject will be used during ticket creation. =cut sub ExtractTicketId { my $entity = shift; my $subject = Encode::decode( "UTF-8", $entity->head->get('Subject') || '' ); chomp $subject; return ParseTicketId( $subject, $entity ); } =head3 ParseTicketId Takes a string (the email subject) and searches for [subjecttag #id] For customizations, the L<MIME::Entity> object is passed as the second argument. Returns the id if a match is found. Otherwise returns undef. =cut sub ParseTicketId { my $Subject = shift; my $Entity = shift; my $rtname = RT->Config->Get('rtname'); my $test_name = RT->Config->Get('EmailSubjectTagRegex') || qr/\Q$rtname\E/i; # We use @captures and pull out the last capture value to guard against # someone using (...) instead of (?:...) in $EmailSubjectTagRegex. my $id; if ( my @captures = $Subject =~ m{\[(?:http://)?$test_name\s+\#(\d+)\s*\]}i ) { $id = $captures[-1]; } else { foreach my $tag ( RT->System->SubjectTag ) { next unless my @captures = $Subject =~ m{\[(?:http://)?\Q$tag\E\s+\#(\d+)\s*\]}i; $id = $captures[-1]; last; } } return undef unless $id; $RT::Logger->debug("Found a ticket ID. It's $id"); return $id; } =head3 MailError PARAM HASH Sends an error message. Takes a param hash: =over 4 =item From Sender's address, defaults to L<RT_Config/$CorrespondAddress>; =item To Recipient, defaults to L<RT_Config/$OwnerEmail>; =item Subject Subject of the message, defaults to C<There has been an error>; =item Explanation Main content of the error, default value is C<Unexplained error>; =item MIMEObj Optional L<MIME::Entity> that is attached to the error mail. Additionally, the C<In-Reply-To> header will point to this message. =item Attach Optional text that attached to the error as a C<message/rfc822> part. =item LogLevel Log level the subject and explanation is written to the log; defaults to C<critical>. =back =cut sub MailError { my %args = ( To => RT->Config->Get('OwnerEmail'), From => RT->Config->Get('CorrespondAddress'), Subject => 'There has been an error', Explanation => 'Unexplained error', MIMEObj => undef, Attach => undef, LogLevel => 'crit', FAILURE => 0, @_ ); $RT::Logger->log( level => $args{'LogLevel'}, message => "$args{Subject}: $args{'Explanation'}", ) if $args{'LogLevel'}; # the colons are necessary to make ->build include non-standard headers my %entity_args = ( Type => "multipart/mixed", From => Encode::encode( "UTF-8", $args{'From'} ), To => Encode::encode( "UTF-8", $args{'To'} ), Subject => EncodeToMIME( String => $args{'Subject'} ), 'X-RT-Loop-Prevention:' => Encode::encode( "UTF-8", RT->Config->Get('rtname') ), ); # only set precedence if the sysadmin wants us to if (defined(RT->Config->Get('DefaultErrorMailPrecedence'))) { $entity_args{'Precedence:'} = Encode::encode( "UTF-8", RT->Config->Get('DefaultErrorMailPrecedence') ); } my $entity = MIME::Entity->build(%entity_args); SetInReplyTo( Message => $entity, InReplyTo => $args{'MIMEObj'} ); $entity->attach( Type => "text/plain", Charset => "UTF-8", Data => Encode::encode( "UTF-8", $args{'Explanation'} . "\n" ), ); if ( $args{'MIMEObj'} ) { $args{'MIMEObj'}->sync_headers; $entity->add_part( $args{'MIMEObj'} ); } if ( $args{'Attach'} ) { $entity->attach( Data => Encode::encode( "UTF-8", $args{'Attach'} ), Type => 'message/rfc822' ); } SendEmail( Entity => $entity, Bounce => 1 ); FAILURE( "$args{Subject}: $args{Explanation}" ) if $args{FAILURE}; } sub _OutgoingMailFrom { my $TicketObj = shift; my $MailFrom = RT->Config->Get('SetOutgoingMailFrom'); my $OutgoingMailAddress = $MailFrom =~ /\@/ ? $MailFrom : undef; my $Overrides = RT->Config->Get('OverrideOutgoingMailFrom') || {}; if ($TicketObj) { my $Queue = $TicketObj->QueueObj; my $QueueAddressOverride = $Overrides->{$Queue->id} || $Overrides->{$Queue->Name}; if ($QueueAddressOverride) { $OutgoingMailAddress = $QueueAddressOverride; } else { $OutgoingMailAddress ||= $Queue->CorrespondAddress || RT->Config->Get('CorrespondAddress'); } } elsif ($Overrides->{'Default'}) { $OutgoingMailAddress = $Overrides->{'Default'}; } return $OutgoingMailAddress; } =head2 SENDING EMAIL =head3 SendEmail Entity => undef, [ Bounce => 0, Ticket => undef, Transaction => undef ] Sends an email (passed as a L<MIME::Entity> object C<ENTITY>) using RT's outgoing mail configuration. If C<BOUNCE> is passed, and is a true value, the message will be marked as an autogenerated error, if possible. Sets Date field of the head to now if it's not set. If the C<X-RT-Squelch> header is set to any true value, the mail will not be sent. One use is to let extensions easily cancel outgoing mail. Ticket and Transaction arguments are optional. If Transaction is specified and Ticket is not then ticket of the transaction is used, but only if the transaction belongs to a ticket. Returns 1 on success, 0 on error or -1 if message has no recipients and hasn't been sent. =head3 Signing and Encrypting This function as well signs and/or encrypts the message according to headers of a transaction's attachment or properties of a ticket's queue. To get full access to the configuration Ticket and/or Transaction arguments must be provided, but you can force behaviour using Sign and/or Encrypt arguments. The following precedence of arguments are used to figure out if the message should be encrypted and/or signed: * if Sign or Encrypt argument is defined then its value is used * else if Transaction's first attachment has X-RT-Sign or X-RT-Encrypt header field then it's value is used * else properties of a queue of the Ticket are used. =cut sub SendEmail { my (%args) = ( Entity => undef, Bounce => 0, Ticket => undef, Transaction => undef, @_, ); my $TicketObj = $args{'Ticket'}; my $TransactionObj = $args{'Transaction'}; unless ( $args{'Entity'} ) { $RT::Logger->crit( "Could not send mail without 'Entity' object" ); return 0; } my $msgid = Encode::decode( "UTF-8", $args{'Entity'}->head->get('Message-ID') || '' ); chomp $msgid; # If we don't have any recipients to send to, don't send a message; unless ( $args{'Entity'}->head->get('To') || $args{'Entity'}->head->get('Cc') || $args{'Entity'}->head->get('Bcc') ) { $RT::Logger->info( $msgid . " No recipients found. Not sending." ); return -1; } if ($args{'Entity'}->head->get('X-RT-Squelch')) { $RT::Logger->info( $msgid . " Squelch header found. Not sending." ); return -1; } if (my $precedence = RT->Config->Get('DefaultMailPrecedence') and !$args{'Entity'}->head->get("Precedence") ) { if ($TicketObj) { my $Overrides = RT->Config->Get('OverrideMailPrecedence') || {}; my $Queue = $TicketObj->QueueObj; $precedence = $Overrides->{$Queue->id} if exists $Overrides->{$Queue->id}; $precedence = $Overrides->{$Queue->Name} if exists $Overrides->{$Queue->Name}; } $args{'Entity'}->head->replace( 'Precedence', Encode::encode("UTF-8",$precedence) ) if $precedence; } if ( $TransactionObj && !$TicketObj && $TransactionObj->ObjectType eq 'RT::Ticket' ) { $TicketObj = $TransactionObj->Object; } my $head = $args{'Entity'}->head; unless ( $head->get('Date') ) { require RT::Date; my $date = RT::Date->new( RT->SystemUser ); $date->SetToNow; $head->replace( 'Date', Encode::encode("UTF-8",$date->RFC2822( Timezone => 'server' ) ) ); } unless ( $head->get('MIME-Version') ) { # We should never have to set the MIME-Version header $head->replace( 'MIME-Version', '1.0' ); } unless ( $head->get('Content-Transfer-Encoding') ) { # fsck.com #5959: Since RT sends 8bit mail, we should say so. $head->replace( 'Content-Transfer-Encoding', '8bit' ); } if ( RT->Config->Get('Crypt')->{'Enable'} ) { %args = WillSignEncrypt( %args, Attachment => $TransactionObj ? $TransactionObj->Attachments->First : undef, Ticket => $TicketObj, ); my $res = SignEncrypt( %args ); return $res unless $res > 0; } my $mail_command = RT->Config->Get('MailCommand'); # if it is a sub routine, we just return it; return $mail_command->($args{'Entity'}) if UNIVERSAL::isa( $mail_command, 'CODE' ); if ( $mail_command eq 'sendmailpipe' ) { my $path = RT->Config->Get('SendmailPath'); my @args = shellwords(RT->Config->Get('SendmailArguments')); push @args, "-t" unless grep {$_ eq "-t"} @args; # SetOutgoingMailFrom and bounces conflict, since they both want -f if ( $args{'Bounce'} ) { push @args, shellwords(RT->Config->Get('SendmailBounceArguments')); } elsif ( RT->Config->Get('SetOutgoingMailFrom') ) { my $OutgoingMailAddress = _OutgoingMailFrom($TicketObj); push @args, "-f", $OutgoingMailAddress if $OutgoingMailAddress; } # VERP if ( $TransactionObj and my $prefix = RT->Config->Get('VERPPrefix') and my $domain = RT->Config->Get('VERPDomain') ) { my $from = $TransactionObj->CreatorObj->EmailAddress; $from =~ s/@/=/g; $from =~ s/\s//g; push @args, "-f", "$prefix$from\@$domain"; } eval { # don't ignore CHLD signal to get proper exit code local $SIG{'CHLD'} = 'DEFAULT'; # if something wrong with $mail->print we will get PIPE signal, handle it local $SIG{'PIPE'} = sub { die "program unexpectedly closed pipe" }; require IPC::Open2; my ($mail, $stdout); my $pid = IPC::Open2::open2( $stdout, $mail, $path, @args ) or die "couldn't execute program: $!"; $args{'Entity'}->print($mail); close $mail or die "close pipe failed: $!"; waitpid($pid, 0); if ($?) { # sendmail exit statuses mostly errors with data not software # TODO: status parsing: core dump, exit on signal or EX_* my $msg = "$msgid: `$path @args` exited with code ". ($?>>8); $msg = ", interrupted by signal ". ($?&127) if $?&127; $RT::Logger->error( $msg ); die $msg; } }; if ( $@ ) { $RT::Logger->crit( "$msgid: Could not send mail with command `$path @args`: " . $@ ); if ( $TicketObj ) { _RecordSendEmailFailure( $TicketObj ); } return 0; } } elsif ( $mail_command eq 'mbox' ) { my $now = RT::Date->new(RT->SystemUser); $now->SetToNow; state $logfile; unless ($logfile) { my $when = $now->ISO( Timezone => "server" ); $when =~ s/\s+/-/g; $logfile = "$RT::VarPath/$when.mbox"; $RT::Logger->info("Storing outgoing emails in $logfile"); } my $fh; unless (open($fh, ">>", $logfile)) { $RT::Logger->crit( "Can't open mbox file $logfile: $!" ); return 0; } my $content = $args{Entity}->stringify; $content =~ s/^(>*From )/>$1/mg; my $user = $ENV{USER} || getpwuid($<); print $fh "From $user\@localhost ".localtime()."\n"; print $fh $content, "\n"; close $fh; } else { local ($ENV{'MAILADDRESS'}, $ENV{'PERL_MAILERS'}); my @mailer_args = ($mail_command); if ( $mail_command eq 'sendmail' ) { $ENV{'PERL_MAILERS'} = RT->Config->Get('SendmailPath'); push @mailer_args, grep {$_ ne "-t"} split(/\s+/, RT->Config->Get('SendmailArguments')); } elsif ( $mail_command eq 'testfile' ) { unless ($Mail::Mailer::testfile::config{outfile}) { $Mail::Mailer::testfile::config{outfile} = File::Temp->new; $RT::Logger->info("Storing outgoing emails in $Mail::Mailer::testfile::config{outfile}"); } } else { push @mailer_args, RT->Config->Get('MailParams'); } unless ( $args{'Entity'}->send( @mailer_args ) ) { $RT::Logger->crit( "$msgid: Could not send mail." ); if ( $TicketObj ) { _RecordSendEmailFailure( $TicketObj ); } return 0; } } return 1; } =head3 PrepareEmailUsingTemplate Template => '', Arguments => {} Loads a template. Parses it using arguments if it's not empty. Returns a tuple (L<RT::Template> object, error message). Note that even if a template object is returned MIMEObj method may return undef for empty templates. =cut sub PrepareEmailUsingTemplate { my %args = ( Template => '', Arguments => {}, @_ ); my $template = RT::Template->new( RT->SystemUser ); $template->LoadGlobalTemplate( $args{'Template'} ); unless ( $template->id ) { return (undef, "Couldn't load template '". $args{'Template'} ."'"); } return $template if $template->IsEmpty; my ($status, $msg) = $template->Parse( %{ $args{'Arguments'} } ); return (undef, $msg) unless $status; return $template; } =head3 SendEmailUsingTemplate Template => '', Arguments => {}, From => CorrespondAddress, To => '', Cc => '', Bcc => '' Sends email using a template, takes name of template, arguments for it and recipients. =cut sub SendEmailUsingTemplate { my %args = ( Template => '', Arguments => {}, To => undef, Cc => undef, Bcc => undef, From => RT->Config->Get('CorrespondAddress'), InReplyTo => undef, ExtraHeaders => {}, @_ ); my ($template, $msg) = PrepareEmailUsingTemplate( %args ); return (0, $msg) unless $template; my $mail = $template->MIMEObj; unless ( $mail ) { $RT::Logger->info("Message is not sent as template #". $template->id ." is empty"); return -1; } $mail->head->replace( $_ => Encode::encode( "UTF-8", $args{ $_ } ) ) foreach grep defined $args{$_}, qw(To Cc Bcc From); $mail->head->replace( $_ => Encode::encode( "UTF-8", $args{ExtraHeaders}{$_} ) ) foreach keys %{ $args{ExtraHeaders} }; SetInReplyTo( Message => $mail, InReplyTo => $args{'InReplyTo'} ); return SendEmail( Entity => $mail ); } =head3 GetForwardFrom Ticket => undef, Transaction => undef Resolve the From field to use in forward mail =cut sub GetForwardFrom { my %args = ( Ticket => undef, Transaction => undef, @_ ); my $txn = $args{Transaction}; my $ticket = $args{Ticket} || $txn->Object; if ( RT->Config->Get('ForwardFromUser') ) { return ( $txn || $ticket )->CurrentUser->EmailAddress; } else { return $ticket->QueueObj->CorrespondAddress || RT->Config->Get('CorrespondAddress'); } } =head3 GetForwardAttachments Ticket => undef, Transaction => undef Resolve the Attachments to forward =cut sub GetForwardAttachments { my %args = ( Ticket => undef, Transaction => undef, @_ ); my $txn = $args{Transaction}; my $ticket = $args{Ticket} || $txn->Object; my $attachments = RT::Attachments->new( $ticket->CurrentUser ); if ($txn) { $attachments->Limit( FIELD => 'TransactionId', VALUE => $txn->id ); } else { $attachments->LimitByTicket( $ticket->id ); $attachments->Limit( ALIAS => $attachments->TransactionAlias, FIELD => 'Type', OPERATOR => 'IN', VALUE => [ qw(Create Correspond) ], ); } return $attachments; } sub WillSignEncrypt { my %args = @_; my $attachment = delete $args{Attachment}; my $ticket = delete $args{Ticket}; if ( not RT->Config->Get('Crypt')->{'Enable'} ) { $args{Sign} = $args{Encrypt} = 0; return wantarray ? %args : 0; } for my $argument ( qw(Sign Encrypt) ) { next if defined $args{ $argument }; if ( $attachment and defined $attachment->GetHeader("X-RT-$argument") ) { $args{$argument} = $attachment->GetHeader("X-RT-$argument"); } elsif ( $ticket and $argument eq "Encrypt" ) { $args{Encrypt} = $ticket->QueueObj->Encrypt(); } elsif ( $ticket and $argument eq "Sign" ) { # Note that $queue->Sign is UI-only, and that all # UI-generated messages explicitly set the X-RT-Crypt header # to 0 or 1; thus this path is only taken for messages # generated _not_ via the web UI. $args{Sign} = $ticket->QueueObj->SignAuto(); } } return wantarray ? %args : ($args{Sign} || $args{Encrypt}); } =head3 SignEncrypt Entity => undef, Sign => 0, Encrypt => 0 Signs and encrypts message using L<RT::Crypt>, but as well handle errors with users' keys. If a recipient has no key or has other problems with it, then the unction sends a error to him using 'Error: public key' template. Also, notifies RT's owner using template 'Error to RT owner: public key' to inform that there are problems with users' keys. Then we filter all bad recipients and retry. Returns 1 on success, 0 on error and -1 if all recipients are bad and had been filtered out. =cut sub SignEncrypt { my %args = ( Entity => undef, Sign => 0, Encrypt => 0, @_ ); return 1 unless $args{'Sign'} || $args{'Encrypt'}; my $msgid = Encode::decode( "UTF-8", $args{'Entity'}->head->get('Message-ID') || '' ); chomp $msgid; $RT::Logger->debug("$msgid Signing message") if $args{'Sign'}; $RT::Logger->debug("$msgid Encrypting message") if $args{'Encrypt'}; my %res = RT::Crypt->SignEncrypt( %args ); return 1 unless $res{'exit_code'}; my @status = RT::Crypt->ParseStatus( Protocol => $res{'Protocol'}, Status => $res{'status'}, ); my @bad_recipients; foreach my $line ( @status ) { # if the passphrase fails, either you have a bad passphrase # or gpg-agent has died. That should get caught in Create and # Update, but at least throw an error here if (($line->{'Operation'}||'') eq 'PassphraseCheck' && $line->{'Status'} =~ /^(?:BAD|MISSING)$/ ) { $RT::Logger->error( "$line->{'Status'} PASSPHRASE: $line->{'Message'}" ); return 0; } next unless ($line->{'Operation'}||'') eq 'RecipientsCheck'; next if $line->{'Status'} eq 'DONE'; $RT::Logger->error( $line->{'Message'} ); push @bad_recipients, $line; } return 0 unless @bad_recipients; $_->{'AddressObj'} = (Email::Address->parse( $_->{'Recipient'} ))[0] foreach @bad_recipients; foreach my $recipient ( @bad_recipients ) { my $status = SendEmailUsingTemplate( To => $recipient->{'AddressObj'}->address, Template => 'Error: public key', Arguments => { %$recipient, TicketObj => $args{'Ticket'}, TransactionObj => $args{'Transaction'}, }, ); unless ( $status ) { $RT::Logger->error("Couldn't send 'Error: public key'"); } } my $status = SendEmailUsingTemplate( To => RT->Config->Get('OwnerEmail'), Template => 'Error to RT owner: public key', Arguments => { BadRecipients => \@bad_recipients, TicketObj => $args{'Ticket'}, TransactionObj => $args{'Transaction'}, }, ); unless ( $status ) { $RT::Logger->error("Couldn't send 'Error to RT owner: public key'"); } DeleteRecipientsFromHead( $args{'Entity'}->head, map $_->{'AddressObj'}->address, @bad_recipients ); unless ( $args{'Entity'}->head->get('To') || $args{'Entity'}->head->get('Cc') || $args{'Entity'}->head->get('Bcc') ) { $RT::Logger->debug("$msgid No recipients that have public key, not sending"); return -1; } # redo without broken recipients %res = RT::Crypt->SignEncrypt( %args ); return 0 if $res{'exit_code'}; return 1; } =head3 DeleteRecipientsFromHead HEAD RECIPIENTS Gets a head object and list of addresses. Deletes addresses from To, Cc or Bcc fields. =cut sub DeleteRecipientsFromHead { my $head = shift; my %skip = map { lc $_ => 1 } @_; foreach my $field ( qw(To Cc Bcc) ) { $head->replace( $field => Encode::encode( "UTF-8", join ', ', map $_->format, grep !$skip{ lc $_->address }, Email::Address->parse( Encode::decode( "UTF-8", $head->get( $field ) ) ) ) ); } } =head3 EncodeToMIME Takes a hash with a String and a Charset. Returns the string encoded according to RFC2047, using B (base64 based) encoding. String must be a perl string, octets are returned. If Charset is not provided then $EmailOutputEncoding config option is used, or "latin-1" if that is not set. =cut sub EncodeToMIME { my %args = ( String => undef, Charset => undef, @_ ); my $value = $args{'String'}; return $value unless $value; # 0 is perfect ascii my $charset = $args{'Charset'} || RT->Config->Get('EmailOutputEncoding'); my $encoding = 'B'; # using RFC2047 notation, sec 2. # encoded-word = "=?" charset "?" encoding "?" encoded-text "?=" # An 'encoded-word' may not be more than 75 characters long # # MIME encoding increases 4/3*(number of bytes), and always in multiples # of 4. Thus we have to find the best available value of bytes available # for each chunk. # # First we get the integer max which max*4/3 would fit on space. # Then we find the greater multiple of 3 lower or equal than $max. my $max = int( ( ( 75 - length( '=?' . $charset . '?' . $encoding . '?' . '?=' ) ) * 3 ) / 4 ); $max = int( $max / 3 ) * 3; chomp $value; if ( $max <= 0 ) { # gives an error... $RT::Logger->crit("Can't encode! Charset or encoding too big."); return ($value); } return ($value) if $value =~ /^(?:[\t\x20-\x7e]|\x0D*\x0A[ \t])+$/s; $value =~ s/\s+$//; my ( $tmp, @chunks ) = ( '', () ); while ( length $value ) { my $char = substr( $value, 0, 1, '' ); my $octets = Encode::encode( $charset, $char ); if ( length($tmp) + length($octets) > $max ) { push @chunks, $tmp; $tmp = ''; } $tmp .= $octets; } push @chunks, $tmp if length $tmp; # encode an join chuncks $value = join "\n ", map MIME::Words::encode_mimeword( $_, $encoding, $charset ), @chunks; return ($value); } sub GenMessageId { my %args = ( Ticket => undef, Scrip => undef, ScripAction => undef, @_ ); my $org = RT->Config->Get('Organization'); my $ticket_id = ( ref $args{'Ticket'}? $args{'Ticket'}->id : $args{'Ticket'} ) || 0; my $scrip_id = ( ref $args{'Scrip'}? $args{'Scrip'}->id : $args{'Scrip'} ) || 0; my $sent = ( ref $args{'ScripAction'}? $args{'ScripAction'}->{'_Message_ID'} : 0 ) || 0; return "<rt-". $RT::VERSION ."-". $$ ."-". CORE::time() ."-". int(rand(2000)) .'.' . $ticket_id ."-". $scrip_id ."-". $sent ."@". $org .">" ; } sub SetInReplyTo { my %args = ( Message => undef, InReplyTo => undef, Ticket => undef, @_ ); return unless $args{'Message'} && $args{'InReplyTo'}; my $get_header = sub { my @res; if ( $args{'InReplyTo'}->isa('MIME::Entity') ) { @res = map {Encode::decode("UTF-8", $_)} $args{'InReplyTo'}->head->get( shift ); } else { @res = $args{'InReplyTo'}->GetHeader( shift ) || ''; } return grep length, map { split /\s+/m, $_ } grep defined, @res; }; my @id = $get_header->('Message-ID'); #XXX: custom header should begin with X- otherwise is violation of the standard my @rtid = $get_header->('RT-Message-ID'); my @references = $get_header->('References'); unless ( @references ) { @references = $get_header->('In-Reply-To'); } push @references, @id, @rtid; if ( $args{'Ticket'} ) { my $pseudo_ref = PseudoReference( $args{'Ticket'} ); push @references, $pseudo_ref unless grep $_ eq $pseudo_ref, @references; } splice @references, 4, -6 if @references > 10; my $mail = $args{'Message'}; $mail->head->replace( 'In-Reply-To' => Encode::encode( "UTF-8", join ' ', @rtid? (@rtid) : (@id)) ) if @id || @rtid; $mail->head->replace( 'References' => Encode::encode( "UTF-8", join ' ', @references) ); } sub PseudoReference { my $ticket = shift; return '<RT-Ticket-'. $ticket->id .'@'. RT->Config->Get('Organization') .'>'; } sub AddSubjectTag { my $subject = shift; my $ticket = shift; unless ( ref $ticket ) { my $tmp = RT::Ticket->new( RT->SystemUser ); $tmp->Load( $ticket ); $ticket = $tmp; } my $id = $ticket->id; my $queue_tag = $ticket->QueueObj->SubjectTag; my $tag_re = RT->Config->Get('EmailSubjectTagRegex'); unless ( $tag_re ) { my $tag = $queue_tag || RT->Config->Get('rtname'); $tag_re = qr/\Q$tag\E/; } elsif ( $queue_tag ) { $tag_re = qr/$tag_re|\Q$queue_tag\E/; } return $subject if $subject =~ /\[$tag_re\s+#$id\]/; $subject =~ s/(\r\n|\n|\s)/ /g; chomp $subject; return "[". ($queue_tag || RT->Config->Get('rtname')) ." #$id] $subject"; } sub _RecordSendEmailFailure { my $ticket = shift; if ($ticket) { $ticket->_NewTransaction( Type => "SystemError", Data => "Sending the previous mail has failed. Please contact your admin, they can find more details in the logs.", #loc ActivateScrips => 0, ); return 1; } else { $RT::Logger->error( "Can't record send email failure as ticket is missing" ); return; } } # Hash describing how various formatters format <blockquote>...</blockquote> # regions. our $BlockquoteDescriptor = { w3m => { indent => 4 }, elinks => { indent => 2 }, links => { indent => 2 }, html2text => { indent => 5 }, lynx => { indent => 2 }, core => { indent => 2 }, }; =head3 ConvertBlockquoteIndentsToQuotemarks Given plain text that has been converted from HTML to text, adjust it to quote blockquote regions with ">". =cut sub ConvertBlockquoteIndentsToQuotemarks { my ($text, $converter) = @_; return $text unless exists($BlockquoteDescriptor->{$converter}); my $n = $BlockquoteDescriptor->{$converter}{indent}; my $spaces = ' ' x $n; # Convert each level of indentation to a ">"; add a space aferwards # for readability $text =~ s|^(($spaces)+)|">" x (length($1)/$n) . " "|gem; return $text; } =head3 ConvertHTMLToText HTML Takes HTML characters and converts it to plain text characters. Appropriate for generating a plain text part from an HTML part of an email. Returns undef if conversion fails. =cut sub ConvertHTMLToText { return _HTMLFormatter()->(@_); } sub _HTMLFormatter { state $formatter; # If we are running under the test harness, we want to create # a new $formatter each time rather than once and caching. return $formatter if defined $formatter && !$ENV{HARNESS_ACTIVE}; my $wanted = RT->Config->Get("HTMLFormatter"); my @options = ("w3m", "elinks", "links", "html2text", "lynx", "core"); my @order; if ($wanted) { @order = ($wanted, "core"); } else { @order = @options; } # Always fall back to core, even if it is not listed for my $prog (@order) { if ($prog eq "core") { RT->Logger->debug("Using internal Perl HTML -> text conversion"); if ( !$ENV{HARNESS_ACTIVE} ) { RT->Logger->warn("Running with the internal HTML converter can result in performance issues with some HTML. Install one of the following utilities with your package manager to improve performance with an external tool: " . join (', ', grep { $_ ne 'core' } @options)); } require HTML::FormatText::WithLinks::AndTables; $formatter = \&_HTMLFormatText; } else { my $path = $prog =~ s{(.*/)}{} ? $1 : undef; my $package = "HTML::FormatText::" . ucfirst($prog); unless ($package->require) { RT->Logger->warn("$prog is not a valid formatter provided by HTML::FormatExternal") if $wanted; next; } if ($path) { local $ENV{PATH} = $path; local $ENV{HOME} = File::Spec->tmpdir(); if (not defined $package->program_version) { RT->Logger->warn("Could not find or run external '$prog' HTML formatter in $path$prog") if $wanted; next; } } else { local $ENV{PATH} = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' unless defined $ENV{PATH}; local $ENV{HOME} = File::Spec->tmpdir(); if (not defined $package->program_version) { RT->Logger->warn("Could not find or run external '$prog' HTML formatter in \$PATH ($ENV{PATH}) -- you may need to install it or provide the full path") if $wanted; next; } } RT->Logger->debug("Using $prog for HTML -> text conversion"); $formatter = sub { my $html = shift; my $text = RT::Util::safe_run_child { local $ENV{PATH} = $path || $ENV{PATH} || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'; local $ENV{HOME} = File::Spec->tmpdir(); $package->format_string( Encode::encode( "UTF-8", $html ), input_charset => "UTF-8", output_charset => "UTF-8", leftmargin => 0, rightmargin => 78 ); }; $text = Encode::decode( "UTF-8", $text ); return ConvertBlockquoteIndentsToQuotemarks($text, $prog); }; } RT->Config->Set( HTMLFormatter => $prog ); last; } return $formatter; } sub _HTMLFormatText { my $html = shift; my $text; eval { $text = HTML::FormatText::WithLinks::AndTables->convert( $html => { leftmargin => 0, rightmargin => 78, no_rowspacing => 1, before_link => '', after_link => ' (%l)', footnote => '', skip_linked_urls => 1, with_emphasis => 0, } ); $text //= ''; }; $RT::Logger->error("Failed to downgrade HTML to plain text: $@") if $@; return ConvertBlockquoteIndentsToQuotemarks($text, 'core'); } RT::Base->_ImportOverlays(); 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Email/��������������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 017023� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Email/Crypt.pm������������������������������������������������������������000644 �000765 �000024 �00000016721 14005011336 020471� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Interface::Email::Crypt; use strict; use warnings; =head1 NAME RT::Interface::Email::Crypt - decrypting and verifying protected emails =head1 SEE ALSO See L<RT::Crypt>. =cut use RT::Crypt; use RT::EmailParser (); sub VerifyDecrypt { my %args = ( Message => undef, RawMessageRef => undef, Queue => undef, @_ ); # we clean all possible headers my @headers = qw( X-RT-Incoming-Encryption X-RT-Incoming-Signature X-RT-Privacy X-RT-Sign X-RT-Encrypt ), map "X-RT-$_-Status", RT::Crypt->Protocols; foreach my $p ( $args{'Message'}->parts_DFS ) { $p->head->delete($_) for @headers; } my (@res) = RT::Crypt->VerifyDecrypt( %args, Entity => $args{'Message'}, ); if ( !@res ) { $args{'Message'}->head->replace( 'X-RT-Incoming-Encryption' => 'Not encrypted' ); return; } if ( grep {$_->{'exit_code'}} @res ) { my @fail = grep {$_->{status}{Status} ne "DONE"} map { my %ret = %{$_}; map {+{%ret, status => $_}} RT::Crypt->ParseStatus( Protocol => $_->{Protocol}, Status => $_->{status})} @res; for my $fail ( @fail ) { $RT::Logger->warning("Failure during ".$fail->{Protocol}." ". lc($fail->{status}{Operation}) . ": ". $fail->{status}{Message}); } my $reject = HandleErrors( Message => $args{'Message'}, Result => \@res ); return if $reject; } # attach the original encrypted message $args{'Message'}->attach( Type => 'application/x-rt-original-message', Disposition => 'inline', Data => ${ $args{'RawMessageRef'} }, ); my @found; my @check_protocols = RT::Crypt->EnabledOnIncoming; foreach my $part ( $args{'Message'}->parts_DFS ) { my $decrypted; foreach my $protocol ( @check_protocols ) { my @status = grep defined && length, map Encode::decode( "UTF-8", $_), $part->head->get( "X-RT-$protocol-Status" ); next unless @status; push @found, $protocol; for ( map RT::Crypt->ParseStatus( Protocol => $protocol, Status => "$_" ), @status ) { if ( $_->{Operation} eq 'Decrypt' && $_->{Status} eq 'DONE' ) { $decrypted = 1; } if ( $_->{Operation} eq 'Verify' && $_->{Status} eq 'DONE' ) { $part->head->replace( 'X-RT-Incoming-Signature' => Encode::encode( "UTF-8", $_->{UserString} ) ); } } } $part->head->replace( 'X-RT-Incoming-Encryption' => $decrypted ? 'Success' : 'Not encrypted' ); } my %seen; $args{'Message'}->head->replace( 'X-RT-Privacy' => Encode::encode( "UTF-8", $_ ) ) foreach grep !$seen{$_}++, @found; } sub HandleErrors { my %args = ( Message => undef, Result => [], @_ ); my $reject = 0; my %sent_once = (); foreach my $run ( @{ $args{'Result'} } ) { my @status = RT::Crypt->ParseStatus( Protocol => $run->{'Protocol'}, Status => $run->{'status'} ); unless ( $sent_once{'NoPrivateKey'} ) { unless ( CheckNoPrivateKey( Message => $args{'Message'}, Status => \@status ) ) { $sent_once{'NoPrivateKey'}++; $reject = 1 if RT->Config->Get('Crypt')->{'RejectOnMissingPrivateKey'}; } } unless ( $sent_once{'BadData'} ) { unless ( CheckBadData( Message => $args{'Message'}, Status => \@status ) ) { $sent_once{'BadData'}++; $reject = 1 if RT->Config->Get('Crypt')->{'RejectOnBadData'}; } } } return $reject; } sub CheckNoPrivateKey { my %args = (Message => undef, Status => [], @_ ); my @status = @{ $args{'Status'} }; my @decrypts = grep $_->{'Operation'} eq 'Decrypt', @status; return 1 unless @decrypts; foreach my $action ( @decrypts ) { # if at least one secrete key exist then it's another error return 1 if grep !$_->{'User'}{'SecretKeyMissing'}, @{ $action->{'EncryptedTo'} }; } $RT::Logger->error("Couldn't decrypt a message: have no private key"); return EmailErrorToSender( %args, Template => 'Error: no private key', Arguments => { Message => $args{'Message'} }, ); } sub CheckBadData { my %args = (Message => undef, Status => [], @_ ); my @bad_data_messages = map $_->{'Message'}, grep $_->{'Status'} ne 'DONE' && $_->{'Operation'} eq 'Data', @{ $args{'Status'} }; return 1 unless @bad_data_messages; return EmailErrorToSender( %args, Template => 'Error: bad encrypted data', Arguments => { Messages => [ @bad_data_messages ] }, ); } sub EmailErrorToSender { my %args = (@_); $args{'Arguments'} ||= {}; $args{'Arguments'}{'TicketObj'} ||= $args{'Ticket'}; my ($address) = RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head ); my ($status) = RT::Interface::Email::SendEmailUsingTemplate( To => $address, Template => $args{'Template'}, Arguments => $args{'Arguments'}, InReplyTo => $args{'Message'}, ); unless ( $status ) { $RT::Logger->error("Couldn't send '$args{'Template'}''"); } return 0; } RT::Base->_ImportOverlays(); 1; �����������������������������������������������rt-5.0.1/lib/RT/Interface/Email/Auth/���������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 017724� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Email/Role.pm�������������������������������������������������������������000644 �000765 �000024 �00000007757 14005011336 020302� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Interface::Email::Role; use strict; use warnings; use Role::Basic; use RT::Interface::Email; =head1 NAME RT::Interface::Email::Role - Role for mail plugins =head1 SYNOPSIS package RT::Interface::Email::Action::Something; use Role::Basic 'with'; with 'RT::Interface::Email::Role'; sub CheckACL { ... } sub HandleSomething { ... } =head1 DESCRIPTION Provides a means to affect the handling of RT's mail gateway. Mail plugins (which appear in L<RT_Config/@MailPlugins> should implement this role. See F<docs/extending/mail_plugins.pod> for a list of hook points which plugins can implement. =head1 METHODS =head2 TMPFAIL This should be called for configuration errors. It results in a temporary failure code to the MTA, ensuring that the message is not lost, and will be retried later. This function should be passed the warning message to log with the temporary failure. Does not return. =head2 FAILURE This should be used upon rejection of a message. It will B<not> be retried by the MTA; as such, it should be used sparingly, and in conjunction with L</MailError> such that the sender is aware of the failure. The function should be passed a message to return to the mailgate. Does not return. =head2 SUCCESS The message was successfully parsed. The function takes an optional L<RT::Ticket> object to return to the mailgate. Does not return. =head2 MailError Sends an error concerning the email, or the processing thereof. Takes the following arguments: =over =item To Only necessary in L</BeforeDecode> and L<BeforeDecrypt> hooks, where it defaults to L<RT_Config/OwnerEmail>; otherwise, defaults to the originator of the message. =item Subject Subject of the email =item Explanation The body of the email =item FAILURE If passed a true value, will call L</FAILURE> after sending the message. =back =cut sub TMPFAIL { RT::Interface::Email::TMPFAIL(@_) } sub FAILURE { RT::Interface::Email::FAILURE(@_) } sub SUCCESS { RT::Interface::Email::SUCCESS(@_) } sub MailError { RT::Interface::Email::MailError(@_) } 1; �����������������rt-5.0.1/lib/RT/Interface/Email/Action/�������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 020240� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Email/Authz/��������������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 020116� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Email/Authz/RequireEncrypted.pm�������������������������������������������000644 �000765 �000024 �00000006072 14005011336 023753� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Interface::Email::Authz::RequireEncrypted; use strict; use warnings; use Role::Basic 'with'; with 'RT::Interface::Email::Role'; =head1 NAME RT::Interface::Email::Authz::RequireEncrypted - Require that incoming email be encrypted =head1 SYNOPSIS This plugin allows restricting incoming emails to those which were encrypted. Plaintext emails are rejected, with a reply sent to the originator using the C<Error: unencrypted message> template. =cut sub BeforeDecode { my %args = ( Action => undef, Message => undef, CurrentUser => undef, Ticket => undef, Queue => undef, @_, ); my $encryption = $args{Message}->head->get('X-RT-Incoming-Encryption'); chomp $encryption; return unless $encryption eq "Not encrypted"; RT::Interface::Email::Crypt::EmailErrorToSender( %args, Template => 'Error: unencrypted message', Arguments => { Message => $args{'Message'} }, ); $RT::Logger->warning("rejected because the message is unencrypted"); FAILURE('rejected because the message is unencrypted'); } 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Email/Authz/Default.pm����������������������������������������������������000644 �000765 �000024 �00000011523 14005011336 022042� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Interface::Email::Authz::Default; use strict; use warnings; use Role::Basic 'with'; with 'RT::Interface::Email::Role'; =head1 NAME RT::Interface::Email::Authz::Default - RT's core authorization for the mail gateway =head1 SYNOPSIS This module B<should not> be explicitly included in L<RT_Config/@MailPlugins>; RT includes it automatically. It provides authorization checks for the core the C<comment> and C<correspond> actions, via examining RT's standard rights for C<CommentOnTicket>, C<ReplyToTicket>, or C<CreateTicket> as necessary. =cut sub CheckACL { my %args = ( Message => undef, CurrentUser => undef, Ticket => undef, Queue => undef, Action => undef, @_, ); my $principal = $args{CurrentUser}->PrincipalObj; my $email = $args{CurrentUser}->UserObj->EmailAddress; my $qname = $args{'Queue'}->Name; my $msg; if ( $args{'Ticket'} && $args{'Ticket'}->Id ) { my $tid = $args{'Ticket'}->id; if ( $args{'Action'} =~ /^comment$/i ) { return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right => 'CommentOnTicket' ); $msg = "$email has no right to comment on ticket $tid in queue $qname"; } elsif ( $args{'Action'} =~ /^correspond$/i ) { return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right => 'ReplyToTicket' ); $msg = "$email has no right to reply to ticket $tid in queue $qname"; # Also notify the owner MailError( To => RT->Config->Get('OwnerEmail'), Subject => "Failed attempt to reply to a ticket by email, from $email", Explanation => <<EOT, $email attempted to reply to a ticket via email in the queue $qname; you might need to grant 'Everyone' the ReplyToTicket right. EOT ); } else { $RT::Logger->warning("Action '". ($args{'Action'}||'') ."' is unknown"); return; } } # We're creating a ticket elsif ( $args{'Action'} =~ /^(comment|correspond)$/i ) { return 1 if $principal->HasRight( Object => $args{'Queue'}, Right => 'CreateTicket' ); $msg = "$email has no right to create tickets in queue $qname"; # Also notify the owner MailError( To => RT->Config->Get('OwnerEmail'), Subject => "Failed attempt to create a ticket by email, from $email", Explanation => <<EOT, $email attempted to create a ticket via email in the queue $qname; you might need to grant 'Everyone' the CreateTicket right. EOT ); } else { $RT::Logger->warning("Action '". ($args{'Action'}||'') ."' is unknown with no ticket"); return; } MailError( Subject => "Permission Denied", Explanation => $msg, FAILURE => 1, ); } 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Email/Action/Resolve.pm���������������������������������������������������000644 �000765 �000024 �00000010327 14005011336 022220� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Interface::Email::Action::Resolve; use strict; use warnings; use Role::Basic 'with'; with 'RT::Interface::Email::Role'; =head1 NAME RT::Interface::Email::Action::Resolve - Resolve tickets via the mail gateway =head1 SYNOPSIS This plugin, if placed in L<RT_Config/@MailPlugins>, allows the mail gateway to specify a resolve action: | rt-mailgate --action correspond-resolve --queue General --url http://localhost/ This can alternately (and more flexibly) be accomplished with a Scrip. =cut sub CheckACL { my %args = ( Message => undef, CurrentUser => undef, Ticket => undef, Queue => undef, Action => undef, @_, ); return unless lc $args{Action} eq "resolve"; unless ( $args{Ticket}->Id ) { MailError( Subject => "Message not recorded: $args{Subject}", Explanation => "Could not find a ticket with id $args{TicketId}", FAILURE => 1, ); } my $principal = $args{CurrentUser}->PrincipalObj; return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right => 'ModifyTicket' ); my $email = $args{CurrentUser}->UserObj->EmailAddress; my $qname = $args{Queue}->Name; my $tid = $args{Ticket}->id; MailError( Subject => "Permission Denied", Explanation => "$email has no right to own ticket $tid in queue $qname", FAILURE => 1, ); } sub HandleResolve { my %args = ( Message => undef, Ticket => undef, Queue => undef, @_, ); unless ( $args{Ticket}->Id ) { MailError( Subject => "Message not recorded: $args{Subject}", Explanation => "Could not find a ticket with id " . $args{TicketId}, FAILURE => 1, ); } my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") ); my $new_status = $args{'Ticket'}->FirstInactiveStatus; return unless $new_status; my ( $status, $msg ) = $args{'Ticket'}->SetStatus($new_status); return if $status; # Warn the sender that we couldn't actually resolve the ticket MailError( Subject => "Ticket not resolved", Explanation => $msg, FAILURE => 1, ); } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Email/Action/Defaults.pm��������������������������������������������������000644 �000765 �000024 �00000011001 14005011336 022336� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Interface::Email::Action::Defaults; use strict; use warnings; use Role::Basic 'with'; with 'RT::Interface::Email::Role'; use RT::Interface::Email; =head1 NAME RT::Interface::Email::Action::Defaults - RT's core email integration =head1 SYNOPSIS This module B<should not> be explicitly included in L<RT_Config/@MailPlugins>; RT includes it automatically. It provides the C<comment> and C<correspond> actions. =cut sub _HandleCreate { my %args = ( Subject => undef, Message => undef, Ticket => undef, Queue => undef, @_, ); my $head = $args{Message}->head; my @Cc; my @Requestors = ( $args{Ticket}->CurrentUser->id ); if (RT->Config->Get('ParseNewMessageForTicketCcs')) { my $user = $args{Ticket}->CurrentUser->UserObj; my $current_address = lc $user->EmailAddress; @Cc = grep $_ ne $current_address && !RT::EmailParser->IsRTAddress( $_ ), map lc $user->CanonicalizeEmailAddress( $_->address ), map RT::EmailParser->CleanupAddresses( Email::Address->parse( Encode::decode( "UTF-8", $head->get( $_ ) ) ) ), qw(To Cc); } # ExtractTicketId may have been overridden, and edited the Subject my $subject = Encode::decode( "UTF-8", $head->get('Subject') ); chomp $subject; my ( $id, $Transaction, $ErrStr ) = $args{Ticket}->Create( Queue => $args{Queue}->Id, Subject => $subject, Requestor => \@Requestors, Cc => \@Cc, MIMEObj => $args{Message}, ); return if $id; MailError( Subject => "Ticket creation failed: $args{Subject}", Explanation => $ErrStr, FAILURE => 1, ); } sub HandleComment { _HandleEither( @_, Action => "Comment" ); } sub HandleCorrespond { _HandleEither( @_, Action => "Correspond" ); } sub _HandleEither { my %args = ( Action => undef, Message => undef, Subject => undef, Ticket => undef, TicketId => undef, Queue => undef, @_, ); return _HandleCreate(@_) unless $args{TicketId}; unless ( $args{Ticket}->Id ) { MailError( Subject => "Message not recorded: $args{Subject}", Explanation => "Could not find a ticket with id " . $args{TicketId}, FAILURE => 1, ); } my $action = ucfirst $args{Action}; my ( $status, $msg ) = $args{Ticket}->$action( MIMEObj => $args{Message} ); return if $status; } 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Email/Action/Take.pm������������������������������������������������������000644 �000765 �000024 �00000007470 14005011336 021472� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Interface::Email::Action::Take; use strict; use warnings; use Role::Basic 'with'; with 'RT::Interface::Email::Role'; =head1 NAME RT::Interface::Email::Action::Take - Take tickets via the mail gateway =head1 SYNOPSIS This plugin, if placed in L<RT_Config/@MailPlugins>, allows the mail gateway to specify a take action: | rt-mailgate --action take-correspond --queue General --url http://localhost/ This can alternately (and more flexibly) be accomplished with a Scrip. =cut sub CheckACL { my %args = ( Message => undef, CurrentUser => undef, Ticket => undef, Queue => undef, Action => undef, @_, ); return unless lc $args{Action} eq "take"; unless ( $args{Ticket}->Id ) { MailError( Subject => "Message not recorded: $args{Subject}", Explanation => "Could not find a ticket with id $args{TicketId}", FAILURE => 1, ); } my $principal = $args{CurrentUser}->PrincipalObj; return 1 if $principal->HasRight( Object => $args{'Ticket'}, Right => 'OwnTicket' ); my $email = $args{CurrentUser}->UserObj->EmailAddress; my $qname = $args{Queue}->Name; my $tid = $args{Ticket}->id; MailError( Subject => "Permission Denied", Explanation => "$email has no right to own ticket $tid in queue $qname", FAILURE => 1, ); } sub HandleTake { my %args = ( Message => undef, Ticket => undef, Queue => undef, @_, ); my $From = Encode::decode( "UTF-8", $args{Message}->head->get("From") ); my ( $status, $msg ) = $args{'Ticket'}->SetOwner( $args{Ticket}->CurrentUser->id ); return if $status; MailError( Subject => "Ticket not taken", Explanation => $msg, FAILURE => 1, ); } 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Email/Auth/MailFrom.pm����������������������������������������������������000644 �000765 �000024 �00000007250 14005011336 021774� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Interface::Email::Auth::MailFrom; use strict; use warnings; use Role::Basic 'with'; with 'RT::Interface::Email::Role'; =head1 NAME RT::Interface::Email::Auth::MailFrom - The default mail gateway authenticator =head1 SYNOPSIS This is the default authentication plugin for RT's email gateway; no no other authentication plugin is found in L<RT_Config/@MailPlugins>, RT will default to this one. This plugin reads the first address found in the C<Reply-To>, C<From>, and C<Sender> headers, and loads or creates the user. It performs no checking of the identity of the user, and trusts the headers of the incoming email. =cut sub GetCurrentUser { my %args = ( Message => undef, @_, ); # We don't need to do any external lookups my ( $Address, $Name, @errors ) = RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head ); $RT::Logger->warning("Failed to parse ".join(', ', @errors)) if @errors; unless ( $Address ) { $RT::Logger->error("Couldn't parse or find sender's address"); FAILURE("Couldn't parse or find sender's address"); } my $CurrentUser = RT::CurrentUser->new; $CurrentUser->LoadByEmail( $Address ); $CurrentUser->LoadByName( $Address ) unless $CurrentUser->Id; if ( $CurrentUser->Id ) { $RT::Logger->debug("Mail from user #". $CurrentUser->Id ." ($Address)" ); return $CurrentUser; } my $user = RT::User->new( RT->SystemUser ); $user->LoadOrCreateByEmail( RealName => $Name, EmailAddress => $Address, Comments => 'Autocreated on ticket submission', ); $CurrentUser = RT::CurrentUser->new; $CurrentUser->Load( $user->id ); return $CurrentUser; } 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Web/Middleware/�����������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 020566� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Web/QueryBuilder/���������������������������������������������������������000755 �000765 �000024 �00000000000 14005011336 021125� 5����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Web/Handler.pm������������������������������������������������������������000644 �000765 �000024 �00000031151 14005011336 020425� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Interface::Web::Handler; use warnings; use strict; use CGI qw/-private_tempfiles/; use MIME::Entity; use Text::Wrapper; use CGI::Cookie; use Time::HiRes; use HTML::Scrubber; use RT::Interface::Web; use RT::Interface::Web::Request; use RT::ObjectCustomFieldValues; use RT::REST2; use File::Path qw( rmtree ); use File::Glob qw( bsd_glob ); use File::Spec::Unix; use HTTP::Message::PSGI; use HTTP::Request; use HTTP::Response; sub DefaultHandlerArgs { ( comp_root => [ RT::Interface::Web->ComponentRoots( Names => 1 ), ], default_escape_flags => 'h', data_dir => "$RT::MasonDataDir", allow_globals => [qw(%session $DECODED_ARGS)], # Turn off static source if we're in developer mode. static_source => (RT->Config->Get('DevelMode') ? '0' : '1'), use_object_files => (RT->Config->Get('DevelMode') ? '0' : '1'), autoflush => 0, error_format => (RT->Config->Get('DevelMode') ? 'html': 'rt_error'), request_class => 'RT::Interface::Web::Request', named_component_subs => $INC{'Devel/Cover.pm'} ? 1 : 0, ) }; sub InitSessionDir { # Activate the following if running httpd as root (the normal case). # Resets ownership of all files created by Mason at startup. # Note that mysql uses DB for sessions, so there's no need to do this. unless ( RT->Config->Get('DatabaseType') =~ /(?:mysql|Pg)/ ) { # Clean up our umask to protect session files umask(0077); if ($CGI::MOD_PERL and $CGI::MOD_PERL < 1.9908 ) { chown( Apache->server->uid, Apache->server->gid, $RT::MasonSessionDir ) if Apache->server->can('uid'); } # Die if WebSessionDir doesn't exist or we can't write to it stat($RT::MasonSessionDir); die "Can't read and write $RT::MasonSessionDir" unless ( ( -d _ ) and ( -r _ ) and ( -w _ ) ); } } sub NewHandler { my $class = shift; $class->require or die $!; my $handler = $class->new( DefaultHandlerArgs(), RT->Config->Get('MasonParameters'), @_ ); $handler->interp->set_escape( h => \&RT::Interface::Web::EscapeHTML ); $handler->interp->set_escape( u => \&RT::Interface::Web::EscapeURI ); $handler->interp->set_escape( j => \&RT::Interface::Web::EscapeJS ); return($handler); } =head2 _mason_dir_index =cut sub _mason_dir_index { my ($self, $interp, $path) = @_; $path =~ s!/$!!; if ( !$interp->comp_exists( $path ) && $interp->comp_exists( $path . "/index.html" ) ) { return $path . "/index.html"; } return $path; } =head2 CleanupRequest Clean ups globals, caches and other things that could be still there from previous requests: =over 4 =item Rollback any uncommitted transaction(s) =item Flush the ACL cache =item Flush records cache of the L<DBIx::SearchBuilder> if WebFlushDbCacheEveryRequest option is enabled, what is true by default and is not recommended to change. =item Clean up state of RT::Action::SendEmail using 'CleanSlate' method =item Flush tmp crypt key preferences =back =cut sub CleanupRequest { if ( $RT::Handle && $RT::Handle->TransactionDepth ) { $RT::Handle->ForceRollback; $RT::Logger->crit( "Transaction not committed. Usually indicates a software fault." . "Data loss may have occurred" ); } # Clean out the ACL cache. the performance impact should be marginal. # Consistency is imprived, too. RT::Principal->InvalidateACLCache(); DBIx::SearchBuilder::Record::Cachable->FlushCache if ( RT->Config->Get('WebFlushDbCacheEveryRequest') and UNIVERSAL::can( 'DBIx::SearchBuilder::Record::Cachable' => 'FlushCache' ) ); # cleanup global squelching of the mails require RT::Action::SendEmail; RT::Action::SendEmail->CleanSlate; if (RT->Config->Get('Crypt')->{'Enable'}) { RT::Crypt->UseKeyForEncryption(); RT::Crypt->UseKeyForSigning( undef ); } %RT::Ticket::MERGE_CACHE = ( effective => {}, merged => {} ); # RT::System persists between requests, so its attributes cache has to be # cleared manually. Without this, for example, subject tags across multiple # processes will remain cached incorrectly delete $RT::System->{attributes}; # Explicitly remove any tmpfiles that GPG opened, and close their # filehandles. unless we are doing inline psgi testing, which kills all the tmp file created by tests. File::Temp::cleanup() unless $INC{'Test/WWW/Mechanize/PSGI.pm'}; RT::ObjectCustomFieldValues::ClearOCFVCache(); } sub HTML::Mason::Exception::as_rt_error { my ($self) = @_; $RT::Logger->error( $self->as_text ); return "An internal RT error has occurred. Your administrator can find more details in RT's log files."; } =head1 CheckModPerlHandler Make sure we're not running with SetHandler perl-script. =cut sub CheckModPerlHandler{ my $self = shift; my $env = shift; # Plack::Handler::Apache2 masks MOD_PERL, so use MOD_PERL_API_VERSION return unless( $env->{'MOD_PERL_API_VERSION'} and $env->{'MOD_PERL_API_VERSION'} == 2); my $handler = $env->{'psgi.input'}->handler; return unless defined $handler && $handler eq 'perl-script'; $RT::Logger->critical(<<MODPERL); RT has problems when SetHandler is set to perl-script. Change SetHandler in your in httpd.conf to: SetHandler modperl For a complete example mod_perl configuration, see: https://bestpractical.com/rt/docs/@{[$RT::VERSION =~ /^(\d\.\d)/]}/web_deployment.html#mod_perl-2.xx MODPERL my $res = Plack::Response->new(500); $res->content_type("text/plain"); $res->body("Server misconfiguration; see error log for details"); return $res; } # PSGI App use RT::Interface::Web::Handler; use CGI::Emulate::PSGI; use Plack::Builder; use Plack::Request; use Plack::Response; use Plack::Util; sub PSGIApp { my $self = shift; # XXX: this is fucked require HTML::Mason::CGIHandler; require HTML::Mason::PSGIHandler::Streamy; my $h = RT::Interface::Web::Handler::NewHandler('HTML::Mason::PSGIHandler::Streamy'); $self->InitSessionDir; my $mason = sub { my $env = shift; # mod_fastcgi starts with an empty %ENV, but provides it on each # request. Pick it up and cache it during the first request. $ENV{PATH} //= $env->{PATH}; # HTML::Mason::Utils::cgi_request_args uses $ENV{QUERY_STRING} to # determine if to call url_param or not # (see comments in HTML::Mason::Utils::cgi_request_args) $ENV{QUERY_STRING} = $env->{QUERY_STRING}; { my $res = $self->CheckModPerlHandler($env); return $self->_psgi_response_cb( $res->finalize ) if $res; } unless (RT->InstallMode) { unless (eval { RT::ConnectToDatabase() }) { my $res = Plack::Response->new(503); $res->content_type("text/plain"); $res->body("Database inaccessible; contact the RT administrator (".RT->Config->Get("OwnerEmail").")"); return $self->_psgi_response_cb( $res->finalize, sub { $self->CleanupRequest } ); } } my $req = Plack::Request->new($env); # CGI.pm normalizes .. out of paths so when you requested # /NoAuth/../Ticket/Display.html we saw Ticket/Display.html # PSGI doesn't normalize .. so we have to deal ourselves. if ( $req->path_info =~ m{(^|/)\.\.?(/|$)} ) { $RT::Logger->crit("Invalid request for ".$req->path_info." aborting"); my $res = Plack::Response->new(400); return $self->_psgi_response_cb($res->finalize,sub { $self->CleanupRequest }); } $env->{PATH_INFO} = $self->_mason_dir_index( $h->interp, $req->path_info); return $self->_psgi_response_cb($h->handle_psgi($env), sub { $self->CleanupRequest() }); }; my $app = $self->StaticWrap($mason); # Add REST2 $app = RT::REST2::PSGIWrap('RT::REST2', $app); for my $plugin (RT->Config->Get("Plugins")) { my $wrap = $plugin->can("PSGIWrap") or next; $app = $wrap->($plugin, $app); } return $app; } sub StaticWrap { my $self = shift; my $app = shift; my $builder = Plack::Builder->new; my $headers = RT::Interface::Web::GetStaticHeaders(Time => 'forever'); for my $static ( RT->Config->Get('StaticRoots') ) { if ( ref $static && ref $static eq 'HASH' ) { $builder->add_middleware( '+RT::Interface::Web::Middleware::StaticHeaders', path => $static->{'path'}, headers => $headers, ); $builder->add_middleware( 'Plack::Middleware::Static', pass_through => 1, %$static ); } else { $RT::Logger->error( "Invalid config StaticRoots: item can only be a hashref" ); } } my $path = sub { s!^/static/!! }; $builder->add_middleware( '+RT::Interface::Web::Middleware::StaticHeaders', path => $path, headers => $headers, ); for my $root (RT::Interface::Web->StaticRoots) { $builder->add_middleware( 'Plack::Middleware::Static', path => $path, root => $root, pass_through => 1, ); } return $builder->to_app($app); } sub _psgi_response_cb { my $self = shift; my ($ret, $cleanup) = @_; Plack::Util::response_cb ($ret, sub { my $res = shift; if ( RT->Config->Get('Framebusting') ) { # XXX TODO: Do we want to make the value of this header configurable? Plack::Util::header_set($res->[1], 'X-Frame-Options' => 'DENY'); } return sub { if (!defined $_[0]) { $cleanup->(); return ''; } # XXX: Ideally, responses should flag if they need # to be encoded, rather than relying on the UTF-8 # flag return Encode::encode("UTF-8",$_[0]) if utf8::is_utf8($_[0]); return $_[0]; }; }); } sub GetStatic { my $class = shift; my $path = shift; my $static = $class->StaticWrap( # Anything the static wrap doesn't handle gets 404'd. sub { [404, [], []] } ); my $response = HTTP::Response->from_psgi( $static->( HTTP::Request->new(GET => $path)->to_psgi ) ); return $response; } 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Web/Scrubber.pm�����������������������������������������������������������000644 �000765 �000024 �00000015273 14005011336 020626� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Interface::Web::Scrubber; use strict; use warnings; use 5.010; use base qw/HTML::Scrubber/; use HTML::Gumbo; =head1 NAME RT::Interface::Web::Scrubber =head1 DESCRIPTION This is a subclass of L<HTML::Scrubber> which automatically configures itself with a sane and safe default set of rules. Additionally, it ensures that the input is balanced HTML by use of the L<HTML::Gumbo> on the input to L</scrub>. =head1 VARIABLES These variables can be altered by creating a C<Scrubber_Local.pm> file, containing something of the form: package RT::Interface::Web::Scrubber; # Allow the "title" attribute $ALLOWED_ATTRIBUTES{title} = 1; =over =item C<@ALLOWED_TAGS> Passed to L<HTML::Scrubber/allow>. =item C<%ALLOWED_ATTRIBUTES> Passed into L<HTML::Scrubber/default>. =item C<%RULES> Passed to L<HTML::Scrubber/rules>. =back =cut our @ALLOWED_TAGS = qw( A B U P BR I HR BR SMALL EM FONT SPAN STRONG SUB SUP S DEL STRIKE H1 H2 H3 H4 H5 H6 INS DIV UL OL LI DL DT DD PRE BLOCKQUOTE BDO TABLE THEAD TBODY TFOOT TR TD TH ); our %ALLOWED_ATTRIBUTES = ( # Match http, https, ftp, mailto and relative urls # XXX: we also scrub format strings with this module then allow simple config options href => qr{^(?:https?:|ftp:|mailto:|/|__Web(?:Path|HomePath|BaseURL|URL)__)}i, face => 1, size => 1, color => 1, target => 1, style => qr{ ^(?:\s* (?:(?:background-)?color: \s* (?:rgb\(\s* \d+, \s* \d+, \s* \d+ \s*\) | # rgb(d,d,d) \#[a-f0-9]{3,6} | # #fff or #ffffff [\w\-]+ | # green, light-blue, etc. hsl\(\s* \d+, \s* \d+\%, \s* \d+ \s*\%\) # hsl(120,75%,60%) ) | text-align: \s* \w+ | font-size: \s* [\w.\-]+ | font-family: \s* [\w\s"',.\-]+ | font-weight: \s* [\w\-]+ | border-style: \s* \w+ | border-color: \s* [#\w]+ | border-width: \s* [\s\w]+ | padding: \s* [\s\w]+ | margin: \s* [\s\w]+ | # MS Office styles, which are probably fine. If we don't, then any # associated styles in the same attribute get stripped. mso-[\w\-]+?: \s* [\w\s"',.\-]+ )\s* ;? \s*) +$ # one or more of these allowed properties from here 'till sunset }ix, dir => qr/^(rtl|ltr)$/i, lang => qr/^\w+(-\w+)?$/, colspan => 1, rowspan => 1, align => 1, valign => 1, cellspacing => 1, cellpadding => 1, border => 1, width => 1, height => 1, class => qr/text/, # generic classes like 'text-huge' # timeworked per user attributes 'data-ticket-id' => 1, 'data-ticket-class' => 1, ); our %RULES = (); =head1 METHODS =head2 new Returns a new L<RT::Interface::Web::Scrubber> object, configured with the above globals. Takes no arguments. =cut sub new { my $class = shift; my $self = $class->SUPER::new(@_); $self->default( 0, { %ALLOWED_ATTRIBUTES, '*' => 0, # require attributes be explicitly allowed }, ); $self->deny(qw[*]); $self->allow(@ALLOWED_TAGS); # If we're displaying images, let embedded ones through if (RT->Config->Get('ShowTransactionImages') or RT->Config->Get('ShowRemoteImages')) { my @src; push @src, qr/^cid:/i if RT->Config->Get('ShowTransactionImages'); push @src, $ALLOWED_ATTRIBUTES{'href'} if RT->Config->Get('ShowRemoteImages'); $RULES{'img'} ||= { '*' => 0, alt => 1, src => join("|", @src), }; } $self->rules(%RULES); # Scrubbing comments is vital since IE conditional comments can contain # arbitrary HTML and we'd pass it right on through. $self->comment(0); return $self; } =head2 gumbo Returns a L<HTML::Gumbo> object. =cut sub gumbo { my $self = shift; return $self->{_gumbo} //= HTML::Gumbo->new; } =head2 scrub TEXT Takes a string of HTML, and returns it scrubbed, via L<HTML::Gumbo> then the rules. This is a more limited interface than L<HTML::Scrubber/scrub>. =cut sub scrub { my $self = shift; my $Content = shift // ''; # First pass through HTML::Gumbo to balance the tags eval { $Content = $self->gumbo->parse( $Content ); chomp $Content }; warn "HTML::Gumbo pre-parse failed: $@" if $@; return $self->SUPER::scrub($Content); } RT::Base->_ImportOverlays(); 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Web/Request.pm������������������������������������������������������������000644 �000765 �000024 �00000013510 14005011336 020477� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Interface::Web::Request; use strict; use warnings; use HTML::Mason::PSGIHandler; use base qw(HTML::Mason::Request::PSGI); use Params::Validate qw(:all); my %deprecated = (); sub new { my $class = shift; $class->valid_params( %{ $class->valid_params },cgi_request => { type => OBJECT, optional => 1 } ); return $class->SUPER::new(@_); } =head2 callback Takes hash with optional C<CallbackPage>, C<CallbackName> and C<CallbackOnce> arguments, other arguments are passed throught to callback components. =over 4 =item CallbackPage Page path relative to the root, leading slash is mandatory. By default is equal to path of the caller component. =item CallbackName Name of the callback. C<Default> is used unless specified. =item CallbackOnce By default is false, otherwise runs callbacks only once per process of the server. Such callbacks can be used to fill structures. =back Searches for callback components in F<< /Callbacks/<any dir>/CallbackPage/CallbackName >>, for example F</Callbacks/MyExtension/autohandler/Default> would be called as default callback for F</autohandler>. =cut { my %cache = (); my %called = (); sub callback { my ($self, %args) = @_; my $name = delete $args{'CallbackName'} || 'Default'; my $page = delete $args{'CallbackPage'} || $self->callers(0)->path; unless ( $page ) { $RT::Logger->error("Couldn't get a page name for callbacks"); return; } my $CacheKey = "$page--$name"; return 1 if delete $args{'CallbackOnce'} && $called{ $CacheKey }; $called{ $CacheKey } = 1; my $callbacks = $cache{ $CacheKey }; unless ( $callbacks ) { $callbacks = []; my $path = "/Callbacks/*$page/$name"; my @roots = RT::Interface::Web->ComponentRoots; my %seen; @$callbacks = ( grep defined && length, # Skip backup files, files without a leading package name, # and files we've already seen grep !$seen{$_}++ && !m{/\.} && !m{~$} && m{^/Callbacks/[^/]+\Q$page/$name\E$}, map { sort $self->interp->resolver->glob_path($path, $_) } @roots ); foreach my $comp (keys %seen) { next unless $seen{$comp} > 1; $RT::Logger->error("Found more than one occurrence of the $comp callback. This may cause only one of the callbacks to run. Look for the duplicate Callback in your @roots"); } $cache{ $CacheKey } = $callbacks unless RT->Config->Get('DevelMode'); if (@{ $callbacks } && $deprecated{$page}{$name}) { RT->Deprecated( Message => "The callback $name on page $page is deprecated", Detail => "Callback list:\n" . join("\n", @{ $callbacks }), Stack => 0, %{ $deprecated{$page}{$name} }, ); } } my @rv; foreach my $cb ( @$callbacks ) { push @rv, scalar $self->comp( $cb, %args ); } return @rv; } sub clear_callback_cache { %cache = %called = (); } } =head2 request_path Returns path of the request. Very close to C<< $m->request_comp->path >>, but if called in a dhandler returns path of the request without dhandler name, but with dhandler arguments instead. =cut sub request_path { my $self = shift; my $path = $self->request_comp->path; # disabled dhandlers, not RT case, but anyway return $path unless my $dh_name = $self->dhandler_name; # not a dhandler return $path unless substr($path, -length("/$dh_name")) eq "/$dh_name"; substr($path, -length $dh_name) = $self->dhandler_arg; return $path; } =head2 abort Logs any recorded SQL statements for this request before calling the standard abort. =cut sub abort { my $self = shift; RT::Interface::Web::LogRecordedSQLStatements( RequestData => { Path => $self->request_path, }, ); return $self->SUPER::abort(@_); } 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Web/Session.pm������������������������������������������������������������000644 �000765 �000024 �00000022356 14005011336 020502� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Interface::Web::Session; use warnings; use strict; use RT::CurrentUser; =head1 NAME RT::Interface::Web::Session - RT web session class =head1 SYNOPSYS =head1 DESCRIPTION RT session class and utilities. CLASS METHODS can be used without creating object instances, it's mainly utilities to clean unused session records. Object is tied hash and can be used to access session data. =head1 METHODS =head2 CLASS METHODS =head3 Class Returns name of the class that is used as sessions storage. =cut sub Class { my $self = shift; my $class = RT->Config->Get('WebSessionClass') || $self->Backends->{RT->Config->Get('DatabaseType')} || 'Apache::Session::File'; $class->require or die "Can't load $class: $@"; return $class; } =head3 Backends Returns hash reference with names of the databases as keys and sessions class names as values. =cut sub Backends { return { mysql => 'Apache::Session::MySQL', Pg => 'Apache::Session::Postgres', Oracle => 'Apache::Session::Oracle', }; } =head3 Attributes Returns hash reference with attributes that are used to create new session objects. =cut sub Attributes { my $class = $_[0]->Class; my $res; if ( my %props = RT->Config->Get('WebSessionProperties') ) { $res = \%props; } elsif ( $class->isa('Apache::Session::File') ) { $res = { Directory => $RT::MasonSessionDir, LockDirectory => $RT::MasonSessionDir, Transaction => 1, }; } else { $res = { Handle => $RT::Handle->dbh, LockHandle => $RT::Handle->dbh, Transaction => 1, }; } $res->{LongReadLen} = RT->Config->Get('MaxAttachmentSize') if $class->isa('Apache::Session::Oracle'); return $res; } =head3 Ids Returns array ref with list of the session IDs. =cut sub Ids { my $self = shift || __PACKAGE__; my $attributes = $self->Attributes; if( $attributes->{Directory} ) { return $self->_IdsDir( $attributes->{Directory} ); } else { return $self->_IdsDB( $RT::Handle->dbh ); } } sub _IdsDir { my ($self, $dir) = @_; require File::Find; my %file; File::Find::find( sub { return unless /^[a-zA-Z0-9]+$/; $file{$_} = (stat($_))[9]; }, $dir, ); return [ sort { $file{$a} <=> $file{$b} } keys %file ]; } sub _IdsDB { my ($self, $dbh) = @_; my $ids = $dbh->selectcol_arrayref("SELECT id FROM sessions ORDER BY LastUpdated DESC"); die "couldn't get ids: ". $dbh->errstr if $dbh->errstr; return $ids; } =head3 ClearOld Takes seconds and deletes all sessions that are older. =cut sub ClearOld { my $class = shift || __PACKAGE__; my $attributes = $class->Attributes; if( $attributes->{Directory} ) { return $class->_ClearOldDir( $attributes->{Directory}, @_ ); } else { return $class->_ClearOldDB( $RT::Handle->dbh, @_ ); } } sub _ClearOldDB { my ($self, $dbh, $older_than) = @_; my $rows; unless( int $older_than ) { $rows = $dbh->do("DELETE FROM sessions"); die "couldn't delete sessions: ". $dbh->errstr unless defined $rows; } else { require POSIX; my $date = POSIX::strftime("%Y-%m-%d %H:%M", localtime( time - int $older_than ) ); my $sth = $dbh->prepare("DELETE FROM sessions WHERE LastUpdated < ?"); die "couldn't prepare query: ". $dbh->errstr unless $sth; $rows = $sth->execute( $date ); die "couldn't execute query: ". $dbh->errstr unless defined $rows; } $RT::Logger->info("successfully deleted $rows sessions"); return; } sub _ClearOldDir { my ($self, $dir, $older_than) = @_; require File::Spec if int $older_than; my $now = time; my $class = $self->Class; my $attrs = $self->Attributes; foreach my $id( @{ $self->Ids } ) { if( int $older_than ) { my $mtime = (stat(File::Spec->catfile($dir,$id)))[9]; if( $mtime > $now - $older_than ) { $RT::Logger->debug("skipped session '$id', isn't old"); next; } } my %session; local $@; eval { tie %session, $class, $id, $attrs }; if( $@ ) { $RT::Logger->debug("skipped session '$id', couldn't load: $@"); next; } tied(%session)->delete; $RT::Logger->info("successfully deleted session '$id'"); } # Apache::Session::Lock::File will clean out locks older than X, but it # leaves around bogus locks if they're too new, even though they're # guaranteed dead. On even just largeish installs, the accumulated number # of them may bump into ext3/4 filesystem limits since Apache::Session # doesn't use a fan-out tree. my $lock = Apache::Session::Lock::File->new; $lock->clean( $dir, $older_than ); # Take matters into our own hands and clear bogus locks hanging around # regardless of how recent they are. $self->ClearOrphanLockFiles($dir); return; } =head3 ClearOrphanLockFiles Takes a directory in which to look for L<Apache::Session::Lock::File> locks which no longer have a corresponding session file. If not provided, the directory is taken from the session configuration data. =cut sub ClearOrphanLockFiles { my $class = shift; my $dir = shift || $class->Attributes->{Directory} or return; if (opendir my $dh, $dir) { for (readdir $dh) { next unless /^Apache-Session-([0-9a-f]{32})\.lock$/; next if -e "$dir/$1"; RT->Logger->debug("deleting orphaned session lockfile '$_'"); unlink "$dir/$_" or warn "Failed to unlink session lockfile $dir/$_: $!"; } closedir $dh; } else { warn "Unable to open directory '$dir' for reading: $!"; } } =head3 ClearByUser Checks all sessions and if user has more then one session then leave only the latest one. =cut sub ClearByUser { my $self = shift || __PACKAGE__; my $class = $self->Class; my $attrs = $self->Attributes; my $deleted; my %seen = (); foreach my $id( @{ $self->Ids } ) { my %session; local $@; eval { tie %session, $class, $id, $attrs }; if( $@ ) { $RT::Logger->debug("skipped session '$id', couldn't load: $@"); next; } if( $session{'CurrentUser'} && $session{'CurrentUser'}->id ) { unless( $seen{ $session{'CurrentUser'}->id }++ ) { $RT::Logger->debug("skipped session '$id', first user's session"); next; } } tied(%session)->delete; $RT::Logger->info("successfully deleted session '$id'"); $deleted++; } $self->ClearOrphanLockFiles if $deleted; } sub TIEHASH { my $self = shift; my $id = shift; my $class = $self->Class; my $attrs = $self->Attributes; my %session; local $@; eval { tie %session, $class, $id, $attrs }; eval { tie %session, $class, undef, $attrs } if $@; if ( $@ ) { die "RT couldn't store your session. " . "This may mean that that the directory '$RT::MasonSessionDir' isn't writable or a database table is missing or corrupt.\n\n" . $@; } return tied %session; } 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Web/MenuBuilder.pm��������������������������������������������������������000644 �000765 �000024 �00000246117 14005011336 021275� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} =head1 NAME RT::Interface::Web::MenuBuilder =cut use strict; use warnings; package RT::Interface::Web::MenuBuilder; sub loc { HTML::Mason::Commands::loc( @_ ); } sub QueryString { my %args = @_; my $u = URI->new(); $u->query_form(map { $_ => $args{$_} } sort keys %args); return $u->query; } sub BuildMainNav { my $request_path = shift; my $top = shift; my $widgets = shift; my $page = shift; my %args = ( @_ ); my $query_string = $args{QueryString}; my $query_args = $args{QueryArgs}; my $current_user = $HTML::Mason::Commands::session{CurrentUser}; if ($request_path =~ m{^/Asset/}) { $widgets->child( asset_search => raw_html => $HTML::Mason::Commands::m->scomp('/Asset/Elements/Search') ); $widgets->child( create_asset => raw_html => $HTML::Mason::Commands::m->scomp('/Asset/Elements/CreateAsset') ); } elsif ($request_path =~ m{^/Articles/}) { $widgets->child( article_search => raw_html => $HTML::Mason::Commands::m->scomp('/Articles/Elements/GotoArticle') ); $widgets->child( create_article => raw_html => $HTML::Mason::Commands::m->scomp('/Articles/Elements/CreateArticleButton') ); } else { $widgets->child( simple_search => raw_html => $HTML::Mason::Commands::m->scomp('SimpleSearch', Placeholder => loc('Search Tickets')) ); $widgets->child( create_ticket => raw_html => $HTML::Mason::Commands::m->scomp('CreateTicket') ); } my $home = $top->child( home => title => loc('Homepage'), path => '/' ); $home->child( create_ticket => title => loc("Create Ticket"), path => "/Ticket/Create.html" ); my $search = $top->child( search => title => loc('Search'), path => '/Search/Simple.html' ); my $tickets = $search->child( tickets => title => loc('Tickets'), path => '/Search/Build.html' ); $tickets->child( simple => title => loc('Simple Search'), path => "/Search/Simple.html" ); $tickets->child( new => title => loc('New Search'), path => "/Search/Build.html?NewQuery=1" ); my $recents = $tickets->child( recent => title => loc('Recently Viewed')); for my $ticket ( $current_user->RecentlyViewedTickets ) { my $title = $ticket->{subject} || loc( "(No subject)" ); if ( length $title > 50 ) { $title = substr($title, 0, 47); $title =~ s/\s+$//; $title .= "..."; } $title = "#$ticket->{id}: " . $title; $recents->child( "$ticket->{id}" => title => $title, path => "/Ticket/Display.html?id=" . $ticket->{id} ); } $search->child( articles => title => loc('Articles'), path => "/Articles/Article/Search.html" ) if $current_user->HasRight( Right => 'ShowArticlesMenu', Object => RT->System ); $search->child( users => title => loc('Users'), path => "/User/Search.html" ); $search->child( groups => title => loc('Groups'), path => "/Group/Search.html", description => 'Group search' ); my $search_assets; if ($HTML::Mason::Commands::session{CurrentUser}->HasRight( Right => 'ShowAssetsMenu', Object => RT->System )) { $search_assets = $search->child( assets => title => loc("Assets"), path => "/Search/Build.html?Class=RT::Assets" ); if (!RT->Config->Get('AssetHideSimpleSearch')) { $search_assets->child("asset_simple", title => loc("Simple Search"), path => "/Asset/Search/"); } $search_assets->child("assetsql", title => loc("New Search"), path => "/Search/Build.html?Class=RT::Assets;NewQuery=1"); } my $txns = $search->child( transactions => title => loc('Transactions'), path => '/Search/Build.html?Class=RT::Transactions;ObjectType=RT::Ticket' ); my $txns_tickets = $txns->child( tickets => title => loc('Tickets'), path => "/Search/Build.html?Class=RT::Transactions;ObjectType=RT::Ticket" ); $txns_tickets->child( new => title => loc('New Search'), path => "/Search/Build.html?Class=RT::Transactions;ObjectType=RT::Ticket;NewQuery=1" ); my $reports = $top->child( reports => title => loc('Reports'), description => loc('Reports and Dashboards'), path => loc('/Reports'), ); unless ($HTML::Mason::Commands::session{'dashboards_in_menu'}) { my $dashboards_in_menu = $current_user->UserObj->Preferences( 'DashboardsInMenu', {}, ); unless ($dashboards_in_menu->{dashboards}) { my ($default_dashboards) = RT::System->new( $current_user ) ->Attributes ->Named('DashboardsInMenu'); if ($default_dashboards) { $dashboards_in_menu = $default_dashboards->Content; } } $HTML::Mason::Commands::session{'dashboards_in_menu'} = $dashboards_in_menu->{dashboards} || []; } my @dashboards; for my $id ( @{$HTML::Mason::Commands::session{'dashboards_in_menu'}} ) { my $dash = RT::Dashboard->new( $current_user ); my ( $status, $msg ) = $dash->LoadById($id); if ( $status ) { push @dashboards, $dash; } else { $RT::Logger->debug( "Failed to load dashboard $id: $msg, removing from menu" ); $home->RemoveDashboardMenuItem( DashboardId => $id, CurrentUser => $HTML::Mason::Commands::session{CurrentUser}->UserObj, ); @{ $HTML::Mason::Commands::session{'dashboards_in_menu'} } = grep { $_ != $id } @{ $HTML::Mason::Commands::session{'dashboards_in_menu'} }; } } if (@dashboards) { for my $dash (@dashboards) { $reports->child( 'dashboard-' . $dash->id, title => $dash->Name, path => '/Dashboards/' . $dash->id . '/' . $dash->Name ); } } # Get the list of reports in the Reports menu unless ( $HTML::Mason::Commands::session{'reports_in_menu'} && ref( $HTML::Mason::Commands::session{'reports_in_menu'}) eq 'ARRAY' && @{$HTML::Mason::Commands::session{'reports_in_menu'}} ) { my $reports_in_menu = $current_user->UserObj->Preferences( 'ReportsInMenu', {}, ); unless ( $reports_in_menu && ref $reports_in_menu eq 'ARRAY' ) { my ($default_reports) = RT::System->new( RT->SystemUser ) ->Attributes ->Named('ReportsInMenu'); if ($default_reports) { $reports_in_menu = $default_reports->Content; } else { $reports_in_menu = []; } } $HTML::Mason::Commands::session{'reports_in_menu'} = $reports_in_menu || []; } for my $report ( @{$HTML::Mason::Commands::session{'reports_in_menu'}} ) { $reports->child( $report->{id} => title => $report->{title}, path => $report->{path}, ); } $reports->child( edit => title => loc('Update This Menu'), path => '/Prefs/DashboardsInMenu.html' ); $reports->child( more => title => loc('All Dashboards'), path => '/Dashboards/index.html' ); my $dashboard = RT::Dashboard->new( $current_user ); if ( $dashboard->CurrentUserCanCreateAny ) { $reports->child('dashboard_create' => title => loc('New Dashboard'), path => "/Dashboards/Modify.html?Create=1" ); } if ($current_user->HasRight( Right => 'ShowArticlesMenu', Object => RT->System )) { my $articles = $top->child( articles => title => loc('Articles'), path => "/Articles/index.html"); $articles->child( articles => title => loc('Overview'), path => "/Articles/index.html" ); $articles->child( topics => title => loc('Topics'), path => "/Articles/Topics.html" ); $articles->child( create => title => loc('Create'), path => "/Articles/Article/PreCreate.html" ); $articles->child( search => title => loc('Search'), path => "/Articles/Article/Search.html" ); } if ($current_user->HasRight( Right => 'ShowAssetsMenu', Object => RT->System )) { my $assets = $top->child( "assets", title => loc("Assets"), path => RT->Config->Get('AssetHideSimpleSearch') ? "/Search/Build.html?Class=RT::Assets;NewQuery=1" : "/Asset/Search/", ); $assets->child( "create", title => loc("Create"), path => "/Asset/Create.html" ); if (!RT->Config->Get('AssetHideSimpleSearch')) { $assets->child( "simple_search", title => loc("Simple Search"), path => "/Asset/Search/" ); } $assets->child( "search", title => loc("New Search"), path => "/Search/Build.html?Class=RT::Assets;NewQuery=1" ); } my $tools = $top->child( tools => title => loc('Tools'), path => '/Tools/index.html' ); $tools->child( my_day => title => loc('My Day'), description => loc('Easy updating of your open tickets'), path => '/Tools/MyDay.html', ); if ( RT->Config->Get('EnableReminders') ) { $tools->child( my_reminders => title => loc('My Reminders'), description => loc('Easy viewing of your reminders'), path => '/Tools/MyReminders.html', ); } if ( $current_user->HasRight( Right => 'ShowApprovalsTab', Object => RT->System ) ) { $tools->child( approval => title => loc('Approval'), description => loc('My Approvals'), path => '/Approvals/', ); } if ( $current_user->HasRight( Right => 'ShowConfigTab', Object => RT->System ) ) { _BuildAdminMenu( $request_path, $top, $widgets, $page, %args ); } my $username = '<span class="current-user">' . $HTML::Mason::Commands::m->interp->apply_escapes($current_user->Name, 'h') . '</span>'; my $about_me = $top->child( 'preferences' => title => loc('Logged in as [_1]', $username), escape_title => 0, path => '/User/Summary.html?id=' . $current_user->id, sort_order => 99, ); $about_me->child( rt_name => title => loc("RT for [_1]", RT->Config->Get('rtname')), path => '/' ); if ( $current_user->UserObj && $current_user->HasRight( Right => 'ModifySelf', Object => RT->System )) { my $settings = $about_me->child( settings => title => loc('Settings'), path => '/Prefs/Other.html' ); $settings->child( options => title => loc('Preferences'), path => '/Prefs/Other.html' ); $settings->child( about_me => title => loc('About me'), path => '/Prefs/AboutMe.html' ); if ( $current_user->HasRight( Right => 'ManageAuthTokens', Object => RT->System ) ) { $settings->child( auth_tokens => title => loc('Auth Tokens'), path => '/Prefs/AuthTokens.html' ); } $settings->child( search_options => title => loc('Search options'), path => '/Prefs/SearchOptions.html' ); $settings->child( myrt => title => loc('RT at a glance'), path => '/Prefs/MyRT.html' ); $settings->child( dashboards_in_menu => title => loc('Modify Reports menu'), path => '/Prefs/DashboardsInMenu.html', ); $settings->child( queue_list => title => loc('Queue list'), path => '/Prefs/QueueList.html' ); my $search_menu = $settings->child( 'saved-searches' => title => loc('Saved Searches') ); my $searches = [ $HTML::Mason::Commands::m->comp( "/Search/Elements/SearchesForObject", Object => RT::System->new( $current_user )) ]; my $i = 0; for my $search (@$searches) { $search_menu->child( "search-" . $i++ => title => $search->[1], path => "/Prefs/Search.html?" . QueryString( name => ref( $search->[2] ) . '-' . $search->[2]->Id ), ); } if ( $request_path =~ qr{/Prefs/(?:SearchOptions|CustomDateRanges)\.html} ) { $page->child( search_options => title => loc('Search Preferences'), path => "/Prefs/SearchOptions.html" ); $page->child( custom_date_ranges => title => loc('Custom Date Ranges'), path => "/Prefs/CustomDateRanges.html" ) } if ( $request_path =~ m{^/Prefs/AuthTokens\.html} ) { $page->child( create_auth_token => title => loc('Create'), raw_html => q[<a class="btn menu-item" href="#create-auth-token" data-toggle="modal" rel="modal:open">].loc("Create")."</a>" ); } } my $logout_url = RT->Config->Get('LogoutURL'); if ( $current_user->Name && ( !RT->Config->Get('WebRemoteUserAuth') || RT->Config->Get('WebFallbackToRTLogin') )) { $about_me->child( logout => title => loc('Logout'), path => $logout_url ); } if ( $request_path =~ m{^/Dashboards/(\d+)?}) { if ( my $id = ( $1 || $HTML::Mason::Commands::DECODED_ARGS->{'id'} ) ) { my $obj = RT::Dashboard->new( $current_user ); $obj->LoadById($id); if ( $obj and $obj->id ) { $page->child( basics => title => loc('Basics'), path => "/Dashboards/Modify.html?id=" . $obj->id); $page->child( content => title => loc('Content'), path => "/Dashboards/Queries.html?id=" . $obj->id); $page->child( subscription => title => loc('Subscription'), path => "/Dashboards/Subscription.html?id=" . $obj->id) if $obj->CurrentUserCanSubscribe; $page->child( show => title => loc('Show'), path => "/Dashboards/" . $obj->id . "/" . $obj->Name) } } } my $search_results_page_menu; if ( $request_path =~ m{^/Ticket/} ) { if ( ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} || '' ) =~ /^(\d+)$/ ) { my $id = $1; my $obj = RT::Ticket->new( $current_user ); $obj->Load($id); if ( $obj and $obj->id ) { my $actions = $page->child( actions => title => loc('Actions'), sort_order => 95 ); my %can = %{ $obj->CurrentUser->PrincipalObj->HasRights( Object => $obj ) }; # since CurrentUserCanSetOwner returns ($ok, $msg), the parens ensure that $can{} gets $ok ( $can{'_ModifyOwner'} ) = $obj->CurrentUserCanSetOwner(); my $can = sub { unless ($_[0] eq 'ExecuteCode') { return $can{$_[0]} || $can{'SuperUser'}; } else { return !RT->Config->Get('DisallowExecuteCode') && ( $can{'ExecuteCode'} || $can{'SuperUser'} ); } }; $page->child( bookmark => raw_html => $HTML::Mason::Commands::m->scomp( '/Ticket/Elements/Bookmark', id => $id ), sort_order => 98 ); if ($can->('ModifyTicket')) { $page->child( timer => raw_html => $HTML::Mason::Commands::m->scomp( '/Ticket/Elements/PopupTimerLink', id => $id ), sort_order => 99 ); } $page->child( display => title => loc('Display'), path => "/Ticket/Display.html?id=" . $id ); $page->child( history => title => loc('History'), path => "/Ticket/History.html?id=" . $id ); # comment out until we can do it for an individual custom field #if ( $can->('ModifyTicket') || $can->('ModifyCustomField') ) { $page->child( basics => title => loc('Basics'), path => "/Ticket/Modify.html?id=" . $id ); #} if ( $can->('ModifyTicket') || $can->('_ModifyOwner') || $can->('Watch') || $can->('WatchAsAdminCc') ) { $page->child( people => title => loc('People'), path => "/Ticket/ModifyPeople.html?id=" . $id ); } if ( $can->('ModifyTicket') ) { $page->child( dates => title => loc('Dates'), path => "/Ticket/ModifyDates.html?id=" . $id ); $page->child( links => title => loc('Links'), path => "/Ticket/ModifyLinks.html?id=" . $id ); } #if ( $can->('ModifyTicket') || $can->('ModifyCustomField') || $can->('_ModifyOwner') ) { $page->child( jumbo => title => loc('Jumbo'), path => "/Ticket/ModifyAll.html?id=" . $id ); #} if ( RT->Config->Get('EnableReminders') ) { $page->child( reminders => title => loc('Reminders'), path => "/Ticket/Reminders.html?id=" . $id ); } if ( $can->('ModifyTicket') or $can->('ReplyToTicket') ) { $actions->child( reply => title => loc('Reply'), path => "/Ticket/Update.html?Action=Respond;id=" . $id ); } if ( $can->('ModifyTicket') or $can->('CommentOnTicket') ) { $actions->child( comment => title => loc('Comment'), path => "/Ticket/Update.html?Action=Comment;id=" . $id ); } if ( $can->('ForwardMessage') ) { $actions->child( forward => title => loc('Forward'), path => "/Ticket/Forward.html?id=" . $id ); } my $hide_resolve_with_deps = RT->Config->Get('HideResolveActionsWithDependencies') && $obj->HasUnresolvedDependencies; my $current = $obj->Status; my $lifecycle = $obj->LifecycleObj; my $i = 1; foreach my $info ( $lifecycle->Actions($current) ) { my $next = $info->{'to'}; next unless $lifecycle->IsTransition( $current => $next ); my $check = $lifecycle->CheckRight( $current => $next ); next unless $can->($check); next if $hide_resolve_with_deps && $lifecycle->IsInactive($next) && !$lifecycle->IsInactive($current); my $action = $info->{'update'} || ''; my $url = '/Ticket/'; $url .= "Update.html?". QueryString( $action ? (Action => $action) : (SubmitTicket => 1, Status => $next), DefaultStatus => $next, id => $id, ); my $key = $info->{'label'} || ucfirst($next); $actions->child( $key => title => loc( $key ), path => $url); } my ($can_take, $tmsg) = $obj->CurrentUserCanSetOwner( Type => 'Take' ); my ($can_steal, $smsg) = $obj->CurrentUserCanSetOwner( Type => 'Steal' ); my ($can_untake, $umsg) = $obj->CurrentUserCanSetOwner( Type => 'Untake' ); if ( $can_take ){ $actions->child( take => title => loc('Take'), path => "/Ticket/Display.html?Action=Take;id=" . $id ); } elsif ( $can_steal ){ $actions->child( steal => title => loc('Steal'), path => "/Ticket/Display.html?Action=Steal;id=" . $id ); } elsif ( $can_untake ){ $actions->child( untake => title => loc('Untake'), path => "/Ticket/Display.html?Action=Untake;id=" . $id ); } # TODO needs a "Can extract article into a class applied to this queue" check $actions->child( 'extract-article' => title => loc('Extract Article'), path => "/Articles/Article/ExtractIntoClass.html?Ticket=".$obj->id, ) if $current_user->HasRight( Right => 'ShowArticlesMenu', Object => RT->System ); $actions->child( 'edit_assets' => title => loc('Edit Assets'), path => "/Asset/Search/Bulk.html?Query=Linked=" . $obj->id, ) if $can->('ModifyTicket') && $HTML::Mason::Commands::session{CurrentUser}->HasRight( Right => 'ShowAssetsMenu', Object => RT->System ); if ( defined $HTML::Mason::Commands::session{"collection-RT::Tickets"} ) { # we have to update session data if we get new ItemMap my $updatesession = 1 unless ( $HTML::Mason::Commands::session{"collection-RT::Tickets"}->{'item_map'} ); my $item_map = $HTML::Mason::Commands::session{"collection-RT::Tickets"}->ItemMap; if ($updatesession) { $HTML::Mason::Commands::session{"collection-RT::Tickets"}->PrepForSerialization(); } my $search = $search_results_page_menu = $HTML::Mason::Commands::m->notes('search-results-page-menu', RT::Interface::Web::Menu->new()); # Don't display prev links if we're on the first ticket if ( $item_map->{$id}->{prev} ) { $search->child( first => title => q{<span class="fas fa-angle-double-left"></span>}, escape_title => 0, class => "nav", path => "/Ticket/Display.html?id=" . $item_map->{first}, attributes => { 'data-toggle' => 'tooltip', 'data-original-title' => loc('First'), alt => loc('First'), }, ); $search->child( prev => title => q{<span class="fas fa-angle-left"></span>}, escape_title => 0, class => "nav", path => "/Ticket/Display.html?id=" . $item_map->{$id}->{prev}, attributes => { 'data-toggle' => 'tooltip', 'data-original-title' => loc('Prev'), alt => loc('Prev'), }, ); } # Don't display next links if we're on the last ticket if ( $item_map->{$id}->{next} ) { $search->child( next => title => q{<span class="fas fa-angle-right"></span>}, escape_title => 0, class => "nav", path => "/Ticket/Display.html?id=" . $item_map->{$id}->{next}, attributes => { 'data-toggle' => 'tooltip', 'data-original-title' => loc('Next'), alt => loc('Next'), }, ); if ( $item_map->{last} ) { $search->child( last => title => q{<span class="fas fa-angle-double-right"></span>}, escape_title => 0, class => "nav", path => "/Ticket/Display.html?id=" . $item_map->{last}, attributes => { 'data-toggle' => 'tooltip', 'data-original-title' => loc('Last'), alt => loc('Last'), }, ); } } } } } } # display "View ticket" link in transactions if ( $request_path =~ m{^/Transaction/Display.html} ) { if ( ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} || '' ) =~ /^(\d+)$/ ) { my $txn_id = $1; my $txn = RT::Transaction->new( $current_user ); $txn->Load( $txn_id ); my $object = $txn->Object; if ( $object->Id ) { my $object_type = $object->RecordType; if ( $object_type eq 'Ticket' ) { $page->child( view_ticket => title => loc("View ticket"), path => "/Ticket/Display.html?id=" . $object->Id ); } } } } # Scope here so we can share in the Privileged callback my $args = ''; my $has_query = ''; if ( ( $request_path =~ m{^/(?:Ticket|Transaction|Search)/} && $request_path !~ m{^/Search/Simple\.html} ) || ( $request_path =~ m{^/Search/Simple\.html} && $HTML::Mason::Commands::DECODED_ARGS->{'q'} ) # TODO: Asset simple search and SQL search don't share query, we # can't simply link to SQL search page on asset pages without # identifying if it's from simple search or SQL search. For now, # show "Current Search" only if asset simple search is disabled. || ( $search_assets && $request_path =~ m{^/Asset/(?!Search/)} && RT->Config->Get('AssetHideSimpleSearch') ) ) { my $class = $HTML::Mason::Commands::DECODED_ARGS->{Class} || ( $request_path =~ m{^/(Transaction|Ticket|Asset)/} ? "RT::$1s" : 'RT::Tickets' ); my $search; if ( $class eq 'RT::Tickets' ) { $search = $top->child('search')->child('tickets'); } elsif ( $class eq 'RT::Assets' ) { $search = $search_assets; } else { $search = $txns_tickets; } my $hash_name = join '-', 'CurrentSearchHash', $class, $HTML::Mason::Commands::DECODED_ARGS->{ObjectType} || ( $class eq 'RT::Transactions' ? 'RT::Ticket' : () ); my $current_search = $HTML::Mason::Commands::session{$hash_name} || {}; my $search_id = $HTML::Mason::Commands::DECODED_ARGS->{'SavedSearchLoad'} || $HTML::Mason::Commands::DECODED_ARGS->{'SavedSearchId'} || $current_search->{'SearchId'} || ''; my $chart_id = $HTML::Mason::Commands::DECODED_ARGS->{'SavedChartSearchId'} || $current_search->{SavedChartSearchId}; $has_query = 1 if ( $HTML::Mason::Commands::DECODED_ARGS->{'Query'} or $current_search->{'Query'} ); my %query_args; my %fallback_query_args = ( SavedSearchId => ( $search_id eq 'new' ) ? undef : $search_id, SavedChartSearchId => $chart_id, ( map { my $p = $_; $p => $HTML::Mason::Commands::DECODED_ARGS->{$p} || $current_search->{$p} } qw(Query Format OrderBy Order Page Class ObjectType ResultPage ExtraQueryParams), ), RowsPerPage => ( defined $HTML::Mason::Commands::DECODED_ARGS->{'RowsPerPage'} ? $HTML::Mason::Commands::DECODED_ARGS->{'RowsPerPage'} : $current_search->{'RowsPerPage'} ), ); if ( my $extra_params = $fallback_query_args{ExtraQueryParams} ) { for my $param ( ref $extra_params eq 'ARRAY' ? @$extra_params : $extra_params ) { $fallback_query_args{$param} = $HTML::Mason::Commands::DECODED_ARGS->{$param} || $current_search->{$param}; } } $fallback_query_args{Class} ||= $class; $fallback_query_args{ObjectType} ||= 'RT::Ticket' if $class eq 'RT::Transactions'; if ($query_string) { $args = '?' . $query_string; } else { my %final_query_args = (); # key => callback to avoid unnecessary work if ( my $extra_params = $query_args->{ExtraQueryParams} ) { $final_query_args{ExtraQueryParams} = $extra_params; for my $param ( ref $extra_params eq 'ARRAY' ? @$extra_params : $extra_params ) { $final_query_args{$param} = $query_args->{$param}; } } for my $param (keys %fallback_query_args) { $final_query_args{$param} = defined($query_args->{$param}) ? $query_args->{$param} : $fallback_query_args{$param}; } for my $field (qw(Order OrderBy)) { if ( ref( $final_query_args{$field} ) eq 'ARRAY' ) { $final_query_args{$field} = join( "|", @{ $final_query_args{$field} } ); } elsif (not defined $final_query_args{$field}) { delete $final_query_args{$field}; } else { $final_query_args{$field} ||= ''; } } $args = '?' . QueryString(%final_query_args); } my $current_search_menu; if ( $class eq 'RT::Tickets' && $request_path =~ m{^/Ticket} || $class eq 'RT::Transactions' && $request_path =~ m{^/Transaction} || $class eq 'RT::Assets' && $request_path =~ m{^/Asset/(?!Search/)} ) { $current_search_menu = $search->child( current_search => title => loc('Current Search') ); $current_search_menu->path("/Search/Results.html$args") if $has_query; if ( $search_results_page_menu && $has_query ) { $search_results_page_menu->child( current_search => title => q{<span class="fas fa-list"></span>}, escape_title => 0, sort_order => -1, path => "/Search/Results.html$args", attributes => { 'data-toggle' => 'tooltip', 'data-original-title' => loc('Return to Search Results'), alt => loc('Return to Search Results'), }, ); } } else { $current_search_menu = $page; } $current_search_menu->child( edit_search => title => loc('Edit Search'), path => "/Search/Build.html" . ( ($has_query) ? $args : '' ) ); if ( $current_user->HasRight( Right => 'ShowSearchAdvanced', Object => RT->System ) ) { $current_search_menu->child( advanced => title => loc('Advanced'), path => "/Search/Edit.html$args" ); } if ($has_query) { my $result_page = $HTML::Mason::Commands::DECODED_ARGS->{ResultPage}; if ( my $web_path = RT->Config->Get('WebPath') ) { $result_page =~ s!^$web_path!!; } $result_page ||= '/Search/Results.html'; $current_search_menu->child( results => title => loc('Show Results'), path => "$result_page$args" ); } if ( $has_query ) { if ( $class eq 'RT::Tickets' ) { if ( $current_user->HasRight( Right => 'ShowSearchBulkUpdate', Object => RT->System ) ) { $current_search_menu->child( bulk => title => loc('Bulk Update'), path => "/Search/Bulk.html$args" ); } $current_search_menu->child( chart => title => loc('Chart'), path => "/Search/Chart.html$args" ); } elsif ( $class eq 'RT::Assets' ) { $current_search_menu->child( bulk => title => loc('Bulk Update'), path => "/Asset/Search/Bulk.html$args" ); } my $more = $current_search_menu->child( more => title => loc('Feeds') ); $more->child( spreadsheet => title => loc('Spreadsheet'), path => "/Search/Results.tsv$args" ); if ( $class eq 'RT::Tickets' ) { my %rss_data = map { $_ => $query_args->{$_} || $fallback_query_args{$_} || '' } qw(Query Order OrderBy); my $RSSQueryString = "?" . QueryString( Query => $rss_data{Query}, Order => $rss_data{Order}, OrderBy => $rss_data{OrderBy} ); my $RSSPath = join '/', map $HTML::Mason::Commands::m->interp->apply_escapes( $_, 'u' ), $current_user->UserObj->Name, $current_user->UserObj->GenerateAuthString( $rss_data{Query} . $rss_data{Order} . $rss_data{OrderBy} ); $more->child( rss => title => loc('RSS'), path => "/NoAuth/rss/$RSSPath/$RSSQueryString" ); my $ical_path = join '/', map $HTML::Mason::Commands::m->interp->apply_escapes( $_, 'u' ), $current_user->UserObj->Name, $current_user->UserObj->GenerateAuthString( $rss_data{Query} ), $rss_data{Query}; $more->child( ical => title => loc('iCal'), path => '/NoAuth/iCal/' . $ical_path ); #XXX TODO better abstraction of SuperUser right check if ( $current_user->HasRight( Right => 'SuperUser', Object => RT->System ) ) { my $shred_args = QueryString( Search => 1, Plugin => 'Tickets', 'Tickets:query' => $rss_data{'Query'}, 'Tickets:limit' => $query_args->{'Rows'}, ); $more->child( shredder => title => loc('Shredder'), path => '/Admin/Tools/Shredder/?' . $shred_args ); } } } } if ( $request_path =~ m{^/Article/} ) { if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ ) { my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'}; $page->child( display => title => loc('Display'), path => "/Articles/Article/Display.html?id=".$id ); $page->child( history => title => loc('History'), path => "/Articles/Article/History.html?id=".$id ); $page->child( modify => title => loc('Modify'), path => "/Articles/Article/Edit.html?id=".$id ); } } if ( $request_path =~ m{^/Articles/} ) { $page->child( search => title => loc("Search"), path => "/Articles/Article/Search.html" ); if ( $request_path =~ m{^/Articles/Article/} and ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} || '' ) =~ /^(\d+)$/ ) { my $id = $1; my $obj = RT::Article->new( $current_user ); $obj->Load($id); if ( $obj and $obj->id ) { $page->child( display => title => loc("Display"), path => "/Articles/Article/Display.html?id=" . $id ); $page->child( history => title => loc('History'), path => '/Articles/Article/History.html?id=' . $id ); if ( $obj->CurrentUserHasRight('ModifyArticle') ) { $page->child(modify => title => loc('Modify'), path => '/Articles/Article/Edit.html?id=' . $id ); } } } } if ($request_path =~ m{^/Asset/} and $HTML::Mason::Commands::DECODED_ARGS->{id} and $HTML::Mason::Commands::DECODED_ARGS->{id} !~ /\D/) { _BuildAssetMenu( $request_path, $top, $widgets, $page, %args ); } elsif ( $request_path =~ m{^/Asset/Search/(?:index\.html)?$} || ( $request_path =~ m{^/Asset/Search/Bulk\.html$} && $HTML::Mason::Commands::DECODED_ARGS->{Catalog} ) ) { my %search = map @{$_}, grep defined $_->[1] && length $_->[1], map {ref $HTML::Mason::Commands::DECODED_ARGS->{$_} ? [$_, $HTML::Mason::Commands::DECODED_ARGS->{$_}[0]] : [$_, $HTML::Mason::Commands::DECODED_ARGS->{$_}] } grep /^(?:q|SearchAssets|!?(Name|Description|Catalog|Status|Role\..+|CF\..+)|Order(?:By)?|Page)$/, keys %$HTML::Mason::Commands::DECODED_ARGS; if ( $request_path =~ /Bulk/) { $page->child('search', title => loc('Show Results'), path => '/Asset/Search/?' . (keys %search ? QueryString(%search) : ''), ); } else { $page->child('bulk', title => loc('Bulk Update'), path => '/Asset/Search/Bulk.html?' . (keys %search ? QueryString(%search) : ''), ); } $page->child('csv', title => loc('Download Spreadsheet'), path => '/Search/Results.tsv?' . QueryString(%search, Class => 'RT::Assets'), ); } elsif ($request_path =~ m{^/Asset/Search/}) { my %search = map @{$_}, grep defined $_->[1] && length $_->[1], map {ref $HTML::Mason::Commands::DECODED_ARGS->{$_} ? [$_, $HTML::Mason::Commands::DECODED_ARGS->{$_}[0]] : [$_, $HTML::Mason::Commands::DECODED_ARGS->{$_}] } grep /^(?:q|SearchAssets|!?(Name|Description|Catalog|Status|Role\..+|CF\..+)|Order(?:By)?|Page)$/, keys %$HTML::Mason::Commands::DECODED_ARGS; my $current_search = $HTML::Mason::Commands::session{"CurrentSearchHash-RT::Assets"} || {}; my $search_id = $HTML::Mason::Commands::DECODED_ARGS->{'SavedSearchLoad'} || $HTML::Mason::Commands::DECODED_ARGS->{'SavedSearchId'} || $current_search->{'SearchId'} || ''; my $args = ''; my $has_query; $has_query = 1 if ( $HTML::Mason::Commands::DECODED_ARGS->{'Query'} or $current_search->{'Query'} ); my %query_args; my %fallback_query_args = ( Class => 'RT::Assets', SavedSearchId => ( $search_id eq 'new' ) ? undef : $search_id, ( map { my $p = $_; $p => $HTML::Mason::Commands::DECODED_ARGS->{$p} || $current_search->{$p} } qw(Query Format OrderBy Order Page) ), RowsPerPage => ( defined $HTML::Mason::Commands::DECODED_ARGS->{'RowsPerPage'} ? $HTML::Mason::Commands::DECODED_ARGS->{'RowsPerPage'} : $current_search->{'RowsPerPage'} ), ); if ($query_string) { $args = '?' . $query_string; } else { my %final_query_args = (); # key => callback to avoid unnecessary work for my $param (keys %fallback_query_args) { $final_query_args{$param} = defined($query_args->{$param}) ? $query_args->{$param} : $fallback_query_args{$param}; } for my $field (qw(Order OrderBy)) { if ( ref( $final_query_args{$field} ) eq 'ARRAY' ) { $final_query_args{$field} = join( "|", @{ $final_query_args{$field} } ); } elsif (not defined $final_query_args{$field}) { delete $final_query_args{$field}; } else { $final_query_args{$field} ||= ''; } } $args = '?' . QueryString(%final_query_args); } $page->child('edit_search', title => loc('Edit Search'), path => '/Search/Build.html' . $args, ); $page->child( advanced => title => loc('Advanced'), path => '/Search/Edit.html' . $args ); if ($has_query) { $page->child( results => title => loc('Show Results'), path => '/Search/Results.html' . $args ); $page->child('bulk', title => loc('Bulk Update'), path => '/Asset/Search/Bulk.html' . $args, ); my $more = $page->child( more => title => loc('Feeds') ); $more->child( spreadsheet => title => loc('Spreadsheet'), path => "/Search/Results.tsv$args" ); } } elsif ($request_path =~ m{^/Admin/Global/CustomFields/Catalog-Assets\.html$}) { $page->child("create", title => loc("Create New"), path => "/Admin/CustomFields/Modify.html?Create=1;LookupType=" . RT::Asset->CustomFieldLookupType); } elsif ($request_path =~ m{^/Admin/CustomFields(/|/index\.html)?$} and $HTML::Mason::Commands::DECODED_ARGS->{'Type'} and $HTML::Mason::Commands::DECODED_ARGS->{'Type'} eq RT::Asset->CustomFieldLookupType) { $page->child("create")->path( $page->child("create")->path . ";LookupType=" . RT::Asset->CustomFieldLookupType ); } elsif ($request_path =~ m{^/Admin/Assets/Catalogs/}) { my $actions = $request_path =~ m{/((index|Create)\.html)?$} ? $page : $page->child("catalogs", title => loc("Catalogs"), path => "/Admin/Assets/Catalogs/"); $actions->child("select", title => loc("Select"), path => "/Admin/Assets/Catalogs/"); $actions->child("create", title => loc("Create"), path => "/Admin/Assets/Catalogs/Create.html"); my $catalog = RT::Catalog->new( $current_user ); $catalog->Load($HTML::Mason::Commands::DECODED_ARGS->{id}) if $HTML::Mason::Commands::DECODED_ARGS->{id}; if ($catalog->id and $catalog->CurrentUserCanSee) { my $query = "id=" . $catalog->id; $page->child("modify", title => loc("Basics"), path => "/Admin/Assets/Catalogs/Modify.html?$query"); $page->child("people", title => loc("Roles"), path => "/Admin/Assets/Catalogs/Roles.html?$query"); $page->child("cfs", title => loc("Asset Custom Fields"), path => "/Admin/Assets/Catalogs/CustomFields.html?$query"); $page->child("group-rights", title => loc("Group Rights"), path => "/Admin/Assets/Catalogs/GroupRights.html?$query"); $page->child("user-rights", title => loc("User Rights"), path => "/Admin/Assets/Catalogs/UserRights.html?$query"); $page->child("default-values", title => loc('Default Values'), path => "/Admin/Assets/Catalogs/DefaultValues.html?$query"); } } if ( $request_path =~ m{^/User/(Summary|History)\.html} ) { if ($page->child('summary')) { # Already set up from having AdminUser and ShowConfigTab; # but rename "Basics" to "Edit" in this context $page->child( 'basics' )->title( loc('Edit') ); } elsif ( $current_user->HasRight( Object => $RT::System, Right => 'ShowUserHistory' ) ) { $page->child( display => title => loc('Summary'), path => '/User/Summary.html?id=' . $HTML::Mason::Commands::DECODED_ARGS->{'id'} ); $page->child( history => title => loc('History'), path => '/User/History.html?id=' . $HTML::Mason::Commands::DECODED_ARGS->{'id'} ); } } if ( $request_path =~ /^\/(?:index.html|$)/ ) { my $alt = loc('Edit'); $page->child( edit => raw_html => q[<a id="page-edit" class="menu-item" href="] . RT->Config->Get('WebPath') . qq[/Prefs/MyRT.html"><span class="fas fa-cog" alt="$alt" data-toggle="tooltip" data-placement="top" data-original-title="$alt"></span></a>] ); } if ( $request_path =~ m{^/Admin/Tools/(Configuration|EditConfig|ConfigHistory)} ) { $page->child( display => title => loc('View'), path => "/Admin/Tools/Configuration.html" ); $page->child( modify => title => loc('Edit'), path => "/Admin/Tools/EditConfig.html" ) if RT->Config->Get('ShowEditSystemConfig'); $page->child( history => title => loc('History'), path => "/Admin/Tools/ConfigHistory.html" ); } # due to historical reasons of always having been in /Elements/Tabs $HTML::Mason::Commands::m->callback( CallbackName => 'Privileged', Path => $request_path, Search_Args => $args, Has_Query => $has_query, ARGSRef => \%args, CallbackPage => '/Elements/Tabs' ); } sub _BuildAssetMenu { my $request_path = shift; my $top = shift; my $widgets = shift; my $page = shift; my %args = ( @_ ); my $current_user = $HTML::Mason::Commands::session{CurrentUser}; my $id = $HTML::Mason::Commands::DECODED_ARGS->{id}; my $asset = RT::Asset->new( $current_user ); $asset->Load($id); if ($asset->id) { $page->child("display", title => HTML::Mason::Commands::loc("Display"), path => "/Asset/Display.html?id=$id"); $page->child("history", title => HTML::Mason::Commands::loc("History"), path => "/Asset/History.html?id=$id"); $page->child("basics", title => HTML::Mason::Commands::loc("Basics"), path => "/Asset/Modify.html?id=$id"); $page->child("links", title => HTML::Mason::Commands::loc("Links"), path => "/Asset/ModifyLinks.html?id=$id"); $page->child("people", title => HTML::Mason::Commands::loc("People"), path => "/Asset/ModifyPeople.html?id=$id"); $page->child("dates", title => HTML::Mason::Commands::loc("Dates"), path => "/Asset/ModifyDates.html?id=$id"); for my $grouping (RT::CustomField->CustomGroupings($asset)) { my $cfs = $asset->CustomFields; $cfs->LimitToGrouping( $asset => $grouping ); next unless $cfs->Count; $page->child( "cf-grouping-$grouping", title => HTML::Mason::Commands::loc($grouping), path => "/Asset/ModifyCFs.html?id=$id;Grouping=" . $HTML::Mason::Commands::m->interp->apply_escapes($grouping, 'u'), ); } _BuildAssetMenuActionSubmenu( $request_path, $top, $widgets, $page, %args, Asset => $asset ); } } sub _BuildAssetMenuActionSubmenu { my $request_path = shift; my $top = shift; my $widgets = shift; my $page = shift; my %args = ( Asset => undef, @_ ); my $asset = $args{Asset}; my $id = $asset->id; my $actions = $page->child("actions", title => HTML::Mason::Commands::loc("Actions")); $actions->child("create-linked-ticket", title => HTML::Mason::Commands::loc("Create linked ticket"), path => "/Asset/CreateLinkedTicket.html?Asset=$id"); my $status = $asset->Status; my $lifecycle = $asset->LifecycleObj; for my $action ( $lifecycle->Actions($status) ) { my $next = $action->{'to'}; next unless $lifecycle->IsTransition( $status => $next ); my $check = $lifecycle->CheckRight( $status => $next ); next unless $asset->CurrentUserHasRight($check); my $label = $action->{'label'} || ucfirst($next); $actions->child( $label, title => HTML::Mason::Commands::loc($label), path => "/Asset/Modify.html?id=$id;Update=1;DisplayAfter=1;Status=" . $HTML::Mason::Commands::m->interp->apply_escapes($next, 'u'), class => "asset-lifecycle-action", attributes => { 'data-current-status' => $status, 'data-next-status' => $next, }, ); } } sub _BuildAdminMenu { my $request_path = shift; my $top = shift; my $widgets = shift; my $page = shift; my %args = ( @_ ); my $current_user = $HTML::Mason::Commands::session{CurrentUser}; my $admin = $top->child( admin => title => loc('Admin'), path => '/Admin/' ); if ( $current_user->HasRight( Object => RT->System, Right => 'AdminUsers' ) ) { my $users = $admin->child( users => title => loc('Users'), description => loc('Manage users and passwords'), path => '/Admin/Users/', ); $users->child( select => title => loc('Select'), path => "/Admin/Users/" ); $users->child( create => title => loc('Create'), path => "/Admin/Users/Modify.html?Create=1" ); } my $groups = $admin->child( groups => title => loc('Groups'), description => loc('Manage groups and group membership'), path => '/Admin/Groups/', ); $groups->child( select => title => loc('Select'), path => "/Admin/Groups/" ); $groups->child( create => title => loc('Create'), path => "/Admin/Groups/Modify.html?Create=1" ); my $queues = $admin->child( queues => title => loc('Queues'), description => loc('Manage queues and queue-specific properties'), path => '/Admin/Queues/', ); $queues->child( select => title => loc('Select'), path => "/Admin/Queues/" ); $queues->child( create => title => loc('Create'), path => "/Admin/Queues/Modify.html?Create=1" ); if ( $current_user->HasRight( Object => RT->System, Right => 'AdminCustomField' ) ) { my $cfs = $admin->child( 'custom-fields' => title => loc('Custom Fields'), description => loc('Manage custom fields and custom field values'), path => '/Admin/CustomFields/', ); $cfs->child( select => title => loc('Select'), path => "/Admin/CustomFields/" ); $cfs->child( create => title => loc('Create'), path => "/Admin/CustomFields/Modify.html?Create=1" ); } if ( $current_user->HasRight( Object => RT->System, Right => 'AdminCustomRoles' ) ) { my $roles = $admin->child( 'custom-roles' => title => loc('Custom Roles'), description => loc('Manage custom roles'), path => '/Admin/CustomRoles/', ); $roles->child( select => title => loc('Select'), path => "/Admin/CustomRoles/" ); $roles->child( create => title => loc('Create'), path => "/Admin/CustomRoles/Modify.html?Create=1" ); } if ( $current_user->HasRight( Object => RT->System, Right => 'ModifyScrips' ) ) { my $scrips = $admin->child( 'scrips' => title => loc('Scrips'), description => loc('Manage scrips'), path => '/Admin/Scrips/', ); $scrips->child( select => title => loc('Select'), path => "/Admin/Scrips/" ); $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html" ); } if ( RT->Config->Get('ShowEditLifecycleConfig') && $current_user->HasRight( Object => RT->System, Right => 'SuperUser' ) ) { my $lifecycles = $admin->child( lifecycles => title => loc('Lifecycles'), path => '/Admin/Lifecycles/', ); $lifecycles->child( select => title => loc('Select'), path => '/Admin/Lifecycles/' ); $lifecycles->child( create => title => loc('Create'), path => '/Admin/Lifecycles/Create.html' ); } my $admin_global = $admin->child( global => title => loc('Global'), description => loc('Manage properties and configuration which apply to all queues'), path => '/Admin/Global/', ); my $scrips = $admin_global->child( scrips => title => loc('Scrips'), description => loc('Modify scrips which apply to all queues'), path => '/Admin/Global/Scrips.html', ); $scrips->child( select => title => loc('Select'), path => "/Admin/Global/Scrips.html" ); $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html?Global=1" ); my $conditions = $admin_global->child( conditions => title => loc('Conditions'), description => loc('Edit system conditions'), path => '/Admin/Global/Conditions.html', ); $conditions->child( select => title => loc('Select'), path => "/Admin/Global/Conditions.html" ); $conditions->child( create => title => loc('Create'), path => "/Admin/Conditions/Create.html" ); my $actions = $admin_global->child( actions => title => loc('Actions'), description => loc('Edit system actions'), path => '/Admin/Global/Actions.html', ); $actions->child( select => title => loc('Select'), path => "/Admin/Global/Actions.html" ); $actions->child( create => title => loc('Create'), path => "/Admin/Actions/Create.html" ); my $templates = $admin_global->child( templates => title => loc('Templates'), description => loc('Edit system templates'), path => '/Admin/Global/Templates.html', ); $templates->child( select => title => loc('Select'), path => "/Admin/Global/Templates.html" ); $templates->child( create => title => loc('Create'), path => "/Admin/Global/Template.html?Create=1" ); my $cfadmin = $admin_global->child( 'custom-fields' => title => loc('Custom Fields'), description => loc('Modify global custom fields'), path => '/Admin/Global/CustomFields/index.html', ); $cfadmin->child( users => title => loc('Users'), description => loc('Select custom fields for all users'), path => '/Admin/Global/CustomFields/Users.html', ); $cfadmin->child( groups => title => loc('Groups'), description => loc('Select custom fields for all user groups'), path => '/Admin/Global/CustomFields/Groups.html', ); $cfadmin->child( queues => title => loc('Queues'), description => loc('Select custom fields for all queues'), path => '/Admin/Global/CustomFields/Queues.html', ); $cfadmin->child( tickets => title => loc('Tickets'), description => loc('Select custom fields for tickets in all queues'), path => '/Admin/Global/CustomFields/Queue-Tickets.html', ); $cfadmin->child( transactions => title => loc('Ticket Transactions'), description => loc('Select custom fields for transactions on tickets in all queues'), path => '/Admin/Global/CustomFields/Queue-Transactions.html', ); $cfadmin->child( 'custom-fields' => title => loc('Articles'), description => loc('Select Custom Fields for Articles in all Classes'), path => '/Admin/Global/CustomFields/Class-Article.html', ); $cfadmin->child( 'assets' => title => loc('Assets'), description => loc('Select Custom Fields for Assets in all Catalogs'), path => '/Admin/Global/CustomFields/Catalog-Assets.html', ); my $article_admin = $admin->child( articles => title => loc('Articles'), path => "/Admin/Articles/index.html" ); my $class_admin = $article_admin->child(classes => title => loc('Classes'), path => '/Admin/Articles/Classes/' ); $class_admin->child( select => title => loc('Select'), description => loc('Modify and Create Classes'), path => '/Admin/Articles/Classes/', ); $class_admin->child( create => title => loc('Create'), description => loc('Modify and Create Custom Fields for Articles'), path => '/Admin/Articles/Classes/Modify.html?Create=1', ); my $cfs = $article_admin->child( 'custom-fields' => title => loc('Custom Fields'), path => '/Admin/CustomFields/index.html?'.$HTML::Mason::Commands::m->comp('/Elements/QueryString', Type => 'RT::Class-RT::Article'), ); $cfs->child( select => title => loc('Select'), path => '/Admin/CustomFields/index.html?'.$HTML::Mason::Commands::m->comp('/Elements/QueryString', Type => 'RT::Class-RT::Article'), ); $cfs->child( create => title => loc('Create'), path => '/Admin/CustomFields/Modify.html?'.$HTML::Mason::Commands::m->comp("/Elements/QueryString", Create=>1, LookupType=> "RT::Class-RT::Article" ), ); my $assets_admin = $admin->child( assets => title => loc("Assets"), path => '/Admin/Assets/' ); my $catalog_admin = $assets_admin->child( catalogs => title => loc("Catalogs"), description => loc("Modify asset catalogs"), path => "/Admin/Assets/Catalogs/" ); $catalog_admin->child( "select", title => loc("Select"), path => $catalog_admin->path ); $catalog_admin->child( "create", title => loc("Create"), path => "Create.html" ); my $assets_cfs = $assets_admin->child( "cfs", title => loc("Custom Fields"), description => loc("Modify asset custom fields"), path => "/Admin/CustomFields/?Type=" . RT::Asset->CustomFieldLookupType ); $assets_cfs->child( "select", title => loc("Select"), path => $assets_cfs->path ); $assets_cfs->child( "create", title => loc("Create"), path => "/Admin/CustomFields/Modify.html?Create=1;LookupType=" . RT::Asset->CustomFieldLookupType); $admin_global->child( 'group-rights' => title => loc('Group Rights'), description => loc('Modify global group rights'), path => '/Admin/Global/GroupRights.html', ); $admin_global->child( 'user-rights' => title => loc('User Rights'), description => loc('Modify global user rights'), path => '/Admin/Global/UserRights.html', ); $admin_global->child( 'my-rt' => title => loc('RT at a glance'), description => loc('Modify the default "RT at a glance" view'), path => '/Admin/Global/MyRT.html', ); if (RT->Config->Get('SelfServiceUseDashboard')) { if ($current_user->HasRight( Right => 'ModifyDashboard', Object => RT->System ) ) { my $self_service = $admin_global->child( selfservice_home => title => loc('Self Service Home Page'), description => loc('Edit self service home page dashboard'), path => '/Admin/Global/SelfServiceHomePage.html'); if ( $request_path =~ m{^/Admin/Global/SelfServiceHomePage} ) { $page->child(content => title => loc('Content'), path => '/Admin/Global/SelfServiceHomePage.html'); $page->child(show => title => loc('Show'), path => '/SelfService'); } } } $admin_global->child( 'dashboards-in-menu' => title => loc('Modify Reports menu'), description => loc('Customize dashboards in menu'), path => '/Admin/Global/DashboardsInMenu.html', ); $admin_global->child( 'topics' => title => loc('Topics'), description => loc('Modify global article topics'), path => '/Admin/Global/Topics.html', ); my $admin_tools = $admin->child( tools => title => loc('Tools'), description => loc('Use other RT administrative tools'), path => '/Admin/Tools/', ); $admin_tools->child( configuration => title => loc('System Configuration'), description => loc('Detailed information about your RT setup'), path => '/Admin/Tools/Configuration.html', ); $admin_tools->child( theme => title => loc('Theme'), description => loc('Customize the look of your RT'), path => '/Admin/Tools/Theme.html', ); if (RT->Config->Get('StatementLog') && $current_user->HasRight( Right => 'SuperUser', Object => RT->System )) { $admin_tools->child( 'sql-queries' => title => loc('SQL Queries'), description => loc('Browse the SQL queries made in this process'), path => '/Admin/Tools/Queries.html', ); } $admin_tools->child( rights_inspector => title => loc('Rights Inspector'), description => loc('Search your configured rights'), path => '/Admin/Tools/RightsInspector.html', ); $admin_tools->child( shredder => title => loc('Shredder'), description => loc('Permanently wipeout data from RT'), path => '/Admin/Tools/Shredder', ); if ( $request_path =~ m{^/Admin/(Queues|Users|Groups|CustomFields|CustomRoles)} ) { my $type = $1; my %labels = ( Queues => loc("Queues"), Users => loc("Users"), Groups => loc("Groups"), CustomFields => loc("Custom Fields"), CustomRoles => loc("Custom Roles"), ); my $section; if ( $request_path =~ m|^/Admin/$type/?(?:index.html)?$| || ( $request_path =~ m|^/Admin/$type/(?:Modify.html)$| && $HTML::Mason::Commands::DECODED_ARGS->{'Create'} ) ) { $section = $page; } else { $section = $page->child( select => title => $labels{$type}, path => "/Admin/$type/" ); } $section->child( select => title => loc('Select'), path => "/Admin/$type/" ); $section->child( create => title => loc('Create'), path => "/Admin/$type/Modify.html?Create=1" ); } if ( $request_path =~ m{^/Admin/Queues} ) { if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ || $HTML::Mason::Commands::DECODED_ARGS->{'Queue'} && $HTML::Mason::Commands::DECODED_ARGS->{'Queue'} =~ /^\d+$/ ) { my $id = $HTML::Mason::Commands::DECODED_ARGS->{'Queue'} || $HTML::Mason::Commands::DECODED_ARGS->{'id'}; my $queue_obj = RT::Queue->new( $current_user ); $queue_obj->Load($id); if ( $queue_obj and $queue_obj->id ) { my $queue = $page; $queue->child( basics => title => loc('Basics'), path => "/Admin/Queues/Modify.html?id=" . $id ); $queue->child( people => title => loc('Watchers'), path => "/Admin/Queues/People.html?id=" . $id ); my $templates = $queue->child(templates => title => loc('Templates'), path => "/Admin/Queues/Templates.html?id=" . $id); $templates->child( select => title => loc('Select'), path => "/Admin/Queues/Templates.html?id=".$id); $templates->child( create => title => loc('Create'), path => "/Admin/Queues/Template.html?Create=1;Queue=".$id); my $scrips = $queue->child( scrips => title => loc('Scrips'), path => "/Admin/Queues/Scrips.html?id=" . $id); $scrips->child( select => title => loc('Select'), path => "/Admin/Queues/Scrips.html?id=" . $id ); $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html?Queue=" . $id); my $cfs = $queue->child( 'custom-fields' => title => loc('Custom Fields') ); my $ticket_cfs = $cfs->child( 'tickets' => title => loc('Tickets'), path => '/Admin/Queues/CustomFields.html?SubType=RT::Ticket;id=' . $id ); my $txn_cfs = $cfs->child( 'transactions' => title => loc('Transactions'), path => '/Admin/Queues/CustomFields.html?SubType=RT::Ticket-RT::Transaction;id='.$id ); $queue->child( 'group-rights' => title => loc('Group Rights'), path => "/Admin/Queues/GroupRights.html?id=".$id ); $queue->child( 'user-rights' => title => loc('User Rights'), path => "/Admin/Queues/UserRights.html?id=" . $id ); $queue->child( 'history' => title => loc('History'), path => "/Admin/Queues/History.html?id=" . $id ); $queue->child( 'default-values' => title => loc('Default Values'), path => "/Admin/Queues/DefaultValues.html?id=" . $id ); # due to historical reasons of always having been in /Elements/Tabs $HTML::Mason::Commands::m->callback( CallbackName => 'PrivilegedQueue', queue_id => $id, page_menu => $queue, CallbackPage => '/Elements/Tabs' ); } } } if ( $request_path =~ m{^(/Admin/Users|/User/(Summary|History)\.html)} and $admin->child("users") ) { if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ ) { my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'}; my $obj = RT::User->new( $current_user ); $obj->Load($id); if ( $obj and $obj->id ) { $page->child( basics => title => loc('Basics'), path => "/Admin/Users/Modify.html?id=" . $id ); $page->child( memberships => title => loc('Memberships'), path => "/Admin/Users/Memberships.html?id=" . $id ); $page->child( history => title => loc('History'), path => "/Admin/Users/History.html?id=" . $id ); $page->child( 'my-rt' => title => loc('RT at a glance'), path => "/Admin/Users/MyRT.html?id=" . $id ); $page->child( 'dashboards-in-menu' => title => loc('Modify Reports menu'), path => '/Admin/Users/DashboardsInMenu.html?id=' . $id, ); if ( RT->Config->Get('Crypt')->{'Enable'} ) { $page->child( keys => title => loc('Private keys'), path => "/Admin/Users/Keys.html?id=" . $id ); } $page->child( 'summary' => title => loc('User Summary'), path => "/User/Summary.html?id=" . $id ); if ( $current_user->HasRight( Right => 'ManageAuthTokens', Object => RT->System ) ) { my $auth_tokens = $page->child( auth_tokens => title => loc('Auth Tokens'), path => '/Admin/Users/AuthTokens.html?id=' . $id ); if ( $request_path =~ m{^/Admin/Users/AuthTokens\.html} ) { $auth_tokens->child( select_auth_token => title => loc('Select'), path => '/Admin/Users/AuthTokens.html?id=' . $id, ); $auth_tokens->child( create_auth_token => title => loc('Create'), raw_html => q[<a class="btn menu-item" href="#create-auth-token" data-toggle="modal" rel="modal:open">] . loc("Create") . "</a>" ); } } } } } if ( $request_path =~ m{^(/Admin/Groups|/Group/(Summary|History)\.html)} ) { if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ ) { my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'}; my $obj = RT::Group->new( $current_user ); $obj->Load($id); if ( $obj and $obj->id ) { $page->child( basics => title => loc('Basics'), path => "/Admin/Groups/Modify.html?id=" . $obj->id ); $page->child( members => title => loc('Members'), path => "/Admin/Groups/Members.html?id=" . $obj->id ); $page->child( memberships => title => loc('Memberships'), path => "/Admin/Groups/Memberships.html?id=" . $obj->id ); $page->child( 'links' => title => loc("Links"), path => "/Admin/Groups/ModifyLinks.html?id=" . $obj->id, description => loc("Group links"), ); $page->child( 'group-rights' => title => loc('Group Rights'), path => "/Admin/Groups/GroupRights.html?id=" . $obj->id ); $page->child( 'user-rights' => title => loc('User Rights'), path => "/Admin/Groups/UserRights.html?id=" . $obj->id ); $page->child( history => title => loc('History'), path => "/Admin/Groups/History.html?id=" . $obj->id ); $page->child( 'summary' => title => loc("Group Summary"), path => "/Group/Summary.html?id=" . $obj->id, description => loc("Group summary page"), ); } } } if ( $request_path =~ m{^/Admin/CustomFields/} ) { if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ ) { my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'}; my $obj = RT::CustomField->new( $current_user ); $obj->Load($id); if ( $obj and $obj->id ) { $page->child( basics => title => loc('Basics'), path => "/Admin/CustomFields/Modify.html?id=".$id ); $page->child( 'group-rights' => title => loc('Group Rights'), path => "/Admin/CustomFields/GroupRights.html?id=" . $id ); $page->child( 'user-rights' => title => loc('User Rights'), path => "/Admin/CustomFields/UserRights.html?id=" . $id ); unless ( $obj->IsOnlyGlobal ) { $page->child( 'applies-to' => title => loc('Applies to'), path => "/Admin/CustomFields/Objects.html?id=" . $id ); } } } } if ( $request_path =~ m{^/Admin/CustomRoles} ) { if ( $HTML::Mason::Commands::DECODED_ARGS->{'id'} && $HTML::Mason::Commands::DECODED_ARGS->{'id'} =~ /^\d+$/ ) { my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'}; my $obj = RT::CustomRole->new( $current_user ); $obj->Load($id); if ( $obj and $obj->id ) { $page->child( basics => title => loc('Basics'), path => "/Admin/CustomRoles/Modify.html?id=".$id ); $page->child( 'applies-to' => title => loc('Applies to'), path => "/Admin/CustomRoles/Objects.html?id=" . $id ); $page->child( 'visibility' => title => loc('Visibility'), path => "/Admin/CustomRoles/Visibility.html?id=" . $id ); } } } if ( $request_path =~ m{^/Admin/Scrips/} ) { if ( $HTML::Mason::Commands::m->request_args->{'id'} && $HTML::Mason::Commands::m->request_args->{'id'} =~ /^\d+$/ ) { my $id = $HTML::Mason::Commands::m->request_args->{'id'}; my $obj = RT::Scrip->new( $current_user ); $obj->Load($id); my ( $admin_cat, $create_path_arg, $from_query_param ); my $from_arg = $HTML::Mason::Commands::DECODED_ARGS->{'From'} || q{}; my ($from_queue) = $from_arg =~ /^(\d+)$/; if ( $from_queue ) { $admin_cat = "Queues/Scrips.html?id=$from_queue"; $create_path_arg = "?Queue=$from_queue"; $from_query_param = ";From=$from_queue"; } elsif ( $from_arg eq 'Global' ) { $admin_cat = 'Global/Scrips.html'; $create_path_arg = '?Global=1'; $from_query_param = ';From=Global'; } else { $admin_cat = 'Scrips'; $from_query_param = $create_path_arg = q{}; } my $scrips = $page->child( scrips => title => loc('Scrips'), path => "/Admin/${admin_cat}" ); $scrips->child( select => title => loc('Select'), path => "/Admin/${admin_cat}" ); $scrips->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html${create_path_arg}" ); $page->child( basics => title => loc('Basics') => path => "/Admin/Scrips/Modify.html?id=" . $id . $from_query_param ); $page->child( 'applies-to' => title => loc('Applies to'), path => "/Admin/Scrips/Objects.html?id=" . $id . $from_query_param ); } elsif ( $request_path =~ m{^/Admin/Scrips/(index\.html)?$} ) { HTML::Mason::Commands::PageMenu->child( select => title => loc('Select') => path => "/Admin/Scrips/" ); HTML::Mason::Commands::PageMenu->child( create => title => loc('Create') => path => "/Admin/Scrips/Create.html" ); } elsif ( $request_path =~ m{^/Admin/Scrips/Create\.html$} ) { my ($queue) = $HTML::Mason::Commands::DECODED_ARGS->{'Queue'} && $HTML::Mason::Commands::DECODED_ARGS->{'Queue'} =~ /^(\d+)$/; my $global_arg = $HTML::Mason::Commands::DECODED_ARGS->{'Global'}; if ($queue) { HTML::Mason::Commands::PageMenu->child( select => title => loc('Select') => path => "/Admin/Queues/Scrips.html?id=$queue" ); HTML::Mason::Commands::PageMenu->child( create => title => loc('Create') => path => "/Admin/Scrips/Create.html?Queue=$queue" ); } elsif ($global_arg) { HTML::Mason::Commands::PageMenu->child( select => title => loc('Select') => path => "/Admin/Global/Scrips.html" ); HTML::Mason::Commands::PageMenu->child( create => title => loc('Create') => path => "/Admin/Scrips/Create.html?Global=1" ); } else { HTML::Mason::Commands::PageMenu->child( select => title => loc('Select') => path => "/Admin/Scrips" ); HTML::Mason::Commands::PageMenu->child( create => title => loc('Create') => path => "/Admin/Scrips/Create.html" ); } } } if ( $request_path =~ m{^/Admin/Lifecycles} && $current_user->HasRight( Object => RT->System, Right => 'SuperUser' ) ) { if (defined($HTML::Mason::Commands::DECODED_ARGS->{'Name'}) && defined($HTML::Mason::Commands::DECODED_ARGS->{'Type'}) ) { my $lifecycles = $page->child( 'lifecycles' => title => loc('Lifecycles'), description => loc('Manage lifecycles'), path => '/Admin/Lifecycles/', ); $lifecycles->child( select => title => loc('Select'), path => "/Admin/Lifecycles/" ); $lifecycles->child( create => title => loc('Create'), path => "/Admin/Lifecycles/Create.html" ); my $LifecycleObj = RT::Lifecycle->new(); $LifecycleObj->Load(Name => $HTML::Mason::Commands::DECODED_ARGS->{'Name'}, Type => $HTML::Mason::Commands::DECODED_ARGS->{'Type'}); if ($LifecycleObj->Name && $LifecycleObj->{data}{type} eq $HTML::Mason::Commands::DECODED_ARGS->{'Type'}) { my $Name_uri = $LifecycleObj->Name; my $Type_uri = $LifecycleObj->Type; RT::Interface::Web::EscapeURI(\$Name_uri); RT::Interface::Web::EscapeURI(\$Type_uri); unless ( RT::Interface::Web->ClientIsIE ) { $page->child( basics => title => loc('Modify'), path => "/Admin/Lifecycles/Modify.html?Type=" . $Type_uri . ";Name=" . $Name_uri ); } $page->child( actions => title => loc('Actions'), path => "/Admin/Lifecycles/Actions.html?Type=" . $Type_uri . ";Name=" . $Name_uri ); $page->child( rights => title => loc('Rights'), path => "/Admin/Lifecycles/Rights.html?Type=" . $Type_uri . ";Name=" . $Name_uri ); $page->child( mappings => title => loc('Mappings'), path => "/Admin/Lifecycles/Mappings.html?Type=" . $Type_uri . ";Name=" . $Name_uri ); $page->child( advanced => title => loc('Advanced'), path => "/Admin/Lifecycles/Advanced.html?Type=" . $Type_uri . ";Name=" . $Name_uri ); } } else { $page->child( select => title => loc('Select'), path => "/Admin/Lifecycles/" ); $page->child( create => title => loc('Create'), path => "/Admin/Lifecycles/Create.html" ); } } if ( $request_path =~ m{^/Admin/Global/Scrips\.html} ) { $page->child( select => title => loc('Select'), path => "/Admin/Global/Scrips.html" ); $page->child( create => title => loc('Create'), path => "/Admin/Scrips/Create.html?Global=1" ); } if ( $request_path =~ m{^/Admin(?:/Global)?/Conditions} ) { $page->child( select => title => loc('Select'), path => "/Admin/Global/Conditions.html" ); $page->child( create => title => loc('Create'), path => "/Admin/Conditions/Create.html" ); } if ( $request_path =~ m{^/Admin(?:/Global)?/Actions} ) { $page->child( select => title => loc('Select'), path => "/Admin/Global/Actions.html" ); $page->child( create => title => loc('Create'), path => "/Admin/Actions/Create.html" ); } if ( $request_path =~ m{^/Admin/Global/Templates?\.html} ) { $page->child( select => title => loc('Select'), path => "/Admin/Global/Templates.html" ); $page->child( create => title => loc('Create'), path => "/Admin/Global/Template.html?Create=1" ); } if ( $request_path =~ m{^/Admin/Articles/Classes/} ) { if ( my $id = $HTML::Mason::Commands::DECODED_ARGS->{'id'} ) { my $obj = RT::Class->new( $current_user ); $obj->Load($id); if ( $obj and $obj->id ) { my $section = $page->child( select => title => loc("Classes"), path => "/Admin/Articles/Classes/" ); $section->child( select => title => loc('Select'), path => "/Admin/Articles/Classes/" ); $section->child( create => title => loc('Create'), path => "/Admin/Articles/Classes/Modify.html?Create=1" ); $page->child( basics => title => loc('Basics'), path => "/Admin/Articles/Classes/Modify.html?id=".$id ); $page->child( topics => title => loc('Topics'), path => "/Admin/Articles/Classes/Topics.html?id=".$id ); $page->child( 'custom-fields' => title => loc('Custom Fields'), path => "/Admin/Articles/Classes/CustomFields.html?id=".$id ); $page->child( 'group-rights' => title => loc('Group Rights'), path => "/Admin/Articles/Classes/GroupRights.html?id=".$id ); $page->child( 'user-rights' => title => loc('User Rights'), path => "/Admin/Articles/Classes/UserRights.html?id=".$id ); $page->child( 'applies-to' => title => loc('Applies to'), path => "/Admin/Articles/Classes/Objects.html?id=$id" ); } } else { $page->child( select => title => loc('Select'), path => "/Admin/Articles/Classes/" ); $page->child( create => title => loc('Create'), path => "/Admin/Articles/Classes/Modify.html?Create=1" ); } } } sub BuildSelfServiceNav { my $request_path = shift; my $top = shift; my $widgets = shift; my $page = shift; my %args = ( @_ ); my $current_user = $HTML::Mason::Commands::session{CurrentUser}; if ( RT->Config->Get('SelfServiceUseDashboard') && $request_path =~ m{^/SelfService/(?:index\.html)?$} && $current_user->HasRight( Right => 'ShowConfigTab', Object => RT->System ) && $current_user->HasRight( Right => 'ModifyDashboard', Object => RT->System ) ) { $page->child( content => title => loc('Content'), path => '/Admin/Global/SelfServiceHomePage.html' ); $page->child( show => title => loc('Show'), path => '/SelfService/' ); } my $queues = RT::Queues->new( $current_user ); $queues->UnLimit; my $queue_count = 0; my $queue_id; while ( my $queue = $queues->Next ) { next unless $queue->CurrentUserHasRight('CreateTicket'); $queue_id = $queue->id; $queue_count++; last if ( $queue_count > 1 ); } my $home = $top->child( home => title => loc('Homepage'), path => '/' ); if ( $queue_count > 1 ) { $home->child( new => title => loc('Create Ticket'), path => '/SelfService/CreateTicketInQueue.html' ); } elsif ( $queue_id ) { $home->child( new => title => loc('Create Ticket'), path => '/SelfService/Create.html?Queue=' . $queue_id ); } my $menu_label = loc('Tickets'); my $menu_path = '/SelfService/'; if ( RT->Config->Get('SelfServiceUseDashboard') ) { $menu_path = '/SelfService/Open.html'; } my $tickets = $top->child( tickets => title => $menu_label, path => $menu_path ); $tickets->child( open => title => loc('Open tickets'), path => '/SelfService/Open.html' ); $tickets->child( closed => title => loc('Closed tickets'), path => '/SelfService/Closed.html' ); $top->child( "assets", title => loc("Assets"), path => "/SelfService/Asset/" ) if $current_user->HasRight( Right => 'ShowAssetsMenu', Object => RT->System ); my $username = '<span class="current-user">' . $HTML::Mason::Commands::m->interp->apply_escapes($current_user->Name, 'h') . '</span>'; my $about_me = $top->child( preferences => title => loc('Logged in as [_1]', $username), escape_title => 0, sort_order => 99, ); if ( ( RT->Config->Get('SelfServiceUserPrefs') || '' ) eq 'view-info' || $current_user->HasRight( Right => 'ModifySelf', Object => RT->System ) ) { $about_me->child( prefs => title => loc('Preferences'), path => '/SelfService/Prefs.html' ); } my $logout_url = RT->Config->Get('LogoutURL'); if ( $current_user->Name && ( !RT->Config->Get('WebRemoteUserAuth') || RT->Config->Get('WebFallbackToRTLogin') )) { $about_me->child( logout => title => loc('Logout'), path => $logout_url ); } if ( RT->Config->Get('SelfServiceShowArticleSearch') ) { $widgets->child( 'goto-article' => raw_html => $HTML::Mason::Commands::m->scomp('/SelfService/Elements/SearchArticle') ); } $widgets->child( goto => raw_html => $HTML::Mason::Commands::m->scomp('/SelfService/Elements/GotoTicket') ); if ($request_path =~ m{^/SelfService/Asset/} and $HTML::Mason::Commands::DECODED_ARGS->{id}) { my $id = $HTML::Mason::Commands::DECODED_ARGS->{id}; $page->child("display", title => loc("Display"), path => "/SelfService/Asset/Display.html?id=$id"); $page->child("history", title => loc("History"), path => "/SelfService/Asset/History.html?id=$id"); if (Menu->child("new")) { my $actions = $page->child("actions", title => loc("Actions")); $actions->child("create-linked-ticket", title => loc("Create linked ticket"), path => "/SelfService/Asset/CreateLinkedTicket.html?Asset=$id"); } } # due to historical reasons of always having been in /Elements/Tabs $HTML::Mason::Commands::m->callback( CallbackName => 'SelfService', Path => $request_path, ARGSRef => \%args, CallbackPage => '/Elements/Tabs' ); } 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rt-5.0.1/lib/RT/Interface/Web/Menu.pm���������������������������������������������������������������000644 �000765 �000024 �00000027546 14005011336 017771� 0����������������������������������������������������������������������������������������������������ustar�00sunnavy�������������������������staff���������������������������000000 �000000 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 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 }}} package RT::Interface::Web::Menu; use strict; use warnings; use base qw/Class::Accessor::Fast/; use URI; use Scalar::Util qw(weaken); __PACKAGE__->mk_accessors(qw( key title description raw_html escape_title sort_order target class attributes )); =head1 NAME RT::Interface::Web::Menu - Handle the API for menu navigation =head1 METHODS =head2 new PARAMHASH Creates a new L<RT::Interface::Web::Menu> object. Possible keys in the I<PARAMHASH> are L</parent>, L, L, L, L, L, L, L, L, L, and L. See the subroutines with the respective name below for each option's use. =cut sub new { my $package = shift; my $args = ref($_[0]) eq 'HASH' ? shift @_ : {@_}; my $parent = delete $args->{'parent'}; $args->{sort_order} ||= 0; # Class::Accessor only wants a hashref; my $self = $package->SUPER::new( $args ); # make sure our reference is weak $self->parent($parent) if defined $parent; return $self; } =head2 title [STRING] Sets or returns the string that the menu item will be displayed as. =head2 escape_title [BOOLEAN] Sets or returns whether or not to HTML escape the title before output. =head2 parent [MENU] Gets or sets the parent L of this item; this defaults to null. This ensures that the reference is weakened. =head2 raw_html [STRING] Sets the content of this menu item to a raw blob of HTML. When building the menu, rather than constructing a link, we will return this raw content. No escaping is done. =cut sub parent { my $self = shift; if (@_) { $self->{parent} = shift; weaken $self->{parent}; } return $self->{parent}; } =head2 sort_order [NUMBER] Gets or sets the sort order of the item, as it will be displayed under the parent. This defaults to adding onto the end. =head2 target [STRING] Get or set the frame or pseudo-target for this link. something like L<_blank> =head2 class [STRING] Gets or sets the CSS class the menu item should have in addition to the default classes. This is only used if L isn't specified. =head2 attributes [HASHREF] Gets or sets a hashref of HTML attribute name-value pairs that the menu item should have in addition to the attributes which have their own accessor, like L and L. This is only used if L isn't specified. =head2 path Gets or sets the URL that the menu's link goes to. If the link provided is not absolute (does not start with a "/"), then is is treated as relative to it's parent's path, and made absolute. =cut sub path { my $self = shift; if (@_) { if (defined($self->{path} = shift)) { my $base = ($self->parent and $self->parent->path) ? $self->parent->path : ""; $base .= "/" unless $base =~ m{/$}; my $uri = URI->new_abs($self->{path}, $base); $self->{path} = $uri->as_string; } } return $self->{path}; } =head2 active [BOOLEAN] Gets or sets if the menu item is marked as active. Setting this cascades to all of the parents of the menu item. This is currently B. =cut sub active { my $self = shift; if (@_) { $self->{active} = shift; $self->parent->active($self->{active}) if defined $self->parent; } return $self->{active}; } =head2 child KEY [, PARAMHASH] If only a I is provided, returns the child with that I. Otherwise, creates or overwrites the child with that key, passing the I to L. Additionally, the paramhash's L defaults to the I, and the L defaults to the pre-existing child's sort order (if a C is being over-written) or the end of the list, if it is a new C. If the paramhash contains a key called C, that will be used instead of creating a new RT::Interface::Web::Menu. =cut sub child { my $self = shift; my $key = shift; my $proto = ref $self || $self; if ( my %args = @_ ) { # Clear children ordering cache delete $self->{children_list}; my $child; if ( $child = $args{menu} ) { $child->parent($self); } else { $child = $proto->new( { parent => $self, key => $key, title => $key, escape_title=> 1, %args } ); } $self->{children}{$key} = $child; $child->sort_order( $args{sort_order} || (scalar values %{ $self->{children} }) ) unless ($child->sort_order()); # URL is relative to parents, and cached, so set it up now $child->path( $child->{path} ); # Figure out the URL my $path = $child->path; # Activate it if ( defined $path and length $path ) { my $base_path = $HTML::Mason::Commands::r->path_info; my $query = $HTML::Mason::Commands::m->cgi_object->query_string; $base_path =~ s!/+!/!g; $base_path .= "?$query" if defined $query and length $query; $base_path =~ s/index\.html$//; $base_path =~ s/\/+$//; $path =~ s/index\.html$//; $path =~ s/\/+$//; require URI::Escape; $base_path = URI::Escape::uri_unescape($base_path); if ( $path eq $base_path ) { $self->{children}{$key}->active(1); } } } return $self->{children}{$key}; } =head2 active_child Returns the first active child node, or C is there is none. =cut sub active_child { my $self = shift; foreach my $kid ($self->children) { return $kid if $kid->active; } return undef; } =head2 delete KEY Removes the child with the provided I. =cut sub delete { my $self = shift; my $key = shift; delete $self->{children_list}; delete $self->{children}{$key}; } =head2 has_children Returns true if there are any children on this menu =cut sub has_children { my $self = shift; if (@{ $self->children}) { return 1 } else { return 0; } } =head2 children Returns the children of this menu item in sorted order; as an array in array context, or as an array reference in scalar context. =cut sub children { my $self = shift; my @kids; if ($self->{children_list}) { @kids = @{$self->{children_list}}; } else { @kids = values %{$self->{children} || {}}; @kids = sort {$a->{sort_order} <=> $b->{sort_order}} @kids; $self->{children_list} = \@kids; } return wantarray ? @kids : \@kids; } =head2 add_after Called on a child, inserts a new menu item after it and shifts any other menu items at this level to the right. L by default would insert at the end of the list of children, unless you did manual sort_order calculations. Takes all the regular arguments to L. =cut sub add_after { shift->_insert_sibling("after", @_) } =head2 add_before Called on a child, inserts a new menu item at the child's location and shifts the child and the other menu items at this level to the right. L by default would insert at the end of the list of children, unless you did manual sort_order calculations. Takes all the regular arguments to L. =cut sub add_before { shift->_insert_sibling("before", @_) } sub _insert_sibling { my $self = shift; my $where = shift; my $parent = $self->parent; my $sort_order; for my $contemporary ($parent->children) { if ( $contemporary->key eq $self->key ) { if ($where eq "before") { # Bump the current child and the following $sort_order = $contemporary->sort_order; } elsif ($where eq "after") { # Leave the current child along, bump the rest $sort_order = $contemporary->sort_order + 1; next; } else { # never set $sort_order, act no differently than ->child() } } if ( $sort_order ) { $contemporary->sort_order( $contemporary->sort_order + 1 ); } } $parent->child( @_, sort_order => $sort_order ); } =head2 RemoveDashboardMenuItems Remove dashboards from individual user and system dash menus. Requires a hash with DashboardId and CurrentUser object. $menu->RemoveDashboardMenuItem( DashboardId => $id, CurrentUser => $session{CurrentUser}->UserObj ); =cut sub RemoveDashboardMenuItem { my $self = shift; my %args = @_; return unless $args{'DashboardId'} and $args{'CurrentUser'}; my $dashboard_id = $args{'DashboardId'}; my $current_user = $args{'CurrentUser'}; # First clear from user's dashboards my $dashboards_in_menu = $current_user->Preferences('DashboardsInMenu', {} ); my @dashboards = grep { $_ != $dashboard_id } @{$dashboards_in_menu->{'dashboards'}}; $dashboards_in_menu->{'dashboards'} = \@dashboards || []; my ($ret, $msg) = $current_user->SetPreferences('DashboardsInMenu', $dashboards_in_menu); RT::Logger->warn("Unable to update dashboard for user " . $current_user->Name . ": $msg") unless $ret; # Now update the system dashboard my $system = RT::System->new( $current_user ); my ($default_dashboards) = $system->Attributes->Named('DashboardsInMenu'); if ($default_dashboards) { $dashboards_in_menu = $default_dashboards->Content; my @dashboards = grep { $_ != $dashboard_id } @{$dashboards_in_menu->{'dashboards'}}; # Update only if we removed one if ( @{$dashboards_in_menu->{'dashboards'}} > @dashboards ){ $dashboards_in_menu->{'dashboards'} = \@dashboards || []; ($ret, $msg) = $default_dashboards->SetContent($dashboards_in_menu); RT::Logger->warn("Unable to update system dashboard menu: $msg") unless $ret; } } return; } 1; rt-5.0.1/lib/RT/Interface/Web/QueryBuilder.pm000644 000765 000024 00000004053 14005011336 021465 0ustar00sunnavystaff000000 000000 # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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 }}} package RT::Interface::Web::QueryBuilder; use strict; use warnings; RT::Base->_ImportOverlays(); 1; rt-5.0.1/lib/RT/Interface/Web/QueryBuilder/Tree.pm000644 000765 000024 00000024165 14005011336 022372 0ustar00sunnavystaff000000 000000 # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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 }}} package RT::Interface::Web::QueryBuilder::Tree; use strict; use warnings; use Tree::Simple qw/use_weak_refs/; use base qw/Tree::Simple/; =head1 NAME RT::Interface::Web::QueryBuilder::Tree - subclass of Tree::Simple used in Query Builder =head1 DESCRIPTION This class provides support functionality for the Query Builder (Search/Build.html). It is a subclass of L. =head1 METHODS =head2 TraversePrePost PREFUNC POSTFUNC Traverses the tree depth-first. Before processing the node's children, calls PREFUNC with the node as its argument; after processing all of the children, calls POSTFUNC with the node as its argument. (Note that unlike Tree::Simple's C, it actually calls its functions on the root node passed to it.) =cut sub TraversePrePost { my ($self, $prefunc, $postfunc) = @_; # XXX: if pre or post action changes siblings (delete or adds) # we could have problems $prefunc->($self) if $prefunc; foreach my $child ($self->getAllChildren()) { $child->TraversePrePost($prefunc, $postfunc); } $postfunc->($self) if $postfunc; } =head2 GetReferencedQueues Returns a hash reference; each queue referenced with an '=' operation will appear as a key whose value is 1. =cut sub GetReferencedQueues { my $self = shift; my %args = ( CurrentUser => '', @_ ); my $queues = {}; $self->traverse( sub { my $node = shift; return if $node->isRoot; return unless $node->isLeaf; my $clause = $node->getNodeValue(); if ( $clause->{Key} =~ /^(?:Ticket)?Queue$/ ) { if ( $clause->{Op} eq '=' ) { $queues->{ $clause->{Value} } ||= 1; } elsif ( $clause->{Op} =~ /^LIKE$/i ) { my $qs = RT::Queues->new( $args{CurrentUser} || $HTML::Mason::Commands::session{CurrentUser} ); $qs->Limit( FIELD => 'Name', VALUE => $clause->{Value}, OPERATOR => 'LIKE', CASESENSITIVE => 0 ); while ( my $q = $qs->Next ) { next unless $q->id; $queues->{ $q->id } ||= 1; } } } elsif ( $clause->{Key} eq 'Lifecycle' ) { if ( $clause->{Op} eq '=' ) { my $qs = RT::Queues->new( $args{CurrentUser} || $HTML::Mason::Commands::session{CurrentUser} ); $qs->Limit( FIELD => 'Lifecycle', VALUE => $clause->{Value} ); while ( my $q = $qs->Next ) { next unless $q->id; $queues->{ $q->id } ||= 1; } } } return; } ); return $queues; } =head2 GetReferencedCatalogs Returns a hash reference; each catalog referenced with an '=' operation will appear as a key whose value is 1. =cut sub GetReferencedCatalogs { my $self = shift; my $catalogs = {}; $self->traverse( sub { my $node = shift; return if $node->isRoot; return unless $node->isLeaf; my $clause = $node->getNodeValue(); return unless $clause->{ Key } eq 'Catalog'; return unless $clause->{ Op } eq '='; $catalogs->{ $clause->{ Value } } = 1; } ); return $catalogs; } =head2 GetQueryAndOptionList SELECTED_NODES Given an array reference of tree nodes that have been selected by the user, traverses the tree and returns the equivalent SQL query and a list of hashes representing the "clauses" select option list. Each has contains the keys TEXT, INDEX, SELECTED, and DEPTH. TEXT is the displayed text of the option (including parentheses, not including indentation); INDEX is the 0-based index of the option in the list (also used as its CGI parameter); SELECTED is either 'SELECTED' or '', depending on whether the node corresponding to the select option was in the SELECTED_NODES list; and DEPTH is the level of indentation for the option. =cut sub GetQueryAndOptionList { my $self = shift; my $selected_nodes = shift; my $list = $self->__LinearizeTree; foreach my $e( @$list ) { $e->{'DEPTH'} = $e->{'NODE'}->getDepth; $e->{'SELECTED'} = (grep $_ == $e->{'NODE'}, @$selected_nodes)? qq[ selected="selected"] : ''; } return (join ' ', map $_->{'TEXT'}, @$list), $list; } =head2 PruneChildLessAggregators If tree manipulation has left it in a state where there are ANDs, ORs, or parenthesizations with no children, get rid of them. =cut sub PruneChildlessAggregators { my $self = shift; $self->TraversePrePost( undef, sub { my $node = shift; return unless $node->isLeaf; # We're only looking for aggregators (AND/OR) return if ref $node->getNodeValue; return if $node->isRoot; # OK, this is a childless aggregator. Remove self. $node->getParent->removeChild($node); $node->DESTROY; } ); } =head2 GetDisplayedNodes This function returns a list of the nodes of the tree in depth-first order which correspond to options in the "clauses" multi-select box. In fact, it's all of them but the root and its child. =cut sub GetDisplayedNodes { return map $_->{NODE}, @{ (shift)->__LinearizeTree }; } sub __LinearizeTree { my $self = shift; my ($list, $i) = ([], 0); $self->TraversePrePost( sub { my $node = shift; return if $node->isRoot; my $str = ''; if( $node->getIndex > 0 ) { $str .= " ". $node->getParent->getNodeValue ." "; } unless( $node->isLeaf ) { $str .= '( '; } else { my $clause = $node->getNodeValue; my $key = $clause->{Key}; $key .= "." . $clause->{Subkey} if defined $clause->{Subkey}; if ($key =~ s/(['\\])/\\$1/g or $key =~ /[^{}\w\.]/) { $key = "'$key'"; } my $value = $clause->{Value}; my $op = $clause->{Op}; if ( $value =~ /^NULL$/i && $op =~ /^(!?)=$/ ) { $op = $1 ? 'IS NOT' : 'IS'; } if ( $op =~ /^IS( NOT)?$/i ) { $value = 'NULL'; } elsif ( $value !~ /^[+-]?[0-9]+$/ ) { $value =~ s/(['\\])/\\$1/g; $value = "'$value'"; } $str .= $key ." ". $op . " " . $value; } $str =~ s/^\s+|\s+$//; push @$list, { NODE => $node, TEXT => $str, INDEX => $i, }; $i++; }, sub { my $node = shift; return if $node->isRoot; return if $node->isLeaf; $list->[-1]->{'TEXT'} .= ' )'; }); return $list; } sub ParseSQL { my $self = shift; my %args = ( Query => '', CurrentUser => '', #XXX: Hack Class => 'RT::Tickets', @_ ); my $string = $args{'Query'}; my @results; my %field = %{ $args{Class}->new( $args{'CurrentUser'} )->FIELDS }; my %lcfield = map { ( lc($_) => $_ ) } keys %field; my $node = $self; my %callback; $callback{'OpenParen'} = sub { $node = __PACKAGE__->new( 'AND', $node ); }; $callback{'CloseParen'} = sub { $node = $node->getParent }; $callback{'EntryAggregator'} = sub { $node->setNodeValue( $_[0] ) }; $callback{'Condition'} = sub { my ($key, $op, $value) = @_; my ($main_key, $subkey) = split /[.]/, $key, 2; unless( $lcfield{ lc $main_key} ) { push @results, [ $args{'CurrentUser'}->loc("Unknown field: [_1]", $key), -1 ] } $main_key = $lcfield{ lc $main_key }; # Hardcode value for IS / IS NOT $value = 'NULL' if $op =~ /^IS( NOT)?$/i; my $clause = { Key => $main_key, Subkey => $subkey, Meta => $field{ $main_key }, Op => $op, Value => $value }; $node->addChild( __PACKAGE__->new( $clause ) ); }; $callback{'Error'} = sub { push @results, @_ }; require RT::SQL; RT::SQL::Parse($string, \%callback); return @results; } RT::Base->_ImportOverlays(); 1; rt-5.0.1/lib/RT/Interface/Web/Middleware/StaticHeaders.pm000644 000765 000024 00000005353 14005011336 023655 0ustar00sunnavystaff000000 000000 # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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 }}} package RT::Interface::Web::Middleware::StaticHeaders; use strict; use warnings; use base 'Plack::Middleware'; use Plack::Util; use Plack::Util::Accessor qw(path headers); sub call { my ( $self, $env ) = @_; my $res = $self->app->($env); my $path_match = $self->path; my $path = $env->{'PATH_INFO'}; for ($path) { my $matched = 'CODE' eq ref $path_match ? $path_match->($_, $env) : $_ =~ $path_match; return $res unless $matched; return $self->response_cb( $res, sub { my $res = shift; my $headers = $res->[1]; Plack::Util::header_iter( $self->headers, sub { Plack::Util::header_set($headers, @_); } ); } ); } } 1; rt-5.0.1/lib/RT/Pod/HTML.pm000644 000765 000024 00000015033 14005011336 015722 0ustar00sunnavystaff000000 000000 # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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::Pod::HTML; use base 'Pod::Simple::XHTML'; use HTML::Entities qw//; __PACKAGE__->_accessorize( "batch" ); sub new { my $self = shift->SUPER::new(@_); $self->index(1); $self->anchor_items(1); return $self; } sub decode_entities { my $self = shift; return HTML::Entities::decode_entities($_[0]); } sub perldoc_url_prefix { "http://metacpan.org/module/" } sub html_header { '' } sub html_footer { my $self = shift; my $toc = "../" x ($self->batch_mode_current_level - 1); return '← Back to index'; } sub start_F { $_[0]{'scratch_F'} = $_[0]{'scratch'}; $_[0]{'scratch'} = ""; } sub end_F { my $self = shift; my $text = $self->{scratch}; my $file = $self->decode_entities($text); if (my $local = $self->resolve_local_link($file)) { $text = qq[$text]; } $self->{'scratch'} = delete $self->{scratch_F}; $self->{'scratch'} .= "$text"; } sub _end_head { my $self = shift; $self->{scratch} = '' . $self->{scratch} . ''; return $self->SUPER::_end_head(@_); } sub handle_text { my ( $self, $text ) = @_; if ( $self->{in_pod} && $self->{scratch} =~ // && $text =~ /^"(.+)" in docs$/ ) { # Tweak default text for local links under docs/, so # q{"customizing/search_result_columns.pod/Column Map" in docs} becomes # q{"Column Map Callback" in customizing/search_result_columns.pod} # # q{"customizing/search_result_columns.pod" in docs} becomes # q{docs/customizing/search_result_columns.pod} my $section = $1; if ( $section =~ qr!(.+\.pod)/(.+)! ) { $text = qq{"$2" in docs/$1}; } else { $text = "docs/$section"; } } $self->SUPER::handle_text( $text ); } sub resolve_pod_page_link { my $self = shift; my ($name, $section) = @_; # Only try to resolve local links if we're in batch mode and are linking # outside the current document. return $self->SUPER::resolve_pod_page_link(@_) unless $self->batch_mode and $name; my $local = $self->resolve_local_link($name, $section); return $local ? $local : $self->SUPER::resolve_pod_page_link(@_); } sub resolve_local_link { my $self = shift; my ($name, $section) = @_; $name .= ""; # stringify name, it may be an object if ( $name eq 'docs' ) { if ( $section =~ qr!(.+\.pod)/(.+)! ) { # support L $name .= '/' . $1; $section = $2; } else { # support L $name .= '/' . $section; undef $section; } } $section = defined $section ? '#' . $self->idify($section, 1) : ''; my $local; if ($name =~ /^RT(::(?!Extension::|Authen::(?!ExternalAuth))|$)/ or $self->batch->found($name)) { $local = join "/", map { $self->encode_entities($_) } split /::/, $name; } elsif ($name =~ /^rt([-_]|$)/) { $local = $self->encode_entities($name); } elsif ($name =~ /^(\w+)_Config(\.pm)?$/) { $name = "$1_Config"; $local = "$1_Config"; } elsif ($name eq 'README') { # We process README separately in devel/tools/rt-static-docs $local = $name; } elsif ($name =~ /^UPGRADING.*/) { # If an UPGRADING file is referred to anywhere else (such as # templates.pod) we won't have seen UPGRADING yet and will treat # it as a non-local file. $local = $name; } # These matches handle links that look like filenames, such as those we # parse out of F<> tags. elsif ( $name =~ m{^(?:lib/)(RT/[\w/]+?)\.pm$} or $name =~ m{^(?:docs/)(.+?)\.pod$}) { $name = join "::", split '/', $1; $local = join "/", map { $self->encode_entities($_) } split /\//, $1; } if ($local) { # Resolve links correctly by going up my $found = $self->batch->found($name); my $depth = $self->batch_mode_current_level + ($found ? -1 : 1); return ($depth ? "../" x $depth : "") . ($found ? "" : "rt/latest/") . "$local.html$section"; } else { return; } } sub batch_mode_page_object_init { my ($self, $batch, $module, $infile, $outfile, $depth) = @_; $self->SUPER::batch_mode_page_object_init(@_[1..$#_]); $self->batch( $batch ); return $self; } 1; rt-5.0.1/lib/RT/Pod/HTMLBatch.pm000644 000765 000024 00000013665 14005011336 016675 0ustar00sunnavystaff000000 000000 # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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::Pod::HTMLBatch; use base 'Pod::Simple::HTMLBatch'; use List::MoreUtils qw/all/; use RT::Pod::Search; use RT::Pod::HTML; sub new { my $self = shift->SUPER::new(@_); $self->verbose(0); # Per-page output options $self->css_flurry(0); # No CSS $self->javascript_flurry(0); # No JS $self->no_contents_links(1); # No header/footer "Back to contents" links # TOC options $self->index(1); # Write a per-page TOC $self->contents_file("index.html"); # Write a global TOC $self->html_render_class('RT::Pod::HTML'); $self->search_class('RT::Pod::Search'); return $self; } sub classify { my $self = shift; my %info = (@_); my $is_install_doc = sub { my %page = @_; local $_ = $page{name}; return 1 if /^(README|UPGRADING)/; return 1 if /^RT\w*?_Config$/; return 1 if $_ eq "web_deployment"; return 1 if $page{infile} =~ m{^configure(\.ac)?$}; return 0; }; my $section = $info{infile} =~ m{/plugins/([^/]+)} ? "05 Extension: $1" : $info{infile} =~ m{/local/} ? '04 Local Documenation' : $is_install_doc->(%info) ? '00 Install and Upgrade '. 'Documentation' : $info{infile} =~ m{/(docs|etc)/} ? '01 User Documentation' : $info{infile} =~ m{/bin/} ? '02 Utilities (bin)' : $info{infile} =~ m{/sbin/} ? '03 Utilities (sbin)' : $info{name} =~ /^RT::Action/ ? '08 Actions' : $info{name} =~ /^RT::Condition/ ? '09 Conditions' : $info{name} =~ /^RT(::|$)/ ? '07 Developer Documentation' : $info{infile} =~ m{/devel/tools/} ? '20 Utilities (devel/tools)' : '06 Miscellaneous' ; if ($info{infile} =~ m{/(docs|etc)/}) { $info{name} =~ s/_/ /g; $info{name} = join "/", map { ucfirst } split /::/, $info{name}; } return ($info{name}, $section); } sub write_contents_file { my ($self, $to) = @_; return unless $self->contents_file; my $file = join "/", $to, $self->contents_file; open my $index, ">", $file or warn "Unable to open index file '$file': $!\n", return; my $pages = $self->_contents; return unless @$pages; # Classify my %toc; for my $page (@$pages) { my ($name, $infile, $outfile, $pieces) = @$page; my ($title, $section) = $self->classify( name => $name, infile => $infile, ); (my $path = $outfile) =~ s{^\Q$to\E/?}{}; push @{ $toc{$section} }, { name => $title, path => $path, }; } # Write out index print $index "
      \n"; for my $key (sort keys %toc) { next unless @{ $toc{$key} }; (my $section = $key) =~ s/^\d+ //; print $index "
      ", esc($section), "
      \n"; print $index "
      \n"; my @sorted = sort { my @names = map { $_->{name} } $a, $b; # Sort just the upgrading docs descending within everything else @names = reverse @names if all { /^UPGRADING-/ } @names; $names[0] cmp $names[1] } @{ $toc{$key} }; for my $page (@sorted) { print $index " ", esc($page->{name}), "
      \n"; } print $index "
      \n"; } print $index '
      '; close $index; } sub esc { Pod::Simple::HTMLBatch::esc(@_); } sub found { my ($self, $module) = @_; return grep { $_->[0] eq $module } @{$self->_contents}; } 1; rt-5.0.1/lib/RT/Pod/Search.pm000644 000765 000024 00000004430 14005011336 016362 0ustar00sunnavystaff000000 000000 # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC # # # (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::Pod::Search; use base 'Pod::Simple::Search'; sub new { my $self = shift->SUPER::new(@_); $self->laborious(1) # Find scripts too ->limit_re(qr/(?inc(0); # Don't look in @INC return $self; } 1; rt-5.0.1/t/pod.t000644 000765 000024 00000000277 14005011336 014221 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::More; use Test::Pod; all_pod_files_ok( all_pod_files("lib","devel","docs","etc","bin","sbin"), , , ); rt-5.0.1/t/transaction/000755 000765 000024 00000000000 14005011336 015571 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/externalauth/000755 000765 000024 00000000000 14005011336 015750 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/validator/000755 000765 000024 00000000000 14005011336 015231 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/externalstorage/000755 000765 000024 00000000000 14005011336 016453 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/customroles/000755 000765 000024 00000000000 14005011336 015623 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/security/000755 000765 000024 00000000000 14005011336 015113 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/crypt/000755 000765 000024 00000000000 14005011336 014405 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/mail/000755 000765 000024 00000000000 14005011336 014166 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/web/000755 000765 000024 00000000000 14005011336 014021 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/00-mason-syntax.t000644 000765 000024 00000001467 14005011336 016317 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodb => 1; use File::Find; find( { no_chdir => 1, wanted => sub { return if /(?:\.(?:jpe?g|png|gif|rej)|\~)$/i; return if m{/\.[^/]+\.sw[op]$}; # vim swap files return unless -f $_; local ($@); ok( eval { compile_file($_) }, "Compiled $File::Find::name ok: $@"); }, }, RT::Test::get_relocatable_dir('../share/html')); use HTML::Mason; use HTML::Mason::Compiler; use HTML::Mason::Compiler::ToObject; BEGIN { require RT::Test; } sub compile_file { my $file = shift; my $text = Encode::decode( "UTF-8", RT::Test->file_content($file)); my $compiler = new HTML::Mason::Compiler::ToObject; $compiler->compile( comp_source => $text, name => 'my', comp_path => 'my', ); return 1; } rt-5.0.1/t/articles/000755 000765 000024 00000000000 14005011336 015052 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/charts/000755 000765 000024 00000000000 14005011336 014530 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/99-policy.t000644 000765 000024 00000012215 14005011336 015170 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use File::Find; use IPC::Run3; my @files; find( { wanted => sub { push @files, $File::Find::name if -f; $File::Find::prune = 1 if $_ eq "t/tmp" or m{/\.git$}; }, no_chdir => 1 }, qw{etc lib share t bin sbin devel/tools docs devel/docs} ); if ( my $dir = `git rev-parse --git-dir 2>/dev/null` ) { # We're in a git repo, use the ignore list chomp $dir; my %ignores; $ignores{ $_ }++ for grep $_, split /\n/, `git ls-files -o -i --exclude-standard .`; @files = grep {not $ignores{$_}} @files; } sub check { my $file = shift; my %check = ( strict => 0, warnings => 0, no_tabs => 0, shebang => 0, exec => 0, bps_tag => 0, compile_perl => 0, @_, ); if ($check{strict} or $check{warnings} or $check{shebang} or $check{bps_tag} or $check{no_tabs}) { local $/; open my $fh, '<', $file or die $!; my $content = <$fh>; unless ($check{shebang} != -1 and $content =~ /^#!(?!.*perl)/i) { like( $content, qr/^use strict(?:;|\s+)/m, "$file has 'use strict'" ) if $check{strict}; like( $content, qr/^use warnings(?:;|\s+)/m, "$file has 'use warnings'" ) if $check{warnings}; } if ($check{shebang} == 1) { like( $content, qr/^#!/, "$file has shebang" ); } elsif ($check{shebang} == -1) { unlike( $content, qr/^#!/, "$file has no shebang" ); } my $other_copyright = 0; $other_copyright = 1 if $file =~ /\.(css|js)$/ and not $content =~ /Copyright\s+\(c\)\s+\d\d\d\d-\d\d\d\d Best Practical Solutions/i and $file =~ /(?:ckeditor|scriptaculous|superfish|tablesorter|farbtastic)/i; $other_copyright = 1 if $file =~ /\.(css|js)$/ and not $content =~ /Copyright\s+\(c\)\s+\d\d\d\d-\d\d\d\d Best Practical Solutions/i and ($content =~ /\b(copyright|GPL|Public Domain)\b/i or $content =~ /\(c\)\s+\d\d\d\d(?:-\d\d\d\d)?/i); $check{bps_tag} = -1 if $check{bps_tag} and $other_copyright; if ($check{bps_tag} == 1) { like( $content, qr/[B]EGIN BPS TAGGED BLOCK \{\{\{/, "$file has BPS license tag"); } elsif ($check{bps_tag} == -1) { unlike( $content, qr/[B]EGIN BPS TAGGED BLOCK \{\{\{/, "$file has no BPS license tag" . ($other_copyright ? " (other copyright)" : "")); } if (not $other_copyright and $check{no_tabs}) { unlike( $content, qr/\t/, "$file has no hard tabs" ); } } my $executable = ( stat $file )[2] & 0100; if ($check{exec} == 1) { if ( $file =~ /\.in$/ ) { ok( !$executable, "$file permission is u-x (.in will add +x)" ); } else { ok( $executable, "$file permission is u+x" ); } } elsif ($check{exec} == -1) { ok( !$executable, "$file permission is u-x" ); } if ($check{compile_perl}) { my ($input, $output, $error) = ('', '', ''); my $pre_check = 1; if ( $file =~ /\bmysql\b/ ) { eval { require DBD::mysql }; undef $pre_check if $@; } if ( $pre_check ) { run3( [ $^X, '-Ilib', '-Mstrict', '-Mwarnings', '-c', $file ], \$input, \$output, \$error, ); is $error, "$file syntax OK\n", "$file syntax is OK"; } } } check( $_, shebang => -1, exec => -1, warnings => 1, strict => 1, bps_tag => 1, no_tabs => 1 ) for grep {m{^lib/.*\.pm$}} @files; check( $_, shebang => -1, exec => -1, warnings => 1, strict => 1, bps_tag => -1, no_tabs => 1 ) for grep {m{^t/.*\.t$}} @files; check( $_, shebang => 1, exec => 1, warnings => 1, strict => 1, bps_tag => 1, no_tabs => 1 ) for grep {m{^s?bin/}} @files; check( $_, compile_perl => 1, exec => 1 ) for grep { -f $_ } map { s/\.in$//; $_ } grep {m{^s?bin/}} @files; check( $_, shebang => 1, exec => 1, warnings => 1, strict => 1, bps_tag => 1, no_tabs => 1 ) for grep {m{^devel/tools/} and not m{/(localhost\.(crt|key)|mime\.types)$}} @files; check( $_, exec => -1 ) for grep {m{^share/static/}} @files; check( $_, exec => -1, bps_tag => 1, no_tabs => 1 ) for grep {m{^share/html/}} @files; check( $_, exec => -1 ) for grep {m{^share/(po|fonts)/}} @files; check( $_, exec => -1 ) for grep {m{^t/data/}} @files; check( $_, exec => -1, bps_tag => -1 ) for grep {m{^etc/[^/]+$}} @files; check( $_, exec => -1, bps_tag => -1 ) for grep {m{^etc/upgrade/[^/]+/}} @files; check( $_, warnings => 1, strict => 1, compile_perl => 1, no_tabs => 1 ) for grep {m{^etc/upgrade/.*/content$}} @files; check( $_, shebang => 1, exec => 1, warnings => 1, strict => 1, bps_tag => 1, no_tabs => 1 ) for grep {m{^etc/upgrade/[^/]+$}} @files; check( $_, compile_perl => 1, exec => 1 ) for grep{ -f $_} map {s/\.in$//; $_} grep {m{^etc/upgrade/[^/]+$}} @files; check( $_, exec => -1 ) for grep {m{^(devel/)?docs/}} @files; done_testing; rt-5.0.1/t/fts/000755 000765 000024 00000000000 14005011336 014040 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/shredder/000755 000765 000024 00000000000 14005011336 015044 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/00-compile.t000644 000765 000024 00000003126 14005011336 015300 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodb => 1, tests => 44; require_ok("RT"); require_ok("RT::Test"); require_ok("RT::ACL"); require_ok("RT::Handle"); require_ok("RT::Transaction"); require_ok("RT::Interface::CLI"); require_ok("RT::Interface::Email"); require_ok("RT::Links"); require_ok("RT::Queues"); require_ok("RT::Scrips"); require_ok("RT::Templates"); require_ok("RT::Principals"); require_ok("RT::Attachments"); require_ok("RT::GroupMember"); require_ok("RT::ScripAction"); require_ok("RT::CustomFields"); require_ok("RT::GroupMembers"); require_ok("RT::ScripActions"); require_ok("RT::Transactions"); require_ok("RT::ScripCondition"); require_ok("RT::Action"); require_ok("RT::ScripConditions"); require_ok("RT::Search"); require_ok("RT::Action::SendEmail"); require_ok("RT::CachedGroupMembers"); require_ok("RT::Condition"); require_ok("RT::Interface::Web"); require_ok("RT::SavedSearch"); require_ok("RT::SavedSearches"); require_ok("RT::Dashboard"); require_ok("RT::Dashboard::Mailer"); require_ok("RT::Dashboards"); require_ok("RT::Installer"); require_ok("RT::Util"); require_ok("RT::Article"); require_ok("RT::Articles"); require_ok("RT::Class"); require_ok("RT::Classes"); require_ok("RT::ObjectClass"); require_ok("RT::ObjectClasses"); require_ok("RT::ObjectTopic"); require_ok("RT::ObjectTopics"); require_ok("RT::Topic"); require_ok("RT::Topics"); # no the following doesn't work yet __END__ use File::Find::Rule; my @files = File::Find::Rule->file() ->name( '*.pm' ) ->in( 'lib' ); plan tests => scalar @files; for (@files) { local $SIG{__WARN__} = sub {}; require_ok($_); } rt-5.0.1/t/ldapimport/000755 000765 000024 00000000000 14005011336 015417 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/customfields/000755 000765 000024 00000000000 14005011336 015745 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/lifecycles/000755 000765 000024 00000000000 14005011336 015366 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/ticket/000755 000765 000024 00000000000 14005011336 014527 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/api/000755 000765 000024 00000000000 14005011336 014015 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/sla/000755 000765 000024 00000000000 14005011336 014023 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/i18n/000755 000765 000024 00000000000 14005011336 014023 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/000755 000765 000024 00000000000 14005011336 014155 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/assets/000755 000765 000024 00000000000 14005011336 014546 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/rest2/000755 000765 000024 00000000000 14005011336 014303 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/approval/000755 000765 000024 00000000000 14005011336 015070 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/approval/basic.t000644 000765 000024 00000016350 14005011336 016343 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use RT::Test::Email; RT->Config->Set( LogToSTDERR => 'debug' ); RT->Config->Set( UseTransactionBatch => 1 ); my $q = RT::Queue->new(RT->SystemUser); $q->Load('___Approvals'); $q->SetDisabled(0); my %users; for my $user_name (qw(minion cfo ceo )) { my $user = $users{$user_name} = RT::User->new(RT->SystemUser); $user->Create( Name => uc($user_name), Privileged => 1, EmailAddress => $user_name.'@company.com'); my ($val, $msg); ($val, $msg) = $user->PrincipalObj->GrantRight(Object =>$q, Right => $_) for qw(ModifyTicket OwnTicket ShowTicket); } # XXX: we need to make the first approval ticket open so notification is sent. my $approvals = '===Create-Ticket: for-CFO Queue: ___Approvals Type: approval Owner: CFO Refers-To: TOP Subject: CFO Approval for PO: {$Tickets{"TOP"}->Id} - {$Tickets{"TOP"}->Subject} Due: {time + 86400} Content-Type: text/plain Content: Your approval is requested for the PO ticket {$Tickets{"TOP"}->Id}: {$Tickets{"TOP"}->Subject} Blah Blah ENDOFCONTENT ===Create-Ticket: for-CEO Queue: ___Approvals Type: approval Owner: CEO Subject: PO approval request for {$Tickets{"TOP"}->Subject} Refers-To: TOP Depends-On: for-CFO Depended-On-By: {$Tickets{"TOP"}->Id} Content-Type: text/plain Content: Your CFO approved PO ticket {$Tickets{"TOP"}->Id} for minion. you ok with that? ENDOFCONTENT '; my $apptemp = RT::Template->new(RT->SystemUser); $apptemp->Create( Content => $approvals, Name => "PO Approvals", Queue => "0"); ok($apptemp->Id); $q = RT::Queue->new(RT->SystemUser); $q->Create(Name => 'PO'); ok ($q->Id, "Created PO queue"); my $scrip = RT::Scrip->new(RT->SystemUser); my ($sval, $smsg) =$scrip->Create( ScripCondition => 'On Create', ScripAction => 'Create Tickets', Template => 'PO Approvals', Queue => $q->Id, Description => 'Create Approval Tickets'); ok ($sval, $smsg); ok ($scrip->Id, "Created the scrip"); ok ($scrip->TemplateObj->Id, "Created the scrip template"); ok ($scrip->ConditionObj->Id, "Created the scrip condition"); ok ($scrip->ActionObj->Id, "Created the scrip action"); my $t = RT::Ticket->new(RT->SystemUser); my ($tid, $ttrans, $tmsg); mail_ok { ($tid, $ttrans, $tmsg) = $t->Create(Subject => "PO for stationary", Owner => "root", Requestor => 'minion', Queue => $q->Id); } { from => qr/PO via RT/, to => 'minion@company.com', subject => qr/PO for stationary/, body => qr/automatically generated in response/ },{ from => qr/RT System/, to => 'root@localhost', subject => qr/PO for stationary/, }, { from => qr/RT System/, to => 'cfo@company.com', subject => qr/New Pending Approval: CFO Approval/, body => qr/pending your approval.*Your approval is requested.*Blah/s }; ok ($tid,$tmsg); is ($t->ReferredToBy->Count,2, "referred to by the two tickets"); my $deps = $t->DependsOn; is ($deps->Count, 1, "The ticket we created depends on one other ticket"); my $dependson_ceo= $deps->First->TargetObj; ok ($dependson_ceo->Id, "It depends on a real ticket"); like($dependson_ceo->Subject, qr/PO approval request.*stationary/); $deps = $dependson_ceo->DependsOn; is ($deps->Count, 1, "The ticket we created depends on one other ticket"); my $dependson_cfo = $deps->First->TargetObj; ok ($dependson_cfo->Id, "It depends on a real ticket"); like($dependson_cfo->Subject, qr/CFO Approval for PO.*stationary/); is_deeply([ $t->Status, $dependson_cfo->Status, $dependson_ceo->Status ], [ 'new', 'open', 'new'], 'tickets in correct state'); mail_ok { my $cfo = RT::CurrentUser->new; $cfo->Load( $users{cfo} ); $dependson_cfo->CurrentUser($cfo); my $notes = MIME::Entity->build( Data => [ 'Resources exist to be consumed.' ] ); RT::I18N::SetMIMEEntityToUTF8($notes); # convert text parts into utf-8 my ( $notesval, $notesmsg ) = $dependson_cfo->Correspond( MIMEObj => $notes ); ok($notesval, $notesmsg); my ($ok, $msg) = $dependson_cfo->SetStatus( Status => 'resolved' ); ok($ok, "cfo can approve - $msg"); } { from => qr/RT System/, to => 'ceo@company.com', subject => qr/New Pending Approval: PO approval request for PO/, body => qr/pending your approval.*CFO approved.*ok with that\?/s },{ from => qr/RT System/, to => 'root@localhost', subject => qr/Ticket Approved:/, },{ from => qr/RT System/, to => 'minion@company.com', subject => qr/Ticket Approved:/, body => qr/approved by CFO.*notes: Resources exist to be consumed/s }; is ($t->DependsOn->Count, 1, "still depends only on the CEO approval"); is ($t->ReferredToBy->Count,2, "referred to by the two tickets"); is_deeply([ $t->Status, $dependson_cfo->Status, $dependson_ceo->Status ], [ 'new', 'resolved', 'open'], 'ticket state after cfo approval'); mail_ok { my $ceo = RT::CurrentUser->new; $ceo->Load( $users{ceo} ); $dependson_ceo->CurrentUser($ceo); my $notes = MIME::Entity->build( Data => [ 'And consumed they will be.' ] ); RT::I18N::SetMIMEEntityToUTF8($notes); # convert text parts into utf-8 my ( $notesval, $notesmsg ) = $dependson_ceo->Correspond( MIMEObj => $notes ); ok($notesval, $notesmsg); my ($ok, $msg) = $dependson_ceo->SetStatus( Status => 'resolved' ); ok($ok, "ceo can approve - $msg"); } { from => qr/RT System/, to => 'root@localhost', subject => qr/Ticket Approved:/, body => qr/approved by CEO.*Its Owner may now start to act on it.*notes: And consumed they will be/s, },{ from => qr/RT System/, to => 'minion@company.com', subject => qr/Ticket Approved:/, body => qr/approved by CEO.*Its Owner may now start to act on it.*notes: And consumed they will be/s, },{ from => qr/CEO via RT/, to => 'root@localhost', subject => qr/Ticket Approved/, body => qr/The ticket has been approved, you may now start to act on it/, }; is_deeply([ $t->Status, $dependson_cfo->Status, $dependson_ceo->Status ], [ 'new', 'resolved', 'resolved'], 'ticket state after ceo approval'); $dependson_cfo->_Set( Field => 'Status', Value => 'open'); $dependson_ceo->_Set( Field => 'Status', Value => 'new'); mail_ok { my $cfo = RT::CurrentUser->new; $cfo->Load( $users{cfo} ); $dependson_cfo->CurrentUser($cfo); my $notes = MIME::Entity->build( Data => [ 'sorry, out of resources.' ] ); RT::I18N::SetMIMEEntityToUTF8($notes); # convert text parts into utf-8 my ( $notesval, $notesmsg ) = $dependson_cfo->Correspond( MIMEObj => $notes ); ok($notesval, $notesmsg); my ($ok, $msg) = $dependson_cfo->SetStatus( Status => 'rejected' ); ok($ok, "cfo can approve - $msg"); } { from => qr/RT System/, to => 'root@localhost', subject => qr/Ticket Rejected: PO for stationary/, body => qr/rejected by CFO.*out of resources/s, },{ from => qr/RT System/, to => 'minion@company.com', subject => qr/Ticket Rejected: PO for stationary/, body => qr/rejected by CFO.*out of resources/s, }; $t->Load($t->id);$dependson_ceo->Load($dependson_ceo->id); is_deeply([ $t->Status, $dependson_cfo->Status, $dependson_ceo->Status ], [ 'rejected', 'rejected', 'deleted'], 'ticket state after cfo rejection'); done_testing; rt-5.0.1/t/approval/admincc.t000644 000765 000024 00000020602 14005011336 016653 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::More; use RT; use RT::Test tests => "no_declare"; use RT::Test::Email; RT->Config->Set( LogToSTDERR => 'debug' ); RT->Config->Set( UseTransactionBatch => 1 ); my ($baseurl, $m) = RT::Test->started_ok; my $q = RT::Queue->new($RT::SystemUser); $q->Load('___Approvals'); $q->SetDisabled(0); my %users; # minion is the requestor, cto is the approval owner, coo and ceo are approval # adminccs for my $user_name (qw(minion cto coo ceo )) { my $user = $users{$user_name} = RT::User->new($RT::SystemUser); $user->Create( Name => uc($user_name), Privileged => 1, EmailAddress => $user_name.'@company.com', Password => 'password', ); my ($val, $msg); ($val, $msg) = $user->PrincipalObj->GrantRight(Object =>$q, Right => $_) for qw(ModifyTicket OwnTicket ShowTicket); } # XXX: we need to make the first approval ticket open so notification is sent. my $approvals = '===Create-Ticket: for-CTO Queue: ___Approvals Type: approval Owner: CTO AdminCCs: COO, CEO DependedOnBy: TOP Subject: CTO Approval for PO: {$Tickets{"TOP"}->Id} - {$Tickets{"TOP"}->Subject} Due: {time + 86400} Content-Type: text/plain Content: Your approval is requested for the PO ticket {$Tickets{"TOP"}->Id}: {$Tickets{"TOP"}->Subject} Blah Blah ENDOFCONTENT '; my $apptemp = RT::Template->new($RT::SystemUser); $apptemp->Create( Content => $approvals, Name => "PO Approvals", Queue => "0"); ok($apptemp->Id); $q = RT::Queue->new($RT::SystemUser); $q->Create(Name => 'PO'); ok ($q->Id, "Created PO queue"); my $scrip = RT::Scrip->new($RT::SystemUser); my ($sval, $smsg) =$scrip->Create( ScripCondition => 'On Create', ScripAction => 'Create Tickets', Template => 'PO Approvals', Queue => $q->Id, Description => 'Create Approval Tickets'); ok ($sval, $smsg); ok ($scrip->Id, "Created the scrip"); ok ($scrip->TemplateObj->Id, "Created the scrip template"); ok ($scrip->ConditionObj->Id, "Created the scrip condition"); ok ($scrip->ActionObj->Id, "Created the scrip action"); my $t = RT::Ticket->new($RT::SystemUser); my ($tid, $ttrans, $tmsg); mail_ok { ( $tid, $ttrans, $tmsg ) = $t->Create( Subject => "PO for stationary", Owner => "root", Requestor => 'minion', Queue => $q->Id, ); } { from => qr/PO via RT/, to => 'minion@company.com', subject => qr/PO for stationary/, body => qr/automatically generated in response/ },{ from => qr/RT System/, to => 'root@localhost', subject => qr/PO for stationary/, },{ from => qr/RT System/, to => 'cto@company.com', bcc => qr/ceo.*coo|coo.*ceo/i, subject => qr/New Pending Approval: CTO Approval/, body => qr/pending your approval.*Your approval is requested.*Blah/s } ; ok ($tid,$tmsg); is ($t->DependsOn->Count, 1, "depends on one ticket"); my $t_cto = RT::Ticket->new( $RT::SystemUser ); $t_cto->Load( $t->DependsOn->First->TargetObj->id ); is_deeply( [ $t->Status, $t_cto->Status ], [ 'new', 'open' ], 'tickets in correct state' ); mail_ok { my $cto = RT::CurrentUser->new; $cto->Load( $users{cto} ); $t_cto->CurrentUser($cto); my $notes = MIME::Entity->build( Data => [ 'Resources exist to be consumed.' ] ); RT::I18N::SetMIMEEntityToUTF8($notes); # convert text parts into utf-8 my ( $notesval, $notesmsg ) = $t_cto->Correspond( MIMEObj => $notes ); ok($notesval, $notesmsg); my ($ok, $msg) = $t_cto->SetStatus( Status => 'resolved' ); ok($ok, "cto can approve - $msg"); } { from => qr/CTO/, bcc => qr/ceo.*coo|coo.*ceo/i, body => qr/Resources exist to be consumed/, }, { from => qr/RT System/, to => 'root@localhost', subject => qr/Ticket Approved:/, }, { from => qr/RT System/, to => 'minion@company.com', subject => qr/Ticket Approved:/, body => qr/approved by CTO.*notes: Resources exist to be consumed/s }, { from => qr/CTO/, to => 'root@localhost', subject => qr/Ticket Approved:/, body => qr/The ticket has been approved, you may now start to act on it/, }; is_deeply( [ $t->Status, $t_cto->Status ], [ 'new', 'resolved' ], 'ticket state after coo approval' ); for my $admin (qw/coo ceo/) { $t_cto->_Set( Field => 'Status', Value => 'open', ); RT::Test->clean_caught_mails; mail_ok { my $user = RT::CurrentUser->new; $user->Load( $users{$admin} ); $t_cto->CurrentUser($user); my $notes = MIME::Entity->build( Data => ['Resources exist to be consumed.'] ); RT::I18N::SetMIMEEntityToUTF8($notes); # convert text parts into utf-8 my ( $notesval, $notesmsg ) = $t_cto->Correspond( MIMEObj => $notes ); ok( $notesval, $notesmsg ); my ( $ok, $msg ) = $t_cto->SetStatus( Status => 'resolved' ); ok( $ok, "cto can approve - $msg" ); } { from => qr/\U$admin/, bcc => $admin eq 'coo' ? qr/ceo/i : qr/coo/, body => qr/Resources exist to be consumed/, }, { from => qr/RT System/, to => 'root@localhost', subject => qr/Ticket Approved:/, body => qr/approved by \U$admin\E.*notes: Resources exist to be consumed/s }, { from => qr/RT System/, to => 'minion@company.com', subject => qr/Ticket Approved:/, body => qr/approved by \U$admin\E.*notes: Resources exist to be consumed/s }, { from => qr/\U$admin/, to => 'root@localhost', subject => qr/Ticket Approved:/, body => qr/The ticket has been approved, you may now start to act on it/, }; } # now we test the web my $approval_link = $baseurl . '/Approvals/'; $t = RT::Ticket->new($RT::SystemUser); ( $tid, $ttrans, $tmsg ) = $t->Create( Subject => "first approval", Owner => "root", Requestor => 'minion', Queue => $q->Id, ); ok( $tid, $tmsg ); my $first_ticket = RT::Ticket->new( $RT::SystemUser ); $first_ticket->Load( $tid ); my $first_approval = $first_ticket->DependsOn->First->TargetObj; $t = RT::Ticket->new($RT::SystemUser); ( $tid, $ttrans, $tmsg ) = $t->Create( Subject => "second approval", Owner => "root", Requestor => 'minion', Queue => $q->Id, ); my $second_ticket = RT::Ticket->new( $RT::SystemUser ); $second_ticket->Load( $tid ); my $second_approval = $second_ticket->DependsOn->First->TargetObj; ok( $m->login( 'cto', 'password' ), 'logged in as coo' ); my $m_coo = RT::Test::Web->new; ok( $m_coo->login( 'coo', 'password' ), 'logged in as coo' ); my $m_ceo = RT::Test::Web->new; ok( $m_ceo->login( 'ceo', 'password' ), 'logged in as coo' ); $m->get_ok( $approval_link ); $m_coo->get_ok( $approval_link ); $m_ceo->get_ok( $approval_link ); $m->content_contains('first approval', 'cto: see both approvals' ); $m->content_contains('second approval', 'cto: see both approvals' ); $m_coo->content_contains('first approval', 'coo: see both approvals'); $m_coo->content_contains('second approval', 'coo: see both approvals'); $m_ceo->content_contains('first approval', 'ceo: see both approvals'); $m_ceo->content_contains('second approval', 'ceo: see both approvals'); # now let's approve the first one via cto $m->submit_form( form_name => 'Approvals', fields => { 'Approval-' . $first_approval->id . '-Action' => 'approve', }, ); $m->content_lacks( 'first approval', 'cto: first approval is gone' ); $m->content_contains( 'second approval', 'cto: second approval is still here' ); $m_coo->get_ok( $approval_link ); $m_ceo->get_ok( $approval_link ); $m_coo->content_lacks( 'first approval', 'coo: first approval is gone' ); $m_coo->content_contains( 'second approval', 'coo: second approval is still here' ); $m_ceo->content_lacks( 'first approval', 'ceo: first approval is gone' ); $m_ceo->content_contains( 'second approval', 'ceo: second approval is still here' ); $m_coo->submit_form( form_name => 'Approvals', fields => { 'Approval-' . $second_approval->id . '-Action' => 'approve', }, ); $m->get_ok( $approval_link ); $m_ceo->get_ok( $approval_link ); $m->content_lacks( 'second approval', 'cto: second approval is gone too' ); $m_coo->content_lacks( 'second approval', 'coo: second approval is gone too' ); $m_ceo->content_lacks( 'second approval', 'ceo: second approval is gone too' ); RT::Test->clean_caught_mails; done_testing; rt-5.0.1/t/rest2/transaction-customfields.t000644 000765 000024 00000013054 14005011336 021517 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; use Test::Deep; my $mech = RT::Test::REST2->mech; my ( $baseurl, $m ) = RT::Test->started_ok; diag "Started server at $baseurl"; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $admin = RT::Test::REST2->user; $admin->PrincipalObj->GrantRight( Right => 'SuperUser' ); my $queue = RT::Test->load_or_create_queue( Name => "General" ); my( $id, $msg); my $cf = RT::CustomField->new( RT->SystemUser ); my $cfid; ($cfid, $msg) = $cf->Create(Name => 'TxnCF', Type => 'FreeformSingle', MaxValues => '0', LookupType => RT::Transaction->CustomFieldLookupType ); ok($cfid,$msg); ($id,$msg) = $cf->AddToObject($queue); ok($id,$msg); my $ticket = RT::Ticket->new(RT->SystemUser); my ( $ticket1_id, $transid ); ($ticket1_id, $transid, $msg) = $ticket->Create(Queue => $queue->id, Subject => 'TxnCF test',); ok( $ticket1_id, $msg ); my $res = $mech->get("$rest_base_path/ticket/$ticket1_id", 'Authorization' => $auth); is( $res->code, 200, 'Fetched ticket via REST2 API'); { my $payload = { Content => "reply one", ContentType => "text/plain", TxnCustomFields => { "TxnCF" => "txncf value one"}, }; my $res = $mech->post_json("$rest_base_path/ticket/$ticket1_id/correspond", $payload, 'Authorization' => $auth); is( $res->code, 201, 'correspond response code is 201'); is_deeply( $mech->json_response, [ "Correspondence added", "Custom fields updated" ], 'message is "Correspondence Added"'); my $ticket = RT::Ticket->new(RT->SystemUser); my ( $ret, $msg ) = $ticket->Load( $ticket1_id ); ok( $ret, $msg ); my $txns = $ticket->Transactions; $txns->Limit( FIELD => 'Type', VALUE => 'Correspond' ); my $txn = $txns->Last; ok( $txn->Id, "Found Correspond transaction" ); is( $txn->FirstCustomFieldValue('TxnCF'), "txncf value one", 'Found transaction custom field'); } { my @warnings; local $SIG{__WARN__} = sub { push @warnings, @_; }; my $payload = { Content => "reply two", ContentType => "text/plain", TxnCustomFields => { "not a real CF name" => "txncf value"}, }; my $res = $mech->post_json("$rest_base_path/ticket/$ticket1_id/correspond", $payload, 'Authorization' => $auth); is( scalar @warnings, 1, 'Got one warning' ); like( $warnings[0], qr/Unable to load transaction custom field: not a real CF name/, 'Got the unable to load warning' ); is( $res->code, 201, 'Correspond response code is 201 because correspond succeeded'); is_deeply( $mech->json_response, [ "Correspondence added", "Unable to load transaction custom field: not a real CF name" ], 'Bogus cf name'); } # Test as a user. my $user = RT::Test::REST2->user; $user->PrincipalObj->GrantRight( Right => 'CreateTicket' ); $user->PrincipalObj->GrantRight( Right => 'ModifyTicket' ); $user->PrincipalObj->GrantRight( Right => 'ReplyToTicket' ); $user->PrincipalObj->GrantRight( Right => 'SeeQueue' ); $user->PrincipalObj->GrantRight( Right => 'ShowTicket' ); $user->PrincipalObj->GrantRight( Right => 'ShowTicketComments' ); $user->PrincipalObj->GrantRight( Right => 'SeeCustomField' ); $user->PrincipalObj->GrantRight( Right => 'ModifyCustomField' ); my ($ticket_url, $ticket_id); { my $payload = { Subject => 'Ticket for CF test', Queue => 'General', Content => 'Ticket for CF test content', }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($ticket_url = $res->header('location')); ok(($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); # We need the hypermedia URLs... $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $payload = { Subject => 'Add Txn with CF', Content => 'Content', ContentType => 'text/plain', 'TxnCustomFields' => { 'TxnCF' => 'Txn CustomField', }, }; $res = $mech->post_json($mech->url_for_hypermedia('correspond'), $payload, 'Authorization' => $auth, ); is($res->code, 201); my $response = $mech->json_response; my $response_value = bag( re(qr/Correspondence added|Message added/), 'Custom fields updated', ); cmp_deeply($mech->json_response, $response_value, 'Response containts correct strings'); } # Look for the Transaction with our CustomField set. { my $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $res = $mech->get($mech->url_for_hypermedia('history'), 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 3); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 3); is(scalar @{$content->{items}}, 3); # Check the correspond txn (0 = create, 1 = correspond) my $txn = @{ $content->{items} }[1]; $res = $mech->get($txn->{_url}, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; like($content->{Data}, qr/^Add Txn with CF/); cmp_deeply( $content->{CustomFields}, [ { 'values' => ['Txn CustomField'], 'type' => 'customfield', 'id' => $cfid, '_url' => ignore(), 'name' => 'TxnCF', } ], 'Txn is set' ); } done_testing(); rt-5.0.1/t/rest2/transactions.t000644 000765 000024 00000011266 14005011336 017206 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; $user->PrincipalObj->GrantRight( Right => 'CreateTicket' ); $user->PrincipalObj->GrantRight( Right => 'ModifyTicket' ); $user->PrincipalObj->GrantRight( Right => 'ShowTicket' ); $user->PrincipalObj->GrantRight( Right => 'ShowTicketComments' ); my $ticket = RT::Ticket->new($user); $ticket->Create(Queue => 'General', Subject => 'hello world'); ok($ticket->Id, 'got an id'); my ($ok, $msg) = $ticket->SetPriority(42); ok($ok, $msg); ($ok, $msg) = $ticket->SetSubject('new subject'); ok($ok, $msg); ($ok, $msg) = $ticket->SetPriority(43); ok($ok, $msg); ($ok, $msg) = $ticket->Comment(Content => "hello world", TimeTaken => 50); ok($ok, $msg); # search transactions for a specific ticket my ($create_txn_url, $create_txn_id); my ($comment_txn_url, $comment_txn_id); { my $res = $mech->post_json("$rest_base_path/transactions", [ { field => 'ObjectType', value => 'RT::Ticket' }, { field => 'ObjectId', value => $ticket->Id }, ], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 5); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 5); is(scalar @{$content->{items}}, 5); my ($create, $priority1, $subject, $priority2, $comment) = @{ $content->{items} }; is($create->{type}, 'transaction'); is($priority1->{type}, 'transaction'); is($subject->{type}, 'transaction'); is($priority2->{type}, 'transaction'); is($comment->{type}, 'transaction'); $create_txn_url = $create->{_url}; ok(($create_txn_id) = $create_txn_url =~ qr[/transaction/(\d+)]); $comment_txn_url = $comment->{_url}; ok(($comment_txn_id) = $comment_txn_url =~ qr[/transaction/(\d+)]); } # Transaction display { my $res = $mech->get($create_txn_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $create_txn_id); is($content->{Type}, 'Create'); is($content->{TimeTaken}, 0); ok(exists $content->{$_}) for qw(Created); my $links = $content->{_hyperlinks}; is(scalar(@$links), 1); is($links->[0]{ref}, 'self'); is($links->[0]{id}, $create_txn_id); is($links->[0]{type}, 'transaction'); is($links->[0]{_url}, $create_txn_url); my $creator = $content->{Creator}; is($creator->{id}, 'test'); is($creator->{type}, 'user'); like($creator->{_url}, qr{$rest_base_path/user/test$}); my $object = $content->{Object}; is($object->{id}, $ticket->Id); is($object->{type}, 'ticket'); like($object->{_url}, qr{$rest_base_path/ticket/@{[$ticket->Id]}$}); } # (invalid) update { my $res = $mech->put_json($create_txn_url, { Type => 'Set' }, 'Authorization' => $auth, ); is($res->code, 405); is($mech->json_response->{message}, 'Method Not Allowed'); $res = $mech->get($create_txn_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{Type}, 'Create'); } # (invalid) delete { my $res = $mech->delete($create_txn_url, 'Authorization' => $auth, ); is($res->code, 405); is($mech->json_response->{message}, 'Method Not Allowed'); } # (invalid) create { my $res = $mech->post_json("$rest_base_path/transaction", { Type => 'Create' }, 'Authorization' => $auth, ); is($res->code, 405); is($mech->json_response->{message}, 'Method Not Allowed'); } # Comment transaction { my $res = $mech->get($comment_txn_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $comment_txn_id); is($content->{Type}, 'Comment'); is($content->{TimeTaken}, 50); ok(exists $content->{$_}) for qw(Created); my $links = $content->{_hyperlinks}; is(scalar @$links, 2); is($links->[0]{ref}, 'self'); is($links->[0]{id}, $comment_txn_id); is($links->[0]{type}, 'transaction'); is($links->[0]{_url}, $comment_txn_url); is($links->[1]{ref}, 'attachment'); like($links->[1]{_url}, qr{$rest_base_path/attachment/\d+$}); my $creator = $content->{Creator}; is($creator->{id}, 'test'); is($creator->{type}, 'user'); like($creator->{_url}, qr{$rest_base_path/user/test$}); my $object = $content->{Object}; is($object->{id}, $ticket->Id); is($object->{type}, 'ticket'); like($object->{_url}, qr{$rest_base_path/ticket/@{[$ticket->Id]}$}); } done_testing; rt-5.0.1/t/rest2/group-members.t000644 000765 000024 00000023147 14005011336 017263 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; use Test::Warn; use Test::Deep; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; my $group1 = RT::Group->new(RT->SystemUser); my ($ok, $msg) = $group1->CreateUserDefinedGroup(Name => 'Group 1'); ok($ok, $msg); my $user1 = RT::User->new(RT->SystemUser); ($ok, $msg) = $user1->Create(Name => 'User 1'); ok($ok, $msg); my $user2 = RT::User->new(RT->SystemUser); ($ok, $msg) = $user2->Create(Name => 'User 2'); ok($ok, $msg); # Group creation my ($group2_url, $group2_id); { my $payload = { Name => 'Group 2', }; # Rights Test - No AdminGroup my $res = $mech->post_json("$rest_base_path/group", $payload, 'Authorization' => $auth, ); is($res->code, 403, 'Cannot create group without AdminGroup right'); # Rights Test - With AdminGroup $user->PrincipalObj->GrantRight(Right => 'AdminGroup'); $res = $mech->post_json("$rest_base_path/group", $payload, 'Authorization' => $auth, ); is($res->code, 201, 'Create group with AdminGroup right'); ok($group2_url = $res->header('location'), 'Created group url'); ok(($group2_id) = $group2_url =~ qr[/group/(\d+)], 'Created group id'); } my $group2 = RT::Group->new(RT->SystemUser); $group2->Load($group2_id); # Group disabling { # Rights Test - No AdminGroup $user->PrincipalObj->RevokeRight(Right => 'AdminGroup'); my $res = $mech->delete($group2_url, 'Authorization' => $auth, ); is($res->code, 403, 'Cannot disable group without AdminGroup right'); # Rights Test - With AdminGroup, no SeeGroup $user->PrincipalObj->GrantRight(Right => 'AdminGroup', Object => $group2); $res = $mech->delete($group2_url, 'Authorization' => $auth, ); is($res->code, 403, 'Cannot disable group without SeeGroup right'); # Rights Test - With AdminGroup, no SeeGroup $user->PrincipalObj->GrantRight(Right => 'SeeGroup', Object => $group2); $res = $mech->delete($group2_url, 'Authorization' => $auth, ); is($res->code, 204, 'Disable group with AdminGroup & SeeGroup rights'); is($group2->Disabled, 1, "Group disabled"); } # Group enabling { my $payload = { Disabled => 0, }; # Rights Test - No AdminGroup $user->PrincipalObj->RevokeRight(Right => 'AdminGroup', Object => $group2); $user->PrincipalObj->RevokeRight(Right => 'SeeGroup', Object => $group2); my $res = $mech->put_json($group2_url, $payload, 'Authorization' => $auth); is($res->code, 403, 'Cannot enable group without AdminGroup right'); # Rights Test - With AdminGroup, no SeeGroup $user->PrincipalObj->GrantRight(Right => 'AdminGroup', Object => $group2); $res = $mech->put_json($group2_url, $payload, 'Authorization' => $auth); is($res->code, 403, 'Cannot enable group without SeeGroup right'); # Rights Test - With AdminGroup, no SeeGroup $user->PrincipalObj->GrantRight(Right => 'SeeGroup', Object => $group2); $res = $mech->put_json($group2_url, $payload, 'Authorization' => $auth); is($res->code, 200, 'Enable group with AdminGroup & SeeGroup rights'); is_deeply($mech->json_response, ['Group enabled']); is($group2->Disabled, 0, "Group enabled"); } my $group1_id = $group1->id; (my $group1_url = $group2_url) =~ s/$group2_id/$group1_id/; $user->PrincipalObj->GrantRight(Right => 'SeeGroup', Object => $group1); # Members addition { my $payload = [ $user1->id, $group2->id, $user1->id + 666, ]; # Rights Test - No AdminGroupMembership my $res = $mech->put_json($group1_url . '/members', $payload, 'Authorization' => $auth, ); is($res->code, 403, 'Cannot add members to group without AdminGroupMembership right'); # Rights Test - With AdminGroupMembership $user->PrincipalObj->GrantRight(Right => 'AdminGroupMembership', Object => $group1); $res = $mech->put_json($group1_url . '/members', $payload, 'Authorization' => $auth, ); is($res->code, 200, 'Add members to group with AdminGroupMembership right'); is_deeply($mech->json_response, [ "Member added: " . $user1->Name, "Member added: " . $group2->Name, "Couldn't find that principal", ], 'Two members added, bad principal rejected'); my $members1 = $group1->MembersObj; is($members1->Count, 2, 'Two members added'); my $member = $members1->Next; is($member->MemberObj->PrincipalType, 'User', 'User added as member'); is($member->MemberObj->id, $user1->id, 'Accurate user added as member'); $member = $members1->Next; is($member->MemberObj->PrincipalType, 'Group', 'Group added as member'); is($member->MemberObj->id, $group2->id, 'Accurate group added as member'); } # Members list { # Add user to subgroup $group2->AddMember($user2->id); # Direct members my $res = $mech->get($group1_url . '/members', 'Authorization' => $auth, ); is($res->code, 200, 'List direct members'); my $content = $mech->json_response; is($content->{total}, 2, 'Two direct members'); is($content->{items}->[0]->{type}, 'user', 'User member'); is($content->{items}->[0]->{id}, $user1->id, 'Accurate user member'); is($content->{items}->[1]->{type}, 'group', 'Group member'); is($content->{items}->[1]->{id}, $group2->id, 'Accurate group member'); # Deep members $res = $mech->get($group1_url . '/members?recursively=1', 'Authorization' => $auth, ); is($res->code, 200, 'List deep members'); $content = $mech->json_response; is($content->{total}, 4, 'Four deep members'); cmp_deeply( $content->{items}, bag({ type => 'group', id => $group1->id, _url => re(qr{$rest_base_path/group/@{[$group1->id]}}), }, { type => 'group', id => $group2->id, _url => re(qr{$rest_base_path/group/@{[$group2->id]}}), }, { type => 'user', id => $user1->id, _url => re(qr{$rest_base_path/user/@{[$user1->id]}}), }, { type => 'user', id => $user2->id, _url => re(qr{$rest_base_path/user/@{[$user2->id]}}), } ), 'Four deep member items' ); # Direct user members $res = $mech->get($group1_url . '/members?groups=0', 'Authorization' => $auth, ); is($res->code, 200, 'List direct user members'); $content = $mech->json_response; is($content->{total}, 1, 'One direct user member'); is($content->{items}->[0]->{type}, 'user', 'Direct user member'); is($content->{items}->[0]->{id}, $user1->id, 'Accurate direct user member'); # Recursive user members $res = $mech->get($group1_url . '/members?groups=0&recursively=1', 'Authorization' => $auth, ); is($res->code, 200, 'List recursive user members'); $content = $mech->json_response; is($content->{total}, 2, 'Two recursive user members'); is($content->{items}->[0]->{type}, 'user', 'First recursive user member'); is($content->{items}->[0]->{id}, $user1->id, 'First accurate recursive user member'); is($content->{items}->[1]->{type}, 'user', 'Second recursive user member'); is($content->{items}->[1]->{id}, $user2->id, 'Second accurate recursive user member'); # Direct group members $res = $mech->get($group1_url . '/members?users=0', 'Authorization' => $auth, ); is($res->code, 200, 'List direct group members'); $content = $mech->json_response; is($content->{total}, 1, 'One direct group member'); is($content->{items}->[0]->{type}, 'group', 'Direct group member'); is($content->{items}->[0]->{id}, $group2->id, 'Accurate direct group member'); # Recursive group members $res = $mech->get($group1_url . '/members?users=0&recursively=1', 'Authorization' => $auth, ); is($res->code, 200, 'List recursive group members'); $content = $mech->json_response; is($content->{total}, 2, 'Two recursive group members'); is($content->{items}->[0]->{type}, 'group', 'First recursive group member'); is($content->{items}->[0]->{id}, $group1->id, 'First accurate recursive group member'); is($content->{items}->[1]->{type}, 'group', 'Second recursive group member'); is($content->{items}->[1]->{id}, $group2->id, 'Second accurate recursive group member'); } # Members removal { my $res = $mech->delete($group1_url . '/member/' . $user1->id, 'Authorization' => $auth, ); is($res->code, 204, 'Remove member'); my $members1 = $group1->MembersObj; is($members1->Count, 1, 'One member removed'); my $member = $members1->Next; is($member->MemberObj->PrincipalType, 'Group', 'Group remaining member'); is($member->MemberObj->id, $group2->id, 'Accurate remaining member'); } # All members removal { my $res = $mech->delete($group1_url . '/members', 'Authorization' => $auth, ); is($res->code, 204, 'Remove all members'); my $members1 = $group1->MembersObj; is($members1->Count, 0, 'All members removed'); } # Group hypermedia links { my $res = $mech->get("$rest_base_path/group/$group1_id", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; my $links = $content->{_hyperlinks}; my @members_links = grep { $_->{ref} =~ /members/ } @$links; is(scalar(@members_links), 1); like($members_links[0]->{_url}, qr{$rest_base_path/group/$group1_id/members$}); } done_testing; rt-5.0.1/t/rest2/cf-image.t000644 000765 000024 00000004332 14005011336 016142 0ustar00sunnavystaff000000 000000 use strict; use warnings; use lib 't/lib'; use RT::Test::REST2 tests => undef; use Test::Deep; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; my $image_name = 'image.png'; my $image_path = RT::Test::get_relocatable_file($image_name, 'data'); my $image_content; open my $fh, '<', $image_path or die "Cannot read $image_path: $!\n"; { local $/; $image_content = <$fh>; } close $fh; my $image_cf = RT::CustomField->new(RT->SystemUser); $image_cf->Create(LookupType => 'RT::Queue-RT::Ticket', Name => 'Image CF', Type => 'Image', MaxValues => 1, Queue => 'General'); my $freeform_cf = RT::CustomField->new(RT->SystemUser); $freeform_cf->Create(LookupType => 'RT::Queue-RT::Ticket', Name => 'Text CF', Type => 'Freeform', MaxValues => 1, Queue => 'General'); my $ticket = RT::Ticket->new(RT->SystemUser); $ticket->Create(Queue => 'General', Subject => 'Test ticket with image cf', "CustomField-" . $freeform_cf->id => 'hello world'); $ticket->AddCustomFieldValue(Field => $image_cf->id, Value => 'image.png', ContentType => 'image/png', LargeContent => $image_content); my $image_ocfv = $ticket->CustomFieldValues('Image CF')->First; my $text_ocfv = $ticket->CustomFieldValues('Text CF')->First; # Rights Test - No SeeCustomField { my $res = $mech->get("$rest_base_path/download/cf/" . $image_ocfv->id, 'Authorization' => $auth, ); is($res->code, 403); } $user->PrincipalObj->GrantRight( Right => 'SeeCustomField' ); # Try undef ObjectCustomFieldValue { my $res = $mech->get("$rest_base_path/download/cf/666", 'Authorization' => $auth, ); is($res->code, 404); } # Download cf text { my $res = $mech->get("$rest_base_path/download/cf/" . $text_ocfv->id, 'Authorization' => $auth, ); is($res->code, 400); is($mech->json_response->{message}, 'Only Image and Binary CustomFields can be downloaded'); } # Download cf image { $user->PrincipalObj->GrantRight( Right => 'SeeCustomField' ); my $res = $mech->get("$rest_base_path/download/cf/" . $image_ocfv->id, 'Authorization' => $auth, ); is($res->code, 200); is($res->content, $image_content); } done_testing; rt-5.0.1/t/rest2/local-custom-fields.t000644 000765 000024 00000005646 14005011336 020351 0ustar00sunnavystaff000000 000000 use strict; use warnings; use lib 't/lib'; use RT::Test::REST2 tests => undef; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; $user->PrincipalObj->GrantRight( Right => 'SeeCustomField' ); my $queue = RT::Queue->new(RT->SystemUser); $queue->Load('General'); my $queue_id = $queue->id; my $attached_single_cf = RT::CustomField->new(RT->SystemUser); $attached_single_cf->Create(LookupType => 'RT::Queue-RT::Ticket', Name => 'Freeform CF', Type => 'Freeform', MaxValues => 1, Queue => 'General'); my $attached_single_cf_id = $attached_single_cf->id; my $attached_multiple_cf = RT::CustomField->new(RT->SystemUser); $attached_multiple_cf->Create(LookupType => 'RT::Queue-RT::Ticket', Name => 'Freeform CF', Type => 'Freeform', MaxValues => 0, Queue => 'General'); my $attached_multiple_cf_id = $attached_multiple_cf->id; my $detached_cf = RT::CustomField->new(RT->SystemUser); $detached_cf->Create(LookupType => 'RT::Queue-RT::Ticket', Name => 'Freeform CF', Type => 'Freeform', MaxValues => 1); my $detached_cf_id = $detached_cf->id; my $queue_cf = RT::CustomField->new(RT->SystemUser); $queue_cf->Create(LookupType => 'RT::Queue', Name => 'Freeform CF', Type => 'Freeform', MaxValues => 1); $queue_cf->AddToObject($queue); my $queue_cf_id = $queue_cf->id; # All tickets customfields { my $res = $mech->post_json("$rest_base_path/customfields", [{field => 'LookupType', value => 'RT::Queue-RT::Ticket'}], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{total}, 3); is($content->{count}, 3); is(scalar @{$content->{items}}, 3); my @ids = sort map {$_->{id}} @{$content->{items}}; is_deeply(\@ids, [$attached_single_cf_id, $attached_multiple_cf_id, $detached_cf_id]); } # All tickets single customfields attached to queue 'General' { my $res = $mech->post_json("$rest_base_path/queue/$queue_id/customfields", [ {field => 'LookupType', value => 'RT::Queue-RT::Ticket'}, {field => 'MaxValues', value => 1}, ], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{total}, 1); is($content->{count}, 1); is(scalar @{$content->{items}}, 1); is($content->{items}->[0]->{id}, $attached_single_cf_id); } # All single customfields attached to queue 'General' { my $res = $mech->post_json("$rest_base_path/queue/$queue_id/customfields", [ {field => 'MaxValues', value => 1}, ], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{total}, 2); is($content->{count}, 2); is(scalar @{$content->{items}}, 2); my @ids = sort map {$_->{id}} @{$content->{items}}; is_deeply(\@ids, [$attached_single_cf_id, $queue_cf_id]); } done_testing; rt-5.0.1/t/rest2/pagination.t000644 000765 000024 00000010633 14005011336 016624 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; my $alpha = RT::Test->load_or_create_queue( Name => 'Alpha' ); my $bravo = RT::Test->load_or_create_queue( Name => 'Bravo' ); $user->PrincipalObj->GrantRight( Right => 'SuperUser' ); my $alpha_id = $alpha->Id; my $bravo_id = $bravo->Id; # Default per_page (20), only 1 page. { my $res = $mech->post_json("$rest_base_path/queues/all", [], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 3); is($content->{page}, 1); is($content->{pages}, 1); is($content->{per_page}, 20); is($content->{total}, 3); undef($content->{prev_page}); undef($content->{next_page}); is(scalar @{$content->{items}}, 3); } # per_page = 3, only 1 page. { my $res = $mech->post_json("$rest_base_path/queues/all?per_page=3", [], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 3); is($content->{page}, 1); is($content->{pages}, 1); is($content->{per_page}, 3); is($content->{total}, 3); undef($content->{prev_page}); undef($content->{next_page}); is(scalar @{$content->{items}}, 3); } # per_page = 1, 3 pages, page 1. { my $url = "$rest_base_path/queues/all?per_page=1"; my $res = $mech->post_json($url, [], 'Authorization' => $auth, ); is($res->code, 200); # Ensure our use of $url as a regex works. $url =~ s/\?/\\?/; my $content = $mech->json_response; is($content->{count}, 1); is($content->{page}, 1); is($content->{pages}, 3); is($content->{per_page}, 1); is($content->{total}, 3); undef($content->{prev_page}); like($content->{next_page}, qr[$url&page=2]); is(scalar @{$content->{items}}, 1); } # per_page = 1, 3 pages, page 2. { my $url = "$rest_base_path/queues/all?per_page=1"; my $res = $mech->post_json("$url&page=2", [], 'Authorization' => $auth, ); is($res->code, 200); # Ensure our use of $url as a regex works. $url =~ s/\?/\\?/; my $content = $mech->json_response; is($content->{count}, 1); is($content->{page}, 2); is($content->{pages}, 3); is($content->{per_page}, 1); is($content->{total}, 3); like($content->{prev_page}, qr[$url&page=1]); like($content->{next_page}, qr[$url&page=3]); is(scalar @{$content->{items}}, 1); } # per_page = 1, 3 pages, page 3. { my $url = "$rest_base_path/queues/all?per_page=1"; my $res = $mech->post_json("$url&page=3", [], 'Authorization' => $auth, ); is($res->code, 200); # Ensure our use of $url as a regex works. $url =~ s/\?/\\?/; my $content = $mech->json_response; is($content->{count}, 1); is($content->{page}, 3); is($content->{pages}, 3); is($content->{per_page}, 1); is($content->{total}, 3); like($content->{prev_page}, qr[$url&page=2]); undef($content->{next_page}); is(scalar @{$content->{items}}, 1); } # Test sanity checking for the pagination parameters. { my $url = "$rest_base_path/queues/all"; for my $param ( 'per_page', 'page' ) { for my $value ( 'abc', '-10', '30' ) { # No need to test the following combination. next if $param eq 'per_page' && $value eq '30'; my $res = $mech->post_json("$url?$param=$value", [], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; if ($param eq 'page') { if ($value eq '30') { is($content->{count}, 0); is($content->{page}, 30); is(scalar @{$content->{items}}, 0); like($content->{prev_page}, qr[$url\?page=1]); } else { is($content->{count}, 3); is($content->{page}, 1); is(scalar @{$content->{items}}, 3); is($content->{prev_page}, undef); } } is($content->{pages}, 1); if ($param eq 'per_page') { if ($value eq '30') { is($content->{per_page}, 30); } else { is($content->{per_page}, 20); } } is($content->{total}, 3); is($content->{next_page}, undef); } } } done_testing; rt-5.0.1/t/rest2/organization.t000644 000765 000024 00000005347 14005011336 017205 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; use_ok('RT::REST2::Util', qw(expand_uid)); diag "Test expand_uid with default RT Organization of example.com"; { my $base_url = RT::REST2->base_uri; my $uid_parts = expand_uid('RT::User-test'); is($uid_parts->{'type'}, 'user', 'Got correct class'); is($uid_parts->{'id'}, 'test', 'Got correct id'); is($uid_parts->{'_url'}, $base_url . '/user/test', 'Got correct url'); # User with dashes in the username $uid_parts = expand_uid('RT::User-test-user'); is($uid_parts->{'type'}, 'user', 'Got correct class'); is($uid_parts->{'id'}, 'test-user', 'Got correct id'); is($uid_parts->{'_url'}, $base_url . '/user/test-user', 'Got correct url'); $uid_parts = expand_uid('RT::CustomField-example.com-3'); is($uid_parts->{'type'}, 'customfield', 'Got correct class'); is($uid_parts->{'id'}, '3', 'Got correct id'); is($uid_parts->{'_url'}, $base_url . '/customfield/3', 'Got correct url'); } RT->Config->Set('Organization', 'name-with-dashes'); diag "Test expand_uid with Organization name with dashes"; { my $base_url = RT::REST2->base_uri; my $uid_parts = expand_uid('RT::User-test'); is($uid_parts->{'type'}, 'user', 'Got correct class'); is($uid_parts->{'id'}, 'test', 'Got correct id'); is($uid_parts->{'_url'}, $base_url . '/user/test', 'Got correct url'); # User with dashes in the username $uid_parts = expand_uid('RT::User-test-user'); is($uid_parts->{'type'}, 'user', 'Got correct class'); is($uid_parts->{'id'}, 'test-user', 'Got correct id'); is($uid_parts->{'_url'}, $base_url . '/user/test-user', 'Got correct url'); $uid_parts = expand_uid('RT::CustomField-name-with-dashes-3'); is($uid_parts->{'type'}, 'customfield', 'Got correct class'); is($uid_parts->{'id'}, '3', 'Got correct id'); is($uid_parts->{'_url'}, $base_url . '/customfield/3', 'Got correct url'); } my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; $user->PrincipalObj->GrantRight( Right => 'SuperUser' ); my $queue_url; # search Name = General { my $res = $mech->post_json("$rest_base_path/queues", [{ field => 'Name', value => 'General' }], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 1); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 1); is(scalar @{$content->{items}}, 1); my $queue = $content->{items}->[0]; is($queue->{type}, 'queue'); is($queue->{id}, 1); like($queue->{_url}, qr{$rest_base_path/queue/1$}); $queue_url = $queue->{_url}; } done_testing; rt-5.0.1/t/rest2/search-json.t000644 000765 000024 00000022541 14005011336 016710 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; my $alpha = RT::Test->load_or_create_queue( Name => 'Alpha', Description => 'Queue for test' ); my $beta = RT::Test->load_or_create_queue( Name => 'Beta', Description => 'Queue for test' ); my $bravo = RT::Test->load_or_create_queue( Name => 'Bravo', Description => 'Queue to test sorted search' ); $user->PrincipalObj->GrantRight( Right => 'SuperUser' ); my $alpha_id = $alpha->Id; my $beta_id = $beta->Id; my $bravo_id = $bravo->Id; # Name = General { my $res = $mech->post_json("$rest_base_path/queues", [{ field => 'Name', value => 'General' }], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 1); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 1); is(scalar @{$content->{items}}, 1); my $queue = $content->{items}->[0]; is($queue->{type}, 'queue'); is($queue->{id}, 1); like($queue->{_url}, qr{$rest_base_path/queue/1$}); } # Name != General { my $res = $mech->post_json("$rest_base_path/queues", [{ field => 'Name', operator => '!=', value => 'General' }], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 3); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 3); is(scalar @{$content->{items}}, 3); my ($first, $second, $third) = @{ $content->{items} }; is($first->{type}, 'queue'); is($first->{id}, $alpha_id); like($first->{_url}, qr{$rest_base_path/queue/$alpha_id$}); is($second->{type}, 'queue'); is($second->{id}, $beta_id); like($second->{_url}, qr{$rest_base_path/queue/$beta_id$}); is($third->{type}, 'queue'); is($third->{id}, $bravo_id); like($third->{_url}, qr{$rest_base_path/queue/$bravo_id$}); } # Name STARTSWITH B { my $res = $mech->post_json("$rest_base_path/queues", [{ field => 'Name', operator => 'STARTSWITH', value => 'B' }], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 2); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 2); is(scalar @{$content->{items}}, 2); my ($first, $second) = @{ $content->{items} }; is($first->{type}, 'queue'); is($first->{id}, $beta_id); like($first->{_url}, qr{$rest_base_path/queue/$beta_id$}); is($second->{type}, 'queue'); is($second->{id}, $bravo_id); like($second->{_url}, qr{$rest_base_path/queue/$bravo_id$}); } # id > 2 { my $res = $mech->post_json("$rest_base_path/queues", [{ field => 'id', operator => '>', value => 2 }], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 3); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 3); is(scalar @{$content->{items}}, 3); my ($first, $second, $third) = @{ $content->{items} }; is($first->{type}, 'queue'); is($first->{id}, $alpha_id); like($first->{_url}, qr{$rest_base_path/queue/$alpha_id$}); is($second->{type}, 'queue'); is($second->{id}, $beta_id); like($second->{_url}, qr{$rest_base_path/queue/$beta_id$}); is($third->{type}, 'queue'); is($third->{id}, $bravo_id); like($third->{_url}, qr{$rest_base_path/queue/$bravo_id$}); } # Invalid query ({ ... }) { my $res = $mech->post_json("$rest_base_path/queues", { field => 'Name', value => 'General' }, 'Authorization' => $auth, ); is($res->code, 400); my $content = $mech->json_response; TODO: { local $TODO = "better error reporting"; is($content->{message}, 'Query must be an array of objects'); } is($content->{message}, 'JSON object must be a ARRAY'); } # Sorted search { my $res = $mech->post_json("$rest_base_path/queues?orderby=Description&order=DESC&orderby=id", [{ field => 'Description', operator => 'LIKE', value => 'test' }], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 3); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 3); is(scalar @{$content->{items}}, 3); my ($first, $second, $third) = @{ $content->{items} }; is($first->{type}, 'queue'); is($first->{id}, $bravo_id); like($first->{_url}, qr{$rest_base_path/queue/$bravo_id$}); is($second->{type}, 'queue'); is($second->{id}, $alpha_id); like($second->{_url}, qr{$rest_base_path/queue/$alpha_id$}); is($third->{type}, 'queue'); is($third->{id}, $beta_id); like($third->{_url}, qr{$rest_base_path/queue/$beta_id$}); } # Aggregate conditions with OR, Queues defaults to AND { my $res = $mech->post_json("$rest_base_path/queues", [ { field => 'id', operator => '>', value => 2 }, { entry_aggregator => 'OR', field => 'id', operator => '<', value => 4 }, ], 'Authorization' => $auth, ); my $content = $mech->json_response; is($content->{count}, 4); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 4); is(scalar @{$content->{items}}, 4); my @ids = sort map {$_->{id}} @{$content->{items}}; is_deeply(\@ids, [1, $alpha_id, $beta_id, $bravo_id]); } # Aggregate conditions with AND, Queues defaults to AND { my $res = $mech->post_json("$rest_base_path/queues", [ { field => 'id', operator => '>', value => 2 }, { field => 'id', operator => '<', value => 4 }, ], 'Authorization' => $auth, ); my $content = $mech->json_response; is($content->{count}, 1); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 1); is(scalar @{$content->{items}}, 1); is($content->{items}->[0]->{id}, $alpha_id); } my $cf1 = RT::Test->load_or_create_custom_field(Name => 'cf1', Type => 'FreeformSingle', Queue => 'General'); my $cf2 = RT::Test->load_or_create_custom_field(Name => 'cf2', Type => 'FreeformSingle', Queue => 'General'); my $cf3 = RT::Test->load_or_create_custom_field(Name => 'cf3', Type => 'FreeformSingle', Queue => 'General'); # Aggregate conditions with OR, CustomFields defaults to OR { my $res = $mech->post_json("$rest_base_path/customfields", [ { field => 'id', operator => '>', value => 2 }, { field => 'id', operator => '<', value => 4 }, ], 'Authorization' => $auth, ); my $content = $mech->json_response; is($content->{count}, 4); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 4); is(scalar @{$content->{items}}, 4); my @ids = sort map {$_->{id}} @{$content->{items}}; is_deeply(\@ids, [1, 2, 3, 4]); } # Aggregate conditions with AND, CustomFields defaults to OR { my $res = $mech->post_json("$rest_base_path/customfields", [ { field => 'id', operator => '>', value => 2 }, { entry_aggregator => 'AND', field => 'id', operator => '<', value => 4 }, ], 'Authorization' => $auth, ); my $content = $mech->json_response; is($content->{count}, 1); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 1); is(scalar @{$content->{items}}, 1); is($content->{items}->[0]->{id}, 3); } # Find disabled row { $alpha->SetDisabled(1); my $res = $mech->post_json("$rest_base_path/queues", [{ field => 'id', operator => '>', value => 2 }], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 2); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 2); is(scalar @{$content->{items}}, 2); my ($first, $second) = @{ $content->{items} }; is($first->{type}, 'queue'); is($first->{id}, $beta_id); like($first->{_url}, qr{$rest_base_path/queue/$beta_id$}); is($second->{type}, 'queue'); is($second->{id}, $bravo_id); like($second->{_url}, qr{$rest_base_path/queue/$bravo_id$}); my $res_disabled = $mech->post_json("$rest_base_path/queues?find_disabled_rows=1", [{ field => 'id', operator => '>', value => 2 }], 'Authorization' => $auth, ); is($res_disabled->code, 200); my $content_disabled = $mech->json_response; is($content_disabled->{count}, 3); is($content_disabled->{page}, 1); is($content_disabled->{per_page}, 20); is($content_disabled->{total}, 3); is(scalar @{$content_disabled->{items}}, 3); my ($first_disabled, $second_disabled, $third_disabled) = @{ $content_disabled->{items} }; is($first_disabled->{type}, 'queue'); is($first_disabled->{id}, $alpha_id); like($first_disabled->{_url}, qr{$rest_base_path/queue/$alpha_id$}); is($second_disabled->{type}, 'queue'); is($second_disabled->{id}, $beta_id); like($second_disabled->{_url}, qr{$rest_base_path/queue/$beta_id$}); is($third_disabled->{type}, 'queue'); is($third_disabled->{id}, $bravo_id); like($third_disabled->{_url}, qr{$rest_base_path/queue/$bravo_id$}); } done_testing; rt-5.0.1/t/rest2/user-customfields.t000644 000765 000024 00000021076 14005011336 020153 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; use Test::Deep; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; my $single_cf = RT::CustomField->new( RT->SystemUser ); my ($ok, $msg) = $single_cf->Create( Name => 'Freeform', Type => 'FreeformSingle', LookupType => RT::User->CustomFieldLookupType ); ok($ok, $msg); my $single_cf_id = $single_cf->Id(); my $ocf = RT::ObjectCustomField->new( RT->SystemUser ); ( $ok, $msg ) = $ocf->Add( CustomField => $single_cf->id, ObjectId => 0 ); ok($ok, "Applied globally" ); my $multi_cf = RT::CustomField->new( RT->SystemUser ); ($ok, $msg) = $multi_cf->Create( Name => 'Multi', Type => 'FreeformMultiple', LookupType => RT::User->CustomFieldLookupType ); ok($ok, $msg); my $multi_cf_id = $multi_cf->Id(); $ocf = RT::ObjectCustomField->new( RT->SystemUser ); ( $ok, $msg ) = $ocf->Add( CustomField => $multi_cf->id, ObjectId => 0 ); ok($ok, "Applied globally" ); # User Creation with no AdminUsers my ($user_url, $user_id); { my $payload = { Name => 'user1', EmailAddress => 'user1@example.com', CustomFields => { $single_cf->Id => 'Hello world!', }, }; # Rights Test - No AdminUsers my $res = $mech->post_json("$rest_base_path/user", $payload, 'Authorization' => $auth, ); TODO: { local $TODO = "this should return 403"; is($res->code, 403); } # Rights Test - With AdminUsers $user->PrincipalObj->GrantRight( Right => 'AdminUsers' ); $res = $mech->post_json("$rest_base_path/user", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($user_url = $res->header('location')); ok(($user_id) = $user_url =~ qr[/user/(\d+)]); } # Rights Test - no SeeCustomField { my $res = $mech->get($user_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $user_id); is($content->{EmailAddress}, 'user1@example.com'); is($content->{Name}, 'user1'); is_deeply($content->{'CustomFields'}, [], 'User custom field not present'); # can fetch user by name too $res = $mech->get("$rest_base_path/user/user1", 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, $content, 'requesting user by name is same as user by id'); } my $no_user_cf_values = bag( { name => 'Freeform', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); # Rights Test - With SeeCustomField { $user->PrincipalObj->GrantRight( Right => 'SeeCustomField'); my $res = $mech->get($user_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $user_id); is($content->{EmailAddress}, 'user1@example.com'); is($content->{Name}, 'user1'); cmp_deeply($content->{'CustomFields'}, $no_user_cf_values, 'User custom field not present'); } # User Update without ModifyCustomField { my $payload = { Name => 'User1', CustomFields => { $single_cf->Id => 'Modified CF', }, }; my $res = $mech->put_json($user_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ["User User1: Name changed from 'user1' to 'User1'", 'Could not add new custom field value: Permission Denied']); $res = $mech->get($user_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $user_id); is($content->{EmailAddress}, 'user1@example.com'); is($content->{Name}, 'User1'); cmp_deeply($content->{'CustomFields'}, $no_user_cf_values, 'User custom field not present'); } # User Update with ModifyCustomField { $user->PrincipalObj->GrantRight( Right => 'ModifyCustomField' ); my $payload = { EmailAddress => 'user1+rt@example.com', CustomFields => { $single_cf_id => 'Modified CF', }, }; my $res = $mech->put_json($user_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ["User User1: EmailAddress changed from 'user1\@example.com' to 'user1+rt\@example.com'", 'Freeform Modified CF added']); $res = $mech->get($user_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{EmailAddress}, 'user1+rt@example.com'); my $set_user_cf_values = bag( { name => 'Freeform', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Modified CF'] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); cmp_deeply($content->{'CustomFields'}, $set_user_cf_values, 'New CF value'); # make sure changing the CF doesn't add a second OCFV $payload->{CustomFields}{$single_cf->Id} = 'Modified Again'; $res = $mech->put_json($user_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ['Freeform Modified CF changed to Modified Again']); $res = $mech->get($user_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; $set_user_cf_values = bag( { name => 'Freeform', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Modified Again'] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); cmp_deeply($content->{'CustomFields'}, $set_user_cf_values, 'New CF value'); # stop changing the CF, change something else, make sure CF sticks around delete $payload->{CustomFields}{$single_cf->Id}; $payload->{EmailAddress} = 'user1+rt.test@example.com'; $res = $mech->put_json($user_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ["User User1: EmailAddress changed from 'user1+rt\@example.com' to 'user1+rt.test\@example.com'"]); $res = $mech->get($user_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; cmp_deeply($content->{'CustomFields'}, $set_user_cf_values, 'Same CF value'); } # User Creation with ModifyCustomField { my $payload = { Name => 'user2', CustomFields => { $single_cf->Id => 'Hello world!', }, }; my $res = $mech->post_json("$rest_base_path/user", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($user_url = $res->header('location')); ok(($user_id) = $user_url =~ qr[/user/(\d+)]); } # Rights Test - With SeeCustomField { my $res = $mech->get($user_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $user_id); is($content->{Name}, 'user2'); my $set_user_cf_values = bag( { name => 'Freeform', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Hello world!'] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); cmp_deeply($content->{'CustomFields'}, $set_user_cf_values, 'User custom field'); } # User Creation for multi-value CF for my $value ( 'scalar', ['array reference'], ['multiple', 'values'], ) { my $payload = { Name => "user-$value", CustomFields => { $multi_cf->Id => $value, }, }; my $res = $mech->post_json("$rest_base_path/user", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($user_url = $res->header('location')); ok(($user_id) = $user_url =~ qr[/user/(\d+)]); $res = $mech->get($user_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $user_id); my $output = ref($value) ? $value : [$value]; # scalar input comes out as array reference my $set_user_cf_values = bag( { name => 'Freeform', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => $output }, ); cmp_deeply($content->{'CustomFields'}, $set_user_cf_values, 'User custom field'); } done_testing; rt-5.0.1/t/rest2/queues.t000644 000765 000024 00000024654 14005011336 016012 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; $user->PrincipalObj->GrantRight( Right => 'SuperUser' ); my $queue_obj = RT::Test->load_or_create_queue( Name => "General", CorrespondAddress => 'general@example.com', CommentAddress => 'comment@example.com', ); my $single_cf = RT::CustomField->new( RT->SystemUser ); my ($ok, $msg) = $single_cf->Create( Name => 'Single', Type => 'FreeformSingle', LookupType => RT::Queue->CustomFieldLookupType ); ok($ok, $msg); ($ok, $msg) = $single_cf->AddToObject($queue_obj); ok($ok, $msg); my $single_cf_id = $single_cf->Id; my $single_ticket_cf = RT::CustomField->new( RT->SystemUser ); ($ok, $msg) = $single_ticket_cf->Create( Name => 'SingleTicket', Type => 'FreeformSingle', Queue => $queue_obj->Id ); ok($ok, $msg); my $single_ticket_cf_id = $single_ticket_cf->Id; my $single_txn_cf = RT::CustomField->new( RT->SystemUser ); ($ok, $msg) = $single_txn_cf->Create( Name => 'SingleTxn', Type => 'FreeformSingle', LookupType => RT::Transaction->CustomFieldLookupType ); ok($ok, $msg); ($ok, $msg) = $single_txn_cf->AddToObject($queue_obj); ok($ok, $msg); my $single_txn_cf_id = $single_txn_cf->Id; my $queue_url; # search Name = General { my $res = $mech->post_json("$rest_base_path/queues", [{ field => 'Name', value => 'General' }], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 1); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 1); is(scalar @{$content->{items}}, 1); my $queue = $content->{items}->[0]; is($queue->{type}, 'queue'); is($queue->{id}, 1); like($queue->{_url}, qr{$rest_base_path/queue/1$}); $queue_url = $queue->{_url}; } # Queue display { my $res = $mech->get($queue_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, 1); is($content->{Name}, 'General'); is($content->{Description}, 'The default queue'); is($content->{Lifecycle}, 'default'); is($content->{Disabled}, 0); my @fields = qw(LastUpdated Created CorrespondAddress CommentAddress); push @fields, qw(SortOrder SLADisabled) if RT::Handle::cmp_version($RT::VERSION, '4.4.0') >= 0; ok(exists $content->{$_}, "got $_") for @fields; my $links = $content->{_hyperlinks}; is(scalar @$links, 4); is($links->[0]{ref}, 'self'); is($links->[0]{id}, 1); is($links->[0]{type}, 'queue'); like($links->[0]{_url}, qr[$rest_base_path/queue/1$]); is($links->[1]{ref}, 'customfield'); like($links->[1]{_url}, qr[$rest_base_path/customfield/$single_cf_id$]); is($links->[1]{name}, 'Single'); is($links->[2]{ref}, 'history'); like($links->[2]{_url}, qr[$rest_base_path/queue/1/history$]); is($links->[2]{ref}, 'history'); like($links->[2]{_url}, qr[$rest_base_path/queue/1/history$]); is($links->[3]{ref}, 'create'); is($links->[3]{type}, 'ticket'); like($links->[3]{_url}, qr[$rest_base_path/ticket\?Queue=1$]); my $creator = $content->{Creator}; is($creator->{id}, 'RT_System'); is($creator->{type}, 'user'); like($creator->{_url}, qr{$rest_base_path/user/RT_System$}); my $updated_by = $content->{LastUpdatedBy}; is($updated_by->{id}, 'RT_System'); is($updated_by->{type}, 'user'); like($updated_by->{_url}, qr{$rest_base_path/user/RT_System$}); is_deeply($content->{Cc}, [], 'no Ccs set'); is_deeply($content->{AdminCc}, [], 'no AdminCcs set'); my $tix_cfs = $content->{TicketCustomFields}; is( $tix_cfs->[0]{id}, $single_ticket_cf_id, 'Returned custom field ' . $single_ticket_cf->Name . ' applied to queue' ); my $txn_cfs = $content->{TicketTransactionCustomFields}; is( $txn_cfs->[0]{id}, $single_txn_cf_id, 'Returned custom field ' . $single_txn_cf->Name . ' applied to queue' ); ok(!exists($content->{Owner}), 'no Owner at the queue level'); ok(!exists($content->{Requestor}), 'no Requestor at the queue level'); } # Queue update { my $payload = { Name => 'Bugs', Description => 'gotta squash em all', }; my $res = $mech->put_json($queue_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ['Queue General: Description changed from "The default queue" to "gotta squash em all"', 'Queue Bugs: Name changed from "General" to "Bugs"']); $res = $mech->get($queue_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{Name}, 'Bugs'); is($content->{Description}, 'gotta squash em all'); my $updated_by = $content->{LastUpdatedBy}; is($updated_by->{id}, 'test'); is($updated_by->{type}, 'user'); like($updated_by->{_url}, qr{$rest_base_path/user/test$}); } # search Name = Bugs { my $res = $mech->post_json("$rest_base_path/queues", [{ field => 'Name', value => 'Bugs' }], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 1); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 1); is(scalar @{$content->{items}}, 1); my $queue = $content->{items}->[0]; is($queue->{type}, 'queue'); is($queue->{id}, 1); like($queue->{_url}, qr{$rest_base_path/queue/1$}); } # Queue delete { my $res = $mech->delete($queue_url, 'Authorization' => $auth, ); is($res->code, 204); my $queue = RT::Queue->new(RT->SystemUser); $queue->Load(1); is($queue->Id, 1, '"deleted" queue still in the database'); ok($queue->Disabled, '"deleted" queue disabled'); $res = $mech->get($queue_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{Name}, 'Bugs'); is($content->{Disabled}, 1); } # Queue create my ($features_url, $features_id); { my $payload = { Name => 'Features', CorrespondAddress => 'features@example.com', CommentAddress => 'comment@example.com', }; my $res = $mech->post_json("$rest_base_path/queue", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($features_url = $res->header('location')); ok(($features_id) = $features_url =~ qr[/queue/(\d+)]); } # Queue display { my $res = $mech->get($features_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $features_id); is($content->{Name}, 'Features'); is($content->{Lifecycle}, 'default'); is($content->{Disabled}, 0); my @fields = qw(LastUpdated Created CorrespondAddress CommentAddress); push @fields, qw(SortOrder SLADisabled) if RT::Handle::cmp_version($RT::VERSION, '4.4.0') >= 0; ok(exists $content->{$_}, "got $_") for @fields; my $links = $content->{_hyperlinks}; is(scalar @$links, 3); is($links->[0]{ref}, 'self'); is($links->[0]{id}, $features_id); is($links->[0]{type}, 'queue'); like($links->[0]{_url}, qr[$rest_base_path/queue/$features_id$]); is($links->[1]{ref}, 'history'); like($links->[1]{_url}, qr[$rest_base_path/queue/$features_id/history$]); is($links->[2]{ref}, 'create'); is($links->[2]{type}, 'ticket'); like($links->[2]{_url}, qr[$rest_base_path/ticket\?Queue=$features_id$]); my $creator = $content->{Creator}; is($creator->{id}, 'test'); is($creator->{type}, 'user'); like($creator->{_url}, qr{$rest_base_path/user/test$}); my $updated_by = $content->{LastUpdatedBy}; is($updated_by->{id}, 'test'); is($updated_by->{type}, 'user'); like($updated_by->{_url}, qr{$rest_base_path/user/test$}); is_deeply($content->{Cc}, [], 'no Ccs set'); is_deeply($content->{AdminCc}, [], 'no AdminCcs set'); ok(!exists($content->{Owner}), 'no Owner at the queue level'); ok(!exists($content->{Requestor}), 'no Requestor at the queue level'); } # id > 0 (finds new Features queue but not disabled Bugs queue) { my $res = $mech->post_json("$rest_base_path/queues", [{ field => 'id', operator => '>', value => 0 }], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 1); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 1); is(scalar @{$content->{items}}, 1); my $queue = $content->{items}->[0]; is($queue->{type}, 'queue'); is($queue->{id}, $features_id); like($queue->{_url}, qr{$rest_base_path/queue/$features_id$}); } # id > 0 (finds new Features queue but not disabled Bugs queue), include Name field { my $res = $mech->post_json("$rest_base_path/queues?fields=Name", [{ field => 'id', operator => '>', value => 0 }], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is(scalar @{$content->{items}}, 1); my $queue = $content->{items}->[0]; is($queue->{Name}, 'Features'); is(scalar keys %$queue, 4); } # all queues, basic fields { my $res = $mech->post_json("$rest_base_path/queues/all", [], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is(scalar @{$content->{items}}, 1); my $queue = $content->{items}->[0]; is(scalar keys %$queue, 3); } # all queues, basic fields plus Name { my $res = $mech->post_json("$rest_base_path/queues/all?fields=Name", [], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is(scalar @{$content->{items}}, 1); my $queue = $content->{items}->[0]; is(scalar keys %$queue, 4); is($queue->{Name}, 'Features'); } # all queues, basic fields plus Name, Lifecycle. Lifecycle should be empty # string as we don't allow returning it. { my $res = $mech->post_json("$rest_base_path/queues/all?fields=Name,Lifecycle", [], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is(scalar @{$content->{items}}, 1); my $queue = $content->{items}->[0]; is(scalar keys %$queue, 5); is($queue->{Name}, 'Features'); is_deeply($queue->{Lifecycle}, 'default', 'Lifecycle is default'); } done_testing; rt-5.0.1/t/rest2/catalogs.t000644 000765 000024 00000015134 14005011336 016271 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; BEGIN { plan skip_all => 'RT 4.4 required' unless RT::Handle::cmp_version($RT::VERSION, '4.4.0') >= 0; } my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; $user->PrincipalObj->GrantRight( Right => 'SuperUser' ); my $catalog_url; # search Name = General assets { my $res = $mech->post_json("$rest_base_path/catalogs", [{ field => 'Name', value => 'General assets' }], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 1); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 1); is(scalar @{$content->{items}}, 1); my $catalog = $content->{items}->[0]; is($catalog->{type}, 'catalog'); is($catalog->{id}, 1); like($catalog->{_url}, qr{$rest_base_path/catalog/1$}); $catalog_url = $catalog->{_url}; } # Catalog display { my $res = $mech->get($catalog_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, 1); is($content->{Name}, 'General assets'); is($content->{Description}, 'The default catalog'); is($content->{Lifecycle}, 'assets'); is($content->{Disabled}, 0); ok(exists $content->{$_}, "got $_") for qw(LastUpdated Created); my $links = $content->{_hyperlinks}; is(scalar @$links, 2); is($links->[0]{ref}, 'self'); is($links->[0]{id}, 1); is($links->[0]{type}, 'catalog'); like($links->[0]{_url}, qr[$rest_base_path/catalog/1$]); is($links->[1]{ref}, 'create'); is($links->[1]{type}, 'asset'); like($links->[1]{_url}, qr[$rest_base_path/asset\?Catalog=1$]); my $creator = $content->{Creator}; is($creator->{id}, 'RT_System'); is($creator->{type}, 'user'); like($creator->{_url}, qr{$rest_base_path/user/RT_System$}); my $updated_by = $content->{LastUpdatedBy}; is($updated_by->{id}, 'RT_System'); is($updated_by->{type}, 'user'); like($updated_by->{_url}, qr{$rest_base_path/user/RT_System$}); is_deeply($content->{Contact}, [], 'no Contact set'); is_deeply($content->{HeldBy}, [], 'no HeldBy set'); ok(!exists($content->{Owner}), 'no Owner at the catalog level'); } # Catalog update { my $payload = { Name => 'Servers', Description => 'gotta serve em all', }; my $res = $mech->put_json($catalog_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ["Catalog General assets: Description changed from 'The default catalog' to 'gotta serve em all'", "Catalog Servers: Name changed from 'General assets' to 'Servers'"]); $res = $mech->get($catalog_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{Name}, 'Servers'); is($content->{Description}, 'gotta serve em all'); my $updated_by = $content->{LastUpdatedBy}; is($updated_by->{id}, 'test'); is($updated_by->{type}, 'user'); like($updated_by->{_url}, qr{$rest_base_path/user/test$}); } # search Name = Servers { my $res = $mech->post_json("$rest_base_path/catalogs", [{ field => 'Name', value => 'Servers' }], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 1); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 1); is(scalar @{$content->{items}}, 1); my $catalog = $content->{items}->[0]; is($catalog->{type}, 'catalog'); is($catalog->{id}, 1); like($catalog->{_url}, qr{$rest_base_path/catalog/1$}); } # Catalog delete { my $res = $mech->delete($catalog_url, 'Authorization' => $auth, ); is($res->code, 204); my $catalog = RT::Catalog->new(RT->SystemUser); $catalog->Load(1); is($catalog->Id, 1, '"deleted" catalog still in the database'); ok($catalog->Disabled, '"deleted" catalog disabled'); $res = $mech->get($catalog_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{Name}, 'Servers'); is($content->{Disabled}, 1); } # Catalog create my ($laptops_url, $laptops_id); { my $payload = { Name => 'Laptops', }; my $res = $mech->post_json("$rest_base_path/catalog", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($laptops_url = $res->header('location')); ok(($laptops_id) = $laptops_url =~ qr[/catalog/(\d+)]); } # Catalog display { my $res = $mech->get($laptops_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $laptops_id); is($content->{Name}, 'Laptops'); is($content->{Lifecycle}, 'assets'); is($content->{Disabled}, 0); ok(exists $content->{$_}, "got $_") for qw(LastUpdated Created); my $links = $content->{_hyperlinks}; is(scalar @$links, 2); is($links->[0]{ref}, 'self'); is($links->[0]{id}, $laptops_id); is($links->[0]{type}, 'catalog'); like($links->[0]{_url}, qr[$rest_base_path/catalog/$laptops_id$]); is($links->[1]{ref}, 'create'); is($links->[1]{type}, 'asset'); like($links->[1]{_url}, qr[$rest_base_path/asset\?Catalog=$laptops_id$]); my $creator = $content->{Creator}; is($creator->{id}, 'test'); is($creator->{type}, 'user'); like($creator->{_url}, qr{$rest_base_path/user/test$}); my $updated_by = $content->{LastUpdatedBy}; is($updated_by->{id}, 'test'); is($updated_by->{type}, 'user'); like($updated_by->{_url}, qr{$rest_base_path/user/test$}); is_deeply($content->{Contact}, [], 'no Contact set'); is_deeply($content->{HeldBy}, [], 'no HeldBy set'); ok(!exists($content->{Owner}), 'no Owner at the catalog level'); } # id > 0 (finds new Laptops catalog but not disabled Servers catalog) { my $res = $mech->post_json("$rest_base_path/catalogs", [{ field => 'id', operator => '>', value => 0 }], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 1); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 1); is(scalar @{$content->{items}}, 1); my $catalog = $content->{items}->[0]; is($catalog->{type}, 'catalog'); is($catalog->{id}, $laptops_id); like($catalog->{_url}, qr{$rest_base_path/catalog/$laptops_id$}); } done_testing; rt-5.0.1/t/rest2/asset-watchers.t000644 000765 000024 00000012317 14005011336 017431 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; use Test::Deep; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; $user->PrincipalObj->GrantRight( Right => $_ ) for qw/CreateAsset ShowAsset ModifyAsset OwnAsset AdminUsers SeeGroup/; # Modify single user allowed roles. { my $payload = { Name => 'Asset creation using REST', Catalog => 'General assets', Content => 'Testing asset creation using REST API.', }; my $res = $mech->post_json("$rest_base_path/asset", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok(my $asset_url = $res->header('location')); ok((my $asset_id) = $asset_url =~ qr[/asset/(\d+)]); $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; cmp_deeply($content->{Owner}, { type => 'user', id => 'Nobody', _url => re(qr{$rest_base_path/user/Nobody$}), }, 'Owner is Nobody'); $res = $mech->get($content->{Owner}{_url}, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response, superhashof({ id => RT->Nobody->id, Name => 'Nobody', RealName => 'Nobody in particular', }), 'Nobody user'); for my $field ('Owner') { for my $identifier ($user->id, $user->Name) { $payload = { $field => $identifier, }; $res = $mech->put_json($asset_url, $payload, 'Authorization' => $auth, ); is_deeply($mech->json_response, ["$field set to test"], "updated $field with identifier $identifier"); $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response->{$field}, { type => 'user', id => 'test', _url => re(qr{$rest_base_path/user/test$}), }, "$field has changed to test"); $payload = { $field => 'Nobody', }; $res = $mech->put_json($asset_url, $payload, 'Authorization' => $auth, ); is_deeply($mech->json_response, ["$field set to Nobody"], "updated $field"); $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response->{$field}, { type => 'user', id => 'Nobody', _url => re(qr{$rest_base_path/user/Nobody$}), }, "$field has changed to Nobody"); } } } # Modify multi-user allowed roles (HeldBy) { my ($asset_url, $asset_id); my $payload = { Name => 'Asset for modifying owner using REST', Catalog => 'General assets', Content => 'Testing asset creation using REST API.', HeldBy => 'root', Contact => 'root', }; my $res = $mech->post_json("$rest_base_path/asset", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($asset_url = $res->header('location')); ok(($asset_id) = $asset_url =~ qr[/asset/(\d+)]); $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); # Initial sanity check. for my $field ('Contact', 'HeldBy') { cmp_deeply($mech->json_response->{$field}, [{ type => 'user', id => 'root', _url => re(qr{$rest_base_path/user/root$}), }], "$field is root"); } for my $field ('Contact', 'HeldBy') { for my $identifier ($user->id, $user->Name) { my $payload = { $field => $identifier, }; $res = $mech->put_json($asset_url, $payload, 'Authorization' => $auth, ); is_deeply($mech->json_response, ["Member added: test", 'Member deleted'], "updated $field with identifier $identifier"); $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response->{$field}, [{ type => 'user', id => 'test', _url => re(qr{$rest_base_path/user/test$}), }], "$field has changed to test"); $payload = { $field => 'Nobody', }; $res = $mech->put_json($asset_url, $payload, 'Authorization' => $auth, ); is_deeply($mech->json_response, ["Member added: Nobody", 'Member deleted'], "updated $field"); $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response->{$field}, [{ type => 'user', id => 'Nobody', _url => re(qr{$rest_base_path/user/Nobody$}), }], "$field has changed to Nobody"); } } } done_testing; rt-5.0.1/t/rest2/users.t000644 000765 000024 00000012560 14005011336 015635 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; use Test::Deep; #use Test::Warn; use Data::Dumper; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $test_user = RT::Test::REST2->user; $test_user->PrincipalObj->RevokeRight(Right => 'ShowUserHistory'); my $user_foo = RT::Test->load_or_create_user( Name => 'foo', RealName => 'Foo Jr III', EmailAddress => 'test@test.test', Password => 'password', ); my $user_bar = RT::Test->load_or_create_user( Name => 'bar', RealName => 'Bar Jr III', ); my $user_baz = RT::Test->load_or_create_user( Name => 'baz' ); my $user_quuz = RT::Test->load_or_create_user( Name => 'quuz' ); $user_baz->SetDisabled(1); my $group1 = RT::Group->new(RT->SystemUser); $group1->CreateUserDefinedGroup(Name => 'Group 1'); my $group2 = RT::Group->new(RT->SystemUser); $group2->CreateUserDefinedGroup(Name => 'Group 2'); my ($ok, $msg) = $group1->AddMember($user_bar->id); ($ok, $msg) = $group1->AddMember($user_foo->id); ($ok, $msg) = $group2->AddMember($user_foo->id); ($ok, $msg) = $group2->AddMember($user_quuz->id); # user search { my $user_id = $test_user->id; my $res = $mech->get("$rest_base_path/users", 'Authorization' => $auth ); is($res->code, 200); my $content = $mech->json_response; my $items = delete $content->{items}; is_deeply( $content, { 'count' => 7, 'total' => 7, 'per_page' => 20, 'pages' => 1, 'page' => 1 }); is(scalar(@$items), 7); foreach my $username (qw(foo bar quuz root test)) { my ($item) = grep { $_->{id} eq $username } @$items; like($item->{_url}, qr{$rest_base_path/user/$username$}); is($item->{type}, 'user'); } ok(not grep { $_->{id} eq 'baz' } @$items); } # basic user request, own user details { my $user_id = $test_user->id; my $res = $mech->get("$rest_base_path/user/$user_id", 'Authorization' => $auth ); is($res->code, 200); my $content = $mech->json_response; cmp_deeply( $content, superhashof({ 'Privileged' => 1, 'Name' => 'test', 'Disabled' => '0', }),'basic summary for own user ok' ); my $links = $content->{_hyperlinks}; my ($history_link) = grep { $_->{ref} eq 'history' } @$links; like($history_link->{_url}, qr{$rest_base_path/user/$user_id/history$}); } # basic user request, no rights { my $user_id = $user_foo->id; my $res = $mech->get("$rest_base_path/user/$user_id", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; my $links = delete $content->{_hyperlinks}; is(scalar(@$links),1, 'only 1 link, should be self'); my ($self_link) = grep { $_->{ref} eq 'self' } @$links; like($self_link->{_url}, qr{$rest_base_path/user/$user_id$}); is_deeply($content, { Name => 'foo', RealName => 'Foo Jr III', EmailAddress => 'test@test.test', 'Privileged' => 1 }, 'basic summary for user ok, no extra fields or hyperlinks'); } # showuserhistory authorised User with hypermedia links $test_user->PrincipalObj->GrantRight(Right => 'ShowUserHistory'); { my $user_id = $user_foo->id; my $res = $mech->get("$rest_base_path/user/$user_id", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; my $links = delete $content->{_hyperlinks}; is(scalar(@$links),2); my @memberships_links = grep { $_->{ref} eq 'memberships' } @$links; is(scalar(@memberships_links), 0); my ($history_link) = grep { $_->{ref} eq 'history' } @$links; like($history_link->{_url}, qr{$rest_base_path/user/$user_id/history$}); is_deeply($content, { Name => 'foo', RealName => 'Foo Jr III', EmailAddress => 'test@test.test', 'Privileged' => 1 }, 'basic summary for user ok, no extra fields'); } # useradmin authorised User with hypermedia links $test_user->PrincipalObj->GrantRight(Right => 'AdminUsers'); $test_user->PrincipalObj->GrantRight(Right => 'AdminGroupMembership'); { my $user_id = $user_foo->id; my $res = $mech->get("$rest_base_path/user/$user_id", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; # test hyperlinks my $links = delete $content->{_hyperlinks}; is(scalar(@$links),3); my ($history_link) = grep { $_->{ref} eq 'history' } @$links; like($history_link->{_url}, qr{$rest_base_path/user/$user_id/history$}); my @memberships_links = grep { $_->{ref} eq 'memberships' } @$links; is(scalar(@memberships_links), 1); like($memberships_links[0]->{_url}, qr{$rest_base_path/user/$user_id/groups$}); my ($self_link) = grep { $_->{ref} eq 'self' } @$links; like($self_link->{_url}, qr{$rest_base_path/user/$user_id$}); cmp_deeply($content, superhashof({ 'RealName' => 'Foo Jr III', 'Privileged' => 1, 'Memberships' => [], 'Disabled' => '0', 'Name' => 'foo', 'EmailAddress' => 'test@test.test', 'CustomFields' => [] }) ); } $test_user->PrincipalObj->RevokeRight(Right => 'ShowUserHistory'); $test_user->PrincipalObj->RevokeRight(Right => 'AdminUsers'); done_testing; rt-5.0.1/t/rest2/classes.t000644 000765 000024 00000014437 14005011336 016136 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; $user->PrincipalObj->GrantRight( Right => 'SuperUser' ); my $class_url; # search Name = General { my $res = $mech->post_json( "$rest_base_path/classes", [ { field => 'Name', value => 'General' } ], 'Authorization' => $auth, ); is( $res->code, 200 ); my $content = $mech->json_response; is( $content->{count}, 1 ); is( $content->{page}, 1 ); is( $content->{per_page}, 20 ); is( $content->{total}, 1 ); is( scalar @{ $content->{items} }, 1 ); my $class = $content->{items}->[0]; is( $class->{type}, 'class' ); is( $class->{id}, 1 ); like( $class->{_url}, qr{$rest_base_path/class/1$} ); $class_url = $class->{_url}; } # Class display { my $res = $mech->get( $class_url, 'Authorization' => $auth, ); is( $res->code, 200 ); my $content = $mech->json_response; is( $content->{id}, 1 ); is( $content->{Name}, 'General' ); is( $content->{Description}, 'The default class' ); is( $content->{Disabled}, 0 ); ok( exists $content->{$_}, "got $_" ) for qw(LastUpdated Created); my $links = $content->{_hyperlinks}; is( scalar @$links, 2 ); is( $links->[0]{ref}, 'self' ); is( $links->[0]{id}, 1 ); is( $links->[0]{type}, 'class' ); like( $links->[0]{_url}, qr[$rest_base_path/class/1$] ); is( $links->[1]{ref}, 'create' ); is( $links->[1]{type}, 'article' ); like( $links->[1]{_url}, qr[$rest_base_path/article\?Class=1$] ); my $creator = $content->{Creator}; is( $creator->{id}, 'RT_System' ); is( $creator->{type}, 'user' ); like( $creator->{_url}, qr{$rest_base_path/user/RT_System$} ); my $updated_by = $content->{LastUpdatedBy}; is( $updated_by->{id}, 'RT_System' ); is( $updated_by->{type}, 'user' ); like( $updated_by->{_url}, qr{$rest_base_path/user/RT_System$} ); } # Class update { my $payload = { Name => 'Servers', Description => 'gotta serve em all', }; my $res = $mech->put_json( $class_url, $payload, 'Authorization' => $auth, ); is( $res->code, 200 ); is_deeply( $mech->json_response, [ 'Class General: Description changed from "The default class" to "gotta serve em all"', 'Class Servers: Name changed from "General" to "Servers"' ] ); $res = $mech->get( $class_url, 'Authorization' => $auth, ); is( $res->code, 200 ); my $content = $mech->json_response; is( $content->{Name}, 'Servers' ); is( $content->{Description}, 'gotta serve em all' ); my $updated_by = $content->{LastUpdatedBy}; is( $updated_by->{id}, 'test' ); is( $updated_by->{type}, 'user' ); like( $updated_by->{_url}, qr{$rest_base_path/user/test$} ); } # search Name = Servers { my $res = $mech->post_json( "$rest_base_path/classes", [ { field => 'Name', value => 'Servers' } ], 'Authorization' => $auth, ); is( $res->code, 200 ); my $content = $mech->json_response; is( $content->{count}, 1 ); is( $content->{page}, 1 ); is( $content->{per_page}, 20 ); is( $content->{total}, 1 ); is( scalar @{ $content->{items} }, 1 ); my $class = $content->{items}->[0]; is( $class->{type}, 'class' ); is( $class->{id}, 1 ); like( $class->{_url}, qr{$rest_base_path/class/1$} ); } # Class delete { my $res = $mech->delete( $class_url, 'Authorization' => $auth, ); is( $res->code, 204 ); my $class = RT::Class->new( RT->SystemUser ); $class->Load(1); is( $class->Id, 1, '"deleted" class still in the database' ); ok( $class->Disabled, '"deleted" class disabled' ); $res = $mech->get( $class_url, 'Authorization' => $auth, ); is( $res->code, 200 ); my $content = $mech->json_response; is( $content->{Name}, 'Servers' ); is( $content->{Disabled}, 1 ); } # Class create my ( $laptops_url, $laptops_id ); { my $payload = { Name => 'Laptops', }; my $res = $mech->post_json( "$rest_base_path/class", $payload, 'Authorization' => $auth, ); is( $res->code, 201 ); ok( $laptops_url = $res->header('location') ); ok( ($laptops_id) = $laptops_url =~ qr[/class/(\d+)] ); } # Class display { my $res = $mech->get( $laptops_url, 'Authorization' => $auth, ); is( $res->code, 200 ); my $content = $mech->json_response; is( $content->{id}, $laptops_id ); is( $content->{Name}, 'Laptops' ); is( $content->{Disabled}, 0 ); ok( exists $content->{$_}, "got $_" ) for qw(LastUpdated Created); my $links = $content->{_hyperlinks}; is( scalar @$links, 2 ); is( $links->[0]{ref}, 'self' ); is( $links->[0]{id}, $laptops_id ); is( $links->[0]{type}, 'class' ); like( $links->[0]{_url}, qr[$rest_base_path/class/$laptops_id$] ); is( $links->[1]{ref}, 'create' ); is( $links->[1]{type}, 'article' ); like( $links->[1]{_url}, qr[$rest_base_path/article\?Class=$laptops_id$] ); my $creator = $content->{Creator}; is( $creator->{id}, 'test' ); is( $creator->{type}, 'user' ); like( $creator->{_url}, qr{$rest_base_path/user/test$} ); my $updated_by = $content->{LastUpdatedBy}; is( $updated_by->{id}, 'test' ); is( $updated_by->{type}, 'user' ); like( $updated_by->{_url}, qr{$rest_base_path/user/test$} ); } # id > 0 (finds new Laptops class but not disabled Servers class) { my $res = $mech->post_json( "$rest_base_path/classes", [ { field => 'id', operator => '>', value => 0 } ], 'Authorization' => $auth, ); is( $res->code, 200 ); my $content = $mech->json_response; is( $content->{count}, 1 ); is( $content->{page}, 1 ); is( $content->{per_page}, 20 ); is( $content->{total}, 1 ); is( scalar @{ $content->{items} }, 1 ); my $class = $content->{items}->[0]; is( $class->{type}, 'class' ); is( $class->{id}, $laptops_id ); like( $class->{_url}, qr{$rest_base_path/class/$laptops_id$} ); } done_testing; rt-5.0.1/t/rest2/tickets.t000644 000765 000024 00000057427 14005011336 016155 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; use Test::Deep; use MIME::Base64; use Encode qw(decode encode); # Test using integer priorities RT->Config->Set(EnablePriorityAsString => 0); my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; # Empty DB { my $res = $mech->get("$rest_base_path/tickets?query=id>0", 'Authorization' => $auth, ); is($res->code, 200); is($mech->json_response->{count}, 0); } # Missing Queue { my $res = $mech->post_json("$rest_base_path/ticket", { Subject => 'Ticket creation using REST', }, 'Authorization' => $auth, ); is($res->code, 400); is($mech->json_response->{message}, 'Could not create ticket. Queue not set'); } # Ticket Creation my ($ticket_url, $ticket_id); { my $payload = { Subject => 'Ticket creation using REST', Queue => 'General', Content => 'Testing ticket creation using REST API.', }; # Rights Test - No CreateTicket my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 403); # Rights Test - With CreateTicket $user->PrincipalObj->GrantRight( Right => 'CreateTicket' ); $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($ticket_url = $res->header('location')); ok(($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); } # Ticket Display { # Rights Test - No ShowTicket my $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 403); } # Rights Test - With ShowTicket { $user->PrincipalObj->GrantRight( Right => 'ShowTicket' ); my $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $ticket_id); is($content->{Type}, 'ticket'); is($content->{Status}, 'new'); is($content->{Subject}, 'Ticket creation using REST'); ok(exists $content->{$_}) for qw(AdminCc TimeEstimated Started Cc LastUpdated TimeWorked Resolved Created Due Priority EffectiveId); my $links = $content->{_hyperlinks}; is(scalar @$links, 2); is($links->[0]{ref}, 'self'); is($links->[0]{id}, 1); is($links->[0]{type}, 'ticket'); like($links->[0]{_url}, qr[$rest_base_path/ticket/$ticket_id$]); is($links->[1]{ref}, 'history'); like($links->[1]{_url}, qr[$rest_base_path/ticket/$ticket_id/history$]); my $queue = $content->{Queue}; is($queue->{id}, 1); is($queue->{type}, 'queue'); like($queue->{_url}, qr{$rest_base_path/queue/1$}); ok(!exists $queue->{Name}, 'queue name is absent'); ok(!exists $queue->{Lifecycle}, 'queue lifecycle is absent'); my $owner = $content->{Owner}; is($owner->{id}, 'Nobody'); is($owner->{type}, 'user'); like($owner->{_url}, qr{$rest_base_path/user/Nobody$}); my $creator = $content->{Creator}; is($creator->{id}, 'test'); is($creator->{type}, 'user'); like($creator->{_url}, qr{$rest_base_path/user/test$}); my $updated_by = $content->{LastUpdatedBy}; is($updated_by->{id}, 'test'); is($updated_by->{type}, 'user'); like($updated_by->{_url}, qr{$rest_base_path/user/test$}); } # Ticket display with additional fields { my $res = $mech->get($ticket_url . '?fields[Queue]=Name,Lifecycle', 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $ticket_id); my $queue = $content->{Queue}; is($queue->{id}, 1); is($queue->{type}, 'queue'); like($queue->{_url}, qr{$rest_base_path/queue/1$}); is($queue->{Name}, '', 'empty queue name'); is($queue->{Lifecycle}, '', 'empty queue lifecycle'); $user->PrincipalObj->GrantRight(Right => 'SeeQueue'); $res = $mech->get($ticket_url . '?fields[Queue]=Name,Lifecycle', 'Authorization' => $auth,); is($res->code, 200); $content = $mech->json_response; is($content->{id}, $ticket_id); $queue = $content->{Queue}; is($queue->{id}, 1); is($queue->{type}, 'queue'); like($queue->{_url}, qr{$rest_base_path/queue/1$}); is($queue->{Name}, 'General', 'queue name'); is($queue->{Lifecycle}, 'default', 'queue lifecycle'); $user->PrincipalObj->RevokeRight(Right => 'SeeQueue'); } # Ticket Create Attachment created correctly { my $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); my $transaction_id = $ticket->Transactions->Last->id; my $attachments = $ticket->Attachments->ItemsArrayRef; # 1 attachment is(scalar(@$attachments), 1); is($attachments->[0]->Parent, 0); is($attachments->[0]->Subject, 'Ticket creation using REST'); ok(!$attachments->[0]->Filename); is($attachments->[0]->ContentType, 'text/plain'); } # Ticket Search { my $res = $mech->get("$rest_base_path/tickets?query=id>0", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 1); is($content->{page}, 1); is($content->{pages}, 1); is($content->{per_page}, 20); is($content->{total}, 1); is(scalar @{$content->{items}}, 1); my $ticket = $content->{items}->[0]; is($ticket->{type}, 'ticket'); is($ticket->{id}, 1); like($ticket->{_url}, qr{$rest_base_path/ticket/1$}); is(scalar keys %$ticket, 3); } # Ticket Search - Fields { my $res = $mech->get("$rest_base_path/tickets?query=id>0&fields=Status,Subject", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is(scalar @{$content->{items}}, 1); my $ticket = $content->{items}->[0]; is($ticket->{Subject}, 'Ticket creation using REST'); is($ticket->{Status}, 'new'); is(scalar keys %$ticket, 5); } # Ticket Search - Fields, sub objects, no right to see Queues { my $res = $mech->get("$rest_base_path/tickets?query=id>0&fields=Status,Owner,Queue&fields[Queue]=Name,Description&fields[Owner]=Name", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is(scalar @{$content->{items}}, 1); my $ticket = $content->{items}->[0]; is($ticket->{Status}, 'new'); is($ticket->{Queue}{Name}, ''); is($ticket->{Queue}{id}, '1'); is($ticket->{Queue}{type}, 'queue'); like($ticket->{Queue}{_url}, qr[$rest_base_path/queue/1$]); is($ticket->{Owner}{Name}, 'Nobody'); is(scalar keys %$ticket, 6); } # Ticket Search - Fields, sub objects with SeeQueue right { $user->PrincipalObj->GrantRight( Right => 'SeeQueue' ); my $res = $mech->get("$rest_base_path/tickets?query=id>0&fields=Status,Owner,Queue&fields[Queue]=Name,Description&fields[Owner]=Name", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is(scalar @{$content->{items}}, 1); my $ticket = $content->{items}->[0]; is($ticket->{Status}, 'new'); is($ticket->{Queue}{Name}, 'General'); is($ticket->{Queue}{Description}, 'The default queue'); is($ticket->{Queue}{id}, '1'); is($ticket->{Queue}{type}, 'queue'); like($ticket->{Queue}{_url}, qr[$rest_base_path/queue/1$]); is($ticket->{Owner}{Name}, 'Nobody'); is(scalar keys %$ticket, 6); } # Ticket Update { my $payload = { Subject => 'Ticket update using REST', Priority => 42, }; # Rights Test - No ModifyTicket my $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); TODO: { local $TODO = "RT ->Update isn't introspectable"; is($res->code, 403); }; is_deeply($mech->json_response, ['Ticket 1: Permission Denied', 'Ticket 1: Permission Denied']); $user->PrincipalObj->GrantRight( Right => 'ModifyTicket' ); $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ["Ticket 1: Priority changed from (no value) to '42'", "Ticket 1: Subject changed from 'Ticket creation using REST' to 'Ticket update using REST'"]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{Subject}, 'Ticket update using REST'); is($content->{Priority}, 42); # now that we have ModifyTicket, we should have additional hypermedia my $links = $content->{_hyperlinks}; is(scalar @$links, 5); is($links->[0]{ref}, 'self'); is($links->[0]{id}, 1); is($links->[0]{type}, 'ticket'); like($links->[0]{_url}, qr[$rest_base_path/ticket/$ticket_id$]); is($links->[1]{ref}, 'history'); like($links->[1]{_url}, qr[$rest_base_path/ticket/$ticket_id/history$]); is($links->[2]{ref}, 'lifecycle'); like($links->[2]{_url}, qr[$rest_base_path/ticket/$ticket_id/correspond$]); is($links->[2]{label}, 'Open It'); is($links->[2]{update}, 'Respond'); is($links->[2]{from}, 'new'); is($links->[2]{to}, 'open'); is($links->[3]{ref}, 'lifecycle'); like($links->[3]{_url}, qr[$rest_base_path/ticket/$ticket_id/comment$]); is($links->[3]{label}, 'Resolve'); is($links->[3]{update}, 'Comment'); is($links->[3]{from}, 'new'); is($links->[3]{to}, 'resolved'); is($links->[4]{ref}, 'lifecycle'); like($links->[4]{_url}, qr[$rest_base_path/ticket/$ticket_id/correspond$]); is($links->[4]{label}, 'Reject'); is($links->[4]{update}, 'Respond'); is($links->[4]{from}, 'new'); is($links->[4]{to}, 'rejected'); # update again with no changes $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, []); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; is($content->{Subject}, 'Ticket update using REST'); is($content->{Priority}, 42); $payload = { 'Owner' => 'root' }; $res = $mech->put_json( $ticket_url, $payload, 'Authorization' => $auth, ); is( $res->code, 200 ); is_deeply( $mech->json_response, ["Ticket $ticket_id: Owner changed from Nobody to root"] ); $user->PrincipalObj->GrantRight( Right => 'OwnTicket' ); my %result = ( steal => 'Owner changed from root to test', untake => 'Owner changed from test to Nobody', take => 'Owner changed from Nobody to test', ); for my $action (qw/steal untake take/) { $res = $mech->put_json( "$ticket_url/$action", undef, 'Authorization' => $auth, ); is( $res->code, 200 ); is_deeply( $mech->json_response, [ $result{$action} ] ); } $payload = { Subject => 'Ticket creation using REST', Queue => 'General', }; $res = $mech->post_json( "$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is( $res->code, 201 ); $payload = { MergeInto => $ticket_id }; $res = $mech->put_json( $res->header('location'), $payload, 'Authorization' => $auth, ); is( $res->code, 200 ); is_deeply( $mech->json_response, ['Merge Successful'] ); } # Transactions { my $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $res = $mech->get($mech->url_for_hypermedia('history'), 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 14); is($content->{page}, 1); is($content->{per_page}, 20); # TODO This 14 VS 15 inconsitency is because user lacks ShowOutgoingEmail. # It'll be perfect if we can keep them in sync is($content->{total}, 15); is(scalar @{$content->{items}}, 14); for my $txn (@{ $content->{items} }) { is($txn->{type}, 'transaction'); like($txn->{_url}, qr{$rest_base_path/transaction/\d+$}); } } # Ticket Reply { # we know from earlier tests that look at hypermedia without ReplyToTicket # that correspond wasn't available, so we don't need to check again here $user->PrincipalObj->GrantRight( Right => 'ReplyToTicket' ); my $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; my ($hypermedia) = grep { $_->{ref} eq 'correspond' } @{ $content->{_hyperlinks} }; ok($hypermedia, 'got correspond hypermedia'); like($hypermedia->{_url}, qr[$rest_base_path/ticket/$ticket_id/correspond$]); $res = $mech->post($mech->url_for_hypermedia('correspond'), 'Authorization' => $auth, 'Content-Type' => 'text/plain', 'Content' => 'Hello from hypermedia!', ); is($res->code, 201); cmp_deeply($mech->json_response, [re(qr/Correspondence added|Message recorded/)]); like($res->header('Location'), qr{$rest_base_path/transaction/\d+$}); $res = $mech->get($res->header('Location'), 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; is($content->{Type}, 'Correspond'); is($content->{TimeTaken}, 0); is($content->{Object}{type}, 'ticket'); is($content->{Object}{id}, $ticket_id); $res = $mech->get($mech->url_for_hypermedia('attachment'), 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; is($content->{Content}, encode_base64('Hello from hypermedia!')); is($content->{ContentType}, 'text/plain'); } # Ticket Comment { my $payload = { Content => "(hello secret camera \x{5e9}\x{5dc}\x{5d5}\x{5dd})", ContentType => 'text/html', Subject => 'shh', TimeTaken => 129, }; # we know from earlier tests that look at hypermedia without ReplyToTicket # that correspond wasn't available, so we don't need to check again here $user->PrincipalObj->GrantRight( Right => 'CommentOnTicket' ); my $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; my ($hypermedia) = grep { $_->{ref} eq 'comment' } @{ $content->{_hyperlinks} }; ok($hypermedia, 'got comment hypermedia'); like($hypermedia->{_url}, qr[$rest_base_path/ticket/$ticket_id/comment$]); $res = $mech->post_json($mech->url_for_hypermedia('comment'), $payload, 'Authorization' => $auth, ); is($res->code, 201); cmp_deeply($mech->json_response, [re(qr/Comments added|Message recorded/)]); my $txn_url = $res->header('Location'); like($txn_url, qr{$rest_base_path/transaction/\d+$}); $res = $mech->get($txn_url, 'Authorization' => $auth, ); is($res->code, 403); $user->PrincipalObj->GrantRight( Right => 'ShowTicketComments' ); $res = $mech->get($txn_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; is($content->{Type}, 'Comment'); is($content->{TimeTaken}, 129); is($content->{Object}{type}, 'ticket'); is($content->{Object}{id}, $ticket_id); $res = $mech->get($mech->url_for_hypermedia('attachment'), 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; is($content->{Subject}, 'shh'); # Note below: D7 A9 is the UTF-8 encoding of U+5E9, etc. is($content->{Content}, encode_base64("(hello secret camera \xD7\xA9\xD7\x9C\xD7\x95\xD7\x9D)")); is($content->{ContentType}, 'text/html'); } # Ticket Sorted Search { my $ticket2 = RT::Ticket->new($RT::SystemUser); ok(my ($ticket2_id) = $ticket2->Create(Queue => 'General', Subject => 'Ticket for test')); my $ticket3 = RT::Ticket->new($RT::SystemUser); ok(my ($ticket3_id) = $ticket3->Create(Queue => 'General', Subject => 'Ticket for test')); my $ticket4 = RT::Ticket->new($RT::SystemUser); ok(my ($ticket4_id) = $ticket4->Create(Queue => 'General', Subject => 'Ticket to test sorted search')); my $res = $mech->get("$rest_base_path/tickets?query=Subject LIKE 'test'&orderby=Subject&order=DESC&orderby=id", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 3); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 3); is(scalar @{$content->{items}}, 3); my $first_ticket = $content->{items}->[0]; is($first_ticket->{type}, 'ticket'); is($first_ticket->{id}, $ticket4_id); like($first_ticket->{_url}, qr{$rest_base_path/ticket/$ticket4_id$}); my $second_ticket = $content->{items}->[1]; is($second_ticket->{type}, 'ticket'); is($second_ticket->{id}, $ticket2_id); like($second_ticket->{_url}, qr{$rest_base_path/ticket/$ticket2_id$}); my $third_ticket = $content->{items}->[2]; is($third_ticket->{type}, 'ticket'); is($third_ticket->{id}, $ticket3_id); like($third_ticket->{_url}, qr{$rest_base_path/ticket/$ticket3_id$}); } # Ticket Creation - with attachments { my $payload = { Subject => 'Ticket creation using REST, with attachments.', Queue => 'General', Content => 'Testing ticket creation with attachments using REST API.', Attachments => [ { FileName => 'plain.txt', FileType => 'text/plain', FileContent => MIME::Base64::encode_base64('Hello, World!') }, { FileName => 'html.htm', FileType => 'text/html', FileContent => MIME::Base64::encode_base64( encode( 'UTF-8', "

      Easy as \x{03c0}

      " ) ), }, { FileName => 'moon.png', FileType => 'image/png', FileContent => 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAGQAAABkABchkaRQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADfSURBVDiNndM9TsNAFATgzy5yjZSAE85JBygETgENUPF3iBCitHAFQkcIhZ/Ryn9gRlrZmp2Z3ef3TBOHOMULPrDBMrhpi/4HI5xjix2+4nmJRbx/Yh7ahvkpRPVV4QDXwT3UQy46zGkAZDgK/iytefvHgCrkJsqZUH6cLnNbABSxd5Jhhf1IbkMXv8Qux7hH1Ic1xvk/jBWy6gavumvtwx7ectwZXkKh7MA95XgObeOtpI2U4zl0kGbpxgiPvwQUcXLrKFchc82f6Ur0PK49azOnmOI4TBu84zm4SV38DeIVYkrYJyNbAAAAAElFTkSuQmCC' }, ] }; my $res = $mech->post_json( "$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is( $res->code, 201 ); ok( $ticket_url = $res->header('location') ); ok( ($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)] ); } # We have the attachments added above { my $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); my $transaction_id = $ticket->Transactions->Last->id; my $attachments = $ticket->Attachments->ItemsArrayRef; # The 5 attachments are: # 1) Top-level multipart # 2) Top-level ticket content # 3-5) The three attachments added in the Attachments array is( scalar(@$attachments), 5 ); is( $attachments->[0]->ContentType, 'multipart/mixed' ); is( $attachments->[1]->ContentType, 'text/plain' ); is( $attachments->[1]->Content, 'Testing ticket creation with attachments using REST API.' ); is( $attachments->[2]->ContentType, 'text/plain' ); is( $attachments->[2]->Filename, 'plain.txt' ); is( $attachments->[2]->Content, 'Hello, World!' ); is( $attachments->[3]->ContentType, 'text/html' ); is( $attachments->[3]->Filename, 'html.htm' ); is( $attachments->[3]->Content, "

      Easy as \x{03c0}

      " ); is( $attachments->[4]->ContentType, 'image/png' ); is( $attachments->[4]->Filename, 'moon.png' ); like( $attachments->[4]->Content, qr/IHDR/, "Looks like a PNG image" ); } # Ticket Creation - with attachments, using multipart/form-data my $json = JSON->new->utf8; { my $plain_name = 'plain.txt'; my $plain_path = RT::Test::get_relocatable_file($plain_name, 'data'); my $html_name = 'html.htm'; my $html_path = RT::Test::get_relocatable_file($html_name, 'data'); my $img_name = 'image.png'; my $img_path = RT::Test::get_relocatable_file($img_name, 'data'); my $payload = { Subject => 'Ticket creation using REST, multipart/form-data, with attachments.', Queue => 'General', Content => 'Testing ticket creation, multipart/form-data, with attachments using REST API.', }; my $res = $mech->post( "$rest_base_path/ticket", $payload, 'Authorization' => $auth, 'Content_Type' => 'form-data', 'Content' => [ 'JSON' => $json->encode($payload), 'Attachments' => [$plain_path, $plain_name, 'text/plain'], 'Attachments' => [$html_path, $html_name, 'text/html' ], 'Attachments' => [$img_path, $img_name, 'image/png' ] ]); is( $res->code, 201 ); ok( $ticket_url = $res->header('location') ); ok( ($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)] ); } # Validate that the ticket was created correctly { my $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); my $transaction_id = $ticket->Transactions->Last->id; my $attachments = $ticket->Attachments->ItemsArrayRef; # The 5 attachments are: # 1) Top-level multipart # 2) Top-level ticket content # 3-5) The three attachments added in the Attachments array is( scalar(@$attachments), 5 ); is( $attachments->[0]->ContentType, 'multipart/mixed' ); is( $attachments->[1]->ContentType, 'text/plain' ); is( $attachments->[1]->Content, 'Testing ticket creation, multipart/form-data, with attachments using REST API.' ); is( $attachments->[2]->ContentType, 'text/plain' ); is( $attachments->[2]->Filename, 'plain.txt' ); is( $attachments->[2]->Content, "Hello, World!\n" ); is( $attachments->[3]->ContentType, 'text/html' ); is( $attachments->[3]->Filename, 'html.htm' ); is( $attachments->[3]->Content, "

      Easy as \x{03c0}

      \n" ); is( $attachments->[4]->ContentType, 'image/png' ); is( $attachments->[4]->Filename, 'image.png' ); like( $attachments->[4]->Content, qr/IHDR/, "Looks like a PNG image" ); } # Ticket Creation - with custom field uploads, using multipart/form-data { my $image_cf = RT::Test->load_or_create_custom_field( Name => 'Image', Queue => 'General', Type => 'Image' ); $user->PrincipalObj->GrantRight( Right => $_ ) for qw/SeeCustomField ModifyCustomField/; my $img_name = 'image.png'; my $img_path = RT::Test::get_relocatable_file( $img_name, 'data' ); my $img_content = do { local $/; open my $fh, '<', $img_path or die $!; <$fh>; }; my $payload = { Subject => 'Ticket creation using REST, multipart/form-data, with custom field uploads', Queue => 'General', Content => 'Testing ticket creation, multipart/form-data, with custom field uploads using REST API.', CustomFields => { Image => { UploadField => 'image' } }, }; my $res = $mech->post( "$rest_base_path/ticket", $payload, 'Authorization' => $auth, 'Content_Type' => 'form-data', 'Content' => [ 'JSON' => $json->encode($payload), 'image' => [ $img_path, $img_name, 'image/png' ], ], ); is( $res->code, 201 ); ok( $ticket_url = $res->header('location') ); ok( ($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)] ); my $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); my $value = $ticket->CustomFieldValues( $image_cf )->First; is( $value->Content, 'image.png', 'image file name'); is( $value->LargeContent, $img_content, 'image file content'); } done_testing; rt-5.0.1/t/rest2/ticket-customroles.t000644 000765 000024 00000042111 14005011336 020327 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; BEGIN { plan skip_all => 'RT 4.4 required' unless RT::Handle::cmp_version($RT::VERSION, '4.4.0') >= 0; } use Test::Deep; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; my $queue = RT::Test->load_or_create_queue( Name => "General" ); my $single = RT::CustomRole->new(RT->SystemUser); my ($ok, $msg) = $single->Create(Name => 'Single Member', MaxValues => 1); ok($ok, $msg); my $single_id = $single->Id; ($ok, $msg) = $single->AddToObject($queue->id); ok($ok, $msg); my $multi = RT::CustomRole->new(RT->SystemUser); ($ok, $msg) = $multi->Create(Name => 'Multi Member'); ok($ok, $msg); my $multi_id = $multi->Id; ($ok, $msg) = $multi->AddToObject($queue->id); ok($ok, $msg); for my $email (qw/multi@example.com test@localhost multi2@example.com single2@example.com/) { my $user = RT::User->new(RT->SystemUser); my ($ok, $msg) = $user->Create(Name => $email, EmailAddress => $email); ok($ok, $msg); } $user->PrincipalObj->GrantRight( Right => $_ ) for qw/CreateTicket ShowTicket ModifyTicket OwnTicket AdminUsers SeeGroup SeeQueue/; # Create and view ticket with no watchers { my $payload = { Subject => 'Ticket with no watchers', Queue => 'General', }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok(my $ticket_url = $res->header('location')); ok((my $ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; cmp_deeply($content->{$multi->GroupType}, [], 'no Multi Member'); cmp_deeply($content->{$single->GroupType}, { type => 'user', id => 'Nobody', _url => re(qr{$rest_base_path/user/Nobody$}), }, 'Single Member is Nobody'); cmp_deeply( [grep { $_->{ref} eq 'customrole' } @{ $content->{'_hyperlinks'} }], [{ ref => 'customrole', id => $single_id, type => 'customrole', group_type => $single->GroupType, _url => re(qr[$rest_base_path/customrole/$single_id$]), }, { ref => 'customrole', id => $multi_id, type => 'customrole', group_type => $multi->GroupType, _url => re(qr[$rest_base_path/customrole/$multi_id$]), }], 'Two CF hypermedia', ); my ($single_url) = map { $_->{_url} } grep { $_->{ref} eq 'customrole' && $_->{id} == $single_id } @{ $content->{'_hyperlinks'} }; my ($multi_url) = map { $_->{_url} } grep { $_->{ref} eq 'customrole' && $_->{id} == $multi_id } @{ $content->{'_hyperlinks'} }; $res = $mech->get($content->{$single->GroupType}{_url}, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response, superhashof({ id => RT->Nobody->id, Name => 'Nobody', RealName => 'Nobody in particular', }), 'Nobody user'); $res = $mech->get($single_url, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response, superhashof({ id => $single_id, Disabled => 0, MaxValues => 1, Name => 'Single Member', }), 'single role'); $res = $mech->get($multi_url, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response, superhashof({ id => $multi_id, Disabled => 0, MaxValues => 0, Name => 'Multi Member', }), 'multi role'); } # Create and view ticket with single users as watchers { my $payload = { Subject => 'Ticket with single watchers', Queue => 'General', $multi->GroupType => 'multi@example.com', $single->GroupType => 'test@localhost', }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok(my $ticket_url = $res->header('location')); ok((my $ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; cmp_deeply($content->{$multi->GroupType}, [{ type => 'user', id => 'multi@example.com', _url => re(qr{$rest_base_path/user/multi\@example\.com$}), }], 'one Multi Member'); cmp_deeply($content->{$single->GroupType}, { type => 'user', id => 'test@localhost', _url => re(qr{$rest_base_path/user/test\@localhost$}), }, 'one Single Member'); } # Create and view ticket with multiple users as watchers { my $payload = { Subject => 'Ticket with multiple watchers', Queue => 'General', $multi->GroupType => ['multi@example.com', 'multi2@example.com'], $single->GroupType => ['test@localhost', 'single2@example.com'], }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok(my $ticket_url = $res->header('location')); ok((my $ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; cmp_deeply($content->{$multi->GroupType}, [{ type => 'user', id => 'multi@example.com', _url => re(qr{$rest_base_path/user/multi\@example\.com$}), }, { type => 'user', id => 'multi2@example.com', _url => re(qr{$rest_base_path/user/multi2\@example\.com$}), }], 'two Multi Members'); cmp_deeply($content->{$single->GroupType}, { type => 'user', id => 'test@localhost', _url => re(qr{$rest_base_path/user/test\@localhost}), }, 'one Single Member'); } # Modify single-member role { my $payload = { Subject => 'Ticket for modifying Single Member', Queue => 'General', }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok(my $ticket_url = $res->header('location')); ok((my $ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response->{$single->GroupType}, { type => 'user', id => 'Nobody', _url => re(qr{$rest_base_path/user/Nobody$}), }, 'Single Member is Nobody'); for my $identifier ($user->id, $user->Name) { $payload = { $single->GroupType => $identifier, }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is_deeply($mech->json_response, ["Single Member changed from Nobody to test"], "updated Single Member with identifier $identifier"); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response->{$single->GroupType}, { type => 'user', id => 'test', _url => re(qr{$rest_base_path/user/test$}), }, 'Single Member has changed to test'); $payload = { $single->GroupType => 'Nobody', }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is_deeply($mech->json_response, ["Single Member changed from test to Nobody"], 'updated Single Member'); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response->{$single->GroupType}, { type => 'user', id => 'Nobody', _url => re(qr{$rest_base_path/user/Nobody$}), }, 'Single Member has changed to Nobody'); } } # Modify multi-member roles { my $payload = { Subject => 'Ticket for modifying watchers', Queue => 'General', }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok(my $ticket_url = $res->header('location')); ok((my $ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; cmp_deeply($content->{$multi->GroupType}, [], 'no Multi Member'); $payload = { $multi->GroupType => 'multi@example.com', }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is_deeply($mech->json_response, ['Added multi@example.com as Multi Member for this ticket'], "updated ticket watchers"); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; cmp_deeply($content->{$multi->GroupType}, [{ type => 'user', id => 'multi@example.com', _url => re(qr{$rest_base_path/user/multi\@example\.com$}), }], 'one Multi Member'); $payload = { $multi->GroupType => ['multi2@example.com'], }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is_deeply($mech->json_response, ['Added multi2@example.com as Multi Member for this ticket', 'multi@example.com is no longer Multi Member for this ticket'], "updated ticket watchers"); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; cmp_deeply($content->{$multi->GroupType}, [{ type => 'user', id => 'multi2@example.com', _url => re(qr{$rest_base_path/user/multi2\@example\.com$}), }], 'new Multi Member'); $payload = { $multi->GroupType => ['multi@example.com', 'multi2@example.com'], }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is_deeply($mech->json_response, ['Added multi@example.com as Multi Member for this ticket'], "updated ticket watchers"); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; cmp_deeply($content->{$multi->GroupType}, bag({ type => 'user', id => 'multi2@example.com', _url => re(qr{$rest_base_path/user/multi2\@example\.com$}), }, { type => 'user', id => 'multi@example.com', _url => re(qr{$rest_base_path/user/multi\@example\.com$}), }), 'two Multi Member'); my $users = RT::Users->new(RT->SystemUser); $users->UnLimit; my %user_id = map { $_->Name => $_->Id } @{ $users->ItemsArrayRef }; my @stable_payloads = ( { Subject => 'no changes to watchers', _messages => ["Ticket 5: Subject changed from 'Ticket for modifying watchers' to 'no changes to watchers'"], _name => 'no watcher keys', }, { $multi->GroupType => ['multi@example.com', 'multi2@example.com'], _name => 'identical watcher values', }, { $multi->GroupType => ['multi2@example.com', 'multi@example.com'], _name => 'out of order watcher values', }, { $multi->GroupType => [$user_id{'multi2@example.com'}, $user_id{'multi@example.com'}], _name => 'watcher ids instead of names', }); for my $payload (@stable_payloads) { my $messages = delete $payload->{_messages} || []; my $name = delete $payload->{_name} || '(undef)'; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is_deeply($mech->json_response, $messages, "watchers are preserved when $name"); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; cmp_deeply($content->{$multi->GroupType}, bag({ type => 'user', id => 'multi2@example.com', _url => re(qr{$rest_base_path/user/multi2\@example\.com$}), }, { type => 'user', id => 'multi@example.com', _url => re(qr{$rest_base_path/user/multi\@example\.com$}), }), "preserved two Multi Members when $name"); } } # groups as members { my $group = RT::Group->new(RT->SystemUser); my ($ok, $msg) = $group->CreateUserDefinedGroup(Name => 'Watcher Group'); ok($ok, $msg); my $group_id = $group->Id; for my $email ('multi@example.com', 'multi2@example.com') { my $user = RT::User->new(RT->SystemUser); $user->LoadByEmail($email); $group->AddMember($user->PrincipalId); } my $payload = { Subject => 'Ticket for modifying watchers', Queue => 'General', $multi->GroupType => $group->PrincipalId, }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok(my $ticket_url = $res->header('location')); ok((my $ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; cmp_deeply($content->{$multi->GroupType}, [{ id => $group_id, type => 'group', _url => re(qr{$rest_base_path/group/$group_id$}), }], 'group Multi Member'); $payload = { $multi->GroupType => 'multi@example.com', }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is_deeply($mech->json_response, ['Added multi@example.com as Multi Member for this ticket', 'Watcher Group is no longer Multi Member for this ticket'], "updated ticket watchers"); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; cmp_deeply($content->{$multi->GroupType}, [{ type => 'user', id => 'multi@example.com', _url => re(qr{$rest_base_path/user/multi\@example\.com$}), }], 'one Multi Member user'); $payload = { $multi->GroupType => [$group_id, 'multi@example.com'], }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is_deeply($mech->json_response, ['Added Watcher Group as Multi Member for this ticket'], "updated ticket watchers"); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; cmp_deeply($content->{$multi->GroupType}, [{ type => 'user', id => 'multi@example.com', _url => re(qr{$rest_base_path/user/multi\@example\.com$}), }, { id => $group_id, type => 'group', _url => re(qr{$rest_base_path/group/$group_id$}), }], 'Multi Member user and group'); $res = $mech->get($content->{$multi->GroupType}[1]{_url}, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response, superhashof({ id => $group->Id, Name => 'Watcher Group', Domain => 'UserDefined', CustomFields => [], Members => [{ type => 'user', id => 'multi@example.com', _url => re(qr{$rest_base_path/user/multi\@example\.com$}), }, { type => 'user', id => 'multi2@example.com', _url => re(qr{$rest_base_path/user/multi2\@example\.com$}), }], }), 'fetched group'); } { my $payload = { Subject => 'Test custom rules applied later', Queue => 'General', }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok(my $ticket_url = $res->header('location')); ok((my $ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); my $later_single = RT::CustomRole->new(RT->SystemUser); ($ok, $msg) = $later_single->Create(Name => 'Later Single Member', MaxValues => 1); ok($ok, $msg); my $later_single_id = $later_single->Id; ($ok, $msg) = $later_single->AddToObject($queue->id); ok($ok, $msg); my $later_multi = RT::CustomRole->new(RT->SystemUser); ($ok, $msg) = $later_multi->Create(Name => 'Later Multi Member'); ok($ok, $msg); my $later_multi_id = $later_multi->Id; ($ok, $msg) = $later_multi->AddToObject($queue->id); ok($ok, $msg); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; cmp_deeply($content->{$later_multi->GroupType}, [], 'no Later Multi Member'); cmp_deeply($content->{$later_single->GroupType}, { type => 'user', id => 'Nobody', _url => re(qr{$rest_base_path/user/Nobody$}), }, 'Later Single Member is Nobody'); } done_testing; rt-5.0.1/t/rest2/asset-customfields.t000644 000765 000024 00000033422 14005011336 020312 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; use Test::Deep; BEGIN { plan skip_all => 'RT 4.4 required' unless RT::Handle::cmp_version($RT::VERSION, '4.4.0') >= 0; } my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; my $catalog = RT::Catalog->new( RT->SystemUser ); $catalog->Load('General assets'); $catalog->Create(Name => 'General assets') if !$catalog->Id; ok($catalog->Id, "General assets catalog"); my $single_cf = RT::CustomField->new( RT->SystemUser ); my ($ok, $msg) = $single_cf->Create( Name => 'Single', Type => 'FreeformSingle', LookupType => RT::Asset->CustomFieldLookupType); ok($ok, $msg); my $single_cf_id = $single_cf->Id; ($ok, $msg) = $single_cf->AddToObject($catalog); ok($ok, $msg); my $multi_cf = RT::CustomField->new( RT->SystemUser ); ($ok, $msg) = $multi_cf->Create( Name => 'Multi', Type => 'FreeformMultiple', LookupType => RT::Asset->CustomFieldLookupType); ok($ok, $msg); my $multi_cf_id = $multi_cf->Id; ($ok, $msg) = $multi_cf->AddToObject($catalog); ok($ok, $msg); # Asset Creation with no ModifyCustomField my ($asset_url, $asset_id); { my $payload = { Name => 'Asset creation using REST', Catalog => 'General assets', CustomFields => { $single_cf_id => 'Hello world!', }, }; # Rights Test - No CreateAsset my $res = $mech->post_json("$rest_base_path/asset", $payload, 'Authorization' => $auth, ); is($res->code, 403); my $content = $mech->json_response; is($content->{message}, 'Permission Denied', "can't create Asset with custom fields you can't set"); # Rights Test - With CreateAsset $user->PrincipalObj->GrantRight( Right => 'CreateAsset' ); $res = $mech->post_json("$rest_base_path/asset", $payload, 'Authorization' => $auth, ); is($res->code, 400); delete $payload->{CustomFields}; $res = $mech->post_json("$rest_base_path/asset", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($asset_url = $res->header('location')); ok(($asset_id) = $asset_url =~ qr[/asset/(\d+)]); } # Asset Display { # Rights Test - No ShowAsset my $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 403); } # Rights Test - With ShowAsset but no SeeCustomField { $user->PrincipalObj->GrantRight( Right => 'ShowAsset' ); my $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $asset_id); is($content->{Status}, 'new'); is($content->{Name}, 'Asset creation using REST'); is_deeply($content->{'CustomFields'}, [], 'Asset custom field not present'); is_deeply([grep { $_->{ref} eq 'customfield' } @{ $content->{'_hyperlinks'} }], [], 'No CF hypermedia'); } my $no_asset_cf_values = bag( { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); # Rights Test - With ShowAsset and SeeCustomField { $user->PrincipalObj->GrantRight( Right => 'SeeCustomField'); my $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $asset_id); is($content->{Status}, 'new'); is($content->{Name}, 'Asset creation using REST'); cmp_deeply($content->{CustomFields}, $no_asset_cf_values, 'No asset custom field values'); cmp_deeply( [grep { $_->{ref} eq 'customfield' } @{ $content->{'_hyperlinks'} }], [{ ref => 'customfield', id => $single_cf_id, name => 'Single', type => 'customfield', _url => re(qr[$rest_base_path/customfield/$single_cf_id$]), }, { ref => 'customfield', id => $multi_cf_id, name => 'Multi', type => 'customfield', _url => re(qr[$rest_base_path/customfield/$multi_cf_id$]), }], 'Two CF hypermedia', ); my ($single_url) = map { $_->{_url} } grep { $_->{ref} eq 'customfield' && $_->{id} == $single_cf_id } @{ $content->{'_hyperlinks'} }; my ($multi_url) = map { $_->{_url} } grep { $_->{ref} eq 'customfield' && $_->{id} == $multi_cf_id } @{ $content->{'_hyperlinks'} }; $res = $mech->get($single_url, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response, superhashof({ id => $single_cf_id, Disabled => 0, LookupType => RT::Asset->CustomFieldLookupType, MaxValues => 1, Name => 'Single', Type => 'Freeform', }), 'single cf'); $res = $mech->get($multi_url, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response, superhashof({ id => $multi_cf_id, Disabled => 0, LookupType => RT::Asset->CustomFieldLookupType, MaxValues => 0, Name => 'Multi', Type => 'Freeform', }), 'multi cf'); } # Asset Update without ModifyCustomField { my $payload = { Name => 'Asset update using REST', Status => 'allocated', CustomFields => { $single_cf_id => 'Modified CF', }, }; # Rights Test - No ModifyAsset my $res = $mech->put_json($asset_url, $payload, 'Authorization' => $auth, ); TODO: { local $TODO = "RT ->Update isn't introspectable"; is($res->code, 403); }; is_deeply($mech->json_response, ['Asset Asset creation using REST: Permission Denied', 'Asset Asset creation using REST: Permission Denied', 'Could not add new custom field value: Permission Denied']); $user->PrincipalObj->GrantRight( Right => 'ModifyAsset' ); $res = $mech->put_json($asset_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ["Asset Asset update using REST: Name changed from 'Asset creation using REST' to 'Asset update using REST'", "Asset Asset update using REST: Status changed from 'new' to 'allocated'", 'Could not add new custom field value: Permission Denied']); $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{Name}, 'Asset update using REST'); is($content->{Status}, 'allocated'); cmp_deeply($content->{CustomFields}, $no_asset_cf_values, 'No update to CF'); } # Asset Update with ModifyCustomField { $user->PrincipalObj->GrantRight( Right => 'ModifyCustomField' ); my $payload = { Name => 'More updates using REST', Status => 'in-use', CustomFields => { $single_cf_id => 'Modified CF', }, }; my $res = $mech->put_json($asset_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ["Asset More updates using REST: Name changed from 'Asset update using REST' to 'More updates using REST'", "Asset More updates using REST: Status changed from 'allocated' to 'in-use'", 'Single Modified CF added']); $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); my $modified_asset_cf_values = bag( { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Modified CF'] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); my $content = $mech->json_response; is($content->{Name}, 'More updates using REST'); is($content->{Status}, 'in-use'); cmp_deeply($content->{CustomFields}, $modified_asset_cf_values, 'New CF value'); # make sure changing the CF doesn't add a second OCFV $payload->{CustomFields}{$single_cf_id} = 'Modified Again'; $res = $mech->put_json($asset_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ['Single Modified CF changed to Modified Again']); $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); $modified_asset_cf_values = bag( { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Modified Again'] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); $content = $mech->json_response; cmp_deeply($content->{CustomFields}, $modified_asset_cf_values, 'New CF value'); # stop changing the CF, change something else, make sure CF sticks around delete $payload->{CustomFields}{$single_cf_id}; $payload->{Name} = 'No CF change'; $res = $mech->put_json($asset_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ["Asset No CF change: Name changed from 'More updates using REST' to 'No CF change'"]); $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; cmp_deeply($content->{CustomFields}, $modified_asset_cf_values, 'Same CF value'); } # Asset Creation with ModifyCustomField { my $payload = { Name => 'Asset creation using REST', Catalog => 'General assets', CustomFields => { $single_cf_id => 'Hello world!', }, }; my $res = $mech->post_json("$rest_base_path/asset", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($asset_url = $res->header('location')); ok(($asset_id) = $asset_url =~ qr[/asset/(\d+)]); } # Rights Test - With ShowAsset and SeeCustomField { my $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); my $asset_cf_values = bag( { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Hello world!'] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); my $content = $mech->json_response; is($content->{id}, $asset_id); is($content->{Status}, 'new'); is($content->{Name}, 'Asset creation using REST'); cmp_deeply($content->{'CustomFields'}, $asset_cf_values, 'Asset custom field'); } # Asset Creation for multi-value CF for my $value ( 'scalar', ['array reference'], ['multiple', 'values'], ) { my $payload = { Name => 'Multi-value CF', Catalog => 'General assets', CustomFields => { $multi_cf_id => $value, }, }; my $res = $mech->post_json("$rest_base_path/asset", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($asset_url = $res->header('location')); ok(($asset_id) = $asset_url =~ qr[/asset/(\d+)]); $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $asset_id); is($content->{Status}, 'new'); is($content->{Name}, 'Multi-value CF'); my $output = ref($value) ? $value : [$value]; # scalar input comes out as array reference my $asset_cf_values = bag( { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => $output }, ); cmp_deeply($content->{'CustomFields'}, $asset_cf_values, 'Asset custom field'); } { sub modify_multi_ok { local $Test::Builder::Level = $Test::Builder::Level + 1; my $input = shift; my $messages = shift; my $output = shift; my $name = shift; my $payload = { CustomFields => { $multi_cf_id => $input, }, }; my $res = $mech->put_json($asset_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, $messages); $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; my $values; for my $cf (@{ $content->{CustomFields} }) { next unless $cf->{id} == $multi_cf_id; $values = [ sort @{ $cf->{values} } ]; } cmp_deeply($values, $output, $name || 'New CF value'); } # starting point: ['multiple', 'values'], modify_multi_ok(['multiple', 'values'], [], ['multiple', 'values'], 'no change'); modify_multi_ok(['multiple', 'values', 'new'], ['new added as a value for Multi'], ['multiple', 'new', 'values'], 'added "new"'); modify_multi_ok(['multiple', 'new'], ['values is no longer a value for custom field Multi'], ['multiple', 'new'], 'removed "values"'); modify_multi_ok('replace all', ['replace all added as a value for Multi', 'multiple is no longer a value for custom field Multi', 'new is no longer a value for custom field Multi'], ['replace all'], 'replaced all values'); modify_multi_ok([], ['replace all is no longer a value for custom field Multi'], [], 'removed all values'); modify_multi_ok(['foo', 'foo', 'bar'], ['foo added as a value for Multi', undef, 'bar added as a value for Multi'], ['bar', 'foo'], 'multiple values with the same name'); modify_multi_ok(['foo', 'bar'], [], ['bar', 'foo'], 'multiple values with the same name'); modify_multi_ok(['bar'], ['foo is no longer a value for custom field Multi'], ['bar'], 'multiple values with the same name'); modify_multi_ok(['bar', 'bar', 'bar'], [undef, undef], ['bar'], 'multiple values with the same name'); } done_testing; rt-5.0.1/t/rest2/assets.t000644 000765 000024 00000016157 14005011336 016004 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; use Test::Deep; BEGIN { plan skip_all => 'RT 4.4 required' unless RT::Handle::cmp_version($RT::VERSION, '4.4.0') >= 0; } my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; # Empty DB { my $res = $mech->post_json("$rest_base_path/assets", [{ field => 'id', operator => '>', value => 0 }], 'Authorization' => $auth, ); is($res->code, 200); is($mech->json_response->{count}, 0); } # Missing Catalog { my $res = $mech->post_json("$rest_base_path/asset", { Name => 'Asset creation using REST', }, 'Authorization' => $auth, ); is($res->code, 400); is($mech->json_response->{message}, 'Invalid Catalog'); } # Asset Creation my ($asset_url, $asset_id); { my $payload = { Name => 'Asset creation using REST', Description => 'Asset description', Catalog => 'General assets', Content => 'Testing asset creation using REST API.', }; # Rights Test - No CreateAsset my $res = $mech->post_json("$rest_base_path/asset", $payload, 'Authorization' => $auth, ); is($res->code, 403); # Rights Test - With CreateAsset $user->PrincipalObj->GrantRight( Right => 'CreateAsset' ); $res = $mech->post_json("$rest_base_path/asset", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($asset_url = $res->header('location')); ok(($asset_id) = $asset_url =~ qr[/asset/(\d+)]); } # Asset Display { # Rights Test - No ShowAsset my $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 403); } # Rights Test - With ShowAsset { $user->PrincipalObj->GrantRight( Right => 'ShowAsset' ); my $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $asset_id); is($content->{Name}, 'Asset creation using REST'); is($content->{Status}, 'new'); is($content->{Name}, 'Asset creation using REST'); ok(exists $content->{$_}) for qw(Creator Created LastUpdated LastUpdatedBy HeldBy Contact Description); my $links = $content->{_hyperlinks}; is(scalar @$links, 2); is($links->[0]{ref}, 'self'); is($links->[0]{id}, 1); is($links->[0]{type}, 'asset'); like($links->[0]{_url}, qr[$rest_base_path/asset/$asset_id$]); is($links->[1]{ref}, 'history'); like($links->[1]{_url}, qr[$rest_base_path/asset/$asset_id/history$]); my $catalog = $content->{Catalog}; is($catalog->{id}, 1); is($catalog->{type}, 'catalog'); like($catalog->{_url}, qr{$rest_base_path/catalog/1$}); my $owner = $content->{Owner}; is($owner->{id}, 'Nobody'); is($owner->{type}, 'user'); like($owner->{_url}, qr{$rest_base_path/user/Nobody$}); my $creator = $content->{Creator}; is($creator->{id}, 'test'); is($creator->{type}, 'user'); like($creator->{_url}, qr{$rest_base_path/user/test$}); my $updated_by = $content->{LastUpdatedBy}; is($updated_by->{id}, 'test'); is($updated_by->{type}, 'user'); like($updated_by->{_url}, qr{$rest_base_path/user/test$}); } # Asset Search { my $res = $mech->post_json("$rest_base_path/assets", [{ field => 'id', operator => '>', value => 0 }], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 1); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 1); is(scalar @{$content->{items}}, 1); my $asset = $content->{items}->[0]; is($asset->{type}, 'asset'); is($asset->{id}, 1); like($asset->{_url}, qr{$rest_base_path/asset/1$}); } # Asset Update { my $payload = { Name => 'Asset update using REST', Status => 'allocated', }; # Rights Test - No ModifyAsset my $res = $mech->put_json($asset_url, $payload, 'Authorization' => $auth, ); TODO: { local $TODO = "RT ->Update isn't introspectable"; is($res->code, 403); }; is_deeply($mech->json_response, ['Asset Asset creation using REST: Permission Denied', 'Asset Asset creation using REST: Permission Denied']); $user->PrincipalObj->GrantRight( Right => 'ModifyAsset' ); $res = $mech->put_json($asset_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ["Asset Asset update using REST: Name changed from 'Asset creation using REST' to 'Asset update using REST'", "Asset Asset update using REST: Status changed from 'new' to 'allocated'"]); $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{Name}, 'Asset update using REST'); is($content->{Status}, 'allocated'); # now that we have ModifyAsset, we should have additional hypermedia my $links = $content->{_hyperlinks}; is(scalar @$links, 5); is($links->[0]{ref}, 'self'); is($links->[0]{id}, 1); is($links->[0]{type}, 'asset'); like($links->[0]{_url}, qr[$rest_base_path/asset/$asset_id$]); is($links->[1]{ref}, 'history'); like($links->[1]{_url}, qr[$rest_base_path/asset/$asset_id/history$]); is($links->[2]{ref}, 'lifecycle'); like($links->[2]{_url}, qr[$rest_base_path/asset/$asset_id$]); is($links->[2]{label}, 'Now in-use'); is($links->[2]{from}, '*'); is($links->[2]{to}, 'in-use'); is($links->[3]{ref}, 'lifecycle'); like($links->[3]{_url}, qr[$rest_base_path/asset/$asset_id$]); is($links->[3]{label}, 'Recycle'); is($links->[3]{from}, '*'); is($links->[3]{to}, 'recycled'); is($links->[4]{ref}, 'lifecycle'); like($links->[4]{_url}, qr[$rest_base_path/asset/$asset_id$]); is($links->[4]{label}, 'Report stolen'); is($links->[4]{from}, '*'); is($links->[4]{to}, 'stolen'); # update again with no changes $res = $mech->put_json($asset_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, []); $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; is($content->{Name}, 'Asset update using REST'); is($content->{Status}, 'allocated'); } # Transactions { my $res = $mech->get($asset_url, 'Authorization' => $auth, ); is($res->code, 200); $res = $mech->get($mech->url_for_hypermedia('history'), 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 3); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 3); is(scalar @{$content->{items}}, 3); for my $txn (@{ $content->{items} }) { is($txn->{type}, 'transaction'); like($txn->{_url}, qr{$rest_base_path/transaction/\d+$}); } } done_testing; rt-5.0.1/t/rest2/tickets-bulk.t000644 000765 000024 00000013114 14005011336 017071 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; use Test::Deep; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; my $base_url = RT::REST2->base_uri; my $queue = RT::Test->load_or_create_queue( Name => "General" ); my @ticket_ids; { my $res = $mech->post_json( "$rest_base_path/tickets/bulk", { Queue => "General", Subject => "test" }, 'Authorization' => $auth, ); is( $res->code, 400 ); is( $mech->json_response->{message}, "JSON object must be a ARRAY", 'hash is not allowed' ); diag "no CreateTicket right"; $res = $mech->post_json( "$rest_base_path/tickets/bulk", [ { Queue => "General", Subject => "test" } ], 'Authorization' => $auth, ); is( $res->code, 201, "bulk returns 201 for POST even no tickets created" ); is_deeply( $mech->json_response, [ { message => "No permission to create tickets in the queue 'General'" } ], 'permission denied' ); diag "grant CreateTicket right"; $user->PrincipalObj->GrantRight( Right => 'CreateTicket' ); $res = $mech->post_json( "$rest_base_path/tickets/bulk", [ { Queue => "General", Subject => "test" } ], 'Authorization' => $auth, ); is( $res->code, 201, 'status code' ); my $content = $mech->json_response; is( scalar @$content, 1, 'array with 1 item' ); ok( $content->[ 0 ]{id}, 'found id' ); push @ticket_ids, $content->[ 0 ]{id}; is_deeply( $content, [ { type => 'ticket', id => $ticket_ids[ -1 ], "_url" => "$base_url/ticket/$ticket_ids[-1]", } ], 'json response content', ); $res = $mech->post_json( "$rest_base_path/tickets/bulk", [ { Queue => 'General', Subject => 'foo' }, { Queue => 'General', Subject => 'bar' } ], 'Authorization' => $auth, ); is( $res->code, 201, 'status code' ); $content = $mech->json_response; is( scalar @$content, 2, 'array with 2 items' ); push @ticket_ids, $_->{id} for @$content; is_deeply( $content, [ { type => 'ticket', id => $ticket_ids[ -2 ], "_url" => "$base_url/ticket/$ticket_ids[-2]", }, { type => 'ticket', id => $ticket_ids[ -1 ], "_url" => "$base_url/ticket/$ticket_ids[-1]", }, ], 'json response content', ); $res = $mech->post_json( "$rest_base_path/tickets/bulk", [ { Subject => 'foo' }, { Queue => 'General', Subject => 'baz' } ], 'Authorization' => $auth, ); is( $res->code, 201, 'status code' ); $content = $mech->json_response; is( scalar @$content, 2, 'array with 2 items' ); push @ticket_ids, $content->[ 1 ]{id}; is_deeply( $content, [ { message => "Could not create ticket. Queue not set" }, { type => 'ticket', id => $ticket_ids[ -1 ], "_url" => "$base_url/ticket/$ticket_ids[-1]", }, ], 'json response content', ); } { diag "no ModifyTicket right"; my $res = $mech->put_json( "$rest_base_path/tickets/bulk", [ { id => $ticket_ids[ 0 ], Subject => 'foo' } ], 'Authorization' => $auth, ); is( $res->code, 200, "bulk returns 200 for PUT" ); is_deeply( $mech->json_response, [ [ $ticket_ids[ 0 ], "Ticket 1: Permission Denied", ] ], 'permission denied' ); diag "grant ModifyTicket right"; $user->PrincipalObj->GrantRight( Right => 'ModifyTicket' ); $res = $mech->put_json( "$rest_base_path/tickets/bulk", [ { id => $ticket_ids[ 0 ], Subject => 'foo' } ], 'Authorization' => $auth, ); is( $res->code, 200, 'status code' ); is_deeply( $mech->json_response, [ [ $ticket_ids[ 0 ], qq{Ticket 1: Subject changed from 'test' to 'foo'} ] ], 'json response content' ); $res = $mech->put_json( "$rest_base_path/tickets/bulk", [ { id => $ticket_ids[ 0 ] }, { id => $ticket_ids[ 1 ], Subject => 'bar' }, ], 'Authorization' => $auth, ); is( $res->code, 200, 'status code' ); is_deeply( $mech->json_response, [ [ $ticket_ids[ 0 ] ], [ $ticket_ids[ 1 ], qq{Ticket $ticket_ids[ 1 ]: Subject changed from 'foo' to 'bar'} ] ], 'json response content' ); $res = $mech->put_json( "$rest_base_path/tickets/bulk", [ { id => $ticket_ids[ 0 ], Subject => 'baz' }, { id => 'foo', Subject => 'baz' }, { id => 999, Subject => 'baz' }, ], 'Authorization' => $auth, ); is( $res->code, 200, 'status code' ); is_deeply( $mech->json_response, [ [ $ticket_ids[ 0 ], qq{Ticket $ticket_ids[ 0 ]: Subject changed from 'foo' to 'baz'} ], [ 'foo', "Resource does not exist" ], [ 999, "Resource does not exist" ], ], 'json response content' ); } { for my $method ( qw/get head delete/ ) { my $res = $mech->get( "$rest_base_path/tickets/bulk", 'Authorization' => $auth ); is( $res->code, 405, "tickets/bulk doesn't support " . uc $method ); } } done_testing; rt-5.0.1/t/rest2/ticket-watchers.t000644 000765 000024 00000044310 14005011336 017573 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; use Test::Deep; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; my $queue = RT::Test->load_or_create_queue( Name => "General" ); $user->PrincipalObj->GrantRight( Right => $_ ) for qw/CreateTicket ShowTicket ModifyTicket OwnTicket AdminUsers SeeGroup/; # Create and view ticket with no watchers { my $payload = { Subject => 'Ticket with no watchers', Queue => 'General', }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok(my $ticket_url = $res->header('location')); ok((my $ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; cmp_deeply($content->{Requestor}, [], 'no Requestor'); cmp_deeply($content->{Cc}, [], 'no Cc'); cmp_deeply($content->{AdminCc}, [], 'no AdminCc'); cmp_deeply($content->{Owner}, { type => 'user', id => 'Nobody', _url => re(qr{$rest_base_path/user/Nobody$}), }, 'Owner is Nobody'); $res = $mech->get($content->{Owner}{_url}, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response, superhashof({ id => RT->Nobody->id, Name => 'Nobody', RealName => 'Nobody in particular', }), 'Nobody user'); } # Create and view ticket with single users as watchers { my $payload = { Subject => 'Ticket with single watchers', Queue => 'General', Requestor => 'requestor@example.com', Cc => 'cc@example.com', AdminCc => 'admincc@example.com', Owner => $user->PrincipalId, }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok(my $ticket_url = $res->header('location')); ok((my $ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; cmp_deeply($content->{Requestor}, [{ type => 'user', id => 'requestor@example.com', _url => re(qr{$rest_base_path/user/requestor\@example\.com$}), }], 'one Requestor'); cmp_deeply($content->{Cc}, [{ type => 'user', id => 'cc@example.com', _url => re(qr{$rest_base_path/user/cc\@example\.com$}), }], 'one Cc'); cmp_deeply($content->{AdminCc}, [{ type => 'user', id => 'admincc@example.com', _url => re(qr{$rest_base_path/user/admincc\@example\.com$}), }], 'one AdminCc'); cmp_deeply($content->{Owner}, { type => 'user', id => 'test', _url => re(qr{$rest_base_path/user/test$}), }, 'Owner is REST test user'); } # Create and view ticket with multiple users as watchers { my $payload = { Subject => 'Ticket with multiple watchers', Queue => 'General', Requestor => ['requestor@example.com', 'requestor2@example.com'], Cc => ['cc@example.com', 'cc2@example.com'], AdminCc => ['admincc@example.com', 'admincc2@example.com'], Owner => $user->PrincipalId, }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok(my $ticket_url = $res->header('location')); ok((my $ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; cmp_deeply($content->{Requestor}, [{ type => 'user', id => 'requestor@example.com', _url => re(qr{$rest_base_path/user/requestor\@example\.com$}), }, { type => 'user', id => 'requestor2@example.com', _url => re(qr{$rest_base_path/user/requestor2\@example\.com$}), }], 'two Requestors'); cmp_deeply($content->{Cc}, [{ type => 'user', id => 'cc@example.com', _url => re(qr{$rest_base_path/user/cc\@example\.com$}), }, { type => 'user', id => 'cc2@example.com', _url => re(qr{$rest_base_path/user/cc2\@example\.com$}), }], 'two Ccs'); cmp_deeply($content->{AdminCc}, [{ type => 'user', id => 'admincc@example.com', _url => re(qr{$rest_base_path/user/admincc\@example\.com$}), }, { type => 'user', id => 'admincc2@example.com', _url => re(qr{$rest_base_path/user/admincc2\@example\.com$}), }], 'two AdminCcs'); cmp_deeply($content->{Owner}, { type => 'user', id => 'test', _url => re(qr{$rest_base_path/user/test$}), }, 'Owner is REST test user'); } # Modify owner { my $payload = { Subject => 'Ticket for modifying owner', Queue => 'General', }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok(my $ticket_url = $res->header('location')); ok((my $ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response->{Owner}, { type => 'user', id => 'Nobody', _url => re(qr{$rest_base_path/user/Nobody$}), }, 'Owner is Nobody'); for my $identifier ($user->id, $user->Name) { $payload = { Owner => $identifier, }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is_deeply($mech->json_response, ["Ticket $ticket_id: Owner changed from Nobody to test"], "updated Owner with identifier $identifier"); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response->{Owner}, { type => 'user', id => 'test', _url => re(qr{$rest_base_path/user/test$}), }, 'Owner has changed to test'); $payload = { Owner => 'Nobody', }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is_deeply($mech->json_response, ["Ticket $ticket_id: Owner changed from test to Nobody"], 'updated Owner'); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response->{Owner}, { type => 'user', id => 'Nobody', _url => re(qr{$rest_base_path/user/Nobody$}), }, 'Owner has changed to Nobody'); } } # Modify multi-member roles { my $payload = { Subject => 'Ticket for modifying watchers', Queue => 'General', }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok(my $ticket_url = $res->header('location')); ok((my $ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; cmp_deeply($content->{Requestor}, [], 'no Requestor'); cmp_deeply($content->{Cc}, [], 'no Cc'); cmp_deeply($content->{AdminCc}, [], 'no AdminCc'); $payload = { Requestor => 'requestor@example.com', Cc => 'cc@example.com', AdminCc => 'admincc@example.com', }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); cmp_deeply($mech->json_response, [ re(qr/Added admincc\@example.com as( a)? AdminCc for this ticket/), re(qr/Added cc\@example.com as( a)? Cc for this ticket/), re(qr/Added requestor\@example.com as( a)? Requestor for this ticket/) ], "updated ticket watchers"); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; cmp_deeply($content->{Requestor}, [{ type => 'user', id => 'requestor@example.com', _url => re(qr{$rest_base_path/user/requestor\@example\.com$}), }], 'one Requestor'); cmp_deeply($content->{Cc}, [{ type => 'user', id => 'cc@example.com', _url => re(qr{$rest_base_path/user/cc\@example\.com$}), }], 'one Cc'); cmp_deeply($content->{AdminCc}, [{ type => 'user', id => 'admincc@example.com', _url => re(qr{$rest_base_path/user/admincc\@example\.com$}), }], 'one AdminCc'); $payload = { Requestor => ['requestor2@example.com'], Cc => ['cc2@example.com'], AdminCc => ['admincc2@example.com'], }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); cmp_deeply($mech->json_response, [ re(qr/Added admincc2\@example.com as( a)? AdminCc for this ticket/), re(qr/admincc\@example.com is no longer( a)? AdminCc for this ticket/), re(qr/Added cc2\@example.com as( a)? Cc for this ticket/), re(qr/cc\@example.com is no longer( a)? Cc for this ticket/), re(qr/Added requestor2\@example.com as( a)? Requestor for this ticket/), re(qr/requestor\@example.com is no longer( a)? Requestor for this ticket/), ], "updated ticket watchers"); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; cmp_deeply($content->{Requestor}, [{ type => 'user', id => 'requestor2@example.com', _url => re(qr{$rest_base_path/user/requestor2\@example\.com$}), }], 'new Requestor'); cmp_deeply($content->{Cc}, [{ type => 'user', id => 'cc2@example.com', _url => re(qr{$rest_base_path/user/cc2\@example\.com$}), }], 'new Cc'); cmp_deeply($content->{AdminCc}, [{ type => 'user', id => 'admincc2@example.com', _url => re(qr{$rest_base_path/user/admincc2\@example\.com$}), }], 'new AdminCc'); $payload = { Requestor => ['requestor@example.com', 'requestor2@example.com'], Cc => ['cc@example.com', 'cc2@example.com'], AdminCc => ['admincc@example.com', 'admincc2@example.com'], }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); cmp_deeply($mech->json_response, [ re(qr/Added admincc\@example.com as( a)? AdminCc for this ticket/), re(qr/Added cc\@example.com as( a)? Cc for this ticket/), re(qr/Added requestor\@example.com as( a)? Requestor for this ticket/) ], "updated ticket watchers"); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; cmp_deeply($content->{Requestor}, bag({ type => 'user', id => 'requestor2@example.com', _url => re(qr{$rest_base_path/user/requestor2\@example\.com$}), }, { type => 'user', id => 'requestor@example.com', _url => re(qr{$rest_base_path/user/requestor\@example\.com$}), }), 'two Requestors'); cmp_deeply($content->{Cc}, bag({ type => 'user', id => 'cc2@example.com', _url => re(qr{$rest_base_path/user/cc2\@example\.com$}), }, { type => 'user', id => 'cc@example.com', _url => re(qr{$rest_base_path/user/cc\@example\.com$}), }), 'two Ccs'); cmp_deeply($content->{AdminCc}, bag({ type => 'user', id => 'admincc2@example.com', _url => re(qr{$rest_base_path/user/admincc2\@example\.com$}), }, { type => 'user', id => 'admincc@example.com', _url => re(qr{$rest_base_path/user/admincc\@example\.com$}), }), 'two AdminCcs'); my $users = RT::Users->new(RT->SystemUser); $users->UnLimit; my %user_id = map { $_->Name => $_->Id } @{ $users->ItemsArrayRef }; my @stable_payloads = ( { Subject => 'no changes to watchers', _messages => ["Ticket 5: Subject changed from 'Ticket for modifying watchers' to 'no changes to watchers'"], _name => 'no watcher keys', }, { Requestor => ['requestor@example.com', 'requestor2@example.com'], Cc => ['cc@example.com', 'cc2@example.com'], AdminCc => ['admincc@example.com', 'admincc2@example.com'], _name => 'identical watcher values', }, { Requestor => ['requestor2@example.com', 'requestor@example.com'], Cc => ['cc2@example.com', 'cc@example.com'], AdminCc => ['admincc2@example.com', 'admincc@example.com'], _name => 'out of order watcher values', }, { Requestor => [$user_id{'requestor2@example.com'}, $user_id{'requestor@example.com'}], Cc => [$user_id{'cc2@example.com'}, $user_id{'cc@example.com'}], AdminCc => [$user_id{'admincc2@example.com'}, $user_id{'admincc@example.com'}], _name => 'watcher ids instead of names', }); for my $payload (@stable_payloads) { my $messages = delete $payload->{_messages} || []; my $name = delete $payload->{_name} || '(undef)'; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is_deeply($mech->json_response, $messages, "watchers are preserved when $name"); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; cmp_deeply($content->{Requestor}, bag({ type => 'user', id => 'requestor2@example.com', _url => re(qr{$rest_base_path/user/requestor2\@example\.com$}), }, { type => 'user', id => 'requestor@example.com', _url => re(qr{$rest_base_path/user/requestor\@example\.com$}), }), "preserved two Requestors when $name"); cmp_deeply($content->{Cc}, bag({ type => 'user', id => 'cc2@example.com', _url => re(qr{$rest_base_path/user/cc2\@example\.com$}), }, { type => 'user', id => 'cc@example.com', _url => re(qr{$rest_base_path/user/cc\@example\.com$}), }), "preserved two Ccs when $name"); cmp_deeply($content->{AdminCc}, bag({ type => 'user', id => 'admincc2@example.com', _url => re(qr{$rest_base_path/user/admincc2\@example\.com$}), }, { type => 'user', id => 'admincc@example.com', _url => re(qr{$rest_base_path/user/admincc\@example\.com$}), }), "preserved two AdminCcs when $name"); } } # groups as members { my $group = RT::Group->new(RT->SystemUser); my ($ok, $msg) = $group->CreateUserDefinedGroup(Name => 'Watcher Group'); ok($ok, $msg); my $group_id = $group->Id; for my $email ('admincc@example.com', 'admincc2@example.com') { my $user = RT::User->new(RT->SystemUser); $user->LoadByEmail($email); $group->AddMember($user->PrincipalId); } my $payload = { Subject => 'Ticket for modifying watchers', Queue => 'General', AdminCc => $group->PrincipalId, }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok(my $ticket_url = $res->header('location')); ok((my $ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; cmp_deeply($content->{AdminCc}, [{ id => $group_id, type => 'group', _url => re(qr{$rest_base_path/group/$group_id$}), }], 'group AdminCc'); $payload = { AdminCc => 'admincc@example.com', }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); cmp_deeply($mech->json_response, [ re(qr/Added admincc\@example.com as( a)? AdminCc for this ticket/), re(qr/Watcher Group is no longer( a)? AdminCc for this ticket/), ], "updated ticket watchers"); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; cmp_deeply($content->{AdminCc}, [{ type => 'user', id => 'admincc@example.com', _url => re(qr{$rest_base_path/user/admincc\@example\.com$}), }], 'one AdminCc user'); $payload = { AdminCc => [$group_id, 'admincc@example.com'], }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); cmp_deeply($mech->json_response, [ re(qr/Added Watcher Group as( a)? AdminCc for this ticket/), ], "updated ticket watchers"); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; cmp_deeply($content->{AdminCc}, [{ type => 'user', id => 'admincc@example.com', _url => re(qr{$rest_base_path/user/admincc\@example\.com$}), }, { id => $group_id, type => 'group', _url => re(qr{$rest_base_path/group/$group_id$}), }], 'AdminCc user and group'); $res = $mech->get($content->{AdminCc}[1]{_url}, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response, superhashof({ id => $group->Id, Name => 'Watcher Group', Domain => 'UserDefined', CustomFields => [], Members => [{ type => 'user', id => 'admincc@example.com', _url => re(qr{$rest_base_path/user/admincc\@example\.com$}), }, { type => 'user', id => 'admincc2@example.com', _url => re(qr{$rest_base_path/user/admincc2\@example\.com$}), }], }), 'fetched group'); } done_testing; rt-5.0.1/t/rest2/ticket-links.t000644 000765 000024 00000010146 14005011336 017073 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; use Test::Deep; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; my $queue = RT::Test->load_or_create_queue( Name => "General" ); my $parent = RT::Ticket->new(RT->SystemUser); my ($ok, undef, $msg) = $parent->Create(Queue => 'General', Subject => 'parent ticket'); ok($ok, $msg); my $parent_id = $parent->Id; my $child = RT::Ticket->new(RT->SystemUser); ($ok, undef, $msg) = $child->Create(Queue => 'General', Subject => 'child ticket'); ok($ok, $msg); my $child_id = $child->Id; ($ok, $msg) = $child->AddLink(Type => 'MemberOf', Target => $parent->id); ok($ok, $msg); ($ok, $msg) = $child->AddLink(Type => 'RefersTo', Target => 'https://bestpractical.com'); ok($ok, $msg); $user->PrincipalObj->GrantRight( Right => 'ShowTicket' ); # Inspect existing ticket links (parent) { my $res = $mech->get("$rest_base_path/ticket/$parent_id", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; my %links; for (@{ $content->{_hyperlinks} }) { push @{ $links{$_->{ref}} }, $_; } cmp_deeply($links{'depends-on'}, undef, 'no depends-on links'); cmp_deeply($links{'depended-on-by'}, undef, 'no depended-on-by links'); cmp_deeply($links{'parent'}, undef, 'no parent links'); cmp_deeply($links{'refers-to'}, undef, 'no refers-to links'); cmp_deeply($links{'referred-to-by'}, undef, 'no referred-to-by links'); cmp_deeply($links{'child'}, [{ ref => 'child', type => 'ticket', id => $child->Id, _url => re(qr{$rest_base_path/ticket/$child_id$}), }], 'one child link'); } # Inspect existing ticket links (child) { my $res = $mech->get("$rest_base_path/ticket/$child_id", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; my %links; for (@{ $content->{_hyperlinks} }) { push @{ $links{$_->{ref}} }, $_; } cmp_deeply($links{'depends-on'}, undef, 'no depends-on links'); cmp_deeply($links{'depended-on-by'}, undef, 'no depended-on-by links'); cmp_deeply($links{'child'}, undef, 'no child links'); cmp_deeply($links{'referred-to-by'}, undef, 'no referred-to-by links'); cmp_deeply($links{'parent'}, [{ ref => 'parent', type => 'ticket', id => $parent->Id, _url => re(qr{$rest_base_path/ticket/$parent_id$}), }], 'one child link'); cmp_deeply($links{'refers-to'}, [{ ref => 'refers-to', type => 'external', _url => re(qr{https\:\/\/bestpractical\.com}), }], 'one external refers-to link'); } # Create/Modify ticket with links $user->PrincipalObj->GrantRight( Right => $_ ) for qw/CreateTicket ModifyTicket/; { my $res = $mech->post_json( "$rest_base_path/ticket", { Queue => 'General', DependsOn => [ $parent_id, $child_id ], RefersTo => $child_id }, 'Authorization' => $auth, ); is( $res->code, 201, 'post response code' ); my $content = $mech->json_response; my $id = $content->{id}; ok( $id, "create another ticket with links" ); $res = $mech->put_json( "$rest_base_path/ticket/$id", { RefersTo => $parent_id }, 'Authorization' => $auth, ); is( $res->code, 200, 'put response code' ); $content = $mech->json_response; is_deeply( $content, [ "Ticket $id refers to Ticket $parent_id.", "Ticket $id no longer refers to Ticket $child_id." ], 'update RefersTo' ); $res = $mech->put_json( "$rest_base_path/ticket/$id", { DeleteDependsOn => $parent_id, AddMembers => [ $parent_id, $child_id ] }, 'Authorization' => $auth ); is( $res->code, 200, 'put response code' ); $content = $mech->json_response; is_deeply( $content, [ "Ticket $id no longer depends on Ticket $parent_id.", "Ticket $parent_id member of Ticket $id.", "Ticket $child_id member of Ticket $id." ], 'add Members and delete DependsOn' ); } done_testing; rt-5.0.1/t/rest2/article-customfields.t000644 000765 000024 00000033376 14005011336 020626 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; use Test::Deep; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; my $class = RT::Class->new( RT->SystemUser ); $class->Load('General'); $class->Create( Name => 'General' ) if !$class->Id; ok( $class->Id, "General class" ); my $single_cf = RT::CustomField->new( RT->SystemUser ); my ( $ok, $msg ) = $single_cf->Load('Content'); ok( $ok, $msg ); my $single_cf_id = $single_cf->Id; ( $ok, $msg ) = $single_cf->AddToObject($class); ok( $ok, $msg ); my $multi_cf = RT::CustomField->new( RT->SystemUser ); ( $ok, $msg ) = $multi_cf->Create( Name => 'Multi', Type => 'FreeformMultiple', LookupType => RT::Article->CustomFieldLookupType ); ok( $ok, $msg ); my $multi_cf_id = $multi_cf->Id; ( $ok, $msg ) = $multi_cf->AddToObject($class); ok( $ok, $msg ); # Article Creation with no ModifyCustomField my ( $article_url, $article_id ); { my $payload = { Name => 'Article creation using REST', Class => 'General', CustomFields => { $single_cf_id => 'Hello world!', }, }; # Rights Test - No CreateArticle my $res = $mech->post_json( "$rest_base_path/article", $payload, 'Authorization' => $auth, ); is( $res->code, 403 ); my $content = $mech->json_response; is( $content->{message}, 'Permission Denied', "can't create Article with custom fields you can't set" ); # Rights Test - With CreateArticle $user->PrincipalObj->GrantRight( Right => 'CreateArticle' ); $res = $mech->post_json( "$rest_base_path/article", $payload, 'Authorization' => $auth, ); is( $res->code, 400 ); delete $payload->{CustomFields}; $res = $mech->post_json( "$rest_base_path/article", $payload, 'Authorization' => $auth, ); is( $res->code, 201 ); ok( $article_url = $res->header('location') ); ok( ($article_id) = $article_url =~ qr[/article/(\d+)] ); } # Article Display { # Rights Test - No ShowArticle my $res = $mech->get( $article_url, 'Authorization' => $auth, ); is( $res->code, 403 ); } # Rights Test - With ShowArticle but no SeeCustomField { $user->PrincipalObj->GrantRight( Right => 'ShowArticle' ); my $res = $mech->get( $article_url, 'Authorization' => $auth, ); is( $res->code, 200 ); my $content = $mech->json_response; is( $content->{id}, $article_id ); is( $content->{Name}, 'Article creation using REST' ); is_deeply( $content->{'CustomFields'}, [], 'Article custom field not present' ); is_deeply( [ grep { $_->{ref} eq 'customfield' } @{ $content->{'_hyperlinks'} } ], [], 'No CF hypermedia' ); } my $no_article_cf_values = bag( { name => 'Content', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); # Rights Test - With ShowArticle and SeeCustomField { $user->PrincipalObj->GrantRight( Right => 'SeeCustomField' ); my $res = $mech->get( $article_url, 'Authorization' => $auth, ); is( $res->code, 200 ); my $content = $mech->json_response; is( $content->{id}, $article_id ); is( $content->{Name}, 'Article creation using REST' ); cmp_deeply( $content->{CustomFields}, $no_article_cf_values, 'No article custom field values' ); cmp_deeply( [ grep { $_->{ref} eq 'customfield' } @{ $content->{'_hyperlinks'} } ], [ { ref => 'customfield', id => $single_cf_id, name => 'Content', type => 'customfield', _url => re(qr[$rest_base_path/customfield/$single_cf_id$]), }, { ref => 'customfield', id => $multi_cf_id, name => 'Multi', type => 'customfield', _url => re(qr[$rest_base_path/customfield/$multi_cf_id$]), } ], 'Two CF hypermedia', ); my ($single_url) = map { $_->{_url} } grep { $_->{ref} eq 'customfield' && $_->{id} == $single_cf_id } @{ $content->{'_hyperlinks'} }; my ($multi_url) = map { $_->{_url} } grep { $_->{ref} eq 'customfield' && $_->{id} == $multi_cf_id } @{ $content->{'_hyperlinks'} }; $res = $mech->get( $single_url, 'Authorization' => $auth, ); is( $res->code, 200 ); cmp_deeply( $mech->json_response, superhashof( { id => $single_cf_id, Disabled => 0, LookupType => RT::Article->CustomFieldLookupType, MaxValues => 1, Name => 'Content', Type => 'Text', } ), 'single cf' ); $res = $mech->get( $multi_url, 'Authorization' => $auth, ); is( $res->code, 200 ); cmp_deeply( $mech->json_response, superhashof( { id => $multi_cf_id, Disabled => 0, LookupType => RT::Article->CustomFieldLookupType, MaxValues => 0, Name => 'Multi', Type => 'Freeform', } ), 'multi cf' ); } # Article Update without ModifyCustomField { my $payload = { Name => 'Article update using REST', CustomFields => { $single_cf_id => 'Modified CF', }, }; # Rights Test - No ModifyArticle my $res = $mech->put_json( $article_url, $payload, 'Authorization' => $auth, ); TODO: { local $TODO = "RT ->Update isn't introspectable"; is( $res->code, 403 ); } is_deeply( $mech->json_response, [ 'Article Article creation using REST: Permission Denied', 'Could not add new custom field value: Permission Denied' ] ); $user->PrincipalObj->GrantRight( Right => 'ModifyArticle' ); $res = $mech->put_json( $article_url, $payload, 'Authorization' => $auth, ); is( $res->code, 200 ); is_deeply( $mech->json_response, [ 'Article Article update using REST: Name changed from "Article creation using REST" to "Article update using REST"', 'Could not add new custom field value: Permission Denied' ] ); $res = $mech->get( $article_url, 'Authorization' => $auth, ); is( $res->code, 200 ); my $content = $mech->json_response; is( $content->{Name}, 'Article update using REST' ); cmp_deeply( $content->{CustomFields}, $no_article_cf_values, 'No update to CF' ); } # Article Update with ModifyCustomField { $user->PrincipalObj->GrantRight( Right => 'ModifyCustomField' ); my $payload = { Name => 'More updates using REST', CustomFields => { $single_cf_id => 'Modified CF', }, }; my $res = $mech->put_json( $article_url, $payload, 'Authorization' => $auth, ); is( $res->code, 200 ); is_deeply( $mech->json_response, [ 'Article More updates using REST: Name changed from "Article update using REST" to "More updates using REST"', 'Content Modified CF added' ] ); $res = $mech->get( $article_url, 'Authorization' => $auth, ); is( $res->code, 200 ); my $modified_article_cf_values = bag( { name => 'Content', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Modified CF'] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); my $content = $mech->json_response; is( $content->{Name}, 'More updates using REST' ); cmp_deeply( $content->{CustomFields}, $modified_article_cf_values, 'New CF value' ); # make sure changing the CF doesn't add a second OCFV $payload->{CustomFields}{$single_cf_id} = 'Modified Again'; $res = $mech->put_json( $article_url, $payload, 'Authorization' => $auth, ); is( $res->code, 200 ); is_deeply( $mech->json_response, ['Content Modified CF changed to Modified Again'] ); $res = $mech->get( $article_url, 'Authorization' => $auth, ); is( $res->code, 200 ); $modified_article_cf_values = bag( { name => 'Content', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Modified Again'] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); $content = $mech->json_response; cmp_deeply( $content->{CustomFields}, $modified_article_cf_values, 'New CF value' ); # stop changing the CF, change something else, make sure CF sticks around delete $payload->{CustomFields}{$single_cf_id}; $payload->{Name} = 'No CF change'; $res = $mech->put_json( $article_url, $payload, 'Authorization' => $auth, ); is( $res->code, 200 ); is_deeply( $mech->json_response, ['Article No CF change: Name changed from "More updates using REST" to "No CF change"'] ); $res = $mech->get( $article_url, 'Authorization' => $auth, ); is( $res->code, 200 ); $content = $mech->json_response; cmp_deeply( $content->{CustomFields}, $modified_article_cf_values, 'Same CF value' ); } # Article Creation with ModifyCustomField { my $payload = { Name => 'Article creation using REST', Class => 'General', CustomFields => { $single_cf_id => 'Hello world!', }, }; my $res = $mech->post_json( "$rest_base_path/article", $payload, 'Authorization' => $auth, ); is( $res->code, 201 ); ok( $article_url = $res->header('location') ); ok( ($article_id) = $article_url =~ qr[/article/(\d+)] ); } # Rights Test - With ShowArticle and SeeCustomField { my $res = $mech->get( $article_url, 'Authorization' => $auth, ); is( $res->code, 200 ); my $article_cf_values = bag( { name => 'Content', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Hello world!'] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); my $content = $mech->json_response; is( $content->{id}, $article_id ); is( $content->{Name}, 'Article creation using REST' ); cmp_deeply( $content->{'CustomFields'}, $article_cf_values, 'Article custom field' ); } # Article Creation for multi-value CF my $i = 1; for my $value ( 'scalar', ['array reference'], [ 'multiple', 'values' ], ) { my $payload = { Name => 'Multi-value CF ' . $i, Class => 'General', CustomFields => { $multi_cf_id => $value, }, }; my $res = $mech->post_json( "$rest_base_path/article", $payload, 'Authorization' => $auth, ); is( $res->code, 201 ); ok( $article_url = $res->header('location') ); ok( ($article_id) = $article_url =~ qr[/article/(\d+)] ); $res = $mech->get( $article_url, 'Authorization' => $auth, ); is( $res->code, 200 ); my $content = $mech->json_response; is( $content->{id}, $article_id ); is( $content->{Name}, 'Multi-value CF ' . $i ); my $output = ref($value) ? $value : [$value]; # scalar input comes out as array reference my $article_cf_values = bag( { name => 'Content', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => $output }, ); cmp_deeply( $content->{'CustomFields'}, $article_cf_values, 'Article custom field' ); $i++; } { sub modify_multi_ok { local $Test::Builder::Level = $Test::Builder::Level + 1; my $input = shift; my $messages = shift; my $output = shift; my $name = shift; my $payload = { CustomFields => { $multi_cf_id => $input, }, }; my $res = $mech->put_json( $article_url, $payload, 'Authorization' => $auth, ); is( $res->code, 200 ); is_deeply( $mech->json_response, $messages ); $res = $mech->get( $article_url, 'Authorization' => $auth, ); is( $res->code, 200 ); my $content = $mech->json_response; my $values; for my $cf ( @{ $content->{CustomFields} } ) { next unless $cf->{id} == $multi_cf_id; $values = [ sort @{ $cf->{values} } ]; } cmp_deeply( $values, $output, $name || 'New CF value' ); } # starting point: ['multiple', 'values'], modify_multi_ok( [ 'multiple', 'values' ], [], [ 'multiple', 'values' ], 'no change' ); modify_multi_ok( [ 'multiple', 'values', 'new' ], ['new added as a value for Multi'], [ 'multiple', 'new', 'values' ], 'added "new"' ); modify_multi_ok( [ 'multiple', 'new' ], ['values is no longer a value for custom field Multi'], [ 'multiple', 'new' ], 'removed "values"' ); modify_multi_ok( 'replace all', [ 'replace all added as a value for Multi', 'multiple is no longer a value for custom field Multi', 'new is no longer a value for custom field Multi' ], ['replace all'], 'replaced all values' ); modify_multi_ok( [], ['replace all is no longer a value for custom field Multi'], [], 'removed all values' ); modify_multi_ok( [ 'foo', 'foo', 'bar' ], [ 'foo added as a value for Multi', undef, 'bar added as a value for Multi' ], [ 'bar', 'foo' ], 'multiple values with the same name' ); modify_multi_ok( [ 'foo', 'bar' ], [], [ 'bar', 'foo' ], 'multiple values with the same name' ); modify_multi_ok( ['bar'], ['foo is no longer a value for custom field Multi'], ['bar'], 'multiple values with the same name' ); modify_multi_ok( [ 'bar', 'bar', 'bar' ], [ undef, undef ], ['bar'], 'multiple values with the same name' ); } done_testing; rt-5.0.1/t/rest2/customfields.t000644 000765 000024 00000030657 14005011336 017204 0ustar00sunnavystaff000000 000000 use strict; use warnings; use lib 't/lib'; use RT::Test::REST2 tests => undef; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; my $select_cf = RT::CustomField->new(RT->SystemUser); $select_cf->Create(Name => 'Select CF', Type => 'Select', MaxValues => 1, Queue => 'General'); $select_cf->AddValue(Name => 'First Value', SortOder => 0); $select_cf->AddValue(Name => 'Second Value', SortOrder => 1); $select_cf->AddValue(Name => 'Third Value', SortOrder => 2); my $select_cf_id = $select_cf->id; my $select_cf_values = $select_cf->Values->ItemsArrayRef; my $basedon_cf = RT::CustomField->new(RT->SystemUser); $basedon_cf->Create(Name => 'SubSelect CF', Type => 'Select', MaxValues => 1, Queue => 'General', BasedOn => $select_cf->id); $basedon_cf->AddValue(Name => 'With First Value', Category => $select_cf_values->[0]->Name, SortOder => 0); $basedon_cf->AddValue(Name => 'With No Value', SortOder => 0); my $basedon_cf_id = $basedon_cf->id; my $basedon_cf_values = $basedon_cf->Values->ItemsArrayRef; my $freeform_cf; my $freeform_cf_id; # Right test - create customfield without SeeCustomField nor AdminCustomField { my $payload = { Name => 'Freeform CF', Type => 'Freeform', MaxValues => 1, }; my $res = $mech->post_json("$rest_base_path/customfield", $payload, 'Authorization' => $auth, ); is($res->code, 403); is($res->message, 'Forbidden'); my $freeform_cf = RT::CustomField->new(RT->SystemUser); my ($ok, $msg) = $freeform_cf->Load('Freeform CF'); is($freeform_cf->id, undef); ok(!$ok); is($msg, 'Not found'); } # Customfield create { $user->PrincipalObj->GrantRight( Right => 'SeeCustomField' ); $user->PrincipalObj->GrantRight( Right => 'AdminCustomField' ); my $payload = { Name => 'Freeform CF', Type => 'Freeform', LookupType => 'RT::Queue-RT::Ticket', MaxValues => 1, }; my $res = $mech->post_json("$rest_base_path/customfield", $payload, 'Authorization' => $auth, ); is($res->code, 201); $freeform_cf = RT::CustomField->new(RT->SystemUser); $freeform_cf->Load('Freeform CF'); $freeform_cf_id = $freeform_cf->id; is($freeform_cf->id, 4); is_empty($freeform_cf->Description); } # Right test - search all tickets customfields without SeeCustomField { $user->PrincipalObj->RevokeRight( Right => 'SeeCustomField' ); my $res = $mech->post_json("$rest_base_path/customfields", [{field => 'LookupType', value => 'RT::Queue-RT::Ticket'}], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{total}, 3); is($content->{count}, 0); is_deeply($content->{items}, []); } # search all tickets customfields { $user->PrincipalObj->GrantRight( Right => 'SeeCustomField' ); my $res = $mech->post_json("$rest_base_path/customfields", [{field => 'LookupType', value => 'RT::Queue-RT::Ticket'}], 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{total}, 3); is($content->{count}, 3); my $items = $content->{items}; is(scalar(@$items), 3); is($items->[0]->{type}, 'customfield'); is($items->[0]->{id}, $freeform_cf->id); like($items->[0]->{_url}, qr{$rest_base_path/customfield/$freeform_cf_id$}); is($items->[1]->{type}, 'customfield'); is($items->[1]->{id}, $select_cf->id); like($items->[1]->{_url}, qr{$rest_base_path/customfield/$select_cf_id$}); is($items->[2]->{type}, 'customfield'); is($items->[2]->{id}, $basedon_cf->id); like($items->[2]->{_url}, qr{$rest_base_path/customfield/$basedon_cf_id$}); } # Freeform CustomField display { my $res = $mech->get("$rest_base_path/customfield/$freeform_cf_id", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $freeform_cf_id); is($content->{Name}, $freeform_cf->Name); is_empty($content->{Description}); is($content->{LookupType}, 'RT::Queue-RT::Ticket'); is($content->{Type}, 'Freeform'); is($content->{MaxValues}, 1); is($content->{Disabled}, 0); my @fields = qw(SortOrder Pattern Created Creator LastUpdated LastUpdatedBy); push @fields, qw(UniqueValues EntryHint) if RT::Handle::cmp_version($RT::VERSION, '4.4.0') >= 0; ok(exists $content->{$_}, "got $_") for @fields; my $links = $content->{_hyperlinks}; is(scalar @$links, 1); is($links->[0]{ref}, 'self'); is($links->[0]{id}, $freeform_cf_id); is($links->[0]{type}, 'customfield'); like($links->[0]{_url}, qr{$rest_base_path/customfield/$freeform_cf_id$}); } # Select CustomField display { my $res = $mech->get("$rest_base_path/customfield/$select_cf_id", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $select_cf_id); is($content->{Name}, $select_cf->Name); is_empty($content->{Description}); is($content->{LookupType}, 'RT::Queue-RT::Ticket'); is($content->{Type}, 'Select'); is($content->{MaxValues}, 1); is($content->{Disabled}, 0); my @fields = qw(SortOrder Pattern Created Creator LastUpdated LastUpdatedBy); push @fields, qw(UniqueValues EntryHint) if RT::Handle::cmp_version($RT::VERSION, '4.4.0') >= 0; ok(exists $content->{$_}, "got $_") for @fields; my $links = $content->{_hyperlinks}; is(scalar @$links, 2); is($links->[0]{ref}, 'self'); is($links->[0]{id}, $select_cf_id); is($links->[0]{type}, 'customfield'); like($links->[0]{_url}, qr{$rest_base_path/customfield/$select_cf_id$}); is($links->[1]{ref}, 'customfieldvalues'); like($links->[1]{_url}, qr{$rest_base_path/customfield/$select_cf_id/values$}); my $values = $content->{Values}; is_deeply($values, ['First Value', 'Second Value', 'Third Value']); } # BasedOn CustomField display { my $res = $mech->get("$rest_base_path/customfield/$basedon_cf_id", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $basedon_cf_id); is($content->{Name}, $basedon_cf->Name); is_empty($content->{Description}); is($content->{LookupType}, 'RT::Queue-RT::Ticket'); is($content->{Type}, 'Select'); is($content->{MaxValues}, 1); is($content->{Disabled}, 0); my @fields = qw(SortOrder Pattern Created Creator LastUpdated LastUpdatedBy); push @fields, qw(UniqueValues EntryHint) if RT::Handle::cmp_version($RT::VERSION, '4.4.0') >= 0; ok(exists $content->{$_}, "got $_") for @fields; my $links = $content->{_hyperlinks}; is(scalar @$links, 2); is($links->[0]{ref}, 'self'); is($links->[0]{id}, $basedon_cf_id); is($links->[0]{type}, 'customfield'); like($links->[0]{_url}, qr{$rest_base_path/customfield/$basedon_cf_id$}); is($links->[1]{ref}, 'customfieldvalues'); like($links->[1]{_url}, qr{$rest_base_path/customfield/$basedon_cf_id/values$}); my $values = $content->{Values}; is_deeply($values, ['With First Value', 'With No Value']); } # BasedOn CustomField display with category filter { my $res = $mech->get("$rest_base_path/customfield/$basedon_cf_id?category=First%20Value", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $basedon_cf_id); is($content->{Name}, $basedon_cf->Name); is_empty($content->{Description}); is($content->{LookupType}, 'RT::Queue-RT::Ticket'); is($content->{Type}, 'Select'); is($content->{MaxValues}, 1); is($content->{Disabled}, 0); my @fields = qw(SortOrder Pattern Created Creator LastUpdated LastUpdatedBy); push @fields, qw(UniqueValues EntryHint) if RT::Handle::cmp_version($RT::VERSION, '4.4.0') >= 0; ok(exists $content->{$_}, "got $_") for @fields; my $links = $content->{_hyperlinks}; is(scalar @$links, 2); is($links->[0]{ref}, 'self'); is($links->[0]{id}, $basedon_cf_id); is($links->[0]{type}, 'customfield'); like($links->[0]{_url}, qr{$rest_base_path/customfield/$basedon_cf_id$}); is($links->[1]{ref}, 'customfieldvalues'); like($links->[1]{_url}, qr{$rest_base_path/customfield/$basedon_cf_id/values$}); my $values = $content->{Values}; is_deeply($values, ['With First Value']); } # BasedOn CustomField display with null category filter { my $res = $mech->get("$rest_base_path/customfield/$basedon_cf_id?category=", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $basedon_cf_id); is($content->{Name}, $basedon_cf->Name); is_empty($content->{Description}); is($content->{LookupType}, 'RT::Queue-RT::Ticket'); is($content->{Type}, 'Select'); is($content->{MaxValues}, 1); is($content->{Disabled}, 0); my @fields = qw(SortOrder Pattern Created Creator LastUpdated LastUpdatedBy); push @fields, qw(UniqueValues EntryHint) if RT::Handle::cmp_version($RT::VERSION, '4.4.0') >= 0; ok(exists $content->{$_}, "got $_") for @fields; my $links = $content->{_hyperlinks}; is(scalar @$links, 2); is($links->[0]{ref}, 'self'); is($links->[0]{id}, $basedon_cf_id); is($links->[0]{type}, 'customfield'); like($links->[0]{_url}, qr{$rest_base_path/customfield/$basedon_cf_id$}); is($links->[1]{ref}, 'customfieldvalues'); like($links->[1]{_url}, qr{$rest_base_path/customfield/$basedon_cf_id/values$}); my $values = $content->{Values}; is_deeply($values, ['With No Value']); } # Display customfield { $user->PrincipalObj->GrantRight( Right => 'SeeCustomField' ); my $res = $mech->get("$rest_base_path/customfield/$freeform_cf_id", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $freeform_cf_id); is($content->{Name}, 'Freeform CF'); is_empty($content->{Description}); is($content->{LookupType}, 'RT::Queue-RT::Ticket'); is($content->{Type}, 'Freeform'); is($content->{MaxValues}, 1); is($content->{Disabled}, 0); my @fields = qw(SortOrder Pattern Created Creator LastUpdated LastUpdatedBy); push @fields, qw(UniqueValues EntryHint) if RT::Handle::cmp_version($RT::VERSION, '4.4.0') >= 0; ok(exists $content->{$_}, "got $_") for @fields; my $links = $content->{_hyperlinks}; is(scalar @$links, 1); is($links->[0]{ref}, 'self'); is($links->[0]{id}, $freeform_cf_id); is($links->[0]{type}, 'customfield'); like($links->[0]{_url}, qr{$rest_base_path/customfield/$freeform_cf_id$}); } # Right test - update customfield without AdminCustomField { $user->PrincipalObj->RevokeRight( Right => 'AdminCustomField' ); my $payload = { Description => 'This is a CF for testing REST CRUD on CFs', }; my $res = $mech->put_json("$rest_base_path/customfield/$freeform_cf_id", $payload, 'Authorization' => $auth, ); is($res->code, 403); is($res->message, 'Forbidden'); } # Update customfield { $user->PrincipalObj->GrantRight( Right => 'AdminCustomField' ); my $payload = { Description => 'This is a CF for testing REST CRUD on CFs', }; my $res = $mech->put_json("$rest_base_path/customfield/$freeform_cf_id", $payload, 'Authorization' => $auth, ); is($res->code, 200); my $freeform_cf = RT::CustomField->new(RT->SystemUser); $freeform_cf->Load('Freeform CF'); is($freeform_cf->id, $freeform_cf_id); is($freeform_cf->Description, 'This is a CF for testing REST CRUD on CFs'); } # Right test - delete customfield without AdminCustomField { $user->PrincipalObj->RevokeRight( Right => 'AdminCustomField' ); my $res = $mech->delete("$rest_base_path/customfield/$freeform_cf_id", 'Authorization' => $auth, ); is($res->code, 403); is($res->message, 'Forbidden'); my $freeform_cf = RT::CustomField->new(RT->SystemUser); $freeform_cf->Load('Freeform CF'); is($freeform_cf->Disabled, 0); } # Delete customfield { $user->PrincipalObj->GrantRight( Right => 'AdminCustomField' ); my $res = $mech->delete("$rest_base_path/customfield/$freeform_cf_id", 'Authorization' => $auth, ); is($res->code, 204); my $freeform_cf = RT::CustomField->new(RT->SystemUser); $freeform_cf->Load('Freeform CF'); is($freeform_cf->Disabled, 1); } done_testing; rt-5.0.1/t/rest2/articles.t000644 000765 000024 00000013056 14005011336 016303 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; use Test::Deep; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; # Empty DB { my $res = $mech->post_json( "$rest_base_path/articles", [ { field => 'id', operator => '>', value => 0 } ], 'Authorization' => $auth, ); is( $res->code, 200 ); is( $mech->json_response->{count}, 0 ); } # Missing Class { my $res = $mech->post_json( "$rest_base_path/article", { Name => 'Article creation using REST', }, 'Authorization' => $auth, ); is( $res->code, 400 ); is( $mech->json_response->{message}, 'Invalid Class' ); } # Article Creation my ( $article_url, $article_id ); { my $payload = { Name => 'Article creation using REST', Summary => 'Article summary', Class => 'General', }; # Rights Test - No CreateArticle my $res = $mech->post_json( "$rest_base_path/article", $payload, 'Authorization' => $auth, ); is( $res->code, 403 ); # Rights Test - With CreateArticle $user->PrincipalObj->GrantRight( Right => 'CreateArticle' ); $res = $mech->post_json( "$rest_base_path/article", $payload, 'Authorization' => $auth, ); is( $res->code, 201 ); ok( $article_url = $res->header('location') ); ok( ($article_id) = $article_url =~ qr[/article/(\d+)] ); } # Article Display { # Rights Test - No ShowArticle my $res = $mech->get( $article_url, 'Authorization' => $auth, ); is( $res->code, 403 ); } # Rights Test - With ShowArticle { $user->PrincipalObj->GrantRight( Right => 'ShowArticle' ); my $res = $mech->get( $article_url, 'Authorization' => $auth, ); is( $res->code, 200 ); my $content = $mech->json_response; is( $content->{id}, $article_id ); is( $content->{Name}, 'Article creation using REST' ); ok( exists $content->{$_} ) for qw(Creator Created LastUpdated LastUpdatedBy Name Summary); my $links = $content->{_hyperlinks}; is( scalar @$links, 2 ); is( $links->[0]{ref}, 'self' ); is( $links->[0]{id}, 1 ); is( $links->[0]{type}, 'article' ); like( $links->[0]{_url}, qr[$rest_base_path/article/$article_id$] ); is( $links->[1]{ref}, 'history' ); like( $links->[1]{_url}, qr[$rest_base_path/article/$article_id/history$] ); my $class = $content->{Class}; is( $class->{id}, 1 ); is( $class->{type}, 'class' ); like( $class->{_url}, qr{$rest_base_path/class/1$} ); my $creator = $content->{Creator}; is( $creator->{id}, 'test' ); is( $creator->{type}, 'user' ); like( $creator->{_url}, qr{$rest_base_path/user/test$} ); my $updated_by = $content->{LastUpdatedBy}; is( $updated_by->{id}, 'test' ); is( $updated_by->{type}, 'user' ); like( $updated_by->{_url}, qr{$rest_base_path/user/test$} ); } # Article Search { my $res = $mech->post_json( "$rest_base_path/articles", [ { field => 'id', operator => '>', value => 0 } ], 'Authorization' => $auth, ); is( $res->code, 200 ); my $content = $mech->json_response; is( $content->{count}, 1 ); is( $content->{page}, 1 ); is( $content->{per_page}, 20 ); is( $content->{total}, 1 ); is( scalar @{ $content->{items} }, 1 ); my $article = $content->{items}->[0]; is( $article->{type}, 'article' ); is( $article->{id}, 1 ); like( $article->{_url}, qr{$rest_base_path/article/1$} ); } # Article Update { my $payload = { Name => 'Article update using REST', }; # Rights Test - No ModifyArticle my $res = $mech->put_json( $article_url, $payload, 'Authorization' => $auth, ); TODO: { local $TODO = "RT ->Update isn't introspectable"; is( $res->code, 403 ); } is_deeply( $mech->json_response, ['Article Article creation using REST: Permission Denied'] ); $user->PrincipalObj->GrantRight( Right => 'ModifyArticle' ); $res = $mech->put_json( $article_url, $payload, 'Authorization' => $auth, ); is( $res->code, 200 ); is_deeply( $mech->json_response, [ 'Article Article update using REST: Name changed from "Article creation using REST" to "Article update using REST"' ] ); $res = $mech->get( $article_url, 'Authorization' => $auth, ); is( $res->code, 200 ); my $content = $mech->json_response; is( $content->{Name}, 'Article update using REST' ); # update again with no changes $res = $mech->put_json( $article_url, $payload, 'Authorization' => $auth, ); is( $res->code, 200 ); is_deeply( $mech->json_response, [] ); $res = $mech->get( $article_url, 'Authorization' => $auth, ); is( $res->code, 200 ); $content = $mech->json_response; is( $content->{Name}, 'Article update using REST' ); } # Transactions { my $res = $mech->get( $article_url, 'Authorization' => $auth, ); is( $res->code, 200 ); $res = $mech->get( $mech->url_for_hypermedia('history'), 'Authorization' => $auth, ); is( $res->code, 200 ); my $content = $mech->json_response; is( $content->{count}, 2 ); is( $content->{page}, 1 ); is( $content->{per_page}, 20 ); is( $content->{total}, 2 ); is( scalar @{ $content->{items} }, 2 ); for my $txn ( @{ $content->{items} } ) { is( $txn->{type}, 'transaction' ); like( $txn->{_url}, qr{$rest_base_path/transaction/\d+$} ); } } done_testing; rt-5.0.1/t/rest2/ticket-customfields.t000644 000765 000024 00000131671 14005011336 020463 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; use Test::Deep; # Test using integer priorities RT->Config->Set(EnablePriorityAsString => 0); my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; my $queue = RT::Test->load_or_create_queue( Name => "General" ); my $single_cf = RT::CustomField->new( RT->SystemUser ); my ($ok, $msg) = $single_cf->Create( Name => 'Single', Type => 'FreeformSingle', Queue => $queue->Id ); ok($ok, $msg); my $single_cf_id = $single_cf->Id; my $multi_cf = RT::CustomField->new( RT->SystemUser ); ($ok, $msg) = $multi_cf->Create( Name => 'Multi', Type => 'FreeformMultiple', Queue => $queue->Id ); ok($ok, $msg); my $multi_cf_id = $multi_cf->Id; # Ticket Creation with no ModifyCustomField my ($ticket_url, $ticket_id); my ($ticket_url_cf_by_name, $ticket_id_cf_by_name); { my $payload = { Subject => 'Ticket creation using REST', From => 'test@bestpractical.com', To => 'rt@localhost', Queue => 'General', Content => 'Testing ticket creation using REST API.', CustomFields => { $single_cf_id => 'Hello world!', }, }; my $payload_cf_by_name = { Subject => 'Ticket creation using REST - CF By Name', From => 'test@bestpractical.com', To => 'rt@localhost', Queue => 'General', Content => 'Testing ticket creation using REST API.', CustomFields => { 'Single' => 'Hello world! Again.', }, }; my $payload_cf_by_name_invalid = { Subject => 'Ticket creation using REST - CF By Name (Invalid)', From => 'test@bestpractical.com', To => 'rt@localhost', Queue => 'General', Content => 'Testing ticket creation using REST API.', CustomFields => { 'Not Existant CF' => 'Hello world!', }, }; # 4.2.3 introduced a bug (e092e23) in CFs fixed in 4.2.9 (ab7ea15) if ( RT::Handle::cmp_version($RT::VERSION, '4.2.3') >= 0 && RT::Handle::cmp_version($RT::VERSION, '4.2.8') <= 0) { delete $payload->{CustomFields}; delete $payload_cf_by_name->{CustomFields}; }; # Rights Test - No CreateTicket my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 403); my @warnings; local $SIG{__WARN__} = sub { push @warnings, @_; }; # Rights Test - With CreateTicket $user->PrincipalObj->GrantRight( Right => 'CreateTicket' ); $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($ticket_url = $res->header('location')); ok(($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); # Create CF using name, mising right, how to fail it? $res = $mech->post_json("$rest_base_path/ticket", $payload_cf_by_name, 'Authorization' => $auth, ); is($res->code, 201); # To be able to lookup a CustomField by name, the user needs to have # that right. $user->PrincipalObj->GrantRight( Right => 'SeeCustomField'); # Create CF using name $res = $mech->post_json("$rest_base_path/ticket", $payload_cf_by_name, 'Authorization' => $auth, ); is($res->code, 201); ok($ticket_url_cf_by_name = $res->header('location')); ok(($ticket_id_cf_by_name) = $ticket_url_cf_by_name =~ qr[/ticket/(\d+)]); # Create CF using name (invalid) $res = $mech->post_json("$rest_base_path/ticket", $payload_cf_by_name_invalid, 'Authorization' => $auth, ); is($res->code, 201); TODO: { local $TODO = "this warns due to specifying a CF with no permission to see" if RT::Handle::cmp_version($RT::VERSION, '4.4.0') || RT::Handle::cmp_version($RT::VERSION, '4.4.4') >= 0; is(@warnings, 0, "no warnings") or diag(join("\n",'warnings : ', @warnings)); } $user->PrincipalObj->RevokeRight( Right => 'SeeCustomField'); } # Ticket Display { # Rights Test - No ShowTicket my $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 403); } # Rights Test - With ShowTicket but no SeeCustomField { $user->PrincipalObj->GrantRight( Right => 'ShowTicket' ); my $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $ticket_id); is($content->{Type}, 'ticket'); is($content->{Status}, 'new'); is($content->{Subject}, 'Ticket creation using REST'); is_deeply($content->{'CustomFields'}, [], 'Ticket custom field not present'); is_deeply([grep { $_->{ref} eq 'customfield' } @{ $content->{'_hyperlinks'} }], [], 'No CF hypermedia'); } my $no_ticket_cf_values = bag( { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); # Rights Test - Searching asking for CustomFields without SeeCustomField { my $res = $mech->get("$rest_base_path/tickets?query=id>0&fields=Status,Owner,CustomFields,Subject&fields[Owner]=Name", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is(scalar @{$content->{items}}, 4); my $ticket = $content->{items}->[0]; is($ticket->{Status}, 'new'); is($ticket->{Owner}{Name}, 'Nobody'); is_deeply($ticket->{CustomFields}, '', 'Ticket custom field not present'); is($ticket->{Subject}, 'Ticket creation using REST'); is(scalar keys %$ticket, 7); } # Rights Test - With ShowTicket and SeeCustomField { $user->PrincipalObj->GrantRight( Right => 'SeeCustomField'); # CustomField by Id my $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $ticket_id); is($content->{Type}, 'ticket'); is($content->{Status}, 'new'); is($content->{Subject}, 'Ticket creation using REST'); cmp_deeply($content->{CustomFields}, $no_ticket_cf_values, 'No ticket custom field values'); cmp_deeply( [grep { $_->{ref} eq 'customfield' } @{ $content->{'_hyperlinks'} }], [{ ref => 'customfield', id => $single_cf_id, name => 'Single', type => 'customfield', _url => re(qr[$rest_base_path/customfield/$single_cf_id$]), }, { ref => 'customfield', id => $multi_cf_id, name => 'Multi', type => 'customfield', _url => re(qr[$rest_base_path/customfield/$multi_cf_id$]), }], 'Two CF hypermedia', ); my ($single_url) = map { $_->{_url} } grep { $_->{ref} eq 'customfield' && $_->{id} == $single_cf_id } @{ $content->{'_hyperlinks'} }; my ($multi_url) = map { $_->{_url} } grep { $_->{ref} eq 'customfield' && $_->{id} == $multi_cf_id } @{ $content->{'_hyperlinks'} }; $res = $mech->get($single_url, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response, superhashof({ id => $single_cf_id, Disabled => 0, LookupType => RT::Ticket->CustomFieldLookupType, MaxValues => 1, Name => 'Single', Type => 'Freeform', }), 'single cf'); $res = $mech->get($multi_url, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response, superhashof({ id => $multi_cf_id, Disabled => 0, LookupType => RT::Ticket->CustomFieldLookupType, MaxValues => 0, Name => 'Multi', Type => 'Freeform', }), 'multi cf'); # CustomField by Name $res = $mech->get($ticket_url_cf_by_name, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; is($content->{id}, $ticket_id_cf_by_name); is($content->{Type}, 'ticket'); is($content->{Status}, 'new'); is($content->{Subject}, 'Ticket creation using REST - CF By Name'); cmp_deeply( [grep { $_->{ref} eq 'customfield' } @{ $content->{'_hyperlinks'} }], [{ ref => 'customfield', id => $single_cf_id, type => 'customfield', _url => re(qr[$rest_base_path/customfield/$single_cf_id$]), name => 'Single', }, { ref => 'customfield', id => $multi_cf_id, type => 'customfield', _url => re(qr[$rest_base_path/customfield/$multi_cf_id$]), name => 'Multi', }], 'Two CF hypermedia', ); } # Ticket Update without ModifyCustomField { my $payload = { Subject => 'Ticket update using REST', Priority => 42, CustomFields => { $single_cf_id => 'Modified CF', }, }; # Rights Test - No ModifyTicket my $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); TODO: { local $TODO = "RT ->Update isn't introspectable"; is($res->code, 403); }; is_deeply($mech->json_response, ['Ticket 1: Permission Denied', 'Ticket 1: Permission Denied', 'Could not add new custom field value: Permission Denied']); $user->PrincipalObj->GrantRight( Right => 'ModifyTicket' ); $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ["Ticket 1: Priority changed from (no value) to '42'", "Ticket 1: Subject changed from 'Ticket creation using REST' to 'Ticket update using REST'", 'Could not add new custom field value: Permission Denied']); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{Subject}, 'Ticket update using REST'); is($content->{Priority}, 42); cmp_deeply($content->{CustomFields}, $no_ticket_cf_values, 'No update to CF'); } # Ticket Update with ModifyCustomField { $user->PrincipalObj->GrantRight( Right => 'ModifyCustomField' ); my $payload = { Subject => 'More updates using REST', Priority => 43, CustomFields => { $single_cf_id => 'Modified CF', }, }; my $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ["Ticket 1: Priority changed from '42' to '43'", "Ticket 1: Subject changed from 'Ticket update using REST' to 'More updates using REST'", 'Single Modified CF added']); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $modified_single_cf_value = bag( { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Modified CF'] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); my $content = $mech->json_response; is($content->{Subject}, 'More updates using REST'); is($content->{Priority}, 43); cmp_deeply($content->{CustomFields}, $modified_single_cf_value, 'New CF value'); # make sure changing the CF doesn't add a second OCFV $payload->{CustomFields}{$single_cf_id} = 'Modified Again'; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ['Single Modified CF changed to Modified Again']); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $modified_again_single_cf_value = bag( { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Modified Again'] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); $content = $mech->json_response; cmp_deeply($content->{CustomFields}, $modified_again_single_cf_value, 'New CF value'); # stop changing the CF, change something else, make sure CF sticks around delete $payload->{CustomFields}{$single_cf_id}; $payload->{Subject} = 'No CF change'; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ["Ticket 1: Subject changed from 'More updates using REST' to 'No CF change'"]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; cmp_deeply($content->{CustomFields}, $modified_again_single_cf_value, 'Same CF value'); # fail to delete the CF if mandatory $single_cf->SetPattern('(?#Mandatory).'); $payload->{Subject} = 'Cannot delete mandatory CF'; $payload->{CustomFields}{$single_cf_id} = undef; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ["Ticket 1: Subject changed from 'No CF change' to 'Cannot delete mandatory CF'", "Input must match [Mandatory]"]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; cmp_deeply($content->{CustomFields}, $modified_again_single_cf_value, 'Still same CF value'); # delete the CF $single_cf->SetPattern(); $payload->{Subject} = 'Delete CF'; $payload->{CustomFields}{$single_cf_id} = undef; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); cmp_deeply($mech->json_response, ["Ticket 1: Subject changed from 'Cannot delete mandatory CF' to 'Delete CF'", 'Modified Again is no longer a value for custom field Single']); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); $modified_again_single_cf_value = bag( { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); $content = $mech->json_response; cmp_deeply($content->{CustomFields}, $modified_again_single_cf_value, 'No more CF value'); } # Ticket Comment with custom field { my $payload = { Content => 'This is some content for a comment', ContentType => 'text/plain', Subject => 'This is a subject', CustomFields => { $single_cf_id => 'Yet another modified CF', }, }; $user->PrincipalObj->GrantRight( Right => 'CommentOnTicket' ); my $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; my ($hypermedia) = grep { $_->{ref} eq 'comment' } @{ $content->{_hyperlinks} }; ok($hypermedia, 'got comment hypermedia'); like($hypermedia->{_url}, qr[$rest_base_path/ticket/$ticket_id/comment$]); $res = $mech->post_json($mech->url_for_hypermedia('comment'), $payload, 'Authorization' => $auth, ); is($res->code, 201); cmp_deeply($mech->json_response, [re(qr/Comments added|Message recorded/), "Single Yet another modified CF added"]); } # Ticket Creation with ModifyCustomField { my $payload = { Subject => 'Ticket creation using REST', From => 'test@bestpractical.com', To => 'rt@localhost', Queue => 'General', Content => 'Testing ticket creation using REST API.', CustomFields => { $single_cf_id => 'Hello world!', }, }; my $payload_cf_by_name = { Subject => 'Ticket creation using REST - CF By Name', From => 'test@bestpractical.com', To => 'rt@localhost', Queue => 'General', Content => 'Testing ticket creation using REST API.', CustomFields => { 'Single' => 'Hello world! Again.', }, }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($ticket_url = $res->header('location')); ok(($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->post_json("$rest_base_path/ticket", $payload_cf_by_name, 'Authorization' => $auth, ); is($res->code, 201); ok($ticket_url_cf_by_name = $res->header('location')); ok(($ticket_id_cf_by_name) = $ticket_url_cf_by_name =~ qr[/ticket/(\d+)]); } # Rights Test - With ShowTicket and SeeCustomField { # CustomField by Id my $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $ticket_cf_value = bag( { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Hello world!'] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => [] }, ); my $content = $mech->json_response; is($content->{id}, $ticket_id); is($content->{Type}, 'ticket'); is($content->{Status}, 'new'); is($content->{Subject}, 'Ticket creation using REST'); cmp_deeply($content->{CustomFields}, $ticket_cf_value, 'Ticket custom field'); # CustomField by Name $res = $mech->get($ticket_url_cf_by_name, 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; is($content->{id}, $ticket_id_cf_by_name); is($content->{Type}, 'ticket'); is($content->{Status}, 'new'); is($content->{Subject}, 'Ticket creation using REST - CF By Name'); } # Ticket Creation for multi-value CF for my $value ( 'scalar', ['array reference'], ['multiple', 'values'], ) { my $payload = { Subject => 'Multi-value CF', Queue => 'General', CustomFields => { $multi_cf_id => $value, }, }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($ticket_url = $res->header('location')); ok(($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{id}, $ticket_id); is($content->{Type}, 'ticket'); is($content->{Status}, 'new'); is($content->{Subject}, 'Multi-value CF'); my $output = ref($value) ? $value : [$value]; # scalar input comes out as array reference my $ticket_cf_value = bag( { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => $output }, ); cmp_deeply($content->{'CustomFields'}, $ticket_cf_value, 'Ticket custom field'); # Ticket Show - Fields, custom fields { $res = $mech->get("$rest_base_path/tickets?query=id>0&fields=Status,Owner,CustomFields,Subject&fields[Owner]=Name", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; # Just look at the last one. my $ticket = $content->{items}->[-1]; is($ticket->{Status}, 'new'); is($ticket->{id}, $ticket_id); is($ticket->{Subject}, 'Multi-value CF'); cmp_deeply($ticket->{'CustomFields'}, $ticket_cf_value, 'Ticket custom field'); } } { sub modify_multi_ok { local $Test::Builder::Level = $Test::Builder::Level + 1; my $input = shift; my $messages = shift; my $output = shift; my $name = shift; my $payload = { CustomFields => { $multi_cf_id => $input, }, }; my $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, $messages); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); my $ticket_cf_value = bag( { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] }, { name => 'Multi', id => $multi_cf_id, type => 'customfield', _url => ignore(), values => bag(@$output) }, ); my $content = $mech->json_response; cmp_deeply($content->{'CustomFields'}, $ticket_cf_value, $name || 'New CF value'); } # starting point: ['multiple', 'values'], modify_multi_ok(['multiple', 'values'], [], ['multiple', 'values'], 'no change'); modify_multi_ok(['multiple', 'values', 'new'], ['new added as a value for Multi'], ['multiple', 'new', 'values'], 'added "new"'); modify_multi_ok(['multiple', 'new'], ['values is no longer a value for custom field Multi'], ['multiple', 'new'], 'removed "values"'); modify_multi_ok('replace all', ['replace all added as a value for Multi', 'multiple is no longer a value for custom field Multi', 'new is no longer a value for custom field Multi'], ['replace all'], 'replaced all values'); modify_multi_ok([], ['replace all is no longer a value for custom field Multi'], [], 'removed all values'); if (RT::Handle::cmp_version($RT::VERSION, '4.2.5') >= 0) { modify_multi_ok(['foo', 'foo', 'bar'], ['foo added as a value for Multi', undef, 'bar added as a value for Multi'], ['bar', 'foo'], 'multiple values with the same name'); modify_multi_ok(['foo', 'bar'], [], ['bar', 'foo'], 'multiple values with the same name'); modify_multi_ok(['bar'], ['foo is no longer a value for custom field Multi'], ['bar'], 'multiple values with the same name'); modify_multi_ok(['bar', 'bar', 'bar'], [undef, undef], ['bar'], 'multiple values with the same name'); } else { modify_multi_ok(['foo', 'foo', 'bar'], ['foo added as a value for Multi', 'foo added as a value for Multi', 'bar added as a value for Multi'], ['bar', 'foo', 'foo'], 'multiple values with the same name'); modify_multi_ok(['foo', 'bar'], ['foo is no longer a value for custom field Multi'], ['bar', 'foo'], 'multiple values with the same name'); modify_multi_ok(['bar'], ['foo is no longer a value for custom field Multi'], ['bar'], 'multiple values with the same name'); modify_multi_ok(['bar', 'bar', 'bar'], ['bar added as a value for Multi', 'bar added as a value for Multi'], ['bar', 'bar', 'bar'], 'multiple values with the same name'); } } # Ticket Creation with image CF through JSON Base64 my $image_name = 'image.png'; my $image_path = RT::Test::get_relocatable_file($image_name, 'data'); my $image_content; open my $fh, '<', $image_path or die "Cannot read $image_path: $!\n"; { local $/; $image_content = <$fh>; } close $fh; my $image_cf = RT::CustomField->new(RT->SystemUser); $image_cf->Create(LookupType => 'RT::Queue-RT::Ticket', Name => 'Image CF', Type => 'Image', MaxValues => 1, Queue => 'General'); my $image_cf_id = $image_cf->id; { my $payload = { Subject => 'Ticket creation with image CF', From => 'test@bestpractical.com', To => 'rt@localhost', Queue => 'General', Content => 'Testing ticket creation with Base64 encoded Image Custom Field using REST API.', CustomFields => { $image_cf_id => { FileName => $image_name, FileType => 'image/png', FileContent => MIME::Base64::encode_base64($image_content), }, }, }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($ticket_url = $res->header('location')); ok(($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); my $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); my $image_ocfv = $ticket->CustomFieldValues('Image CF')->First; is($image_ocfv->Content, $image_name); is($image_ocfv->ContentType, 'image/png'); is($image_ocfv->LargeContent, $image_content); } # Ticket Update with image CF through JSON Base64 { # Ticket update to delete image CF my $payload = { CustomFields => { $image_cf_id => undef, }, }; my $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); my $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); my $image_ocfv = $ticket->CustomFieldValues('Image CF')->First; is($image_ocfv, undef); # Ticket update with a value for image CF $payload = { Subject => 'Ticket with image CF', CustomFields => { $image_cf_id => { FileName => $image_name, FileType => 'image/png', FileContent => MIME::Base64::encode_base64($image_content), }, }, }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ["Ticket $ticket_id: Subject changed from 'Ticket creation with image CF' to 'Ticket with image CF'", "Image CF $image_name added"]); $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); $image_ocfv = $ticket->CustomFieldValues('Image CF')->First; is($image_ocfv->Content, $image_name); is($image_ocfv->ContentType, 'image/png'); is($image_ocfv->LargeContent, $image_content); } # Ticket Creation with multi-value image CF through JSON Base64 my $multi_image_cf = RT::CustomField->new(RT->SystemUser); $multi_image_cf->Create(LookupType => 'RT::Queue-RT::Ticket', Name => 'Multi Image CF', Type => 'Image', MaxValues => 0, Queue => 'General'); my $multi_image_cf_id = $multi_image_cf->id; { my $payload = { Subject => 'Ticket creation with multi-value image CF', From => 'test@bestpractical.com', To => 'rt@localhost', Queue => 'General', Content => 'Testing ticket creation with Base64 encoded Multi-Value Image Custom Field using REST API.', CustomFields => { $multi_image_cf_id => [ { FileName => $image_name, FileType => 'image/png', FileContent => MIME::Base64::encode_base64($image_content), }, { FileName => 'Duplicate', FileType => 'image/png', FileContent => MIME::Base64::encode_base64($image_content), }, ], }, }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($ticket_url = $res->header('location')); ok(($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); my $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); my @multi_image_ocfvs = @{$ticket->CustomFieldValues('Multi Image CF')->ItemsArrayRef}; is(scalar(@multi_image_ocfvs), 2); is($multi_image_ocfvs[0]->Content, $image_name); is($multi_image_ocfvs[0]->ContentType, 'image/png'); is($multi_image_ocfvs[0]->LargeContent, $image_content); is($multi_image_ocfvs[1]->Content, 'Duplicate'); is($multi_image_ocfvs[1]->ContentType, 'image/png'); is($multi_image_ocfvs[1]->LargeContent, $image_content); } # Ticket Update with multi-value image CF through JSON Base64 { # Ticket Creation with empty multi-value image CF my $payload = { Subject => 'Ticket creation with empty multi-value image CF', From => 'test@bestpractical.com', To => 'rt@localhost', Queue => 'General', Content => 'Testing ticket creation with Base64 encoded Multi-Value Image Custom Field using REST API.', }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($ticket_url = $res->header('location')); ok(($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); my $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); my $multi_image_ocfvs = $ticket->CustomFieldValues('Multi Image CF'); is($multi_image_ocfvs->Count, 0); # Ticket update with two values for multi-value image CF $payload = { Subject => 'Ticket with multi-value image CF', CustomFields => { $multi_image_cf_id => [ { FileName => $image_name, FileType => 'image/png', FileContent => MIME::Base64::encode_base64($image_content), }, { FileName => 'Duplicate', FileType => 'image/png', FileContent => MIME::Base64::encode_base64($image_content), }, ], }, }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ["Ticket $ticket_id: Subject changed from 'Ticket creation with empty multi-value image CF' to 'Ticket with multi-value image CF'", "$image_name added as a value for Multi Image CF", "Duplicate added as a value for Multi Image CF"]); $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); my @multi_image_ocfvs = @{$ticket->CustomFieldValues('Multi Image CF')->ItemsArrayRef}; is(scalar(@multi_image_ocfvs), 2); is($multi_image_ocfvs[0]->Content, $image_name); is($multi_image_ocfvs[0]->ContentType, 'image/png'); is($multi_image_ocfvs[0]->LargeContent, $image_content); is($multi_image_ocfvs[1]->Content, 'Duplicate'); is($multi_image_ocfvs[1]->ContentType, 'image/png'); is($multi_image_ocfvs[1]->LargeContent, $image_content); # Ticket update with deletion of one value for multi-value image CF $payload = { Subject => 'Ticket with deletion of one value for multi-value image CF', CustomFields => { $multi_image_cf_id => [ $image_name ], }, }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); is_deeply($mech->json_response, ["Ticket $ticket_id: Subject changed from 'Ticket with multi-value image CF' to 'Ticket with deletion of one value for multi-value image CF'", "Duplicate is no longer a value for custom field Multi Image CF"]); $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); @multi_image_ocfvs = @{$ticket->CustomFieldValues('Multi Image CF')->ItemsArrayRef}; is(scalar(@multi_image_ocfvs), 1); is($multi_image_ocfvs[0]->Content, $image_name); is($multi_image_ocfvs[0]->ContentType, 'image/png'); is($multi_image_ocfvs[0]->LargeContent, $image_content); # Ticket update with non-unique values for multi-value image CF $payload = { Subject => 'Ticket with non-unique values for multi-value image CF', CustomFields => { $multi_image_cf_id => [ { FileName => $image_name, FileType => 'image/png', FileContent => MIME::Base64::encode_base64($image_content), }, $image_name, { FileName => 'Duplicate', FileType => 'image/png', FileContent => MIME::Base64::encode_base64($image_content), }, ], }, }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, ); is($res->code, 200); $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); @multi_image_ocfvs = @{$ticket->CustomFieldValues('Multi Image CF')->ItemsArrayRef}; if (RT::Handle::cmp_version($RT::VERSION, '4.2.5') >= 0) { is_deeply($mech->json_response, ["Ticket $ticket_id: Subject changed from 'Ticket with deletion of one value for multi-value image CF' to 'Ticket with non-unique values for multi-value image CF'", undef, "Duplicate added as a value for Multi Image CF"]); is(scalar(@multi_image_ocfvs), 2); is($multi_image_ocfvs[0]->Content, $image_name); is($multi_image_ocfvs[0]->ContentType, 'image/png'); is($multi_image_ocfvs[0]->LargeContent, $image_content); is($multi_image_ocfvs[1]->Content, 'Duplicate'); is($multi_image_ocfvs[1]->ContentType, 'image/png'); is($multi_image_ocfvs[1]->LargeContent, $image_content); } else { is_deeply($mech->json_response, ["Ticket $ticket_id: Subject changed from 'Ticket with deletion of one value for multi-value image CF' to 'Ticket with non-unique values for multi-value image CF'", "$image_name added as a value for Multi Image CF", "Duplicate added as a value for Multi Image CF"]); is(scalar(@multi_image_ocfvs), 3); is($multi_image_ocfvs[0]->Content, $image_name); is($multi_image_ocfvs[0]->ContentType, 'image/png'); is($multi_image_ocfvs[0]->LargeContent, $image_content); is($multi_image_ocfvs[1]->Content, $image_name); is($multi_image_ocfvs[1]->ContentType, 'image/png'); is($multi_image_ocfvs[1]->LargeContent, $image_content); is($multi_image_ocfvs[2]->Content, 'Duplicate'); is($multi_image_ocfvs[2]->ContentType, 'image/png'); is($multi_image_ocfvs[2]->LargeContent, $image_content); } } # Ticket Creation with image CF through multipart/form-data my $json = JSON->new->utf8; { my $payload = { Subject => 'Ticket creation with image CF', From => 'test@bestpractical.com', To => 'rt@localhost', Queue => 'General', Content => 'Testing ticket creation with multipart/form-data Image Custom Field using REST API.', CustomFields => { $image_cf_id => { UploadField => 'IMAGE' }, }, }; no warnings 'once'; $HTTP::Request::Common::DYNAMIC_FILE_UPLOAD = 1; my $res = $mech->post("$rest_base_path/ticket", 'Authorization' => $auth, 'Content_Type' => 'form-data', 'Content' => [ 'JSON' => $json->encode($payload), 'IMAGE' => [$image_path, $image_name, 'Content-Type' => 'image/png'], ] ); is($res->code, 201); ok($ticket_url = $res->header('location')); ok(($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); my $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); my $image_ocfv = $ticket->CustomFieldValues('Image CF')->First; is($image_ocfv->Content, $image_name); is($image_ocfv->ContentType, 'image/png'); is($image_ocfv->LargeContent, $image_content); } # Ticket Update with image CF through multipart/form-data { # Ticket Creation with empty image CF my $payload = { Subject => 'Ticket creation with empty image CF', From => 'test@bestpractical.com', To => 'rt@localhost', Queue => 'General', Content => 'Testing ticket update with multipart/form-data Image Custom Field using REST API.', }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($ticket_url = $res->header('location')); ok(($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); my $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); my $image_ocfv = $ticket->CustomFieldValues('Image CF')->First; is($image_ocfv, undef); # Ticket update with a value for image CF $payload = { Subject => 'Ticket with image CF', CustomFields => { $image_cf_id => { UploadField => 'IMAGE' }, }, }; no warnings 'once'; $HTTP::Request::Common::DYNAMIC_FILE_UPLOAD = 1; $res = $mech->put("$ticket_url", 'Authorization' => $auth, 'Content_Type' => 'form-data', 'Content' => [ 'JSON' => $json->encode($payload), 'IMAGE' => [$image_path, $image_name, 'Content-Type' => 'image/png'], ] ); is($res->code, 200); is_deeply($mech->json_response, ["Ticket $ticket_id: Subject changed from 'Ticket creation with empty image CF' to 'Ticket with image CF'", "Image CF $image_name added"]); $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); $image_ocfv = $ticket->CustomFieldValues('Image CF')->First; is($image_ocfv->Content, $image_name); is($image_ocfv->ContentType, 'image/png'); is($image_ocfv->LargeContent, $image_content); } # Ticket Creation with multi-value image CF through multipart/form-data { my $payload = { Subject => 'Ticket creation with multi-value image CF', From => 'test@bestpractical.com', To => 'rt@localhost', Queue => 'General', Content => 'Testing ticket creation with multipart/form-data Multi-Value Image Custom Field using REST API.', CustomFields => { $multi_image_cf_id => [ { UploadField => 'IMAGE_1' }, { UploadField => 'IMAGE_2' } ], }, }; my $res = $mech->post("$rest_base_path/ticket", 'Authorization' => $auth, 'Content_Type' => 'form-data', 'Content' => [ 'JSON' => $json->encode($payload), 'IMAGE_1' => [$image_path, $image_name, 'Content-Type' => 'image/png'], 'IMAGE_2' => [$image_path, 'Duplicate', 'Content-Type' => 'image/png'], ] ); is($res->code, 201); ok($ticket_url = $res->header('location')); ok(($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); my $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); my @multi_image_ocfvs = @{$ticket->CustomFieldValues('Multi Image CF')->ItemsArrayRef}; is(scalar(@multi_image_ocfvs), 2); is($multi_image_ocfvs[0]->Content, $image_name); is($multi_image_ocfvs[0]->ContentType, 'image/png'); is($multi_image_ocfvs[0]->LargeContent, $image_content); is($multi_image_ocfvs[1]->Content, 'Duplicate'); is($multi_image_ocfvs[1]->ContentType, 'image/png'); is($multi_image_ocfvs[1]->LargeContent, $image_content); } # Ticket Update with multi-value image CF through multipart/form-data { # Ticket Creation with empty multi-value image CF my $payload = { Subject => 'Ticket creation with empty multi-value image CF', From => 'test@bestpractical.com', To => 'rt@localhost', Queue => 'General', Content => 'Testing ticket creation with multipart/form-data Multi-Value Image Custom Field using REST API.', }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($ticket_url = $res->header('location')); ok(($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); my $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); my $multi_image_ocfvs = $ticket->CustomFieldValues('Multi Image CF'); is($multi_image_ocfvs->Count, 0); # Ticket update with two values for multi-value image CF $payload = { Subject => 'Ticket with multi-value image CF', CustomFields => { $multi_image_cf_id => [ { UploadField => 'IMAGE_1' }, { UploadField => 'IMAGE_2' } ], }, }; $res = $mech->put($ticket_url, 'Authorization' => $auth, 'Content_Type' => 'form-data', 'Content' => [ 'JSON' => $json->encode($payload), 'IMAGE_1' => [$image_path, $image_name, 'Content-Type' => 'image/png'], 'IMAGE_2' => [$image_path, 'Duplicate', 'Content-Type' => 'image/png'], ] ); is($res->code, 200); is_deeply($mech->json_response, ["Ticket $ticket_id: Subject changed from 'Ticket creation with empty multi-value image CF' to 'Ticket with multi-value image CF'", "$image_name added as a value for Multi Image CF", "Duplicate added as a value for Multi Image CF"]); $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); my @multi_image_ocfvs = @{$ticket->CustomFieldValues('Multi Image CF')->ItemsArrayRef}; is(scalar(@multi_image_ocfvs), 2); is($multi_image_ocfvs[0]->Content, $image_name); is($multi_image_ocfvs[0]->ContentType, 'image/png'); is($multi_image_ocfvs[0]->LargeContent, $image_content); is($multi_image_ocfvs[1]->Content, 'Duplicate'); is($multi_image_ocfvs[1]->ContentType, 'image/png'); is($multi_image_ocfvs[1]->LargeContent, $image_content); # Ticket update with deletion of one value for multi-value image CF $payload = { Subject => 'Ticket with deletion of one value for multi-value image CF', CustomFields => { $multi_image_cf_id => [ $image_name ], }, }; $res = $mech->put($ticket_url, 'Authorization' => $auth, 'Content_Type' => 'form-data', 'Content' => [ 'JSON' => $json->encode($payload), ] ); is($res->code, 200); is_deeply($mech->json_response, ["Ticket $ticket_id: Subject changed from 'Ticket with multi-value image CF' to 'Ticket with deletion of one value for multi-value image CF'", "Duplicate is no longer a value for custom field Multi Image CF"]); $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); @multi_image_ocfvs = @{$ticket->CustomFieldValues('Multi Image CF')->ItemsArrayRef}; is(scalar(@multi_image_ocfvs), 1); is($multi_image_ocfvs[0]->Content, $image_name); is($multi_image_ocfvs[0]->ContentType, 'image/png'); is($multi_image_ocfvs[0]->LargeContent, $image_content); # Ticket update with non-unique values for multi-value image CF $payload = { Subject => 'Ticket with non-unique values for multi-value image CF', CustomFields => { $multi_image_cf_id => [ { UploadField => 'IMAGE_1' }, $image_name, { UploadField => 'IMAGE_2' } ], }, }; $res = $mech->put($ticket_url, 'Authorization' => $auth, 'Content_Type' => 'form-data', 'Content' => [ 'JSON' => $json->encode($payload), 'IMAGE_1' => [$image_path, $image_name, 'Content-Type' => 'image/png'], 'IMAGE_2' => [$image_path, 'Duplicate', 'Content-Type' => 'image/png'], ] ); is($res->code, 200); $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); @multi_image_ocfvs = @{$ticket->CustomFieldValues('Multi Image CF')->ItemsArrayRef}; if (RT::Handle::cmp_version($RT::VERSION, '4.2.5') >= 0) { is_deeply($mech->json_response, ["Ticket $ticket_id: Subject changed from 'Ticket with deletion of one value for multi-value image CF' to 'Ticket with non-unique values for multi-value image CF'", undef, "Duplicate added as a value for Multi Image CF"]); is(scalar(@multi_image_ocfvs), 2); is($multi_image_ocfvs[0]->Content, $image_name); is($multi_image_ocfvs[0]->ContentType, 'image/png'); is($multi_image_ocfvs[0]->LargeContent, $image_content); is($multi_image_ocfvs[1]->Content, 'Duplicate'); is($multi_image_ocfvs[1]->ContentType, 'image/png'); is($multi_image_ocfvs[1]->LargeContent, $image_content); } else { is_deeply($mech->json_response, ["Ticket $ticket_id: Subject changed from 'Ticket with deletion of one value for multi-value image CF' to 'Ticket with non-unique values for multi-value image CF'", "$image_name added as a value for Multi Image CF", "Duplicate added as a value for Multi Image CF"]); is(scalar(@multi_image_ocfvs), 3); is($multi_image_ocfvs[0]->Content, $image_name); is($multi_image_ocfvs[0]->ContentType, 'image/png'); is($multi_image_ocfvs[0]->LargeContent, $image_content); is($multi_image_ocfvs[1]->Content, $image_name); is($multi_image_ocfvs[1]->ContentType, 'image/png'); is($multi_image_ocfvs[1]->LargeContent, $image_content); is($multi_image_ocfvs[2]->Content, 'Duplicate'); is($multi_image_ocfvs[2]->ContentType, 'image/png'); is($multi_image_ocfvs[2]->LargeContent, $image_content); } } { my $space_cf = RT::CustomField->new( RT->SystemUser ); my ( $ok, $msg ) = $space_cf->Create( Name => 'Single Text', Type => 'FreeformSingle', Queue => $queue->Id ); ok( $ok, $msg ); my $payload = { Queue => 'General', Subject => 'Ticket creation with custom field names containing spaces', Content => 'Testing ticket creation using REST API.', CustomFields => { 'Single Text' => 'Hello world!', }, }; my $res = $mech->post_json( "$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is( $res->code, 201 ); ok( $ticket_url = $res->header('location') ); ok( ($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)] ); my $ticket = RT::Ticket->new($user); $ticket->Load($ticket_id); is( $ticket->FirstCustomFieldValue($space_cf), 'Hello world!', 'custom field value is set' ); $payload = { CustomFields => { 'Single Text' => 'Howdy world!', }, }; $res = $mech->put_json( $ticket_url, $payload, 'Authorization' => $auth, ); is( $res->code, 200 ); is_deeply( $mech->json_response, ["Single Text Hello world! changed to Howdy world!"] ); is( $ticket->FirstCustomFieldValue($space_cf), 'Howdy world!', 'custom field value is updated' ); } done_testing; rt-5.0.1/t/rest2/not_found.t000644 000765 000024 00000001361 14005011336 016464 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; sub is_404 { local $Test::Builder::Level = $Test::Builder::Level + 1; my $res = shift; is($res->code, 404); is($mech->json_response->{message}, 'Not Found'); } # Proper 404 Response { for (qw[ /foobar /foo /index.html /ticket.do/1 /ticket/foo /1/1 /record /collection ]) { my $path = $rest_base_path . $_; is_404($mech->get($path, 'Authorization' => $auth)); is_404($mech->post($path, { param => 'value' }, 'Authorization' => $auth)); } } done_testing; rt-5.0.1/t/rest2/search-simple.t000644 000765 000024 00000003306 14005011336 017226 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; my $alpha = RT::Test->load_or_create_queue( Name => 'Alpha', Description => 'Queue for test' ); my $beta = RT::Test->load_or_create_queue( Name => 'Beta', Description => 'Queue for test' ); my $bravo = RT::Test->load_or_create_queue( Name => 'Bravo', Description => 'Queue to test sorted search' ); $user->PrincipalObj->GrantRight( Right => 'SuperUser' ); my $alpha_id = $alpha->Id; my $beta_id = $beta->Id; my $bravo_id = $bravo->Id; # without disabled { my $res = $mech->get("$rest_base_path/queues/all", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 4); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 4); is(scalar @{$content->{items}}, 4); } # Find disabled { $alpha->SetDisabled(1); my $res = $mech->get("$rest_base_path/queues/all", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{count}, 3); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 3); is(scalar @{$content->{items}}, 3); $res = $mech->get("$rest_base_path/queues/all?find_disabled_rows=1", 'Authorization' => $auth, ); is($res->code, 200); $content = $mech->json_response; is($content->{count}, 5); is($content->{page}, 1); is($content->{per_page}, 20); is($content->{total}, 5); is(scalar @{$content->{items}}, 5); } done_testing; rt-5.0.1/t/rest2/attachments.t000644 000765 000024 00000027101 14005011336 017004 0ustar00sunnavystaff000000 000000 use strict; use warnings; use lib 't/lib'; use RT::Test::REST2 tests => undef; use Test::Deep; use MIME::Base64; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; $user->PrincipalObj->GrantRight(Right => 'CreateTicket'); $user->PrincipalObj->GrantRight(Right => 'ReplyToTicket'); $user->PrincipalObj->GrantRight(Right => 'CommentOnTicket'); $user->PrincipalObj->GrantRight(Right => 'ShowTicket'); $user->PrincipalObj->GrantRight(Right => 'ShowTicketComments'); my $ticket = RT::Ticket->new($user); $ticket->Create(Queue => 'General', Subject => 'hello world'); my $ticket_id = $ticket->id; my $image_name = 'image.png'; my $image_path = RT::Test::get_relocatable_file($image_name, 'data'); my $image_content; open my $fh, '<', $image_path or die "Cannot read $image_path: $!\n"; { local $/; $image_content = <$fh>; } close $fh; $image_content = MIME::Base64::encode_base64($image_content); # Comment ticket with image and text attachments through JSON Base64 { my $payload = { Content => 'Have you seen this image', ContentType => 'text/html', Subject => 'HTML comment with PNG image and text file', Attachments => [ { FileName => $image_name, FileType => 'image/png', FileContent => $image_content, }, { FileName => 'password', FileType => 'text/plain', FileContent => MIME::Base64::encode_base64('Hey this is secret!'), }, ], }; my $res = $mech->post_json("$rest_base_path/ticket/$ticket_id/comment", $payload, 'Authorization' => $auth, ); is($res->code, 201); cmp_deeply($mech->json_response, [re(qr/Comments added|Message recorded/)]); my $transaction_id = $ticket->Transactions->Last->id; my $attachments = $ticket->Attachments->ItemsArrayRef; # 3 attachments + 1 wrapper is(scalar(@$attachments), 4); # 1st attachment is wrapper is($attachments->[0]->TransactionId, $transaction_id); is($attachments->[0]->Parent, 0); is($attachments->[0]->Subject, 'HTML comment with PNG image and text file'); ok(!$attachments->[0]->Filename); is($attachments->[0]->ContentType, 'multipart/mixed'); # 2nd attachment is comment's content is($attachments->[1]->Parent, $attachments->[0]->id); is($attachments->[1]->TransactionId, $transaction_id); is($attachments->[1]->ContentType, 'text/html'); is($attachments->[1]->ContentEncoding, 'none'); is($attachments->[1]->Content, 'Have you seen this image'); ok(!$attachments->[1]->Subject); # 3rd attachment is image my $expected_encoding = $RT::Handle->BinarySafeBLOBs ? 'none' : 'base64'; is($attachments->[2]->Parent, $attachments->[0]->id); is($attachments->[2]->TransactionId, $transaction_id); is($attachments->[2]->ContentType, 'image/png'); is($attachments->[2]->ContentEncoding, $expected_encoding); is($attachments->[2]->Filename, $image_name); ok(!$attachments->[2]->Subject); # 4th attachment is text file is($attachments->[3]->Parent, $attachments->[0]->id); is($attachments->[3]->TransactionId, $transaction_id); is($attachments->[3]->ContentType, 'text/plain'); is($attachments->[3]->ContentEncoding, 'none'); is($attachments->[3]->Filename, 'password'); is($attachments->[3]->Content, 'Hey this is secret!'); ok(!$attachments->[3]->Subject); } # Check the GET /ticket/:id/attachments endpoint { my $res = $mech->get( "$rest_base_path/ticket/$ticket_id/attachments?fields=Subject,Filename,ContentType,ContentLength", 'Authorization' => $auth ); is( $res->code, 200 ); cmp_deeply( $mech->json_response, { per_page => 20, pages => 1, total => 4, page => 1, count => 4, items => [ { type => 'attachment', Subject => 'HTML comment with PNG image and text file', _url => re(qr/^https?:/), ContentType => 'multipart/mixed', ContentLength => 0, Filename => '', id => re(qr/^\d+$/) }, { type => 'attachment', Subject => '', _url => re(qr/^https?:/), ContentType => 'text/html', ContentLength => 31, Filename => '', id => re(qr/^\d+$/) }, { type => 'attachment', Subject => '', _url => re(qr/^https?:/), ContentType => 'image/png', ContentLength => 3929, Filename => 'image.png', id => re(qr/^\d+$/) }, { type => 'attachment', Subject => '', _url => re(qr/^https?:/), ContentType => 'text/plain', ContentLength => 19, Filename => 'password', id => re(qr/^\d+$/) }, ] }, 'Got expected attachment list' ); } # Comment ticket with image attachment and no content through JSON Base64 { my $payload = { Subject => 'No content, just an image', Attachments => [ { FileName => $image_name, FileType => 'image/png', FileContent => $image_content, }, ], }; my $res = $mech->post_json("$rest_base_path/ticket/$ticket_id/comment", $payload, 'Authorization' => $auth, ); is($res->code, 201); cmp_deeply($mech->json_response, [re(qr/Comments added|Message recorded/)]); my $transaction_id = $ticket->Transactions->Last->id; my @attachments = grep { $_->TransactionId == $transaction_id } @{$ticket->Attachments->ItemsArrayRef}; # 2 attachments + 1 wrapper is(scalar(@attachments), 3); # 1st attachment is wrapper is($attachments[0]->Parent, 0); is($attachments[0]->Subject, 'No content, just an image'); ok(!$attachments[0]->Filename); is($attachments[0]->ContentType, 'multipart/mixed'); # 2nd attachment is empty comment's content is($attachments[1]->Parent, $attachments[0]->id); is($attachments[1]->TransactionId, $transaction_id); is($attachments[1]->ContentType, 'application/octet-stream'); ok(!$attachments[1]->ContentEncoding); ok(!$attachments[1]->Content); ok(!$attachments[1]->Subject); # 3rd attachment is image my $expected_encoding = $RT::Handle->BinarySafeBLOBs ? 'none' : 'base64'; is($attachments[2]->Parent, $attachments[0]->id); is($attachments[2]->TransactionId, $transaction_id); is($attachments[2]->ContentType, 'image/png'); is($attachments[2]->ContentEncoding, $expected_encoding); is($attachments[2]->Filename, $image_name); ok(!$attachments[2]->Subject); } my $json = JSON->new->utf8; # Comment ticket with image and text attachments through multipart/form-data { my $payload = { Content => 'Have you seen this image', ContentType => 'text/html', Subject => 'HTML comment with PNG image and text file', }; $HTTP::Request::Common::DYNAMIC_FILE_UPLOAD = 1; my $res = $mech->post("$rest_base_path/ticket/$ticket_id/comment", 'Authorization' => $auth, 'Content_Type' => 'form-data', 'Content' => [ 'JSON' => $json->encode($payload), 'Attachments' => [$image_path, $image_name, 'Content-Type' => 'image/png'], 'Attachments' => [undef, 'password', 'Content-Type' => 'text/plain', Content => 'Hey this is secret!']]); is($res->code, 201); cmp_deeply($mech->json_response, [re(qr/Comments added|Message recorded/)]); my $transaction_id = $ticket->Transactions->Last->id; my @attachments = grep { $_->TransactionId == $transaction_id } @{$ticket->Attachments->ItemsArrayRef}; # 3 attachments + 1 wrapper is(scalar(@attachments), 4); # 1st attachment is wrapper is($attachments[0]->TransactionId, $transaction_id); is($attachments[0]->Parent, 0); is($attachments[0]->Subject, 'HTML comment with PNG image and text file'); ok(!$attachments[0]->Filename); is($attachments[0]->ContentType, 'multipart/mixed'); # 2nd attachment is comment's content is($attachments[1]->Parent, $attachments[0]->id); is($attachments[1]->TransactionId, $transaction_id); is($attachments[1]->ContentType, 'text/html'); is($attachments[1]->ContentEncoding, 'none'); is($attachments[1]->Content, 'Have you seen this image'); ok(!$attachments[1]->Subject); # 3rd attachment is image my $expected_encoding = $RT::Handle->BinarySafeBLOBs ? 'none' : 'base64'; is($attachments[2]->Parent, $attachments[0]->id); is($attachments[2]->TransactionId, $transaction_id); is($attachments[2]->ContentType, 'image/png'); is($attachments[2]->ContentEncoding, $expected_encoding); is($attachments[2]->Filename, $image_name); ok(!$attachments[2]->Subject); # 4th attachment is text file is($attachments[3]->Parent, $attachments[0]->id); is($attachments[3]->TransactionId, $transaction_id); is($attachments[3]->ContentType, 'text/plain'); is($attachments[3]->ContentEncoding, 'none'); is($attachments[3]->Filename, 'password'); is($attachments[3]->Content, 'Hey this is secret!'); ok(!$attachments[3]->Subject); } # Comment ticket with image attachment and no content through multipart/form-data { my $payload = { Subject => 'No content, just an image', }; $HTTP::Request::Common::DYNAMIC_FILE_UPLOAD = 1; my $res = $mech->post("$rest_base_path/ticket/$ticket_id/comment", 'Authorization' => $auth, 'Content_Type' => 'form-data', 'Content' => [ 'JSON' => $json->encode($payload), 'Attachments' => [$image_path, $image_name, 'Content-Type' => 'image/png']]); is($res->code, 201); cmp_deeply($mech->json_response, [re(qr/Comments added|Message recorded/)]); my $transaction_id = $ticket->Transactions->Last->id; my @attachments = grep { $_->TransactionId == $transaction_id } @{$ticket->Attachments->ItemsArrayRef}; # 2 attachments + 1 wrapper is(scalar(@attachments), 3); # 1st attachment is wrapper is($attachments[0]->Parent, 0); is($attachments[0]->Subject, 'No content, just an image'); ok(!$attachments[0]->Filename); is($attachments[0]->ContentType, 'multipart/mixed'); # 2nd attachment is empty comment's content is($attachments[1]->Parent, $attachments[0]->id); is($attachments[1]->TransactionId, $transaction_id); is($attachments[1]->ContentType, 'application/octet-stream'); ok(!$attachments[1]->ContentEncoding); ok(!$attachments[1]->Content); ok(!$attachments[1]->Subject); # 3rd attachment is image my $expected_encoding = $RT::Handle->BinarySafeBLOBs ? 'none' : 'base64'; is($attachments[2]->Parent, $attachments[0]->id); is($attachments[2]->TransactionId, $transaction_id); is($attachments[2]->ContentType, 'image/png'); is($attachments[2]->ContentEncoding, $expected_encoding); is($attachments[2]->Filename, $image_name); ok(!$attachments[2]->Subject); } done_testing; rt-5.0.1/t/rest2/data/000755 000765 000024 00000000000 14005011336 015214 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/rest2/conflict.t000644 000765 000024 00000015476 14005011336 016306 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; $user->PrincipalObj->GrantRight( Right => $_ ) for qw/CreateTicket ShowTicket ModifyTicket/; # Create and update a ticket without conflicts { my ($ticket_url, $ticket_id); my $payload = { Subject => 'Version 1', Queue => 'General', }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($ticket_url = $res->header('location')); ok(($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); is($mech->json_response->{Subject}, 'Version 1'); my $first_etag = $res->header('ETag'); ok($first_etag, "got an ETag"); $payload = { Subject => 'Version 2', }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, 'If-Match' => $first_etag, ); is($res->code, 200); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); is($mech->json_response->{Subject}, 'Version 2'); my $second_etag = $res->header('ETag'); ok($second_etag, "got an ETag"); $payload = { Subject => 'Version 3', }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, 'If-Match' => $second_etag, ); is($res->code, 200); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); is($mech->json_response->{Subject}, 'Version 3'); my $third_etag = $res->header('ETag'); ok($third_etag, "got an ETag"); $res = $mech->get($ticket_url, 'Authorization' => $auth, 'If-None-Match' => $third_etag, ); is($res->code, 304, 'not modified'); $res = $mech->get($ticket_url, 'Authorization' => $auth, 'If-None-Match' => $second_etag, ); is($res->code, 200, 'has been modified'); is($mech->json_response->{Subject}, 'Version 3'); is($res->header('ETag'), $third_etag, 'ETag unchanged'); } # Create and update a ticket with reusing old ETags { my ($ticket_url, $ticket_id); my $payload = { Subject => 'Version 1', Queue => 'General', }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($ticket_url = $res->header('location')); ok(($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); is($mech->json_response->{Subject}, 'Version 1'); my $first_etag = $res->header('ETag'); ok($first_etag, "got an ETag"); $payload = { Subject => 'Version 2', }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, 'If-Match' => $first_etag, ); is($res->code, 200); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); is($mech->json_response->{Subject}, 'Version 2'); my $second_etag = $res->header('ETag'); ok($second_etag, "got an ETag"); $payload = { Subject => 'Version 3', }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, 'If-Match' => $first_etag, # <-- note old etag use ); is($res->code, 412); is($mech->json_response->{message}, 'Precondition Failed'); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); is($mech->json_response->{Subject}, 'Version 2'); my $third_etag = $res->header('ETag'); ok($third_etag, "got an ETag"); is($third_etag, $second_etag, "ETag is unchanged from the previous one"); $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, # no If-Match header ); is($res->code, 200); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); is($mech->json_response->{Subject}, 'Version 3'); my $fourth_etag = $res->header('ETag'); ok($fourth_etag, "got an ETag"); } # Create and update a ticket with legitimate conflicts { my ($ticket_url, $ticket_id); my $payload = { Subject => 'Version 1', Queue => 'General', }; my $res = $mech->post_json("$rest_base_path/ticket", $payload, 'Authorization' => $auth, ); is($res->code, 201); ok($ticket_url = $res->header('location')); ok(($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)]); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); is($mech->json_response->{Subject}, 'Version 1'); my $first_etag = $res->header('ETag'); ok($first_etag, "got an ETag"); $payload = { Subject => 'Version 2', }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, 'If-Match' => $first_etag, ); is($res->code, 200); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); is($mech->json_response->{Subject}, 'Version 2'); my $second_etag = $res->header('ETag'); ok($second_etag, "got an ETag"); # some other user comes in to update the ticket... my $ticket_obj = RT::Ticket->new(RT->SystemUser); $ticket_obj->Load($ticket_id); $ticket_obj->Correspond(Content => 'oops'); $payload = { Subject => 'Version 3', }; $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, 'If-Match' => $second_etag, # <-- note old etag use ); is($res->code, 412); is($mech->json_response->{message}, 'Precondition Failed'); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); is($mech->json_response->{Subject}, 'Version 2'); my $third_etag = $res->header('ETag'); ok($third_etag, "got an ETag"); isnt($third_etag, $second_etag, "ETag is changed from the previous one"); # now at this point the REST API consumer can either prompt the user # what to do, or analyze the changes to see that the other changes # made don't affect the changes we want to make $res = $mech->put_json($ticket_url, $payload, 'Authorization' => $auth, 'If-Match' => $third_etag, ); is($res->code, 200); $res = $mech->get($ticket_url, 'Authorization' => $auth, ); is($res->code, 200); is($mech->json_response->{Subject}, 'Version 3'); my $fourth_etag = $res->header('ETag'); ok($fourth_etag, "got an ETag"); } done_testing; rt-5.0.1/t/rest2/customfieldvalues.t000644 000765 000024 00000016737 14005011336 020244 0ustar00sunnavystaff000000 000000 use strict; use warnings; use lib 't/lib'; use RT::Test::REST2 tests => undef; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; my $select_cf = RT::CustomField->new(RT->SystemUser); $select_cf->Create(Name => 'Select CF', Type => 'Select', MaxValues => 1); $select_cf->AddValue(Name => 'First Value', SortOrder => 0); $select_cf->AddValue(Name => 'Second Value', SortOrder => 1); $select_cf->AddValue(Name => 'Third Value', SortOrder => 2); my $select_cf_id = $select_cf->id; my $select_cf_values = $select_cf->Values->ItemsArrayRef; my $basedon_cf = RT::CustomField->new(RT->SystemUser); $basedon_cf->Create(Name => 'SubSelect CF', Type => 'Select', MaxValues => 1, BasedOn => $select_cf->id); $basedon_cf->AddValue(Name => 'With First Value', Category => $select_cf_values->[0]->Name, SortOder => 0); $basedon_cf->AddValue(Name => 'With No Value', SortOder => 0); my $basedon_cf_id = $basedon_cf->id; my $basedon_cf_values = $basedon_cf->Values->ItemsArrayRef; # Right test - retrieve all values without SeeCustomField { my $res = $mech->get("$rest_base_path/customfield/$select_cf_id/values", 'Authorization' => $auth, ); is($res->code, 403); } $user->PrincipalObj->GrantRight(Right => 'SeeCustomField'); # Retrieve customfield's hypermedia link for customfieldvalues { my $res = $mech->get("$rest_base_path/customfield/$select_cf_id", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; my $links = $content->{_hyperlinks}; my @cfvs_links = grep { $_->{ref} eq 'customfieldvalues' } @$links; is(scalar(@cfvs_links), 1); like($cfvs_links[0]->{_url}, qr{$rest_base_path/customfield/$select_cf_id/values$}); } # No customfieldvalues hypermedia link for non-select customfield { my $freeform_cf = RT::CustomField->new(RT->SystemUser); $freeform_cf->Create(Name => 'Freeform CF', Type => 'Freeform', MaxValues => 1, Queue => 'General'); my $freeform_cf_id = $freeform_cf->id; my $res = $mech->get("$rest_base_path/customfield/$freeform_cf_id", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; my $links = $content->{_hyperlinks}; my @cfvs_links = grep { $_->{ref} eq 'customfieldvalues' } @$links; is(scalar(@cfvs_links), 0); } # Retrieve all values { my $res = $mech->get("$rest_base_path/customfield/$select_cf_id/values", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{total}, 3); is($content->{count}, 3); my $items = $content->{items}; is(scalar(@$items), 3); for (my $i=0; $i < scalar @$items; $i++) { my $cf_value_id = $select_cf_values->[$i]->id; is($items->[$i]->{type}, 'customfieldvalue'); is($items->[$i]->{id}, $cf_value_id); is($items->[$i]->{name}, $select_cf_values->[$i]->Name); like($items->[$i]->{_url}, qr{$rest_base_path/customfield/$select_cf_id/value/$cf_value_id$}); } } # Right test - udpate a value without AdminCustomFieldValues nor AdminCustomField { my $payload = { Name => 'Third and Last Value', }; my $res = $mech->put_json("$rest_base_path/customfield/$select_cf_id/value/" . $select_cf_values->[-1]->id, $payload, 'Authorization' => $auth, ); is($res->code, 403); $select_cf_values = $select_cf->Values->ItemsArrayRef; is($select_cf_values->[-1]->Name, 'Third Value'); } # Right test - udpate a value without AdminCustomFieldValues but with AdminCustomField { $user->PrincipalObj->GrantRight(Right => 'AdminCustomField'); my $payload = { Name => 'Third and Last Value', }; my $res = $mech->put_json("$rest_base_path/customfield/$select_cf_id/value/" . $select_cf_values->[-1]->id, $payload, 'Authorization' => $auth, ); is($res->code, 200); $select_cf_values = $select_cf->Values->ItemsArrayRef; is($select_cf_values->[-1]->Name, 'Third and Last Value'); } # Right test - udpate a value without AdminCustomField but with AdminCustomFieldValues { $user->PrincipalObj->RevokeRight(Right => 'AdminCustomField'); $user->PrincipalObj->GrantRight(Right => 'AdminCustomFieldValues', Object => $select_cf); $user->PrincipalObj->GrantRight(Right => 'AdminCustomFieldValues', Object => $basedon_cf); my $payload = { Name => 'Third and Last but NOT Least Value', }; my $res = $mech->put_json("$rest_base_path/customfield/$select_cf_id/value/" . $select_cf_values->[-1]->id, $payload, 'Authorization' => $auth, ); is($res->code, 200); $select_cf_values = $select_cf->Values->ItemsArrayRef; is($select_cf_values->[-1]->Name, 'Third and Last but NOT Least Value'); } # Add a value { my $payload = { Name => 'Fourth Value', SortOrder => 3, }; my $res = $mech->post_json("$rest_base_path/customfield/$select_cf_id/value", $payload, 'Authorization' => $auth, ); is($res->code, 201); $select_cf_values = $select_cf->Values->ItemsArrayRef; is(scalar(@$select_cf_values), 4); is($select_cf_values->[-1]->Name, 'Fourth Value'); } # Retrieve a value { my $cfv = $select_cf_values->[-2]; my $cfv_id = $cfv->id; my $res = $mech->get("$rest_base_path/customfield/$select_cf_id/value/$cfv_id", 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; foreach my $field (qw/id Name Description SortOrder Category/) { is($content->{$field}, $cfv->$field); } ok(exists $content->{$_}, "got $_") for qw/Created Creator LastUpdated LastUpdatedBy/; is($content->{CustomField}->{id}, $select_cf_id); my $links = $content->{_hyperlinks}; is(scalar @$links, 2); is($links->[0]{ref}, 'self'); is($links->[0]{id}, $cfv_id); is($links->[0]{type}, 'customfieldvalue'); like($links->[0]{_url}, qr{$rest_base_path/customfield/$select_cf_id/customfieldvalue/$cfv_id$}); is($links->[1]{ref}, 'customfield'); is($links->[1]{id}, $select_cf_id); is($links->[1]{type}, 'customfield'); like($links->[1]{_url}, qr{$rest_base_path/customfield/$select_cf_id$}); } # Retrieve all values filtered by category { my $payload = [ { field => 'Category', value => $select_cf_values->[0]->Name, } ]; my $res = $mech->post_json("$rest_base_path/customfield/$basedon_cf_id/values", $payload, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; is($content->{total}, 1); is($content->{count}, 1); my $items = $content->{items}; is(scalar(@$items), 1); for (my $i=0; $i < scalar @$items; $i++) { my $cf_value_id = $basedon_cf_values->[$i]->id; is($items->[$i]->{type}, 'customfieldvalue'); is($items->[$i]->{id}, $cf_value_id); is($items->[$i]->{name}, $basedon_cf_values->[$i]->Name); like($items->[$i]->{_url}, qr{$rest_base_path/customfield/$basedon_cf_id/value/$cf_value_id$}); } } # Delete a value { my $res = $mech->delete("$rest_base_path/customfield/$select_cf_id/value/" . $select_cf_values->[-1]->id, 'Authorization' => $auth, ); is($res->code, 204); $select_cf_values = $select_cf->Values->ItemsArrayRef; is(scalar(@$select_cf_values), 3); is($select_cf_values->[-1]->Name, 'Third and Last but NOT Least Value'); } done_testing; rt-5.0.1/t/rest2/user-memberships.t000644 000765 000024 00000007451 14005011336 017771 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::REST2 tests => undef; use Test::Warn; my $mech = RT::Test::REST2->mech; my $auth = RT::Test::REST2->authorization_header; my $rest_base_path = '/REST/2.0'; my $user = RT::Test::REST2->user; my $group1 = RT::Group->new(RT->SystemUser); my ($ok, $msg) = $group1->CreateUserDefinedGroup(Name => 'Group 1'); ok($ok, $msg); my $group2 = RT::Group->new(RT->SystemUser); ($ok, $msg) = $group2->CreateUserDefinedGroup(Name => 'Group 2'); ok($ok, $msg); ($ok, $msg) = $group1->AddMember($group2->id); ok($ok, $msg); # Membership addition { my $payload = [ $group2->id ]; # Rights Test - No ModifyOwnMembership my $res = $mech->put_json("$rest_base_path/user/" . $user->id . '/groups', $payload, 'Authorization' => $auth, ); is($res->code, 403, 'Cannot add user to group without ModifyOwnMembership right'); # Rights Test - With ModifyOwnMembership $user->PrincipalObj->GrantRight(Right => 'ModifyOwnMembership'); $res = $mech->put_json("$rest_base_path/user/" . $user->id . '/groups', $payload, 'Authorization' => $auth, ); is($res->code, 200, 'Add user to group with ModifyOwnMembership right'); my $members2 = $group2->MembersObj; is($members2->Count, 1, 'One member added'); my $member = $members2->Next; is($member->MemberObj->PrincipalType, 'User', 'User added as member'); is($member->MemberObj->id, $user->id, 'Accurate user added as member'); } # Memberships list { # Rights Test - No SeeGroup my $res = $mech->get("$rest_base_path/user/" . $user->id . '/groups', 'Authorization' => $auth, ); is($res->code, 200, 'List direct members'); my $content = $mech->json_response; is($content->{total}, 2, 'Two recursive memberships'); is(scalar(@{$content->{items}}), 0, 'Cannot see memberships content withtout SeeGroup right'); # Recursive memberships $user->PrincipalObj->GrantRight(Right => 'SeeGroup'); $res = $mech->get("$rest_base_path/user/" . $user->id . '/groups', 'Authorization' => $auth, ); is($res->code, 200, 'List direct members'); $content = $mech->json_response; is($content->{total}, 2, 'Two recursive memberships'); is($content->{items}->[0]->{type}, 'group', 'First group membership'); is($content->{items}->[0]->{id}, $group1->id, 'Accurate first group membership'); is($content->{items}->[1]->{type}, 'group', 'Second group membership'); is($content->{items}->[1]->{id}, $group2->id, 'Accurate second group membership'); } ($ok, $msg) = $group1->AddMember($user->id); ok($ok, $msg); # Membership removal { my $res = $mech->delete("$rest_base_path/user/" . $user->id . '/group/' . $group2->id, 'Authorization' => $auth, ); is($res->code, 204, 'Remove membership'); my $memberships = $user->OwnGroups; is($memberships->Count, 1, 'One membership removed'); my $membership = $memberships->Next; is($membership->id, $group1->id, 'Accurate membership removed'); } ($ok, $msg) = $group2->AddMember($user->id); ok($ok, $msg); # All members removal { my $res = $mech->delete("$rest_base_path/user/" . $user->id . '/groups', 'Authorization' => $auth, ); is($res->code, 204, 'Remove all memberships'); my $memberships = $user->OwnGroups; is($memberships->Count, 0, 'All membership removed'); } # User hypermedia links { my $res = $mech->get("$rest_base_path/user/" . $user->id, 'Authorization' => $auth, ); is($res->code, 200); my $content = $mech->json_response; my $links = $content->{_hyperlinks}; my @memberships_links = grep { $_->{ref} eq 'memberships' } @$links; is(scalar(@memberships_links), 1); my $user_id = $user->id; like($memberships_links[0]->{_url}, qr{$rest_base_path/user/$user_id/groups$}); } done_testing; rt-5.0.1/t/rest2/data/plain.txt000644 000765 000024 00000000016 14005011336 017055 0ustar00sunnavystaff000000 000000 Hello, World! rt-5.0.1/t/rest2/data/html.htm000644 000765 000024 00000000031 14005011336 016664 0ustar00sunnavystaff000000 000000

      Easy as π

      rt-5.0.1/t/rest2/data/image.png000644 000765 000024 00000007531 14005011336 017012 0ustar00sunnavystaff000000 000000 ‰PNG  IHDRµ&‚¡ÚDsRGB®ÎébKGDïïïÁ̃ pHYs × ×B(›xtIMEÚ$GÙIDATxÚíœ{pUEžÇ?Ýçœû ‘—„‡2H‚ˆ( Z2"!(2¨£[–a’Ak°fw­ÝM­«®jxD¤Vبuvåa@]¢Ž" â„ð L^÷žÓÝûÇ9ɽ¹¹7$XVy¾U·RésNwŸîoÿžÝB„"Ä·VWUÅž/á. Ó_ÃÎp¨C|Sá„I"DHê!¾YØß…— ÎA8?Ûµz0upHæx¼WDÛªÿ(D€@mæµ?½-è^ëßsÐYª¹  {yf[ðuHÝï8©w€°‘eÎËvÝúK «€?e\¾¶yC[õ;@7ô/Nµ$u?ªFŽ„ÜgОÄ>ªðv*؈)®Á»æLïâ ÿàÂòºßqRR¸8¬$úDÓ K*¼ /±ÐíÂâôcP«Ubo¶Ê *ï$ÒŠòlä\ 7XkÜ8©±zièú‰¼1Ž>X:Iò«(Öi–á*”ȯÀü±©TÀi7ämHê–N„ÜžÀý,U¢À—äåy1è‚ÀDÈ(ëP#î+íiÃQ¡÷¸¸Ï¬¨ -ŠÐ=ª`J-¤/’í‰àº¿ØÔYèá}®à•ª!©Û \Âé[ͦ 0H@ 9jvt¤:p[Knâ˜~ø?4„†CаNkBúu5©Ë^+ÀäSQüß­î*Ût3RíaÑ »¼G?_݃ú¾7±¤ä¥V×J·^‡NV±ô†Ï¶úòþݬl‚‹^¾# I}«®ˆ /N/X'¸ 3ïÂG ˜ º¯ƒ¼·©¼Úð“}À&þa ï…ì mœ2—!XÆÜ­pûêT¦ñÞm#0öóèèîß6®K{3çõ‹©ë¿äbʶOiIèÊ9± +²‚ò½‘³oDba½ßôëÓì¬Ù·ñ¥+¢ùçâf˜ÔAµ‡~ÂE¯±Ñï(ô—œ0Ð  'Èщ{âpuHÁ.5?ä¿‚Y÷ѯï0ØüNªá¹ñ1¯r š±ôo)«ü9K&¬èôžÌÝt=Â~ÁyVѧçn\¥hÌ„¹Ó™ÙS>"yö«8º±‘Äg­¥«sŸF]æÀ¥.ü!óºÀú0»²Uqnœ*‚SѺˆS%r²‡3 Üÿ iØU’zÉ„]D­0æ=„œ„g­çÞ7.aXTüÆÌò£fe›ÿòòγÇK+ç í—€<<ñ0Åe”H2ïÝ~þ¤¹Ã~“?dÉø ]1îi@¬xÌäW¦ùvz«HŒ€FßÜq“!»ÚQ|züÊ÷ÞÆ‘cOb‰iÄ"ë)Û>›%ã7P1ñîÝö9QõŸkÇJ†1ëõY¼0õÄY·ÞR ŸB0‹g'lõM‘ͣщ•HÑ£7c›ŸP1¹æÜWqâÊ<¢…)2{–F yy0$Ÿ‘…h6Zpä’}Axvµ8MyÕ!ú# ÚÁ9báöT8ƒAÄO´¼R°ó‘[}Îyë^,ó‰0²xâ"f½Ý›¸»!®sãÜCÅ5Ÿ´Vçö|D®dÁ¼wûá%_Dr%ð)^r:ÏNÞïKî·þ ÌB„ˆ¡Í2ŽT—³æu¶/9œ=È1н •õ•@¿Û•éåÜ$‘SÏÔF„ø uÔíȃQIg`¨@Ʋµå _ËéeÔÇí9¬\ÜCªv©|k"žyDO„y™ªêXsGåå6Ç®{Á1Ôá1›eÅëÛMê9›GcÉ• úcÌtõl–ÞQK¹‘Ûöó†$’Ÿ±¸xU'¼g¬ä4+·A˜-ô€X[•7‚ðÃv­´Vôøšh_M¢»„Ú B|m"F ’ÕCªv©æn¿áýB Ø·©˜ø©P_å Œy…?æ™ kÏHê²Ê+0z-BÄf½Š£\ø^TÙ– g`t5š™,- Õsˆs éåÀ3×Ę7¿hH‹knòM¤¨}ËÈ€1 'Püo3¡´ ê—¿ç‹Ý»Ãé Ñù¤ž¿+9•¿DÊ9N`™ûR&Ä–«°3Xªúe»Zì½õ ¤yAo¯RºõÖáÕ߃ùÁÍ\<úæ¼×?œ¢g~ÌÛ8]‰à2Œù˜¨=ã}ï¾tëÝñ4à Í"? ÂtÈQüiåt´yÑOQ1ñ)†6÷DÉ@ƒ9ŒÒÓY:)<â%õýÛÆ¡£\†ëèS} ÆàöÕ¥[CˆE£@ÎfÑÄË$t»°xÂJ„¼ 8ŽQZ¹œù»òX8©†ÃÕw`ôs ú#åïühHˆg+©Ë¶ÎÀˆ'| ªPQòD+ j¨Bˆé,™°+WÅg”Ô-5 #0ìÅJÌhÞc2¯òn´ñ5‚ ésíã-lðöÃr™2~š<ÛÂìMö´y=þÁ‚öâÂ௡õ~휸º ÿÂálm„øQ˜ðûDîù [¾"Hü}Im}9JîC=£Ð/á÷©jœnG÷#@A“÷Œq.Á: Ÿ|·–Ú#Gêòr›c“Aˆ¿ÃЦ”Љþ–ÇÒÍ—"ä …ñ>–3“EãªÛÄv’ºÉvo<õ ‚›ãxòoyöÚw›mw)…  Eø¯ƒ)âÇ©{e»hàÏ6Ö'6ÑõéƒêÀ8wgF× 8¤á È–± Ú¶‘ çÞé$úŸÈ‰q¸Z!§fö×@ƒFiá¼çâîTô¯Aþc{ÁE—„cP¢iÐÇë}mø mõCjd‰ï{¨m¾S©w 䘯AuùÑ´ïÜq®,sШðþä`–ÀÛ’+zbÐçù“--…îÞä„ýc–gú‚i!O7¢#Àð$ú¸O5¸ =bãF¼oÁgX¢XQÁ"r†€Hv80Æ@/ öïó£>8$`ŒÂŒÖÓòrHês…/þ¸¥ÙÂ^Ìb¿É¯xO÷ 9(ÒpØÍNh,¬0B¡»;ÈûSÖCóä_ßä­û6/_Ÿj4œ\ÇÊ‚X†;@Êø êQèÁAt,«DñC~_ê2Æê:íjQê½›µ[ÜÆïámêR1Jêø¾SèJdI>ùó™ésµAרèÕé? ý²O9––!»# }H c»q¦å˜ ¸+Š=««æ®–ÚÏ ºÆ D±n!K.ÃËYÜÙRÊG‡jøžA™ùÞñF E¯£ ?Lšg#½V oKR? ?Λ.±Æû˜ÊØÖZZCA²`’‚—ÓtÆs6üÌ ¾Ã܈÷ŽãŸmì)`°À.4è|ƒ®ÂD†]ÞIHÚÄžW$çiÌäò2Ü¡'5n„Â#؃ :f'»PO}•ßÿÄ$Ä`}ë½âJØ0ZA®t!Û1¶Ë-?á•ÕÑRðZHê–)ž v´Û>ìá :ž©Š=Øbûg/ppfù€ÞÑ@{A,¹µÝê¾@Ž’Èkº’TÜüdýT¦xè8È›i!*uÂÆ~§äZ‚Øn4„Øá‘*ëÉ$E]/(¼nd|ꡑƃÀ“ÀäXƒ¾%0-šºƒ½©žä:úD馑#Ô×f„LÓËw€[ r èm-œç‹n9JúŸ”Èævïiï)ÑU,êPF±ë‘ž&ïHŠ;=M^•îË'¿o-µM©æš@Zç“:›x 'Laptops'); my $servers = create_catalog(Name => 'Servers'); my $keyboards = create_catalog(Name => 'Keyboards'); my $manufacturer = create_cf(Name => 'Manufacturer'); apply_cfs($manufacturer); my $blank = create_cf(Name => 'Blank'); apply_cfs($blank); my $shawn = RT::User->new(RT->SystemUser); my ($ok, $msg) = $shawn->Create(Name => 'shawn', EmailAddress => 'shawn@bestpractical.com'); ok($ok, $msg); my $sysadmins = RT::Group->new( RT->SystemUser ); ($ok, $msg) = $sysadmins->CreateUserDefinedGroup( Name => 'Sysadmins' ); ok($ok, $msg); ($ok, $msg) = $sysadmins->AddMember($shawn->PrincipalId); ok($ok, $msg); my $memberless = RT::Group->new( RT->SystemUser ); ($ok, $msg) = $memberless->CreateUserDefinedGroup( Name => 'Memberless' ); ok($ok, $msg); my $bloc = create_asset( Name => 'bloc', Description => "Shawn's BPS office media server", Catalog => 'Servers', Owner => $shawn->PrincipalId, Contact => $shawn->PrincipalId, 'CustomField-Manufacturer' => 'Raspberry Pi', ); my $deleted = create_asset( Name => 'deleted', Description => "for making sure we don't search deleted", Catalog => 'Servers', Owner => $shawn->PrincipalId, Contact => $shawn->PrincipalId, 'CustomField-Manufacturer' => 'Dell', ); my $ecaz = create_asset( Name => 'ecaz', Description => "Shawn's BPS laptop", Catalog => 'Laptops', Owner => $shawn->PrincipalId, Contact => $shawn->PrincipalId, 'CustomField-Manufacturer' => 'Apple', ); my $kaitain = create_asset( Name => 'kaitain', Description => "unused BPS laptop", Catalog => 'Laptops', Owner => $shawn->PrincipalId, 'CustomField-Manufacturer' => 'Apple', ); my $morelax = create_asset( Name => 'morelax', Description => "BPS in the data center", Catalog => 'Servers', 'CustomField-Manufacturer' => 'Dell', ); my $stilgar = create_asset( Name => 'stilgar', Description => "English layout", Catalog => 'Keyboards', Owner => $shawn->PrincipalId, Contact => $shawn->PrincipalId, 'CustomField-Manufacturer' => 'Apple', ); ($ok, $msg) = $bloc->SetStatus('stolen'); ok($ok, $msg); ($ok, $msg) = $deleted->SetStatus('deleted'); ok($ok, $msg); ($ok, $msg) = $ecaz->SetStatus('in-use'); ok($ok, $msg); ($ok, $msg) = $kaitain->SetStatus('in-use'); ok($ok, $msg); ($ok, $msg) = $kaitain->SetStatus('recycled'); ok($ok, $msg); ($ok, $msg) = $morelax->SetStatus('in-use'); ok($ok, $msg); ($ok, $msg) = $ecaz->AddLink(Type => 'RefersTo', Target => $kaitain->URI); ok($ok, $msg); ($ok, $msg) = $stilgar->AddLink(Type => 'MemberOf', Target => $ecaz->URI); ok($ok, $msg); my $ticket = RT::Ticket->new(RT->SystemUser); ($ok, $msg) = $ticket->Create(Queue => 'General', Subject => "reboot the server please"); ($ok, $msg) = $morelax->AddLink(Type => 'RefersTo', Target => $ticket->URI); ok($ok, $msg); my $bloc_id = $bloc->id; my $ecaz_id = $ecaz->id; my $kaitain_id = $kaitain->id; my $morelax_id = $morelax->id; my $stilgar_id = $stilgar->id; my $ticket_id = $ticket->id; assetsql "id = 1" => $bloc; assetsql "id != 1" => $ecaz, $kaitain, $morelax, $stilgar; assetsql "id = 2" => (); # deleted assetsql "id < 3" => $bloc; assetsql "id >= 3" => $ecaz, $kaitain, $morelax, $stilgar; assetsql "Name = 'ecaz'" => $ecaz; assetsql "Name != 'ecaz'" => $bloc, $kaitain, $morelax, $stilgar; assetsql "Name = 'no match'" => (); assetsql "Name != 'no match'" => $bloc, $ecaz, $kaitain, $morelax, $stilgar; assetsql "Status = 'new'" => $stilgar; assetsql "Status = 'allocated'" => (); assetsql "Status = 'in-use'" => $ecaz, $morelax; assetsql "Status = 'recycled'" => $kaitain; assetsql "Status = 'stolen'" => $bloc; assetsql "Status = 'deleted'" => (); assetsql "Status = '__Active__'" => $ecaz, $morelax, $stilgar; assetsql "Status != '__Inactive__'" => $ecaz, $morelax, $stilgar; assetsql "Status = '__Inactive__'" => $bloc, $kaitain; assetsql "Status != '__Active__'" => $bloc, $kaitain; assetsql "Catalog = 'Laptops'" => $ecaz, $kaitain; assetsql "Catalog = 'Servers'" => $bloc, $morelax; assetsql "Catalog = 'Keyboards'" => $stilgar; assetsql "Catalog != 'Servers'" => $ecaz, $kaitain, $stilgar; assetsql "Catalog != 'Laptops'" => $bloc, $morelax, $stilgar; assetsql "Catalog != 'Keyboards'" => $bloc, $ecaz, $kaitain, $morelax; assetsql "Description LIKE 'data center'" => $morelax; assetsql "Description LIKE 'Shawn'" => $bloc, $ecaz; assetsql "Description LIKE 'media'" => $bloc; assetsql "Description NOT LIKE 'laptop'" => $bloc, $morelax, $stilgar; assetsql "Description LIKE 'deleted'" => (); assetsql "Description LIKE 'BPS'" => $bloc, $ecaz, $kaitain, $morelax; assetsql "Lifecycle = 'assets'" => $bloc, $ecaz, $kaitain, $morelax, $stilgar; assetsql "Lifecycle != 'assets'" => (); assetsql "Lifecycle = 'default'" => (); assetsql "Lifecycle != 'default'" => $bloc, $ecaz, $kaitain, $morelax, $stilgar; assetsql "Linked IS NOT NULL" => $ecaz, $kaitain, $morelax, $stilgar; assetsql "Linked IS NULL" => $bloc; assetsql "RefersTo = 'asset:$kaitain_id'" => $ecaz; assetsql "RefersTo = $ticket_id" => $morelax; assetsql "HasMember = 'asset:$stilgar_id'" => $ecaz; assetsql "MemberOf = 'asset:$stilgar_id'" => (); assetsql "Owner.Name = 'shawn'" => $bloc, $ecaz, $kaitain, $stilgar; assetsql "Owner.EmailAddress LIKE 'bestpractical'" => $bloc, $ecaz, $kaitain, $stilgar; assetsql "Owner.Name = 'Nobody'" => $morelax; assetsql "Owner = '__CurrentUser__'" => (); assetsql "Owner != '__CurrentUser__'" => $bloc, $ecaz, $kaitain, $morelax, $stilgar; assetsql "OwnerGroup = 'Sysadmins'" => $bloc, $ecaz, $kaitain, $stilgar; assetsql "OwnerGroup = 'Memberless'" => (); assetsql "Contact.Name = 'shawn'" => $bloc, $ecaz, $stilgar; assetsql "Contact = '__CurrentUser__'" => (); assetsql "Contact != '__CurrentUser__'" => $bloc, $ecaz, $kaitain, $morelax, $stilgar; assetsql "ContactGroup = 'Sysadmins'" => $bloc, $ecaz, $stilgar; assetsql "ContactGroup = 'Memberless'" => (); assetsql "CustomField.{Manufacturer} = 'Apple'" => $ecaz, $kaitain, $stilgar; assetsql "CF.{Manufacturer} != 'Apple'" => $bloc, $morelax; assetsql "CustomFieldValue.{Manufacturer} = 'Raspberry Pi'" => $bloc; assetsql "CF.{Manufacturer} IS NULL" => (); assetsql "CF.{Blank} IS NULL" => $bloc, $ecaz, $kaitain, $morelax, $stilgar; assetsql "CF.{Blank} IS NOT NULL" => (); assetsql "Status = '__Active__' AND Catalog = 'Servers'" => $morelax; assetsql "Status = 'in-use' AND Catalog = 'Laptops'" => $ecaz; assetsql "Catalog != 'Servers' AND Catalog != 'Laptops'" => $stilgar; assetsql "Description LIKE 'BPS' AND Contact.Name IS NULL" => $kaitain, $morelax; assetsql "CF.{Manufacturer} = 'Apple' AND Catalog = 'Laptops'" => $ecaz, $kaitain; assetsql "Catalog = 'Servers' AND Linked IS NULL" => $bloc; assetsql "Catalog = 'Servers' OR Linked IS NULL" => $bloc, $morelax; assetsql "(Catalog = 'Keyboards' AND CF.{Manufacturer} = 'Apple') OR (Catalog = 'Servers' AND CF.{Manufacturer} = 'Raspberry Pi')" => $bloc, $stilgar; rt-5.0.1/t/assets/rights.t000644 000765 000024 00000007403 14005011336 016237 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::Assets tests => undef; my $user = RT::Test->load_or_create_user( Name => 'testuser' ); ok $user->id, "Created user"; my $ticket = RT::Test->create_ticket( Queue => 1, Subject => 'a test ticket', ); ok $ticket->id, "Created ticket"; my $catalog_one = create_catalog( Name => "One" ); ok $catalog_one && $catalog_one->id, "Created catalog one"; my $catalog_two = create_catalog( Name => "Two" ); ok $catalog_two && $catalog_two->id, "Created catalog two"; ok(RT::Test->add_rights({ Principal => 'Privileged', Right => 'ShowCatalog', Object => $catalog_one, }), "Granted ShowCatalog"); my $asset = RT::Asset->new( RT::CurrentUser->new($user) ); diag "CreateAsset"; { my %create = ( Name => 'Thinkpad T420s', Contact => 'trs@example.com', Catalog => $catalog_one->id, ); my ($id, $msg) = $asset->Create(%create); ok !$id, "Create denied: $msg"; ok(RT::Test->add_rights({ Principal => 'Privileged', Right => 'CreateAsset', Object => $catalog_one, }), "Granted CreateAsset"); ($id, $msg) = $asset->Create(%create); ok $id, "Created: $msg"; is $asset->id, $id, "id matches"; is $asset->CatalogObj->Name, $catalog_one->Name, "Catalog matches"; }; diag "ShowAsset"; { is $asset->Name, undef, "Can't see Name without ShowAsset"; ok !$asset->Contacts->id, "Can't see Contacts role group"; ok(RT::Test->add_rights({ Principal => 'Privileged', Right => 'ShowAsset', Object => $catalog_one, }), "Granted ShowAsset"); is $asset->Name, "Thinkpad T420s", "Got Name"; is $asset->Contacts->UserMembersObj->First->EmailAddress, 'trs@example.com', "Got Contact"; } diag "ModifyAsset"; { my ($txnid, $txnmsg) = $asset->SetName("Lenovo Thinkpad T420s"); ok !$txnid, "Update failed: $txnmsg"; is $asset->Name, "Thinkpad T420s", "Name didn't change"; my ($ok, $msg) = $asset->AddLink( Type => 'RefersTo', Target => 't:1' ); ok !$ok, "No rights to AddLink: $msg"; ($ok, $msg) = $asset->DeleteLink( Type => 'RefersTo', Target => 't:1' ); ok !$ok, "No rights to DeleteLink: $msg"; ok(RT::Test->add_rights({ Principal => 'Privileged', Right => 'ModifyAsset', Object => $catalog_one, }), "Granted ModifyAsset"); ($txnid, $txnmsg) = $asset->SetName("Lenovo Thinkpad T420s"); ok $txnid, "Updated Name: $txnmsg"; is $asset->Name, "Lenovo Thinkpad T420s", "Name changed"; } diag "Catalogs"; { my ($txnid, $txnmsg) = $asset->SetCatalog($catalog_two->id); ok !$txnid, "Failed to update Catalog: $txnmsg"; is $asset->CatalogObj->Name, $catalog_one->Name, "Catalog unchanged"; ok(RT::Test->add_rights({ Principal => 'Privileged', Right => 'CreateAsset', Object => $catalog_two, }), "Granted CreateAsset in second catalog"); ($txnid, $txnmsg) = $asset->SetCatalog($catalog_two->id); ok $txnid, "Updated Catalog: $txnmsg"; unlike $txnmsg, qr/Permission Denied/i, "Transaction message isn't Permission Denied"; ok !$asset->CurrentUserCanSee, "Can no longer see the asset"; ok(RT::Test->add_rights({ Principal => 'Privileged', Right => 'ShowAsset', Object => $catalog_two, }), "Granted ShowAsset"); ok $asset->CurrentUserCanSee, "Can see the asset now"; is $asset->CatalogObj->Name, undef, "Can't see the catalog name still"; ok(RT::Test->add_rights({ Principal => 'Privileged', Right => 'ShowCatalog', Object => $catalog_two, }), "Granted ShowCatalog"); is $asset->CatalogObj->Name, $catalog_two->Name, "Now we can see the catalog name"; } done_testing; rt-5.0.1/t/assets/roles.t000644 000765 000024 00000001556 14005011336 016066 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::Assets tests => undef; my $catalog = create_catalog( Name => "A catalog" ); my $asset = create_asset( Name => "Test asset", Catalog => $catalog->id ); ok $asset && $asset->id, "Created asset"; for my $object ($asset, $catalog, RT->System) { for my $role (RT::Asset->Roles) { my $group = $object->RoleGroup($role); ok $group->id, "Loaded role group $role for " . ref($object); my $principal = $group->PrincipalObj; ok $principal && $principal->id, "Found PrincipalObj for role group" or next; if ($object->DOES("RT::Record::Role::Rights")) { my ($ok, $msg) = $principal->GrantRight( Object => $object, Right => "ShowAsset", ); ok $ok, "Granted right" or diag "Error: $msg"; } } } done_testing; rt-5.0.1/t/assets/links.t000644 000765 000024 00000011261 14005011336 016054 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::Assets tests => undef; use Test::Warn; my $catalog = create_catalog( Name => "BPS" ); ok $catalog && $catalog->id, "Created Catalog"; ok( create_assets( { Name => "Thinkpad T420s", Catalog => $catalog->id }, { Name => "Standing desk", Catalog => $catalog->id }, { Name => "Chair", Catalog => $catalog->id }, ), "Created assets" ); my $ticket = RT::Test->create_ticket( Queue => 1, Subject => 'a test ticket', ); ok $ticket->id, "Created ticket"; diag "RT::URI::asset"; { my %uris = ( # URI => Asset Name "asset:1" => { id => 1, Name => "Thinkpad T420s" }, "asset:01" => { id => 1, Name => "Thinkpad T420s" }, "asset://example.com/2" => { id => 2, Name => "Standing desk" }, "asset:13" => undef, ); while (my ($url, $expected) = each %uris) { my $uri = RT::URI->new( RT->SystemUser ); if ($expected) { my $parsed = $uri->FromURI($url); ok $parsed, "Parsed $url"; my $asset = $uri->Object; ok $asset, "Got object"; is ref($asset), "RT::Asset", "... it's a RT::Asset"; while (my ($field, $value) = each %$expected) { is $asset->$field, $value, "... $field is $value"; } } else { my $parsed; warnings_like { $parsed = $uri->FromURI($url); } [qr/Unable to load asset/, qr/\Q$url\E/], "Caught warnings about unknown URI"; ok !$parsed, "Failed to parse $url, as expected"; } } } diag "RT::Asset link support"; { my $chair = RT::Asset->new( RT->SystemUser ); $chair->LoadByCols( Name => "Chair" ); ok $chair->id, "Loaded asset"; is $chair->URI, "asset://example.com/".$chair->id, "->URI works"; my ($link_id, $msg) = $chair->AddLink( Type => 'MemberOf', Target => 'asset:2' ); ok $link_id, "Added link: $msg"; my $parents = $chair->MemberOf; my $desk = $parents->First->TargetObj; is $parents->Count, 1, "1 parent"; is $desk->Name, "Standing desk", "Correct parent asset"; for my $asset ($chair, $desk) { my $txns = $asset->Transactions; $txns->Limit( FIELD => 'Type', VALUE => 'AddLink' ); is $txns->Count, 1, "1 AddLink txn on asset ".$asset->Name; } my ($ok, $err) = $chair->DeleteLink( Type => 'MemberOf', Target => 'asset:1' ); ok !$ok, "Delete link failed on non-existent: $err"; my ($deleted, $delete_msg) = $chair->DeleteLink( Type => 'MemberOf', Target => $parents->First->Target ); ok $deleted, "Deleted link: $delete_msg"; for my $asset ($chair, $desk) { my $txns = $asset->Transactions; $txns->Limit( FIELD => 'Type', VALUE => 'DeleteLink' ); is $txns->Count, 1, "1 DeleteLink txn on asset ".$asset->Name; } }; diag "Linking to tickets"; { my $laptop = RT::Asset->new( RT->SystemUser ); $laptop->LoadByCols( Name => "Thinkpad T420s" ); my ($ok, $msg) = $ticket->AddLink( Type => 'RefersTo', Target => $laptop->URI ); ok $ok, "Ticket refers to asset: $msg"; my $links = $laptop->ReferredToBy; is $links->Count, 1, "Found a ReferredToBy link via asset"; ($ok, $msg) = $laptop->DeleteLink( Type => 'RefersTo', Base => $ticket->URI ); ok $ok, "Deleted link from opposite side: $msg"; } diag "Linking to tickets, asset leading zeros"; { my $laptop = RT::Asset->new( RT->SystemUser ); $laptop->LoadByCols( Name => "Thinkpad T420s" ); my ($ok, $msg) = $ticket->AddLink( Type => 'RefersTo', Target => 'asset:' . '0' . $laptop->Id ); ok $ok, "Ticket refers to asset: $msg"; my $links = $laptop->ReferredToBy; is $links->Count, 1, "Found a ReferredToBy link via asset"; ($ok, $msg) = $laptop->DeleteLink( Type => 'RefersTo', Base => $ticket->URI ); ok $ok, "Deleted link from opposite side: $msg"; } diag "Links on ->Create"; { my $desk = RT::Asset->new( RT->SystemUser ); $desk->LoadByCols( Name => "Standing desk" ); ok $desk->id, "Loaded standing desk asset"; my $asset = create_asset( Name => "Anti-fatigue mat", Catalog => $catalog->id, Parent => $desk->URI, ReferredToBy => [$ticket->id], ); ok $asset->id, "Created asset with Parent link"; my $parents = $asset->MemberOf; is $parents->Count, 1, "Found one Parent"; is $parents->First->Target, $desk->URI, "... it's a desk!"; my $referrals = $asset->ReferredToBy; is $referrals->Count, 1, "Found one ReferredToBy"; is $referrals->First->Base, $ticket->URI, "... it's the ticket!"; } done_testing; rt-5.0.1/t/assets/api.t000644 000765 000024 00000014251 14005011336 015507 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::Assets tests => undef; use Test::Warn; my $catalog; diag "Create a catalog"; { $catalog = create_catalog( Name => 'Test Catalog', Disabled => 1 ); ok $catalog && $catalog->id, "Created catalog"; is $catalog->Name, "Test Catalog", "Name is correct"; ok $catalog->Disabled, "Disabled"; my $asset; warning_like { $asset = create_asset( Name => "Test", Catalog => $catalog->id ); } qr/^Failed to create asset .* Invalid catalog/i; ok !$asset, "Couldn't create asset in disabled catalog"; my ($ok, $msg) = $catalog->SetDisabled(0); ok $ok, "Enabled catalog: $msg"; ok !$catalog->Disabled, "Enabled"; } diag "Create basic asset (no CFs)"; { my $asset = RT::Asset->new( RT->SystemUser ); my ($id, $msg) = $asset->Create( Name => 'Thinkpad T420s', Description => 'Laptop', Catalog => $catalog->Name, ); ok $id, "Created: $msg"; is $asset->id, $id, "id matches"; is $asset->Name, "Thinkpad T420s", "Name matches"; is $asset->Description, "Laptop", "Description matches"; # Create txn my @txns = @{$asset->Transactions->ItemsArrayRef}; is scalar @txns, 1, "One transaction"; is $txns[0]->Type, "Create", "... of type Create"; # Update my ($txnid, $txnmsg) = $asset->SetName("Lenovo Thinkpad T420s"); ok $txnid, "Updated Name: $txnmsg"; is $asset->Name, "Lenovo Thinkpad T420s", "New Name matches"; # Set txn @txns = @{$asset->Transactions->ItemsArrayRef}; is scalar @txns, 2, "Two transactions"; is $txns[1]->Type, "Set", "... the second of which is Set"; is $txns[1]->Field, "Name", "... Field is Name"; is $txns[1]->OldValue, "Thinkpad T420s", "... OldValue is correct"; # Delete my ($ok, $err) = $asset->Delete; ok !$ok, "Deletes are prevented: $err"; $asset->Load($id); ok $asset->id, "Asset not deleted"; } diag "Create with CFs"; { my $height = create_cf( Name => 'Height' ); ok $height->id, "Created CF"; my $material = create_cf( Name => 'Material' ); ok $material->id, "Created CF"; ok apply_cfs($height, $material), "Applied CFs"; my $asset = RT::Asset->new( RT->SystemUser ); my ($id, $msg) = $asset->Create( Name => 'Standing desk', "CustomField-".$height->id => '46"', "CustomField-Material" => 'pine', Catalog => $catalog->Name, ); ok $id, "Created: $msg"; is $asset->FirstCustomFieldValue('Height'), '46"', "Found height"; is $asset->FirstCustomFieldValue('Material'), 'pine', "Found material"; is $asset->Transactions->Count, 1, "Only a single txn"; } note "Create/update with Roles"; { my $root = RT::User->new( RT->SystemUser ); $root->Load("root"); ok $root->id, "Found root"; my $bps = RT::Test->load_or_create_user( Name => "BPS" ); ok $bps->id, "Created BPS user"; my $asset = RT::Asset->new( RT->SystemUser ); my ($id, $msg) = $asset->Create( Name => 'RT server', HeldBy => $root->PrincipalId, Owner => $bps->PrincipalId, Contact => $bps->PrincipalId, Catalog => $catalog->id, ); ok $id, "Created: $msg"; is $asset->HeldBy->UserMembersObj->First->Name, "root", "root is Holder"; is $asset->Owner->Name, "BPS", "BPS is Owner"; is $asset->Contacts->UserMembersObj->First->Name, "BPS", "BPS is Contact"; my $sysadmins = RT::Group->new( RT->SystemUser ); $sysadmins->CreateUserDefinedGroup( Name => 'Sysadmins' ); ok $sysadmins->id, "Created group"; is $sysadmins->Name, "Sysadmins", "Got group name"; (my $ok, $msg) = $asset->AddRoleMember( Type => 'Contact', Group => 'Sysadmins', ); ok $ok, "Added Sysadmins as Contact: $msg"; is $asset->Contacts->MembersObj->Count, 2, "Found two members"; my @txn = grep { $_->Type eq 'AddWatcher' } @{$asset->Transactions->ItemsArrayRef}; ok @txn == 1, "Found one AddWatcher txn"; is $txn[0]->Field, "Contact", "... of a Contact"; is $txn[0]->NewValue, $sysadmins->PrincipalId, "... for the right principal"; ($ok, $msg) = $asset->DeleteRoleMember( Type => 'Contact', PrincipalId => $bps->PrincipalId, ); ok $ok, "Removed BPS user as Contact: $msg"; is $asset->Contacts->MembersObj->Count, 1, "Now just one member"; is $asset->Contacts->GroupMembersObj(Recursively => 0)->First->Name, "Sysadmins", "... it's Sysadmins"; @txn = grep { $_->Type eq 'DelWatcher' } @{$asset->Transactions->ItemsArrayRef}; ok @txn == 1, "Found one DelWatcher txn"; is $txn[0]->Field, "Contact", "... of a Contact"; is $txn[0]->OldValue, $bps->PrincipalId, "... for the right principal"; } diag "Custom Field handling"; { diag "Make sure we don't load queue CFs"; my $queue_cf = RT::CustomField->new( RT->SystemUser ); my ($ok, $msg) = $queue_cf->Create( Name => "Queue CF", Type => "Text", LookupType => RT::Queue->CustomFieldLookupType, ); ok( $queue_cf->Id, "Created test CF: " . $queue_cf->Id); my $cf1 = RT::CustomField->new( RT->SystemUser ); $cf1->LoadByNameAndCatalog ( Name => "Queue CF" ); ok( (not $cf1->Id), "Queue CF not loaded with LoadByNameAndCatalog"); my $cf2 = RT::CustomField->new( RT->SystemUser ); $cf2->LoadByNameAndCatalog ( Name => "Height" ); ok( $cf2->Id, "Loaded CF id: " . $cf2->Id . " with name"); ok( $cf2->Name, "Loaded CF name: " . $cf2->Name . " with name"); my $cf3 = RT::CustomField->new( RT->SystemUser ); ($ok, $msg) = $cf3->LoadByNameAndCatalog ( Name => "Height", Catalog => $catalog->Name ); ok( (not $cf3->Id), "CF 'Height'" . " not added to catalog: " . $catalog->Name); my $color = create_cf( Name => 'Color' ); ok $color->Id, "Created CF " . $color->Name; ($ok, $msg) = $color->AddToObject( $catalog ); ($ok, $msg) = $color->LoadByNameAndCatalog ( Name => "Color", Catalog => $catalog->Name ); ok( $color->Id, "Loaded CF id: " . $color->Id . " for catalog: " . $catalog->Name); ok( $color->Name, "Loaded CF name: " . $color->Name . " for catalog: " . $catalog->Name); } done_testing; rt-5.0.1/t/assets/compile.t000644 000765 000024 00000000242 14005011336 016361 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::More; use_ok('RT::Test::Assets'); use_ok('RT::Asset'); use_ok('RT::Assets'); use_ok('RT::Catalog'); use_ok('RT::Catalogs'); rt-5.0.1/t/assets/collection.t000644 000765 000024 00000004210 14005011336 017063 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::Assets tests => undef; my $user = RT::Test->load_or_create_user( Name => 'testuser' ); ok $user->id, "Created user"; my $catalog = create_catalog( Name => "BPS" ); ok $catalog && $catalog->id, "Created catalog"; my $location = create_cf( Name => 'Location' ); ok $location->id, "Created CF"; ok apply_cfs($location), "Applied CF"; ok( create_assets( { Name => "Thinkpad T420s", Catalog => $catalog->id, "CustomField-Location" => "Home" }, { Name => "Standing desk", Catalog => $catalog->id, "CustomField-Location" => "Office" }, { Name => "Chair", Catalog => $catalog->id, "CustomField-Location" => "Office" }, ), "Created assets" ); diag "Mark chair as deleted"; { my $asset = RT::Asset->new( RT->SystemUser ); $asset->LoadByCols( Name => "Chair" ); my ($ok, $msg) = $asset->SetStatus( "deleted" ); ok($ok, "Deleted the chair: $msg"); } diag "Basic types of limits"; { my $assets = RT::Assets->new( RT->SystemUser ); $assets->Limit( FIELD => 'Name', OPERATOR => 'LIKE', VALUE => 'thinkpad' ); is $assets->Count, 1, "Found 1 like thinkpad"; is $assets->First->Name, "Thinkpad T420s"; $assets = RT::Assets->new( RT->SystemUser ); $assets->UnLimit; is $assets->Count, 2, "Found 2 total"; ok((!grep { $_->Name eq "Chair" } @{$assets->ItemsArrayRef}), "No chair (disabled)"); $assets = RT::Assets->new( RT->SystemUser ); $assets->Limit( FIELD => 'Status', VALUE => 'deleted' ); $assets->{allow_deleted_search} = 1; is $assets->Count, 1, "Found 1 deleted"; is $assets->First->Name, "Chair", "Found chair"; $assets = RT::Assets->new( RT->SystemUser ); $assets->UnLimit; $assets->LimitCustomField( CUSTOMFIELD => $location->id, VALUE => "Office", ); is $assets->Count, 1, "Found 1 in Office"; ok $assets->First, "Got record"; is $assets->First->Name, "Standing desk", "Found standing desk"; } diag "Test ACLs"; { my $assets = RT::Assets->new( RT::CurrentUser->new($user) ); $assets->UnLimit; is scalar @{$assets->ItemsArrayRef}, 0, "Found none"; } done_testing; rt-5.0.1/t/assets/sql-rights.t000644 000765 000024 00000030433 14005011336 017033 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::Assets; my $laptops = create_catalog(Name => 'Laptops'); my $servers = create_catalog(Name => 'Servers'); my $keyboards = create_catalog(Name => 'Keyboards'); my $manufacturer = create_cf(Name => 'Manufacturer'); apply_cfs($manufacturer); my $blank = create_cf(Name => 'Blank'); apply_cfs($blank); my $shawn = RT::User->new(RT->SystemUser); my ($ok, $msg) = $shawn->Create(Name => 'shawn', EmailAddress => 'shawn@bestpractical.com'); ok($ok, $msg); my $rightsless = RT::User->new(RT->SystemUser); ($ok, $msg) = $rightsless->Create(Name => 'rightsless', EmailAddress => 'rightsless@bestpractical.com'); ok($ok, $msg); my $sysadmins = RT::Group->new( RT->SystemUser ); ($ok, $msg) = $sysadmins->CreateUserDefinedGroup( Name => 'Sysadmins' ); ok($ok, $msg); ($ok, $msg) = $sysadmins->AddMember($shawn->PrincipalId); ok($ok, $msg); my $memberless = RT::Group->new( RT->SystemUser ); ($ok, $msg) = $memberless->CreateUserDefinedGroup( Name => 'Memberless' ); ok($ok, $msg); ok(RT::Test->add_rights({ Principal => 'Privileged', Right => 'ShowCatalog', }), "Granted ShowCatalog"); ok(RT::Test->add_rights({ Principal => 'Owner', Right => 'ShowAsset', }), "Granted ShowAsset"); my $bloc = create_asset( Name => 'bloc', Description => "Shawn's BPS office media server", Catalog => 'Servers', Owner => $shawn->PrincipalId, Contact => $shawn->PrincipalId, 'CustomField-Manufacturer' => 'Raspberry Pi', ); my $deleted = create_asset( Name => 'deleted', Description => "for making sure we don't search deleted", Catalog => 'Servers', Owner => $shawn->PrincipalId, Contact => $shawn->PrincipalId, 'CustomField-Manufacturer' => 'Dell', ); my $ecaz = create_asset( Name => 'ecaz', Description => "Shawn's BPS laptop", Catalog => 'Laptops', Owner => $shawn->PrincipalId, Contact => $shawn->PrincipalId, 'CustomField-Manufacturer' => 'Apple', ); my $kaitain = create_asset( Name => 'kaitain', Description => "unused BPS laptop", Catalog => 'Laptops', Owner => $shawn->PrincipalId, 'CustomField-Manufacturer' => 'Apple', ); my $morelax = create_asset( Name => 'morelax', Description => "BPS in the data center", Catalog => 'Servers', 'CustomField-Manufacturer' => 'Dell', ); my $stilgar = create_asset( Name => 'stilgar', Description => "English layout", Catalog => 'Keyboards', Owner => $shawn->PrincipalId, Contact => $shawn->PrincipalId, 'CustomField-Manufacturer' => 'Apple', ); ($ok, $msg) = $bloc->SetStatus('stolen'); ok($ok, $msg); ($ok, $msg) = $deleted->SetStatus('deleted'); ok($ok, $msg); ($ok, $msg) = $ecaz->SetStatus('in-use'); ok($ok, $msg); ($ok, $msg) = $kaitain->SetStatus('in-use'); ok($ok, $msg); ($ok, $msg) = $kaitain->SetStatus('recycled'); ok($ok, $msg); ($ok, $msg) = $morelax->SetStatus('in-use'); ok($ok, $msg); ($ok, $msg) = $ecaz->AddLink(Type => 'RefersTo', Target => $kaitain->URI); ok($ok, $msg); ($ok, $msg) = $stilgar->AddLink(Type => 'MemberOf', Target => $ecaz->URI); ok($ok, $msg); my $ticket = RT::Ticket->new(RT->SystemUser); ($ok, $msg) = $ticket->Create(Queue => 'General', Subject => "reboot the server please"); ($ok, $msg) = $morelax->AddLink(Type => 'RefersTo', Target => $ticket->URI); ok($ok, $msg); my $bloc_id = $bloc->id; my $ecaz_id = $ecaz->id; my $kaitain_id = $kaitain->id; my $morelax_id = $morelax->id; my $stilgar_id = $stilgar->id; my $ticket_id = $ticket->id; my $shawn_cu = RT::CurrentUser->new($shawn); my $rightsless_cu = RT::CurrentUser->new($rightsless); my $assetsql_shawn = sub { local $Test::Builder::Level = $Test::Builder::Level + 1; my ($sql, @expected) = @_; assetsql({ sql => $sql, CurrentUser => $shawn_cu, }, @expected); }; my $assetsql_rightsless = sub { local $Test::Builder::Level = $Test::Builder::Level + 1; my ($sql, @expected) = @_; assetsql({ sql => $sql, CurrentUser => $rightsless_cu, }, @expected); }; $assetsql_shawn->("id = 1" => $bloc); $assetsql_shawn->("id != 1" => $ecaz, $kaitain, $stilgar); $assetsql_shawn->("id = 2" => ()); # deleted $assetsql_shawn->("id = 5" => ()); # morelax $assetsql_shawn->("id < 3" => $bloc); $assetsql_shawn->("id >= 3" => $ecaz, $kaitain, $stilgar); $assetsql_shawn->("Name = 'ecaz'" => $ecaz); $assetsql_shawn->("Name != 'ecaz'" => $bloc, $kaitain, $stilgar); $assetsql_shawn->("Name = 'no match'" => ()); $assetsql_shawn->("Name != 'no match'" => $bloc, $ecaz, $kaitain, $stilgar); $assetsql_shawn->("Status = 'new'" => $stilgar); $assetsql_shawn->("Status = 'allocated'" => ()); $assetsql_shawn->("Status = 'in-use'" => $ecaz); $assetsql_shawn->("Status = 'recycled'" => $kaitain); $assetsql_shawn->("Status = 'stolen'" => $bloc); $assetsql_shawn->("Status = 'deleted'" => ()); $assetsql_shawn->("Status = '__Active__'" => $ecaz, $stilgar); $assetsql_shawn->("Status != '__Inactive__'" => $ecaz, $stilgar); $assetsql_shawn->("Status = '__Inactive__'" => $bloc, $kaitain); $assetsql_shawn->("Status != '__Active__'" => $bloc, $kaitain); $assetsql_shawn->("Catalog = 'Laptops'" => $ecaz, $kaitain); $assetsql_shawn->("Catalog = 'Servers'" => $bloc); $assetsql_shawn->("Catalog = 'Keyboards'" => $stilgar); $assetsql_shawn->("Catalog != 'Servers'" => $ecaz, $kaitain, $stilgar); $assetsql_shawn->("Catalog != 'Laptops'" => $bloc, $stilgar); $assetsql_shawn->("Catalog != 'Keyboards'" => $bloc, $ecaz, $kaitain); $assetsql_shawn->("Description LIKE 'data center'" => ()); $assetsql_shawn->("Description LIKE 'Shawn'" => $bloc, $ecaz); $assetsql_shawn->("Description LIKE 'media'" => $bloc); $assetsql_shawn->("Description NOT LIKE 'laptop'" => $bloc, $stilgar); $assetsql_shawn->("Description LIKE 'deleted'" => ()); $assetsql_shawn->("Description LIKE 'BPS'" => $bloc, $ecaz, $kaitain); $assetsql_shawn->("Lifecycle = 'assets'" => $bloc, $ecaz, $kaitain, $stilgar); $assetsql_shawn->("Lifecycle != 'assets'" => ()); $assetsql_shawn->("Lifecycle = 'default'" => ()); $assetsql_shawn->("Lifecycle != 'default'" => $bloc, $ecaz, $kaitain, $stilgar); $assetsql_shawn->("Linked IS NOT NULL" => $ecaz, $kaitain, $stilgar); $assetsql_shawn->("Linked IS NULL" => $bloc); $assetsql_shawn->("RefersTo = 'asset:$kaitain_id'" => $ecaz); $assetsql_shawn->("RefersTo = $ticket_id" => ()); $assetsql_shawn->("HasMember = 'asset:$stilgar_id'" => $ecaz); $assetsql_shawn->("MemberOf = 'asset:$stilgar_id'" => ()); $assetsql_shawn->("Owner.Name = 'shawn'" => $bloc, $ecaz, $kaitain, $stilgar); $assetsql_shawn->("Owner.EmailAddress LIKE 'bestpractical'" => $bloc, $ecaz, $kaitain, $stilgar); $assetsql_shawn->("Owner.Name = 'Nobody'" => ()); $assetsql_shawn->("Owner = '__CurrentUser__'" => $bloc, $ecaz, $kaitain, $stilgar); $assetsql_shawn->("Owner != '__CurrentUser__'" => ()); $assetsql_shawn->("OwnerGroup = 'Sysadmins'" => $bloc, $ecaz, $kaitain, $stilgar); $assetsql_shawn->("OwnerGroup = 'Memberless'" => ()); $assetsql_shawn->("Contact.Name = 'shawn'" => $bloc, $ecaz, $stilgar); $assetsql_shawn->("Contact = '__CurrentUser__'" => $bloc, $ecaz, $stilgar); $assetsql_shawn->("Contact != '__CurrentUser__'" => $kaitain); $assetsql_shawn->("ContactGroup = 'Sysadmins'" => $bloc, $ecaz, $stilgar); $assetsql_shawn->("ContactGroup = 'Memberless'" => ()); $assetsql_shawn->("CustomField.{Manufacturer} = 'Apple'" => $ecaz, $kaitain, $stilgar); $assetsql_shawn->("CF.{Manufacturer} != 'Apple'" => $bloc); $assetsql_shawn->("CustomFieldValue.{Manufacturer} = 'Raspberry Pi'" => $bloc); $assetsql_shawn->("CF.{Manufacturer} IS NULL" => ()); $assetsql_shawn->("CF.{Blank} IS NULL" => $bloc, $ecaz, $kaitain, $stilgar); $assetsql_shawn->("CF.{Blank} IS NOT NULL" => ()); $assetsql_shawn->("Status = '__Active__' AND Catalog = 'Servers'" => ()); $assetsql_shawn->("Status = 'in-use' AND Catalog = 'Laptops'" => $ecaz); $assetsql_shawn->("Catalog != 'Servers' AND Catalog != 'Laptops'" => $stilgar); $assetsql_shawn->("Description LIKE 'BPS' AND Contact.Name IS NULL" => $kaitain); $assetsql_shawn->("CF.{Manufacturer} = 'Apple' AND Catalog = 'Laptops'" => $ecaz, $kaitain); $assetsql_shawn->("Catalog = 'Servers' AND Linked IS NULL" => $bloc); $assetsql_shawn->("Catalog = 'Servers' OR Linked IS NULL" => $bloc); $assetsql_shawn->("(Catalog = 'Keyboards' AND CF.{Manufacturer} = 'Apple') OR (Catalog = 'Servers' AND CF.{Manufacturer} = 'Raspberry Pi')" => $bloc, $stilgar); $assetsql_rightsless->("id = 1"); $assetsql_rightsless->("id != 1"); $assetsql_rightsless->("id = 2"); $assetsql_rightsless->("id < 3"); $assetsql_rightsless->("id >= 3"); $assetsql_rightsless->("Name = 'ecaz'"); $assetsql_rightsless->("Name != 'ecaz'"); $assetsql_rightsless->("Name = 'no match'"); $assetsql_rightsless->("Name != 'no match'"); $assetsql_rightsless->("Status = 'new'"); $assetsql_rightsless->("Status = 'allocated'"); $assetsql_rightsless->("Status = 'in-use'"); $assetsql_rightsless->("Status = 'recycled'"); $assetsql_rightsless->("Status = 'stolen'"); $assetsql_rightsless->("Status = 'deleted'"); $assetsql_rightsless->("Status = '__Active__'"); $assetsql_rightsless->("Status != '__Inactive__'"); $assetsql_rightsless->("Status = '__Inactive__'"); $assetsql_rightsless->("Status != '__Active__'"); $assetsql_rightsless->("Catalog = 'Laptops'"); $assetsql_rightsless->("Catalog = 'Servers'"); $assetsql_rightsless->("Catalog = 'Keyboards'"); $assetsql_rightsless->("Catalog != 'Servers'"); $assetsql_rightsless->("Catalog != 'Laptops'"); $assetsql_rightsless->("Catalog != 'Keyboards'"); $assetsql_rightsless->("Description LIKE 'data center'"); $assetsql_rightsless->("Description LIKE 'Shawn'"); $assetsql_rightsless->("Description LIKE 'media'"); $assetsql_rightsless->("Description NOT LIKE 'laptop'"); $assetsql_rightsless->("Description LIKE 'deleted'"); $assetsql_rightsless->("Description LIKE 'BPS'"); $assetsql_rightsless->("Lifecycle = 'assets'"); $assetsql_rightsless->("Lifecycle != 'assets'"); $assetsql_rightsless->("Lifecycle = 'default'"); $assetsql_rightsless->("Lifecycle != 'default'"); $assetsql_rightsless->("Linked IS NOT NULL"); $assetsql_rightsless->("Linked IS NULL"); $assetsql_rightsless->("RefersTo = 'asset:$kaitain_id'"); $assetsql_rightsless->("RefersTo = $ticket_id"); $assetsql_rightsless->("HasMember = 'asset:$stilgar_id'"); $assetsql_rightsless->("MemberOf = 'asset:$stilgar_id'"); $assetsql_rightsless->("Owner.Name = 'shawn'"); $assetsql_rightsless->("Owner.EmailAddress LIKE 'bestpractical'"); $assetsql_rightsless->("Owner.Name = 'Nobody'"); $assetsql_rightsless->("Owner = '__CurrentUser__'"); $assetsql_rightsless->("Owner != '__CurrentUser__'"); $assetsql_rightsless->("OwnerGroup = 'Sysadmins'"); $assetsql_rightsless->("OwnerGroup = 'Memberless'"); $assetsql_rightsless->("Contact.Name = 'shawn'"); $assetsql_rightsless->("Contact = '__CurrentUser__'"); $assetsql_rightsless->("Contact != '__CurrentUser__'"); $assetsql_rightsless->("ContactGroup = 'Sysadmins'"); $assetsql_rightsless->("ContactGroup = 'Memberless'"); $assetsql_rightsless->("CustomField.{Manufacturer} = 'Apple'"); $assetsql_rightsless->("CF.{Manufacturer} != 'Apple'"); $assetsql_rightsless->("CustomFieldValue.{Manufacturer} = 'Raspberry Pi'"); $assetsql_rightsless->("CF.{Manufacturer} IS NULL"); $assetsql_rightsless->("CF.{Blank} IS NULL"); $assetsql_rightsless->("CF.{Blank} IS NOT NULL"); $assetsql_rightsless->("Status = '__Active__' AND Catalog = 'Servers'"); $assetsql_rightsless->("Status = 'in-use' AND Catalog = 'Laptops'"); $assetsql_rightsless->("Catalog != 'Servers' AND Catalog != 'Laptops'"); $assetsql_rightsless->("Description LIKE 'BPS' AND Contact.Name IS NULL"); $assetsql_rightsless->("CF.{Manufacturer} = 'Apple' AND Catalog = 'Laptops'"); $assetsql_rightsless->("Catalog = 'Servers' AND Linked IS NULL"); $assetsql_rightsless->("Catalog = 'Servers' OR Linked IS NULL"); $assetsql_rightsless->("(Catalog = 'Keyboards' AND CF.{Manufacturer} = 'Apple') OR (Catalog = 'Servers' AND CF.{Manufacturer} = 'Raspberry Pi')"); rt-5.0.1/t/assets/web.t000644 000765 000024 00000007122 14005011336 015512 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::Assets tests => undef; RT->Config->Set("CustomFieldGroupings", "RT::Asset" => { Dates => [qw(Purchased)], }, ); my $catalog = create_catalog( Name => "Office" ); ok $catalog->id, "Created Catalog"; my $purchased = create_cf( Name => 'Purchased', Pattern => '(?#Year)^(?:19|20)\d{2}$' ); ok $purchased->id, "Created CF"; my $height = create_cf( Name => 'Height', Pattern => '(?#Inches)^\d+"?$' ); ok $height->id, "Created CF"; my $material = create_cf( Name => 'Material' ); ok $material->id, "Created CF"; my %CF = ( Height => ".CF-" . $height->id . "-Edit form-control", Material => ".CF-" . $material->id . "-Edit form-control", Purchased => ".CF-" . $purchased->id . "-Edit form-control", ); my ($base, $m) = RT::Test::Assets->started_ok; ok $m->login, "Logged in agent"; diag "Create basic asset (no CFs)"; { $m->follow_link_ok({ id => "assets-create" }, "Asset create link"); $m->submit_form_ok({ with_fields => { id => 'new', Catalog => $catalog->id, Name => 'Thinkpad T420s', Description => 'A laptop', }, }, "submited create form"); $m->content_like(qr/Asset .* created/, "Found created message"); my ($id) = $m->uri =~ /id=(\d+)/; my $asset = RT::Asset->new( RT->SystemUser ); $asset->Load($id); is $asset->id, $id, "id matches"; is $asset->Name, "Thinkpad T420s", "Name matches"; is $asset->Description, "A laptop", "Description matches"; } diag "Create with CFs"; { ok apply_cfs($height, $material), "Applied CFs"; $m->follow_link_ok({ id => "assets-create" }, "Asset create link"); $m->submit_form_ok({ with_fields => { Catalog => $catalog->id } }, "Picked a catalog"); ok $m->form_with_fields(qw(id Name Description)), "Found form"; $m->submit_form_ok({ fields => { id => 'new', Name => 'Standing desk', $CF{Height} => 'forty-six inches', $CF{Material} => 'pine', }, }, "submited create form"); $m->content_unlike(qr/Asset .* created/, "Lacks created message"); $m->content_like(qr/must match .*?Inches/, "Found validation error"); # Intentionally fix only the invalid CF to test the other fields are # preserved across errors ok $m->form_with_fields(qw(id Name Description)), "Found form again"; $m->set_fields( $CF{Height} => '46"' ); $m->submit_form_ok({}, "resubmitted form"); $m->content_like(qr/Asset .* created/, "Found created message"); my ($id) = $m->uri =~ /id=(\d+)/; my $asset = RT::Asset->new( RT->SystemUser ); $asset->Load($id); is $asset->id, $id, "id matches"; is $asset->FirstCustomFieldValue('Height'), '46"', "Found height"; is $asset->FirstCustomFieldValue('Material'), 'pine', "Found material"; } diag "Create with CFs in other groups"; { ok apply_cfs($purchased), "Applied CF"; $m->follow_link_ok({ id => "assets-create" }, "Asset create link"); $m->submit_form_ok({ with_fields => { Catalog => $catalog->id } }, "Picked a catalog"); ok $m->form_with_fields(qw(id Name Description)), "Found form"; $m->submit_form_ok({ fields => { id => 'new', Name => 'Chair', $CF{Height} => '23', }, }, "submited create form"); $m->content_like(qr/Asset .* created/, "Found created message"); $m->content_unlike(qr/Purchased.*?must match .*?Year/, "Lacks validation error for Purchased"); } # XXX TODO: test other modify pages done_testing; rt-5.0.1/t/data/smime/000755 000765 000024 00000000000 14005011336 015267 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/000755 000765 000024 00000000000 14005011336 015636 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/emails/000755 000765 000024 00000000000 14005011336 015427 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/bpslogo.png000644 000765 000024 00000007531 14005011336 016336 0ustar00sunnavystaff000000 000000 ‰PNG  IHDRµ&‚¡ÚDsRGB®ÎébKGDïïïÁ̃ pHYs × ×B(›xtIMEÚ$GÙIDATxÚíœ{pUEžÇ?Ýçœû ‘—„‡2H‚ˆ( Z2"!(2¨£[–a’Ak°fw­ÝM­«®jxD¤Vبuvåa@]¢Ž" â„ð L^÷žÓÝûÇ9ɽ¹¹7$XVy¾U·RésNwŸîoÿžÝB„"Ä·VWUÅž/á. Ó_ÃÎp¨C|Sá„I"DHê!¾YØß…— ÎA8?Ûµz0upHæx¼WDÛªÿ(D€@mæµ?½-è^ëßsÐYª¹  {yf[ðuHÝï8©w€°‘eÎËvÝúK «€?e\¾¶yC[õ;@7ô/Nµ$u?ªFŽ„ÜgОÄ>ªðv*؈)®Á»æLïâ ÿàÂòºßqRR¸8¬$úDÓ K*¼ /±ÐíÂâôcP«Ubo¶Ê *ï$ÒŠòlä\ 7XkÜ8©±zièú‰¼1Ž>X:Iò«(Öi–á*”ȯÀü±©TÀi7ämHê–N„ÜžÀý,U¢À—äåy1è‚ÀDÈ(ëP#î+íiÃQ¡÷¸¸Ï¬¨ -ŠÐ=ª`J-¤/’í‰àº¿ØÔYèá}®à•ª!©Û \Âé[ͦ 0H@ 9jvt¤:p[Knâ˜~ø?4„†CаNkBúu5©Ë^+ÀäSQüß­î*Ût3RíaÑ »¼G?_݃ú¾7±¤ä¥V×J·^‡NV±ô†Ï¶úòþݬl‚‹^¾# I}«®ˆ /N/X'¸ 3ïÂG ˜ º¯ƒ¼·©¼Úð“}À&þa ï…ì mœ2—!XÆÜ­pûêT¦ñÞm#0öóèèîß6®K{3çõ‹©ë¿äbʶOiIèÊ9± +²‚ò½‘³oDba½ßôëÓì¬Ù·ñ¥+¢ùçâf˜ÔAµ‡~ÂE¯±Ñï(ô—œ0Ð  'Èщ{âpuHÁ.5?ä¿‚Y÷ѯï0ØüNªá¹ñ1¯r š±ôo)«ü9K&¬èôžÌÝt=Â~ÁyVѧçn\¥hÌ„¹Ó™ÙS>"yö«8º±‘Äg­¥«sŸF]æÀ¥.ü!óºÀú0»²Uqnœ*‚SѺˆS%r²‡3 Üÿ iØU’zÉ„]D­0æ=„œ„g­çÞ7.aXTüÆÌò£fe›ÿòòγÇK+ç í—€<<ñ0Åe”H2ïÝ~þ¤¹Ã~“?dÉø ]1îi@¬xÌäW¦ùvz«HŒ€FßÜq“!»ÚQ|züÊ÷ÞÆ‘cOb‰iÄ"ë)Û>›%ã7P1ñîÝö9QõŸkÇJ†1ëõY¼0õÄY·ÞR ŸB0‹g'lõM‘ͣщ•HÑ£7c›ŸP1¹æÜWqâÊ<¢…)2{–F yy0$Ÿ‘…h6Zpä’}Axvµ8MyÕ!ú# ÚÁ9báöT8ƒAÄO´¼R°ó‘[}Îyë^,ó‰0²xâ"f½Ý›¸»!®sãÜCÅ5Ÿ´Vçö|D®dÁ¼wûá%_Dr%ð)^r:ÏNÞïKî·þ ÌB„ˆ¡Í2ŽT—³æu¶/9œ=È1н •õ•@¿Û•éåÜ$‘SÏÔF„ø uÔíȃQIg`¨@Ʋµå _ËéeÔÇí9¬\ÜCªv©|k"žyDO„y™ªêXsGåå6Ç®{Á1Ôá1›eÅëÛMê9›GcÉ• úcÌtõl–ÞQK¹‘Ûöó†$’Ÿ±¸xU'¼g¬ä4+·A˜-ô€X[•7‚ðÃv­´Vôøšh_M¢»„Ú B|m"F ’ÕCªv©æn¿áýB Ø·©˜ø©P_å Œy…?æ™ kÏHê²Ê+0z-BÄf½Š£\ø^TÙ– g`t5š™,- Õsˆs éåÀ3×Ę7¿hH‹knòM¤¨}ËÈ€1 'Püo3¡´ ê—¿ç‹Ý»Ãé Ñù¤ž¿+9•¿DÊ9N`™ûR&Ä–«°3Xªúe»Zì½õ ¤yAo¯RºõÖáÕ߃ùÁÍ\<úæ¼×?œ¢g~ÌÛ8]‰à2Œù˜¨=ã}ï¾tëÝñ4à Í"? ÂtÈQüiåt´yÑOQ1ñ)†6÷DÉ@ƒ9ŒÒÓY:)<â%õýÛÆ¡£\†ëèS} ÆàöÕ¥[CˆE£@ÎfÑÄË$t»°xÂJ„¼ 8ŽQZ¹œù»òX8©†ÃÕw`ôs ú#åïühHˆg+©Ë¶ÎÀˆ'| ªPQòD+ j¨Bˆé,™°+WÅg”Ô-5 #0ìÅJÌhÞc2¯òn´ñ5‚ ésíã-lðöÃr™2~š<ÛÂìMö´y=þÁ‚öâÂ௡õ~휸º ÿÂálm„øQ˜ðûDîù [¾"Hü}Im}9JîC=£Ð/á÷©jœnG÷#@A“÷Œq.Á: Ÿ|·–Ú#Gêòr›c“Aˆ¿ÃЦ”Љþ–ÇÒÍ—"ä …ñ>–3“EãªÛÄv’ºÉvo<õ ‚›ãxòoyöÚw›mw)…  Eø¯ƒ)âÇ©{e»hàÏ6Ö'6ÑõéƒêÀ8wgF× 8¤á È–± Ú¶‘ çÞé$úŸÈ‰q¸Z!§fö×@ƒFiá¼çâîTô¯Aþc{ÁE—„cP¢iÐÇë}mø mõCjd‰ï{¨m¾S©w 䘯AuùÑ´ïÜq®,sШðþä`–ÀÛ’+zbÐçù“--…îÞä„ýc–gú‚i!O7¢#Àð$ú¸O5¸ =bãF¼oÁgX¢XQÁ"r†€Hv80Æ@/ öïó£>8$`ŒÂŒÖÓòrHês…/þ¸¥ÙÂ^Ìb¿É¯xO÷ 9(ÒpØÍNh,¬0B¡»;ÈûSÖCóä_ßä­û6/_Ÿj4œ\ÇÊ‚X†;@Êø êQèÁAt,«DñC~_ê2Æê:íjQê½›µ[ÜÆïámêR1Jêø¾SèJdI>ùó™ésµAרèÕé? ý²O9––!»# }H c»q¦å˜ ¸+Š=««æ®–ÚÏ ºÆ D±n!K.ÃËYÜÙRÊG‡jøžA™ùÞñF E¯£ ?Lšg#½V oKR? ?Λ.±Æû˜ÊØÖZZCA²`’‚—ÓtÆs6üÌ ¾Ã܈÷ŽãŸmì)`°À.4è|ƒ®ÂD†]ÞIHÚÄžW$çiÌäò2Ü¡'5n„Â#؃ :f'»PO}•ßÿÄ$Ä`}ë½âJØ0ZA®t!Û1¶Ë-?á•ÕÑRðZHê–)ž v´Û>ìá :ž©Š=Øbûg/ppfù€ÞÑ@{A,¹µÝê¾@Ž’Èkº’TÜüdýT¦xè8È›i!*uÂÆ~§äZ‚Øn4„Øá‘*ëÉ$E]/(¼nd|ꡑƃÀ“ÀäXƒ¾%0-šºƒ½©žä:úD馑#Ô×f„LÓËw€[ r èm-œç‹n9JúŸ”Èævïiï)ÑU,êPF±ë‘ž&ïHŠ;=M^•îË'¿o-µM©æš@Zç“:›xŸ»'O³Ýÿ^©ÿÄÔߨzË2ÃwýzÇÿÄÕäkr6ÓøÇ?*ýâÍýÚ—2­”^ÒÛq:fžª½Y­"ÿâjœÚN–Ë¥éíÏÞûJÍÿŽÕÉo¾Ðª‰òÄ:µþÕG#$+—;qPäÍT›&§|ÄiZü Ò?þ&£‡IÓ&lE£éîäk(¶ÿÀ~Z¸lþÜß½ÊÀ¿òËûßïñ5¢°ˆ×qÓýš‡6U—c-|;¤*ó¤i­ÏüùGÿÄÓ—AÑÙ—þ$Úg?ôãÿWÝwœ·û5,1ùŽÌ>oý³æcQ]Œ¿øFôv ?°ôµôÿ@‹ÿ‰§¯…ô)âŸÒ›pþ+øšÙÚsóQ÷ÙZM¾æŠ+±‡ÿž„ßBÒ•Ù±…öZËÕ|+¤ÝMµ¦‹¥Ûí;¦•4ø~UôݶºK¹Žì&o÷ñUXG·Ì“|«ÉÛ¹¿ô*Ã÷5P[دmàßù[äÐ4¦_½¹¬aû¿÷ÍA…ü8×ÑÙWø¾Áßý¶ãéÕ6H©ÿ|ÔŸÙñ+ç—UBç壚]ÅËÆKø?ÂÌ> 謧þ¡Ðÿñ5Vû¾†-ñø[F‘û/öt[[ÿ®”G8ùvôjÆ×}öÞ?Ýë·íPç>ãŒ!Øæ¬þè’\Éw>…§E+ÿË·F„|ß»V®\|?ÑnÏi[sÊ¥Œ\ÿãµ}.#}¿X7¬ãrDv¿ì¨æ¨/Æ !µvÒ.ÚÞ|ØXíeò›ííÀ?ÅYóLÓ‘\Ý‹ÀþT¿‡4µ_îµ’m?øíM„ü&»‡ö&Œ¯ÏÝ´‰[o÷~í_‹Äüó ÊØÜµJâÞÃV}òÆñÛ·ÿ¯KÚO¸ýšêŒëÿ ø;O áÒ"ôe´DW?ïU(|3¥\mò<5¥{îµU_ö‡šÞÓt½¡V7d;•¥;¾oïVå´ÖßÀWn‡µϸù"º•·€ô…ÜeÑô½¸û©j›V¬¿ô )5‚ÿÛ]7gp8ä½v?Ì´æ†Ùvàü«ü4sO£${«x'Ãò.‡d»[ïy óSá ÐY¾MMo÷`U®Š-!¹’â9äl…]Œ~QVÖÜLÌ £þú£š}Ö=Ž=ü#áûv]úš§÷š•rßÁ~™X¾‡bÛ¿‰c®†M7ÌFØà-YÓi²[®äÊãûµ>Ò¢ê?gN] ‰¾ès,ïmak?Ì7&åCþÊÕÍ+áβ‹ý Lž_ùéÏüu[Kë–܉+/Í»ïmÿ€Öí­óÌ©æŒÛkEU¾¤:Q]Œß|,ÈØÐ¬ÞÃ|¿øõ ð÷Ã*ê?Ბ٠ÿÅWHaF|¡ØØÝþÍW¼º†ÍâŽæXâgùSÌ}»¿ÝÍ_µ}ÈT¡Ñ ðçÂÛ?äf¿ír¿û5A7Ã? c)£[.…wÿñUÓiú…¾¤¬c‘%ÛÏÊjTdY¶àíqà ÿ³Tª>äºK±ç¶>ðÔsKo.nÒƒ¸owÝ·ýŸ›‘Váð/…ãdó4Keó=ßiÿÇ«µ›KŽe䌻«6÷EC·Ê?#|ßúuÌ)õFÇÃÿ FT ÏsüÃvÿº;ýÿ÷j[?‡þºVŒørÚ+„ëù9_oC « Ç!ÜË»;“ïS.­ÞdP’½»¡VIcûËþ÷³}Ú|ÓzÜ^ΞÖ1¤øcá†VLJí—þ'ÿU›á¿…ןì+uÿgÌ“ÿ‹®ÆËPKï6.¡Ûæ"†ÛÈʲ·qüºSîmB®~îðÖŠ£}LÝ4ž¨á¥øsáßò·ÛþÌÒ®ßü~²´¿†º5¬·P]é^D¯¾ ÚIöŸàl6>_ájô#aз5_Éù×?ÃÓoùÿvŸ;î/gÇÿ|6£?Øðÿwåš_ûëïÒ‡¾‘ÿcD¬>÷ïæÿâ묚ßÌ^>e/SQ"íù;s·­ÒîÎŽ+Pø¢}Ÿ÷ZZ#(ã÷Ò|¿øýwßþxź\K *^FÆ+…[¹øqÿªsZùqãqmÝà-eü-ã„ýƒRÚ‡û©'c]æÔ”ÞÆ¦¥¢Œ¯?ô? xyu½ N·Š Âö ›‰Š28 ümm¿ƒJòÿ Ûèþ"ÔÞÂÿ@“M•%ÝÜ )t9nOÞã­}¡aoªØÜYÝD“ÚÜFÑKŒ«¡ ×çÄ¿ˆþ#øañÕôÍsOŒiºnØD¶p&„üÈã?ű—wüSÇR¨—µ¦Íðœ¿u4u·ð?…¼æHâÿ—ö©v…ü?÷Õ7þ]+þ€ïÿ3ñuÂh?4ï\5å´RŤ®äG–6Üä}à¸ÿÙ«¡ÿ…aÿ<5ûôÕãûZ½Ùê<=>Ëð)øvêÒâòÃL³M÷ÿº+¯*—¯ü ¾ºÐt˜ôÒÆ.RÂgÔ÷5óïìéàM0ëï«Eq%óY¡UóÐ~äýÐÿßUô&½|,4É¥ÏÌÖ¾¢ B-£æêIÎV<ßÆÚŸÛõI9Š/”W7{{|ÏW.7Ü;÷™©»xãïW,ŠÜ…!ÜÝ)ï…UÄÃÿ©Ñ|½ÏŽj-¦FÏðš’‘G¹ªt_›ýšsC"ÄÞX ý·T²°…rNÚ†Z‡Ì²u;‚ÉåÛ eþ÷ÝÝïWîî7/”‡ýöÿÙjœ±îà|«YH´R‰’Þ.IÚƒhlT© ™”È0OO÷©ðÂLlܨ~í^L»AdqÇòò)Ò°VÇ,Íü+÷¨ÝØãÔD¾^à>÷vjųDˆ–ÜÍË«ýÕ«I…L ÀE …ëÿ|Ô qó1çh5&„¯ WP_ýª©q}å§îÁóXœ*ûT¼2± ¹Ú¬}b;=RdŠq-ÆÇÛ±OËÏ÷±ÿ¨“.(ŠÙ`º•q:Ë*>ÒÛ÷`þ©ö[‰R;–çËwÏüü´iº|m¾ËxÎâª>÷ûÕ{wbEfÛ#†Ö;~¯$­Ý¤zÏÖ5ë43Ï(ÜNæ'ýÚ]Rk%¼Ù6ÄŸwÊ?àMXºuœ—×pC¿Íå·ßfçsûê“wØÒ «,Zêך¢)¶‰øŸå¦]hrÝ+yúœˆ[þYÄvóVîn¨H#ŽIç~‹ÿǿ٧}¡,Ñã%×øV ½LHüi ï7·LÝü·Ù¿æÏoý–³d±´±•çC$’±ÿ–Ÿ:±{o½vPj_'ï uÿbAÒ©ßÙé÷–ÍÄe‹wrŸøõMÊŽÞ9ƒâ'·‘…ÌŠ©ÁP|Ã5Xµñ–•5«…óÏÊŒ¨Û¿ÞÛV&ðÎˆÒ®Ë ®Ÿ)Ü>l¼kKBðÇþ…aoNÑ÷”}ÝÞ´$»š¶»rýŽêŽ-ìÄû£î¬Û ´—)Ÿ­/#9'¸^jZŒ7q%’JÙ;dFCòUÿwÒº¸“gïÓæ?søk>ý®l¶Wa×?2¸ù¾”é›íƒÈ¸Ÿc1W_ÈöbÜ׊8ÙÁ»3ºÞ©Òò• ¤Sã…oâÿv šÑæ96úí•w§ÛêžQrö÷VϹÏF­Ì¬tÈÂFáþ^ÜVwˆtÔÖ-ÎFEY ãÞûËE¼&7ól’ÝÇ;_ø¿Š§—O7‹ìŒ?‡Ò„ȱÿ,ö2%Þv‘6¼ù_ûß÷ÕhÛCv·pNÑùìh¤äí(>ïýóÿÅUØf¹µ¶çºu_»š¹ôWЬ˜toºU‡Ì¿ïQ ï"iÕeÙóö–§Yâèw³ýÚ˜/9wm?uªE·FVp?xG>õvd;]B®>êÕWÉáó·vßö«@e†vmqÕj¼ÊU?º¹ûßÝ­nEŠöw\˼Š~÷¥jeêµ[SEó¢aùVV«bf·mŸ+Åó£u…jÂÆ9vrÔé! » ~íR`ûžàíkû{@µºa¶B›d_Få?´'-.æÓ¼JºrÜ\Çþ‰tì¨SÊÎàÏžËós×¥t_ õ#g©ßéLCûØë®ñ÷„`ñσµ] áåŽ+ØL{à“c©ê0~ W«íðîŸ]Ž4ýu.‡ÂZöTŸ¯Å…½×†®-“í:¸x^¿4±'¦:Òÿ„ÞÃþŠl?÷î:ô;?øoÂ2ù² è5´û*_\§ÚTY6ôÇû?ìÖ‘á?[OüÇÿÄ×Í·wªü£»éùžµû_™ú-dÍR Ž%?ô*YðËßÞ§™¶!¦¼{¸Œ÷ªn4†3 Ûj9¤xÛ朗Y£„uÝŽ­X÷R\3`ìLýïïVSf©¼àÏ€w5JÌqû«Y-pöñb1³ý¦1¦4rLªnÕþY-g} ,X’ê[‰Y ;Sîš „Z†9§ný×—mUéBlUÞÿ="ÂËPžed{qäìV}ß/«Sæ›và\üß(Ú:R=çÉÀªw7·~ô§ñnÅK¤ÍëáâÆÓæT‰%¼,±G±xác« mbdÃÈqÂÕ+¿ѧwcŽ+3KZÝ"¿æï¸S&Ô*¯ýî»k̯/µJK'­•Ú²mn~žÕF=$Gû[É;H+•ëýÖÝéÅ]‹QG§ÿoG$Þ\oUumµBïÄÖQ»$—îÆâªw|µÈèz}¶ø-÷ÊîÒ—nÁ?üOݬ¹µˆìoܦuq;ɽçdÿÍéüª cÙèŸÛ’*ï?+ç ™¿½OÿFº‹ ÅyC.Úä£Ö®ÙHíÕ¿éœR&½¨«¦mÕSûÛ÷5Hùlj¶“w¬ðklcþ©Ð7áWášæÅTãeÿ¾[us7(¼†&»½ýÚmŸŒ­¤ MÄ//ÝjtEò¶u¿Ú’G `Þü7ËüUVOiÍþµ«giÞ:sX/ã 5b2Á¿2+üÕKZÔ­Ù¥’"Ľß_÷}½»ÑmtK©Ý@ÖKÚDe#w™ýå«–ßg‘7 ùˆÜkÎt/$ȈetÙòX~]¿í7ÿ]]Ÿ‹#WPéò·µ3’±y4y-îíç’%ù~_á?çó­VT“bH›yÝýàßîÖzx’9ýXfÏðÕi5iî.WìΛTíxäø÷Ö‹vÍë_´ÆždI&Ü•x¥Xb¤¸Õ£·¸Š „{w–6ÇÊà_ð¡µs .ÿ-Ô†­ÿlEt‹ÄA²x¨HÉšzn´Vâ{k„Ù埑›v×_÷«yÛrda³üXë\n£ Z…»FFØÊíÚµGC¾¹ð½’ZK$·pŸÝ³ååUô«Ø‹#¼eI†OáÍDúx_ž"U¿Ùû­Yë­G2!RŸ0Ü”lIç¢Ç¯ÍU¡š‘Èñ¯ï>\ü»½jÄ2<}\7ñjÃkéf‹z~*ÔÅÔ¥]ÁËýÚ/b¬tÏ™±Ÿ›¿Ú¨%Qµð¾¾õ‘­:ļSå¾y¡ò߸ûËUs;¼i±“†F;½Xšn’–·wÄo¹·üóQ.©<~l$ï•-ýïâÍ=õéÙYÄAÝ gýê.RGA¥jW]yF¾V ¾ß½¿×þZ’©ù±\m¦°n¡óâ%C´«‡ü+rÏZ7–ˆê>l|êßy[ѪâÈ’,ÍÍ‚{U8¡ò]“ûý?Þ§M¨>Ì‘úU9¯·7#îþ•«25¾Öb(‰|ÈTÿ5µO3æ ÿªiªynÈRŸºKL–Öc¤x‚ÏP\±ÿÝÍ{TR 2œ‚2+çíPòí¥,>Vø÷ð×°|?Ö¶ü/gp~ø]Ÿjô0³å»œ•áîó:ütéÞ8ÔtýNûMËD·‘Ëu7•#ä—ÏC‡6×–i|@ÿ µ—þÿöUôOí=¯é>°Ð¼S«é2ê–¢wÓäX™¸ó²nPpGÈßïWΟð¸þÿÑ<´ÿ?ð*óñt• ­¥£Ôö0•äé+;Ÿ~É ‚пd\þ•æþ•pò[.ßz½Ä7gѧnåq\E’í8è+Ûªõ>v$BÒq«SKÔTÇZu²î~›¨•„’··JålÙ!«Âçÿ¨¬íö£—'t®ÏûÎõZo—îî©adµo1¾eDÝÓï7ðΤµr¦¢»QmÓ·Í'×Ò¨MŒ/÷˜íïUƒ#mi$ùþ=Täù¦W)»oÊ?Ù¬[7Hª¶æi¾oÞ"ßSRH£,~öôšnQ°ûY‡Êß{^Ú1(.Ñ÷ä;™«_1¾^ÖÿëÔNû]±þíòXÂòIüÃPÁ ’“wÔ®]ˆ.-|ÍÙû¿Ýª®¨­òÛz7¥X¹¸ÜxùV²å¼ýç—ÜØûÕ›f‰»u|ÿ÷ÖÚ§åÉ%Ä®IÛ÷EZÜŸïwj¥.©»ùñýÑPXù¾þ Û½¹¬»½Z }ÅŸlKómÏËYz¾©+šç+;„ ó3·õ¬;›ícµã }YÙXvª·Ú)-¿ˆä›"ù•z¶>QÿצÞ_Fæþ*Á]A#Eò—û£nk#QÖ&’MˆûûÌ)¥ÌU[™#‘·y»¿‡å¨dºE??ÍŽŸì×:ÚЇvÿâÿ¾Gû5Ÿw­,ÿ/þ…V 3«mR=ËÆßö±L}Pvzò}wÆ—\©ÍŠK|Éó-Mgã¨î­Ieò¥QÎàÛEW²*ç¦Ï¬;nòòµVmBIýiUÍy.³ñA!‰’ÉÌìÇoš£åZ§¦|@žòfBœü»¤4{¸*Šç¸&¡˜×tƒýìÔo¬G’Büq·æ¯=·ñ þÿ7÷½V!ñuœ!„g§^;Òöhw;™¯ [°µˆÛó Ì›GÓî6I·¯ÉüUÆÏñÚKÄ´‹{;mbßÂ*Ëø#ååI\¦à»öZN‘¢™ÐÁ¥éÑËû¢"•:2ÈÛ–¯Éª]ۮÞ¸âLÕÊÁâHc³ŠI@‰]§v*¼¾*³¾Ü‰&å ´µC¦_1ÐK¬D²´’ qýÔÛÿ}UøüQl°©wÆß­qÐ^:†Ùzg‰ŽæŽìR\ÉnË´Gþòü´*`Ú;ÄñÇØöù°HÐ7IæU­Xîvüµ‹“B$•$ãwÝ5XC»ž¾ëñòš³›—'¿ýõQÝ,±—’ 3c;øªÉ°‰nc\Ç÷{Çü&›¥Ü}—ZkyaÆïâoóµïš– ’H›‘òõþuͺLÈùÇF_¼µ@jqøS ŠOÞ4y…UA;¿»þÕUšbÛ³ò¢ÿãÕ,´GqoÂl#äÏðÕ+‰‹~ï…\ E&¥ö†tO‘U¹“ýš¡}u"¦"ùe~›¾êV¬[5HuÌÞ[ùc ÿÝþèª2©„¨*Äîvj¹ "4åÏÌwo¼O÷ª–¡}œrÎè>A»æ©‘¢*ßÂú”¨¯#¤wm_âÿz²|Q«\i6É™göËéNß¿µbÞf©lõIï¡Iò3|åj„Ó$ÓOvNæ`Ü>î>õ •c:á^ÎÐ ]¦½lçnÅÿvªÄ±ZÁËïm¼³ý ª-発â‘Ù~ëº? åõ_[Û¦ÙüÖcò6ömõºW¬oꚷɈßgû_{ms:ޏWøÆìñº¹íKÅÑG;˹”íÜÇmy¦¯ñ0–œ#†”œFØÚª¿ÃÖº)ÓobÔ~#ÑõoP ›[<µq÷¾>‹OW6ãͺøkËu/^j‘î /Þùб¿´$·¸WeÝÕX×|p¿Ìr¼T÷NßVñV¯¬y'D]Ü* µ—q¬j~J•üµéº™6¤Ûù`m)¸Ó-¤¥Ë}ª¸¡Z+cV¹¢õ"»Öµ³q¹ûÕ¡¤øÒKXV®z²ŽµSW…6`˜ Ö˜mÙ¸ Ÿâ­%E±Ë5RŒ÷=fÇ^܈î\ kÛk0àíÏ÷¿½^Y¤ëBR;„*Ž~G®¢ßRHzßíz×éÙ°—::׳¶šo5÷¹çŸ½Z¶Ël„nãýºãíµŒ´¢Õƒ 麱f«C§xÃ}ÉÝ7á¬ÇÓd… œ&çß¿5T]IøöÒ½ânnwzî¨apMRçOR ß÷£ Õ øÁ¸!eÛÕpU¾íÚ2̈‹XúÞ­ÄÂᘎ~öÚqÔ»šrøª;¤ÿF“kwY~ZÆŸÆFd‘ÌLÒÛÎÝÀ}ãÍcXXÉ©Cxðo¶Ю74ŸÞOû嫵†Ýös™Tï |ìʵÕi½ e+nmÜø™ã»iyJß~7ïþ½[VŸ¤ÓU¹ßýÖíÿ¯<¹¾¶ižD'áoºŸìíèE&©©YÝN©mŠ-Чws¹«§Ù&qºÖ¾§½xwâ’\2£åîÅy®Â=xê×v·–Ò£yoÌJ~ú}ÖZùJ©,fWIn7Æß—øk¿ð¯%³š"eû§óÉV‡.¨è¥WŸF}Ÿ ÛÙÍuøUàM¯"¾jÿµ]µÖߘüʽ^àŸGq[ç/Jõm7XûUº¦~oöMyN éhê®-âYДÝòþëï.~óS´=s̉í/ý"Ýü§o»Ÿî·ü å¬kmY#*.pŒ$ÏJÒ–ÆßPY§æ)Ý#,gæÚ>eÿЪ&‹Lº^¡Hè©p[cgø©ú•œ÷HÏe(Šé>`²ýÇÿ÷«ñ šŠèÍ‘›Ë¤ ðËÚÛÇm¿í|Øoö«ªÑµEÕ´«[ÔB›Ç)'Þ ÷X7ã@ Ò5'¸uŽæ -ocÿYlíó÷}GûB¶Õ·6 ßÞ5NîÖ-R8÷å]>hå_¾‡ýš,ÚOš;€<Øß_ºÃÕhZ¹*Zí¸~›qÿÄÔ–Ì|½Ž¿wåZyP»‡÷¾j‰ÛÉš2çå?/ü žÄnZÓdÿLh³÷ÿô!W †è?à5œêwy£åt;†ÚÖÜ,ŸÂãwÿW$f\®ÕéX²[ù—+"e\ ¥¿Ù­û¿—wë!ùÍÓkÔV„ ²‘2à—éT|/¿LÕ,¤w,`œcwÞØ_åû/ü¥šo²Ý«“µ[øš¡·oôœýíŒÛ½W?-W¹zŸH©Ü¥ÝTtkµiv“~$oҮ§ïE3׺ìpž$“uâãï UÞ?Ý þ”ýW÷š‡Óæ]Ê ƒ¸W:žä·™[dLmÝÖ¨²•eÇÝ^µ¡¨ÇûÅøj™…5”PÝI„zc§ÝyßÊO÷GÌÕ‘p¥må1ü¯Ž7 jk+ºîÖÔÇÏûÇ–¬ÛÖÓò¯zÆF±)¦!…co,mÚ´ÅoÞs˜p¾¢¥Ý÷ïó˹™áŠ{ K¨µ¿÷Ižó̸hÓæØ9ªš„ŽÛ"ŒÇæ<üÛj[8^Ö×y;¥q¸îîj!òï.76ˆVLÖ%9£Ž¼¤?Å÷}j»Ç*Äþ?íRܱó÷j…ÔÛ– ²Yn8ÆOZå¼Eqý¡tºtGsü²ÎßËU­GR{tÊ!–Sò¤wæÿ Ëó“M…òûås¾IøÍš¥mHµ­Q4û)Z 8Sšâõ_EŠÒ{ƒD>uÏßoëXž5øˆö·in›¥PX³ƒøö«Í.üE:—Lïv¼òìÿwÐVЦØ]#gRñeî½zÀɲ"[Ÿ+‘«k‰£Û4Žè›zm%·{ûÕ[{èô»gD”K/vQòý+Ï<]¬I}rÑœ²§Ìº©Ã™Ø‡.UÌʾ ñEÞ°òÈdÙ©úW35ᑹJ‚[ÇfaŸök±ø{á{}hj2^È©j–§27ðáeÏ|íü7W©¥Ýž=牑ÊÛZÉtøOá‹zT°èï#eåŠ$þôÿ²ÕûÉ ŽÝ-")åÂìÞlcnÿ÷ª‚ÜF³.c2ñ·j»šŽvöJœ>")­gYX$‰.ß”2žµbÞFÞE>C¶>m«Q%Ñ…äqò°è­ó:¿e«K®ðelm+’¿Ê†ÝЧ_v;yòÅÝäÝ(;Jµd¼i'ɃþóSãìîòF‡cüÁ}?¼¾õ}fŽÞ?5#IW;wcüâ¹þ A8Ô÷YV) måN›ân•«gªYªTWo÷Z±¯/ ‘ÕÑ6±÷ûµKU†5 àá˜r¿íU(süDJnš¼u±ØGâ(—‡­jX^jqÌ©Ã*Ÿùèü*þ5„°Öêm ZŸØ=]ÖžÎÍŒgço”n®Qu-É<÷ù²Åºk;Ä‘’áÈ'dA–_»ÿ­K? Ýêž ŸQˆÄª×v|Þk&=»eñÚp‚„o"çQÊMC¡—a⫸a–?1¾wÞ?*7ûµ¥5Ä‚Þ;í0Ù<Ü åB¹þéæ°ôèßOh®ü½Ê“ù¾ëcæ­-O\ó-µC. ×­}0–8›vèÎíÛ›=>_–¶’\Ë‘±N_}™×–·V7/osÅ*uV¨Z=©÷þfý+ÑôÝbËÅÚ}”ä£íPBÌsº¾ùþ/Ö³õ¿ø{A’ÖYïe9HÎí¸_•U«5‰×’kSG„Ó=CÉ•aŠG“´yùŠÿ»Wl.¾Ç,AЪž»½*žÛÕ{«»7H<”Þ`I7:EþZ«\ë—ˆ±Èûöž¾öß÷«mfb¥zžáŸI¦Þ ŠPöåùfÜ»*ú3Á^0žPóþd®wþÕ|s£êIl¿uÖ½OÀÚÓÛîO5›nï&_îÿ²ß5yÕéXô)TçW>¸·Ô ÄÉæËýßZÚ´Ô¤ÒdÞO›`z·ñCþ×Ò¼§Â^&:•ŒFO•þãóüUßéZ—îUîS÷¿Ú¯>Hè;+­÷Ö›­äNFälîSG…5c5¼°]âgÎߺ½XÚ‰¦¿Ùâqö';¡?4müKôþíkKn’/È6±}á½*@Þ·šE9#äsÿ|Ö‚âH”ãÕ›c2MÎFõëE¶ ÿìÒåY†äþë³Pgbý›3E‰¬ŸíSfpÙü]—û¸§3|½÷~õ5Ô´«ÑŠ„ ž9ƒC¸ŽµwN“u³ÇüQ_á5ÀûL‘“ó!ݵ½ëRÁ¶Þ ?vA³ÿ‰ÿÇ«X³9Ü®íÃýšÊUÛ³8àúV¼Ëó7ñmëY_)lïø [ Æñ oö7•w‰Õöÿ{§þ…U¼7ï ÙFùù#Ùµ¾òà‘·þ÷kbó÷ѱõ T4Åòm–=å¶;¯þ<Õ{{‡€¤2xGJ-÷„ ¿ÅtuÍxÅ+d=þuÒ×»‡þ{–;™Ío zÝŠ!î’[„1´_Ç$§ï·ðýk%ìRÜy÷>ì—?/ýóÿë­[ö‰c@?ÕD6¢ÿ Àk†×õç’o"¹žºâ›ÑG«"¯H»/þÍ^}©_I$ÒàŸœúö®×ÎußË1¬tm{Ê?2£줔.Îz÷©hG©‘§Ç%â}£;;×g®}OÓlÄ:Å¢9UU¿Úé\ÞšÛo§àE+eƒUÜNº—+?îÆÕV¥$êTZ“NJ…&ìtO¡¾¼%kO+th_vvï¬GÐõ;oµ=´©vïÇñUmÖÚþîG™ÖΖ8-¤ØÌÅ”g=‚î®ËÁóZ]_ý›TÛNÕl¼è$ö´R¡*Ëéü-õùk7RT¯ÇËN»Rz3€·_ô¤sÏ̹ûßìÓ¯f}­˜?ïãÕßë n4»×žÊནmû=Ê£2¹Ú§æ=‡Í×¥cj¿ |E§ÛKwqg¶þw‘fO‘͆ÿwrî­cZœîLèÔJÖ8øÚFÄgð­ ‚«Fè]ðçï´’Øéíb’›Ùà¿ú¥ríõݺ›öXíß"D•¶|ÑçærjHŠq”ÌëÉ®ªóPßÈX'_”RÊ¡ŸÌw/ÏÝ_½QM!ê>ïðÕ£ ê™T·5:á†jÿÇ©É'¥nq"Ô4¨3Þº; ]P°ˆe‡¥/y»q¯DÐmþÇyû§03¨ÙïþÕyøš¼‹Ý=¼ SÞ{‡ˆtû}?ND(~Üç–cü?î×Kð2hµ-zã@»#ìúŠ}æ}¼¦í¿ú`ø²Æ[­Ggš%”¡¾êšæmî®t]IeŠCÕ¼œûʳj)åïyï Ùû¨:×|Aðøð®«>ë+gÞû|Ø7*«mþéfÿ>•ÐiŸ´%ûE¸p²°Ù$ŠNïMßþÍeø“ÇøŠÉ Ô“ý#ïfܯŸö½~ípBiÏTzr•:ÐÑ¡|%fu 6½{`ÞNæèË•ˆ7]µÆjVï©K*[É縙›jŸø þ5ÙhZäz/„/nã¸ÖišÒ8%˜|™gÛÿ|ÿß5ÌøOG³Õµ6IgX¿Ýgٹٮµ.[ϱÆâê5æT×’F×IöCou8Û<»s¿*öÝYM•›çùÙ¯eÖ|u«fŽ)U­mÓ÷2çs'vVn¸¯%ÕÚ [‡´¶"||¦\}ï÷}+Z•M‘ÍŠ éjÙŸæ5ˆ®·ÂºÃÛÊ‘‰ Æçžv²Ÿök“L|ÛÁ«–,a|òýáþÍtUJjÇ6R„î})à¯}ÚG;Uð²sòîþ^É¢kD\¿Þþ,×ɾñ7–SŸ™~S¹«×<3ãhã¹·ä)¼í ÇïgîüÕáÔ‡)ô ó{ÈúÆãÌ݆ùØr¿Þ­Ÿk†ù§¶¹CÄTnû®¸ûËÿ}W›Øø‚+xRI\ªçieþílØj ‘J'DÜðü»Y¿ÙoP¸Ú+sÕ,¦1º6²þ«Wõ _í-=ÄO²v£‘~ò°û¦°tÛáql“âwýªÚÓu¦™£ÀÜê¿Â++‘aÚ& ×Aâ¸lñ§Ÿ—îæµö‰8ü¿Ù¬·µòïRT¹l㵨ò翺¹ªHLÅÖÚKÄ¿ˆnýÛ#®~ö9ÿâ«speY:î Uï#ÎŽ>^›Z–ÍJÙ¤Dîuw5_RzW˜ÞÅ~ëá‚Ö1[눜|¿+/÷y_þƵ­¦ó´Ûw)ó(d+醪wñídp>oºk_ïeÛâKEÇðîO›ÙŠÑ ~[8/;ºÔ¶±…ûPo¹ÚÍB¯Îܘ.j£¹“={áúíðͯãü륮wÀ‹·Ã–¹÷þuÑW»‡þ÷÷ªÛIòd›ûµ‘© Ä›‹ýÑ´V5ÅÖæÈÆÑüY«·—/û5ƒ=ÁÛ—Îá÷j bRÔ.ŸÍþò(ÜÌßÞ®ÅúâZÚÜ;HwchÝü?íWE¬êB’™ ¬XûßŶ¼oUÕ¤ñåăä‰>`Íó Ú¿Z¨G˜Ýæ£$š•ÊÇõ•¾üì>èûÛWÖ–îêÿtŸq?¼zÓ.d6v«-9 ½~à®sXÕÞ6 ÷ŸÞºÒf?h¥­êÆgxÓåEùkŽÔ/„r±•gR¾÷Ú‹Õ³\–©|n‚eQñêì¥NæUjªh[½b]ØþlýêÑÐak]÷¤ßt*ýêæ‘¼¹T‘»šîõ+7†ÚÃÊŒªWÚêÿz·­h%ç>õœºüÒIg]I!k‡$î¿Þ®æ_ ÁñC°k:dÁüKä¾³Œ7Ì£åGü—øïšâ/>Ñ$W ¿{w_E­¯ƒž:À¾+YnÝ–ÂäySòv¯÷NÑ÷¿û&¬eÉÏ Ðs%QB{2Xþø‡KŽ)Ë¥•ê ÑÄÒ”§÷¶õªÙë7ÚÜW ‘]néþ_”}ÕÛÿ²×ÔúÆ—¢xâÂÚþ[}÷/¶¹áeaÝ~ƒú x?|©è:»½¥Ã½šÛ#mÛún¯>‡7iÚç¥,4"¹£{#Ñ´ Jxü9gqù¸JÆCn@¿(?*ñþÍyŽ|Xui­îB[ÚÜ8–o WluÛ¹¿‹æÛþõvzÄÖú_€mI,óÉ (\m;OÓšåü?á™|dÌÍ¢¼¬£ÍO2M™Ùü;½1E8B›s9V‡$YÄ^Ü}ŸGk6¼7­çy¡ðv nÕÎÓóì«YTŸ¾sæ"|›¿y÷[ýšö'ø6½ÏhN–¸Üö²’íÿnâ¼ûâ.eáÝv 2Îu¸û4³4i·÷¿Å¹·7?•z4jÓŸº·g™Z•J~ü¶FUº…|ì Îâ¿ç¥UÔÖ5uxÈßÂßzºÝ#I–Ìù ?—¹wrz˜åtqó)檜“™¥jn®g¼‡­>%,»¸¨·7J—vÑŠë<¤Íé-õ(*žk¿KÄ’x¤Œ•Ü8V¯1‡|Ž_›ûÕÚ‰£±Óà//Îã„A¹«ƒNíÖ¯*i–¼@±Û»Hçó»5Ä]*\\;’[wÍÖ´u=`Ý7–#Úª6üϺ²VÜêº4ܦ8ºª£ÐŽ[9!e!~Vû´Æi!}„–¢iw E¸Ú‹×w÷«³SÉÒúS½h,T“ýª®÷“ûªcqæJ®±†`zz 9GÎh·‰µÓ–Â;™ÝNí¨vîªpÛÉ$»É,ÍóQe i—»ÿ²µ½›w#ñnQO˺AX7luÓ„ëk's6C;Èãj¯ZTïf/5èzG‡aµ³sv#d?ÅŠÉ×,í#ûˆ»2ÿr,Jç±éýB\œ÷1mn%³e•+°Óud¼RC¹ðÿjçÇ[Èÿd­Aóéòy‘Ù÷ªej†ƒ£è}àŸniO§ÜÊ|õwgæoö«ºðUÁÓv[JDSÚ¾èÙ~Uu+ùùwÃÞ2}&þÞäåS;]”ôô‡õH5‹4 ‰QÇÈÊ~õyÓ‡&ŒêÑëÚt‹¯Ü¯;QÎï”×Q¥ì[¿5G#iãïW˜øBo´hñÛJÿ4ghoâÝ®ûJºùSæÜØå³\ÝflëŸ ™ÏZ‘&Ú¨ƒ;ñU“tj¤íô©!´*ÿvOýš¬Ìµ ‰ùx¨­¦òîÕÝo—ñ¦Äß¾ãï›­8ǺçÍ6Ãýꛫaû5Ê |²+~kÿØÔ7hb|Ì)tÛ¤këÛbÿ;B% øÑyú‚å ùpÕ¹‘GpóX9á·ÿŠ¥\*)á;ià ,¯üX‹ºÿj¨îK=‹Á±ù~±`ì­ÊÍðü^Fg÷bZÑǹ¯~е4x“w›9Íayhã¼{k:)>uÏÝU­}Y XZ?÷xý+1mGöæ¹ênmÌlw·÷ŽÚ¯ª–i`}ØÎ?SÆ¡Ÿ¡ÕŸ|ñŸH€®g𛣒»Änù!w=c^0RÄü­ŠÚÕmÃ4Rü¿0®g^o.Ù¤n:W4âP´¸òl^@ 1,Û‰˜ýi¾sª°þ$«åÀ»þVWæªw…Žw²Õ™±Jþm˳šÁÔî<¸Ôœí_âþíi^Éò±=–¸Ýrø4¾Xûßz•µ.=â«£pK±æ‘¿½þÍy½ÍÆØüÄù7ÁWûßþÍu~(˜y+lŽÊÌKgµy·Šu†µV ÷¾èZê„ÙCRÖÎýÎÇjW­_ ’FÜT½ÔŸlg.Yñ÷sò­sš® do(ʧ–þõvÓ¥sÔPE-Fùî€sågîÿz©2žŸÃRÈ¿5G÷Wæ®ô¹V‡“6ÛæeW]Ï÷Å{Ô­›ÁÚZÃ$Éåsµƒÿâ«Çæ=*ôZõÌv d\µº¾ð¾XW£*Ö·C£ ]PmË©ÛèzLV:“ÜçÅ(eÚÜ·5ÌxÃsØÞK,¿“¿†þëUÏøºK}¶Æ=¶ÿtíûÍÿ¯X›M·Ô4«),‘%ˆ&ÙNææ¼Ú•'…çÔöaN–.Ÿ$9ðÏÆMgÃpÊ“Ë%ë$gµ‚š$Ë|ÌÞ¼VžñjîúúPb‚)n7å™7)Ïðíôª&ð\¬ï-µ»¼Kýáÿ³W5g¥«]Á”S?ûÕvÃ×4Q¸šPnèõ¸|uá;}6ÎG¸Åh0ß.Oâ ¿^7VÆ•ñÓÃz}œñâTYAbé̼nUÿ¾–¼óC•] ;Ã0fþ,S&ðÝÌ6É)!‘ÎѶ«ê”ž®f/Z*g£x§ãåî©¥KÓ§¹%d’3¹•‹nWø½k†ð—„ïðLºµÔAX›«0û«œf½ª-./èñAAÔÞ.vî÷ZµzxXrQÝšÑÃÕÅMN¾Ë¡ço ÄbMöñÚù'k2žµÃx’Ííæc#gù¾ZôÿjÈÐù¿úñwñ¯ »‘î.˹³÷kL4ýù0”cB‚çsqº­%™m¹ùsRîâuXÓ,ÞòùK’Éù¥W¸é¹[SçJí.çKqáX4T³2ÈïæÆÒùŠŸ.~îßâÏÝþíg\Â/-”Z[ϵO-!ÝÓ²ü«ZšO‰5xdò-ä‰Þy6BÌ>d$ûôþ¹m®GϽ[Û+yÕnãX6nRØÞžÕæóÔÅ©írÒ~êѼz{î]ƒ÷¬xéS]iogþèJ–Îï›Ò½¦o†¾Ö4W¼ðþ¢f}ñK±€Þ™ ò{½øí¬‰¾ ëw–íp.-"³”üÁFß»Y¬\[å½^ËcÄnaù˜sŸöª&·’k?öP³zê5(拾–ò%ºG*Uwmún¬K‹›}Á²ŽªµéFªg“RƒŽ¦+Ç·žjÞ™¤Üj ˜²çijK˜þLŒ×¨|1·Ž5~Òè°>ç ß. *Õ:w‰ž ªÔ´/|9K8§‘÷O,{‘[ïbº´x,íYäÙ¹_›åþš«ë_4û8íâ³yqT>WÜÞù«”ÔU?ÂCUËeÝy’FÜmÝýæù¨ˆ6EhÞOŒàÿ§›P¼Œõ­™—ŽŸv²¼³ÿ ’ÿwç•:ú§ÿcZ³)òXó¶ºQƒ2׿óþnÿ-Ko-¼yýi±üÞnGñúV¾…§ý£[Ó×ïm#?úêL™ëpGåAz*zAÐRn¯¤ŠåŠG†ÝÌ˨Œº7O˜ ?•dC ÷8®ŽX"†0¿¥aË…r‡ršæª ÅE+¸ûU]Nô2Õ›ð­Wrçû¡ÕaÄàçþY½\ht'﮵ú"à|Ãæé\n±»|C¿-wºÄe­”cïWª/™$”¸Þ¸ÝòŠå‘Ó6ýŽvqµk:鼸°àU­4a™É;vç \þµpcyûÇúV} £¹‹ª^nGú´<ÿµ\³xñ“x]±ïÛŸ›ÿÕò×U!ŽÑP›y¾¹'™ró“û×QÿÝÜx­`µ/ds¾!Ô6£¹o—ïnjò›û§¾¸yçûª~MÕØx²ðÈZ4!xü«Ï5äŽ%ÝŽ7}ã]ÔàfÚ05[ã½ÿ‰ßø¿»XS}î¹q!šF”ü­š¯*îlcpÏêB6<º•9ÙUþ÷ûµŸyªÃ.æbTmﺢXÌÒà}êfL¦Ùdÿv‘á}ŠxäíÛZ‚jŸ! ßÄßüMSf2KÉùñSLN6Üdk,n¡1»«Ò~j÷ŸjD>RBF[øk†Ðc{j(ñ3ü»¥;QÞ«šGˆ$ðíýÄK.ø7îÜ¿t‘üUʼn§í áÔïÂÔöS¾‡Ñ+ \B†@7¸Ú}Ö¹)üws ºó¸FýQÑþ&Eu‰%2mËf¢Ô¾)ZFÌ™U[æÛé_2°Õé»@ú¬Ò’æl³}áXäØoË·æÛ¿j¯ËMÒ|/¦]^J—² «þ¥WåeZᵿŠ^2‹h¼¦îÍÞ¹™¼]¨ÈêDì¬?‰kѧ‚ÄI{ÎÇ LÇÜúšÏGXÐn|>>è®sÆ^6`ò2ü[£=¿ÚZòk¿j—Í}¤¦Ñ³åûƨy“Ý;rë÷7f<ºÏš£2žb¥ü4\×¼Msª>ÞV!ò…¬˜a,Û‹o­KKW[˜W”ü¡X~µ=íŸØfh¤Yzµ^œZ‡¹Ë9Õ|óe;M>Kë”·‰ ;¨«]³áø´ù´í*Í>Õ¨ÈY•{/áþÎꯣÞypÏöp‹tpˆÌ~UÏÞmÕØ|ó[ã4ί+$°,¬~óÚÛ~÷þ<¸®Z’ïØî…8B+»9 øWTÔ®eŠÊÞW¿†@ñù_y0~VôÆïâ©u«RÍï,äÑ¢Ò甫ÝíVW”›o=~m«Šúqüoà[»ÍbõÚ-9ÁYžÚ6f'ûÉòñ»v{³þ×Î_>$\\kwÖr {_bE?0þó7RÚ¬)Ty^(ª°§F>ó;Ç{¥é²ÇOÝÆ¬bÙµåÈ ÍýÖÝü5ËüCñÆ »ü‰ÚÊ|µ¼žCîݳøY¿ÙmÕìŸõ/ ÿ™ñ?ˆoí•VÏ6÷Λ¶dºÞ­¹}ëç‡ñ¦­²õÚêÕÎã$#Ï­E:w¨äÕìTªóC•;_c";YäšÀÇh—Vs {‹™>n¿sÚ͆òXC”Ÿåû£wñ ôÍgà̶ú<·šUÏÛR(þÐñ(?4'æGUÛÏö^7yŽ£g-¿ü³*ª>ö>Zï¥(TêpÕU)ô"yžŽÓþÕtÚ<× c›ÊënéÚ3÷WÒ¹Ko5ƒ”MÌ¿6ê±ÒI&oñµkRŸ2±Ö•Ùê^Ðô¯k:»—k hÄÏsmµ[v6û½ÿ|ÖWÄM}/R[ˆäŠ{'ùaž¹Jÿ zìùâK]Gtº†g†á>Uo)ö²ûü®»¿ï¯ïV¼š§Ã…×#€¬RÈÓBÍ÷’"ÿu¿à-šò½o-nþÖ™à^snWÜ>S÷[ï}kZÏÄWvòÅpÇç žbÆ–£K¸Þ°\íûÕ^áDhøAµ[ýßûæ»ß,´8ÒšW:7¸“û>¸ž–]¿4HU_Á—þù«znŠúä/*i××’¼›c•Ÿj9ÛÕŸø­s/Ú¶™$‰w £aû¿/|/á[z^¡öO³¼ñ¼¹Ý³qUû»~†¹ÜyV†éÜ˼ԒÏP¸¶Žá.’'(O_÷sRÝj2¬nÿ)ΛâÈЮ¡P$BO+ÌŠMÍ!Ûó|»¿=«š¹™®<évK)Ú+¡SŒÕу¯*o–g¥ü)Ô“Mº–ܾ՗÷©#âÃ_P|4ñ$Z‚Ïm;“ÔíüëÎ|C¨vî:ê¼CyºX£Üv‚Ì óoß4jÛÎæÏ«²œ6qzýáß9.If®[¸ÜU3ó˜×A¬\»`OÊ£šã/î Ó<Ÿy¥z” qV©Ê¹Hß sÎÚ×nãÎÖ÷§ùˆÉŸájNØg¥t •¦UTRMH‘É䳦6cnÜüÆ£h亹Dæg;QqRÏR‰†GQ´íù·T³T¾ÑCË,ËŸºOðÕ¯ì÷ÞÆ0YWø±SÙY¼ÓÅË þëJûUOû[U±ZpFë¼ÈŒÛ\&ÆVl⢤ùMiRçܱàýÞ[jÒ}šYÞS2mWhå¾j˹_ø“Å·p\1tkt‹æx—æÜ­éü?-zï„<+.Ÿ ß˜¢“mÌaü¹æÆÖoþÇmqWÞ“Gºk“ms’é…Ø~fÆåÙŽ¿ðà…e9¹v= Ðq‚©‰¥xöò&ùe˜ì†Ûî»6Öoý–¹›«;»;ém.RH.£;]meoö«Ù<;­iú^›£}æ4ùóL™ýì?‹°_âUÁ?Þ¯4ñ¨ž(ñmþ ò t’Faµ6®ÝU_–º)U”Û¹…z1„!c—–²¯Ö¥{Qœn\ß5«obf•\!h·íÛZZÆ‹>›fסhœð¬:-nëE5šY4ærÑ.çÙÏËüU¥»ìûáj%Äl§Ÿîµ£ y*¦UØØáY:ÔÔaJ(µ¦ÝGg:}¦¸‚AµÕOÏá­gÓàñ±e·Òã}­.ÍÎÇû«ïþÍb[Èqtܹ۹~õkX_%ºE(»0K6óµ¿Ù® ‰üKsÖ¤Ó÷e±½¤ø_N·´–æÑ×Ïš7DG?72ö9®^Ëíþñ•í¤W¶u¸ ß"íÏÞfùqþõlø~â95¥–äºÀ$ÝûÁ÷Ûø›mkêYÕ­Âà¼Pyßêãù—ÑNÑÔöËt®%QÒŸ½­Î×ET§îicé øÛJøµá[Í*Iá·×¯­ Y<ç™G̲´HÌP/÷Ÿþ_ øÛº†uë«MF"“¬‡æþù¾òµ{…u¨|ñL·:\OÒy&\y¨Ñ•›qÛþòU_î×¥xžãÂß´ûA¥Û -ѳFÒ̲‡%w1çç]ÝÙú hUT%u³8«áÝug£>X‹ÅÚŸ€î<1q&›sz—ÒI³÷¥Â2(Ýéó}ßïW?m$Šë´üÕí¶?ubMZ'HôÛ=:C÷—‡dQàüÛ›§üzü¢ºoþÎ6\qkú¥ãjZL(n6DÑ2ß2îÊîÛò«u®×ˆ¥M;œsÀÖŒ¢Ž·ÂÞ Õ5o†ºt¢8íÞÿE’"|Їïwýªò?Œ´ð>œ¶ÒÜIöÉŸwØØ#*¶­»8¯¡þ!ü^ѾèŸgIlˤ{m--‘w—rõýêøçÆ~(¿ø‰â¿ ;üˆ‹Ø®<=')óËc·_–‘Ý™:&›.¬î‰"EެÇî×§|*øWÿ †‰â;û»‰,­­¤K}ë²³ýîì¿wåÿ¾«Î¬4ÛŸ¶%Œ;ÝHvùk_X| ñ¶oðýt@ñYÞÃ3´ÑÊ¥Rv'ûÛ[ÿBþÛUÅ{¬Ã Gš×Z£ç_h:¯Ãÿ8"Ü ÐË+ÑŸâ_fô'‰¯o¾.‰«ÅZ¨1œ|Š¥·*·~ÿ—ÝöÄ—GÕ|QãöÑ­l û!g†æÒ1±ð¬Ãåãwðõù»-v*·¹ø3ªý!Úh×6²Éó²áãpó«NUà—S¾ŒcJo]Õ>ø‚ÇO³’{yK·žƒb²çï.[§ùÿ{#[øOâ=ÃϹÓÿp£–ù[gûØæº?ülÖ,õ¶ƒO¿’Ý3³c|¨¬q»éÊîÿW¡èz¶¡¨¿Ô|B)B·Ð„·Í÷˜+øê|؈%)XÒÔ¦ùn|ãijñÍæJ ª‘cªÚ|ÇÌíq×uz¦±gfÚ–£;ÛFÐ^n{³dö[ÄjzYŽýŒl@nur¿ïšºx…6[ à‘‡¯ÌÿØð#Áë –Ý€®ªÚþ'5ÆÜXÊß¿Ts¸WGâ %·Ù:™Ü»qµ¶Õk xäÓ¿ÌÌ[åoî×£N\¹æV¤«T±•ap¿hÙ!ÚÝš½wÁ«­Ö‘ªãʼµ›ioïÿþÍ^Y©Xý†d–ßçoö…uÞÖ5 ëG¶¹(ˆÜ’F•sb×ßø?öjÒš2Û©¨t¨÷j/'ð¥¹ù¾¬µ~Ú2î,ýÕùºV«á"[—m­wHçÑB×k¦Ûý–Î8ÿº¼×?¤Ûy÷ªNãþЮ©zW¥„†¼ÇŸ‰—Ùô®SþŸüù^ߺ¿ã_Xx'ÂÚ–»ª\-­…ŒFYea£·ëŠù«þwÃÿóô)k¶¥WNÉ+™Ñ¤ª]¶{‡Ã à¾m±o–D¿Q÷«ÐˆÜ+Å<ª}žÛMÔù@ û»÷[ÿf¯iG  ô«Zèr&Te 6?O»ù¡Þ–àm+'§QN|4'™®;jât_faê »H×âÛSæ¤ÿÜù ïu8rØÿ¾«—ñ%ŸÚ-åÃ$wAž] …IdÎíÒ?Í\‡‰ï…½œó’<´Fs]“Âmã•þzW•xÒðGàû‚ó<°¨ÄtDó¹µaqd²¹›õ¯?ñ5Öé°æNþÕt tZÛíÝýã\^»6îqòœ×§˜¹\ãuɶ£só9®Zy m”>ÛkW[Írüüª6Ò>Ÿ0.ü³°ü«Ñ‡¸6¥êKCتy‘ýÁü>•™¹ÉÚÄýÚYTÃ3'»Ll¹µhæOQÖqÉÊKÉó|¿ykBþèÉ5ĈQ^c½¿‡oñ6Úf—²9§¶Åeå¿öZcÆ÷Mù]¹ù«šZÈô¢­n¥»8ÞÞîæu[v!¤_æük·¼ñ4M-ävÞÓ],ãó|‰äd›`ûÌÈ:ÿ»^cr³Âè%%Wø?¼+rrçZÁ޼¸EŠIâlÓ»µ¾l}v­rV¢çi=MéVTýÈ«àŸ‰ñª˜Ù¯ÌÃc^Gº-»~Tãùö¯øuû@iíz·úÌvºt³Ï,Qß@7$Œ»Õ¾ï÷kÆ!ø[=¿›¢iÅ%òmÅÅåä[›æº‹ØŸ—ò¯:øƒ¢ÛøSûp~õ£ ˆ|±ûßû/ýõ^c:ïÙÄô¯*qö“ù÷í)¢Ùø7Ç3ÝÚÙù:v¯ÚíZ'ó¡›,èYxù™²;ðí-^Ý28¥m­rÇ»ÍçøXñ[ŸÚ×þ&Òìì'¿»º²¶,Ém;ü¹?6ÎÃpUôæ„ðû늖èÒ¥¸ØŸ"£2e¾fÇö«Ð¤¾¯O–läëÍNt7<+ ÎÖ $¦ŸçYæ+[šÖ“ÖšÑâ6Âm>ZÛð÷ؤÓ`yßÊ•>p®~SÇÝnÕ?‰d·Ðí^Hö}ÒßÄ?Ƽ9Vs­îžò¦¡K”ð&EÔšßz#!ošOº¸­›)%¼¹ŠIP2ƒÆÚ¥5×ö–¥p\ïÞü6>cýÚÙ³ÓÎÔdÚ±Ë7Þ5ô3Ÿº¹ž£OÞ|»–´Ë‡?ÝÝš&µ‹øÂ"÷lnÿ-][WWÀÜí†ýßðí¦\ÚËn°$¯ iÍò¢?2b¸®z<¦u„Ò\;O¹·Z½ÁúÂG2Ç;þõ±Ç·Já XíÙ‰ÄK¿0ÿÙªòÈc_”ŸÏøk ôÕEcj ÀôÉô½FãM”¥´w¶ÂÕ‘i§ê>ñ—¬[ÈíoÌrÍÞfQ·r/o»¸ß5sá¾±¨ÞC=²K"[ùmo/Ý6q·½vzÇ…ïüD–è±q¶Dþþ×µx®¬èTäg¦©Æ¢¹‘ñ÷ã¿5¸ôÏ[ÜYøj"»#xöKy>2ϳÓwÊ¿÷Õyôúo‰-mžÒÊöçk&Ù7­ÓvÏOâÛ·ýžÕèšoÃ{ÿíFgÄq'Ý•OÍÿ}W¯é‚×G%°RÅÍñnû¿Þ÷+[Km‘‚Á¯´Ï¦ø{qw^™Ÿ µ¾÷÷TW¯øWá>q¦:El-ø•‰Þ[7ÍÚ»«ÿéÍ©ÅqäI,×w͵ÿ»]âÁ¥Ú0Ÿ#'þƒù-cWR¤5fÔð4£-óLJ¾¤~)¸¹œHÖ¶Ço™•Øÿêçcµÿ„GÆKfŽQ6mßêQ ƒŽ~__ö—åùkèY¥ôóÉçï|ÛsòóÛmy×ů Ç#²D ìÝûµ'w®íßû-U [©.Yìô"¶AsC¡î^ø‰oà_¯‰. 2ê7ÿ¹²Š0Ìò„Îçúgø«À¾…£XE©Þjw)æC4ëÆw³r~oœD1þÕ|Mÿ Çâ7ý zýý_þ*¾ÍøÛñÝÕ?³µTW;a¹ù×øOùþõwÓ—2º<º‘äŸ)è² eÁéPÚ#Øz¡ÅXê&]²nõàÒœlù„»o#ýËc­sZ¬#És‚Ù@Ý+®š=ưõ+‘«Ï©;tÏñ=¯Ùå¸AŸï-xÄy……¬NvÁör§ôÿì«é_Zù{gÏÈé³ò¯–¾9³Ç¦¤ •g$.=V²¦½ûÉésÌšco¥4Žw4¥Ÿ¯ðÿ þUÅëw^\lN8WY¬L#¶XÇÊ¿wmp>#˜y>Y}»ÎÚõ髳–nÈæ"a$êqæ¶wl¥¿¼+¹*Øû¸¨ŸPû(@~õEºMJ_¼wËI]ÖÖç6–ŽåV‘ä@›6íêÔ·0•·Š^0x©îcHÛÇû[©ŽÇìÞ^w*Õ-“k¦V[[6rÏÚ¤{¯:e$|*•þµ]®É‚µwN†9&O57'}§kTÊÈÖÏÝE™¬Ã" }£w÷j]X³ðÝüɧ´ï›¾i>R´ß°Üý¥!´Vs°Gš½Gý›üI¯2&"Šy ª,§gÍèÞ•ÅR¥8«Tz’§Q»ÓZ—dý§ :-Õž…onòǶ6Ë2£•¾Voîÿwšò«ÛGÇZÅäîMÕë!¸Ý,¿b ²®[£ÿA¯EŸömñ%œ÷I=¡u€²»Dá—p›æªÚW‚N›jÏqi, αnhÏÞÿãUpôStw7ö8ŠÞímŒOxNçÄše¥ºJ®|Ù%‹w›³ø¾]Øm¿z½Dð-ž‡tözd†òõ£+4rª1þ×ùûµ[À‚ãMIl¹*$dù‡ÉóWAàÍJKëÝJüA½šÝ¥vÞU]†¼ŒF&u/mr†QŠ+ê:§c¦ÜOsoqn–iºO4}U»ðÊÕä~?×>Õ¥)ˆy¼ì“o÷±†jû'áݯÄOM¡Gòêºle—a<¦s×—¿Ýjø;År™lŠyN“:¼_ÜÇ®šOCŸVÔÌM*Åãu”FZ'M…˜ÿãÕÖ[ÆñÃæû®ïƒòÕ#|ÐEAµŽÝ«÷UkR(dkl ªgæUû§Å^•Y]œ”i¨CB¼ÌwðÂàU«©‡S·2Ÿ!¤MåŸ;¶žð­=Wå‹ÄÏ.öhö¨þõPÒ¼6·Î÷:…çî›û¿Ý•»RVKŽroÜH«-×¾=Ýþê/͹kR×÷÷Qo»‘lâ?ßš¥›^³Òeû6•i›ýöûßð&¨¥[Cý"[¶f²«ò­fäûXÒ =ÝÍ}T³Ñoíàˆ•NÒÊ~f¯¡<9¬@ÖË.ÀÛ>WÚ7cýšùRƒìðn ËóqþÕw >&]è¯=µÿ˜ñ\ío3ïm=+‹„s::)b”gÈϪt¨wA,ò¦ä|û~U­+û©,áO³\ùR´;Gñ#·÷wïW˜é,õ#ž5žDMìÏòñ»·£ ¶Þ<Óï4û‹9ïþË,#äe“ï¨^œõ¯ÙJü§¢çxã|G«^h>$ûeâCœ®s:þ;wg©÷ÛVuG}¥=Á”,[7nQµWÝ®;Å߬µÈ¿² üÎxIFÝõã^ ¸ŸM—ìð^4¶d|‹¿vßökÒ§öé)hrTÅû{t{ßìõâ‹¿xûT°µ“Á»¦/þ<¬Õô>½ð¯HšÁ–1ä>Ͼ£vÕì×Îß²©m¦ÝÞÊmö\1 $ò}çä¶Õþà]«õ-þÍ}TúÒMc*¹6Õù‡ÍšåÆS:®0/V¥ZjLù£Ä? îUa•ôljí]¶¼®÷Fþ˜{wùÅygˆo´ÿ ë¢æ9'—jÄw”Æ‹ëžýªÊ•Z‰Øè«N<Ò>t›á½Þ¥yÿøË!¾_™»»w¦åþ*ÑÑ>ë—Ú’[Ü„³]žnù?»_Ohž ³ðýËE$fÞ ¨ø‘~÷Çéü_øítºg‡b“N|IçÞ«•<®ÙX6ÝÊÝáÊ×t³*±÷VÇ ¶ŒýæŸtÏ€°Fò“ì&ÓÃú¯¶ïý ·4ïƒz]ª\% ºmüyïóqü¿àUí[™ ‚O<$±>›ål8¬{ë€Öjc—;]WånZ°–*¤º0ÃÓÂŽZçám…ÅÔ‰«¢+ÕZEþëV¾•á›hàû0DF`ØVTžªÞ§MY!ºq#ùY@Á›ÿÚ´&©Åd‘Ã:ü¿ìî‹”äh¢£¡“©x^%…üˆÌûµçºåœ–32Û©Gs¿r¾ÖôjõF¾ó—d®Ê[?5pºû=­Ëù’Tù‘Z”[L§cÕgXÌ~fv.ù۹ͷq¯s¶Ô¾œ’œ²—TFîIÛ^ðoø§¥=ö§ükÛ­#?f³ˆóΟ*ÿ½¹¿ôê§³<Êÿ½sÿV©ýçû«ü_çm_ÑìÞãXº\nó ”zíªB÷­¸OàFo˜ýÚë|bZú{—á ŒÊ¾È¡?š×DUçc‚rå1ÔË«yJ~X ü*ÞX¶8*¾ÒíêMkA† ¢½j4ùçäyUgËRLtª÷—Igk,ï‘¡s޼TìsÅrÞ:Ô¦³³Ž xã‘¥9“|›6 ÿ?λªÕöpsìaJŸ´šó'ÄêVw7Zž§>›=ÛÍ;˜>û“»*¿6F~]¿p_ð®ôŸúÜÿßñëüy†|3u¹¸½i§T¬ÓÍ—v3Ÿ1?ݯágjßÞñoþ $ÿâ«ç#:•5GÓ¨Â*Ìêgø©yãMn RËOyôÝ 6KVÁbÈGúC¿a¹W¸ë_Fx3ÄVž,ðÝž¡g!t–?ùiòº?÷Y{¯œôêwZ=éLcXC$Ë»îmí»«åí^—ð‹R»Ónå°“Oxt»žÍŠžl£†mƒ*®¾Úöã8Eò#À«MÉsŸRø_W]gKI ýô$Ÿ__ƶw òÿ ëGEÖœŸ³ÍòH¾ŸÝ5êƒ ŽEv­UŽ!ÊäÖuü!ƒ/µimÏQ¹^9ì1\U—.¦ôž§âË?;HœcsEóŠùã–¿çî‚Ó#}1¶¾ØÕ­AGR>Vm¯Ž~9iÿgÔ<<ioóþíqÃãG¥ ™àšÝÆÖQþ÷zóízëÌÔöª&ãüUØj“nvÉ®òf›TŸ`{m{tŽ*»–Ü\3\N~Bx_â5V}öîV?™W«f»7Ãñ[Å·‚$E„M»{Ö΋á}!‚‹‹H¶\&✳²ÿ³–àµ)âa%„œÑä›K#Iï»u_°¸HÎ$·f½wQøO¡ÜXKxòI£DFäe“zÿßô®Äÿ ïô8RæÂåu»'ÿ––ÈÞj¾GÝ©Xšu4)ájÐ÷ís”»š9®YÐS[žÑgÕ.Ò(†ïâ>Ãø«Ùc›÷{6;ŸJöï…ÞÙ-Ëlº”áž&?.ÃXbë*TΜ ^§1£ðª<;®$ŸgI[?#lù‘þí{‚B¢\Iqþ‘rí7›ü[ù[þÿ³W:~º§À¨ûJ+ü¼×Q¯x¢O: †‰Óç‹;mV¯”«9U|Ç×Ó§hzÇŽ#¾[«håH€¢_½½GÞf¯?×dGçÎÓ'Þfÿyª[›‰u 0÷%ômû—ýš”-½¾šØ!-Ôm-ŠË¶³–;\·ÛX O,[¸ù¤’ƒ1µU¸’WÞÛåŠùªzÕÍ Ã÷šô±GFwr¸EûÍWáµK«•BÞW«c£WÔ¿ 4» ÀSê–еàO;Ÿ•¶gœnÝYâq>Âé ¶©yly‡qðîþ±óD;^]¬ÿ0ùYà5ë¶Ÿ´ËÆ··‚çv£²Z°Ý°¹Ÿßåí×å¬;oÜÞMqxg£Û’óß7¶6×›ü4Òì®>LÕå&©?Æi›H»¿¹7'›k¹Ñ¶7ñmgçýœnæ©~Òv¦oŠzÚ[-½£­¹xòʬ>_½þöêí?cÿƒwž,Öï×Â0·þÒÐQÊZêò£ù°Fãî¿Þ;2ß)\ãæûÊ»W³ñˆ¯<y"ìk«´s§Íãïtè[ò®óXºðG‡õ2Mf-Fuuóüùk6ÕT;rÿuq¹¿ô,טè ѯ¼VºeÞ£q>›x]--®Ûc&~f!·dŽß3o½^Mzww±ëЩËvF\~>]YufâŸwÍò¶í¿-d‰ÖvwÑAqÉ*ìû»på›wËü5‘ñ×À·þÕþПìé¾_>8úáWÇ_â÷¯7 Üÿf˨ÞO5¼NwÙ_[~úÊVùY¿÷vlšº)á#(s·¡“Å8»[SвüGyfîM} ®æßÇËœ5xOÁ»S…§'ñ(þ•ï6#þîÔÝþïšó¬\ß36´Èüë™Èù¶ü¿Ù óèUè>±kM<“Õðû uýs\¿„ôÖ6°3§ïdþúÕß uÊBŸuF+ª„:ž~"v÷I,¡çyü*ñ¦¢ˆÔQÌÄ£ñ¯nöP<¦ùØ£-Ø×„|Lñ}½ö»™íaŽFIµ\ÂåY¿¸×æ®—ö‡ñG‰¼7ðîé|§É¨ø†å•"Æè¡Ïï¤û~Uÿi×Ò¼xOÆ´ø§¸h´m.ôìžÖð:j-_•å]¼ín‰þ×5çã&¬©·êzx(Zóêaêž.ñ ‘6¯ iÖ7Zv©}ö[8!UiU7mÛ÷þVn[¥]ÿ„‚/ú|iÿ×ÿ‹®¯áÏ‚ôÏ„°¥¦¸ng{hD°Ëy0}ã•×åPK|¼eeÂ}cÿA+Ÿü¯%%&Þßê94••ÌÂñøò%·{òÚu¦s¶g·—qÅŽÍüE¾j¾ðøëÃz|Wo¥éI-¸[{;HË~ûŸºŽW÷Ÿ(ûÉúVÿÁM-?ÁÑm¢ûEä>o”Øv V´æðg47šXK‹<%­ÓÈ­òîùB±á>jî•NWcŽ+š7:½RmsE·¹’k;§ï s¹¢â]Ýþjôßëÿm´û íþ‘ùsüiÿÖ¯+Ò¼+ƒâžX"‘ês-µÁ•wîç§û«ZÑ\ËauĤ‘œ‚+Ò¥W]EZ^ÍØöÚ§w9þÁªÚ¹ˆ4ô¹‹å=$NèÕ¤ë¹q]5#ÏùYÎjP–R>ës_+~Óz?ÙmçÔîÌOÔgÿejúâþÑçÓŠðOÚ/C:‡ÃÍmðí¡7ßO½úW“ðÍ3Ö¤ù‘ùíyp$Ÿ…á¾ZÙм7ok]Ék©¶±UÜ»Wý¯›®îÕ—nVÉ/峉·|¨².æ ¼¿ìÿõëu.>Ñ»¹yÖ3þ®1±bv³{×]ZhšR¦¾)M.y™$ùSò_»°íáW¦?øšì¼3à{«mR÷ËžvÓí~Ѳ2kgû¿7 ÿ²ÕÏYØx‚ú+)fk²SlsnAýÝ蟪״Ûü)·ð_‡µB×1j6·VÞSÆÇìë0º7•åU­ÉîËsÔ„SÔð¤ðlž Õn¤¼×}rÂÑ5aá©mÆÑiü%_¦ÿï&Ñ÷©‰gol—^ý—í)û¹`%%vÎÝŒ¡ºîÝò×`—Þ'ÓáÔm†Ÿáë ‹¨VÊëÄ@±^Í]ªÎá¾}¡vçoðüÕë ¾iÞ*5Ïök6§ÆÞMåÌ;æ]ß3ª/O=þ•›œ¿Ëúþ™œ_'3™òÇÄO‚:¯‡|;Œa±ký8šy³ÆOñ·Ê¹MßÅVü!$’CZ~žSR‹ïéìY:ýÒ¹ìÛ·|Õõ‡í‘â!áo…Rév¡!KÅ0é3­ä$R¢‘…aón¨|yðÿTð.¢ÖZœB(ä,ÐKÆ×_èÙ­_êRÙ¬WvR}µÈÿU!•…z¥ÏOÜÙ‘O–ÞÜõ¯xŠ}?FkInþÏqÿ,eX[å>œ/¥|¿ñÅ×úµãÄo%UÞÙEOâ¯Cñ·ÄëÝaÞݤóÚN|ØNø¿½óé^E§Ü__Î#€Ëƒò?÷›ÓñíZàhò¾yœ¸Ú¾å fhÌó^En˜m‡qóS^€ºµ–i‡Â3uÿ9¯9±·¼·¼œÇ‰÷¶åqòÿ»]M†3?™çnû<·ûÕñãÌ7ìmí·î×Ù³¾“<~·žól+û¦=Aù•¶ýXÿÀ}«ÆÌ†™êऽ£,xsAºÔ4”°·¸yw&ɤޫü-µ¹ÛÆJæ¼ðÏTÓüA¨ëf—¶ëÎ_&òñ¡µ?Ç6ó¸¹Âü¿6;·¡ë|sö-a.æÂå…•„mî—'j»É¯ñ¯¼ck+YiÞ§k§[# ±ŠFH”ueTè×›‚•çcÑÅécÐn4=?Áo“SûÿkMѶÏ9‹¹[Õ½øªùïâ'Å/ÿlK§Ûê7vú\!R6A*uûÚ®ÿáνâ\ϧj¯s²(ÕöÜŸpíUùs÷Zµ4¯Ù6ãÆš¯Ú.uˆ­ü×/$VÑü‘§ðáÕîBtiTr¨õ<ÌeIâé(Ñ1ø{}oâHu(õ7_>(LÑÉ!Ú¼ïó÷±÷«’ñНçöW°ðž©¶ñT’é· åM$Vªî‡«6íÊ1ÿªZ쟥jš­ÃÛx®k8ˆÛÚí73?àÊ«Ž#í¹çÔÃâ4­±cáwĨ¾+h+¢k¿ü$1DzÞEÚís°g{)ïü>õÌüEð>¡¦ÜNæ)­të±²utQHãîîÇþís^%øo¯üñ5†«§ÞGtÉ MÚ£2ÅÔ6õí»rÿß[~m¹¯S‹ãvñD–À"A¨ÜǸÙÜüÊ®?…Xõ­e%ì¥í(üð5§/h¹+|kñ<ÛJÓt«¯ 6Ÿ­¤Oul]£ßwåK³øJ¬›Aî1ÿv¼³Z† åò¾{W£e™_#vÞÝ>•ÖüGºûE½œwKn6<ŸßÇõ^•Á±Í.É_b7ñãÿB®ì5>TçÜáÅÔæ’§mŠ.ÃÌbS]ƒ!7ýºãjüµ{YpqýàË÷XWgð—O7Þ)·nÁV®º¯÷mžuÕT™÷‡Âk]ºDª~Z÷ O3:Çšgû¿ìóW–|:Ò;›j¹ÚÎwŸ¦ï–½çÁÚq3I&Ï•BÄŸÍ«æß½+#Þ”¹N·E±[dó1òÆ6­nZÅå¦OÞ5µ¨{/êjѯoG—Þg…V|ìlŽ#BMAÝi€:“P¼†æ`Ü+—øâÙ<;¬Gn"—s]Ïs8‰!ú–<~usªµ›Ù NMAnÏŸ>9üE¹¿ñä ¥XÛIH¶ëª´Ó>È󹇓Þ;¾`ÜzUã&¹¤§‘âuYä-¯g?•ÏáÜ¥r‡ýæ«ñõž¹âÙìg‰tÕw™íí¤Û²eNæÏ3nÚÊßÅ[7z=”2^i‘‰ ûTåYŸæù+µGffû­ÿ}WÍW©Ï'̵>ž•>D¢¶!ñ?‹V÷²E`mµ++Kß³JoÝ6z¼«+kþÍþ€—?ø ÿüUtQü3Õ<+ª>§¢Ak¨ÄÑùW6x… üÛGGú›íÚ§ý Z?ýôøšÂ3’Vc’…ô<«FÖµ_ ßÛÞiV2ËÃtÏsEþ×ÌAÿuy¯FOhž>Ó'Ñ/î-^âdØ-¼ÿ5ƒ¹±ÀÚvüµÃØxºïÇ’ÛÛYhcWµ‡æ#tX˜m]²å”>ãócÑkÐl|7a£é‘Z}’Ö)Øï2Åj©óónã’økÓªûœpK윭ÏÃÿ‰‹e‡ü,ÿ>ÊPÊ%û}£aûß?Ì~•é>iôý:ßMÔ'šêêÕ ÝÈUkŒ/ßâa¬E¦Â±\ɵî%¼W%æ>‰ûê³uZMòê;”ŽÞêY?sY·6>UúUSÄ8O]½…L:šÓS³Ðµ™|1©ý¡I{wÀ–/Qþ+^»iy ý´wò anV^õáºmçöÅž÷ŒÁ:ü²A'ÞñÑ« ð‡ŠÏ‡.¾ÏrÙÓå<ÿÓ#ëô¯jœÏQ±ê’ÂMp~3ÑÆ©¦^Z öˆ^-¿U®ù$Y2Êà Žõ›¬iÆæ-ñýõç–"2扵 œ’?/^êïó_Ç-¼oxòwŠB6ðÛ›¯û[EeÀÖúxK‹Ä‘’Ü. ‰öoN[o_š½wã'„í¼+ño\{‹q;IßY«Ÿ—s‘Uvóó«WxŠkØu[;K´’òÎÎéf¾Š7*ªÛ½Êü¼³\ NO”÷%ecм'ñÓBðKÅ4Ôó ‚òíeÏEa¿;¼Ø¯Z´øÝ¡kZÇ•­ö ¯Ù%`è…Æß™™vÿï—ýšù¿Sðî•càëø"ñyñ½íýÒ5”PDûm×ý­ý·t^ËZ÷ÞÔõ++ #C³–õí6¸»”¬P¡6Ä«µrаä¤ß=¾ÿÏP‹¨ÓOúûyø¥ðý5¯[Ühš…Ì:^Ï*4ŠOÝr۲ͻŸîío­z¾#èþðͦ£7úBBŒmEfFÚ¿.ÝÝsÒ¾]Ѿ$k bHõ[>›s¹'Ó.Jìuyxéÿ¯>×~%A®xž]VÞ9–/0EÝ܈?ºÝK·MÛqµŸ²œ´†ÈÕrÿËÓ׿j?Œ·u*ÍÞÛK´Ú<Œ.é_ø˜¿Í¿-q? äÓæ’Å®ì&ÕµŽÔžy$TµTo—o÷øùv«+ËÞòï\¹óà¶wŽ'fDÎç•Éû¾ÿʽWñ¶›ö;› ¥¤w|¿hI‰ÜÊÿøî>_ᮊ‘öt­ÔªiJ¤yv=£]ø•q¡Ü¿‘içÊØMÌ>]¸ö®ÇáÅ®§u¦Ax¶› ¹vù˜îþ/ᯟ­mî$e¹7ry²? Ķõ?3 ¾¿7Þ¯£üâ Á–þRôaòo}¸?î×ÏT‚G²ÛKÝ1¾3xÃÅÞžÛSŠ?µ,ea¹dùâcþ×ûÕñoÃ߆úæµâ±Îò£˜Äó¯Ý 7}ìtÿWÖÿþ&Iq ®ž’C=ÅÎÐì¨Ê²ô_YÛéWéQZÅoj?},¨Îö»’Þµ×B´©Siu9%C§-Ðxࡦ5iEû7úå‘77+ò”n ýíÛkªñµðóá]£Àúe¢´Ðí0-ª»6åÜ=jµçŠ.a†ÊæS`–¦qkevb¿ÅþëWœk·Ú.g߬¹Úï$(ÿÝÚ7:ÿwîÔGžoR½œNOÅÿ4 ÿ»ðÕ­”ûÒ'W—-ó|£îzªi<=ªLéw¢En’'”Z/™JmUÃ)ÛýÕù¿Ù­Ë¿ø?Ä×6ñj’Üéۆ϶XÆ(Fï›rÿìÕBÿáá=cìž)ÏÑ/fòl5í*à2¦X2³ãÂç¿Ú¯J¤ÖÚœ“” ÷ÐëtOƒ~ñ†œ—z\ó5ÔroIâ}­ îÜ»Tpvÿvº7ðŸŠôÛˈžHuK?¼9¦ñ·îËò±Çû­ÍrS~Îþ%ð=²kž ñ(×4ÒK¤I!…¶r¹çÛo5Üü9ø˜ž$[j7†[Ä)â.ðÃæeõ;Ý®j¼Û§tkOÞò8;?Ë©k𾙬W#Í´f]â3•ÜûxûÍé÷«Ò#Ò³|=z~ÕzŸ?žÈÉû«ÜãîûÔÞ>øý›4Ze¤iÕÊ2™v|À/š¼îþèè¦ÔÎé{·vÏ1Õ—våÚÌ¿ð*ã¼›;c .n=1´ç–÷Q¸kË—Ýæ\‘3VÉ®JÇÆÚuŽ«qÛ"‘Êù¹9ûÛ½*‡‰µÝWÇþ%s0‡ÈÙ³j H­/ʨƒþÜwj§âÛ點[4h‘;OÌÍüE½?Ý£•_S^go_xˆxÛ|VRÜÛª Í,[Yݳµ•—¯ü µ¡ŽÛû&+ŠYåDÛ%Õͺ«1ûß•yf…co¦ßÅ=œðÀîë-ÉpÎ?ØÇü ïWy¦x˜i²´2íç}Ë£.ßî…ïü_y«¥û«Ü9¤ýã2ãÀ~ñtH—úoöuêˆ+e¾o¿éóv¸Ï~Í7‘ù·z2B–IÖWŸåÿf½W[ð¸¼y~ÅýŽHUQb*ª[wßç·Ê¿‚ÖwÄl|(}#JI¢×-NË¿+rü¨Ÿ3{ŠÞzšE3’­|V>g¸ð^¡k¯Yé~dSÝLûCAʯÍ_ ? ¼"øsN†æVß në÷|ÕùYýõ_ |cŽ`}@G;`¨Šóæù¿½´÷¯Ñ‡S´‹WÞ™G–Ù]«þÏ©fzA™áRåsZÚ¶¤ØÙÏ+ÃýŸ:É÷G?y«æOŒ4-Qû™§ÛNð†UÂò8þïLW¬|yñ´>ð¤¾T¡gØ_æÜûÅ}ûWÃ?gÔþ#xÎËN²Ž[«‹Ë±¬I¹†OÌÛzà EKßžÇUIòFËs¯·ø™yqy*æYo'Ã$xÝ›¾fË|ÇojÑ}KÅ–ƒå¦£6—+ùjû¥“bnÙ´p>îìõ¯A×>Çà­Kû6-­n6ª&¡½—yØåßýYÏ̓òüß/Þ­-ÁñxžOÛ=–†û"’yʘŒ©÷w¶î¾Ý½»V®µ7w·ÞKR‚WêržøN—R¸ií.Ó«õóæÿ{ëN·¼Žßç<Ÿt«Êë]/‰¼7-ÓÇ%»C8;ÞvƒþÍs3Y•í_K Âq>Z¥:”å©ZYŒƒü½«Ø?fýï¼Bóãr£*î¯òNq_YþÊ‘m–ì§Þ;Çõ†*\”ËÂ'*œÒè}‰à=rÀë·-èÞ5îžÓ>Ëk¸Ž¬Oã\WÃß ºÚ@H*¯†,ÝtüIÿÐkÕ!‰bŒ"Œ(í^~‹›ç{ªÖ\‘ÐU É÷)ûßáN¾¼òj¬?¥S·ˆ¹wþõz5'öÅNi’µÄZUœ·3¨£¯­|£âŸ‹ú ŸŒ¦ÒµÛØõMvId³dâ|˸ ·ïWÐÚæ¿oz¬«"½€Är¤÷ Ÿéɨé°ïÕ-©‰[buüO·½|'k¨'„ežÒW1j‹3»ùð‡Ë óßÿeÿf¿UQÒxÕ‘ƒ£ †SÁ¯ž?iÙFÃâÔM¬h¥4ß',@Ú—Cý¯Gÿkóõ®Z¸u~e±éa±|«’gÇ šç^×m³4ˆçºË+Ë"îMÁ~f\×Ó­áÝÀ?>Ùâ8Ò)m£k©çŽvW2ÌË´/¾eùz×Ë6^=×¾ \\i1øtéú½£åšéŽr?‹a^­\ŸŒ~'x·â„+©x"åÎÑó;wé\2ÂÔ©+ËcÑU¡ #¹Ïøÿ\Ô~%x‹QÔ~ÆlíÞ}Éu€ûÝ>ïÍPhþžà"\Ÿ+ï†ge7Ý­;m4Âÿé4 ƒg”¡› üMþÍwþðý͈Šâ}í,€¹ûNæUS÷vÿÀk¢¥hч$ ¥CÚKžz±¾𠦽á ¶ýê$gÊ•‰ãè?ÝÛ]T^ {‹”»…{s¸>UX÷¶Ö§‡áŠÍOÞ*Ê»DŠwa¿‡üÿµO:¤ê™ F®ví‘~óW‡R¬æ÷=ÚtTQ8[;[V”ÛÄŠSïÀw±ÿy{è|)âƒghö‘ܤ:Ý"\~ë’ÑXµ¶¤á¢Td\Äè7«gøéüUVÇ6soÿtÃvæûßþºäqG^êĺgƒïþ!kw’\[Ë-•›»Ra (þ-í×îÖ·‡­í!ÚbÓÊý–Ó{:¢ÚÒ7Ëœµgxzòæ?êÚ,EÖ+Ɇÿß~~÷N¿Ã÷ºÕSÕÃ÷2ÙÝÉ$Jû—t{ñ¶º”U´9.îî\½¸‚é%¸‘<»BïÚËŠãüAn’#Oou,òù?î6Y›ý•[×-ÍŽµcμKâ+=sA²Ö<¡o{än}ß+&>ò²žœ×á?[ñuú,PÛënÔ_éQxŸãÖ§ñróû:ÒÚÎÖÊg #I3ÿ½¸¯ðv×y¦ê០D|;{­¬;~Ý©Oò+ËÊ‘g’mrÔ¤é¾Cº•^xs³"ÃGñ„_m’+vdÜZY>Föã»ý ½JÖKMzÇÊDyw4Ì÷Rò%A¸(Eïóû5ç—qØßMk*/"ý©"l²;¹ûë¤üÛ¸ä÷omÛíFêUŽ[5·’(ü™2ÞLˆÿÞR¼ÿb¥ÓKqûG=…ÖþézLö·¦ò{…·92,<í˜/§®{S-ü/áo²Dñë²Ï{^ÆÅ #æVsÇÿZ™6›,0¥”w_%÷&évôù°½þ^ÕjM<ê:– '…˜e#‘ÿ† ®Ý¼íÛVž–%§Üäü1ãǵÔ%ðôSÂñÞLìw«?÷»Šä¯õ)ôŸÞÝïx ¹ùUwòFåôù›þù®ûHð^™ªi¶÷š^ÙZÅ<§VvH?ºÝÇñæ¸ÿY¿Ú ½·¼[{¥AæG¹[ªŸ§ûÕ•ýûµî\Áø%cg‹ïg“ý"𧔌ñïàÿç»WÚv‹áëw’#j„mÛ'ÞûµàŸ ¬lÿá!{»kh ³Š5qs{w<Ÿ3|Þ¿Ò½Ç:ÄPé¶öâPˆSi’Gm¿÷ÖÚËS™P§±óÿí9âéº{ˆícQqMæùrŸ™€ÇÈfþït®º2^Æ+¡Ï‹ƒæ|¦ŸÇmKðU„w×2iv±»:óP”íAü;UNKŸáúWÈÚ¯ío¨j2éðèI Ç;bå!•“kü»öü¸ ýí¼ òÚ‹Åž!øãýKQ¼¹š{ i¶ClÍò[ägvÛ»¿-y¯€ml¦ñž›mâ]fãÚ3÷WÞK»ˆÂîÂ*«¾Ý«Û,§îשOM'7ýÁ<ç‹+S’ùŸvøÀgXH¯t}iÉË<";D¤Qt?7?68_Zöû ŸG‡NÕÖ)n‘wù¬èƒ?óÑï¡_/^þØšMƃq¡øKº²²· K8ÚòŒY‡û¿Zàõ¿x³K¼·Ó’G½ÔoaØ'YÞVeæURØù—iùqÿþ2¥ôžŒíNeÍÓÈí~0i÷±ýŸxûÑÚÛ¹6Êmùx ¹ErVwƒOû›Íÿ>ph‘t3µU›¦[ø½ª}SÆ#º·°³××F—I@,’=Šý¶ ÃzôaÅjKà˜4EßQ\2œ@É Ì›Íµ˜7’jÚ»ú«ÉžoñAŠ=I/ãy÷Ãʸh¤èûwn¯XÒ1äɽ޾_ûæ¾£ñÜ6­¡ÎòA%½º|ï$£r£nþÝŸâoZùsVŒjK$oæ¡vdãoË^¶nq±åㆥ[m=æ¾æÞêkô{öYøg5æ‘gÂc·^gÇv¾bý˜g}[ãOŠme¶­ô;'ß}©ÊŸ"7÷ûÏíÿ}Wê·…<+§x3E·ÒôÈ6°.Ð?‰½ÉîkiBX‰Û¢<þu‡†Ÿ4ì¬ã±"‰v¢ŠKëÕ´OW=›}~¶QäüÎz-aniæ2ÈrMuJJšäÅ9¾iźi 6I=X׌þÑ_b:­à}&âxõ«ÛOßM †.m¬¸êÜçnkÔµXYÿ£B ÜH>ìg”_]Ý¥yö½§Ù¯ÙÓV̺mºþîùsæÁê]»×“Wì´[ž­,?´÷žÇ†þÍ_F™mkð×UÑ®—V±§™#³,È9ó2Gñn^?Ú®ÓÇ~Öá0—XÓîê1 Åý˜Î­Á´«ïRÙÏÌܯ÷ksÄŸ oVßFÕ´]KûbßM˜]Ƭ#ó¦Œðê²íÆNÇ®Ñ[÷úJkQi·p\Iò¾øRê/»ýá^\¥fäz‘JÈòý7XÐôÛ *ÁæM] ."Ó­äÞ"%¹enÊÜ ÜÖäÞ ³ÕRY>Ñq1M÷,Ìò›,Ãî¿Ä¸¬Ûߨxc÷ZŒVö—·W{ü»k¦·Ê¼Ÿ}æÈ?ïZ©jMý›©6¦úíÆ“´d†¼ŸÎ´v:ʉÐû³íÎê\­ì_2Zgü*_jCí:UÍ„WöÅ¡šv|ÛˆåY÷nÈõÍsÿð£µúu/ûöÿüUnØx¹5-6ßX²½¶—Ï ÞF<¥™GÞëþïË]/ü%·Ÿóï'ýüOþ"³æ¶Å%3Îm/-¤‚ÂÏN–9g¹Fd“c2ö×ýÚÜð÷†ïmlb³3Ú}•4Ð(ef÷ßÓýªðÏ x›Xð·e¥Gy'ˆ`¸“÷–ÍóKcÞމݺÃì~"µ»’ÚÞÿ—ï< ïžÚ)Ùdþ$lt&½Z”ù=æÓ•þF¡ðýƃ|ϤK+­ÉÜËw#:}Ý¹ÝØmü÷UßøDu;ëû9m/—om—Ú°lI²»YU7gþXÞø¤|a çØ­Æ“qi2Å5òoxðªýÜýÚô½#\°×-’ââxå–2ÑnTÛå?ÝeÝY4׺5#…ñG†^ù®¯?³âÕî¶ö3ɲ)‡æ]¸Ÿö«‰ð—Ž<5á]!¬5].O Ü[²ùö7a‘?ÅÞÏ?ݯu½ºÓìL^t1WÀÞ~ç÷Híšñ{ÂzV­ãIuË{ø®Íu'˜¬‘oÛò§|íþïMÕ’Œ¬Í¾$nø âV‰âļUÛÏoi1GYã18ÂûO;ûÕÙL¡—)ó+v¾gø‰ñSHð?ŠÖM Á<øö¤×1²á¸ÏÔŠöÏøÚËÄÚ5¾§a&û)¾üm÷­ßøƒW½Mº¹àÖ‡³C𿌮<57Ùæ 6žy)Ÿ™=×ÿ‰¯X°Ô-õ;T¹µ•fÆU–¼1£"º|ÊGÕ­_¾ðÅÖûS¹þòÙþãÿ«Œœ}Ó'/Æ€žøÅ`¥´Õa·ÔíÔy©ì¾¿ìŸÃðÏÄ?€ºßÃ{Ø-u}.=ˆÎ«k#º^ îSóp¾„pô*ýðçŠl¼EoºÙ2Þ@ççOñõkYÑ,hÏùþ¯›­ ‘Ÿ¾}~­)Âôµ4/õ+8á_/*ŠŒmóo_â®RþøÝ\1L¬Jv¤÷]÷½+¯¼Ò^Öɤ–ÏÍD¢fU?ΠÑü#{¬irÜIo"Zð:'˳?®ÞµËuÎÕvkCàÛ=?Á‹,¤}±ÎäÜÿ0ãîæ¼ÞâK»‹ ¨<‰ÙØ­‹nFõ¯CñŽŸsk4ði’‹: ªSóáÎÚáƒo‹“k›w tº‹4ÿ\ÁçÆQ ›aƒ.ÄÛ÷º“»oþZÆÑî'Õ¼'â9Ö9õk‰•Íôc|¦1ü+è?Ýë\æá±¨i„“ïžÞaµ’eW)÷²ËÔ¼=¿ïšôâùï~‡–ãc£Ò¯'¾ÔTÅŸ²÷U+¹ÜüÛY{šÙ¸Ö.­YàÙµ—sL¬‹¹?Ù_JÈð{j·lú}‘¼òœ`Tܪÿuw|½~eþ/ᮦãG×-áSyQ[&å™W#ržw|Û–¹ÝCXƒRø¨Ú•¤_eÓvZ[¤x•d`¿4Œ½Ã6ïüv¯|jÕ´…Ó`Ò-ü©or&šHþuLòɸîÁÝ»oþ=^‚¥mO5»Æm½ oƒž ºñåà¶ín£Œ<,›í÷nÚÛÛø ›æ¯¤¯4™.¼0š\VV×—çí, ÛBíû©»·ËŸ—Œ¯Ý¯'øe!›Ãqi–úsZÅuy$_ùbûñ½~_ŸïnÛÛþù¯H™­-Êéž·$Xt—V:M†Ý…n 3+}ìà·ûUω—¾o†… ‘~ÓÃúŽŸkaqw¯ñÉ>ådávË»×ÙºnZ–‰o,oç¹¶g¼]¹¹wܲ!oá^§i­HuOíë¸$¹»¾µ‚XZ[E–ßÎLäíM½J|­÷›‚­YÚ­ÆŸusÙ.ùG•q·ï+ ^9*ÿãÕÅêv£;ûsNk&‚O2ßy°Ê 6ÿï}ïûëï KhÁl+ËÁf—&7MØ_´Cænû½‡ü ¹É5‹MCíyrZ¤ÈYàc¹‘°ˆßUKT¼·“Nk9d›|(ˆv¾ý˜ÂasþÎïj¤³[Âþ ´ðߎü[áÍâ+i¦o"%?9ùq•öý~lýÚá9hé†<3â 1Û¢²H®.Ô*<ÛåÝógîþ›Ep>)ñZ–‘§l÷ÂFî`ÿ ¯_½»ïQì¹­4©¼Ú|&ñeüz}¥°Ÿmº—T‰AÞÉ»3?ÙV_î×·x²ÞëRÐl®?³îï6ád‡nì}ïý 俱„ì¼M®kz¥Á–d‚o*=ÈŒyfgñ…Ç—uo¢ažÑs+¶P2>ï÷þÕÙxSö—àÖ“Yéé«J‰y!k›Û·ªÊ¨­0nþç8ùš¼ûÇ–¨º?Š­.~Ky–µiX³¾1ÖöhÙ[þßÝ®LUu^¥žÛ}úØj·»º8¯‹“xKKÐõ-+ÂZ^£ký¥z5 F}Jtdø–(T3_÷¾ítÿüyªÜxm§óc¹ºµÛj œ34ˆ7*ºçºýÖÿukÂeׯíîâ´B`P6%´²o‰~_›ïûüد\øKáû5ðÂÙ oî/n¤ù"µ·ÞÈä}ÄîwzQRŒhÒäZúê*õ9™½ã(ï5­è[„³–h]æŠyÒl•—{r[æ¬ÿÙ×ö4Õþ4êVÚή·:/‚ãÛºéÓdׄ}ä„w=äv’jWì-4ø繓•WÛåÉn´u­rÏúd÷—“ˆ „oyøÄú/zñ]_BÖ~(xªóT‡U›G¶Ò¿Ð ‚8Õܪ<­»øOÌ«ÿ®*’Tàê3²”}¤Ô<gcâÍYuGš=fHÖâ{–¿u¸~ñdG€~UŠ¢º­OL¼ݺ½ô–Vw!ŒîI©'§Ë_7øÿÅžðïÄøí|c{o«éþÆpYf]ÊΊá_½òäW¹YßiÞ8ðյ櫵ԱÚùOåȪó ?s~x=9¯›ªê;Ê{ŸEÅ[‘èt>ºÕ´Hîtøì®fdº´Ž†6MÜÈ0ß_”VÍ¿Žô¹õ»2ç_ÛìÞ»q÷ë…Ð|[∼Cý‰8¶ÒþE»±kÄYšòÄ©ò²üÈÌ¿2õ ¿-mkº™ÕõItËÛuÐõ]Šl5U;–gs§û-þÕ5ªÑ™5®¨Âñ׃ïüa©7ö¬&6Úo6Ê[7>toÿ|ã{ïU«‡¶š…¥ÓÞ[¥þ`ùÞxWʹR|ËÐñÔUÝ+R»þÌõY.­õ$M­nñ¾Ä˜pOÝùƒcrš‹X¹Ô5o ê/mauoy ;ˆ˜Ã3Œ€ã¿µo ]êKVG‰ ÇáÙ¯í¼'w§X[ù³\hÑßÂÕá_ð”|AÿŸýwÿ"ñ5õƒuf[ÞG§G:„T\Áå+*6ß–^ÿì×w¿Pÿ jßôÿâ+e^1Ñ«‹ÙË£±åÞ†/iÐj¶IåA,-Ëk´Á|èÌy?¥wv2骖zqû’¹–Aµ¦Só(eõÿj¹Ý7À¦H­õP>¿1«‘7CsòÿËTeÇýóÍuÚ7‰.þÒú~¢mì(ü׉eM¿ßFë±¾o½Òº]žÇ [êyÏÄíK]ð^«ý´–·öé-Âìû<¿ÞܬwgnÖ®@ñ¦‡ö­GHžÔHU'ŠIÄ(%þgù†ÝþíhüBðM‡<1{§½¼HÓ"²K#üÙÈ|Õç‘iú?ƒb·²}>[+XáKqb²2£6²Óñ}îjn¥ u5¶¼ÝßPñi­÷Ëv·ŽC}–Íí=Ów÷»®OÇ~4³øcá‹«›m" &{ÏK8{‡s÷“¨Îïîçï}Ú¬þ.´Òtf³Ñï-<‘nòÖÄ%Ì·ü'jr¿ð, |õñOÄZç‰5e¸¼=¯Gæº5ÄÄ »Ü…þ/ʪyûÛêòBç ¬kz‡Ùãhe³I¸|«ŸáZô?…ÿ5‡~ im’Iô²ª·vkòùŸí/º×¤]\Bðo·†V¹…¼µ_õ¨²üËÛîÕ),nuDžt’6ºC¶;h²ß'­{¯Ýg‘nuÜý ðŸ‹,5í2-SH¸[Í:a»lg£ÐÿxWFÔ[â;—î×Âþ j™žÑÞ÷N•ÿÒ,Øýÿï:ÿtר>ñ%Ÿˆ¬"Õ4«µ–'ý?¼Ž½ŠÖrV1³êtbIlçImåx'N’!ù«½ð¿Å[y±m­‘k(éu÷Q¿ÞôþUÁAyöèÈò§_½ñõê)ìþF›—îýk;¸jŠ÷g£>…Id„:0È äà¼yðc@ñ¹’æH–¦ÈÉöËuÃ6¾?‹ùû× áÿj¾tRyö ÷ì¥[{ùáÑšIV=²í*~NWåoáæ¼ØP”_,ÏzXŠs\Ôä{'ìéá½"MFâ-B¦ŒÄ©öpw*“ÛÞ¼çãÃK?„< ¯w¤_$’¡]¡Ó9ùûß7þËU¾k×¾¼²óoB¢¾çUÝ–ÿ€Ž¿ì⾊þ0¹[Ë»³? XÕOú”ù¾ë}Y¸©æä–¤{.}QÃh’%æ‚’X^}žtw{‹gÜ»—vä(ǃ÷¶ã¯Ë[0é·ºziÎéqÆ÷‚æ1+u߯kÎ/­ïü?t¥ ÞT2nóâù>aßÿÙ«v¿5–[yç… ”íÃnóò×K‹—½4ù}Ùˆ¸’I¥yÚFá@n ?Ùÿ•p?¼A…lt˜4yåMFá%šîMß.ÒWjíïük]›âˆ®]º"Ë’›WkoÇ÷½ëF?ø{ÅZl·ܬº‹£yq ]±õeëó-:5=KÍV>Ò›„^§ÏZWˆ¯ô›Eû“~õg|nþ/•k°ðÇ€õO:ÞêH4ÓóÜ3mÇð¯®Ý­^±á/ ÙX¦¥ZDz¶›$>S¤§jðêݺmþ.µjmòÕ"¶‚àA€ªèȲ¤K»®ÍØ#oðÿ½]µ±ȬÎJ8I}·tZ¼“Hð<]„–BÿË^˜î¼›‹`~mÿw”û¹GR?¥û³ÜY,÷vRO¹Oï6Õ[¿ñ}Þ>jòOŠÚ=ÝŽ ·7wÿÚ—åt!b Ÿ. ~÷ Ç5Òx3ƇO°H%v·ƒËmñNY¾pßÃóp~Zå«ïEM4ââÚgi©-ݬ-=¸‘?†Ès'?u—o÷»Õu·Õ&…n­Ñü«¹.qû§q‘Ûý¯»óUm7R¶Ô5-ö×Jì7H¨§æMÁyÿÐݯH¶ñUžf—$S ÈJO§Ü¢¼,øÎæ^á·tn{VhÞÇ’èšƒÙø‡N’XÊB—HÆ5Ú­Ãco=yUÛ«òêæÔ9Ô³¸tIB·Ý‘_k)F™‹ÿÕ^®i¶Ói³ÙËgl÷ómºŽO´ èÛw}Ï”ƒýåV?ï|¤/ͤÜÿhYG%íä·*Ý*Cò§-·cmÁù™y­c8³ &Pøc¡ÛüN׭쵋©mìþÄÖñ³G÷˜mÚ¾èþ÷÷«Åïô;7ÅPxrí&¸Ho¥‡ìÏ&Çò„¥~o— µ¶¾–? þ[êéwuguj&¸†E@ë.S”}=ù"¼‹À&½kq®ÞÁ ·—2ÜÁ½'ËneÞ~îÕfÿ¾j£W÷nkoøs>_ÞrŸOüÐ`ð΄–¶VÑZÀèZÛm¤¯ñ±äŸöš½£M“̇ç\ ¥WµxßÃÛ°Û[°•™Z1ó$‡Ž>UÝ÷ïšõý;kD¾^ â¼gtΪº•u›F™$ ¶áÓ·?Ò¾wø«à”ñ$×ÜÛ_¸!÷3»šújöÜIÖíü?Þ>õÀx‡Ã73[IĪ\¿—šæ—4%Íl<£(òÈøSÆ|Gá÷[Ë9Uu«[Œ‰·æoýwRÕ×ü3ý¯|Cá8ìµ?2òÖ/"ßÍ“ætØâÞ¼gÿ¯\ñ~‹ªé{ËŽùs+/ÍœýÕÿ€µx~±ð÷Nñ‡Š¯ncItëY6K ¸^/ïFïMßÃ^Þ ŠÕ‘ɈÃ4ïIžáiûzxa-E½Ý„³Eµá_¾9i^.Ô‘<%a=•Ŭm?ÛçŠP›Uz}ÝÁºýÕ+]ûø»ÅþEÆ‹}§ÝØ>"óç/"{ø¿ñÜ×Ô¿aOxC¨xŒ/Šõ„ù‚ÏÛHû1ü?JõiS£;:jç‡V´è¹FR·¡òÀOÙ·Æÿ.F¢ÖCMÑK®ý^ú6Ep?çë!ß/ËËWèoÂøA¦,ze§Ÿ~ê<ýFàfY°èƒÙZô¨m£·cHÕFT`éTµ=rßNù3æMýÅþµÚ°ð‹ç¨yóÄTª¹"_–T·žF‹É$ô®rûÄFñJZ±ço›Üÿ»Y•Äú̪glÄ/ ÿõ©"…Tvœª¹i… J>ô‰b„74ËíB >ÞWi#E䕾ì`wj«{ªCgm,¯ï6çgÝyW°,Ë´ß_ìô~Öµ]/K‚=ð}±ÿÒ¤ó_äši>r›„|د-Ô´OZÐn/4+8§¸×ošhlüÍŠÖvÏüY^#që¶µôŸi6h¯-œÄ`ŠÊ8û«ê[ò¯/0¨ãC«×ü¿SÔÁÓŒœçÓdt:¯|ñ¯V¾ÖeÒ-m¼f-ZÆê w'œ1×?.O`õÃxcà§ÃX^Yø¢m.ðÓhúº#«!ã+/û?2ש^kZޱàk}cÂňP¢IgªZ®òWïá_k~¹¬8<9©xþÆJ†âîÝcŠDùäS÷¶o]è?ÙÿЫÏ眗¼ÎØÅEèŽUÔ5?ø‡S:}°¿ÒîFé®™ÿ³î>mΉ¹ŠFß)ÚµÒ¯ÄèôÛMïľ0´¼Á(¹Ž°º†ˆ'¢ÿ¨5b ‹-»¶‚ÚÎ ›&[Ï’ltû›·§ñ.ïZn±£øBdÑ.u»‹+ÝR ÂçP}ñ$¹ùC|¸¾îæ­S¾èMÊRîïMiìµÖÕmÙÇÚQ HO'îm=0~jæÎ–þ¿¼iuVöKl§ÊŒ<(Ûz}ûæºÍÁZ.ŠòêZv›· Dd?;(êò*ó_øÃNñµŠé’i:Žˆ¶ÛÝØêP”o½Ñ¸­,íÌsEëÊRûŸ…ÿ‡.ÞËsæ¼ijv4 ÏÎÈ[+5y·ü#¾4ÿ¡úÃÿÑW]ñkPŸá.¥Š4Í+ûGÃóeud³uÜ?ݸÜw/FþSº¸ßøl 'þ}5ïûå¿øÕm U$¯M]|‚U"ž¯ó=kÂW“Úø·».>`­·d?Þ;¿Ú­èºeÄ1^Op–÷Vz\¯Þ‹=Ù»ö[ƒT-&’Þ:îÚ®.¢P®¾r®øÿŠ´wOâ i² ºÎä¤Ò_|ÑCò¾Í¿¼»ü@ìÝ âG ªü@–6Õÿ´-þÅuk¶Wû4++\Ú¼ð©eo}ÍÇë_8j^8M[Ä2¿Ù.µ a.ôÙKÞʆÏÚYÂìGù¾êô¯¢/~Ï6•q{ý„þ!Žo´[ê·c|®Àmdl2€>òìUÅ\ÿ„C±Ò‘Æ‘káý:hZâ{e ¬óoGÙ÷ö·Ý­hÓÒÅ:sŸSÆí¾!|?³ð”±xr ¿µh¬b6Ê ýæ\vÿz¼¾_YIrÂòâ7¹r¤ñŸ¸ßÅòž¦·>3é~º¹Ó£Ó4ñ¤ê)óãW¥>m®«þïã^Yo¡§›,7"îù‰»?ím¯F”!nc’¤ª'Ê_ЦOy;£Ü}§sF«'̈wÍè)–ºÔZ8šX 1<ˆÈ’å¶'øÖR^E¥ÛËcsÌ6î|ËVtíä¶Aq(‰í‡ÍùÞozëi±o¡ÑxGPt·ñĉŸ2W;~_ᮓÂ->Þ´ú4²\.W϶`ËÀÿzâ/¼?i7îíÃNÈw1UݼÜÕÛo ÙÙ¢{dfsÇÊv·û+Pܹ\³jÇÛ?¾$h_´u¾Ò§+*|ÒA!Û4 þ×øô®Ò `Ú•Ž÷çAÒuú×ç Ö¯ü4®…ztÛ«c±ý×þò²ú{WÓßi-âDPiš¡‡Iñ|©Ø®ý2cßý†ýj99•àbýÇižþѤʯ ‡ø–©ÜY£äeèËò²Õ¼ÛWó-g¬MÐÖ•®µÃù3§Ùgþë}ÓX4ž…¦Ñ¿¢üEÕü<<»€u{D+ßìßÅø×£ø{Ç:O‰`‰íæ1I Ȇa±ëÉßÐý*¹‡o?woF¢'p„Ï ÊL×™üBýž|ñ9Íþ™ö;©¿Ö]éÍöy_ýüpÿð kAñî«§HÑ4ßhiIùüš»+âN{µ.Uì¥ÿ¦œ¯ç[óÒª½äBZ/š ùsÄÿ±_ˆ<:ësá}RßX‰1û«Ÿô{Á¾áÿÇk‰Ô´ÏxnU³Õ´;>ß„½ÃûÊÝ>_öX×è­õ½òn‚d™}Q³Kug Ü/ñ$Ñ8Ã#®Aü+Ž®_ ºÁžÖ­-*+þ振­¦¶[) OÅÛå›$®íµæ>$þφæw$1BO•æ·ð(ÿdw¯Òÿ~Ìÿ¼bZIôU±¸9ýþ!€ø ü¿¥|ëã¿ø'Õõµèþ0‘¡ßŸ²j6ý³ÓzñÍK*OßÛÈí©™Â¢÷wó>Føoá}cÄÚÅÔÚuí²iqMûÿ=ñåùÕ6ó»w÷¿…«èÍ7Â~ÒaHô覽¼9ŽxÃ4­ü[— Ýü^ÕÔøcöUñO´ÙÚGªßº´r}‘¼ÅHÈ9UÝ´î÷Ç¥rþ ð¦µÜFûG¿´Œ&ùQìÝý•-·æÛþé®\Ss©Ù}ÇfÅGâ»ûÎü$ÑÚö1”Q)-¢}½æTO˜â¹o麕œROtrVÎVVAü;;Œ³ZÚNštøíÞ7•'•Y^F}Œ£ýÞ´ßØÿcù˜ºi“c´…·®kϳR;÷2|?¦éðîûfŸ%æ:´RDÉæªžå]¼üßv¼ŸÅ¾ƒá¾»=ö‘gu¡ëo²â{”g{eߺU_º~feûܸæ»Ý+T¼··ÄN-RÚfŠh÷7Î…¾m¾í]–~ÌÂ4·–ßcæ CçI“ï|¹®…zný šUt{œŠ|}á Û·ˆt=>ÛV¼b–¶–,Øüˆ7; nBíéÝ™³Xþý¥±®.¡{áë5¸ß»ìºu¢Å ù_—ûUæ?<7‡üSxt„‘­e>hUû©“•ZÊÒüQ©è7Kqý—m<ð¦ÈÚX÷*7ð¿×¿ýó^´(Ó;¥sÆ©9Ó©i¶¿{§Ä¯ŒZŸÆ­9´û?yKl Y´-ýåÇ?Þù·S<­x“ÁòÚÿÂWxÐi¦àÒù^r m]¯·$ÿ:â´/ZÄzDú}—‡ìmï%Fg»bß7Ëó|§®ï›ø«ÿ„Ä<•n5V4G“Ê äã eUT/üj—%D’ûÁUwç§vþãêM'âæ‡ñåô½C¶I•ëµd|Í·¯FÛÿ¯ ø•®^|/»ÿ„~Ø‹‹8çÜŒ£ïÅ»*YzgmQÒ´;Ã0Êï,sÞÊ®WÃòû÷®SŨxëR·I±»Ô¥TTòí y_þùù©R§Ë–+ÜC­RTâå}YöÂÿ\kZDGÊÙ„‰ü†mÊ™íêOݯoÒ|@lìÕï_<tWÏ?²·ÁÏhº{}³Ã—Ö)rŒÓOªÀaÛ“„DWë÷w}ßâZú¯KøSzÑíº™!-†+ïaíž•ãUÃÔöSÎß­RäNr±˜|W 4—dìó~^ËþE:MKΙ^Ï7R©,$ÜÍÇL íí¾èxò&¾~þaÀü…ušn‡c£À±YZÅmè± Z륕֞³ÐóªfT£ü5sÄo>ø‹Æ)ìɤ±Ý ë"ê!‡ûUÐø7öjð…®ZöêÛûoPgßæÞ¨(‡ýˆúÇ5ë,UF[zÖUç‰l­‹"¿œãøSükØ¥¡GYjyÕ1¸ŠÚ-‘©ÀŠˆ¡ £Uou‹[‰dû ä×1â[«Ä"6û:É÷¿:ÉE ÀË1ùŽïâ®—[—HDçT^ó6µOzao³Ç÷xûçñ¬ÔŒ·aÿ³Pª1sµ¼Æ¢{ãÒç£W;r–²7^î‘-M4vh¦BwŠ¿y«[ñm„ú…ýÄv\ ¾Y%?(_ê}«Ÿøñ3Bøc¥-î»xy3äZ©ÝqrÞŠ¾žíÅ|=ñ‡ãw‰>&j/sw(µÒ!ÜöZu·ú¨?Ûfþ7ÿm¿ QcHE¶vŸhkßCug¦,ºw‡í ·Ù¥=ÊõY%ÿâ;}î\øhÖš€Žâêâíb·‰Â·Ï#nó?à5á:N¹®›¨Éä\ê:¤ÁÔÜÿ \|{s^ãðšÏP“ÁVZ„艨ÈÖÆ(ÆÏô‰8WöÚ‹–®nYÎK›dvÊJ6–ìô]cA×õÍJÎãÂ~#‡FÒ-ÿâEO›çC íwöß*ÊïšÚÑ<;¨xI¼:…ïŠ#O"&xPü£6æþo™»*Õ+ýD¸Ñ.¬ï4ùÞÎ4M!‰üÐ7$ªÁ¸9ÝózÖ¶y©hpÂñjŒº%Ý¢y…öÙ—ø·c¿_½ÏÊÕâׯíç~ŸÕJ•e¤~ºÔõdº¼OÃâTÞ;e‘"{&Ûó'Þè¿Åº»K KûSM³¸þÑo2ý™>ðÇñ&õ®[øjOµÆŸ§Øj’êpï3ÄB-ÂÚß/ñŸáùºÿz®üµÐ´sâ;ÃÑIu*ÜK¿íA™ éû”²¾ìüz¥ÓKÞ+™Û”êo5dÐüDº”Úd÷K{ÙŸQÓ#Ye‰1Òdûäï*šä¾ -äÞÕŸN°°Õ]ZÛ\ɱž1µ™ÕO(U¾ðí^â?¶¹£±·¹ŽÞññ1WÞëœs³æ\ ýk™Ò5 GÃöKâ+ uýdn·76ÐnSŽ6mMÅ8îÕk£#ÐÖð7Š5‹Áþ ’ÇMÔ¦@¶öÑÎ]œmùö¶ì?Íó}ºÙÿkÞ@d—r¯Ïå@Û<̵þ~ípmÖ¦é°jÙj ö§Ø4}Bá]aùÙ[bŸî•úâ®ø2Q[WÔ5ËØ·$acŽ9×l(3÷¾èËnÿÐj’ÒĽùŠ^.¸ñåÓ,ä´•¦Ø´¿+ƒåmä7Î~ïËÞ¼·íž,ÿŸ?ÿ±ÿñ5éz¦¡®¬S¼A~×6ë43ØÂâŒ0dÝÓî|ßíW ŸПÿƒdÿâ뢲èfã©£à x|8Âîñ'–Ï ¶AæÍ ù°ìµ¹7ˆ®õ qcÒ¬Vñ«<FY‰ù·+îÈùôòK-rͼf‘Z\"º·fº{C½UŽÝW¹foý ·¼)u¥Íâ´‹9%ûüî‘Hñ5©ŒíÊ㟛ýìW£:mjyðhômBmVÎÂâãû,yïÝðL‰¸ÇñÿÙ¿à5庺i·Z>§qt×–„j©òžá•º8_”üÙÏÍò×MªÙéÞ¼žãȼ½û,,Éw=Ùš+Uû¬ê¾cÛ;kÎ~'_Yéïa”f¸½Ö i÷]oŠq·ïnë¿-sSƒ—õÿën1<;ÄìúÆ©ý¯säI»·Ÿ=°]×wù7}ïö›øª(ä‚á<û—cQ¼EÛ¸º¾¿ðÜÓô¸ÛÃOŠ@Ï4edXþfÜ»íX±h#Â(°j2¬«1o&Lª¿.íÞ†½¸ÿ)æ>ýËVm¥ºOsö8þÔ\,j¿6?ÝÏݪu¨I¸Ïo¶$»ÍŸ÷ª[]pÇñÛÛy°8l|ûU}ê„WÓ̱8„-º|ÛÛZ®VÞ¤Þ+`Ö.<Ãöx­äf ÷¾ócïeš£µñ¾zÇzþj Ýÿ§j·‡RydlG …Pw/÷·U ëW·¹ˆA‡‡ÈñüÛª”˜‰JÎ豫³Éjüîó3Ÿ›îæ¹ábðºÊ‡j¨ãk|ßZé_c[ì$¶Fл?Îk:hä[µ‰~a·w÷©Å¸…HÆ{žëðsö˜Ô<>öºG‹'}GM;RAï¡ÿeÛøÇû]kêm;V²×¬Ökic¹B?åŸð׿ž½jöplóB¢»æÝÿ¯WøñjûGò4ë›¶‰GËosŸüpúÔT§Î¹¢`Ÿ$¹¸mî.ì[÷Sy©ÿ<¤ù—þú­{mzÚá‚Ë›y»%yf‡ñ2;‚±ê( •¿å¼4Oÿí]¼ ô),n’Äÿ0e;”× ätr÷:¿³‰>tù·t¤žƒåþ•Í[Ç%˜ÍµÄ‘6yV;–´ ñ‹´\Á÷Ž:WˆìÍ‹[©ô÷ó-å’þòµÐZü@Õ­ðÅp£ûãæ®j BÒð/—(ÝþÑ«gùxû¿Z­WÂO»/ˆíô߉Ð;ì¾¶{öÓæZéìO5¶ˆ†ÓþÏÉÀ®nãöøuysçÜ\jòî „Uü‚W¶Ÿˆz¾Ãzwcwú—ÿ •ì/òªŽÆfùòÍŸâ;©ºöøb ‡vwW,°·,†fþìb±îüi31X!XÏý4ä×<±¾ÞËR´'=÷VnµFj©Aº¿žíó5ÃÉþÎ~Zb©fæ–Kˆ-y’D\|ÇšÇÔüeoc°F…ÝÇ ÍÿV-ÛVÍÔ[÷boùaS.BíþõU—P Û!Mþ¬ßv¨FÒ]”ìÿ3î»Çðÿ㵋ãoˆ^øo¥O¨ëw‚%„n1EË–þ¶­^[Õ·:cÈòÉ#Ÿï6ãòŠù³ã¯í© ü=iôO¼A¯/É%çÞ²µ?ïËcì¼µü5ãÿiü\ŽöÞÓ~áQ¼ûi^QÆÒî>ùÿc§Ö¼Ä*—Ö+r1²<2Hɵ™>îæüVº¡®¤Ê-£Ôõ_[øÛTmFÿX:•äáindùÎ{/÷0Ý— ³Òîm÷Ù¢Dð;»oÿdöÿ€ô¯3KVÔ7\@#µqµv³íYoó¯Fð½ÕçúÏó%ÌnÆH·.×G!ƒ¯c\µ#c¶ £áêz—ˆ[LŽ9bŠi6"+îWù¾Zõ-_ÄZî›âû Ã#ƒJG[‡‚ï—ûåWrÿuÕ[Ò®Ù¬^Ð_Uyß^Bñ[´¿ÁñËýü ½Ä? ÓÅß ´;íò-7ĺt*ö7ÑI±ÿ¼Ñ3Íïõ©«/cO•ý¢)Å֩打àKÍoÆ-]>û@Õôm:kFwžòÝ•w}Íœ×eÃ?O³³‹TÕ%[x®meµ ’Å–ß±»ÀÕj‡Ñk<³®ë¨7rÉ…ãoËòÖ>½âÈuË™ì-õúMå]2ÆðÄð¿ÝWì_nßâ¯$ñ߆nü`ú]Ƈ¯˜õËp­ ±\y^B?,ŽMy¯Œ¼iñßWµðÿˆï~ßfŸ%­Û:ÿ«>ÞÝjô(Su)ßfqÕjœÏ¬o$>±óï."•žD]Ò#<@Ü/O•wç¼ak¥ê×0iÈ—*“YLïåüÈbÛ×o}ÁÎ+Í~x‹ÅôñZ=ÌvpÌ-î•gó™þçÞÿgvEz–µàùt}+ÍÑ&Ó/om]ÚÞÚì}¿Ü‰ƒt£—•Û¨´~ñKUð®‡ÿ’uBîv†4KMFK‚“,½1»¾ïºÈÕ»Dÿ¨ïþËÿÄÕŸ†Qø‘¯kº\ßËoiyµš5ÝóSν¿‹ïW­Â7¨ÿÏ[ûå¿øª9œ4µw>nø¥xfÉFŸ.Ÿª|Ï uµÔ"Š“·!Z`qß}z?‰~|?Ó4\C¡jJÓç[ë “°+÷^CÃQ^ÍJ’RVg“£Î4 ü-¡h~'V>#E3Èók6ÓK¹xì‰_¼Þ½kŽÕ´¯iž$·èž&ŽI<µ-¿oŒla¬˜þ´Q^…7©Ë=™oI³ð yeMÄa÷…Ëk–ÇOøñ®{â1ð {Xî´_MÎ=~Ñþ›ÍVÐþ!…Oá³"ÊßÀˆÄZ‰ãÜvëö‡üÖ¨ƒÀë•]Äj½p5»oþA¢ŠÖ§ÄsÒ(IkàA:Èt_±ÝÐë¶ØÿÒYl</âA¤xû¿Ûvû~ï§Ø¨¢†YVîßÁió¦âcÜkVü}?Ðë>uðxh±¤ëã¿ü†-ÿùŠ*c±R3ªðv*؈)®Á»æLïâ ÿàÂòºßqRR¸8¬$úDÓ K*¼ /±ÐíÂâôcP«Ubo¶Ê *ï$ÒŠòlä\ 7XkÜ8©±zièú‰¼1Ž>X:Iò«(Öi–á*”ȯÀü±©TÀi7ämHê–N„ÜžÀý,U¢À—äåy1è‚ÀDÈ(ëP#î+íiÃQ¡÷¸¸Ï¬¨ -ŠÐ=ª`J-¤/’í‰àº¿ØÔYèá}®à•ª!©Û \Âé[ͦ 0H@ 9jvt¤:p[Knâ˜~ø?4„†CаNkBúu5©Ë^+ÀäSQüß­î*Ût3RíaÑ »¼G?_݃ú¾7±¤ä¥V×J·^‡NV±ô†Ï¶úòþݬl‚‹^¾# I}«®ˆ /N/X'¸ 3ïÂG ˜ º¯ƒ¼·©¼Úð“}À&þa ï…ì mœ2—!XÆÜ­pûêT¦ñÞm#0öóèèîß6®K{3çõ‹©ë¿äbʶOiIèÊ9± +²‚ò½‘³oDba½ßôëÓì¬Ù·ñ¥+¢ùçâf˜ÔAµ‡~ÂE¯±Ñï(ô—œ0Ð  'Èщ{âpuHÁ.5?ä¿‚Y÷ѯï0ØüNªá¹ñ1¯r š±ôo)«ü9K&¬èôžÌÝt=Â~ÁyVѧçn\¥hÌ„¹Ó™ÙS>"yö«8º±‘Äg­¥«sŸF]æÀ¥.ü!óºÀú0»²Uqnœ*‚SѺˆS%r²‡3 Üÿ iØU’zÉ„]D­0æ=„œ„g­çÞ7.aXTüÆÌò£fe›ÿòòγÇK+ç í—€<<ñ0Åe”H2ïÝ~þ¤¹Ã~“?dÉø ]1îi@¬xÌäW¦ùvz«HŒ€FßÜq“!»ÚQ|züÊ÷ÞÆ‘cOb‰iÄ"ë)Û>›%ã7P1ñîÝö9QõŸkÇJ†1ëõY¼0õÄY·ÞR ŸB0‹g'lõM‘ͣщ•HÑ£7c›ŸP1¹æÜWqâÊ<¢…)2{–F yy0$Ÿ‘…h6Zpä’}Axvµ8MyÕ!ú# ÚÁ9báöT8ƒAÄO´¼R°ó‘[}Îyë^,ó‰0²xâ"f½Ý›¸»!®sãÜCÅ5Ÿ´Vçö|D®dÁ¼wûá%_Dr%ð)^r:ÏNÞïKî·þ ÌB„ˆ¡Í2ŽT—³æu¶/9œ=È1н •õ•@¿Û•éåÜ$‘SÏÔF„ø uÔíȃQIg`¨@Ʋµå _ËéeÔÇí9¬\ÜCªv©|k"žyDO„y™ªêXsGåå6Ç®{Á1Ôá1›eÅëÛMê9›GcÉ• úcÌtõl–ÞQK¹‘Ûöó†$’Ÿ±¸xU'¼g¬ä4+·A˜-ô€X[•7‚ðÃv­´Vôøšh_M¢»„Ú B|m"F ’ÕCªv©æn¿áýB Ø·©˜ø©P_å Œy…?æ™ kÏHê²Ê+0z-BÄf½Š£\ø^TÙ– g`t5š™,- Õsˆs éåÀ3×Ę7¿hH‹knòM¤¨}ËÈ€1 'Püo3¡´ ê—¿ç‹Ý»Ãé Ñù¤ž¿+9•¿DÊ9N`™ûR&Ä–«°3Xªúe»Zì½õ ¤yAo¯RºõÖáÕ߃ùÁÍ\<úæ¼×?œ¢g~ÌÛ8]‰à2Œù˜¨=ã}ï¾tëÝñ4à Í"? ÂtÈQüiåt´yÑOQ1ñ)†6÷DÉ@ƒ9ŒÒÓY:)<â%õýÛÆ¡£\†ëèS} ÆàöÕ¥[CˆE£@ÎfÑÄË$t»°xÂJ„¼ 8ŽQZ¹œù»òX8©†ÃÕw`ôs ú#åïühHˆg+©Ë¶ÎÀˆ'| ªPQòD+ j¨Bˆé,™°+WÅg”Ô-5 #0ìÅJÌhÞc2¯òn´ñ5‚ ésíã-lðöÃr™2~š<ÛÂìMö´y=þÁ‚öâÂ௡õ~휸º ÿÂálm„øQ˜ðûDîù [¾"Hü}Im}9JîC=£Ð/á÷©jœnG÷#@A“÷Œq.Á: Ÿ|·–Ú#Gêòr›c“Aˆ¿ÃЦ”Љþ–ÇÒÍ—"ä …ñ>–3“EãªÛÄv’ºÉvo<õ ‚›ãxòoyöÚw›mw)…  Eø¯ƒ)âÇ©{e»hàÏ6Ö'6ÑõéƒêÀ8wgF× 8¤á È–± Ú¶‘ çÞé$úŸÈ‰q¸Z!§fö×@ƒFiá¼çâîTô¯Aþc{ÁE—„cP¢iÐÇë}mø mõCjd‰ï{¨m¾S©w 䘯AuùÑ´ïÜq®,sШðþä`–ÀÛ’+zbÐçù“--…îÞä„ýc–gú‚i!O7¢#Àð$ú¸O5¸ =bãF¼oÁgX¢XQÁ"r†€Hv80Æ@/ öïó£>8$`ŒÂŒÖÓòrHês…/þ¸¥ÙÂ^Ìb¿É¯xO÷ 9(ÒpØÍNh,¬0B¡»;ÈûSÖCóä_ßä­û6/_Ÿj4œ\ÇÊ‚X†;@Êø êQèÁAt,«DñC~_ê2Æê:íjQê½›µ[ÜÆïámêR1Jêø¾SèJdI>ùó™ésµAרèÕé? ý²O9––!»# }H c»q¦å˜ ¸+Š=««æ®–ÚÏ ºÆ D±n!K.ÃËYÜÙRÊG‡jøžA™ùÞñF E¯£ ?Lšg#½V oKR? ?Λ.±Æû˜ÊØÖZZCA²`’‚—ÓtÆs6üÌ ¾Ã܈÷ŽãŸmì)`°À.4è|ƒ®ÂD†]ÞIHÚÄžW$çiÌäò2Ü¡'5n„Â#؃ :f'»PO}•ßÿÄ$Ä`}ë½âJØ0ZA®t!Û1¶Ë-?á•ÕÑRðZHê–)ž v´Û>ìá :ž©Š=Øbûg/ppfù€ÞÑ@{A,¹µÝê¾@Ž’Èkº’TÜüdýT¦xè8È›i!*uÂÆ~§äZ‚Øn4„Øá‘*ëÉ$E]/(¼nd|ꡑƃÀ“ÀäXƒ¾%0-šºƒ½©žä:úD馑#Ô×f„LÓËw€[ r èm-œç‹n9JúŸ”Èævïiï)ÑU,êPF±ë‘ž&ïHŠ;=M^•îË'¿o-µM©æš@Zç“:›xSubject}\nContent-Type: text/html\n\n

      Greetings,

      \n\n

      This message has been automatically generated in response to the \ncreation of a trouble ticket regarding {$Ticket->Subject()},\na summary of which appears below.

      \n\n

      There is no need to reply to this message right now. Your ticket has been\nassigned an ID of {$Ticket->SubjectTag}.

      \n\n

      Please include the string {$Ticket->SubjectTag}\nin the subject line of all future correspondence about this issue. To do so, \nyou may reply to this message.

      \n\n

      Thank you,
      \n{$Ticket->QueueObj->CorrespondAddress()}

      \n\n
      \n{$Transaction->Content(Type => 'text/html')}\n" } ], "CustomFields":[ { "Name":"Favorite Color red or blue", "Type":"FreeformSingle", "LookupType":"RT::Queue-RT::Ticket", "ApplyTo":"Test Queue 1", "Pattern":"^(red|blue)$" }, { "Name":"Favorite Song", "Type":"SelectSingle", "LookupType":"RT::Queue-RT::Ticket", "RenderType":"Dropdown", "Values":[ {"Name":"Never Gonna Give You Up - Rick Astley","Description":"Best Song Ever","SortOrder":1}, {"Name":"Nyan Cat","Description":"12 Hours Continuous","SortOrder":2} ] } ], "CustomRoles":[ ], "Scrips":[ { "Description":"Test Scrip 1", "ScripCondition":"Test Condition 1", "ScripAction":"Test Action 1", "Template":"Test Template 1" } ], "Attributes":[ { "Name":"Test Search 1", "Description":"Stalled Tickets Test 1", "Content":{ "Query":"Status = 'stalled' AND Queue = 'Test Queue'", "OrderBy":"id", "Order":"DESC" } }, { "Name":"Test Search 2", "Description":"Stalled Tickets Test 2", "ObjectType":"RT::User", "Object":"root", "Content":{ "Query":"Status = 'stalled' AND Queue = 'Test Queue'", "OrderBy":"id", "Order":"DESC" } } ], "Initial":[ "sub {die('This Initial block will be skipped and never run')}", "die('This Initial block will be skipped and never run, either')" ], "Final":[ "sub {die('This Final block will be skipped and never run')}", "die('This Final block will be skipped and never run, either')" ], "Catalogs":[ ], "Assets":[ ] } rt-5.0.1/t/data/initialdata/transaction-cfs000644 000765 000024 00000002414 14005011336 021462 0ustar00sunnavystaff000000 000000 use strict; use warnings; our @Queues = ( { Name => "Blues" }, { Name => "Purples" }, ); our @CustomFields = ( map +{ LookupType => RT::Transaction->CustomFieldLookupType, MaxValues => 1, Type => "Freeform", %$_ }, { Name => "Billable", Type => "Select", Values => [ { Name => "Yes", SortOrder => 1 }, { Name => "No", SortOrder => 2 }, ], }, { Name => "Who", Type => "SelectMultiple", Values => [ map +{ Name => $_ }, "Facilities", "Information Technology", "Library", "Telecom", ], }, { Name => "When", Type => "Date", }, # Two CFs named the same, but each applied to only one queue # Note: Queue => ref forces RT::Handle to apply rather than # RT::CustomField->Create; the former respects LookupType, the latter # doesn't. { Name => "Color", Queue => ["Blues"], }, { Name => "Color", Queue => ["Purples"], }, # Some ticket CFs to test mixed searches { Name => "Location", LookupType => RT::Ticket->CustomFieldLookupType, }, ); rt-5.0.1/t/data/initialdata/initialdata000644 000765 000024 00000005553 14005011336 020656 0ustar00sunnavystaff000000 000000 # Samples of all things we support in initialdata @Queues = ( { Name => 'Test Queue', CorrespondAddress => 'help@example.com', CommentAddress => 'help-comment@example.com', } ); @Scrips = ( { Description => 'Test Without Stage', ScripCondition => 'On Resolve', ScripAction => 'Notify Requestors', Template => 'Correspondence in HTML', }, { Queue => 'General', Description => 'Test Without Stage and One Queue', ScripCondition => 'On Resolve', ScripAction => 'Notify Requestors', Template => 'Correspondence in HTML', }, { Queue => ['General', 'Test Queue'], Description => 'Test Without Stage and Two Queues', ScripCondition => 'On Resolve', ScripAction => 'Notify Requestors', Template => 'Correspondence in HTML', }, { Description => 'Test TransactionCreate', ScripCondition => 'On Resolve', ScripAction => 'Notify Requestors', Template => 'Correspondence in HTML', Stage => 'TransactionCreate', }, { Description => 'Test TransactionBatch', ScripCondition => 'On Resolve', ScripAction => 'Notify Requestors', Template => 'Correspondence in HTML', Stage => 'TransactionBatch', }, ); @CustomFields = ( { Name => 'Favorite color', Type => 'FreeformSingle', LookupType => 'RT::Queue-RT::Ticket', Queue => 'Test Queue', }, ); @Groups = ( { Name => 'Test Employees', Description => 'All of the employees of my company', Attributes => [ { Name => 'SavedSearch', Description => 'Stalled Tickets in Test Queue', Content => { Query => "Status = 'stalled' AND Queue = 'Test Queue'", OrderBy => 'id', Order => 'DESC' }, }, ], } ); @ACL = ( { GroupId => 'Test Employees', GroupDomain => 'UserDefined', CF => 'Favorite Color', Queue => 'Test Queue', Right => ['SeeCustomField', 'ModifyCustomField'], }, ); @Attributes = ({ Name => 'SavedSearch', Description => 'New Tickets in Test Queue', Object => sub { my $GroupName = 'Test Employees'; my $group = RT::Group->new( RT->SystemUser ); my( $ret, $msg ) = $group->LoadUserDefinedGroup( $GroupName ); die $msg unless $ret; return $group; }, Content => { Query => "Status = 'new' AND Queue = 'Test Queue'", OrderBy => 'id', Order => 'DESC' }, }); rt-5.0.1/t/data/gnupg/keyrings/000755 000765 000024 00000000000 14005011336 017130 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/gnupg/emails/000755 000765 000024 00000000000 14005011336 016547 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/gnupg/keys/000755 000765 000024 00000000000 14005011336 016250 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/gnupg/keys/general-at-example.com.public.key000644 000765 000024 00000003213 14005011336 024463 0ustar00sunnavystaff000000 000000 -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.7 (Darwin) mQGiBEYrdhQRBACpTwAjSJchxV9rgWJj/4GUe92xZ2wWHVrkv7cELO5GD1ie8wtA nh57oXfcFhuSmtLTyT/C1Mbzo/tz4Sigf33bZlEMXusp0bLsSz1S/5mslBGRdApJ Dz5jETEcakpzWsHA5sHfv8HLn+o8WUDtlGZf+Edi0DqUKSiLRkjWMAdcJwCg7cN1 IIhhFFAf9Lr3Ny7ngJDwn/sEAJoZVUmhBHo9TipR9lZY1si5U0hA8Yn4XghLp4z9 0rm8dAgLSZwFI2/zoU5u9qjW0UAo8Sp2SO9F03wQpfUGnpQtea/HVNuwiZVU42bB E5gn5EIYrHYT8X7cd+ZpWVGYu2117uoJtRHwnfuh857ocs7M7xeo7IQUZArqeHOG i3hABACV9mqnZoPyCOtaBogdXtDlEbqDvYclONJTsSKAfPsNRjJi8lzvJL9ZhtS8 YKIUvxFu+XX0UVXWoNnzte8Ip/0hwupJu9jIcBJpI9dVEK3H2tWr+NElzML/uch+ VO7UUmk2H/hF8+a3wXkdEN45FnJCyqC0Kk59OcY3bJIrI56SZrQdZ2VuZXJhbCA8 Z2VuZXJhbEBleGFtcGxlLmNvbT6IYAQTEQIAIAUCRit2FAIbAwYLCQgHAwIEFQII AwQWAgMBAh4BAheAAAoJEJ+mYsBt4i/CNxwAoMEYt4mJlH9rCoqc8kkaE2qJslG+ AJ9plkUk5LWv7ncWcrUpMxVo5J3Eh7kCDQRGK3YwEAgAqxHhoyoViGJsImMKG2XQ wxHIBJc21zaLwG8MAn6xNXIZRDrjlgy0ItteGB0Hs1VqibE70fwZ9q5O/Ev32M/s fxtEgJMyfZzOVPMxY0AZu7sUdsfDiTqV/0FwKMjGM1aV2ulqaVZLJr4hpdxjgynf TUqtkJjYtUMAwMcsee/ORnyuSBGcaG3SjWyOyrI+oDm4e2QZ/jGmINP4b3PHJu9C JXq3dR/vJGlYxr9uApaXHjVksa5+uPA6iqjyCoRo/Sv+V3SANSOYJYI9tnKFHuLM YAqwOufWNnvHjb2zS0hCAJJ6p0CMcBhmaNieXcOgmt8MIJe/b5W4ItfgS/2D+yI/ iwADBQf+ISTkh2pY2QExXtGmhjO1LSxB/sO5n0zJOkQvf/I5aOgWxXDP8dYcc9mp 2sV5okB+S4Dol/ppGaxas+kisdwTNja12Er21Lrm6CGfyqp214EKZcM44KmIb1HA utLlujBJsWgf6LDggH4s785GKzwtA9NHfiWeWkXWGXrGFsFK6OWDcdSH0GcHus2F 9FHLyOh5jpkd4Xczx5lEAn1wvUhQtRn9bBU7FLGUind352vbKegeFjUvhGCLp+YF IxSB2OXeVdzUbMjiUM0zJu3VNDsq9IP5EBvV3cwixuOF/XzYiDIPqezDDU9P5H2R d1IQBgMIPl9WXAFRkCIJUnsmvQOINIhJBBgRAgAJBQJGK3YwAhsMAAoJEJ+mYsBt 4i/CEuwAn1QHO3umF9MHhpWQIBaacJUfBboqAKCYPpflpFvDNn0ioLA7Dw5qVSix 5A== =CF0O -----END PGP PUBLIC KEY BLOCK----- rt-5.0.1/t/data/gnupg/keys/rt-recipient-at-example.com.secret.key000644 000765 000024 00000003543 14005011336 025470 0ustar00sunnavystaff000000 000000 -----BEGIN PGP PRIVATE KEY BLOCK----- Version: GnuPG v1.4.7 (GNU/Linux) lQHhBEa8u6QRBADCqPh8w3cO51hPVb1Sttqq5UhCeB5t2dAL8aVEDkpPfV7LItDi pN4VqHo2zbGE8q2bCoqW06Ogn0R4xsxEeD9Jq9/k3dHReFL2gbA5F/el1PKXVxG8 62BnjLkDub8yCdWsg0QDJ6ah7LC7vukTMlJj+3HhoXWEqBrTBKjtFkNIrwCg/LtU CEyj+z/cl6NQGZUw2A6+5DUD/2DfcLeSir7xrlcidqO4BxtxdWkEBDAnmARKrqaw zSATIK11+HO3Gteovfa08J1XXU2+IFqi2Ssyaqss1kteJE8DmOAcllSXqmCfOmPm xoW4gXOQfEv6tkTvF9JST1OZRj5w+ecyxn0282XrzKcxNeLjc+JcLfzPmmuhw4lA s/nJA/4tBqT0V7QiwaznBo8Bh7N3sz75x0vgSdZLUA0e2VzHKh9mAfK/FeVS1mcJ 04iHWvxOGMqEfXnpxUrogME7f/TWNBVfT4M2JW0sHLvaiJhTtIhn+Q67awQ1f0qG mGQLIo9OAWZnIfBZ8e2tBwJ3ajiSZ2LIPWFv4Q1hKxOclODpf/4DAwIc+jyN96r4 cWCKJH3rJKMiam7fzkjUhawkIXBXWlau1oZeQKvQCxCpj02aFks9bSTK9wseazjU JRF6D7QmUlQgVXNlciBCb2IgPHJ0LXJlY2lwaWVudEBleGFtcGxlLmNvbT6IYAQT EQIAIAUCRry7pAIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEM4wxB7CJ0om PPUAoL3A7jiEZX6xSyXEduAtnmMplqHrAKDB8mrNols8/ni0VOv0QletwEwbVp0C YwRGvLuvEAgA/qDyDeDPFrDhh757tpgvJp2CmIx8fyv+i9nLEBVCZjtkLqgrcvtN h8l+xu3y8vjGB6+ToPvGZE3FRxyLWNPGIlq1pQSREC9faEDWDrN7yA8miaikLIlf MnGwwzb5bEXWsmXzctTvDgxTCufDj8T66TKv+cCqc9T956XY6q49Z/p6yZDiY7LZ 0N6GkHSoT8o6ZCOvl87nIjwKR8AXDWBxL5+SeenNkZ8e30pSVDJTOe4u6W/MKK3R BD0FKYr+DOMh5BQtE7yTQEhzmDTPfGe9m52FV8FbSLpimMnIFM2hGRf6jynoR10s 0tk2DVADXDycwNYarRYGAxV6XafLCPDv4wADBQgAtpM7zhVch/NsL56aIG0QZmSa KCdk6UPsJua91eLEHJFozOzethsAWED5KHD5ThsYBKPGq+mFz7QQtw8/DBmcajtB xMv2fvVOE7SrWfeHyMVlRgidJc3O6HlPPnA/v8lQhsYTxpUddYqB4lC0ktpncxCz X/VNr62YkmrpJx2Yvyd0L/lK5fiko65gQC1v/XQ/QI9kpGbOFXFnEgQXmFcDTX4k zTgpJ3cOBrM9GAO/hcwH82eC0j8fYw8mLYR8yQG0jsXJKCvHxTgkOh0nSkLaeLoq 1maLp+NbJKCqgpsmeV4nQmEJE4Ye7I/L077BtJLv1tk0G0Jh3F2WeSzEvB7cS/4D AwIc+jyN96r4cWBgxgN9v8Z6ySQrlQfJWkB3LDcYVugGb3Ht6vIMaFMnW9KFwOgd /nuf5uqyuy2/jQ0SZT1fUC3skhxT1BXKewfmmlnzoLJu3rz6iEkEGBECAAkFAka8 u68CGwwACgkQzjDEHsInSib8VwCeLdgy5axSbYZ8Ez42Kcj8Ku2Q4ZUAnjJ92jFc fC7XWcM/6AX5IyU0jtEW =DS2U -----END PGP PRIVATE KEY BLOCK----- rt-5.0.1/t/data/gnupg/keys/rt-test-at-example.com.2.secret.key000644 000765 000024 00000003543 14005011336 024625 0ustar00sunnavystaff000000 000000 -----BEGIN PGP PRIVATE KEY BLOCK----- Version: GnuPG v1.4.7 (GNU/Linux) lQHhBEbl4D0RBADu+s2KTTSMl2/aK3Jkhy9ZTBMFOOCPeleidjV8z7RVGEwTjcby B0DbPFC/eK0ot9m/F9CojE6QHK0hqjCKPfARptjG6C/Iqxql0DaRWdo4UYTgT6WW hhoKK5DUN57Eu0essy1qTyzcXVIRsQdfkn2ldRKC1XSXnKAiL0vODLtL7wCgoDgj tDtOHdi0vlSHvRhPD1F9P3sEAMvSaiEMN/3AlAWQLqrg0rQRr4dpRZqahoffBIeX OZGzDSrWtIshMQLLA0HmkphPtRe/y74GBWfpwr6Hs6yl5tP2PSXsAAl+W92st2Vp lKJWsLZtvW39nS5cwmv0Etz6j0F9tn7Ah4+x89egzIg9GwU14cS2GNqxYsK3+YMY jSXzA/4zEDOQkrRuSEm9JNG5JCFKexAvjLzhYQQRCOI1PrX3iAMzbYFFIgTpr26h sPfOb5SMy2OGeECXGd0rxF4+rMCbp0jrQ8B18CWuho7HJK97WuT6NFoaPZCh/pYK OQkKGnJCUQNSm5u4uWY6yNs/+U4kvYJvIGw7F7uNWuXcpfREaf4DAwJXViZBji9w O2Bhhip5V1QOR7FbE8SLAJVVPoX4Lv8iXOMm4jaXAqTPkvWmWZDiUKfDf6Dv5wT1 aj8N8LQmUlQgVGVzdCB0aGUgc2FtZSA8cnQtdGVzdEBleGFtcGxlLmNvbT6IYAQT EQIAIAUCRuXgPQIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEMeYWRqoMdv7 v1oAnR9bV/4nbzizkEAm691AuqGLFyryAJ9WjLWviRXuiEKMR6LMIn9HC0POap0C YwRG5eA9EAgAoexx9eOohJpX0VJ8gNVSlFsLLq60ugiWzfwzrGkL2v4o5QoCwj6X FhK/xtDmqhWu3USBVtqWvrMuq2VCWWPiezZ/8Zl8NY7GjxDyLgeuEotfBkS8qFLQ H7TGqNFLoJOIi3UjNFX8cRx29CSQyc3jj88HiC1InuMMwDXf8ukpkYNG0n7E3lZ3 dWOadCXY2+kAxJ5qGV0UWmEBoux9TU8hFAq4DtRmf2x/Mt3k+e5ZXpbnbKGxAAIm MV8WR0SGAd5OcgeE+rB3ziAtD8YbM+WDxbihloje7YN0hVSFSTsGvypIC/jPSNTV gkJdG7N0NepGh/T3wbAVeDebMYDmUQYLiwADBQgAmrAkyor1V/M21ERoZdoNFMlb dxuuQ5QhBIkkiygn2dq1d7CaOF1Hi5s3o6DsD9ER2YENlPYgPtVPH/agGuITHHug GKitj9sRM/QCGMKf/IKazxcTQfcHmOUMXrM6GWzPZiiTrIBpOZyRDyFN1x0y9G9z 7lVt507AkyF3aS2da6ycl2knSX3vPtFkHCUVlvKz6yXGE4xLm8/ACH/zPDjuQ2X/ Yz+FfpJAeNihz6/J88GaO7sgU1jx3zdRaH5hZXJRlyO7AW1KdOtI8xZi3SBNXKBn fCbUUSBbxa2spXst0EepijEDtsDhK+imdFjX8ul/JE0XhJooRrcdevEbohMn2P4D AwJXViZBji9wO2DcFJ5JiQd3X934uG+AeTFIWQG75qOWS3j8BpRn50H73tUn9dxB Z+V9tI7sMFW3cQTnt6c+FSNJBTUAfLTExQO0BqPSrVwVmKImiEkEGBECAAkFAkbl 4D0CGwwACgkQx5hZGqgx2/s4jgCfeukl8vl9mMuariu08MsuywQ77y4An1cIwl1x 997LwJrR5WF/WoGvPQ61 =E3hO -----END PGP PRIVATE KEY BLOCK----- rt-5.0.1/t/data/gnupg/keys/general-at-example.com.2.secret.key000644 000765 000024 00000003530 14005011336 024634 0ustar00sunnavystaff000000 000000 -----BEGIN PGP PRIVATE KEY BLOCK----- Version: GnuPG v1.4.7 (Darwin) lQHhBEbgjJQRBACx8uMHcl9JlKCUU5yh0rBw636MXhKr7cH/+zBTeUVQyfs+J4fo HlI3fwJzZOxwXKTwmXsHSKxomeBJxPpFCnwHvn7xTYh6Wtis1Rm2Vr4lsw4gxrFj bpk1ISkHo4tO3dPTNx6Jhz+gYzoVUyfclz/byUmEbe2HJkBCQNmC9/lcuwCg477S siOdObqTKtQhDqXzFfOHKmsEAKVB8RImtLAO/HPY5+rxiVkfKjsmZovi0PfioGDI 3o1jcSwq/RwWPZTNB0vnlEx1aD3zedUn1T7ZPnKoRttIv0xYlg9wYhX/xvOA9mKy G5aXSypiJiNwJSQfcChlyVHX3R88pURIzPiwWv2OBOvRagE8Bmgz0DFa0/AQfmvI vpUmA/4qZVzOJ5hC6fVP+ESKEC6fStvHAEZM3sQK5AnZsuUls3+tgfkD9T6ei5YF MXDLMGz3thue1M0QEu3IL7cLoTMalWQpjpyDuqXS8UAsd5cG2eP2iA6Uf2VoKQ11 w6Q1FyMwTC9B0F8JijwdLF77ERSCtIG+TOA1EtH9HfTV5+BfHf4DAwJaviEcavOA YWAyNs+6FnvZb5JMpdZWxKrmidVFbPUx7sHk2vEW0UTzcnqnlxmoJ/tKeWwiNl1j ztCVFbQfZ2VuZXJhbCAyIDxnZW5lcmFsQGV4YW1wbGUuY29tPohgBBMRAgAgBQJG 4IyUAhsDBgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQ32UfoGMsT1De7QCgu2Gw s7EHllWWhJC9mHgDHC45Z8YAn2mBLO7ZBi5lptavTfQlR05RvTSKnQJjBEbgjJQQ CAD55mgtF494BvjhumUEZu15W9QL99772EntAHnGd6tx6r2GlqWmIHrze/jguIdy XqVGEVWAosDAu08vF1ECDDJoCz/tkyjhXOT4MBKWL0Gpetk61Pi2Qv0GsxQSYG+F wKjTk+pE4qxnT932n3U87KxUtgIxzH7Y/oFxgb0LGQxgokSPuq/E+jtglYxey9fz eFWjr2T77g5oWu9PXPCFjg+km3f9rQxxRgDuM4bPr5tWjhLTpNB0xKZMbx6YUVUP /DyK/DhqaMZ5Qt1vZ0QI/COSPy9Kq76wR1WW6E0SDFU49xAbtMm2MZgDh5uj/lFp 95yDGmBSZzwxY0qy/BbNc4HDAAMFCADbiPty42xieGtEtQdvjqejEsE15Zhna+zf /4Fch/Ee9laOmtW9Nekg2ltUMHw+Nc5VseWIKtFqlnOZtcutO6yYgVH+GJ287gnX w3gK8zEcbQ2RUxYEdiuwORuMqowmVlOCFxgPW6Vv5Em8E3kb1dEMx+Ec/SVmeXpO cGV5kS9PS9veO3ruoXqx6l3Gq3BU3xvDhJ3FRRlsUYtGOBuJl0/oXk/1TUjAuQin sxM+Nh4VljeI3eiT2YgbypdXjH6zmTDy7PlEy5RDYdFOsKW1sZXKWoY6y2TU4IO9 JBWehS8Lhn3pMTv65FME6Ow9W5+hLTBYo9E/kUZXWvPlJgGA1439/gMDAlq+IRxq 84BhYLhZWMP6gRz3MuuJr0YX10x+bx3/96Wkh505MRMLqefr0J8WgzjIJS1aIUqA nusWttcVsQZS3ZHIZk/tp5dq54CICn09Rl+UNySISQQYEQIACQUCRuCMlAIbDAAK CRDfZR+gYyxPUET6AJwNpkgxKEdjvIbdB7Y+IPgA1wyt+ACdGc0py11j1RLa2gn0 1+nPnBIS7R8= =oSXs -----END PGP PRIVATE KEY BLOCK----- rt-5.0.1/t/data/gnupg/keys/rt-test-at-example.com.public.key000644 000765 000024 00000003216 14005011336 024453 0ustar00sunnavystaff000000 000000 -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.7 (GNU/Linux) mQGiBEa4o04RBADc+2sFcnuTTaqcKRTmSBQKdXvumT4GoATk194UYMghwprrNb1/ flXQRk9zLkc0YENFHLMoRUmXKEF+WFxzXrZgHJS096tGn+Ud2FXQbSL47Vl3EHng c+jSvvVaZRcEySaCyQrsDR7gWlQtCbxbe96Y2x9jX3Zbih9UYnRvWBeczwCg4tgz EOmScnWiwUdyZNQsvXDqvKUD/REf0WjWDaykQvXYZ0aTpc/WMBsDS16nl8GNz5eD lCB/JJHKh5QDu89p0557AbVDSi5LCOYAM+v4oi8k5zgiO/7HJptirDkZ27Ichyes kzhu3Xr9rPLawie/o4FCfncNLbOAEE4EjEGDGRlyowAaXlW7DWT+TLbxY0qL0uHy AQPGA/9AmYHBJQqHTfQ4/QXdCnp+UwYs+rhPh7YHymBLn8Saa14heE9SZcYfSerL FAE7KKeBx96+RplgsiaqfWrliUwrV3KnnJICMyqWmn2OyMYiV9iFWqAHFTCsitS2 q1COv5/Lg1a+XkAwEfoIuLrAXT8buIxXs/BhLc1PD1t9My8srbQdUlQgVGVzdCA8 cnQtdGVzdEBleGFtcGxlLmNvbT6IYAQTEQIAIAUCRrijTgIbAwYLCQgHAwIEFQII AwQWAgMBAh4BAheAAAoJENMoA12EiB8bMZoAoIxLNWQ9d9+W4ImPMpUmjLl9ttxW AJ9ELlhkfdhPukRe508p5fZqKUfl/rkCDQRGuKNhEAgAjSKFedFcU9RjLmSEGo7E 4qMQWOtiNooW4NtRsKC2cbJXnGJUOT+GBzGCxjBZt89T6MVsOy7DoAzs+xWKA5Cf gFEX5xZWM1c6EA4f7LhC1hawtGQkMQIyHzEy9b7NPEcMlkdOebjjhZ4Ob8svGily Q9jN6zpR2c36i0sLaZ5gORIHJ9DOX1k5lUzEhkogEYoYof48VQwHt/5xUURli2kL Daqi+X2+6j/vNp96EQ3sbFifmNejWNaDyyrlyGUvx9g/Eh5wMRospmFA/oE1kSws tKiBxAPs11OJGBRre2Q6QVW2ULAhxZOFgkCq0DNb8TMnhJOY4jhOP57rrvpMyu9q 4wADBQgAhRl4aiej9lX+YpZUcyhBkqIB/cDwYemmtIWzo6mVWuDuVcyLl//sJsBi pwJF6O5nr0ZC5CT+GRgjBmh9rQjv/UtWBldJ7og/HfuSMG6xIfljO2FxKjabDhGa iKzgTk75LnPqfx0FeRNbN78dPy4hV/iIvHPANuyUlmbBsx9hSGqMc78FIDwwfZtB im5XUJbpHsahu4/8agQLBu+PFK+5CIVWskrYVL1R66nPHCzsfYcOv+1CYCsuFldG tFNPCegGxE7T7CFs7m7aYeSdgycNaR9wuBZeV17JnOJ2z/mtsR+8p4vZZzSMcTkj f1j7UCgiKS/ioTD03pU/OZEy9+l1SYhJBBgRAgAJBQJGuKNhAhsMAAoJENMoA12E iB8bOvUAoMbxSLj4OsaYEQ1FxOfsXPRmSPVWAJ91PvFOwLkhsm88kjPAIc8oymjd 7A== =ABM8 -----END PGP PUBLIC KEY BLOCK----- rt-5.0.1/t/data/gnupg/keys/recipient-at-example.com.secret.key000644 000765 000024 00000003534 14005011336 025045 0ustar00sunnavystaff000000 000000 -----BEGIN PGP PRIVATE KEY BLOCK----- Version: GnuPG v1.4.7 (Darwin) lQHhBEWOOBARBAClfdK23heqW39sO3K0p+KtZkDxWhgpjjRfMSWQWwY++eDFhDr5 BGG3zxsB2R8MOaHVKetuDjWjmfDi/LvDR8br8+eaLt94F4PBcLa+vVjboYyjvTFs 2t3leyXjNd+mBTZmUcY6WLM0E+biIJdlDhVOv95n8VwvT10R0J/mGO7hTwCg20NC vJJ/fpWzPItFfKHnKg3gO3MD/RhG6CxmsZs+1bdzY07UwABQG8NhoR5Veqg2+uBr xiYjemtC+8fAtthEojJ334BE7qDuXEO7eq1R+JOtEj/Hx8gtgWQfDNZlgLA4NUSc aU3PthtXD4CnY5MsTrxjpD+bjTde6ziEJ3RHPQQSq2S1fKqo5Bf6H7GYWueRiSGS cNK2A/4/WAYFQbj9Jm7zgvPrLRRnk7RP3A4+ABaWEtGMRbpCaFEHd63gjYEH7P3i /O6y9kXsYr3SkDbhk6h1Cx8+4fjpZHAd4XbgabZhp5u7Nq3m/TIzQiXMYXrZGleB CGoQrERbM9mavEgOHGZEwO1I/JKSHdmAg/adTRAbG+AxZK79u/4DAwKKjpV+74Fq wGCggxQNXbWP1fy2s8C5f3k/jsyGcPyDv2qcVoo9f1cLdb6Alu3f6kSAGWpUink9 dHErTbQhVGVzdCBVc2VyIDxyZWNpcGllbnRAZXhhbXBsZS5jb20+iGAEExECACAF AkWOOBACGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRBIVe2Ik+ud58m2AKCA O8NDxsU8m6ahhfwrPaQGO7MjQwCgtDn5aYx4G93pv5UKpGzkHKh2T1GdAmMERY44 FBAIALAc3V9FsEK3pxdcILYVjV85rrL58+hGcsgeNqF2TIgneJ3Dwi0zA82K1gch fvBT7Ab+A7WGi6E0rXnOs/rMoDV+5Qoac4M9hW1qb1seBt+0au72npIMSqF5V/4n Zr4L21g6vE4/cgrLd3BKbA1hCDdIGeqG3Ljiy++RGnhIkiY+FGpNYYAI9bkXltC9 BYtl5DBvMrpTqlDUfSzqrz3zZYaD30FUGbTTqfISU1RC4sY7aEmgFvB6vvia/s9X yldngPFwuTELCAG/JkFdZvodkwlTdv/vIY0SFkHJSjmT2a797wuhxuaC17eWrQhf F0sxsZhJ1Ac3osrGQQvr8dZCd2cAAwUH/icHkxwmYKa7UPQZYexD4QsGS+rq7TbY zbSWcxz2l5J11/pHdD6mtdGbtEn5mQPjBnOI3GYwvtqbzG4WO5y5qssYoW22ZL9o v67QpJyKJILNLInWwiqxoRgB3Kgk5J5vDNw7CZLxrEvQNKE1gTEqfmQGUAiXipJ2 VXbTWenPN6fDv5vdKesFdnDmk+jfjbL0/G8jUwt6vnQXMVZnIuxTMxs+4tTQfK1q h5iMdaC3wy15pg2wZokyOzjVEJywIQmqAA8lcvD0S/l4+JM6Epfx8gd9IjOVULUd 2/TiPSvGM/gpJdop3bUcUmlWeF59Xx9uZPo7twDubKB6grMWOZwGgM3+AwMCio6V fu+BasBgNB3AutvANzxcWBPZSJFs3uN0m4ii+g7eu8fYaiIJO7GFk1WD5UORHs9w 0C4JIqUj10yUzvnHBZ6DdkV2aZE9FZljLLpRs+iiwYhJBBgRAgAJBQJFjjgUAhsM AAoJEEhV7YiT653n4RUAoJd1DuGANpd7VGGwNfoMCCpAk/4TAKCrbh6NpoJr0Ab4 1toDA5lM/EYYPw== =5O4y -----END PGP PRIVATE KEY BLOCK----- rt-5.0.1/t/data/gnupg/keys/rt-test-at-example.com.2.public.key000644 000765 000024 00000003232 14005011336 024611 0ustar00sunnavystaff000000 000000 -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.7 (GNU/Linux) mQGiBEbl4D0RBADu+s2KTTSMl2/aK3Jkhy9ZTBMFOOCPeleidjV8z7RVGEwTjcby B0DbPFC/eK0ot9m/F9CojE6QHK0hqjCKPfARptjG6C/Iqxql0DaRWdo4UYTgT6WW hhoKK5DUN57Eu0essy1qTyzcXVIRsQdfkn2ldRKC1XSXnKAiL0vODLtL7wCgoDgj tDtOHdi0vlSHvRhPD1F9P3sEAMvSaiEMN/3AlAWQLqrg0rQRr4dpRZqahoffBIeX OZGzDSrWtIshMQLLA0HmkphPtRe/y74GBWfpwr6Hs6yl5tP2PSXsAAl+W92st2Vp lKJWsLZtvW39nS5cwmv0Etz6j0F9tn7Ah4+x89egzIg9GwU14cS2GNqxYsK3+YMY jSXzA/4zEDOQkrRuSEm9JNG5JCFKexAvjLzhYQQRCOI1PrX3iAMzbYFFIgTpr26h sPfOb5SMy2OGeECXGd0rxF4+rMCbp0jrQ8B18CWuho7HJK97WuT6NFoaPZCh/pYK OQkKGnJCUQNSm5u4uWY6yNs/+U4kvYJvIGw7F7uNWuXcpfREabQmUlQgVGVzdCB0 aGUgc2FtZSA8cnQtdGVzdEBleGFtcGxlLmNvbT6IYAQTEQIAIAUCRuXgPQIbAwYL CQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEMeYWRqoMdv7v1oAnR9bV/4nbzizkEAm 691AuqGLFyryAJ9WjLWviRXuiEKMR6LMIn9HC0POarkCDQRG5eA9EAgAoexx9eOo hJpX0VJ8gNVSlFsLLq60ugiWzfwzrGkL2v4o5QoCwj6XFhK/xtDmqhWu3USBVtqW vrMuq2VCWWPiezZ/8Zl8NY7GjxDyLgeuEotfBkS8qFLQH7TGqNFLoJOIi3UjNFX8 cRx29CSQyc3jj88HiC1InuMMwDXf8ukpkYNG0n7E3lZ3dWOadCXY2+kAxJ5qGV0U WmEBoux9TU8hFAq4DtRmf2x/Mt3k+e5ZXpbnbKGxAAImMV8WR0SGAd5OcgeE+rB3 ziAtD8YbM+WDxbihloje7YN0hVSFSTsGvypIC/jPSNTVgkJdG7N0NepGh/T3wbAV eDebMYDmUQYLiwADBQgAmrAkyor1V/M21ERoZdoNFMlbdxuuQ5QhBIkkiygn2dq1 d7CaOF1Hi5s3o6DsD9ER2YENlPYgPtVPH/agGuITHHugGKitj9sRM/QCGMKf/IKa zxcTQfcHmOUMXrM6GWzPZiiTrIBpOZyRDyFN1x0y9G9z7lVt507AkyF3aS2da6yc l2knSX3vPtFkHCUVlvKz6yXGE4xLm8/ACH/zPDjuQ2X/Yz+FfpJAeNihz6/J88Ga O7sgU1jx3zdRaH5hZXJRlyO7AW1KdOtI8xZi3SBNXKBnfCbUUSBbxa2spXst0Eep ijEDtsDhK+imdFjX8ul/JE0XhJooRrcdevEbohMn2IhJBBgRAgAJBQJG5eA9AhsM AAoJEMeYWRqoMdv7OI4AnR5PPGNNdhVSTle3B9cV2Vy11gEjAJ0QHe4iFf7wfASF jybMsNKYwFb5FQ== =XFiw -----END PGP PUBLIC KEY BLOCK----- rt-5.0.1/t/data/gnupg/keys/general-at-example.com.secret.key000644 000765 000024 00000003356 14005011336 024502 0ustar00sunnavystaff000000 000000 -----BEGIN PGP PRIVATE KEY BLOCK----- Version: GnuPG v1.4.7 (Darwin) lQG7BEYrdhQRBACpTwAjSJchxV9rgWJj/4GUe92xZ2wWHVrkv7cELO5GD1ie8wtA nh57oXfcFhuSmtLTyT/C1Mbzo/tz4Sigf33bZlEMXusp0bLsSz1S/5mslBGRdApJ Dz5jETEcakpzWsHA5sHfv8HLn+o8WUDtlGZf+Edi0DqUKSiLRkjWMAdcJwCg7cN1 IIhhFFAf9Lr3Ny7ngJDwn/sEAJoZVUmhBHo9TipR9lZY1si5U0hA8Yn4XghLp4z9 0rm8dAgLSZwFI2/zoU5u9qjW0UAo8Sp2SO9F03wQpfUGnpQtea/HVNuwiZVU42bB E5gn5EIYrHYT8X7cd+ZpWVGYu2117uoJtRHwnfuh857ocs7M7xeo7IQUZArqeHOG i3hABACV9mqnZoPyCOtaBogdXtDlEbqDvYclONJTsSKAfPsNRjJi8lzvJL9ZhtS8 YKIUvxFu+XX0UVXWoNnzte8Ip/0hwupJu9jIcBJpI9dVEK3H2tWr+NElzML/uch+ VO7UUmk2H/hF8+a3wXkdEN45FnJCyqC0Kk59OcY3bJIrI56SZgAAn2a5NRggeMB1 vXJiR6c1NjYO2+hHCRC0HWdlbmVyYWwgPGdlbmVyYWxAZXhhbXBsZS5jb20+iGAE ExECACAFAkYrdhQCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRCfpmLAbeIv wjccAKDBGLeJiZR/awqKnPJJGhNqibJRvgCfaZZFJOS1r+53FnK1KTMVaOSdxIed Aj0ERit2MBAIAKsR4aMqFYhibCJjChtl0MMRyASXNtc2i8BvDAJ+sTVyGUQ645YM tCLbXhgdB7NVaomxO9H8GfauTvxL99jP7H8bRICTMn2czlTzMWNAGbu7FHbHw4k6 lf9BcCjIxjNWldrpamlWSya+IaXcY4Mp301KrZCY2LVDAMDHLHnvzkZ8rkgRnGht 0o1sjsqyPqA5uHtkGf4xpiDT+G9zxybvQiV6t3Uf7yRpWMa/bgKWlx41ZLGufrjw Ooqo8gqEaP0r/ld0gDUjmCWCPbZyhR7izGAKsDrn1jZ7x429s0tIQgCSeqdAjHAY ZmjYnl3DoJrfDCCXv2+VuCLX4Ev9g/siP4sAAwUH/iEk5IdqWNkBMV7RpoYztS0s Qf7DuZ9MyTpEL3/yOWjoFsVwz/HWHHPZqdrFeaJAfkuA6Jf6aRmsWrPpIrHcEzY2 tdhK9tS65ughn8qqdteBCmXDOOCpiG9RwLrS5bowSbFoH+iw4IB+LO/ORis8LQPT R34lnlpF1hl6xhbBSujlg3HUh9BnB7rNhfRRy8joeY6ZHeF3M8eZRAJ9cL1IULUZ /WwVOxSxlIp3d+dr2ynoHhY1L4Rgi6fmBSMUgdjl3lXc1GzI4lDNMybt1TQ7KvSD +RAb1d3MIsbjhf182IgyD6nsww1PT+R9kXdSEAYDCD5fVlwBUZAiCVJ7Jr0DiDQA AVQKdEMomLc49M9nDgc3mQkmkS1Ce0xI+BBEf0LhdiUYRU9mclN7HkCj3fl8EUCI SQQYEQIACQUCRit2MAIbDAAKCRCfpmLAbeIvwhLsAKCoZAIqS3Bp2WndQqZJvHBS u4f0VACgslT4IpJF1BdbMvA+oyvYVgv3g/c= =linj -----END PGP PRIVATE KEY BLOCK----- rt-5.0.1/t/data/gnupg/keys/rt-recipient-at-example.com.public.key000644 000765 000024 00000003232 14005011336 025454 0ustar00sunnavystaff000000 000000 -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.7 (GNU/Linux) mQGiBEa8u6QRBADCqPh8w3cO51hPVb1Sttqq5UhCeB5t2dAL8aVEDkpPfV7LItDi pN4VqHo2zbGE8q2bCoqW06Ogn0R4xsxEeD9Jq9/k3dHReFL2gbA5F/el1PKXVxG8 62BnjLkDub8yCdWsg0QDJ6ah7LC7vukTMlJj+3HhoXWEqBrTBKjtFkNIrwCg/LtU CEyj+z/cl6NQGZUw2A6+5DUD/2DfcLeSir7xrlcidqO4BxtxdWkEBDAnmARKrqaw zSATIK11+HO3Gteovfa08J1XXU2+IFqi2Ssyaqss1kteJE8DmOAcllSXqmCfOmPm xoW4gXOQfEv6tkTvF9JST1OZRj5w+ecyxn0282XrzKcxNeLjc+JcLfzPmmuhw4lA s/nJA/4tBqT0V7QiwaznBo8Bh7N3sz75x0vgSdZLUA0e2VzHKh9mAfK/FeVS1mcJ 04iHWvxOGMqEfXnpxUrogME7f/TWNBVfT4M2JW0sHLvaiJhTtIhn+Q67awQ1f0qG mGQLIo9OAWZnIfBZ8e2tBwJ3ajiSZ2LIPWFv4Q1hKxOclODpf7QmUlQgVXNlciBC b2IgPHJ0LXJlY2lwaWVudEBleGFtcGxlLmNvbT6IYAQTEQIAIAUCRry7pAIbAwYL CQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEM4wxB7CJ0omPPUAoL3A7jiEZX6xSyXE duAtnmMplqHrAKDB8mrNols8/ni0VOv0QletwEwbVrkCDQRGvLuvEAgA/qDyDeDP FrDhh757tpgvJp2CmIx8fyv+i9nLEBVCZjtkLqgrcvtNh8l+xu3y8vjGB6+ToPvG ZE3FRxyLWNPGIlq1pQSREC9faEDWDrN7yA8miaikLIlfMnGwwzb5bEXWsmXzctTv DgxTCufDj8T66TKv+cCqc9T956XY6q49Z/p6yZDiY7LZ0N6GkHSoT8o6ZCOvl87n IjwKR8AXDWBxL5+SeenNkZ8e30pSVDJTOe4u6W/MKK3RBD0FKYr+DOMh5BQtE7yT QEhzmDTPfGe9m52FV8FbSLpimMnIFM2hGRf6jynoR10s0tk2DVADXDycwNYarRYG AxV6XafLCPDv4wADBQgAtpM7zhVch/NsL56aIG0QZmSaKCdk6UPsJua91eLEHJFo zOzethsAWED5KHD5ThsYBKPGq+mFz7QQtw8/DBmcajtBxMv2fvVOE7SrWfeHyMVl RgidJc3O6HlPPnA/v8lQhsYTxpUddYqB4lC0ktpncxCzX/VNr62YkmrpJx2Yvyd0 L/lK5fiko65gQC1v/XQ/QI9kpGbOFXFnEgQXmFcDTX4kzTgpJ3cOBrM9GAO/hcwH 82eC0j8fYw8mLYR8yQG0jsXJKCvHxTgkOh0nSkLaeLoq1maLp+NbJKCqgpsmeV4n QmEJE4Ye7I/L077BtJLv1tk0G0Jh3F2WeSzEvB7cS4hJBBgRAgAJBQJGvLuvAhsM AAoJEM4wxB7CJ0om/FcAn2tCGofP7IPmw6VxGBZNPHal4sIBAJ9UCgpOaGtX2fRl +vvcvfcuIys27g== =mo7N -----END PGP PUBLIC KEY BLOCK----- rt-5.0.1/t/data/gnupg/keys/general-at-example.com.2.public.key000644 000765 000024 00000003213 14005011336 024623 0ustar00sunnavystaff000000 000000 -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.7 (Darwin) mQGiBEbgjJQRBACx8uMHcl9JlKCUU5yh0rBw636MXhKr7cH/+zBTeUVQyfs+J4fo HlI3fwJzZOxwXKTwmXsHSKxomeBJxPpFCnwHvn7xTYh6Wtis1Rm2Vr4lsw4gxrFj bpk1ISkHo4tO3dPTNx6Jhz+gYzoVUyfclz/byUmEbe2HJkBCQNmC9/lcuwCg477S siOdObqTKtQhDqXzFfOHKmsEAKVB8RImtLAO/HPY5+rxiVkfKjsmZovi0PfioGDI 3o1jcSwq/RwWPZTNB0vnlEx1aD3zedUn1T7ZPnKoRttIv0xYlg9wYhX/xvOA9mKy G5aXSypiJiNwJSQfcChlyVHX3R88pURIzPiwWv2OBOvRagE8Bmgz0DFa0/AQfmvI vpUmA/4qZVzOJ5hC6fVP+ESKEC6fStvHAEZM3sQK5AnZsuUls3+tgfkD9T6ei5YF MXDLMGz3thue1M0QEu3IL7cLoTMalWQpjpyDuqXS8UAsd5cG2eP2iA6Uf2VoKQ11 w6Q1FyMwTC9B0F8JijwdLF77ERSCtIG+TOA1EtH9HfTV5+BfHbQfZ2VuZXJhbCAy IDxnZW5lcmFsQGV4YW1wbGUuY29tPohgBBMRAgAgBQJG4IyUAhsDBgsJCAcDAgQV AggDBBYCAwECHgECF4AACgkQ32UfoGMsT1De7QCgu2Gws7EHllWWhJC9mHgDHC45 Z8YAn2mBLO7ZBi5lptavTfQlR05RvTSKuQINBEbgjJQQCAD55mgtF494BvjhumUE Zu15W9QL99772EntAHnGd6tx6r2GlqWmIHrze/jguIdyXqVGEVWAosDAu08vF1EC DDJoCz/tkyjhXOT4MBKWL0Gpetk61Pi2Qv0GsxQSYG+FwKjTk+pE4qxnT932n3U8 7KxUtgIxzH7Y/oFxgb0LGQxgokSPuq/E+jtglYxey9fzeFWjr2T77g5oWu9PXPCF jg+km3f9rQxxRgDuM4bPr5tWjhLTpNB0xKZMbx6YUVUP/DyK/DhqaMZ5Qt1vZ0QI /COSPy9Kq76wR1WW6E0SDFU49xAbtMm2MZgDh5uj/lFp95yDGmBSZzwxY0qy/BbN c4HDAAMFCADbiPty42xieGtEtQdvjqejEsE15Zhna+zf/4Fch/Ee9laOmtW9Nekg 2ltUMHw+Nc5VseWIKtFqlnOZtcutO6yYgVH+GJ287gnXw3gK8zEcbQ2RUxYEdiuw ORuMqowmVlOCFxgPW6Vv5Em8E3kb1dEMx+Ec/SVmeXpOcGV5kS9PS9veO3ruoXqx 6l3Gq3BU3xvDhJ3FRRlsUYtGOBuJl0/oXk/1TUjAuQinsxM+Nh4VljeI3eiT2Ygb ypdXjH6zmTDy7PlEy5RDYdFOsKW1sZXKWoY6y2TU4IO9JBWehS8Lhn3pMTv65FME 6Ow9W5+hLTBYo9E/kUZXWvPlJgGA1439iEkEGBECAAkFAkbgjJQCGwwACgkQ32Uf oGMsT1BE+gCeOni/yGg3+IIPpIoTCVsf8jUcJXgAn3nsf/TTUli9XyftXRiJbua9 CM0m =bGLz -----END PGP PUBLIC KEY BLOCK----- rt-5.0.1/t/data/gnupg/keys/recipient-at-example.com.public.key000644 000765 000024 00000003217 14005011336 025034 0ustar00sunnavystaff000000 000000 -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.7 (Darwin) mQGiBEWOOBARBAClfdK23heqW39sO3K0p+KtZkDxWhgpjjRfMSWQWwY++eDFhDr5 BGG3zxsB2R8MOaHVKetuDjWjmfDi/LvDR8br8+eaLt94F4PBcLa+vVjboYyjvTFs 2t3leyXjNd+mBTZmUcY6WLM0E+biIJdlDhVOv95n8VwvT10R0J/mGO7hTwCg20NC vJJ/fpWzPItFfKHnKg3gO3MD/RhG6CxmsZs+1bdzY07UwABQG8NhoR5Veqg2+uBr xiYjemtC+8fAtthEojJ334BE7qDuXEO7eq1R+JOtEj/Hx8gtgWQfDNZlgLA4NUSc aU3PthtXD4CnY5MsTrxjpD+bjTde6ziEJ3RHPQQSq2S1fKqo5Bf6H7GYWueRiSGS cNK2A/4/WAYFQbj9Jm7zgvPrLRRnk7RP3A4+ABaWEtGMRbpCaFEHd63gjYEH7P3i /O6y9kXsYr3SkDbhk6h1Cx8+4fjpZHAd4XbgabZhp5u7Nq3m/TIzQiXMYXrZGleB CGoQrERbM9mavEgOHGZEwO1I/JKSHdmAg/adTRAbG+AxZK79u7QhVGVzdCBVc2Vy IDxyZWNpcGllbnRAZXhhbXBsZS5jb20+iGAEExECACAFAkWOOBACGwMGCwkIBwMC BBUCCAMEFgIDAQIeAQIXgAAKCRBIVe2Ik+ud58m2AKCAO8NDxsU8m6ahhfwrPaQG O7MjQwCgtDn5aYx4G93pv5UKpGzkHKh2T1G5Ag0ERY44FBAIALAc3V9FsEK3pxdc ILYVjV85rrL58+hGcsgeNqF2TIgneJ3Dwi0zA82K1gchfvBT7Ab+A7WGi6E0rXnO s/rMoDV+5Qoac4M9hW1qb1seBt+0au72npIMSqF5V/4nZr4L21g6vE4/cgrLd3BK bA1hCDdIGeqG3Ljiy++RGnhIkiY+FGpNYYAI9bkXltC9BYtl5DBvMrpTqlDUfSzq rz3zZYaD30FUGbTTqfISU1RC4sY7aEmgFvB6vvia/s9XyldngPFwuTELCAG/JkFd ZvodkwlTdv/vIY0SFkHJSjmT2a797wuhxuaC17eWrQhfF0sxsZhJ1Ac3osrGQQvr 8dZCd2cAAwUH/icHkxwmYKa7UPQZYexD4QsGS+rq7TbYzbSWcxz2l5J11/pHdD6m tdGbtEn5mQPjBnOI3GYwvtqbzG4WO5y5qssYoW22ZL9ov67QpJyKJILNLInWwiqx oRgB3Kgk5J5vDNw7CZLxrEvQNKE1gTEqfmQGUAiXipJ2VXbTWenPN6fDv5vdKesF dnDmk+jfjbL0/G8jUwt6vnQXMVZnIuxTMxs+4tTQfK1qh5iMdaC3wy15pg2wZoky OzjVEJywIQmqAA8lcvD0S/l4+JM6Epfx8gd9IjOVULUd2/TiPSvGM/gpJdop3bUc UmlWeF59Xx9uZPo7twDubKB6grMWOZwGgM2ISQQYEQIACQUCRY44FAIbDAAKCRBI Ve2Ik+ud5+EVAJ0TZHopcAmCANc26gPWWDfTaZovmQCcC5jl3o/yXvdaAhfbqKMr cts0+T8= =wrrX -----END PGP PUBLIC KEY BLOCK----- rt-5.0.1/t/data/gnupg/keys/rt-test-at-example.com.secret.key000644 000765 000024 00000003524 14005011336 024464 0ustar00sunnavystaff000000 000000 -----BEGIN PGP PRIVATE KEY BLOCK----- Version: GnuPG v1.4.7 (Darwin) lQHhBEa4o04RBADc+2sFcnuTTaqcKRTmSBQKdXvumT4GoATk194UYMghwprrNb1/ flXQRk9zLkc0YENFHLMoRUmXKEF+WFxzXrZgHJS096tGn+Ud2FXQbSL47Vl3EHng c+jSvvVaZRcEySaCyQrsDR7gWlQtCbxbe96Y2x9jX3Zbih9UYnRvWBeczwCg4tgz EOmScnWiwUdyZNQsvXDqvKUD/REf0WjWDaykQvXYZ0aTpc/WMBsDS16nl8GNz5eD lCB/JJHKh5QDu89p0557AbVDSi5LCOYAM+v4oi8k5zgiO/7HJptirDkZ27Ichyes kzhu3Xr9rPLawie/o4FCfncNLbOAEE4EjEGDGRlyowAaXlW7DWT+TLbxY0qL0uHy AQPGA/9AmYHBJQqHTfQ4/QXdCnp+UwYs+rhPh7YHymBLn8Saa14heE9SZcYfSerL FAE7KKeBx96+RplgsiaqfWrliUwrV3KnnJICMyqWmn2OyMYiV9iFWqAHFTCsitS2 q1COv5/Lg1a+XkAwEfoIuLrAXT8buIxXs/BhLc1PD1t9My8srf4DAwKhnHYPLWS2 9GBnewzagq2czolDuKHrmtb1Eiv4mb4S8X6HhSn4gQSUJ59mVsn7L1TwK7yWJgK0 +Ix66LQdUlQgVGVzdCA8cnQtdGVzdEBleGFtcGxlLmNvbT6IYAQTEQIAIAUCRrij TgIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJENMoA12EiB8bMZoAoIxLNWQ9 d9+W4ImPMpUmjLl9ttxWAJ9ELlhkfdhPukRe508p5fZqKUfl/p0CYwRGuKNhEAgA jSKFedFcU9RjLmSEGo7E4qMQWOtiNooW4NtRsKC2cbJXnGJUOT+GBzGCxjBZt89T 6MVsOy7DoAzs+xWKA5CfgFEX5xZWM1c6EA4f7LhC1hawtGQkMQIyHzEy9b7NPEcM lkdOebjjhZ4Ob8svGilyQ9jN6zpR2c36i0sLaZ5gORIHJ9DOX1k5lUzEhkogEYoY of48VQwHt/5xUURli2kLDaqi+X2+6j/vNp96EQ3sbFifmNejWNaDyyrlyGUvx9g/ Eh5wMRospmFA/oE1kSwstKiBxAPs11OJGBRre2Q6QVW2ULAhxZOFgkCq0DNb8TMn hJOY4jhOP57rrvpMyu9q4wADBQgAhRl4aiej9lX+YpZUcyhBkqIB/cDwYemmtIWz o6mVWuDuVcyLl//sJsBipwJF6O5nr0ZC5CT+GRgjBmh9rQjv/UtWBldJ7og/HfuS MG6xIfljO2FxKjabDhGaiKzgTk75LnPqfx0FeRNbN78dPy4hV/iIvHPANuyUlmbB sx9hSGqMc78FIDwwfZtBim5XUJbpHsahu4/8agQLBu+PFK+5CIVWskrYVL1R66nP HCzsfYcOv+1CYCsuFldGtFNPCegGxE7T7CFs7m7aYeSdgycNaR9wuBZeV17JnOJ2 z/mtsR+8p4vZZzSMcTkjf1j7UCgiKS/ioTD03pU/OZEy9+l1Sf4DAwKhnHYPLWS2 9GBUcgG+SE35K+ynz0mpxRRx4kbgN9Ap6oxzhDYVGRbfDpVxE8hgJuc7zJ27pmPr VwmdzDROCDy1W9bwjfrV8yhln81npumXxndSiEkEGBECAAkFAka4o2ECGwwACgkQ 0ygDXYSIHxs69QCgxvFIuPg6xpgRDUXE5+xc9GZI9VYAn3U+8U7AuSGybzySM8Ah zyjKaN3s =cv/+ -----END PGP PRIVATE KEY BLOCK----- rt-5.0.1/t/data/gnupg/emails/15-signed-encrypted-MIME-binary.txt000644 000765 000024 00000005056 14005011336 025014 0ustar00sunnavystaff000000 000000 Received: by anduril (Postfix, from userid 1000) id 171AA37F6B; Fri, 10 Aug 2007 16:13:42 -0400 (EDT) Date: Fri, 10 Aug 2007 16:13:42 -0400 To: rt-recipient@example.com Subject: Test Email ID:15 Message-ID: <20070810201341.GE5815@mit.edu> MIME-Version: 1.0 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="QWpDgw58+k1mSFBj" Content-Disposition: inline User-Agent: Mutt/1.5.13 (2006-08-11) From: rt-test@example.com --QWpDgw58+k1mSFBj Content-Type: application/pgp-encrypted Content-Disposition: attachment Version: 1 --QWpDgw58+k1mSFBj Content-Type: application/octet-stream Content-Disposition: inline; filename="msg.asc" -----BEGIN PGP MESSAGE----- Version: GnuPG v1.4.6 (GNU/Linux) hQIOA076g5OuwfIOEAgAiH54xEwaxc/DL+nyd5f/77c6Vk27bdxRz1eXHVU0ofqs oeZHkEWpkub80YQL1HOQTabAF3ZUIGfyfGufnqXpWWNjhnxP/Bi/Itxk4p3oq6Pj RDoDsrO6SPcM1wlbz7M/8rTmpjTk0cYl3nA9Bc7ZDP7jRWRRKFp3Jl63fvK2BWG3 x0yA1WwJX/gzgsRlFSBBFukaffCqaeQZJ2GSKGbuHTH8EYvGDjqALlEwE/iPYRYq OoSa27KIzDCwPFJf8/ZK+03qX2D2LrEViflbFlZMcMGrIFmU0BLrlBx8Obimu3lm 8bCDX5zQsOIptLQqrhQ5E1HIeEFHqJAxJwHu5v7AFggAqSNH4adYAxz3vZyQ3HdV IiDYdwwGh4usb7R7afc8uiFGzxmcc9ktyQ8bBAEpGHMU+ahcUMcfg1svcSKKFQqS xNJb5rl3Q9D7qqsoFnNdVFGIxhnUpjt16TdFEtRHDUnC6dWjxKw2fzdR7YsclzkP IjUP1yHRK0ees3r7LGU90zQUcUXGnuX4xbMi3+t8NFPqpi9DIkOKS77XEpLFg+Yg qrMi3/p72jGBcgbchyS5JJyrVfZjz3igEDy4A2rEw9lJvyemahpccYf9v8LjVCn8 ofdeLujIyDV1T+wFuB2gIY8fbnvP2UC+ffJ2qGA4uDiz9T1t+IapBjwwrhG/P541 KdLpAfXAJzKb/gnXdicy/TyGnMguVXryCVZXAx392jA5se/OVjtVkUV8hMcqBPFC 6zMEPZDXVaCZ7r4VX4xK1y7MGTrq+t4v8UhmfxckiwDVrKZkt6MOwdhTq1YBwFwq DEDN391R2fFr08i+3ogKv30gNlqs1gfqh55uCsZMsrtjMAEnM7JcB6pnptND/mRm 9MYsz5Sxra5L59SNNhJS0GjuTVGIaEhzwusWdFTAeE358AqTOTwdSNe/Fa3qEXBr YzmrCgTn4x4YTn8IDQkh80It7ENEc7tkgRN/FcG+Q6cwHWgPOj7QDBJsN/lm8V3K cWKVxwvp+hoP8isigZxi/7nOD1EZkQkMAWyZSFp+iCZH14GcAKwqxp9Y8bgSuF/5 jzASoge+POvEIpsbS7lmfalU8qR2kCYg17fEvDH+6olqRCT6Bq30cVUYY+LVAHgz KXgJjK3WYue2XQieGaNYjtwr9AsVl4fxpjXmb+QwL6H0K4JMdXc9tabF5j5YFeTQ rWYzED39EiXst9dAFomVhXDHD1OdpAbNh5F9/9wlccgZ7co+tK3bdcD18k2G4XGq 1AX27utveaSU8M1jeHG4a9//f6NLn4cqJ8Qryv0uiY7N0iiwWHsoHrci0doMQI0U glPhkk7qjHf7YAhsRsybfdNrum4jPHMpk1wqY4GR4xhV3JLAbDfKN17yHJWR3c2w TQ8sOLMPKMibyo/KRBLCz3CpnuSvpc7A5tCenDJLYtDhmUMofTN1ki3gBW3OFQpd zH6pCuVBQDQ3iLO/lg6Y434fPz3cuKnxBdN/QvdbeiX4H7tGzC/q+qXpu/8Yv2x0 AVSQkrXcc4CQXAvLzqNMXa6NgKrVtVNXUgHyIxvOgGyVxULKDQo+3bByccCOjNOZ 3gN/JnU2HvEk3iYDYPa+VOJzS1i6itZOCeCBF+NDaaTvG30owmINGCGl/nxv+yIO 9nlCF3QYdaod2TVYfxdp2X7hlPEhv6nHYt2r3/pXYW4Hjy5M/mT7sR+OVAgknpiJ yOzeNy/dVoxpAAlOuzwl+sYI6TkDnF0vduJO0jxWP5+oa+Al9sWr4x4E59OkGAg/ lQ== =NbQY -----END PGP MESSAGE----- --QWpDgw58+k1mSFBj-- rt-5.0.1/t/data/gnupg/emails/14-signed-encrypted-MIME-attachment.txt000644 000765 000024 00000004031 14005011336 025647 0ustar00sunnavystaff000000 000000 Received: by anduril (Postfix, from userid 1000) id 061F037F56; Fri, 10 Aug 2007 16:12:29 -0400 (EDT) Date: Fri, 10 Aug 2007 16:12:29 -0400 To: rt-recipient@example.com Subject: Test Email ID:14 Message-ID: <20070810201229.GD5815@mit.edu> MIME-Version: 1.0 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="N1GIdlSm9i+YlY4t" Content-Disposition: inline User-Agent: Mutt/1.5.13 (2006-08-11) From: rt-test@example.com --N1GIdlSm9i+YlY4t Content-Type: application/pgp-encrypted Content-Disposition: attachment Version: 1 --N1GIdlSm9i+YlY4t Content-Type: application/octet-stream Content-Disposition: inline; filename="msg.asc" -----BEGIN PGP MESSAGE----- Version: GnuPG v1.4.6 (GNU/Linux) hQIOA076g5OuwfIOEAf+MEje2ZVc5yRFxINYBmz8l5oRdrDkldSFISKNlcFogKeA ZAwvP0Y2Utxjs+aT25gqR72uftGzCLhKdIb8X/i8CswKHX/1NnLKFTFB+T3Mmwtg 3a9jiB6kQ5haWvcd9y1DBcm1Wnw5B51QmiHZzHu209dphl7qnoak79M0NK4KPSqr NKqn7yTKFOjJ3qIlCLqawQbQy++IFmtJVKMQSNZawqSX/JSGMNPz9hioyOgyjxtE XZlrdgV1jumnZ3nVF1bQFMGUMGpi6NAA7+TiPjSZgFkKp2zvyOXwFKjKqkVgAgUh +295JO9kjFmRYOxpWcSfBaibX4qFSsWvJbvltNceDAf/WCCcvP6+JYHmZL32YL6+ oSM3SnHuG3PIqaT0/99pMVIhHzhrdrmWsyR6ijcK7PRpQg3mIZDd5pzAgcae3c4M mYsImADyUC6CX0PvSOYYvG7CskDUiGk76fjDKTkKPkgqEFJXz1v9TA+ptx9v2PTd CW8cES9Du5PdwPLbltfC54ax/tCKcyyHbtgozVQJB6RfkXKhUjYI632qnXa7BDqO c5Pcpn7gKKik4MkGEWnf50aqQ6ph4Q9MfOZ+5hc/EBUQd2Genqn8iCTPa2rkrI49 6sKJ9FSdXL4Xdlat09MxnD5Dw6DKF6G6Ig2SAQA/WBG5+sZxWC5NMd5oeJWHVnCz VNLA5QEIo0BNtY+wm2W/YH3hSHV5K+Mz+MEKEUaimYSLWKl1p6vboBjHuk0XY1Tn FDGmLVcO/jLBkiPOhNyibRXaCW/x3iws+xCHngjX4rDeidEtphHCxihVtS1Sh1wT IHHE+Kl6jlateIeBpug79RsGKcdwYTUg2SuaZXEf5A2tQtUhU6TEWYqhFc0g2dCV u/lkhrVRt4S+En4DYJ7myQYdMBeHym52Mz9tFZKKQ54dMWDhDsRZIe0Zy8TKBwXd cwc5kMpkwFK3drbhFuf3WnQUUR80oyGKCffJw3n40ET91b+nAYJ8bPvYFE+gDB+S vHktXvM+vk4PTAKGN21TyZfB9n8VVtIHq3DUPpStrFFQQUDKjMeZf0TDtEWVd1zl 7jVJ2d+Su/KcAXUITXAQMl4ECXMZ3VD3fnbyRAHI375+wcBh08rM1DQRdhWd40XZ 55rzTy6T0RVa3JcQBCtM8FYubYQvy8iU5XcA2LEDWdDn8D89IJ+UAnFHIYoeipj8 qbYjl9JetF1gcmm3QlUwtec6nb5VSmWdQrdfnvdxrW54J3gqlGnzCQg= =7/EE -----END PGP MESSAGE----- --N1GIdlSm9i+YlY4t-- rt-5.0.1/t/data/gnupg/emails/6-signed-inline-with-binary.txt000644 000765 000024 00000003611 14005011336 024434 0ustar00sunnavystaff000000 000000 Message-ID: <46BCDAE6.4090803@mit.edu> Date: Fri, 10 Aug 2007 17:38:46 -0400 From: rt-test@example.com User-Agent: Thunderbird 1.5.0.12 (X11/20070604) MIME-Version: 1.0 To: rt-recipient@example.com Subject: Test Email ID:6 X-Enigmail-Version: 0.94.2.0 Content-Type: multipart/signed; micalg=pgp-sha1; protocol="application/pgp-signature"; boundary="------------enigAEEA002E4229CA8E5445ED73" This is an OpenPGP/MIME signed message (RFC 2440 and 3156) --------------enigAEEA002E4229CA8E5445ED73 Content-Type: multipart/mixed; boundary="------------000104020205010403010301" This is a multi-part message in MIME format. --------------000104020205010403010301 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable This is a email signed inline with a binary attachment. ID:6 --------------000104020205010403010301 Content-Type: image/png; name="favicon.png" Content-Transfer-Encoding: base64 Content-Disposition: inline; filename="favicon.png" iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QAAAAAAAD5Q7t/AAAB BElEQVR42u1WWw6DMAwz0+5FbzbvZuZk2cfUritpea77wVIRIBQ7dhsBdIQkM8AMMJImyW6d BXweyJ7UAMnUvQFGwHp2bizIJfUTUHZO8j/k1pt8lntvchbdH8ndtqyS+Gj3fyVPAtZAkm3N ffCyi/chBIQQ3iqs3cQ0TZCERzbhngDocOS4z94wXTCmu2V45LuQW8hsSWpaP8v9sy+2IRZj ZTP5ububbp8Az4ly5W6QqJ33YwKSkIYbZVy5uNMFsOJGLaLTBMRC8Yy7bmR/OD8TUB00DvkW AcPSB7FIPoji0AGQBtU4jt+Fh1R6Dcc6B2Znv4HTHTiAJkfXv+ILFy5c8PACgtsiPj7qOgAA AAAASUVORK5CYII= --------------000104020205010403010301-- --------------enigAEEA002E4229CA8E5445ED73 Content-Type: application/pgp-signature; name="signature.asc" Content-Description: OpenPGP digital signature Content-Disposition: attachment; filename="signature.asc" -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux) iD8DBQFGvNrm0ygDXYSIHxsRAvwSAKC4d3U6SjfhYpUHu2V/vXtgxGFa1QCfeK6p dyDDlvlqP9Ns4EExvHXfHuY= =sX3V -----END PGP SIGNATURE----- --------------enigAEEA002E4229CA8E5445ED73-- rt-5.0.1/t/data/gnupg/emails/8-encrypted-MIME-with-attachment.txt000644 000765 000024 00000003552 14005011336 025303 0ustar00sunnavystaff000000 000000 Received: by anduril (Postfix, from userid 1000) id C962637F57; Fri, 10 Aug 2007 15:39:14 -0400 (EDT) Date: Fri, 10 Aug 2007 15:39:14 -0400 To: rt-recipient@example.com Subject: Test Email ID:8 Message-ID: <20070810193914.GC5572@mit.edu> MIME-Version: 1.0 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="FFoLq8A0u+X9iRU8" Content-Disposition: inline User-Agent: Mutt/1.5.13 (2006-08-11) From: rt-test@example.com --FFoLq8A0u+X9iRU8 Content-Type: application/pgp-encrypted Content-Disposition: attachment Version: 1 --FFoLq8A0u+X9iRU8 Content-Type: application/octet-stream Content-Disposition: inline; filename="msg.asc" -----BEGIN PGP MESSAGE----- Version: GnuPG v1.4.6 (GNU/Linux) hQIOA076g5OuwfIOEAgA4dZa3t55kB5pY4Q7+h4thTJVZ+wV2QxI5wZFRvMs7yw3 MEIWqHusoe7R2zeXLxaT0gMSQj3osO+GrGJRDk4qCGjcjSneFmYKkELx8KRML59N X7/HeX7v8nSGpyqM5foaky38uAGxj2xBpswfvsY8qdQ37a4mNH2atESnkxYLomZb 4vTRTSE1XtvhaG7oHaXxfmeNln0JezfbfpDGTktZJOupEX99j6/bvloimXyBT6SZ 3wbcP/EYoqC8DQOQTtFTAisWwveTVp6FDFvi5L2BCJ2NzYZSDOBNeXrzf1AJ04dN 9lTzbtejdE/AgwDclDnZYZByiGLM43X6nTk7c9gH1AgAlrxxvO1yz4sZchGM2rrn yzNJpcvcgbJLTt54gqie/BdQHSnvlCBLZzplx2xV9HIwJB7Kf29Ka2gg2p6mjyID YUd1451K1hDUmBHgya8jb6g2c6bBxiusUODlGfmv+c+kq/yG0tKws5d0/IGanL+Z h7XkcEYq3its0acCbcKizgzLSmxSqu6NK7rnDvioUOHHKG8uC9fk2JetdTdYOTBn AWYDxa3D+kRvOAiOWGMqtvOWAC1BZJ8hssBpesuG1+sucTh7W4ZROtQfEbqC8W51 s6e17x1YUqE8QIs/aUYkUkX2JqObKsZPJAiVYCQkqJceIk/lT4rXNOfUwl5dAynk WNLAZQGKuVghSfSoRyKbFMRDimigisWN2JUudaV0Ld6E/5iO7EP7XhjqkzTlwsaN wui2n0Omxye2dNVMKz3q76fVp79XbznaI1ckeVjiiDmkiaQOp1Au/Sx2Bj0/wolJ OXnp27oqc9THy3RANLRXIQuRaYOyoxUIjVvhbOVfM3U7MlcAW3jT2kMTI7H83HWc ezxKfqM7nJUKkIVmRY4J/6X4uo5c8DdIxLeG+ioj+3I7BRkLfIAPYDGaeB5+BD1k EPcVGd3u1EW7D8f5CRARnU8aaC8DCPk5YYN6wM9JY9n0FEgIkwoniTpqHLs/UMa8 DWGer1UCGp4qElmHvWHVV8b82nw6Ta2BhWKjA9pphf96KOTt3y4jqxfU4GbrOYDJ 786/cggItsc0 =qJrM -----END PGP MESSAGE----- --FFoLq8A0u+X9iRU8-- rt-5.0.1/t/data/gnupg/emails/17-signed-encrypted-inline-attachment.txt000644 000765 000024 00000010363 14005011336 026406 0ustar00sunnavystaff000000 000000 Message-ID: <46D73CE2.8060202@example.com> Date: Fri, 31 Aug 2007 01:55:46 +0400 From: rt-test@example.com User-Agent: Thunderbird 2.0.0.6 (X11/20070804) MIME-Version: 1.0 To: rt-recipient@example.com Subject: Test Email ID:17 X-Enigmail-Version: 0.95.3 Content-Type: multipart/mixed; boundary="------------070807070206050202070908" This is a multi-part message in MIME format. --------------070807070206050202070908 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -----BEGIN PGP MESSAGE----- Charset: UTF-8 Version: GnuPG v1.4.7 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org hQIOA076g5OuwfIOEAgAn0IWP59DHAWRYz8MG09D5vp+V3rfVdwv8ud2hp/jAUDZ ogXobK3KkUH5CIDaohqgrxobAtU9D7XhxO1ti6G9ana7+u2GiTIQmC7F1THSipNJ GZajUNG4E0WQXjvkWvDgx2cLdjn+L/i4Y5lQvP6CEmzZpdfKQk4DZdmMJf/Fz0Ag jqPI0weYSEr8YBz/p0bEy2Xh7UPw8rwC6ajk/v/E/SfZXI+TpWnFxLt9OUN+o0E/ o8RQ+5LTpPvkR4RTFFnqZAKu8CU2LqNIWYlzhm67pi+QqeepMuhbW+Ix8tt6oBbN EXFrwBYfjLeLcmMJcm2fEwE7otDqKHHW5/G/lu8RBwf9E0TjxnGbpTH3ERrj172E T/LT0LD2qRitQQCdFyeDvnq6KKyoUtkyhwrrfpDfB1ZYBSjgIB8rgolbO6OxnY8s O6dob+07mQrC3EsbrTQhRjwtLCWB/4kaI0d/9Y4/InStq14AvW0wZWX3kukUq8Us ReJhrDA/fOV+duOQgcEc6ZsMjLE/snQv6KMN7ey7iOe10ejLs9qHFZClqHhpjbn7 zTPRapgTOV1hwBWq9603NDP1EQM/wAOFCw1TDUnhFOzUocpBfSwajWY1bKB4pz94 Nf/U4BBUFq3aQVX3g4mh4sBesYInZ8wMq7fw9fegyiaLL7YzTbxg/YSXHWkTaYCk YIUCDgOxaIPydPr+7xAH/1BIgIWDNzXHWbrmi2pxg547LWoJ3EpJQ46fk2ryfIll 6Ot1xpCXGStZA1bUnRB0KTZlTNVfXkIy2TuFKJ0xP5JgkeeVQ+Vn8wCXyPB1nYte WJ9aKHIk9UhKHhW6FHIjWs3CYjfmpJPaI3AXu4hwT/W1yPIFbAb2UYwuvRn5XABk RDxauFRDoHKPvy4IsorGBPIa5ZkJxuBsP7lxp03CfgnUHX7HYehlNp+rKzp82uLg fXJdwQAE3OcrnB7rbD9AirIyJpy4q/sYUOnC2M137PF+HYA5zCxktpnkVDtWd6Te DyQy7T5Ey5/NzP8IbJpS3Wv+bYOiozv1zNCZ4ZbopbcH/RKMlLN78fSTZmgC6Snb dcwZqi8lvL6vx83VBwUAaQeNDLvtlyjojt0UuDWUI3JQJpv5vcGdLjP21+5f/INt lua80hg/pKwoKijzQPPtnPBJQOiRkzIEYlaqMkmPi+jgkosf0kcAWxnQGt0L74tD Q4ENpgCK6jJVkQFN9Gd2efT7EFUc2acY7/YfFcTOBVm6xi0FXoa0HzL4NzMhbrsx NhlC7/Yp34y3NHjzVlMIKiU9kbHrVCyZyXKuSSV2em0a1SqbJu2wj2/qjK6Ibtts 0aowVn5HafWR6jm8sEEyX/v8c1BdR5Ibmup7Z2DPd0j+fY65GK0nNYZ5QeYq7PxR rSbSwA0B+Ot9P47q0c2vGrQF7EKvug2sgnE/9K8DX+Q5IVwMQEoJUZvKf66x7DTA YIMnWxlJeeU6fkURzeBpv0TzidU6/KRa/2xg/QoUTZvftAoaQidsajzFSIZTBEqC xjzLnkqlDMtlZYBtdvVYDxrBvj8u5whGnhe6GhO8GUfIe/N+bWM5N1kbUAsE7Tau ify77kszQdf20rLVO9eU0MjXyvKpWcT/Dpk/Nzth3iLACEtjr/dXo5pMwK0mofx1 I4lLS7P5PtTz4Oat7aV1up1o =LNkV -----END PGP MESSAGE----- --------------070807070206050202070908 Content-Type: application/octet-stream; name="text-attachment.pgp" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="text-attachment.pgp" hQIOA076g5OuwfIOEAf/fLTYMB41pbPXfa0LaNlQ5PVkAa8i9W/RVfA8s+BGgFYz/herlLi+ eCE9Ls1RoiEmh3e3ZOG0M/iPA0qBMryH4fcYWqx3R7uaZDVIOEcUM3lR9Q0ffNfGRyVMKX+N nUUI/HAaSLCl1NWF6Qnh+/S8tV97D6FDdqQqJKeG7HtPcXMJ3daqHcCl5mMrL/OgcwEcqdHl dw8VnESjnkQ3NAvzyNJEpyB7fHN6HtaynxmZbU262ez9Ywh8XsfBbKsDhnLRf/rEUt7X3RQz YjXL2C6scGT9ctZ6i1yxPQJOP1+Z+UxijfSd8S59n51/SATsdjmhNFst1JZTbX9Xm/Gv//mj aQgAuzhrqVBvyOtX1fMNxGACBhMuJKMLvb9bCltnl4V8IN76d1HP65bw9FvFC7JNcLnbeLBk 6J6CGhwZYcQHuLBiTAfycM636RotiszjLTx1/7j/Jp3vvzToLm3sNNb4INVmoRBrmVqqP2wl Ykl7mxdHAq0Lr6NMiTnk9/TVbJnXdCFseLONJaX6ZPrPZUbw6jhgDMeyAjoBkHJjGqfbde2S TsHvPvT53g8d2RgZx5ATptTphU5QsBbS0eYEIiKoU4lUh7augb6EratU63xTsKc9pJ8DJOqB l7Ic1ujUDQqlccTVJgYyIJJVO/GUR/AkIYrzWcvEwhtk+PW5CMwrcbEo5IUCDgOxaIPydPr+ 7xAH/RYdKbPnEo/0R8mZls6xuycjGoGNWYuhhaze7NiDPuflz/oMkPL9f/o/7a+9t5mBbdQ9 XXnNKuhclocbDg6N/TLXWWw9011Ba2ko2l8LpvzlLo7oARaMYZu4kmMYZIkIeQ2GW97gIV4w yXLJOuVnH5cudt36/P4QqJM0PxSqmQA5sqAMB7cvgK3RFKbDvvrMvmyrdoTFmrcFhKMWbTq+ g51hVLm7HEHjicP6LEhHv7m4ooe+rojS4D+0HofvkPou4XWJ+R2T4CVKOSfobrp0RjMqjRuJ PvdzhyUNjP1qhtvUyM0JBgYHd7biFKOKjYG7E7j9+5EhHG0uSG+xRWq9NtgH+QF2Mhb1WB24 lpXZM2KPM6OdStgEJFEzjr9DwULJpEzQhvOAUI45Jd7oqLQjEdmzuPmkEBdNfcUBKkCNdADp JBn/0Ak2C4OQImtZX6kyJ2afF7zwMQ5J1eH2e/eOYJTOAVnt29EH51UZHi6OCWosYQhDMNZW 8AXoza4H3474bPzlxw7utSRm/CJwetrmp3L9BahuEheYv3PZCmy/1aHpt5OHIJDmrDVMzaAH DhWNkRuY4/CLvg/+r4W21Y7tsPllPGNfxLJt9kilu8F3M/alhNK8CxbM3sskNnxSPPid5vE2 e88iO74uhwvQzTrPKlXfsUDpnGacAwG3uNS3x3CbimjSwAkBjvCRETU1DziOU5xVTCGEQFPv gF3YtRG7Bmvm0d1zeAlPDuflRhadYCdNvy17gA8LI+YBuk74XavAqKURN6FEevULYaSPZCx8 HXgjE0uyo8U7l2D9hrYjGuvON84Cwp2O6+jsiLk4oVvdBVbb0PRz4xj/Egnlu+yjGD88Swge zpQzBdaqE8m6sizWBlYyPGh8amL8/PfS3OC6hZcg4LOiLhj+h+h3eeamTyAE62zjJjC4UMKc ZEI9ZhQ95BxH9bTS+JxP6UySLnQ= --------------070807070206050202070908-- rt-5.0.1/t/data/gnupg/emails/4-signed-inline-plain.txt000644 000765 000024 00000001272 14005011336 023301 0ustar00sunnavystaff000000 000000 Received: by anduril (Postfix, from userid 1000) id 3EDA537F80; Mon, 13 Aug 2007 15:34:17 -0400 (EDT) Date: Mon, 13 Aug 2007 15:34:17 -0400 To: rt-recipient@example.com Subject: Test Email ID:4 Message-ID: <20070813193417.GD6392@mit.edu> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii; x-action=pgp-signed Content-Disposition: inline User-Agent: Mutt/1.5.13 (2006-08-11) From: rt-test@example.com -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 This is a test email with inline signature. ID:4 -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux) iD8DBQFGwLI50ygDXYSIHxsRAp40AJ9ErYdLH2SVRXtgRtx7n/FVFOmKDwCgl/0T BeRSaF4Xbi8uGhVIkmU+YCs= =e4u6 -----END PGP SIGNATURE----- rt-5.0.1/t/data/gnupg/emails/2-signed-MIME-plain-with-attachment.txt000644 000765 000024 00000002376 14005011336 025655 0ustar00sunnavystaff000000 000000 Received: by anduril (Postfix, from userid 1000) id 74BF637F75; Mon, 13 Aug 2007 15:23:57 -0400 (EDT) Date: Mon, 13 Aug 2007 15:23:57 -0400 To: rt-recipient@example.com Subject: Test Email ID:2 Message-ID: <20070813192357.GB6392@mit.edu> MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha1; protocol="application/pgp-signature"; boundary="bKyqfOwhbdpXa4YI" Content-Disposition: inline User-Agent: Mutt/1.5.13 (2006-08-11) From: rt-test@example.com --bKyqfOwhbdpXa4YI Content-Type: multipart/mixed; boundary="DKU6Jbt7q3WqK7+M" Content-Disposition: inline --DKU6Jbt7q3WqK7+M Content-Type: text/plain; charset=us-ascii Content-Disposition: inline This is a test email with a text attachment. ID:2 --DKU6Jbt7q3WqK7+M Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=text-attachment This is a test attachment. The magic word is: zanzibar. --DKU6Jbt7q3WqK7+M-- --bKyqfOwhbdpXa4YI Content-Type: application/pgp-signature; name="signature.asc" Content-Description: Digital signature Content-Disposition: inline -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux) iD8DBQFGwK/N0ygDXYSIHxsRAlbuAJ4wxUVCNerg6dLm+w7llCj51YYbFACgvNJR ajbUy9MMkljajl6Of3IlqRA= =R6Gi -----END PGP SIGNATURE----- --bKyqfOwhbdpXa4YI-- rt-5.0.1/t/data/gnupg/emails/18-signed-encrypted-inline-binary.txt000644 000765 000024 00000011067 14005011336 025545 0ustar00sunnavystaff000000 000000 Message-ID: <46BCDC0C.6090400@mit.edu> Date: Fri, 10 Aug 2007 17:43:40 -0400 From: rt-test@example.com User-Agent: Thunderbird 1.5.0.12 (X11/20070604) MIME-Version: 1.0 To: rt-recipient@example.com Subject: Test Email ID:18 X-Enigmail-Version: 0.94.2.0 Content-Type: multipart/mixed; boundary="------------090909060406090905060708" This is a multi-part message in MIME format. --------------090909060406090905060708 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 8bit -----BEGIN PGP MESSAGE----- Charset: ISO-8859-1 Version: GnuPG v1.4.6 (GNU/Linux) hQIOA076g5OuwfIOEAf+KwEEEyzyWahb5iwjSff0D3MfGimfD7AJTEBjjDgtxcC4 8xKSJsXhZ2feCi3Wx3TYAoFedoiR0DkyNd//B6dE51bpDpoUX1R8M4W0tOKH03ZZ N3sIBYSnJBF66GdLfjSvyEGD9sdi+OnWTv9DbQ4x9Sq2d3u8PBLu3krwk75hJEH7 z9Q4rhqzpRU9ymhWpS/QPd+4SvuhlJ0ciUGeYLmOFc1YAUJNWzTtnja/jZptFY7l 8oDXtHTapeJPB9M0WaDf6R60Evkl9DcvGA6FRBMCzWRjqMSdAPGYiceeMiDYcwCq hJWSzbDKAHDSY2qwlHZ+2y7mIze3x05qcBHYCIOKewgA5mer22WKZIiEP++p4TZF kJbwFxfCqf7mB/nsRGDWsfThGED4KSr18feba8ychNLbVx83FEtTZ5tei96N6NrK eS6BdjedTCbvrq7lhIFmC/qPXy/5cjNxr50RBT0sLUEdnY9dhrwLxo69Rqr5qHQT luYu7/NhQwOV8OlMTpvy/AfUXGiICLa1iBxtVK8UQ9YMLe1GOyCmseeF3UyfTmKN YfEylY17YRsmYZerulSwsHnEoCipjEHOwzksR5zXDHmnv7cqVq1gV1SnjSXOd1Cd QuJFdXY5fBdugcrAAJsiB8Iq4t0B7Ai1Lou6x+kKQoigF4i3zhSmH6blZmc99B1r jYUCDgOxaIPydPr+7xAH/j7bst78EmNmr4RJvQ4A5bss5BJeOdG36MsTkQ4rXTKv tA+chOccB3irMYqpWpKoDMWRz8VgAu9MVVc8SBUr6XRIhLRyCpuZxlwLA+EJJUk6 yeKBHZZ8KgQ+PgC6WYMaTcRRLeOWbTxhhIrZmT2EbEEDE1jbeeLNrEu1wmrdMBBb fhtQTjOHKb7iau3LOGTbbV+F9llnPHrdy2WTp8ozFbsruEqdUG85zQ8X58sy/iC4 hS9mj+vRs8nanbYABfDHUhoBk8VWUWPlpWTUfzDX0Wuai7LWo5wn7mK0p2i84vMU IghS5OGRJfrGVK/1giklqUrmPuiz8M2bG9voLoOVnVIH/izycMW2zZh12TD+YySt D1NP6OPy/5PgYgVZvzTYtOW7Y5EI1eTYoC0cMgjITokq6NKqN9aI7sReVV9P24El J/LxLvv9Nk7/8Jq6z1CjF0THtFg1mQYTWn52mrYwBr7aPpv/UDDFA0dmIancXwf0 CHwFMMHFlYSBChy/vwx8+QiE60pLiz6EnajSq8lEQCf6nSFIvmkWn8y3IxmYRgr/ CMn0BZc+VLgyzIJYG1Ygll10vXDdmjV4Y4f33stWr2Qse5Wp2Pyg/Lsfqw3C7+H1 1BoxYJma2NmJ6sQu8xDNKPt8dsyOCYEyJqf8KGhi8eslRPuilKOreZ9b/KZh6OE2 cNTSwA4Buvhbv4626sdq/BJxEi4Cxrhu3dMPCyeyl1540BOSMpThXdKa5pheJTs1 78UDi87cJBZx8eQ5Gcg2VfwXkolc/dOvIRBEvnuIvA/3DaqH4gxMZ90UCFSzZ9Jm By83H0GVB0l8UbneSKtbr4wms5qjSUUzT7NTmqOdZwFmxJIbB0hVZtC1ve00WlkG qSmK2BXyG2bccsjeC/XOKO2WmZm9Gfuvtr6KcOnHFQYj08ZSfDPzJCuY7PK4/egc TR4wApehk6BzQwbz66uN2PlHIw== =2ZQG -----END PGP MESSAGE----- --------------090909060406090905060708 Content-Type: application/octet-stream; name="favicon.png.pgp" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="favicon.png.pgp" hQIOA076g5OuwfIOEAf/fVq/LJs3vrFnybm6thFjpDte1mawn7xw9op6UIFqEFRjkesreTn2 b4vzYgi/8HUKJj6A/b9OBBobsOuHkKddZ+wRnTnMTc6ZGahJOFdOc3C84hEJYvyEgRqJ9hBh F6awMfPYHIfN3y6PpeJ8jbtWN2QxYvy9a9dsb7oUGi9FuBC7nI0qIqdxeLwjK4MkVGzDhdJU N4OCCe3LhYyD+Ev8Ote2NiWFdPnCkFiO6vwYI1cEyzltJXeOXYe/7YCKDb3FcTG9UszG2nDl jz7hIXF4NtYVQjIxxtRxPfxKgkzboXIoAD1enJpBkNtTdVS3jtdbFvSlypDhG3+mfMlUo/k4 vwf/RF+30fL1kZjue/Jecd4oh6NcOokKRRScIwxTwittYPVYtR5W+swTRyF+YA5SLfygmmYb QHDjFX+cOx8u8WzmVgK4szOBgh921p6DWxMdD6aLK2wDT//ZVdRDqNV/0AbApexlDriCP/CH 8BQxHKTCsentMtdVDf66Y/44K6HYceeU8iZT3pvQKXfAmetiMau7G/pN+BvkN2HXFhBPa8id dHYIpixvjk7Mg3PBzt6mJa4SOSf8vzProFx4UmnkexOQcRxpS0Zoep1mB16oWqm7tXEUjBJx 39BwDg8e9sU4Yrri2WzUkIPU0pG3ub5sxbCTMSxMJnZkRk8ul8s9GMtWYIUCDgOxaIPydPr+ 7xAIAIu4kheO0n6eNJeng1XE/giRvZodwsO27kA00KIW8mi6WGLvehlmFoxntck1r2oCNVvH Oj94nfWsG2i1zvjQDvMp2Cnp3KTmhTrOss37dFayWVODF/Q9Kx+7WXnh9zCEjQe0eCd6PiKF fvb4zCU3ANAY8dTmqRcDDB9TK39nafpWHEfzhjClHrQhuQDh+yb4ayXAvUXkNLIYzAvuwvku 9x46MPLHf/4VVQAplymRvsMy/Vj3R9triHoE9tD42EOWgbo525IwLQ590x/AZoZhkCOeffWv 6fq29Z6om8TqFTHFGPb7I88s5ihlpvUgvXkAA7ZYdF6q06pDldWqVOCUm98H/0LZKiiURX1P 30EAe7DpKi0bAUvHj2Qc5GCU+Hf1ASGJmeENFNMwY+eDwYA6wuBf0tN5zitvncUj5SBZjXjV o8Uz7JJ9/6BuUcfAa4/O2qTIFdctBI9bwzzzuXYF3LbYXfjB4WmoaR0nRh8kTlpgFij7WQlg l+C1G630ynSm4bVHSmD0J/kBDfNUF7Kyr/riPuqnyqf2wVffbLIJYe/axb4slACZ+F4FhfsZ 0NLYL2+pvGW05sv+k/kEyhSQeQZwmu8X4iZdfFK6kOpkHeS7K/yFX7BYRD9nMnZ5Zwekh8l7 eVno90OdJ4rVJkp4+c2N3kEPPLgrEVlaut9gybv553PSwSIBeo8TwJX0khartEsmJRh4fuQq Ni8JhsMDG5Mr8DMvmjhWy5Awp+uQ+neNcnk11wQ+OYDFw4JopRED0m+igEK0i/737ANzEhLN ssbKnPSivQCBo7Yzv9o3XUx+UU4c3E2bE3MQRGDgDmB1SS2h+wyLDWDoBHGXZ17Hxcb83huJ HxFt+5e2K8T6hL3Rwghtg1DHt9eVS05v35xomDn1zPR+EWTEEhvWrPEw00CGpsq3ub4vJajJ Aa+4ClaBivaZVKE4rC87cdLNkiBFbXR3ROvkzoSnrA7/ZUgupDLfagDQKAj91hC1hmF1nRix SOIYGGeM1IsKm806Ah58IOUWsE5vAzoFqCMB4kYNZLbXU1ccxZVTU6QnpE8DzHoDqOtyN5y3 2nyRLH9jBwKfECR3YHH3NxRbbI317fhmU5pAvRWeHpRp1yzGrCf21lgx12Ot1EWxHuXrd23p 0O2EiQAcwadItgkfKW+UwAxGsVX+vueqSBYmYViCTFWKGqMXmgXgirR4nLt7L20WqrXBFKBH ms+7e9LwLHStfAzilr1deHnriNfwz0b9UPAvCV3n6FN3uz2asBWZKjOxlEr2VmaQYLiiFWe0 UhX685wevk7AOX2LZ7iq7SkVmMLSIMgOVI8vmt2PEw== --------------090909060406090905060708-- rt-5.0.1/t/data/gnupg/emails/README000644 000765 000024 00000001561 14005011336 017432 0ustar00sunnavystaff000000 000000 Set of emails signed and/or encrypted using real MUAs(mutt, thunderbird). All messages have subejct 'Test Email ID:\d+'. ID matches number in name of the corresponding file. Top most entity of a message as well contains 'ID:\d+' text. Emails may have either text plain or binary attachment. Text of plain attachment is "This is a test attachment. The magic word is: zanzibar." Content of binary attachment is RT's favicon.png. Encrypted messages encrypted for rt-recipient@example.com. Messages signed using rt-test@example.com key. Name of a file may contain the following parts separated by dash: * signed * encrypted * MIME - RFC format * inline - inline format * with-attachment - text plain attachment * with-binary - binary attachment In total 18 emails using all possible combinations. 18 = 3 (signed/encrypted/both) * 2 (MIME/inline) * 3 (no/text/binary) rt-5.0.1/t/data/gnupg/emails/special/000755 000765 000024 00000000000 14005011336 020167 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/gnupg/emails/12-encrypted-inline-binary.txt000644 000765 000024 00000010540 14005011336 024263 0ustar00sunnavystaff000000 000000 Message-ID: <46BCDCF2.3080704@mit.edu> Date: Fri, 10 Aug 2007 17:47:30 -0400 From: rt-test@example.com User-Agent: Thunderbird 1.5.0.12 (X11/20070604) MIME-Version: 1.0 To: rt-recipient@example.com Subject: Test Email ID:12 X-Enigmail-Version: 0.94.2.0 Content-Type: multipart/mixed; boundary="------------090206040704060905090502" This is a multi-part message in MIME format. --------------090206040704060905090502 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 8bit -----BEGIN PGP MESSAGE----- Charset: ISO-8859-1 Version: GnuPG v1.4.6 (GNU/Linux) hQIOA076g5OuwfIOEAf/bD4qivq3N/0qZZnLucbOARr1mypqomT+gvm1It/DllR/ F3RRLlm0wcBoVQp52aNecT9OJiZNh32oxPW78XBG2OCZv+jATd2vMf4ESOX1Pj4+ 1DATM9V3lOJ8XYJqAX9dVe3MdUntpwqmGvlv2bYmbrc1o6IVQroPm6eV+ttB0BKc ELj+1NjpaXDGERoV2P300JcJNR6tu9pXo56kz1vAjAQj/xQBNCoSOxBB1TJA1sKF iNuQVObWYG2cE63PRZFTlzvTofeHo9kt9ykfyfNMnslwAaXPI5LJP3KGjUOZiomq XA+FYLzMuFzHaOgRGcpJGKTeZ578N7WZ21zmNOgS8ggA0/RNae8CHklYRd9sySdo 0vxoAO6pokK9HSCaxeX96nS4JJfij/uTMRyR1cbwFBvJSQHUtGNGL1Tn9vhgGfqL TjrkvkC/HhBqu4QDYH3ekobV/tu/uQjGhxAASTFB5vtYSQvZtmCR1COTmO559aDz oO+93N4x3AOhC1Nhcmyds9mSCKCt4TCWE3gmHuPKYuLpSwj5Ndi5er6jCsq7RpsJ W0GzGOGg8wUUPDgBWJbXS5CVAkiJs/SlP0yUqeVR18mf9r/oKUt9DL3GCp230/o+ 0wyVkzqOBPNiIHpFRN1NhikXdKRVOL4ewJeCc9nrlBtLwqpEyZ1g4Ht7SKkE3rl1 64UCDgOxaIPydPr+7xAH+gNwaV49PwLd8WUJpPuL+YmH5RRsAasWvX+VlnxM+g/7 cn1BrvpXekq7vctMByxHaV2WnaC42n4M8xC+V1B/7Wk1rmhRMZKpIVjM6X9eNsPg FvOX5DhJdhgNOhjwT7rk29P24p9TQj06TwPnvLYjQJ6LViiX5l4T1YpG0mMb6Sc2 vmp1MbGeV8zZ3hVbSpWOmwlotvfiGa4LjLGNduXIiOFF93pviV2xApYNt+FgJHLc p63GfqUXuj/K2iqKwFIbo7nssx/ceWBRdlgPrE/MSStYhBm/e9mw55nwDsUIB4Pg tkle/6XgRsOgvHydiTD+mxng/1bx9cmvVtNfvOq82wMH/3JI/rfmQUQGRJ/JFufk Kl26lQcbd/Vyxff3MVww7oUFBY2oUPwYL/5N3TjhI7HwiCkMP6S9bL1AfHvkLenu n5GKjSLJmyx7BNQMEIIKWD4tNSjA0CCk3+QVGr9sRIVIVD44Be9I8jlcpkn2r5Qs 9djSXzEBpM+Aiqv5cCBFq2rgYyd9m2c2iHsI6EBaxCzYFz1qobsfvXk4a7ax0/Ck ug/jPEq8GK36UTp+51G8JhxzgJKSm4Fo7N3Os8emIlZ2QymPrzPkxEQj9eRsMmp3 qIQ0rOPjTC8847s4lIlAx7di80OYQ1dV2J8NLL/qxF09rr6f978oH59IfSHRKJ2b aCnSfAGfFBr68Zm20se6/0mzrYY2MDljPrKSDQPmNW7nJ761/YjMgVAeZlnKW7JM hTdZciuHMTX7+3BDTGCMbAnmYYWurX0jKGJ4X0UJLV8m4nsXvqHvsG6FBzVC5z2w kytey5Bqi2gXRjk6xqckH4O79kEJw0kLkxJbT9IPBKw= =vGEb -----END PGP MESSAGE----- --------------090206040704060905090502 Content-Type: application/octet-stream; name="favicon.png.pgp" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="favicon.png.pgp" hQIOA076g5OuwfIOEAf+P0Qp/k1B0WDRr9bNcEANStTaiefYoLrUrtMJv+aFtkiSqKfft0A9 okrYkVUKs6kxfgxueuqNMFQh58nl8+d7Z2qGIgVEXxC8rRxexEQ8mXu5LXzzBbc6Dq8Jsa7B bXzwGty51culYcKeMEjpEY8Qx76qoNQDNCuvth1JJxJ6xQix/pVyZZbJRu/nLrv1i3Z4KRFY qafnJlcsUTVj2o9dLfeU13z8nd0uBPY+hiCgYJHSPDLr+mkA+c6YK0m4a88r/wjLmsVHMkn2 N5nCjuqP4tzT8SCjhoICGTbu+fFdks9NhQjvsW7MHBi9HFFzm6SoEvquFHThzwMl3hAhTLpi Jwf/a6unMP/swAxoFTJ2GRXBmQOH4sJHR/M31rEVkLZGJixhU94Tpx8ptgLXqme5VCXgl+M3 Oh0GHRXqFYjR/HGUTZokRKR/BgCEpOGlH5FcabHiu/Gy8UBezPbuC+BNvxuCbuwODMp9R5DE F8RSCAQ1hrRoJjeHT2wyE7HdCvN/xx7NyenA3GdJa5Z6W7Y0gshr2fAOFL39jKXw4WwCh/Yq XnyG8uOyPgFrnHI3WpO24VpQHp3MBKebWNhQ/Opy/cABunCSwWQpDB9Ar4GeS3R1WGtMNC4r ph4afmTHJcQQkoa0VfvHL0hEzycwysYD46O9QhZfhxtKKShgX356oCeMEIUCDgOxaIPydPr+ 7xAH+gMnbi5OLPf5xMeZydvNWdHE/wJTub2rrWFtzvj0Aa5Ne/KFhcDDqSjaL3MXP1WfIJr1 /ANe1eWcM2hlYVDpEn6YOh0bz6BASE4kbHA5nGMyUrgH0hWfcOgkMUloRZdf1q32j80mchCJ rF4YsQ6EndnUYzAiXKHGJRUy/6IA2qBH2n/fRiyC2FmmQPtWO4c6t15Vgh4fB3QXSTri8J5r 577yIiHRE+dq6gg1BfyqCtw0DW56lSFQ7dxyMXeLyTGyjTGPlUDc+FbP23CRK7zDIVujARmm mX3bP2lMfCK326FwBZf2Q4Zl/ac1BN8Mcb4wwcnKvRzfEw8d1Y6pkphe7KYH/0MDDqtmuEw2 D/xdw4FHB16/HW32bcPaMVvFuseczEfrwPGCrCiPHPm++edAoY0rWoBtzHVpgN+s5bset5OR snhjuWceuCb+Ga0QV0s/xmIPIQ8VYaXyD5hob6nHEIeskS68Vbni0BpY3nejDPoV3dNHY2Tp 2fjYNHCpsdTz8yyavQVixoQjZQH9hUb48zZDHCt0Af9Rfq6Et5/Qr8iJqAyEU8JzZtrkpO4O HgLU7JTPLxOGzYtOj8JkLmLguVA5kOafAuU4OTEU43utQfS3KYbdEWT2jJ1QaJVS8CjFJrqH V99FUsDvgKWSTy5hA9gkQAQE1QdwkoQKpkCWm18KZqTSwNkBfrsEuvHC3Cz4Sy+cJmhr4Hvx dyZY8DuuWExUcVCjkeuASLgjLEgahnCbMkyKATazswwTEdfzjcOowjLTdaWFEN/Cg22nF/px 9MXMtzBkrTkjYPhfywETKoMVH/Nw7rRNZhkOSb5WJV5ynF1BlbzXI7Z8rA/KrIn8aydzwJUU qFr8Dw1C7kbE76+SFVWX8fqpwGmQhDAO+kos6ivgN9HDHtuXmwfGeROi2U0WcmFGbAyLo5fT LCcNPOMflU27WDXm8m+tjq9naUynqvwg5zBBz/xY67L1R8uOwfZplvRi35iZAJjzMHGirkiB W3ZDXbDqEfKl4aCXqU+XhQZsku2z3OtKZOBVVI5p8nGVEfavg6QECRUNUS7qbtMxlj5IwCGl babK3W5YVuERjklrrLUYZjqFIZ2yLK3Z2VmSn7yKAb/eRvdEeha+9PKcN11pXPkS/M3t+Vpr G+4TqNgqwLVWMvbENp08dS3OAPpZLDnqG9CJV0qacDMjv69X26V3Xp6vuZoKqAPxMG9QKAfX E9LInR1Kd0cpRUkb --------------090206040704060905090502-- rt-5.0.1/t/data/gnupg/emails/1-signed-MIME-plain.txt000644 000765 000024 00000002167 14005011336 022553 0ustar00sunnavystaff000000 000000 Message-ID: <46D7309C.9040804@example.com> Date: Fri, 31 Aug 2007 01:03:24 +0400 From: rt-test@example.com User-Agent: Thunderbird 2.0.0.6 (X11/20070804) MIME-Version: 1.0 To: rt-recipient@example.com Subject: Test Email ID:1 X-Enigmail-Version: 0.95.3 Content-Type: multipart/signed; micalg=pgp-sha1; protocol="application/pgp-signature"; boundary="------------enigF67974ED650702891ACEBB10" This is an OpenPGP/MIME signed message (RFC 2440 and 3156) --------------enigF67974ED650702891ACEBB10 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable This is a test email with MIME signature. ID:1 --------------enigF67974ED650702891ACEBB10 Content-Type: application/pgp-signature; name="signature.asc" Content-Description: OpenPGP digital signature Content-Disposition: attachment; filename="signature.asc" -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.7 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iD8DBQFG1zCi0ygDXYSIHxsRAqEmAJ9mKiEdoWg9IFGlUlhPrzo9+1tKSwCfdqRG +HKnj+81jWpBEhj6D00uNrQ= =ZFav -----END PGP SIGNATURE----- --------------enigF67974ED650702891ACEBB10-- rt-5.0.1/t/data/gnupg/emails/19-signed-inline-plain-nested.txt000644 000765 000024 00000001677 14005011336 024660 0ustar00sunnavystaff000000 000000 Received: by anduril (Postfix, from userid 1000) id 3EDA537F80; Mon, 13 Aug 2007 15:34:17 -0400 (EDT) Date: Mon, 13 Aug 2007 15:34:17 -0400 To: rt-recipient@example.com Subject: Test Email ID:19 Message-ID: <20070813193417.GD6392@mit.edu> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii; x-action=pgp-signed Content-Disposition: inline User-Agent: Mutt/1.5.13 (2006-08-11) From: rt-test@example.com -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 - -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 This is a test email with inline nested signature. ID:19 - -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (Darwin) iEYEARECAAYFAkmETNkACgkQ0ygDXYSIHxv3ewCgijZQyL5vWIOfk+06XjqTXdrN VDcAnj13TCHvhas6rMtxcljNNGvPidw6 =VMc6 - -----END PGP SIGNATURE----- -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (Darwin) iEYEARECAAYFAkmETOIACgkQ0ygDXYSIHxvvvQCfZlRPNjt77jJ7ANxwOpkHkwCY wsIAn0PzLhCKhIcAm+hk8CpduzYcY0xW =Xy5t -----END PGP SIGNATURE----- rt-5.0.1/t/data/gnupg/emails/3-signed-MIME-plain-with-binary.txt000644 000765 000024 00000003253 14005011336 025005 0ustar00sunnavystaff000000 000000 Received: by anduril (Postfix, from userid 1000) id 9745537F7B; Mon, 13 Aug 2007 15:29:41 -0400 (EDT) Date: Mon, 13 Aug 2007 15:29:41 -0400 To: rt-recipient@example.com Subject: Test Email ID:3 Message-ID: <20070813192941.GC6392@mit.edu> MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha1; protocol="application/pgp-signature"; boundary="pY3vCvL1qV+PayAL" Content-Disposition: inline User-Agent: Mutt/1.5.13 (2006-08-11) From: rt-test@example.com --pY3vCvL1qV+PayAL Content-Type: multipart/mixed; boundary="at6+YcpfzWZg/htY" Content-Disposition: inline --at6+YcpfzWZg/htY Content-Type: text/plain; charset=us-ascii Content-Disposition: inline This is a test email with binary attachment and MIME signature. ID:3 --at6+YcpfzWZg/htY Content-Type: image/png Content-Disposition: attachment; filename="favicon.png" Content-Transfer-Encoding: base64 iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QAAAAAAAD5Q7t/AAAB BElEQVR42u1WWw6DMAwz0+5FbzbvZuZk2cfUritpea77wVIRIBQ7dhsBdIQkM8AMMJImyW6d BXweyJ7UAMnUvQFGwHp2bizIJfUTUHZO8j/k1pt8lntvchbdH8ndtqyS+Gj3fyVPAtZAkm3N ffCyi/chBIQQ3iqs3cQ0TZCERzbhngDocOS4z94wXTCmu2V45LuQW8hsSWpaP8v9sy+2IRZj ZTP5ububbp8Az4ly5W6QqJ33YwKSkIYbZVy5uNMFsOJGLaLTBMRC8Yy7bmR/OD8TUB00DvkW AcPSB7FIPoji0AGQBtU4jt+Fh1R6Dcc6B2Znv4HTHTiAJkfXv+ILFy5c8PACgtsiPj7qOgAA AAAASUVORK5CYII= --at6+YcpfzWZg/htY-- --pY3vCvL1qV+PayAL Content-Type: application/pgp-signature; name="signature.asc" Content-Description: Digital signature Content-Disposition: inline -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux) iD8DBQFGwLEl0ygDXYSIHxsRAuYxAKDQeRS40bRiW5jmrwHNsCDN67vu7wCfV0Pd 7T/gCO7TrbuGaJ0BVsJnJsY= =Pjdg -----END PGP SIGNATURE----- --pY3vCvL1qV+PayAL-- rt-5.0.1/t/data/gnupg/emails/13-signed-encrypted-MIME-plain.txt000644 000765 000024 00000003543 14005011336 024630 0ustar00sunnavystaff000000 000000 Received: by anduril (Postfix, from userid 1000) id 19FD037F61; Fri, 10 Aug 2007 16:03:35 -0400 (EDT) Date: Fri, 10 Aug 2007 16:03:35 -0400 To: rt-recipient@example.com Subject: Test Email ID:13 Message-ID: <20070810200335.GC5815@mit.edu> MIME-Version: 1.0 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="+nBD6E3TurpgldQp" Content-Disposition: inline User-Agent: Mutt/1.5.13 (2006-08-11) From: rt-test@example.com --+nBD6E3TurpgldQp Content-Type: application/pgp-encrypted Content-Disposition: attachment Version: 1 --+nBD6E3TurpgldQp Content-Type: application/octet-stream Content-Disposition: inline; filename="msg.asc" -----BEGIN PGP MESSAGE----- Version: GnuPG v1.4.6 (GNU/Linux) hQIOA076g5OuwfIOEAgAhFFX4adckICRP4VEhrElZ8rRYAwJVbTLd7S5yOnrovcG sRAIZTjEmb8CRPX926hm3fJ/5O+pm9ZPE1wzvEBQGcJ+6oZPD4GMwuMqJpaZLw4v 6zj4ogzac0i32vkwLYDLHXle9ytgiBwTFHuOKllCvBmTe5PrFAHqhA0EPJ824gD9 H93LJUyvn/elOHASKVcUhm1XLV0RxdNTh8Afc7dEAIr0uPaWu1rVatdnc9JnQjsB luYLP8M+UD9u/sZwSBv7x/PVIOE9QsaFQGQZTYEzKb/4zeujcUyM0+4rjJ+45QUX cDsrFVmXoQfts2nw0BN5mERZdbOIwvkhZMZzsf+EKgf/dA7x9rguO/eGy/keQf0f sBHAz24WGWRqcmRNdBsaecVgAsygAEi564RQYvxM7eJxqKl+1TsjUmGUAmacShN7 JjpqTH9HRrV522UNvVXFitel4Ri3UItP3zI+951x7YvkzUtIz8gfaCOHC71NmPBO RdKDDBYDEajJkYN6mhL+QpX9fIIP5ALkfVz0JmdHN1e81Z5myuEWnCSuxeyZHCgl Xw8PuV/Af/+GHqjNUaXRDxN4SWm82pKkK4rxioMI4liI5zuR73GH8wd1RjspCkKd gPEhmqDxvkxykjhtJt5Izj+iQsyZNRDHRkGJA4BLOLUnvFtwpNCg6DaRDUOBjBmI P9LAXwGbhZU7uZLtSzwn1gr8TD+cqkpQrFlKRiUCdh6sTwfp+HE3JDmKAwX6t8bM ndBMYwIPddYdkFFpWOQbl1G72zR3SSuwgyCC5+xZxDWPHT97iCKbvCYAmdN3cGHZ 8Lqu56ulz54pIkBZzSsu8TWzZner2eax1MqITH3WNYYuH57yiMSaUK1DSdFrg9Mc o/aK0QUaW6pCFbYGZRS1cqUBaN11Z2debMC60JiNFA/htzLwgSwmZiEdAIt4lvU6 xMdA7dFYuVLmVh9+VQYp3KspNDWsILiPsKbV8oomlcLiZ+g8cRY/NzLKXVqX2a+7 Jl3hLlEuVKdcWe+XCfWt07Y2Ibwtpkq/vph61cPdus0dgV8U+QITQ4kt1ky8xNBc L51c =zSIe -----END PGP MESSAGE----- --+nBD6E3TurpgldQp-- rt-5.0.1/t/data/gnupg/emails/11-encrypted-inline-attachment.txt000644 000765 000024 00000007742 14005011336 025140 0ustar00sunnavystaff000000 000000 Message-ID: <46BCDCA0.4000205@mit.edu> Date: Fri, 10 Aug 2007 17:46:08 -0400 From: rt-test@example.com User-Agent: Thunderbird 1.5.0.12 (X11/20070604) MIME-Version: 1.0 To: rt-recipient@example.com Subject: Test Email ID:11 X-Enigmail-Version: 0.94.2.0 Content-Type: multipart/mixed; boundary="------------010900070701080408060501" This is a multi-part message in MIME format. --------------010900070701080408060501 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 8bit -----BEGIN PGP MESSAGE----- Charset: ISO-8859-1 Version: GnuPG v1.4.6 (GNU/Linux) hQIOA076g5OuwfIOEAf/dbKZrl//1Q5r/FjPmqjp2+NDajR7Fj2afjKtkR8ySKni gc8kTvx2ceQ8bdOMfdn2AngPlKKyADp8xLDge3hwZg/mBRpOtk5B6eqGZDSwDxgQ Q2+mUVilhDBqLB5uv4rXoclT/JqR/WBWW1SjZgVVaf/lB9mI5amcmOPuY5vBq/iY q+qBAlWc27AfafGPSz0nifohc2qmq/pj2G6YDkKBE01EZRwbwvm35bTjI6rFL6dp Zzx2kGhAu928orL5Ft/KtyOEqdGwGlrGApLVV2X6+G1X6ASdgR24OAqnPxYMnhoT I3uYidSE6DSmUAm4R0rGRc1yMG5rBgRDDOYumRJFHAgA9WsNlQTkSuzypFzW7xT1 zAzurHA1OYttjjWSAFb/eOnLxc27r1mBS0JVoEz/9+HtHOgGcsRe/sym6uwOOMT1 znpgRGtQ+vL0bwBK3HyLqQzY9o5BGphf3ipCpI6HWwBDdGUJT+K7S8J9Og4AmV9V iq6V1PCdGwWS/XD4tulHIPUiA7Z6osVqtbHVTuQNWwsFU06SEHOmhNEGDSmO7Q7i bUS1YvC1STmJttzSsh+A/dh7uN/mfrPG1BJEAi4iZRGM6GFoKS5CQnpyQ4Az222n w1iB3u170ijcgLvDNR1Nz5DVnXdJcbVg46GvAGjctI7qWmFIKuazCA/AA0hwRjfz j4UCDgOxaIPydPr+7xAH/AzvNqeXEU9n7Y7K6rALWKwETU98ltzyp0FdO0YCbN/Z HWAyazGpPL6VqECWdc9xAYa1zYRf9HEW25yi5BMqbPav7oxfkEMbfLEl2OJr+EfO /GeTijv8riPFazcXdj7CKuumsld/GQgVLjwmRdoVtGN8WMRZfM/CII6uhg2wVbxq iThRc03DSNwLr0N0aNr66rySOnvTZNEARljPA1VaRgy1YkHXAyj6Z+s5OBZgj3yh 3f6KZXaRBq9r+7iXJFuOpQ35k/pykHL6wwaTYMmEvlPZ9EO1zgvBxS6NWm4Ct4+X l9anXjRRPS9aSEUZQRjP6VfQPrrlhBNIgAEoKHlIJe4H/RVbTUAqRlTf3k4XBwkR HoTk/b/vM5grRQTR12DLXKB+NeNEpQPQFbdFOhfX22ZJbVzl3CVxaOEYHB29KT4D 2frp99spiO8gksYdC5kviXeXXlXINtqsAWCEQr96wOlE03NuKh9TLF3ykAKageqr yDVdFjmeuMwL5XWVdWFUCDKROQz0gbX3WglCVhccrB4aMPx1vP4HiQIn7R2Q9/GJ I0lrn/se5GuT078BnhpApZkA0zU+Fi32NWTY7TLXYKxK1BpHWREEFIwrPe1D3Sot tLh+E65rDp2dt9LrZgGerSQqP1V8z50reNcGdRuWXfJSBwWJfVVo6ApEqD17bPo1 Y8HSfAGGTN7euRDBuov2eMPKlLTYqQAqOE7DJK1IWQtw9aIMxLuTBCORy+DHh8v3 elFBb1PxNZ64BJrOK4+gUm09eizynTdtHYhvhe8Gplu6JO5U28djXVqlIiHtEhF1 VltikOnWlztV/2M8XoUKZneB9MBqhJ3W0lqN5Xzw7Jk= =bB9/ -----END PGP MESSAGE----- --------------010900070701080408060501 Content-Type: application/octet-stream; name="text-attachment.pgp" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="text-attachment.pgp" hQIOA076g5OuwfIOEAf+KE8JLsuGPug17A+1JJwCliRR8Iid4CxXyFplTXYgeFGKh2EN0UJw Rj0k10BKnefbhL2gRM90aQaAF4gBF9mCh4H4Rg1reZ5ohoinNPk1Qgkotx1JnyVxIEk7mzKE tDIU4BGdcaN6LPhlBRBsSA/nzA19oM8HwecBstws9fbS6ZRwV8XLXKEWsQASEfmJxSYWpdkJ GEWIprcwkKClXf9RO+FXhFeg9i1ucFC05/1053w1CZtBAB9ohqs/GhILiCzVc8RZtQ+Xig6c Pdv7Q7zZBRy3lFz3o6bAjefxZfNbvVZmuJGMLPrevXF02v4aGazCtzqm7wR5cH0sxaaW0maD Fgf8DAeE4A+GqG16s6GGodVllO2JsDWYlyVnf+kfE4FU94uspYteDeGBf4zXhd9ccRYOnDWO o6yBwgonzgrZHicF6kcYhJLQS0uM4C1cK9S5nh6CP8cZEUUmEar9g5TKQjWivNGl4qNdzm10 4dZOK1/u5xFb6dkrqOZ1a1M2DfrZRxgdz2RwZZbwre88wGKP3taBtvJvFXk26QNEa78fuoGL wqo3jvsn1evswD/JL4bVJl6VGRDNAsqSxkR6209W8qseFA1tw1aFM8XhpZzhchOlshg+ri60 r7fMKln4t1hiaBstROGCCtdLomb4ezxJQddp/SH4K3XdHBJg+4ttjsUmkoUCDgOxaIPydPr+ 7xAH/RbwjSq8A8C7lJVXcwnmEIIpQfaKNaFkSRhp9+/y8e8skhUz6JRopeLnMv0u4o6jos+2 9VGQ8ei+wzA8NgIbWtnxktT4aobI+lWpy7fyC13aMcG64lYbDUjktW7YNxkMYD9+Pk5bBpwJ hf9cXhfdSPPofqUM1DmA1LcrkkO+d/qvTCnHjrrjwvTFGBmvKi+iKUk1pltTvOdc5rkK7oI4 6U6JTYWp9HfvNt7oYCMuILYEftvY0XhBCiUIWUClsQ7WhfqoGpix1peOv4ajzDYeDPo18cMi oyDrAHAeWAOjYWtuDXOtwpdaWkAoxgZOJgcu2ZVUWdGyFYvSCwOxfZYzv5IH/2a3xIjO21OC M5ql+lXb1OFfqsqXYXAhOP5+JhBOa5UN+VBIKG9pxVL48mWCK1GwHN5RX5aB+ky6tvkrK1QD XjCZRxj8o+le03bZ16QiWSEuqxTg2e4/zWY1ZUXBqkxpBVRwT5hzEbLVays0Bju6vKV8CyX9 apFxFf/vWZ347x+/cK6jc/Bpc8u9PSdOBwGC1ReZixmvgL3fI9ozLOlkNXGvjKxx2Ui6v6LB e6SBQsD0cnhRGgfWXgJQsmmPDVPbcmM9+pU/p4JsqdA15lNUILXeeiieFNGHjUORgZtjhY45 Z1Hw8EOfZWBquFbNt5tRaR6UmroviHO4kNx5N39DtfvSegEma6N5QIsTxrpw0IP2NMVXJ9qa 4in+Orrc1EQfljFTLYSyK3zowghdHlZsMUo7d5+AMjDlsjKf9H0f1gMB8hWebQQNlyNOmKa/ uHMcJVlbpGFCOspBgN4N2+s/Ldz+c9hy9W9x7J5U2bcdsdbEKlMF0EkgUE+f3Jhc --------------010900070701080408060501-- rt-5.0.1/t/data/gnupg/emails/5-signed-inline-with-attachment.txt000644 000765 000024 00000002544 14005011336 025303 0ustar00sunnavystaff000000 000000 Message-ID: <46BCDA81.3030308@mit.edu> Date: Fri, 10 Aug 2007 17:37:05 -0400 From: rt-test@example.com User-Agent: Thunderbird 1.5.0.12 (X11/20070604) MIME-Version: 1.0 To: rt-recipient@example.com Subject: Test Email ID:5 X-Enigmail-Version: 0.94.2.0 Content-Type: multipart/mixed; boundary="------------010302000403000103080306" This is a multi-part message in MIME format. --------------010302000403000103080306 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 This is a test email with a text attachment and inline signature. ID:5 -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux) iD8DBQFGvNqA0ygDXYSIHxsRAuCHAKC0HnduLWqihY5wzGYDFGbFtA4chwCgr6+t mQo4oXIqu+kIZ0ExWyiUENs= =3ABp -----END PGP SIGNATURE----- --------------010302000403000103080306 Content-Type: text/plain; name="text-attachment" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="text-attachment" This is a test attachment. The magic word is: zanzibar. --------------010302000403000103080306 Content-Type: application/octet-stream; name="text-attachment.sig" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="text-attachment.sig" iD8DBQBGvNqB0ygDXYSIHxsRAmkbAJ0esGNEDMr01u45ZHIIKZpCFSE8tgCfXBedq0Yu5mnZ zOZyASZYUIf9wSE= --------------010302000403000103080306-- rt-5.0.1/t/data/gnupg/emails/9-encrypted-MIME-with-binary.txt000644 000765 000024 00000004602 14005011336 024435 0ustar00sunnavystaff000000 000000 Received: by anduril (Postfix, from userid 1000) id 215C737F5D; Fri, 10 Aug 2007 15:46:27 -0400 (EDT) Date: Fri, 10 Aug 2007 15:46:27 -0400 To: rt-recipient@example.com Subject: Test Email ID:9 Message-ID: <20070810194627.GA5815@mit.edu> MIME-Version: 1.0 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="GID0FwUMdk1T2AWN" Content-Disposition: inline User-Agent: Mutt/1.5.13 (2006-08-11) From: rt-test@example.com --GID0FwUMdk1T2AWN Content-Type: application/pgp-encrypted Content-Disposition: attachment Version: 1 --GID0FwUMdk1T2AWN Content-Type: application/octet-stream Content-Disposition: inline; filename="msg.asc" -----BEGIN PGP MESSAGE----- Version: GnuPG v1.4.6 (GNU/Linux) hQIOA076g5OuwfIOEAgAqwVRwpeOIRMvrgswuYagJfk57HvSvrAg7tVzXHaHW5LI zJeWpKg0PVwmJUYTG9cZN0gkfpr33SULruvniZRSX650E/vv9H03P1dVYhBbcP01 AyfPoaySinipoqG5OK7aTJVEnPZqqsCKgzFbqEg+FdF3YjNVa1etfmKV/JvoXTxR il6pOqv+xUJ66GpPUoP7mCZJ/cJEF1zz/EbZoYcS9MgxoP42bOIYBhyqIv3fUh7X fuDpup5xnJwM3FRjkNQPlqKhgGHCgk04OGgLUw8wzRwYQ5jSyij45J/UcGoNWePi eVTZignbYIfrlwa5GH6776U9QA9ei7PFP3RqO2CB2Af6AiIdkeN9MfPFfiQ/ASwR /zePg+OOmKC6locoUHWTEuBCZhgty7XarfPZufBHlA3Z3f369Pxz9nMV07KkO22t thhltpsxan2pFZL4oPfwA9OdDRGeWIISn4jGc+foNfKTmERY7EZv9ruDC0lxaSvs HEWJC38sJ3xGdlS439qaddSbm0Lft8JbNwi6FEpThE4abBdITu+BxbkoFrjIy6aC UJgyJ3YtPTpU8JuT49Ocn+51YPXZTSc/ePnvlzSqRXHeJrOKp5Oyoa9001242MRM mpaC1lw/sfGIIAXv5t2B569RXDsZ2jIvPjFIleIdyKOG17m8qsP82nWKbN2N96am p9LpAb5gqid4Hw1PjjjCB0A9w7G9orQeEQuF+2V9MGWAEWOHoVbGWTRjfh4uXASz 4PlazbNDIZZB09ACaDfxG3Y3/TI2X0sl9KJNRALFtHm4+DNbWVeWe+aKrG9ZY9Os xjD/UQFFQrchpNPD7xV98WWEg4+s8pkk9Mwv+Q/gNf2xBXJXDOhIm2LlY0VlhETq fk4YS3EUS78Ti4V99w/L9PTnoaZY/8kkI6NNjb+bqldhd9AQHXEkpV5Gh/PrsgFn FzBZxEwcY8wKP2yKMTp+A3rsjHwM4OChjtkDShf4KDwGS5D+E4o1RJVcM3jfh2SG RRKge5ewErBmIDIqeU8Wpj5cuyqS77CIB3aplSKLqUu2bC2EiLFZxs2UTg+cp5WK WFzt+YmH6/p0y9eyaAGOleSh3dnIZHv2BDtzF9x5fHFesjaVp3jpmMsHQ1ol53TR 8N4fUuO140oE0Gnci61EXzLjkGCSBhzZqy/+K8PR0d1hGv+vlwfadpzsPfX8WcxZ DBuI1E7OcqVaXemR90/C2AbLB8qGuwQ+wTIHPqXlm8s0+6wZ4YUcUoa/F+2VLSxN VWGji1yioKrSaBjlBBUpHfoC/Q/0hLk2FgIrAZFIPvhHl9nF3Vq/HqlxtGZQ7gnL TjQtRuI+oU7IhJRR1VZbhr8xn1pirueiiwJUPub6w4XfMGyvcvlIOVpUImW5Hab7 kg20iwrcJNLCcPJsR5iEbhJBBHLsViMtfdRbstlEV8I4wTY2tNAkfMtUtWdUhwOA fdx/UmHjbNZFSS6cpsGHL8+QJ2Bo1urkHIz6z1w4f1vTL31NAPeyvZ4kNstSqkNt 6fjdrt8mBRVGQfqRQsK2b/R61ErJoFULboeqOT/ed/Dufu2Wxf/wuuRvG77S8xoq OiDT8nhyHh7ljjdHZZ2uLro8gg== =BIke -----END PGP MESSAGE----- --GID0FwUMdk1T2AWN-- rt-5.0.1/t/data/gnupg/emails/16-signed-encrypted-inline-plain.txt000644 000765 000024 00000002764 14005011336 025366 0ustar00sunnavystaff000000 000000 Received: by anduril (Postfix, from userid 1000) id 98F6C37F69; Fri, 10 Aug 2007 16:17:42 -0400 (EDT) Date: Fri, 10 Aug 2007 16:17:42 -0400 To: rt-recipient@example.com Subject: Test Email ID:16 Message-ID: <20070810201742.GF5815@mit.edu> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii; x-action=pgp-encrypted Content-Disposition: inline User-Agent: Mutt/1.5.13 (2006-08-11) From: rt-test@example.com -----BEGIN PGP MESSAGE----- Version: GnuPG v1.4.6 (GNU/Linux) hQIOA076g5OuwfIOEAgAsnA+yHerSZnvxR6TIiWSIRlohPBIQHUhoeHBH+9Q0DFJ r6in5UmN1luVGtT8kl1dbLAnoVu2Yyf/d57IWgam41MbWi1EmGLQaaLHcIbQ3JXR MmXXwG7PTHWdZJismmqCOuT3svTlkqerIBicsHXvfKE209y+jP3lruJ7cVpxZyCI M75H8d313r3MwBpoRuEiNMBOjG6MXOATFmgRw93J6pzjWirxwmawhaSNeghkO8tN 3vdDkzZlLmM/Pq4jQrkWbIbGH/EwdchbWNnhd91o/Lll77fshkXNMYQlgyU154sk 3wCY5IFGJTUdR1hrETz2nOASLDHIdamhhz1xMItClwgA2uXkVhG0Fslp/A7z4a09 ivX/gM6a4KuFSNVJtHrlc6Z8/WKe1LNdLiulbFMtbppvMtIkzgZfv/DBavZJBqVI EOI+9VzLb6IdqybbNp54nRbniU6aiboEk90waSjqHggCnk5qnOUxrxp2ZCIn9pwP KUisxy6cAKGCEeFdvtXIBqfC6uITAu9kNq78rPN64TUaZbJs8VshIj1zpGi6DUYa uPvGPIhlLi5xN/oK3Yu2TDrIPxO4m1ZdHQJH8TBn6l6EifAVsDeskSQ4nCZJzc0H 0D+ofkCzxqRkjDY0IOcl7hI79cRxO5tagtf6od0vcK883wUHxe6Kfv26tKm8IsX1 ANLAFQFi867dLG+X9wc/QyBwNXZ1hPSE8MulfAYyzT/rXno4nkmQrw6zFLf9q4gA Z7aZsgxXPAvWwhwLGyinNgi0ua5LHZL17CWOfG8/GlIK59mesnk8tC9Gyj8aLU3/ ROteGBBGfk0UJbfcQ+reAjmORofZdHSMCsGYZ5DJEy3KIUrNHzW4yYAlfzLWYfX8 k9R41E0xGBuooe578MTBtVOPZKY4gurQZdfrHdYnsUXgfpV1w6WYvEM31n90Px6m aXWFWq9JVxX/JFOmWJV38fw+EhNgApncTw== =J8xa -----END PGP MESSAGE----- rt-5.0.1/t/data/gnupg/emails/10-encrypted-inline-plain.txt000644 000765 000024 00000002602 14005011336 024100 0ustar00sunnavystaff000000 000000 Received: by anduril (Postfix, from userid 1000) id 3AAFC37F5A; Fri, 10 Aug 2007 15:52:03 -0400 (EDT) Date: Fri, 10 Aug 2007 15:52:03 -0400 To: rt-recipient@example.com Subject: Test Email ID:10 Message-ID: <20070810195203.GB5815@mit.edu> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii; x-action=pgp-encrypted Content-Disposition: inline User-Agent: Mutt/1.5.13 (2006-08-11) From: rt-test@example.com -----BEGIN PGP MESSAGE----- Version: GnuPG v1.4.6 (GNU/Linux) hQIOA076g5OuwfIOEAgAyB2wkXAxcNw6+iKwzSlXlyZCsdZFSstJbhIr/i+ujIrk +NLUAIgPfgIeg1nGVVK1z/hszrHRo3FT2b+jSrPF2Ox/wsUqpSvDf641hFbDCIlx bbqNmUEOCP5Q64Bw+RHoDoGLx9CPgeXLbciXvPZtlC3MPqG9w8lQdJlhTM1lqwCs 1kqyvQ8YiFrvCNxU6x/81O9wWwiGOVELVnwX62crUK8howCpmeGx28Uo3HKG7NMv vTsdO6vwh38tNRz0kX05+AlxWP+3Vs4se6YwePc+XnhgWQoHqrYcxTOA0OrivJja 1ty5CAHzGudBdXBcDncg0+6d1Ih60d5JP7nhl3lyzAgArA8CO/iv/kFOkQN9qm4O 4zq189niju7mVCmcTLgBoxN2U1AeAlSl/JacPw4b3CGDQKmj1L/SMKOfUvOvCUlU o6XB7fHPxsFrJag9Yp5snenMRrvoUbypRljebHYsjtkCOsLqK7KUYLx2JQ/pHJjs AXwuyytBpLVxwEy0xkujpAQ4rsYT+z410zdH8hAthFo9FwFGKsT95WzfGcOa2+B3 Qi1LX2uav8q5PAQgbjatp/aiHn3mHZSkRtLbOPr9A8GkmjQaNqIFgUMfaKx2hu8z nIZmAjpZ8CDvyKoFU0g9Z2KqNPagOqHgq6sRKuAp/5nz62sMyl4Hc7UfeYMTHWn/ tNKCAYtyAHAGuvwLiRCN3M5h5Y1DdgEyIri7RtH/LW2QLCqYt9MLvmVRsPIEtuEh tZAb+KQzr9W6+fOhpW/1zkBCGxw2PEhv0HafXsVxEjOITbc2Hfwn9kySZ1aeSxL8 DXzE9fSwcCOXQE1YY7TVNKuqqm34BADCnO7jw/b8EaOHRWgElw== =MYSm -----END PGP MESSAGE----- rt-5.0.1/t/data/gnupg/emails/signed_old_style_with_attachment.eml000644 000765 000024 00000002444 14005011336 026044 0ustar00sunnavystaff000000 000000 Message-ID: <45A10003.1090705@bestpractical.com> Date: Sun, 07 Jan 2007 17:13:23 +0300 From: "Ruslan U. Zakirov" User-Agent: Thunderbird 1.5.0.9 (X11/20061221) MIME-Version: 1.0 To: rt@example.com Subject: test X-Enigmail-Version: 0.94.1.0 Content-Type: multipart/mixed; boundary="------------030903040907010006050500" This is a multi-part message in MIME format. --------------030903040907010006050500 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 inline -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iD8DBQFFoQADtaRiGUNF96URAmHSAKCDdVnRJ2gb1idhE1ZXEg1JARalsQCgkaU8 74cnNxVyp/0XwBA73qzkvx0= =UmxP -----END PGP SIGNATURE----- --------------030903040907010006050500 Content-Type: text/plain; name="test.txt" Content-Transfer-Encoding: base64 Content-Disposition: inline; filename="test.txt" YXR0YWNobWVudAo= --------------030903040907010006050500 Content-Type: application/pgp-signature; name="test.txt.sig" Content-Transfer-Encoding: base64 Content-Disposition: inline; filename="test.txt.sig" iD8DBQBFoQADtaRiGUNF96URAqmBAJ42zyr06nK6R4dNpZD5067DNDgjRwCgkR+SKgz7OdAq p11D7PQGCR1Wuvg= --------------030903040907010006050500-- rt-5.0.1/t/data/gnupg/emails/7-encrypted-MIME-plain.txt000644 000765 000024 00000003344 14005011336 023303 0ustar00sunnavystaff000000 000000 Received: by anduril (Postfix, from userid 1000) id CEA9137F51; Fri, 10 Aug 2007 15:27:49 -0400 (EDT) Date: Fri, 10 Aug 2007 15:27:49 -0400 To: rt-recipient@example.com Subject: Test Email ID:7 Message-ID: <20070810192749.GA5572@mit.edu> MIME-Version: 1.0 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="YiEDa0DAkWCtVeE4" Content-Disposition: inline User-Agent: Mutt/1.5.13 (2006-08-11) From: rt-test@example.com --YiEDa0DAkWCtVeE4 Content-Type: application/pgp-encrypted Content-Disposition: attachment Version: 1 --YiEDa0DAkWCtVeE4 Content-Type: application/octet-stream Content-Disposition: inline; filename="msg.asc" -----BEGIN PGP MESSAGE----- Version: GnuPG v1.4.6 (GNU/Linux) hQIOA076g5OuwfIOEAf9FtBMrkLWUKK5BtwuUsYXV9Mbe/YkkmK61MuysAtLcX/M DiXPzngjL62Dr9l88R3imf2kPmY36yx5WNeXUrFFVmFPaeZrHEJiMNvPGVVQCqRK uar2vsYRK9th4msZnn0hBYnA8+8kZ8rWefWHpszOcJ1YZpyyEMLf8Vnstyf0Pebp Wxixr99+mn3MVH38CrhoErI6CMiCFJgPAl5wtGWd0lT3+657dLJCsNI0cT3AY/JC IJwWD2sdOXOzDo7tdC3l7/YuGsXvd4jGu4A8PdoBMOgPx/N4KT3+uPhp2sRD3PMg LU59613xT8/FOYxQSib9hGqNZPqXS3ryC3ZvY4Sp8AgApCMocKsN7vm8N+6Yh7Nc Jjy/kuf8tjuTTs32Yk0ACU3y2SFXKSBZo6cVXgJhUvmG2Dq4O/A8mtP0cjqeBFqp +vZOb7xhtxxTE6HWWThvx5qxcwjpijzDMS9uzfGaHwLvewdGVLODCup06MJmeAmj N1WEZqc/cqFZvZ9omCpcvTGoELpOzcUY1MxAq1IVkMzAk7dPIHYuyPSFQK6Y8IPl xsfSMcq9gjth8qautnriB0ohwkUebGnxgM4CjGjnSmLmUFXkndUOXKbM7R7E3QdL +6TKMr2pvLl8U3OJrCiyyPVyVi3in4TYi4uegXJl05CAEjEXRf5RFhaWRnn66EYN WdLAAQHpkESfESVUaWvJwI+JB+LMBoKZjWgvIQ7AQKqLAvIsAqs9PKM4mYOMaawl en9XNRkW0dSGUxjW4K8u7fLS/xzWrZeCrafEkvCowVv/nR+Wxm296bxX+7z2R52/ j+J0zms1fRdVxEs+rOI6JuXg4xWxUdLTav7cqvQ5c/izM+jU4yWEa3y0fHma6Jeh o4+1NerQby8Yzxszh9XVfkbYnQilhP8qCVxYe4HGjKlNi5v/xOgCznCKsqkGYMPU S32K6lg= =xeKr -----END PGP MESSAGE----- --YiEDa0DAkWCtVeE4-- rt-5.0.1/t/data/gnupg/emails/special/binary-asc-attach-marked-plain-text.txt000644 000765 000024 00000005460 14005011336 027553 0ustar00sunnavystaff000000 000000 Message-ID: <46BCDCF2.3080704@mit.edu> Date: Fri, 10 Aug 2007 17:47:30 -0400 From: Christian Ternus User-Agent: Thunderbird 1.5.0.12 (X11/20070604) MIME-Version: 1.0 To: rt-recipient@example.com Subject: Test Email ID:12 X-Enigmail-Version: 0.94.2.0 Content-Type: multipart/mixed; boundary="------------090206040704060905090502" This is a multi-part message in MIME format. --------------090206040704060905090502 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 8bit Attached file has .asc extension and content type is text/plain. ASC in some clients stands for ascii, so it's attached as text/plain. --------------090206040704060905090502 Content-Type: text/plain; name="favicon.png.asc" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="favicon.png.asc" hQIOA076g5OuwfIOEAf+P0Qp/k1B0WDRr9bNcEANStTaiefYoLrUrtMJv+aFtkiSqKfft0A9 okrYkVUKs6kxfgxueuqNMFQh58nl8+d7Z2qGIgVEXxC8rRxexEQ8mXu5LXzzBbc6Dq8Jsa7B bXzwGty51culYcKeMEjpEY8Qx76qoNQDNCuvth1JJxJ6xQix/pVyZZbJRu/nLrv1i3Z4KRFY qafnJlcsUTVj2o9dLfeU13z8nd0uBPY+hiCgYJHSPDLr+mkA+c6YK0m4a88r/wjLmsVHMkn2 N5nCjuqP4tzT8SCjhoICGTbu+fFdks9NhQjvsW7MHBi9HFFzm6SoEvquFHThzwMl3hAhTLpi Jwf/a6unMP/swAxoFTJ2GRXBmQOH4sJHR/M31rEVkLZGJixhU94Tpx8ptgLXqme5VCXgl+M3 Oh0GHRXqFYjR/HGUTZokRKR/BgCEpOGlH5FcabHiu/Gy8UBezPbuC+BNvxuCbuwODMp9R5DE F8RSCAQ1hrRoJjeHT2wyE7HdCvN/xx7NyenA3GdJa5Z6W7Y0gshr2fAOFL39jKXw4WwCh/Yq XnyG8uOyPgFrnHI3WpO24VpQHp3MBKebWNhQ/Opy/cABunCSwWQpDB9Ar4GeS3R1WGtMNC4r ph4afmTHJcQQkoa0VfvHL0hEzycwysYD46O9QhZfhxtKKShgX356oCeMEIUCDgOxaIPydPr+ 7xAH+gMnbi5OLPf5xMeZydvNWdHE/wJTub2rrWFtzvj0Aa5Ne/KFhcDDqSjaL3MXP1WfIJr1 /ANe1eWcM2hlYVDpEn6YOh0bz6BASE4kbHA5nGMyUrgH0hWfcOgkMUloRZdf1q32j80mchCJ rF4YsQ6EndnUYzAiXKHGJRUy/6IA2qBH2n/fRiyC2FmmQPtWO4c6t15Vgh4fB3QXSTri8J5r 577yIiHRE+dq6gg1BfyqCtw0DW56lSFQ7dxyMXeLyTGyjTGPlUDc+FbP23CRK7zDIVujARmm mX3bP2lMfCK326FwBZf2Q4Zl/ac1BN8Mcb4wwcnKvRzfEw8d1Y6pkphe7KYH/0MDDqtmuEw2 D/xdw4FHB16/HW32bcPaMVvFuseczEfrwPGCrCiPHPm++edAoY0rWoBtzHVpgN+s5bset5OR snhjuWceuCb+Ga0QV0s/xmIPIQ8VYaXyD5hob6nHEIeskS68Vbni0BpY3nejDPoV3dNHY2Tp 2fjYNHCpsdTz8yyavQVixoQjZQH9hUb48zZDHCt0Af9Rfq6Et5/Qr8iJqAyEU8JzZtrkpO4O HgLU7JTPLxOGzYtOj8JkLmLguVA5kOafAuU4OTEU43utQfS3KYbdEWT2jJ1QaJVS8CjFJrqH V99FUsDvgKWSTy5hA9gkQAQE1QdwkoQKpkCWm18KZqTSwNkBfrsEuvHC3Cz4Sy+cJmhr4Hvx dyZY8DuuWExUcVCjkeuASLgjLEgahnCbMkyKATazswwTEdfzjcOowjLTdaWFEN/Cg22nF/px 9MXMtzBkrTkjYPhfywETKoMVH/Nw7rRNZhkOSb5WJV5ynF1BlbzXI7Z8rA/KrIn8aydzwJUU qFr8Dw1C7kbE76+SFVWX8fqpwGmQhDAO+kos6ivgN9HDHtuXmwfGeROi2U0WcmFGbAyLo5fT LCcNPOMflU27WDXm8m+tjq9naUynqvwg5zBBz/xY67L1R8uOwfZplvRi35iZAJjzMHGirkiB W3ZDXbDqEfKl4aCXqU+XhQZsku2z3OtKZOBVVI5p8nGVEfavg6QECRUNUS7qbtMxlj5IwCGl babK3W5YVuERjklrrLUYZjqFIZ2yLK3Z2VmSn7yKAb/eRvdEeha+9PKcN11pXPkS/M3t+Vpr G+4TqNgqwLVWMvbENp08dS3OAPpZLDnqG9CJV0qacDMjv69X26V3Xp6vuZoKqAPxMG9QKAfX E9LInR1Kd0cpRUkb --------------090206040704060905090502-- rt-5.0.1/t/data/gnupg/emails/special/inline-binary-attachment-with-wrap.txt000644 000765 000024 00000004231 14005011336 027536 0ustar00sunnavystaff000000 000000 Message-ID: <46BCDCF2.3080704@mit.edu> Date: Fri, 10 Aug 2007 17:47:30 -0400 From: Christian Ternus User-Agent: Thunderbird 1.5.0.12 (X11/20070604) MIME-Version: 1.0 To: rt-recipient@example.com Subject: Test Email ID:12 X-Enigmail-Version: 0.94.2.0 Content-Type: multipart/mixed; boundary="------------090206040704060905090502" This is a multi-part message in MIME format. --------------090206040704060905090502 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 8bit A binary file encrypted with PGP MESSAGE wrapping then attached to a mail. As it's .asc some clients attach it as content type is text/plain. --------------090206040704060905090502 Content-Type: text/plain; name="favicon.png.asc" Content-Disposition: attachment; filename="favicon.png.asc" -----BEGIN PGP MESSAGE----- Version: GnuPG v1.4.11 (Darwin) hQIOA076g5OuwfIOEAf/Qxa6rk76JTSjlyb27k1kZwpsvtXocnALZEd1tbJ4HwwH o0CQb5Ew6apUzAcfSFdVcZ+IbFXYmjJ7bH1mpG5kjfSz8mEYO0muX+ZrgVeL+Q3J /lR+A+THD7LNxnsZe11OfDi1aX8Uz71R03vmK+0o1ZIge3zzdm1BoUT9cmy1QPMi aJSdBAJn01EJ++ETfrBqspFj8n6yR/q0BukAcoefaMA0qzrJNQeluMsrqSEjbcuq nwXDEv8fhqF6efrWW+wtyMnY/JpIEwmlNbcCI0NZQFM92vkp89nusRPwbP0+z87m Qsr1vyLHMX79kqwcRKadf1EW/3eWVqlbrOC+yhvKKAf+MAcixgFDIUDup+NuWpjp YUbS357E4yqh2LXogpTZDsvjSL+Z4hKEurKq8TYju8T4exGHkas3VbcWnoPBKiMU 3RjyVNCQzS3XeOtQv9g8WclENq6Pg8eQVNKd5tbpQ6Sios0J1FYFVXHSr/dQOaET Y1jSVKV/6GxVwERpR7DN+yTIVIr//pYnMIb6agNapSKbosvTKNEcGPSjjSB8ixpo C9yiZuIhml5lQUXV9ouDDMtHOFtguDUSSm7Ta84wRJ2VcVQUIv/ADg3jBC5Lmp5Q CGqwq5Hx7q5AKKZDnGNqHepWzeCiEs5Dg9J9onBYx4zrNMrGVP5uvF+RwK4J0pf0 XtLAzgFryuv7cswKKxP9vJaEjLDI4Tizdui+la0vA2tJ6gVQ1CPVtREt0s3s+LM0 oUzgVMvnb1X9KgyazcGp/a+bGHAZwy9/CscxoDYyDrQuUbypNK+f0xefpOURiQ0z N86G/DeaRwLkePoGWZySWk5Ktat6594lRP5h1/B/VYXhvBeh98Rm6E7S8gpjJlMr 6wit2Bd3oPVeqv1fxru9BR1CEl4N+/4ODVwphqMhYM9MplIzGrrCC9bTudKC7g34 UZ+IQV4GLlCK/1BckJQgkWEHTS5fDxAdwG4F7TWfpGzsp43IlDUr073aL7Z3TfkY iyAYGVdnq1EpzVAOPAc2IUMlybUGjoYTmolI1/XIEAxZG9cSFuSMXlrJ2On2kvFO /rla7j5euFpucFWumcNDbZ1HIgDUQ0AMxpzIqwVtd8ZGE0OhSqbT6lgP7jZUwiSA H4g6/4w+aM5XIy26jifvHOlHzsCraG/xGbzXgYkocXbmSuBhtFdoR4KFzOyFaI7H uFoUez7HUuEjl80w77pBeU2v =j0Yd -----END PGP MESSAGE----- --------------090206040704060905090502-- rt-5.0.1/t/data/gnupg/emails/special/quoted_inline_signature.txt000644 000765 000024 00000001175 14005011336 025654 0ustar00sunnavystaff000000 000000 To: rt-recipient@example.com Subject: Test Email ID:4 MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline From: rt-test@example.com Forwarded email with signature quoted. RT shouldn't bother about it. > ------ Forwarded Message > From: Xxx Yyy > Subject: test email > > -----BEGIN PGP SIGNED MESSAGE----- > Hash: SHA1 > > This is a test email with inline signature. > ID:4 > -----BEGIN PGP SIGNATURE----- > Version: GnuPG v1.4.6 (GNU/Linux) > > iD8DBQFGwLI50ygDXYSIHxsRAp40AJ9ErYdLH2SVRXtgRtx7n/FVFOmKDwCgl/0T > BeRSaF4Xbi8uGhVIkmU+YCs= > =e4u6 > -----END PGP SIGNATURE----- rt-5.0.1/t/data/gnupg/keyrings/secring.gpg000644 000765 000024 00000011747 14005011336 021273 0ustar00sunnavystaff000000 000000 •áEˆ¨û÷œmšEɤóψJ©gܾ¢ë ‘ ’ï‰Ô¤èäF†ËÁ¸¤ýVòTHyÀK¼…Äî£U¡Á7¹ €bhu-ì&\‚¢³_vžþÍ]B¡Í¦½­‚ê­¨Þ8ð[ ñ “OKöLzî%ñüßDæbçí~hgG ¢ÌoÜ7UÄ.ʰs}T@Æ9û?¦ÿº‡Šñþ"yj`Ûë( (ÒØ¼ÌJĆ †¢ÙZèÖ¹ÙqZ›ˆ»@[8ÔZ•ÝSæËåƒã»gÆSMHpÏìJûÆ‚ñËWüÔ7n.Ã[ù§î¸M¨DãF?_cϪ»ÔÿÀ~:¨Ö@½æ!žúàwó'FX¥‹UÿkžYm”Fr¯( ÿ9Ÿñ.χsŒ»í Õ½Ç6NÌJq‰N;|[ìiZHHó½’_Ñsx0¸Éóc¡Ê?ÓC؈~©@÷ͱA¤Ðy¡Þˆ:!ɹ¬«‚·­ü‘êéH@ KézÊHÔ×K|âüIJ·löYµ£lãêa«þ1ÁÆ|Ù¡²`G!šÂKœû2¨ZOl'§×ýqÞ<ȯYØc#BWòßõTØÔwÁ–¤£ˆ¨Ø‡Ï^à‡´Test User ˆ` Eˆ¨û € µ¤bCE÷¥”ë ””™îBi×ÞÍÅ#ó@UóyÇy9 ›„˜e„÷ä^›¥3äË“gü°cEˆ©Èr6Ù§…—O“(Õ||"ˆ™h^À jlL§+ð·$ÏK4Wb¸+¥ÙÒâê«ÏÀœ˜ “"~œé­¶RLýõõ;)Y'ÝG*©j^#7ΛèÈ8üÎ*RF f‰d˜`Ä»T@Àž«ù¾ÉH*÷jÆÁ“c‰s¯£‚r¼ØPš [­‘öÃkÈɘ²2dcdË% ìž6GòÙ£ç£";ý@¨ßÎ…×?©Åä%Ì_Ò €óøõøj„¾‡Ö´*°Û“ÉîE³T§Î…D =Ó‡§¬TIñ“},;»»ÐÃäL¶¨™Ä|;÷ŒjúÍ„ëÃ:yóÛÝËÏÌm7ÇÿW¤úã÷²m´81´uš€[Tʉ Ø]äZ²÷‘¿„ü `–ÏšË ™¶­ØvL¡µÉ)·Øæ`•€ô©ã¾ô÷vœg˜]õa€§_ÛK®O¦JHà?¯ÚÑÿGö2‹‘Òrúê,²0÷YšæU°PãôpxVÇ™B>Ã@é!íy빦ù5‚ƒrz9•š=ff‰3)–‰|;ٞΧ ÷¬"ùÕ Ƙ†¾u¸A¨ß1Ç1=gŠä+‡ TP«P+ÊæR^Ê·VŒqÓÚ= ‡GàTc Oo­„{™¸ÍΩW,Þ)ñ¶µNþ(„iäþn¤¢y%g[]—®âcþ1ÁÆ|Ù¡²`™=:(Z%,Êwõyl¡¦ÅTÃ:¥Ás°ÞÅÿ-3ÎÑP×Põ÷¾ßÙ]äW¡Q[ìÚÀt_Ò݃iÿÉ£±‚ I¬%¸© §ÌˆI Eˆ© µ¤bCE÷¥‰I ƒ½˜m:ÆáMhtm!¦Ï»€¦ŸŸeùàÅ„:ùa·ÏÙ 9¡Õ)ën5£™ðâü»ÃGÆëóçš.ßxƒÁp¶¾½XÛ¡Œ£½1lÚÝå{%ã5ߦ6fQÆ:X³4æâ —eN¿Þgñ\/O]ПæîáO ÛCB¼’~•³<‹E|¡ç* à;sýFè,f±›>Õ·scNÔÀPÃa¡Uz¨6úàkÆ&#zkBûÇÀ¶ØD¢2w߀Dî î\C»z­Qø“­?ÇÇÈ-d Öe€°85DœiM϶W€§c“,N¼c¤?›7^ë8„'tG=«dµ|ª¨äú±˜Z瑉!’pÒ¶þ?XA¸ý&nó‚óë-g“´OÜ>–ÑŒEºBhQw­àìýâüî²öEìb½Ò6ᓨu >áøédpávài¶a§›»6­æý23B%ÌazÙWj¬D[3Ùš¼HfDÀíHü’’Ù€ƒöMà1d®ý»þŠŽ•~ïjÀ` ƒ ]µÕü¶³À¹y?ŽÌ†püƒ¿jœVŠ=W u¾€–íßêD€jTŠy=tq+M´!Test User ˆ` EŽ8 € HU툓ëçɶ €;ÃCÆÅ<›¦¡…ü+=¤;³#C ´9ùiŒxÝé¿• ¤lä¨vOQ°cEŽ8°Ý_E°B·§\ ¶_9®²ùóèFrÈ6¡vLˆ'xÃÂ-3ÍŠÖ!~ðSìþµ†‹¡4­yγúÌ 5~å sƒ=…mjo[ß´jîöž’ J¡yWþ'f¾ ÛX:¼N?r ËwpJl a7Hê†Ü¸âËï‘xH’&>jMa€õ¹–н‹eä0o2ºSªPÔ},ê¯=óe†ƒßAT´Ó©òSTBâÆ;hI ðz¾øšþÏWÊWg€ñp¹1 ¿&A]fú“ Svÿï!AÉJ9“Ù®ýï ¡Ææ‚×·–­_K1±˜IÔ7¢ÊÆA ëñÖBwgþ'“&`¦»PôaìCá Kêêí6ØÍ´–sö—’u×úGt>¦µÑ›´Iù™ãsˆÜf0¾Ú›Ìn;œ¹ªË¡m¶d¿h¿®Ð¤œŠ$‚Í,‰ÖÂ*±¡ܨ$äžo Ü; ’ñ¬KÐ4¡51*~dP—Š’vUvÓYéÏ7§Ã¿›Ý)ëvpæ“èß²ôüo#S z¾t1Vg"ìS3>âÔÐ|­j‡˜Œu ·Ã-y¦ °f‰2;8Õœ°! ª%rðôKùxø“:—ñò}"3•PµÛôâ=+Æ3ø)%Ú)ݵRiVx^}_ndú;·îl z‚³9œ€ÍþŠŽ•~ïjÀ`4ÀºÛÀ7<\XÙH‘lÞãt›ˆ¢úÞ»ÇØj" ;±…“UƒåC‘ÏpÐ. "¥#×L”ÎùÇžƒvEvi‘=™c,ºQ³è¢ÁˆI EŽ8 HU툓ëçá —uá€6—{Ta°5ú *@“þ «n¦‚kÐøÖÚ™LüF?°•»F+v©O#H—!Å_kbcÿ”{ݱglZä¿·,îFXžó @ž{¡wÜ’šÒÓÉ?ÂÔÆó£ûsá( }ÛfQ ^ë)ѲìK=Rÿ™¬”‘t I>c1jJsZÁÀæÁß¿ÁËŸêˆ` F+v € Ÿ¦bÀmâ/Â7 Á·‰‰”k ŠœòIj‰²Q¾Ÿi–E$䵯îwrµ)3häć°=F+v0«á£*ˆbl"c eÐÃÈ—6×6‹Ào ~±5rD:ã– ´"Û^³Uj‰±;Ñüö®NüK÷ØÏìD€“2}œÎTó1c@»»vÇÉ:•ÿAp(ÈÆ3V•ÚéjiVK&¾!¥Ücƒ)ßMJ­˜ØµCÀÇ,yïÎF|®HœhmÒlŽÊ²> 9¸{dþ1¦ ÓøosÇ&ïB%z·uï$iXÆ¿n–—5d±®~¸ð:Ѝò „hý+þWt€5#˜%‚=¶r…âÌ` °:çÖ6{ǽ³KHB’z§@ŒpfhØž]àšß —¿o•¸"×àKýƒû"?‹þ!$ä‡jXÙ1^Ѧ†3µ-,AþùŸLÉ:D/ò9hèÅpÏñÖsÙ©ÚÅy¢@~K€è—úi¬Z³é"±Ü66µØJöÔºæè!ŸÊªv× eÃ8ੈoQÀºÒåº0I±hè°à€~,ïÎF+<-ÓG~%žZEÖzÆÁJèåƒqÔ‡ÐgºÍ…ôQËÈèyŽ™áw3Ç™D}p½HPµýl;±”ŠwwçkÛ)è5/„`‹§æ#ØåÞUÜÔlÈâPÍ3&íÕ4;*ôƒùÕÝÌ"Æã…ý|؈2©ìà OOä}‘wR>_V\Q" R{&½ˆ4T tC(˜·8ôÏg7™ &‘-B{LHøDBáv%EOfrS{@£Ýù|@ˆI F+v0 Ÿ¦bÀmâ/Âì ¨d*KpiÙiÝB¦I¼pR»‡ôT ²Tø"’EÔ[2ð>£+ØV ÷ƒ÷°•»F,w™“>|iÀ1}a9šc¨Irʼnó…Âð g+n/ÜŠ5‘‰ tlß)¯„æ?Ö)÷®‘)¥ÞŸsŸ—²ú›±1Äž Üõ|³²nçò²(ÂÛ™öølåtÂ¥Q:Qªiò(©{d†ÝëcÃ#“7_œyeÑñ¯^{dlë; ›g¾0º†—*ƒ¯ÆÁËW…ÙQéþ3šó+q;bó‹­·’ÌòAp…r5­ê+ ”óL<»™VæÏ >9GÝâM¹ ú®çpéT±zAŠ ^¸Šb¯º(Íiü˜†Me¶ö7ÚÎsˆà_å¨zÐë)“L¼z°€ªbq¥2që+/~ø=ªv#3}Ẍ¦Õ(Ò»Qþ)3$„:¼’eqNg¼eg䌵fßR:=Ÿ®²ÒƸÉgË)‹z²…ø¹ ªMté“ddQŽÖ¼(êH„ˆY{¶êµA~å«ð‹*|5Š“pd±¼¼±]¡¡˜Fdˆ’¹5šS+yÄ—–ð]œn±ÚáÏ’SÝc_’6»AS®N—kS+½t NúŸV lk´#random@localhost ˆ` F,w™ €  ÈŒßXDþæ{¾ŸUü) Þ³ÂÖ§©_n@#²Ÿo¥8FX˜g8JØAæ4ï½ ©«°=F,w¥íôìÆˆm*y6”©1vÕ}å%±Ü}/ÃÇÓæ€ 1ž¦“¼^ï¹ê­Ô$Ä gl¦6y0dÔ ¿Ë¶‘vöf¹%nÀ¼± 1‹ªÿAÞì {&’|eîó^6c ˆŠêUì¿GÃÓ šfCàÈïi5ñ+dmß¹k¯Q¬’i’ü÷Å»Sp~FÁCOEºaû{†¸ÈPÖ0 ‘Yg©„O÷ÛÛVO„„á)eÖLr§ho]Cî(ûxùŒXÍ”$„¸ß™ÿ¶ ÎHg0“^r?õmAºx0cÿ’ÕR6÷¸T8zž¤W4ç$ÑM3ço}Ýi­9õ_zÁÀæ—@YŠÂ²è‡I(bµ(Ô¬õ—RöúËœ!’9¤ç ù’X£Ñ$ÇrÒ”ÃÝ» ×vm¶fLÓäy«î 1X‡ùÐ>äY&¥^­:}xb±–{Ì‚ž÷5B› ›ù~@kÂcÃDŠßÛûöXä£|nªo‚f«‡‡ŠDm¬¡Ë}½Ì_C’¸¿Tÿ!<¡ÁàNÏ_jÝÐiÂøºõgäý— ´ƒhçˆ` Eˆ¨û € µ¤bCE÷¥”ë ””™îBi×ÞÍÅ#ó@UóyÇy9 ›„˜e„÷ä^›¥3äË“gü°¹ Eˆ©Èr6Ù§…—O“(Õ||"ˆ™h^À jlL§+ð·$ÏK4Wb¸+¥ÙÒâê«ÏÀœ˜ “"~œé­¶RLýõõ;)Y'ÝG*©j^#7ΛèÈ8üÎ*RF f‰d˜`Ä»T@Àž«ù¾ÉH*÷jÆÁ“c‰s¯£‚r¼ØPš [­‘öÃkÈɘ²2dcdË% ìž6GòÙ£ç£";ý@¨ßÎ…×?©Åä%Ì_Ò €óøõøj„¾‡Ö´*°Û“ÉîE³T§Î…D =Ó‡§¬TIñ“},;»»ÐÃäL¶¨™Ä|;÷ŒjúÍ„ëÃ:yóÛÝËÏÌm7ÇÿW¤úã÷²m´81´uš€[Tʉ Ø]äZ²÷‘¿„ü `–ÏšË ™¶­ØvL¡µÉ)·Øæ`•€ô©ã¾ô÷vœg˜]õa€§_ÛK®O¦JHà?¯ÚÑÿGö2‹‘Òrúê,²0÷YšæU°PãôpxVÇ™B>Ã@é!íy빦ù5‚ƒrz9•š=ff‰3)–‰|;ٞΧ ÷¬"ùÕ Ƙ†¾u¸A¨ß1Ç1=gŠä+‡ TP«P+ÊæR^Ê·VŒqÓÚ= ‡GàTc Oo­„{™¸ÍΩW,Þ)ñ¶µNþ(„iäþn¤¢y%g[]—®âcˆI Eˆ© µ¤bCE÷¥‰IŸ[ê ËÊÎ,{J  ˜ku\t3‡ŸjUðÃë UÁ„üè·3Ø¿àA°™¢EŽ8¥}Ò¶Þª[l;r´§â­f@ñZ)Ž4_1%[>ùàÅ„:ùa·ÏÙ 9¡Õ)ën5£™ðâü»ÃGÆëóçš.ßxƒÁp¶¾½XÛ¡Œ£½1lÚÝå{%ã5ߦ6fQÆ:X³4æâ —eN¿Þgñ\/O]ПæîáO ÛCB¼’~•³<‹E|¡ç* à;sýFè,f±›>Õ·scNÔÀPÃa¡Uz¨6úàkÆ&#zkBûÇÀ¶ØD¢2w߀Dî î\C»z­Qø“­?ÇÇÈ-d Öe€°85DœiM϶W€§c“,N¼c¤?›7^ë8„'tG=«dµ|ª¨äú±˜Z瑉!’pÒ¶þ?XA¸ý&nó‚óë-g“´OÜ>–ÑŒEºBhQw­àìýâüî²öEìb½Ò6ᓨu >áøédpávài¶a§›»6­æý23B%ÌazÙWj¬D[3Ùš¼HfDÀíHü’’Ù€ƒöMà1d®ý»´!Test User ˆ` EŽ8 € HU툓ëçɶ €;ÃCÆÅ<›¦¡…ü+=¤;³#C ´9ùiŒxÝé¿• ¤lä¨vOQ°¹ EŽ8°Ý_E°B·§\ ¶_9®²ùóèFrÈ6¡vLˆ'xÃÂ-3ÍŠÖ!~ðSìþµ†‹¡4­yγúÌ 5~å sƒ=…mjo[ß´jîöž’ J¡yWþ'f¾ ÛX:¼N?r ËwpJl a7Hê†Ü¸âËï‘xH’&>jMa€õ¹–н‹eä0o2ºSªPÔ},ê¯=óe†ƒßAT´Ó©òSTBâÆ;hI ðz¾øšþÏWÊWg€ñp¹1 ¿&A]fú“ Svÿï!AÉJ9“Ù®ýï ¡Ææ‚×·–­_K1±˜IÔ7¢ÊÆA ëñÖBwgþ'“&`¦»PôaìCá Kêêí6ØÍ´–sö—’u×úGt>¦µÑ›´Iù™ãsˆÜf0¾Ú›Ìn;œ¹ªË¡m¶d¿h¿®Ð¤œŠ$‚Í,‰ÖÂ*±¡ܨ$äžo Ü; ’ñ¬KÐ4¡51*~dP—Š’vUvÓYéÏ7§Ã¿›Ý)ëvpæ“èß²ôüo#S z¾t1Vg"ìS3>âÔÐ|­j‡˜Œu ·Ã-y¦ °f‰2;8Õœ°! ª%rðôKùxø“:—ñò}"3•PµÛôâ=+Æ3ø)%Ú)ݵRiVx^}_ndú;·îl z‚³9œ€ÍˆI EŽ8 HU툓ëçádz)p ‚×6êÖX7Óiš/™œ ˜åÞò^÷ZÛ¨£+rÛ4ù?°™¢F+v©O#H—!Å_kbcÿ”{ݱglZä¿·,îFXžó @ž{¡wÜ’šÒÓÉ?ÂÔÆó£ûsá( }ÛfQ ^ë)ѲìK=Rÿ™¬”‘t I>c1jJsZÁÀæÁß¿ÁËŸêˆ` F+v € Ÿ¦bÀmâ/Â7 Á·‰‰”k ŠœòIj‰²Q¾Ÿi–E$䵯îwrµ)3häć°¹ F+v0«á£*ˆbl"c eÐÃÈ—6×6‹Ào ~±5rD:ã– ´"Û^³Uj‰±;Ñüö®NüK÷ØÏìD€“2}œÎTó1c@»»vÇÉ:•ÿAp(ÈÆ3V•ÚéjiVK&¾!¥Ücƒ)ßMJ­˜ØµCÀÇ,yïÎF|®HœhmÒlŽÊ²> 9¸{dþ1¦ ÓøosÇ&ïB%z·uï$iXÆ¿n–—5d±®~¸ð:Ѝò „hý+þWt€5#˜%‚=¶r…âÌ` °:çÖ6{ǽ³KHB’z§@ŒpfhØž]àšß —¿o•¸"×àKýƒû"?‹þ!$ä‡jXÙ1^Ѧ†3µ-,AþùŸLÉ:D/ò9hèÅpÏñÖsÙ©ÚÅy¢@~K€è—úi¬Z³é"±Ü66µØJöÔºæè!ŸÊªv× eÃ8ੈoQÀºÒåº0I±hè°à€~,ïÎF+<-ÓG~%žZEÖzÆÁJèåƒqÔ‡ÐgºÍ…ôQËÈèyŽ™áw3Ç™D}p½HPµýl;±”ŠwwçkÛ)è5/„`‹§æ#ØåÞUÜÔlÈâPÍ3&íÕ4;*ôƒùÕÝÌ"Æã…ý|؈2©ìà OOä}‘wR>_V\Q" R{&½ˆ4ˆI F+v0 Ÿ¦bÀmâ/ÂìŸT;{¦Ó†• šp•º* ˜>—å¤[Ã6}" °;jU(±ä°™¢F,w™“>|iÀ1}a9šc¨Irʼnó…Âð g+n/ÜŠ5‘‰ tlß)¯„æ?Ö)÷®‘)¥ÞŸsŸ—²ú›±1Äž Üõ|³²nçò²(ÂÛ™öølåtÂ¥Q:Qªiò(©{d†ÝëcÃ#“7_œyeÑñ¯^{dlë; ›g¾0º†—*ƒ¯ÆÁËW…ÙQéþ3šó+q;bó‹­·’ÌòAp…r5­ê+ ”óL<»™VæÏ >9GÝâM¹ ú®çpéT±zAŠ ^¸Šb¯º(Íiü˜†Me¶ö7ÚÎsˆà_å¨zÐë)“L¼z°€ªbq¥2që+/~ø=ªv#3}Ẍ¦Õ(Ò»Qþ)3$„:¼’eqNg¼eg䌵fßR:=Ÿ®²ÒƸÉgË)‹z²…ø¹ ªMté“ddQŽÖ¼(êH„ˆY{¶êµA~å«ð‹*|5Š“pd±¼¼±]¡¡˜Fdˆ’¹5šS+yÄ—–ð]œn±ÚáÏ’SÝc_’6»AS´#random@localhost ˆ` F,w™ €  ÈŒßXDþæ{¾ŸUü) Þ³ÂÖ§©_n@#²Ÿo¥8FX˜g8JØAæ4ï½ ©«°¹ F,w¥íôìÆˆm*y6”©1vÕ}å%±Ü}/ÃÇÓæ€ 1ž¦“¼^ï¹ê­Ô$Ä gl¦6y0dÔ ¿Ë¶‘vöf¹%nÀ¼± 1‹ªÿAÞì {&’|eîó^6c ˆŠêUì¿GÃÓ šfCàÈïi5ñ+dmß¹k¯Q¬’i’ü÷Å»Sp~FÁCOEºaû{†¸ÈPÖ0 ‘Yg©„O÷ÛÛVO„„á)eÖLr§ho]Cî(ûxùŒXÍ”$„¸ß™ÿ¶ ÎHg0“^r?õmAºx0cÿ’ÕR6÷¸T8zž¤W4ç$ÑM3ço}Ýi­9õ_zÁÀæ—@YŠÂ²è‡I(bµ(Ô¬õ—RöúËœ!’9¤ç ù’X£Ñ$ÇrÒ”ÃÝ» ×vm¶fLÓäy«î 1X‡ùÐ>äY&¥^­:}xb±–{Ì‚ž÷5B› ›ù~@kÂcÃDŠßÛûöXä£|nªo‚f«‡‡ŠDm¬¡Ë}½Ì_C’¸¿Tÿ!<¡ÁàNÏ_jÝÐiÂøºõgäý— ´ƒhç StartServers 1 MinSpareServers 1 MaxSpareServers 1 MaxClients 1 MaxRequestsPerChild 0 StartServers 1 MinSpareThreads 1 MaxSpareThreads 1 ThreadLimit 1 ThreadsPerChild 1 MaxClients 1 MaxRequestsPerChild 0 ServerRoot %%SERVER_ROOT%% PidFile %%PID_FILE%% LockFile %%LOCK_FILE%% ServerAdmin root@localhost %%LOAD_MODULES%% User www Group www ServerName localhost Listen %%LISTEN%% ErrorLog "%%LOG_FILE%%" LogLevel debug Options FollowSymLinks AllowOverride None Order deny,allow Deny from all AddDefaultCharset UTF-8 PerlSetEnv RT_SITE_CONFIG %%RT_SITE_CONFIG%% DocumentRoot "%%DOCUMENT_ROOT%%" Order allow,deny Allow from all %%BASIC_AUTH%% SetHandler modperl PerlResponseHandler Plack::Handler::Apache2 PerlSetVar psgi_app %%RT_SBIN_PATH%%/rt-server $ENV{RT_TESTING}=1; use Plack::Handler::Apache2; Plack::Handler::Apache2->preload("%%RT_SBIN_PATH%%/rt-server"); rt-5.0.1/t/data/configs/passwords000644 000765 000024 00000000045 14005011336 017554 0ustar00sunnavystaff000000 000000 # root / password root:8NbrT44Shvnco rt-5.0.1/t/data/configs/apache2.4+mod_perl.conf.in000644 000765 000024 00000002410 14005011336 022320 0ustar00sunnavystaff000000 000000 StartServers 1 MinSpareServers 1 MaxSpareServers 1 MaxClients 1 MaxRequestsPerChild 0 StartServers 1 MinSpareThreads 1 MaxSpareThreads 1 ThreadLimit 1 ThreadsPerChild 1 MaxClients 1 MaxRequestsPerChild 0 ServerRoot %%SERVER_ROOT%% PidFile %%PID_FILE%% ServerAdmin root@localhost %%LOAD_MODULES%% User @WEB_USER@ Group @WEB_GROUP@ ServerName localhost Listen %%LISTEN%% ErrorLog "%%LOG_FILE%%" LogLevel debug Options FollowSymLinks AllowOverride None Require all denied AddDefaultCharset UTF-8 PerlSetEnv RT_SITE_CONFIG %%RT_SITE_CONFIG%% DocumentRoot "%%DOCUMENT_ROOT%%" Require all granted %%BASIC_AUTH%% SetHandler modperl PerlResponseHandler Plack::Handler::Apache2 PerlSetVar psgi_app %%RT_SBIN_PATH%%/rt-server $ENV{RT_TESTING}=1; use Plack::Handler::Apache2; Plack::Handler::Apache2->preload("%%RT_SBIN_PATH%%/rt-server"); rt-5.0.1/t/data/configs/apache2.2+fastcgi.conf000644 000765 000024 00000001547 14005021226 021541 0ustar00sunnavystaff000000 000000 ServerRoot %%SERVER_ROOT%% PidFile %%PID_FILE%% LockFile %%LOCK_FILE%% ServerAdmin root@localhost %%LOAD_MODULES%% User www Group www ServerName localhost Listen %%LISTEN%% ErrorLog "%%LOG_FILE%%" LogLevel debug Options FollowSymLinks AllowOverride None Order deny,allow Deny from all AddDefaultCharset UTF-8 FastCgiServer %%RT_SBIN_PATH%%/rt-server.fcgi \ -socket %%TMP_DIR%%/socket \ -processes 1 \ -idle-timeout 180 \ -initial-env RT_SITE_CONFIG=%%RT_SITE_CONFIG%% \ -initial-env RT_TESTING=1 ScriptAlias / %%RT_SBIN_PATH%%/rt-server.fcgi/ DocumentRoot "%%DOCUMENT_ROOT%%" Order allow,deny Allow from all %%BASIC_AUTH%% Options +ExecCGI AddHandler fastcgi-script fcgi rt-5.0.1/t/data/configs/apache2.4+fastcgi.conf000644 000765 000024 00000001527 14005021226 021541 0ustar00sunnavystaff000000 000000 ServerRoot %%SERVER_ROOT%% PidFile %%PID_FILE%% ServerAdmin root@localhost %%LOAD_MODULES%% User www Group www ServerName localhost Listen %%LISTEN%% ErrorLog "%%LOG_FILE%%" LogLevel debug Options FollowSymLinks AllowOverride None Require all denied AddDefaultCharset UTF-8 FastCgiServer %%RT_SBIN_PATH%%/rt-server.fcgi \ -socket %%TMP_DIR%%/socket \ -processes 1 \ -idle-timeout 180 \ -initial-env RT_SITE_CONFIG=%%RT_SITE_CONFIG%% \ -initial-env RT_TESTING=1 ScriptAlias / %%RT_SBIN_PATH%%/rt-server.fcgi/ DocumentRoot "%%DOCUMENT_ROOT%%" Require all granted %%BASIC_AUTH%% Options +ExecCGI AddHandler fastcgi-script fcgi rt-5.0.1/t/data/configs/apache2.4+fastcgi.conf.in000644 000765 000024 00000001546 14005011336 022150 0ustar00sunnavystaff000000 000000 ServerRoot %%SERVER_ROOT%% PidFile %%PID_FILE%% ServerAdmin root@localhost %%LOAD_MODULES%% User @WEB_USER@ Group @WEB_GROUP@ ServerName localhost Listen %%LISTEN%% ErrorLog "%%LOG_FILE%%" LogLevel debug Options FollowSymLinks AllowOverride None Require all denied AddDefaultCharset UTF-8 FastCgiServer %%RT_SBIN_PATH%%/rt-server.fcgi \ -socket %%TMP_DIR%%/socket \ -processes 1 \ -idle-timeout 180 \ -initial-env RT_SITE_CONFIG=%%RT_SITE_CONFIG%% \ -initial-env RT_TESTING=1 ScriptAlias / %%RT_SBIN_PATH%%/rt-server.fcgi/ DocumentRoot "%%DOCUMENT_ROOT%%" Require all granted %%BASIC_AUTH%% Options +ExecCGI AddHandler fastcgi-script fcgi rt-5.0.1/t/data/configs/apache2.2+fastcgi.conf.in000644 000765 000024 00000001566 14005011336 022150 0ustar00sunnavystaff000000 000000 ServerRoot %%SERVER_ROOT%% PidFile %%PID_FILE%% LockFile %%LOCK_FILE%% ServerAdmin root@localhost %%LOAD_MODULES%% User @WEB_USER@ Group @WEB_GROUP@ ServerName localhost Listen %%LISTEN%% ErrorLog "%%LOG_FILE%%" LogLevel debug Options FollowSymLinks AllowOverride None Order deny,allow Deny from all AddDefaultCharset UTF-8 FastCgiServer %%RT_SBIN_PATH%%/rt-server.fcgi \ -socket %%TMP_DIR%%/socket \ -processes 1 \ -idle-timeout 180 \ -initial-env RT_SITE_CONFIG=%%RT_SITE_CONFIG%% \ -initial-env RT_TESTING=1 ScriptAlias / %%RT_SBIN_PATH%%/rt-server.fcgi/ DocumentRoot "%%DOCUMENT_ROOT%%" Order allow,deny Allow from all %%BASIC_AUTH%% Options +ExecCGI AddHandler fastcgi-script fcgi rt-5.0.1/t/data/configs/apache2.2+mod_perl.conf.in000644 000765 000024 00000002430 14005011336 022320 0ustar00sunnavystaff000000 000000 StartServers 1 MinSpareServers 1 MaxSpareServers 1 MaxClients 1 MaxRequestsPerChild 0 StartServers 1 MinSpareThreads 1 MaxSpareThreads 1 ThreadLimit 1 ThreadsPerChild 1 MaxClients 1 MaxRequestsPerChild 0 ServerRoot %%SERVER_ROOT%% PidFile %%PID_FILE%% LockFile %%LOCK_FILE%% ServerAdmin root@localhost %%LOAD_MODULES%% User @WEB_USER@ Group @WEB_GROUP@ ServerName localhost Listen %%LISTEN%% ErrorLog "%%LOG_FILE%%" LogLevel debug Options FollowSymLinks AllowOverride None Order deny,allow Deny from all AddDefaultCharset UTF-8 PerlSetEnv RT_SITE_CONFIG %%RT_SITE_CONFIG%% DocumentRoot "%%DOCUMENT_ROOT%%" Order allow,deny Allow from all %%BASIC_AUTH%% SetHandler modperl PerlResponseHandler Plack::Handler::Apache2 PerlSetVar psgi_app %%RT_SBIN_PATH%%/rt-server $ENV{RT_TESTING}=1; use Plack::Handler::Apache2; Plack::Handler::Apache2->preload("%%RT_SBIN_PATH%%/rt-server"); rt-5.0.1/t/data/configs/apache2.4+mod_perl.conf000644 000765 000024 00000002371 14005021226 021720 0ustar00sunnavystaff000000 000000 StartServers 1 MinSpareServers 1 MaxSpareServers 1 MaxClients 1 MaxRequestsPerChild 0 StartServers 1 MinSpareThreads 1 MaxSpareThreads 1 ThreadLimit 1 ThreadsPerChild 1 MaxClients 1 MaxRequestsPerChild 0 ServerRoot %%SERVER_ROOT%% PidFile %%PID_FILE%% ServerAdmin root@localhost %%LOAD_MODULES%% User www Group www ServerName localhost Listen %%LISTEN%% ErrorLog "%%LOG_FILE%%" LogLevel debug Options FollowSymLinks AllowOverride None Require all denied AddDefaultCharset UTF-8 PerlSetEnv RT_SITE_CONFIG %%RT_SITE_CONFIG%% DocumentRoot "%%DOCUMENT_ROOT%%" Require all granted %%BASIC_AUTH%% SetHandler modperl PerlResponseHandler Plack::Handler::Apache2 PerlSetVar psgi_app %%RT_SBIN_PATH%%/rt-server $ENV{RT_TESTING}=1; use Plack::Handler::Apache2; Plack::Handler::Apache2->preload("%%RT_SBIN_PATH%%/rt-server"); rt-5.0.1/t/data/emails/russian-subject-no-content-type000644 000765 000024 00000004057 14005011336 023542 0ustar00sunnavystaff000000 000000 Return-Path: X-Real-To: Received: from [194.87.5.31] (HELO sinbin.example.com) by cgp.second.example.com (CommuniGate Pro SMTP 4.0.5/D) with ESMTP-TLS id 69661026 for mitya@second.example.com; Wed, 18 Jun 2003 11:14:49 +0400 Received: (from daemon@localhost) by sinbin.example.com (8.12.8/8.11.6) id h5I7EfOj096595 for mitya@second.example.com; Wed, 18 Jun 2003 11:14:41 +0400 (MSD) (envelope-from mitya@fling-wing.example.com) Received: from example.com by sinbin.example.com with ESMTP id h5I7Ee8K096580; (8.12.9/D) Wed, 18 Jun 2003 11:14:40 +0400 (MSD) X-Real-To: Received: from [194.87.0.31] (HELO mail.example.com) by example.com (CommuniGate Pro SMTP 4.1b7/D) with ESMTP id 76217696 for mitya@example.com; Wed, 18 Jun 2003 11:14:40 +0400 Received: by mail.example.com (CommuniGate Pro PIPE 4.1b7/D) with PIPE id 63920083; Wed, 18 Jun 2003 11:14:40 +0400 Received: from [194.87.5.69] (HELO fling-wing.example.com) by mail.example.com (CommuniGate Pro SMTP 4.1b7/D) with ESMTP-TLS id 63920055 for mitya@example.com; Wed, 18 Jun 2003 11:14:38 +0400 Received: from fling-wing.example.com (localhost [127.0.0.1]) by fling-wing.example.com (8.12.9/8.12.6) with ESMTP id h5I7Ec5R000153 for ; Wed, 18 Jun 2003 11:14:38 +0400 (MSD) (envelope-from mitya@fling-wing.example.com) Received: (from mitya@localhost) by fling-wing.example.com (8.12.9/8.12.6/Submit) id h5I7Ec0J000152 for mitya@example.com; Wed, 18 Jun 2003 11:14:38 +0400 (MSD) Date: Wed, 18 Jun 2003 11:14:38 +0400 (MSD) From: "Dmitry S. Sivachenko" Message-Id: <200306180714.h5I7Ec0J000152@fling-wing.example.com> To: mitya@example.com Subject: ÔÅÓÔ ÔÅÓÔ X-Spam-Checker-Version: SpamAssassin 2.60-cvs-mail.demos (1.193-2003-06-13-exp) X-Spam-Level: + X-Spam-Status: No, hits=1.0 required=5.0 tests=SUBJ_ILLEGAL_CHARS autolearn=no version=2.60-cvs-mail.demos X-Spam-Report: * SUBJ_ILLEGAL_CHARS 1.0 (Subject contains too many raw illegal characters) Content-Length: 6 ôåóô rt-5.0.1/t/data/emails/email-file-attachment.eml000644 000765 000024 00000004733 14005011336 022267 0ustar00sunnavystaff000000 000000 Return-Path: X-Spam-Flag: NO X-Spam-Score: -2.449 X-Spam-Level: X-Spam-Status: No, score=-2.449 tagged_above=-99.9 required=10 tests=[AWL=0.250, BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, FREEMAIL_FROM=0.001, KHOP_DYNAMIC=0.001, RCVD_IN_DNSWL_LOW=-0.7, SPF_PASS=-0.001] autolearn=ham X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10432:,, definitions=2017-07-28_05:,, signatures=0 X-Proofpoint-Spam-Details: rule=notspam policy=default score=0 spamscore=0 clxscore=1034 suspectscore=1 malwarescore=0 phishscore=0 adultscore=0 bulkscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.0.1-1701120000 definitions=main-1707280209 From: Jim Brandt Content-type: multipart/mixed; boundary="Apple-Mail=_E5E623A8-7064-4736-9F2E-2A0F35E3635B" Subject: This is a test Message-id: Date: Fri, 28 Jul 2017 09:21:48 -0400 To: rt@example.com MIME-version: 1.0 (Mac OS X Mail 9.3 \(3124\)) X-Mailer: Apple Mail (2.3124) --Apple-Mail=_E5E623A8-7064-4736-9F2E-2A0F35E3635B Content-Transfer-Encoding: 7bit Content-Type: text/plain; charset=us-ascii This is a test with an email file attachment. --Apple-Mail=_E5E623A8-7064-4736-9F2E-2A0F35E3635B Content-Disposition: attachment; filename=test-email.eml Content-Type: message/rfc822; name="test-email.eml" Content-Transfer-Encoding: binary Return-Path: X-Spam-Flag: NO X-Spam-Score: -2.199 X-Spam-Level: X-Spam-Status: No, score=-2.199 tagged_above=-99.9 required=10 tests=[AWL=0.500, BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, FREEMAIL_FROM=0.001, KHOP_DYNAMIC=0.001, RCVD_IN_DNSWL_LOW=-0.7, SPF_PASS=-0.001] autolearn=ham X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10432:,, definitions=2017-07-28_05:,, signatures=0 X-Proofpoint-Spam-Details: rule=notspam policy=default score=0 spamscore=0 clxscore=1034 suspectscore=1 malwarescore=0 phishscore=0 adultscore=0 bulkscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.0.1-1701120000 definitions=main-1707280209 From: Jim Brandt Content-type: text/plain; charset=us-ascii Content-transfer-encoding: 7bit Subject: This is a test Message-id: <8DB138B7-D410-4640-81B0-7A3363EB0180@mac.com> Date: Fri, 28 Jul 2017 09:19:28 -0400 To: rt@example.com MIME-version: 1.0 (Mac OS X Mail 9.3 \(3124\)) X-Mailer: Apple Mail (2.3124) This is a test. --Apple-Mail=_E5E623A8-7064-4736-9F2E-2A0F35E3635B-- rt-5.0.1/t/data/emails/crashes-file-based-parser000644 000765 000024 00000020453 14005011336 022271 0ustar00sunnavystaff000000 000000 X-Real-To: Received: from [194.87.5.31] (HELO sinbin.d-s.example.com) by cgp.example.com (CommuniGate Pro SMTP 4.0.6/D4) with ESMTP-TLS id 125035761 for mitya@example.com; Thu, 11 Dec 2003 15:17:46 +0300 Received: (from daemon@localhost) by sinbin.d-s.example.com (8.12.9p1/8.11.6) id hBBCHjN0031595 for mitya@example.com; Thu, 11 Dec 2003 15:17:45 +0300 (MSK) (envelope-from noc@rt3.mx.example.com) Received: from d-s.example.com by sinbin.d-s.example.com with ESMTP id hBBCHjar031575; (8.12.9p2/D) Thu, 11 Dec 2003 15:17:45 +0300 (MSK) X-Real-To: Sender: (Network Operation Center) To: mitya@example.com Date: Thu, 11 Dec 2003 15:17:45 +0300 Message-ID: X-Original-Return-Path: Received: from [194.87.0.16] (HELO mail.d-s.example.com) by d-s.example.com (CommuniGate Pro SMTP 4.1.5/D1) with ESMTP id 120757484 for noc@rt3.mx.example.com; Mon, 27 Oct 2003 09:40:53 +0300 Received: from [194.87.0.22] (HELO moscvax.d-s.example.com) by mail.d-s.example.com (CommuniGate Pro SMTP 4.1.5/D) with ESMTP-TLS id 107945800 for noc@rt3.mx.example.com; Mon, 27 Oct 2003 09:40:53 +0300 Received: from d-s.example.com (mx.d-s.example.com [194.87.0.32]) by moscvax.d-s.example.com (8.12.9/8.12.9) with ESMTP id h9R6erFm062621 for ; Mon, 27 Oct 2003 09:40:53 +0300 (MSK) (envelope-from vox19@b92.d-s.example.com) Received: by d-s.example.com (CommuniGate Pro PIPE 4.1.5/D1) with PIPE id 120757490; Mon, 27 Oct 2003 09:40:53 +0300 Received: from [194.87.2.108] (HELO b92.d-s.example.com) by d-s.example.com (CommuniGate Pro SMTP 4.1.5/D1) with ESMTP-TLS id 120757480 for security@d.example.com; Mon, 27 Oct 2003 09:40:52 +0300 Received: from b92.d-s.example.com (localhost [127.0.0.1]) by b92.d-s.example.com (8.12.8p1/8.12.3) with ESMTP id h9R6eqIe014669 for ; Mon, 27 Oct 2003 09:40:52 +0300 (MSK) (envelope-from vox19@b92.d-s.example.com) Received: from localhost (localhost [[UNIX: localhost]]) by b92.d-s.example.com (8.12.8p1/8.12.3/Submit) id h9R6epst014668 for security@d.example.com; Mon, 27 Oct 2003 09:40:51 +0300 (MSK) From: "Stanislav" Subject: Fwd: scanning my ports X-Original-Date: Mon, 27 Oct 2003 10:40:51 +0400 User-Agent: KMail/1.5.4 X-Original-To: security@d.example.com MIME-Version: 1.0 Content-Type: Multipart/Mixed; boundary="Boundary-00=_z3Ln/tUeUBipHgx" X-Original-Message-Id: <200310270940.51758.vox19@d.example.com> X-Spam-Checker-Version: SpamAssassin 2.60-jumbo.demos (1.212-2003-09-23-exp) X-Spam-Level: X-Spam-Status: No, hits=-6.8 required=5.0 tests=BAYES_00,FROM_ENDS_IN_NUMS, HTML_MESSAGE,SUBJECT_RT autolearn=ham version=2.60-jumbo.demos X-Spam-Report: -6.8 points, 5.0 required; * -3.0 SUBJECT_RT Tracking system * 1.0 FROM_ENDS_IN_NUMS From: ends in numbers * 0.1 HTML_MESSAGE BODY: HTML included in message * -4.9 BAYES_00 BODY: Bayesian spam probability is 0 to 1% * [score: 0.0000] --Boundary-00=_z3Ln/tUeUBipHgx Content-Type: text/plain; charset="koi8-r" Content-Transfer-Encoding: 7bit Content-Disposition: inline FYI ---------- Forwarded Message ---------- Subject: [DEMOS #12148] scanning my ports Date: Sunday 26 October 2003 20:19 From: 1stwizard@isp.example.com To: no-reply@d-r.example.com This transaction appears to have no content ------------------------------------------------------- -- best wishes, Stanislav A. Mushkat http://www.di.example.com --Boundary-00=_z3Ln/tUeUBipHgx Content-Type: text/plain; charset="iso-8859-1"; name=" " Content-Transfer-Encoding: 7bit Content-Disposition: inline Somebody at IP 127.0.0.1 scanned my ports. --Boundary-00=_z3Ln/tUeUBipHgx Content-Type: text/html; charset="iso-8859-1"; name=" " Content-Transfer-Encoding: 7bit Content-Disposition: inline
      Somebody at IP 127.0.0.1 scanned my ports.
       
       
      --Boundary-00=_z3Ln/tUeUBipHgx Content-Type: image/jpeg; charset="iso-8859-1"; name="BackGrnd.jpg" Content-Transfer-Encoding: base64 Content-Disposition: inline; filename="BackGrnd.jpg" /9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAHgAA/+4AIUFk b2JlAGTAAAAAAQMAEAMCAwYAAAHbAAAC1gAABZX/2wCEABALCwsMCxAMDBAX Dw0PFxsUEBAUGx8XFxcXFx8eFxoaGhoXHh4jJSclIx4vLzMzLy9AQEBAQEBA QEBAQEBAQEABEQ8PERMRFRISFRQRFBEUGhQWFhQaJhoaHBoaJjAjHh4eHiMw Ky4nJycuKzU1MDA1NUBAP0BAQEBAQEBAQEBAQP/CABEIAGUAcwMBIgACEQED EQH/xACAAAEBAQEAAAAAAAAAAAAAAAAAAQIGAQEBAAAAAAAAAAAAAAAAAAAA ARABAAICAwEAAgMAAAAAAAAAAQARIQIxQRIiQDIQMFARAAICAgIBBAIDAQEA AAAAAAERACExQVFhcYGRobECEsHhMtHxEgEAAAAAAAAAAAAAAAAAAABQ/9oA DAMBAAIRAxEAAADtRZYE1ASghQFgUZoCkKSwLmhcllAEqkSkqFAlhUomoAS3 IoJqFlDNpFEAQFE1AIVYAWIVKAJRNZpYCwVmmshKACA0CBAUCBYGwf/aAAgB AgABBQD8B/yP/9oACAEDAAEFAPz6/or8H//aAAgBAQABBQC2+ZeHjbD+saX6 hwXeDW1Rg4xLLTa+m7ZiIEsI1MTiHP1dYpvFADiFM1/X6nq9byuwdPPz5oFo fWlEMQ9ULKrWq2ppG9Y2J6INQma9lVTRdlUKgHzXXSEECw1SYu5WsGoJPkis ZYpx31GvXZQ/JM3VwShzVTsp1EZbBI8LcaUSih86+s2Zl4Wp6+lAZnVsDkjd ku5m+lJTdXDG2SHM9M2wKX1YxsaZTTwmoVrYnqsMrM652yjs01K0mtbGAz6Y 5dpfqNz06qpq5QNjiIjiZtbhtceNuf0jyeqGgu6rXMvI4omPWbPMYzEfMI+a xHnFvOP4/9oACAECAgY/AGP/2gAIAQMCBj8AY//aAAgBAQEGPwB72Yucb1Bf IhFEaeZ+xRXFQELN+HEUQdjU0Xn4g9gRCQcpw1yajGYsP/kFvUzvjUBWrIMF HI2OJQNEAjiEEFdTmfG/MTHq5RFOnpTV3kzCBx7x4YOD1AV5uYJvnqMA0hep jfwpYCwC4Bx3q55zeZRBCw9TkoIuHw78RdczSNH2mgqcLpRC+RASAkA3B13m cYd5mR84c/yOx4lWtRAZ6mGDhiP9WgXVyhWA+xDgMOWGMsTg/wBTz8SjjXrP 8hHIlX1MZ6mDzgc/cIV/iyN1GBR0MQMKjnEzvvMz8mUkErKlfqU63iV+IKNH 7mNZBLFQEpEDeDOV32IVn8WR4caoywqI2p695mbZzNUQIcKfk0bo+0NpCqn7 CiQiNGXkdQen1DpjGeZ7WNw3pK+I93maCPc16+Zkf6XxMCsFwAkaiIB57vc/ IAhZ/HqZBBbB0ZokAEOGxsYqBgPp8agQBu4VSMJdqx6SwDsGBrTmAR93uZGX 6KePowEADAIjoX8gw459CICaW/MLGvodQfkDW71zBxRHtB3j3jC4PMIYoAgK NfPMCQNN7jCzvlzXPopzhQvNZY3CRya9ZrEFfRE0iCB5mscZuVYfKmAi94uE 3Q8qfytQ7xD0svmFcmaxNPI8iMjh3pmF2HbzqeUi+YkiD/MrOl5LmbwPuWVf mXpv3hDH8qAjPpiZHXkRnSd6ZhB53mejzKV6US0K9TCCLyCeIhtETX5MsHBG JkD/ANiFkMCE2qGoCdZ8Q8AMGpYFqEhdhRIYH3CF3d1M/Mexma+4CwdQ2Ddc x0exAlmj04QUQd8QWLB/iB5GxmEg5TENVZqPYzFV8eHAy9T/AEc8a4n3Ov6g /VwvE6lpQ4VNysXzhS8esOO8w/rlF/rypjV3B5H1Knr8T//Z --Boundary-00=_z3Ln/tUeUBipHgx-- rt-5.0.1/t/data/emails/8859-15-message-series/000755 000765 000024 00000000000 14005011336 021201 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/emails/multipart-report000644 000765 000024 00000004227 14005011336 020711 0ustar00sunnavystaff000000 000000 Return-Path: Date: Sat, 23 Aug 2003 00:15:18 +0800 (SGT) From: Mail Delivery Subsystem Message-Id: <200308221615.CGA36111@mailbox.other.example.com> To: support@example.com MIME-Version: 1.0 Content-Type: multipart/report; report-type=delivery-status; boundary="CGA36111.1061568918/mailbox.other.example.com" Subject: Returned mail: User unknown Auto-Submitted: auto-generated (failure) This is a MIME-encapsulated message --CGA36111.1061568918/mailbox.other.example.com The original message was received at Sat, 23 Aug 2003 00:15:18 +0800 (SGT) from mx12.mcis.other.example.com [10.1.1.232] ----- The following addresses had permanent delivery errors ----- --CGA36111.1061568918/mailbox.other.example.com Content-Type: message/delivery-status Reporting-MTA: dns; mailbox.other.example.com Arrival-Date: Sat, 23 Aug 2003 00:15:18 +0800 (SGT) Final-Recipient: RFC822; jesmund@mailbox.other.example.com Action: failed Status: 5.1.1 Remote-MTA: DNS; mail.mcis.other.example.com Diagnostic-Code: SMTP; 550 5.1.1 ... User unknown Last-Attempt-Date: Sat, 23 Aug 2003 00:15:18 +0800 (SGT) --CGA36111.1061568918/mailbox.other.example.com Content-Type: message/rfc822 Return-Path: Received: from mx12.other.example.com (mx12.mcis.other.example.com [10.1.1.232]) by mailbox.other.example.com (Mirapoint Messaging Server MOS 3.3.3-GR) with ESMTP id CGA36101; Sat, 23 Aug 2003 00:15:17 +0800 (SGT) Received: from STATION13 (rhala.dsl.pe.net [64.38.69.104]) by mx12.other.example.com (8.12.9/8.12.9) with ESMTP id h7MGFGac020135 for ; Sat, 23 Aug 2003 00:15:17 +0800 Message-Id: <200308221615.h7MGFGac020135@mx12.other.example.com> From: To: Subject: Thank you! Date: Fri, 22 Aug 2003 9:15:19 --0700 X-MailScanner: Found to be clean Importance: Normal X-Mailer: Microsoft Outlook Express 6.00.2600.0000 X-MSMail-Priority: Normal X-Priority: 3 (Normal) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="_NextPart_000_05684DA4" --_NextPart_000_05684DA4-- --CGA36111.1061568918/mailbox.other.example.com-- rt-5.0.1/t/data/emails/nested-rfc-822000644 000765 000024 00000023100 14005011336 017711 0ustar00sunnavystaff000000 000000 Return-Path: Delivered-To: j@pallas.eruditorum.org Received: from example.com (example.com [213.88.137.35]) by pallas.eruditorum.org (Postfix) with ESMTP id 869591115E for ; Sun, 29 Jun 2003 18:04:04 -0400 (EDT) Received: from jonas by example.com with local (Exim 4.20) id 19WkLK-0004Vr-0I for jesse@bestpractical.com; Mon, 30 Jun 2003 00:08:18 +0200 Resent-To: jesse@bestpractical.com Resent-From: Jonas Liljegren Resent-Date: Mon, 30 Jun 2003 00:08:17 +0200 Received: from mail by example.com with spam-scanned (Exim 4.20) id 19Wayz-00068j-KO for jonas@astral.example.com; Sun, 29 Jun 2003 14:08:42 +0200 Received: from jonas by example.com with local (Exim 4.20) id 19Wayz-00068g-FY for red@example.com; Sun, 29 Jun 2003 14:08:37 +0200 To: Redaktionen Subject: [Jonas Liljegren] Re: [Para] =?iso-8859-1?q?Niv=E5er=3F?= From: Jonas Liljegren Date: Sun, 29 Jun 2003 14:08:37 +0200 Message-ID: <87d6gxt7ay.fsf@example.com> User-Agent: Gnus/5.1002 (Gnus v5.10.2) Emacs/21.2 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Sender: Jonas Liljegren Resent-Message-Id: Resent-Sender: Jonas Liljegren Resent-Date: Mon, 30 Jun 2003 00:08:18 +0200 X-Spam-Status: No, hits=-5.7 required=5.0 tests=AWL,BAYES_10,EMAIL_ATTRIBUTION,MAILTO_WITH_SUBJ, QUOTED_EMAIL_TEXT,USER_AGENT_GNUS_UA version=2.55 X-Spam-Level: X-Spam-Checker-Version: SpamAssassin 2.55 (1.174.2.19-2003-05-19-exp) --=-=-= Content-Type: text/plain; charset=iso-8859-1 Content-Transfer-Encoding: quoted-printable Material f=F6r att uppdatera texten om niv=E5er. Denna text b=F6r dessutom ligga som ett uppslagsord och inte d=E4r den ligg= er nu. --=-=-= Content-Type: message/rfc822 Content-Disposition: inline Return-path: Received: from mail by example.com with spam-scanned (Exim 4.20) id 19WFzq-0005i1-WE for jonas@example.com; Sat, 28 Jun 2003 15:44:13 +0200 Received: from localhost ([127.0.0.1] helo=example.com ident=list) by example.com with esmtp (Exim 4.20) id 19WFzp-0005hf-Tz; Sat, 28 Jun 2003 15:44:05 +0200 Received: from mail by example.com with spam-scanned (Exim 4.20) id 19WFzh-0005hR-Bu for list@example.com; Sat, 28 Jun 2003 15:44:03 +0200 Received: from jonas by example.com with local (Exim 4.20) id 19WFzh-0005hO-AO for list@example.com; Sat, 28 Jun 2003 15:43:57 +0200 To: list@example.com Subject: Re: [Para] =?iso-8859-1?q?Niv=E5er=3F?= References: <002701c33d62$170fb2e0$a33740d5@TELIA.COM> <002301c33d66$bf6483e0$d97864d5@remotel2tu76c8> <64753.217.210.4.156.1056801224.squirrel@example.com> From: Jonas Liljegren Date: Sat, 28 Jun 2003 15:43:57 +0200 In-Reply-To: <64753.217.210.4.156.1056801224.squirrel@example.com> (Jakob Carlsson's message of "Sat, 28 Jun 2003 13:53:44 +0200 (CEST)") Message-ID: <877k76uxk2.fsf@example.com> User-Agent: Gnus/5.1002 (Gnus v5.10.2) Emacs/21.2 (gnu/linux) X-BeenThere: list@example.com X-Mailman-Version: 2.1.2 Precedence: list List-Id: Öppen lista för alla medlemmar List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: list-bounces@example.com Errors-To: list-bounces@example.com X-Spam-Status: No, hits=-7.0 required=5.0 tests=BAYES_00,EMAIL_ATTRIBUTION,IN_REP_TO,QUOTED_EMAIL_TEXT, REFERENCES,REPLY_WITH_QUOTES,USER_AGENT_GNUS_UA version=2.55 X-Spam-Level: X-Spam-Checker-Version: SpamAssassin 2.55 (1.174.2.19-2003-05-19-exp) MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1 Content-Transfer-Encoding: quoted-printable "Jakob Carlsson" writes: >> Om du g=E5r in p=E5 Hemsidan och sen p=E5 Torget. >> D=E4r ser du att det st=E5r ditt anv=E4ndarnamn och >> bredvid det Niv=E5 5. >> Klicka p=E5 niv=E5 5 s=E5 kommer du in p=E5 en sida som >> f=F6rklarar allt om niv=E5systemet. > > Bra svar. Men jag k=E4nner f=F6r att ge en kort f=F6rklaring av niv=E5-sy= stemet. Jag skulle kunna l=E4gga en massa tid p=E5 at skriva samma sak om och om igen. Fliker in h=E4r f=F6r att s=E4ga detta =E4nnu en g=E5ng...: * Det =E4r jag som hittat p=E5 det h=E4r med niv=E5system * Det =E4r en stor skillnad p=E5 hur det =E4r t=E4nkt att vara och hur det= =E4r nu. Jag har stora planer och en massa id=E9er jag vill genomf=F6ra. * Niv=E5systemet =E4r en =E5terkoppling f=F6r vad man gjort f=F6r webbplat= sen. Som ett tack g=F6r hj=E4lpen. * Systemet finns som en inspiration f=F6r de som d=E5 k=E4nner f=F6r att g= =F6ra mer. Men jag vill inte att det ska ge en negativ influens f=F6r de som inte gillar niv=E5er. Var och en ska kunna v=E4lja att ignorera niv=E5n. Speciellt b=F6r de f=F6rst=E5 att det inte har att g=F6ra med graden av andlig utveckling, esoteriska kunskaper eller n=E5got s=E5dant. * Inspirationen till niv=E5erna kommer fr=E5n spel, hemliga ordenssystem, kosmska hiearkier, skr=E5v=E4sen, akademier, politisk administration, osv. Det =E4r ett element av rollspel. En lek. * Olika niv=E5er motsvarar olika roller p=E5 webbplatsen. Webbplatsen webbmaster och ansvbariga har en viss niv=E5, bes=F6kare och g=E4ster har en annan niv=E5. * Alla datorsystem har administrat=F6rssystem f=F6r dem som sk=F6ter systemet. Jag har valt att arrangera dessa funktioner i en skala. Niv=E5n anger hur mycket av systemet du har r=E4tt att administrera. * Att ha ett niv=E5system f=F6r access g=F6r att man kan g=F6ra som p=E5 f= ilm; att l=E5ta de med h=F6gre access komma =E5t mer information. De med riktigt h=F6g niv=E5 kan n=E5 topphemlig information. P=E5 denna webbpl= ats kan varje anv=E4ndae v=E4lja att h=E5lla vissa personliga uppgifter. Har du h=F6g niv=E5 har du rollen som anv=E4ndaradministrat=F6r och har tillg=E5ng till dessa uppgifter. Just nu =E4r vi tre personer med denna niv=E5n. * Niv=E5systemet =E4r ett m=E5tt p=E5 p=E5litlighet. Vi ger dig h=F6gre n= iv=E5 n=E4r vi litar p=E5 att du inte kommer att f=F6rst=F6ra f=F6r oss. F=F6r ju h= =F6gre niv=E5, desto l=E4ttare kan du sabbotera inneh=E5llet. * P=E5 en h=F6gre niv=E5 beh=F6vs det inte bara att vi litar p=E5 att du v= ill v=E4l. Du m=E5ste =E4ven ha ett gott omd=F6me, teknisk f=F6rst=E5else, intresse och logiskt t=E4nkande. Utan detta =E4r det l=E4tt h=E4nt att = du f=F6rst=F6r saker av misstag. * Vi vill uppmuntra medlemmarna att g=F6ra det som =E4r bra f=F6r webbplatsen. Tilldelandet av h=F6gre niv=E5 ska uppmuntra till att g=F6ra det som =E4r bra. * F=F6r att minska missbruk av e-postadresser visar vi e-postadresser bara f=F6r de med lite h=F6gre niv=E5. P=E5 s=E5 vis vill vi undvika att n=E5gon g=E5r med som medlem bara f=F6r att samla e-postadresser f=F6r a= tt sedan g=F6ra reklamutskick. * Idag n=E5r du olika niv=E5er p=E5 detta vis: 0. Kom in p=E5 webbplatsen som g=E4st 1. V=E4lj anv=E4ndarnamn och ange e-postadress 2. Logga in med det l=F6senord som skickats till dig 3. Skrivit en presentation 5. Presentationen har godk=E4nts 6. Du har svarat p=E5 ett f=E5tal fr=E5gor om dina intressen 7. Du har svarat p=E5 en massa fr=E5gor om intressen och beskrivit dem detaljerat 10. N=E5gon v=E4ktare tycker du f=F6rtj=E4nar h=F6gre niv=E5 f=F6r att du= =E4r s=E5 trevlig och engagerad i webbplatsen. 11. Du har gjort ett antal kopplingar mellan =E4mnen och =F6verv=E4gande delan av dem har godk=E4nts av en v=E4ktare, och du accepterar att b=F6rja som l=E4rling i v=E4ktarakademin (jobbet som systemadministrat=F6r) 12-39. D=E5 och d=E5 tittar jag p=E5 vad du gjort i form av skrivande av texter och arbetande med uppslagsverkets =E4mnen, och justerar din niv=E5 i f=F6rh=E5llande till m=E4ngd och kvalit=E9 p=E5 arbetet 40. Du har full=E4ndat ett helt =E4mnesomr=E5de. En m=E4ngd sammanl=E4nk= ade =E4mnen med bra textinneh=E5ll. 41. F=F6rtroende att arbeta med adminstration av medlemsregistret. 42. Delaktig i utvecklandet av webbplatsens prgrammering. * Allts=E5. Automatik tar dig till niv=E5 7. * Men som sagt. Jag har en massa andra planer d=E4r mycket mer kopplas till niv=E5er och d=E4r det finns systemautomatik f=F6r hela v=E4gen till niv=E5 40. * 41 och 42 ligger utanf=F6r niv=E5systemet i =F6vrigt. Den som har de niv=E5erna har inte n=F6dv=E4ndigtvis tagit sig till niv=E5 40 innan. De motsvaras av anv=E4ndaradministrat=F6r och systemadministrat=F6r och niv=E5n speglar maktbefogenheterna snarare =E4n vad man i =F6vrigt gjort f=F6r webbplatsen. * Alla texter. Allt inneh=E5ll =E4r =F6ppet f=F6r alla. =C4ven f=F6r bes= =F6kare som inte loggar in. Du kan till och med g=E5 in p=E5 administrationssidorna utan att logga in. Vi g=F6mmer inte inneh=E5ll. Det vi g=F6r =E4r att hindra dig fr=E5n att =E4ndra inneh=E5llet. Vi d= =F6ljer dock en del information om andra medlemmar i syfte att f=E5 s=E5 m=E5nga som m=F6jligt att sj=E4lv skriva in sig och skriva en presentation. --=20 / Jonas - http://jonas.example.com/myself/en/index.html _______________________________________________ List mailing list List@example.com http://example.com/cgi-bin/mailman/listinfo/list --=-=-= -- / Jonas - http://jonas.example.com/myself/en/index.html --=-=-=-- rt-5.0.1/t/data/emails/lorem-ipsum000644 000765 000024 00000000677 14005011336 017635 0ustar00sunnavystaff000000 000000 Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. rt-5.0.1/t/data/emails/rfc2231-attachment-filename-continuations000644 000765 000024 00000002237 14005011336 025237 0ustar00sunnavystaff000000 000000 Message-ID: Date: Wed, 7 Dec 2011 20:32:40 +0900 (JST) From: root@localhost Subject: Client =?ISO-2022-JP?B?Pw==?= Japanese Attachment Name issue MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----=_Part_2017_4220946.1323257560967" X-Priority: 3 X-MSMail-Priority: Normal Priority: normal X-Mailer: @nifty Webmail ------=_Part_2017_4220946.1323257560967 Content-Type: text/plain; charset="iso-2022-jp" Content-Transfer-Encoding: 7bit Some data in a file ------=_Part_2017_4220946.1323257560967 Content-Type: text/plain; name="=?ISO-2022-JP?B?GyRCPzckNyQkJUYlLSU5JUgbKEIgGyRCJUkbKEI=?= =?ISO-2022-JP?B?GyRCJS0lZSVhJXMlSBsoQi50eHQ=?=" Content-Transfer-Encoding: quoted-printable Content-Disposition: inline; filename*0*=ISO-2022-JP'ja'%1b$B%3f7$7$$%25F%25%2d%259%25H%1b%28B; filename*1*=%20; filename*2*=%1b$B%25I%25%2d%25e%25a%25s%25H%1b%28B; filename*3=.txt =EF=BB=BFSome data in a file with a file name of "=E6=96=B0=E3=81=97=E3=81= =84=E3=83=86=E3=82=AD=E3=82=B9=E3=83=88" and "=E3=83=89=E3=82=AD=E3=83=A5=E3=83= =A1=E3=83=B3=E3=83=88.txt" joined with a space; for testing in RT with ------=_Part_2017_4220946.1323257560967-- rt-5.0.1/t/data/emails/multipart-alternative-with-umlaut000644 000765 000024 00000004331 14005011336 024166 0ustar00sunnavystaff000000 000000 Return-Path: Delivered-To: j@pallas.eruditorum.org Received: from vis.example.com (vis.example.com [212.68.68.251]) by pallas.eruditorum.org (Postfix) with SMTP id 59236111C3 for ; Thu, 12 Jun 2003 02:14:44 -0400 (EDT) Received: (qmail 29541 invoked by uid 502); 12 Jun 2003 06:14:42 -0000 Received: from sivd.example.com (HELO example.com) (192.168.42.1) by 192.168.42.42 with SMTP; 12 Jun 2003 06:14:42 -0000 Received: received from 172.20.72.174 by odie.example.com; Thu, 12 Jun 2003 08:14:27 +0200 Received: by mailserver.example.com with Internet Mail Service (5.5.2653.19) id ; Thu, 12 Jun 2003 08:14:39 +0200 Message-ID: <50362EC956CBD411A339009027F6257E013DD495@mailserver.example.com> Date: Thu, 12 Jun 2003 08:14:39 +0200 From: "Stever, Gregor" MIME-Version: 1.0 X-Mailer: Internet Mail Service (5.5.2653.19) To: "'jesse@example.com'" Subject: RE: [rt-users] HTML-encoded mails with umlaute Date: Thu, 12 Jun 2003 08:14:39 +0200 Content-Type: multipart/alternative; boundary="----_=_NextPart_001_01C330A9.E7BDD590" X-Spam-Status: No, hits=0.0 required=5.0 tests=AWL,HTML_50_60,HTML_MESSAGE,INVALID_DATE version=2.55 X-Spam-Level: X-Spam-Checker-Version: SpamAssassin 2.55 (1.174.2.19-2003-05-19-exp) ------_=_NextPart_001_01C330A9.E7BDD590 Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable Hello, ist this kind of Messages, that causes rt to crash.=20 Mit freundlichen Gr=FC=DFen Gregor Stever ^^causes Error!! ------_=_NextPart_001_01C330A9.E7BDD590 Content-Type: text/html; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable
      Hello,

      ist this kind of Messa= ges, that=20 causes rt to crash.

      Mit freundlichen Gr=FC=DFen
      Gregor=20 Stever      ^^causes Error!!
      ------_=_NextPart_001_01C330A9.E7BDD590-- rt-5.0.1/t/data/emails/new-ticket-from-iso-8859-1000644 000765 000024 00000002572 14005011336 021734 0ustar00sunnavystaff000000 000000 Return-Path: Delivered-To: j@pallas.eruditorum.org Received: from sm1.nordkapp.net (sm1.nordkapp.net [62.70.54.150]) by pallas.eruditorum.org (Postfix) with ESMTP id 48F4E11112 for ; Mon, 2 Jun 2003 14:58:37 -0400 (EDT) Received: (qmail 3612 invoked by uid 1009); 2 Jun 2003 18:58:36 -0000 Received: from unknown (HELO office.nordkapp.net) (213.161.186.83) by 0 with SMTP; 2 Jun 2003 18:58:36 -0000 Message-Id: <5.2.1.1.0.20030602205708.0314c5f8@mail.nordkapp.net> X-Sender: hw@nordkapp.net@mail.nordkapp.net X-Mailer: QUALCOMM Windows Eudora Version 5.2.1 Date: Mon, 02 Jun 2003 20:58:30 +0200 To: Jesse Vincent From: Wilhelmsen Håvard Subject: Re: rt-3.0.3pre1 In-Reply-To: <20030602185607.GN10811@fsck.com> References: <5.2.1.1.0.20030602204834.031406d8@mail.nordkapp.net> <5.2.1.1.0.20030530194214.0371c988@mail.nordkapp.net> <5.2.1.1.0.20030530194214.0371c988@mail.nordkapp.net> <5.2.1.1.0.20030602204834.031406d8@mail.nordkapp.net> Mime-Version: 1.0 Content-Type: text/plain; charset="iso-8859-1"; format=flowed Content-Transfer-Encoding: 8bit X-Spam-Status: No, hits=-1.9 required=5.0 tests=AWL,EMAIL_ATTRIBUTION,IN_REP_TO,QUOTED_EMAIL_TEXT, REFERENCES,REPLY_WITH_QUOTES autolearn=ham version=2.55 X-Spam-Level: X-Spam-Checker-Version: SpamAssassin 2.55 (1.174.2.19-2003-05-19-exp) Håvard rt-5.0.1/t/data/emails/rt-send-cc000644 000765 000024 00000000361 14005011336 017311 0ustar00sunnavystaff000000 000000 From: rt@example.com subject: testing send-cc headers RT-Send-Cc: this-is-a-sample-test1e@example.com, second-this-is-a-sample-test2@example.com, test-sample-sample-sample-test3@example.com, afourthtest4@example.com, test5@example.com rt-5.0.1/t/data/emails/new-ticket-from-gb18030000644 000765 000024 00000000347 14005011336 021353 0ustar00sunnavystaff000000 000000 Content-Type: text/plain; charset="gb18030" Content-Disposition: inline Content-Transfer-Encoding: base64 MIME-Version: 1.0 X-Mailer: MIME-tools 5.505 (Entity 5.505) From: root@localhost Subject: =?gb18030?B?suLK1A===?= suLK1A== rt-5.0.1/t/data/emails/subject-with-folding-ws000644 000765 000024 00000000350 14005011336 022027 0ustar00sunnavystaff000000 000000 Subject: =?ISO-8859-1?Q?te?= =?ISO-8859-1?Q?st?= Date: Mon, 02 Jun 2003 20:58:30 +0200 To: rt@example.com From: foo@example.com Mime-Version: 1.0 Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: 8bit test rt-5.0.1/t/data/emails/new-ticket-from-iso-8859-1-full000644 000765 000024 00000002333 14005011336 022667 0ustar00sunnavystaff000000 000000 X-Mailer: QUALCOMM Windows Eudora Version 5.2.1 To: Jesse Vincent From: Wilhelmsen Håvard Subject: Re: rt-3.0.3pre1 X-Spam-Status: No, hits=-1.9 required=5.0 tests=AWL,EMAIL_ATTRIBUTION,IN_REP_TO,QUOTED_EMAIL_TEXT, REFERENCES,REPLY_WITH_QUOTES autolearn=ham version=2.55 X-Spam-Level: X-Spam-Checker-Version: SpamAssassin 2.55 (1.174.2.19-2003-05-19-exp) At 14:56 02.06.2003 -0400, you wrote: >> This patch didn't help us out. >> We still got problems with auto responding e-mails sent from the system >> when a new ticket is created. >> The same problem appears when one of the staff replays to an new ticket. >> All Norwegian letters is converted to strange letters like ø >> >> We would love if this bug could be fixed. On our mail server we are >running >> perl 5.6.1 since we are using debian stabel packet lists. > >I'd love it too. I just can't find it. Can you send me >(jesse@bestpractical.com) a couple of email messages containing >characters that break your RT? Hello again, Thanks for your fast replay! I don't know how this looks at your end but it is letters like: ø æ Ã¥ If your want to make this in html it will be ø å and &aerlig; -- HÃ¥vard rt-5.0.1/t/data/emails/very-long-subject000644 000765 000024 00000001207 14005011336 020731 0ustar00sunnavystaff000000 000000 Subject: 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 Date: Mon, 02 Jun 2003 20:58:30 +0200 To: rt@example.com From: foo@example.com Mime-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit This email has a very long subject. Our DB allows you to use subject no longer than 200 chars, but we creat ticket, don't generate an error and trancate long line. rt-5.0.1/t/data/emails/nested-mime-sample000644 000765 000024 00000030434 14005011336 021044 0ustar00sunnavystaff000000 000000 Return-Path: Delivered-To: jesse@pallas.eruditorum.org Received: by pallas.eruditorum.org (Postfix) id B5D3E1123A; Fri, 12 Jul 2002 11:35:27 -0400 (EDT) Delivered-To: rt-2.0-bugs@pallas.eruditorum.org Received: from postman.some.net (postman.some.net [193.0.0.199]) by pallas.eruditorum.org (Postfix) with SMTP id 2736011234 for ; Fri, 12 Jul 2002 11:35:27 -0400 (EDT) Received: (qmail 11615 invoked by uid 0); 12 Jul 2002 15:35:26 -0000 Received: from x22.some.net (HELO x22.some.net.some.net) (193.0.1.22) by postman.some.net with SMTP; 12 Jul 2002 15:35:26 -0000 Date: Fri, 12 Jul 2002 17:35:26 +0200 (CEST) From: Xxxxxx Yyyyyyy To: rt-0.0-bugs@fsck.com Subject: Example MIME within MIME within MIME message Message-ID: MIME-Version: 1.0 Content-Type: MULTIPART/MIXED; BOUNDARY="12654081-192303556-1026488126=:25020" X-Spam-Status: No, hits=4.0 required=7.0 tests=DOUBLE_CAPSWORD,MIME_NULL_BLOCK,MIME_MISSING_BOUNDARY version=2.31 Content-Length: 11478 This message is in MIME format. The first part should be readable text, while the remaining parts are likely unreadable without MIME-aware tools. Send mail to mime@docserver.cac.washington.edu for more info. --12654081-192303556-1026488126=:25020 Content-Type: TEXT/PLAIN; charset=US-ASCII MIME is fun at times. -- Xxxxxx Yyyyyyy SOME Systems/Network Engineer NCC www.some.net - PGP000C8B1B Operations/Security --12654081-192303556-1026488126=:25020 Content-Type: MULTIPART/Digest; BOUNDARY="12654081-2102091261-1026488126=:25020" Content-ID: Content-Description: Digest of 2 messages This message is in MIME format. The first part should be readable text, while the remaining parts are likely unreadable without MIME-aware tools. Send mail to mime@docserver.cac.washington.edu for more info. --12654081-2102091261-1026488126=:25020 Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII Content-ID: Content-Description: first outer message (fwd) Date: Fri, 12 Jul 2002 17:32:37 +0200 (CEST) From: Xxxxxx Yyyyyyy X-X-Sender: bc@x22.some.net To: Xxxxxx_Yyyyyyy@some.net Subject: first outer message Message-ID: MIME-Version: 1.0 Content-Type: MULTIPART/MIXED; BOUNDARY="12654081-113777422-1026487957=:25020" --12654081-113777422-1026487957=:25020 Content-Type: TEXT/PLAIN; charset=US-ASCII first outer message --12654081-113777422-1026487957=:25020 Content-Type: MULTIPART/Digest; BOUNDARY="12654081-387266385-1026487957=:25020" Content-ID: Content-Description: Digest of 2 messages --12654081-387266385-1026487957=:25020 Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII Content-ID: Content-Description: middle message (fwd) Date: Fri, 12 Jul 2002 17:31:45 +0200 (CEST) From: Xxxxxx Yyyyyyy X-X-Sender: bc@x22.some.net To: Xxxxxx_Yyyyyyy@some.net Subject: middle message Message-ID: MIME-Version: 1.0 Content-Type: MULTIPART/MIXED; BOUNDARY="12654081-1711788944-1026487905=:25020" --12654081-1711788944-1026487905=:25020 Content-Type: TEXT/PLAIN; charset=US-ASCII This is the first middle message --12654081-1711788944-1026487905=:25020 Content-Type: MULTIPART/Digest; BOUNDARY="12654081-1221085552-1026487905=:25020" Content-ID: Content-Description: Digest of 2 messages --12654081-1221085552-1026487905=:25020 Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII Content-ID: Content-Description: This is the inner-most message (fwd) Date: Fri, 12 Jul 2002 17:30:31 +0200 (CEST) From: Xxxxxx Yyyyyyy X-X-Sender: bc@x22.some.net To: Xxxxxx_Yyyyyyy@some.net Subject: This is the inner-most message Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII inner-msg --12654081-1221085552-1026487905=:25020 Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII Content-ID: Content-Description: another inner message (need two before pine will do the mime-digest thing) (fwd) Date: Fri, 12 Jul 2002 17:31:12 +0200 (CEST) From: Xxxxxx Yyyyyyy X-X-Sender: bc@x22.some.net To: Xxxxxx_Yyyyyyy@some.net Subject: another inner message (need two before pine will do the mime-digest thing) Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII again --12654081-1221085552-1026487905=:25020-- --12654081-1711788944-1026487905=:25020-- --12654081-387266385-1026487957=:25020 Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII Content-ID: Content-Description: middle message (fwd) Date: Fri, 12 Jul 2002 17:32:05 +0200 (CEST) From: Xxxxxx Yyyyyyy X-X-Sender: bc@x22.some.net To: Xxxxxx_Yyyyyyy@some.net Subject: middle message Message-ID: MIME-Version: 1.0 Content-Type: MULTIPART/MIXED; BOUNDARY="12654081-1731270459-1026487925=:25020" --12654081-1731270459-1026487925=:25020 Content-Type: TEXT/PLAIN; charset=US-ASCII This is the second middle message --12654081-1731270459-1026487925=:25020 Content-Type: MULTIPART/Digest; BOUNDARY="12654081-128832654-1026487925=:25020" Content-ID: Content-Description: Digest of 2 messages --12654081-128832654-1026487925=:25020 Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII Content-ID: Content-Description: This is the inner-most message (fwd) Date: Fri, 12 Jul 2002 17:30:31 +0200 (CEST) From: Xxxxxx Yyyyyyy X-X-Sender: bc@x22.some.net To: Xxxxxx_Yyyyyyy@some.net Subject: This is the inner-most message Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII inner-msg --12654081-128832654-1026487925=:25020 Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII Content-ID: Content-Description: another inner message (need two before pine will do the mime-digest thing) (fwd) Date: Fri, 12 Jul 2002 17:31:12 +0200 (CEST) From: Xxxxxx Yyyyyyy X-X-Sender: bc@x22.some.net To: Xxxxxx_Yyyyyyy@some.net Subject: another inner message (need two before pine will do the mime-digest thing) Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII again --12654081-128832654-1026487925=:25020-- --12654081-1731270459-1026487925=:25020-- --12654081-387266385-1026487957=:25020-- --12654081-113777422-1026487957=:25020-- --12654081-2102091261-1026488126=:25020 Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII Content-ID: Content-Description: 2nd outer message (fwd) Date: Fri, 12 Jul 2002 17:32:54 +0200 (CEST) From: Xxxxxx Yyyyyyy X-X-Sender: bc@x22.some.net To: Xxxxxx_Yyyyyyy@some.net Subject: 2nd outer message Message-ID: MIME-Version: 1.0 Content-Type: MULTIPART/MIXED; BOUNDARY="12654081-1955637437-1026487974=:25020" --12654081-1955637437-1026487974=:25020 Content-Type: TEXT/PLAIN; charset=US-ASCII 2nd outer message --12654081-1955637437-1026487974=:25020 Content-Type: MULTIPART/Digest; BOUNDARY="12654081-362457126-1026487974=:25020" Content-ID: Content-Description: Digest of 2 messages --12654081-362457126-1026487974=:25020 Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII Content-ID: Content-Description: middle message (fwd) Date: Fri, 12 Jul 2002 17:31:45 +0200 (CEST) From: Xxxxxx Yyyyyyy X-X-Sender: bc@x22.some.net To: Xxxxxx_Yyyyyyy@some.net Subject: middle message Message-ID: MIME-Version: 1.0 Content-Type: MULTIPART/MIXED; BOUNDARY="12654081-1711788944-1026487905=:25020" --12654081-1711788944-1026487905=:25020 Content-Type: TEXT/PLAIN; charset=US-ASCII This is the first middle message --12654081-1711788944-1026487905=:25020 Content-Type: MULTIPART/Digest; BOUNDARY="12654081-1221085552-1026487905=:25020" Content-ID: Content-Description: Digest of 2 messages --12654081-1221085552-1026487905=:25020 Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII Content-ID: Content-Description: This is the inner-most message (fwd) Date: Fri, 12 Jul 2002 17:30:31 +0200 (CEST) From: Xxxxxx Yyyyyyy X-X-Sender: bc@x22.some.net To: Xxxxxx_Yyyyyyy@some.net Subject: This is the inner-most message Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII inner-msg --12654081-1221085552-1026487905=:25020 Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII Content-ID: Content-Description: another inner message (need two before pine will do the mime-digest thing) (fwd) Date: Fri, 12 Jul 2002 17:31:12 +0200 (CEST) From: Xxxxxx Yyyyyyy X-X-Sender: bc@x22.some.net To: Xxxxxx_Yyyyyyy@some.net Subject: another inner message (need two before pine will do the mime-digest thing) Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII again --12654081-1221085552-1026487905=:25020-- --12654081-1711788944-1026487905=:25020-- --12654081-362457126-1026487974=:25020 Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII Content-ID: Content-Description: middle message (fwd) Date: Fri, 12 Jul 2002 17:32:05 +0200 (CEST) From: Xxxxxx Yyyyyyy X-X-Sender: bc@x22.some.net To: Xxxxxx_Yyyyyyy@some.net Subject: middle message Message-ID: MIME-Version: 1.0 Content-Type: MULTIPART/MIXED; BOUNDARY="12654081-1731270459-1026487925=:25020" --12654081-1731270459-1026487925=:25020 Content-Type: TEXT/PLAIN; charset=US-ASCII This is the second middle message --12654081-1731270459-1026487925=:25020 Content-Type: MULTIPART/Digest; BOUNDARY="12654081-128832654-1026487925=:25020" Content-ID: Content-Description: Digest of 2 messages --12654081-128832654-1026487925=:25020 Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII Content-ID: Content-Description: This is the inner-most message (fwd) Date: Fri, 12 Jul 2002 17:30:31 +0200 (CEST) From: Xxxxxx Yyyyyyy X-X-Sender: bc@x22.some.net To: Xxxxxx_Yyyyyyy@some.net Subject: This is the inner-most message Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII inner-msg --12654081-128832654-1026487925=:25020 Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII Content-ID: Content-Description: another inner message (need two before pine will do the mime-digest thing) (fwd) Date: Fri, 12 Jul 2002 17:31:12 +0200 (CEST) From: Xxxxxx Yyyyyyy X-X-Sender: bc@x22.some.net To: Xxxxxx_Yyyyyyy@some.net Subject: another inner message (need two before pine will do the mime-digest thing) Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII again --12654081-128832654-1026487925=:25020-- --12654081-1731270459-1026487925=:25020-- --12654081-362457126-1026487974=:25020-- --12654081-1955637437-1026487974=:25020-- --12654081-2102091261-1026488126=:25020-- --12654081-192303556-1026488126=:25020-- rt-5.0.1/t/data/emails/notes-uuencoded000644 000765 000024 00000434554 14005011336 020472 0ustar00sunnavystaff000000 000000 Return-Path: Delivered-To: j@pallas.eruditorum.org Received: from serveurlotus.example.com (unknown [213.56.193.67]) by pallas.eruditorum.org (Postfix) with SMTP id C21DB113AA for ; Thu, 27 Nov 2003 10:55:58 -0500 (EST) Received: by serveurlotus.example.com(Lotus SMTP MTA v4.6.1 (569.2 2-6-1998)) id C1256DEB.00578401 ; Thu, 27 Nov 2003 16:55:54 +0100 X-Lotus-FromDomain: DOMAINEQZ From: "Maxime HENRION" To: jesse@vendor.example.com Cc: support@example.com Message-ID: Date: Thu, 27 Nov 2003 16:55:50 +0100 Subject: Test e-mail which exhibits problems with RT X-Spam-Status: No, hits=-2.6 required=7.0 tests=BAYES_20 version=2.55 X-Spam-Level: X-Spam-Checker-Version: SpamAssassin 2.55 (1.174.2.19-2003-05-19-exp) Content-Length: 144905 I send you this mail from Lotus Notes to make sure it'll exhibit the reported symptoms (lost attachment and body). I Cc: it to our RT address to verify it does cause the reported problems. Could you please mail me any replies to my personal e-mail, mux@example.org ? Thanks in advance, Maxime (See attached file: Naz_Head.jpg) (UUEncoded file named: Naz_Head.jpg follows) (Its format is: JPEG File Interchange ) begin 644 Naz_Head.jpg M_]C_X``02D9)1@`!`@(```````#__@`>04-$(%-Y56:T,@P9,:ML)Y$]AW03FLXR78A.VY=&=IV58& M2!^X1-BFWO)S""S_`!#M,.,'VV2;<.(C48"JR'#&!V*>!LUV_OLBK)KO@C5) M_9)M=V0(GNJSAL9"7!!E066UR&X)^BD%P06DOR%2&00WG=$WYSQ[JHL&Y&>9 MQV3ON7.;ZG:@.).%59,Y.#[)H&F7.`/QNBK;:Y#?S$XP"4(N'[:S[Y46H-8< M2#C.4``SZA!V")I9?5=AVO[')L[3G?=15_P#BG`0"[/=(W;N3NJ@/J]>8PG=M(5'#_`)CL>Z`7CY]1=`[$Y50& M1,Y]BF&^G!]I07_XRII!#JD3]E(ROHU9_/O]5G,+F"9@>Z?5D3Q[HC2'4*PF'9=]T_\?6!' M\QQ^2LZG,[R-U(3Z):<<"9A*+K.I7(<2*KA]4XZI<:R[S78YE9H))$NB?=+4 M(C.^4&O2ZM<:Y-=^F(+\`#^+JQ[/*P/,<'SI`]E/1D[@'.`D*V[GKMZUP>;N MOW`#RJEQXCZB^KJ_BJ_P*CEG]1JD5PW5D?HJC2`V8,]P>$A.&PSKO41Z'7E; M/_649\0]18YO_K:I@Y]9@K%:\DDM/P.4#G._J)39IT%3Q#U(48;>U9]GE*WZ M_P!3:=;KVMCC65@L,M],_=3C\I9@^_*IIT-GX@ZL]P<;ZK#3)EY5YWB;J9`_ M]75`]G9*YJB-%&!@G<=T?F!K`T3'LL4=(/$_5-`TWE4#_P"Y,?%750__`-[5 MS[KG?,U-`)..W*$N$F=QW4TKH?\`S5U?7)O:@^J3O%/5&`3>5<_]2YQS_3,D M90OJ%L9S[*CI'>*^JA\_QE4>VN?[)4?%76`''^/KCC\Q7-.?WG[PHR\9[\!" M.I/C+K8R.I7(CL\J1OC?K[!J'5KH>WF$+DC5TM&#J_="YY=].$Z'8?\`G_Q( MTS_QJ\$\"L[_`#1?_4;Q8T"/$-^('_SN_P`UQ.O/`(3O?Z<\JZAR[=WXG>+V MMQXBZB)Y-R__`#3?_4[QHV2/$W4\)NJ0.]W4_S3?_5'QII.KQ/U0@;?^I?M]UPDN()DCLE2J/%6>?8Y5X-U MWP_$[QDTZO\`S+U0#L;A_P#FA/XI>-1__D_5!`__`-A_^:XBK4+A#=]LJ'62 MT@@2H;KO:?XK^.`#'BCJ7_\`'=_FI&?B]XY;G_S1U$]OY[O\UYWYA+")*;6- MNRJ/26_C)X]8UI_\T]1@XCSBI/\`ZT_B$W;Q5?D]O-*\R#P`#O&R$UC,SDH/ M4*?XX?B,R)\6]1$?_M4=3\HB?\`]IM^B\LU.W(D$*.M7\L$-DGW M31MZF_\`';\2*6H'Q3U!Q(P?.V_15S^./XE:-;O%O42WDFIM^B\K#_3).0>4 M1>-,R,YB86M0Y>HL_'+\2"X$>*NH?_Q/]%+1_'7\1FNSXHO2>_F+RG5#,F/8 M=U(S+@Z23\)J'+VNT_\`$'^)M.W:QOBJ[@=WJ3_]8;\3_P#_`*NZ_P#WUX]0 M/\IOJ`13_P!8_P!_1.#E)>;X.DJE4`G4&CW]U>O/2YTY/95"VD\@O(8(_-NE M-(*I$\#ZJ.((DP#MRC?I!,.#@3P@`3L`F@0!0`")).=10F`,SG9*`Y^DS'9#0VZM.' M@0=NZ51S@XYR=PG9IF'?1!4<)QF4!`X&TTQ@",3R5'[%V$1,Y\B8S&Q0:S^;4")R9@(2^`

      T[ M)`@^IH&GL%%2-?@B8^J;60">3]T+=C.J1O@_ND<_U8'(RJ8`/)3#3OJ'T*(D:[&8B=Y4C7$"&M,J)L1Q'>$3@1L8'L@8F3@1]4+G M&(VX2(#3^:>\%`?54P9'SNJHZ;B.^4]0D@X!(0@..QCZIR`V"7-.)0)CR6P1 MDHFZ@-_BVZ.>9&?T32';4C&4FN.24S!G,B.4@"7P-D4;CZ02G MUQM@'E,`7,D-F-TP:03#B)XW03TW0`3/=.ZHT[20HJ0(=JP<83U=1'Y??=$I M.>20)B/JF<\D^W$)+@3@D=@@.1J(C\RGI.TTAL`%!;@U'@&8)4U< M#40..%*LA!WKD@?,JQ3<*;=6J?959U`<%*HXBG,R=MU`JM05+G42#[QLF>8: M0V"1O)W4;):`V3G>$4@$&()W0/YA+H&$S"=Q$'E,X34D!.&MF<>_NBIZ.G5. M1C=6*32XZSMP56:Z#I#1/=6ZKAY8:!$;PI:1(7B!)SV0O$+R(+B![(IY&DQN$!U$=OE*99G;V0ZVDSVXE7:0I$@DY12W5 MF0.P47ID%(N@03]R@.1DL'ZH#_DD#^G"C!R=_NE()$'?!0$X%Q$?NF(& MY!&.Z;;4`24#RYH!!)]T#U7A@,\JNZ-6?ND\EYDN$]T]-[\MUX[0M!/;+?E, MUDQ/WW2=4TGL3V*!SAQQS*(D:WD$P?T4M+8@29W]E!J],CE'J&H`.XR$&G0< M!1:(&R/6WL%#1TFDTEW'9%#/\?Z)N+I/L@B%`B9(SQF$/&TIHT[B M2F;!.3'L4#N:&P[?Y*:1!WD=T\S(.3\RA,:2Z.?T3:GD%I&Y&/A*8&9CLA(S M$B/S8PGQ&R%0;B#F#CW14 MXU>QX4?]1X*3,$X(]U!,_2'0XS"%S@'20(CLAJF'#E)I!!:2J$XMS(]L(26P M)S[(W._EENH@3J4+FR2/902.TZCP(V[HH!!C(*A;#@)V[8@M^"J#<1&QE-SL0.Y2(SOLE'IF<($XB$3P0[)"%VD9!D#^Q02!D;F2>P0&(,[I,:XB-4B,Q"1#@=49" M`J9!R^3&R=Q;J$(0(GD(*A<(='U2"1V&;#_-`R(D;%,\DL$B>Z$;X!55)G48 M1-'IS&VZ'48&#GE,4[2[5`(GL902`.:#ID(2,Y$D]D33Z().$(F,'ZH#:"6GU MB9_*F$SO]$F-)V,%(.Y`D(:'4P0`?LD`[2)D`]]B@#LX:1*Y0D02-.>ZE<\NHM`:)'/*HBDQ.=MYW2C,D[(2XN;))"32XM(! M@'906+-LEU4DXF(2TD$$.)3AX91:WDY0DM#?RQQNI5A]!+@3/PA<=\@HGNCT M_?/"AJG$#8^V$"UD8!4E,P9;G':5$"`?5()X4[6.@.VCLJ@2UT`B?E.3#`9$ M\93:H/I/SA.P-J$``R#NH+%KAVLOD=H4I<9@SW[*(0T>EV/8)5'$N&9![K-Y M5+J<68(R-TTB=0P4!<-`@(`YVH24$],SF?A"]P#MQG&4&J'">$+W>H09SA%% MEI(D"=RA+9;D_;!3.+B\DCG9,]X(,.T@;?'R@6Y,]L94L4_X4N=Z7R(`V4.M MPR2W*=S@:@&P(VE5."?KTR1!)F4#LB"-LRBGYH`$85"!G@'ZIG0>$S8D3*L=2_@!58+)U4MTC6: MD3JYVX00'T[YS\I4W`\H9#G"8^`D\M#AZ9^40Y<7'@?.4#@)!R24Q,]FI$ZG M$_W5#XW$S'=,UHF(W0DB#DY3R0V29(W0"78)*KU':R`9]D]=^O\`*%&60\1P M@,#5`VGNG`:T.,9!30`!#N,I0!N(`5*.B*/G!U5ITC)&<_91N+!J(G`*54-6=H"B/I?MDA2U3DG&.%&]Y=!+H(/W4`N_/@&$B6R'#=)[AJ(P( M0-$/@"9303HF0TR/T2:06!I)E*3!$8E-K=K!``;QE`0#9`P8]L(&_(CV*<&0 M.9.4[OR['L"A<1$`A&_\`(.!.854F-(:?4?A$6AKX[;Y3 M4G.#,[=RE.!(CNB'=&X.R8F``,)2X',0)"B)).T245(..>=L)B MXX0'R!SAP),>R1Y(SV]D+V_S2V))YE,T:F@`X"J)&2UI M'._*1Q$&/7:9,`;0H&WR`(.(1;;D",8*:)Q)D;F4B#.'8!GL@?+7$'OPDV"_.3.Z6"1 MM]1E.ULSD2,90$TES#\@83M&3&W=,USA$R0=RG`T@.!WVA%Z$UQ+CB!Q[I.( M.`([^R<.EFW/*`DEYDX(@RB:%.8,&/V1MD8R<*,B'`[QPIV#^43D#]D`Y#H. MK`Y"*J#$F028DY4?YJN,`=N457!<=I&K/919@P1DJ5^EEO`(G(W]EEI%5(+ISE!4=D<$' MO"6OU1Q*:)=G!Y5036G7)VVE76.<+5S1I')=L2J;"00`.5,S\D@C2$^!JAQL M('LCH,<&E\@$YCZ(6-UNB7$!2@[-&`-]U*';.2#`CLE3'I$@'O/"8P!G'=%2 M=J>9^L#$#$8P@:3DD@J5X<##?Z9)([J(?F_+)B9E`#IG<`\2FEN=L?JB%4($Y"!Q#8&1[]TG26`Z<`;=DG0&C[HH0,R M('(.\*H?`@'O.-T+RUOIW,Y@J:XHU:5.G4 MJTRT5&ZV3R-I^X4+H+BXE.3J('`Y"6&M^NZ:- M!)RYV0"G!``&DB1DH8&7'`,C"8N],ZAP)0._2XB`94%5\NTCGE27)?2+6N8X M:A(GLJ\`F)S^ZH8P!`R-Y3G\V)D#^Z0(&,F#O[)IY)A$&T@&08QRF=!:,$3& MR<^H0TD$_JHWY]!W^J!W-);!&#NG.EI!U9!VRD0)`G/"%P;$@2(0&!%0.F09 MG*.F`#^:3O""FT$`D[X^5)1G4!@GL54K1HU8I-!F81><.Q48H]W9&,92\D?X MBJ<+=T&!T-,R,:>%1J!DDQ,=RKEW`Y,X1/]1)!`&T(H6Q'..QW0^^Q]D1/],`X2!<<_\` M9,2Z?RC!X1%IRX?=,-RPF$\>HC()V2<'!V/ZNZ%WYL#G[J=D$W#=S')'9"-B=C*+,3JG[X2:#C(SW5 M#M(U>TIB8P"!GE-FV4[`6QB3[!%I=H+P"&SM*6HZ1'',($\PR(.3@A0P9)VCA&XNG)R< MPF,$02,?HB$&XD_7NF@',9[)2=CE%`'LBI#5J&6ZX`&PV48>-7JV`_5/HV@? M/ND02Z7?TG[H&@#W._PB@Z@):1'=.)>_)@\^Z%Q/YG8(Y0%@$@N`G$#NDTRU MT<\IAB3.3G>4[7"#N0).$47]43.)_1$PB(C$J,8#8@2C9,SM_9-(-@]$`X.4 M),C`(#433@M]S*&)=&<]D!TA)S^O)4U9H;;AHDSV4%(@`;F.%+6=Z`>$#,$. MW]H*8DDDG;M"'S#C2,#L93R3D"2@(0'#5$B813J<6R/="P'RR22!P$S8:^)U M3N2I5D2'3,1B=E$^"[_5&YP:6@A19F7#"FE$`8`'.91%IJX$B!RGI_FG)C,! M$[\LP?8@JHC@M#OS23W1EQ\L#&IW9"XR?=3T*;=1>/\`,(:%0:6CDR)_LG<0 MVI^:.=U-TW^&_C!_$BJ]G^&F!)/`SQ*&]=JJ%PHLI@>G0&Q`[+*HR-1R?9*EI%3+CGA`FEQ-, MF)V^4]1PT;DD\("XZC'`50@_U#A!)@S.=H.R-[A$0@#@3+>4@=[M3`"3+1&3 MPA.6:8R,9.Z6`-1.)0D#\VK3\;H?].02[!$D)G3IG4)/[IO=HE(@@29^B:*1 M/H+7`P=@HW5"`/T`3$Z0=1.=@%%4(+OR[[JAWU'P-1,1@=DP=#L"1L"F`]$$ M92S)'M`0$[!!!(GE".2?L4A&LD"('=,X1!D%"HU7-I-;)V1^>[N5'2CRQZ47 MI_PE39__`%9N'D/=#RP$&8Y"H5B7`Q!`5V]U$G<-E4:N9_:%:(B!I^,B5&X0 M,GTF`/U2]$&"9A._5IR1"C.-B?A!:Z76MZ-;76 MIZQ!@1.?<**Y(\USF&&N<2`,0%'J=J&`.9*62XD.^J!9($../E)PSD'YE,XD MNDRB+6Z))`)X'"!-B(+A(3THR2<`Y4;?2Z"W[HV^Q@^Z"S>OH5?+\FB:4,AV M9U'NH-!D$`CW]DAD`';W2DB1/"!'TGA(3J&4P^#M".1Y8](!)C5R@'5#N8([ MRF$1W[A,^-A)A)L[`9/97L'3`,^J/9.08^>4(])@G$Y"Z*$U,AI)+)]I0N M($[3[IV#4'`D-Q(0,-\B/E.2-0Y[H6D'?CB40(B/LB[."7&(`^2CDSG([(#& M'$Q"9CFAWM^J"0D"=+O_`,(2:0?S8'<(7?D(9/=.TC>4!'_F?&P3M<"P`M)^ M2A:2?Z))^J<'.PCF4!O@Q`C&TIP&C+2\:PUQCF$``@G`)[C"ELW%E20.,%`U>)(,[<)FZ/,!R8X2;-2H07'/) M35&C9@R-R"LJ:Z?JJ2TQ.P*9CR6^H;8A`6%P&H93M(@B2/A4&7NSI,81:VBD MT'NA$@#!":,:AN@DID/J1$QMA7':12TM>2!C3W5>T8&F7#<9A2G2'&-H6:HF M$MRTQIY&(2Y(&??=/Z=XD#W3TV4RQQ=4T MXD")DJ@=,TX'U!&4G%L@D$GB.4G$EH``B(D(20!I`@2@R'8F3]95#U`"TZG#V0,PZ8$?*0_*3J.-P4.H3D0!PF@3F1(D%!` M#?2_=$]K1SO[H"V`3[\(AA^724AEN(":)$G.>4Q`!+I]D(9X$21N8^5(:C?X M=M(TV`M.K5SMRH@T22YQ([RHZKPY\3LJ<&J$D[8&<8E1P-8WA&]L1O\`"$M$ MG]B@*#I/;N$AW.2D[)B?\DQ:6MW0)L3!E+2`X<#;*;=VK:$>F6R3\(A%I`R) M!0./H)X]RG.N8:Z!V"`G_J'Q*+L[20"6N`'*4-+IG;@SE+3ODD']$T@;$_=$ M2!KB3!)PBI2'1D3P2@IO(;@[CDJ1FJRHO4R`P`MV]T6IO^']4%/5H$3]D M_J[G[(BS?.:1O/Q_=4*S@XR($*W7+M6,?14;C\Y&DGY5JHWU&%NJ(^%$7-#I MR0.$YVWPAR';@1P0L[#.=,D$IAM,I.P/5L4FB1#^2(14?+TDO#@Z,0-S[H!D$B3]CA/Z9P))XF4C`.WW3"`[9OP-T!:O5I.( M1#,9Y0-`@D'(XSE/I$3O&=T$D9R0,IJD<#`[(7B!`B$=(,\LN/YAPJ'I!U2J M*8DEV!">H?5$Q'=0OB=C\IV%LB0?NH%J&`,?7=$7-F2?JHW?G@`QS"?1J@9D MJB1L.$EWZ%,XAQP)]X0-P-!DQ[E(@3B4#D09!QP$[BTO_P`/LHR`'01(&Y[) M`-@@GX"@,1M(*>`!.('9,`(B-]H2>WU3)^$BCH,+@ZH&ZVL$NCA"!)W.=D`D M'00[W1:?^HY]T00T[$B?E+3F9$]D).ET.]/8IC_TNYG=4.W!CT@C8%%B9G[% M`X-2=T\S)XVC=`8$"03GW1`B3.2>5 M%Z_\7UE$9`$\\I%&X"(+H[(@V!+=O9!,M&04B3.`?NH#C5MN4[!GT)P(;.#W]D`+RB),[[Y]BH!((!$<;RI`T#^J9$!"T@5` M73],HF/=,Y!^5*IV"&^P1.!@:1]45NUU6HUC07%YC9=5XA\,ML^@T+NWU.>/ M^;G8(;Z-,$1V3=I](*4R[5!^H2?CC(S,+*E$.YQ\IW`SB<[RDZH M=S$H"\:).)[H",QB8W[)I+1)Y[(=8/!TQPG-22TP2`J%F#W"9@)9'*1=+>\E M(&,\(!J-,B3CVV3M\H:PXEQ/Y\-&"#)[H`@DZM0'UW4FC^5)W/*`N;,@Q.QA25GD4FM+@0-E1$ MT%(^HYA,YS8$G[%#4>W5G,>Z`J0+WP2T?*!P+1GGA"7Q//NB#M3I=$#N@%TD MQ!RG:USCI`)+N(3!X)[?&ZBJU@`()U`]U4*N2'Z9/V43_>$^H&3,8YI8?V(^BJA[#<$ MAH:'&8_LJBRS\H_-]"G^CONI*8.@:=D\.[JFCU]&K))!6=D#;.>R(:);)D92TYAQ( MA!K]&DXGE.^IZ]1.Z!R!,.)PF<9,R!\IB[N)^J)Q@B0W81D($^7.DD.)RF;^ M:)@\04P))P!/$[):])!/?=%'`WGZ)W21Q'8*.G.HD;GNB:^#B`"$B#@D`0/E M)K3&'8Y0M?`!$%*F_.VRI$KJ!IL8\/:0\2,R1F,J-ATY_1,2.3GY3/?+G.TY M*BG`DSRI,MIZ=,3O[J/5&",I.>"W5'^B"322V<9V*'!=`R1^J8&2/ZD0[V@B&#$X3P]N)`U;CN@U0Z2)^4J;QKEPD?/*`J8$8W3G.^.R&1C MD#9,7("JZ]6EV^V4I]6T=RH^W/'RGUB`!L<(I'7$YSRG+8XF,X*'40P`#,I% M[33].LCW0&?R`G?A.]L8&2>R%C@21$QV3Z@1IB/D[(&>),SO[I>Q$)B]H<1. MV)28<@%THAR8;M\J2A7?1:XL.7")W46IH:0D'M+<`Y0&T^H%PD'=&`"2"0!\ MH&N!SF2,E.UX+>/HBBI-8714<=/[(]88``R)P8*B#MO5]$S'%I)X/?\`[()G M?E(#<_J@(AA`S[HO^:XBF)`R8[=U%JP1(A`[LNVVX3M:#]DVK.0!'*9KP'D[ M(@H),`E.[:(@[Y3,(U9C&-)2>9S@1P4X4F3I):)C,)__`,4)VN&DQPA9$:9B M>$T'`R2'8^$;&SZ9^J'>=(,?ME%OVA`U,,#4`09+@G>`3@$?(1@M!)(WY"3CB8&>4#.C@P1S")T!QS(3-=C2(SR43 ML.:"=N`B!,1)"3JA>B^);RA4Z'>U650PD"F`>?9>5 M,:0[21!"MUJ]>G;MM:CB&-,D'!!*K-G*.H3Y<;25!+M1G96*CFU&@-!$;F=U M#6;MF)X470:;"]_J,0%9!ETM<"/=10&MW$A'Z2"1.,PI06HM9I+M^YPDYVI" M-(W)SPDV)&!CLHIH<3))_P`DY=-/1((!^J9^_?5]4[9PUS`9._=4`TP,#;A* M1K./H$;O*#X9VV/=`"0\@8/)0(@@0D-3H!2V.9(F/A!L3R$!8+#$2-\;H9(S MJR.`E`TQ.^Z1;Z?3QA`F/(!=@\Y3$MEWOLF([;'VW0D@$1N3,!4.T@C+FYX3 MO=#(:Z1V0.$,G8WL$`Y_J'RFT@&>QV2<3'YLM3``3!^$#$$3$)BZ M&C$\)-$P(.4#W9[J@G8+CO/"K/WVS\J0R#O]"FT:<@C=$!$X''*3\")`"+2! M_6)[[(3ZAGO"!,@1Z8)[A(F#@#(^4Y$@-#I*1WF2/:4`N/K&!/LGT`.$I.)! M`;N>$Y;@B9A$[",_W3$>D0#*(&"?9,V3OP@30`22#^R0`DX3_P!)DI$``.(P M?;*='!I^<^Z>$X5+W M0U\`&!LXB/T5&IVP<[;Z=;HG'?95:H<6N&J`-_4LU5>J6R=(`GA`UPU3 MSP0C<,'T_=!I,@$1/"BE-,$:@2W8B>4PC2X:=_N$B#IEP&/9(#U`SL4#M#G"29(]\ MH2(@29/8HV-BGJ.T[IG#TG=`-,0W&?:43@Z"1J,')3.PPC^KY39`]C@^Z!P6 MP<;^Z;MW[IS,$AWU*0F2H]S7VP:060<'WD*FP;C'R1*.LW M)#H!&9!0-(:XG3(CN@)T$0/J!RBTC6886G_#RHX!_-()W2)@S/\`F$!O>3F( MDS`$(#(/'RG:9.2?<)%OTR@1W_1$"X/:0)0T\D_NB+=9@`YW0+(G5B0G/YRX M#XW3D:J<$NG?;W2U$$S!&^/KR@31+-0(D',82$:-I,X"=K26''Z[IP(=!=C; M<_Y(IQ/L9C*)C9:!)SPE,8TS/;9/2:X#L9F5=(3@X``#'"-S8.7<*32UU*)R M.R'2';MF,RHIG4R,$1&^=D_I`&,C*DIM+O:4=4`50U MC1,3/=,,.D`X_J*>OZB=R1G]%+RL5WODZ1W&,B$X75>_O*EY=NFI4RX@``G[*I1IR#@>YE2-_P"8 M""94T@2[33,`R!A/2.LZS@#9-<5'U:HF2[8'X&%)IAH:(]/92KHC(VF>X128 M$GU>R%V'`<\II.DP"H$]TOB?5VV2<9&J3'9-!+06DB3ND`YQ)B>#[JD+5!!^ MIRDQVH@.<1[IIC9N/W2&",`DG7-`D1*3,?F<)2):_!@>RJ M&&,Q@;J#S`YP=ICV5BXKTS0TM;\E5?1,D'XE('J/$Q(@\!(U(;&H#ZIBT`3] M0AC$%TJAR\E\"#'.R;4!L,X3`AN(XR4(C)@[J`G.)&`W`^$['2\@C(&(*9I@ M3!/RG8UFF9W1`EV9D8V2EQ=O*8MGMVW3Y$@1/[H'UX2:X3!/W3``M^".4VSA MJ]0]T!DB3`QLF>['.>R;TZ@0=^4PWD@F>`$#ZL1!@29[(FN(//P90$3,@0>" MEIT^D9CD(B4.'((CWW4U")]IG95F07'2I:8`]6Y$*PZ:U!X=1:9C'9'J_P"K M]%%;,8Z@USG"2I/*I_X@IJ)Q^%>\@2,3[\*G4]3L`EQ=/*O7H&HXP.9W56A7 M=;W(JM]4;`JU52K&DP#W`0/!TC(R.>%(]TDDF0=T!,NF?NH!.^<@X3<;P2G& M"#''=.W2=6K&,(&+L'`SW2&_"8DN@0!'LF9@QD#V0.#B0V9X1,:PYTY/NASP M"DW?)_1!)`!@SGF4U,G3S]$1!+@&`D^PDH6983G"J'):3GYW2DS\(=68(&$1 MP=OD%%*8$9R>$[SO'9,TPX#VYY0D^QR@7],$E/3U!TZL^R8`:9RDT9,X]T03 MR"P;GC!W34R2X8^Z1([8Y2#@#Z.$4XG9VR0<3)SE)I!$$`9V'"0/](D"5`[7 M'5+C!'LE4<7.`WX3&/,+@/NE4@B6SODH!;D3J/.R<[S((Y2&G5D3/,I#\\EV M_(0+48F1":>=H/=.XX/J@H9&0#]^4#D[N)D\IB&D>K\3G[),P@88$"3^RD8)]_K*`F#\HTJ:@TEQ#),]LRE86]6X MJM928YQ=@87J/X=?A_YY9+,LX\!L_PUJNM M]=1V>T#O\KG_`!7X3J=(`<8=G;_9]U]']4MF6EN8;@+R[Q9:U>J=F?3VSLDT""9,'L4+02=.T= MDX.D$9^2@:63J:=^)3/#0,'/'LF<(,`3'ND[6!$R`-]T41_(,XA,T`MXP/[I M9&(!`0C83"H.8$'CLE3INU"/LIMJ8VG:W40,8F$VG!/ M;:5.VVJ.W!RI_P"#TB"TR>W98]X[3]/E5(0[D8'"8@-(@Y[=LJ^RS#:8)$_! MW41LW;_V5F<,OTV<4F:-0SGLB>9=C,;'LC?;5&Y:PP.0=_=1.I.#B-,'?LM2 MQQN&4-Z`T$S*CJN)>&C')37))=C!43B0)'9:C(G!L8@@\(2T`P<1W3-,Y<,> MQ2HQ]%C:-,N.MX;/L)W*@ MID8D&$.!DC/:4[QB3./="#O\;2G_`%#@1.?OA$T&1/*!L3`XW`*X;6HF',Y(! M_0_*&T9;G&VR+&C^W=-J).)!/9-+L$\]R@?2"Z&R`=I2:1ORFGN8^B=OYL"> M4!TIF6_*E:,R-Q^JB;F"7P/A2TR"<2"#NK$:-!O\H8_4H]/M^I0VU1IH-+G- MGW"/73_QM^RNS:.[@SJ='SF51KENJ`(^JNWS0'&#,?,*C5B-QV2FD3C!P/NA M<0'3(1.D849(Y60GYV"8@!^EY"9GYM]CLGJM(JG5AWNBFEL@!(#.0CI/%-^L ML94'(=,(6[B-CW0(!NN#D)-_-$8&Z;!?,?5$&P/.Y*`[$%Q^94-$./5&=TY] M6"YL43>8)RB#P7', M`I,,`_XB<&4V(@G*$>DY!)E!(UKC@[)SO&J!W(0M#B,Z0-]Y3M;!$P MA=(P(3`N/930D='E9F>R3!Z)(0N)P)Y1ET",#LJ'@AHC:.Z=F6\Y]T+"Z2.% M)1:[2=O9$%3:&-V$GDJS86M6XJMI,:7.<<>ZKTR3Z0)/<3*]#_"+PU4O+IMW M49+`9`(/^2,Y9>LVZW\*_`=O2M67-S3#ZKA.1M^B]8Z+T^G;L#13`^%'X7Z> M*5!C-,8[+H*-L)S"YY9;<9-\T-.V!`X/LK5&U!&6Y5FVH>D<*W3I:6KFZ2*= MO;!KXC!5YM$%L$;?JCI4I,E6649XW4VU%-U$`:0%!7MQH/9:IH]A@J&K2$?L MAIS'4^GBLTC.?9%Z+7MY9$96==V6=7]E=LW%S->W%.V@! M>7?BY8&O:/>`1&Y"]BZE2#&$'8;KS+\5*M)G3*PQLI+I9'@]]3%.I$[<*M5' MI;DS[*U?5'/JN(V!RJ[W=FQV7:.J"7:I:<)Y,1@E.3#C.4.2=DL4XYVQ@X2@ M%N9)]MD+I`PW"0=N8,G$(0Q(B-N\),#G#TR>PWE6;*SK5ZA#*9"V^G]'#-+J MH),GU?'_Y>,_LYUG23H]69[*Q;],8QTP#P!V6Z*=,4/'J,T68G#1",6L[;K1TM),`)F@%T8F%CWKK^S)\9K[2&@C'OW0 M&W@"1[[\K7T^DN@`*&JP.PWZ*S-,O#BQZMOI=(;+NTJI6L0]Q!$'B.5N5:32 M)(4%1AG`77'-YL_T\O<<]<=-)+LQ*IUNG5&9'J75>6W4<`_51.H!P@M77'S7 MZ\?D_0XWF.3-N]H/I46DY#1F5TEQ;,)TN;LJ-S:-)D-A=\?)*\/D_27'IC:3 MO@\PCHOF>%J(T:+Z8I`&E/\`^)%YE/\`^'_^=/1T>4/[(_0FDVCN MR&/CN=E1KM+GX`#5=O<./:?LJ=?6`!CVREYK2*N"&Y:-/=0`1O!5BYK&I3:S M2T>6W3@;_*KDSF!\+*[`\`"<]R$CDYA.7&#(&4P=),`(A^Y`S^Z1)!G0/A,= M]Q\)&#C5&$#P#Z2V9X",`!LM)'_2=T#7%K@1N#,2B;!SB`/ MNF8-6J8QF$YTGL([H%)B"A8)SC")FF9)D>R1&"-`^Z!FZ2!,(V#!C9`S)C`" MDIZ)&H`*:@T:B'\;94+&@G8#WV5FDT;8'U07NAV#[J]ITJ8U&HZ.Z^ MCOPPZ"VRZ328&@$-$XB5X_\`@OTT777FU32U"G]>R^DO#EJVG;LP-EC*Z!W0VS0T",?"O-8'-Y*YTD*W;F8PK0IEPD;)K.E(R MKK&%K0UH$$9*EK2O18(^%W"IW5'!ANZUJS`!)^%4N*;2,$PAIRW7*.JF6E>7?B%TFI>T MJE!LC5B3E>S=3H-=3,#9>=>-*#VO4WDA8W4^MUG4]%VUTQDA<9Y?)/CO/'OJO M-Z]I5H.TU&$9@X4-1@$P?ONO0NFVEEUB62))W6%XR\+W/3'&K2!=3<=X.%Z, M?-+=4]+O3EG#T0>5+:TV.J,&K$[!.:,#U'`XA36+:0K!I#H_ISRM7+AZ,/!K M*>SI^AVM(46QDE:1MVZ9)CYV6?TJHUE-K08+1F#R?I9\8);D$@X]DM.T_?BN*N0-+3DGDJ,@&9;]U+5:1(((`P(49;%,N&\K3E MK0?2#@(FMD3^Q0AI,G^Z)K=HDQP@0`/TX2.()!`*6-X..4BW$Y^VR`<$R?OE M'#=>MS0?;9#_`%R<_=2>HC),#[HA4V@O`!&3B2K+:3J54L?^8;P0?V5=@+7X M&_<*9H((F9[+2::-$GRADHI/$50P)(!08#)].4`!P:9W2,'( M@?"1;!QM"1T[&8X4"F>('LD73N,\)$2<"$@UL[[=@AH[@W!&4\@`2AF,-$=D M0((/I*!1/'PGJ`#&_NAV`(^ONB@3,Q`RD*6F(S[%,(C!V1`@$1&4(.8T[?J@ M)P&#P3PDZ"9@;X3$0X#(3;-R1WA`WG5M&=^(0(PYT`&#R2F`@S(ENV)3P,D&1$(20?2#))A`[LM.WP.4M+3`#ON MD/T30"[&)0.1,YVRFB&#YP$@")$_F2C3SE#1')P/CLG@;;)FQL#LG9&,F!C? M=`G&#!,@]D,>WU3Z(&K,3"8?E(!S*!P($Y^=TX`(._QP`@;40R`1ZA MF0EN`(R,$IB"!NB:-I/&R!B"#N91-P"ZN6=)U6JUC M#)<8WA4QJ#N-UT/@6W;5Z_:-J:2USQ/;<*I7M7X)>'/X/IE*K48-=4!Q/.P] ME[#TVDQE!HD;!`G`A+1F7X]!,?*Y?JMHRO6ES!`.5T=_4+9!&.Z MQ+BH"7$B(&%C*FF/UNWL'VIM/+:`X:0LNX_":UZ_T\MH-(J.&".Z@N:[W^)M M)R`?[KU#\,NMT+6[;0K.&1&5Y/W+A=UWF&^GR?XZ\+=;_#SQ2VUNVO;2<[4U MYF")_P!%V'2[=OB7PT0&@E[(F)S"]6_\9%C8=:\(_P`51:UU>V]0,KRK\ M`:NJU?:U#,9S\#"WGG,M91N;T\?\7]/N.D=F>7&8O3X?#Y//=AZ M?4K-HB08]E>I:B(_0\*:VM<#TG'SA7&VA#@"V9]EY,LY:^]XO'<<=519J),Y M/"-I>(EH6M;]-J.:!Y#B79P%J],\*W-=H?HT@;:@LSGIK/R8X3=KE1KW@Y2# M:T?E.>5WMGX.`<#6=,C(6W;>%K%E-H-%KL025VQ\-KP^7_T?'A>.7DP9<$C0 MUP=QA6Z'2NHU73Y+W%QDSRO6*/ARRI^H4F`]EHVUE;,9'EB`NV/@GVO'Y/\` MUO\`\QY78^&.IU&RYND^ZL,\)W^DAQ:#[+U&E;T22)$=D?ETI(C$+7[.#SW_ M`-3S?'EG_E&]TE^H'YW0N\)7;F>H@$GY7J-S3IM&1QM*K-8P.VW[J_LX)_\` M4\SR^X\*7S#Z:<@'$*"MX:OV"32,[X7K;J3'#\L(76M$M]7V3]C%9_ZOE^O% M;SI=W1,&@<;JE5H.#9#")]E[7==.MZH(-,&5F77A^S>Z/*;]E+X)\KMC_P"K M_P#J/'GM(RX`905*(\LN*]4OO"-BYN*<$]ES_6/"#X)MS_\`A_V%F^'+X[X_ M^CXLN+PX"XI2,CN5"6MC@SA=+?\`AJ^I./\`+)![2L6YLJM#4'TG-+=Y!_R4 M]0&Q[+5-0@$`^^%4:M`32:0#$(M+O\`"5!39%,>MR+1_P!;E4W"OC,@M.,22>ZI M5#L#B`K=WO+HDA4J@:79'ZK-:1O.O/W05`2X"!`'Z(X(,EP@CNHWY(P84.@N M@'WW3$F(Y3R0[OREJ:701`Y0"Z0\&1!Q*41#6QMV3.,NW.F4@<9&=D#_`&WV M*D86B3@8Q$84Z9 MPTD2/LG!YS&WPD\!I<6@ELX*<*%HB=]L>Z?!+2XR(C'"9I/`YW31!COE`1]+ M8WD\X2<"1Q`.R$EI@`00Q31Z<9`]T$A@S,8&$FY(`CZ(0!(V$[3.?A%)C?< M(O18#?HG`+6->Z(?C[(=Q^B=Q,MD^PE$,[5P<#DIH)GDI5!#N/@)-'K$.F$! M.;I9)<"(G!W48G5O$G8HW3DNV03!11D-T$`@&$@[88QN"DT"&S@E*/1C;LH@ M00\P=OA&=R!C.)0T\&(W3D'5![JZ#N/!,(V.B21GN$+0TF"G.V=D!TX)U#E; MGA&N+?KEO6C#'`[_``L*1O'T6ATUPIUF/R8,YPI87E]0>$KYMQTN@YCL.`./ M@+LNBTW5-,X$;]UY5^"5S5O^ET0=F`"3/LO9NA6^BFS&?V7&SEQC7Z;2AH)X M[+5M@T&.V%2M6P,<]U;HNR,@?59;BU(:TYGZI4A+O;E1.S$!3VPVD*?6DU)H M&)4C&2-X2:T1Q,(I:,3[?"H<#2()F#RF(G`10#A,[TLD&(10.I>C5&V.RH7[ MM+'9V"MW-7T?FC'?=9=\^=0=RH.8\27[:3RW(E4NG!UY(:/S--+:X(=R MH/#W6:-D]_G/`U#!*\N>?K77'';,\16'\'U`U?ZAF"LZTZQ4M[X.UF0Y:/C? MK-"ZJZZ+A`9!(^JXVR+ZO4`."9)7&WV:ZKM?%]Z>I^&JE*H20ZGM]%P'X:6M M>RZQ7-++02/U"[#J=84^D>6#)+8CZ*CX2LC1I5*C6$N>2DWKUC4NG+?B]KO. MJT61EC>WN5@]'Z#>73FZ:+HVD@KTR[\/_P`=U'^)JTR2T1!"UK#HK+>D`RFU MH[0N^'AM_L]W_P!#'PX3'"S2^X/P`%NT/"UK3A[J;3\C_1=2VTEA M):.P3_P[BT""N^/AQG.GES_]'RY_63;]%MFL;I8W`X"M4[)M.G#6A:5M;9DE M7&6[-.6B1NNDQD>7+S99=USKK5_F3"E%M4$`K;-!NK`1?PX+IC;E:IO^BCKVSRXZ9A= M#Y!DB(X0U;<#>)A!S+J%5C9(.$#608(GE=)5M6.'TX5.I9-+L(;8E1C2[2!^ MJ@N*`$P-^ZV+NPT9`YW55]!PG5'=-&V'5M&5'9:(&=EG]5Z!95J;@ZBP`C:]:\#TGS4HXGC_87-=7\)7EHPN:USF^R]IK6 M\#+1GV52O9TJK-+F@@[@J62]QVQ_4YX_7@%6UK47N\QKA'>5#7T"F8&W=>S= M?\,6EXPN--K701@;K@O$_@^ZM];Z#"?CLLWP[YCUX?K9>*XQ_J!=V&RJWE(. M;@;[+4NK*O;^FJTMA4[ILM(VCCNL3J!SA-)PTZ6D4P"[CN44L_Q_J5%3<\,`#G$!/KJ=W*^IN%>; M%Q700R<2J@VMP,GV[)%PWD`QLE(V$I.;F"443 M1C3(E#4WXSPGW.!OB4+L`0B':1_LIY'F#\H'OE`W?:/=.!P<\H=E4TS$G*6) MTB3!W)0U`9B1&ZR3_`$`01!V*$@%WM[2A($3!440+8DP> M_P`)-]7'LFW$@@$<).:X$#(,3"($3= MI]D.B&CU;IZ8/.?9%.W2'1$F.4Y'9XD\H""`08![RA`)[0/=!+H`HZB]KB3^ M4`RFQIB!\%#"3E-SEKI*`FQHQCB>R>(&#N4)V`[CE.X>GCY"!VCU2,]D[@#.\I@) M`EQ(`[IVF!/*(36_U"!",@:-6!/"C:7-.F)&$[LY&P/*`P&^V=RM"P8YSF@3 M/8+.8'7_@ MY58WHMLT<,'[!>G=/K#2#.ZY95QQC2HXW^ZN4F8&RIT'<\*U3<(X6'18IM$[ M^RLT0`/T56F9((^JMTHP9V14S8#"XCZ*"I5/F1SW4I&(!4+J<&82K$U"H8,G M,IJK_>4-,:0%#3A_'#'AP<) MBN"2&,)'5YSXDZ.ZD7/:V2"N.?B]NFL<],FI;U/ MX)I+IGNJ@K,MGR-QE3BIW#7U&G3,Y7*>'*_&O:`Z1; M7?5*S1I(9O\`*[OH_2&6UJT:1,=E>Z#T2C96[0&B5IOIZ6B&X7HP\4Q8N5K/ M99TV@@`;3LH*U%C7:8"TVTGF1D(769).)*ZQBLKR#'ID\J7R/3^671@+4IV9 MQNK%*P<2"6R5H8M&T=@Z4?\`#/#IC!X"Z>\\L'LI!T\NC"I(YW^#($[J6C M;3(^JZ.GTX!OY0C;TYK0(:$-.=_@L;;H76T#9=2;`%L`2H*G3`'9"&G/?PX: MV0W"C-/3,#]UT3NG']5!4Z:X@]BJ:8+J<_EYW1A@B#QPM&K8.!(TE!_!G)VQ ME$9KZ0&PW0NH-+K:%JBF9(C=15 MJ0DGE!F/HASH.QY*AK6D[#9:3:<.)_=1UE[9"V:E"23'W52K3@D!NRHX[Q'X9MKT.+&!K_`&Y7 MGWB?POTU6%C28A9E[:T;MQ:^#\J\7MO#RY8=/G6_MWTZQU@@[ M0JO'8KU[QQX.I50^M;MAR\RZSTRXL:[A49!G'LM7'4W&YY)E6RM`U6T&V;'MJEU5SB',TX`[RH!^;=$\^K!!08=.8(X M[J!-;_U2.R$M<)D0>Z=I@YPED`D.SW)0"]I(DB?JK+`*-E4(>TOK```;CNJY M+B28VY3.RV!QPH&J/U2`C,'X28<29^Z?7F0<'NJ#IR':Q,QC.R%P).`#/ M,H28.TH@2'9.-E#9Z>VYE)XW!;/:$MAV0ZR3/?94/Q[)#3W^H0Y<#`VWA,"# M+H(*`G279PEI,;[YQE,2!DRBD:9S\!`+MH/Z(28<"6Z@/U1-TCN/=-5<=69A M-*$'?LB)<,SN(3.(@P)0D@`X/RH"DY,<)'CW*8G,?=(.,Q&P0.)C3LG)(,-. M=Y".JRFV@VHVL'$[MY"BU:=\]E=!W%SOTM(."$@0?A`+H!@S\H2'''[HR3IC!^0F(@F?M*H;3F2Z M`4OS2(!]TX(CB1PD`.,=RH%`$22DV(Y/LE@G/T3C3&((F,HAVB"1]$M#MHG2 M(1@P,X]Y0)N/G96^G/+:@)G&% M4P3),@^ZFH%K8,Q"#Z&_!#K!J=.I4M660-U[3T>L'4FS\[KYE_`GJM*C>>2Y MP&H[DKZ&Z-=--%I:<'LN.3EU77V]00.)X4VO.)PLBQN6N$3^JTK7U#^9EKMU8%RYH_,M3R5+XXZS^)I.;^Z<5J+O3@2N7 MI7;MR25)_%/U#2\_"W/-6?VG2>73?S]$#K)AF`/HL2EU"HSG;E6:/5SR2MSS M?EB^);K=/;I,#/95:UA(,"`K5/J5-[W,`+I/)*YWQU@/LBV7053 MN:'(;E=:ZE1>"1"K5NGTG">%N65BXZHY,!;=:W<#$3"JU[:0=059L8D!KLY"CN*;7D#A: M56VD$8^57\HM.1*(RZ]!H!`;"H5:$/)B%NW%$ETZ57N:`TR1*K+G[ND"TF-E MEUK5S27[+JW6[).B5;"Z>W3(G!A8P8`2"(CB%[_XEZ%:7])S0 MP!W=>7>,?"E6SJN?2G3RKZR\QVQ\F^*Y-K00!.VZFI?F&-U4>X$1@-X'*"#3/J@X0.QQ,J4O@$MVV(0.#0!ZM]S*B(R!$@E"8(C@*1[ MB7N>3E,6^D.Z`8&"4T0=_UW1%H.9)!0N@$_M*@:8WRGUY@?J4MX'*3&M[Q_=#1 M$G3,QW1%Q&ZAHFX.-N?=.TY'LD1O'";\L'ETWBL"Z,B>?NOE<&#.K]5U7@OQ;>='J-#3%/V)/]USSQ^Q+CM]8]+Z MB-7YC]UU/2:XJ-!#XYW7A7@?QG;=0H-+ZH;4Y;,2?NO1/#O7FD@&JT_!7&\, MZL[>E47M#7$CE*RMM3M1PC#=1WGV5RR8`?=6(L4[66`A05'LIDR1 MA6ZUS3I4"=0PN"\:^(_X?4VD<[83+.2+CCMTU[U:WH-)-1N/=:]>\0]3JEVESH/RL!]:[N'ES]><%>3/RVN^/CCM.K>.JKI#7&/EL=/:3IA]) M=7O:!;5)SC.ZSO$/2KRE4-6DXP.R[K_@[:%0.:W3_97*G2V5Z&DM!/=)-,^U MEW'GO1+R^H/%-Y,#NM^EU&XIM\S45?J=":VM(;'O""KTPAT:#'"Q8U[;;'AS MKS]3653@>ZZZROJ==DM(7GM&P?3:'#$+6Z76K4:@:7'"2V+K;M#5WAREIO?/ ML%C65Q,:C'NM>@6%HV^JU*FA.JNU0)1M>X['9`Z"TY^B!WY/VRM,Z6F5W-Q) M@X4S+EXR'&5EE\#>5(VIZ<.A39ILTNIU6`2?U5ZWZN',+7+FJ;R2! M,976NI4ZC<*C=V6#NNDRCE<7*.:0XR,J"Y@;-"W[BQ.LDM69=6NX`"TSID5Y MF(4+V@L@MB5JOM"&203"IU:0!C9&6'?48.V%0N;.6X"WZX(&P@\JG6AWHB6K M6TD[>&>-^@5:-R: ME*GCF%RX9I?!!@<+WCQ'TUMQ:U&.`)(PO)O%/1JEEX^RY:CLJ.KUJ;*U*G5=396:&5`#AP MD'/V_14GP#`D_P":M7@;JF/]54J#B=\[JT`2UI`G_)';5*#!4%>FY^K8M,05 M&R`PB.VR$.]4Q*E@,TF/>UM$N<7;`[J*."XHW!NJ0"UO!E"8(`.4`#Y^DIL" M)._;*.(.ES3(.W9,6MWDQ\H:"[V)'PD08TS(Y3@`S&!W"1`+2)D[;) M@V#G>82!D<_912!@Y.`G=$C.`F($@$004[P)W]T`@2[*=QQ)^GRGB`29^(W3 M:0002/V3F0()(0N_,1G/'=`+B8B1',)LGW[)W?_'9CG.R$.,SCX"-K8P3OCE!'IG[[J`B1(C8[2G$EI`'ND9D;93& M9S$H'GU&!`A,XNS`PC:"3$A`X#/Q%NK2X:+EYLFI3:X5.>G>2)KM/O*+_SI M;&`VN"1C&?[)N4>B5[T-F'?""A<^>,.,''RN/Z1U5_4BT,G2>2NOZ=3;2I#6 MMR;9VNL>6'\TC]D]UU"G:T2XN&%5N:K:=-Q)^ZX[Q7U-Y8ZFUT`*97U:DVF\ M4>,0POITWKC']5=?57>8=S*C?:/NJ^27%:%ET.'MAL^Z\F6[7IQD@;>QIW#- M6G)Y5BUZ'1G\@QG9;G3^G!M/0=BM.C9L:P`"/E6+_P`<]2Z12`/IB.5'$]E5M M[44JF<05&H@T.:T@#A:/3ZCO*#3NBI6S2Z2%*VV,^D?"B[2M,C8?=([R%+3I MP())"*E193:0R0)P.RK*HYA)QO\`LH7/(,*[59I$JI787.V1=&I5#\(C5]4! M0GTDD[A1O=I((,PIH6WU!I$#=0.<"R1(^%%K+G`26I`D:H)DY24T-]9S8TJ> MC=O8R0X[\*EL3G!4-2L6OC/PM)9MTEEU8C#C';*U[:_H5J8@B8R9W7$4WDLP M[Y4E"[J4LASH^5TQ\ECCEXY7;U&4ZK2&@+.N[`#(;NLNSZN6.ASX[Y6O;]1I M7#1Z@O3CY97GR\5C(N:)8XM((E9EQ;.+RVS@8`$+,OK?09Y"U&+-,>H/3J+=/UE4KVF'QZ\N(R>Z;_@]O MV"W_`!=?W*\#NZD/APSR=U4JOAT@8XY4U?\`.2,859PAW8>Y7.NVR+H.TB,% M,7##HC'>0FQ$B$S@`T=SQMA0V,U6;-!@C8H?,TD.8=MI0%H`F9A`(,AT^R"5 M]8OJE[R2YYDDF24.L3JSGNA@:9!Q/^PE&(S!W0V(5#N`2"93O>#F8]O9+$S.P022,DG!2#O1$X''NHY` M[YV2:(S.>Z`@0'0-MLIVN9D\;*,@S,XC9-`!DN@'A%'Z9U<#$HG.86CG/*BW M>R'6W0TM.1PHL@`0(&Y3`8,9A#:1SFAN\CO*4LC\V3R@CD8"1 M&9*(P&@%TQNHH@9VYRDT2 M,J&TNKT_F&/T14:C09G'90`&,?;NFC.VWNJ;62[UR#(D)FS&#C]E"&.B8._& M82(AOPH;3D`#?9"/SQ,R@9L3,':"A&"8P=O=4VL?TB=C[)B?4,S.)4+2YVD@ MP!LE,;*&UAI$!TB/=`XC!^ZB'IP23W3.RT#GE71M)N![)X'$Y*A`(?F8GNG! M(GV]T5,WL.$F<-])A0DGO\P48,N:9G"(E#23,_24X+2!`CVC"BUG,DMQPFU8 MR0"-LY1=IZ<9''S!2:2)$P.Q4<@/W,3K&))X3AQ)D;;C*AI MN(V.0B$ZMX]DT;3-J.;D3@YA7+?J%RT'2\YBG&:!TKIS*(!?$K5H46X`&%%2:YYX4]'7OL>5C;K(MT6L`G2G>0'"% M$R3N8Y1!CC`F5-[;D.XZA&`0F;3^P1BF=)[J;R@Y@/[J=KPA\OG,*Q1;!C>$ M@SB"/HC8P$AORKK25)I:3D`=_9(6[=)@#Z)B0-A/"-KQMA:E9TJ5:68CY4#[ M?2"8RM%PE^P3:0Z,*5J*`I13_=4G4?YA@8'=;GDRT[2JXMP2#A9UPUM4MZ`@ M$_JIJ5-IF0K#J):.WU0-9$08/92B(,=JP,!3-I_RSW4]&F3EQ^JE%.7#N4D& M959B'#!Q"J7-*#$;=EM5J30W;*HUZ4O@<;H,FNQT$D0J[R)C<[+3NZ3B-L+, MK-WWQRHL!4R#$9[)A4+1,92;)$&$&\JE-4<">Q]TWE!XF2/JD]P!@CZH758: M2W*=()K0UF#,IY;$"%!YH&"=NZ'S@';_`$2&DSAWW1T*]2B):2(XE1MJ@GF4 M;(>V"5N,6-CIO6"QH8]Q$]EN4;FE7HX>,KAZC8!(,?W3V74*U$^EQ@<%=03C*KW%LW M20&_5:5S;Z'Z@JM5P+M)V"#--"F#!R4WD4NQ6LRC1+`2""47D4?=.%?)%56?O.XXRH])J@@$.TRWLHV@@',GA&]QYR.4SO MRDR,<(R"1)`W^4B(V!2G>&IB3JVX[HIW:-1`.`<80.B9!(([IYDP2)[IB2)Q M]D#.=/(^Z)H@:L'ZH3&G#3O\)R8B#G]$"=J(_U3M$X;B>$YD`MF0=QW3-@C+9(Y0%I;K)VC M8=T-0R[_`%3AH<[N!PF>-),B/8H'8).9,(!IF/;!)1$ZL>R$B,<]T#@0P'?@ MI@9/Z).,-B4FG&V_*!P`1C/LD-0!]1CV3,.(`^,HFF),8^4#C5N/L$[9/IC, MY(2$@8Q^J*1(:1]$"(],0<<)J>F-DY@M$?ND(F0J&/I<5+3(,3D$IG!I!.QV MA.&P-ON4"(!$#E=A^'7AQW4;MKZC)8TA8/0.FOO[UC`PP2O>?PZ\/FTM6`L' MRLWIG+/UZ=#X.Z*RA2IT*;,-'*]+Z);MH4`-`"R?#5@U@!(B-PNB>6TZ>-EB MNF7#U)K2F2-ONKU"G!CLO/>7IQA6S`QHA3&F0"4@`'2<*1[FAN( M6:Z2$P"6XP5,T#&%`Q[2_)_56&'MNIMK22FUNG_52M'HQRH&$ZO2K5`#3_=6 M4T`,,0!",LT@3^BD$.F,)G./*K*%T\H28B``GJG_`*E&7`[S`02XB)1-B9C" MB8X1,DX1TG09/*FQ/'I@#Y3^6,N)@CV3-)(D(Z?YC^J*!](O?`0U:`;D?16& M-&8'Z)ZE)KP`X2.45'08W1&".Y5@4QH,)4F'4('W4^D:)'*1*I5*0!DSE5*U M(:C&Q6C6:!.)A5JC6S);E18S*U"096?=6S1_3!6[7``^50K,#R3C"G2L-]') M)&`@\HP?9;#K=A&!@JLZD!(`0K(?3)!&WNJ]1C]/I(^@6S5H`B`,JM_#:21^ MB:1EO;IG!*A#'ZCO'*TW6WJ,A)M!H$&$TNU&DPG?CW5BG3@;[J846L),)5:O2#G;;K4K"FUY:[4Q\%;/1.LN8[RWN MU`+*=;$B&/< M1_OX7765[3O*(@CW*]/B\ORO+Y/%KIR74K0LEQ'ZK%K6O\S6Z3"[OK/3@6DM MV7-=4M2*1TSCL%ZIR\MX9#7M`B?U2\QO0<`!6*YEQ;`&..56>1Q"E>H+@2/8)/!D2-TSMA)QV2(F'$X]D-&<,X[(2) M))&41`'9"X]T#$&9@&=BD9`B"2$PSMQPB4)@#_1%3=+80/+MR23WE-LTZ@DZ8&04TX@ ME$.!Z9S!2W[I9#1.4I+M.?:.Z`?3S,<)`$DX=/`2AP<J^GJ;H MU;>K.Y00TGL=DY!C4[Z2D3)D'/NA#`@..GM&R0)`^>R=I+4T>F"W=)L!ID?14/=.I.JEU*G MH:=FZIA1N]@B#26ET8:)WV0F-!Q]5`Y!<=1&!RF&Y@>^Z0(E.8!$85":V3D' M"<`:<#;=+4T99(/)*=H$=^Z!"1$#*<09F?HD1MRG='(DSW0T8>SI]D]-OJ@D M;[)@,;F?E&(&^_LBB.6P>58M[>I7F6^&B%3Z):!K!A;MM3T`8^'3'SI:,GW5D=%J-R&N! M"OK4]HWK6[-5FH'Z`JRR^+?S#3[K!L35M7!KP8^%J4GT[AHV"FMK&A0O&N/Y ME-4K!W]06-68^B8&J!V34;PZH.RSNSMJS?34>Z7%`V=63`4=&J'"094K3Z@1 M&5N7;-@R('I&ZDH@ALB$PRTG]$3()&(5TSM-3P8S"GIM`,A1,(.5*#VQW32[ M6*8!@2BTMG?.TJ&B1JVE66%IW"!1`P[[(RW!F4@0`)&/E-J&IX(:[([JG5ZH8!UPH;\-9(:!/*R+IQ) M@'*9$TZ&WZDW5)=,JT.HTW-`7$UKEU/)<<=E2NNN/I,($^V84F5B7';T,WK# MC4/;*FIUJ+VP0"?E>3?^9;IE3,Z?=:?2O&#&U`*U32??*U,_\2^*NZOZ/]30 M1_=6>@=0=0KBF2L;IO7;6ZIC2Z9&_P#L*=[VEVMI'>97693+IQN/RO1[6LV[ MMPT1)&95'J=DT,((^BQO"G4SK:U[L]UV+A1N+4.!X7J\7DWQ7C\OCTXBK9-% M0Y_1-_!-[_HNCJ6)+S#"A_@3_@*].W#3X'N-RZ8G"KDP2"#&RL5\F&S([*"I MZJ8@F1B%-O2A._<#W2.H#V3NB3N1&2FJ.);OM$?"JR:">3\J!#,G*7S(A-M[)\=\Z=P:!D^^$HD2=O MV0)F#R)3NB,QGNDX``9'U"1'I,<(IA^4$B>Z9\``C]D_],S!A*0/^H>R`(SB M<>R=@)G.Z3@``Z1GA,#/$(%IC'"49!V^4XR)/"3))$9[94"(R2(3/V_=&0#. M-]\JUTQ_3J=.Y%^RJ\NI$4?+,0^1!/M$JBG!C<=HW3"=6#D(FDZI`GZIRZ6P M2/D;H$"TN`<(2<,P)^$((@#;Y*=TD8?C_P"Y`\C3_ACLG:X@[S*;$$S@=RFU M9E`1)X.=D)!C"67&>P3`8,9*!`^J28'<<)R0-G<]LIB!/")C27`-&78B$"&/ M43@IP#!,'2>3RFDR6]P8,IB0#I&8[%.6Z9 M;I(/,H?S/COR25%$QQ+H(1F3&G[E-0\LEQ<7#'')4]M3+W-`R51?\.]/?>7; M`&D@D?NO>_PS\-MHVE*:?J@25QOX.^'75`*[Z4S!R/A>[>%NG-H4VC3&%,KI MPROM=-+HMCY%)N`"KUUYE.F-6(7.^(;\'4QA@^Q7*UJ17\0=1=5< MZFUQ2Z#8N)#WY)]]E3Z=;FXN`]V5U%E1;3HC'U7FRRV]&&.EJW8&L;Z=E:%0 M!NX5%U9K6Q(V[IA4WK*=(Y7-7?G]0N-+7'3^RT;MCZSX.%H] M.LZ5"V#C^8Y6=;:WIF].Z2*;AJ))706%FP``-5>B[75@;<+5MW,ITY<8"WC' M/+)RZ+J?3#2JZV<=E5%)E1A:=QW7++'7%=\56NK1U*H2V?E-;EP,+E.'2S;7ID%L0$MLZH"BH$>7JG=-4>5U[W3A'4(+L9&RC>!&6C"XN#C]`L3K-E+"0S8 MKN+BP@2!C9974NGZV$'93U-V/,>LNJT7END$#W61:;@!R.3\+TOJ72 MNF^0T>5_,`AQX/NN3\06MM3)T,?[+OO M#GB)MT/++B';1_L+R/JE-S'DM'I.T!2^'>LU;&Z`.8Q!6KX_N+%LO%?072KK M14:X;E=]X6OQ5IM#SF(7C'@[KM.\HL!/JB8/_9=]X=O13M(,0(V MF=U7=(?&5Z;VTC<09`XY2JX`;!VS*?&L`NB3DH.=\;)0F@D3G?=,\.!S@^_* M:21OE(N)(!=/RH&R7<;)`ZN3DP MA(#$?N@/`<07`\2G1G.Q0_P!'O.^_A%X: M%K:L?49ZW9_92UC.ZXCJ_`'0:=G0IL;3#0!L%VU.FV@WM"@Z?0IV](#:`J?6 M^I-8TZ'?5SH=4N:Y,[G=!7J5;FK(G=:G2K4,:"3\K MSYY;>C#!;Z72\ILN&%>KW(IT\QLJP(`+01E*C1-5^J?NN5=I(9M1]5\SOE7: M;M-,9(,*2VLQL&G.ZLUK2&&!!C*QITE9U2JXU>ZN![BP1C$8*J_P[F5@8WV* MMTQI;D9(315RQ9Z0X1C<]UC^-.M&RM##@#NMFU>T4R-B!"\]_%-Y<2!\_.ZZ MXS;SY\./\5>,',8Y[ZPF<"?E5_-.ET@']%E>/7/IW08`=)Q^Z[C\"_P MUZ'XDZ"[JW7*]8L-7RV4Z#@#L#)D>Z[SQX_6-^LW6A^'/CJJ^JP&J02=I^%] M#?AYUD7UJPN?F!SLOE?QIX9'@_\`$3^`Z=4?4MG$/ID_FTDX!@;KW7\%;ISP MUAF,?V7++#URX:W+'KMS2;5I;;KG[^D;:L2``"9E=-2@T&&(D+*ZU1:^FXM& M96?)-QT\=U6-69Y]/4(E4*E`M>8^JNL?Y;P#SA2.HM>=0V.5P[>GI3HU#^7? MV4E:"W/'*CKTS3<2!`05*X#(..Y5G"6`,->/A.ZH&[''RJU>LT2951]R"V`X M+>V%]]UIP24A>-`!!SV60^L7$29E.R9P%A6Q_&`DP2)2_BIP=OE9;6NB#REJ M+9.P_=%;3:NJ>ZFMG'5JGZ+$IW`!''.ZNVESD'ZJ+MO4R-`DH75`2J(NP6[I MZ58ND?JKM(MM<&NB=T-;).1'LHR^`)2\S8?9:A2:UN3LH:C2YY4KW`@D'/RH MWNS@_P"BJ*U2B`ENRR.IV9+3S/"Z&HTZHX56XI!P((B$B;:=\\.VOJJ5@7.))DSNN;\3=`M?))INTD;2%<Z3@!!$DSRG+CI`!PF=)`QL-^ZBFEQ)=RG@`Q)DH0X3(G*<.( M.)5#P8&?LG:3$1DI,)&"XP0FGW_5`8#-&"9!VXA(^IQP`$.-0DY'9'J!,.)# M9RE@$_\`+`G=(B!EVV(2`]6#A$0)W!$2B`R&X.4Y+HB?NG<0.,).@"/K\(&U M&>2>4.2#D=OE/(`:3N<)SD`@B>W"*0@.D)F@EV-Y1`C3&`)V"3G1,``H$0`Z M3A!$CL$3C!R`3\I`Y&=NZ!A.@-G!X3&200C=+8$C*;4)WCL.Z!MP)CY3QB9V M_1,2)WPD\C&-T#.DY.\II@DD`GW[IM7J[)SET[(IR9C`$#;W2`)@3!2&!@"4 MQ+-,YDH$<>G$1E+`<(XV3-P?=.)!]\X2C$NR?9`P)TD'(E.R3)T_=,V"#@R>.R=A8706F3L`@0 M)D1,S&R-XGX[(0<2/L43W"((R$`@F0`)3$SD#`12.1$A,E8VP,M$#EFT&L M:'$;=UH,=C?8J&BV:8#0K5K0+M^%RM=I#TF%S_>%HVC(`'9-;T&Z1)QV5FDT M8(*RUI9H1N["DR[!4(@;'=.7EA$LP/TA#YT'3Q\K;*\:H([?L@J5=(D@J$.!V=[[IJSI&#*!5JL`F!["<*( MOD\9"'U&9W0M!C.,;**D80.<3LG>[U1Q*B<^-MR4SB=."`FTT*070<*-S!). MQ0PZ*SO3YSZK3K6US%0' MT&22/=;G@WJ#J5W3;,-D3&W"['QMT2TJT7O$!PG^ZXFPMA1OAI$M!WGW6[JQ MJ7;UBROV.M*;IF1O*E_C6+G+"NT6=($G#0%+_$-[E8W7-\XU]#JN#`YY*J52 M`?1'NK5?#N!.ZK5A$C$;1"^C]<43R9/?LE$24[],3J@SV0N_)^B@8G.(2;#B M9@XPDT$X`^R$MAV1D(%$)",SLGW$%+2=])`]T"D:0($^RQ]TH@"?V3M&=X^$0TDQ@8"=S1&=B)3R`-`!GNF('Y3OO** M;WTQ\)&#G!^4OZ)PD-\8'NB:,!)C8)$!I(TIV``;_?*8S.P^RBIKAU%PIMHT MG4H:`X%^K49WVPH7;[9/9.\&-XG@'=,9+AD&.Y5^AMC),I;X**1)*%D:23,H M'='/Q,IH&DR-]DPY=)GY3R=$G"!F@-)G;^Z0@D\A.&NXS*?4?+#8&.VZ`,!Q MB/A(D!I:0`/;=)P,9,2-I3<>R*(1$;'O"8M$1!'RA^3"(S&Y"@&)^B7>(!*( MQI&3JY28`7?YJH3)B&F`[!2#6"?4F]IPGB^'*-'I%@*E0LUZ>#"Y3I56E9GS-/JX&Z;JO4;FYJ>6"X#L"8"7+7 M3%Q]JWNI]#^DNJ%M2H)F,%>@=&M6 ML#1'/'*\^==<9\B_TRVT@'*W+2B2T0(5>RHDN&_T6U:41H!7&NV,%;4,-&RM MTJ3ALBH49:%9;3TK-=)!4F0-M]Q*GMV0#(F-E'1'J^BMT6SB$;]0BF=\'V*) MU*?@(W,=P)"E#'Z?=#U5C1&D"#(,PCIVIU=L*=K3,'=3T6:LB%E?52?;.!]. M%&:-0#?Z+6%$N(X1FVD@IK:,;RZ\2'$0$%6KRD`:U MD",**H^?3W34B;V=PEWIW"NV%`ZQC*AZ?2+W_P":W[&T@`D96:W.#VM$0`!M MN5:-`%L3(CNK=&UTL!")[`QIDJQ&<^F&S`V]U4NZH:R-NZL]2JM#?W6!U&X] M4S]%-M:VI=;K:FN`&^ZYBZ=-:/HMOJ+]605AW&*DD+6*7A=Z>X-<"?JMBV?` M&GNN>LZGKAI"V[)P<-BMZ8K2IO)]1V'"($&(Y5=@=C.ZFI%F))'$*[1,T&1B M$;((AT(?,:!L-E&*X)SLLVM2;2^ANWU"@J/DF-/R4-2K(,<>Z@:USB3G"SMJ M8K6(GORFIQJ@C?*9@EN94K&9!Y5BZ$6C$#(0&!,B%9IT^"AJ4A&)`5TS5&L1 M.^_*A=`,GA37+!JGMW4%3+(!6F=&UC29$*EU1NJD((D^RM0`-Y3,8'-AQ)2, MY.!\3].N:E-^EARO/+VWN+/J$U&:0X[+WGJ-JQ[(`PO._'/2AJ+PW:2"%TQD M8]K.&+:5&FV9SCNI-;>Q_P#WE5MZ3FT6MTNP$?EN_P`+E=)IX77R_`C.ZKO` M$G^ZL5LDD'_55ZL`[8/=>ZUP0C,YPF>#`C9$`-)W33$S*&C#`$2F&X<,)3+I M!33G=0.7!SB?V3`8W,>Z33F"/[I\3DC*H+29#C!]A,I&.3E-`G3LG/I&2/F4 MV'#0,A,Z0<(,PG)#0-L[A`A(,GG9(G&YRG).F!L$C!W,1W1##`P?HF!@ MD(@(GLFQO)12'Y)'ZIB9;JGZ2G&TSD8^4S]Y;(4")X(@H7&#/ZS*-P)$Z8)4 M>G)(/UX5-$UQ&0?E(?/*0!!,)X@3!0/Q";U;$[)V_E2'YIF90."9"3S@$P[&81Z8(S*0;V,^Z* M`?XA&$33Z?=+CG[;)-V]E`^H:?RC='3+7.EY(&TJ,`!TI^=-J M*,-`F=49*$X$<)G-!@,G43D*H0,8@?5.3D0!A#ICM@_=.TD?1021,>G?&4Q` M:>X3R8`:9`R,X0PX.#?U[($R7&(E$6[%KI)W'9#!$Y1,^,'L@-KO1V*`3YFH M\H@)`TPDS$3QN$&MT4C?A;-)M-U,Z78Y'=9/0:>JH&MS\K6KL%%FB#/*Q>S_ M`!7J57`G\P'>5I^&[9UW>`@.C&^ZS&`UZN@-R>Z[OP/TLTRUY'N"N>66HW(Z MCH%B*=!GH"Z;IU!K6B&K/Z;1R#$+>Z;1B#W7GW]=,8T.FT1`)"U*%/(@0.RJ MV+-+1[K3MV"97.NTB:VIG3MA6&4=42E2;#8VE6Z%/GA&I$=&@)_+A6*=&?\` M)6:3,[05-3I@[?:$:5V40.(4C*$M]O96J5)IYW1BF&Q&Z:6549:B1.%+3HMD MX.%:;1Q*D9;SM,J6&T#*>WLC\LAL!6Z=N1`1&B-6VRK%40P@$\J-P(_=:3J` M!.,*)]($0%$VR;BGJ!G=4*]OK.0<+=KT@W,`JI7:UL]NRB,2K;1@&/A15:+` M!`,K0N2)G[`*G5(CU*;%5U(;DPH*M5K<3A'?W#6-C^ZS!4=7>0V3QA#6UD5" M]_IX5NTMGU*@)'T3=+L7ZI=RM_I]F`084M;F)=(L,R1]5OV%`#C906-NYKI# ML'A:5`0`8^4C5B3RVAA[PJ'4,-*OU9T[0LCJU0L80,CNE28L/JM1NDDOCV7/ M7E4%QRM'K-7)B9*YZL^:D:E(TDNCJ9`S*R+X#(,"%L4V:QE9?5Z6G8+>+GE5 M6Q)\[?"W^G@B).%A]-9#YX[+:Z3[HF;D@_W4-W@]Q\IJ)<>?T4+$U;+28_58W7K(UZ#B M6SV6N#)@;?LH[MWE#N"NF-TX91Y[4L'-J%N@X*;^!?\`X"NKK,INJEQ:)*'R MJ7^$+ON./+Y#N3+M,$_)4%PYQ`&HD#8RK-<:B`V/5Q,0JE<-V:\P,;1*]*(Q MS!]T,^DA%B#F$V6ND.RTJ(`D`$PEQ[]T[AOD?"3V@#!,';"!-/JD&(]T0,NP MXXV*9\BL[4S3F=/9-`/,R>$$C=()F2?F$PR3[!`3B-6!$Y2:-3PWOW2B=,'/(3$Y@!02,)#2,04&-6 MV$G1\^R:0!M,@*A'2TDDQ]$P(P=,Q]D0PWN@&#)P(WA%27%4UG%[PV2(,"`H MQI@[>Z08#OEQY2`(<"Z80+TC&)3#:0!"=H.QC*2F<`<@X]RG(.G\WT";8Y$`_JJ$V)SB/=,W_ISRGTD-G'/*0C2 M21!X'=`Y$_/LDQOJ`G/ORF!!`Q]47],0-D!%HV).>R$M@0#ORI/3_0'("/27 M&2=D#.$1P.Z>F/5D[);#;ZA"S88WYA`8$F92@2#KR>Z>89@2A`R<">R((MD` MZA]$3.T'ZE-&T[=@G+73IB>%`;/2\0`83MU5*FV_8(*9),;*WT^BUQD3\(-C MP\?*8"X>P@*]>.#OZM@=E2IQ3%+(U; MQKVM)@\C*]1Z%;>70:V`)7(^#[-K7M<6@>\?*[_IE.=.!G]%Y\[MTD:-E1V& MRV[&F!`P)6?8T@&:C)_NM6RI%Q`C/=<[7;&-&U9Z1Z<+3M:8E5[2EB./=7[6 ME#?=9=(L4:8A6Z+(XPHJ#/3D25=MV>K))114VQCG?"G93<8Y*EMZ4%6Z5(1@ M1"U$VAHTR"9`^ZG;1U#:)1LI@'T[JU09W(CE-%J"C;Q(`R%89;D"8W4LM#AG M=$XC3(X2QG=1>6!QL@J,Y4T^O'U0N!T$`[J"/2(@M4-9K8]U8,=Y"AK"9VGA M04KPAHP2LN]+0"=EIW8AIET3W6)U.X:`YH(6+6I%.ZJ9QRLZ\KX(`SV1UJI< MXL;RHA:U'ND@=UF+I2F`$YMI;/*UI95*@UC3^ZL$`-D;%)M&'3 MI@)G@AN\J-\`KO.GNL;JKO3(XY6K6.!WX67U$@L,]E$TX_KKX>'',<:E*,"WFDX.^(5>T=J.O7Z@N=GX=-_EWS:K7LF9!Y4;JD3)E8/1NH#%)YY MV6M4<'&6D0=H3_#1KIH(G49]D#):V=7NB![C/LG#=+=$9*U"](VN!=`&?200?E-KI^_W7 M5P?'U8`%QU2,S_95JA&GXXY&5/<..HDW"`B`6D3/\`=-I$3N-MR4X@M$P=/!35B`XM!+AR=N$-A(AN$VF1@?=%4+=) M$1]4FX!P2@30=MCV2?E(F#&\8RFG!SG9%(F!`^"D!Q/T2!`/,I2-<@$0B M'<-1!!@3"%['DP!MB`B);]1RB)D2XR9Y0`<-DREI$0=D\MUD@[;2B9),B".0 MH!P03,EW_`'4],#5'9+^K?/.$3()$;'E0-4;.3VW2`,`$R1V1.`'NG8W2Z=_? MNH!T`YXV2%/[+:8DK0KD-,3L.5S_7;P,81 M.2LVZ63;/ZU>L@C4N>J^96JG'1QGB9S1JS]UB]-@W!+5K>+B"TG;V6-T36^Z[:3]E/C&3IZ+8MS!SLL M?K+_=95_3)?EOR4E#/;*Z+H=:EITN@G;"XVRN@)#S@>ZU;*Z-.'-/U6= M:JW>M.\M7-;3D\*AU_KUITZF2^HT1W(6-=>(&6]@XN&OQ#IW,!]5IG$$ M[+N>F7]#J%MJ:09XE?'OA?KE=I:"^#N).R]P_"#Q,ZJ!1K.]0.<_*XV7"\EU M\=OXIZ2RJ'56B"1A>=]6\WI]SZB=(.0O6[A[:M"<&>%P_C?I+:]%[@T`1O\` M93/'P4-L-5,3!'96*3(GNMPM0W!#7P MX:&DR>%0N7RSM"Z1QR+1J]0>(*7E'_&%6\QO^RG\QO;]5UT MX;?(=S`,`AP[]U7J"'=YR)4]P6DQ(DY[(].F6ELGB#_DJ:"0!L#'M MRD(_*1F-TGD%VXPD"-,R"XJ*0'H@[CE*'3,_ZI/`(Y^J=FDND?9`@&:#).K@ M<)23C4E5!D'(D2A8`AH36G8'/L81`[`B.Q/"%H(_F3@8[IH9)R8^TH$3`B"G MR6:9'>4_Y6G;`Y*%@D9)]D0S6#`GZH]()R=DP!`&84M33H])@#@JJC$`=@,` M),P[5!(V*1,Y,`%,WZ<`.=$B?V2`#L1_V3DD`8CA3H,`_?V6_XP8VW:VFT-U;=H5[\.6&C MTXAK,D3\+/\`&.IUZ6NF)V"XYYS?JUAX[;[52Z)0\RL=8+I@8*[+HEGH:V`- MYPLCPO:%T$@D[X7:],M0W3I&%P\EVZ3AI]%MA`UUI'3[^RC<6;9@:9$*];4S MA0T&`$*_;T8Y*-;36],`CLK5!D-'LHZ;6@*>UAPGCL5=":F`0`3/]E8IC$<0 MHF`[*:EL!,!6"4-]0,HP!'RHV[_W4U(2)[J$`W>8(A3,9)S`A%2IAQRIPT!N M4T(6TQ,$\)5H8P03E2NTB?=4>HW+64YD*45>IW(IL))7+WCC<5=,D\JQU>\= M4):UQ*/I5`$!SQ)]UQRNZZ2:2],LVL`)9GW6DXTZ+,B!W0.+A<7K7_S M#GW*\TNJ%?I/7J=1Y(:X[Y@J7VK>&..^7T;X:K"XI"I,SW74VE2(_P!RO//P MVOA6L6YS_P!UW-O5D`XE7&\)9RV*50"`#A7;6L0T$D2L:C4(]2M4:OW"Z3)F MQLMN1IW56]N?285&M<:6D3^JI75X,@._T6KFDQ!U&JV""`9"Y[JC_28C*T;J MX$.AP^%E=1?JIGV7.UN<.,\6.!!D[K&Z!J%R6G'Z+>\3T=3'/VC]5B=#9%WM M,G"EZ+'7VXF@`2W8KJJ]$ M1$+.O+8%LP`MQ&`ZB1D'/[([>X=3<`XF#[J]5MP&$9SLJ%S1+7'>.86M;54?BAX9NF:JUNS#1D0<[>R]/Z9=.;4#7-AOLMFKTVTZE;::C& MND=EK#+5<<\?KY)\,7%ET_QI:W'5Z`?;TJFI[*@X'RO6/_$'XT\$7_X?OL>G MT[*YO:VGR'TF-FB`X$Y&TA:?XE?A9:W=%]6VH-94`P6#_1>&>(?!W5.FU1Y[ M/29&QD1$S]UZ\?[>^WGRQF4U^&9T6L0]I(N$W>'`>+Z1H5!5:(`.3"BZ7?,?7I"#+MCP5T7B M.C2K6K]0W"Y#H]L*74H!($X,KAY/R]F&OKU+I%8.MV@X,+0:26RZW'M`9(/RKBEJG>G+O5CGW61>U0`8=\+2OGQ.?E<]UNNUC'&?U7?&..5X5 M*MZX5"&D0A_CG_X@N?KW;_-=IV0?Q=3_`&%W%5JN].D1!5N MX+7OEK(G8`[JMN2%S2,@$2)R@&>%(X3$ND(1`/?V32ASI(@I@ M.-TT"(TDD?;V1L$B"\#G)_1!IYS]U`B#JV^R0D M081-`#ADHB!L'?W5@8DNW'Z0$VDGZ(X])WA)S=D#`^F,S^Z8"<1E.W:"=L)$ M$"90(3$29W14C#FRT'V.Q33@YB.1RF(@9A`3RW=I4=:=3B'&"BS`@@2F-LYW09)[J@I))@Y"D-1SZ(IP(;[*-C9,$@>Z)H])R$ M#$&3V[A(`B#"D;ALD84CRP6X:6#6#.J=QV326H7$XS/PF(=));]`C:9(,#/= M'#8@E#8!J()&0/9"3#IZ.!IF"`>$PQ^5L'N@.D3.!N-TU0D-@3*3=X@9V ME$]D@X'R%`!(#F@".,KHO`/0J_5NIL;HEDY*PK.AYM9M,'\R]J_">SM[#IS" MYK=3LRN7FS]8Z>/';J.B]!I6/3?+#`-(R>ZX+QC18[K@I,=MV*]*ZSU.C0Z: M\AW&%Y:*K[[K3JO+BO)CSD[R:EM;_ANWAK1I=\KM.C420"6K!Z#1`RW["GHI1!/NJ_1[<""0"%M4Z M;`T8CX7.1M0N*3GB<_"PNOT&LIE[H$!=:]K0"3PN3\8%S@\,)5LU-D_#A^J> MNJ?9<=XXZ36NJ(-,B09&%W#Z+G5O4/JAO.GBK3RS"LO&EN.KM%^$[:M&T8RH M22`O3K0N\L?YKA_"EJ;>H.R[6U>&@">-RL8S2Y]M"DX3$J1U9K6Y.0L\W`8" M2R>RZ&VIE\'/\`FI,C M+'3'ZW2!ID1PN>Z93#;T;[KM>K6Q-N[&ZYFE;Q?2-YRM6\,1NV3]-,@[*>FT MG)RGZ=;%U,$;J\R@`?WE,>6:SZM,021"KOH!_P#3]5JW%`$8(*A%&&G"VS6% M!LL>O0$]99RT;[J%)S(:1]UF0ZM4]!D$K+N65&F"=D5 ME>BW?+B2`N>6=^MX82+?B"U>RS+78D+DK*FZGU(:OZ3L%U=[U)MVP,F)[K.? M9M%<505G/5G#4NNW0]`+6M!)(A;AJ,-'4:D'@+G>GO#6#U+294+F$$C(X*WB M9(>HU@1#>5QWC"[\J@:3J,[\@I2P$Z09XGLDX3).23W0/W'<;H$( MP3E,8G?!1`9Q"'?!_P"R!O8@GW2';!1%C@W+26S`(3.:0^1('$JAA^6(B$6\ MG?;"%HR`"B(@Y"@+4<'@)#\LF4#$4HVQCW"<'./W3` M83!L'`4!.;!("8>[3LEI`,28`S*<`:?9`P@`04@)G"1$B-B$6F&_"H;TPGF! MQ]TS0=<3&.4S=Y/'NH@CEHS@)`#5ONG`(W`^)3&)[>R*?T@'T@CY03Q)E&V( M(WG;A,V`_@9V"(:28R8E'ID0-TY)#2UKX!W2!@QW0)H.DR["+4(@Y*#$P#NG M`;OPJ&D`@?NB<2`1&>$((!QD)F)=)!PC,MQ*"F,F!"EIM+GC2TDSG*@N]%8'7;7`1G.%ZAX4N7&M3H:H!@ M&%S'@SI%-U'4X&>Z[?PCT84KP5BTX&Y7F\MEX=\,O6+_`(W:VAT@PXDQB%R7 MA.@'W7FR22>5L_B9=Z6-H!RK^!+?8N$Y[+CCQ+8U;P[+H=``-DB>5U73*>1C M'98O1Z0$8PNCZ53AX)V4VQ(T[)D1Q[+1H`$P!E5[1H!SSP5>H-;M*S6XMVS9 M`,%:5N,#"J6K1I_-^JT+1L-U;A%6J$Z5:8`(]U7H@=HE6!M`$JJGIQ,*=CL1 M.%!1VR-E8HB8,9*C6DM$?4*S1:V1.%#0:6^TJ1U4-W5V:25:[J;]-.GCG5FM``6BVKK:`#"P[*F\ M/`+EJVI(@?ND6Q/4<\LCA974+!UP3JV6TQNJ!PIQ;-TSI]UO6V.G%U.BM827 M#G=05K#2=L=UV%[0:&N`;E9-S1;L`5-2+.6/96K6OD"(6FU[64LIVTAO^JAN MA#2&\+#2IU6ZTT708,+@?%+[BZF6[YU,&5D]1Z-0J/T:0>(6KG9#'*N M:_#?JU>C=,HUB8G&/]%[CX=N&W%HTC)/N5Y=;^'Q1K-J,:`9X7HW@FF:=%K2 MXDQNN/,K6=F4;74&`T"#&0N9-$-ZA`&Y767PBD8,K`#`;[5L96ZY1M]-MR+< M1V4WDN`("L=,9_(:8X4U1OK.%TG3G6/LM>K1&8'TE4[FF0#`!51AWM)L:EDWM$`D@[KH[JB(]0"RKRB M-41^BC6G.D/IU"YI+2M3I=X'0UYSLH[VD`2"/LJ<>55#FR"J6;=`ZFVI3G"S M.J6@+.ZGZ=AU#> M^,=.N6M.P71>+K@MMGD/CTG=9OX2VC+KK!K%N=>Y7HG3SW\O7>EVX'3Z0#?Z M>58\@?X5IV=LP6K`!B%)_#M[+K'E?`-Q)JDSG=5WP.EY@0"<(4-!R3,),'I(D=Y3EPC`G M*$F3/]D#N)VC?A.2YP&HR>QW0!_^RG)Q&^-^Z:#9!B-NX1GU.)(P3F,)@8G; M/UA-)D-)$#8*AY$!K6DG^R4XVV288[#Y3N)!(TX]U`S2=XA-F8C'=&S:8QLA M<[.V1A`T@-T@9F9X2).F83NRT2`8"%Q((B$T'`)]1!SRF#.R<9]Y2!W`&4[2([_``J"#HD&"(V*8$;C MZX3$B,3GW3LB,$SQA1"8`23JX3ZMH/RG%0:1Z8@S,)C^>50\C5P)2)$$P/9, M=$A$S000XG:0BADX[)-/9.V"1.>"B,:1IC"`&"&_*?&Y@?"=X$0(CDII!,DX M&\)H)OOE$.^`WMLD0-YS&1[)@?5B/[J(=ADB`M#H],5+EK($SLLX1W``XE:O MAMS/^(4]1`$J9<19-UZ1X.L7-HTSGX'*[BT'D6VJ,`25B^#:;/X.F&Z5T=YI M9T^I$8;_`&7S[;M[,L9T\W\;W)N.M>6TB-41]5T?A"EY=LR2)AMFR!$K-:BW:#(,+0MP!OM/=4K89VV5RD1GD ME%7*)C`&RFI27*O;\*S1&>))V2M1:I``S*LT0,$E04FY$J9KH&!NHJ8OTM]X M5*]N!,:LA/=U@![K)O*^IQB=E+5B2M<:G$`D*(ND$3E5M0D[H@2,0IMK20X^ MB-CA`(.5&R'$#E6[>A)]E%!2]3NZLTK<$"1NI:%L"9A7;>B(`(5TOLJTK6(+ M6Y5RWMR`)^RMV]NT1'"L,I,V5F)[(*5,1B,=U.UOHB%,RB`,DPK%.@(!W6IB MSMD75OJ!.!&2LJ]MW-<3NNJJ46EN6CX5.YMFO#F@#/)4RP=,KI[1S_\`!S,A4>IV8T'V755+8`0,E9G5*/H( MVQ*FE<)U!OE.?MA95-CJESM,E=9>=,\^L0)AVZL]/\.`.U8.%B[IN1B6=H*F MEI$+J>B6XI4P1`14NF"B[;;E:%*D`P8XX*2!1,[A8]@T5;V?=:/5W MM#2TF.%#X>M]5?61REYJ=1TMC1B@&B9C=$]FY.ZFM9%,B2%4JI6G@?*HUQ.P^5I5VAQA4Z[(;I)"J1GW=.1,02LVY MHF3^F%MU&C3@@_*IW3&AIG[J::CF^H4C!D[%8US#:G(@[A=-?T0YC@!(6+>V MA#C@&5-MQ3MW.#Y!PCV];31:)B`C\_P!U7&!&HI9_Q%>OT>/V?!5TT3J( M#>T&56J@1+3DXRK-P/YG`/;=05&>K2X<*O2K$Z9`!!^4GMTZ02W!)4`;B,;IB,Y*>!ID83Z,$E`+A.!*9A)&Y`]D,S@[)F$MJ-J`!VGAPWA27ES7N7,\^IJ%,0T1L-X_5!`P-`,C!_5.(X M&R>!`<4Y:-.HC?*`($[3E-$$1RB($8R)[*ST9]K1ZE;U[VV-S;,>#4HM=H+V MSD3PH*PS.,A,YQU2[C`^%/^488=,EPC@S"8@#5(:<1GA`+FM!'O[I-TD=C\I.:#_4`# MV1A@+8)&_=4"($EHD:D_E,`:2_A-@&'W/=%&29.W?=/Y;/\8']T!#)F3"!L:-9KQ\ MSV0!LMEDCZH(PX3LH=/6OPTZ[2T4Z%=X#L`2?9=IUR_I4^CU"QP&L0#*^?>G MWE:U>'TW?E[+;/BB[KT!2=4,;+RY^&WIZL/++_9TW2GBKU\O)D:H_5>B]*IB M&,#3``@RO-/`G\VY%0Y)S\Y"]0Z,"6,Q&Q7/.:X8SR]KMU'0V::;<[?JNBL6 MRT`F,Q]U?MP<3\J-1 M98(`D0K-%IB95<$@YV'Z*:@\GX^4VU%VB,`JU1C"K6WJ`5IK0`,J5J+`VU9" M:I4`:/5!0EVD8^RS[^X+=7J@!2J'J-Q!/JQLLXU2Z MGD3E7Z%)P:.RL460)A-2!=@8PK%(-@3\0M*DIB`TR,J2F`7 M)`!V&P2G9^;M"HL4Q(]E-3$-Q^JBIG/]E/3$C/PMQ*"KANP,JNX'=6ZC-\J( MLA^)"595>G3#B<"$]6D"V8E3,`#\B/=/6(`(^JFFMLRM1R8$86;U"WD'`6S6 M[XB%1O=);@;KG9'25S;J.BX@C8K4L-#F`1E5NHTAKU-$'=*SJ^7$F5QZKI9N M+US2U<1"JW3A3IND20,*=]VSRSZA*PNMW^#DB>)2UF1G]6K&ITRZ:U'#!S")X!&-^R"BZ![E2O`^(79R0 M5&&9(QW4%1F8C'=6G-QOME05)F`JE5JK`!@&53>S)5^O.D\*K4VF=E44JS-! M)5"\!+<8E:=U!DA4;B#@G)4VU&749)(GC*S[VE`+8,E;%8-!D$*A=AIU1OOE M-*YZ[IN;4)B.,*`@;[$\K3O!/ITX69=L]4@^\*QJS:.JXND$<*M5T@1"FDD^ M^RCN"-)!&58Y933*ZBZ).^E85R]KJL-'J&5O=3PUTD?*PJ[8?M))PY,JXKMC M6+:6(B),*OU*\++=[FGU.!!,*%U;2T,&_99_7*[OX9P$1&4PIE&+;UA5Z\V? M5Z^WNO:_P_I>784_3!(F>Z\2\)M?<]>:3_34X^B]Y\*L?3Z6QH&-*].$_D\W MFO#3?T)0YTD[X]D`&X+D0@-$B?HA. M1(R@=C?2-LE-Q,0@:=.YQ\(">WW3`ZZ1D@095(4F/68E(NA^_QE+20(SG8]D[A#@`\N`R3L@=CM.'-D M1M*$.,'3NF:`1C]24I]1C"B#IG`](GY4M!H\P0/U45,8!G92VT>:-P)RBQZ) M^'E.&LU8C$_9>H]$;AITXP%YK^'%+66DN:.TGE>G]$IP]LU&G"\7EFZZ1U71 M_P#EMF8XA;MF8`GY6)TMHAI6W:-,`X^JYUN-*UG`S&ZOT(`ET_'94K1I(GZ* MVP:>2HTG)D8)D[*2T$B!^ZAI9V5TJ]9MDC8B4T;7+1AU?FPKE$MF9D[95>@P`[1"O6H$[X*?\6+-J#J#B MM*W`(`GW5&DX-P#*O4*@:!ZLA6"Q2;Z"1]DS"2^",;RDVLTX$2I&:"Z9&RTL M'2,84[#J(,`2HZ6DN$?=3!XF`=D@D8R/ZL`*1CHV.V84#*ITZ<;J1KLC:2M" M<.PF$%V^/=1.?I^4[*D"<*[-)7QI)D;J*KZA&)3O?)F,)M33)4%2L#JSG*HW M^)P%H5R-EGWC06F2L9-XL;J-32#E8ES=Z'D-,0M?K`Q`.>"N=NZ9+BX$X7#) MZ<$M:^/DDZ\]ED7]TZK5#9QLFO2]F!RJUI2>`KU;_P"["S[P2^`?JC3. MKB7$[$9^JH7%,:'0UY]E!5?+C[*6[:0XF8$K/JO` M>?40[A8OB> MX:*+@2&X[9`6G6,N=JGPS^3Q>>HJUZSS734C* M'^-I_P#R+!N6N-=YU')[H-#O\7ZKW/*^7:WY@V`3['=5J[8)])'"M7`!)*KU M-);.?A<'N5WD;K./A,YIX./A#H+0 M"(V^4B2T07$CLG@1G<;82ZH(")@X3.+=)W M)3_T[(7-,?FQLH%)$PF$\$%/I($2DSG2=MT#`NDG5D^Z6VQ`QLD0>=NR$P!W M^J51$_U2,\2GI5#3,M,&(D'9`UH.Z<@!`MR8.V8)2/M@]DT`'O\`!2`$D;_* M!QN,Q&0DYL8@;;('#LG`@IHSF` M/A.1C:#O@H3ODX0$T8P`4B)$:1/>4FMS,.A)S9=@[(@F2/\`)3V7_/;C"@`X M*L].$5`,CX18]'\`:139(X7IW0A(;"\R\`L,,#B97J?06AM-N%X?)/Y.N^'5 M=+@-:3QNMRR`+1DQ&%B=*@M&(6Y:B",;K%:C3MP/+YV5BGAH[2H+0"/[J<8? M"RTEI.@C'*T+,`JE2:(SLKE"&MW1J+3<`DE5KNL`(!QV2K5@&F52N*FL0TY4 M$-R^2294%>JPT@UM.'3.N=PFNW$-,G94PYSZ@RB=K5`-DP9/*O6T-V!PJ-!N MD@;*4U-.2Y1J1KTZK0T$NA2_Q8#0)(GNN;K]2T#33,RL^ZO+UP])(:<[)MOU MUV[=G463ET0IJ?4Z9P'97FU"^NGW'EZX.RZ+IM"Y>`XN*;OX6W&?77TK]D@Z ML0K=&^8X8=EJTB3,%79H% M5\'A1/<'#(^O">J8YG]E!DDPJF@5@'-@B0J=S3GLKSF0"3^BJ5ORF/W6NS;+ MNF.DPJ%<<1E:=WALK-KCU?KA--13N&!X^%F7Q@XV'ZK6K2LV\``D@(LK)O2W M MCQSEQSZ=W^"[7/ZJTN_5>N=>J"E99($!>-?@<[5>NK:X`"]!\47HJ4"P/]EZ MO!/Y5X/U%Y57]0I%Y))W3?Q]+N5GTM)I@D$HH9V*[^S$E?/=RQH&.>.RK/TD MF6\<*U6>X$_E^55KETB2`2N3U(@!JDX"&-1_9)Y)=O\`5($ENXQPB&3L.4S3+0[3M]D`P9V/T2$P2EK)))W]BF:X:C MG`0/EISRFV/*69$[#ND2,`2BF&<@Q*0!W,E$8G/Z)AC9N_,H&VD$2-X3-ATY M._=$8,,[^Z4,F&X!Q\)W@#TDS'(0,R(W@^Z3BX^WR4WHT[_3**6EH&G(Y!.55 M$2#$-(')W09`AN?=)L3))QC9.<&<91#AW$X"N]*@U@=PJ;6D@$`1W6GTND&D M.QE9H]`_#_-8`#CE>J=`TEC,%>5?A\0;IC2['*]7Z""UC>5X\_[.KING;@<+ M?M22T2L/I>X&WRMVT:-(GCAX(#42JUV^7$3A16U,% MP=ND]NNI`,@*8Z:5/=-$A[BJ*3,X7->)/$C+)CB7[<)>*^JTZ-LXO>`!LO)/ M$G6!>W[J;JI%.=X6+;O4>KQ>/?->A>'_`!11N+R7/G,1_L+L_P"-H5K::?(7 M@5O6I4&MJ4ZI#VKL?#_BNG2L]-:H!A;PMG%8V/$76V],OA5J&&3NM[ MH/XA].9;-#Z[!`Y*\:_$7Q"WJ5?RJ#O2WGNN0;JLJ0-2IW'A[S:T4P/E4K_I%[TT"H M#+0?=3^6/;M,\ZJW+0`3*GJ.TD@G"AKP MYF%C8RKQYU0`LR\;(,B/=:MY3AI)P%DW67;[_HL5O%2TG5$?5/I/,(B3&-^Z M"8=!^ZQIO:S0QMN%J]/.`0G.55NG#20`M(IW+001NLZLT`GM[J[7<1,*C=.,F/T15. MY=!,RLZ_@L)!A7;DDM67=O=M,@H,^NTQ(*R;W+HB/A:5Y4`$;$+*NZX)))D* MEJA?._EF`[/"PJCCHYEQR5Z_'-.'DKT7\'!Y5HXC[_5=3UI MI>#J)$9PLK\+K`T^@,K3EXG]5E5'EC40"B\UG<+>DV\%N7;-('IDY"IU6'))&#RKE8>C42T@=U4KQ,<3 M*YO6@)B1B"FKO"36@/@=OE4.\$'26P0<@J>G7;3MRQE*G M+CE[A)^D[*`-`)./F=D0D-@$`(&`],DC!2(;J_`1%HG83O*8-$@$?7V0T89:?G,)X( M,C'9,1C';=2.CR\B%0,Z6D#&K=)Y=`U2G#89,?FV/=(M$80,9)B4(:?>$^B1 M@0$;8TF9S[H!#009"8-_I.(1N:(=F$+FD@#4<\H&CQ>/VKCO'WB&XN*[F4G'1/=<5_P"H=4)=J))^ MB]1;X)-2B:M7+CG*Q;OPH^WNH#-0!Y*S/),7T,)CU'-65.N6B7[9C=/=U"PP MYQ`[%=)5Z=$KL/=J88!'U4'4/#%U;VVMU+*Z8^6.>>,_+D[>L^G4EKB,[KN/` M7C_J'0WL8Y[WT@-BGZ`>&NKVUZR6O!'L5J]0H"[MM+&APXY7R9^%GXM_P`$&6_4*NGC M43@[^Z^A/!7C_I'4K&F]M]2!C(UC_-7'/?%>?/QY8)NI]*+=1##J"S)KV57) MP/==1<=2L[QYJ4GM+3[[K!ZZ^@X$-ZV.C]4%0:=0D?JL[LXKO-7F.ANJG:%&:GIR1\JLVL'4HE M`:@.)]E*:*\+3.HX65=,TM*T*YEL<*A=G$AWT6:D4:DM)(Q[*"H\`8E'<.WQ MD*M4J0V94;3T7CS/E;72WN(#L^ZY^W?_`#-UN])&IN^R%;5&IM`^ZD+B<'Z* M*W`@2,J5Q:.8[:`PG(S*N$Y2ZDX875GDO<"9Y`5*@WS;UM!LET]X*>^KN=4E MHEP)VPM#\.;=USXIIO$PS,_0KU7C';S7F[>R^&Z!LO#5*B['IR5A=4JL-P7& M)GX6_P!8J.IV082`(Y7%=0JDDN(F-EZO%CK&1\_+^657V=2H!L2?NG_XE0_Q M?JN6>^7DR<^Z;6>Y^ZZZ:]8X2X9',G;2#Z!K/YIV'^:J M5:[)E[6O`P1IC[+S/4H5`X"=B@:(SOPK3G-8QX:^6NSIC*KD@B>WZ(!J%SW% M[@.V/]^R",_F@SO*DC;4=QE"6AQ!(^@4`$&`"?@=D_IW=O\`*83>K,"1\H&:]P)(P"$W!).4^\"82()$3@I5.3@9D'A% M4)D&2!P/[H&M`=C[IVN,^HC!W1#28QGX^48)W:0.P3:0,@X3L#9&H2#P#'ZJ M@8(=)@E.Q[PSRPYL.()!'(_[I&(Q$#&R1$G3C3V"@)@)D'2"9WY^J1!Q(]Y' MPF`Y..ZGI:!N3L. MRXY]MQ>\#S_&R-H7K_0!#6#;"\B\$M#;_3.)V^B]=\/D:6CLN&?:NKZ61H$# M$+6MB2(_99'33D"`?JMBS:/3&WNN5;C2M&@C;]%HT@UK?=4[6=H5@XGWX4;. M\->\F<=D-=H&>>45(-&\R%#?/`9RFH6JE5TO+0=E!5.AA6P?943TEMR\D#=;=G85:]3;=; M-KTSRHD9"7^79N8].:Z3X48^MYCV8/9=E;^#;+^!DDZHSA6*#&TFB!GNIA=7 M'_*\PZ2NF,QCCE*'3W,IXC$KV2YL:CJ+WR` M<8*Y'Q7T(WC7`M)XPLWCITPRW>7R]U2VT5G`;?XEDUFAH&73)7K7BKP15I7! M=3:8G:%Q_4/#-Y2.H4G.S!PNF'FDXKV98XYSAR]-Q>T3.,J1N@X!,QNM"YZ3 M5MVZGTG"/LLRK2J-,220N\RF73SY>*PJC@&2''.)'T6MT+Q'U+IM1AM;VJS$ MEH<8^RQ'3N1'M$)WD:,8'9:NJY^M>P^&?QDO;6W#+ISW$#=I/WW6YTK\7&W_ M`%1E#4X,.[G'_5>!!Y`#I$D3(&ZEMKFI2>"UV1V*SZZG%<_VL+\?973>HV_4 M>EC0YI,<+.I7KK._`+S&VZ\6\`?B(ZRMA2KU':F",G??W6[?^-FWEXTL<2'< MA9\EEF[VY>/PY89:^/=^G7C*EN#KD0KC*HD1LN'\$]4-Q9M:7#@[_*ZNA4;I MR5REVWE-+I=S,A5;P#08&/V1!YU3M*KWE26D?LCE5"[AI,;*@^H3G:.5+?U# MF8*HEQ\S<%9K<7+,GS02<%=)TD^F`87/=+IEU0?JNGZ:`U@GG@++5:=-WIP4 M-1QA,PB,[H*AR?=:VD(N[[%05W<3*3G9CA05'K4*%S\\QW*C>^!_JB, MX8D[G=9O4:@`,.CE61%;J#Y;`B?98M[6`:X=^2KMW7#3_=8O4:I>Z!WE;2 MHJ[A$G8K(ZE5AKAJGOE7;IX;1(<[/>5SW6;EK6%PF7;DJ,=UG=;N@V3/I'9< M5UJX#I@2"<-E;?7[II9`P`5QO5;B:CSK)/)7;";Y9SOQ1N:LO'IDCV7:?A/; MG^.%PUD!HWWX*X>QMW7%ZRF&DS/&Z]L\*=(H]-Z*P-/K(EV9[_YKOK=F+R^3 M+UQVGZ_?LH]QM_N5=\05A1N708U&%POBJZUO(! M)R9A>SC%Y<,?:B_BJE3U^:&SPEY]3_YPJ%NXFBTM+H^`CEW=WV"Y>U_+U^D= MGXR\-]/;3\QE,LJ'AIW7$]0Z8UCR((/*],ZXUUQU`TW?E'U4;NC6M>@T.'U7 M.8UPGD]>WC]W1=1`U-)CF5"_2*8,[S(A>H=7\&"Z9_)<1&=ES]WX!OZ;=37A MXY$;*;=9GC?KC3$("G&P[I, MR<'[!4,79QQC9.(F7O,DIZE-["0]KFD\0F@3.<\(&!&0#)E,2.,RB:&YU`F= MDTCL80(#G>$PTG`$0B@2E`T[01RH'I`!DDB>ZF)HFU`'IJ-,&,Z@?V4&S!P1 MN40B<';.RH<@#'VSDHGZ0"21)0D@SEL>R9VEP`D;=T"D$$`1/NA>Z378.<[9*!H)VPG$#!PD!P?NEIF3.RH36@8)!E2!H< M,`F/=`W><%2-<6[8/SNH!:!D@$CY3:!S(/.$5327$M$#M,H#$D"3]4!M!([_ M`*I])#])!!Y!3"-')?QV2+3.<(.H\&4PTS`&)(717=-C6^IO')6+X+HES0X+ M$[*G2?YM025RSYX>O#'TFVQTKHM'I_2Y M;AP"IN:*M:^+/!U&I;.#*0'NT+R+Q)X9N;*LX%AB5]8=?Z53IT6N:X%K^.RX/Q+X< MHW#S_+&<_"L]L:WXO+^7S1>652FZ/+/RH1:U0X^C=>T]7\$->\%M,#/`_P!% M?Z1^'=@*7G/I^8Y@D-=^7]EUQ\UO&G3.X2;>"5:%2GAS7=LC*K.U,<=M)^Z] MF_$;P>RM;:>FVK35:9FFWCM@+R:^M*E&X=2J4]+V&"'`@A>B>25QF,RFX73? M56;F`<+T+HMK:LZ<7ZAJB?=>>VC8,!H!'NMNRZC4#6,U&!N)68WE>KV-352;F)"X8=/-Y>*O/?+("IW- M0EN\2CK/AF<*G7<)SF5IYZI7U0P1.Z@MP7P3GV1WCB7'&$]AETD$_5*U&QTE MD$%RZ*S_`"?V67TJB-`*UZ#2T`]ME%3,;Z!Q&Z"L1IV1>8,Y^?=15GG3GA6* M@<<^RBJ[%&^I(Q$*M4<2[E6):=[X=&?E5JQ;$20B?4P2JUQ5):)@?"TQ45P\ M]RJ55X;JER*XJ9+MRJ-P]V2#*H"ZK9*R>H59!`/W5B]K2>0LB^J03E:D9V@N MZI#?4LN[<8)!W5BYJDR#.-EEW=:)DA7>F;RH=5N]!+3D+F>K7;7`AQU#D+6Z MS6`$G\IW]ER75GZB'`B)5QA(RNNUAJ(#L@[^ZYVYESBX!IG`!5[JM8/J.:2" M>\RJ=C1=5JZ&YU87IFI''+FNB_#OHW\5U-A73O`=/^BXR\ MN]=4OW,_HM#Q;_P"JQMWY>K6W3!<.-2X>XU%H-Z;28T`-..96YTCI8-/43G=7 M*]FUHPI:\#EZMI4;)8^?91:'-9_,9`_=;M>@T..<+/ZRP4[7,$\*;-;85:T_ MC+OTLAE/HNMP6MI2J5;K+V/-3RW#V"LARAZST6WNF%KV" M7=PN(\0^%:UO4+[<2)VR?[+MZOB&B7#S*+E`_KMH]_\`,C3&Y"GK^'3'.QY7 M6MW4ZNFH-)&(.$#6Z3(=MR%W/B2PZ=?,-2W+14W[+BKVVK6]8L!MJ,P@,EQ2+\>EH$)M3HB/]56B>-O5MV3AIQ!W33[!(., M[!$V<9R8$>R4NT@H2XDF44D#;]478>9G?L40&-\?"',_ZHVF#,1[;H'8#ITB M!_=)C!)`$^X3`N#J_*G<"3B#'NF+F@"<%$"W2:_V M:G3:\!,+KO5F?=>J=$PUIC/LO./P\I:GDD;-U9\0!M"F6@X"Y:O=>GVENHSF`UKD.X'"N]6ZK3LK')&`L2 MCU!E%I).W*X?\1O$%9[#3H.=]%-Z;F'M4WBGQZRA4>RD[4Z8@'_1<9U#K_6. MK.TAYIM=V_[+.Z;;NNKXU*HF3RNCI6S:8V(Q]UC+6+V888RZ8M#HQJ.#J[I) MXW72]'Z=;4:`UM#BW]$UG3!>T`\\!=1:VE(V>&B8F96,;I=,H7 M37%M(-DKF3T?^%ZH"#&<+OKRF*3W`-()Y7$>+;Q]"\:[8`Y*WC;T87EZ]^'] MH;JFRD'0T-G==)U.>DO8\50^"/';+*BUKC!;B94OBWQ_5NS.LN<-HV MA)-37USR\65R_P`>K'Q#Y\,K5AC:2KEM9U.HTP:,OC:,KYLNO%767534HEQY MQ*[O\(OQ;%AIIX4RGK7+&^^/*+J-K0\Y^FG@ M\B%Y'^+'A9U6[-]0HQK)+H$?V7LKP"X&"8[Y5#Q#T^E>6I:Y@)`X"U,JL_AE MN/ERM;NH5M&W&$SG%A!'!7:?B%X?-A>NK"GAQ.RXVY&C):"?NNN%]GJLFMQZ M3^#MVYU4#)(`PO;.E5?Y`)'"\)_!>/XDQOC9>Y=.`%N.#"QCW7A_4]IKFL9P MJWFD.)F92KDZL$2H8=&ZUIY052Y[HTX'5R'B"OYME5+SJC2&RS< MF5D6W3:CR):N_A;T5MOTQM0@A[ARNEN^(XYSUFZZBUMJ=I8-)(V7EGXM M]3+'FFS(G/9>D^)KC^$L'DO&.Z\$\?=1-UU%X#@9*]N$]<=O!)[YN?KU75GE MSC]$]#)AT.4#!!)@94]`MU`M])YDKGMZHV+=H\AL=NZ/2/=!;$B@T3PCU.[_ M`**.D?45M0T4Q&,*/J-.*1(.0K]-N(4?4*8-J9WA9KP1S=.F*E4DF0J/6;8U M(80(6U:V\`F/=4[JWKOJDMO8-&/+6)U:TA[FL!E==?VEQ@4W>HGZ M*B[H]:"?,:2HSF3F6_Z*K6L M2]^DAHWB$TU,G$U^E,V#=NRIW'3:@P&Q'.E-."R6QA4KCH#:KCY8R3QNHU[1S5O;5*];12 M:7$]EOV'ABYJTVNJ,/\`DNU\%>'+6SH.?5HZZIYA=*;5M8M92HD1R,*V:#GZ-;F&&Y(!2H^'Z#20:1WB)S^Z],N[#RK1VH9/SE5K'IE%C-3F^LP MI$][]W<>RXWJ'2+RT>0^BX#.87U`^R;4'J:"L3Q#X5M+NB6FD,[8Y3==,<_ ME?-;VEIS@;Y0$D#!F>R](\9^`JM#55MFN,3L"8"\^O+2M;5C3JM'2.%$Z0<'Z(BT#(G[)B,SJ)^55"X&9+OT3C3J.N8CC>4[L`2XSW" M"#/O/952@SB)2(.=@"EDDP=N2E!F"5-!S&H$'YGNDXX@"`G,$C(R4H,Y('8( M&D:<8*6(.R9L`?F$)5!)&!\)RXN`DM$"!"&B M)VV4U#);)D_"KM!QLIK=L.;)[0@[[PN\LL8+1D0K5;0^L`=_94O#CW"PV`(" MOV-(FZ$B? MMJ.AZ:,`&/JM>U$#(!]UG63)B``5J6W&%ATBW2$-DQ*BKO!@$A$ZH6L,B<[* MM2FK7C@<)5BU1IC27'D;KG>M5F_\0#?Z6E=)>'R+0_\`V]UP?6;E[KXEN=., M+.7$>KPX[=M9=5HT;)H:1JB-UG]2O/XPD`R5SEK6K5G`$D#Y6WTNAL9RIOV; MN/HJOZ:Y[#C#ED7WAAE4Z],'.05VM-@TQ"I533`@[X61:L%$QI,K1H5-5.<_!7#"/5Y;LUX!4<97(>-.A" M[I%U+<<+IKRLYK@!LJ3[C,O$3W6\;-[8FYT\O'2;NA M2#C*W+NE1J'4*<&9E2L#!18QQB<+>7DV[;W!6?1+048+`0)5_R`^U@P9R0ICDX7^-V\M\.^(NJ=!O12KU:A9.Q<5['X M-\56W4J+7-JC5&1J7F'C?HYKZZC&Z2V"05X7UND:5RX"9E?47C^WI5>GN`'J((V7SCXUM'TNKN9WSVE0>0-UY?\`A+:Z;5K]($QO]%Z53=%./9;Q M_+Q_J;R.J0XD\\H@TEL]E'1)>\S@?NK=)GNJ\RO0HN=5Q^RZ#I%#0T$B%1LZ M4OGW6Q;"&QNI)^6MKU(C3[IB<2@&1GA)VV^$#/>0<%5[I^KE%7V@*K5<"2)C MY5*&JXD$*O6=C*.K4$C.54N'DM*UME'<$`95*LXP7$XX4E=Y.Y52Y=#Q6XEJO%^@UNI5#62L_IUN_J74FM@F#W7KWAFP%ITVF MQS0T@!/)EZ_QC>$O]JY9WAUE*HQH8#GOLN\Z#:BUZ96G M=N;0L]($$!=/T\W=O-^KSNM.%_%2_=2Z8\N]P#_L+P?J-P:UT]SA.>2O3/QA MZPQSC0.'YDYRO*ZT$EPF">^Z^CGQ)'D\,[IPX:9C8Y$J6@X:MB1QE04CISG( M4U$G8$D;KD[[:M&L/*;B,(_.:E0/\EOIX1R?\*;:?6]-LF..4/5`T6)(^(E3 M`>O"BZL6_P`.&]S"S7AG;,MJ9-N9W*KW-,LIDD+5I4P:0T]E3ZE2.(Y*7LC$ M;1+W&JX8]U'79`@">ZUC0B/A5G4QR$5C5K=[S$P-EEW5JW4=()DY*Z.ZHN/I M`W45O92XN<`594L7 M*5^B:1E7?#GA^:HJ5JVZFVN?K,; M:.&&T84U&W=2;JV'*U_.81D0J]=XK.AC<`9*RK'=1?6JESR8&P'"(6S7'5L5 MI.I:7/<2# MPK(L6%N1/>5+(2V/F?Q!T:ZZ9<&G68=(Q,++R!!_5?1GBCPU9=2M7T:E(:\E MKH&\?"\+\7=&K]'ZF^A581DQ@Y"2_*[XY>S'(+78^$,N$"20#LB,9,-2](=. MX6V]@R9!.-TAZ20<@H\-<<80^GDJ!GD;MVE//!(^4G``CU2$B)/:!RJ'+70" M!B,IMP9@GZH@,.]0CLF@`X(RH!9)SR$0+L@@;93M'NT)R`08*`#.W^JFMI;4 M`/?91[GTCVGLI[8$UFD[R,RE'9=!J.;;MP(WRM[H=$U+QI(_J!/W6'X?IOKL M`I@O($P,PNN\+VX_BV:A#@X#3O[KS^2M8NXZ-3BDQL8@0NOZ#3.(;(7,]-I_ MECV76]`I;'(G9>:UN1OV3(;"T*8B3A4[1L08.(W5L;;_`"L5LUQ5TCYV1=+R M^8&ZI7+R*D;B530<\[*SMJ(O%%X*=$M#L[;KCG-%2H29"T>M7/\`$WD2 M2`=Y]U'2IB=ESSNZ^AXZGJN`$3*@I@QOD(WCT223B5TCR54NH<9V!]UD=8`%)P.=X6U5I'1)[ MK'ZO0<_N1"-8WEQ%_5-.[+'0!,J0NBC(.47BBR=3)'C0?S=UG'J8MZT-S`R"-E;/5VU'-< M3'?W6'>8V78+BDZB1JW/"AJ5#(SC?"FO7F\IRPR&_<*G>-JMI8!!GA734_U- M9OU5&Y]MUJMOBR*>'?!7/VCJK()'/V5FGY[7E^C$R=U>9TSG)>TO6'^8QYT@ M3*X3Q'8-;4\YH#3JE=TYS7L=KXGZ+G/$S6N<"TG=;PRU3&?':_@W=/%G!,@1 M$[[!>O>'#YC))7BGX4/\MI&"TD)VG4YTNQ\RNF5TQ^G=K^']D M*=BR!B`?V74D-C3MA9OA6CY5@P_].WT"U`S4[;`77#B/%Y;NI+2E+I/)W6C2 MIPV4%E0),1]5<;3.T;>ZUVXGLVB<#"T[<"`)RJEM2@29@JVQS6CMP5`6N9$[ M)B_?$H7GU$YRA=F/]PB[1UR""(S.ZK57"3D*6L8=`XE5;ATR`?[*I45PX<%4 MZKY)"EN'&<'95:E4##HSR545[MXW!]QE4+BK(._,*:\=(D'?LLZZ<1SLM1$- M=X$DG'*R[^J"QP`^JL75:6D?NL:[J$DMG'RM=,54N/4^2,;X56\K"G3($`_* MENJ@8'.]BN=ZY?8(F.P":3M3\1=1\LG0U]7>_A;T!FD7503,;_1>@/MJ;6P'8'9< MIX,O!0H-IC@!='6N@ZEK!P5Y9=VV]O1EQ1VA:VY@GZRL7\4.N4NF])J.%0-< M00T'X*H=8Z_3L:[YJB0"=_\`1>1_B=XG?UB[+!4FFTD8/N5]'])CJ7*OF?J? MYY:C!Z_U*O?WKZI>XAQ,_$K-,$`R<=D(<2222<)A!S$1[J M2W$.S('N>5`S#Y))5BEE^JWZJZ:T^N MZ5P`_2[!E1=5J--:FTG!5VO;LJNEV"/U697LWU+\14):-@?E3?/+PZ:%%@T2 M"J-PT.N''LM"V86TG`G8*"G2PXD*6F>Y"J7+BYQ(!QL@Q^L-F2(CW7-R/XL@R M#,1WRNCZXXMI'(F#PL7HEK1JW)J57$F<`;!:Q9R:?2K1SAD>DCLM!UJ&TPP9 M)Y[*?IC=1\M@)^FRV[7IH%*7"7.4JSABVS&V].`V4KDT=.K+2MJM94J#2YX5 M2C:-NJWJ9Z`5!B4Z=6M7`)=H!6E3%%K`QC8`WE;#K.C2ID,:-L*O4LV:,B"4 M5EUF-VD(6-&DJ6[MW-?#23W4894:ZS;H$L,XA M:70P'4!WV*J+6IT0'!5ZM`.)+R8]U?%(D845R`QDQ/NIH8]]9TMW.`5FRL6> M6W2,#"&HWS@2[8<*WTTZJ!#>$4%:U]`[JK5MXD8^%K.!B")Y45:GJ&`)4(Y_ MJ-$-,F)',+S/\;^B-K]/;>TZ8ULG41]5ZQU>EI:0N5\6VS;OH-PQPDACL`9V M6;TWC=5\W/;I,$B9Y0-(@AQCV5[K5NZAU"HPA^'$01PJ+V^H&<>RW.GH,?S` MR/2A&6Y."G!^H>)KYHI,+*,P7P/\U+=08?0^BW74KHMMZ1A^&[4-MVSN0O/Y+RZ M3IO](8?,!P5VWA^A+`2,_LN4Z'3]07==$I!M`$`SN5QR;Q6Z3-.3A*X?Z8'* M*J0%2NGR)#MEG;8*9UW')&Y5CK;W4.GD`XC*J].!=6U$\\H/&%8LM(#MQ&ZO M4M;\>.\I&'9ESZI<3N>ZT;9A(L6=(M'NKU)L& M%6HZME&%9^'62)IQRKI\.T6TSZ=^% MNM9#X4PIE[<@CA;F$CC?)E?K@>J=`EITLW[!<5XDZ)=4*@Q>'+AM"RSVE> M?=%Z0:5\1387$NG&5UEKYC6:#+2,0N,WOEZ/+E,X+Q1>.KL>&C2`"%P%C:ON MO$)>[=KI_5=]U.AIZ'*&GJ+ZK@)G?ZJY3=C&.4QETZCI](4K=L MB/3'Z*]:L+GB,RJE-X>0T;+9Z31)C"[O#GVOV-,-I@$94H:"XP8]U(U@#1,= MT3609X*TY'8W2/\`-$^8C]T1`"` M,H%6JX.)5.L\03'ZHGN#N?DJK<56Z2B(KBH!/;]EGW575($;_HCNJS9)G?"S M;JX`;@\PKH#=5B)$X`6=HW#:=(P86Y&;=*_4:T&>RQKR MY$$@?)"DO+K7))^ZQKZZ9)]7^15W^&-;#U.]`:>T&%RG6+AS@XD%L;$]EI7]RZU-(/(XPLV^]FG2X>N*]:]491Z@[2,3M M/NK'7?%3;7ISW`^K,"?9<97J&C3+G.)>>5S'B;J=2XJ>7K,-VDK>'BWEM/)Y M)ZGZYU^ZO[FJ35(:02_5,YY.4QD&)^@,IG.$@-Q`7M^/%KD[8&0$[RQK M@&O+\"3$0>R8`:9#OLA;^8`$F-U`='&I+B&MTX!*53);I`506+R^:E0_"32AK/I@$:A.RI7-TQH(:`>_*T*EE2&3)/N50O*3=6 MFFT0J.;ZW4N*N=`T9WRKG0K)CZ8].8RFZPUK7BGB5J>%J!=5;3`QW5B5O^&N MF-ILUEGW6U492I,U&('"*U93MK8<+-ZC<&ZJ>32&.2I2(JC?XRX@1H!^ZOT; M>C09@!!8VH8,1`W4M;TC3,E155[0ZH2!@*"Z:`PGG97"W0P[R51NG3,\JHS+ MIH%6#GW3TZ>)`VRB<-50XDJQ18XMC^GV**HWUN#3EI(/96/#?J:6Y[M+0=,[K2%.K5:2_TM[*C?-# M7P%"*XI#RH4?3JGDW9IDX)5ZFV:4++ZAJHW#7C8%%;=1P#9(5=M:GD$B5:Z6 M^E=VW$J.[M:;7X_109?7'-\B8V'*YSJ`\RRJ0W)!X6]U\.;2ADD'NL^C95;J MB:;&N<7!17A'COH-?S'W%*@2"XR0-\KC*E+2Z#+?HOIR\\-U*5!S*M#6'`_F M;*\_\5_AN;FL:MHUM.H6%Q9U-->DYL>45-OI=$']$0;(&?T75_AEX2K^(^LL9I(H`C4Z-PK>$WI9_" MSP1=>).I-.-?PI2.BFTNYE>A].IM%```+C_!EK+VQ M]EW=M3`8T2%PR[=&QX;I@U@/<;+M;4BG2:-H"YGPQ;RX'3]ET55NEH`Q\KCD MZXP=>I+`%3O'@-V$]RIFN&C:52O8\P;JQ?J[T:FXF=_W5#QDX:6,`,DP95RT MK&A;:R`/JL._KNONH`?TM]UG.ZFGJ_3X;RVGZ91@-$'LMJTMB0(V5/IU+T@Q M_9;MDP!@$+GC-O7G=!HTM+L[^RN4:)Z50R#Q[+;A8S7L()0O:3!.`K5>GG=,&R,$I$JN:+8] M2J7K&M.`K]P2,;\3*H/IFH\N!='*;28LZM;ZG!WU1"FW#2-NRO5J$"&E1>3[ M&=U-JJ5*#29`,FPATDJQ0M MM+,C[IZM/&\=D`Z`:9(^JSNJ4QY1['=6*M=P<03$+,ZM?R/JQ&ZTO$PZ?==8\[ISF.9I&HTXTS*\RZK6)J:P3*Z3PG?A MMMH>^2=LKAC]OUOOM9\37&FEY;")VA9W1[5P`>`02K5S2==W9=F`M+ MI]O``(Q*J9743=,M"\@E=%84`T"1@*M8T=#0(6C1EL#=:CRV[$`V8C"*DW.> M43&S!3NAN%I@-0"/\E$Z&A&\Z22>5#5J>F$5#7=P"55JEP:23*.O5@02%3KU MH;$B40-:J(*SKJX&H]D]W7P1*R[NJ2-4[85@&]KC.5F5ZQU=%O[:I8M=2"L^> M7>OCT>*R8[G:;J5"D+8LC`&%PGB&B*1/EM[GMA=C?7M,L<'>I<=XDKZJ;I/! MQV6,.W3UX11:(=MW1^8[L[[I4"XT6D(O7V1M M]F5?3;O=(&%!TMKC3)!$%7.HLTV3S,$JG9.#;2?ZE,?KYU6J4OJ;?9&]I&(2 MLF$-DG)4CPA%.YIEPR85-],`&8A:-9IF(5"\:0[_`%05+MS!1C)*R;V&TRYW M/"T[W3IP?HL7JSI],S/"JL:ZEU:??'*Z_P`(6IHVGG/$2N;Z=:FXOZ=-K,DY M7.+JF@8 M[H(J=,N?JB)1UZNEHILF5*QH92AIU/.V5:L[+0-=;+BJ*%*R#O75R>R+I=%M M/J0`:%I5:0TQ&>,JG1IM'4&NGE23E=MFLS^6LJXI!SR3]%NFGKI;2:1D)2.8Z!3JVKWMJ,(DX5FY- M4U-3*9/T70"U9,EH@)5J=-E.=()^$',5+$W+@:K?HMOHW2Z5O2UAH!4EM::Z MWFG;MPM+'EC2<++4C&ZM0IN&6C[+`O+"FYQ$#/9=)?R\XY[JA<4/23"NDKG* M_014&IH#F\J,="I`8:%U/32R2QYP<*6M:,95D9:5FPCSCQ1X$L.JVCV5+=@> M1AP&0O"O'_@^[\/7SFOIS2=^5R^NZENWY6!XT\,6G6NG5*->DT^GE9LLNXZX M9ZXKY2\*]%N.K]7HVE"G)>[/LOJO\*/!MMT/HU-OE@.W)]USGX0_AU2Z/U&I M7>`XEV">`O7Z5$4F!K1@!:WLRRW=*8(MQ)&!R@LA4O[L!OY0>%!UFMYERVWH MB9Q*ZCPMTSR:#7$03[J;33.\74*=KT&H2-F%?*GB(,K>*JU3TEH<>_=?4OXO MW'\/T&KG=I"^4[C57ZR]Q.SID!D/)!`W[+6O6CRLJMX?IBG;C"N=0,49PN.3K&; M3J0TF8`X51[Q4K_F^B5W5TT#IF?E5+)Q<\F?F5N1<>:T.HU&LM-!,X[JETRB M7.UD`2E>N=4](SQNKW3[?32&%QSO+Z/AQU&A94\<+5M\-`5.PI>D2`M&DP1` MW2+FFI9=LK;*9T=YY4-%F0K=('3S\+>+SY(:0=,%3-9(DIJC2,!/!;DG*TS0 M5:9[B%$:4L_+[S*FUY@_HA.,]ME6;BK/IDB"-R@-`,W$>RLB29V^J187?V*) MZJYH!P@Q*B?9D'MW"O4Z+FOS]T=0"0T*4TSFV8`,G*=EKZQ`C*O!AG`D!.&! MIWV[IK:JM6CIID\K-NM9&,GNM.[>`#E9E<@$P2LY5O#%0JAVJ7=UE=8&V3?6H=+A.1PBZ#1N/XEK1,3]U?9;.JNT`3*Z M;P[TAK*0<6C'LN4F[PZ^3*8SE/TRR8*'J;G=7+2T'F;):2RH!,`*[8N$\+M' M@SR34*1!5AK2$31$>\!1FJ2%#4JQ*J)*M0!BIW%8 M@;&4-Q7);^8?"HU[@Q'=5-E=W!!PJ%W7+@3C"&ZN"2086=<5_>1/":0US7,D MG99EY=','Z(KRO`,&%D7EP)))6I"TKRN2#J.0LFZK9,';WV3W=R).8"S:U23 M&J2[LJQLU]=%K<&"5D=3O@*;BYT$!2=1K:)#IQE<7XJZU3:XM:2#V73#Q^R6 MR`\3]::QI&K(D`RN'ZC>5*]Q)D@\(NIW;KBI+I@]YRJ]2A79:MN'47-I/<6M M?!@GM*]>.,CE:BUEKI`),]UTGAOQ=?=.:&.+BP<$E/W0[R)F M-@@8OQIS'.4^H[Y^Z1T_[RG&?4Z8[JH=IYS]2K-&"\`X]U`(@<_"FMY#R).> MQ5&M1@4FC*/[I6[6^0V>W)1Z&?[*;:?:'60!8NGLLSI;?->`#("T_$.EO37$ M%4?#M-K:0,J8_7@R:#0`(V1.`DGA$\B1G=(@:KYCB2W`*TNIESG\85-E#55#`,NQA6*N>%:?EU MG7+VP-Y(72VVN]J:W`AC=IY63;TVTZE.V$`'\RZ&T=3I,T,B$J)6L+3MLI*_ M_*$X1B"W!4%5Q<=,X4$=,'6FN'0$B[2('T*@KNTMU$[(*U[5#&',$JI38(\Q M\DHG'S:VMQP%Z:@A#26`*CCMJVH#K4$%5;IDF85SI0!L MT-XR,B)4O;416C<>P5RBR20H[2F="L._EL01UG-I#\RCMJ3Z[];YTC:4=&W? MZ:&:ZH7 MOS./9*JS6R-_A2NHZQ,9Y1T$`'L56637I.8Z1A7^D5#<,\MYDH.HTP6$ M90]#FC7$P!*"Q=6]2W,\*"Y,+"\+"K?76MTZ0>5G3 M5='T2S;3M-3ADH[VIY5N]^TA:=*C%O`.0,!9'7V'0VF.5*U%/PS9&ZO?.>WG M"[VWI>70T1$+&\.6C;>FUVDR5T$Q0D;PIIJ/)_\`Q`W?E=&J`.$EI'[+YPZ: M[7U02`2797N?_B7NG-LW-:?Z<_<+PCP\[7U8-``),R.5PO-KT83AZEX<<\8.ZZ"TM"&3DK'\,4R6M>=B>5VMM1::(@-&-UY^Z^A;ZS M2I:4X;C4>,J[2IG$H&L+2,`_"L,!C,$+>G*U-0:T>DA3M#1@'=5Z32!NI6!S M7;Y6XYT;FP9,$(G`8(&$6B6Y.R"H9(`B$9TK5B0XZ1(0@@L@;GA2O;/Y1LH@ M-+P)E&]"#2!WGE'0G,D_(34LMC*EIB0!QPC-@3JX$DIPP1$0C+=3_;A&&0-I M[^R)I$ZE@&=E6N7.#?=7GDAA`5"Y>.5;-$FU"YJ:R1"J5`T?"NUV:C,*M78` MTR1LN==\69U&'`MGCA8E>A#IW6S>&'23\++NWZG:0L5WQXB]X;R;IGD5'X,\JW9,=J!/"KVE/@[ MA:5JS2((DJO/M-3:`U1U0`XRBJU"`H'O<79A/\0-2-.#"JUZL$PY'>5`UORL MNYK@@@`>ZTSLKVN6YF2%G7%<:3!37=8Y=B>\K-N:I79#LE#>7;B<9G8=U3J@EI&O M'&-M+0^)^K MM;2=H=$#$%>>]5NG7%1SG$F3DA2]:OJM9Y`<8[`K+)).1^J]F&,QCC>:=P`X M$_=*<:9D'B=D!'J.)^J>9SO\+89S=($[;E)PDDZJ]6O^H4:-*[J@TZ M,^6QM-K`V=\-`[*@V0Z)(![JB5KCI@R/E34!/)4+7#<3CLI:3FSN?\E4;EJ/ M_3MWV[*2/]PH[8?R&R\C&R.!_P#(FF]?X^Q_$MQ3/3G2HN@U*?D@2$7BVSI' MISGM,$]I5+H%G4\D'S2.@N!$+F^MWU+\C#K)]U-#/N#JJ%Q=Z1[I M^A_S*YJ$'2W8JJYEQ6<*;6D:CL970](LQ3@X.EAV0T MZ[FXJCW!14M0MY.RSKVKYCO+;L-U)=73?RM,RH&-QI&7%`]I0=7J"DP>GDKI M+2W;;VX8`H>B6;:%#(EQY6@X2PJ-2*M>?RCE4Z@(82095YXSN20J5T2!IDF5 M4I4&R-7U073&FD3P5-;M'EQ&WNBK,UL)[)2#Z'#J43E6*](YDJKT0AM9S8V. MRT;AL.]7=2K$=)K0R20$U*FZXJ[4`(W1?CPO\`\2ET"7-Q!;S] M%XYX5<:O56D.'I(R!$;KO_\`Q#]0=7N@`8D',_"X?P#;/-1U:K^7G=>?\UZL M9_&/3[*NT6S079CE=7X/+7U&D@#A<1T>7,:!!^5WW@JU.L%IDKS^UJR.\Z/1 M!I@Z<+G_`!^2!Z=EUO3:99;\97)>/P"3)V"-1P=S4_GO\`2X\[=%T2T+*;=+,<0MZU,^O3E=W14BW?96:5-I;@XW5!CS/$!7K=W8;KHYU/2P M(($*2!EW/""D,"<^RD<<;+;G]1ZCO&$+SF2/U1@-Q*BN,-#P-E&H=VV9RHW4 MMD`8YN)Q[JRP>D2HHU1J"FI.]7J&.R%$/4R M9",-T-R@!C\F`FJO])@E(Q8CK/R2/LJ-:2(W5MQ#B9.ZJ.#G/ALD]E+6\8B< M#"S^HOAI,&0M&Y):V'`A9/4)N+;M@&4H!5BW.NIL<*M1:Z!$96E84QB5UKYN]K=I M1!9)*M1I;*%C6L:-_IRFJF>R@"L\:H)^ZK5ZH:./E-7J`'*S+NX&HYF58E-? MW!+3GA9-:O+LE27-=VHQ^JSKBHW(QE73-I[ROS)$>ZRKVX#?ZL=D=]<-ILR= MN5B=1NY80TA;D8M0]2NI=^;`637K%YTS`15R:CC+C(Y[(6T);!&(A3:(:C`U MNJ<_*J7-0MIR,&)5RY#:5-V#LL3J-UIEI]..=U9RU&5UR](IG5L)S*\]\2]1 M?5K.#2<]N5O>+NJ-\WRZ;I:9!65UWH%RSI+.JM:31(RX<9A>OQX^O;CEERYH MO?J@?^:<=MT>`=L]Y0`QOW[KLAC$>_P`I?E;`S\%(EG90)CHVWVA6*1(=C]5'+RGH M#U#V*HUK:J]M!HEN.Z/SJG_3]D%`@T@8E'([+6XV^TO%;0>F.'^PL_P[_P`L M"9'RM/Q.#_PJH0=@L7PNZ*4#)4P^OG9]1T8P)$0HZK&/&6S*.EJ\J>.%#=7` MITMP(5L3;-ZI19&D$_`*P+FA3IOTM9)]UJ7UT^L_2P$D@+H:5"F*`;IA/^+"H%I9C=$\>G8@' ME0U*;Z1EFW924ZP>!J])45&]AQ`^0LN^<-8CNM>K_P`LDDK'O/\`F'4>58E3 MVAU`"9]U:`#V2852S,F&F%?8V&^RM2*MF!2O(F)*T;SUO;39,K)NWZ;IND[E M=!TBTEK:KSD]U*U!]/M6TJ7N5<#0/=$RF!Z3DI3&_P!U&P/.D&52O:H8UQ)B M%9KOWW"Q>HW&I^C[HBO/F/UN[\J5CS(:P%T]DK:B:D3^5:-M2938`&B?=$4* MEO4=3+G'3[+"Z_;,;2.9]I74=0(93);V7*]5_FG1).5-+M1Z;;AL0T">0MNU MJ,M*!U"'.V4-E0#&C4,!,VF;R_:&Y:-RKIEJ^&K5AJ^:3))77V09Y>.%D='L M138(PMRC1(9B%ETQB&](;;N)/RN=KVIJ%U4#(X6QUA[F4RW,E4@1Y!EN_*G% M6M3IU;18YSA2NLZY2:^^?7<`3Q(6#?AKZ[7'U0?C" M\-RWV]V/4C0\-TJH8'O81)WG=>K?A_1+@WT[KSCHM>E6+*;-Y7K?@&U#*32> M5F,]UV5*D!:ZH'RN%\?,+M1G`"]#T`6A/ML%PGCD-%&J=.S5:L>;63!5ZAQN MO0.@6[6T6X_1<+T"GYG5"\#E>D>';9U>JRBW!)A<,KNOH>''UQ=/TNQ8ZT#M M)(.9"H79T5BSCW76T>D5+?I[@VL-31D+D>I:?XHNR(V&RU9K289>UIJ>'#.Z MNVT-<`-E0H/$B2/;*M4G(`3ADM47F%P``F$[7'(),)M-"B&2,PHS(:9*EIND$_NF>T8Y515 M>9!`RBM7"F"YQ@]RGJ`#)/RH:QEQ`/PLUJ3:/K3FN#0QP)&Y"Q;L@$CLK]T0 MT03!63U!T@Y"YY5WPFF3>>JY`X6KTNCZ!&25G6U#76!/!70V%(``Q$!B1 M]5EW]S#3G*NDM1WEP?\`%,+)OJA+B2BN[B!(.^ZSZU0N<07+4C--<5)#A@E4 M;ZLV)$3RCNZP:T@"7'8K#ZE=#45J1BU#U.X.HRX9X6-6+S4@9A25JCZM6&B? MJK]I9`L#G?7*MUU$_P"L^VMI;J<,3N5*X"FWW&[J7X77%G<4]3F@P7#?,KM>O=$M M[6Q=0\H"!M"'P-2HNZ54M:;(;D1]UZM7BUXO+G[33YDN:;J-5S"`(,*)V@.Y M!Y!V7:_B=X5NNG==N'T*+G43D$3C9<;4IO:8UWY7`=BA!(W&?=+DP9^J42WOW[H"C_`#^%/:'^9,[F5"(` M!$J:V.0=O=4C8M@/)'K(1X_^0_9-;TM5%IU0C\G_`*_U3AO3[7Z\&CIM68(A M0P!Q4U%M2Z<&Z2!W&$NF6)?#GB! M[\K6IT]`A@&.%!#2HBF!3:/JK-.F*7J&8_1/38&C41G]DY(+3$**4SF/HJMS M5;.<$J6H^*>2J%1VIW*`*I+]R@ITW5J@IM^I4E41#0,G[K4Z/9AK-1W/=%6> MFVM.G2$1CW5MVT):0/3`3&[HZVD`Q*Q.IM?1)$F%8E3]-/IW@JS7N#^5@U%9O3ZAF`)_1*N-+)!.>$Z5E=9J0V))_LL$TV/K!V5 ML=6]1(G/L%G/I"F`9(`[HS0W]44Z&EL:CA:OA&RTTP]PDG*PZ+?XSJ+6@$M" M[GH=J&46B%2-/I]!H&0KS&CX06M.&A2O&)V*Q76,OJK`^N&G8*&[L@ZW.@YA M3U&.==XV5C20R#F%F1IQK[8T[QS7C=8/C)F/*;@0NP\0T]%3S&[KCO$+@0YS MG9A=^(_P"43F,96!6BJP%@D$?,K7\7GS*[H,">.5CVY(G= M>2UZI&WX-M3_`!3!$97N/@FWBBTD+R/P%0\V[86[2O;_``G0TVS!"2G^M.Z) M;;P5Y]^)#PSI]1Q/'"]!ZE#6%OLO+OQ?N!2L7:3GLF5:QFZYWP8QKW^:<">5 MW?0JKZ->G4IG+3*X7P++[1KSSW7>=+#6,'JSW"XU].36.G=W'7VOL"`P,JN& M2=ER-U4UO<00?=*K(E15'2PD'=*2`JU),`J$ M@G,IC(='[HZ/JQ$+%NW233,Z@3J*R;U[B[3_`(EN=1$'+868]@=<#G*PZ[U# M]*MOZHF%LVM+`&TJ.RI`4Q$+1MZ0%.0NLFGSL[NBI-T#'^B&K4&DGMV*)[]+ M2.ZS+^XT-,.581]1K[P0L>\KZOZI*>^N'/<<_JJGYR25IBJUP79$JO5>13)F M25;KPUI61U&L&M):8A61+57J=Q!.5AW0=7?#3NI[ISJCHSGE6.GVLN!(@\%: MZ9[5^GV#F&3![K4HT3I@&(4KJ6P@8]E(P!@+B`?=9:TK730UF7K!ZY/#5W7G\F7L\L\>6_\U\",+E/!\V_ M4JE'(ER[_P`?4--8D`0N&L6AG7`T")*]&4X>?MI=1Z+9W=9[;FG3>U^\B5S7 M7?PNZ#U"7TJ(I./_`,<#^R[2\?Y-9A?B0I[2K2B<'NL^NXN.5G3R"]_!>W-3 M^7>5&CW`/]E4J?@T&"6WI/R/]%[57N*+CAID=E`74W3_`$G=3T_UT_=R>.4/ MPFHTGDU*IC/O: MXRKX"Z-5;B@-7PLGK'X;6A8]U`"8P"O2J5,3D94SJ;'B"$F$/>O!^I>!ZUO. MJ@Y\(W6DFE4#CM$_ZKONJ=/K650TZE,5*8YY"ITJ+`X.HO)_Z7*S%? MW*\^NO#G4Z+=3[O2=%2FX+UP.8ZF&5&9_Q`S"AN.G=,KRVX:\] ML$)JK^]^7DNSMM]@3LIZ.H.DC1[95&IX9I-.IKYC`3IJ> M25D4`31:>_\`U(])[?\`\RZ*U\.,_AVS,J3_`,N4^[D=/:/K6^:W^&>R=PO/ MVN\KK3Z9.-2]"K@&F1R5YWXCFCXA='*N/&;Q=XUT/GGRPUF\836%)KKW^8=O MW5?I`<:#71+W;+6%J*3!4_J.ZW>W.=-"FP.,```*5S6LDG)4=D[^3)^Z:H35 MJXV"BFJ/)_+LA,P,Q\*8LXC`[*"X=VQA18K7;H!]4A5V_EU&9.%([UO/8$M(,]D%2J`<1*R^MTP:;OW6R]H MZBL*^AMQDA4>K/#:&D8QPM#JE,MEVK*QJSG7-<,,C,>R1FM+P99E[Q4(.3RN M[L*(`!"Q/"]IY=HR`%T=K3TP05:UBM4F@#9#6`\MW"E8"1)05VQ3<3V6'1ET M\W!/NIW/8X`(K!@=4),;H[JUQJ'V4I&-U^D/X9SNP7FOB*N6U*K2=EZ=U8'R M'@Q,=UY'^(#C3O*@:=UP\_3KXN].`\1UVU.H&F9WX4E&G0ITVET"?U4%_3F^ MU5G`1QW5RQMO,+.>9]E\[*[>WIW'X=T:3M#M`B5Z[T$-;;M;,%>8?A];-8YA MU8X"]3Z7`MQC/==<.&:;J[P6.',+Q+\=[TT:08#NX8GW7L_6'^EQ'9?.O_B+ MOGLZA3IXC4#/U6';P3>4=#X`J"ITNFX?HNYZ;4EK9`"\W_"R\+^E4A@X7 MJ'A:C3N'?S-@)*YV;NH^A>)RF?&F<`JM4_Y@.!*T^M6].DT/ID@.X*QZKX=$ MS/NK.*QW-Q:HN&_"M:Q&%1MX(F2IVB#Q"Z,)A5(."(]T-=X<))^@43@1L9"C MU.U;*+(.K^3='T]PDSO[JO6<"S!0VCB' MD1W5NDX1M/96,W:Y;D@"5+4>-$!5Z3O2/V3U'?TCE;VP(.D=Y451X:=X33#9 MSV4-2"9!6;6I#O?DG<*[TNDPTY<[?.5#T:R=>W0IC\NY*UNM6++"T#V&8QNL MR6\KE9.'.^)G-94U,)(B%F68-2IJE%UBX=6J%L[&,*ST6V)`EIRLSFL^7+6. MFA8LP,X^5=:0`FITFLI@`9]U%XMU%7+VKDC.ZJ"GYE9;ZC-Y1V%MK()$_*V*%NUC,#Z)K*B`P#WRF8C?9=L)NN.=X4 MNG6>FIJ(6@]C0PCE*SI/J58+H"O/MVLI0!PO1BXUYSX_I>H_"\_1<-K[@$+K>8X_6CXBIN?4I%DY@2M+I/36?PP4G6E<6-$"?+'U5>[L*4$%FGW"U_*+Z\`>GVRI; MJU_D$QF%K45PU\U]"OY>[95JVHM?0@@94G6+=SKO26R)S*&F*M!H:YDM[A7I M$-6S-,ZL?11MI@&(RM*F!5IX<,]U6O;&HT%[:GZJ+M2KL83#CMPHGT*;S@82 MJ6MA5UT&E_QB%Z!4IO9)()A M05-3@<%!YY0Z=7HO]=J]I';*GI=.96U$5'!Q]LKMWTVU6!I;OP4%3I;20ZF" M"-T''4>@-!Q&_(W4KNENI^GRI`Y74_P=1M3(EH.R3J0#3CZ(,&VLZ`H-&DCV ME2?PE#L?NMIMO3+9+,^Q3_PU+_`?NIMMZY4,@8_5<%XWIZ.N-<=L2%UM1]RS MTN$B,$+B_'-U5;U2DXC3ZAGMNKQ[1RF]5=Z#62[`F/=)S_,?OONE6BF(F2[A1H+6^8\4:>>\+6Z? M1;28`&Y'95>EVX#0?ZB=UI-`:R.0%")`X-&Z0.=]T#3("-F7`1]4:'3W5EC2 M6B3A!3IC$JGP">,K18&N9&9*HVK"6<25(*CJ9VE9 MI%3KMN!2@65Q-,2Z2[W7?&QBINID. MIOD<+YN_\2E$"_%3,2/[KZ1N`'4C.<+P#_Q,VSO(+QGU#'W4R[CO^GO\F?\` M@RZ;)D/QVE>O=#N3;N#VNC$+P[\&[HL:RGL/^R];Z;<>D!T_=<[Q:^E9MTEY M>/N1JZ3EC6EVA6!DK M.L=P>/U5LDP%T8L2!PF2>-E'6>-/LH7EQ,;?"=C9(UZ,Y4[;7I$[NRE MRUCJ,^O.Z:QH/N*\D3)W72=/H-I40,*/HMDVE1#W#*LUR=6(`3"L^I5,J2DR M,F?D)4:$L!/Z(JKQ386SLG:!NG13X"R+UY<2-6.,JQ?7!((D;+,N:DB)W]UN M33*O5ESR.ZM]/M3Z<2HJ#/4#"VNGL'DCNIE5D!3MRT`#,!,^6$P,JU5<`P25 M1N3(,&.RS%JCU)Q+MU5I4#4N&RTFYIM:S;V7H-"A%D,0LCI%E1H51`C*WZSPRV$8PO3C.7FO2.WI:&^D94D M.+<]E7HUB3Z1(1U+@Z8(A=)IFN9\=40^@[&87F7B6V!H.&C9>J>*:M-UL^1Z MEP'7J0?:OFU?/O-&DD2MQENP4M.B0=UOCX* M!J4KB[FJ`"=E:JVI%/+99"S[^DZC6EHD+0Z3U!C6-HUY@XRE@HU;-NN6%U,E M0W5K=END5);\KJOX>TKT=3"V2JQL:8.G4/B5!R[NG78;K:9CA!0JOI5--PR/ M==DZSBG`V5&\Z8'`@,!:>%(U6,/(J-P0<*&I1I%D`R#PKM?H;2=36EHY$H'] M%!8-+W-([*HQ;FTAY+3[A%:$D:2#(4W4K*[M07AY>WW""Q>RHV8AP&0B'>!/ MJ9'NJMU39O/Z+5#6N"SNH,TUH:@&C;!U(.C='_"#L%=M:8-NP[84GE!1T=G5 M,%<1^)=,Z6U`V8(S]UVCW>@YA6-X.ZDZF^G3<[#@ MMN]J-KO)X&RXOHD^:]S2!IF(^BZ"TKO-#)RN^7+G)ILV#=#-3#*M-N'.(8_' MRJ?2ZAT@`_=7Z=%KQ),%9O"K#0P-U2-DUNPU;CS#EC3"H7KZC*C:+#(G.5I] M&EONK5]4'_`"FS+C&$5&AY=#(]2@YSJE,-NM0&!LM'H9#@#V5? MKU,Z]1[[H_#]08`SE;G3-[;>F:4.,R(6=48:5Z"#`GE:E/+0X[1W5+JC)<'1 MGNLUIKV;IHM(.1&0J/4ZI=?-;4;J]^58Z6X&V$G80LV_JM?UIK9D#=-:MYY3<@&2IV54MZ1<=9,EQB5U7AV@&4FD\CA<]1+16:P8B-ETO2:G MH:`-A"TD:](P(E6[8:HD*A2U/<,;*W2K-IMAQ`^JRZ1>D!H"K7IFB04S;NB3 MFHU*L]KF2""#[I8NXBLY#\D3@2O+^HRN MM._@QF]U@6M"I4NVN8--/N976].M&^2(&&@'4J[:5(D#$MV"U>D.;YK&U7^D MVVWX<>ZCH.D@=UVW1;HN:TQD^ZY6VITQ3:X'!V70]&T!K8,X6I: M.B8XN89[+R'_`,2EH7]"K/#3B#^Z];MJA\L'8+S[\=[7^)\-W0&?23/T*WE> M-M^+C./#?PHN-/4&4W.V);O\+V?ICR:3#@[8"\%\"5C:^(RTNP'G'U7M_0[A MKK>F=0R%C.:R?5EX=+;52*<']%#6J%U6&[3E5_XE@I_F`.ZCH5BZH07;]BI. M$L;%K(:"T$_*NMAP'ZJA9$E@!*U^EVS'M+GDXX[KHY7A!;T"^I,'*.[I.8!( MCLMRUL0(((@Y^$U_T[6PD)IGWFW-D$OP=U(Q@!X^%+>T#2K:3N$`@OSN%&JM M6YTN!CV5BJ[T@1&%2`TM!:<'=3:G&C$B2M2L6!?I$GG]UTLD MG`NM5!2HEHW*;PQ;^;4;5<#,[K,O+G^.O6TF8$]UU70J'D6K3$8W6I-W3S^; M/UQTTWD,MPP=EE]0O&4:3BXY'5M-)QD!<9XLZ@?4&N_WE=G@1=9ZJ:]< MTVG?;*&PH_U/YRLCHS:E>^#GB1,KI&4PRD!$'NL[VMD@:C@QF/NLV\JAP(GE M6>H513ENQ]BL>]K@N.DQ*Z2.=J"XK9/)F,%1T6>94!,Y.$J=)U1\1N5J].LF MAHG$%+=$@;:T`#7#*T&4PVF2!PC93]$!"_5&D+';>E.]?K):!!04+1`5VK-MK`U*C6-;DE=9T;IE2E:^EF8W6SX4\.M?IK5& M9&'"V[6[[0O M/NJL>R@]CL`=UZKXAI3:N)W`.%Y_UNU\RC5])F"NL<,F;X*I,9YCBX%HG^R? MQ=JUW_4J%"D:_5'-W:#,;KECQ;3ZO>%K$-8'O'J//*U MKNEI8=,XX4_3K<,H-],8E27,-89:/9:QBUS_`%!@<"1(C=/2M65[4.:WU`J> MJR7N!R"I+-GE&!!!6F8KT65K?U-V&X**\K^8T5&.+7#A:/EM<9PJG4+1NG6W M#AE(JWT^J]]JQQ,PBKO<L0UU([[JS6)TR#$%9BU%6N:E/S&C^I475!5O`'2)X6K:WUS M4I-:7!W!;"L46VCW@7%#23RLW_"?Z&A3:*31JX1Z&_XBM"CTN@:32RLX-(QE M%_PJG_\`,Y9Y::A_(3SRN0_$"H?*+)R=PNPJO#:)<<>RX#QK<"I734>3C4Y;G37%^ESI(VE0W'<+8Z-<#0)=[+K.6'5=-)V[;+ M4IU&M:23@#*Q^FU6BFT@F85NM4):Q@_J.5*JU:-;5KNJ1O@`K2IVNBF'L.2% M6L&C4UK0(`X6D'`0LJ@I5*K'^MN.ZO4:@J1ZOI*#0'M@Q]4!MW,'H)G=1II4 M```=_E2^PPL^VN-(#*@,\K0H.8YHC,H"8R#G/U4]/#8RHFCU[X*E9@RBG,"% M%=5@QIB22E=56L$\H+&@:K_.J_0=E*"Z=;D@UJDEQ.)5IS<;*1H`;M[).8"F MFF%UVEZ"9Q\*ET,AMQ$E;'6J0=0=!("P^G'1=:"0KBSDZ>AMLHNH@&B9.45! M[12;D!4^IUS4864Q)VE+`NGW#W$4VFTY/=GNN'-JU& MG/\`B73M;9V%`.J/:(WDPL2XZQ;=+Z<8(U,$+S#Q_P",>H7E1]"VJN8V<1*S ME9.VL>>(]0ZYXWZ1TYKP:["1PTA>?^(_Q5#ZSFV@)'!7G)H]0O:DU/,>3DDR M59L^@7-3/E.'T7.^61TF'Y=/2_$+JKJX=.Y[KUG\/NI5^J6%*K6.7"87@W_# M:MM5;YK2!*]I_"FH&](I0=FQ^RZX9^T8RFK'H#&M;2WX6;UHL;;N).P4C[T, M9I<%E=9KMKV[@'QC98M='EWXD=4)NGT6NVW*\PZ[U5[:[J=&-1W<XX(P%X/-=W3V_I\>F\>)>YQ+BY>ZY:X M;-./=<9TBX#JHU8@X`Q'Z+I["\8`-1DCL%X\YIZ;B[SI]R'4VDNR8$+KO#Y9 MY()[+S7I-RX!CXQ[E=MX>NBZDT%PRDNT^.OI/:*\+70?94R"XD M`+RKQ_1-CXKJ%HTC5(79_A[?-N+.F)F-\K.Z@=4@97.NLG"1E6&$DR=D+ZY#8#B M/E0"K+S$QM*DHTC4=),#NEIZ_DF/J"G3=+_JL7I51W4^ MK-`)T3_=+>$O\>:ZCPA;&I7\Y^Q/^2[#S0R@&@[+-Z70IVMH-,`ANZK=0OO+ MGU+IC-3E\WR9^]V;Q%U5M*FYH\`*ZRE+HCZJW;6_J`T_*"O:V<1V72^&>F>9 M5#W``*#IMGYE1K0,=EV71K-M"DT@?HNOCP]JSGEJ+5NQMM:@8!`63UBO4J4W M!H^5LUJ>OTX`*I=1IT:5N[5!@+WS\1YX7$W0:+FJUP,96_CCDXJ_N'6=2HUN)G`*M>&:?F-\\@R3.57 MZ_08Z_,@Y*Z+P?:4VVX:1*F?%3&?5AE9K:<.!P%!7J-,\8D3B5JW-*E!!8/A M9UU;4RTC;L0K"L]P!=B-U/1I8"@J4*U)X+3J:%8M:S7$-?A#0].E\#`'9'5` M=0@C=&\LQD05%)'!S-;-Y5#IU M>Y?3+6C9+J4DW&N\-R7X:JM:M2:XR=DS:5>H`'NW3U+*B!ZQJGA38I7-:B>! M"EL_+J"!D)7EC0\LPU4J5LZF^:57(&`@NNM?(JBHT0KU+16H9RY4@;A]`-D$ MC*&UN*E"IZV$`*&FM1;IIALG"*/^IR"E>TC3!T%%_&4O\!472[U2MIH$`K@^ MN4Q6O7@977]5JM;0).ZY&Y9I>^N2<[+KIRC&N+:F:+B2"6\!0=*<&W$.P.$] MPZN*C]/Y">55:]S;C!@<*XI796-;32&95_IKWU;@N)PU<[T^M-O'*VNAU)8& M\GDV3V")PD^R"C?-+Z3MX7,OTT^H$KK+K\D@;^ZY7K M32V]R2)/9)Q4O32M"^Y(IM..5IMM:;*>1J57H3`R@T@[K5()8>ZMF^R*%DT, MN=(Q[*WUNCYG321N%6J?RKH%:<"O9EL[A9O,:G;`Z.36_EDX:CZ[K(`!F,K4L<;F5R_;WVU/XL3IWA"RI``4FS\+5H>'K9@@4FX] MEL6WE@"",JVQK2TA:](UNO)_Q(L*5N_T-`@RM7\*+F:;:,F1PG_%>AZ-0V!6 M3^&-P*75=&I3QW5,IN/4KJV+Z7R%R_B?S[2@_1+AMNNXM&"K;-/$;K'\16#: MM-P(A7+A9'SKX\>^O?U'N$9WW7#=;#R2X&1PO3_Q=L76E>H^F/LO+ZC_`#[@ MAP).T+Y^#*ZKPQXVK47,96+H&#J)3+#[B]>%^/?.C515J-#CCY704JX MIT8IN.<$+RGPSXE9-[=[TS3_#AY:-,2 M5F=>O&"F]K#`*O65]19TUI;49$>J2N-Z_?\`G7E3089.%TO$>;#'VR-6NQD< MJM5N!)@JK4J9&=U&YY7.UZIC%VE5.J,Y5NC0/A M,=UG*1KWG5&`_F((&`L#K76&,8Y]2J,=RN7\5^*[>W+A1,N7+VESU'Q!?AFA MY8XYB``EXYJ=3<=)4ZG6ZI?BA2RR8*[WP;T@6U-M5P`<%Z5G;TZM1 MGJC)*W[RY%NT-;B"LXS?->'S>7VXC4JUPRE!VC92P$SRIA/M,N>(LW=X#+&9/R@LNG5[BL'%KH/RM+H?1'O<*E5H]Y746=G1I M,#6M'9:N=O$)AID=.Z2&4Q(`*T*%DUIVA7]#)`#83N89VA8_ZJLVFUH['LB% M(N$$85AE(`3^Z.DT:M/Z*[-(K>C#@%=H4X(`&2DUK6>H[K4\.VKKBY#W#!6L M>6YJZ*BSTP1@)NG6X92#1B%.88W,+W^/'4>;*[0W!#:1("YKK M]9SY:"M?JURUK"-6.RYVX<:UQ`[KK>(Y]KG2:1;;22K-%DGOE*VIAEL`,'E6 M;>F/S'/LIC.$O9]):T#90U`(.5<+?;"@N&@C"Z1BLKJ4&@X?NN*ZG2!OGEHE M=O?L_E.7(WC)OG'&ZU\<[VXCQ/1(N-78R5I^%:X#6Y/O*C\749:\Q$W77;0YD@JE4;(BQW5.],TRFD8M]0I-IN@3A M<_3J5*%_#?R$PNDK,+CN85'J-F`TO&%=<:B3B\I:(:]D@&3RIB*9;#L$")4? M2ZK74])&6A2UF@L@%2%*M3T&?U5.YH^OS&.WX`5[4`\M)P5!4!:#!,3A5 M%>@\TZH!_+SG9:;6-JT0[3)'*H56,+)#H/*N=+J!A%-QP5FK%BE2:*8V1^4W MV5RG2E@(B"B\D^RFFW/WU4O86Q)=C=<]U5YIWE.@X8G(6[6:ZG2-3VINNJ_F/)T\`J^ZV8]H;`$;)6S0P`- M$0IVD3)32J['.MSI=,*];N#X(*AJ5&UKP^6,DK4Z9:MM;85'1J=[*MT.V:]_G5`M-C?XBN*;/ MRA9O";#;6[[FM);CX6@;1C*4"![*]96S*3!C,)ZS6DSCZ+$YY:CF^I6T5):W MY52E+70X8707ENTM)61=T@QVQE6+I);@1A6QKTX*HV[A$?NK5!QVU*K'*?B9 M:5JG3G."\X\,=0?8^(*;7XET$KV3Q6UE3IKVG.%XCU.@]G67O$@,=(*Y?UR; M[CZ-\*UVW/3J9!F0I^I6VIAYE>7_`(=^,6V[&V]P^"T1)PO3;#J=M>V^L/!) M"ZW'?,9QRUQ7D?XRV+74G0T3*\9O^EOI52ZFV9.Q7TCX_P"DF]+BT%P.87GS MO";J]8@LQV7B\F.WHPR]7EUC8UZ3R\M!+OT1=0#C#=,M9[KNNL=!%E4TO!PN M>ZS;LI"8E>;R8<;=\<[:PQ7K4Z;:5$?)[+5Z+6=3:"1]5F&[IMJEI_-V0UKT M0-#H:,0O/=[>C3LNGWTU`7GT^Q706U1E:@W3$_JO/>FW!(:[4872=-OFTPV7 M3([[*XUF\.@MB16RYMM# M@""IZ\[CKCY+B^;>D=4ONB]1\BJ'-T&#)*].\'^+;>X:T.<&NCD[JKXY\(4; MNXJ.8S29D0%Y]U/I?5.C5O,87.8W&"KN9?Y7MP\TO;W@=8\VCZ:L@C@J(W8> M8)V]UXOTCQG=6Q%.J\^G'JV"V:?CINQ(^04N-=98])JWD'!D;)S>4VLU%T%> M5U_'#G$N8=("H5O%5_>$TJ3WDGM_V4F%[I);2V#O5+EPW6?$M[U% MQHVVHR<`(^C>&.K]6JZZ[BUIS!7HO@OP1:V9:^I3U.[DG_-+G)QBXY>3&=N* M\%^#.H=6KMK7FKR]].R]:\+^&+/IM$!M,!S1(6Q:6]I9T6A@#<+.ZCU`TZA# M7XE9U]R>7/RW/B-2YN:5&EI]H@+G^IW,DOF8V6?U7J3R]I!CZJM'W@!]2G!]UL^#N@T+>@V6#;>%U=&UIM:`UHA;QW8S;)TPK6P> MUH;IP%:IV&Q=,?9:YI@&0(]D+FM;D\K@68H4FXX7+^!>EMMZ+:K_SDK>G'LUS7+R7.,A1=.+7W.H$#Y3U[-Q$$+ M0Z59-I4]3FB8WA3OAG_4]-[7O+,$MY5R@W$X*KLI@NW*M-U#&WNNC)WC&57J MC)GA3N?@M*KW)`&#NM1*S^H-!I.7(W-/_P!T[A.ZGZN,[*KX4N6W'2Z<'40%HPX[[+#:LYF2T-D$6L]T-`M\H8E%+?\*SIMS'7#_"T-'(W6/08]@-0Y:_*VO&8_D'&9X5 M2WJ,=TP-J-`0W:>5E==93=1@CV6SU*DQO\P+%\05)MAB M3*L*HVE336;JTXSA=5XEW^:XUSBQ[3'W70^'ZY#)X/*WWRQU7963]39!V M6ST]P(&#ED6NUL)]PBIR)S&/=9OB6GKM25?H5P6AKL%!U5GF6C@"I2,#PP\A^ M@KJ;=PB97(]#>&WSV.X.RW[F[93H:0X2>RVST;KO4`QOEL,E8_3Z56]N(&W= M2/8ZXJ:1))Y6Q94*=C:`F-<96=G8+RX;:4VVU*#4/9;'1*7E6X?4&2-UC],Z MD$&5#>-:*>8(2QIS ME;^6X[@!1MO=!C)6C<6KJ[SL`HSTQ@&1*SLTSKBJ^Y)9_2N'\9=&+*AJTF3. M\+T6I:-9^4`:55N;!E5A%2,K&<]N6L;IXU7IU*;I`(A;OACQ3==.J-94>2S9 M=/USPO2JM)I0#[+C.L]&KVE8RWTM*QCG<6[C,GI?0_%%EU!@8]PF,_[A;0MK M.K3-1L`N"\-M*M>UK:V/+7!=#TGQ?=4G>75>=/O_`-ET_CFQSCTWO'?1YHOK M"!"\?\4U88^F<0=Y7J/5?$'_`!"R\L.ES@N&ZIT2K<5'.#=1)PO+YO'KAW\7 MDWV\SJ>8ZLY\&3M"EL];ZKJ;@`!GY787GA_RO4YD+&N.G,I7&L-+?A>:XZCU M3RRHQ7=;T#/S`1]-ZG4J7`;J,3D2J'4V.<[,CY0V3/*)>[Z<+EZZCK-6.^Z7 M?@4@TG/`*UZ%1M2D)$D[A>?],OG/N@ULS\G9==0J.IV8<(EWNM2\.=XK>MPQ MFI[2/H5:97]49C]UA=*K.JB,GYY5FI=EC\DAW8JR+[).IU`XF0-^5E=7Z1;7 ME(L

      K*DZK=--,&23\IK*O4J.$\>ZQE_KI*X[K7X=LN-3Z(ATJI2_#6NZD& MAH:!S.Z];MS3%LQK@)(S*FHUJ+1,1"LEZVZ3S61X_5\"%CJ=)]-H:S^H#+OE M=3X=\&6=HYM3R?4-B0NHJ%E2Z!#>58K564P,PI9;Q:7RVK]E:6MK:M+:8!(Y M2-^QC/Y9,MX"R;SJ;74M,RJHNV`Q*MLG#G.>URYZH75C)52[NP\ZM655O&:S M+"&%KB09Q*X^_QK2QZY;PM2\QH;J))7:]`L:E(X^JU).TMXT[ M'IU2F&`#$J[YX#=\++Z?:52P1)6I9](NZ[L@@+K+\7QB^3&.2J&XJ.@`Y3#IM>J9=NNG%I2!.EH M1,H-G\L+>/Z>WMB^;\,&UZ&TP7"?E:-CT2D'`BGMW"V+>BR>,*[;AH'$+T8_ MI\8Y9>6U4M+(TV@-=MV4E>W>6@-*O4V:L@Q[*5C`)E=ICKIC;#=T^JXYRAJ4 M*MN=0^RZ-K`L_J3FG`[K4P9M8E2\J.=!D`=@H.J>)[?I]-IJU`/;_82\075. MSMG/YX7E'B^[N^HO>:;"6@K>YC/;)PSMZQ>DV?X@]+J5=)KM!^O^2Z#I_7K> M]IAU&H#.<%?*U6G5J7]2F7O8YIG!73^#_$'4>D5!3J/+V#:5G]W&W34E?1_\ M4',`)RHZE8/V,KB/"OBVVZBQM-]0!T97327MUT7RU=$V.]>=!@A8%)Y_CG$@ M22KO4KE[00X$++L:[7WD#E6])]-X@;-$X^B\U\5TCYSH:!!X7J75!KHG'T7G MWBVVRXYP5J.=['^'%V^7473@?W78R8_U7G/@ZLZWZL!,:L'*]!+R:8(^\[K' M^-"J.`;D*E3Z9E`69G909O5:6J@[.%1Z82&.89QMA:O48-$\@<+&L"16>$ MO42?5DAAV'R(2:P`AP">F`XR1A3M8W3@04%&YSB)16M,@SIW4KZX2;HC;]4O3V_59TZ.:94'4>JN8XC0P_=#U6A2I5 M2`0.T*#I3?(NJM7,%1]7>XN:^"5?Z0X:0 MP5/:>ZU.F:ZBP?',+H>DW$4XQ\KDK*J`X;SW*W>G58@DXC9(O;KK!P(&=QDJ M6O4#M-!I!)W6=T^J74QZN%/T]X?=NJ$&!LI81OV+6M8!V5ZG^2(*SK)PF!`5 MZF^!NHTM429W*EIB8[*O3<)B=U9;@"%%3,_+`W4C6X'WE14\22I9$1.%%/F, M#/RBI@_U94-2LVGDN`4)OJ0$`?571M8KVQ)U,*@KUBRF14G'*=G46:=L+/ZK M>'Z MM&KJI20F[]2R?%&_Z?0N*9.D+E^K=!8ZF][6R`K2UNTJK=='<:&?EV_=KA[*W=1J@D!L8GNM6ZZEHH"'Y'=:0L*=1I&`%G]5Z:#3 M);&3O"YY>*QN>297E?Z)>NI40YQ;!&X4M[=MK/:01)]]ED68G5`:'2`!O/*EI5W5'[ MRUJQMN.CI7CG$MG"G_B(GU3.ZR*%9E%AAPF%4N;\@N>"M^Q&ZRZ:VO@SW17E M?73UMW/'9<]KFY]N5IV5`LM]>K$8PL[I]J\7D%OY<# M*ZNTL:E2ES'*LPW>&SI!KFQ`V^Z]7B\4MU7G\GDNN&_TKIE*E3:"`5JQ1H4Y;I4%K3?H#LA M!>M<&Y)7OP\>./3S7*U6N[MU1Y:W;N%4KU''!XW*FH#+DSK6K5K:H,+>^&5< M9,P$3`XG`6C0ZRD&71I.DR,*W3MW2M-EDT$G=2MMVZIE/98=[<>6"3]ET'5"&4\[E'NGVE>V=J:-495^WZ9K::CQ^9+H-D:5Y4` M/I/"X>3^4W6B(U'98G7/#%U9G#-0!WA>ULLAIU-/Z*M=] M/;5:6/:#VE9GBUS&Y>-/":1K6=>6RQS5VW@OQFZ@YEO>.G&Y4GC7PN&36H,@ M@SA&5Q,IM[O5JVO5;'72(DB5R5RVM9=3)$Q*X_PCXMK M=,K>35J33(@2=EWEA=VW5XJ4RUQB=_\`?9=]RS;EK5%_Q!M1FEXAWV7.^)F- MJ,>=,@G=;/7K(T3YC20?98EW7<:#V.`U'&1LNDD:=(2V=X[*3;`5>W=``" MLTFF>9]U%-1:#5]0(A3/`!E$QL"2<]D-P0,_HHJ6F\!@&4_F#W4+3Z1ZH]D\ M_P#6HUIS=9WH;3(`,969U!AT$QE2U;DFLZH1`.TJM&:S:;M$P1A:QM MR_LSE).G;68``;$1PIZT"F52LGZF`RI[JJ'-#1E,NEVJ5ZFFF23NI.E4C4=J M>=ME'6HZR)..RO\`3@&P,=LK&*XS2]28(P43FM.WZI,&(!!^JD:.\?=:;C(Z MYTYMQ2/^+VP%SEN]UGK=7*+VD#..,KF;-[J=4TW...Y6Q;O)8,KKVQTTP6G8P.RCJL;"@IBI M.ZE.N((6:T@K4L3&ZJNIGX6B[20,;J)P;R/NH*@!&!A#6IMJ@#WW5I],3,?9 M`8;N84[X7I49;,INGGNF%!M5T$81N>:C](`5R@QK&#W4OX(R.H=/%1D:<+'J M]/'FZ=/LNV\K53P%5J6+7/U1NL7%K;F[;HM$PXLRL[Q'X=HUJ9T4Q)W@+O:- MG@`C"CO;!I&V%+A%V\AN/"M1C"6@_*Y?K?3*M'4T3\+W>ZLV"@06CZKS/QI0 M\JY=%,@$]EBX-3)Y6\/9=%A#AGE-6B"8,CLNDONF-J.+].%B7ED6/+73'[KQ MY^/5>G'/;+\XNJN9I!NJX8]I=`7++#3I,]E5N7; M!Q'*IEI=K*LML*;FESV$D]UC4CK+(R+8/#C4)]+LD+6 MIUM&F3Z>Z"[I4J,`"&G@*(.B-50_=%5ZGXAB07Q]4_<\9Z9O M?CXBL6X\YN?^I1N\46`.:K?NOGQW4NM:CYCJP^Z.C==1?E]6H([DJ_NX)<,W MT+1\1V53`K-^ZF/6+73+7A?/G3KCJ8KRVN\`^ZWJ?5+UC`U]5T_*OOA4UE'I M?6^L4G-TM=)'995"B;EQ>X$MW7/^'?/O*\U2=*ZI[A0H!C#^JS\=+)Z]J MM^\,864QD*'HC-%4ZOS%6J-L:KBYQGE2T:(:\F!]%G/IG2_;N](DRCTMDF<% M0T8$P1*DDQD[+>/,%>_MJ55A8YLA>??B#X,IW=-U6U;I?/'T7HU0@C;*JW+` MX$`*Y8S)9=/FCK?3[VQK&C589:2%K?A_XDK=*Z@UM;46$1GC_U@U[X7E?6NBU>GW+@]A`G=<;[8UOC)[(:E#JW3&U*3VY$X*Y#Q#:/HM=$ MS.ZS_P`.^O/L[@6E.GDW52YU7,`@[K9 M\*LJ7%N=#H@+.ZY1ASLPX'NM/\.G#SC3C!5RK.+7:+BD0"?96*3*[@,`+5K6 M8.0`HV6[V]L;++2@:-P)!<$#FW#,3*U7"&B?U4%9NF85&;6J7.VP"S[JM7!] M;/LM>Y,.R`53N2UXG2$1E?QF/5QW6=4J-=?AS7)Y5VAIF)^BPPRNS.DJS:7548&J%CVZ!Z@G\QO<*-.$NZL^GA.`/+!=E1U634P M/=2L:U]/2.%V<5:H095:J=0CG967`ZCC95ZS03'ZJ:5E]8I`T-``E9UFP`.: M0/HM/JC9JM9V"IT*(:\P))25FHJC21#A\:5=L:CJ+@W8#@R%7SKCCOPK)IEU M-N-7N$&O85IK-DF"NJL:HAN9PN#L*FF[%,SA=/T^O#1"U.5=/:UB7!H^RV;* MI#1U`5`8DY69=9+>G4VEPQM)LJ;D1 M&RZ;I5(LI9Y4SO.G.Z&V)#O4%/5&H?Z*!PAPSE633JT:)$-RI9RJU MLZ6@&`58:"8[(T51H&905F![()!"F+0/IYM,&%B7UO^+9MM6M5KP,R2.%;`$9Y7 M,](O@3I(R-UMT*NH`@[KK6)5M]-I]E&ZU!R$;*DP.49]+20<]EC4K6V=>L-, M8*I-IU:C@'#'>5HO'FU2(Y[JRVB`T#3"S_D5FV])E-V55<2",8[J1E8!%70UH;`E#4:"S*A%6=M_E.'M(R54>A52]S(Q*Y[ MQ7X?NJ)+@TP3_OA>P4NFN;5-7RR0[L%'U3I-.[9#J6X[+A<-NLRT\";1J4_3 M4$_*MV?3&5R-`A>B^(_"5%E$O:T`_94/#/0CYI#FX"Q<-\6+[:YC`M_"DT-? MUCLLWJ/3S0FF*9G9>K'IS@T4VTXGE"/#--_JJ,U'E+XI.B>2WMXG<]'N*KQ_ M*<0I+3H=Q(:6GY*]J'A^V;Z13_15+SPZ)!8T1\+$\N#Z'T>+F2["[CI%G2I:-3H5YGA M&L!+"1[PM&T\)W3J0ESI[PM8R8LY6UK]!JVK&CU`'NNCHWM!K``]H7`7W0NI M6DF@]Q473K;KSW'4XPO1/)XYVY^N5=U>=2MV/!+Q]%/2ZM;>0#J'W7#5>C]6 MKN#B]TCLIZ'0^JD:75'1]4_=PV>F3H[_`*]0H_\`Z1L?*CL+\WSBX&0L^S\+ MU:@FKJ=\K>Z7T06S!N,*_N;XD/37U%>V%KP_R5O_RY9-;'D-(^ M`N@JQ*@KNU+8ITQ]%U^E]3$84M*U! M;D9*7Q8_#VKSEWA]U(D"F`HO_+UQ5J`Y#5Z3<65-PF(*SKLMH'3`,]E/V[\I M[,KI=HVQH``>H"%;HT'U'R9,J6A2=6=J<,%:-O1#0!"WC-349O/**WHPS+8^ MJCN&:8M+78S*1,$J:MFB#'U55\SE2<5DG.TG MY4535F"C)$@'"9\&8*TJK5;JF2N;\3]"HW])PT#5W74%A)V0OI:FQI4N,IMX M7UGI]QTN])@B#Z2N\\!=7;U'I9MJSO6T`>I:7CGH-.\LR]K1K&<+S[P]UITN."%E>`JOE=4+"XYXE>@>)[6GU/H M@JL&0)QRO-^AAUKXA@-YA>B_U<9V]4I`.IC!P.2HW,R5-8Q4MV$DB0AK@#(4 MBJ58.S.P4%9TM^5;J@0>52KG!C"(I7,D20J+R-6ZO5RTX"I730(`PJB"X.#( MGLLDLF[U`+3N7.GV673,7I(VY4O5)VO,$S_DIZ+!AT#"CI`;S$\J>FT3@J*" MM1IO&VRK5;5IR'*XX$-R@=!,CA-$0LM:VD14PB_A:_\`\BMTP-`S">!_B4:< M"XESH."$=$D3!D!0L)#2,SA155K)'<'LK-`ZTZ)S1_$-)D&-PM.QK.:((.>5E7NJE4:YAF3$'A7K" MIKPX3"16]87!'($KH^DUM%,$'=<10JNI51.&KH^B7(>T'5*TS766+Y=RM:W< MV,RL#IM4$3NM:V>20I2-$U64J9$QD&0-^Q4K#QV35#`C)*:5-:N.L$G`V5ZFXG/&^4WL#ZC01)W6,HTH^&+-U"V;K! MD[KI:+=#`J=NP`-``$*9]R&D-E8QYK&.EID'!450#5A/1JM)B4540<+I72#M MR!LK=%V),+/8=.T^ZN4G`MXPBQ+(WWE(Q_LI-!/;'9"T0XP5&C.`TD2J74*- M.I1+7KTRH;?_`)REHVC'0(5ZI:U'$P"FM[=]-TP5C*S>ED9]QTX%X@<\*:GT<.8"6+:L;37 M4!<%K4K6F&Z>RSCA]:ZO5:S&LW.%F79J57P-N%9C(EY0/8VK4RR>ZFZ-0&LOXE,%7*-N7G4XRK+*``@<)645O1TMPV% M*&CD*4@S`,)0"?=601D'Y_LJ74Z0@&/HKYDG!5?J(]`D[*I4=`:K<3!`4;J0 MG"L=/&JC.T(W"#')6/\`4RXK.J4(<9"C-/@K1J"./E15&3D05L4_+$8RDQD- MSCY5O2(E1$;@B$%2XHM7_`(E]'_AKD75`$23,!>JOP")E8?B^PIWE MBX.:"8*93<3>G,>![L7O2G4'.ES1&3[+D.J6O\)XK((_,XG]5T/@B@^TZI5I M%IB?@)O&]H1U>C7:`?5O]5,,MXV,Y363H>GD?P=.,X&R>XVSA*P'_HVF!D!# M=P`,K>*5!5V/"HW!,D#]%/6J0)!E4Z[B3+9515JCU($[*O0;YUPZI,M;@+-6'>P>3@Y5.L``2<=CW5\!KG$=N5!>4Y9EON$JL: MH)J`D$CLI:!)<``(]U)=42*0=F?904FD.F00#L"D2Q)>M:6M#@"?CE'8.#'X M&?V454&I4B<`FZ'"00-B4B5IL+:A!&/E7>GW+J#X;^4\K,L7S5+85YD: MM)C/Z*SAJNQZ+=M?3$>PRMNG<::)*XGICWTP"PB)6W:7IJU&42XQO"U6>G4= M%8\36=C4M:VENY]UFV-1OE-`.!'[+1HN$3,+&FEEASE&X225%1@Y)4\0T&8! M4L:!)!`"M6U0$Q]%3<,GD_*DMW:'=Y1&K2)B8A-#7OGLH#6TT9$!3V)+J>HQ ME8S+4VK13+BJCGMJF0=BH?$UT;:UP=U1Z1BX0J=%X=$JS2QSNHTM!P`ALGW2/ M>4+-.F!^J*0!D*:4%:&LU$!5*`-6L29@84EZ_P!):'&45C3AD]TJIF@2#V5# MK-DVYI$`96BXP0(3.`TZ5,IM8X*X8^SN""#$JW:70\T21D2MOKG3V7#"=.RY M.X94M;C27>F=UG&WJLY3['96E753W5JG4).-AV6!TBZ#FB7+9MWR)E;TDJVP MR(/=3,@`S/95J!>7^IP(XQLK$<\J-G:)R#(1`RL`->T84;J(>9[%76A M(QP>-]E7ZA#:>#**K2=3RT_=4:M74_0\\[J\!J-)U0YF%890:!NI:+0U@&_P MIV!H"FOR*OD-[;G=%Y`U1"LD-F>4_I@^AW]M2;#IV04:[:=.&!0-K/N0"\DCWX4U*FP-RDQDZ2VWL%4U M:SX)PCIV^@[`^ZL,\L#>041@[%:TR&FP!IGA.-_A.X`#TF4$#5NJ'?G(*$3` M)^$SS,]PA>2UL]T!8&(S\JMU""S&2IM7V4-_`IRWZJQFGZ8TZ3&RDK$:@=H4 M/2\-,E2UXF9P5SLX3()<")(]D#R`(*7[#*$YAP*W.E"8TX0.&9`GY4D3_4AB M'^RHKUJV%IU8O:/S'^Z#Q/0\SR MGZ3W*UNI4`ZX#SIE4>OF&,`B9W4DUMF\E;PR@P#``4%TZ2OEWM[JM4#OS<=E8N M#J]H*A<($`_=!3KM9I)DY53IP'GO),$<*Y/4*CJ;]1)<.%KVM21,P>RR[?3IT.SV4],NHF=Y^Z[ M;_+RK=P]U)AP>PRKG3F>7:^K!(G*RJU7^)N:=,8:W)6L'?R2W[*::@0"PD\( M+C\H$3/Z*5Y/E1$DJ/3.\[(TJ5VX`@[S*I!A95(G[K3J`D9:%0>#Y\:02HE5 MVG_U;A)!`P%-J(.9]E#/_JR,R,;84]P"TSIGXE$Y%0<:;I#MSW6@Q[?S+*HE MSJ@!&^P"M^8-@[E58W.GUG>6%J4CJ;PN;Z/<9`G*W;>HV`9)6S%8JLVV"-\+-QVNW#V-1U"KI=N/==!TZZ!8)/LJ/6[!K'ZX&%5L;G2^. MWNM2[<[-.JMZDG#L*SK$]UCV=QJY6C1>#C=-+*OL)+)E24CE5Z1ALD>TJ>B9 M(SMP%&ED$$`;]T1_+,[**8*)YTL10T0'5YD8'=6JL0%7LBV22`K+&!SO]5EH M]"G!E7K6F&[0@HTP8PK--H:`"Y%AW`!D2H;HPS^RF>0!G?Y5.\)TQ/NJE0V@ M'F&45P8YP5%9_F),[JQ<4R1A94%"M!A7:3FGZY654)IU([*S0J>D=RM)%NZT MZ3`&VZQ<&N8V6A7J$TX<#"S[9NJL7`*6+M:+](QPA_B-)DIBSDG'9`:8]\\J M@_XKD9A+^+&5`:?J@=^`F92`=D%-HE_BS)]TOXP\%0BG\I"D,%-B2I7J$1)4 M%057XU8*L-:`$X89SB%-BF+5Q,.$1"VYP9' M$)V5Z9`(*9]$'X41M@#@P$1/K;O,T^DJ-[JH]U=C0])&-P MJ]_4;Y<$A5Q=N!R%6NZFL:M7W59J[TUQ`#E4[PC M3."%8JN),-5&[<6X,JLUD=3J,;5!,?*Q.MW-.I48UI$K9ZM0UTBXN`PN.>QP MORTN)SS\J\:VQ]:A<^Z"@P,I M!@R.4C0TN)&^ZSTWI>)!@`(7,(4/3K@ZB'JW4#33F55BM6U:2T`'4%GO]%:) M(/Z+3=3BFXSNLRZ#6.)=R,+/TO2L[2ZL7N`[*\UC74X':!E5K>D#1U%NYY4U M":9`F0>2D$%9HHUO?=*K^77,%3WC&N]>F>ZJD#3I,P$5;L7M-,ZCQN%L]`J^ M6S5.YY7-V=3^2X@''"U.EUY8`>/=;B1WG2K@$`D_9;UE5#V@E<3T:YD!DKI+ M&X``$E+$Z;HIAX)8`3Q+E:M0^F1G!5+I]68]2U*+@6C"SIK:W;W`C2XY/NGJ MP&EP)4'D2-0W[H;ASVLT[GY6+56>F4O,?J$[K9HP*<3LJ'1O10]6>5/5KR=` MY7/&;J3M!U-IKO#&G"Q.K4-!CLNGMK>*9>[<[++ZQ;R#CZJUUTQNG5?+JPZ5 MT=A5+V@`Q]5R[V^74Y"U>E7&(E=)S&.JZBW,B)'RIW?EG*S[-X+9F58K50QD M24TW*@OJ@;LPXRN5ZA3\FXD8GW7:5@'",K`Z[:C27#CE2\%Y4^FW):()6S:5@ M1)*Y6D]S'D=BM;I]>0`=EISZ=-;U1Y6\PK5JZ3LLFRJ2)F%HT*D[%--RKP=* M&L\!D3/U0-*&X+@WY45-8"3NM.U8/^ZSNEL<6:EJVH)B2/A9;6:;0.,A%(.$ M&2-PD>$43^TRJMZ`*9(W5C;)A5;QTMP/F54J.Q:1)5MP!'!56RGZ*RXB5%5+ MFE+I4+':7Z0"5)`TX(4-%T5HW,H+1[$2% M%IWR84CL9P4SLA`,`@;X]T@V$1_+,(3G?ZJFS?LD`",<).$#!320`-,('#<3 MRD02(U$)]1_[II_W*!RK=1ANG/U1**RHFG0])*"X:\OU! M6*+HM1/*$9V"QC$O:G-5IR20=E+2KN&3LI7@")3M:TB0/NNF@S;@!LN3>=JF M(2JT6N,!/2HM`01OJ!K3/[JE5.H%Q..ZM7H#1M"P.M]290IEK'"0K)MFW2KX MEZ@RA;%K2"3[KF>G!U6H:A$F9RH^KW%:M5-1P,#Y1V5RT,R(E7.SB1B;[JV_ M>0%`]I.VZD=7IEH@P9V4?FLF0X#W01OUJ`0@A=CZJ M-X`._P!U-4B%"1G,HBMD0+I\DY/=6KD0L,X4)@G(W5BL0!$[Y5=\[?W1",QJ#20.?91U#J=+?DIR^&1_=1%X@D['& MZ*E#V`;):V=D#64M(EZ6BE_C4TKP6RJ.=4`)F.ZU^D5/-O9T@Z%SMN[RP3R- MY6OX5KZ*+]0`W.Y4_XI$BJP1/NL[J@T-/I$*U4:^D^!.RI]4J M"H!3$R=PE4]L!_#"#S*CR:F&F%.&^4P-X]E6I:C7<9(XD8E/J?%QH#J<1,*G MWT%X!C'!16-=S>25#F6U)3'AG*.D MZ;=`L!R/E60\UZX:#(Y7-VUWHI[Y6_T&IJ9YA,F5K),:VK9C6TX&X4A@;**@ MZ1DB%)CV'PLNAJ3C)P5-3,NS*!C94C(X&%%%H8[?"B=0G,*8D:1.Z)H&TQ*: M7:J*;QWA-JJ,<1RK;?S1PGD:A@JI M4]CLK#`W5E5[/##*DU2[!A9X5)5$[*K69ZIX4SW$Y`RA<)YA+5T@J,=C2J-= MYI5IM4\2-PB5T=U;$E2@F#E- MQD)-&.4Q'ND4YC>4+R3]$VXG8)I;R@/)$(9XA,#`W3:H!$H@I/('LJO4/R8, M'MW5C5[_`'5;J1:&AP.?96)36GY.WPI`9)4-HX&EN4^J#`<2D*)YEVZ1SNY, M82;AI&Z!W;;H,3_FDXD&>4+B1F50G$`PA<(.^2FY]3D-0R\"9A$.YPVC/RJ- M\[4\-/W5RI@;Y6?=NU5P-XY3XB]3;_(!S]TB0#A$`?X<0O"H=%&2?99++>KUR_Q- M2,'J]*FQS6!H3LLZ;J8AH4_5VS6;D8*F9I%,-VE+VDZ9=Q9-.Q`52M9N:8:Y MRVJNDC;E0N;JR)51CNIUVJ8]NJK$%N^%"20W.RH-K5O*!T\*$WE0$M+AN M:VBWM/)7-5W/\QI+0X.,#NMZPK^50:<^K@B/U7HG+AG/PZ?I]1KR`=S^BU6' M`$GZ+!Z56@@XSB5LVQ+@-UFLXK31G)4OJT@`?51")D'Z*3U&F`0LNAVL:\$N M"RKFBX7\TYPHE]$U';.*E(I/K-=Z9D]U&_\Y!,8[J2[H$5 M"]@)X@*J'Q(,[[#9)R7<7;9N"=D;YP*FM*L M-`)6H1W71+@%C3JRNGZ;7D`2=UP'1K@!@`HUVB@!,:N5T/1V_\`I@[NN&<^&]KE=@(/[(NG.#'@2F+01NHZ/HJ@ MY2<.K?H'4T$.2O6C3)W473ZC74\%3U1(D\)6XPNJ4SJE8E^P]ETO4J3BQ8=[ M1,%PS"R5B7#GC=Q71^':Y\@#5^JYWJ3"!($?*T.AU7LIB7+RAIOYVA22`#.RC21CI.1A&3] ME#(#2`G:??;A%2B)WD]T61B?LHVD;''PI0#&%E35`",F0LZ_C6&@Y6C4.EA) M,?"S6M-6ZB9'=+T`KT/Y.DB5S/6;?2^=.W9=A480-IGE8O6:1F8GW4TETRRIQ)W*L=)N0QX!=.5JB[5=MC:4:CJNG@>0(_16@Z/8?*K63HH`@B5.YT`3"SIT@Y!'"37" M(.Z`NGLGRTA#L0L[K#0)D<=TB5E MT029&ZLV]32?4HNG`Y!;';*>NW2[5)3%*T:;Q(`.Z-L]BL^UK1)<-E=H5`6^ MGE4V+3B?T0.W_P`U(_NHW$``P44B0-DP,`2GYG2?E"3/QV*B"+LQCZ*MU*#3 M[J5[VXB(4%[4!I20K"HK7\ASGV1"=?S[J*T=Z20$;3)F$A4T3W*8$SC]4['" M"!PFD?EC!50GNRHW'NB>8X4;CE`_]/NHR9=(Y12W3N<(,.$Q]4`UC)A4:\&X M:-2NDR8./94;@`W(R$O2?6I3]5'_`#59PTO+295B@[TCD?917C3(.RG255KG M.ZK//T4UP02JM4B,GZ+:!JNE0U?R[?52$MX*BJ?EW05G_G3.YX1N#2<2FTG3 MC[(C$ZP'>U]%IC"CN:3)P M(!3=+<'6HD9/NBJ%PQJ6<>BH/(I%OY<_94[NU8X%TP>5>&3O!5>Z$-R95TLJ MJRU>&@-=A/\`PU3NK--P#``#]T^O_I6--OFRL\%U,F"),9D+2MJWE46%L25D M6]1QN00=4;85ZFXEY$@C<$8XRO5'/*.IZ74U071@\%=%T^JT;F5R/2JCF@`. M#F]PNAZ;6AVDY@;I7GG%;K2"V8B=BI'.@1S\JO0<2R(V"L`$MC$!8KK*K=2> M!1#`8<]7+-I9;@"1`*HN:;B_V]-,85^K+*9C'UW655(EQU?94.I4"'!S)$[K M3:T:9,2H7L\QVV9P+J30ZF0YQ(CE8K612).-6(6W6+:EE+I$X*K.MVFF(:#"EFE[9MNXZH8V M#/)4VKR\.,_W356.%:`2)454D3[;95EVS9IL=)NF@`.)$''NNIZ1=%S!GEF78:WTSGB5M([SIU<5+RE3F<_"[OIL-MPWO[KS+P54-QU$' MM&?NO2*#]+!G"XYMW0_!P%J6U:&Y,(W!7K)D1A9%]2(:0MUYUTYD!9G4&3(V^%FM.8 MZC3_`)>)^JALJA#8$"%I=3H^@[8652@2!NK&*W^F7&T\\%;%G5U-B8"Y2WJN MIQN?%-+LUV^!'*"S9$N)RH*8\^MC(5Y MF`!P%+VLZ"ZZRV-#:@>.%T MW6;?4TB..%SMRPM?&PY4C-;_`$DEU$96G8!PO!/"Y[H%0T;J.D:0?'^92U0-RL]MWJ$DS\)VW8` M[&47:]4?Z2>2%EUG?SQ.41`!.<>Z!T"2,2@!Y'!*@O'>C'&V5,\S_HH+LS2.\K42H+)X(R= MU8:9)AQSQPJ=G!,?NK3(F`T!2%2?TX3SP<2HW.(P-D[7%Q"H*3![*.I,P70B M).GM"#5ZT"QHW2,`1.R=WY=Q@(()!,R50!B2`250N#INACGNK9_,8(RJ-VXF MZ"EZ3ZTZ>6M]MD;B'TR"H6$!@/9%4=Z-RFMI5*X&DQ./=5JK3J@*W7`)SQNJ MS\XP4B17((.(0UL#<%2EHE1U&@B()(5$%)L@DC)3$>HC92AIG/V2+1N$1B]; M!!F$5*'4`8"DZXTFB2,$#=5.F/#K>!CV5O;,$]PU>W)4=1@=&DX2=.J"(^$X M<-,;=E16KC)!W"AJ/AT$*6X/K.256J&0W0'$B=ENO-DZFTJ-=3W M`]E9?5TT"XC99O3Z@-,,]E)6>^K=T[<'$R5BS2XU>Z73_D:_5+E9KSH`.=\I M42&-T[`;IJH:XQE9TZ2A8!`!(PHJI]4@XYA2M`CDJ!Q]LS6T2J-)@KWT$0&^ZFNFFE M6D"8@)WVR*]IM\T;*G=LTLD.$*[2+'TSJ'JW454!U,YSP5-::WMF$`D:'1W* MT^GW/I`:<=E1=1)!(`QRH:%7RFNP1GG8K4K%>G_A:\N]7_5]MUZ'YS=($PO+ M?PGN`ZDYWO/[KNZEP=8.K[K&N7/&\MNA5EX$A7J#@6[_`%7/V-?.#]5KV521 M\K5CO*N#TO!!*TK3348%F_F9A6.G57!\3CW6-?&]KSWN83,PH*SFU,X'=7G- M#V9"I7=+2/3LLUME=69%-T9`"Q6LF1L#WX6QU1Y%-P&)"SZ--II&3]D1")D@ M&85JPK>7'=0-I%K^W*3AI=^:0KM'16MQJ8,G=7Z%3T8."N8L:Y;`U+9MJXC! MW5TLK8I/)&\_*F:Z,`X6?0J3_4K-*H8_-NHU*M:]B0J]W6]6DY!..5D=3I&/RY42L2U(I58SA;W07BO4!^JBNAKIQRD2HVU=>=_JF/'<(;1GELR[/[J0C,I.C2*K@J,>H'( M"DJ\XCV40D"`(E4"X0XC.?JFJMC:8'=%$$H71O\`NB,_J@_E.!Y"Q^FU-+W- M@A;G4&!U,SD$+G+8Z.H$#<[JWIF=K]1LFI?R^JM>"`!RKE M9XJ-#@GVK>9"<8.3,(:KMYW"![W:#&Z!SG%GJ;!0@J8]`X3Q[H&5'!H&H?9/ MYKO\0^RSPV^3`XT[@M(TP>2M*RJ!SA)&>52\04G6_57B``?=*TJG!C=:\>6X MZ>3%U73GAQ$'!,;K>L7:G""<87*=%KM:R9]/.F75GU=,294 MCVN!![I.D#4ID-TM%)SHB1"TK<@M@`PFZC3#K5Y9G'"6Z339_"2MIH.&6GG]5VG\9J>?5/NN` M_#:6TCP5T3KDMNBV2IC=UQDYKJ>G7/JP2NCZ?5!I#,%<%TR['FAL[KK.DW&H M`+I8Z8UTMLZ`#RI@[34!&%1M:LQE7(!9@K%=8V;)X?2&4=5@.ZS>FU8=!,1P M5HNJ!M,PX+-;E87B"C+]`&5GFDZDP!TB.RV:X%>[WD!1WULTL,#(V)6-ZJZ8 MU,MU[D^Y0W+9.,#N%+6I.IU%'4>-B M0))B.RJ.AMJH+!ZE>H50(Y7.V=?.ZTZ%Q#-6T(LJ]>5Q.D&"5/T]NFG/DX!D8@>R3AJ.5`#HHSL/=2+5B01\I.`)[J)K]N$X=ZXVE4#4I`@Z5&T%IV*L.<2-T M'YO=30=M=^,?*=U8Z<@X2`;&=RC+&'$A!`;G,1]E&^X&))RI32;MA15*(&QE M7E%&YJQ<"(A6VO#VZEG=29IJ!P,0C8][:8$E/J-!CNY4S"2/[=UF,N8)#E:H MUPX`2J+<^D]U#5._!"<5!W4=4C:440=.Z?4)WV46J!$I!W(1!/P(&RS+V/-! M.RT:A=!!/T6=U&=8DCX3XE7J('E`C8>Z"H01&?NAMG?R(0ETG*3I:>(9)2ID M%N9"$N@1O\IFD:>RJ(ZL%Q`.$+\#L$3_`,RB>X]P40HR$U68PG),25$Z9,_< M((+PM\LCV7,51IZD-]UTET'%IPN:ZK++L.`"U>F?K5>6Z!C?NJ=4EM0S'PIJ M;II!P,JM>._F$@_5(@*I,F1NJQ$O#I/NCJ.)`,('.,1C*(:H6G$?JJ\Z26G8 MJ6KC(,_"KU<9B?JFBH*NVD?=0L=O.$=PXD_YJN7DNW,!5&5UP@W#3SW5FG/E M`\_NJ?6S-P#LK5)W\EI:F7]B='.7GE)SF^7`@>RCJ.=(+HREK+A)<-ONI5A` MXPV0GD_X/T4)J'M/NF\P_P"%&]/G7\3K1U#J!JMD-/8KG+*J69:8E>A_B=:" MI0D4B33;F=I7FK*FG^61)#MYX7+"ZNG:?RQE;G3*HD#_`&%T?3JQ+6F3(W7) M=+K%WHG![E;_`$JX`IZ&G;D+UX7CU75>J-$X;P%VG3G.=I+CMC=< M'X8=_P"L<^",KL^F58TD#;NK7GO%;1(:R2!E%2;B8^O"AI.+QJ+I4K"XG$Y7 M-T!4R3RFEL<[(JC2#O\`W4%0%KMPR#J)T6QAVZ>D\Z(U3*@O' M:[BFP3[I4/2`;;-$"2)4-;43'?@*^YLB(&.RK5&Q6$;J=*B$M#02E=-'EF#& M%-4_F;B"%7N\-W)0X-94&NIG.4UV"V@\$3(X*DZ406D@G>([*W<40^V?,S&R MF71`^!7:V]R0X=@NC\/7\.:"9GZ+TSE7IW3J^K25L4'@LCNN3Z M%HSGNGN&G5Q$*6E&@:3'U2J@&9(E8UIT95U1#G$EJS+RA!."MVJP'_54+ MQD-/)[*#`?-.MA&UVK\RN5K=KP21!Y5&LQS3`.`DK.DU-^G'NK#[K13^5G:_ M?9-=51Y>J<^RW*E;O0JFHR5N,J;`F?A?HHUM<8[U8^%)J(,JNUX.?V4DPV>Z*)[OJH*Y!, M']$;G=B@<&D&5!GWP]!PI?"P)J?7DJ'J`ACC)]D?AA^DR2D1T8P,Y'=$"-U& MRH"S@2A+@2(2,Y5J3MNM.-T8=`_P!55UX&8]DFOG))459+S.\A)K^2?HH" M9P$.HPBK?F-R!CNLKJCLF"85DU#!S/LLSJ50_E,2JB2V>`TD291.?F05!;._ MD[_5$TB?41)YV4@LM>0-YY1!\G=5R[:<)P[.=D$X?G=&Q\#C*KL>"-]D[70X M9^J;%EQ.-DB\]U"7\&8*<.X!_54$YY&9E`7DNVW35'@;G*&0[Z>ZNT5.HG.4 MJ8'D[8_9-U+4!N82M_\`D`[J7L+RVDX*3:3ID(FN]4(VNAW"I`/UM[X4=2X< MP9G/Z*V#+5%5I-.[=]U!"VY$_P":E;<-)`U0H*M$<*$TG;#A!H>:TM]+I5/J M)`$G"B:VJUQSA0=2-9M/97:5I6;@:`)=$(7D`K/Z;<5?X>'`X[(GW1+O4DUH MJ\'`P#PA<3&VRJLNFZO[(S781C'=42U'2V3NHAOLA\YFG\WU0"H)W.564Q/I MRHJFQC"=U1L;J-[@6SJPFC:O=.,?(6!UO)+APMN]ZK=7ES/92=/(;:-!S\E3+N+.DI+6M)(CA"8%,$'"5:" MTXSPAI@^5I!!]U%@FM!;)A/H'M]T+#Z1A%/LHV\H\54&UZ%9KID-/RO)NK4' M4.I50T`M!R9VRO;.I4P]CG')CO@+R?Q[9NI]0>Z<3,1[KGGQE*O@O<9EKEH+ M0=.X^%K=-K@4M((_NL*SJ/IO:`>^3PM2E6UR74Q(WC"[X7ZN4VZGPW6+*+[41OV3&2 M3P2HT3JFEA."!N`HNG@5*KJAG?"#J-0TK8GDX1],+V6K=49[IVB_JVF=E%4$ MG494EN\$?/":K),D;84L:B*W'J)WE07;7:S&/W5JF"!,;SRJU1NMSL\[H([3 M4PYG)VA7GUO_`$;@,8YG"JZ((+)!XRGZA4A]!O= M=-I!6\HDKJC6#:;CJW]U'9U7&[!`QW6>^Y\RJVDVB@)B>5RRNN(ZX_ MEMVM<&G'>%8W&3/NLBUJZ7`$C_):-!VHCU+-CI*-S1I,`RJ=VQI?[_"O'/[* M"HT203)E8L:9=>FT$B"J=>CC;'*U+ML$@`RJU>GB2D1D7%N#)!R?T5&^8YM* M"86U780TJA?M#J#I'"L2JG3*^FGI!S^ZV>G78TCDA8%M3(!(5BA6-+>!E:E9 MKK+6X!;DQ[*];U/2,Q*YCI]WL)_5:MI<:B#@_5+&I6W3J>H02A)!Q@%#4)V. M%-"O>F1OLJ_3?^;GNBOG>@J'I9]9DR9PK?B1L:A&_P`)M>(*B:XS&(A,>ZBI MVU1.Z(O:3,CO"JD'![^Z3@[28)E-B!R MK\%RS>?+!"F+O>51M"?*!,A2TZDNW*S%6'.)_IPEN,&"HW.!.1A#J'?'""P- M38!VB9]U%YF-T@Z>51%U%WH*&S(\D9^B;J+OY1SCY4=B[^5OE*BP9!D)&0Z5&YW M\R9^B?428)Y06&ET`IY)89CWRHV.QI"9S^$#5=\)##8[H7G.#$IJ;G28*"5D M$[1]5%?-#J9D3[2B:XYXY0ULTR"58*E@QLN`:BK46.>3&>RCM'1=.!.)4]0R M3"D*K/H>J02F=0<6G=3EQ`"?4-)Y*ND4'4G`8)4(-459E7RX:C.Q*!S0'3CZ MHBD]]4&0#!*%U:H&Q!5\M#C^7Y05J8#<-'U5FRZ8M]J1."5:K63 MB-33"F]6GP5Q7'ED[PJ8JM+Y!W2K6]700"9A9U5M:F2-/RJC4+\&#]%!Y7H M5=S2")XW7,^*K8U*%0;R)SPKY)N.'COKE'E#7Z*A:!B<%:%FX/8T["9A4^JT MO(O:E,-(@]]U-TYWY6C`:5,+N/7G&S1=H\J3G<05U7ARZ#FX<&D#OGA3'<>@]-JM--H[[S\+3M*Y+1,&-ES72;IKVL`)' MN5MT*LZ<[*6:>>72^QS#4DD[X4FN2!G[JJTOB8GLI2X8<=Q@SA8TZ2JW5'M? M=MI-<7'!(4U`C0&;`=E7HM-;J#JN(;C]58JC0>T_HH)Z=8!P`/U[JVW2ZGD; M+,HN+KAH)P5ITX-(-[CE6K#`'1@G_)02!4(GE6)`:0)PJE4^H_=1=I6YR3[R MHNJ`&R?C=34W32$MVPJ?6ZFBV&PEP^J6);PZ3PSZ.D4LQ#0._P!$_6&R0^9` MX1=!+?\`@E$[2T)^I#52U8@'"XSMQPO+.K-8^CI`XA<_URSECM.^\+HJ;'`C MW5>_HM-%T@+=_+O'+=(N'6U=K3JP$P@YG*J M6U<.&2(4X>"!$9]UG3ZV;FFPZ@=BLKJ[!Y+@WZ*Q*SK.F8U`XF)1U*32XQ"EZ?2/D`]T=>G#HXX M1&<7NI'\V!Q"N6=X6ELF9S!45:D2Z`,E5JE/2[&",K4J5U%E=A])L'?]%HV] M66@3NN+M+MU&J`[8+>Z;?->&RX!+$E=`Q_$J859:/?A9M*J'`:73/96:3IS. MRPZ'OS-%P!E'TADT_A17#AY)4W29%(P0I!?;@'V1-=GE1S]$3=.)("JI(,S^ MZ%QW#M]DY+8WW4;S`B1\H*G47``CE1],`UDCOPBZ@X!AR%%TQ_/<\*4:E-V) MTHO4<[*)CL"%(TXGNFC9.<`(Q/=$'PTB5&Z9G&$%28[JB34P-=.>ZR>JN:*G M;*OU'::1XX63U1\U0`('LH59MG#R0)&`DYWJWP5#1GRP,YY3N,.P3ME)2IQ4 M'!^B37^M0%VT)B\@S@#A!:+AW3L><*&0<1N$F'2X23'RE%L.YF>Z;4`2?[JN M7^DQ*(/P)@H)=?`!^4=-X+<[RH`Y)K@,JANHP*9SB%'8QY>#LFZ@_P#DD$'Y M4=@[^6=]E:D67:B9G,951#6$_P!U488=,QRJ]1WJQVV5T;8M\VNRA4D$X5'I=W M4;4<#N"8"Z&[:W09SC*Q:%)C>H/$R'$J7>EG:9MV7&2WY4E&Z:1$[)A;L\QQ M@9PDVT&J1O\`NFUFEFFYI9.I%+?\85=ML^-T_P##/[K&W3U>?U'.IO`/T69> MO\T/#I@<+7T4WTGM>\-%S=*J]M00/RY/NN&/%T]TN\8WK<^EIG&N&3I>FO+;A@:YP&"(."NBMKJ:@; M+=+<8*YKHU:=,P=(6E;^:UX>X`P9PMWAYLG46Q,B?YD.:5=M7%D#@H;'6UBF2=O90522PQO M&P5BK4EL$*O6)R-(AR+L]&12C:5E^(7PRF`[=X!$;Y6C.BC)9!]C,K)O#YE7 M46R`X1/=3+IFV.SZ"7'H]$$02!CA7*P+J7T5;H'_`.44]P(5UP::6VZY3MSP MJC5;#Q,#B945>D#D;=E<(!(&)35F12F1]%T=]N8Z[9BI,M&W*Y:Z;4MK@B3H M:X;KT"ZH!U-Q+J(`!D[X4XJ$C$A9-K M7Q\8PKC*DLP9D+C765?H7!:[2=CRK]O5&D#NL4/`$SE6K.YAP`4:VU]6(E-N M#PH*+PX27$`YW4@>"["S6Y456F223A9G5VC0["U:SN%E]0<'5`SDE016E/3; M"!&.Z&H-1DC?=6PS32;.T0HG!HRT*K?48C?ZJ* MM3EL@_,J[33*?3.K41"*G6=3J`M5FK3(SLJSQ`WRK*EC9Z;?D:6..8W6S;7; M2!#IGW7&4ZFDB)'LK]M=EH&8@)4G#I;JN#2)D#ZJWTJN/(&09]UR]3J`>UK- M6^`MSI&;=N8E2\++MK^=VVV1LK>KC*H.IU/1NAZ62*8,[E5NIU"6&"$73'N=2:`=^4J M1K4ZO!/ZHV5H'YC$JD")"/6",G=1I*7[ANZ1=&5`7PW!A(O^I518;5)[(M6TE56N``(")M01ML@N!P M@0=TXRV9"K!\B$8?!03AS28!D]D;2.3]%5UQSGV1!Y@[_P":H74'?RS$=H45 MBXMIP"`=U'?/EARFLG2W$B$OPBTY\&-^4P=F4$CG?N@U0X[0B++7$'Y2>[B5 M`U_<)R\[$G"HDJ.(;,IZ;QIB,PH''N8RE3)F)*@G#R3'',*<&6B`53U28W^B ME;4<&C*HK=5!:=4;*6SJAUL#]"HNI^JD>?[*'IK_`$P3LE2+-5^ZAJN M$$QE6%!4_.CZOUI`(!PJ542\R(5QS<;Y*IW1C$F=E M652NS\QF>5%2=#H.WRK#SCW5:!)SLBCK-;HB9"INC.^^59>3IWSL0H:[=+"1 M^Z(K5(/YLK)NJIHWIIZ1I>9)T@G[Q*UZPEL3GV67UNBXT!4$XSNEG&EG%3R" MT%G92VY)!(WXA4[.MKMF'^IHC=3VKH):5)>%URL!S>93ZF^ZCAO8E*&]BG#; MSU].##9PH:E%AR>-YQ*NG7%+6#('TE0=*+_.?4<##R0`5E'\K?A:5CO]%?B]4]=O\V3NK%$0S)GLJ];=6:/Y6_19 MC6^3U`00.Z3@=6G.VZ=WYDF_\QORFC?*&^;IHR"1'M*RJM)PI"I/YGXE:W5/ M^0LZO_[+_P#&%G*_$RX==T)I;TFG)G&5=R:>TJGT7_\`+6?`_966;!8P[KEC M"PT^Y4PIZJ$]U!5W5NG_`,KZ+HZJ5:B13.096%U2U+R8`RNDK?\`*/RLFZV* MS>G2.#ZO;.MK[S`3`,KJO!?5X;387Q[2L+Q9_P"X8@\'?\_ZKKX[PQGC]>Q= M&O&U*;3KS&RV:%0:1ZOU7(^'/RM736WY&K.4Y7%?U%WI_9)KG-=$_5#;[!)_ MY'+G6XT+2O,`NV]U=8_TR#ORL:WV/PM*A_R6I5B6I4'EJA3FK>DQLK5S_P`L MJ/IGYS\KG;J-3E.6C1![1"@>V1&_NK+]C\J`_F5-*[VD/.,;[H'@.:0)^B.X M_*F=_P`GZ(:5*U,M!Q/RJE5AR5H5/RGZ*H[^KZIM*I5&&<843JKV'NK=;\BI MW'YEK:6!97?_`!5/W*[/I%QIM&#&RX1O_O&?*Z[IO_MF_16I.&UYX+2F+F$` MJK3_`.2/E)OY?J5C36UN*D^K]*I#!&Z/63_4HJ?_`"_HC;^8_`5THR\8@)]<[2@& M[OA*EL4T"-4#&X"0>.^_91'_`)A3MW;\J"PUY`B81!Y.02H'?\M'0V^B"5KB M#*E;4QO!]E`/S?1,W?F^BDZ?^1+\2+#R4QC5,IG M[_5#4_YGV5@<.`,GZ(I,@]U$/^8%,>/HJD,YQWDIVNS$H*WY/JDS\P45('>K M!PI`YI&V-L*!OYDXW*J&OS%$MD0JG3':JQ$JS=_^VHM?L"5/PAY*#%LIIWCJ)(`) I("O4!#X(DJA<_P#YE_\`B6G2_P">U9ZNFXE:#I_,$^EW^,*4;)(T_]EK ` end rt-5.0.1/t/data/emails/text-html-with-umlaut000644 000765 000024 00000003177 14005011336 021566 0ustar00sunnavystaff000000 000000 Return-Path: Delivered-To: j@pallas.eruditorum.org Received: from vis.example.com (vis.example.com [212.68.68.251]) by pallas.eruditorum.org (Postfix) with SMTP id 59236111C3 for ; Thu, 12 Jun 2003 02:14:44 -0400 (EDT) Received: (qmail 29541 invoked by uid 502); 12 Jun 2003 06:14:42 -0000 Received: from sivd.example.com (HELO example.com) (192.168.42.1) by 192.168.42.42 with SMTP; 12 Jun 2003 06:14:42 -0000 Received: received from 172.20.72.174 by odie.example.com; Thu, 12 Jun 2003 08:14:27 +0200 Received: by mailserver.example.com with Internet Mail Service (5.5.2653.19) id ; Thu, 12 Jun 2003 08:14:39 +0200 Message-ID: <50362EC956CBD411A339009027F6257E013DD495@mailserver.example.com> Date: Thu, 12 Jun 2003 08:14:39 +0200 From: "Stever, Gregor" MIME-Version: 1.0 X-Mailer: Internet Mail Service (5.5.2653.19) To: "'jesse@example.com'" Subject: An example of mail containing text-html with an umlaut in the content Date: Thu, 12 Jun 2003 08:14:39 +0200 Content-Type: text/html; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable

      Hello,

      ist this kind of Messa= ges, that=20 causes rt to crash.

      Mit freundlichen Gr=FC=DFen
      Gregor=20 Stever      ^^causes Error!!
      rt-5.0.1/t/data/emails/email-file-attachment-2.eml000644 000765 000024 00000004240 14005011336 022417 0ustar00sunnavystaff000000 000000 Return-Path: X-Spam-Flag: NO X-Spam-Score: -2.449 X-Spam-Level: X-Spam-Status: No, score=-2.449 tagged_above=-99.9 required=10 tests=[AWL=0.250, BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, FREEMAIL_FROM=0.001, KHOP_DYNAMIC=0.001, RCVD_IN_DNSWL_LOW=-0.7, SPF_PASS=-0.001] autolearn=ham X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10432:,, definitions=2017-07-28_05:,, signatures=0 X-Proofpoint-Spam-Details: rule=notspam policy=default score=0 spamscore=0 clxscore=1034 suspectscore=1 malwarescore=0 phishscore=0 adultscore=0 bulkscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.0.1-1701120000 definitions=main-1707280209 From: Jim Brandt Content-type: multipart/mixed; boundary="Apple-Mail=_E5E623A8-7064-4736-9F2E-2A0F35E3635B" Subject: This is another test Message-id: Date: Fri, 28 Jul 2017 09:21:48 -0400 To: rt@example.com MIME-version: 1.0 (Mac OS X Mail 9.3 \(3124\)) X-Mailer: Apple Mail (2.3124) --Apple-Mail=_E5E623A8-7064-4736-9F2E-2A0F35E3635B Content-Transfer-Encoding: 7bit Content-Type: text/plain; charset=us-ascii This is a test with a multipart email file attachment. --Apple-Mail=_E5E623A8-7064-4736-9F2E-2A0F35E3635B Content-Type: message/rfc822; charset="ascii"; name="test-email-2.eml" Content-Disposition: attachment; filename="test-email-2.eml" X-RT-Original-Encoding: ascii Content-Length: 0 MIME-Version: 1.0 To: root@example.com Date: Thu, 5 Dec 2019 06:12:57 +0000 (UTC) Content-Type: multipart/alternative; boundary="===============4845098068763498141==" Reply-To: root@example.com Content-Length: 0 This is a multi-part message in MIME format... --===============4845098068763498141== Content-Transfer-Encoding: quoted-printable X-RT-Original-Encoding: utf-8 Content-Type: text/plain; charset="utf-8" Content-Length: 11 plain text --===============4845098068763498141== X-RT-Original-Encoding: utf-8 Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset="utf-8" Content-Length: 18

      plain text

      --===============4845098068763498141==-- --Apple-Mail=_E5E623A8-7064-4736-9F2E-2A0F35E3635B-- rt-5.0.1/t/data/emails/8859-15-message-series/msg4000644 000765 000024 00000003303 14005011336 021775 0ustar00sunnavystaff000000 000000 Return-Path: Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de Received: (qmail 28283 invoked by uid 9804); 26 May 2003 18:12:39 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:12:36 +0200 Received: (Qmail 28256 invoked from network); 26 May 2003 18:12:35 +0200 Received: From es.inf.fu-berlin.de (160.45.110.22) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:12:35 +0200 Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de) by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian)) id 19KKaQ-0000ZQ-00 for ; Mon, 26 May 2003 18:12:34 +0200 Received: (qmail 28236 invoked by uid 9804); 26 May 2003 18:12:34 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:12:30 +0200 Received: (Qmail 28224 invoked from network); 26 May 2003 18:12:30 +0200 Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:12:30 +0200 Date: Mon, 26 May 2003 18:12:50 +0200 From: Dirk Pape To: staff@tec.mi.fu-berlin.de Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?= Message-ID: <2147483647.1053972770@[10.0.255.35]> X-Mailer: Mulberry/3.0.3 (Mac OS X) X-Envelope-Sender: pape@inf.fu-berlin.de X-Envelope-Sender: pape@inf.fu-berlin.de X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [28259] (NAI-uvscan@math.fu-berlin.de) X-Remote-IP: 160.45.110.22 MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed Content-Transfer-Encoding: quoted-printable Content-Disposition: inline test nochmal in anderer Queue test =E4=F6=FC=DF=C4=D6=DC=DF=A4 rt-5.0.1/t/data/emails/8859-15-message-series/msg3000644 000765 000024 00000003303 14005011336 021774 0ustar00sunnavystaff000000 000000 Return-Path: Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de Received: (qmail 27971 invoked by uid 9804); 26 May 2003 18:12:02 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:11:52 +0200 Received: (Qmail 27908 invoked from network); 26 May 2003 18:11:52 +0200 Received: From es.inf.fu-berlin.de (160.45.110.22) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:11:52 +0200 Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de) by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian)) id 19KKZj-0000ZC-00 for ; Mon, 26 May 2003 18:11:51 +0200 Received: (qmail 27848 invoked by uid 9804); 26 May 2003 18:11:50 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:11:46 +0200 Received: (Qmail 27809 invoked from network); 26 May 2003 18:11:45 +0200 Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:11:45 +0200 Date: Mon, 26 May 2003 18:12:05 +0200 From: Dirk Pape To: staff@tec.mi.fu-berlin.de Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?= Message-ID: <2147483647.1053972725@[10.0.255.35]> X-Mailer: Mulberry/3.0.3 (Mac OS X) X-Envelope-Sender: pape@inf.fu-berlin.de X-Envelope-Sender: pape@inf.fu-berlin.de X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [27911] (NAI-uvscan@math.fu-berlin.de) X-Remote-IP: 160.45.110.22 MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed Content-Transfer-Encoding: quoted-printable Content-Disposition: inline test nochmal in anderer Queue test =E4=F6=FC=DF=C4=D6=DC=DF=A4 rt-5.0.1/t/data/emails/8859-15-message-series/msg2000644 000765 000024 00000003304 14005011336 021774 0ustar00sunnavystaff000000 000000 Return-Path: Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de Received: (qmail 27754 invoked by uid 9804); 26 May 2003 18:11:24 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:11:20 +0200 Received: (Qmail 27704 invoked from network); 26 May 2003 18:11:19 +0200 Received: From es.inf.fu-berlin.de (160.45.110.22) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:11:19 +0200 Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de) by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian)) id 19KKZA-0000Yy-00 for ; Mon, 26 May 2003 18:11:16 +0200 Received: (qmail 27690 invoked by uid 9804); 26 May 2003 18:11:16 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:11:13 +0200 Received: (Qmail 27677 invoked from network); 26 May 2003 18:11:13 +0200 Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:11:13 +0200 Date: Mon, 26 May 2003 18:11:32 +0200 From: Dirk Pape To: staff@tec.mi.fu-berlin.de Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?= Message-ID: <2147483647.1053972692@[10.0.255.35]> X-Mailer: Mulberry/3.0.3 (Mac OS X) X-Envelope-Sender: pape@inf.fu-berlin.de X-Envelope-Sender: pape@inf.fu-berlin.de X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [27711] (NAI-uvscan@math.fu-berlin.de) X-Remote-IP: 160.45.110.22 MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed Content-Transfer-Encoding: quoted-printable Content-Disposition: inline test nochmal in anderer Queue test =E4=F6=FC=DF=C4=D6=DC=DF=A4 rt-5.0.1/t/data/emails/8859-15-message-series/msg5000644 000765 000024 00000003303 14005011336 021776 0ustar00sunnavystaff000000 000000 Return-Path: Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de Received: (qmail 28578 invoked by uid 9804); 26 May 2003 18:13:20 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:13:15 +0200 Received: (Qmail 28534 invoked from network); 26 May 2003 18:13:14 +0200 Received: From es.inf.fu-berlin.de (160.45.110.22) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:13:14 +0200 Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de) by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian)) id 19KKb1-0000Ze-00 for ; Mon, 26 May 2003 18:13:11 +0200 Received: (qmail 28516 invoked by uid 9804); 26 May 2003 18:13:11 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:13:08 +0200 Received: (Qmail 28479 invoked from network); 26 May 2003 18:13:07 +0200 Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:13:07 +0200 Date: Mon, 26 May 2003 18:13:27 +0200 From: Dirk Pape To: staff@tec.mi.fu-berlin.de Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?= Message-ID: <2147483647.1053972807@[10.0.255.35]> X-Mailer: Mulberry/3.0.3 (Mac OS X) X-Envelope-Sender: pape@inf.fu-berlin.de X-Envelope-Sender: pape@inf.fu-berlin.de X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [28540] (NAI-uvscan@math.fu-berlin.de) X-Remote-IP: 160.45.110.22 MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed Content-Transfer-Encoding: quoted-printable Content-Disposition: inline test nochmal in anderer Queue test =E4=F6=FC=DF=C4=D6=DC=DF=A4 rt-5.0.1/t/data/emails/8859-15-message-series/msg7000644 000765 000024 00000003304 14005011336 022001 0ustar00sunnavystaff000000 000000 Return-Path: Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de Received: (qmail 29551 invoked by uid 9804); 26 May 2003 18:15:16 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:15:12 +0200 Received: (Qmail 29521 invoked from network); 26 May 2003 18:15:12 +0200 Received: From es.inf.fu-berlin.de (160.45.110.22) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:15:12 +0200 Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de) by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian)) id 19KKcx-0000a4-00 for ; Mon, 26 May 2003 18:15:11 +0200 Received: (qmail 29511 invoked by uid 9804); 26 May 2003 18:15:10 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:15:07 +0200 Received: (Qmail 29465 invoked from network); 26 May 2003 18:15:06 +0200 Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:15:06 +0200 Date: Mon, 26 May 2003 18:15:26 +0200 From: Dirk Pape To: staff@tec.mi.fu-berlin.de Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?= Message-ID: <2147483647.1053972926@[10.0.255.35]> X-Mailer: Mulberry/3.0.3 (Mac OS X) X-Envelope-Sender: pape@inf.fu-berlin.de X-Envelope-Sender: pape@inf.fu-berlin.de X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [29524] (NAI-uvscan@math.fu-berlin.de) X-Remote-IP: 160.45.110.22 MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed Content-Transfer-Encoding: quoted-printable Content-Disposition: inline test nochmal in anderer Queue test =E4=F6=FC=DF=C4=D6=DC=DF=A4 rt-5.0.1/t/data/emails/8859-15-message-series/msg6000644 000765 000024 00000003303 14005011336 021777 0ustar00sunnavystaff000000 000000 Return-Path: Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de Received: (qmail 29108 invoked by uid 9804); 26 May 2003 18:14:15 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:14:10 +0200 Received: (Qmail 29066 invoked from network); 26 May 2003 18:14:10 +0200 Received: From es.inf.fu-berlin.de (160.45.110.22) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:14:10 +0200 Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de) by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian)) id 19KKbw-0000Zr-00 for ; Mon, 26 May 2003 18:14:08 +0200 Received: (qmail 29054 invoked by uid 9804); 26 May 2003 18:14:08 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:14:04 +0200 Received: (Qmail 29036 invoked from network); 26 May 2003 18:14:04 +0200 Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:14:04 +0200 Date: Mon, 26 May 2003 18:14:24 +0200 From: Dirk Pape To: staff@tec.mi.fu-berlin.de Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?= Message-ID: <2147483647.1053972864@[10.0.255.35]> X-Mailer: Mulberry/3.0.3 (Mac OS X) X-Envelope-Sender: pape@inf.fu-berlin.de X-Envelope-Sender: pape@inf.fu-berlin.de X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [29069] (NAI-uvscan@math.fu-berlin.de) X-Remote-IP: 160.45.110.22 MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed Content-Transfer-Encoding: quoted-printable Content-Disposition: inline test nochmal in anderer Queue test =E4=F6=FC=DF=C4=D6=DC=DF=A4 rt-5.0.1/t/data/emails/8859-15-message-series/msg1000644 000765 000024 00000003304 14005011336 021773 0ustar00sunnavystaff000000 000000 Return-Path: Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de Received: (qmail 27591 invoked by uid 9804); 26 May 2003 18:10:50 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:10:46 +0200 Received: (Qmail 27575 invoked from network); 26 May 2003 18:10:46 +0200 Received: From es.inf.fu-berlin.de (160.45.110.22) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:10:46 +0200 Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de) by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian)) id 19KKYe-0000Yi-00 for ; Mon, 26 May 2003 18:10:44 +0200 Received: (qmail 27557 invoked by uid 9804); 26 May 2003 18:10:44 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:10:40 +0200 Received: (Qmail 27540 invoked from network); 26 May 2003 18:10:40 +0200 Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:10:40 +0200 Date: Mon, 26 May 2003 18:11:00 +0200 From: Dirk Pape To: staff@tec.mi.fu-berlin.de Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?= Message-ID: <2147483647.1053972660@[10.0.255.35]> X-Mailer: Mulberry/3.0.3 (Mac OS X) X-Envelope-Sender: pape@inf.fu-berlin.de X-Envelope-Sender: pape@inf.fu-berlin.de X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [27578] (NAI-uvscan@math.fu-berlin.de) X-Remote-IP: 160.45.110.22 MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed Content-Transfer-Encoding: quoted-printable Content-Disposition: inline test nochmal in anderer Queue test =E4=F6=FC=DF=C4=D6=DC=DF=A4 rt-5.0.1/t/data/emails/8859-15-message-series/dir000644 000765 000024 00000037173 14005011336 021715 0ustar00sunnavystaff000000 000000 Return-Path: Delivered-To: j@pallas.eruditorum.org Received: from pallas.eruditorum.org (localhost [127.0.0.1]) by pallas.eruditorum.org (Postfix) with ESMTP id 72E3A111B3; Mon, 26 May 2003 14:50:14 -0400 (EDT) Delivered-To: rt-users@pallas.eruditorum.org Received: from mail-in-02.arcor-online.net (mail-in-02.arcor-online.net [151.189.21.42]) by pallas.eruditorum.org (Postfix) with ESMTP id 15E761118D for ; Mon, 26 May 2003 14:49:56 -0400 (EDT) Received: from otdial-212-144-012-186.arcor-ip.net (otdial-212-144-011-024.arcor-ip.net [212.144.11.24]) by mail-in-02.arcor-online.net (Postfix) with ESMTP id 745EE15E87; Mon, 26 May 2003 20:53:15 +0200 (CEST) From: Dirk Pape To: Jesse Vincent , rt-users Subject: Re: [rt-users] [rt-announce] Development Snapshot 3.0.2++ Message-ID: <2147483647.1053982235@otdial-212-144-011-024.arcor-ip.net> In-Reply-To: <2147483647.1053974498@[10.0.255.35]> References: <20030523202405.GF23719@fsck.com> <2147483647.1053974498@[10.0.255.35]> X-Mailer: Mulberry/3.0.3 (Mac OS X) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="==========2147500486==========" Sender: rt-users-admin@lists.fsck.com Errors-To: rt-users-admin@lists.fsck.com X-BeenThere: rt-users@lists.fsck.com X-Mailman-Version: 2.0.12 Precedence: bulk List-Help: List-Post: List-Subscribe: , List-Id: For users of RT: Request Tracker List-Unsubscribe: , List-Archive: Date: Mon, 26 May 2003 20:50:36 +0200 X-Spam-Status: No, hits=-2.5 required=5.0 tests=AWL,IN_REP_TO,KNOWN_MAILING_LIST,QUOTED_EMAIL_TEXT, REFERENCES,REPLY_WITH_QUOTES autolearn=ham version=2.55 X-Spam-Level: X-Spam-Checker-Version: SpamAssassin 2.55 (1.174.2.19-2003-05-19-exp) --==========2147500486========== Content-Type: text/plain; charset=us-ascii; format=flowed Content-Transfer-Encoding: 7bit Content-Disposition: inline Hello, here is the digest I forgot to attach. And I also forgot to say, that these were the only messages after a restart of apache. The messages in the digest are the copies which I - for testing purpose - allways queue into a mailbox just befor it is queued via rt-mailgate into the rt-system. --Am Montag, 26. Mai 2003 18:41 Uhr +0200 schrieb Dirk Pape : > I attach a digest with mails I send one after another to the rt-system > and they get queued into one queue, each as a new ticket. --==========2147500486========== Content-Type: multipart/digest; boundary="==========2147489407==========" --==========2147489407========== Content-Type: message/rfc822; name="test _________" Return-Path: Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de Received: (qmail 27591 invoked by uid 9804); 26 May 2003 18:10:50 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:10:46 +0200 Received: (Qmail 27575 invoked from network); 26 May 2003 18:10:46 +0200 Received: From es.inf.fu-berlin.de (160.45.110.22) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:10:46 +0200 Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de) by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian)) id 19KKYe-0000Yi-00 for ; Mon, 26 May 2003 18:10:44 +0200 Received: (qmail 27557 invoked by uid 9804); 26 May 2003 18:10:44 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:10:40 +0200 Received: (Qmail 27540 invoked from network); 26 May 2003 18:10:40 +0200 Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:10:40 +0200 Date: Mon, 26 May 2003 18:11:00 +0200 From: Dirk Pape To: staff@tec.mi.fu-berlin.de Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?= Message-ID: <2147483647.1053972660@[10.0.255.35]> X-Mailer: Mulberry/3.0.3 (Mac OS X) X-Envelope-Sender: pape@inf.fu-berlin.de X-Envelope-Sender: pape@inf.fu-berlin.de X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [27578] (NAI-uvscan@math.fu-berlin.de) X-Remote-IP: 160.45.110.22 MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed Content-Transfer-Encoding: quoted-printable Content-Disposition: inline test nochmal in anderer Queue test =E4=F6=FC=DF=C4=D6=DC=DF=A4 --==========2147489407========== Content-Type: message/rfc822; name="test _________" Return-Path: Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de Received: (qmail 27754 invoked by uid 9804); 26 May 2003 18:11:24 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:11:20 +0200 Received: (Qmail 27704 invoked from network); 26 May 2003 18:11:19 +0200 Received: From es.inf.fu-berlin.de (160.45.110.22) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:11:19 +0200 Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de) by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian)) id 19KKZA-0000Yy-00 for ; Mon, 26 May 2003 18:11:16 +0200 Received: (qmail 27690 invoked by uid 9804); 26 May 2003 18:11:16 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:11:13 +0200 Received: (Qmail 27677 invoked from network); 26 May 2003 18:11:13 +0200 Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:11:13 +0200 Date: Mon, 26 May 2003 18:11:32 +0200 From: Dirk Pape To: staff@tec.mi.fu-berlin.de Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?= Message-ID: <2147483647.1053972692@[10.0.255.35]> X-Mailer: Mulberry/3.0.3 (Mac OS X) X-Envelope-Sender: pape@inf.fu-berlin.de X-Envelope-Sender: pape@inf.fu-berlin.de X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [27711] (NAI-uvscan@math.fu-berlin.de) X-Remote-IP: 160.45.110.22 MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed Content-Transfer-Encoding: quoted-printable Content-Disposition: inline test nochmal in anderer Queue test =E4=F6=FC=DF=C4=D6=DC=DF=A4 --==========2147489407========== Content-Type: message/rfc822; name="test _________" Return-Path: Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de Received: (qmail 27971 invoked by uid 9804); 26 May 2003 18:12:02 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:11:52 +0200 Received: (Qmail 27908 invoked from network); 26 May 2003 18:11:52 +0200 Received: From es.inf.fu-berlin.de (160.45.110.22) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:11:52 +0200 Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de) by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian)) id 19KKZj-0000ZC-00 for ; Mon, 26 May 2003 18:11:51 +0200 Received: (qmail 27848 invoked by uid 9804); 26 May 2003 18:11:50 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:11:46 +0200 Received: (Qmail 27809 invoked from network); 26 May 2003 18:11:45 +0200 Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:11:45 +0200 Date: Mon, 26 May 2003 18:12:05 +0200 From: Dirk Pape To: staff@tec.mi.fu-berlin.de Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?= Message-ID: <2147483647.1053972725@[10.0.255.35]> X-Mailer: Mulberry/3.0.3 (Mac OS X) X-Envelope-Sender: pape@inf.fu-berlin.de X-Envelope-Sender: pape@inf.fu-berlin.de X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [27911] (NAI-uvscan@math.fu-berlin.de) X-Remote-IP: 160.45.110.22 MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed Content-Transfer-Encoding: quoted-printable Content-Disposition: inline test nochmal in anderer Queue test =E4=F6=FC=DF=C4=D6=DC=DF=A4 --==========2147489407========== Content-Type: message/rfc822; name="test _________" Return-Path: Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de Received: (qmail 28283 invoked by uid 9804); 26 May 2003 18:12:39 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:12:36 +0200 Received: (Qmail 28256 invoked from network); 26 May 2003 18:12:35 +0200 Received: From es.inf.fu-berlin.de (160.45.110.22) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:12:35 +0200 Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de) by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian)) id 19KKaQ-0000ZQ-00 for ; Mon, 26 May 2003 18:12:34 +0200 Received: (qmail 28236 invoked by uid 9804); 26 May 2003 18:12:34 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:12:30 +0200 Received: (Qmail 28224 invoked from network); 26 May 2003 18:12:30 +0200 Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:12:30 +0200 Date: Mon, 26 May 2003 18:12:50 +0200 From: Dirk Pape To: staff@tec.mi.fu-berlin.de Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?= Message-ID: <2147483647.1053972770@[10.0.255.35]> X-Mailer: Mulberry/3.0.3 (Mac OS X) X-Envelope-Sender: pape@inf.fu-berlin.de X-Envelope-Sender: pape@inf.fu-berlin.de X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [28259] (NAI-uvscan@math.fu-berlin.de) X-Remote-IP: 160.45.110.22 MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed Content-Transfer-Encoding: quoted-printable Content-Disposition: inline test nochmal in anderer Queue test =E4=F6=FC=DF=C4=D6=DC=DF=A4 --==========2147489407========== Content-Type: message/rfc822; name="test _________" Return-Path: Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de Received: (qmail 28578 invoked by uid 9804); 26 May 2003 18:13:20 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:13:15 +0200 Received: (Qmail 28534 invoked from network); 26 May 2003 18:13:14 +0200 Received: From es.inf.fu-berlin.de (160.45.110.22) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:13:14 +0200 Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de) by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian)) id 19KKb1-0000Ze-00 for ; Mon, 26 May 2003 18:13:11 +0200 Received: (qmail 28516 invoked by uid 9804); 26 May 2003 18:13:11 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:13:08 +0200 Received: (Qmail 28479 invoked from network); 26 May 2003 18:13:07 +0200 Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:13:07 +0200 Date: Mon, 26 May 2003 18:13:27 +0200 From: Dirk Pape To: staff@tec.mi.fu-berlin.de Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?= Message-ID: <2147483647.1053972807@[10.0.255.35]> X-Mailer: Mulberry/3.0.3 (Mac OS X) X-Envelope-Sender: pape@inf.fu-berlin.de X-Envelope-Sender: pape@inf.fu-berlin.de X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [28540] (NAI-uvscan@math.fu-berlin.de) X-Remote-IP: 160.45.110.22 MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed Content-Transfer-Encoding: quoted-printable Content-Disposition: inline test nochmal in anderer Queue test =E4=F6=FC=DF=C4=D6=DC=DF=A4 --==========2147489407========== Content-Type: message/rfc822; name="test _________" Return-Path: Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de Received: (qmail 29108 invoked by uid 9804); 26 May 2003 18:14:15 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:14:10 +0200 Received: (Qmail 29066 invoked from network); 26 May 2003 18:14:10 +0200 Received: From es.inf.fu-berlin.de (160.45.110.22) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:14:10 +0200 Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de) by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian)) id 19KKbw-0000Zr-00 for ; Mon, 26 May 2003 18:14:08 +0200 Received: (qmail 29054 invoked by uid 9804); 26 May 2003 18:14:08 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:14:04 +0200 Received: (Qmail 29036 invoked from network); 26 May 2003 18:14:04 +0200 Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:14:04 +0200 Date: Mon, 26 May 2003 18:14:24 +0200 From: Dirk Pape To: staff@tec.mi.fu-berlin.de Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?= Message-ID: <2147483647.1053972864@[10.0.255.35]> X-Mailer: Mulberry/3.0.3 (Mac OS X) X-Envelope-Sender: pape@inf.fu-berlin.de X-Envelope-Sender: pape@inf.fu-berlin.de X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [29069] (NAI-uvscan@math.fu-berlin.de) X-Remote-IP: 160.45.110.22 MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed Content-Transfer-Encoding: quoted-printable Content-Disposition: inline test nochmal in anderer Queue test =E4=F6=FC=DF=C4=D6=DC=DF=A4 --==========2147489407========== Content-Type: message/rfc822; name="test _________" Return-Path: Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de Received: (qmail 29551 invoked by uid 9804); 26 May 2003 18:15:16 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:15:12 +0200 Received: (Qmail 29521 invoked from network); 26 May 2003 18:15:12 +0200 Received: From es.inf.fu-berlin.de (160.45.110.22) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:15:12 +0200 Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de) by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian)) id 19KKcx-0000a4-00 for ; Mon, 26 May 2003 18:15:11 +0200 Received: (qmail 29511 invoked by uid 9804); 26 May 2003 18:15:10 +0200 Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1) by localhost with SMTP; 26 May 2003 18:15:07 +0200 Received: (Qmail 29465 invoked from network); 26 May 2003 18:15:06 +0200 Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36) by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:15:06 +0200 Date: Mon, 26 May 2003 18:15:26 +0200 From: Dirk Pape To: staff@tec.mi.fu-berlin.de Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?= Message-ID: <2147483647.1053972926@[10.0.255.35]> X-Mailer: Mulberry/3.0.3 (Mac OS X) X-Envelope-Sender: pape@inf.fu-berlin.de X-Envelope-Sender: pape@inf.fu-berlin.de X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [29524] (NAI-uvscan@math.fu-berlin.de) X-Remote-IP: 160.45.110.22 MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed Content-Transfer-Encoding: quoted-printable Content-Disposition: inline test nochmal in anderer Queue test =E4=F6=FC=DF=C4=D6=DC=DF=A4 --==========2147489407==========-- --==========2147500486==========-- _______________________________________________ rt-users mailing list rt-users@lists.fsck.com http://lists.fsck.com/mailman/listinfo/rt-users Have you read the FAQ? The RT FAQ Manager lives at http://fsck.com/rtfm rt-5.0.1/t/data/plugins/Overlays/000755 000765 000024 00000000000 14005011336 017442 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/MakeClicky/000755 000765 000024 00000000000 14005011336 017652 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/RT-Extension-PSGIWrap/000755 000765 000024 00000000000 14005011336 021527 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/RT-Extension-ScripExecModule/000755 000765 000024 00000000000 14005011336 023166 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/RT-Extension-ScripExecModule/lib/000755 000765 000024 00000000000 14005011336 023734 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/RT-Extension-ScripExecModule/lib/RT/000755 000765 000024 00000000000 14005011336 024261 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/RT-Extension-ScripExecModule/lib/RT/Extension/000755 000765 000024 00000000000 14005011336 026235 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/RT-Extension-ScripExecModule/lib/RT/Action/000755 000765 000024 00000000000 14005011336 025476 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/RT-Extension-ScripExecModule/lib/RT/Condition/000755 000765 000024 00000000000 14005011336 026207 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/RT-Extension-ScripExecModule/lib/RT/Condition/Foo/000755 000765 000024 00000000000 14005011336 026732 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/RT-Extension-ScripExecModule/lib/RT/Condition/Foo/Bar.pm000644 000765 000024 00000000171 14005011336 027773 0ustar00sunnavystaff000000 000000 package RT::Condition::Foo::Bar; use strict; use warnings; use base 'RT::Condition'; sub IsApplicable { return 1 } 1; rt-5.0.1/t/data/plugins/RT-Extension-ScripExecModule/lib/RT/Action/Foo/000755 000765 000024 00000000000 14005011336 026221 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/RT-Extension-ScripExecModule/lib/RT/Action/Foo/Bar.pm000644 000765 000024 00000000207 14005011336 027262 0ustar00sunnavystaff000000 000000 package RT::Action::Foo::Bar; use strict; use warnings; use base 'RT::Action'; sub Prepare { return 1 } sub Commit { return 1 } 1; rt-5.0.1/t/data/plugins/RT-Extension-ScripExecModule/lib/RT/Extension/ScripExecModule.pm000644 000765 000024 00000000054 14005011336 031625 0ustar00sunnavystaff000000 000000 package RT::Extension::ScripExecModule; 1; rt-5.0.1/t/data/plugins/RT-Extension-PSGIWrap/lib/000755 000765 000024 00000000000 14005011336 022275 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/RT-Extension-PSGIWrap/lib/RT/000755 000765 000024 00000000000 14005011336 022622 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/RT-Extension-PSGIWrap/lib/RT/Extension/000755 000765 000024 00000000000 14005011336 024576 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/RT-Extension-PSGIWrap/lib/RT/Extension/PSGIWrap.pm000644 000765 000024 00000000523 14005011336 026530 0ustar00sunnavystaff000000 000000 package RT::Extension::PSGIWrap; use base 'Plack::Middleware'; sub call { my ( $self, $env ) = @_; my $res = $self->app->($env); return $self->response_cb( $res, sub { my $headers = shift->[1]; Plack::Util::header_set($headers, 'X-RT-PSGIWrap' => '1'); } ); } sub PSGIWrap { return shift->wrap(@_) } 1; rt-5.0.1/t/data/plugins/MakeClicky/html/000755 000765 000024 00000000000 14005011336 020616 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/MakeClicky/lib/000755 000765 000024 00000000000 14005011336 020420 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/MakeClicky/lib/MakeClicky.pm000644 000765 000024 00000000027 14005011336 022771 0ustar00sunnavystaff000000 000000 package MakeClicky; 1; rt-5.0.1/t/data/plugins/MakeClicky/html/makeclicky000644 000765 000024 00000000241 14005011336 022652 0ustar00sunnavystaff000000 000000 <%args> $content => "" $html => 0 <%init> $m->comp("/Elements/MakeClicky", content => \$content, html => $html); $m->out($content); $m->abort; rt-5.0.1/t/data/plugins/Overlays/html/000755 000765 000024 00000000000 14005011336 020406 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/Overlays/lib/000755 000765 000024 00000000000 14005011336 020210 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/Overlays/lib/RT/000755 000765 000024 00000000000 14005011336 020535 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/plugins/Overlays/lib/Overlays.pm000644 000765 000024 00000000025 14005011336 022347 0ustar00sunnavystaff000000 000000 package Overlays; 1; rt-5.0.1/t/data/plugins/Overlays/lib/RT/User_Local.pm000644 000765 000024 00000000210 14005011336 023114 0ustar00sunnavystaff000000 000000 package RT::User; use strict; use warnings; our $LOADED_OVERLAY = 1; sub _LocalAccessible { { Comments => { public => 1 } } } 1; rt-5.0.1/t/data/plugins/Overlays/html/user_accessible000644 000765 000024 00000000254 14005011336 023465 0ustar00sunnavystaff000000 000000 <%flags> inherit => undef # avoid auth <%init> $r->content_type("application/json"); $m->out( JSON( RT::User->_ClassAccessible() ) ); $m->abort(200); rt-5.0.1/t/data/plugins/Overlays/html/overlay_loaded000644 000765 000024 00000000252 14005011336 023321 0ustar00sunnavystaff000000 000000 <%flags> inherit => undef # avoid auth <%init> $r->content_type("text/plain"); $m->out( $RT::User::LOADED_OVERLAY ? "yes" : "no" ); $m->abort(200); rt-5.0.1/t/data/smime/mails/000755 000765 000024 00000000000 14005011336 016374 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/smime/keys/000755 000765 000024 00000000000 14005011336 016242 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/smime/keys/root@example.com.pem000644 000765 000024 00000005461 14005011336 022167 0ustar00sunnavystaff000000 000000 Certificate: Data: Version: 1 (0x0) Serial Number: 9974010075738841110 (0x8a6acd51be94a016) Signature Algorithm: sha1WithRSAEncryption Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=CA Owner/emailAddress=ca.owner@example.com Validity Not Before: Aug 28 21:41:07 2013 GMT Not After : Aug 28 21:41:07 2023 GMT Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=Enoch Root/emailAddress=root@example.com Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (512 bit) Modulus: 00:b2:77:b9:bc:09:7d:14:8e:6b:6f:7e:33:a9:95: 21:5d:f3:3c:91:61:f1:bc:5c:1d:7e:e7:54:25:e8: cb:5f:b7:18:0e:23:26:00:42:09:bd:89:da:5c:06: cb:52:08:43:f6:4e:fe:dd:f8:0a:8a:95:35:8f:4a: 25:16:da:e6:bf Exponent: 65537 (0x10001) Signature Algorithm: sha1WithRSAEncryption 1a:cd:7e:0e:e0:6f:90:b7:22:0e:4d:79:4d:6a:9b:ac:a1:6a: ab:85:32:9c:86:9c:d2:10:96:f7:e0:00:2c:7d:3c:16:a4:ff: dd:9e:37:fb:a3:7a:43:ab:2f:ee:c4:ff:be:77:0f:40:f8:0e: 45:3e:48:46:bf:ec:e1:b0:46:8d:13:37:7a:a6:d1:7c:16:cb: 28:6b:37:88:4d:0a:12:6b:87:b9:7c:d9:c4:d7:57:93:b9:f6: 21:26:1b:32:88:1d:cd:84:0f:6a:f9:05:0a:76:01:de:5e:99: 86:10:fc:7d:ee:d5:70:b2:44:99:41:0a:d7:0e:e8:5b:c9:ca: 10:39 -----BEGIN CERTIFICATE----- MIICKzCCAZQCCQCKas1RvpSgFjANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJB VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 cyBQdHkgTHRkMREwDwYDVQQDDAhDQSBPd25lcjEjMCEGCSqGSIb3DQEJARYUY2Eu b3duZXJAZXhhbXBsZS5jb20wHhcNMTMwODI4MjE0MTA3WhcNMjMwODI4MjE0MTA3 WjB7MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMY SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRMwEQYDVQQDEwpFbm9jaCBSb290MR8w HQYJKoZIhvcNAQkBFhByb290QGV4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQAD SwAwSAJBALJ3ubwJfRSOa29+M6mVIV3zPJFh8bxcHX7nVCXoy1+3GA4jJgBCCb2J 2lwGy1IIQ/ZO/t34CoqVNY9KJRba5r8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQAa zX4O4G+QtyIOTXlNapusoWqrhTKchpzSEJb34AAsfTwWpP/dnjf7o3pDqy/uxP++ dw9A+A5FPkhGv+zhsEaNEzd6ptF8FssoazeITQoSa4e5fNnE11eTufYhJhsyiB3N hA9q+QUKdgHeXpmGEPx97tVwskSZQQrXDuhbycoQOQ== -----END CERTIFICATE----- -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,6356CE6012402B9B Lco5rf3/rHlShktH/o6NHF1mVH00k+pZ3bWodejMaHW1ofZXe9/yjzPM2jqqi+Dj xmzZ9R/MijO07vpxWHqdvhXeFf0TW67gW413M/bwiRd/rV0mUFz81nowFe9e15tm Itku1sePFvvL/UUxBGeYhplHAP6e76JqQcJTkBaG04KitH9GHtj1HFQR8P9/8h6d f0ZtU8wqnhkZvtzb72ZLwsw0YZ7R9YLIqCmOn1twW0CC77deACy+deJOC0N4CxW6 +jEGbJKMN5rOPsFiieDzZXAaTlGd6qXVWaxUPYH89yWedYoAZgbi6zxGGwNGbc/Q 2Y7g+qHi3L30uJvgJEGihIM+9iAKUJSazyGYl9Xl2FwTpNFOMJAYFyNKNv5FHwdm deoslrbEXVtqurOQYr955cyqs2NN+JYLsz5nNnfBpGo= -----END RSA PRIVATE KEY----- rt-5.0.1/t/data/smime/keys/root@example.com.csr000644 000765 000024 00000000761 14005011336 022173 0ustar00sunnavystaff000000 000000 -----BEGIN CERTIFICATE REQUEST----- MIIBNTCB4AIBADB7MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEh MB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRMwEQYDVQQDEwpFbm9j aCBSb290MR8wHQYJKoZIhvcNAQkBFhByb290QGV4YW1wbGUuY29tMFwwDQYJKoZI hvcNAQEBBQADSwAwSAJBALJ3ubwJfRSOa29+M6mVIV3zPJFh8bxcHX7nVCXoy1+3 GA4jJgBCCb2J2lwGy1IIQ/ZO/t34CoqVNY9KJRba5r8CAwEAAaAAMA0GCSqGSIb3 DQEBBQUAA0EABuN/lyQxMY6DNb9XZ7H+UZLJrNYei1HRvfIXig7EvkSDEnArSwfZ uzAeLo3mnIp7WiDk3M7e19LQFkERs2xvHw== -----END CERTIFICATE REQUEST----- rt-5.0.1/t/data/smime/keys/sender@example.com.crt000644 000765 000024 00000004372 14005011336 022473 0ustar00sunnavystaff000000 000000 Certificate: Data: Version: 1 (0x0) Serial Number: 9974010075738841109 (0x8a6acd51be94a015) Signature Algorithm: sha1WithRSAEncryption Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=CA Owner/emailAddress=ca.owner@example.com Validity Not Before: Aug 28 21:41:45 2013 GMT Not After : Aug 28 21:41:45 2023 GMT Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=sender/emailAddress=sender@example.com Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (512 bit) Modulus: 00:a8:38:41:90:1d:e7:cd:2b:cb:62:cf:ad:ff:70: f6:44:5d:f3:4b:7e:21:75:b6:5c:e1:7e:c2:27:3b: 85:eb:72:9b:5a:94:0a:69:1d:83:ca:c5:91:b2:3f: 04:72:61:e4:b8:eb:5b:ce:b5:10:77:d8:a7:df:8b: c9:5a:14:15:61 Exponent: 65537 (0x10001) Signature Algorithm: sha1WithRSAEncryption 91:74:84:00:98:40:30:6b:a6:61:6b:7b:d7:c9:9d:6e:ef:bb: c8:ba:8b:83:15:62:3e:d1:c2:9d:1c:4e:ce:09:ce:d8:4f:4a: 49:a8:97:e8:3b:ed:82:2c:a3:20:45:72:f3:d9:23:66:93:d5: 54:14:ce:ce:cf:27:04:52:43:b4:a7:0b:ac:b8:45:a3:96:bf: 2f:43:59:61:02:7a:36:39:9c:01:ad:b7:63:6e:b5:b6:29:cb: 79:78:93:95:25:24:4a:83:bd:1d:d6:07:86:06:6a:fa:04:60: 6e:ba:41:11:0a:cb:b2:84:03:ac:30:55:94:ed:b2:2d:3c:c5: 99:6f -----BEGIN CERTIFICATE----- MIICKTCCAZICCQCKas1RvpSgFTANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJB VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 cyBQdHkgTHRkMREwDwYDVQQDDAhDQSBPd25lcjEjMCEGCSqGSIb3DQEJARYUY2Eu b3duZXJAZXhhbXBsZS5jb20wHhcNMTMwODI4MjE0MTQ1WhcNMjMwODI4MjE0MTQ1 WjB5MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMY SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDEwZzZW5kZXIxITAfBgkq hkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sA MEgCQQCoOEGQHefNK8tiz63/cPZEXfNLfiF1tlzhfsInO4XrcptalAppHYPKxZGy PwRyYeS461vOtRB32Kffi8laFBVhAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAkXSE AJhAMGumYWt718mdbu+7yLqLgxViPtHCnRxOzgnO2E9KSaiX6DvtgiyjIEVy89kj ZpPVVBTOzs8nBFJDtKcLrLhFo5a/L0NZYQJ6NjmcAa23Y261tinLeXiTlSUkSoO9 HdYHhgZq+gRgbrpBEQrLsoQDrDBVlO2yLTzFmW8= -----END CERTIFICATE----- rt-5.0.1/t/data/smime/keys/demoCA/000755 000765 000024 00000000000 14005011336 017372 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/smime/keys/otherCA/000755 000765 000024 00000000000 14005011336 017567 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/smime/keys/root@example.com.key000644 000765 000024 00000001061 14005011336 022166 0ustar00sunnavystaff000000 000000 -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,6356CE6012402B9B Lco5rf3/rHlShktH/o6NHF1mVH00k+pZ3bWodejMaHW1ofZXe9/yjzPM2jqqi+Dj xmzZ9R/MijO07vpxWHqdvhXeFf0TW67gW413M/bwiRd/rV0mUFz81nowFe9e15tm Itku1sePFvvL/UUxBGeYhplHAP6e76JqQcJTkBaG04KitH9GHtj1HFQR8P9/8h6d f0ZtU8wqnhkZvtzb72ZLwsw0YZ7R9YLIqCmOn1twW0CC77deACy+deJOC0N4CxW6 +jEGbJKMN5rOPsFiieDzZXAaTlGd6qXVWaxUPYH89yWedYoAZgbi6zxGGwNGbc/Q 2Y7g+qHi3L30uJvgJEGihIM+9iAKUJSazyGYl9Xl2FwTpNFOMJAYFyNKNv5FHwdm deoslrbEXVtqurOQYr955cyqs2NN+JYLsz5nNnfBpGo= -----END RSA PRIVATE KEY----- rt-5.0.1/t/data/smime/keys/sender@example.com.key000644 000765 000024 00000001061 14005011336 022463 0ustar00sunnavystaff000000 000000 -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,605762440BC8261C MpUs66ILz2ePX4NKQ408LOAwvmpLLLnSwDX/Zmr/LG4SyZ7AnY6dY06XB6suev3m AS+xm/LM44lvUaDvPnl4gO8jnCw3D1yktcfeHc6XqcFx2U9AiUTawmoSTKwrT4P+ tnpSrrBJY3WghElbckK3vbZboX9Eld+dJjGPf9YqMrkixObp0ul1zW7Wt+aSEV5B ngP3VmQinB1EjSUhGF/gsFzhJsutsX4Z1SE/U4K1A1OPl3Oz4e+9VLGgUN4ao84y pcNYdXO/BCax4Uk8l0r0DcMd73P9WZs9+bcSgmkqduWCXkNXDbfi4RTOEn19Ehpu MyKc3JrskRhNRN1vfMSRFUsrmppxBdPfkrGrTCJNBuL7zdbQh9k9XMaNzfw5Tt2R oCWay5shBGEEKXRLIEqzO+Jx1BWVlWwxUwDLr73ItHA= -----END RSA PRIVATE KEY----- rt-5.0.1/t/data/smime/keys/root@example.com.crt000644 000765 000024 00000004400 14005011336 022166 0ustar00sunnavystaff000000 000000 Certificate: Data: Version: 1 (0x0) Serial Number: 9974010075738841110 (0x8a6acd51be94a016) Signature Algorithm: sha1WithRSAEncryption Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=CA Owner/emailAddress=ca.owner@example.com Validity Not Before: Aug 28 21:41:07 2013 GMT Not After : Aug 28 21:41:07 2023 GMT Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=Enoch Root/emailAddress=root@example.com Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (512 bit) Modulus: 00:b2:77:b9:bc:09:7d:14:8e:6b:6f:7e:33:a9:95: 21:5d:f3:3c:91:61:f1:bc:5c:1d:7e:e7:54:25:e8: cb:5f:b7:18:0e:23:26:00:42:09:bd:89:da:5c:06: cb:52:08:43:f6:4e:fe:dd:f8:0a:8a:95:35:8f:4a: 25:16:da:e6:bf Exponent: 65537 (0x10001) Signature Algorithm: sha1WithRSAEncryption 1a:cd:7e:0e:e0:6f:90:b7:22:0e:4d:79:4d:6a:9b:ac:a1:6a: ab:85:32:9c:86:9c:d2:10:96:f7:e0:00:2c:7d:3c:16:a4:ff: dd:9e:37:fb:a3:7a:43:ab:2f:ee:c4:ff:be:77:0f:40:f8:0e: 45:3e:48:46:bf:ec:e1:b0:46:8d:13:37:7a:a6:d1:7c:16:cb: 28:6b:37:88:4d:0a:12:6b:87:b9:7c:d9:c4:d7:57:93:b9:f6: 21:26:1b:32:88:1d:cd:84:0f:6a:f9:05:0a:76:01:de:5e:99: 86:10:fc:7d:ee:d5:70:b2:44:99:41:0a:d7:0e:e8:5b:c9:ca: 10:39 -----BEGIN CERTIFICATE----- MIICKzCCAZQCCQCKas1RvpSgFjANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJB VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 cyBQdHkgTHRkMREwDwYDVQQDDAhDQSBPd25lcjEjMCEGCSqGSIb3DQEJARYUY2Eu b3duZXJAZXhhbXBsZS5jb20wHhcNMTMwODI4MjE0MTA3WhcNMjMwODI4MjE0MTA3 WjB7MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMY SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRMwEQYDVQQDEwpFbm9jaCBSb290MR8w HQYJKoZIhvcNAQkBFhByb290QGV4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQAD SwAwSAJBALJ3ubwJfRSOa29+M6mVIV3zPJFh8bxcHX7nVCXoy1+3GA4jJgBCCb2J 2lwGy1IIQ/ZO/t34CoqVNY9KJRba5r8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQAa zX4O4G+QtyIOTXlNapusoWqrhTKchpzSEJb34AAsfTwWpP/dnjf7o3pDqy/uxP++ dw9A+A5FPkhGv+zhsEaNEzd6ptF8FssoazeITQoSa4e5fNnE11eTufYhJhsyiB3N hA9q+QUKdgHeXpmGEPx97tVwskSZQQrXDuhbycoQOQ== -----END CERTIFICATE----- rt-5.0.1/t/data/smime/keys/sender@example.com.csr000644 000765 000024 00000000755 14005011336 022473 0ustar00sunnavystaff000000 000000 -----BEGIN CERTIFICATE REQUEST----- MIIBMzCB3gIBADB5MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEh MB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDEwZzZW5k ZXIxITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmNvbTBcMA0GCSqGSIb3 DQEBAQUAA0sAMEgCQQCoOEGQHefNK8tiz63/cPZEXfNLfiF1tlzhfsInO4Xrcpta lAppHYPKxZGyPwRyYeS461vOtRB32Kffi8laFBVhAgMBAAGgADANBgkqhkiG9w0B AQUFAANBAFoi5bepEWsl0cQiO7k314NAuHenXaVrsWt3kPWfwgWn0aLp3aH86aZ5 g4MYNjJzTqnkU1apyY8MV+BUZaXfnII= -----END CERTIFICATE REQUEST----- rt-5.0.1/t/data/smime/keys/sender@example.com.pem000644 000765 000024 00000005453 14005011336 022465 0ustar00sunnavystaff000000 000000 Certificate: Data: Version: 1 (0x0) Serial Number: 9974010075738841109 (0x8a6acd51be94a015) Signature Algorithm: sha1WithRSAEncryption Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=CA Owner/emailAddress=ca.owner@example.com Validity Not Before: Aug 28 21:41:45 2013 GMT Not After : Aug 28 21:41:45 2023 GMT Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=sender/emailAddress=sender@example.com Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (512 bit) Modulus: 00:a8:38:41:90:1d:e7:cd:2b:cb:62:cf:ad:ff:70: f6:44:5d:f3:4b:7e:21:75:b6:5c:e1:7e:c2:27:3b: 85:eb:72:9b:5a:94:0a:69:1d:83:ca:c5:91:b2:3f: 04:72:61:e4:b8:eb:5b:ce:b5:10:77:d8:a7:df:8b: c9:5a:14:15:61 Exponent: 65537 (0x10001) Signature Algorithm: sha1WithRSAEncryption 91:74:84:00:98:40:30:6b:a6:61:6b:7b:d7:c9:9d:6e:ef:bb: c8:ba:8b:83:15:62:3e:d1:c2:9d:1c:4e:ce:09:ce:d8:4f:4a: 49:a8:97:e8:3b:ed:82:2c:a3:20:45:72:f3:d9:23:66:93:d5: 54:14:ce:ce:cf:27:04:52:43:b4:a7:0b:ac:b8:45:a3:96:bf: 2f:43:59:61:02:7a:36:39:9c:01:ad:b7:63:6e:b5:b6:29:cb: 79:78:93:95:25:24:4a:83:bd:1d:d6:07:86:06:6a:fa:04:60: 6e:ba:41:11:0a:cb:b2:84:03:ac:30:55:94:ed:b2:2d:3c:c5: 99:6f -----BEGIN CERTIFICATE----- MIICKTCCAZICCQCKas1RvpSgFTANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJB VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 cyBQdHkgTHRkMREwDwYDVQQDDAhDQSBPd25lcjEjMCEGCSqGSIb3DQEJARYUY2Eu b3duZXJAZXhhbXBsZS5jb20wHhcNMTMwODI4MjE0MTQ1WhcNMjMwODI4MjE0MTQ1 WjB5MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMY SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDEwZzZW5kZXIxITAfBgkq hkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sA MEgCQQCoOEGQHefNK8tiz63/cPZEXfNLfiF1tlzhfsInO4XrcptalAppHYPKxZGy PwRyYeS461vOtRB32Kffi8laFBVhAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAkXSE AJhAMGumYWt718mdbu+7yLqLgxViPtHCnRxOzgnO2E9KSaiX6DvtgiyjIEVy89kj ZpPVVBTOzs8nBFJDtKcLrLhFo5a/L0NZYQJ6NjmcAa23Y261tinLeXiTlSUkSoO9 HdYHhgZq+gRgbrpBEQrLsoQDrDBVlO2yLTzFmW8= -----END CERTIFICATE----- -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,605762440BC8261C MpUs66ILz2ePX4NKQ408LOAwvmpLLLnSwDX/Zmr/LG4SyZ7AnY6dY06XB6suev3m AS+xm/LM44lvUaDvPnl4gO8jnCw3D1yktcfeHc6XqcFx2U9AiUTawmoSTKwrT4P+ tnpSrrBJY3WghElbckK3vbZboX9Eld+dJjGPf9YqMrkixObp0ul1zW7Wt+aSEV5B ngP3VmQinB1EjSUhGF/gsFzhJsutsX4Z1SE/U4K1A1OPl3Oz4e+9VLGgUN4ao84y pcNYdXO/BCax4Uk8l0r0DcMd73P9WZs9+bcSgmkqduWCXkNXDbfi4RTOEn19Ehpu MyKc3JrskRhNRN1vfMSRFUsrmppxBdPfkrGrTCJNBuL7zdbQh9k9XMaNzfw5Tt2R oCWay5shBGEEKXRLIEqzO+Jx1BWVlWwxUwDLr73ItHA= -----END RSA PRIVATE KEY----- rt-5.0.1/t/data/smime/keys/otherCA/private/000755 000765 000024 00000000000 14005011336 021241 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/smime/keys/otherCA/cacert.pem000644 000765 000024 00000010607 14005011336 021537 0ustar00sunnavystaff000000 000000 Certificate: Data: Version: 3 (0x2) Serial Number: 16372135729078323798 (0xe33582b3ca31ca56) Signature Algorithm: sha1WithRSAEncryption Issuer: C=AU, ST=Some-State, O=Other Widgits, LLC, CN=CA Owner/emailAddress=ca.owner@example.net Validity Not Before: Aug 28 22:16:28 2013 GMT Not After : Aug 28 22:16:28 2023 GMT Subject: C=AU, ST=Some-State, O=Other Widgits, LLC, CN=CA Owner/emailAddress=ca.owner@example.net Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 00:d6:b6:53:04:53:e8:98:91:c6:6a:ab:27:c3:ba: 01:53:e1:f3:56:1e:90:c9:61:7e:73:37:36:80:49: a9:b4:6a:9f:3a:d8:08:6f:ba:82:64:c5:85:92:41: 53:71:25:ec:18:85:1c:9e:80:4b:30:f7:16:b4:f8: 07:3e:f7:9b:aa:2d:9f:f8:08:a4:0a:e6:9e:0a:d2: 2f:06:59:28:53:9e:b4:77:8a:2b:f0:b5:c6:ca:af: 41:be:ed:17:12:0f:37:2e:e9:b8:43:3a:76:20:fd: e8:81:91:b8:bf:03:92:76:1f:40:d3:e0:44:fd:34: c7:f3:d4:f6:77:c9:52:59:da:37:95:ab:54:a7:11: a5:1a:03:fa:cc:71:19:72:cb:29:39:15:69:b5:f6: 5b:16:22:d8:ed:a4:b3:b5:83:ed:69:d9:91:7f:2d: 0c:af:4f:c6:4a:4a:4f:1d:a3:dc:1f:10:f4:77:c8: 48:e5:94:64:3b:29:3d:9d:16:0c:d2:30:3a:44:0d: a4:87:04:04:84:ec:fd:19:82:08:77:b5:77:64:f4: ce:bc:6c:a5:c1:b7:17:7e:a2:4a:de:28:62:40:5e: 3d:77:5c:9a:09:dc:7e:a6:b6:a3:34:ca:73:a4:c2: 42:74:4e:d8:52:2d:98:4f:28:6e:89:93:7e:34:3b: eb:37 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Key Identifier: 7F:75:3B:B2:1E:CF:EF:D6:A7:D1:42:F8:1C:A9:13:63:CF:C9:0E:5A X509v3 Authority Key Identifier: keyid:7F:75:3B:B2:1E:CF:EF:D6:A7:D1:42:F8:1C:A9:13:63:CF:C9:0E:5A X509v3 Basic Constraints: CA:TRUE Signature Algorithm: sha1WithRSAEncryption 44:f7:e8:e6:af:a9:be:cf:28:51:dc:86:14:e2:4d:e4:14:9f: 09:4d:cb:e9:10:2c:ef:21:ec:b0:8c:14:57:59:45:52:b4:e3: db:f4:34:e3:39:b6:de:0c:eb:68:78:db:d0:21:d2:c1:51:18: ce:33:14:a4:4d:91:88:eb:cc:b0:4a:93:73:75:48:e8:56:ce: 29:c9:07:73:18:28:20:e1:2e:ba:0f:cc:4c:26:e7:45:d5:4c: 60:89:ef:1d:d7:7a:a5:80:62:bf:30:da:ac:bf:be:f8:54:f3: fc:8a:09:1c:89:2d:2a:12:20:99:66:54:a0:78:50:f0:46:44: 9d:ad:95:81:83:c0:47:38:b8:4a:81:3c:72:49:68:a2:a1:04: c7:d3:e9:e8:6f:65:ce:10:11:7f:0a:8b:96:ce:4e:1e:55:c7: 54:34:25:5e:ba:95:62:ad:45:43:b1:69:70:d4:c4:33:29:56: cd:45:08:7d:e5:1e:5c:77:55:7b:f7:34:ea:c5:d5:48:21:b1: 71:a5:02:16:50:78:64:e4:01:85:28:3e:e4:b8:f6:f8:02:3d: 01:23:ba:2c:54:c3:72:a5:2a:3d:41:fd:c1:15:60:37:0b:65: bf:23:bd:33:f6:d8:75:03:71:46:47:97:93:ae:bc:7f:76:1e: f3:5f:ba:0f -----BEGIN CERTIFICATE----- MIIDwTCCAqmgAwIBAgIJAOM1grPKMcpWMA0GCSqGSIb3DQEBBQUAMHcxCzAJBgNV BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMRswGQYDVQQKDBJPdGhlciBXaWRn aXRzLCBMTEMxETAPBgNVBAMMCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5v d25lckBleGFtcGxlLm5ldDAeFw0xMzA4MjgyMjE2MjhaFw0yMzA4MjgyMjE2Mjha MHcxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMRswGQYDVQQKDBJP dGhlciBXaWRnaXRzLCBMTEMxETAPBgNVBAMMCENBIE93bmVyMSMwIQYJKoZIhvcN AQkBFhRjYS5vd25lckBleGFtcGxlLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEP ADCCAQoCggEBANa2UwRT6JiRxmqrJ8O6AVPh81YekMlhfnM3NoBJqbRqnzrYCG+6 gmTFhZJBU3El7BiFHJ6ASzD3FrT4Bz73m6otn/gIpArmngrSLwZZKFOetHeKK/C1 xsqvQb7tFxIPNy7puEM6diD96IGRuL8DknYfQNPgRP00x/PU9nfJUlnaN5WrVKcR pRoD+sxxGXLLKTkVabX2WxYi2O2ks7WD7WnZkX8tDK9PxkpKTx2j3B8Q9HfISOWU ZDspPZ0WDNIwOkQNpIcEBITs/RmCCHe1d2T0zrxspcG3F36iSt4oYkBePXdcmgnc fqa2ozTKc6TCQnRO2FItmE8obomTfjQ76zcCAwEAAaNQME4wHQYDVR0OBBYEFH91 O7Iez+/Wp9FC+BypE2PPyQ5aMB8GA1UdIwQYMBaAFH91O7Iez+/Wp9FC+BypE2PP yQ5aMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAET36Oavqb7PKFHc hhTiTeQUnwlNy+kQLO8h7LCMFFdZRVK049v0NOM5tt4M62h429Ah0sFRGM4zFKRN kYjrzLBKk3N1SOhWzinJB3MYKCDhLroPzEwm50XVTGCJ7x3XeqWAYr8w2qy/vvhU 8/yKCRyJLSoSIJlmVKB4UPBGRJ2tlYGDwEc4uEqBPHJJaKKhBMfT6ehvZc4QEX8K i5bOTh5Vx1Q0JV66lWKtRUOxaXDUxDMpVs1FCH3lHlx3VXv3NOrF1UghsXGlAhZQ eGTkAYUoPuS49vgCPQEjuixUw3KlKj1B/cEVYDcLZb8jvTP22HUDcUZHl5OuvH92 HvNfug8= -----END CERTIFICATE----- rt-5.0.1/t/data/smime/keys/otherCA/serial000644 000765 000024 00000000021 14005011336 020762 0ustar00sunnavystaff000000 000000 FB573398E9349E9D rt-5.0.1/t/data/smime/keys/otherCA/private/cakey.pem000644 000765 000024 00000003213 14005011336 023037 0ustar00sunnavystaff000000 000000 -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA1rZTBFPomJHGaqsnw7oBU+HzVh6QyWF+czc2gEmptGqfOtgI b7qCZMWFkkFTcSXsGIUcnoBLMPcWtPgHPvebqi2f+AikCuaeCtIvBlkoU560d4or 8LXGyq9Bvu0XEg83Lum4Qzp2IP3ogZG4vwOSdh9A0+BE/TTH89T2d8lSWdo3latU pxGlGgP6zHEZcsspORVptfZbFiLY7aSztYPtadmRfy0Mr0/GSkpPHaPcHxD0d8hI 5ZRkOyk9nRYM0jA6RA2khwQEhOz9GYIId7V3ZPTOvGylwbcXfqJK3ihiQF49d1ya Cdx+prajNMpzpMJCdE7YUi2YTyhuiZN+NDvrNwIDAQABAoIBABa6G9V0cEVeAMuf rEjacnOHkjNGbvrx9+mIKZuwsGbpdktLPLFe45h5E+dkRMnQQsphpKLeX5ciQGQN cO7oVLDRvYIKoBqLSKVKlDGu1EbtoJqapIYJJ66imGn2PJ/rvmKX2Ko9EO3zEl5M p2qInUMlkb4bmhHXOWcE3sXVKINcFSjUxx/EkE/hS4z4gZX1ZFz8r6NmnnSk3G5p yS7JlTx9gIEqIp3LFmgPY8yhjdbQ+Qsde4FU1MSWWvmE4+LT4AicTAUGf61VEc+s gVHVHl9yuOGJYRaKuqHevCMxr8Bh27WpPT+NGdPxVRZJ/kSoDKPdrv9oU99Rtgwp RaanetECgYEA8Uuk/2pqOHQKd83jHynejJSK/B1XxAddn9PHWNw9gYFBPnrxL76/ lIuEAHyjcqYC4XV7dmEpWklWFInV1cBUAGimX4ykwArQcneq9nJXxR5KQ7ofozB+ eYZ1/QvhySJeg+ucsyi99HLFL845aGf4y48VkHD9MKnKMCNwYcytgBkCgYEA48v2 6K70spBv/j4QQ3v/5ovsmvv9xQei5mPZKawKOx6OxDZJ0he6ltGQ6bJoNFFtcC/u Lb/uX/0Ah/V5gurAVQAJU53o2t2Ai32NX80b2lUXi0H8nGvOxW8i95SUWx3dn1yz EBJMgfjH5XJV+kZVUWeOIIl+hPXew+u3XAdq788CgYAobDa4/zfKO05hoaEx4E7D GENsVvIUCfPaSZ00urinEGNAt1HeYMMxfGnhtv+evkbvREIpo79Mu8pq6GhlRbIM 23s7uJEFBwrCkl+Wp7Mid5+TVwPjz8TwUOFFQg9SJarVyMvYi7O+1tdH2fFuFzTr zQ2cxAD2fQs9I0K5b5OFSQKBgQDXM0QiE86VtsAmhslkh4t8aKnwzKiz73/keWWZ 6a6MpVSoZsUcllAu1PI65NFuw5JIzu8LB2wSAHj0+GF/3XgvlOY6uU5XHbSnksfx PlrWy1Z/t6oGuA5SFKkLDbGN1swdFj0PrMnca4Ok7nvtAW7uhY8Oi/YbdA+sNU42 wccznwKBgBswApZRfZKCUD/1Khdz+HmG/YEPbk4Kqgi7a8MKpT4No0hjRsoO1HV2 WggtvBjzagHkzZNkjJv+WvSU0DHk/JQnWIZFJd+72ZGR56neq8iXIQ28LnnGhcvk m0YNZzB8MCvD5ZztH6GU5ecPzO+4Tjkruau2an4etLSs60ogKy5u -----END RSA PRIVATE KEY----- rt-5.0.1/t/data/smime/keys/demoCA/private/000755 000765 000024 00000000000 14005011336 021044 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/data/smime/keys/demoCA/cacert.pem000644 000765 000024 00000006076 14005011336 021347 0ustar00sunnavystaff000000 000000 Certificate: Data: Version: 3 (0x2) Serial Number: 11236924883769032812 (0x9bf193a560cd006c) Signature Algorithm: sha1WithRSAEncryption Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=CA Owner/emailAddress=ca.owner@example.com Validity Not Before: Aug 28 21:19:44 2013 GMT Not After : Aug 26 21:19:44 2023 GMT Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=CA Owner/emailAddress=ca.owner@example.com Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (1024 bit) Modulus: 00:be:cc:62:70:bf:42:ee:9d:f0:05:04:2b:05:46: 4e:c9:60:6a:b4:31:8c:a5:60:25:79:05:61:88:fe: 36:9e:63:24:bf:33:91:6f:6a:90:27:81:47:5e:2f: 49:54:19:c7:02:51:37:d9:ff:0b:9b:8a:cd:ed:7f: b7:6b:bc:0a:de:e5:c8:32:f7:a4:16:51:d1:3f:a4: 02:96:98:09:83:e2:ed:81:19:bb:e3:d4:2b:f1:87: 97:03:08:05:e6:f7:65:c6:90:48:9d:75:07:31:93: 04:6d:09:b7:0f:df:fa:f2:b3:ff:e1:44:f4:18:03: 4f:59:b6:ba:d2:36:8b:0e:b3 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Key Identifier: 8D:1B:2D:BD:BD:24:E8:19:62:AE:4C:C9:2A:58:90:08:1C:D1:05:2B X509v3 Authority Key Identifier: keyid:8D:1B:2D:BD:BD:24:E8:19:62:AE:4C:C9:2A:58:90:08:1C:D1:05:2B X509v3 Basic Constraints: CA:TRUE Signature Algorithm: sha1WithRSAEncryption 7b:f5:8f:d2:b9:44:34:fe:91:ab:1d:52:d3:10:2d:23:75:05: 8e:17:70:be:52:11:b0:8e:ee:f6:33:50:7c:c7:82:f3:c4:d2: 98:90:b3:a6:ad:00:33:36:dc:95:f4:4e:45:d2:09:e9:88:ae: 88:a2:72:e4:75:95:7a:78:31:16:34:a3:50:e0:c9:25:7f:65: 51:d4:59:20:23:d5:3e:35:79:cf:ed:3d:3c:8c:d1:79:b0:99: d3:6b:99:ed:32:c5:29:7a:82:8a:98:cb:c6:95:c7:52:59:7c: f8:1d:fd:18:b8:ef:4d:1f:9d:5d:09:b0:eb:68:50:ed:c0:21: 61:eb -----BEGIN CERTIFICATE----- MIICyDCCAjGgAwIBAgIJAJvxk6VgzQBsMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMMCENBIE93bmVyMSMwIQYJKoZIhvcNAQkB FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMzA4MjgyMTE5NDRaFw0yMzA4MjYy MTE5NDRaMH0xCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxETAPBgNVBAMMCENBIE93bmVy MSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNvbTCBnzANBgkqhkiG 9w0BAQEFAAOBjQAwgYkCgYEAvsxicL9C7p3wBQQrBUZOyWBqtDGMpWAleQVhiP42 nmMkvzORb2qQJ4FHXi9JVBnHAlE32f8Lm4rN7X+3a7wK3uXIMvekFlHRP6QClpgJ g+LtgRm749Qr8YeXAwgF5vdlxpBInXUHMZMEbQm3D9/68rP/4UT0GANPWba60jaL DrMCAwEAAaNQME4wHQYDVR0OBBYEFI0bLb29JOgZYq5MySpYkAgc0QUrMB8GA1Ud IwQYMBaAFI0bLb29JOgZYq5MySpYkAgc0QUrMAwGA1UdEwQFMAMBAf8wDQYJKoZI hvcNAQEFBQADgYEAe/WP0rlENP6Rqx1S0xAtI3UFjhdwvlIRsI7u9jNQfMeC88TS mJCzpq0AMzbclfRORdIJ6YiuiKJy5HWVengxFjSjUODJJX9lUdRZICPVPjV5z+09 PIzRebCZ02uZ7TLFKXqCipjLxpXHUll8+B39GLjvTR+dXQmw62hQ7cAhYes= -----END CERTIFICATE----- rt-5.0.1/t/data/smime/keys/demoCA/serial000644 000765 000024 00000000021 14005011336 020565 0ustar00sunnavystaff000000 000000 8A6ACD51BE94A017 rt-5.0.1/t/data/smime/keys/demoCA/private/cakey.pem000644 000765 000024 00000001703 14005011336 022644 0ustar00sunnavystaff000000 000000 -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,8580147E208C5674 GTz9b2WFdP7gNjUWQnhWqq2o8bpYPbmPTLSyefUfI2UxL0bW96VBKyLpx/FO7Zxr itfItZA4A7hG+CJLa6pz5C4/9onzHeihhLLDov3pE1hjZwwPFs1IHM/q1KLU4tK4 yb/Xx1pw/3L1nlvWy4CQ/F1pmHG+akQNopy2Ru0XWLVw/gysmff8GW94Awx5MyZd 81tvuFu2U2BYdPbC/Zc+hrlTdqG2btgdll39gjRoNvLbA4tifLNy264yOS71lxF/ rOtavqzCULo/cTTumcZzbMnowjpdrPliuGg6rox3xc3zFjNfogu7okH53XtOZClQ n3/jjqI1LEUhOC0omUck4q3XbaCWGg6X/MUL8Fae+jDUs5NISt75xVs1uJdU2DuB xUwtgzJCbt5eovbczmoKm44nY3TqsITG+vuI7qim3wds8WPbM4lnz7fx0AbHYOIK ceCxDJirQRmblImJybPHJL6uuCo91Ahx7NmLcGw35QhhQf/EfKPJyh4Ih7+Cn2il EGW9RWS7hl9JSCOZs30YwPQz1bgCHIt0+31WSK4hbZ/IyPnDrMY4XNVCeWxX2xcF y2VjpoW305Glu2D522n0jUe/YJGHBaA7ijQkLpw2nL0qstlkq/2RoGZaDm0gUCUG dNbmeQrOF7dJtSKKjxy/DqMPw+ymn/YCXVaCPvIEuqHyFKnUNJ/ak4vnAeV7Jrhz 0OlyqNR4O/FKjf4pgsTHqodTQrxHA2d/n/Evnes/TevnIp6sa8HpkMcJc2DL9hKB aIWFQxGynI/S9juZXSKdTOMcUbSsicVELzzk+spHlZ9xKpuBvJvWxQ== -----END RSA PRIVATE KEY----- rt-5.0.1/t/data/smime/mails/9-signed-encrypted-binary.eml000644 000765 000024 00000017037 14005011336 023777 0ustar00sunnavystaff000000 000000 X-Mozilla-Status: 0801 X-Mozilla-Status2: 00000000 X-Mozilla-Keys: FCC: imap://sender@localhost/Sent X-Identity-Key: id1 X-Account-Key: account1 Message-ID: <4B709ECC.4020500@example.com> Date: Tue, 09 Feb 2010 02:31:24 +0300 From: tester X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0 User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1 MIME-Version: 1.0 To: root@example.com Subject: Test Email ID:9 Content-Type: application/pkcs7-mime; name="smime.p7m" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="smime.p7m" Content-Description: S/MIME Encrypted Message MIAGCSqGSIb3DQEHA6CAMIACAQAxggHIMIHhAgEAMIGKMH0xCzAJBgNVBAYTAkFVMRMwEQYD VQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxETAP BgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNvbQIJ AIpqzVG+lKAVMA0GCSqGSIb3DQEBAQUABEAS1T2vHU5laVZc98o4TkRhMbMRNq/ScHm0yBUG 3ibvOwes56fhE65qZvzpKlpv5dtl/7ZXn99GHxmybCyUN1tcMIHhAgEAMIGKMH0xCzAJBgNV BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz IFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBl eGFtcGxlLmNvbQIJAIpqzVG+lKAWMA0GCSqGSIb3DQEBAQUABEBrG0OYfAeKnXGrznADm/YK zVh4n//J85fRJhKOEgCjBmo6nUrB5oklBe9nn7/6B5/75+sR7O9yVAlSAx4arlzeMIAGCSqG SIb3DQEHATAaBggqhkiG9w0DAjAOAgIAoAQI1SyfOlDkrbmggASCEZDOvqqmIEtaqMi6LwQY W2lZzGx7RhdT1cRbLA3z684xCAe18T/9iI36HH8Kn711eBQYbSPOn6CRnEbNckP+u0F07fJf MX0bdx/HeVOc+KFqHl+JYIkzD91IVqBjK6NsbCFz3ratZTthLgoRM/oj2T0RCpqf13AhnfZ6 Pm+HoeQBr80XfhcKAhRKrSZRONjQ+rcljh8fGJiLiqu+MjP1OWPdkTx/zO+iMAM7ZCi6JYLh 5+cCRxX8MqWCmOJyNTd3UGgU+Fl7XQcpGYHKeP5LdrBCv+a/w94uqCtjVbAPzBEF4hilxiFt 2JyDMIksh+spaIvQTaY5OhsEg1yg3JOSzwEYoo9qRS2RbBh9kSoB6rlTcQ/1TWr8rU64G/OR WsYUeMIaVJh0FPhm58//NUe3NbPl8Np8lOc31HWwUG3b4vX7VcaF07MzsRNxPQ199Cv11Rqz DtJyjuS3kMgIHcl6oFixsJ6/jGzPL4FAyYFHfEfL4+iuBQzYWfZ0dvNbCDsguGo9Tn7Hd95K 2JqKkw0VwPHFPBpL5fpDXp+5KPKRKLsRl6Vz7WK9xJ+4J9c5H1Hu1t89j4WcG6bnxgLmTN4L n3QKXLgc8MHjRA2rWrOqmK9s/NAUcALqiBCD3LLFkJ9IAglPRS0d8Gqgc+7gQ+x3KWU0/MH2 rpkcYoHAR8gUaQ4ub+p4DrBunF2sT3vlEPcbDY/nxY0hF2532earCX186QuaQQN5R415mjCf cQ9+sQ8UVdbfYZd9iDnbDa24dR87h5GusfvVqmkph2LcnUTGmJFzGEfktCmWA+jqukS+43PI vvnLqYlftnfjrhybG15XwQy3zxJJ2vX80RpfQfPhDMILOlCM1axe10XPys4814qIWrvq9HGK g45iwttLOpxC6nQTj3goZxGRjLTKTARDnVN4/WNOh357MsGwt5xXcNDSxSemyE81u/Cg0m3k pbCOU9AoY1hTSUdZQjL0UqYo3DSicUSVd9tjcUru83S1O+cM/a4CLSXDNu3XSbu9in2+JJBN QX5Qabx5Fat3u2towEnYPqrVFDgwCIXVS/5DlpTk9evbYNqqms42dD6ahz2eYiwReqJ3gYTx eNZ/MXEOFTIMCMajTo576G5Wpg8LcP+2CCAR/MeY+FtijT7jslPBoLgVUy/yTCuo2FsSN6T9 CFsMZjc29OO+xPgEq33vv/jiiPYVRvp6jiB5Y6rv0D83Sza4GhpNDx5TQ6ohJwTIyeUjnMt0 ogdvvo2Wj3YokdTrp7aJlscYH5jqUw/4SghveQaosipJ1x18CIFWzhdLrqFxclaJobXhSfBw oxwPKSVdE9E2iFTdCQBWmS78BuMs6CEit2Hhg3zYMYm1gYSeTQBQ+9Ktpv8im0YikOJ9lFfG noQrE+pDJxFTAfbaGNw7oYr7ovsA0xECGNeHjKs88pHC8jPOK7FmTo7GsRYR71Mq5mvZef7C Mbg3wIEd2lfTF5oUrym/1dWj7QCeBFwPXrhcD5lYFISo0rIUk5DjsEuLoS/c4PQa6tgSKRmL 35O+67dPYbCL/e6XTf6pmcxawFGa3O1CDCJCUQsKbZgU1HlMHjdGm9nHPq69BiUrEl1hY+Ik +R6vVby8Ki29Ff9Tgza9Lzm3GbUZ7EEg6MpKOBcyaWsg4JJoH/ERKIWlfBy+99JQ9tZ98BNC MvwmHggpWAWJ4Q6c77DAadhkjVaXcCIJnNsSDVfhtvy0VXfbrjkXWboMqBEag2PKfDLnS2uQ SEZc+OalsUHTUip/b+NUtBwhViBYVyj1KSjEFRCZXuE8vq8m7sDnOR+2FIk/u71G2/y2q7M8 euJBBfjWmUU4IFs2Bj74odqT/JwV9HedWX+Z7InZJZtLKLPnMos3ojky3YnbKEWMoNFpimp6 LoZdJUB7PbzFWEXPwy1MkHd2joOg2WZ2/Hl8FU+enJ+rQbXYPMTAz/MSLUOe8b/JnINBWRNq bRpcXsHdrF6V9afyJTCaitSGu4kvoGfWNjpla45Se1XicR7Yu8C+niTsb187Irqamza+XTEc CucoZp2RvYAdMd4jx0995ElGCc8tPT4VY1CEVziY/Bvd5FLxy8Y/HEG0Z63hbrTfxSp1jp08 WfLf93p01iXOZi8wH+EMfajQ415jHDCMK/RY9JJWJFKcu6w50l3EpHqJOqMol0r3ZkXRGy/2 gxyDbwizs/2xqsMf/zQYP2SWroKEMdaapaJ1HMyVKWxLD6/yyn9bAW5kMoolcTZrBILc/+5q MxYN8RVr2pmt+WIXnQxhcVh45n9zGl2UcA3m/MgIlGrOQNzAfiCQXWz0ejK3mp/Bl4ljve90 SVKnBd28HNDLU+Z92ISt/mWC6JCK3zGDl2GvAcDh4IYPveKNd2sRrCFqPwdcag2qMdVpNaoN 9Pw6MduH/ei9HZqJgYpNOKsI9YBq7rotbWG6Ioa7Vvr4wbPLBR/ZPsEB8i7sEtNJJR/eytao l0uZJ9KlPMAWG71DP/a8aUQMJV5leL6Ct5vuQ8GllAAU7xZHbUEedLUVCR1gasuSDLa6dJRN iljb5zYdod0DI1etK03N2ZPc2He4Jc+rcjq6VmM/EUro6hX4xWjNnDkpw7d8dWcjVDOO54Dw BK6RvVxHW3/QknE1l+Mdja+SKqxV6IWhFr5YJATSMlJWLRJyVpBLP5Ba5mcPdIk1wyc4Xy2n MOGBvX7qfuR4osNY/Lpul2yA/MzppY+KUGMs3UIBEKKY5IrTMZLfqV9lMJeUlgIXul3sQK9w KIv5rr93QGmbTDklfy+7vXi+MLL6x0aSgeIHjdx1FNSbbpG+rK1q5dFa/2iI/JrloxeK2sDh XMR0i23OZv4J5wTGhZ10KR3einN2dVzGiVmTsxYC1oLmulwOOFLPR8dTmQBmTPN00cCTuf4J UTE5oNsGN9SU2etuKHIBmsMOG/9N8qRA2PxGMMYXoJLKEBu6BqTBz59bF1PVoFG2cF4ORlTd rKcYMSPjpvvZCswbp5enGOTEcph4FYCkGc18YTdkh0QnKzT2GC+Ta//NX1ecIp9fgn0r0Qxo xNAqnOf8C66QBsQdM8lwOnqYLvVLOmAkr2/EhocVSylzxith8TlAFwoUHN1HzN6mhGi1MotU aQNfutSPKUPjSwS/sXUmRZB0YUcWa+ZyZw1mPgjvAThDOIEAWci+F2zreFbMaYyHiY8di6h3 MBWgJtkfYEQo9A4bQ8bmbVKvwpQ8vMtGlckMvUgZQU/+kcQ3R9f2ngHseEmnT/MOjiv651R6 m9XwT9hb5qu31OFQPSfrCZxKyK7enVI6wnGav13vvq2//i9WKJTE1Y0gj1kfroMOwo8OpcUD PaqBJFiozXJSwIX7kL6ElWMxs0sgQlcDK0AcU2b8u0BJWWQCFMz9cua9DF6t2Qjg4ZGrnbGX H9iC7lJnDnBmdc3mjrVFKdakx1ElJufAxrb6Dysf+OZva9wZvA80xCCL3Z6AoIKqC9QYeFg9 0lbgsTkJOZ7BOI25O8vf6AJHMvI/ZAwxtMf92fnL1zEnGPq0M1gQoFRG+e5pa440Du7NwktC b7p4Wg/Kcz42E8frPdF7RXp9QHnktnpC4q7/+4BYaSCPkfGFWQL5nPtzJZaujlHI+RV8ch92 Q6x1jEpxicawAdjAgoERojl2kjvgQeB9wwPzua8tqwwoAtIAcEQgfdLCiMWgJRAzUNFgd7H7 ruu+h5WpPCCFc6B5j/2Ahw8ZcN2uT+FZ2lCSSbmZ01J3PKeSzv8jWT9DvfkJXv7wUTOxfyB8 LPsaaQ7NqQQewSW3eWk23U+frd0Mohu11KXxWAB1qFxSCDgUOKn7Fe7zhI4HKCZpfhzmylsm TjMNia0xySn57rWdGiUFunHNGfCuKEVe38/iamFHjS2X7CNoVXwRa/cCtGC7ZWa82qYx7hKT fghbAMvQnDW2IXQPhwwxJ6pI9tanpswB0Dkf1wJwBsdhFWFRBxMGHvHl/gcgRNU5Lu4+Fiih sieGaucaG2SAV6lYINPA5ycc9HMQpJJ9byy6Ghk+jPRl9610u3YkWY7+IMaS1wZMdHdDeaO7 a1V2IDiTOgIrvtxT+p3lNeXS6GPP+krWU4K4f8T+sQITeo5vtQujDVtxgqTo7rulNNy5Yv5w g852LqjpnQgFmKhMopZbwuHpTpIuQ93Z3N6kP4aXaLNySx/wJC7tsOfzYBI6EDYJNvSEtYtf zNdECVpFVrVegA48uiMh1FoxDD/QYao9+2bAgcrjZUiN4pKLCh09QTK4xW8HtZLrM7qA9PB+ rGb5wqZyCgyvEMG7P/b0TsBO9tKgStTv6vV557GxGhno36c40auAaMMfGqQzFQ8y1qJObXek pz0Qjsk//hSGoEl0o4vxVZOMbGzUWF49sckkS3EdEjeOKlT8jWk0phzrAGM13KJci5uxdRD0 Y033xZY74PQ9XXLAQ1/f3EBmPFEmfeTNWMkSqDG7/ZDV3tA5rA7Ys2ay6uzk+Fpd7oWsvkph kaeK+0NHYtJuxrIevmT9P9hznLK2QXWE0fVtHlmzr7JPa6/a1QKxG0Pr0ydY1weIV1w0uYqf T/Sj4Mghkws26gCCaR4iaAIEcBvJ6vENCSaDFnL+sxw96IthDy6ttbY2jF34BaIwpAdlu/qY gdr/Ii6EvQZX5wHolPFuz0jwJrndmBTuH9ZVki3eEvS9qFJjI/OHnhW2BegaFcgQcMnx6x07 C466Ex8Cs+W1AGoDCYtVjQMAmkLHNzeGLr6Hszf5MIUKtFG+IN3CKL8hJKtGbZ5szQrRN3K4 gDqHApJ4TiE+Uhgpjrq8ik9huJxIRcQXOCZVL8WDfCkybDWy1sVcYgzqf/0WhJDXeeE/ncJi UXBTv4tS46DjOlt70zYfs34gPrrU2aOhy5QF01RnA4HYoEGB963A4GmE+nZv5ezu94M1k1FG KtLbW/OTJQ6qKVMZSadUyOFWtxolF7SfUzGHIpZuzLlBhyihJ/x9evMR8yOPk0/BFdFBRk2R 2lRDMvo3J7czmeYeZjUdb8Mxg5EEPKmi1TndzZ2eJ55hdwpR+klE/+FL0l9TfiBfb8/rYv2M +DYIIsGDUDafialHmVp06dUuxBw0kbjjzB0TWIdX8F6+6Z6c2dFF7Cfy2EhN/834hu5owc1q Lxg6GFoUPI2olPdTaK4iFDT7kXHYdjZ2b26Wy6piPeyVRQg9sJzzMGfCnNM8SyLi7MtfKsSQ bsP3NzrSx3+YMF8nXCpNrmk3MwSwC614H0euyDxckhQe3DY/OAGxhFz/uHKy3bbydY3I6WuX ix+CbovWufHrlXJv29qMT0as5BpXHQnXcEi87FCcFoYCv34J3TPrUQRkW/UKR/OQB9ghWyn2 3nN61B+lpOygnTqCvoBYs0ZF3GEFP/fpqNYRJZn7ThnIOOwTcbp2kawWMmcZ1jLxvpT1KnM+ I01c8v+Nsu6G3h/HhI5aOc1qhZgzNbo3BMPScpf0rMvwdAxoXd9E/Z4JX58v33m64fImSg4r +oByCxR1NjPFZdc9NeC29hTLsEd6pzC/ojsQQqpjw0ezKDuCBxgiPep9NdXoqrS2AsJQqeDI nim5yf4EDteUlS8NruqHhSCHfgi9Ofn5R84dWn1rGTZCZdxLKWa+BlSA0AHiJSHv04Ozj8H0 GrZvM7GEXATNYN/cMr9Hi0BQTZVDzjXnCjpRqjvuY6K1TbrxwbAjMfGJyKSJCZQOhgGZySbh iB9WbqH2O7cEmUQTgzFnSY5TEVuJZGxyX9TSHUOLCt+KgxATKudGtjWX80G0+058BrqRHpu4 AGqJ+nBifnxMihVcm8fZ6XbC8r/KWgbFt7laXBJLal0i847kYnFYwGGOdhsImfiCrbdaQ99c BLqjGxMyqHF9p+WJjVq2GleBLTZNADdAbi8ILc+1aNAWrCvgYC2CXEJIyw5xFzLByklP1IY5 VJl16vAQxKLzOzQET0xzS5Jic/d6ponxjcBiXLsTxNnS/DHXkPpDWzz+2Fy9p7sz7NScH9+3 qwQIeNe1tczOd/sAAAAAAAAAAAAA rt-5.0.1/t/data/smime/mails/7-signed-encrypted-plain.eml000644 000765 000024 00000014553 14005011336 023614 0ustar00sunnavystaff000000 000000 X-Mozilla-Status: 0801 X-Mozilla-Status2: 00000000 X-Mozilla-Keys: FCC: imap://sender@localhost/Sent X-Identity-Key: id1 X-Account-Key: account1 Message-ID: <4B709E37.3080003@example.com> Date: Tue, 09 Feb 2010 02:28:55 +0300 From: tester X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0 User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1 MIME-Version: 1.0 To: root@example.com Subject: Test Email ID:7 Content-Type: application/pkcs7-mime; name="smime.p7m" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="smime.p7m" Content-Description: S/MIME Encrypted Message MIAGCSqGSIb3DQEHA6CAMIACAQAxggHIMIHhAgEAMIGKMH0xCzAJBgNVBAYTAkFVMRMwEQYD VQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxETAP BgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNvbQIJ AIpqzVG+lKAVMA0GCSqGSIb3DQEBAQUABEBn4w9xFhp6LNJPyt9G4QzJMyNIHsRVgJRb5gnw TjP9wid5D1+bi6FKg4ydAmC1xicBtrUj5P+ZVwZHEnPKl2DqMIHhAgEAMIGKMH0xCzAJBgNV BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz IFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBl eGFtcGxlLmNvbQIJAIpqzVG+lKAWMA0GCSqGSIb3DQEBAQUABECW3Ck8XGyHghaSy3AklUgR hgeyhd/eARl2L6MZDLZX54xjSY1rFpgXreuM9Ttscp9lWvXv7zt0cYO4Aq178SHmMIAGCSqG SIb3DQEHATAaBggqhkiG9w0DAjAOAgIAoAQI6O53d4iWD66ggASCDiAtaWyrM3VPhlRupSYC oQQfwOeYYGLUp4s44UgdDiKjtPAsDdyrwPQMXI8ETFzEZp9XQ8bVKVLJ5c/PT+7LCqPtfihE 4gqXABe3qePPKlBYZhJkmfLHRxU096JUviZK7VjRBEKYaRD+8xOqBDFipedAPyvq4GvzT631 PAAgRBqLF/i+puFUlbd9RPM4l6aSajnM/pTrbl3IuYKFiWyJ+K8RlD2rDcEw4fJLMr+/2Lx2 uIL0VUAz3VDl+ja7bfo0XfRCmpgkf2utyZLlRSRbKuyaNJ7nGFK6m54UJYO7FD5sRKHiLk87 poxmuZCvcamnoEiLManXxzeuB6UcmCZvvGCoVz8WGgOM21p5DLdmaIguq38yh4F993em2Lrd +ii+dN+ChPdBdq58CqUovqj4Aic96i25ZGXFJIh7hgeVkhulzYuaduZABS2yXzoOcBxsnQdk Vfb4lTTsdSrONStSmSsA9wZg1ksagPHZZkWLCTvi1GBQkv0PE4jo4AR6WdJzWnxA50eOoGIZ F0OhjXJKTtQCsmsSEcfTEcAnE7zXzDNAYQ1DpCGvrCMUD2aLdocF2J/qIc0NlKhH03zjBwpc DMYn54K3JD1qH+4vRYpk51PAitI0DBlI/iYAzoFpCrx+gsckMccwm03J/VdgXU6iEt/J3+Ma hCcg6mpcBFkhL/MKPb3a4sf6KOXejYNufoGV4J3D91FeNh6NXEr0EO5axvye44afe/0kTZ+k GFMe8IgURVzpwEt+04nMjqj+xOFUNgRF7sG9i2jFQenAaj1uz1DZtkeK56WkifEZIUNfPdII DFcBDBM/Gvpg+oRf1aQNckZyGTFc5SYUFZmrVdbN5EgZ2NI+yEl7eM57+bWa14zUk6/0nuRY 0PLsyu7iUQB0xZRSL/WI9mkPB42EIiXOwyHquQSv9qGPFETOM1CBvkTtRwmrTQFfOPRYME7T 6+MC3r/Xf8z4ZDxmkxck16U0/r6GHGszOcmTlFE/5nuZbeTegU0nHJHR81Es4i4oP9JPDYdT kzQS5jk+ttBWq9gBvNFrNbXZ6d4F+61Wqy5fFRcCBM76l2cpt+PGREh7ExGDAg57QTM6RUkg g2AG9IHwLvCqkaF5u1K6Ijy10n/BXQ7Nnf+myFgxdZwe2JX8BQeRgDZBNgg2EhbzMNjfkbX1 s1JW5kWj+CjdzHIqSpa4XJcfGhAkTdju7RTjjzArMGgqWL5VveSZJThBx1hoggkPeWxQBqEP wWflxbp3QTOgNKNOGt1p0++fkdkSpQmdCnPYnMgY/PXDuW9My1CSq5NKsHWdiAjwR5UWYEat BLT1yDoV7Ofk32304k0DgFXC/Z0zdbXddrL31JW48GrOsU/eX8fGZXFthfKeWgH28NJvqaSg 1Lyfa22Ssun2e80ieLoQnYBkN4XkX0oLClpr6B/ig4qdJVRn5D1O1HLcvEcd+zi+gfToQmxf UR1VAaeZiLtJ4xlhWJAYeI60xhCt7vQoaGDtybLWup/oFZG+e7zWV5UKpktrhf+Uew2F1+P6 8jRjXUckkQU+wqXlZMXoSrsL6LZHWt9NLA0CNeiX23L+A0MVqjRRyJwUp8M1FvU+I7WlYL1p xHZmy7mGCbyTBKJTPt40U/DAbEQBvKZWVSN2NqmoJf8q/cpwaN3IuVJl/LWOuC6cXHqjvF/8 PvWb9QMqgMRMS8jOfUc51+XV5Sh1H5zod5rpTRRKMgnwZmuhkXWPWNjIMfVHo0Rl+mPfMJiy PyXjPMYZd9TzoaaFm+pPfMJxa657UmgO5Wh7hFLNH6n5wz8RHpifLQJlIZoDdV+sixnzjZok WOsnKji+2TPbbx9eeBBbap8FnsHC28tNoVRuT5JqL3pu6/df+/MetthF9YnEMBMt+uZBYcnA +j0Wyh6kgW+72jG8TtmiwWRFnRR3Qv9D3s9EplG6oAV90BBsorxaaBUgHc53m0d+phLIWm5k OTayvoGkwj3GubRGeKtONC+hdZqYU7Jsh1zOXqEn+GvRC3T/R+G7GxKAoAkGqVjPUTOq8BHH QIwpVvcac+fYNOxHVRaCK+yieEXLkunH1Ty2tVn3wi4OWhGURu3aJDhM+a/Qh9ulCYJjzeOt XM8/wZFElmg4+ih+u4wiOkgTF5Cv7EpZS5INtdNooiu/fYPCdaqhUKR7Xa5mOdoCDmzI6XRD JKnXCUkiOqBXdv6SnzD7d7OYWJrul6FCCErobU059PhRV0xp5rLgGnrJE/Q28F2SFN93SOQF k+imZHbpxGwoKULrORzTGQb7KUNYZYTxqEtMUErodMrw9y2tfAufNAWNvEn/cGTsHhhJNATS qgoeuFxSmvGTtSunFr/mhTcy7/Gg9eZqEdpJjmywKLDjefCUYWRs8dv9mEnhoFLrXv1/NmMo aPtJ9bysI9NpxP+zVXz6cQRCVO8PTZOYrZsVT7Wq/s3oiArY29hxNNRJT89BzVO9LLgEgM8h ApV5LczVFmAd+hKHnW1/+y4uYhqsXlB3OhcLwleNFlAV0hJqd4G24yGNV/36+Mp3epZX3Tn3 QLYn55EkwU0EV7Z/KzhlpUYbIqYY7V+mrx+YlW8K+D1b8FIayMY0m8joQFbM/NWyoVjPoSXW r1mQlmXlMhDCnaow3HtWWX3kIvFrg1JmmyDdsdi2v8W8l+CkAmEhMrY4cTfXFw1uBzZI9Ayx TCGu83B+G2wo0bp/UtsxpV8vQRgjm02hwODqLIHWqtTPzIrP3nBDc9jbwBB4w5XKiTAZob+Q nJnCUgwD+2RV8TDzUDezRPbvs5M93Wu4JPFo2pQsOfIj8gDESgiRoaC/qAITsnE8LF9HwTLG 3BL736DFTwHrURp/Er+3j7o+Dj53Fyei+/nMQ4GVF6kk34laQv6VC4i70DXx86J2eT3Dvkdk 2n29KPAyFCE4TLO2vOWaeXSzFSsDbSGwWbum8AzehUB7gpz3TPU16LSrfXY6Wu3t5Gn6L1ai dwyRQu02DXQPZ1QtwrRBRLGC2zHRk2dsAOwhW8D3ua3FYNyBEActet+Ln1W6AAAYWGUVbaBt FzdRQdw8wpHunDp21Tv9EEGR0SbbntPtW+DDQC5HHfKM2bI0y7zfdyacFm6pCwLWF4pbbH46 O5gTIkvR/MfzDWPylmkvqG4+XW6yV/rGx4cRwapAuhGpH6OCDYOHjIN6/murzVTi9frwqKX1 Ktxso1OWANL+DvZZWAVwGnZQ3oR3u6Ng537aQKiBJjlET3H7N/ofe5zlrr16dNjp9Zq1i09h e7/3vrAnQXP7MHrSl8eQYrwip6q5XD+2PGaLbfulxMZueaA+0UDn8MgfwhYRjDmRk7HNIQ+Z R7isjgtt8USEUichKVTI0WDO+708wRi8pldqrPTRJGoN4kmofnLteluFRlzmRejCVYmgC3Kr Cou3rrgSs/aCLkuwa2DcexBhX51Vzu5ILEJPn8eMOM/IzaFGu2RuUgMOV3f9TN87UB/3LFLJ QswdqtMqQIwKWL+8QVbYVo9OqDiY6IReJUZbaPUgyy6A/QIk4U06DwqKOBqDXORWtU0CGZro X0w2Ed92HOpWvXK/fz8Wm5a3fdE+1nDc2ZRk4zFDF0+armg8H9qsmSU5q/qEsMCTlTnl7EUt hwXxDRLu9FzAG4day3VcO+mBE6vwNb+JUBcAr9e8aWbIqXEZLszS3j2XkGfESVc5bAy44YTB PZykK1hYbuTwFFDhizPy6pU38rk5bJNV8c/Dt+bjL0WANLDh6sDEU0nzpcae4nR9YDHbi8h4 DNyEH+tHtXPYkJtj5+jLoyD5kbFFeB0rtywhCnzrsdMDFeR5hnYbf9VnFdIUVFTdD+3FRMr1 Vl3uCMxRA51UUUOE42cLRTOTauYCaqNsbwPKfvu1zbSupTy3H85Tm4iUuZFEsdaBpJIvwZGI dzB3Ug05ARAJBoo49xsuISJcI6KpkmgfO/M3xOsiIm4K+mYKoIA8obUjFOT5KvGAlxz1kaXb XjV5HOUQDtWh3Z4eFX39CkxDLR+K0jPe9Ny0Z3us4fjl0twTpiK5Bn8KriCTo5GG8uyry7W0 hdxjd7fGZiKE/jMgxFtU1SLY6zLnM9ynbDoA4EWKNe6tBHtfB/TES2uxLzXseKly+RfItTuA Qo54LuY4hdExOPPZS0s98moejtWkS/feh5bUAh1cXkwZr0da7ESjHYT/Gua3OthPf3xT3Rv6 ZaQaNOiVMfqCenAnCQX+yi3HgLJA2iWddhre61GE3x4ggpCa/UbLVs4gvlNaQO9bANZDDMwr rk+5fw6paoabhLulMUe+XM1ZUA5hj/rBXkr/ytEDNefB9aKoPUm0ZI6WDtKttJsa4FNV+6Bf SoOrQ/mRAmhaOyGxwCkG+cVo4KkZz1XgcLypnRv/VLB32td1KwmpJZ+hZihI/s4NhO+9BHNT ATu2wsHCeO/e0hzLvKVcRjuzUu/wGzK3r2XX6Bkynz2g1NRLw0dFfmiz/QjlWdDatrmTX6QB /7e4h7zFrYB8YGqzFEoPTVvLoLCilBs563LrgVCAOi8v/lm88ZKBN95G5x1nI6YvSrxqAMzE mSciIlosHS4baea67BNAkRIUg4Bgx+J8vmJIYhGQJSQNtFPc3mJgtOQ/NQtombr4PdzLJda6 rpdeHoU+F1/pq46TqwQl7deB3YPfa8D84Ma0/5W7ZyO9UZ3sGUXcZhJBsxo8rzd8NqF4whag 4wrSWe8NSrigwkzVTc0r3M8jz2xzTLJqSnvuM04fgrCZEsz2aroP6dR4mhbuj5F8JdEZVW3g /xtcSkPsRuRVo8cD0FzQdjSbNG3GO2ITOA2+ZPnh7vBDfAMekmAeBAin2A20Eh6sGAAAAAAA AAAAAAA= rt-5.0.1/t/data/smime/mails/1-signed.eml000644 000765 000024 00000010314 14005011336 020501 0ustar00sunnavystaff000000 000000 X-Mozilla-Status: 0801 X-Mozilla-Status2: 00000000 X-Mozilla-Keys: FCC: imap://sender@localhost/Sent X-Identity-Key: id1 X-Account-Key: account1 Message-ID: <4B709B50.6040609@example.com> Date: Tue, 09 Feb 2010 02:16:32 +0300 From: tester X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0 User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1 MIME-Version: 1.0 To: root@example.com Subject: Test Email ID:1 Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha1; boundary="------------ms010504020705000203070202" This is a cryptographically signed message in MIME format. --------------ms010504020705000203070202 Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: quoted-printable This is a test email with detached signature. ID:1 --------------ms010504020705000203070202 Content-Type: application/pkcs7-signature; name="smime.p7s" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="smime.p7s" Content-Description: S/MIME Cryptographic Signature MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIFXjCC AqswggIUoAMCAQICCQCKas1RvpSgFTANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJBVTET MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk MREwDwYDVQQDEwhDQSBPd25lcjEjMCEGCSqGSIb3DQEJARYUY2Eub3duZXJAZXhhbXBsZS5j b20wHhcNMTAwMjA4MTYyNTQyWhcNMTEwMjA4MTYyNTQyWjB5MQswCQYDVQQGEwJBVTETMBEG A1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8w DQYDVQQDEwZzZW5kZXIxITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmNvbTBcMA0G CSqGSIb3DQEBAQUAA0sAMEgCQQCoOEGQHefNK8tiz63/cPZEXfNLfiF1tlzhfsInO4Xrcpta lAppHYPKxZGyPwRyYeS461vOtRB32Kffi8laFBVhAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJ YIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTz NObDUXiSm5NJjk4wvBiIJGk0CTAfBgNVHSMEGDAWgBSNGy29vSToGWKuTMkqWJAIHNEFKzAN BgkqhkiG9w0BAQUFAAOBgQBTkNtt0KoPyzKULqW80Q0nfIXYCGxS/rFNGJTCEP9Pj3Ergb+j qhE+bnJfSho4oD9fLInWr8Vs/1ljEy37Wi9Ysnc+UoMHAcZOzxmcmvMXrGPCAPMYuSem1RoU Edbw25ve5K3NKQH9OMEhnPqiC15tXRRUzgo8968P5n/HOQCzMzCCAqswggIUoAMCAQICCQCK as1RvpSgFTANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1T dGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMREwDwYDVQQDEwhDQSBP d25lcjEjMCEGCSqGSIb3DQEJARYUY2Eub3duZXJAZXhhbXBsZS5jb20wHhcNMTAwMjA4MTYy NTQyWhcNMTEwMjA4MTYyNTQyWjB5MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0 ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDEwZzZW5kZXIx ITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sA MEgCQQCoOEGQHefNK8tiz63/cPZEXfNLfiF1tlzhfsInO4XrcptalAppHYPKxZGyPwRyYeS4 61vOtRB32Kffi8laFBVhAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9w ZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTzNObDUXiSm5NJjk4wvBiI JGk0CTAfBgNVHSMEGDAWgBSNGy29vSToGWKuTMkqWJAIHNEFKzANBgkqhkiG9w0BAQUFAAOB gQBTkNtt0KoPyzKULqW80Q0nfIXYCGxS/rFNGJTCEP9Pj3Ergb+jqhE+bnJfSho4oD9fLInW r8Vs/1ljEy37Wi9Ysnc+UoMHAcZOzxmcmvMXrGPCAPMYuSem1RoUEdbw25ve5K3NKQH9OMEh nPqiC15tXRRUzgo8968P5n/HOQCzMzGCAvAwggLsAgEBMIGKMH0xCzAJBgNVBAYTAkFVMRMw EQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQx ETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNv bQIJAIpqzVG+lKAVMAkGBSsOAwIaBQCgggH8MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEw HAYJKoZIhvcNAQkFMQ8XDTEwMDIwODIzMTYzMlowIwYJKoZIhvcNAQkEMRYEFMOPwVBhOpsi ON90KfnmXL2eK6NdMF8GCSqGSIb3DQEJDzFSMFAwCwYJYIZIAWUDBAECMAoGCCqGSIb3DQMH MA4GCCqGSIb3DQMCAgIAgDANBggqhkiG9w0DAgIBQDAHBgUrDgMCBzANBggqhkiG9w0DAgIB KDCBmwYJKwYBBAGCNxAEMYGNMIGKMH0xCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93 bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNvbQIJAIpqzVG+lKAVMIGd BgsqhkiG9w0BCRACCzGBjaCBijB9MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0 ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMREwDwYDVQQDEwhDQSBPd25l cjEjMCEGCSqGSIb3DQEJARYUY2Eub3duZXJAZXhhbXBsZS5jb20CCQCKas1RvpSgFTANBgkq hkiG9w0BAQEFAARAU+TWo0+Dn6Os7e1q4GrQqDvSEPcEA9mx4SotzuLfQ/TQdzquucB0967F SMKKtZ91LwT/wfT8cqCADfh0LaTIFAAAAAAAAA== --------------ms010504020705000203070202-- rt-5.0.1/t/data/smime/mails/4-encrypted-plain.eml000644 000765 000024 00000003311 14005011336 022330 0ustar00sunnavystaff000000 000000 X-Mozilla-Status: 0801 X-Mozilla-Status2: 00000000 X-Mozilla-Keys: FCC: imap://sender@localhost/Sent X-Identity-Key: id1 X-Account-Key: account1 Message-ID: <4B709D39.6090102@example.com> Date: Tue, 09 Feb 2010 02:24:41 +0300 From: tester X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0 User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1 MIME-Version: 1.0 To: root@example.com Subject: Test Email ID:4 Content-Type: application/pkcs7-mime; name="smime.p7m" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="smime.p7m" Content-Description: S/MIME Encrypted Message MIAGCSqGSIb3DQEHA6CAMIACAQAxggHIMIHhAgEAMIGKMH0xCzAJBgNVBAYTAkFVMRMwEQYD VQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxETAP BgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNvbQIJ AIpqzVG+lKAVMA0GCSqGSIb3DQEBAQUABEB4NUWl1nJB+cQVXPRmHEj+uxapSKRQ2PFeP+Eh VJHyPpsgf8APPxhS/6s1DBIWE9fwkghiM7JTgYZow42q/tdfMIHhAgEAMIGKMH0xCzAJBgNV BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz IFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBl eGFtcGxlLmNvbQIJAIpqzVG+lKAWMA0GCSqGSIb3DQEBAQUABEAFd/zqPwzjH8gKZoGUA/yY 7aDfJzlAsg2tar47hM1xeSTgJ5JpluYy9V/43oK++Q+3HceI4P+aE91CjMrcbqvlMIAGCSqG SIb3DQEHATAaBggqhkiG9w0DAjAOAgIAoAQIsDsGzNXDhPmggASBiBdO/BdF/SrEjAeIi2is G71RuJ/lcnNlAltdk9lMJLoOxxTaa495lk8HuVD0xFYQueNS8AsACRjkOwgSf9Avh1elFRV5 U3XZrmCOqbnDsWRTr2KEc8K9CXxqY6CwFizaoFlTftpji7W3ATU2+/QufIKYBS7Za3Zq1u7M HLbv4GLdEP1GVPDj2fAECP7azsN17fhCAAAAAAAAAAAAAA== rt-5.0.1/t/data/smime/mails/5-encrypted-attachment.eml000644 000765 000024 00000004661 14005011336 023367 0ustar00sunnavystaff000000 000000 X-Mozilla-Status: 0801 X-Mozilla-Status2: 00000000 X-Mozilla-Keys: FCC: imap://sender@localhost/Sent X-Identity-Key: id1 X-Account-Key: account1 Message-ID: <4B709D8E.1000001@example.com> Date: Tue, 09 Feb 2010 02:26:06 +0300 From: tester X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0 User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1 MIME-Version: 1.0 To: root@example.com Subject: Test Email ID:5 Content-Type: application/pkcs7-mime; name="smime.p7m" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="smime.p7m" Content-Description: S/MIME Encrypted Message MIAGCSqGSIb3DQEHA6CAMIACAQAxggHIMIHhAgEAMIGKMH0xCzAJBgNVBAYTAkFVMRMwEQYD VQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxETAP BgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNvbQIJ AIpqzVG+lKAVMA0GCSqGSIb3DQEBAQUABEALN6in6tg2C0yVmkb0XWJr6qRLrwrJLiqcoamd a3VAyQeHcqIB14UYuHiN6zZA2lABUI1DsjFlDiCEg8TSyJuAMIHhAgEAMIGKMH0xCzAJBgNV BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz IFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBl eGFtcGxlLmNvbQIJAIpqzVG+lKAWMA0GCSqGSIb3DQEBAQUABEBDqZbltMcqBRxIshfZ+jSa 49l6RJAX6HVIBVZRu77rmlyVs2ft18qP0YVgwDPgD5Iok4c1Taemo3Rg7M2bHLwlMIAGCSqG SIb3DQEHATAaBggqhkiG9w0DAjAOAgIAoAQIWcv/5Jc8r/uggASCAqjneNECFsRSAPgwjW7G Hi+zLy+vPBLNfWgyuEAlKGeM347PdUciZNLhiz3D49lfHmVypGOxYTNU0kfeJVTW5bfwYHdS ZmPRx49tNJt08GR0eqbePKZtH0/0BW7LF1//lcNeJchsSdyRvkMB8zvTBhnNVhUSQWumrbda OUqvVpSdqx4SeqbyiyQKI+7AiZ2ChcZX9fA+YoiWT85NVtmgBNMMne0uHgmdBMaQHF4bTXvY /Mg8ew7Vg3TkVjg9QlaAe7JGrgyvSx/H4f+sn6mb68NaF5jGjiwen4a6ThRJO8lIJ30rTlb+ WMqszV6OZK2ieWNn5BQlXOI/ew92UIuoyd5PtDkrLbkYio20KfCLpLbt1tvT8ZG0csgg9PO3 iM3S0PWpjg4axknCYonphwSczsPcvUYZ+y4cIMdXvk5A3byMAQjLPYh0N6F9Q9tETc3HhDhA rSdVRot6JILv/tUs2ISPxJcMlLh8TcHZNchnRUcDg0wojULs49rONZIw8UGzbZi1H6IoGebk 1HBsskw21pPDUjG7LpV0bKFKan0wxE5kJP1Xk5EN2Yw+2EHDE19QHs/ru2FdTjfbtcQFGlT1 yiNUI7UwAPpCPyLoOpfvwYL2u4nSnbnKHCdjDHl4VAre8bngCMTzdRM91w/nydjpHfBbv+l5 /EOKbYPC/SNG3IJZy70iExcXU+WydHdYCW0NhR7K1sCdwDsUpziQMvzlkJKclPC894Yljqnn 83S8G2z3pTJ+SEAabdXY1GmYdfFeLGwnRmegzmWe0wCZKz0m4CabkDX4h0u8xu/C/5gbfU5Y JO1s3iVxzGa1TgyvD4aPvqh9pIQ/wSFt43HtZ5/ReG2ul7PTzOK92xC1c3xpMnXdtLEeRmNx 2KU0Kfk1Si0lBJHf3R/JhwhHvwQI0IeGb5Ho55oAAAAAAAAAAAAA rt-5.0.1/t/data/smime/mails/8-signed-encrypted-attachment.eml000644 000765 000024 00000016123 14005011336 024635 0ustar00sunnavystaff000000 000000 X-Mozilla-Status: 0801 X-Mozilla-Status2: 00000000 X-Mozilla-Keys: FCC: imap://sender@localhost/Sent X-Identity-Key: id1 X-Account-Key: account1 Message-ID: <4B709E7D.8050407@example.com> Date: Tue, 09 Feb 2010 02:30:05 +0300 From: tester X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0 User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1 MIME-Version: 1.0 To: root@example.com Subject: Test Email ID:8 Content-Type: application/pkcs7-mime; name="smime.p7m" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="smime.p7m" Content-Description: S/MIME Encrypted Message MIAGCSqGSIb3DQEHA6CAMIACAQAxggHIMIHhAgEAMIGKMH0xCzAJBgNVBAYTAkFVMRMwEQYD VQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxETAP BgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNvbQIJ AIpqzVG+lKAVMA0GCSqGSIb3DQEBAQUABEADxC8CAlzKMNF0mslS0vildCM5FQxOllhn1/nC DWM3qsFtrFLIy56M3Knz4GZUFAk3cRObg21WABJysenXaqYEMIHhAgEAMIGKMH0xCzAJBgNV BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz IFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBl eGFtcGxlLmNvbQIJAIpqzVG+lKAWMA0GCSqGSIb3DQEBAQUABEAVZIiRyhVjCAOFOZpkGsES yu/zqlRn/AhhtHVwB+9+RpLr+POuaBCXlp8705wOyokMpFbV5Lan5MGitd7vmGpqMIAGCSqG SIb3DQEHATAaBggqhkiG9w0DAjAOAgIAoAQIEhlk2O2sbNCggASCEED/gpJpoTTN4xn+aW+X G+pGKN5s4lp00IyQTZ2yfrUIrEtcekJBSzGPbOTK6sbwN2LJzNUlgWzcblxvIhP+6kJtMQgC 09O5/0BDpXhbq1KpZVizMqRoiHZc9K8X2vS7pZerCIG1FqWr5qyEdl7WMiSSrLxrGWWW5PQB CORILs8vJ+KLkVOGyJJavFFG3NLZrwiOSKlWevVPuRBWQAu3Z35CviVvGb1up0wbCZxx/hcI rnxoT7qMFv6x9KbNT/kiK6KbVIwdUb17YycxFyhihf7Eo+U+4twP12bXMCoGMR25KSHafyka 3u8Y8Sww1MS3HHmfXOZ3sLEwq1NLjuHEKTYQKyo2eG6PHXXEnStUwhuYWtYKkVoJWjCv4I1e MryU4e8PkfLouktGOSzPON0Ju4KPwq1eIWRtTjI/AuhCGR59RHlwiDV2F5lXWCuqVOMD7hpv kgytAkUlkXRU1USSA4GQd6RIzuaxDn/VI2NyRJLqCo97J2XBU067yhgbwvDJCD1Of/01h4vw oSut3jXvLhz4daOW1hbuMtu4T429ye89+K/lLi6pCxxedr3DRqJDw38jbHCBS92zeGMDnmd2 zUbBS8w/fRyLG6ac9tbKDG8fg2qtmJuR0rVKgkd4uw0HxBz2B2DOVcsy5/KoPWXAmTMGSHUh S4Dpxa2NHsPfYn89/o61nfKbwATEqUlPinnVaT8TllHfkSANP1MfFowr03W7e3e9FS3aILTt 8scxSlkJ5Wn50R5shbu5QzZiQtOAkDHf/szhSR6SE5yeCmUuXzhEjv1Lplb6Go9FkPsZcZAy iieQTzidJ/0iUEkrjiTJdVC+jwPJhE6wPJ79PPqYCchJsdDiFrwOWtzaetZUzPrPrucyLxvk FojSDmCJ94XpGhdR+PwVabdeoAIac/bb3I8gfWH6q6e5TL4p01tyWMdCLVEuYJcBK1xZ8KGn Gmq+vYUOdTqLasdZ3Uu5D/KDlRYr1eSNOU51D8bukZT2D0ojEA4t9tXLNus1TAu3kuC39U0S qwXfm71n2wZZF0FUzHpelJbNTGwA6ZYL8Yb483yhDFOJyDly+3siN5u9Q6knmqHsyZ4VE7iJ O/pFOogowW0wn8kG6Wr5QCTd5eCHSehz7ZJHahPTVCeAr2i23W7GqT+Shp/FW9h1Iv2ARlEU QSY8ZKtOG8PBc6M4fvsDiS7LqwE20++DznHAFU5pRxUtRUo6Q5lO7xCoYD0MPgoW9IUgAFaS 5RzGyYmGqFIMFZ9Q135/wA1SM3Fsx9J0oZbxOUCaSfwkpL/MlkPaRmIh9PEGuo1/Fs6AH9ui VEfbHWU6eixY8DY8UCKudptgeYpBfgXTtgyprwAxC3+/jhxcTBmJ5vRpTePOLjRbLZIJQ5nK +cWtW0JvQR2Lm/I/cHtycZmz1oEPqZHVk98Sn6vxqByZFPLTvtf39tSzWaT4rpcTXUS6ybp9 wxZeUkqEztbmnN6jSgSzk+7HP9jSiy+xYqm6oyjgVKIosaNAMKJfy/OHrUW08Mefook7Mprd nOVWMMRVVeLLewPVeN9mBnncocUWXVRvNL4BJ9G2jriEEx2St9JNsUkdTd3cACRvDywxreU4 ubfVzzC9PLrUL1GW5UiR/lGqjD9mUvz9ZL5kR2Z4UyiUqyGAeul41TRPNwyosD7Fs+ryeU6/ om2I/CJTwOq5oLtB6azEqR7C3cF3VZ44Q4IYjf/i/HNIhufFti1owJgG8wENNNQiQuEEMpeo j1fyBmUiAyv+4I1g83wnWTTWPNoHfQ4IWP2TlqdRjv7pYShIYoaPdgdTE5aYactX6B2+Bw0F ge3YyXfxevrhWkObPeyLDEjtAhidET8qSbQL1Z9X2hQZ5YnY9J1zay2JOIP1mqc+otapNQ1f rotU9cFBSI4oUykl+DulmDLbzR+QYRprtTgff5it3QaV0IDWp1oiUNm8PTAczek67o1Ftpw6 PdsWDycs37GfP+KC0wJoSHRyR4Xw91WZth7le1Tuxe0Do+cDK3owP/b7dMZxggeHKrPi9V1u 6HjS1N6SNxn4gsaPZaHOurAJAF42gOiV6O0pisAQy7kj/0hQYSiRZ8G8gIsgJVYPnT1JVQIs dZ8mqbNmM/bQXWkLjBNU1APzhmdaejzenYyJEnYiLfWfcWs/lyOgUwO3sU+5qQj0pN11k2bJ Ma2Auweq018H61jEetP4/aEIfFp4+jqjlKa07g1JdNpq5X7Sfytb6qQm0Mj6eK2bwR9JGFpH CihoWICFEpNYeZZdKUQBivLqep09JBOJYuPXUpeoJHx+tqe0ZFIEh+Ef6W+nm1ReKGZ1YGt4 29noZnTTY7cgWbixVfTcCYLSQTgVNd87QIgqZwK8y3YwmMz1d4brlXUdT+uGC265aTOMtIDH jYv//4new3nP5RYijcPgdh1NrJm0ItoYpjk3/1OQsYy0WjVOF/XungHqblnZfLb+ll2+M6f6 9J0nvXVyV2eqBGkLcVkBT/9T2qMEeChGXOQn0VLeBr7hpg/I8tHRw2COykagS8gIUiE6ymID JrsfT9L9smfXapoFbsazfi5sYK6/4/+YaLQX7yvVOrW36Fq2acAM3P6g0ICKBHiiSn0ob1WF QPZ3K8+Aool6V3RGM9S4gl/tsHqEmDwHmUt5bGn+P4pCB8idppljAW9Wce4Tco+w/CDrNrEW 9nckhopXWpE0O2R6Jn1OWkWwJ+hNKd06HS0eBpMhnmJ1AWGpUR4dh89jCVoNmEvj/ar1QVkV h+hMHV6ti3VI5XjQ+1sXF3Zcxu8vjmf+fePgjrvoBpCxR1psxKLj1lprV5evtqSdKH5tjpnq JpfhtVsQ76/s5HkSknhkz8GAW+SvlfOR9DTx9leR1ZvM1KEW4ID+WcSzQoC2w1MlIdtGILua q2jZsK+Sb01VxxmHSuUn7DMvUo9R/7D0ukTPFOt57ZH7Ipr0S25CSxFLLmgaEUf1LCl5Mfew m89yJQso7SWHMEC10YJhJNkeCH+qMN4PnX2e0N6zDlHFPd7CqxoJJKba53rjOqKtEbudzHuv gvmQCPULmTIp9GhIS12y+jS3LkW1l7tbHHBJtQ7NdzyCqv74JHzOlxkawEoLcJDRdlsekOg9 oHVSMRNVTvr8KF5QkI9n5pY+O3iKPVIUTGzvANbYjg99oSvWWSd5gqSSBZA2O1uSVSMcuhkg xNXRfDEL6AObmSULAkG3ymBQkBVt9XUMZKSMuHxTKFTGcJXf+vzsJYSHuvOvtTArV6FMm4gH s+eRpL/BgZu3tXU4MqNXhE5OdXNQJSxC4DkWetaocIYXPgSDbPv6L2oluTC+p9KSQZFkeH7o hxnj+xYB/KvR266kgsyd8tAxlvdUdTnIkhq+GCmvlRjgZv0N+qOinQCqtseUl4WlM+h7940l MNCNhjBlhLI1fD11eJLw5DjzPAamIoJWwhPfw2RRi4I0gKWTKBiWqoM6k04frrnKFLFqTm6x HdIPJxxexW4A9LopWAEotqF/av07+JpAnRPFVW4rqZENAeSz7ao1AlIz4yDTTiqubbP91f0e DbLmIz8X7tRDe/r9WA9OAIlzm4ISnqjazlQl0LvhIGerOebHshJnAskOVLwYhoAKW1bPGRaD PiWzHxHEFXz89bH6lLTLf9DuFq3HGOaXdiFTtqCForOpABXQaczIjjwRY4i/WyXKDDjrPmOl 6g0OjJL4ZP83fryiG9qaaEMnXLJPkjy3Kd0yBVDZBhKfebOlQlNvSsJ7Q4SxOqGBsVecxiWf d3ftb1BUJRroTeAZDfQ/hADbCgLAgOfsiCCgV3E7shOTdJ91OaWrPYNAZiAnsjbGFYDmWJYH /tcYTh4o5v6y+PaiI6YCYOCV1vd9x0B2hQNrCFUloR7j9osBVqFMRtm3nJyTl5/69oGW1tMI EYUBm1QzLOmhnL9+nnDp8kEyjXJMEkhwG0duauOV1UY9Ouy1LgCT34TM1TE3hkvOF4weOF9L b4LRtdEp0SRqyL8cYYlEotMIqwyO2H3/J4BNiEuVGauFhUZhqnbZ4Ll9hebYGmb/sHrsQdC1 5IlNzlTIOFjvhI4VzFYanPAkZTCZzMqiEIqqa4befyG28XRw3SGG8GjgQ6qMCWEIzRg6WNzz CRqEG4eBwG3txivFcysa48BTpiteo9hT7k1zu9MTypEiHiRh9x61PKnyL87Fh26IEA5vjda4 HpDdgblqQu2kFo0UK+Sg52+l4SojTPSrC+K+1phmz4Y+OsVFFrog22ZQIJ684XP6cXjLI3G9 0gkor2iMtYfV/MvEe5EAyw4NexLFpSo4wcxJxKGauyYAf/bzk9XNfrrWTG/CdVBswAP8psJ2 W0pWFh1EVrPEvtU1FXFHHmNmPjFtusPJ8TvY9s5f/xnC6Deo3/gNojgpK4mRYICh8nnmYFJQ Mi0F13r7QQhDfHli8m0hqJ2c+rv2Iw43Ok7b7mBkDc4rOAIp7sXu6qaBOqmIE6fA0NYVI33p EFraKFJNM1mhUcj4c6UKa8mBPNba/CxHBEnN65peleMqiidGsiNLD26IKXISXb2uwFwTWOPV heOecqvyvZ7lcI32Lvqw9D4p4oKkHyJzxzgkcMGMcrw/TBT5gnGdw8cDDD8D3/Noxvv8w+dV OY6JHTnG6d8tS6th6ADuduiP+x4QLYfIvnTh1uK0COOpBYltuB88IsBqJ6DN8v6tAlgf7/+e r2EGJCpNDtJK5zkl8v73Ny5nhMgYUSZG9NlwYI+b+SrAJalZpWRU/vY14cbDSq+u4awzvlNH eATqqMdz6qVH6rl6NJksD+RxrzTxEI1WJVri3xBV1XfSiHm7R0YBGolfsQW95J1gSxY0w651 qjVnSEWvCMClJ5eZstnXh9Vbw1gp+E1Lmg0SG4bxj7V9aIzWbKqLygWrVNNzUSbd4Efi8pu3 bJ0/qy/stElV/+g5ULy+6qAb4zF1cqjfkS71yGJd8pQ8O6PLPVADbTe6Kvh4obYgJ3zeT3Vm oq4AKnfi6XYBLw6CACm/IuMa3zfyFKMw9m8r7uklGDJMa6/y1BPp/5qRMNgjeprX4er9U/EC bkSDeZKpAvXIh9Q7dVdpVGAL+Z+H8EJW+pe3sOurRR+HkXGPgIfjUg/XL32n/OqoMPWr28K1 8ME63jxX5cPjMN4749Y7AbVlbw3yw6BKHpbZl5A56l3mJN9ejeJ9PEBsF9tBtzkjDSw6LV4+ Rr/KoyC6IAro2fWUVy5rFQSDk7jWSQZIyCzD9VNhxMgpElVlwFBvve5tBRIzOJVJCplM0Ybf ovtJB+BA5qzaeMkH0ZbsZ5WLiuodF1RHf6dndDWyF0zP0hqn+DaBav4xUAtBAxSWGvZokShr sCPx2mzmvbuiCuQwHrioZveWsp6RA6pS3AGH1p+NvBzo1rbijBwiFlsgL7VyLUmXz6uxDvNm cnVof0+Y/nUJ30vWUzbW38lUDQOks+ZOhWBDZ4Dx1B9lrQ+SyQtIlXi5AEmTJcbYb+f1rp7O BKknFBBRd8kW7H5ryxyY1wyfYZ7TVhCQD7I41pXg8Mj41C6BTlqXvndhJQQI5iqg/OuwSHwA AAAAAAAAAAAA rt-5.0.1/t/data/smime/mails/2-signed-attachment.eml000644 000765 000024 00000011330 14005011336 022627 0ustar00sunnavystaff000000 000000 X-Mozilla-Status: 0801 X-Mozilla-Status2: 00000000 X-Mozilla-Keys: FCC: imap://sender@localhost/Sent X-Identity-Key: id1 X-Account-Key: account1 Message-ID: <4B709C48.4030908@example.com> Date: Tue, 09 Feb 2010 02:20:40 +0300 From: tester X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0 User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1 MIME-Version: 1.0 To: root@example.com Subject: Test Email ID:2 Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha1; boundary="------------ms090206030705090204050109" This is a cryptographically signed message in MIME format. --------------ms090206030705090204050109 Content-Type: multipart/mixed; boundary="------------090009090000030005040209" This is a multi-part message in MIME format. --------------090009090000030005040209 Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: quoted-printable This is a test email with a text attachment. ID:2 --------------090009090000030005040209 Content-Type: text/plain; x-mac-type="0"; x-mac-creator="0"; name="text-attachment" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="text-attachment" VGhpcyBpcyBhIHRlc3QgYXR0YWNobWVudC4gIFRoZSBtYWdpYyB3b3JkIGlzOiAgemFuemli YXIuCg== --------------090009090000030005040209-- --------------ms090206030705090204050109 Content-Type: application/pkcs7-signature; name="smime.p7s" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="smime.p7s" Content-Description: S/MIME Cryptographic Signature MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIFXjCC AqswggIUoAMCAQICCQCKas1RvpSgFTANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJBVTET MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk MREwDwYDVQQDEwhDQSBPd25lcjEjMCEGCSqGSIb3DQEJARYUY2Eub3duZXJAZXhhbXBsZS5j b20wHhcNMTAwMjA4MTYyNTQyWhcNMTEwMjA4MTYyNTQyWjB5MQswCQYDVQQGEwJBVTETMBEG A1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8w DQYDVQQDEwZzZW5kZXIxITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmNvbTBcMA0G CSqGSIb3DQEBAQUAA0sAMEgCQQCoOEGQHefNK8tiz63/cPZEXfNLfiF1tlzhfsInO4Xrcpta lAppHYPKxZGyPwRyYeS461vOtRB32Kffi8laFBVhAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJ YIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTz NObDUXiSm5NJjk4wvBiIJGk0CTAfBgNVHSMEGDAWgBSNGy29vSToGWKuTMkqWJAIHNEFKzAN BgkqhkiG9w0BAQUFAAOBgQBTkNtt0KoPyzKULqW80Q0nfIXYCGxS/rFNGJTCEP9Pj3Ergb+j qhE+bnJfSho4oD9fLInWr8Vs/1ljEy37Wi9Ysnc+UoMHAcZOzxmcmvMXrGPCAPMYuSem1RoU Edbw25ve5K3NKQH9OMEhnPqiC15tXRRUzgo8968P5n/HOQCzMzCCAqswggIUoAMCAQICCQCK as1RvpSgFTANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1T dGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMREwDwYDVQQDEwhDQSBP d25lcjEjMCEGCSqGSIb3DQEJARYUY2Eub3duZXJAZXhhbXBsZS5jb20wHhcNMTAwMjA4MTYy NTQyWhcNMTEwMjA4MTYyNTQyWjB5MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0 ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDEwZzZW5kZXIx ITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sA MEgCQQCoOEGQHefNK8tiz63/cPZEXfNLfiF1tlzhfsInO4XrcptalAppHYPKxZGyPwRyYeS4 61vOtRB32Kffi8laFBVhAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9w ZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTzNObDUXiSm5NJjk4wvBiI JGk0CTAfBgNVHSMEGDAWgBSNGy29vSToGWKuTMkqWJAIHNEFKzANBgkqhkiG9w0BAQUFAAOB gQBTkNtt0KoPyzKULqW80Q0nfIXYCGxS/rFNGJTCEP9Pj3Ergb+jqhE+bnJfSho4oD9fLInW r8Vs/1ljEy37Wi9Ysnc+UoMHAcZOzxmcmvMXrGPCAPMYuSem1RoUEdbw25ve5K3NKQH9OMEh nPqiC15tXRRUzgo8968P5n/HOQCzMzGCAvAwggLsAgEBMIGKMH0xCzAJBgNVBAYTAkFVMRMw EQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQx ETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNv bQIJAIpqzVG+lKAVMAkGBSsOAwIaBQCgggH8MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEw HAYJKoZIhvcNAQkFMQ8XDTEwMDIwODIzMjA0MFowIwYJKoZIhvcNAQkEMRYEFJXLFU9+rB4Q gPV6QSV6J7blwox4MF8GCSqGSIb3DQEJDzFSMFAwCwYJYIZIAWUDBAECMAoGCCqGSIb3DQMH MA4GCCqGSIb3DQMCAgIAgDANBggqhkiG9w0DAgIBQDAHBgUrDgMCBzANBggqhkiG9w0DAgIB KDCBmwYJKwYBBAGCNxAEMYGNMIGKMH0xCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93 bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNvbQIJAIpqzVG+lKAVMIGd BgsqhkiG9w0BCRACCzGBjaCBijB9MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0 ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMREwDwYDVQQDEwhDQSBPd25l cjEjMCEGCSqGSIb3DQEJARYUY2Eub3duZXJAZXhhbXBsZS5jb20CCQCKas1RvpSgFTANBgkq hkiG9w0BAQEFAARAai2FuYDJS0n8idViQ6y3pocwSKJRg0hrSP1K3GiVyh4an5y1lWuotK/q tziPXZ2qeGSB/mmBf7mwfjPYgGZkoQAAAAAAAA== --------------ms090206030705090204050109-- rt-5.0.1/t/data/smime/mails/6-encrypted-binary.eml000644 000765 000024 00000005575 14005011336 022531 0ustar00sunnavystaff000000 000000 X-Mozilla-Status: 0801 X-Mozilla-Status2: 00000000 X-Mozilla-Keys: FCC: imap://sender@localhost/Sent X-Identity-Key: id1 X-Account-Key: account1 Message-ID: <4B709DE8.9000101@example.com> Date: Tue, 09 Feb 2010 02:27:36 +0300 From: tester X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0 User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1 MIME-Version: 1.0 To: root@example.com Subject: Test Email ID:6 Content-Type: application/pkcs7-mime; name="smime.p7m" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="smime.p7m" Content-Description: S/MIME Encrypted Message MIAGCSqGSIb3DQEHA6CAMIACAQAxggHIMIHhAgEAMIGKMH0xCzAJBgNVBAYTAkFVMRMwEQYD VQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxETAP BgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNvbQIJ AIpqzVG+lKAVMA0GCSqGSIb3DQEBAQUABEBaD8gD14bNN6JP7//sSTSKfd8xt9qWPMhY9bua KfkSNpEiV0NIcdnJLJxMfgQ3ox6+eHOt5PlU67FgdStmUoHZMIHhAgEAMIGKMH0xCzAJBgNV BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz IFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBl eGFtcGxlLmNvbQIJAIpqzVG+lKAWMA0GCSqGSIb3DQEBAQUABEAdwlvCFxp7N94tveDDJs6Q 9hDYS7AMp+tc2Z0SNCQmCW803P+iVkZdEPJ0VIDvefAoqKfZlXwZKYydsN041UGlMIAGCSqG SIb3DQEHATAaBggqhkiG9w0DAjAOAgIAoAQI0HAJB8sjnyaggASCA/jxFMDm7gsDSM2rSbqO +bPNrSatAQTjkYcCnqsExZKCB+P4IwBIODEQ3pbYL3AMF3STZTFEIKxQG4+3lfO3qoIfSWPK HRvabwW+trH3tVsgf+KkdVSfCXBfSV9sIdmXHIAJOSKUZvmbJ8iuQy6543v0InIgNsGgmH9M I+i5bkgWrJHXVn8N0iAH3Unkf0qJgb6E6lvqcbfOpgKzYJ0cBSn9Z00NXo0qw8DuORAeev6i EABlIzv/v7P/7ptvxDzlu+EoiMIiuo9t/aL3lKT27UK1yPUfEZTqevhMwXYzRHi6AQcsZGKJ 33xpaVDkZYZQolQInBIlx23o04K0dm+iAcOau6xpWSvGwlI590MryKg0GIjeNOuy7CkEbZXX P7gF/ExBMM03Xoa93ss7Q3CJHey2xwC+Ozf4Zbny1wvbs54bT//Oin22jdtSOxDIa72vYH+F DKRdqHdSr6RKg4vnIKOxoOys+P4oKbJP66SOKni86XovDR/iVClu0lxHqjJKsW9r1p0O1iOT +aNGMKaXkIa7UEKfVDPdaRUPMszjo8vjqZDWOV6aO8mg52bg/nnbbviVNtToqKweEJA8ellJ sxCtrE/lUerWPZG6d073bMQ2yl9i/2pF8UnEwZNF0vP3hMZ+k8w4uQbAX9BEcDKMKGB1x3FL GvgdzypGzzX/yk5UvTAA1KVT3+HDWCQHmH3fSB+8XyTcnlX4WGJ/oqWMU01C7GWEjj17q2CS 8c11m9/IC0dBgc1iffkdIGMwjHBdbUNmuAmCM0qTMmzrFWrHiahLXhvzz3+X/oqbltemmlpQ GD/v699pN1vp6ito4qSmJ6WOrS0Uud6V9UOPDHXI3nQNBnI+IijHQVVL/H9FxuUYFrKsYupt ssdGrdrKb8+CmiMpuDp8w3QoCfxBf/Y+FykX/rBF6T5MJtS90LQtq/7iYv3sTS9PDqXBHW4+ SgLSOcFH4vmodrP3nu+TnLWU33aboPBvdNzGO2CpOzGRsvft0QanTq3vhSwLG5Nhqiv7XHKE MRphztDRdNlxNQjEyV5q0ER/bEltrMtksFIcPKEWXfsNjbW4PTgjlsjLVBnMOkOU/dX812UK byJW7l0SrWo6bFrooP1pht0IyGWLSxmRMb4CivKgqQuLq+z869I479RlReVBwoYJ+bxoOjD5 5RQUkljjEg/ZCo9OvVVgA9LFUhT3nATjHQJTqJKx6jZxITiy9EfXymX71SxDXyPsM9sdqrdQ SYX217aHveMarK6v9V8smV+LtxKq9Bgkm2ZbUiqZ0/oBcsIIca9HYC7hUxIXkQRvJBcsi7BB Lk7Xm/Egkcc9ZfcnGevgIntzdVYod6LpUodzYavdewQIiEXE/XWx8fgAAAAAAAAAAAAA rt-5.0.1/t/data/smime/mails/3-signed-binary.eml000644 000765 000024 00000012075 14005011336 021773 0ustar00sunnavystaff000000 000000 X-Mozilla-Status: 0801 X-Mozilla-Status2: 00000000 X-Mozilla-Keys: FCC: imap://sender@localhost/Sent X-Identity-Key: id1 X-Account-Key: account1 Message-ID: <4B709CC5.4010607@example.com> Date: Tue, 09 Feb 2010 02:22:45 +0300 From: tester X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0 User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1 MIME-Version: 1.0 To: root@example.com Subject: Test Email ID:3 Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha1; boundary="------------ms020101060809030506070801" This is a cryptographically signed message in MIME format. --------------ms020101060809030506070801 Content-Type: multipart/mixed; boundary="------------060502090104050607070406" This is a multi-part message in MIME format. --------------060502090104050607070406 Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: quoted-printable This is a test email with binary attachment and detached signature. ID:3 --------------060502090104050607070406 Content-Type: image/png; name="favicon.png" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="favicon.png" iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QAAAAAAAD5Q7t/AAAB BElEQVR42u1WWw6DMAwz0+5FbzbvZuZk2cfUritpea77wVIRIBQ7dhsBdIQkM8AMMJImyW6d BXweyJ7UAMnUvQFGwHp2bizIJfUTUHZO8j/k1pt8lntvchbdH8ndtqyS+Gj3fyVPAtZAkm3N ffCyi/chBIQQ3iqs3cQ0TZCERzbhngDocOS4z94wXTCmu2V45LuQW8hsSWpaP8v9sy+2IRZj ZTP5ububbp8Az4ly5W6QqJ33YwKSkIYbZVy5uNMFsOJGLaLTBMRC8Yy7bmR/OD8TUB00DvkW AcPSB7FIPoji0AGQBtU4jt+Fh1R6Dcc6B2Znv4HTHTiAJkfXv+ILFy5c8PACgtsiPj7qOgAA AAAASUVORK5CYII= --------------060502090104050607070406-- --------------ms020101060809030506070801 Content-Type: application/pkcs7-signature; name="smime.p7s" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="smime.p7s" Content-Description: S/MIME Cryptographic Signature MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIFXjCC AqswggIUoAMCAQICCQCKas1RvpSgFTANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJBVTET MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk MREwDwYDVQQDEwhDQSBPd25lcjEjMCEGCSqGSIb3DQEJARYUY2Eub3duZXJAZXhhbXBsZS5j b20wHhcNMTAwMjA4MTYyNTQyWhcNMTEwMjA4MTYyNTQyWjB5MQswCQYDVQQGEwJBVTETMBEG A1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8w DQYDVQQDEwZzZW5kZXIxITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmNvbTBcMA0G CSqGSIb3DQEBAQUAA0sAMEgCQQCoOEGQHefNK8tiz63/cPZEXfNLfiF1tlzhfsInO4Xrcpta lAppHYPKxZGyPwRyYeS461vOtRB32Kffi8laFBVhAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJ YIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTz NObDUXiSm5NJjk4wvBiIJGk0CTAfBgNVHSMEGDAWgBSNGy29vSToGWKuTMkqWJAIHNEFKzAN BgkqhkiG9w0BAQUFAAOBgQBTkNtt0KoPyzKULqW80Q0nfIXYCGxS/rFNGJTCEP9Pj3Ergb+j qhE+bnJfSho4oD9fLInWr8Vs/1ljEy37Wi9Ysnc+UoMHAcZOzxmcmvMXrGPCAPMYuSem1RoU Edbw25ve5K3NKQH9OMEhnPqiC15tXRRUzgo8968P5n/HOQCzMzCCAqswggIUoAMCAQICCQCK as1RvpSgFTANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1T dGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMREwDwYDVQQDEwhDQSBP d25lcjEjMCEGCSqGSIb3DQEJARYUY2Eub3duZXJAZXhhbXBsZS5jb20wHhcNMTAwMjA4MTYy NTQyWhcNMTEwMjA4MTYyNTQyWjB5MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0 ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDEwZzZW5kZXIx ITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sA MEgCQQCoOEGQHefNK8tiz63/cPZEXfNLfiF1tlzhfsInO4XrcptalAppHYPKxZGyPwRyYeS4 61vOtRB32Kffi8laFBVhAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9w ZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTzNObDUXiSm5NJjk4wvBiI JGk0CTAfBgNVHSMEGDAWgBSNGy29vSToGWKuTMkqWJAIHNEFKzANBgkqhkiG9w0BAQUFAAOB gQBTkNtt0KoPyzKULqW80Q0nfIXYCGxS/rFNGJTCEP9Pj3Ergb+jqhE+bnJfSho4oD9fLInW r8Vs/1ljEy37Wi9Ysnc+UoMHAcZOzxmcmvMXrGPCAPMYuSem1RoUEdbw25ve5K3NKQH9OMEh nPqiC15tXRRUzgo8968P5n/HOQCzMzGCAvAwggLsAgEBMIGKMH0xCzAJBgNVBAYTAkFVMRMw EQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQx ETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNv bQIJAIpqzVG+lKAVMAkGBSsOAwIaBQCgggH8MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEw HAYJKoZIhvcNAQkFMQ8XDTEwMDIwODIzMjI0NVowIwYJKoZIhvcNAQkEMRYEFI7CVTBf4yX6 Twycl/Zaa56huywsMF8GCSqGSIb3DQEJDzFSMFAwCwYJYIZIAWUDBAECMAoGCCqGSIb3DQMH MA4GCCqGSIb3DQMCAgIAgDANBggqhkiG9w0DAgIBQDAHBgUrDgMCBzANBggqhkiG9w0DAgIB KDCBmwYJKwYBBAGCNxAEMYGNMIGKMH0xCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93 bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNvbQIJAIpqzVG+lKAVMIGd BgsqhkiG9w0BCRACCzGBjaCBijB9MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0 ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMREwDwYDVQQDEwhDQSBPd25l cjEjMCEGCSqGSIb3DQEJARYUY2Eub3duZXJAZXhhbXBsZS5jb20CCQCKas1RvpSgFTANBgkq hkiG9w0BAQEFAARAYC9J5HJ1uSWhqT+WUyoEH/mUn9ZLg/yB3KnRRs3tsqYeJt2SlQrD+zN9 53knAqbgZ9v3viuGCo0fj6RvFU4CHgAAAAAAAA== --------------ms020101060809030506070801-- rt-5.0.1/t/i18n/caching.t000644 000765 000024 00000001543 14005011336 015607 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test; { my $french = RT::User->new(RT->SystemUser); $french->LoadOrCreateByEmail('french@example.com'); $french->SetName('french'); $french->SetLang('fr'); $french->SetPrivileged(1); $french->SetPassword('password'); $french->PrincipalObj->GrantRight(Right => 'SuperUser'); } my ($baseurl, $m) = RT::Test->started_ok; $m->login( root => "password" ); $m->get_ok('/Prefs/Other.html'); $m->content_lacks('Ne pas','Lacks translated french'); $m->get_ok( "/NoAuth/Logout.html" ); $m->login( french => "password" ); $m->get_ok('/Prefs/Other.html'); $m->content_contains('Ne pas','Has translated french'); $m->get_ok( "/NoAuth/Logout.html" ); # ->logout fails because it's translated $m->login( root => "password" ); $m->get_ok('/Prefs/Other.html'); $m->content_lacks('Ne pas','Lacks translated french'); rt-5.0.1/t/i18n/footer.t000644 000765 000024 00000001557 14005011336 015516 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test; { my $chinese = RT::User->new(RT->SystemUser); $chinese->LoadOrCreateByEmail('chinese@example.com'); $chinese->SetName('chinese'); $chinese->SetLang('zh_tw'); $chinese->SetPrivileged(1); $chinese->SetPassword('password'); $chinese->PrincipalObj->GrantRight(Right => 'SuperUser'); } my ($baseurl, $m) = RT::Test->started_ok; $m->login( root => "password" ); $m->content_contains('Copyright','Has english coypright'); $m->get_ok( "/NoAuth/Logout.html" ); $m->login( chinese => "password" ); TODO: { local $TODO = 'pending new translation for version 5 release'; $m->content_lacks('Copyright','Lacks english copyright'); }; $m->get_ok( "/NoAuth/Logout.html" ); # ->logout fails because it's translated $m->login( root => "password" ); $m->content_contains('Copyright','Still has english copyright'); rt-5.0.1/t/i18n/extract.t000644 000765 000024 00000000512 14005011336 015660 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef, nodb => 1; use RT::I18N::Extract; my $extract = RT::I18N::Extract->new; ok($extract); my %PO = $extract->all; ok(keys %PO, "Extracted keys successfully"); my @errors = $extract->errors; diag "$_" for @errors; ok(! @errors, "No errors during extraction"); done_testing; rt-5.0.1/t/i18n/default.t000644 000765 000024 00000001354 14005011336 015637 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1; my ($baseurl, $m) = RT::Test->started_ok; $m->get_ok('/'); $m->title_is('Login'); $m->get_ok('/', { 'Accept-Language' => 'x-klingon' }); $m->title_is('Login', 'unavailable language fallback to en'); $m->content_contains(''); $m->add_header('Accept-Language' => 'zh-tw,zh;q=0.8,en-gb;q=0.5,en;q=0.3'); $m->get_ok('/'); $m->title_is( Encode::decode("UTF-8",'登入'), 'Page title properly translated to chinese'); $m->content_contains( Encode::decode("UTF-8",'密碼'), 'Password properly translated'); { local $TODO = "We fail to correctly advertise the langauage in the block"; $m->content_contains(''); } rt-5.0.1/t/sla/due.t000644 000765 000024 00000025465 14005011336 015001 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; RT::Test->load_or_create_queue( Name => 'General', SLADisabled => 0 ); diag 'check change of Due date when SLA for a ticket is changed' if $ENV{'TEST_VERBOSE'}; { RT->Config->Set(ServiceAgreements => ( Default => '2', Levels => { '2' => { Resolve => { RealMinutes => 60*2 } }, '4' => { Resolve => { RealMinutes => 60*4 } }, }, )); my $time = time; my $ticket = RT::Ticket->new( $RT::SystemUser ); my ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx' ); ok $id, "created ticket #$id"; is $ticket->SLA, '2', 'default sla'; my $orig_due = $ticket->DueObj->Unix; ok $orig_due > 0, 'Due date is set'; ok $orig_due > $time, 'Due date is in the future'; $ticket->SetSLA('4'); is $ticket->SLA, '4', 'new sla'; my $new_due = $ticket->DueObj->Unix; ok $new_due > 0, 'Due date is set'; ok $new_due > $time, 'Due date is in the future'; is $new_due, $orig_due+2*60*60, 'difference is two hours'; } diag 'when not requestor creates a ticket, we dont set due date' if $ENV{'TEST_VERBOSE'}; { RT->Config->Set(ServiceAgreements => ( Default => '2', Levels => { '2' => { Response => { RealMinutes => 60*2 } }, }, )); my $ticket = RT::Ticket->new( $RT::SystemUser ); my ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx', Requestor => 'user@example.com', ); ok $id, "created ticket #$id"; is $ticket->SLA, '2', 'default sla'; my $due = $ticket->DueObj->Unix; ok $due <= 0, 'Due date is not set'; } diag 'check that reply to requestors unset due date' if $ENV{'TEST_VERBOSE'}; { RT->Config->Set(ServiceAgreements => ( Default => '2', Levels => { '2' => { Response => { RealMinutes => 60*2 } }, }, )); my $root = RT::User->new( $RT::SystemUser ); $root->LoadByEmail('root@localhost'); ok $root->id, 'loaded root user'; # requestor creates my $id; { my $ticket = RT::Ticket->new( $root ); ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx', Requestor => $root->id, ); ok $id, "created ticket #$id"; is $ticket->SLA, '2', 'default sla'; my $due = $ticket->DueObj->Unix; ok $due > 0, 'Due date is set'; } # non-requestor reply { my $ticket = RT::Ticket->new( $RT::SystemUser ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; $ticket->Correspond( Content => 'we are working on this.' ); $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; my $due = $ticket->DueObj->Unix; ok $due <= 0, 'Due date is not set'; } # non-requestor reply again { my $ticket = RT::Ticket->new( $RT::SystemUser ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; $ticket->Correspond( Content => 'we are still working on this.' ); $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; my $due = $ticket->DueObj->Unix; ok $due <= 0, 'Due date is not set'; } # requestor reply my $last_unreplied_due; { my $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; $ticket->Correspond( Content => 'what\'s going on with my ticket?' ); $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; my $due = $ticket->DueObj->Unix; ok $due > 0, 'Due date is set again'; $last_unreplied_due = $due; } # sleep at least one second and requestor replies again sleep 1; { my $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; $ticket->Correspond( Content => 'HEY! Were is my answer?' ); $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; my $due = $ticket->DueObj->Unix; ok $due > 0, 'Due date is still set'; is $due, $last_unreplied_due, 'due is unchanged'; } } diag 'check that reply to requestors dont unset due date with KeepInLoop' if $ENV{'TEST_VERBOSE'}; { RT->Config->Set(ServiceAgreements => ( Default => '2', Levels => { '2' => { Response => { RealMinutes => 60*2 }, KeepInLoop => { RealMinutes => 60*4 }, }, }, )); my $root = RT::User->new( $RT::SystemUser ); $root->LoadByEmail('root@localhost'); ok $root->id, 'loaded root user'; # requestor creates my $id; my $due; { my $ticket = RT::Ticket->new( $root ); ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx', Requestor => $root->id, ); ok $id, "created ticket #$id"; is $ticket->SLA, '2', 'default sla'; $due = $ticket->DueObj->Unix; ok $due > 0, 'Due date is set'; } # non-requestor reply { my $ticket = RT::Ticket->new( $RT::SystemUser ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; $ticket->Correspond( Content => 'we are working on this.' ); $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; my $tmp = $ticket->DueObj->Unix; ok $tmp > 0, 'Due date is set'; ok $tmp > $due, "keep in loop is 4hours when response is 2hours"; $due = $tmp; } # non-requestor reply again { sleep 1; my $ticket = RT::Ticket->new( $RT::SystemUser ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; $ticket->Correspond( Content => 'we are still working on this.' ); $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; my $tmp = $ticket->DueObj->Unix; ok $tmp > 0, 'Due date is set'; ok $tmp > $due, "keep in loop sligtly moved"; $due = $tmp; } # requestor reply my $last_unreplied_due; { my $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; $ticket->Correspond( Content => 'what\'s going on with my ticket?' ); $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; my $tmp = $ticket->DueObj->Unix; ok $tmp > 0, 'Due date is set'; ok $tmp < $due, "response deadline is 2 hours earlier"; $due = $tmp; $last_unreplied_due = $due; } # sleep at least one second and requestor replies again sleep 1; { my $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; $ticket->Correspond( Content => 'HEY! Were is my answer?' ); $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; my $tmp = $ticket->DueObj->Unix; ok $tmp > 0, 'Due date is set'; is $tmp, $last_unreplied_due, 'due is unchanged'; $due = $tmp; } } diag 'check that replies dont affect resolve deadlines' if $ENV{'TEST_VERBOSE'}; { RT->Config->Set(ServiceAgreements => ( Default => '2', Levels => { '2' => { Resolve => { RealMinutes => 60*2 } }, }, )); my $root = RT::User->new( $RT::SystemUser ); $root->LoadByEmail('root@localhost'); ok $root->id, 'loaded root user'; # requestor creates my ($id, $orig_due); { my $ticket = RT::Ticket->new( $root ); ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx', Requestor => $root->id, ); ok $id, "created ticket #$id"; is $ticket->SLA, '2', 'default sla'; $orig_due = $ticket->DueObj->Unix; ok $orig_due > 0, 'Due date is set'; } # non-requestor reply { my $ticket = RT::Ticket->new( $RT::SystemUser ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; $ticket->Correspond( Content => 'we are working on this.' ); $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; my $due = $ticket->DueObj->Unix; ok $due > 0, 'Due date is set'; is $due, $orig_due, 'due is not changed'; } # requestor reply { my $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; $ticket->Correspond( Content => 'what\'s going on with my ticket?' ); $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; my $due = $ticket->DueObj->Unix; ok $due > 0, 'Due date is set'; is $due, $orig_due, 'due is not changed'; } } diag 'check that owner is not treated as requestor' if $ENV{'TEST_VERBOSE'}; { RT->Config->Set(ServiceAgreements => ( Default => '2', Levels => { '2' => { Response => { RealMinutes => 60*2 } }, }, )); my $root = RT::User->new( $RT::SystemUser ); $root->LoadByEmail('root@localhost'); ok $root->id, 'loaded root user'; # requestor creates and he is owner my $id; { my $ticket = RT::Ticket->new( $root ); ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx', Requestor => $root->id, Owner => $root->id, ); ok $id, "created ticket #$id"; is $ticket->SLA, '2', 'default sla'; is $ticket->Owner, $root->id, 'correct owner'; my $due = $ticket->DueObj->Unix; ok $due <= 0, 'Due date is not set'; } } diag 'check that response deadline is left alone when there is no requestor' if $ENV{'TEST_VERBOSE'}; { RT->Config->Set(ServiceAgreements => ( Default => '2', Levels => { '2' => { Response => { RealMinutes => 60*2 } }, }, )); my $root = RT::User->new( $RT::SystemUser ); $root->LoadByEmail('root@localhost'); ok $root->id, 'loaded root user'; # create a ticket without requestor my $id; { my $ticket = RT::Ticket->new( $root ); ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx', ); ok $id, "created ticket #$id"; is $ticket->SLA, '2', 'default sla'; my $due = $ticket->DueObj->Unix; ok $due <= 0, 'Due date is not set'; } } done_testing(); rt-5.0.1/t/sla/business_hours.t000644 000765 000024 00000004512 14005011336 017265 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::MockTime qw( :all ); use RT::Test tests => undef; # we assume the RT's Timezone is UTC now, need a smart way to get over that. $ENV{'TZ'} = 'GMT'; RT->Config->Set( Timezone => 'GMT' ); RT::Test->load_or_create_queue( Name => 'General', SLADisabled => 0 ); diag 'check business hours' if $ENV{'TEST_VERBOSE'}; { RT->Config->Set(ServiceAgreements => ( Default => 'Sunday', Levels => { Sunday => { Resolve => { BusinessMinutes => 60 }, BusinessHours => 'Sunday', }, Monday => { Resolve => { BusinessMinutes => 60 }, }, }, )); RT->Config->Set(ServiceBusinessHours => ( Sunday => { 0 => { Name => 'Sunday', Start => '9:00', End => '17:00' } }, Default => { 1 => { Name => 'Monday', Start => '9:00', End => '17:00' }, }, )); set_fixed_time('2007-01-01T00:00:00Z'); my $ticket = RT::Ticket->new($RT::SystemUser); my ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx' ); ok( $id, "created ticket #$id" ); is( $ticket->SLA, 'Sunday', 'default sla' ); my $start = $ticket->StartsObj->Unix; my $due = $ticket->DueObj->Unix; is( $start, 1168160400, 'Start date is 2007-01-07T09:00:00Z' ); is( $due, 1168164000, 'Due date is 2007-01-07T10:00:00Z' ); $ticket->SetSLA( 'Monday' ); is( $ticket->SLA, 'Monday', 'new sla' ); $due = $ticket->DueObj->Unix; is( $due, 1167645600, 'Due date is 2007-01-01T10:00:00Z' ); } diag 'check that RT warns about specifying Sunday as 7 rather than 0' if $ENV{'TEST_VERBOSE'}; { my @warnings; local $SIG{__WARN__} = sub { push @warnings, $_[0]; }; RT->Config->Set(ServiceBusinessHours => ( Invalid => { 7 => { Name => 'Domingo', Start => '9:00', End => '17:00' } }, )); RT->Config->PostLoadCheck; is(@warnings, 1); like($warnings[0], qr/Config option %ServiceBusinessHours 'Invalid' erroneously specifies 'Domingo' as day 7; Sunday should be specified as day 0\./); } done_testing(); rt-5.0.1/t/sla/starts.t000644 000765 000024 00000004370 14005011336 015534 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::MockTime qw( :all ); use RT::Test tests => undef; RT::Test->load_or_create_queue( Name => 'General', SLADisabled => 0 ); # we assume the RT's Timezone is UTC now, need a smart way to get over that. $ENV{'TZ'} = 'GMT'; RT->Config->Set( Timezone => 'GMT' ); my $bhours = RT::SLA->BusinessHours; diag 'check Starts date' if $ENV{'TEST_VERBOSE'}; { RT->Config->Set(ServiceAgreements => ( Default => 'standard', Levels => { 'standard' => { Response => 2 * 60, Resolve => 7 * 60 * 24, }, }, )); RT->Config->Set(ServiceBusinessHours => ( Default => { 1 => { Name => 'Monday', Start => '09:00', End => '17:00' }, 2 => { Name => 'Tuesday', Start => '09:00', End => '17:00' }, } )); my %time = ( '2007-01-01T13:15:00Z' => 1167657300, # 2007-01-01T13:15:00Z '2007-01-01T19:15:00Z' => 1167728400, # 2007-01-02T09:00:00Z '2007-01-06T13:15:00Z' => 1168246800, # 2007-01-08T09:00:00Z ); for my $time ( keys %time ) { set_fixed_time($time); my $ticket = RT::Ticket->new($RT::SystemUser); my ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx' ); ok $id, "created ticket #$id"; is $ticket->StartsObj->Unix, $time{$time}, 'Starts date is right'; } restore_time(); } diag 'check Starts date with StartImmediately enabled' if $ENV{'TEST_VERBOSE'}; { RT->Config->Set(ServiceAgreements => ( Default => 'start immediately', Levels => { 'start immediately' => { StartImmediately => 1, Response => 2 * 60, Resolve => 7 * 60 * 24, }, }, )); my $time = time; my $ticket = RT::Ticket->new($RT::SystemUser); my ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx' ); ok $id, "created ticket #$id"; my $starts = $ticket->StartsObj->Unix; ok $starts > 0, 'Starts date is set'; is $starts, $ticket->CreatedObj->Unix, 'Starts is correct'; } done_testing; rt-5.0.1/t/sla/timezone.t000644 000765 000024 00000003104 14005011336 016040 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::MockTime qw( :all ); use RT::Test tests => undef; my $ru_queue = RT::Test->load_or_create_queue( Name => 'RU', SLADisabled => 0 ); ok $ru_queue && $ru_queue->id, 'created RU queue'; my $us_queue = RT::Test->load_or_create_queue( Name => 'US', SLADisabled => 0 ); ok $us_queue && $ru_queue->id, 'created US queue'; RT->Config->Set(ServiceAgreements => ( Default => 2, QueueDefault => { RU => { Timezone => 'Europe/Moscow' }, US => { Timezone => 'America/New_York' }, }, Levels => { '2' => { Resolve => { BusinessMinutes => 60 * 2 } }, }, )); set_fixed_time('2007-01-01T22:00:00Z'); diag 'check dates in US queue' if $ENV{'TEST_VERBOSE'}; { my $ticket = RT::Ticket->new($RT::SystemUser); my ($id) = $ticket->Create( Queue => 'US', Subject => 'xxx' ); ok( $id, "created ticket #$id" ); my $start = $ticket->StartsObj->ISO( Timezone => 'utc' ); is( $start, '2007-01-01 22:00:00', 'Start date is right' ); my $due = $ticket->DueObj->ISO( Timezone => 'utc' ); is( $due, '2007-01-02 15:00:00', 'Due date is right' ); } diag 'check dates in RU queue' if $ENV{'TEST_VERBOSE'}; { my $ticket = RT::Ticket->new($RT::SystemUser); my ($id) = $ticket->Create( Queue => 'RU', Subject => 'xxx' ); ok( $id, "created ticket #$id" ); my $start = $ticket->StartsObj->ISO( Timezone => 'utc' ); is( $start, '2007-01-02 06:00:00', 'Start date is right' ); my $due = $ticket->DueObj->ISO( Timezone => 'utc' ); is( $due, '2007-01-02 08:00:00', 'Due date is right' ); } done_testing; rt-5.0.1/t/sla/ignore-on-statuses.t000644 000765 000024 00000017455 14005011336 017772 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; RT::Test->load_or_create_queue( Name => 'General', SLADisabled => 0 ); diag 'check that reply to requestors dont unset due date with KeepInLoop' if $ENV{'TEST_VERBOSE'}; { RT->Config->Set(ServiceAgreements => ( Default => '2', Levels => { '2' => { KeepInLoop => { RealMinutes => 60*4, IgnoreOnStatuses => ['stalled'] }, }, }, )); my $root = RT::User->new( $RT::SystemUser ); $root->LoadByEmail('root@localhost'); ok $root->id, 'loaded root user'; # requestor creates my $id; my $due; { my $ticket = RT::Ticket->new( $root ); ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx', Requestor => $root->id, ); ok $id, "created ticket #$id"; is $ticket->SLA, '2', 'default sla'; ok !$ticket->DueObj->Unix, 'no response deadline'; $due = 0; } # non-requestor reply { my $ticket = RT::Ticket->new( $RT::SystemUser ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; $ticket->Correspond( Content => 'we are working on this.' ); $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; my $tmp = $ticket->DueObj->Unix; ok $tmp > 0, 'Due date is set'; ok $tmp > $due, "keep in loop due set"; $due = $tmp; } # stalling ticket { my $ticket = RT::Ticket->new( $RT::SystemUser ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; my ($status, $msg) = $ticket->SetStatus('stalled'); ok $status, 'stalled the ticket'; $ticket->Load( $id ); ok !$ticket->DueObj->Unix, 'keep in loop deadline ignored for stalled'; } # non-requestor reply again { sleep 1; my $ticket = RT::Ticket->new( $RT::SystemUser ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; $ticket->Correspond( Content => 'we are still working on this.' ); $ticket->SetStatus('open'); $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; is $ticket->Status, 'open', 'ticket was opened'; my $tmp = $ticket->DueObj->Unix; ok $tmp > 0, 'Due date is set'; ok $tmp > $due, "keep in loop sligtly moved"; $due = $tmp; } } diag 'Check that failing to reply to the requestors is not ignored' if $ENV{'TEST_VERBOSE'}; { RT->Config->Set(ServiceAgreements => ( Default => '2', Levels => { '2' => { Response => { RealMinutes => 60*2 }, KeepInLoop => { RealMinutes => 60*4, IgnoreOnStatuses => ['stalled'] }, }, }, )); my $root = RT::User->new( $RT::SystemUser ); $root->LoadByEmail('root@localhost'); ok $root->id, 'loaded root user'; # requestor creates my $id; my $due; { my $ticket = RT::Ticket->new( $root ); ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx', Requestor => $root->id, ); ok $id, "created ticket #$id"; is $ticket->SLA, '2', 'default sla'; $due = $ticket->DueObj->Unix; ok $due > 0, 'response deadline'; } # stalling ticket { my $ticket = RT::Ticket->new( $RT::SystemUser ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; my ($status, $msg) = $ticket->SetStatus('stalled'); ok $status, 'stalled the ticket'; $ticket->Load( $id ); my $tmp = $ticket->DueObj->Unix; ok $tmp, 'response deadline not unset'; is $tmp, $due, 'due not changed'; } # non-requestor reply { sleep 1; my $ticket = RT::Ticket->new( $RT::SystemUser ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; $ticket->Correspond( Content => 'we are still working on this.' ); $ticket->SetStatus('open'); $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; is $ticket->Status, 'open', 'ticket was opened'; my $tmp = $ticket->DueObj->Unix; ok $tmp > 0, 'Due date is set'; ok $tmp > $due, "keep in loop is greater than response"; $due = $tmp; } # stalling ticket again { my $ticket = RT::Ticket->new( $RT::SystemUser ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; my ($status, $msg) = $ticket->SetStatus('stalled'); ok $status, 'stalled the ticket'; $ticket->Load( $id ); ok !$ticket->DueObj->Unix, 'keep in loop deadline unset for stalled'; } } diag 'check the ExcludeTimeOnIgnoredStatuses option' if $ENV{'TEST_VERBOSE'}; { RT->Config->Set(ServiceAgreements => ( Default => '2', Levels => { '2' => { Response => { RealMinutes => 60*2, IgnoreOnStatuses => ['stalled'] }, }, }, )); my $root = RT::User->new( $RT::SystemUser ); $root->LoadByEmail('root@localhost'); ok $root->id, 'loaded root user'; my $bob = RT::Test->load_or_create_user( Name => 'bob', EmailAddress => 'bob@example.com', Password => 'password' ); ok( $bob->Id, "Created test user bob" ); ok( RT::Test->add_rights( { Principal => 'Privileged', Right => [ qw(CreateTicket ShowTicket ModifyTicket SeeQueue) ] } ), 'Granted ticket management rights' ); # requestor creates my $id; my $due; { my $ticket = RT::Ticket->new( $bob ); ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx', Requestor => $bob->id, ); ok $id, "created ticket #$id"; is $ticket->SLA, '2', 'default sla'; $due = $ticket->DueObj->Unix; ok $due > 0, 'response deadline'; } # stalling ticket { my $ticket = RT::Ticket->new( $RT::SystemUser ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; my ($status, $msg) = $ticket->SetStatus('stalled'); ok $status, 'stalled the ticket'; $ticket->Load( $id ); ok !$ticket->DueObj->Unix, 'deadline ignored for stalled'; } # requestor reply again { sleep 1; my $ticket = RT::Ticket->new( $bob ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; $ticket->Correspond( Content => 'please reopen this ticket, we are good to continue' ); $ticket->SetStatus('open'); $ticket = RT::Ticket->new( $root ); $ticket->Load( $id ); ok $ticket->id, "loaded ticket #$id"; is $ticket->Status, 'open', 'ticket was opened'; my $tmp = $ticket->DueObj->Unix; ok $tmp > 0, 'Due date is set'; ok $tmp == $due, "deadline not changed"; } RT->Config->Set(ServiceAgreements => ( Default => '2', Levels => { '2' => { Response => { RealMinutes => 60*2, IgnoreOnStatuses => ['stalled'], ExcludeTimeOnIgnoredStatuses => 1 }, }, }, )); { my $ticket = RT::Ticket->new( $RT::SystemUser ); $ticket->Load( $id ); my ($status, $msg) = $ticket->SetStatus('stalled'); ok $status, 'stalled the ticket'; ok !$ticket->DueObj->Unix, 'deadline ignored for stalled'; sleep 1; $ticket->SetStatus('open'); is $ticket->Status, 'open', 'ticket was opened'; my $tmp = $ticket->DueObj->Unix; ok $tmp > 0, 'Due date is set'; ok $tmp >= $due+1, "deadline slighted moved"; ok $tmp <= $due+5, "deadline slighted moved but not much"; } } done_testing; rt-5.0.1/t/sla/web.t000644 000765 000024 00000006572 14005011336 014777 0ustar00sunnavystaff000000 000000 use strict; use warnings; BEGIN { use Test::MockTime 'set_fixed_time'; use constant TIME => 1442455200; set_fixed_time(TIME); use RT::Test tests => undef, config => "use Test::MockTime 'set_fixed_time'; set_fixed_time(". TIME . q!); Set( %ServiceAgreements, ( Default => '2', Levels => { '2' => { StartImmediately => 1, Response => { RealMinutes => 60 * 2 }, }, '4' => { StartImmediately => 1, Response => { RealMinutes => 60 * 4 }, }, }, ));! ; } my $now = TIME; my $queue = RT::Test->load_or_create_queue( Name => 'General', SLADisabled => 0 ); my $user = RT::Test->load_or_create_user( Name => 'user', Password => 'password', EmailAddress => 'user@example.com', ); my ( $baseurl, $m ) = RT::Test->started_ok; ok( RT::Test->set_rights( { Principal => $user, Right => [ qw(SeeQueue CreateTicket ShowTicket ModifyTicket ShowConfigTab AdminQueue) ] }, ), 'set rights' ); ok $m->login( 'user', 'password' ), 'logged in as user'; { $m->goto_create_ticket( $queue->id ); my $form = $m->form_name( 'TicketCreate' ); my $sla = $form->find_input( 'SLA' ); is_deeply( [$sla->possible_values], [ 2, 4 ], 'possible sla' ); $m->submit_form( fields => { Subject => 'ticket foo with default sla' }, button => 'SubmitTicket' ); my $ticket = RT::Test->last_ticket; ok( $ticket->id, 'ticket is created' ); my $id = $ticket->id; is( $ticket->SLA, 2, 'default SLA is 2' ); is( $ticket->StartsObj->Unix, $now, 'Starts' ); is( $ticket->DueObj->Unix, $now + 60 * 60 * 2, 'Due' ); } { $m->goto_create_ticket( $queue->id ); my $form = $m->form_name( 'TicketCreate' ); $m->submit_form( fields => { Subject => 'ticket foo with default sla', SLA => 4 }, button => 'SubmitTicket' ); my $ticket = RT::Test->last_ticket; ok( $ticket->id, 'ticket is created' ); my $id = $ticket->id; is( $ticket->SLA, 4, 'SLA is set to 4' ); is( $ticket->StartsObj->Unix, $now, 'Starts' ); is( $ticket->DueObj->Unix, $now + 60 * 60 * 4, 'Due' ); $m->follow_link_ok( { text => 'Basics' }, 'Ticket -> Basics' ); $m->submit_form( form_name => 'TicketModify', fields => { SLA => 2 }, ); $ticket->Load( $id ); is( $ticket->SLA, 2, 'SLA is set to 2' ); is( $ticket->DueObj->Unix, $now + 60 * 60 * 2, 'Due is updated accordingly' ); } { $m->get_ok( $baseurl . '/Admin/Queues/Modify.html?id=' . $queue->id ); my $form = $m->form_name( 'ModifyQueue' ); $m->untick( 'SLAEnabled', 1 ); $m->submit; $m->text_contains( q{SLADisabled changed from (no value) to "1"} ); } { $m->goto_create_ticket( $queue->id ); my $form = $m->form_name( 'TicketCreate' ); ok( !$form->find_input( 'SLA' ), 'no SLA input' ); $m->submit_form( fields => { Subject => 'ticket foo without sla' }, button => 'SubmitTicket' ); my $ticket = RT::Test->last_ticket; ok( $ticket->id, 'ticket is created' ); ok( !$ticket->SLA, 'no SLA' ); ok( !$ticket->StartsObj->Unix, 'no Starts' ); ok( !$ticket->DueObj->Unix, 'no Due' ); } done_testing; rt-5.0.1/t/sla/queue.t000644 000765 000024 00000002541 14005011336 015336 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::MockTime qw( :all ); use RT::Test tests => undef; my $queue = RT::Test->load_or_create_queue( Name => 'General', SLADisabled => 0 ); my $queue_sla = RT::Attribute->new($RT::SystemUser); diag 'check set of Due date with Queue default SLA' if $ENV{'TEST_VERBOSE'}; { # add default SLA for 'General'; my ($id) = $queue_sla->Create( Name => 'SLA', Description => 'Default Queue SLA', Content => '4', Object => $queue ); ok( $id, 'Created SLA Attribute for General' ); RT->Config->Set(ServiceAgreements => ( Default => '2', Levels => { '2' => { Resolve => { RealMinutes => 60 * 2 } }, '4' => { StartImmediately => 1, Resolve => { RealMinutes => 60 * 4 } }, }, )); set_fixed_time('2007-01-01T00:00:00Z'); my $time = time; my $ticket = RT::Ticket->new($RT::SystemUser); ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx' ); ok( $id, "created ticket #$id" ); is $ticket->SLA, '4', 'default sla'; my $start = $ticket->StartsObj->Unix; my $due = $ticket->DueObj->Unix; is( $start, $time, 'Start Date is right' ); is( $due, $time+3600*4, 'Due date is right'); my ( $status, $message ) = $queue->DeleteAttribute('SLA'); ok( $status, $message ); } done_testing; rt-5.0.1/t/api/ticket.t000644 000765 000024 00000027635 14005011336 015502 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => undef; { use_ok ('RT::Queue'); ok(my $testqueue = RT::Queue->new(RT->SystemUser)); ok($testqueue->Create( Name => 'ticket tests')); isnt($testqueue->Id , 0); use_ok('RT::CustomField'); ok(my $testcf = RT::CustomField->new(RT->SystemUser)); my ($ret, $cmsg) = $testcf->Create( Name => 'selectmulti', Queue => $testqueue->id, Type => 'SelectMultiple'); ok($ret,"Created the custom field - ".$cmsg); ($ret,$cmsg) = $testcf->AddValue ( Name => 'Value1', SortOrder => '1', Description => 'A testing value'); ok($ret, "Added a value - ".$cmsg); ok($testcf->AddValue ( Name => 'Value2', SortOrder => '2', Description => 'Another testing value')); ok($testcf->AddValue ( Name => 'Value3', SortOrder => '3', Description => 'Yet Another testing value')); is($testcf->Values->Count , 3); use_ok('RT::Ticket'); my $u = RT::User->new(RT->SystemUser); $u->Load("root"); ok ($u->Id, "Found the root user"); ok(my $t = RT::Ticket->new(RT->SystemUser)); ok(my ($id, $msg) = $t->Create( Queue => $testqueue->Id, Subject => 'Testing', Owner => $u->Id )); isnt($id , 0); is ($t->OwnerObj->Id , $u->Id, "Root is the ticket owner"); ok(my ($cfv, $cfm) =$t->AddCustomFieldValue(Field => $testcf->Id, Value => 'Value1')); isnt($cfv , 0, "Custom field creation didn't return an error: $cfm"); is($t->CustomFieldValues($testcf->Id)->Count , 1); ok($t->CustomFieldValues($testcf->Id)->First && $t->CustomFieldValues($testcf->Id)->First->Content eq 'Value1'); ok(my ($cfdv, $cfdm) = $t->DeleteCustomFieldValue(Field => $testcf->Id, Value => 'Value1')); isnt ($cfdv , 0, "Deleted a custom field value: $cfdm"); is($t->CustomFieldValues($testcf->Id)->Count , 0); ok(my $t2 = RT::Ticket->new(RT->SystemUser)); ok($t2->Load($id)); is($t2->Subject, 'Testing'); is($t2->QueueObj->Id, $testqueue->id); is($t2->OwnerObj->Id, $u->Id); my $t3 = RT::Ticket->new(RT->SystemUser); my ($id3, $msg3) = $t3->Create( Queue => $testqueue->Id, Subject => 'Testing', Owner => $u->Id); my ($cfv1, $cfm1) = $t->AddCustomFieldValue(Field => $testcf->Id, Value => 'Value1'); isnt($cfv1 , 0, "Adding a custom field to ticket 1 is successful: $cfm"); my ($cfv2, $cfm2) = $t3->AddCustomFieldValue(Field => $testcf->Id, Value => 'Value2'); isnt($cfv2 , 0, "Adding a custom field to ticket 2 is successful: $cfm"); my ($cfv3, $cfm3) = $t->AddCustomFieldValue(Field => $testcf->Id, Value => 'Value3'); isnt($cfv3 , 0, "Adding a custom field to ticket 1 is successful: $cfm"); is($t->CustomFieldValues($testcf->Id)->Count , 2, "This ticket has 2 custom field values"); is($t3->CustomFieldValues($testcf->Id)->Count , 1, "This ticket has 1 custom field value"); } { ok(require RT::Ticket, "Loading the RT::Ticket library"); } { my $t = RT::Ticket->new(RT->SystemUser); ok( $t->Create(Queue => 'General', Due => '2002-05-21 00:00:00', ReferredToBy => 'http://www.cpan.org', RefersTo => 'http://fsck.com', Subject => 'This is a subject'), "Ticket Created"); ok ( my $id = $t->Id, "Got ticket id"); like ($t->RefersTo->First->Target , qr/fsck.com/, "Got refers to"); like ($t->ReferredToBy->First->Base , qr/cpan.org/, "Got referredtoby"); ok (!$t->ResolvedObj->IsSet, "It hasn't been resolved"); } { my $ticket = RT::Ticket->new(RT->SystemUser); my ($id, $msg) = $ticket->Create(Subject => "Foo", Owner => RT->SystemUser->Id, Status => 'open', Requestor => ['jesse@example.com'], Queue => '1' ); ok ($id, "Ticket $id was created"); ok(my $group = $ticket->RoleGroup('Requestor')); ok ($group->Id, "Found the requestors object for this ticket"); ok(my $jesse = RT::User->new(RT->SystemUser), "Creating a jesse rt::user"); $jesse->LoadByEmail('jesse@example.com'); ok($jesse->Id, "Found the jesse rt user"); ok ($ticket->IsWatcher(Type => 'Requestor', PrincipalId => $jesse->PrincipalId), "The ticket actually has jesse at fsck.com as a requestor"); ok (my ($add_id, $add_msg) = $ticket->AddWatcher(Type => 'Requestor', Email => 'bob@fsck.com'), "Added bob at fsck.com as a requestor"); ok ($add_id, "Add succeeded: ($add_msg)"); ok(my $bob = RT::User->new(RT->SystemUser), "Creating a bob rt::user"); $bob->LoadByEmail('bob@fsck.com'); ok($bob->Id, "Found the bob rt user"); ok ($ticket->IsWatcher(Type => 'Requestor', PrincipalId => $bob->PrincipalId), "The ticket actually has bob at fsck.com as a requestor"); ok ( ($add_id, $add_msg) = $ticket->DeleteWatcher(Type =>'Requestor', Email => 'bob@fsck.com'), "Added bob at fsck.com as a requestor"); ok (!$ticket->IsWatcher(Type => 'Requestor', PrincipalId => $bob->PrincipalId), "The ticket no longer has bob at fsck.com as a requestor"); $group = $ticket->RoleGroup('Cc'); ok ($group->Id, "Found the cc object for this ticket"); $group = $ticket->RoleGroup('AdminCc'); ok ($group->Id, "Found the AdminCc object for this ticket"); $group = $ticket->RoleGroup('Owner'); ok ($group->Id, "Found the Owner object for this ticket"); ok($group->HasMember(RT->SystemUser->UserObj->PrincipalObj), "the owner group has the member 'RT_System'"); } { my $t = RT::Ticket->new(RT->SystemUser); ok($t->Create(Queue => 'general', Subject => 'SquelchTest', SquelchMailTo => 'nobody@example.com')); my @returned = $t->SquelchMailTo(); is($#returned, 0, "The ticket has one squelched recipients"); my ($ret, $msg) = $t->UnsquelchMailTo('nobody@example.com'); ok($ret, "Removed nobody as a squelched recipient - ".$msg); @returned = $t->SquelchMailTo(); is($#returned, -1, "The ticket has no squelched recipients". join(',',@returned)); @returned = $t->SquelchMailTo('nobody@example.com'); is($#returned, 0, "The ticket has one squelched recipients"); my @names = $t->Attributes->Names; is(shift @names, 'SquelchMailTo', "The attribute we have is SquelchMailTo"); @returned = $t->SquelchMailTo('nobody@example.com'); is($#returned, 0, "The ticket has one squelched recipients"); @names = $t->Attributes->Names; is(shift @names, 'SquelchMailTo', "The attribute we have is SquelchMailTo"); ($ret, $msg) = $t->UnsquelchMailTo('nobody@example.com'); ok($ret, "Removed nobody as a squelched recipient - ".$msg); @returned = $t->SquelchMailTo(); is($#returned, -1, "The ticket has no squelched recipients". join(',',@returned)); @returned = $t->SquelchMailTo('somebody@example.com','nobody@example.com'); is($#returned, 1, "The ticket has two squelched recipients, multiple args"); @returned = $t->SquelchMailTo('third@example.com'); is($#returned, 2, "The ticket has three squelched recipients, additive calls"); my $t2 = RT::Ticket->new(RT->SystemUser); ok($t2->Create(Queue => 'general', Subject => 'SquelchTest', SquelchMailTo => [ 'nobody@example.com', 'test@example.com' ])); my @returned2 = $t2->SquelchMailTo(); is($#returned2,1, "The ticket has two squelched recipients"); $t2->SquelchMailTo('test@example.com'); my @returned3 = $t2->SquelchMailTo(); is($#returned2,1, "The ticket still has two squelched recipients, no duplicate squelchers"); } { my $t1 = RT::Ticket->new(RT->SystemUser); $t1->Create ( Subject => 'Merge test 1', Queue => 'general', Requestor => 'merge1@example.com'); my $t1id = $t1->id; my $t2 = RT::Ticket->new(RT->SystemUser); $t2->Create ( Subject => 'Merge test 2', Queue => 'general', Requestor => 'merge2@example.com'); my $t2id = $t2->id; my ($msg, $val) = $t1->MergeInto($t2->id); ok ($msg,$val); $t1 = RT::Ticket->new(RT->SystemUser); is ($t1->id, undef, "ok. we've got a blank ticket1"); $t1->Load($t1id); is ($t1->id, $t2->id); is ($t1->Requestors->MembersObj->Count, 2); } diag "Test owner changes"; { my $root = RT::User->new(RT->SystemUser); $root->Load('root'); ok ($root->Id, "Loaded the root user"); my $t = RT::Ticket->new(RT->SystemUser); my ($val, $msg) = $t->Create( Subject => 'Owner test 1', Queue => 'General'); ok( $t->Id, "Created a new ticket with id $val: $msg"); ($val, $msg) = $t->SetOwner('bogususer'); ok( !$val, "Can't set owner to bogus user"); is( $msg, "That user does not exist", "Got message: $msg"); ($val, $msg) = $t->SetOwner('root'); is ($t->OwnerObj->Name, 'root' , "Root owns the ticket"); ($val, $msg) = $t->SetOwner('root'); ok( !$val, "User already owns ticket"); is( $msg, "That user already owns that ticket", "Got message: $msg"); $t->Steal(); is ($t->OwnerObj->id, RT->SystemUser->id , "SystemUser owns the ticket"); my $txns = RT::Transactions->new(RT->SystemUser); $txns->OrderBy(FIELD => 'id', ORDER => 'DESC'); $txns->Limit(FIELD => 'ObjectId', VALUE => $t->Id); $txns->Limit(FIELD => 'ObjectType', VALUE => 'RT::Ticket'); $txns->Limit(FIELD => 'Type', OPERATOR => '!=', VALUE => 'EmailRecord'); my $steal = $txns->First; is($steal->OldValue , $root->Id , "Stolen from root"); is($steal->NewValue , RT->SystemUser->Id , "Stolen by the systemuser"); ok(my $user1 = RT::User->new(RT->SystemUser), "Creating a user1 rt::user"); ($val, $msg) = $user1->Create(Name => 'User1', EmailAddress => 'user1@example.com'); ok( $val, "Created new user with id: $val"); ok( $user1->Id, "Found the user1 rt user"); my $t1 = RT::Ticket->new($user1); ($val, $msg) = $t1->Load($t->Id); ok( $t1->Id, "Loaded ticket with id $val"); ($val, $msg) = $t1->SetOwner('root'); ok( !$val, "user1 can't set owner to root: $msg"); is ($t->OwnerObj->id, RT->SystemUser->id , "SystemUser still owns ticket " . $t1->Id); my $queue = RT::Queue->new(RT->SystemUser); $queue->Load("General"); ($val, $msg) = $user1->PrincipalObj->GrantRight( Object => $queue, Right => 'ModifyTicket' ); ($val, $msg) = $t1->SetOwner('root'); ok( !$val, "With ModifyTicket user1 can't set owner to root: $msg"); is ($t->OwnerObj->id, RT->SystemUser->id , "SystemUser still owns ticket " . $t1->Id); ($val, $msg) = $user1->PrincipalObj->GrantRight( Object => $queue, Right => 'ReassignTicket' ); ($val, $msg) = $t1->SetOwner('root'); ok( $val, "With ReassignTicket user1 reassigned ticket " . $t1->Id . " to root: $msg"); is ($t1->OwnerObj->Name, 'root' , "Root owns ticket " . $t1->Id); } { my $tt = RT::Ticket->new(RT->SystemUser); my ($id, $tid, $msg)= $tt->Create(Queue => 'general', Subject => 'test'); ok($id, $msg); is($tt->Status, 'new', "New ticket is created as new"); ($id, $msg) = $tt->SetStatus('open'); ok($id, $msg); like($msg, qr/open/i, "Status message is correct"); ($id, $msg) = $tt->SetStatus('resolved'); ok($id, $msg); like($msg, qr/resolved/i, "Status message is correct"); ($id, $msg) = $tt->SetStatus('resolved'); ok(!$id,$msg); my $dep = RT::Ticket->new( RT->SystemUser ); my ( $dep_id, undef, $dep_msg ) = $dep->Create( Queue => 'general', Subject => 'dep ticket', 'DependedOnBy' => $tt->id, ); ok( $dep->id, $dep_msg ); ($id, $msg) = $tt->SetStatus('rejected'); ok( $id, $msg ); } diag("Test ticket types with different cases"); { my $t = RT::Ticket->new(RT->SystemUser); my ($ok) = $t->Create( Queue => 'general', Subject => 'type test', Type => 'Ticket', ); ok($ok, "Ticket allows passing upper-case Ticket as type during Create"); is($t->Type, "ticket", "Ticket type is lowercased during create"); ($ok) = $t->SetType("REMINDER"); ok($ok, "Ticket allows passing upper-case REMINDER to SetType"); is($t->Type, "reminder", "Ticket type is lowercased during set"); ($ok) = $t->SetType("OTHER"); ok($ok, "Allows setting Type to non-RT types"); is($t->Type, "OTHER", "Non-RT types are case-insensitive"); ($ok) = $t->Create( Queue => 'general', Subject => 'type test', Type => 'Approval', ); ok($ok, "Tickets can be created with an upper-case Approval type"); is($t->Type, "approval", "Approvals, the third and final internal type, are also lc'd during Create"); } done_testing; rt-5.0.1/t/api/i18n_guess.t000644 000765 000024 00000003103 14005011336 016164 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 16; my $string = "\x{442}\x{435}\x{441}\x{442} \x{43f}\x{43e}\x{434}\x{434}\x{435}\x{440}\x{436}\x{43a}\x{430}"; sub guess { local $Test::Builder::Level = $Test::Builder::Level + 1; is( RT::I18N::_GuessCharset( Encode::encode($_[0], $_[1]) ), $_[2] || $_[0], "$_[0] guesses as @{[$_[2]||$_[0]]}" ); } RT->Config->Set(EmailInputEncodings => qw(*)); SKIP: { guess('utf-8', $string); guess('cp1251', $string); guess('koi8-r', $string); } RT->Config->Set(EmailInputEncodings => qw(UTF-8 cp1251 koi8-r)); SKIP: { guess('utf-8', $string); guess('cp1251', $string); guess('windows-1251', $string, 'cp1251'); { local $TODO = "Encode::Guess can't distinguish cp1251 from koi8-r"; guess('koi8-r', $string); } } RT->Config->Set(EmailInputEncodings => qw(UTF-8 koi8-r cp1251)); SKIP: { guess('utf-8', $string); guess('koi8-r', $string); { local $TODO = "Encode::Guess can't distinguish cp1251 from koi8-r"; guess('cp1251', $string); } } # windows-1251 is an alias for cp1251, post load check cleanups array for us RT->Config->Set(EmailInputEncodings => qw(UTF-8 windows-1251 koi8-r)); RT->Config->PostLoadCheck; SKIP: { guess('utf-8', $string); guess('cp1251', $string); { local $TODO = "Encode::Guess can't distinguish cp1251 from koi8-r"; guess('koi8-r', $string); } } RT->Config->Set(EmailInputEncodings => qw(* UTF-8 cp1251 koi8-r)); SKIP: { guess('utf-8', $string); guess('cp1251', $string); guess('koi8-r', $string); } rt-5.0.1/t/api/squish.t000644 000765 000024 00000001015 14005011336 015513 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test nodb => 1, tests => undef; use Test::Warn; use RT::Squish; my $squish; warning_like { $squish = RT::Squish->new(); } [qr/implement/], "warns this is only an abstract base class"; for my $method ( qw/Content ModifiedTime ModifiedTimeString Key/ ) { can_ok($squish, $method); } like( $squish->Key, qr/[a-f0-9]{32}/, 'Key is like md5' ); ok( (time()-$squish->ModifiedTime) <= 2, 'ModifiedTime' ); use RT::Squish::CSS; can_ok('RT::Squish::CSS', 'Style'); done_testing; rt-5.0.1/t/api/reminder-permissions.t000644 000765 000024 00000002145 14005011336 020362 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 9; my $user_a = RT::Test->load_or_create_user( Name => 'user_a', Password => 'password', ); ok( $user_a && $user_a->id, 'created user_a' ); ok( RT::Test->add_rights( { Principal => $user_a, Right => [qw/SeeQueue CreateTicket ShowTicket OwnTicket/] }, ), 'add basic rights for user_a' ); my $ticket = RT::Test->create_ticket( Subject => 'test reminder permission', Queue => 'General', ); ok( $ticket->id, 'created a ticket' ); $ticket->CurrentUser($user_a); my ( $status, $msg ) = $ticket->Reminders->Add( Subject => 'user a reminder', Owner => $user_a->id, ); ok( !$status, "couldn't create reminders without ModifyTicket: $msg" ); ok( RT::Test->add_rights( { Principal => $user_a, Right => [qw/ModifyTicket/] }, ), 'add ModifyTicket right for user_a' ); ( $status, $msg ) = $ticket->Reminders->Add( Subject => 'user a reminder', Owner => $user_a->id, ); ok( $status, "created a reminder with ModifyTicket: $msg" ); rt-5.0.1/t/api/condition-ownerchange.t000644 000765 000024 00000002423 14005011336 020467 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => 11; { my $q = RT::Queue->new(RT->SystemUser); $q->Create(Name =>'ownerChangeTest'); ok($q->Id, "Created a scriptest queue"); my $s1 = RT::Scrip->new(RT->SystemUser); my ($val, $msg) =$s1->Create( Queue => $q->Id, ScripAction => 'User Defined', ScripCondition => 'On Owner Change', CustomIsApplicableCode => '', CustomPrepareCode => 'return 1', CustomCommitCode => ' $self->TicketObj->SetPriority($self->TicketObj->Priority+1); return(1); ', Template => 'Blank' ); ok($val,$msg); my $ticket = RT::Ticket->new(RT->SystemUser); my ($tv,$ttv,$tm) = $ticket->Create(Queue => $q->Id, Subject => "hair on fire", InitialPriority => '20' ); ok($tv, $tm); ok($ticket->SetOwner('root')); is ($ticket->Priority , '21', "Ticket priority is set right"); ok($ticket->Steal); is ($ticket->Priority , '22', "Ticket priority is set right"); ok($ticket->Untake); is ($ticket->Priority , '23', "Ticket priority is set right"); ok($ticket->Take); is ($ticket->Priority , '24', "Ticket priority is set right"); } rt-5.0.1/t/api/condition-reject.t000644 000765 000024 00000002367 14005011336 017452 0ustar00sunnavystaff000000 000000 # # Check that the "On Reject" scrip condition exists and is working # use strict; use warnings; use RT; use RT::Test tests => 7; { my $q = RT::Queue->new(RT->SystemUser); $q->Create(Name =>'rejectTest'); ok($q->Id, "Created a scriptest queue"); my $s1 = RT::Scrip->new(RT->SystemUser); my ($val, $msg) =$s1->Create( Queue => $q->Id, ScripAction => 'User Defined', ScripCondition => 'On reject', CustomIsApplicableCode => '', CustomPrepareCode => 'return 1', CustomCommitCode => ' $self->TicketObj->SetPriority($self->TicketObj->Priority+1); return(1); ', Template => 'Blank' ); ok($val,$msg); my $ticket = RT::Ticket->new(RT->SystemUser); my ($tv,$ttv,$tm) = $ticket->Create(Queue => $q->Id, Subject => "hair on fire", InitialPriority => '20' ); ok($tv, $tm); ok($ticket->SetStatus('rejected'), "Status set to \"rejected\""); is ($ticket->Priority , '21', "Condition is true, scrip triggered"); ok($ticket->SetStatus('open'), "Status set to \"open\""); is ($ticket->Priority , '21', "Condition is false, scrip skipped"); } rt-5.0.1/t/api/rt.t000644 000765 000024 00000000537 14005011336 014634 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test nodata => 1, tests => 4; { is (RT->Nobody->Name() , 'Nobody', "Nobody is nobody"); isnt (RT->Nobody->Name() , 'root', "Nobody isn't named root"); is (RT->SystemUser->Name() , 'RT_System', "The system user is RT_System"); isnt (RT->SystemUser->Name() , 'noname', "The system user isn't noname"); } rt-5.0.1/t/api/initialdata.t000644 000765 000024 00000001045 14005011336 016465 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 'no_declare'; # This test script processes the sample initialdata file in # ../data/initialdata/initialdata # To add initialdata tests, add the data to the initialdata file and it # will be processed by this script. my $initialdata = RT::Test::get_relocatable_file("initialdata" => "..", "data", "initialdata"); my ($rv, $msg) = RT->DatabaseHandle->InsertData( $initialdata, undef, disconnect_after => 0 ); ok($rv, "Inserted test data from $initialdata") or diag "Error: $msg"; done_testing();rt-5.0.1/t/api/transaction-quoting.t000644 000765 000024 00000021417 14005011336 020220 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => 19; use_ok('RT::Transaction'); diag "Test quoting on transaction content"; { my $mail = <<'.'; From: root@localhost Subject: Testing quoting on long lines Content-Type: text/plain > This is a short line. This is a short line. . my ( $status, $id ) = RT::Test->send_via_mailgate($mail); is( $status >> 8, 0, "The mail gateway exited normally" ); ok( $id, "Created ticket $id" ); my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $id ); my $txns = $ticket->Transactions; my $txn = $txns->Next; my $expected = <<'QUOTED'; > > This is a short line. > > This is a short line. QUOTED my ($content) = $txn->Content( Quote => 1 ); like( $content, qr/$expected/, 'Text quoted properly'); } diag "Test quoting on transaction content with lines > 70 chars"; { my $mail = <<'.'; From: root@localhost Subject: Testing quoting on long lines Content-Type: text/plain > This is a line that is longer than the 70 characters that will demonstrate quoting when text is wrapped to multiple lines. This is a line that is longer than the 70 characters that will demonstrate quoting when text is wrapped to multiple lines. . my ( $status, $id ) = RT::Test->send_via_mailgate($mail); is( $status >> 8, 0, "The mail gateway exited normally" ); ok( $id, "Created ticket $id" ); my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $id ); my $txns = $ticket->Transactions; my $txn = $txns->Next; my $expected = <<'QUOTED'; > > This is a line that is longer than the 70 characters that will > > demonstrate quoting when text is wrapped to multiple lines. > > This is a line that is longer than the 70 characters that will > demonstrate quoting when text is wrapped to multiple lines. QUOTED my ($content) = $txn->Content( Quote => 1 ); like( $content, qr/$expected/, 'Text quoted properly'); } diag "More complex quoting"; { my $mail = <<'.'; From: root@localhost Subject: Testing quoting on long lines Content-Type: text/plain # # This is a line that is longer than the 70 characters that will demonstrate quoting when text is wrapped to multiple lines. # # This is a line that is longer than the 70 characters that will demonstrate quoting when text is wrapped to multiple lines. > This is a line that is longer than the 70 characters that will demonstrate quoting when text is wrapped to multiple lines. > This is a line that is longer than the 70 characters that will demonstrate quoting when text is wrapped to multiple lines. This is a line that is longer than the 70 characters that will demonstrate quoting when text is wrapped to multiple lines. This is a line that is longer than the 70 characters that will demonstrate quoting when text is wrapped to multiple lines. . my ( $status, $id ) = RT::Test->send_via_mailgate($mail); is( $status >> 8, 0, "The mail gateway exited normally" ); ok( $id, "Created ticket $id" ); my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $id ); my $txns = $ticket->Transactions; my $txn = $txns->Next; my $expected = < # # This is a line that is longer than the 70 characters that will > # # demonstrate quoting when text is wrapped to multiple lines. > # # This is a line that is longer than the 70 characters that will > # # demonstrate quoting when text is wrapped to multiple lines. > > This is a line that is longer than the 70 characters that will > > demonstrate quoting when text is wrapped to multiple lines. > > This is a line that is longer than the 70 characters that will > > demonstrate quoting when text is wrapped to multiple lines. > > This is a line that is longer than the 70 characters that will > demonstrate quoting when text is wrapped to multiple lines. > This is a line that is longer than the 70 characters that will > demonstrate quoting when text is wrapped to multiple lines. QUOTED my ($content) = $txn->Content( Quote => 1 ); like( $content, qr/$expected/, 'Text quoted properly'); } diag "Test different wrap value"; { my $mail = <<'.'; From: root@localhost Subject: Testing quoting on long lines Content-Type: text/plain > This is a line that is longer than the 70 characters that will demonstrate quoting when text is wrapped to multiple lines. This is a line that is longer than the 70 characters that will demonstrate quoting when text is wrapped to multiple lines. . my ( $status, $id ) = RT::Test->send_via_mailgate($mail); is( $status >> 8, 0, "The mail gateway exited normally" ); ok( $id, "Created ticket $id" ); my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $id ); my $txns = $ticket->Transactions; my $txn = $txns->Next; my $expected = <<'QUOTED'; > > This is a line that is longer > > than the 70 characters that > > will demonstrate quoting when > > text is wrapped to multiple > > lines. > > This is a line that is longer > than the 70 characters that > will demonstrate quoting when > text is wrapped to multiple > lines. QUOTED my ($content) = $txn->Content( Quote => 1, Wrap => 30 ); like( $content, qr/$expected/, 'Text quoted properly'); } diag "Test no quoting on transaction content"; { my $mail = <<'.'; From: root@localhost Subject: Testing quoting on long lines Content-Type: text/plain > This is a line that is longer than the 70 characters that will demonstrate quoting when text is wrapped to multiple lines. This is a line that is longer than the 70 characters that will demonstrate quoting when text is wrapped to multiple lines. . my ( $status, $id ) = RT::Test->send_via_mailgate($mail); is( $status >> 8, 0, "The mail gateway exited normally" ); ok( $id, "Created ticket $id" ); my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $id ); my $txns = $ticket->Transactions; my $txn = $txns->Next; my $expected = <<'QUOTED'; > This is a line that is longer than the 70 characters that will demonstrate quoting when text is wrapped to multiple lines. This is a line that is longer than the 70 characters that will demonstrate quoting when text is wrapped to multiple lines. QUOTED my ($content) = $txn->Content( ); # Quote defaults to 0 like( $content, qr/$expected/, 'Text quoted properly'); } diag "Test wrapping with no initial quoting"; { my $content =< This is a line that is longer than the 70 characters that will > demonstrate quoting when text is wrapped to multiple lines. It is not > already quoted > This is a line that is exactly 70 characters before it is wrapped here > This is a line that is exactly 76 characters before it is wrapped by > the code > This is a short line. > These should remain separate lines. > > Line after a line break. EXPECTED my $txn = RT::Transaction->new(RT->SystemUser); my $result = $txn->ApplyQuoteWrap( content => $content, cols => 70 ); is( $result, $expected, 'Text quoted properly after one quoting.'); $expected =< > This is a line that is longer than the 70 characters that will > > demonstrate quoting when text is wrapped to multiple lines. It is not > > already quoted > > This is a line that is exactly 70 characters before it is wrapped here > > This is a line that is exactly 76 characters before it is wrapped by > > the code > > This is a short line. > > These should remain separate lines. > > > > Line after a line break. EXPECTED $result = $txn->ApplyQuoteWrap( content => $result, cols => 70 ); is( $result, $expected, 'Text quoted properly after two quotings'); # Wrapping is only triggered over 76 chars, so quote until 76 is exceeded $result = $txn->ApplyQuoteWrap( content => $result, cols => 70 ); $result = $txn->ApplyQuoteWrap( content => $result, cols => 70 ); $result = $txn->ApplyQuoteWrap( content => $result, cols => 70 ); $expected =< > > > > This is a line that is longer than the 70 characters that will > > > > > demonstrate quoting when text is wrapped to multiple lines. It > > > > > is not > > > > > already quoted > > > > > This is a line that is exactly 70 characters before it is > > > > > wrapped here > > > > > This is a line that is exactly 76 characters before it is > > > > > wrapped by > > > > > the code > > > > > This is a short line. > > > > > These should remain separate lines. > > > > > > > > > > Line after a line break. EXPECTED is( $result, $expected, 'Text quoted properly after five quotings'); } rt-5.0.1/t/api/cron.t000644 000765 000024 00000007143 14005011336 015150 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test nodata => 1, tests => 18; ### Set up some testing data. Test the testing data because why not? # Create a user with rights, a queue, and some tickets. my $user_obj = RT::User->new(RT->SystemUser); my ($ret, $msg) = $user_obj->LoadOrCreateByEmail('tara@example.com'); ok($ret, 'record test user creation'); $user_obj->SetName('tara'); $user_obj->PrincipalObj->GrantRight(Right => 'SuperUser'); my $CurrentUser = RT::CurrentUser->new('tara'); # Create our template, which will be used for tests of RT::Action::Record*. my $template_content = 'RT-Send-Cc: tla@example.com RT-Send-Bcc: jesse@example.com This is a content string with no content.'; my $template_obj = RT::Template->new($CurrentUser); $template_obj->Create(Queue => '0', Name => 'recordtest', Description => 'testing Record actions', Content => $template_content, ); # Create a queue and some tickets. my $queue_obj = RT::Queue->new($CurrentUser); ($ret, $msg) = $queue_obj->Create(Name => 'recordtest', Description => 'queue for Action::Record testing'); ok($ret, 'record test queue creation'); my $ticket1 = RT::Ticket->new($CurrentUser); my ($id, $tobj, $msg2) = $ticket1->Create(Queue => $queue_obj, Requestor => ['tara@example.com'], Subject => 'bork bork bork', Priority => 22, ); ok($id, 'record test ticket creation 1'); my $ticket2 = RT::Ticket->new($CurrentUser); ($id, $tobj, $msg2) = $ticket2->Create(Queue => $queue_obj, Requestor => ['root@localhost'], Subject => 'hurdy gurdy' ); ok($id, 'record test ticket creation 2'); ### OK. Have data, will travel. # First test the search. ok(require RT::Search::FromSQL, "Search::FromSQL loaded"); my $ticketsqlstr = "Requestor.EmailAddress = '" . $CurrentUser->EmailAddress . "' AND Priority > '20'"; my $search = RT::Search::FromSQL->new(Argument => $ticketsqlstr, TicketsObj => RT::Tickets->new($CurrentUser), ); is(ref($search), 'RT::Search::FromSQL', "search created"); ok($search->Prepare(), "fromsql search run"); my $counter = 0; while(my $t = $search->TicketsObj->Next() ) { is($t->Id(), $ticket1->Id(), "fromsql search results 1"); $counter++; } is ($counter, 1, "fromsql search results 2"); # Right. Now test the actions. ok(require RT::Action::RecordComment); ok(require RT::Action::RecordCorrespondence); my ($comment_act, $correspond_act); ok($comment_act = RT::Action::RecordComment->new(TicketObj => $ticket1, TemplateObj => $template_obj, CurrentUser => $CurrentUser), "RecordComment created"); ok($correspond_act = RT::Action::RecordCorrespondence->new(TicketObj => $ticket2, TemplateObj => $template_obj, CurrentUser => $CurrentUser), "RecordCorrespondence created"); ok($comment_act->Prepare(), "Comment prepared"); ok($correspond_act->Prepare(), "Correspond prepared"); ok($comment_act->Commit(), "Comment committed"); ok($correspond_act->Commit(), "Correspondence committed"); # Now test for loop suppression. my ($trans, $desc, $transaction) = $ticket2->Comment(MIMEObj => $template_obj->MIMEObj); my $bogus_action = RT::Action::RecordComment->new(TicketObj => $ticket1, TemplateObj => $template_obj, TransactionObj => $transaction, CurrentUser => $CurrentUser); ok(!$bogus_action->Prepare(), "Comment aborted to prevent loop"); rt-5.0.1/t/api/currentuser.t000644 000765 000024 00000001170 14005011336 016562 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test noinitialdata => 1, tests => 8; { ok (require RT::CurrentUser); } { ok (my $cu = RT::CurrentUser->new('root')); ok (my $lh = $cu->LanguageHandle('en-us')); isnt ($lh, undef, '$lh is defined'); ok ($lh->isa('Locale::Maketext')); is ($cu->loc('TEST_STRING'), "Concrete Mixer", "Localized TEST_STRING into English"); SKIP: { skip "French localization is not enabled", 2 unless grep $_ && $_ =~ /^(\*|fr)$/, RT->Config->Get('LexiconLanguages'); ok ($lh = $cu->LanguageHandle('fr')); is ($cu->loc('before'), "avant", "Localized TEST_STRING into French"); } } rt-5.0.1/t/api/rights.t000644 000765 000024 00000013200 14005011336 015476 0ustar00sunnavystaff000000 000000 use RT::Test nodata => 1, tests => 38; use strict; use warnings; use Test::Warn; sub reset_rights { RT::Test->set_rights } # clear all global right reset_rights; my $queue = RT::Test->load_or_create_queue( Name => 'Regression' ); ok $queue && $queue->id, 'loaded or created queue'; my $qname = $queue->Name; my $user = RT::Test->load_or_create_user( Name => 'user', Password => 'password', ); ok $user && $user->id, 'loaded or created user'; { ok( !$user->HasRight( Right => 'OwnTicket', Object => $queue ), "user can't own ticket" ); ok( !$user->HasRight( Right => 'ReplyToTicket', Object => $queue ), "user can't reply to ticket" ); } { my $group = $queue->RoleGroup( 'Owner' ); ok( $group->Id, "load queue owners role group" ); my $ace = RT::ACE->new( RT->SystemUser ); my ($ace_id, $msg) = $group->PrincipalObj->GrantRight( Right => 'ReplyToTicket', Object => $queue ); ok( $ace_id, "Granted queue owners role group with ReplyToTicket right: $msg" ); ok( $group->PrincipalObj->HasRight( Right => 'ReplyToTicket', Object => $queue ), "role group can reply to ticket" ); ok( !$user->HasRight( Right => 'ReplyToTicket', Object => $queue ), "user can't reply to ticket" ); } my $ticket; { # new ticket $ticket = RT::Ticket->new(RT->SystemUser); my ($ticket_id) = $ticket->Create( Queue => $queue->id, Subject => 'test'); ok( $ticket_id, 'new ticket created' ); is( $ticket->Owner, RT->Nobody->Id, 'owner of the new ticket is nobody' ); ok( !$user->HasRight( Right => 'OwnTicket', Object => $ticket ), "user can't reply to ticket" ); my ($status, $msg) = $ticket->SetOwner( $user->id ); ok( !$status, "no permissions to be an owner" ); } { my ($status, $msg) = $user->PrincipalObj->GrantRight( Object => $queue, Right => 'OwnTicket' ); ok( $status, "successfuly granted right: $msg" ); ok( $user->HasRight( Right => 'OwnTicket', Object => $queue ), "user can own ticket" ); ok( $user->HasRight( Right => 'OwnTicket', Object => $ticket ), "user can own ticket" ); ($status, $msg) = $ticket->SetOwner( $user->id ); ok( $status, "successfuly set owner: $msg" ); is( $ticket->Owner, $user->id, "set correct owner" ); ok( $user->HasRight( Right => 'ReplyToTicket', Object => $ticket ), "user is owner and can reply to ticket" ); } { # Testing of EquivObjects my $group = $queue->RoleGroup( 'AdminCc' ); ok( $group->Id, "load queue AdminCc role group" ); my $ace = RT::ACE->new( RT->SystemUser ); my ($ace_id, $msg) = $group->PrincipalObj->GrantRight( Right => 'ModifyTicket', Object => $queue ); ok( $ace_id, "Granted queue AdminCc role group with ModifyTicket right: $msg" ); ok( $group->PrincipalObj->HasRight( Right => 'ModifyTicket', Object => $queue ), "role group can modify ticket" ); ok( !$user->HasRight( Right => 'ModifyTicket', Object => $ticket ), "user is not AdminCc and can't modify ticket" ); } { my ($status, $msg) = $ticket->AddWatcher( Type => 'AdminCc', PrincipalId => $user->PrincipalId ); ok( $status, "successfuly added user as AdminCc"); ok( $user->HasRight( Right => 'ModifyTicket', Object => $ticket ), "user is AdminCc and can modify ticket" ); } my $ticket2; { $ticket2 = RT::Ticket->new(RT->SystemUser); my ($id) = $ticket2->Create( Queue => $queue->id, Subject => 'test2'); ok( $id, 'new ticket created' ); ok( !$user->HasRight( Right => 'ModifyTicket', Object => $ticket2 ), "user is not AdminCc and can't modify ticket2" ); # now we can finally test EquivObjectsa my $has = $user->HasRight( Right => 'ModifyTicket', Object => $ticket2, EquivObjects => [$ticket], ); ok( $has, "user is not AdminCc but can modify ticket2 because of EquivObjects" ); } { # the first a third test below are the same, so they should both pass # make sure passed equive list is not changed my @list = (); ok( !$user->HasRight( Right => 'ModifyTicket', Object => $ticket2, EquivObjects => \@list ), "user is not AdminCc and can't modify ticket2" ); ok( $user->HasRight( Right => 'ModifyTicket', Object => $ticket, EquivObjects => \@list ), "user is AdminCc and can modify ticket" ); ok( !$user->HasRight( Right => 'ModifyTicket', Object => $ticket2, EquivObjects => \@list ), "user is not AdminCc and can't modify ticket2 (same question different answer)" ); } my $queue2 = RT::Test->load_or_create_queue( Name => 'Rights' ); ok $queue2 && $queue2->id, 'loaded or created queue'; my $user2 = RT::Test->load_or_create_user( Name => 'user2', Password => 'password', ); ok $user2 && $user2->id, 'Created user: ' . $user2->Name . ' with id ' . $user2->Id; warning_like { ok( !$user2->HasRight( Right => 'Foo', Object => $queue2 ), "HasRight false for invalid right Foo" ); } qr/Invalid right\. Couldn't canonicalize right 'Foo'/, 'Got warning on invalid right'; note "Right name canonicalization"; { reset_rights; my ($ok, $msg) = $user->PrincipalObj->GrantRight( Right => "showticket", Object => RT->System, ); ok $ok, "Granted showticket: $msg"; ok $user->HasRight( Right => "ShowTicket", Object => RT->System ), "HasRight ShowTicket"; reset_rights; ($ok, $msg) = $user->PrincipalObj->GrantRight( Right => "ShowTicket", Object => RT->System, ); ok $ok, "Granted ShowTicket: $msg"; ok $user->HasRight( Right => "showticket", Object => RT->System ), "HasRight showticket"; } rt-5.0.1/t/api/report_tickets.t000644 000765 000024 00000000711 14005011336 017242 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 5; use RT::Report::Tickets; my $ticket = RT::Test->create_ticket( Queue => 'General', Subject => 'test' ); my $tickets = RT::Report::Tickets->new( RT->SystemUser ); $tickets->FromSQL('Updated <= "tomorrow"'); is( $tickets->Count, 1, "search with transaction join and positive results" ); $tickets->FromSQL('Updated < "yesterday"'); is( $tickets->Count, 0, "search with transaction join and 0 results" ); rt-5.0.1/t/api/group-rights.t000644 000765 000024 00000012711 14005011336 016636 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => 114; RT::Group->AddRight( General => 'RTxGroupRight' => 'Just a right for testing rights', ); # this company is split into two halves, the hackers and the non-hackers # herbert is a hacker but eric is not. my $herbert = RT::User->new(RT->SystemUser); my ($ok, $msg) = $herbert->Create(Name => 'herbert'); ok($ok, $msg); my $eric = RT::User->new(RT->SystemUser); ($ok, $msg) = $eric->Create(Name => 'eric'); ok($ok, $msg); my $hackers = RT::Group->new(RT->SystemUser); ($ok, $msg) = $hackers->CreateUserDefinedGroup(Name => 'Hackers'); ok($ok, $msg); my $employees = RT::Group->new(RT->SystemUser); ($ok, $msg) = $employees->CreateUserDefinedGroup(Name => 'Employees'); ok($ok, $msg); ($ok, $msg) = $employees->AddMember($hackers->PrincipalId); ok($ok, $msg); ($ok, $msg) = $hackers->AddMember($herbert->PrincipalId); ok($ok, $msg); ($ok, $msg) = $employees->AddMember($eric->PrincipalId); ok($ok, $msg); ok($employees->HasMemberRecursively($hackers->PrincipalId), 'employees has member hackers'); ok($employees->HasMemberRecursively($herbert->PrincipalId), 'employees has member herbert'); ok($employees->HasMemberRecursively($eric->PrincipalId), 'employees has member eric'); ok($hackers->HasMemberRecursively($herbert->PrincipalId), 'hackers has member herbert'); ok(!$hackers->HasMemberRecursively($eric->PrincipalId), 'hackers does not have member eric'); # There's also a separate group, "Other", which both are a member of. my $other = RT::Group->new(RT->SystemUser); ($ok, $msg) = $other->CreateUserDefinedGroup(Name => 'Other'); ok($ok, $msg); ($ok, $msg) = $other->AddMember($eric->PrincipalId); ok($ok, $msg); ($ok, $msg) = $other->AddMember($herbert->PrincipalId); ok($ok, $msg); # Everyone can SeeGroup on all three groups my $everyone = RT::Group->new( RT->SystemUser ); ($ok, $msg) = $everyone->LoadSystemInternalGroup( 'Everyone' ); ok($ok, $msg); $everyone->PrincipalObj->GrantRight(Right => 'SeeGroup', Object => $employees); $everyone->PrincipalObj->GrantRight(Right => 'SeeGroup', Object => $hackers); $everyone->PrincipalObj->GrantRight(Right => 'SeeGroup', Object => $other); sub CheckRights { my $cu = shift; my %groups = (Employees => 0, Hackers => 0, Other => 0, @_); my $name = $cu->Name; my $groups = RT::Groups->new(RT::CurrentUser->new($cu)); $groups->LimitToUserDefinedGroups; $groups->ForWhichCurrentUserHasRight(Right => 'RTxGroupRight'); my %has_right = map { ($_->Name => 1) } @{ $groups->ItemsArrayRef }; local $Test::Builder::Level = $Test::Builder::Level + 1; for my $groupname (sort keys %groups) { my $g = RT::Group->new(RT::CurrentUser->new($cu)); $g->LoadUserDefinedGroup($groupname); if ($groups{$groupname}) { ok( $g->CurrentUserHasRight("RTxGroupRight"), "$name has right on $groupname (direct query)" ); ok( delete $has_right{$groupname}, "..and also in ForWhichCurrentUserHasRight"); } else { ok( !$g->CurrentUserHasRight("RTxGroupRight"), "$name doesn't have right on $groupname (direct query)" ); ok( !delete $has_right{$groupname}, "..and also not in ForWhichCurrentUserHasRight"); } } ok(not(keys %has_right), "ForWhichCurrentUserHasRight has no extra groups"); } # Neither should have it on any group yet CheckRights($eric); CheckRights($herbert); # Grant it to employees, on employees. Both Herbert and Eric will have # it on employees, though Herbert gets it by way of hackers. Neither # will have it on hackers, because the target does not recurse. $employees->PrincipalObj->GrantRight( Right => 'RTxGroupRight', Object => $employees); CheckRights($eric, Employees => 1); CheckRights($herbert, Employees => 1); # Grant it to employees, on hackers. This means both Eric and Herbert # will have the right on hackers, but not on employees. $employees->PrincipalObj->RevokeRight(Right => 'RTxGroupRight', Object => $employees); $employees->PrincipalObj->GrantRight( Right => 'RTxGroupRight', Object => $hackers); CheckRights($eric, Hackers => 1); CheckRights($herbert, Hackers => 1); # Grant it to hackers, on employees. Eric will have it nowhere, and # Herbert will have it on employees. Note that the target of the right # itself does _not_ recurse down, so Herbert will not have it on # hackers. $employees->PrincipalObj->RevokeRight(Right => 'RTxGroupRight', Object => $hackers); $hackers->PrincipalObj->GrantRight( Right => 'RTxGroupRight', Object => $employees); CheckRights($eric); CheckRights($herbert, Employees => 1); # Grant it globally to hackers; herbert will see the right on all # employees, hackers, and other. $hackers->PrincipalObj->RevokeRight( Right => 'RTxGroupRight', Object => $employees); $hackers->PrincipalObj->GrantRight( Right => 'RTxGroupRight', Object => RT->System); CheckRights($eric); CheckRights($herbert, Employees => 1, Hackers => 1, Other => 1 ); # Grant it globally to employees; both eric and herbert will see the # right on all employees, hackers, and other. $hackers->PrincipalObj->RevokeRight( Right => 'RTxGroupRight', Object => RT->System); $employees->PrincipalObj->GrantRight( Right => 'RTxGroupRight', Object => RT->System); CheckRights($eric, Employees => 1, Hackers => 1, Other => 1 ); CheckRights($herbert, Employees => 1, Hackers => 1, Other => 1 ); # Disable the employees group. Neither eric nor herbert will see the # right anywhere. $employees->SetDisabled(1); CheckRights($eric); CheckRights($herbert); rt-5.0.1/t/api/config.t000644 000765 000024 00000004415 14005011336 015453 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => undef; use Test::Warn; ok( RT::Config->AddOption( Name => 'foo', Section => 'bar', ), 'added option foo' ); my $meta = RT::Config->Meta('foo'); is( $meta->{Section}, 'bar', 'Section is bar' ); is( $meta->{Widget}, '/Widgets/Form/String', 'default Widget is string' ); is_deeply( $meta->{WidgetArguments}, {},, 'default WidgetArguments is empty hashref' ); ok( RT::Config->UpdateOption( Name => 'foo', Section => 'baz', Widget => '/Widgets/Form/Boolean', ), 'updated option foo to section baz' ); is( $meta->{Section}, 'baz', 'section is updated to baz' ); 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_like {RT::Config->PostLoadCheck} qr{Unknown encoding \'foo\' in \@EmailInputEncodings option}, 'Correct warning for encoding foo'; RT::Config->Set( WebDefaultStylesheet => 'non-existent-skin-name' ); warning_like {RT::Config->PostLoadCheck} qr{elevator-light}, 'Correct warning for default stylesheet'; my @canonical_encodings = RT::Config->Get('EmailInputEncodings'); is_deeply(\@encodings, \@canonical_encodings, 'Got correct encoding list'); RT->Config->Set( ExternalSettings => { 'My_LDAP' => { 'user' => 'rt_ldap_username', 'pass' => 'rt_ldap_password', 'net_ldap_args' => [ raw => qr/^givenName/, ], subroutine => sub { }, }, } ); my $external_settings = RT::Config->GetObfuscated( 'ExternalSettings', RT->SystemUser ); is( $external_settings->{My_LDAP}{user}, 'rt_ldap_username', 'plain value' ); is( $external_settings->{My_LDAP}{pass}, 'Password not printed', 'obfuscated password' ); is( $external_settings->{My_LDAP}{net_ldap_args}[ 1 ], qr/^givenName/, 'regex correct' ); is( ref $external_settings->{My_LDAP}{subroutine}, 'CODE', 'subroutine type correct' ); done_testing; rt-5.0.1/t/api/json-initialdata.t000644 000765 000024 00000005322 14005011336 017436 0ustar00sunnavystaff000000 000000 use warnings; use strict; use RT::Test tests => undef; RT->Config->Set('InitialdataFormatHandlers' => [ 'perl', 'RT::Initialdata::JSON' ]); # None of this should be in the DB CheckDB('not_ok'); # Load the db from the initialdata test file my $initialdata = RT::Test::get_relocatable_file("initialdata.json" => "..", "data", "initialdata"); my ($rv, $msg) = RT->DatabaseHandle->InsertData($initialdata, undef, disconnect_after => 0); ok ($rv, "Insert test data from $initialdata ($msg)"); # Now all of this should be in the DB CheckDB('ok'); done_testing(); sub CheckDB { no strict 'refs'; no warnings 'uninitialized'; my $tester = shift; my ($r,$m); my $su = RT->SystemUser; ($r,$m) = RT::Group->new($su)->LoadByCols(Name => 'Test Group 1'); &$tester ($r, "Test Group 1 found in DB - should be $tester ($m)"); my $tu1 = RT::User->new($su); ($r,$m) = $tu1->Load('testuser1'); &$tester ($r, "testuser1 user found in DB - should be $tester ($m)"); my $tq1 = RT::Queue->new($su); ($r,$m) = $tq1->Load('Test Queue 1'); &$tester ($r, "Test Queue 1 found in DB - should be $tester ($m)"); &$tester ($tu1->HasRight(Object => $tq1, Right => 'SeeQueue'), "testuser1 has SeeQueue on Test Queue 1 - should be $tester" ) if ($tu1->id and $tq1->id); ($r,$m) = RT::ScripAction->new($su)->Load('Test Action 1'); &$tester ($r, "Test Action 1 found in DB - should be $tester ($m)"); ($r,$m) = RT::ScripCondition->new($su)->Load('Test Condition 1'); &$tester ($r, "Test Condition 1 found in DB - should be $tester ($m)"); ($r,$m) = RT::Template->new($su)->Load('Test Template 1'); &$tester ($r, "Test Template 1 found in DB - should be $tester ($m)"); ($r,$m) = RT::CustomField->new($su)->Load('Favorite Color red or blue'); &$tester ($r, "Favorite Color CF found in DB - should be $tester ($m)"); ($r,$m) = RT::CustomField->new($su)->Load('Favorite Song'); &$tester ($r, "Favorite Song CF found in DB - should be $tester ($m)"); ($r,$m) = RT::Scrip->new($su)->LoadByCols(Description => 'Test Scrip 1'); &$tester ($r, "Test Scrip 1 found in DB - should be $tester ($m)"); ($r,$m) = RT::Attribute->new($su)->LoadByNameAndObject( Name => 'Test Search 1', Object => RT->System ); &$tester ($r, "Test Search 1 found in DB - should be $tester ($m)"); my $root = RT::Test->load_or_create_user( Name => 'root' ); ( $r, $m ) = RT::Attribute->new($su)->LoadByNameAndObject( Name => 'Test Search 2', Object => $root ); &$tester( $r, "Test Search 2 found in DB - should be $tester ($m)" ); } sub not_ok { local $Test::Builder::Level = $Test::Builder::Level + 1; ok (!shift, shift); } rt-5.0.1/t/api/template-parsing.t000644 000765 000024 00000017371 14005011336 017467 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => undef; 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 14", ); 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/; warning_like { TemplateTest( Content => "\ntest { \$rtname ", SyntaxError => 1, PerlOutput => undef, SimpleOutput => undef, ); } qr/Template parsing error in Test-\d+ \(#\d+\): End of data inside program text/; 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"); note "test arguments passing"; { PerlTemplateTest( Content => "\ntest { \$Nonexistent }", Output => "test ", ); PerlTemplateTest( Content => "\ntest { \$Nonexistent }", Arguments => { Nonexistent => 'foo' }, Output => "test foo", ); PerlTemplateTest( Content => "\n".'array: { join ", ", @array }', Arguments => { array => [qw(foo bar)] }, Output => "array: foo, bar", ); PerlTemplateTest( Content => "\n".'hash: { join ", ", map "$_ => $hash{$_}", sort keys %hash }', Arguments => { hash => {1 => 2, a => 'b'} }, Output => "hash: 1 => 2, a => b", ); PerlTemplateTest( Content => "\n".'code: { code() }', Arguments => { code => sub { "baz" } }, Output => "code: baz", ); } # 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; done_testing; 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( $args{'Arguments'} ? ( %{ $args{'Arguments'} } ) : (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') { IndividualTemplateTest( %args, Type => $type, Output => $args{$type . 'Output'}, ); } } sub SimpleTemplateTest { local $Test::Builder::Level = $Test::Builder::Level + 1; IndividualTemplateTest( @_, Type => 'Simple' ); } sub PerlTemplateTest { local $Test::Builder::Level = $Test::Builder::Level + 1; IndividualTemplateTest( @_, Type => 'Perl' ); } rt-5.0.1/t/api/custom-date-ranges.t000644 000765 000024 00000005551 14005011336 017712 0ustar00sunnavystaff000000 000000 use warnings; use strict; use Test::MockTime qw( :all ); use RT::Test; set_fixed_time('2016-01-01T00:00:00Z'); my $cf = RT::Test->load_or_create_custom_field( Name => 'Beta Date', Type => 'DateTime', MaxValues => 1, LookupType => RT::Ticket->CustomFieldLookupType, Queue => 'General', ); ok($cf && $cf->Id, 'created Beta Date CF'); my $t = RT::Test->create_ticket( Queue => 'General', Status => 'resolved', Created => '2015-12-10 00:00:00', Starts => '2015-12-13 00:00:00', Started => '2015-12-12 12:00:00', Due => '2015-12-20 00:00:00', Resolved => '2015-12-15 18:00:00', ); # see t/customfields/datetime.t for timezone issues $t->AddCustomFieldValue(Field => 'Beta Date', Value => '2015-12-13 19:00:00'); is($t->FirstCustomFieldValue('Beta Date'), '2015-12-14 00:00:00'); my @tests = ( 'Starts - Created' => '3 days', 'Created - Starts' => '3 days prior', 'Started - Created' => '3 days', # uses only the most significant unit 'Resolved - Due' => '4 days prior', 'Due - Resolved' => '4 days', 'Due - Told' => undef, # told is unset 'now - LastContact' => undef, # told is unset 'now - LastUpdated' => '0 seconds', 'Due - CF.{Beta Date}' => '6 days', 'now - CF.{Beta Date}' => '3 weeks', 'CF.{Beta Date} - now' => '3 weeks prior', ); while (my ($spec, $expected) = splice @tests, 0, 2) { is($t->CustomDateRange(test => $spec), $expected, $spec); } is($t->CustomDateRange(test => { value => 'Resolved - Created', format => sub { my ($seconds, $end, $start, $ticket) = @_; join '/', $seconds, $end->Unix, $start->Unix, $ticket->Id; }, }), '496800/1450202400/1449705600/1', 'format'); diag 'test business time' if $ENV{'TEST_VERBOSE'}; { RT->Config->Set( ServiceAgreements => ( Default => '2h', Levels => { '2h' => { Response => 2 * 60, Timezone => 'UTC' }, }, ) ); RT->Config->Set( ServiceBusinessHours => ( 'Default' => { 1 => { Name => 'Monday', Start => '9:00', End => '18:00' }, 2 => { Name => 'Tuesday', Start => '9:00', End => '18:00' }, 3 => { Name => 'Wednesday', Start => '9:00', End => '18:00' }, 4 => { Name => 'Thursday', Start => '9:00', End => '18:00' }, 5 => { Name => 'Friday', Start => '9:00', End => '18:00' }, }, ) ); ok( $t->QueueObj->SetSLADisabled(0), 'Enabled SLA' ); ok( $t->SetSLA('2h'), 'Set sla to 2h' ); # from 2015-12-10 00:00:00 to 2015-12-15 18:00:00, there are 4 work days is( $t->CustomDateRange( test => { value => 'Resolved - Created', business_time => 1, } ), '36 hours', 'Business time of Resolved - Created' ); } rt-5.0.1/t/api/interface_web.t000644 000765 000024 00000002075 14005011336 017003 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodb => 1, tests => 5; use Test::Warn; use RT::Interface::Web; # This gets us HTML::Mason::Commands { my $cf = 2; my %args = ( 'GroupingName' => { 'Value' => "bar", 'Value-Magic' => 1 }, ); my ($ret, $grouping) = HTML::Mason::Commands::_ValidateConsistentCustomFieldValues($cf, \%args); ok ( $ret, 'No duplicates found'); is ( $grouping, 'GroupingName', 'Grouping is GroupingName'); } { my $cf = 2; my %args = ( 'GroupingName' => { 'Value' => "foo", 'Value-Magic' => 1 }, 'AnotherGrouping' => { 'Value' => "bar", 'Value-Magic' => 1 }, ); my ($ret, $grouping); warning_like { ($ret, $grouping) = HTML::Mason::Commands::_ValidateConsistentCustomFieldValues($cf, \%args); } qr/^CF 2 submitted with multiple differing values/i; ok ( !$ret, 'Caught duplicate values'); is ( $grouping, 'AnotherGrouping', 'Defaulted to AnotherGrouping'); } rt-5.0.1/t/api/safe-run-child-util.t000644 000765 000024 00000011610 14005011336 017755 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 35; use Test::Warn; use RT::Util qw(safe_run_child); use POSIX qw//; is_handle_ok(); { my $res = safe_run_child { return 1 }; is $res, 1, "correct return value"; is_handle_ok(); } # test context { my $context; my $sub = sub { if ( wantarray ) { $context = 'array'; return 1, 2, 3; } elsif ( defined wantarray ) { $context = 'scalar'; return 'foo'; } elsif ( !wantarray ) { $context = 'void'; return; } }; is_deeply [ safe_run_child { $sub->(@_) } ], [1, 2, 3]; is $context, 'array'; is_handle_ok(); is scalar safe_run_child {$sub->(@_)}, 'foo'; is $context, 'scalar'; is_handle_ok(); safe_run_child {$sub->(@_)}; is $context, 'void'; is_handle_ok(); } # fork+child returns { my $res = safe_run_child { if (fork) { wait; return 'parent' } open my $fh, '>', RT::Test->temp_directory .'/tttt'; print $fh "child"; close $fh; return 'child'; }; is $res, 'parent', "correct return value"; is( RT::Test->file_content([RT::Test->temp_directory, 'tttt'], unlink => 1 ), 'child', 'correct file content', ); is_handle_ok(); } # fork+child dies { warning_like { my $res = safe_run_child { if (fork) { wait; return 'parent' } open my $fh, '>', RT::Test->temp_directory .'/tttt'; print $fh "child"; close $fh; die 'child'; }; is $res, 'parent', "correct return value"; is( RT::Test->file_content([RT::Test->temp_directory, 'tttt'], unlink => 1 ), 'child', 'correct file content', ); } qr/System Error: child/; is_handle_ok(); } # fork+child exits { my $res = safe_run_child { if (fork) { wait; return 'parent' } open my $fh, '>', RT::Test->temp_directory .'/tttt'; print $fh "child"; close $fh; exit 0; }; is $res, 'parent', "correct return value"; is( RT::Test->file_content([RT::Test->temp_directory, 'tttt'], unlink => 1 ), 'child', 'correct file content', ); is_handle_ok(); } # parent dies { my $res = eval { safe_run_child { die 'parent'; } }; is $res, undef, "correct return value"; like $@, qr'System Error: parent', "correct error message value"; is_handle_ok(); } # fork+exec { my $script = RT::Test->temp_directory .'/true.pl'; open my $fh, '>', $script; print $fh <', '$script.res'; print \$fh "child"; close \$fh; exit 0; END close $fh; chmod 0777, $script; my $res = safe_run_child { if (fork) { wait; return 'parent' } exec $script; }; is $res, 'parent', "correct return value"; is( RT::Test->file_content([$script .'.res'], unlink => 1 ), 'child', 'correct file content', ); is_handle_ok(); } # fork+parent that doesn't wait() { require Time::HiRes; my $start = Time::HiRes::time(); my $pid; # Set up a poor man's semaphore my $all_set = 0; $SIG{USR1} = sub {$all_set++}; my $res = safe_run_child { if ($pid = fork) { return 'parent' } open my $fh, '>', RT::Test->temp_directory .'/first'; print $fh "child"; close $fh; # Signal that the first file is now all set; we need to do this # to avoid a race condition kill POSIX::SIGUSR1(), getppid(); sleep 5; open $fh, '>', RT::Test->temp_directory .'/second'; print $fh "child"; close $fh; exit 0; }; ok( Time::HiRes::time() - $start < 5, "Didn't wait until child finished" ); # Wait for up to 3 seconds to get signaled that the child has made # the file (the USR1 will break out of the sleep()). This _should_ # be immediate, but there's a race between the parent and child # here, since there's no wait()'ing. There's still a tiny race # where the signal could come in betwene the $all_set check and the # sleep, but that just means we sleep for 3 seconds uselessly. sleep 3 unless $all_set; is $res, 'parent', "correct return value"; is( RT::Test->file_content([RT::Test->temp_directory, 'first'], unlink => 1 ), 'child', 'correct file content', ); ok( not(-f RT::Test->temp_directory.'/second'), "Second file does not exist yet"); is_handle_ok(); ok(waitpid($pid,0), "Waited until child finished to reap"); is( RT::Test->file_content([RT::Test->temp_directory, 'second'], unlink => 1 ), 'child', 'correct file content', ); is_handle_ok(); } sub is_handle_ok { local $Test::Builder::Level = $Test::Builder::Level + 1; my $test = $RT::Handle->dbh->selectall_arrayref( "SELECT id FROM Users WHERE Name = 'Nobody'" ); ok $test && $test->[0][0], "selected, DB is there"; } rt-5.0.1/t/api/record.t000644 000765 000024 00000003732 14005011336 015465 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => 22; { ok (require RT::Record); } { my $ticket = RT::Ticket->new(RT->SystemUser); my $group = RT::Group->new(RT->SystemUser); is($ticket->RecordType, 'Ticket', "Ticket returns correct typestring"); is($group->RecordType, 'Group', "Group returns correct typestring"); } { my $t1 = RT::Ticket->new(RT->SystemUser); my ($id, $trans, $msg) = $t1->Create(Subject => 'DepTest1', Queue => 'general'); ok($id, "Created dep test 1 - $msg"); my $t2 = RT::Ticket->new(RT->SystemUser); (my $id2, $trans, my $msg2) = $t2->Create(Subject => 'DepTest2', Queue => 'general'); ok($id2, "Created dep test 2 - $msg2"); my $t3 = RT::Ticket->new(RT->SystemUser); (my $id3, $trans, my $msg3) = $t3->Create(Subject => 'DepTest3', Queue => 'general', Type => 'approval'); ok($id3, "Created dep test 3 - $msg3"); my ($addid, $addmsg); ok (($addid, $addmsg) =$t1->AddLink( Type => 'DependsOn', Target => $t2->id)); ok ($addid, $addmsg); ok (($addid, $addmsg) =$t1->AddLink( Type => 'DependsOn', Target => $t3->id)); ok ($addid, $addmsg); my $link = RT::Link->new(RT->SystemUser); (my $rv, $msg) = $link->Load($addid); ok ($rv, $msg); is ($link->LocalTarget , $t3->id, "Link LocalTarget is correct"); is ($link->LocalBase , $t1->id, "Link LocalBase is correct"); ok ($t1->HasUnresolvedDependencies, "Ticket ".$t1->Id." has unresolved deps"); ok (!$t1->HasUnresolvedDependencies( Type => 'blah' ), "Ticket ".$t1->Id." has no unresolved blahs"); ok ($t1->HasUnresolvedDependencies( Type => 'approval' ), "Ticket ".$t1->Id." has unresolved approvals"); ok (!$t2->HasUnresolvedDependencies, "Ticket ".$t2->Id." has no unresolved deps"); ; my ($rid, $rmsg)= $t1->SetStatus('resolved'); ok(!$rid, $rmsg); my ($rid2, $rmsg2) = $t2->SetStatus('resolved'); ok ($rid2, $rmsg2); ($rid, $rmsg)= $t1->SetStatus('resolved'); ok(!$rid, $rmsg); my ($rid3,$rmsg3) = $t3->SetStatus('resolved'); ok ($rid3,$rmsg3); ($rid, $rmsg)= $t1->SetStatus('resolved'); ok($rid, $rmsg); } rt-5.0.1/t/api/customfield.t000644 000765 000024 00000042171 14005011336 016525 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => undef; use Test::Warn; use_ok('RT::CustomField'); my $queue = RT::Queue->new( RT->SystemUser ); $queue->Load( "General" ); ok( $queue->id, "found the General queue" ); my $cf = RT::CustomField->new(RT->SystemUser); ok($cf, "Have a CustomField object"); # Use the old Queue field to set up a ticket CF my ($ok, $msg) = $cf->Create( Name => 'TestingCF', Queue => '0', Description => 'A Testing custom field', Type => 'SelectSingle' ); ok($ok, 'Global custom field correctly created'); is($cf->Type, 'Select', "Is a select CF"); ok($cf->SingleValue, "Also a single-value CF"); is($cf->MaxValues, 1, "...meaning only one value, max"); ($ok, $msg) = $cf->SetMaxValues('0'); ok($ok, "Set to infinite values: $msg"); is($cf->Type, 'Select', "Still a select CF"); ok( ! $cf->SingleValue, "No longer single-value" ); is($cf->MaxValues, 0, "...meaning no maximum values"); # Test our sanity checking of CF types ($ok, $msg) = $cf->SetType('BogusType'); ok( ! $ok, "Unable to set a custom field's type to a bogus type: $msg"); $cf = RT::CustomField->new(RT->SystemUser); ($ok, $msg) = $cf->Create( Name => 'TestingCF-bad', Queue => '0', SortOrder => '1', Description => 'A Testing custom field with a bogus Type', Type=> 'SelectSingleton' ); ok( ! $ok, "Correctly could not create with bogus type: $msg"); # Test adding and removing CFVs $cf->Load(2); ($ok, $msg) = $cf->AddValue(Name => 'foo' , Description => 'TestCFValue', SortOrder => '6'); ok($ok, "Added a new value to the select options"); ($ok, $msg) = $cf->DeleteValue($ok); ok($ok, "Deleting it again"); # Loading, and context objects $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName( Name => "TestingCF" ); ok($cf->id, "Load finds it, given just a name" ); ok( ! $cf->ContextObject, "Did not get a context object"); # Old Queue => form should find the global, gain no context object $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 0); ok($cf->id, "Load finds it, given a Name and Queue => 0" ); ok( ! $cf->ContextObject, 'Context object not set when queue is 0'); # We don't default to also searching global -- but do pick up a contextobject $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 1); ok( ! $cf->id, "Load does not finds it, given a Name and Queue => 1" ); ok($cf->ContextObject->id, 'Context object is now set'); # If we IncludeGlobal, we find it $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 1, IncludeGlobal => 1 ); ok($cf->id, "Load now finds it, given a Name and Queue => 1 and IncludeGlobal" ); ok($cf->ContextObject->id, 'Context object is also set'); # The explicit LookupType works $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', LookupType => RT::Ticket->CustomFieldLookupType ); ok($cf->id, "Load now finds it, given a Name and LookupType" ); ok( ! $cf->ContextObject, 'No context object gained'); # The explicit LookupType, ObjectId, and IncludeGlobal -- what most folks want $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', LookupType => RT::Ticket->CustomFieldLookupType, ObjectId => 1, IncludeGlobal => 1 ); ok($cf->id, "Load now finds it, given a Name, LookupType, ObjectId, IncludeGlobal" ); ok($cf->ContextObject->id, 'And gains a context obj'); # Look for a queue by name $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => "General" ); ok( ! $cf->id, "No IncludeGlobal, so queue by name fails" ); ok($cf->ContextObject->id, 'But gains a context object'); # Look for a queue by name, include global $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => "General", IncludeGlobal => 1 ); ok($cf->id, "By name, and queue name works with IncludeGlobal" ); ok($cf->ContextObject->id, 'And gains a context object'); # A bogus Queue gets you no results, but a warning $cf = RT::CustomField->new( RT->SystemUser ); warning_like { $cf->LoadByName(Name => 'TestingCF', Queue => "Bogus" ); ok( ! $cf->id, "With a bogus queue name gets no results" ); ok( ! $cf->ContextObject, 'And also no context object'); } qr/Failed to load RT::Queue 'Bogus'/, "Generates a warning"; # Ditto by number which is bogus $cf = RT::CustomField->new( RT->SystemUser ); warning_like { $cf->LoadByName(Name => 'TestingCF', Queue => "9000" ); ok( ! $cf->id, "With a bogus queue number gets no results" ); ok( ! $cf->ContextObject, 'And also no context object'); } qr/Failed to load RT::Queue '9000'/, "Generates a warning"; # But if they also wanted global results, we might have an answer $cf = RT::CustomField->new( RT->SystemUser ); warning_like { $cf->LoadByName(Name => 'TestingCF', Queue => "9000", IncludeGlobal => 1 ); ok($cf->id, "Bogus queue but IncludeGlobal founds it" ); ok( ! $cf->ContextObject, 'But no context object'); } qr/Failed to load RT::Queue '9000'/, "And generates a warning"; # Make it only apply to one queue $cf->Load(2); my $ocf = RT::ObjectCustomField->new( RT->SystemUser ); ( $ok, $msg ) = $ocf->LoadByCols( CustomField => $cf->id, ObjectId => 0 ); ok( $ok, "Found global application of CF" ); ( $ok, $msg ) = $ocf->Delete; ok( $ok, "...and deleted it"); ( $ok, $msg ) = $ocf->Add( CustomField => $cf->id, ObjectId => 1 ); ok($ok, "Applied to just queue 1" ); # Looking for it globally with Queue => 0 should fail, gain no context object $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 0); ok( ! $cf->id, "Load fails to find, given a Name and Queue => 0" ); ok( ! $cf->ContextObject, 'Context object not set when queue is 0'); # Looking it up by Queue => 1 works fine, and gets context object $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 1); ok($cf->id, "Load does finds it, given a Name and Queue => 1" ); ok($cf->ContextObject->id, 'Context object is now set'); # Also find it with IncludeGlobal $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 1, IncludeGlobal => 1 ); ok($cf->id, "Load also finds it, given a Name and Queue => 1 and IncludeGlobal" ); ok($cf->ContextObject->id, 'Context object is also set'); # The explicit LookupType works $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', LookupType => RT::Ticket->CustomFieldLookupType ); ok($cf->id, "Load also finds it, given a Name and LookupType" ); ok( ! $cf->ContextObject, 'But no context object gained'); # Explicit LookupType, ObjectId works $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', LookupType => RT::Ticket->CustomFieldLookupType, ObjectId => 1 ); ok($cf->id, "Load still finds it, given a Name, LookupType, ObjectId" ); ok($cf->ContextObject->id, 'And gains a context obj'); # Explicit LookupType, ObjectId works $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', LookupType => RT::Ticket->CustomFieldLookupType, ObjectId => 1, IncludeGlobal => 1 ); ok($cf->id, "Load also finds it, given a Name, LookupType, ObjectId, and IncludeGlobal" ); ok($cf->ContextObject->id, 'And gains a context obj'); # Look for a queue by name $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => "General" ); ok($cf->id, "Finds it by queue name" ); ok($cf->ContextObject->id, 'But gains a context object'); # Look for a queue by name, include global $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => "General", IncludeGlobal => 1 ); ok($cf->id, "By name, and queue name works with IncludeGlobal" ); ok($cf->ContextObject->id, 'And gains a context object'); # Change the lookup type to be a _queue_ CF ($ok, $msg) = $cf->SetLookupType( RT::Queue->CustomFieldLookupType ); ok($ok, "Changed CF type to be a CF on queues" ); $ocf = RT::ObjectCustomField->new( RT->SystemUser ); ( $ok, $msg ) = $ocf->Add( CustomField => $cf->id, ObjectId => 0 ); ok($ok, "Applied globally" ); # Just looking by name gets you CFs of any type $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF'); ok($cf->id, "Find the CF by name, with no queue" ); # Queue => 0 means "ticket CF", so doesn't find it $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 0); ok( ! $cf->id, "Wrong lookup type to find with Queue => 0" ); # Queue => 1 and IncludeGlobal also doesn't find it $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 0, IncludeGlobal => 1); ok( ! $cf->id, "Also doesn't find with Queue => 0 and IncludeGlobal" ); # Find it with the right LookupType $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', LookupType => RT::Queue->CustomFieldLookupType ); ok($cf->id, "Found for the right lookup type" ); # Found globally $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', LookupType => RT::Queue->CustomFieldLookupType, ObjectId => 0 ); ok($cf->id, "Found for the right lookup type and ObjectId 0" ); # Also works with Queue instead of ObjectId $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', LookupType => RT::Queue->CustomFieldLookupType, Queue => 0 ); ok($cf->id, "Found for the right lookup type and Queue 0" ); # Not found without IncludeGlobal $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', LookupType => RT::Queue->CustomFieldLookupType, ObjectId => 1 ); ok( ! $cf->id, "Not found for ObjectId 1 and no IncludeGlobal" ); # Found with IncludeGlobal $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', LookupType => RT::Queue->CustomFieldLookupType, ObjectId => 1, IncludeGlobal => 1 ); ok($cf->id, "Found for ObjectId 1 and IncludeGlobal" ); # Found with IncludeGlobal and Queue instead of ObjectId $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', LookupType => RT::Queue->CustomFieldLookupType, ObjectId => 1, IncludeGlobal => 1 ); ok($cf->id, "Found for Queue 1 and IncludeGlobal" ); # Change the lookup type to be a _transaction_ CF ($ok, $msg) = $cf->SetLookupType( RT::Transaction->CustomFieldLookupType ); ok($ok, "Changed CF type to be a CF on transactions" ); $ocf = RT::ObjectCustomField->new( RT->SystemUser ); ( $ok, $msg ) = $ocf->Add( CustomField => $cf->id, ObjectId => 0 ); ok($ok, "Applied globally" ); # Just looking by name gets you CFs of any type $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF'); ok($cf->id, "Find the CF by name, with no queue" ); # Queue => 0 means "ticket CF", so doesn't find it $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 0); ok( ! $cf->id, "Wrong lookup type to find with Queue => 0" ); # Queue => 1 and IncludeGlobal also doesn't find it $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 0, IncludeGlobal => 1); ok( ! $cf->id, "Also doesn't find with Queue => 0 and IncludeGlobal" ); # Change the lookup type to be a _user_ CF $cf->Load(2); ($ok, $msg) = $cf->SetLookupType( RT::User->CustomFieldLookupType ); ok($ok, "Changed CF type to be a CF on users" ); $ocf = RT::ObjectCustomField->new( RT->SystemUser ); ( $ok, $msg ) = $ocf->Add( CustomField => $cf->id, ObjectId => 0 ); ok($ok, "Applied globally" ); # Just looking by name gets you CFs of any type $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF'); ok($cf->id, "Find the CF by name, with no queue" ); # Queue => 0 means "ticket CF", so doesn't find it $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 0); ok( ! $cf->id, "Wrong lookup type to find with Queue => 0" ); # Queue => 1 and IncludeGlobal also doesn't find it $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 0, IncludeGlobal => 1); ok( ! $cf->id, "Also doesn't find with Queue => 0 and IncludeGlobal" ); # But RT::User->CustomFieldLookupType does $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', LookupType => RT::User->CustomFieldLookupType ); ok($cf->id, "User lookuptype does" ); # Also with an explicit global $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', LookupType => RT::User->CustomFieldLookupType, ObjectId => 0 ); ok($cf->id, "Also with user CF and explicit global" ); # Add a second, queue-specific CF to test load order $cf->Load(2); ($ok, $msg) = $cf->SetLookupType( RT::Ticket->CustomFieldLookupType ); ok($ok, "Changed CF type back to be a CF on tickets" ); $ocf = RT::ObjectCustomField->new( RT->SystemUser ); ( $ok, $msg ) = $ocf->Add( CustomField => $cf->id, ObjectId => 0 ); ok($ok, "Applied globally" ); ($ok, $msg) = $cf->SetDescription( "Global CF" ); ok($ok, "Changed CF type back to be a CF on tickets" ); ($ok, $msg) = $cf->Create( Name => 'TestingCF', Queue => '1', Description => 'Queue-specific CF', Type => 'SelectSingle' ); ok($ok, "Created second CF successfully"); # If passed just a name, you get the first by id $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF' ); like($cf->Description, qr/Global/, "Gets the first (global) one if just loading by name" ); # Ditto if also limited to lookuptype $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', LookupType => RT::Ticket->CustomFieldLookupType ); like($cf->Description, qr/Global/, "Same, if one adds a LookupType" ); # Gets the global with Queue => 0 $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 0 ); like($cf->Description, qr/Global/, "Specify Queue => 0 and get global" ); # Gets the queue with Queue => 1 $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 1 ); like($cf->Description, qr/Queue/, "Specify Queue => 1 and get the queue" ); # Gets the queue with Queue => 1 and IncludeGlobal $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 1, IncludeGlobal => 1 ); like($cf->Description, qr/Queue/, "Specify Queue => 1 and IncludeGlobal and get the queue" ); # Disable one of them ($ok, $msg) = $cf->SetDisabled(1); is($msg, "Disabled", "Disabling custom field gives correct message"); ok($ok, "Disabled the Queue-specific one"); ($ok, $msg) = $cf->SetDisabled(0); is($msg, "Enabled", "Enabling custom field gives correct message"); ok($ok, "Enabled the Queue-specific one"); ($ok, $msg) = $cf->SetDisabled(1); is($msg, "Disabled", "Disabling custom field again gives correct message"); ok($ok, "Disabled the Queue-specific one again"); # With just a name, prefers the non-disabled $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF' ); like($cf->Description, qr/Global/, "Prefers non-disabled CFs" ); # Still finds the queue one, if asked $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 1 ); like($cf->Description, qr/Queue/, "Still loads the disabled queue CF" ); # Prefers the global one if IncludeGlobal $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 1, IncludeGlobal => 1 ); like($cf->Description, qr/Global/, "Prefers the global one with IncludeGlobal" ); # IncludeDisabled allows filtering out the disabled one $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName(Name => 'TestingCF', Queue => 1, IncludeDisabled => 0 ); ok( ! $cf->id, "Doesn't find it if IncludeDisabled => 0" ); $cf->LoadByName( Name => 'TestingCF', Queue => 0, IncludeGlobal => 1 ); is( $cf->MaxValues, 0, 'Max value is 0' ); my $ticket = RT::Test->create_ticket( Queue => 1, Subject => 'test cf values' ); ok( $ticket->AddCustomFieldValue( Field => $cf, Value => 'first value' ) ); ok( $ticket->AddCustomFieldValue( Field => $cf, Value => 'second value' ) ); my $cf_values = $cf->ValuesForObject($ticket); is( $cf_values->Count, 2, 'Found 2 values' ); is( $ticket->CustomFieldValuesAsString( $cf, Separator => ', ' ), 'first value, second value', 'Current cf contents' ); ($ok, $msg) = $cf->SetMaxValues(1); is( $cf->MaxValues, 1, 'Max value is 1' ); ok( $ticket->AddCustomFieldValue( Field => $cf, Value => 'third value' ) ); $cf_values = $cf->ValuesForObject($ticket); is( $cf_values->Count, 1, 'Found 1 value' ); is( $ticket->CustomFieldValuesAsString( $cf, Separator => ', ' ), 'third value', 'Current cf contents' ); ($ok, $msg) = $cf->SetMaxValues(2); is( $cf->MaxValues, 2, 'Max value is 2' ); ok( $ticket->AddCustomFieldValue( Field => $cf, Value => 'forth value' ) ); $cf_values = $cf->ValuesForObject($ticket); is( $cf_values->Count, 2, 'Found 2 values' ); is( $ticket->CustomFieldValuesAsString( $cf, Separator => ', ' ), 'third value, forth value', 'Current cf contents' ); ok( $ticket->AddCustomFieldValue( Field => $cf, Value => 'fifth value' ) ); $cf_values = $cf->ValuesForObject($ticket); is( $cf_values->Count, 2, 'Found 2 values' ); is( $ticket->CustomFieldValuesAsString( $cf, Separator => ', ' ), 'forth value, fifth value', 'Current cf contents' ); done_testing; rt-5.0.1/t/api/uri-t.t000644 000765 000024 00000001126 14005011336 015242 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => 6; my $t1 = RT::Ticket->new(RT->SystemUser); my ($id,$trans,$msg) =$t1->Create (Queue => 'general', Subject => 'Requestor test one', ); ok ($id, $msg); use_ok("RT::URI::t"); my $uri = RT::URI::t->new(RT->SystemUser); ok(ref($uri), "URI object exists"); my $uristr = "t:1"; $uri->ParseURI($uristr); is(ref($uri->Object), "RT::Ticket", "Object loaded is a ticket"); is($uri->Object->Id, 1, "Object loaded has correct ID"); is($uri->URI, 'fsck.com-rt://'.RT->Config->Get('Organization').'/ticket/1', "URI object has correct URI string"); rt-5.0.1/t/api/tickets_overlay_sql.t000644 000765 000024 00000006042 14005011336 020272 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 20, config => 'Set( %FullTextSearch, Enable => 1 );'; use Test::Warn; my $tix = RT::Tickets->new(RT->SystemUser); { my $query = "Status = 'open'"; my ($status, $msg) = $tix->FromSQL($query); ok ($status, "correct query") or diag("error: $msg"); } my (@created,%created); my $string = 'subject/content SQL test'; { my $t = RT::Ticket->new(RT->SystemUser); ok( $t->Create(Queue => 'General', Subject => $string), "Ticket Created"); $created{ $t->Id }++; push @created, $t->Id; } { my $Message = MIME::Entity->build( Subject => 'this is my subject', From => 'jesse@example.com', Data => [ $string ], ); my $t = RT::Ticket->new(RT->SystemUser); ok( $t->Create( Queue => 'General', Requestor => 'jesse@example.com', Subject => 'another ticket', MIMEObj => $Message, MemberOf => $created[0] ), "Ticket Created" ); $created{ $t->Id }++; push @created, $t->Id; } { my $query = ("Subject LIKE '$string' OR Content LIKE '$string'"); my ($status, $msg) = $tix->FromSQL($query); ok ($status, "correct query") or diag("error: $msg"); my $count = 0; while (my $tick = $tix->Next) { $count++ if $created{ $tick->id }; } is ($count, scalar @created, "number of returned tickets same as entered"); } { my $query = "id = $created[0] OR MemberOf = $created[0]"; my ($status, $msg) = $tix->FromSQL($query); ok ($status, "correct query") or diag("error: $msg"); my $count = 0; while (my $tick = $tix->Next) { $count++ if $created{ $tick->id }; } is ($count, scalar @created, "number of returned tickets same as entered"); } diag "Make sure we don't barf on invalid input for IS / IS NOT"; { my ($status, $msg) = $tix->FromSQL("Subject IS 'foobar'"); ok ($status, "valid query") or diag("error: $msg"); is $tix->Count, 0, "found no tickets"; unlike $tix->BuildSelectQuery, qr/foobar/, "didn't find foobar in the select"; like $tix->BuildSelectQuery, qr/Subject IS NULL/, "found right clause"; ($status, $msg) = $tix->FromSQL("Subject IS NOT 'foobar'"); ok ($status, "valid query") or diag("error: $msg"); is $tix->Count, 2, "found two tickets"; unlike $tix->BuildSelectQuery, qr/foobar/, "didn't find foobar in the select"; like $tix->BuildSelectQuery, qr/Subject IS NOT NULL/, "found right clause"; } { my ($status, $msg); warning_like { ($status, $msg) = $tix->FromSQL("Requestor.Signature LIKE 'foo'"); } qr/Invalid watcher subfield: 'Signature'/; ok(!$status, "invalid query - Signature not valid") or diag("error: $msg"); ($status, $msg) = $tix->FromSQL("Requestor.EmailAddress LIKE 'jesse'"); ok ($status, "valid query") or diag("error: $msg"); is $tix->Count, 1, "found one ticket"; like $tix->First->Subject, qr/another ticket/, "found the right ticket"; } rt-5.0.1/t/api/date.t000644 000765 000024 00000061420 14005011336 015122 0ustar00sunnavystaff000000 000000 use Test::MockTime qw(set_fixed_time restore_time); use DateTime; use warnings; use strict; use RT::Test tests => undef; use RT::User; use Test::Warn; use_ok('RT::Date'); { my $date = RT::Date->new(RT->SystemUser); isa_ok($date, 'RT::Date', "constructor returned RT::Date oject"); $date = $date->new(RT->SystemUser); isa_ok($date, 'RT::Date', "constructor returned RT::Date oject"); } { # set timezone in all places to UTC RT->SystemUser->UserObj->__Set(Field => 'Timezone', Value => 'UTC') if RT->SystemUser->UserObj->Timezone; RT->Config->Set( Timezone => 'UTC' ); } my $current_user; { my $user = RT::User->new(RT->SystemUser); my($uid, $msg) = $user->Create( Name => "date_api". rand(200), Lang => 'en', Privileged => 1, ); ok($uid, "user was created") or diag("error: $msg"); $current_user = RT::CurrentUser->new($user); } { my $date = RT::Date->new( $current_user ); is($date->Timezone('user'), 'UTC', "dropped all timzones to UTC"); is($date->Timezone('server'), 'UTC', "dropped all timzones to UTC"); is($date->Timezone('unknown'), 'UTC', "with wrong context returns UTC"); $current_user->UserObj->__Set( Field => 'Timezone', Value => 'Europe/Moscow'); is($current_user->UserObj->Timezone, 'Europe/Moscow', "successfuly changed user's timezone"); is($date->Timezone('user'), 'Europe/Moscow', "in user context returns user's timezone"); is($date->Timezone('server'), 'UTC', "wasn't changed"); RT->Config->Set( Timezone => 'Africa/Ouagadougou' ); is($date->Timezone('server'), 'Africa/Ouagadougou', "timezone of the RT server was changed"); is($date->Timezone('user'), 'Europe/Moscow', "in user context still returns user's timezone"); $current_user->UserObj->__Set( Field => 'Timezone', Value => ''); is_empty($current_user->UserObj->Timezone, "successfuly changed user's timezone"); is($date->Timezone('user'), 'Africa/Ouagadougou', "in user context returns timezone of the server if user's one is not defined"); RT->Config->Set( Timezone => 'GMT' ); is($date->Timezone('server'), 'UTC', "timezone is GMT which one is alias for UTC"); RT->Config->Set( Timezone => '' ); is($date->Timezone('user'), 'UTC', "user's and server's timzones are not defined, so UTC"); is($date->Timezone('server'), 'UTC', "timezone of the server is not defined so UTC"); RT->Config->Set( Timezone => 'UTC' ); } { my $date = RT::Date->new(RT->SystemUser); is($date->IsSet,0, "new date isn't set"); is($date->Unix, 0, "new date returns 0 in Unix format"); is($date->Get, '1970-01-01 00:00:00', "default is ISO format"); warning_like { is($date->Get(Format =>'SomeBadFormat'), '1970-01-01 00:00:00', "don't know format, return ISO format"); } qr/Invalid date formatter/; is($date->Get(Format =>'W3CDTF'), '1970-01-01T00:00:00Z', "W3CDTF format with defaults"); is($date->Get(Format =>'RFC2822'), 'Thu, 01 Jan 1970 00:00:00 +0000', "RFC2822 format with defaults"); is($date->Get(Format =>'LocalizedDateTime'), 'Thu, Jan 1, 1970 12:00:00 AM', "LocalizedDateTime format with defaults"); is($date->ISO(Time => 0), '1970-01-01', "ISO format without time part"); is($date->W3CDTF(Time => 0), '1970-01-01', "W3CDTF format without time part"); is($date->RFC2822(Time => 0), 'Thu, 01 Jan 1970', "RFC2822 format without time part"); is($date->LocalizedDateTime(Time => 0), 'Thu, Jan 1, 1970', "LocalizedDateTime format without time part"); is($date->ISO(Date => 0), '00:00:00', "ISO format without date part"); is($date->W3CDTF(Date => 0), '1970-01-01T00:00:00Z', "W3CDTF format is incorrect without date part"); is($date->RFC2822(Date => 0), '00:00:00 +0000', "RFC2822 format without date part"); is($date->LocalizedDateTime(Date => 0), '12:00:00 AM', "LocalizedDateTime format without date part"); is($date->ISO(Date => 0, Seconds => 0), '00:00', "ISO format without date part and seconds"); is($date->W3CDTF(Date => 0, Seconds => 0), '1970-01-01T00:00Z', "W3CDTF format without seconds, but we ship date part even if Date is false"); is($date->RFC2822(Date => 0, Seconds => 0), '00:00 +0000', "RFC2822 format without date part and seconds"); is($date->RFC2822(DayOfWeek => 0), '01 Jan 1970 00:00:00 +0000', "RFC2822 format without 'day of week' part"); is($date->RFC2822(DayOfWeek => 0, Date => 0), '00:00:00 +0000', "RFC2822 format without 'day of week' and date parts(corner case test)"); is($date->LocalizedDateTime(AbbrDay => 0), 'Thursday, Jan 1, 1970 12:00:00 AM', "LocalizedDateTime format without abbreviation of day"); is($date->LocalizedDateTime(AbbrMonth => 0), 'Thu, January 1, 1970 12:00:00 AM', "LocalizedDateTime format without abbreviation of month"); is($date->LocalizedDateTime(DateFormat => 'date_format_short'), '1/1/70 12:00:00 AM', "LocalizedDateTime format with non default DateFormat"); is($date->LocalizedDateTime(TimeFormat => 'time_format_short'), 'Thu, Jan 1, 1970 12:00 AM', "LocalizedDateTime format with non default TimeFormat"); is($date->Date, '1970-01-01', "the default format for the 'Date' method is ISO"); is($date->Date(Format => 'W3CDTF'), '1970-01-01', "'Date' method, W3CDTF format"); is($date->Date(Format => 'RFC2822'), 'Thu, 01 Jan 1970', "'Date' method, RFC2822 format"); is($date->Date(Time => 1), '1970-01-01', "'Date' method doesn't pass through 'Time' argument"); is($date->Date(Date => 0), '1970-01-01', "'Date' method overrides 'Date' argument"); is($date->Time, '00:00:00', "the default format for the 'Time' method is ISO"); is($date->Time(Format => 'W3CDTF'), '1970-01-01T00:00:00Z', "'Time' method, W3CDTF format, date part is required by w3c doc"); is($date->Time(Format => 'RFC2822'), '00:00:00 +0000', "'Time' method, RFC2822 format"); is($date->Time(Date => 1), '00:00:00', "'Time' method doesn't pass through 'Date' argument"); is($date->Time(Time => 0), '00:00:00', "'Time' method overrides 'Time' argument"); is($date->DateTime, '1970-01-01 00:00:00', "the default format for the 'DateTime' method is ISO"); is($date->DateTime(Format =>'W3CDTF'), '1970-01-01T00:00:00Z', "'DateTime' method, W3CDTF format"); is($date->DateTime(Format =>'RFC2822'), 'Thu, 01 Jan 1970 00:00:00 +0000', "'DateTime' method, RFC2822 format"); is($date->DateTime(Date => 0, Time => 0), '1970-01-01 00:00:00', "the 'DateTime' method overrides both 'Date' and 'Time' arguments"); } { # positive timezone $current_user->UserObj->__Set( Field => 'Timezone', Value => 'Europe/Moscow'); my $date = RT::Date->new( $current_user ); $date->Set( Format => 'ISO', Timezone => 'utc', Value => '2005-01-01 15:10:00' ); is($date->IsSet,1,"Date has been set"); is($date->ISO( Timezone => 'user' ), '2005-01-01 18:10:00', "ISO"); is($date->W3CDTF( Timezone => 'user' ), '2005-01-01T18:10:00+03:00', "W3C DTF"); is($date->RFC2822( Timezone => 'user' ), 'Sat, 01 Jan 2005 18:10:00 +0300', "RFC2822"); is($date->LocalizedDateTime( Timezone => 'user' ), 'Sat, Jan 1, 2005 6:10:00 PM', "LocalizedDateTime"); # DST $date = RT::Date->new( $current_user ); $date->Set( Format => 'ISO', Timezone => 'utc', Value => '2005-07-01 15:10:00' ); is($date->ISO( Timezone => 'user' ), '2005-07-01 19:10:00', "ISO"); is($date->W3CDTF( Timezone => 'user' ), '2005-07-01T19:10:00+04:00', "W3C DTF"); is($date->RFC2822( Timezone => 'user' ), 'Fri, 01 Jul 2005 19:10:00 +0400', "RFC2822"); is($date->LocalizedDateTime( Timezone => 'user' ), 'Fri, Jul 1, 2005 7:10:00 PM', "LocalizedDateTime"); } { # negative timezone $current_user->UserObj->__Set( Field => 'Timezone', Value => 'America/New_York'); my $date = RT::Date->new( $current_user ); $date->Set( Format => 'ISO', Timezone => 'utc', Value => '2005-01-01 15:10:00' ); is($date->ISO( Timezone => 'user' ), '2005-01-01 10:10:00', "ISO"); is($date->W3CDTF( Timezone => 'user' ), '2005-01-01T10:10:00-05:00', "W3C DTF"); is($date->RFC2822( Timezone => 'user' ), 'Sat, 01 Jan 2005 10:10:00 -0500', "RFC2822"); is($date->LocalizedDateTime( Timezone => 'user' ), 'Sat, Jan 1, 2005 10:10:00 AM', "LocalizedDateTime"); # DST $date = RT::Date->new( $current_user ); $date->Set( Format => 'ISO', Timezone => 'utc', Value => '2005-07-01 15:10:00' ); is($date->ISO( Timezone => 'user' ), '2005-07-01 11:10:00', "ISO"); is($date->W3CDTF( Timezone => 'user' ), '2005-07-01T11:10:00-04:00', "W3C DTF"); is($date->RFC2822( Timezone => 'user' ), 'Fri, 01 Jul 2005 11:10:00 -0400', "RFC2822"); is($date->LocalizedDateTime( Timezone => 'user' ), 'Fri, Jul 1, 2005 11:10:00 AM', "LocalizedDateTime"); } warning_like { # bad format my $date = RT::Date->new(RT->SystemUser); $date->Set( Format => 'bad' ); ok(!$date->IsSet, "bad format"); } qr{Unknown Date format: bad}; { # setting value via Unix method my $date = RT::Date->new(RT->SystemUser); $date->Unix(1); is($date->ISO, '1970-01-01 00:00:01', "correct value"); foreach (undef, 0, '', -5){ $date->Unix(1); is($date->ISO, '1970-01-01 00:00:01', "correct value"); is($date->IsSet,1,"Date has been set to a value"); $date->Set(Format => 'unix', Value => $_); is($date->ISO, '1970-01-01 00:00:00', "Set a date to midnight 1/1/1970 GMT due to wrong call"); is($date->Unix, 0, "unix is 0 => unset"); is($date->IsSet,0,"Date has been unset"); } foreach (undef, 0, '', -5){ $date->Unix(1); is($date->ISO, '1970-01-01 00:00:01', "correct value"); is($date->IsSet,1,"Date has been set to a value"); $date->Unix($_); is($date->ISO, '1970-01-01 00:00:00', "Set a date to midnight 1/1/1970 GMT due to wrong call"); is($date->Unix, 0, "unix is 0 => unset"); is($date->IsSet,0,"Date has been unset"); } } my $year = (localtime(time))[5] + 1900; { # set+ISO format my $date = RT::Date->new(RT->SystemUser); warning_like { $date->Set(Format => 'ISO', Value => 'weird date'); } qr/Couldn't parse date 'weird date' as a ISO format/; ok(!$date->IsSet, "date was wrong => unix == 0"); # XXX: ISO format has more feature than we suport # http://www.cl.cam.ac.uk/~mgk25/iso-time.html $date->Set(Format => 'ISO', Value => '2005-11-28 15:10:00'); is($date->ISO, '2005-11-28 15:10:00', "YYYY-DD-MM hh:mm:ss"); $date->Set(Format => 'ISO', Value => '2005-11-28 15:10:00+00'); is($date->ISO, '2005-11-28 15:10:00', "YYYY-DD-MM hh:mm:ss+00"); $date->Set(Format => 'ISO', Value => '11-28 15:10:00'); is($date->ISO, $year .'-11-28 15:10:00', "DD-MM hh:mm:ss"); $date->Set(Format => 'ISO', Value => '11-28 15:10:00+00'); is($date->ISO, $year .'-11-28 15:10:00', "DD-MM hh:mm:ss+00"); $date->Set(Format => 'ISO', Value => '20051128151000'); is($date->ISO, '2005-11-28 15:10:00', "YYYYDDMMhhmmss"); $date->Set(Format => 'ISO', Value => '1128151000'); is($date->ISO, $year .'-11-28 15:10:00', "DDMMhhmmss"); $date->Set(Format => 'ISO', Value => '2005112815:10:00'); is($date->ISO, '2005-11-28 15:10:00', "YYYYDDMMhh:mm:ss"); $date->Set(Format => 'ISO', Value => '112815:10:00'); is($date->ISO, $year .'-11-28 15:10:00', "DDMMhh:mm:ss"); warning_like { $date->Set(Format => 'ISO', Value => '2005-13-28 15:10:00'); } qr/Invalid date/; ok(!$date->IsSet, "wrong month value"); warning_like { $date->Set(Format => 'ISO', Value => '2005-00-28 15:10:00'); } qr/Invalid date/; ok(!$date->IsSet, "wrong month value"); $date->Set(Format => 'ISO', Value => '1960-01-28 15:10:00'); ok(!$date->IsSet, "too old, we don't support"); } { # set+datemanip format(Time::ParseDate) my $date = RT::Date->new(RT->SystemUser); RT->Config->Set( Timezone => 'Europe/Moscow' ); $date->Set(Format => 'datemanip', Value => '2005-11-28 15:10:00'); is($date->ISO, '2005-11-28 12:10:00', "YYYY-DD-MM hh:mm:ss"); RT->Config->Set( Timezone => 'UTC' ); $date->Set(Format => 'datemanip', Value => '2005-11-28 15:10:00'); is($date->ISO, '2005-11-28 15:10:00', "YYYY-DD-MM hh:mm:ss"); $current_user->UserObj->__Set( Field => 'Timezone', Value => 'Europe/Moscow'); $date = RT::Date->new( $current_user ); $date->Set(Format => 'datemanip', Value => '2005-11-28 15:10:00'); is($date->ISO, '2005-11-28 12:10:00', "YYYY-DD-MM hh:mm:ss"); } { # set+unknown format(Time::ParseDate) my $date = RT::Date->new(RT->SystemUser); warnings_like { $date->Set(Format => 'unknown', Value => 'weird date'); } [ qr{Couldn't parse date 'weird date' by Time::ParseDate}, qr{Couldn't parse date 'weird date' by DateTime::Format::Natural} ]; ok(!$date->IsSet, "date was wrong"); RT->Config->Set( Timezone => 'Europe/Moscow' ); $date->Set(Format => 'unknown', Value => '2005-11-28 15:10:00'); is($date->ISO, '2005-11-28 12:10:00', "YYYY-DD-MM hh:mm:ss"); $date->Set(Format => 'unknown', Value => '2005-11-28 15:10:00', Timezone => 'utc' ); is($date->ISO, '2005-11-28 15:10:00', "YYYY-DD-MM hh:mm:ss"); # test relative dates { set_fixed_time("2005-11-28T15:10:00Z"); $date->Set(Format => 'unknown', Value => 'now'); is($date->ISO, '2005-11-28 15:10:00', "YYYY-DD-MM hh:mm:ss"); $date->Set(Format => 'unknown', Value => '1 day ago'); is($date->ISO, '2005-11-27 15:10:00', "YYYY-DD-MM hh:mm:ss"); restore_time(); } RT->Config->Set( Timezone => 'UTC' ); $date->Set(Format => 'unknown', Value => '2005-11-28 15:10:00'); is($date->ISO, '2005-11-28 15:10:00', "YYYY-DD-MM hh:mm:ss"); $current_user->UserObj->__Set( Field => 'Timezone', Value => 'Europe/Moscow'); $date = RT::Date->new( $current_user ); $date->Set(Format => 'unknown', Value => '2005-11-28 15:10:00'); is($date->ISO, '2005-11-28 12:10:00', "YYYY-DD-MM hh:mm:ss"); $date->Set(Format => 'unknown', Value => '2005-11-28 15:10:00', Timezone => 'server' ); is($date->ISO, '2005-11-28 15:10:00', "YYYY-DD-MM hh:mm:ss"); $date->Set(Format => 'unknown', Value => '2005-11-28 15:10:00', Timezone => 'utc' ); is($date->ISO, '2005-11-28 15:10:00', "YYYY-DD-MM hh:mm:ss"); } { # 'tomorrow 10am' with TZ $current_user->UserObj->__Set( Field => 'Timezone', Value => 'Europe/Moscow'); set_fixed_time("2012-06-14T15:10:00Z"); # 14th in UTC and Moscow my $date = RT::Date->new( $current_user ); $date->Set(Format => 'unknown', Value => 'tomorrow 10am'); is($date->ISO, '2012-06-15 06:00:00', "YYYY-DD-MM hh:mm:ss"); set_fixed_time("2012-06-13T23:10:00Z"); # 13th in UTC and 14th in Moscow $date = RT::Date->new( $current_user ); $date->Set(Format => 'unknown', Value => 'tomorrow 10am'); is($date->ISO, '2012-06-15 06:00:00', "YYYY-DD-MM hh:mm:ss"); $current_user->UserObj->__Set( Field => 'Timezone', Value => 'US/Hawaii'); set_fixed_time("2012-06-14T20:10:00Z"); # 14th in UTC and Hawaii $date = RT::Date->new( $current_user ); $date->Set(Format => 'unknown', Value => 'tomorrow 10am'); is($date->ISO, '2012-06-15 20:00:00', "YYYY-DD-MM hh:mm:ss"); set_fixed_time("2012-06-15T05:10:00Z"); # 15th in UTC and 14th in Hawaii $date = RT::Date->new( $current_user ); $date->Set(Format => 'unknown', Value => 'tomorrow 10am'); is($date->ISO, '2012-06-15 20:00:00', "YYYY-DD-MM hh:mm:ss"); restore_time(); } { # SetToMidnight my $date = RT::Date->new(RT->SystemUser); RT->Config->Set( Timezone => 'Europe/Moscow' ); $date->Set(Format => 'ISO', Value => '2005-11-28 15:10:00'); $date->SetToMidnight; is($date->ISO, '2005-11-28 00:00:00', "default is utc"); $date->Set(Format => 'ISO', Value => '2005-11-28 15:10:00'); $date->SetToMidnight(Timezone => 'utc'); is($date->ISO, '2005-11-28 00:00:00', "utc context"); $date->Set(Format => 'ISO', Value => '2005-11-28 15:10:00'); $date->SetToMidnight(Timezone => 'user'); is($date->ISO, '2005-11-27 21:00:00', "user context, user has no preference, fallback to server"); $date->Set(Format => 'ISO', Value => '2005-11-28 15:10:00'); $date->SetToMidnight(Timezone => 'server'); is($date->ISO, '2005-11-27 21:00:00', "server context"); $current_user->UserObj->__Set( Field => 'Timezone', Value => 'Europe/Moscow'); $date = RT::Date->new( $current_user ); $date->Set(Format => 'ISO', Value => '2005-11-28 15:10:00'); $date->SetToMidnight; is($date->ISO, '2005-11-28 00:00:00', "default is utc"); $date->Set(Format => 'ISO', Value => '2005-11-28 15:10:00'); $date->SetToMidnight(Timezone => 'utc'); is($date->ISO, '2005-11-28 00:00:00', "utc context"); $date->Set(Format => 'ISO', Value => '2005-11-28 15:10:00'); $date->SetToMidnight(Timezone => 'user'); is($date->ISO, '2005-11-27 21:00:00', "user context"); $date->SetToMidnight(Timezone => 'server'); is($date->ISO, '2005-11-27 21:00:00', "server context"); RT->Config->Set( Timezone => 'UTC' ); } { # SetToNow my $date = RT::Date->new(RT->SystemUser); my $time = time; $date->SetToNow; ok($date->Unix >= $time, 'close enough'); ok($date->Unix < $time+5, 'difference is less than five seconds'); } { my $date = RT::Date->new(RT->SystemUser); $date->Unix(0); $date->AddSeconds; is($date->ISO, '1970-01-01 00:00:00', "nothing changed"); $date->AddSeconds(0); is($date->ISO, '1970-01-01 00:00:00', "nothing changed"); $date->Unix(0); $date->AddSeconds(5); is($date->ISO, '1970-01-01 00:00:05', "added five seconds"); $date->AddSeconds(-2); is($date->ISO, '1970-01-01 00:00:03', "substracted two seconds"); $date->Unix(0); $date->AddSeconds(3661); is($date->ISO, '1970-01-01 01:01:01', "added one hour, minute and a second"); # XXX: TODO, doesn't work with Test::Warn # TODO: { # local $TODO = "BUG or subject to change Date handling to support unix time <= 0"; # $date->Unix(0); # $date->AddSeconds(-2); # ok($date->Unix > 0); # } $date->Unix(0); $date->AddDay; is($date->ISO, '1970-01-02 00:00:00', "added one day"); $date->AddDays(2); is($date->ISO, '1970-01-04 00:00:00', "added two days"); $date->AddDays(-1); is($date->ISO, '1970-01-03 00:00:00', "substructed one day"); $date->Unix(0); $date->AddDays(31); is($date->ISO, '1970-02-01 00:00:00', "added one month"); $date->Unix(0); $date->AddDays(0); is($date->ISO, '1970-01-01 00:00:00', "added no days"); $date->Unix(0); $date->AddDays(); is($date->ISO, '1970-01-02 00:00:00', "added one day with no argument"); } { $current_user->UserObj->__Set( Field => 'Timezone', Value => ''); my $date = RT::Date->new( $current_user ); is($date->AsString, "Not set", "AsString returns 'Not set'"); RT->Config->Set( DateTimeFormat => ''); $date->Unix(1); is($date->AsString, 'Thu Jan 01 00:00:01 1970', "correct string"); is($date->AsString(Date => 0), '00:00:01', "correct string"); is($date->AsString(Time => 0), 'Thu Jan 01 1970', "correct string"); is($date->AsString(Date => 0, Time => 0), 'Thu Jan 01 00:00:01 1970', "invalid input"); RT->Config->Set( DateTimeFormat => 'RFC2822' ); $date->Unix(1); is($date->AsString, 'Thu, 01 Jan 1970 00:00:01 +0000', "correct string"); RT->Config->Set( DateTimeFormat => { Format => 'RFC2822', Seconds => 0 } ); $date->Unix(1); is($date->AsString, 'Thu, 01 Jan 1970 00:00 +0000', "correct string"); is($date->AsString(Seconds => 1), 'Thu, 01 Jan 1970 00:00:01 +0000', "correct string"); } { # DurationAsString my $date = RT::Date->new(RT->SystemUser); is($date->DurationAsString(1), '1 second', '1 sec'); is($date->DurationAsString(59), '59 seconds', '59 sec'); is($date->DurationAsString(60), '1 minute', '1 min'); is($date->DurationAsString(60*119), '119 minutes', '119 min'); is($date->DurationAsString(60*60*2-1), '120 minutes', '120 min'); is($date->DurationAsString(60*60*2), '2 hours', '2 hours'); is($date->DurationAsString(60*60*48-1), '48 hours', '48 hours'); is($date->DurationAsString(60*60*48), '2 days', '2 days'); is($date->DurationAsString(60*60*24*14-1), '14 days', '14 days'); is($date->DurationAsString(60*60*24*14), '2 weeks', '2 weeks'); is($date->DurationAsString(60*60*24*7*8-1), '8 weeks', '8 weeks'); is($date->DurationAsString(60*60*24*61), '2 months', '2 months'); is($date->DurationAsString(60*60*24*365-1), '12 months', '12 months'); is($date->DurationAsString(60*60*24*366), '1 year', '1 year'); is($date->DurationAsString(-1), '1 second ago', '1 sec ago'); } { # DiffAsString my $date = RT::Date->new(RT->SystemUser); is($date->DiffAsString(1), '', 'no diff, wrong input'); is($date->DiffAsString(-1), '', 'no diff, wrong input'); is($date->DiffAsString('qwe'), '', 'no diff, wrong input'); $date->Unix(2); is($date->DiffAsString(-1), '', 'no diff, wrong input'); is($date->DiffAsString(3), '1 second ago', 'diff: 1 sec ago'); is($date->DiffAsString(1), '1 second', 'diff: 1 sec'); my $ndate = RT::Date->new(RT->SystemUser); is($date->DiffAsString($ndate), '', 'no diff, wrong input'); $ndate->Unix(3); is($date->DiffAsString($ndate), '1 second ago', 'diff: 1 sec ago'); } { # Diff my $date = RT::Date->new(RT->SystemUser); $date->SetToNow; my $diff = $date->Diff; ok($diff <= 0, 'close enought'); ok($diff > -5, 'close enought'); } { # AgeAsString my $date = RT::Date->new(RT->SystemUser); $date->SetToNow; my $diff = $date->AgeAsString; like($diff, qr/^(0 seconds|(1 second|[2-5] seconds) ago)$/, 'close enought'); } { # GetWeekday my $date = RT::Date->new(RT->SystemUser); is($date->GetWeekday(7), '', '7 and greater are invalid'); is($date->GetWeekday(6), 'Sat', '6 is Saturday'); is($date->GetWeekday(0), 'Sun', '0 is Sunday'); is($date->GetWeekday(-1), 'Sat', '-1 is Saturday'); is($date->GetWeekday(-7), 'Sun', '-7 is Sunday'); is($date->GetWeekday(-8), '', '-8 and lesser are invalid'); } { # GetMonth my $date = RT::Date->new(RT->SystemUser); is($date->GetMonth(12), '', '12 and greater are invalid'); is($date->GetMonth(11), 'Dec', '11 is December'); is($date->GetMonth(0), 'Jan', '0 is January'); is($date->GetMonth(-1), 'Dec', '11 is December'); is($date->GetMonth(-12), 'Jan', '0 is January'); is($date->GetMonth(-13), '', '-13 and lesser are invalid'); } { # set unknown format: edge cases my $date = RT::Date->new(RT->SystemUser); $date->Set( Value => 0, Format => 'unknown' ); ok( !$date->IsSet, "unix is 0 with Value => 0, Format => 'unknown'" ); $date->Set( Value => '', Format => 'unknown' ); ok( !$date->IsSet, "unix is 0 with Value => '', Format => 'unknown'" ); $date->Set( Value => ' ', Format => 'unknown' ); ok( !$date->IsSet, "unix is 0 with Value => ' ', Format => 'unknown'" ); } { # set+unknown format(DateTime::Format::Natural) my $date = RT::Date->new(RT->SystemUser); warnings_like { $date->Set(Format => 'unknown', Value => 'another weird date'); } [ qr{Couldn't parse date 'another weird date' by Time::ParseDate}, qr{Couldn't parse date 'another weird date' by DateTime::Format::Natural} ], 'warning for "another weird date"'; ok(!$date->IsSet, "date was wrong"); RT->Config->Set( AmbiguousDayInPast => 0 ); RT->Config->Set( AmbiguousDayInFuture => 0 ); RT->Config->Set( Timezone => 'Asia/Shanghai' ); set_fixed_time("2015-11-28T15:10:00Z"); warnings_like { $date->Set(Format => 'unknown', Value => 'Apr'); } qr{Couldn't parse date 'Apr' by Time::ParseDate}, "warning by Time::ParseDate"; is($date->ISO, '2015-03-31 16:00:00', "April in the past"); warnings_like { $date->Set(Format => 'unknown', Value => 'Monday'); } qr{Couldn't parse date 'Monday' by Time::ParseDate}, "warning by Time::ParseDate"; is($date->ISO, '2015-11-22 16:00:00', "Monday in the past"); RT->Config->Set( AmbiguousDayInFuture => 1 ); warnings_like { $date->Set(Format => 'unknown', Value => 'Apr'); } qr{Couldn't parse date 'Apr' by Time::ParseDate}, "warning by Time::ParseDate"; is($date->ISO, '2016-03-31 16:00:00', "April in the future"); } #TODO: AsString #TODO: RFC2822, W3CDTF with Timezones done_testing; rt-5.0.1/t/api/users.t000644 000765 000024 00000005170 14005011336 015346 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 10; RT::System->AddRight( General => 'RTxUserRight' => 'Just a right for testing rights', ); { no warnings qw(redefine once); ok(my $users = RT::Users->new(RT->SystemUser)); $users->WhoHaveRight(Object => RT->System, Right =>'SuperUser'); is($users->Count , 1, "There is one privileged superuser - Found ". $users->Count ); # TODO: this wants more testing my $RTxUser = RT::User->new(RT->SystemUser); my ($id, $msg) = $RTxUser->Create( Name => 'RTxUser', Comments => "RTx extension user", Privileged => 1); ok ($id,$msg); my $group = RT::Group->new(RT->SystemUser); $group->LoadACLEquivalenceGroup($RTxUser->PrincipalObj); my $RTxSysObj = {}; bless $RTxSysObj, 'RTx::System'; *RTx::System::Id = sub { 1; }; *RTx::System::id = *RTx::System::Id; my $ace = RT::Record->new(RT->SystemUser); $ace->Table('ACL'); $ace->_BuildTableAttributes unless ($RT::Record::_TABLE_ATTR->{ref($ace)}); ($id, $msg) = $ace->Create( PrincipalId => $group->id, PrincipalType => 'Group', RightName => 'RTxUserRight', ObjectType => 'RTx::System', ObjectId => 1 ); ok ($id, "ACL for RTxSysObj created"); my $RTxObj = {}; bless $RTxObj, 'RTx::System::Record'; *RTx::System::Record::Id = sub { 4; }; *RTx::System::Record::id = *RTx::System::Record::Id; $users = RT::Users->new(RT->SystemUser); $users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxSysObj); is($users->Count, 1, "RTxUserRight found for RTxSysObj"); $users = RT::Users->new(RT->SystemUser); $users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxObj); is($users->Count, 0, "RTxUserRight not found for RTxObj"); $users = RT::Users->new(RT->SystemUser); $users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxObj, EquivObjects => [ $RTxSysObj ]); is($users->Count, 1, "RTxUserRight found for RTxObj using EquivObjects"); $ace = RT::Record->new(RT->SystemUser); $ace->Table('ACL'); $ace->_BuildTableAttributes unless ($RT::Record::_TABLE_ATTR->{ref($ace)}); ($id, $msg) = $ace->Create( PrincipalId => $group->id, PrincipalType => 'Group', RightName => 'RTxUserRight', ObjectType => 'RTx::System::Record', ObjectId => 5 ); ok ($id, "ACL for RTxObj created"); my $RTxObj2 = {}; bless $RTxObj2, 'RTx::System::Record'; *RTx::System::Record::Id = sub { 5; }; *RTx::System::Record::id = sub { 5; }; $users = RT::Users->new(RT->SystemUser); $users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxObj2); is($users->Count, 1, "RTxUserRight found for RTxObj2"); $users = RT::Users->new(RT->SystemUser); $users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxObj2, EquivObjects => [ $RTxSysObj ]); is($users->Count, 1, "RTxUserRight found for RTxObj2"); } rt-5.0.1/t/api/scrip_order.t000644 000765 000024 00000021066 14005011336 016522 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 204; my $queue = RT::Test->load_or_create_queue( Name => 'General' ); ok $queue && $queue->id, 'loaded or created queue'; note "check that execution order reflects sort order"; { my $ten = main->create_scrip_ok( Description => "Set priority to 10", Queue => $queue->id, CustomCommitCode => '$self->TicketObj->SetPriority(10);', ); my $five = main->create_scrip_ok( Description => "Set priority to 5", Queue => $queue->id, CustomCommitCode => '$self->TicketObj->SetPriority(5);', ); my $ticket = RT::Ticket->new(RT->SystemUser); my ($id, $msg) = $ticket->Create( Queue => $queue->id, Subject => "Scrip order test $$", ); ok($ticket->id, "Created ticket? id=$id"); is($ticket->Priority , 5, "By default newer scrip is last"); main->move_scrip_ok( $five, $queue->id, 'up' ); $ticket = RT::Ticket->new(RT->SystemUser); ($id, $msg) = $ticket->Create( Queue => $queue->id, Subject => "Scrip order test $$", ); ok($ticket->id, "Created ticket? id=$id"); is($ticket->Priority , 10, "Moved scrip and result is different"); } my $queue_B = RT::Test->load_or_create_queue( Name => 'Other' ); ok $queue_B && $queue_B->id, 'loaded or created queue'; note "move around two local scrips"; { main->delete_all_scrips(); my @scrips; push @scrips, main->create_scrip_ok( Queue => $queue->id ); push @scrips, main->create_scrip_ok( Queue => $queue->id ); main->check_scrips_order(\@scrips, [$queue]); main->move_scrip_ok( $scrips[0], $queue->id, 'down' ); @scrips = @scrips[1, 0]; main->check_scrips_order(\@scrips, [$queue]); main->move_scrip_ok( $scrips[0], $queue->id, 'down' ); @scrips = @scrips[1, 0]; main->check_scrips_order(\@scrips, [$queue]); main->move_scrip_ok( $scrips[1], $queue->id, 'up' ); @scrips = @scrips[1, 0]; main->check_scrips_order(\@scrips, [$queue]); main->move_scrip_ok( $scrips[1], $queue->id, 'up' ); @scrips = @scrips[1, 0]; main->check_scrips_order(\@scrips, [$queue]); } note "move around two global scrips"; { main->delete_all_scrips(); my @scrips; push @scrips, main->create_scrip_ok( Queue => 0 ); push @scrips, main->create_scrip_ok( Queue => 0 ); main->check_scrips_order(\@scrips, [$queue]); main->move_scrip_ok( $scrips[0], 0, 'down' ); @scrips = @scrips[1, 0]; main->check_scrips_order(\@scrips, [$queue]); main->move_scrip_ok( $scrips[0], 0, 'down' ); @scrips = @scrips[1, 0]; main->check_scrips_order(\@scrips, [$queue]); main->move_scrip_ok( $scrips[1], 0, 'up' ); @scrips = @scrips[1, 0]; main->check_scrips_order(\@scrips, [$queue]); main->move_scrip_ok( $scrips[1], 0, 'up' ); @scrips = @scrips[1, 0]; main->check_scrips_order(\@scrips, [$queue]); } note "move local scrip below global"; { main->delete_all_scrips(); my @scrips; push @scrips, main->create_scrip_ok( Queue => $queue->id ); push @scrips, main->create_scrip_ok( Queue => $queue_B->id ); push @scrips, main->create_scrip_ok( Queue => 0 ); push @scrips, main->create_scrip_ok( Queue => $queue->id ); main->check_scrips_order(\@scrips, [$queue, $queue_B]); main->move_scrip_ok( $scrips[0], $queue->id, 'down' ); @scrips = @scrips[1, 2, 0, 3]; main->check_scrips_order(\@scrips, [$queue, $queue_B]); } note "move local scrip above global"; { main->delete_all_scrips(); my @scrips; push @scrips, main->create_scrip_ok( Queue => $queue_B->id ); push @scrips, main->create_scrip_ok( Queue => 0 ); push @scrips, main->create_scrip_ok( Queue => $queue->id ); push @scrips, main->create_scrip_ok( Queue => $queue_B->id ); main->check_scrips_order(\@scrips, [$queue, $queue_B]); main->move_scrip_ok( $scrips[-1], $queue_B->id, 'up' ); @scrips = @scrips[0, 3, 1, 2]; main->check_scrips_order(\@scrips, [$queue, $queue_B]); } note "move global scrip down with local in between"; { main->delete_all_scrips(); my @scrips; push @scrips, main->create_scrip_ok( Queue => 0 ); push @scrips, main->create_scrip_ok( Queue => $queue_B->id ); push @scrips, main->create_scrip_ok( Queue => $queue->id ); push @scrips, main->create_scrip_ok( Queue => 0 ); push @scrips, main->create_scrip_ok( Queue => $queue->id ); main->check_scrips_order(\@scrips, [$queue, $queue_B]); main->move_scrip_ok( $scrips[0], 0, 'down' ); @scrips = @scrips[1, 2, 3, 0, 4]; main->check_scrips_order(\@scrips, [$queue, $queue_B]); } note "move global scrip up with local in between"; { main->delete_all_scrips(); my @scrips; push @scrips, main->create_scrip_ok( Queue => $queue->id ); push @scrips, main->create_scrip_ok( Queue => 0 ); push @scrips, main->create_scrip_ok( Queue => $queue_B->id ); push @scrips, main->create_scrip_ok( Queue => $queue->id ); push @scrips, main->create_scrip_ok( Queue => 0 ); main->check_scrips_order(\@scrips, [$queue, $queue_B]); main->move_scrip_ok( $scrips[-1], 0, 'up' ); @scrips = @scrips[0, 4, 1, 2, 3]; main->check_scrips_order(\@scrips, [$queue, $queue_B]); } note "delete scrips one by one"; { main->delete_all_scrips(); my @scrips; push @scrips, main->create_scrip_ok( Queue => $queue->id ); push @scrips, main->create_scrip_ok( Queue => $queue_B->id ); push @scrips, main->create_scrip_ok( Queue => 0 ); push @scrips, main->create_scrip_ok( Queue => $queue_B->id ); push @scrips, main->create_scrip_ok( Queue => $queue->id ); push @scrips, main->create_scrip_ok( Queue => 0 ); main->check_scrips_order(\@scrips, [$queue, $queue_B]); foreach my $idx (3, 2, 0 ) { $_->Delete foreach splice @scrips, $idx, 1; main->check_scrips_order(\@scrips, [$queue, $queue_B]); } } sub create_scrip_ok { local $Test::Builder::Level = $Test::Builder::Level + 1; my $self = shift; my %args = ( ScripCondition => 'On Create', ScripAction => 'User Defined', CustomPrepareCode => 'return 1', CustomCommitCode => 'return 1', Template => 'Blank', Stage => 'TransactionCreate', @_ ); my $scrip = RT::Scrip->new( RT->SystemUser ); my ($id, $msg) = $scrip->Create( %args ); ok($id, "Created scrip") or diag "error: $msg"; return $scrip; } sub delete_all_scrips { local $Test::Builder::Level = $Test::Builder::Level + 1; my $self = shift; my $scrips = RT::Scrips->new( RT->SystemUser ); $scrips->UnLimit; $_->Delete foreach @{ $scrips->ItemsArrayRef }; } sub move_scrip_ok { local $Test::Builder::Level = $Test::Builder::Level + 1; my $self = shift; my ($scrip, $queue, $dir) = @_; my $rec = RT::ObjectScrip->new( RT->SystemUser ); $rec->LoadByCols( Scrip => $scrip->id, ObjectId => $queue ); ok $rec->id, 'found application of the scrip'; my $method = 'Move'. ucfirst lc $dir; my ($status, $msg) = $rec->$method(); ok $status, "moved scrip $dir" or diag "error: $msg"; } sub check_scrips_order { local $Test::Builder::Level = $Test::Builder::Level + 1; my $self = shift; my $scrips = shift; my $queues = shift; foreach my $qid ( 0, map $_->id, @$queues ) { my $list = RT::Scrips->new( RT->SystemUser ); $list->LimitToGlobal; $list->LimitToQueue( $qid ) if $qid; $list->ApplySortOrder; is_deeply( [map $_->id, @{ $list->ItemsArrayRef } ], [map $_->id, grep $_->IsAdded( $qid ) || $_->IsGlobal, @$scrips], 'list of scrips match expected' ) } foreach my $qid ( map $_->id, @$queues ) { my $list = RT::ObjectScrips->new( RT->SystemUser ); $list->LimitToObjectId( 0 ); $list->LimitToObjectId( $qid ); my %so; $so{ $_->SortOrder }++ foreach @{ $list->ItemsArrayRef }; ok( !grep( {$_ != 1} values %so), 'no dublicate order' ); } { my $list = RT::ObjectScrips->new( RT->SystemUser ); $list->UnLimit; $list->OrderBy( FIELD => 'SortOrder', ORDER => 'ASC' ); my $prev; foreach my $rec ( @{ $list->ItemsArrayRef } ) { my $so = $rec->SortOrder; do { $prev = $so; next } unless defined $prev; ok $so == $prev || $so == $prev+1, "sequential order"; $prev = $so; } } } sub dump_sort_order { diag " id oid so"; diag join "\n", map { join "\t", @$_ } map @$_, $RT::Handle->dbh->selectall_arrayref( "select Scrip, ObjectId, SortOrder from ObjectScrips ORDER BY SortOrder" ); } rt-5.0.1/t/api/tickets.t000644 000765 000024 00000011161 14005011336 015650 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => undef; use Test::Warn; { ok (require RT::Tickets); ok( my $testtickets = RT::Tickets->new( RT->SystemUser ) ); ok( $testtickets->LimitStatus( VALUE => 'deleted' ) ); # Should be zero until 'allow_deleted_search' is( $testtickets->Count , 0 ); } { # Test to make sure that you can search for tickets by requestor address and # by requestor name. my ($id,$msg); my $u1 = RT::User->new(RT->SystemUser); ($id, $msg) = $u1->Create( Name => 'RequestorTestOne', EmailAddress => 'rqtest1@example.com'); ok ($id,$msg); my $u2 = RT::User->new(RT->SystemUser); ($id, $msg) = $u2->Create( Name => 'RequestorTestTwo', EmailAddress => 'rqtest2@example.com'); ok ($id,$msg); my $t1 = RT::Ticket->new(RT->SystemUser); my ($trans); ($id,$trans,$msg) =$t1->Create (Queue => 'general', Subject => 'Requestor test one', Requestor => [$u1->EmailAddress]); ok ($id, $msg); my $t2 = RT::Ticket->new(RT->SystemUser); ($id,$trans,$msg) =$t2->Create (Queue => 'general', Subject => 'Requestor test one', Requestor => [$u2->EmailAddress]); ok ($id, $msg); my $t3 = RT::Ticket->new(RT->SystemUser); ($id,$trans,$msg) =$t3->Create (Queue => 'general', Subject => 'Requestor test one', Requestor => [$u2->EmailAddress, $u1->EmailAddress]); ok ($id, $msg); my $tix1 = RT::Tickets->new(RT->SystemUser); $tix1->FromSQL('Requestor.EmailAddress LIKE "rqtest1" OR Requestor.EmailAddress LIKE "rqtest2"'); is ($tix1->Count, 3); my $tix2 = RT::Tickets->new(RT->SystemUser); $tix2->FromSQL('Requestor.Name LIKE "TestOne" OR Requestor.Name LIKE "TestTwo"'); is ($tix2->Count, 3); my $tix3 = RT::Tickets->new(RT->SystemUser); $tix3->FromSQL('Requestor.EmailAddress LIKE "rqtest1"'); is ($tix3->Count, 2); my $tix4 = RT::Tickets->new(RT->SystemUser); $tix4->FromSQL('Requestor.Name LIKE "TestOne" '); is ($tix4->Count, 2); # Searching for tickets that have two requestors isn't supported # There's no way to differentiate "one requestor name that matches foo and bar" # and "two requestors, one matching foo and one matching bar" # my $tix5 = RT::Tickets->new(RT->SystemUser); # $tix5->FromSQL('Requestor.Name LIKE "TestOne" AND Requestor.Name LIKE "TestTwo"'); # # is ($tix5->Count, 1); # # my $tix6 = RT::Tickets->new(RT->SystemUser); # $tix6->FromSQL('Requestor.EmailAddress LIKE "rqtest1" AND Requestor.EmailAddress LIKE "rqtest2"'); # # is ($tix6->Count, 1); } { my $t1 = RT::Ticket->new(RT->SystemUser); $t1->Create(Queue => 'general', Subject => "LimitWatchers test", Requestors => \['requestor1@example.com']); } { # We assume that we've got some tickets hanging around from before. ok( my $unlimittickets = RT::Tickets->new( RT->SystemUser ) ); ok( $unlimittickets->UnLimit ); ok( $unlimittickets->Count > 0, "UnLimited tickets object should return tickets" ); } { my $tickets = RT::Tickets->new( RT->SystemUser ); $tickets->Limit( FIELD => 'id', OPERATOR => '>', VALUE => 0 ); my $count = $tickets->Count(); ok $count > 1, "found more than one ticket"; undef $count; $tickets->Limit( FIELD => 'id', OPERATOR => '=', VALUE => 1, ENTRYAGGREGATOR => 'none' ); $count = $tickets->Count(); ok $count == 1, "found one ticket"; } { my $tickets = RT::Tickets->new( RT->SystemUser ); my ($ret, $msg) = $tickets->FromSQL("Resolved IS NULL"); ok $ret, "Ran query with IS NULL: $msg"; my $count = $tickets->Count(); ok $count > 1, "Found more than one ticket"; undef $count; } { my $ticket = RT::Ticket->new( RT->SystemUser ); ok $ticket->Load(1), "Loaded test ticket 1"; ok $ticket->SetStatus('resolved'), "Set to resolved"; my $tickets = RT::Tickets->new( RT->SystemUser ); my ($ret, $msg) = $tickets->FromSQL("Resolved IS NOT NULL"); ok $ret, "Ran query with IS NOT NULL: $msg"; my $count = $tickets->Count(); ok $count == 1, "Found one ticket"; undef $count; } { my $tickets = RT::Tickets->new( RT->SystemUser ); $tickets->LimitDate( FIELD => "Resolved", OPERATOR => "IS", VALUE => "NULL" ); $tickets->LimitDate( FIELD => "Resolved", OPERATOR => "IS NOT", VALUE => "NULL" ); my $count = $tickets->Count(); ok $count > 1, "Found more than one ticket"; undef $count; } { my $tickets = RT::Tickets->new( RT->SystemUser ); my ( $ret, $msg ); warning_like { ( $ret, $msg ) = $tickets->FromSQL( "LastUpdated < yesterday" ); } qr/Couldn't parse query: Wrong query, expecting a VALUE in 'LastUpdated < >yesterday<--here'/; ok( !$ret, 'Invalid query' ); like( $msg, qr/Wrong query, expecting a VALUE in 'LastUpdated < >yesterday<--here'/, 'Invalid query message' ); } done_testing; rt-5.0.1/t/api/rights_show_ticket.t000644 000765 000024 00000016467 14005011336 020123 0ustar00sunnavystaff000000 000000 use RT::Test nodata => 1, tests => 264; use strict; use warnings; my $queue_a = RT::Test->load_or_create_queue( Name => 'A' ); ok $queue_a && $queue_a->id, 'loaded or created queue_a'; my $qa_id = $queue_a->id; my $queue_b = RT::Test->load_or_create_queue( Name => 'B' ); ok $queue_b && $queue_b->id, 'loaded or created queue_b'; my $qb_id = $queue_b->id; my $user_a = RT::Test->load_or_create_user( Name => 'user_a', Password => 'password', ); ok $user_a && $user_a->id, 'loaded or created user'; my $user_b = RT::Test->load_or_create_user( Name => 'user_b', Password => 'password', ); ok $user_b && $user_b->id, 'loaded or created user'; foreach my $option (0 .. 1 ) { RT->Config->Set( 'UseSQLForACLChecks' => $option ); diag "Testing with UseSQLForACLChecks => $option"; # Global Cc has right, a User is nobody { cleanup(); RT::Test->set_rights( { Principal => 'Everyone', Right => [qw(SeeQueue)] }, { Principal => 'Cc', Right => [qw(ShowTicket)] }, ); create_tickets_set(); have_no_rights($user_a, $user_b); } # Global Cc has right, a User is Queue Cc { cleanup(); RT::Test->set_rights( { Principal => 'Everyone', Right => [qw(SeeQueue)] }, { Principal => 'Cc', Right => [qw(ShowTicket)] }, ); create_tickets_set(); have_no_rights($user_a, $user_b); my ($status, $msg) = $queue_a->AddWatcher( Type => 'Cc', PrincipalId => $user_a->id ); ok($status, "user A is now queue A watcher"); foreach my $q ( '', "Queue = $qa_id OR Queue = $qb_id", "Queue = $qb_id OR Queue = $qa_id", ) { my $tickets = RT::Tickets->new( RT::CurrentUser->new( $user_a ) ); $q? $tickets->FromSQL($q) : $tickets->UnLimit; my $found = 0; while ( my $t = $tickets->Next ) { $found++; is( $t->Queue, $queue_a->id, "user sees tickets only in queue A" ); } is($found, 2, "user sees tickets"); } have_no_rights( $user_b ); } # global Cc has right, a User is ticket Cc { cleanup(); RT::Test->set_rights( { Principal => 'Everyone', Right => [qw(SeeQueue)] }, { Principal => 'Cc', Right => [qw(ShowTicket)] }, ); my @tickets = create_tickets_set(); have_no_rights($user_a, $user_b); my ($status, $msg) = $tickets[1]->AddWatcher( Type => 'Cc', PrincipalId => $user_a->id ); ok($status, "user A is now queue A watcher"); foreach my $q ( '', "Queue = $qa_id OR Queue = $qb_id", "Queue = $qb_id OR Queue = $qa_id", ) { my $tickets = RT::Tickets->new( RT::CurrentUser->new( $user_a ) ); $q? $tickets->FromSQL($q) : $tickets->UnLimit; my $found = 0; while ( my $t = $tickets->Next ) { $found++; is( $t->Queue, $queue_a->id, "user sees tickets only in queue A" ); is( $t->id, $tickets[1]->id, "correct ticket"); } is($found, 1, "user sees tickets"); } have_no_rights($user_b); } # Queue Cc has right, a User is nobody { cleanup(); RT::Test->set_rights( { Principal => 'Everyone', Right => [qw(SeeQueue)] }, { Principal => 'Cc', Object => $queue_a, Right => [qw(ShowTicket)] }, ); create_tickets_set(); have_no_rights($user_a, $user_b); } # Queue Cc has right, Users are Queue Ccs { cleanup(); RT::Test->set_rights( { Principal => 'Everyone', Right => [qw(SeeQueue)] }, { Principal => 'Cc', Object => $queue_a, Right => [qw(ShowTicket)] }, ); create_tickets_set(); have_no_rights($user_a, $user_b); my ($status, $msg) = $queue_a->AddWatcher( Type => 'Cc', PrincipalId => $user_a->id ); ok($status, "user A is now queue A watcher"); ($status, $msg) = $queue_b->AddWatcher( Type => 'Cc', PrincipalId => $user_b->id ); ok($status, "user B is now queue B watcher"); foreach my $q ( '', "Queue = $qa_id OR Queue = $qb_id", "Queue = $qb_id OR Queue = $qa_id", ) { my $tickets = RT::Tickets->new( RT::CurrentUser->new( $user_a ) ); $q? $tickets->FromSQL($q) : $tickets->UnLimit; my $found = 0; while ( my $t = $tickets->Next ) { $found++; is( $t->Queue, $queue_a->id, "user sees tickets only in queue A" ); } is($found, 2, "user sees tickets"); } have_no_rights( $user_b ); } # Queue Cc has right, Users are ticket Ccs { cleanup(); RT::Test->set_rights( { Principal => 'Everyone', Right => [qw(SeeQueue)] }, { Principal => 'Cc', Object => $queue_a, Right => [qw(ShowTicket)] }, ); my @tickets = create_tickets_set(); have_no_rights($user_a, $user_b); my ($status, $msg) = $tickets[1]->AddWatcher( Type => 'Cc', PrincipalId => $user_a->id ); ok($status, "user A is now Cc on a ticket in queue A"); ($status, $msg) = $tickets[2]->AddWatcher( Type => 'Cc', PrincipalId => $user_b->id ); ok($status, "user B is now Cc on a ticket in queue B"); foreach my $q ( '', "Queue = $qa_id OR Queue = $qb_id", "Queue = $qb_id OR Queue = $qa_id", ) { my $tickets = RT::Tickets->new( RT::CurrentUser->new( $user_a ) ); $q? $tickets->FromSQL($q) : $tickets->UnLimit; my $found = 0; while ( my $t = $tickets->Next ) { $found++; is( $t->Queue, $queue_a->id, "user sees tickets only in queue A" ); is( $t->id, $tickets[1]->id, ) } is($found, 1, "user sees tickets"); } have_no_rights( $user_b ); } # Users has direct right on queue { cleanup(); RT::Test->set_rights( { Principal => 'Everyone', Right => [qw(SeeQueue)] }, { Principal => $user_a, Object => $queue_a, Right => [qw(ShowTicket)] }, ); my @tickets = create_tickets_set(); foreach my $q ( '', "Queue = $qa_id OR Queue = $qb_id", "Queue = $qb_id OR Queue = $qa_id", ) { my $tickets = RT::Tickets->new( RT::CurrentUser->new( $user_a ) ); $q? $tickets->FromSQL($q) : $tickets->UnLimit; my $found = 0; while ( my $t = $tickets->Next ) { $found++; is( $t->Queue, $queue_a->id, "user sees tickets only in queue A" ); } is($found, 2, "user sees tickets"); } have_no_rights( $user_b ); } } sub have_no_rights { local $Test::Builder::Level = $Test::Builder::Level + 1; foreach my $u ( @_ ) { foreach my $q ( '', "Queue = $qa_id OR Queue = $qb_id", "Queue = $qb_id OR Queue = $qa_id", ) { my $tickets = RT::Tickets->new( RT::CurrentUser->new( $u ) ); $q? $tickets->FromSQL($q) : $tickets->UnLimit; ok(!$tickets->First, "no tickets"); } } } sub create_tickets_set{ local $Test::Builder::Level = $Test::Builder::Level + 1; my @res; foreach my $q ($queue_a, $queue_b) { foreach my $n (1 .. 2) { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($tid) = $ticket->Create( Queue => $q->id, Subject => $q->Name .' - '. $n ); ok( $tid, "created ticket #$tid"); push @res, $ticket; } } return @res; } sub cleanup { RT::Test->delete_tickets( "Queue = $qa_id OR Queue = $qb_id" ); RT::Test->delete_queue_watchers( $queue_a, $queue_b ); }; rt-5.0.1/t/api/total-time-worked.t000644 000765 000024 00000013023 14005011336 017551 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => undef; my $scrip = RT::Scrip->new(RT->SystemUser); $scrip->LoadByCols(Description => 'On TimeWorked Change Update Parent TimeWorked'); $scrip->SetDisabled(1); diag("Test tickets total time worked"); { my $parent = RT::Ticket->new(RT->SystemUser); my ($ok1,$msg1) = $parent->Create( Queue => 'general', Subject => 'total time worked test parent', ); ok($ok1,"Created parent ticket $msg1"); my $child = RT::Ticket->new(RT->SystemUser); my ($ok2,$msg2) = $child->Create( Queue => 'general', Subject => 'total time worked test child', ); ok($ok2,"Created child ticket $msg2"); my $grandchild = RT::Ticket->new(RT->SystemUser); my ($ok3,$msg3) = $grandchild->Create( Queue => 'general', Subject => 'total time worked test child child', ); ok($ok3,"Created grandchild ticket $msg3"); my ($ok4,$msg4) = $parent->AddLink( Type => 'MemberOf', Base => $child->id, ); ok($ok4,"Created parent -> child link $msg4"); my ($ok5,$msg5) = $child->AddLink( Type => 'MemberOf', Base => $grandchild->id, ); ok($ok5,"Created child -> grandchild link $msg5"); my $grandchild2 = RT::Ticket->new(RT->SystemUser); my ($ok6,$msg6) = $grandchild2->Create( Queue => 'general', Subject => 'total time worked test other child child', ); ok($ok6,"Create second grandchild $msg6"); my ($ok7,$msg7) = $child->AddLink( Type => 'MemberOf', Base => $grandchild2->id, ); ok($ok7,"Create child -> second grandchild link $msg7"); $parent->SetTimeWorked(10); $child->SetTimeWorked(20); $grandchild->SetTimeWorked(40); $grandchild2->SetTimeWorked(50); is $grandchild2->TimeWorked, 50, 'check other child child time worked'; is $grandchild->TimeWorked, 40, 'check child child time worked'; is $child->TimeWorked, 20, 'check child time worked'; is $parent->TimeWorked, 10, 'check parent time worked'; is $parent->TotalTimeWorked, 120, 'check parent total time worked'; is $child->TotalTimeWorked, 110, 'check child total time worked'; is $grandchild->TotalTimeWorked, 40, 'check child child total time worked'; is $grandchild2->TotalTimeWorked, 50, 'check other child child total time worked'; is $parent->TotalTimeWorkedAsString, '2 hours (120 minutes)', 'check parent total time worked as string'; is $grandchild->TotalTimeWorkedAsString, '40 minutes', 'check child child total time workd as string'; } diag("Test multiple inheritance total time worked"); { my $parent = RT::Ticket->new(RT->SystemUser); my ($ok1,$msg1) = $parent->Create( Queue => 'general', Subject => 'total time worked test parent', ); ok $ok1, "created parent ticket $msg1"; my $child = RT::Ticket->new(RT->SystemUser); my ($ok2,$msg2) = $child->Create( Queue => 'general', Subject => 'total time worked test child', ); ok $ok2, "created child ticket $msg2"; my $grandchild = RT::Ticket->new(RT->SystemUser); my ($ok3,$msg3) = $grandchild->Create( Queue => 'general', Subject => 'total time worked test child child, and test child', ); ok $ok3, "created grandchild ticket $msg3"; $parent->SetTimeWorked(10); $child->SetTimeWorked(20); $grandchild->SetTimeWorked(40); my ($ok4,$msg4) = $parent->AddLink( Type => 'MemberOf', Base => $child->id, ); ok $ok4, "Create parent -> child link $msg4"; my ($ok5,$msg5) = $child->AddLink( Type => 'MemberOf', Base => $grandchild->id, ); ok $ok5, "Create child -> grandchild link $msg5"; my ($ok6,$msg6) = $parent->AddLink( Type => 'MemberOf', Base => $grandchild->id, ); ok $ok6, "Created parent -> grandchild link $msg6"; is $parent->TotalTimeWorked, 70, 'check parent total time worked'; is $child->TotalTimeWorked, 60, 'check child total time worked'; is $grandchild->TotalTimeWorked, 40, 'check child child total time worked'; } diag("Test inheritance total time worked"); { my @warnings; my $parent = RT::Ticket->new(RT->SystemUser); my ($ok1,$msg1) = $parent->Create( Queue => 'general', Subject => 'total time worked test parent', ); ok $ok1, "created parent ticket $msg1"; my $child = RT::Ticket->new(RT->SystemUser); my ($ok2,$msg2) = $child->Create( Queue => 'general', Subject => 'total time worked test child', ); ok $ok2, "created child ticket $msg2"; { local $SIG{__WARN__} = sub { push @warnings, @_; }; my ($ok3,$msg3) = $parent->AddLink( Type => 'MemberOf', Base => $child->id, ); ok $ok3, "Created parent -> child link $msg3"; my ($ok4,$msg4) = $parent->AddLink( Type => 'MemberOf', Base => 'http://bestpractical.com', ); ok $ok4, "Create parent -> url link $msg4"; my ($ok5,$msg5) = $child->AddLink( Type => 'MemberOf', Base => 'http://docs.bestpractical.com/', ); ok $ok5, "Created child -> url link $msg5"; } $parent->SetTimeWorked(10); $child->SetTimeWorked(20); is $parent->TotalTimeWorked, 30, 'check parent total time worked'; is $child->TotalTimeWorked, 20, 'check child total time worked'; TODO: { local $TODO = "this warns because of the unrelated I#31399"; is(@warnings, 0, "no warnings"); } } done_testing; rt-5.0.1/t/api/versions_sorter.t000644 000765 000024 00000000735 14005011336 017455 0ustar00sunnavystaff000000 000000 use RT::Test nodata => 1, tests => 3; use strict; use warnings; sub is_right_sorting { my @order = @_; my @tmp = sort { int(rand(3)) - 1 } @order; is_deeply( [ sort RT::Handle::cmp_version @tmp ], \@order, 'test sorting of ('. join(' ', @tmp) .')' ); } is_right_sorting(qw(1 2 3)); is_right_sorting(qw(1.1 1.2 1.3 2.0 2.1)); is_right_sorting(qw(4.0.0a1 4.0.0alpha2 4.0.0b1 4.0.0beta2 4.0.0pre1 4.0.0pre2 4.0.0rc1 4.0.0rc2 4.0.0)); rt-5.0.1/t/api/canonical_charset.t000644 000765 000024 00000001377 14005011336 017652 0ustar00sunnavystaff000000 000000 use warnings; use strict; use RT::Test nodata => 1, tests => 11; use RT::I18N; my %map = ( 'euc-cn' => 'gbk', 'gb-2312' => 'gbk', gb2312 => 'gbk', utf8 => 'utf-8', 'utf-8' => 'utf-8', ); for my $charset ( keys %map ) { is( RT::I18N::_CanonicalizeCharset($charset), $map{$charset}, "$charset => $map{$charset}" ); is( RT::I18N::_CanonicalizeCharset( uc $charset ), $map{$charset}, uc( $charset ) . " => $map{$charset}" ); } my $mime = MIME::Entity->build( Type => 'text/plain; charset=gb2312', Data => [Encode::encode("gbk", Encode::decode( "UTF-8", "法新社倫敦11日電"))], ); RT::I18N::SetMIMEEntityToUTF8($mime); is( $mime->stringify_body, '法新社倫敦11日電', 'gb2312 => gbk in mail' ); rt-5.0.1/t/api/execute-code.t000644 000765 000024 00000006747 14005011336 016572 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 17; my $ticket = RT::Ticket->new(RT->SystemUser); ok( $ticket->Create( Subject => 'blue lines', Queue => 'General', ) ); my $attacker = RT::User->new(RT->SystemUser); ok( $attacker->Create( Name => 'attacker', Password => 'foobar', Privileged => 1, ) ); my $template_as_attacker = RT::Template->new($attacker); # can't create templates without ModifyTemplate my ($ok, $msg) = $template_as_attacker->Create( Name => 'Harmless, honest!', Content => "\nhello ;)", Type => 'Perl', ); ok(!$ok, 'permission to create denied'); # permit modifying templates but they must be simple $attacker->PrincipalObj->GrantRight(Right => 'ShowTemplate', Object => $RT::System); $attacker->PrincipalObj->GrantRight(Right => 'ModifyTemplate', Object => $RT::System); ($ok, $msg) = $template_as_attacker->Create( Name => 'Harmless, honest!', Content => "\nhello ;)", Type => 'Perl', ); ok(!$ok, 'permission to create denied'); ($ok, $msg) = $template_as_attacker->Create( Name => 'Harmless, honest!', Content => "\nhello ;)", Type => 'Simple', ); ok($ok, 'created template now that we have ModifyTemplate'); ($ok, $msg) = $template_as_attacker->SetType('Perl'); ok(!$ok, 'permission to update type to Perl denied'); my $template_as_root = RT::Template->new(RT->SystemUser); $template_as_root->Load('Harmless, honest!'); is($template_as_root->Content, "\nhello ;)"); is($template_as_root->Type, 'Simple'); $template_as_root->Parse(TicketObj => $ticket); is($template_as_root->MIMEObj->stringify_body, "hello ;)"); # update the content to include code (even though Simple won't parse it) ($ok, $msg) = $template_as_attacker->SetContent("\nYou are { (my \$message = 'bjarq') =~ tr/a-z/n-za-m/; \$message }!"); ok($ok, 'updating Content permitted since the template is Simple'); $template_as_root = RT::Template->new(RT->SystemUser); $template_as_root->Load('Harmless, honest!'); is($template_as_root->Content, "\nYou are { (my \$message = 'bjarq') =~ tr/a-z/n-za-m/; \$message }!"); is($template_as_root->Type, 'Simple'); $template_as_root->Parse(TicketObj => $ticket); is($template_as_root->MIMEObj->stringify_body, "You are { (my \$message = 'bjarq') =~ tr/a-z/n-za-m/; \$message }!"); # try again, why not ($ok, $msg) = $template_as_attacker->SetType('Perl'); ok(!$ok, 'permission to update type to Perl denied'); # now root will change the template to genuine code $template_as_root = RT::Template->new(RT->SystemUser); $template_as_root->Load('Harmless, honest!'); $template_as_root->SetType('Perl'); $template_as_root->SetContent("\n{ scalar reverse \$Ticket->Subject }"); $template_as_root->Parse(TicketObj => $ticket); is($template_as_root->MIMEObj->stringify_body, "senil eulb"); # see if we can update anything $template_as_attacker = RT::Template->new($attacker); $template_as_attacker->Load('Harmless, honest!'); ($ok, $msg) = $template_as_attacker->SetContent("\nYou are { (my \$message = 'bjarq') =~ tr/a-z/n-za-m/; \$message }!"); ok(!$ok, 'updating Content forbidden since the template is Perl'); # try again just to be absolutely sure it doesn't work $template_as_root = RT::Template->new(RT->SystemUser); $template_as_root->Load('Harmless, honest!'); $template_as_root->SetType('Perl'); $template_as_root->SetContent("\n{ scalar reverse \$Ticket->Subject }"); $template_as_root->Parse(TicketObj => $ticket); is($template_as_root->MIMEObj->stringify_body, "senil eulb"); rt-5.0.1/t/api/savedsearch.t000644 000765 000024 00000020245 14005011336 016475 0ustar00sunnavystaff000000 000000 use strict; use warnings; BEGIN { $ENV{'LANG'} = 'C' } use RT::Test tests => undef; use_ok('RT::SavedSearch'); use_ok('RT::SavedSearches'); use Test::Warn; # Set up some infrastructure. These calls are tested elsewhere. my $searchuser = RT::User->new(RT->SystemUser); my ($ret, $msg) = $searchuser->Create(Name => 'searchuser'.$$, Privileged => 1, EmailAddress => "searchuser\@p$$.example.com", RealName => 'Search user'); ok($ret, "created searchuser: $msg"); $searchuser->PrincipalObj->GrantRight(Right => 'LoadSavedSearch'); $searchuser->PrincipalObj->GrantRight(Right => 'CreateSavedSearch'); $searchuser->PrincipalObj->GrantRight(Right => 'ModifySelf'); # This is the group whose searches searchuser should be able to see. my $ingroup = RT::Group->new(RT->SystemUser); $ingroup->CreateUserDefinedGroup(Name => 'searchgroup1'.$$); $ingroup->AddMember($searchuser->Id); diag('Check saved search rights'); my @create_objects = RT::SavedSearch->new($searchuser)->ObjectsForCreating; is( scalar @create_objects, 1, 'Got one Privacy option for saving searches'); is( $create_objects[0]->Id, $searchuser->Id, 'Privacy option is personal saved search'); $searchuser->PrincipalObj->GrantRight(Right => 'EditSavedSearches', Object => $ingroup); $searchuser->PrincipalObj->GrantRight(Right => 'ShowSavedSearches', Object => $ingroup); @create_objects = RT::SavedSearch->new($searchuser)->ObjectsForCreating; is( scalar @create_objects, 2, 'Got two Privacy options for saving searches'); is( $create_objects[1]->Id, $ingroup->Id, 'Second Privacy option is group saved search'); # This is the group whose searches searchuser should not be able to see. my $outgroup = RT::Group->new(RT->SystemUser); $outgroup->CreateUserDefinedGroup(Name => 'searchgroup2'.$$); $outgroup->AddMember(RT->SystemUser->Id); my $queue = RT::Queue->new(RT->SystemUser); $queue->Create(Name => 'SearchQueue'.$$); $searchuser->PrincipalObj->GrantRight(Right => 'SeeQueue', Object => $queue); $searchuser->PrincipalObj->GrantRight(Right => 'ShowTicket', Object => $queue); $searchuser->PrincipalObj->GrantRight(Right => 'OwnTicket', Object => $queue); my $ticket = RT::Ticket->new(RT->SystemUser); $ticket->Create(Queue => $queue->Id, Requestor => [ $searchuser->Name ], Owner => $searchuser, Subject => 'saved search test'); # Now start the search madness. my $curruser = RT::CurrentUser->new($searchuser); my $format = '\' __id__/TITLE:#\', \'__Subject__/TITLE:Subject\', \'__Status__\', \'__QueueName__\', \'__OwnerName__\', \'__Priority__\', \'__NEWLINE__\', \'\', \'__Requestors__\', \'__CreatedRelative__\', \'__ToldRelative__\', \'__LastUpdatedRelative__\', \'__TimeLeft__\''; my $mysearch = RT::SavedSearch->new($curruser); ($ret, $msg) = $mysearch->Save(Privacy => 'RT::User-' . $searchuser->Id, Type => 'Ticket', Name => 'owned by me', SearchParams => {'Format' => $format, 'Query' => "Owner = '" . $searchuser->Name . "'"}); ok($ret, "mysearch was created"); my $groupsearch = RT::SavedSearch->new($curruser); ($ret, $msg) = $groupsearch->Save(Privacy => 'RT::Group-' . $ingroup->Id, Type => 'Ticket', Name => 'search queue', SearchParams => {'Format' => $format, 'Query' => "Queue = '" . $queue->Name . "'"}); ok($ret, "groupsearch was created"); my $othersearch = RT::SavedSearch->new($curruser); ($ret, $msg) = $othersearch->Save(Privacy => 'RT::Group-' . $outgroup->Id, Type => 'Ticket', Name => 'searchuser requested', SearchParams => {'Format' => $format, 'Query' => "Requestor.Name LIKE 'search'"}); ok(!$ret, "othersearch NOT created"); like($msg, qr/Failed to load object for/, "...for the right reason"); $othersearch = RT::SavedSearch->new(RT->SystemUser); ($ret, $msg) = $othersearch->Save(Privacy => 'RT::Group-' . $outgroup->Id, Type => 'Ticket', Name => 'searchuser requested', SearchParams => {'Format' => $format, 'Query' => "Requestor.Name LIKE 'search'"}); ok($ret, "othersearch created by systemuser"); # Now try to load some searches. # This should work. my $loadedsearch1 = RT::SavedSearch->new($curruser); $loadedsearch1->Load('RT::User-'.$curruser->Id, $mysearch->Id); is($loadedsearch1->Id, $mysearch->Id, "Loaded mysearch"); like($loadedsearch1->GetParameter('Query'), qr/Owner/, "Retrieved query of mysearch"); # Check through the other accessor methods. is($loadedsearch1->Privacy, 'RT::User-' . $curruser->Id, "Privacy of mysearch correct"); is($loadedsearch1->Name, 'owned by me', "Name of mysearch correct"); is($loadedsearch1->Type, 'Ticket', "Type of mysearch correct"); # See if it can be used to search for tickets. my $tickets = RT::Tickets->new($curruser); $tickets->FromSQL($loadedsearch1->GetParameter('Query')); is($tickets->Count, 1, "Found a ticket"); # This should fail -- wrong object. # my $loadedsearch2 = RT::SavedSearch->new($curruser); # $loadedsearch2->Load('RT::User-'.$curruser->Id, $groupsearch->Id); # isnt($loadedsearch2->Id, $othersearch->Id, "Didn't load groupsearch as mine"); # ...but this should succeed. my $loadedsearch3 = RT::SavedSearch->new($curruser); $loadedsearch3->Load('RT::Group-'.$ingroup->Id, $groupsearch->Id); is($loadedsearch3->Id, $groupsearch->Id, "Loaded groupsearch"); like($loadedsearch3->GetParameter('Query'), qr/Queue/, "Retrieved query of groupsearch"); # Can it get tickets? $tickets = RT::Tickets->new($curruser); $tickets->FromSQL($loadedsearch3->GetParameter('Query')); is($tickets->Count, 1, "Found a ticket"); # This should fail -- no permission. my $loadedsearch4 = RT::SavedSearch->new($curruser); warning_like { $loadedsearch4->Load($othersearch->Privacy, $othersearch->Id); } qr/Could not load object RT::Group-\d+ when loading search/; isnt($loadedsearch4->Id, $othersearch->Id, "Did not load othersearch"); # Try to update an existing search. $loadedsearch1->Update( SearchParams => {'Format' => $format, 'Query' => "Queue = '" . $queue->Name . "'" } ); like($loadedsearch1->GetParameter('Query'), qr/Queue/, "Updated mysearch parameter"); is($loadedsearch1->Type, 'Ticket', "mysearch is still for tickets"); is($loadedsearch1->Privacy, 'RT::User-'.$curruser->Id, "mysearch still belongs to searchuser"); like($mysearch->GetParameter('Query'), qr/Queue/, "other mysearch object updated"); ## Right ho. Test the pseudo-collection object. my $genericsearch = RT::SavedSearch->new($curruser); $genericsearch->Save(Name => 'generic search', Type => 'all', SearchParams => {'Query' => "Queue = 'General'"}); my $ticketsearches = RT::SavedSearches->new($curruser); $ticketsearches->LimitToPrivacy('RT::User-'.$curruser->Id, 'Ticket'); is($ticketsearches->Count, 1, "Found searchuser's ticket searches"); my $allsearches = RT::SavedSearches->new($curruser); $allsearches->LimitToPrivacy('RT::User-'.$curruser->Id); is($allsearches->Count, 2, "Found all searchuser's searches"); # Delete a search. ($ret, $msg) = $genericsearch->Delete; ok($ret, "Deleted genericsearch"); $allsearches->LimitToPrivacy('RT::User-'.$curruser->Id); is($allsearches->Count, 1, "Found all searchuser's searches after deletion"); done_testing(); rt-5.0.1/t/api/attachment.t000644 000765 000024 00000015331 14005011336 016335 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => undef; { ok (require RT::Attachment); } { my $test1 = "From: jesse"; my @headers = RT::Attachment->_SplitHeaders($test1); is ($#headers, 0, $test1 ); my $test2 = qq{From: jesse To: bobby Subject: foo }; @headers = RT::Attachment->_SplitHeaders($test2); is ($#headers, 2, "testing a bunch of singline multiple headers" ); my $test3 = qq{From: jesse To: bobby, Suzie, Sally, Joey: bizzy, Subject: foo }; @headers = RT::Attachment->_SplitHeaders($test3); is ($#headers, 2, "testing a bunch of singline multiple headers" ); } { my $iso_8859_1_ticket_email = RT::Test::get_relocatable_file( 'new-ticket-from-iso-8859-1', ( File::Spec->updir(), 'data', 'emails' ) ); my $content = RT::Test->file_content($iso_8859_1_ticket_email); my $parser = RT::EmailParser->new; $parser->ParseMIMEEntityFromScalar($content); my $attachment = RT::Attachment->new( $RT::SystemUser ); my ( $id, $msg ) = $attachment->Create( TransactionId => 1, Attachment => $parser->Entity ); ok( $id, $msg ); my $mime = $attachment->ContentAsMIME; like( $mime->head->get('Content-Type'), qr/charset="iso-8859-1"/, 'content type of ContentAsMIME is original' ); is( Encode::decode( 'iso-8859-1', $mime->stringify_body ), Encode::decode( 'UTF-8', "HÃ¥vard\n" ), 'body of ContentAsMIME is original' ); } diag 'Test clearing and replacing header and content in attachments table'; { my $queue = RT::Test->load_or_create_queue( Name => 'General' ); ok $queue && $queue->id, 'loaded or created queue'; my $t = RT::Test->create_ticket( Queue => 'General', Subject => 'test' ); ok $t && $t->id, 'created a ticket'; $t->Comment( Content => 'test' ); my $attachments = RT::Attachments->new(RT->SystemUser); $attachments->Limit( FIELD => 'Content', OPERATOR => 'LIKE', VALUE => 'test', ); is $attachments->Count, 1, 'Found content with "test"'; # Replace attachment value for 'test' in Conetent col my ($ret, $msg) = $attachments->ReplaceAttachments(Search => 'test', Replacement => 'new_value', Headers => 0); ok $ret, $msg; $attachments->CleanSlate; $attachments->Limit( FIELD => 'Content', OPERATOR => 'LIKE', VALUE => 'test', ); is $attachments->Count, 0, 'Found no content with "test"'; $attachments->Limit( FIELD => 'Content', OPERATOR => 'LIKE', VALUE => 'new_value', ); is $attachments->Count, 1, 'Found content with "new_value"'; $attachments->CleanSlate; $attachments->Limit( FIELD => 'Headers', OPERATOR => 'LIKE', VALUE => 'API', ); is $attachments->Count, 1, 'Found header with content "API"'; # Replace attachment value for 'API' in Header col ($ret, $msg) = $attachments->ReplaceAttachments(Search => 'API', Replacement => 'replacement', Content => 0); ok $ret, $msg; $attachments->CleanSlate; $attachments->Limit( FIELD => 'Headers', OPERATOR => 'LIKE', VALUE => 'API', ); is $attachments->Count, 0, 'Found no header with content "API"'; $attachments->CleanSlate; $attachments->Limit( FIELD => 'Headers', OPERATOR => 'LIKE', VALUE => 'replacement', ); is $attachments->Count, 1, 'Found header with content "replacement"'; ($ret, $msg) = $attachments->ReplaceAttachments(Search => 'new_value', Replacement => 'replacement', Content => 0); ok $ret, $msg; $attachments->CleanSlate; $attachments->Limit( FIELD => 'Content', OPERATOR => 'LIKE', VALUE => 'new_value', ); is $attachments->Count, 1, 'Content is not changed when flagged as false'; ($ret, $msg) = $attachments->ReplaceAttachments(Search => 'replacement', Replacement => 'new_value', Headers => 0); ok $ret, $msg; $attachments->CleanSlate; $attachments->Limit( FIELD => 'Headers', OPERATOR => 'LIKE', VALUE => 'replacement', ); is $attachments->Count, 1, 'Headers are not replaced when flagged as false'; } diag 'Test clearing and replacing header and content in attachments from example emails'; { my $email_file = RT::Test::get_relocatable_file( 'multipart-alternative-with-umlaut', ( File::Spec->updir(), 'data', 'emails' ) ); my $content = RT::Test->file_content($email_file); my $parser = RT::EmailParser->new; $parser->ParseMIMEEntityFromScalar($content); my $ticket = RT::Test->create_ticket( Queue => 'General', Subject => 'test munge', MIMEObj => $parser->Entity ); my $decoded_umlaut = Encode::decode( 'UTF-8', 'Grüßen' ); my $attachments = $ticket->Attachments( WithHeaders => 1, WithContent => 1 ); while ( my $att = $attachments->Next ) { if ( $att->Content ) { like( $att->Content, qr/$decoded_umlaut/, "Content contains $decoded_umlaut" ); unlike( $att->Content, qr/anonymous/, 'Content lacks anonymous' ); } else { like( $att->Headers, qr/"Stever, Gregor" /, 'Headers contain gst@example.com' ); unlike( $att->Headers, qr/anon\@example.com/, 'Headers lack anon@example.com' ); } } my $ticket_id = $ticket->id; # ticket id could have utf8 flag on On Oracle :/ if ( utf8::is_utf8($ticket_id) ) { $ticket_id = Encode::encode( 'UTF-8', $ticket_id ); } RT::Test->run_and_capture( command => $RT::SbinPath . '/rt-munge-attachments', tickets => $ticket_id, search => 'Grüßen', replacement => 'anonymous', ); RT::Test->run_and_capture( command => $RT::SbinPath . '/rt-munge-attachments', tickets => $ticket_id, search => '"Stever, Gregor" ', replacement => 'anon@example.com', ); $attachments = $ticket->Attachments( WithHeaders => 1, WithContent => 1 ); while ( my $att = $attachments->Next ) { my $decoded_umlaut = Encode::decode( 'UTF-8', 'Grüßen' ); if ( $att->Content ) { unlike( $att->Content, qr/$decoded_umlaut/, "Content lacks $decoded_umlaut" ); like( $att->Content, qr/anonymous/, 'Content contains anonymous' ); } else { unlike( $att->Headers, qr/"Stever, Gregor" /, 'Headers lack gst@example.com' ); like( $att->Headers, qr/anon\@example.com/, 'Headers contain anon@example.com' ); } } } done_testing(); rt-5.0.1/t/api/cf_render_type.t000644 000765 000024 00000002602 14005011336 017172 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 13; my $cf = RT::CustomField->new($RT::SystemUser); my ( $id, $ret, $msg ); diag "single select"; ( $id, $msg ) = $cf->Create( Name => 'single_select', Type => 'Select', MaxValues => '1', Queue => 0, ); ok( $id, $msg ); is( $cf->RenderType, 'Dropdown', 'default render type is Dropdown' ); ( $ret, $msg ) = $cf->SetRenderType('Select box'); ok( $ret, 'changed to Select box' ); is( $cf->RenderType, 'Select box', 'render type is indeed updated' ); ( $ret, $msg ) = $cf->SetRenderType('List'); ok( $ret, 'changed to List' ); is( $cf->RenderType, 'List', 'render type is indeed updated' ); ( $ret, $msg ) = $cf->SetRenderType('fakeone'); ok( !$ret, 'failed to set an invalid render type' ); is( $cf->RenderType, 'List', 'render type is still List' ); diag "multiple select"; ( $id, $msg ) = $cf->Create( Name => 'multiple_select', Type => 'Select', MaxValues => '0', Queue => 0, RenderType => 'List', ); is( $cf->RenderType, 'List', 'set render type to List' ); ( $ret, $msg ) = $cf->SetRenderType('Dropdown'); ok( !$ret, 'Dropdown is invalid for multiple select' ); is( $cf->RenderType, 'List', 'render type is still List' ); ( $ret, $msg ) = $cf->SetRenderType('Select box'); ok( $ret, 'changed to Select box' ); is( $cf->RenderType, 'Select box', 'render type is indeed updated' ); rt-5.0.1/t/api/rtname.t000644 000765 000024 00000004114 14005011336 015470 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => undef; use RT::Interface::Email; # normal use case, regexp set to rtname RT->Config->Set( rtname => "site" ); RT->Config->Set( EmailSubjectTagRegex => qr/site/ ); RT->Config->Set( rtname => undef ); is(RT::Interface::Email::ParseTicketId("[site #123] test"), 123); is(RT::Interface::Email::ParseTicketId("[othersite #123] test"), undef); # oops usecase, where the regexp is scragged RT->Config->Set( rtname => "site" ); RT->Config->Set( EmailSubjectTagRegex => undef ); is(RT::Interface::Email::ParseTicketId("[site #123] test"), 123); is(RT::Interface::Email::ParseTicketId("[othersite #123] test"), undef); # set to a simple regexp. NOTE: we no longer match "site" RT->Config->Set( rtname => "site"); RT->Config->Set( EmailSubjectTagRegex => qr/newsite/); is(RT::Interface::Email::ParseTicketId("[site #123] test"), undef); is(RT::Interface::Email::ParseTicketId("[newsite #123] test"), 123); # set to a more complex regexp RT->Config->Set( rtname => "site" ); RT->Config->Set( EmailSubjectTagRegex => qr/newsite|site/ ); is(RT::Interface::Email::ParseTicketId("[site #123] test"), 123); is(RT::Interface::Email::ParseTicketId("[newsite #123] test"), 123); is(RT::Interface::Email::ParseTicketId("[othersite #123] test"), undef); # Parens work fine RT->Config->Set( EmailSubjectTagRegex => qr/(new|)(site)/ ); is(RT::Interface::Email::ParseTicketId("[site #123] test"), 123); is(RT::Interface::Email::ParseTicketId("[newsite #123] test"), 123); is(RT::Interface::Email::ParseTicketId("[othersite #123] test"), undef); # extra http:// added by Outlook 365 for web RT->Config->Set( rtname => 'site.com' ); RT->Config->Set( EmailSubjectTagRegex => undef ); is(RT::Interface::Email::ParseTicketId("[http://site.com #123] test"), 123); is(RT::Interface::Email::ParseTicketId("[http://othersite.com #123] test"), undef); RT->Config->Set( EmailSubjectTagRegex => qr/\Qsite.com\E/ ); is(RT::Interface::Email::ParseTicketId("[http://site.com #123] test"), 123); is(RT::Interface::Email::ParseTicketId("[http://othersite.com #123] test"), undef); done_testing; rt-5.0.1/t/api/i18n_mime_encoding.t000644 000765 000024 00000001517 14005011336 017642 0ustar00sunnavystaff000000 000000 use warnings; use strict; use RT::Test nodata => 1, tests => undef; use RT::I18N; use Test::Warn; diag "normal mime encoding conversion: utf8 => iso-8859-1"; { my $mime = MIME::Entity->build( Type => 'text/plain; charset=utf-8', Data => ['À中文'], ); warning_like { RT::I18N::SetMIMEEntityToEncoding( $mime, 'iso-8859-1', ); } [ qr/does not map to iso-8859-1/ ], 'got one "not map" error'; is( $mime->stringify_body, 'À中文', 'body is not changed' ); is( $mime->head->mime_attr('Content-Type'), 'application/octet-stream' ); } diag "mime encoding conversion: utf8 => iso-8859-1"; { my $mime = MIME::Entity->build( Type => 'text/plain; charset=utf-8', Data => ['À中文'], ); is( $mime->stringify_body, 'À中文', 'body is not changed' ); } done_testing; rt-5.0.1/t/api/user_recently_visited.t000644 000765 000024 00000005776 14005011336 020633 0ustar00sunnavystaff000000 000000 use strict; use warnings; # test the RecentlyViewedTickets method use RT::Test::Shredder tests => undef; my $test = "RT::Test::Shredder"; { my $user = RT::Test->load_or_create_user( Name => 'tester' ); ok( $user && $user->Id, 'created user' ); # need ShowTicket to visit, ModifyTicket to merge ok( RT::Test->add_rights( { Principal => $user, Right => [qw( SeeQueue CreateTicket ShowTicket ModifyTicket )] } ), 'granting rights' ); is( visited_nb( $user ), 0, 'init: empty RecentlyViewedTickets' ); my %ticket; foreach my $ticket_code ( 'a' .. 't' ) { my $new_ticket = RT::Test->create_ticket( Subject => $ticket_code, Queue => 'General' ); my $id = $new_ticket->id; ok( $new_ticket, "ticket '$ticket_code' created" ); $ticket{$ticket_code} = { ticket => $new_ticket, id => $id, subject => $ticket_code }; $new_ticket->ApplyTransactionBatch ; # needed for the tests with shredder to work } is( visited_nb( $user ), 0, 'tickets created: empty RecentlyViewedTickets' ); # using reverse on the list so it's easier to read later (last visited is first in RecentlyViewedTicket) foreach my $viewed ( reverse qw( c q p b e d c b a ) ) { $user->AddRecentlyViewedTicket( $ticket{$viewed}->{ticket} ); } is( visited( $user ), 'c,q,p,b,e,d,a', 'visited tickets after inital visits' ); my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $ticket{m}->{ticket} ); $shredder->WipeoutAll; is( visited( $user ), 'c,q,p,b,e,d,a', 'visited tickets after shredding an unvisited ticket' ); $shredder->PutObjects( Objects => $ticket{p}->{ticket} ); $shredder->PutObjects( Objects => $ticket{d}->{ticket} ); $shredder->WipeoutAll; is( visited( $user ), 'c,q,b,e,a', 'visited tickets after shredding 2 visited tickets' ); my ( $ok, $msg ) = $ticket{b}->{ticket}->MergeInto( $ticket{j}->{id} ); if ( !$ok ) { die "error merging: $msg\n"; } is( visited( $user ), 'c,q,j,e,a', 'visited tickets after merging into a ticket that was NOT on the list' ); $ticket{c}->{ticket}->MergeInto( $ticket{a}->{id} ); is( visited( $user ), 'a,q,j,e', 'visited tickets after merging into a ticket that was on the list' ); $ticket{e}->{ticket}->MergeInto( $ticket{j}->{id} ); is( visited( $user ), 'a,q,j', 'visited tickets after merging into a ticket that was on the list (merged)' ); foreach my $viewed ( reverse qw( r j a h k a q a k i s t f g d ) ) { $user->AddRecentlyViewedTicket( $ticket{$viewed}->{ticket} ); } is( visited( $user ), 'r,j,a,h,k,q,i,s,t,f', 'visited more than 10 tickets' ); } done_testing(); sub visited_nb { return scalar shift->RecentlyViewedTickets; } sub visited { return join ',', map { $_->{subject} } shift->RecentlyViewedTickets; } rt-5.0.1/t/api/scrip.t000644 000765 000024 00000022031 14005011336 015320 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test; my $queue = RT::Test->load_or_create_queue( Name => 'General' ); ok $queue && $queue->id, 'loaded or created queue'; note 'basic scrips functionality test: create+execute'; { my $s1 = RT::Scrip->new(RT->SystemUser); my ($val, $msg) = $s1->Create( Queue => $queue->Id, ScripAction => 'User Defined', ScripCondition => 'User Defined', CustomIsApplicableCode => '$self->TicketObj->Subject =~ /fire/? 1 : 0', CustomPrepareCode => 'return 1', CustomCommitCode => '$self->TicketObj->SetPriority("87");', Template => 'Blank' ); ok($val,$msg); my $ticket = RT::Ticket->new(RT->SystemUser); my ($tv,$ttv,$tm) = $ticket->Create( Queue => $queue->Id, Subject => "hair on fire", ); ok($tv, $tm); is ($ticket->Priority , '87', "Ticket priority is set right"); my $ticket2 = RT::Ticket->new(RT->SystemUser); my ($t2v,$t2tv,$t2m) = $ticket2->Create( Queue => $queue->Id, Subject => "hair in water", ); ok($t2v, $t2m); isnt ($ticket2->Priority , '87', "Ticket priority is set right"); } note 'modify properties of a scrip'; { my $scrip = RT::Scrip->new($RT::SystemUser); my ( $val, $msg ) = $scrip->Create( ScripCondition => 'On Comment', ScripAction => 'Notify Owner', ); ok( !$val, "missing template: $msg" ); ( $val, $msg ) = $scrip->Create( ScripCondition => 'On Comment', ScripAction => 'Notify Owner', Template => 'not exists', ); ok( !$val, "invalid template: $msg" ); ( $val, $msg ) = $scrip->Create( ScripAction => 'Notify Owner', Template => 'Blank', ); ok( !$val, "missing condition: $msg" ); ( $val, $msg ) = $scrip->Create( ScripCondition => 'not exists', ScripAction => 'Notify Owner', Template => 'Blank', ); ok( !$val, "invalid condition: $msg" ); ( $val, $msg ) = $scrip->Create( ScripCondition => 'On Comment', Template => 'Blank', ); ok( !$val, "missing action: $msg" ); ( $val, $msg ) = $scrip->Create( ScripCondition => 'On Comment', ScripAction => 'not exists', Template => 'Blank', ); ok( !$val, "invalid action: $msg" ); ( $val, $msg ) = $scrip->Create( ScripAction => 'Notify Owner', ScripCondition => 'On Comment', Template => 'Blank', ); ok( $val, "created scrip: $msg" ); $scrip->Load($val); ok( $scrip->id, 'loaded scrip ' . $scrip->id ); ( $val, $msg ) = $scrip->SetScripCondition(); ok( !$val, "missing condition: $msg" ); ( $val, $msg ) = $scrip->SetScripCondition('not exists'); ok( !$val, "invalid condition: $msg" ); ( $val, $msg ) = $scrip->SetScripCondition('On Correspond'); ok( $val, "updated condition to 'On Correspond': $msg" ); ( $val, $msg ) = $scrip->SetScripAction(); ok( !$val, "missing action: $msg" ); ( $val, $msg ) = $scrip->SetScripAction('not exists'); ok( !$val, "invalid action: $msg" ); ( $val, $msg ) = $scrip->SetScripAction('Notify AdminCcs'); ok( $val, "updated action to 'Notify AdminCcs': $msg" ); ( $val, $msg ) = $scrip->SetTemplate(); ok( !$val, "missing template $msg" ); ( $val, $msg ) = $scrip->SetTemplate('not exists'); ok( !$val, "invalid template $msg" ); ( $val, $msg ) = $scrip->SetTemplate('Forward'); ok( $val, "updated template to 'Forward': $msg" ); ok( $scrip->Delete, 'delete the scrip' ); } my $queue_B = RT::Test->load_or_create_queue( Name => 'B' ); ok $queue_B && $queue_B->id, 'loaded or created queue'; note 'check creation errors vs. templates'; { my $scrip = RT::Scrip->new(RT->SystemUser); my ($status, $msg) = $scrip->Create( Queue => $queue->id, ScripAction => 'User Defined', ScripCondition => 'User Defined', Template => 'not exist', ); ok(!$status, "couldn't create scrip, not existing template"); ($status, $msg) = $scrip->Create( ScripAction => 'User Defined', ScripCondition => 'User Defined', Template => 'not exist', ); ok(!$status, "couldn't create scrip, not existing template"); ($status, $msg) = $scrip->Create( Queue => $queue->id, ScripAction => 'User Defined', ScripCondition => 'User Defined', Template => 54321, ); ok(!$status, "couldn't create scrip, not existing template"); ($status, $msg) = $scrip->Create( ScripAction => 'User Defined', ScripCondition => 'User Defined', Template => 54321, ); ok(!$status, "couldn't create scrip, not existing template"); my $template = RT::Template->new( RT->SystemUser ); ($status, $msg) = $template->Create( Queue => $queue->id, Name => 'bar' ); ok $status, 'created a template'; ($status, $msg) = $scrip->Create( ScripAction => 'User Defined', ScripCondition => 'User Defined', Template => $template->id, ); ok(!$status, "couldn't create scrip, wrong template"); ($status, $msg) = $scrip->Create( Queue => $queue_B->id, ScripAction => 'User Defined', ScripCondition => 'User Defined', Template => $template->id, ); ok(!$status, "couldn't create scrip, wrong template"); } note 'check applications vs. templates'; { my $template = RT::Template->new( RT->SystemUser ); my ($status, $msg) = $template->Create( Queue => $queue->id, Name => 'foo' ); ok $status, 'created a template'; my $scrip = RT::Scrip->new(RT->SystemUser); ($status, $msg) = $scrip->Create( Queue => $queue->Id, ScripAction => 'User Defined', ScripCondition => 'User Defined', Template => 'foo', CustomIsApplicableCode => "1;", CustomPrepareCode => "1;", CustomCommitCode => "1;", ); ok($status, 'created a scrip') or diag "error: $msg"; RT::Test->object_scrips_are($scrip, [$queue], [0, $queue_B]); ($status, $msg) = $scrip->AddToObject( $queue_B->id ); ok(!$status, $msg); RT::Test->object_scrips_are($scrip, [$queue], [0, $queue_B]); my $obj_scrip = RT::ObjectScrip->new( RT->SystemUser ); ok($obj_scrip->LoadByCols( Scrip => $scrip->id, ObjectId => $queue->id )); is($obj_scrip->Stage, 'TransactionCreate'); is($obj_scrip->FriendlyStage, 'Normal'); $template = RT::Template->new( RT->SystemUser ); ($status, $msg) = $template->Create( Queue => $queue_B->id, Name => 'foo' ); ok $status, 'created a template'; ($status, $msg) = $scrip->AddToObject( $queue_B->id ); ok($status, 'added scrip to another queue'); RT::Test->object_scrips_are($scrip, [$queue, $queue_B], [0]); ($status, $msg) = $scrip->RemoveFromObject( $queue_B->id ); ok($status, 'removed scrip from queue'); ($status, $msg) = $template->Delete; ok $status, 'deleted template foo in queue B'; ($status, $msg) = $scrip->AddToObject( $queue_B->id ); ok(!$status, $msg); RT::Test->object_scrips_are($scrip, [$queue], [0, $queue_B]); ($status, $msg) = $template->Create( Queue => 0, Name => 'foo' ); ok $status, 'created a global template'; ($status, $msg) = $scrip->AddToObject( $queue_B->id ); ok($status, 'added scrip'); RT::Test->object_scrips_are($scrip, [$queue, $queue_B], [0]); } note 'basic check for disabling scrips'; { my $scrip = RT::Scrip->new(RT->SystemUser); my ($status, $msg) = $scrip->Create( Queue => $queue->id, ScripCondition => 'On Create', ScripAction => 'User Defined', CustomPrepareCode => 'return 1', CustomCommitCode => '$self->TicketObj->SetPriority("87"); return 1', Template => 'Blank' ); ok($status, "created scrip"); is($scrip->Disabled, 0, "not disabled"); { my $ticket = RT::Ticket->new(RT->SystemUser); my ($tid, undef, $msg) = $ticket->Create( Queue => $queue->id, Subject => "test", ); ok($tid, "created ticket") or diag "error: $msg"; is ($ticket->Priority , '87', "Ticket priority is set right"); } ($status,$msg) = $scrip->SetDisabled(1); is($scrip->Disabled, 1, "disabled"); { my $ticket = RT::Ticket->new(RT->SystemUser); my ($tid, undef, $msg) = $ticket->Create( Queue => $queue->id, Subject => "test", ); ok($tid, "created ticket") or diag "error: $msg"; isnt ($ticket->Priority , '87', "Ticket priority is set right"); } is($scrip->FriendlyStage('TransactionCreate'), 'Normal', 'Correct stage wording for TransactionCreate'); is($scrip->FriendlyStage('TransactionBatch'), 'Batch', 'Correct stage wording for TransactionBatch'); RT->Config->Set('UseTransactionBatch', 0); is($scrip->FriendlyStage('TransactionBatch'), 'Batch (disabled by config)', 'Correct stage wording for TransactionBatch with UseTransactionBatch disabled'); } rt-5.0.1/t/api/i18n.t000644 000765 000024 00000001150 14005011336 014756 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test nodb => 1, tests => 9; { use_ok ('RT::I18N'); ok(RT::I18N->Init); } { ok(my $chinese = RT::I18N->get_handle('zh_tw')); ok(UNIVERSAL::can($chinese, 'maketext')); like($chinese->maketext('__Content-Type') , qr/utf-8/i, "Found the utf-8 charset for traditional chinese in the string ".$chinese->maketext('__Content-Type')); is($chinese->encoding , 'utf-8', "The encoding is 'utf-8' -".$chinese->encoding); ok(my $en = RT::I18N->get_handle('en')); ok(UNIVERSAL::can($en, 'maketext')); is($en->encoding , 'utf-8', "The encoding ".$en->encoding." is 'utf-8'"); } rt-5.0.1/t/api/attribute-tests.t000644 000765 000024 00000004627 14005011336 017356 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test nodata => 1, tests => 34; my $runid = rand(200); my $attribute = "squelch-$runid"; ok(require RT::Attributes); my $user = RT::User->new(RT->SystemUser); ok (UNIVERSAL::isa($user, 'RT::User')); my ($id,$msg) = $user->Create(Name => 'attrtest-'.$runid); ok ($id, $msg); ok($user->id, "Created a test user"); ok(1, $user->Attributes->BuildSelectQuery); my $attr = $user->Attributes; # XXX: Order by id as some tests depend on it $attr->OrderByCols({ FIELD => 'id' }); ok(1, $attr->BuildSelectQuery); ok (UNIVERSAL::isa($attr,'RT::Attributes'), 'got the attributes object'); ($id, $msg) = $user->AddAttribute(Name => 'TestAttr', Content => 'The attribute has content'); ok ($id, $msg); is ($attr->Count,1, " One attr after adding a first one"); my $first_attr = $user->FirstAttribute('TestAttr'); ok($first_attr, "got some sort of attribute"); isa_ok($first_attr, 'RT::Attribute'); is($first_attr->Content, 'The attribute has content', "got the right content back"); ($id, $msg) = $attr->DeleteEntry(Name => $runid); ok(!$id, "Deleted non-existant entry - $msg"); is ($attr->Count,1, "1 attr after deleting an empty attr"); my @names = $attr->Names; is ("@names", "TestAttr"); ($id, $msg) = $user->AddAttribute(Name => $runid, Content => "First"); ok($id, $msg); my $runid_attr = $user->FirstAttribute($runid); ok($runid_attr, "got some sort of attribute"); isa_ok($runid_attr, 'RT::Attribute'); is($runid_attr->Content, 'First', "got the right content back"); is ($attr->Count,2, " Two attrs after adding an attribute named $runid"); ($id, $msg) = $user->AddAttribute(Name => $runid, Content => "Second"); ok($id, $msg); $runid_attr = $user->FirstAttribute($runid); ok($runid_attr, "got some sort of attribute"); isa_ok($runid_attr, 'RT::Attribute'); is($runid_attr->Content, 'First', "got the first content back still"); is ($attr->Count,3, " Three attrs after adding a secondvalue to $runid"); ($id, $msg) = $attr->DeleteEntry(Name => $runid, Content => "First"); ok($id, $msg); is ($attr->Count,2); #$attr->_DoSearch(); ($id, $msg) = $attr->DeleteEntry(Name => $runid, Content => "Second"); ok($id, $msg); is ($attr->Count,1); #$attr->_DoSearch(); ok(1, $attr->BuildSelectQuery); ($id, $msg) = $attr->DeleteEntry(Name => "moose"); ok(!$id, "Deleted non-existant entry - $msg"); is ($attr->Count,1); ok(1, $attr->BuildSelectQuery); @names = $attr->Names; is("@names", "TestAttr"); rt-5.0.1/t/api/user-prefs.t000644 000765 000024 00000003777 14005011336 016313 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => undef; use_ok( 'RT::User' ); my $create_user = RT::User->new(RT->SystemUser); isa_ok($create_user, 'RT::User'); my ($ret, $msg) = $create_user->Create(Name => 'CreateTest1'.$$, EmailAddress => $$.'create-test-1@example.com'); ok ($ret, "Creating user CreateTest1 - " . $msg ); # Create object to operate as the test user my $user1 = RT::User->new($create_user); ($ret, $msg) = $user1->Load($create_user->Id); ok ($ret, "Loaded the new user $msg"); diag "Set a search preference"; my $prefs = { 'Order' => 'DESC|ASC|ASC|ASC', 'OrderBy' => 'Due', 'Format' => '\'__id__/TITLE:#\', \'__Subject__/TITLE:Subject\', \'__Priority__\', \'__QueueName__\', \'__ExtendedStatus__\', \'__Due__\'', 'RowsPerPage' => '50' }; ok (!$user1->HasRight( Right => 'ModifySelf', Object => $RT::System), "Can't ModifySelf"); ($ret, $msg) = $user1->SetPreferences("SearchDisplay", $prefs); ok( !$ret, "No permission to set preferences"); ok (($ret, $msg) = $create_user->PrincipalObj->GrantRight( Right => 'ModifySelf'), "Granted ModifySelf"); ($ret, $msg) = $user1->SetPreferences("SearchDisplay", $prefs); ok( $ret, "Search preference set"); diag "Fetch preference"; ok (my $saved_prefs = $user1->Preferences("SearchDisplay"), "Fetched prefs"); is ($prefs->{OrderBy}, 'Due', "Prefs look ok"); diag "Delete prefs"; ok (($ret, $msg) = $create_user->PrincipalObj->RevokeRight( Right => 'ModifySelf'), "Revoked ModifySelf"); ($ret, $msg) = $user1->DeletePreferences("SearchDisplay"); ok( !$ret, "No permission to delete preferences"); ok (($ret, $msg) = $create_user->PrincipalObj->GrantRight( Right => 'ModifySelf'), "Granted ModifySelf"); ($ret, $msg) = $user1->DeletePreferences("SearchDisplay"); ok( $ret, "Search preference deleted"); $saved_prefs = $user1->Preferences("SearchDisplay"); ok (!$saved_prefs, "No saved preferences returned"); done_testing; rt-5.0.1/t/api/group.t000644 000765 000024 00000010265 14005011336 015342 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test nodata => 1, tests => undef; { ok (require RT::Group); ok (my $group = RT::Group->new(RT->SystemUser), "instantiated a group object"); ok (my ($id, $msg) = $group->CreateUserDefinedGroup( Name => 'TestGroup', Description => 'A test group', ), 'Created a new group'); isnt ($id , 0, "Group id is $id"); is ($group->Name , 'TestGroup', "The group's name is 'TestGroup'"); my $ng = RT::Group->new(RT->SystemUser); ok($ng->LoadUserDefinedGroup('TestGroup'), "Loaded testgroup"); is($ng->id , $group->id, "Loaded the right group"); my @users = (undef); for my $number (1..3) { my $user = RT::User->new(RT->SystemUser); $user->Create( Name => "User $number" ); push @users, $user->id; } ok (($id,$msg) = $ng->AddMember( $users[1] ), "Added a member to the group"); ok($id, $msg); ok (($id,$msg) = $ng->AddMember( $users[2] ), "Added a member to the group"); ok($id, $msg); ok (($id,$msg) = $ng->AddMember( $users[3] ), "Added a member to the group"); ok($id, $msg); # Group 1 now has members 1, 2 ,3 my $group_2 = RT::Group->new(RT->SystemUser); ok (my ($id_2, $msg_2) = $group_2->CreateUserDefinedGroup( Name => 'TestGroup2', Description => 'A second test group'), , 'Created a new group'); isnt ($id_2 , 0, "Created group 2 ok- $msg_2 "); ok (($id,$msg) = $group_2->AddMember($ng->PrincipalId), "Made TestGroup a member of testgroup2"); ok($id, $msg); ok (($id,$msg) = $group_2->AddMember( $users[1] ), "Added member User 1 to the group TestGroup2"); ok($id, $msg); # Group 2 how has 1, g1->{1, 2,3} my $group_3 = RT::Group->new(RT->SystemUser); ok (my ($id_3, $msg_3) = $group_3->CreateUserDefinedGroup( Name => 'TestGroup3', Description => 'A second test group'), 'Created a new group'); isnt ($id_3 , 0, "Created group 3 ok - $msg_3"); ok (($id,$msg) =$group_3->AddMember($group_2->PrincipalId), "Made TestGroup a member of testgroup2"); ok($id, $msg); # g3 now has g2->{1, g1->{1,2,3}} my $principal_1 = RT::Principal->new(RT->SystemUser); $principal_1->Load( $users[1] ); my $principal_2 = RT::Principal->new(RT->SystemUser); $principal_2->Load( $users[2] ); ok (($id,$msg) = $group_3->AddMember( $users[1] ), "Added member User 1 to the group TestGroup2"); ok($id, $msg); # g3 now has 1, g2->{1, g1->{1,2,3}} is($group_3->HasMember($principal_2), undef, "group 3 doesn't have member 2"); ok($group_3->HasMemberRecursively($principal_2), "group 3 has member 2 recursively"); ok($ng->HasMember($principal_2) , "group ".$ng->Id." has member 2"); my ($delid , $delmsg) =$ng->DeleteMember($principal_2->Id); isnt ($delid ,0, "Sucessfully deleted it-".$delid."-".$delmsg); #Gotta reload the group objects, since we've been messing with various internals. # we shouldn't need to do this. #$ng->LoadUserDefinedGroup('TestGroup'); #$group_2->LoadUserDefinedGroup('TestGroup2'); #$group_3->LoadUserDefinedGroup('TestGroup'); # G1 now has 1, 3 # Group 2 how has 1, g1->{1, 3} # g3 now has 1, g2->{1, g1->{1, 3}} ok(!$ng->HasMember($principal_2) , "group ".$ng->Id." no longer has member 2"); is($group_3->HasMemberRecursively($principal_2), undef, "group 3 doesn't have member 2"); is($group_2->HasMemberRecursively($principal_2), undef, "group 2 doesn't have member 2"); is($ng->HasMember($principal_2), undef, "group 1 doesn't have member 2"); is($group_3->HasMemberRecursively($principal_2), undef, "group 3 has member 2 recursively"); } { ok(my $u = RT::Group->new(RT->SystemUser)); ok($u->Load(4), "Loaded the first user"); is($u->PrincipalObj->id , 4, "user 4 is the fourth principal"); is($u->PrincipalObj->PrincipalType , 'Group' , "Principal 4 is a group"); } { my $u = RT::Group->new(RT->SystemUser); $u->LoadUserDefinedGroup('TestGroup'); ok( $u->id, 'loaded TestGroup' ); ok( $u->SetName('testgroup'), 'rename to lower cased version: testgroup' ); ok( $u->SetName('TestGroup'), 'rename back' ); my $u2 = RT::Group->new( RT->SystemUser ); my ( $id, $msg ) = $u2->CreateUserDefinedGroup( Name => 'TestGroup' ); ok( !$id, "can't create duplicated group: $msg" ); ( $id, $msg ) = $u2->CreateUserDefinedGroup( Name => 'testgroup' ); ok( !$id, "can't create duplicated group even case is different: $msg" ); } done_testing; rt-5.0.1/t/api/has_rights.t000644 000765 000024 00000002253 14005011336 016337 0ustar00sunnavystaff000000 000000 use RT::Test nodata => 1, tests => 9; use strict; use warnings; my $queue = RT::Test->load_or_create_queue( Name => 'A' ); ok $queue && $queue->id, 'loaded or created queue_a'; my $qid = $queue->id; my $user = RT::Test->load_or_create_user( Name => 'user', Password => 'password', EmailAddress => 'test@example.com', ); ok $user && $user->id, 'loaded or created user'; { cleanup(); RT::Test->set_rights( { Principal => 'Everyone', Right => [qw(SeeQueue)] }, { Principal => 'Cc', Right => [qw(ShowTicket)] }, ); my ($t) = RT::Test->create_tickets( { Queue => $queue->id }, { }, ); my $rights = $user->PrincipalObj->HasRights( Object => $t ); is_deeply( $rights, { SeeQueue => 1 }, 'got it' ); ($t) = RT::Test->create_tickets( { Queue => $queue->id }, { Cc => $user->EmailAddress }, ); ok($t->Cc->HasMember( $user->id ), 'user is cc'); $rights = $user->PrincipalObj->HasRights( Object => $t ); is_deeply( $rights, { SeeQueue => 1, ShowTicket => 1 }, 'got it' ) } sub cleanup { RT::Test->delete_tickets( "Queue = $qid" ); RT::Test->delete_queue_watchers( $queue ); }; rt-5.0.1/t/api/menu.t000644 000765 000024 00000005702 14005011336 015152 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use RT::Interface::Web::Menu; sub child_path_is($$$) { my ($menu, $child, $expected) = @_; my $c = $menu->child($child->[0], path => $child->[1]); is $c->path, $expected, "'$child->[1]' normalizes to '$expected'"; return $c; } { package FakeRequest; sub new { bless {}, shift } sub path_info { "" } package FakeInterp; require CGI; sub new { bless {}, shift } sub cgi_object { CGI->new } } local $HTML::Mason::Commands::r = FakeRequest->new; local $HTML::Mason::Commands::m = FakeInterp->new; my $menu = RT::Interface::Web::Menu->new; ok $menu, "Created top level menu"; child_path_is $menu, [search => "Search/Simple.html"], "/Search/Simple.html"; child_path_is $menu, [absolute => "/Prefs/Other.html"], "/Prefs/Other.html"; child_path_is $menu, [scheme => "http://example.com"], "http://example.com"; my $tools = child_path_is $menu, [tools => "/Tools/"], "/Tools/"; child_path_is $tools, [myday => "MyDay.html"], "/Tools/MyDay.html"; child_path_is $tools, [activity => "/Activity.html"], "/Activity.html"; my $ext = child_path_is $tools, [external => "http://example.com"], "http://example.com"; child_path_is $ext, [wiki => "wiki/"], "http://example.com/wiki/"; # Pathological case of multiplying slashes my $home = child_path_is $menu, [home => "/"], "/"; child_path_is $home, [slash => "/"], "/"; child_path_is $home, [empty => ""], "/"; sub order_ok($$;$) { my ($menu, $expected, $name) = @_; my @children = $menu->children; is scalar @children, scalar @$expected, "correct number of children"; is_deeply [map { $_->key } @children], $expected, $name; my $last_child = shift @children; # first child's sort doesn't matter for (@children) { ok $_->sort_order > $last_child->sort_order, sprintf "%s order higher than %s's", $_->key, $last_child->key; $last_child = $_; } } $menu = RT::Interface::Web::Menu->new; ok $menu->child("foo", title => "foo"), "added child foo"; order_ok $menu, [qw(foo)], "sorted"; ok $menu->child("foo")->add_after("bar", title => "bar"), "added child bar after foo"; order_ok $menu, [qw(foo bar)], "sorted after"; ok $menu->child("bar")->add_before("baz", title => "baz"), "added child baz before bar"; order_ok $menu, [qw(foo baz bar)], "sorted before (in between)"; ok $menu->child("bat", title => "bat", sort_order => 2.2), "added child bat between baz and bar"; order_ok $menu, [qw(foo baz bat bar)], "sorted between manually"; ok $menu->child("bat")->add_before("pre", title => "pre"), "added child pre before bat"; order_ok $menu, [qw(foo baz pre bat bar)], "sorted between (before)"; ok $menu->child("bat")->add_after("post", title => "post"), "added child post after bat"; order_ok $menu, [qw(foo baz pre bat post bar)], "sorted between (after)"; done_testing; rt-5.0.1/t/api/searchbuilder.t000644 000765 000024 00000003436 14005011336 017024 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => 19; { ok (require RT::SearchBuilder); } { use_ok('RT::Queues'); ok(my $queues = RT::Queues->new(RT->SystemUser), 'Created a queues object'); ok( $queues->UnLimit(),'Unlimited the result set of the queues object'); my $items = $queues->ItemsArrayRef(); my @items = @{$items}; ok($queues->RecordClass->_Accessible('Name','read')); my @sorted = sort {lc($a->Name) cmp lc($b->Name)} @items; ok (@sorted, "We have an array of queues, sorted". join(',',map {$_->Name} @sorted)); ok (@items, "We have an array of queues, raw". join(',',map {$_->Name} @items)); my @sorted_ids = map {$_->id } @sorted; my @items_ids = map {$_->id } @items; is ($#sorted, $#items); is ($sorted[0]->Name, $items[0]->Name); is ($sorted[-1]->Name, $items[-1]->Name); is_deeply(\@items_ids, \@sorted_ids, "ItemsArrayRef sorts alphabetically by name"); } #20767: CleanSlate doesn't clear RT::SearchBuilder's flags for handling Disabled columns { my $items; ok(my $queues = RT::Queues->new(RT->SystemUser), 'Created a queues object'); ok( $queues->UnLimit(),'Unlimited the result set of the queues object'); # sanity check is( $queues->{'handled_disabled_column'} => undef, 'handled_disabled_column IS NOT set' ); is( $queues->{'find_disabled_rows'} => undef, 'find_disabled_rows IS NOT set ' ); $queues->LimitToDeleted; # sanity check ok( $queues->{'handled_disabled_column'}, 'handled_disabled_column IS set' ); ok( $queues->{'find_disabled_rows'}, 'find_disabled_rows IS set ' ); $queues->CleanSlate; # these fail without the overloaded CleanSlate method is( $queues->{'handled_disabled_column'} => undef, 'handled_disabled_column IS NOT set' ); is( $queues->{'find_disabled_rows'} => undef, 'find_disabled_rows IS NOT set ' ); } rt-5.0.1/t/api/initialdata-roundtrip.t000644 000765 000024 00000136736 14005011336 020531 0ustar00sunnavystaff000000 000000 use utf8; use strict; use warnings; use JSON; use RT::Test tests => undef, config => << 'CONFIG'; Set($InitialdataFormatHandlers, [ 'perl', 'RT::Initialdata::JSON' ]); CONFIG my $general = RT::Queue->new(RT->SystemUser); $general->Load('General'); my @tests = ( { name => 'Simple user-defined group', create => sub { my $group = RT::Group->new(RT->SystemUser); my ($ok, $msg) = $group->CreateUserDefinedGroup(Name => 'Staff'); ok($ok, $msg); }, absent => sub { my $group = RT::Group->new(RT->SystemUser); $group->LoadUserDefinedGroup('Staff'); ok(!$group->Id, 'No such group'); }, present => sub { my $group = RT::Group->new(RT->SystemUser); $group->LoadUserDefinedGroup('Staff'); ok($group->Id, 'Loaded group'); is($group->Name, 'Staff', 'Group name'); is($group->Domain, 'UserDefined', 'Domain'); }, }, { name => 'Group membership and ACLs', create => sub { my $outer = RT::Group->new(RT->SystemUser); my ($ok, $msg) = $outer->CreateUserDefinedGroup(Name => 'Outer'); ok($ok, $msg); my $inner = RT::Group->new(RT->SystemUser); ($ok, $msg) = $inner->CreateUserDefinedGroup(Name => 'Inner'); ok($ok, $msg); my $unrelated = RT::Group->new(RT->SystemUser); ($ok, $msg) = $unrelated->CreateUserDefinedGroup(Name => 'Unrelated'); ok($ok, $msg); my $user = RT::User->new(RT->SystemUser); ($ok, $msg) = $user->Create(Name => 'User'); ok($ok, $msg); my $unprivileged = RT::User->new(RT->SystemUser); ($ok, $msg) = $unprivileged->Create(Name => 'Unprivileged'); ok($ok, $msg); ($ok, $msg) = $outer->AddMember($inner->PrincipalId); ok($ok, $msg); ($ok, $msg) = $inner->AddMember($user->PrincipalId); ok($ok, $msg); ($ok, $msg) = $general->AddWatcher(Type => 'AdminCc', PrincipalId => $outer->PrincipalId); ok($ok, $msg); ($ok, $msg) = $general->AdminCc->PrincipalObj->GrantRight(Object => $general, Right => 'ShowTicket'); ok($ok, $msg); ($ok, $msg) = $inner->PrincipalObj->GrantRight(Object => $general, Right => 'ModifyTicket'); ok($ok, $msg); ($ok, $msg) = $user->PrincipalObj->GrantRight(Object => $general, Right => 'OwnTicket'); ok($ok, $msg); ($ok, $msg) = $unprivileged->PrincipalObj->GrantRight(Object => RT->System, Right => 'ModifyTicket'); ok($ok, $msg); ($ok, $msg) = $inner->PrincipalObj->GrantRight(Object => $inner, Right => 'SeeGroup'); ok($ok, $msg); }, present => sub { my $outer = RT::Group->new(RT->SystemUser); $outer->LoadUserDefinedGroup('Outer'); ok($outer->Id, 'Loaded group'); is($outer->Name, 'Outer', 'Group name'); my $inner = RT::Group->new(RT->SystemUser); $inner->LoadUserDefinedGroup('Inner'); ok($inner->Id, 'Loaded group'); is($inner->Name, 'Inner', 'Group name'); my $unrelated = RT::Group->new(RT->SystemUser); $unrelated->LoadUserDefinedGroup('Unrelated'); ok($unrelated->Id, 'Loaded group'); is($unrelated->Name, 'Unrelated', 'Group name'); my $user = RT::User->new(RT->SystemUser); $user->Load('User'); ok($user->Id, 'Loaded user'); is($user->Name, 'User', 'User name'); my $unprivileged = RT::User->new(RT->SystemUser); $unprivileged->Load('Unprivileged'); ok($unprivileged->Id, 'Loaded Unprivileged'); is($unprivileged->Name, 'Unprivileged', 'Unprivileged name'); ok($outer->HasMember($inner->PrincipalId), 'outer hasmember inner'); ok($inner->HasMember($user->PrincipalId), 'inner hasmember user'); ok($outer->HasMemberRecursively($user->PrincipalId), 'outer hasmember user recursively'); ok(!$outer->HasMember($user->PrincipalId), 'outer does not have member user directly'); ok(!$inner->HasMember($outer->PrincipalId), 'inner does not have member outer'); ok($general->AdminCc->HasMember($outer->PrincipalId), 'queue AdminCc'); ok($general->AdminCc->HasMemberRecursively($inner->PrincipalId), 'queue AdminCc'); ok($general->AdminCc->HasMemberRecursively($user->PrincipalId), 'queue AdminCc'); ok(!$outer->HasMemberRecursively($unrelated->PrincipalId), 'unrelated group membership'); ok(!$inner->HasMemberRecursively($unrelated->PrincipalId), 'unrelated group membership'); ok(!$general->AdminCc->HasMemberRecursively($unrelated->PrincipalId), 'unrelated group membership'); ok($general->AdminCc->PrincipalObj->HasRight(Object => $general, Right => 'ShowTicket'), 'AdminCc ShowTicket right'); ok($outer->PrincipalObj->HasRight(Object => $general, Right => 'ShowTicket'), 'outer ShowTicket right'); ok($inner->PrincipalObj->HasRight(Object => $general, Right => 'ShowTicket'), 'inner ShowTicket right'); ok($user->PrincipalObj->HasRight(Object => $general, Right => 'ShowTicket'), 'user ShowTicket right'); ok(!$unrelated->PrincipalObj->HasRight(Object => $general, Right => 'ShowTicket'), 'unrelated ShowTicket right'); ok(!$general->AdminCc->PrincipalObj->HasRight(Object => $general, Right => 'ModifyTicket'), 'AdminCc ModifyTicket right'); ok(!$outer->PrincipalObj->HasRight(Object => $general, Right => 'ModifyTicket'), 'outer ModifyTicket right'); ok($inner->PrincipalObj->HasRight(Object => $general, Right => 'ModifyTicket'), 'inner ModifyTicket right'); ok($user->PrincipalObj->HasRight(Object => $general, Right => 'ModifyTicket'), 'user ModifyTicket right'); ok(!$unrelated->PrincipalObj->HasRight(Object => $general, Right => 'ModifyTicket'), 'unrelated ModifyTicket right'); ok(!$general->AdminCc->PrincipalObj->HasRight(Object => $general, Right => 'OwnTicket'), 'AdminCc OwnTicket right'); ok(!$outer->PrincipalObj->HasRight(Object => $general, Right => 'OwnTicket'), 'outer OwnTicket right'); ok(!$inner->PrincipalObj->HasRight(Object => $general, Right => 'OwnTicket'), 'inner OwnTicket right'); ok($user->PrincipalObj->HasRight(Object => $general, Right => 'OwnTicket'), 'inner OwnTicket right'); ok(!$unrelated->PrincipalObj->HasRight(Object => $general, Right => 'OwnTicket'), 'unrelated OwnTicket right'); ok($unprivileged->PrincipalObj->HasRight(Object => RT->System, Right => 'ModifyTicket'), 'unprivileged ModifyTicket right'); ok(!$general->AdminCc->PrincipalObj->HasRight(Object => $inner, Right => 'SeeGroup'), 'AdminCc SeeGroup right'); ok(!$outer->PrincipalObj->HasRight(Object => $inner, Right => 'SeeGroup'), 'outer SeeGroup right'); ok($inner->PrincipalObj->HasRight(Object => $inner, Right => 'SeeGroup'), 'inner SeeGroup right'); ok($user->PrincipalObj->HasRight(Object => $inner, Right => 'SeeGroup'), 'user SeeGroup right'); ok(!$unrelated->PrincipalObj->HasRight(Object => $inner, Right => 'SeeGroup'), 'unrelated SeeGroup right'); }, }, { name => 'Custom field on two queues', create => sub { my $bugs = RT::Queue->new(RT->SystemUser); my ($ok, $msg) = $bugs->Create(Name => 'Bugs'); ok($ok, $msg); my $features = RT::Queue->new(RT->SystemUser); ($ok, $msg) = $features->Create(Name => 'Features'); ok($ok, $msg); my $cf = RT::CustomField->new(RT->SystemUser); ($ok, $msg) = $cf->Create( Name => 'Fixed In', Type => 'SelectSingle', LookupType => RT::Ticket->CustomFieldLookupType, ); ok($ok, $msg); ($ok, $msg) = $cf->AddToObject($bugs); ok($ok, $msg); ($ok, $msg) = $cf->AddToObject($features); ok($ok, $msg); ($ok, $msg) = $cf->AddValue(Name => '0.1', Description => 'Prototype', SortOrder => '1'); ok($ok, $msg); ($ok, $msg) = $cf->AddValue(Name => '1.0', Description => 'Gold', SortOrder => '10'); ok($ok, $msg); # these next two are intentionally added in an order different from their SortOrder ($ok, $msg) = $cf->AddValue(Name => '2.0', Description => 'Remaster', SortOrder => '20'); ok($ok, $msg); ($ok, $msg) = $cf->AddValue(Name => '1.1', Description => 'Gold Bugfix', SortOrder => '11'); ok($ok, $msg); }, present => sub { my $bugs = RT::Queue->new(RT->SystemUser); $bugs->Load('Bugs'); ok($bugs->Id, 'Bugs queue loaded'); is($bugs->Name, 'Bugs'); my $features = RT::Queue->new(RT->SystemUser); $features->Load('Features'); ok($features->Id, 'Features queue loaded'); is($features->Name, 'Features'); my $cf = RT::CustomField->new(RT->SystemUser); $cf->Load('Fixed In'); ok($cf->Id, 'Fixed In CF loaded'); is($cf->Name, 'Fixed In'); is($cf->Type, 'Select', 'Type'); is($cf->MaxValues, 1, 'MaxValues'); is($cf->LookupType, RT::Ticket->CustomFieldLookupType, 'LookupType'); ok($cf->IsAdded($bugs->Id), 'CF is on Bugs queue'); ok($cf->IsAdded($features->Id), 'CF is on Features queue'); ok(!$cf->IsAdded(0), 'CF is not global'); ok(!$cf->IsAdded($general->Id), 'CF is not on General queue'); my @values = map { { Name => $_->Name, Description => $_->Description, SortOrder => $_->SortOrder, } } @{ $cf->Values->ItemsArrayRef }; is_deeply(\@values, [ { Name => '0.1', Description => 'Prototype', SortOrder => '1' }, { Name => '1.0', Description => 'Gold', SortOrder => '10' }, { Name => '1.1', Description => 'Gold Bugfix', SortOrder => '11' }, { Name => '2.0', Description => 'Remaster', SortOrder => '20' }, ], 'CF values'); }, }, { name => 'Custom field lookup types', create => sub { my %extra = ( Group => { method => 'CreateUserDefinedGroup' }, Asset => undef, Article => { Class => 'General' }, Ticket => undef, Transaction => undef, User => undef, ); for my $type (qw/Asset Article Group Queue Ticket Transaction User/) { my $class = "RT::$type"; my $cf = RT::CustomField->new(RT->SystemUser); my ($ok, $msg) = $cf->Create( Name => "$type CF", Type => "FreeformSingle", LookupType => $class->CustomFieldLookupType, ); ok($ok, $msg); # apply globally ($ok, $msg) = $cf->AddToObject($cf->RecordClassFromLookupType->new(RT->SystemUser)); ok($ok, $msg); next if exists($extra{$type}) && !defined($extra{$type}); my $obj = $class->new(RT->SystemUser); my $method = delete($extra{$type}{method}) || 'Create'; ($ok, $msg) = $obj->$method( Name => $type, %{ $extra{$type} || {} }, ); ok($ok, "created $type: $msg"); ok($obj->Id, "loaded $type"); ($ok, $msg) = $obj->AddCustomFieldValue( Field => $cf->Id, Value => "$type Value", ); ok($ok, $msg); } }, present => sub { my %load = ( Transaction => undef, Ticket => undef, User => undef, Asset => undef, ); for my $type (qw/Asset Article Group Queue Ticket Transaction User/) { my $class = "RT::$type"; my $cf = RT::CustomField->new(RT->SystemUser); $cf->Load("$type CF"); ok($cf->Id, "loaded $type CF"); is($cf->Name, "$type CF", 'Name'); is($cf->Type, 'Freeform', 'Type'); is($cf->MaxValues, 1, 'MaxValues'); is($cf->LookupType, $class->CustomFieldLookupType, 'LookupType'); next if exists($load{$type}) && !defined($load{$type}); my $obj = $class->new(RT->SystemUser); $obj->LoadByCols( %{ $load{$type} || { Name => $type } }, ); ok($obj->Id, "loaded $type"); is($obj->FirstCustomFieldValue($cf->Id), "$type Value", "CF value for $type"); } }, }, { name => 'Custom field LargeContent', create => sub { my $cf = RT::CustomField->new(RT->SystemUser); my ($ok, $msg) = $cf->Create( Name => "Group CF", Type => "FreeformSingle", LookupType => RT::Group->CustomFieldLookupType, ); ok($ok, $msg); ($ok, $msg) = $cf->AddToObject(RT::Group->new(RT->SystemUser)); ok($ok, $msg); my $group = RT::Group->new(RT->SystemUser); ($ok, $msg) = $group->CreateUserDefinedGroup(Name => 'Group'); ok($ok, $msg); ($ok, $msg) = $group->AddCustomFieldValue( Field => $cf->Id, Value => scalar("abc" x 256), ); ok($ok, $msg); }, present => sub { my $group = RT::Group->new(RT->SystemUser); $group->LoadUserDefinedGroup('Group'); ok($group->Id, 'loaded Group'); is($group->FirstCustomFieldValue('Group CF'), scalar("abc" x 256), "CF LargeContent"); }, # the following test peers into the initialdata only to make sure that # we are roundtripping LargeContent as expected; if this starts # failing it's not necessarily a problem, but it's worthy of # investigating whether the "present" tests are still testing # what they were meant to test raw => sub { my $json = shift; my ($group) = grep { $_->{Name} eq 'Group' } @{ $json->{Groups} }; ok($group, 'found the group'); my ($ocfv) = @{ $group->{CustomFields} }; ok($ocfv, 'found the OCFV'); is($ocfv->{CustomField}, 'Group CF', 'CustomField'); is($ocfv->{Content}, undef, 'no Content'); is($ocfv->{LargeContent}, scalar("abc" x 256), 'LargeContent'); is($ocfv->{ContentType}, "text/plain", 'ContentType'); } }, { name => 'Scrips including Disabled', export_args => { FollowDisabled => 1 }, create => sub { my $bugs = RT::Queue->new(RT->SystemUser); my ($ok, $msg) = $bugs->Create(Name => 'Bugs'); ok($ok, $msg); my $features = RT::Queue->new(RT->SystemUser); ($ok, $msg) = $features->Create(Name => 'Features'); ok($ok, $msg); my $disabled = RT::Scrip->new(RT->SystemUser); ($ok, $msg) = $disabled->Create( Queue => 0, Description => 'Disabled Scrip', Template => 'Blank', ScripCondition => 'User Defined', ScripAction => 'User Defined', CustomIsApplicableCode => 'return "condition"', CustomPrepareCode => 'return "prepare"', CustomCommitCode => 'return "commit"', ); ok($ok, $msg); ($ok, $msg) = $disabled->SetDisabled(1); ok($ok, $msg); my $stages = RT::Scrip->new(RT->SystemUser); ($ok, $msg) = $stages->Create( Description => 'Staged Scrip', Template => 'Transaction', ScripCondition => 'On Create', ScripAction => 'Notify Owner', ); ok($ok, $msg); ($ok, $msg) = $stages->RemoveFromObject(0); ok($ok, $msg); ($ok, $msg) = $stages->AddToObject( ObjectId => $bugs->Id, Stage => 'TransactionBatch', SortOrder => 42, ); ok($ok, $msg); ($ok, $msg) = $stages->AddToObject( ObjectId => $features->Id, Stage => 'TransactionCreate', SortOrder => 99, ); ok($ok, $msg); }, present => sub { my $bugs = RT::Queue->new(RT->SystemUser); $bugs->Load('Bugs'); ok($bugs->Id, 'Bugs queue loaded'); is($bugs->Name, 'Bugs'); my $features = RT::Queue->new(RT->SystemUser); $features->Load('Features'); ok($features->Id, 'Features queue loaded'); is($features->Name, 'Features'); my $disabled = RT::Scrip->new(RT->SystemUser); $disabled->LoadByCols(Description => 'Disabled Scrip'); ok($disabled->Id, 'Disabled scrip loaded'); is($disabled->Description, 'Disabled Scrip', 'Description'); is($disabled->Template, 'Blank', 'Template'); is($disabled->ConditionObj->Name, 'User Defined', 'Condition'); is($disabled->ActionObj->Name, 'User Defined', 'Action'); is($disabled->CustomIsApplicableCode, 'return "condition"', 'Condition code'); is($disabled->CustomPrepareCode, 'return "prepare"', 'Prepare code'); is($disabled->CustomCommitCode, 'return "commit"', 'Commit code'); ok($disabled->Disabled, 'Disabled'); ok($disabled->IsGlobal, 'IsGlobal'); my $stages = RT::Scrip->new(RT->SystemUser); $stages->LoadByCols(Description => 'Staged Scrip'); ok($stages->Id, 'Staged scrip loaded'); is($stages->Description, 'Staged Scrip'); ok(!$stages->Disabled, 'not Disabled'); ok(!$stages->IsGlobal, 'not Global'); my $bug_objectscrip = $stages->IsAdded($bugs->Id); ok($bug_objectscrip, 'added to Bugs'); is($bug_objectscrip->Stage, 'TransactionBatch', 'Stage'); is($bug_objectscrip->SortOrder, 42, 'SortOrder'); my $features_objectscrip = $stages->IsAdded($features->Id); ok($features_objectscrip, 'added to Features'); is($features_objectscrip->Stage, 'TransactionCreate', 'Stage'); is($features_objectscrip->SortOrder, 99, 'SortOrder'); ok(!$stages->IsAdded($general->Id), 'not added to General'); }, }, { name => 'No disabled scrips', create => sub { my $disabled = RT::Scrip->new(RT->SystemUser); my ($ok, $msg) = $disabled->Create( Description => 'Disabled Scrip', Template => 'Transaction', ScripCondition => 'On Create', ScripAction => 'Notify Owner', ); ok($ok, $msg); ($ok, $msg) = $disabled->SetDisabled(1); ok($ok, $msg); my $enabled = RT::Scrip->new(RT->SystemUser); ($ok, $msg) = $enabled->Create( Description => 'Enabled Scrip', Template => 'Transaction', ScripCondition => 'On Create', ScripAction => 'Notify Owner', ); ok($ok, $msg); }, present => sub { my $from_initialdata = shift; my $disabled = RT::Scrip->new(RT->SystemUser); $disabled->LoadByCols(Description => 'Disabled Scrip'); if ($from_initialdata) { ok(!$disabled->Id, 'Disabled scrip absent in initialdata'); } else { ok($disabled->Id, 'Disabled scrip present because of the original creation'); ok($disabled->Disabled, 'Disabled scrip disabled'); } my $enabled = RT::Scrip->new(RT->SystemUser); $enabled->LoadByCols(Description => 'Enabled Scrip'); ok($enabled->Id, 'Enabled scrip present'); }, }, { name => 'Disabled many-to-many relationships', create => sub { my $enabled_queue = RT::Queue->new(RT->SystemUser); my ($ok, $msg) = $enabled_queue->Create( Name => 'Enabled Queue', ); ok($ok, $msg); my $disabled_queue = RT::Queue->new(RT->SystemUser); ($ok, $msg) = $disabled_queue->Create( Name => 'Disabled Queue', ); ok($ok, $msg); my $enabled_cf = RT::CustomField->new(RT->SystemUser); ($ok, $msg) = $enabled_cf->Create( Name => 'Enabled CF', Type => 'FreeformSingle', LookupType => RT::Queue->CustomFieldLookupType, ); ok($ok, $msg); my $disabled_cf = RT::CustomField->new(RT->SystemUser); ($ok, $msg) = $disabled_cf->Create( Name => 'Disabled CF', Type => 'FreeformSingle', LookupType => RT::Queue->CustomFieldLookupType, ); ok($ok, $msg); my $enabled_scrip = RT::Scrip->new(RT->SystemUser); ($ok, $msg) = $enabled_scrip->Create( Queue => 0, Description => 'Enabled Scrip', Template => 'Blank', ScripCondition => 'On Create', ScripAction => 'Notify Owner', ); ok($ok, $msg); $enabled_scrip->RemoveFromObject(0); my $disabled_scrip = RT::Scrip->new(RT->SystemUser); ($ok, $msg) = $disabled_scrip->Create( Queue => 0, Description => 'Disabled Scrip', Template => 'Blank', ScripCondition => 'On Create', ScripAction => 'Notify Owner', ); ok($ok, $msg); $disabled_scrip->RemoveFromObject(0); my $enabled_class = RT::Class->new(RT->SystemUser); ($ok, $msg) = $enabled_class->Create( Name => 'Enabled Class', ); ok($ok, $msg); my $disabled_class = RT::Class->new(RT->SystemUser); ($ok, $msg) = $disabled_class->Create( Name => 'Disabled Class', ); ok($ok, $msg); my $enabled_role = RT::CustomRole->new(RT->SystemUser); ($ok, $msg) = $enabled_role->Create( Name => 'Enabled Role', ); ok($ok, $msg); my $disabled_role = RT::CustomRole->new(RT->SystemUser); ($ok, $msg) = $disabled_role->Create( Name => 'Disabled Role', ); ok($ok, $msg); my $enabled_group = RT::Group->new(RT->SystemUser); ($ok, $msg) = $enabled_group->CreateUserDefinedGroup( Name => 'Enabled Group', ); ok($ok, $msg); my $disabled_group = RT::Group->new(RT->SystemUser); ($ok, $msg) = $disabled_group->CreateUserDefinedGroup( Name => 'Disabled Group', ); ok($ok, $msg); my $enabled_user = RT::User->new(RT->SystemUser); ($ok, $msg) = $enabled_user->Create( Name => 'Enabled User', ); ok($ok, $msg); my $disabled_user = RT::User->new(RT->SystemUser); ($ok, $msg) = $disabled_user->Create( Name => 'Disabled User', ); ok($ok, $msg); for my $object ($enabled_cf, $disabled_cf, $enabled_scrip, $disabled_scrip, $enabled_class, $disabled_class, $enabled_role, $disabled_role) { # slightly inconsistent API my ($queue_a, $queue_b) = ($disabled_queue, $enabled_queue); ($queue_a, $queue_b) = ($queue_a->Id, $queue_b->Id) if $object->isa('RT::Scrip') || $object->isa('RT::CustomRole'); ($ok, $msg) = $object->AddToObject($queue_a); ok($ok, $msg); ($ok, $msg) = $object->AddToObject($queue_b); ok($ok, $msg); } for my $principal ($enabled_group, $disabled_group, $enabled_user, $disabled_user) { ($ok, $msg) = $principal->PrincipalObj->GrantRight(Object => RT->System, Right => 'SeeQueue'); ok($ok, $msg); for my $queue ($enabled_queue, $disabled_queue) { ($ok, $msg) = $principal->PrincipalObj->GrantRight(Object => $queue, Right => 'ShowTicket'); ok($ok, $msg); ($ok, $msg) = $queue->AddWatcher(Type => 'AdminCc', PrincipalId => $principal->PrincipalId); ok($ok, $msg); } } for my $cf ($enabled_cf, $disabled_cf) { for my $queue ($enabled_queue, $disabled_queue) { ($ok, $msg) = $queue->AddCustomFieldValue(Field => $cf->Id, Value => $cf->Name); ok($ok, $msg); } } for my $object ($disabled_queue, $disabled_cf, $disabled_scrip, $disabled_class, $disabled_role, $disabled_group, $disabled_user) { ($ok, $msg) = $object->SetDisabled(1); ok($ok, $msg); } }, present => sub { my $from_initialdata = shift; my $enabled_queue = RT::Queue->new(RT->SystemUser); $enabled_queue->Load('Enabled Queue'); ok($enabled_queue->Id, 'loaded Enabled queue'); is($enabled_queue->Name, 'Enabled Queue', 'Enabled Queue Name'); my $disabled_queue = RT::Queue->new(RT->SystemUser); $disabled_queue->Load('Disabled Queue'); my $enabled_cf = RT::CustomField->new(RT->SystemUser); $enabled_cf->Load('Enabled CF'); ok($enabled_cf->Id, 'loaded Enabled CF'); is($enabled_cf->Name, 'Enabled CF', 'Enabled CF Name'); ok($enabled_cf->IsAdded($enabled_queue->Id), 'Enabled CF added to General'); is($enabled_queue->FirstCustomFieldValue('Enabled CF'), 'Enabled CF', 'OCFV'); my $disabled_cf = RT::CustomField->new(RT->SystemUser); $disabled_cf->Load('Disabled CF'); my $enabled_scrip = RT::Scrip->new(RT->SystemUser); $enabled_scrip->LoadByCols(Description => 'Enabled Scrip'); ok($enabled_scrip->Id, 'loaded Enabled Scrip'); is($enabled_scrip->Description, 'Enabled Scrip', 'Enabled Scrip Name'); ok($enabled_scrip->IsAdded($enabled_queue->Id), 'Enabled Scrip added to General'); my $disabled_scrip = RT::Scrip->new(RT->SystemUser); $disabled_scrip->LoadByCols(Description => 'Disabled Scrip'); my $enabled_class = RT::Class->new(RT->SystemUser); $enabled_class->Load('Enabled Class'); ok($enabled_class->Id, 'loaded Enabled Class'); is($enabled_class->Name, 'Enabled Class', 'Enabled Class Name'); ok($enabled_class->IsApplied($enabled_queue->Id), 'Enabled Class added to General'); my $disabled_class = RT::Class->new(RT->SystemUser); $disabled_class->Load('Disabled Class'); my $enabled_role = RT::CustomRole->new(RT->SystemUser); $enabled_role->Load('Enabled Role'); ok($enabled_role->Id, 'loaded Enabled Role'); is($enabled_role->Name, 'Enabled Role', 'Enabled Role Name'); ok($enabled_role->IsAdded($enabled_queue->Id), 'Enabled Role added to General'); my $disabled_role = RT::CustomRole->new(RT->SystemUser); $disabled_role->Load('Disabled Role'); my $enabled_group = RT::Group->new(RT->SystemUser); $enabled_group->LoadUserDefinedGroup('Enabled Group'); ok($enabled_group->Id, 'loaded Enabled Group'); is($enabled_group->Name, 'Enabled Group', 'Enabled Group Name'); ok($enabled_group->PrincipalObj->HasRight(Object => $enabled_queue, Right => 'ShowTicket'), 'Enabled Group has queue right'); ok($enabled_group->PrincipalObj->HasRight(Object => RT->System, Right => 'SeeQueue'), 'Enabled Group has global right'); ok($enabled_queue->AdminCc->HasMember($enabled_group->PrincipalObj), 'Enabled Group still queue watcher'); my $disabled_group = RT::Group->new(RT->SystemUser); $disabled_group->LoadUserDefinedGroup('Disabled Group'); my $enabled_user = RT::User->new(RT->SystemUser); $enabled_user->Load('Enabled User'); ok($enabled_user->Id, 'loaded Enabled User'); is($enabled_user->Name, 'Enabled User', 'Enabled User Name'); ok($enabled_user->PrincipalObj->HasRight(Object => $enabled_queue, Right => 'ShowTicket'), 'Enabled User has queue right'); ok($enabled_user->PrincipalObj->HasRight(Object => RT->System, Right => 'SeeQueue'), 'Enabled User has global right'); ok($enabled_queue->AdminCc->HasMember($enabled_user->PrincipalObj), 'Enabled User still queue watcher'); my $disabled_user = RT::User->new(RT->SystemUser); $disabled_user->Load('Disabled User'); for my $object ($disabled_queue, $disabled_cf, $disabled_scrip, $disabled_class, $disabled_role, $disabled_group, $disabled_user) { if ($from_initialdata) { ok(!$object->Id, "disabled " . ref($object) . " excluded"); } else { ok($object->Disabled, "disabled " . ref($object)); } } }, }, { name => 'Unapplied Objects', create => sub { my $scrip = RT::Scrip->new(RT->SystemUser); my ($ok, $msg) = $scrip->Create( Queue => 0, Description => 'Unapplied Scrip', Template => 'Blank', ScripCondition => 'On Create', ScripAction => 'Notify Owner', ); ok($ok, $msg); ($ok, $msg) = $scrip->RemoveFromObject(0); ok($ok, $msg); my $cf = RT::CustomField->new(RT->SystemUser); ($ok, $msg) = $cf->Create( Name => 'Unapplied CF', Type => 'FreeformSingle', LookupType => RT::Ticket->CustomFieldLookupType, ); ok($ok, $msg); my $class = RT::Class->new(RT->SystemUser); ($ok, $msg) = $class->Create( Name => 'Unapplied Class', ); ok($ok, $msg); my $role = RT::CustomRole->new(RT->SystemUser); ($ok, $msg) = $role->Create( Name => 'Unapplied Custom Role', ); ok($ok, $msg); }, present => sub { my $scrip = RT::Scrip->new(RT->SystemUser); $scrip->LoadByCols(Description => 'Unapplied Scrip'); ok($scrip->Id, 'Unapplied scrip loaded'); is($scrip->Description, 'Unapplied Scrip'); ok(!$scrip->Disabled, 'not Disabled'); ok(!$scrip->IsGlobal, 'not Global'); ok(!$scrip->IsAdded($general->Id), 'not applied to General queue'); my $cf = RT::CustomField->new(RT->SystemUser); $cf->Load('Unapplied CF'); ok($cf->Id, 'Unapplied CF loaded'); is($cf->Name, 'Unapplied CF'); ok(!$cf->Disabled, 'not Disabled'); ok(!$cf->IsGlobal, 'not Global'); ok(!$cf->IsAdded($general->Id), 'not applied to General queue'); my $class = RT::Class->new(RT->SystemUser); $class->Load('Unapplied Class'); ok($class->Id, 'Unapplied Class loaded'); is($class->Name, 'Unapplied Class'); ok(!$class->Disabled, 'not Disabled'); ok(!$class->IsApplied(0), 'not Global'); ok(!$class->IsApplied($general->Id), 'not applied to General queue'); my $role = RT::CustomRole->new(RT->SystemUser); $role->Load('Unapplied Custom Role'); ok($role->Id, 'Unapplied Custom Role loaded'); is($role->Name, 'Unapplied Custom Role'); ok(!$role->Disabled, 'not Disabled'); ok(!$role->IsAdded(0), 'not Global'); ok(!$role->IsAdded($general->Id), 'not applied to General queue'); }, }, { name => 'Global Objects', create => sub { my $scrip = RT::Scrip->new(RT->SystemUser); my ($ok, $msg) = $scrip->Create( Queue => 0, Description => 'Global Scrip', Template => 'Blank', ScripCondition => 'On Create', ScripAction => 'Notify Owner', ); ok($ok, $msg); my $cf = RT::CustomField->new(RT->SystemUser); ($ok, $msg) = $cf->Create( Name => 'Global CF', Type => 'FreeformSingle', LookupType => RT::Ticket->CustomFieldLookupType, ); ok($ok, $msg); ($ok, $msg) = $cf->AddToObject(RT::Queue->new(RT->SystemUser)); ok($ok, $msg); my $class = RT::Class->new(RT->SystemUser); ($ok, $msg) = $class->Create( Name => 'Global Class', ); ok($ok, $msg); ($ok, $msg) = $class->AddToObject(RT::Queue->new(RT->SystemUser)); ok($ok, $msg); }, present => sub { my $scrip = RT::Scrip->new(RT->SystemUser); $scrip->LoadByCols(Description => 'Global Scrip'); ok($scrip->Id, 'Global scrip loaded'); is($scrip->Description, 'Global Scrip'); ok(!$scrip->Disabled, 'not Disabled'); ok($scrip->IsGlobal, 'Global'); ok(!$scrip->IsAdded($general->Id), 'not applied to General queue'); my $cf = RT::CustomField->new(RT->SystemUser); $cf->Load('Global CF'); ok($cf->Id, 'Global CF loaded'); is($cf->Name, 'Global CF'); ok(!$cf->Disabled, 'not Disabled'); ok($cf->IsGlobal, 'Global'); ok(!$cf->IsAdded($general->Id), 'not applied to General queue'); my $class = RT::Class->new(RT->SystemUser); $class->Load('Global Class'); ok($class->Id, 'Global Class loaded'); is($class->Name, 'Global Class'); ok(!$class->Disabled, 'not Disabled'); ok($class->IsApplied(0), 'Global'); ok(!$class->IsApplied($general->Id), 'not applied to General queue'); }, }, { name => 'Templates', create => sub { my $global = RT::Template->new(RT->SystemUser); my ($ok, $msg) = $global->Create( Name => 'Initialdata test', Queue => 0, Description => 'foo', Content => "Hello ã“ã‚“ã«ã¡ã¯", Type => "Simple", ); ok($ok, $msg); my $queue = RT::Template->new(RT->SystemUser); ($ok, $msg) = $queue->Create( Name => 'Initialdata test', Queue => $general->Id, Description => 'override for Swedes', Content => "Hello HallÃ¥", Type => "Simple", ); ok($ok, $msg); my $standalone = RT::Template->new(RT->SystemUser); ($ok, $msg) = $standalone->Create( Name => 'Standalone test', Queue => $general->Id, Description => 'no global version', Content => "this was broken!", Type => "Perl", ); ok($ok, $msg); }, present => sub { my $global = RT::Template->new(RT->SystemUser); $global->LoadGlobalTemplate('Initialdata test'); ok($global->Id, 'loaded template'); is($global->Name, 'Initialdata test', 'Name'); is($global->Queue, 0, 'Queue'); is($global->Description, 'foo', 'Description'); is($global->Content, 'Hello ã“ã‚“ã«ã¡ã¯', 'Content'); is($global->Type, 'Simple', 'Type'); my $queue = RT::Template->new(RT->SystemUser); $queue->LoadQueueTemplate(Name => 'Initialdata test', Queue => $general->Id); ok($queue->Id, 'loaded template'); is($queue->Name, 'Initialdata test', 'Name'); is($queue->Queue, $general->Id, 'Queue'); is($queue->Description, 'override for Swedes', 'Description'); is($queue->Content, 'Hello HallÃ¥', 'Content'); is($queue->Type, 'Simple', 'Type'); my $standalone = RT::Template->new(RT->SystemUser); $standalone->LoadQueueTemplate(Name => 'Standalone test', Queue => $general->Id); ok($standalone->Id, 'loaded template'); is($standalone->Name, 'Standalone test', 'Name'); is($standalone->Queue, $general->Id, 'Queue'); is($standalone->Description, 'no global version', 'Description'); is($standalone->Content, 'this was broken!', 'Content'); is($standalone->Type, 'Perl', 'Type'); }, }, { name => 'Articles', create => sub { my $class = RT::Class->new(RT->SystemUser); my ($ok, $msg) = $class->Create( Name => 'Test', ); ok($ok, $msg); my $content = RT::CustomField->new(RT->SystemUser); $content->LoadByCols( Name => "Content", Type => "Text", LookupType => RT::Article->CustomFieldLookupType, ); ok($content->Id, "loaded builtin Content CF"); my $tags = RT::CustomField->new(RT->SystemUser); ($ok, $msg) = $tags->Create( Name => "Tags", Type => "FreeformMultiple", LookupType => RT::Article->CustomFieldLookupType, ); ok($ok, $msg); ($ok, $msg) = $tags->AddToObject($class); ok($ok, $msg); my $clearance = RT::CustomField->new(RT->SystemUser); ($ok, $msg) = $clearance->Create( Name => "Clearance", Type => "SelectSingle", LookupType => RT::Article->CustomFieldLookupType, ); ok($ok, $msg); ($ok, $msg) = $clearance->AddToObject($class); ok($ok, $msg); ($ok, $msg) = $clearance->AddValue(Name => 'Unclassified'); ok($ok, $msg); ($ok, $msg) = $clearance->AddValue(Name => 'Classified'); ok($ok, $msg); ($ok, $msg) = $clearance->AddValue(Name => 'Top Secret'); ok($ok, $msg); my $coffee = RT::Article->new(RT->SystemUser); ($ok, $msg) = $coffee->Create( Class => 'Test', Name => 'Coffee time', "CustomField-" . $content->Id => 'Always', "CustomField-" . $clearance->Id => 'Unclassified', "CustomField-" . $tags->Id => ['drink', 'coffee', 'how the humans live'], ); ok($ok, $msg); my $twd = RT::Article->new(RT->SystemUser); ($ok, $msg) = $twd->Create( Class => 'Test', Name => 'Total world domination plans', "CustomField-" . $content->Id => 'REDACTED', "CustomField-" . $clearance->Id => 'Top Secret', "CustomField-" . $tags->Id => ['snakes', 'clowns'], ); ok($ok, $msg); }, present => sub { my $class = RT::Class->new(RT->SystemUser); $class->Load('Test'); ok($class->Id, 'loaded class'); is($class->Name, 'Test', 'Name'); my $coffee = RT::Article->new(RT->SystemUser); $coffee->LoadByCols(Name => 'Coffee time'); ok($coffee->Id, 'loaded article'); is($coffee->Name, 'Coffee time', 'Name'); is($coffee->Class, $class->Id, 'Class'); is($coffee->FirstCustomFieldValue('Content'), 'Always', 'Content CF'); is($coffee->FirstCustomFieldValue('Clearance'), 'Unclassified', 'Clearance CF'); is($coffee->CustomFieldValuesAsString('Tags', Separator => '.'), 'drink.coffee.how the humans live', 'Tags CF'); my $twd = RT::Article->new(RT->SystemUser); $twd->LoadByCols(Name => 'Total world domination plans'); ok($twd->Id, 'loaded article'); is($twd->Name, 'Total world domination plans', 'Name'); is($twd->Class, $class->Id, 'Class'); is($twd->FirstCustomFieldValue('Content'), 'REDACTED', 'Content CF'); is($twd->FirstCustomFieldValue('Clearance'), 'Top Secret', 'Clearance CF'); is($twd->CustomFieldValuesAsString('Tags', Separator => '.'), 'snakes.clowns', 'Tags CF'); }, }, { name => 'Attributes', create => sub { my $root = RT::User->new(RT->SystemUser); my ($ok, $msg) = $root->Load('root'); ok($ok, $msg); my $dashboard = RT::Dashboard->new($root); ($ok, $msg) = $dashboard->Save( Name => 'My Dashboard', Privacy => 'RT::User-' . $root->Id, ); ok($ok, $msg); my $subscription = RT::Attribute->new($root); ($ok, $msg) = $subscription->Create( Name => 'Subscription', Description => 'Subscription to dashboard ' . $dashboard->Id, ContentType => 'storable', Object => $root, Content => { 'Tuesday' => '1', 'DashboardId' => $dashboard->Id }, ); }, present => sub { # Provided in core initialdata my $homepage = RT::Attribute->new(RT->SystemUser); $homepage->LoadByNameAndObject(Name => 'HomepageSettings', Object => RT->System); ok($homepage->Id, 'Loaded homepage attribute'); is($homepage->Name, 'HomepageSettings', 'Name is HomepageSettings'); is($homepage->Description, 'HomepageSettings', 'Description is HomepageSettings'); is($homepage->ContentType, 'storable', 'ContentType is storable'); my $root = RT::User->new(RT->SystemUser); my ($ok, $msg) = $root->Load('root'); ok($ok, $msg); my $dashboard = RT::Attribute->new($root); $dashboard->LoadByNameAndObject(Name => 'Dashboard', Object => $root); ok($dashboard->Id, 'Loaded dashboard attribute with id ' . $dashboard->Id); my $subscription = RT::Attribute->new($root); $subscription->LoadByNameAndObject(Name => 'Subscription', Object => $root); ok($subscription->Id, 'Loaded subscription attribute with id ' . $subscription->Id); is($subscription->ContentType, 'storable', 'ContentType is storable'); is($subscription->Content->{DashboardId}, $dashboard->Id, 'Dashboard Id is ' . $dashboard->Id); is( $subscription->Description, 'Subscription to dashboard ' . $dashboard->Id, 'Description is "Subscription to dashboard ' . $dashboard->Id . '"' ); }, }, ); my $id = 0; for my $test (@tests) { $id++; my $directory = File::Spec->catdir(RT::Test->temp_directory, "export-$id"); # we get a lot of warnings about already-existing objects; suppress them # for now until we clean it up my $warn = $SIG{__WARN__}; local $SIG{__WARN__} = sub { return if $_[0] =~ join '|', ( qr/^Name in use$/, qr/^A Template with that name already exists$/, qr/^.* already has the right .* on .*$/, qr/^Invalid value for Name$/, qr/^Queue already exists$/, qr/^Invalid Name \(names must be unique and may not be all digits\)$/, ); # Avoid reporting this anonymous call frame as the source of the warning goto &$warn; }; my $name = delete $test->{name}; my $create = delete $test->{create}; my $absent = delete $test->{absent}; my $present = delete $test->{present}; my $raw = delete $test->{raw}; my $export_args = delete $test->{export_args}; fail("Unexpected keys for test #$id ($name): " . join(', ', sort keys %$test)) if keys %$test; subtest "$name (ordinary creation)" => sub { autorollback(sub { $absent->(0) if $absent; $create->(); $present->(0) if $present; export_initialdata($directory, %{ $export_args || {} }); }); }; if ($raw) { subtest "$name (testing initialdata)" => sub { my $file = File::Spec->catfile($directory, "initialdata.json"); my $content = slurp($file); my $json = JSON->new->decode($content); $raw->($json, $content); }; } subtest "$name (from export-$id/initialdata.json)" => sub { autorollback(sub { $absent->(1) if $absent; import_initialdata($directory); $present->(1) if $present; }); }; } RT::Test::done_testing(); sub autorollback { my $code = shift; $RT::Handle->BeginTransaction; { # avoid "Rollback and commit are mixed while escaping nested transaction" warnings # due to (begin; (begin; commit); rollback) no warnings 'redefine'; local *DBIx::SearchBuilder::Handle::BeginTransaction = sub {}; local *DBIx::SearchBuilder::Handle::Commit = sub {}; local *DBIx::SearchBuilder::Handle::Rollback = sub {}; $code->(); } $RT::Handle->Rollback; } sub export_initialdata { my $directory = shift; my %args = @_; local @RT::Record::ISA = qw( DBIx::SearchBuilder::Record RT::Base ); use RT::Migrate::Serializer::JSON; my $migrator = RT::Migrate::Serializer::JSON->new( Directory => $directory, Verbose => 0, AllUsers => 0, FollowACL => 1, FollowScrips => 1, FollowTransactions => 0, FollowTickets => 0, FollowAssets => 0, FollowDisabled => 0, %args, ); $migrator->Export; } sub import_initialdata { my $directory = shift; my $initialdata = File::Spec->catfile($directory, "initialdata.json"); ok(-e $initialdata, "File $initialdata exists"); my ($rv, $msg) = RT->DatabaseHandle->InsertData( $initialdata, undef, disconnect_after => 0 ); ok($rv, "Inserted test data from $initialdata") or diag "Error: $msg"; } sub slurp { my $file = shift; local $/; open (my $f, '<:encoding(UTF-8)', $file) or die "Cannot open initialdata file '$file' for read: $@"; return scalar <$f>; } rt-5.0.1/t/api/attachment_filename.t000644 000765 000024 00000002067 14005011336 020177 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 5; use MIME::Entity; my $ticket = RT::Ticket->new(RT->SystemUser); my $mime = MIME::Entity->build( From => 'test@example.com', Type => 'text/html', Data => ["test attachment's filename\n"], ); $mime->attach( Path => 'share/static/images/bpslogo.png', Type => 'image/png', ); $mime->attach( Path => 'share/static/images/bpslogo.png', Type => 'image/png', Filename => 'bpslogo.png', ); $mime->attach( Path => 'share/static/images/bpslogo.png', Filename => 'images/bpslogo.png', Type => 'image/png', ); my $id = $ticket->Create( MIMEObj => $mime, Queue => 'General' ); ok( $id, "created ticket $id" ); my $atts = RT::Attachments->new( RT->SystemUser ); $atts->Limit( FIELD => 'ContentType', VALUE => 'image/png' ); is( $atts->Count, 3, 'got 3 png files' ); # no matter if mime's filename include path or not, # we should throw away the path all the time. while ( my $att = $atts->Next ) { is( $att->Filename, 'bpslogo.png', "attachment's filename" ); } rt-5.0.1/t/api/cfsearch.t000644 000765 000024 00000005212 14005011336 015760 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 18; my $suffix = '-'. $$; use_ok 'RT::Users'; use_ok 'RT::CustomField'; my $u1 = RT::User->new( RT->SystemUser ); isa_ok( $u1, 'RT::User' ); ok( $u1->Load('root'), "Loaded user 'root'" ); # create cf my $cfname = 'TestUserCF'. $suffix; my $cf = RT::CustomField->new( RT->SystemUser ); isa_ok( $cf, 'RT::CustomField' ); { my ($id, $msg) = $cf->Create( Name => $cfname, LookupType => 'RT::User', Type => 'Freeform', Description => 'Freeform CF for tests', ); ok( $id, "Created cf '$cfname' - " . $msg ); } { my ($status, $msg) = $cf->AddToObject( $u1 ); ok( $status, "Added CF to user object - " . $msg); } my $cfvalue1 = 'Foo'; { my ($id, $msg) = $u1->AddCustomFieldValue( Field => $cfname, Value => $cfvalue1, RecordTransaction => 0 ); ok( $id, "Adding CF value '$cfvalue1' - " . $msg ); } # Confirm value is returned. { my $cf_value_ref = QueryCFValue( $cfvalue1, $cf->id ); is( scalar(@$cf_value_ref), 1, 'Got one value.' ); is( $cf_value_ref->[0], 'Foo', 'Got Foo back for value.' ); } { my ($id, $msg) = $u1->DeleteCustomFieldValue( Field => $cfname, Value => $cfvalue1, RecordTransaction => 0 ); ok( $id, "Deleting CF value - " . $msg ); } my $cfvalue2 = 'Bar'; { my ($id, $msg) = $u1->AddCustomFieldValue( Field => $cfname, Value => $cfvalue2, RecordTransaction => 0 ); ok( $id, "Adding second CF value '$cfvalue2' - " . $msg ); } # Confirm no value is returned for Foo. { # Calling with $cfvalue1 on purpose to confirm # it has been disabled by the delete above. my $cf_value_ref = QueryCFValue( $cfvalue1, $cf->id ); is( scalar(@$cf_value_ref), 0, 'No values returned for Foo.' ); } # Confirm value is returned for Bar. { my $cf_value_ref = QueryCFValue( $cfvalue2, $cf->id ); is( scalar(@$cf_value_ref), 1, 'Got one value.' ); is( $cf_value_ref->[0], 'Bar', 'Got Bar back for value.' ); } sub QueryCFValue{ my $cf_value = shift; my $cf_id = shift; my @cf_value_strs; my $users = RT::Users->new(RT->SystemUser); isa_ok( $users, 'RT::Users' ); $users->LimitCustomField( CUSTOMFIELD => $cf_id, OPERATOR => "=", VALUE => $cf_value ); while ( my $filtered_user = $users->Next() ){ my $cf_values = $filtered_user->CustomFieldValues($cf->id); while (my $cf_value = $cf_values->Next() ){ push @cf_value_strs, $cf_value->Content; } } return \@cf_value_strs; } rt-5.0.1/t/api/user.t000644 000765 000024 00000041655 14005011336 015173 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => undef; { ok(require RT::User); } { # Make sure we can create a user my $u1 = RT::User->new(RT->SystemUser); is(ref($u1), 'RT::User'); my ($id, $msg) = $u1->Create(Name => 'CreateTest1'.$$, EmailAddress => $$.'create-test-1@example.com'); ok ($id, "Creating user CreateTest1 - " . $msg ); # Make sure we can't create a second user with the same name my $u2 = RT::User->new(RT->SystemUser); ($id, $msg) = $u2->Create(Name => 'CreateTest1'.$$, EmailAddress => $$.'create-test-2@example.com'); ok (!$id, $msg); # Make sure we can't create a second user with the same EmailAddress address my $u3 = RT::User->new(RT->SystemUser); ($id, $msg) = $u3->Create(Name => 'CreateTest2'.$$, EmailAddress => $$.'create-test-1@example.com'); ok (!$id, $msg); # Make sure we can create a user with no EmailAddress address my $u4 = RT::User->new(RT->SystemUser); ($id, $msg) = $u4->Create(Name => 'CreateTest3'.$$); ok ($id, $msg); # make sure we can create a second user with no EmailAddress address my $u5 = RT::User->new(RT->SystemUser); ($id, $msg) = $u5->Create(Name => 'CreateTest4'.$$); ok ($id, $msg); # make sure we can create a user with a blank EmailAddress address my $u6 = RT::User->new(RT->SystemUser); ($id, $msg) = $u6->Create(Name => 'CreateTest6'.$$, EmailAddress => ''); ok ($id, $msg); # make sure we can create a second user with a blankEmailAddress address my $u7 = RT::User->new(RT->SystemUser); ($id, $msg) = $u7->Create(Name => 'CreateTest7'.$$, EmailAddress => ''); ok ($id, $msg); # Can we change the email address away from from ""; ($id,$msg) = $u7->SetEmailAddress('foo@bar'.$$); ok ($id, $msg); # can we change the address back to ""; ($id,$msg) = $u7->SetEmailAddress(''); ok ($id, $msg); is_empty ($u7->EmailAddress); # back to something, so we can set undef next successfully ($id,$msg) = $u7->SetEmailAddress('foo@bar'.$$); ok ($id, $msg); ($id,$msg) = $u7->SetEmailAddress(undef); ok ($id, $msg); is_empty ($u7->EmailAddress); RT->Config->Set('ValidateUserEmailAddresses' => 1); # Make sur we can't create a user with multiple email adresses separated by comma my $u8 = RT::User->new(RT->SystemUser); ($id, $msg) = $u8->Create(Name => 'CreateTest8'.$$, EmailAddress => $$.'create-test-81@example.com, '.$$.'create-test-82@example.com'); ok (!$id, $msg); # Make sur we can't create a user with multiple email adresses separated by space my $u9 = RT::User->new(RT->SystemUser); ($id, $msg) = $u9->Create(Name => 'CreateTest9'.$$, EmailAddress => $$.'create-test-91@example.com '.$$.'create-test-92@example.com'); ok (!$id, $msg); # Make sur we can't create a user with invalid email address my $u10 = RT::User->new(RT->SystemUser); ($id, $msg) = $u10->Create(Name => 'CreateTest10'.$$, EmailAddress => $$.'create-test10}@[.com'); ok (!$id, $msg); RT->Config->Set('ValidateUserEmailAddresses' => undef); # Set User CFs on create my $cf = RT::CustomField->new(RT->SystemUser); ok($cf, "Have a CustomField object"); # Use the old Queue field to set up a ticket CF ($id, $msg) = $cf->Create( Name => 'Testing CF', Description => 'A Testing custom field', Type => 'Freeform', MaxValues => 1, LookupType => RT::User->CustomFieldLookupType, ); ok($id, 'User custom field correctly created'); ok( $cf->AddToObject( RT::User->new( RT->SystemUser ) ), 'applied Testing CF globally' ); my $u11 = RT::User->new(RT->SystemUser); ($id, $msg) = $u11->Create( Name => 'CreateTest11'.$$, EmailAddress => $$.'create-test10@.com', 'UserCF.Testing CF' => 'Testing' ); ok ($id, $msg); is ( $u11->FirstCustomFieldValue('Testing CF'), 'Testing', 'Got Testing for Testing CF'); } { ok(my $user = RT::User->new(RT->SystemUser)); ok($user->Load('root'), "Loaded user 'root'"); ok($user->Privileged, "User 'root' is privileged"); ok(my ($v,$m) = $user->SetPrivileged(0)); is ($v ,1, "Set unprivileged suceeded ($m)"); ok(!$user->Privileged, "User 'root' is no longer privileged"); ok(my ($v2,$m2) = $user->SetPrivileged(1)); is ($v2 ,1, "Set privileged suceeded ($m2"); ok($user->Privileged, "User 'root' is privileged again"); } { ok(my $u = RT::User->new(RT->SystemUser)); ok($u->Load(1), "Loaded the first user"); is($u->PrincipalObj->id , 1, "user 1 is the first principal"); is($u->PrincipalObj->PrincipalType, 'User' , "Principal 1 is a user, not a group"); } { my $root = RT::User->new(RT->SystemUser); $root->Load('root'); ok($root->Id, "Found the root user"); my $rootq = RT::Queue->new($root); $rootq->Load(1); ok($rootq->Id, "Loaded the first queue"); ok ($rootq->CurrentUser->HasRight(Right=> 'CreateTicket', Object => $rootq), "Root can create tickets"); my $new_user = RT::User->new(RT->SystemUser); my ($id, $msg) = $new_user->Create(Name => 'ACLTest'.$$); ok ($id, "Created a new user for acl test $msg"); my $q = RT::Queue->new($new_user); $q->Load(1); ok($q->Id, "Loaded the first queue"); ok (!$q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "Some random user doesn't have the right to create tickets"); ok (my ($gval, $gmsg) = $new_user->PrincipalObj->GrantRight( Right => 'CreateTicket', Object => $q), "Granted the random user the right to create tickets"); ok ($gval, "Grant succeeded - $gmsg"); ok ($q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "The user can create tickets after we grant him the right"); ok ( ($gval, $gmsg) = $new_user->PrincipalObj->RevokeRight( Right => 'CreateTicket', Object => $q), "revoked the random user the right to create tickets"); ok ($gval, "Revocation succeeded - $gmsg"); ok (!$q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "The user can't create tickets anymore"); # Create a ticket in the queue my $new_tick = RT::Ticket->new(RT->SystemUser); my ($tickid, $tickmsg) = $new_tick->Create(Subject=> 'ACL Test', Queue => 'General'); ok($tickid, "Created ticket: $tickid"); # Make sure the user doesn't have the right to modify tickets in the queue ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership"); # Create a new group my $group = RT::Group->new(RT->SystemUser); $group->CreateUserDefinedGroup(Name => 'ACLTest'.$$); ok($group->Id, "Created a new group Ok"); # Grant a group the right to modify tickets in a queue ok(my ($gv,$gm) = $group->PrincipalObj->GrantRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets"); ok($gv,"Grant succeeed - $gm"); # Add the user to the group ok( my ($aid, $amsg) = $group->AddMember($new_user->PrincipalId), "Added the member to the group"); ok ($aid, "Member added to group: $amsg"); # Make sure the user does have the right to modify tickets in the queue ok ($new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can modify the ticket with group membership"); # Remove the user from the group ok( my ($did, $dmsg) = $group->DeleteMember($new_user->PrincipalId), "Deleted the member from the group"); ok ($did,"Deleted the group member: $dmsg"); # Make sure the user doesn't have the right to modify tickets in the queue ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership"); my $q_as_system = RT::Queue->new(RT->SystemUser); $q_as_system->Load(1); ok($q_as_system->Id, "Loaded the first queue"); # Create a ticket in the queue my $new_tick2 = RT::Ticket->new(RT->SystemUser); (my $tick2id, $tickmsg) = $new_tick2->Create(Subject=> 'ACL Test 2', Queue =>$q_as_system->Id); ok($tick2id, "Created ticket: $tick2id"); is($new_tick2->QueueObj->id, $q_as_system->Id, "Created a new ticket in queue 1"); # make sure that the user can't do this without subgroup membership ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership"); # Create a subgroup my $subgroup = RT::Group->new(RT->SystemUser); $subgroup->CreateUserDefinedGroup(Name => 'Subgrouptest'.$$); ok($subgroup->Id, "Created a new group ".$subgroup->Id."Ok"); #Add the subgroup as a subgroup of the group my ($said, $samsg) = $group->AddMember($subgroup->PrincipalId); ok ($said, "Added the subgroup as a member of the group"); # Add the user to a subgroup of the group my ($usaid, $usamsg) = $subgroup->AddMember($new_user->PrincipalId); ok($usaid,"Added the user ".$new_user->Id."to the subgroup"); # Make sure the user does have the right to modify tickets in the queue ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket with subgroup membership"); # {{{ Deal with making sure that members of subgroups of a disabled group don't have rights ($id, $msg) = $group->SetDisabled(1); ok ($id,$msg); ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$group->Id. " is disabled"); ($id, $msg) = $group->SetDisabled(0); ok($id,$msg); # Test what happens when we disable the group the user is a member of directly ($id, $msg) = $subgroup->SetDisabled(1); ok ($id,$msg); ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$subgroup->Id. " is disabled"); ($id, $msg) = $subgroup->SetDisabled(0); ok ($id,$msg); ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket without group membership"); my ($usrid, $usrmsg) = $subgroup->DeleteMember($new_user->PrincipalId); ok($usrid,"removed the user from the group - $usrmsg"); # Make sure the user doesn't have the right to modify tickets in the queue ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership"); #revoke the right to modify tickets in a queue ok(($gv,$gm) = $group->PrincipalObj->RevokeRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets"); ok($gv,"revoke succeeed - $gm"); # Grant queue admin cc the right to modify ticket in the queue ok(my ($qv,$qm) = $q_as_system->AdminCc->PrincipalObj->GrantRight( Object => $q_as_system, Right => 'ModifyTicket'),"Granted the queue adminccs the right to modify tickets"); ok($qv, "Granted the right successfully - $qm"); # Add the user as a queue admincc ok (my ($add_id, $add_msg) = $q_as_system->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc"); ok ($add_id, "the user is now a queue admincc - $add_msg"); # Make sure the user does have the right to modify tickets in the queue ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc"); # Remove the user from the role group ok (my ($del_id, $del_msg) = $q_as_system->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc"); # Make sure the user doesn't have the right to modify tickets in the queue ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership"); # Add the user as a ticket admincc ok (my( $uadd_id, $uadd_msg) = $new_tick2->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc"); ok ($add_id, "the user is now a queue admincc - $add_msg"); # Make sure the user does have the right to modify tickets in the queue ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc"); # Remove the user from the role group ok (( $del_id, $del_msg) = $new_tick2->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc"); # Make sure the user doesn't have the right to modify tickets in the queue ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership"); # Revoke the right to modify ticket in the queue ok(my ($rqv,$rqm) = $q_as_system->AdminCc->PrincipalObj->RevokeRight( Object => $q_as_system, Right => 'ModifyTicket'),"Revokeed the queue adminccs the right to modify tickets"); ok($rqv, "Revoked the right successfully - $rqm"); # Before we start Make sure the user does not have the right to modify tickets in the queue ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without it being granted"); ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue without it being granted"); # Grant queue admin cc the right to modify ticket in the queue ok(($qv,$qm) = $q_as_system->AdminCc->PrincipalObj->GrantRight( Object => $RT::System, Right => 'ModifyTicket'),"Granted the queue adminccs the right to modify tickets"); ok($qv, "Granted the right successfully - $qm"); # Make sure the user can't modify the ticket before they're added as a watcher ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without being an admincc"); ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue without being an admincc"); # Add the user as a queue admincc ok (($add_id, $add_msg) = $q_as_system->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc"); ok ($add_id, "the user is now a queue admincc - $add_msg"); # Make sure the user does have the right to modify tickets in the queue ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc"); ok ($new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can modify tickets in the queue as an admincc"); # Remove the user from the role group ok (($del_id, $del_msg) = $q_as_system->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc"); # Make sure the user doesn't have the right to modify tickets in the queue ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership"); ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can't modify tickets in the queue without group membership"); ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without being an admincc"); ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj without being an admincc"); # Add the user as a ticket admincc ok ( ($uadd_id, $uadd_msg) = $new_tick2->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc"); ok ($add_id, "the user is now a queue admincc - $add_msg"); # Make sure the user does have the right to modify tickets in the queue ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc"); ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj being only a ticket admincc"); # Remove the user from the role group ok ( ($del_id, $del_msg) = $new_tick2->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc"); # Make sure the user doesn't have the right to modify tickets in the queue ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without being an admincc"); ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj without being an admincc"); # Revoke the right to modify ticket in the queue ok(($rqv,$rqm) = $q_as_system->AdminCc->PrincipalObj->RevokeRight( Object => $RT::System, Right => 'ModifyTicket'),"Revokeed the queue adminccs the right to modify tickets"); ok($rqv, "Revoked the right successfully - $rqm"); # Grant "privileged users" the system right to create users # Create a privileged user. # have that user create another user # Revoke the right for privileged users to create users # have the privileged user try to create another user and fail the ACL check } { my $root = RT::Test->load_or_create_user( Name => 'root' ); ok $root && $root->id; my $queue = RT::Test->load_or_create_queue( Name => 'General' ); ok $queue && $queue->id; my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id) = $ticket->Create( Subject => 'test', Queue => $queue ); ok $id; my $b_ticket = RT::Ticket->new( RT->SystemUser ); ($id) = $b_ticket->Create( Subject => 'test', Queue => $queue ); ok $id; ok $root->ToggleBookmark($b_ticket); ok !$root->ToggleBookmark($b_ticket); ok $root->ToggleBookmark($b_ticket); ok $root->HasBookmark( $b_ticket ); ok !$root->HasBookmark( $ticket ); my @marks = $root->Bookmarks; is scalar @marks, 1; is $marks[0], $b_ticket->id; } done_testing(); rt-5.0.1/t/api/txn_content.t000644 000765 000024 00000001537 14005011336 016553 0ustar00sunnavystaff000000 000000 use warnings; use strict; use RT::Test tests => 4; use MIME::Entity; my $ticket = RT::Ticket->new(RT->SystemUser); my $mime = MIME::Entity->build( From => 'test@example.com', Type => 'text/html', Data => ["this is body\n"], ); $mime->attach( Data => ['this is attachment'] ); my $id = $ticket->Create( MIMEObj => $mime, Queue => 'General' ); ok( $id, "created ticket $id" ); my $txns = $ticket->Transactions; $txns->Limit( FIELD => 'Type', VALUE => 'Create' ); my $txn = $txns->First; ok( $txn, 'got Create txn' ); # ->Content converts from text/html to plain text if we don't explicitly ask # for html. Our html -> text converter seems to add an extra trailing newline like( $txn->Content, qr/^\s*this is body\s*$/, "txn's html content converted to plain text" ); is( $txn->Content(Type => 'text/html'), "this is body\n", "txn's html content" ); rt-5.0.1/t/api/emailparser.t000644 000765 000024 00000002664 14005011336 016516 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodb => 1, tests => undef; ok(require RT::EmailParser); RT->Config->Set( RTAddressRegexp => undef ); is(RT::EmailParser::IsRTAddress("",""),undef, "Empty emails from users don't match queues without email addresses" ); RT->Config->Set( RTAddressRegexp => qr/^rt\@example.com$/i ); is(RT::EmailParser::IsRTAddress("","rt\@example.com"),1, "Regexp matched rt address" ); is(RT::EmailParser::IsRTAddress("","frt\@example.com"),undef, "Regexp didn't match non-rt address" ); my @before = ("rt\@example.com", "frt\@example.com"); my @after = ("frt\@example.com"); ok(eq_array(RT::EmailParser->CullRTAddresses(@before),@after), "CullRTAddresses only culls RT addresses"); { my ( $addr ) = RT::EmailParser->ParseEmailAddress('foo@example.com'); is( $addr->address, 'foo@example.com', 'addr for foo@example.com' ); is( $addr->phrase, undef, 'no name for foo@example.com' ); ( $addr ) = RT::EmailParser->ParseEmailAddress('Foo '); is( $addr->address, 'foo@example.com', 'addr for Foo ' ); is( $addr->phrase, 'Foo', 'name for Foo ' ); ( $addr ) = RT::EmailParser->ParseEmailAddress('foo@example.com (Comment)'); is( $addr->address, 'foo@example.com', 'addr for foo@example.com (Comment)' ); is( $addr->phrase, undef, 'no name for foo@example.com (Comment)' ); } done_testing; rt-5.0.1/t/api/reminders.t000644 000765 000024 00000005052 14005011336 016174 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => 20; { # Create test queues use_ok ('RT::Queue'); ok(my $testqueue = RT::Queue->new(RT->SystemUser), 'Instantiate RT::Queue'); ok($testqueue->Create( Name => 'reminders tests'), 'Create new queue: reminders tests'); isnt($testqueue->Id , 0, 'Success creating queue'); ok($testqueue->Create( Name => 'reminders tests 2'), 'Create new queue: reminders tests 2'); isnt($testqueue->Id , 0, 'Success creating queue'); # Create test ticket use_ok('RT::Ticket'); my $u = RT::User->new(RT->SystemUser); $u->Load("root"); ok ($u->Id, "Found the root user"); ok(my $t = RT::Ticket->new(RT->SystemUser), 'Instantiate RT::Ticket'); ok(my ($id, $msg) = $t->Create( Queue => $testqueue->Id, Subject => 'Testing', Owner => $u->Id ), 'Create sample ticket'); isnt($id , 0, 'Success creating ticket'); # Add reminder my $due_obj = RT::Date->new( RT->SystemUser ); $due_obj->SetToNow; ok(my ( $add_id, $add_msg, $txnid ) = $t->Reminders->Add( Subject => 'TestReminder', Owner => 'root', Due => $due_obj->ISO ), 'Add reminder'); # Check that the new Reminder is here my $reminders = $t->Reminders->Collection; ok($reminders, 'Loading reminders for this ticket'); my $found = 0; while ( my $reminder = $reminders->Next ) { next unless $found == 0; $found = 1 if ( $reminder->Subject =~ m/TestReminder/ ); } is($found, 1, 'Reminder successfully added'); # Change queue ok (my ($move_val, $move_msg) = $t->SetQueue('reminders tests 2'), 'Moving ticket from queue "reminders tests" to "reminders tests 2"'); is ($t->QueueObj->Name, 'reminders tests 2', 'Ticket successfully moved'); # Check that the new reminder is still there and moved to the new queue $reminders = $t->Reminders->Collection; ok($reminders, 'Loading reminders for this ticket'); $found = 0; my $ok_queue = 0; while ( my $reminder = $reminders->Next ) { next unless $found == 0; if ( $reminder->Subject =~ m/TestReminder/ ) { $found = 1; $ok_queue = 1 if ( $reminder->QueueObj->Name eq 'reminders tests 2' ); } } is($found, 1, 'Reminder successfully added'); is($ok_queue, 1, 'Reminder automatically moved to new queue'); # Resolve reminder my $r_resolved = 0; while ( my $reminder = $reminders->Next ) { if ( $reminder->Subject =~ m/TestReminder/ ) { if ( $reminder->Status ne 'resolved' ) { $t->Reminders->Resolve($reminder); $r_resolved = 1 if ( $reminder->Status eq 'resolved' ); } } } is($r_resolved, 1, 'Reminder resolved'); } rt-5.0.1/t/api/system.t000644 000765 000024 00000003566 14005011336 015540 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test nodata => 1, tests => 16; BEGIN{ use_ok('RT::System'); } # Skipping most of the methods added just to make RT::System # look like RT::Record. can_ok('RT::System', qw( AvailableRights RightCategories AddRight id Id SubjectTag Name QueueCacheNeedsUpdate AddUpgradeHistory UpgradeHistory )); { my $s = RT::System->new(RT->SystemUser); my $rights = $s->AvailableRights; ok ($rights, "Rights defined"); ok ($rights->{'AdminUsers'},"AdminUsers right found"); ok ($rights->{'CreateTicket'},"CreateTicket right found"); ok ($rights->{'AdminGroupMembership'},"ModifyGroupMembers right found"); ok (!$rights->{'CasdasdsreateTicket'},"bogus right not found"); } { my $sys = RT::System->new(); is( $sys->Id, 1, 'Id is 1'); is ($sys->id, 1, 'id is 1'); } { # Test upgrade history methods. my $sys = RT::System->new(RT->SystemUser); isa_ok($sys, 'RT::System'); my $file = 'test_file.txt'; my $content = 'Some file contents.'; my $upgrade_history = RT->System->UpgradeHistory(); is( keys %$upgrade_history, 0, 'No history in test DB'); RT->System->AddUpgradeHistory(RT =>{ action => 'insert', filename => $file, content => $content, stage => 'before', }); $upgrade_history = RT->System->UpgradeHistory(); ok( exists($upgrade_history->{'RT'}), 'History has an RT key.'); is( @{$upgrade_history->{'RT'}}, 1, '1 item in history array'); is($upgrade_history->{RT}[0]{stage}, 'before', 'stage is before for item 1'); RT->System->AddUpgradeHistory(RT =>{ action => 'insert', filename => $file, content => $content, stage => 'after', }); $upgrade_history = RT->System->UpgradeHistory(); is( @{$upgrade_history->{'RT'}}, 2, '2 item in history array'); is($upgrade_history->{RT}[1]{stage}, 'after', 'stage is after for item 2'); } rt-5.0.1/t/api/bookmarks.t000644 000765 000024 00000001627 14005011336 016200 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 36; my ( $url, $m ) = RT::Test->started_ok; my $root = RT::Test->load_or_create_user( Name => 'root' ); my @tickets = RT::Test->create_tickets( { }, map { { Subject => "Test $_" } } ( 1 .. 9 ) ); # 4.2 gives us $user->ToggleBookmark which is nicer $root->SetAttribute( Name => 'Bookmarks', Content => { map { $_ => 1 } (3,6,9) } ); my $cu = RT::CurrentUser->new($root); my $bookmarks = RT::Tickets->new($cu); for my $search ( "Queue = 'General' AND id = '__Bookmarked__'", "id = '__Bookmarked__' AND Queue = 'General'", "id > 0 AND id = '__Bookmarked__'", "id = '__Bookmarked__' AND id > 0", "id = 3 OR id = '__Bookmarked__'", "id = '__Bookmarked__' OR id = 3", ) { $bookmarks->FromSQL($search); is($bookmarks->Count,3,"Found my 3 bookmarks for [$search]"); } rt-5.0.1/t/api/system-available-rights.t000644 000765 000024 00000003223 14005011336 020742 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use Set::Tiny; my @warnings; local $SIG{__WARN__} = sub { push @warnings, "@_"; }; my $requestor = RT::Group->new( RT->SystemUser ); $requestor->LoadRoleGroup( Object => RT->System, Name => "Requestor", ); ok $requestor->id, "Loaded global requestor role group"; $requestor = $requestor->PrincipalObj; ok $requestor->id, "Loaded global requestor role group principal"; note "Try granting an article right to a system role group"; { my ($ok, $msg) = $requestor->GrantRight( Right => "ShowArticle", Object => RT->System, ); ok !$ok, "Couldn't grant nonsensical right to global Requestor role: $msg"; like shift @warnings, qr/Couldn't validate right name.*?ShowArticle/; ($ok, $msg) = $requestor->GrantRight( Right => "ShowTicket", Object => RT->System, ); ok $ok, "Granted queue right to global queue role: $msg"; ($ok, $msg) = RT->PrivilegedUsers->PrincipalObj->GrantRight( Right => "ShowArticle", Object => RT->System, ); ok $ok, "Granted article right to non-role global group: $msg"; reset_rights(); } note "AvailableRights"; { my @available = ( [ keys %{RT->System->AvailableRights} ], [ keys %{RT->System->AvailableRights( $requestor )} ], ); my $all = Set::Tiny->new( @{$available[0]} ); my $role = Set::Tiny->new( @{$available[1]} ); ok $role->is_proper_subset($all), "role rights are a proper subset of all"; } ok !@warnings, "No uncaught warnings" or diag explain \@warnings; # for clarity sub reset_rights { RT::Test->set_rights() } done_testing; rt-5.0.1/t/api/link.t000644 000765 000024 00000016622 14005011336 015146 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => 83; use RT::Test::Web; use Test::Warn; use RT::Link; my $link = RT::Link->new(RT->SystemUser); ok (ref $link); isa_ok( $link, 'RT::Link'); isa_ok( $link, 'RT::Base'); isa_ok( $link, 'RT::Record'); isa_ok( $link, 'DBIx::SearchBuilder::Record'); my $queue = RT::Test->load_or_create_queue(Name => 'General'); ok($queue->Id, "loaded the General queue"); my $parent = RT::Ticket->new(RT->SystemUser); my ($pid, undef, $msg) = $parent->Create( Queue => $queue->id, Subject => 'parent', ); ok $pid, 'created a ticket #'. $pid or diag "error: $msg"; my $child = RT::Ticket->new(RT->SystemUser); ((my $cid), undef, $msg) = $child->Create( Queue => $queue->id, Subject => 'child', ); ok $cid, 'created a ticket #'. $cid or diag "error: $msg"; { my ($status, $msg); clean_links(); ($status, $msg) = $parent->AddLink; ok(!$status, "didn't create a link: $msg"); warning_like { ($status, $msg) = $parent->AddLink( Base => $parent->id ); } qr/Can't link a ticket to itself/, "warned about linking a ticket to itself"; ok(!$status, "didn't create a link: $msg"); warning_like { ($status, $msg) = $parent->AddLink( Base => $parent->id, Type => 'HasMember' ); } qr/Can't link a ticket to itself/, "warned about linking a ticket to itself"; ok(!$status, "didn't create a link: $msg"); } { clean_links(); my ($status, $msg) = $parent->AddLink( Type => 'MemberOf', Base => $child->id, ); ok($status, "created a link: $msg"); my $children = $parent->Members; $children->RedoSearch; $children->GotoFirstItem; is $children->Count, 1, 'link is there'; my $link = $children->First; ok $link->id, 'correct link'; is $link->Type, 'MemberOf', 'type'; is $link->LocalTarget, $parent->id, 'local target'; is $link->LocalBase, $child->id, 'local base'; is $link->Target, 'fsck.com-rt://example.com/ticket/'. $parent->id, 'local target'; is $link->Base, 'fsck.com-rt://example.com/ticket/'. $child->id, 'local base'; isa_ok $link->TargetObj, 'RT::Ticket'; is $link->TargetObj->id, $parent->id, 'correct ticket'; isa_ok $link->TargetURI, 'RT::URI'; is $link->TargetURI->Scheme, 'fsck.com-rt', 'correct scheme'; is $link->TargetURI->URI, 'fsck.com-rt://example.com/ticket/'. $parent->id, 'correct URI' ; ok $link->TargetURI->IsLocal, 'local object'; is $link->TargetURI->AsHREF, RT::Test::Web->rt_base_url .'Ticket/Display.html?id='. $parent->id, 'correct href' ; isa_ok $link->BaseObj, 'RT::Ticket'; is $link->BaseObj->id, $child->id, 'correct ticket'; isa_ok $link->BaseURI, 'RT::URI'; is $link->BaseURI->Scheme, 'fsck.com-rt', 'correct scheme'; is $link->BaseURI->URI, 'fsck.com-rt://example.com/ticket/'. $child->id, 'correct URI' ; ok $link->BaseURI->IsLocal, 'local object'; is $link->BaseURI->AsHREF, RT::Test::Web->rt_base_url .'Ticket/Display.html?id='. $child->id, 'correct href' ; } { clean_links(); my ($status, $msg) = $parent->AddLink( Type => 'MemberOf', Base => $child->URI, ); ok($status, "created a link: $msg"); my $children = $parent->Members; $children->RedoSearch; $children->GotoFirstItem; is $children->Count, 1, 'link is there'; my $link = $children->First; ok $link->id, 'correct link'; is $link->Type, 'MemberOf', 'type'; is $link->LocalTarget, $parent->id, 'local target'; is $link->LocalBase, $child->id, 'local base'; is $link->Target, 'fsck.com-rt://example.com/ticket/'. $parent->id, 'local target'; is $link->Base, 'fsck.com-rt://example.com/ticket/'. $child->id, 'local base'; isa_ok $link->TargetObj, 'RT::Ticket'; is $link->TargetObj->id, $parent->id, 'correct ticket'; isa_ok $link->TargetURI, 'RT::URI'; is $link->TargetURI->Scheme, 'fsck.com-rt', 'correct scheme'; is $link->TargetURI->URI, 'fsck.com-rt://example.com/ticket/'. $parent->id, 'correct URI' ; ok $link->TargetURI->IsLocal, 'local object'; is $link->TargetURI->AsHREF, RT::Test::Web->rt_base_url .'Ticket/Display.html?id='. $parent->id, 'correct href' ; isa_ok $link->BaseObj, 'RT::Ticket'; is $link->BaseObj->id, $child->id, 'correct ticket'; isa_ok $link->BaseURI, 'RT::URI'; is $link->BaseURI->Scheme, 'fsck.com-rt', 'correct scheme'; is $link->BaseURI->URI, 'fsck.com-rt://example.com/ticket/'. $child->id, 'correct URI' ; ok $link->BaseURI->IsLocal, 'local object'; is $link->BaseURI->AsHREF, RT::Test::Web->rt_base_url .'Ticket/Display.html?id='. $child->id, 'correct href' ; } { clean_links(); my ($status, $msg) = $parent->AddLink( Type => 'MemberOf', Base => 't:'. $child->id, ); ok($status, "created a link: $msg"); my $children = $parent->Members; $children->RedoSearch; $children->GotoFirstItem; is $children->Count, 1, 'link is there'; my $link = $children->First; ok $link->id, 'correct link'; is $link->Type, 'MemberOf', 'type'; is $link->LocalTarget, $parent->id, 'local target'; is $link->LocalBase, $child->id, 'local base'; is $link->Target, 'fsck.com-rt://example.com/ticket/'. $parent->id, 'local target'; is $link->Base, 'fsck.com-rt://example.com/ticket/'. $child->id, 'local base'; isa_ok $link->TargetObj, 'RT::Ticket'; is $link->TargetObj->id, $parent->id, 'correct ticket'; isa_ok $link->TargetURI, 'RT::URI'; is $link->TargetURI->Scheme, 'fsck.com-rt', 'correct scheme'; is $link->TargetURI->URI, 'fsck.com-rt://example.com/ticket/'. $parent->id, 'correct URI' ; ok $link->TargetURI->IsLocal, 'local object'; is $link->TargetURI->AsHREF, RT::Test::Web->rt_base_url .'Ticket/Display.html?id='. $parent->id, 'correct href' ; isa_ok $link->BaseObj, 'RT::Ticket'; is $link->BaseObj->id, $child->id, 'correct ticket'; isa_ok $link->BaseURI, 'RT::URI'; is $link->BaseURI->Scheme, 'fsck.com-rt', 'correct scheme'; is $link->BaseURI->URI, 'fsck.com-rt://example.com/ticket/'. $child->id, 'correct URI' ; ok $link->BaseURI->IsLocal, 'local object'; is $link->BaseURI->AsHREF, RT::Test::Web->rt_base_url .'Ticket/Display.html?id='. $child->id, 'correct href' ; } { clean_links(); $child->SetStatus('deleted'); my ($status, $msg) = $parent->AddLink( Type => 'MemberOf', Base => $child->id, ); ok(!$status, "can't link to deleted ticket: $msg"); $child->SetStatus('new'); ($status, $msg) = $parent->AddLink( Type => 'MemberOf', Base => $child->id, ); ok($status, "created a link: $msg"); $child->SetStatus('deleted'); my $children = $parent->Members; $children->RedoSearch; my $total = 0; $total++ while $children->Next; is( $total, 0, 'Next skips deleted tickets' ); is( @{ $children->ItemsArrayRef }, 0, 'ItemsArrayRef skips deleted tickets' ); # back to active status $child->SetStatus('new'); } sub clean_links { my $links = RT::Links->new( RT->SystemUser ); $links->UnLimit; while ( my $link = $links->Next ) { my ($status, $msg) = $link->Delete; $RT::Logger->error("Couldn't delete a link: $msg") unless $status; } } rt-5.0.1/t/api/uri-canonicalize.t000644 000765 000024 00000003170 14005011336 017437 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my @warnings; local $SIG{__WARN__} = sub { push @warnings, "@_"; }; # Create ticket my $ticket = RT::Test->create_ticket( Queue => 1, Subject => 'test ticket' ); ok $ticket->id, 'created ticket'; # Create article class my $class = RT::Class->new( $RT::SystemUser ); $class->Create( Name => 'URItest - '. $$ ); ok $class->id, 'created a class'; # Create article my $article = RT::Article->new( $RT::SystemUser ); $article->Create( Name => 'Testing URI parsing - '. $$, Summary => 'In which this should load', Class => $class->Id ); ok $article->id, 'create article'; # Test permutations of URIs my $ORG = RT->Config->Get('Organization'); my $URI = RT::URI->new( RT->SystemUser ); my %expected = ( # tickets "1" => "fsck.com-rt://$ORG/ticket/1", "t:1" => "fsck.com-rt://$ORG/ticket/1", "fsck.com-rt://$ORG/ticket/1" => "fsck.com-rt://$ORG/ticket/1", # articles "a:1" => "fsck.com-article://$ORG/article/1", "fsck.com-article://$ORG/article/1" => "fsck.com-article://$ORG/article/1", # random stuff "http://$ORG" => "http://$ORG", "mailto:foo\@example.com" => "mailto:foo\@example.com", "invalid" => "invalid", # doesn't trigger die ); for my $uri (sort keys %expected) { is $URI->CanonicalizeURI($uri), $expected{$uri}, "canonicalized as expected"; } is_deeply \@warnings, [ "Could not determine a URI scheme for invalid\n", ], "expected warnings"; done_testing; rt-5.0.1/t/api/attribute.t000644 000765 000024 00000001563 14005011336 016212 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test nodata => 1, tests => 7; { my $user = RT->SystemUser; my ($id, $msg) = $user->AddAttribute(Name => 'SavedSearch', Content => { Query => 'Foo'} ); ok ($id, $msg); my $attr = RT::Attribute->new(RT->SystemUser); $attr->Load($id); is($attr->Name , 'SavedSearch'); $attr->SetSubValues( Format => 'baz'); my $format = $attr->SubValue('Format'); is ($format , 'baz'); $attr->SetSubValues( Format => 'bar'); $format = $attr->SubValue('Format'); is ($format , 'bar'); $attr->DeleteAllSubValues(); $format = $attr->SubValue('Format'); is ($format, undef); $attr->SetSubValues(Format => 'This is a format'); my $attr2 = RT::Attribute->new(RT->SystemUser); $attr2->Load($id); is ($attr2->SubValue('Format'), 'This is a format'); $attr2->Delete; my $attr3 = RT::Attribute->new(RT->SystemUser); ($id) = $attr3->Load($id); is ($id, 0); } rt-5.0.1/t/api/password-types.t000644 000765 000024 00000005212 14005011336 017206 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test; use Digest::MD5; my $default = "bcrypt"; my $root = RT::User->new(RT->SystemUser); $root->Load("root"); # bcrypt (default) my $old = $root->__Value("Password"); like($old, qr/^\!$default\!/, "Stored as salted $default"); ok($root->IsPassword("password")); is($root->__Value("Password"), $old, "Unchanged after password check"); # bcrypt (smaller number of rounds) my $rounds = RT->Config->Get("BcryptCost"); my $salt = Crypt::Eksblowfish::Bcrypt::en_base64("a"x16); $root->_Set( Field => "Password", Value => RT::User->_GeneratePassword_bcrypt("smaller", 6, $salt) ); like($root->__Value("Password"), qr/^\!$default\!06\!/, "Stored with a smaller number of rounds"); ok($root->IsPassword("smaller"), "Smaller number of bcrypt rounds works"); like($root->__Value("Password"), qr/^\!$default\!$rounds\!/, "And is now upgraded to $rounds rounds"); # Salted SHA-512, one round $root->_Set( Field => "Password", Value => RT::User->_GeneratePassword_sha512("other", "salt") ); ok($root->IsPassword("other"), "SHA-512 password works"); like($root->__Value("Password"), qr/^\!$default\!/, "And is now upgraded to salted $default"); # Crypt $root->_Set( Field => "Password", Value => crypt("something", "salt")); ok($root->IsPassword("something"), "crypt()ed password works"); like($root->__Value("Password"), qr/^\!$default\!/, "And is now upgraded to salted $default"); # MD5, hex $root->_Set( Field => "Password", Value => Digest::MD5::md5_hex("changed")); ok($root->IsPassword("changed"), "Unsalted MD5 hex works"); like($root->__Value("Password"), qr/^\!$default\!/, "And is now upgraded to salted $default"); # MD5, base64 $root->_Set( Field => "Password", Value => Digest::MD5::md5_base64("new")); ok($root->IsPassword("new"), "Unsalted MD5 base64 works"); like($root->__Value("Password"), qr/^\!$default\!/, "And is now upgraded to salted $default"); # Salted truncated SHA-256 my $trunc = MIME::Base64::encode_base64( "salt" . substr(Digest::SHA::sha256("salt".Digest::MD5::md5("secret")),0,26), "" ); $root->_Set( Field => "Password", Value => $trunc); ok($root->IsPassword("secret"), "Unsalted MD5 base64 works"); like($root->__Value("Password"), qr/^\!$default\!/, "And is now upgraded to salted $default"); # Non-ASCII salted truncated SHA-256 my $non_ascii_trunc = MIME::Base64::encode_base64( "salt" . substr(Digest::SHA::sha256("salt".Digest::MD5::md5("áěšý")),0,26), "" ); $root->_Set( Field => "Password", Value => $non_ascii_trunc); ok($root->IsPassword(Encode::decode("UTF-8", "áěšý")), "Unsalted MD5 base64 works"); like($root->__Value("Password"), qr/^\!$default\!/, "And is now upgraded to salted $default"); rt-5.0.1/t/api/cf_rights.t000644 000765 000024 00000002316 14005011336 016154 0ustar00sunnavystaff000000 000000 use warnings; use strict; use RT; use RT::Test tests => 12; my $q = RT::Queue->new($RT::SystemUser); my ($id,$msg) =$q->Create(Name => "CF-Rights-".$$); ok($id,$msg); my $cf = RT::CustomField->new($RT::SystemUser); ($id,$msg) = $cf->Create(Name => 'CF-'.$$, Type => 'Select', MaxValues => '1', Queue => $q->id); ok($id,$msg); ($id,$msg) =$cf->AddValue(Name => 'First'); ok($id,$msg); my $u = RT::User->new($RT::SystemUser); ($id,$msg) = $u->Create( Name => 'User1', Privileged => 1 ); ok ($id,$msg); ($id,$msg) = $u->PrincipalObj->GrantRight( Object => $cf, Right => 'SeeCustomField' ); ok ($id,$msg); my $ucf = RT::CustomField->new($u); ($id,$msg) = $ucf->Load( $cf->Id ); ok ($id,$msg); my $cfv = $ucf->Values->First; ($id,$msg) = $cfv->SetName( 'First1' ); ok (!$id,$msg); ($id,$msg) = $u->PrincipalObj->GrantRight( Object => $cf, Right => 'AdminCustomFieldValues' ); ok ($id,$msg); ($id,$msg) = $cfv->SetName( 'First2' ); ok ($id,$msg); ($id,$msg) = $u->PrincipalObj->RevokeRight( Object => $cf, Right => 'AdminCustomFieldValues' ); ok ($id,$msg); ($id,$msg) = $u->PrincipalObj->GrantRight( Object => $cf, Right => 'AdminCustomField' ); ok ($id,$msg); ($id,$msg) = $cfv->SetName( 'First3' ); ok ($id,$msg); 1; rt-5.0.1/t/api/scrip_execmodule.t000644 000765 000024 00000002321 14005011336 017532 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test plugins => [qw(RT::Extension::ScripExecModule)]; my $system_user = RT->SystemUser; my $queue = RT::Test->load_or_create_queue( Name => 'General' ); ok $queue && $queue->id, 'loaded or created queue'; { my $action = RT::ScripAction->new($system_user); my ( $val, $msg) = $action->Create( Name => 'TestExecModuleAction', Description => '', ExecModule => 'Foo::Bar', ); ok($val, $msg); my $condition = RT::ScripCondition->new($system_user); ( $val, $msg ) = $condition->Create( Name => 'TestExecModuleCondition', Description => '', ApplicableTransTypes => 'Create', ExecModule => 'Foo::Bar', ); ok($val, $msg); my $scrip = RT::Scrip->new($system_user); ($val, $msg) = $scrip->Create( Queue => $queue->Id, ScripAction => 'TestExecModuleAction', ScripCondition => 'TestExecModuleCondition', Template => 'Blank' ); ok($val,$msg); my $ticket = RT::Ticket->new($system_user); my ( $tid, $trans_id, $tmsg ) = $ticket->Create( Subject => 'Sample workflow test', Owner => 'root', Queue => $queue->Id ); ok($tid, $tmsg); } rt-5.0.1/t/api/action-createtickets.t000644 000765 000024 00000014213 14005011336 020310 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => 54; { ok (require RT::Action::CreateTickets); use_ok('RT::Scrip'); use_ok('RT::Template'); use_ok('RT::ScripAction'); use_ok('RT::ScripCondition'); use_ok('RT::Ticket'); use_ok('RT::CustomField'); my $global_cf = RT::CustomField->new($RT::SystemUser); my ($id, $msg)= $global_cf->Create( Name => 'GlobalCF', Queue => '0', SortOrder => '1', Description => 'A Testing custom field', Type=> 'SelectSingle'); ok($id, 'Global custom field correctly created'); my $approvalsq = RT::Queue->new(RT->SystemUser); $approvalsq->Create(Name => 'Approvals'); ok ($approvalsq->Id, "Created Approvals test queue"); my $queue_cf = RT::CustomField->new($RT::SystemUser); ($id) = $queue_cf->Create( Name => 'QueueCF', Queue => $approvalsq->Id, SortOrder => 2, Description => 'A testing queue-specific custom field', Type => 'SelectSingle', ); ok($id, 'Queue-specific custom field correctly created'); my $approvals = '===Create-Ticket: approval Queue: Approvals Type: approval AdminCc: {join ("\nAdminCc: ",@admins) } Depended-On-By: {$Tickets{"TOP"}->Id} Refers-To: TOP CustomField-GlobalCF: A Value CustomField-QueueCF: Another Value Subject: Approval for ticket: {$Tickets{"TOP"}->Id} - {$Tickets{"TOP"}->Subject} Due: {time + 86400} Content-Type: text/plain Content: Your approval is requested for the ticket {$Tickets{"TOP"}->Id}: {$Tickets{"TOP"}->Subject} Blah Blah ENDOFCONTENT ===Create-Ticket: two Subject: Manager approval. Depended-On-By: approval Queue: Approvals Content-Type: text/plain Content: Your minion approved ticket {$Tickets{"TOP"}->Id}. you ok with that? ENDOFCONTENT '; like ($approvals , qr/Content/, "Read in the approvals template"); my $apptemp = RT::Template->new(RT->SystemUser); $apptemp->Create( Content => $approvals, Name => "Approvals", Queue => "0"); ok ($apptemp->Id); my $q = RT::Queue->new(RT->SystemUser); $q->Create(Name => 'WorkflowTest'); ok ($q->Id, "Created workflow test queue"); my $scrip = RT::Scrip->new(RT->SystemUser); my ($sval, $smsg) =$scrip->Create( ScripCondition => 'On Transaction', ScripAction => 'Create Tickets', Template => 'Approvals', Queue => $q->Id); ok ($sval, $smsg); ok ($scrip->Id, "Created the scrip"); ok ($scrip->TemplateObj->Id, "Created the scrip template"); ok ($scrip->ConditionObj->Id, "Created the scrip condition"); ok ($scrip->ActionObj->Id, "Created the scrip action"); my $t = RT::Ticket->new(RT->SystemUser); my($tid, $ttrans, $tmsg) = $t->Create(Subject => "Sample workflow test", Owner => "root", Queue => $q->Id); ok ($tid,$tmsg); my $deps = $t->DependsOn; is ($deps->Count, 1, "The ticket we created depends on one other ticket"); my $dependson= $deps->First->TargetObj; ok ($dependson->Id, "It depends on a real ticket"); is ($dependson->FirstCustomFieldValue('GlobalCF'), 'A Value', 'global custom field was set'); is ($dependson->FirstCustomFieldValue('QueueCF'), 'Another Value', 'queue custom field was set'); unlike ($dependson->Subject, qr/\{/, "The subject doesn't have braces in it. that means we're interpreting expressions"); is ($t->ReferredToBy->Count,1, "It's only referred to by one other ticket"); is ($t->ReferredToBy->First->BaseObj->Id,$t->DependsOn->First->TargetObj->Id, "The same ticket that depends on it refers to it."); use RT::Action::CreateTickets; my $action = RT::Action::CreateTickets->new( CurrentUser => RT->SystemUser); # comma-delimited templates my $commas = <<"EOF"; id,Queue,Subject,Owner,Content ticket1,General,"foo, bar",root,blah ticket2,General,foo bar,root,blah ticket3,General,foo' bar,root,blah'boo ticket4,General,foo' bar,,blah'boo EOF # Comma delimited templates with missing data my $sparse_commas = <<"EOF"; id,Queue,Subject,Owner,Requestor ticket14,General,,,bobby ticket15,General,,,tommy ticket16,General,,suzie,tommy ticket17,General,Foo "bar" baz,suzie,tommy ticket18,General,'Foo "bar" baz',suzie,tommy ticket19,General,'Foo bar' baz,suzie,tommy EOF # tab-delimited templates my $tabs = <<"EOF"; id\tQueue\tSubject\tOwner\tContent ticket10\tGeneral\t"foo' bar"\troot\tblah' ticket11\tGeneral\tfoo, bar\troot\tblah ticket12\tGeneral\tfoo' bar\troot\tblah'boo ticket13\tGeneral\tfoo' bar\t\tblah'boo EOF my %expected; $expected{ticket1} = <Parse(Content =>$commas); $action->Parse(Content =>$sparse_commas); $action->Parse(Content => $tabs); my %got; foreach (@{ $action->{'create_tickets'} }) { $got{$_} = $action->{'templates'}->{$_}; } foreach my $id ( sort keys %expected ) { ok(exists($got{"create-$id"}), "template exists for $id"); is($got{"create-$id"}, $expected{$id}, "template is correct for $id"); } } rt-5.0.1/t/api/web-config.t000644 000765 000024 00000014022 14005011336 016221 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test nodb => 1, tests => 89; sub no_warnings_ok { local $Test::Builder::Level = $Test::Builder::Level + 1; my $option = shift; my $value = shift; my $name = shift; is(warnings_from($option => $value), 0, $name); } sub one_warning_like { local $Test::Builder::Level = $Test::Builder::Level + 1; my $option = shift; my $value = shift; my $regex = shift; my $name = shift; my @w = warnings_from($option => $value); is(@w, 1); like($w[0], $regex, $name); } sub warnings_from { my $option = shift; my $value = shift; my @warnings; local $SIG{__WARN__} = sub { push @warnings, $_[0]; }; RT->Config->Set($option => $value); RT->Config->PostLoadCheck; return @warnings; } # WebPath no_warnings_ok(WebPath => ''); no_warnings_ok(WebPath => '/foo'); no_warnings_ok(WebPath => '/foo/bar'); one_warning_like(WebPath => '/foo/', qr/The WebPath config option requires no trailing slash/); one_warning_like(WebPath => 'foo', qr/The WebPath config option requires a leading slash/); my @w = warnings_from(WebPath => 'foo/'); is(@w, 2); like($w[0], qr/The WebPath config option requires no trailing slash/); like($w[1], qr/The WebPath config option requires a leading slash/); one_warning_like(WebPath => '/foo/bar/', qr/The WebPath config option requires no trailing slash/); one_warning_like(WebPath => 'foo/bar', qr/The WebPath config option requires a leading slash/); @w = warnings_from(WebPath => 'foo/bar/'); is(@w, 2); like($w[0], qr/The WebPath config option requires no trailing slash/); like($w[1], qr/The WebPath config option requires a leading slash/); one_warning_like(WebPath => '/', qr{For the WebPath config option, use the empty string instead of /}); # reinstate a valid WebPath for other tests no_warnings_ok(WebPath => '/rt'); # WebDomain no_warnings_ok(WebDomain => 'example.com'); no_warnings_ok(WebDomain => 'rt.example.com'); no_warnings_ok(WebDomain => 'localhost'); one_warning_like(WebDomain => '', qr{You must set the WebDomain config option}); one_warning_like(WebDomain => 'http://rt.example.com', qr{The WebDomain config option must not contain a scheme \(http://\)}); one_warning_like(WebDomain => 'https://rt.example.com', qr{The WebDomain config option must not contain a scheme \(https://\)}); one_warning_like(WebDomain => 'rt.example.com/path', qr{The WebDomain config option must not contain a path \(/path\)}); one_warning_like(WebDomain => 'rt.example.com/path/more', qr{The WebDomain config option must not contain a path \(/path/more\)}); one_warning_like(WebDomain => 'rt.example.com:80', qr{The WebDomain config option must not contain a port \(80\)}); # reinstate a valid WebDomain for other tests no_warnings_ok(WebDomain => 'rt.example.com'); # WebPort no_warnings_ok(WebDomain => 80); no_warnings_ok(WebDomain => 443); no_warnings_ok(WebDomain => 8888); one_warning_like(WebPort => '', qr{You must set the WebPort config option}); one_warning_like(WebPort => 3.14, qr{The WebPort config option must be an integer}); one_warning_like(WebPort => 'wha?', qr{The WebPort config option must be an integer}); # reinstate a valid WebDomain for other tests no_warnings_ok(WebPort => 443); # WebBaseURL no_warnings_ok(WebBaseURL => 'http://rt.example.com'); no_warnings_ok(WebBaseURL => 'HTTP://rt.example.com', 'uppercase scheme is okay'); no_warnings_ok(WebBaseURL => 'http://rt.example.com:8888', 'nonstandard port is okay'); no_warnings_ok(WebBaseURL => 'https://rt.example.com:8888', 'nonstandard port with https is okay'); one_warning_like(WebBaseURL => '', qr{You must set the WebBaseURL config option}); one_warning_like(WebBaseURL => 'rt.example.com', qr{The WebBaseURL config option must contain a scheme}); one_warning_like(WebBaseURL => 'xtp://rt.example.com', qr{The WebBaseURL config option must contain a scheme \(http or https\)}); one_warning_like(WebBaseURL => 'http://rt.example.com/', qr{The WebBaseURL config option requires no trailing slash}); one_warning_like(WebBaseURL => 'http://rt.example.com/rt', qr{The WebBaseURL config option must not contain a path \(/rt\)}); @w = warnings_from(WebBaseURL => 'http://rt.example.com/rt/'); is(@w, 2); like($w[0], qr{The WebBaseURL config option requires no trailing slash}); like($w[1], qr{The WebBaseURL config option must not contain a path \(/rt/\)}); one_warning_like(WebBaseURL => 'http://rt.example.com/rt/ir', qr{The WebBaseURL config option must not contain a path \(/rt/ir\)}); @w = warnings_from(WebBaseURL => 'http://rt.example.com/rt/ir/'); is(@w, 2); like($w[0], qr{The WebBaseURL config option requires no trailing slash}); like($w[1], qr{The WebBaseURL config option must not contain a path \(/rt/ir/\)}); # reinstate a valid WebBaseURL for other tests no_warnings_ok(WebBaseURL => 'http://rt.example.com'); # WebURL no_warnings_ok(WebURL => 'http://rt.example.com/'); no_warnings_ok(WebURL => 'HTTP://rt.example.com/', 'uppercase scheme is okay'); no_warnings_ok(WebURL => 'http://example.com/rt/'); no_warnings_ok(WebURL => 'http://example.com/rt/ir/'); no_warnings_ok(WebURL => 'http://rt.example.com:8888/', 'nonstandard port is okay'); no_warnings_ok(WebURL => 'https://rt.example.com:8888/', 'nonstandard port with https is okay'); one_warning_like(WebURL => '', qr{You must set the WebURL config option}); @w = warnings_from(WebURL => 'rt.example.com'); is(@w, 2); like($w[0], qr{The WebURL config option must contain a scheme}); like($w[1], qr{The WebURL config option requires a trailing slash}); one_warning_like(WebURL => 'http://rt.example.com', qr{The WebURL config option requires a trailing slash}); one_warning_like(WebURL => 'xtp://example.com/rt/', qr{The WebURL config option must contain a scheme \(http or https\)}); one_warning_like(WebURL => 'http://rt.example.com/rt', qr{The WebURL config option requires a trailing slash}); one_warning_like(WebURL => 'http://rt.example.com/rt/ir', qr{The WebURL config option requires a trailing slash}); # reinstate a valid WebURL for other tests no_warnings_ok(WebURL => 'http://rt.example.com/rt/'); rt-5.0.1/t/api/transaction.t000644 000765 000024 00000003153 14005011336 016531 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => undef; use Test::Warn; use_ok ('RT::Transaction'); { my $u = RT::User->new(RT->SystemUser); $u->Load("root"); ok ($u->Id, "Found the root user"); ok(my $t = RT::Ticket->new(RT->SystemUser)); my ($id, $msg) = $t->Create( Queue => 'General', Subject => 'Testing', Owner => $u->Id ); ok($id, "Create new ticket $id"); isnt($id , 0); my $txn = RT::Transaction->new(RT->SystemUser); my ($txn_id, $txn_msg) = $txn->Create( Type => 'AddLink', Field => 'RefersTo', Ticket => $id, NewValue => 'ticket 42', ); ok( $txn_id, "Created transaction $txn_id: $txn_msg"); my $brief; warning_like { $brief = $txn->BriefDescription } qr/Could not determine a URI scheme/, "Caught URI warning"; is( $brief, 'Reference to ticket 42 added', "Got string description: $brief"); $txn = RT::Transaction->new(RT->SystemUser); ($txn_id, $txn_msg) = $txn->Create( Type => 'DeleteLink', Field => 'RefersTo', Ticket => $id, OldValue => 'ticket 42', ); ok( $txn_id, "Created transaction $txn_id: $txn_msg"); warning_like { $brief = $txn->BriefDescription } qr/Could not determine a URI scheme/, "Caught URI warning"; is( $brief, 'Reference to ticket 42 deleted', "Got string description: $brief"); } done_testing; rt-5.0.1/t/api/uri-fsck_com_rt.t000644 000765 000024 00000001404 14005011336 017267 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => 8; use_ok("RT::URI::fsck_com_rt"); my $uri = RT::URI::fsck_com_rt->new(RT->SystemUser); my $t1 = RT::Ticket->new(RT->SystemUser); my ($id,$trans,$msg) =$t1->Create (Queue => 'general', Subject => 'Requestor test one', ); ok ($id, $msg); ok(ref($uri)); ok (UNIVERSAL::isa($uri,"RT::URI::fsck_com_rt"), "It's an RT::URI::fsck_com_rt"); ok ($uri->isa('RT::URI::base'), "It's an RT::URI::base"); ok ($uri->isa('RT::Base'), "It's an RT::Base"); is ($uri->LocalURIPrefix , 'fsck.com-rt://'.RT->Config->Get('Organization')); my $ticket = RT::Ticket->new(RT->SystemUser); $ticket->Load(1); $uri = RT::URI::fsck_com_rt->new($ticket->CurrentUser); is($uri->LocalURIPrefix. "/ticket/1" , $uri->URIForObject($ticket)); rt-5.0.1/t/api/groups.t000644 000765 000024 00000011007 14005011336 015520 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => 27; RT::Group->AddRight( General => 'RTxGroupRight' => 'Just a right for testing rights', ); { my $g = RT::Group->new(RT->SystemUser); my ($id, $msg) = $g->CreateUserDefinedGroup(Name => 'GroupsNotEqualTest'); ok ($id, "created group #". $g->id) or diag("error: $msg"); my $groups = RT::Groups->new(RT->SystemUser); $groups->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => $g->id ); $groups->LimitToUserDefinedGroups(); my $bug = grep $_->id == $g->id, @{$groups->ItemsArrayRef}; ok (!$bug, "didn't find group"); } { my $u = RT::User->new(RT->SystemUser); my ($id, $msg) = $u->Create( Name => 'Membertests'. $$ ); ok ($id, 'created user') or diag "error: $msg"; my $g = RT::Group->new(RT->SystemUser); ($id, $msg) = $g->CreateUserDefinedGroup(Name => 'Membertests'); ok ($id, $msg); my ($aid, $amsg) =$g->AddMember($u->id); ok ($aid, $amsg); ok($g->HasMember($u->PrincipalObj),"G has member u"); my $groups = RT::Groups->new(RT->SystemUser); $groups->LimitToUserDefinedGroups(); $groups->WithMember(PrincipalId => $u->id); is ($groups->Count , 1,"found the 1 group - " . $groups->Count); is ($groups->First->Id , $g->Id, "it's the right one"); } no warnings qw/redefine once/; my $q = RT::Queue->new(RT->SystemUser); my ($id, $msg) =$q->Create( Name => 'GlobalACLTest'); ok ($id, $msg); my $testuser = RT::User->new(RT->SystemUser); ($id,$msg) = $testuser->Create(Name => 'JustAnAdminCc'); ok ($id,$msg); my $global_admin_cc = RT->System->RoleGroup( 'AdminCc' ); ok($global_admin_cc->id, "Found the global admincc group"); my $groups = RT::Groups->new(RT->SystemUser); $groups->WithRight(Right => 'OwnTicket', Object => $q); is($groups->Count, 1); ($id, $msg) = $global_admin_cc->PrincipalObj->GrantRight(Right =>'OwnTicket', Object=> RT->System); ok ($id,$msg); ok (!$testuser->HasRight(Object => $q, Right => 'OwnTicket') , "The test user does not have the right to own tickets in the test queue"); ($id, $msg) = $q->AddWatcher(Type => 'AdminCc', PrincipalId => $testuser->id); ok($id,$msg); ok ($testuser->HasRight(Object => $q, Right => 'OwnTicket') , "The test user does have the right to own tickets now. thank god."); $groups = RT::Groups->new(RT->SystemUser); $groups->WithRight(Right => 'OwnTicket', Object => $q); ok ($id,$msg); is($groups->Count, 3); my $RTxGroup = RT::Group->new(RT->SystemUser); ($id, $msg) = $RTxGroup->CreateUserDefinedGroup( Name => 'RTxGroup', Description => "RTx extension group"); ok ($id,$msg); is ($RTxGroup->id, $id, "group loaded"); my $RTxSysObj = {}; bless $RTxSysObj, 'RTx::System'; *RTx::System::Id = sub { 1; }; *RTx::System::id = *RTx::System::Id; my $ace = RT::Record->new(RT->SystemUser); $ace->Table('ACL'); $ace->_BuildTableAttributes unless ($RT::Record::_TABLE_ATTR->{ref($ace)}); ($id, $msg) = $ace->Create( PrincipalId => $RTxGroup->id, PrincipalType => 'Group', RightName => 'RTxGroupRight', ObjectType => 'RTx::System', ObjectId => 1); ok ($id, "ACL for RTxSysObj created"); my $RTxObj = {}; bless $RTxObj, 'RTx::System::Record'; *RTx::System::Record::Id = sub { 4; }; *RTx::System::Record::id = *RTx::System::Record::Id; $groups = RT::Groups->new(RT->SystemUser); $groups->WithRight(Right => 'RTxGroupRight', Object => $RTxSysObj); is($groups->Count, 1, "RTxGroupRight found for RTxSysObj"); $groups = RT::Groups->new(RT->SystemUser); $groups->WithRight(Right => 'RTxGroupRight', Object => $RTxObj); is($groups->Count, 0, "RTxGroupRight not found for RTxObj"); $groups = RT::Groups->new(RT->SystemUser); $groups->WithRight(Right => 'RTxGroupRight', Object => $RTxObj, EquivObjects => [ $RTxSysObj ]); is($groups->Count, 1, "RTxGroupRight found for RTxObj using EquivObjects"); $ace = RT::Record->new(RT->SystemUser); $ace->Table('ACL'); $ace->_BuildTableAttributes unless ($RT::Record::_TABLE_ATTR->{ref($ace)}); ($id, $msg) = $ace->Create( PrincipalId => $RTxGroup->id, PrincipalType => 'Group', RightName => 'RTxGroupRight', ObjectType => 'RTx::System::Record', ObjectId => 5 ); ok ($id, "ACL for RTxObj created"); my $RTxObj2 = {}; bless $RTxObj2, 'RTx::System::Record'; *RTx::System::Record::Id = sub { 5; }; $groups = RT::Groups->new(RT->SystemUser); $groups->WithRight(Right => 'RTxGroupRight', Object => $RTxObj2); is($groups->Count, 1, "RTxGroupRight found for RTxObj2"); $groups = RT::Groups->new(RT->SystemUser); $groups->WithRight(Right => 'RTxGroupRight', Object => $RTxObj2, EquivObjects => [ $RTxSysObj ]); is($groups->Count, 1, "RTxGroupRight found for RTxObj2"); rt-5.0.1/t/api/queue.t000644 000765 000024 00000005653 14005011336 015337 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test nodata => 1, tests => undef, config => <<'CONFIG'; Set( %ServiceAgreements, ( Default => 'standard', Levels => { 'standard' => { Starts => { RealMinutes => 0 }, Resolve => { RealMinutes => 8*60 }, }, 'urgent' => { Starts => { RealMinutes => 0 }, Resolve => { RealMinutes => 2*60 }, }, }, )); CONFIG { use RT::Queue; } { my $q = RT::Queue->new(RT->SystemUser); is($q->IsValidStatus('new'), 1, 'New is a valid status'); is($q->IsValidStatus('f00'), 0, 'f00 is not a valid status'); } { my $q = RT::Queue->new(RT->SystemUser); is($q->IsActiveStatus('new'), 1, 'New is a Active status'); is($q->IsActiveStatus('rejected'), 0, 'Rejected is an inactive status'); is($q->IsActiveStatus('f00'), 0, 'f00 is not a Active status'); } { my $q = RT::Queue->new(RT->SystemUser); is($q->IsInactiveStatus('new'), 0, 'New is a Active status'); is($q->IsInactiveStatus('rejected'), 1, 'rejeected is an Inactive status'); is($q->IsInactiveStatus('f00'), 0, 'f00 is not a Active status'); } { my $queue = RT::Queue->new(RT->SystemUser); my ($id, $val) = $queue->Create( Name => 'Test1'); ok($id, $val); ($id, $val) = $queue->Create( Name => '66'); ok(!$id, $val); } { my $Queue = RT::Queue->new(RT->SystemUser); my ($id, $msg) = $Queue->Create(Name => "Foo"); ok ($id, "Foo $id was created"); ok(my $group = $Queue->RoleGroup('Requestor')); ok ($group->Id, "Found the requestors object for this Queue"); { my ($status, $msg) = $Queue->AddWatcher(Type => 'Cc', Email => 'bob@fsck.com'); ok ($status, "Added bob at fsck.com as a requestor") or diag "error: $msg"; } ok(my $bob = RT::User->new(RT->SystemUser), "Creating a bob rt::user"); $bob->LoadByEmail('bob@fsck.com'); ok($bob->Id, "Found the bob rt user"); ok ($Queue->IsWatcher(Type => 'Cc', PrincipalId => $bob->PrincipalId), "The Queue actually has bob at fsck.com as a requestor"); { my ($status, $msg) = $Queue->DeleteWatcher(Type =>'Cc', Email => 'bob@fsck.com'); ok ($status, "Deleted bob from Ccs") or diag "error: $msg"; ok (!$Queue->IsWatcher(Type => 'Cc', PrincipalId => $bob->PrincipalId), "The Queue no longer has bob at fsck.com as a requestor"); } $group = $Queue->RoleGroup('Cc'); ok ($group->Id, "Found the cc object for this Queue"); $group = $Queue->RoleGroup('AdminCc'); ok ($group->Id, "Found the AdminCc object for this Queue"); } { my $NoSLA = RT::Queue->new(RT->SystemUser); my ($id, $msg) = $NoSLA->Create(Name => "NoSLA"); ok($id, "created queue NoSLA"); is($NoSLA->SLA, undef, 'No SLA for NoSLA'); my $WithSLA = RT::Queue->new(RT->SystemUser); ($id, $msg) = $WithSLA->Create(Name => "WithSLA", SLA => 'urgent'); ok($id, "created queue WithSLA"); is($WithSLA->SLA, 'urgent', 'SLA is set'); $WithSLA->SetSLA('standard'); is($WithSLA->SLA, 'standard', 'SLA is updated'); } done_testing; rt-5.0.1/t/api/db_indexes.t000644 000765 000024 00000011450 14005011336 016307 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::Warn; use RT::Test tests => undef; my $handle = $RT::Handle; my $db_type = RT->Config->Get('DatabaseType'); # Pg,Oracle needs DBA RT::Test::__reconnect_rt('as dba'); ok( $handle->dbh->do("ALTER SESSION SET CURRENT_SCHEMA=". RT->Config->Get('DatabaseUser') ) ) if $db_type eq 'Oracle'; note "test handle->Indexes method"; { my %indexes = $handle->Indexes; ok grep $_ eq 'tickets1', @{ $indexes{'tickets'} }; ok grep $_ eq 'tickets2', @{ $indexes{'tickets'} }; ok grep $_ eq 'users1', @{ $indexes{'users'} }; ok grep $_ eq 'users4', @{ $indexes{'users'} }; } note "test handle->DropIndex method"; { my ($status, $msg) = $handle->DropIndex( Table => 'Tickets', Name => 'Tickets1' ); ok $status, $msg; my %indexes = $handle->Indexes; ok !grep $_ eq 'tickets1', @{ $indexes{'tickets'} }; ($status, $msg) = $handle->DropIndex( Table => 'Tickets', Name => 'Tickets1' ); ok !$status, $msg; } note "test handle->DropIndexIfExists method"; { my ($status, $msg) = $handle->DropIndexIfExists( Table => 'Tickets', Name => 'Tickets2' ); ok $status, $msg; my %indexes = $handle->Indexes; ok !grep $_ eq 'tickets2', @{ $indexes{'tickets'} }; ($status, $msg) = $handle->DropIndexIfExists( Table => 'Tickets', Name => 'Tickets2' ); ok $status, $msg; } note "test handle->IndexInfo method"; { if ($db_type ne 'Oracle' && $db_type ne 'mysql') { my %res = $handle->IndexInfo( Table => 'Attachments', Name => 'Attachments1' ); is_deeply( \%res, { Table => 'attachments', Name => 'attachments1', Unique => 0, Functional => 0, Columns => ['parent'] } ); } else { my %res = $handle->IndexInfo( Table => 'Attachments', Name => 'Attachments2' ); is_deeply( \%res, { Table => 'attachments', Name => 'attachments2', Unique => 0, Functional => 0, Columns => ['transactionid'] } ); } my %res = $handle->IndexInfo( Table => 'GroupMembers', Name => 'GroupMembers1' ); is_deeply( \%res, { Table => 'groupmembers', Name => 'groupmembers1', Unique => 1, Functional => 0, Columns => ['groupid', 'memberid'] } ); if ( $db_type eq 'Pg' || $db_type eq 'Oracle' ) { %res = $handle->IndexInfo( Table => 'Queues', Name => 'Queues1' ); is_deeply( \%res, { Table => 'queues', Name => 'queues1', Unique => 1, Functional => 1, Columns => ['name'], CaseInsensitive => { name => 1 }, } ); } } note "test ->CreateIndex and ->IndexesThatBeginWith methods"; { { my ($name, $msg) = $handle->CreateIndex( Table => 'Users', Name => 'test_users1', Columns => ['Organization'], ); ok $name, $msg; } { my ($name, $msg) = $handle->CreateIndex( Table => 'Users', Name => 'test_users2', Columns => ['Organization', 'Name'], ); ok $name, $msg; } my @list = $handle->IndexesThatBeginWith( Table => 'Users', Columns => ['Organization'] ); is_deeply([sort map $_->{Name}, @list], [qw(test_users1 test_users2)]); my ($status, $msg) = $handle->DropIndex( Table => 'Users', Name => 'test_users1' ); ok $status, $msg; ($status, $msg) = $handle->DropIndex( Table => 'Users', Name => 'test_users2' ); ok $status, $msg; } note "Test some cases sensitivity aspects"; { { my %res = $handle->IndexInfo( Table => 'groupmembers', Name => 'groupmembers1' ); is_deeply( \%res, { Table => 'groupmembers', Name => 'groupmembers1', Unique => 1, Functional => 0, Columns => ['groupid', 'memberid'] } ); } { my ($status, $msg) = $handle->DropIndex( Table => 'groupmembers', Name => 'groupmembers1' ); ok $status, $msg; my %indexes = $handle->Indexes; ok !grep $_ eq 'groupmembers1', @{ $indexes{'groupmembers'} }; } { my ($name, $msg) = $handle->CreateIndex( Table => 'groupmembers', Name => 'groupmembers1', Unique => 1, Columns => ['groupid', 'memberid'] ); ok $name, $msg; my %indexes = $handle->Indexes; ok grep $_ eq 'groupmembers1', @{ $indexes{'groupmembers'} }; } { my ($status, $msg) = $handle->DropIndexIfExists( Table => 'groupmembers', Name => 'groupmembers1' ); ok $status, $msg; my %indexes = $handle->Indexes; ok !grep $_ eq 'groupmembers1', @{ $indexes{'groupmembers'} }; } } done_testing(); rt-5.0.1/t/api/template.t000644 000765 000024 00000013750 14005011336 016023 0ustar00sunnavystaff000000 000000 use warnings; use strict; use RT::Test tests => 37; use_ok('RT::Template'); my $queue = RT::Test->load_or_create_queue( Name => 'Templates' ); ok $queue && $queue->id, "loaded or created a queue"; my $alt_queue = RT::Test->load_or_create_queue( Name => 'Alternative' ); ok $alt_queue && $alt_queue->id, 'loaded or created queue'; { my $template = RT::Template->new(RT->SystemUser); isa_ok($template, 'RT::Template'); } { my $template = RT::Template->new( RT->SystemUser ); my ($val,$msg) = $template->Create( Queue => $queue->id, Name => 'Test', 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, 'Test'; 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"; } note "can not create template w/o Name"; { clean_templates( Queue => $queue->id ); my $template = RT::Template->new( RT->SystemUser ); my ($val,$msg) = $template->Create( Queue => $queue->id ); ok(!$val,$msg); } note "can not create template with duplicate name"; { clean_templates( Queue => $queue->id ); my $template = RT::Template->new( RT->SystemUser ); my ($val,$msg) = $template->Create( Queue => $queue->id, Name => 'Test' ); ok($val,$msg); ($val,$msg) = $template->Create( Queue => $queue->id, Name => 'Test' ); ok(!$val,$msg); } note "change template's name"; { clean_templates( Queue => $queue->id ); my $template = RT::Template->new( RT->SystemUser ); my ($val,$msg) = $template->Create( Queue => $queue->id, Name => 'Test' ); ok($val,$msg); ($val,$msg) = $template->SetName( 'Some' ); ok($val,$msg); is $template->Name, 'Some'; } note "can not change name to empty"; { clean_templates( Queue => $queue->id ); my $template = RT::Template->new( RT->SystemUser ); my ($val,$msg) = $template->Create( Queue => $queue->id, Name => 'Test' ); ok($val,$msg); ($val,$msg) = $template->Create( Queue => $queue->id, Name => '' ); ok(!$val,$msg); ($val,$msg) = $template->Create( Queue => $queue->id, Name => undef ); ok(!$val,$msg); } note "can not change name to duplicate"; { clean_templates( Queue => $queue->id ); my $template = RT::Template->new( RT->SystemUser ); my ($val,$msg) = $template->Create( Queue => $queue->id, Name => 'Test' ); ok($val,$msg); ($val,$msg) = $template->Create( Queue => $queue->id, Name => 'Some' ); ok($val,$msg); } note "changing queue of template is not implemented"; { clean_templates( Queue => $queue->id ); my $template = RT::Template->new( RT->SystemUser ); my ($val,$msg) = $template->Create( Queue => $queue->id, Name => 'Test' ); ok($val,$msg); ($val,$msg) = $template->SetQueue( $alt_queue->id ); ok(!$val,$msg); } note "make sure template can not be deleted if it has scrips"; { clean_templates( Queue => $queue->id ); my $template = RT::Template->new( RT->SystemUser ); my ($val,$msg) = $template->Create( Queue => $queue->id, Name => 'Test' ); ok($val,$msg); my $scrip = RT::Scrip->new( RT->SystemUser ); ($val,$msg) = $scrip->Create( Queue => $queue->id, ScripCondition => "On Create", ScripAction => 'Autoreply To Requestors', Template => $template->Name, ); ok($val, $msg); ($val, $msg) = $template->Delete; ok(!$val,$msg); } note "make sure template can be deleted if it's an override"; { clean_templates( Queue => $queue->id ); my $template = RT::Template->new( RT->SystemUser ); my ($val,$msg) = $template->Create( Queue => $queue->id, Name => 'Overrided' ); ok($val,$msg); $template = RT::Template->new( RT->SystemUser ); ($val,$msg) = $template->Create( Queue => 0, Name => 'Overrided' ); ok($val,$msg); my $scrip = RT::Scrip->new( RT->SystemUser ); ($val,$msg) = $scrip->Create( Queue => $queue->id, ScripCondition => "On Create", ScripAction => 'Autoreply To Requestors', Template => $template->Name, ); ok($val, $msg); ($val, $msg) = $template->Delete; ok($val,$msg); } note "make sure template can be deleted if it has an override"; { clean_templates( Queue => $queue->id ); my $template = RT::Template->new( RT->SystemUser ); my ($val,$msg) = $template->Create( Queue => 0, Name => 'Overrided' ); ok($val,$msg); $template = RT::Template->new( RT->SystemUser ); ($val,$msg) = $template->Create( Queue => $queue->id, Name => 'Overrided' ); ok($val,$msg); my $scrip = RT::Scrip->new( RT->SystemUser ); ($val,$msg) = $scrip->Create( Queue => $queue->id, ScripCondition => "On Create", ScripAction => 'Autoreply To Requestors', Template => $template->Name, ); ok($val, $msg); ($val, $msg) = $template->Delete; ok($val,$msg); } { my $t = RT::Template->new(RT->SystemUser); $t->Create(Name => "Foo", Queue => $queue->id); my $t2 = RT::Template->new(RT->Nobody); $t2->Load($t->Id); ok($t2->QueueObj->id, "Got the template's queue objet"); } sub clean_templates { my %args = (@_); my $templates = RT::Templates->new( RT->SystemUser ); $templates->Limit( FIELD => 'Queue', VALUE => $args{'Queue'} ) if defined $args{'Queue'}; $templates->Limit( FIELD => 'Name', VALUE => $_ ) foreach ref $args{'Name'}? @{$args{'Name'}} : ($args{'Name'}||()); while ( my $t = $templates->Next ) { my ($status) = $t->Delete; unless ( $status ) { $_->Delete foreach @{ $t->UsedBy->ItemsArrayRef }; $t->Delete; } } } rt-5.0.1/t/ticket/action_linear_escalate.t000644 000765 000024 00000006415 14005011336 021372 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => 17; my ($id, $msg); my $RecordTransaction; my $UpdateLastUpdated; use_ok('RT::Action::LinearEscalate'); my $q = RT::Test->load_or_create_queue( Name => 'Regression' ); ok $q && $q->id, 'loaded or created queue'; # rt-cron-tool uses Gecos name to get rt user, so we'd better create one my $gecos = RT::Test->load_or_create_user( Name => 'gecos', Password => 'password', Gecos => (getpwuid($<))[0], ); ok $gecos && $gecos->id, 'loaded or created gecos user'; # get rid of all right permissions $gecos->PrincipalObj->GrantRight( Right => 'SuperUser' ); my $user = RT::Test->load_or_create_user( Name => 'user', Password => 'password', ); ok $user && $user->id, 'loaded or created user'; $user->PrincipalObj->GrantRight( Right => 'SuperUser' ); my $current_user = RT::CurrentUser->new(RT->SystemUser); ($id, $msg) = $current_user->Load($user->id); ok( $id, "Got current user? $msg" ); #defaults $RecordTransaction = 0; $UpdateLastUpdated = 1; my $ticket2 = create_ticket_as_ok($current_user); escalate_ticket_ok($ticket2); ok( $ticket2->LastUpdatedBy != $user->id, "Set LastUpdated" ); ok( $ticket2->Transactions->Last->Type =~ /Create/i, "Did not record a transaction" ); $RecordTransaction = 1; $UpdateLastUpdated = 1; my $ticket1 = create_ticket_as_ok($current_user); escalate_ticket_ok($ticket1); ok( $ticket1->LastUpdatedBy != $user->id, "Set LastUpdated" ); ok( $ticket1->Transactions->Last->Type !~ /Create/i, "Recorded a transaction" ); $RecordTransaction = 0; $UpdateLastUpdated = 0; my $ticket3 = create_ticket_as_ok($current_user); escalate_ticket_ok($ticket3); ok( $ticket3->LastUpdatedBy == $user->id, "Did not set LastUpdated" ); ok( $ticket3->Transactions->Last->Type =~ /Create/i, "Did not record a transaction" ); sub create_ticket_as_ok { my $user = shift; my $created = RT::Date->new(RT->SystemUser); $created->Unix(time() - ( 7 * 24 * 60**2 )); my $due = RT::Date->new(RT->SystemUser); $due->Unix(time() + ( 7 * 24 * 60**2 )); my $ticket = RT::Ticket->new($user); ($id, $msg) = $ticket->Create( Queue => $q->id, Subject => "Escalation test", Priority => 0, InitialPriority => 0, FinalPriority => 50, ); ok($id, "Created ticket? ".$id); $ticket->__Set( Field => 'Created', Value => $created->ISO, ); $ticket->__Set( Field => 'Due', Value => $due->ISO, ); return $ticket; } sub escalate_ticket_ok { my $ticket = shift; my $id = $ticket->id; print "$RT::BinPath/rt-crontool --search RT::Search::FromSQL --search-arg \"id = @{[$id]}\" --action RT::Action::LinearEscalate --action-arg \"RecordTransaction:$RecordTransaction; UpdateLastUpdated:$UpdateLastUpdated\"\n"; print STDERR `$RT::BinPath/rt-crontool --search RT::Search::FromSQL --search-arg "id = @{[$id]}" --action RT::Action::LinearEscalate --action-arg "RecordTransaction:$RecordTransaction; UpdateLastUpdated:$UpdateLastUpdated"`; $ticket->Load($id); # reload, because otherwise we get the cached value ok( $ticket->Priority != 0, "Escalated ticket" ); } rt-5.0.1/t/ticket/search_by_links.t000644 000765 000024 00000012330 14005011336 020052 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => 100; use RT::Ticket; my $q = RT::Test->load_or_create_queue( Name => 'Regression' ); ok $q && $q->id, 'loaded or created queue'; my ($total, @tickets, %test) = (0, ()); sub run_tests { my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets; foreach my $key ( sort keys %test ) { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL( "( $query_prefix ) AND ( $key )" ); my $error = 0; my $count = 0; $count++ foreach grep $_, values %{ $test{$key} }; is($tix->Count, $count, "found correct number of ticket(s) by '$key'") or $error = 1; my $good_tickets = 1; while ( my $ticket = $tix->Next ) { next if $test{$key}->{ $ticket->Subject }; diag $ticket->Subject ." ticket has been found when it's not expected"; $good_tickets = 0; } ok( $good_tickets, "all tickets are good with '$key'" ) or $error = 1; diag "Wrong SQL query for '$key':". $tix->BuildSelectQuery if $error; } } # simple set with "no links", "parent and child" @tickets = RT::Test->create_tickets( { Queue => $q->id }, { Subject => '-', }, { Subject => 'p', }, { Subject => 'c', MemberOf => -1 }, ); $total += @tickets; %test = ( 'Linked IS NOT NULL' => { '-' => 0, c => 1, p => 1 }, 'Linked IS NULL' => { '-' => 1, c => 0, p => 0 }, 'LinkedTo IS NOT NULL' => { '-' => 0, c => 1, p => 0 }, 'LinkedTo IS NULL' => { '-' => 1, c => 0, p => 1 }, 'LinkedFrom IS NOT NULL' => { '-' => 0, c => 0, p => 1 }, 'LinkedFrom IS NULL' => { '-' => 1, c => 1, p => 0 }, 'HasMember IS NOT NULL' => { '-' => 0, c => 0, p => 1 }, 'HasMember IS NULL' => { '-' => 1, c => 1, p => 0 }, 'MemberOf IS NOT NULL' => { '-' => 0, c => 1, p => 0 }, 'MemberOf IS NULL' => { '-' => 1, c => 0, p => 1 }, 'RefersTo IS NOT NULL' => { '-' => 0, c => 0, p => 0 }, 'RefersTo IS NULL' => { '-' => 1, c => 1, p => 1 }, 'Linked = '. $tickets[0]->id => { '-' => 0, c => 0, p => 0 }, 'Linked != '. $tickets[0]->id => { '-' => 1, c => 1, p => 1 }, 'MemberOf = '. $tickets[1]->id => { '-' => 0, c => 1, p => 0 }, 'MemberOf != '. $tickets[1]->id => { '-' => 1, c => 0, p => 1 }, ); { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '". $q->id ."'"); is($tix->Count, $total, "found $total tickets"); } run_tests(); # make sure search by id is on LocalXXX columns { my $tickets = RT::Tickets->new( RT->SystemUser ); $tickets->FromSQL('MemberOf = '. $tickets[0]->id); like $tickets->BuildSelectQuery, qr/LocalBase/; like $tickets->BuildSelectQuery, qr/LocalTarget/; } # another set with tests of combinations searches @tickets = RT::Test->create_tickets( { Queue => $q->id }, { Subject => '-', }, { Subject => 'p', }, { Subject => 'rp', RefersTo => -1 }, { Subject => 'c', MemberOf => -2 }, { Subject => 'rc1', RefersTo => -1 }, { Subject => 'rc2', RefersTo => -2 }, ); $total += @tickets; my $pid = $tickets[1]->id; %test = ( 'RefersTo IS NOT NULL' => { '-' => 0, c => 0, p => 0, rp => 1, rc1 => 1, rc2 => 1 }, 'RefersTo IS NULL' => { '-' => 1, c => 1, p => 1, rp => 0, rc1 => 0, rc2 => 0 }, 'RefersTo IS NOT NULL AND MemberOf IS NOT NULL' => { '-' => 0, c => 0, p => 0, rp => 0, rc1 => 0, rc2 => 0 }, 'RefersTo IS NOT NULL AND MemberOf IS NULL' => { '-' => 0, c => 0, p => 0, rp => 1, rc1 => 1, rc2 => 1 }, 'RefersTo IS NULL AND MemberOf IS NOT NULL' => { '-' => 0, c => 1, p => 0, rp => 0, rc1 => 0, rc2 => 0 }, 'RefersTo IS NULL AND MemberOf IS NULL' => { '-' => 1, c => 0, p => 1, rp => 0, rc1 => 0, rc2 => 0 }, 'RefersTo IS NOT NULL OR MemberOf IS NOT NULL' => { '-' => 0, c => 1, p => 0, rp => 1, rc1 => 1, rc2 => 1 }, 'RefersTo IS NOT NULL OR MemberOf IS NULL' => { '-' => 1, c => 0, p => 1, rp => 1, rc1 => 1, rc2 => 1 }, 'RefersTo IS NULL OR MemberOf IS NOT NULL' => { '-' => 1, c => 1, p => 1, rp => 0, rc1 => 0, rc2 => 0 }, 'RefersTo IS NULL OR MemberOf IS NULL' => { '-' => 1, c => 1, p => 1, rp => 1, rc1 => 1, rc2 => 1 }, "RefersTo = $pid AND MemberOf = $pid" => { '-' => 0, c => 0, p => 0, rp => 0, rc1 => 0, rc2 => 0 }, "RefersTo = $pid AND MemberOf != $pid" => { '-' => 0, c => 0, p => 0, rp => 1, rc1 => 0, rc2 => 0 }, "RefersTo != $pid AND MemberOf = $pid" => { '-' => 0, c => 1, p => 0, rp => 0, rc1 => 0, rc2 => 0 }, "RefersTo != $pid AND MemberOf != $pid" => { '-' => 1, c => 0, p => 1, rp => 0, rc1 => 1, rc2 => 1 }, "RefersTo = $pid OR MemberOf = $pid" => { '-' => 0, c => 1, p => 0, rp => 1, rc1 => 0, rc2 => 0 }, "RefersTo = $pid OR MemberOf != $pid" => { '-' => 1, c => 0, p => 1, rp => 1, rc1 => 1, rc2 => 1 }, "RefersTo != $pid OR MemberOf = $pid" => { '-' => 1, c => 1, p => 1, rp => 0, rc1 => 1, rc2 => 1 }, "RefersTo != $pid OR MemberOf != $pid" => { '-' => 1, c => 1, p => 1, rp => 1, rc1 => 1, rc2 => 1 }, ); { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '". $q->id ."'"); is($tix->Count, $total, "found $total tickets"); } run_tests(); @tickets = (); rt-5.0.1/t/ticket/search_by_watcher_group.t000644 000765 000024 00000004211 14005011336 021602 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use Test::Warn; my $q = RT::Test->load_or_create_queue( Name => 'General' ); ok $q && $q->id, 'loaded or created queue'; my $queue = $q->Name; my $group; { $group = RT::Group->new( RT->SystemUser ); my ($id, $msg) = $group->CreateUserDefinedGroup( Name => 'Test' ); ok $id, "$msg"; } my $root = RT::Test->load_or_create_user( Name => 'root', MemberOf => $group->id ); ok $root && $root->id; RT::Test->create_tickets( { Queue => $q, }, { Subject => '-', }, { Subject => 'o', Owner => $root->id }, { Subject => 'r', Requestor => $root->id }, { Subject => 'c', Cc => $root->id }, { Subject => 'a', AdminCc => $root->id }, ); run_tests( 'OwnerGroup = "Test"' => { '-' => 0, o => 1, r => 0, c => 0, a => 0 }, 'RequestorGroup = "Test"' => { '-' => 0, o => 0, r => 1, c => 0, a => 0 }, 'CCGroup = "Test"' => { '-' => 0, o => 0, r => 0, c => 1, a => 0 }, 'AdminCCGroup = "Test"' => { '-' => 0, o => 0, r => 0, c => 0, a => 1 }, 'WatcherGroup = "Test"' => { '-' => 0, o => 1, r => 1, c => 1, a => 1 }, ); warning_like { my $tickets = RT::Tickets->new( RT->SystemUser ); my ($status, $msg) = $tickets->FromSQL('OwnerGroup != "Test"'); ok !$status, "incorrect op: $msg"; } qr{Invalid OwnerGroup Op: !=}; done_testing(); sub run_tests { my @test = @_; while ( my ($query, $checks) = splice @test, 0, 2 ) { run_test( $query, %$checks ); } } sub run_test { my ($query, %checks) = @_; my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL($query); my $error = 0; my $count = 0; $count++ foreach grep $_, values %checks; is($tix->Count, $count, "found correct number of ticket(s) by '$query'") or $error = 1; my $good_tickets = ($tix->Count == $count); while ( my $ticket = $tix->Next ) { next if $checks{ $ticket->Subject }; diag $ticket->Subject ." ticket has been found when it's not expected"; $good_tickets = 0; } ok( $good_tickets, "all tickets are good with '$query'" ) or $error = 1; diag "Wrong SQL query for '$query':". $tix->BuildSelectQuery if $error; } rt-5.0.1/t/ticket/search_by_cf_date.t000644 000765 000024 00000004472 14005011336 020327 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::MockTime 'set_fixed_time'; use RT::Test nodata => 1, tests => undef; my $queue = RT::Test->load_or_create_queue( Name => 'General' ); my $cf = RT::Test->load_or_create_custom_field( Name => 'test_cf', Queue => $queue->id, Type => 'Date' ); my $cfid = $cf->id; set_fixed_time("2019-04-12T00:00:00Z"); my @tickets = RT::Test->create_tickets( { Queue => $queue->Name }, { Subject => 'Past date ticket', "CustomField-$cfid" => '2019-04-01' }, { Subject => 'Future date ticket', "CustomField-$cfid" => '2020-01-01' }, ); my $tickets = RT::Tickets->new( RT->SystemUser ); $tickets->FromSQL(q{Queue = 'General' AND CF.test_cf < 'today'}); is( $tickets->Count, 1, 'Found 1 ticket' ); is( $tickets->First->id, $tickets[0]->id, 'Found the past ticket' ); $tickets->FromSQL(q{Queue = 'General' AND CF.test_cf > 'today'}); is( $tickets->Count, 1, 'Found 1 ticket' ); is( $tickets->First->id, $tickets[1]->id, 'Found the future ticket' ); my $alice = RT::Test->load_or_create_user( Name => 'alice' ); $alice->PrincipalObj->GrantRight( Object => $queue, Right => 'ShowTicket' ); my $current_alice = RT::CurrentUser->new( RT->SystemUser ); $current_alice->Load('alice'); $tickets = RT::Tickets->new($current_alice); $tickets->FromSQL(q{Queue = 'General' AND CF.test_cf < 'today'}); TODO: { local $TODO = 'Do not filter by cfs user lacks SeeCustomField'; is( $tickets->Count, 2, 'Found 2 tickets' ); is( scalar @{ $tickets->ItemsArrayRef }, 2, 'Found 2 tickets' ); } $alice->PrincipalObj->GrantRight( Object => $queue, Right => 'SeeCustomField' ); $tickets->FromSQL(q{Queue = 'General' AND CF.test_cf < 'today'}); is( $tickets->Count, 1, 'Found 1 ticket' ); is( $tickets->First->id, $tickets[0]->id, 'Found the past ticket' ); $tickets->FromSQL(q{Queue = 'General' AND CF.test_cf > 'today'}); is( $tickets->Count, 1, 'Found 1 ticket' ); is( $tickets->First->id, $tickets[1]->id, 'Found the future ticket' ); $tickets->FromSQL(qq{Queue = 'General' AND CF.$cfid < 'today'}); is( $tickets->Count, 1, 'Found 1 ticket' ); is( $tickets->First->id, $tickets[0]->id, 'Found the past ticket' ); $tickets->FromSQL(qq{Queue = 'General' AND CF.$cfid > 'today'}); is( $tickets->Count, 1, 'Found 1 ticket' ); is( $tickets->First->id, $tickets[1]->id, 'Found the future ticket' ); done_testing; rt-5.0.1/t/ticket/sort-by-custom-ownership.t000644 000765 000024 00000005570 14005011336 021646 0ustar00sunnavystaff000000 000000 use RT; use RT::Test nodata => 1, tests => 7; use strict; use warnings; use RT::Tickets; use RT::Queue; use RT::CustomField; my($ret,$msg); # Test Paw Sort # ---- Create a queue to test with. my $queue = "PAWSortQueue-$$"; my $queue_obj = RT::Queue->new(RT->SystemUser); ($ret, $msg) = $queue_obj->Create(Name => $queue, Description => 'queue for custom field sort testing'); ok($ret, "$queue test queue creation. $msg"); # ---- Create some users my $me = RT::User->new(RT->SystemUser); ($ret, $msg) = $me->Create(Name => "Me$$", EmailAddress => $$.'create-me-1@example.com'); ($ret, $msg) = $me->PrincipalObj->GrantRight(Object =>$queue_obj, Right => 'OwnTicket'); ($ret, $msg) = $me->PrincipalObj->GrantRight(Object =>$queue_obj, Right => 'SeeQueue'); ($ret, $msg) = $me->PrincipalObj->GrantRight(Object =>$queue_obj, Right => 'ShowTicket'); my $you = RT::User->new(RT->SystemUser); ($ret, $msg) = $you->Create(Name => "You$$", EmailAddress => $$.'create-you-1@example.com'); ($ret, $msg) = $you->PrincipalObj->GrantRight(Object =>$queue_obj, Right => 'OwnTicket'); ($ret, $msg) = $you->PrincipalObj->GrantRight(Object =>$queue_obj, Right => 'SeeQueue'); ($ret, $msg) = $you->PrincipalObj->GrantRight(Object =>$queue_obj, Right => 'ShowTicket'); my $nobody = RT::User->new(RT->SystemUser); $nobody->Load('nobody'); # ----- Create some tickets to test with. Assign them some values to # make it easy to sort with. my @tickets = ( [qw[1 10], $me], [qw[2 20], $me], [qw[3 20], $you], [qw[4 30], $you], [qw[5 5], $nobody], [qw[6 55], $nobody], ); for (@tickets) { my $t = RT::Ticket->new(RT->SystemUser); $t->Create( Queue => $queue_obj->Id, Subject => $_->[0], Owner => $_->[2]->Id, Priority => $_->[1], ); } sub check_order { my ($tx, @order) = @_; my @results; while (my $t = $tx->Next) { push @results, $t->Subject; } my $results = join (" ",@results); my $order = join(" ",@order); is( $results, $order ); } # The real tests start here my $cme = RT::CurrentUser->new( $me ); my $metx = RT::Tickets->new( $cme ); # Make sure we can sort in both directions on a queue specific field. $metx->FromSQL(qq[queue="$queue"] ); $metx->OrderBy( FIELD => "Custom.Ownership", ORDER => 'ASC' ); is($metx->Count,6); check_order( $metx, qw[2 1 6 5 4 3]); $metx->OrderBy( FIELD => "Custom.Ownership", ORDER => 'DESC' ); is($metx->Count,6); check_order( $metx, reverse qw[2 1 6 5 4 3]); my $cyou = RT::CurrentUser->new( $you ); my $youtx = RT::Tickets->new( $cyou ); # Make sure we can sort in both directions on a queue specific field. $youtx->FromSQL(qq[queue="$queue"] ); $youtx->OrderBy( FIELD => "Custom.Ownership", ORDER => 'ASC' ); is($youtx->Count,6); check_order( $youtx, qw[4 3 6 5 2 1]); __END__ rt-5.0.1/t/ticket/search_by_cf_freeform_single.t000644 000765 000024 00000027031 14005011336 022554 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => undef; my $q = RT::Test->load_or_create_queue( Name => 'Regression' ); ok $q && $q->id, 'loaded or created queue'; my $queue = $q->Name; diag "create a CF"; my ($cf_name, $cf_id, $cf) = ("Test", 0, undef); { $cf = RT::CustomField->new( RT->SystemUser ); my ($ret, $msg) = $cf->Create( Name => $cf_name, Queue => $q->id, Type => 'FreeformSingle', ); ok($ret, "Custom Field Order created"); $cf_id = $cf->id; } my $other_q = RT::Test->load_or_create_queue( Name => 'Other' ); ok $other_q && $other_q->id, 'loaded or created queue'; my $ylong = 'y' x 300; subtest "Creating tickets" => sub { RT::Test->create_tickets( { Queue => $q->id }, { Subject => '-' }, { Subject => "other", Queue => $other_q->id }, { Subject => 'x', "CustomField-$cf_id" => 'x', }, { Subject => 'y', "CustomField-$cf_id" => 'y', }, { Subject => 'z', "CustomField-$cf_id" => 'z', }, { Subject => 'ylong', "CustomField-$cf_id" => $ylong, }, ); }; my @tests = ( "CF.{$cf_id} IS NULL" => { '-' => 1, other => 1, x => 0, y => 0, z => 0, ylong => 0 }, "CF.{$cf_id}.Content IS NULL" => { '-' => 1, other => 1, x => 0, y => 0, z => 0, ylong => 1 }, "CF.{$cf_id}.LargeContent IS NULL" => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 0 }, "'CF.{$cf_name}' IS NULL" => { '-' => 1, other => 1, x => 0, y => 0, z => 0, ylong => 0 }, "'CF.{$cf_name}.Content' IS NULL" => { '-' => 1, other => 1, x => 0, y => 0, z => 0, ylong => 1 }, "'CF.{$cf_name}.LargeContent' IS NULL" => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 0 }, "'CF.$queue.{$cf_id}' IS NULL" => { '-' => 1, other => 1, x => 0, y => 0, z => 0, ylong => 0 }, "'CF.$queue.{$cf_name}' IS NULL" => { '-' => 1, other => 1, x => 0, y => 0, z => 0, ylong => 0 }, "CF.{$cf_id} IS NOT NULL" => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 1 }, "CF.{$cf_id}.Content IS NOT NULL" => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 0 }, "CF.{$cf_id}.LargeContent IS NOT NULL" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 1 }, "'CF.{$cf_name}' IS NOT NULL" => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 1 }, "'CF.{$cf_name}.Content' IS NOT NULL" => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 0 }, "'CF.{$cf_name}.LargeContent' IS NOT NULL" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 1 }, "'CF.$queue.{$cf_id}' IS NOT NULL" => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 1 }, "'CF.$queue.{$cf_name}' IS NOT NULL" => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 1 }, "CF.{$cf_id} = 'x'" => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 }, "CF.{$cf_id}.Content = 'x'" => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 }, "CF.{$cf_id}.LargeContent = 'x'" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 }, "CF.{$cf_id} = '$ylong'" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 1 }, "CF.{$cf_id}.Content = '$ylong'" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 }, "CF.{$cf_id}.LargeContent = '$ylong'" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 1 }, "CF.{$cf_id} LIKE 'yyy'" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 1 }, "CF.{$cf_id}.Content LIKE 'yyy'" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 }, "CF.{$cf_id}.LargeContent LIKE 'yyy'" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 1 }, "'CF.{$cf_name}' = 'x'" => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 }, "'CF.{$cf_name}.Content' = 'x'" => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 }, "'CF.{$cf_name}.LargeContent' = 'x'" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 }, "'CF.$queue.{$cf_id}' = 'x'" => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 }, "'CF.$queue.{$cf_id}.Content' = 'x'" => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 }, "'CF.$queue.{$cf_id}.LargeContent' = 'x'" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 }, "'CF.$queue.{$cf_name}' = 'x'" => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 }, "'CF.$queue.{$cf_name}.Content' = 'x'" => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 }, "'CF.$queue.{$cf_name}.LargeContent' = 'x'" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 }, "CF.{$cf_id} != 'x'" => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 }, "CF.{$cf_id}.Content != 'x'" => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 }, "CF.{$cf_id}.LargeContent != 'x'" => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 1 }, "CF.{$cf_id} != '$ylong'" => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 0 }, "CF.{$cf_id}.Content != '$ylong'" => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 1 }, "CF.{$cf_id}.LargeContent != '$ylong'" => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 0 }, "CF.{$cf_id} NOT LIKE 'yyy'" => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 0 }, "CF.{$cf_id}.Content NOT LIKE 'yyy'" => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 1 }, "CF.{$cf_id}.LargeContent NOT LIKE 'yyy'" => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 0 }, "'CF.{$cf_name}' != 'x'" => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 }, "'CF.{$cf_name}.Content' != 'x'" => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 }, "'CF.{$cf_name}.LargeContent' != 'x'" => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 1 }, "'CF.$queue.{$cf_id}' != 'x'" => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 }, "'CF.$queue.{$cf_id}.Content' != 'x'" => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 }, "'CF.$queue.{$cf_id}.LargeContent' != 'x'" => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 1 }, "'CF.$queue.{$cf_name}' != 'x'" => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 }, "'CF.$queue.{$cf_name}.Content' != 'x'" => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 }, "'CF.$queue.{$cf_name}.LargeContent' != 'x'" => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 1 }, "CF.{$cf_id} = 'x' OR CF.{$cf_id} = 'y'" => { '-' => 0, other => 0, x => 1, y => 1, z => 0, ylong => 0 }, "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' = 'y'" => { '-' => 0, other => 0, x => 1, y => 1, z => 0, ylong => 0 }, "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' = 'y'" => { '-' => 0, other => 0, x => 1, y => 1, z => 0, ylong => 0 }, "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' = 'y'" => { '-' => 0, other => 0, x => 1, y => 1, z => 0, ylong => 0 }, "CF.{$cf_id} = 'x' AND CF.{$cf_id} = 'y'" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 }, "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' = 'y'" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 }, "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' = 'y'" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 }, "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' = 'y'" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 }, "CF.{$cf_id} != 'x' AND CF.{$cf_id} != 'y'" => { '-' => 1, other => 1, x => 0, y => 0, z => 1, ylong => 1 }, "'CF.{$cf_name}' != 'x' AND 'CF.{$cf_name}' != 'y'" => { '-' => 1, other => 1, x => 0, y => 0, z => 1, ylong => 1 }, "'CF.$queue.{$cf_id}' != 'x' AND 'CF.$queue.{$cf_id}' != 'y'" => { '-' => 1, other => 1, x => 0, y => 0, z => 1, ylong => 1 }, "'CF.$queue.{$cf_name}' != 'x' AND 'CF.$queue.{$cf_name}' != 'y'" => { '-' => 1, other => 1, x => 0, y => 0, z => 1, ylong => 1 }, "CF.{$cf_id} = 'x' AND CF.{$cf_id} IS NULL" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 }, "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' IS NULL" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 }, "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' IS NULL" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 }, "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' IS NULL" => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 }, "CF.{$cf_id} = 'x' OR CF.{$cf_id} IS NULL" => { '-' => 1, other => 1, x => 1, y => 0, z => 0, ylong => 0 }, "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NULL" => { '-' => 1, other => 1, x => 1, y => 0, z => 0, ylong => 0 }, "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NULL" => { '-' => 1, other => 1, x => 1, y => 0, z => 0, ylong => 0 }, "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NULL" => { '-' => 1, other => 1, x => 1, y => 0, z => 0, ylong => 0 }, "CF.{$cf_id} = 'x' AND CF.{$cf_id} IS NOT NULL" => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 }, "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' IS NOT NULL" => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 }, "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' IS NOT NULL" => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 }, "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' IS NOT NULL" => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 }, "CF.{$cf_id} = 'x' OR CF.{$cf_id} IS NOT NULL" => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 1 }, "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NOT NULL" => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 1 }, "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NOT NULL" => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 1 }, "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NOT NULL" => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 1 }, ); run_tests(@tests); sub run_tests { my @tests = @_; while (@tests) { my $query = shift @tests; my %results = %{ shift @tests }; subtest $query => sub { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL( "$query" ); my $error = 0; my $count = 0; $count++ foreach grep $_, values %results; is($tix->Count, $count, "found correct number of ticket(s)") or $error = 1; my $good_tickets = ($tix->Count == $count); while ( my $ticket = $tix->Next ) { next if $results{ $ticket->Subject }; diag $ticket->Subject ." ticket has been found when it's not expected"; $good_tickets = 0; } ok( $good_tickets, "all tickets are good" ) or $error = 1; diag "Wrong SQL: ". $tix->BuildSelectQuery if $error; }; } } done_testing; rt-5.0.1/t/ticket/clicky.t000644 000765 000024 00000007063 14005011336 016200 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::More; use RT::Test tests => undef; my $plain = MIME::Entity->build( Subject => 'plain mime', Type => 'text/plain', Data => <build( Type => 'text/html', Subject => 'html mime', Data => <wiki or find known bugs on http://rt3.fsck.com to test anchor: https://wiki.bestpractical.com/test#anchor -- Best regards. BestPractical Team. END ); my $ticket = RT::Ticket->new( RT->SystemUser ); my ($plain_id) = $ticket->Create( Subject => 'test', Queue => 'General', MIMEObj => $plain, ); ok($plain_id, "We created a ticket #$plain_id"); my ($html_id) = $ticket->Create( Subject => 'test', Queue => 'General', MIMEObj => $html, ); ok($html_id, "We created a ticket #$html_id"); diag 'test no clicky'; { RT->Config->Set( 'Active_MakeClicky' => () ); my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in'; $m->goto_ticket($plain_id); my @links = $m->find_link( tag => 'a', url => 'http://wiki.bestpractical.com', ); ok( @links == 0, 'no clicky link found with plain message' ); @links = $m->find_link( tag => 'a', url => 'http://rt3.fsck.com', ); ok( @links == 0, 'no extra clicky link found with html message' ); } diag 'test httpurl'; { RT::Test->stop_server; RT->Config->Set( 'Active_MakeClicky' => qw/httpurl/ ); my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in'; $m->goto_ticket($plain_id); my @links = $m->find_link( tag => 'a', url => 'http://wiki.bestpractical.com', text => 'Open URL', ); ok( scalar @links, 'found clicky link' ); @links = $m->find_link( tag => 'a', url => 'https://wiki.bestpractical.com/test#anchor', text => 'Open URL', ); ok( scalar @links, 'found clicky link with anchor' ); $m->goto_ticket($html_id); @links = $m->find_link( tag => 'a', url => 'http://wiki.bestpractical.com', text => 'Open URL', ); ok( @links == 0, 'not make clicky links clicky twice' ); @links = $m->find_link( tag => 'a', url => 'http://rt3.fsck.com', text => 'Open URL', ); ok( scalar @links, 'found clicky link' ); @links = $m->find_link( tag => 'a', url => 'https://wiki.bestpractical.com/test#anchor', text => 'Open URL', ); ok( scalar @links, 'found clicky link with anchor' ); } diag 'test httpurl_overwrite'; { RT::Test->stop_server; RT->Config->Set( 'Active_MakeClicky' => 'httpurl_overwrite' ); my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in'; ok $m->goto_ticket($plain_id), 'opened diplay page of the ticket'; my @links = $m->find_link( tag => 'a', url => 'http://wiki.bestpractical.com', text => 'http://wiki.bestpractical.com', ); ok( scalar @links, 'found clicky link' ); @links = $m->find_link( tag => 'a', url => 'https://wiki.bestpractical.com/test#anchor', text => 'https://wiki.bestpractical.com/test#anchor', ); ok( scalar @links, 'found clicky link with anchor' ); } done_testing; rt-5.0.1/t/ticket/add-watchers.t000644 000765 000024 00000015307 14005011336 017270 0ustar00sunnavystaff000000 000000 use RT::Test nodata => 1, tests => undef; use strict; use warnings; use RT::Queue; use RT::User; use RT::Group; use RT::Ticket; use RT::CurrentUser; # clear all global right my $acl = RT::ACL->new(RT->SystemUser); $acl->Limit( FIELD => 'RightName', OPERATOR => '!=', VALUE => 'SuperUser' ); $acl->LimitToObject( RT->System ); while( my $ace = $acl->Next ) { $ace->Delete; } # create new queue to be sure we do not mess with rights my $queue = RT::Queue->new(RT->SystemUser); my ($queue_id) = $queue->Create( Name => 'watcher tests '.$$); ok( $queue_id, 'queue created for watcher tests' ); # Create custom role on the queue my $custom_role = RT::CustomRole->new(RT->SystemUser); my ($ok, $msg) = $custom_role->Create( Name => 'Custom Role', MaxValues => 0, ); ok($ok, "Created custom role: $msg"); ($ok, $msg) = $custom_role->AddToObject($queue->Id); ok($ok, "Added custom role to queue: $msg"); my $custom_role_type = $custom_role->GroupType; # new privileged user to check rights my $user = RT::User->new( RT->SystemUser ); my ($user_id) = $user->Create( Name => 'watcher'.$$, EmailAddress => "watcher$$".'@localhost', Privileged => 1, Password => 'qwe123', ); my $cu= RT::CurrentUser->new($user); # make sure user can see tickets in the queue my $principal = $user->PrincipalObj; ok( $principal, "principal loaded" ); $principal->GrantRight( Right => 'ShowTicket', Object => $queue ); $principal->GrantRight( Right => 'SeeQueue' , Object => $queue ); ok( $user->HasRight( Right => 'SeeQueue', Object => $queue ), "user can see queue" ); ok( $user->HasRight( Right => 'ShowTicket', Object => $queue ), "user can show queue tickets" ); ok( !$user->HasRight( Right => 'ModifyTicket', Object => $queue ), "user can't modify queue tickets" ); ok( !$user->HasRight( Right => 'Watch', Object => $queue ), "user can't watch queue tickets" ); my $ticket = RT::Ticket->new( RT->SystemUser ); my $rv; ($rv, $msg) = $ticket->Create( Subject => 'watcher tests', Queue => $queue->Name ); ok( $ticket->id, "ticket created" ); my $ticket2 = RT::Ticket->new( $cu ); $ticket2->Load( $ticket->id ); ok( $ticket2->Subject, "ticket load by user" ); # user can add self to ticket only after getting Watch right ($rv, $msg) = $ticket2->AddWatcher( Type => 'Cc', PrincipalId => $user->PrincipalId ); ok( !$rv, "user can't add self as Cc" ); ($rv, $msg) = $ticket2->AddWatcher( Type => 'Requestor', PrincipalId => $user->PrincipalId ); ok( !$rv, "user can't add self as Requestor" ); ($rv, $msg) = $ticket2->AddWatcher( Type => $custom_role_type, PrincipalId => $user->PrincipalId ); ok( !$rv, "user can't add self to Custom Role $msg" ); $principal->GrantRight( Right => 'Watch' , Object => $queue ); ok( $user->HasRight( Right => 'Watch', Object => $queue ), "user can watch queue tickets" ); ($rv, $msg) = $ticket2->AddWatcher( Type => 'Cc', PrincipalId => $user->PrincipalId ); ok( $rv, "user can add self as Cc by PrincipalId" ); ($rv, $msg) = $ticket2->AddWatcher( Type => 'Requestor', PrincipalId => $user->PrincipalId ); ok( $rv, "user can add self as Requestor by PrincipalId" ); # remove user and try adding with Email address ($rv, $msg) = $ticket->DeleteWatcher( Type => 'Cc', PrincipalId => $user->PrincipalId ); ok( $rv, "watcher removed by PrincipalId" ); ($rv, $msg) = $ticket->DeleteWatcher( Type => 'Requestor', Email => $user->EmailAddress ); ok( $rv, "watcher removed by Email" ); ($rv, $msg) = $ticket2->AddWatcher( Type => 'Cc', Email => $user->EmailAddress ); ok( $rv, "user can add self as Cc by Email" ); ($rv, $msg) = $ticket2->AddWatcher( Type => 'Requestor', Email => $user->EmailAddress ); ok( $rv, "user can add self as Requestor by Email" ); # remove user and try adding by username # This worked in 3.6 and is a regression in 3.8 ($rv, $msg) = $ticket->DeleteWatcher( Type => 'Cc', Email => $user->EmailAddress ); ok( $rv, "watcher removed by Email" ); ($rv, $msg) = $ticket->DeleteWatcher( Type => 'Requestor', Email => $user->EmailAddress ); ok( $rv, "watcher removed by Email" ); ($rv, $msg) = $ticket2->AddWatcher( Type => 'Cc', Email => $user->Name ); ok( $rv, "user can add self as Cc by username" ); ($rv, $msg) = $ticket2->AddWatcher( Type => 'Requestor', Email => $user->Name ); ok( $rv, "user can add self as Requestor by username" ); # Add an email address with a phrase ($rv, $msg) = $ticket->AddWatcher( Type => 'Cc', Email => q["Foo Bar" ] ); ok $rv, "Added email address with phrase" or diag $msg; my $foo = RT::Test->load_or_create_user( EmailAddress => 'foo@example.com' ); is $foo->RealName, "Foo Bar", "RealName matches"; # Queue watcher tests $principal->RevokeRight( Right => 'Watch' , Object => $queue ); ok( !$user->HasRight( Right => 'Watch', Object => $queue ), "user queue watch right revoked" ); my $queue2 = RT::Queue->new( $cu ); ($rv, $msg) = $queue2->Load( $queue->id ); ok( $rv, "user loaded queue" ); # user can add self to queue only after getting Watch right ($rv, $msg) = $queue2->AddWatcher( Type => 'Cc', PrincipalId => $user->PrincipalId ); ok( !$rv, "user can't add self as Cc" ); ($rv, $msg) = $queue2->AddWatcher( Type => 'Requestor', PrincipalId => $user->PrincipalId ); ok( !$rv, "user can't add self as Requestor" ); ($rv, $msg) = $queue2->AddWatcher( Type => $custom_role_type, PrincipalId => $user->PrincipalId ); ok( !$rv, "user can't add self to Custom Role $msg" ); $principal->GrantRight( Right => 'Watch' , Object => $queue ); ok( $user->HasRight( Right => 'Watch', Object => $queue ), "user can watch queue queues" ); ($rv, $msg) = $queue2->AddWatcher( Type => 'Cc', PrincipalId => $user->PrincipalId ); ok( $rv, "user can add self as Cc by PrincipalId" ); ($rv, $msg) = $queue2->AddWatcher( Type => 'Requestor', PrincipalId => $user->PrincipalId ); ok( $rv, "user can add self as Requestor by PrincipalId" ); $principal->GrantRight( Right => 'ModifyQueueWatchers' , Object => $queue ); ok( $user->HasRight( Right => 'ModifyQueueWatchers', Object => $queue ), "user can modify all queue watchers" ); ($rv, $msg) = $queue2->AddWatcher( Type => $custom_role_type, PrincipalId => $user->PrincipalId ); ok( $rv, "user can add self to Custom Role $msg" ); # remove user and try adding with Email address ($rv, $msg) = $queue->DeleteWatcher( Type => 'Cc', PrincipalId => $user->PrincipalId ); ok( $rv, "watcher removed by PrincipalId" ); ($rv, $msg) = $queue->DeleteWatcher( Type => 'Requestor', Email => $user->EmailAddress ); ok( $rv, "watcher removed by Email" ); ($rv, $msg) = $queue2->AddWatcher( Type => 'Cc', Email => $user->EmailAddress ); ok( $rv, "user can add self as Cc by Email" ); ($rv, $msg) = $queue2->AddWatcher( Type => 'Requestor', Email => $user->EmailAddress ); ok( $rv, "user can add self as Requestor by Email" ); done_testing(); rt-5.0.1/t/ticket/simple_search.t000644 000765 000024 00000002763 14005011336 017542 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 12; use_ok('RT'); my $q = RT::Queue->new(RT->SystemUser); my $queue = 'SearchTests-'.$$; $q->Create(Name => $queue); ok ($q->id, "Created the queue"); my $t1 = RT::Ticket->new(RT->SystemUser); my ( $id, undef, $msg ) = $t1->Create( Queue => $q->id, Subject => 'SearchTest1', Requestor => ['search2@example.com'], ); ok( $id, $msg ); use_ok("RT::Search::Simple"); my $tickets = RT::Tickets->new(RT->SystemUser); my $quick = RT::Search::Simple->new(Argument => "", TicketsObj => $tickets); my @tests = ( "General new open root" => "( Owner = 'root' ) AND ( Queue = 'General' ) AND ( Status = 'new' OR Status = 'open' )", "General" => "( Queue = 'General' ) AND ( Status = '__Active__' )", "General any" => "( Queue = 'General' )", "fulltext:jesse" => "( Content LIKE 'jesse' ) AND ( Status = '__Active__' )", $queue => "( Queue = '$queue' ) AND ( Status = '__Active__' )", "root $queue" => "( Owner = 'root' ) AND ( Queue = '$queue' ) AND ( Status = '__Active__' )", "notauser $queue" => "( Subject LIKE 'notauser' ) AND ( Queue = '$queue' ) AND ( Status = '__Active__' )", "notauser $queue root" => "( Subject LIKE 'notauser' ) AND ( Owner = 'root' ) AND ( Queue = '$queue' ) AND ( Status = '__Active__' )"); while (my ($from, $to) = splice @tests, 0, 2) { is($quick->QueryToSQL($from), $to, "<$from> -> <$to>"); } rt-5.0.1/t/ticket/search_long_cf_values.t000644 000765 000024 00000005075 14005011336 021236 0ustar00sunnavystaff000000 000000 # tests relating to searching. Especially around custom fields with long values # (> 255 chars) use strict; use warnings; use RT::Test nodata => 1, tests => 10; # setup the queue my $q = RT::Queue->new(RT->SystemUser); my $queue = 'SearchTests-'.$$; $q->Create(Name => $queue); ok ($q->id, "Created the queue"); # setup the CF my $cf = RT::CustomField->new(RT->SystemUser); $cf->Create(Name => 'SearchTest', Type => 'Freeform', MaxValues => 0, Queue => $q->id); ok($cf->id, "Created the SearchTest CF"); my $cflabel = "CustomField-".$cf->id; # setup some tickets my $t1 = RT::Ticket->new(RT->SystemUser); my ( $id, undef, $msg ) = $t1->Create( Queue => $q->id, Subject => 'SearchTest1', Requestor => ['search@example.com'], $cflabel => 'foo', ); ok( $id, $msg ); my $t2 = RT::Ticket->new(RT->SystemUser); ( $id, undef, $msg ) = $t2->Create( Queue => $q->id, Subject => 'SearchTest2', Requestor => ['searchlong@example.com'], $cflabel => 'bar' x 150, ); ok( $id, $msg ); my $t3 = RT::Ticket->new(RT->SystemUser); ( $id, undef, $msg ) = $t3->Create( Queue => $q->id, Subject => 'SearchTest3', Requestor => ['searchlong@example.com'], $cflabel => 'bar', ); ok( $id, $msg ); # we have tickets. start searching my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND CF.SearchTest LIKE 'foo'"); is($tix->Count, 1, "matched short string foo") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND CF.SearchTest LIKE 'bar'"); is($tix->Count, 2, "matched long+short string bar") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND ( CF.SearchTest LIKE 'foo' OR CF.SearchTest LIKE 'bar' )"); is($tix->Count, 3, "matched short string foo or long+short string bar") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND CF.SearchTest NOT LIKE 'foo' AND CF.SearchTest LIKE 'bar'"); is($tix->Count, 2, "not matched short string foo and matched long+short string bar") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND CF.SearchTest LIKE 'foo' AND CF.SearchTest NOT LIKE 'bar'"); is($tix->Count, 1, "matched short string foo and not matched long+short string bar") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; rt-5.0.1/t/ticket/batch-upload-csv.t000644 000765 000024 00000002473 14005011336 020056 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 12; use_ok('RT'); use_ok('RT::Action::CreateTickets'); my $QUEUE = 'uploadtest-'.$$; my $queue_obj = RT::Queue->new(RT->SystemUser); $queue_obj->Create(Name => $QUEUE); my $cf = RT::CustomField->new(RT->SystemUser); my ($val,$msg) = $cf->Create(Name => 'Work Package-'.$$, Type => 'Freeform', LookupType => RT::Ticket->CustomFieldLookupType, MaxValues => 1); ok($cf->id); ok($val,$msg); ($val, $msg) = $cf->AddToObject($queue_obj); ok($val,$msg); ok($queue_obj->TicketCustomFields()->Count, "We have a custom field, at least"); my $data = <Name]} create-1,$QUEUE,hi,new,root,2.0 create-2,$QUEUE,hello,new,root,3.0 EOF my $action = RT::Action::CreateTickets->new(CurrentUser => RT::CurrentUser->new('root')); ok ($action->CurrentUser->id , "WE have a current user"); $action->Parse(Content => $data); my @results = $action->CreateByTemplate(); my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL ("Queue = '". $QUEUE."'"); $tix->OrderBy( FIELD => 'id', ORDER => 'ASC' ); is($tix->Count, 2, '2 tickets'); my $first = $tix->First(); is($first->Subject(), 'hi'); is($first->FirstCustomFieldValue($cf->id), '2.0'); my $second = $tix->Next; is($second->Subject(), 'hello'); is($second->FirstCustomFieldValue($cf->id), '3.0'); rt-5.0.1/t/ticket/circular_links.t000644 000765 000024 00000003371 14005011336 017724 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ( $foo, $bar, $baz ) = RT::Test->create_tickets( { Queue => 'General' }, { Subject => 'foo' }, { Subject => 'bar' }, { Subject => 'baz' } ); diag "test circular DependsOn"; my ( $status, $msg ) = $foo->AddLink( Type => 'DependsOn', Target => $bar->id ); ok( $status, "foo depends on bar" ); ( $status, $msg ) = $foo->AddLink( Type => 'DependsOn', Base => $bar->id ); ok( !$status, "foo can't be depended on bar" ); ( $status, $msg ) = $bar->AddLink( Type => 'DependsOn', Target => $foo->id ); ok( !$status, "bar can't depend on foo back" ); ( $status, $msg ) = $bar->AddLink( Type => 'DependsOn', Target => $baz->id ); ok( $status, "bar depends on baz" ); ( $status, $msg ) = $baz->AddLink( Type => 'DependsOn', Target => $foo->id ); ok( !$status, "baz can't depend on foo back" ); diag "test circular MemberOf"; ( $status, $msg ) = $foo->AddLink( Type => 'MemberOf', Target => $bar->id ); ok( $status, "foo is a member of bar" ); ( $status, $msg ) = $foo->AddLink( Type => 'MemberOf', Base => $bar->id ); ok( !$status, "foo can't have member bar" ); ( $status, $msg ) = $bar->AddLink( Type => 'MemberOf', Target => $foo->id ); ok( !$status, "bar can't be a member of foo" ); ( $status, $msg ) = $bar->AddLink( Type => 'MemberOf', Target => $baz->id ); ok( $status, "baz is a member of bar" ); ( $status, $msg ) = $baz->AddLink( Type => 'DependsOn', Target => $foo->id ); ok( !$status, "baz can't be a member of foo" ); diag "test circular RefersTo"; ( $status, $msg ) = $foo->AddLink( Type => 'RefersTo', Target => $bar->id ); ok( $status, "foo refers to bar" ); ( $status, $msg ) = $foo->AddLink( Type => 'RefersTo', Base => $bar->id ); ok( $status, "foo can be referred to by bar" ); done_testing; rt-5.0.1/t/ticket/cfsort-freeform-single.t000644 000765 000024 00000017055 14005011336 021306 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => undef; my $queue = RT::Test->load_or_create_queue( Name => "sorting" ); ok $queue && $queue->id, "Created queue"; my $queue_name = $queue->Name; # CFs for testing, later we create another one my $cf; my $cf_name = "ordering"; diag "create a CF"; { $cf = RT::CustomField->new( RT->SystemUser ); my ($ret, $msg) = $cf->Create( Name => $cf_name, Queue => $queue->id, Type => 'FreeformSingle', ); ok($ret, "Custom Field created"); } run_tests( [ { Subject => '-' }, { Subject => 'aa', 'CustomField-' . $cf->id => 'aa' }, { Subject => 'bb', 'CustomField-' . $cf->id => 'bb' }, { Subject => 'cc', 'CustomField-' . $cf->id => 'cc' }, ], { Count => 4, Order => "CF.{$cf_name}" }, { Count => 4, Order => "CF.$queue_name.{$cf_name}" }, { Query => "CF.{$cf_name} LIKE 'a'", Count => 1, Order => "CF.{$cf_name}" }, { Query => "CF.{$cf_name} LIKE 'a'", Count => 1, Order => "CF.$queue_name.{$cf_name}" }, { Query => "CF.{$cf_name} != 'cc'", Count => 3, Order => "CF.{$cf_name}" }, { Query => "CF.{$cf_name} != 'cc'", Count => 3, Order => "CF.$queue_name.{$cf_name}" }, ); my $other_cf; my $other_name = "othercf"; diag "create another CF"; { $other_cf = RT::CustomField->new( RT->SystemUser ); my ($ret, $msg) = $other_cf->Create( Name => $other_name, Queue => $queue->id, Type => 'FreeformSingle', ); ok($ret, "Other Custom Field created"); } # Test that order is not affected by other CFs run_tests( [ { Subject => '-', }, { Subject => 'aa', "CustomField-" . $cf->id => 'aa', "CustomField-" . $other_cf->id => 'za' }, { Subject => 'bb', "CustomField-" . $cf->id => 'bb', "CustomField-" . $other_cf->id => 'ya' }, { Subject => 'cc', "CustomField-" . $cf->id => 'cc', "CustomField-" . $other_cf->id => 'xa' }, ], { Count => 4, Order => "CF.{$cf_name}" }, { Count => 4, Order => "CF.$queue_name.{$cf_name}" }, { Query => "CF.{$cf_name} LIKE 'a'", Count => 1, Order => "CF.{$cf_name}" }, { Query => "CF.{$cf_name} LIKE 'a'", Count => 1, Order => "CF.$queue_name.{$cf_name}" }, { Query => "CF.{$cf_name} != 'cc'", Count => 3, Order => "CF.{$cf_name}" }, { Query => "CF.{$cf_name} != 'cc'", Count => 3, Order => "CF.$queue_name.{$cf_name}" }, { Query => "CF.{$other_name} != 'za'", Count => 3, Order => "CF.{$cf_name}" }, { Query => "CF.{$other_name} != 'za'", Count => 3, Order => "CF.$queue_name.{$cf_name}" }, ); # And then add a CF with a duplicate name, on a different queue { my $other_queue = RT::Test->load_or_create_queue( Name => "other_queue" ); ok $other_queue && $other_queue->id, "Created queue"; my $dup = RT::CustomField->new( RT->SystemUser ); my ($ret, $msg) = $dup->Create( Name => $cf_name, Queue => $other_queue->id, Type => 'FreeformSingle', ); ok($ret, "Custom Field created"); } my $cf_id = $cf->id; run_tests( [ { Subject => '-', }, { Subject => 'aa', "CustomField-" . $cf->id => 'aa', "CustomField-" . $other_cf->id => 'za' }, { Subject => 'bb', "CustomField-" . $cf->id => 'bb', "CustomField-" . $other_cf->id => 'ya' }, { Subject => 'cc', "CustomField-" . $cf->id => 'cc', "CustomField-" . $other_cf->id => 'xa' }, ], { Count => 4, Order => "CF.{$cf_name}" }, { Count => 4, Order => "CF.$queue_name.{$cf_name}" }, { Query => "CF.{$cf_id} LIKE 'a'", Count => 1, Order => "CF.{$cf_name}" }, { Query => "CF.{$cf_id} LIKE 'a'", Count => 1, Order => "CF.$queue_name.{$cf_name}" }, { Query => "CF.{$cf_id} != 'cc'", Count => 3, Order => "CF.{$cf_name}" }, { Query => "CF.{$cf_id} != 'cc'", Count => 3, Order => "CF.$queue_name.{$cf_name}" }, { Query => "CF.$queue_name.{$cf_name} LIKE 'a'", Count => 1, Order => "CF.{$cf_name}" }, { Query => "CF.$queue_name.{$cf_name} LIKE 'a'", Count => 1, Order => "CF.$queue_name.{$cf_name}" }, { Query => "CF.$queue_name.{$cf_name} != 'cc'", Count => 3, Order => "CF.{$cf_name}" }, { Query => "CF.$queue_name.{$cf_name} != 'cc'", Count => 3, Order => "CF.$queue_name.{$cf_name}" }, { Query => "CF.{$other_name} != 'za'", Count => 3, Order => "CF.{$cf_name}" }, { Query => "CF.{$other_name} != 'za'", Count => 3, Order => "CF.$queue_name.{$cf_name}" }, { Query => "CF.{$cf_id} != 'cc'", Count => 3, Order => "CF.{$cf_id}" }, { Query => "CF.{$cf_id} != 'cc'", Count => 3, Order => "CF.$queue_name.{$cf_id}" }, { Query => "CF.$queue_name.{$cf_name} != 'cc'", Count => 3, Order => "CF.{$cf_id}" }, { Query => "CF.$queue_name.{$cf_name} != 'cc'", Count => 3, Order => "CF.$queue_name.{$cf_id}" }, { Query => "CF.{$other_name} != 'za'", Count => 3, Order => "CF.{$cf_id}" }, { Query => "CF.{$other_name} != 'za'", Count => 3, Order => "CF.$queue_name.{$cf_id}" }, ); sub run_tests { my $tickets = shift; my @tickets = RT::Test->create_tickets( { Queue => $queue->id, RandomOrder => 1 }, @{ $tickets }); my $base_query = join(" OR ", map {"id = ".$_->id} @tickets) || "id > 0"; my @tests = @_; for my $test ( @tests ) { $test->{'Query'} ||= "id > 0"; my $query = "( $base_query ) AND " . $test->{'Query'}; for my $order (qw(ASC DESC)) { subtest $test->{'Query'} . " ORDER BY ".$test->{'Order'}. " $order" => sub { my $error = 0; my $tix = RT::Tickets->new( RT->SystemUser ); $tix->FromSQL( $query ); $tix->OrderBy( FIELD => $test->{'Order'}, ORDER => $order ); is($tix->Count, $test->{'Count'}, "found right number of tickets (".$test->{Count}.")") or $error = 1; my ($order_ok, $last) = (1, $order eq 'ASC'? '-': 'zzzzzz'); if ($tix->Count) { my $last_id = $tix->Last->id; while ( my $t = $tix->Next ) { my $tmp; next if $t->id == $last_id and $t->Subject eq "-"; # Nulls are allowed to come last, in Pg if ( $order eq 'ASC' ) { $tmp = ((split( /,/, $last))[0] cmp (split( /,/, $t->Subject))[0]); } else { $tmp = -((split( /,/, $last))[-1] cmp (split( /,/, $t->Subject))[-1]); } if ( $tmp > 0 ) { $order_ok = 0; last; } $last = $t->Subject; } } ok( $order_ok, "$order order of tickets is good" ) or $error = 1; if ( $error ) { diag "Wrong SQL query:". $tix->BuildSelectQuery; $tix->GotoFirstItem; while ( my $t = $tix->Next ) { diag sprintf "%02d - %s", $t->id, $t->Subject; } } }; } } } done_testing; rt-5.0.1/t/ticket/merge.t000644 000765 000024 00000014452 14005011336 016021 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => undef; # validate that when merging two tickets, the comments from both tickets # are integrated into the new ticket { my $queue = RT::Queue->new(RT->SystemUser); my ($id,$msg) = $queue->Create(Name => 'MergeTest-'.rand(25)); ok ($id,$msg); my $t1 = RT::Ticket->new(RT->SystemUser); my ($tid,$transid, $t1msg) =$t1->Create( Queue => $queue->Name, Subject => 'Merge test. orig', ); ok ($tid, $t1msg); ($id, $msg) = $t1->Comment(Content => 'This is a Comment on the original'); ok($id,$msg); my $txns = $t1->Transactions; my $Comments = 0; while (my $txn = $txns->Next) { $Comments++ if ($txn->Type eq 'Comment'); } is($Comments,1, "our first ticket has only one Comment"); my $t2 = RT::Ticket->new(RT->SystemUser); my ($t2id,$t2transid, $t2msg) =$t2->Create ( Queue => $queue->Name, Subject => 'Merge test. duplicate'); ok ($t2id, $t2msg); ($id, $msg) = $t2->Comment(Content => 'This is a commet on the duplicate'); ok($id,$msg); $txns = $t2->Transactions; $Comments = 0; while (my $txn = $txns->Next) { $Comments++ if ($txn->Type eq 'Comment'); } is($Comments,1, "our second ticket has only one Comment"); ($id, $msg) = $t1->Comment(Content => 'This is a second Comment on the original'); ok($id,$msg); $txns = $t1->Transactions; $Comments = 0; while (my $txn = $txns->Next) { $Comments++ if ($txn->Type eq 'Comment'); } is($Comments,2, "our first ticket now has two Comments"); ($id,$msg) = $t2->MergeInto($t1->id); ok($id,$msg); $txns = $t1->Transactions; $Comments = 0; while (my $txn = $txns->Next) { $Comments++ if ($txn->Type eq 'Comment'); } is($Comments,3, "our first ticket now has three Comments - we merged safely"); } # when you try to merge duplicate links on postgres, eveyrything goes to hell due to referential integrity constraints. { my $t = RT::Ticket->new(RT->SystemUser); $t->Create(Subject => 'Main', Queue => 'general'); ok ($t->id); my $t2 = RT::Ticket->new(RT->SystemUser); $t2->Create(Subject => 'Second', Queue => 'general'); ok ($t2->id); my $t3 = RT::Ticket->new(RT->SystemUser); $t3->Create(Subject => 'Third', Queue => 'general'); ok ($t3->id); my ($id,$val); ($id,$val) = $t->AddLink(Type => 'DependsOn', Target => $t3->id); ok($id,$val); ($id,$val) = $t2->AddLink(Type => 'DependsOn', Target => $t3->id); ok($id,$val); ($id,$val) = $t->MergeInto($t2->id); ok($id,$val); } my $user = RT::Test->load_or_create_user( Name => 'a user', Password => 'password', ); ok $user && $user->id, 'loaded or created user'; # check rights { RT::Test->set_rights( { Principal => 'Everyone', Right => [qw(SeeQueue ShowTicket CreateTicket OwnTicket TakeTicket)] }, { Principal => 'Owner', Right => [qw(ModifyTicket)] }, ); my $t = RT::Ticket->new(RT::CurrentUser->new($user)); $t->Create(Subject => 'Main', Queue => 'general'); ok ($t->id, "Created ticket"); my $t2 = RT::Ticket->new(RT::CurrentUser->new($user)); $t2->Create(Subject => 'Second', Queue => 'general'); ok ($t2->id, "Created ticket"); foreach my $ticket ( $t, $t2 ) { ok( !$ticket->CurrentUserHasRight('ModifyTicket'), "can not modify" ); } my ($status,$msg) = $t->MergeInto($t2->id); ok(!$status, "Can not merge: $msg"); ($status, $msg) = $t->SetOwner( $user->id ); ok( $status, "User took ticket"); ok( $t->CurrentUserHasRight('ModifyTicket'), "can modify after take" ); ($status,$msg) = $t->MergeInto($t2->id); ok(!$status, "Can not merge: $msg"); ($status, $msg) = $t2->SetOwner( $user->id ); ok( $status, "User took ticket"); ok( $t2->CurrentUserHasRight('ModifyTicket'), "can modify after take" ); ($status,$msg) = $t->MergeInto($t2->id); ok($status, "Merged tickets: $msg"); } # check Time* fields after merge { my @tickets; my @values = ( { Worked => 11, Estimated => 17, Left => 6 }, { Worked => 7, Estimated => 12, Left => 5 }, ); for my $i (0 .. 1) { my $t = RT::Ticket->new(RT->SystemUser); $t->Create( Queue => 'general'); ok ($t->id); push @tickets, $t; foreach my $field ( keys %{ $values[ $i ] } ) { my $method = "SetTime$field"; my ($status, $msg) = $t->$method( $values[ $i ]{ $field } ); ok $status, "changed $field on the ticket" or diag "error: $msg"; } } my ($status, $msg) = $tickets[1]->MergeInto($tickets[0]->id); ok($status,$msg); my $t = RT::Ticket->new(RT->SystemUser); $t->Load( $tickets[0]->id ); foreach my $field ( keys %{ $values[0] } ) { my $method = "Time$field"; my $expected = 0; $expected += $_->{ $field } foreach @values; is $t->$method, $expected, "correct value"; my $from_history = 0; my $txns = $t->Transactions; while ( my $txn = $txns->Next ) { next unless $txn->Type eq 'Set' && $txn->Field eq $method; $from_history += $txn->NewValue - $txn->OldValue; } is $from_history, $expected, "history is correct"; } } # forbid merging tickets into non-ticket types { # create two tickets my $ticket_1 = RT::Test->create_ticket( Queue => 'General', Subject => 'test ticket 1' ); my $ticket_2 = RT::Test->create_ticket( Queue => 'General', Subject => 'test ticket 2' ); # create a reminder on ticket_1 $ticket_1->Reminders->Add( Subject => 'Test Reminder', Owner => 'root', ); # verify reminder was created my $reminders = $ticket_1->Reminders->Collection; is($reminders->Count, 1, "Reminder successfully added"); my $reminder = $reminders->First; is($reminder->Subject, "Test Reminder"); # verify ticket cannot be merged into non-ticket type my($status, $msg) = $ticket_2->MergeInto($reminder->Id); ok(!$status, 'Only tickets can be merged'); like($msg, qr/Only tickets can be merged/); # verify non-ticket type cannot merge into a ticket ($status, $msg) = $reminder->MergeInto($ticket_2->Id); ok(!$status, 'Non-ticket types cannot merge into tickets'); like($msg, qr/Only tickets can be merged/); } done_testing(); rt-5.0.1/t/ticket/priority.t000644 000765 000024 00000011044 14005011336 016575 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my $queue = RT::Test->load_or_create_queue( Name => 'General' ); my $ticket = RT::Test->create_ticket( Queue => $queue->Id, ); diag "Default PriorityAsString"; for my $field (qw/Priority InitialPriority FinalPriority/) { is( $ticket->$field, 0, "$field is 0" ); my $string_method = $field . 'AsString'; is( $ticket->$string_method, 'Low', "$string_method is Low" ); } diag "Disable PriorityAsString"; RT->Config->Set( 'EnablePriorityAsString', 0 ); for my $field (qw/Priority InitialPriority FinalPriority/) { my $string_method = $field . 'AsString'; is( $ticket->$string_method, undef, "$string_method is undef" ); } diag "Disable PriorityAsString at queue level"; RT->Config->Set( 'EnablePriorityAsString', 1 ); RT->Config->Set( 'PriorityAsString', General => 0 ); for my $field (qw/Priority InitialPriority FinalPriority/) { my $string_method = $field . 'AsString'; is( $ticket->$string_method, undef, "$string_method is undef" ); } diag "Specific PriorityAsString config at queue level"; RT->Config->Set( 'PriorityAsString', Default => { Low => 0, Medium => 50, High => 100 }, General => { VeryLow => 0, Low => 20, Medium => 50, High => 100, VeryHigh => 200 }, ); for my $field (qw/Priority InitialPriority FinalPriority/) { my $string_method = $field . 'AsString'; is( $ticket->$string_method, 'VeryLow', "$string_method is updated" ); } diag "Update Priorities"; my ( $ret, $msg ) = $ticket->SetPriority(50); ok( $ret, "Priority is updated" ); is( $msg, "Priority changed from 'VeryLow' to 'Medium'", 'Priority updated message' ); ( $ret, $msg ) = $ticket->SetPriority('Low'); ok( $ret, "Priority is updated" ); is( $msg, "Priority changed from 'Medium' to 'Low'", 'Priority updated message' ); is( $ticket->Priority, 20, 'Priority is 20'); ( $ret, $msg ) = $ticket->SetPriority('Medium'); ok( $ret, "Priority is updated" ); is( $msg, "Priority changed from 'Low' to 'Medium'", 'Priority updated message' ); is( $ticket->Priority, 50, 'Priority is 50'); ( $ret, $msg ) = $ticket->SetFinalPriority(100); ok( $ret, "FinalPriority is updated" ); is( $msg, "FinalPriority changed from 'VeryLow' to 'High'", 'FinalPriority updated message' ); diag "Queue default priorities"; ( $ret, $msg ) = $queue->SetDefaultValue( Name => 'InitialPriority', Value => 20 ); ok( $ret, "InitialPriority defaulted to Low" ); is( $msg, 'Default value of InitialPriority changed from (no value) to Low', "InitialPriority updated message" ); ( $ret, $msg ) = $queue->SetDefaultValue( Name => 'FinalPriority', Value => 100 ); ok( $ret, "FinalPriority defaulted to High" ); is( $msg, 'Default value of FinalPriority changed from (no value) to High', "FinalPriority updated message" ); $ticket = RT::Test->create_ticket( Queue => $queue->Id, ); is( $ticket->PriorityAsString, 'Low', 'PriorityAsString is correct' ); is( $ticket->InitialPriorityAsString, 'Low', 'InitialPriorityAsString is correct' ); is( $ticket->FinalPriorityAsString, 'High', 'FinalPriorityAsString is correct' ); diag "Explicitly set priorities on create"; $ticket = RT::Test->create_ticket( Queue => $queue->Id, InitialPriority => '50', FinalPriority => 200 ); is( $ticket->PriorityAsString, 'Medium', 'PriorityAsString is correct' ); is( $ticket->InitialPriorityAsString, 'Medium', 'InitialPriorityAsString is correct' ); is( $ticket->FinalPriorityAsString, 'VeryHigh', 'FinalPriorityAsString is correct' ); diag "Ticket/Transaction search"; for my $field (qw/Priority InitialPriority FinalPriority/) { my $tickets = RT::Tickets->new( RT->SystemUser ); $tickets->FromSQL("Queue = 'General' AND $field = 'Low'"); like( $tickets->BuildSelectQuery, qr/$field = '20'/, "$field is translated properly" ); my $txns = RT::Transactions->new( RT->SystemUser ); $txns->FromSQL("TicketQueue = 'General' AND Ticket$field = 'Low'"); like( $txns->BuildSelectQuery, qr/$field = '20'/, "Ticket$field is translated properly" ); } my $tickets = RT::Tickets->new( RT->SystemUser ); $tickets->FromSQL("Queue = 'General' AND Priority = 'Medium'"); is( $tickets->Count, 2, 'Found 2 tickets' ); while ( my $ticket = $tickets->Next ) { is( $ticket->PriorityAsString, 'Medium', 'Priority is correct' ); } my $txns = RT::Transactions->new( RT->SystemUser ); $txns->FromSQL("TicketQueue = 'General' AND TicketPriority = 'Medium' AND Field = 'Priority'"); is( $txns->Count, 3, 'Found 3 txn' ); my $txn = $txns->First; is( $txn->OldValue, 0, 'OldValue is correct' ); is( $txn->NewValue, 50, 'NewValue is correct' ); done_testing; rt-5.0.1/t/ticket/cfsort-freeform-multiple.t000644 000765 000024 00000006336 14005011336 021660 0ustar00sunnavystaff000000 000000 use RT::Test nodata => 1, tests => 41; use strict; use warnings; use RT::Tickets; use RT::Queue; use RT::CustomField; # Test Sorting by custom fields. diag "Create a queue to test with."; my $queue_name = "CFSortQueue-$$"; my $queue; { $queue = RT::Queue->new( RT->SystemUser ); my ($ret, $msg) = $queue->Create( Name => $queue_name, Description => 'queue for custom field sort testing' ); ok($ret, "$queue_name - test queue creation. $msg"); } diag "create a CF"; my $cf_name = "Order$$"; my $cf; { $cf = RT::CustomField->new( RT->SystemUser ); my ($ret, $msg) = $cf->Create( Name => $cf_name, Queue => $queue->id, Type => 'FreeformMultiple', ); ok($ret, "Custom Field Order created"); } my (@data, @tickets, @test) = (0, ()); sub run_tests { my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets; foreach my $test ( @test ) { my $query = join " AND ", map "( $_ )", grep defined && length, $query_prefix, $test->{'Query'}; foreach my $order (qw(ASC DESC)) { my $error = 0; my $tix = RT::Tickets->new( RT->SystemUser ); $tix->FromSQL( $query ); $tix->OrderBy( FIELD => $test->{'Order'}, ORDER => $order ); ok($tix->Count, "found ticket(s)") or $error = 1; my ($order_ok, $last) = (1, $order eq 'ASC'? '-': 'zzzzzz'); my $last_id = $tix->Last->id; while ( my $t = $tix->Next ) { my $tmp; next if $t->id == $last_id and $t->Subject eq "-"; # Nulls are allowed to come last, in Pg if ( $order eq 'ASC' ) { $tmp = ((split( /,/, $last))[0] cmp (split( /,/, $t->Subject))[0]); } else { $tmp = -((split( /,/, $last))[-1] cmp (split( /,/, $t->Subject))[-1]); } if ( $tmp > 0 ) { $order_ok = 0; last; } $last = $t->Subject; } ok( $order_ok, "$order order of tickets is good" ) or $error = 1; if ( $error ) { diag "Wrong SQL query:". $tix->BuildSelectQuery; $tix->GotoFirstItem; while ( my $t = $tix->Next ) { diag sprintf "%02d - %s", $t->id, $t->Subject; } } } } } @data = ( { Subject => '-' }, { Subject => 'b-d', 'CustomField-' . $cf->id => ['b', 'd'] }, { Subject => 'a-c', 'CustomField-' . $cf->id => ['a', 'c'] }, ); @tickets = RT::Test->create_tickets( {Queue => $queue->id, RandomOrder => 1 }, @data); @test = ( { Order => "CF.{$cf_name}" }, { Order => "CF.$queue_name.{$cf_name}" }, ); run_tests(); @data = ( { Subject => 'm-a', 'CustomField-' . $cf->id => ['m', 'a'] }, { Subject => 'm', 'CustomField-' . $cf->id => ['m'] }, { Subject => 'm-o', 'CustomField-' . $cf->id => ['m', 'o'] }, ); @tickets = RT::Test->create_tickets( {Queue => $queue->id, RandomORder => 1 }, @data); @test = ( { Order => "CF.{$cf_name}", Query => "CF.{$cf_name} = 'm'" }, { Order => "CF.$queue_name.{$cf_name}", Query => "CF.{$cf_name} = 'm'" }, ); run_tests(); @tickets = (); rt-5.0.1/t/ticket/search_utf8.t000644 000765 000024 00000002452 14005011336 017132 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use utf8; my @tickets = ( 'ñèñ', # accent '你好', # chinese "\x{20779}", # 4 bytes han "\x{1F36A}", # cookie "\x{1F4A9}", # pile of poo "\x{1F32E}", # taco "\x{1F336}", # pepper ); RT::Test->load_or_create_custom_field( Name => 'foo', Type => 'Freeform', Queue => 'General', ); for my $str (@tickets) { RT::Test->create_ticket( Queue => 'General', Subject => "Help: $str", Content => "Content is $str", CustomFields => { foo => $str }, ); } SKIP: for my $str (@tickets) { skip "MySQL's 4-byte char search is inaccurate", 20 if length $str == 1 && RT->Config->Get('DatabaseType') eq 'mysql'; my $tickets = RT::Tickets->new( RT->SystemUser ); $tickets->FromSQL("Subject LIKE '$str'"); diag "Search $str in subject"; is( $tickets->Count, 1, 'Found 1 ticket' ); like( $tickets->First->Subject, qr/$str/, 'Found the ticket' ); diag "Search $str in custom field"; $tickets->FromSQL("CustomField.foo = '$str'"); is( $tickets->Count, 1, 'Found 1 ticket' ); is( $tickets->First->FirstCustomFieldValue('foo'), $str, 'Found the ticket' ); } done_testing; rt-5.0.1/t/ticket/badlinks.t000644 000765 000024 00000002247 14005011336 016510 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 14; my ($baseurl, $m) = RT::Test->started_ok; ok($m->login, "Logged in"); my $queue = RT::Test->load_or_create_queue(Name => 'General'); ok($queue->Id, "loaded the General queue"); my $ticket = RT::Ticket->new(RT->SystemUser); my ($tid, $txn, $msg) = $ticket->Create( Queue => $queue->id, Subject => 'test links', ); ok $tid, 'created a ticket #'. $tid or diag "error: $msg"; $m->goto_ticket($tid); $m->follow_link_ok( { text => 'Links' }, "Followed link to Links" ); ok $m->form_with_fields("$tid-DependsOn"), "found the form"; my $not_a_ticket_url = "http://example.com/path/to/nowhere"; $m->field("$tid-DependsOn", $not_a_ticket_url); $m->field("DependsOn-$tid", $not_a_ticket_url); $m->field("$tid-MemberOf", $not_a_ticket_url); $m->field("MemberOf-$tid", $not_a_ticket_url); $m->field("$tid-RefersTo", $not_a_ticket_url); $m->field("RefersTo-$tid", $not_a_ticket_url); $m->submit; foreach my $type ("depends on", "member of", "refers to") { $m->content_like(qr/$type.+$not_a_ticket_url/,"base for $type"); $m->content_like(qr/$not_a_ticket_url.+$type/,"target for $type"); } $m->goto_ticket($tid); rt-5.0.1/t/ticket/search_by_watcher.t000644 000765 000024 00000021436 14005011336 020376 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => undef; use RT::Ticket; my $q = RT::Test->load_or_create_queue( Name => 'Regression' ); ok $q && $q->id, 'loaded or created queue'; my $queue = $q->Name; my ($total, @tickets, @test, @conditions) = (0, ()); sub generate_tix { my @list = ( [], ['x@foo.com'], ['y@bar.com'], ['z@bar.com'], ['x@foo.com', 'y@bar.com'], ['y@bar.com', 'z@bar.com'], ['x@foo.com', 'z@bar.com'], ['x@foo.com', 'y@bar.com', 'z@bar.com'], ); my @data = (); foreach my $r (@list) { foreach my $c (@list) { my $subject = 'r:'. (join( '', map substr($_, 0, 1), @$r ) || '-') .';'; $subject .= 'c:'. (join( '', map substr($_, 0, 1), @$c ) || '-') .';'; push @data, { Subject => $subject, Requestor => $r, Cc => $c, }; } } return RT::Test->create_tickets( { Queue => $q->id }, @data ); } sub run_tests { while ( my ($query, $checks) = splice @test, 0, 2 ) { run_test( $query, %$checks ); } } sub run_test { my ($query, %checks) = @_; my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL($query); my $error = 0; my $count = 0; $count++ foreach grep $_, values %checks; is($tix->Count, $count, "found correct number of ticket(s) by '$query'") or $error = 1; my $good_tickets = ($tix->Count == $count); while ( my $ticket = $tix->Next ) { next if $checks{ $ticket->Subject }; diag $ticket->Subject ." ticket has been found when it's not expected"; $good_tickets = 0; } ok( $good_tickets, "all tickets are good with '$query'" ) or $error = 1; diag "Wrong SQL query for '$query':". $tix->BuildSelectQuery if $error; } sub run_auto_tests { { my @atmp = @conditions; while ( my ($query, $cb) = splice @atmp, 0, 2 ) { my %checks = (); foreach my $ticket ( @tickets ) { my $s = $ticket->Subject; $checks{ $s } = $cb->($s); } run_test($query, %checks); } } my @queries = ( '? AND ?' => sub { $_[0] and $_[1] }, '? OR ?' => sub { $_[0] or $_[1] }, ); while ( my ($template, $t_cb) = splice @queries, 0, 2 ) { my @atmp = @conditions; while ( my ($a, $a_cb) = splice @atmp, 0, 2 ) { my @btmp = @conditions; while ( my ($b, $b_cb) = splice @btmp, 0, 2 ) { next if $a eq $b; my %checks = (); foreach my $ticket ( @tickets ) { my $s = $ticket->Subject; $checks{ $s } = $t_cb->( scalar $a_cb->($s), scalar $b_cb->($s) ); } my $query = $template; foreach my $tmp ($a, $b) { $query =~ s/\?/$tmp/; } run_test( $query, %checks ); } } } return unless $ENV{'RT_TEST_HEAVY'}; @queries = ( '? AND ? AND ?' => sub { $_[0] and $_[1] and $_[2] }, '(? OR ?) AND ?' => sub { return (($_[0] or $_[1]) and $_[2]) }, '? OR (? AND ?)' => sub { $_[0] or ($_[1] and $_[2]) }, '(? AND ?) OR ?' => sub { ($_[0] and $_[1]) or $_[2] }, '? AND (? OR ?)' => sub { $_[0] and ($_[1] or $_[2]) }, '? OR ? OR ?' => sub { $_[0] or $_[1] or $_[2] }, ); while ( my ($template, $t_cb) = splice @queries, 0, 2 ) { my @atmp = @conditions; while ( my ($a, $a_cb) = splice @atmp, 0, 2 ) { my @btmp = @conditions; while ( my ($b, $b_cb) = splice @btmp, 0, 2 ) { next if $a eq $b; my @ctmp = @conditions; while ( my ($c, $c_cb) = splice @ctmp, 0, 2 ) { next if $a eq $c; next if $b eq $c; my %checks = (); foreach my $ticket ( @tickets ) { my $s = $ticket->Subject; $checks{ $s } = $t_cb->( scalar $a_cb->($s), scalar $b_cb->($s), scalar $c_cb->($s) ); } my $query = $template; foreach my $tmp ($a, $b, $c) { $query =~ s/\?/$tmp/; } run_test( $query, %checks ); } } } } } @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 = "x@foo.com"' => sub { $_[0] =~ /c:[^;]*x/ }, 'Cc != "x@foo.com"' => sub { $_[0] !~ /c:[^;]*x/ }, 'Cc LIKE "@bar.com"' => sub { $_[0] =~ /c:[^;]*(?:y|z)/ }, # TODO: # 'Cc NOT LIKE "@bar.com"' => sub { $_[0] !~ /y|z/ }, 'Requestor = "not@exist"' => sub { 0 }, 'Requestor != "not@exist"' => sub { 1 }, '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)/ }, # TODO: # 'Requestor NOT LIKE "@bar.com"' => sub { $_[0] !~ /y|z/ }, 'Watcher = "not@exist"' => sub { 0 }, 'Watcher != "not@exist"' => sub { 1 }, # TODO: # 'Watcher IS NULL' => sub { $_[0] eq 'r:-;c:-;' }, # 'Watcher IS NOT NULL' => sub { $_[0] ne 'r:-;c:-;' }, 'Watcher = "x@foo.com"' => sub { $_[0] =~ /x/ }, # 'Watcher != "x@foo.com"' => sub { $_[0] !~ /x/ }, 'Watcher LIKE "@bar.com"' => sub { $_[0] =~ /(?:y|z)/ }, # TODO: # 'Watcher NOT LIKE "@bar.com"' => sub { $_[0] !~ /y|z/ }, '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/ }, ); @tickets = generate_tix(); $total += scalar @tickets; { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue'"); is($tix->Count, $total, "found $total tickets"); } run_auto_tests(); # owner is special watcher because reference is duplicated in two places, # owner was an ENUM field now it's WATCHERFIELD, but should support old # style ENUM searches for backward compatibility my $nobody = RT::Nobody(); { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Owner = '". $nobody->id ."'"); ok($tix->Count, "found ticket(s)"); } { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Owner = '". $nobody->Name ."'"); ok($tix->Count, "found ticket(s)"); } { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Owner != '". $nobody->id ."'"); is($tix->Count, 0, "found ticket(s)"); } { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Owner != '". $nobody->Name ."'"); is($tix->Count, 0, "found ticket(s)"); } { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Owner.Name LIKE 'nob'"); ok($tix->Count, "found ticket(s)"); } { # create ticket and force type to not a 'ticket' value # bug #6898@rt3.fsck.com # and http://marc.theaimsgroup.com/?l=rt-devel&m=112662934627236&w=2 my($t) = RT::Test->create_tickets( { Queue => $q->id }, { Subject => 'not a ticket' } ); $t->_Set( Field => 'Type', Value => 'not a ticket', CheckACL => 0, RecordTransaction => 0, ); my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Owner = 'Nobody'"); is($tix->Count, $total, "found ticket(s)"); } { my $everyone = RT::Group->new( RT->SystemUser ); $everyone->LoadSystemInternalGroup('Everyone'); ok($everyone->id, "loaded 'everyone' group"); my($id, $msg) = $everyone->PrincipalObj->GrantRight( Right => 'OwnTicket', Object => $q ); ok($id, "granted OwnTicket right to Everyone on '$queue'") or diag("error: $msg"); my $u = RT::User->new( RT->SystemUser ); $u->LoadOrCreateByEmail('alpha@e.com'); ok($u->id, "loaded user"); my($t) = RT::Test->create_tickets( { Queue => $q->id }, { Subject => '4', Owner => $u->id }, ); my $u_alpha_id = $u->id; $u = RT::User->new( RT->SystemUser ); $u->LoadOrCreateByEmail('bravo@e.com'); ok($u->id, "loaded user"); ($t) = RT::Test->create_tickets( { Queue => $q->id }, { Subject => '5', Owner => $u->id }, ); my $u_bravo_id = $u->id; my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND ( Owner = '$u_alpha_id' OR Owner = '$u_bravo_id' )" ); is($tix->Count, 2, "found ticket(s)"); } @tickets = (); done_testing(); rt-5.0.1/t/ticket/scrips_batch.t000644 000765 000024 00000005545 14005011336 017371 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 19; use_ok('RT'); use_ok('RT::Ticket'); my $queue = RT::Test->load_or_create_queue( Name => 'Regression' ); ok $queue && $queue->id, 'loaded or created queue'; RT->Config->Set( UseTransactionBatch => 1 ); my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in as root'; my $sid; { $m->follow_link_ok( { id => 'admin-queues' } ); $m->follow_link_ok( { text => $queue->Name } ); $m->follow_link_ok( { id => 'page-scrips-create'}); $m->form_name('CreateScrip'); $m->field('Description' => 'test'); $m->select('ScripCondition' => 'On Transaction'); $m->select('ScripAction' => 'User Defined'); $m->select('Template' => 'Blank'); $m->select('Stage' => 'Batch'); $m->field('CustomPrepareCode' => 'return 1;'); $m->field('CustomCommitCode' => 'return 1;'); $m->click('Create'); $m->content_contains("Scrip Created"); my $form = $m->form_name('ModifyScrip'); $sid = $form->value('id'); is $m->value("Description"), 'test', 'correct description'; is value_name($form, "ScripCondition"), 'On Transaction', 'correct condition'; is value_name($form, "ScripAction"), 'User Defined', 'correct action'; is value_name($form, "Template"), 'Blank', 'correct template'; { my $rec = RT::ObjectScrip->new( RT->SystemUser ); $rec->LoadByCols( Scrip => $sid, ObjectId => $queue->id ); is $rec->Stage, 'TransactionBatch', "correct stage"; } my $tmp_fn = File::Spec->catfile( RT::Test->temp_directory, 'transactions' ); open my $tmp_fh, '+>', $tmp_fn or die $!; my $code = <', '$tmp_fn' ) or die "Couldn't open '$tmp_fn':\$!"; my \$batch = \$self->TicketObj->TransactionBatch; unless ( \$batch && \@\$batch ) { print \$fh "no batch\n"; return 1; } foreach my \$txn ( \@\$batch ) { print \$fh \$txn->Type ."\n"; } return 1; END $m->field( "CustomCommitCode" => $code ); $m->click('Update'); $m->goto_create_ticket( $queue ); $m->form_name('TicketCreate'); $m->click('SubmitTicket'); is_deeply parse_handle($tmp_fh), ['Create'], 'Create'; $m->follow_link_ok( { text => 'Resolve' } ); $m->form_name('TicketUpdate'); $m->field( "UpdateContent" => 'resolve it' ); $m->click('SubmitTicket'); is_deeply parse_handle($tmp_fh), ['Comment', 'Status'], 'Comment + Resolve'; } sub value_name { my $form = shift; my $field = shift; my $input = $form->find_input( $field ); my @names = $input->value_names; my @values = $input->possible_values; for ( my $i = 0; $i < @values; $i++ ) { return $names[ $i ] if $values[ $i ] eq $input->value; } return undef; } sub parse_handle { my $fh = shift; seek $fh, 0, 0; my @lines = <$fh>; foreach ( @lines ) { s/^\s+//gms; s/\s+$//gms } truncate $fh, 0; return \@lines; } rt-5.0.1/t/ticket/sort-by-queue.t000644 000765 000024 00000004673 14005011336 017447 0ustar00sunnavystaff000000 000000 use RT::Test nodata => 1, tests => 12; use strict; use warnings; use RT::Tickets; use RT::Queue; use RT::CustomField; ######################################################### # Test sorting by Queue, we sort by its name ######################################################### diag "Create queues to test with."; my @qids; my @queues; # create them in reverse order to avoid false positives foreach my $name ( qw(sort-by-queue-Z sort-by-queue-A) ) { my $queue = RT::Queue->new( RT->SystemUser ); my ($ret, $msg) = $queue->Create( Name => $name ."-$$", Description => 'queue to test sorting by queue' ); ok($ret, "test queue creation. $msg"); push @queues, $queue; push @qids, $queue->id; } my ($total, @tickets, @test) = (0, ()); sub run_tests { my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets; foreach my $test ( @test ) { my $query = join " AND ", map "( $_ )", grep defined && length, $query_prefix, $test->{'Query'}; foreach my $order (qw(ASC DESC)) { my $error = 0; my $tix = RT::Tickets->new( RT->SystemUser ); $tix->FromSQL( $query ); $tix->OrderBy( FIELD => $test->{'Order'}, ORDER => $order ); ok($tix->Count, "found ticket(s)") or $error = 1; my ($order_ok, $last) = (1, $order eq 'ASC'? '-': 'zzzzzz'); while ( my $t = $tix->Next ) { my $tmp; if ( $order eq 'ASC' ) { $tmp = ((split( /,/, $last))[0] cmp (split( /,/, $t->Subject))[0]); } else { $tmp = -((split( /,/, $last))[-1] cmp (split( /,/, $t->Subject))[-1]); } if ( $tmp > 0 ) { $order_ok = 0; last; } $last = $t->Subject; } ok( $order_ok, "$order order of tickets is good" ) or $error = 1; if ( $error ) { diag "Wrong SQL query:". $tix->BuildSelectQuery; $tix->GotoFirstItem; while ( my $t = $tix->Next ) { diag sprintf "%02d - %s", $t->id, $t->Subject; } } } } } @tickets = RT::Test->create_tickets( { RandomOrder => 1 }, { Queue => $qids[0], Subject => 'z' }, { Queue => $qids[1], Subject => 'a' }, ); @test = ( { Order => "Queue" }, ); run_tests(); @tickets = (); rt-5.0.1/t/ticket/sort_by_cf.t000644 000765 000024 00000013420 14005011336 017045 0ustar00sunnavystaff000000 000000 use RT::Test nodata => 1, tests => 21; RT::Init(); use strict; use warnings; use RT::Tickets; use RT::Queue; use RT::CustomField; my($ret,$msg); # Test Sorting by custom fields. # TODO: it's hard to read this file, conver to new style, # for example look at 23cfsort-freeform-single.t # ---- Create a queue to test with. my $queue = "CFSortQueue-$$"; my $queue_obj = RT::Queue->new( RT->SystemUser ); ($ret, $msg) = $queue_obj->Create( Name => $queue, Description => 'queue for custom field sort testing' ); ok($ret, "$queue test queue creation. $msg"); # ---- Create some custom fields. We're not currently using all of # them to test with, but the more the merrier. my $cfO = RT::CustomField->new(RT->SystemUser); my $cfA = RT::CustomField->new(RT->SystemUser); my $cfB = RT::CustomField->new(RT->SystemUser); my $cfC = RT::CustomField->new(RT->SystemUser); ($ret, $msg) = $cfO->Create( Name => 'Order', Queue => 0, SortOrder => 1, Description => q{Something to compare results for, since we can't guarantee ticket ID}, Type=> 'FreeformSingle'); ok($ret, "Custom Field Order created"); ($ret, $msg) = $cfA->Create( Name => 'Alpha', Queue => $queue_obj->id, SortOrder => 1, Description => 'A Testing custom field', Type=> 'FreeformSingle'); ok($ret, "Custom Field Alpha created"); ($ret, $msg) = $cfB->Create( Name => 'Beta', Queue => $queue_obj->id, Description => 'A Testing custom field', Type=> 'FreeformSingle'); ok($ret, "Custom Field Beta created"); ($ret, $msg) = $cfC->Create( Name => 'Charlie', Queue => $queue_obj->id, Description => 'A Testing custom field', Type=> 'FreeformSingle'); ok($ret, "Custom Field Charlie created"); # ----- Create some tickets to test with. Assign them some values to # make it easy to sort with. my $t1 = RT::Ticket->new(RT->SystemUser); $t1->Create( Queue => $queue_obj->Id, Subject => 'One', ); $t1->AddCustomFieldValue(Field => $cfO->Id, Value => '1'); $t1->AddCustomFieldValue(Field => $cfA->Id, Value => '2'); $t1->AddCustomFieldValue(Field => $cfB->Id, Value => '1'); $t1->AddCustomFieldValue(Field => $cfC->Id, Value => 'BBB'); my $t2 = RT::Ticket->new(RT->SystemUser); $t2->Create( Queue => $queue_obj->Id, Subject => 'Two', ); $t2->AddCustomFieldValue(Field => $cfO->Id, Value => '2'); $t2->AddCustomFieldValue(Field => $cfA->Id, Value => '1'); $t2->AddCustomFieldValue(Field => $cfB->Id, Value => '2'); $t2->AddCustomFieldValue(Field => $cfC->Id, Value => 'AAA'); # helper sub check_order { my ($tx, @order) = @_; my @results; while (my $t = $tx->Next) { push @results, $t->CustomFieldValues($cfO->Id)->First->Content; } my $results = join (" ",@results); my $order = join(" ",@order); @_ = ($results, $order , "Ordered correctly: $order"); goto \&is; } # The real tests start here my $tx = RT::Tickets->new( RT->SystemUser ); # Make sure we can sort in both directions on a queue specific field. $tx->FromSQL(qq[queue="$queue"] ); $tx->OrderBy( FIELD => "CF.${queue}.{Charlie}", ORDER => 'DES' ); is($tx->Count,2 ,"We found 2 tickets when looking for cf charlie"); check_order( $tx, 1, 2); $tx = RT::Tickets->new( RT->SystemUser ); $tx->FromSQL(qq[queue="$queue"] ); $tx->OrderBy( FIELD => "CF.${queue}.{Charlie}", ORDER => 'ASC' ); is($tx->Count,2, "We found two tickets when sorting by cf charlie without limiting to it" ); check_order( $tx, 2, 1); # When ordering by _global_ CustomFields, if more than one queue has a # CF named Charlie, things will go bad. So, these results are uniqued # in Tickets_Overlay. $tx = RT::Tickets->new( RT->SystemUser ); $tx->FromSQL(qq[queue="$queue"] ); $tx->OrderBy( FIELD => "CF.{Charlie}", ORDER => 'DESC' ); is($tx->Count,2); check_order( $tx, 1, 2); $tx = RT::Tickets->new( RT->SystemUser ); $tx->FromSQL(qq[queue="$queue"] ); $tx->OrderBy( FIELD => "CF.{Charlie}", ORDER => 'ASC' ); is($tx->Count,2); check_order( $tx, 2, 1); # Add a new ticket, to test sorting on multiple columns. my $t3 = RT::Ticket->new(RT->SystemUser); $t3->Create( Queue => $queue_obj->Id, Subject => 'Three', ); $t3->AddCustomFieldValue(Field => $cfO->Id, Value => '3'); $t3->AddCustomFieldValue(Field => $cfA->Id, Value => '3'); $t3->AddCustomFieldValue(Field => $cfB->Id, Value => '2'); $t3->AddCustomFieldValue(Field => $cfC->Id, Value => 'AAA'); $tx = RT::Tickets->new( RT->SystemUser ); $tx->FromSQL(qq[queue="$queue"] ); $tx->OrderByCols( { FIELD => "CF.${queue}.{Charlie}", ORDER => 'ASC' }, { FIELD => "CF.${queue}.{Alpha}", ORDER => 'DES' }, ); is($tx->Count,3); check_order( $tx, 3, 2, 1); $tx = RT::Tickets->new( RT->SystemUser ); $tx->FromSQL(qq[queue="$queue"] ); $tx->OrderByCols( { FIELD => "CF.${queue}.{Charlie}", ORDER => 'DES' }, { FIELD => "CF.${queue}.{Alpha}", ORDER => 'ASC' }, ); is($tx->Count,3); check_order( $tx, 1, 2, 3); # Reverse the order of the secondary column, which changes the order # of the first two tickets. $tx = RT::Tickets->new( RT->SystemUser ); $tx->FromSQL(qq[queue="$queue"] ); $tx->OrderByCols( { FIELD => "CF.${queue}.{Charlie}", ORDER => 'ASC' }, { FIELD => "CF.${queue}.{Alpha}", ORDER => 'ASC' }, ); is($tx->Count,3); check_order( $tx, 2, 3, 1); $tx = RT::Tickets->new( RT->SystemUser ); $tx->FromSQL(qq[queue="$queue"] ); $tx->OrderByCols( { FIELD => "CF.${queue}.{Charlie}", ORDER => 'DES' }, { FIELD => "CF.${queue}.{Alpha}", ORDER => 'DES' }, ); is($tx->Count,3); check_order( $tx, 1, 3, 2); rt-5.0.1/t/ticket/search_by_queue.t000644 000765 000024 00000005265 14005011336 020067 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => undef; use RT::Ticket; my $qa = RT::Test->load_or_create_queue( Name => 'Queue A' ); ok $qa && $qa->id, 'loaded or created queue'; my $qb = RT::Test->load_or_create_queue( Name => 'Queue B' ); ok $qb && $qb->id, 'loaded or created queue'; my @tickets = RT::Test->create_tickets( {}, { Queue => $qa->id, Subject => 'a1', }, { Queue => $qa->id, Subject => 'a2', }, { Queue => $qb->id, Subject => 'b1', }, { Queue => $qb->id, Subject => 'b2', }, ); run_tests( \@tickets, 'Queue = "Queue A"' => { a1 => 1, a2 => 1, b1 => 0, b2 => 0 }, 'Queue = '. $qa->id => { a1 => 1, a2 => 1, b1 => 0, b2 => 0 }, 'Queue != "Queue A"' => { a1 => 0, a2 => 0, b1 => 1, b2 => 1 }, 'Queue != '. $qa->id => { a1 => 0, a2 => 0, b1 => 1, b2 => 1 }, 'Queue = "Queue B"' => { a1 => 0, a2 => 0, b1 => 1, b2 => 1 }, 'Queue = '. $qb->id => { a1 => 0, a2 => 0, b1 => 1, b2 => 1 }, 'Queue != "Queue B"' => { a1 => 1, a2 => 1, b1 => 0, b2 => 0 }, 'Queue != '. $qb->id => { a1 => 1, a2 => 1, b1 => 0, b2 => 0 }, 'Queue = "Bad Queue"' => { a1 => 0, a2 => 0, b1 => 0, b2 => 0 }, 'Queue != "Bad Queue"' => { a1 => 1, a2 => 1, b1 => 1, b2 => 1 }, 'Queue LIKE "Queue A"' => { a1 => 1, a2 => 1, b1 => 0, b2 => 0 }, 'Queue LIKE "Queue B"' => { a1 => 0, a2 => 0, b1 => 1, b2 => 1 }, 'Queue LIKE "Bad Queue"' => { a1 => 0, a2 => 0, b1 => 0, b2 => 0 }, 'Queue LIKE "Queue"' => { a1 => 1, a2 => 1, b1 => 1, b2 => 1 }, 'Queue NOT LIKE "Queue B"' => { a1 => 1, a2 => 1, b1 => 0, b2 => 0 }, 'Queue NOT LIKE "Queue A"' => { a1 => 0, a2 => 0, b1 => 1, b2 => 1 }, 'Queue NOT LIKE "Bad Queue"' => { a1 => 1, a2 => 1, b1 => 1, b2 => 1 }, 'Queue NOT LIKE "Queue"' => { a1 => 0, a2 => 0, b1 => 0, b2 => 0 }, ); done_testing; sub run_tests { my @tickets = @{ shift() }; my %test = @_; my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets; foreach my $key ( sort keys %test ) { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL( "( $query_prefix ) AND ( $key )" ); my $error = 0; my $count = 0; $count++ foreach grep $_, values %{ $test{$key} }; is($tix->Count, $count, "found correct number of ticket(s) by '$key'") or $error = 1; my $good_tickets = 1; while ( my $ticket = $tix->Next ) { next if $test{$key}->{ $ticket->Subject }; diag $ticket->Subject ." ticket has been found when it's not expected"; $good_tickets = 0; } ok( $good_tickets, "all tickets are good with '$key'" ) or $error = 1; diag "Wrong SQL query for '$key':". $tix->BuildSelectQuery if $error; } } rt-5.0.1/t/ticket/requestor-order.t000644 000765 000024 00000011313 14005011336 020055 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 58; use_ok('RT'); use RT::Ticket; my $q = RT::Queue->new(RT->SystemUser); my $queue = 'SearchTests-'.rand(200); $q->Create(Name => $queue); my @requestors = ( ('bravo@example.com') x 6, ('alpha@example.com') x 6, ('delta@example.com') x 6, ('charlie@example.com') x 6, (undef) x 6); my @subjects = ("first test", "second test", "third test", "fourth test", "fifth test") x 6; while (@requestors) { my $t = RT::Ticket->new(RT->SystemUser); my ( $id, undef, $msg ) = $t->Create( Queue => $q->id, Subject => shift @subjects, Requestor => [ shift @requestors ] ); ok( $id, $msg ); } { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue'"); is($tix->Count, 30, "found thirty tickets"); } { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND requestor = 'alpha\@example.com'"); $tix->OrderByCols({ FIELD => "Subject" }); my @subjects; while (my $t = $tix->Next) { push @subjects, $t->Subject; } is(@subjects, 6, "found six tickets"); is_deeply( \@subjects, [ sort @subjects ], "Subjects are sorted"); } sub check_emails_order { my ($tix,$count,$order) = (@_); my @mails; while (my $t = $tix->Next) { push @mails, $t->RequestorAddresses; } is(@mails, $count, "found $count tickets for ". $tix->Query); my @required_order; if( $order =~ /asc/i ) { @required_order = sort { $a? ($b? ($a cmp $b) : -1) : 1} @mails; } else { @required_order = sort { $a? ($b? ($b cmp $a) : -1) : 1} @mails; } foreach( reverse splice @mails ) { if( $_ ) { unshift @mails, $_ } else { push @mails, $_ } } is_deeply( \@mails, \@required_order, "Addresses are sorted"); } { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND subject = 'first test' AND Requestor.EmailAddress LIKE 'example.com'"); $tix->OrderByCols({ FIELD => "Requestor.EmailAddress" }); check_emails_order($tix, 5, 'ASC'); $tix->OrderByCols({ FIELD => "Requestor.EmailAddress", ORDER => 'DESC' }); check_emails_order($tix, 5, 'DESC'); } { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Subject = 'first test'"); $tix->OrderByCols({ FIELD => "Requestor.EmailAddress" }); check_emails_order($tix, 6, 'ASC'); $tix->OrderByCols({ FIELD => "Requestor.EmailAddress", ORDER => 'DESC' }); check_emails_order($tix, 6, 'DESC'); } { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Subject = 'first test'"); $tix->OrderByCols({ FIELD => "Requestor.EmailAddress" }); check_emails_order($tix, 6, 'ASC'); $tix->OrderByCols({ FIELD => "Requestor.EmailAddress", ORDER => 'DESC' }); check_emails_order($tix, 6, 'DESC'); } { # create ticket with group as member of the requestors group my $t = RT::Ticket->new(RT->SystemUser); my ( $id, $msg ) = $t->Create( Queue => $q->id, Subject => "first test", Requestor => 'badaboom@example.com', ); ok( $id, "ticket created" ) or diag( "error: $msg" ); my $g = RT::Group->new(RT->SystemUser); my ($gid); ($gid, $msg) = $g->CreateUserDefinedGroup(Name => '20-sort-by-requestor.t-'.rand(200)); ok($gid, "created group") or diag("error: $msg"); ($id, $msg) = $t->Requestors->AddMember( $gid ); ok($id, "added group to requestors group") or diag("error: $msg"); } my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Subject = 'first test'"); $tix->OrderByCols({ FIELD => "Requestor.EmailAddress" }); check_emails_order($tix, 7, 'ASC'); $tix->OrderByCols({ FIELD => "Requestor.EmailAddress", ORDER => 'DESC' }); check_emails_order($tix, 7, 'DESC'); { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue'"); $tix->OrderByCols({ FIELD => "Requestor.EmailAddress" }); $tix->RowsPerPage(30); my @mails; while (my $t = $tix->Next) { push @mails, $t->RequestorAddresses; } is(@mails, 30, "found thirty tickets"); is_deeply( [grep {$_} @mails], [ sort grep {$_} @mails ], "Paging works (exclude nulls, which are db-dependant)"); } { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue'"); $tix->OrderByCols({ FIELD => "Requestor.EmailAddress" }); $tix->RowsPerPage(30); my @mails; while (my $t = $tix->Next) { push @mails, $t->RequestorAddresses; } is(@mails, 30, "found thirty tickets"); is_deeply( [grep {$_} @mails], [ sort grep {$_} @mails ], "Paging works (exclude nulls, which are db-dependant)"); } RT::Test->mailsent_ok(25); rt-5.0.1/t/ticket/deferred_owner.t000644 000765 000024 00000007766 14005011336 017726 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => undef; use Test::Warn; my $tester = RT::Test->load_or_create_user( EmailAddress => 'tester@localhost', ); ok $tester && $tester->id, 'loaded or created user'; my $queue = RT::Test->load_or_create_queue( Name => 'General' ); ok $queue && $queue->id, 'loaded or created queue'; my $owner_role_group = $queue->RoleGroup( 'Owner' ); ok $owner_role_group->id, 'loaded owners role group of the queue'; diag "check that deffering owner doesn't regress"; { RT::Test->set_rights( { Principal => $tester->PrincipalObj, Right => [qw(SeeQueue ShowTicket CreateTicket OwnTicket)], }, { Principal => $owner_role_group->PrincipalObj, Object => $queue, Right => [qw(ModifyTicket)], }, ); my $ticket = RT::Ticket->new( $tester ); # tester is owner, owner has right to modify owned tickets, # this right is required to set somebody as AdminCc my ($tid, $txn_id, $msg) = $ticket->Create( Queue => $queue->id, Owner => $tester->id, AdminCc => 'root@localhost', ); diag $msg if $msg; ok $tid, "created a ticket"; is $ticket->Owner, $tester->id, 'correct owner'; like $ticket->AdminCcAddresses, qr/root\@localhost/, 'root is there'; } diag "check that previous trick doesn't work without sufficient rights"; { RT::Test->set_rights( { Principal => $tester->PrincipalObj, Right => [qw(SeeQueue ShowTicket CreateTicket OwnTicket)], }, ); my $ticket = RT::Ticket->new( $tester ); # tester is owner, owner has right to modify owned tickets, # this right is required to set somebody as AdminCc my ($tid, $txn_id, $msg) = $ticket->Create( Queue => $queue->id, Owner => $tester->id, AdminCc => 'root@localhost', ); diag $msg if $msg; ok $tid, "created a ticket"; is $ticket->Owner, $tester->id, 'correct owner'; unlike $ticket->AdminCcAddresses, qr/root\@localhost/, 'root is not there'; } diag "check that deffering owner really works"; { RT::Test->set_rights( { Principal => $tester->PrincipalObj, Right => [qw(SeeQueue ShowTicket CreateTicket)], }, { Principal => $queue->Cc->PrincipalObj, Object => $queue, Right => [qw(OwnTicket TakeTicket)], }, ); my $ticket = RT::Ticket->new( $tester ); # set tester as Cc, Cc role group has right to own and take tickets my ($tid, $txn_id, $msg) = $ticket->Create( Queue => $queue->id, Owner => $tester->id, Cc => 'tester@localhost', ); diag $msg if $msg; ok $tid, "created a ticket"; like $ticket->CcAddresses, qr/tester\@localhost/, 'tester is in the cc list'; is $ticket->Owner, $tester->id, 'tester is also owner'; my $owners = $ticket->OwnerGroup->MembersObj; is $owners->Count, 1, 'one record in owner group'; is $owners->First->MemberObj->Id, $tester->id, 'and it is tester'; } diag "check that deffering doesn't work without correct rights"; { RT::Test->set_rights( { Principal => $tester->PrincipalObj, Right => [qw(SeeQueue ShowTicket CreateTicket)], }, ); my $ticket = RT::Ticket->new( $tester ); # set tester as Cc, Cc role group has right to own and take tickets my ($tid, $txn_id, $msg); warning_like { ($tid, $txn_id, $msg) = $ticket->Create( Queue => $queue->id, Owner => $tester->id, Cc => 'tester@localhost', ); } qr/User .* was proposed as a ticket owner but has no rights to own tickets in General/; diag $msg if $msg; ok $tid, "created a ticket"; like $ticket->CcAddresses, qr/tester\@localhost/, 'tester is in the cc list'; is $ticket->Owner, RT->Nobody->id, 'nobody is the owner'; my $owners = $ticket->OwnerGroup->MembersObj; is $owners->Count, 1, 'one record in owner group'; is $owners->First->MemberObj->Id, RT->Nobody->id, 'and it is nobody'; } done_testing; rt-5.0.1/t/ticket/linking.t000644 000765 000024 00000033143 14005011336 016353 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 102; use Test::Warn; use_ok('RT'); use_ok('RT::Ticket'); use_ok('RT::ScripConditions'); use_ok('RT::ScripActions'); use_ok('RT::Template'); use_ok('RT::Scrips'); use_ok('RT::Scrip'); my $filename = File::Spec->catfile( RT::Test->temp_directory, 'link_count' ); open my $fh, '>', $filename or die $!; close $fh; my $link_acl_checks_orig = RT->Config->Get( 'StrictLinkACL' ); RT->Config->Set( 'StrictLinkACL', 1); my $condition = RT::ScripCondition->new( RT->SystemUser ); $condition->Load('User Defined'); ok($condition->id); my $action = RT::ScripAction->new( RT->SystemUser ); $action->Load('User Defined'); ok($action->id); my $template = RT::Template->new( RT->SystemUser ); $template->Load('Blank'); ok($template->id); my $q1 = RT::Queue->new(RT->SystemUser); my ($id,$msg) = $q1->Create(Name => "LinkTest1.$$"); ok ($id,$msg); my $q2 = RT::Queue->new(RT->SystemUser); ($id,$msg) = $q2->Create(Name => "LinkTest2.$$"); ok ($id,$msg); my $commit_code = <; \$data += 0; close \$file; \$RT::Logger->debug("Data is \$data"); open( \$file, '>', "$filename" ) or die "couldn't open $filename"; if (\$self->TransactionObj->Type eq 'AddLink') { \$RT::Logger->debug("AddLink"); print \$file \$data+1, "\n"; } elsif (\$self->TransactionObj->Type eq 'DeleteLink') { \$RT::Logger->debug("DeleteLink"); print \$file \$data-1, "\n"; } else { \$RT::Logger->error("THIS SHOULDN'T HAPPEN"); print \$file "666\n"; } close \$file; 1; END my $Scrips = RT::Scrips->new( RT->SystemUser ); $Scrips->UnLimit; while ( my $Scrip = $Scrips->Next ) { $Scrip->Delete if $Scrip->Description and $Scrip->Description =~ /Add or Delete Link \d+/; } my $scrip = RT::Scrip->new(RT->SystemUser); ($id,$msg) = $scrip->Create( Description => "Add or Delete Link $$", ScripCondition => $condition->id, ScripAction => $action->id, Template => $template->id, Stage => 'TransactionCreate', Queue => 0, CustomIsApplicableCode => '$self->TransactionObj->Type =~ /(Add|Delete)Link/;', CustomPrepareCode => '1;', CustomCommitCode => $commit_code, ); ok($id, "Scrip created"); my $u1 = RT::User->new(RT->SystemUser); ($id,$msg) = $u1->Create(Name => "LinkTestUser.$$"); ok ($id,$msg); # grant ShowTicket right to allow count transactions ($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q1, Right => 'ShowTicket'); ok ($id,$msg); ($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q2, Right => 'ShowTicket'); ok ($id,$msg); ($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q1, Right => 'CreateTicket'); ok ($id,$msg); my $creator = RT::CurrentUser->new($u1->id); diag('Create tickets without rights to link'); { # on q2 we have no rights, yet my $parent = RT::Ticket->new( RT->SystemUser ); my ($id,$tid,$msg) = $parent->Create( Subject => 'Link test 1', Queue => $q2->id ); ok($id,$msg); my $child = RT::Ticket->new( $creator ); ($id,$tid,$msg) = $child->Create( Subject => 'Link test 1', Queue => $q1->id, MemberOf => $parent->id ); ok($id,$msg); $child->CurrentUser( RT->SystemUser ); is($child->_Links('Base')->Count, 0, 'link was not created, no permissions'); is($child->_Links('Target')->Count, 0, 'link was not create, no permissions'); } diag('Create tickets with rights checks on one end of a link'); { # on q2 we have no rights, but use checking one only on thing RT->Config->Set( StrictLinkACL => 0 ); my $parent = RT::Ticket->new( RT->SystemUser ); my ($id,$tid,$msg) = $parent->Create( Subject => 'Link test 1', Queue => $q2->id ); ok($id,$msg); my $child = RT::Ticket->new( $creator ); ($id,$tid,$msg) = $child->Create( Subject => 'Link test 1', Queue => $q1->id, MemberOf => $parent->id ); ok($id,$msg); $child->CurrentUser( RT->SystemUser ); is($child->_Links('Base')->Count, 1, 'link was created'); is($child->_Links('Target')->Count, 0, 'link was created only one'); # only one scrip run (on second ticket) since this is on a ticket Create txn is(link_count($filename), 1, "scrips ok"); RT->Config->Set( StrictLinkACL => 1 ); } ($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q1, Right => 'ModifyTicket'); ok ($id,$msg); diag('try to add link without rights'); { # on q2 we have no rights, yet my $parent = RT::Ticket->new( RT->SystemUser ); my ($id,$tid,$msg) = $parent->Create( Subject => 'Link test 1', Queue => $q2->id ); ok($id,$msg); my $child = RT::Ticket->new( $creator ); ($id,$tid,$msg) = $child->Create( Subject => 'Link test 1', Queue => $q1->id ); ok($id,$msg); ($id, $msg) = $child->AddLink(Type => 'MemberOf', Target => $parent->id); ok(!$id, $msg); is(link_count($filename), 0, "scrips ok"); $child->CurrentUser( RT->SystemUser ); is($child->_Links('Base')->Count, 0, 'link was not created, no permissions'); is($child->_Links('Target')->Count, 0, 'link was not create, no permissions'); } diag('add link with rights only on base'); { # on q2 we have no rights, but use checking one only on thing RT->Config->Set( StrictLinkACL => 0 ); my $parent = RT::Ticket->new( RT->SystemUser ); my ($id,$tid,$msg) = $parent->Create( Subject => 'Link test 1', Queue => $q2->id ); ok($id,$msg); my $child = RT::Ticket->new( $creator ); ($id,$tid,$msg) = $child->Create( Subject => 'Link test 1', Queue => $q1->id ); ok($id,$msg); ($id, $msg) = $child->AddLink(Type => 'MemberOf', Target => $parent->id); ok($id, $msg); is(link_count($filename), 2, "scrips ok"); $child->CurrentUser( RT->SystemUser ); is($child->_Links('Base')->Count, 1, 'link was created'); is($child->_Links('Target')->Count, 0, 'link was created only one'); $child->CurrentUser( $creator ); # turn off feature and try to delete link, we should fail RT->Config->Set( StrictLinkACL => 1 ); ($id, $msg) = $child->DeleteLink(Type => 'MemberOf', Target => $parent->id); ok(!$id, $msg); is(link_count($filename), 0, "scrips ok"); $child->CurrentUser( RT->SystemUser ); $child->_Links('Base')->_DoCount; is($child->_Links('Base')->Count, 1, 'link was not deleted'); $child->CurrentUser( $creator ); # try to delete link, we should success as feature is active RT->Config->Set( StrictLinkACL => 0 ); ($id, $msg) = $child->DeleteLink(Type => 'MemberOf', Target => $parent->id); ok($id, $msg); is(link_count($filename), -2, "scrips ok"); $child->CurrentUser( RT->SystemUser ); $child->_Links('Base')->_DoCount; is($child->_Links('Base')->Count, 0, 'link was deleted'); RT->Config->Set( StrictLinkACL => 1 ); } my $tid; my $ticket = RT::Ticket->new( $creator); ok($ticket->isa('RT::Ticket')); ($id,$tid, $msg) = $ticket->Create(Subject => 'Link test 1', Queue => $q1->id); ok ($id,$msg); diag('try link to itself'); { warning_like { ($id, $msg) = $ticket->AddLink(Type => 'RefersTo', Target => $ticket->id); } qr/Can't link a ticket to itself/; ok(!$id, $msg); is(link_count($filename), 0, "scrips ok"); } my $ticket2 = RT::Ticket->new(RT->SystemUser); ($id, $tid, $msg) = $ticket2->Create(Subject => 'Link test 2', Queue => $q2->id); ok ($id, $msg); ($id,$msg) =$ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id); ok(!$id,$msg); is(link_count($filename), 0, "scrips ok"); ($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q2, Right => 'CreateTicket'); ok ($id,$msg); ($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q2, Right => 'ModifyTicket'); ok ($id,$msg); ($id,$msg) = $ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id); ok($id,$msg); is(link_count($filename), 2, "scrips ok"); warnings_like { ($id,$msg) = $ticket->AddLink(Type => 'RefersTo', Target => -1); } [ qr/Could not determine a URI scheme for -1/, ]; ($id,$msg) = $ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id); ok($id,$msg); is(link_count($filename), 0, "scrips ok"); # already added my $transactions = $ticket2->Transactions; $transactions->Limit( FIELD => 'Type', VALUE => 'AddLink' ); is( $transactions->Count, 1, "Transaction found in other ticket" ); is( $transactions->First->Field , 'ReferredToBy'); is( $transactions->First->NewValue , $ticket->URI ); ($id,$msg) = $ticket->DeleteLink(Type => 'RefersTo', Target => $ticket2->id); ok($id,$msg); is(link_count($filename), -2, "scrips ok"); $transactions = $ticket2->Transactions; $transactions->Limit( FIELD => 'Type', VALUE => 'DeleteLink' ); is( $transactions->Count, 1, "Transaction found in other ticket" ); is( $transactions->First->Field , 'ReferredToBy'); is( $transactions->First->OldValue , $ticket->URI ); ($id,$msg) =$ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id); ok($id,$msg); is(link_count($filename), 2, "scrips ok"); ($id,$msg) =$ticket->DeleteLink(Type => 'RefersTo', Target => $ticket2->id); ok($id,$msg); is(link_count($filename), -2, "scrips ok"); # tests for silent behaviour ($id,$msg) = $ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id, Silent => 1); ok($id,$msg); is(link_count($filename), 0, "scrips ok"); { my $transactions = $ticket->Transactions; $transactions->Limit( FIELD => 'Type', VALUE => 'AddLink' ); is( $transactions->Count, 2, "Still two txns on the base" ); $transactions = $ticket2->Transactions; $transactions->Limit( FIELD => 'Type', VALUE => 'AddLink' ); is( $transactions->Count, 2, "Still two txns on the target" ); } ($id,$msg) =$ticket->DeleteLink(Type => 'RefersTo', Target => $ticket2->id, Silent => 1); ok($id,$msg); is(link_count($filename), 0, "scrips ok"); ($id,$msg) = $ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id, SilentBase => 1); ok($id,$msg); is(link_count($filename), 1, "scrips ok"); { my $transactions = $ticket->Transactions; $transactions->Limit( FIELD => 'Type', VALUE => 'AddLink' ); is( $transactions->Count, 2, "still five txn on the base" ); $transactions = $ticket2->Transactions; $transactions->Limit( FIELD => 'Type', VALUE => 'AddLink' ); is( $transactions->Count, 3, "+1 txn on the target" ); } ($id,$msg) =$ticket->DeleteLink(Type => 'RefersTo', Target => $ticket2->id, SilentBase => 1); ok($id,$msg); is(link_count($filename), -1, "scrips ok"); ($id,$msg) = $ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id, SilentTarget => 1); ok($id,$msg); is(link_count($filename), 1, "scrips ok"); { my $transactions = $ticket->Transactions; $transactions->Limit( FIELD => 'Type', VALUE => 'AddLink' ); is( $transactions->Count, 3, "+1 txn on the base" ); $transactions = $ticket2->Transactions; $transactions->Limit( FIELD => 'Type', VALUE => 'AddLink' ); is( $transactions->Count, 3, "three txns on the target" ); } ($id,$msg) =$ticket->DeleteLink(Type => 'RefersTo', Target => $ticket2->id, SilentTarget => 1); ok($id,$msg); is(link_count($filename), -1, "scrips ok"); # restore RT->Config->Set( StrictLinkACL => $link_acl_checks_orig ); { my $Scrips = RT::Scrips->new( RT->SystemUser ); $Scrips->Limit( FIELD => 'Description', OPERATOR => 'STARTSWITH', VALUE => 'Add or Delete Link '); while ( my $s = $Scrips->Next ) { $s->Delete }; } my $link = RT::Link->new( RT->SystemUser ); ($id,$msg) = $link->Create( Base => $ticket->URI, Target => $ticket2->URI, Type => 'MyLinkType' ); ok($id, $msg); ok($link->LocalBase == $ticket->id, "LocalBase set correctly"); ok($link->LocalTarget == $ticket2->id, "LocalTarget set correctly"); { no warnings 'once'; *RT::NotTicket::Id = sub { return $$ }; *RT::NotTicket::id = \&RT::NotTicket::Id; } { package RT::URI::not_ticket; use RT::URI::base; use vars qw(@ISA); @ISA = qw/RT::URI::base/; sub IsLocal { 1; } sub Object { return bless {}, 'RT::NotTicket'; } } my $orig_getresolver = \&RT::URI::_GetResolver; { no warnings 'redefine'; *RT::URI::_GetResolver = sub { my $self = shift; my $scheme = shift; $scheme =~ s/(\.|-)/_/g; my $resolver; my $module = "RT::URI::$scheme"; $resolver = $module->new($self->CurrentUser); if ($resolver) { $self->{'resolver'} = $resolver; } else { $self->{'resolver'} = RT::URI::base->new($self->CurrentUser); } }; } ($id,$msg) = $link->Create( Base => "not_ticket::$RT::Organization/notticket/$$", Target => $ticket2->URI, Type => 'MyLinkType' ); ok($id, $msg); ok($link->LocalBase == 0, "LocalBase set correctly"); ok($link->LocalTarget == $ticket2->id, "LocalTarget set correctly"); ($id,$msg) = $link->Create( Target => "not_ticket::$RT::Organization/notticket/$$", Base => $ticket->URI, Type => 'MyLinkType' ); ok($id, $msg); ok($link->LocalTarget == 0, "LocalTarget set correctly"); ok($link->LocalBase == $ticket->id, "LocalBase set correctly"); ($id,$msg) = $link->Create( Target => "not_ticket::$RT::Organization/notticket/1$$", Base => "not_ticket::$RT::Organization/notticket/$$", Type => 'MyLinkType' ); ok($id, $msg); ok($link->LocalTarget == 0, "LocalTarget set correctly"); ok($link->LocalBase == 0, "LocalBase set correctly"); # restore _GetResolver { no warnings 'redefine'; *RT::URI::_GetResolver = $orig_getresolver; } sub link_count { my $file = shift; open( my $fh, '<', $file ) or die "couldn't open $file"; my $data = <$fh>; close $fh; truncate($file, 0); return 0 unless defined $data; chomp $data; return $data + 0; } rt-5.0.1/t/ticket/race.t000644 000765 000024 00000006130 14005011336 015626 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use Test::MockTime qw/set_fixed_time/; use constant KIDS => 50; my $id; { my $t = RT::Ticket->new( RT->SystemUser ); ($id) = $t->Create( Queue => "General", Subject => "Race $$", ); } diag "Created ticket $id"; RT->DatabaseHandle->Disconnect; my @kids; for (1..KIDS) { if (my $pid = fork()) { push @kids, $pid; next; } # In the kid, load up the ticket and correspond RT->ConnectToDatabase; my $t = RT::Ticket->new( RT->SystemUser ); $t->Load( $id ); $t->Correspond( Content => "Correspondence from PID $$" ); undef $t; exit 0; } diag "Forked @kids"; waitpid $_, 0 for @kids; diag "All kids finished corresponding"; RT->ConnectToDatabase; my $t = RT::Ticket->new( RT->SystemUser ); $t->Load($id); my $txns = $t->Transactions; $txns->Limit( FIELD => 'Type', VALUE => 'Status' ); is($txns->Count, 1, "Only one transaction change recorded" ); $txns = $t->Transactions; $txns->Limit( FIELD => 'Type', VALUE => 'Correspond' ); is($txns->Count, KIDS, "But all correspondences were recorded" ); my @users; for my $n (0..2) { push @users, RT::Test->load_or_create_user( Name => "user_$n", Password => 'password', )->id; } ok(RT::Test->add_rights({ Principal => 'Privileged', Right => 'OwnTicket', }), "Granted OwnTicket"); for my $round (1..10) { my ($ok, $msg) = $t->SetOwner($users[0]); ok $ok, "Set owner back to base"; my $last_txn = $t->Transactions->Last->id; RT->DatabaseHandle->Disconnect; diag "Round $round..\n"; @kids = (); for my $n (1..2) { if (my $pid = fork()) { push @kids, $pid; next; } set_fixed_time("2017-01-03T17:17:17Z"); # In the kid, load up the ticket and claim the owner RT->ConnectToDatabase; my $t = RT::Ticket->new( RT->SystemUser ); $t->Load( $id ); my ($ok, $msg); if ($n == 1) { $RT::Handle->BeginTransaction; $t->LockForUpdate; ($ok, $msg) = $t->SetOwner( $users[$n] ); undef $t; $RT::Handle->Commit; } else { ($ok, $msg) = $t->SetOwner( $users[$n] ); undef $t; } exit(1 - $ok); } diag "Forked @kids"; for my $pid (@kids) { waitpid $pid, 0; my $ret = $? >> 8; is $ret, 0, "$pid returned $ret"; } RT->ConnectToDatabase; # Flush the process-local cache and reload, since the changes # happened in other processes DBIx::SearchBuilder::Record::Cachable->FlushCache; $t->Load( $id ); $txns = $t->Transactions; $txns->Limit( FIELD => 'id', OPERATOR => '>', VALUE => $last_txn ); $txns->Limit( FIELD => 'Type', VALUE => 'SetWatcher' ); is $txns->Count, 2, "Found two new SetWatcher transactions"; my $winner = $t->Owner; isnt $winner, $users[0], "Not the base owner"; ok $t->OwnerGroup->HasMember( $winner ), "GroupMembers agrees"; ok $t->OwnerGroup->HasMemberRecursively( $winner ), "CachedGroupMembers agrees"; } done_testing; rt-5.0.1/t/ticket/sort-by-user.t000644 000765 000024 00000007024 14005011336 017272 0ustar00sunnavystaff000000 000000 use RT::Test nodata => 1, tests => 52; use strict; use warnings; use RT::Tickets; use RT::Queue; use RT::CustomField; ######################################################### # Test sorting by Owner, Creator and LastUpdatedBy # we sort by user name ######################################################### diag "Create a queue to test with."; my $queue_name = "OwnerSortQueue$$"; my $queue; { $queue = RT::Queue->new( RT->SystemUser ); my ($ret, $msg) = $queue->Create( Name => $queue_name, Description => 'queue for custom field sort testing' ); ok($ret, "$queue test queue creation. $msg"); } my @uids; my @users; # create them in reverse order to avoid false positives foreach my $u (qw(Z A)) { my $name = $u ."-user-to-test-ordering-$$"; my $user = RT::User->new( RT->SystemUser ); my ($uid) = $user->Create( Name => $name, Privileged => 1, ); ok $uid, "created user #$uid"; my ($status, $msg) = $user->PrincipalObj->GrantRight( Right => 'OwnTicket', Object => $queue ); ok $status, "granted right"; ($status, $msg) = $user->PrincipalObj->GrantRight( Right => 'CreateTicket', Object => $queue ); ok $status, "granted right"; push @users, $user; push @uids, $user->id; } my (@data, @tickets, @test) = (0, ()); sub run_tests { my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets; foreach my $test ( @test ) { my $query = join " AND ", map "( $_ )", grep defined && length, $query_prefix, $test->{'Query'}; foreach my $order (qw(ASC DESC)) { my $error = 0; my $tix = RT::Tickets->new( RT->SystemUser ); $tix->FromSQL( $query ); $tix->OrderBy( FIELD => $test->{'Order'}, ORDER => $order ); ok($tix->Count, "found ticket(s)") or $error = 1; my ($order_ok, $last) = (1, $order eq 'ASC'? '-': 'zzzzzz'); while ( my $t = $tix->Next ) { my $tmp; if ( $order eq 'ASC' ) { $tmp = ((split( /,/, $last))[0] cmp (split( /,/, $t->Subject))[0]); } else { $tmp = -((split( /,/, $last))[-1] cmp (split( /,/, $t->Subject))[-1]); } if ( $tmp > 0 ) { $order_ok = 0; last; } $last = $t->Subject; } ok( $order_ok, "$order order of tickets is good" ) or $error = 1; if ( $error ) { diag "Wrong SQL query:". $tix->BuildSelectQuery; $tix->GotoFirstItem; while ( my $t = $tix->Next ) { diag sprintf "%02d - %s", $t->id, $t->Subject; } } } } } @data = ( { Subject => 'Nobody' }, { Subject => 'Z', Owner => $uids[0] }, { Subject => 'A', Owner => $uids[1] }, ); @tickets = RT::Test->create_tickets( { Queue => $queue->id }, @data ); @test = ( { Order => "Owner" }, ); run_tests(); @data = ( { Subject => 'RT' }, { Subject => 'Z', Creator => $uids[0] }, { Subject => 'A', Creator => $uids[1] }, ); @tickets = RT::Test->create_tickets( { Queue => $queue->id }, @data ); @test = ( { Order => "Creator" }, ); run_tests(); @data = ( { Subject => 'RT' }, { Subject => 'Z', LastUpdatedBy => $uids[0] }, { Subject => 'A', LastUpdatedBy => $uids[1] }, ); @tickets = RT::Test->create_tickets( { Queue => $queue->id }, @data ); @test = ( { Order => "LastUpdatedBy" }, ); run_tests(); @tickets = (); rt-5.0.1/t/ticket/search_by_cf_freeform_multiple.t000644 000765 000024 00000031225 14005011336 023126 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => undef; my $q = RT::Test->load_or_create_queue( Name => 'Regression' ); ok $q && $q->id, 'loaded or created queue'; my $queue = $q->Name; diag "create a CF"; my ($cf_name, $cf_id, $cf) = ("Test", 0, undef); { $cf = RT::CustomField->new( RT->SystemUser ); my ($ret, $msg) = $cf->Create( Name => $cf_name, Queue => $q->id, Type => 'FreeformMultiple', ); ok($ret, "Custom Field Order created"); $cf_id = $cf->id; } my $ylong = "y" x 300; subtest "Creating tickets" => sub { RT::Test->create_tickets( { Queue => $q->id }, { Subject => '-' }, { Subject => 'x', "CustomField-$cf_id" => 'x', }, { Subject => 'y', "CustomField-$cf_id" => 'y', }, { Subject => 'z', "CustomField-$cf_id" => 'z', }, { Subject => 'xy', "CustomField-$cf_id" => [ 'x', 'y' ], }, { Subject => 'xz', "CustomField-$cf_id" => [ 'x', 'z' ], }, { Subject => 'yz', "CustomField-$cf_id" => [ 'y', 'z' ], }, { Subject => 'x_ylong', "CustomField-$cf_id" => [ 'x', $ylong ], }, { Subject => 'ylong', "CustomField-$cf_id" => $ylong, }, ); }; my @tests = ( "CF.{$cf_id} IS NULL" => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 }, "CF.{$cf_id}.Content IS NULL" => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 }, "CF.{$cf_id}.LargeContent IS NULL" => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 0 }, "'CF.{$cf_name}' IS NULL" => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 }, "'CF.{$cf_name}.Content' IS NULL" => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 }, "'CF.{$cf_name}.LargeContent' IS NULL" => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 0 }, "'CF.$queue.{$cf_id}' IS NULL" => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 }, "'CF.$queue.{$cf_name}' IS NULL" => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 }, "CF.{$cf_id} IS NOT NULL" => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 }, "CF.{$cf_id}.Content IS NOT NULL" => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 0 }, "CF.{$cf_id}.LargeContent IS NOT NULL" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 }, "'CF.{$cf_name}' IS NOT NULL" => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 }, "'CF.{$cf_name}.Content' IS NOT NULL" => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 0 }, "'CF.{$cf_name}.LargeContent' IS NOT NULL" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 }, "'CF.$queue.{$cf_id}' IS NOT NULL" => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 }, "'CF.$queue.{$cf_name}' IS NOT NULL" => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 }, "CF.{$cf_id} = 'x'" => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 }, "CF.{$cf_id}.Content = 'x'" => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 }, "CF.{$cf_id}.LargeContent = 'x'" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 }, "CF.{$cf_id} = '$ylong'" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 }, "CF.{$cf_id}.Content = '$ylong'" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 }, "CF.{$cf_id}.LargeContent = '$ylong'" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 }, "CF.{$cf_id} LIKE 'yyy'" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 }, "CF.{$cf_id}.Content LIKE 'yyy'" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 }, "CF.{$cf_id}.LargeContent LIKE 'yyy'" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 }, "'CF.{$cf_name}' = 'x'" => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 }, "'CF.{$cf_name}.Content' = 'x'" => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 }, "'CF.{$cf_name}.LargeContent' = 'x'" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 }, "'CF.$queue.{$cf_id}' = 'x'" => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 }, "'CF.$queue.{$cf_name}' = 'x'" => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 }, "CF.{$cf_id} != 'x'" => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1, x_ylong => 0, ylong => 1 }, "CF.{$cf_id}.Content != 'x'" => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1, x_ylong => 0, ylong => 1 }, "CF.{$cf_id}.LargeContent != 'x'" => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 }, "CF.{$cf_id} != '$ylong'" => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 0, ylong => 0 }, "CF.{$cf_id}.Content != '$ylong'" => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 }, "CF.{$cf_id}.LargeContent != '$ylong'" => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 0, ylong => 0 }, "TODO: CF.{$cf_id} NOT LIKE 'yyy'" => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 0, ylong => 0 }, "CF.{$cf_id}.Content NOT LIKE 'yyy'" => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 }, "CF.{$cf_id}.LargeContent NOT LIKE 'yyy'" => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 0, ylong => 0 }, "'CF.{$cf_name}' != 'x'" => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1, x_ylong => 0, ylong => 1 }, "'CF.{$cf_name}.Content' != 'x'" => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1, x_ylong => 0, ylong => 1 }, "'CF.{$cf_name}.LargeContent' != 'x'" => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 }, "'CF.$queue.{$cf_id}' != 'x'" => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1, x_ylong => 0, ylong => 1 }, "'CF.$queue.{$cf_name}' != 'x'" => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1, x_ylong => 0, ylong => 1 }, "CF.{$cf_id} = 'x' OR CF.{$cf_id} = 'y'" => { '-' => 0, x => 1, y => 1, z => 0, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 0 }, "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' = 'y'" => { '-' => 0, x => 1, y => 1, z => 0, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 0 }, "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' = 'y'" => { '-' => 0, x => 1, y => 1, z => 0, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 0 }, "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' = 'y'" => { '-' => 0, x => 1, y => 1, z => 0, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 0 }, "CF.{$cf_id} = 'x' AND CF.{$cf_id} = 'y'" => { '-' => 0, x => 0, y => 0, z => 0, xy => 1, xz => 0, yz => 0, x_ylong => 0, ylong => 0 }, "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' = 'y'" => { '-' => 0, x => 0, y => 0, z => 0, xy => 1, xz => 0, yz => 0, x_ylong => 0, ylong => 0 }, "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' = 'y'" => { '-' => 0, x => 0, y => 0, z => 0, xy => 1, xz => 0, yz => 0, x_ylong => 0, ylong => 0 }, "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' = 'y'" => { '-' => 0, x => 0, y => 0, z => 0, xy => 1, xz => 0, yz => 0, x_ylong => 0, ylong => 0 }, "CF.{$cf_id} != 'x' AND CF.{$cf_id} != 'y'" => { '-' => 1, x => 0, y => 0, z => 1, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 1 }, "'CF.{$cf_name}' != 'x' AND 'CF.{$cf_name}' != 'y'" => { '-' => 1, x => 0, y => 0, z => 1, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 1 }, "'CF.$queue.{$cf_id}' != 'x' AND 'CF.$queue.{$cf_id}' != 'y'" => { '-' => 1, x => 0, y => 0, z => 1, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 1 }, "'CF.$queue.{$cf_name}' != 'x' AND 'CF.$queue.{$cf_name}' != 'y'" => { '-' => 1, x => 0, y => 0, z => 1, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 1 }, "CF.{$cf_id} = 'x' AND CF.{$cf_id} IS NULL" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 }, "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' IS NULL" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 }, "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' IS NULL" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 }, "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' IS NULL" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 }, "CF.{$cf_id} = 'x' OR CF.{$cf_id} IS NULL" => { '-' => 1, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 }, "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NULL" => { '-' => 1, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 }, "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NULL" => { '-' => 1, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 }, "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NULL" => { '-' => 1, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 }, "CF.{$cf_id} = 'x' AND CF.{$cf_id} IS NOT NULL" => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 }, "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' IS NOT NULL" => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 }, "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' IS NOT NULL" => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 }, "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' IS NOT NULL" => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 }, "CF.{$cf_id} = 'x' OR CF.{$cf_id} IS NOT NULL" => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 }, "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NOT NULL" => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 }, "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NOT NULL" => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 }, "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NOT NULL" => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 }, ); run_tests(@tests); sub run_tests { my @tests = @_; while (@tests) { my $query = shift @tests; my %results = %{ shift @tests }; local $TODO = "Not implemented correctly" if $query =~ s/^TODO:\s*//; subtest $query => sub { my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL( "$query" ); my $error = 0; my $count = 0; $count++ foreach grep $_, values %results; is($tix->Count, $count, "found correct number of ticket(s)") or $error = 1; my $good_tickets = ($tix->Count == $count); while ( my $ticket = $tix->Next ) { next if $results{ $ticket->Subject }; diag $ticket->Subject ." ticket has been found when it's not expected"; $good_tickets = 0; } ok( $good_tickets, "all tickets are good" ) or $error = 1; diag "Wrong SQL: ". $tix->BuildSelectQuery if $error; }; } } done_testing; rt-5.0.1/t/ticket/search_by_txn.t000644 000765 000024 00000001724 14005011336 017550 0ustar00sunnavystaff000000 000000 use warnings; use strict; BEGIN{ $ENV{'TZ'} = 'GMT'}; use RT::Test tests => 10; my $SUBJECT = "Search test - ".$$; use_ok('RT::Tickets'); my $tix = RT::Tickets->new(RT->SystemUser); can_ok($tix, 'FromSQL'); $tix->FromSQL('Updated = "2005-08-05" AND Subject = "$SUBJECT"'); ok(! $tix->Count, "Searching for tickets updated on a random date finds nothing" . $tix->Count); my $ticket = RT::Ticket->new(RT->SystemUser); $ticket->Create(Queue => 'General', Subject => $SUBJECT); ok ($ticket->id, "We created a ticket"); my ($id, $txnid, $txnobj) = $ticket->Comment( Content => 'A comment that happend on 2004-01-01'); isa_ok($txnobj, 'RT::Transaction'); ok($txnobj->CreatedObj->ISO); my ( $sid,$smsg) = $txnobj->__Set(Field => 'Created', Value => '2005-08-05 20:00:56'); ok($sid,$smsg); is($txnobj->Created,'2005-08-05 20:00:56'); is($txnobj->CreatedObj->ISO,'2005-08-05 20:00:56'); $tix->FromSQL(qq{Updated = "2005-08-05" AND Subject = "$SUBJECT"}); is( $tix->Count, 1); rt-5.0.1/t/ticket/time-worked.t000644 000765 000024 00000004257 14005011336 017153 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 27; my $queue = RT::Test->load_or_create_queue( Name => 'General' ); ok $queue && $queue->id, "loaded or created a queue"; note 'set on Create'; { my $ticket = RT::Test->create_ticket( Queue => $queue->id, TimeWorked => 10, ); is $ticket->TimeWorked, 10, 'correct value'; my $txn = RT::Transaction->new( RT->SystemUser ); $txn->LoadByCols( ObjectType => 'RT::Ticket', ObjectId => $ticket->id, Type => 'Create', ); ok $txn->id, 'found transaction'; is $txn->TimeTaken, 10, 'correct value'; } note 'set on Comment'; { my $ticket = RT::Test->create_ticket( Queue => $queue->id ); ok !$ticket->TimeWorked, 'correct value'; $ticket->Comment( Content => 'test', TimeTaken => 10 ); is $ticket->TimeWorked, 10, 'correct value'; my $txn = RT::Transaction->new( RT->SystemUser ); $txn->LoadByCols( ObjectType => 'RT::Ticket', ObjectId => $ticket->id, Type => 'Comment', ); ok $txn->id, 'found transaction'; is $txn->TimeTaken, 10, 'correct value'; } note 'update'; { my $ticket = RT::Test->create_ticket( Queue => $queue->id ); ok !$ticket->TimeWorked, 'correct value'; $ticket->SetTimeWorked( 10 ); is $ticket->TimeWorked, 10, 'correct value'; my $txn = RT::Transaction->new( RT->SystemUser ); $txn->LoadByCols( ObjectType => 'RT::Ticket', ObjectId => $ticket->id, Type => 'Set', Field => 'TimeWorked', ); ok $txn->id, 'found transaction'; is $txn->TimeTaken, 10, 'correct value'; } note 'on Merge'; { my $ticket = RT::Test->create_ticket( Queue => $queue->id, TimeWorked => 7, ); { my $tmp = RT::Test->create_ticket( Queue => $queue->id, TimeWorked => 13, ); my ($status, $msg) = $tmp->MergeInto( $ticket->id ); ok $status, "merged tickets"; } $ticket->Load( $ticket->id ); is $ticket->TimeWorked, 20, 'correct value'; } sub dump_txns { my $ticket = shift; my $txns = $ticket->Transactions; while ( my $txn = $txns->Next ) { diag sprintf "#%d\t%s\t%s\t%d", map $txn->$_() // '', qw(id Type Field TimeTaken); } } rt-5.0.1/t/ticket/search.t000644 000765 000024 00000031000 14005011336 016153 0ustar00sunnavystaff000000 000000 # tests relating to searching. Especially around custom fields, and # corner cases. use strict; use warnings; use RT::Test nodata => 1, tests => undef; # setup the queue my $q = RT::Queue->new(RT->SystemUser); my $queue = 'SearchTests-'.$$; $q->Create(Name => $queue); ok ($q->id, "Created the queue"); # and setup the CFs # we believe the Type shouldn't matter. my $cf = RT::CustomField->new(RT->SystemUser); $cf->Create(Name => 'SearchTest', Type => 'Freeform', MaxValues => 0, Queue => $q->id); ok($cf->id, "Created the SearchTest CF"); my $cflabel = "CustomField-".$cf->id; my $cf2 = RT::CustomField->new(RT->SystemUser); $cf2->Create(Name => 'SearchTest2', Type => 'Freeform', MaxValues => 0, Queue => $q->id); ok($cf2->id, "Created the SearchTest2 CF"); my $cflabel2 = "CustomField-".$cf2->id; my $cf3 = RT::CustomField->new(RT->SystemUser); $cf3->Create(Name => 'SearchTest3', Type => 'Freeform', MaxValues => 0, Queue => $q->id); ok($cf3->id, "Created the SearchTest3 CF"); my $cflabel3 = "CustomField-".$cf3->id; # There was a bug involving a missing join to ObjectCustomFields that # caused spurious results on negative searches if another custom field # with the same name existed on a different queue. Hence, we make # duplicate CFs on a different queue here my $dup = RT::Queue->new(RT->SystemUser); $dup->Create(Name => $queue . "-Copy"); ok ($dup->id, "Created the duplicate queue"); my $dupcf = RT::CustomField->new(RT->SystemUser); $dupcf->Create(Name => 'SearchTest', Type => 'Freeform', MaxValues => 0, Queue => $dup->id); ok($dupcf->id, "Created the duplicate SearchTest CF"); $dupcf = RT::CustomField->new(RT->SystemUser); $dupcf->Create(Name => 'SearchTest2', Type => 'Freeform', MaxValues => 0, Queue => $dup->id); ok($dupcf->id, "Created the SearchTest2 CF"); $dupcf = RT::CustomField->new(RT->SystemUser); $dupcf->Create(Name => 'SearchTest3', Type => 'Freeform', MaxValues => 0, Queue => $dup->id); ok($dupcf->id, "Created the SearchTest3 CF"); # setup some tickets # we'll need a small pile of them, to test various combinations and nulls. # there's probably a way to think harder and do this with fewer my $t1 = RT::Ticket->new(RT->SystemUser); my ( $id, undef, $msg ) = $t1->Create( Queue => $q->id, Subject => 'SearchTest1', Requestor => ['search1@example.com'], $cflabel => 'foo1', $cflabel2 => 'bar1', $cflabel3 => 'qux1', ); ok( $id, $msg ); my $t2 = RT::Ticket->new(RT->SystemUser); ( $id, undef, $msg ) = $t2->Create( Queue => $q->id, Subject => 'SearchTest2', Requestor => ['search2@example.com'], # $cflabel => 'foo2', $cflabel2 => 'bar2', $cflabel3 => 'qux2', ); ok( $id, $msg ); my $t3 = RT::Ticket->new(RT->SystemUser); ( $id, undef, $msg ) = $t3->Create( Queue => $q->id, Subject => 'SearchTest3', Requestor => ['search3@example.com'], $cflabel => 'foo3', # $cflabel2 => 'bar3', $cflabel3 => 'qux3', ); ok( $id, $msg ); my $t4 = RT::Ticket->new(RT->SystemUser); ( $id, undef, $msg ) = $t4->Create( Queue => $q->id, Subject => 'SearchTest4', Requestor => ['search4@example.com'], $cflabel => 'foo4', $cflabel2 => 'bar4', # $cflabel3 => 'qux4', ); ok( $id, $msg ); my $t5 = RT::Ticket->new(RT->SystemUser); ( $id, undef, $msg ) = $t5->Create( Queue => $q->id, # Subject => 'SearchTest5', Requestor => ['search5@example.com'], $cflabel => 'foo5', $cflabel2 => 'bar5', $cflabel3 => 'qux5', ); ok( $id, $msg ); my $t6 = RT::Ticket->new(RT->SystemUser); ( $id, undef, $msg ) = $t6->Create( Queue => $q->id, Subject => 'SearchTest6', # Requestor => ['search6@example.com'], $cflabel => 'foo6', $cflabel2 => 'bar6', $cflabel3 => 'qux6', ); ok( $id, $msg ); my $t7 = RT::Ticket->new(RT->SystemUser); ( $id, undef, $msg ) = $t7->Create( Queue => $q->id, Subject => 'SearchTest7', Requestor => ['search7@example.com'], # $cflabel => 'foo7', # $cflabel2 => 'bar7', $cflabel3 => 'qux7', ); ok( $id, $msg ); # we have tickets. start searching my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue'"); is($tix->Count, 7, "found all the tickets") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; # very simple searches. both CF and normal $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND CF.SearchTest = 'foo1'"); is($tix->Count, 1, "matched identical subject") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue LIKE '$queue' AND CF.SearchTest = 'foo1'"); is($tix->Count, 1, "matched identical subject and LIKE Queue") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND CF.SearchTest LIKE 'foo1'"); is($tix->Count, 1, "matched LIKE subject") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue LIKE '$queue' AND CF.SearchTest LIKE 'foo1'"); is($tix->Count, 1, "matched LIKE queue and LIKE subject") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND CF.SearchTest = 'foo'"); is($tix->Count, 0, "IS a regexp match") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND CF.SearchTest LIKE 'foo'"); is($tix->Count, 5, "matched LIKE subject") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND CF.SearchTest IS NULL"); is($tix->Count, 2, "IS null CF") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Requestors LIKE 'search1'"); is($tix->Count, 1, "LIKE requestor") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Requestors = 'search1\@example.com'"); is($tix->Count, 1, "IS requestor") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Requestors LIKE 'search'"); is($tix->Count, 6, "LIKE requestor") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Requestors IS NULL"); is($tix->Count, 1, "Search for no requestor") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Subject = 'SearchTest1'"); is($tix->Count, 1, "IS subject") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Subject LIKE 'SearchTest1'"); is($tix->Count, 1, "LIKE subject") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Subject = ''"); is($tix->Count, 1, "found one ticket") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Subject LIKE 'SearchTest'"); is($tix->Count, 6, "found two ticket") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND Subject LIKE 'qwerty'"); is($tix->Count, 0, "found zero ticket") or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery; # various combinations $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("CF.SearchTest LIKE 'foo' AND CF.SearchTest2 LIKE 'bar1'"); is($tix->Count, 1, "LIKE cf and LIKE cf"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("CF.SearchTest = 'foo1' AND CF.SearchTest2 = 'bar1'"); is($tix->Count, 1, "is cf and is cf"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("CF.SearchTest = 'foo' AND CF.SearchTest2 LIKE 'bar1'"); is($tix->Count, 0, "is cf and like cf"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("CF.SearchTest LIKE 'foo' AND CF.SearchTest2 LIKE 'bar' AND CF.SearchTest3 LIKE 'qux'"); is($tix->Count, 3, "like cf and like cf and like cf"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("CF.SearchTest LIKE 'foo' AND CF.SearchTest2 LIKE 'bar' AND CF.SearchTest3 LIKE 'qux6'"); is($tix->Count, 1, "like cf and like cf and is cf"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("CF.SearchTest LIKE 'foo' AND Subject LIKE 'SearchTest'"); is($tix->Count, 4, "like cf and like subject"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("CF.SearchTest IS NULL AND CF.SearchTest2 = 'bar2'"); is($tix->Count, 1, "null cf and is cf"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("Queue = '$queue' AND CF.SearchTest IS NULL AND CF.SearchTest2 IS NULL"); is($tix->Count, 1, "null cf and null cf"); # tests with the same CF listed twice $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("CF.{SearchTest} = 'foo1'"); is($tix->Count, 1, "is cf.{name} format"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("CF.SearchTest = 'foo1' OR CF.SearchTest = 'foo3'"); is($tix->Count, 2, "is cf1 or is cf1"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("CF.SearchTest = 'foo1' OR CF.SearchTest IS NULL"); is($tix->Count, 3, "is cf1 or null cf1"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("(CF.SearchTest = 'foo1' OR CF.SearchTest = 'foo3') AND (CF.SearchTest2 = 'bar1' OR CF.SearchTest2 = 'bar2')"); is($tix->Count, 1, "(is cf1 or is cf1) and (is cf2 or is cf2)"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("(Queue LIKE '$queue') AND (CF.SearchTest = 'foo1' OR CF.SearchTest = 'foo3') AND (CF.SearchTest2 = 'bar1' OR CF.SearchTest2 = 'bar2')"); is($tix->Count, 1, "(queue LIKE) and (is cf1 or is cf1) and (is cf2 or is cf2)"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("CF.SearchTest = 'foo1' OR CF.SearchTest = 'foo3' OR CF.SearchTest2 = 'bar1' OR CF.SearchTest2 = 'bar2'"); is($tix->Count, 3, "is cf1 or is cf1 or is cf2 or is cf2"); # tests with disabled CF $cf->SetDisabled(1); ok($cf->Disabled, 'cf1 is disabled'); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("CF.SearchTest = 'foo1'"); is($tix->Count, 0, "disabled cf1 with name"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("CF." . $cf->id . " = 'foo1'"); is($tix->Count, 0, "disabled cf1 with id"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("CF.SearchTest != 'foo1'"); is($tix->Count, 7, "disabled cf1 with name and negative operator"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL("CF." . $cf->id . " != 'foo1'"); is($tix->Count, 7, "disabled cf1 with id and negative operator"); $cf->SetDisabled(0); ok(!$cf->Disabled, 'cf1 is enabled'); # tests with lower cased NULL $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL('Requestor.Name IS null'); is($tix->Count, 1, "t6 doesn't have a Requestor"); like($tix->BuildSelectCountQuery, qr/\bNULL\b/, "Contains upper-case NULL"); unlike($tix->BuildSelectCountQuery, qr/\bnull\b/, "Lacks lower-case NULL"); # tests for searching by queue lifecycle $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL('Lifecycle="default"'); is($tix->Count,7,"We found all 7 tickets in a queue with the default lifecycle"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL('Lifecycle ="approvals" OR Lifecycle="default"'); is($tix->Count,7,"We found 7 tickets in a queue with a lifecycle of default or approvals"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL('Lifecycle ="approvals" AND Lifecycle="default"'); is($tix->Count,0,"We found 0 tickets in a queue with a lifecycle of default AND approvals...(because that's impossible"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL('Queue="'.$queue.'" AND Lifecycle="default"'); is($tix->Count,7,"We found 7 tickets in $queue with a lifecycle of default"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL('Lifecycle !="approvals"'); is($tix->Count,7,"We found 7 tickets in a queue with a lifecycle other than approvals"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL('Lifecycle!="default"'); is($tix->Count,0,"We found 0 tickets in a queue with a lifecycle other than default"); $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL('Lifecycle="approvals"'); is($tix->Count,0,"We found 0 tickets in a queue with the approvals lifecycle"); done_testing; rt-5.0.1/t/ticket/link_search.t000644 000765 000024 00000020651 14005011336 017202 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; # Load the config file use RT::Test tests => 63; #Connect to the database and get RT::SystemUser and RT::Nobody loaded #Get the current user all loaded my $CurrentUser = RT->SystemUser; my $queue = RT::Queue->new($CurrentUser); $queue->Load('General') || Abort(loc("Queue could not be loaded.")); my $child_ticket = RT::Ticket->new( $CurrentUser ); my ($childid) = $child_ticket->Create( Subject => 'test child', Queue => $queue->Id, ); ok($childid, "We created a child ticket"); my $parent_ticket = RT::Ticket->new( $CurrentUser ); my ($parentid) = $parent_ticket->Create( Subject => 'test parent', Children => [ $childid ], Queue => $queue->Id, ); ok($parentid, "We created a parent ticket"); my $Collection = RT::Tickets->new($CurrentUser); $Collection->LimitMemberOf( $parentid ); is($Collection->Count,1, "We found only one result"); ok($Collection->First); is($Collection->First->id, $childid, "We found the collection of all children of $parentid with Limit"); $Collection = RT::Tickets->new($CurrentUser); $Collection->FromSQL("MemberOf = $parentid"); is($Collection->Count, 1, "We found only one result"); ok($Collection->First); is($Collection->First->id, $childid, "We found the collection of all children of $parentid with TicketSQL"); $Collection = RT::Tickets->new($CurrentUser); $Collection->LimitHasMember ($childid); is($Collection->Count,1, "We found only one result"); ok($Collection->First); is($Collection->First->id, $parentid, "We found the collection of all parents of $childid with Limit"); $Collection = RT::Tickets->new($CurrentUser); $Collection->FromSQL("HasMember = $childid"); is($Collection->Count,1, "We found only one result"); ok($Collection->First); is($Collection->First->id, $parentid, "We found the collection of all parents of $childid with TicketSQL"); # Now we find a collection of all the tickets which have no members. they should have no children. $Collection = RT::Tickets->new($CurrentUser); $Collection->LimitHasMember(''); # must contain child; must not contain parent my %has; while (my $t = $Collection->Next) { ++$has{$t->id}; } ok( $has{$childid}, "The collection has our child - $childid"); ok( !$has{$parentid}, "The collection doesn't have our parent - $parentid"); # Now we find a collection of all the tickets which are not members of anything. they should have no parents. $Collection = RT::Tickets->new($CurrentUser); $Collection->LimitMemberOf(''); # must contain parent; must not contain child %has = (); while (my $t = $Collection->Next) { ++$has{$t->id}; } ok ($has{$parentid} , "The collection has our parent - $parentid"); ok( !$has{$childid}, "The collection doesn't have our child - $childid"); # Do it all over with TicketSQL # # Now we find a collection of all the tickets which have no members. they should have no children. $Collection = RT::Tickets->new($CurrentUser); $Collection->FromSQL ("HasMember IS NULL"); # must contain parent; must not contain child %has = (); while (my $t = $Collection->Next) { ++$has{$t->id}; } ok( !$has{$parentid}, "The collection doesn't have our parent - $parentid"); ok( $has{$childid}, "The collection has our child - $childid"); # Now we find a collection of all the tickets which have no members. they should have no children. # Alternate syntax $Collection = RT::Tickets->new($CurrentUser); $Collection->FromSQL("HasMember = ''"); # must contain parent; must not contain child %has = (); while (my $t = $Collection->Next) { ++$has{$t->id}; } ok( !$has{$parentid}, "The collection doesn't have our parent - $parentid"); ok( $has{$childid}, "The collection has our child - $childid"); # Now we find a collection of all the tickets which are not members of anything. they should have no parents. $Collection = RT::Tickets->new($CurrentUser); $Collection->FromSQL("MemberOf IS NULL"); # must not contain parent; must contain parent %has = (); while (my $t = $Collection->Next) { ++$has{$t->id}; } ok( $has{$parentid}, "The collection has our parent - $parentid"); ok( !$has{$childid}, "The collection doesn't have our child - $childid"); # Now we find a collection of all the tickets which are not members of anything. they should have no parents. $Collection = RT::Tickets->new($CurrentUser); $Collection->FromSQL("MemberOf = ''"); # must not contain parent; must contain parent %has = (); while (my $t = $Collection->Next) { ++$has{$t->id}; } ok( $has{$parentid}, "The collection has our parent - $parentid"); ok( !$has{$childid}, "The collection doesn't have our child - $childid"); # Now we find a collection of all the tickets which are not members of the parent ticket $Collection = RT::Tickets->new($CurrentUser); $Collection->FromSQL("MemberOf != $parentid"); %has = (); while (my $t = $Collection->Next) { ++$has{$t->id}; } ok( $has{$parentid}, "The collection has our parent - $parentid"); ok( !$has{$childid}, "The collection doesn't have our child - $childid"); $Collection = RT::Tickets->new($CurrentUser); $Collection->LimitMemberOf($parentid, OPERATOR => '!='); %has = (); while (my $t = $Collection->Next) { ++$has{$t->id}; } ok( $has{$parentid}, "The collection has our parent - $parentid"); ok( !$has{$childid}, "The collection doesn't have our child - $childid"); my $grand_child_ticket = RT::Ticket->new( $CurrentUser ); my ($grand_childid) = $child_ticket->Create( Subject => 'test child', Queue => $queue->Id, MemberOf => $childid, ); ok($childid, "We created a grand child ticket"); my $unlinked_ticket = RT::Ticket->new( $CurrentUser ); my ($unlinked_id) = $child_ticket->Create( Subject => 'test unlinked', Queue => $queue->Id, ); ok($unlinked_id, "We created a grand child ticket"); $Collection = RT::Tickets->new($CurrentUser); $Collection->FromSQL( "LinkedTo = $childid" ); is($Collection->Count,1, "We found only one result"); ok($Collection->First); is($Collection->First->id, $grand_childid, "We found all tickets linked to ticket #$childid"); $Collection = RT::Tickets->new($CurrentUser); $Collection->FromSQL( "LinkedFrom = $childid" ); is($Collection->Count,1, "We found only one result"); ok($Collection->First); is($Collection->First->id, $parentid, "We found all tickets linked from ticket #$childid"); $Collection = RT::Tickets->new($CurrentUser); $Collection->FromSQL( "LinkedTo IS NULL" ); ok($Collection->Count, "Result is set is not empty"); %has = (); while (my $t = $Collection->Next) { ++$has{$t->id}; } ok( $has{$parentid}, "parent is in collection"); ok( $has{$unlinked_id}, "unlinked is in collection"); ok( !$has{$childid}, "child is NOT in collection"); ok( !$has{$grand_childid}, "grand child too is not in collection"); $Collection = RT::Tickets->new($CurrentUser); $Collection->FromSQL( "LinkedTo IS NOT NULL" ); ok($Collection->Count, "Result set is not empty"); %has = (); while (my $t = $Collection->Next) { ++$has{$t->id}; } ok( !$has{$parentid}, "The collection has no our parent - $parentid"); ok( !$has{$unlinked_id}, "unlinked is not in collection"); ok( $has{$childid}, "The collection have our child - $childid"); ok( $has{$grand_childid}, "The collection have our grand child - $grand_childid"); $Collection = RT::Tickets->new($CurrentUser); $Collection->FromSQL( "LinkedFrom IS NULL" ); ok($Collection->Count, "Result is set is not empty"); %has = (); while (my $t = $Collection->Next) { ++$has{$t->id}; } ok( !$has{$parentid}, "parent is NOT in collection"); ok( !$has{$childid}, "child is NOT in collection"); ok( $has{$grand_childid}, "grand child is in collection"); ok( $has{$unlinked_id}, "unlinked is in collection"); $Collection = RT::Tickets->new($CurrentUser); $Collection->FromSQL( "LinkedFrom IS NOT NULL" ); ok($Collection->Count, "Result set is not empty"); %has = (); while (my $t = $Collection->Next) { ++$has{$t->id}; } ok( $has{$parentid}, "The collection has our parent - $parentid"); ok( $has{$childid}, "The collection have our child - $childid"); ok( !$has{$grand_childid}, "The collection have no our grand child - $grand_childid"); ok( !$has{$unlinked_id}, "unlinked is not in collection"); $Collection = RT::Tickets->new($CurrentUser); $Collection->FromSQL( "Linked = $childid" ); is($Collection->Count, 2, "We found two tickets: parent and child"); %has = (); while (my $t = $Collection->Next) { ++$has{$t->id}; } ok( !$has{$childid}, "Ticket is not linked to itself"); ok( $has{$parentid}, "The collection has our parent"); ok( $has{$grand_childid}, "The collection have our child"); ok( !$has{$unlinked_id}, "unlinked is not in collection"); rt-5.0.1/t/lifecycles/unresolved-deps.t000644 000765 000024 00000002122 14005011336 020667 0ustar00sunnavystaff000000 000000 use strict; use warnings; BEGIN {require './t/lifecycles/utils.pl'}; my $general = RT::Test->load_or_create_queue( Name => 'General', ); ok $general && $general->id, 'loaded or created a queue'; # different value tested in basics RT->Config->Set('HideResolveActionsWithDependencies' => 1); my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in'; { my $child_ticket = RT::Test->create_ticket( Queue => $general->id, Subject => 'child', ); my $cid = $child_ticket->id; my $parent_ticket = RT::Test->create_ticket( Queue => $general->id, Subject => 'parent', DependsOn => $child_ticket->id, ); my $pid = $parent_ticket->id; ok $m->goto_ticket( $pid ), 'opened a ticket'; $m->check_links( has => ['Open It'], has_no => ['Stall', 'Re-open', 'Undelete', 'Resolve', 'Reject', 'Delete'], ); ok $m->goto_ticket( $cid ), 'opened a ticket'; $m->check_links( has => ['Open It', 'Resolve', 'Reject', 'Delete'], has_no => ['Stall', 'Re-open', 'Undelete'], ); } done_testing; rt-5.0.1/t/lifecycles/types.t000644 000765 000024 00000003242 14005011336 016720 0ustar00sunnavystaff000000 000000 use strict; use warnings; BEGIN {require './t/lifecycles/utils.pl'}; is_deeply( [ RT::Lifecycle->ListAll ], [qw/ approvals default delivery sales sales-engineering/], "Get the list of all lifecycles (implicitly for for tickets)"); is_deeply( [ RT::Lifecycle->ListAll('ticket') ], [qw/ approvals default delivery sales sales-engineering/], "Get the list of all lifecycles for tickets"); is_deeply( [ RT::Lifecycle->List], [qw/ default delivery sales sales-engineering /], "Get the list of lifecycles without approvals (implicitly for for tickets)"); is_deeply( [ RT::Lifecycle->List('ticket') ], [qw/ default delivery sales sales-engineering/], "Get the list of lifecycles without approvals for tickets"); is_deeply( [ RT::Lifecycle->List('racecar') ], [qw/ racing /], "Get the list of lifecycles for other types"); my $tickets = RT::Lifecycle->Load( Name => '', Type => 'ticket' ); ok($tickets, "Got a generalized lifecycle for tickets"); isa_ok( $tickets, "RT::Lifecycle::Ticket", "Is the right subclass" ); is_deeply( [ sort $tickets->Valid ], [ sort qw(new open stalled resolved rejected deleted ordered), 'on way', 'delayed', 'delivered', 'sales', 'engineering', 'initial', 'active', 'inactive'], "Only gets ticket statuses" ); my $racecars = RT::Lifecycle->Load( Name => '', Type => 'racecar' ); ok($racecars, "Got a generalized lifecycle for racecars"); isa_ok( $racecars, "RT::Lifecycle", "Is the generalized subclass" ); is_deeply( [ sort $racecars->Valid ], [ sort ('on-your-mark', 'get-set', 'go', 'first', 'second', 'third', 'no-place') ], "Only gets racecar statuses" ); done_testing; rt-5.0.1/t/lifecycles/moving.t000644 000765 000024 00000005623 14005011336 017060 0ustar00sunnavystaff000000 000000 use strict; use warnings; BEGIN {require './t/lifecycles/utils.pl'}; my $general = RT::Test->load_or_create_queue( Name => 'General', ); ok $general && $general->id, 'loaded or created a queue'; my $delivery = RT::Test->load_or_create_queue( Name => 'delivery', Lifecycle => 'delivery', ); ok $delivery && $delivery->id, 'loaded or created a queue'; my $tstatus = sub { DBIx::SearchBuilder::Record::Cachable->FlushCache; my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $_[0] ); return $ticket->Status; }; diag "check moving without a map"; { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $general->id, Subject => 'test', Status => 'new', ); ok $id, 'created a ticket'; (my $status, $msg) = $ticket->SetQueue( $delivery->id ); ok !$status, "couldn't change queue when there is no maps between schemas"; is $ticket->Queue, $general->id, 'queue is steal the same'; is $ticket->Status, 'new', 'status is steal the same'; } diag "add partial map"; { my $schemas = RT->Config->Get('Lifecycles'); $schemas->{'__maps__'} = { 'default -> delivery' => { new => 'ordered', }, }; RT::Lifecycle->FillCache; } diag "check moving with a partial map"; { { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $general->id, Subject => 'test', Status => 'new', ); ok $id, 'created a ticket'; (my $status, $msg) = $ticket->SetQueue( $delivery->id ); ok $status, "moved ticket between queues with different schemas"; is $ticket->Queue, $delivery->id, 'queue has been changed' or diag "error: $msg"; is $ticket->Status, 'ordered', 'status has been changed'; } { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $general->id, Subject => 'test', Status => 'open', ); ok $id, 'created a ticket'; (my $status, $msg) = $ticket->SetQueue( $delivery->id ); ok !$status, "couldn't change queue when map is not complete"; is $ticket->Queue, $general->id, 'queue is steal the same'; is $ticket->Status, 'open', 'status is steal the same'; } } diag "one way map doesn't work backwards"; { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $delivery->id, Subject => 'test', Status => 'ordered', ); ok $id, 'created a ticket'; (my $status, $msg) = $ticket->SetQueue( $general->id ); ok !$status, "couldn't change queue when there is no maps between schemas"; is $ticket->Queue, $delivery->id, 'queue is steal the same'; is $ticket->Status, 'ordered', 'status is steal the same'; } done_testing; rt-5.0.1/t/lifecycles/utils.pl000644 000765 000024 00000005610 14005011336 017065 0ustar00sunnavystaff000000 000000 use strict; use warnings; my $config; BEGIN { $config = < { initial => [qw(new)], active => [qw(open stalled)], inactive => [qw(resolved rejected deleted)], defaults => { on_create => 'new', }, transitions => { '' => [qw(new open resolved)], new => [qw(open resolved rejected deleted)], open => [qw(stalled resolved rejected deleted)], stalled => [qw(open)], resolved => [qw(open)], rejected => [qw(open)], deleted => [qw(open)], }, actions => { 'new -> open' => {label => 'Open It', update => 'Respond'}, 'new -> resolved' => {label => 'Resolve', update => 'Comment'}, 'new -> rejected' => {label => 'Reject', update => 'Respond'}, 'new -> deleted' => {label => 'Delete', update => ''}, 'open -> stalled' => {label => 'Stall', update => 'Comment'}, 'open -> resolved' => {label => 'Resolve', update => 'Comment'}, 'open -> rejected' => {label => 'Reject', update => 'Respond'}, 'stalled -> open' => {label => 'Open It', update => ''}, 'resolved -> open' => {label => 'Re-open', update => 'Comment'}, 'rejected -> open' => {label => 'Re-open', update => 'Comment'}, 'deleted -> open' => {label => 'Undelete', update => ''}, }, }, delivery => { initial => ['ordered'], active => ['on way', 'delayed'], inactive => ['delivered'], defaults => { on_create => 'ordered', }, transitions => { '' => ['ordered'], ordered => ['on way', 'delayed'], 'on way' => ['delivered'], delayed => ['on way'], delivered => [], }, actions => { 'ordered -> on way' => {label => 'Put On Way', update => 'Respond'}, 'ordered -> delayed' => {label => 'Delay', update => 'Respond'}, 'on way -> delivered' => {label => 'Done', update => 'Respond'}, 'delayed -> on way' => {label => 'Put On Way', update => 'Respond'}, }, }, racing => { type => 'racecar', active => ['on-your-mark', 'get-set', 'go'], inactive => ['first', 'second', 'third', 'no-place'], }, "sales" => { type => 'ticket', initial => ['initial'], active => ['active'], inactive => ['inactive'], }, "sales-engineering" => { "initial" => ["sales"], "active" => [ "engineering", "stalled" ], "inactive" => [ "resolved", "rejected", "deleted" ], }, ); END } use RT::Test config => $config, tests => undef; 1; rt-5.0.1/t/lifecycles/dates.t000644 000765 000024 00000024704 14005011336 016662 0ustar00sunnavystaff000000 000000 use strict; use warnings; BEGIN {require './t/lifecycles/utils.pl'}; my $general = RT::Test->load_or_create_queue( Name => 'General', ); ok $general && $general->id, 'loaded or created a queue'; my $delivery = RT::Test->load_or_create_queue( Name => 'delivery', Lifecycle => 'delivery', ); ok $delivery && $delivery->id, 'loaded or created a queue'; my $tstatus = sub { DBIx::SearchBuilder::Record::Cachable->FlushCache; my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $_[0] ); return $ticket->Status; }; diag "check basic API"; { my $schema = $general->LifecycleObj; isa_ok($schema, 'RT::Lifecycle'); is $schema->Name, 'default', "it's a default schema"; $schema = $delivery->LifecycleObj; isa_ok($schema, 'RT::Lifecycle'); is $schema->Name, 'delivery', "it's a delivery schema"; } diag "dates on create for default schema"; { { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $general->id, Subject => 'test', Status => 'new', ); ok $id, 'created a ticket'; ok !$ticket->StartedObj->IsSet, 'started is not set'; ok !$ticket->ResolvedObj->IsSet, 'resolved is not set'; } { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $general->id, Subject => 'test', Status => 'open', ); ok $id, 'created a ticket'; ok $ticket->StartedObj->IsSet, 'started is set'; ok !$ticket->ResolvedObj->IsSet, 'resolved is not set'; } { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $general->id, Subject => 'test', Status => 'resolved', ); ok $id, 'created a ticket'; ok $ticket->StartedObj->IsSet, 'started is set'; ok $ticket->ResolvedObj->IsSet, 'resolved is set'; } my $test_date = '2008-11-28 12:00:00'; { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $general->id, Subject => 'test', Status => 'new', Started => $test_date, Resolved => $test_date, ); ok $id, 'created a ticket'; is $ticket->StartedObj->ISO, $test_date, 'started is set'; is $ticket->ResolvedObj->ISO, $test_date, 'resolved is set'; } { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $general->id, Subject => 'test', Status => 'open', Started => $test_date, Resolved => $test_date, ); ok $id, 'created a ticket'; is $ticket->StartedObj->ISO, $test_date, 'started is set'; is $ticket->ResolvedObj->ISO, $test_date, 'resolved is set'; } { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $general->id, Subject => 'test', Status => 'resolved', Started => $test_date, Resolved => $test_date, ); ok $id, 'created a ticket'; is $ticket->StartedObj->ISO, $test_date, 'started is set'; is $ticket->ResolvedObj->ISO, $test_date, 'resolved is set'; } } diag "dates on create for delivery schema"; { { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $delivery->id, Subject => 'test', Status => 'ordered', ); ok $id, 'created a ticket'; is $ticket->StartedObj->Unix , 0, 'started is not set'; is $ticket->ResolvedObj->Unix, 0, 'resolved is not set'; } { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $txn, $msg) = $ticket->Create( Queue => $delivery->id, Subject => 'test', ); ok $id, 'created a ticket'; diag($msg); is $ticket->Status, 'ordered', "Status is ordered"; my ($statusval,$statusmsg) = $ticket->SetStatus('on way'); ok($statusval,$statusmsg); ok $ticket->StartedObj->IsSet, 'started is set to ' .$ticket->StartedObj->AsString ; is $ticket->ResolvedObj->Unix, 0, 'resolved is not set'; } { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $delivery->id, Subject => 'test', ); ok $id, 'created a ticket'; my ($statusval,$statusmsg) = $ticket->SetStatus('on way'); ok($statusval,$statusmsg); ($statusval,$statusmsg) = $ticket->SetStatus('delivered'); ok($statusval,$statusmsg); ok $ticket->StartedObj->IsSet, 'started is set'; ok $ticket->ResolvedObj->IsSet, 'resolved is set'; } my $test_date = '2008-11-28 12:00:00'; { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $statusmsg) = $ticket->Create( Queue => $delivery->id, Subject => 'test', Status => 'ordered', Started => $test_date, Resolved => $test_date, ); ok $id, 'created a ticket'; is $ticket->StartedObj->ISO, $test_date, 'started is set'; is $ticket->ResolvedObj->ISO, $test_date, 'resolved is set'; } { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $delivery->id, Subject => 'test', Status => 'ordered', Started => $test_date, Resolved => $test_date, ); ok $id, 'created a ticket'; my ($statusval,$statusmsg) = $ticket->SetStatus('on way'); ok($statusval,$statusmsg); is $ticket->StartedObj->ISO, $test_date, 'started is set'; is $ticket->ResolvedObj->ISO, $test_date, 'resolved is set'; } { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $delivery->id, Subject => 'test', Started => $test_date, Resolved => $test_date, ); ok $id, 'created a ticket'; my ($statusval,$statusmsg) = $ticket->SetStatus('on way'); ok($statusval,$statusmsg); ($statusval,$statusmsg) = $ticket->SetStatus('delivered'); ok($statusval,$statusmsg); is $ticket->StartedObj->ISO, $test_date, 'started is set'; TODO: { local $TODO = "we should decide if we set resolved repeatedly"; is $ticket->ResolvedObj->ISO, $test_date, 'resolved is set'; }; } } diag "dates on status change for default schema"; { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $general->id, Subject => 'test', Status => 'new', ); ok $id, 'created a ticket'; ok !$ticket->StartedObj->IsSet, 'started is not set'; ok !$ticket->ResolvedObj->IsSet, 'resolved is not set'; (my $status, $msg) = $ticket->SetStatus('open'); ok $status, 'changed status' or diag "error: $msg"; ok $ticket->StartedObj->IsSet, 'started is set'; ok !$ticket->ResolvedObj->IsSet, 'resolved is not set'; my $started = $ticket->StartedObj->Unix; ($status, $msg) = $ticket->SetStatus('stalled'); ok $status, 'changed status' or diag "error: $msg"; is $ticket->StartedObj->Unix, $started, 'started is set and the same'; ok !$ticket->ResolvedObj->IsSet, 'resolved is not set'; ($status, $msg) = $ticket->SetStatus('open'); ok $status, 'changed status' or diag "error: $msg"; is $ticket->StartedObj->Unix, $started, 'started is set and the same'; ok !$ticket->ResolvedObj->IsSet, 'resolved is not set'; ($status, $msg) = $ticket->SetStatus('resolved'); ok $status, 'changed status' or diag "error: $msg"; is $ticket->StartedObj->Unix, $started, 'started is set and the same'; ok $ticket->ResolvedObj->IsSet, 'resolved is set'; } diag "dates on status change for delivery schema"; { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $delivery->id, Subject => 'test', Status => 'ordered', ); ok $id, 'created a ticket'; ok !$ticket->StartedObj->IsSet, 'started is not set'; ok !$ticket->ResolvedObj->IsSet, 'resolved is not set'; (my $status, $msg) = $ticket->SetStatus('delayed'); ok $status, 'changed status' or diag "error: $msg"; ok $ticket->StartedObj->IsSet, 'started is set'; ok !$ticket->ResolvedObj->IsSet, 'resolved is not set'; my $started = $ticket->StartedObj->Unix; ($status, $msg) = $ticket->SetStatus('on way'); ok $status, 'changed status' or diag "error: $msg"; is $ticket->StartedObj->Unix, $started, 'started is set and the same'; ok !$ticket->ResolvedObj->IsSet, 'resolved is not set'; ($status, $msg) = $ticket->SetStatus('delivered'); ok $status, 'changed status' or diag "error: $msg"; is $ticket->StartedObj->Unix, $started, 'started is set and the same'; ok $ticket->ResolvedObj->IsSet, 'resolved is set'; } diag "add partial map between general->delivery"; { my $schemas = RT->Config->Get('Lifecycles'); $schemas->{'__maps__'} = { 'default -> delivery' => { new => 'on way', }, 'delivery -> default' => { 'on way' => 'resolved', }, }; RT::Lifecycle->FillCache; } diag "check date changes on moving a ticket"; { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $general->id, Subject => 'test', Status => 'new', ); ok $id, 'created a ticket'; ok !$ticket->StartedObj->IsSet, 'started is not set'; ok !$ticket->ResolvedObj->IsSet, 'resolved is not set'; (my $status, $msg) = $ticket->SetQueue( $delivery->id ); ok $status, "moved ticket between queues with different schemas"; is $ticket->Status, 'on way', 'status has been changed'; ok $ticket->StartedObj->IsSet, 'started is set'; ok !$ticket->ResolvedObj->IsSet, 'resolved is not set'; ($status, $msg) = $ticket->SetQueue( $general->id ); ok $status, "moved ticket between queues with different schemas"; is $ticket->Status, 'resolved', 'status has been changed'; ok $ticket->StartedObj->IsSet, 'started is set'; ok $ticket->ResolvedObj->IsSet, 'resolved is set'; } done_testing; rt-5.0.1/t/lifecycles/basics.t000644 000765 000024 00000023306 14005011336 017023 0ustar00sunnavystaff000000 000000 use strict; use warnings; BEGIN {require './t/lifecycles/utils.pl'}; my $general = RT::Test->load_or_create_queue( Name => 'General', ); ok $general && $general->id, 'loaded or created a queue'; my $tstatus = sub { DBIx::SearchBuilder::Record::Cachable->FlushCache; my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $_[0] ); return $ticket->Status; }; diag "check basic API"; { my $schema = $general->LifecycleObj; isa_ok($schema, 'RT::Lifecycle'); is $schema->Name, 'default', "it's a default schema"; is_deeply [$schema->Valid], [qw(new open stalled resolved rejected deleted)], 'this is the default set from our config file'; foreach my $s ( qw(new open stalled resolved rejected deleted) ) { ok $schema->IsValid($s), "valid"; } ok !$schema->IsValid(), 'invalid'; ok !$schema->IsValid(''), 'invalid'; ok !$schema->IsValid(undef), 'invalid'; ok !$schema->IsValid('foo'), 'invalid'; is_deeply [$schema->Initial], ['new'], 'initial set'; ok $schema->IsInitial('new'), "initial"; ok !$schema->IsInitial('open'), "not initial"; ok !$schema->IsInitial, "not initial"; ok !$schema->IsInitial(''), "not initial"; ok !$schema->IsInitial(undef), "not initial"; ok !$schema->IsInitial('foo'), "not initial"; is_deeply [$schema->Active], [qw(open stalled)], 'active set'; ok( $schema->IsActive($_), "active" ) foreach qw(open stalled); ok !$schema->IsActive('new'), "not active"; ok !$schema->IsActive, "not active"; ok !$schema->IsActive(''), "not active"; ok !$schema->IsActive(undef), "not active"; ok !$schema->IsActive('foo'), "not active"; is_deeply [$schema->Inactive], [qw(resolved rejected deleted)], 'inactive set'; ok( $schema->IsInactive($_), "inactive" ) foreach qw(resolved rejected deleted); ok !$schema->IsInactive('new'), "not inactive"; ok !$schema->IsInactive, "not inactive"; ok !$schema->IsInactive(''), "not inactive"; ok !$schema->IsInactive(undef), "not inactive"; ok !$schema->IsInactive('foo'), "not inactive"; is_deeply [$schema->Transitions('')], [qw(new open resolved)], 'on create transitions'; ok $schema->IsTransition('' => $_), 'good transition' foreach qw(new open resolved); } my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in'; diag "check status input on create"; { $m->goto_create_ticket( $general ); my $form = $m->form_name('TicketCreate'); ok my $input = $form->find_input('Status'), 'found status selector'; my @form_values = $input->possible_values; ok scalar @form_values, 'some options in the UI'; my $valid = 1; foreach ( @form_values ) { next if $general->LifecycleObj->IsValid($_); $valid = 0; diag("$_ doesn't appear to be a valid status, but it was in the form"); } ok $valid, 'all statuses in the form are valid'; } diag "create a ticket"; my $tid; { my $ticket = RT::Ticket->new( RT->SystemUser ); ($tid) = $ticket->Create( Queue => $general->id, Subject => 'test' ); ok $tid, "created a ticket #$tid"; is $ticket->Status, 'new', 'correct status'; } diag "new ->(open it)-> open"; { ok $m->goto_ticket( $tid ), 'opened a ticket'; $m->check_links( has => ['Open It', 'Resolve', 'Reject', 'Delete'], has_no => ['Stall', 'Re-open', 'Undelete'], ); $m->follow_link_ok({text => 'Open It'}); $m->form_name('TicketUpdate'); $m->click('SubmitTicket'); is $tstatus->($tid), 'open', 'changed status'; } diag "open ->(stall)-> stalled"; { is $tstatus->($tid), 'open', 'ticket is open'; ok $m->goto_ticket( $tid ), 'opened a ticket'; $m->check_links( has => ['Stall', 'Resolve', 'Reject'], has_no => ['Open It', 'Delete', 'Re-open', 'Undelete'], ); $m->follow_link_ok({text => 'Stall'}); $m->form_name('TicketUpdate'); $m->click('SubmitTicket'); is $tstatus->($tid), 'stalled', 'changed status'; } diag "stall ->(open it)-> open"; { is $tstatus->($tid), 'stalled', 'ticket is stalled'; ok $m->goto_ticket( $tid ), 'opened a ticket'; $m->check_links( has => ['Open It'], has_no => ['Delete', 'Re-open', 'Undelete', 'Stall', 'Resolve', 'Reject'], ); $m->follow_link_ok({text => 'Open It'}); is $tstatus->($tid), 'open', 'changed status'; } diag "open -> deleted, only via modify"; { is $tstatus->($tid), 'open', 'ticket is open'; $m->get_ok( '/Ticket/Modify.html?id='. $tid ); my $form = $m->form_name('TicketModify'); ok my $input = $form->find_input('Status'), 'found status selector'; my @form_values = $input->possible_values; ok scalar @form_values, 'some options in the UI'; ok grep($_ eq 'deleted', @form_values), "has deleted"; $m->select( Status => 'deleted' ); $m->submit; is $tstatus->($tid), 'deleted', 'deleted ticket'; } diag "deleted -> X via modify, only open is available"; { is $tstatus->($tid), 'deleted', 'ticket is deleted'; $m->get_ok( '/Ticket/Modify.html?id='. $tid ); my $form = $m->form_name('TicketModify'); ok my $input = $form->find_input('Status'), 'found status selector'; my @form_values = $input->possible_values; ok scalar @form_values, 'some options in the UI'; is join('-', @form_values), '-deleted-open', 'only default, current and open available'; } diag "check illegal values and transitions"; { { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $general->id, Subject => 'test', Status => 'illegal', ); ok !$id, 'have not created a ticket'; } { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $general->id, Subject => 'test', Status => 'new', ); ok $id, 'created a ticket'; } { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $msg) = $ticket->Create( Queue => $general->id, Subject => 'test', Status => 'new', ); ok $id, 'created a ticket'; (my $status, $msg) = $ticket->SetStatus( 'illeagal' ); ok !$status, "couldn't set illeagal status"; is $ticket->Status, 'new', 'status is steal the same'; ($status, $msg) = $ticket->SetStatus( 'stalled' ); ok !$status, "couldn't set status, transition is illeagal"; is $ticket->Status, 'new', 'status is steal the same'; } } diag "'!inactive -> inactive' actions are shown even if ticket has unresolved dependencies"; { my $child_ticket = RT::Test->create_ticket( Queue => $general->id, Subject => 'child', ); my $cid = $child_ticket->id; my $parent_ticket = RT::Test->create_ticket( Queue => $general->id, Subject => 'parent', DependsOn => $child_ticket->id, ); my $pid = $parent_ticket->id; ok $m->goto_ticket( $pid ), 'opened a ticket'; $m->check_links( has => ['Open It', 'Resolve', 'Reject', 'Delete' ], has_no => ['Stall', 'Re-open', 'Undelete', ], ); ok $m->goto_ticket( $cid ), 'opened a ticket'; $m->check_links( has => ['Open It', 'Resolve', 'Reject', 'Delete'], has_no => ['Stall', 'Re-open', 'Undelete'], ); } diag "Role rights are checked for lifecycles at ticket level"; { my $user_a = RT::Test->load_or_create_user( Name => 'user_a', Password => 'password', ); ok $user_a && $user_a->id, 'loaded or created user'; RT::Test->set_rights( { Principal => 'AdminCc', Right => [qw(SeeQueue)] }, { Principal => 'Everyone', Right => [qw(WatchAsAdminCc)] }, ); my $ticket = RT::Test->create_ticket(Queue => 'General'); ok $ticket->id, 'Created new ticket'; my $id = $ticket->id; is $ticket->QueueObj->Lifecycle, 'default', 'Successfully loaded lifecycle'; $ticket->AddWatcher(Type => 'AdminCc', PrincipalId => $user_a->PrincipalId); $ticket = RT::Ticket->new($user_a); my ($ret, $msg) = $ticket->Load($id); ok $ticket->id, 'Loaded ticket in user context'; is $ticket->QueueObj->Lifecycle($ticket), 'default', "Rights check for role at ticket level passes"; } diag "Role rights are checked for lifecycles at asset level"; { my $user_a = RT::Test->load_or_create_user( Name => 'user_a', Password => 'password', ); ok $user_a && $user_a->id, 'loaded or created user'; RT::Test->set_rights( { Principal => 'Owner', Right => [qw(ShowCatalog AdminCatalog)] }, { Principal => 'Everyone', Right => [qw(ShowAsset ModifyAsset)] }, ); my $asset = RT::Asset->new(RT->SystemUser); my ($ret, $msg) = $asset->Create(Catalog => 'General assets'); ok $asset->id, 'Created new asset'; my $id = $asset->id; is $asset->CatalogObj->Lifecycle, 'assets', "System user can load asset without context object"; $asset = RT::Asset->new($user_a); $asset->Load($id); ok $asset->id, 'Loaded asset in user_a context'; is $asset->CatalogObj->Lifecycle, undef, "user_a can\'t see lifecycle without ShowCatalog and AdminCatalog"; is $asset->CatalogObj->Lifecycle($asset), undef, "user_a can\'t see lifecycle without ShowCatalog and AdminCatalog"; ($ret, $msg) = $asset->AddRoleMember(Type => 'Owner', User => $user_a); ok $ret, $msg; is $asset->CatalogObj->Lifecycle($asset), 'assets', 'Successfully loaded lifecycle with rights check at role level'; my $lifecycle = $asset->CatalogObj->LifecycleObj; is $lifecycle->Name, 'assets', 'Test LifecycleObj method'; $lifecycle = $asset->CatalogObj->LifecycleObj($asset); is $lifecycle->Name, 'assets', 'Test LifecycleObj method'; } done_testing; rt-5.0.1/t/customfields/combo_cascade.t000644 000765 000024 00000001614 14005011336 020676 0ustar00sunnavystaff000000 000000 use warnings; use strict; use RT::Test nodata => 1, tests => 11; my $q = RT::Queue->new($RT::SystemUser); works($q->Create(Name => "CF-Pattern-".$$)); my $cf = RT::CustomField->new($RT::SystemUser); my @cf_args = (Name => $q->Name, Type => 'Combobox', Queue => $q->id); works($cf->Create(@cf_args)); # Set some CFVs with Category markers my $t = RT::Ticket->new($RT::SystemUser); my ($id,undef,$msg) = $t->Create(Queue => $q->id, Subject => 'CF Test'); works($id,$msg); sub add_works { works( $cf->AddValue(Name => $_[0], Description => $_[0], Category => $_[1]) ); }; add_works('value1', '1. Category A'); add_works('value2'); add_works('value3', '1.1. A-sub one'); add_works('value4', '1.2. A-sub two'); add_works('value5', ''); my $cfv = $cf->Values->First; is($cfv->Category, '1. Category A'); works($cfv->SetCategory('1. Category AAA')); is($cfv->Category, '1. Category AAA'); rt-5.0.1/t/customfields/canonicalizer.t000644 000765 000024 00000007630 14005011336 020761 0ustar00sunnavystaff000000 000000 use utf8; use warnings; use strict; use RT::Test tests => undef; my $t = RT::Test->create_ticket( Subject => 'test canonicalize values', Queue => 'General' ); { diag "testing invalid canonicalizer"; my $invalid = RT::CustomField->new(RT->SystemUser); my ($ok, $msg) = $invalid->Create( Name => 'uppercase', Type => 'FreeformSingle', Queue => 0, CanonicalizeClass => 'RT::CustomFieldValues::Canonicalizer::NonExistent', ); ok(!$ok, "Didn't create CF"); like($msg, qr/Invalid custom field values canonicalizer/); } { diag "testing uppercase canonicalizer"; my $uppercase = RT::Test->load_or_create_custom_field( Name => 'uppercase', Type => 'FreeformSingle', Queue => 0, CanonicalizeClass => 'RT::CustomFieldValues::Canonicalizer::Uppercase', ); is($uppercase->CanonicalizeClass, 'RT::CustomFieldValues::Canonicalizer::Uppercase', 'CanonicalizeClass'); my @tests = ( 'hello world' => 'HELLO WORLD', 'Hello World' => 'HELLO WORLD', 'ABC 123 xyz !@#' => 'ABC 123 XYZ !@#', 'Unicode aware: "ω Ω"' => 'UNICODE AWARE: "Ω Ω"', 'ã¦ã™ã¨ãƒ†ã‚¹ãƒˆ' => 'ã¦ã™ã¨ãƒ†ã‚¹ãƒˆ', ); while (my ($input, $expected) = splice @tests, 0, 2) { my ($ok, $msg) = $t->AddCustomFieldValue( Field => $uppercase, Value => $input, ); ok( $ok, $msg ); is( $t->FirstCustomFieldValue($uppercase), $expected, 'canonicalized to uppercase' ); } } { diag "testing lowercase canonicalizer"; my $lowercase = RT::Test->load_or_create_custom_field( Name => 'lowercase', Type => 'FreeformSingle', Queue => 0, CanonicalizeClass => 'RT::CustomFieldValues::Canonicalizer::Lowercase', ); is($lowercase->CanonicalizeClass, 'RT::CustomFieldValues::Canonicalizer::Lowercase', 'CanonicalizeClass'); my @tests = ( 'hello world' => 'hello world', 'Hello World' => 'hello world', 'ABC 123 xyz !@#' => 'abc 123 xyz !@#', 'Unicode aware: "ω Ω"' => 'unicode aware: "ω ω"', 'ã¦ã™ã¨ãƒ†ã‚¹ãƒˆ' => 'ã¦ã™ã¨ãƒ†ã‚¹ãƒˆ', ); while (my ($input, $expected) = splice @tests, 0, 2) { my ($ok, $msg) = $t->AddCustomFieldValue( Field => $lowercase, Value => $input, ); ok( $ok, $msg ); is( $t->FirstCustomFieldValue($lowercase), $expected, 'canonicalized to lowercase' ); } } { diag "testing asset canonicalizer"; my $assetcf = RT::Test->load_or_create_custom_field( Name => 'assetcf', Type => 'FreeformSingle', LookupType => RT::Asset->CustomFieldLookupType, CanonicalizeClass => 'RT::CustomFieldValues::Canonicalizer::Uppercase', ); $assetcf->AddToObject(RT::Catalog->new(RT->SystemUser)); is($assetcf->CanonicalizeClass, 'RT::CustomFieldValues::Canonicalizer::Uppercase', 'CanonicalizeClass'); my $asset = RT::Asset->new(RT->SystemUser); my ($ok, $msg) = $asset->Create(Subject => 'test canonicalizers', Catalog => 'General assets'); ok($ok, $msg); my @tests = ( 'hello world' => 'HELLO WORLD', 'Hello World' => 'HELLO WORLD', 'ABC 123 xyz !@#' => 'ABC 123 XYZ !@#', 'Unicode aware: "ω Ω"' => 'UNICODE AWARE: "Ω Ω"', 'ã¦ã™ã¨ãƒ†ã‚¹ãƒˆ' => 'ã¦ã™ã¨ãƒ†ã‚¹ãƒˆ', ); while (my ($input, $expected) = splice @tests, 0, 2) { my ($ok, $msg) = $asset->AddCustomFieldValue( Field => $assetcf, Value => $input, ); ok( $ok, $msg ); is( $asset->FirstCustomFieldValue($assetcf), $expected, 'canonicalized to uppercase' ); } } done_testing; rt-5.0.1/t/customfields/repeated_values.t000644 000765 000024 00000011652 14005011336 021307 0ustar00sunnavystaff000000 000000 use warnings; use strict; use RT::Test tests => undef; my $ticket = RT::Test->create_ticket( Subject => 'test repeated values', Queue => 'General' ); my ( $ret, $msg ); { diag "testing freeform single cf"; my $freeform_single = RT::Test->load_or_create_custom_field( Name => 'freeform single', Type => 'FreeformSingle', Queue => 0, ); ( $ret, $msg ) = $ticket->AddCustomFieldValue( Field => $freeform_single, Value => 'foo' ); ok( $ret, $msg ); is( $ticket->FirstCustomFieldValue($freeform_single), 'foo', 'value is foo' ); my $ocfv = $ticket->CustomFieldValues($freeform_single)->First; ( $ret, $msg ) = $ticket->AddCustomFieldValue( Field => $freeform_single, Value => 'foo' ); is( $ret, $ocfv->id, "got the same previous object" ); is( $ticket->FirstCustomFieldValue($freeform_single), 'foo', 'value is still foo' ); ( $ret, $msg ) = $ticket->AddCustomFieldValue( Field => $freeform_single, Value => 'FOO' ); ok( $ret, $msg ); isnt( $ret, $ocfv->id, "got a new value" ); is( $ticket->FirstCustomFieldValue($freeform_single), 'FOO', 'value is FOO' ); } { diag "testing freeform multiple cf"; my $freeform_multiple = RT::Test->load_or_create_custom_field( Name => 'freeform multiple', Type => 'FreeformMultiple', Queue => 0, ); ($ret, $msg) = $ticket->AddCustomFieldValue( Field => $freeform_multiple, Value => 'foo' ); ok($ret, $msg); is( $ticket->FirstCustomFieldValue($freeform_multiple), 'foo', 'value is foo' ); my $ocfv = $ticket->CustomFieldValues($freeform_multiple)->First; ($ret, $msg) = $ticket->AddCustomFieldValue( Field => $freeform_multiple, Value => 'foo' ); is($ret, $ocfv->id, "got the same previous object"); is( $ticket->FirstCustomFieldValue($freeform_multiple), 'foo', 'value is still foo' ); ($ret, $msg) = $ticket->AddCustomFieldValue( Field => $freeform_multiple, Value => 'bar' ); ok($ret, $msg); my $ocfvs = $ticket->CustomFieldValues($freeform_multiple)->ItemsArrayRef; is( scalar @$ocfvs, 2, 'has 2 values'); is( $ocfvs->[0]->Content, 'foo', 'first is foo' ); is( $ocfvs->[1]->Content, 'bar', 'sencond is bar' ); } { diag "testing select single cf"; my $select_single = RT::Test->load_or_create_custom_field( Name => 'select single', Type => 'SelectSingle', Queue => 0, ); for my $value ( qw/foo bar baz/ ) { $select_single->AddValue( Name => $value ); } ( $ret, $msg ) = $ticket->AddCustomFieldValue( Field => $select_single, Value => 'foo' ); ok( $ret, $msg ); my $ocfv = $ticket->CustomFieldValues($select_single)->First; is( $ticket->FirstCustomFieldValue($select_single), 'foo', 'value is foo' ); ( $ret, $msg ) = $ticket->AddCustomFieldValue( Field => $select_single, Value => 'foo' ); is( $ret, $ocfv->id, "got the same previous object" ); is( $ticket->FirstCustomFieldValue($select_single), 'foo', 'value is still foo' ); diag "select values are case insensitive"; ( $ret, $msg ) = $ticket->AddCustomFieldValue( Field => $select_single, Value => 'FOO' ); is( $ret, $ocfv->id, "got the same previous object" ); is( $ticket->FirstCustomFieldValue($select_single), 'foo', 'value is still foo' ); ($ret, $msg) = $ticket->AddCustomFieldValue( Field => $select_single, Value => 'bar' ); ok($ret, $msg); isnt( $ret, $ocfv->id, "got a new value" ); is( $ticket->FirstCustomFieldValue($select_single), 'bar', 'new value is bar' ); } { diag "testing binary single cf"; my $binary_single = RT::Test->load_or_create_custom_field( Name => 'upload single', Type => 'BinarySingle', Queue => 0, ); ( $ret, $msg ) = $ticket->AddCustomFieldValue( Field => $binary_single, Value => 'foo', LargeContent => 'bar' ); ok( $ret, $msg ); my $ocfv = $ticket->CustomFieldValues($binary_single)->First; ( $ret, $msg ) = $ticket->AddCustomFieldValue( Field => $binary_single, Value => 'foo', LargeContent => 'bar' ); is( $ret, $ocfv->id, "got the same previous object" ); is($ocfv->Content, 'foo', 'name is foo'); is($ocfv->LargeContent, 'bar', 'content is bar'); ( $ret, $msg ) = $ticket->AddCustomFieldValue( Field => $binary_single, Value => 'foo', LargeContent => 'baz' ); ok( $ret, $msg ); isnt( $ret, $ocfv->id, "got a new value" ); $ocfv = $ticket->CustomFieldValues($binary_single)->First; is($ocfv->Content, 'foo', 'name is foo'); is($ocfv->LargeContent, 'baz', 'content is baz'); ( $ret, $msg ) = $ticket->AddCustomFieldValue( Field => $binary_single, Value => 'foo.2', LargeContent => 'baz' ); ok( $ret, $msg ); isnt( $ret, $ocfv->id, "got a new value" ); $ocfv = $ticket->CustomFieldValues($binary_single)->First; is($ocfv->Content, 'foo.2', 'name is foo.2'); is($ocfv->LargeContent, 'baz', 'content is baz'); } done_testing(); rt-5.0.1/t/customfields/date_search.t000644 000765 000024 00000012205 14005011336 020374 0ustar00sunnavystaff000000 000000 use Test::MockTime qw(set_fixed_time restore_time); use warnings; use strict; use RT::Test nodata => 1, tests => undef; RT::Test->set_rights( { Principal => 'Everyone', Right => [qw( SeeQueue ShowTicket CreateTicket SeeCustomField ModifyCustomField )] }, ); my $q = RT::Test->load_or_create_queue( Name => 'General' ); ok $q && $q->id, 'loaded or created a queue'; my $user_m = RT::Test->load_or_create_user( Name => 'moscow', Timezone => 'Europe/Moscow' ); ok $user_m && $user_m->id; $user_m = RT::CurrentUser->new( $user_m ); my $user_b = RT::Test->load_or_create_user( Name => 'boston', Timezone => 'America/New_York' ); ok $user_b && $user_b->id; $user_b = RT::CurrentUser->new( $user_b ); my $cf = RT::CustomField->new(RT->SystemUser); ok( $cf->Create( Name => 'TestDate', Type => 'Date', MaxValues => 1, LookupType => RT::Ticket->CustomFieldLookupType, ), 'create cf date' ); ok( $cf->AddToObject($q), 'date cf apply to queue' ); my $cf_name = $cf->Name; my $ticket = RT::Ticket->new(RT->SystemUser); ok( $ticket->Create( Queue => $q->id, Subject => 'Test', 'CustomField-' . $cf->id => '2010-05-04', ), 'create ticket with cf set to 2010-05-04' ); is( $ticket->CustomFieldValues->First->Content, '2010-05-04', 'date in db is' ); { my $tickets = RT::Tickets->new(RT->SystemUser); $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => '=', VALUE => '2010-05-04', ); is( $tickets->Count, 1, 'found the ticket with exact date: 2010-05-04' ); } { my $tickets = RT::Tickets->new(RT->SystemUser); $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => '>', VALUE => '2010-05-03', ); is( $tickets->Count, 1, 'found ticket with > 2010-05-03' ); } { my $tickets = RT::Tickets->new(RT->SystemUser); $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => '<', VALUE => '2010-05-05', ); is( $tickets->Count, 1, 'found ticket with < 2010-05-05' ); } { my $tickets = RT::Tickets->new(RT->SystemUser); $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => '=', VALUE => '2010-05-05', ); is( $tickets->Count, 0, 'did not find the ticket with = 2010-05-05' ); } { my $tickets = RT::Tickets->new(RT->SystemUser); $tickets->FromSQL( "'CF.{$cf_name}' = 'May 4 2010'" ); is( $tickets->Count, 1, 'found the ticket with = May 4 2010' ); $tickets->FromSQL( "'CF.{$cf_name}' < 'May 4 2010'" ); is( $tickets->Count, 0, 'did not find the ticket with < May 4 2010' ); $tickets->FromSQL( "'CF.{$cf_name}' < 'May 5 2010'" ); is( $tickets->Count, 1, 'found the ticket with < May 5 2010' ); $tickets->FromSQL( "'CF.{$cf_name}' > 'May 3 2010'" ); is( $tickets->Count, 1, 'found the ticket with > May 3 2010' ); } { my $tickets = RT::Tickets->new(RT->SystemUser); $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => '<', VALUE => '2010-05-03', ); is( $tickets->Count, 0, 'did not find the ticket with < 2010-05-03' ); } { my $tickets = RT::Tickets->new(RT->SystemUser); $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => '>', VALUE => '2010-05-05', ); is( $tickets->Count, 0, 'did not find the ticket with > 2010-05-05' ); } { my $tickets = RT::Tickets->new(RT->SystemUser); $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => 'IS', VALUE => 'NULL', ); is( $tickets->Count, 0, 'did not find the ticket with date IS NULL' ); } { my $tickets = RT::Tickets->new(RT->SystemUser); $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => 'IS NOT', VALUE => 'NULL', ); is( $tickets->Count, 1, 'did find the ticket with date IS NOT NULL' ); } # relative search by users in different TZs { my $ticket = RT::Ticket->new(RT->SystemUser); my ($tid) = $ticket->Create( Queue => $q->id, Subject => 'Test', 'CustomField-' . $cf->id => '2013-02-12', ); set_fixed_time("2013-02-10T23:10:00Z"); my $tickets = RT::Tickets->new($user_m); $tickets->FromSQL("'CustomField.{$cf_name}' = 'tomorrow' AND id = $tid"); is( $tickets->Count, 1, 'found the ticket' ); set_fixed_time("2013-02-10T15:10:00Z"); $tickets = RT::Tickets->new($user_m); $tickets->FromSQL("'CustomField.{$cf_name}' = 'tomorrow' AND id = $tid"); is( $tickets->Count, 0, 'found no tickets' ); set_fixed_time("2013-02-10T23:10:00Z"); $tickets = RT::Tickets->new($user_b); $tickets->FromSQL("'CustomField.{$cf_name}' = 'tomorrow' AND id = $tid"); is( $tickets->Count, 0, 'found no tickets' ); set_fixed_time("2013-02-11T23:10:00Z"); $tickets = RT::Tickets->new($user_b); $tickets->FromSQL("'CustomField.{$cf_name}' = 'tomorrow' AND id = $tid"); is( $tickets->Count, 1, 'found the tickets' ); } done_testing; rt-5.0.1/t/customfields/external.t000644 000765 000024 00000004155 14005011336 017761 0ustar00sunnavystaff000000 000000 use warnings; use strict; use RT; use RT::Test nodata => 1, tests => undef; sub new (*) { my $class = shift; return $class->new(RT->SystemUser); } use constant VALUES_CLASS => 'RT::CustomFieldValues::Groups'; RT->Config->Set(CustomFieldValuesSources => VALUES_CLASS); my $q = new( RT::Queue ); isa_ok( $q, 'RT::Queue' ); my ($qid) = $q->Create( Name => "CF-External-". $$ ); ok( $qid, "created queue" ); my %arg = ( Name => $q->Name, Type => 'Select', Queue => $q->id, MaxValues => 1, ValuesClass => VALUES_CLASS ); my $cf = new( RT::CustomField ); isa_ok( $cf, 'RT::CustomField' ); { my ($cfid, $msg) = $cf->Create( %arg ); ok( $cfid, "created cf" ) or diag "error: $msg"; is( $cf->ValuesClass, VALUES_CLASS, "right values class" ); ok( $cf->IsExternalValues, "custom field has external values" ); } { # create at least on group for the tests my $group = RT::Group->new( RT->SystemUser ); my ($ret, $msg) = $group->CreateUserDefinedGroup( Name => $q->Name ); ok $ret, 'created group' or diag "error: $msg"; } { my $values = $cf->Values; isa_ok( $values, VALUES_CLASS ); ok( $values->Count, "we have values" ); my ($failure, $count) = (0, 0); while( my $value = $values->Next ) { $count++; $failure = 1 unless $value->Name; } ok( !$failure, "all values have name" ); is( $values->Count, $count, "count is correct" ); is( $values->CustomFieldObject->id, $cf->id, "Values stored the CF id" ); is( $values->CustomFieldObject, $cf, "Values stored the identical CF object" ); is( $values->First->CustomFieldObj->id, $cf->id, "A value stored the CF id" ); is( $values->First->CustomFieldObj, $cf, "A value stored the identical CF object" ); } { my ($ret, $msg) = $cf->SetValuesClass('RT::CustomFieldValues'); ok $ret, 'Reverting this CF as internal source values based' or diag "error: $msg"; ($ret, $msg) = $cf->SetValuesClass('RT::CustomFieldValues::Groups'); ok $ret, 'Reverting this CF as external source values based' or diag "error: $msg"; } done_testing; rt-5.0.1/t/customfields/transaction_searching.t000644 000765 000024 00000012110 14005011336 022475 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 'no_declare'; my $initialdata = RT::Test::get_relocatable_file("transaction-cfs" => "..", "data", "initialdata"); my ($rv, $msg) = RT->DatabaseHandle->InsertData( $initialdata, undef, disconnect_after => 0 ); ok($rv, "Inserted test data from $initialdata") or diag "Error: $msg"; create_tickets( Spam => { }, Coffee => { Billable => "No", }, Phone => { Billable => "Yes", Who => ["Telecom", "Information Technology"], When => "2013-06-25", Location => "Geology" }, Stacks => { Billable => "Yes", Who => "Library", When => "2013-06-01" }, Benches => { Billable => "Yes", Location => "Outdoors" }, ); # Sanity check results_are("CF.Location IS NOT NULL", [qw( Phone Benches )]); results_are("CF.Location IS NULL", [qw( Spam Coffee Stacks )]); # TODO: Ideal behaviour of TxnCF IS NULL not yet determined #results_are("TxnCF.Billable IS NULL", [qw( Spam )]); results_are("TxnCF.Billable IS NOT NULL", [qw( Coffee Phone Stacks Benches )]); results_are("TxnCF.Billable = 'No'", [qw( Coffee )]); results_are("TxnCF.Billable = 'Yes'", [qw( Phone Stacks Benches )]); results_are("TxnCF.Billable = 'Yes' AND CF.Location IS NOT NULL", [qw( Phone Benches )]); results_are("TxnCF.Billable = 'Yes' AND CF.Location = 'Outdoors'", [qw( Benches )]); results_are("TxnCF.Billable = 'Yes' AND CF.Location LIKE 'o'", [qw( Phone Benches )]); results_are("TxnCF.Who = 'Telecom' OR TxnCF.Who = 'Library'", [qw( Phone Stacks )]); # TODO: Negative searching finds tickets with at least one txn doesn't have the value #results_are("TxnCF.Who != 'Library'", [qw( Spam Coffee Phone Benches )]); results_are("TxnCF.When > '2013-06-24'", [qw( Phone )]); results_are("TxnCF.When < '2013-06-24'", [qw( Stacks )]); results_are("TxnCF.When >= '2013-06-01' and TxnCF.When <= '2013-06-30'", [qw( Phone Stacks )]); results_are("TxnCF.Who LIKE 'e'", [qw( Phone )]); # TODO: Negative searching finds tickets with at least one txn doesn't have the value #results_are("TxnCF.Who NOT LIKE 'e'", [qw( Spam Coffee Stacks Benches )]); results_are("TxnCF.Who NOT LIKE 'e' and TxnCF.Who IS NOT NULL", [qw( Stacks )]); # Multiple CFs with same name applied to different queues clear_tickets(); create_tickets( BlueNone => { Queue => "Blues" }, PurpleNone => { Queue => "Purples" }, Blue => { Queue => "Blues", Color => "Blue" }, Purple => { Queue => "Purples", Color => "Purple" }, ); # Queue-specific txn CFs results_are("TxnCF.Blues.{Color} = 'Blue'", [qw( Blue )]); results_are("TxnCF.Blues.{Color} = 'Purple'", []); # Multiple transaction CFs by name results_are("TxnCF.{Color} IS NOT NULL", [qw( Blue Purple )]); results_are("TxnCF.{Color} = 'Blue'", [qw( Blue )]); results_are("TxnCF.{Color} = 'Purple'", [qw( Purple )]); results_are("TxnCF.{Color} LIKE 'e'", [qw( Blue Purple )]); done_testing; sub results_are { local $Test::Builder::Level = $Test::Builder::Level + 1; my $query = shift; my $expected = shift; my %expected = map { $_ => 1 } @$expected; my @unexpected; my $tickets = RT::Tickets->new(RT->SystemUser); my ($ok, $msg) = $tickets->FromSQL($query); ok($ok, "Searched: $query") or return diag $msg; for my $t (@{$tickets->ItemsArrayRef || []}) { if (delete $expected{$t->Subject}) { ok(1, "Found expected ticket ".$t->Subject); } else { push @unexpected, $t->Subject; } } ok(0, "Didn't find expected ticket $_") for grep $expected{$_}, @$expected; ok(0, "Found unexpected tickets: ".join ", ", @unexpected) if @unexpected; } sub create_tickets { my %ticket = @_; for my $subject (sort keys %ticket) { my %data = %{$ticket{$subject}}; my $location = delete $data{Location}; my $queue = delete $data{Queue} || "General"; my $ticket = RT::Ticket->new( RT->SystemUser ); my ($ok, $msg) = $ticket->Create( Queue => $queue, Subject => $subject, ); ok($ticket->id, "Created ticket: $msg") or next; if ($location) { ($ok, $msg) = $ticket->AddCustomFieldValue( Field => "Location", Value => $location ); ok($ok, "Added Location: $msg") or next; } my ($txnid, $txnmsg, $txn) = $ticket->Correspond( Content => "test transaction" ); unless ($txnid) { RT->Logger->error("Unable to correspond on ticket $ok: $txnmsg"); next; } for my $name (sort keys %data) { my $values = ref $data{$name} ? $data{$name} : [$data{$name}]; for my $v (@$values) { ($ok, $msg) = $txn->_AddCustomFieldValue( Field => $name, Value => $v, RecordTransaction => 0 ); ok($ok, "Added txn CF $name value '$v'") or diag $msg; } } } } sub clear_tickets { my $tickets = RT::Tickets->new( RT->SystemUser ); $tickets->FromSQL("id > 0"); $_->SetStatus("deleted") for @{$tickets->ItemsArrayRef}; } rt-5.0.1/t/customfields/date.t000644 000765 000024 00000005101 14005011336 017044 0ustar00sunnavystaff000000 000000 use Test::MockTime qw(set_fixed_time restore_time); use warnings; use strict; use RT::Test tests => undef; RT::Test->set_rights( { Principal => 'Everyone', Right => [qw( SeeQueue ShowTicket CreateTicket SeeCustomField ModifyCustomField )] }, ); my $q = RT::Test->load_or_create_queue( Name => 'General' ); ok $q && $q->id, 'loaded or created a queue'; my $user_m = RT::Test->load_or_create_user( Name => 'moscow', Timezone => 'Europe/Moscow' ); ok $user_m && $user_m->id; my $user_b = RT::Test->load_or_create_user( Name => 'boston', Timezone => 'America/New_York' ); ok $user_b && $user_b->id; my $cf_name = 'A Date'; my $cf; { $cf = RT::CustomField->new(RT->SystemUser); ok( $cf->Create( Name => $cf_name, Type => 'Date', MaxValues => 1, LookupType => RT::Ticket->CustomFieldLookupType, ), 'create cf date' ); ok( $cf->AddToObject($q), 'date cf apply to queue' ); } { my $ticket = RT::Ticket->new( RT::CurrentUser->new( $user_m ) ); my ($id) = $ticket->Create( Queue => $q->id, Subject => 'Test', 'CustomField-'. $cf->id => '2013-02-11', ); my $cf_value = $ticket->CustomFieldValues($cf_name)->First; is( $cf_value->Content, '2013-02-11', 'correct value' ); $ticket = RT::Ticket->new( RT::CurrentUser->new( $user_b ) ); $ticket->Load($id); is( $ticket->FirstCustomFieldValue($cf_name), '2013-02-11', 'correct value' ); } { my $ticket = RT::Ticket->new(RT->SystemUser); ok( $ticket->Create( Queue => $q->id, Subject => 'Test', 'CustomField-' . $cf->id => '2010-05-04 11:34:56', ), 'create ticket with cf set to 2010-05-04 11:34:56' ); is( $ticket->CustomFieldValues->First->Content, '2010-05-04', 'date in db only has date' ); } # in moscow it's already Feb 11, so tomorrow is Feb 12 set_fixed_time("2013-02-10T23:10:00Z"); { my $ticket = RT::Ticket->new( RT::CurrentUser->new( $user_m ) ); my ($id) = $ticket->Create( Queue => $q->id, Subject => 'Test', 'CustomField-'. $cf->id => 'tomorrow', ); my $cf_value = $ticket->CustomFieldValues($cf_name)->First; is( $cf_value->Content, '2013-02-12', 'correct value' ); $ticket = RT::Ticket->new( RT::CurrentUser->new( $user_b ) ); $ticket->Load($id); is( $ticket->FirstCustomFieldValue($cf_name), '2013-02-12', 'correct value' ); } done_testing(); rt-5.0.1/t/customfields/datetime.t000644 000765 000024 00000004537 14005011336 017737 0ustar00sunnavystaff000000 000000 use Test::MockTime qw(set_fixed_time restore_time); use warnings; use strict; use RT::Test tests => undef; RT::Test->set_rights( { Principal => 'Everyone', Right => [qw( SeeQueue ShowTicket CreateTicket SeeCustomField ModifyCustomField )] }, ); my $q = RT::Test->load_or_create_queue( Name => 'General' ); ok $q && $q->id, 'loaded or created a queue'; my $user_m = RT::Test->load_or_create_user( Name => 'moscow', Timezone => 'Europe/Moscow' ); ok $user_m && $user_m->id; my $user_b = RT::Test->load_or_create_user( Name => 'boston', Timezone => 'America/New_York' ); ok $user_b && $user_b->id; my $cf_name = 'A Date and Time'; my $cf; { $cf = RT::CustomField->new(RT->SystemUser); ok( $cf->Create( Name => $cf_name, Type => 'DateTime', MaxValues => 1, LookupType => RT::Ticket->CustomFieldLookupType, ), 'create cf date' ); ok( $cf->AddToObject($q), 'date cf apply to queue' ); } { my $ticket = RT::Ticket->new( RT::CurrentUser->new( $user_m ) ); my ($id) = $ticket->Create( Queue => $q->id, Subject => 'Test', 'CustomField-'. $cf->id => '2013-02-11 00:00:00', ); my $cf_value = $ticket->CustomFieldValues($cf_name)->First; TODO: { local $TODO = 'questionable result, should we change?'; # $Ticket->Created returns UTC, not user's date, but # ticket has ->CreatedObj method to get all required # transformation # No more TODO. is( $cf_value->Content, '2013-02-11 00:00:00', 'correct value' ); } is( $cf_value->Content, '2013-02-10 20:00:00', 'correct value' ); $ticket = RT::Ticket->new( RT::CurrentUser->new( $user_b ) ); $ticket->Load($id); is( $ticket->FirstCustomFieldValue($cf_name), '2013-02-10 20:00:00', 'correct value' ); } # in moscow it's already Feb 11, so tomorrow is Feb 12 set_fixed_time("2013-02-10T23:10:00Z"); { my $ticket = RT::Ticket->new( RT::CurrentUser->new( $user_m ) ); my ($id) = $ticket->Create( Queue => $q->id, Subject => 'Test', 'CustomField-'. $cf->id => 'tomorrow', ); my $cf_value = $ticket->CustomFieldValues($cf_name)->First; is( $cf_value->Content, '2013-02-11 23:10:00', 'correct value' ); } done_testing(); rt-5.0.1/t/customfields/enter_one.t000644 000765 000024 00000001426 14005011336 020113 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => undef; my $q = RT::Test->load_or_create_queue(Name => 'General'); ok $q && $q->id, "loaded or created queue 'General'"; my $cf = RT::CustomField->new(RT->SystemUser); my ($id,$msg) = $cf->Create(Name => 'Enter-One', Type => 'Freeform', MaxValues => '1', Queue => $q->id); ok $id, $msg; my $t = RT::Ticket->new(RT->SystemUser); ($id,undef,$msg) = $t->Create(Queue => $q->id, Subject => 'CF Enter-One Test'); ok $id, $msg; $t->AddCustomFieldValue(Field => $cf->id, Value => 'LOWER'); is $t->FirstCustomFieldValue($cf->id), "LOWER", "CF value is 'LOWER'"; $t->AddCustomFieldValue(Field => $cf->id, Value => 'lower'); is $t->FirstCustomFieldValue($cf->id), "lower", "CF value changed from 'LOWER' to 'lower'"; done_testing(); rt-5.0.1/t/customfields/pattern.t000644 000765 000024 00000002473 14005011336 017615 0ustar00sunnavystaff000000 000000 use warnings; use strict; use RT; use RT::Test nodata => 1, tests => 17; my $q = RT::Queue->new($RT::SystemUser); works($q->Create(Name => "CF-Pattern-".$$)); my $cf = RT::CustomField->new($RT::SystemUser); my @cf_args = (Name => $q->Name, Type => 'Freeform', Queue => $q->id, MaxValues => 1); fails($cf->Create(@cf_args, Pattern => ')))bad!regex(((')); works($cf->Create(@cf_args, Pattern => 'good regex')); my $t = RT::Ticket->new($RT::SystemUser); my ($id,undef,$msg) = $t->Create(Queue => $q->id, Subject => 'CF Test'); works($id,$msg); # OK, I'm thoroughly brain washed by HOP at this point now... sub cnt { $t->CustomFieldValues($cf->id)->Count }; sub add { $t->AddCustomFieldValue(Field => $cf->id, Value => $_[0]) }; sub del { $t->DeleteCustomFieldValue(Field => $cf->id, Value => $_[0]) }; is(cnt(), 0, "No values yet"); fails(add('not going to match')); is(cnt(), 0, "No values yet"); works(add('here is a good regex')); is(cnt(), 1, "Value filled"); fails(del('here is a good regex')); is(cnt(), 1, "Single CF - Value _not_ deleted"); $cf->SetMaxValues(0); # Unlimited MaxValues works(del('here is a good regex')); is(cnt(), 0, "Multiple CF - Value deleted"); fails($cf->SetPattern('(?{ "insert evil code here" })')); works($cf->SetPattern('(?!)')); # reject everything fails(add('')); fails(add('...')); undef $t; rt-5.0.1/t/customfields/unique_values.t000644 000765 000024 00000004000 14005011336 021011 0ustar00sunnavystaff000000 000000 use warnings; use strict; use RT::Test tests => undef; { diag "testing freeform single cf"; my $unique_single = RT::Test->load_or_create_custom_field( Name => 'unique single', Type => 'FreeformSingle', Queue => 0, UniqueValues => 1, ); ok($unique_single->UniqueValues, 'unique values for this CF'); my @tests = ( ['foo', 'bar'], # Content [('foo' x 256), ('bar' x 256)], # LargeContent ); for (@tests) { my ($foo, $bar) = @$_; my $alpha = RT::Test->create_ticket( Subject => 'test unique values alpha', Queue => 'General' ); my $beta = RT::Test->create_ticket( Subject => 'test unique values beta', Queue => 'General' ); my ( $ret, $msg ) = $alpha->AddCustomFieldValue( Field => $unique_single, Value => $foo ); ok( $ret, $msg ); is( $alpha->FirstCustomFieldValue($unique_single), $foo, 'value is foo' ); ( $ret, $msg ) = $beta->AddCustomFieldValue( Field => $unique_single, Value => $foo ); ok( !$ret, "can't reuse the OCFV 'foo'"); like($msg, qr/That is not a unique value/); is( $beta->FirstCustomFieldValue($unique_single), undef, 'no value since it was a duplicate' ); ( $ret, $msg ) = $alpha->AddCustomFieldValue( Field => $unique_single, Value => $bar ); ok( $ret, $msg ); is( $alpha->FirstCustomFieldValue($unique_single), $bar, 'value is now bar' ); ( $ret, $msg ) = $beta->AddCustomFieldValue( Field => $unique_single, Value => $foo ); ok( $ret, "can reuse foo since alpha switched away"); is( $beta->FirstCustomFieldValue($unique_single), $foo, 'now beta has foo' ); ( $ret, $msg ) = $alpha->AddCustomFieldValue( Field => $unique_single, Value => $foo ); ok( !$ret, "alpha can't switch back to foo since beta uses it"); is( $alpha->FirstCustomFieldValue($unique_single), $bar, 'value is still bar' ); } } done_testing; rt-5.0.1/t/customfields/api.t000644 000765 000024 00000031771 14005011336 016714 0ustar00sunnavystaff000000 000000 use strict; use warnings FATAL => 'all'; use RT::Test nodata => 1, tests => undef; use Test::Warn; # Before we get going, ditch all object_cfs; this will remove # all custom fields systemwide; my $object_cfs = RT::ObjectCustomFields->new(RT->SystemUser); $object_cfs->UnLimit(); while (my $ocf = $object_cfs->Next) { $ocf->Delete(); } my $queue = RT::Queue->new( RT->SystemUser ); $queue->Create( Name => 'RecordCustomFields-'.$$ ); ok ($queue->id, "Created the queue"); my $queue2 = RT::Queue->new( RT->SystemUser ); $queue2->Create( Name => 'RecordCustomFields2' ); my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Create( Queue => $queue->Id, Requestor => 'root@localhost', Subject => 'RecordCustomFields1', ); my $cfs = $ticket->CustomFields; is( $cfs->Count, 0 ); # Check that record has no any CF values yet my $cfvs = $ticket->CustomFieldValues; is( $cfvs->Count, 0 ); is( $ticket->FirstCustomFieldValue, undef ); my $local_cf1 = RT::CustomField->new( RT->SystemUser ); $local_cf1->Create( Name => 'RecordCustomFields1-'.$$, Type => 'SelectSingle', Queue => $queue->id ); $local_cf1->AddValue( Name => 'RecordCustomFieldValues11' ); $local_cf1->AddValue( Name => 'RecordCustomFieldValues12' ); my $local_cf2 = RT::CustomField->new( RT->SystemUser ); $local_cf2->Create( Name => 'RecordCustomFields2-'.$$, Type => 'SelectSingle', Queue => $queue->id ); $local_cf2->AddValue( Name => 'RecordCustomFieldValues21' ); $local_cf2->AddValue( Name => 'RecordCustomFieldValues22' ); my $global_cf3 = RT::CustomField->new( RT->SystemUser ); $global_cf3->Create( Name => 'RecordCustomFields3-'.$$, Type => 'SelectSingle', Queue => 0 ); $global_cf3->AddValue( Name => 'RecordCustomFieldValues31' ); $global_cf3->AddValue( Name => 'RecordCustomFieldValues32' ); my $local_cf4 = RT::CustomField->new( RT->SystemUser ); $local_cf4->Create( Name => 'RecordCustomFields4', Type => 'SelectSingle', Queue => $queue2->id ); $local_cf4->AddValue( Name => 'RecordCustomFieldValues41' ); $local_cf4->AddValue( Name => 'RecordCustomFieldValues42' ); my @custom_fields = ($local_cf1, $local_cf2, $global_cf3); $cfs = $ticket->CustomFields; is( $cfs->Count, 3 ); # Check that record has no any CF values yet $cfvs = $ticket->CustomFieldValues; is( $cfvs->Count, 0 ); is( $ticket->FirstCustomFieldValue, undef ); # CF with ID -1 shouldnt exist at all warning_like { $cfvs = $ticket->CustomFieldValues( -1 ); } qr{Couldn't load custom field}; is( $cfvs->Count, 0 ); warning_like { is( $ticket->FirstCustomFieldValue( -1 ), undef ); } qr{Couldn't load custom field}; warning_like { $cfvs = $ticket->CustomFieldValues( 'SomeUnexpedCustomFieldName' ); } qr{Couldn't load custom field}; is( $cfvs->Count, 0 ); warning_like { is( $ticket->FirstCustomFieldValue( 'SomeUnexpedCustomFieldName' ), undef ); } qr{Couldn't load custom field}; for (@custom_fields) { $cfvs = $ticket->CustomFieldValues( $_->id ); is( $cfvs->Count, 0 ); $cfvs = $ticket->CustomFieldValues( $_->Name ); is( $cfvs->Count, 0 ); is( $ticket->FirstCustomFieldValue( $_->id ), undef ); is( $ticket->FirstCustomFieldValue( $_->Name ), undef ); } # try to add field value with fields that do not exist my ($status, $msg) = $ticket->AddCustomFieldValue( Field => -1 , Value => 'foo' ); ok(!$status, "shouldn't add value" ); ($status, $msg) = $ticket->AddCustomFieldValue( Field => 'SomeUnexpedCustomFieldName' , Value => 'foo' ); ok(!$status, "shouldn't add value" ); SKIP: { skip "TODO: We want fields that are not allowed to set unexpected values", 10; for (@custom_fields) { ($status, $msg) = $ticket->AddCustomFieldValue( Field => $_ , Value => 'SomeUnexpectedCFValue' ); ok( !$status, 'value doesn\'t exist'); ($status, $msg) = $ticket->AddCustomFieldValue( Field => $_->id , Value => 'SomeUnexpectedCFValue' ); ok( !$status, 'value doesn\'t exist'); ($status, $msg) = $ticket->AddCustomFieldValue( Field => $_->Name , Value => 'SomeUnexpectedCFValue' ); ok( !$status, 'value doesn\'t exist'); } # Let check that we did not add value to be sure # using only FirstCustomFieldValue sub because # we checked other variants allready for (@custom_fields) { is( $ticket->FirstCustomFieldValue( $_->id ), undef ); } } # Add some values to our custom fields for (@custom_fields) { # this should be tested elsewhere $_->AddValue( Name => 'Foo' ); $_->AddValue( Name => 'Bar' ); } my $test_add_delete_cycle = sub { my $cb = shift; for (@custom_fields) { ($status, $msg) = $ticket->AddCustomFieldValue( Field => $cb->($_) , Value => 'Foo' ); ok( $status, "message: $msg"); } # does it exist? $cfvs = $ticket->CustomFieldValues; is( $cfvs->Count, 3, "We found all three custom fields on our ticket" ); for (@custom_fields) { $cfvs = $ticket->CustomFieldValues( $_->id ); is( $cfvs->Count, 1 , "we found one custom field when searching by id"); $cfvs = $ticket->CustomFieldValues( $_->Name ); is( $cfvs->Count, 1 , " We found one custom field when searching by name for " . $_->Name); is( $ticket->FirstCustomFieldValue( $_->id ), 'Foo' , "first value by id is foo"); is( $ticket->FirstCustomFieldValue( $_->Name ), 'Foo' , "first value by name is foo"); } # because our CFs are SingleValue then new value addition should override for (@custom_fields) { ($status, $msg) = $ticket->AddCustomFieldValue( Field => $_ , Value => 'Bar' ); ok( $status, "message: $msg"); } $cfvs = $ticket->CustomFieldValues; is( $cfvs->Count, 3 ); for (@custom_fields) { $cfvs = $ticket->CustomFieldValues( $_->id ); is( $cfvs->Count, 1 ); $cfvs = $ticket->CustomFieldValues( $_->Name ); is( $cfvs->Count, 1 ); is( $ticket->FirstCustomFieldValue( $_->id ), 'Bar' ); is( $ticket->FirstCustomFieldValue( $_->Name ), 'Bar' ); } # delete it for (@custom_fields ) { ($status, $msg) = $ticket->DeleteCustomFieldValue( Field => $_ , Value => 'Bar' ); ok( $status, "Deleted a custom field value 'Bar' for field ".$_->id.": $msg"); } $cfvs = $ticket->CustomFieldValues; is( $cfvs->Count, 0, "The ticket (".$ticket->id.") no longer has any custom field values" ); for (@custom_fields) { $cfvs = $ticket->CustomFieldValues( $_->id ); is( $cfvs->Count, 0, $ticket->id." has no values for cf ".$_->id ); $cfvs = $ticket->CustomFieldValues( $_->Name ); is( $cfvs->Count, 0 , $ticket->id." has no values for cf '".$_->Name. "'" ); is( $ticket->FirstCustomFieldValue( $_->id ), undef , "There is no first custom field value when loading by id" ); is( $ticket->FirstCustomFieldValue( $_->Name ), undef, "There is no first custom field value when loading by Name" ); } }; # lets test cycle via CF id $test_add_delete_cycle->( sub { return $_[0]->id } ); # lets test cycle via CF object reference $test_add_delete_cycle->( sub { return $_[0] } ); $ticket->AddCustomFieldValue( Field => $local_cf2->id , Value => 'Baz' ); $ticket->AddCustomFieldValue( Field => $global_cf3->id , Value => 'Baz' ); # now if we ask for cf values on RecordCustomFields4 we should not get any warning_like { $cfvs = $ticket->CustomFieldValues( 'RecordCustomFields4' ); } qr{Couldn't load custom field}; is( $cfvs->Count, 0, "No custom field values for non-Queue cf" ); warning_like { is( $ticket->FirstCustomFieldValue( 'RecordCustomFields4' ), undef, "No first custom field value for non-Queue cf" ); } qr{Couldn't load custom field}; { my $cfname = $global_cf3->Name; ($status, $msg) = $global_cf3->SetDisabled(1); ok($status, "Disabled CF named $cfname"); my $load = RT::CustomField->new( RT->SystemUser ); $load->LoadByName( Name => $cfname); ok($load->Id, "Loaded CF named $cfname"); is($load->Id, $global_cf3->Id, "Can load disabled CFs"); my $dup = RT::CustomField->new( RT->SystemUser ); $dup->Create( Name => $cfname, Type => 'SelectSingle', Queue => 0 ); ok($dup->Id, "Created CF with duplicate name"); $load->LoadByName( Name => $cfname); is($load->Id, $dup->Id, "Loading by name gets non-disabled first"); $dup->SetDisabled(1); $global_cf3->SetDisabled(0); $load->LoadByName( Name => $cfname); is($load->Id, $global_cf3->Id, "Loading by name gets non-disabled first, even with order swapped"); } { my $cf = RT::Test->load_or_create_custom_field( Name => 'HasEntry cache', Type => 'FreeformSingle', Queue => 0, ); my ( $ret, $msg ) = $ticket->AddCustomFieldValue( Field => $cf, Value => 'foo' ); ok( $ret, $msg ); is( $ticket->FirstCustomFieldValue( $cf ), 'foo', 'value is foo' ); my $ocfvs = $ticket->CustomFieldValues( $cf ); ok( $ocfvs->HasEntry( 'foo' ), 'foo is cached in HasEntry' ); ( $ret, $msg ) = $ticket->AddCustomFieldValue( Field => $cf, Value => 'bar' ); ok( $ret, $msg ); is( $ticket->FirstCustomFieldValue( $cf ), 'bar', 'value is bar' ); ok( !$ocfvs->HasEntry( 'foo' ), 'foo is not cached in HasEntry' ); ok( $ocfvs->HasEntry( 'bar' ), 'bar is cached in HasEntry' ); ( $ret, $msg ) = $ticket->AddCustomFieldValue( Field => $cf, Value => 'foo' ); ok( $ret, $msg ); is( $ticket->FirstCustomFieldValue( $cf ), 'foo', 'value is foo' ); ok( $ocfvs->HasEntry( 'foo' ), 'foo is cached in HasEntry' ); ok( !$ocfvs->HasEntry( 'bar' ), 'bar is not cached in HasEntry' ); } #SKIP: { # skip "TODO: should we add CF values to objects via CF Name?", 48; # names are not unique # lets test cycle via CF Name # $test_add_delete_cycle->( sub { return $_[0]->Name } ); #} # These represent adding the custom field to all objects my $all_queues = RT::Queue->new( RT->SystemUser ); my $all_classes = RT::Class->new( RT->SystemUser ); # Queue CustomField Message Test { my $queue = RT::Queue->new( RT->SystemUser ); $queue->Create( Name => 'queue_name_0' ); my $custom_field = RT::CustomField->new( RT->SystemUser ); $custom_field->Create( Name => 'custom_field_0', Type => 'SelectSingle', LookupType => 'RT::Queue' ); my ($status, $msg) = $custom_field->AddToObject( $queue ); is($msg, 'Added custom field custom_field_0 to queue_name_0.', "Adding custom field to queue produces appropriate message"); ($status, $msg) = $custom_field->RemoveFromObject( $queue ); is($msg, 'Removed custom field custom_field_0 from queue_name_0.', "Removing custom field from queue produces appropriate message"); ($status, $msg) = $custom_field->AddToObject( $all_queues ); is($msg, 'Globally added custom field custom_field_0.', "Adding custom field globally produces appropriate message"); ($status, $msg) = $custom_field->RemoveFromObject( $all_queues ); is($msg, 'Globally removed custom field custom_field_0.', "Rmeoving custom field globally produces appropriate message"); } # Ticket CustomField Message Test { my $queue = RT::Queue->new( RT->SystemUser ); $queue->Create( Name => 'queue_name_1' ); my $custom_field = RT::CustomField->new( RT->SystemUser ); $custom_field->Create( Name => 'custom_field_1', Type => 'SelectSingle', LookupType => 'RT::Queue-RT::Ticket' ); my ($status, $msg) = $custom_field->AddToObject( $queue ); is($msg, 'Added custom field custom_field_1 to queue_name_1.', "Adding custom field to queue-ticket produces appropriate message"); ($status, $msg) = $custom_field->RemoveFromObject( $queue ); is($msg, 'Removed custom field custom_field_1 from queue_name_1.', "Removing custom field from queue produces appropriate message"); ($status, $msg) = $custom_field->AddToObject( $all_queues ); is($msg, 'Globally added custom field custom_field_1.', "Adding custom field globally produces appropriate message"); ($status, $msg) = $custom_field->RemoveFromObject( $all_queues ); is($msg, 'Globally removed custom field custom_field_1.', "Removing custom field globally produces appropriate message"); } # Class CustomField Message Test { my $class = RT::Class->new( RT->SystemUser ); $class->Create( Name => 'class_name_0' ); my $custom_field = RT::CustomField->new( RT->SystemUser ); $custom_field->Create( Name => 'custom_field_2', Type => 'SelectSingle', LookupType => 'RT::Class-RT::Article' ); my ($status, $msg) = $custom_field->AddToObject( $class ); is($msg, 'Added custom field custom_field_2 to class_name_0.', "Adding custom field to class-ticket produces appropriate message"); ($status, $msg) = $custom_field->RemoveFromObject( $class ); is($msg, 'Removed custom field custom_field_2 from class_name_0.', "Remove custom field from class produces appropriate message"); ($status, $msg) = $custom_field->AddToObject( $all_classes ); is($msg, 'Globally added custom field custom_field_2.', "Adding custom field globally produces appropriate message"); ($status, $msg) = $custom_field->RemoveFromObject( $all_classes ); is($msg, 'Globally removed custom field custom_field_2.', "Removing custom field globally produces appropriate message"); } done_testing; rt-5.0.1/t/customfields/group_rights.t000644 000765 000024 00000004515 14005011336 020653 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; # These tests catch a previous issue that resulted in the CF # canonicalize call failing because an internal cf object lacked # sufficient context to properly do a rights check. my $general = RT::Test->load_or_create_queue( Name => 'General' ); my $staff1 = RT::Test->load_or_create_user( EmailAddress => 'staff1@example.com', Name => 'staff1', Timezone => 'America/New_York'); my $staff2 = RT::Test->load_or_create_user( EmailAddress => 'staff2@example.com', Name => 'staff2', Timezone => 'America/New_York'); my $group = RT::Test->load_or_create_group( 'Staff', Members => [$staff1, $staff2], ); ok( RT::Test->add_rights( { Principal => $group, Object => $general, Right => [ qw(ModifyTicket CreateTicket SeeQueue ShowTicket SeeCustomField ModifyCustomField) ] } )); my $cf_name = 'A Date and Time'; my $cf; { $cf = RT::CustomField->new(RT->SystemUser); ok( $cf->Create( Name => $cf_name, Type => 'DateTime', MaxValues => 1, LookupType => RT::Ticket->CustomFieldLookupType, ), 'create cf date' ); ok( $cf->AddToObject($general), 'date cf apply to queue' ); } diag "Confirm DateTime CF is properly created for root"; { my $ticket = RT::Ticket->new( RT::CurrentUser->new( RT->SystemUser ) ); my ($id) = $ticket->Create( Queue => $general->id, Subject => 'Test', 'CustomField-'. $cf->id => '2016-05-01 00:00:00', ); my $cf_value = $ticket->CustomFieldValues($cf_name)->First; is( $cf_value->Content, '2016-05-01 04:00:00', 'got correct value for datetime' ); } diag "Confirm DateTime CF is properly created for staff1"; { my $ticket = RT::Ticket->new( RT::CurrentUser->new( $staff1 ) ); my ($id) = $ticket->Create( Queue => $general->id, Subject => 'Test', 'CustomField-'. $cf->id => '2016-05-01 00:00:00', ); my $cf_value = $ticket->CustomFieldValues($cf_name)->First; is( $cf_value->Content, '2016-05-01 04:00:00', 'correct value' ); $ticket = RT::Ticket->new( RT::CurrentUser->new( $staff2 ) ); $ticket->Load($id); is( $ticket->FirstCustomFieldValue($cf_name), '2016-05-01 04:00:00', 'staff2 gets correct value' ); } done_testing; rt-5.0.1/t/customfields/sort_order.t000644 000765 000024 00000004715 14005011336 020323 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use RT::Ticket; use RT::CustomField; my $queue_name = "CFSortQueue-$$"; my $queue = RT::Test->load_or_create_queue( Name => $queue_name ); ok($queue && $queue->id, "$queue_name - test queue creation"); diag "create multiple CFs: B, A and C"; my @cfs = (); { my $cf = RT::CustomField->new( RT->SystemUser ); my ($ret, $msg) = $cf->Create( Name => "CF B", Queue => $queue->id, Type => 'FreeformSingle', ); ok($ret, "Custom Field Order created"); push @cfs, $cf; } { my $cf = RT::CustomField->new( RT->SystemUser ); my ($ret, $msg) = $cf->Create( Name => "CF A", Queue => $queue->id, Type => 'FreeformSingle', ); ok($ret, "Custom Field Order created"); push @cfs, $cf; } { my $cf = RT::CustomField->new( RT->SystemUser ); my ($ret, $msg) = $cf->Create( Name => "CF C", Queue => $queue->id, Type => 'FreeformSingle', ); ok($ret, "Custom Field Order created"); push @cfs, $cf; } my ($baseurl, $m) = RT::Test->started_ok; ok $m->login( root => 'password' ), 'logged in'; diag "reorder CFs: C, A and B"; { $m->get( '/Admin/Queues/' ); $m->follow_link_ok( {text => $queue->id} ); $m->follow_link_ok( {id => 'page-custom-fields-tickets'} ); my @tmp = ($m->content =~ /(CF [ABC])/g); is_deeply(\@tmp, ['CF B', 'CF A', 'CF C']); $m->follow_link_ok( {text => '[Up]', n => 3} ); $m->follow_link_ok( {text => '[Up]', n => 2} ); $m->follow_link_ok( {text => '[Up]', n => 3} ); @tmp = ($m->content =~ /(CF [ABC])/g); is_deeply(\@tmp, ['CF C', 'CF A', 'CF B']); } diag "check ticket create, display and edit pages"; { $m->get_ok( '/Ticket/Create.html?Queue=' . $queue->id, 'go to ticket create page with queue id' ); my @tmp = ($m->content =~ /(CF [ABC])/g); is_deeply(\@tmp, ['CF C', 'CF A', 'CF B']); $m->submit_form( form_name => "TicketCreate", fields => { Subject => 'test' }, button => 'SubmitTicket', ); my ($tid) = ($m->content =~ /Ticket (\d+) created/i); ok $tid, "created a ticket succesfully"; @tmp = ($m->content =~ /(CF [ABC])/g); # x2 here because inline-edit also adds corresponding labels is_deeply(\@tmp, [('CF C', 'CF A', 'CF B')x2]); $m->follow_link_ok( {id => 'page-basics'}); @tmp = ($m->content =~ /(CF [ABC])/g); is_deeply(\@tmp, ['CF C', 'CF A', 'CF B']); } done_testing; rt-5.0.1/t/customfields/access_via_queue.t000644 000765 000024 00000013440 14005011336 021440 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => undef; use RT::Ticket; use RT::CustomField; my $queue_name = "CFSortQueue-$$"; my $queue = RT::Test->load_or_create_queue( Name => $queue_name ); ok($queue && $queue->id, "$queue_name - test queue creation"); diag "create a CF"; my $cf_name = "Rights$$"; my $cf; { $cf = RT::CustomField->new( RT->SystemUser ); my ($ret, $msg) = $cf->Create( Name => $cf_name, Queue => $queue->id, Type => 'FreeformSingle', ); ok($ret, "Custom Field Order created"); } my $tester = RT::Test->load_or_create_user( Name => 'tester', Password => 'password', ); ok $tester && $tester->id, 'loaded or created user'; my $cc_role = $queue->RoleGroup( 'Cc' ); my $owner_role = $queue->RoleGroup( 'Owner' ); ok( RT::Test->set_rights( { Principal => $tester, Right => [qw(SeeQueue ShowTicket CreateTicket ReplyToTicket Watch OwnTicket TakeTicket)] }, { Principal => $cc_role, Object => $queue, Right => [qw(SeeCustomField)] }, { Principal => $owner_role, Object => $queue, Right => [qw(ModifyCustomField)] }, ), 'set rights'); { my $ticket = RT::Ticket->new( $tester ); my ($tid, $msg) = $ticket->Create( Queue => $queue, Subject => 'test' ); ok $tid, "created ticket"; ok !$ticket->CustomFields->First, "see no fields"; } { my $ticket = RT::Ticket->new( $tester ); my ($tid, $msg) = $ticket->Create( Queue => $queue, Subject => 'test', Cc => $tester->id ); ok $tid, "created ticket"; my $cf = $ticket->CustomFields->First; ok $cf, "Ccs see cf"; } { my $ticket = RT::Ticket->new( $tester ); my ($tid, $msg) = $ticket->Create( Queue => $queue, Subject => 'test', Cc => $tester->id ); ok $tid, "created ticket"; (my $status, $msg) = $ticket->AddCustomFieldValue( Field => $cf->Name, Value => 'test' ); ok !$status, "Can not change CF"; } { my $ticket = RT::Ticket->new( $tester ); my ($tid, $msg) = $ticket->Create( Queue => $queue, Subject => 'test', Cc => $tester->id, Owner => $tester->id ); ok $tid, "created ticket"; (my $status, $msg) = $ticket->AddCustomFieldValue( Field => $cf->Name, Value => 'test' ); ok $status, "Changed CF"; is $ticket->FirstCustomFieldValue( $cf->Name ), 'test'; ($status, $msg) = $ticket->DeleteCustomFieldValue( Field => $cf->Name, Value => 'test' ); ok $status, "Changed CF"; is $ticket->FirstCustomFieldValue( $cf->Name ), undef; } { my $ticket = RT::Ticket->new( $tester ); my ($tid, $msg) = $ticket->Create( Queue => $queue, Subject => 'test', Cc => $tester->id, Owner => $tester->id ); ok $tid, "created ticket"; (my $status, $msg) = $ticket->AddCustomFieldValue( Field => $cf->id, Value => 'test' ); ok $status, "Changed CF"; is $ticket->FirstCustomFieldValue( $cf->id ), 'test'; ($status, $msg) = $ticket->DeleteCustomFieldValue( Field => $cf->id, Value => 'test' ); ok $status, "Changed CF"; is $ticket->FirstCustomFieldValue( $cf->id ), undef; } my ($baseurl, $m) = RT::Test->started_ok; ok $m->login( tester => 'password' ), 'logged in'; diag "check that we don't have the cf on create"; { $m->get_ok( '/Ticket/Create.html?Queue=' . $queue->id, 'go to ticket create page with queue id' ); my $form = $m->form_name("TicketCreate"); my $cf_field = "Object-RT::Ticket--CustomField-". $cf->id ."-Value"; ok !$form->find_input( $cf_field ), 'no form field on the page'; $m->submit_form( form_name => "TicketCreate", fields => { Subject => 'test' }, button => 'SubmitTicket', ); my ($tid) = ($m->content =~ /Ticket (\d+) created/i); ok $tid, "created a ticket succesfully"; $m->content_lacks($cf_name, "don't see CF"); $m->follow_link( id => 'page-basics'); $form = $m->form_name('TicketModify'); $cf_field = "Object-RT::Ticket-$tid-CustomField-". $cf->id ."-Value"; ok !$form->find_input( $cf_field ), 'no form field on the page'; } diag "check that we see CF as Cc"; { my $ticket = RT::Ticket->new( $tester ); my ($tid, $msg) = $ticket->Create( Queue => $queue, Subject => 'test', Cc => $tester->id ); ok $tid, "created ticket"; ok $m->goto_ticket( $tid ), "opened ticket"; $m->content_contains($cf_name, "see CF"); } diag "check that owner can see and edit CF"; { my $ticket = RT::Ticket->new( $tester ); my ($tid, $msg) = $ticket->Create( Queue => $queue, Subject => 'test', Cc => $tester->id, Owner => $tester->id ); ok $tid, "created ticket"; ok $m->goto_ticket( $tid ), "opened ticket"; $m->content_contains($cf_name, "see CF"); $m->follow_link( id => 'page-basics'); my $form = $m->form_name('TicketModify'); my $cf_field = "Object-RT::Ticket-$tid-CustomField-". $cf->id ."-Value"; ok $form->find_input( $cf_field ), 'form field on the page'; $m->submit_form( form_name => 'TicketModify', fields => { $cf_field => "changed cf", }, ); ok $m->goto_ticket( $tid ), "opened ticket"; $m->content_contains($cf_name, "changed cf"); } note 'make sure CF is not reset to no value'; { my $t = RT::Test->create_ticket( Queue => $queue->id, Subject => 'test', 'CustomField-'.$cf->id => '2012-02-12', Cc => $tester->id, Owner => $tester->id, ); ok $t && $t->id, 'created ticket'; is $t->FirstCustomFieldValue($cf_name), '2012-02-12'; $m->goto_ticket($t->id); $m->follow_link_ok({id => 'page-basics'}); my $form = $m->form_name('TicketModify'); my $input = $form->find_input( 'Object-RT::Ticket-'. $t->id .'-CustomField-'. $cf->id .'-Value' ); ok $input, 'found input'; $m->click('SubmitTicket'); my $tid = $t->id; $t = RT::Ticket->new( $RT::SystemUser ); $t->Load( $tid ); is $t->FirstCustomFieldValue($cf_name), '2012-02-12'; } done_testing; rt-5.0.1/t/customfields/datetime_search.t000644 000765 000024 00000017301 14005011336 021255 0ustar00sunnavystaff000000 000000 use Test::MockTime qw(set_fixed_time restore_time); use warnings; use strict; use RT::Test nodata => 1, tests => undef; RT->Config->Set( 'Timezone' => 'EST5EDT' ); # -04:00 RT::Test->set_rights( { Principal => 'Everyone', Right => [qw( SeeQueue ShowTicket CreateTicket SeeCustomField ModifyCustomField )] }, ); my $q = RT::Test->load_or_create_queue( Name => 'General' ); ok $q && $q->id, 'loaded or created a queue'; my $user_m = RT::Test->load_or_create_user( Name => 'moscow', Timezone => 'Europe/Moscow' ); ok $user_m && $user_m->id; $user_m = RT::CurrentUser->new( $user_m ); my $user_b = RT::Test->load_or_create_user( Name => 'boston', Timezone => 'America/New_York' ); ok $user_b && $user_b->id; $user_b = RT::CurrentUser->new( $user_b ); my $cf = RT::CustomField->new(RT->SystemUser); ok( $cf->Create( Name => 'TestDateTime', Type => 'DateTime', MaxValues => 1, LookupType => RT::Ticket->CustomFieldLookupType, ), 'create cf datetime' ); ok( $cf->AddToObject($q), 'date cf apply to queue' ); my $cf_name = $cf->Name; my $ticket = RT::Ticket->new(RT->SystemUser); ok( $ticket->Create( Queue => $q->id, Subject => 'Test', 'CustomField-' . $cf->id => '2010-05-04 07:00:00', ), 'create ticket with cf set to 2010-05-04 07:00:00( 2010-05-04 11:00:00 with UTC )' ); is( $ticket->CustomFieldValues->First->Content, '2010-05-04 11:00:00', 'date in db is in timezone UTC' ); { my $tickets = RT::Tickets->new(RT->SystemUser); $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => '=', VALUE => '2010-05-04 07:00:00', # this timezone is server ); is( $tickets->Count, 1, 'found the ticket with exact date: 2010-05-04 07:00:00' ); } { # TODO according to the code, if OPERATOR is '=', it means on that day # this will test this behavior my $tickets = RT::Tickets->new(RT->SystemUser); $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => '=', VALUE => '2010-05-04', ); is( $tickets->Count, 1, 'found the ticket with rough date: 2010-05-04' ); } { # TODO according to the code, if OPERATOR is '=', it means on that day # this will test this behavior my $tickets = RT::Tickets->new(RT->SystemUser); $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => '=', VALUE => '2010-05-05', ); is( $tickets->Count, 0, 'did not find the ticket with wrong datetime: 2010-05-05' ); } { my $tickets = RT::Tickets->new(RT->SystemUser); $tickets->FromSQL( "'CF.{$cf_name}' = 'May 4 2010 7am'" ); is( $tickets->Count, 1, 'found the ticket with = May 4 2010 7am' ); $tickets->FromSQL( "'CF.{$cf_name}' = 'May 4 2010 8am'" ); is( $tickets->Count, 0, 'did not find the ticket with = May 4 2010 8am' ); $tickets->FromSQL( "'CF.{$cf_name}' > 'May 3 2010 7am'" ); is( $tickets->Count, 1, 'found the ticket with > May 3 2010 7am' ); $tickets->FromSQL( "'CF.{$cf_name}' < 'May 4 2010 8am'" ); is( $tickets->Count, 1, 'found the ticket with < May 4 2010 8am' ); } my $tickets = RT::Tickets->new( RT->SystemUser ); $tickets->UnLimit; while( my $ticket = $tickets->Next ) { $ticket->Delete(); } { ok( $ticket->Create( Queue => $q->id, Subject => 'Test', 'CustomField-' . $cf->id => '2010-06-21 17:00:01', ), 'create ticket with cf set to 2010-06-21 17:00:01( 2010-06-21 21:00:01 with UTC )' ); my $shanghai = RT::Test->load_or_create_user( Name => 'shanghai', Timezone => 'Asia/Shanghai', ); ok( $shanghai->PrincipalObj->GrantRight( Right => 'SuperUser', Object => $RT::System, ) ); my $current_user = RT::CurrentUser->new($shanghai); my $tickets = RT::Tickets->new($current_user); $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => '=', VALUE => '2010-06-22', ); is( $tickets->Count, 1, 'found the ticket with rough datetime: 2010-06-22' ); $tickets->UnLimit; $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => '>', VALUE => '2010-06-21', ); is( $tickets->Count, 1, 'found the ticket with > 2010-06-21' ); $tickets->UnLimit; $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => '<', VALUE => '2010-06-23', ); is( $tickets->Count, 1, 'found the ticket with < 2010-06-23' ); $tickets->UnLimit; $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => '=', VALUE => '2010-06-22 05:00:01', ); is( $tickets->Count, 1, 'found the ticket with = 2010-06-22 01:00:01' ); } # set timezone in all places to UTC { RT->SystemUser->UserObj->__Set(Field => 'Timezone', Value => 'UTC') if RT->SystemUser->UserObj->Timezone; RT->Config->Set( Timezone => 'UTC' ); } # search by absolute date with '=', but date only { my $ticket = RT::Ticket->new(RT->SystemUser); my ($tid) = $ticket->Create( Queue => $q->id, Subject => 'Test', 'CustomField-' . $cf->id => '2013-02-11 23:14:15', ); is $ticket->FirstCustomFieldValue($cf_name), '2013-02-11 23:14:15'; my $tickets = RT::Tickets->new($user_m); $tickets->FromSQL("'CustomField.{$cf_name}' = '2013-02-11' AND id = $tid"); is( $tickets->Count, 0); $tickets = RT::Tickets->new($user_m); $tickets->FromSQL("'CustomField.{$cf_name}' = '2013-02-12' AND id = $tid"); is( $tickets->Count, 1); $tickets = RT::Tickets->new($user_b); $tickets->FromSQL("'CustomField.{$cf_name}' = '2013-02-11' AND id = $tid"); is( $tickets->Count, 1); $tickets = RT::Tickets->new($user_b); $tickets->FromSQL("'CustomField.{$cf_name}' = '2013-02-12' AND id = $tid"); is( $tickets->Count, 0); } { my $tickets = RT::Tickets->new(RT->SystemUser); $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => 'IS', VALUE => 'NULL', ); is( $tickets->Count, 0, 'did not find the ticket with date IS NULL' ); } { my $tickets = RT::Tickets->new(RT->SystemUser); $tickets->LimitCustomField( CUSTOMFIELD => $cf->id, OPERATOR => 'IS NOT', VALUE => 'NULL', ); is( $tickets->Count, 2, 'did find the ticket with date IS NOT NULL' ); } # search by relative date with '=', but date only { my $ticket = RT::Ticket->new(RT->SystemUser); my ($tid) = $ticket->Create( Queue => $q->id, Subject => 'Test', 'CustomField-' . $cf->id => '2013-02-11 23:14:15', ); is $ticket->FirstCustomFieldValue($cf_name), '2013-02-11 23:14:15'; set_fixed_time("2013-02-10T16:10:00Z"); my $tickets = RT::Tickets->new($user_m); $tickets->FromSQL("'CustomField.{$cf_name}' = 'tomorrow' AND id = $tid"); is( $tickets->Count, 0); set_fixed_time("2013-02-10T23:10:00Z"); $tickets = RT::Tickets->new($user_m); $tickets->FromSQL("'CustomField.{$cf_name}' = 'tomorrow' AND id = $tid"); is( $tickets->Count, 1); set_fixed_time("2013-02-10T23:10:00Z"); $tickets = RT::Tickets->new($user_b); $tickets->FromSQL("'CustomField.{$cf_name}' = 'tomorrow' AND id = $tid"); is( $tickets->Count, 1); set_fixed_time("2013-02-10T02:10:00Z"); $tickets = RT::Tickets->new($user_b); $tickets->FromSQL("'CustomField.{$cf_name}' = 'tomorrow' AND id = $tid"); is( $tickets->Count, 0); } done_testing; rt-5.0.1/t/customfields/iprangev6.t000644 000765 000024 00000043306 14005011336 020041 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 158; my ($baseurl, $agent) =RT::Test->started_ok; ok( $agent->login, 'log in' ); my $q = RT::Queue->new($RT::SystemUser); $q->Load('General'); my $ip_cf = RT::CustomField->new($RT::SystemUser); my ($val,$msg) = $ip_cf->Create(Name => 'IP', Type =>'IPAddressRange', LookupType => 'RT::Queue-RT::Ticket'); ok($val,$msg); my $cf_id = $val; $ip_cf->AddToObject($q); use_ok('RT'); my $cf; diag "load and check basic properties of the IP CF" if $ENV{'TEST_VERBOSE'}; { my $cfs = RT::CustomFields->new( $RT::SystemUser ); $cfs->Limit( FIELD => 'Name', VALUE => 'IP', CASESENSITIVE => 0 ); is( $cfs->Count, 1, "found one CF with name 'IP'" ); $cf = $cfs->First; is( $cf->Type, 'IPAddressRange', 'type check' ); is( $cf->LookupType, 'RT::Queue-RT::Ticket', 'lookup type check' ); ok( !$cf->MaxValues, "unlimited number of values" ); ok( !$cf->Disabled, "not disabled" ); } diag "check that CF applies to queue General" if $ENV{'TEST_VERBOSE'}; { my $cfs = $q->TicketCustomFields; $cfs->Limit( FIELD => 'id', VALUE => $cf->id, ENTRYAGGREGATOR => 'AND' ); is( $cfs->Count, 1, 'field applies to queue' ); } my %valid = ( 'abcd:' x 7 . 'abcd' => 'abcd:' x 7 . 'abcd', '034:' x 7 . '034' => '34:' x 7 . '34', 'abcd::' => 'abcd::', '::abcd' => '::abcd', 'abcd::034' => 'abcd::34', 'abcd::192.168.1.1' => 'abcd::c0a8:101', '::192.168.1.1' => '::c0a8:101', '::' => '::', ); diag "create a ticket via web and set IP" if $ENV{'TEST_VERBOSE'}; { for my $ip ( keys %valid ) { ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $ip, }, button => 'SubmitTicket', ); $agent->content_like( qr/$valid{$ip}/, "IP on the page" ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), $valid{$ip}, 'correct value' ); } } diag "create a ticket via web with CIDR" if $ENV{'TEST_VERBOSE'}; { my $val = 'abcd:034::/31'; ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $val, }, button => 'SubmitTicket' ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), 'abcd:34::-abcd:35:ffff:ffff:ffff:ffff:ffff:ffff', 'correct value' ); } diag "create a ticket and edit IP field using Edit page" if $ENV{'TEST_VERBOSE'}; { my $val = 'abcd' . ':abcd' x 7; ok $agent->goto_create_ticket($q), "go to create ticket"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', }, button => 'SubmitTicket' ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $cf_field = "Object-RT::Ticket-$id-CustomField-$cf_id-Values"; $agent->follow_link_ok( { text => 'Basics', n => "1" }, "Followed 'Basics' link" ); $agent->form_name('TicketModify'); is( $agent->value($cf_field), '', 'IP is empty' ); $agent->field( $cf_field => $val ); $agent->click('SubmitTicket'); $agent->content_contains( $val, "IP on the page" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), $val, 'correct value' ); diag "set IP with spaces around" if $ENV{'TEST_VERBOSE'}; $agent->follow_link_ok( { text => 'Basics', n => "1" }, "Followed 'Basics' link" ); $agent->form_name('TicketModify'); is( $agent->value($cf_field), $val, 'IP is in input box' ); $val = 'bbcd' . ':abcd' x 7; $agent->field( $cf_field => " $val " ); $agent->click('SubmitTicket'); $agent->content_contains( $val, "IP on the page" ); $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), $val, 'correct value' ); diag "replace IP with a range" if $ENV{'TEST_VERBOSE'}; $agent->follow_link_ok( { text => 'Basics', n => "1" }, "Followed 'Basics' link" ); $agent->form_name('TicketModify'); is( $agent->value($cf_field), $val, 'IP is in input box' ); $val = 'abcd::' . '-' . 'abcd' . ':ffff' x 7; $agent->field( $cf_field => 'abcd::/16' ); $agent->click('SubmitTicket'); $agent->content_contains( $val, "IP on the page" ); $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), $val, 'correct value' ); diag "delete range, add another range using CIDR" if $ENV{'TEST_VERBOSE'}; $agent->follow_link_ok( { text => 'Basics', n => "1" }, "Followed 'Basics' link" ); $agent->form_name('TicketModify'); is( $agent->value($cf_field), $val, 'IP is in input box' ); $val = 'bb::' . '-' . 'bbff' . ':ffff' x 7; $agent->field( $cf_field => $val ); $agent->click('SubmitTicket'); $agent->content_contains( $val, "IP on the page" ); $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), $val, 'correct value' ); } diag "check that we parse correct IPs only" if $ENV{'TEST_VERBOSE'}; { my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; my @invalid = ( 'abcd:', 'efgh', 'abcd:' x 8 . 'abcd', 'abcd::abcd::abcd' ); for my $invalid (@invalid) { ok $agent->goto_create_ticket($q), "go to create ticket"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $invalid, }, button => 'SubmitTicket' ); $agent->content_like( qr/is not a valid IP address range/, 'ticket fails to create' ); } } diag "search tickets by IP" if $ENV{'TEST_VERBOSE'}; { my $val = 'abcd::/16'; ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $val, }, button => 'SubmitTicket' ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); my $tickets = RT::Tickets->new($RT::SystemUser); $tickets->FromSQL("id = $id AND CF.{IP} = 'abcd::/16'"); ok( $tickets->Count, "found tickets" ); is( $ticket->FirstCustomFieldValue('IP'), 'abcd::-abcd:ffff:ffff:ffff:ffff:ffff:ffff:ffff', 'correct value' ); } diag "search tickets by IP range" if $ENV{'TEST_VERBOSE'}; { my $val = 'abcd:ef00::/24'; ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $val, }, button => 'SubmitTicket' ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); my $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("id = $id AND CF.{IP} = 'abcd:ef::-abcd:efff:ffff:ffff:ffff:ffff:ffff:ffff'"); ok( $tickets->Count, "found tickets" ); is( $ticket->FirstCustomFieldValue('IP'), 'abcd:ef00::-abcd:efff:ffff:ffff:ffff:ffff:ffff:ffff', 'correct value' ); } diag "create two tickets with different IPs and check several searches" if $ENV{'TEST_VERBOSE'}; { ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; my $first_ip = 'cbcd::'; my $second_ip = 'cbdd::'; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $first_ip, }, button => 'SubmitTicket' ); my ($id1) = $agent->content =~ /Ticket (\d+) created/; ok( $id1, "created first ticket $id1" ); ok $agent->goto_create_ticket($q), "go to create ticket"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $second_ip, }, button => 'SubmitTicket' ); my ($id2) = $agent->content =~ /Ticket (\d+) created/; ok( $id2, "created second ticket $id2" ); my $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("id = $id1 OR id = $id2"); is( $tickets->Count, 2, "found both tickets by 'id = x OR y'" ); # IP $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '$first_ip'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), $first_ip, "correct value" ); $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '$second_ip'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), $second_ip, "correct value" ); # IP/32 - one address $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'cbcd::/16'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), $first_ip, "correct value" ); $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'cbdd::/16'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), $second_ip, "correct value" ); # IP range $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL( "(id = $id1 OR id = $id2) AND CF.{IP} = '$first_ip-cbcf::'" ); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), $first_ip, "correct value" ); $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL( "(id = $id1 OR id = $id2) AND CF.{IP} = '$second_ip-cbdf::'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), $second_ip, "correct value" ); # IP range, with start IP greater than end $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'cbcf::-$first_ip'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), $first_ip,, "correct value" ); $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'cbdf::-$second_ip'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), $second_ip, "correct value" ); # CIDR/12 $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'cbcd::/12'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), $first_ip, "correct value" ); $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'cbdd::/12'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), $second_ip, "correct value" ); # IP is not in CIDR/24 $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} != 'cbcd::/12'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), $second_ip,, "correct value" ); $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} != 'cbdd::/12'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), $first_ip, "correct value" ); # CIDR or CIDR $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND " ."(CF.{IP} = 'cbcd::/12' OR CF.{IP} = 'cbdd::/12')"); is( $tickets->Count, 2, "found both tickets" ); } diag "create two tickets with different IP ranges and check several searches" if $ENV{'TEST_VERBOSE'}; { ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => 'ddcd::/16', }, button => 'SubmitTicket' ); my ($id1) = $agent->content =~ /Ticket (\d+) created/; ok( $id1, "created first ticket $id1" ); ok $agent->goto_create_ticket($q), "go to create ticket"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => 'edcd::/16', }, button => 'SubmitTicket' ); my ($id2) = $agent->content =~ /Ticket (\d+) created/; ok( $id2, "created ticket $id2" ); my $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("id = $id1 OR id = $id2"); is( $tickets->Count, 2, "found both tickets by 'id = x OR y'" ); # IP $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'ddcd::'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id1, "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'ddcd:abcd::'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id1, "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'ddcd:ffff::'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id1, "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'edcd::abcd'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id2, "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'edcd::ffff'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id2, "correct value" ); $tickets->FromSQL( "(id = $id1 OR id = $id2) AND CF.{IP} = 'edcd:ffff:ffff:ffff:ffff:ffff:ffff:ffff'" ); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id2, "correct value" ); # IP/32 - one address $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'ddcd::/32'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id1, "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'edcd::/32'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id2, "correct value" ); # IP range, lower than both $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'abcd::/32'"); is( $tickets->Count, 0, "didn't finnd ticket" ) or diag "but found ". $tickets->First->id; # IP range, intersect with the first range $tickets->FromSQL( "(id = $id1 OR id = $id2) AND CF.{IP} = 'ddcc::-ddcd:ab::'" ); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id1, "correct value" ); # IP range, equal to the first range $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'ddcd::/16'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id1, "correct value" ); # IP range, lay inside the first range $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'ddcd:ab::'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id1, "correct value" ); # IP range, intersect with the ranges $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'ddcc::-edcd:ab::'"); is( $tickets->Count, 2, "found both tickets" ); # IP range, equal to range from the starting IP of the first ticket to the ending IP of the second $tickets->FromSQL( "(id = $id1 OR id = $id2) AND CF.{IP} = 'ddcd::-edcd:ffff:ffff:ffff:ffff:ffff:ffff:ffff'" ); is( $tickets->Count, 2, "found both tickets" ); # IP range, has the both ranges inside it $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'd000::/2'"); is( $tickets->Count, 2, "found both tickets" ); # IP range, greater than both $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'ffff::/16'"); is( $tickets->Count, 0, "didn't find ticket" ) or diag "but found ". $tickets->First->id; } rt-5.0.1/t/customfields/ip.t000644 000765 000024 00000023454 14005011336 016552 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use Test::Warn; my ( $baseurl, $agent ) = RT::Test->started_ok; ok( $agent->login, 'log in' ); my $q = RT::Queue->new($RT::SystemUser); $q->Load('General'); my $ip_cf = RT::CustomField->new($RT::SystemUser); my ( $val, $msg ) = $ip_cf->Create( Name => 'IP', Type => 'IPAddress', LookupType => 'RT::Queue-RT::Ticket' ); ok( $val, $msg ); my $cf_id = $val; $ip_cf->AddToObject($q); use_ok('RT'); my $cf; diag "load and check basic properties of the IP CF" if $ENV{'TEST_VERBOSE'}; { my $cfs = RT::CustomFields->new($RT::SystemUser); $cfs->Limit( FIELD => 'Name', VALUE => 'IP', CASESENSITIVE => 0 ); is( $cfs->Count, 1, "found one CF with name 'IP'" ); $cf = $cfs->First; is( $cf->Type, 'IPAddress', 'type check' ); is( $cf->LookupType, 'RT::Queue-RT::Ticket', 'lookup type check' ); ok( !$cf->MaxValues, "unlimited number of values" ); ok( !$cf->Disabled, "not disabled" ); } diag "check that CF applies to queue General" if $ENV{'TEST_VERBOSE'}; { my $cfs = $q->TicketCustomFields; $cfs->Limit( FIELD => 'id', VALUE => $cf->id, ENTRYAGGREGATOR => 'AND' ); is( $cfs->Count, 1, 'field applies to queue' ); } diag "create a ticket via web and set IP" if $ENV{'TEST_VERBOSE'}; { my $val = '192.168.20.1'; ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $val, }, button => 'SubmitTicket', ); $agent->content_contains( $val, "IP on the page" ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), $val, 'correct value' ); } diag "create a ticket and edit IP field using Edit page" if $ENV{'TEST_VERBOSE'}; { my $val = '172.16.0.1'; ok $agent->goto_create_ticket($q), "go to create ticket"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', }, button => 'SubmitTicket', ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $cf_field = "Object-RT::Ticket-$id-CustomField-$cf_id-Values"; $agent->follow_link_ok( { text => 'Basics', n => "1" }, "Followed 'Basics' link" ); $agent->form_name('TicketModify'); is( $agent->value($cf_field), '', 'IP is empty' ); $agent->field( $cf_field => $val ); $agent->click('SubmitTicket'); $agent->content_contains( $val, "IP on the page" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), '172.16.0.1' ); diag "set IP with spaces around" if $ENV{'TEST_VERBOSE'}; $val = " 172.16.0.2 \n "; $agent->follow_link_ok( { text => 'Basics', n => "1" }, "Followed 'Basics' link" ); $agent->form_name('TicketModify'); is( $agent->value($cf_field), '172.16.0.1', 'IP is in input box' ); $agent->field( $cf_field => $val ); $agent->click('SubmitTicket'); $agent->content_contains( '172.16.0.2', "IP on the page" ); $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), '172.16.0.2', 'correct value' ); } diag "check that we parse correct IPs only" if $ENV{'TEST_VERBOSE'}; { my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; for my $valid (qw/1.0.0.0 255.255.255.255/) { ok $agent->goto_create_ticket($q), "go to create ticket"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $valid, }, button => 'SubmitTicket', ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); is( $ticket->id, $id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), $valid, 'correct value' ); } for my $invalid (qw{255.255.255.256 355.255.255.255 8.13.8/8.13.0/1.0}) { ok $agent->goto_create_ticket($q), "go to create ticket"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $invalid, } ); $agent->content_contains( 'is not a valid IP address', 'ticket fails to create' ); } } diag "search tickets by IP" if $ENV{'TEST_VERBOSE'}; { my $val = '172.16.1.1'; ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $val, }, button => 'SubmitTicket', ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); my $tickets = RT::Tickets->new($RT::SystemUser); $tickets->FromSQL("id = $id AND CF.{IP} = '172.16.1.1'"); ok( $tickets->Count, "found tickets" ); } diag "create two tickets with different IPs and check several searches" if $ENV{'TEST_VERBOSE'}; { ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => '192.168.21.10', }, button => 'SubmitTicket', ); my ($id1) = $agent->content =~ /Ticket (\d+) created/; ok( $id1, "created first ticket $id1" ); ok $agent->goto_create_ticket($q), "go to create ticket"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => '192.168.22.10', }, button => 'SubmitTicket', ); my ($id2) = $agent->content =~ /Ticket (\d+) created/; ok( $id2, "created second ticket $id2" ); my $tickets = RT::Tickets->new($RT::SystemUser); $tickets->FromSQL("id = $id1 OR id = $id2"); is( $tickets->Count, 2, "found both tickets by 'id = x OR y'" ); # IP $tickets = RT::Tickets->new($RT::SystemUser); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.10'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.21.10', "correct value" ); $tickets = RT::Tickets->new($RT::SystemUser); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.22.10'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.22.10', "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} <= '192.168.21.10'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.21.10', "correct value" ); $tickets = RT::Tickets->new($RT::SystemUser); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} >= '192.168.22.10'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.22.10', "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} > '192.168.22.10'"); is( $tickets->Count, 0, "no tickets found" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} < '192.168.21.10'"); is( $tickets->Count, 0, "no tickets found" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} < '192.168.22.10'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.21.10', "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} > '192.168.21.10'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.22.10', "correct value" ); } diag "create a ticket with an IP of 10.0.0.1 and search for doesn't match '10.0.0.'." if $ENV{'TEST_VERBOSE'}; { ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'local', $cf_field => '10.0.0.1', }, button => 'SubmitTicket', ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created first ticket $id" ); my $tickets = RT::Tickets->new($RT::SystemUser); warning_like { $tickets->FromSQL("id=$id AND CF.{IP} NOT LIKE '10.0.0.'"); } [qr/not a valid IPAddress/], "caught warning about valid IP address"; TODO: { local $TODO = "partial ip parse causes ambiguity"; is( $tickets->Count, 0, "should not have found the ticket" ); } } diag "test the operators in search page" if $ENV{'TEST_VERBOSE'}; { $agent->get_ok( $baseurl . "/Search/Build.html?Query=Queue='General'" ); $agent->content_contains('CF.{IP}', 'got CF.{IP}'); my $form = $agent->form_name('BuildQuery'); my $op = $form->find_input("CF.{IP}Op"); ok( $op, "found CF.{IP}Op" ); is_deeply( [ $op->possible_values ], [ '=', '!=', '<', '>' ], 'op values' ); } done_testing; rt-5.0.1/t/customfields/single_values.t000644 000765 000024 00000001613 14005011336 020773 0ustar00sunnavystaff000000 000000 use warnings; use strict; use RT; use RT::Test nodata => 1, tests => 8; my $q = RT::Queue->new(RT->SystemUser); my ($id,$msg) =$q->Create(Name => "CF-Single-".$$); ok($id,$msg); my $cf = RT::CustomField->new(RT->SystemUser); ($id,$msg) = $cf->Create(Name => 'Single-'.$$, Type => 'Select', MaxValues => '1', Queue => $q->id); ok($id,$msg); ($id,$msg) =$cf->AddValue(Name => 'First'); ok($id,$msg); ($id,$msg) =$cf->AddValue(Name => 'Second'); ok($id,$msg); my $t = RT::Ticket->new(RT->SystemUser); ($id,undef,$msg) = $t->Create(Queue => $q->id, Subject => 'CF Test'); ok($id,$msg); is($t->CustomFieldValues($cf->id)->Count, 0, "No values yet"); $t->AddCustomFieldValue(Field => $cf->id, Value => 'First'); is($t->CustomFieldValues($cf->id)->Count, 1, "One now"); $t->AddCustomFieldValue(Field => $cf->id, Value => 'Second'); is($t->CustomFieldValues($cf->id)->Count, 1, "Still one"); rt-5.0.1/t/customfields/iprange.t000644 000765 000024 00000044605 14005011336 017570 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 133; my ($baseurl, $agent) =RT::Test->started_ok; ok( $agent->login, 'log in' ); my $q = RT::Queue->new($RT::SystemUser); $q->Load('General'); my $ip_cf = RT::CustomField->new($RT::SystemUser); my ($val,$msg) = $ip_cf->Create(Name => 'IP', Type =>'IPAddressRange', LookupType => 'RT::Queue-RT::Ticket'); ok($val,$msg); my $cf_id = $val; $ip_cf->AddToObject($q); use_ok('RT'); my $cf; diag "load and check basic properties of the IP CF" if $ENV{'TEST_VERBOSE'}; { my $cfs = RT::CustomFields->new( $RT::SystemUser ); $cfs->Limit( FIELD => 'Name', VALUE => 'IP', CASESENSITIVE => 0 ); is( $cfs->Count, 1, "found one CF with name 'IP'" ); $cf = $cfs->First; is( $cf->Type, 'IPAddressRange', 'type check' ); is( $cf->LookupType, 'RT::Queue-RT::Ticket', 'lookup type check' ); ok( !$cf->MaxValues, "unlimited number of values" ); ok( !$cf->Disabled, "not disabled" ); } diag "check that CF applies to queue General" if $ENV{'TEST_VERBOSE'}; { my $cfs = $q->TicketCustomFields; $cfs->Limit( FIELD => 'id', VALUE => $cf->id, ENTRYAGGREGATOR => 'AND' ); is( $cfs->Count, 1, 'field applies to queue' ); } diag "create a ticket via web and set IP" if $ENV{'TEST_VERBOSE'}; { my $val = '192.168.20.1'; ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $val, }, button => 'SubmitTicket', ); $agent->content_like( qr/\Q$val/, "IP on the page" ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), $val, 'correct value' ); } diag "create a ticket via web with CIDR" if $ENV{'TEST_VERBOSE'}; { my $val = '172.16.20/31'; ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $val, }, button => 'SubmitTicket', ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), '172.16.20.0-172.16.20.1', 'correct value' ); } diag "create a ticket and edit IP field using Edit page" if $ENV{'TEST_VERBOSE'}; { my $val = '172.16.0.1'; ok $agent->goto_create_ticket($q), "go to create ticket"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', }, button => 'SubmitTicket', ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $cf_field = "Object-RT::Ticket-$id-CustomField-$cf_id-Values"; $agent->follow_link_ok( { text => 'Basics', n => "1" }, "Followed 'Basics' link" ); $agent->form_name('TicketModify'); like( $agent->value($cf_field), qr/^\s*$/, 'IP is empty' ); $agent->field( $cf_field => $val ); $agent->click('SubmitTicket'); $agent->content_like( qr/\Q$val/, "IP on the page" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), $val, 'correct value' ); diag "set IP with spaces around" if $ENV{'TEST_VERBOSE'}; $val = " 172.16.0.2 \n "; $agent->follow_link_ok( { text => 'Basics', n => "1" }, "Followed 'Basics' link" ); $agent->form_name('TicketModify'); like( $agent->value($cf_field), qr/^\s*\Q172.16.0.1\E\s*$/, 'IP is in input box' ); $agent->field( $cf_field => $val ); $agent->click('SubmitTicket'); $agent->content_like( qr/\Q172.16.0.2/, "IP on the page" ); $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), '172.16.0.2', 'correct value' ); diag "replace IP with a range" if $ENV{'TEST_VERBOSE'}; $val = '172.16.0.0-172.16.0.255'; $agent->follow_link_ok( { text => 'Basics', n => "1" }, "Followed 'Basics' link" ); $agent->form_name('TicketModify'); like( $agent->value($cf_field), qr/^\s*\Q172.16.0.2\E\s*$/, 'IP is in input box' ); $agent->field( $cf_field => $val ); $agent->click('SubmitTicket'); $agent->content_like( qr/\Q$val/, "IP on the page" ); $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), $val, 'correct value' ); diag "delete range, add another range using CIDR" if $ENV{'TEST_VERBOSE'}; $val = '172.16/16'; $agent->follow_link_ok( { text => 'Basics', n => "1" }, "Followed 'Basics' link" ); $agent->form_name('TicketModify'); is( $agent->value($cf_field), '172.16.0.0-172.16.0.255', 'IP is in input box' ); $agent->field( $cf_field => $val ); $agent->click('SubmitTicket'); $agent->content_like( qr/\Q$val/, "IP on the page" ); $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), '172.16.0.0-172.16.255.255', 'correct value' ); } diag "check that we parse correct IPs only" if $ENV{'TEST_VERBOSE'}; { my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; for my $valid (qw/1.0.0.0 255.255.255.255/) { ok $agent->goto_create_ticket($q), "go to create ticket"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $valid, }, button => 'SubmitTicket', ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); is( $ticket->id, $id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), $valid, 'correct value' ); } for my $invalid (qw{255.255.255.256 355.255.255.255 8.13.8/8.13.0/1.0}) { ok $agent->goto_create_ticket($q), "go to create ticket"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $invalid, }, button => 'SubmitTicket', ); $agent->content_like( qr/is not a valid IP address range/, 'ticket fails to create' ); } } diag "search tickets by IP" if $ENV{'TEST_VERBOSE'}; { my $val = '172.16.1/31'; ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $val, }, button => 'SubmitTicket', ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); my $tickets = RT::Tickets->new($RT::SystemUser); $tickets->FromSQL("id = $id AND CF.{IP} = '172.16.1.1'"); ok( $tickets->Count, "found tickets" ); is( $ticket->FirstCustomFieldValue('IP'), '172.16.1.0-172.16.1.1', 'correct value' ); } diag "search tickets by IP range" if $ENV{'TEST_VERBOSE'}; { my $val = '172.16.2/26'; ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $val, }, button => 'SubmitTicket', ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); my $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("id = $id AND CF.{IP} = '172.16.2.0-172.16.2.255'"); ok( $tickets->Count, "found tickets" ); is( $ticket->FirstCustomFieldValue('IP'), '172.16.2.0-172.16.2.63', 'correct value' ); } diag "create two tickets with different IPs and check several searches" if $ENV{'TEST_VERBOSE'}; { ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => '192.168.21.10', }, button => 'SubmitTicket', ); my ($id1) = $agent->content =~ /Ticket (\d+) created/; ok( $id1, "created first ticket $id1" ); ok $agent->goto_create_ticket($q), "go to create ticket"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => '192.168.22.10', }, button => 'SubmitTicket', ); my ($id2) = $agent->content =~ /Ticket (\d+) created/; ok( $id2, "created second ticket $id2" ); my $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("id = $id1 OR id = $id2"); is( $tickets->Count, 2, "found both tickets by 'id = x OR y'" ); # IP $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.10'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.21.10', "correct value" ); $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.22.10'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.22.10', "correct value" ); # IP/32 - one address $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.10/32'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.21.10', "correct value" ); $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.22.10/32'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.22.10', "correct value" ); # IP range $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.0-192.168.21.255'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.21.10', "correct value" ); $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.22.0-192.168.22.255'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.22.10', "correct value" ); # IP range, with start IP greater than end $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.255-192.168.21.0'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.21.10', "correct value" ); $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.22.255-192.168.22.0'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.22.10', "correct value" ); # CIDR/24 $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.0/24'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.21.10', "correct value" ); $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.22.0/24'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.22.10', "correct value" ); # IP is not in CIDR/24 $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} != '192.168.21.0/24'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.22.10', "correct value" ); $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} != '192.168.22.0/24'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), '192.168.21.10', "correct value" ); # CIDR or CIDR $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND " ."(CF.{IP} = '192.168.21.0/24' OR CF.{IP} = '192.168.22.0/24')"); is( $tickets->Count, 2, "found both tickets" ); } diag "create two tickets with different IP ranges and check several searches" if $ENV{'TEST_VERBOSE'}; { ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => '192.168.21.0-192.168.21.127', }, button => 'SubmitTicket' ); my ($id1) = $agent->content =~ /Ticket (\d+) created/; ok( $id1, "created first ticket $id1" ); ok $agent->goto_create_ticket($q), "go to create ticket"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => '192.168.21.128-192.168.21.255', }, button => 'SubmitTicket' ); my ($id2) = $agent->content =~ /Ticket (\d+) created/; ok( $id2, "created ticket $id2" ); my $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("id = $id1 OR id = $id2"); is( $tickets->Count, 2, "found both tickets by 'id = x OR y'" ); # IP $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.0'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id1, "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.64'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id1, "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.127'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id1, "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.128'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id2, "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.191'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id2, "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.255'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id2, "correct value" ); # IP/32 - one address $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.63/32'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id1, "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.191/32'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id2, "correct value" ); # IP range, lower than both $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.20.0-192.168.20.255'"); is( $tickets->Count, 0, "didn't finnd ticket" ) or diag "but found ". $tickets->First->id; # IP range, intersect with the first range $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.20.0-192.168.21.63'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id1, "correct value" ); # IP range, equal to the first range $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.0-192.168.21.127'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id1, "correct value" ); # IP range, lay inside the first range $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.31-192.168.21.63'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->id, $id1, "correct value" ); # IP range, intersect with the ranges $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.31-192.168.21.191'"); is( $tickets->Count, 2, "found both tickets" ); # IP range, equal to range from the starting IP of the first ticket to the ending IP of the second $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.21.0-192.168.21.255'"); is( $tickets->Count, 2, "found both tickets" ); # IP range, has the both ranges inside it $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168/16'"); is( $tickets->Count, 2, "found both tickets" ); # IP range, greater than both $tickets = RT::Tickets->new( $RT::SystemUser ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = '192.168.22/24'"); is( $tickets->Count, 0, "didn't find ticket" ) or diag "but found ". $tickets->First->id; } diag "test the operators in search page" if $ENV{'TEST_VERBOSE'}; { $agent->get_ok( $baseurl . "/Search/Build.html?Query=Queue='General'" ); $agent->content_contains('CF.{IP}', 'got CF.{IP}'); my $form = $agent->form_name('BuildQuery'); my $op = $form->find_input("CF.{IP}Op"); ok( $op, "found CF.{IP}Op" ); is_deeply( [ $op->possible_values ], [ '=', '!=', '<', '>' ], 'op values' ); } rt-5.0.1/t/customfields/transaction.t000644 000765 000024 00000002512 14005011336 020457 0ustar00sunnavystaff000000 000000 use warnings; use strict; use Data::Dumper; use RT::Test nodata => 1, tests => 13; use_ok('RT'); use_ok('RT::Transactions'); my $q = RT::Queue->new(RT->SystemUser); my ($id,$msg) = $q->Create( Name => 'TxnCFTest'.$$); ok($id,$msg); my $cf = RT::CustomField->new(RT->SystemUser); ($id,$msg) = $cf->Create(Name => 'Txnfreeform-'.$$, Type => 'Freeform', MaxValues => '0', LookupType => RT::Transaction->CustomFieldLookupType ); ok($id,$msg); ($id,$msg) = $cf->AddToObject($q); ok($id,$msg); my $ticket = RT::Ticket->new(RT->SystemUser); my $transid; ($id,$transid, $msg) = $ticket->Create(Queue => $q->id, Subject => 'TxnCF test', ); ok($id,$msg); my $trans = RT::Transaction->new(RT->SystemUser); $trans->Load($transid); is($trans->ObjectId,$id); is ($trans->ObjectType, 'RT::Ticket'); is ($trans->Type, 'Create'); my $txncfs = $trans->CustomFields; is ($txncfs->Count, 1, "We have one custom field"); my $txn_cf = $txncfs->First; is ($txn_cf->id, $cf->id, "It's the right custom field"); my $values = $trans->CustomFieldValues($txn_cf->id); is ($values->Count, 0, "It has no values"); $trans->UpdateCustomFields( 'CustomField-'.$cf->id => 'Test'); $values = $trans->CustomFieldValues($txn_cf->id); is ($values->Count, 1, "it has a value"); # TODO ok(0, "Should updating custom field values remove old values?"); rt-5.0.1/t/customfields/ipv6.t000644 000765 000024 00000021133 14005011336 017016 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use Test::Warn; my ( $baseurl, $agent ) = RT::Test->started_ok; ok( $agent->login, 'log in' ); my $q = RT::Queue->new($RT::SystemUser); $q->Load('General'); my $ip_cf = RT::CustomField->new($RT::SystemUser); my ( $val, $msg ) = $ip_cf->Create( Name => 'IP', Type => 'IPAddress', LookupType => 'RT::Queue-RT::Ticket' ); ok( $val, $msg ); my $cf_id = $val; $ip_cf->AddToObject($q); use_ok('RT'); my $cf; diag "load and check basic properties of the IP CF" if $ENV{'TEST_VERBOSE'}; { my $cfs = RT::CustomFields->new($RT::SystemUser); $cfs->Limit( FIELD => 'Name', VALUE => 'IP', CASESENSITIVE => 0 ); is( $cfs->Count, 1, "found one CF with name 'IP'" ); $cf = $cfs->First; is( $cf->Type, 'IPAddress', 'type check' ); is( $cf->LookupType, 'RT::Queue-RT::Ticket', 'lookup type check' ); ok( !$cf->MaxValues, "unlimited number of values" ); ok( !$cf->Disabled, "not disabled" ); } diag "check that CF applies to queue General" if $ENV{'TEST_VERBOSE'}; { my $cfs = $q->TicketCustomFields; $cfs->Limit( FIELD => 'id', VALUE => $cf->id, ENTRYAGGREGATOR => 'AND' ); is( $cfs->Count, 1, 'field applies to queue' ); } my %valid = ( 'abcd:' x 7 . 'abcd' => 'abcd:' x 7 . 'abcd', '034:' x 7 . '034' => '34:' x 7 . '34', 'abcd::' => 'abcd::', '::abcd' => '::abcd', 'abcd::034' => 'abcd::34', 'abcd::192.168.1.1' => 'abcd::c0a8:101', '::192.168.1.1' => '::c0a8:101', '::' => '::', '034:' x 7 . '034' => '34:'x7 . '34', ); diag "create a ticket via web and set IP" if $ENV{'TEST_VERBOSE'}; { for my $ip ( keys %valid ) { ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $ip, }, button => 'SubmitTicket', ); $agent->content_contains( $valid{$ip}, "IP on the page" ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), $valid{$ip}, 'correct value' ); my $tickets = RT::Tickets->new($RT::SystemUser); $tickets->FromSQL("id = $id AND CF.{IP} = '$ip'"); ok( $tickets->Count, "found tickets" ); } } diag "create a ticket and edit IP field using Edit page" if $ENV{'TEST_VERBOSE'}; { my $ip = 'abcd::034'; ok $agent->goto_create_ticket($q), "go to create ticket"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', }, button => 'SubmitTicket', ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created ticket $id" ); my $cf_field = "Object-RT::Ticket-$id-CustomField-$cf_id-Values"; $agent->follow_link_ok( { text => 'Basics', n => "1" }, "Followed 'Basics' link" ); $agent->form_name('TicketModify'); is( $agent->value($cf_field), '', 'IP is empty' ); $agent->field( $cf_field => $valid{$ip} ); $agent->click('SubmitTicket'); $agent->content_contains( $valid{$ip}, "IP on the page" ); my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); my $values = $ticket->CustomFieldValues('IP'); is( $ticket->FirstCustomFieldValue('IP'), $valid{$ip}, 'correct value' ); diag "set IP with spaces around" if $ENV{'TEST_VERBOSE'}; my $new_ip = '::3141'; $agent->follow_link_ok( { text => 'Basics', n => "1" }, "Followed 'Basics' link" ); $agent->form_name('TicketModify'); is( $agent->value($cf_field), $valid{$ip}, 'IP is in input box' ); $agent->field( $cf_field => $new_ip ); $agent->click('SubmitTicket'); $agent->content_contains( $new_ip, "IP on the page" ); $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($id); ok( $ticket->id, 'loaded ticket' ); is( $ticket->FirstCustomFieldValue('IP'), $new_ip, 'correct value' ); } diag "check that we parse correct IPs only" if $ENV{'TEST_VERBOSE'}; { my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; my @invalid = ( 'abcd:', 'efgh', 'abcd:' x 8 . 'abcd', 'abcd::abcd::abcd' ); for my $invalid (@invalid) { ok $agent->goto_create_ticket($q), "go to create ticket"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => $invalid, }, button => 'SubmitTicket', ); $agent->content_contains( 'is not a valid IP address', 'ticket fails to create' ); } } diag "create two tickets with different IPs and check several searches" if $ENV{'TEST_VERBOSE'}; { ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => 'abcd::', }, button => 'SubmitTicket', ); my ($id1) = $agent->content =~ /Ticket (\d+) created/; ok( $id1, "created first ticket $id1" ); ok $agent->goto_create_ticket($q), "go to create ticket"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ip', $cf_field => 'bbcd::', }, button => 'SubmitTicket', ); my ($id2) = $agent->content =~ /Ticket (\d+) created/; ok( $id2, "created second ticket $id2" ); my $tickets = RT::Tickets->new($RT::SystemUser); $tickets->FromSQL("id = $id1 OR id = $id2"); is( $tickets->Count, 2, "found both tickets by 'id = x OR y'" ); # IP $tickets = RT::Tickets->new($RT::SystemUser); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'abcd::'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), 'abcd::', "correct value" ); $tickets = RT::Tickets->new($RT::SystemUser); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} = 'bbcd::'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), 'bbcd::', "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} <= 'abcd::'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), 'abcd::', "correct value" ); $tickets = RT::Tickets->new($RT::SystemUser); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} >= 'bbcd::'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), 'bbcd::', "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} > 'bbcd::'"); is( $tickets->Count, 0, "no tickets found" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} < 'abcd::'"); is( $tickets->Count, 0, "no tickets found" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} < 'bbcd::'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), 'abcd::', "correct value" ); $tickets->FromSQL("(id = $id1 OR id = $id2) AND CF.{IP} > 'abcd::'"); is( $tickets->Count, 1, "found one ticket" ); is( $tickets->First->FirstCustomFieldValue('IP'), 'bbcd::', "correct value" ); } diag "create a ticket with an IP of abcd:23:: and search for doesn't match 'abcd:23'." if $ENV{'TEST_VERBOSE'}; { ok $agent->goto_create_ticket($q), "go to create ticket"; my $cf_field = "Object-RT::Ticket--CustomField-$cf_id-Values"; $agent->submit_form( form_name => 'TicketCreate', fields => { Subject => 'local', $cf_field => 'abcd:23::', }, button => 'SubmitTicket', ); my ($id) = $agent->content =~ /Ticket (\d+) created/; ok( $id, "created first ticket $id" ); my $tickets = RT::Tickets->new($RT::SystemUser); warning_like { $tickets->FromSQL("id=$id AND CF.{IP} NOT LIKE 'abcd:23'"); } [qr/not a valid IPAddress/], "caught warning about IPAddress"; TODO: { local $TODO = "partial ip parse can causes ambiguity"; is( $tickets->Count, 0, "should not have found the ticket" ); } } done_testing; rt-5.0.1/t/ldapimport/group-callbacks.t000644 000765 000024 00000006220 14005011336 020655 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; eval { require RT::LDAPImport; require Net::LDAP::Server::Test; 1; } or do { plan skip_all => 'Unable to test without RT::LDAPImport and Net::LDAP::Server::Test'; }; my $importer = RT::LDAPImport->new; isa_ok($importer,'RT::LDAPImport'); my $ldap_port = RT::Test->find_idle_port; ok( my $server = Net::LDAP::Server::Test->new( $ldap_port, auto_schema => 1 ), "spawned test LDAP server on port $ldap_port"); my $ldap = Net::LDAP->new("localhost:$ldap_port"); $ldap->bind(); $ldap->add("dc=bestpractical,dc=com"); my @ldap_user_entries; for ( 1 .. 12 ) { my $username = "testuser$_"; my $dn = "uid=$username,ou=foo,dc=bestpractical,dc=com"; my $entry = { dn => $dn, cn => "Test User $_", mail => "$username\@invalid.tld", uid => $username, objectClass => 'User', }; push @ldap_user_entries, $entry; $ldap->add( $dn, attr => [%$entry] ); } my @ldap_group_entries; for ( 1 .. 4 ) { my $groupname = "Test Group $_"; my $dn = "cn=$groupname,ou=groups,dc=bestpractical,dc=com"; my $entry = { cn => $groupname, gid => $_, members => [ map { 'mail="'. $_->{'mail'} .'"' } @ldap_user_entries[($_-1),($_+3),($_+7)] ], objectClass => 'Group', }; $ldap->add( $dn, attr => [%$entry] ); push @ldap_group_entries, $entry; } RT->Config->Set('LDAPHost',"ldap://localhost:$ldap_port"); RT->Config->Set('LDAPMapping', {Name => 'uid', EmailAddress => 'mail', RealName => 'cn'}); RT->Config->Set('LDAPBase','dc=bestpractical,dc=com'); RT->Config->Set('LDAPFilter','(objectClass=User)'); RT->Config->Set('LDAPSkipAutogeneratedGroup',1); RT->Config->Set('LDAPGroupBase','dc=bestpractical,dc=com'); RT->Config->Set('LDAPGroupFilter','(objectClass=Group)'); RT->Config->Set('LDAPGroupMapping', { Name => 'cn', Member_Attr => sub { my %args = @_; my $self = $args{'self'}; my $members = $args{ldap_entry}->get_value('members', asref => 1); foreach my $record ( @$members ) { my $user = RT::User->new( RT->SystemUser ); $user->LoadByEmail($record =~ /mail="(.*)"/); $self->_users->{ lc $record } = $user->Name; } return @$members; }, }); ok( $importer->import_users( import => 1 ), 'imported users'); # no id mapping { ok( $importer->import_groups( import => 1 ), "imported groups" ); is_member_of('testuser1', 'Test Group 1'); } done_testing; sub is_member_of { my $uname = shift; my $gname = shift; my $group = get_group($gname); return ok(0, "found group $gname") unless $group->id; my $user = RT::User->new($RT::SystemUser); $user->Load( $uname ); return ok(0, "found user $uname") unless $user->id; return ok($group->HasMember($user->id), "$uname is member of $gname"); } sub get_group { my $gname = shift; my $group = RT::Group->new($RT::SystemUser); $group->LoadUserDefinedGroup( $gname ); return $group; } rt-5.0.1/t/ldapimport/user-import-cfs.t000644 000765 000024 00000006775 14005011336 020662 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; eval { require RT::LDAPImport; require Net::LDAP::Server::Test; 1; } or do { plan skip_all => 'Unable to test without RT::LDAPImport and Net::LDAP::Server::Test'; }; { my $cf = RT::CustomField->new(RT->SystemUser); my ($ok, $msg) = $cf->Create( Name => 'Employee Number', LookupType => 'RT::User', Type => 'FreeformSingle', Disabled => 0, ); ok $cf->Id, $msg; my $ocf = RT::ObjectCustomField->new(RT->SystemUser); ($ok, $msg) = $ocf->Create( CustomField => $cf->Id ); ok $ocf->Id, $msg; } my $importer = RT::LDAPImport->new; isa_ok($importer,'RT::LDAPImport'); my $ldap_port = RT::Test->find_idle_port; ok( my $server = Net::LDAP::Server::Test->new( $ldap_port, auto_schema => 1 ), "spawned test LDAP server on port $ldap_port"); my $ldap = Net::LDAP->new("localhost:$ldap_port"); $ldap->bind(); $ldap->add("ou=foo,dc=bestpractical,dc=com"); my @ldap_entries; for ( 0 .. 12 ) { my $username = "testuser$_"; my $dn = "uid=$username,ou=foo,dc=bestpractical,dc=com"; my $entry = { cn => "Test User $_ ".int rand(200), mail => "$username\@invalid.tld", uid => $username, employeeId => $_, objectClass => 'User', }; push @ldap_entries, { dn => $dn, %$entry }; $ldap->add( $dn, attr => [%$entry] ); } RT->Config->Set('LDAPHost',"ldap://localhost:$ldap_port"); RT->Config->Set('LDAPMapping', {Name => 'uid', EmailAddress => 'mail', RealName => 'cn', 'UserCF.Employee Number' => 'employeeId',}); RT->Config->Set('LDAPBase','ou=foo,dc=bestpractical,dc=com'); RT->Config->Set('LDAPFilter','(objectClass=User)'); # check that we don't import ok($importer->import_users()); { my $users = RT::Users->new($RT::SystemUser); for my $username (qw/RT_System root Nobody/) { $users->Limit( FIELD => 'Name', OPERATOR => '!=', VALUE => $username, ENTRYAGGREGATOR => 'AND' ); } is($users->Count,0); } # check that we do import ok($importer->import_users( import => 1 )); for my $entry (@ldap_entries) { my $user = RT::User->new($RT::SystemUser); $user->LoadByCols( EmailAddress => $entry->{mail}, Realname => $entry->{cn}, Name => $entry->{uid} ); ok($user->Id, "Found $entry->{cn} as ".$user->Id); ok(!$user->Privileged, "User created as Unprivileged"); is($user->FirstCustomFieldValue('Employee Number'), $entry->{employeeId}, "cf is good: $entry->{employeeId}"); } # import again, check that it was cleared { my $delete = $ldap_entries[0]; $ldap->modify( $delete->{dn}, delete => ['employeeId'] ); delete $delete->{employeeId}; my $update = $ldap_entries[1]; $ldap->modify( $update->{dn}, replace => ['employeeId' => 42] ); $update->{employeeId} = 42; ok($importer->import_users( import => 1 )); for my $entry (@ldap_entries[0,1]) { my $user = RT::User->new($RT::SystemUser); $user->LoadByCols( EmailAddress => $entry->{mail}, Realname => $entry->{cn}, Name => $entry->{uid} ); ok($user->Id, "Found $entry->{cn} as ".$user->Id); is($user->FirstCustomFieldValue('Employee Number'), $entry->{employeeId}, "cf is updated"); } } # can't unbind earlier or the server will die $ldap->unbind; done_testing; rt-5.0.1/t/ldapimport/group-rename.t000644 000765 000024 00000010325 14005011336 020206 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; eval { require RT::LDAPImport; require Net::LDAP::Server::Test; 1; } or do { plan skip_all => 'Unable to test without RT::LDAPImport and Net::LDAP::Server::Test'; }; my $importer = RT::LDAPImport->new; isa_ok($importer,'RT::LDAPImport'); my $ldap_port = RT::Test->find_idle_port; ok( my $server = Net::LDAP::Server::Test->new( $ldap_port, auto_schema => 1 ), "spawned test LDAP server on port $ldap_port"); my $ldap = Net::LDAP->new("localhost:$ldap_port"); $ldap->bind(); $ldap->add("dc=bestpractical,dc=com"); my @ldap_user_entries; for ( 1 .. 12 ) { my $username = "testuser$_"; my $dn = "uid=$username,ou=foo,dc=bestpractical,dc=com"; my $entry = { dn => $dn, cn => "Test User $_", mail => "$username\@invalid.tld", uid => $username, objectClass => 'User', }; push @ldap_user_entries, $entry; $ldap->add( $dn, attr => [%$entry] ); } my @ldap_group_entries; for ( 1 .. 4 ) { my $groupname = "Test Group $_"; my $dn = "cn=$groupname,ou=groups,dc=bestpractical,dc=com"; my $entry = { cn => $groupname, gid => $_, members => [ map { $_->{dn} } @ldap_user_entries[($_-1),($_+3),($_+7)] ], objectClass => 'Group', }; $ldap->add( $dn, attr => [%$entry] ); push @ldap_group_entries, $entry; } RT->Config->Set('LDAPHost',"ldap://localhost:$ldap_port"); RT->Config->Set('LDAPMapping', {Name => 'uid', EmailAddress => 'mail', RealName => 'cn'}); RT->Config->Set('LDAPBase','dc=bestpractical,dc=com'); RT->Config->Set('LDAPFilter','(objectClass=User)'); RT->Config->Set('LDAPSkipAutogeneratedGroup',1); RT->Config->Set('LDAPGroupBase','dc=bestpractical,dc=com'); RT->Config->Set('LDAPGroupFilter','(objectClass=Group)'); RT->Config->Set('LDAPGroupMapping', { Name => 'cn', Member_Attr => 'members', }); ok( $importer->import_users( import => 1 ), 'imported users'); # no id mapping { ok( $importer->import_groups( import => 1 ), "imported groups" ); is_member_of('testuser1', 'Test Group 1'); ok !get_group('Test Group 1')->FirstAttribute('LDAPImport-gid-1'); } # map id { RT->Config->Get('LDAPGroupMapping')->{'id'} = 'gid'; ok( $importer->import_groups( import => 1 ), "imported groups" ); is_member_of('testuser1', 'Test Group 1'); ok get_group('Test Group 1')->FirstAttribute('LDAPImport-gid-1'); } # rename a group { $ldap->modify( "cn=Test Group 1,ou=groups,dc=bestpractical,dc=com", replace => { 'cn' => 'Test Group 1 Renamed' }, ); ok( $importer->import_groups( import => 1 ), "imported groups" ); ok !get_group('Test Group 1')->id; is_member_of('testuser1', 'Test Group 1 Renamed'); ok get_group('Test Group 1 Renamed')->FirstAttribute('LDAPImport-gid-1'); } # swap two groups { is_member_of('testuser2', 'Test Group 2'); is_member_of('testuser3', 'Test Group 3'); $ldap->modify( "cn=Test Group 2,ou=groups,dc=bestpractical,dc=com", replace => { 'cn' => 'Test Group 3' }, ); $ldap->modify( "cn=Test Group 3,ou=groups,dc=bestpractical,dc=com", replace => { 'cn' => 'Test Group 2' }, ); ok( $importer->import_groups( import => 1 ), "imported groups" ); is_member_of('testuser2', 'Test Group 3'); is_member_of('testuser3', 'Test Group 2'); ok get_group('Test Group 2')->FirstAttribute('LDAPImport-gid-3'); ok get_group('Test Group 3')->FirstAttribute('LDAPImport-gid-2'); } done_testing; sub is_member_of { my $uname = shift; my $gname = shift; my $group = get_group($gname); return ok(0, "found group $gname") unless $group->id; my $user = RT::User->new($RT::SystemUser); $user->Load( $uname ); return ok(0, "found user $uname") unless $user->id; return ok($group->HasMember($user->id), "$uname is member of $gname"); } sub get_group { my $gname = shift; my $group = RT::Group->new($RT::SystemUser); $group->LoadUserDefinedGroup( $gname ); return $group; } rt-5.0.1/t/ldapimport/group-import.t000644 000765 000024 00000011674 14005011336 020261 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; eval { require RT::LDAPImport; require Net::LDAP::Server::Test; 1; } or do { plan skip_all => 'Unable to test without RT::LDAPImport and Net::LDAP::Server::Test'; }; my $importer = RT::LDAPImport->new; isa_ok($importer,'RT::LDAPImport'); my $ldap_port = RT::Test->find_idle_port; ok( my $server = Net::LDAP::Server::Test->new( $ldap_port, auto_schema => 1 ), "spawned test LDAP server on port $ldap_port"); my $ldap = Net::LDAP->new("localhost:$ldap_port"); $ldap->bind(); $ldap->add("dc=bestpractical,dc=com"); my @ldap_user_entries; for ( 1 .. 12 ) { my $username = "testuser$_"; my $dn = "uid=$username,ou=foo,dc=bestpractical,dc=com"; my $entry = { dn => $dn, cn => "Test User $_ ".int rand(200), mail => "$username\@invalid.tld", uid => $username, objectClass => 'User', }; push @ldap_user_entries, $entry; $ldap->add( $dn, attr => [%$entry] ); } my @ldap_group_entries; for ( 1 .. 4 ) { my $groupname = "Test Group $_"; my $dn = "cn=$groupname,ou=groups,dc=bestpractical,dc=com"; my $entry = { cn => $groupname, members => [ map { $_->{dn} } @ldap_user_entries[($_-1),($_+3),($_+7)] ], memberUid => [ map { $_->{uid} } @ldap_user_entries[($_+1),($_+3),($_+5)] ], objectClass => 'Group', }; $ldap->add( $dn, attr => [%$entry] ); push @ldap_group_entries, $entry; } $ldap->add( "cn=42,ou=groups,dc=bestpractical,dc=com", attr => [ cn => "42", members => [ "uid=testuser1,ou=foo,dc=bestpractical,dc=com" ], objectClass => 'Group', ], ); RT->Config->Set('LDAPHost',"ldap://localhost:$ldap_port"); RT->Config->Set('LDAPMapping', {Name => 'uid', EmailAddress => 'mail', RealName => 'cn'}); RT->Config->Set('LDAPBase','dc=bestpractical,dc=com'); RT->Config->Set('LDAPFilter','(objectClass=User)'); RT->Config->Set('LDAPSkipAutogeneratedGroup',1); ok($importer->import_users( import => 1 )); for my $entry (@ldap_user_entries) { my $user = RT::User->new($RT::SystemUser); $user->LoadByCols( EmailAddress => $entry->{mail}, Realname => $entry->{cn}, Name => $entry->{uid} ); ok($user->Id, "Found $entry->{cn} as ".$user->Id); } RT->Config->Set('LDAPGroupBase','dc=bestpractical,dc=com'); RT->Config->Set('LDAPGroupFilter','(objectClass=Group)'); RT->Config->Set('LDAPGroupMapping', {Name => 'cn', Member_Attr => 'members', }); # confirm that we skip the import ok( $importer->import_groups() ); { my $groups = RT::Groups->new($RT::SystemUser); $groups->LimitToUserDefinedGroups; is($groups->Count,0); } import_group_members_ok( members => 'dn' ); RT->Config->Set('LDAPGroupMapping', {Name => 'cn', Member_Attr => 'memberUid', Member_Attr_Value => 'uid', }); import_group_members_ok( memberUid => 'uid' ); { my $uid = $ldap_user_entries[2]->{uid}; # the first user used for memberUid my $user = RT::User->new($RT::SystemUser); my ($ok, $msg) = $user->Load($uid); ok $ok, "Loaded user #$uid" or diag $msg; ($ok, $msg) = $user->SetDisabled(1); ok $ok, "Disabled user #$uid" or diag $msg; } import_group_members_ok( memberUid => 'uid' ); sub import_group_members_ok { my $attr = shift; my $user_attr = shift; ok( $importer->import_groups( import => 1 ), "imported groups" ); for my $entry (@ldap_group_entries) { my $group = RT::Group->new($RT::SystemUser); $group->LoadUserDefinedGroup( $entry->{cn} ); ok($group->Id, "Found $entry->{cn} as ".$group->Id); my $idlist; my $members = $group->MembersObj; while (my $group_member = $members->Next) { my $member = $group_member->MemberObj; next unless $member->IsUser(); $idlist->{$member->Object->Id}++; } foreach my $member ( @{$entry->{$attr}} ) { my ($user) = grep { $_->{$user_attr} eq $member } @ldap_user_entries; my $rt_user = RT::User->new($RT::SystemUser); my ($res,$msg) = $rt_user->Load($user->{uid}); unless ($res) { diag("Couldn't load user $user->{uid}: $msg"); next; } ok($group->HasMember($rt_user->PrincipalObj->Id),"Correctly assigned $user->{uid} to $entry->{cn}"); delete $idlist->{$rt_user->Id}; } is(keys %$idlist,0,"No dangling users"); } my $group = RT::Group->new($RT::SystemUser); $group->LoadUserDefinedGroup( "42" ); ok( !$group->Id ); $group->LoadByCols( Domain => 'UserDefined', Name => "42", ); ok( !$group->Id ); } done_testing; rt-5.0.1/t/ldapimport/user-import-privileged.t000644 000765 000024 00000004232 14005011336 022223 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; eval { require RT::LDAPImport; require Net::LDAP::Server::Test; 1; } or do { plan skip_all => 'Unable to test without RT::LDAPImport and Net::LDAP::Server::Test'; }; my $importer = RT::LDAPImport->new; isa_ok($importer,'RT::LDAPImport'); my $ldap_port = RT::Test->find_idle_port; ok( my $server = Net::LDAP::Server::Test->new( $ldap_port, auto_schema => 1 ), "spawned test LDAP server on port $ldap_port"); my $ldap = Net::LDAP->new("localhost:$ldap_port"); $ldap->bind(); $ldap->add("ou=foo,dc=bestpractical,dc=com"); my @ldap_entries; for ( 1 .. 13 ) { my $username = "testuser$_"; my $dn = "uid=$username,ou=foo,dc=bestpractical,dc=com"; my $entry = { cn => "Test User $_ ".int rand(200), mail => "$username\@invalid.tld", uid => $username, objectClass => 'User', }; push @ldap_entries, $entry; $ldap->add( $dn, attr => [%$entry] ); } RT->Config->Set('LDAPHost',"ldap://localhost:$ldap_port"); RT->Config->Set('LDAPMapping', {Name => 'uid', EmailAddress => 'mail', RealName => 'cn'}); RT->Config->Set('LDAPBase','ou=foo,dc=bestpractical,dc=com'); RT->Config->Set('LDAPFilter','(objectClass=User)'); RT->Config->Set('LDAPCreatePrivileged', 1); # check that we don't import ok($importer->import_users()); { my $users = RT::Users->new($RT::SystemUser); for my $username (qw/RT_System root Nobody/) { $users->Limit( FIELD => 'Name', OPERATOR => '!=', VALUE => $username, ENTRYAGGREGATOR => 'AND' ); } is($users->Count,0); } # check that we do import ok($importer->import_users( import => 1 )); for my $entry (@ldap_entries) { my $user = RT::User->new($RT::SystemUser); $user->LoadByCols( EmailAddress => $entry->{mail}, Realname => $entry->{cn}, Name => $entry->{uid} ); ok($user->Id, "Found $entry->{cn} as ".$user->Id); ok($user->Privileged, "User created as Privileged"); } # can't unbind earlier or the server will die $ldap->unbind; done_testing; rt-5.0.1/t/ldapimport/user-import.t000644 000765 000024 00000005164 14005011336 020100 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; eval { require RT::LDAPImport; require Net::LDAP::Server::Test; 1; } or do { plan skip_all => 'Unable to test without RT::LDAPImport and Net::LDAP::Server::Test'; }; my $importer = RT::LDAPImport->new; isa_ok($importer,'RT::LDAPImport'); my $ldap_port = RT::Test->find_idle_port; ok( my $server = Net::LDAP::Server::Test->new( $ldap_port, auto_schema => 1 ), "spawned test LDAP server on port $ldap_port"); my $ldap = Net::LDAP->new("localhost:$ldap_port"); $ldap->bind(); $ldap->add("ou=foo,dc=bestpractical,dc=com"); my @ldap_entries; for ( 1 .. 13 ) { my $username = "testuser$_"; my $dn = "uid=$username,ou=foo,dc=bestpractical,dc=com"; my $entry = { cn => "Test User $_ ".int rand(200), mail => "$username\@invalid.tld", uid => $username, objectClass => 'User', }; push @ldap_entries, $entry; $ldap->add( $dn, attr => [%$entry] ); } $ldap->add( "uid=9000,ou=foo,dc=bestpractical,dc=com", attr => [ cn => "Numeric user", mail => "numeric\@invalid.tld", uid => 9000, objectclass => 'User', ], ); RT->Config->Set('LDAPHost',"ldap://localhost:$ldap_port"); RT->Config->Set('LDAPOptions', [ port => $ldap_port ]); RT->Config->Set('LDAPMapping', {Name => 'uid', EmailAddress => 'mail', RealName => 'cn'}); RT->Config->Set('LDAPBase','ou=foo,dc=bestpractical,dc=com'); RT->Config->Set('LDAPFilter','(objectClass=User)'); # check that we don't import ok($importer->import_users()); { my $users = RT::Users->new($RT::SystemUser); for my $username (qw/RT_System root Nobody/) { $users->Limit( FIELD => 'Name', OPERATOR => '!=', VALUE => $username, ENTRYAGGREGATOR => 'AND' ); } is($users->Count,0); } # check that we do import ok($importer->import_users( import => 1 )); for my $entry (@ldap_entries) { my $user = RT::User->new($RT::SystemUser); $user->LoadByCols( EmailAddress => $entry->{mail}, Realname => $entry->{cn}, Name => $entry->{uid} ); ok($user->Id, "Found $entry->{cn} as ".$user->Id); ok(!$user->Privileged, "User created as Unprivileged"); } # Check that we skipped numeric usernames my $user = RT::User->new($RT::SystemUser); $user->LoadByCols( EmailAddress => "numeric\@invalid.tld" ); ok(!$user->Id); $user->LoadByCols( Name => 9000 ); ok(!$user->Id); $user->Load( 9000 ); ok(!$user->Id); # can't unbind earlier or the server will die $ldap->unbind; done_testing; rt-5.0.1/t/ldapimport/group-member-import.t000644 000765 000024 00000011165 14005011336 021521 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; eval { require RT::LDAPImport; require Net::LDAP::Server::Test; 1; } or do { plan skip_all => 'Unable to test without RT::LDAPImport and Net::LDAP::Server::Test'; }; my $importer = RT::LDAPImport->new; isa_ok($importer,'RT::LDAPImport'); my $ldap_port = RT::Test->find_idle_port; ok( my $server = Net::LDAP::Server::Test->new( $ldap_port, auto_schema => 1 ), "spawned test LDAP server on port $ldap_port"); my $ldap = Net::LDAP->new("localhost:$ldap_port"); $ldap->bind(); $ldap->add("dc=bestpractical,dc=com"); my @ldap_user_entries; for ( 1 .. 12 ) { my $username = "testuser$_"; my $dn = "uid=$username,ou=foo,dc=bestpractical,dc=com"; my $entry = { dn => $dn, cn => "Test User $_ ".int rand(200), mail => "$username\@invalid.tld", uid => $username, objectClass => 'User', }; push @ldap_user_entries, $entry; $ldap->add( $dn, attr => [%$entry] ); } my @ldap_group_entries; for ( 1 .. 4 ) { my $groupname = "Test Group $_"; my $dn = "cn=$groupname,ou=groups,dc=bestpractical,dc=com"; my $entry = { cn => $groupname, members => [ map { $_->{dn} } @ldap_user_entries[($_-1),($_+3),($_+7)] ], memberUid => [ map { $_->{uid} } @ldap_user_entries[($_+1),($_+3),($_+5)] ], objectClass => 'Group', }; $ldap->add( $dn, attr => [%$entry] ); push @ldap_group_entries, $entry; } $ldap->add( "cn=42,ou=groups,dc=bestpractical,dc=com", attr => [ cn => "42", members => [ "uid=testuser1,ou=foo,dc=bestpractical,dc=com" ], objectClass => 'Group', ], ); RT->Config->Set('LDAPHost',"ldap://localhost:$ldap_port"); RT->Config->Set('LDAPMapping', {Name => 'uid', EmailAddress => 'mail', RealName => 'cn'}); RT->Config->Set('LDAPBase','dc=bestpractical,dc=com'); RT->Config->Set('LDAPFilter','(objectClass=User)'); RT->Config->Set('LDAPSkipAutogeneratedGroup',1); RT->Config->Set('LDAPGroupBase','dc=bestpractical,dc=com'); RT->Config->Set('LDAPGroupFilter','(objectClass=Group)'); RT->Config->Set('LDAPGroupMapping', {Name => 'cn', Member_Attr => 'members', }); RT->Config->Set('LDAPImportGroupMembers',1); # confirm that we skip the import ok( $importer->import_groups() ); { my $groups = RT::Groups->new($RT::SystemUser); $groups->LimitToUserDefinedGroups; is($groups->Count,0); } import_group_members_ok( members => 'dn' ); RT->Config->Set('LDAPGroupMapping', {Name => 'cn', Member_Attr => 'memberUid', Member_Attr_Value => 'uid', }); import_group_members_ok( memberUid => 'uid' ); sub import_group_members_ok { my $attr = shift; my $user_attr = shift; ok( $importer->import_groups( import => 1 ), "imported groups" ); for my $entry (@ldap_user_entries) { my $user = RT::User->new($RT::SystemUser); $user->LoadByCols( EmailAddress => $entry->{mail}, Realname => $entry->{cn}, Name => $entry->{uid} ); ok($user->Id, "Found $entry->{cn} as ".$user->Id); } for my $entry (@ldap_group_entries) { my $group = RT::Group->new($RT::SystemUser); $group->LoadUserDefinedGroup( $entry->{cn} ); ok($group->Id, "Found $entry->{cn} as ".$group->Id); my $idlist; my $members = $group->MembersObj; while (my $group_member = $members->Next) { my $member = $group_member->MemberObj; next unless $member->IsUser(); $idlist->{$member->Object->Id}++; } foreach my $member ( @{$entry->{$attr}} ) { my ($user) = grep { $_->{$user_attr} eq $member } @ldap_user_entries; my $rt_user = RT::User->new($RT::SystemUser); my ($res,$msg) = $rt_user->Load($user->{uid}); unless ($res) { diag("Couldn't load user $user->{uid}: $msg"); next; } ok($group->HasMember($rt_user->PrincipalObj->Id),"Correctly assigned $user->{uid} to $entry->{cn}"); delete $idlist->{$rt_user->Id}; } is(keys %$idlist,0,"No dangling users"); } my $group = RT::Group->new($RT::SystemUser); $group->LoadUserDefinedGroup( "42" ); ok( !$group->Id ); $group->LoadByCols( Domain => 'UserDefined', Name => "42", ); ok( !$group->Id ); } done_testing; rt-5.0.1/t/shredder/03plugin_summary.t000644 000765 000024 00000000560 14005011336 020450 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::Shredder nodb => 1, tests => 4; use_ok('RT::Shredder::Plugin'); my $plugin_obj = RT::Shredder::Plugin->new; isa_ok($plugin_obj, 'RT::Shredder::Plugin'); my ($status, $msg) = $plugin_obj->LoadByName('Summary'); ok($status, 'loaded summary plugin') or diag "error: $msg"; isa_ok($plugin_obj, 'RT::Shredder::Plugin::Summary'); rt-5.0.1/t/shredder/02cfs.t000644 000765 000024 00000004173 14005011336 016153 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::Deep; use File::Spec; use RT::Test::Shredder tests => undef; my $test = "RT::Test::Shredder"; $test->create_savepoint('clean'); my $cf = RT::Test->load_or_create_custom_field( Name => "Favorite Color", LookupType => "RT::Queue-RT::Ticket", Type => "FreeformSingle", ); ok $cf->id, "Created ticket CF"; $test->create_savepoint('clean_with_cf'); diag 'global ticket custom field'; { my $global_queue = RT::Queue->new(RT->SystemUser); my ($ok, $msg) = $cf->AddToObject($global_queue); ok $ok, "Added ticket CF globally: $msg"; my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $cf ); $shredder->WipeoutAll; cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); } diag 'queue-specific ticket custom field'; { $test->restore_savepoint('clean_with_cf'); my $general = RT::Test->load_or_create_queue( Name => 'General' ); my ($ok, $msg) = $cf->AddToObject($general); ok $ok, "Added ticket CF to General queue: $msg"; my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $cf ); $shredder->WipeoutAll; cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); } diag 'global ticket custom field + ticket'; { $test->restore_savepoint('clean'); my $ticket = RT::Test->create_ticket( Queue => 'General', Subject => 'test ticket' ); $test->create_savepoint('clean_with_ticket'); my $cf = RT::Test->load_or_create_custom_field( Name => "Favorite Color", LookupType => "RT::Queue-RT::Ticket", Type => "FreeformSingle", ); my $global_queue = RT::Queue->new(RT->SystemUser); my ($ok, $msg) = $cf->AddToObject($global_queue); ok $ok, "Added ticket CF globally: $msg"; $ticket->AddCustomFieldValue( Field => $cf->Name, Value => 'foo' ); my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $cf ); $shredder->WipeoutAll; cmp_deeply( $test->dump_current_and_savepoint('clean_with_ticket'), "current DB equal to savepoint"); } done_testing; rt-5.0.1/t/shredder/00skeleton.t000644 000765 000024 00000000555 14005011336 017222 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::Deep; use RT::Test::Shredder tests => 1; my $test = "RT::Test::Shredder"; $test->create_savepoint('clean'); # backup of the clean RT DB my $shredder = $test->shredder_new(); # new shredder object # .... # create and wipe RT objects # cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); rt-5.0.1/t/shredder/03plugin_users.t000644 000765 000024 00000012140 14005011336 020111 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::Deep; use RT::Test::Shredder tests => undef; my $test = "RT::Test::Shredder"; my @ARGS = sort qw(limit status name member_of not_member_of email replace_relations no_tickets no_ticket_transactions); use_ok('RT::Shredder::Plugin::Users'); { my $plugin = RT::Shredder::Plugin::Users->new; isa_ok($plugin, 'RT::Shredder::Plugin::Users'); is(lc $plugin->Type, 'search', 'correct type'); my @args = sort $plugin->SupportArgs; cmp_deeply(\@args, \@ARGS, "support all args"); my ($status, $msg) = $plugin->TestArgs( name => 'r??t*' ); ok($status, "arg name = 'r??t*'") or diag("error: $msg"); for (qw(any disabled enabled)) { my ($status, $msg) = $plugin->TestArgs( status => $_ ); ok($status, "arg status = '$_'") or diag("error: $msg"); } ($status, $msg) = $plugin->TestArgs( status => '!@#' ); ok(!$status, "bad 'status' arg value"); } RT::Test->set_rights( { Principal => 'Everyone', Right => [qw(CreateTicket)] }, ); $test->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"; $ticket->ApplyTransactionBatch; 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 $shredder = $test->shredder_new(); 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"); ($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( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); { # Same as previous test, but pass Objects to PutObjects in the same form as the web interface 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"; $ticket->ApplyTransactionBatch; 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 $shredder = $test->shredder_new(); my @objs; ($status, @objs) = $plugin->Run; ok($status, "executed plugin successfully") or diag "error: @objs"; # Same form as param coming in via the web interface $shredder->PutObjects( Objects => ['RT::User-userB'] ); ($status, $msg) = $plugin->SetResolvers( Shredder => $shredder ); ok($status, "set conflicts resolver") or diag "error: $msg"; $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( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); done_testing(); rt-5.0.1/t/shredder/01basics.t000644 000765 000024 00000001161 14005011336 016635 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::Deep; use RT::Test::Shredder tests => 4; my $test = "RT::Test::Shredder"; $test->create_savepoint(); use RT::Tickets; my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id) = $ticket->Create( Subject => 'test', Queue => 1 ); ok( $id, "created new ticket" ); $ticket = RT::Ticket->new( RT->SystemUser ); my ($status, $msg) = $ticket->Load( $id ); ok( $id, "load ticket" ) or diag( "error: $msg" ); my $shredder = $test->shredder_new(); $shredder->Wipeout( Object => $ticket ); $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint(), "current DB equal to savepoint"); rt-5.0.1/t/shredder/00load.t000644 000765 000024 00000001024 14005011336 016305 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodb => 1, tests => 11; use_ok("RT::Shredder"); use_ok("RT::Shredder::Plugin"); use_ok("RT::Shredder::Plugin::Base"); # search plugins use_ok("RT::Shredder::Plugin::Base::Search"); use_ok("RT::Shredder::Plugin::Objects"); use_ok("RT::Shredder::Plugin::Attachments"); use_ok("RT::Shredder::Plugin::Tickets"); use_ok("RT::Shredder::Plugin::Users"); # dump plugins use_ok("RT::Shredder::Plugin::Base::Dump"); use_ok("RT::Shredder::Plugin::SQLDump"); use_ok("RT::Shredder::Plugin::Summary"); rt-5.0.1/t/shredder/02queue.t000644 000765 000024 00000013575 14005011336 016532 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::Deep; use RT::Test::Shredder tests => undef; my $test = "RT::Test::Shredder"; diag 'simple queue' if $ENV{TEST_VERBOSE}; { $test->create_savepoint('clean'); my $queue = RT::Queue->new( RT->SystemUser ); my ($id, $msg) = $queue->Create( Name => 'my queue' ); ok($id, 'created queue') or diag "error: $msg"; my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $queue ); $shredder->WipeoutAll; $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); } diag 'queue with scrip' if $ENV{TEST_VERBOSE}; { my $scrip = RT::Scrip->new( RT->SystemUser ); my ($id, $msg) = $scrip->Create( Description => 'my scrip', ScripCondition => 'On Create', ScripAction => 'Open Tickets', Template => 'Blank', ); ok($id, 'created scrip') or diag "error: $msg"; ok( $scrip->RemoveFromObject(0), 'unapplied scrip from global' ); # Commit 7d5502ffe makes Scrips not be deleted when a queue is shredded. # we need to create the savepoint before applying the scrip so we can test # to make sure it's remaining after shredding the queue. $test->create_savepoint('clean'); my $queue = RT::Queue->new( RT->SystemUser ); ($id, $msg) = $queue->Create( Name => 'my queue' ); ok($id, 'created queue') or diag "error: $msg"; # apply the scrip to the queue. ($id, $msg) = $scrip->AddToObject( ObjectId => $queue->id ); ok($id, 'applied scrip to queue') or diag "error: $msg"; my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $queue ); $shredder->WipeoutAll; $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); } diag 'queue with template' if $ENV{TEST_VERBOSE}; { $test->create_savepoint('clean'); my $queue = RT::Queue->new( RT->SystemUser ); my ($id, $msg) = $queue->Create( Name => 'my queue' ); ok($id, 'created queue') or diag "error: $msg"; my $template = RT::Template->new( RT->SystemUser ); ($id, $msg) = $template->Create( Name => 'my template', Queue => $queue->id, Content => "\nsome content", ); ok($id, 'created template') or diag "error: $msg"; my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $queue ); $shredder->WipeoutAll; $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); } diag 'queue with a right granted' if $ENV{TEST_VERBOSE}; { $test->create_savepoint('clean'); my $queue = RT::Queue->new( RT->SystemUser ); my ($id, $msg) = $queue->Create( Name => 'my queue' ); ok($id, 'created queue') or diag "error: $msg"; my $group = RT::Group->new( RT->SystemUser ); $group->LoadSystemInternalGroup('Everyone'); ok($group->id, 'loaded group'); ($id, $msg) = $group->PrincipalObj->GrantRight( Right => 'CreateTicket', Object => $queue, ); ok($id, 'granted right') or diag "error: $msg"; my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $queue ); $shredder->WipeoutAll; $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); } diag 'queue with a watcher' if $ENV{TEST_VERBOSE}; { # XXX, FIXME: if uncomment these lines then we'll get 'Bizarre...' # $test->create_savepoint('clean'); my $group = RT::Group->new( RT->SystemUser ); my ($id, $msg) = $group->CreateUserDefinedGroup(Name => 'my group'); ok($id, 'created group') or diag "error: $msg"; $test->create_savepoint('bqcreate'); my $queue = RT::Queue->new( RT->SystemUser ); ($id, $msg) = $queue->Create( Name => 'my queue' ); ok($id, 'created queue') or diag "error: $msg"; ($id, $msg) = $queue->AddWatcher( Type => 'Cc', PrincipalId => $group->id, ); ok($id, 'added watcher') or diag "error: $msg"; my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $queue ); $shredder->WipeoutAll; $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint('bqcreate'), "current DB equal to savepoint"); # $shredder->PutObjects( Objects => $group ); # $shredder->WipeoutAll; # cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); } diag 'queue with custom fields' if $ENV{TEST_VERBOSE}; { my $ticket_custom_field = RT::CustomField->new( RT->SystemUser ); my ($id, $msg) = $ticket_custom_field->Create( Name => 'ticket custom field', Type => 'Freeform', LookupType => RT::Ticket->CustomFieldLookupType, MaxValues => '1', ); ok($id, 'created ticket custom field') or diag "error: $msg"; my $transaction_custom_field = RT::CustomField->new( RT->SystemUser ); ($id, $msg) = $transaction_custom_field->Create( Name => 'transaction custom field', Type => 'Freeform', LookupType => RT::Transaction->CustomFieldLookupType, MaxValues => '1', ); ok($id, 'created transaction custom field') or diag "error: $msg"; $test->create_savepoint('clean'); my $queue = RT::Queue->new( RT->SystemUser ); ($id, $msg) = $queue->Create( Name => 'my queue' ); ok($id, 'created queue') or diag "error: $msg"; # apply the custom fields to the queue. ($id, $msg) = $ticket_custom_field->AddToObject( $queue ); ok($id, 'applied ticket cf to queue') or diag "error: $msg"; ($id, $msg) = $transaction_custom_field->AddToObject( $queue ); ok($id, 'applied txn cf to queue') or diag "error: $msg"; my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $queue ); $shredder->WipeoutAll; $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); } done_testing; rt-5.0.1/t/shredder/02group_member.t000644 000765 000024 00000014563 14005011336 020067 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::Deep; use RT::Test::Shredder tests => 35; my $test = "RT::Test::Shredder"; ### nested membership check { $test->create_savepoint('clean'); my $pgroup = RT::Group->new( RT->SystemUser ); my ($pgid) = $pgroup->CreateUserDefinedGroup( Name => 'Parent group' ); ok( $pgid, "created parent group" ); is( $pgroup->id, $pgid, "id is correct" ); my $cgroup = RT::Group->new( RT->SystemUser ); my ($cgid) = $cgroup->CreateUserDefinedGroup( Name => 'Child group' ); ok( $cgid, "created child group" ); is( $cgroup->id, $cgid, "id is correct" ); my ($status, $msg) = $pgroup->AddMember( $cgroup->id ); ok( $status, "added child group to parent") or diag "error: $msg"; $test->create_savepoint('bucreate'); # before user create my $user = RT::User->new( RT->SystemUser ); my $uid; ($uid, $msg) = $user->Create( Name => 'new user', Privileged => 1, Disabled => 0 ); ok( $uid, "created new user" ) or diag "error: $msg"; is( $user->id, $uid, "id is correct" ); $test->create_savepoint('buadd'); # before group add ($status, $msg) = $cgroup->AddMember( $user->id ); ok( $status, "added user to child group") or diag "error: $msg"; my $members = RT::GroupMembers->new( RT->SystemUser ); $members->Limit( FIELD => 'MemberId', VALUE => $uid ); $members->Limit( FIELD => 'GroupId', VALUE => $cgid ); is( $members->Count, 1, "find membership record" ); my $transactions = RT::Transactions->new(RT->SystemUser); $transactions->_OpenParen('member'); $transactions->Limit( SUBCLAUSE => 'member', FIELD => 'Type', VALUE => 'AddMember'); $transactions->Limit( SUBCLAUSE => 'member', FIELD => 'Field', VALUE => $user->PrincipalObj->id, ENTRYAGGREGATOR => 'AND' ); $transactions->Limit( SUBCLAUSE => 'member', FIELD => 'ObjectId', VALUE => $cgroup->id, ENTRYAGGREGATOR => 'AND' ); $transactions->_CloseParen('member'); $transactions->_OpenParen('member'); $transactions->Limit( SUBCLAUSE => 'member', FIELD => 'Type', VALUE => 'AddMembership'); $transactions->Limit( SUBCLAUSE => 'member', FIELD => 'Field', VALUE => $cgroup->PrincipalObj->id, ENTRYAGGREGATOR => 'AND' ); $transactions->Limit( SUBCLAUSE => 'member', FIELD => 'ObjectId', VALUE => $user->id, ENTRYAGGREGATOR => 'AND' ); $transactions->_CloseParen('member'); is( $transactions->Count, 2, "find membership transaction records" ); my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => [$members, $transactions] ); $shredder->WipeoutAll(); $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint('buadd'), "current DB equal to savepoint"); $shredder->PutObjects( Objects => $user ); $shredder->WipeoutAll(); $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint('bucreate'), "current DB equal to savepoint"); $shredder->PutObjects( Objects => [$pgroup, $cgroup] ); $shredder->WipeoutAll(); $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); } ### deleting member of the ticket AdminCc role group { $test->restore_savepoint('clean'); my $user = RT::User->new( RT->SystemUser ); my ($uid, $msg) = $user->Create( Name => 'new user', Privileged => 1, Disabled => 0 ); ok( $uid, "created new user" ) or diag "error: $msg"; is( $user->id, $uid, "id is correct" ); use RT::Queue; my $queue = RT::Queue->new( RT->SystemUser ); $queue->Load('general'); ok( $queue->id, "queue loaded succesfully" ); use RT::Tickets; my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id) = $ticket->Create( Subject => 'test', Queue => $queue->id ); ok( $id, "created new ticket" ); $ticket = RT::Ticket->new( RT->SystemUser ); my $status; ($status, $msg) = $ticket->Load( $id ); ok( $id, "load ticket" ) or diag( "error: $msg" ); ($status, $msg) = $ticket->AddWatcher( Type => "AdminCc", PrincipalId => $user->id ); ok( $status, "AdminCC successfuly added") or diag( "error: $msg" ); my $member = $ticket->AdminCc->MembersObj->First; my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $member ); $shredder->WipeoutAll(); $test->db_is_valid; $shredder->PutObjects( Objects => $user ); $shredder->WipeoutAll(); $test->db_is_valid; } ### deleting member of the ticket Owner role group { $test->restore_savepoint('clean'); my $user = RT::User->new( RT->SystemUser ); my ($uid, $msg) = $user->Create( Name => 'new user', Privileged => 1, Disabled => 0 ); ok( $uid, "created new user" ) or diag "error: $msg"; is( $user->id, $uid, "id is correct" ); use RT::Queue; my $queue = RT::Queue->new( RT->SystemUser ); $queue->Load('general'); ok( $queue->id, "queue loaded succesfully" ); $user->PrincipalObj->GrantRight( Right => 'OwnTicket', Object => $queue ); use RT::Tickets; my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id) = $ticket->Create( Subject => 'test', Queue => $queue->id ); ok( $id, "created new ticket" ); $ticket = RT::Ticket->new( RT->SystemUser ); my $status; ($status, $msg) = $ticket->Load( $id ); ok( $id, "load ticket" ) or diag( "error: $msg" ); ($status, $msg) = $ticket->SetOwner( $user->id ); ok( $status, "owner successfuly set") or diag( "error: $msg" ); is( $ticket->Owner, $user->id, "owner successfuly set") or diag( "error: $msg" ); my $member = $ticket->OwnerGroup->MembersObj->First; my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $member ); $shredder->WipeoutAll(); $test->db_is_valid; $ticket = RT::Ticket->new( RT->SystemUser ); ($status, $msg) = $ticket->Load( $id ); ok( $id, "load ticket" ) or diag( "error: $msg" ); is( $ticket->Owner, RT->Nobody->id, "owner switched back to nobody" ); is( $ticket->OwnerGroup->MembersObj->First->MemberId, RT->Nobody->id, "and owner role group member is nobody"); } rt-5.0.1/t/shredder/02user.t000644 000765 000024 00000003606 14005011336 016356 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::Deep; use RT::Test::Shredder tests => 10; my $test = "RT::Test::Shredder"; $test->create_savepoint('clean'); my $queue = RT::Queue->new( RT->SystemUser ); my ($qid) = $queue->Load( 'General' ); ok( $qid, "loaded queue" ); my $ticket = RT::Ticket->new( RT->SystemUser ); my ($tid) = $ticket->Create( Queue => $qid, Subject => 'test' ); ok( $tid, "ticket created" ); $test->create_savepoint('bucreate'); # berfore user create my $user = RT::User->new( RT->SystemUser ); my ($uid, $msg) = $user->Create( Name => 'new user', Privileged => 1, Disabled => 0 ); ok( $uid, "created new user" ) or diag "error: $msg"; is( $user->id, $uid, "id is correct" ); # HACK: set ticket props to enable VARIABLE dependencies $ticket->__Set( Field => 'LastUpdatedBy', Value => $uid ); $test->create_savepoint('aucreate'); # after user create { my $resolver = sub { my %args = (@_); my $t = $args{'TargetObject'}; my $resolver_uid = RT->SystemUser->id; foreach my $method ( qw(Creator LastUpdatedBy) ) { next unless $t->_Accessible( $method => 'read' ); $t->__Set( Field => $method, Value => $resolver_uid ); } }; my $shredder = $test->shredder_new(); $shredder->PutResolver( BaseClass => 'RT::User', Code => $resolver ); $shredder->Wipeout( Object => $user ); $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint('bucreate'), "current DB equal to savepoint"); } { $test->restore_savepoint('aucreate'); my $user = RT::User->new( RT->SystemUser ); $user->Load($uid); ok($user->id, "loaded user after restore"); my $shredder = $test->shredder_new(); eval { $shredder->Wipeout( Object => $user ) }; ok($@, "wipeout throw exception if no resolvers"); $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint('aucreate'), "current DB equal to savepoint"); } rt-5.0.1/t/shredder/03plugin_tickets.t000644 000765 000024 00000013720 14005011336 020423 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::Deep; use RT::Test::Shredder tests => 49; my $test = "RT::Test::Shredder"; use_ok('RT::Shredder'); use_ok('RT::Shredder::Plugin::Tickets'); { my $plugin = RT::Shredder::Plugin::Tickets->new; isa_ok($plugin, 'RT::Shredder::Plugin::Tickets'); is(lc $plugin->Type, 'search', 'correct type'); } $test->create_savepoint('clean'); use_ok('RT::Ticket'); use_ok('RT::Tickets'); { # create parent and child and check functionality of 'with_linked' arg my $parent = RT::Ticket->new( RT->SystemUser ); my ($pid) = $parent->Create( Subject => 'parent', Queue => 1 ); ok( $pid, "created new ticket" ); 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'); my ($status, $msg, @objs); ($status, $msg) = $plugin->TestArgs( query => 'Subject = "parent"' ); ok($status, "plugin arguments are ok") or diag "error: $msg"; ($status, @objs) = $plugin->Run; ok($status, "executed plugin successfully") or diag "error: @objs"; @objs = RT::Shredder->CastObjectsToRecords( Objects => \@objs ); is(scalar @objs, 1, "only one object in result set"); is($objs[0]->id, $pid, "parent is in result set"); ($status, $msg) = $plugin->TestArgs( query => 'Subject = "parent"', with_linked => 1 ); ok($status, "plugin arguments are ok") or diag "error: $msg"; ($status, @objs) = $plugin->Run; ok($status, "executed plugin successfully") or diag "error: @objs"; @objs = RT::Shredder->CastObjectsToRecords( Objects => \@objs ); my %has = map { $_->id => 1 } @objs; is(scalar @objs, 2, "two objects in the result set"); ok($has{$pid}, "parent is in the result set"); ok($has{$cid}, "child is in the result set"); my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => \@objs ); $shredder->WipeoutAll; $test->db_is_valid; } cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); { # create parent and child and link them reqursively to check that we don't hang my $parent = RT::Ticket->new( RT->SystemUser ); my ($pid) = $parent->Create( Subject => 'parent', Queue => 1 ); ok( $pid, "created new ticket" ); my $child = RT::Ticket->new( RT->SystemUser ); my ($cid) = $child->Create( Subject => 'child', Queue => 1, MemberOf => $pid ); ok( $cid, "created new ticket" ); 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'); my (@objs); ($status, $msg) = $plugin->TestArgs( query => 'Subject = "parent"' ); ok($status, "plugin arguments are ok") or diag "error: $msg"; ($status, @objs) = $plugin->Run; ok($status, "executed plugin successfully") or diag "error: @objs"; @objs = RT::Shredder->CastObjectsToRecords( Objects => \@objs ); is(scalar @objs, 1, "only one object in result set"); is($objs[0]->id, $pid, "parent is in result set"); ($status, $msg) = $plugin->TestArgs( query => 'Subject = "parent"', with_linked => 1 ); ok($status, "plugin arguments are ok") or diag "error: $msg"; ($status, @objs) = $plugin->Run; ok($status, "executed plugin successfully") or diag "error: @objs"; @objs = RT::Shredder->CastObjectsToRecords( Objects => \@objs ); is(scalar @objs, 2, "two objects in the result set"); my %has = map { $_->id => 1 } @objs; ok($has{$pid}, "parent is in the result set"); ok($has{$cid}, "child is in the result set"); my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => \@objs ); $shredder->WipeoutAll; $test->db_is_valid; } cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); { # create parent and child and check functionality of 'apply_query_to_linked' arg my $parent = RT::Ticket->new( RT->SystemUser ); my ($pid) = $parent->Create( Subject => 'parent', Queue => 1 ); ok( $pid, "created new ticket" ); $parent->SetStatus('resolved'); my $child1 = RT::Ticket->new( RT->SystemUser ); my ($cid1) = $child1->Create( Subject => 'child', Queue => 1, MemberOf => $pid ); ok( $cid1, "created new ticket" ); my $child2 = RT::Ticket->new( RT->SystemUser ); my ($cid2) = $child2->Create( Subject => 'child', Queue => 1, MemberOf => $pid); 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'); my ($status, $msg) = $plugin->TestArgs( query => 'Status = "resolved"', apply_query_to_linked => 1 ); 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, 2, "two objects in the result set"); my %has = map { $_->id => 1 } @objs; ok($has{$pid}, "parent is in the result set"); ok(!$has{$cid1}, "first child is in the result set"); ok($has{$cid2}, "second child is in the result set"); my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => \@objs ); $shredder->WipeoutAll; $test->db_is_valid; my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $cid1 ); is($ticket->id, $cid1, 'loaded ticket'); $shredder->PutObjects( Objects => $ticket ); $shredder->WipeoutAll; $test->db_is_valid; } cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); rt-5.0.1/t/shredder/02template.t000644 000765 000024 00000004202 14005011336 017204 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::Deep; use RT::Test::Shredder tests => 10; my $test = "RT::Test::Shredder"; diag 'global template' if $ENV{TEST_VERBOSE}; { $test->create_savepoint('clean'); my $template = RT::Template->new( RT->SystemUser ); my ($id, $msg) = $template->Create( Name => 'my template', Content => "\nsome content", ); ok($id, 'created template') or diag "error: $msg"; my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $template ); $shredder->WipeoutAll; $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); } diag 'local template' if $ENV{TEST_VERBOSE}; { $test->create_savepoint('clean'); my $template = RT::Template->new( RT->SystemUser ); my ($id, $msg) = $template->Create( Name => 'my template', Queue => 'General', Content => "\nsome content", ); ok($id, 'created template') or diag "error: $msg"; my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $template ); $shredder->WipeoutAll; $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); } diag 'template used in scrip' if $ENV{TEST_VERBOSE}; { $test->create_savepoint('clean'); my $template = RT::Template->new( RT->SystemUser ); my ($id, $msg) = $template->Create( Name => 'my template', Queue => 'General', Content => "\nsome content", ); ok($id, 'created template') or diag "error: $msg"; my $scrip = RT::Scrip->new( RT->SystemUser ); ($id, $msg) = $scrip->Create( Description => 'my scrip', Queue => 'General', ScripCondition => 'On Create', ScripAction => 'Open Tickets', Template => $template->id, ); ok($id, 'created scrip') or diag "error: $msg"; my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $template ); $shredder->WipeoutAll; $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); } rt-5.0.1/t/shredder/01ticket.t000644 000765 000024 00000005737 14005011336 016671 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::Deep; use RT::Test::Shredder tests => 20; my $test = "RT::Test::Shredder"; $test->create_savepoint('clean'); use RT::Ticket; use RT::Tickets; { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id) = $ticket->Create( Subject => 'test', Queue => 1 ); ok( $id, "created new ticket" ); $ticket->Delete; is( $ticket->Status, 'deleted', "successfuly changed status" ); $ticket->ApplyTransactionBatch; my $tickets = RT::Tickets->new( RT->SystemUser ); $tickets->{'allow_deleted_search'} = 1; $tickets->LimitStatus( VALUE => 'deleted' ); is( $tickets->Count, 1, "found one deleted ticket" ); my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $tickets ); $shredder->WipeoutAll; $test->db_is_valid; } cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); { my $parent = RT::Ticket->new( RT->SystemUser ); my ($pid) = $parent->Create( Subject => 'test', Queue => 1 ); ok( $pid, "created new ticket" ); $test->create_savepoint('parent_ticket'); my $child = RT::Ticket->new( RT->SystemUser ); my ($cid) = $child->Create( Subject => 'test', Queue => 1 ); ok( $cid, "created new ticket" ); my ($status, $msg) = $parent->AddLink( Type => 'MemberOf', Target => $cid ); ok( $status, "Added link between tickets") or diag("error: $msg"); $parent->ApplyTransactionBatch; $child->ApplyTransactionBatch; my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $child ); $shredder->WipeoutAll; $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint('parent_ticket'), "current DB equal to savepoint"); $shredder->PutObjects( Objects => $parent ); $shredder->WipeoutAll; $test->db_is_valid; } cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); { my $parent = RT::Ticket->new( RT->SystemUser ); my ($pid) = $parent->Create( Subject => 'test', Queue => 1 ); ok( $pid, "created new ticket" ); my ($status, $msg) = $parent->Delete; ok( $status, 'deleted parent ticket'); $test->create_savepoint('parent_ticket'); my $child = RT::Ticket->new( RT->SystemUser ); my ($cid) = $child->Create( Subject => 'test', Queue => 1 ); ok( $cid, "created new ticket #$cid" ); ($status, $msg) = $parent->AddLink( Type => 'DependsOn', Target => $cid ); ok( $status, "Added link between tickets") or diag("error: $msg"); $parent->ApplyTransactionBatch; $child->ApplyTransactionBatch; my $shredder = $test->shredder_new(); $shredder->PutObjects( Objects => $child ); $shredder->WipeoutAll; $test->db_is_valid; cmp_deeply( $test->dump_current_and_savepoint('parent_ticket'), "current DB equal to savepoint"); $shredder->PutObjects( Objects => $parent ); $shredder->WipeoutAll; $test->db_is_valid; } cmp_deeply( $test->dump_current_and_savepoint('clean'), "current DB equal to savepoint"); rt-5.0.1/t/shredder/03plugin.t000644 000765 000024 00000002337 14005011336 016677 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::Deep; use RT::Test::Shredder nodb => 1, tests => 28; my $test = "RT::Test::Shredder"; my @PLUGINS = sort qw(Attachments Base Objects SQLDump Summary Tickets Users); use_ok('RT::Shredder::Plugin'); { my $plugin = RT::Shredder::Plugin->new; isa_ok($plugin, 'RT::Shredder::Plugin'); my %plugins = $plugin->List; cmp_deeply( [sort keys %plugins], [@PLUGINS], "correct plugins" ); } { # test ->List as class method my %plugins = RT::Shredder::Plugin->List; cmp_deeply( [sort keys %plugins], [@PLUGINS], "correct plugins" ); } { # reblessing on LoadByName foreach (@PLUGINS) { my $plugin = RT::Shredder::Plugin->new; isa_ok($plugin, 'RT::Shredder::Plugin'); my ($status, $msg) = $plugin->LoadByName( $_ ); ok($status, "loaded plugin by name") or diag("error: $msg"); isa_ok($plugin, "RT::Shredder::Plugin::$_" ); } } { # error checking in LoadByName my $plugin = RT::Shredder::Plugin->new; isa_ok($plugin, 'RT::Shredder::Plugin'); my ($status, $msg) = $plugin->LoadByName; ok(!$status, "not loaded plugin - empty name"); ($status, $msg) = $plugin->LoadByName('Foo'); ok(!$status, "not loaded plugin - not exist"); } rt-5.0.1/t/fts/not_indexed.t000644 000765 000024 00000003343 14005011336 016530 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 20; RT->Config->Set( FullTextSearch => Enable => 1, Indexed => 0 ); my $q = RT::Test->load_or_create_queue( Name => 'General' ); ok $q && $q->id, 'loaded or created queue'; my $queue = $q->Name; sub run_tests { my @test = @_; while ( my ($query, $checks) = splice @test, 0, 2 ) { run_test( $query, %$checks ); } } my @tickets; sub run_test { my ($query, %checks) = @_; my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets; my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL( "( $query_prefix ) AND ( $query )" ); my $error = 0; my $count = 0; $count++ foreach grep $_, values %checks; is($tix->Count, $count, "found correct number of ticket(s) by '$query'") or $error = 1; my $good_tickets = ($tix->Count == $count); while ( my $ticket = $tix->Next ) { next if $checks{ $ticket->Subject }; diag $ticket->Subject ." ticket has been found when it's not expected"; $good_tickets = 0; } ok( $good_tickets, "all tickets are good with '$query'" ) or $error = 1; diag "Wrong SQL query for '$query':". $tix->BuildSelectQuery if $error; } @tickets = RT::Test->create_tickets( { Queue => $q->id }, { Subject => 'book', Content => 'book' }, { Subject => 'bar', Content => 'bar' }, { Subject => 'no content', Content => undef }, ); run_tests( "Content LIKE 'book'" => { book => 1, bar => 0 }, "Content LIKE 'bar'" => { book => 0, bar => 1 }, "(Content LIKE 'baz' OR Subject LIKE 'con')" => { 'no content' => 1 }, "(Content LIKE 'bar' OR Subject LIKE 'con')" => { 'no content' => 1, bar => 1 }, "(Content LIKE 'bar' OR Subject LIKE 'missing')" => { bar => 1 }, ); rt-5.0.1/t/fts/indexed_oracle.t000644 000765 000024 00000004342 14005011336 017175 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; plan skip_all => 'Not Oracle' unless RT->Config->Get('DatabaseType') eq 'Oracle'; plan tests => 13; RT->Config->Set( FullTextSearch => Enable => 1, Indexed => 1, IndexName => 'rt_fts_index' ); setup_indexing(); my $q = RT::Test->load_or_create_queue( Name => 'General' ); ok $q && $q->id, 'loaded or created queue'; my $queue = $q->Name; sub setup_indexing { my %args = ( 'no-ask' => 1, command => $RT::SbinPath .'/rt-setup-fulltext-index', dba => $ENV{'RT_DBA_USER'}, 'dba-password' => $ENV{'RT_DBA_PASSWORD'}, ); my ($exit_code, $output) = RT::Test->run_and_capture( %args ); ok(!$exit_code, "setted up index") or diag "output: $output"; } sub sync_index { my %args = ( command => $RT::SbinPath .'/rt-fulltext-indexer', ); my ($exit_code, $output) = RT::Test->run_and_capture( %args ); ok(!$exit_code, "synced the index") or diag "output: $output"; } sub run_tests { my @test = @_; while ( my ($query, $checks) = splice @test, 0, 2 ) { run_test( $query, %$checks ); } } my @tickets; sub run_test { my ($query, %checks) = @_; my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets; my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL( "( $query_prefix ) AND ( $query )" ); my $error = 0; my $count = 0; $count++ foreach grep $_, values %checks; is($tix->Count, $count, "found correct number of ticket(s) by '$query'") or $error = 1; my $good_tickets = ($tix->Count == $count); while ( my $ticket = $tix->Next ) { next if $checks{ $ticket->Subject }; diag $ticket->Subject ." ticket has been found when it's not expected"; $good_tickets = 0; } ok( $good_tickets, "all tickets are good with '$query'" ) or $error = 1; diag "Wrong SQL query for '$query':". $tix->BuildSelectQuery if $error; } @tickets = RT::Test->create_tickets( { Queue => $q->id }, { Subject => 'book', Content => 'book' }, { Subject => 'bar', Content => 'bar' }, ); sync_index(); run_tests( "Content LIKE 'book'" => { book => 1, bar => 0 }, "Content LIKE 'bar'" => { book => 0, bar => 1 }, ); @tickets = (); rt-5.0.1/t/fts/indexed_sphinx.t000644 000765 000024 00000011152 14005011336 017236 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; plan skip_all => 'Not mysql' unless RT->Config->Get('DatabaseType') eq 'mysql'; plan skip_all => "No SphinxSE in mysql" unless $RT::Handle->CheckSphinxSE; my %sphinx; $sphinx{'searchd'} = RT::Test->find_executable('searchd'); $sphinx{'indexer'} = RT::Test->find_executable('indexer'); plan skip_all => "No searchd and indexer under PATH" unless $sphinx{'searchd'} && $sphinx{'indexer'}; plan skip_all => "Can't determine sphinx version" unless `$sphinx{searchd} --version` =~ /Sphinx (\d+)\.(\d+)(?:\.(\d+))?/; $sphinx{version} = sprintf "%d.%03d%03d", $1, $2, ($3 || 0); plan tests => 15; setup_indexing(); my $q = RT::Test->load_or_create_queue( Name => 'General' ); ok $q && $q->id, 'loaded or created queue'; my $queue = $q->Name; sub setup_indexing { # Since we're not running a webserver in this test, use the # known-safe port we determined at test setup my $port = $RT::Test::port; my ($exit_code, $output) = RT::Test->run_and_capture( 'no-ask' => 1, command => $RT::SbinPath .'/rt-setup-fulltext-index', dba => $ENV{'RT_DBA_USER'}, 'dba-password' => $ENV{'RT_DBA_PASSWORD'}, url => "sphinx://127.0.0.1:$port/rt", 'index-type' => 'sphinx', ); ok(!$exit_code, "setted up index"); diag "output: $output" if $ENV{'TEST_VERBOSE'}; my $tmp = $sphinx{'directory'} = File::Spec->catdir( RT::Test->temp_directory, 'sphinx' ); mkdir $tmp; my $sphinx_conf = $output; $sphinx_conf =~ s/.*?source rt \{/source rt {/ms; $sphinx_conf =~ s{\Q$RT::VarPath\E/sphinx/}{$tmp/}g; # Remove lines for different versions of sphinx than we're running $sphinx_conf =~ s{^(\s+ \# \s+ for \s+ sphinx \s+ (<=?|>=?|=) \s* (\d+) \. (\d+) (?:\. (\d+))? .* \n) ((?:^\s* \w .*\n)+)}{ my $v = sprintf "%d.%03d%03d", $3, $4, ($5 || 0); my $prefix = eval "$sphinx{version} $2 $v" ? "" : "#"; $1 . join("\n",map{"$prefix$_"} split "\n", $6) . "\n"; }emix; $sphinx{'config'} = File::Spec->catfile( $tmp, 'sphinx.conf' ); { open my $fh, ">", $sphinx{'config'}; print $fh $sphinx_conf; close $fh; } sync_index(); { my ($exit_code, $output) = RT::Test->run_and_capture( command => $sphinx{'searchd'}, config => $sphinx{'config'}, ); ok(!$exit_code, "setted up index") or diag "output: $output"; $sphinx{'started'} = 1 if !$exit_code; } } sub sync_index { local $SIG{'CHLD'} = 'DEFAULT'; local $SIG{'PIPE'} = 'DEFAULT'; open my $fh, '-|', $sphinx{'indexer'}, '--all', '--config' => $sphinx{'config'}, $sphinx{'started'}? ('--rotate') : (), ; my $output = <$fh>; close $fh; my $exit_code = $?>>8; ok(!$exit_code, "indexed") or diag "output: $output"; # We may need to wait a second for searchd to pick up the changes sleep 1; } sub run_tests { my @test = @_; while ( my ($query, $checks) = splice @test, 0, 2 ) { run_test( $query, %$checks ); } } my @tickets; sub run_test { my ($query, %checks) = @_; my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets; my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL( "( $query_prefix ) AND ( $query )" ); my $error = 0; my $count = 0; $count++ foreach grep $_, values %checks; is($tix->Count, $count, "found correct number of ticket(s) by '$query'") or $error = 1; my $good_tickets = ($tix->Count == $count); while ( my $ticket = $tix->Next ) { next if $checks{ $ticket->Subject }; diag $ticket->Subject ." ticket has been found when it's not expected"; $good_tickets = 0; } ok( $good_tickets, "all tickets are good with '$query'" ) or $error = 1; diag "Wrong SQL query for '$query':". $tix->BuildSelectQuery if $error; } @tickets = RT::Test->create_tickets( { Queue => $q->id }, { Subject => 'book', Content => 'book' }, { Subject => 'bar', Content => 'bar' }, ); sync_index(); RT->Config->Set( FullTextSearch => Enable => 1, Indexed => 1, Table => 'AttachmentsIndex', MaxMatches => 1000, Sphinx => 1 ); run_tests( "Content LIKE 'book'" => { book => 1, bar => 0 }, "Content LIKE 'bar'" => { book => 0, bar => 1 }, ); END { my $Test = RT::Test->builder; return if $Test->{Original_Pid} != $$; return unless $sphinx{'started'}; my $pid = int RT::Test->file_content([$sphinx{'directory'}, 'searchd.pid']); kill TERM => $pid if $pid; } rt-5.0.1/t/fts/indexed_pg.t000644 000765 000024 00000007606 14005011336 016344 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; plan skip_all => 'Not Pg' unless RT->Config->Get('DatabaseType') eq 'Pg'; my ($major, $minor) = $RT::Handle->dbh->get_info(18) =~ /^0*(\d+)\.0*(\d+)/; plan skip_all => "Need Pg 8.2 or higher; we have $major.$minor" if "$major.$minor" < 8.2; RT->Config->Set( FullTextSearch => Enable => 1, Indexed => 1, Column => 'ContentIndex', Table => 'AttachmentsIndex' ); setup_indexing(); my $q = RT::Test->load_or_create_queue( Name => 'General' ); ok $q && $q->id, 'loaded or created queue'; my $queue = $q->Name; sub setup_indexing { my %args = ( 'no-ask' => 1, command => $RT::SbinPath .'/rt-setup-fulltext-index', dba => $ENV{'RT_DBA_USER'}, 'dba-password' => $ENV{'RT_DBA_PASSWORD'}, ); my ($exit_code, $output) = RT::Test->run_and_capture( %args ); ok(!$exit_code, "setted up index") or diag "output: $output"; } sub sync_index { my %args = ( command => $RT::SbinPath .'/rt-fulltext-indexer', ); my ($exit_code, $output) = RT::Test->run_and_capture( %args ); ok(!$exit_code, "setted up index") or diag "output: $output"; } sub run_tests { my @test = @_; while ( my ($query, $checks) = splice @test, 0, 2 ) { run_test( $query, %$checks ); } } my @tickets; sub run_test { my ($query, %checks) = @_; my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets; my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL( "( $query_prefix ) AND ( $query )" ); my $error = 0; my $count = 0; $count++ foreach grep $_, values %checks; is($tix->Count, $count, "found correct number of ticket(s) by '$query'") or $error = 1; my $good_tickets = ($tix->Count == $count); while ( my $ticket = $tix->Next ) { next if $checks{ $ticket->id }; diag $ticket->Subject ." ticket has been found when it's not expected"; $good_tickets = 0; } ok( $good_tickets, "all tickets are good with '$query'" ) or $error = 1; diag "Wrong SQL query for '$query':". $tix->BuildSelectQuery if $error; } my $blase = Encode::decode_utf8("blasé"); @tickets = RT::Test->create_tickets( { Queue => $q->id }, { Subject => 'fts test 1', Content => "book $blase" }, { Subject => 'fts test 2', Content => "bars blasé", ContentType => 'text/html' }, ); sync_index(); my $book = $tickets[0]; my $bars = $tickets[1]; run_tests( "Content LIKE 'book'" => { $book->id => 1, $bars->id => 0 }, "Content LIKE 'bars'" => { $book->id => 0, $bars->id => 1 }, # Unicode searching "Content LIKE '$blase'" => { $book->id => 1, $bars->id => 1 }, "Content LIKE 'blase'" => { $book->id => 0, $bars->id => 0 }, "Content LIKE 'blas'" => { $book->id => 0, $bars->id => 0 }, # make sure that Pg stemming works "Content LIKE 'books'" => { $book->id => 1, $bars->id => 0 }, "Content LIKE 'bar'" => { $book->id => 0, $bars->id => 1 }, # no matches "Content LIKE 'baby'" => { $book->id => 0, $bars->id => 0 }, "Content LIKE 'pubs'" => { $book->id => 0, $bars->id => 0 }, ); # Test the "ts_vector too long" skip my $content = ""; $content .= "$_\n" for 1..200_000; @tickets = RT::Test->create_tickets( { Queue => $q->id }, { Subject => 'Short content', Content => '50' }, { Subject => 'Long content', Content => $content }, { Subject => 'More short', Content => '50' }, ); my ($exit_code, $output) = RT::Test->run_and_capture( command => $RT::SbinPath .'/rt-fulltext-indexer' ); like($output, qr/string is too long for tsvector/, "Got a warning for the ticket"); ok(!$exit_code, "set up index"); # The long content is skipped entirely run_tests( "Content LIKE '1'" => { $tickets[0]->id => 0, $tickets[1]->id => 0, $tickets[2]->id => 0 }, "Content LIKE '50'" => { $tickets[0]->id => 1, $tickets[1]->id => 0, $tickets[2]->id => 1 }, ); @tickets = (); done_testing; rt-5.0.1/t/fts/indexed_mysql.t000644 000765 000024 00000004614 14005011336 017077 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; plan skip_all => 'Not mysql' unless RT->Config->Get('DatabaseType') eq 'mysql'; RT->Config->Set( FullTextSearch => Enable => 1, Indexed => 1, Table => 'AttachmentsIndex' ); setup_indexing(); my $q = RT::Test->load_or_create_queue( Name => 'General' ); ok $q && $q->id, 'loaded or created queue'; my $queue = $q->Name; sub setup_indexing { my %args = ( 'no-ask' => 1, command => $RT::SbinPath .'/rt-setup-fulltext-index', dba => $ENV{'RT_DBA_USER'}, 'dba-password' => $ENV{'RT_DBA_PASSWORD'}, ); my ($exit_code, $output) = RT::Test->run_and_capture( %args ); ok(!$exit_code, "setted up index") or diag "output: $output"; } sub sync_index { my %args = ( command => $RT::SbinPath .'/rt-fulltext-indexer', ); my ($exit_code, $output) = RT::Test->run_and_capture( %args ); ok(!$exit_code, "setted up index") or diag "output: $output"; } sub run_tests { my @test = @_; while ( my ($query, $checks) = splice @test, 0, 2 ) { run_test( $query, %$checks ); } } my @tickets; sub run_test { my ($query, %checks) = @_; my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets; my $tix = RT::Tickets->new(RT->SystemUser); $tix->FromSQL( "( $query_prefix ) AND ( $query )" ); my $error = 0; my $count = 0; $count++ foreach grep $_, values %checks; is($tix->Count, $count, "found correct number of ticket(s) by '$query'") or $error = 1; my $good_tickets = ($tix->Count == $count); while ( my $ticket = $tix->Next ) { next if $checks{ $ticket->Subject }; diag $ticket->Subject ." ticket has been found when it's not expected"; $good_tickets = 0; } ok( $good_tickets, "all tickets are good with '$query'" ) or $error = 1; diag "Wrong SQL query for '$query':". $tix->BuildSelectQuery if $error; } @tickets = RT::Test->create_tickets( { Queue => $q->id }, { Subject => 'first', Content => 'english' }, { Subject => 'second', Content => 'french' }, { Subject => 'third', Content => 'spanish' }, { Subject => 'fourth', Content => 'german' }, ); sync_index(); run_tests( "Content LIKE 'english'" => { first => 1, second => 0, third => 0, fourth => 0 }, "Content LIKE 'french'" => { first => 0, second => 1, third => 0, fourth => 0 }, ); @tickets = (); done_testing; rt-5.0.1/t/charts/search-queue-cf.t000644 000765 000024 00000006161 14005011336 017676 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use RT::Ticket; my $general = RT::Test->load_or_create_queue(Name => 'General'); ok $general && $general->id, 'loaded or created queue'; my $test_queue1 = RT::Test->load_or_create_queue(Name => 'Test Queue 1'); ok $test_queue1 && $test_queue1->id, 'created Test Queue 1'; my $test_queue2 = RT::Test->load_or_create_queue(Name => 'Test Queue 2'); ok $test_queue2 && $test_queue2->id, 'created Test Queue 2'; my @tickets = RT::Test->create_tickets( {}, # 3 tickets in General { Queue => $general, Subject => 'new general', Status => 'new' }, { Queue => $general, Subject => 'open general 1', Status => 'open' }, { Queue => $general, Subject => 'open general 2', Status => 'open' }, # 2 tickets in Test Queue 1 { Queue => $test_queue1, Subject => 'new test queue 1', Status => 'new' }, { Queue => $test_queue1, Subject => 'open tests queue 1', Status => 'open' }, # 1 tickets in Test Queue 2 { Queue => $test_queue2, Subject => 'new test queue 2', Status => 'new' }, ); my $test_cf1 = RT::CustomField->new(RT->SystemUser); my ($cf1_id,$msg1) = $test_cf1->Create(ObjectId => 0, Name => 'Test Field 1', Type => 'Freeform', MaxValues => 1, LookupType => 'RT::Queue', Description => 'First queue test field'); ok $cf1_id, "Created custom field 1 $msg1"; my $test_cf2 = RT::CustomField->new(RT->SystemUser); my ($cf2_id,$msg2) = $test_cf2->Create(ObjectId => 0, Name => 'Test Field 2', Type => 'Freeform', MaxValues => 1, LookupType => 'RT::Queue', Description => 'Second queue test field'); ok $cf2_id, "Created custom field 2 $msg2"; my ($value1_id,$msg3) = $test_cf1->AddValueForObject(Object => $general, Content => 'Test A'); ok $value1_id, "Create Custom Field Value 1"; my ($value2_id,$msg4) = $test_cf1->AddValueForObject(Object => $test_queue1, Content => 'Test A'); ok $value2_id, "Create Custom Field Value 2"; my ($value3_id,$msg5) = $test_cf2->AddValueForObject(Object => $test_queue2, Content => 'Test B'); ok $value3_id, "Create Custom Field Value 3"; use_ok 'RT::Report::Tickets'; diag "Test A search"; { my $report = RT::Report::Tickets->new(RT->SystemUser); my %columns = $report->SetupGroupings( Query => "'QueueCF.{Test Field 1}' = 'Test A'", GroupBy => ['Status'], Function => ['COUNT'], ); $report->SortEntries; my %table = $report->FormatTable(%columns); is $table{tbody}[0]{cells}[0]{value},'new', "Test A new tickets"; is $table{tbody}[0]{cells}[1]{value},2, "Test A 2 new tickets"; is $table{tbody}[1]{cells}[0]{value},'open', "Test A open tickets"; is $table{tbody}[1]{cells}[1]{value},3, "Test A 3 open tickets"; } diag "Test B search"; { my $report = RT::Report::Tickets->new(RT->SystemUser); my %columns = $report->SetupGroupings( Query => "'QueueCF.{Test Field 2}' = 'Test B'", GroupBy => ['Status'], Function => ['COUNT'], ); $report->SortEntries; my %table = $report->FormatTable(%columns); is $table{tbody}[0]{cells}[0]{value},'new', "Test B new tickets"; is $table{tbody}[0]{cells}[1]{value},1, "Test B 1 new tickets"; } done_testing; rt-5.0.1/t/charts/compound-sql-function.t000644 000765 000024 00000011210 14005011336 021154 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 14; use RT::Ticket; my $q1 = RT::Test->load_or_create_queue( Name => 'One' ); ok $q1 && $q1->id, 'loaded or created queue'; my $q2 = RT::Test->load_or_create_queue( Name => 'Two' ); ok $q2 && $q2->id, 'loaded or created queue'; my @tickets = add_tix_from_data( { Queue => $q1->id, Resolved => 3*60 }, { Queue => $q1->id, Resolved => 3*60*60 }, { Queue => $q1->id, Resolved => 3*24*60*60 }, { Queue => $q1->id, Resolved => 3*30*24*60*60 }, { Queue => $q1->id, Resolved => 9*30*24*60*60 }, { Queue => $q2->id, Resolved => 7*60 }, { Queue => $q2->id, Resolved => 7*60*60 }, { Queue => $q2->id, Resolved => 7*24*60*60 }, { Queue => $q2->id, Resolved => 7*30*24*60*60 }, { Queue => $q2->id, Resolved => 24*30*24*60*60 }, ); use_ok 'RT::Report::Tickets'; { my $report = RT::Report::Tickets->new( RT->SystemUser ); my %columns = $report->SetupGroupings( Query => 'id > 0', GroupBy => ['Queue'], Function => ['ALL(Created to Resolved)'], ); $report->SortEntries; my @colors = RT->Config->Get("ChartColors"); my $expected = { 'thead' => [ { 'cells' => [ { 'rowspan' => 2, 'value' => 'Queue', 'type' => 'head' }, { 'colspan' => 4, 'value' => 'Summary of Created to Resolved', 'type' => 'head' } ] }, { 'cells' => [ { 'value' => 'Minimum', 'type' => 'head', 'color' => $colors[0] }, { 'value' => 'Average', 'type' => 'head', 'color' => $colors[1] }, { 'value' => 'Maximum', 'type' => 'head', 'color' => $colors[2] }, { 'value' => 'Total', 'type' => 'head', 'color' => $colors[3] } ] } ], 'tfoot' => [ { 'cells' => [ { 'colspan' => 1, 'value' => 'Total', 'type' => 'label' }, { 'value' => '10m', 'type' => 'value' }, { 'value' => '8M 2W 3d', 'type' => 'value' }, { 'value' => '2Y 8M 2W', 'type' => 'value' }, { 'value' => '3Y 6M 3W', 'type' => 'value' } ], 'even' => 1 } ], 'tbody' => [ { 'cells' => [ { 'value' => 'One', 'type' => 'label' }, { 'query' => '(Queue = 3)', 'value' => '3m', 'type' => 'value' }, { 'query' => '(Queue = 3)', 'value' => '2M 1W 5d', 'type' => 'value' }, { 'query' => '(Queue = 3)', 'value' => '8M 3W 6d', 'type' => 'value' }, { 'query' => '(Queue = 3)', 'value' => '11M 4W 8h', 'type' => 'value' } ], 'even' => 1 }, { 'cells' => [ { 'value' => 'Two', 'type' => 'label' }, { 'query' => '(Queue = 4)', 'value' => '7m', 'type' => 'value' }, { 'query' => '(Queue = 4)', 'value' => '6M 4d 20h', 'type' => 'value' }, { 'query' => '(Queue = 4)', 'value' => '1Y 11M 3W', 'type' => 'value' }, { 'query' => '(Queue = 4)', 'value' => '2Y 6M 3W', 'type' => 'value' } ], 'even' => 0 } ] }; my %table = $report->FormatTable( %columns ); is_deeply( \%table, $expected, "basic table" ); } sub add_tix_from_data { my @data = @_; my @res = (); my $created = RT::Date->new( $RT::SystemUser ); $created->SetToNow; my $resolved = RT::Date->new( $RT::SystemUser ); while (@data) { $resolved->Set( Value => $created->Unix ); $resolved->AddSeconds( $data[0]{'Resolved'} ); my $t = RT::Ticket->new($RT::SystemUser); my ( $id, undef, $msg ) = $t->Create( %{ shift(@data) }, Created => $created->ISO, Resolved => $resolved->ISO, ); ok( $id, "ticket created" ) or diag("error: $msg"); push @res, $t; } return @res; } rt-5.0.1/t/charts/group-by-sla.t000644 000765 000024 00000005062 14005011336 017241 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef, config => < '2', Levels => { '2' => { StartImmediately => 1, Response => { RealMinutes => 60 * 2 }, }, '4' => { StartImmediately => 1, Response => { RealMinutes => 60 * 4 }, }, }, )); THERE use RT::Ticket; my $q = RT::Test->load_or_create_queue( Name => 'test', SLADisabled => 0 ); ok $q && $q->id, 'loaded or created queue'; my $queue = $q->Name; my $t1 = RT::Ticket->new( $RT::SystemUser ); my $t1_id = $t1->Create( Queue => $queue, Subject => 'test 1' ); ok $t1_id, "created ticket #$t1_id"; is $t1->SLA, '2', 'default sla'; my $t2 = RT::Ticket->new( $RT::SystemUser ); my $t2_id = $t2->Create( Queue => $queue, Subject => 'test 2' ); ok $t2_id, "created ticket #$t2_id"; is $t2->SLA, '2', 'default sla'; $t2->SetSLA('4'); is $t2->SLA, '4', 'new sla'; my $t3 = RT::Ticket->new($RT::SystemUser); my $t3_id = $t3->Create( Queue => $queue, Subject => 'test 3' ); ok $t3_id, "created ticket #$t3_id"; is $t3->SLA, '2', 'default sla'; use_ok 'RT::Report::Tickets'; { my $report = RT::Report::Tickets->new( RT->SystemUser ); my %columns = $report->SetupGroupings( Query => 'Queue = '. $q->id, GroupBy => ["SLA"], Function => ['COUNT'], ); $report->SortEntries; my @colors = RT->Config->Get("ChartColors"); my $expected = { 'thead' => [ { 'cells' => [ { 'value' => 'SLA', 'type' => 'head' }, { 'rowspan' => 1, 'value' => 'Ticket count', 'type' => 'head', 'color' => $colors[0] }, ], } ], 'tfoot' => [ { 'cells' => [ { 'colspan' => 1, 'value' => 'Total', 'type' => 'label' }, { 'value' => 3, 'type' => 'value' }, ], 'even' => 1 } ], 'tbody' => [ { 'cells' => [ { 'value' => '2', 'type' => 'label' }, { 'query' => "(SLA = 2)", 'value' => '2', 'type' => 'value' }, ], 'even' => 1 }, { 'cells' => [ { 'value' => '4', 'type' => 'label' }, { 'query' => "(SLA = 4)", 'value' => '1', 'type' => 'value' }, ], 'even' => 0 }, ] }; my %table = $report->FormatTable( %columns ); is_deeply( \%table, $expected, "basic table" ); } done_testing; rt-5.0.1/t/charts/group-by-cf.t000644 000765 000024 00000004142 14005011336 017050 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use RT::Ticket; my $q = RT::Test->load_or_create_queue( Name => 'General' ); ok $q && $q->id, 'loaded or created queue'; my $queue = $q->Name; my $cf = RT::CustomField->new(RT->SystemUser); my ($id,$msg) = $cf->Create(Name => 'Test', Type => 'Freeform', MaxValues => '1', Queue => $q->id); ok $id, $msg; my $cfid = $cf->id; my @tickets = RT::Test->create_tickets( {}, { Subject => 't1', Status => 'new', CustomFields => { Test => 'a' } }, { Subject => 't2', Status => 'open', CustomFields => { Test => 'b' } }, ); use_ok 'RT::Report::Tickets'; { my $report = RT::Report::Tickets->new( RT->SystemUser ); my %columns = $report->SetupGroupings( Query => 'Queue = '. $q->id, GroupBy => ["CF.{$cfid}"], # TODO: CF.{Name} is not supported at the moment Function => ['COUNT'], ); $report->SortEntries; my @colors = RT->Config->Get("ChartColors"); my $expected = { 'thead' => [ { 'cells' => [ { 'value' => 'Custom field Test', 'type' => 'head' }, { 'rowspan' => 1, 'value' => 'Ticket count', 'type' => 'head', 'color' => $colors[0] }, ], } ], 'tfoot' => [ { 'cells' => [ { 'colspan' => 1, 'value' => 'Total', 'type' => 'label' }, { 'value' => 2, 'type' => 'value' }, ], 'even' => 1 } ], 'tbody' => [ { 'cells' => [ { 'value' => 'a', 'type' => 'label' }, { 'query' => "(CF.{$cfid} = 'a')", 'value' => '1', 'type' => 'value' }, ], 'even' => 1 }, { 'cells' => [ { 'value' => 'b', 'type' => 'label' }, { 'query' => "(CF.{$cfid} = 'b')", 'value' => '1', 'type' => 'value' }, ], 'even' => 0 }, ] }; my %table = $report->FormatTable( %columns ); is_deeply( \%table, $expected, "basic table" ); } done_testing; rt-5.0.1/t/charts/basics.t000644 000765 000024 00000005166 14005011336 016171 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use RT::Ticket; my $q = RT::Test->load_or_create_queue( Name => 'General' ); ok $q && $q->id, 'loaded or created queue'; my $queue = $q->Name; my @tickets = add_tix_from_data( { Subject => 'n', Status => 'new' }, { Subject => 'o', Status => 'open' }, { Subject => 'o', Status => 'open' }, { Subject => 'r', Status => 'resolved' }, { Subject => 'r', Status => 'resolved' }, { Subject => 'r', Status => 'resolved' }, ); use_ok 'RT::Report::Tickets'; { my $report = RT::Report::Tickets->new( RT->SystemUser ); my %columns = $report->SetupGroupings( Query => 'Queue = '. $q->id, GroupBy => ['Status'], Function => ['COUNT'], ); $report->SortEntries; my @colors = RT->Config->Get("ChartColors"); my $expected = { 'thead' => [ { 'cells' => [ { 'value' => 'Status', 'type' => 'head' }, { 'rowspan' => 1, 'value' => 'Ticket count', 'type' => 'head', 'color' => $colors[0] }, ], } ], 'tfoot' => [ { 'cells' => [ { 'colspan' => 1, 'value' => 'Total', 'type' => 'label' }, { 'value' => 6, 'type' => 'value' }, ], 'even' => 0 } ], 'tbody' => [ { 'cells' => [ { 'value' => 'new', 'type' => 'label' }, { 'query' => '(Status = \'new\')', 'value' => '1', 'type' => 'value' }, ], 'even' => 1 }, { 'cells' => [ { 'value' => 'open', 'type' => 'label' }, { 'query' => '(Status = \'open\')', 'value' => '2', 'type' => 'value' } ], 'even' => 0 }, { 'cells' => [ { 'value' => 'resolved', 'type' => 'label' }, { 'query' => '(Status = \'resolved\')', 'value' => '3', 'type' => 'value' } ], 'even' => 1 }, ] }; my %table = $report->FormatTable( %columns ); is_deeply( \%table, $expected, "basic table" ); } done_testing; sub add_tix_from_data { my @data = @_; my @res = (); while (@data) { my %info = %{ shift(@data) }; my $t = RT::Ticket->new($RT::SystemUser); my ( $id, undef, $msg ) = $t->Create( Queue => $q->id, %info ); ok( $id, "ticket created" ) or diag("error: $msg"); is $t->Status, $info{'Status'}, 'correct status'; push @res, $t; } return @res; } rt-5.0.1/t/articles/upload-customfields.t000644 000765 000024 00000005314 14005011336 021225 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 20; use RT; my $logo; BEGIN { $logo = -e $RT::StaticPath . '/images/bpslogo.png' ? 'bpslogo.png' : 'bplogo.gif'; } use constant ImageFile => $RT::StaticPath . "/images/$logo"; use constant ImageFileContent => do { local $/; open my $fh, '<', ImageFile or die ImageFile.$!; binmode($fh); scalar <$fh>; }; use RT::Class; my $class = RT::Class->new($RT::SystemUser); my ($ret, $msg) = $class->Create('Name' => 'tlaTestClass-'.$$, 'Description' => 'A general-purpose test class'); ok($ret, "Test class created"); my ($url, $m) = RT::Test->started_ok; isa_ok($m, 'Test::WWW::Mechanize'); ok($m->login, 'logged in'); $m->follow_link_ok( { text => 'Admin' } ); $m->title_is(q/RT Administration/, 'admin screen'); $m->follow_link_ok( { text => 'Custom Fields' } ); $m->title_is(q/Select a Custom Field/, 'admin-cf screen'); $m->follow_link_ok( { text => 'Create', url_regex => qr{^/Admin/CustomFields/} } ); $m->submit_form( form_name => "ModifyCustomField", fields => { TypeComposite => 'Image-0', LookupType => 'RT::Class-RT::Article', Name => 'img'.$$, Description => 'img', EntryHint => 'Upload multiple images', }, ); $m->title_is(qq/Editing CustomField img$$/, 'admin-cf created'); $m->follow_link( text => 'Applies to' ); $m->title_is(qq/Modify associated objects for img$$/, 'pick cf screenadmin screen'); $m->form_number(3); my $tcf = (grep { /AddCustomField-/ } map { $_->name } $m->current_form->inputs )[0]; $m->tick( $tcf, 0 ); # Associate the new CF with this queue $m->click('UpdateObjs'); $m->content_contains("Globally added custom field img$$", 'TCF added to the queue' ); $m->follow_link_ok( { text => 'Articles', url_regex => qr!^/Articles/! }, 'UI -> Articles' ); $m->follow_link( text => 'Create'); $m->follow_link( url_regex => qr/Edit.html\?Class=1/ ); $m->title_is(qq/Create a new article/); $m->content_like(qr/Upload multiple images/, 'has a upload image field'); $tcf =~ /(\d+)$/ or die "Hey this is impossible dude"; my $upload_field = "Object-RT::Article--CustomField-$1-Upload"; diag("Uploading an image to $upload_field") if $ENV{TEST_VERBOSE}; $m->submit_form( form_name => "EditArticle", fields => { $upload_field => ImageFile, Name => 'Image Test '.$$, Summary => 'testing img cf creation', }, ); $m->content_like(qr/Article \d+ created/, "an article was created succesfully"); my $id = $1 if $m->content =~ /Article (\d+) created/; $m->title_like(qr/Modify article #$id/, "Editing article $id"); $m->follow_link( text => $logo ); $m->content_is(ImageFileContent, "it links to the uploaded image"); rt-5.0.1/t/articles/queue-specific-class.t000644 000765 000024 00000012123 14005011336 021250 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ( $url, $m ) = RT::Test->started_ok; diag "Running server at: $url"; $m->login; my %class = map { $_ => '' } qw/foo bar/; diag "create classes foo and bar" if $ENV{TEST_VERBOSE}; for my $name ( keys %class ) { $m->get_ok( '/Admin/Articles/Classes/Modify.html?Create=1', 'class create page' ); $m->submit_form( form_number => 3, fields => { Name => $name }, ); $m->content_contains( "Modify the Class $name", 'created class $name' ); my ($id) = ( $m->content =~ /name="id" value="(\d+)"/ ); ok( $id, "id of $name" ); $class{$name} = $id; } diag "create articles in foo and bar" if $ENV{TEST_VERBOSE}; for my $name ( keys %class ) { $m->get_ok( '/Articles/Article/Edit.html?Class=' . $class{$name}, 'article create page' ); $m->submit_form( form_number => 3, fields => { Name => "article $name", 'Summary' => "$name summary", 'Object-RT::Article--CustomField-1-Values' => "$name content"} ); $m->content_like( qr/Article \d+ created/, "created article $name" ); } diag "apply foo to queue General" if $ENV{TEST_VERBOSE}; { $m->get_ok( '/Admin/Articles/Classes/Objects.html?id=' . $class{foo}, 'apply page' ); $m->submit_form( form_number => 3, fields => { 'AddClass-' . $class{foo} => 1 }, button => 'UpdateObjs', ); $m->content_contains( 'Object created', 'applied foo to General' ); } my $ticket_id; diag "create ticket in General" if $ENV{TEST_VERBOSE}; { $m->get_ok( '/Ticket/Create.html?Queue=1', 'ticket create page' ); $m->submit_form( form_number => 3, fields => { 'Subject' => 'test article', Content => 'test article' }, button => 'SubmitTicket', ); ($ticket_id) = ( $m->content =~ /Ticket \d+ created/ ); ok( $ticket_id, "id of ticket: $ticket_id" ); } diag "update ticket to see if there is article foo" if $ENV{TEST_VERBOSE}; { $m->get_ok( '/Ticket/Update.html?Action=Comment&id=' . $ticket_id, 'ticket update page' ); $m->content_contains( 'article foo:', 'got article foo in dropdown' ); $m->content_lacks( 'article bar:', 'no article bar in dropdown' ); } diag "apply bar to globally" if $ENV{TEST_VERBOSE}; { $m->get_ok( '/Admin/Articles/Classes/Objects.html?id=' . $class{bar}, 'apply page' ); $m->submit_form( form_number => 3, fields => { 'AddClass-' . $class{bar} => 0 }, button => 'UpdateObjs', ); $m->content_contains( 'Object created', 'applied bar globally' ); } diag "update ticket to see if there are both article foo and bar" if $ENV{TEST_VERBOSE}; { $m->get_ok( '/Ticket/Update.html?Action=Comment&id=' . $ticket_id, 'ticket update page' ); $m->content_contains( 'article foo:', 'got article foo in dropdown' ); $m->content_contains( 'article bar:', 'got article bar in dropdown' ); $m->submit_form( form_number => 3, fields => { 'IncludeArticleId' => '1' }, ); $m->content_like( qr/foo summary/s, 'article included' ); } diag "remove both foo and bar" if $ENV{TEST_VERBOSE}; { $m->get_ok( '/Admin/Articles/Classes/Objects.html?id=' . $class{foo}, 'apply page' ); $m->submit_form( form_number => 3, fields => { 'RemoveClass-' . $class{foo} => 1 }, button => 'UpdateObjs', ); $m->content_contains( 'Object deleted', 'removed foo' ); $m->get_ok( '/Admin/Articles/Classes/Objects.html?id=' . $class{bar}, 'apply page' ); $m->submit_form( form_number => 3, fields => { 'RemoveClass-' . $class{bar} => 0 }, button => 'UpdateObjs', ); $m->content_contains( 'Object deleted', 'removed bar' ); } diag "update ticket to see if there are both article foo and bar" if $ENV{TEST_VERBOSE}; { $m->get_ok( '/Ticket/Update.html?Action=Comment&id=' . $ticket_id, 'ticket update page' ); $m->content_lacks( 'article foo:', 'no article foo in hotlist' ); $m->content_lacks( 'article bar:', 'no article bar in hotlist' ); $m->submit_form( form_number => 3, fields => { 'Articles_Content' => 'article' }, ); $m->content_lacks( 'article foo', 'no article foo' ); $m->content_lacks( 'article bar', 'no article bar' ); $m->get_ok( '/Ticket/Update.html?Action=Comment&id=' . $ticket_id, 'ticket update page' ); $m->submit_form( form_number => 3, fields => { 'Articles-Include-Article-Named' => 'article foo' }, ); $m->content_lacks( 'article foo', 'no article foo' ); $m->content_lacks( 'article bar', 'no article bar' ); $m->get_ok( '/Ticket/Update.html?Action=Comment&id=' . $ticket_id, 'ticket update page' ); $m->submit_form( form_number => 3, fields => { 'Articles-Include-Article-Named' => 'article bar' }, ); $m->content_lacks( 'article foo', 'no article foo' ); $m->content_lacks( 'article bar', 'no article bar' ); } done_testing(); rt-5.0.1/t/articles/uri-a.t000644 000765 000024 00000003734 14005011336 016263 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 15; use_ok("RT::URI::a"); my $uri = RT::URI::a->new($RT::SystemUser); ok(ref($uri), "URI object exists"); my $class = RT::Class->new( $RT::SystemUser ); $class->Create( Name => 'URItest - '. $$ ); ok $class->id, 'created a class'; my $article = RT::Article->new( $RT::SystemUser ); my ($id, $msg) = $article->Create( Name => 'Testing URI parsing - '. $$, Summary => 'In which this should load', Class => $class->Id ); ok($id,$msg); my $uristr = "a:" . $article->Id; $uri->ParseURI($uristr); 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"; } rt-5.0.1/t/articles/uri-articles.t000644 000765 000024 00000002362 14005011336 017645 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use Test::Warn; use_ok "RT::URI::fsck_com_article"; my $uri = RT::URI::fsck_com_article->new( $RT::SystemUser ); ok $uri; isa_ok $uri, 'RT::URI::fsck_com_article'; isa_ok $uri, 'RT::URI::base'; isa_ok $uri, 'RT::Base'; is $uri->LocalURIPrefix, 'fsck.com-article://example.com'; my $class = RT::Class->new( $RT::SystemUser ); $class->Create( Name => 'URItest - '. $$ ); ok $class->id, 'created a class'; my $article = RT::Article->new( $RT::SystemUser ); my ($id, $msg) = $article->Create( Name => 'Testing URI parsing - '. $$, Summary => 'In which this should load', Class => $class->Id ); ok($id,$msg); $uri = RT::URI::fsck_com_article->new( $article->CurrentUser ); is $uri->URIForObject( $article ), 'fsck.com-article://example.com/article/' . $article->id, 'Got correct URIForObject'; my $article_id = $article->Id; ok ($uri->ParseURI("fsck.com-article://example.com/article/$article_id"), 'Parsed URI'); ok ($article->Delete(), 'Deleted article'); ok($article->Disabled, 'deleted article is actually just disabled'); ok (!$uri->ParseURI("fsck.com-article://foo.com/article/$article_id"), 'ParseURI returned false with incorrect Organization'); done_testing(); rt-5.0.1/t/articles/search-interface.t000644 000765 000024 00000015116 14005011336 020446 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use RT::CustomField; use RT::Queue; use RT::Ticket; use_ok 'RT::Class'; use_ok 'RT::Topic'; use_ok 'RT::Article'; my ($url, $m) = RT::Test->started_ok; # Variables to test return values my ($ret, $msg); # Create two classes my $class = RT::Class->new($RT::SystemUser); ($ret, $msg) = $class->Create('Name' => 'First-class', 'Description' => 'A general-purpose test class'); ok($ret, "Test class created"); ($ret, $msg) = $class->Create('Name' => 'Second-class', 'Description' => 'Another class'); ok($ret, "Test class created"); my $questionCF = RT::CustomField->new($RT::SystemUser); my $answerCF = RT::CustomField->new($RT::SystemUser); my $ticketCF = RT::CustomField->new($RT::SystemUser); ($ret, $msg) = $questionCF->Create('Name' => 'Question-'.$$, 'Type' => 'Text', 'MaxValues' => 1, 'LookupType' => 'RT::Class-RT::Article', 'Description' => 'The question to be answered', 'Disabled' => 0); ok($ret, "Question CF created: $msg"); ($ret, $msg) = $answerCF->Create('Name' => 'Answer-'.$$, 'Type' => 'Text', 'MaxValues' => 1, 'LookupType' => 'RT::Class-RT::Article', 'Description' => 'The answer to the question', 'Disabled' => 0); ok($ret, "Answer CF created: $msg"); ($ret, $msg) = $ticketCF->Create('Name' => 'Class', 'Type' => 'Text', 'MaxValues' => 1, 'LookupType' => 'RT::Queue-RT::Ticket', 'Disabled' => 0); ok($ret, "Ticket CF 'Class' created: $msg"); # Attach the custom fields to our class ($ret, $msg) = $questionCF->AddToObject($class); ok($ret, "Question CF added to class: $msg"); ($ret, $msg) = $answerCF->AddToObject($class); ok($ret, "Answer CF added to class: $msg"); my ($qid, $aid) = ($questionCF->Id, $answerCF->Id); my $global_queue = RT::Queue->new($RT::SystemUser); ($ret, $msg) = $ticketCF->AddToObject($global_queue); ok($ret, "Ticket CF added globally: $msg"); my %cvals = ('article1q' => 'Some question about swallows', 'article1a' => 'Some answer about Europe and Africa', 'article2q' => 'Another question about Monty Python', 'article2a' => 'Romani ite domum', 'article3q' => 'Why should I eat my supper?', 'article3a' => 'There are starving children in Africa', 'article4q' => 'What did Brian originally write?', '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. my $article1 = RT::Article->new($RT::SystemUser); my $article2 = RT::Article->new($RT::SystemUser); my $article3 = RT::Article->new($RT::SystemUser); my $article4 = RT::Article->new($RT::SystemUser); ($ret, $msg) = $article1->Create(Name => 'First article '.$$, Summary => 'blah blah 1', Class => $class->Id, "CustomField-$qid" => $cvals{'article1q'}, "CustomField-$aid" => $cvals{'article1a'}, ); ok($ret, "article 1 created"); ($ret, $msg) = $article2->Create(Name => 'Second article '.$$, Summary => 'foo bar 2', Class => $class->Id, "CustomField-$qid" => $cvals{'article2q'}, "CustomField-$aid" => $cvals{'article2a'}, ); ok($ret, "article 2 created"); ($ret, $msg) = $article3->Create(Name => 'Third article '.$$, Summary => 'ping pong 3', Class => $class->Id, "CustomField-$qid" => $cvals{'article3q'}, "CustomField-$aid" => $cvals{'article3a'}, ); ok($ret, "article 3 created"); ($ret, $msg) = $article4->Create(Name => 'Fourth article '.$$, Summary => 'hoi polloi 4', Class => $class->Id, "CustomField-$qid" => $cvals{'article4q'}, "CustomField-$aid" => $cvals{'article4a'}, ); ok($ret, "article 4 created"); isa_ok($m, 'Test::WWW::Mechanize'); ok($m->login, 'logged in'); $m->follow_link_ok( { text => 'Articles', url_regex => qr!^/Articles/! }, 'UI -> Articles' ); # 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('Last Updated'); # 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('Last Updated'); # 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('Last Updated'); # Did we do a search? $m->text_contains('hoi polloi 4'); # Case insensitive search on large field. DoArticleSearch($m, $class->Name, 'lorem'); TODO:{ local $TODO = 'Case insensitive search on LONGBLOB not available in MySQL' if RT->Config->Get('DatabaseType') eq 'mysql'; $m->text_contains('Last Updated'); # Did we do a search? $m->text_contains('hoi polloi 4'); } done_testing; # 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 => 'Articles'}, 'Articles Search'); $m->follow_link_ok( {text => $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; } rt-5.0.1/t/articles/basic-api.t000644 000765 000024 00000005607 14005011336 017077 0ustar00sunnavystaff000000 000000 use warnings; use strict; use RT::Test tests => 37; use_ok('RT::Class'); my $class = RT::Class->new($RT::SystemUser); isa_ok($class, 'RT::Class'); isa_ok($class, 'RT::Record'); isa_ok($class, 'RT::Record'); my $name = 'test-'.$$; my ($id,$msg) = $class->Create( Name =>$name, Description => 'Test class'); ok($id,$msg); is ($class->Name, $name); is ($class->Description, 'Test class'); # Test custom fields. can_ok($class, 'CustomFields'); can_ok($class, 'AddCustomFieldValue'); can_ok($class, 'DeleteCustomFieldValue'); can_ok($class, 'FirstCustomFieldValue'); can_ok($class, 'CustomFieldValues'); can_ok($class, 'CurrentUserHasRight'); # Add a custom field to our class my $cf = RT::CustomField->new($RT::SystemUser); isa_ok($cf, 'RT::CustomField'); # the following tests don't expect to see the new pre-defined "Content" cf. # disabling the pre-defined one so we can test if old behavior still works $cf->Load(1); $cf->SetDisabled(1); $cf = RT::CustomField->new($RT::SystemUser); ($id,$msg) = $cf->Create( Name => 'Articles::Sample-'.$$, Description => 'Test text cf', LookupType => RT::Article->CustomFieldLookupType, Type => 'Text' ); ok($id,$msg); ($id,$msg) = $cf->AddToObject($class); ok ($id,$msg); # Does our class have a custom field? my $cfs = $class->ArticleCustomFields; isa_ok($cfs, 'RT::CustomFields'); is($cfs->Count, 1, "We only have one custom field"); my $found_cf = $cfs->First; is ($cf->id, $found_cf->id, "it's the right one"); ($id,$msg) = $cf->RemoveFromObject($class); is($class->ArticleCustomFields->Count, 0, "All gone!"); # Put it back. we want to go forward. ($id,$msg) = $cf->AddToObject($class); ok ($id,$msg); use_ok('RT::Article'); my $art = RT::Article->new($RT::SystemUser); ($id,$msg) =$art->Create(Class => $class->id, Name => 'Sample'.$$, Description => 'A sample article'); ok($id,"Created article ".$id." - " .$msg); # make sure there is one transaction. my $txns = $art->Transactions; is($txns->Count, 1, "One txn"); my $txn = $txns->First; is ($txn->ObjectType, 'RT::Article'); is ($txn->ObjectId , $id , "It's the right article"); is ($txn->Type, 'Create', "It's a create!"); my $art_cfs = $art->CustomFields(); is ($art_cfs->Count, 1, "It has a custom field"); my $values = $art->CustomFieldValues($art_cfs->First); is ($values->Count, 0); $art->AddCustomFieldValue(Field => $cf->id, Value => 'Testing'); $values = $art->CustomFieldValues($art_cfs->First->id); # We added the custom field is ($values->Count, 1); is ($values->First->Content, 'Testing', "We added the CF"); is ($art->Transactions->Count,2, "We added a second transaction for the custom field addition"); my $txn2 = $art->Transactions->Last; is ($txn2->ObjectId, $art->id); is ($txn2->id, ($txn->id +1)); is ($txn2->Type, 'CustomField'); is($txn2->NewValue, 'Testing'); ok (!$txn2->OldValue, "It had no old value"); 1; rt-5.0.1/t/articles/interface.t000644 000765 000024 00000023143 14005011336 017202 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 53; use RT::CustomField; use RT::EmailParser; use RT::Queue; use RT::Ticket; use_ok 'RT::Class'; use_ok 'RT::Topic'; use_ok 'RT::Article'; my ($url, $m) = RT::Test->started_ok; # Variables to test return values my ($ret, $msg); # Create a test class my $class = RT::Class->new($RT::SystemUser); ($ret, $msg) = $class->Create('Name' => 'tlaTestClass-'.$$, 'Description' => 'A general-purpose test class'); ok($ret, "Test class created"); my $class2 = RT::Class->new($RT::SystemUser); ($ret, $msg) = $class2->Create('Name' => 'tlaTestClass2-'.$$, 'Description' => 'Another general-purpose test class'); ok($ret, "Test class 2 created"); # Create a hierarchy of test topics my $topic1 = RT::Topic->new($RT::SystemUser); my $topic11 = RT::Topic->new($RT::SystemUser); my $topic12 = RT::Topic->new($RT::SystemUser); my $topic2 = RT::Topic->new($RT::SystemUser); my $topic_class2= RT::Topic->new($RT::SystemUser); my $gtopic = RT::Topic->new($RT::SystemUser); ($ret, $msg) = $topic1->Create('Parent' => 0, 'Name' => 'tlaTestTopic1-'.$$, 'ObjectType' => 'RT::Class', 'ObjectId' => $class->Id); ok($ret, "Topic 1 created"); ($ret, $msg) = $topic11->Create('Parent' => $topic1->Id, 'Name' => 'tlaTestTopic1.1-'.$$, 'ObjectType' => 'RT::Class', 'ObjectId' => $class->Id); ok($ret, "Topic 1.1 created"); ($ret, $msg) = $topic12->Create('Parent' => $topic1->Id, 'Name' => 'tlaTestTopic1.2-'.$$, 'ObjectType' => 'RT::Class', 'ObjectId' => $class->Id); ok($ret, "Topic 1.2 created"); ($ret, $msg) = $topic2->Create('Parent' => 0, 'Name' => 'tlaTestTopic2-'.$$, 'ObjectType' => 'RT::Class', 'ObjectId' => $class->Id); ok($ret, "Topic 2 created"); ($ret, $msg) = $topic_class2->Create('Parent' => 0, 'Name' => 'tlaTestTopicClass2-'.$$, 'ObjectType' => 'RT::Class', 'ObjectId' => $class2->Id); ok($ret, "Topic Class2 created"); ($ret, $msg) = $gtopic->Create('Parent' => 0, 'Name' => 'tlaTestTopicGlobal-'.$$, 'ObjectType' => 'RT::System', 'ObjectId' => $RT::System->Id ); ok($ret, "Global Topic created"); # Create some article custom fields my $questionCF = RT::CustomField->new($RT::SystemUser); my $answerCF = RT::CustomField->new($RT::SystemUser); ($ret, $msg) = $questionCF->Create('Name' => 'Question-'.$$, 'Type' => 'Text', 'MaxValues' => 1, 'LookupType' => 'RT::Class-RT::Article', 'Description' => 'The question to be answered', 'Disabled' => 0); ok($ret, "Question CF created: $msg"); ($ret, $msg) = $answerCF->Create('Name' => 'Answer-'.$$, 'Type' => 'Text', 'MaxValues' => 1, 'LookupType' => 'RT::Class-RT::Article', 'Description' => 'The answer to the question', 'Disabled' => 0); ok($ret, "Answer CF created: $msg"); # Attach the custom fields to our class ($ret, $msg) = $questionCF->AddToObject($class); ok($ret, "Question CF added to class: $msg"); ($ret, $msg) = $answerCF->AddToObject($class); ok($ret, "Answer CF added to class: $msg"); my ($qid, $aid) = ($questionCF->Id, $answerCF->Id); my %cvals = ('article1q' => 'Some question about swallows', 'article1a' => 'Some answer about Europe and Africa', 'article2q' => 'Another question about Monty Python', 'article2a' => 'Romani ite domum', 'article3q' => 'Why should I eat my supper?', 'article3a' => 'There are starving children in Africa', 'article4q' => 'What did Brian originally write?', 'article4a' => 'Romanes eunt domus'); # Create an article or two with our custom field values. my $article1 = RT::Article->new($RT::SystemUser); my $article2 = RT::Article->new($RT::SystemUser); my $article3 = RT::Article->new($RT::SystemUser); my $article4 = RT::Article->new($RT::SystemUser); ($ret, $msg) = $article1->Create(Name => 'First article '.$$, Summary => 'blah blah 1', Class => $class->Id, Topics => [$topic1->Id], "CustomField-$qid" => $cvals{'article1q'}, "CustomField-$aid" => $cvals{'article1a'}, ); ok($ret, "article 1 created"); ($ret, $msg) = $article2->Create(Name => 'Second article '.$$, Summary => 'foo bar 2', Class => $class->Id, Topics => [$topic11->Id], "CustomField-$qid" => $cvals{'article2q'}, "CustomField-$aid" => $cvals{'article2a'}, ); ok($ret, "article 2 created"); ($ret, $msg) = $article3->Create(Name => 'Third article '.$$, Summary => 'ping pong 3', Class => $class->Id, Topics => [$topic12->Id], "CustomField-$qid" => $cvals{'article3q'}, "CustomField-$aid" => $cvals{'article3a'}, ); ok($ret, "article 3 created"); ($ret, $msg) = $article4->Create(Name => 'Fourth article '.$$, Summary => 'hoi polloi 4', Class => $class->Id, Topics => [$topic2->Id], "CustomField-$qid" => $cvals{'article4q'}, "CustomField-$aid" => $cvals{'article4a'}, ); ok($ret, "article 4 created"); # Create a ticket. my $parser = RT::EmailParser->new(); $parser->ParseMIMEEntityFromScalar('From: root@localhost To: rt@example.com Subject: test ticket for articles This is some form of new request. May as well say something about Africa.'); my $ticket = RT::Ticket->new($RT::SystemUser); my $obj; ($ret, $obj, $msg) = $ticket->Create(Queue => 'General', Subject => 'test ticket for articles '.$$, MIMEObj => $parser->Entity); ok($ret, "Test ticket for articles created: $msg"); #### Right. That's our data. Now begin the real testing. isa_ok($m, 'Test::WWW::Mechanize'); ok($m->login, 'logged in'); $m->follow_link_ok( { text => 'Articles', url_regex => qr!^/Articles/index.html! }, 'UI -> Articles' ); $m->content_contains($article3->Name); $m->follow_link_ok( {text => $article3->Name}, 'Articles -> '. $article3->Name ); $m->title_is("Article #" . $article3->Id . ": " . $article3->Name); $m->follow_link_ok( { text => 'Modify'}, 'Article -> Modify' ); { $m->content_like(qr/Refers to/, "found links edit box"); my $ticket_id = $ticket->Id; my $turi = "t:$ticket_id"; my $a1uri = 'a:'.$article1->Id; $m->submit_form(form_name => 'EditArticle', fields => { $article3->Id.'-RefersTo' => $turi, 'RefersTo-'.$article3->Id => $a1uri } ); $m->content_like(qr/Ticket.*$ticket_id/, "Ticket linkto was created"); $m->content_like(qr/URI.*$a1uri/, "Article linkfrom was created"); } # Now try to extract an article from a link. $m->get_ok($url."/Ticket/Display.html?id=".$ticket->Id, "Loaded ticket display"); $m->content_like(qr/Extract Article/, "Article extraction link shows up"); $m->follow_link_ok( { text => 'Extract Article' }, '-> Extract Article' ); $m->content_contains($class->Name); $m->follow_link_ok( { text => $class->Name }, 'Extract Article -> '. $class->Name ); $m->content_like(qr/Select topics for this article/i, 'selecting topic'); $m->form_number(3); $m->set_visible([option => $topic1->Name]); $m->submit; $m->form_number(3); $m->set_visible([option => $answerCF->Name]); $m->click(); $m->title_like(qr/Create a new article/, "got edit page from extraction"); $m->submit_form(form_name => 'EditArticle'); $m->title_like(qr/Modify article/); $m->follow_link_ok( { text => 'Display' }, '-> Display' ); $m->content_like(qr/Africa/, "Article content exist"); $m->content_contains($ticket->Subject, "Article references originating ticket"); diag("Test creating a ticket in Class2 and make sure we don't see Class1 Topics") if $ENV{TEST_VERBOSE}; { $m->follow_link_ok( { text => 'Articles', url_regex => qr!^/Articles/! }, 'UI -> Articles' ); $m->follow_link_ok( {text => 'Create' }, 'Articles -> New Article' ); $m->follow_link_ok( {text => 'in class '.$class2->Name }, 'New Article -> in class '.$class2->Name ); $m->content_lacks( $topic1->Name, "Topic1 from Class1 isn't shown" ); $m->content_lacks( $topic11->Name, "Topic11 from Class1 isn't shown" ); $m->content_lacks( $topic12->Name, "Topic12 from Class1 isn't shown" ); $m->content_lacks( $topic2->Name, "Topic2 from Class1 isn't shown" ); $m->content_contains( $gtopic->Name, "Global Topic is shown" ); $m->content_contains( $topic_class2->Name, "Class2 topic is shown" ); } rt-5.0.1/t/articles/cfsearch.t000644 000765 000024 00000005441 14005011336 017021 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 11; my $suffix = '-'. $$; use_ok 'RT::Class'; use_ok 'RT::Article'; use_ok 'RT::CustomField'; my $classname = 'TestClass'; my $class = RT::Class->new( $RT::SystemUser ); { $class->Load( $classname ); unless ( $class->Id ) { my ($id, $msg) = $class->Create( Name => $classname, Description => 'class for cf tests', ); ok $id, "created class '$classname' #$id" or diag "error: $msg"; } else { ok 1, "class '$classname' exists"; } } # create cf my $cfname = 'TestCF'. $suffix; my $cf = RT::CustomField->new( $RT::SystemUser ); { my ($id, $msg) = $cf->Create( Name => $cfname, LookupType => 'RT::Class-RT::Article', Type => 'Select', MaxValues => 1, Description => 'singleselect cf for tests', ); ok $id, "created cf '$cfname' #$id" or diag "error: $msg"; } # attach cf to class { my ($status, $msg) = $cf->AddToObject( $class ); ok $status, "attached the cf to the class" or diag "error: $msg"; } # create two cf-values { my ($status, $msg) = $cf->AddValue( Name => 'Value1' ); ok $status, "added a value to the cf" or diag "error: $msg"; ($status, $msg) = $cf->AddValue( Name => 'Value2' ); ok $status, "added a value to the cf" or diag "error: $msg"; } my $article1name = 'TestArticle1'.$suffix; my $article1 = RT::Article->new($RT::SystemUser); $article1->Create( Name => $article1name, Summary => 'Test', Class => $class->Id); $article1->AddCustomFieldValue(Field => $cf->Id, Value => 'Value1'); my $article2name = 'TestArticle2'.$suffix; my $article2 = RT::Article->new($RT::SystemUser); $article2->Create( Name => $article2name, Summary => 'Test', Class => $class->Id); $article2->AddCustomFieldValue(Field => $cf->Id, Value => 'Value2'); # search for articles containing 1st value { my $articles = RT::Articles->new( $RT::SystemUser ); $articles->UnLimit; $articles->Limit( FIELD => "Class", SUBCLAUSE => 'ClassMatch', VALUE => $class->Id); $articles->LimitCustomField( FIELD => $cf->Id, VALUE => 'Value1' ); is $articles->Count, 1, 'found correct number of articles'; } { my $articles = RT::Articles->new($RT::SystemUser); $articles->UnLimit; $articles->Limit( FIELD => "Class", SUBCLAUSE => 'ClassMatch', VALUE => $class->Id); $articles->LimitCustomField( FIELD => $cf, VALUE => 'Value1' ); is $articles->Count, 1, 'found correct number of articles'; } { my $articles = RT::Articles->new($RT::SystemUser); $articles->UnLimit( ); $articles->Limit( FIELD => "Class", SUBCLAUSE => 'ClassMatch', VALUE => $class->Id); $articles->LimitCustomField( FIELD => $cf->Name, VALUE => 'Value1' ); is $articles->Count, 1, 'found correct number of articles'; } rt-5.0.1/t/articles/articles.t000644 000765 000024 00000010534 14005011336 017050 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 29; use_ok 'RT::Articles'; use_ok 'RT::Classes'; use_ok 'RT::Class'; my $class = RT::Class->new($RT::SystemUser); my ( $id, $msg ) = $class->Create( Name => 'CollectionTest-' . $$ ); ok( $id, $msg ); # Add a custom field to our class use_ok('RT::CustomField'); my $cf = RT::CustomField->new($RT::SystemUser); isa_ok($cf, 'RT::CustomField'); ($id,$msg) = $cf->Create( Name => 'Articles::Sample-'.$$, Description => 'Test text cf', LookupType => RT::Article->CustomFieldLookupType, Type => 'Freeform' ); ok($id,$msg); ($id,$msg) = $cf->AddToObject($class); ok ($id,$msg); my $art = RT::Article->new($RT::SystemUser); ( $id, $msg ) = $art->Create( Class => $class->id, Name => 'Collection-1-' . $$, Summary => 'Coll-1-' . $$, 'CustomField-'.$cf->Name => 'Test-'.$$ ); ok( $id, $msg ); my $arts = RT::Articles->new($RT::SystemUser); $arts->LimitName( VALUE => 'Collection-1-' . $$ . 'fake' ); is( $arts->Count, 0, "Found no artlcles with names matching something that is not there" ); my $arts2 = RT::Articles->new($RT::SystemUser); $arts2->LimitName( VALUE => 'Collection-1-' . $$ ); is( $arts2->Count, 1, 'Found one with names matching the word "test"' ); $arts = RT::Articles->new($RT::SystemUser); $arts->LimitSummary( VALUE => 'Coll-1-' . $$ . 'fake' ); is( $arts->Count, 0, 'Found no artlcles with summarys matching something that is not there' ); $arts2 = RT::Articles->new($RT::SystemUser); $arts2->LimitSummary( VALUE => 'Coll-1-' . $$ ); is( $arts2->Count, 1, 'Found one with summarys matching the word "Coll-1"' ); my $new_art = RT::Article->new($RT::SystemUser); ( $id, $msg ) = $new_art->Create( Class => $class->id, Name => 'CFSearchTest1' . $$, 'CustomField-'.$cf->Name => 'testing' . $$ ); ok( $id, $msg . " Created a second testable article" ); $arts = RT::Articles->new($RT::SystemUser); $arts->LimitCustomField( OPERATOR => 'LIKE', VALUE => "esting".$$ ); is( $arts->Count, 1, "Found 1 cf values matching 'esting" . $$ . "' for an unspecified field"); $arts = RT::Articles->new($RT::SystemUser); $arts->LimitCustomField( OPERATOR => '=', VALUE => "esting".$$ ); is( $arts->Count, 0, "Found 0 cf values EXACTLY matching 'esting" . $$ . "' for an unspecified field"); $arts = RT::Articles->new($RT::SystemUser); $arts->LimitCustomField( OPERATOR => '=', VALUE => "testing".$$ ); is( $arts->Count, 1, "Found 0 cf values EXACTLY matching 'testing" . $$ . "' for an unspecified field"); $arts = RT::Articles->new($RT::SystemUser); $arts->LimitCustomField( OPERATOR => 'LIKE', VALUE => $$ ); is( $arts->Count, 2, "Found 1 cf values matching '" . $$ . "' for an unspecified field"); # Test searching on named custom fields $arts = RT::Articles->new($RT::SystemUser); $arts->LimitCustomField( OPERATOR => 'LIKE', VALUE => $$, FIELD => $cf->Name ); is( $arts->Count, 2, "Found 1 Article with cf values matching '".$$."' for CF named " .$cf->Name); $arts = RT::Articles->new($RT::SystemUser); $arts->LimitCustomField( OPERATOR => 'LIKE', VALUE => $$, FIELD => 'NO-SUCH-CF' ); is( $arts->Count,0, "Found no cf values matching '".$$."' for CF 'NO-SUCH-CF' " ); $arts = RT::Articles->new($RT::SystemUser); $arts->Limit(FIELD =>'Class', VALUE => $class->id); $arts->LimitCustomField( OPERATOR => 'NOT LIKE', VALUE => 'blah', FIELD => $cf->id ); is( $arts->Count ,2, "Found 1 articles with custom field values not matching blah"); $arts = RT::Articles->new($RT::SystemUser); $arts->Limit(FIELD =>'Class', VALUE => $class->id); $arts->LimitCustomField( OPERATOR => 'NOT LIKE', VALUE => 'est', FIELD => $cf->id ); is( $arts->Count , 0, "Found 0 cf values not matching 'est' for CF ".$cf->id. " " . join(',', map {$_->id} @{$arts->ItemsArrayRef})); $arts = RT::Articles->new($RT::SystemUser); $arts->Limit(FIELD =>'Class', VALUE => $class->id); $arts->LimitCustomField( OPERATOR => 'NOT LIKE', VALUE => 'BOGUS', FIELD => $cf->id ); is( $arts->Count , 2, "Found 2 articles not matching 'BOGUS' for CF ".$cf->id); my $ac = RT::Articles->new($RT::SystemUser); ok( $ac->isa('RT::Articles') ); ok( $ac->isa('DBIx::SearchBuilder') ); ok( $ac->LimitRefersTo('http://dead.link') ); is( $ac->Count, 0 ); $ac = RT::Articles->new($RT::SystemUser); ok( $ac->LimitReferredToBy('http://dead.link') ); is( $ac->Count, 0 ); rt-5.0.1/t/articles/class.t000644 000765 000024 00000004714 14005011336 016352 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use_ok 'RT::Articles'; use_ok 'RT::Classes'; use_ok 'RT::Class'; my $root = RT::CurrentUser->new('root'); ok ($root->Id, "Loaded root"); my $cl = RT::Class->new($root); ok (UNIVERSAL::isa($cl, 'RT::Class'), "the new class is a class"); my ($id, $msg) = $cl->Create(Name => 'Test-'.$$, Description => 'A test class'); ok ($id, $msg); ok( $cl->SetName( 'test-' . $$ ), 'rename to lower cased version' ); ok( $cl->SetName( 'Test-' . $$ ), 'rename back' ); # no duplicate class names should be allowed ($id, $msg) = RT::Class->new($root)->Create(Name => 'Test-'.$$, Description => 'A test class'); ok (!$id, $msg); ($id, $msg) = RT::Class->new($root)->Create(Name => 'test-'.$$, Description => 'A test class'); ok (!$id, $msg); #class name should be required ($id, $msg) = RT::Class->new($root)->Create(Name => '', Description => 'A test class'); ok (!$id, $msg); $cl->Load('Test-'.$$); ok($cl->id, "Loaded the class we want"); # Create a new user. make sure they can't create a class my $u= RT::User->new(RT->SystemUser); $u->Create(Name => "ArticlesTest".time, Privileged => 1); ok ($u->Id, "Created a new user"); # Make sure you can't create a group with no acls $cl = RT::Class->new($u); ok (UNIVERSAL::isa($cl, 'RT::Class'), "the new class is a class"); ($id, $msg) = $cl->Create(Name => 'Test-nobody'.$$, Description => 'A test class'); ok (!$id, $msg. "- Can not create classes as a random new user - " .$u->Id); $u->PrincipalObj->GrantRight(Right =>'AdminClass', Object => RT->System); ($id, $msg) = $cl->Create(Name => 'Test-nobody-'.$$, Description => 'A test class'); ok ($id, $msg. "- Can create classes as a random new user after ACL grant"); # now check the Web UI my ($url, $m) = RT::Test->started_ok; ok($m->login); $m->get_ok("$url/Admin/Articles/Classes/Modify.html?Create=1"); $m->content_contains('Create a Class', 'found title'); $m->submit_form_ok({ form_number => 3, fields => { Name => 'Test Redirect' }, }); $m->content_contains('Object created', 'found results'); $m->content_contains('Modify the Class Test Redirect', 'found title'); $m->form_number(3); $m->untick( 'Include-Name', 1 ); $m->field( 'Description', 'Test Description' ); $m->submit(); $m->content_like(qr/Description changed from.*no value.*to .*Test Description/,'description changed'); $m->form_number(3); is($m->current_form->find_input('Include-Name')->value,undef,'Disabled Including Names for this Class'); done_testing(); rt-5.0.1/t/articles/article.t000644 000765 000024 00000014677 14005011336 016701 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 67; use_ok 'RT::Articles'; use_ok 'RT::Classes'; use_ok 'RT::Class'; my $CLASS = 'ArticleTest-'.$$; my $user = RT::CurrentUser->new('root'); my $class = RT::Class->new($user); my ($id, $msg) = $class->Create(Name =>$CLASS); ok ($id, $msg); my $article = RT::Article->new($user); ok (UNIVERSAL::isa($article, 'RT::Article')); ok (UNIVERSAL::isa($article, 'RT::Record')); ok (UNIVERSAL::isa($article, 'RT::Record')); ok (UNIVERSAL::isa($article, 'DBIx::SearchBuilder::Record') , "It's a searchbuilder record!"); ($id, $msg) = $article->Create( Class => $CLASS, Summary => $CLASS); ok ($id, $msg); $article->Load($id); is ($article->Summary, $CLASS, "The summary is set correct"); my $at = RT::Article->new($RT::SystemUser); $at->Load($id); is ($at->id , $id); is ($at->Summary, $article->Summary); my $a1 = RT::Article->new($RT::SystemUser); ($id, $msg) = $a1->Create(Class => $class->id, Name => 'ValidateNameTest'.$$); ok ($id, $msg); my $a2 = RT::Article->new($RT::SystemUser); ($id, $msg) = $a2->Create(Class => $class->id, Name => 'ValidateNameTest'.$$); ok (!$id, $msg); my $a3 = RT::Article->new($RT::SystemUser); ($id, $msg) = $a3->Create(Class => $class->id, Name => 'ValidateNameTest2'.$$); ok ($id, $msg); ($id, $msg) =$a3->SetName('ValidateNameTest'.$$); ok (!$id, $msg); ($id, $msg) =$a3->SetName('ValidateNametest2'.$$); ok ($id, $msg); my $newart = RT::Article->new($RT::SystemUser); $newart->Create(Name => 'DeleteTest'.$$, Class => '1'); $id = $newart->Id; ok($id, "New article has an id"); $article = RT::Article->new($RT::SystemUser); $article->Load($id); ok ($article->Id, "Found the article"); my $val; ($val, $msg) = $article->Delete(); ok ($val, "Article Deleted: $msg"); $a2 = RT::Article->new($RT::SystemUser); $a2->Load($id); ok ($a2->Disabled, "the article is disabled"); # NOT OK #$RT::Handle->SimpleQuery("DELETE FROM Links"); my $article_a = RT::Article->new($RT::SystemUser); ($id, $msg) = $article_a->Create( Class => $CLASS, Summary => "ArticleTestlink1".$$); ok($id,$msg); my $article_b = RT::Article->new($RT::SystemUser); ($id, $msg) = $article_b->Create( Class => $CLASS, Summary => "ArticleTestlink2".$$); ok($id,$msg); # Create a link between two articles ($id, $msg) = $article_a->AddLink( Type => 'RefersTo', Target => $article_b->URI); ok($id,$msg); # Make sure that Article Bs "ReferredToBy" links object refers to to this article my $refers_to_b = $article_b->ReferredToBy; is($refers_to_b->Count, 1, "Found one thing referring to b"); my $first = $refers_to_b->First; ok ($first->isa('RT::Link'), "IT's an RT link - ref ".ref($first) ); is($first->TargetObj->Id, $article_b->Id, "Its target is B"); ok($refers_to_b->First->BaseObj->isa('RT::Article'), "Yep. its an article"); # Make sure that Article A's "RefersTo" links object refers to this article" my $referred_To_by_a = $article_a->RefersTo; is($referred_To_by_a->Count, 1, "Found one thing referring to b ".$referred_To_by_a->Count. "-".$referred_To_by_a->First->id . " - ".$referred_To_by_a->Last->id); $first = $referred_To_by_a->First; ok ($first->isa('RT::Link'), "IT's an RT link - ref ".ref($first) ); is ($first->TargetObj->Id, $article_b->Id, "Its target is B - " . $first->TargetObj->Id); is ($first->BaseObj->Id, $article_a->Id, "Its base is A"); ok($referred_To_by_a->First->BaseObj->isa('RT::Article'), "Yep. its an article"); # Delete the link ($id, $msg) = $article_a->DeleteLink(Type => 'RefersTo', Target => $article_b->URI); ok($id,$msg); # Create an Article A RefersTo Ticket 1 from the Articles side use RT::Ticket; my $tick = RT::Ticket->new($RT::SystemUser); $tick->Create(Subject => "Article link test ", Queue => 'General'); $tick->Load($tick->Id); ok ($tick->Id, "Found ticket ".$tick->id); ($id, $msg) = $article_a->AddLink(Type => 'RefersTo', Target => $tick->URI); ok($id,$msg); # Find all tickets whhich refer to Article A use RT::Tickets; use RT::Links; my $tix = RT::Tickets->new($RT::SystemUser); ok ($tix, "Got an RT::Tickets object"); ok ($tix->LimitReferredToBy($article_a->URI)); is ($tix->Count, 1, "Found one ticket linked to that article"); is ($tix->First->Id, $tick->id, "It's even the right one"); # Find all articles which refer to Ticket 1 use RT::Articles; my $articles = RT::Articles->new($RT::SystemUser); ok($articles->isa('RT::Articles'), "Created an article collection"); ok($articles->isa('RT::SearchBuilder'), "Created an article collection"); ok($articles->isa('DBIx::SearchBuilder'), "Created an article collection"); ok($tick->URI, "The ticket does still have a URI"); $articles->LimitRefersTo($tick->URI); is($articles->Count(), 1); is ($articles->First->Id, $article_a->Id); is ($articles->First->URI, $article_a->URI); # Find all things which refer to ticket 1 using the RT API. my $tix2 = RT::Links->new($RT::SystemUser); ok ($tix2->isa('RT::Links')); ok($tix2->LimitRefersTo($tick->URI)); is ($tix2->Count, 1); is ($tix2->First->BaseObj->URI ,$article_a->URI); # Delete the link from the RT side. my $t2 = RT::Ticket->new($RT::SystemUser); $t2->Load($tick->Id); ($id, $msg)= $t2->DeleteLink( Base => $article_a->URI, Type => 'RefersTo'); ok ($id, $msg . " - $id - $msg"); # it is actually deleted my $tix3 = RT::Links->new($RT::SystemUser); $tix3->LimitReferredToBy($tick->URI); is ($tix3->Count, 0); # Recreate the link from teh RT site ($id, $msg) = $t2->AddLink( Base => $article_a->URI, Type => 'RefersTo'); ok ($id, $msg); # Find all tickets whhich refer to Article A # Find all articles which refer to Ticket 1 my $art = RT::Article->new($RT::SystemUser); ($id, $msg) = $art->Create (Class => $CLASS); ok ($id,$msg); ok($art->URI); ok($art->__Value('URI') eq $art->URI, "The uri in the db is set correctly"); $art = RT::Article->new($RT::SystemUser); ($id, $msg) = $art->Create (Class => $CLASS); ok ($id,$msg); ok($art->URIObj); ok($art->__Value('URI') eq $art->URIObj->URI, "The uri in the db is set correctly"); my $art_id = $art->id; $art = RT::Article->new($RT::SystemUser); $art->Load($art_id); is ($art->Id, $art_id, "Loaded article 1"); my $s =$art->Summary; ($val, $msg) = $art->SetSummary("testFoo"); ok ($val, $msg); ok ($art->Summary eq 'testFoo', "The Summary was set to foo"); my $t = $art->Transactions(); my $trans = $t->Last; ok ($trans->Type eq 'Set', "It's a Set transaction"); ok ($trans->Field eq 'Summary', "it is about setting the Summary"); is ($trans->NewValue , 'testFoo', "The new content is 'foo'"); is ($trans->OldValue,$s, "the old value was preserved"); rt-5.0.1/t/articles/set-subject.t000644 000765 000024 00000007736 14005011336 017504 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use RT::CustomField; use RT::EmailParser; use RT::Queue; use RT::Ticket; use_ok 'RT::Class'; use_ok 'RT::Topic'; use_ok 'RT::Article'; # Variables to test return values my ($ret, $msg); # Create a test class my $class = RT::Class->new($RT::SystemUser); ($ret, $msg) = $class->Create('Name' => 'TestClass-'.$$, 'Description' => 'A general-purpose test class'); ok($ret, "Test class created: $msg"); # because id 0 represents global, it uses an empty Queue object... ($ret, $msg) = $class->AddToObject(RT::Queue->new($RT::SystemUser)); ok($ret, "Applied Class globally: $msg"); # Create some article custom fields my $bodyCF = RT::CustomField->new($RT::SystemUser); my $subjectCF = RT::CustomField->new($RT::SystemUser); ($ret, $msg) = $subjectCF->Create('Name' => 'Subject-'.$$, 'Type' => 'Text', 'MaxValues' => 1, 'LookupType' => 'RT::Class-RT::Article', 'Description' => 'The subject to be answered', 'Disabled' => 0); ok($ret, "Question CF created: $msg"); ($ret, $msg) = $bodyCF->Create('Name' => 'Body-'.$$, 'Type' => 'Text', 'MaxValues' => 1, 'LookupType' => 'RT::Class-RT::Article', 'Description' => 'The body to the subject', 'Disabled' => 0); ok($ret, "Answer CF created: $msg"); my ($sid, $bid) = ($subjectCF->Id, $bodyCF->Id); # Attach the custom fields to our class ($ret, $msg) = $subjectCF->AddToObject($class); ok($ret, "Subject CF added to class: $msg"); ($ret, $msg) = $bodyCF->AddToObject($class); ok($ret, "Body CF added to class: $msg"); my $article = RT::Article->new($RT::SystemUser); ($ret, $msg) = $article->Create(Name => 'First article '.$$, Summary => 'blah blah 1', Class => $class->Id, "CustomField-$bid" => 'This goes in the body', "CustomField-$sid" => 'This clobbers your subject', ); ok($ret, "article 1 created: $msg"); # Create a ticket. my $parser = RT::EmailParser->new(); $parser->ParseMIMEEntityFromScalar('From: root@localhost To: rt@example.com Subject: test ticket for articles This is some form of new request. May as well say something about Africa.'); my $ticket = RT::Ticket->new($RT::SystemUser); my $obj; ($ret, $obj, $msg) = $ticket->Create(Queue => 'General', Subject => 'test ticket for articles '.$$, MIMEObj => $parser->Entity); ok($ret, "Test ticket for articles created: $msg"); #### Right. That's our data. Now begin the real testing. my ($url, $m) = RT::Test->started_ok; ok($m->login, 'logged in'); $m->get_ok( '/Ticket/Update.html?Action=Comment&id=' . $ticket->id, 'ticket update page' ); is($m->form_number(3)->find_input('UpdateSubject')->value,$ticket->Subject,'Ticket Subject Found'); $m->submit_form( form_number => 3, fields => { 'Articles-Include-Article-Named' => $article->Id }, ); is($m->form_number(3)->find_input('UpdateSubject')->value,$ticket->Subject,'Ticket Subject Not Clobbered'); $m->get_ok("$url/Admin/Articles/Classes/"); $m->follow_link_ok( { text => 'TestClass-'.$$ } ); $m->submit_form_ok({ form_number => 3, fields => { SubjectOverride => $sid }, }); $m->content_contains("Added Subject Override: Subject-$$"); $m->get_ok( '/Ticket/Update.html?Action=Comment&id=' . $ticket->id, 'ticket update page' ); is($m->form_number(3)->find_input('UpdateSubject')->value,$ticket->Subject,'Ticket Subject Found'); $m->submit_form( form_number => 3, fields => { 'IncludeArticleId' => $article->id }, ); is($m->form_number(3)->find_input('UpdateSubject')->value,$article->FirstCustomFieldValue("Subject-$$"),'Ticket Subject Clobbered'); done_testing; rt-5.0.1/t/web/crypt-gnupg.t000644 000765 000024 00000035002 14005011336 016465 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::GnuPG tests => undef, gnupg_options => { passphrase => 'recipient', 'trust-model' => 'always', }; use Test::Warn; use MIME::Head; use RT::Action::SendEmail; RT->Config->Set( CommentAddress => 'general@example.com'); RT->Config->Set( CorrespondAddress => 'general@example.com'); RT->Config->Set( DefaultSearchResultFormat => qq{ '__id__/TITLE:#', '__Subject__/TITLE:Subject', 'OO-__Owner__-O', 'OR-__Requestors__-O', 'KO-__KeyOwner__-K', 'KR-__KeyRequestors__-K', Status}); RT::Test->import_gnupg_key('recipient@example.com', 'public'); RT::Test->import_gnupg_key('recipient@example.com', 'secret'); RT::Test->import_gnupg_key('general@example.com', 'public'); RT::Test->import_gnupg_key('general@example.com', 'secret'); RT::Test->import_gnupg_key('general@example.com.2', 'public'); RT::Test->import_gnupg_key('general@example.com.2', 'secret'); ok(my $user = RT::User->new(RT->SystemUser)); ok($user->Load('root'), "Loaded user 'root'"); $user->SetEmailAddress('recipient@example.com'); my $queue = RT::Test->load_or_create_queue( Name => 'General', CorrespondAddress => 'general@example.com', ); ok $queue && $queue->id, 'loaded or created queue'; my $qid = $queue->id; my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in'; $m->get_ok("/Admin/Queues/Modify.html?id=$qid"); $m->form_with_fields('Sign', 'Encrypt'); $m->field(Encrypt => 1); $m->submit; 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"); ok(!$m->value('Sign', 2), "sign tick box is unchecked"); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); $m->get($baseurl); # ensure that the mail has been processed my @mail = RT::Test->fetch_caught_mails; ok(@mail, "got some mail"); $user->SetEmailAddress('general@example.com'); for my $mail (@mail) { unlike $mail, qr/Some content/, "outgoing mail was encrypted"; my ($content_type, $mime_version) = get_headers($mail, "Content-Type", "MIME-Version"); my $body = strip_headers($mail); $mail = << "MAIL"; Subject: RT mail sent back into RT From: general\@example.com To: recipient\@example.com $mime_version $content_type $body MAIL my ($status, $id) = RT::Test->send_via_mailgate($mail); is ($status >> 8, 0, "The mail gateway exited normally"); ok ($id, "got id of a newly created ticket - $id"); my $tick = RT::Ticket->new( RT->SystemUser ); $tick->Load( $id ); ok ($tick->id, "loaded ticket #$id"); is ($tick->Subject, "RT mail sent back into RT", "Correct subject" ); my $txn = $tick->Transactions->First; my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef}; is( $msg->GetHeader('X-RT-Privacy'), 'GnuPG', "RT's outgoing mail has crypto" ); is( $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success', "RT's outgoing mail looks encrypted" ); like($attachments[0]->Content, qr/Some content/, "RT's mail includes copy of ticket text"); like($attachments[0]->Content, qr/$RT::rtname/, "RT's mail includes this instance's name"); } $m->get("$baseurl/Admin/Queues/Modify.html?id=$qid"); $m->form_with_fields('Sign', 'Encrypt'); $m->field(Encrypt => undef); $m->field(Sign => 1); $m->submit; 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"); ok($m->value('Sign', 2), "sign tick box is checked"); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); $m->get($baseurl); # ensure that the mail has been processed @mail = RT::Test->fetch_caught_mails; ok(@mail, "got some mail"); for my $mail (@mail) { like $mail, qr/Some other content/, "outgoing mail was not encrypted"; like $mail, qr/-----BEGIN PGP SIGNATURE-----[\s\S]+-----END PGP SIGNATURE-----/, "data has some kind of signature"; my ($content_type, $mime_version) = get_headers($mail, "Content-Type", "MIME-Version"); my $body = strip_headers($mail); $mail = << "MAIL"; Subject: More RT mail sent back into RT From: general\@example.com To: recipient\@example.com $mime_version $content_type $body MAIL my ($status, $id) = RT::Test->send_via_mailgate($mail); is ($status >> 8, 0, "The mail gateway exited normally"); ok ($id, "got id of a newly created ticket - $id"); my $tick = RT::Ticket->new( RT->SystemUser ); $tick->Load( $id ); ok ($tick->id, "loaded ticket #$id"); is ($tick->Subject, "More RT mail sent back into RT", "Correct subject" ); my $txn = $tick->Transactions->First; my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef}; is( $msg->GetHeader('X-RT-Privacy'), 'GnuPG', "RT's outgoing mail has crypto" ); is( $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted', "RT's outgoing mail looks unencrypted" ); is( $msg->GetHeader('X-RT-Incoming-Signature'), 'general ', "RT's outgoing mail looks signed" ); like($attachments[0]->Content, qr/Some other content/, "RT's mail includes copy of ticket text"); like($attachments[0]->Content, qr/$RT::rtname/, "RT's mail includes this instance's name"); } $m->get("$baseurl/Admin/Queues/Modify.html?id=$qid"); $m->form_with_fields('Sign', 'Encrypt'); $m->field(Encrypt => 1); $m->field(Sign => 1); $m->submit; 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"); ok($m->value('Sign', 2), "sign tick box is checked"); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); $m->get($baseurl); # ensure that the mail has been processed @mail = RT::Test->fetch_caught_mails; ok(@mail, "got some mail"); for my $mail (@mail) { unlike $mail, qr/Some other content/, "outgoing mail was encrypted"; my ($content_type, $mime_version) = get_headers($mail, "Content-Type", "MIME-Version"); my $body = strip_headers($mail); $mail = << "MAIL"; Subject: Final RT mail sent back into RT From: general\@example.com To: recipient\@example.com $mime_version $content_type $body MAIL my ($status, $id) = RT::Test->send_via_mailgate($mail); is ($status >> 8, 0, "The mail gateway exited normally"); ok ($id, "got id of a newly created ticket - $id"); my $tick = RT::Ticket->new( RT->SystemUser ); $tick->Load( $id ); ok ($tick->id, "loaded ticket #$id"); is ($tick->Subject, "Final RT mail sent back into RT", "Correct subject" ); my $txn = $tick->Transactions->First; my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef}; is( $msg->GetHeader('X-RT-Privacy'), 'GnuPG', "RT's outgoing mail has crypto" ); is( $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success', "RT's outgoing mail looks encrypted" ); is( $msg->GetHeader('X-RT-Incoming-Signature'), 'general ', "RT's outgoing mail looks signed" ); like($attachments[0]->Content, qr/Some final\? content/, "RT's mail includes copy of ticket text"); like($attachments[0]->Content, qr/$RT::rtname/, "RT's mail includes this instance's name"); } 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 ok(!$m->value('Encrypt', 2), "encrypt tick box is now unchecked"); ok($m->value('Sign', 2), "sign tick box is still checked"); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); $m->get($baseurl); # ensure that the mail has been processed @mail = RT::Test->fetch_caught_mails; ok(@mail, "got some mail"); for my $mail (@mail) { like $mail, qr/Thought you had me figured out didya/, "outgoing mail was unencrypted"; my ($content_type, $mime_version) = get_headers($mail, "Content-Type", "MIME-Version"); my $body = strip_headers($mail); $mail = << "MAIL"; Subject: Post-final! RT mail sent back into RT From: general\@example.com To: recipient\@example.com $mime_version $content_type $body MAIL my ($status, $id) = RT::Test->send_via_mailgate($mail); is ($status >> 8, 0, "The mail gateway exited normally"); ok ($id, "got id of a newly created ticket - $id"); my $tick = RT::Ticket->new( RT->SystemUser ); $tick->Load( $id ); ok ($tick->id, "loaded ticket #$id"); is ($tick->Subject, "Post-final! RT mail sent back into RT", "Correct subject" ); my $txn = $tick->Transactions->First; my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef}; is( $msg->GetHeader('X-RT-Privacy'), 'GnuPG', "RT's outgoing mail has crypto" ); is( $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted', "RT's outgoing mail looks unencrypted" ); is( $msg->GetHeader('X-RT-Incoming-Signature'), 'general ', "RT's outgoing mail looks signed" ); like($attachments[0]->Content, qr/Thought you had me figured out didya/, "RT's mail includes copy of ticket text"); like($attachments[0]->Content, qr/$RT::rtname/, "RT's mail includes this instance's name"); } sub get_headers { my $mail = shift; open my $fh, "<", \$mail or die $!; my $head = MIME::Head->read($fh); return @{[ map { my $hdr = "$_: " . $head->get($_); chomp $hdr; $hdr; } @_ ]}; } sub strip_headers { my $mail = shift; $mail =~ s/.*?\n\n//s; return $mail; } # now test the OwnerNameKey and RequestorsKey fields my $nokey = RT::Test->load_or_create_user(Name => 'nokey', EmailAddress => 'nokey@example.com'); $nokey->PrincipalObj->GrantRight(Right => 'CreateTicket'); $nokey->PrincipalObj->GrantRight(Right => 'OwnTicket'); my $tick = RT::Ticket->new( RT->SystemUser ); warning_like { $tick->Create(Subject => 'owner lacks pubkey', Queue => 'general', Owner => $nokey); } [ qr/nokey\@example.com: skipped: public key not found|error retrieving 'nokey\@example.com' via WKD: No data/, qr/Recipient 'nokey\@example.com' is unusable/, ]; ok(my $id = $tick->id, 'created ticket for owner-without-pubkey'); $tick = RT::Ticket->new( RT->SystemUser ); $tick->Create(Subject => 'owner has pubkey', Queue => 'general', Owner => 'root'); ok($id = $tick->id, 'created ticket for owner-with-pubkey'); my $mail = << "MAIL"; Subject: Nokey requestor From: nokey\@example.com To: general\@example.com hello MAIL my $status; warning_like { ($status, $id) = RT::Test->send_via_mailgate($mail); } [ qr/nokey\@example.com: skipped: public key not found|error retrieving 'nokey\@example.com' via WKD: No data/, qr/Recipient 'nokey\@example.com' is unusable/, ]; is ($status >> 8, 0, "The mail gateway exited normally"); ok ($id, "got id of a newly created ticket - $id"); $tick = RT::Ticket->new( RT->SystemUser ); $tick->Load( $id ); ok ($tick->id, "loaded ticket #$id"); is ($tick->Subject, "Nokey requestor", "Correct subject" ); # test key selection my $key1 = "EC1E81E7DC3DB42788FB0E4E9FA662C06DE22FC2"; my $key2 = "75E156271DCCF02DDD4A7A8CDF651FA0632C4F50"; ok($user = RT::User->new(RT->SystemUser)); ok($user->Load('root'), "Loaded user 'root'"); is($user->PreferredKey, $key1, "preferred key is set correctly"); $m->get("$baseurl/Prefs/Other.html"); like($m->content, qr/Preferred key/, "preferred key option shows up in preference"); # XXX: mech doesn't let us see the current value of the select, apparently like($m->content, qr/$key1/, "first key shows up in preferences"); like($m->content, qr/$key2/, "second key shows up in preferences"); like($m->content, qr/$key1.*?$key2/s, "first key shows up before the second"); $m->form_name('ModifyPreferences'); $m->select("PreferredKey" => $key2); $m->submit; ok($user = RT::User->new(RT->SystemUser)); ok($user->Load('root'), "Loaded user 'root'"); is($user->PreferredKey, $key2, "preferred key is set correctly to the new value"); $m->get("$baseurl/Prefs/Other.html"); like($m->content, qr/Preferred key/, "preferred key option shows up in preference"); # XXX: mech doesn't let us see the current value of the select, apparently like($m->content, qr/$key2/, "second key shows up in preferences"); like($m->content, qr/$key1/, "first key shows up in preferences"); like($m->content, qr/$key2.*?$key1/s, "second key (now preferred) shows up before the first"); $m->no_warnings_ok; # test that the new fields work $m->get("$baseurl/Search/Simple.html?q=General"); my $content = $m->content; $content =~ s/(/(/g; $content =~ s/)/)/g; $content =~ s/<(a|span)\b[^>]+>//g; $content =~ s/<\/(a|span)>//g; $content =~ s/<//g; like($content, qr/OO-Nobody in particular-O/, "original Owner untouched"); like($content, qr/OO-nokey-O/, "original Owner untouched"); like($content, qr/OO-root \(Enoch Root\)-O/, "original Owner untouched"); like($content, qr/OR--O/, "original Requestors untouched"); like($content, qr/OR-nokey-O/, "original Requestors untouched"); like($content, qr/KO-Nobody in particular \(no pubkey!\)-K/, "KeyOwner issues no-pubkey warning for nobody"); like($content, qr/KO-nokey \(no pubkey!\)-K/, "KeyOwner issues no-pubkey warning for root"); like($content, qr/KO-root \(Enoch Root\)-K/, "KeyOwner does not issue no-pubkey warning for recipient"); like($content, qr/KR--K/, "KeyRequestors does not issue no-pubkey warning for recipient\@example.com"); like($content, qr/KR-nokey \(no pubkey!\)-K/, "KeyRequestors DOES issue no-pubkey warning for nokey\@example.com"); $m->next_warning_like(qr/public key not found|No public key/); $m->next_warning_like(qr/public key not found|No public key/); $m->no_leftover_warnings_ok; done_testing; rt-5.0.1/t/web/rest.t000644 000765 000024 00000022704 14005011336 015170 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Interface::REST; use RT::Test tests => undef; my ($baseurl, $m) = RT::Test->started_ok; for my $name ("severity", "fu()n:k/") { my $cf = RT::Test->load_or_create_custom_field( Name => $name, Type => 'FreeformMultiple', Queue => 'General', ); ok($cf->Id, "created a CustomField"); is($cf->Name, $name, "correct CF name"); } { my $cf = RT::Test->load_or_create_custom_field( Name => 'single', Type => 'FreeformSingle', Queue => 'General', ); ok($cf->Id, "created a CustomField"); } my $queue = RT::Test->load_or_create_queue(Name => 'General'); ok($queue->Id, "loaded the General queue"); $m->post("$baseurl/REST/1.0/ticket/new", [ user => 'root', pass => 'password', format => 'l', ]); my $text = $m->content; my @lines = $text =~ m{.*}g; shift @lines; # header # CFs aren't in the default ticket form push @lines, "CF-fu()n:k/: maximum"; # old style push @lines, "CF.{severity}: explosive"; # new style $text = join "\n", @lines; ok($text =~ s/Subject:\s*$/Subject: REST interface/m, "successfully replaced subject"); $m->post("$baseurl/REST/1.0/ticket/edit", [ user => 'root', pass => 'password', content => $text, ], Content_Type => 'form-data'); my ($id) = $m->content =~ /Ticket (\d+) created/; ok($id, "got ticket #$id"); my $ticket = RT::Ticket->new(RT->SystemUser); $ticket->Load($id); is($ticket->Id, $id, "loaded the REST-created ticket"); is($ticket->Subject, "REST interface", "subject successfully set"); is($ticket->FirstCustomFieldValue("fu()n:k/"), "maximum", "CF successfully set"); $m->post("$baseurl/REST/1.0/search/ticket", [ user => 'root', pass => 'password', query => "id=$id", fields => "Subject,CF-fu()n:k/,CF.{severity},Status", ]); # the fields are interpreted server-side a hash (why?), so we can't depend # on order for ("id: ticket/1", "Subject: REST interface", "CF.{fu()n:k/}: maximum", "CF.{severity}: explosive", "Status: new") { $m->content_contains($_); } # Create ticket 2 for testing ticket links for (2 .. 4) { $m->post("$baseurl/REST/1.0/ticket/edit", [ user => 'root', pass => 'password', content => $text, ], Content_Type => 'form-data'); $m->post( "$baseurl/REST/1.0/ticket/1/links", [ user => 'root', pass => 'password', ], Content_Type => 'form-data', ); } diag "Add one link"; my $link_data = <<'END_LINKS'; id: ticket/1/links DependsOn: 2 END_LINKS $m->post( "$baseurl/REST/1.0/ticket/1/links", [ user => 'root', pass => 'password', content => $link_data, ], Content_Type => 'form-data', ); # See what links get reported for ticket 1. $m->post( "$baseurl/REST/1.0/ticket/1/links/show", [ user => 'root', pass => 'password', ], Content_Type => 'form-data', ); # Verify that the link was added correctly. my $content = form_parse($m->content); my $depends_on = vsplit($content->[0]->[2]->{DependsOn}); @$depends_on = sort @$depends_on; like( $depends_on->[0], qr{/ticket/2$}, "Link to ticket 2 added.", ) or diag("'content' obtained:\n", $m->content); diag "Add two links"; $link_data = <<'END_LINKS'; id: ticket/1/links DependsOn: 3,4 END_LINKS $m->post( "$baseurl/REST/1.0/ticket/1/links", [ user => 'root', pass => 'password', content => $link_data, ], Content_Type => 'form-data', ); # See what links get reported for ticket 1. $m->post( "$baseurl/REST/1.0/ticket/1/links/show", [ user => 'root', pass => 'password', ], Content_Type => 'form-data', ); $content = form_parse($m->content); $depends_on = vsplit($content->[0]->[2]->{DependsOn}); @$depends_on = sort @$depends_on; like( $depends_on->[0], qr{/ticket/3$}, "Link to ticket 3 found", ) or diag("'content' obtained:\n", $m->content); like( $depends_on->[1], qr{/ticket/4$}, "Link to ticket 4 found", ) or diag("'content' obtained:\n", $m->content); $m->post( "$baseurl/REST/1.0/ticket/2/links/show", [ user => 'root', pass => 'password', ], Content_Type => 'form-data', ); my ($link) = $m->content =~ m|DependedOnBy:.*ticket/(\d+)|; is($link, undef, "Link removed from ticket 2") or diag("'content' obtained:\n", $m->content); $m->post( "$baseurl/REST/1.0/ticket/3/links/show", [ user => 'root', pass => 'password', ], Content_Type => 'form-data', ); ($link) = $m->content =~ m|DependedOnBy:.*ticket/(\d+)|; is($link, 1, "Ticket 3 has link to 1.") or diag("'content' obtained:\n", $m->content); $m->post( "$baseurl/REST/1.0/ticket/4/links/show", [ user => 'root', pass => 'password', ], Content_Type => 'form-data', ); ($link) = $m->content =~ m|DependedOnBy:.*ticket/(\d+)|; is($link, 1, "Ticket 4 has link to 1.") or diag("'content' obtained:\n", $m->content); diag "Test custom fields"; { $m->post("$baseurl/REST/1.0/ticket/new", [ user => 'root', pass => 'password', format => 'l', ]); my $text = $m->content; my @lines = $text =~ m{.*}g; shift @lines; # header push @lines, "CF.{severity}: explosive"; push @lines, "CF.{severity}: very"; $text = join "\n", @lines; $m->post("$baseurl/REST/1.0/ticket/edit", [ user => 'root', pass => 'password', content => $text, ], Content_Type => 'form-data'); my ($id) = $m->content =~ /Ticket (\d+) created/; ok($id, "got ticket #$id"); my $ticket = RT::Ticket->new(RT->SystemUser); $ticket->Load($id); is($ticket->Id, $id, "loaded the REST-created ticket"); is_deeply( [sort map $_->Content, @{ $ticket->CustomFieldValues("severity")->ItemsArrayRef }], ["explosive", "very"], "CF successfully set" ); $m->post( "$baseurl/REST/1.0/ticket/show", [ user => 'root', pass => 'password', format => 'l', id => "ticket/$id", ] ); $text = $m->content; $text =~ s/.*?\n\n//; $text =~ s/\n\n/\n/; $text =~ s{CF\.\{severity\}:.*\n}{}img; $text .= "CF.{severity}: explosive, a bit\n"; $m->post( "$baseurl/REST/1.0/ticket/edit", [ user => 'root', pass => 'password', content => $text, ], Content_Type => 'form-data' ); $m->content =~ /Ticket ($id) updated/; $ticket->Load($id); is_deeply( [sort map $_->Content, @{ $ticket->CustomFieldValues("severity")->ItemsArrayRef }], ['a bit', 'explosive'], "CF successfully set" ); $m->post( "$baseurl/REST/1.0/ticket/show", [ user => 'root', pass => 'password', format => 'l', id => "ticket/$id", ] ); $text = $m->content; $text =~ s{CF\.\{severity\}:.*\n}{}img; $text .= "CF.{severity}:\n"; $m->post( "$baseurl/REST/1.0/ticket/edit", [ user => 'root', pass => 'password', content => $text, ], Content_Type => 'form-data' ); $m->content =~ /Ticket ($id) updated/; $ticket->Load($id); is_deeply( [sort map $_->Content, @{ $ticket->CustomFieldValues("severity")->ItemsArrayRef }], [], "CF successfully set" ); my @txns = map [$_->OldValue, $_->NewValue], grep $_->Type eq 'CustomField', @{ $ticket->Transactions->ItemsArrayRef }; is_deeply(\@txns, [['very', undef], [undef, 'a bit'], ['explosive', undef], ['a bit', undef]]); } { $m->post("$baseurl/REST/1.0/ticket/new", [ user => 'root', pass => 'password', format => 'l', ]); my $text = $m->content; my @lines = $text =~ m{.*}g; shift @lines; # header push @lines, "CF.{single}: this"; $text = join "\n", @lines; $m->post("$baseurl/REST/1.0/ticket/edit", [ user => 'root', pass => 'password', content => $text, ], Content_Type => 'form-data'); my ($id) = $m->content =~ /Ticket (\d+) created/; ok($id, "got ticket #$id"); my $ticket = RT::Ticket->new(RT->SystemUser); $ticket->Load($id); is($ticket->Id, $id, "loaded the REST-created ticket"); is_deeply( [sort map $_->Content, @{ $ticket->CustomFieldValues("single")->ItemsArrayRef }], ["this"], "CF successfully set" ); $m->post( "$baseurl/REST/1.0/ticket/show", [ user => 'root', pass => 'password', format => 'l', id => "ticket/$id", ] ); $text = $m->content; $text =~ s{CF\.\{single\}:.*\n}{}img; $text .= "CF.{single}: that\n"; $m->post( "$baseurl/REST/1.0/ticket/edit", [ user => 'root', pass => 'password', content => $text, ], Content_Type => 'form-data' ); $m->content =~ /Ticket ($id) updated/; $ticket->Load($id); is_deeply( [sort map $_->Content, @{ $ticket->CustomFieldValues("single")->ItemsArrayRef }], ['that'], "CF successfully set" ); my @txns = map [$_->OldValue, $_->NewValue], grep $_->Type eq 'CustomField', @{ $ticket->Transactions->ItemsArrayRef }; is_deeply(\@txns, [['this', 'that']]); } done_testing(); rt-5.0.1/t/web/smime/000755 000765 000024 00000000000 14005011336 015133 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/web/squish.t000644 000765 000024 00000004762 14005011336 015533 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => undef; RT->Config->Set( DevelMode => 0 ); RT->Config->Set( WebDefaultStylesheet => 'elevator-light' ); RT->Config->Set( LocalStaticPath => RT::Test::get_abs_relocatable_dir('static') ); my ( $url, $m ) = RT::Test->started_ok; $m->login; diag "test squished files with devel mode disabled"; $m->follow_link_ok( { url_regex => qr!elevator-light/squished-([a-f0-9]{32})\.css! }, 'follow squished css' ); $m->content_like( qr/.{10000}/, 'squished css' ); $m->content_lacks( 'a#fullsite', 'no mobile.css by default' ); $m->back; my ($js_link) = $m->content =~ m!src="([^"]+?squished-([a-f0-9]{32})\.js)"!; $m->get_ok( $url . $js_link, 'follow squished js' ); $m->content_lacks('function just_testing', "no not-by-default.js"); $m->content_contains('jQuery.noConflict', "found default js content"); RT::Test->stop_server; diag "test squished files with customized files and devel mode disabled"; RT->AddJavaScript( 'not-by-default.js' ); RT->AddStyleSheets( 'mobile.css' ); ( $url, $m ) = RT::Test->started_ok; $m->login; $m->follow_link_ok( { url_regex => qr!elevator-light/squished-([a-f0-9]{32})\.css! }, 'follow squished css' ); $m->content_like( qr/.{10000}/, 'squished css' ); $m->content_contains( 'a#fullsite', 'has mobile.css' ); $m->back; ($js_link) = $m->content =~ m!src="([^"]+?squished-([a-f0-9]{32})\.js)"!; $m->get_ok( $url . $js_link, 'follow squished js' ); $m->content_contains( 'function just_testing', "has not-by-default.js" ); $m->content_contains('jQuery.noConflict', "found default js content"); RT::Test->stop_server; ( $url, $m ) = RT::Test->started_ok; $m->login; ($js_link) = $m->content =~ m!src="([^"]+?squished-([a-f0-9]{32})\.js)"!; $m->get_ok( $url . $js_link, 'follow squished js' ); $m->content_contains( 'function just_testing', "has not-by-default.js" ); $m->content_contains('jQuery.noConflict', "found default js content"); RT::Test->stop_server; diag "test squished files with devel mode enabled"; RT->Config->Set( 'DevelMode' => 1 ); RT->AddJavaScript( 'not-by-default.js' ); RT->AddStyleSheets( 'nottherebutwedontcare.css' ); ( $url, $m ) = RT::Test->started_ok; $m->login; $m->content_unlike( qr!squished-.*?\.(js|css)!, 'no squished link with develmode' ); $m->content_contains('not-by-default.js', "found extra javascript resource"); $m->content_contains('nottherebutwedontcare.css', "found extra css resource"); $m->content_contains('jquery_noconflict.js', "found a default js resource"); done_testing; rt-5.0.1/t/web/rest-sort.t000644 000765 000024 00000002155 14005011336 016153 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ($baseurl, $m) = RT::Test->started_ok; RT::Test->create_tickets( { }, { Subject => 'uno' }, { Subject => 'dos' }, { Subject => 'tres' }, ); ok($m->login, 'logged in'); sorted_tickets_ok('Subject', ['2: dos', '3: tres', '1: uno']); sorted_tickets_ok('+Subject', ['2: dos', '3: tres', '1: uno']); sorted_tickets_ok('-Subject', ['1: uno', '3: tres', '2: dos']); sorted_tickets_ok('id', ['1: uno', '2: dos', '3: tres']); sorted_tickets_ok('+id', ['1: uno', '2: dos', '3: tres']); sorted_tickets_ok('-id', ['3: tres', '2: dos', '1: uno']); done_testing; sub sorted_tickets_ok { local $Test::Builder::Level = $Test::Builder::Level + 1; my $order = shift; my $expected = shift; my $query = 'id > 0'; my $uri = URI->new("$baseurl/REST/1.0/search/ticket"); $uri->query_form( query => $query, orderby => $order, ); $m->get_ok($uri); my @lines = split /\n/, $m->content; shift @lines; # header shift @lines; # empty line is_deeply(\@lines, $expected, "sorted results by '$order'"); } rt-5.0.1/t/web/attachment_dropping.t000644 000765 000024 00000003230 14005011336 020236 0ustar00sunnavystaff000000 000000 use warnings; use strict; use RT::Test tests => undef; use File::Temp 'tempfile'; my $content = 'a' x 1000 . 'b' x 10; my ( $fh, $path ) = tempfile( UNLINK => 1, SUFFIX => '.txt' ); print $fh $content; close $fh; my $name = ( File::Spec->splitpath($path) )[2]; RT->Config->Set( 'WebSessionClass', "Apache::Session::File"); RT->Config->Set( 'MaxAttachmentSize', 1000 ); RT->Config->Set( 'TruncateLongAttachments', '0' ); RT->Config->Set( 'DropLongAttachments', '1' ); my $cf = RT::CustomField->new( RT->SystemUser ); ok( $cf->Create( Name => 'test truncation', Queue => '0', Type => 'FreeformSingle', ), ); my $cfid = $cf->id; my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in'; my $queue = RT::Test->load_or_create_queue( Name => 'General' ); ok( $queue->id, "Loaded General queue" ); $m->get_ok( $baseurl . '/Ticket/Create.html?Queue=' . $queue->id ); $m->content_contains( "Create a new ticket", 'ticket create page' ); $m->form_name('TicketCreate'); $m->field( 'Subject', 'Attachments dropping test' ); $m->field( 'Attach', $path ); $m->field( 'Content', 'Some content' ); my $cf_content = 'cf' . 'a' x 998 . 'cfb'; $m->field( "Object-RT::Ticket--CustomField-$cfid-Value", $cf_content ); $m->click('SubmitTicket'); is( $m->status, 200, "request successful" ); $m->content_contains( "File '$name' dropped because its size (1010 bytes) exceeded configured maximum size setting (1000 bytes).", 'dropped message' ); $m->content_lacks( 'cfaaaa', 'cf value was dropped' ); $m->follow_link_ok( { url_regex => qr/Attachment\/\d+\/\d+\/$name/ } ); is( $m->content, 'Large attachment dropped', 'dropped $name' ); done_testing; rt-5.0.1/t/web/search_assets.t000644 000765 000024 00000007057 14005011336 017046 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::Assets tests => undef; RT::Test::Assets::create_assets( { Catalog => 'General assets', Name => 'iMac 27', Status => 'new', }, { Catalog => 'General assets', Name => 'Macbook Pro 2019', Status => 'allocated', }, ); my ( $baseurl, $m ) = RT::Test->started_ok; $m->login; diag "Query builder"; { $m->follow_link_ok( { text => 'New Search', url_regex => qr/Class=RT::Assets/ }, 'Query builder' ); $m->title_is('Asset Query Builder'); my $form = $m->form_name('BuildQuery'); is_deeply( [$form->find_input('AttachmentField')->possible_values], [qw/Name Description/], 'AttachmentField options' ); my @watcher_options = ( '' ); for my $role ( qw/Owner HeldBy Contact/ ) { for my $field ( qw/EmailAddress Name RealName Nickname Organization Address1 Address2 City State Zip Country WorkPhone HomePhone MobilePhone PagerPhone id/ ) { push @watcher_options, "$role.$field"; } } is_deeply( [ $form->find_input('WatcherField')->possible_values ], \@watcher_options, 'WatcherField options' ); $m->field( ValueOfCatalog => 'General assets' ); $m->click('AddClause'); $m->follow_link_ok( { id => 'page-results' } ); $m->title_is('Found 2 assets'); $m->back; $m->form_name('BuildQuery'); $m->field( ValueOfAttachment => 'iMac' ); $m->click('AddClause'); $m->follow_link_ok( { id => 'page-results' } ); $m->title_is('Found 1 asset'); $m->text_contains('iMac 27'); } diag "Advanced"; { $m->follow_link_ok( { text => 'New Search', url_regex => qr/Class=RT::Assets/ }, 'Query builder' ); $m->follow_link_ok( { text => 'Advanced' }, 'Advanced' ); $m->title_is('Edit Asset Query'); $m->form_name('BuildQueryAdvanced'); $m->field( Query => q{Status = 'allocated'} ); $m->submit; $m->follow_link_ok( { id => 'page-results' } ); $m->title_is('Found 1 asset'); $m->text_contains('Macbook Pro 2019'); } diag "Saved searches"; { $m->follow_link_ok( { text => 'New Search', url_regex => qr/Class=RT::Assets/ }, 'Query builder' ); $m->form_name('BuildQuery'); $m->field( ValueOfCatalog => 'General assets' ); $m->submit('AddClause'); $m->form_name('BuildQuery'); $m->field( SavedSearchDescription => 'test asset search' ); $m->click('SavedSearchSave'); $m->text_contains('Current search: test asset search'); my $form = $m->form_name('BuildQuery'); my $input = $form->find_input('SavedSearchLoad'); # an empty search and the real saved search is( scalar $input->possible_values, 2, '2 SavedSearchLoad options' ); my ($attr_id) = ( $input->possible_values )[1] =~ /(\d+)$/; my $attr = RT::Attribute->new( RT->SystemUser ); $attr->Load($attr_id); is_deeply( $attr->Content, { 'Order' => 'ASC|ASC|ASC|ASC', 'Format' => q{'__id__/TITLE:#', '__Name__/TITLE:Name', Status, Catalog, Owner, '__ActiveTickets__ __InactiveTickets__/TITLE:Related tickets', '__NEWLINE__', '__NBSP__', '__Description__', '__CreatedRelative__', '__LastUpdatedRelative__', '__Contacts__'}, 'SearchType' => 'Asset', 'RowsPerPage' => '50', 'OrderBy' => 'Name|||', 'ObjectType' => '', 'Query' => 'Catalog = \'General assets\'' }, 'Saved search content' ); } done_testing; rt-5.0.1/t/web/admin_tools_editconfig.t000644 000765 000024 00000011747 14005011336 020723 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::Deep; use Data::Dumper (); use RT::Test tests => undef, config => 'Set($ShowEditSystemConfig, 0);'; my ( $url, $m ) = RT::Test->started_ok; ok( $m->login(), 'logged in' ); $m->follow_link_ok( { text => 'System Configuration' }, 'followed link to "System Configuration"' ); ok( !$m->find_link( text => 'Edit' ), 'no edit link' ); $m->get_ok('/Admin/Tools/EditConfig.html'); $m->content_contains('Permission Denied'); RT::Test->stop_server; RT->Config->Set( ShowEditSystemConfig => 1 ); ( $url, $m ) = RT::Test->started_ok; ok( $m->login(), 'logged in' ); $m->follow_link_ok( { text => 'System Configuration' }, 'followed link to "System Configuration"' ); $m->follow_link_ok( { text => 'History' }, 'followed link to History page' ); $m->follow_link_ok( { text => 'Edit' }, 'followed link to Edit page' ); # In the tests below, new_value is *always* the string we feed # into the Web form. For compound objects such as hashes and arrays, # we have a separate expected member that is the Perl data structure # resulting from feeding new_value into the Web interface. my $tests = [ { name => 'change a string value', form_id => 'form-System-Base_configuration', setting => 'CorrespondAddress', new_value => 'rt-correspond-edited@example.com', }, { name => 'change a boolean value', form_id => 'form-System-Outgoing_mail', setting => 'NotifyActor', new_value => 1, }, { name => 'change an arrayref value', form_id => 'form-System-Extra_security', setting => 'ReferrerWhitelist', new_value => '["www.example.com:443", "www3.example.com:80"]', expected => ['www.example.com:443', 'www3.example.com:80'], }, { name => 'change a hashref value', form_id => 'form-System-Outgoing_mail', setting => 'OverrideOutgoingMailFrom', new_value => '{"1":"new-outgoing-from@example.com"}', expected => {1 => 'new-outgoing-from@example.com'}, }, ]; run_test( %{$_} ) for @{$tests}; # check tx log for configuration my $transactions = RT::Transactions->new(RT->SystemUser); $transactions->Limit(FIELD => 'ObjectType', VALUE => 'RT::Configuration'); $transactions->OrderBy(FIELD => 'Created', ORDER => 'ASC'); my $tx_items = $transactions->ItemsArrayRef; my $i = 0; foreach my $change (@{$tests}) { check_transaction( $tx_items->[$i++], $change ); } # check config history page $m->get_ok( $url . '/Admin/Tools/ConfigHistory.html'); $i = 0; foreach my $change (@{$tests}) { check_history_page_item($tx_items->[$i++], $change ); } sub run_test { my %args = @_; diag $args{name} if $ENV{TEST_VERBOSE}; $m->submit_form_ok( { form_id => $args{form_id}, fields => { $args{setting} => $args{new_value}, }, }, 'form was submitted successfully' ); # RT::Config in the test is not running in the same process as the one in the test server. # ensure the config object in the test is up to date with the changes. RT->Config->LoadConfigFromDatabase(); $m->content_like( qr/$args{setting} changed from/, 'UI indicated the value was changed' ); # RT::Configuration->Content returns the value as string. # in the test below we need to also ensure the new value is string. my $rt_configuration = RT::Configuration->new( RT->SystemUser ); $rt_configuration->Load( $args{setting} ); my $rt_configuration_value = $rt_configuration->Content; my $rt_config_value = RT->Config->Get( $args{setting} ); is( $rt_configuration_value, stringify($args{expected}) || $args{new_value}, 'value from RT::Configuration->Load matches new value' ); cmp_deeply( $rt_config_value, $args{expected} || $args{new_value}, 'value from RT->Config->Get matches new value' ); } sub check_transaction { my ($tx, $change) = @_; is($tx->ObjectType, 'RT::Configuration', 'tx is config change'); is($tx->Field, $change->{setting}, 'tx matches field changed'); is($tx->NewValue, stringify($change->{expected}) || $change->{new_value}, 'tx value matches'); } sub check_history_page_item { my ($tx, $change) = @_; my $link = sprintf('ConfigHistory.html?id=%d#txn-%d', $tx->ObjectId, $tx->id); ok($m->find_link(url => $link), 'found tx link in history'); if ($change->{expected}) { $m->text_contains(compactify($change->{expected}), 'fetched tx has new value'); } else { $m->text_contains($change->{new_value}); } $m->text_contains("$change->{setting} changed", 'fetched tx has changed field'); } sub compactify { my $value = stringify(shift); $value =~ s/\s+/ /g; return $value; } sub stringify { my $value = shift; return $value unless ref $value; local $Data::Dumper::Terse = 1; local $Data::Dumper::Indent = 2; local $Data::Dumper::Sortkeys = 1; my $output = Data::Dumper::Dumper $value; chomp $output; return $output; } done_testing; rt-5.0.1/t/web/gnupg-headers.t000644 000765 000024 00000003165 14005011336 016744 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::GnuPG tests => 15, gnupg_options => { passphrase => 'recipient', 'trust-model' => 'always', }; RT::Test->import_gnupg_key( 'recipient@example.com', 'public' ); RT::Test->import_gnupg_key( 'general@example.com', 'secret' ); ok( my $user = RT::User->new( RT->SystemUser ) ); ok( $user->Load('root'), "Loaded user 'root'" ); $user->SetEmailAddress('recipient@example.com'); my $queue = RT::Test->load_or_create_queue( Name => 'General', CorrespondAddress => 'general@example.com', ); ok $queue && $queue->id, 'loaded or created queue'; my $qid = $queue->id; my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in'; diag "test with Encrypt and Sign disabled"; $m->goto_create_ticket($queue); $m->form_name('TicketCreate'); $m->field( 'Subject', 'Signing test' ); $m->field( 'Content', 'Some other content' ); $m->click('SubmitTicket'); $m->content_like( qr/Ticket \d+ created/i, 'created the ticket' ); $m->follow_link_ok( { url_regex => qr/Attachment\/WithHeaders\/\d+/ } ); $m->content_contains('X-RT-Encrypt: 0'); $m->content_contains('X-RT-Sign: 0'); diag "test with Encrypt and Sign enabled"; $m->goto_create_ticket($queue); $m->form_name('TicketCreate'); $m->field( 'Subject', 'Signing test' ); $m->field( 'Content', 'Some other content' ); $m->tick( 'Encrypt', 1 ); $m->tick( 'Sign', 1 ); $m->click('SubmitTicket'); $m->content_like( qr/Ticket \d+ created/i, 'created the ticket' ); $m->follow_link_ok( { url_regex => qr/Attachment\/WithHeaders\/\d+/ } ); $m->content_contains('X-RT-Encrypt: 1'); $m->content_contains('X-RT-Sign: 1'); rt-5.0.1/t/web/search_rss.t000644 000765 000024 00000003631 14005011336 016345 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 36; my ($baseurl, $agent) = RT::Test->started_ok; my $ticket = RT::Ticket->new(RT->SystemUser); for ( 1 .. 5 ) { $ticket->Create( Subject => 'Ticket ' . $_, Queue => 'General', Owner => 'root', Requestor => 'rss@localhost', ); } ok $agent->login('root', 'password'), 'logged in as root'; $agent->get_ok('/Search/Build.html'); $agent->form_name('BuildQuery'); $agent->field('idOp', '>'); $agent->field('ValueOfid', '0'); $agent->submit('DoSearch'); $agent->follow_link_ok({id => 'page-results'}); for ( 1 .. 5 ) { $agent->content_contains('Ticket ' . $_); } $agent->follow_link_ok( { text => 'RSS' } ); my $noauth_uri = $agent->uri; is( $agent->content_type, 'application/rss+xml', 'content type' ); for ( 1 .. 5 ) { $agent->content_contains('Ticket ' . $_); } my $rss_content = $agent->content; use XML::Simple; my $rss = XML::Simple::XMLin( $rss_content ); is( scalar @{ $rss->{item} }, 5, 'item number' ); for ( 1 .. 5 ) { is( $rss->{item}[$_-1]{title}, 'Ticket ' . $_, 'title' . $_ ); } # not login at all my $agent_b = RT::Test::Web->new; $agent_b->get_ok($noauth_uri); is( $agent_b->content_type, 'application/rss+xml', 'content type' ); is( $agent_b->content, $rss_content, 'content' ); $agent_b->get_ok('/', 'back to homepage'); $agent_b->content_lacks( 'Logout', 'still not login' ); # lets login as another user my $user_b = RT::Test->load_or_create_user( Name => 'user_b', Password => 'password', ); ok $user_b && $user_b->id, 'loaded or created user'; $agent_b->login('user_b', 'password'); $agent_b->get_ok($noauth_uri); is( $agent_b->content_type, 'application/rss+xml', 'content type' ); is( $agent_b->content, $rss_content, 'content' ); $agent_b->get_ok('/', 'back to homepage'); $agent_b->content_contains( 'Logout', 'still loggedin' ); $agent_b->content_contains( 'user_b', 'still loggedin as user_b' ); rt-5.0.1/t/web/reminder-permissions.t000644 000765 000024 00000013635 14005011336 020374 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 40; my $user_a = RT::Test->load_or_create_user( Name => 'user_a', Password => 'password', ); ok( $user_a && $user_a->id, 'created user_a' ); ok( RT::Test->add_rights( { Principal => $user_a, Right => [qw/SeeQueue CreateTicket ShowTicket OwnTicket/] }, ), 'add basic rights for user_a' ); ok( RT::Test->add_rights( { Principal => 'Owner', Right => [qw/ModifyTicket/], }, ), 'add basic rights for owner' ); my $ticket = RT::Test->create_ticket( Subject => 'test reminder permission', Queue => 'General', ); ok( $ticket->id, 'created a ticket' ); my ( $baseurl, $m ) = RT::Test->started_ok; $m->login; my ( $root_reminder_id, $user_a_reminder_id ); diag "create two reminders, with owner root and user_a, respectively"; { $m->goto_ticket( $ticket->id ); $m->text_contains( 'New reminder:', 'can create a new reminder' ); $m->form_name('UpdateReminders'); $m->field( 'NewReminder-Subject' => "root reminder" ); $m->submit; $m->text_contains( "Reminder 'root reminder': Created", 'created root reminder' ); $m->form_name('UpdateReminders'); $m->field( 'NewReminder-Subject' => "user_a reminder", ); $m->field( 'NewReminder-Owner' => $user_a->id, ); $m->submit; $m->text_contains( "Reminder 'user_a reminder': Created", 'created user_a reminder' ); my $reminders = RT::Reminders->new($user_a); $reminders->Ticket( $ticket->id ); my $col = $reminders->Collection; while ( my $c = $col->Next ) { if ( $c->Subject eq 'root reminder' ) { $root_reminder_id = $c->id; } elsif ( $c->Subject eq 'user_a reminder' ) { $user_a_reminder_id = $c->id; } } } diag "check root_a can update user_a reminder but not root reminder"; my $m_a = RT::Test::Web->new; { ok( $m_a->login( user_a => 'password' ), 'logged in as user_a' ); $m_a->goto_ticket( $ticket->id ); $m_a->content_lacks( 'New reminder:', 'can not create a new reminder' ); $m_a->content_contains( 'root reminder', 'can see root reminder' ); $m_a->content_contains( 'user_a reminder', 'can see user_a reminder' ); $m_a->content_like( qr!form_name('UpdateReminders'); $m_a->tick( "Complete-Reminder-$user_a_reminder_id" => 1 ); $m_a->submit; $m_a->text_contains( "Reminder 'user_a reminder': Status changed from 'open' to 'resolved'", 'complete user_a reminder' ); $m_a->follow_link_ok( { id => 'page-reminders' } ); $m_a->title_is("Reminders for ticket #" . $ticket->id . ": " . $ticket->Subject); $m_a->content_contains( 'root reminder', 'can see root reminder' ); $m_a->content_contains( 'user_a reminder', 'can see user_a reminder' ); $m_a->content_lacks( 'New reminder:', 'can not create a new reminder' ); $m_a->content_like( qr!form_name('UpdateReminders'); $m_a->untick( "Complete-Reminder-$user_a_reminder_id", 1 ); $m_a->submit; $m_a->text_contains( "Reminder 'user_a reminder': Status changed from 'resolved' to 'open'", 'reopen user_a reminder' ); } diag "set ticket owner to user_a to let user_a grant modify ticket right"; { $ticket->SetOwner( $user_a->id ); $m_a->goto_ticket( $ticket->id ); $m_a->content_contains( 'New reminder:', 'can create a new reminder' ); $m_a->content_like( qr!form_name('UpdateReminders'); $m_a->field( 'NewReminder-Subject' => "user_a from display reminder" ); $m_a->submit; $m_a->text_contains( "Reminder 'user_a from display reminder': Created", 'created user_a from display reminder' ); $m_a->follow_link_ok( { id => 'page-reminders' } ); $m_a->title_is("Reminders for ticket #" . $ticket->id . ": " . $ticket->Subject); $m_a->content_contains( 'New reminder:', 'can create a new reminder' ); $m_a->content_like( qr!form_name('UpdateReminders'); $m_a->field( 'NewReminder-Subject' => "user_a from reminders reminder" ); $m_a->submit; $m_a->text_contains( "Reminder 'user_a from reminders reminder': Created", 'created user_a from reminders reminder' ); } diag "grant user_a with ModifyTicket globally"; { ok( RT::Test->add_rights( { Principal => $user_a, Right => [qw/ModifyTicket/], }, ), 'add ModifyTicket rights to user_a' ); $m_a->goto_ticket( $ticket->id ); $m_a->content_unlike( qr!form_name('UpdateReminders'); $m_a->tick( "Complete-Reminder-$root_reminder_id" => 1 ); $m_a->submit; $m_a->text_contains( "Reminder 'root reminder': Status changed from 'open' to 'resolved'", 'complete root reminder' ); $m_a->follow_link_ok( { id => 'page-reminders' } ); $m_a->content_unlike( qr!form_name('UpdateReminders'); $m_a->untick( "Complete-Reminder-$root_reminder_id" => 1 ); $m_a->submit; $m_a->text_contains( "Reminder 'root reminder': Status changed from 'resolved' to 'open'", 'reopen root reminder' ); } rt-5.0.1/t/web/case-sensitivity.t000644 000765 000024 00000004221 14005011336 017510 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 18; my $q = RT::Test->load_or_create_queue( Name => 'General' ); ok $q && $q->id, 'loaded or created queue'; my ($root, $root_id); { $root = RT::User->new( RT->SystemUser ); $root->Load('root'); ok $root_id = $root->id, 'found root'; } my ($baseurl, $m) = RT::Test->started_ok; $m->login; # test users auto completer { $m->get_ok('/Helpers/Autocomplete/Users?term=eNo'); require JSON; is_deeply( JSON::from_json( $m->content ), [ { id => 14, "value" => "root\@localhost", "label" => "root (Enoch Root)", "text" => join( "\n", 'root@localhost', 'root', 'Enoch Root', ), } ] ); } # test ticket's People page { my $ticket = RT::Test->create_ticket( Queue => $q->id ); ok $ticket && $ticket->id, "created ticket"; $m->goto_ticket( $ticket->id ); $m->follow_link_ok( {text => 'People'} ); $m->form_number(3); $m->select( UserField => 'RealName' ); $m->field( UserString => 'eNo' ); $m->click('OnlySearchForPeople'); my $form = $m->form_number(3); my $input = $form->find_input('Ticket-AddWatcher-Principal-'. $root->id ); ok $input, 'input is there'; } # test users' admin UI { $m->get_ok('/Admin/Users/'); $m->form_number(4); $m->select( UserField => 'RealName' ); $m->field( UserString => 'eNo' ); $m->submit; like $m->uri, qr{\QAdmin/Users/Modify.html?id=$root_id\E}; } # create a cf for testing my $cf; { $cf = RT::CustomField->new(RT->SystemUser); my ($id,$msg) = $cf->Create( Name => 'Test', Type => 'Select', MaxValues => '1', Queue => $q->id, ); ok($id,$msg); ($id,$msg) = $cf->AddValue(Name => 'Enoch', Description => 'Root'); ok($id,$msg); } # test custom field values auto completer { $m->get_ok('/Helpers/Autocomplete/CustomFieldValues?term=eNo&Object-RT::Ticket--CustomField-'. $cf->id .'-Value&ContextId=1&ContextType=RT::Queue'); require JSON; is_deeply( JSON::from_json( $m->content ), [{"value" => "Enoch","label" => "Enoch (Root)"}] ); } rt-5.0.1/t/web/attachment_encoding.t000644 000765 000024 00000007121 14005011336 020205 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 32; my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in as root'; use File::Spec; my $subject = Encode::decode("UTF-8",'标题'); my $content = Encode::decode("UTF-8",'测试'); my $filename = Encode::decode("UTF-8",'附件.txt'); diag 'test without attachments' if $ENV{TEST_VERBOSE}; { $m->get_ok( $baseurl . '/Ticket/Create.html?Queue=1' ); $m->form_name('TicketModify'); $m->submit_form( form_number => 3, fields => { Subject => $subject, Content => $content }, button => 'SubmitTicket', ); $m->content_like( qr/Ticket \d+ created/i, 'created the ticket' ); $m->follow_link_ok( { url_regex => qr/Attachment\/WithHeaders\/\d+/ }, '-> /Ticket/Attachment/WithHeaders/...' ); $m->content_contains( $subject, "has subject $subject" ); $m->content_contains( $content, "has content $content" ); my ( $id ) = $m->uri =~ /(\d+)$/; ok( $id, 'found attachment id' ); my $attachment = RT::Attachment->new( $RT::SystemUser ); ok($attachment->Load($id), "load att $id"); # let make original encoding to gbk ok( $attachment->SetHeader( 'X-RT-Original-Encoding' => 'gbk' ), 'set original encoding to gbk' ); $m->get( $m->uri ); $m->content_contains( $subject, "has subject $subject" ); $m->content_contains( $content, "has content $content" ); } diag 'test with attachemnts' if $ENV{TEST_VERBOSE}; { my $file = File::Spec->catfile( RT::Test->temp_directory, Encode::encode("UTF-8",$filename) ); open( my $fh, '>', $file ) or die $!; binmode $fh, ':utf8'; print $fh $filename; close $fh; $m->get_ok( $baseurl . '/Ticket/Create.html?Queue=1' ); $m->form_name('TicketModify'); $m->submit_form( form_number => 3, fields => { Subject => $subject, Content => $content, Attach => $file }, button => 'SubmitTicket', ); $m->content_like( qr/Ticket \d+ created/i, 'created the ticket' ); $m->content_contains( $filename, 'attached filename' ); $m->content_lacks( Encode::encode("UTF-8",$filename), 'no double encoded attached filename' ); $m->follow_link_ok( { url_regex => qr/Attachment\/WithHeaders\/\d+/ }, '-> /Ticket/Attachment/WithHeaders/...' ); # subject is in the parent attachment, so there is no 标题 $m->content_lacks( $subject, "does not have content $subject" ); $m->content_contains( $content, "has content $content" ); my ( $id ) = $m->uri =~ /(\d+)$/; ok( $id, 'found attachment id' ); my $attachment = RT::Attachment->new( $RT::SystemUser ); ok($attachment->Load($id), "load att $id"); # let make original encoding to gbk ok( $attachment->SetHeader( 'X-RT-Original-Encoding' => 'gbk' ), 'set original encoding to gbk' ); $m->get( $m->uri ); $m->content_lacks( $subject, "does not have content $subject" ); $m->content_contains( $content, "has content $content" ); $m->back; $m->back; $m->follow_link_ok( { url_regex => qr/Attachment\/\d+\/\d+\// }, '-> /Ticket/Attachment/...' ); $m->content_contains( $filename, "has file content $filename" ); ( $id ) = $m->uri =~ m{/(\d+)/[^/]+$}; ok( $id, 'found attachment id' ); $attachment = RT::Attachment->new( $RT::SystemUser ); ok($attachment->Load($id), "load att $id"); # let make original encoding to gbk ok( $attachment->SetHeader( 'X-RT-Original-Encoding' => 'gbk' ), 'set original encoding to gbk' ); $m->get( $m->uri ); $m->content_contains( $filename, "has content $filename" ); unlink $file; } rt-5.0.1/t/web/cf_set_initial.t000644 000765 000024 00000007443 14005011336 017172 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in'; my $cf = RT::CustomField->new( RT->SystemUser ); my ($cfid, $msg) = $cf->Create( Name => 'Test Set Initial CF', Queue => '0', Type => 'FreeformSingle', ); my $multi_cf = RT::CustomField->new( RT->SystemUser ); my ($multi_cfid) = $multi_cf->Create( Name => 'Multi Set Initial CF', Queue => '0', Type => 'FreeformMultiple', ); my $tester = RT::Test->load_or_create_user( Name => 'tester', Password => '123456' ); RT::Test->set_rights( { Principal => $tester->PrincipalObj, Right => [qw(SeeQueue ShowTicket CreateTicket)], }, ); ok $m->login( $tester->Name, 123456, logout => 1), 'logged in'; diag "check that we have no CFs on the create" ." ticket page when user has no SetInitialCustomField right"; { $m->submit_form( form_name => "CreateTicketInQueue" ); $m->content_lacks('Test Set Initial CF', 'has no CF input'); $m->content_lacks('Multi Set Initial CF', 'has no CF input'); my $form = $m->form_name("TicketCreate"); my $edit_field = "Object-RT::Ticket--CustomField-$cfid-Value"; ok !$form->find_input( $edit_field ), 'no form field on the page'; my $multi_field = "Object-RT::Ticket--CustomField-$multi_cfid-Values"; ok !$form->find_input( $multi_field ), 'no form field on the page'; $m->submit_form( form_name => "TicketCreate", fields => { Subject => 'test' }, button => 'SubmitTicket', ); $m->content_like(qr/Ticket \d+ created/, "a ticket is created succesfully"); $m->content_lacks('Test Set Initial CF', 'has no CF on the page'); $m->content_lacks('Multi Set Initial CF', 'has no CF on the page'); $m->follow_link( text => 'Custom Fields'); $m->content_lacks('Test Set Initial CF', 'has no CF field'); $m->content_lacks('Multi Set Initial CF', 'has no CF field'); } RT::Test->set_rights( { Principal => $tester->PrincipalObj, Right => [qw(SeeQueue ShowTicket CreateTicket SetInitialCustomField)], }, ); diag "check that we have the CF on the create" ." ticket page when user has SetInitialCustomField but no SeeCustomField"; { $m->submit_form( form_name => "CreateTicketInQueue" ); $m->content_contains('Test Set Initial CF', 'has CF input'); $m->content_contains('Multi Set Initial CF', 'has CF input'); my $form = $m->form_name("TicketCreate"); my $edit_field = "Object-RT::Ticket--CustomField-$cfid-Value"; ok $form->find_input( $edit_field ), 'has form field on the page'; my $multi_field = "Object-RT::Ticket--CustomField-$multi_cfid-Values"; ok $form->find_input( $multi_field ), 'has form field on the page'; $m->submit_form( form_name => "TicketCreate", fields => { $edit_field => 'yatta', $multi_field => 'hiro', Subject => 'test 2', }, button => 'SubmitTicket', ); $m->content_like(qr/Ticket \d+ created/, "a ticket is created succesfully"); if (my ($id) = $m->content =~ /Ticket (\d+) created/) { my $ticket = RT::Ticket->new(RT->SystemUser); my ($ok, $msg) = $ticket->Load($id); ok($ok, "loaded ticket $id"); is($ticket->Subject, 'test 2', 'subject is correct'); is($ticket->FirstCustomFieldValue('Test Set Initial CF'), 'yatta', 'CF was set correctly'); is($ticket->FirstCustomFieldValue('Multi Set Initial CF'), 'hiro', 'CF was set correctly'); } $m->content_lacks('Test Set Initial CF', 'has no CF on the page'); $m->content_lacks('Multi Set Initial CF', 'has no CF on the page'); $m->follow_link( text => 'Custom Fields'); $m->content_lacks('Test Set Initial CF', 'has no CF edit field'); $m->content_lacks('Multi Set Initial CF', 'has no CF edit field'); } done_testing; rt-5.0.1/t/web/cf_date.t000644 000765 000024 00000021154 14005011336 015576 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in as root'; my $root = RT::User->new(RT->SystemUser); ok( $root->Load('root'), 'load root user' ); my $cf_name = 'test cf date'; my $cfid; diag "Create a CF"; { $m->follow_link( id => 'admin-custom-fields-create'); $m->submit_form( form_name => "ModifyCustomField", fields => { Name => $cf_name, TypeComposite => 'Date-1', LookupType => 'RT::Queue-RT::Ticket', EntryHint => 'Select date', }, ); $m->content_contains('Object created', 'created CF sucessfully' ); $cfid = $m->form_name('ModifyCustomField')->value('id'); ok $cfid, "found id of the CF in the form, it's #$cfid"; } diag "apply the CF to General queue"; my $queue = RT::Test->load_or_create_queue( Name => 'General' ); ok $queue && $queue->id, 'loaded or created queue'; { $m->follow_link( id => 'admin-queues-select'); $m->title_is( q/Admin queues/, 'admin-queues screen' ); $m->follow_link( text => 'General' ); $m->title_is( q/Configuration for queue General/, 'admin-queue: general' ); $m->follow_link( id => 'page-custom-fields-tickets' ); $m->title_is( q/Custom Fields for queue General/, 'admin-queue: general cfid' ); $m->form_name('EditCustomFields'); $m->tick( "AddCustomField" => $cfid ); $m->click('UpdateCFs'); $m->content_contains("Added custom field $cf_name to General", 'TCF added to the queue' ); } diag 'check valid inputs with various timezones in ticket create page'; { my ( $ticket, $id ); $m->submit_form( form_name => "CreateTicketInQueue" ); $m->content_contains('Select date', 'has cf field' ); $m->submit_form( form_name => "TicketCreate", fields => { Subject => 'test 2010-05-04', Content => 'test', "Object-RT::Ticket--CustomField-$cfid-Values" => '2010-05-04', }, button => 'SubmitTicket', ); ok( ($id) = $m->content =~ /Ticket (\d+) created/, "created ticket $id" ); $ticket = RT::Ticket->new(RT->SystemUser); $ticket->Load($id); is( $ticket->CustomFieldValues($cfid)->First->Content, '2010-05-04', 'date in db' ); $m->content_contains('test cf date:', 'has no cf date field on the page' ); $m->content_contains('Tue May 04 2010', 'has cf date value on the page' ); } diag 'check search build page'; { $m->get_ok( $baseurl . '/Search/Build.html?Query=Queue=1' ); $m->form_name('BuildQuery'); my ($cf_op) = $m->find_all_inputs( type => 'option', name_regex => qr/test cf date/ ); is_deeply( [ $cf_op->possible_values ], [ '<', '=', '>' ], 'right oprators' ); my ($cf_field) = $m->find_all_inputs( type => 'text', name_regex => qr/test cf date/ ); $m->submit_form( fields => { $cf_op->name => '=', $cf_field->name => '2010-05-04' }, button => 'DoSearch', ); $m->content_contains( 'Found 1 ticket', 'Found 1 ticket' ); $m->content_contains( '2010-05-04', 'got the right ticket' ); $m->content_lacks( '2010-05-06', 'did not get the wrong ticket' ); $m->get_ok( $baseurl . '/Search/Build.html?Query=Queue=1' ); $m->form_name('BuildQuery'); $m->submit_form( fields => { $cf_op->name => '<', $cf_field->name => '2010-05-05' }, button => 'DoSearch', ); $m->content_contains( 'Found 1 ticket', 'Found 1 ticket' ); $m->get_ok( $baseurl . '/Search/Build.html?Query=Queue=1' ); $m->form_name('BuildQuery'); $m->submit_form( fields => { $cf_op->name => '>', $cf_field->name => '2010-05-03', }, button => 'DoSearch', ); $m->content_contains( 'Found 1 ticket', 'Found 1 ticket' ); $m->get_ok( $baseurl . '/Search/Build.html?Query=Queue=1' ); $m->form_name('BuildQuery'); $m->submit_form( fields => { $cf_op->name => '=', $cf_field->name => '2010-05-05', }, button => 'DoSearch', ); $m->content_contains( 'Found 0 tickets', 'Found 0 tickets' ); $m->get_ok( $baseurl . '/Search/Build.html?Query=Queue=1' ); $m->form_name('BuildQuery'); $m->submit_form( fields => { $cf_op->name => '<', $cf_field->name => '2010-05-03', }, button => 'DoSearch', ); $m->content_contains( 'Found 0 tickets', 'Found 0 tickets' ); $m->get_ok( $baseurl . '/Search/Build.html?Query=Queue=1' ); $m->form_name('BuildQuery'); $m->submit_form( fields => { $cf_op->name => '>', $cf_field->name => '2010-05-05', }, button => 'DoSearch', ); $m->content_contains( 'Found 0 tickets', 'Found 0 tickets' ); } diag 'check invalid inputs'; { $m->submit_form( form_name => "CreateTicketInQueue" ); my $form = $m->form_name("TicketCreate"); $m->submit_form( form_name => "TicketCreate", fields => { Subject => 'test', Content => 'test', "Object-RT::Ticket--CustomField-$cfid-Values" => 'foodate', }, button => 'SubmitTicket', ); $m->content_like( qr/Ticket \d+ created/, "a ticket is created succesfully" ); $m->content_contains('test cf date:', 'has no cf date field on the page' ); $m->content_lacks('foodate', 'invalid dates not set' ); my @warnings = $m->get_warnings; chomp @warnings; is_deeply( [ @warnings ], [ ( q{Couldn't parse date 'foodate' by Time::ParseDate}, q{Couldn't parse date 'foodate' by DateTime::Format::Natural} ) x 2 ] ); } diag 'retain values when adding attachments'; { my ( $ticket, $id ); my $txn_cf = RT::CustomField->new( RT->SystemUser ); my ( $ret, $msg ) = $txn_cf->Create( Name => 'test txn cf date', TypeComposite => 'Date-1', LookupType => 'RT::Queue-RT::Ticket-RT::Transaction', ); ok( $ret, "created 'txn datetime': $msg" ); $txn_cf->AddToObject(RT::Queue->new(RT->SystemUser)); my $txn_cfid = $txn_cf->id; $m->submit_form( form_name => "CreateTicketInQueue" ); $m->content_contains('test cf date', 'has cf' ); $m->content_contains('test txn cf date', 'has txn cf' ); $m->submit_form_ok( { form_name => "TicketCreate", fields => { Subject => 'test 2015-06-04', Content => 'test', "Object-RT::Ticket--CustomField-$cfid-Values" => '2015-06-04', "Object-RT::Transaction--CustomField-$txn_cfid-Values" => '2015-08-15', }, button => 'AddMoreAttach', }, 'create test ticket' ); $m->form_name("TicketCreate"); is( $m->value( "Object-RT::Ticket--CustomField-$cfid-Values" ), "2015-06-04", "ticket cf date value still on form" ); is( $m->value( "Object-RT::Transaction--CustomField-$txn_cfid-Values" ), "2015-08-15", "txn cf date date value still on form" ); $m->submit_form( button => 'SubmitTicket' ); ok( ($id) = $m->content =~ /Ticket (\d+) created/, "created ticket $id" ); $m->follow_link_ok( {text => 'Reply'} ); $m->title_like( qr/Update/ ); $m->content_contains('test txn cf date', 'has txn cf'); $m->submit_form_ok( { form_name => "TicketUpdate", fields => { Content => 'test', "Object-RT::Transaction--CustomField-$txn_cfid-Values" => '2015-09-16', }, button => 'AddMoreAttach', }, 'Update test ticket' ); $m->form_name("TicketUpdate"); is( $m->value( "Object-RT::Transaction--CustomField-$txn_cfid-Values" ), "2015-09-16", "txn date value still on form" ); $m->follow_link_ok( {text => 'Jumbo'} ); $m->title_like( qr/Jumbo/ ); $m->submit_form_ok( { form_name => "TicketModifyAll", fields => { "Object-RT::Transaction--CustomField-$txn_cfid-Values" => '2015-12-16', }, button => 'AddMoreAttach', }, 'jumbo form' ); $m->form_name("TicketModifyAll"); is( $m->value( "Object-RT::Transaction--CustomField-$txn_cfid-Values" ), "2015-12-16", "txn date value still on form" ); } done_testing; rt-5.0.1/t/web/lifecycle_mappings.t000644 000765 000024 00000014261 14005011336 020047 0ustar00sunnavystaff000000 000000 use strict; use warnings; BEGIN { require './t/lifecycles/utils.pl' } my ( $url, $m ) = RT::Test->started_ok; ok( $m->login(), 'logged in' ); my $sales = RT::Lifecycle->new(); my ( $ret, $msg ) = $sales->Load( Name => 'sales', Type => 'ticket' ); ok $ret, "Loaded lifecycle sales successfully"; my $default = RT::Lifecycle->new(); ( $ret, $msg ) = $default->Load( Name => 'default', Type => 'ticket' ); ok $ret, "Loaded lifecycle default successfully"; my $sales_engineering = RT::Lifecycle->new(); ( $ret, $msg ) = $sales_engineering->Load( Name => 'sales-engineering', Type => 'ticket' ); ok $ret, "Loaded lifecycle sales_engineering successfully"; diag "Test updating mappings"; { $m->get_ok( $url . '/Admin/Lifecycles/Mappings.html?Type=ticket&Name=default' ); my $form = $m->form_name('ModifyMappings'); $m->submit_form( fields => { "map-default--new--sales" => "initial", "map-default--open--sales" => "active", "map-default--resolved--sales" => "inactive", "map-sales--initial--default" => "new", "map-sales--active--default" => "open", "map-sales--inactive--default" => "resolved", "map-default--deleted--sales" => "inactive", "map-default--rejected--sales" => "inactive", "map-default--stalled--sales" => "active", "Name" => "default", "Type" => "ticket", }, button => 'Update' ); $m->content_contains('Lifecycle mappings updated'); reload_lifecycle(); my $from = { deleted => "inactive", new => "initial", open => "active", rejected => "inactive", resolved => "inactive", stalled => "active" }; my $to = { active => "open", inactive => "resolved", initial => "new", }; is_deeply( $from, $default->MoveMap($sales), "Move map from default -> sales set correctly" ); is_deeply( $to, $sales->MoveMap($default), "Move map from sales -> default set correctly" ); $from->{'new'} = 'active'; $m->get_ok( $url . '/Admin/Lifecycles/Mappings.html?Type=ticket&Name=default' ); $form = $m->form_name('ModifyMappings'); $m->submit_form( fields => { "map-default--new--sales" => "active", "map-default--open--sales" => "active", "map-default--resolved--sales" => "inactive", "map-sales--initial--default" => "new", "map-sales--active--default" => "open", "map-sales--inactive--default" => "resolved", "map-default--deleted--sales" => "inactive", "map-default--rejected--sales" => "inactive", "map-default--stalled--sales" => "active", "Name" => "default", "Type" => "ticket", }, button => 'Update' ); $m->content_contains('Lifecycle mappings updated'); reload_lifecycle(); is_deeply( $from, $default->MoveMap($sales), "Move map from default -> sales updated correctly" ); } diag "Confirm the web UI correctly displays mappings"; { $m->get_ok( $url . '/Admin/Lifecycles/Mappings.html?Type=ticket&Name=default' ); my $form = $m->form_name('ModifyMappings'); my $from = { deleted => "inactive", new => "active", open => "active", rejected => "inactive", resolved => "inactive", stalled => "active", }; my $to = { active => "open", inactive => "resolved", initial => "new", }; my @inputs = $form->inputs; foreach my $input (@inputs) { my ( $default_from, $default_status, $default_to ) = $input->name =~ /^map-(default)--(.*)--(sales)$/; my ( $sales_from, $sales_status, $sales_to ) = $input->name =~ /^map-(sales)--(.*)--(default)$/; if ($default_from) { is( $input->value, $from->{$default_status}, "Mapping set correctly for default -> sales for status: $default_status" ); } elsif ($sales_from) { is( $input->value, $to->{$sales_status}, "Mapping set correctly for sales -> default for status: $sales_status" ); } } } diag "Test updating sales-engineering mappings"; { $m->get_ok( $url . '/Admin/Lifecycles/Mappings.html?Type=ticket&Name=sales-engineering' ); my $form = $m->form_name('ModifyMappings'); $m->submit_form( fields => { "map-sales-engineering--sales--default" => "new", "map-sales-engineering--engineering--default" => "open", "map-sales-engineering--rejected--default" => "rejected", "map-sales-engineering--resolved--default" => "resolved", "map-sales-engineering--stalled--default" => "stalled", "map-sales-engineering--deleted--default" => "deleted", "Name" => "sales-engineering", "Type" => "ticket", }, button => 'Update' ); $m->content_contains('Lifecycle mappings updated'); $form = $m->form_name('ModifyMappings'); my $from = { sales => "new", engineering => "open", stalled => "stalled", rejected => "rejected", resolved => "resolved", deleted => "deleted", }; for my $status ( keys %$from ) { is( $form->value("map-sales-engineering--$status--default"), $from->{$status}, "Mapping set correctly for sales-engineering -> default for status: $status" ); } reload_lifecycle(); is_deeply( $from, $sales_engineering->MoveMap($default), "Move map from sales_enginnering -> default updated correctly" ); } sub reload_lifecycle { # to get rid of the warning of: # you're changing config option in a test file when server is active RT::Test->stop_server; RT->Config->LoadConfigFromDatabase(); RT::Lifecycle->FillCache(); ( $url, $m ) = RT::Test->started_ok; ok( $m->login(), 'logged in' ); } done_testing; rt-5.0.1/t/web/logout.t000644 000765 000024 00000002124 14005011336 015516 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 12; my ($baseurl, $agent) = RT::Test->started_ok; my $url = $agent->rt_base_url; diag $url if $ENV{TEST_VERBOSE}; # test that logout would actually redirects to the correct URL { ok $agent->login, "logged in"; $agent->follow_link_ok({ text => 'Logout' }); like $agent->uri, qr'/Logout\.html$', "right url"; $agent->content_contains('stop_server; RT->Config->Set(MasonLocalComponentRoot => RT::Test::get_abs_relocatable_dir('html')); ($baseurl, $agent) = RT::Test->started_ok; $url = $agent->rt_base_url; diag $url if $ENV{TEST_VERBOSE}; # test that logout would actually redirects to URL from the callback { ok $agent->login, "logged in"; $agent->follow_link_ok({ text => 'Logout' }); like $agent->uri, qr'/Logout\.html$', "right url"; $agent->content_contains(' 85; my ($baseurl, $agent) = RT::Test->started_ok; my $ticket = RT::Ticket->new(RT->SystemUser); for ( 1 .. 75 ) { ok $ticket->Create( Subject => 'Ticket ' . $_, Queue => 'General', Owner => 'root', Requestor => 'unlimitedsearch@localhost', ); } ok $agent->login('root', 'password'), 'logged in as root'; $agent->get_ok('/Search/Build.html'); $agent->form_name('BuildQuery'); $agent->field('idOp', '>'); $agent->field('ValueOfid', '0'); $agent->submit('AddClause'); $agent->form_name('BuildQuery'); $agent->field('RowsPerPage', '0'); $agent->submit('DoSearch'); $agent->follow_link_ok({text=>'Show Results'}); $agent->content_contains("Ticket 75"); $agent->follow_link_ok({text=>'New Search'}); $agent->form_name('BuildQuery'); $agent->field('idOp', '>'); $agent->field('ValueOfid', '0'); $agent->submit('AddClause'); $agent->form_name('BuildQuery'); $agent->field('RowsPerPage', '50'); $agent->submit('DoSearch'); $agent->follow_link_ok({text=>'Bulk Update'}); $agent->content_lacks("Ticket 51"); rt-5.0.1/t/web/charting.t000644 000765 000024 00000006757 14005011336 016024 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef, config => 'Set($EnableJSChart, 0);'; plan skip_all => 'GD required' unless GD->require; for my $n (1..7) { my $ticket = RT::Ticket->new( RT->SystemUser ); my $req = 'root' . ($n % 2) . '@localhost'; my ( $ret, $msg ) = $ticket->Create( Subject => "base ticket $_", Queue => "General", Owner => "root", Requestor => $req, MIMEObj => MIME::Entity->build( From => $req, To => 'rt@localhost', Subject => "base ticket $_", Data => "Content $_", ), ); ok( $ret, "ticket $n created: $msg" ); } my ($url, $m) = RT::Test->started_ok; ok( $m->login, "Logged in" ); # Test that defaults work $m->get_ok( "/Search/Chart.html?Query=id>0" ); $m->content_like(qr{]*>Status\s*\s*]*>Ticket count\s*}, "Grouped by status"); $m->content_like(qr{new\s*\s*]*>\s*]*>7}, "Found results in table"); $m->content_like(qr{get_ok( "/Search/Chart?Query=id>0" ); is( $m->content_type, "image/png" ); ok( length($m->content), "Has content" ); # Group by Queue $m->get_ok( "/Search/Chart.html?Query=id>0&GroupBy=Queue" ); $m->content_like(qr{]*>Queue\s*\s*]*>Ticket count\s*}, "Grouped by queue"); $m->content_like(qr{General\s*\s*]*>\s*]*>7}, "Found results in table"); $m->content_like(qr{get_ok( "/Search/Chart?Query=id>0&GroupBy=Queue" ); is( $m->content_type, "image/png" ); ok( length($m->content), "Has content" ); # Group by Requestor email $m->get_ok( "/Search/Chart.html?Query=id>0&GroupBy=Requestor.EmailAddress" ); $m->content_like(qr{]*>Requestor\s+EmailAddress\s*]*>Ticket count\s*}, "Grouped by requestor"); $m->content_like(qr{root0\@localhost\s*\s*]*>\s*]*>3}, "Found results in table"); $m->content_like(qr{get_ok( "/Search/Chart?Query=id>0&GroupBy=Requestor.EmailAddress" ); is( $m->content_type, "image/png" ); ok( length($m->content), "Has content" ); # Group by Requestor phone -- which is bogus, and falls back to queue $m->get_ok( "/Search/Chart.html?Query=id>0&GroupBy=Requestor.Phone" ); $m->warning_like( qr{'Requestor\.Phone' is not a valid grouping for reports} ); TODO: { local $TODO = "UI should show that it's group by status"; $m->content_like(qr{new\s*\s*]*>\s*]*>7}, "Found queue results in table, as a default"); } $m->content_like(qr{get_ok( "/Search/Chart?Query=id>0&GroupBy=Requestor.Phone" ); $m->warning_like( qr{'Requestor\.Phone' is not a valid grouping for reports} ); is( $m->content_type, "image/png" ); ok( length($m->content), "Has content" ); diag "Confirm subnav links use Query param before saved search in session."; $m->get_ok( "/Search/Chart.html?Query=id>0" ); my $advanced = $m->find_link( text => 'Advanced' )->URI->equery; like( $advanced, qr{Query=id%3E0}, 'Advanced link has Query param with id search' ); # Load the session with another search. $m->get_ok( "/Search/Results.html?Query=Queue='General'" ); $m->get_ok( "/Search/Chart.html?Query=id>0" ); $advanced = $m->find_link( text => 'Advanced' )->URI->equery; like( $advanced, qr{Query=id%3E0}, 'Advanced link still has Query param with id search' ); done_testing; rt-5.0.1/t/web/walk.t000644 000765 000024 00000004503 14005011336 015146 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test; use HTML::TreeBuilder; my ( $baseurl, $m ) = RT::Test->started_ok; ok( $m->login( 'root' => 'password' ), 'login as root' ); my %viewed = ( '/NoAuth/Logout.html' => 1 ); # in case logout my $user = RT::User->new($RT::SystemUser); $user->Load('root'); ok( $user->id, 'loaded root' ); my $queue = RT::Queue->new($RT::SystemUser); $queue->Load('General'); ok( $queue->id, 'loaded General queue' ); my $group = RT::Group->new($RT::SystemUser); ok( $group->CreateUserDefinedGroup( Name => 'group_foo' ) ); my $cf = RT::CustomField->new($RT::SystemUser); ok( $cf->Create( Name => 'cf_foo', Type => 'Freeform', LookupType => 'RT::Queue-RT::Ticket', ) ); ok( $cf->id, 'created cf_foo' ); my $class = RT::Class->new($RT::SystemUser); ok( $class->Create( Name => 'class_foo' ) ); ok( $class->id, 'created class_foo' ); # to make search have results my $open_ticket = RT::Test->create_ticket( Subject => 'ticket_foo', Queue => 1, ); my $resolved_ticket = RT::Test->create_ticket( Subject => 'ticket_bar', Status => 'resolved', Queue => 1, ); my @links = ( '/', '/Admin/Users/Modify.html?id=' . $user->id, '/Admin/Groups/Modify.html?id=' . $group->id, '/Admin/Queues/Modify.html?id=' . $queue->id, '/Admin/CustomFields/Modify.html?id=' . $cf->id, '/Admin/Scrips/Modify.html?id=1', '/Admin/Global/Template.html?Template=1', '/Admin/Articles/Classes/Modify.html?id=' . $class->id, '/Search/Build.html?Query=id<10', '/Ticket/Display.html?id=' . $open_ticket->id, '/Ticket/Display.html?id=' . $resolved_ticket->id, ); for my $link (@links) { test_page($m, $link); } $m->get_ok('/NoAuth/Logout.html'); sub test_page { my $m = shift; my $link = shift; $m->get_ok( $link, $link ); $m->no_warnings_ok($link); my $tree = HTML::TreeBuilder->new(); $tree->parse( $m->content ); $tree->elementify; my ($top_menu) = $tree->look_down( id => 'main-navigation' ); my ($page_menu) = $tree->look_down( id => 'page-navigation' ); my (@links) = grep { !$viewed{$_}++ && /^[^#]/ } map { $_->attr('href') || () } ( $top_menu ? $top_menu->find('a') : () ), ( $page_menu ? $page_menu->find('a') : () ); for my $link (@links) { test_page($m, $link); } } rt-5.0.1/t/web/custom_search.t000644 000765 000024 00000004723 14005011336 017053 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 13; my ($baseurl, $m) = RT::Test->started_ok; my $url = $m->rt_base_url; # reset preferences for easier test? my $t = RT::Ticket->new(RT->SystemUser); $t->Create(Subject => 'for custom search'.$$, Queue => 'general', Owner => 'root', Requestor => 'customsearch@localhost'); ok(my $id = $t->id, 'created ticket for custom search'); ok $m->login, 'logged in'; my $t_link = $m->find_link( text => "for custom search".$$ ); like ($t_link->url, qr/$id/, 'link to the ticket we created'); $m->content_lacks ('customsearch@localhost', 'requestor not displayed '); $m->get ( $url.'Prefs/MyRT.html' ); my $cus_hp = $m->find_link( text => "My Tickets" ); my $cus_qs = $m->find_link( text => "Queue list" ); $m->get ($cus_hp); $m->content_contains('highest priority tickets'); # add Requestor to the fields $m->form_name ('BuildQuery'); # can't use submit form for mutli-valued select as it uses set_fields $m->field (SelectDisplayColumns => ['Requestors']); $m->click_button (name => 'AddCol') ; $m->form_name ('BuildQuery'); $m->click_button (name => 'Save'); $m->get( $url ); $m->content_contains ('customsearch@localhost', 'requestor now displayed '); # now remove Requestor from the fields $m->get ($cus_hp); $m->form_name ('BuildQuery'); my $cdc = $m->current_form->find_input('CurrentDisplayColumns'); my ($requestor_value) = grep { /Requestor/ } $cdc->possible_values; ok($requestor_value, "got the requestor value"); $m->field (CurrentDisplayColumns => $requestor_value); $m->click_button (name => 'RemoveCol') ; $m->form_name ('BuildQuery'); $m->click_button (name => 'Save'); $m->get( $url ); $m->content_lacks ('customsearch@localhost', 'requestor not displayed '); # try to disable General from queue list # Note that there's a small problem in the current implementation, # since ticked quese are wanted, we do the invesrsion. So any # queue added during the queue list setting will be unticked. my $nlinks = $#{$m->find_all_links( text => "General" )}; $m->get ($cus_qs); $m->form_name ('Preferences'); $m->untick('Want-General', '1'); $m->click_button (name => 'Save'); $m->get( $url ); is ($#{$m->find_all_links( text => "General" )}, $nlinks - 1, 'General gone from queue list'); # get it back $m->get ($cus_qs); $m->form_name ('Preferences'); $m->tick('Want-General', '1'); $m->click_button (name => 'Save'); $m->get( $url ); is ($#{$m->find_all_links( text => "General" )}, $nlinks, 'General back in queue list'); rt-5.0.1/t/web/command_line.t000644 000765 000024 00000072336 14005011336 016646 0ustar00sunnavystaff000000 000000 use strict; use warnings; use File::Spec (); use Test::Expect; use RT::Test tests => undef, actual_server => 1; my ($baseurl, $m) = RT::Test->started_ok; use RT::User; use RT::Queue; my $rt_tool_path = "$RT::BinPath/rt"; # config directives: # (in $CWD/.rtrc) # - server URL to RT server. # - user RT username. # - passwd RT user's password. # - query Default RT Query for list action # - orderby Default RT order for list action # # Blank and #-commented lines are ignored. # environment variables # The following environment variables override any corresponding # values defined in configuration files: # # - RTUSER $ENV{'RTUSER'} = 'root'; # - RTPASSWD $ENV{'RTPASSWD'} = 'password'; # - RTSERVER $RT::Logger->debug("Connecting to server at ".RT->Config->Get('WebBaseURL')); $ENV{'RTSERVER'} =RT->Config->Get('WebBaseURL') ; # - RTDEBUG Numeric debug level. (Set to 3 for full logs.) $ENV{'RTDEBUG'} = '1'; # - RTCONFIG Specifies a name other than ".rtrc" for the # configuration file. $ENV{'RTCONFIG'} = '/dev/null'; # # - RTQUERY Default RT Query for rt list # - RTORDERBY Default order for rt list # create a ticket expect_run( command => "$rt_tool_path shell", prompt => 'rt> ', quit => 'quit', ); 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"); expect_handle->before() =~ /Ticket (\d+) created/; my $ticket_id = $1; ok($ticket_id, "Got ticket id=$ticket_id"); expect_send(q{create -t ticket set subject='new ticket'}, "Creating a ticket as just a subject..."); expect_like(qr/Ticket \d+ created/, "Created the ticket"); # make sure we can request things as 'rt foo' expect_send(q{rt create -t ticket set subject='rt ticket'}, "Creating a ticket with 'rt create'..."); expect_like(qr/Ticket \d+ created/, "Created the ticket"); # creating queues expect_send("create -t queue set Name='NewQueue$$'", 'Creating a queue...'); expect_like(qr/Queue \d+ created/, 'Created the queue'); expect_handle->before() =~ /Queue (\d+) created/; my $queue_id = $1; ok($queue_id, "Got queue id=$queue_id"); # updating users expect_send("edit queue/$queue_id set Name='EditedQueue$$'", 'Editing the queue'); expect_like(qr/Queue $queue_id updated/, 'Edited the queue'); expect_send("show queue/$queue_id", 'Showing the queue...'); expect_like(qr/id: queue\/$queue_id/, 'Saw the queue'); expect_like(qr/Name: EditedQueue$$/, 'Saw the modification'); TODO: { todo_skip "Listing non-ticket items doesn't work", 2; expect_send("list -t queue 'id > 0'", 'Listing the queues...'); expect_like(qr/$queue_id: EditedQueue$$/, 'Found the queue'); } # Queues with spaces in their names expect_send("create -t queue set Name='Spaced Out'", 'Creating a queue...'); expect_like(qr/Queue \d+ created/, 'Created the queue'); expect_handle->before() =~ /Queue (\d+) created/; my $other_queue = $1; ok($other_queue, "Got queue id=$other_queue"); expect_send("show 'queue/Spaced Out'", 'Showing the queue...'); expect_like(qr/id: queue\/$other_queue/, 'Saw the queue'); expect_like(qr/Name: Spaced Out/, 'Saw the modification'); # Set up a custom field for editing tests my $cf = RT::CustomField->new(RT->SystemUser); my ($val,$msg) = $cf->Create(Name => 'MyCF'.$$, Type => 'FreeformSingle', Queue => $queue_id); ok($val,$msg); my $othercf = RT::CustomField->new(RT->SystemUser); ($val,$msg) = $othercf->Create(Name => 'My CF'.$$, Type => 'FreeformSingle', Queue => $queue_id); ok($val,$msg); my $multiple_cf = RT::CustomField->new(RT->SystemUser); ($val,$msg) = $multiple_cf->Create(Name => 'MultipleCF'.$$, Type => 'FreeformMultiple', Queue => $queue_id); ok($val,$msg); # add a comment to ticket expect_send("comment -m 'comment-$$' $ticket_id", "Adding a comment..."); expect_like(qr/Comments added/, "Added the comment"); ### should test to make sure it actually got added # add correspondance to ticket (?) expect_send("correspond -m 'correspond-$$' $ticket_id", "Adding correspondence..."); expect_like(qr/Correspondence added/, "Added the correspondence"); ### should test to make sure it actually got added my $test_email = RT::Test::get_relocatable_file('lorem-ipsum', (File::Spec->updir(), 'data', 'emails')); # add attachments to a ticket # text attachment check_attachment($test_email); # binary attachment check_attachment($RT::StaticPath . '/images/bpslogo.png'); # change a ticket's Owner expect_send("edit ticket/$ticket_id set owner=root", 'Changing owner...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed owner'); expect_send("show ticket/$ticket_id -f owner", 'Verifying change...'); expect_like(qr/Owner: root/, 'Verified change'); # change a ticket's Requestor expect_send("edit ticket/$ticket_id set requestors=foo\@example.com", 'Changing Requestor...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed Requestor'); expect_send("show ticket/$ticket_id -f requestors", 'Verifying change...'); expect_like(qr/Requestors: foo\@example.com/, 'Verified change'); # set multiple Requestors expect_send("edit ticket/$ticket_id set requestors=foo\@example.com,bar\@example.com", 'Changing Requestor...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed Requestor'); expect_send("show ticket/$ticket_id -f requestors", 'Verifying change...'); expect_like(qr/Requestors: bar\@example.com, foo\@example.com/, 'Verified change'); # change a ticket's Cc expect_send("edit ticket/$ticket_id set cc=bar\@example.com", 'Changing Cc...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed Cc'); expect_send("show ticket/$ticket_id -f cc", 'Verifying change...'); expect_like(qr/Cc: bar\@example.com/, 'Verified change'); # change a ticket's priority expect_send("edit ticket/$ticket_id set priority=10", 'Changing priority...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed priority'); expect_send("show ticket/$ticket_id -f priority", 'Verifying change...'); expect_like(qr/Priority: 10/, 'Verified change'); # move a ticket to a different queue expect_send("edit ticket/$ticket_id set queue=EditedQueue$$", 'Changing queue...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed queue'); expect_send("show ticket/$ticket_id -f queue", 'Verifying change...'); expect_like(qr/Queue: EditedQueue$$/, 'Verified change'); # cannot move ticket to a nonexistent queue expect_send("edit ticket/$ticket_id set queue=nonexistent-$$", 'Changing to nonexistent queue...'); expect_like(qr/Queue nonexistent-$$ does not exist/i, 'Errored out'); expect_send("show ticket/$ticket_id -f queue", 'Verifying lack of change...'); expect_like(qr/Queue: EditedQueue$$/, 'Verified lack of change'); # Test reading and setting custom fields without spaces expect_send("show ticket/$ticket_id -f CF-myCF$$", 'Checking initial value'); expect_like(qr/\QCF.{myCF$$}\E:/i, 'Verified initial empty value (CF-x syntax)'); expect_send("show ticket/$ticket_id -f CF.{myCF$$}", 'Checking initial value'); expect_like(qr/\QCF.{myCF$$}\E:/i, 'Verified initial empty value (CF.{x} syntax)'); expect_send("edit ticket/$ticket_id set 'CF-myCF$$=VALUE' ", 'Changing CF...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed cf'); expect_send("show ticket/$ticket_id -f CF-myCF$$", 'Checking new value'); expect_like(qr/\QCF.{myCF$$}\E: VALUE/i, 'Verified change'); # Test setting 0 as value of the custom field expect_send("edit ticket/$ticket_id set 'CF-myCF$$=0' ", 'Changing CF...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed cf'); expect_send("show ticket/$ticket_id -f CF-myCF$$", 'Checking new value'); expect_like(qr/\QCF.{myCF$$}\E: 0/i, 'Verified change'); expect_send("edit ticket/$ticket_id set 'CF.{myCF$$}=VALUE' ",'Changing CF...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed cf'); expect_send("show ticket/$ticket_id -f CF.{myCF$$}", 'Checking new value'); expect_like(qr/\QCF.{myCF$$}\E: VALUE/i, 'Verified change'); # Test setting 0 as value of the custom field expect_send("edit ticket/$ticket_id set 'CF.{myCF$$}=0' ", 'Changing CF...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed cf'); expect_send("show ticket/$ticket_id -f CF.{myCF$$}", 'Checking new value'); expect_like(qr/\QCF.{myCF$$}\E: 0/i, 'Verified change'); # Test reading and setting custom fields with spaces expect_send("show ticket/$ticket_id -f 'CF-my CF$$'", 'Checking initial value'); expect_like(qr/\QCF.{my CF$$}\E:/i, 'Verified change'); expect_send("edit ticket/$ticket_id set 'CF-my CF$$=VALUE' ", 'Changing CF...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed cf'); expect_send("show ticket/$ticket_id -f 'CF-my CF$$'", 'Checking new value'); expect_like(qr/\QCF.{my CF$$}\E: VALUE/i, 'Verified change'); expect_send("ls -l 'id = $ticket_id' -f 'CF-my CF$$'", 'Checking new value'); expect_like(qr/\QCF.{my CF$$}\E: VALUE/i, 'Verified change'); expect_send("show ticket/$ticket_id -f 'CF.{my CF$$}'", 'Checking initial value'); expect_like(qr/\QCF.{my CF$$}\E: VALUE/i, 'Verified change'); expect_send("edit ticket/$ticket_id set 'CF.{my CF$$}=NEW' ", 'Changing CF...'); expect_send("show ticket/$ticket_id -f 'CF.{my CF$$}'", 'Checking new value'); expect_like(qr/\QCF.{my CF$$}\E: NEW/i, 'Verified change'); expect_send("ls -l 'id = $ticket_id' -f 'CF.{my CF$$}'", 'Checking new value'); expect_like(qr/\QCF.{my CF$$}\E: NEW/i, 'Verified change'); # Test reading and setting single value custom field with commas or quotes expect_send("show ticket/$ticket_id -f CF-myCF$$", 'Checking initial value'); expect_like(qr/\QCF.{myCF$$}\E:/i, 'Verified change'); expect_send("edit ticket/$ticket_id set CF-myCF$$=1,2,3", 'Changing CF...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed cf'); expect_send("show ticket/$ticket_id -f CF-myCF$$", 'Checking new value'); expect_like(qr/\QCF.{myCF$$}\E: 1,2,3/i, 'Verified change'); expect_send(qq{edit ticket/$ticket_id set CF-myCF$$="1's,2,3"}, 'Changing CF...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed cf'); expect_send("show ticket/$ticket_id -f CF-myCF$$", 'Checking new value'); expect_like(qr/\QCF.{myCF$$}\E: 1's,2,3/i, 'Verified change'); # Test reading and setting custom fields with multiple values expect_send("show ticket/$ticket_id -f CF-MultipleCF$$", 'Checking initial value'); expect_like(qr/\QCF.{MultipleCF$$}\E:/i, 'Verified multiple cf change'); expect_send("edit ticket/$ticket_id set CF.{MultipleCF$$}=1,2,3 ", 'Changing CF...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed multiple cf'); expect_send("show ticket/$ticket_id -f CF.{MultipleCF$$}", 'Checking new value'); expect_like(qr/\QCF.{MultipleCF$$}\E: 1,\s*2,\s*3/i, 'Verified multiple cf change'); expect_send("edit ticket/$ticket_id set CF.{MultipleCF$$}=a,b,c ", 'Changing CF...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed multiple cf'); expect_send("show ticket/$ticket_id -f CF.{MultipleCF$$}", 'Checking new value'); expect_like(qr/\QCF.{MultipleCF$$}\E: a,\s*b,\s*c/i, 'Verified change'); expect_send("edit ticket/$ticket_id del CF.{MultipleCF$$}=a", 'Changing CF...'); expect_like(qr/Ticket $ticket_id updated/, 'del multiple cf'); expect_send("show ticket/$ticket_id -f CF.{MultipleCF$$}", 'Checking new value'); expect_like(qr/\QCF.{MultipleCF$$}\E: b,\s*c/i, 'Verified multiple cf change'); expect_send("edit ticket/$ticket_id add CF.{MultipleCF$$}=o", 'Changing CF...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed multiple cf'); expect_send("show ticket/$ticket_id -f CF.{MultipleCF$$}", 'Checking new value'); expect_like(qr/\QCF.{MultipleCF$$}\E: b,\s*c,\s*o/i, 'Verified multiple cf change'); sub multi_round_trip { my ($op, $value, $regex) = @_; $Test::Builder::Level++; # The outer double quotes are for the argument parsing that the # command-line does; the extra layer of escaping is to for them, as # well. It is equivilent to the quoting that the shell would # require. my $quoted = $value; $quoted =~ s/(["\\])/\\$1/g; expect_send(qq{edit ticket/$ticket_id $op CF.{MultipleCF$$}="$quoted"}, qq{CF $op $value}); expect_like(qr/Ticket $ticket_id updated/, qq{Got expected "updated" answer}); expect_send(qq{show ticket/$ticket_id -f CF.{MultipleCF$$}}, qq{Sent "show"}); expect_like(qr/\QCF.{MultipleCF$$}\E: $regex$/i, qq{Answer matches $regex}); } # Test simple quoting my $ticket = RT::Ticket->new($RT::SystemUser); $ticket->Load($ticket_id); multi_round_trip(set => q|'a,b,c'|, qr/'a,b,c'/); is($ticket->CustomFieldValues("MultipleCF$$")->Count, 1, "Has only one CF value"); is($ticket->FirstCustomFieldValue("MultipleCF$$"), q{a,b,c}, "And that CF value is as expected"); multi_round_trip(del => q|a|, qr/'a,b,c'/); is($ticket->CustomFieldValues("MultipleCF$$")->Count, 1, "Still has only one CF value"); is($ticket->FirstCustomFieldValue("MultipleCF$$"), q{a,b,c}, "And that CF value is as expected"); multi_round_trip(set => q|q{a,b,c}|, qr/'a,b,c'/); is($ticket->CustomFieldValues("MultipleCF$$")->Count, 1, "Still has only one CF value"); is($ticket->FirstCustomFieldValue("MultipleCF$$"), q{a,b,c}, "And that CF value is as expected"); multi_round_trip(del => q|a|, qr/'a,b,c'/); is($ticket->CustomFieldValues("MultipleCF$$")->Count, 1, "Still has only one CF value"); is($ticket->FirstCustomFieldValue("MultipleCF$$"), q{a,b,c}, "And that CF value is as expected"); multi_round_trip(del => q|'a,b,c'|, qr/\s*/); is($ticket->CustomFieldValues("MultipleCF$$")->Count, 0, "Now has no CF values"); multi_round_trip(set => q|q{1,2's,3}|, qr/'1,2\\'s,3'/); is($ticket->CustomFieldValues("MultipleCF$$")->Count, 1, "Still has only one CF value"); is($ticket->FirstCustomFieldValue("MultipleCF$$"), q{1,2's,3}, "And that CF value is as expected"); multi_round_trip(del => q|q{1,2's,3}|, qr/\s*/); is($ticket->CustomFieldValues("MultipleCF$$")->Count, 0, "Now has no CF values"); # Test escaping of quotes - generate (foo)(bar') with no escapes multi_round_trip(set => q|'foo',bar'|, qr/foo,bar'/); is($ticket->CustomFieldValues("MultipleCF$$")->Count, 2, "Has two values"); is($ticket->CustomFieldValues("MultipleCF$$")->First->Content, q|foo|, "Direct value checks out"); is($ticket->CustomFieldValues("MultipleCF$$")->Last->Content, q|bar'|, "Direct value checks out"); multi_round_trip(del => q|bar'|, qr/foo/); # With one \, generate (foo',bar) # We obviously need two \s in the following q|| string in order to # generate a string with one actual \ in it; this causes the string to, # in general, have twice as many \s in it as we wish to test. multi_round_trip(set => q|'foo\\',bar'|, qr/'foo\\',bar'/); is($ticket->CustomFieldValues("MultipleCF$$")->Count, 1, "Has one value"); is($ticket->CustomFieldValues("MultipleCF$$")->First->Content, q|foo',bar|, "Direct value checks out"); # With two \, generate (foo\)(bar') multi_round_trip(set => q|'foo\\\\',bar'|, qr/foo\\,bar'/); is($ticket->CustomFieldValues("MultipleCF$$")->Count, 2, "Has two values"); is($ticket->CustomFieldValues("MultipleCF$$")->First->Content, q|foo\\|, "Direct value checks out"); is($ticket->CustomFieldValues("MultipleCF$$")->Last->Content, q|bar'|, "Direct value checks out"); multi_round_trip(del => q|bar'|, qr/foo\\/); # With three \, generate (foo\',bar) multi_round_trip(set => q|'foo\\\\\\',bar'|, qr/'foo\\\\\\',bar'/); is($ticket->CustomFieldValues("MultipleCF$$")->Count, 1, "Has one value"); is($ticket->CustomFieldValues("MultipleCF$$")->First->Content, q|foo\\',bar|, "Direct value checks out"); # Check that we don't infinite-loop on 'foo'bar,baz; this should be ('foo'bar)(baz) expect_send("edit ticket/$ticket_id set CF.{MultipleCF$$}=\"'foo'bar,baz\"", 'Changing CF to have quotes not at commas'); expect_like(qr/Ticket $ticket_id updated/, 'Changed multiple cf'); is($ticket->CustomFieldValues("MultipleCF$$")->Count, 2, "Has two value"); is($ticket->CustomFieldValues("MultipleCF$$")->First->Content, q|'foo'bar|, "Direct value checks out"); is($ticket->CustomFieldValues("MultipleCF$$")->Last->Content, q|baz|, "Direct value checks out"); # ... # change a ticket's ...[other properties]... # ... # stall a ticket expect_send("edit ticket/$ticket_id set status=stalled", 'Changing status to "stalled"...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed status'); expect_send("show ticket/$ticket_id -f status", 'Verifying change...'); expect_like(qr/Status: stalled/, 'Verified change'); # resolve a ticket expect_send("edit ticket/$ticket_id set status=resolved", 'Changing status to "resolved"...'); expect_like(qr/Ticket $ticket_id updated/, 'Changed status'); expect_send("show ticket/$ticket_id -f status", 'Verifying change...'); expect_like(qr/Status: resolved/, 'Verified change'); # try to set status to an illegal value expect_send("edit ticket/$ticket_id set status=quux", 'Changing status to an illegal value...'); expect_like(qr/isn't a valid status/i, 'Errored out'); expect_send("show ticket/$ticket_id -f status", 'Verifying lack of change...'); expect_like(qr/Status: resolved/, 'Verified change'); # show ticket list expect_send("ls -s -t ticket -o +id \"Status='resolved'\"", 'Listing resolved tickets...'); expect_like(qr/$ticket_id: new ticket/, 'Found our ticket'); expect_send("ls -s -t ticket -f Requestors $ticket_id", 'getting Requestors'); expect_like(qr/$ticket_id\s+bar\@example.com,\s+foo\@example.com/, 'got Requestors'); # show ticket list verbosely expect_send("ls -l -t ticket -o +id \"Status='resolved'\"", 'Listing resolved tickets verbosely...'); expect_like(qr/id: ticket\/$ticket_id/, 'Found our ticket'); # show ticket expect_send("show -s -t ticket $ticket_id", 'Showing our ticket...'); expect_like(qr/id: ticket\/$ticket_id/, 'Got our ticket'); # show ticket history expect_send("show ticket/$ticket_id/history", 'Showing our ticket\'s history...'); expect_like(qr/Ticket created by root/, 'Got our history'); expect_send("show -v ticket/$ticket_id/history", 'Showing our ticket\'s history verbosely...'); TODO: { local $TODO = "Cannot show verbose ticket history right now"; # show ticket history verbosely expect_like(qr/Ticket created by root/, 'Got our history'); } # get attachments from a ticket expect_send("show -s ticket/$ticket_id/attachments", 'Showing ticket attachments...'); expect_like(qr/id: ticket\/$ticket_id\/attachments/, 'Got our ticket\'s attachments'); expect_like(qr/Attachments: \d+: \(Unnamed\) \(\S+ \/ \d+\w+\)/, 'Our ticket has an attachment'); expect_handle->before() =~ /Attachments: (\d+): \(Unnamed\) \((\S+)/; my $attachment_id = $1; my $attachment_type = $2; ok($attachment_id, "Got attachment id=$attachment_id $attachment_type"); expect_send("show -s ticket/$ticket_id/attachments/$attachment_id", "Showing attachment $attachment_id..."); expect_like(qr/ContentType: $attachment_type/, 'Got the attachment'); # creating users expect_send("create -t user set Name='NewUser$$' EmailAddress='fbar$$\@example.com'", 'Creating a user...'); expect_like(qr/User \d+ created/, 'Created the user'); expect_handle->before() =~ /User (\d+) created/; my $user_id = $1; ok($user_id, "Got user id=$user_id"); # updating users expect_send("edit user/$user_id set Name='EditedUser$$'", 'Editing the user'); expect_like(qr/User $user_id updated/, 'Edited the user'); expect_send("show user/$user_id", 'Showing the user...'); expect_like(qr/id: user\/$user_id/, 'Saw the user'); expect_like(qr/Name: EditedUser$$/, 'Saw the modification'); TODO: { todo_skip "Listing non-ticket items doesn't work", 2; expect_send("list -t user 'id > 0'", 'Listing the users...'); expect_like(qr/$user_id: EditedUser$$/, 'Found the user'); } TODO: { todo_skip "Group manipulation doesn't work right now", 8; # creating groups expect_send("create -t group set Name='NewGroup$$'", 'Creating a group...'); expect_like(qr/Group \d+ created/, 'Created the group'); expect_handle->before() =~ /Group (\d+) created/; my $group_id = $1; ok($group_id, "Got group id=$group_id"); # updating groups expect_send("edit group/$group_id set Name='EditedGroup$$'", 'Editing the group'); expect_like(qr/Group $group_id updated/, 'Edited the group'); expect_send("show group/$group_id", 'Showing the group...'); expect_like(qr/id: group\/$group_id/, 'Saw the group'); expect_like(qr/Name: EditedGroup$$/, 'Saw the modification'); TODO: { local $TODO = "Listing non-ticket items doesn't work"; expect_send("list -t group 'id > 0'", 'Listing the groups...'); expect_like(qr/$group_id: EditedGroup$$/, 'Found the group'); } } TODO: { todo_skip "Custom field manipulation not yet implemented", 8; # creating custom fields expect_send("create -t custom_field set Name='NewCF$$'", 'Creating a custom field...'); expect_like(qr/Custom Field \d+ created/, 'Created the custom field'); expect_handle->before() =~ /Custom Field (\d+) created/; my $cf_id = $1; ok($cf_id, "Got custom field id=$cf_id"); # updating custom fields expect_send("edit cf/$cf_id set Name='EditedCF$$'", 'Editing the custom field'); expect_like(qr/Custom field $cf_id updated/, 'Edited the custom field'); expect_send("show cf/$cf_id", 'Showing the queue...'); expect_like(qr/id: custom_field\/$cf_id/, 'Saw the custom field'); expect_like(qr/Name: EditedCF$$/, 'Saw the modification'); TODO: { todo_skip "Listing non-ticket items doesn't work", 2; expect_send("list -t custom_field 'id > 0'", 'Listing the CFs...'); expect_like(qr/$cf_id: EditedCF$$/, 'Found the custom field'); } } expect_send("create -t ticket set subject='CLIMergeTest1-$$'", 'Creating first ticket to merge...'); expect_like(qr/Ticket \d+ created/, 'Created first ticket'); expect_handle->before() =~ /Ticket (\d+) created/; my $merge_ticket_A = $1; ok($merge_ticket_A, "Got first ticket to merge id=$merge_ticket_A"); expect_send("create -t ticket set subject='CLIMergeTest2-$$'", 'Creating second ticket to merge...'); expect_like(qr/Ticket \d+ created/, 'Created second ticket'); expect_handle->before() =~ /Ticket (\d+) created/; my $merge_ticket_B = $1; ok($merge_ticket_B, "Got second ticket to merge id=$merge_ticket_B"); expect_send("merge $merge_ticket_B $merge_ticket_A", 'Merging the tickets...'); expect_like(qr/Merge completed/, 'Merged the tickets'); expect_send("show ticket/$merge_ticket_A/history", 'Checking merge on first ticket'); expect_like(qr/Merged into #$merge_ticket_A: CLIMergeTest1-$$ by root/, 'Merge recorded in first ticket'); expect_send("show ticket/$merge_ticket_B/history", 'Checking merge on second ticket'); expect_like(qr/Merged into #$merge_ticket_A: CLIMergeTest1-$$ by root/, 'Merge recorded in second ticket'); { # create a user; give them privileges to take and steal ### TODO: implement 'grant' in the CLI tool; use that here instead. ### this breaks the abstraction barrier, like, a lot. my $steal_user = RT::User->new(RT->SystemUser); my ($steal_user_id, $msg) = $steal_user->Create( Name => "fooser$$", EmailAddress => "fooser$$\@localhost", Privileged => 1, Password => 'foobar', ); ok($steal_user_id, "Created the user? $msg"); my $steal_queue = RT::Queue->new(RT->SystemUser); my $steal_queue_id; ($steal_queue_id, $msg) = $steal_queue->Create( Name => "Steal$$" ); ok($steal_queue_id, "Got the queue? $msg"); ok($steal_queue->id, "queue obj has id"); my $status; ($status, $msg) = $steal_user->PrincipalObj->GrantRight( Right => 'ShowTicket', Object => $steal_queue ); ok($status, "Gave 'ShowTicket' to our user? $msg"); ($status, $msg) = $steal_user->PrincipalObj->GrantRight( Right => 'OwnTicket', Object => $steal_queue ); ok($status, "Gave 'OwnTicket' to our user? $msg"); ($status, $msg) = $steal_user->PrincipalObj->GrantRight( Right => 'StealTicket', Object => $steal_queue ); ok($status, "Gave 'StealTicket' to our user? $msg"); ($status, $msg) = $steal_user->PrincipalObj->GrantRight( Right => 'TakeTicket', Object => $steal_queue ); ok($status, "Gave 'TakeTicket' to our user? $msg"); # create a ticket to take/steal expect_send("create -t ticket set queue=$steal_queue_id subject='CLIStealTest-$$'", 'Creating ticket to steal...'); expect_like(qr/Ticket \d+ created/, 'Created ticket'); expect_handle->before() =~ /Ticket (\d+) created/; my $steal_ticket_id = $1; ok($steal_ticket_id, "Got ticket to steal id=$steal_ticket_id"); # root takes the ticket expect_send("take $steal_ticket_id", 'root takes the ticket...'); expect_like(qr/Owner changed from Nobody to root/, 'root took the ticket'); expect_quit(); # log in as the non-root user $ENV{'RTUSER'} = "fooser$$"; $ENV{'RTPASSWD'} = 'foobar'; expect_run( command => "$rt_tool_path shell", prompt => 'rt> ', quit => 'quit',); # user tries to take the ticket, fails # shouldn't be able to 'take' a ticket which someone else has taken out from # under you; that should produce an error. should have to explicitly # 'steal' it back from them. 'steal' can automatically 'take' a ticket, # though. expect_send("take $steal_ticket_id", 'user tries to take the ticket...'); expect_like(qr/You can only take tickets that are unowned/, '...and fails.'); expect_send("show ticket/$steal_ticket_id -f owner", 'Double-checking...'); expect_like(qr/Owner: root/, '...no change.'); # user steals the ticket expect_send("steal $steal_ticket_id", 'user tries to *steal* the ticket...'); expect_like(qr/Owner changed from root to fooser$$/, '...and succeeds!'); expect_send("show ticket/$steal_ticket_id -f owner", 'Double-checking...'); expect_like(qr/Owner: fooser$$/, '...yup, it worked.'); expect_quit(); # log back in as root $ENV{'RTUSER'} = 'root'; $ENV{'RTPASSWD'} = 'password'; expect_run( command => "$rt_tool_path shell", prompt => 'rt> ', quit => 'quit',); # root steals the ticket back expect_send("steal $steal_ticket_id", 'root steals the ticket back...'); expect_like(qr/Owner changed from fooser$$ to root/, '...and succeeds.'); } my @link_relns = ( 'DependsOn', 'DependedOnBy', 'RefersTo', 'ReferredToBy', 'MemberOf', 'HasMember', ); my %display_relns = map { $_ => $_ } @link_relns; $display_relns{HasMember} = 'Members'; my $link1_id = ok_create_ticket( "LinkTicket1-$$" ); my $link2_id = ok_create_ticket( "LinkTicket2-$$" ); foreach my $reln (@link_relns) { # create link expect_send("link $link1_id $reln $link2_id", "Link by $reln..."); 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..."); expect_like(qr/Deleted link $link1_id $reln $link2_id/, 'Deleted'); expect_send("show ticket/$link1_id/links", "Checking removal of $reln..."); ok( expect_handle->before() !~ /\Q$display_relns{$reln}: \E[\w\d\.\-]+:\/\/[w\d\.]+\/ticket\/$link2_id/, "Removed link $reln" ); #expect_unlike(qr/\Q$reln: \E[\w\d\.]+\Q://\E[w\d\.]+\/ticket\/$link2_id/, "Removed link $reln"); } expect_quit(); # We need to do this ourselves, so that we quit # *before* we tear down the webserver. # helper function sub ok_create_ticket { my $subject = shift; expect_send("create -t ticket set subject='$subject'", 'Creating ticket...'); expect_like(qr/Ticket \d+ created/, "Created ticket '$subject'"); expect_handle->before() =~ /Ticket (\d+) created/; my $id = $1; ok($id, "Got ticket id=$id"); return $id; } # wrap up all the file handling stuff for attachment testing sub check_attachment { my $attachment_path = shift; (my $filename = $attachment_path) =~ s/.*\/(.*)$/$1/; expect_send("comment -m 'attach file' -a $attachment_path $ticket_id", "Adding an attachment ($filename)"); expect_like(qr/Comments added/, "Added the attachment"); expect_send("show ticket/$ticket_id/attachments","Finding Attachment"); my $attachment_regex = qr/(\d+):\s+$filename/; expect_like($attachment_regex,"Attachment Uploaded"); expect_handle->before() =~ $attachment_regex; my $attachment_id = $1; expect_send("show ticket/$ticket_id/attachments/$attachment_id/content","Fetching Attachment"); open( my $fh, '<', $attachment_path ) or die "Can't open $attachment_path: $!"; my $attachment_content = do { local($/); <$fh> }; close $fh; chomp $attachment_content; TODO: { local $TODO = "Binary PNG content is getting mangled somewhere along the way" if $attachment_path =~ /\.png$/; is( MIME::Base64::encode_base64(Test::Expect::before()), MIME::Base64::encode_base64($attachment_content), "Attachment contains original text" ); } } # you may encounter warning like Use of uninitialized value $ampm # ... in Time::ParseDate my @warnings = grep { $_ !~ /\$ampm/ } $m->get_warnings; is( scalar @warnings, 0, 'no extra warnings' ); done_testing; 1; # needed to avoid a weird exit value from expect_quit rt-5.0.1/t/web/ticket_txn_subject.t000644 000765 000024 00000005225 14005011336 020105 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ($base, $m) = RT::Test->started_ok; ok $m->login, 'logged in'; my @tickets; diag "create a ticket via the API"; { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $txn, $msg) = $ticket->Create( Queue => 'General', Subject => Encode::decode("UTF-8",'bad subject‽'), ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Subject, Encode::decode("UTF-8",'bad subject‽'), 'correct subject'; push @tickets, $id; } diag "create a ticket via the web"; { $m->get_ok('/Ticket/Create.html?Queue=1', 'open ticket create page'); $m->submit_form_ok({ with_fields => { Subject => Encode::decode("UTF-8",'bad subject #2‽'), }, button => 'SubmitTicket' }, 'create ticket'); $m->content_contains(Encode::decode("UTF-8",'bad subject #2‽'), 'correct subject'); push @tickets, 2; } diag "create a ticket via the web without a unicode subject"; { $m->get_ok('/Ticket/Create.html?Queue=1', 'open ticket create page'); $m->submit_form_ok({ with_fields => { Subject => 'a fine subject #3', }, button => 'SubmitTicket' }, 'create ticket'); $m->content_contains('a fine subject #3', 'correct subject'); push @tickets, 3; } for my $tid (@tickets) { diag "ticket #$tid"; diag "add a reply which adds to the subject, but without an attachment"; { $m->goto_ticket($tid); $m->follow_link_ok({ id => 'page-actions-reply' }, "Actions -> Reply"); $m->submit_form_ok({ with_fields => { UpdateSubject => Encode::decode("UTF-8",'bad subject‽ without attachment'), UpdateContent => 'testing unicode txn subjects', }, button => 'SubmitTicket', }, 'submit reply'); $m->content_contains(Encode::decode("UTF-8",'bad subject‽ without attachment'), "found txn subject"); } diag "add a reply which adds to the subject with an attachment"; { $m->goto_ticket($tid); $m->follow_link_ok({ id => 'page-actions-reply' }, "Actions -> Reply"); $m->submit_form_ok({ with_fields => { UpdateSubject => Encode::decode("UTF-8",'bad subject‽ with attachment'), UpdateContent => 'testing unicode txn subjects', Attach => RT::Test::get_relocatable_file('bpslogo.png', '..', 'data'), }, button => 'SubmitTicket', }, 'submit reply'); $m->content_contains(Encode::decode("UTF-8",'bad subject‽ with attachment'), "found txn subject"); } } done_testing; rt-5.0.1/t/web/ticket_modify_people.t000644 000765 000024 00000004744 14005011336 020415 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my $root = RT::Test->load_or_create_user( Name => 'root' ); my $group_foo = RT::Group->new($RT::SystemUser); my ( $ret, $msg ) = $group_foo->CreateUserDefinedGroup( Name => 'group_foo', Description => 'group_foo', ); ok( $ret, 'created group_foo' ); my $ticket = RT::Test->create_ticket( Subject => 'test modify people', Queue => 'General', Requestor => $root->id, Cc => $group_foo->id, ); my $user = RT::Test->load_or_create_user( Name => 'user', Password => 'password', ); ok $user && $user->id, 'loaded or created user'; ok( RT::Test->set_rights( { Principal => $user, Right => [qw(SeeQueue ShowTicket ModifyTicket)] }, ), 'set rights' ); my ( $url, $m ) = RT::Test->started_ok; ok( $m->login( 'user', 'password' ), 'logged in' ); $m->get_ok( $url . "/Ticket/ModifyPeople.html?id=" . $ticket->id ); ok( $m->find_link( text => 'root (Enoch Root)', url_regex => qr!/User/Summary\.html!, ), 'contains link to user summary page' ); $m->content_contains('Enoch Root', 'still has the user name' ); ok( !$m->find_link( text => 'group_foo', url_regex => qr!/Admin/Groups/Modify\.html!, ), 'no link to modify group' ); $m->content_contains('group_foo', 'still has the group name' ); ok( RT::Test->add_rights( { Principal => $user, Right => ['ShowConfigTab'] }, ), 'added ShowConfigTab right', ); $m->reload; ok( $m->find_link( text => 'root (Enoch Root)', url_regex => qr!/User/Summary\.html!, ), 'still contains link to user summary page' ); ok( !$m->find_link( text => 'group_foo', url_regex => qr!/Admin/Groups/Modify\.html!, ), 'still no link to modify group' ); ok( RT::Test->add_rights( { Principal => $user, Right => ['AdminGroup'] }, ), 'added AdminGroup right' ); $m->reload; ok( $m->find_link( text => 'group_foo', url_regex => qr!/Admin/Groups/Modify\.html!, ), 'got link to modify group' ); $m->submit_form_ok({ with_fields => { WatcherTypeEmail1 => 'Cc', WatcherAddressEmail1 => '"Foo Bar" ', }, button => 'SubmitTicket', }, "Added email with phrase as watcher"); my $foo = RT::Test->load_or_create_user( EmailAddress => 'foo@example.com' ); is $foo->RealName, "Foo Bar", "RealName matches"; undef $m; done_testing; # TODO test Add|Delete people rt-5.0.1/t/web/requestor_groups_limit.t000644 000765 000024 00000001702 14005011336 021034 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 11; diag "set groups limit to 1"; RT->Config->Set( ShowMoreAboutPrivilegedUsers => 1 ); RT->Config->Set( MoreAboutRequestorGroupsLimit => 1 ); my $ticket = RT::Ticket->new(RT->SystemUser); my ($id) = $ticket->Create( Subject => 'groups limit', Queue => 'General', Requestor => 'root@localhost', ); ok( $id, 'created ticket' ); my ( $url, $m ) = RT::Test->started_ok; ok( $m->login(), 'logged in as root' ); $m->goto_ticket($id); $m->content_like( qr/Everyone|Privileged/, 'got one group' ); $m->content_unlike( qr/Everyone.*?Privileged/, 'not 2 groups' ); RT::Test->stop_server; diag "set groups limit to 2"; RT->Config->Set( MoreAboutRequestorGroupsLimit => 2 ); ( $url, $m ) = RT::Test->started_ok; ok( $m->login(), 'logged in as root' ); $m->goto_ticket($id); $m->content_contains( 'Everyone', 'got the first group' ); $m->content_contains( 'Privileged', 'got the second group' ); rt-5.0.1/t/web/sidebyside_layout.t000644 000765 000024 00000002313 14005011336 017726 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 11; RT->Config->Set( UseSideBySideLayout => 0 ); my $root = RT::Test->load_or_create_user( Name => 'root', ); my ( $status, $msg ) = $root->SetPreferences( $RT::System => { %{ $root->Preferences($RT::System) || {} }, 'UseSideBySideLayout' => 1 } ); ok( $status, 'use side by side layout for root' ); my $user_a = RT::Test->load_or_create_user( Name => 'user_a', Password => 'password', ); ok( $user_a->id, 'created user_a' ); ok( RT::Test->set_rights( { Principal => $user_a, Right => ['CreateTicket'] }, ), 'granted user_a the right of CreateTicket' ); my ( $url, $m ) = RT::Test->started_ok; $m->login; $m->get_ok( $url . '/Ticket/Create.html?Queue=1', "root's ticket create page" ); $m->content_like( qr/]*class="[^>"]*\bsidebyside\b/, 'found sidebyside css for root' ); my $m_a = RT::Test::Web->new; ok $m_a->login( 'user_a', 'password' ), 'logged in as user_a'; $m_a->get_ok( $url . '/Ticket/Create.html?Queue=1', "user_a's ticket create page" ); $m_a->content_unlike( qr/]*class="[^>"]*\bsidebyside\b/, "didn't find sidebyside class for user_a" ); rt-5.0.1/t/web/attach-from-txn.t000644 000765 000024 00000015700 14005011336 017225 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 70; my $LogoName = 'image.png'; my $ImageName = 'owls.jpg'; my $LogoFile = RT::Test::get_relocatable_file($LogoName, '..', 'data'); my $ImageFile = RT::Test::get_relocatable_file($ImageName, '..', 'data'); # reply to ticket = nothing # reply to correspond = getting the right list # maintaining the checked ones # storing the header # getting attached to mail # showing up in the web history my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in'; my $queue = RT::Queue->new(RT->Nobody); my $qid = $queue->Load('General'); ok( $qid, "Loaded General queue" ); # Create ticket $m->form_name('CreateTicketInQueue'); $m->field('Queue', $qid); $m->field('Requestors', 'owls@localhost'); $m->submit; is($m->status, 200, "request successful"); $m->content_contains("Create a new ticket", 'ticket create page'); $m->form_name('TicketCreate'); $m->field('Subject', 'Attachments test'); $m->field('Content', 'Some content'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); $m->content_contains('Attachments test', 'we have subject on the page'); $m->content_contains('Some content', 'and content'); # Reply with uploaded attachments $m->follow_link_ok({class => 'reply-link'}, "reply to the ticket"); $m->content_lacks('AttachExisting'); $m->form_name('TicketUpdate'); $m->field('Attach', $LogoFile); $m->click('AddMoreAttach'); is($m->status, 200, "request successful"); $m->form_name('TicketUpdate'); $m->field('Attach', $ImageFile); $m->field('UpdateContent', 'Message'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); ok( $m->find_link( text => $LogoName, url_regex => qr{Attachment/} ), 'page has file link'); ok( $m->find_link( text => $ImageName, url_regex => qr{Attachment/} ), 'page has file link'); # clear mail catcher RT::Test->fetch_caught_mails; # Reply to first correspondence, including an attachment $m->follow_link_ok({class => 'reply-link', n => 2}, "reply to the reply"); $m->content_contains('AttachExisting'); $m->content_contains($LogoName); $m->content_contains($ImageName); # check stuff $m->form_name('TicketUpdate'); $m->current_form->find_input('AttachExisting', 'checkbox', 2)->check; # owls.jpg $m->click('AddMoreAttach'); is($m->status, 200, "request successful"); # ensure it's still checked $m->form_name('TicketUpdate'); ok $m->current_form->find_input('AttachExisting', 'checkbox', 2)->value, 'still checked'; $m->field('UpdateContent', 'Here are some attachments'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); # yep, we got it and processed the header! $m->content_contains('Here are some attachments'); $m->content_like(qr/RT-Attach:.+?\Q$ImageName\E/s, 'found rt attach header'); # outgoing looks good $m->follow_link_ok( { url_regex => qr/ShowEmailRecord/, n => 3 } ); $m->content_like(qr/RT-Attach: \d+/, "found RT-Attach header"); $m->content_like(qr/RT-Attachment: \d+\/\d+\/\d+/, "found RT-Attachment header"); $m->content_lacks($ImageName); $m->back; # check that it got into mail my @mails = RT::Test->fetch_caught_mails; is scalar @mails, 1, "got one outgoing email"; my $mail = shift @mails; like $mail, qr/To: owls\@localhost/, 'got To'; like $mail, qr/RT-Attach: \d+/, "found attachment we expected"; like $mail, qr/RT-Attachment: \d+\/\d+\/\d+/, "found RT-Attachment header"; like $mail, qr/filename=.?\Q$ImageName\E.?/, "found filename"; # Reply to first correspondence, including an attachment with an uploaded one $m->follow_link_ok({class => 'reply-link', n => 3}, "reply to the reply"); $m->form_name('TicketUpdate'); $m->current_form->find_input('AttachExisting', 'checkbox', 2)->check; # owls.jpg $m->field( 'UpdateContent', 'attachments from both list and upload' ); $m->field('Attach', $LogoFile); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); # yep, we got it and processed the header! $m->content_contains('attachments from both list and upload'); $m->content_like(qr/(RT-Attach:.+?\Q$ImageName\E).*\1/s, 'found rt attach header'); $m->content_like(qr/Subject:.+?\Q$LogoName\E/s, 'found rt attach header'); # outgoing looks good $m->follow_link_ok( { url_regex => qr/ShowEmailRecord/, n => 4 } ); $m->content_like(qr/RT-Attach: \d+/, "found RT-Attach header"); $m->content_like(qr/RT-Attachment: \d+\/\d+\/\d+/, "found RT-Attachment header"); $m->content_lacks($ImageName); $m->content_lacks($LogoName); $m->back; # check that it got into mail @mails = RT::Test->fetch_caught_mails; is scalar @mails, 1, "got one outgoing email"; $mail = shift @mails; like $mail, qr/To: owls\@localhost/, 'got To'; like $mail, qr/RT-Attach: \d+/, "found attachment we expected"; like $mail, qr/RT-Attachment: \d+\/\d+\/\d+/, "found RT-Attachment header"; like $mail, qr/filename=.?\Q$ImageName\E.?/, "found selected filename"; like $mail, qr/filename=.?\Q$LogoName\E.?/, "found uploaded filename"; # add header to template, make a normal reply, and see that it worked my $link = $m->find_link( url_regex => qr/Attachment\/\d+\/\d+\/$LogoName/ ); ok $link; my ($LogoId) = $link->url =~ /Attachment\/\d+\/(\d+)/; ok $LogoId; my $template = RT::Template->new( RT->SystemUser ); $template->LoadGlobalTemplate('Correspondence in HTML'); ok $template->Id; my $old_template = $template->Content; $template->SetContent( "RT-Attach: $LogoId\n" . $template->Content ); like $template->Content, qr/RT-Attach:/, "updated template"; # reply... $m->follow_link_ok({class => 'reply-link'}, "reply to the ticket"); $m->form_name('TicketUpdate'); $m->field('UpdateContent', 'who gives a hoot'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); $m->content_contains('who gives a hoot'); # then see if we got the right mail @mails = RT::Test->fetch_caught_mails; is scalar @mails, 1, "got one outgoing email"; $mail = shift @mails; like $mail, qr/To: owls\@localhost/, 'got To'; like $mail, qr/RT-Attach: $LogoId/, "found attachment we expected"; like $mail, qr/RT-Attachment: \d+\/\d+\/$LogoId/, "found RT-Attachment header"; like $mail, qr/filename=.?\Q$LogoName\E.?/, "found filename"; # create a new, privileged user who can't see this ticket but can reply $template->SetContent($old_template); unlike $template->Content, qr/RT-Attach:/, "no header in template anymore"; my $peter = RT::Test->load_or_create_user( Name => 'peter', EmailAddress => 'peter@localhost', ); ok( RT::Test->add_rights({ Principal => 'Everyone', Right => [qw(ReplyToTicket)] }), 'add ReplyToTicket rights'); my $ticket = RT::Ticket->new($peter); $ticket->Load(1); ok $ticket->Id, "loaded ticket"; my ($ok, $msg, $txn) = $ticket->Correspond( AttachExisting => $LogoId, Content => 'Hi' ); ok $ok, $msg; # check mail that went out doesn't contain the logo @mails = RT::Test->fetch_caught_mails; is scalar @mails, 1, "got one outgoing email"; $mail = shift @mails; like $mail, qr/RT-Attach: $LogoId/, "found header we expected"; unlike $mail, qr/RT-Attachment: \d+\/\d+\/$LogoId/, "lacks RT-Attachment header"; unlike $mail, qr/filename=.?\Q$LogoName\E.?/, "lacks filename"; rt-5.0.1/t/web/transaction_batch.t000644 000765 000024 00000004661 14005011336 017703 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => 12; my $q = RT::Test->load_or_create_queue ( Name => 'General' ); my $s1 = RT::Scrip->new(RT->SystemUser); my ($val, $msg) =$s1->Create( Queue => $q->Id, ScripCondition => 'User Defined', ScripAction => 'User Defined', CustomIsApplicableCode => 'return ($self->TransactionObj->Field||"") eq "TimeEstimated"', CustomPrepareCode => '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', ); ok($val,$msg); my $ticket = RT::Ticket->new(RT->SystemUser); my ($tv,$ttv,$tm) = $ticket->Create(Queue => $q->Id, Subject => "hair on fire", ); ok($tv, $tm); # Flush the Create transaction off of the ticket $ticket->ApplyTransactionBatch; my $testuser = RT::Test->load_or_create_user( Name => 'bob', EmailAddress => 'bob@example.com', Password => 'password' ); ok($testuser->Id, "Created test user bob"); ok( RT::Test->add_rights({ Principal => 'Privileged', Right => [qw(ShowTicket ModifyTicket SeeQueue)]}), 'Granted ticket management rights'); my $test_current_user = RT::CurrentUser->new(); $test_current_user->LoadByName($testuser->Name); my $api_test = RT::Ticket->new($test_current_user); $api_test->Load($ticket->Id); is($api_test->Priority,0,"Ticket priority starts at 0"); $api_test->SetTimeEstimated(12); $api_test->ApplyTransactionBatch; is($api_test->CurrentUser->UserObj->Name, $testuser->Name,"User didn't change running Transaction Batch scrips"); $api_test->Load($api_test->Id); is($api_test->Priority,2,"Ticket priority updated"); my ($baseurl, $m) = RT::Test->started_ok; $m->login('bob','password'); $m->get_ok("$baseurl/Ticket/Modify.html?id=".$ticket->Id); $m->submit_form( form_name => 'TicketModify', fields => { TimeEstimated => 5 } ); $ticket->Load($ticket->Id); is ($ticket->Priority , 4, "Ticket priority is set right"); rt-5.0.1/t/web/command_line_cf_edge_cases.t000644 000765 000024 00000005176 14005011336 021456 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::Expect; use RT::Test tests => 100, actual_server => 1; my ( $baseurl, $m ) = RT::Test->started_ok; my $rt_tool_path = "$RT::BinPath/rt"; $ENV{'RTUSER'} = 'root'; $ENV{'RTPASSWD'} = 'password'; $ENV{'RTSERVER'} = RT->Config->Get('WebBaseURL'); $ENV{'RTDEBUG'} = '1'; $ENV{'RTCONFIG'} = '/dev/null'; my @cfs = ( 'foo=bar', 'foo.bar', 'foo:bar', 'foo bar', 'foo{bar}', 'foo-bar', 'foo()bar', ); for my $name (@cfs) { RT::Test->load_or_create_custom_field( Name => $name, Type => 'Freeform', MaxValues => 1, Queue => 0, ); } expect_run( command => "$rt_tool_path shell", prompt => 'rt> ', quit => 'quit', ); # create a ticket for my $name (@cfs) { expect_send( qq{create -t ticket set subject='test cf $name' 'CF.{$name}=foo:b a.r=baz'}, "creating a ticket for cf $name" ); expect_handle->before() =~ /Ticket (\d+) created/; my $ticket_id = $1; expect_send( "show ticket/$ticket_id -f 'CF.{$name}'", 'checking new value' ); expect_like( qr/CF\.\Q{$name}\E: foo:b a\.r=baz/i, 'verified change' ); expect_send( "edit ticket/$ticket_id set 'CF.{$name}=bar'", "changing cf $name to bar" ); expect_like( qr/Ticket $ticket_id updated/, 'changed cf' ); expect_send( "show ticket/$ticket_id -f 'CF.{$name}'", 'checking new value' ); expect_like( qr/CF\.\Q{$name}\E: bar/i, 'verified change' ); expect_send( qq{create -t ticket set subject='test cf $name' 'CF-$name=foo:b a.r=baz'}, "creating a ticket for cf $name" ); expect_handle->before() =~ /Ticket (\d+) created/; $ticket_id = $1; expect_send( "show ticket/$ticket_id -f 'CF-$name'", 'checking new value' ); if ( $name eq 'foo=bar' ) { expect_like( qr/CF\.\Q{$name}\E: $/mi, "can't use = in cf name with old style" ); } else { expect_like( qr/CF\.\Q{$name}\E: foo:b a\.r=baz/i, 'verified change' ); expect_send( "edit ticket/$ticket_id set 'CF-$name=bar'", "changing cf $name to bar" ); expect_like( qr/Ticket $ticket_id updated/, 'changed cf' ); expect_send( "show ticket/$ticket_id -f 'CF-$name'", 'checking new value' ); expect_like( qr/CF\.\Q{$name}\E: bar/i, 'verified change' ); } } my @invalid = ('foo,bar'); for my $name (@invalid) { expect_send( qq{create -t ticket set subject='test cf $name' 'CF.{$name}=foo'}, "creating a ticket for cf $name" ); expect_like( qr/You shouldn't specify objects as arguments to create/i, '$name is not a valid cf name' ); } expect_quit(); rt-5.0.1/t/web/rights.t000644 000765 000024 00000023022 14005011336 015505 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, "logged in"; sub get_rights { my $agent = shift; my $principal_id = shift; my $object = shift; my $form_name = shift; $agent->form_name($form_name); my @inputs = $agent->current_form->find_input("SetRights-$principal_id-$object"); my @rights = sort grep $_, map $_->possible_values, grep $_ && $_->value, @inputs; return @rights; }; sub test_role { my $role_name = shift; my $right_name = shift; $m->follow_link_ok({ id => 'admin-global-group-rights'}); diag "load $role_name role group"; my $group = RT::Group->new( RT->SystemUser ); $group->LoadRoleGroup( Object => RT->System, Name => $role_name ); ok($group->id, "loaded '$role_name' role group"); rights_for_group_ok ( $group, $role_name, $right_name, 'ModifyGroupRights' ); } sub test_system_internal_group { my $group_name = shift; my $right_name = shift; $m->follow_link_ok({ id => 'admin-global-group-rights'}); diag "load $group_name group"; my $group = RT::Group->new( RT->SystemUser ); $group->LoadSystemInternalGroup($group_name); ok($group->id, "loaded '$group_name' system internal group"); rights_for_group_ok ( $group, $group_name, $right_name, 'ModifyGroupRights' ); } sub test_user_defined_group { my $user_group = shift; my $group_name = shift; my $right_name = shift; my $user_group_id = $user_group->id; my $user_group_name = $user_group->Name; $m->get_ok("/Admin/Groups/GroupRights.html?id=$user_group_id"); diag "load $user_group_name group"; my $group = RT::Group->new( RT->SystemUser ); $group->LoadSystemInternalGroup($group_name); ok($group->id, "loaded '$group_name' system internal group"); rights_for_group_ok ( $group, $group_name, $right_name, 'ModifyGroupRights', "RT::Group-$user_group_id", $user_group); } sub test_user { my $user_name = shift; my $right_name = shift; $m->follow_link_ok({ id => 'admin-global-user-rights'}); diag "load $user_name"; my $user = RT::User->new( RT->SystemUser ); $user->Load($user_name); ok($user->id, "loaded user '$user_name'"); diag "load $user_name group"; my $group = RT::Group->new( RT->SystemUser ); $group->LoadACLEquivalenceGroup($user->PrincipalId); ok($group->id, "loaded '$user_name' UserEquiv group"); rights_for_group_ok ( $group, $user_name, $right_name, 'ModifyUserRights' ); } sub test_system_internal_queue_group { my $queue_name = shift; my $group_name = shift; my $right_name = shift; my $queue = RT::Queue->new( RT->SystemUser ); $queue->Load($queue_name); $m->get_ok('/Admin/Queues/GroupRights.html?id=' . $queue->id); diag "load $group_name group"; my $group = RT::Group->new( RT->SystemUser ); $group->LoadSystemInternalGroup($group_name); ok($group->id, "loaded '$group_name' system internal group"); rights_for_group_ok ( $group, $group_name, $right_name, 'ModifyGroupRights', 'RT::Queue-'.$queue->id, $queue); } sub test_system_internal_queue_role { my $queue_name = shift; my $role_name = shift; my $right_name = shift; my $queue = RT::Queue->new( RT->SystemUser ); $queue->Load($queue_name); $m->get_ok('/Admin/Queues/GroupRights.html?id=' . $queue->id); diag "load $role_name role group"; my $group = RT::Group->new( RT->SystemUser ); $group->LoadRoleGroup( Object => $queue, Name => $role_name ); ok($group->id, "loaded '$role_name' role group"); rights_for_group_ok ( $group, $role_name, $right_name, 'ModifyGroupRights', 'RT::Queue-'.$queue->id, $queue); } sub rights_for_group_ok { my $group = shift; my $group_name = shift; my $right_name = shift; my $form_name = shift; my $html_element_suffix = shift || 'RT::System-1'; my $right_context_obj = shift || $RT::System; my $html_element_id = $group->id; # if we have a non-system instance object, use that as the id if ($group->InstanceObj && $group->Instance > 1) { $html_element_id = $group->Instance; } my $is_user = $form_name eq 'ModifyUserRights'; my $is_root_user = $is_user && $group_name eq 'root'; diag "revoke all global rights from $group_name group"; my @original_rights = get_rights( $m, $html_element_id, $html_element_suffix, $form_name ); # this is important because all of the checkbox ids change if we're trying to modify a new user my $user_missing_from_list = $is_user && !$is_root_user; # We can't remove the SuperUser right from root or else we won't be able to access the admin section if ($is_root_user) { @original_rights = grep { $_ ne 'SuperUser' } @original_rights; } if ( @original_rights ) { $m->form_name($form_name); if ($is_root_user) { $m->untick("SetRights-$html_element_id-$html_element_suffix", $_) foreach (@original_rights); $m->submit; is_deeply([get_rights( $m, $html_element_id, $html_element_suffix, $form_name )], ['SuperUser'], 'deleted all rights but SuperUser' ); } elsif (not $user_missing_from_list) { $m->untick("SetRights-$html_element_id-$html_element_suffix", $_) foreach @original_rights; $m->submit; is_deeply([get_rights( $m, $html_element_id, $html_element_suffix, $form_name )], [], 'deleted all rights' ); } } else { ok(1, 'the group has no global rights'); } diag "grant $right_name right to $group_name group"; { $m->form_name($form_name); if ($user_missing_from_list) { # we must enter the username into the 'ADD USER' textbox $m->field('AddPrincipalForRights-user', $group_name); $m->tick("SetRights-addprincipal-$html_element_suffix", $right_name); $m->submit; } else { $m->tick("SetRights-$html_element_id-$html_element_suffix", $right_name); $m->submit; } if ($right_name eq 'AssignCustomFields') { print "\n$html_element_id $html_element_suffix\n"; } $m->text_contains("Granted right '$right_name' to $group_name", 'got message'); RT::Principal::InvalidateACLCache(); my $rights = $group->PrincipalObj->HasRights( Object => $right_context_obj ); ok($rights->{$right_name}, 'group has right'); is_deeply( [get_rights( $m, $html_element_id, $html_element_suffix, $form_name )], $is_root_user ? [$right_name, 'SuperUser'] : [$right_name], "granted $right_name right" ); } diag "revoke the $right_name right from $group_name group"; { $m->form_name($form_name); $m->untick("SetRights-$html_element_id-$html_element_suffix", $right_name); $m->submit; $m->text_contains("Revoked right '$right_name' from $group_name", 'got message'); RT::Principal::InvalidateACLCache(); my $rights = $group->PrincipalObj->HasRights( Object => $right_context_obj ); ok(!$rights->{$right_name}, 'group does not have right'); is_deeply( [get_rights( $m, $html_element_id, $html_element_suffix, $form_name )], $is_root_user ? ['SuperUser'] : [], "revoked $right_name right" ); } diag "return rights the $group_name group had in the beginning"; if ( @original_rights ) { $m->form_name($form_name); $m->tick("SetRights-$html_element_id-$html_element_suffix", $_) for @original_rights; $m->submit; $m->text_contains("Granted right '$_' to $group_name", 'got message') foreach (@original_rights); is_deeply( [ get_rights( $m, $html_element_id, $html_element_suffix, $form_name ) ], [ @original_rights ], 'returned back all rights' ); } else { ok(1, 'the group had no global rights, so nothing to return'); } } # User rights tests test_user ( 'root', 'CreateSavedSearch' ); my ($test_user_name, $test_user) = ('rights-test-000', RT::User->new( RT->SystemUser )); diag "create $test_user_name test user"; $test_user->Create( Name => $test_user_name, Privileged => 1); test_user ( $test_user_name, 'CreateTicket' ); # Group rights tests test_system_internal_group ( 'Everyone', 'SuperUser' ); test_system_internal_group ( 'Privileged', 'DeleteTicket' ); test_system_internal_group ( 'Unprivileged', 'Watch' ); # Role rights tests test_role ( 'AdminCc', 'ModifyACL' ); test_role ( 'Cc', 'DeleteTicket' ); test_role ( 'Owner', 'SeeQueue' ); test_role ( 'Requestor', 'CreateTicket' ); # User-defined group tests my ($user_group_name, $user_group) = ('rights user group test', RT::Group->new( RT->SystemUser )); diag "create $user_group_name custom user group"; $user_group->CreateUserDefinedGroup( Name => $user_group_name, Description => '' ); test_user_defined_group ( $user_group, 'Everyone', 'ModifyOwnMembership' ); test_user_defined_group ( $user_group, 'Privileged', 'SeeGroup' ); test_user_defined_group ( $user_group, 'Unprivileged', 'AdminGroup' ); # Queue tests test_system_internal_queue_group ( 'General', 'Everyone', 'ShowTemplate' ); test_system_internal_queue_group ( 'General', 'Privileged', 'ModifyTicket' ); test_system_internal_queue_group ( 'General', 'Unprivileged', 'Watch' ); test_system_internal_queue_role ( 'General', 'AdminCc', 'AssignCustomFields' ); test_system_internal_queue_role ( 'General', 'Cc', 'ModifyScrips' ); test_system_internal_queue_role ( 'General', 'Owner', 'ForwardMessage' ); test_system_internal_queue_role ( 'General', 'Requestor', 'SeeQueue' ); done_testing; rt-5.0.1/t/web/cf_values_class.t000644 000765 000024 00000003121 14005011336 017337 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 8; use constant VALUES_CLASS => 'RT::CustomFieldValues::Groups'; RT->Config->Set(CustomFieldValuesSources => VALUES_CLASS); my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in as root'; my $cf_name = 'test values class'; my $cfid; diag "Create a CF"; { $m->follow_link( id => 'admin-custom-fields-create'); $m->submit_form( form_name => "ModifyCustomField", fields => { Name => $cf_name, TypeComposite => 'Select-1', LookupType => 'RT::Queue-RT::Ticket', }, ); $m->content_contains('Object created', 'created Select-1' ); $cfid = $m->form_name('ModifyCustomField')->value('id'); ok $cfid, "found id of the CF in the form, it's #$cfid"; } diag "change to external values class"; { $m->submit_form( form_name => "ModifyCustomField", fields => { ValuesClass => 'RT::CustomFieldValues::Groups', }, button => 'Update', ); $m->content_contains( "Field values source changed from 'RT::CustomFieldValues' to 'RT::CustomFieldValues::Groups'", 'changed to external values class' ); } diag "change to internal values class"; { $m->submit_form( form_name => "ModifyCustomField", fields => { ValuesClass => 'RT::CustomFieldValues', }, button => 'Update', ); $m->content_contains( "Field values source changed from 'RT::CustomFieldValues::Groups' to 'RT::CustomFieldValues'", 'changed to internal values class' ); } rt-5.0.1/t/web/ticket_display.t000644 000765 000024 00000004242 14005011336 017220 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my $queue = RT::Test->load_or_create_queue( Name => 'General' ); my $user = RT::Test->load_or_create_user( Name => 'user', Password => 'password', ); my $cf = RT::Test->load_or_create_custom_field( Name => 'test_cf', Queue => $queue->Name, Type => 'FreeformSingle' ); my $cf_form_id = 'Object-RT::Ticket--CustomField-'.$cf->Id.'-Value'; my $cf_test_value = "some string for test_cf $$"; my ( $baseurl, $m ) = RT::Test->started_ok; ok( RT::Test->set_rights( { Principal => $user, Right => [qw(SeeQueue CreateTicket)] }, { Principal => $user, Object => $queue, Right => [qw(SeeCustomField ModifyCustomField)] } ), 'set rights' ); ok $m->login( 'user', 'password' ), 'logged in as user'; diag "test ShowTicket right"; { $m->get_ok( '/Ticket/Create.html?Queue=' . $queue->id, 'go to ticket create page' ); my $form = $m->form_name('TicketCreate'); $m->submit_form( fields => { Subject => 'ticket foo', $cf_form_id => $cf_test_value }, button => 'SubmitTicket' ); my $ticket = RT::Test->last_ticket; ok( $ticket->id, 'ticket is created' ); my $id = $ticket->id; $m->content_lacks( "Ticket $id created", 'created ticket' ); $m->content_contains( "No permission to view newly created ticket #$id", 'got no permission msg' ); $m->warning_like( qr/No permission to view newly created ticket #$id/, 'got no permission warning' ); $m->goto_ticket($id, undef, HTTP::Status::HTTP_FORBIDDEN); is($m->status, HTTP::Status::HTTP_FORBIDDEN, 'No permission'); $m->content_contains( "No permission to view ticket", 'got no permission msg' ); $m->warning_like( qr/No permission to view ticket/, 'got warning' ); $m->title_is('RT Error'); ok( RT::Test->add_rights( { Principal => $user, Right => [qw(ShowTicket)] }, ), 'add ShowTicket right' ); $m->reload; $m->content_lacks( "No permission to view ticket", 'no error msg' ); $m->title_is( "#$id: ticket foo", 'we can it' ); $m->content_contains($cf_test_value, "Custom Field was submitted and saved"); } done_testing(); rt-5.0.1/t/web/rest_cfs_with_same_name.t000644 000765 000024 00000004643 14005011336 021065 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Interface::REST; use RT::Test tests => 25; my ( $baseurl, $m ) = RT::Test->started_ok; for my $queue_name (qw/foo bar/) { my $queue = RT::Test->load_or_create_queue( Name => $queue_name ); ok( $queue, "created queue $queue_name" ); my $cf = RT::Test->load_or_create_custom_field( Name => 'test', Type => 'Freeform', Queue => $queue_name, ); ok( $cf->id, "created cf test for queue $queue_name " . $cf->id ); $m->post( "$baseurl/REST/1.0/ticket/new", [ user => 'root', pass => 'password', format => 'l', ] ); my $text = $m->content; my @lines = $text =~ m{.*}g; shift @lines; # header # cfs aren't in the default ticket form push @lines, "CF.{test}: baz"; $text = join "\n", @lines; ok( $text =~ s/Subject:\s*$/Subject: test cf/m, "successfully replaced subject" ); ok( $text =~ s/Queue: General\s*$/Queue: $queue_name/m, "successfully replaced Queue" ); $m->post( "$baseurl/REST/1.0/ticket/edit", [ user => 'root', pass => 'password', content => $text, ], Content_Type => 'form-data' ); my ($id) = $m->content =~ /Ticket (\d+) created/; ok( $id, "got ticket #$id" ); my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load($id); is( $ticket->id, $id, "loaded the REST-created ticket" ); is( $ticket->Subject, "test cf", "subject successfully set" ); is( $ticket->Queue, $queue->id, "queue successfully set" ); is( $ticket->FirstCustomFieldValue("test"), "baz", "cf successfully set" ); $m->post( "$baseurl/REST/1.0/ticket/show", [ user => 'root', pass => 'password', format => 'l', id => "ticket/$id", ] ); $text = $m->content; like( $text, qr/^CF\.\{test\}: baz\s*$/m, 'cf value in rest show' ); $text =~ s{.*}{}; # remove header $text =~ s!CF\.\{test\}: baz!CF.{test}: newbaz!; $m->post( "$baseurl/REST/1.0/ticket/edit", [ user => 'root', pass => 'password', content => $text, ], Content_Type => 'form-data' ); $m->content =~ /Ticket ($id) updated/; is( $ticket->FirstCustomFieldValue("test"), "newbaz", "cf successfully updated" ); } rt-5.0.1/t/web/install.t000644 000765 000024 00000013036 14005011336 015657 0ustar00sunnavystaff000000 000000 use strict; use warnings; use File::Spec; $ENV{RT_TEST_WEB_HANDLER} = 'plack+rt-server'; use RT::Test tests => undef, nodb => 1, server_ok => 1; my $dbname = 'rt5test_install_xxx'; my $rtname = 'rttestname'; my $domain = 'rttes.com'; my $password = 'newpass'; my $correspond = 'reply@example.com'; my $comment = 'comment@example.com'; # use bin/rt to fake sendmail to make sure the file exists my $sendmail = File::Spec->catfile( $RT::BinPath, 'rt' ); my $owner = 'root@localhost'; unlink File::Spec->catfile( $RT::VarPath, $dbname ); my ( $url, $m ) = RT::Test->started_ok; $m->warning_like(qr/If this is a new installation of RT/, "Got startup warning"); my ($port) = $url =~ /:(\d+)/; $m->get_ok($url); is( $m->uri, $url . '/Install/index.html', 'install page' ); $m->select( 'Lang', 'zh-cn' ); $m->click('ChangeLang'); $m->content_contains( Encode::decode("UTF-8",'语言'), 'select chinese' ); $m->click('Run'); $m->content_contains( Encode::decode("UTF-8",'æ•°æ®åº“'), 'select db type in chinese' ); $m->back; $m->select( 'Lang', 'en' ); $m->click('ChangeLang'); $m->content_contains( 'Select another language', 'back to english' ); $m->click('Run'); is( $m->uri, $url . '/Install/DatabaseType.html', 'db type page' ); my $select_type = $m->current_form->find_input('DatabaseType'); my @possible_types = $select_type->possible_values; ok( @possible_types, 'we have at least 1 db type' ); SKIP: { skip 'no mysql found', 7 unless grep { /mysql/ } @possible_types; $m->select( 'DatabaseType', 'mysql' ); $m->click; for my $field (qw/Name Host Port Admin AdminPassword User Password/) { ok( $m->current_form->find_input("Database$field"), "db mysql has field Database$field" ); } $m->back; } SKIP: { skip 'no pg found', 8 unless grep { /Pg/ } @possible_types; $m->select( 'DatabaseType', 'Pg' ); $m->click; for my $field ( qw/Name Host Port Admin AdminPassword User Password/) { ok( $m->current_form->find_input("Database$field"), "db Pg has field Database$field" ); } $m->back; } $m->select( 'DatabaseType', 'SQLite' ); $m->click; is( $m->uri, $url . '/Install/DatabaseDetails.html', 'db details page' ); $m->field( 'DatabaseName' => $dbname ); $m->submit_form( fields => { DatabaseName => $dbname } ); $m->content_contains( 'Connection succeeded', 'succeed msg' ); $m->content_contains( qq{$dbname already exists, but does not contain RT's tables or metadata. The 'Initialize Database' step later on can insert tables and metadata into this existing database. if this is acceptable, click 'Customize Basic' below to continue customizing RT.}, 'more db state msg' ); $m->click; is( $m->uri, $url . '/Install/Basics.html', 'basics page' ); $m->click; $m->content_contains( 'You must enter an Administrative password', "got password can't be empty error" ); for my $field (qw/rtname WebDomain WebPort Password/) { ok( $m->current_form->find_input($field), "has field $field" ); } is( $m->value('WebPort'), $port, 'default port' ); $m->field( 'rtname' => $rtname ); $m->field( 'WebDomain' => $domain ); $m->field( 'Password' => $password ); $m->click; is( $m->uri, $url . '/Install/Sendmail.html', 'mail page' ); for my $field (qw/SendmailPath OwnerEmail/) { ok( $m->current_form->find_input($field), "has field $field" ); } $m->field( 'OwnerEmail' => '' ); $m->click; $m->content_contains( "doesn't look like an email address", 'got email error' ); $m->field( 'SendmailPath' => '/fake/path/sendmail' ); $m->click; $m->content_contains( "/fake/path/sendmail doesn't exist", 'got sendmail error' ); $m->field( 'SendmailPath' => $sendmail ); $m->field( 'OwnerEmail' => $owner ); $m->click; is( $m->uri, $url . '/Install/Global.html', 'global page' ); for my $field (qw/CommentAddress CorrespondAddress/) { ok( $m->current_form->find_input($field), "has field $field" ); } $m->click; is( $m->uri, $url . '/Install/Initialize.html', 'init db page' ); $m->back; is( $m->uri, $url . '/Install/Global.html', 'global page' ); $m->field( 'CorrespondAddress' => 'reply' ); $m->click; $m->content_contains( "doesn't look like an email address", 'got email error' ); $m->field( 'CommentAddress' => 'comment' ); $m->click; $m->content_contains( "doesn't look like an email address", 'got email error' ); $m->field( 'CorrespondAddress' => 'reply@example.com' ); $m->field( 'CommentAddress' => 'comment@example.com' ); $m->click; is( $m->uri, $url . '/Install/Initialize.html', 'init db page' ); $m->click; is( $m->uri, $url . '/Install/Finish.html', 'finish page' ); $m->click; is( $m->uri, $url . '/', 'home page' ); $m->login( 'root', $password ); $m->content_contains( 'RT at a glance', 'logged in with newpass' ); RT->LoadConfig; my $config = RT->Config; is( $config->Get('DatabaseType'), 'SQLite', 'DatabaseType in config' ); is( $config->Get('DatabaseName'), $dbname, 'DatabaseName in config' ); is( $config->Get('rtname'), $rtname, 'rtname in config' ); is( $config->Get('WebDomain'), $domain, 'WebDomain email in config' ); is( $config->Get('WebPort'), $port, 'WebPort email in config' ); is( $config->Get('SendmailPath'), $sendmail, 'SendmailPath in config' ); is( $config->Get('OwnerEmail'), $owner, 'OwnerEmail in config' ); is( $config->Get('CorrespondAddress'), $correspond, 'correspond address in config' ); is( $config->Get('CommentAddress'), $comment, 'comment address in config' ); unlink File::Spec->catfile( $RT::VarPath, $dbname ); done_testing; rt-5.0.1/t/web/ticket_update_without_content.t000644 000765 000024 00000002263 14005011336 022353 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 12; my ( $url, $m ) = RT::Test->started_ok; # merged tickets still show up in search my $ticket = RT::Ticket->new(RT->SystemUser); my ( $ret, $msg ) = $ticket->Create( Subject => 'base ticket' . $$, Queue => 'general', Owner => 'root', Requestor => 'root@localhost', MIMEObj => MIME::Entity->build( From => 'root@localhost', To => 'rt@localhost', Subject => 'base ticket' . $$, Data => "", ), ); ok( $ret, "ticket created: $msg" ); ok( $m->login, 'logged in' ); $m->get_ok( $url . "/Ticket/ModifyAll.html?id=" . $ticket->id ); $m->submit_form( form_number => 3, fields => { Priority => '1', } ); $m->content_contains("Priority changed"); $m->content_lacks("message recorded"); my $root = RT::User->new( RT->SystemUser ); $root->Load('root'); ( $ret, $msg ) = $root->SetSignature(<get_ok( $url . "/Ticket/ModifyAll.html?id=" . $ticket->id ); $m->submit_form( form_number => 3, fields => { Priority => '2', } ); $m->content_contains("Priority changed"); $m->content_lacks("message recorded"); rt-5.0.1/t/web/customrole_visibility.t000644 000765 000024 00000010212 14005011336 020645 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in as root'; my $customrole_name = 'test customrole'; diag( 'Create custom role' ); # create custom role $m->follow_link_ok({ id => 'admin-custom-roles-create' }, "Followed link to 'Admin > Custom Roles > Create'" ); $m->submit_form( form_name => 'ModifyCustomRole', fields => { Name => $customrole_name, }, ); $m->content_contains( 'Custom role created', 'Created customrole' ); my $customrole_id = $m->form_name( 'ModifyCustomRole' )->value( 'id' ); # set applies to $m->follow_link_ok({ id => 'page-applies-to' }, "Followed link to 'Applies to'" ); $m->form_name( 'AddRemoveCustomRole' ); $m->current_form->find_input( 'AddRole-1' )->check; $m->click_ok( 'Update', "Added '$customrole_name' to General queue" ); $m->content_contains( "$customrole_name added to queue General" ); diag( 'Verify visibility is shown by default' ); # ensure all pages are set visible by default $m->follow_link_ok({ id => 'page-visibility' }, "Followed link to 'Visibility'" ); ok( !$m->form_name( 'Visibility' )->value( 'hide-/Ticket/Create.html' ), 'Ticket create is set visible by default' ); ok( !$m->form_name( 'Visibility' )->value( 'hide-/Ticket/Display.html' ), 'Ticket display is set visible by default' ); ok( !$m->form_name( 'Visibility' )->value( 'hide-/Ticket/ModifyPeople.html' ), 'Ticket modify people is set visible by default' ); ok( !$m->form_name( 'Visibility' )->value( 'hide-/Ticket/ModifyAll.html' ), 'Ticket jumbo is set visible by default' ); # confirm each page is visible by default $m->get_ok( '/Ticket/Create.html?Queue=1' ); $m->content_contains( "$customrole_name" ); $m->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ticket', }, button => 'SubmitTicket', ); $m->content_contains( 'Ticket 1 created in queue', 'Created ticket' ); $m->get_ok( '/Ticket/Display.html?id=1' ); $m->content_contains( "$customrole_name" ); $m->get_ok( '/Ticket/ModifyPeople.html?id=1' ); $m->content_contains( "$customrole_name" ); $m->get_ok( '/Ticket/ModifyAll.html?id=1' ); $m->content_contains( "$customrole_name" ); diag( 'Remove visibility' ); # set each visibility to hidden $m->follow_link_ok({ id => 'admin-custom-roles-select' }, "Followed link to 'Admin > Custom Roles > Select'" ); $m->follow_link_ok({ text => $customrole_name }, "Followed link to '$customrole_name'" ); $m->follow_link_ok({ id => 'page-visibility' }, "Followed link to 'Visibility'" ); $m->form_name( 'Visibility' ); $m->current_form->find_input( 'hide-/Ticket/Create.html' )->check; $m->current_form->find_input( 'hide-/Ticket/Display.html' )->check; $m->current_form->find_input( 'hide-/Ticket/ModifyPeople.html' )->check; $m->current_form->find_input( 'hide-/Ticket/ModifyAll.html' )->check; $m->click_ok( 'Update', 'Set visibility to hide for all pages' ); $m->content_contains( 'Updated visibility' ); ok( $m->form_name( 'Visibility' )->value( 'hide-/Ticket/Create.html' ), 'Ticket create is set hidden' ); ok( $m->form_name( 'Visibility' )->value( 'hide-/Ticket/Display.html' ), 'Ticket display is set hidden' ); ok( $m->form_name( 'Visibility' )->value( 'hide-/Ticket/ModifyPeople.html' ), 'Ticket modify people is set hidden' ); ok( $m->form_name( 'Visibility' )->value( 'hide-/Ticket/ModifyAll.html' ), 'Ticket jumbo is set hidden' ); diag( 'Verify visibility is hidden' ); # confirm hidden on each page $m->get_ok( '/Ticket/Create.html?Queue=1' ); $m->content_lacks( "$customrole_name" ); $m->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test ticket 2', }, button => 'SubmitTicket', ); $m->content_contains( 'Ticket 2 created in queue', 'Created ticket' ); $m->get_ok( '/Ticket/Display.html?id=2' ); $m->content_lacks( "$customrole_name" ); $m->get_ok( '/Ticket/ModifyPeople.html?id=2' ); $m->content_lacks( "$customrole_name" ); $m->get_ok( '/Ticket/ModifyAll.html?id=2' ); $m->content_lacks( "$customrole_name" ); # TODO: # create customrole not for multiple users # verify additional pages customrole is visible for a single user # - Ticket modify basics # - Ticket reply/comment done_testing(); rt-5.0.1/t/web/redirect.t000644 000765 000024 00000005524 14005011336 016015 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 13; use CGI::PSGI; my $r = $HTML::Mason::Commands::r = bless {}, 'R'; my $m = $HTML::Mason::Commands::m = bless { cgi_object => CGI::PSGI->new( {} ) }, 'M'; set_config( CanonicalizeRedirectURLs => 0, WebDomain => 'localhost', WebPort => 80, WebPath => '', ); is( RT->Config->Get('WebBaseURL'), 'http://localhost' ); is( RT->Config->Get('WebURL'), 'http://localhost/' ); redirect_ok( 'http://localhost/Ticket/', 'http://localhost/Ticket/', { SERVER_NAME => 'localhost', SERVER_PORT => 80 }, ); redirect_ok( '/Ticket/', 'http://localhost/Ticket/', { SERVER_NAME => 'localhost', SERVER_PORT => 80 }, ); redirect_ok( 'http://localhost/Ticket/', 'http://example.com/Ticket/', { SERVER_NAME => 'example.com', SERVER_PORT => 80 }, ); set_config( CanonicalizeRedirectURLs => 0, WebDomain => 'localhost', WebPort => 443, WebPath => '', ); is( RT->Config->Get('WebBaseURL'), 'https://localhost' ); is( RT->Config->Get('WebURL'), 'https://localhost/' ); redirect_ok( 'https://localhost/Ticket/', 'https://localhost/Ticket/', { SERVER_NAME => 'localhost', SERVER_PORT => 443, 'psgi.url_scheme' => 'https' }, ); redirect_ok( '/Ticket/', 'https://localhost/Ticket/', { SERVER_NAME => 'localhost', SERVER_PORT => 443, 'psgi.url_scheme' => 'https' }, ); redirect_ok( 'https://localhost/Ticket/', 'http://localhost/Ticket/', { SERVER_NAME => 'localhost', SERVER_PORT => 80 }, ); redirect_ok( '/Ticket/', 'http://localhost/Ticket/', { SERVER_NAME => 'localhost', SERVER_PORT => 80 }, ); redirect_ok( 'https://localhost/Ticket/', 'http://example.com/Ticket/', { SERVER_NAME => 'example.com', SERVER_PORT => 80 }, ); redirect_ok( 'https://localhost/Ticket/', 'https://example.com/Ticket/', { SERVER_NAME => 'example.com', SERVER_PORT => 443, 'psgi.url_scheme' => 'https' }, ); sub set_config { my %values = @_; while ( my ($k, $v) = each %values ) { RT->Config->Set( $k => $v ); } unless ( $values{'WebBaseURL'} ) { my $port = RT->Config->Get('WebPort'); RT->Config->Set( WebBaseURL => ($port == 443? 'https': 'http') .'://' . RT->Config->Get('WebDomain') . ($port != 80 && $port != 443? ":$port" : '') ); } unless ( $values{'WebURL'} ) { RT->Config->Set( WebURL => RT->Config->Get('WebBaseURL') . RT->Config->Get('WebPath') . "/" ); } } sub redirect_ok { my ($to, $expected, $env, $details) = @_; %{$m->cgi_object->env} = %$env; RT::Interface::Web::Redirect( $to ); is($m->redirect, $expected, $details || "correct for '$to'"); } package R; sub status {}; package M; sub redirect { $_[0]{'last'} = $_[1] if @_ > 1; return $_[0]{'last'} } sub abort {} sub cgi_object { $_[0]{'cgi_object'} } rt-5.0.1/t/web/cf_pattern.t000644 000765 000024 00000004414 14005011336 016336 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 'no_declare'; my ($base, $m) = RT::Test->started_ok; my $cf = RT::Test->load_or_create_custom_field( Name => 'Yaks', Type => 'FreeformSingle', Pattern => '(?#Digits)^\d+$', Queue => 0, LookupType => 'RT::Queue-RT::Ticket', ); ok $cf && $cf->id, "Created CF with Pattern"; my $ticket = RT::Test->create_ticket( Queue => 1, Subject => 'a test ticket', ); ok $ticket && $ticket->id, "Created ticket"; $m->login; for my $page ("/Ticket/Create.html?Queue=1", "/Ticket/Modify.html?id=".$ticket->id) { diag $page; $m->get_ok($page, "Fetched $page"); $m->content_contains("Yaks"); $m->content_contains("Input must match [Digits]"); $m->content_lacks("cfinvalidfield"); my $cfinput = RT::Interface::Web::GetCustomFieldInputName( Object => ( $page =~ /Create/ ? RT::Ticket->new( RT->SystemUser ) : $ticket ), CustomField => $cf, ); $m->submit_form_ok({ with_fields => { $cfinput => "too many", "${cfinput}-Magic" => "1", }, }); $m->content_contains("Input must match [Digits]"); $m->content_contains("cfinvalidfield"); $m->submit_form_ok({ with_fields => { $cfinput => "42", "${cfinput}-Magic" => "1", }, button => 'SubmitTicket', }); if ($page =~ /Create/) { $m->content_like(qr/Ticket \d+ created/, "Created ticket"); } else { $m->content_contains("Yaks 42 added", "Updated ticket"); $m->content_contains("Input must match [Digits]"); $m->content_lacks("cfinvalidfield"); } } diag "Quick ticket creation"; { $m->get_ok("/"); $m->submit_form_ok({ with_fields => { Subject => "test quick create", QuickCreate => 1, }, }); my $tickets = RT::Tickets->new(RT->SystemUser); $tickets->FromSQL("Subject = 'test quick create'"); is $tickets->Count, 0, "No ticket created"; like $m->uri, qr/Ticket\/Create\.html/, "Redirected to the ticket create page"; $m->content_contains("Yaks: Input must match", "Found CF validation error"); $m->content_contains("test quick create", "Found prefilled Subject"); } done_testing; rt-5.0.1/t/web/rest-search-user.t000644 000765 000024 00000005265 14005011336 017412 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my $root = RT::Test->load_or_create_user( Name => 'root', ); my $user_foo = RT::Test->load_or_create_user( Name => 'foo', Password => 'password', ); my $user_bar = RT::Test->load_or_create_user( Name => 'bar' ); my $user_baz = RT::Test->load_or_create_user( Name => 'baz' ); $user_baz->SetDisabled(1); my ( $baseurl, $m ) = RT::Test->started_ok; ok( $m->login, 'logged in' ); search_users_ok( { query => 'id = ' . $user_foo->id }, [ $user_foo->id . ': foo' ], 'search by id' ); search_users_ok( { query => 'Name = ' . $user_foo->Name, format => 's', fields => 'id,name' }, [ "id\tName", $user_foo->id . "\tfoo" ], 'search by name with customized fields' ); search_users_ok( { query => 'foo = 3' }, ['Invalid field specification: foo'], 'invalid field' ); search_users_ok( { query => 'id foo 3' }, ['Invalid operator specification: foo'], 'invalid op' ); search_users_ok( { query => 'password = foo' }, ['Invalid field specification: password'], "can't search password" ); search_users_ok( { query => '', orderby => 'id' }, [ $root->id . ': root', $user_foo->id . ': foo', $user_bar->id . ': bar', ], 'order by id' ); search_users_ok( { query => '', orderby => 'name' }, [ $user_bar->id . ': bar', $user_foo->id . ': foo', $root->id . ': root' ], 'order by name' ); search_users_ok( { query => '', orderby => '+name' }, [ $user_bar->id . ': bar', $user_foo->id . ': foo', $root->id . ': root' ], 'order by +name' ); search_users_ok( { query => '', orderby => '-name' }, [ $root->id . ': root', $user_foo->id . ': foo', $user_bar->id . ': bar' ], 'order by -name' ); search_users_ok( { query => 'Disabled = 0', orderby => 'id' }, [ $root->id . ': root', $user_foo->id . ': foo', $user_bar->id . ': bar', ], 'enabled users' ); search_users_ok( { query => 'Disabled = 1', orderby => 'id' }, [ $user_baz->id . ': baz', ], 'disabled users' ); ok( $m->login( 'foo', 'password', logout => 1 ), 'logged in as foo' ); search_users_ok( { query => 'id = ' . $user_foo->id }, [ 'Permission denied' ], "can't search without permission" ); sub search_users_ok { local $Test::Builder::Level = $Test::Builder::Level + 1; my $query = shift; my $expected = shift; my $name = shift || 'search users'; my $uri = URI->new("$baseurl/REST/1.0/search/user"); $uri->query_form(%$query); $m->get_ok($uri); my @lines = split /\n/, $m->content; shift @lines; # header shift @lines; # empty line is_deeply( \@lines, $expected, $name ); } done_testing; rt-5.0.1/t/web/saved_search_context.t000644 000765 000024 00000003436 14005011336 020407 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test no_plan => 1; my ( $url, $m ) = RT::Test->started_ok; my $ticket = RT::Ticket->new(RT->SystemUser); for (['x', 50], ['y', 40], ['z', 30]) { $ticket->Create( Subject => $_->[0], Queue => 'general', Owner => 'root', Priority => $_->[1], Requestor => 'root@localhost', ); } ok( $m->login, 'logged in' ); $m->get($url . '/Search/Build.html?NewQuery=1'); $m->form_name('BuildQuery'); $m->field(ValueOfPriority => 45); $m->click('DoSearch'); #RT->Logger->error($m->uri); sleep 100; #{ open my $fh, '>', 'm.html'; print $fh $m->content; close $fh; }; die; $m->text_contains('Found 2 tickets'); $m->follow_link(id => 'page-edit_search'); $m->form_name('BuildQuery'); $m->field(ValueOfAttachment => 'z'); $m->click('DoSearch'); $m->text_contains('Found 1 ticket'); $m->follow_link(id => 'page-bulk'); $m->form_name('BulkUpdate'); ok(!$m->value('UpdateTicket2'), "There is no Ticket #2 in the search's bulk update"); sub edit_search_link_has { my ($m, $id, $msg) = @_; local $Test::Builder::Level = $Test::Builder::Level + 1; (my $dec_id = $id) =~ s/:/%3A/g; my $chart_url = $m->find_link(id => 'page-edit_search')->url; like( $chart_url, qr{SavedSearchId=\Q$dec_id\E}, $msg || 'Search link matches the pattern we expected' ); } diag("Test search context"); { $m->get_ok($url . '/Search/Build.html'); $m->form_name('BuildQuery'); $m->field(ValueOfPriority => 45); $m->click('AddClause'); $m->form_name('BuildQuery'); $m->set_fields( SavedSearchDescription => 'my saved search', ); $m->click('SavedSearchSave'); my $saved_search_id = $m->form_name('BuildQuery')->value('SavedSearchId'); edit_search_link_has($m, $saved_search_id); } rt-5.0.1/t/web/search_bulk_update_links.t000644 000765 000024 00000011475 14005011336 021242 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 46; my ( $url, $m ) = RT::Test->started_ok; ok( $m->login, 'logged in' ); my $rtname = RT->Config->Get('rtname'); # create tickets use RT::Ticket; my ( @link_tickets, @search_tickets ); for ( 1 .. 3 ) { my $link_ticket = RT::Ticket->new(RT->SystemUser); my ( $ret, $msg ) = $link_ticket->Create( Subject => "link ticket $_", Queue => 'general', Owner => 'root', Requestor => 'root@localhost', ); ok( $ret, "link ticket created: $msg" ); push @link_tickets, $ret; } for ( 1 .. 3 ) { my $ticket = RT::Ticket->new(RT->SystemUser); my ( $ret, $msg ) = $ticket->Create( Subject => "search ticket $_", Queue => 'general', Owner => 'root', Requestor => 'root@localhost', ); ok( $ret, "search ticket created: $msg" ); push @search_tickets, $ret; } # let's add link to 1 search ticket first $m->get_ok( $url . "/Search/Bulk.html?Query=id=$search_tickets[0]&Rows=10" ); $m->content_contains( 'Current Links', 'has current links part' ); $m->content_lacks( 'DeleteLink--', 'no delete link stuff' ); $m->submit_form( form_name => 'BulkUpdate', fields => { 'Ticket-DependsOn' => $link_tickets[0], 'Ticket-MemberOf' => $link_tickets[1], 'Ticket-RefersTo' => $link_tickets[2], }, ); $m->content_contains( "Ticket $search_tickets[0] depends on Ticket $link_tickets[0]", 'depends on msg', ); $m->content_contains( "Ticket $search_tickets[0] member of Ticket $link_tickets[1]", 'member of msg', ); $m->content_contains( "Ticket $search_tickets[0] refers to Ticket $link_tickets[2]", 'refers to msg', ); $m->content_contains( "DeleteLink--DependsOn-fsck.com-rt://$rtname/ticket/$link_tickets[0]", 'found depends on link' ); $m->content_contains( "DeleteLink--MemberOf-fsck.com-rt://$rtname/ticket/$link_tickets[1]", 'found member of link' ); $m->content_contains( "DeleteLink--RefersTo-fsck.com-rt://$rtname/ticket/$link_tickets[2]", 'found refers to link' ); # here we check the *real* bulk update my $query = join ' OR ', map { "id=$_" } @search_tickets; $m->get_ok( $url . "/Search/Bulk.html?Query=$query&Rows=10" ); $m->content_contains( 'Current Links', 'has current links part' ); $m->content_lacks( 'DeleteLink--', 'no delete link stuff' ); $m->form_name('BulkUpdate'); my @fields = qw/Owner AddRequestor DeleteRequestor AddCc DeleteCc AddAdminCc DeleteAdminCc Subject Priority Queue Status Starts_Date Told_Date Due_Date UpdateSubject/; for my $field ( @fields ) { is( $m->value($field), '', "default $field is empty" ); } like( $m->value('UpdateContent'), qr/^\s*$/, "default UpdateContent is effectively empty" ); # test DependsOn, MemberOf and RefersTo $m->submit_form( form_name => 'BulkUpdate', fields => { 'Ticket-DependsOn' => $link_tickets[0], 'Ticket-MemberOf' => $link_tickets[1], 'Ticket-RefersTo' => $link_tickets[2], }, ); $m->content_contains( "DeleteLink--DependsOn-fsck.com-rt://$rtname/ticket/$link_tickets[0]", 'found depends on link' ); $m->content_contains( "DeleteLink--MemberOf-fsck.com-rt://$rtname/ticket/$link_tickets[1]", 'found member of link' ); $m->content_contains( "DeleteLink--RefersTo-fsck.com-rt://$rtname/ticket/$link_tickets[2]", 'found refers to link' ); $m->submit_form( form_name => 'BulkUpdate', fields => { "DeleteLink--DependsOn-fsck.com-rt://$rtname/ticket/$link_tickets[0]" => 1, "DeleteLink--MemberOf-fsck.com-rt://$rtname/ticket/$link_tickets[1]" => 1, "DeleteLink--RefersTo-fsck.com-rt://$rtname/ticket/$link_tickets[2]" => 1, }, ); $m->content_lacks( 'DeleteLink--', 'links are all deleted' ); # test DependedOnBy, Members and ReferredToBy $m->submit_form( form_name => 'BulkUpdate', fields => { 'DependsOn-Ticket' => $link_tickets[0], 'MemberOf-Ticket' => $link_tickets[1], 'RefersTo-Ticket' => $link_tickets[2], }, ); $m->content_contains( "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[0]-DependsOn-", 'found depended on link' ); $m->content_contains( "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[1]-MemberOf-", 'found members link' ); $m->content_contains( "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[2]-RefersTo-", 'found referrd to link' ); $m->submit_form( form_name => 'BulkUpdate', fields => { "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[0]-DependsOn-" => 1, "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[1]-MemberOf-" => 1, "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[2]-RefersTo-" => 1, }, ); $m->content_lacks( 'DeleteLink--', 'links are all deleted' ); rt-5.0.1/t/web/admin_queue_lifecycle.t000644 000765 000024 00000006042 14005011336 020523 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use Test::Warn; my $lifecycles = RT->Config->Get('Lifecycles'); RT->Config->Set( Lifecycles => %{$lifecycles}, foo => { initial => ['initial'], active => ['open'], inactive => ['resolved'], } ); RT::Lifecycle->FillCache(); my ( $url, $m ) = RT::Test->started_ok; ok( $m->login(), 'logged in' ); $m->get_ok( $url . '/Admin/Queues/Modify.html?id=1' ); my $form = $m->form_name('ModifyQueue'); my $lifecycle_input = $form->find_input('Lifecycle'); is( $lifecycle_input->value, 'default', 'default lifecycle' ); my @lifecycles = sort $lifecycle_input->possible_values; is_deeply( \@lifecycles, [qw/default foo/], 'found all lifecycles' ); $m->submit_form(); $m->content_lacks( 'Lifecycle changed from', 'no message of "Lifecycle changed from"' ); $m->content_lacks( 'That is already the current value', 'no message of "That is already the current value"' ); $form = $m->form_name('ModifyQueue'); $m->submit_form( fields => { Lifecycle => 'foo' }, ); $m->content_contains( 'Lifecycle changed from "default" to "foo"'); $lifecycle_input = $form->find_input('Lifecycle'); is( $lifecycle_input->value, 'foo', 'lifecycle is changed to foo' ); $form = $m->form_name('ModifyQueue'); $m->submit_form( fields => { Lifecycle => 'default' }, ); $m->content_contains( 'Lifecycle changed from "foo" to "default"'); $lifecycle_input = $form->find_input('Lifecycle'); is( $lifecycle_input->value, 'default', 'lifecycle is changed back to default' ); RT::Test->stop_server; RT->Config->Set( Lifecycles => %{$lifecycles}, foo => { initial => ['initial'], active => ['open'], inactive => ['resolved'], }, bar => { initial => ['initial'], active => ['open'], inactive => ['resolved'], }, ); RT::Lifecycle->FillCache(); ( $url, $m ) = RT::Test->started_ok; ok( $m->login(), 'logged in' ); $m->get_ok( $url . '/Admin/Queues/Modify.html?id=1' ); $form = $m->form_name('ModifyQueue'); $m->submit_form( fields => { Lifecycle => 'bar' }, ); $m->text_contains(q{Lifecycle changed from "default" to "bar"}); $lifecycle_input = $form->find_input('Lifecycle'); is( $lifecycle_input->value, 'bar', 'lifecycle is changed to bar' ); RT::Test->stop_server; RT->Config->Set( Lifecycles => %$lifecycles ); warning_like { RT::Lifecycle->FillCache(); } qr/Lifecycle bar is missing in %Lifecycles config/; ( $url, $m ) = RT::Test->started_ok; ok( $m->login(), 'logged in' ); $m->get_ok( $url . '/Admin/Queues/Modify.html?id=1' ); $m->next_warning_like(qr/Unable to load lifecycle for bar/, 'warning of missing lifecycle bar'); $form = $m->form_name('ModifyQueue'); $m->submit_form( fields => { Lifecycle => 'default' }, ); $m->text_contains(q{Lifecycle changed from "bar" to "default"}); $lifecycle_input = $form->find_input('Lifecycle'); is( $lifecycle_input->value, 'default', 'lifecycle is changed to default' ); done_testing; rt-5.0.1/t/web/remove_user_info.t000644 000765 000024 00000007033 14005011336 017557 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; RT::Config->Set( 'ShredderStoragePath', RT::Test->temp_directory . '' ); my ( $baseurl, $agent ) = RT::Test->started_ok; diag("Test server running at $baseurl"); my $url = $agent->rt_base_url; # Login $agent->login( 'root' => 'password' ); # Anonymize User { my %skip_clear = map { $_ => 1 } qw/Name Password AuthToken/; my @user_identifying_info = grep { !$skip_clear{$_} && RT::User->_Accessible( $_, 'write' ) } keys %{ RT::User->_CoreAccessible() }; my $user = RT::Test->load_or_create_user( map( { $_ => 'test_string' } @user_identifying_info, 'AuthToken' ), Name => 'Test User', EmailAddress => 'test@example.com', ); ok( $user && $user->id ); foreach my $attr (@user_identifying_info) { ok( $user->$attr, 'Attribute ' . $attr . ' is set' ); } my $user_id = $user->id; $agent->get_ok( $url . "Admin/Users/Modify.html?id=" . $user_id ); $agent->submit_form_ok( { form_id => 'user-info-modal-form', }, "Anonymize user" ); # UserId is still the same, but all other records should be anonimyzed for TestUser my ( $ret, $msg ) = $user->Load($user_id); ok($ret); like( $user->Name, qr/anon_/, 'Username replaced with anon name' ); $user->Load($user_id); # Ensure that all other user fields are unset foreach my $attr (@user_identifying_info) { ok( !$user->$attr, 'Attribute ' . $attr . ' is unset' ); } ok( !$user->HasPassword, 'Password is unset' ); # Can't call AuthToken here because it creates new one automatically ok( !$user->_Value('AuthToken'), 'Authtoken is unset' ); # Test that customfield values are removed with anonymize user action my $customfield = RT::CustomField->new( RT->SystemUser ); ( $ret, $msg ) = $customfield->Create( Name => 'TestCustomfield', LookupType => 'RT::User', Type => 'FreeformSingle', ); ok( $ret, $msg ); ( $ret, $msg ) = $customfield->AddToObject($user); ok( $ret, "Added CF to user object - " . $msg ); ( $ret, $msg ) = $user->AddCustomFieldValue( Field => 'TestCustomfield', Value => 'Testing' ); ok( $ret, $msg ); is( $user->FirstCustomFieldValue('TestCustomfield'), 'Testing', 'Customfield exists and has value for user.' ); $agent->get_ok( $url . "Admin/Users/Modify.html?id=" . $user->id ); $agent->submit_form_ok( { form_id => 'user-info-modal-form', fields => { clear_customfields => 'On' }, }, "Anonymize user and customfields" ); is( $user->FirstCustomFieldValue('TestCustomfield'), undef, 'Customfield value cleared' ); } # Test replace user { my $user = RT::Test->load_or_create_user( Name => 'user', Password => 'password', Privileged => 1 ); ok( $user && $user->id ); my $id = $user->id; ok( RT::Test->set_rights( { Principal => $user, Right => [qw(SuperUser)] }, ), 'set rights' ); ok( $agent->logout ); ok( $agent->login( 'root' => 'password' ) ); $agent->get_ok( $url . "Admin/Users/Modify.html?id=" . $user->id ); $agent->follow_link_ok( { text => 'Replace User' } ); $agent->submit_form_ok( { form_id => 'shredder-search-form', fields => { WipeoutObject => 'RT::User-' . $user->Name, }, button => 'Wipeout' }, "Replace user" ); my ( $ret, $msg ) = $user->Load($id); is( $ret, 0, 'User successfully deleted with replace' ); } done_testing(); rt-5.0.1/t/web/query_builder.t000644 000765 000024 00000034751 14005011336 017073 0ustar00sunnavystaff000000 000000 use strict; use warnings; use HTTP::Request::Common; use HTTP::Cookies; use LWP; use RT::Test tests => undef; my $cookie_jar = HTTP::Cookies->new; my ($baseurl, $agent) = RT::Test->started_ok; # give the agent a place to stash the cookies $agent->cookie_jar($cookie_jar); # create a regression queue if it doesn't exist my $queue = RT::Test->load_or_create_queue( Name => 'Regression' ); ok $queue && $queue->id, 'loaded or created queue'; my $url = $agent->rt_base_url; ok $agent->login, "logged in"; my $response = $agent->get($url."Search/Build.html"); ok $response->is_success, "Fetched ". $url ."Search/Build.html"; sub getQueryFromForm { my $agent = shift; $agent->form_name('BuildQuery'); # This pulls out the "hidden input" query from the page my $q = $agent->current_form->find_input("Query")->value; $q =~ s/^\s+//g; $q =~ s/\s+$//g; $q =~ s/\s+/ /g; return $q; } sub selectedClauses { my $agent = shift; my @clauses = grep { defined } map { $_->value } $agent->current_form->find_input("clauses"); return [ @clauses ]; } diag "add the first condition"; { ok $agent->form_name('BuildQuery'), "found the form once"; $agent->field("ActorField", "Owner"); $agent->field("ActorOp", "="); $agent->field("ValueOfActor", "Nobody"); $agent->submit; is getQueryFromForm($agent), "Owner = 'Nobody'", 'correct query'; } diag "set the next condition"; { ok($agent->form_name('BuildQuery'), "found the form again"); $agent->field("QueueOp", "!="); $agent->field("ValueOfQueue", "Regression"); $agent->submit; is getQueryFromForm($agent), "Owner = 'Nobody' AND Queue != 'Regression'", 'correct query'; } diag "We're going to delete the owner"; { $agent->select("clauses", ["0"] ); $agent->click("DeleteClause"); ok $agent->form_name('BuildQuery'), "found the form"; is getQueryFromForm($agent), "Queue != 'Regression'", 'correct query'; } diag "add a cond with OR and se number by the way"; { $agent->field("AndOr", "OR"); $agent->select("idOp", ">"); $agent->field("ValueOfid" => "1234"); $agent->click("AddClause"); ok $agent->form_name('BuildQuery'), "found the form again"; is getQueryFromForm($agent), "Queue != 'Regression' OR id > 1234", "added something as OR, and number not quoted"; is_deeply selectedClauses($agent), ["1"], 'the id that we just entered is still selected'; } diag "Move the second one up a level"; { $agent->click("Up"); ok $agent->form_name('BuildQuery'), "found the form again"; is getQueryFromForm($agent), "id > 1234 OR Queue != 'Regression'", "moved up one"; is_deeply selectedClauses($agent), ["0"], 'the one we moved up is selected'; } diag "Move the second one right"; { $agent->click("Right"); ok $agent->form_name('BuildQuery'), "found the form again"; is getQueryFromForm($agent), "Queue != 'Regression' OR ( id > 1234 )", "moved over to the right (and down)"; is_deeply selectedClauses($agent), ["2"], 'the one we moved right is selected'; } diag "Move the block up"; { $agent->select("clauses", ["1"]); $agent->click("Up"); ok $agent->form_name('BuildQuery'), "found the form again"; is getQueryFromForm($agent), "( id > 1234 ) OR Queue != 'Regression'", "moved up"; is_deeply selectedClauses($agent), ["0"], 'the one we moved up is selected'; } diag "Can not move up the top most clause"; { $agent->select("clauses", ["0"]); $agent->click("Up"); ok $agent->form_name('BuildQuery'), "found the form again"; $agent->content_contains("error: can't move up", "i shouldn't have been able to hit up"); is_deeply selectedClauses($agent), ["0"], 'the one we tried to move is selected'; } diag "Can not move left the left most clause"; { $agent->click("Left"); ok($agent->form_name('BuildQuery'), "found the form again"); $agent->content_contains("error: can't move left", "i shouldn't have been able to hit left"); is_deeply selectedClauses($agent), ["0"], 'the one we tried to move is selected'; } diag "Add a condition into a nested block"; { $agent->select("clauses", ["1"]); $agent->select("ValueOfStatus" => "stalled"); $agent->submit; ok $agent->form_name('BuildQuery'), "found the form again"; is_deeply selectedClauses($agent), ["2"], 'the one we added is only selected'; is getQueryFromForm($agent), "( id > 1234 AND Status = 'stalled' ) OR Queue != 'Regression'", "added new one"; } diag "click advanced, enter 'C1 OR ( C2 AND C3 )', apply, aggregators should stay the same."; { my $response = $agent->get($url."Search/Edit.html"); ok( $response->is_success, "Fetched /Search/Edit.html" ); ok($agent->form_name('BuildQueryAdvanced'), "found the form"); $agent->field("Query", "Status = 'new' OR ( Status = 'open' AND Subject LIKE 'office' )"); $agent->submit; is( getQueryFromForm($agent), "Status = 'new' OR ( Status = 'open' AND Subject LIKE 'office' )", "no aggregators change" ); } # - new items go one level down # - add items at currently selected level # - if nothing is selected, add at end, one level down # # move left # - error if nothing selected # - same item should be selected after move # - can't move left if you're at the top level # # move right # - error if nothing selected # - same item should be selected after move # - can always move right (no max depth...should there be?) # # move up # - error if nothing selected # - same item should be selected after move # - can't move up if you're first in the list # # move down # - error if nothing selected # - same item should be selected after move # - can't move down if you're last in the list # # toggle # - error if nothing selected # - change all aggregators in the grouping # - don't change any others # # delete # - error if nothing selected # - delete currently selected item # - delete all children of a grouping # - if delete leaves a node with no children, delete that, too # - what should be selected? # # Clear # - clears entire query # - clears it from the session, too # create a custom field with nonascii name and try to add a condition { my $cf = RT::CustomField->new( RT->SystemUser ); $cf->LoadByName( Name => "\x{442}", LookupType => RT::Ticket->CustomFieldLookupType, ObjectId => 0 ); if ( $cf->id ) { is($cf->Type, 'Freeform', 'loaded and type is correct'); } else { my ($return, $msg) = $cf->Create( Name => "\x{442}", Queue => 0, Type => 'Freeform', ); ok($return, 'created CF') or diag "error: $msg"; } my $response = $agent->get($url."Search/Build.html?NewQuery=1"); ok( $response->is_success, "Fetched " . $url."Search/Build.html" ); ok($agent->form_name('BuildQuery'), "found the form once"); $agent->field("ValueOfCF.{\x{442}}", "\x{441}"); $agent->submit(); is( getQueryFromForm($agent), "CF.{\x{442}} LIKE '\x{441}'", "no changes, no duplicate condition with badly encoded text" ); $cf->SetDisabled(1); } diag "input a condition, select (several conditions), click delete"; { my $response = $agent->get( $url."Search/Edit.html" ); ok $response->is_success, "Fetched /Search/Edit.html"; ok $agent->form_name('BuildQueryAdvanced'), "found the form"; $agent->field("Query", "( Status = 'new' OR Status = 'open' )"); $agent->submit; is( getQueryFromForm($agent), "( Status = 'new' OR Status = 'open' )", "query is the same" ); $agent->select("clauses", [qw(0 1 2)]); $agent->field( ValueOfid => 10 ); $agent->click("DeleteClause"); is( getQueryFromForm($agent), "id < 10", "replaced query successfuly" ); } diag "send query with not quoted negative number"; { my $response = $agent->get($url."Search/Build.html?Query=Priority%20>%20-2"); ok( $response->is_success, "Fetched " . $url."Search/Build.html" ); is( getQueryFromForm($agent), "Priority > -2", "query is the same" ); } diag "click advanced, enter an invalid SQL IS restriction, apply and check that we corrected it"; { my $response = $agent->get($url."Search/Edit.html"); ok( $response->is_success, "Fetched /Search/Edit.html" ); ok($agent->form_name('BuildQueryAdvanced'), "found the form"); $agent->field("Query", "Requestor.EmailAddress IS 'FOOBAR'"); $agent->submit; is( getQueryFromForm($agent), "Requestor.EmailAddress IS NULL", "foobar is replaced by NULL" ); } diag "click advanced, enter an invalid SQL IS NOT restriction, apply and check that we corrected it"; { my $response = $agent->get($url."Search/Edit.html"); ok( $response->is_success, "Fetched /Search/Edit.html" ); ok($agent->form_name('BuildQueryAdvanced'), "found the form"); $agent->field("Query", "Requestor.EmailAddress IS NOT 'FOOBAR'"); $agent->submit; is( getQueryFromForm($agent), "Requestor.EmailAddress IS NOT NULL", "foobar is replaced by NULL" ); } diag "click advanced, enter a valid SQL, but the field is lower cased"; { my $response = $agent->get($url."Search/Edit.html"); ok( $response->is_success, "Fetched /Search/Edit.html" ); ok($agent->form_name('BuildQueryAdvanced'), "found the form"); $agent->field("Query", "status = 'new'"); $agent->submit; $agent->content_lacks( 'Unknown field:', 'no "unknown field" warning' ); is( getQueryFromForm($agent), "Status = 'new'", "field's case is corrected" ); } diag "make sure skipped order by field doesn't break search"; { my $t = RT::Test->create_ticket( Queue => 'General', Subject => 'test' ); ok $t && $t->id, 'created a ticket'; $agent->get_ok($url."Search/Edit.html"); ok($agent->form_name('BuildQueryAdvanced'), "found the form"); $agent->field("Query", "id = ". $t->id); $agent->submit; $agent->follow_link_ok({id => 'page-results'}); ok( $agent->find_link( text => $t->id, url_regex => qr{/Ticket/Display\.html}, ), "link to the ticket" ); $agent->follow_link_ok({id => 'page-edit_search'}); $agent->form_name('BuildQuery'); $agent->field("OrderBy", 'Requestor.EmailAddress', 3); $agent->submit; $agent->form_name('BuildQuery'); is $agent->value('OrderBy', 1), 'id'; is $agent->value('OrderBy', 2), ''; is $agent->value('OrderBy', 3), 'Requestor.EmailAddress'; $agent->follow_link_ok({id => 'page-results'}); ok( $agent->find_link( text => $t->id, url_regex => qr{/Ticket/Display\.html}, ), "link to the ticket" ); } diag "make sure the list of columns available in the 'Order by' dropdowns are complete"; { $agent->get_ok($url . 'Search/Build.html'); my @orderby = qw( AdminCc.EmailAddress Cc.EmailAddress Created Creator Custom.Ownership Due FinalPriority InitialPriority LastUpdated LastUpdatedBy Owner Priority Queue Requestor.EmailAddress Resolved SLA Started Starts Status Subject TimeEstimated TimeLeft TimeWorked Told Type id ); my $orderby = join(' ', sort @orderby); my @scraped_orderbys = $agent->scrape_text_by_attr('name', 'OrderBy'); my $first_orderby = shift @scraped_orderbys; is ($first_orderby, $orderby); foreach my $scraped_orderby ( @scraped_orderbys ) { is ($scraped_orderby, '[none] '.$orderby); } my $cf = RT::Test->load_or_create_custom_field( Name => 'Location', Queue => 'General', Type => 'FreeformSingle', ); isa_ok( $cf, 'RT::CustomField' ); ok($agent->form_name('BuildQuery'), "found the form"); $agent->field("ValueOfQueue", "General"); $agent->submit; push @orderby, 'CustomField.{Location}'; $orderby = join(' ', sort @orderby); @scraped_orderbys = $agent->scrape_text_by_attr('name', 'OrderBy'); $first_orderby = shift @scraped_orderbys; is ($first_orderby, $orderby); foreach my $scraped_orderby ( @scraped_orderbys ) { is ($scraped_orderby, '[none] '.$orderby); } $cf->SetDisabled(1); } diag "make sure active and inactive statuses generate the correct query"; { $agent->get_ok( $url . '/Search/Build.html?NewQuery=1' ); ok( $agent->form_name( 'BuildQuery' ), "found the form" ); $agent->field( ValueOfStatus => 'active' ); $agent->click( 'AddClause' ); is getQueryFromForm( $agent ), "Status = '__Active__'", "active status generated the correct query"; $agent->get_ok( $url . '/Search/Build.html?NewQuery=1' ); ok( $agent->form_name( 'BuildQuery' ), "found the form" ); $agent->field( ValueOfStatus => 'inactive' ); $agent->click( 'AddClause' ); is getQueryFromForm( $agent ), "Status = '__Inactive__'", "inactive status generated the correct query"; } diag "test null values"; { my $cf = RT::Test->load_or_create_custom_field( Name => 'foo', Type => 'FreeformSingle', Queue => 0, ); RT::Test->create_tickets( { Queue => 'General' }, { Subject => 'ticket bar', 'CustomField-' . $cf->id => 'bar' }, { Subject => 'ticket baz', 'CustomField-' . $cf->id => 'baz' }, { Subject => 'ticket null' }, ); $agent->get_ok( '/Search/Build.html?NewQuery=1' ); $agent->submit_form( form_name => 'BuildQuery', fields => { 'CF.{foo}Op' => '=', 'ValueOfCF.{foo}' => 'NULL', }, button => 'DoSearch', ); $agent->title_is( 'Found 2 tickets', 'found 2 tickets with CF.{foo} IS NULL' ); # the other ticket was created before the block $agent->content_contains( 'ticket null', 'has ticket null' ); $agent->follow_link_ok( { text => 'Advanced' } ); $agent->text_lacks( q[CF.{foo} = 'NULL'] ); $agent->text_contains( 'CF.{foo} IS NULL', q["= 'NULL'" is converted to "IS NULL"] ); $agent->get_ok( '/Search/Build.html?NewQuery=1' ); $agent->submit_form( form_name => 'BuildQuery', fields => { 'CF.{foo}Op' => '!=', 'ValueOfCF.{foo}' => 'NULL', }, button => 'DoSearch', ); $agent->title_is( 'Found 2 tickets', 'found 2 ticket with CF.{foo} IS NOT NULL' ); $agent->content_contains( 'ticket bar', 'has ticket bar' ); $agent->content_contains( 'ticket baz', 'has ticket baz' ); $agent->follow_link_ok( { text => 'Advanced' } ); $agent->text_lacks( q[CF.{foo} != 'NULL'] ); $agent->text_contains( 'CF.{foo} IS NOT NULL', q["!= 'NULL'" is converted to "IS NOT NULL"] ); } done_testing; rt-5.0.1/t/web/command_line_link_to_articles.t000644 000765 000024 00000002640 14005011336 022242 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::Expect; use RT::Test tests => 12, actual_server => 1; my $class = RT::Class->new( RT->SystemUser ); my ( $class_id, $msg ) = $class->Create( Name => 'foo' ); ok( $class_id, $msg ); my $article = RT::Article->new( RT->SystemUser ); ( my $article_id, $msg ) = $article->Create( Class => 'foo', Summary => 'article summary' ); ok( $article_id, $msg ); my ( $baseurl, $m ) = RT::Test->started_ok; my $rt_tool_path = "$RT::BinPath/rt"; $ENV{'RTUSER'} = 'root'; $ENV{'RTPASSWD'} = 'password'; $RT::Logger->debug( "Connecting to server at " . RT->Config->Get('WebBaseURL') ); $ENV{'RTSERVER'} = RT->Config->Get('WebBaseURL'); $ENV{'RTDEBUG'} = '1'; $ENV{'RTCONFIG'} = '/dev/null'; expect_run( command => "$rt_tool_path shell", prompt => 'rt> ', quit => 'quit', ); expect_send( q{create -t ticket set subject='new ticket'}, "creating a ticket..." ); expect_like( qr/Ticket \d+ created/, "created the ticket" ); expect_handle->before() =~ /Ticket (\d+) created/; my $ticket_id = $1; expect_send( "link $ticket_id RefersTo a:$article_id", "link $ticket_id RefersTo a:$article_id" ); expect_like( qr/Created link $ticket_id RefersTo a:$article_id/, 'created link' ); expect_send( "show -s ticket/$ticket_id/links", "show ticket links" ); expect_like( qr|RefersTo: fsck\.com-article://example\.com/article/$article_id|, "found new created link" ); expect_quit(); rt-5.0.1/t/web/config_tab_right.t000644 000765 000024 00000001524 14005011336 017500 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => 10; my ($uname, $upass, $user) = ('tester', 'tester'); { $user = RT::User->new(RT->SystemUser); my ($status, $msg) = $user->Create( Name => $uname, Password => $upass, Disabled => 0, Privileged => 1, ); ok($status, 'created a user'); } my ($baseurl, $m) = RT::Test->started_ok; ok $m->login($uname, $upass), "logged in"; { $m->content_lacks('li-admin', 'no Admin tab'); $m->get('/Admin/'); is $m->status, 403, 'no access to /Admin/'; } RT::Test->set_rights( { Principal => $user->PrincipalObj, Right => [qw(ShowConfigTab)], }, ); { $m->get('/'); $m->content_contains('li-admin', 'admin tab is there'); $m->follow_link_ok({text => 'Admin'}); is $m->status, 200, 'user has access to /Admin/'; } rt-5.0.1/t/web/gnupg-select-keys-on-create.t000644 000765 000024 00000021544 14005011336 021435 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::GnuPG tests => undef, gnupg_options => { passphrase => 'rt-test' }; use RT::Action::SendEmail; my $queue = RT::Test->load_or_create_queue( Name => 'Regression', CorrespondAddress => 'rt-recipient@example.com', CommentAddress => 'rt-recipient@example.com', ); ok $queue && $queue->id, 'loaded or created queue'; my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in'; diag "check that signing doesn't work if there is no key"; { RT::Test->clean_caught_mails; ok $m->goto_create_ticket( $queue ), "UI -> create ticket"; $m->form_name('TicketCreate'); $m->tick( Sign => 1 ); $m->field( Requestors => 'rt-test@example.com' ); $m->field( Content => 'Some content' ); $m->click('SubmitTicket'); $m->content_contains( 'unable to sign outgoing email messages', 'problems with passphrase' ); $m->warning_like(qr/signing failed: (?:secret key not available|No secret key)/); my @mail = RT::Test->fetch_caught_mails; ok !@mail, 'there are no outgoing emails'; } { RT::Test->import_gnupg_key('rt-recipient@example.com'); RT::Test->trust_gnupg_key('rt-recipient@example.com'); my %res = RT::Crypt->GetKeysInfo( Key => 'rt-recipient@example.com' ); is $res{'info'}[0]{'TrustTerse'}, 'ultimate', 'ultimately trusted key'; } diag "check that things don't work if there is no key"; { RT::Test->clean_caught_mails; ok $m->goto_create_ticket( $queue ), "UI -> create ticket"; $m->form_name('TicketCreate'); $m->tick( Encrypt => 1 ); $m->field( Requestors => 'rt-test@example.com' ); $m->field( Content => 'Some content' ); $m->click('SubmitTicket'); $m->content_contains( 'You are going to encrypt outgoing email messages', 'problems with keys' ); $m->content_contains( 'There is no key suitable for encryption', 'problems with keys' ); my $form = $m->form_name('TicketCreate'); ok !$form->find_input( 'UseKey-rt-test@example.com' ), 'no key selector'; my @mail = RT::Test->fetch_caught_mails; ok !@mail, 'there are no outgoing emails'; $m->next_warning_like(qr/public key not found|No public key/) for 1 .. 2; $m->no_leftover_warnings_ok; } diag "import first key of rt-test\@example.com"; my $fpr1 = ''; { RT::Test->import_gnupg_key('rt-test@example.com', 'secret'); my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test@example.com' ); is $res{'info'}[0]{'TrustLevel'}, 0, 'is not trusted key'; $fpr1 = $res{'info'}[0]{'Fingerprint'}; } diag "check that things still doesn't work if key is not trusted"; { RT::Test->clean_caught_mails; ok $m->goto_create_ticket( $queue ), "UI -> create ticket"; $m->form_name('TicketCreate'); $m->tick( Encrypt => 1 ); $m->field( Requestors => 'rt-test@example.com' ); $m->field( Content => 'Some content' ); $m->click('SubmitTicket'); $m->content_contains( 'You are going to encrypt outgoing email messages', 'problems with keys' ); $m->content_contains( 'There is one suitable key, but trust level is not set', 'problems with keys' ); my $form = $m->form_name('TicketCreate'); ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector'; is scalar $input->possible_values, 1, 'one option'; $m->select( 'UseKey-rt-test@example.com' => $fpr1 ); $m->click('SubmitTicket'); $m->content_contains( 'You are going to encrypt outgoing email messages', 'problems with keys' ); $m->content_contains( 'Selected key either is not trusted', 'problems with keys' ); my @mail = RT::Test->fetch_caught_mails; ok !@mail, 'there are no outgoing emails'; $m->no_warnings_ok; } diag "import a second key of rt-test\@example.com"; my $fpr2 = ''; { RT::Test->import_gnupg_key('rt-test@example.com.2', 'secret'); my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test@example.com' ); is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key'; $fpr2 = $res{'info'}[2]{'Fingerprint'}; } diag "check that things still doesn't work if two keys are not trusted"; { RT::Test->clean_caught_mails; ok $m->goto_create_ticket( $queue ), "UI -> create ticket"; $m->form_name('TicketCreate'); $m->tick( Encrypt => 1 ); $m->field( Requestors => 'rt-test@example.com' ); $m->field( Content => 'Some content' ); $m->click('SubmitTicket'); $m->content_contains( 'You are going to encrypt outgoing email messages', 'problems with keys' ); $m->content_contains( 'There are several keys suitable for encryption', 'problems with keys' ); my $form = $m->form_name('TicketCreate'); ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector'; is scalar $input->possible_values, 2, 'two options'; $m->select( 'UseKey-rt-test@example.com' => $fpr1 ); $m->click('SubmitTicket'); $m->content_contains( 'You are going to encrypt outgoing email messages', 'problems with keys' ); $m->content_contains( 'Selected key either is not trusted', 'problems with keys' ); my @mail = RT::Test->fetch_caught_mails; ok !@mail, 'there are no outgoing emails'; $m->no_warnings_ok; } { RT::Test->lsign_gnupg_key( $fpr1 ); my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test@example.com' ); ok $res{'info'}[0]{'TrustLevel'} > 0, 'trusted key'; is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key'; } diag "check that we see key selector even if only one key is trusted but there are more keys"; { RT::Test->clean_caught_mails; ok $m->goto_create_ticket( $queue ), "UI -> create ticket"; $m->form_name('TicketCreate'); $m->tick( Encrypt => 1 ); $m->field( Requestors => 'rt-test@example.com' ); $m->field( Content => 'Some content' ); $m->click('SubmitTicket'); $m->content_contains( 'You are going to encrypt outgoing email messages', 'problems with keys' ); $m->content_contains( 'There are several keys suitable for encryption', 'problems with keys' ); my $form = $m->form_name('TicketCreate'); ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector'; is scalar $input->possible_values, 2, 'two options'; my @mail = RT::Test->fetch_caught_mails; ok !@mail, 'there are no outgoing emails'; $m->no_warnings_ok; } diag "check that key selector works and we can select trusted key"; { RT::Test->clean_caught_mails; ok $m->goto_create_ticket( $queue ), "UI -> create ticket"; $m->form_name('TicketCreate'); $m->tick( Encrypt => 1 ); $m->field( Requestors => 'rt-test@example.com' ); $m->field( Content => 'Some content' ); $m->click('SubmitTicket'); $m->content_contains( 'You are going to encrypt outgoing email messages', 'problems with keys' ); $m->content_contains( 'There are several keys suitable for encryption', 'problems with keys' ); my $form = $m->form_name('TicketCreate'); ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector'; is scalar $input->possible_values, 2, 'two options'; $m->select( 'UseKey-rt-test@example.com' => $fpr1 ); $m->click('SubmitTicket'); $m->content_like( qr/Ticket \d+ created in queue/i, 'ticket created' ); my @mail = RT::Test->fetch_caught_mails; ok @mail, 'there are some emails'; check_text_emails( { Encrypt => 1 }, @mail ); $m->no_warnings_ok; } diag "check encrypting of attachments"; for my $encrypt (0, 1) { RT::Test->clean_caught_mails; ok $m->goto_create_ticket( $queue ), "UI -> create ticket"; $m->form_name('TicketCreate'); $m->tick( Encrypt => 1 ) if $encrypt; $m->field( Requestors => '' ); $m->field( Cc => 'rt-test@example.com' ); $m->field( Content => 'Some content' ); $m->field( Attach => $0 ); $m->click('SubmitTicket'); if ($encrypt) { $m->content_contains( 'You are going to encrypt outgoing email messages', 'problems with keys' ); $m->content_contains( 'There are several keys suitable for encryption', 'problems with keys' ); my $form = $m->form_name('TicketCreate'); ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector'; is scalar $input->possible_values, 2, 'two options'; $m->select( 'UseKey-rt-test@example.com' => $fpr1 ); $m->click('SubmitTicket'); } $m->content_like( qr/Ticket \d+ created in queue/i, 'ticket created' ); my @mail = RT::Test->fetch_caught_mails; ok @mail, 'there are some emails'; check_text_emails( { Encrypt => $encrypt, Attachment => "Attachment content" }, @mail ); $m->no_warnings_ok; } done_testing; rt-5.0.1/t/web/dashboards-in-menu.t000644 000765 000024 00000020662 14005011336 017674 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ($baseurl, $m) = RT::Test->started_ok; my $system_foo = RT::Dashboard->new($RT::SystemUser); $system_foo->Save( Name => 'system foo', Privacy => 'RT::System-' . $RT::System->id, ); my $system_bar = RT::Dashboard->new($RT::SystemUser); $system_bar->Save( Name => 'system bar', Privacy => 'RT::System-' . $RT::System->id, ); ok( $m->login(), "logged in" ); my $root = RT::CurrentUser->new( $RT::SystemUser ); ok( $root->Load('root') ); diag "global setting"; # in case "RT at a glance" contains dashboards stuff. $m->get_ok( $baseurl . "/Search/Simple.html" ); ok( !$m->find_link( text => 'system foo' ), 'no system foo link' ); $m->get_ok( $baseurl."/Admin/Global/DashboardsInMenu.html"); my $args = { UpdateSearches => "Save", dashboard_id => "DashboardsInMenu", dashboard => ( "dashboard-".$system_foo->Name ) }; my $res = $m->post( $baseurl . '/Admin/Global/DashboardsInMenu.html', $args, ); my ($dashboard_attr) = RT->System->Attributes->Named('DashboardsInMenu'); is_deeply( $dashboard_attr->Content, { dashboards => [ $system_foo->Id ] }, "DashboardsInMenu attribute correctly updated" ); is( $res->code, 200, "remove all dashboards from dashboards menu except 'system foo'" ); $m->content_contains( 'Global dashboards in menu saved.' ); $args = { UpdateSearches => "Save", dashboard_id => "ReportsInMenu", report => 'report-Created in a date range' }; $res = $m->post( $baseurl . '/Admin/Global/DashboardsInMenu.html', $args, ); my ($report_attr) = RT::System->new( RT->SystemUser )->Attributes->Named('ReportsInMenu'); is_deeply( @{$report_attr->Content}, { id => "createdindaterange", path => "/Reports/CreatedByDates.html", title => "Created in a date range", type => 'report', name => 'Created in a date range', label => 'Created in a date range', }, "ReportsInMenu attribute correctly updated" ); is( $res->code, 200, "remove all reports from reports menu except 'Created in a date range'" ); $m->content_contains( 'Preferences saved for reports in menu.' ); $m->logout; ok( $m->login(), "relogged in" ); $m->get_ok( $baseurl . "/Search/Simple.html" ); $m->follow_link_ok( { text => 'system foo' }, 'follow system foo link' ); $m->title_is( 'system foo Dashboard', 'got system foo dashboard page' ); diag "setting in admin users"; my $self_foo = RT::Dashboard->new($root); $self_foo->Save( Name => 'self foo', Privacy => 'RT::User-' . $root->id ); my $self_bar = RT::Dashboard->new($root); $self_bar->Save( Name => 'self bar', Privacy => 'RT::User-' . $root->id ); ok( !$m->find_link( text => 'self foo' ), 'no self foo link' ); $m->get_ok( $baseurl."/Admin/Users/DashboardsInMenu.html?id=" . $root->id); $args = { UpdateSearches => "Save", dashboard_id => "DashboardsInMenu", dashboard => [ "dashboard-".$self_foo->Name ] }; $res = $m->post( $baseurl . '/Admin/Users/DashboardsInMenu.html?id='.$root->Id, $args, ); my $dashboard_prefs = $root->Preferences('DashboardsInMenu'); is_deeply( $dashboard_prefs, { dashboards => [ $self_foo->Id ] }, "DashboardsInMenu attribute correctly updated for user" ); is( $res->code, 200, "Add dashboard ".$self_foo->Name." to user DashboardsInMenu prefs" ); $m->content_contains( 'Preferences saved for dashboards in menu.' ); diag "setting in prefs"; $m->get_ok( $baseurl."/Prefs/DashboardsInMenu.html"); $args = { UpdateSearches => "Save", dashboard_id => "DashboardsInMenu", dashboard => "dashboard-".$self_bar->Name }; $res = $m->post( $baseurl . '/Prefs/DashboardsInMenu.html', $args, ); $root = RT::CurrentUser->new( $RT::SystemUser ); ok( $root->Load('root') ); $dashboard_prefs = $root->Preferences('DashboardsInMenu'); is_deeply( $dashboard_prefs, { dashboards => [ $self_bar->Id ] }, "DashboardsInMenu user pref correctly updated" ); is( $res->code, 200, "Add dashboard ".$self_bar->Name." to user DashboardsInMenu prefs" ); $m->content_contains( 'Preferences saved for dashboards in menu.' ); diag "Reset dashboard menu"; $m->get_ok( $baseurl."/Prefs/DashboardsInMenu.html"); $m->form_with_fields('ResetDashboards'); $m->click; $m->content_contains( 'Preferences saved', 'prefs saved' ); ok( $m->find_link( text => 'system foo' ), 'got system foo link' ); ok( !$m->find_link( text => 'self bar' ), 'no self bar link' ); foreach my $test_path ( '/Prefs/DashboardsInMenu.html', '/Admin/Global/DashboardsInMenu.html', '/Admin/Users/DashboardsInMenu.html' ) { diag "Testing $test_path"; { my @tests = ( { args => { UpdateSearches => "Save", dashboard_id => "DashboardsInMenu", dashboard => [ ], }, ret => { dashboards => [ ] } }, { args => { UpdateSearches => "Save", dashboard_id => "DashboardsInMenu", dashboard => [ "dashboard-".$system_foo->Name ], }, ret => { dashboards => [ $system_foo->Id ] } }, { args => { UpdateSearches => "Save", dashboard_id => "DashboardsInMenu", dashboard => [ "dashboard-".$system_foo->Name, "dashboard-".$system_bar->Name ], }, ret => { dashboards => [ $system_foo->Id, $system_bar->Id ] } } ); foreach my $test ( @tests ) { $m->get_ok( $baseurl."$test_path?id=".$root->Id ); my $res = $m->post( $baseurl."$test_path?id=".$root->Id, $test->{'args'}, ); my $msg; if ( $test_path eq '/Admin/Global/DashboardsInMenu.html' ) { $msg = 'Global dashboards in menu saved.'; } else { $msg = 'Preferences saved for dashboards in menu.'; } is( $res->code, 200, "Update dashboards" ); $m->content_contains( $msg ); my $dashboard_attr; if ( $test_path eq '/Admin/Global/DashboardsInMenu.html' ) { my $sys = RT::System->new( $root ); ($dashboard_attr) = $sys->Attributes->Named('DashboardsInMenu'); $dashboard_attr = $dashboard_attr->Content; } else { $root = RT::CurrentUser->new( $RT::SystemUser ); ok( $root->Load('root') ); $dashboard_attr = $root->Preferences( 'DashboardsInMenu' ); } is_deeply( $dashboard_attr, $test->{'ret'}, "DashboardsInMenu attribute correctly updated" ); } @tests = ( { args => { UpdateSearches => "Save", dashboard_id => "ReportsInMenu", report => [ "report-Created in a date range" ], }, ret => [{ id => "createdindaterange", path => "/Reports/CreatedByDates.html", title => "Created in a date range", type => 'report', name => 'Created in a date range', label => 'Created in a date range', }] }, ); foreach my $test ( @tests ) { $m->get_ok( $baseurl."$test_path?id=".$root->Id ); my $res = $m->post( $baseurl."$test_path?id=".$root->Id, $test->{'args'}, ); my $report_attr; if ( $test_path eq '/Admin/Global/DashboardsInMenu.html' ) { my $sys = RT::System->new( $root ); ($report_attr) = $sys->Attributes->Named('ReportsInMenu'); $report_attr = $report_attr->Content; } else { $root = RT::CurrentUser->new( $RT::SystemUser ); ok( $root->Load('root') ); $report_attr = $root->Preferences( 'ReportsInMenu' ); } is_deeply( $report_attr, $test->{'ret'} ); } } } diag "Delete system dashboard"; $m->get_ok( $baseurl . "/Dashboards/index.html" ); $m->follow_link_ok( { text => 'system foo' }, 'follow self foo link' ); $m->follow_link_ok( { text => 'Basics' }, 'Click dashboard Basics' ); $m->form_name('ModifyDashboard'); $m->click_button(name => 'Delete'); $m->get_ok( $baseurl . "/Dashboards/index.html" ); $m->content_lacks('system foo', 'Dashboard is deleted'); done_testing; rt-5.0.1/t/web/action-results.t000644 000765 000024 00000003615 14005011336 017167 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 'no_declare'; my ($url, $m) = RT::Test->started_ok; ok $m->login, "Logged in"; # We test two ticket creation paths since one historically doesn't update the # session (quick create) and the other does. for my $quick (1, 0) { diag $quick ? "Quick ticket creation" : "Normal ticket creation"; $m->get_ok("/"); $m->get_ok( '/Ticket/Create.html?Queue=1', "Create new ticket form" ) unless $quick; $m->submit_form_ok({ with_fields => { Subject => "The Plants", Content => "Please water them.", }, button => 'SubmitTicket', }, "Submitted new ticket"); my $id = RT::Test->last_ticket->id; like $m->uri, qr/results=[A-Za-z0-9]{32}/, "URI contains results hash"; $m->content_contains("Ticket $id created", "Page contains results message"); $m->content_contains("#$id: The Plants") unless $quick; diag "Reloading without a referer but with a results hash doesn't trigger the CSRF"; { # Mech's API here sucks. To drop the Referer and simulate a real browser # reload, we need to make a new request which explicitly adds an empty Referer # header (causing it to never be sent) and then deletes the empty Referer # header to let it be automatically managed again. $m->add_header("Referer" => undef); $m->get_ok( $m->uri, "Reloading the results page without a Referer" ); $m->delete_header("Referer"); like $m->uri, qr/results=[A-Za-z0-9]{32}/, "URI contains results hash"; $m->content_lacks("cross-site request forgery", "Skipped the CSRF interstitial") or $m->follow_link_ok({ text => "click here to resume your request" }, "Ignoring CSRF warning"); $m->content_lacks("Ticket $id created", "Page lacks results message"); $m->content_contains("#$id: The Plants") unless $quick; } } done_testing; rt-5.0.1/t/web/cf_image.t000644 000765 000024 00000003340 14005011336 015740 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 'no_declare'; my (undef, $m) = RT::Test->started_ok; $m->login; $m->follow_link( id => 'admin-custom-fields-create' ); $m->submit_form_ok({ form_name => "ModifyCustomField", fields => { Name => 'Images', TypeComposite => 'Image-1', LookupType => 'RT::Queue-RT::Ticket', EntryHint => 'Upload one image', }, }); $m->content_contains("Object created"); my $cfid = $m->form_name('ModifyCustomField')->value('id'); ok $cfid, "Created CF correctly"; $m->follow_link_ok( {id => "page-applies-to"} ); $m->form_with_fields( "AddCustomField-2" ); $m->tick( "AddCustomField-2", 0 ); $m->click_ok( "UpdateObjs" ); $m->content_contains("Globally added custom field Images"); $m->submit_form( form_name => "CreateTicketInQueue" ); $m->content_contains("Upload one image"); $m->submit_form_ok({ form_name => "TicketCreate", fields => { Subject => 'Test ticket', Content => 'test', }, button => 'SubmitTicket', }); $m->content_like( qr/Ticket \d+ created/, "a ticket is created succesfully" ); $m->follow_link_ok( {id => "page-basics"} ); $m->content_contains("Upload one image"); $m->submit_form_ok({ form_name => "TicketModify", fields => { "Object-RT::Ticket-1-CustomField-2-Upload" => RT::Test::get_relocatable_file('bpslogo.png', '..', 'data'), }, }); $m->content_contains("bpslogo.png added"); $m->content_contains("/Download/CustomFieldValue/1/bpslogo.png"); $m->form_name("TicketModify"); $m->tick("Object-RT::Ticket-1-CustomField-2-DeleteValueIds", 1); $m->click_ok("SubmitTicket"); $m->content_lacks("/Download/CustomFieldValue/1/bpslogo.png"); done_testing; rt-5.0.1/t/web/clickjacking-preventions.t000644 000765 000024 00000001370 14005011336 021175 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 11; my ($url, $m); # Enabled by default { ok(RT->Config->Get('Framebusting'), "Framebusting enabled by default"); ($url, $m) = RT::Test->started_ok; $m->get_ok($url); $m->content_contains('if (window.top !== window.self) {', "Found the framekiller javascript"); is $m->response->header('X-Frame-Options'), 'DENY', "X-Frame-Options is set to DENY"; RT::Test->stop_server; } # Disabled { RT->Config->Set('Framebusting', 0); ($url, $m) = RT::Test->started_ok; $m->get_ok($url); $m->content_lacks('if (window.top !== window.self) {', "Didn't find the framekiller javascript"); is $m->response->header('X-Frame-Options'), undef, "X-Frame-Options is not present"; } rt-5.0.1/t/web/admin_user.t000644 000765 000024 00000011351 14005011336 016335 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::GnuPG tests => undef, gnupg_options => { passphrase => 'recipient', 'trust-model' => 'always', }; RT::Test->import_gnupg_key( 'rt-test@example.com', 'secret' ); ok( my $user = RT::User->new( RT->SystemUser ) ); ok( $user->Load('root'), "loaded user 'root'" ); $user->SetEmailAddress('rt-test@example.com'); my ( $url, $m ) = RT::Test->started_ok; ok( $m->login(), 'logged in' ); my $root = RT::User->new( $RT::SystemUser ); $root->Load('root'); ok( $root->id, 'loaded root' ); diag "test the history page" if $ENV{TEST_VERBOSE}; $m->get_ok( $url . '/Admin/Users/History.html?id=' . $root->id ); $m->content_contains('User created', 'has User created entry'); diag "test keys page" if $ENV{TEST_VERBOSE}; $m->follow_link_ok( { text => 'Private keys' } ); $m->content_contains('Public key(s) for rt-test@example.com'); $m->content_contains('The key is ultimately trusted'); $m->content_contains('F0CB3B482CFA485680A4A0BDD328035D84881F1B'); $m->content_contains('Tue Aug 07 2007'); $m->content_contains('never'); $m->content_contains('GnuPG private key'); my $form = $m->form_with_fields('PrivateKey'); is( $form->find_input('PrivateKey')->value, '__empty_value__', 'default no private key' ); $m->submit_form_ok( { fields => { PrivateKey => 'D328035D84881F1B' }, button => 'Update', }, 'submit PrivateKey form' ); $m->content_contains('Set private key'); $form = $m->form_with_fields('PrivateKey'); is( $form->find_input('PrivateKey')->value, 'D328035D84881F1B', 'set private key' ); $m->submit_form_ok( { fields => { PrivateKey => '__empty_value__' }, button => 'Update', }, 'submit PrivateKey form' ); $m->content_contains('Unset private key'); is( $form->find_input('PrivateKey')->value, '__empty_value__', 'unset private key' ); $form = $m->form_with_fields('PrivateKey'); $m->submit_form_ok( { fields => { PrivateKey => 'C798591AA831DBFB' }, button => 'Update', }, 'submit PrivateKey form' ); is( $form->find_input('PrivateKey')->value, 'C798591AA831DBFB', 'set private key' ); diag "Test user searches"; my @cf_names = qw( CF1 CF2 CF3 ); my @cfs = (); foreach my $cf_name ( @cf_names ) { my $cf = RT::CustomField->new( RT->SystemUser ); my ( $id, $msg ) = $cf->Create( Name => $cf_name, TypeComposite => 'Freeform-1', LookupType => RT::User->CustomFieldLookupType, ); ok( $id, $msg ); # Create a global ObjectCustomField record my $object = $cf->RecordClassFromLookupType->new( RT->SystemUser ); ( $id, $msg ) = $cf->AddToObject( $object ); ok( $id, $msg ); push ( @cfs, $cf ); } my $cf_1 = $cfs[0]; my $cf_2 = $cfs[1]; my $cf_3 = $cfs[2]; my @user_names = qw( user1 user2 user3 user4 ); my @users = (); foreach my $user_name ( @user_names ) { my $user = RT::Test->load_or_create_user( Name => $user_name, Password => 'password', ); ok( $user && $user->id, 'Created '.$user->Name.' with id '.$user->Id ); push ( @users, $user ); } $users[0]->AddCustomFieldValue( Field => $cf_1->id, Value => 'one' ); $users[1]->AddCustomFieldValue( Field => $cf_1->id, Value => 'one' ); $users[1]->AddCustomFieldValue( Field => $cf_2->id, Value => 'two' ); $users[2]->AddCustomFieldValue( Field => $cf_1->id, Value => 'one' ); $users[2]->AddCustomFieldValue( Field => $cf_2->id, Value => 'two' ); $users[2]->AddCustomFieldValue( Field => $cf_3->id, Value => 'three' ); $m->get_ok( $url . '/Admin/Users/index.html' ); ok( $m->form_name( 'UsersAdmin' ), 'found the filter admin users form'); $m->select( UserField => 'Name', UserOp => 'LIKE' ); $m->field( UserString => 'user' ); $m->select( UserField2 => 'CustomField: '.$cf_1->Name, UserOp2 => 'LIKE' ); $m->field( UserString2 => 'one' ); $m->select( UserField3 => 'CustomField: '.$cf_2->Name, UserOp3 => 'LIKE' ); $m->field( UserString3 => 'two' ); $m->click( 'Go' ); diag "Verify results contain users 2 & 3, but not 1 & 4"; $m->content_contains( $users[1]->Name ); $m->content_contains( $users[2]->Name ); $m->content_lacks( $users[0]->Name ); $m->content_lacks( $users[3]->Name ); diag 'Test NULL value searches'; $m->form_name( 'UsersAdmin' ); $m->select( UserField2 => 'CustomField: '.$cf_2->Name, UserOp2 => 'is' ); $m->field( UserString2 => 'NULL' ); $m->field( UserString3 => '' ); $m->click( 'Go' ); $m->text_lacks( $_->Name ) for @users[1..2]; $m->text_contains( $_->Name ) for @users[0,3]; ok( $users[0]->SetRealName('user1') ); $m->form_name( 'UsersAdmin' ); $m->select( UserField2 => 'Real Name', UserOp2 => 'is' ); $m->field( UserString2 => 'NULL' ); $m->click( 'Go' ); $m->text_lacks( $_->Name ) for $users[0]; $m->text_contains( $_->Name ) for @users[1..3]; # TODO more /Admin/Users tests done_testing; rt-5.0.1/t/web/scrub.t000644 000765 000024 00000005274 14005011336 015334 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodb => 1, tests => undef; use RT::Interface::Web; # This gets us HTML::Mason::Commands use Test::LongString; sub scrub_html { return HTML::Mason::Commands::ScrubHTML(shift); } { my $html = 'This is a test of color and font and boldness.'; is_string(scrub_html($html), $html, "CKEditor produced HTML sails through"); } { my $html = '

      And alignment with color?

      '; is_string(scrub_html($html), $html, "CKEditor produced HTML sails through"); } { my $html = 'This is a test of color and font and boldness.'; my $expected = 'This is a test of color and font and boldness.'; is_string(scrub_html($html), $expected, "nasty CSS not allowed through"); } { my $html = 'Let\'s add some color up in here.'; is_string(scrub_html($html), $html, "multiple props and color specs allowed"); } { my $html = q[oh hai I'm some text]; my $expected = q[oh hai I'm some text]; is_string(scrub_html($html), $expected, "font lists"); } { my $html = q[oh hai I'm some text]; my $expected = q[oh hai I'm some text]; is_string(scrub_html($html), $expected, "outlook html"); } { my $html = q[
      Some content here
      An unclosed bold tag.]; my $expected = q[
      Some content here
      An unclosed bold tag.]; is_string(scrub_html($html), $expected, "Unbalanced tags get balanced"); } done_testing; rt-5.0.1/t/web/html_template.t000644 000765 000024 00000004412 14005011336 017046 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in as root'; diag('make Autoreply template a html one and add utf8 chars') if $ENV{TEST_VERBOSE}; my $template = Encode::decode("UTF-8", "你好 éèà€"); my $subject = Encode::decode("UTF-8", "标题"); my $content = Encode::decode("UTF-8", "测试"); { $m->follow_link_ok( { id => 'admin-global-templates' }, '-> Templates' ); $m->follow_link_ok( { text => 'Autoreply in HTML' }, '-> Autoreply in HTML' ); $m->submit_form( form_name => 'ModifyTemplate', fields => { Content => <Subject} Content-Type: text/html $template {\$Ticket->Subject} ------------------------------------------------------------------------- {\$Transaction->Content()} EOF }, ); $m->content_like( qr/Content updated/, 'content is changed' ); $m->content_contains( $template, 'content is really updated' ); } diag('create a ticket to see the autoreply mail') if $ENV{TEST_VERBOSE}; { $m->get_ok( $baseurl . '/Ticket/Create.html?Queue=1' ); $m->submit_form( form_name => 'TicketCreate', fields => { Subject => $subject, Content => "

      $content

      ", ContentType => 'text/html' }, button => 'SubmitTicket', ); $m->content_like( qr/Ticket \d+ created/i, 'created the ticket' ); $m->follow_link( url_regex => qr/ShowEmailRecord/ ); $m->content_contains( $template, "html has $template" ); $m->content_contains( $subject, "html has ticket subject $subject" ); $m->content_contains( "<h1>$content</h1>", "html has ticket html content $content" ); } diag('test real mail outgoing') if $ENV{TEST_VERBOSE}; { # $mail is utf8 encoded my ($mail) = RT::Test->fetch_caught_mails; $mail = Encode::decode("UTF-8", $mail ); like( $mail, qr/$template.*$template/s, 'mail has template content $template twice' ); like( $mail, qr/$subject.*$subject/s, 'mail has ticket subject $sujbect twice' ); like( $mail, qr/$content.*$content/s, 'mail has ticket content $content twice' ); like( $mail, qr!

      $content

      !, 'mail has ticket html content

      $content

      ' ); } done_testing; rt-5.0.1/t/web/simple_search.t000644 000765 000024 00000021542 14005011336 017030 0ustar00sunnavystaff000000 000000 use strict; use warnings; 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; my $queue = RT::Queue->new($RT::SystemUser); $queue->Create( Name => 'other' ); ok( $queue->id, 'created queue other'); my $two_words_queue = RT::Test->load_or_create_queue( Name => 'Two Words', ); ok $two_words_queue && $two_words_queue->id, 'loaded or created a queue'; { my $tickets = RT::Tickets->new( RT->SystemUser ); require RT::Search::Simple; my $parser = RT::Search::Simple->new( TicketsObj => $tickets, Argument => '', ); is $parser->QueryToSQL("foo"), "( Subject LIKE 'foo' ) AND ( Status = '__Active__' )", "correct parsing"; is $parser->QueryToSQL("1 foo"), "( Subject LIKE 'foo' AND Subject LIKE '1' ) AND ( Status = '__Active__' )", "correct parsing"; is $parser->QueryToSQL("1"), "( Id = 1 )", "correct parsing"; is $parser->QueryToSQL("#1"), "( Id = 1 )", "correct parsing"; is $parser->QueryToSQL("'1'"), "( Subject LIKE '1' ) AND ( Status = '__Active__' )", "correct parsing"; is $parser->QueryToSQL("foo bar"), "( Subject LIKE 'foo' AND Subject LIKE 'bar' ) AND ( Status = '__Active__' )", "correct parsing"; is $parser->QueryToSQL("'foo bar'"), "( Subject LIKE 'foo bar' ) AND ( Status = '__Active__' )", "correct parsing"; is $parser->QueryToSQL("'foo \\' bar'"), "( Subject LIKE 'foo \\' bar' ) AND ( Status = '__Active__' )", "correct parsing"; is $parser->QueryToSQL('"foo \' bar"'), "( Subject LIKE 'foo \\' bar' ) AND ( Status = '__Active__' )", "correct parsing"; is $parser->QueryToSQL('"\f\o\o"'), "( Subject LIKE '\\\\f\\\\o\\\\o' ) AND ( Status = '__Active__' )", "correct parsing"; is $parser->QueryToSQL("General"), "( Queue = 'General' ) AND ( Status = '__Active__' )", "correct parsing"; is $parser->QueryToSQL("'Two Words'"), "( Subject LIKE 'Two Words' ) AND ( Status = '__Active__' )", "correct parsing"; is $parser->QueryToSQL("queue:'Two Words'"), "( Queue = 'Two Words' ) AND ( Status = '__Active__' )", "correct parsing"; is $parser->QueryToSQL("subject:'Two Words'"), "( Status = '__Active__' ) AND ( Subject LIKE 'Two Words' )", "correct parsing"; is $parser->QueryToSQL("me"), "( Owner.id = '__CurrentUser__' ) AND ( Status = '__Active__' )", "correct parsing"; is $parser->QueryToSQL("'me'"), "( Subject LIKE 'me' ) AND ( Status = '__Active__' )", "correct parsing"; is $parser->QueryToSQL("owner:me"), "( Owner.id = '__CurrentUser__' ) AND ( Status = '__Active__' )", "correct parsing"; is $parser->QueryToSQL("owner:'me'"), "( Owner = 'me' ) AND ( Status = '__Active__' )", "correct parsing"; is $parser->QueryToSQL('owner:root@localhost'), "( Owner.EmailAddress = 'root\@localhost' ) AND ( Status = '__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 = '__Active__' )", "correct parsing"; is $parser->QueryToSQL("status:active"), "( Status = '__Active__' )", "Explicit active search"; is $parser->QueryToSQL("status:'active'"), "( Status = 'active' )", "Quoting active makes it the actual word"; is $parser->QueryToSQL("inactive me"), "( Owner.id = '__CurrentUser__' ) AND ( Status = '__Inactive__' )", "correct parsing"; is $parser->QueryToSQL("cf.Foo:bar"), "( 'CF.{Foo}' LIKE 'bar' ) AND ( Status = '__Active__' )", "correct parsing of CFs"; is $parser->QueryToSQL(q{cf."don't foo?":'bar n\\' baz'}), qq/( 'CF.{don\\'t foo?}' LIKE 'bar n\\' baz' ) AND ( Status = '__Active__' )/, "correct parsing of CFs with quotes"; } my $ticket_found_1 = RT::Ticket->new($RT::SystemUser); my $ticket_found_2 = RT::Ticket->new($RT::SystemUser); my $ticket_not_found = RT::Ticket->new($RT::SystemUser); $ticket_found_1->Create( Subject => 'base ticket 1'.$$, Queue => 'general', Owner => 'root', Requestor => 'customsearch@localhost', Content => 'this is base ticket 1', ); ok( $ticket_found_1->id, 'created ticket for custom search'); $ticket_found_2->Create( Subject => 'base ticket 2'.$$, Queue => 'general', Owner => 'root', Requestor => 'customsearch@localhost', Content => 'this is base ticket 2', ); ok( $ticket_found_2->id, 'created ticket for custom search'); $ticket_not_found = RT::Ticket->new($RT::SystemUser); $ticket_not_found->Create( Subject => 'not found subject' . $$, Queue => 'other', Owner => 'nobody', Requestor => 'notfound@localhost', Content => 'this is not found content', ); ok( $ticket_not_found->id, 'created ticket for custom search'); ok($m->login, 'logged in'); my @queries = ( 'base ticket', 'root', 'customsearch@localhost', 'requestor:customsearch', 'subject:base', 'subject:"base ticket"', 'queue:general', 'owner:root', ); for my $q (@queries) { $m->form_with_fields('q'); $m->field( q => $q ); $m->submit; $m->content_contains( 'base ticket 1', 'base ticket 1 is found' ); $m->content_contains( 'base ticket 2', 'base ticket 2 is found' ); $m->content_lacks( 'not found subject', 'not found ticket is not found' ); } $ticket_not_found->SetStatus('open'); is( $ticket_not_found->Status, 'open', 'status of not found ticket is open' ); @queries = qw/new status:new/; for my $q (@queries) { $m->form_with_fields('q'); $m->field( q => $q ); $m->submit; $m->content_contains( 'base ticket 1', 'base ticket 1 is found' ); $m->content_contains( 'base ticket 2', 'base ticket 2 is found' ); $m->content_lacks( 'not found subject', 'not found ticket is not found' ); } @queries = ( 'fulltext:"base ticket 1"', "'base ticket 1'" ); for my $q (@queries) { $m->form_with_fields('q'); $m->field( q => $q ); $m->submit; $m->content_contains( 'base ticket 1', 'base ticket 1 is found' ); $m->content_lacks( 'base ticket 2', 'base ticket 2 is not found' ); $m->content_lacks( 'not found subject', 'not found ticket is not found' ); } # now let's test with ' or " for my $quote ( q{'}, q{"} ) { my $user = RT::User->new($RT::SystemUser); is( ref($user), 'RT::User' ); my ( $id, $msg ) = $user->Create( Name => qq!foo${quote}bar!, EmailAddress => qq!foo${quote}bar$$\@example.com !, Privileged => 1, ); ok ($id, "Creating user - " . $msg ); my ( $grantid, $grantmsg ) = $user->PrincipalObj->GrantRight( Right => 'OwnTicket' ); ok( $grantid, $grantmsg ); my $ticket_quote = RT::Ticket->new($RT::SystemUser); $ticket_quote->Create( Subject => qq!base${quote}ticket $$!, Queue => 'general', Owner => $user->Name, ( $quote eq q{'} ? (Requestor => qq!custom${quote}search\@localhost!) : () ), Content => qq!this is base${quote}ticket with quote inside!, ); ok( $ticket_quote->id, 'created ticket with quote for custom search' ); @queries = ( qq!fulltext:base${quote}ticket!, "base${quote}ticket", "owner:foo${quote}bar", "foo${quote}bar", # email doesn't allow " character $quote eq q{'} ? ( "requestor:custom${quote}search\@localhost", "custom${quote}search\@localhost", ) : (), ); for my $q (@queries) { $m->form_with_fields('q'); $m->field( q => $q ); $m->submit; my $escape_quote = $quote; RT::Interface::Web::EscapeHTML(\$escape_quote); $m->content_contains( "base${escape_quote}ticket", "base${quote}ticket is found" ); } } # Create a CF { my $cf = RT::CustomField->new(RT->SystemUser); ok( $cf->Create(Name => 'Foo', Type => 'Freeform', MaxValues => '1', Queue => 0) ); ok $cf->Id; $ticket_found_1->AddCustomFieldValue( Field => 'Foo', Value => 'bar' ); $ticket_found_2->AddCustomFieldValue( Field => 'Foo', Value => 'bar' ); $ticket_not_found->AddCustomFieldValue( Field => 'Foo', Value => 'baz' ); is( $ticket_found_1->FirstCustomFieldValue('Foo'), 'bar', 'cf value is ok' ); is( $ticket_found_2->FirstCustomFieldValue('Foo'), 'bar', 'cf value is ok' ); is( $ticket_not_found->FirstCustomFieldValue('Foo'), 'baz', 'cf value is ok' ); @queries = qw/cf.Foo:bar/; for my $q (@queries) { $m->form_with_fields('q'); $m->field( q => $q ); $m->submit; $m->content_contains( 'base ticket 1', 'base ticket 1 is found' ); $m->content_contains( 'base ticket 2', 'base ticket 2 is found' ); $m->content_lacks( 'not found subject', 'not found ticket is not found' ); } } done_testing; rt-5.0.1/t/web/user_update.t000644 000765 000024 00000003001 14005011336 016520 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ( $url, $m ) = RT::Test->started_ok; ok( $m->login(), 'logged in' ); $m->follow_link_ok({text => 'About me'}); $m->submit_form_ok({ with_fields => { Lang => 'ja'} }, "Change to Japanese"); $m->text_contains(Encode::decode("UTF-8","Langã¯ã€Œ(値ãªã—)ã€ã‹ã‚‰ã€Œ'ja'ã€ã«å¤‰æ›´ã•れã¾ã—ãŸ")); $m->text_contains(Encode::decode("UTF-8","実å"), "Page content is japanese"); # we only changed one field, and it wasn't the default, so this feedback is # spurious and annoying $m->content_lacks("That is already the current value"); # change back to English $m->submit_form_ok({ with_fields => { Lang => 'en_us'} }, "Change back to english"); $m->text_contains("Lang changed from 'ja' to 'en_us'"); $m->text_contains("Real Name", "Page content is english"); # Check for a lack of spurious updates $m->content_lacks("That is already the current value"); # Ensure that we can change the language back to the default. $m->submit_form_ok({ with_fields => { Lang => 'ja'} }, "Back briefly to Japanese"); $m->text_contains(Encode::decode("UTF-8","Langã¯ã€Œ'en_us'ã€ã‹ã‚‰ã€Œ'ja'ã€ã«å¤‰æ›´ã•れã¾ã—ãŸ")); $m->text_contains(Encode::decode("UTF-8","実å"), "Page content is japanese"); $m->submit_form_ok({ with_fields => { Lang => ''} }, "And set to the default"); $m->text_contains("Lang changed from 'ja' to (no value)"); $m->text_contains("Real Name", "Page content is english"); done_testing; rt-5.0.1/t/web/mobile.t000644 000765 000024 00000017364 14005011336 015470 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 170; RT->Config->Set(ShowMobileSite => 1); my ( $url, $m ) = RT::Test->started_ok; my $root = RT::Test->load_or_create_user( Name => 'root' ); diag "create another queue"; my $test_queue = RT::Queue->new( $RT::SystemUser ); ok( $test_queue->Create( Name => 'foo' ) ); diag "create cf cfbar"; my $cfbar = RT::CustomField->new( $RT::SystemUser ); ok( $cfbar->Create( Name => 'cfbar', Type => 'Freeform', LookupType => 'RT::Queue-RT::Ticket' ) ); $cfbar->AddToObject( $test_queue ); diag "create some tickets to link"; # yep, create 3 tickets for DependsOn my @tickets = map { { Subject => "link of $_" } } qw/DependsOn DependsOn DependsOn DependedOnBy HasMember HasMember MemberOf RefersTo RefersTo ReferredToBy/; RT::Test->create_tickets( { Status => 'resolved' }, @tickets ); diag "test different mobile agents"; my @agents = ( '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' ); for my $agent (@agents) { $m->agent($agent); $m->get_ok($url); $m->content_contains( 'Not using a mobile browser', "mobile login page for agent $agent" ); } $m->submit_form( fields => { user => 'root', pass => 'password' } ); is( $m->uri, $url . '/m/', 'logged in via mobile ui' ); ok( $m->find_link( text => 'Home' ), 'has homepage link, so really logged in' ); diag "create some tickets"; $m->follow_link_ok( { text => 'New ticket' } ); like( $m->uri, qr'/m/ticket/select_create_queue', 'queue select page' ); $m->follow_link_ok( { text => 'General' } ); like( $m->uri, qr'/m/ticket/create', 'ticket create page' ); $m->submit_form( fields => { Subject => 'ticket1', Content => 'content 1', Status => 'open', Cc => 'cc@example.com', AdminCc => 'admincc@example.com', InitialPriority => 13, FinalPriority => 93, TimeEstimated => 2, 'TimeEstimated-TimeUnits' => 'hours', TimeWorked => 30, TimeLeft => 60, Starts => '2011-01-11 11:11:11', Due => '2011-02-12 12:12:12', 'new-DependsOn' => '1 2 3', 'DependsOn-new' => '4', 'new-MemberOf' => '5 6', 'MemberOf-new' => '7', 'new-RefersTo' => '8 9', 'RefersTo-new' => '10', } ); like( $m->uri, qr'/m/ticket/show', 'ticket show page' ); $m->content_contains( 'ticket1', 'subject' ); $m->content_contains( 'open', 'status' ); $m->content_contains( 'cc@example.com', 'cc' ); $m->content_contains( 'admincc@example.com', 'admincc' ); $m->text_contains( 'Low/Medium', 'priority' ); $m->content_contains( '2 hour', 'time estimates' ); $m->content_contains( '30 min', 'time worked' ); $m->content_contains( '60 min', 'time left' ); $m->content_contains( 'Tue Jan 11 11:11:11', 'starts' ); $m->content_contains( 'Sat Feb 12 12:12:12', 'due' ); $m->content_like( qr/(link of DependsOn).*\1.*\1/s, 'depends on' ); $m->content_contains( 'link of DependedOnBy', 'depended on by' ); $m->content_like( qr/(link of HasMember).*\1/s, 'has member' ); $m->content_contains( 'link of MemberOf', 'member of' ); $m->content_like( qr/(link of RefersTo).*\1/s, 'refers to' ); $m->content_contains( 'link of ReferredToBy', 'referred to by' ); diag "test ticket reply"; $m->follow_link_ok( { text => 'Reply' } ); like( $m->uri, qr'/m/ticket/reply', 'ticket reply page' ); $m->submit_form( fields => { UpdateContent => 'reply 1', UpdateTimeWorked => '30', UpdateStatus => 'resolved', UpdateType => 'response', }, button => 'SubmitTicket', ); like( $m->uri, qr'/m/ticket/show', 'back to ticket show page' ); $m->content_contains( '1 hour', 'time worked' ); $m->content_contains( 'resolved', 'status' ); $m->follow_link_ok( { text => 'Reply' } ); like( $m->uri, qr'/m/ticket/reply', 'ticket reply page' ); $m->submit_form( fields => { UpdateContent => 'reply 2', UpdateSubject => 'ticket1', UpdateStatus => 'open', UpdateType => 'private', }, button => 'SubmitTicket', ); $m->no_warnings_ok; $m->content_contains( 'ticket1', 'subject' ); $m->content_contains( 'open', 'status' ); like( $m->uri, qr'/m/ticket/show', 'back to ticket show page' ); diag "test ticket history"; $m->follow_link_ok( { text => 'History' } ); like( $m->uri, qr'/m/ticket/history', 'ticket history page' ); $m->content_contains( 'content 1', 'has main content' ); $m->content_contains( 'reply 1', 'has replied content' ); $m->content_contains( 'reply 2', 'has replied content' ); diag "create another ticket in queue foo"; $m->follow_link_ok( { text => 'Home' } ); is( $m->uri, "$url/m/", 'main mobile page' ); $m->follow_link_ok( { text => 'New ticket' } ); like( $m->uri, qr'/m/ticket/select_create_queue', 'queue select page' ); $m->follow_link_ok( { text => 'foo' } ); like( $m->uri, qr'/m/ticket/create', 'ticket create page' ); $m->content_contains( 'cfbar', 'has cf name' ); $m->content_contains( 'Object-RT::Ticket--CustomField-' . $cfbar->id . '-Value', 'has cf input name' ); $m->submit_form( fields => { Subject => 'ticket2', Content => 'content 2', Owner => $root->id, 'Object-RT::Ticket--CustomField-' . $cfbar->id . '-Value' => 'cfvalue', } ); $m->no_warnings_ok; like( $m->uri, qr'/m/ticket/show', 'ticket show page' ); $m->content_contains( 'cfbar', 'has cf name' ); $m->content_contains( 'cfvalue', 'has cf value' ); $m->follow_link_ok( { text => 'Home' } ); is( $m->uri, "$url/m/", 'main mobile page' ); diag "test unowned tickets link"; $m->follow_link_ok( { text => 'Unowned tickets' } ); $m->content_contains( 'Found 1 ticket', 'found 1 ticket' ); $m->content_contains( 'ticket1', 'has ticket1' ); $m->content_lacks( 'ticket2', 'no ticket2' ); $m->back; diag "test tickets I own link"; $m->follow_link_ok( { text => 'Tickets I own' } ); $m->content_contains( 'Found 1 ticket', 'found 1 ticket' ); $m->content_lacks( 'ticket1', 'no ticket1' ); ok( $m->find_link( text_regex => qr/ticket2/ ), 'has ticket2 link' ); $m->back; diag "test all tickets link"; $m->follow_link_ok( { text => 'All tickets' } ); $m->content_contains( 'Found 12 tickets', 'found 12 tickets' ); ok( $m->find_link( text_regex => qr/ticket1/ ), 'has ticket1 link' ); ok( $m->find_link( text_regex => qr/ticket2/ ), 'has ticket2 link' ); $m->back; diag "test bookmarked tickets link"; my $ticket = RT::Ticket->new(RT::CurrentUser->new('root')); $ticket->Load(11); $root->ToggleBookmark($ticket); $m->follow_link_ok( { text => 'Bookmarked tickets' } ); $m->content_contains( 'Found 1 ticket', 'found 1 ticket' ); ok( $m->find_link( text_regex => qr/ticket1/ ), 'has ticket1 link' ); $m->content_lacks( 'ticket2', 'no ticket2' ); $m->back; diag "test tickets search"; $m->submit_form( fields => { q => 'ticket2' } ); $m->content_contains( 'Found 1 ticket', 'found 1 ticket' ); $m->content_lacks( 'ticket1', 'no ticket1' ); ok( $m->find_link( text_regex => qr/ticket2/ ), 'has ticket2 link' ); $m->back; diag "test logout link"; $m->follow_link_ok( { text => 'Logout' } ); is( $m->uri, "$url/m/", 'still in mobile' ); $m->submit_form( fields => { user => 'root', pass => 'password' } ); diag "test notmobile link"; $m->follow_link_ok( { text => 'Home' } ); $m->follow_link_ok( { text => 'Not using a mobile browser?' } ); is( $m->uri, $url . '/', 'got full ui' ); rt-5.0.1/t/web/remote_user.t000644 000765 000024 00000013557 14005011336 016552 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test plan => 'no_plan'; sub stop_server { my $mech = shift; # Ensure we're logged in for the final warnings check $$mech->auth("root"); # Force the warnings check before we stop the server undef $$mech; RT::Test->stop_server; } diag "Continuous + Fallback"; { RT->Config->Set( DevelMode => 0 ); RT->Config->Set( WebRemoteUserAuth => 1 ); RT->Config->Set( WebRemoteUserAuthContinuous => 1 ); RT->Config->Set( WebFallbackToRTLogin => 1 ); RT->Config->Set( WebRemoteUserAutocreate => 0 ); my ( $url, $m ) = RT::Test->started_ok( basic_auth => 'anon' ); diag "Internal auth"; { # Empty REMOTE_USER $m->auth(""); # First request gets the login form $m->get_ok($url, "No basic auth is OK"); $m->content_like(qr/Login/, "Login form"); # Log in using RT's form $m->submit_form_ok({ with_fields => { user => 'root', pass => 'password', }, }, "Submitted login form"); ok $m->logged_in_as("root"), "Logged in as root"; # Still logged in on another request without REMOTE_USER $m->follow_link_ok({ text => 'My Tickets' }); ok $m->logged_in_as("root"), "Logged in as root"; ok $m->logout, "Logged out"; # We're definitely logged out? $m->get_ok($url); $m->content_like(qr/Login/, "Login form"); } diag "External auth"; { # REMOTE_USER of root $m->auth("root"); # Automatically logged in as root without Login page $m->get_ok($url); ok $m->logged_in_as("root"), "Logged in as root"; # Still logged in on another request $m->follow_link_ok({ text => 'My Tickets' }); ok $m->logged_in_as("root"), "Still logged in as root"; # Drop credentials and... $m->auth(""); # ...see if RT notices $m->get($url); is $m->status, 403, "403 Forbidden from RT"; # Next request gets us the login form $m->get_ok($url); $m->content_like(qr/Login/, "Login form"); } diag "External auth with invalid user, login internally"; { # REMOTE_USER of invalid $m->auth("invalid"); # Login internally via the login link $m->get("$url/Search/Build.html"); is $m->status, 403, "403 Forbidden"; $m->follow_link_ok({ url_regex => qr'NoAuth/Login\.html' }, "follow logout link"); $m->content_like(qr/Login/, "Login form"); # Log in using RT's form $m->submit_form_ok({ with_fields => { user => 'root', pass => 'password', }, }, "Submitted login form"); ok $m->logged_in_as("root"), "Logged in as root"; like $m->uri, qr'Search/Build\.html', "at our originally requested page"; # Still logged in on another request $m->follow_link_ok({ text => 'Tools' }); ok $m->logged_in_as("root"), "Logged in as root"; ok $m->logout, "Logged out"; $m->next_warning_like(qr/Couldn't find internal user for 'invalid'/, "found warning for first request"); $m->next_warning_like(qr/Couldn't find internal user for 'invalid'/, "found warning for second request"); } stop_server(\$m); } diag "Fallback OFF"; { RT->Config->Set( DevelMode => 0 ); RT->Config->Set( WebRemoteUserAuth => 1 ); RT->Config->Set( WebRemoteUserContinuous => 0 ); RT->Config->Set( WebFallbackToRTLogin => 0 ); RT->Config->Set( WebRemoteUserAutocreate => 0 ); my ( $url, $m ) = RT::Test->started_ok( basic_auth => 'anon' ); diag "No remote user"; { $m->auth(""); $m->get($url); is $m->status, 403, "Forbidden"; } stop_server(\$m); } diag "WebRemoteUserAutocreate"; { RT->Config->Set( DevelMode => 0 ); RT->Config->Set( WebRemoteUserAuth => 1 ); RT->Config->Set( WebRemoteUserContinuous => 1 ); RT->Config->Set( WebFallbackToRTLogin => 0 ); RT->Config->Set( WebRemoteUserAutocreate => 1 ); RT->Config->Set( UserAutocreateDefaultsOnLogin => { Organization => "BPS" } ); my ( $url, $m ) = RT::Test->started_ok( basic_auth => 'anon' ); diag "New user"; { $m->auth("anewuser"); $m->get_ok($url); ok $m->logged_in_as("anewuser"), "Logged in as anewuser"; my $user = RT::User->new( RT->SystemUser ); $user->Load("anewuser"); ok $user->id, "Found newly created user"; is $user->Organization, "BPS", "Found Organization from UserAutocreateDefaultsOnLogin hash"; ok $user->Privileged, "Privileged by default"; } stop_server(\$m); RT->Config->Set( UserAutocreateDefaultsOnLogin => { Privileged => 0, EmailAddress => 'foo@example.com', }, ); ( $url, $m ) = RT::Test->started_ok( basic_auth => 'anon' ); diag "Create unprivileged users"; { $m->auth("unpriv"); $m->get_ok($url); ok $m->logged_in_as("unpriv"), "Logged in as an unpriv user"; like $m->uri->path, RT->Config->Get('SelfServiceRegex'), "SelfService URL"; my $user = RT::User->new( RT->SystemUser ); $user->Load("unpriv"); ok $user->id, "Found newly created user"; ok !$user->Privileged, "Unprivileged per config"; is $user->EmailAddress, 'foo@example.com', "Email address per config"; } diag "User creation failure"; { $m->auth("conflicting"); $m->get($url); is $m->status, 403, "Forbidden"; $m->next_warning_like(qr/Couldn't auto-create user 'conflicting' when attempting WebRemoteUser: Email address in use/, 'found failed auth warning'); my $user = RT::User->new( RT->SystemUser ); $user->Load("conflicting"); ok !$user->id, "Couldn't find conflicting user"; } stop_server(\$m); } rt-5.0.1/t/web/command_line_ticket_content_type.t000644 000765 000024 00000002727 14005011336 023001 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::Expect; use RT::Test tests => 22, actual_server => 1; my ( $baseurl, $m ) = RT::Test->started_ok; $m->login(); my $rt_tool_path = "$RT::BinPath/rt"; $ENV{'RTUSER'} = 'root'; $ENV{'RTPASSWD'} = 'password'; $ENV{'RTSERVER'} = RT->Config->Get('WebBaseURL'); $ENV{'RTCONFIG'} = '/dev/null'; # create a ticket expect_run( command => "$rt_tool_path shell", prompt => 'rt> ', quit => 'quit', ); for my $content_type ( 'text/plain', 'text/html' ) { expect_send( qq{create -t ticket -ct $content_type set subject='new ticket' text=foo}, "creating a ticket with content-type $content_type" ); expect_like( qr/Ticket \d+ created/, "created the ticket" ); expect_handle->before() =~ /Ticket (\d+) created/; my $id = $1; ok( $id, "got ticket $id" ); $m->goto_ticket($id); $m->follow_link_ok( { url_regex => qr/Attachment\/WithHeaders\/\d+/, n => 1 } ); $m->content_contains( "Content-Type: $content_type", 'content-type' ); expect_send( qq{comment ticket/$id -ct $content_type -m bar}, "commenting a ticket with content-type $content_type" ); expect_like( qr/Comments added/, "commented the ticket" ); $m->goto_ticket($id); $m->follow_link_ok( { url_regex => qr/Attachment\/WithHeaders\/\d+/, n => 2 } ); $m->content_contains( "Content-Type: $content_type", 'content-type' ); } expect_quit(); 1; # needed to avoid a weird exit value from expect_quit rt-5.0.1/t/web/redirect-after-login.t000644 000765 000024 00000022174 14005011336 020222 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 122; my ($baseurl, $agent) = RT::Test->started_ok; my $url = $agent->rt_base_url; diag $url if $ENV{TEST_VERBOSE}; # test a login from the main page { $agent->get_ok($url); is($agent->{'status'}, 200, "Loaded a page"); is($agent->uri, $url, "didn't redirect to /NoAuth/Login.html for base URL"); ok($agent->current_form->find_input('user')); ok($agent->current_form->find_input('pass')); like($agent->current_form->action, qr{/NoAuth/Login\.html$}, "login form action is correct"); ok($agent->content =~ /username:/i); $agent->field( 'user' => 'root' ); $agent->field( 'pass' => 'password' ); # the field isn't named, so we have to click link 0 $agent->click(0); is( $agent->status, 200, "Fetched the page ok"); ok( $agent->content =~ /Logout/i, "Found a logout link"); is( $agent->uri, $url, "right URL" ); like( $agent->{redirected_uri}, qr{/NoAuth/Login\.html$}, "We redirected from login"); $agent->logout(); } # test a bogus login from the main page { $agent->get_ok($url); is($agent->{'status'}, 200, "Loaded a page"); is($agent->uri, $url, "didn't redirect to /NoAuth/Login.html for base URL"); ok($agent->current_form->find_input('user')); ok($agent->current_form->find_input('pass')); like($agent->current_form->action, qr{/NoAuth/Login\.html$}, "login form action is correct"); ok($agent->content =~ /username:/i); $agent->field( 'user' => 'root' ); $agent->field( 'pass' => 'wrongpass' ); # the field isn't named, so we have to click link 0 $agent->click(0); is( $agent->status, 200, "Fetched the page ok"); ok( $agent->content =~ /Your username or password is incorrect/i, "Found the error message"); like( $agent->uri, qr{/NoAuth/Login\.html$}, "now on /NoAuth/Login.html" ); $agent->warning_like(qr/FAILED LOGIN for root/, "got failed login warning"); $agent->logout(); } # test a login from a non-front page, both with a double leading slash and without for my $path (qw(Prefs/Other.html /Prefs/Other.html)) { my $requested = $url.$path; $agent->get_ok($requested); is($agent->status, 200, "Loaded a page"); like($agent->uri, qr'/NoAuth/Login\.html\?next=[a-z0-9]{32}', "on login page, with next page hash"); is($agent->{redirected_uri}, $requested, "redirected from our requested page"); ok($agent->current_form->find_input('user')); ok($agent->current_form->find_input('pass')); ok($agent->current_form->find_input('next')); like($agent->value('next'), qr/^[a-z0-9]{32}$/i, "next page argument is a hash"); like($agent->current_form->action, qr{/NoAuth/Login\.html$}, "login form action is correct"); ok($agent->content =~ /username:/i); $agent->field( 'user' => 'root' ); $agent->field( 'pass' => 'password' ); # the field isn't named, so we have to click link 0 $agent->click(0); is( $agent->status, 200, "Fetched the page ok"); ok( $agent->content =~ /Logout/i, "Found a logout link"); if ($path =~ m{/}) { (my $collapsed = $path) =~ s{^/}{}; is( $agent->uri, $url.$collapsed, "right URL, with leading slashes in path collapsed" ); } else { is( $agent->uri, $requested, "right URL" ); } like( $agent->{redirected_uri}, qr{/NoAuth/Login\.html}, "We redirected from login"); $agent->logout(); } # test a bogus login from a non-front page { my $requested = $url.'Prefs/Other.html'; $agent->get_ok($requested); is($agent->status, 200, "Loaded a page"); like($agent->uri, qr'/NoAuth/Login\.html\?next=[a-z0-9]{32}', "on login page, with next page hash"); is($agent->{redirected_uri}, $requested, "redirected from our requested page"); ok($agent->current_form->find_input('user')); ok($agent->current_form->find_input('pass')); ok($agent->current_form->find_input('next')); like($agent->value('next'), qr/^[a-z0-9]{32}$/i, "next page argument is a hash"); like($agent->current_form->action, qr{/NoAuth/Login\.html$}, "login form action is correct"); ok($agent->content =~ /username:/i); $agent->field( 'user' => 'root' ); $agent->field( 'pass' => 'wrongpass' ); # the field isn't named, so we have to click link 0 $agent->click(0); is( $agent->status, 200, "Fetched the page ok"); ok( $agent->content =~ /Your username or password is incorrect/i, "Found the error message"); like( $agent->uri, qr{/NoAuth/Login\.html$}, "still on /NoAuth/Login.html" ); $agent->warning_like(qr/FAILED LOGIN for root/, "got failed login warning"); # try to login again ok($agent->current_form->find_input('user')); ok($agent->current_form->find_input('pass')); ok($agent->current_form->find_input('next')); like($agent->value('next'), qr/^[a-z0-9]{32}$/i, "next page argument is a hash"); like($agent->current_form->action, qr{/NoAuth/Login\.html$}, "login form action is correct"); ok($agent->content =~ /username:/i); $agent->field( 'user' => 'root' ); $agent->field( 'pass' => 'password' ); # the field isn't named, so we have to click link 0 $agent->click(0); is( $agent->status, 200, "Fetched the page ok"); # check out where we got to is( $agent->uri, $requested, "right URL" ); like( $agent->{redirected_uri}, qr{/NoAuth/Login\.html}, "We redirected from login"); $agent->logout(); } # test a login from the main page with query params { my $requested = $url."?user=root;pass=password"; $agent->get_ok($requested); is($agent->{'status'}, 200, "Loaded a page"); is($agent->uri, $requested, "didn't redirect to /NoAuth/Login.html for base URL"); ok($agent->content =~ /Logout/i, "Found a logout link - we're logged in"); $agent->logout(); } # test a bogus login from the main page with query params { my $requested = $url."?user=root;pass=wrongpass"; $agent->get_ok($requested); is($agent->{'status'}, 200, "Loaded a page"); is($agent->uri, $requested, "didn't redirect to /NoAuth/Login.html for base URL"); ok($agent->content =~ /Your username or password is incorrect/i, "Found the error message"); ok($agent->current_form->find_input('user')); ok($agent->current_form->find_input('pass')); like($agent->current_form->action, qr{/NoAuth/Login\.html$}, "login form action is correct"); $agent->warning_like(qr/FAILED LOGIN for root/, "got failed login warning"); } # test a bogus login from a non-front page with query params { my $requested = $url."Prefs/Other.html?user=root;pass=wrongpass"; $agent->get_ok($requested); is($agent->status, 200, "Loaded a page"); like($agent->uri, qr'/NoAuth/Login\.html\?next=[a-z0-9]{32}', "on login page, with next page hash"); is($agent->{redirected_uri}, $requested, "redirected from our requested page"); ok( $agent->content =~ /Your username or password is incorrect/i, "Found the error message"); ok($agent->current_form->find_input('user')); ok($agent->current_form->find_input('pass')); ok($agent->current_form->find_input('next')); like($agent->value('next'), qr/^[a-z0-9]{32}$/i, "next page argument is a hash"); like($agent->current_form->action, qr{/NoAuth/Login\.html$}, "login form action is correct"); $agent->warning_like(qr/FAILED LOGIN for root/, "got failed login warning"); # Try to login again ok($agent->content =~ /username:/i); $agent->field( 'user' => 'root' ); $agent->field( 'pass' => 'password' ); # the field isn't named, so we have to click link 0 $agent->click(0); is( $agent->status, 200, "Fetched the page ok"); # check out where we got to is( $agent->uri, $requested, "right URL" ); like( $agent->{redirected_uri}, qr{/NoAuth/Login\.html}, "We redirected from login"); $agent->logout(); } # test REST login response { $agent = RT::Test::Web->new; my $requested = $url."REST/1.0/?user=root;pass=password"; $agent->get($requested); is($agent->status, 200, "Loaded a page"); is($agent->uri, $requested, "didn't redirect to /NoAuth/Login.html for REST"); $agent->get_ok($url."REST/1.0"); } # test REST login response for wrong pass { $agent = RT::Test::Web->new; my $requested = $url."REST/1.0/?user=root;pass=passwrong"; $agent->get_ok($requested); is($agent->status, 200, "Loaded a page"); is($agent->uri, $requested, "didn't redirect to /NoAuth/Login.html for REST"); like($agent->content, qr/401 Credentials required/i, "got error status"); like($agent->content, qr/Your username or password is incorrect/, "got error message"); $agent->warning_like(qr/FAILED LOGIN for root/, "got failed login warning"); } # test REST login response for no creds { $agent = RT::Test::Web->new; my $requested = $url."REST/1.0/"; $agent->get_ok($requested); is($agent->status, 200, "Loaded a page"); is($agent->uri, $requested, "didn't redirect to /NoAuth/Login.html for REST"); like($agent->content, qr/401 Credentials required/i, "got error status"); unlike($agent->content, qr/Your username or password is incorrect/, "didn't get any error message"); } # XXX TODO: we should also be testing WebRemoteUserAuth here, but we don't have # the framework for dealing with that 1; rt-5.0.1/t/web/search_simple.t000644 000765 000024 00000004440 14005011336 017026 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 30; my ( $baseurl, $m ) = RT::Test->started_ok; RT::Test->create_tickets( { Queue => 'General' }, { Subject => 'ticket foo' }, { Subject => 'ticket bar' }, ); ok( $m->login, 'logged in' ); $m->get_ok('/Search/Simple.html'); $m->content_lacks( 'Show Results', 'no page menu' ); $m->get_ok('/Search/Simple.html?q=ticket foo'); $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 rt-5.0.1/t/web/plugin-overlays.t000644 000765 000024 00000001401 14005011336 017342 0ustar00sunnavystaff000000 000000 use strict; use warnings; BEGIN { use Test::More; plan skip_all => "Testing the rt-server init sequence in isolation requires Apache" unless ($ENV{RT_TEST_WEB_HANDLER} || '') =~ /^apache/; } use JSON qw(from_json); use RT::Test tests => undef, plugins => ["Overlays"]; my ($base, $m) = RT::Test->started_ok; # Check that the overlay was actually loaded $m->get_ok("$base/overlay_loaded"); is $m->content, "yes", "Plugin's RT/User_Local.pm was loaded"; # Check accessible is correct and doesn't need to be rebuilt from overlay $m->get_ok("$base/user_accessible"); ok $m->content, "Received some content"; my $info = from_json($m->content) || {}; ok $info->{Comments}{public}, "User.Comments is marked public via overlay"; done_testing; rt-5.0.1/t/web/dryrun.t000644 000765 000024 00000003707 14005011336 015540 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ($baseurl, $agent) = RT::Test->started_ok; my $url = $agent->rt_base_url; diag "Running server at $url"; $agent->login('root' => 'password'); is( $agent->status, 200, "Fetched the page ok"); $agent->content_contains("Logout", "Found a logout link"); my ($ok, $msg); my $ticket = RT::Ticket->new(RT->SystemUser); my ($tv,$ttv,$tm) = $ticket->Create( Queue => 'General', Subject => "An Interesting Title", ); ok($tv, "Ticket created"); my $cf = RT::CustomField->new(RT->SystemUser); ok($cf, "RT::CustomField object initialized"); ($ok, $msg) = $cf->Create( Name => 'My Custom Field', Queue => '0', Description => 'A testing custom field', Type => 'SelectSingle' ); ok($ok, 'Global custom field created'); my $cf_id = $cf->Id; #($ok, $msg) = $ticket->Load($tv); #ok($ok, 'created a scrip') or diag "error: $msg"; $ticket->AddCustomFieldValue(Field => $cf->Id, Value => '1'); diag "Create test scrip"; my $scrip = RT::Scrip->new(RT->SystemUser); ($ok, $msg) = $scrip->Create( Queue => 'General', ScripAction => 'User Defined', ScripCondition => 'User Defined', Template => 'blank', CustomIsApplicableCode => "return 1;", CustomPrepareCode => "return 1;", CustomCommitCode => "warn 'Commit should not run for PreviewScrips'; return 1;", ); ok($ok, 'Scrip created'); $agent->get_ok( $url . "Ticket/Update.html?Action=Respond;id=$tv" ); diag "Confirm commit does not run for Preview Scrips"; $agent->post_ok( $url . "Helpers/PreviewScrips", { id => $tv, "Object-RT::Ticket-$tv-CustomField-$cf_id-Value" => 'Test Value', UpdateType => 'response', TxnRecipients => 'root@localhost', }, Content_Type => 'form-data' ); is( $agent->status, 200, "PreviewScrips returned 200"); done_testing(); rt-5.0.1/t/web/ticket_forward.t000644 000765 000024 00000025630 14005011336 017223 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use File::Spec; my $att_file = File::Spec->catfile( RT::Test->temp_directory, 'attachment' ); open my $att_fh, '>', $att_file or die $!; print $att_fh "this is an attachment"; close $att_fh; my $att_name = ( File::Spec->splitpath($att_file) )[-1]; my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in as root'; # Create a ticket with content and an attachment $m->get_ok( $baseurl . '/Ticket/Create.html?Queue=1' ); $m->submit_form( form_name => 'TicketCreate', fields => { Subject => 'test forward', Content => 'this is content', Attach => $att_file, }, button => 'SubmitTicket' ); $m->content_like( qr/Ticket \d+ created/i, 'created the ticket' ); RT::Test->clean_caught_mails; diag "Forward Ticket" if $ENV{TEST_VERBOSE}; { $m->follow_link_ok( { id => 'page-actions-forward' }, 'follow 1st Forward to forward ticket' ); $m->submit_form( form_name => 'ForwardMessage', fields => { To => '"Foo" , rt-too@example.com', Cc => 'rt-cc@example.com', Bcc => 'root', }, button => 'ForwardAndReturn' ); $m->content_contains( 'Forwarded Ticket to Foo <rt-foo@example.com>, <rt-too@example.com>, <rt-cc@example.com>, root (Enoch Root)', 'txn msg' ); my ($mail) = RT::Test->fetch_caught_mails; like( $mail, qr!Subject: test forward!, 'Subject field' ); like( $mail, qr!To: .*?rt-foo\@example.com!i, 'To field' ); like( $mail, qr!To: .*?rt-too\@example.com!i, 'To field' ); like( $mail, qr!Cc: rt-cc\@example.com!i, 'Cc field' ); like( $mail, qr!Bcc: root\@localhost!i, 'Bcc field' ); like( $mail, qr!This is a forward of ticket!, 'content' ); like( $mail, qr!this is an attachment!, 'att content' ); like( $mail, qr!$att_name!, 'att file name' ); } diag "Forward Transaction" if $ENV{TEST_VERBOSE}; { $m->follow_link_ok( { class => 'forward-link', n => 1 }, 'follow 2nd Forward' ); $m->submit_form( form_name => 'ForwardMessage', fields => { To => 'rt-to@example.com, rt-too@example.com', Cc => 'rt-cc@example.com', Bcc => 'root' }, button => 'ForwardAndReturn' ); $m->content_like( qr/Forwarded .*?Transaction #\d+.*? to <rt-to\@example\.com>, <rt-too\@example\.com>, <rt-cc\@example\.com>, root (Enoch Root)/, 'txn msg' ); my ($mail) = RT::Test->fetch_caught_mails; like( $mail, qr!Subject: test forward!, 'Subject field' ); like( $mail, qr!To: .*rt-to\@example.com!i, 'To field' ); like( $mail, qr!To: .*rt-too\@example.com!i, 'To field' ); like( $mail, qr!Cc: rt-cc\@example.com!i, 'Cc field' ); like( $mail, qr!Bcc: root\@localhost!i, 'Bcc field' ); like( $mail, qr!This is a forward of transaction!, 'content' ); like( $mail, qr!$att_name!, 'att file name' ); like( $mail, qr!this is an attachment!, 'att content' ); } diag "Forward Ticket without content" if $ENV{TEST_VERBOSE}; { my $ticket = RT::Test->create_ticket( Subject => 'test forward without content', Queue => 1, ); $m->get_ok( $baseurl . '/Ticket/Forward.html?id=' . $ticket->id ); $m->submit_form( form_name => 'ForwardMessage', fields => { To => 'rt-test@example.com', }, button => 'ForwardAndReturn' ); my ($mail) = RT::Test->fetch_caught_mails; like( $mail, qr/Subject: \[example\.com #\d\] Fwd: test forward without content/, 'Subject field' ); like( $mail, qr/To: rt-test\@example\.com/, 'To field' ); like( $mail, qr/This is a forward of ticket #\d/, 'content' ); } diag "Forward Transaction with attachments but empty content" if $ENV{TEST_VERBOSE}; { # Create a ticket without content but with a non-text/plain attachment $m->get_ok( $baseurl . '/Ticket/Create.html?Queue=1' ); $m->form_name('TicketCreate'); my $attach = $m->current_form->find_input('Attach'); $attach->filename('awesome.pátch'); $attach->headers('Content-Type' => 'text/x-diff'); $m->set_fields( Subject => 'test forward, empty content but attachments', Attach => $att_file, # from up top ); $m->click('AddMoreAttach'); $m->form_name('TicketCreate'); $attach = $m->current_form->find_input('Attach'); $attach->filename("bpslogo.png"); $attach->headers('Content-Type' => 'image/png'); $m->set_fields( Attach => RT::Test::get_relocatable_file('bpslogo.png', '..', 'data'), # an image! ); $m->click('SubmitTicket'); $m->content_like( qr/Ticket \d+ created/i, 'created the ticket' ); $m->content_like( qr/awesome.p\%C3\%A1tch/, 'uploaded patch file' ); $m->content_like( qr/text\/x-diff/, 'uploaded patch file content type' ); $m->content_like( qr/bpslogo\.png/, 'uploaded image file' ); $m->content_like( qr/image\/png/, 'uploaded image file content type' ); RT::Test->clean_caught_mails; $m->follow_link_ok( { class => 'forward-link', n => 1 }, 'follow 2nd Forward' ); $m->submit_form( form_name => 'ForwardMessage', fields => { To => 'rt-test@example.com', }, button => 'ForwardAndReturn' ); $m->content_like( qr/Forwarded .*?Transaction #\d+.*? to <rt-test\@example\.com>/, 'txn msg' ); my ($mail) = RT::Test->fetch_caught_mails; like( $mail, qr/Subject: test forward, empty content but attachments/, 'Subject field' ); like( $mail, qr/To: rt-test\@example.com/, 'To field' ); like( $mail, qr/This is a forward of transaction/, 'content' ); like( $mail, qr/filename\*\=\"UTF\-8\'\'awesome.p\%C3\%A1tch\"/, 'att file name' ); like( $mail, qr/this is an attachment/, 'att content' ); like( $mail, qr/text\/x-diff/, 'att content type' ); like( $mail, qr/bpslogo\.png/, 'att image file name' ); like( $mail, qr/image\/png/, 'att image content type' ); } diag "Forward Transaction with attachments but no 'content' part" if $ENV{TEST_VERBOSE}; { my $mime = MIME::Entity->build( From => '"Tést" ', Subject => 'attachments for everyone', Type => 'multipart/mixed', ); $mime->attach( Path => $att_file, Type => 'text/x-diff', Filename => 'awesome.patch', Disposition => 'attachment', ); $mime->attach( Path => RT::Test::get_relocatable_file('bpslogo.png', '..', 'data'), Type => 'image/png', Filename => 'bpslogo.png', Encoding => 'base64', Disposition => 'attachment', ); my $ticket = RT::Test->create_ticket( Queue => 1, Subject => 'test forward, attachments but no "content"', MIMEObj => $mime, ); $m->get_ok( $baseurl . '/Ticket/Display.html?id=' . $ticket->Id ); $m->content_like( qr/awesome\.patch/, 'uploaded patch file' ); $m->content_like( qr/text\/x-diff/, 'uploaded patch file content type' ); $m->content_like( qr/bpslogo\.png/, 'uploaded image file' ); $m->content_like( qr/image\/png/, 'uploaded image file content type' ); RT::Test->clean_caught_mails; # Forward txn $m->follow_link_ok( { class => 'forward-link', n => 1 }, 'follow 2nd Forward' ); $m->submit_form( form_name => 'ForwardMessage', fields => { To => 'rt-test@example.com', }, button => 'ForwardAndReturn' ); $m->content_like( qr/Forwarded .*?Transaction #\d+.*? to <rt-test\@example\.com>/, 'txn msg' ); # Forward ticket $m->follow_link_ok( { text => 'Forward', n => 1 }, 'follow 1st Forward' ); $m->submit_form( form_name => 'ForwardMessage', fields => { To => 'rt-test@example.com', }, button => 'ForwardAndReturn' ); $m->content_like( qr/Forwarded Ticket to <rt-test\@example\.com>/, 'txn msg' ); my ($forward_txn, $forward_ticket) = RT::Test->fetch_caught_mails; my $tag = qr/\[example\.com #\d+\] Fwd:/; like( $forward_txn, qr/Subject: $tag attachments for everyone/, 'Subject field is from txn' ); like( $forward_txn, qr/This is a forward of transaction/, 'forward description' ); like( $forward_ticket, qr/Subject: $tag test forward, attachments but no "content"/, 'Subject field is from ticket' ); like( $forward_ticket, qr/This is a forward of ticket/, 'forward description' ); like( $forward_ticket, qr/From: \=\?UTF-8\?.* \/i ); for my $mail ($forward_txn, $forward_ticket) { like( $mail, qr/To: rt-test\@example.com/, 'To field' ); like( $mail, qr/awesome\.patch/, 'att file name' ); like( $mail, qr/this is an attachment/, 'att content' ); like( $mail, qr/text\/x-diff/, 'att content type' ); like( $mail, qr/bpslogo\.png/, 'att image file name' ); like( $mail, qr/image\/png/, 'att image content type' ); } } RT::Test->clean_caught_mails; diag "Forward Ticket Template with a Subject: line" if $ENV{TEST_VERBOSE}; { require RT::Template; my $template = RT::Template->new($RT::SystemUser); $template->Load('Forward Ticket'); # prepend a Subject: line $template->SetContent("Subject: OVERRIDING SUBJECT\n\n" . $template->Content); my $ticket = RT::Test->create_ticket( Subject => 'test ticket', Queue => 1, ); $m->goto_ticket($ticket->Id); $m->follow_link_ok( { id => 'page-actions-forward' }, 'follow 1st Forward to forward ticket' ); $m->submit_form( form_name => 'ForwardMessage', fields => { To => 'rt-to@example.com', }, button => 'ForwardAndReturn' ); my ($mail) = RT::Test->fetch_caught_mails; like($mail, qr/Subject: \[example.com #\d+\] OVERRIDING SUBJECT/); } diag "Forward Transaction with non-ascii subject" if $ENV{TEST_VERBOSE}; { $m->follow_link_ok( { class => 'forward-link', n => 1 }, 'follow 2nd Forward' ); my $subject = Encode::decode("UTF-8", 'test non-ascii äöü'); $m->submit_form( form_name => 'ForwardMessage', fields => { Subject => $subject, To => 'rt-to@example.com', }, button => 'ForwardAndReturn' ); my ($mail) = RT::Test->fetch_caught_mails; if ( $mail =~ /Subject: (.+)/ ) { like( Encode::decode("UTF-8", RT::I18N::DecodeMIMEWordsToUTF8( $1, 'Subject' )), qr/$subject/, 'non-ascii subject' ); } $m->content_contains( $subject, 'non-ascii subject got displayed correctly' ); } done_testing; rt-5.0.1/t/web/cf_access.t000644 000765 000024 00000016546 14005011336 016133 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 32; my ($baseurl, $m) = RT::Test->started_ok; use constant ImageFile => $RT::StaticPath .'/images/bpslogo.png'; use constant ImageFileContent => RT::Test->file_content(ImageFile); ok $m->login, 'logged in'; diag "Create a CF"; { $m->follow_link( id => 'admin-custom-fields-create'); # Test form validation $m->submit_form( form_name => "ModifyCustomField", fields => { TypeComposite => 'Image-0', LookupType => 'RT::Queue-RT::Ticket', Name => '', Description => 'img', }, ); $m->text_contains('Invalid value for Name'); $m->submit_form( form_name => "ModifyCustomField", fields => { TypeComposite => 'Image-0', LookupType => 'RT::Queue-RT::Ticket', Name => '0', Description => 'img', }, ); $m->text_contains('Invalid value for Name'); $m->submit_form( form_name => "ModifyCustomField", fields => { TypeComposite => 'Image-0', LookupType => 'RT::Queue-RT::Ticket', Name => '1', Description => 'img', }, ); $m->text_contains('Invalid value for Name'); # The real submission $m->submit_form( form_name => "ModifyCustomField", fields => { TypeComposite => 'Image-0', LookupType => 'RT::Queue-RT::Ticket', Name => 'img', Description => 'img', EntryHint => 'Upload multiple images', }, ); $m->text_contains('Object created'); # Validation on update $m->form_name("ModifyCustomField"); $m->set_fields( TypeComposite => 'Image-0', LookupType => 'RT::Queue-RT::Ticket', Name => '', Description => 'img', ); $m->click('Update'); $m->text_contains('Illegal value for Name'); $m->form_name("ModifyCustomField"); $m->set_fields( TypeComposite => 'Image-0', LookupType => 'RT::Queue-RT::Ticket', Name => '0', Description => 'img', ); $m->click('Update'); $m->text_contains('Illegal value for Name'); $m->form_name("ModifyCustomField"); $m->set_fields( TypeComposite => 'Image-0', LookupType => 'RT::Queue-RT::Ticket', Name => '1', Description => 'img', ); $m->click('Update'); $m->text_contains('Illegal value for Name'); } diag "apply the CF to General queue"; my ( $cf, $cfid, $tid ); { $m->title_is(q/Editing CustomField img/, 'admin-cf created'); $m->follow_link( id => 'admin-queues'); $m->follow_link( text => 'General' ); $m->title_is(q/Configuration for queue General/, 'admin-queue: general'); $m->follow_link( id => 'page-custom-fields-tickets'); $m->title_is(q/Custom Fields for queue General/, 'admin-queue: general cfid'); $m->form_name('EditCustomFields'); # Sort by numeric IDs in names my @names = sort grep defined, $m->current_form->find_input('AddCustomField')->possible_values; $cf = pop(@names); $cf =~ /(\d+)$/ or die "Hey this is impossible dude"; $cfid = $1; $m->tick( AddCustomField => $cf => 1 ); # Associate the new CF with this queue $m->tick( AddCustomField => $_ => 0 ) for @names; # ...and not any other. ;-) $m->click('UpdateCFs'); $m->content_contains("Added custom field img to General", 'TCF added to the queue' ); } my $tester = RT::Test->load_or_create_user( Name => 'tester', Password => '123456' ); RT::Test->set_rights( { Principal => $tester->PrincipalObj, Right => [qw(SeeQueue ShowTicket CreateTicket)], }, ); ok $m->login( $tester->Name, 123456, logout => 1), 'logged in'; diag "check that we have no the CF on the create" ." ticket page when user has no SeeCustomField right"; { $m->submit_form( form_name => "CreateTicketInQueue" ); $m->content_lacks('Upload multiple images', 'has no upload image field'); my $form = $m->form_name("TicketCreate"); my $upload_field = "Object-RT::Ticket--CustomField-$cfid-Upload"; ok !$form->find_input( $upload_field ), 'no form field on the page'; $m->submit_form( form_name => "TicketCreate", fields => { Subject => 'test' }, button => 'SubmitTicket', ); $m->content_like(qr/Ticket \d+ created/, "a ticket is created succesfully"); $m->content_lacks('img:', 'has no img field on the page'); $m->follow_link( text => 'Custom Fields'); $m->content_lacks('Upload multiple images', 'has no upload image field'); } RT::Test->set_rights( { Principal => $tester->PrincipalObj, Right => [qw(SeeQueue ShowTicket CreateTicket SeeCustomField)], }, ); diag "check that we have no the CF on the create" ." ticket page when user has no ModifyCustomField right"; { $m->submit_form( form_name => "CreateTicketInQueue" ); $m->content_lacks('Upload multiple images', 'has no upload image field'); my $form = $m->form_name("TicketCreate"); my $upload_field = "Object-RT::Ticket--CustomField-$cfid-Upload"; ok !$form->find_input( $upload_field ), 'no form field on the page'; $m->submit_form( form_name => "TicketCreate", fields => { Subject => 'test' }, button => 'SubmitTicket', ); $tid = $1 if $m->content =~ /Ticket (\d+) created/i; ok $tid, "a ticket is created succesfully"; $m->follow_link( id => 'page-basics'); $m->content_lacks('Upload multiple images', 'has no upload image field'); $form = $m->form_name('TicketModify'); $upload_field = "Object-RT::Ticket-$tid-CustomField-$cfid-Upload"; ok !$form->find_input( $upload_field ), 'no form field on the page'; } RT::Test->set_rights( { Principal => $tester->PrincipalObj, Right => [qw(SeeQueue ShowTicket CreateTicket SeeCustomField ModifyCustomField)], }, ); diag "create a ticket with an image"; { $m->submit_form( form_name => "CreateTicketInQueue" ); $m->content_contains('Upload multiple images', 'has a upload image field'); $cf =~ /(\d+)$/ or die "Hey this is impossible dude"; my $upload_field = "Object-RT::Ticket--CustomField-$1-Upload"; $m->submit_form( form_name => "TicketCreate", fields => { $upload_field => ImageFile, Subject => 'testing img cf creation', }, button => 'SubmitTicket', ); $m->content_like(qr/Ticket \d+ created/, "a ticket is created succesfully"); $tid = $1 if $m->content =~ /Ticket (\d+) created/; $m->title_like(qr/testing img cf creation/, "its title is the Subject"); $m->follow_link( text => 'bpslogo.png' ); $m->content_is(ImageFileContent, "it links to the uploaded image"); } $m->get( $m->rt_base_url ); $m->follow_link( id => 'search-tickets-new'); $m->title_is(q/Query Builder/, 'Query building'); $m->submit_form( form_name => "BuildQuery", fields => { idOp => '=', ValueOfid => $tid, ValueOfQueue => 'General', QueueOp => '=', }, button => 'AddClause', ); $m->form_name('BuildQuery'); my $col = ($m->current_form->find_input('SelectDisplayColumns'))[-1]; $col->value( ($col->possible_values)[-1] ); $m->click('AddCol'); $m->form_name('BuildQuery'); $m->click('DoSearch'); $m->follow_link( text_regex => qr/bpslogo\.png/ ); $m->content_is(ImageFileContent, "it links to the uploaded image"); __END__ [FC] Bulk Update does not have custom fields. rt-5.0.1/t/web/lifecycle_rights.t000644 000765 000024 00000003373 14005011336 017533 0ustar00sunnavystaff000000 000000 use strict; use warnings; BEGIN {require './t/lifecycles/utils.pl'}; diag 'Test web UI for ticket status when rights granted at role level'; { my ( $url, $agent ) = RT::Test->started_ok; my $delivery = RT::Test->load_or_create_queue( Name => 'delivery', Lifecycle => 'delivery', ); ok $delivery && $delivery->id, 'loaded or created a queue'; my $ticket = RT::Test->create_ticket(Queue => 'Delivery'); ok $ticket && $ticket->Id; my $user_a = RT::Test->load_or_create_user( Name => 'user_a', Password => 'password', Privileged => 1, ); ok $user_a && $user_a->id, 'loaded or created user'; RT::Test->set_rights( { Principal => 'AdminCc', Right => [qw(SeeQueue)] }, { Principal => 'Everyone', Right => [qw(ModifyTicket ShowTicket)] }, ); ok( $agent->login( 'user_a' , 'password' ), 'logged in as user_a'); $agent->get_ok($url . '/Ticket/Modify.html?id=' . $ticket->Id); $agent->form_name('TicketModify'); my ($inputs) = $agent->find_all_inputs( type => 'option', name => 'Status', ); # Greater equal to 2 because you can change to current status and current status (Unchanged) without 'SeeQueu' ok $inputs->value_names eq 2, 'We are unable to transition to other statuses without role rights'; ok $ticket->AddRoleMember(Type => 'AdminCc', User => $user_a); $agent->get_ok($url . '/Ticket/Modify.html?id=' . $ticket->Id); $agent->form_name('TicketModify'); # Refresh page after rights update ($inputs) = $agent->find_all_inputs( type => 'option', name => 'Status', ); ok $inputs->value_names > 2, 'We are able to transition to other statuses with role rights'; } done_testing; rt-5.0.1/t/web/gnupg-select-keys-on-update.t000644 000765 000024 00000022711 14005011336 021451 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::GnuPG tests => undef, gnupg_options => { passphrase => 'rt-test' }; use RT::Action::SendEmail; my $queue = RT::Test->load_or_create_queue( Name => 'Regression', CorrespondAddress => 'rt-recipient@example.com', CommentAddress => 'rt-recipient@example.com', ); ok $queue && $queue->id, 'loaded or created queue'; my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in'; my $tid; { my $ticket = RT::Ticket->new( RT->SystemUser ); ($tid) = $ticket->Create( Subject => 'test', Queue => $queue->id, ); ok $tid, 'ticket created'; } diag "check that signing doesn't work if there is no key"; { RT::Test->clean_caught_mails; ok $m->goto_ticket( $tid ), "UI -> ticket #$tid"; $m->follow_link_ok( { text => 'Reply' }, 'ticket -> reply' ); $m->form_name('TicketUpdate'); $m->tick( Sign => 1 ); $m->field( UpdateCc => 'rt-test@example.com' ); $m->field( UpdateContent => 'Some content' ); $m->click('SubmitTicket'); $m->content_contains( 'unable to sign outgoing email messages', 'problems with passphrase' ); my @mail = RT::Test->fetch_caught_mails; ok !@mail, 'there are no outgoing emails'; $m->next_warning_like(qr/(secret key not available|No secret key)/); $m->no_leftover_warnings_ok; } { RT::Test->import_gnupg_key('rt-recipient@example.com'); RT::Test->trust_gnupg_key('rt-recipient@example.com'); my %res = RT::Crypt->GetKeysInfo( Key => 'rt-recipient@example.com' ); is $res{'info'}[0]{'TrustTerse'}, 'ultimate', 'ultimately trusted key'; } diag "check that things don't work if there is no key"; { RT::Test->clean_caught_mails; ok $m->goto_ticket( $tid ), "UI -> ticket #$tid"; $m->follow_link_ok( { text => 'Reply' }, 'ticket -> reply' ); $m->form_name('TicketUpdate'); $m->tick( Encrypt => 1 ); $m->field( UpdateCc => 'rt-test@example.com' ); $m->field( UpdateContent => 'Some content' ); $m->click('SubmitTicket'); $m->content_contains( 'You are going to encrypt outgoing email messages', 'problems with keys' ); $m->content_contains( 'There is no key suitable for encryption', 'problems with keys' ); my $form = $m->form_name('TicketUpdate'); ok !$form->find_input( 'UseKey-rt-test@example.com' ), 'no key selector'; my @mail = RT::Test->fetch_caught_mails; ok !@mail, 'there are no outgoing emails'; $m->next_warning_like(qr/(public key not found|No public key)/) for 1 .. 2; $m->no_leftover_warnings_ok; } diag "import first key of rt-test\@example.com"; my $fpr1 = ''; { RT::Test->import_gnupg_key('rt-test@example.com', 'secret'); my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test@example.com' ); is $res{'info'}[0]{'TrustLevel'}, 0, 'is not trusted key'; $fpr1 = $res{'info'}[0]{'Fingerprint'}; } diag "check that things still doesn't work if key is not trusted"; { RT::Test->clean_caught_mails; ok $m->goto_ticket( $tid ), "UI -> ticket #$tid"; $m->follow_link_ok( { text => 'Reply' }, 'ticket -> reply' ); $m->form_name('TicketUpdate'); $m->tick( Encrypt => 1 ); $m->field( UpdateCc => 'rt-test@example.com' ); $m->field( UpdateContent => 'Some content' ); $m->click('SubmitTicket'); $m->content_contains( 'You are going to encrypt outgoing email messages', 'problems with keys' ); $m->content_contains( 'There is one suitable key, but trust level is not set', 'problems with keys' ); my $form = $m->form_name('TicketUpdate'); ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector'; is scalar $input->possible_values, 1, 'one option'; $m->select( 'UseKey-rt-test@example.com' => $fpr1 ); $m->click('SubmitTicket'); $m->content_contains( 'You are going to encrypt outgoing email messages', 'problems with keys' ); $m->content_contains( 'Selected key either is not trusted', 'problems with keys' ); my @mail = RT::Test->fetch_caught_mails; ok !@mail, 'there are no outgoing emails'; $m->no_warnings_ok; } diag "import a second key of rt-test\@example.com"; my $fpr2 = ''; { RT::Test->import_gnupg_key('rt-test@example.com.2', 'secret'); my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test@example.com' ); is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key'; $fpr2 = $res{'info'}[2]{'Fingerprint'}; } diag "check that things still doesn't work if two keys are not trusted"; { RT::Test->clean_caught_mails; ok $m->goto_ticket( $tid ), "UI -> ticket #$tid"; $m->follow_link_ok( { text => 'Reply' }, 'ticket -> reply' ); $m->form_name('TicketUpdate'); $m->tick( Encrypt => 1 ); $m->field( UpdateCc => 'rt-test@example.com' ); $m->field( UpdateContent => 'Some content' ); $m->click('SubmitTicket'); $m->content_contains( 'You are going to encrypt outgoing email messages', 'problems with keys' ); $m->content_contains( 'There are several keys suitable for encryption', 'problems with keys' ); my $form = $m->form_name('TicketUpdate'); ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector'; is scalar $input->possible_values, 2, 'two options'; $m->select( 'UseKey-rt-test@example.com' => $fpr1 ); $m->click('SubmitTicket'); $m->content_contains( 'You are going to encrypt outgoing email messages', 'problems with keys' ); $m->content_contains( 'Selected key either is not trusted', 'problems with keys' ); my @mail = RT::Test->fetch_caught_mails; ok !@mail, 'there are no outgoing emails'; $m->no_warnings_ok; } { RT::Test->lsign_gnupg_key( $fpr1 ); my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test@example.com' ); ok $res{'info'}[0]{'TrustLevel'} > 0, 'trusted key'; is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key'; } diag "check that we see key selector even if only one key is trusted but there are more keys"; { RT::Test->clean_caught_mails; ok $m->goto_ticket( $tid ), "UI -> ticket #$tid"; $m->follow_link_ok( { text => 'Reply' }, 'ticket -> reply' ); $m->form_name('TicketUpdate'); $m->tick( Encrypt => 1 ); $m->field( UpdateCc => 'rt-test@example.com' ); $m->field( UpdateContent => 'Some content' ); $m->click('SubmitTicket'); $m->content_contains( 'You are going to encrypt outgoing email messages', 'problems with keys' ); $m->content_contains( 'There are several keys suitable for encryption', 'problems with keys' ); my $form = $m->form_name('TicketUpdate'); ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector'; is scalar $input->possible_values, 2, 'two options'; my @mail = RT::Test->fetch_caught_mails; ok !@mail, 'there are no outgoing emails'; $m->no_warnings_ok; } diag "check that key selector works and we can select trusted key"; { RT::Test->clean_caught_mails; ok $m->goto_ticket( $tid ), "UI -> ticket #$tid"; $m->follow_link_ok( { text => 'Reply' }, 'ticket -> reply' ); $m->form_name('TicketUpdate'); $m->tick( Encrypt => 1 ); $m->field( UpdateCc => 'rt-test@example.com' ); $m->field( UpdateContent => 'Some content' ); $m->click('SubmitTicket'); $m->content_contains( 'You are going to encrypt outgoing email messages', 'problems with keys' ); $m->content_contains( 'There are several keys suitable for encryption', 'problems with keys' ); my $form = $m->form_name('TicketUpdate'); ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector'; is scalar $input->possible_values, 2, 'two options'; $m->select( 'UseKey-rt-test@example.com' => $fpr1 ); $m->click('SubmitTicket'); $m->content_contains('Correspondence added', 'Correspondence added' ); my @mail = RT::Test->fetch_caught_mails; ok @mail, 'there are some emails'; check_text_emails( { Encrypt => 1 }, @mail ); $m->no_warnings_ok; } diag "check encrypting of attachments"; for my $encrypt (0, 1) { RT::Test->clean_caught_mails; ok $m->goto_ticket( $tid ), "UI -> ticket #$tid"; $m->follow_link_ok( { text => 'Reply' }, 'ticket -> reply' ); $m->form_name('TicketUpdate'); $m->tick( Encrypt => 1 ) if $encrypt; $m->field( UpdateCc => 'rt-test@example.com' ); $m->field( UpdateContent => 'Some content' ); $m->field( Attach => $0 ); $m->click('SubmitTicket'); if ($encrypt) { $m->content_contains( 'You are going to encrypt outgoing email messages', 'problems with keys' ); $m->content_contains( 'There are several keys suitable for encryption', 'problems with keys' ); my $form = $m->form_name('TicketUpdate'); ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector'; is scalar $input->possible_values, 2, 'two options'; $m->select( 'UseKey-rt-test@example.com' => $fpr1 ); $m->click('SubmitTicket'); } $m->content_contains('Correspondence added', 'Correspondence added' ); my @mail = RT::Test->fetch_caught_mails; ok @mail, 'there are some emails'; check_text_emails( { Encrypt => $encrypt, Attachment => "Attachment content" }, @mail ); $m->no_warnings_ok; } done_testing; rt-5.0.1/t/web/ticket_txn_cf.t000644 000765 000024 00000007472 14005011336 017044 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in as root'; my $root = RT::User->new(RT->SystemUser); ok( $root->Load('root'), 'load root user' ); my $cf_name = 'test txn cf'; my $cfid; diag "Create a CF"; { $m->follow_link( id => 'admin-custom-fields-create'); $m->submit_form( form_name => "ModifyCustomField", fields => { Name => $cf_name, TypeComposite => 'Freeform-1', LookupType => 'RT::Queue-RT::Ticket-RT::Transaction', }, ); $m->content_contains('Object created', 'created CF sucessfully' ); $cfid = $m->form_name('ModifyCustomField')->value('id'); ok $cfid, "found id of the CF in the form, it's #$cfid"; } diag "apply the CF to General queue"; my $queue = RT::Test->load_or_create_queue( Name => 'General' ); ok $queue && $queue->id, 'loaded or created queue'; { $m->follow_link( id => 'admin-queues-select'); $m->title_is( q/Admin queues/, 'admin-queues screen' ); $m->follow_link( text => 'General' ); $m->title_is( q/Configuration for queue General/, 'admin-queue: general' ); $m->follow_link( id => 'page-custom-fields-transactions' ); $m->title_is( q/Custom Fields for queue General/, 'admin-queue: general cfid' ); $m->form_name('EditCustomFields'); $m->tick( "AddCustomField" => $cfid ); $m->click('UpdateCFs'); $m->content_contains('Added custom field test txn cf to General.', 'TCF added to the queue' ); } my ( $ticket, $id ); diag 'submit value on ticket create page'; { $m->submit_form( form_name => "CreateTicketInQueue" ); $m->content_contains($cf_name, 'has cf field' ); $m->submit_form( form_name => "TicketCreate", fields => { Subject => 'test 2017-01-04', Content => 'test', "Object-RT::Transaction--CustomField-$cfid-Values" => 'hello from create', }, button => 'SubmitTicket' ); ok( ($id) = $m->content =~ /Ticket (\d+) created/, "created ticket $id" ); $ticket = RT::Ticket->new(RT->SystemUser); $ticket->Load($id); is( $ticket->Transactions->First->CustomFieldValues($cfid)->First->Content, 'hello from create', 'txn cf value in db' ); $m->content_contains($cf_name, 'has txn cf name on the page' ); $m->content_contains('hello from create', 'has txn cf value on the page' ); } diag 'submit value on ticket update page'; { $m->follow_link_ok( { text => 'Reply' }, "reply to the ticket" ); $m->content_contains($cf_name, 'has cf field' ); $m->form_name('TicketUpdate'); $m->field(UpdateContent => 'test 2'); $m->field("Object-RT::Transaction--CustomField-$cfid-Values" => 'hello from update'); $m->click('SubmitTicket'); $m->content_contains('Correspondence added'); my $txns = $ticket->Transactions; $txns->Limit(FIELD => 'Type', VALUE => 'Correspond'); is( $txns->Last->CustomFieldValues($cfid)->First->Content, 'hello from update', 'txn cf value in db' ); $m->content_contains($cf_name, 'has txn cf name on the page' ); $m->content_contains('hello from update', 'has txn cf value on the page' ); } diag 'submit no value on ticket update page'; { $m->follow_link_ok( { text => 'Reply' }, "reply to the ticket" ); $m->content_contains($cf_name, 'has cf field' ); $m->form_name('TicketUpdate'); $m->field(UpdateContent => 'test 2'); $m->click('SubmitTicket'); $m->content_contains('Correspondence added'); my $txns = $ticket->Transactions; $txns->Limit(FIELD => 'Type', VALUE => 'Correspond'); is( $txns->Last->CustomFieldValues($cfid)->Count, 0, 'no txn cf value in db' ); } done_testing; rt-5.0.1/t/web/quicksearch.t000644 000765 000024 00000002646 14005011336 016520 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 9; my ($baseurl, $m) = RT::Test->started_ok; my $url = $m->rt_base_url; # merged tickets still show up in search my $t1 = RT::Ticket->new(RT->SystemUser); $t1->Create( Subject => 'base ticket'.$$, Queue => 'general', Owner => 'root', Requestor => 'customsearch@localhost', MIMEObj => MIME::Entity->build( From => 'customsearch@localhost', To => 'rt@localhost', Subject => 'base ticket'.$$, Data => "DON'T SEARCH FOR ME", ), ); ok(my $id1 = $t1->id, 'created ticket for custom search'); my $t2 = RT::Ticket->new(RT->SystemUser); $t2->Create( Subject => 'merged away'.$$, Queue => 'general', Owner => 'root', Requestor => 'customsearch@localhost', MIMEObj => MIME::Entity->build( From => 'customsearch@localhost', To => 'rt@localhost', Subject => 'merged away'.$$, Data => "MERGEDAWAY", ), ); ok(my $id2 = $t2->id, 'created ticket for custom search'); my ($ok, $msg) = $t2->MergeInto($id1); ok($ok, "merge: $msg"); ok($m->login, 'logged in'); $m->form_with_fields('q'); $m->field(q => 'fulltext:MERGEDAWAY'); TODO: { local $TODO = "We don't yet handle merged ticket content searches right"; $m->content_contains('

      Found 1 ticket

      '); } $m->content_contains('base ticket', "base ticket is found, not the merged-away ticket"); rt-5.0.1/t/web/quickcreate.t000644 000765 000024 00000002020 14005011336 016500 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 11; RT->Config->Set('DisplayTicketAfterQuickCreate' => 0); my ($baseurl, $m) = RT::Test->started_ok; ok($m->login, 'logged in'); $m->form_with_fields('Subject', 'Content'); $m->field(Subject => 'from quick create'); $m->submit; $m->content_like(qr/Ticket \d+ created in queue/, 'created ticket'); like( $m->uri, qr{^\Q$baseurl\E/(?:index\.html)?\?results=}, 'still in homepage' ); unlike( $m->uri, qr{Ticket/Display.html}, 'not on ticket display page' ); $m->get_ok($baseurl . '/Prefs/Other.html'); $m->submit_form( form_name => 'ModifyPreferences', fields => { 'DisplayTicketAfterQuickCreate' => 1, }, button => 'Update', ); $m->content_contains( 'Preferences saved', 'enabled DisplayTicketAfterQuickCreate' ); $m->get($baseurl); $m->form_with_fields('Subject', 'Content'); $m->field(Subject => 'from quick create'); $m->submit; $m->content_like(qr/Ticket \d+ created in queue/, 'created ticket'); like( $m->uri, qr!/Ticket/Display.html!, 'still in homepage' ); rt-5.0.1/t/web/ticket_owner.t000644 000765 000024 00000043320 14005011336 016705 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => undef; my $queue = RT::Test->load_or_create_queue( Name => 'Regression' ); ok $queue && $queue->id, 'loaded or created queue'; my $user_a = RT::Test->load_or_create_user( Name => 'user_a', Password => 'password', ); ok $user_a && $user_a->id, 'loaded or created user: ' . $user_a->Name; my $user_b = RT::Test->load_or_create_user( Name => 'user_b', Password => 'password', ); ok $user_b && $user_b->id, 'loaded or created user: ' . $user_b->Name; # To give ReassignTicket my $user_c = RT::Test->load_or_create_user( Name => 'user_c', Password => 'password', ); ok $user_c && $user_c->id, 'loaded or created user: ' . $user_c->Name; my ($baseurl, $agent_a) = RT::Test->started_ok; ok( RT::Test->set_rights( { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket ReplyToTicket)] }, { Principal => $user_b, Right => [qw(SeeQueue ShowTicket OwnTicket)] }, ), 'set rights'); ok $agent_a->login('user_a', 'password'), 'logged in as user A'; diag "current user has no right to own, nobody selected as owner on create"; { $agent_a->get_ok('/Ticket/Create.html?Queue=' . $queue->id, 'open ticket create page'); $agent_a->content_contains('Create a new ticket', 'opened create ticket page'); my $form = $agent_a->form_name('TicketCreate'); is $form->value('Owner'), RT->Nobody->id, 'correct owner selected'; ok !grep($_ == $user_a->id, $form->find_input('Owner')->possible_values), 'user A can not own tickets'; $agent_a->click('SubmitTicket'); $agent_a->content_like(qr/Ticket \d+ created in queue/i, 'created ticket'); my ($id) = ($agent_a->content =~ /Ticket (\d+) created in queue/); ok $id, 'found id of the ticket'; my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $id ); ok $ticket->id, 'loaded the ticket'; is $ticket->Owner, RT->Nobody->id, 'correct owner'; } diag "user can chose owner of a new ticket"; { $agent_a->get_ok('/Ticket/Create.html?Queue=' . $queue->id, 'open ticket create page'); $agent_a->content_contains('Create a new ticket', 'opened create ticket page'); my $form = $agent_a->form_name('TicketCreate'); is $form->value('Owner'), RT->Nobody->id, 'correct owner selected'; ok grep($_ == $user_b->id, $form->find_input('Owner')->possible_values), 'user B is listed as potential owner'; $agent_a->select('Owner', $user_b->id); $agent_a->click('SubmitTicket'); $agent_a->content_like(qr/Ticket \d+ created in queue/i, 'created ticket'); my ($id) = ($agent_a->content =~ /Ticket (\d+) created in queue/); ok $id, 'found id of the ticket'; my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $id ); ok $ticket->id, 'loaded the ticket'; is $ticket->Owner, $user_b->id, 'correct owner'; } my $agent_b = RT::Test::Web->new; ok $agent_b->login('user_b', 'password'), 'logged in as user B'; diag "user A can not change owner after create"; { my $ticket = RT::Ticket->new( $user_a ); my ($id, $txn, $msg) = $ticket->Create( Queue => $queue->id, Owner => $user_b->id, Subject => 'test', ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Owner, $user_b->id, 'correct owner'; # try the following group of tests twice with different agents(logins) my $test_cb = sub { my $agent = shift; $agent->get("/Ticket/Modify.html?id=$id"); my $form = $agent->form_name('TicketModify'); is $form->value('Owner'), $user_b->id, 'correct owner selected'; $agent->select('Owner', RT->Nobody->id); $agent->submit; $agent->content_contains( 'Permission Denied', 'no way to change owner after create if you have no rights' ); my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $id ); ok $ticket->id, 'loaded the ticket'; is $ticket->Owner, $user_b->id, 'correct owner'; }; $test_cb->($agent_a); diag "even owner(user B) can not change owner"; $test_cb->($agent_b); } diag "on reply correct owner is selected"; { my $ticket = RT::Ticket->new( $user_a ); my ($id, $txn, $msg) = $ticket->Create( Queue => $queue->id, Owner => $user_b->id, Subject => 'test', ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Owner, $user_b->id, 'correct owner'; $agent_a->goto_ticket( $id ); $agent_a->follow_link_ok({text => 'Reply'}, 'Ticket -> Reply'); my $form = $agent_a->form_name('TicketUpdate'); is $form->value('Owner'), '', 'empty value selected'; $agent_a->submit; $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $id ); ok $ticket->id, 'loaded the ticket'; is $ticket->Owner, $user_b->id, 'correct owner'; } ok( RT::Test->set_rights( { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket OwnTicket)] }, { Principal => $user_b, Right => [qw(SeeQueue ShowTicket OwnTicket)] }, ), 'set rights'); diag "Couldn't take without coresponding right"; { my $ticket = RT::Ticket->new( $user_a ); my ($id, $txn, $msg) = $ticket->Create( Queue => $queue->id, Subject => 'test', ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Owner, RT->Nobody->id, 'correct owner'; $agent_a->goto_ticket( $id ); ok !($agent_a->find_all_links( text => 'Take' ))[0], 'no Take link'; ok !($agent_a->find_all_links( text => 'Steal' ))[0], 'no Steal link as well'; } diag "Couldn't steal without coresponding right"; { my $ticket = RT::Ticket->new( $user_a ); my ($id, $txn, $msg) = $ticket->Create( Queue => $queue->id, Owner => $user_b->id, Subject => 'test', ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Owner, $user_b->id, 'correct owner'; $agent_a->goto_ticket( $id ); ok !($agent_a->find_all_links( text => 'Steal' ))[0], 'no Steal link'; ok !($agent_a->find_all_links( text => 'Take' ))[0], 'no Take link as well'; } ok( RT::Test->set_rights( { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket TakeTicket)] }, ), 'set rights'); diag "TakeTicket require OwnTicket to work"; { my $ticket = RT::Ticket->new( $user_a ); my ($id, $txn, $msg) = $ticket->Create( Queue => $queue->id, Subject => 'test', ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Owner, RT->Nobody->id, 'correct owner'; $agent_a->goto_ticket( $id ); ok !($agent_a->find_all_links( text => 'Take' ))[0], 'no Take link'; ok !($agent_a->find_all_links( text => 'Steal' ))[0], 'no Steal link as well'; } ok( RT::Test->set_rights( { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket OwnTicket TakeTicket)] }, { Principal => $user_b, Right => [qw(SeeQueue ShowTicket OwnTicket)] }, ), 'set rights'); diag "TakeTicket+OwnTicket work"; { my $ticket = RT::Ticket->new( $user_a ); my ($id, $txn, $msg) = $ticket->Create( Queue => $queue->id, Subject => 'test', ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Owner, RT->Nobody->id, 'correct owner'; $agent_a->goto_ticket( $id ); ok !($agent_a->find_all_links( text => 'Steal' ))[0], 'no Steal link'; $agent_a->follow_link_ok({text => 'Take'}, 'Ticket -> Take'); $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $id ); ok $ticket->id, 'loaded the ticket'; is $ticket->Owner, $user_a->id, 'correct owner'; } diag "TakeTicket+OwnTicket don't work when owner is not nobody"; { my $ticket = RT::Ticket->new( $user_a ); my ($id, $txn, $msg) = $ticket->Create( Queue => $queue->id, Owner => $user_b->id, Subject => 'test', ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Owner, $user_b->id, 'correct owner'; $agent_a->goto_ticket( $id ); ok !($agent_a->find_all_links( text => 'Take' ))[0], 'no Take link'; ok !($agent_a->find_all_links( text => 'Steal' ))[0], 'no Steal link too'; } ok( RT::Test->set_rights( { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket StealTicket)] }, { Principal => $user_b, Right => [qw(SeeQueue ShowTicket OwnTicket)] }, ), 'set rights'); diag "StealTicket require OwnTicket to work"; { my $ticket = RT::Ticket->new( $user_a ); my ($id, $txn, $msg) = $ticket->Create( Queue => $queue->id, Owner => $user_b->id, Subject => 'test', ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Owner, $user_b->id, 'correct owner'; $agent_a->goto_ticket( $id ); ok !($agent_a->find_all_links( text => 'Steal' ))[0], 'no Steal link'; ok !($agent_a->find_all_links( text => 'Take' ))[0], 'no Take link too'; } ok( RT::Test->set_rights( { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket OwnTicket StealTicket)] }, { Principal => $user_b, Right => [qw(SeeQueue ShowTicket OwnTicket)] }, ), 'set rights'); diag "StealTicket+OwnTicket work"; { my $ticket = RT::Ticket->new( $user_a ); my ($id, $txn, $msg) = $ticket->Create( Queue => $queue->id, Owner => $user_b->id, Subject => 'test', ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Owner, $user_b->id, 'correct owner'; $agent_a->goto_ticket( $id ); ok !($agent_a->find_all_links( text => 'Take' ))[0], 'but no Take link'; $agent_a->follow_link_ok({text => 'Steal'}, 'Ticket -> Steal'); $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $id ); ok $ticket->id, 'loaded the ticket'; is $ticket->Owner, $user_a->id, 'correct owner'; } diag "StealTicket+OwnTicket don't work when owner is nobody"; { my $ticket = RT::Ticket->new( $user_a ); my ($id, $txn, $msg) = $ticket->Create( Queue => $queue->id, Subject => 'test', ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Owner, RT->Nobody->id, 'correct owner'; $agent_a->goto_ticket( $id ); ok !($agent_a->find_all_links( text => 'Steal' ))[0], 'no Steal link'; ok !($agent_a->find_all_links( text => 'Take' ))[0], 'no Take link as well (no right)'; } ok( RT::Test->set_rights( { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket OwnTicket TakeTicket StealTicket)] }, { Principal => $user_b, Right => [qw(SeeQueue ShowTicket OwnTicket)] }, ), 'set rights'); diag "no Steal link when owner nobody"; { my $ticket = RT::Ticket->new( $user_a ); my ($id, $txn, $msg) = $ticket->Create( Queue => $queue->id, Subject => 'test', ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Owner, RT->Nobody->id, 'correct owner'; $agent_a->goto_ticket( $id ); ok !($agent_a->find_all_links( text => 'Steal' ))[0], 'no Steal link'; ok( ($agent_a->find_all_links( text => 'Take' ))[0], 'but have Take link'); } diag "no Take link when owner is not nobody"; { my $ticket = RT::Ticket->new( $user_a ); my ($id, $txn, $msg) = $ticket->Create( Queue => $queue->id, Owner => $user_b->id, Subject => 'test', ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Owner, $user_b->id, 'correct owner'; $agent_a->goto_ticket( $id ); ok !($agent_a->find_all_links( text => 'Take' ))[0], 'no Take link'; ok( ($agent_a->find_all_links( text => 'Steal' ))[0], 'but have Steal link'); } ok( RT::Test->set_rights( { Principal => $user_a, Right => [ qw(SeeQueue ShowTicket CreateTicket ReplyToTicket OwnTicket TakeTicket StealTicket) ] }, { Principal => $user_b, Right => [qw(SeeQueue ShowTicket OwnTicket)] }, { Principal => $user_c, Right => [qw(SeeQueue ShowTicket ReassignTicket)] }, ), 'set rights' ); diag "action is Take if old owner is nobody and new owner is current user in update page"; { my $ticket = RT::Ticket->new( $user_a ); my ( $id, $txn, $msg ) = $ticket->Create( Queue => $queue->id, Subject => 'test', ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Owner, RT->Nobody->id, 'correct owner'; $agent_a->goto_ticket( $id ); $agent_a->content_lacks('Taken', 'no Taken'); $agent_a->follow_link_ok({text => 'Reply'}, 'Ticket -> Reply'); $agent_a->submit_form( form_name => 'TicketUpdate', fields => { Owner => $user_a->id }, button => 'SubmitTicket', ); like($agent_a->dom->at('.transaction.people .description')->all_text, qr/user_a\s*-\s*Taken/, 'got user_a Taken message' ); $agent_b->goto_ticket($id); like($agent_b->dom->at('.transaction.people .description')->all_text, qr/user_a\s*-\s*Taken/, 'got user_a Taken message for user b' ); } diag "action is Take if old owner is nobody and new owner is current user in basics page"; { my $ticket = RT::Ticket->new($user_a); my ( $id, $txn, $msg ) = $ticket->Create( Queue => $queue->id, Subject => 'test', ); ok $id, 'created a ticket #' . $id or diag "error: $msg"; is $ticket->Owner, RT->Nobody->id, 'correct owner'; $agent_a->goto_ticket($id); $agent_a->content_lacks('Taken', 'no Taken'); $agent_a->follow_link_ok( { text => 'Basics' }, 'Ticket -> Basics' ); $agent_a->submit_form( form_name => 'TicketModify', fields => { Owner => $user_a->id }, ); $agent_a->content_contains( 'Owner changed from Nobody to user_a', 'got set message in Basics' ); $agent_a->goto_ticket($id); like($agent_a->dom->at('.transaction.people .description')->all_text, qr/user_a\s*-\s*Taken/, 'got user_a Taken message' ); $agent_b->goto_ticket($id); like($agent_b->dom->at('.transaction.people .description')->all_text, qr/user_a\s*-\s*Taken/, 'got user_a Taken message for user b' ); } my $agent_c = RT::Test::Web->new; ok $agent_c->login('user_c', 'password'), 'logged in as user C'; diag "user can assign ticket to new owner with ReassignTicket right"; { my $ticket = RT::Ticket->new($user_a); my ( $id, $txn, $msg ) = $ticket->Create( Queue => $queue->id, Subject => 'test', ); ok $id, 'created a ticket #' . $id or diag "error: $msg"; is $ticket->Owner, RT->Nobody->id, 'correct owner'; $agent_c->goto_ticket($id); ok !($agent_c->find_all_links( text => 'Take' ))[0], 'no Take link'; ok !($agent_c->find_all_links( text => 'Steal' ))[0], 'no Steal link'; $agent_a->goto_ticket($id); $agent_a->content_lacks('Taken', 'no Taken'); $agent_a->follow_link_ok( { text => 'Basics' }, 'Ticket -> Basics' ); $agent_a->submit_form( form_name => 'TicketModify', fields => { Owner => $user_a->id }, ); $agent_a->content_contains( 'Owner changed from Nobody to user_a', 'got set message in Basics' ); $agent_a->goto_ticket($id); like($agent_a->dom->at('.transaction.people .description')->all_text, qr{user_a\s*-\s*Taken}, 'got user_a Taken message' ); $agent_c->goto_ticket($id); ok !($agent_c->find_all_links( text => 'Take' ))[0], 'no Take link'; ok !($agent_c->find_all_links( text => 'Steal' ))[0], 'no Steal link'; $agent_c->follow_link_ok( { text => 'Basics' }, 'Ticket -> Basics' ); my $form = $agent_c->form_name('TicketModify'); is $form->value('Owner'), $user_a->id, 'correct owner selected'; ok grep($_ == $user_b->id, $form->find_input('Owner')->possible_values), 'user B is listed as potential owner'; $agent_c->select('Owner', $user_b->id); $agent_c->submit; $agent_c->content_contains( 'Owner changed from user_a to user_b', 'got set message in Basics' ); $agent_c->goto_ticket($id); $agent_c->content_like( qr{Owner forcibly changed}, 'got owner forcibly changed message' ); ok !($agent_c->find_all_links( text => 'Take' ))[0], 'no Take link'; } ok( RT::Test->add_rights( { Principal => $user_c, Right => [qw(OwnTicket)] }, ), 'add rights' ); diag "user can take/steal ticket with ReassignTicket+OwnTicket right"; { my $ticket = RT::Ticket->new($user_a); my ( $id, $txn, $msg ) = $ticket->Create( Queue => $queue->id, Subject => 'test', ); ok $id, 'created a ticket #' . $id or diag "error: $msg"; is $ticket->Owner, RT->Nobody->id, 'correct owner'; $agent_c->goto_ticket($id); ok( ($agent_c->find_all_links( text => 'Take' ))[0], 'has Take link' ); ok !($agent_c->find_all_links( text => 'Steal' ))[0], 'no Steal link'; $agent_a->goto_ticket($id); $agent_a->content_lacks('Taken', 'no Taken'); $agent_a->follow_link_ok( { text => 'Basics' }, 'Ticket -> Basics' ); $agent_a->submit_form( form_name => 'TicketModify', fields => { Owner => $user_a->id }, ); $agent_a->content_contains( 'Owner changed from Nobody to user_a', 'got set message in Basics' ); $agent_a->goto_ticket($id); like($agent_a->dom->at('.transaction.people .description')->all_text, qr{user_a\s*-\s*Taken}, 'got user_a Taken message' ); $agent_c->goto_ticket($id); ok !($agent_c->find_all_links( text => 'Take' ))[0], 'no Take link'; ok( ($agent_c->find_all_links( text => 'Steal' ))[0], 'has Steal link' ); $agent_c->follow_link_ok( { text => 'Steal' }, 'Ticket -> Steal' ); $agent_c->content_contains( 'Owner changed from user_a to user_c', 'steal message' ); ok !($agent_c->find_all_links( text => 'Take' ))[0], 'no Take link'; ok !($agent_c->find_all_links( text => 'Steal' ))[0], 'no Steal link'; } done_testing; rt-5.0.1/t/web/make-clicky.t000644 000765 000024 00000005425 14005011336 016405 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 'no_declare', plugins => ["MakeClicky"], config => 'Set( @Active_MakeClicky => "httpurl_overwrite" );'; use Test::LongString; my ($base, $m) = RT::Test->started_ok; $m->login; $m->get_ok("/"); diag "Trailing punctuation"; { my $url = 'http://bestpractical.com/rt'; for my $punc (qw( . ! ? ), ",") { is_string( make_clicky($m, "Refer to $url$punc A following sentence."), qq[Refer to $url$punc A following sentence.], "$punc not included in url", ); } } diag "Punctuation as part of the url"; { my $url = 'http://bestpractical.com/rt/download.html?foo=bar,baz.2'; is_string( make_clicky($m, "Refer to $url. A following sentence."), qq[Refer to $url. A following sentence.], "Punctuation in middle of URL", ); } diag "Anchor in URL"; { my $url = 'http://wiki.bestpractical.com/test#anchor'; is_string( make_clicky($m, "Anchor $url here"), qq[Anchor $url here], "Captured anchor in URL", ); } diag "Query parameters in URL"; for my $html (0, 1) { my $url = "https://wiki.bestpractical.com/?q=test&search=1"; my $escaped_url = $url; RT::Interface::Web::EscapeHTML( \$escaped_url ); is_string( make_clicky($m, $html ? $escaped_url : $url, $html), qq[$escaped_url], "Single escaped @{[$html ? 'HTML' : 'text']} query parameters", ); } diag "Found in href"; { my $url = 'Best Practical'; is_string( make_clicky($m, $url, 1), $url, "URL in existing href is a no-op" ); } diag "Found in other attribute"; { my $url = 'Some image'; is_string( make_clicky($m, $url, 1), $url, "URL in image src= is a no-op" ); } diag "Do not double encode & test"; { my $url = 'http://bestpractical.com/search?q=me&irene;token=foo'; my $string = qq[http://bestpractical.com/search?q=me&irene;token=foo]; is_string( make_clicky($m,$url, 1), $string, "URL with & should not rencode" ); } sub make_clicky { my $m = shift; my $text = shift; my $html = shift || 0; RT::Interface::Web::EscapeURI(\$text); $m->get_ok("/makeclicky?content=$text&html=$html", "made clicky") or diag $m->status; return $m->success ? $m->content : ""; } done_testing(); rt-5.0.1/t/web/group_create.t000644 000765 000024 00000005614 14005011336 016673 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in as root'; my $root = RT::User->new(RT->SystemUser); ok( $root->Load('root'), 'load root user' ); my $group_name = 'test group'; my $group_id; diag "Create a group"; { $m->follow_link( id => 'admin-groups-create'); # Test group form validation $m->submit_form( form_name => 'ModifyGroup', fields => { Name => '', }, ); $m->text_contains('Name is required'); $m->submit_form( form_name => 'ModifyGroup', fields => { Name => '0', }, ); $m->text_contains('Could not create group'); $m->submit_form( form_name => 'ModifyGroup', fields => { Name => '1', }, ); $m->text_contains('Could not create group'); $m->submit_form( form_name => 'ModifyGroup', fields => { Name => $group_name, }, ); $m->content_contains('Group created', 'created group sucessfully' ); # Test validation on updae $m->form_name('ModifyGroup'); $m->set_fields( Name => '', ); $m->click_button(value => 'Save Changes'); $m->text_contains('Illegal value for Name'); $m->form_name('ModifyGroup'); $m->set_fields( Name => '0', ); $m->click_button(value => 'Save Changes'); $m->text_contains('Illegal value for Name'); $m->form_name('ModifyGroup'); $m->set_fields( Name => '1', ); $m->click_button(value => 'Save Changes'); $m->text_contains('Illegal value for Name'); $group_id = $m->form_name('ModifyGroup')->value('id'); ok $group_id, "found id of the group in the form, it's #$group_id"; } ok($m->logout(), 'Logged out'); { my $tester = RT::Test->load_or_create_user( Name => 'staff1', Password => 'password' ); ok $m->login( $tester->Name, 'password' ), 'Logged in'; $m->get('/Admin/Groups/'); is( $m->status, 403, "No access without ShowConfigTab" ); RT::Test->set_rights( { Principal => $tester->PrincipalObj, Right => [qw(ShowConfigTab)], }, ); $m->get('/Admin/Groups/'); is( $m->status, 200, "Can see group admin page" ); load_group_admin_pages($m, $group_id, '403'); ok($tester->PrincipalObj->GrantRight(Right => 'SeeGroup', Object => $RT::System), 'Grant SeeGroup'); load_group_admin_pages($m, $group_id, '200'); $m->get("/Group/Summary.html?id=$group_id"); is( $m->status, 200, "Got 200 for Group Summary page"); } sub load_group_admin_pages{ my $m = shift; my $group_id = shift; my $status = shift; foreach my $page (qw(GroupRights Members Modify History Memberships ModifyLinks UserRights)){ $m->get("/Admin/Groups/$page.html?id=$group_id"); is( $m->status, $status, "Got $status for $page page"); } } done_testing(); rt-5.0.1/t/web/cf_render_type.t000644 000765 000024 00000002405 14005011336 017177 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 8; my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in as root'; my $cf_name = 'test render type'; my $cfid; diag "Create a CF"; { $m->follow_link( id => 'admin-custom-fields-create'); $m->submit_form( form_name => "ModifyCustomField", fields => { Name => $cf_name, TypeComposite => 'Freeform-1', LookupType => 'RT::Queue-RT::Ticket', }, ); $m->content_contains('Object created', 'created Freeform-1' ); $cfid = $m->form_name('ModifyCustomField')->value('id'); ok $cfid, "found id of the CF in the form, it's #$cfid"; } diag "change to Select type"; { $m->submit_form( form_name => "ModifyCustomField", fields => { TypeComposite => 'Select-1', }, button => 'Update', ); $m->content_contains( "Type changed from 'Enter one value' to 'Select one value'", 'changed to Select-1' ); } diag "let's save it again"; { $m->submit_form( form_name => "ModifyCustomField", button => 'Update', ); $m->content_lacks( "Render Type changed from '1' to 'Select box'", 'no buggy RenderType change msg' ); } rt-5.0.1/t/web/html/000755 000765 000024 00000000000 14005011336 014765 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/web/queue_caching.t000644 000765 000024 00000020060 14005011336 017004 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; # make an initial queue, so we have more than 1 my $original_test_queue = new_queue("Test$$"); my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in'; diag("Check for 2 existing queues being visible"); { check_queues($m); } diag("Add a new queue, which won't show up until we fix the cache"); { new_queue("New Test $$"); check_queues($m); } diag("Disable an existing queue, it should stop appearing in the list"); { ok($original_test_queue->SetDisabled(1)); check_queues($m); } diag("Bring back a disabled queue"); { ok($original_test_queue->SetDisabled(0)); check_queues($m); } diag("Rename the original queue, make sure the name change is uncached"); { ok($original_test_queue->SetName("Name Change $$")); check_queues($m); } diag("Test a user who has more limited rights Queues"); { my $user_a = RT::Test->load_or_create_user( Name => 'user_a', Password => 'password', ); ok $user_a && $user_a->id, 'loaded or created user'; ok( RT::Test->set_rights( { Principal => $user_a, Right => [qw(SeeQueue CreateTicket)], Object => $original_test_queue }, ), 'Allow user a to see the testing queue'); my $a_m = RT::Test::Web->new; ok $a_m->login('user_a', 'password'), 'logged in as user A'; # check that they see a single queue check_queues($a_m,[$original_test_queue->Id],[$original_test_queue->Name]); ok( RT::Test->add_rights( { Principal => $user_a, Right => [qw(SeeQueue CreateTicket)] }, ), 'add global queue viewing rights'); check_queues($a_m); } diag( "Test a user who has only CreateTicket right" ); { my $user_b = RT::Test->load_or_create_user( Name => 'user_b', Password => 'password', ); ok $user_b && $user_b->id, 'loaded or created user'; ok( RT::Test->add_rights( { Principal => $user_b, Right => [qw(CreateTicket)] }, ), 'add global queue CreateTicket right' ); my $b_m = RT::Test::Web->new; ok $b_m->login( 'user_b', 'password' ), 'logged in as user B'; check_queues( $b_m, [], [] ); } diag( "Test a user who has only SeeQueue right" ); { my $user_c = RT::Test->load_or_create_user( Name => 'user_c', Password => 'password', ); ok $user_c && $user_c->id, 'loaded or created user'; ok( RT::Test->add_rights( { Principal => $user_c, Right => [qw(SeeQueue)] }, ), 'add global queue SeeQueue right' ); my $c_m = RT::Test::Web->new; ok $c_m->login( 'user_c', 'password' ), 'logged in as user C'; check_queues( $c_m, [], [] ); } diag( "Test a user starting with ShowTicket and ModifyTicket rights" ); { my $user_d = RT::Test->load_or_create_user( Name => 'user_d', Password => 'password', ); ok $user_d && $user_d->id, 'loaded or created user'; ok( RT::Test->add_rights( { Principal => $user_d, Right => [qw(ShowTicket ModifyTicket)] }, ), 'add global queue ShowTicket/ModifyTicket rights' ); my $d_m = RT::Test::Web->new; ok $d_m->login( 'user_d', 'password' ), 'logged in as user D'; for my $queue ( 1, $original_test_queue->id ) { RT::Test->create_ticket( Queue => $queue, Subject => "Ticket in queue $queue", ); check_queues( $d_m, [], [] ); $d_m->follow_link_ok( { text => "Ticket in queue $queue" } ); $d_m->follow_link_ok( { text => 'Basics' } ); check_queues( $d_m, [$queue], ["#$queue"], $d_m->uri, 'TicketModify' ); } ok( RT::Test->add_rights( { Principal => $user_d, Right => [qw(SeeQueue)] }, ), 'add global queue SeeQueue right' ); for my $queue ( 1, $original_test_queue->id ) { check_queues( $d_m, [], [] ); $d_m->follow_link_ok( { text => "Ticket in queue $queue" } ); $d_m->follow_link_ok( { text => 'Basics' } ); check_queues( $d_m, undef, undef, $d_m->uri, 'TicketModify' ); } ok( RT::Test->add_rights( { Principal => $user_d, Right => [qw(CreateTicket)] }, ), 'add global queue CreateTicket right' ); for my $queue ( 1, $original_test_queue->id ) { check_queues( $d_m ); $d_m->follow_link_ok( { text => "Ticket in queue $queue" } ); $d_m->follow_link_ok( { text => 'Basics' } ); check_queues( $d_m, undef, undef, $d_m->uri, 'TicketModify' ); } } diag( "Test a user with SuperUser granted later" ); { my $user_e = RT::Test->load_or_create_user( Name => 'user_e', Password => 'password', ); ok( $user_e && $user_e->id, 'loaded or created user' ); my $m = RT::Test::Web->new; ok( $m->login( 'user_e', 'password' ), 'logged in as user E' ); check_queues( $m, [], [] ); ok( RT::Test->add_rights( { Principal => $user_e, Right => [qw(SuperUser)] }, ), 'add SuperUser right' ); for my $queue ( 1, $original_test_queue->id ) { check_queues( $m ); $m->follow_link_ok( { text => "Ticket in queue $queue" } ); $m->follow_link_ok( { text => 'Basics' } ); check_queues( $m, undef, undef, $m->uri, 'TicketModify' ); } } diag( "Test a user with ShowTicket granted later" ); { my $user_f = RT::Test->load_or_create_user( Name => 'user_f', Password => 'password', ); ok( $user_f && $user_f->id, 'loaded or created user' ); ok( RT::Test->add_rights( { Principal => $user_f, Right => [qw(SeeQueue)] }, ), 'add global SeeQueue right' ); my $m = RT::Test::Web->new; ok( $m->login( 'user_f', 'password' ), 'logged in as user F' ); # Links in Queue List portlet for my $queue ( 'General', $original_test_queue->Name ) { ok( !$m->find_link( text => 'General' ), "no link to $queue ticket search" ); } ok( RT::Test->add_rights( { Principal => $user_f, Right => [qw(ShowTicket)] }, ), 'add global ShowTicket right' ); $m->reload; # Links in Queue List portlet for my $queue ( 'General', $original_test_queue->Name ) { ok( $m->find_link( text => $queue ), "found link to $queue ticket search" ); } } sub new_queue { my $name = shift; my $new_queue = RT::Queue->new(RT->SystemUser); ok($new_queue->Create( Name => $name, Description => "Testing for $name queue" ), "Created queue ".$new_queue->Name); return $new_queue; } sub internal_queues { my $internal_queues = RT::Queues->new(RT->SystemUser); $internal_queues->Limit(FIELD => 'Disabled', VALUE => 0); my $queuelist; while ( my $q = $internal_queues->Next ) { $queuelist->{$q->Id} = $q->Name; } return $queuelist; } # takes a WWW::Mech object and two optional arrayrefs of queue ids and names # compares the list of ids and names to the dropdown of Queues for the New Ticket In form sub check_queues { my ($browser, $queue_id_list, $queue_name_list, $url, $form_name) = @_; $url ||= $baseurl; $browser->get_ok( $url, "Navigated to $url" ); # Get rid of inline-edit forms my ($form) = grep { $_->action !~ m{/Helpers/TicketUpdate} } $browser->all_forms_with_fields('Subject'); ok( $form, "Found form" ); my ( @queue_ids, @queue_names ); if ( !$queue_id_list || @$queue_id_list > 0 ) { ok(my $queuelist = $form->find_input('Queue','option'), "Found queue select"); @queue_ids = $queuelist->possible_values; @queue_names = $queuelist->value_names; } else { ok( !$form->find_input( 'Queue', 'option' ), "No queue select options" ); } my $full_queue_list = internal_queues(); $queue_id_list = [keys %$full_queue_list] unless $queue_id_list; $queue_name_list = [values %$full_queue_list] unless $queue_name_list; is_deeply([sort @queue_ids],[sort @$queue_id_list], "Queue list contains the expected queue ids"); is_deeply([sort @queue_names],[sort @$queue_name_list], "Queue list contains the expected queue names"); } done_testing; rt-5.0.1/t/web/class_create.t000644 000765 000024 00000003417 14005011336 016643 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 13; my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in as root'; my $root = RT::User->new(RT->SystemUser); ok( $root->Load('root'), 'load root user' ); my $class_name = 'test class'; my $class_id; diag "Create a class"; { $m->follow_link( id => 'admin-articles-classes-create'); # Test class form validation $m->submit_form( form_name => 'ModifyClass', fields => { Name => '', }, ); $m->text_contains('Invalid value for Name'); $m->submit_form( form_name => 'ModifyClass', fields => { Name => '0', }, ); $m->text_contains('Invalid value for Name'); $m->submit_form( form_name => 'ModifyClass', fields => { Name => '1', }, ); $m->text_contains('Invalid value for Name'); $m->submit_form( form_name => 'ModifyClass', fields => { Name => $class_name, }, ); $m->content_contains('Object created', 'created class sucessfully' ); # Test validation on updae $m->form_name('ModifyClass'); $m->set_fields( Name => '', ); $m->click_button(value => 'Save Changes'); $m->text_contains('Illegal value for Name'); $m->form_name('ModifyClass'); $m->set_fields( Name => '0', ); $m->click_button(value => 'Save Changes'); $m->text_contains('Illegal value for Name'); $m->form_name('ModifyClass'); $m->set_fields( Name => '1', ); $m->click_button(value => 'Save Changes'); $m->text_contains('Illegal value for Name'); $class_id = $m->form_name('ModifyClass')->value('id'); ok $class_id, "found id of the class in the form, it's #$class_id"; } rt-5.0.1/t/web/rest_user_cf.t000644 000765 000024 00000001240 14005011336 016666 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Interface::REST; use RT::Test tests => undef; my ( $baseurl, $m ) = RT::Test->started_ok; my $cf = RT::Test->load_or_create_custom_field( Name => 'foo', Type => 'Freeform', LookupType => 'RT::User', ); $cf->AddToObject(RT::User->new(RT->SystemUser)); my $root = RT::User->new( RT->SystemUser ); $root->Load('root'); $root->AddCustomFieldValue( Field => 'foo', Value => 'blabla' ); is( $root->FirstCustomFieldValue('foo'), 'blabla', 'cf is set' ); ok( $m->login, 'logged in' ); $m->post( "$baseurl/REST/1.0/show", [ id => 'user/14', ] ); like( $m->content, qr/CF-foo: blabla/, 'found the cf' ); done_testing; rt-5.0.1/t/web/attachment-with-name-0.t000644 000765 000024 00000001225 14005011336 020362 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 8; my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in as root'; use File::Spec; my $file = File::Spec->catfile( RT::Test->temp_directory, 0 ); open my $fh, '>', $file or die $!; print $fh 'foobar'; close $fh; $m->get_ok( '/Ticket/Create.html?Queue=1' ); $m->submit_form( form_number => 3, fields => { Subject => 'test att 0', Content => 'test', Attach => $file }, button => 'SubmitTicket', ); $m->content_like( qr/Ticket \d+ created/i, 'created the ticket' ); $m->follow_link_ok( { url_regex => qr/Attachment\/\d+\/\d+\/0/ } ); $m->content_contains( 'foobar', 'file content' ); rt-5.0.1/t/web/current_user_outdated_email.t000644 000765 000024 00000002242 14005011336 021766 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 39; my ( $url, $m ) = RT::Test->started_ok; $m->login(); my @links = ( '/', '/Ticket/Create.html?Queue=1', '/SelfService/Create.html?Queue=1', '/m/ticket/create?Queue=1' ); my $root = RT::Test->load_or_create_user( Name => 'root' ); ok( $root->id, 'loaded root' ); is( $root->EmailAddress, 'root@localhost', 'default root email' ); for my $link (@links) { $m->get_ok($link); $m->content_contains( '"root@localhost"', "default email in $link" ); } $root->SetEmailAddress('foo@example.com'); is( $root->EmailAddress, 'foo@example.com', 'changed to foo@example.com' ); for my $link (@links) { $m->get_ok($link); $m->content_lacks( '"root@localhost"', "no default email in $link" ); $m->content_contains( '"foo@example.com"', "new email in $link" ); } $root->SetEmailAddress('root@localhost'); is( $root->EmailAddress, 'root@localhost', 'changed back to root@localhost' ); for my $link (@links) { $m->get_ok($link); $m->content_lacks( '"foo@example.com"', "no previous email in $link" ); $m->content_contains( '"root@localhost"', "default email in $link" ); } rt-5.0.1/t/web/cf_textarea.t000644 000765 000024 00000010255 14005011336 016476 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 'no_declare'; my $content = join ' ', ('The quick brown fox jumps over the lazy dog.') x 5; $content = join "\n\n", $content, $content, $content; my ($base, $m) = RT::Test->started_ok; $m->login; my $ticket = RT::Test->create_ticket( Queue => 1, Subject => 'a test ticket', ); ok $ticket && $ticket->id, "Created ticket"; my $EditUrl = "/Ticket/Modify.html?id=" . $ticket->id; my $cfs = { area => { type => 'Text', name => 'TheTextarea', }, text => { type => 'FreeformSingle', name => 'TheControlField', }, zero => { type => 'FreeformSingle', name => 'Zero', }, }; while ( my( $label, $data ) = each %$cfs ) { my $cf = $data->{obj} = RT::Test->load_or_create_custom_field( Name => $data->{name}, Type => $data->{type}, Queue => 0, LookupType => 'RT::Queue-RT::Ticket', ); ok $cf && $cf->id, "Created $data->{type} CF"; # get cf input field name $data->{input} = RT::Interface::Web::GetCustomFieldInputName( Object => $ticket, CustomField => $cf, ); } # open ticket "Basics" page $m->get_ok($EditUrl, "Fetched $EditUrl"); $m->content_contains($_->{name} . ':') for ( values %$cfs ); $m->submit_form_ok({ with_fields => { $cfs->{area}{input} => $content, $cfs->{area}{input} . '-Magic' => "1", $cfs->{text}{input} => 'value a', $cfs->{text}{input} . '-Magic' => "1", $cfs->{zero}{input} => '0', $cfs->{zero}{input} . '-Magic' => "1", }, }, 'submitted form to initially set CFs'); $m->content_contains('
    • TheControlField value a added
    • '); $m->content_contains("
    • TheTextarea $content added
    • ", 'content found'); $m->content_contains("
    • Zero 0 added
    • ", 'zero field found'); # http://issues.bestpractical.com/Ticket/Display.html?id=30378 # #30378: RT 4.2.6 - Very long text fields get updated even when they haven't changed $m->submit_form_ok({ with_fields => { $cfs->{text}{input} => 'value b', $cfs->{text}{input} . '-Magic' => "1", }, }, 'submitted form to initially set CFs'); $m->content_contains('
    • TheControlField value a changed to value b
    • '); $m->content_lacks("
    • TheTextarea $content changed to $content
    • ", 'textarea wasnt updated'); # http://issues.bestpractical.com/Ticket/Display.html?id=32440 # #32440: Spurious "CF changed from 0 to 0" $m->content_lacks("
    • Zero 0 changed to 0
    • ", "Zero wasn't updated"); my $new_content = 'The quick brown fox jumps over the lazy dog.'; $m->submit_form_ok({ with_fields => { $cfs->{area}{input} => $new_content, $cfs->{area}{input} . '-Magic' => "1", }, }, 'submitted form to update textarea CF'); $m->content_contains( "
    • TheTextarea $content changed to $new_content
    • ", 'textarea was updated' ); my $newer_content = 'The quick yellow fox jumps over the lazy dog.'; $m->submit_form_ok({ with_fields => { $cfs->{area}{input} => $newer_content, $cfs->{area}{input} . '-Magic' => "1", }, }, 'submitted form to update textarea CF'); $m->content_contains( "
    • TheTextarea $new_content changed to $newer_content
    • ", 'textarea was updated' ); my $txn = $ticket->Transactions->Last; $m->get_ok( '/Helpers/TextDiff?TransactionId=' . $txn->id ); $m->content_like( qr{brown\s*yellow\s*}, 'text diff has the brown => yellow change' ); $m->back; $m->submit_form_ok({ with_fields => { $cfs->{area}{input} => '', $cfs->{area}{input} . '-Magic' => "1", }, }, 'submitted form to update textarea CF'); $m->content_contains( "
    • $newer_content is no longer a value for custom field TheTextarea
    • ", 'textarea was deleted' ); $m->follow_link_ok( { text => 'Display' } ); $content =~ s!\n+!!g; $m->text_like( qr/TheTextarea\sadded.+\Q$content\E.+ TheTextarea\schanged.+From:\Q$content\ETo:\Q$new_content\E.+ TheTextarea\schanged.+From:\Q$new_content\ETo:\Q$newer_content\E.+ TheTextarea\sdeleted.+\Q$newer_content\E/xs, 'textarea change details' ); done_testing; rt-5.0.1/t/web/queue_create.t000644 000765 000024 00000003406 14005011336 016660 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 13; my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in as root'; my $root = RT::User->new(RT->SystemUser); ok( $root->Load('root'), 'load root user' ); my $queue_name = 'test queue'; my $queue_id; diag "Create a queue"; { $m->follow_link( id => 'admin-queues-create'); # Test queue form validation $m->submit_form( form_name => 'ModifyQueue', fields => { Name => '', }, ); $m->text_contains('Queue name is required'); $m->submit_form( form_name => 'ModifyQueue', fields => { Name => '0', }, ); $m->text_contains("'0' is not a valid name"); $m->submit_form( form_name => 'ModifyQueue', fields => { Name => '1', }, ); $m->text_contains("'1' is not a valid name"); $m->submit_form( form_name => 'ModifyQueue', fields => { Name => $queue_name, }, ); $m->content_contains('Queue created', 'created queue sucessfully' ); # Test validation on update $m->form_name('ModifyQueue'); $m->set_fields( Name => '', ); $m->click_button(value => 'Save Changes'); $m->content_contains('Illegal value for Name'); $m->form_name('ModifyQueue'); $m->set_fields( Name => '0', ); $m->click_button(value => 'Save Changes'); $m->content_contains('Illegal value for Name'); $m->form_name('ModifyQueue'); $m->set_fields( Name => '1', ); $m->click_button(value => 'Save Changes'); $m->content_contains('Illegal value for Name'); $queue_id = $m->form_name('ModifyQueue')->value('id'); ok $queue_id, "found id of the queue in the form, it's #$queue_id"; } rt-5.0.1/t/web/language_update.t000644 000765 000024 00000001335 14005011336 017335 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 9; my ( $url, $m ) = RT::Test->started_ok; ok( $m->login(), 'logged in' ); $m->follow_link_ok({text => 'About me'}); $m->form_with_fields('Lang'); $m->field(Lang => 'zh_TW'); $m->submit; $m->text_contains(Encode::decode("UTF-8","é›»å­éƒµä»¶ä¿¡ç®±"), "successfully updated to zh_TW"); $m->text_contains(Encode::decode("UTF-8","使用語言 的值從 (ç„¡) 改為 'zh_TW'"), "when updating to language zh_TW, results are in zh_TW"); $m->form_with_fields('Lang'); $m->field(Lang => 'en_us'); $m->submit; $m->text_contains("Email", "successfully updated to en_us"); $m->text_contains("Lang changed from 'zh_TW' to 'en_us'", "when updating to language en_us, results are in en_us"); rt-5.0.1/t/web/scrips.t000644 000765 000024 00000024067 14005011336 015522 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; RT->Config->Set( UseTransactionBatch => 1 ); # TODO: # Test the rest of the conditions. # Test actions. # Test templates? # Test cleanup scripts. my $queue_g = RT::Test->load_or_create_queue( Name => 'General' ); ok $queue_g && $queue_g->id, 'loaded or created queue'; my $queue_r = RT::Test->load_or_create_queue( Name => 'Regression' ); ok $queue_r && $queue_r->id, 'loaded or created queue'; my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, "logged in"; $m->follow_link_ok({id => 'admin-global-scrips-create'}); sub prepare_code_with_value { my $value = shift; # changing the ticket is an easy scrip check for a test return '$self->TicketObj->SetSubject(' . '$self->TicketObj->Subject . ' . '"|" . ' . $value . ')'; } { # preserve order for checking the subject string later my @values_for_actions; my $conds = RT::ScripConditions->new(RT->SystemUser); foreach my $cond_value ('On Forward', 'On Forward Ticket', 'On Forward Transaction') { $conds->Limit( FIELD => 'name', VALUE => $cond_value, ENTRYAGGREGATOR => 'OR', ); } while (my $rec = $conds->Next) { push @values_for_actions, [$rec->Id, '"' . $rec->Name . '"']; } @values_for_actions = sort { $a->[0] cmp $b->[0] } @values_for_actions; foreach my $data (@values_for_actions) { my ($condition, $prepare_code_value) = @$data; diag "Create Scrip (Cond #$condition)" if $ENV{TEST_VERBOSE}; $m->follow_link_ok({id => 'admin-global-scrips-create'}); my $prepare_code = prepare_code_with_value($prepare_code_value); $m->form_name('CreateScrip'); $m->set_fields( 'ScripCondition' => $condition, 'ScripAction' => 'User Defined', 'Template' => 'Blank', 'CustomPrepareCode' => $prepare_code, ); $m->click('Create'); $m->content_like(qr{Scrip Created}); } my $ticket_obj = RT::Test->create_ticket( Subject => 'subject', Content => 'stuff', Queue => 1, ); my $ticket = $ticket_obj->id; $m->goto_ticket($ticket); $m->follow_link_ok( { id => 'page-actions-forward' }, 'follow 1st Forward to forward ticket' ); diag "Forward Ticket" if $ENV{TEST_VERBOSE}; $m->submit_form( form_name => 'ForwardMessage', fields => { To => 'rt-test@example.com, rt-to@example.com', }, button => 'ForwardAndReturn' ); $m->text_contains("#${ticket}: subject|On Forward|On Forward Ticket"); diag "Forward Transaction" if $ENV{TEST_VERBOSE}; # get the first transaction on the ticket my ($transaction) = $ticket_obj->Transactions->First->id; $m->get( "$baseurl/Ticket/Forward.html?id=1&QuoteTransaction=$transaction" ); $m->submit_form( form_name => 'ForwardMessage', fields => { To => 'rt-test@example.com, rt-to@example.com', }, button => 'ForwardAndReturn' ); $m->text_contains("#${ticket}: subject|On Forward|On Forward Ticket|On Forward|On Forward Transaction"); RT::Test->clean_caught_mails; } note "check basics in scrip's admin interface"; { $m->follow_link_ok( { id => 'admin-global-scrips-create' } ); ok $m->form_name('CreateScrip'); is $m->value_name('Description'), '', 'empty value'; is $m->value_name('ScripAction'), '-', 'empty value'; is $m->value_name('ScripCondition'), '-', 'empty value'; is $m->value_name('Template'), '-', 'empty value'; $m->field('Description' => 'test'); $m->click('Create'); $m->content_contains("Action is mandatory argument"); ok $m->form_name('CreateScrip'); is $m->value_name('Description'), 'test', 'value stays on the page'; $m->select('ScripAction' => 'Notify Ccs'); $m->click('Create'); $m->content_contains("Template is mandatory argument"); ok $m->form_name('CreateScrip'); is $m->value_name('Description'), 'test', 'value stays on the page'; is $m->value_name('ScripAction'), 'Notify Ccs', 'value stays on the page'; $m->select('Template' => 'Blank'); $m->click('Create'); $m->content_contains("Condition is mandatory argument"); ok $m->form_name('CreateScrip'); is $m->value_name('Description'), 'test', 'value stays on the page'; is $m->value_name('ScripAction'), 'Notify Ccs', 'value stays on the page'; $m->select('ScripCondition' => 'On Close'); $m->click('Create'); $m->content_contains("Scrip Created"); ok $m->form_name('ModifyScrip'); is $m->value_name('Description'), 'test', 'correct value'; is $m->value_name('ScripCondition'), 'On Close', 'correct value'; is $m->value_name('ScripAction'), 'Notify Ccs', 'correct value'; is $m->value_name('Template'), 'Blank', 'correct value'; $m->field('Description' => 'test test'); $m->click('Update'); # regression $m->content_lacks("Template is mandatory argument"); ok $m->form_name('ModifyScrip'); is $m->value_name('Description'), 'test test', 'correct value'; $m->content_contains("Description changed from", "found action result message"); } note "check application in admin interface"; { $m->follow_link_ok({ id => 'admin-global-scrips-create' }); $m->submit_form_ok({ with_fields => { Description => "testing application", ScripCondition => "On Create", ScripAction => "Open Tickets", Template => "Blank", }, button => 'Create', }, "created scrip"); $m->content_contains("Scrip Created", "found result message"); my ($sid) = ($m->content =~ /Modify scrip #(\d+)/); ok $sid, "found scrip id on the page"; RT::Test->object_scrips_are($sid, [0]); $m->follow_link_ok({ id => 'page-applies-to' }); ok $m->form_name("AddRemoveScrip"), "found form"; $m->tick("RemoveScrip-$sid", 0); $m->click_ok("Update", "update scrip application"); RT::Test->object_scrips_are($sid, []); ok $m->form_name("AddRemoveScrip"), "found form"; $m->tick("AddScrip-$sid", 0); $m->tick("AddScrip-$sid", $queue_g->id); $m->click_ok("Update", "update scrip application"); RT::Test->object_scrips_are($sid, [0], [$queue_g->id, $queue_r->id]); } note "check templates in scrip's admin interface"; { my $template = RT::Template->new( RT->SystemUser ); my ($status, $msg) = $template->Create( Queue => $queue_g->id, Name => 'foo' ); ok $status, 'created a template'; my $templates = RT::Templates->new( RT->SystemUser ); $templates->LimitToGlobal; my @default = ( '', map $_->Name, @{$templates->ItemsArrayRef} ); $m->follow_link_ok( { id => 'admin-global-scrips-create' } ); ok $m->form_name('CreateScrip'); my @templates = ($m->find_all_inputs( type => 'option', name => 'Template' ))[0] ->possible_values; is_deeply([sort @templates], [sort @default]); $m->follow_link_ok( { id => 'admin-queues' } ); $m->follow_link_ok( { text => 'General' } ); $m->follow_link_ok( { id => 'page-scrips-create' } ); ok $m->form_name('CreateScrip'); @templates = ($m->find_all_inputs( type => 'option', name => 'Template' ))[0] ->possible_values; is_deeply([sort @templates], [sort @default, 'foo']); note "make sure we can not apply scrip to queue without required template"; $m->field('Description' => 'test template'); $m->select('ScripCondition' => 'On Close'); $m->select('ScripAction' => 'Notify Ccs'); $m->select('Template' => 'foo'); $m->click('Create'); $m->content_contains("Scrip Created"); $m->follow_link_ok( { id => 'page-applies-to' } ); my ($id) = ($m->content =~ /Modify associated objects for scrip #(\d+)/); $m->form_name('AddRemoveScrip'); $m->tick('AddScrip-'.$id, $queue_r->id); $m->click('Update'); $m->content_like(qr{No template foo in queue Regression or global}); note "unapply the scrip from any queue"; $m->form_name('AddRemoveScrip'); $m->tick('RemoveScrip-'.$id, $queue_g->id); $m->click('Update'); $m->content_like(qr{Object deleted}); note "you can pick any template"; $m->follow_link_ok( { id => 'page-basics' } ); ok $m->form_name('ModifyScrip'); @templates = ($m->find_all_inputs( type => 'option', name => 'Template' ))[0] ->possible_values; is_deeply( [sort @templates], [sort do { my $t = RT::Templates->new( RT->SystemUser ); $t->UnLimit; ('', $t->DistinctFieldValues('Name')) }], ); note "go to apply page and apply with template change"; $m->follow_link_ok( { id => 'page-applies-to' } ); $m->form_name('AddRemoveScrip'); $m->field('Template' => 'blank'); $m->tick('AddScrip-'.$id, $queue_g->id); $m->tick('AddScrip-'.$id, $queue_r->id); $m->click('Update'); $m->content_contains("Template: Template changed from "); $m->content_contains("Object created"); } note "apply scrip in different stage to different queues"; { $m->follow_link_ok( { id => 'admin-queues' } ); $m->follow_link_ok( { text => 'General' } ); $m->follow_link_ok( { id => 'page-scrips-create'}); ok $m->form_name('CreateScrip'); $m->field('Description' => 'test stage'); $m->select('ScripCondition' => 'On Close'); $m->select('ScripAction' => 'Notify Ccs'); $m->select('Template' => 'Blank'); $m->click('Create'); $m->content_contains("Scrip Created"); my ($sid) = ($m->content =~ /Modify scrip #(\d+)/); ok $sid, "found scrip id on the page"; $m->follow_link_ok({ text => 'Applies to' }); ok $m->form_name('AddRemoveScrip'); $m->select('Stage' => 'Batch'); $m->tick( "AddScrip-$sid" => $queue_r->id ); $m->click('Update'); $m->content_contains("Object created"); $m->follow_link_ok({ text => 'General' }); $m->follow_link_ok({ id => 'page-scrips' }); my (@matches) = $m->content =~ /test stage/g; # regression is scalar @matches, 1, 'scrip mentioned only once'; } done_testing; rt-5.0.1/t/web/self_service.t000644 000765 000024 00000006714 14005011336 016667 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef, config => 'Set( $ShowUnreadMessageNotifications, 1 );' ; my ($url, $m) = RT::Test->started_ok; my $user_a = RT::Test->load_or_create_user( Name => 'user_a', Password => 'password', EmailAddress => 'user_a@example.com', Privileged => 0, ); ok( $user_a && $user_a->id, 'loaded or created user' ); ok( ! $user_a->Privileged, 'user is not privileged' ); # Load Cc group my $Cc = RT::System->RoleGroup( 'Cc' ); ok($Cc->id); RT::Test->add_rights( { Principal => $Cc, Right => ['ShowTicket'] } ); my ($ticket) = RT::Test->create_ticket( Queue => 'General', Subject => 'test subject', Cc => 'user_a@example.com', ); my @results = $ticket->Correspond( Content => 'sample correspondence' ); ok( $m->login('user_a' => 'password'), 'unprivileged user logged in' ); $m->get_ok( '/SelfService/Display.html?id=' . $ticket->id, 'got selfservice display page' ); my $title = '#' . $ticket->id . ': test subject'; $m->title_is( $title ); $m->content_contains( "

      $title

      ", "contains

      $title

      " ); # $ShowUnreadMessageNotifications tests: $m->content_contains( "There are unread messages on this ticket." ); # mark the message as read $m->follow_link_ok( { text => 'Mark as Seen' }, 'followed mark as seen link' ); $m->content_contains( "

      $title

      ", "contains

      $title

      " ); $m->content_lacks( "There are unread messages on this ticket." ); diag 'Test $SelfServiceUserPrefs config'; { # Verify the $SelfServiceUserPrefs config option renders the correct display at # /SelfService/Prefs.html for each of the available options is( RT->Config->Get( 'SelfServiceUserPrefs' ), 'edit-prefs', '$SelfServiceUserPrefs is set to "edit-prefs" by default' ); for my $config ( 'edit-prefs', 'view-info', 'edit-prefs-view-info', 'full-edit' ) { RT::Test->stop_server; RT->Config->Set( SelfServiceUserPrefs => $config ); ( $url, $m ) = RT::Test->started_ok; ok( $m->login('user_a' => 'password'), 'unprivileged user logged in' ); $m->get_ok( '/SelfService/Prefs.html'); if ( $config eq 'edit-prefs' ) { $m->content_lacks( 'Nickname', "'Edit-Prefs' option does not contain full user info" ); $m->content_contains( 'content_lacks( 'name="NickName" value=""', "'View-Info' option contains no input fields for full user info" ); $m->content_contains( "Nickname:", "'View-Info' option contains full user info" ); } elsif ( $config eq 'edit-prefs-view-info' ) { $m->content_contains( 'content_contains( 'Nickname:', "'Edit-Prefs-View-Info' option contains full user info" ); $m->content_lacks( 'name="NickName" value=""', "'Edit-Prefs-View-Info' option contains no input fields for full user info" ); } else { RT::Test->add_rights( { Principal => $user_a, Right => ['ModifySelf'] } ); my $nickname = 'user_a_nickname'; $m->submit_form_ok({ form_name => 'EditAboutMe', with_fields => { NickName => $nickname,} }, 'Form submitted'); $m->text_contains("NickName changed from (no value) to '$nickname'", "NickName updated"); } } } # TODO need more SelfService tests done_testing(); rt-5.0.1/t/web/csrf.t000644 000765 000024 00000022571 14005011336 015152 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my $ticket = RT::Ticket->new(RT::CurrentUser->new('root')); my ($ok, $msg) = $ticket->Create(Queue => 1, Owner => 'nobody', Subject => 'bad music'); ok($ok); my $other = RT::Test->load_or_create_queue(Name => "Other queue", Disabled => 0); my $other_queue_id = $other->id; my ($baseurl, $m) = RT::Test->started_ok; my $test_page = "/Ticket/Create.html?Queue=1"; my $test_path = "/Ticket/Create.html"; ok $m->login, 'logged in'; # valid referer $m->add_header(Referer => $baseurl); $m->get_ok($test_page); $m->content_lacks("Possible cross-site request forgery"); $m->title_is('Create a new ticket in General'); # off-site referer BUT provides auth $m->add_header(Referer => 'http://example.net'); $m->get_ok("$test_page&user=root&pass=password"); $m->content_lacks("Possible cross-site request forgery"); $m->title_is('Create a new ticket in General'); # explicitly no referer BUT provides auth $m->add_header(Referer => undef); $m->get_ok("$test_page&user=root&pass=password"); $m->content_lacks("Possible cross-site request forgery"); $m->title_is('Create a new ticket in General'); # CSRF parameter whitelist tests my $searchBuildPath = '/Search/Build.html'; # CSRF whitelist for /Search/Build.html param SavedSearchLoad $m->add_header(Referer => undef); $m->get_ok("$searchBuildPath?SavedSearchLoad=foo"); $m->content_lacks('Possible cross-site request forgery'); $m->title_is('Query Builder'); # CSRF pass for /Search/Build.html no param $m->add_header(Referer => undef); $m->get_ok("$searchBuildPath"); $m->content_lacks('Possible cross-site request forgery'); $m->title_is('Query Builder'); # CSRF fail for /Search/Build.html arbitrary param only $m->add_header(Referer => undef); $m->get_ok("$searchBuildPath?foo=bar"); $m->content_contains('Possible cross-site request forgery'); $m->title_is('Possible cross-site request forgery'); # CSRF fail for /Search/Build.html arbitrary param with SavedSearchLoad $m->add_header(Referer => undef); $m->get_ok("$searchBuildPath?SavedSearchLoad=foo&foo=bar"); $m->content_contains('Possible cross-site request forgery'); $m->title_is('Possible cross-site request forgery'); # CSRF pass for /Search/Build.html param NewQuery $m->add_header(Referer => undef); $m->get_ok("$searchBuildPath?NewQuery=1"); $m->content_lacks('Possible cross-site request forgery'); $m->title_is('Query Builder'); # CSRF pass for /Ticket/Update.html items in ticket action menu $m->add_header(Referer => undef); $m->get_ok('/Ticket/Update.html?id=1&Action=foo'); $m->content_lacks('Possible cross-site request forgery'); # CSRF pass for /Ticket/Update.html reply to message in ticket history $m->add_header(Referer => undef); $m->get_ok('/Ticket/Update.html?id=1&QuoteTransaction=1&Action=Reply'); $m->content_lacks('Possible cross-site request forgery'); # CSRF pass for /Articles/Article/ExtractIntoClass.html # Action->Extract Article on ticket menu $m->add_header(Referer => undef); $m->get_ok('/Articles/Article/ExtractIntoClass.html?Ticket=1'); $m->content_lacks('Possible cross-site request forgery'); # now send a referer from an attacker $m->add_header(Referer => 'http://example.net'); $m->get_ok($test_page); $m->content_contains("Possible cross-site request forgery"); $m->content_contains("If you really intended to visit $baseurl/Ticket/Create.html"); $m->content_contains("the Referrer header supplied by your browser (example.net:80) is not allowed"); $m->title_is('Possible cross-site request forgery'); # reinstate mech's usual header policy $m->delete_header('Referer'); # clicking the resume request button gets us to the test page $m->follow_link(text_regex => qr{resume your request}); $m->content_lacks("Possible cross-site request forgery"); like($m->response->request->uri, qr{^http://[^/]+\Q$test_path\E\?CSRF_Token=\w+$}); $m->title_is('Create a new ticket in General'); # try a whitelisted argument from an attacker $m->add_header(Referer => 'http://example.net'); $m->get_ok("/Ticket/Display.html?id=1"); $m->content_lacks("Possible cross-site request forgery"); $m->title_is('#1: bad music'); # now a non-whitelisted argument $m->get_ok("/Ticket/Display.html?id=1&Action=Take"); $m->content_contains("Possible cross-site request forgery"); $m->content_contains("If you really intended to visit $baseurl/Ticket/Display.html"); $m->content_contains("the Referrer header supplied by your browser (example.net:80) is not allowed"); $m->title_is('Possible cross-site request forgery'); $m->delete_header('Referer'); $m->follow_link(text_regex => qr{resume your request}); $m->content_lacks("Possible cross-site request forgery"); like($m->response->request->uri, qr{^http://[^/]+\Q/Ticket/Display.html}); $m->title_is('#1: bad music'); $m->content_contains('Owner changed from Nobody to root'); # force mech to never set referer $m->add_header(Referer => undef); $m->get_ok($test_page); $m->content_contains("Possible cross-site request forgery"); $m->content_contains("If you really intended to visit $baseurl/Ticket/Create.html"); $m->content_contains("your browser did not supply a Referrer header"); $m->title_is('Possible cross-site request forgery'); $m->follow_link(text_regex => qr{resume your request}); $m->content_lacks("Possible cross-site request forgery"); is($m->response->redirects, 0, "no redirection"); like($m->response->request->uri, qr{^http://[^/]+\Q$test_path\E\?CSRF_Token=\w+$}); $m->title_is('Create a new ticket in General'); # try sending the wrong csrf token, then the right one $m->add_header(Referer => undef); $m->get_ok($test_page); $m->content_contains("Possible cross-site request forgery"); $m->content_contains("If you really intended to visit $baseurl/Ticket/Create.html"); $m->content_contains("your browser did not supply a Referrer header"); $m->title_is('Possible cross-site request forgery'); # Sending a wrong CSRF is just a normal request. We'll make a request # with just an invalid token, which means no Queue=x so default queue used my $link = $m->find_link(text_regex => qr{resume your request}); (my $broken_url = $link->url) =~ s/(CSRF_Token)=\w+/$1=crud/; $m->get($broken_url); $m->content_like(qr/Create\sa\snew\sticket\sin\sGeneral/); $m->title_is('Create a new ticket in General'); # The token doesn't work for other pages, or other arguments to the same page. $m->add_header(Referer => undef); $m->get_ok($test_page); $m->content_contains("Possible cross-site request forgery"); my ($token) = $m->content =~ m{CSRF_Token=(\w+)}; $m->add_header(Referer => undef); $m->get_ok("/Admin/Queues/Modify.html?id=new&Name=test&CSRF_Token=$token"); $m->content_contains("Possible cross-site request forgery"); $m->content_contains("If you really intended to visit $baseurl/Admin/Queues/Modify.html"); $m->content_contains("your browser did not supply a Referrer header"); $m->title_is('Possible cross-site request forgery'); $m->follow_link(text_regex => qr{resume your request}); $m->content_lacks("Possible cross-site request forgery"); $m->title_is('Configuration for queue test'); # Try the same page, but different query parameters, which are blatted by the token $m->get_ok("/Ticket/Create.html?Queue=$other_queue_id&CSRF_Token=$token"); $m->content_lacks("Possible cross-site request forgery"); $m->title_is('Create a new ticket in General'); is($m->form_name('TicketCreate')->value('Queue'), 1, 'Queue selection dropdown populated and pre-selected'); # Ensure that file uploads work across the interstitial $m->delete_header('Referer'); $m->get_ok($test_page); $m->content_contains("Create a new ticket in General", 'ticket create page'); $m->form_name('TicketCreate'); $m->field('Subject', 'Attachments test'); my $logofile = "$RT::StaticPath/images/bpslogo.png"; open LOGO, "<", $logofile or die "Can't open logo file: $!"; binmode LOGO; my $logo_contents = do {local $/; }; close LOGO; $m->field('Attach', $logofile); # Lose the referer before the POST $m->add_header(Referer => undef); $m->click('SubmitTicket'); $m->content_contains("Possible cross-site request forgery"); $m->content_contains("If you really intended to visit $baseurl/Ticket/Create.html"); $m->follow_link(text_regex => qr{resume your request}); ok( $m->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page has the file link' ); $m->follow_link_ok( { url_regex => qr/Attachment\/\d+\/\d+\/bpslogo\.png/ } ); is($m->content, $logo_contents, "Binary content matches"); # now try self-service with CSRF my $user = RT::User->new(RT->SystemUser); $user->Create(Name => "SelfService", Password => "chops", Privileged => 0); $m = RT::Test::Web->new; $m->get_ok("$baseurl/index.html?user=SelfService&pass=chops"); $m->title_is("Open tickets", "got self-service interface"); $m->content_contains("My open tickets", "got self-service interface"); # post without referer $m->add_header(Referer => undef); $m->get_ok("/SelfService/Create.html?Queue=1"); $m->content_contains("Possible cross-site request forgery"); $m->content_contains("If you really intended to visit $baseurl/SelfService/Create.html"); $m->content_contains("your browser did not supply a Referrer header"); $m->title_is('Possible cross-site request forgery'); $m->follow_link(text_regex => qr{resume your request}); $m->content_lacks("Possible cross-site request forgery"); is($m->response->redirects, 0, "no redirection"); like($m->response->request->uri, qr{^http://[^/]+\Q/SelfService/Create.html\E\?CSRF_Token=\w+$}); $m->title_is('Create a ticket in #1'); done_testing; rt-5.0.1/t/web/compilation_errors.t000644 000765 000024 00000004332 14005011336 020122 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Test::More; use File::Find; use HTTP::Status qw(); BEGIN { $ENV{RT_TEST_DEVEL} ||= 1; sub wanted { -f && /\.html$/ && $_ !~ /Logout.html$/ && $File::Find::dir !~ /RichText/; } my $tests = 7; find( sub { wanted() and $tests += 4 }, 'share/html/', 'share/html/SelfService/' ); plan tests => $tests + 1; # plus one for warnings check } use HTTP::Request::Common; use HTTP::Cookies; use LWP; my $cookie_jar = HTTP::Cookies->new; use RT::Test; my ($baseurl, $agent) = RT::Test->started_ok; # give the agent a place to stash the cookies $agent->cookie_jar($cookie_jar); # get the top page my $url = $agent->rt_base_url; $agent->get($url); is($agent->status, HTTP::Status::HTTP_OK, "Loaded a page"); # follow the link marked "Login" $agent->login(root => 'password'); is($agent->status, HTTP::Status::HTTP_OK, "Fetched the page ok"); $agent->content_contains('Logout', "Found a logout link"); find ( { wanted => sub { wanted() and test_get($agent, $File::Find::name) }, no_chdir => 1 } , 'share/html/'); my $customer = RT::Test->load_or_create_user( Name => 'user1', Password => 'password', EmailAddress => 'user1@example.com', Privileged => 0, ); # Classic permission settings RT::Test->add_rights( { Principal => 'Everyone', Right => [qw(CreateTicket SeeQueue)] } ); $agent->login( user1 => 'password', logout => 1 ); find( { wanted => sub { wanted() and test_get( $agent, $File::Find::name ) }, no_chdir => 1 }, 'share/html/SelfService' ); # We expect to spew a lot of warnings; toss them away $agent->get_warnings; sub test_get { my $agent = shift; my $file = shift; $file =~ s#^share/html/##; diag( "testing $url/$file" ); $agent->get("$url/$file"); isnt($agent->status, HTTP::Status::HTTP_INTERNAL_SERVER_ERROR, "Loaded $file"); $agent->content_lacks('Not logged in', "Still logged in for $file"); $agent->content_lacks('raw error', "Didn't get a Mason compilation error on $file") or do { if (my ($error) = $agent->content =~ /
      (.*?line.*?)$/s) {
                      diag "$file: $error";
                  }
              };
              $agent->content_contains('', "Found HTML end tag");
      }
      
      rt-5.0.1/t/web/session.t000644 000765 000024 00000004422 14005011336 015673 0ustar00sunnavystaff000000 000000 
      use strict;
      use warnings;
      
      use RT::Test tests => undef;
      
      plan skip_all => 'SQLite has shared file sessions' if RT->Config->Get('DatabaseType') eq 'SQLite';
      
      # Web server hangs when processing the same session row after tied
      # %session on Oracle with non-inline web servers :/
      # Use file session instead for now.
      if ( RT->Config->Get('DatabaseType') eq 'Oracle' && ( $ENV{'RT_TEST_WEB_HANDLER'} || '' ) ne 'inline' ) {
          RT->Config->Set( 'WebSessionClass', 'Apache::Session::File' );
      }
      
      my ($baseurl, $agent) = RT::Test->started_ok;
      my $url = $agent->rt_base_url;
      
      diag "Test server running at $baseurl";
      
      # get the top page
      {
          $agent->get($url);
          is ($agent->status, 200, "Loaded a page");
      }
      
      # test a login
      {
          $agent->login('root' => 'password');
          # the field isn't named, so we have to click link 0
          is( $agent->status, 200, "Fetched the page ok");
          $agent->content_contains("Logout", "Found a logout link");
      }
      
      my ($session_id) = $agent->cookie_jar->as_string =~ /RT_SID_[^=]+=(\w+);/;
      
      diag 'Load session for root user';
      my %session;
      tie %session, 'RT::Interface::Web::Session', $session_id;
      is ( $session{'_session_id'}, $session_id, 'Got session id ' . $session_id );
      is ( $session{'CurrentUser'}->Name, 'root', 'Session is for root user' );
      
      diag 'Test queues cache';
      my $user_id = $session{'CurrentUser'}->Id;
      ok ( $session{'SelectObject---RT::Queue---' . $user_id . '---CreateTicket---0'}, 'Queues cached for create ticket');
      is ( $session{'SelectObject---RT::Queue---' . $user_id . '---CreateTicket---0'}{'objects'}->[0]{'Name'},
          'General', 'General queue is in cached list' );
      
      my $last_updated = $session{'SelectObject---RT::Queue---' . $user_id . '---CreateTicket---0'}{'lastupdated'};
      ok( $last_updated, "Got a lastupdated timestamp of $last_updated");
      
      untie(%session);
      # Wait for 1 sec so we can confirm lastupdated doesn't change
      sleep 1;
      $agent->get($url);
      is ($agent->status, 200, "Loaded a page");
      
      tie %session, 'RT::Interface::Web::Session', $session_id;
      is ( $session{'_session_id'}, $session_id, 'Got session id ' . $session_id );
      is ( $session{'CurrentUser'}->Name, 'root', 'Session is for root user' );
      is ($last_updated, $session{'SelectObject---RT::Queue---' . $user_id . '---CreateTicket---0'}{'lastupdated'},
          "lastupdated is still $last_updated");
      
      done_testing;
      rt-5.0.1/t/web/asset_create.t000644 000765 000024 00000005714 14005011336 016657 0ustar00sunnavystaff000000 000000 use strict;
      use warnings;
      
      use RT::Test::Assets tests => undef;
      use RT::Lifecycle;
      
      # populate lifecycles
      my $lifecycles = RT->Config->Get('Lifecycles');
      RT->Config->Set(
          Lifecycles => %{$lifecycles},
          foo        => {
              type => 'asset',
              initial  => ['new'],
              active   => ['tracked'],
              inactive => ['retired'],
              defaults => { on_create => 'new', },
          },
      );
      RT::Lifecycle->FillCache();
      
      RT->Config->Set( DefaultCatalog => 'Default catalog' );
      
      # populate test catalogs
      my $catalog_1 = RT::Test::Assets->load_or_create_catalog( Name => 'Default catalog' );
      ok( $catalog_1, 'created catalog 1' );
      my $catalog_2 = RT::Test::Assets->load_or_create_catalog( Name => 'Another catalog', Lifecycle => 'foo' );
      ok( $catalog_2, 'created catalog 2 id:' . $catalog_2->id );
      
      my ( $baseurl, $m ) = RT::Test->started_ok;
      ok $m->login;
      
      # set up custom field on catalog 2
      my $cf = RT::Test::Assets::create_cf( Name => 'test_cf', Catalog => $catalog_2->Name, Type => 'FreeformSingle' );
      $cf->AddToObject( $catalog_2 );
      ok( $cf, "custom field created (id:" . $cf->Id . ")" );
      my $cf_form_id    = 'Object-RT::Asset--CustomField-' . $cf->Id . '-Value';
      my $cf_test_value = "some string for test_cf $$";
      
      # load initial asset create page without specifying catalog
      # should have default catalog with no custom fields
      note('load create asset page with defaults');
      $m->get_ok('/Asset/Create.html?');
      
      ok( !$m->form_name('CreateAsset')->find_input($cf_form_id), 'custom field not present' );
      is( $m->form_name('CreateAsset')->value('Catalog'), $catalog_1->id, 'Catalog selection dropdown populated and pre-selected' );
      is( $m->form_name('CreateAsset')->value('Status'), 'new', 'Status selection dropdown populated and pre-selected' );
      
      # test asset creation on reload from selected catalog, specifying catalog with custom fields
      note('reload asset create page with selected catalog');
      $m->get_ok( '/Asset/Create.html?Catalog=' . $catalog_2->id, 'go to asset create page' );
      
      is( $m->form_name('CreateAsset')->value('Catalog'), $catalog_2->id, 'Catalog selection dropdown populated and pre-selected' );
      ok( $m->form_name('CreateAsset')->find_input($cf_form_id), 'custom field present' );
      is( $m->form_name('CreateAsset')->value($cf_form_id), '', 'custom field present and empty' );
      
      my $form         = $m->form_name('CreateAsset');
      my $status_input = $form->find_input('Status');
      is_deeply(
          [ $status_input->possible_values ],
          [ 'new', 'tracked', 'retired' ],
          'status selectbox shows custom lifecycle for queue'
      );
      note('submit populated form');
      $m->submit_form( fields => { Name => 'asset foo', 'Catalog' => $catalog_2->id, $cf_form_id => $cf_test_value } );
      $m->text_contains( 'test_cf',      'custom field populated in display' );
      $m->text_contains( $cf_test_value, 'custom field populated in display' );
      
      my $asset = RT::Test::Assets->last_asset;
      ok( $asset->id, 'asset is created' );
      is( $asset->CatalogObj->id, $catalog_2->id, 'asset created with correct catalog' );
      
      done_testing();
      rt-5.0.1/t/web/ticket_timeworked.t000644 000765 000024 00000011347 14005011336 017731 0ustar00sunnavystaff000000 000000 use strict;
      use warnings;
      use RT::Test tests => undef, config => 'Set($DisplayTotalTimeWorked, 1);';
      
      my ( $baseurl, $m ) = RT::Test->started_ok;
      ok( $m->login, "Logged in" );
      
      my $queue = RT::Test->load_or_create_queue( Name => 'General' );
      ok( $queue->id, "loaded the General queue" );
      
      my ( $child1, $child2 ) = RT::Test->create_tickets(
          { Queue   => 'General' },
          { Subject => 'child ticket 1', },
          { Subject => 'child ticket 2', },
      );
      
      my ( $child1_id, $child2_id ) = ( $child1->id, $child2->id );
      my $parent_id; # id of the parent ticket
      
      diag "add ticket links for timeworked tests"; {
          my $ticket = RT::Test->create_ticket(
              Queue   => 'General',
              Subject => "timeworked parent",
          );
          my $id = $parent_id = $ticket->id;
      
          $m->goto_ticket($id);
          $m->follow_link_ok( { text => 'Links' }, "Followed link to Links" );
      
          ok( $m->form_with_fields("MemberOf-$id"), "found the form" );
          $m->field( "MemberOf-$id", "$child1_id $child2_id" );
      
          $m->submit;
      
          $m->content_like(
              qr{"DeleteLink-.*?ticket/$child1_id-MemberOf-"},
              "base for MemberOf: has child1 ticket",
          );
          $m->content_like(
              qr{"DeleteLink-.*?ticket/$child2_id-MemberOf-"},
              "base for MemberOf: has child2 ticket",
          );
      
          $m->goto_ticket($id);
          $m->content_like( qr{$child1_id:.*?\[new\]}, "has active ticket", );
      }
      
      diag "adding timeworked values for child tickets"; {
          my $user_a = RT::Test->load_or_create_user(
              Name => 'user_a', Password => 'password',
          );
          ok $user_a && $user_a->id, 'loaded or created user';
      
          my $user_b = RT::Test->load_or_create_user(
              Name => 'user_b', Password => 'password',
          );
          ok $user_b && $user_b->id, 'loaded or created user';
      
          ok( RT::Test->set_rights(
              { Principal => $user_a, Right => [qw(SeeQueue ShowTicket ModifyTicket CommentOnTicket)] },
              { Principal => $user_b, Right => [qw(SeeQueue ShowTicket ModifyTicket CommentOnTicket)] },
          ), 'set rights');
      
      
          my @updates = ({
              id => $child1_id,
              view => 'Modify',
              field => 'TimeWorked',
              form => 'TicketModify',
              title => "Modify ticket #$child1_id: child ticket 1",
              time => 45,
              user => 'user_a',
          }, {
              id => $child2_id,
              view => 'Modify',
              field => 'TimeWorked',
              form => 'TicketModify',
              title => "Modify ticket #$child2_id: child ticket 2",
              time => 35,
              user => 'user_a',
          }, {
              id => $child2_id,
              view => 'Update',
              field => 'UpdateTimeWorked',
              form => 'TicketUpdate',
              title => "Update ticket #$child2_id: child ticket 2",
              time => 90,
              user => 'user_b',
          });
      
          foreach my $update ( @updates ) {
              my $agent = RT::Test::Web->new;
              ok $agent->login($update->{user}, 'password'), 'logged in as user';
              $agent->goto_ticket( $update->{id}, $update->{view} );
              $agent->title_is( $update->{title}, 'have child ticket page' );
              ok( $agent->form_name( $update->{form} ), 'found the form' );
              $agent->field( $update->{field}, $update->{time} );
              $agent->submit_form( button => 'SubmitTicket' );
          }
      }
      
      diag "checking parent ticket for expected timeworked data"; {
          $m->goto_ticket( $parent_id );
          $m->title_is( "#$parent_id: timeworked parent");
          $m->content_like(
              qr{(?s)Worked:.+?value">2\.83 hours \(170 minutes\)},
              "found expected total TimeWorked in parent ticket"
          );
          $m->content_like(
              qr{(?s)user_a:.+?value">1\.33 hours \(80 minutes\)},
              "found expected user_a TimeWorked in parent ticket"
          );
          $m->content_like(
              qr{(?s)user_b:.+?value">1\.5 hours \(90 minutes\)},
              "found expected user_b TimeWorked in parent ticket"
          );
      }
      
      diag "checking child ticket 1 for expected timeworked data"; {
          $m->goto_ticket( $child1_id );
          $m->title_is( "#$child1_id: child ticket 1");
          $m->content_like(
              qr{(?s)Worked:.+?value">45 minutes},
              "found expected total TimeWorked in child ticket 1"
          );
          $m->content_like(
              qr{(?s)user_a:.+?value">45 minutes},
              "found expected user_a TimeWorked in child ticket 1"
          );
      }
      
      diag "checking child ticket 2 for expected timeworked data"; {
          $m->goto_ticket( $child2_id );
          $m->title_is( "#$child2_id: child ticket 2");
          $m->content_like(
              qr{(?s)Worked:.+?value">2\.08 hours \(125 minutes\)},
              "found expected total TimeWorked in child ticket 2"
          );
          $m->content_like(
              qr{(?s)user_a:.+?value">35 minutes},
              "found expected user_a TimeWorked in child ticket 2"
          );
          $m->content_like(
              qr{(?s)user_b:.+?value">1\.5 hours \(90 minutes\)},
              "found expected user_b TimeWorked in child ticket 2"
          );
      }
      
      done_testing();
      rt-5.0.1/t/web/shredder.t000644 000765 000024 00000004717 14005011336 016017 0ustar00sunnavystaff000000 000000 use strict;
      use warnings;
      
      use RT::Test tests => undef;
      
      RT::Config->Set('ShredderStoragePath', RT::Test->temp_directory . '');
      
      my ( $baseurl, $agent ) = RT::Test->started_ok;
      
      diag("Test server running at $baseurl");
      
      $agent->login('root' => 'password');
      
      my $ticket_id;
      # Ticket created in block to avoid scope error on destroy
      {
          my $ticket = RT::Test->create_ticket( Subject => 'test shredder', Queue => 1, );
          ok( $ticket->Id, "created new ticket" );
      
          $ticket_id = $ticket->id;
      }
      
      {
          $agent->get_ok($baseurl . '/Admin/Tools/Shredder/');
          $agent->submit_form_ok({
              form_id     => 'shredder-search-form',
              fields      => { Plugin => 'Tickets'},
          }, "Select Tickets shredder plugin");
      
          $agent->submit_form_ok({
              form_id     => 'shredder-search-form',
              fields      => {
                  'Tickets:query'  => 'id=' . $ticket_id,
              },
              button => 'Search',
          }, "Search for ticket object");
      
          $agent->submit_form_ok({
              form_id     => 'shredder-search-form',
              fields      => {
                  'WipeoutObject'     => 'RT::Ticket-example.com-' . $ticket_id,
              },
              button => 'Wipeout',
          }, "Select and destroy ticket object");
          $agent->text_contains('objects were successfuly removed', 'Found success message' );
      
          my $ticket = RT::Ticket->new(RT->SystemUser);
          my ($ret, $msg) = $ticket->Load($ticket_id);
      
          ok !$ret, 'Ticket successfully shredded';
      }
      
      # Shred RT::User
      {
          my $user = RT::Test->load_or_create_user( EmailAddress => 'test@example.com' );
      
          my $id = $user->id;
          ok $id;
      
          $agent->get_ok($baseurl . '/Admin/Tools/Shredder/');
          $agent->submit_form_ok({
              form_id     => 'shredder-search-form',
              fields      => { Plugin => 'Users'},
          }, "Select Users shredder plugin");
      
          $agent->submit_form_ok({
              form_id     => 'shredder-search-form',
              fields      => {
                  'Users:email'  => 'test@example.com',
                  'Users:status' => 'Enabled',
              },
              button => 'Search',
          }, "Search for user");
      
          $agent->submit_form_ok({
              form_id     => 'shredder-search-form',
              fields      => {
                  'WipeoutObject'     => 'RT::User-test@example.com',
              },
              button => 'Wipeout',
          }, "Select and destroy searched user");
          $agent->text_contains('objects were successfuly removed', 'Found success message' );
      
          my ($ret, $msg) = $user->Load($id);
          ok !$ret, 'User successfully shredded';
      }
      
      done_testing();
      rt-5.0.1/t/web/basic.t000644 000765 000024 00000007146 14005011336 015277 0ustar00sunnavystaff000000 000000 
      use strict;
      use warnings;
      
      use RT::Test tests => undef;
      
      my ($baseurl, $agent) = RT::Test->started_ok;
      
      my $url = $agent->rt_base_url;
      
      # get the top page
      {
          $agent->get($url);
          is ($agent->status, 200, "Loaded a page");
      }
      
      # test a login
      {
          $agent->login('root' => 'password');
          # the field isn't named, so we have to click link 0
          is( $agent->status, 200, "Fetched the page ok");
          $agent->content_contains("Logout", "Found a logout link");
      }
      
      {
          $agent->goto_create_ticket(1);
          is ($agent->status, 200, "Loaded Create.html");
          $agent->form_name('TicketCreate');
          my $string = Encode::decode("UTF-8","I18N Web Testing æøå");
          $agent->field('Subject' => "Ticket with utf8 body");
          $agent->field('Content' => $string);
          ok($agent->click('SubmitTicket'), "Created new ticket with $string as Content");
          $agent->content_contains($string, "Found the content");
          ok($agent->{redirected_uri}, "Did redirection");
      
          {
              my $ticket = RT::Test->last_ticket;
              my $content = $ticket->Transactions->First->Content;
              like(
                  $content, qr{$string},
                  'content is there, API check'
              );
          }
      }
      
      {
          $agent->goto_create_ticket(1);
          is ($agent->status, 200, "Loaded Create.html");
          $agent->form_name('TicketCreate');
      
          my $string = Encode::decode( "UTF-8","I18N Web Testing æøå");
          $agent->field('Subject' => $string);
          $agent->field('Content' => "Ticket with utf8 subject");
          ok($agent->click('SubmitTicket'), "Created new ticket with $string as Content");
          $agent->content_contains($string, "Found the content");
          ok($agent->{redirected_uri}, "Did redirection");
      
          {
              my $ticket = RT::Test->last_ticket;
              is(
                  $ticket->Subject, $string,
                  'subject is correct, API check'
              );
          }
      }
      
      # Update time worked in hours
      {
          $agent->follow_link( text_regex => qr/Basics/ );
          $agent->submit_form( form_name => 'TicketModify',
              fields => { TimeWorked => 5, 'TimeWorked-TimeUnits' => "hours" }
          );
      
          $agent->content_contains("5 hours", "5 hours is displayed");
          $agent->content_contains("300 min", "but minutes is also");
      }
      
      
      $agent->get( $url."static/images/test.png" );
      my $file = RT::Test::get_relocatable_file(
        File::Spec->catfile(
          qw(.. .. share static images test.png)
        )
      );
      is(
          length($agent->content),
          -s $file,
          "got a file of the correct size ($file)",
      );
      
      #
      # XXX: hey-ho, we have these tests in t/web/query-builder
      # TODO: move everything about QB there
      
      my $response = $agent->get($url."Search/Build.html");
      ok( $response->is_success, "Fetched " . $url."Search/Build.html" );
      
      # Parsing TicketSQL
      #
      # Adding items
      
      # set the first value
      ok($agent->form_name('BuildQuery'));
      $agent->field("AttachmentField", "Subject");
      $agent->field("AttachmentOp", "LIKE");
      $agent->field("ValueOfAttachment", "aaa");
      $agent->submit("AddClause");
      
      # set the next value
      ok($agent->form_name('BuildQuery'));
      $agent->field("AttachmentField", "Subject");
      $agent->field("AttachmentOp", "LIKE");
      $agent->field("ValueOfAttachment", "bbb");
      $agent->submit("AddClause");
      
      ok($agent->form_name('BuildQuery'));
      
      # get the query
      my $query = $agent->current_form->find_input("Query")->value;
      # strip whitespace from ends
      $query =~ s/^\s*//g;
      $query =~ s/\s*$//g;
      
      # collapse other whitespace
      $query =~ s/\s+/ /g;
      
      is ($query, "Subject LIKE 'aaa' AND Subject LIKE 'bbb'");
      
      {
          my $queue = RT::Test->load_or_create_queue( Name => 'foo&bar' );
          $agent->goto_create_ticket( $queue->id );
          is( $agent->status, 200, "Loaded Create.html" );
          $agent->title_is('Create a new ticket in foo&bar');
      }
      
      done_testing;
      rt-5.0.1/t/web/csrf-rest.t000644 000765 000024 00000004501 14005011336 016116 0ustar00sunnavystaff000000 000000 use strict;
      use warnings;
      
      use RT::Test tests => undef;
      
      my ($baseurl, $m) = RT::Test->started_ok;
      
      # Get a non-REST session
      diag "Standard web session";
      ok $m->login, 'logged in';
      $m->content_contains("RT at a glance", "Get full UI content");
      
      # Requesting a REST page should be fine, as we have a Referer
      $m->post("$baseurl/REST/1.0/ticket/new", [
          format  => 'l',
      ]);
      $m->content_like(qr{^id: ticket/new}m, "REST request with referrer");
      
      # Removing the Referer header gets us an interstitial
      $m->add_header(Referer => undef);
      $m->post("$baseurl/REST/1.0/ticket/new", [
          format  => 'l',
          foo     => 'bar',
      ]);
      $m->content_contains("Possible cross-site request forgery",
                       "REST request without referrer is blocked");
      
      # But passing username and password lets us though
      $m->post("$baseurl/REST/1.0/ticket/new", [
          user    => 'root',
          pass    => 'password',
          format  => 'l',
      ]);
      $m->content_like(qr{^id: ticket/new}m, "REST request without referrer, but username/password supplied, is OK");
      
      # And we can still access non-REST urls
      $m->get("$baseurl");
      $m->content_contains("RT at a glance", "Full UI is still available");
      
      
      # Now go get a REST session
      diag "REST session";
      $m = RT::Test::Web->new;
      $m->post("$baseurl/REST/1.0/ticket/new", [
          user    => 'root',
          pass    => 'password',
          format  => 'l',
      ]);
      $m->content_like(qr{^id: ticket/new}m, "REST request to log in");
      
      # Requesting that page again, with a username/password but no referrer,
      # is fine
      $m->add_header(Referer => undef);
      $m->post("$baseurl/REST/1.0/ticket/new", [
          user    => 'root',
          pass    => 'password',
          format  => 'l',
      ]);
      $m->content_like(qr{^id: ticket/new}m, "REST request with no referrer, but username/pass");
      
      # And it's still fine without both referer and username and password,
      # because REST is special-cased
      $m->post("$baseurl/REST/1.0/ticket/new", [
          format  => 'l',
      ]);
      $m->content_like(qr{^id: ticket/new}m, "REST request with no referrer or username/pass is special-cased for REST sessions");
      
      # But the REST page can't request normal pages
      $m->get("$baseurl");
      $m->content_lacks("RT at a glance", "Full UI is denied for REST sessions");
      $m->content_contains("This login session belongs to a REST client", "Tells you why");
      $m->warning_like(qr/This login session belongs to a REST client/, "Logs a warning");
      
      done_testing;
      
      rt-5.0.1/t/web/admin_groups.t000644 000765 000024 00000016160 14005011336 016701 0ustar00sunnavystaff000000 000000 use strict;
      use warnings;
      
      use RT::Test tests => undef;
      
      my ( $url, $m ) = RT::Test->started_ok;
      ok( $m->login(), 'logged in' );
      
      {
          diag "test creating a group" if $ENV{TEST_VERBOSE};
          $m->get_ok( $url . '/Admin/Groups/Modify.html?Create=1' );
          $m->content_contains('Create a new group', 'found title');
          $m->submit_form_ok({
              form_number => 3,
              fields => { Name => 'test group' },
          });
          $m->content_contains('Group created', 'found results');
          $m->content_contains('Modify the group test group', 'found title');
      }
      
      {
          diag "Add group members" if $ENV{TEST_VERBOSE};
          my $group = RT::Group->new( RT->SystemUser );
          my ($ret, $msg) = $group->LoadUserDefinedGroup('test group');
      
          $m->get_ok( $url . '/Admin/Groups/Members.html?id=' . $group->Id );
          $m->content_contains('Editing membership for group test group', 'Loaded group members page');
          $m->submit_form_ok({
              form_number => 3,
              fields => { AddMembersUsers => 'root' },
          });
          $m->content_contains('Member added: root', 'Added root to group');
      
          $m->get_ok( $url . '/Admin/Groups/Members.html?id=' . $group->Id );
          $m->content_contains('Editing membership for group test group', 'Loaded group members page');
          $m->submit_form_ok({
              form_number => 3,
              fields => { AddMembersUsers => 'user1@example.com' },
          });
          $m->content_contains('Member added: user1@example.com', 'Added user1@example.com to group');
      }
      
      {
          diag "test creating another group" if $ENV{TEST_VERBOSE};
          $m->get_ok( $url . '/Admin/Groups/Modify.html?Create=1' );
          $m->content_contains('Create a new group', 'found title');
          $m->submit_form_ok({
              form_number => 3,
              fields => { Name => 'test group2' },
          });
          $m->content_contains('Group created', 'found results');
          $m->content_contains('Modify the group test group2', 'found title');
      }
      
      {
          diag "test creating an overlapping group" if $ENV{TEST_VERBOSE};
          $m->get_ok( $url . '/Admin/Groups/Modify.html?Create=1' );
          $m->content_contains('Create a new group', 'found title');
          $m->submit_form_ok({
              form_number => 3,
              fields => { Name => 'test group' },
          });
          $m->content_contains('Group could not be created', 'found results');
          $m->content_like(qr/Group name .+? is already in use/, 'found message');
      }
      
      {
          diag "test updating a group name to overlap" if $ENV{TEST_VERBOSE};
          $m->get_ok( $url . '/Admin/Groups/' );
          $m->follow_link_ok({text => 'test group2'}, 'found title');
          $m->content_contains('Modify the group test group2');
          $m->submit_form_ok({
              form_number => 3,
              fields => { Name => 'test group' },
          });
          $m->content_lacks('Name changed', "name not changed");
          $m->content_contains('Illegal value for Name', 'found error message');
          $m->content_contains('test group', 'did not find new name');
      }
      
      {
          diag "Test group searches";
          my @cf_names = qw( CF1 CF2 CF3 );
          my @cfs = ();
          foreach my $cf_name ( @cf_names ) {
              my $cf = RT::CustomField->new( RT->SystemUser );
              my ( $id, $msg ) = $cf->Create(
                  Name => $cf_name,
                  TypeComposite => 'Freeform-1',
                  LookupType => RT::Group->CustomFieldLookupType,
              );
              ok( $id, $msg );
              # Create a global ObjectCustomField record
              my $object = $cf->RecordClassFromLookupType->new( RT->SystemUser );
              ( $id, $msg ) = $cf->AddToObject( $object );
              ok( $id, $msg );
              push ( @cfs, $cf );
          }
          my $cf_1 = $cfs[0];
          my $cf_2 = $cfs[1];
          my $cf_3 = $cfs[2];
      
          my @group_names = qw( Group1 Group2 Group3 Group4 );
          my @groups = ();
          foreach my $group_name ( @group_names ) {
              my $group = RT::Group->new( RT->SystemUser );
              my ( $id, $msg ) = $group->CreateUserDefinedGroup( Name => $group_name );
              ok ( $id, $msg.': '.$group_name );
              push ( @groups, $group );
          }
          $groups[0]->AddCustomFieldValue( Field => $cf_1->id, Value => 'one' );
      
          $groups[1]->AddCustomFieldValue( Field => $cf_1->id, Value => 'one' );
          $groups[1]->AddCustomFieldValue( Field => $cf_2->id, Value => 'two' );
      
          $groups[2]->AddCustomFieldValue( Field => $cf_1->id, Value => 'one' );
          $groups[2]->AddCustomFieldValue( Field => $cf_2->id, Value => 'two' );
          $groups[2]->AddCustomFieldValue( Field => $cf_3->id, Value => 'three' );
      
          $m->get_ok( $url . '/Admin/Groups/index.html' );
          ok( $m->form_name( 'GroupsAdmin' ), 'found the filter admin groups form');
          $m->select( GroupField => 'Name', GroupOp => 'LIKE' );
          $m->field( GroupString => 'Group' );
          $m->select( GroupField2 => 'CustomField: '.$cf_1->Name, GroupOp2 => 'LIKE' );
          $m->field( GroupString2 => 'one' );
          $m->select( GroupField3 => 'CustomField: '.$cf_2->Name, GroupOp3 => 'LIKE' );
          $m->field( GroupString3 => 'two' );
          $m->click( 'Go' );
      
          diag "Verify results contain Groups 2 & 3, but not 1 & 4";
          $m->content_contains( $groups[1]->Name );
          $m->content_contains( $groups[2]->Name );
          $m->content_lacks( $groups[0]->Name );
          $m->content_lacks( $groups[3]->Name );
      
          diag 'Test NULL value searches';
          ok( $m->form_name( 'GroupsAdmin' ), 'found the filter admin groups form');
          $m->select( GroupField => 'Name', GroupOp => 'LIKE' );
          $m->field( GroupString => 'Group' );
          $m->select( GroupField2 => 'CustomField: '.$cf_2->Name, GroupOp2 => 'is' );
          $m->field( GroupString2 => 'NULL' );
          $m->field( GroupString3 => '' );
          $m->click( 'Go' );
          $m->text_lacks( $_->Name ) for @groups[1..2];
          $m->text_contains( $_->Name ) for @groups[0,3];
      
          ok( $groups[0]->SetDescription('group1') );
          $m->form_name( 'GroupsAdmin' );
          $m->select( GroupField2 => 'Description', GroupOp2 => 'is' );
          $m->field( GroupString2 => 'NULL' );
          $m->click( 'Go' );
          $m->text_lacks( $_->Name ) for $groups[0];
          $m->text_contains( $_->Name ) for @groups[1..3];
      }
      
      {
          diag "Delete group members" if $ENV{TEST_VERBOSE};
          my $group = RT::Group->new( RT->SystemUser );
          $group->LoadUserDefinedGroup('test group');
      
          my $root = RT::User->new( RT->SystemUser );
          $root->Load('root');
          $m->get_ok( $url . '/Admin/Groups/Members.html?id=' . $group->Id );
          $m->content_contains( 'Editing membership for group test group', 'Loaded group members page' );
      
          $m->form_number(3);
          $m->tick( 'DeleteMember-' . $root->Id, 1 );
          $m->submit_form_ok( {}, 'Delete "root" from group' );
          $m->content_contains( 'Member deleted', 'Deleted "root" from group' );
          $m->content_lacks( 'DeleteMember-' . $root->Id );
      
          $m->submit_form_ok(
              {   form_number => 3,
                  fields      => { AddMembersGroups => 'test group2' },
              },
              'Add "test group2" to group',
          );
          $m->content_contains( 'Member added: test group2', 'Added "test group2" to group' );
      
          my $group2 = RT::Group->new( RT->SystemUser );
          $group2->LoadUserDefinedGroup('test group2');
      
          $m->form_number(3);
          $m->tick( 'DeleteMember-' . $group2->Id, 1 );
          $m->submit_form_ok( {}, 'Delete "test group2" from group' );
          $m->content_contains( 'Member deleted', 'Deleted "test group2" from group' );
          $m->content_lacks( 'DeleteMember-' . $group2->Id );
      }
      
      done_testing;
      rt-5.0.1/t/web/dashboards-search-cache.t000644 000765 000024 00000007745 14005011336 020641 0ustar00sunnavystaff000000 000000 use strict;
      use warnings;
      
      use RT::Test tests => undef;
      
      my $root = RT::Test->load_or_create_user( Name => 'root' );
      my ($baseurl, $m) = RT::Test->started_ok;
      
      my $url = $m->rt_base_url;
      
      ok($m->login, 'logged in');
      
      # create a search
      $m->follow_link_ok({text => 'Tickets'}, 'to query builder');
      $m->form_name('BuildQuery');
      
      $m->field(ValueOfid => 10 );
      $m->click('AddClause');
      $m->text_contains( 'id < 10', 'added new clause');
      
      $m->form_name('BuildQuery');
      $m->field(SavedSearchDescription => 'Original Name');
      $m->click('SavedSearchSave');
      
      # create the inner dashboard
      $m->get_ok("$url/Dashboards/Modify.html?Create=1");
      $m->form_name('ModifyDashboard');
      $m->field('Name' => 'inner dashboard');
      $m->click_button(value => 'Create');
      $m->text_contains('Saved dashboard inner dashboard');
      
      my ($inner_id) = $m->content =~ /name="id" value="(\d+)"/;
      ok($inner_id, "got an ID, $inner_id");
      
      # create a dashboard
      $m->get_ok("$url/Dashboards/Modify.html?Create=1");
      $m->form_name('ModifyDashboard');
      $m->field('Name' => 'cachey dashboard');
      $m->click_button(value => 'Create');
      $m->text_contains('Saved dashboard cachey dashboard');
      
      my ($dashboard_id) = $m->content =~ /name="id" value="(\d+)"/;
      ok($dashboard_id, "got an ID, $dashboard_id");
      
      # add the search to the dashboard
      $m->follow_link_ok({text => 'Content'});
      
      # we need to get the saved search id from the content before submitting the args.
      my $regex = 'data-type="saved" data-name="RT::User-' . $root->id . '-SavedSearch-(\d+)"';
      my ($saved_search_id) = $m->content =~ /$regex/;
      ok($saved_search_id, "got an ID for the saved search, $saved_search_id");
      
      my $args = {
          UpdateSearches => "Save",
          dashboard_id   => $dashboard_id,
          body           => [],
          sidebar        => [],
      };
      
      # add 'Original Name' and 'inner dashboard' portlets to body
      push(
          @{$args->{body}},
          (
            "saved-" . "RT::User-" . $root->id . "-SavedSearch-" . $saved_search_id,
            "dashboard-dashboard-" . $inner_id . "-RT::User-" . $root->id,
          )
      );
      
      my $res = $m->post(
          $url . 'Dashboards/Queries.html?id=' . $dashboard_id,
          $args,
      );
      
      is( $res->code, 200, "add 'Original Name' and 'inner dashboard' portlets to body" );
      like( $m->uri, qr/results=[A-Za-z0-9]{32}/, 'URL redirected for results' );
      $m->content_contains( 'Dashboard updated' );
      
      # subscribe to the dashboard
      $m->follow_link_ok({text => 'Subscription'});
      $m->text_contains('Saved Search: Original Name');
      $m->text_contains('Dashboard: inner dashboard');
      $m->form_name('SubscribeDashboard');
      $m->click_button(name => 'Save');
      $m->text_contains('Subscribed to dashboard cachey dashboard');
      
      # rename the search
      $m->follow_link_ok({text => 'Tickets'}, 'to query builder');
      my $form = $m->form_name('BuildQuery');
      my @input = $form->find_input('SavedSearchLoad');
      my ($search_value) =
        map { ( $_->possible_values )[1] }
        grep { ( $_->value_names )[1] =~ /Original Name/ } @input;
      $form->value('SavedSearchLoad' => $search_value );
      $m->click_button(value => 'Load');
      $m->text_contains('Loaded saved search "Original Name"');
      
      $m->form_name('BuildQuery');
      $m->field('SavedSearchDescription' => 'New Name');
      $m->click_button(value => 'Update');
      $m->text_contains('Updated saved search "New Name"');
      
      # rename the dashboard
      $m->get_ok("/Dashboards/Modify.html?id=$inner_id");
      $m->form_name('ModifyDashboard');
      $m->field('Name' => 'recursive dashboard');
      $m->click_button(value => 'Save Changes');
      $m->text_contains('Dashboard recursive dashboard updated');
      
      # check subscription page again
      $m->get_ok("/Dashboards/Subscription.html?id=$dashboard_id");
      TODO: {
          local $TODO = 'we cache search names too aggressively';
          $m->text_contains('Saved Search: New Name');
          $m->text_unlike(qr/Saved Search: Original Name/); # t-w-m lacks text_lacks
      
          $m->text_contains('Dashboard: recursive dashboard');
          $m->text_unlike(qr/Dashboard: inner dashboard/); # t-w-m lacks text_lacks
      }
      
      $m->get_ok("/Dashboards/Render.html?id=$dashboard_id");
      $m->text_contains('New Name');
      $m->text_unlike(qr/Original Name/); # t-w-m lacks text_lacks
      
      done_testing;
      rt-5.0.1/t/web/priority.t000644 000765 000024 00000017245 14005011336 016100 0ustar00sunnavystaff000000 000000 use strict;
      use warnings;
      
      use RT::Test tests => undef;
      
      my ( $baseurl, $m ) = RT::Test->started_ok;
      ok( $m->login, 'Log in' );
      
      my $queue = RT::Test->load_or_create_queue( Name => 'General' );
      
      diag "Default PriorityAsString";
      
      $m->goto_create_ticket( $queue->Id );
      my $form = $m->form_name('TicketCreate');
      
      for my $field (qw/InitialPriority FinalPriority/) {
          my $priority_input = $form->find_input($field);
          is( $priority_input->type, 'option', "$field input is a select" );
          is_deeply( [ $priority_input->possible_values ], [ '', 0, 50, 100 ], "$field options" );
          is( $form->value($field), '', "$field default value" );
      }
      
      $m->submit_form_ok( { fields => { Subject => 'Test PriorityAsString', InitialPriority => 50 }, button => 'SubmitTicket' }, 'Create ticket' );
      $m->text_like( qr{Priority:\s*Medium/Low}, 'Priority/FinalPriority on display' );
      
      $m->follow_link_ok( { text => 'Basics' } );
      $form = $m->form_name('TicketModify');
      
      for my $field (qw/Priority FinalPriority/) {
          my $priority_input = $form->find_input($field);
          is( $priority_input->type, 'option', "$field input is a select" );
          is_deeply( [ $priority_input->possible_values ], [ 0, 50, 100 ], "$field options" );
          is( $form->value($field), $field eq 'Priority' ? 50 : 0, "$field default value" );
      }
      
      $m->submit_form_ok( { fields => { Priority => 100, FinalPriority => 100 } }, 'Update Priority' );
      $m->text_contains( qq{Priority changed from 'Medium' to 'High'},   'Priority is updated' );
      $m->text_contains( qq{FinalPriority changed from 'Low' to 'High'}, 'FinalPriority is updated' );
      
      diag "Disable PriorityAsString";
      
      my $config = RT::Configuration->new( RT->SystemUser );
      my ( $ret, $msg ) = $config->Create( Name => 'EnablePriorityAsString', Content => 0 );
      ok( $ret, 'Updated config' );
      
      $m->goto_create_ticket( $queue->Id );
      $form = $m->form_name('TicketCreate');
      for my $field (qw/InitialPriority FinalPriority/) {
          my $priority_input = $form->find_input($field);
          is( $priority_input->type, 'text', "$field input is a text" );
          is( $form->value($field),  '',     "$field default value" );
      }
      
      $config->Load('EnablePriorityAsString');
      ( $ret, $msg ) = $config->SetContent(1);
      ok( $ret, 'Updated config' );
      
      diag "Set PriorityAsString config of General to a hashref";
      
      # config cache refreshes at most once in one second, so wait a bit.
      sleep 1;
      
      $config = RT::Configuration->new( RT->SystemUser );
      ( $ret, $msg ) = $config->Create(
          Name    => 'PriorityAsString',
          Content => {
              Default => { Low     => 0, Medium => 50, High   => 100 },
              General => { VeryLow => 0, Low    => 20, Medium => 50, High => 100, VeryHigh => 200 },
          },
      );
      ok( $ret, 'Updated config' );
      
      $m->goto_create_ticket( $queue->Id );
      $form = $m->form_name('TicketCreate');
      for my $field (qw/InitialPriority FinalPriority/) {
          my $priority_input = $form->find_input($field);
          is( $priority_input->type, 'option', "$field input is a select" );
          is_deeply( [ $priority_input->possible_values ], [ '', 0, 20, 50, 100, 200 ], "$field options" );
          is( $form->value($field), '', "$field default value" );
      }
      
      diag "Disable PriorityAsString for General";
      
      sleep 1;
      ( $ret, $msg ) = $config->SetContent(
          {   Default => { Low => 0, Medium => 50, High => 100 },
              General => 0,
          }
      );
      ok( $ret, 'Updated config' );
      $m->goto_create_ticket( $queue->Id );
      $form = $m->form_name('TicketCreate');
      for my $field (qw/InitialPriority FinalPriority/) {
          my $priority_input = $form->find_input($field);
          is( $priority_input->type, 'text', "$field input is a text" );
          is( $form->value($field),  '',     "$field default value" );
      }
      
      diag "Set PriorityAsString config of General to an arrayref";
      
      sleep 1;
      ( $ret, $msg ) = $config->SetContent(
          {   Default => { Low    => 0,  Medium  => 50, High => 100 },
              General => [ Medium => 50, VeryLow => 0,  Low  => 20, High => 100, VeryHigh => 200 ],
          }
      );
      ok( $ret, 'Updated config' );
      
      $m->goto_create_ticket( $queue->Id );
      $form = $m->form_name('TicketCreate');
      for my $field (qw/InitialPriority FinalPriority/) {
          my $priority_input = $form->find_input($field);
          is( $priority_input->type, 'option', "$field input is a select" );
          is_deeply( [ $priority_input->possible_values ], [ '', 50, 0, 20, 100, 200 ], "$field options" );
          is( $form->value($field), '', "$field default value" );
      }
      
      diag "Queue default values";
      
      $m->get_ok('/Admin/Queues/DefaultValues.html?id=1');
      $form = $m->form_name('ModifyDefaultValues');
      for my $field (qw/InitialPriority FinalPriority/) {
          my $priority_input = $form->find_input($field);
          is( $priority_input->type, 'option', 'Priority input is a select' );
          is_deeply( [ $priority_input->possible_values ], [ '', 50, 0, 20, 100, 200 ], 'Priority options' );
      }
      $m->submit_form_ok( { fields => { InitialPriority => 50, FinalPriority => 100 }, button => 'Update' },
          'Update default values' );
      $m->text_contains( 'Default value of InitialPriority changed from (no value) to Medium', 'InitialPriority is updated' );
      $m->text_contains( 'Default value of FinalPriority changed from (no value) to High',     'FinalPriority is updated' );
      
      $m->goto_create_ticket( $queue->Id );
      $form = $m->form_name('TicketCreate');
      is( $form->value('InitialPriority'), 50,  'InitialPriority default value' );
      is( $form->value('FinalPriority'),   100, 'FinalPriority default value' );
      
      diag "Search builder";
      
      $m->follow_link_ok( { text => 'Tickets' }, 'Ticket search builder' );
      $form = $m->form_name('BuildQuery');
      my $priority_input = $form->find_input('ValueOfPriority');
      is( $priority_input->type, 'option', 'ValueOfPriority input is a select' );
      is_deeply(
          [ $priority_input->possible_values ],
          [ '', 0, 50, 100, 50, 0, 20, 100, 200 ],
          'ValueOfPriority option values are numbers'
      );
      
      $m->submit_form_ok( { fields => { ValueOfQueue => 'General' }, button => 'AddClause' }, 'Limit queue' );
      $form           = $m->form_name('BuildQuery');
      $priority_input = $form->find_input('ValueOfPriority');
      is( $priority_input->type, 'option', 'ValueOfPriority input is a select' );
      is_deeply(
          [ $priority_input->possible_values ],
          [ '', 'Medium', 'VeryLow', 'Low', 'High', 'VeryHigh' ],
          'ValueOfTicketPriority option values are strings'
      );
      
      $m->submit_form_ok( { fields => { PriorityOp => '=', ValueOfPriority => 'High' }, button => 'DoSearch' },
          'Limit priority' );
      $m->title_is('Found 1 ticket');
      $m->text_contains('Test PriorityAsString');
      
      $m->follow_link_ok( { text => 'Advanced' } );
      $m->submit_form_ok(
          { form_name => 'BuildQueryAdvanced', fields => { Query => qq{Queue = 'General' AND Priority = 'Low'} } },
          'Search tickets with LowPriority' );
      $m->submit_form_ok( { form_name => 'BuildQuery', button => 'DoSearch' } );
      $m->title_is('Found 0 tickets');
      
      $m->follow_link_ok( { text => 'Transactions' }, 'Transaction search builder' );
      $form           = $m->form_name('BuildQuery');
      $priority_input = $form->find_input('ValueOfTicketPriority');
      is_deeply(
          [ $priority_input->possible_values ],
          [ '', 0, 50, 100, 50, 0, 20, 100, 200 ],
          'ValueOfPriority option values are numbers'
      );
      
      $m->submit_form_ok( { fields => { ValueOfTicketQueue => 'General' }, button => 'AddClause' }, 'Limit queue' );
      $form           = $m->form_name('BuildQuery');
      $priority_input = $form->find_input('ValueOfTicketPriority');
      is( $priority_input->type, 'option', 'ValueOfTicketPriority input is a select' );
      is_deeply(
          [ $priority_input->possible_values ],
          [ '', 'Medium', 'VeryLow', 'Low', 'High', 'VeryHigh' ],
          'ValueOfTicketPriority option values are strings'
      );
      
      $m->submit_form_ok( { fields => { TicketPriorityOp => '=', ValueOfTicketPriority => 'High' }, button => 'DoSearch' },
          'Limit priority' );
      $m->title_is('Found 4 transactions');
      $m->text_contains('Test PriorityAsString');
      
      done_testing;
      rt-5.0.1/t/web/owner_disabled_group_19221.t000644 000765 000024 00000013004 14005011336 021137 0ustar00sunnavystaff000000 000000 use strict;
      use warnings;
      
      use RT::Test tests => undef;
      
      my $queue = RT::Test->load_or_create_queue( Name => 'Test' );
      ok $queue && $queue->id, 'loaded or created queue';
      
      my $user = RT::Test->load_or_create_user(
          Name        => 'ausername',
          Privileged  => 1,
      );
      ok $user && $user->id, 'loaded or created user';
      
      my $group = RT::Group->new(RT->SystemUser);
      my ($ok, $msg) = $group->CreateUserDefinedGroup(Name => 'Disabled Group');
      ok($ok, $msg);
      
      ($ok, $msg) = $group->AddMember( $user->PrincipalId );
      ok($ok, $msg);
      
      ok( RT::Test->set_rights({
          Principal   => $group,
          Object      => $queue,
          Right       => [qw(OwnTicket)]
      }), 'set rights');
      
      RT->Config->Set( AutocompleteOwners => 0 );
      my ($base, $m) = RT::Test->started_ok;
      ok $m->login, 'logged in';
      
      diag "user from group shows up in create form";
      {
          $m->get_ok('/Ticket/Create.html?Queue=' . $queue->id, 'open ticket create page');
          $m->content_contains('Create a new ticket', 'opened create ticket page');
          my $form = $m->form_name('TicketCreate');
          my $input = $form->find_input('Owner');
          is $input->value, RT->Nobody->Id, 'correct owner selected';
          ok((scalar grep { $_ == $user->Id } $input->possible_values), 'user from group is in dropdown');
      }
      
      diag "user from disabled group DOESN'T shows up in create form";
      {
          ($ok, $msg) = $group->SetDisabled(1);
          ok($ok, $msg);
      
          $m->get_ok('/Ticket/Create.html?Queue=' . $queue->id, 'open ticket create page');
          $m->content_contains('Create a new ticket', 'opened create ticket page');
          my $form = $m->form_name('TicketCreate');
          my $input = $form->find_input('Owner');
          is $input->value, RT->Nobody->Id, 'correct owner selected';
          ok((not scalar grep { $_ == $user->Id } $input->possible_values), 'user from disabled group is NOT in dropdown');
          ($ok, $msg) = $group->SetDisabled(0);
          ok($ok, $msg);
      }
      
      
      
      diag "Put us in a nested group";
      my $super = RT::Group->new(RT->SystemUser);
      ($ok, $msg) = $super->CreateUserDefinedGroup(Name => 'Supergroup');
      ok($ok, $msg);
      
      ($ok, $msg) = $super->AddMember( $group->PrincipalId );
      ok($ok, $msg);
      
      ok( RT::Test->set_rights({
          Principal   => $super,
          Object      => $queue,
          Right       => [qw(OwnTicket)]
      }), 'set rights');
      
      
      diag "Disable the middle group";
      {
          ($ok, $msg) = $group->SetDisabled(1);
          ok($ok, "Disabled group: $msg");
      
          $m->get_ok('/Ticket/Create.html?Queue=' . $queue->id, 'open ticket create page');
          $m->content_contains('Create a new ticket', 'opened create ticket page');
          my $form = $m->form_name('TicketCreate');
          my $input = $form->find_input('Owner');
          is $input->value, RT->Nobody->Id, 'correct owner selected';
          ok((not scalar grep { $_ == $user->Id } $input->possible_values), 'user from disabled group is NOT in dropdown');
          ($ok, $msg) = $group->SetDisabled(0);
          ok($ok, "Re-enabled group: $msg");
      }
      
      diag "Disable the top group";
      {
          ($ok, $msg) = $super->SetDisabled(1);
          ok($ok, "Disabled supergroup: $msg");
      
          $m->get_ok('/Ticket/Create.html?Queue=' . $queue->id, 'open ticket create page');
          $m->content_contains('Create a new ticket', 'opened create ticket page');
          my $form = $m->form_name('TicketCreate');
          my $input = $form->find_input('Owner');
          is $input->value, RT->Nobody->Id, 'correct owner selected';
          ok((not scalar grep { $_ == $user->Id } $input->possible_values), 'user from disabled group is NOT in dropdown');
          ($ok, $msg) = $super->SetDisabled(0);
          ok($ok, "Re-enabled supergroup: $msg");
      }
      
      
      diag "Check WithMember and WithoutMember recursively";
      {
          my $with = RT::Groups->new( RT->SystemUser );
          $with->WithMember( PrincipalId => $user->PrincipalObj->Id, Recursively => 1 );
          $with->LimitToUserDefinedGroups;
          is_deeply(
              [map {$_->Name} @{$with->ItemsArrayRef}],
              ['Disabled Group','Supergroup'],
              "Get expected recursive memberships",
          );
      
          my $without = RT::Groups->new( RT->SystemUser );
          $without->WithoutMember( PrincipalId => $user->PrincipalObj->Id, Recursively => 1 );
          $without->LimitToUserDefinedGroups;
          is_deeply(
              [map {$_->Name} @{$without->ItemsArrayRef}],
              [],
              "And not a member of no groups",
          );
      
          ($ok, $msg) = $super->SetDisabled(1);
          ok($ok, "Disabled supergroup: $msg");
          $with->RedoSearch;
          $without->RedoSearch;
          is_deeply(
              [map {$_->Name} @{$with->ItemsArrayRef}],
              ['Disabled Group'],
              "Recursive check only contains subgroup",
          );
          is_deeply(
              [map {$_->Name} @{$without->ItemsArrayRef}],
              [],
              "Doesn't find the currently disabled group",
          );
          ($ok, $msg) = $super->SetDisabled(0);
          ok($ok, "Re-enabled supergroup: $msg");
      
          ($ok, $msg) = $group->SetDisabled(1);
          ok($ok, "Disabled intermediate group: $msg");
          $with->RedoSearch;
          $without->RedoSearch;
          is_deeply(
              [map {$_->Name} @{$with->ItemsArrayRef}],
              [],
              "Recursive check finds no groups",
          );
          is_deeply(
              [map {$_->Name} @{$without->ItemsArrayRef}],
              ['Supergroup'],
              "Now not a member of the supergroup",
          );
          ($ok, $msg) = $group->SetDisabled(0);
          ok($ok, "Re-enabled intermediate group: $msg");
      }
      
      diag "Check MemberOfGroup";
      {
          ($ok, $msg) = $group->SetDisabled(1);
          ok($ok, "Disabled intermediate group: $msg");
          my $users = RT::Users->new(RT->SystemUser);
          $users->MemberOfGroup($super->PrincipalObj->id);
          is($users->Count, 0, "Supergroup claims no members");
          ($ok, $msg) = $group->SetDisabled(0);
          ok($ok, "Re-enabled intermediate group: $msg");
      }
      
      
      done_testing;
      rt-5.0.1/t/web/search_txns.t000644 000765 000024 00000006115 14005011336 016532 0ustar00sunnavystaff000000 000000 use strict;
      use warnings;
      
      use RT::Test tests => undef;
      my ( $baseurl, $m ) = RT::Test->started_ok;
      
      my $ticket = RT::Ticket->new( RT->SystemUser );
      $ticket->Create(
          Subject   => 'Test ticket',
          Queue     => 'General',
          Owner     => 'root',
      );
      
      ok( $ticket->SetStatus('open') );
      
      is( $ticket->Transactions->Count, 3, 'Ticket has 3 txns' );
      
      $m->login;
      
      diag "Query builder";
      {
          $m->follow_link_ok( { text => 'New Search', url_regex => qr/Class=RT::Transaction/ }, 'Query builder' );
          $m->title_is('Transaction Query Builder');
      
          $m->form_name('BuildQuery');
          $m->field( TicketIdOp      => '=' );
          $m->field( ValueOfTicketId => 1 );
          $m->click('AddClause');
      
          $m->follow_link_ok( { id => 'page-results' } );
          $m->title_is('Found 3 transactions');
      
          $m->back;
          $m->form_name('BuildQuery');
          $m->field( TypeOp      => '=' );
          $m->field( ValueOfType => 'Create' );
          $m->click('AddClause');
      
          $m->follow_link_ok( { id => 'page-results' } );
          $m->title_is('Found 1 transaction');
          $m->text_contains( 'Ticket created', 'Got create txn' );
      }
      
      diag "Advanced";
      {
          $m->follow_link_ok( { text => 'New Search', url_regex => qr/Class=RT::Transaction/ }, 'Query builder' );
          $m->follow_link_ok( { text => 'Advanced' }, 'Advanced' );
          $m->title_is('Edit Transaction Query');
      
          $m->form_name('BuildQueryAdvanced');
          $m->field( Query => q{OldValue = 'new'} );
          $m->submit;
      
          $m->follow_link_ok( { id => 'page-results' } );
          $m->title_is('Found 1 transaction');
          $m->text_contains( q{Status changed from 'new' to 'open'}, 'Got status change txn' );
      }
      
      diag "Saved searches";
      {
          $m->follow_link_ok( { text => 'New Search', url_regex => qr/Class=RT::Transaction/ }, 'Query builder' );
          $m->form_name('BuildQuery');
          $m->field( ValueOfTicketId => 10 );
          $m->submit('AddClause');
      
          $m->form_name('BuildQuery');
          $m->field( SavedSearchDescription => 'test txn search' );
          $m->click('SavedSearchSave');
          $m->text_contains('Current search: test txn search');
      
          my $form = $m->form_name('BuildQuery');
          my $input = $form->find_input( 'SavedSearchLoad' );
          # an empty search and the real saved search
          is( scalar $input->possible_values, 2, '2 SavedSearchLoad options' );
      
          my ($attr_id) = ($input->possible_values)[1] =~ /(\d+)$/;
          my $attr = RT::Attribute->new(RT->SystemUser);
          $attr->Load($attr_id);
          is_deeply(
              $attr->Content,
              {
                  'Format' => '\'__id__/TITLE:ID\',
      \'__ObjectId__/TITLE:Ticket\',
      \'__Description__\',
      \'__OldValue__\',
      \'__NewValue__\',
      \'__Content__\',
      \'__CreatedRelative__\'',
                  'OrderBy'     => 'id|||',
                  'SearchType'  => 'Transaction',
                  'RowsPerPage' => '50',
                  'Order'       => 'ASC|ASC|ASC|ASC',
                  'Query'       => 'TicketId < 10',
                  'ObjectType'  => 'RT::Ticket'
              },
              'Saved search content'
          );
      }
      
      done_testing;
      rt-5.0.1/t/web/cf_groupings.t000644 000765 000024 00000023130 14005011336 016672 0ustar00sunnavystaff000000 000000 use strict;
      use warnings;
      
      use RT::Test tests => undef;
      
      my @groupings = qw/Basics Dates People Links More/;
      RT->Config->Set( 'CustomFieldGroupings',
          'RT::Ticket' => {
              map { +($_ => ["Test$_"]) } @groupings,
          },
      );
      
      my %CF;
      for my $grouping (@groupings) {
          my $name = "Test$grouping";
          my $cf = RT::CustomField->new( RT->SystemUser );
          my ($id, $msg) = $cf->Create(
              Name => $name,
              Queue => '0',
              Description => 'A Testing custom field',
              Type => 'FreeformSingle',
              Pattern => '^(?!bad value).*$',
          );
          ok $id, "custom field '$name' correctly created";
          $CF{$grouping} = $id;
      }
      
      my $queue = RT::Test->load_or_create_queue( Name => 'General' );
      
      my ( $baseurl, $m ) = RT::Test->started_ok;
      ok $m->login, 'logged in as root';
      
      my %location = (
          Basics => ".ticket-info-basics",
          Dates  => ".ticket-info-dates",
          People => "#ticket-create-message",
          Links  => ".ticket-info-links",
          More   => ".ticket-info-cfs",
      );
      {
          note "testing Create";
          $m->goto_create_ticket($queue);
      
          my $prefix = 'Object-RT::Ticket--CustomField:';
          my $dom = $m->dom;
          $m->form_name('TicketCreate');
          $m->field("Subject", "CF grouping test");
      
          for my $grouping (@groupings) {
              my $input_name = $prefix . "$grouping-$CF{$grouping}-Value";
              is $dom->find(qq{input[name="$input_name"]})->size, 1, "only one CF input on the page";
              ok $dom->at(qq{$location{$grouping} input[name="$input_name"]}), "CF is in the right place";
              $m->field( $input_name, "Test" . $grouping . "Value" );
          }
          $m->click('SubmitTicket');
      }
      
      my $id = $m->get_ticket_id;
      {
          note "testing Display";
          ok $id, "created a ticket";
          my $dom = $m->dom;
      
          $location{People} = ".ticket-info-people";
          foreach my $grouping (@groupings) {
              my $row_id = "CF-$CF{$grouping}-ShowRow";
              is $dom->find(qq{#$row_id})->size, 1, "CF on the page";
              like $dom->at(qq{#$row_id})->all_text, qr/Test$grouping:\s*Test${grouping}Value/, "value is set";
              ok $dom->at(qq{$location{$grouping} #$row_id}), "CF is in the right place";
          }
      }
      
      {
          note "testing Basics/People/Dates/Links pages";
          my $prefix = 'Object-RT::Ticket-'. $id .'-CustomField:';
          { # Basics and More both show up on "Basics"
              for my $name (qw/Basics More/) {
                  $m->follow_link_ok({id => 'page-basics'}, 'Ticket -> Basics');
                  is $m->dom->find(qq{input[name^="$prefix"][name\$="-Value"]})->size, 2,
                      "two CF inputs on the page";
      
                  my $input_name = "$prefix$name-$CF{$name}-Value";
                  ok $m->dom->at(qq{$location{$name} input[name="$input_name"]}),
                      "CF is in the right place";
                  $m->submit_form_ok({
                      with_fields => { $input_name => "Test${name}Changed" },
                      button      => 'SubmitTicket',
                  });
                  $m->content_like(qr{to Test${name}Changed});
      
                  $m->submit_form_ok({
                      with_fields => { $input_name => "bad value" },
                      button      => 'SubmitTicket',
                  });
                  $m->content_like(qr{Test\Q$name\E: Input must match});
              }
          }
      
          # Everything else gets its own page
          foreach my $name ( qw(People Dates Links) ) {
              $m->follow_link_ok({id => "page-\L$name"}, "Ticket's $name page");
              is $m->dom->find(qq{input[name^="$prefix"][name\$="-Value"]})->size, 1,
                  "only one CF input on the page";
              my $input_name = "$prefix$name-$CF{$name}-Value";
              $m->submit_form_ok({
                  with_fields => { $input_name => "Test${name}Changed" },
                  button      => 'SubmitTicket',
              });
              $m->content_like(qr{to Test${name}Changed});
      
              $m->submit_form_ok({
                  with_fields => { $input_name => "bad value" },
                  button      => 'SubmitTicket',
              });
              $m->content_like(qr{Could not add new custom field value: Input must match});
          }
      }
      
      {
          note "testing Jumbo";
          my $prefix = 'Object-RT::Ticket-'. $id .'-CustomField:';
          $m->follow_link_ok({id => "page-jumbo"}, "Ticket's Jumbo page");
          my $dom = $m->dom;
          $m->form_name("TicketModifyAll");
      
          foreach my $name ( qw(Basics People Dates Links More) ) {
              my $input_name = "$prefix$name-$CF{$name}-Value";
              is $dom->find(qq{input[name="$input_name"]})->size, 1,
                  "only one CF input on the page";
              $m->field( $input_name, "Test${name}Again" );
          }
          $m->click('SubmitTicket');
          foreach my $name ( qw(Basics People Dates Links More) ) {
              $m->content_like(qr{to Test${name}Again});
          }
      }
      
      {
          note "Reconfigure to place one CF in multiple boxes";
          $m->no_warnings_ok;
          RT::Test->stop_server;
      
          RT->Config->Set( 'CustomFieldGroupings',
              'RT::Ticket' => {
                  Basics => [ 'TestMore' ],
                  More   => [ 'TestMore' ],
              },
          );
      
          ( $baseurl, $m ) = RT::Test->started_ok;
          ok $m->login, 'logged in as root';
      }
      
      {
          note "Testing one CF in multiple boxes";
          $m->goto_create_ticket($queue);
      
          my $prefix = 'Object-RT::Ticket--CustomField:';
          my $dom = $m->dom;
          $m->form_name('TicketCreate');
      
          my $cf = $CF{More};
          is $m->dom->find(qq{input[name^="$prefix"][name\$="-$cf-Value"]})->size, 2,
              "Two 'More' CF inputs on the page";
          for my $grouping (qw/Basics More/) {
              my $input_name = $prefix . "$grouping-$cf-Value";
              is $dom->find(qq{input[name="$input_name"]})->size, 1, "Found the $grouping grouping";
              ok $dom->at(qq{$location{$grouping} input[name="$input_name"]}), "CF is in the right place";
              $m->field( $input_name, "TestMoreValue" );
          }
          $m->click('SubmitTicket');
          $m->no_warnings_ok( "Submitting CF with two (identical) values had no warnings" );
      }
      
      $id = $m->get_ticket_id;
      my $ticket = RT::Ticket->new ( RT->SystemUser );
      $ticket->Load( $id );
      is $ticket->CustomFieldValuesAsString( "TestMore", Separator => "|" ), "TestMoreValue",
          "Value submitted twice is set correctly (and only once)";
      
      my $cf = $CF{More};
      my $prefix = 'Object-RT::Ticket-'. $id .'-CustomField:';
      {
          note "Updating with multiple appearances of a CF";
          $m->follow_link_ok({id => 'page-basics'}, 'Ticket -> Basics');
      
          is $m->dom->find(qq{input[name^="$prefix"][name\$="-$cf-Value"]})->size, 2,
              "Two 'More' CF inputs on the page";
          my @inputs;
          for my $grouping (qw/Basics More/) {
              my $input_name =  "$prefix$grouping-$cf-Value";
              push @inputs, $input_name;
              ok $m->dom->at(qq{$location{$grouping} input[name="$input_name"]}),
                  "CF is in the right place";
          }
          $m->submit_form_ok({
              with_fields => {
                  map {+($_ => "TestMoreChanged")} @inputs,
              },
              button => 'SubmitTicket',
          });
          $m->no_warnings_ok;
          $m->content_like(qr{to TestMoreChanged});
      
          $ticket->Load( $id );
          is $ticket->CustomFieldValuesAsString( "TestMore", Separator => "|" ), "TestMoreChanged",
              "Updated value submitted twice is set correctly (and only once)";
      }
      
      {
          note "Updating with _differing_ values in multiple appearances of a CF";
      
          my %inputs = map {+($_ => "$prefix$_-$cf-Value")} qw/Basics More/;
          $m->submit_form_ok({
              with_fields => {
                  $inputs{Basics} => "BasicsValue",
                  $inputs{More}   => "MoreValue",
              },
              button => 'SubmitTicket',
          });
          $m->warning_like(qr{CF $cf submitted with multiple differing values});
          $m->content_like(qr{to BasicsValue}, "Arbitrarily chose first value");
      
          $ticket->Load( $id );
          is $ticket->CustomFieldValuesAsString( "TestMore", Separator => "|" ), "BasicsValue",
              "Conflicting value submitted twice is set correctly (and only once)";
      }
      
      {
          note "Configuring CF to be a select-multiple";
          my $custom_field = RT::CustomField->new( RT->SystemUser );
          $custom_field->Load( $cf );
          $custom_field->SetType( "Select" );
          $custom_field->SetMaxValues( 0 );
          $custom_field->AddValue( Name => $_ ) for 1..9;
      }
      
      {
          note "Select multiples do not interfere with each other when appearing multiple times";
          $m->follow_link_ok({id => 'page-basics'}, 'Ticket -> Basics');
      
          $m->form_name('TicketModify');
          my %inputs = map {+($_ => "$prefix$_-$cf-Values")} qw/Basics More/;
          ok $m->dom->at(qq{select[name="$inputs{Basics}"]}), "Found 'More' CF in Basics box";
          ok $m->dom->at(qq{select[name="$inputs{More}"]}),   "Found 'More' CF in More box";
      
          $m->select( $inputs{Basics} => [1, 3, 9] );
          $m->select( $inputs{More}   => [1, 3, 9] );
          $m->click( 'SubmitTicket' );
          $m->no_warnings_ok;
          $m->content_like(qr{$_ added as a value for TestMore}) for 1, 3, 9;
          $m->content_like(qr{BasicsValue is no longer a value for custom field TestMore});
      
          $ticket->Load( $id );
          is $ticket->CustomFieldValuesAsString( "TestMore", Separator => "|" ), "1|3|9",
              "Multi-select values submitted correctly";
      }
      
      {
          note "Submit multiples correctly choose one set of values when conflicting information is submitted";
          $m->form_name('TicketModify');
          my %inputs = map {+($_ => "$prefix$_-$cf-Values")} qw/Basics More/;
          $m->select( $inputs{Basics} => [2, 3, 4] );
          $m->select( $inputs{More}   => [8, 9] );
          $m->click( 'SubmitTicket' );
          $m->warning_like(qr{CF $cf submitted with multiple differing values});
          $m->content_like(qr{$_ added as a value for TestMore}) for 2, 4;
          $m->content_unlike(qr{$_ added as a value for TestMore}) for 8;
          $m->content_like(qr{$_ is no longer a value for custom field TestMore}) for 1, 9;
      
          $ticket->Load( $id );
          is $ticket->CustomFieldValuesAsString( "TestMore", Separator => "|" ), "3|2|4",
              "Multi-select values submitted correctly";
      }
      
      done_testing;
      rt-5.0.1/t/web/custom_frontpage.t000644 000765 000024 00000013730 14005011336 017571 0ustar00sunnavystaff000000 000000 use strict;
      use warnings;
      
      use RT::Test tests => undef;
      my ($baseurl, $m) = RT::Test->started_ok;
      
      my $url = $m->rt_base_url;
      
      my $user_obj = RT::User->new(RT->SystemUser);
      my ($ret, $msg) = $user_obj->LoadOrCreateByEmail('customer@example.com');
      ok($ret, 'ACL test user creation');
      $user_obj->SetName('customer');
      $user_obj->SetPrivileged(1);
      ($ret, $msg) = $user_obj->SetPassword('customer');
      $user_obj->PrincipalObj->GrantRight(Right => 'LoadSavedSearch');
      $user_obj->PrincipalObj->GrantRight(Right => 'EditSavedSearches');
      $user_obj->PrincipalObj->GrantRight(Right => 'CreateSavedSearch');
      $user_obj->PrincipalObj->GrantRight(Right => 'ModifySelf');
      
      ok $m->login( customer => 'customer' ), "logged in";
      
      $m->get ( $url."Search/Build.html");
      
      #create a saved search
      $m->form_name ('BuildQuery');
      
      $m->field ( "ValueOfAttachment" => 'stupid');
      $m->field ( "SavedSearchDescription" => 'stupid tickets');
      $m->click_button (name => 'SavedSearchSave');
      
      $m->get ( $url.'Prefs/MyRT.html' );
      $m->content_contains('stupid tickets', 'saved search listed in rt at a glance items');
      
      ok $m->login('root', 'password', logout => 1), 'we did log in as root';
      
      my $args = {
          UpdateSearches => "Save",
          dashboard_id   => "MyRT",
          body           => [],
          sidebar        => [],
      };
      
      # remove all portlets from the body pane except 'newest unowned tickets'
      push(
          @{$args->{body}},
          ( "system-Unowned Tickets", )
      );
      
      my $res = $m->post(
          $url . 'Prefs/MyRT.html',
          $args,
      );
      
      is( $res->code, 200, "remove all portlets from body except 'newest unowned tickets'" );
      like( $m->uri, qr/results=[A-Za-z0-9]{32}/, 'URL redirected for results' );
      $m->content_contains( 'Preferences saved' );
      
      $m->get( $url );
      $m->content_contains( 'newest unowned tickets', "'newest unowned tickets' is present" );
      $m->content_lacks( 'highest priority tickets', "'highest priority tickets' is not present" );
      $m->content_lacks( 'Bookmarked Tickets', "'Bookmarked Tickets' is not present" );  # 'Bookmarked Tickets' also shows up in the nav, so we need to be more specific
      $m->content_lacks( 'Quick ticket creation', "'Quick ticket creation' is not present" );
      
      # add back the previously removed portlets
      push(
          @{$args->{body}},
          ( "system-My Tickets", "system-Bookmarked Tickets", "component-QuickCreate" )
      );
      
      push(
          @{$args->{sidebar}},
          ( "component-MyReminders", "component-QueueList", "component-Dashboards", "component-RefreshHomepage", )
      );
      
      $res = $m->post(
          $url . 'Prefs/MyRT.html',
          $args,
      );
      
      is( $res->code, 200, 'add back previously removed portlets' );
      like( $m->uri, qr/results=[A-Za-z0-9]{32}/, 'URL redirected for results' );
      $m->content_contains( 'Preferences saved' );
      
      $m->get( $url );
      $m->content_contains( 'newest unowned tickets', "'newest unowned tickets' is present" );
      $m->content_contains( 'highest priority tickets', "'highest priority tickets' is present" );
      $m->content_contains( 'Bookmarked Tickets', "'Bookmarked Tickets' is present" );
      $m->content_contains( 'Quick ticket creation', "'Quick ticket creation' is present" );
      
      #create a saved search with special chars
      $m->get( $url . "Search/Build.html" );
      $m->form_name('BuildQuery');
      $m->field( "ValueOfAttachment"      => 'stupid' );
      $m->field( "SavedSearchDescription" => 'special chars [test] [_1] ~[_1~]' );
      $m->click_button( name => 'SavedSearchSave' );
      my ($name) = $m->content =~ /value="(RT::User-\d+-SavedSearch-\d+)"/;
      ok( $name, 'saved search name' );
      $m->get( $url . 'Prefs/MyRT.html' );
      $m->content_contains( 'special chars [test] [_1] ~[_1~]',
          'saved search listed in rt at a glance items' );
      
      # add saved search to body
      push(
          @{$args->{body}},
          ( "saved-" . $name )
      );
      
      $res = $m->post(
          $url . 'Prefs/MyRT.html',
          $args,
      );
      
      is( $res->code, 200, 'add saved search to body' );
      like( $m->uri, qr/results=[A-Za-z0-9]{32}/, 'URL redirected for results' );
      $m->content_contains( 'Preferences saved' );
      
      $m->get($url);
      $m->content_like( qr/special chars \[test\] \d+ \[_1\]/,
          'special chars in titlebox' );
      
      # Edit a system saved search to contain "[more]"
      {
          my $search = RT::Attribute->new( RT->SystemUser );
          $search->LoadByNameAndObject( Name => 'Search - My Tickets', Object => RT->System );
          my ($id, $desc) = ($search->id, RT->SystemUser->loc($search->Description, '"N"'));
          ok $id, 'loaded search attribute';
      
          $m->get_ok($url);
          $m->follow_link_ok({ url_regex => qr"Prefs/Search\.html\?name=.+?Attribute-$id" }, 'Edit link');
          $m->content_contains($desc, "found description: $desc");
      
          ok +($search->SetDescription( $search->Description . " [more]" ));
      
          $m->get_ok($m->uri); # "reload_ok"
          $m->content_contains($desc . " [more]", "found description: $desc");
      }
      
      # Add some system non-ticket searches
      $m->get_ok( $url . "/Search/Chart.html?Query=" . 'id=1' );
      
      $m->submit_form(
          form_name => 'SaveSearch',
          fields    => {
              SavedSearchDescription => 'first chart',
              SavedSearchOwner       => 'RT::System-1',
          },
          button => 'SavedSearchSave',
      );
      $m->content_contains("Chart first chart saved", 'saved first chart' );
      
      $m->get_ok( $url . "/Search/Build.html?Class=RT::Transactions&Query=" . 'TicketId=1' );
      
      $m->submit_form(
          form_name => 'BuildQuery',
          fields    => {
              SavedSearchDescription => 'first txn search',
              SavedSearchOwner       => 'RT::System-1',
          },
          button => 'SavedSearchSave',
      );
      # We don't show saved message on page :/
      $m->content_contains("Save as New", 'saved first txn search' );
      
      $m->get_ok( $url . 'Prefs/MyRT.html' );
      push(
          @{$args->{body}},
          "saved-" . $m->dom->find('[data-description="first chart"]')->first->attr('data-name'),
          "saved-" . $m->dom->find('[data-description="first txn search"]')->first->attr('data-name'),
      );
      
      $res = $m->post(
          $url . 'Prefs/MyRT.html',
          $args,
      );
      
      is( $res->code, 200, 'add system saved searches to body' );
      $m->text_contains( 'Preferences saved' );
      
      $m->get_ok($url);
      $m->text_contains('first chart');
      $m->text_contains('first txn search');
      
      done_testing;
      rt-5.0.1/t/web/query_log.t000644 000765 000024 00000001130 14005011336 016207 0ustar00sunnavystaff000000 000000 use strict;
      use warnings;
      
      use RT::Test tests => 9;
      
      RT->Config->Set(StatementLog => 1);
      
      my ($baseurl, $m) = RT::Test->started_ok;
      ok $m->login, 'logged in';
      
      my $root = RT::User->new($RT::SystemUser);
      $root->LoadByEmail('root@localhost');
      
      $m->get_ok("/Admin/Tools/Queries.html");
      $m->text_contains("/index.html", "we include info about a page we hit while logging in");
      $m->text_contains("Stack:", "stack traces");
      $m->text_like(qr{/autohandler:\d+}, "stack trace includes mason components");
      $m->text_contains("SELECT * FROM Principals WHERE id = '".$root->id."'", "we interpolate bind params");
      rt-5.0.1/t/web/articles-links.t000644 000765 000024 00000003250 14005011336 017132 0ustar00sunnavystaff000000 000000 use strict;
      use warnings;
      
      use RT::Test tests => 18;
      
      RT->Config->Set( MasonLocalComponentRoot => RT::Test::get_abs_relocatable_dir('html') );
      
      my ($baseurl, $m) = RT::Test->started_ok;
      
      my $queue = RT::Queue->new(RT->SystemUser);
      $queue->Load('General');
      
      my $class = RT::Class->new(RT->SystemUser);
      my ($ok, $msg) = $class->Create(Name => "issues");
      ok($ok, "created class: $msg");
      
      ($ok, $msg) = $class->AddToObject($queue);
      ok($ok, "applied class to General: $msg");
      
      my $article = RT::Article->new(RT->SystemUser);
      ($ok, $msg) = $article->Create(Name => "instance of ticket #17421", Class => $class->id);
      ok($ok, "created article: $msg");
      
      ok($m->login, "logged in");
      
      my $ticket = RT::Test->create_ticket(Queue => $queue->Id, Subject => 'oh wow! an AUTOLOAD bug');
      
      $m->goto_ticket($ticket->id);
      $m->follow_link_ok({text => 'Reply'});
      
      $m->form_name('TicketUpdate');
      $m->field('IncludeArticleId' => $article->Id);
      $m->submit;
      
      $m->content_contains('instance of ticket #17421', 'got the name of the article in the ticket');
      
      # delete RT::Article's Name method on the server so we'll need to AUTOLOAD it
      my $clone = $m->clone;
      $clone->get_ok('/delete-article-name-method.html');
      like($clone->content, qr/\{deleted\}/);
      
      $m->form_name('TicketUpdate');
      $m->click('SubmitTicket');
      
      $m->follow_link_ok({text => 'Links'});
      
      $m->text_contains('Article #' . $article->id . ': instance of ticket #17421', 'Article appears with its name in the links table');
      
      my $refers_to = $ticket->RefersTo;
      is($refers_to->Count, 1, 'the ticket has a refers-to link');
      is($refers_to->First->TargetURI->URI, 'fsck.com-article://example.com/article/' . $article->Id, 'when we included the article it created a refers-to');
      
      rt-5.0.1/t/web/rest-search-group.t000644 000765 000024 00000004455 14005011336 017570 0ustar00sunnavystaff000000 000000 use strict;
      use warnings;
      use RT::Test tests => undef;
      
      my $group_foo = RT::Group->new($RT::SystemUser);
      $group_foo->CreateUserDefinedGroup( Name => 'foo' );
      
      my $group_bar = RT::Group->new($RT::SystemUser);
      $group_bar->CreateUserDefinedGroup( Name => 'bar' );
      
      my $group_baz = RT::Group->new($RT::SystemUser);
      $group_baz->CreateUserDefinedGroup( Name => 'baz' );
      $group_baz->SetDisabled(1);
      
      my ( $baseurl, $m ) = RT::Test->started_ok;
      
      ok( $m->login, 'logged in' );
      
      search_groups_ok(
          { query => 'id = ' . $group_foo->id },
          [ $group_foo->id . ': foo' ],
          'search by id'
      );
      
      search_groups_ok(
          {
              query  => 'Name = ' . $group_foo->Name,
              format => 's',
              fields => 'id,name',
          },
          [ "id\tName", $group_foo->id . "\tfoo" ],
          'search by name with customized fields'
      );
      
      search_groups_ok(
          { query => 'foo = 3' },
          ['Invalid field specification: foo'],
          'invalid field'
      );
      
      search_groups_ok(
          { query => 'id foo 3' },
          ['Invalid operator specification: foo'],
          'invalid op'
      );
      
      search_groups_ok(
          { query => '', orderby => 'id' },
          [ $group_foo->id . ': foo', $group_bar->id . ': bar', ],
          'order by id'
      );
      
      search_groups_ok(
          { query => '', orderby => 'name' },
          [ $group_bar->id . ': bar', $group_foo->id . ': foo' ],
          'order by name'
      );
      
      search_groups_ok(
          { query => '', orderby => '+name' },
          [ $group_bar->id . ': bar', $group_foo->id . ': foo' ],
          'order by +name'
      );
      
      search_groups_ok(
          { query => '', orderby => '-name' },
          [ $group_foo->id . ': foo', $group_bar->id . ': bar' ],
          'order by -name'
      );
      
      search_groups_ok(
          { query => 'Disabled = 0', orderby => 'id' },
          [ $group_foo->id . ': foo', $group_bar->id . ': bar' ],
          'enabled groups'
      );
      
      search_groups_ok(
          { query => 'Disabled = 1', orderby => 'id' },
          [ $group_baz->id . ': baz' ],
          'disabled groups'
      );
      
      sub search_groups_ok {
          local $Test::Builder::Level = $Test::Builder::Level + 1;
          my $query    = shift;
          my $expected = shift;
          my $name     = shift || 'search groups';
      
          my $uri = URI->new("$baseurl/REST/1.0/search/group");
          $uri->query_form(%$query);
          $m->get_ok($uri);
      
          my @lines = split /\n/, $m->content;
          shift @lines;    # header
          shift @lines;    # empty line
      
          is_deeply( \@lines, $expected, $name );
      
      }
      
      done_testing;
      rt-5.0.1/t/web/signatures.t000644 000765 000024 00000007136 14005011336 016401 0ustar00sunnavystaff000000 000000 use strict;
      use warnings;
      
      use RT::Test tests => undef;
      use HTML::Entities qw/decode_entities/;
      
      my ($baseurl, $m) = RT::Test->started_ok;
      ok( $m->login, 'logged in' );
      
      my $root = RT::Test->load_or_create_user( Name => 'root' );
      my ($ok) = $root->SetSignature("Signature one\nSignature two\n");
      ok($ok, "Set signature");
      
      my $t = RT::Test->create_ticket(
          Queue   => 'General',
          Subject => 'Signature quoting',
          Content => "First\nSecond\nThird\n",
      );
      
      my $initial = $t->Transactions->First->id;
      
      sub template_is {
          my (%args) = (
              HTML        => 0,
              Quote       => 0,
              BeforeQuote => 0,
          );
          my $expected = pop(@_);
          $args{$_}++ for @_;
      
          my $prefs = $root->Preferences($RT::System);
          $prefs->{SignatureAboveQuote} = $args{BeforeQuote};
          $prefs->{MessageBoxRichText}  = $args{HTML};
          ($ok) = $root->SetPreferences($RT::System, $prefs);
          ok($ok, "Preferences updated");
      
          my $url = "/Ticket/Update.html?id=" . $t->id;
          $url .= "&QuoteTransaction=$initial" if $args{Quote};
          $m->get_ok($url);
      
          $m->form_name('TicketUpdate');
          my $value = $m->value("UpdateContent");
      
          # Work around a bug in Mechanize, wherein it thinks newlines
          # following textareas are significant -- browsers do not.
          $value =~ s/^\n//;
      
          # For ease of interpretation, replace blank lines with dots, and
          # put a $ after trailing whitespace.
          my $display = $value;
          $display =~ s/^$/./mg;
          $display =~ s/([ ]+)$/$1\$/mg;
      
          # Remove the timestamp from the quote header
          $display =~ s/On \w\w\w \w\w\w+ \d\d \d\d:\d\d:\d\d \d\d\d\d, \w+ wrote:/Someone wrote:/;
      
          is($display, $expected, "Content matches expected");
      
          my $trim = RT::Interface::Web::StripContent(
              CurrentUser    => RT::CurrentUser->new($root),
              Content        => $value,
              ContentType    => $args{HTML} ? "text/html" : "text/plain",
              StripSignature => 1,
          );
          if ($args{Quote}) {
              ok($trim, "Counts as not empty");
          } else {
              is($trim, '', "Counts as empty");
          }
      }
      
      
      ### Text
      
      subtest "Non-HTML, no reply" => sub {
          template_is(<<'EOT') };
      .
      .
      -- $
      Signature one
      Signature two
      EOT
      
      
      subtest "Non-HTML, no reply, before quote (which is irrelevant)" => sub {
          template_is(qw/BeforeQuote/, <<'EOT') };
      .
      .
      -- $
      Signature one
      Signature two
      EOT
      
      subtest "Non-HTML, reply" => sub {
          template_is(qw/Quote/, <<'EOT') };
      Someone wrote:
      > First
      > Second
      > Third
      .
      .
      .
      -- $
      Signature one
      Signature two
      EOT
      
      subtest "Non-HTML, reply, before quote" => sub {
          template_is(qw/Quote BeforeQuote/, <<'EOT') };
      .
      .
      -- $
      Signature one
      Signature two
      .
      Someone wrote:
      > First
      > Second
      > Third
      EOT
      
      
      
      ### HTML
      
      my $quote = '
      Someone wrote:
      ' .'
      ' .'
      '
          ."First\nSecond\nThird\n"
          .'
      '; subtest "HTML, no reply" => sub { template_is( qw/HTML/, '

      -- 
      Signature one
      Signature two

      ', ) }; subtest "HTML, no reply, before quote (which is irrelevant)" => sub { template_is( qw/HTML BeforeQuote/, '

      -- 
      Signature one
      Signature two

      ', ) }; subtest "HTML, reply" => sub { template_is( qw/HTML Quote/, $quote.'

      -- 
      Signature one
      Signature two

      ', ) }; subtest "HTML, reply, before quote" => sub { template_is( qw/HTML Quote BeforeQuote/, '

      -- 
      Signature one
      Signature two

      ' . "
      " . $quote, ) }; done_testing; rt-5.0.1/t/web/download_user_info.t000644 000765 000024 00000005407 14005011336 020074 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; RT->Config->Set( UserTicketDataResultFormat => "'__id__', '__Subject__', '__Status__', '__QueueName__', '__Owner__', '__Priority__', '__Requestors__'" ); my ( $baseurl, $agent ) = RT::Test->started_ok; my $url = $agent->rt_base_url; # Login $agent->login( 'root' => 'password' ); { my $root = RT::Test->load_or_create_user( Name => 'root' ); ok $root && $root->id; # We want transactions attached to our user, so not using test method for ticket create my $ticket = RT::Ticket->new($root); $ticket->Create( Subject => 'Test', Requestor => 'root', Queue => 'General' ); my $id = $ticket->id; ok $id; $ticket->Comment( Content => 'Test - Comment' ); $ticket->Correspond( Content => 'Test - Reply' ); my @dates; my $trans = $ticket->Transactions; while ( my $tran = $trans->Next ) { if ( $tran->Type =~ /Create|Comment|Correspond/ ) { push @dates, $tran->CreatedObj->AsString; } } my ( $date_created, $date_commented, $date_correspondence ) = @dates; # Make sure we have the expected amount of transactions is scalar @dates, 3; # TSV file for user record information $agent->get_ok( $url . "Admin/Users/Modify.html?id=" . $root->id ); $agent->follow_link_ok( { text => 'User Data' } ); my $user_info_tsv = <content, $user_info_tsv, "User record information downloaded correctly"; # TSV file for Transactions $agent->get_ok( $url . "Admin/Users/Modify.html?id=" . $root->id ); $agent->follow_link_ok( { text => 'User Transactions' } ); my $transaction_info_tsv = <content, $transaction_info_tsv, "User transaction information downloaded correctly"; # TSV file for user's Tickets $agent->get_ok( $url . "Admin/Users/Modify.html?id=" . $root->id ); $agent->follow_link_ok( { text => 'User Tickets' } ); my $ticket_info_tsv = <content, $ticket_info_tsv, "User tickets downloaded correctly"; } done_testing(); rt-5.0.1/t/web/installer.t000644 000765 000024 00000005053 14005011336 016206 0ustar00sunnavystaff000000 000000 use strict; use warnings; $ENV{RT_TEST_WEB_HANDLER} = 'plack+rt-server'; use RT::Test tests => undef, nodb => 1, server_ok => 1; my ($base, $m) = RT::Test->started_ok; $m->warning_like(qr/If this is a new installation of RT/, "Got startup warning"); $m->get_ok($base); like $m->uri, qr/Install/, 'at installer'; diag "Testing language change"; { $m->submit_form_ok( { with_fields => { Lang => 'fr', }, button => 'ChangeLang', }, 'change language to french' ); $m->content_like(qr/Pour commencer/); $m->submit_form_ok( { with_fields => { Lang => 'en', }, button => 'ChangeLang', }, 'change language to english' ); $m->content_like(qr/Getting started/); } diag "Walking through install screens setting defaults"; { $m->click_ok('Run'); # Database type $m->content_contains('DatabaseType'); $m->content_contains($_, "found database $_") for qw(MySQL PostgreSQL Oracle SQLite); $m->submit(); # Database details $m->content_contains('DatabaseName'); if (RT->Config->Get('DatabaseType') eq 'SQLite') { $m->submit; } else { $m->submit_form(with_fields => { DatabaseAdmin => $ENV{RT_DBA_USER}, DatabaseAdminPassword => $ENV{RT_DBA_PASSWORD}, DatabasePassword => "rt_pass", }); } $m->content_contains('Connection succeeded'); $m->submit_form_ok({ button => 'Next' }); # Basic options $m->submit_form_ok({ with_fields => { Password => 'password', } }, 'set root password'); # Mail options my $sendmail = File::Spec->rel2abs( File::Spec->catfile( 't', 'security', 'fake-sendmail' ) ); $m->submit_form_ok({ with_fields => { OwnerEmail => 'admin@example.com', SendmailPath => $sendmail, }, }, 'set admin email'); # Mail addresses $m->submit_form_ok({ with_fields => { CorrespondAddress => 'rt@example.com', CommentAddress => 'rt-comment@example.com', }, }, 'set addresses'); # Initialize database $m->content_contains('database'); $m->submit(); # Finish $m->content_contains('/RT_SiteConfig.pm'); $m->content_contains('Finish'); $m->submit(); $m->content_contains('Login'); ok $m->login(), 'logged in'; } RT::Test::__drop_database(); done_testing; rt-5.0.1/t/web/static/000755 000765 000024 00000000000 14005011336 015310 5ustar00sunnavystaff000000 000000 rt-5.0.1/t/web/ticket-create-utf8.t000644 000765 000024 00000006560 14005011336 017625 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 49; my $ru_test = "\x{442}\x{435}\x{441}\x{442}"; my $ru_support = "\x{43f}\x{43e}\x{434}\x{434}\x{435}\x{440}\x{436}\x{43a}\x{430}"; # latin-1 is very special in perl, we should test everything with latin-1 umlauts # and not-ascii+not-latin1, for example cyrillic my $l1_test = Encode::decode('latin-1', "t\xE9st"); my $l1_support = Encode::decode('latin-1', "supp\xF6rt"); my $q = RT::Test->load_or_create_queue( Name => 'Regression' ); ok $q && $q->id, 'loaded or created queue'; RT::Test->set_rights( Principal => 'Everyone', Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ReplyToTicket', 'ModifyTicket'], ); my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in'; # create a ticket with a subject only foreach my $test_str ( $ru_test, $l1_test ) { ok $m->goto_create_ticket( $q ), "go to create ticket"; $m->form_name('TicketCreate'); $m->field( Subject => $test_str ); $m->click('SubmitTicket'); $m->content_like( qr{]*>\s*\Q$test_str\E\s*}i, 'header on the page' ); my $ticket = RT::Test->last_ticket; is $ticket->Subject, $test_str, "correct subject"; } # create a ticket with a subject and content foreach my $test_str ( $ru_test, $l1_test ) { foreach my $support_str ( $ru_support, $l1_support ) { ok $m->goto_create_ticket( $q ), "go to create ticket"; $m->form_name('TicketCreate'); $m->field( Subject => $test_str ); $m->field( Content => $support_str ); $m->click('SubmitTicket'); $m->content_like( qr{]*>\s*\Q$test_str\E\s*}i, 'header on the page' ); $m->content_contains( $support_str, 'content on the page' ); my $ticket = RT::Test->last_ticket; is $ticket->Subject, $test_str, "correct subject"; } } # create a ticket with a subject and content foreach my $test_str ( $ru_test, $l1_test ) { foreach my $support_str ( $ru_support, $l1_support ) { ok $m->goto_create_ticket( $q ), "go to create ticket"; $m->form_name('TicketCreate'); $m->field( Subject => $test_str ); $m->field( Content => $support_str ); $m->click('SubmitTicket'); $m->content_like( qr{]*>\s*\Q$test_str\E\s*}i, 'header on the page' ); $m->content_contains( $support_str, 'content on the page' ); my $ticket = RT::Test->last_ticket; is $ticket->Subject, $test_str, "correct subject"; } } my $article = RT::Article->new($RT::SystemUser); my ( $id, $msg ) = $article->Create( Class => 'General', Name => 'My Article', 'CustomField-Content' => 'My Article Test Content', ); ok( $id, $msg ); (my $ret, $msg) = $article->Load(1); ok ($ret, $msg); my $queue = RT::Queue->new(RT->SystemUser); $queue->Load('General'); ok( $queue, 'Loaded General Queue' ); ($ret, $msg) = $queue->SetDefaultValue( Name => 'Article', Value => $article->Id); ok( $ret, $msg ); ok $m->login(root => 'password'), "logged in"; $m->goto_create_ticket('General'); $m->scraped_id_is('Content', '#1: My Article
      --------------
      Content:
      -------
      My Article Test Content
      '); rt-5.0.1/t/web/command_line_with_unknown_field.t000644 000765 000024 00000004137 14005011336 022615 0ustar00sunnavystaff000000 000000 use strict; use warnings; use File::Spec (); use Test::Expect; use RT::Test tests => 21, actual_server => 1; my ($baseurl, $m) = RT::Test->started_ok; my $rt_tool_path = "$RT::BinPath/rt"; $ENV{'RTUSER'} = 'root'; $ENV{'RTPASSWD'} = 'password'; $RT::Logger->debug("Connecting to server at ".RT->Config->Get('WebBaseURL')); $ENV{'RTSERVER'} =RT->Config->Get('WebBaseURL') ; $ENV{'RTDEBUG'} = '1'; $ENV{'RTCONFIG'} = '/dev/null'; expect_run( command => "$rt_tool_path shell", 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"); expect_handle->before() =~ /Ticket (\d+) created/; my $ticket_id = $1; expect_send("edit ticket/$ticket_id set marge=simpson", 'set unknown field'); expect_like(qr/marge: Unknown field/, 'marge is unknown field'); expect_like(qr/marge: simpson/, 'the value we set for marge is shown too'); expect_send("edit ticket/$ticket_id set homer=simpson", 'set unknown field'); expect_like(qr/homer: Unknown field/, 'homer is unknown field'); expect_like(qr/homer: simpson/, 'the value we set for homer is shown too'); expect_send( q{create -t ticket set requestors='foo@example.com, bar@example.com'}, "create ticket with field 'requestors'" ); expect_like(qr/Ticket \d+ created/, "Created the ticket"); expect_handle->before() =~ /Ticket (\d+) created/; $ticket_id = $1; expect_send("show ticket/$ticket_id", 'check requestors'); expect_like(qr/From: (?:foo\@example\.com, bar\@example\.com|bar\@example\.com, foo\@example\.com)/, "requestors are set correctly"); expect_quit(); # you may encounter warning like Use of uninitialized value $ampm # ... in Time::ParseDate my @warnings = grep { $_ !~ /\$ampm/ } $m->get_warnings; is( scalar @warnings, 0, 'no extra warnings' ); 1; # needed to avoid a weird exit value from expect_quit rt-5.0.1/t/web/dashboards-permissions.t000644 000765 000024 00000002400 14005011336 020665 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => 8; my ($baseurl, $m) = RT::Test->started_ok; my $url = $m->rt_base_url; # create user and queue {{{ my $user_obj = RT::User->new(RT->SystemUser); my ($ok, $msg) = $user_obj->LoadOrCreateByEmail('customer@example.com'); ok($ok, 'ACL test user creation'); $user_obj->SetName('customer'); $user_obj->SetPrivileged(1); ($ok, $msg) = $user_obj->SetPassword('customer'); $user_obj->PrincipalObj->GrantRight(Right => 'ModifySelf'); my $currentuser = RT::CurrentUser->new($user_obj); my $queue = RT::Queue->new(RT->SystemUser); $queue->Create(Name => 'SearchQueue'.$$); $user_obj->PrincipalObj->GrantRight(Right => $_, Object => $queue) for qw/SeeQueue ShowTicket OwnTicket/; $user_obj->PrincipalObj->GrantRight(Right => $_, Object => $RT::System) for qw/SubscribeDashboard CreateOwnDashboard SeeOwnDashboard ModifyOwnDashboard DeleteOwnDashboard/; ok $m->login(customer => 'customer'), "logged in"; $m->follow_link_ok( {id => 'reports-dashboard_create'}); $m->form_name('ModifyDashboard'); is_deeply([$m->current_form->find_input('Privacy')->possible_values], ["RT::User-" . $user_obj->Id], "the only selectable privacy is user"); $m->content_lacks('Delete', "Delete button hidden because we are creating"); rt-5.0.1/t/web/ticket_display_unset_fields.t000644 000765 000024 00000006762 14005011336 021775 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef, config => 'Set( $HideUnsetFieldsOnDisplay, 1 );'; my @link_classes = qw( DependsOn DependedOnBy MemberOf Members RefersTo ReferredToBy ); my $foo = RT::Test->create_ticket( Queue => 'General', Subject => 'test display page', ); my $dep = RT::Test->create_ticket( Queue => 'General', Subject => 'dep ticket', ); my $bar = RT::Test->create_ticket( Queue => 'General', Subject => 'depend ticket', Starts => '2011-07-08 00:00:00', Started => '2011-07-09 00:00:00', Resolved => '2011-07-11 00:00:00', Due => '2011-07-12 00:00:00', Cc => 'foo@example.com', AdminCc => 'admin@example.com', DependsOn => [ $dep->id ], ); $bar->SetTold; my ( $baseurl, $m ) = RT::Test->started_ok; diag "URL is: $baseurl"; diag "test with root"; { $m->login; $m->goto_ticket( $foo->id ); my $dom = $m->dom; for my $class (qw/starts started due resolved cc admincc/) { is $dom->find(qq{div.$class.unset-field})->size, 1, "found unset $class"; } is $dom->find(qq{div.told:not(.unset-field)})->size, 1, "has Told as root can modify it"; for my $class (@link_classes) { is $dom->find(qq{div.$class:not(.unset-field)})->size, 1, "has $class as root can create"; } $m->goto_ticket( $bar->id ); $dom = $m->dom; for my $class (qw/starts started due resolved cc admincc/) { is $dom->find(qq{div.$class:not(.unset-field)})->size, 1, "has $class as value is set"; } } diag "test without ModifyTicket right"; { my $user = RT::Test->load_or_create_user( Name => 'foo', Password => 'password' ); RT::Test->set_rights( Principal => $user, Right => ['ShowTicket'] ); $m->login( 'foo', 'password', logout => 1 ); $m->goto_ticket( $foo->id ); my $dom = $m->dom; is $dom->find(qq{div.told.unset-field})->size, 1, "lacks Told as it is unset and user has no modify right"; for my $class ( @link_classes ) { is $dom->find(qq{div.$class.unset-field})->size, 1, "lacks $class as it is unset and user has no modify right"; } $m->goto_ticket( $bar->id ); $dom = $m->dom; is $dom->find(qq{div.DependsOn:not(.unset-field)})->size, 1, "has Depends on as it is set"; } diag "Test unset custom fields"; { my $cf = RT::Test->load_or_create_custom_field( Name => 'TextArea', Type => 'Text', Queue => 0, LookupType => 'RT::Queue-RT::Ticket', ); ok $cf && $cf->id, "Created TextArea CF"; $m->login( 'root', 'password', logout => 1 ); $m->goto_ticket( $foo->id ); my $dom = $m->dom; my $cfid = $cf->Id; is $dom->find(qq{div.custom-field.custom-field-$cfid.unset-field})->size, 1, "found unset custom field"; # open ticket "Basics" page my $EditUrl = "/Ticket/Modify.html?id=" . $foo->id; $m->get_ok($EditUrl, "Fetched $EditUrl"); $m->content_contains('TextArea:'); my $cf_input = RT::Interface::Web::GetCustomFieldInputName( Object => $foo, CustomField => $cf, ); $m->submit_form_ok({ with_fields => { $cf_input => 'some unique content', $cf_input . '-Magic' => "1", }, }, 'submitted form to initially set CFs'); $m->goto_ticket( $foo->id ); $dom = $m->dom; $m->content_contains('some unique content'); isnt $dom->find(qq{div.customfield.unset-field})->size, 1, "no unset custom fields"; } done_testing; rt-5.0.1/t/web/cf_select_one.t000644 000765 000024 00000014751 14005011336 017006 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in as root'; my $cf_name = 'test select one value'; my $cfid; diag "Create a CF"; { $m->follow_link( id => 'admin-custom-fields-create'); $m->submit_form( form_name => "ModifyCustomField", fields => { Name => $cf_name, TypeComposite => 'Select-1', LookupType => 'RT::Queue-RT::Ticket', }, ); $m->content_contains('Object created', 'created CF sucessfully' ); $cfid = $m->form_name('ModifyCustomField')->value('id'); ok $cfid, "found id of the CF in the form, it's #$cfid"; } diag "add 'qwe', 'ASD', '0' and ' foo ' as values to the CF"; { foreach my $value(qw(qwe ASD 0), 'foo') { $m->submit_form( form_name => "AddCustomFieldValue", fields => { "CustomField-". $cfid ."-Value-new-Name" => $value, }, button => 'AddValue', ); $m->content_contains("Custom field value $value added", 'added a value to the CF' ); # or diag $m->content; my $v = $value; $v =~ s/^\s+$//; $v =~ s/\s+$//; $m->content_contains("value=\"$v\"", 'the added value is right' ); } } my $queue = RT::Test->load_or_create_queue( Name => 'General' ); ok $queue && $queue->id, 'loaded or created queue'; diag "apply the CF to General queue"; { $m->follow_link( id => 'admin-queues'); $m->follow_link( text => 'General' ); $m->title_is(q/Configuration for queue General/, 'admin-queue: general'); $m->follow_link( id => 'page-custom-fields-tickets'); $m->title_is(q/Custom Fields for queue General/, 'admin-queue: general cfid'); $m->form_name('EditCustomFields'); $m->tick( "AddCustomField" => $cfid ); $m->click('UpdateCFs'); $m->content_contains("Added custom field $cf_name to General", 'TCF added to the queue' ); } my $tid; diag "create a ticket using API with 'asd'(not 'ASD') as value of the CF"; { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($txnid, $msg); ($tid, $txnid, $msg) = $ticket->Create( Subject => 'test', Queue => $queue->id, "CustomField-$cfid" => 'asd', ); ok $tid, "created ticket"; diag $msg if $msg; # we use lc as we really don't care about case # so if later we'll add canonicalization of value # test should work is lc $ticket->FirstCustomFieldValue( $cf_name ), 'asd', 'assigned value of the CF'; } diag "check that values of the CF are case insensetive(asd vs. ASD)"; { ok $m->goto_ticket( $tid ), "opened ticket's page"; $m->follow_link( id => 'page-basics'); $m->title_like(qr/Modify ticket/i, 'modify ticket'); $m->content_contains($cf_name, 'CF on the page'); my $value = $m->form_name('TicketModify')->value("Object-RT::Ticket-$tid-CustomField-$cfid-Values"); is lc $value, 'asd', 'correct value is selected'; $m->submit; $m->content_unlike(qr/\Q$cf_name\E.*?changed/mi, 'field is not changed'); $value = $m->form_name('TicketModify')->value("Object-RT::Ticket-$tid-CustomField-$cfid-Values"); is lc $value, 'asd', 'the same value is still selected'; my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $tid ); ok $ticket->id, 'loaded the ticket'; is lc $ticket->FirstCustomFieldValue( $cf_name ), 'asd', 'value is still the same'; } diag "check that 0 is ok value of the CF"; { ok $m->goto_ticket( $tid ), "opened ticket's page"; $m->follow_link( id => 'page-basics'); $m->title_like(qr/Modify ticket/i, 'modify ticket'); $m->content_contains($cf_name, 'CF on the page'); my $value = $m->form_name('TicketModify')->value("Object-RT::Ticket-$tid-CustomField-$cfid-Values"); is lc $value, 'asd', 'correct value is selected'; $m->select("Object-RT::Ticket-$tid-CustomField-$cfid-Values" => 0 ); $m->submit; $m->content_like(qr/\Q$cf_name\E.*?changed/mi, 'field is changed'); $m->content_lacks('0 is no longer a value for custom field', 'no bad message in results'); $value = $m->form_name('TicketModify')->value("Object-RT::Ticket-$tid-CustomField-$cfid-Values"); is lc $value, '0', 'new value is selected'; my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $tid ); ok $ticket->id, 'loaded the ticket'; is lc $ticket->FirstCustomFieldValue( $cf_name ), '0', 'API returns correct value'; } diag "check that we can set empty value when the current is 0"; { ok $m->goto_ticket( $tid ), "opened ticket's page"; $m->follow_link( id => 'page-basics'); $m->title_like(qr/Modify ticket/i, 'modify ticket'); $m->content_contains($cf_name, 'CF on the page'); my $value = $m->form_name('TicketModify')->value("Object-RT::Ticket-$tid-CustomField-$cfid-Values"); is lc $value, '0', 'correct value is selected'; $m->select("Object-RT::Ticket-$tid-CustomField-$cfid-Values" => '' ); $m->submit; $m->content_contains('0 is no longer a value for custom field', '0 is no longer a value'); $value = $m->form_name('TicketModify')->value("Object-RT::Ticket-$tid-CustomField-$cfid-Values"); is $value, '', '(no value) is selected'; my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $tid ); ok $ticket->id, 'loaded the ticket'; is $ticket->FirstCustomFieldValue( $cf_name ), undef, 'API returns correct value'; } diag "check that a default value is displayed"; { my $default_ticket_id = RT::Test->create_ticket(Queue => 'General'); $m->get_ok("/Ticket/Modify.html?id=" . $default_ticket_id->Id . "&CustomField-$cfid=qwe"); $m->content_like(qr/\
      }, "no reminder titlebar"); $m->follow_link_ok({id => 'page-reminders'}); $m->title_is("Reminders for ticket #" . $ticket->id . ": " . $ticket->Subject); $m->text_contains('New reminder:', 'can create a new reminder'); $m->content_unlike(qr{Check box to complete}, "we don't display this text when there are no reminders"); $m->content_unlike(qr{]*>Reminders?
      }, "no reminder titlebar"); $m->goto_ticket($ticket->id); $m->form_name('UpdateReminders'); $m->field( 'NewReminder-Subject' => "baby's first reminder" ); $m->submit; $m->content_contains("Reminder 'baby's first reminder': Created"); $ticket->SetStatus('deleted'); is( $ticket->Status, 'deleted', 'deleted ticket' ); $m->form_name('UpdateReminders'); $m->field( 'NewReminder-Subject' => "link to a deleted ticket" ); $m->submit; $m->content_contains("Can't link to a deleted ticket"); $m->get_ok('/Tools/MyReminders.html'); $m->content_contains( "baby's first reminder", 'got the reminder even the ticket is deleted' ); $m->goto_ticket( $ticket->id ); $m->content_lacks('New reminder:', "can't create a new reminder"); $m->text_contains('Check box to complete', "we DO display this text when there are reminders"); $m->content_like(qr{Reminders}, "now we have a reminder titlebar"); $m->text_contains("baby's first reminder", "display the reminder's subject"); my $reminders = RT::Reminders->new($user); $reminders->Ticket($ticket->id); my $col = $reminders->Collection; is($col->Count, 1, 'got a reminder'); my $reminder = $col->First; is($reminder->Subject, "baby's first reminder"); my $reminder_id = $reminder->id; is($reminder->Status, 'open'); $ticket->SetStatus('open'); is( $ticket->Status, 'open', 'changed back to new' ); $m->goto_ticket($ticket->id); $m->text_contains('New reminder:', "can create a new reminder"); $m->text_contains('Check box to complete', "we DO display this text when there are reminders"); $m->content_like(qr{Reminders}, "now we have a reminder titlebar"); $m->text_contains("baby's first reminder", "display the reminder's subject"); $m->follow_link_ok({id => 'page-reminders'}); $m->title_is("Reminders for ticket #" . $ticket->id . ": " . $ticket->Subject); $m->form_name('UpdateReminders'); $m->field("Reminder-Subject-$reminder_id" => "changed the subject"); $m->submit; $reminder = RT::Ticket->new($user); $reminder->Load($reminder_id); is($reminder->Subject, 'changed the subject'); is($reminder->Status, 'open'); $m->goto_ticket($ticket->id); $m->form_name('UpdateReminders'); $m->tick("Complete-Reminder-$reminder_id" => 1); $m->submit; $reminder = RT::Ticket->new($user); $reminder->Load($reminder_id); is($reminder->Status, 'resolved'); $m->text_contains('New reminder:', 'can create a new reminder'); $m->content_unlike(qr{Check box to complete}, "we don't display this text when there are open reminders"); $m->content_unlike(qr{]*>Reminders?
      }, "no reminder titlebar"); $m->content_unlike(qr{baby's first reminder}, "we don't display resolved reminders"); $m->follow_link_ok({id => 'page-reminders'}); $m->title_is("Reminders for ticket #" . $ticket->id . ": " . $ticket->Subject); $m->text_contains('New reminder:', 'can create a new reminder'); $m->text_contains('Check box to complete', "we DO display this text when there are reminders"); $m->content_contains("changed the subject", "display the resolved reminder's subject"); # make sure that when we submit the form, it doesn't accidentally reopen # resolved reminders $m->goto_ticket($ticket->id); $m->form_name('UpdateReminders'); $m->submit; $reminder = RT::Ticket->new($user); $reminder->Load($reminder_id); is($reminder->Status, 'resolved'); rt-5.0.1/t/web/ticket_preserve_basics.t000644 000765 000024 00000005650 14005011336 020736 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my $ticket = RT::Test->create_ticket( Subject => 'test ticket basics', Queue => 1, ); my ( $url, $m ) = RT::Test->started_ok; ok( $m->login, 'logged in' ); my $root = RT::Test->load_or_create_user( Name => 'root' ); # Failing test where the time units are not preserved when you # click 'Add more files' on Display my @form_tries = ( {Subject => "hello rt"}, {Status => "open"}, {Owner => $root->id}, ( map +{ "Time$_" => undef, "Time$_-TimeUnits" => 'hours', }, qw/Estimated Worked Left/ ), ( map +{ "Time$_" => '1', "Time$_-TimeUnits" => 'hours', }, qw/Estimated Worked Left/ ), {InitialPriority => "10"}, {FinalPriority => "10"}, ); for my $try (@form_tries) { $m->goto_create_ticket(1); $m->form_name('TicketCreate'); $m->set_fields(%$try); $m->click('AddMoreAttach'); $m->form_name('TicketCreate'); for my $field (keys %$try) { is( $m->value($field), defined($try->{$field}) ? $try->{$field} : '', "field $field is the same after the form was submitted" ); } } # Test for time unit preservation in Jumbo for my $try (@form_tries) { my $jumbo_ticket = RT::Test->create_ticket( Subject => 'test jumbo ticket basics', Queue => 1, ); local($try->{Priority}) = delete local($try->{InitialPriority}) if exists $try->{InitialPriority}; $m->get( $url . "/Ticket/ModifyAll.html?id=" . $jumbo_ticket->id ); $m->form_name('TicketModifyAll'); $m->set_fields(%$try); $m->click('AddMoreAttach'); $m->form_name('TicketModifyAll'); for my $field (keys %$try) { is( $m->value($field), defined($try->{$field}) ? $try->{$field} : '', "field $field is the same after the Jumbo form was submitted" ); } } my $cf = RT::Test->load_or_create_custom_field( Name => 'CF1', Type => 'Freeform', Pattern => '.', # mandatory Queue => 'General', ); # More time unit testing by a failing CF validation $m->get_ok($url.'/Admin/CustomFields/Objects.html?id='.$cf->id); $m->form_with_fields('UpdateObjs'); $m->tick('AddCustomField-'.$cf->id => '0'); # Make CF global $m->click('UpdateObjs'); $m->text_contains("Globally added custom field CF1", 'CF applied globally'); # Test for preservation when a ticket is submitted and CF validation fails for my $try (@form_tries) { $m->goto_create_ticket(1); $m->form_name('TicketCreate'); $m->set_fields(%$try); $m->submit(); $m->form_name('TicketCreate'); for my $field (keys %$try) { is( $m->value($field), defined($try->{$field}) ? $try->{$field} : '', "field $field is the same after the form was submitted" ); } } done_testing(); rt-5.0.1/t/web/search_linkdisplay.t000644 000765 000024 00000003437 14005011336 020065 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ( $baseurl, $m ) = RT::Test->started_ok; my $ticket = RT::Test->create_ticket( Queue => 'General', Subject => 'ticket foo', ); my $generic_url = 'http://generic_url.example.com'; my $link = RT::Link->new( RT->SystemUser ); my ($id,$msg) = $link->Create( Base => $ticket->URI, Target => $generic_url, Type => 'RefersTo' ); ok($id, $msg); my $ticket2 = RT::Test->create_ticket( Queue => 'General', Subject => 'ticket bar', ); $link = RT::Link->new( RT->SystemUser ); ($id,$msg) = $link->Create( Base => $ticket->URI, Target => $ticket2->URI, Type => 'RefersTo' ); ok($id, $msg); my $class = RT::Class->new( RT->SystemUser ); ($id, $msg) = $class->Create( Name => 'Test Class' ); ok ($id, $msg); my $article = RT::Article->new( RT->SystemUser ); ($id, $msg) = $article->Create( Class => $class->Name, Summary => 'Test Article' ); ok ($id, $msg); $article->Load($id); $link = RT::Link->new( RT->SystemUser ); ($id,$msg) = $link->Create( Base => $ticket->URI, Target => $article->URI, Type => 'RefersTo' ); ok($id, $msg); ok( $m->login, 'logged in' ); $m->get_ok("/Search/Results.html?Format=id,RefersTo;Query=id=".$ticket->Id); $m->title_is( 'Found 1 ticket', 'title' ); my $ref = $m->find_link( url_regex => qr!generic_url! ); ok( $ref, "found generic link" ); is( $ref->text, $generic_url, $generic_url . " is displayed" ); $ref = $m->find_link( url_regex => qr!/Ticket/Display.html! ); ok( $ref, "found ticket link" ); is( $ref->text, "#".$ticket2->Id.": ticket bar", $ticket2->Id . " is displayed" ); $ref = $m->find_link( url_regex => qr!/Article/Display.html! ); ok( $ref, "found article link" ); is( $ref->text, $article->URIObj->Resolver->AsString, $article->URIObj->Resolver->AsString . " is displayed" ); done_testing; rt-5.0.1/t/web/private-components.t000644 000765 000024 00000002256 14005011336 020050 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 24; my ($baseurl, $agent) = RT::Test->started_ok; ok $agent->login, 'logged in'; $agent->get("/Elements/Refresh?Name=private"); is($agent->status, 403); $agent->content_lacks("private"); $agent->content_lacks("Refresh this page every"); $agent->get("/Ticket/Elements/ShowTime?minutes=42"); is($agent->status, 403); $agent->content_lacks("42 min"); $agent->get("/Widgets/TitleBox?title=private"); is($agent->status, 403); $agent->content_lacks("private"); $agent->get("/m/_elements/header?title=private"); is($agent->status, 403); $agent->content_lacks("private"); $agent->get("/autohandler"); is($agent->status, 403); $agent->content_lacks("comp called without component"); $agent->get("/NoAuth/js/autohandler"); is($agent->status, 403); $agent->content_lacks("no next component"); $agent->get("/l"); is($agent->status, 403); $agent->content_lacks("No handle/phrase"); $agent->get("/%61utohandler"); is($agent->status, 403); $agent->content_lacks("comp called without component"); $agent->get("/%45lements/Refresh?Name=private"); is($agent->status, 403); $agent->content_lacks("private"); $agent->content_lacks("Refresh this page every"); rt-5.0.1/t/web/saved_search_update.t000644 000765 000024 00000003721 14005011336 020202 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 18; my $root = RT::User->new( $RT::SystemUser ); $root->Load('root'); my $uid = $root->id; ok( $uid, 'loaded root' ); my $group = RT::Group->new( $RT::SystemUser ); my ($gid) = $group->CreateUserDefinedGroup( Name => 'foo' ); ok( $gid, 'created group foo'); ok( $group->AddMember( $root->PrincipalId ) ); my ( $baseurl, $m ) = RT::Test->started_ok; ok($m->login, 'logged in'); $m->follow_link_ok({text => "Tickets"}, "to query builder"); $m->form_name("BuildQuery"); $m->field(ValueOfid => 10 ); $m->click("AddClause"); $m->content_contains( 'id < 10', "added new clause"); $m->form_name("BuildQuery"); $m->field(SavedSearchDescription => 'user_saved'); $m->click("SavedSearchSave"); $m->form_name("BuildQuery"); is($m->value('SavedSearchDescription'), 'user_saved', "name is correct"); like($m->value('SavedSearchOwner'), qr/^RT::User-\d+$/, "name is correct"); ok( scalar grep { $_ eq "RT::Group-$gid" } $m->current_form->find_input('SavedSearchOwner')->possible_values, 'found group foo' ); $m->field(SavedSearchDescription => 'group_saved'); $m->select(SavedSearchOwner => "RT::Group-$gid"); $m->click("SavedSearchSave"); $m->form_name("BuildQuery"); is($m->value('SavedSearchOwner'), "RT::Group-$gid", "privacy is correct"); is($m->value('SavedSearchDescription'), 'group_saved', "name is correct"); $m->select(SavedSearchOwner => "RT::User-$uid"); $m->field(SavedSearchDescription => 'user_saved'); $m->click("SavedSearchSave"); $m->form_name("BuildQuery"); is($m->value('SavedSearchOwner'), "RT::User-$uid", "privacy is correct"); is($m->value('SavedSearchDescription'), 'user_saved', "name is correct"); $m->select(SavedSearchOwner => "RT::System-1"); $m->field(SavedSearchDescription => 'system_saved'); $m->click("SavedSearchSave"); $m->form_name("BuildQuery"); is($m->value('SavedSearchOwner'), "RT::System-1", "privacy is correct"); is($m->value('SavedSearchDescription'), 'system_saved', "name is correct"); rt-5.0.1/t/web/dashboards-groups.t000644 000765 000024 00000020571 14005011336 017642 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => 64; my ($baseurl, $m) = RT::Test->started_ok; my $url = $m->rt_base_url; # create user and queue my $user_obj = RT::User->new(RT->SystemUser); my ($ok, $msg) = $user_obj->LoadOrCreateByEmail('customer@example.com'); ok($ok, 'ACL test user creation'); $user_obj->SetName('customer'); $user_obj->SetPrivileged(1); ($ok, $msg) = $user_obj->SetPassword('customer'); $user_obj->PrincipalObj->GrantRight(Right => 'ModifySelf'); my $currentuser = RT::CurrentUser->new($user_obj); my $queue = RT::Queue->new(RT->SystemUser); $queue->Create(Name => 'SearchQueue'.$$); $user_obj->PrincipalObj->GrantRight(Right => $_, Object => $queue) for qw/SeeQueue ShowTicket OwnTicket/; # grant the user all these rights so we can make sure that the group rights # are checked and not these as well $user_obj->PrincipalObj->GrantRight(Right => $_, Object => $RT::System) for qw/SubscribeDashboard CreateOwnDashboard SeeOwnDashboard ModifyOwnDashboard DeleteOwnDashboard/; # create and test groups (outer < inner < user) my $inner_group = RT::Group->new(RT->SystemUser); ($ok, $msg) = $inner_group->CreateUserDefinedGroup(Name => "inner", Description => "inner group"); ok($ok, "created inner group: $msg"); my $outer_group = RT::Group->new(RT->SystemUser); ($ok, $msg) = $outer_group->CreateUserDefinedGroup(Name => "outer", Description => "outer group"); ok($ok, "created outer group: $msg"); ($ok, $msg) = $outer_group->AddMember($inner_group->PrincipalId); ok($ok, "added inner as a member of outer: $msg"); ($ok, $msg) = $inner_group->AddMember($user_obj->PrincipalId); ok($ok, "added user as a member of member: $msg"); ok($outer_group->HasMember($inner_group->PrincipalId), "outer has inner"); ok(!$outer_group->HasMember($user_obj->PrincipalId), "outer doesn't have user directly"); ok($outer_group->HasMemberRecursively($inner_group->PrincipalId), "outer has inner recursively"); ok($outer_group->HasMemberRecursively($user_obj->PrincipalId), "outer has user recursively"); ok(!$inner_group->HasMember($outer_group->PrincipalId), "inner doesn't have outer"); ok($inner_group->HasMember($user_obj->PrincipalId), "inner has user"); ok(!$inner_group->HasMemberRecursively($outer_group->PrincipalId), "inner doesn't have outer, even recursively"); ok($inner_group->HasMemberRecursively($user_obj->PrincipalId), "inner has user recursively"); ok $m->login(customer => 'customer'), "logged in"; $m->follow_link_ok({ id => 'reports-dashboard_create'}); $m->form_name('ModifyDashboard'); is_deeply([$m->current_form->find_input('Privacy')->possible_values], ["RT::User-" . $user_obj->Id], "the only selectable privacy is user"); $m->content_lacks('Delete', "Delete button hidden because we are creating"); $user_obj->PrincipalObj->GrantRight(Right => 'CreateGroupDashboard', Object => $inner_group); $m->follow_link_ok({ id => 'reports-dashboard_create'}); $m->form_name('ModifyDashboard'); is_deeply([$m->current_form->find_input('Privacy')->possible_values], ["RT::User-" . $user_obj->Id, "RT::Group-" . $inner_group->Id], "the only selectable privacies are user and inner group (not outer group)"); $m->field("Name" => 'broken dashboard'); $m->field("Privacy" => "RT::Group-" . $inner_group->Id); $m->content_lacks('Delete', "Delete button hidden because we are creating"); $m->click_button(value => 'Create'); $m->content_contains("saved", "we lack SeeGroupDashboard, so we end up back at the index."); $user_obj->PrincipalObj->GrantRight( Right => 'SeeGroupDashboard', Object => $inner_group, ); $m->follow_link_ok({ id => 'reports-dashboard_create'}); $m->form_name('ModifyDashboard'); $m->field("Name" => 'inner dashboard'); $m->field("Privacy" => "RT::Group-" . $inner_group->Id); $m->click_button(value => 'Create'); $m->content_lacks("Permission Denied", "we now have SeeGroupDashboard"); $m->content_contains("Saved dashboard inner dashboard"); $m->content_lacks('Delete', "Delete button hidden because we lack DeleteDashboard"); my $dashboard = RT::Dashboard->new($currentuser); my ($id) = $m->content =~ /name="id" value="(\d+)"/; ok($id, "got an ID, $id"); $dashboard->LoadById($id); is($dashboard->Name, "inner dashboard"); is($dashboard->Privacy, 'RT::Group-' . $inner_group->Id, "correct privacy"); is($dashboard->PossibleHiddenSearches, 0, "all searches are visible"); $m->get_ok("/Dashboards/Modify.html?id=$id"); $m->content_contains("inner dashboard", "we now have SeeGroupDashboard right"); $m->content_lacks("Permission Denied"); $m->content_contains('Subscription', "Subscription link not hidden because we have SubscribeDashboard"); $m->get_ok("/Dashboards/index.html"); $m->content_contains("inner dashboard", "We can see the inner dashboard from the UI"); $m->get_ok("/Prefs/DashboardsInMenu.html"); $m->content_contains("inner dashboard", "Can also see it in the menu options"); my ($group) = grep {$_->isa("RT::Group") and $_->Id == $inner_group->Id} RT::Dashboard->new($currentuser)->_PrivacyObjects; ok($group, "Found the group in the privacy objects list"); my @loading = map {ref($_)."-".$_->Id} RT::Dashboard->new($currentuser)->ObjectsForLoading; is_deeply( \@loading, ["RT::User-".$user_obj->Id, "RT::Group-".$inner_group->Id], "We can load from ourselves (SeeOwnDashboard) and a group we are with SeeGroupDashboard" ); # If you are granted SeeGroupDashboard globally, you can only see # dashboards in groups you are in. $user_obj->PrincipalObj->RevokeRight( Right => 'SeeGroupDashboard', Object => $inner_group, ); $user_obj->PrincipalObj->GrantRight( Right => 'SeeGroupDashboard', Object => RT->System, ); $m->get_ok("/Dashboards/index.html"); $m->content_contains("inner dashboard", "Having SeeGroupDashboard gobally is fine for groups you are in"); @loading = map {ref($_)."-".$_->Id} RT::Dashboard->new($currentuser)->ObjectsForLoading; is_deeply( \@loading, ["RT::User-".$user_obj->Id, "RT::Group-".$inner_group->Id], "SeeGroupDashboard globally still works for groups you are in" ); $inner_group->DeleteMember($user_obj->PrincipalObj->Id); ok(!$outer_group->HasMemberRecursively($user_obj->PrincipalId), "outer no longer has user recursively"); ok(!$inner_group->HasMemberRecursively($user_obj->PrincipalId), "inner no longer has user recursively"); $m->get_ok("/Dashboards/index.html"); $m->content_lacks("inner dashboard", "But global SeeGroupDashboard isn't enough for other groups"); $m->no_warnings_ok; @loading = map {ref($_)."-".$_->Id} RT::Dashboard->new($currentuser)->ObjectsForLoading; is_deeply( \@loading, ["RT::User-".$user_obj->Id], "We only have our SeeOwnDashboard right, as we are no longer in inner" ); # Similarly, if you're a SuperUser, you still only see dashboards for # groups you belong to $user_obj->PrincipalObj->RevokeRight( Right => 'SeeGroupDashboard', Object => RT->System, ); $user_obj->PrincipalObj->GrantRight( Right => 'SuperUser', Object => RT->System, ); $m->get_ok("/Dashboards/index.html"); $m->content_lacks("inner dashboard", "Superuser can't see dashboards in groups they're not in"); @loading = map {ref($_)."-".$_->Id} RT::Dashboard->new($currentuser)->ObjectsForLoading; is_deeply( \@loading, ["RT::User-".$user_obj->Id, "RT::System-1"], "We pick up the system-level SeeDashboard right from superuser" ); @loading = map {ref($_)."-".$_->Id} RT::Dashboard->new($currentuser)->ObjectsForLoading(IncludeSuperuserGroups => 0); is_deeply( \@loading, ["RT::User-".$user_obj->Id, "RT::System-1"], "IncludeSuperusers only cuts out _group_ dashboard objects for loading, not user and system ones" ); $inner_group->AddMember($user_obj->PrincipalId); $m->get_ok("/Dashboards/index.html"); $m->content_contains("inner dashboard", "Superuser can see dashboards in groups they are in"); @loading = map {ref($_)."-".$_->Id} RT::Dashboard->new($currentuser)->ObjectsForLoading; is_deeply( \@loading, ["RT::User-".$user_obj->Id, "RT::Group-".$inner_group->Id, "RT::System-1"], "Becoming a member of the group makes it a possibility" ); @loading = map {ref($_)."-".$_->Id} RT::Dashboard->new($currentuser)->ObjectsForLoading(IncludeSuperuserGroups => 0); is_deeply( \@loading, ["RT::User-".$user_obj->Id, "RT::System-1"], "But only via superuser" ); $m->get_ok("/Dashboards/index.html"); $m->content_contains("inner dashboard", "The dashboards list includes superuser rights"); $m->get_ok("/Prefs/DashboardsInMenu.html"); $m->content_lacks("inner dashboard", "But the menu skips them"); rt-5.0.1/t/web/ticket_links.t000644 000765 000024 00000015600 14005011336 016673 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 152; my ( $baseurl, $m ) = RT::Test->started_ok; ok( $m->login, "Logged in" ); my $queue = RT::Test->load_or_create_queue( Name => 'General' ); ok( $queue->id, "loaded the General queue" ); # Create a new queue my $queue_2 = RT::Test->load_or_create_queue( Name => 'NewQueue'); ok( $queue_2->id, "New queue created, 'NewQueue'"); # Create a new ticket $m->get_ok($baseurl . '/Ticket/Create.html?Queue=1'); $m->form_name('TicketCreate'); $m->field(Subject => 'testing new ticket'); $m->click_button(value => 'Create'); # Set NewQueue as default queue $m->get_ok($baseurl . '/Prefs/Other.html'); $m->submit_form_ok({ form_name => 'ModifyPreferences', fields => { DefaultQueue => 'NewQueue', }, button => 'Update' }, 'NewQueue set as default queue'); # Verify NewQueue is the default queue on ticket page $m->get_ok($baseurl . '/Ticket/Display.html?id=1'); my $selected_queue_node = $m->dom->at('select[name=CloneQueue] option:checked'); if ($selected_queue_node) { is($selected_queue_node->all_text, 'NewQueue'); } else { fail("no selected queue for clone queue"); } my ( $deleted, $active, $inactive ) = RT::Test->create_tickets( { Queue => 'General' }, { Subject => 'deleted ticket', }, { Subject => 'active ticket', }, { Subject => 'inactive ticket', } ); my ( $deleted_id, $active_id, $inactive_id ) = ( $deleted->id, $active->id, $inactive->id ); $deleted->SetStatus('deleted'); is( $deleted->Status, 'deleted', "deleted $deleted_id" ); $inactive->SetStatus('resolved'); is( $inactive->Status, 'resolved', 'resolved $inactive_id' ); # Create an article for linking require RT::Class; my $class = RT::Class->new($RT::SystemUser); $class->Create(Name => 'test class'); require RT::Article; my $article = RT::Article->new($RT::SystemUser); $article->Create(Class => $class->Id, Name => 'test article'); for my $type ( "DependsOn", "MemberOf", "RefersTo" ) { for my $c (qw/base target/) { my $id; diag "create ticket with links of type $type $c"; { ok( $m->goto_create_ticket($queue), "go to create ticket" ); $m->form_name('TicketCreate'); $m->field( Subject => "test ticket creation with $type $c" ); if ( $c eq 'base' ) { $m->field( "new-$type", "$deleted_id $active_id $inactive_id" ); } else { $m->field( "$type-new", "$deleted_id $active_id $inactive_id" ); } $m->click('SubmitTicket'); $m->content_like(qr/Ticket \d+ created/, 'created ticket'); $m->content_contains("Linking to a deleted ticket is not allowed"); $id = RT::Test->last_ticket->id; } diag "add ticket links of type $type $c"; { my $ticket = RT::Test->create_ticket( Queue => 'General', Subject => "test $type $c", ); $id = $ticket->id; $m->goto_ticket($id); $m->follow_link_ok( { text => 'Links' }, "Followed link to Links" ); ok( $m->form_with_fields("$id-DependsOn"), "found the form" ); if ( $c eq 'base' ) { $m->field( "$id-$type", "$deleted_id $active_id $inactive_id" ); } else { $m->field( "$type-$id", "$deleted_id $active_id $inactive_id" ); } $m->submit; $m->content_contains("Linking to a deleted ticket is not allowed"); if ( $c eq 'base' ) { $m->content_like( qr{"DeleteLink--$type-.*?ticket/$active_id"}, "$c for $type: has active ticket", ); $m->content_like( qr{"DeleteLink--$type-.*?ticket/$inactive_id"}, "base for $type: has inactive ticket", ); $m->content_unlike( qr{"DeleteLink--$type-.*?ticket/$deleted_id"}, "base for $type: no deleted ticket", ); } else { $m->content_like( qr{"DeleteLink-.*?ticket/$active_id-$type-"}, "$c for $type: has active ticket", ); $m->content_like( qr{"DeleteLink-.*?ticket/$inactive_id-$type-"}, "base for $type: has inactive ticket", ); $m->content_unlike( qr{"DeleteLink-.*?ticket/$deleted_id-$type-"}, "base for $type: no deleted ticket", ); } } $m->goto_ticket($id); $m->content_like( qr{$active_id:.*?\[new\]}, "has active ticket", ); $m->content_like( qr{$inactive_id:.*?\[resolved\]}, "has inactive ticket", ); $m->content_unlike( qr{$deleted_id.*?\[deleted\]}, "no deleted ticket", ); diag "[$type]: Testing that reminders don't get copied for $c tickets"; { my $ticket = RT::Test->create_ticket( Subject => 'test ticket', Queue => 1, ); $m->goto_ticket($ticket->Id); $m->form_name('UpdateReminders'); $m->field('NewReminder-Subject' => 'hello test reminder subject'); $m->click_button(value => 'Save'); $m->text_contains('hello test reminder subject'); my $id = $ticket->Id; my $type_value = my $link_field = $type; if ($c eq 'base') { $type_value = "new-$type_value"; $link_field = "$link_field-$id"; } else { $type_value = "$type_value-new"; $link_field = "$id-$link_field"; } if ($type eq 'RefersTo') { $m->goto_ticket($ticket->Id); $m->follow_link(id => 'page-links'); # add $baseurl as a link $m->form_name('ModifyLinks'); $m->field($link_field => "$baseurl/test_ticket_reference"); $m->click('SubmitTicket'); # add an article as a link $m->form_name('ModifyLinks'); $m->field($link_field => 'a:' . $article->Id); $m->click('SubmitTicket'); } my $depends_on_url = sprintf( '%s/Ticket/Create.html?Queue=%s&CloneTicket=%s&%s=%s', $baseurl, '1', $id, $type_value, $id, ); $m->get_ok($depends_on_url); $m->form_name('TicketCreate'); $m->click_button(value => 'Create'); $m->content_lacks('hello test reminder subject'); if ($type eq 'RefersTo') { $m->text_contains("$baseurl/test_ticket_reference"); $m->text_contains("Article #" . $article->Id . ': test article'); } } } } rt-5.0.1/t/web/helpers-http-cache-headers.t000644 000765 000024 00000005134 14005011336 021302 0ustar00sunnavystaff000000 000000 use strict; use warnings; # trs: I'd write a quick t/web/caching-headers.t file which loops the available # endpoints checking for the right headers. use File::Find; BEGIN { # Ensure that the test and server processes use the same fixed time. use constant TIME => 1365175699; use Test::MockTime 'set_fixed_time'; set_fixed_time(TIME); use RT::Test tests => undef, config => "use Test::MockTime 'set_fixed_time'; set_fixed_time(".TIME.");"; } my ($base, $m) = RT::Test->started_ok; ok $m->login, 'logged in'; my $docroot = join '/', qw(share html); # files to exclude from testing headers my @exclude = ( 'SpawnLinkedTicket', # results in redirect, expires header not expected ); # find endpoints to loop over my @endpoints = ( "/NoAuth/css/elevator-light/squished-".("0"x32).".css", '/static/images/bpslogo.png', ); find({ wanted => sub { if ( -f $_ && $_ !~ m|autohandler$| ) { return if m{/\.[^/]+\.sw[op]$}; # vim swap files ( my $endpoint = $_ ) =~ s|^$docroot||; return if grep $endpoint =~ m{/$_$}, @exclude; push @endpoints, $endpoint; } }, no_chdir => 1, } => join '/', $docroot => 'Helpers'); my $ticket_id; diag "create a ticket via the API"; { my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id, $txn, $msg) = $ticket->Create( Queue => 'General', Subject => 'test ticket', ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Subject, 'test ticket', 'correct subject'; $ticket_id = $id; } my $expected; diag "set up expected date headers"; { # expected headers $expected = { Autocomplete => { 'Cache-Control' => 'max-age=120, private', 'Expires' => 'Fri, 05 Apr 2013 15:30:19 GMT', }, NoAuth => { 'Cache-Control' => 'max-age=2592000, public', 'Expires' => 'Sun, 05 May 2013 15:28:19 GMT', }, default => { 'Cache-Control' => 'no-cache', 'Expires' => 'Fri, 05 Apr 2013 15:28:19 GMT', }, }; } foreach my $endpoint ( @endpoints ) { $m->get_ok( $endpoint . "?id=${ticket_id}&Status=open&Requestor=root" ); my $header_key = 'default'; if ( $endpoint =~ m|Autocomplete| ) { $header_key = 'Autocomplete'; } elsif ( $endpoint =~ m/NoAuth|static/ ) { $header_key = 'NoAuth'; } my $headers = $expected->{$header_key}; is( $m->res->header('Cache-Control') => $headers->{'Cache-Control'}, 'got expected Cache-Control header' ); is( $m->res->header('Expires') => $headers->{'Expires'}, 'got expected Expires header' ); } done_testing; rt-5.0.1/t/web/ticket_owner_autocomplete.t000644 000765 000024 00000013216 14005011336 021467 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => 43; use JSON qw(from_json); my $queue = RT::Test->load_or_create_queue( Name => 'Regression' ); ok $queue && $queue->id, 'loaded or created queue'; my $user_a = RT::Test->load_or_create_user( Name => 'user_a', Password => 'password', ); ok $user_a && $user_a->id, 'loaded or created user'; my $user_b = RT::Test->load_or_create_user( Name => 'user_b', Password => 'password', ); ok $user_b && $user_b->id, 'loaded or created user'; RT->Config->Set( AutocompleteOwners => 1 ); my ($baseurl, $agent_a) = RT::Test->started_ok; ok( RT::Test->set_rights( { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket ReplyToTicket)] }, { Principal => $user_b, Right => [qw(SeeQueue ShowTicket OwnTicket)] }, ), 'set rights'); ok $agent_a->login('user_a', 'password'), 'logged in as user A'; diag "current user has no right to own, nobody selected as owner on create"; { $agent_a->get_ok('/Ticket/Create.html?Queue=' . $queue->id, 'open ticket create page'); $agent_a->content_contains('Create a new ticket', 'opened create ticket page'); my $form = $agent_a->form_name('TicketCreate'); is $form->value('Owner'), RT->Nobody->Name, 'correct owner selected'; autocomplete_lacks( 'RT::Queue-'.$queue->id, 'user_a' ); $agent_a->click('SubmitTicket'); $agent_a->content_like(qr/Ticket \d+ created in queue/i, 'created ticket'); my ($id) = ($agent_a->content =~ /Ticket (\d+) created in queue/); ok $id, 'found id of the ticket'; my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $id ); ok $ticket->id, 'loaded the ticket'; is $ticket->Owner, RT->Nobody->id, 'correct owner'; } diag "user can chose owner of a new ticket"; { $agent_a->get_ok('/Ticket/Create.html?Queue=' . $queue->id, 'open ticket create page'); $agent_a->content_contains('Create a new ticket', 'opened create ticket page'); my $form = $agent_a->form_name('TicketCreate'); is $form->value('Owner'), RT->Nobody->Name, 'correct owner selected'; autocomplete_contains( 'RT::Queue-'.$queue->id, 'user_b' ); $form->value('Owner', $user_b->Name); $agent_a->click('SubmitTicket'); $agent_a->content_like(qr/Ticket \d+ created in queue/i, 'created ticket'); my ($id) = ($agent_a->content =~ /Ticket (\d+) created in queue/); ok $id, 'found id of the ticket'; my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $id ); ok $ticket->id, 'loaded the ticket'; is $ticket->Owner, $user_b->id, 'correct owner'; } my $agent_b = RT::Test::Web->new; ok $agent_b->login('user_b', 'password'), 'logged in as user B'; diag "user A can not change owner after create"; { my $ticket = RT::Ticket->new( $user_a ); my ($id, $txn, $msg) = $ticket->Create( Queue => $queue->id, Owner => $user_b->id, Subject => 'test', ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Owner, $user_b->id, 'correct owner'; # try the following group of tests twice with different agents(logins) my $test_cb = sub { my $agent = shift; $agent->get("/Ticket/Modify.html?id=$id"); my $form = $agent->form_name('TicketModify'); is $form->value('Owner'), $user_b->Name, 'correct owner selected'; $form->value('Owner', RT->Nobody->Name); $agent->submit; $agent->content_contains( 'Permission Denied', 'no way to change owner after create if you have no rights' ); my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $id ); ok $ticket->id, 'loaded the ticket'; is $ticket->Owner, $user_b->id, 'correct owner'; }; $test_cb->($agent_a); diag "even owner(user B) can not change owner"; $test_cb->($agent_b); } diag "on reply correct owner is selected"; { my $ticket = RT::Ticket->new( $user_a ); my ($id, $txn, $msg) = $ticket->Create( Queue => $queue->id, Owner => $user_b->id, Subject => 'test', ); ok $id, 'created a ticket #'. $id or diag "error: $msg"; is $ticket->Owner, $user_b->id, 'correct owner'; $agent_a->goto_ticket( $id ); $agent_a->follow_link_ok( { id => 'page-actions-reply' }, 'Reply' ); my $form = $agent_a->form_number(3); is $form->value('Owner'), 'user_b', 'current user selected'; $agent_a->submit; $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $id ); ok $ticket->id, 'loaded the ticket'; is $ticket->Owner, $user_b->id, 'correct owner'; } sub autocomplete { my $limit = shift; my $agent = shift; $agent->get_ok("/Helpers/Autocomplete/Owners?term=&limit=$limit&return=Name", "fetched autocomplete values"); return from_json($agent->content); } sub autocomplete_contains { my $limit = shift; my $expected = shift; my $agent = shift; unless ( $agent ) { $agent = RT::Test::Web->new; $agent->login('user_a', 'password'); } my $results = autocomplete( $limit, $agent ); my %seen; $seen{$_->{value}}++ for @$results; $expected = [$expected] unless ref $expected eq 'ARRAY'; is((scalar grep { not $seen{$_} } @$expected), 0, "got all expected values"); } sub autocomplete_lacks { my $limit = shift; my $lacks = shift; my $agent = shift; unless ( $agent ) { $agent = RT::Test::Web->new; $agent->login('user_a', 'password'); } my $results = autocomplete( $limit, $agent ); my %seen; $seen{$_->{value}}++ for @$results; $lacks = [$lacks] unless ref $lacks eq 'ARRAY'; is((scalar grep { $seen{$_} } @$lacks), 0, "didn't get any unexpected values"); } rt-5.0.1/t/web/pending-tickets.t000644 000765 000024 00000001662 14005011336 017303 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 12; my ($baseurl, $m) = RT::Test->started_ok; my $url = $m->rt_base_url; ok $m->login, 'logged in'; my @tickets; for my $ticket_num (1..3) { my $t = RT::Ticket->new(RT->SystemUser); $t->Create(Subject => "test $ticket_num", Queue => 'general', Owner => 'root'); ok(my $id = $t->id, "created ticket $ticket_num"); push @tickets, $t; } my ($t1, $t2, $t3) = @tickets; my ($status, $msg) = $t2->AddLink( Type => 'DependsOn', Base => $t1->id ); ok($status, "created a link: $msg"); ($status, $msg) = $t3->AddLink( Type => 'DependsOn', Base => $t1->id ); ok($status, "created a link: $msg"); ($status, $msg) = $t3->AddLink( Type => 'DependsOn', Base => $t2->id ); ok($status, "created a link: $msg"); $m->reload(); $m->content_contains('pending ticket #'.$t3->id.'', 'single ticket pending text'); $m->content_contains('pending 2 other tickets', 'multiple tickets pending text'); rt-5.0.1/t/web/saved_search_permissions.t000644 000765 000024 00000002021 14005011336 021263 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 12; my $user = RT::User->new(RT->SystemUser); ok( $user->Create( Name => 'foo', Privileged => 1, Password => 'foobar' ) ); my ( $url, $m ) = RT::Test->started_ok; ok( $m->login, 'root logged in' ); $m->get_ok( $url . '/Search/Build.html?Query=id<100' ); $m->submit_form( form_name => 'BuildQuery', fields => { SavedSearchDescription => 'test' }, button => 'SavedSearchSave', ); $m->content_contains( q{name="SavedSearchDescription" value="test"}, 'saved test search' ); my ($id) = $m->content =~ /value="(RT::User-\d+-SavedSearch-\d+)"/; ok( $m->login( 'foo', 'foobar', logout => 1 ), 'logged in' ); $m->get_ok( $url . "/Search/Build.html?SavedSearchLoad=$id" ); my $message = qq{Can not load saved search "$id"}; RT::Interface::Web::EscapeHTML( \$message ); $m->content_contains( $message, 'user foo can not load saved search of root' ); $m->warning_like( qr/User #\d+ tried to load container user #\d+/, 'get warning' ); rt-5.0.1/t/web/search_cf_quotes.t000644 000765 000024 00000002766 14005011336 017536 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 24; my ( $baseurl, $m ) = RT::Test->started_ok; my $cf = RT::CustomField->new($RT::SystemUser); ok( $cf->Create( Name => "I'm a cf", Type => 'Date', LookupType => 'RT::Queue-RT::Ticket', ) ); ok( $cf->AddToObject( RT::Queue->new($RT::SystemUser) ) ); RT::Test->create_tickets( { Queue => 'General' }, { Subject => 'ticket foo', 'CustomField-' . $cf->id => '2011-09-15' }, { Subject => 'ticket bar', 'CustomField-' . $cf->id => '2011-10-15' }, { Subject => 'ticket baz' }, ); ok( $m->login, 'logged in' ); $m->get_ok('/Search/Build.html'); $m->form_name( 'BuildQuery' ); my ($cf_op) = $m->find_all_inputs( type => 'option', name_regex => qr/I'm a cf/ ); my ($cf_field) = $m->find_all_inputs( type => 'text', name_regex => qr/I'm a cf/ ); diag "search directly"; $m->submit_form( fields => { $cf_op->name => '<', $cf_field->name => '2011-09-30', }, button => 'DoSearch', ); $m->title_is( 'Found 1 ticket', 'found only 1 ticket' ); $m->content_contains( 'ticket foo', 'has ticket foo' ); diag "first add clause, then search"; $m->get_ok('/Search/Build.html?NewQuery=1'); $m->form_name( 'BuildQuery' ); $m->submit_form( fields => { $cf_op->name => '<', $cf_field->name => '2011-09-30', }, button => 'AddClause', ); $m->follow_link_ok( { text => 'Show Results' } ); $m->title_is( 'Found 1 ticket', 'found only 1 ticket' ); $m->content_contains( 'ticket foo', 'has ticket foo' ); rt-5.0.1/t/web/css.t000644 000765 000024 00000002367 14005011336 015006 0ustar00sunnavystaff000000 000000 use utf8; use strict; use warnings; use RT::Test tests => undef; # Each custom field must have a corresponding class selector with invalid characters escaped { my( $baseurl, $m ) = RT::Test->started_ok; ok( $m->login, 'logged in' ); my $queue = RT::Test->load_or_create_queue( Name => 'General' ); ok ( $m->goto_create_ticket( $queue ) ); $m->form_name( 'TicketCreate' ); $m->field( Subject => 'Test Ticket' ); $m->click('SubmitTicket'); $m->follow_link_ok( { id => 'admin-custom-fields' } ); $m->follow_link_ok( { id => 'page-create' } ); $m->form_name( 'ModifyCustomField' ); $m->field( 'Name' => 'test class% م 例 name' ); $m->click( 'Update' ); my ( $cf_id ) = ( $m->uri =~ /id=(\d+)/ ); $m->follow_link_ok( { text => 'Applies to' } ); $m->submit_form_ok( { with_fields => { "AddCustomField-$cf_id" => 0, }, button => 'UpdateObjs', }, 'Added new custom field globally' ); my $res = $m->get( $baseurl . '/Ticket/Display.html?id=1' ); my $element = $m->dom->at( ".custom-field-$cf_id" ); like( $element->attr( 'class' ), qr/test-class-م-例-name/, 'Class selector added to custom field, invalid characters have been escaped' ); } done_testing(); rt-5.0.1/t/web/attachments.t000644 000765 000024 00000041731 14005011336 016527 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use constant LogoFile => $RT::StaticPath .'/images/bpslogo.png'; use constant FaviconFile => $RT::StaticPath .'/images/favicon.png'; use constant TextFile => $RT::StaticPath .'/css/mobile.css'; my ($url, $m) = RT::Test->started_ok; ok $m->login, 'logged in'; my $queue = RT::Test->load_or_create_queue( Name => 'General' ); ok( $queue && $queue->id, "Loaded General queue" ); diag "create a ticket in full interface"; diag "w/o attachments"; { $m->goto_create_ticket( $queue ); is($m->status, 200, "request successful"); $m->form_name('TicketCreate'); $m->content_contains("Create a new ticket", 'ticket create page'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); } diag "with one attachment"; { $m->goto_create_ticket( $queue ); $m->form_name('TicketCreate'); $m->field('Subject', 'Attachments test'); $m->field('Attach', LogoFile); $m->field('Content', 'Some content'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); $m->content_contains('Attachments test', 'we have subject on the page'); $m->content_contains('Some content', 'and content'); ok( $m->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page has the file link' ); } diag "with two attachments"; { $m->goto_create_ticket( $queue ); $m->form_name('TicketCreate'); $m->field('Attach', LogoFile); $m->click('AddMoreAttach'); is($m->status, 200, "request successful"); $m->form_name('TicketCreate'); $m->field('Attach', FaviconFile); $m->field('Subject', 'Attachments test'); $m->field('Content', 'Some content'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); $m->content_contains('Attachments test', 'we have subject on the page'); $m->content_contains('Some content', 'and content'); ok( $m->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page has the file link' ); ok( $m->find_link( text => 'favicon.png', url_regex => qr{Attachment/} ), 'page has the file link' ); } SKIP: { skip "delete attach function is ajaxified, no checkbox anymore", 8; diag "with one attachment, but delete one along the way"; { $m->goto_create_ticket( $queue ); $m->form_name('TicketCreate'); $m->field('Attach', LogoFile); $m->click('AddMoreAttach'); is($m->status, 200, "request successful"); $m->form_name('TicketCreate'); $m->field('Attach', FaviconFile); $m->tick( 'DeleteAttach', LogoFile ); $m->field('Subject', 'Attachments test'); $m->field('Content', 'Some content'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); $m->content_contains('Attachments test', 'we have subject on the page'); $m->content_contains('Some content', 'and content'); ok( !$m->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page lacks the file link' ); ok( $m->find_link( text => 'favicon.png', url_regex => qr{Attachment/} ), 'page has the file link' ); } diag "with one attachment, but delete one along the way"; { $m->goto_create_ticket( $queue ); $m->form_name('TicketCreate'); $m->field('Attach', LogoFile); $m->click('AddMoreAttach'); is($m->status, 200, "request successful"); $m->form_name('TicketCreate'); $m->tick( 'DeleteAttach', LogoFile ); $m->click('AddMoreAttach'); is($m->status, 200, "request successful"); $m->form_name('TicketCreate'); $m->field('Attach', FaviconFile); $m->click('AddMoreAttach'); is($m->status, 200, "request successful"); $m->form_name('TicketCreate'); $m->field('Subject', 'Attachments test'); $m->field('Content', 'Some content'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); $m->content_contains('Attachments test', 'we have subject on the page'); $m->content_contains('Some content', 'and content'); ok( !$m->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page lacks the file link' ); ok( $m->find_link( text => 'favicon.png', url_regex => qr{Attachment/} ), 'page has the file link' ); } } diag "reply to a ticket in full interface"; diag "with one attachment"; { my $ticket = RT::Test->create_ticket( Queue => $queue, Subject => 'Attachments test', Content => 'Some content', ); $m->goto_ticket( $ticket->id ); $m->follow_link_ok({text => 'Reply'}, "reply to the ticket"); $m->form_name('TicketUpdate'); $m->field('Attach', LogoFile); $m->field('UpdateContent', 'Message'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); ok( $m->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page has the file link' ); } diag "with two attachments"; { my $ticket = RT::Test->create_ticket( Queue => $queue, Subject => 'Attachments test', Content => 'Some content', ); $m->goto_ticket( $ticket->id ); $m->follow_link_ok({text => 'Reply'}, "reply to the ticket"); $m->form_name('TicketUpdate'); $m->field('Attach', LogoFile); $m->click('AddMoreAttach'); is($m->status, 200, "request successful"); $m->form_name('TicketUpdate'); $m->field('Attach', FaviconFile); $m->field('UpdateContent', 'Message'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); ok( $m->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page has the file link' ); ok( $m->find_link( text => 'favicon.png', url_regex => qr{Attachment/} ), 'page has the file link' ); } SKIP: { skip "delete attach function is ajaxified, no checkbox anymore", 4; diag "with one attachment, delete one along the way"; { my $ticket = RT::Test->create_ticket( Queue => $queue, Subject => 'Attachments test', Content => 'Some content', ); $m->goto_ticket( $ticket->id ); $m->follow_link_ok({text => 'Reply'}, "reply to the ticket"); $m->form_name('TicketUpdate'); $m->field('Attach', LogoFile); $m->click('AddMoreAttach'); is($m->status, 200, "request successful"); $m->form_name('TicketUpdate'); $m->tick('DeleteAttach', LogoFile); $m->field('Attach', FaviconFile); $m->field('UpdateContent', 'Message'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); ok( !$m->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page lacks the file link' ); ok( $m->find_link( text => 'favicon.png', url_regex => qr{Attachment/} ), 'page has the file link' ); } } diag "jumbo interface"; diag "with one attachment"; { my $ticket = RT::Test->create_ticket( Queue => $queue, Subject => 'Attachments test', Content => 'Some content', ); $m->goto_ticket( $ticket->id ); $m->follow_link_ok({text => 'Jumbo'}, "jumbo the ticket"); $m->form_name('TicketModifyAll'); $m->field('Attach', LogoFile); $m->field('UpdateContent', 'Message'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); $m->goto_ticket( $ticket->id ); ok( $m->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page has the file link' ); } diag "with two attachments"; { my $ticket = RT::Test->create_ticket( Queue => $queue, Subject => 'Attachments test', Content => 'Some content', ); $m->goto_ticket( $ticket->id ); $m->follow_link_ok({text => 'Jumbo'}, "jumbo the ticket"); $m->form_name('TicketModifyAll'); $m->field('Attach', LogoFile); $m->click('AddMoreAttach'); is($m->status, 200, "request successful"); $m->form_name('TicketModifyAll'); $m->field('Attach', FaviconFile); $m->field('UpdateContent', 'Message'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); $m->goto_ticket( $ticket->id ); ok( $m->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page has the file link' ); ok( $m->find_link( text => 'favicon.png', url_regex => qr{Attachment/} ), 'page has the file link' ); } SKIP: { skip "delete attach function is ajaxified, no checkbox anymore", 4; diag "with one attachment, delete one along the way"; { my $ticket = RT::Test->create_ticket( Queue => $queue, Subject => 'Attachments test', Content => 'Some content', ); $m->goto_ticket( $ticket->id ); $m->follow_link_ok({text => 'Jumbo'}, "jumbo the ticket"); $m->form_name('TicketModifyAll'); $m->field('Attach', LogoFile); $m->click('AddMoreAttach'); is($m->status, 200, "request successful"); $m->form_name('TicketModifyAll'); $m->tick('DeleteAttach', LogoFile); $m->field('Attach', FaviconFile); $m->field('UpdateContent', 'Message'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); $m->goto_ticket( $ticket->id ); ok( !$m->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page lacks the file link' ); ok( $m->find_link( text => 'favicon.png', url_regex => qr{Attachment/} ), 'page has the file link' ); } } diag "bulk update"; diag "one attachment"; { my @tickets = RT::Test->create_tickets( { Queue => $queue, Subject => 'Attachments test', Content => 'Some content', }, {}, {}, ); my $query = join ' OR ', map "id=$_", map $_->id, @tickets; $query =~ s/ /%20/g; $m->get_ok( $url . "/Search/Bulk.html?Query=$query&Rows=10" ); $m->form_name('BulkUpdate'); $m->field('Attach', FaviconFile); $m->field('UpdateContent', 'Message'); $m->submit; is($m->status, 200, "request successful"); foreach my $ticket ( @tickets ) { $m->goto_ticket( $ticket->id ); ok( !$m->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page lacks the file link' ); ok( $m->find_link( text => 'favicon.png', url_regex => qr{Attachment/} ), 'page has the file link' ); } } diag "two attachments"; { my @tickets = RT::Test->create_tickets( { Queue => $queue, Subject => 'Attachments test', Content => 'Some content', }, {}, {}, ); my $query = join ' OR ', map "id=$_", map $_->id, @tickets; $query =~ s/ /%20/g; $m->get_ok( $url . "/Search/Bulk.html?Query=$query&Rows=10" ); $m->form_name('BulkUpdate'); $m->field('Attach', LogoFile); $m->click('AddMoreAttach'); is($m->status, 200, "request successful"); $m->form_name('BulkUpdate'); $m->field('Attach', FaviconFile); $m->field('UpdateContent', 'Message'); $m->submit; is($m->status, 200, "request successful"); foreach my $ticket ( @tickets ) { $m->goto_ticket( $ticket->id ); ok( $m->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page has the file link' ); ok( $m->find_link( text => 'favicon.png', url_regex => qr{Attachment/} ), 'page has the file link' ); } } SKIP: { skip "delete attach function is ajaxified, no checkbox anymore", 8; diag "one attachment, delete one along the way"; { my @tickets = RT::Test->create_tickets( { Queue => $queue, Subject => 'Attachments test', Content => 'Some content', }, {}, {}, ); my $query = join ' OR ', map "id=$_", map $_->id, @tickets; $query =~ s/ /%20/g; $m->get_ok( $url . "/Search/Bulk.html?Query=$query&Rows=10" ); $m->form_name('BulkUpdate'); $m->field('Attach', LogoFile); $m->click('AddMoreAttach'); is($m->status, 200, "request successful"); $m->form_name('BulkUpdate'); $m->tick('DeleteAttach', LogoFile); $m->field('Attach', FaviconFile); $m->field('UpdateContent', 'Message'); $m->submit; is($m->status, 200, "request successful"); foreach my $ticket ( @tickets ) { $m->goto_ticket( $ticket->id ); ok( !$m->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page lacks the file link' ); ok( $m->find_link( text => 'favicon.png', url_regex => qr{Attachment/} ), 'page has the file link' ); } } } diag "self service"; diag "create with attachment"; { $m->get_ok( $url . "/SelfService/Create.html?Queue=". $queue->id ); $m->form_name('TicketCreate'); $m->field('Attach', FaviconFile); $m->field('Subject', 'Subject'); $m->field('Content', 'Message'); ok($m->current_form->find_input('AddMoreAttach'), "more than one attachment"); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); ok( $m->find_link( text => 'favicon.png', url_regex => qr{Attachment/} ), 'page has the file link' ); } diag "update with attachment"; { my $ticket = RT::Test->create_ticket( Queue => $queue, Subject => 'Attachments test', Content => 'Some content', ); $m->get_ok( $url . "/SelfService/Update.html?id=". $ticket->id ); $m->form_name('TicketUpdate'); $m->field('Attach', FaviconFile); $m->field('UpdateContent', 'Message'); ok($m->current_form->find_input('AddMoreAttach'), "more than one attachment"); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); ok( $m->find_link( text => 'favicon.png', url_regex => qr{Attachment/} ), 'page has the file link' ); } diag "mobile ui"; diag "simple create + reply"; { $m->get_ok( $url . '/m/ticket/create?Queue=' . $queue->id ); $m->form_name('TicketCreate'); $m->field('Subject', 'Attachments test'); $m->field('Attach', LogoFile); $m->field('Content', 'Some content'); $m->submit; is($m->status, 200, "request successful"); $m->content_contains('Attachments test', 'we have subject on the page'); $m->content_contains('bpslogo.png', 'page has file name'); $m->follow_link_ok({text => 'Reply'}, "reply to the ticket"); $m->form_name('TicketUpdate'); $m->field('Attach', LogoFile); $m->click('AddMoreAttach'); is($m->status, 200, "request successful"); $m->form_name('TicketUpdate'); $m->field('Attach', FaviconFile); $m->field('UpdateContent', 'Message'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); $m->content_contains('bpslogo.png', 'page has file name'); $m->content_contains('favicon.png', 'page has file name'); } diag "check content type and content"; { $m->goto_create_ticket( $queue ); $m->form_name('TicketCreate'); $m->field('Attach', LogoFile); $m->click('AddMoreAttach'); is($m->status, 200, "request successful"); $m->form_name('TicketCreate'); $m->field('Attach', TextFile); $m->field('Subject', 'Attachments test'); $m->field('Content', 'Some content'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); $m->content_contains('Attachments test', 'we have subject on the page'); $m->content_contains('Some content', 'and content'); ok( $m->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page has the file link' ); ok( $m->find_link( text => 'mobile.css', url_regex => qr{Attachment/} ), 'page has the file link' ); $m->follow_link_ok( { url_regex => qr/Attachment\/\d+\/\d+\/bpslogo\.png/ } ); is($m->response->header('Content-Type'), 'image/png', 'Content-Type of png lacks charset' ); is($m->content_type, "image/png"); is($m->content, RT::Test->file_content(LogoFile), "Binary content matches"); $m->back; $m->follow_link_ok( { url_regex => qr/Attachment\/\d+\/\d+\/mobile\.css/ } ); is( $m->response->header('Content-Type'), 'text/css;charset=UTF-8', 'Content-Type of text has charset', ); is($m->content_type, "text/css"); is($m->content, RT::Test->file_content(TextFile), "Text content matches"); } diag "concurent actions"; my $m2 = RT::Test::Web->new; ok $m2->login, 'second login'; diag "update and create"; { my $ticket = RT::Test->create_ticket( Queue => $queue, Subject => 'Attachments test', Content => 'Some content', ); $m2->goto_ticket( $ticket->id ); $m2->follow_link_ok({text => 'Reply'}, "reply to the ticket"); $m2->form_name('TicketUpdate'); $m2->field('Attach', LogoFile); $m2->click('AddMoreAttach'); is($m2->status, 200, "request successful"); $m->goto_create_ticket( $queue ); $m->form_name('TicketCreate'); $m->field('Attach', FaviconFile); $m->field('Subject', 'Attachments test'); $m->field('Content', 'Some content'); $m->click('SubmitTicket'); is($m->status, 200, "request successful"); ok( !$m->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page lacks the file link' ); ok( $m->find_link( text => 'favicon.png', url_regex => qr{Attachment/} ), 'page has the file link' ); $m2->form_name('TicketUpdate'); $m2->click('SubmitTicket'); ok( $m2->find_link( text => 'bpslogo.png', url_regex => qr{Attachment/} ), 'page has the file link' ); ok( !$m2->find_link( text => 'favicon.png', url_regex => qr{Attachment/} ), 'page lacks the file link' ); } done_testing; rt-5.0.1/t/web/path-traversal.t000644 000765 000024 00000003633 14005011336 017150 0ustar00sunnavystaff000000 000000 use strict; use warnings; use HTTP::Status qw(); use RT::Test tests => undef; my ($baseurl, $agent) = RT::Test->started_ok; ok($agent->login); $agent->get("$baseurl/NoAuth/../Elements/HeaderJavascript"); is($agent->status, HTTP::Status::HTTP_BAD_REQUEST); $agent->warning_like(qr/Invalid request.*aborting/); $agent->get("$baseurl/NoAuth/../%45lements/HeaderJavascript"); is($agent->status, HTTP::Status::HTTP_BAD_REQUEST); $agent->warning_like(qr/Invalid request.*aborting/); $agent->get("$baseurl/NoAuth/%2E%2E/Elements/HeaderJavascript"); is($agent->status, HTTP::Status::HTTP_BAD_REQUEST); $agent->warning_like(qr/Invalid request.*aborting/); $agent->get("$baseurl/NoAuth/../../../etc/RT_Config.pm"); is($agent->status, HTTP::Status::HTTP_BAD_REQUEST); $agent->warning_like(qr/Invalid request.*aborting/) unless $ENV{RT_TEST_WEB_HANDLER} =~ /^apache/; $agent->get("$baseurl/static/css/elevator-light/images/../../../../../../etc/RT_Config.pm"); # Apache hardcodes a 400 but the static handler returns a 403 for traversal too high is($agent->status, $ENV{RT_TEST_WEB_HANDLER} =~ /^apache/ ? HTTP::Status::HTTP_BAD_REQUEST : HTTP::Status::HTTP_FORBIDDEN); # Do not reject a simple /. in the URL, for downloading uploaded # dotfiles, for example. $agent->get("$baseurl/Ticket/Attachment/28/9/.bashrc"); is($agent->status, HTTP::Status::HTTP_NOT_FOUND); $agent->next_warning_like(qr/could not be loaded/, "couldn't loaded warning"); $agent->content_like(qr/Attachment \S+ could not be loaded/); # do not reject these URLs, even though they contain /. outside the path $agent->get("$baseurl/index.html?ignored=%2F%2E"); is($agent->status, HTTP::Status::HTTP_OK); $agent->get("$baseurl/index.html?ignored=/."); is($agent->status, HTTP::Status::HTTP_OK); $agent->get("$baseurl/index.html#%2F%2E"); is($agent->status, HTTP::Status::HTTP_OK); $agent->get("$baseurl/index.html#/."); is($agent->status, HTTP::Status::HTTP_OK); done_testing; rt-5.0.1/t/web/query_builder_queue_limits.t000644 000765 000024 00000013656 14005011336 021661 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my $lifecycles = RT->Config->Get('Lifecycles'); $lifecycles->{foo} = { initial => ['initial'], active => ['open'], inactive => ['resolved'], }; # 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' ); my $foo = RT::Test->load_or_create_queue( Name => 'foo', Lifecycle => 'foo' ); my $global_cf = RT::Test->load_or_create_custom_field( Name => 'global_cf', Queue => 0, Type => 'FreeformSingle', ); my $general_cf = RT::Test->load_or_create_custom_field( Name => 'general_cf', Queue => 'General', Type => 'FreeformSingle', ); my $foo_cf = RT::Test->load_or_create_custom_field( Name => 'foo_cf', Queue => 'foo', Type => 'FreeformSingle' ); my $root = RT::Test->load_or_create_user( Name => 'root', ); my $user_a = RT::Test->load_or_create_user( Name => 'user_a', Password => 'password', ); my $user_b = RT::Test->load_or_create_user( Name => 'user_b', Password => 'password', ); ok( RT::Test->set_rights( { Principal => $user_a, Object => $general, Right => ['OwnTicket'], }, { Principal => $user_b, Object => $foo, Right => ['OwnTicket'], }, ), 'granted OwnTicket right for user_a and user_b' ); my ( $url, $m ) = RT::Test->started_ok; ok( $m->login, 'logged in' ); $m->get_ok( $url . '/Search/Build.html' ); diag "check default statuses, cf and owners"; my $form = $m->form_name('BuildQuery'); ok( $form, 'found BuildQuery form' ); ok( $form->find_input("ValueOfCF.{global_cf}"), 'found global_cf by default' ); ok( !$form->find_input("ValueOfCF.{general_cf}"), 'no general_cf by default' ); ok( !$form->find_input("ValueOfCF.{foo_cf}"), 'no foo_cf by default' ); my $status_input = $form->find_input('ValueOfStatus'); my @statuses = sort $status_input->possible_values; is_deeply( \@statuses, [ '', qw/__Active__ __Inactive__ initial new open open rejected resolved resolved stalled/], 'found all statuses' ) or diag "Statuses are: ", explain \@statuses; my $owner_input = $form->find_input('ValueOfActor'); my @owners = sort $owner_input->possible_values; is_deeply( \@owners, [ '', qw/Nobody root user_a user_b/], 'found all users' ); diag "limit queue to foo"; $m->submit_form( fields => { ValueOfQueue => 'foo', QueueOp => '=' }, button => 'AddClause', ); $form = $m->form_name('BuildQuery'); ok( $form->find_input("ValueOfCF.{foo_cf}"), 'found foo_cf' ); ok( $form->find_input("ValueOfCF.{global_cf}"), 'found global_cf' ); ok( !$form->find_input("ValueOfCF.{general_cf}"), 'still no general_cf' ); $status_input = $form->find_input('ValueOfStatus'); @statuses = sort $status_input->possible_values; is_deeply( \@statuses, [ '', qw/__Active__ __Inactive__ initial open resolved/ ], 'found statuses from foo only' ); $owner_input = $form->find_input('ValueOfActor'); @owners = sort $owner_input->possible_values; is_deeply( \@owners, [ '', qw/Nobody root user_b/], 'no user_a' ); diag "limit queue to general too"; $m->submit_form( fields => { ValueOfQueue => 'General', QueueOp => '=' }, button => 'AddClause', ); $form = $m->form_name('BuildQuery'); ok( $form->find_input("ValueOfCF.{general_cf}"), 'found general_cf' ); ok( $form->find_input("ValueOfCF.{foo_cf}"), 'found foo_cf' ); ok( $form->find_input("ValueOfCF.{global_cf}"), 'found global_cf' ); $status_input = $form->find_input('ValueOfStatus'); @statuses = sort $status_input->possible_values; is_deeply( \@statuses, [ '', qw/__Active__ __Inactive__ initial new open open rejected resolved resolved stalled/ ], 'found all statuses again' ) or diag "Statuses are: ", explain \@statuses; $owner_input = $form->find_input('ValueOfActor'); @owners = sort $owner_input->possible_values; is_deeply( \@owners, [ '', qw/Nobody root user_a user_b/], 'found all users again' ); diag "limit queue to != foo"; $m->get_ok( $url . '/Search/Build.html?NewQuery=1' ); $m->submit_form( form_name => 'BuildQuery', fields => { ValueOfQueue => 'foo', QueueOp => '!=' }, button => 'AddClause', ); $form = $m->form_name('BuildQuery'); ok( $form->find_input("ValueOfCF.{global_cf}"), 'found global_cf' ); ok( !$form->find_input("ValueOfCF.{foo_cf}"), 'no foo_cf' ); ok( !$form->find_input("ValueOfCF.{general_cf}"), 'no general_cf' ); $status_input = $form->find_input('ValueOfStatus'); @statuses = sort $status_input->possible_values; is_deeply( \@statuses, [ '', qw/__Active__ __Inactive__ initial new open open rejected resolved resolved stalled/], 'found all statuses' ) or diag "Statuses are: ", explain \@statuses; $owner_input = $form->find_input('ValueOfActor'); @owners = sort $owner_input->possible_values; is_deeply( \@owners, [ '', qw/Nobody root user_a user_b/], 'found all users' ); diag "limit queue to General OR foo"; $m->get_ok( $url . '/Search/Edit.html' ); $m->submit_form( form_name => 'BuildQueryAdvanced', fields => { Query => q{Queue = 'General' OR Queue = 'foo'} }, ); $form = $m->form_name('BuildQuery'); ok( $form->find_input("ValueOfCF.{general_cf}"), 'found general_cf' ); ok( $form->find_input("ValueOfCF.{foo_cf}"), 'found foo_cf' ); ok( $form->find_input("ValueOfCF.{global_cf}"), 'found global_cf' ); $status_input = $form->find_input('ValueOfStatus'); @statuses = sort $status_input->possible_values; is_deeply( \@statuses, [ '', qw/__Active__ __Inactive__ initial new open open rejected resolved resolved stalled/ ], 'found all statuses' ) or diag "Statuses are: ", explain \@statuses; $owner_input = $form->find_input('ValueOfActor'); @owners = sort $owner_input->possible_values; is_deeply( \@owners, [ '', qw/Nobody root user_a user_b/], 'found all users' ); done_testing; rt-5.0.1/t/web/dashboards-subscription.t000644 000765 000024 00000012471 14005011336 021047 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ($baseurl, $m) = RT::Test->started_ok; my $url = $m->rt_base_url; # Create User my $user = RT::User->new(RT->SystemUser); my ($ret, $msg) = $user->LoadOrCreateByEmail('customer@example.com'); ok($ret, 'ACL test user creation'); $user->SetName('customer'); $user->SetPrivileged(1); ($ret, $msg) = $user->SetPassword('customer'); $user->PrincipalObj->GrantRight(Right => 'ModifySelf'); $user->PrincipalObj->GrantRight(Right => 'ModifyOwnDashboard', Object => $RT::System); $user->PrincipalObj->GrantRight(Right => 'CreateOwnDashboard', Object => $RT::System); $user->PrincipalObj->GrantRight(Right => 'SeeOwnDashboard', Object => $RT::System); $user->PrincipalObj->GrantRight(Right => 'SeeGroup', Object => $RT::System); my $currentuser = RT::CurrentUser->new($user); ok $m->login(customer => 'customer'), "logged in"; $m->get_ok($url."Dashboards/Modify.html?Create=1"); # Create Dashboard $m->follow_link_ok({ id => 'reports-dashboard_create' }); $m->form_name('ModifyDashboard'); $m->field("Name" => 'test dashboard'); $m->click_button(value => 'Create'); $m->content_contains("Saved dashboard test dashboard"); # Make sure dashboard exists my $dashboard = RT::Dashboard->new($currentuser); my ($id) = $m->content =~ /name="id" value="(\d+)"/; ok($id, "got an ID, $id"); $dashboard->LoadById($id); is($dashboard->Name, "test dashboard"); # Attempt subscription without right $m->get_ok("/Dashboards/Subscription.html?id=$id"); $m->content_lacks('id="page-subscription"', "shouldn't have Subscription link since we don't have the SubscribeDashboard right"); $m->form_name('SubscribeDashboard'); $m->click_button(name => 'Save'); $m->content_contains("Permission Denied"); $m->warning_like(qr/Unable to subscribe to dashboard.*Permission Denied/, "got a permission denied warning when trying to subscribe to a dashboard"); # Make sure subscription doesn't exist $user->Attributes->RedoSearch; is($user->Attributes->Named('Subscription'), 0, "no subscriptions"); # Attempt subscription with right $user->PrincipalObj->GrantRight(Right => 'SubscribeDashboard', Object => $RT::System); $m->get_ok("/Dashboards/Subscription.html?id=$id"); $m->content_contains('id="page-subscription"', "subscription link should be visible"); $m->form_name('SubscribeDashboard'); $m->click_button(name => 'Save'); $m->content_lacks("Permission Denied"); $m->content_contains("Subscribed to dashboard test dashboard"); # Verify subscription exists $user->Attributes->RedoSearch; is($user->Attributes->Named('Subscription'), 1, "we have a subscription"); # Test recipients missing warning $m->follow_link_ok({ id => 'page-subscription' }); $m->form_name('SubscribeDashboard'); $m->untick("Dashboard-Subscription-Users-".$user->id,1); $m->click_button(name => 'Save'); $m->content_contains('customer removed from dashboard subscription recipients'); $m->content_contains("Warning: This dashboard has no recipients"); # Create new user to search for my $search_user = RT::User->new(RT->SystemUser); ($ret, $msg) = $search_user->LoadOrCreateByEmail('customer2@example.com'); ok($ret, 'ACL test user creation'); $search_user->SetName('customer2'); # Search for customer2 user and subscribe $m->form_name('SubscribeDashboard'); $m->field(UserString => 'customer'); $m->click_button(name => 'OnlySearchForPeople'); $m->content_contains('customer2@example.com'); # Subscribe customer2 $m->form_name('SubscribeDashboard'); $m->tick("Dashboard-Subscription-Users-".$search_user->id, 1); $m->click_button(name => 'Save'); $m->content_contains('customer2 added to dashboard subscription recipients'); # Make sure customer2 is listed as a recipient $m->follow_link_ok({ id => 'page-subscription' }); $m->content_contains('customer2@example.com'); # Disable user $search_user->SetDisabled(1); # Confirm they are not shown $m->follow_link_ok({ id => 'page-subscription' }); $m->content_lacks('customer2@example.com'); # Create new group to search for my $search_group = RT::Group->new(RT->SystemUser); ($ret, $msg) = $search_group->CreateUserDefinedGroup(Name => 'customers test group'); ok($ret, 'Test customers group creation'); # Search for group $m->form_name('SubscribeDashboard'); $m->field(GroupString => 'customers'); $m->click_button(name => 'OnlySearchForGroup'); $m->content_contains('customers test group'); # Subscribe group $m->form_name('SubscribeDashboard'); $m->tick("Dashboard-Subscription-Groups-".$search_group->id, 1); $m->click_button(name => 'Save'); $m->content_contains('customers test group added to dashboard subscription recipients'); # Make sure customers group is listed as a recipient $m->follow_link_ok({ id => 'page-subscription' }); $m->content_contains('customers test group'); # Disable user $search_group->SetDisabled(1); # Confirm they are not shown $m->follow_link_ok({ id => 'page-subscription' }); $m->content_lacks('customers test group'); $search_group->SetDisabled(0); $m->follow_link_ok({ id => 'page-subscription' }); # Unsubscribe group $m->form_name('SubscribeDashboard'); $m->untick("Dashboard-Subscription-Groups-".$search_group->id, 1); $m->click_button(name => 'Save'); $m->content_contains('customers test group removed from dashboard subscription recipients'); # Make sure customers group is no longer listed as a recipient $m->follow_link_ok({ id => 'page-subscription' }); $m->content_lacks('customers test group'); done_testing; rt-5.0.1/t/web/attachment_truncation.t000644 000765 000024 00000003342 14005011336 020606 0ustar00sunnavystaff000000 000000 use warnings; use strict; use RT::Test tests => undef; use File::Temp 'tempfile'; my $content = 'a' x 1000 . 'b' x 10; my ( $fh, $path ) = tempfile( UNLINK => 1, SUFFIX => '.txt' ); print $fh $content; close $fh; my $name = ( File::Spec->splitpath($path) )[2]; RT->Config->Set( 'WebSessionClass', "Apache::Session::File"); RT->Config->Set( 'MaxAttachmentSize', 1000 ); RT->Config->Set( 'TruncateLongAttachments', '1' ); my $queue = RT::Test->load_or_create_queue( Name => 'General' ); ok( $queue->id, "Loaded General queue" ); my $cf = RT::CustomField->new( RT->SystemUser ); ok( $cf->Create( Name => 'test truncation', Queue => '0', Type => 'FreeformSingle', ), ); my $cfid = $cf->id; my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in'; $m->get_ok( $baseurl . '/Ticket/Create.html?Queue=' . $queue->id ); $m->content_contains( "Create a new ticket", 'ticket create page' ); $m->form_name('TicketCreate'); $m->field( 'Subject', 'Attachments test' ); $m->field( 'Attach', $path ); $m->field( 'Content', 'Some content' ); my $cf_content = 'cf' . 'a' x 998 . 'cfb'; $m->field( "Object-RT::Ticket--CustomField-$cfid-Value", $cf_content ); $m->click('SubmitTicket'); is( $m->status, 200, "request successful" ); $m->content_contains( "File '$name' truncated because its size (1010 bytes) exceeded configured maximum size setting (1000 bytes).", 'truncated message' ); $m->content_contains( 'cf' . 'a' x 998, 'has the first 1000 cf chars' ); $m->content_lacks( 'aaacfb', 'lacks cf chars after that' ); $m->follow_link_ok( { url_regex => qr/Attachment\/\d+\/\d+\/$name/ } ); $m->content_contains( 'a' x 1000, 'has the first 1000 chars' ); $m->content_lacks( 'b', 'lacks chars after that' ); done_testing; rt-5.0.1/t/web/ticket_txn_content.t000644 000765 000024 00000011155 14005011336 020117 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 63; my $plain_file = File::Spec->catfile( RT::Test->temp_directory, 'attachment.txt' ); open my $plain_fh, '>', $plain_file or die $!; print $plain_fh "this is plain content"; close $plain_fh; my $plain_name = (File::Spec->splitpath($plain_file))[-1]; my $html_file = File::Spec->catfile( RT::Test->temp_directory, 'attachment.html' ); open my $html_fh, '>', $html_file or die $!; print $html_fh "this is plain content"; close $html_fh; my $html_name = (File::Spec->splitpath($html_file))[-1]; my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in'; my $queue = RT::Queue->new(RT->Nobody); my $qid = $queue->Load('General'); ok( $qid, "Loaded General queue" ); RT::Test->clean_caught_mails; sub follow_parent_with_headers_link { my $m = shift; my $link = $m->find_link(@_)->url; $link =~ s{/(\d+)$}{"/" . ($1-1)}e; # get the parent attach $m->get_ok($link); } sub follow_with_headers_link { my $m = shift; my $link = $m->find_link(@_)->url; $link =~ s{/\d+/(\d+)/.+$}{/WithHeaders/$1}; # frob into a with headers url $m->get_ok($link); } for my $type ( 'text/plain', 'text/html' ) { $m->form_name('CreateTicketInQueue'); $m->field( 'Queue', $qid ); $m->submit; is( $m->status, 200, "request successful" ); $m->content_contains('Create a new ticket', 'ticket create page' ); $m->form_name('TicketCreate'); $m->field( 'Subject', 'with plain attachment' ); $m->field( 'Attach', $plain_file ); $m->field( 'Content', 'this is main content' ); $m->field( 'ContentType', $type ) unless $type eq 'text/plain'; $m->click('SubmitTicket'); is( $m->status, 200, "request successful" ); $m->content_contains('with plain attachment', 'we have subject on the page' ); $m->content_contains('this is main content', 'main content' ); ok( $m->find_link( text => $plain_name, url_regex => qr{Attachment/} ), 'download plain file link' ); # Check for Message-IDs follow_parent_with_headers_link($m, url_regex => qr/Attachment\/WithHeaders\//, n => 1); $m->content_like(qr/^Message-ID:/im, 'create content has one Message-ID'); $m->content_unlike(qr/^Message-ID:.+?Message-ID:/ism, 'but not two Message-IDs'); $m->back; follow_with_headers_link($m, url_regex => qr/Attachment\/\d+\/\d+\/$plain_name/, n => 1); $m->content_unlike(qr/^Message-ID:/im, 'attachment lacks a Message-ID'); $m->back; my ( $mail ) = RT::Test->fetch_caught_mails; like( $mail, qr/this is main content/, 'email contains main content' ); # check the email link in page too $m->follow_link_ok( { url_regex => qr/ShowEmailRecord/ }, 'show the email outgoing' ); $m->content_contains('this is main content', 'email contains main content'); $m->back; $m->follow_link_ok( { text => 'Reply' }, "reply to the ticket" ); $m->form_name('TicketUpdate'); $m->field( 'Attach', $plain_file ); $m->click('AddMoreAttach'); is( $m->status, 200, "request successful" ); $m->form_name('TicketUpdate'); $m->field( 'Attach', $html_file ); # add UpdateCc so we can get email record $m->field( 'UpdateCc', 'rt-test@example.com' ); $m->field( 'UpdateContent', 'this is main reply content' ); $m->field( 'UpdateContentType', $type ) unless $type eq 'text/plain'; $m->click('SubmitTicket'); is( $m->status, 200, "request successful" ); $m->content_contains("this is main reply content", 'main reply content' ); ok( $m->find_link( text => $html_name, url_regex => qr{Attachment/} ), 'download html file link' ); # Check for Message-IDs follow_parent_with_headers_link($m, url_regex => qr/Attachment\/WithHeaders\//, n => 2); $m->content_like(qr/^Message-ID:/im, 'correspondence has one Message-ID'); $m->content_unlike(qr/^Message-ID:.+?Message-ID:/ism, 'but not two Message-IDs'); $m->back; follow_with_headers_link($m, url_regex => qr/Attachment\/\d+\/\d+\/$plain_name/, n => 2); $m->content_unlike(qr/^Message-ID:/im, 'text/plain attach lacks a Message-ID'); $m->back; follow_with_headers_link($m, url_regex => qr/Attachment\/\d+\/\d+\/$html_name/, n => 1); $m->content_unlike(qr/^Message-ID:/im, 'text/html attach lacks a Message-ID'); $m->back; ( $mail ) = RT::Test->fetch_caught_mails; like( $mail, qr/this is main reply content/, 'email contains main reply content' ); # check the email link in page too $m->follow_link_ok( { url_regex => qr/ShowEmailRecord/, n => 2 }, 'show the email outgoing' ); $m->content_contains("this is main reply content", 'email contains main reply content'); $m->back; } rt-5.0.1/t/web/dashboards-deleted-saved-search.t000644 000765 000024 00000005444 14005011336 022276 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ( $url, $m ) = RT::Test->started_ok; ok( $m->login, 'logged in' ); # create a saved search $m->get_ok( $url . "/Search/Build.html?Query=" . 'id=1' ); $m->submit_form( form_name => 'BuildQuery', fields => { SavedSearchDescription => 'foo', }, button => 'SavedSearchSave', ); my ( $search_uri, $user_id, $search_id ) = $m->content =~ /value="(RT::User-(\d+)-SavedSearch-(\d+))"/; $m->submit_form( form_name => 'BuildQuery', fields => { SavedSearchLoad => $search_uri }, button => 'SavedSearchSave', ); $m->content_like( qr/name="SavedSearchDelete"\s+value="Delete"/, 'found Delete button' ); $m->content_like( qr/name="SavedSearchDescription"\s+value="foo"/, 'found Description input with the value filled' ); # create a dashboard with the created search $m->get_ok( $url . "/Dashboards/Modify.html?Create=1" ); $m->submit_form( form_name => 'ModifyDashboard', fields => { Name => 'bar' }, ); $m->content_contains('Saved dashboard bar', 'dashboard saved' ); my $dashboard_queries_link = $m->find_link( text_regex => qr/Content/ ); my ( $dashboard_id ) = $dashboard_queries_link->url =~ /id=(\d+)/; $m->get_ok( $url . "/Dashboards/Queries.html?id=$dashboard_id" ); $m->content_lacks( 'value="Update"', 'no update button' ); # add foo saved search to the dashboard my $args = { "dashboard_id" => $dashboard_id, "body" => "saved-" . "RT::User-" . $user_id . "-SavedSearch-" . $search_id, }; $m->submit_form_ok({ form_name => 'UpdateSearches', fields => $args, button => 'UpdateSearches', }, "added search foo to dashboard bar" ); like( $m->uri, qr/results=[A-Za-z0-9]{32}/, 'URL redirected for results' ); $m->content_contains( 'Dashboard updated' ); # delete the created search $m->get_ok( $url . "/Search/Build.html?Query=" . 'id=1' ); $m->submit_form( form_name => 'BuildQuery', fields => { SavedSearchLoad => $search_uri }, ); $m->submit_form( form_name => 'BuildQuery', button => 'SavedSearchDelete', ); $m->content_lacks( $search_uri, 'deleted search foo' ); # here is what we really want to test $m->get_ok( $url . "/Dashboards/Queries.html?id=$dashboard_id" ); $m->content_contains('Unable to find search Saved Search: foo', 'found deleted message' ); $args = { "dashboard_id" => $dashboard_id, }; $m->submit_form_ok({ form_name => 'UpdateSearches', fields => $args, button => 'UpdateSearches', }, "removed search foo from dashboard" ); like( $m->uri, qr/results=[A-Za-z0-9]{32}/, 'URL redirected for results' ); $m->content_contains( 'Dashboard updated' ); $m->get_ok( $url . "/Dashboards/Queries.html?id=$dashboard_id" ); $m->content_lacks('Unable to find search Saved Search: foo', 'deleted message is gone' ); done_testing; rt-5.0.1/t/web/cf_groupings_user.t000644 000765 000024 00000006532 14005011336 017737 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; RT->Config->Set( 'CustomFieldGroupings', 'RT::User' => { Identity => ['TestIdentity'], 'Access control' => ['TestAccessControl'], Location => ['TestLocation'], Phones => ['TestPhones'], More => ['TestMore'], }, ); my %CF; while (my ($group,$cfs) = each %{ RT->Config->Get('CustomFieldGroupings')->{'RT::User'} } ) { my $name = $cfs->[0]; my $cf = RT::CustomField->new( RT->SystemUser ); my ($id, $msg) = $cf->Create( Name => $name, Description => 'A custom field', LookupType => RT::User->new( $RT::SystemUser )->CustomFieldLookupType, Type => 'FreeformSingle', Pattern => '^(?!bad value).*$', ); ok $id, "custom field '$name' correctly created"; ($id, $msg) = $cf->AddToObject( RT::User->new( $cf->CurrentUser ) ); ok $id, "applied custom field" or diag "error: $msg"; $group =~ s/\W//g; $CF{$name} = "$group-" . $cf->Id; } my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in as root'; my %location = ( Identity => ".user-info-identity", AccessControl => ".user-info-access-control", Location => ".user-info-location", Phones => ".user-info-phones", More => ".user-info-cfs", ); { note "testing Create"; $m->follow_link_ok({id => 'admin-users-create'}, 'Create '); my $dom = $m->dom; $m->form_name('UserCreate'); $m->field( 'Name', 'user1' ); my $prefix = 'Object-RT::User--CustomField:'; for my $name (keys %location) { my $input_name = $prefix . $CF{"Test$name"} .'-Value'; is $dom->find(qq{input[name="$input_name"]})->size, 1, "only one CF input on the page"; ok $dom->at(qq{$location{$name} input[name="$input_name"]}), "CF is in the right place"; $m->field( $input_name, "Test${name}Value" ); } $m->submit; $m->content_like(qr{User created}); } my ($id) = ($m->uri =~ /id=(\d+)/); ok $id, "found user's id #$id"; { note "testing values on Modify page and on the object"; my $user = RT::User->new( RT->SystemUser ); $user->Load( $id ); ok $user->id, "loaded user"; my $dom = $m->dom; $m->form_name('UserModify'); my $prefix = "Object-RT::User-$id-CustomField:"; foreach my $name ( keys %location ) { is $user->FirstCustomFieldValue("Test$name"), "Test${name}Value", "correct value of Test$name CF"; my $input_name = $prefix . $CF{"Test$name"} .'-Value'; is $m->value($input_name), "Test${name}Value", "correct value in UI"; $m->field( $input_name, "Test${name}Changed" ); ok $dom->at(qq{$location{$name} input[name="$input_name"]}), "CF is in the right place"; } $m->submit; } { note "testing that update works"; my $user = RT::User->new( RT->SystemUser ); $user->Load( $id ); ok $user->id, "loaded user"; $m->form_name('UserModify'); my $prefix = "Object-RT::User-$id-CustomField:"; foreach my $name ( keys %location ) { is $user->FirstCustomFieldValue("Test$name"), "Test${name}Changed", "correct value of Test$name CF"; my $input = $prefix . $CF{"Test$name"} .'-Value'; is $m->value($input), "Test${name}Changed", "correct value in UI"; } } done_testing; rt-5.0.1/t/web/ticket_create.t000644 000765 000024 00000010640 14005011336 017015 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use RT::Lifecycle; # populate lifecycles my $lifecycles = RT->Config->Get('Lifecycles'); RT->Config->Set( Lifecycles => %{$lifecycles}, foo => { initial => ['initial'], active => ['open'], inactive => ['resolved'], } ); RT::Lifecycle->FillCache(); # populate test queues and test user my $queue1 = RT::Test->load_or_create_queue( Name => 'General' ); my $queue2 = RT::Test->load_or_create_queue( Name => 'Another queue' ); my $user = RT::Test->load_or_create_user( Name => 'user', Password => 'password', ); my ( $baseurl, $m ) = RT::Test->started_ok; #set up lifecycle for one of the queues ok $m->login; $m->get_ok( '/Admin/Queues/Modify.html?id=' . $queue1->id ); $m->form_name('ModifyQueue'); $m->submit_form( fields => { Lifecycle => 'foo' } ); # set up custom field my $cf = RT::Test->load_or_create_custom_field( Name => 'test_cf', Queue => $queue1->Name, Type => 'FreeformSingle' ); my $cf_form_id = 'Object-RT::Ticket--CustomField-' . $cf->Id . '-Value'; my $cf_test_value = "some string for test_cf $$"; # load initial ticket create page without specifying queue # should have default queue with no custom fields note('load create ticket page with defaults'); $m->get_ok('/'); $m->submit_form( form_name => "CreateTicketInQueue", ); ok( !$m->form_name('TicketCreate')->find_input($cf_form_id), 'custom field not present' ); is( $m->form_name('TicketCreate')->value('Queue'), $queue2->id, 'Queue selection dropdown populated and pre-selected' ); is( $m->form_name('TicketCreate')->value('Status'), 'new', 'Status selection dropdown populated and pre-selected' ); $m->get_ok( '/Ticket/Create.html', 'go to ticket create page with no queue id' ); ok( !$m->form_name('TicketCreate')->find_input($cf_form_id), 'custom field not present' ); is( $m->form_name('TicketCreate')->value('Queue'), $queue2->id, 'Queue selection dropdown populated and pre-selected' ); is( $m->form_name('TicketCreate')->value('Status'), 'new', 'Status selection dropdown populated and pre-selected' ); # test ticket creation on reload from selected queue, specifying queue with custom fields note('reload ticket create page with selected queue'); $m->get_ok( '/Ticket/Create.html?Queue=' . $queue1->id, 'go to ticket create page' ); is( $m->form_name('TicketCreate')->value('Queue'), $queue1->id, 'Queue selection dropdown populated and pre-selected' ); ok( $m->form_name('TicketCreate')->find_input($cf_form_id), 'custom field present' ); is( $m->form_name('TicketCreate')->value($cf_form_id), '', 'custom field present and empty' ); my $form = $m->form_name('TicketCreate'); my $status_input = $form->find_input('Status'); is_deeply( [ $status_input->possible_values ], [ 'initial', 'open', 'resolved' ], 'status selectbox shows custom lifecycle for queue' ); note('submit populated form'); $m->submit_form( fields => { Subject => 'ticket foo', 'Queue' => $queue1->id, $cf_form_id => $cf_test_value }, button => 'SubmitTicket' ); $m->text_contains( 'test_cf', 'custom field populated in display' ); $m->text_contains( $cf_test_value, 'custom field populated in display' ); my $ticket = RT::Test->last_ticket; ok( $ticket->id, 'ticket is created' ); is( $ticket->QueueObj->id, $queue1->id, 'Ticket created with correct queue' ); ok( $m->logout, 'Logged out' ); ok( $m->login( 'user', 'password' ), 'logged in as user' ); $m->submit_form_ok( { form_name => 'CreateTicketInQueue' }, 'Try to create ticket' ); $m->content_contains('Permission Denied', 'No permission to create ticket'); $m->warning_like(qr/Permission Denied/, 'Permission denied warning' ); ok( $user->PrincipalObj->GrantRight( Right => 'SeeQueue', Object => RT->System ), 'Grant SeeQueue right' ); $m->submit_form_ok( { form_name => 'CreateTicketInQueue' }, 'Try to create ticket' ); $m->content_contains( 'Permission Denied', 'No permission to create ticket even with SeeQueue' ); $m->warning_like(qr/Permission Denied/, 'Permission denied warning' ); ok( $user->PrincipalObj->GrantRight( Right => 'CreateTicket', Object => $queue2 ), 'Grant CreateTicket right' ); $m->submit_form_ok( { form_name => 'CreateTicketInQueue' }, 'Try to create ticket' ); $m->content_lacks( 'Permission Denied', 'Has permission to create ticket' ); $form = $m->form_name('TicketCreate'); is_deeply( [ $form->find_input('Queue','option')->possible_values ], [ $queue2->id ], 'Only Another queue is listed' ); done_testing(); rt-5.0.1/t/web/group_summary.t000644 000765 000024 00000004265 14005011336 017126 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my ( $baseurl, $m ) = RT::Test->started_ok; ok $m->login, 'logged in as root'; my $group_name = 'test group'; my $group_id; diag( 'Group Summary access and ticket creation' ); { $m->follow_link( id => 'admin-groups-create'); $m->submit_form( form_name => 'ModifyGroup', fields => { Name => $group_name, }, ); $m->content_contains( 'Group created', 'created group successfully' ); $group_id = $m->form_name( 'ModifyGroup' )->value( 'id' ); ok( $group_id, "Found id of the group in the form, #$group_id" ); $m->follow_link_ok({ id => 'page-summary', url_regex => qr|/Group/Summary\.html\?id=$group_id$!| }, 'Followed Group Summary link'); $m->submit_form_ok({ form_name => 'CreateTicket' }, "Submitted form to create ticket with group $group_id as Cc" ); like( $m->uri, qr{/Ticket/Create\.html\?AddGroupCc=$group_id&Queue=1$}, "now on /Ticket/Create\.html with param AddGroupCc=$group_id" ); my $subject = 'test AddGroupCc ticket'; $m->submit_form_ok({ form_name => 'TicketCreate', fields => { Subject => $subject, }, button => 'SubmitTicket' }, 'Submitted form to create ticket with group cc'); like( $m->uri, qr{/Ticket/Display\.html\?id}, "now on /Ticket/Display\.html" ); $m->get( "/Group/Summary.html?id=$group_id" ); $m->content_contains( $subject, 'Group Cc ticket was found on Group Summary page' ); } ok( $m->logout(), 'Logged out' ); diag( 'Access Group Summary with non-root user' ); { my $tester = RT::Test->load_or_create_user( Name => 'staff1', Password => 'password' ); ok( $m->login( $tester->Name, 'password' ), 'Logged in' ); $m->get_ok( "/Group/Summary.html?id=$group_id" ); $m->warning_like( qr/No permission to view group/, "Got permission denied warning without SeeGroup right" ); ok( $tester->PrincipalObj->GrantRight( Right => 'SeeGroup', Object => $RT::System ), 'Grant SeeGroup' ); $m->get_ok( "/Group/Summary.html?id=$group_id" ); $m->no_warnings_ok( "No warning with SeeGroup right" ); } done_testing(); rt-5.0.1/t/web/ticket_owner_issues_16656.t000644 000765 000024 00000004161 14005011336 021047 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 19; my $queue = RT::Test->load_or_create_queue( Name => 'Test' ); ok $queue && $queue->id, 'loaded or created queue'; my $user_a = RT::Test->load_or_create_user( Name => 'user_a', EmailAddress => 'user_a@example.com', Password => 'password', ); ok $user_a && $user_a->id, 'loaded or created user'; RT->Config->Set( AutocompleteOwners => 0 ); my ($baseurl, $agent_root) = RT::Test->started_ok; ok( RT::Test->set_rights({ Principal => 'Requestor', Object => $queue, Right => [qw(OwnTicket)] }), 'set rights'); ok $agent_root->login('root', 'password'), 'logged in as user root'; diag "user_a doesn't show up in create form"; { $agent_root->get_ok('/Ticket/Create.html?Queue=1', 'open ticket create page'); $agent_root->content_contains('Create a new ticket', 'opened create ticket page'); my $form = $agent_root->form_name('TicketCreate'); my $input = $form->find_input('Owner'); is $input->value, RT->Nobody->Id, 'correct owner selected'; ok((not scalar grep { $_ == $user_a->Id } $input->possible_values), 'no user_a value in dropdown'); $form->value('Requestors', 'user_a@example.com'); $agent_root->click('SubmitTicket'); $agent_root->content_like(qr/Ticket \d+ created in queue/i, 'created ticket'); my ($id) = ($agent_root->content =~ /Ticket (\d+) created in queue/); ok $id, 'found id of the ticket'; my $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load( $id ); ok $ticket->id, 'loaded the ticket'; is $ticket->Queue, '1', 'correct queue'; is $ticket->Owner, RT->Nobody->Id, 'correct owner'; is $ticket->RequestorAddresses, 'user_a@example.com', 'correct requestor'; } diag "user_a doesn't appear in owner list after being made requestor"; { $agent_root->get("/Ticket/Modify.html?id=1"); my $form = $agent_root->form_name('TicketModify'); my $input = $form->find_input('Owner'); is $input->value, RT->Nobody->Id, 'correct owner selected'; ok((not scalar grep { $_ == $user_a->Id } $input->possible_values), 'no user_a value in dropdown'); } rt-5.0.1/t/web/dashboards-basics.t000644 000765 000024 00000026530 14005011336 017570 0ustar00sunnavystaff000000 000000 use strict; use warnings; use HTTP::Status qw(); use RT::Test tests => undef; my ($baseurl, $m) = RT::Test->started_ok; my $url = $m->rt_base_url; my $user_obj = RT::User->new(RT->SystemUser); my ($ret, $msg) = $user_obj->LoadOrCreateByEmail('customer@example.com'); ok($ret, 'ACL test user creation'); $user_obj->SetName('customer'); $user_obj->SetPrivileged(1); ($ret, $msg) = $user_obj->SetPassword('customer'); $user_obj->PrincipalObj->GrantRight(Right => 'ModifySelf'); my $currentuser = RT::CurrentUser->new($user_obj); my $onlooker = RT::User->new(RT->SystemUser); ($ret, $msg) = $onlooker->LoadOrCreateByEmail('onlooker@example.com'); ok($ret, 'ACL test user creation'); $onlooker->SetName('onlooker'); $onlooker->SetPrivileged(1); ($ret, $msg) = $onlooker->SetPassword('onlooker'); my $queue = RT::Queue->new(RT->SystemUser); $queue->Create(Name => 'SearchQueue'.$$); for my $user ($user_obj, $onlooker) { for my $right (qw/ModifySelf ShowSavedSearches/) { $user->PrincipalObj->GrantRight(Right => $right); } for my $right (qw/SeeQueue ShowTicket OwnTicket/) { $user->PrincipalObj->GrantRight(Right => $right, Object => $queue); } } # Add some system non-ticket searches ok $m->login('root'), "logged in as root"; $m->get_ok( $url . "/Search/Chart.html?Query=" . 'id=1' ); $m->submit_form( form_name => 'SaveSearch', fields => { SavedSearchDescription => 'first chart', SavedSearchOwner => 'RT::System-1', }, button => 'SavedSearchSave', ); $m->content_contains("Chart first chart saved", 'saved first chart' ); $m->get_ok( $url . "/Search/Build.html?Class=RT::Transactions&Query=" . 'TicketId=1' ); $m->submit_form( form_name => 'BuildQuery', fields => { SavedSearchDescription => 'first txn search', SavedSearchOwner => 'RT::System-1', }, button => 'SavedSearchSave', ); # We don't show saved message on page :/ $m->content_contains("Save as New", 'saved first txn search' ); ok $m->login(customer => 'customer', logout => 1), "logged in"; $m->get_ok($url."Dashboards/index.html"); $m->content_lacks('New', "No 'new dashboard' link because we have no CreateOwnDashboard"); $m->no_warnings_ok; $m->get($url."Dashboards/Modify.html?Create=1"); is($m->status, HTTP::Status::HTTP_FORBIDDEN); $m->content_contains("Permission Denied"); $m->content_lacks("Save Changes"); $m->warning_like(qr/Permission Denied/, "got a permission denied warning"); $user_obj->PrincipalObj->GrantRight(Right => 'ModifyOwnDashboard', Object => $RT::System); # Modify itself is no longer good enough, you need Create $m->get($url."Dashboards/Modify.html?Create=1"); is($m->status, HTTP::Status::HTTP_FORBIDDEN); $m->content_contains("Permission Denied"); $m->content_lacks("Save Changes"); $m->warning_like(qr/Permission Denied/, "got a permission denied warning"); $user_obj->PrincipalObj->GrantRight(Right => 'CreateOwnDashboard', Object => $RT::System); $m->get_ok($url."Dashboards/Modify.html?Create=1"); $m->content_lacks("Permission Denied"); $m->content_contains("Create"); $m->get_ok($url."Dashboards/index.html"); $m->content_contains("New", "'New' link because we now have ModifyOwnDashboard"); $m->follow_link_ok({ id => 'reports-dashboard_create'}); $m->form_name('ModifyDashboard'); $m->field("Name" => 'different dashboard'); $m->content_lacks('Delete', "Delete button hidden because we are creating"); $m->click_button(value => 'Create'); $m->content_contains("Saved dashboard different dashboard"); $user_obj->PrincipalObj->GrantRight(Right => 'SeeOwnDashboard', Object => $RT::System); $m->get($url."Dashboards/index.html"); $m->follow_link_ok({ text => 'different dashboard'}); $m->content_lacks("Permission Denied", "we now have SeeOwnDashboard"); $m->content_lacks('Delete', "Delete button hidden because we lack DeleteOwnDashboard"); $m->get_ok($url."Dashboards/index.html"); $m->content_contains("different dashboard", "we now have SeeOwnDashboard"); $m->content_lacks("Permission Denied"); $m->follow_link_ok({text => "different dashboard"}); $m->content_contains("Basics"); $m->content_contains("Content"); $m->content_lacks("Subscription", "we don't have the SubscribeDashboard right"); $m->follow_link_ok({text => "Basics"}); $m->content_contains("Modify the dashboard different dashboard"); # add 'Unowned Tickets' to body of 'different dashboard' dashboard $m->follow_link_ok({text => "Content"}); $m->content_contains("Modify the content of dashboard different dashboard"); my ( $id ) = ( $m->uri =~ /id=(\d+)/ ); ok( $id, "got a dashboard ID, $id" ); # 8 my $args = { UpdateSearches => "Save", body => ["saved-" . $m->dom->find('[data-description="Unowned Tickets"]')->first->attr('data-name')], sidebar => [], }; my $res = $m->post( $url . "Dashboards/Queries.html?id=$id", $args, ); is( $res->code, 200, "add 'unowned tickets' to body" ); like( $m->uri, qr/results=[A-Za-z0-9]{32}/, 'URL redirected for results' ); $m->content_contains( 'Dashboard updated' ); my $dashboard = RT::Dashboard->new($currentuser); $dashboard->LoadById($id); is($dashboard->Name, 'different dashboard', "'different dashboard' name is correct"); is($dashboard->Privacy, 'RT::User-' . $user_obj->Id, "correct privacy"); is($dashboard->PossibleHiddenSearches, 0, "all searches are visible"); my @searches = $dashboard->Searches; is(@searches, 1, "one saved search in the dashboard"); like($searches[0]->Name, qr/newest unowned tickets/, "correct search name"); push( @{$args->{body}}, "saved-" . $m->dom->find('[data-description="My Tickets"]')->first->attr('data-name'), "saved-" . $m->dom->find('[data-description="first chart"]')->first->attr('data-name'), "saved-" . $m->dom->find('[data-description="first txn search"]')->first->attr('data-name'), ); $res = $m->post( $url . 'Dashboards/Queries.html?id=' . $id, $args, ); is( $res->code, 200, "add more searches to body" ); like( $m->uri, qr/results=[A-Za-z0-9]{32}/, 'URL redirected for results' ); $m->content_contains( 'Dashboard updated' ); $dashboard->LoadById($id); @searches = $dashboard->Searches; is(@searches, 4, "4 saved searches in the dashboard"); like($searches[0]->Name, qr/newest unowned tickets/, "correct existing search name"); like($searches[1]->Name, qr/highest priority tickets I own/, "correct new search name"); is($searches[2]->Name, 'first chart', "correct existing search name"); is($searches[3]->Name, 'first txn search', "correct new search name"); my $ticket = RT::Ticket->new(RT->SystemUser); $ticket->Create( Queue => $queue->Id, Requestor => [ $user_obj->Name ], Owner => $user_obj, Subject => 'dashboard test', ); $m->get_ok($url."Dashboards/index.html"); $m->follow_link_ok({text => "different dashboard"}); $m->follow_link_ok({id => 'page-show'}); $m->content_contains("50 highest priority tickets I own"); $m->content_contains("50 newest unowned tickets"); $m->content_contains("first chart"); $m->content_contains("first txn search"); $m->content_unlike( qr/Bookmarked Tickets.*Bookmarked Tickets/s, 'only dashboard queries show up' ); $m->content_contains("dashboard test", "ticket subject"); $m->get_ok("/Dashboards/$id/This fragment left intentionally blank"); $m->content_contains("50 highest priority tickets I own"); $m->content_contains("50 newest unowned tickets"); $m->content_unlike( qr/Bookmarked Tickets.*Bookmarked Tickets/s, 'only dashboard queries show up' ); $m->content_contains("dashboard test", "ticket subject"); $m->get("/Dashboards/Modify.html?id=$id&Delete=1"); is($m->status, HTTP::Status::HTTP_FORBIDDEN); $m->content_contains("Permission Denied", "unable to delete dashboard because we lack DeleteOwnDashboard"); $m->warning_like(qr/Couldn't delete dashboard.*Permission Denied/, "got a permission denied warning when trying to delete the dashboard"); $user_obj->PrincipalObj->GrantRight(Right => 'DeleteOwnDashboard', Object => $RT::System); $m->get_ok("/Dashboards/Modify.html?id=$id"); $m->content_contains('Delete', "Delete button shows because we have DeleteOwnDashboard"); $m->form_name('ModifyDashboard'); $m->click_button(name => 'Delete'); $m->content_contains("Deleted dashboard"); $m->get("/Dashboards/Modify.html?id=$id"); $m->content_lacks("different dashboard", "dashboard was deleted"); $m->content_contains("Could not load dashboard $id"); $m->next_warning_like(qr/Failed to load dashboard/, "the dashboard was deleted"); $m->next_warning_like(qr/Could not load dashboard/, "the dashboard was deleted"); $user_obj->PrincipalObj->GrantRight(Right => "SuperUser", Object => $RT::System); # now test that we warn about searches others can't see # first create a personal saved search... $m->get_ok($url."Search/Build.html"); $m->follow_link_ok({text => 'Advanced'}); $m->form_with_fields('Query'); $m->field(Query => "id > 0"); $m->submit; $m->form_with_fields('SavedSearchDescription'); $m->field(SavedSearchDescription => "personal search"); $m->click_button(name => "SavedSearchSave"); # then the system-wide dashboard $m->get_ok($url."Dashboards/Modify.html?Create=1"); $m->form_name('ModifyDashboard'); $m->field("Name" => 'system dashboard'); $m->field("Privacy" => 'RT::System-1'); $m->content_lacks('Delete', "Delete button hidden because we are creating"); $m->click_button(value => 'Create'); $m->content_lacks("No permission to create dashboards"); $m->content_contains("Saved dashboard system dashboard"); $m->follow_link_ok({id => 'page-content'}); my ( $system_id ) = ( $m->uri =~ /id=(\d+)/ ); ok( $system_id, "got a dashboard ID for the system dashboard, $system_id" ); # get the saved search name from the content my ( $saved_search_name ) = ( $m->content =~ /(RT::User-\d+-SavedSearch-\d+)/ ); ok( $saved_search_name, "got a saved search name, $saved_search_name" ); # RT::User-27-SavedSearch-9 push( @{$args->{body}}, ( "saved-" . $saved_search_name, ) ); $res = $m->post( $url . 'Dashboards/Queries.html?id=' . $system_id, $args, ); is( $res->code, 200, "add 'personal search' to body" ); like( $m->uri, qr/results=[A-Za-z0-9]{32}/, 'URL redirected for results' ); $m->content_contains( 'Dashboard updated' ); $m->get_ok($url."Dashboards/Queries.html?id=$system_id"); $m->content_contains("Warning: may not be visible to all viewers"); $m->follow_link_ok({id => 'page-show'}); $m->content_contains("personal search", "saved search shows up"); $m->content_contains("dashboard test", "matched ticket shows up"); # make sure the onlooker can't see the search... $onlooker->PrincipalObj->GrantRight(Right => 'SeeDashboard', Object => $RT::System); my $omech = RT::Test::Web->new; ok $omech->login(onlooker => 'onlooker'), "logged in"; $omech->get_ok("/Dashboards"); $omech->follow_link_ok({text => 'system dashboard'}); $omech->content_lacks("personal search", "saved search doesn't show up"); $omech->content_lacks("dashboard test", "matched ticket doesn't show up"); $omech->warning_like(qr/User .* tried to load container user /, "can't see other users' personal searches"); # make sure that navigating to dashboard pages with bad IDs throws an error my $bad_id = $system_id + 1; for my $page (qw/Modify Queries Render Subscription/) { $m->get("/Dashboards/$page.html?id=$bad_id"); $m->content_like(qr/Could not load dashboard $bad_id/); $m->next_warning_like(qr/Unable to load dashboard with $bad_id/); $m->next_warning_like(qr/Could not load dashboard $bad_id/); } done_testing(); rt-5.0.1/t/web/requestor_groups_edit_link.t000644 000765 000024 00000002452 14005011336 021663 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 11; RT->Config->Set( ShowMoreAboutPrivilegedUsers => 1 ); my ( $url, $m ) = RT::Test->started_ok; my $user_a = RT::Test->load_or_create_user( Name => 'user_a', Password => 'password', ); ok( $user_a, 'created user user_a' ); ok( RT::Test->set_rights( { Principal => $user_a, Right => [ qw/SeeQueue ShowTicket CreateTicket/ ] }, ), 'set rights for user_a' ); my $ticket = RT::Ticket->new(RT->SystemUser); my ($id) = $ticket->Create( Subject => 'groups limit', Queue => 'General', Requestor => $user_a->id, ); ok( $id, 'created ticket' ); my $url_regex = qr{\/Admin\/Users\/Memberships\.html}; ok( $m->login( user_a => 'password' ), 'logged in as user_a' ); $m->goto_ticket($id); ok( !$m->find_link( url_regex => $url_regex ), 'no Edit link without AdminUsers permission' ); ok( RT::Test->add_rights( { Principal => $user_a, Right => [ qw/AdminUsers ShowConfigTab/ ] }, ), 'add AdminUsers and ShowConfigTab rights for user_a' ); $m->goto_ticket($id); $m->follow_link_ok( { url_regex => $url_regex }, 'follow the Edit link' ); is( $m->uri, $url . "/Admin/Users/Memberships.html?id=" . $user_a->id, 'url is right' ); rt-5.0.1/t/web/cf_datetime.t000644 000765 000024 00000025322 14005011336 016456 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; RT->Config->Set( 'Timezone' => 'EST5EDT' ); # -04:00 my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in as root'; my $root = RT::User->new( RT->SystemUser ); ok( $root->Load('root'), 'load root user' ); my $cf_name = 'test cf datetime'; my $why; if ( ( $ENV{RT_TEST_WEB_HANDLER} || '' ) =~ /^apache(\+mod_perl)?$/ && RT::Test::Apache->apache_mpm_type =~ /^(?:worker|event)$/ ) { $why = 'localizing $ENV{TZ} does *not* work with mod_perl+mpm_event or mod_perl+mpm_worker'; } my $cfid; diag "Create a CF"; { $m->follow_link( id => 'admin-custom-fields-create'); $m->submit_form( form_name => "ModifyCustomField", fields => { Name => $cf_name, TypeComposite => 'DateTime-1', LookupType => 'RT::Queue-RT::Ticket', EntryHint => 'Select datetime', }, ); $m->content_contains('Object created', 'created CF sucessfully' ); $cfid = $m->form_name('ModifyCustomField')->value('id'); ok $cfid, "found id of the CF in the form, it's #$cfid"; } diag "apply the CF to General queue"; my $queue = RT::Test->load_or_create_queue( Name => 'General' ); ok $queue && $queue->id, 'loaded or created queue'; { $m->follow_link( text => 'Queues' ); $m->title_is(q/Admin queues/, 'admin-queues screen'); $m->follow_link( text => 'General' ); $m->title_is(q/Configuration for queue General/, 'admin-queue: general'); $m->follow_link( id => 'page-custom-fields-tickets' ); $m->title_is(q/Custom Fields for queue General/, 'admin-queue: general cfid'); $m->form_name('EditCustomFields'); $m->tick( "AddCustomField" => $cfid ); $m->click('UpdateCFs'); $m->content_contains("Added custom field $cf_name to General", 'TCF added to the queue' ); } diag 'check valid inputs with various timezones in ticket create page'; { my ( $ticket, $id ); $m->submit_form( form_name => "CreateTicketInQueue" ); $m->content_contains('Select datetime', 'has cf field'); $m->submit_form( form_name => "TicketCreate", fields => { Subject => 'test 2010-05-04 13:00:01', Content => 'test', "Object-RT::Ticket--CustomField-$cfid-Values" => '2010-05-04 13:00:01', }, button => 'SubmitTicket', ); ok( ($id) = $m->content =~ /Ticket (\d+) created/, "created ticket $id" ); $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load($id); TODO: { local $TODO = $why; is( $ticket->CustomFieldValues($cfid)->First->Content, '2010-05-04 17:00:01', 'date in db is in UTC' ); } $m->content_contains('test cf datetime:', 'has cf datetime field on the page'); $m->content_contains('Tue May 04 13:00:01 2010', 'has cf datetime value on the page'); $root->SetTimezone( 'Asia/Shanghai' ); # interesting that $m->reload doesn't work $m->get_ok( $m->uri ); TODO: { local $TODO = $why; $m->content_contains( 'Wed May 05 01:00:01 2010', 'cf datetime value respects user timezone' ); } $m->submit_form( form_name => "CreateTicketInQueue" ); $m->submit_form( form_name => "TicketCreate", fields => { Subject => 'test 2010-05-06 07:00:01', Content => 'test', "Object-RT::Ticket--CustomField-$cfid-Values" => '2010-05-06 07:00:01', }, button => 'SubmitTicket', ); ok( ($id) = $m->content =~ /Ticket (\d+) created/, "created ticket $id" ); $ticket = RT::Ticket->new( RT->SystemUser ); $ticket->Load($id); TODO: { local $TODO = $why; is( $ticket->CustomFieldValues($cfid)->First->Content, '2010-05-05 23:00:01', 'date in db is in UTC' ); } $m->content_contains('test cf datetime:', 'has cf datetime field on the page'); $m->content_contains( 'Thu May 06 07:00:01 2010', 'cf datetime input respects user timezone' ); $root->SetTimezone( 'EST5EDT' ); # back to -04:00 $m->get_ok( $m->uri ); TODO: { local $TODO = $why; $m->content_contains( 'Wed May 05 19:00:01 2010', 'cf datetime value respects user timezone' ); } } diag 'check search build page'; { $m->get_ok( $baseurl . '/Search/Build.html?Query=Queue=1' ); $m->form_name('BuildQuery'); my ($cf_op) = $m->find_all_inputs( type => 'option', name_regex => qr/test cf datetime/ ); is_deeply( [ $cf_op->possible_values ], [ '<', '=', '>' ], 'right oprators' ); my ($cf_field) = $m->find_all_inputs( type => 'text', name_regex => qr/test cf datetime/ ); is_results_number( { $cf_op->name => '=', $cf_field->name => '2010-05-04', }, 1 ); like($m->dom->at('table.ticket-list')->all_text, qr/2010-05-04/, 'got the right ticket'); unlike($m->dom->at('table.ticket-list')->all_text, qr/2010-05-06/, 'did not get the wrong ticket'); my $shanghai = RT::Test->load_or_create_user( Name => 'shanghai', Password => 'password', Timezone => 'Asia/Shanghai', ); ok( $shanghai->PrincipalObj->GrantRight( Right => 'SuperUser', Object => $RT::System, )); $m->login( 'shanghai', 'password', logout => 1 ); is_results_number( { $cf_op->name => '<', $cf_field->name => '2010-05-07', }, 2 ); is_results_number( { $cf_op->name => '>', $cf_field->name => '2010-05-04', }, 2 ); TODO: { local $TODO = $why; is_results_number( { $cf_op->name => '=', $cf_field->name => '2010-05-05', }, 1 ); is_results_number( { $cf_op->name => '=', $cf_field->name => '2010-05-05 01:00:01', }, 1 ); } is_results_number( { $cf_op->name => '=', $cf_field->name => '2010-05-05 02:00:01', }, 0 ); is_results_number( { $cf_op->name => '=', $cf_field->name => '2010-05-06', }, 1 ); is_results_number( { $cf_op->name => '=', $cf_field->name => '2010-05-06 07:00:01', }, 1 ); is_results_number( { $cf_op->name => '=', $cf_field->name => '2010-05-06 08:00:01', }, 0 ); } diag 'check invalid inputs'; { $m->submit_form( form_name => "CreateTicketInQueue" ); my $form = $m->form_name("TicketCreate"); $m->submit_form( form_name => "TicketCreate", fields => { Subject => 'test', Content => 'test', "Object-RT::Ticket--CustomField-$cfid-Values" => 'foodate', }, button => 'SubmitTicket', ); $m->content_like(qr/Ticket \d+ created/, "a ticket is created succesfully"); $m->content_contains('test cf datetime:', 'has cf datetime field on the page'); $m->content_lacks('foodate', 'invalid dates not set'); my @warnings = $m->get_warnings; chomp @warnings; is_deeply( [ @warnings ], [ ( q{Couldn't parse date 'foodate' by Time::ParseDate}, q{Couldn't parse date 'foodate' by DateTime::Format::Natural} ) x 2 ] ); } diag 'retain values when adding attachments'; { my ( $ticket, $id ); my $txn_cf = RT::CustomField->new( RT->SystemUser ); my ( $ret, $msg ) = $txn_cf->Create( Name => 'test txn cf datetime', TypeComposite => 'DateTime-1', LookupType => 'RT::Queue-RT::Ticket-RT::Transaction', ); ok( $ret, "created 'txn datetime': $msg" ); $txn_cf->AddToObject(RT::Queue->new(RT->SystemUser)); my $txn_cfid = $txn_cf->id; $m->submit_form( form_name => "CreateTicketInQueue" ); $m->content_contains('test cf datetime', 'has cf' ); $m->content_contains('test txn cf datetime', 'has txn cf' ); $m->submit_form_ok( { form_name => "TicketCreate", fields => { Subject => 'test 2015-06-04', Content => 'test', "Object-RT::Ticket--CustomField-$cfid-Values" => '2015-06-04 08:30:00', "Object-RT::Transaction--CustomField-$txn_cfid-Values" => '2015-08-15 12:30:30', }, button => 'AddMoreAttach', }, 'Create test ticket' ); $m->form_name("TicketCreate"); is( $m->value( "Object-RT::Ticket--CustomField-$cfid-Values" ), "2015-06-04 08:30:00", "ticket cf date value still on form" ); $m->content_contains( "Jun 04 08:30:00 2015", 'date in parens' ); is( $m->value( "Object-RT::Transaction--CustomField-$txn_cfid-Values" ), "2015-08-15 12:30:30", "txn cf date date value still on form" ); $m->content_contains( "Aug 15 12:30:30 2015", 'date in parens' ); $m->submit_form( button => 'SubmitTicket' ); ok( ($id) = $m->content =~ /Ticket (\d+) created/, "Created ticket $id" ); $m->follow_link_ok( {text => 'Reply'} ); $m->title_like( qr/Update/ ); $m->content_contains('test txn cf date', 'has txn cf'); $m->submit_form_ok( { form_name => "TicketUpdate", fields => { Content => 'test', "Object-RT::Transaction--CustomField-$txn_cfid-Values" => '2015-09-16 09:30:40', }, button => 'AddMoreAttach', }, 'Update test ticket' ); $m->form_name("TicketUpdate"); is( $m->value( "Object-RT::Transaction--CustomField-$txn_cfid-Values" ), "2015-09-16 09:30:40", "Date value still on form" ); $m->content_contains( "Sep 16 09:30:40 2015", 'date in parens' ); $m->follow_link_ok( {text => 'Jumbo'} ); $m->title_like( qr/Jumbo/ ); $m->submit_form_ok( { form_name => "TicketModifyAll", fields => { "Object-RT::Transaction--CustomField-$txn_cfid-Values" => '2015-12-16 03:00:00', }, button => 'AddMoreAttach', }, 'jumbo form' ); $m->form_name("TicketModifyAll"); is( $m->value( "Object-RT::Transaction--CustomField-$txn_cfid-Values" ), "2015-12-16 03:00:00", "txn date value still on form" ); $m->content_contains( "Dec 16 03:00:00 2015", 'date in parens' ); } sub is_results_number { local $Test::Builder::Level = $Test::Builder::Level + 1; my $fields = shift; my $number = shift; my $operator = shift; my $value = shift; { local $TODO; $m->get_ok( $baseurl . '/Search/Build.html?Query=Queue=1' ); } $m->form_name('BuildQuery'); $m->submit_form( fields => $fields, button => 'DoSearch', ); $m->content_contains( "Found $number ticket", "Found $number ticket" ); } done_testing; rt-5.0.1/t/web/gnupg-tickyboxes.t000644 000765 000024 00000004620 14005011336 017512 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test::GnuPG tests => 22, gnupg_options => { passphrase => 'rt-test' }; use RT::Action::SendEmail; RT::Test->import_gnupg_key('rt-recipient@example.com'); RT::Test->import_gnupg_key('rt-test@example.com', 'public'); my $queue = RT::Test->load_or_create_queue( Name => 'Regression', CorrespondAddress => 'rt-recipient@example.com', CommentAddress => 'rt-recipient@example.com', ); ok $queue && $queue->id, 'loaded or created queue'; my ($baseurl, $m) = RT::Test->started_ok; ok $m->login, 'logged in'; my @variants = ( {}, { Sign => 1 }, { Encrypt => 1 }, { Sign => 1, Encrypt => 1 }, ); # collect emails my %mail = ( plain => [], signed => [], encrypted => [], signed_encrypted => [], ); diag "check in read-only mode that queue's props influence create/update ticket pages"; { foreach my $variant ( @variants ) { set_queue_crypt_options( $queue => %$variant ); $m->goto_create_ticket( $queue ); $m->form_name('TicketCreate'); if ( $variant->{'Encrypt'} ) { ok $m->value('Encrypt', 2), "encrypt tick box is checked"; } else { ok !$m->value('Encrypt', 2), "encrypt tick box is unchecked"; } if ( $variant->{'Sign'} ) { ok $m->value('Sign', 2), "sign tick box is checked"; } else { ok !$m->value('Sign', 2), "sign tick box is unchecked"; } } # to avoid encryption/signing during create set_queue_crypt_options($queue); my $ticket = RT::Ticket->new( RT->SystemUser ); my ($id) = $ticket->Create( Subject => 'test', Queue => $queue->id, Requestor => 'rt-test@example.com', ); ok $id, 'ticket created'; foreach my $variant ( @variants ) { set_queue_crypt_options( $queue => %$variant ); $m->get( $m->rt_base_url . "/Ticket/Update.html?Action=Respond&id=$id" ); $m->form_name('TicketUpdate'); if ( $variant->{'Encrypt'} ) { ok $m->value('Encrypt', 2), "encrypt tick box is checked"; } else { ok !$m->value('Encrypt', 2), "encrypt tick box is unchecked"; } if ( $variant->{'Sign'} ) { ok $m->value('Sign', 2), "sign tick box is checked"; } else { ok !$m->value('Sign', 2), "sign tick box is unchecked"; } } } rt-5.0.1/t/web/search_tabs.t000644 000765 000024 00000004610 14005011336 016465 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 21; my ($baseurl, $agent) = RT::Test->started_ok; my $ticket = RT::Ticket->new(RT->SystemUser); for ( 1 .. 3 ) { $ticket->Create( Subject => 'Ticket ' . $_, Queue => 'General', Owner => 'root', Requestor => 'clownman@localhost', ); } ok $agent->login('root', 'password'), 'logged in as root'; # [issues.bestpractical.com #16841] { $agent->get_ok('/Search/Build.html'); $agent->form_name('BuildQuery'); $agent->field('idOp', '='); $agent->field('ValueOfid', '1'); $agent->submit('AddClause'); $agent->form_name('BuildQuery'); $agent->field('idOp', '='); $agent->field('ValueOfid', '2'); $agent->field('AndOr', 'OR'); $agent->submit('AddClause'); $agent->follow_link_ok({id => 'page-results'}); $agent->title_is('Found 2 tickets'); # } # [issues.bestpractical.com #17237] { $agent->follow_link_ok({text => 'New Search'}); $agent->title_is('Query Builder'); $agent->form_name('BuildQuery'); $agent->field('idOp', '='); $agent->field('ValueOfid', '1'); $agent->submit('AddClause'); $agent->form_name('BuildQuery'); $agent->field('idOp', '='); $agent->field('ValueOfid', '2'); $agent->field('AndOr', 'OR'); $agent->click_button(name => 'DoSearch'); $agent->title_is('Found 2 tickets'); $agent->follow_link_ok({id => 'page-results'}); $agent->title_is('Found 2 tickets'); # } $agent->follow_link_ok({text => 'Chart'}); $agent->text_contains('id = 1 OR id = 2'); $agent->form_name('SaveSearch'); $agent->field('SavedSearchDescription' => 'this is my saved chart'); $agent->click_button(name => 'SavedSearchSave'); # Confirm that we saved the chart and that it's the "current chart" $agent->text_contains('Chart this is my saved chart saved.'); $agent->form_name('SaveSearch'); is($agent->value('SavedSearchDescription'), 'this is my saved chart'); $agent->follow_link_ok({text => 'Edit Search'}); $agent->form_name('BuildQuery'); $agent->field('idOp', '='); $agent->field('ValueOfid', '3'); $agent->field('AndOr', 'OR'); $agent->click_button(name => 'DoSearch'); $agent->title_is('Found 3 tickets'); $agent->follow_link_ok({text => 'Chart'}); $agent->text_contains('id = 1 OR id = 2 OR id = 3'); # The interesting bit: confirm that the chart we saved is still the # "current chart" after roundtripping through search builder $agent->form_name('SaveSearch'); is($agent->value('SavedSearchDescription'), 'this is my saved chart'); rt-5.0.1/t/web/login.t000644 000765 000024 00000007627 14005011336 015332 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test; my ( $baseurl, $m ) = RT::Test->started_ok; my $ticket = RT::Test->create_ticket( Subject => 'ticket_foo', Queue => 'General', ); my ( $user, $pass ) = ( 'root', 'password' ); diag "normal login"; { $m->get($baseurl); $m->title_is('Login'); is( $m->uri, $baseurl, "right url" ); $m->content_lacks('autocomplete="off"'); $m->submit_form( form_id => 'login', fields => { user => $user, pass => 'wrong pass', } ); $m->content_contains( "Your username or password is incorrect", 'login error message' ); $m->warning_like( qr/FAILED LOGIN for root/, "got failed login warning" ); $m->submit_form( form_id => 'login', fields => { user => $user, pass => $pass, } ); $m->title_is( 'RT at a glance', 'logged in' ); $m->follow_link_ok( { text => 'Logout' }, 'follow logout' ); $m->title_is( 'Logout', 'logout' ); } diag "tangent login"; { $m->get( $baseurl . '/Ticket/Display.html?id=1' ); $m->title_is('Login'); $m->submit_form( form_id => 'login', fields => { user => $user, pass => $pass, } ); like( $m->uri, qr{/Ticket/Display\.html}, 'normal ticket page' ); $m->follow_link_ok( { text => 'Logout' }, 'follow logout' ); } diag "mobile login with not mobile client"; { $m->get( $baseurl . '/m' ); is( $m->uri, $baseurl . '/m', "right url" ); $m->content_contains( "/m/index.html?NotMobile=1", 'mobile login' ); $m->submit_form( form_id => 'login', fields => { user => $user, pass => 'wrong pass', } ); $m->content_contains( "Your username or password is incorrect", 'login error message' ); $m->warning_like( qr/FAILED LOGIN for root/, "got failed login warning" ); $m->submit_form( form_id => 'login', fields => { user => $user, pass => $pass, } ); like( $m->uri, qr{\Q$baseurl/m\E}, "mobile url" ); $m->follow_link_ok( { text => 'Logout' }, 'follow logout' ); $m->content_contains( "/m/index.html?NotMobile=1", 'back to mobile login page' ); $m->content_lacks( 'Logout', 'really logout' ); } diag "mobile normal login, mobile off"; { # default browser in android 2.3.6 $m->agent( "Mozilla/5.0 (Linux; U; Android 2.3.6; en-us; Nexus One Build/GRK39F) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1" ); $m->get($baseurl); is( $m->uri, $baseurl, "right url" ); $m->content_lacks( "/m/index.html?NotMobile=1", 'normal UI login' ); } RT::Test->stop_server; RT->Config->Set(ShowMobileSite => 1); ( $baseurl, $m ) = RT::Test->started_ok; diag "mobile normal login"; { # default browser in android 2.3.6 $m->agent( "Mozilla/5.0 (Linux; U; Android 2.3.6; en-us; Nexus One Build/GRK39F) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1" ); $m->get($baseurl); is( $m->uri, $baseurl, "right url" ); $m->content_contains( "/m/index.html?NotMobile=1", 'mobile login' ); $m->submit_form( form_id => 'login', fields => { user => $user, pass => $pass, } ); is( $m->uri, $baseurl . '/m/', "mobile url" ); $m->follow_link_ok( { text => 'Logout' }, 'follow logout' ); $m->content_contains( "/m/index.html?NotMobile=1", 'back to mobile login page' ); $m->content_lacks( 'Logout', 'really logout' ); } diag "mobile tangent login"; { $m->get( $baseurl . '/Ticket/Display.html?id=1' ); $m->content_contains( "/m/index.html?NotMobile=1", 'mobile login' ); $m->submit_form( form_id => 'login', fields => { user => $user, pass => $pass, } ); like( $m->uri, qr{/m/ticket/show}, 'mobile ticket page' ); } rt-5.0.1/t/web/search_ical.t000644 000765 000024 00000014661 14005011336 016453 0ustar00sunnavystaff000000 000000 use strict; use warnings; use Data::ICal; use RT::Test tests => undef; my $start_obj = RT::Date->new( RT->SystemUser ); $start_obj->SetToNow; my $start = $start_obj->iCal( Time => 1); my $due_obj = RT::Date->new( RT->SystemUser ); $due_obj->SetToNow; $due_obj->AddDays(2); my $due = $due_obj->iCal( Time => 1); diag 'Test iCal with date only'; { my ($baseurl, $agent) = RT::Test->started_ok; my $ticket = RT::Ticket->new(RT->SystemUser); for ( 1 .. 5 ) { $ticket->Create( Subject => 'Ticket ' . $_, Queue => 'General', Owner => 'root', Requestor => 'ical@localhost', Starts => $start_obj->ISO, Due => $due_obj->ISO, ); } ok $agent->login('root', 'password'), 'logged in as root'; $agent->get_ok('/Search/Build.html'); $agent->form_name('BuildQuery'); $agent->field('idOp', '>'); $agent->field('ValueOfid', '0'); $agent->submit('DoSearch'); $agent->follow_link_ok({id => 'page-results'}); for ( 1 .. 5 ) { $agent->content_contains('Ticket ' . $_); } $agent->follow_link_ok( { text => 'iCal' } ); is( $agent->content_type, 'text/calendar', 'content type is text/calendar' ); for ( 1 .. 5 ) { $agent->content_like(qr/URL\:$baseurl\/Ticket\/Display\.html\?id=$_/); } my $ical = Data::ICal->new(data => $agent->content); my @entries = $ical->entries; my $ical_count = @{$entries[0]}; is( $ical_count, 10, "Got $ical_count ical entries"); my $prop_ref = $entries[0]->[0]->properties; my $start_as_root = RT::Date->new( RT::CurrentUser->new( 'root' ) ); $start_as_root->Unix( $start_obj->Unix ); my $start = $start_as_root->ISO( Time => 0, Timezone => 'user' ); $start =~ s/-//g; is($prop_ref->{'dtstart'}->[0]->value, $start, "Got start date: $start"); like( $prop_ref->{'dtstart'}->[0]->as_string, qr/VALUE=DATE\:/, 'Got DATE value'); $prop_ref = $entries[0]->[1]->properties; my $due_as_root = RT::Date->new( RT::CurrentUser->new( 'root' ) ); $due_as_root->Unix( $due_obj->Unix ); my $due = $due_as_root->ISO( Time => 0, Timezone => 'user' ); $due =~ s/-//g; is($prop_ref->{'dtend'}->[0]->value, $due, "Got due date: $due"); like( $prop_ref->{'dtend'}->[0]->as_string, qr/VALUE=DATE\:/, 'Got DATE value'); } RT::Test->stop_server; diag 'Test iCal with date and time with config option'; { RT->Config->Set(TimeInICal =>1); my ($baseurl, $agent) = RT::Test->started_ok; ok $agent->login('root', 'password'), 'logged in as root'; $agent->get_ok('/Search/Build.html'); $agent->form_name('BuildQuery'); $agent->field('idOp', '>'); $agent->field('ValueOfid', '0'); $agent->submit('DoSearch'); $agent->follow_link_ok({id => 'page-results'}); for ( 1 .. 5 ) { $agent->content_contains('Ticket ' . $_); } my $link = $agent->find_link( text => 'iCal' ); # use $link later $agent->get_ok($link->url); is( $agent->content_type, 'text/calendar', 'content type is text/calendar' ); for ( 1 .. 5 ) { $agent->content_like(qr/URL\:$baseurl\/Ticket\/Display\.html\?id=$_/); } my $ical = Data::ICal->new(data => $agent->content); my @entries = $ical->entries; my $ical_count = @{$entries[0]}; is( $ical_count, 10, "Got $ical_count ical entries"); my $prop_ref = $entries[0]->[0]->properties; $start =~ s/-//g; is($prop_ref->{'dtstart'}->[0]->value, $start, "Got start date with time: $start"); like( $prop_ref->{'dtstart'}->[0]->as_string, qr/VALUE=DATE-TIME\:/, 'Got DATE-TIME value'); $prop_ref = $entries[0]->[1]->properties; $due =~ s/-//g; is($prop_ref->{'dtend'}->[0]->value, $due, "Got due date with time: $due"); like( $prop_ref->{'dtend'}->[0]->as_string, qr/VALUE=DATE-TIME\:/, 'Got DATE-TIME value'); } RT::Test->stop_server; diag 'Test iCal with date and time using query param'; { RT->Config->Set(TimeInICal =>0); my ($baseurl, $agent) = RT::Test->started_ok; ok $agent->login('root', 'password'), 'logged in as root'; $agent->get_ok('/Search/Build.html'); $agent->form_name('BuildQuery'); $agent->field('idOp', '>'); $agent->field('ValueOfid', '0'); $agent->submit('DoSearch'); $agent->follow_link_ok({id => 'page-results'}); for ( 1 .. 5 ) { $agent->content_contains('Ticket ' . $_); } my $link = $agent->find_link( text => 'iCal' ); $agent->get_ok($link->url . '?Time=1'); is( $agent->content_type, 'text/calendar', 'content type is text/calendar' ); for ( 1 .. 5 ) { $agent->content_like(qr/URL\:$baseurl\/Ticket\/Display\.html\?id=$_/); } my $ical = Data::ICal->new(data => $agent->content); my @entries = $ical->entries; my $ical_count = @{$entries[0]}; is( $ical_count, 10, "Got $ical_count ical entries"); my $prop_ref = $entries[0]->[0]->properties; $start =~ s/-//g; is($prop_ref->{'dtstart'}->[0]->value, $start, "Got start date with time: $start"); like( $prop_ref->{'dtstart'}->[0]->as_string, qr/VALUE=DATE-TIME\:/, 'Got DATE-TIME value'); $prop_ref = $entries[0]->[1]->properties; $due =~ s/-//g; is($prop_ref->{'dtend'}->[0]->value, $due, "Got due date with time: $due"); like( $prop_ref->{'dtend'}->[0]->as_string, qr/VALUE=DATE-TIME\:/, 'Got DATE-TIME value'); diag 'Test iCal with date and time in single events'; my $url = $link->url . '?SingleEvent=1&Time=1'; $agent->get_ok($url); is( $agent->content_type, 'text/calendar', 'content type is text/calendar' ); for ( 1 .. 5 ) { $agent->content_like(qr/URL\:$baseurl\/Ticket\/Display\.html\?id=$_/); } $ical = Data::ICal->new(data => $agent->content); @entries = $ical->entries; $ical_count = @{$entries[0]}; # Only 5 entries in single event mode is( $ical_count, 5, "Got $ical_count ical entries"); $prop_ref = $entries[0]->[0]->properties; $start =~ s/-//g; is($prop_ref->{'dtstart'}->[0]->value, $start, "Got start date with time: $start"); like( $prop_ref->{'dtstart'}->[0]->as_string, qr/VALUE=DATE-TIME\:/, 'Got DATE-TIME value'); $prop_ref = $entries[0]->[1]->properties; $due =~ s/-//g; is($prop_ref->{'dtend'}->[0]->value, $due, "Got due date with time: $due"); like( $prop_ref->{'dtend'}->[0]->as_string, qr/VALUE=DATE-TIME\:/, 'Got DATE-TIME value'); } done_testing; rt-5.0.1/t/web/rest-non-ascii-subject.t000644 000765 000024 00000002421 14005011336 020475 0ustar00sunnavystaff000000 000000 # Test ticket creation with REST using non ascii subject use strict; use warnings; use RT::Test tests => 9; my $subject = Encode::decode('latin1', "Sujet accentu\x{e9}"); my $text = Encode::decode('latin1', "Contenu accentu\x{e9}"); my ($baseurl, $m) = RT::Test->started_ok; my $queue = RT::Test->load_or_create_queue(Name => 'General'); ok($queue->Id, "loaded the General queue"); my $content = "id: ticket/new Queue: General Requestor: root Subject: $subject Cc: AdminCc: Owner: Status: new Priority: InitialPriority: FinalPriority: TimeEstimated: Starts: 2009-03-10 16:14:55 Due: 2009-03-10 16:14:55 Text: $text"; $m->post("$baseurl/REST/1.0/ticket/new", [ user => 'root', pass => 'password', content => Encode::encode( "UTF-8", $content), ], Content_Type => 'form-data' ); my ($id) = $m->content =~ /Ticket (\d+) created/; ok($id, "got ticket #$id"); my $ticket = RT::Ticket->new(RT->SystemUser); $ticket->Load($id); is($ticket->Id, $id, "loaded the REST-created ticket"); is($ticket->Subject, $subject, "ticket subject successfully set"); my $attach = $ticket->Transactions->First->Attachments->First; is($attach->Subject, $subject, "attachement subject successfully set"); is($attach->GetHeader('Subject'), $subject, "attachement header subject successfully set"); rt-5.0.1/t/web/rest-search-queue.t000644 000765 000024 00000004734 14005011336 017560 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my $queue_foo = RT::Test->load_or_create_queue( Name => 'Foo' ); my $queue_bar = RT::Test->load_or_create_queue( Name => 'Bar' ); my $queue_baz = RT::Test->load_or_create_queue( Name => 'Baz' ); $queue_baz->SetDisabled(1); my ( $baseurl, $m ) = RT::Test->started_ok; ok( $m->login, 'logged in' ); search_queues_ok( { query => 'id = 1' }, ['1: General'], 'search id = 1' ); search_queues_ok( { query => 'Name = General', format => 's', fields => 'id,name,description' }, [ "id\tName\tDescription", "1\tGeneral\tThe default queue" ], 'search by name with customized fields' ); search_queues_ok( { query => 'id > 10' }, ['No matching results.'], 'no matching results' ); search_queues_ok( { query => 'foo = 3' }, ['Invalid field specification: foo'], 'invalid field' ); search_queues_ok( { query => 'id foo 3' }, ['Invalid operator specification: foo'], 'invalid op' ); search_queues_ok( { query => '', orderby => 'id' }, [ '1: General', $queue_foo->id . ': Foo', $queue_bar->id . ': Bar', ], 'order by id' ); search_queues_ok( { query => '', orderby => 'name' }, [ $queue_bar->id . ': Bar', $queue_foo->id . ': Foo', '1: General', ], 'order by name' ); search_queues_ok( { query => '', orderby => '+name' }, [ $queue_bar->id . ': Bar', $queue_foo->id . ': Foo', '1: General', ], 'order by +name' ); search_queues_ok( { query => '', orderby => '-name' }, [ '1: General', $queue_foo->id . ': Foo', $queue_bar->id . ': Bar', ], 'order by -name' ); search_queues_ok( { query => 'Disabled = 0', orderby => 'id' }, [ '1: General', $queue_foo->id . ': Foo', $queue_bar->id . ': Bar', ], 'enabled queues' ); search_queues_ok( { query => 'Disabled = 1', orderby => 'id' }, [ $queue_baz->id . ': Baz', ], 'disabled queues' ); search_queues_ok( { query => 'Disabled = 2', orderby => 'id' }, [ '2: ___Approvals', ], 'special Approvals queue' ); sub search_queues_ok { local $Test::Builder::Level = $Test::Builder::Level + 1; my $query = shift; my $expected = shift; my $name = shift || 'search queues'; my $uri = URI->new("$baseurl/REST/1.0/search/queue"); $uri->query_form(%$query); $m->get_ok($uri); my @lines = split /\n/, $m->content; shift @lines; # header shift @lines; # empty line is_deeply( \@lines, $expected, $name ); } done_testing; rt-5.0.1/t/web/psgi-wrap.t000644 000765 000024 00000000513 14005011336 016116 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef, plugins => [qw(RT::Extension::PSGIWrap)]; my ($base, $m) = RT::Test->started_ok; $m->login; ok(my $res = $m->get("/")); is($res->code, 200, 'Successful request to /'); ok($res->header('X-RT-PSGIWrap'), 'X-RT-PSGIWrap header set from the plugin'); done_testing(); rt-5.0.1/t/web/cf_parse.t000644 000765 000024 00000004555 14005011336 016001 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; use RT::Interface::Web; #my ($baseurl,$m) = RT::Test->started_ok; # ok $m->login, 'logged in'; use Data::Dumper; diag "ParseObjectCustomFieldArgs"; { my %test1; my %test2; my %test3; my @values = qw( a b c d e f ); my @ObjectCustomFields = qw( Object-RT::Wicket-44-CustomField:Grouping-123-Snarf Object-RT::Wicket-45-CustomField:Grouping-456-Frobnicate Object-RT::Ticket-46-CustomField-789-Shizzle ); my @BulkCustomFields = qw( Bulk-Add-CustomField:Grouping-123-Snarf Bulk-Delete-CustomField:Grouping-456-Frobnicate Bulk-Add-CustomField-789-Shizzle ); # structure returned my $test1Values = { 'RT::Ticket' => { '46' => { '789' => { '' => { 'Shizzle' => 'c' } } } }, 'RT::Wicket' => { '45' => { '456' => { 'Grouping' => { 'Frobnicate' => 'b' } } }, '44' => { '123' => { 'Grouping' => { 'Snarf' => 'a' } } } } }; my $test2Values = { '' => { '0' => { '123' => { 'Grouping' => { 'Snarf' => 'd' } }, '789' => { '' => { 'Shizzle' => 'f' } }, '456' => { 'Grouping' => { 'Frobnicate' => 'e' } } } } }; # assemble the union of the two prior test sets my $test3Values = { %$test2Values, %$test1Values }; @test1{@ObjectCustomFields} = @values[0..2]; @test2{@BulkCustomFields} = @values[3 .. $#values ]; @test3{@ObjectCustomFields,@BulkCustomFields} = @values; # parse Object w/o IncludeBulkUpdate my $ref1 = HTML::Mason::Commands::_ParseObjectCustomFieldArgs( \%test1 ); is_deeply $ref1, $test1Values, 'Object CustomField parsing'; # parse Bulk w/o IncludeBulkUpdate my $ref2 = HTML::Mason::Commands::_ParseObjectCustomFieldArgs( \%test2 ); is_deeply $ref2, {}, 'ObjectCustomField paring with no Object- fields'; # parse only Bulk Fields w/ IncludeBulkupdate $ref2 = HTML::Mason::Commands::_ParseObjectCustomFieldArgs( \%test2, IncludeBulkUpdate => 1 ); is_deeply $ref2, $test2Values, 'Bulk CustomField parsing'; # include both Object and Bulk CustomField args my $ref3 = HTML::Mason::Commands::_ParseObjectCustomFieldArgs( \%test3, IncludeBulkUpdate => 1 ); is_deeply $ref3, $test3Values, 'Object and Bulk CustomField parsing'; } done_testing; rt-5.0.1/t/web/ticket_seen.t000644 000765 000024 00000005623 14005011336 016511 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test nodata => 1, tests => undef, config => 'Set($ShowUnreadMessageNotifications, 1);'; my $queue = RT::Test->load_or_create_queue( Name => 'Regression' ); ok $queue && $queue->id, 'loaded or created queue'; my $user_a = RT::Test->load_or_create_user( Name => 'user_a', Password => 'password', ); ok $user_a && $user_a->id, 'loaded or created user'; my $user_b = RT::Test->load_or_create_user( Name => 'user_b', Password => 'password', ); ok $user_b && $user_b->id, 'loaded or created user'; ok( RT::Test->set_rights( { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket OwnTicket ModifyTicket)] }, { Principal => $user_b, Right => [qw(SeeQueue ShowTicket ReplyToTicket)] }, ), 'set rights'); RT::Test->started_ok; my $agent_a = RT::Test::Web->new; ok $agent_a->login('user_a', 'password'), 'logged in as user A'; my $agent_b = RT::Test::Web->new; ok $agent_b->login('user_b', 'password'), 'logged in as user B'; diag "create a ticket for testing"; my $tid; { my $ticket = RT::Ticket->new( $user_a ); my ($txn, $msg); ($tid, $txn, $msg) = $ticket->Create( Queue => $queue->id, Owner => $user_a->id, Subject => 'test', ); ok $tid, 'created a ticket #'. $tid or diag "error: $msg"; is $ticket->Owner, $user_a->id, 'correct owner'; } diag "user B adds a message, we check that user A see notification and can clear it"; { my $ticket = RT::Ticket->new( $user_b ); $ticket->Load( $tid ); ok $ticket->id, 'loaded the ticket'; my ($status, $msg) = $ticket->Correspond( Content => 'bla-bla' ); ok $status, 'added reply' or diag "error: $msg"; my $txns = $ticket->Transactions; $txns->Limit( FIELD => 'Type', VALUE => "Correspond", ); my $txn = $txns->Last; my $reply_id = $txn->id; ok( $reply_id, 'got correspond txn id' ); $agent_a->goto_ticket($tid); $agent_a->content_contains('bla-bla', 'the message on the page'); $agent_a->content_contains( 'unread message', 'we have not seen something' ); $agent_a->follow_link_ok( { text => 'Jump to Unread' }, 'try to jump to first unread message' ); like( $agent_a->base, qr/#txn-$reply_id$/, 'contains anchor' ); $agent_a->follow_link_ok( { text => 'Jump & Mark as Seen' }, 'try to jump to first unread message' ); like( $agent_a->base, qr/#txn-$reply_id$/, 'contains anchor' ); $agent_a->content_contains( 'Marked all messages as seen', 'see success message' ); like( $agent_a->base, qr/#txn-$reply_id$/, 'contains anchor' ); $agent_a->content_contains( 'Marked all messages as seen', 'see success message' ); $agent_a->goto_ticket($tid); $agent_a->content_lacks( 'unread message', 'we have seen everything, so no messages' ); } done_testing; rt-5.0.1/t/web/ticket_modify_all.t000644 000765 000024 00000007176 14005011336 017703 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => undef; my $ticket = RT::Test->create_ticket( Subject => 'test bulk update', Queue => 1, ); RT->Config->Set(AutocompleteOwners => 1); my ( $url, $m ) = RT::Test->started_ok; ok( $m->login, 'logged in' ); $m->get_ok( $url . "/Ticket/ModifyAll.html?id=" . $ticket->id ); $m->submit_form( form_number => 3, fields => { 'UpdateContent' => 'this is update content' }, button => 'SubmitTicket', ); $m->content_contains("Comments added", 'updated ticket'); $m->content_lacks("this is update content", 'textarea is clear'); $m->get_ok($url . '/Ticket/Display.html?id=' . $ticket->id ); $m->content_contains("this is update content", 'updated content in display page'); $m->get_ok($url . '/Ticket/ModifyAll.html?id=' . $ticket->id); $m->form_name('TicketModifyAll'); $m->field(Owner => 'root'); $m->field(TimeWorked => 120); $m->click('SubmitTicket'); $m->text_contains('Owner changed from Nobody to root'); $m->text_contains('Worked 2 hours (120 minutes)'); $m->form_name('TicketModifyAll'); is($m->value('Owner'), 'root', 'owner was successfully changed to root'); is($m->value('TimeWorked'), 120, 'logged 2 hours'); $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_like(qr/Starts:\s*\Q(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_like(qr/Started:\s*\Q(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_like(qr/Last Contact:\s*\Q(Thu Jan 01 00:00:00 2015)/, 'told date successfully updated'); for my $unset ("0", "-", " ") { $m->form_name('TicketModifyAll'); $m->field('Due_Date' => "2016-01-01 00:00:00"); $m->click('SubmitTicket'); $m->text_like(qr/Due:\s*\Q(Fri Jan 01 00:00:00 2016)/, 'due date successfully updated'); $m->form_name('TicketModifyAll'); $m->field('Due_Date' => $unset); $m->click('SubmitTicket'); $m->text_like(qr/Due:\s*\Q(Not set)/, "due date successfully cleared with '$unset'"); if ( $unset eq '-' ) { my @warnings = $m->get_warnings; chomp @warnings; is_deeply( [ @warnings ], [ ( q{Couldn't parse date '-' by Time::ParseDate}, q{Couldn't parse date '-' by DateTime::Format::Natural} ) ] ); } } $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 root as 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( "root is already Requestor", 'no duplicate watchers', ); $m->get( $url . '/Ticket/ModifyAll.html?id=' . $ticket->id ); $m->form_name('TicketModifyAll'); $m->click('SubmitTicket'); $m->content_lacks("That is already the current value", 'no spurious messages'); $m->form_name('TicketModifyAll'); $m->field(TimeWorked => 0); $m->click('SubmitTicket'); $m->text_contains('Adjusted time worked by -120 minutes'); $m->form_name('TicketModifyAll'); is($m->value('TimeWorked'), "", 'no time worked'); done_testing; rt-5.0.1/t/web/basic_auth.t000644 000765 000024 00000001754 14005011336 016317 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT; use RT::Test tests => 9; RT->Config->Set( DevelMode => 0 ); RT->Config->Set( WebRemoteUserAuth => 1 ); my ( $url, $m ) = RT::Test->started_ok( basic_auth => 1 ); # This tests the plack middleware, not RT $m->get($url); is($m->status, 401, "Initial request with no creds gets 401"); # This tests the plack middleware, not RT $m->get($url, $m->auth_header( root => "wrong" )); is($m->status, 401, "Request with wrong creds gets 401"); $m->get($url, $m->auth_header( root => "password" )); is($m->status, 200, "Request with right creds gets 200"); $m->content_like( qr{\Qroot\E}i, "Has user on the page" ); $m->content_unlike(qr/Logout/i, "Has no logout button, no WebFallbackToRTLogin"); # Again, testing the plack middleware $m->get($url); is($m->status, 401, "Subsequent requests without credentials aren't still logged in"); # Put the credentials back for the warnings check at the end $m->auth( root => "password" ); rt-5.0.1/t/web/rights1.t000644 000765 000024 00000010623 14005011336 015571 0ustar00sunnavystaff000000 000000 use strict; use warnings; use HTTP::Cookies; use RT::Test nodata => 1, tests => 31; my ($baseurl, $agent) = RT::Test->started_ok; # Create a user with basically no rights, to start. my $user_obj = RT::User->new(RT->SystemUser); my ($ret, $msg) = $user_obj->LoadOrCreateByEmail('customer-'.$$.'@example.com'); ok($ret, 'ACL test user creation'); $user_obj->SetName('customer-'.$$); $user_obj->SetPrivileged(1); ($ret, $msg) = $user_obj->SetPassword('customer'); ok($ret, "ACL test password set. $msg"); # Now test the web interface, making sure objects come and go as # required. my $cookie_jar = HTTP::Cookies->new; # give the agent a place to stash the cookies $agent->cookie_jar($cookie_jar); # get the top page $agent->login( $user_obj->Name, 'customer'); # Test for absence of Configure and Preferences tabs. ok(!$agent->find_link( url => "$RT::WebPath/Admin/", text => 'Admin'), "No admin tab" ); ok(!$agent->find_link( url => "$RT::WebPath/User/Prefs.html", text => 'Preferences'), "No prefs pane" ); # Now test for their presence, one at a time. Sleep for a bit after # ACL changes, thanks to the 10s ACL cache. my ($grantid,$grantmsg) =$user_obj->PrincipalObj->GrantRight(Right => 'ShowConfigTab', Object => RT->System); ok($grantid,$grantmsg); $agent->reload; $agent->content_contains('Logout', "Reloaded page successfully"); ok($agent->find_link( url => "$RT::WebPath/Admin/", text => 'Admin'), "Found admin tab" ); my ($revokeid,$revokemsg) =$user_obj->PrincipalObj->RevokeRight(Right => 'ShowConfigTab'); ok ($revokeid,$revokemsg); ($grantid,$grantmsg) =$user_obj->PrincipalObj->GrantRight(Right => 'ModifySelf'); ok ($grantid,$grantmsg); $agent->reload(); $agent->content_contains('Logout', "Reloaded page successfully"); ok($agent->find_link( id => 'preferences-settings' ), "Found prefs pane" ); ($revokeid,$revokemsg) = $user_obj->PrincipalObj->RevokeRight(Right => 'ModifySelf'); ok ($revokeid,$revokemsg); # Good. Now load the search page and test Load/Save Search. $agent->follow_link( url => "$RT::WebPath/Search/Build.html", text => 'Tickets'); is($agent->status, 200, "Fetched search builder page"); $agent->content_lacks("Load saved search", "No search loading box"); $agent->content_lacks("Saved searches", "No saved searches box"); ($grantid,$grantmsg) = $user_obj->PrincipalObj->GrantRight(Right => 'LoadSavedSearch'); ok($grantid,$grantmsg); $agent->reload(); $agent->content_contains("Load saved search", "Search loading box exists"); $agent->content_unlike(qr/input\s+type=['"]submit['"][^>]+name=['"]SavedSearchSave['"]/i, "Still no saved searches box"); ($grantid,$grantmsg) =$user_obj->PrincipalObj->GrantRight(Right => 'CreateSavedSearch'); ok ($grantid,$grantmsg); $agent->reload(); $agent->content_contains("Load saved search", "Search loading box still exists"); $agent->content_like(qr/input\s+type=['"]submit['"][^>]+name=['"]SavedSearchSave['"]/i, "Saved searches box exists"); # Create a group, and a queue, so we can test limited user visibility # via SelectOwner. my $queue_obj = RT::Queue->new(RT->SystemUser); ($ret, $msg) = $queue_obj->Create(Name => 'CustomerQueue-'.$$, Description => 'queue for SelectOwner testing'); ok($ret, "SelectOwner test queue creation. $msg"); my $group_obj = RT::Group->new(RT->SystemUser); ($ret, $msg) = $group_obj->CreateUserDefinedGroup(Name => 'CustomerGroup-'.$$, Description => 'group for SelectOwner testing'); ok($ret, "SelectOwner test group creation. $msg"); # Add our customer to the customer group, and give it queue rights. ($ret, $msg) = $group_obj->AddMember($user_obj->PrincipalObj->Id()); ok($ret, "Added customer to its group. $msg"); ($grantid,$grantmsg) =$group_obj->PrincipalObj->GrantRight(Right => 'OwnTicket', Object => $queue_obj); ok($grantid,$grantmsg); ($grantid,$grantmsg) =$group_obj->PrincipalObj->GrantRight(Right => 'SeeQueue', Object => $queue_obj); ok ($grantid,$grantmsg); # Now. When we look at the search page we should be able to see # ourself in the list of possible owners. $agent->reload(); ok($agent->form_name('BuildQuery'), "Yep, form is still there"); my $input = $agent->current_form->find_input('ValueOfActor'); ok(grep(/customer-$$/, $input->value_names()), "Found self in the actor listing"); rt-5.0.1/t/web/template.t000644 000765 000024 00000005365 14005011336 016032 0ustar00sunnavystaff000000 000000 use strict; use warnings; use RT::Test tests => 22; my $user_a = RT::Test->load_or_create_user( Name => 'user_a', Password => 'password', ); ok $user_a && $user_a->id, 'loaded or created user'; my ($baseurl, $m) = RT::Test->started_ok; ok( RT::Test->set_rights( { Principal => $user_a, Right => [qw(ShowConfigTab ShowTemplate ModifyTemplate)] }, ), 'set rights'); ok $m->login('user_a', 'password'), 'logged in as user A'; # get to the templates screen $m->follow_link( text => 'Admin' ); $m->title_is(q{RT Administration}, 'admin screen'); $m->follow_link( text => 'Global' ); $m->title_is(q{Admin/Global configuration}, 'global admin'); $m->follow_link( text => 'Templates' ); $m->title_is(q{Modify templates which apply to all queues}, 'global templates'); $m->follow_link( text => 'Resolved' ); # template name $m->title_is(q{Modify template Resolved}, 'modifying the Resolved template'); # now try changing Type back and forth $m->form_name('ModifyTemplate'); is($m->value('Type'), 'Perl'); $m->field(Type => 'Simple'); $m->submit; $m->title_is(q{Modify template Resolved}, 'modifying the Resolved template'); $m->form_name('ModifyTemplate'); is($m->value('Type'), 'Simple', 'updated type to simple'); $m->field(Type => 'Perl'); $m->submit; $m->title_is(q{Modify template Resolved}, 'modifying the Resolved template'); $m->form_name('ModifyTemplate'); is($m->value('Type'), 'Simple', 'need the ExecuteCode right to update Type to Perl'); $m->content_contains('Permission Denied'); ok( RT::Test->add_rights( { Principal => $user_a, Right => [qw(ExecuteCode)] }, ), 'add ExecuteCode rights'); $m->field(Type => 'Perl'); $m->submit; $m->title_is(q{Modify template Resolved}, 'modifying the Resolved template'); $m->form_name('ModifyTemplate'); is($m->value('Type'), 'Perl', 'now that we have ExecuteCode we can update Type to Perl'); { # 21152: Each time you save a Template a newline is chopped off the front $m->form_name('ModifyTemplate'); my $content; TODO: { local $TODO = "WWW::Mechanize doesn't strip newline following