[rt] Initial import.
corsepiu
corsepiu at fedoraproject.org
Fri Jan 23 14:37:13 UTC 2015
commit 7e612d4fac640e841ac07885dc7f796766ffbd0b
Author: Ralf Corsépius <corsepiu at fedoraproject.org>
Date: Fri Jan 23 15:37:18 2015 +0100
Initial import.
.gitignore | 1 +
0001-Add-Fedora-configuration.patch | 34 +
0002-Broken-test-dependencies.patch | 33 +
...-usr-bin-perl-instead-of-usr-bin-env-perl.patch | 165 +
0004-Remove-fixperms-font-install.patch | 37 +
0005-Fix-permissions.patch | 91 +
0006-Remove-configure-time-generated-files.patch |15540 ++++++++++++++++++++
0007-Adjust-path-to-html-autohandler.patch | 24 +
README.fedora.in | 68 +
README.tests | 12 +
rt.conf.in | 20 +
rt.logrotate.in | 5 +
rt.spec | 624 +
sources | 1 +
14 files changed, 16655 insertions(+), 0 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index e69de29..ae1a497 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1 @@
+/rt-4.0.22.tar.gz
diff --git a/0001-Add-Fedora-configuration.patch b/0001-Add-Fedora-configuration.patch
new file mode 100644
index 0000000..508d953
--- /dev/null
+++ b/0001-Add-Fedora-configuration.patch
@@ -0,0 +1,34 @@
+From abe19fcddf23c61c4ba5f4bc627cf70ea74ac21f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ralf=20Cors=C3=A9pius?= <corsepiu at fedoraproject.org>
+Date: Sat, 19 Jan 2013 08:28:35 +0100
+Subject: [PATCH 1/6] Add Fedora configuration.
+
+---
+ etc/RT_SiteConfig.pm | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/etc/RT_SiteConfig.pm b/etc/RT_SiteConfig.pm
+index 4d2ef35..0c09743 100755
+--- a/etc/RT_SiteConfig.pm
++++ b/etc/RT_SiteConfig.pm
+@@ -10,13 +10,14 @@
+ #
+ # The converse is also true, if this file isn't valid perl, you're
+ # going to run into trouble. To check your SiteConfig file, use
+-# this comamnd:
++# this command:
+ #
+-# perl -c /path/to/your/etc/RT_SiteConfig.pm
++# perl -c /etc/rt/RT_SiteConfig.pm
+ #
+ # You must restart your webserver after making changes to this file.
+
+-Set( $rtname, 'example.com');
++Set($rtname, 'example.com');
++Set($WebPath, '/rt');
+
+ # You must install Plugins on your own, this is only an example
+ # of the correct syntax to use when activating them.
+--
+1.9.3
+
diff --git a/0002-Broken-test-dependencies.patch b/0002-Broken-test-dependencies.patch
new file mode 100644
index 0000000..bb42158
--- /dev/null
+++ b/0002-Broken-test-dependencies.patch
@@ -0,0 +1,33 @@
+From 0a16f6f7f45757d02ddba10b6959a34773ed1cd7 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ralf=20Cors=C3=A9pius?= <corsepiu at fedoraproject.org>
+Date: Sat, 19 Jan 2013 08:28:35 +0100
+Subject: [PATCH 2/6] Broken test-dependencies.
+
+---
+ sbin/rt-test-dependencies.in | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/sbin/rt-test-dependencies.in b/sbin/rt-test-dependencies.in
+index 679b41d..5a10411 100644
+--- a/sbin/rt-test-dependencies.in
++++ b/sbin/rt-test-dependencies.in
+@@ -189,7 +189,7 @@ sub set_dep {
+ }
+
+ $deps{'CORE'} = [ text_to_hash( << '.') ];
+-Class::Accessor 0.34
++Class::Accessor::Fast 0.34
+ DateTime 0.44
+ DateTime::Locale 0.40
+ Digest::base
+@@ -289,7 +289,6 @@ $deps{'DEV'} = [ text_to_hash( << '.') ];
+ Email::Abstract
+ Test::Email
+ HTML::Form
+-HTML::TokeParser
+ WWW::Mechanize 1.52
+ Test::WWW::Mechanize 1.30
+ Module::Refresh 0.03
+--
+1.9.3
+
diff --git a/0003-Use-usr-bin-perl-instead-of-usr-bin-env-perl.patch b/0003-Use-usr-bin-perl-instead-of-usr-bin-env-perl.patch
new file mode 100644
index 0000000..ac80dd5
--- /dev/null
+++ b/0003-Use-usr-bin-perl-instead-of-usr-bin-env-perl.patch
@@ -0,0 +1,165 @@
+From b8a30c1a215fd3eaa0f1bc607e71a194c67cbeff Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ralf=20Cors=C3=A9pius?= <corsepiu at fedoraproject.org>
+Date: Sat, 19 Jan 2013 08:47:25 +0100
+Subject: [PATCH 3/6] Use /usr/bin/perl instead of /usr/bin/env perl.
+
+---
+ devel/tools/change-loc-msgstr | 2 +-
+ devel/tools/extract-message-catalog | 2 +-
+ devel/tools/factory | 2 +-
+ devel/tools/license_tag | 2 +-
+ devel/tools/merge-rosetta.pl | 2 +-
+ devel/tools/rt-apache | 2 +-
+ devel/tools/rt-attributes-editor | 2 +-
+ devel/tools/rt-static-docs | 2 +-
+ devel/tools/tweak-template-locstring | 2 +-
+ etc/upgrade/shrink_cgm_table.pl | 2 +-
+ etc/upgrade/shrink_transactions_table.pl | 2 +-
+ etc/upgrade/upgrade-mysql-schema.pl | 2 +-
+ sbin/rt-message-catalog | 2 +-
+ t/mail/fake-sendmail | 2 +-
+ 14 files changed, 14 insertions(+), 14 deletions(-)
+
+diff --git a/devel/tools/change-loc-msgstr b/devel/tools/change-loc-msgstr
+index bd1892a..ffeb5e1 100755
+--- a/devel/tools/change-loc-msgstr
++++ b/devel/tools/change-loc-msgstr
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env perl
++#!/usr/bin/perl
+ # BEGIN BPS TAGGED BLOCK {{{
+ #
+ # COPYRIGHT:
+diff --git a/devel/tools/extract-message-catalog b/devel/tools/extract-message-catalog
+index 0afec0b..5c654e0 100755
+--- a/devel/tools/extract-message-catalog
++++ b/devel/tools/extract-message-catalog
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env perl
++#!/usr/bin/perl
+ # BEGIN BPS TAGGED BLOCK {{{
+ #
+ # COPYRIGHT:
+diff --git a/devel/tools/factory b/devel/tools/factory
+index 5d05d08..38d1fe8 100755
+--- a/devel/tools/factory
++++ b/devel/tools/factory
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env perl
++#!/usr/bin/perl
+ # BEGIN BPS TAGGED BLOCK {{{
+ #
+ # COPYRIGHT:
+diff --git a/devel/tools/license_tag b/devel/tools/license_tag
+index 4cf0917..0d1e5f3 100755
+--- a/devel/tools/license_tag
++++ b/devel/tools/license_tag
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env perl
++#!/usr/bin/perl
+
+
+ # BEGIN BPS TAGGED BLOCK {{{
+diff --git a/devel/tools/merge-rosetta.pl b/devel/tools/merge-rosetta.pl
+index ec587ad..cfd1651 100755
+--- a/devel/tools/merge-rosetta.pl
++++ b/devel/tools/merge-rosetta.pl
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env perl
++#!/usr/bin/perl
+ # BEGIN BPS TAGGED BLOCK {{{
+ #
+ # COPYRIGHT:
+diff --git a/devel/tools/rt-apache b/devel/tools/rt-apache
+index 22df8ee..7a39dcf 100755
+--- a/devel/tools/rt-apache
++++ b/devel/tools/rt-apache
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env perl
++#!/usr/bin/perl
+
+ # BEGIN BPS TAGGED BLOCK {{{
+ #
+diff --git a/devel/tools/rt-attributes-editor b/devel/tools/rt-attributes-editor
+index 92998a4..117f167 100755
+--- a/devel/tools/rt-attributes-editor
++++ b/devel/tools/rt-attributes-editor
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env perl
++#!/usr/bin/perl
+ # BEGIN BPS TAGGED BLOCK {{{
+ #
+ # COPYRIGHT:
+diff --git a/devel/tools/rt-static-docs b/devel/tools/rt-static-docs
+index 2c77711..99335dc 100755
+--- a/devel/tools/rt-static-docs
++++ b/devel/tools/rt-static-docs
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env perl
++#!/usr/bin/perl
+ # BEGIN BPS TAGGED BLOCK {{{
+ #
+ # COPYRIGHT:
+diff --git a/devel/tools/tweak-template-locstring b/devel/tools/tweak-template-locstring
+index ca44d39..b31570b 100755
+--- a/devel/tools/tweak-template-locstring
++++ b/devel/tools/tweak-template-locstring
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env perl
++#!/usr/bin/perl
+ # BEGIN BPS TAGGED BLOCK {{{
+ #
+ # COPYRIGHT:
+diff --git a/etc/upgrade/shrink_cgm_table.pl b/etc/upgrade/shrink_cgm_table.pl
+index fcfe8c5..585b210 100644
+--- a/etc/upgrade/shrink_cgm_table.pl
++++ b/etc/upgrade/shrink_cgm_table.pl
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env perl
++#!/usr/bin/perl
+ # BEGIN BPS TAGGED BLOCK {{{
+ #
+ # COPYRIGHT:
+diff --git a/etc/upgrade/shrink_transactions_table.pl b/etc/upgrade/shrink_transactions_table.pl
+index 66bdcf5..3e82203 100644
+--- a/etc/upgrade/shrink_transactions_table.pl
++++ b/etc/upgrade/shrink_transactions_table.pl
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env perl
++#!/usr/bin/perl
+ # BEGIN BPS TAGGED BLOCK {{{
+ #
+ # COPYRIGHT:
+diff --git a/etc/upgrade/upgrade-mysql-schema.pl b/etc/upgrade/upgrade-mysql-schema.pl
+index 8d6615d..204676c 100755
+--- a/etc/upgrade/upgrade-mysql-schema.pl
++++ b/etc/upgrade/upgrade-mysql-schema.pl
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env perl
++#!/usr/bin/perl
+ # BEGIN BPS TAGGED BLOCK {{{
+ #
+ # COPYRIGHT:
+diff --git a/sbin/rt-message-catalog b/sbin/rt-message-catalog
+index b428369..6effdfd 100755
+--- a/sbin/rt-message-catalog
++++ b/sbin/rt-message-catalog
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env perl
++#!/usr/bin/perl
+ # BEGIN BPS TAGGED BLOCK {{{
+ #
+ # COPYRIGHT:
+diff --git a/t/mail/fake-sendmail b/t/mail/fake-sendmail
+index 44c2377..ab3b912 100755
+--- a/t/mail/fake-sendmail
++++ b/t/mail/fake-sendmail
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env perl
++#!/usr/bin/perl
+
+ # captures command line arguments so you can validate
+ # what is being generated in sendmailpipe
+--
+1.9.3
+
diff --git a/0004-Remove-fixperms-font-install.patch b/0004-Remove-fixperms-font-install.patch
new file mode 100644
index 0000000..6bb5aa1
--- /dev/null
+++ b/0004-Remove-fixperms-font-install.patch
@@ -0,0 +1,37 @@
+From fff3f340cbf67886b292c77ae8774fe5bd8c4f68 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ralf=20Cors=C3=A9pius?= <corsepiu at fedoraproject.org>
+Date: Sat, 19 Jan 2013 08:28:35 +0100
+Subject: [PATCH 4/6] Remove fixperms, font-install
+
+---
+ Makefile.in | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/Makefile.in b/Makefile.in
+index d8c7799..54faca8 100755
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -260,7 +260,7 @@ upgrade-instruct:
+ @echo " make upgrade-database"
+
+
+-upgrade: testdeps config-install dirs files-install fixperms upgrade-instruct
++upgrade: testdeps config-install dirs files-install upgrade-instruct
+
+ my_with_web_handlers= $(shell $(PERL) -e 'print join " ", map "--with-$$_", grep defined && length, split /,/, "$(WEB_HANDLER)"')
+ testdeps:
+@@ -346,9 +346,9 @@ dirs:
+ $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_PLUGIN_PATH)
+ $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_LEXICON_PATH)
+
+-install: testdeps config-install dirs files-install fixperms instruct
++install: testdeps config-install dirs files-install instruct
+
+-files-install: libs-install etc-install config-install bin-install sbin-install html-install doc-install font-install po-install
++files-install: libs-install etc-install config-install bin-install sbin-install html-install doc-install po-install
+
+ config-install:
+ @COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0755 -o $(BIN_OWNER) -g $(RTGROUP) -d $(DESTDIR)$(CONFIG_FILE_PATH)
+--
+1.9.3
+
diff --git a/0005-Fix-permissions.patch b/0005-Fix-permissions.patch
new file mode 100644
index 0000000..c76bf8f
--- /dev/null
+++ b/0005-Fix-permissions.patch
@@ -0,0 +1,91 @@
+From 6fd8bcac095f502bb3c196f86a8d2a85715b5f50 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ralf=20Cors=C3=A9pius?= <corsepiu at fedoraproject.org>
+Date: Tue, 18 Mar 2014 10:00:00 +0100
+Subject: [PATCH 5/6] Fix permissions.
+
+---
+ Makefile.in | 0
+ aclocal.m4 | 0
+ config.layout | 0
+ configure.ac | 0
+ docs/schema.dot | 0
+ etc/RT_Config.pm.in | 0
+ etc/RT_SiteConfig.pm | 0
+ etc/acl.Oracle | 0
+ etc/acl.Pg | 0
+ etc/acl.mysql | 0
+ etc/initialdata | 0
+ etc/schema.Oracle | 0
+ etc/schema.Pg | 0
+ etc/schema.SQLite | 0
+ etc/schema.mysql | 0
+ etc/upgrade/vulnerable-passwords.in | 0
+ 16 files changed, 0 insertions(+), 0 deletions(-)
+ mode change 100755 => 100644 Makefile.in
+ mode change 100755 => 100644 aclocal.m4
+ mode change 100755 => 100644 config.layout
+ mode change 100755 => 100644 configure.ac
+ mode change 100755 => 100644 docs/schema.dot
+ mode change 100755 => 100644 etc/RT_Config.pm.in
+ mode change 100755 => 100644 etc/RT_SiteConfig.pm
+ mode change 100755 => 100644 etc/acl.Oracle
+ mode change 100755 => 100644 etc/acl.Pg
+ mode change 100755 => 100644 etc/acl.mysql
+ mode change 100755 => 100644 etc/initialdata
+ mode change 100755 => 100644 etc/schema.Oracle
+ mode change 100755 => 100644 etc/schema.Pg
+ mode change 100755 => 100644 etc/schema.SQLite
+ mode change 100755 => 100644 etc/schema.mysql
+ mode change 100755 => 100644 etc/upgrade/vulnerable-passwords.in
+
+diff --git a/Makefile.in b/Makefile.in
+old mode 100755
+new mode 100644
+diff --git a/aclocal.m4 b/aclocal.m4
+old mode 100755
+new mode 100644
+diff --git a/config.layout b/config.layout
+old mode 100755
+new mode 100644
+diff --git a/configure.ac b/configure.ac
+old mode 100755
+new mode 100644
+diff --git a/docs/schema.dot b/docs/schema.dot
+old mode 100755
+new mode 100644
+diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
+old mode 100755
+new mode 100644
+diff --git a/etc/RT_SiteConfig.pm b/etc/RT_SiteConfig.pm
+old mode 100755
+new mode 100644
+diff --git a/etc/acl.Oracle b/etc/acl.Oracle
+old mode 100755
+new mode 100644
+diff --git a/etc/acl.Pg b/etc/acl.Pg
+old mode 100755
+new mode 100644
+diff --git a/etc/acl.mysql b/etc/acl.mysql
+old mode 100755
+new mode 100644
+diff --git a/etc/initialdata b/etc/initialdata
+old mode 100755
+new mode 100644
+diff --git a/etc/schema.Oracle b/etc/schema.Oracle
+old mode 100755
+new mode 100644
+diff --git a/etc/schema.Pg b/etc/schema.Pg
+old mode 100755
+new mode 100644
+diff --git a/etc/schema.SQLite b/etc/schema.SQLite
+old mode 100755
+new mode 100644
+diff --git a/etc/schema.mysql b/etc/schema.mysql
+old mode 100755
+new mode 100644
+diff --git a/etc/upgrade/vulnerable-passwords.in b/etc/upgrade/vulnerable-passwords.in
+old mode 100755
+new mode 100644
+--
+1.9.3
+
diff --git a/0006-Remove-configure-time-generated-files.patch b/0006-Remove-configure-time-generated-files.patch
new file mode 100644
index 0000000..8968578
--- /dev/null
+++ b/0006-Remove-configure-time-generated-files.patch
@@ -0,0 +1,15540 @@
+From c4a3d1f76d592c7b4c52b198feff6a3b99f58197 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ralf=20Cors=C3=A9pius?= <corsepiu at fedoraproject.org>
+Date: Fri, 16 May 2014 11:21:51 +0200
+Subject: [PATCH 6/6] Remove configure time generated files.
+
+---
+ Makefile | 569 -----
+ bin/rt | 2648 ----------------------
+ bin/rt-crontool | 468 ----
+ bin/rt-mailgate | 526 -----
+ etc/RT_Config.pm | 2772 ------------------------
+ etc/upgrade/3.8-ical-extension | 96 -
+ etc/upgrade/4.0-customfield-checkbox-extension | 86 -
+ etc/upgrade/generate-rtaddressregexp | 109 -
+ etc/upgrade/split-out-cf-categories | 171 --
+ etc/upgrade/upgrade-articles | 264 ---
+ etc/upgrade/vulnerable-passwords | 142 --
+ lib/RT/Generated.pm | 81 -
+ sbin/rt-attributes-viewer | 122 --
+ sbin/rt-clean-sessions | 190 --
+ sbin/rt-dump-metadata | 357 ---
+ sbin/rt-email-dashboards | 173 --
+ sbin/rt-email-digest | 380 ----
+ sbin/rt-email-group-admin | 527 -----
+ sbin/rt-fulltext-indexer | 479 ----
+ sbin/rt-preferences-viewer | 149 --
+ sbin/rt-server | 285 ---
+ sbin/rt-server.fcgi | 285 ---
+ sbin/rt-session-viewer | 121 --
+ sbin/rt-setup-database | 607 ------
+ sbin/rt-setup-fulltext-index | 720 ------
+ sbin/rt-shredder | 325 ---
+ sbin/rt-test-dependencies | 694 ------
+ sbin/rt-validate-aliases | 343 ---
+ sbin/rt-validator | 1182 ----------
+ sbin/standalone_httpd | 285 ---
+ t/data/configs/apache2.2+fastcgi.conf | 50 -
+ t/data/configs/apache2.2+mod_perl.conf | 67 -
+ 32 files changed, 15273 deletions(-)
+ delete mode 100644 Makefile
+ delete mode 100755 bin/rt
+ delete mode 100755 bin/rt-crontool
+ delete mode 100755 bin/rt-mailgate
+ delete mode 100644 etc/RT_Config.pm
+ delete mode 100755 etc/upgrade/3.8-ical-extension
+ delete mode 100755 etc/upgrade/4.0-customfield-checkbox-extension
+ delete mode 100755 etc/upgrade/generate-rtaddressregexp
+ delete mode 100755 etc/upgrade/split-out-cf-categories
+ delete mode 100755 etc/upgrade/upgrade-articles
+ delete mode 100755 etc/upgrade/vulnerable-passwords
+ delete mode 100644 lib/RT/Generated.pm
+ delete mode 100755 sbin/rt-attributes-viewer
+ delete mode 100755 sbin/rt-clean-sessions
+ delete mode 100755 sbin/rt-dump-metadata
+ delete mode 100755 sbin/rt-email-dashboards
+ delete mode 100755 sbin/rt-email-digest
+ delete mode 100755 sbin/rt-email-group-admin
+ delete mode 100755 sbin/rt-fulltext-indexer
+ delete mode 100755 sbin/rt-preferences-viewer
+ delete mode 100755 sbin/rt-server
+ delete mode 100755 sbin/rt-server.fcgi
+ delete mode 100755 sbin/rt-session-viewer
+ delete mode 100755 sbin/rt-setup-database
+ delete mode 100755 sbin/rt-setup-fulltext-index
+ delete mode 100755 sbin/rt-shredder
+ delete mode 100755 sbin/rt-test-dependencies
+ delete mode 100755 sbin/rt-validate-aliases
+ delete mode 100755 sbin/rt-validator
+ delete mode 100755 sbin/standalone_httpd
+ delete mode 100644 t/data/configs/apache2.2+fastcgi.conf
+ delete mode 100644 t/data/configs/apache2.2+mod_perl.conf
+
+diff --git a/Makefile b/Makefile
+deleted file mode 100644
+index dd3209c..0000000
+--- a/Makefile
++++ /dev/null
+@@ -1,569 +0,0 @@
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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/rt4/etc
+-CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_Config.pm
+-SITE_CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_SiteConfig.pm
+-
+-
+-RT_VERSION_MAJOR = 4
+-RT_VERSION_MINOR = 0
+-RT_VERSION_PATCH = 22
+-
+-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-data
+-
+-
+-# 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-data
+-WEB_GROUP = www-data
+-
+-
+-APACHECTL = /usr/sbin/apachectl
+-
+-
+-# 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/rt4
+-RT_ETC_PATH = /opt/rt4/etc
+-RT_BIN_PATH = /opt/rt4/bin
+-RT_SBIN_PATH = /opt/rt4/sbin
+-RT_LIB_PATH = /opt/rt4/lib
+-RT_MAN_PATH = /opt/rt4/man
+-RT_VAR_PATH = /opt/rt4/var
+-RT_DOC_PATH = /opt/rt4/docs
+-RT_FONT_PATH = /opt/rt4/share/fonts
+-RT_LEXICON_PATH = /opt/rt4/share/po
+-RT_LOCAL_PATH = /opt/rt4/local
+-LOCAL_PLUGIN_PATH = /opt/rt4/local/plugins
+-LOCAL_ETC_PATH = /opt/rt4/local/etc
+-LOCAL_LIB_PATH = /opt/rt4/local/lib
+-LOCAL_LEXICON_PATH = /opt/rt4/local/po
+-MASON_HTML_PATH = /opt/rt4/share/html
+-MASON_LOCAL_HTML_PATH = /opt/rt4/local/html
+-MASON_DATA_PATH = /opt/rt4/var/mason_data
+-MASON_SESSION_PATH = /opt/rt4/var/session_data
+-RT_LOG_PATH = /opt/rt4/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-clean-sessions \
+- rt-dump-metadata \
+- rt-email-dashboards \
+- rt-email-digest \
+- rt-email-group-admin \
+- rt-fulltext-indexer \
+- rt-preferences-viewer \
+- 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 \
+- 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 = rt4
+-DB_RT_USER = rt_user
+-DB_RT_PASS = rt_pass
+-
+-
+-
+-TEST_FILES = 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
+-
+-my_with_web_handlers= $(shell $(PERL) -e 'print join " ", map "--with-$$_", grep defined && length, split /,/, "$(WEB_HANDLER)"')
+-testdeps:
+- $(PERL) ./sbin/rt-test-dependencies --verbose --with-$(DB_TYPE) $(my_with_web_handlers)
+-
+-depends: fixdeps
+-
+-fixdeps:
+- $(PERL) ./sbin/rt-test-dependencies --verbose --install --with-$(DB_TYPE) $(my_with_web_handlers)
+-
+-#}}}
+-
+-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.
+- #
+- # Note that we use the deprecated (by GNU/POSIX find) -perm +0NNN syntax
+- # instead of -perm /0NNN since BSD find doesn't support the latter.
+- ( 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)
+- chown -R $(LIBS_OWNER) $(DESTDIR)$(MASON_HTML_PATH) \
+- $(DESTDIR)$(MASON_LOCAL_HTML_PATH) \
+- $(DESTDIR)$(RT_LEXICON_PATH) \
+- $(DESTDIR)$(LOCAL_LEXICON_PATH)
+- chgrp -R $(LIBS_GROUP) $(DESTDIR)$(MASON_HTML_PATH) \
+- $(DESTDIR)$(MASON_LOCAL_HTML_PATH) \
+- $(DESTDIR)$(RT_LEXICON_PATH) \
+- $(DESTDIR)$(LOCAL_LEXICON_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 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: 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
+-
+-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
+-
+-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
+-
+-
+-
+-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)
+- 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
+-
+-factory: initialize-database
+- cd lib; $(PERL) ../devel/tools/factory $(DB_DATABASE) RT
+-
+-start-httpd:
+- $(PERL) sbin/standalone_httpd &
+-
+-start-server:
+- $(PERL) sbin/rt-server &
+-
+-apachectl:
+- $(APACHECTL) stop
+- sleep 10
+- $(APACHECTL) start
+- sleep 5
+-
+-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 --no-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 --no-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
+-
+-JSMIN_URL = http://download.bestpractical.com/mirror/jsmin-2011-01-22.c
+-JSMIN_SHA = 8a6b3b980a52c028eb73aee4a82ebe060c1ee854
+-
+-jsmin: jsmin-checkcc jsmin-fetch jsmin-verify jsmin-confirm jsmin-build jsmin-install
+- @echo ""
+- @echo "To configure RT to use jsmin, add the following line to $(DESTDIR)$(RT_ETC_PATH)/RT_SiteConfig.pm:"
+- @echo ""
+- @echo " Set(\$$JSMinPath, '$(DESTDIR)$(RT_BIN_PATH)/jsmin');"
+- @echo ""
+-
+-jsmin-checkcc:
+- @[ -n "$(CC)" ] || (echo "You don't appear to have a C compiler, please set CC and re-run configure" && exit 1)
+-
+-jsmin-confirm:
+- @echo "jsmin is distributed under a slightly unusual license and can't be shipped"
+- @echo "with RT. Before configuring RT to use jsmin, please read jsmin's license"
+- @echo "below:"
+- @echo ""
+- @$(PERL) -pe 'print && exit if /^\*\// or /^#include/' jsmin.c
+- @echo ""
+- @echo "Press Enter to accept the license, or Ctrl-C to stop now."
+- @$(PERL) -e '<STDIN>'
+-
+-jsmin-fetch:
+- @echo ""
+- @echo "Downloading jsmin.c from $(JSMIN_URL)"
+- @echo ""
+- @$(PERL) -MLWP::Simple -e 'exit not is_success(getstore("$(JSMIN_URL)", "jsmin.c"))' \
+- || (echo "Failed to download $(JSMIN_URL)" && exit 1)
+-
+-jsmin-verify:
+- @$(PERL) -MDigest::SHA -e \
+- 'exit not Digest::SHA->new(1)->addfile("jsmin.c")->hexdigest eq "$(JSMIN_SHA)"' \
+- || (echo "Verification of jsmin.c failed! Possible man in the middle?" && exit 1)
+-
+-jsmin-build:
+- $(CC) -o jsmin jsmin.c
+-
+-jsmin-install:
+- $(INSTALL) -o $(BIN_OWNER) -g $(RTGROUP) -m 0755 "jsmin" "$(DESTDIR)$(RT_BIN_PATH)/"
+diff --git a/bin/rt b/bin/rt
+deleted file mode 100755
+index dffd19f..0000000
+--- a/bin/rt
++++ /dev/null
+@@ -1,2648 +0,0 @@
+-#!/usr/bin/perl -w
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license to use, copy, create derivative
+-# works based on those contributions, and sublicense and distribute
+-# those contributions and any derivatives thereof.
+-#
+-# END BPS TAGGED BLOCK }}}
+-# Designed and implemented for Best Practical Solutions, LLC by
+-# Abhijit Menon-Sen <ams at wiw.org>
+-
+-use strict;
+-use warnings;
+-
+-if ( $ARGV[0] && $ARGV[0] =~ /^(?:--help|-h)$/ ) {
+- require Pod::Usage;
+- print Pod::Usage::pod2usage( { verbose => 2 } );
+- exit;
+-}
+-
+-# This program is intentionally written to have as few non-core module
+-# dependencies as possible. It should stay that way.
+-
+-use Cwd;
+-use LWP;
+-use Text::ParseWords;
+-use HTTP::Request::Common;
+-use HTTP::Headers;
+-use Term::ReadLine;
+-use Time::Local; # used in prettyshow
+-use File::Temp;
+-
+-# strong (GSSAPI based) authentication is supported if the server does provide
+-# it and the perl modules GSSAPI and LWP::Authen::Negotiate are installed
+-# it can be suppressed by setting externalauth=0 (default is undef)
+-eval { require GSSAPI };
+-my $no_strong_auth = 'missing perl module GSSAPI';
+-if ( ! $@ ) {
+- eval {require LWP::Authen::Negotiate};
+- $no_strong_auth = $@ ? 'missing perl module LWP::Authen::Negotiate' : 0;
+-}
+-
+-# We derive configuration information from hardwired defaults, dotfiles,
+-# and the RT* environment variables (in increasing order of precedence).
+-# Session information is stored in ~/.rt_sessions.
+-
+-my $VERSION = 0.02;
+-my $HOME = eval{(getpwuid($<))[7]}
+- || $ENV{HOME} || $ENV{LOGDIR} || $ENV{HOMEPATH}
+- || ".";
+-my %config = (
+- (
+- debug => 0,
+- user => eval{(getpwuid($<))[0]} || $ENV{USER} || $ENV{USERNAME},
+- passwd => undef,
+- server => 'http://localhost/',
+- query => "Status!='resolved' and Status!='rejected'",
+- orderby => 'id',
+- queue => undef,
+-# to protect against unlimited searches a better choice would be
+-# queue => 'Unknown_Queue',
+-# setting externalauth => undef will try GSSAPI auth if the corresponding perl
+-# modules are installed, externalauth => 0 is the backward compatible choice
+- externalauth => 0,
+- ),
+- config_from_file($ENV{RTCONFIG} || ".rtrc"),
+- config_from_env()
+-);
+-my $session = Session->new("$HOME/.rt_sessions");
+-my $REST = "$config{server}/REST/1.0";
+-$no_strong_auth = 'switched off by externalauth=0'
+- if defined $config{externalauth};
+-
+-
+-my $prompt = 'rt> ';
+-
+-sub whine;
+-sub DEBUG { warn @_ if $config{debug} >= shift }
+-
+-# These regexes are used by command handlers to parse arguments.
+-# (XXX: Ask Autrijus how i18n changes these definitions.)
+-
+-my $name = '[\w.-]+';
+-my $CF_name = '[^,]+?';
+-my $field = '(?i:[a-z][a-z0-9_-]*|C(?:ustom)?F(?:ield)?-'.$CF_name.'|CF\.\{'.$CF_name.'\})';
+-my $label = '[^,\\/]+';
+-my $labels = "(?:$label,)*$label";
+-my $idlist = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+';
+-
+-# Our command line looks like this:
+-#
+-# rt <action> [options] [arguments]
+-#
+-# We'll parse just enough of it to decide upon an action to perform, and
+-# leave the rest to per-action handlers to interpret appropriately.
+-
+-my %handlers = (
+-# handler => [ ...aliases... ],
+- version => ["version", "ver"],
+- shell => ["shell"],
+- logout => ["logout"],
+- help => ["help", "man"],
+- show => ["show", "cat"],
+- edit => ["create", "edit", "new", "ed"],
+- list => ["search", "list", "ls"],
+- comment => ["comment", "correspond"],
+- link => ["link", "ln"],
+- merge => ["merge"],
+- grant => ["grant", "revoke"],
+- take => ["take", "steal", "untake"],
+- quit => ["quit", "exit"],
+- setcommand => ["del", "delete", "give", "res", "resolve",
+- "subject"],
+-);
+-
+-my %actions;
+-foreach my $fn (keys %handlers) {
+- foreach my $alias (@{ $handlers{$fn} }) {
+- $actions{$alias} = \&{"$fn"};
+- }
+-}
+-
+-# Once we find and call an appropriate handler, we're done.
+-
+-sub handler {
+- my $action;
+-
+- push @ARGV, 'shell' if (!@ARGV); # default to shell mode
+- shift @ARGV if ($ARGV[0] eq 'rt'); # ignore a leading 'rt'
+- if (@ARGV && exists $actions{$ARGV[0]}) {
+- $action = shift @ARGV;
+- return $actions{$action}->($action);
+- }
+- else {
+- print STDERR "rt: Unknown command '@ARGV'.\n";
+- print STDERR "rt: For help, run 'rt help'.\n";
+- return 1;
+- }
+-}
+-
+-exit handler();
+-
+-# Handler functions.
+-# ------------------
+-#
+-# The following subs are handlers for each entry in %actions.
+-
+-sub shell {
+- $|=1;
+- my $term = Term::ReadLine->new('RT CLI');
+- while ( defined ($_ = $term->readline($prompt)) ) {
+- next if /^#/ || /^\s*$/;
+-
+- @ARGV = shellwords($_);
+- handler();
+- }
+-}
+-
+-sub version {
+- print "rt $VERSION\n";
+- return 0;
+-}
+-
+-sub logout {
+- submit("$REST/logout") if defined $session->cookie;
+- return 0;
+-}
+-
+-sub quit {
+- logout();
+- exit;
+-}
+-
+-my %help;
+-sub help {
+- my ($action, $type, $rv) = @_;
+- $rv = defined $rv ? $rv : 0;
+- my $key;
+-
+- # What help topics do we know about?
+- if (!%help) {
+- local $/ = undef;
+- foreach my $item (@{ Form::parse(<DATA>) }) {
+- my $title = $item->[2]{Title};
+- my @titles = ref $title eq 'ARRAY' ? @$title : $title;
+-
+- foreach $title (grep $_, @titles) {
+- $help{$title} = $item->[2]{Text};
+- }
+- }
+- }
+-
+- # What does the user want help with?
+- undef $action if ($action && $actions{$action} eq \&help);
+- unless ($action || $type) {
+- # If we don't know, we'll look for clues in @ARGV.
+- foreach (@ARGV) {
+- if (exists $help{$_}) { $key = $_; last; }
+- }
+- unless ($key) {
+- # Tolerate possibly plural words.
+- foreach (@ARGV) {
+- if ($_ =~ s/s$// && exists $help{$_}) { $key = $_; last; }
+- }
+- }
+- }
+-
+- if ($type && $action) {
+- $key = "$type.$action";
+- }
+- $key ||= $type || $action || "introduction";
+-
+- # Find a suitable topic to display.
+- while (!exists $help{$key}) {
+- if ($type && $action) {
+- if ($key eq "$type.$action") { $key = $action; }
+- elsif ($key eq $action) { $key = $type; }
+- else { $key = "introduction"; }
+- }
+- else {
+- $key = "introduction";
+- }
+- }
+-
+- print STDERR $help{$key}, "\n\n";
+- return $rv;
+-}
+-
+-# Displays a list of objects that match some specified condition.
+-
+-sub list {
+- my ($q, $type, %data);
+- my $orderby = $config{orderby};
+-
+- if ($config{orderby}) {
+- $data{orderby} = $config{orderby};
+- }
+- my $bad = 0;
+- my $rawprint = 0;
+- my $reverse_sort = 0;
+- my $queue = $config{queue};
+-
+- while (@ARGV) {
+- $_ = shift @ARGV;
+-
+- if (/^-t$/) {
+- $bad = 1, last unless defined($type = get_type_argument());
+- }
+- elsif (/^-S$/) {
+- $bad = 1, last unless get_var_argument(\%data);
+- }
+- elsif (/^-o$/) {
+- $data{'orderby'} = shift @ARGV;
+- }
+- elsif (/^-([isl])$/) {
+- $data{format} = $1;
+- $rawprint = 1;
+- }
+- elsif (/^-q$/) {
+- $queue = shift @ARGV;
+- }
+- elsif (/^-r$/) {
+- $reverse_sort = 1;
+- }
+- elsif (/^-f$/) {
+- if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) {
+- whine "No valid field list in '-f $ARGV[0]'.";
+- $bad = 1; last;
+- }
+- $data{fields} = shift @ARGV;
+- $data{format} = 's' if ! $data{format};
+- $rawprint = 1;
+- }
+- elsif (!defined $q && !/^-/) {
+- $q = $_;
+- }
+- else {
+- my $datum = /^-/ ? "option" : "argument";
+- whine "Unrecognised $datum '$_'.";
+- $bad = 1; last;
+- }
+- }
+- if ( ! $rawprint and ! exists $data{format} ) {
+- $data{format} = 'l';
+- $data{fields} = 'subject,status,queue,created,told,owner,requestors';
+- }
+- if ( $reverse_sort and $data{orderby} =~ /^-/ ) {
+- $data{orderby} =~ s/^-/+/;
+- } elsif ($reverse_sort) {
+- $data{orderby} =~ s/^\+?(.*)/-$1/;
+- }
+-
+- if (!defined $q) {
+- $q = $config{query};
+- }
+-
+- $q =~ s/^#//; # get rid of leading hash
+- if ($q =~ /^\d+$/) {
+- # only digits, must be an id, formulate a correct query
+- $q = "id=$q" if $q =~ /^\d+$/;
+- } else {
+- # a string only, take it as an owner or requestor (quoting done later)
+- $q = "(Owner=$q or Requestor like $q) and $config{query}"
+- if $q =~ /^[\w\-]+$/;
+- # always add a query for a specific queue or (comma separated) queues
+- $queue =~ s/,/ or Queue=/g if $queue;
+- $q .= " and (Queue=$queue)" if $queue and $q and $q !~ /Queue\s*=/i
+- and $q !~ /id\s*=/i;
+- }
+- # correctly quote strings in a query
+- $q =~ s/(=|like\s)\s*([^'\d\s]\S*)\b/$1\'$2\'/g;
+-
+- $type ||= "ticket";
+- unless ($type && defined $q) {
+- my $item = $type ? "query string" : "object type";
+- whine "No $item specified.";
+- $bad = 1;
+- }
+- #return help("list", $type) if $bad;
+- return suggest_help("list", $type, $bad) if $bad;
+-
+- print "Query:$q\n" if ! $rawprint;
+- my $r = submit("$REST/search/$type", { query => $q, %data });
+- if ( $rawprint ) {
+- print $r->content;
+- } else {
+- my $forms = Form::parse($r->content);
+- prettylist ($forms);
+- }
+- return 0;
+-}
+-
+-# Displays selected information about a single object.
+-
+-sub show {
+- my ($type, @objects, %data);
+- my $slurped = 0;
+- my $bad = 0;
+- my $rawprint = 0;
+- my $histspec;
+-
+- while (@ARGV) {
+- $_ = shift @ARGV;
+- s/^#// if /^#\d+/; # get rid of leading hash
+- if (/^-t$/) {
+- $bad = 1, last unless defined($type = get_type_argument());
+- }
+- elsif (/^-S$/) {
+- $bad = 1, last unless get_var_argument(\%data);
+- }
+- elsif (/^-([isl])$/) {
+- $data{format} = $1;
+- $rawprint = 1;
+- }
+- elsif (/^-$/ && !$slurped) {
+- chomp(my @lines = <STDIN>);
+- foreach (@lines) {
+- unless (is_object_spec($_, $type)) {
+- whine "Invalid object on STDIN: '$_'.";
+- $bad = 1; last;
+- }
+- push @objects, $_;
+- }
+- $slurped = 1;
+- }
+- elsif (/^-f$/) {
+- if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) {
+- whine "No valid field list in '-f $ARGV[0]'.";
+- $bad = 1; last;
+- }
+- $data{fields} = shift @ARGV;
+- # option f requires short raw listing format
+- $data{format} = 's';
+- $rawprint = 1;
+- }
+- elsif (/^\d+$/ and my $spc2 = is_object_spec("ticket/$_", $type)) {
+- push @objects, $spc2;
+- $histspec = is_object_spec("ticket/$_/history", $type);
+- }
+- elsif (/^\d+\// and my $spc3 = is_object_spec("ticket/$_", $type)) {
+- push @objects, $spc3;
+- $rawprint = 1 if $_ =~ /\/content$/;
+- }
+- elsif (my $spec = is_object_spec($_, $type)) {
+- push @objects, $spec;
+- $rawprint = 1 if $_ =~ /\/content$/ or $_ =~ /\/links/ or $_ !~ /^ticket/;
+- }
+- else {
+- my $datum = /^-/ ? "option" : "argument";
+- whine "Unrecognised $datum '$_'.";
+- $bad = 1; last;
+- }
+- }
+- if ( ! $rawprint ) {
+- push @objects, $histspec if $histspec;
+- $data{format} = 'l' if ! exists $data{format};
+- }
+-
+- unless (@objects) {
+- whine "No objects specified.";
+- $bad = 1;
+- }
+- #return help("show", $type) if $bad;
+- return suggest_help("show", $type, $bad) if $bad;
+-
+- my $r = submit("$REST/show", { id => \@objects, %data });
+- my $c = $r->content;
+- # if this isn't a text reply, remove the trailing newline so we
+- # don't corrupt things like tarballs when people do
+- # show ticket/id/attachments/id/content > foo.tar.gz
+- if ($r->content_type !~ /^text\//) {
+- chomp($c);
+- $rawprint = 1;
+- }
+- if ( $rawprint ) {
+- print $c;
+- } else {
+- # I do not know how to get more than one form correctly returned
+- $c =~ s!^RT/[\d\.]+ 200 Ok$!--!mg;
+- my $forms = Form::parse($c);
+- prettyshow ($forms);
+- }
+- return 0;
+-}
+-
+-# To create a new object, we ask the server for a form with the defaults
+-# filled in, allow the user to edit it, and send the form back.
+-#
+-# To edit an object, we must ask the server for a form representing that
+-# object, make changes requested by the user (either on the command line
+-# or interactively via $EDITOR), and send the form back.
+-
+-sub edit {
+- my ($action) = @_;
+- my (%data, $type, @objects);
+- my ($cl, $text, $edit, $input, $output, $content_type);
+-
+- use vars qw(%set %add %del);
+- %set = %add = %del = ();
+- my $slurped = 0;
+- my $bad = 0;
+-
+- while (@ARGV) {
+- $_ = shift @ARGV;
+- s/^#// if /^#\d+/; # get rid of leading hash
+-
+- if (/^-e$/) { $edit = 1 }
+- elsif (/^-i$/) { $input = 1 }
+- elsif (/^-o$/) { $output = 1 }
+- elsif (/^-ct$/) { $content_type = shift @ARGV }
+- elsif (/^-t$/) {
+- $bad = 1, last unless defined($type = get_type_argument());
+- }
+- elsif (/^-S$/) {
+- $bad = 1, last unless get_var_argument(\%data);
+- }
+- elsif (/^-$/ && !($slurped || $input)) {
+- chomp(my @lines = <STDIN>);
+- foreach (@lines) {
+- unless (is_object_spec($_, $type)) {
+- whine "Invalid object on STDIN: '$_'.";
+- $bad = 1; last;
+- }
+- push @objects, $_;
+- }
+- $slurped = 1;
+- }
+- elsif (/^set$/i) {
+- my $vars = 0;
+-
+- while (@ARGV && $ARGV[0] =~ /^($field)([+-]?=)(.*)$/s) {
+- my ($key, $op, $val) = ($1, $2, $3);
+- my $hash = ($op eq '=') ? \%set : ($op =~ /^\+/) ? \%add : \%del;
+-
+- vpush($hash, lc $key, $val);
+- shift @ARGV;
+- $vars++;
+- }
+- unless ($vars) {
+- whine "No variables to set.";
+- $bad = 1; last;
+- }
+- $cl = $vars;
+- }
+- elsif (/^(?:add|del)$/i) {
+- my $vars = 0;
+- my $hash = ($_ eq "add") ? \%add : \%del;
+-
+- while (@ARGV && $ARGV[0] =~ /^($field)=(.*)$/s) {
+- my ($key, $val) = ($1, $2);
+-
+- vpush($hash, lc $key, $val);
+- shift @ARGV;
+- $vars++;
+- }
+- unless ($vars) {
+- whine "No variables to set.";
+- $bad = 1; last;
+- }
+- $cl = $vars;
+- }
+- elsif (/^\d+$/ and my $spc2 = is_object_spec("ticket/$_", $type)) {
+- push @objects, $spc2;
+- }
+- elsif (my $spec = is_object_spec($_, $type)) {
+- push @objects, $spec;
+- }
+- else {
+- my $datum = /^-/ ? "option" : "argument";
+- whine "Unrecognised $datum '$_'.";
+- $bad = 1; last;
+- }
+- }
+-
+- if ($action =~ /^ed(?:it)?$/) {
+- unless (@objects) {
+- whine "No objects specified.";
+- $bad = 1;
+- }
+- }
+- else {
+- if (@objects) {
+- whine "You shouldn't specify objects as arguments to $action.";
+- $bad = 1;
+- }
+- unless ($type) {
+- whine "What type of object do you want to create?";
+- $bad = 1;
+- }
+- @objects = ("$type/new") if defined($type);
+- }
+- #return help($action, $type) if $bad;
+- return suggest_help($action, $type, $bad) if $bad;
+-
+- # We need a form to make changes to. We usually ask the server for
+- # one, but we can avoid that if we are fed one on STDIN, or if the
+- # user doesn't want to edit the form by hand, and the command line
+- # specifies only simple variable assignments. We *should* get a
+- # form if we're creating a new ticket, so that the default values
+- # get filled in properly.
+-
+- my @new_objects = grep /\/new$/, @objects;
+-
+- if ($input) {
+- local $/ = undef;
+- $text = <STDIN>;
+- }
+- elsif ($edit || %add || %del || !$cl || @new_objects) {
+- my $r = submit("$REST/show", { id => \@objects, format => 'l' });
+- $text = $r->content;
+- }
+-
+- # If any changes were specified on the command line, apply them.
+- if ($cl) {
+- if ($text) {
+- # We're updating forms from the server.
+- my $forms = Form::parse($text);
+-
+- foreach my $form (@$forms) {
+- my ($c, $o, $k, $e) = @$form;
+- my ($key, $val);
+-
+- next if ($e || !@$o);
+-
+- local %add = %add;
+- local %del = %del;
+- local %set = %set;
+-
+- # Make changes to existing fields.
+- foreach $key (@$o) {
+- if (exists $add{lc $key}) {
+- $val = delete $add{lc $key};
+- vpush($k, $key, $val);
+- $k->{$key} = vsplit($k->{$key}) if $val =~ /[,\n]/;
+- }
+- if (exists $del{lc $key}) {
+- $val = delete $del{lc $key};
+- my %val = map {$_=>1} @{ vsplit($val) };
+- $k->{$key} = vsplit($k->{$key});
+- @{$k->{$key}} = grep {!exists $val{$_}} @{$k->{$key}};
+- }
+- if (exists $set{lc $key}) {
+- $k->{$key} = delete $set{lc $key};
+- }
+- }
+-
+- # Then update the others.
+- foreach $key (keys %set) { vpush($k, $key, $set{$key}) }
+- foreach $key (keys %add) {
+- vpush($k, $key, $add{$key});
+- $k->{$key} = vsplit($k->{$key});
+- }
+- push @$o, (keys %add, keys %set);
+- }
+-
+- $text = Form::compose($forms);
+- }
+- else {
+- # We're rolling our own set of forms.
+- my @forms;
+- foreach (@objects) {
+- my ($type, $ids, $args) =
+- m{^($name)/($idlist|$labels)(?:(/.*))?$}o;
+-
+- $args ||= "";
+- foreach my $obj (expand_list($ids)) {
+- my %set = (%set, id => "$type/$obj$args");
+- push @forms, ["", [keys %set], \%set];
+- }
+- }
+- $text = Form::compose(\@forms);
+- }
+- }
+-
+- if ($output) {
+- print $text;
+- return 0;
+- }
+-
+- my @files;
+- @files = @{ vsplit($set{'attachment'}) } if exists $set{'attachment'};
+-
+- my $synerr = 0;
+-
+-EDIT:
+- # We'll let the user edit the form before sending it to the server,
+- # unless we have enough information to submit it non-interactively.
+- if ( $type && $type eq 'ticket' && $text !~ /^Content-Type:/m ) {
+- $text .= "Content-Type: $content_type\n"
+- if $content_type and $content_type ne "text/plain";
+- }
+-
+- if ($edit || (!$input && !$cl)) {
+- my ($newtext) = vi_form_while(
+- $text,
+- sub {
+- my ($text, $form) = @_;
+- return 1 unless exists $form->[2]{'Attachment'};
+-
+- foreach my $f ( @{ vsplit($form->[2]{'Attachment'}) } ) {
+- return (0, "File '$f' doesn't exist") unless -f $f;
+- }
+- @files = @{ vsplit($form->[2]{'Attachment'}) };
+- return 1;
+- },
+- );
+- return $newtext unless $newtext;
+- # We won't resubmit a bad form unless it was changed.
+- $text = ($synerr && $newtext eq $text) ? undef : $newtext;
+- }
+-
+- delete @data{ grep /^attachment_\d+$/, keys %data };
+- my $i = 1;
+- foreach my $file (@files) {
+- $data{"attachment_$i"} = bless([ $file ], "Attachment");
+- $i++;
+- }
+-
+- if ($text) {
+- my $r = submit("$REST/edit", {content => $text, %data});
+- if ($r->code == 409) {
+- # If we submitted a bad form, we'll give the user a chance
+- # to correct it and resubmit.
+- if ($edit || (!$input && !$cl)) {
+- my $content = $r->content . "\n";
+- $content =~ s/^(?!#)/# /mg;
+- $text = $content . $text;
+- $synerr = 1;
+- goto EDIT;
+- }
+- else {
+- print $r->content;
+- return 0;
+- }
+- }
+- print $r->content;
+- }
+- return 0;
+-}
+-
+-# handler for special edit commands. A valid edit command is constructed and
+-# further work is delegated to the edit handler
+-
+-sub setcommand {
+- my ($action) = @_;
+- my ($id, $bad, $what);
+- if ( @ARGV ) {
+- $_ = shift @ARGV;
+- $id = $1 if (m|^(?:ticket/)?($idlist)$|);
+- }
+- if ( ! $id ) {
+- $bad = 1;
+- whine "No ticket number specified.";
+- }
+- if ( @ARGV ) {
+- if ($action eq 'subject') {
+- my $subject = '"'.join (" ", @ARGV).'"';
+- @ARGV = ();
+- $what = "subject=$subject";
+- } elsif ($action eq 'give') {
+- my $owner = shift @ARGV;
+- $what = "owner=$owner";
+- }
+- } else {
+- if ( $action eq 'delete' or $action eq 'del' ) {
+- $what = "status=deleted";
+- } elsif ($action eq 'resolve' or $action eq 'res' ) {
+- $what = "status=resolved";
+- } elsif ($action eq 'take' ) {
+- $what = "owner=$config{user}";
+- } elsif ($action eq 'untake') {
+- $what = "owner=Nobody";
+- }
+- }
+- if (@ARGV) {
+- $bad = 1;
+- whine "Extraneous arguments for action $action: @ARGV.";
+- }
+- if ( ! $what ) {
+- $bad = 1;
+- whine "unrecognized action $action.";
+- }
+- return help("edit", undef, $bad) if $bad;
+- @ARGV = ( $id, "set", $what );
+- print "Executing: rt edit @ARGV\n";
+- return edit("edit");
+-}
+-
+-# We roll "comment" and "correspond" into the same handler.
+-
+-sub comment {
+- my ($action) = @_;
+- my (%data, $id, @files, @bcc, @cc, $msg, $content_type, $wtime, $edit);
+- my $bad = 0;
+-
+- while (@ARGV) {
+- $_ = shift @ARGV;
+-
+- if (/^-e$/) {
+- $edit = 1;
+- }
+- elsif (/^-(?:[abcmw]|ct)$/) {
+- unless (@ARGV) {
+- whine "No argument specified with $_.";
+- $bad = 1; last;
+- }
+-
+- if (/-a/) {
+- unless (-f $ARGV[0] && -r $ARGV[0]) {
+- whine "Cannot read attachment: '$ARGV[0]'.";
+- return 0;
+- }
+- push @files, shift @ARGV;
+- }
+- elsif (/-ct/) {
+- $content_type = shift @ARGV;
+- }
+- elsif (/-([bc])/) {
+- my $a = $_ eq "-b" ? \@bcc : \@cc;
+- @$a = split /\s*,\s*/, shift @ARGV;
+- }
+- elsif (/-m/) {
+- $msg = shift @ARGV;
+- if ( $msg =~ /^-$/ ) {
+- undef $msg;
+- while (<STDIN>) { $msg .= $_ }
+- }
+- }
+- elsif (/-w/) { $wtime = shift @ARGV }
+- }
+- elsif (!$id && m|^(?:ticket/)?($idlist)$|) {
+- $id = $1;
+- }
+- else {
+- my $datum = /^-/ ? "option" : "argument";
+- whine "Unrecognised $datum '$_'.";
+- $bad = 1; last;
+- }
+- }
+-
+- unless ($id) {
+- whine "No object specified.";
+- $bad = 1;
+- }
+- #return help($action, "ticket") if $bad;
+- return suggest_help($action, "ticket") if $bad;
+-
+- my $form = [
+- "",
+- [ "Ticket", "Action", "Cc", "Bcc", "Attachment", "TimeWorked", "Content-Type", "Text" ],
+- {
+- Ticket => $id,
+- Action => $action,
+- Cc => [ @cc ],
+- Bcc => [ @bcc ],
+- Attachment => [ @files ],
+- TimeWorked => $wtime || '',
+- 'Content-Type' => $content_type || 'text/plain',
+- Text => $msg || '',
+- Status => ''
+- }
+- ];
+-
+- my $text = Form::compose([ $form ]);
+-
+- if ($edit || !$msg) {
+- my ($tmp) = vi_form_while(
+- $text,
+- sub {
+- my ($text, $form) = @_;
+- foreach my $f ( @{ vsplit($form->[2]{'Attachment'}) } ) {
+- return (0, "File '$f' doesn't exist") unless -f $f;
+- }
+- @files = @{ vsplit($form->[2]{'Attachment'}) };
+- return 1;
+- },
+- );
+- return $tmp unless $tmp;
+- $text = $tmp;
+- }
+-
+- my $i = 1;
+- foreach my $file (@files) {
+- $data{"attachment_$i"} = bless([ $file ], "Attachment");
+- $i++;
+- }
+- $data{content} = $text;
+-
+- my $r = submit("$REST/ticket/$id/comment", \%data);
+- print $r->content;
+- return 0;
+-}
+-
+-# Merge one ticket into another.
+-
+-sub merge {
+- my @id;
+- my $bad = 0;
+-
+- while (@ARGV) {
+- $_ = shift @ARGV;
+- s/^#// if /^#\d+/; # get rid of leading hash
+-
+- if (/^\d+$/) {
+- push @id, $_;
+- }
+- else {
+- whine "Unrecognised argument: '$_'.";
+- $bad = 1; last;
+- }
+- }
+-
+- unless (@id == 2) {
+- my $evil = @id > 2 ? "many" : "few";
+- whine "Too $evil arguments specified.";
+- $bad = 1;
+- }
+- #return help("merge", "ticket") if $bad;
+- return suggest_help("merge", "ticket", $bad) if $bad;
+-
+- my $r = submit("$REST/ticket/$id[0]/merge/$id[1]");
+- print $r->content;
+- return 0;
+-}
+-
+-# Link one ticket to another.
+-
+-sub link {
+- my ($bad, $del, %data) = (0, 0, ());
+- my $type;
+-
+- my %ltypes = map { lc $_ => $_ } qw(DependsOn DependedOnBy RefersTo
+- ReferredToBy HasMember MemberOf);
+-
+- while (@ARGV && $ARGV[0] =~ /^-/) {
+- $_ = shift @ARGV;
+-
+- if (/^-d$/) {
+- $del = 1;
+- }
+- elsif (/^-t$/) {
+- $bad = 1, last unless defined($type = get_type_argument());
+- }
+- else {
+- whine "Unrecognised option: '$_'.";
+- $bad = 1; last;
+- }
+- }
+-
+- $type = "ticket" unless $type; # default type to tickets
+-
+- if (@ARGV == 3) {
+- my ($from, $rel, $to) = @ARGV;
+- if (($type eq "ticket") && ( ! exists $ltypes{lc $rel})) {
+- whine "Invalid link '$rel' for type $type specified.";
+- $bad = 1;
+- }
+- %data = (id => $from, rel => $rel, to => $to, del => $del);
+- }
+- else {
+- my $bad = @ARGV < 3 ? "few" : "many";
+- whine "Too $bad arguments specified.";
+- $bad = 1;
+- }
+- return suggest_help("link", $type, $bad) if $bad;
+-
+- my $r = submit("$REST/$type/link", \%data);
+- print $r->content;
+- return 0;
+-}
+-
+-# Take/steal a ticket
+-sub take {
+- my ($cmd) = @_;
+- my ($bad, %data) = (0, ());
+-
+- my $id;
+-
+- # get the ticket id
+- if (@ARGV == 1) {
+- ($id) = @ARGV;
+- unless ($id =~ /^\d+$/) {
+- whine "Invalid ticket ID $id specified.";
+- $bad = 1;
+- }
+- my $form = [
+- "",
+- [ "Ticket", "Action" ],
+- {
+- Ticket => $id,
+- Action => $cmd,
+- Status => '',
+- }
+- ];
+-
+- my $text = Form::compose([ $form ]);
+- $data{content} = $text;
+- }
+- else {
+- $bad = @ARGV < 1 ? "few" : "many";
+- whine "Too $bad arguments specified.";
+- $bad = 1;
+- }
+- return suggest_help("take", "ticket", $bad) if $bad;
+-
+- my $r = submit("$REST/ticket/$id/take", \%data);
+- print $r->content;
+- return 0;
+-}
+-
+-# Grant/revoke a user's rights.
+-
+-sub grant {
+- my ($cmd) = @_;
+-
+- whine "$cmd is unimplemented.";
+- return 1;
+-}
+-
+-# Client <-> Server communication.
+-# --------------------------------
+-#
+-# This function composes and sends an HTTP request to the RT server, and
+-# interprets the response. It takes a request URI, and optional request
+-# data (a string, or a reference to a set of key-value pairs).
+-
+-sub submit {
+- my ($uri, $content) = @_;
+- my ($req, $data);
+- my $ua = LWP::UserAgent->new(agent => "RT/3.0b", env_proxy => 1);
+- my $h = HTTP::Headers->new;
+-
+- # Did the caller specify any data to send with the request?
+- $data = [];
+- if (defined $content) {
+- unless (ref $content) {
+- # If it's just a string, make sure LWP handles it properly.
+- # (By pretending that it's a file!)
+- $content = [ content => [undef, "", Content => $content] ];
+- }
+- elsif (ref $content eq 'HASH') {
+- my @data;
+- foreach my $k (keys %$content) {
+- if (ref $content->{$k} eq 'ARRAY') {
+- foreach my $v (@{ $content->{$k} }) {
+- push @data, $k, $v;
+- }
+- }
+- else { push @data, $k, $content->{$k} }
+- }
+- $content = \@data;
+- }
+- $data = $content;
+- }
+-
+- # Should we send authentication information to start a new session?
+- my $how = $config{server} =~ /^https/ ? 'over SSL' : 'unencrypted';
+- (my $server = $config{server}) =~ s/^.*\/\/([^\/]+)\/?/$1/;
+- if ($config{externalauth}) {
+- $h->authorization_basic($config{user}, $config{passwd} || read_passwd() );
+- print " Password will be sent to $server $how\n",
+- " Press CTRL-C now if you do not want to continue\n"
+- if ! $config{passwd};
+- } elsif ( $no_strong_auth ) {
+- if (!defined $session->cookie) {
+- print " Strong encryption not available, $no_strong_auth\n",
+- " Password will be sent to $server $how\n",
+- " Press CTRL-C now if you do not want to continue\n"
+- if ! $config{passwd};
+- push @$data, ( user => $config{user} );
+- push @$data, ( pass => $config{passwd} || read_passwd() );
+- }
+- }
+-
+- # Now, we construct the request.
+- if (@$data) {
+- $req = POST($uri, $data, Content_Type => 'form-data');
+- }
+- else {
+- $req = GET($uri);
+- }
+- $session->add_cookie_header($req);
+- if ($config{externalauth}) {
+- $req->header(%$h);
+- }
+-
+- # Then we send the request and parse the response.
+- DEBUG(3, $req->as_string);
+- my $res = $ua->request($req);
+- DEBUG(3, $res->as_string);
+-
+- if ($res->is_success) {
+- # The content of the response we get from the RT server consists
+- # of an HTTP-like status line followed by optional header lines,
+- # a blank line, and arbitrary text.
+-
+- my ($head, $text) = split /\n\n/, $res->content, 2;
+- my ($status, @headers) = split /\n/, $head;
+- $text =~ s/\n*$/\n/ if ($text);
+-
+- # "RT/3.0.1 401 Credentials required"
+- if ($status !~ m#^RT/\d+(?:\S+) (\d+) ([\w\s]+)$#) {
+- warn "rt: Malformed RT response from $config{server}.\n";
+- warn "(Rerun with RTDEBUG=3 for details.)\n" if $config{debug} < 3;
+- exit -1;
+- }
+-
+- # Our caller can pretend that the server returned a custom HTTP
+- # response code and message. (Doing that directly is apparently
+- # not sufficiently portable and uncomplicated.)
+- $res->code($1);
+- $res->message($2);
+- $res->content($text);
+- $session->update($res) if ($res->is_success || $res->code != 401);
+-
+- if (!$res->is_success) {
+- # We can deal with authentication failures ourselves. Either
+- # we sent invalid credentials, or our session has expired.
+- if ($res->code == 401) {
+- my %d = @$data;
+- if (exists $d{user}) {
+- warn "rt: Incorrect username or password.\n";
+- exit -1;
+- }
+- elsif ($req->header("Cookie")) {
+- # We'll retry the request with credentials, unless
+- # we only wanted to logout in the first place.
+- $session->delete;
+- return submit(@_) unless $uri eq "$REST/logout";
+- }
+- }
+- # Conflicts should be dealt with by the handler and user.
+- # For anything else, we just die.
+- elsif ($res->code != 409) {
+- warn "rt: ", $res->content;
+- #exit;
+- }
+- }
+- }
+- else {
+- warn "rt: Server error: ", $res->message, " (", $res->code, ")\n";
+- exit -1;
+- }
+-
+- return $res;
+-}
+-
+-# Session management.
+-# -------------------
+-#
+-# Maintains a list of active sessions in the ~/.rt_sessions file.
+-{
+- package Session;
+- my ($s, $u);
+-
+- # Initialises the session cache.
+- sub new {
+- my ($class, $file) = @_;
+- my $self = {
+- file => $file || "$HOME/.rt_sessions",
+- sids => { }
+- };
+-
+- # The current session is identified by the currently configured
+- # server and user.
+- ($s, $u) = @config{"server", "user"};
+-
+- bless $self, $class;
+- $self->load();
+-
+- return $self;
+- }
+-
+- # Returns the current session cookie.
+- sub cookie {
+- my ($self) = @_;
+- my $cookie = $self->{sids}{$s}{$u};
+- return defined $cookie ? "RT_SID_$cookie" : undef;
+- }
+-
+- # Deletes the current session cookie.
+- sub delete {
+- my ($self) = @_;
+- delete $self->{sids}{$s}{$u};
+- }
+-
+- # Adds a Cookie header to an outgoing HTTP request.
+- sub add_cookie_header {
+- my ($self, $request) = @_;
+- my $cookie = $self->cookie();
+-
+- $request->header(Cookie => $cookie) if defined $cookie;
+- }
+-
+- # Extracts the Set-Cookie header from an HTTP response, and updates
+- # session information accordingly.
+- sub update {
+- my ($self, $response) = @_;
+- my $cookie = $response->header("Set-Cookie");
+-
+- if (defined $cookie && $cookie =~ /^RT_SID_(.[^;,\s]+=[0-9A-Fa-f]+);/) {
+- $self->{sids}{$s}{$u} = $1;
+- }
+- }
+-
+- # Loads the session cache from the specified file.
+- sub load {
+- my ($self, $file) = @_;
+- $file ||= $self->{file};
+-
+- open( my $handle, '<', $file ) or return 0;
+-
+- $self->{file} = $file;
+- my $sids = $self->{sids} = {};
+- while (<$handle>) {
+- chomp;
+- next if /^$/ || /^#/;
+- next unless m#^https?://[^ ]+ \w+ [^;,\s]+=[0-9A-Fa-f]+$#;
+- my ($server, $user, $cookie) = split / /, $_;
+- $sids->{$server}{$user} = $cookie;
+- }
+- return 1;
+- }
+-
+- # Writes the current session cache to the specified file.
+- sub save {
+- my ($self, $file) = shift;
+- $file ||= $self->{file};
+-
+- open( my $handle, '>', "$file" ) or return 0;
+-
+- my $sids = $self->{sids};
+- foreach my $server (keys %$sids) {
+- foreach my $user (keys %{ $sids->{$server} }) {
+- my $sid = $sids->{$server}{$user};
+- if (defined $sid) {
+- print $handle "$server $user $sid\n";
+- }
+- }
+- }
+- close($handle);
+- chmod 0600, $file;
+- return 1;
+- }
+-
+- sub DESTROY {
+- my $self = shift;
+- $self->save;
+- }
+-}
+-
+-# Form handling.
+-# --------------
+-#
+-# Forms are RFC822-style sets of (field, value) specifications with some
+-# initial comments and interspersed blank lines allowed for convenience.
+-# Sets of forms are separated by --\n (in a cheap parody of MIME).
+-#
+-# Each form is parsed into an array with four elements: commented text
+-# at the start of the form, an array with the order of keys, a hash with
+-# key/value pairs, and optional error text if the form syntax was wrong.
+-
+-# Returns a reference to an array of parsed forms.
+-sub Form::parse {
+- my $state = 0;
+- my @forms = ();
+- my @lines = split /\n/, $_[0] if $_[0];
+- my ($c, $o, $k, $e) = ("", [], {}, "");
+-
+- 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+(.*))?$/) {
+- # Read a field: value specification.
+- my $f = $1;
+- my @v = ($2 || ());
+-
+- # 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;
+-
+- 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 $v = $k->{$key};
+- my @values = ref $v eq 'ARRAY' ? @$v : $v;
+-
+- $sp = " "x(length("$key: "));
+- $sp = " "x4 if length($sp) > 16;
+-
+- foreach $v (@values) {
+- 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;
+-}
+-
+-# Configuration.
+-# --------------
+-
+-# Returns configuration information from the environment.
+-sub config_from_env {
+- my %env;
+-
+- foreach my $k (qw(EXTERNALAUTH DEBUG USER PASSWD SERVER QUERY ORDERBY)) {
+-
+- if (exists $ENV{"RT$k"}) {
+- $env{lc $k} = $ENV{"RT$k"};
+- }
+- }
+-
+- return %env;
+-}
+-
+-# Finds a suitable configuration file and returns information from it.
+-sub config_from_file {
+- my ($rc) = @_;
+-
+- if ($rc =~ m#^/#) {
+- # We'll use an absolute path if we were given one.
+- return parse_config_file($rc);
+- }
+- else {
+- # Otherwise we'll use the first file we can find in the current
+- # directory, or in one of its (increasingly distant) ancestors.
+-
+- my @dirs = split /\//, cwd;
+- while (@dirs) {
+- my $file = join('/', @dirs, $rc);
+- if (-r $file) {
+- return parse_config_file($file);
+- }
+-
+- # Remove the last directory component each time.
+- pop @dirs;
+- }
+-
+- # Still nothing? We'll fall back to some likely defaults.
+- for ("$HOME/$rc", "local/etc/rt.conf", "/etc/rt.conf") {
+- return parse_config_file($_) if (-r $_);
+- }
+- }
+-
+- return ();
+-}
+-
+-# Makes a hash of the specified configuration file.
+-sub parse_config_file {
+- my %cfg;
+- my ($file) = @_;
+- local $_; # $_ may be aliased to a constant, from line 1163
+-
+- open( my $handle, '<', $file ) or return;
+-
+- while (<$handle>) {
+- chomp;
+- next if (/^#/ || /^\s*$/);
+-
+- if (/^(externalauth|user|passwd|server|query|orderby|queue)\s+(.*)\s?$/) {
+- $cfg{$1} = $2;
+- }
+- else {
+- die "rt: $file:$.: unknown configuration directive.\n";
+- }
+- }
+-
+- return %cfg;
+-}
+-
+-# Helper functions.
+-# -----------------
+-
+-sub whine {
+- my $sub = (caller(1))[3];
+- $sub =~ s/^main:://;
+- warn "rt: $sub: @_\n";
+- return 0;
+-}
+-
+-sub read_passwd {
+- eval 'require Term::ReadKey';
+- if ($@) {
+- die "No password specified (and Term::ReadKey not installed).\n";
+- }
+-
+- print "Password: ";
+- Term::ReadKey::ReadMode('noecho');
+- chomp(my $passwd = Term::ReadKey::ReadLine(0));
+- Term::ReadKey::ReadMode('restore');
+- print "\n";
+-
+- return $passwd;
+-}
+-
+-sub vi_form_while {
+- my $text = shift;
+- my $cb = shift;
+-
+- my $error = 0;
+- my ($c, $o, $k, $e);
+- do {
+- my $ntext = vi($text);
+- return undef if ($error && $ntext eq $text);
+-
+- $text = $ntext;
+-
+- my $form = Form::parse($text);
+- $error = 0;
+- ($c, $o, $k, $e) = @{ $form->[0] };
+- if ( $e ) {
+- $error = 1;
+- $c = "# Syntax error.";
+- goto NEXT;
+- }
+- elsif (!@$o) {
+- return 0;
+- }
+-
+- my ($status, $msg) = $cb->( $text, [$c, $o, $k, $e] );
+- unless ( $status ) {
+- $error = 1;
+- $c = "# $msg";
+- }
+-
+- NEXT:
+- $text = Form::compose([[$c, $o, $k, $e]]);
+- } while ($error);
+-
+- return $text;
+-}
+-
+-sub vi {
+- my ($text) = @_;
+- my $editor = $ENV{EDITOR} || $ENV{VISUAL} || "vi";
+-
+- local $/ = undef;
+-
+- my $handle = File::Temp->new;
+- print $handle $text;
+- close($handle);
+-
+- system($editor, $handle->filename) && die "Couldn't run $editor.\n";
+-
+- open( $handle, '<', $handle->filename ) or die "$handle: $!\n";
+- $text = <$handle>;
+- close($handle);
+-
+- return $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) = @_;
+- my ($word, @words);
+- my @values = ref $val eq 'ARRAY' ? @$val : $val;
+-
+- foreach my $line (map {split /\n/} @values) {
+- # XXX: This should become a real parser, à la Text::ParseWords.
+- $line =~ s/^\s+//;
+- $line =~ s/\s+$//;
+- my ( $a, $b ) = split /\s*,\s*/, $line, 2;
+-
+- while ($a) {
+- no warnings 'uninitialized';
+- if ( $a =~ /^'/ ) {
+- my $s = $a;
+- while ( $a !~ /'$/ || ( $a !~ /(\\\\)+'$/
+- && $a =~ /(\\)+'$/ )) {
+- ( $a, $b ) = split /\s*,\s*/, $b, 2;
+- $s .= ',' . $a;
+- }
+- push @words, $s;
+- }
+- elsif ( $a =~ /^q\{/ ) {
+- my $s = $a;
+- while ( $a !~ /\}$/ ) {
+- ( $a, $b ) =
+- split /\s*,\s*/, $b, 2;
+- $s .= ',' . $a;
+- }
+- $s =~ s/^q\{/'/;
+- $s =~ s/\}/'/;
+- push @words, $s;
+- }
+- else {
+- push @words, $a;
+- }
+- ( $a, $b ) = split /\s*,\s*/, $b, 2;
+- }
+-
+-
+- }
+-
+- return \@words;
+-}
+-
+-# WARN: this code is duplicated in lib/RT/Interface/REST.pm
+-# 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;
+-}
+-
+-sub get_type_argument {
+- my $type;
+-
+- if (@ARGV) {
+- $type = shift @ARGV;
+- unless ($type =~ /^[A-Za-z0-9_.-]+$/) {
+- # We want whine to mention our caller, not us.
+- @_ = ("Invalid type '$type' specified.");
+- goto &whine;
+- }
+- }
+- else {
+- @_ = ("No type argument specified with -t.");
+- goto &whine;
+- }
+-
+- $type =~ s/s$//; # "Plural". Ugh.
+- return $type;
+-}
+-
+-sub get_var_argument {
+- my ($data) = @_;
+-
+- if (@ARGV) {
+- my $kv = shift @ARGV;
+- if (my ($k, $v) = $kv =~ /^($field)=(.*)$/) {
+- push @{ $data->{$k} }, $v;
+- }
+- else {
+- @_ = ("Invalid variable specification: '$kv'.");
+- goto &whine;
+- }
+- }
+- else {
+- @_ = ("No variable argument specified with -S.");
+- goto &whine;
+- }
+-}
+-
+-sub is_object_spec {
+- my ($spec, $type) = @_;
+-
+- $spec =~ s|^(?:$type/)?|$type/| if defined $type;
+- return $spec if ($spec =~ m{^$name/(?:$idlist|$labels)(?:/.*)?$}o);
+- return 0;
+-}
+-
+-sub suggest_help {
+- my ($action, $type, $rv) = @_;
+-
+- print STDERR "rt: For help, run 'rt help $action'.\n" if defined $action;
+- print STDERR "rt: For help, run 'rt help $type'.\n" if defined $type;
+- return $rv;
+-}
+-
+-sub str2time {
+- # simplified procedure for parsing date, avoid loading Date::Parse
+- my %month = (Jan => 0, Feb => 1, Mar => 2, Apr => 3, May => 4, Jun => 5,
+- Jul => 6, Aug => 7, Sep => 8, Oct => 9, Nov => 10, Dec => 11);
+- $_ = shift;
+- my ($mon, $day, $hr, $min, $sec, $yr, $monstr);
+- if ( /(\w{3})\s+(\d\d?)\s+(\d\d):(\d\d):(\d\d)\s+(\d{4})/ ) {
+- ($monstr, $day, $hr, $min, $sec, $yr) = ($1, $2, $3, $4, $5, $6);
+- $mon = $month{$monstr} if exists $month{$monstr};
+- } elsif ( /(\d{4})-(\d\d)-(\d\d)\s+(\d\d):(\d\d):(\d\d)/ ) {
+- ($yr, $mon, $day, $hr, $min, $sec) = ($1, $2-1, $3, $4, $5, $6);
+- }
+- if ( $yr and defined $mon and $day and defined $hr and defined $sec ) {
+- return timelocal($sec,$min,$hr,$day,$mon,$yr);
+- } else {
+- print "Unknown date format in parsedate: $_\n";
+- return undef;
+- }
+-}
+-
+-sub date_diff {
+- my ($old, $new) = @_;
+- $new = time() if ! $new;
+- $old = str2time($old) if $old !~ /^\d+$/;
+- $new = str2time($new) if $new !~ /^\d+$/;
+- return "???" if ! $old or ! $new;
+-
+- my %seconds = (min => 60,
+- hr => 60*60,
+- day => 60*60*24,
+- wk => 60*60*24*7,
+- mth => 60*60*24*30,
+- yr => 60*60*24*365);
+-
+- my $diff = $new - $old;
+- my $what = 'sec';
+- my $howmuch = $diff;
+- for ( sort {$seconds{$a} <=> $seconds{$b}} keys %seconds) {
+- last if $diff < $seconds{$_};
+- $what = $_;
+- $howmuch = int($diff/$seconds{$_});
+- }
+- return "$howmuch $what";
+-}
+-
+-sub prettyshow {
+- my $forms = shift;
+- my ($form) = grep { exists $_->[2]->{Queue} } @$forms;
+- my $k = $form->[2];
+- # dates are in local time zone
+- if ( $k ) {
+- print "Date: $k->{Created}\n";
+- print "From: $k->{Requestors}\n";
+- print "Cc: $k->{Cc}\n" if $k->{Cc};
+- print "X-AdminCc: $k->{AdminCc}\n" if $k->{AdminCc};
+- print "X-Queue: $k->{Queue}\n";
+- print "Subject: [rt #$k->{id}] $k->{Subject}\n\n";
+- }
+- # dates in these attributes are in GMT and will be converted
+- foreach my $form (@$forms) {
+- my ($c, $o, $k, $e) = @$form;
+- next if ! $k->{id} or exists $k->{Queue};
+- if ( exists $k->{Created} ) {
+- my ($y,$m,$d,$hh,$mm,$ss) = ($k->{Created} =~ /(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/);
+- $m--;
+- my $created = localtime(timegm($ss,$mm,$hh,$d,$m,$y));
+- if ( exists $k->{Description} ) {
+- print "===> $k->{Description} on $created\n";
+- }
+- }
+- print "$k->{Content}\n" if exists $k->{Content} and
+- $k->{Content} !~ /to have no content$/ and
+- ($k->{Type}||'') ne 'EmailRecord';
+- print "$k->{Attachments}\n" if exists $k->{Attachments} and
+- $k->{Attachments};
+- }
+-}
+-
+-sub prettylist {
+- my $forms = shift;
+- my $heading = "Ticket Owner Queue Age Told Status Requestor Subject\n";
+- $heading .= '-' x 80 . "\n";
+- my (@open, @me);
+- foreach my $form (@$forms) {
+- my ($c, $o, $k, $e) = @$form;
+- next if ! $k->{id};
+- print $heading if $heading;
+- $heading = '';
+- my $id = $k->{id};
+- $id =~ s!^ticket/!!;
+- my $owner = $k->{Owner} eq 'Nobody' ? '' : $k->{Owner};
+- $owner = substr($owner, 0, 5);
+- my $queue = substr($k->{Queue}, 0, 5);
+- my $subject = substr($k->{Subject}, 0, 30);
+- my $age = date_diff($k->{Created});
+- my $told = $k->{Told} eq 'Not set' ? '' : date_diff($k->{Told});
+- my $status = substr($k->{Status}, 0, 6);
+- my $requestor = substr($k->{Requestors}, 0, 9);
+- my $line = sprintf "%6s %5s %5s %6s %6s %-6s %-9s %-30s\n",
+- $id, $owner, $queue, $age, $told, $status, $requestor, $subject;
+- if ( $k->{Owner} eq 'Nobody' ) {
+- push @open, $line;
+- } elsif ($k->{Owner} eq $config{user} ) {
+- push @me, $line;
+- } else {
+- print $line;
+- }
+- }
+- print "No matches found\n" if $heading;
+- printf "========== my %2d open tickets ==========\n", scalar @me if @me;
+- print @me if @me;
+- printf "========== %2d unowned tickets ==========\n", scalar @open if @open;
+- print @open if @open;
+-}
+-
+-__DATA__
+-
+-Title: intro
+-Title: introduction
+-Text:
+-
+- This is a command-line interface to RT 3.0 or newer.
+-
+- It allows you to interact with an RT server over HTTP, and offers an
+- interface to RT's functionality that is better-suited to automation
+- and integration with other tools.
+-
+- In general, each invocation of this program should specify an action
+- to perform on one or more objects, and any other arguments required
+- to complete the desired action.
+-
+- For more information:
+-
+- - rt help usage (syntax information)
+- - rt help objects (how to specify objects)
+- - rt help actions (a list of possible actions)
+- - rt help types (a list of object types)
+-
+- - rt help config (configuration details)
+- - rt help examples (a few useful examples)
+- - rt help topics (a list of help topics)
+-
+---
+-
+-Title: usage
+-Title: syntax
+-Text:
+-
+- Syntax:
+-
+- rt <action> [options] [arguments]
+- or
+- rt shell
+-
+- Each invocation of this program must specify an action (e.g. "edit",
+- "create"), options to modify behaviour, and other arguments required
+- by the specified action. (For example, most actions expect a list of
+- numeric object IDs to act upon.)
+-
+- The details of the syntax and arguments for each action are given by
+- "rt help <action>". Some actions may be referred to by more than one
+- name ("create" is the same as "new", for example).
+-
+- You may also call "rt shell", which will give you an 'rt>' prompt at
+- which you can issue commands of the form "<action> [options]
+- [arguments]". See "rt help shell" for details.
+-
+- Objects are identified by a type and an ID (which can be a name or a
+- number, depending on the type). For some actions, the object type is
+- implied (you can only comment on tickets); for others, the user must
+- specify it explicitly. See "rt help objects" for details.
+-
+- In syntax descriptions, mandatory arguments that must be replaced by
+- appropriate value are enclosed in <>, and optional arguments are
+- indicated by [] (for example, <action> and [options] above).
+-
+- For more information:
+-
+- - rt help objects (how to specify objects)
+- - rt help actions (a list of actions)
+- - rt help types (a list of object types)
+- - rt help shell (how to use the shell)
+-
+---
+-
+-Title: conf
+-Title: config
+-Title: configuration
+-Text:
+-
+- This program has two major sources of configuration information: its
+- configuration files, and the environment.
+-
+- The program looks for configuration directives in a file named .rtrc
+- (or $RTCONFIG; see below) in the current directory, and then in more
+- distant ancestors, until it reaches /. If no suitable configuration
+- files are found, it will also check for ~/.rtrc, local/etc/rt.conf
+- and /etc/rt.conf.
+-
+- Configuration directives:
+-
+- The following directives may occur, one per line:
+-
+- - server <URL> URL to RT server.
+- - user <username> RT username.
+- - passwd <passwd> RT user's password.
+- - query <RT Query> Default RT Query for list action
+- - orderby <order> Default RT order for list action
+- - queue <queuename> Default RT Queue for list action
+- - externalauth <0|1> Use HTTP Basic authentication
+- explicitely setting externalauth to 0 inhibits also GSSAPI based
+- authentication, if LWP::Authen::Negotiate (and GSSAPI) is installed
+-
+- Blank and #-commented lines are ignored.
+-
+- Sample configuration file contents:
+-
+- server https://rt.somewhere.com/
+- # more than one queue can be given (by adding a query expression)
+- queue helpdesk or queue=support
+- query Status != resolved and Owner=myaccount
+-
+-
+- Environment variables:
+-
+- The following environment variables override any corresponding
+- values defined in configuration files:
+-
+- - RTUSER
+- - RTPASSWD
+- - RTEXTERNALAUTH
+- - RTSERVER
+- - RTDEBUG Numeric debug level. (Set to 3 for full logs.)
+- - RTCONFIG Specifies a name other than ".rtrc" for the
+- configuration file.
+- - RTQUERY Default RT Query for rt list
+- - RTORDERBY Default order for rt list
+-
+---
+-
+-Title: objects
+-Text:
+-
+- Syntax:
+-
+- <type>/<id>[/<attributes>]
+-
+- Every object in RT has a type (e.g. "ticket", "queue") and a numeric
+- ID. Some types of objects can also be identified by name (like users
+- and queues). Furthermore, objects may have named attributes (such as
+- "ticket/1/history").
+-
+- An object specification is like a path in a virtual filesystem, with
+- object types as top-level directories, object IDs as subdirectories,
+- and named attributes as further subdirectories.
+-
+- A comma-separated list of names, numeric IDs, or numeric ranges can
+- be used to specify more than one object of the same type. Note that
+- the list must be a single argument (i.e., no spaces). For example,
+- "user/root,1-3,5,7-10,ams" is a list of ten users; the same list
+- can also be written as "user/ams,root,1,2,3,5,7,8-10".
+-
+- If just a number is given as object specification it will be
+- interpreted as ticket/<number>
+-
+- Examples:
+-
+- 1 # the same as ticket/1
+- ticket/1
+- ticket/1/attachments
+- ticket/1/attachments/3
+- ticket/1/attachments/3/content
+- ticket/1-3/links
+- ticket/1-3,5-7/history
+-
+- user/ams
+-
+- For more information:
+-
+- - rt help <action> (action-specific details)
+- - rt help <type> (type-specific details)
+-
+---
+-
+-Title: actions
+-Title: commands
+-Text:
+-
+- You can currently perform the following actions on all objects:
+-
+- - list (list objects matching some condition)
+- - show (display object details)
+- - edit (edit object details)
+- - create (create a new object)
+-
+- Each type may define actions specific to itself; these are listed in
+- the help item about that type.
+-
+- For more information:
+-
+- - rt help <action> (action-specific details)
+- - rt help types (a list of possible types)
+-
+- The following actions on tickets are also possible:
+-
+- - comment Add comments to a ticket
+- - correspond Add comments to a ticket
+- - merge Merge one ticket into another
+- - link Link one ticket to another
+- - take Take a ticket (steal and untake are possible as well)
+-
+- For several edit set subcommands that are frequently used abbreviations
+- have been introduced. These abbreviations are:
+-
+- - delete or del delete a ticket (edit set status=deleted)
+- - resolve or res resolve a ticket (edit set status=resolved)
+- - subject change subject of ticket (edit set subject=string)
+- - give give a ticket to somebody (edit set owner=user)
+-
+---
+-
+-Title: types
+-Text:
+-
+- You can currently operate on the following types of objects:
+-
+- - tickets
+- - users
+- - groups
+- - queues
+-
+- For more information:
+-
+- - rt help <type> (type-specific details)
+- - rt help objects (how to specify objects)
+- - rt help actions (a list of possible actions)
+-
+---
+-
+-Title: ticket
+-Text:
+-
+- Tickets are identified by a numeric ID.
+-
+- The following generic operations may be performed upon tickets:
+-
+- - list
+- - show
+- - edit
+- - create
+-
+- In addition, the following ticket-specific actions exist:
+-
+- - link
+- - merge
+- - comment
+- - correspond
+- - take
+- - steal
+- - untake
+- - give
+- - resolve
+- - delete
+- - subject
+-
+- Attributes:
+-
+- The following attributes can be used with "rt show" or "rt edit"
+- to retrieve or edit other information associated with tickets:
+-
+- links A ticket's relationships with others.
+- history All of a ticket's transactions.
+- history/type/<type> Only a particular type of transaction.
+- history/id/<id> Only the transaction of the specified id.
+- attachments A list of attachments.
+- attachments/<id> The metadata for an individual attachment.
+- attachments/<id>/content The content of an individual attachment.
+-
+---
+-
+-Title: user
+-Title: group
+-Text:
+-
+- Users and groups are identified by name or numeric ID.
+-
+- The following generic operations may be performed upon them:
+-
+- - list
+- - show
+- - edit
+- - create
+-
+---
+-
+-Title: queue
+-Text:
+-
+- Queues are identified by name or numeric ID.
+-
+- Currently, they can be subjected to the following actions:
+-
+- - show
+- - edit
+- - create
+-
+---
+-
+-Title: subject
+-Text:
+-
+- Syntax:
+-
+- rt subject <id> <new subject text>
+-
+- Change the subject of a ticket whose ticket id is given.
+-
+---
+-
+-Title: give
+-Text:
+-
+- Syntax:
+-
+- rt give <id> <accountname>
+-
+- Give a ticket whose ticket id is given to another user.
+-
+---
+-
+-Title: steal
+-Text:
+-
+- rt steal <id>
+-
+- Steal a ticket whose ticket id is given, i.e. set the owner to myself.
+-
+---
+-
+-Title: take
+-Text:
+-
+- Syntax:
+-
+- rt take <id>
+-
+- Take a ticket whose ticket id is given, i.e. set the owner to myself.
+-
+---
+-
+-Title: untake
+-Text:
+-
+- Syntax:
+-
+- rt untake <id>
+-
+- Untake a ticket whose ticket id is given, i.e. set the owner to Nobody.
+-
+---
+-
+-Title: resolve
+-Title: res
+-Text:
+-
+- Syntax:
+-
+- rt resolve <id>
+-
+- Resolves a ticket whose ticket id is given.
+-
+---
+-
+-Title: delete
+-Title: del
+-Text:
+-
+- Syntax:
+-
+- rt delete <id>
+-
+- Deletes a ticket whose ticket id is given.
+-
+---
+-
+-Title: logout
+-Text:
+-
+- Syntax:
+-
+- rt logout
+-
+- Terminates the currently established login session. You will need to
+- provide authentication credentials before you can continue using the
+- server. (See "rt help config" for details about authentication.)
+-
+---
+-
+-Title: ls
+-Title: list
+-Title: search
+-Text:
+-
+- Syntax:
+-
+- rt <ls|list|search> [options] "query string"
+-
+- Displays a list of objects matching the specified conditions.
+- ("ls", "list", and "search" are synonyms.)
+-
+- Conditions are expressed in the SQL-like syntax used internally by
+- RT. (For more information, see "rt help query".) The query string
+- must be supplied as one argument.
+-
+- (Right now, the server doesn't support listing anything but tickets.
+- Other types will be supported in future; this client will be able to
+- take advantage of that support without any changes.)
+-
+- Options:
+-
+- The following options control how much information is displayed
+- about each matching object:
+-
+- -i Numeric IDs only. (Useful for |rt edit -; see examples.)
+- -s Short description.
+- -l Longer description.
+- -f <field[s] Display only the fields listed and the ticket id
+-
+- In addition,
+-
+- -o +/-<field> Orders the returned list by the specified field.
+- -r reversed order (useful if a default was given)
+- -q queue[s] restricts the query to the queue[s] given
+- multiple queues are separated by comma
+- -S var=val Submits the specified variable with the request.
+- -t type Specifies the type of object to look for. (The
+- default is "ticket".)
+-
+- Examples:
+-
+- rt ls "Priority > 5 and Status=new"
+- rt ls -o +Subject "Priority > 5 and Status=new"
+- rt ls -o -Created "Priority > 5 and Status=new"
+- rt ls -i "Priority > 5"|rt edit - set status=resolved
+- rt ls -t ticket "Subject like '[PATCH]%'"
+- rt ls -q systems
+- rt ls -f owner,subject
+-
+---
+-
+-Title: show
+-Text:
+-
+- Syntax:
+-
+- rt show [options] <object-ids>
+-
+- Displays details of the specified objects.
+-
+- For some types, object information is further classified into named
+- attributes (for example, "1-3/links" is a valid ticket specification
+- that refers to the links for tickets 1-3). Consult "rt help <type>"
+- and "rt help objects" for further details.
+-
+- If only a number is given it will be interpreted as the objects
+- ticket/number and ticket/number/history
+-
+- This command writes a set of forms representing the requested object
+- data to STDOUT.
+-
+- Options:
+-
+- The following options control how much information is displayed
+- about each matching object:
+-
+- Without any formatting options prettyprinted output is generated.
+- Giving any of the two options below reverts to raw output.
+- -s Short description (history and attachments only).
+- -l Longer description (history and attachments only).
+-
+- In addition,
+- - Read IDs from STDIN instead of the command-line.
+- -t type Specifies object type.
+- -f a,b,c Restrict the display to the specified fields.
+- -S var=val Submits the specified variable with the request.
+-
+- Examples:
+-
+- rt show -t ticket -f id,subject,status 1-3
+- rt show ticket/3/attachments/29
+- rt show ticket/3/attachments/29/content
+- rt show ticket/1-3/links
+- rt show ticket/3/history
+- rt show -l ticket/3/history
+- rt show -t user 2
+- rt show 2
+-
+---
+-
+-Title: new
+-Title: edit
+-Title: create
+-Text:
+-
+- Syntax:
+-
+- rt edit [options] <object-ids> set field=value [field=value] ...
+- add field=value [field=value] ...
+- del field=value [field=value] ...
+-
+- Edits information corresponding to the specified objects.
+-
+- A purely numeric object id nnn is translated into ticket/nnn
+-
+- If, instead of "edit", an action of "new" or "create" is specified,
+- then a new object is created. In this case, no numeric object IDs
+- may be specified, but the syntax and behaviour remain otherwise
+- unchanged.
+-
+- This command typically starts an editor to allow you to edit object
+- data in a form for submission. If you specified enough information
+- on the command-line, however, it will make the submission directly.
+-
+- The command line may specify field-values in three different ways.
+- "set" sets the named field to the given value, "add" adds a value
+- to a multi-valued field, and "del" deletes the corresponding value.
+- Each "field=value" specification must be given as a single argument.
+-
+- For some types, object information is further classified into named
+- attributes (for example, "1-3/links" is a valid ticket specification
+- that refers to the links for tickets 1-3). These attributes may also
+- be edited. Consult "rt help <type>" and "rt help object" for further
+- details.
+-
+- Options:
+-
+- - Read numeric IDs from STDIN instead of the command-line.
+- (Useful with rt ls ... | rt edit -; see examples below.)
+- -i Read a completed form from STDIN before submitting.
+- -o Dump the completed form to STDOUT instead of submitting.
+- -e Allows you to edit the form even if the command-line has
+- enough information to make a submission directly.
+- -S var=val
+- Submits the specified variable with the request.
+- -t type Specifies object type.
+- -ct content-type Specifies content type of message(tickets only).
+-
+- Examples:
+-
+- # Interactive (starts $EDITOR with a form).
+- rt edit ticket/3
+- rt create -t ticket
+- rt create -t ticket -ct text/html
+-
+- # Non-interactive.
+- rt edit ticket/1-3 add cc=foo at example.com set priority=3 due=tomorrow
+- rt ls -t tickets -i 'Priority > 5' | rt edit - set status=resolved
+- rt edit ticket/4 set priority=3 owner=bar at example.com \
+- add cc=foo at example.com bcc=quux at example.net
+- rt create -t ticket set subject='new ticket' priority=10 \
+- add cc=foo at example.com
+-
+---
+-
+-Title: comment
+-Title: correspond
+-Text:
+-
+- Syntax:
+-
+- rt <comment|correspond> [options] <ticket-id>
+-
+- Adds a comment (or correspondence) to the specified ticket (the only
+- difference being that comments aren't sent to the requestors.)
+-
+- This command will typically start an editor and allow you to type a
+- comment into a form. If, however, you specified all the necessary
+- information on the command line, it submits the comment directly.
+-
+- (See "rt help forms" for more information about forms.)
+-
+- Options:
+-
+- -m <text> Specify comment text.
+- -ct <content-type> Specify content-type of comment text.
+- -a <file> Attach a file to the comment. (May be used more
+- than once to attach multiple files.)
+- -c <addrs> A comma-separated list of Cc addresses.
+- -b <addrs> A comma-separated list of Bcc addresses.
+- -w <time> Specify the time spent working on this ticket.
+- -e Starts an editor before the submission, even if
+- arguments from the command line were sufficient.
+-
+- Examples:
+-
+- rt comment -m 'Not worth fixing.' -a stddisclaimer.h 23
+-
+---
+-
+-Title: merge
+-Text:
+-
+- Syntax:
+-
+- rt merge <from-id> <to-id>
+-
+- Merges the first ticket specified into the second ticket specified.
+-
+---
+-
+-Title: link
+-Text:
+-
+- Syntax:
+-
+- rt link [-d] <id-A> <link> <id-B>
+-
+- Creates (or, with -d, deletes) a link between the specified tickets.
+- The link can (irrespective of case) be any of:
+-
+- DependsOn/DependedOnBy: A depends upon B (or vice versa).
+- RefersTo/ReferredToBy: A refers to B (or vice versa).
+- MemberOf/HasMember: A is a member of B (or vice versa).
+-
+- To view a ticket's links, use "rt show ticket/3/links". (See
+- "rt help ticket" and "rt help show".)
+-
+- Options:
+-
+- -d Deletes the specified link.
+-
+- Examples:
+-
+- rt link 2 dependson 3
+- rt link -d 4 referredtoby 6 # 6 no longer refers to 4
+-
+---
+-
+-Title: query
+-Text:
+-
+- RT uses an SQL-like syntax to specify object selection constraints.
+- See the <RT:...> documentation for details.
+-
+- (XXX: I'm going to have to write it, aren't I?)
+-
+- Until it exists here a short description of important constructs:
+-
+- The two simple forms of query expressions are the constructs
+- Attribute like Value and
+- Attribute = Value or Attribute != Value
+-
+- Whether attributes can be matched using like or using = is built into RT.
+- The attributes id, Queue, Owner Priority and Status require the = or !=
+- tests.
+-
+- If Value is a string it must be quoted and may contain the wildcard
+- character %. If the string does not contain white space, the quoting
+- may however be omitted, it will be added automatically when parsing
+- the input.
+-
+- Simple query expressions can be combined using and, or and parentheses
+- can be used to group expressions.
+-
+- As a special case a standalone string (which would not form a correct
+- query) is transformed into (Owner='string' or Requestor like 'string%')
+- and added to the default query, i.e. the query is narrowed down.
+-
+- If no Queue=name clause is contained in the query, a default clause
+- Queue=$config{queue} is added.
+-
+- Examples:
+- Status!='resolved' and Status!='rejected'
+- (Owner='myaccount' or Requestor like 'myaccount%') and Status!='resolved'
+-
+---
+-
+-Title: form
+-Title: forms
+-Text:
+-
+- This program uses RFC822 header-style forms to represent object data
+- in a form that's suitable for processing both by humans and scripts.
+-
+- A form is a set of (field, value) specifications, with some initial
+- commented text and interspersed blank lines allowed for convenience.
+- Field names may appear more than once in a form; a comma-separated
+- list of multiple field values may also be specified directly.
+-
+- Field values can be wrapped as in RFC822, with leading whitespace.
+- The longest sequence of leading whitespace common to all the lines
+- is removed (preserving further indentation). There is no limit on
+- the length of a value.
+-
+- Multiple forms are separated by a line containing only "--\n".
+-
+- (XXX: A more detailed specification will be provided soon. For now,
+- the server-side syntax checking will suffice.)
+-
+---
+-
+-Title: topics
+-Text:
+-
+- Syntax:
+-
+- rt help <topic>
+-
+- Get help on any of the following subjects:
+-
+- - tickets, users, groups, queues.
+- - show, edit, ls/list/search, new/create.
+-
+- - query (search query syntax)
+- - forms (form specification)
+-
+- - objects (how to specify objects)
+- - types (a list of object types)
+- - actions/commands (a list of actions)
+- - usage/syntax (syntax details)
+- - conf/config/configuration (configuration details)
+- - examples (a few useful examples)
+-
+---
+-
+-Title: example
+-Title: examples
+-Text:
+-
+- some useful examples
+-
+- All the following list requests will be restricted to the default queue.
+- That can be changed by adding the option -q queuename
+-
+- List all tickets that are not rejected/resolved
+- rt ls
+- List all tickets that are new and do not have an owner
+- rt ls "status=new and owner=nobody"
+- List all tickets which I have sent or of which I am the owner
+- rt ls myaccount
+- List all attributes for the ticket 6977 (ls -l instead of ls)
+- rt ls -l 6977
+- Show the content of ticket 6977
+- rt show 6977
+- Show all attributes in the ticket and in the history of the ticket
+- rt show -l 6977
+- Comment a ticket (mail is sent to all queue watchers, i.e. AdminCc's)
+- rt comment 6977
+- This will open an editor and lets you add text (attribute Text:)
+- Other attributes may be changed as well, but usually don't do that.
+- Correspond a ticket (like comment, but mail is also sent to requestors)
+- rt correspond 6977
+- Edit a ticket (generic change, interactive using the editor)
+- rt edit 6977
+- Change the owner of a ticket non interactively
+- rt edit 6977 set owner=myaccount
+- or
+- rt give 6977 account
+- or
+- rt take 6977
+- Change the status of a ticket
+- rt edit 6977 set status=resolved
+- or
+- rt resolve 6977
+- Change the status of all tickets I own to resolved !!!
+- rt ls -i owner=myaccount | rt edit - set status=resolved
+-
+---
+-
+-Title: shell
+-Text:
+-
+- Syntax:
+-
+- rt shell
+-
+- Opens an interactive shell, at which you can issue commands of
+- the form "<action> [options] [arguments]".
+-
+- To exit the shell, type "quit" or "exit".
+-
+- Commands can be given at the shell in the same form as they would
+- be given at the command line without the leading 'rt' invocation.
+-
+- Example:
+- $ rt shell
+- rt> create -t ticket set subject='new' add cc=foo at example.com
+- # Ticket 8 created.
+- rt> quit
+- $
+-
+---
+-
+-Title: take
+-Title: untake
+-Title: steal
+-Text:
+-
+- Syntax:
+-
+- rt <take|untake|steal> <ticket-id>
+-
+- Sets the owner of the specified ticket to the current user,
+- assuming said user has the bits to do so, or releases the
+- ticket.
+-
+- 'Take' is used on tickets which are not currently owned
+- (Owner: Nobody), 'steal' is used on tickets which *are*
+- currently owned, and 'untake' is used to "release" a ticket
+- (reset its Owner to Nobody). 'Take' cannot be used on
+- tickets which are currently owned.
+-
+- Example:
+- alice$ rt create -t ticket set subject="New ticket"
+- # Ticket 7 created.
+- alice$ rt take 7
+- # Owner changed from Nobody to alice
+- alice$ su bob
+- bob$ rt steal 7
+- # Owner changed from alice to bob
+- bob$ rt untake 7
+- # Owner changed from bob to Nobody
+-
+---
+-
+-Title: quit
+-Title: exit
+-Text:
+-
+- Use "quit" or "exit" to leave the shell. Only valid within shell
+- mode.
+-
+- Example:
+- $ rt shell
+- rt> quit
+- $
+-
+-__END__
+-
+-=head1 NAME
+-
+-rt - command-line interface to RT 3.0 or newer
+-
+-=head1 SYNOPSIS
+-
+- rt help
+-
+-=head1 DESCRIPTION
+-
+-This script allows you to interact with an RT server over HTTP, and offers an
+-interface to RT's functionality that is better-suited to automation and
+-integration with other tools.
+-
+-In general, each invocation of this program should specify an action to
+-perform on one or more objects, and any other arguments required to complete
+-the desired action.
+-
+diff --git a/bin/rt-crontool b/bin/rt-crontool
+deleted file mode 100755
+index ca9a6d6..0000000
+--- a/bin/rt-crontool
++++ /dev/null
+@@ -1,468 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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 Carp;
+-
+-# fix lib paths, some may be relative
+-BEGIN {
+- require File::Spec;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- }
+- else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-
+-}
+-
+-use RT;
+-
+-use Getopt::Long;
+-
+-use RT::Interface::CLI qw(CleanEnv GetCurrentUser GetMessageContent loc);
+-
+-#Clean out all the nasties from the environment
+-CleanEnv();
+-
+-my ( $search, $condition, $action, $search_arg, $condition_arg, $action_arg,
+- $template, $template_id, $transaction, $transaction_type, $help, $log, $verbose );
+-GetOptions(
+- "search=s" => \$search,
+- "search-arg=s" => \$search_arg,
+- "condition=s" => \$condition,
+- "condition-arg=s" => \$condition_arg,
+- "action-arg=s" => \$action_arg,
+- "action=s" => \$action,
+- "template=s" => \$template,
+- "template-id=s" => \$template_id,
+- "transaction=s" => \$transaction,
+- "transaction-type=s" => \$transaction_type,
+- "log=s" => \$log,
+- "verbose|v" => \$verbose,
+- "help" => \$help,
+-);
+-
+-# Load the config file
+-RT::LoadConfig();
+-
+-# adjust logging to the screen according to options
+-RT->Config->Set( LogToScreen => $log ) if $log;
+-
+-#Connect to the database and get RT::SystemUser and RT::Nobody loaded
+-RT::Init();
+-
+-require RT::Tickets;
+-require RT::Template;
+-
+-#Get the current user all loaded
+-my $CurrentUser = GetCurrentUser();
+-
+-# show help even if there is no current user
+-help() if $help;
+-
+-unless ( $CurrentUser->Id ) {
+- print loc("No RT user found. Please consult your RT administrator.");
+- exit(1);
+-}
+-
+-help() unless $search && $action;
+-
+-$transaction = lc( $transaction||'' );
+-if ( $transaction && $transaction !~ /^(first|all|last)$/i ) {
+- print STDERR loc("--transaction argument could be only 'first', 'last' or 'all'");
+- exit 1;
+-}
+-
+-if ( $template && $template_id ) {
+- print STDERR loc("--template-id is deprecated argument and can not be used with --template");
+- exit 1;
+-}
+-elsif ( $template_id ) {
+-# don't warn
+- $template = $template_id;
+-}
+-
+-# We _must_ have a search object
+-load_module($search);
+-load_module($action) if ($action);
+-load_module($condition) if ($condition);
+-
+-my $void_scrip = RT::Scrip->new( $CurrentUser );
+-my $void_scrip_action = RT::ScripAction->new( $CurrentUser );
+-
+-#At the appointed time:
+-
+-#find a bunch of tickets
+-my $tickets = RT::Tickets->new($CurrentUser);
+-$search = $search->new(
+- TicketsObj => $tickets,
+- Argument => $search_arg,
+- CurrentUser => $CurrentUser
+-);
+-$search->Prepare();
+-
+-#for each ticket we've found
+-while ( my $ticket = $tickets->Next() ) {
+- print $ticket->Id() . ":\n" if ($verbose);
+-
+- my $template_obj = get_template( $ticket );
+-
+- if ( $transaction ) {
+- my $txns = get_transactions($ticket);
+- my $found = 0;
+- while ( my $txn = $txns->Next ) {
+- print "\t".loc("Using transaction #[_1]...", $txn->id)."\n"
+- if $verbose;
+- process($ticket, $txn, $template_obj);
+- $found = 1;
+- }
+- print "\t".loc("Couldn't find suitable transaction, skipping")."\n"
+- if $verbose && !$found;
+- } else {
+- print "\t".loc("Processing without transaction, some conditions and actions may fail. Consider using --transaction argument")."\n"
+- if $verbose;
+-
+- process($ticket, undef, $template_obj);
+- }
+-}
+-
+-sub process {
+- my $ticket = shift;
+- my $transaction = shift;
+- my $template_obj = shift;
+-
+- # perform some more advanced check
+- if ($condition) {
+- my $condition_obj = $condition->new(
+- TransactionObj => $transaction,
+- TicketObj => $ticket,
+- ScripObj => $void_scrip,
+- TemplateObj => $template_obj,
+- Argument => $condition_arg,
+- CurrentUser => $CurrentUser,
+- );
+-
+- # if the condition doesn't apply, get out of here
+-
+- return unless $condition_obj->IsApplicable;
+- print "\t".loc("Condition matches...")."\n" if $verbose;
+- }
+-
+- #prepare our action
+- my $action_obj = $action->new(
+- TicketObj => $ticket,
+- TransactionObj => $transaction,
+- TemplateObj => $template_obj,
+- Argument => $action_arg,
+- ScripObj => $void_scrip,
+- ScripActionObj => $void_scrip_action,
+- CurrentUser => $CurrentUser,
+- );
+-
+- #if our preparation, move onto the next ticket
+- return unless $action_obj->Prepare;
+- print "\t".loc("Action prepared...")."\n" if $verbose;
+-
+- #commit our action.
+- return unless $action_obj->Commit;
+- print "\t".loc("Action committed.")."\n" if $verbose;
+-}
+-
+-# =head2 get_transactions
+-#
+-# Takes ticket and returns L<RT::Transactions> object with transactions
+-# of the ticket according to command line arguments C<--transaction>
+-# and <--transaction-type>.
+-#
+-# =cut
+-
+-sub get_transactions {
+- my $ticket = shift;
+- my $txns = $ticket->Transactions;
+- my $order = $transaction eq 'last'? 'DESC': 'ASC';
+- $txns->OrderByCols(
+- { FIELD => 'Created', ORDER => $order },
+- { FIELD => 'id', ORDER => $order },
+- );
+- if ( $transaction_type ) {
+- $transaction_type =~ s/^\s+//;
+- $transaction_type =~ s/\s+$//;
+- foreach my $type ( split /\s*,\s*/, $transaction_type ) {
+- $txns->Limit( FIELD => 'Type', VALUE => $type, ENTRYAGGREGATOR => 'OR' );
+- }
+- }
+- $txns->RowsPerPage(1) unless $transaction eq 'all';
+- return $txns;
+-}
+-
+-# =head2 get_template
+-#
+-# Takes a ticket and returns a template according to command line options.
+-#
+-# =cut
+-
+-{ my $cache = undef;
+-sub get_template {
+- my $ticket = shift;
+- return undef unless $template;
+-
+- unless ( $template =~ /\D/ ) {
+- # by id
+- return $cache if $cache;
+-
+- my $cache = RT::Template->new( RT->SystemUser );
+- $cache->Load( $template );
+- die "Failed to load template '$template'"
+- unless $cache->id;
+- return $cache;
+- }
+-
+- my $queue = $ticket->Queue;
+- return $cache->{ $queue } if $cache->{ $queue };
+-
+- my $res = RT::Template->new( RT->SystemUser );
+- $res->LoadQueueTemplate( Queue => $queue, Name => $template );
+- unless ( $res->id ) {
+- $res->LoadGlobalTemplate( $template );
+- die "Failed to load template '$template', either for queue #$queue or global"
+- unless $res->id;
+- }
+- return $cache->{ $queue } = $res;
+-} }
+-
+-
+-# =head2 load_module
+-#
+-# Loads a perl module, dying nicely if it can't find it.
+-#
+-# =cut
+-
+-sub load_module {
+- my $modname = shift;
+- eval "require $modname";
+- if ($@) {
+- die loc( "Failed to load module [_1]. ([_2])", $modname, $@ );
+- }
+-
+-}
+-
+-
+-sub help {
+-
+- print loc( "[_1] is a tool to act on tickets from an external scheduling tool, such as cron.", $0 )
+- . "\n";
+- print loc("It takes several arguments:") . "\n\n";
+-
+- print " "
+- . loc( "[_1] - Specify the search module you want to use", "--search" )
+- . "\n";
+- print " "
+- . loc( "[_1] - An argument to pass to [_2]", "--search-arg", "--search" )
+- . "\n";
+-
+- print " "
+- . loc( "[_1] - Specify the condition module you want to use", "--condition" )
+- . "\n";
+- print " "
+- . loc( "[_1] - An argument to pass to [_2]", "--condition-arg", "--condition" )
+- . "\n";
+- print " "
+- . loc( "[_1] - Specify the action module you want to use", "--action" )
+- . "\n";
+- print " "
+- . loc( "[_1] - An argument to pass to [_2]", "--action-arg", "--action" )
+- . "\n";
+- print " "
+- . loc( "[_1] - Specify name or id of template(s) you want to use", "--template" )
+- . "\n";
+- print " "
+- . loc( "[_1] - Specify if you want to use either 'first', 'last' or 'all' transactions", "--transaction" )
+- . "\n";
+- print " "
+- . loc( "[_1] - Specify the comma separated list of transactions' types you want to use", "--transaction-type" )
+- . "\n";
+- print " "
+- . loc( "[_1] - Adjust LogToScreen config option", "--log" ) . "\n";
+- print " "
+- . loc( "[_1] - Output status updates to STDOUT", "--verbose" ) . "\n";
+- print "\n";
+- print "\n";
+- print loc("Security:")."\n";
+- print loc("This tool allows the user to run arbitrary perl modules from within RT.")." ".
+- loc("If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT.")." ".
+- loc("It is incredibly important that nonprivileged users not be allowed to run this tool."). " " .
+- loc("It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool.")."\n";
+- print "\n";
+- print loc("Example:");
+- print "\n";
+- print " "
+- . loc( "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they are overdue:"
+- )
+- . "\n\n";
+-
+- print " bin/rt-crontool \\\n";
+- print " --search RT::Search::ActiveTicketsInQueue --search-arg general \\\n";
+- print " --condition RT::Condition::Overdue \\\n";
+- print " --action RT::Action::SetPriority --action-arg 99 \\\n";
+- print " --verbose\n";
+-
+- print "\n";
+- print loc("Escalate tickets"). "\n";
+- print " bin/rt-crontool \\\n";
+- print " --search RT::Search::ActiveTicketsInQueue --search-arg general \\\n";
+- print" --action RT::Action::EscalatePriority\n";
+-
+-
+-
+-
+-
+-
+- exit(0);
+-}
+-
+-__END__
+-
+-=head1 NAME
+-
+-rt-crontool - a tool to act on tickets from an external scheduling tool
+-
+-=head1 SYNOPSIS
+-
+- # find all active tickets in the queue 'general' and set their priority to 99 if they are overdue:
+- rt-crontool \
+- --search RT::Search::ActiveTicketsInQueue --search-arg general \
+- --condition RT::Condition::Overdue \
+- --action RT::Action::SetPriority --action-arg 99 \
+- --verbose
+-
+- # Escalate tickets
+- rt-crontool \
+- --search RT::Search::ActiveTicketsInQueue --search-arg general \
+- --action RT::Action::EscalatePriority
+-
+-=head1 DESCRIPTION
+-
+-This script is a tool to act on tickets from an external scheduling tool, such
+-as cron.
+-
+-Security:
+-
+-This tool allows the user to run arbitrary perl modules from within RT. If
+-this tool were setgid, a hostile local user could use this tool to gain
+-administrative access to RT. It is incredibly important that nonprivileged
+-users not be allowed to run this tool. It is suggested that you create a
+-non-privileged unix user with the correct group membership and RT access to
+-run this tool.
+-
+-
+-=head1 OPTIONS
+-
+-=over
+-
+-=item search
+-
+-Specify the search module you want to use
+-
+-=item search-arg
+-
+-An argument to pass to --search
+-
+-=item condition
+-
+-Specify the condition module you want to use
+-
+-=item condition-arg
+-
+-An argument to pass to --condition
+-
+-=item action
+-
+-Specify the action module you want to use
+-
+-=item action-arg
+-
+-An argument to pass to --action
+-
+-=item template
+-
+-Specify name or id of template(s) you want to use
+-
+-=item transaction
+-
+-Specify if you want to use either 'first', 'last' or 'all' transactions
+-
+-
+-=item transaction-type
+-
+-Specify the comma separated list of transactions' types you want to use
+-
+-=item log
+-
+-Adjust LogToScreen config option
+-
+-=item verbose
+-
+-Output status updates to STDOUT
+-
+-=back
+-
+diff --git a/bin/rt-mailgate b/bin/rt-mailgate
+deleted file mode 100755
+index 2e1c76f..0000000
+--- a/bin/rt-mailgate
++++ /dev/null
+@@ -1,526 +0,0 @@
+-#!/usr/bin/perl -w
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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-mailgate - Mail interface to RT.
+-
+-=cut
+-
+-use strict;
+-use warnings;
+-
+-use Getopt::Long;
+-
+-my $opts = { };
+-GetOptions( $opts, "queue=s", "action=s", "url=s",
+- "jar=s", "help", "debug", "extension=s",
+- "timeout=i", "verify-ssl!", "ca-file=s",
+- );
+-
+-my $gateway = RT::Client::MailGateway->new();
+-
+-$gateway->run($opts);
+-
+-package RT::Client::MailGateway;
+-
+-use LWP::UserAgent;
+-use HTTP::Request::Common qw($DYNAMIC_FILE_UPLOAD);
+-use File::Temp qw(tempfile tempdir);
+-$DYNAMIC_FILE_UPLOAD = 1;
+-
+-use constant EX_TEMPFAIL => 75;
+-use constant BUFFER_SIZE => 8192;
+-
+-sub new {
+- my $class = shift;
+- my $self = bless {}, $class;
+- return $self;
+-}
+-
+-sub run {
+- my $self = shift;
+- my $opts = shift;
+-
+- if ( $opts->{running_in_test_harness} ) {
+- $self->{running_in_test_harness} = 1;
+- }
+-
+- $self->validate_cli_flags($opts);
+-
+- my $ua = $self->get_useragent($opts);
+- my $post_params = $self->setup_session($opts);
+- $self->upload_message( $ua => $post_params );
+- $self->exit_with_success();
+-}
+-
+-sub exit_with_success {
+- my $self = shift;
+- if ( $self->{running_in_test_harness} ) {
+- return 1;
+- } else {
+- exit 0;
+- }
+-}
+-
+-sub tempfail {
+- my $self = shift;
+- if ( $self->{running_in_test_harness} ) {
+- die "tempfail";
+- } else {
+-
+- exit EX_TEMPFAIL;
+- }
+-}
+-
+-sub permfail {
+- my $self = shift;
+- if ( $self->{running_in_test_harness} ) {
+- die "permfail";
+- } else {
+-
+- exit 1;
+- }
+-}
+-
+-sub validate_cli_flags {
+- my $self = shift;
+- my $opts = shift;
+- if ( $opts->{'help'} ) {
+- require Pod::Usage;
+- Pod::Usage::pod2usage( { verbose => 2 } );
+- return $self->permfail()
+- ; # Don't want to succeed if this is really an email!
+- }
+-
+- unless ( $opts->{'url'} ) {
+- print STDERR
+- "$0 invoked improperly\n\nNo 'url' provided to mail gateway!\n";
+- return $self->permfail();
+- }
+-
+- if (($opts->{'ca-file'} or $opts->{"verify-ssl"})
+- and not LWP::UserAgent->can("ssl_opts")) {
+- print STDERR "Verifying SSL certificates requires LWP::UserAgent 6.0 or higher.\n";
+- return $self->tempfail();
+- }
+-
+- $opts->{"verify-ssl"} = 1 unless defined $opts->{"verify-ssl"};
+-}
+-
+-sub get_useragent {
+- my $self = shift;
+- my $opts = shift;
+- my $ua = LWP::UserAgent->new();
+- $ua->cookie_jar( { file => $opts->{'jar'} } ) if $opts->{'jar'};
+-
+- if ( $ua->can("ssl_opts") ) {
+- $ua->ssl_opts( verify_hostname => $opts->{'verify-ssl'} );
+- $ua->ssl_opts( SSL_ca_file => $opts->{'ca-file'} )
+- if $opts->{'ca-file'};
+- }
+-
+- return $ua;
+-}
+-
+-sub setup_session {
+- my $self = shift;
+- my $opts = shift;
+- my %post_params;
+- foreach (qw(queue action)) {
+- $post_params{$_} = $opts->{$_} if defined $opts->{$_};
+- }
+-
+- if ( ( $opts->{'extension'} || '' ) =~ /^(?:action|queue|ticket)$/i ) {
+- $post_params{ lc $opts->{'extension'} } = $ENV{'EXTENSION'}
+- || $opts->{ $opts->{'extension'} };
+- } elsif ( $opts->{'extension'} && $ENV{'EXTENSION'} ) {
+- print STDERR
+- "Value of the --extension argument is not action, queue or ticket"
+- . ", but environment variable EXTENSION is also defined. The former is ignored.\n";
+- }
+-
+- # add ENV{'EXTENSION'} as X-RT-MailExtension to the message header
+- if ( my $value = ( $ENV{'EXTENSION'} || $opts->{'extension'} ) ) {
+-
+- # prepare value to avoid MIME format breakage
+- # strip trailing newline symbols
+- $value =~ s/(\r*\n)+$//;
+-
+- # make a correct multiline header field,
+- # with tabs in the beginning of each line
+- $value =~ s/(\r*\n)/$1\t/g;
+- $opts->{'headers'} .= "X-RT-Mail-Extension: $value\n";
+- }
+-
+- # Read the message in from STDIN
+- # _raw_message is used for testing
+- my $message = $opts->{'_raw_message'} || $self->slurp_message();
+- unless ( $message->{'filename'} ) {
+- $post_params{'message'} = [
+- undef, '',
+- 'Content-Type' => 'application/octet-stream',
+- Content => ${ $message->{'content'} },
+- ];
+- } else {
+- $post_params{'message'} = [
+- $message->{'filename'}, '',
+- 'Content-Type' => 'application/octet-stream',
+- ];
+- }
+-
+- return \%post_params;
+-}
+-
+-sub upload_message {
+- my $self = shift;
+- my $ua = shift;
+- my $post_params = shift;
+- my $full_url = $opts->{'url'} . "/REST/1.0/NoAuth/mail-gateway";
+- print STDERR "$0: connecting to $full_url\n" if $opts->{'debug'};
+-
+- $ua->timeout( exists( $opts->{'timeout'} ) ? $opts->{'timeout'} : 180 );
+- my $r = $ua->post( $full_url, $post_params, Content_Type => 'form-data' );
+- $self->check_failure($r);
+-
+- my $content = $r->content;
+- print STDERR $content . "\n" if $opts->{'debug'};
+-
+- return if ( $content =~ /^(ok|not ok)/ );
+-
+- # It's not the server's fault if the mail is bogus. We just want to know that
+- # *something* came out of the server.
+- print STDERR <<EOF;
+-RT server error.
+-
+-The RT server which handled your email did not behave as expected. It
+-said:
+-
+-$content
+-EOF
+-
+- return $self->tempfail();
+-}
+-
+-sub check_failure {
+- my $self = shift;
+- my $r = shift;
+- return if $r->is_success;
+-
+- # XXX TODO 4.2: Remove the multi-line error strings in favor of something more concise
+- print STDERR <<" ERROR";
+-An Error Occurred
+-=================
+-
+-@{[ $r->status_line ]}
+- ERROR
+- print STDERR "\n$0: undefined server error\n" if $opts->{'debug'};
+- return $self->tempfail();
+-}
+-
+-sub slurp_message {
+- my $self = shift;
+-
+- local $@;
+-
+- my %message;
+- my ( $fh, $filename )
+- = eval { tempfile( DIR => tempdir( CLEANUP => 1 ) ) };
+- if ( !$fh || $@ ) {
+- print STDERR "$0: Couldn't create temp file, using memory\n";
+- print STDERR "error: $@\n" if $@;
+-
+- my $message = \do { local ( @ARGV, $/ ); <STDIN> };
+- unless ( $$message =~ /\S/ ) {
+- print STDERR "$0: no message passed on STDIN\n";
+- $self->exit_with_success;
+- }
+- $$message = $opts->{'headers'} . $$message if $opts->{'headers'};
+- return ( { content => $message } );
+- }
+-
+- binmode $fh;
+- binmode \*STDIN;
+-
+- print $fh $opts->{'headers'} if $opts->{'headers'};
+-
+- my $buf;
+- my $empty = 1;
+- while (1) {
+- my $status = read \*STDIN, $buf, BUFFER_SIZE;
+- unless ( defined $status ) {
+- print STDERR "$0: couldn't read message: $!\n";
+- return $self->tempfail();
+- } elsif ( !$status ) {
+- last;
+- }
+- $empty = 0 if $buf =~ /\S/;
+- print $fh $buf;
+- }
+- close $fh;
+-
+- if ($empty) {
+- print STDERR "$0: no message passed on STDIN\n";
+- $self->exit_with_success;
+- }
+- print STDERR "$0: temp file is '$filename'\n" if $opts->{'debug'};
+- return ( { filename => $filename } );
+-}
+-
+-=head1 SYNOPSIS
+-
+- rt-mailgate --help : this text
+-
+-Usual invocation (from MTA):
+-
+- rt-mailgate --action (correspond|comment|...) --queue queuename
+- --url http://your.rt.server/
+- [ --debug ]
+- [ --extension (queue|action|ticket) ]
+- [ --timeout seconds ]
+-
+-
+-
+-=head1 OPTIONS
+-
+-=over 3
+-
+-=item C<--action>
+-
+-Specifies what happens to email sent to this alias. The avaliable
+-basic actions are: C<correspond>, C<comment>.
+-
+-
+-If you've set the RT configuration variable B<< C<UnsafeEmailCommands> >>,
+-C<take> and C<resolve> are also available. You can execute two or more
+-actions on a single message using a C<-> separated list. RT will execute
+-the actions in the listed order. For example you can use C<take-comment>,
+-C<correspond-resolve> or C<take-comment-resolve> as actions.
+-
+-Note that C<take> and C<resolve> actions ignore message text if used
+-alone. Include a C<comment> or C<correspond> action if you want RT
+-to record the incoming message.
+-
+-The default action is C<correspond>.
+-
+-=item C<--queue>
+-
+-This flag determines which queue this alias should create a ticket in if no ticket identifier
+-is found.
+-
+-=item C<--url>
+-
+-This flag tells the mail gateway where it can find your RT server. You should
+-probably use the same URL that users use to log into RT.
+-
+-If your RT server uses SSL, you will need to install additional Perl
+-libraries. RT will detect and install these dependencies if you pass the
+-C<--enable-ssl-mailgate> flag to configure as documented in RT's README.
+-
+-If you have a self-signed SSL certificate, you may also need to pass
+-C<--ca-file> or C<--no-verify-ssl>, below.
+-
+-=item C<--ca-file> I<path>
+-
+-Specifies the path to the public SSL certificate for the certificate
+-authority that should be used to verify the website's SSL certificate.
+-If your webserver uses a self-signed certificate, you should
+-preferentially use this option over C<--no-verify-ssl>, as it will
+-ensure that the self-signed certificate that the mailgate is seeing the
+-I<right> self-signed certificate.
+-
+-=item C<--no-verify-ssl>
+-
+-This flag tells the mail gateway to trust all SSL certificates,
+-regardless of if their hostname matches the certificate, and regardless
+-of CA. This is required if you have a self-signed certificate, or some
+-other certificate which is not traceable back to an certificate your
+-system ultimitely trusts.
+-
+-Verifying SSL certificates requires L<LWP::UserAgent> version 6.0 or
+-higher; explicitly passing C<--verify-ssl> on prior versions will error.
+-
+-=item C<--extension> OPTIONAL
+-
+-Some MTAs will route mail sent to user-foo at host or user+foo at host to user at host
+-and present "foo" in the environment variable $EXTENSION. By specifying
+-the value "queue" for this parameter, the queue this message should be
+-submitted to will be set to the value of $EXTENSION. By specifying
+-"ticket", $EXTENSION will be interpreted as the id of the ticket this message
+-is related to. "action" will allow the user to specify either "comment" or
+-"correspond" in the address extension.
+-
+-=item C<--debug> OPTIONAL
+-
+-Print debugging output to standard error
+-
+-
+-=item C<--timeout> OPTIONAL
+-
+-Configure the timeout for posting the message to the web server. The
+-default timeout is 3 minutes (180 seconds).
+-
+-=back
+-
+-
+-=head1 DESCRIPTION
+-
+-The RT mail gateway is the primary mechanism for communicating with RT
+-via email. This program simply directs the email to the RT web server,
+-which handles filing correspondence and sending out any required mail.
+-It is designed to be run as part of the mail delivery process, either
+-called directly by the MTA or C<procmail>, or in a F<.forward> or
+-equivalent.
+-
+-=head1 SETUP
+-
+-Much of the set up of the mail gateway depends on your MTA and mail
+-routing configuration. However, you will need first of all to create an
+-RT user for the mail gateway and assign it a password; this helps to
+-ensure that mail coming into the web server did originate from the
+-gateway.
+-
+-Next, you need to route mail to C<rt-mailgate> for the queues you're
+-monitoring. For instance, if you're using F</etc/aliases> and you have a
+-"bugs" queue, you will want something like this:
+-
+- bugs: "|/opt/rt4/bin/rt-mailgate --queue bugs --action correspond
+- --url http://rt.mycorp.com/"
+-
+- bugs-comment: "|/opt/rt4/bin/rt-mailgate --queue bugs --action comment
+- --url http://rt.mycorp.com/"
+-
+-Note that you don't have to run your RT server on your mail server, as
+-the mail gateway will happily relay to a different machine.
+-
+-=head1 CUSTOMIZATION
+-
+-By default, the mail gateway will accept mail from anyone. However,
+-there are situations in which you will want to authenticate users
+-before allowing them to communicate with the system. You can do this
+-via a plug-in mechanism in the RT configuration.
+-
+-You can set the array C<@MailPlugins> to be a list of plugins. The
+-default plugin, if this is not given, is C<Auth::MailFrom> - that is,
+-authentication of the person is done based on the C<From> header of the
+-email. If you have additional filters or authentication mechanisms, you
+-can list them here and they will be called in order:
+-
+- Set( @MailPlugins =>
+- "Filter::SpamAssassin",
+- "Auth::LDAP",
+- # ...
+- );
+-
+-See the documentation for any additional plugins you have.
+-
+-You may also put Perl subroutines into the C<@MailPlugins> array, if
+-they behave as described below.
+-
+-=head1 WRITING PLUGINS
+-
+-What's actually going on in the above is that C<@MailPlugins> is a
+-list of Perl modules; RT prepends C<RT::Interface::Email::> to the name,
+-to form a package name, and then C<use>'s this module. The module is
+-expected to provide a C<GetCurrentUser> subroutine, which takes a hash of
+-several parameters:
+-
+-=over 4
+-
+-=item Message
+-
+-A C<MIME::Entity> object representing the email
+-
+-=item CurrentUser
+-
+-An C<RT::CurrentUser> object
+-
+-=item AuthStat
+-
+-The authentication level returned from the previous plugin.
+-
+-=item Ticket [OPTIONAL]
+-
+-The ticket under discussion
+-
+-=item Queue [OPTIONAL]
+-
+-If we don't already have a ticket id, we need to know which queue we're talking about
+-
+-=item Action
+-
+-The action being performed. At the moment, it's one of "comment" or "correspond"
+-
+-=back
+-
+-It returns two values, the new C<RT::CurrentUser> object, and the new
+-authentication level. The authentication level can be zero, not allowed
+-to communicate with RT at all, (a "permission denied" error is mailed to
+-the correspondent) or one, which is the normal mode of operation.
+-Additionally, if C<-1> is returned, then the processing of the plug-ins
+-stops immediately and the message is ignored.
+-
+-=head1 ENVIRONMENT
+-
+-=over 4
+-
+-=item EXTENSION
+-
+-Some MTAs will route mail sent to user-foo at host or user+foo at host to user at host
+-and present "foo" in the environment variable C<EXTENSION>. Mailgate adds value
+-of this variable to message in the C<X-RT-Mail-Extension> field of the message
+-header.
+-
+-See also C<--extension> option. Note that value of the environment variable is
+-always added to the message header when it's not empty even if C<--extension>
+-option is not provided.
+-
+-=back
+-
+-=cut
+-
+diff --git a/etc/RT_Config.pm b/etc/RT_Config.pm
+deleted file mode 100644
+index 59eca8d..0000000
+--- a/etc/RT_Config.pm
++++ /dev/null
+@@ -1,2772 +0,0 @@
+-#
+-# RT was configured with:
+-#
+-# $ ./configure --with-db-type=SQLite --enable-layout=relative --with-web-handler=standalone
+-#
+-
+-package RT;
+-
+-############################# WARNING #############################
+-# #
+-# NEVER EDIT RT_Config.pm ! #
+-# #
+-# Instead, copy any sections you want to change to #
+-# RT_SiteConfig.pm and edit them there. Otherwise, #
+-# your changes will be lost when you upgrade RT. #
+-# #
+-############################# WARNING #############################
+-
+-=head1 NAME
+-
+-RT::Config
+-
+-=head1 Base configuration
+-
+-=over 4
+-
+-=item C<$rtname>
+-
+-C<$rtname> is the string that RT will look for in mail messages to
+-figure out what ticket a new piece of mail belongs to.
+-
+-Your domain name is recommended, so as not to pollute the namespace.
+-Once you start using a given tag, you should probably never change it;
+-otherwise, mail for existing tickets won't get put in the right place.
+-
+-=cut
+-
+-Set($rtname, "example.com");
+-
+-=item C<$Organization>
+-
+-You should set this to your organization's DNS domain. For example,
+-I<fsck.com> or I<asylum.arkham.ma.us>. It is used by the linking
+-interface to guarantee that ticket URIs are unique and easy to
+-construct. Changing it after you have created tickets in the system
+-will B<break> all existing ticket links!
+-
+-=cut
+-
+-Set($Organization, "example.com");
+-
+-=item C<$CorrespondAddress>, C<$CommentAddress>
+-
+-RT is designed such that any mail which already has a ticket-id
+-associated with it will get to the right place automatically.
+-
+-C<$CorrespondAddress> and C<$CommentAddress> are the default addresses
+-that will be listed in From: and Reply-To: headers of correspondence
+-and comment mail tracked by RT, unless overridden by a queue-specific
+-address. They should be set to email addresses which have been
+-configured as aliases for F<rt-mailgate>.
+-
+-=cut
+-
+-Set($CorrespondAddress, '');
+-
+-Set($CommentAddress, '');
+-
+-=item C<$WebDomain>
+-
+-Domain name of the RT server, e.g. 'www.example.com'. It should not
+-contain anything except the server name.
+-
+-=cut
+-
+-Set($WebDomain, "localhost");
+-
+-=item C<$WebPort>
+-
+-If we're running as a superuser, run on port 80. Otherwise, pick a
+-high port for this user.
+-
+-443 is default port for https protocol.
+-
+-=cut
+-
+-Set($WebPort, 80);
+-
+-=item C<$WebPath>
+-
+-If you're putting the web UI somewhere other than at the root of your
+-server, you should set C<$WebPath> to the path you'll be serving RT
+-at.
+-
+-C<$WebPath> requires a leading / but no trailing /, or it can be
+-blank.
+-
+-In most cases, you should leave C<$WebPath> set to "" (an empty
+-value).
+-
+-=cut
+-
+-Set($WebPath, "");
+-
+-=item C<$Timezone>
+-
+-C<$Timezone> is the default timezone, used to convert times entered by
+-users into GMT, as they are stored in the database, and back again;
+-users can override this. It should be set to a timezone recognized by
+-your server.
+-
+-=cut
+-
+-Set($Timezone, "US/Eastern");
+-
+-=item C<@Plugins>
+-
+-Set C<@Plugins> to a list of external RT plugins that should be
+-enabled (those plugins have to be previously downloaded and
+-installed).
+-
+-Example:
+-
+-C<Set(@Plugins, (qw(RT::Extension::SLA RT::Authen::ExternalAuth)));>
+-
+-=cut
+-
+-Set(@Plugins, ());
+-
+-=back
+-
+-
+-
+-
+-=head1 Database connection
+-
+-=over 4
+-
+-=item C<$DatabaseType>
+-
+-Database driver being used; case matters. Valid types are "mysql",
+-"Oracle" and "Pg".
+-
+-=cut
+-
+-Set($DatabaseType, "SQLite");
+-
+-=item C<$DatabaseHost>, C<$DatabaseRTHost>
+-
+-The domain name of your database server. If you're running MySQL and
+-on localhost, leave it blank for enhanced performance.
+-
+-C<DatabaseRTHost> is the fully-qualified hostname of your RT server,
+-for use in granting ACL rights on MySQL.
+-
+-=cut
+-
+-Set($DatabaseHost, "localhost");
+-Set($DatabaseRTHost, "localhost");
+-
+-=item C<$DatabasePort>
+-
+-The port that your database server is running on. Ignored unless it's
+-a positive integer. It's usually safe to leave this blank; RT will
+-choose the correct default.
+-
+-=cut
+-
+-Set($DatabasePort, "");
+-
+-=item C<$DatabaseUser>
+-
+-The name of the user to connect to the database as.
+-
+-=cut
+-
+-Set($DatabaseUser, "rt_user");
+-
+-=item C<$DatabasePassword>
+-
+-The password the C<$DatabaseUser> should use to access the database.
+-
+-=cut
+-
+-Set($DatabasePassword, q{rt_pass});
+-
+-=item C<$DatabaseName>
+-
+-The name of the RT database on your database server. For Oracle, the
+-SID and database objects are created in C<$DatabaseUser>'s schema.
+-
+-=cut
+-
+-Set($DatabaseName, q{rt4});
+-
+-=item C<$DatabaseRequireSSL>
+-
+-If you're using PostgreSQL and have compiled in SSL support, set
+-C<$DatabaseRequireSSL> to 1 to turn on SSL communication with the
+-database.
+-
+-=cut
+-
+-Set($DatabaseRequireSSL, undef);
+-
+-=back
+-
+-
+-
+-
+-=head1 Logging
+-
+-The default is to log anything except debugging information to syslog.
+-Check the L<Log::Dispatch> POD for information about how to get things
+-by syslog, mail or anything else, get debugging info in the log, etc.
+-
+-It might generally make sense to send error and higher by email to
+-some administrator. If you do this, be careful that this email isn't
+-sent to this RT instance. Mail loops will generate a critical log
+-message.
+-
+-=over 4
+-
+-=item C<$LogToSyslog>, C<$LogToScreen>
+-
+-The minimum level error that will be logged to the specific device.
+-From lowest to highest priority, the levels are:
+-
+- debug info notice warning error critical alert emergency
+-
+-Many syslogds are configured to discard or file debug messages away, so
+-if you're attempting to debug RT you may need to reconfigure your
+-syslogd or use one of the other logging options.
+-
+-Logging to your screen affects scripts run from the command line as well
+-as the STDERR sent to your webserver (so these logs will usually show up
+-in your web server's error logs).
+-
+-=cut
+-
+-Set($LogToSyslog, "info");
+-Set($LogToScreen, "info");
+-
+-=item C<$LogToFile>, C<$LogDir>, C<$LogToFileNamed>
+-
+-Logging to a standalone file is also possible. The file needs to both
+-exist and be writable by all direct users of the RT API. This generally
+-includes the web server and whoever rt-crontool runs as. Note that
+-rt-mailgate and the RT CLI go through the webserver, so their users do
+-not need to have write permissions to this file. If you expect to have
+-multiple users of the direct API, Best Practical recommends using syslog
+-instead of direct file logging.
+-
+-You should set C<$LogToFile> to one of the levels documented above.
+-
+-=cut
+-
+-Set($LogToFile, undef);
+-Set($LogDir, q{var/log});
+-Set($LogToFileNamed, "rt.log"); #log to rt.log
+-
+-=item C<$LogStackTraces>
+-
+-If set to a log level then logging will include stack traces for
+-messages with level equal to or greater than specified.
+-
+-NOTICE: Stack traces include parameters supplied to functions or
+-methods. It is possible for stack trace logging to reveal sensitive
+-information such as passwords or ticket content in your logs.
+-
+-=cut
+-
+-Set($LogStackTraces, "");
+-
+-=item C<@LogToSyslogConf>
+-
+-On Solaris or UnixWare, set to ( socket => 'inet' ). Options here
+-override any other options RT passes to L<Log::Dispatch::Syslog>.
+-Other interesting flags include facility and logopt. (See the
+-L<Log::Dispatch::Syslog> documentation for more information.) (Maybe
+-ident too, if you have multiple RT installations.)
+-
+-=cut
+-
+-Set(@LogToSyslogConf, ());
+-
+-=back
+-
+-
+-
+-=head1 Incoming mail gateway
+-
+-=over 4
+-
+-=item C<$EmailSubjectTagRegex>
+-
+-This regexp controls what subject tags RT recognizes as its own. If
+-you're not dealing with historical C<$rtname> values, or historical
+-queue-specific subject tags, you'll likely never have to change this
+-configuration.
+-
+-Be B<very careful> with it. Note that it overrides C<$rtname> for
+-subject token matching and that you should use only "non-capturing"
+-parenthesis grouping. For example:
+-
+-C<Set($EmailSubjectTagRegex, qr/(?:example.com|example.org)/i );>
+-
+-and NOT
+-
+-C<Set($EmailSubjectTagRegex, qr/(example.com|example.org)/i );>
+-
+-The setting below would make RT behave exactly as it does without the
+-setting enabled.
+-
+-=cut
+-
+-# Set($EmailSubjectTagRegex, qr/\Q$rtname\E/i );
+-
+-=item C<$OwnerEmail>
+-
+-C<$OwnerEmail> is the address of a human who manages RT. RT will send
+-errors generated by the mail gateway to this address. This address
+-should I<not> be an address that's managed by your RT instance.
+-
+-=cut
+-
+-Set($OwnerEmail, 'root');
+-
+-=item C<$LoopsToRTOwner>
+-
+-If C<$LoopsToRTOwner> is defined, RT will send mail that it believes
+-might be a loop to C<$OwnerEmail>.
+-
+-=cut
+-
+-Set($LoopsToRTOwner, 1);
+-
+-=item C<$StoreLoops>
+-
+-If C<$StoreLoops> is defined, RT will record messages that it believes
+-to be part of mail loops. As it does this, it will try to be careful
+-not to send mail to the sender of these messages.
+-
+-=cut
+-
+-Set($StoreLoops, undef);
+-
+-=item C<$MaxAttachmentSize>
+-
+-C<$MaxAttachmentSize> sets the maximum size (in bytes) of attachments
+-stored in the database. This setting is irrelevant unless one of
+-$TruncateLongAttachments or $DropLongAttachments (below) are set.
+-
+-=cut
+-
+-Set($MaxAttachmentSize, 10_000_000);
+-
+-=item C<$TruncateLongAttachments>
+-
+-If this is set to a non-undef value, RT will truncate attachments
+-longer than C<$MaxAttachmentSize>.
+-
+-=cut
+-
+-Set($TruncateLongAttachments, undef);
+-
+-=item C<$DropLongAttachments>
+-
+-If this is set to a non-undef value, RT will silently drop attachments
+-longer than C<MaxAttachmentSize>. C<$TruncateLongAttachments>, above,
+-takes priority over this.
+-
+-=cut
+-
+-Set($DropLongAttachments, undef);
+-
+-=item C<$RTAddressRegexp>
+-
+-C<$RTAddressRegexp> is used to make sure RT doesn't add itself as a
+-ticket CC if C<$ParseNewMessageForTicketCcs>, above, is enabled. It
+-is important that you set this to a regular expression that matches
+-all addresses used by your RT. This lets RT avoid sending mail to
+-itself. It will also hide RT addresses from the list of "One-time Cc"
+-and Bcc lists on ticket reply.
+-
+-If you have a number of addresses configured in your RT database
+-already, you can generate a naive first pass regexp by using:
+-
+- perl etc/upgrade/generate-rtaddressregexp
+-
+-If left blank, RT will compare each address to your configured
+-C<$CorrespondAddress> and C<$CommentAddress> before searching for a
+-Queue configured with a matching "Reply Address" or "Comment Address"
+-on the Queue Admin page.
+-
+-=cut
+-
+-Set($RTAddressRegexp, undef);
+-
+-=item C<$CanonicalizeEmailAddressMatch>, C<$CanonicalizeEmailAddressReplace>
+-
+-RT provides functionality which allows the system to rewrite incoming
+-email addresses. In its simplest form, you can substitute the value
+-in C<CanonicalizeEmailAddressReplace> for the value in
+-C<CanonicalizeEmailAddressMatch> (These values are passed to the
+-C<CanonicalizeEmailAddress> subroutine in F<RT/User.pm>)
+-
+-By default, that routine performs a C<s/$Match/$Replace/gi> on any
+-address passed to it.
+-
+-=cut
+-
+-# Set($CanonicalizeEmailAddressMatch, '@subdomain\.example\.com$');
+-# Set($CanonicalizeEmailAddressReplace, '@example.com');
+-
+-=item C<$CanonicalizeOnCreate>
+-
+-Set this to 1 and the create new user page will use the values that
+-you enter in the form but use the function CanonicalizeUserInfo in
+-F<RT/User_Local.pm>
+-
+-=cut
+-
+-Set($CanonicalizeOnCreate, 0);
+-
+-=item C<$ValidateUserEmailAddresses>
+-
+-If C<$ValidateUserEmailAddresses> is 1, RT will refuse to create
+-users with an invalid email address (as specified in RFC 2822) or with
+-an email address made of multiple email addresses.
+-
+-=cut
+-
+-Set($ValidateUserEmailAddresses, undef);
+-
+-=item C<@MailPlugins>
+-
+-C<@MailPlugins> is a list of authentication plugins for
+-L<RT::Interface::Email> to use; see L<rt-mailgate>
+-
+-=cut
+-
+-=item C<$UnsafeEmailCommands>
+-
+-C<$UnsafeEmailCommands>, if set to 1, enables 'take' and 'resolve'
+-as possible actions via the mail gateway. As its name implies, this
+-is very unsafe, as it allows email with a forged sender to possibly
+-resolve arbitrary tickets!
+-
+-=cut
+-
+-=item C<$ExtractSubjectTagMatch>, C<$ExtractSubjectTagNoMatch>
+-
+-The default "extract remote tracking tags" scrip settings; these
+-detect when your RT is talking to another RT, and adjust the subject
+-accordingly.
+-
+-=cut
+-
+-Set($ExtractSubjectTagMatch, qr/\[[^\]]+? #\d+\]/);
+-Set($ExtractSubjectTagNoMatch, ( ${RT::EmailSubjectTagRegex}
+- ? qr/\[(?:${RT::EmailSubjectTagRegex}) #\d+\]/
+- : qr/\[\Q$RT::rtname\E #\d+\]/));
+-
+-=item C<$CheckMoreMSMailHeaders>
+-
+-Some email clients create a plain text version of HTML-formatted
+-email to help other clients that read only plain text.
+-Unfortunately, the plain text parts sometimes end up with
+-doubled newlines and these can then end up in RT. This
+-is most often seen in MS Outlook.
+-
+-Enable this option to have RT check for additional mail headers
+-and attempt to identify email from MS Outlook. When detected,
+-RT will then clean up double newlines. Note that it may
+-clean up intentional double newlines as well.
+-
+-=cut
+-
+-Set( $CheckMoreMSMailHeaders, 0);
+-
+-=back
+-
+-
+-
+-=head1 Outgoing mail
+-
+-=over 4
+-
+-=item C<$MailCommand>
+-
+-C<$MailCommand> defines which method RT will use to try to send mail.
+-We know that 'sendmailpipe' works fairly well. If 'sendmailpipe'
+-doesn't work well for you, try 'sendmail'. Other options are 'smtp'
+-or 'qmail'.
+-
+-Note that you should remove the '-t' from C<$SendmailArguments> if you
+-use 'sendmail' rather than 'sendmailpipe'
+-
+-For testing purposes, or to simply disable sending mail out into the
+-world, you can set C<$MailCommand> to 'testfile' which writes all mail
+-to a temporary file. RT will log the location of the temporary file
+-so you can extract mail from it afterward.
+-
+-On shutdown, RT will clean up the temporary file created when using
+-the 'testfile' option. If testing while the RT server is still running,
+-you can find the files in the location noted in the log file. If you run
+-a tool like C<rt-crontool> however, or if you look after stopping the server,
+-the files will have been deleted when the process completed. If you need to
+-keep the files for development or debugging, you can manually set
+-C<< UNLINK => 0 >> where the testfile config is processed in
+-F<lib/RT/Interface/Email.pm>.
+-
+-=cut
+-
+-Set($MailCommand, "sendmailpipe");
+-
+-=item C<$SetOutgoingMailFrom>
+-
+-C<$SetOutgoingMailFrom> tells RT to set the sender envelope to the
+-Correspond mail address of the ticket's queue.
+-
+-Warning: If you use this setting, bounced mails will appear to be
+-incoming mail to the system, thus creating new tickets.
+-
+-If the value contains an C<@>, it is assumed to be an email address and used as
+-a global envelope sender. Expected usage in this case is to simply set the
+-same envelope sender on all mail from RT, without defining
+-C<$OverrideOutgoingMailFrom>. If you do define C<$OverrideOutgoingMailFrom>,
+-anything specified there overrides the global value (including Default).
+-
+-This option only works if C<$MailCommand> is set to 'sendmailpipe'.
+-
+-=cut
+-
+-Set($SetOutgoingMailFrom, 0);
+-
+-=item C<$OverrideOutgoingMailFrom>
+-
+-C<$OverrideOutgoingMailFrom> is used for overwriting the Correspond
+-address of the queue as it is handed to sendmail -f. This helps force
+-the From_ header away from www-data or other email addresses that show
+-up in the "Sent by" line in Outlook.
+-
+-The option is a hash reference of queue name to email address. If
+-there is no ticket involved, then the value of the C<Default> key will
+-be used.
+-
+-This option only works if C<$SetOutgoingMailFrom> is enabled and
+-C<$MailCommand> is set to 'sendmailpipe'.
+-
+-=cut
+-
+-Set($OverrideOutgoingMailFrom, {
+-# 'Default' => 'admin at rt.example.com',
+-# 'General' => 'general at rt.example.com',
+-});
+-
+-=item C<$DefaultMailPrecedence>
+-
+-C<$DefaultMailPrecedence> is used to control the default Precedence
+-level of outgoing mail where none is specified. By default it is
+-C<bulk>, but if you only send mail to your staff, you may wish to
+-change it.
+-
+-Note that you can set the precedence of individual templates by
+-including an explicit Precedence header.
+-
+-If you set this value to C<undef> then we do not set a default
+-Precedence header to outgoing mail. However, if there already is a
+-Precedence header, it will be preserved.
+-
+-=cut
+-
+-Set($DefaultMailPrecedence, "bulk");
+-
+-=item C<$DefaultErrorMailPrecedence>
+-
+-C<$DefaultErrorMailPrecedence> is used to control the default
+-Precedence level of outgoing mail that indicates some kind of error
+-condition. By default it is C<bulk>, but if you only send mail to your
+-staff, you may wish to change it.
+-
+-If you set this value to C<undef> then we do not add a Precedence
+-header to error mail.
+-
+-=cut
+-
+-Set($DefaultErrorMailPrecedence, "bulk");
+-
+-=item C<$UseOriginatorHeader>
+-
+-C<$UseOriginatorHeader> is used to control the insertion of an
+-RT-Originator Header in every outgoing mail, containing the mail
+-address of the transaction creator.
+-
+-=cut
+-
+-Set($UseOriginatorHeader, 1);
+-
+-=item C<$UseFriendlyFromLine>
+-
+-By default, RT sets the outgoing mail's "From:" header to "SenderName
+-via RT". Setting C<$UseFriendlyFromLine> to 0 disables it.
+-
+-=cut
+-
+-Set($UseFriendlyFromLine, 1);
+-
+-=item C<$FriendlyFromLineFormat>
+-
+-C<sprintf()> format of the friendly 'From:' header; its arguments are
+-SenderName and SenderEmailAddress.
+-
+-=cut
+-
+-Set($FriendlyFromLineFormat, "\"%s via RT\" <%s>");
+-
+-=item C<$UseFriendlyToLine>
+-
+-RT can optionally set a "Friendly" 'To:' header when sending messages
+-to Ccs or AdminCcs (rather than having a blank 'To:' header.
+-
+-This feature DOES NOT WORK WITH SENDMAIL[tm] BRAND SENDMAIL. If you
+-are using sendmail, rather than postfix, qmail, exim or some other
+-MTA, you _must_ disable this option.
+-
+-=cut
+-
+-Set($UseFriendlyToLine, 0);
+-
+-=item C<$FriendlyToLineFormat>
+-
+-C<sprintf()> format of the friendly 'To:' header; its arguments are
+-WatcherType and TicketId.
+-
+-=cut
+-
+-Set($FriendlyToLineFormat, "\"%s of ". RT->Config->Get('rtname') ." Ticket #%s\":;");
+-
+-=item C<$NotifyActor>
+-
+-By default, RT doesn't notify the person who performs an update, as
+-they already know what they've done. If you'd like to change this
+-behavior, Set C<$NotifyActor> to 1
+-
+-=cut
+-
+-Set($NotifyActor, 0);
+-
+-=item C<$RecordOutgoingEmail>
+-
+-By default, RT records each message it sends out to its own internal
+-database. To change this behavior, set C<$RecordOutgoingEmail> to 0
+-
+-If this is disabled, users' digest mail delivery preferences
+-(i.e. EmailFrequency) will also be ignored.
+-
+-=cut
+-
+-Set($RecordOutgoingEmail, 1);
+-
+-=item C<$VERPPrefix>, C<$VERPDomain>
+-
+-Setting these options enables VERP support
+-L<http://cr.yp.to/proto/verp.txt>.
+-
+-Uncomment the following two directives to generate envelope senders
+-of the form C<${VERPPrefix}${originaladdress}@${VERPDomain}>
+-(i.e. rt-jesse=fsck.com at rt.example.com ).
+-
+-This currently only works with sendmail and sendmailpipe.
+-
+-=cut
+-
+-# Set($VERPPrefix, "rt-");
+-# Set($VERPDomain, $RT::Organization);
+-
+-
+-=item C<$ForwardFromUser>
+-
+-By default, RT forwards a message using queue's address and adds RT's
+-tag into subject of the outgoing message, so recipients' replies go
+-into RT as correspondents.
+-
+-To change this behavior, set C<$ForwardFromUser> to 1 and RT
+-will use the address of the current user and remove RT's subject tag.
+-
+-=cut
+-
+-Set($ForwardFromUser, 0);
+-
+-=back
+-
+-=head2 Email dashboards
+-
+-=over 4
+-
+-=item C<$DashboardAddress>
+-
+-The email address from which RT will send dashboards. If none is set,
+-then C<$OwnerEmail> will be used.
+-
+-=cut
+-
+-Set($DashboardAddress, '');
+-
+-=item C<$DashboardSubject>
+-
+-Lets you set the subject of dashboards. Arguments are the frequency (Daily,
+-Weekly, Monthly) of the dashboard and the dashboard's name.
+-
+-=cut
+-
+-Set($DashboardSubject, "%s Dashboard: %s");
+-
+-=item C<@EmailDashboardRemove>
+-
+-A list of regular expressions that will be used to remove content from
+-mailed dashboards.
+-
+-=cut
+-
+-Set(@EmailDashboardRemove, ());
+-
+-=back
+-
+-
+-
+-=head2 Sendmail configuration
+-
+-These options only take effect if C<$MailCommand> is 'sendmail' or
+-'sendmailpipe'
+-
+-=over 4
+-
+-=item C<$SendmailArguments>
+-
+-C<$SendmailArguments> defines what flags to pass to C<$SendmailPath>
+-If you picked 'sendmailpipe', you MUST add a -t flag to
+-C<$SendmailArguments> These options are good for most sendmail
+-wrappers and work-a-likes.
+-
+-These arguments are good for sendmail brand sendmail 8 and newer:
+-C<Set($SendmailArguments,"-oi -t -ODeliveryMode=b -OErrorMode=m");>
+-
+-=cut
+-
+-Set($SendmailArguments, "-oi -t");
+-
+-
+-=item C<$SendmailBounceArguments>
+-
+-C<$SendmailBounceArguments> defines what flags to pass to C<$Sendmail>
+-assuming RT needs to send an error (i.e. bounce).
+-
+-=cut
+-
+-Set($SendmailBounceArguments, '-f "<>"');
+-
+-=item C<$SendmailPath>
+-
+-If you selected 'sendmailpipe' above, you MUST specify the path to
+-your sendmail binary in C<$SendmailPath>.
+-
+-=cut
+-
+-Set($SendmailPath, "/usr/sbin/sendmail");
+-
+-
+-=back
+-
+-=head2 SMTP configuration
+-
+-These options only take effect if C<$MailCommand> is 'smtp'
+-
+-=over 4
+-
+-=item C<$SMTPServer>
+-
+-C<$SMTPServer> should be set to the hostname of the SMTP server to use
+-
+-=cut
+-
+-Set($SMTPServer, undef);
+-
+-=item C<$SMTPFrom>
+-
+-C<$SMTPFrom> should be set to the 'From' address to use, if not the
+-email's 'From'
+-
+-=cut
+-
+-Set($SMTPFrom, undef);
+-
+-=item C<$SMTPDebug>
+-
+-C<$SMTPDebug> should be set to 1 to debug SMTP mail sending
+-
+-=cut
+-
+-Set($SMTPDebug, 0);
+-
+-=back
+-
+-=head2 Other mailers
+-
+-=over 4
+-
+-=item C<@MailParams>
+-
+-C<@MailParams> defines a list of options passed to $MailCommand if it
+-is not 'sendmailpipe', 'sendmail', or 'smtp'
+-
+-=cut
+-
+-Set(@MailParams, ());
+-
+-=back
+-
+-
+-=head1 Web interface
+-
+-=over 4
+-
+-=item C<$WebDefaultStylesheet>
+-
+-This determines the default stylesheet the RT web interface will use.
+-RT ships with several themes by default:
+-
+- web2 The default layout for RT 3.8
+- aileron The default layout for RT 4.0
+- ballard Theme which doesn't rely on JavaScript for menuing
+-
+-This value actually specifies a directory in F<share/html/NoAuth/css/>
+-from which RT will try to load the file main.css (which should @import
+-any other files the stylesheet needs). This allows you to easily and
+-cleanly create your own stylesheets to apply to RT. This option can
+-be overridden by users in their preferences.
+-
+-=cut
+-
+-Set($WebDefaultStylesheet, "aileron");
+-
+-=item C<$DefaultQueue>
+-
+-Use this to select the default queue name that will be used for
+-creating new tickets. You may use either the queue's name or its
+-ID. This only affects the queue selection boxes on the web interface.
+-
+-=cut
+-
+-# Set($DefaultQueue, "General");
+-
+-=item C<$RememberDefaultQueue>
+-
+-When a queue is selected in the new ticket dropdown, make it the new
+-default for the new ticket dropdown.
+-
+-=cut
+-
+-# Set($RememberDefaultQueue, 1);
+-
+-=item C<$EnableReminders>
+-
+-Hide all links and portlets related to Reminders by setting this to 0
+-
+-=cut
+-
+-Set($EnableReminders, 1);
+-
+-=item C<@CustomFieldValuesSources>
+-
+-Set C<@CustomFieldValuesSources> to a list of class names which extend
+-L<RT::CustomFieldValues::External>. This can be used to pull lists of
+-custom field values from external sources at runtime.
+-
+-=cut
+-
+-Set(@CustomFieldValuesSources, ());
+-
+-=item C<$CanonicalizeRedirectURLs>
+-
+-Set C<$CanonicalizeRedirectURLs> to 1 to use C<$WebURL> when
+-redirecting rather than the one we get from C<%ENV>.
+-
+-Apache's UseCanonicalName directive changes the hostname that RT
+-finds in C<%ENV>. You can read more about what turning it On or Off
+-means in the documentation for your version of Apache.
+-
+-If you use RT behind a reverse proxy, you almost certainly want to
+-enable this option.
+-
+-=cut
+-
+-Set($CanonicalizeRedirectURLs, 0);
+-
+-=item C<@JSFiles>
+-
+-A list of JavaScript files to be included in head. Removing any of
+-the default entries is not suggested.
+-
+-If you're a plugin author, refer to RT->AddJavaScript.
+-
+-=cut
+-
+-Set(@JSFiles, qw/
+- jquery-1.4.2.min.js
+- jquery_noconflict.js
+- jquery-ui-1.8.4.custom.min.js
+- jquery-ui-timepicker-addon.js
+- jquery-ui-patch-datepicker.js
+- jquery.cookie.js
+- titlebox-state.js
+- util.js
+- userautocomplete.js
+- jquery.event.hover-1.0.js
+- superfish.js
+- supersubs.js
+- jquery.supposition.js
+- history-folding.js
+- late.js
+-/);
+-
+-=item C<$JSMinPath>
+-
+-Path to the jsmin binary; if specified, it will be used to minify
+-C<JSFiles>. The default, and the fallback if the binary cannot be
+-found, is to simply concatenate the files.
+-
+-jsmin can be installed by running 'make jsmin' from the RT install
+-directory, or from http://www.crockford.com/javascript/jsmin.html
+-
+-=cut
+-
+-# Set($JSMinPath, "/path/to/jsmin");
+-
+-=item C<@CSSFiles>
+-
+-A list of additional CSS files to be included in head.
+-
+-If you're a plugin author, refer to RT->AddStyleSheets.
+-
+-=cut
+-
+-Set(@CSSFiles, qw//);
+-
+-=item C<$UsernameFormat>
+-
+-This determines how user info is displayed. 'concise' will show one of
+-either NickName, RealName, Name or EmailAddress, depending on what
+-exists and whether the user is privileged or not. 'verbose' will show
+-RealName and EmailAddress.
+-
+-=cut
+-
+-Set($UsernameFormat, "concise");
+-
+-=item C<$WebBaseURL>, C<$WebURL>
+-
+-Usually you don't want to set these options. The only obvious reason
+-is if RT is accessible via https protocol on a non standard port, e.g.
+-'https://rt.example.com:9999'. In all other cases these options are
+-computed using C<$WebDomain>, C<$WebPort> and C<$WebPath>.
+-
+-C<$WebBaseURL> is the scheme, server and port
+-(e.g. 'http://rt.example.com') for constructing URLs to the web
+-UI. C<$WebBaseURL> doesn't need a trailing /.
+-
+-C<$WebURL> is the C<$WebBaseURL>, C<$WebPath> and trailing /, for
+-example: 'http://www.example.com/rt/'.
+-
+-=cut
+-
+-my $port = RT->Config->Get('WebPort');
+-Set($WebBaseURL,
+- ($port == 443? 'https': 'http') .'://'
+- . RT->Config->Get('WebDomain')
+- . ($port != 80 && $port != 443? ":$port" : '')
+-);
+-
+-Set($WebURL, RT->Config->Get('WebBaseURL') . RT->Config->Get('WebPath') . "/");
+-
+-=item C<$WebImagesURL>
+-
+-C<$WebImagesURL> points to the base URL where RT can find its images.
+-Define the directory name to be used for images in RT web documents.
+-
+-=cut
+-
+-Set($WebImagesURL, RT->Config->Get('WebPath') . "/NoAuth/images/");
+-
+-=item C<$LogoURL>
+-
+-C<$LogoURL> points to the URL of the RT logo displayed in the web UI.
+-This can also be configured via the web UI.
+-
+-=cut
+-
+-Set($LogoURL, RT->Config->Get('WebImagesURL') . "bpslogo.png");
+-
+-=item C<$LogoLinkURL>
+-
+-C<$LogoLinkURL> is the URL that the RT logo hyperlinks to.
+-
+-=cut
+-
+-Set($LogoLinkURL, "http://bestpractical.com");
+-
+-=item C<$LogoAltText>
+-
+-C<$LogoAltText> is a string of text for the alt-text of the logo. It
+-will be passed through C<loc> for localization.
+-
+-=cut
+-
+-Set($LogoAltText, "Best Practical Solutions, LLC corporate logo");
+-
+-=item C<$LogoImageHeight>
+-
+-C<$LogoImageHeight> is the value of the C<height> attribute of the logo
+-C<img> tag.
+-
+-=cut
+-
+-Set($LogoImageHeight, 38);
+-
+-=item C<$LogoImageWidth>
+-
+-C<$LogoImageWidth> is the value of the C<width> attribute of the logo
+-C<img> tag.
+-
+-=cut
+-
+-Set($LogoImageWidth, 181);
+-
+-=item C<$WebNoAuthRegex>
+-
+-What portion of RT's URL space should not require authentication. The
+-default is almost certainly correct, and should only be changed if you
+-are extending RT.
+-
+-=cut
+-
+-Set($WebNoAuthRegex, qr{^ (?:/+NoAuth/ | /+REST/\d+\.\d+/NoAuth/) }x );
+-
+-=item C<$SelfServiceRegex>
+-
+-What portion of RT's URLspace should be accessible to Unprivileged
+-users This does not override the redirect from F</Ticket/Display.html>
+-to F</SelfService/Display.html> when Unprivileged users attempt to
+-access ticked displays.
+-
+-=cut
+-
+-Set($SelfServiceRegex, qr!^(?:/+SelfService/)!x );
+-
+-=item C<$WebFlushDbCacheEveryRequest>
+-
+-By default, RT clears its database cache after every page view. This
+-ensures that you've always got the most current information when
+-working in a multi-process (mod_perl or FastCGI) Environment. Setting
+-C<$WebFlushDbCacheEveryRequest> to 0 will turn this off, which will
+-speed RT up a bit, at the expense of a tiny bit of data accuracy.
+-
+-=cut
+-
+-Set($WebFlushDbCacheEveryRequest, 1);
+-
+-=item C<%ChartFont>
+-
+-The L<GD> module (which RT uses for graphs) ships with a built-in font
+-that doesn't have full Unicode support. You can use a given TrueType
+-font for a specific language by setting %ChartFont to (language =E<gt>
+-the absolute path of a font) pairs. Your GD library must have support
+-for TrueType fonts to use this option. If there is no entry for a
+-language in the hash then font with 'others' key is used.
+-
+-RT comes with two TrueType fonts covering most available languages.
+-
+-=cut
+-
+-Set(
+- %ChartFont,
+- 'zh-cn' => "$RT::BasePath/share/fonts/DroidSansFallback.ttf",
+- 'zh-tw' => "$RT::BasePath/share/fonts/DroidSansFallback.ttf",
+- 'ja' => "$RT::BasePath/share/fonts/DroidSansFallback.ttf",
+- 'others' => "$RT::BasePath/share/fonts/DroidSans.ttf",
+-);
+-
+-=item C<$ChartsTimezonesInDB>
+-
+-RT stores dates using the UTC timezone in the DB, so charts grouped by
+-dates and time are not representative. Set C<$ChartsTimezonesInDB> to 1
+-to enable timezone conversions using your DB's capabilities. You may
+-need to do some work on the DB side to use this feature, read more in
+-F<docs/customizing/timezones_in_charts.pod>.
+-
+-At this time, this feature only applies to MySQL and PostgreSQL.
+-
+-=cut
+-
+-Set($ChartsTimezonesInDB, 0);
+-
+-=back
+-
+-
+-
+-=head2 Home page
+-
+-=over 4
+-
+-=item C<$DefaultSummaryRows>
+-
+-C<$DefaultSummaryRows> is default number of rows displayed in for
+-search results on the front page.
+-
+-=cut
+-
+-Set($DefaultSummaryRows, 10);
+-
+-=item C<$HomePageRefreshInterval>
+-
+-C<$HomePageRefreshInterval> is default number of seconds to refresh
+-the RT home page. Choose from [0, 120, 300, 600, 1200, 3600, 7200].
+-
+-=cut
+-
+-Set($HomePageRefreshInterval, 0);
+-
+-=item C<$HomepageComponents>
+-
+-C<$HomepageComponents> is an arrayref of allowed components on a
+-user's customized homepage ("RT at a glance").
+-
+-=cut
+-
+-Set(
+- $HomepageComponents,
+- [
+- qw(QuickCreate Quicksearch MyAdminQueues MySupportQueues MyReminders RefreshHomepage Dashboards SavedSearches) # loc_qw
+- ]
+-);
+-
+-=back
+-
+-
+-
+-
+-=head2 Ticket search
+-
+-=over 4
+-
+-=item C<$UseSQLForACLChecks>
+-
+-Historically, ACLs were checked on display, which could lead to empty
+-search pages and wrong ticket counts. Set C<$UseSQLForACLChecks> to 1
+-to limit search results in SQL instead, which eliminates these
+-problems.
+-
+-This option is still relatively new; it may result in performance
+-problems in some cases, or significant speedups in others.
+-
+-=cut
+-
+-Set($UseSQLForACLChecks, undef);
+-
+-=item C<$TicketsItemMapSize>
+-
+-On the display page of a ticket from search results, RT provides links
+-to the first, next, previous and last ticket from the results. In
+-order to build these links, RT needs to fetch the full result set from
+-the database, which can be resource-intensive.
+-
+-Set C<$TicketsItemMapSize> to number of tickets you want RT to examine
+-to build these links. If the full result set is larger than this
+-number, RT will omit the "last" link in the menu. Set this to zero to
+-always examine all results.
+-
+-=cut
+-
+-Set($TicketsItemMapSize, 1000);
+-
+-=item C<$SearchResultsRefreshInterval>
+-
+-C<$SearchResultsRefreshInterval> is default number of seconds to
+-refresh search results in RT. Choose from [0, 120, 300, 600, 1200,
+-3600, 7200].
+-
+-=cut
+-
+-Set($SearchResultsRefreshInterval, 0);
+-
+-=item C<$DefaultSearchResultFormat>
+-
+-C<$DefaultSearchResultFormat> is the default format for RT search
+-results
+-
+-=cut
+-
+-Set ($DefaultSearchResultFormat, qq{
+- '<B><A HREF="__WebPath__/Ticket/Display.html?id=__id__">__id__</a></B>/TITLE:#',
+- '<B><A HREF="__WebPath__/Ticket/Display.html?id=__id__">__Subject__</a></B>/TITLE:Subject',
+- Status,
+- QueueName,
+- OwnerName,
+- Priority,
+- '__NEWLINE__',
+- '__NBSP__',
+- '<small>__Requestors__</small>',
+- '<small>__CreatedRelative__</small>',
+- '<small>__ToldRelative__</small>',
+- '<small>__LastUpdatedRelative__</small>',
+- '<small>__TimeLeft__</small>'});
+-
+-=item C<$DefaultSelfServiceSearchResultFormat>
+-
+-C<$DefaultSelfServiceSearchResultFormat> is the default format of
+-searches displayed in the SelfService interface.
+-
+-=cut
+-
+-Set($DefaultSelfServiceSearchResultFormat, qq{
+- '<B><A HREF="__WebPath__/SelfService/Display.html?id=__id__">__id__</a></B>/TITLE:#',
+- '<B><A HREF="__WebPath__/SelfService/Display.html?id=__id__">__Subject__</a></B>/TITLE:Subject',
+- Status,
+- Requestors,
+- OwnerName});
+-
+-=item C<%FullTextSearch>
+-
+-Full text search (FTS) without database indexing is a very slow
+-operation, and is thus disabled by default.
+-
+-Before setting C<Indexed> to 1, read F<docs/full_text_indexing.pod> for
+-the full details of FTS on your particular database.
+-
+-It is possible to enable FTS without database indexing support, simply
+-by setting the C<Enable> key to 1, while leaving C<Indexed> set to 0.
+-This is not generally suggested, as unindexed full-text searching can
+-cause severe performance problems.
+-
+-=cut
+-
+-Set(%FullTextSearch,
+- Enable => 0,
+- Indexed => 0,
+-);
+-
+-=item C<$DontSearchFileAttachments>
+-
+-If C<$DontSearchFileAttachments> is set to 1, then uploaded files
+-(attachments with file names) are not searched during content
+-search.
+-
+-Note that if you use indexed FTS then named attachments are still
+-indexed by default regardless of this option.
+-
+-=cut
+-
+-Set($DontSearchFileAttachments, undef);
+-
+-=item C<$OnlySearchActiveTicketsInSimpleSearch>
+-
+-When query in simple search doesn't have status info, use this to only
+-search active ones.
+-
+-=cut
+-
+-Set($OnlySearchActiveTicketsInSimpleSearch, 1);
+-
+-=item C<$SearchResultsAutoRedirect>
+-
+-When only one ticket is found in search, use this to redirect to the
+-ticket display page automatically.
+-
+-=cut
+-
+-Set($SearchResultsAutoRedirect, 0);
+-
+-=back
+-
+-
+-
+-=head2 Ticket display
+-
+-=over 4
+-
+-=item C<$ShowMoreAboutPrivilegedUsers>
+-
+-This determines if the 'More about requestor' box on
+-Ticket/Display.html is shown for Privileged Users.
+-
+-=cut
+-
+-Set($ShowMoreAboutPrivilegedUsers, 0);
+-
+-=item C<$MoreAboutRequestorTicketList>
+-
+-This can be set to Active, Inactive, All or None. It controls what
+-ticket list will be displayed in the 'More about requestor' box on
+-Ticket/Display.html. This option can be controlled by users also.
+-
+-=cut
+-
+-Set($MoreAboutRequestorTicketList, "Active");
+-
+-=item C<$MoreAboutRequestorExtraInfo>
+-
+-By default, the 'More about requestor' box on Ticket/Display.html
+-shows the Requestor's name and ticket list. If you would like to see
+-extra information about the user, this expects a Format string of user
+-attributes. Please note that not all the attributes are supported in
+-this display because we're not building a table.
+-
+-Example:
+-C<Set($MoreAboutRequestorExtraInfo,"Organization, Address1")>
+-
+-=cut
+-
+-Set($MoreAboutRequestorExtraInfo, "");
+-
+-=item C<$MoreAboutRequestorGroupsLimit>
+-
+-By default, the 'More about requestor' box on Ticket/Display.html
+-shows all the groups of the Requestor. Use this to limit the number
+-of groups; a value of undef removes the group display entirely.
+-
+-=cut
+-
+-Set($MoreAboutRequestorGroupsLimit, 0);
+-
+-=item C<$UseSideBySideLayout>
+-
+-Should the ticket create and update forms use a more space efficient
+-two column layout. This layout may not work in narrow browsers if you
+-set a MessageBoxWidth (below).
+-
+-=cut
+-
+-Set($UseSideBySideLayout, 1);
+-
+-=item C<$EditCustomFieldsSingleColumn>
+-
+-When displaying a list of Ticket Custom Fields for editing, RT
+-defaults to a 2 column list. If you set this to 1, it will instead
+-display the Custom Fields in a single column.
+-
+-=cut
+-
+-Set($EditCustomFieldsSingleColumn, 0);
+-
+-=item C<$ShowUnreadMessageNotifications>
+-
+-If set to 1, RT will prompt users when there are new,
+-unread messages on tickets they are viewing.
+-
+-=cut
+-
+-Set($ShowUnreadMessageNotifications, 0);
+-
+-=item C<$AutocompleteOwners>
+-
+-If set to 1, the owner drop-downs for ticket update/modify and the query
+-builder are replaced by text fields that autocomplete. This can
+-alleviate the sometimes huge owner list for installations where many
+-users have the OwnTicket right.
+-
+-=cut
+-
+-Set($AutocompleteOwners, 0);
+-
+-=item C<$AutocompleteOwnersForSearch>
+-
+-If set to 1, the owner drop-downs for the query builder are always
+-replaced by text field that autocomplete and C<$AutocompleteOwners>
+-is ignored. Helpful when owners list is huge in the query builder.
+-
+-=cut
+-
+-Set($AutocompleteOwnersForSearch, 0);
+-
+-=item C<$UserAutocompleteFields>
+-
+-Specifies which fields of L<RT::User> to match against and how to
+-match each field when autocompleting users. Valid match methods are
+-LIKE, STARTSWITH, ENDSWITH, =, and !=.
+-
+-=cut
+-
+-Set($UserAutocompleteFields, {
+- EmailAddress => 'STARTSWITH',
+- Name => 'STARTSWITH',
+- RealName => 'LIKE',
+-});
+-
+-=item C<$AllowUserAutocompleteForUnprivileged>
+-
+-Should unprivileged users be allowed to autocomplete users. Setting
+-this option to 1 means unprivileged users will be able to search all
+-your users.
+-
+-=cut
+-
+-Set($AllowUserAutocompleteForUnprivileged, 0);
+-
+-=item C<$DisplayTicketAfterQuickCreate>
+-
+-Enable this to redirect to the created ticket display page
+-automatically when using QuickCreate.
+-
+-=cut
+-
+-Set($DisplayTicketAfterQuickCreate, 0);
+-
+-=item C<$WikiImplicitLinks>
+-
+-Support implicit links in WikiText custom fields? Setting this to 1
+-causes InterCapped or ALLCAPS words in WikiText fields to automatically
+-become links to searches for those words. If used on Articles, it links
+-to the Article with that name.
+-
+-=cut
+-
+-Set($WikiImplicitLinks, 0);
+-
+-=item C<$PreviewScripMessages>
+-
+-Set C<$PreviewScripMessages> to 1 if the scrips preview on the ticket
+-reply page should include the content of the messages to be sent.
+-
+-=cut
+-
+-Set($PreviewScripMessages, 0);
+-
+-=item C<$SimplifiedRecipients>
+-
+-If C<$SimplifiedRecipients> is set, a simple list of who will receive
+-B<any> kind of mail will be shown on the ticket reply page, instead of a
+-detailed breakdown by scrip.
+-
+-=cut
+-
+-Set($SimplifiedRecipients, 0);
+-
+-=item C<$HideResolveActionsWithDependencies>
+-
+-If set to 1, this option will skip ticket menu actions which can't be
+-completed successfully because of outstanding active Depends On tickets.
+-
+-By default, all ticket actions are displayed in the menu even if some of
+-them can't be successful until all Depends On links are resolved or
+-transitioned to another inactive status.
+-
+-=cut
+-
+-Set($HideResolveActionsWithDependencies, 0);
+-
+-=back
+-
+-
+-
+-=head2 Articles
+-
+-=over 4
+-
+-=item C<$ArticleOnTicketCreate>
+-
+-Set this to 1 to display the Articles interface on the Ticket Create
+-page in addition to the Reply/Comment page.
+-
+-=cut
+-
+-Set($ArticleOnTicketCreate, 0);
+-
+-=item C<$HideArticleSearchOnReplyCreate>
+-
+-Set this to 1 to hide the search and include boxes from the Article
+-UI. This assumes you have enabled Article Hotlist feature, otherwise
+-you will have no access to Articles.
+-
+-=cut
+-
+-Set($HideArticleSearchOnReplyCreate, 0);
+-
+-=back
+-
+-
+-
+-=head2 Message box properties
+-
+-=over 4
+-
+-=item C<$MessageBoxWidth>, C<$MessageBoxHeight>
+-
+-For message boxes, set the entry box width, height and what type of
+-wrapping to use. These options can be overridden by users in their
+-preferences.
+-
+-When the width is set to undef, no column count is specified and the
+-message box will take up 100% of the available width. Combining this
+-with HARD messagebox wrapping (below) is not recommended, as it will
+-lead to inconsistent width in transactions between browsers.
+-
+-These settings only apply to the non-RichText message box. See below
+-for Rich Text settings.
+-
+-=cut
+-
+-Set($MessageBoxWidth, undef);
+-Set($MessageBoxHeight, 15);
+-
+-=item C<$MessageBoxWrap>
+-
+-Wrapping is disabled when using MessageBoxRichText because of a bad
+-interaction between IE and wrapping with the Rich Text Editor.
+-
+-=cut
+-
+-Set($MessageBoxWrap, "SOFT");
+-
+-=item C<$MessageBoxRichText>
+-
+-Should "rich text" editing be enabled? This option lets your users
+-send HTML email messages from the web interface.
+-
+-=cut
+-
+-Set($MessageBoxRichText, 1);
+-
+-=item C<$MessageBoxRichTextHeight>
+-
+-Height of rich text JavaScript enabled editing boxes (in pixels)
+-
+-=cut
+-
+-Set($MessageBoxRichTextHeight, 200);
+-
+-=item C<$MessageBoxIncludeSignature>
+-
+-Should your users' signatures (from their Preferences page) be
+-included in Comments and Replies.
+-
+-=cut
+-
+-Set($MessageBoxIncludeSignature, 1);
+-
+-=item C<$MessageBoxIncludeSignatureOnComment>
+-
+-Should your users' signatures (from their Preferences page) be
+-included in Comments. Setting this to false overrides
+-C<$MessageBoxIncludeSignature>.
+-
+-=cut
+-
+-Set($MessageBoxIncludeSignatureOnComment, 1);
+-
+-=back
+-
+-
+-=head2 Transaction display
+-
+-=over 4
+-
+-=item C<$OldestTransactionsFirst>
+-
+-By default, RT shows newest transactions at the bottom of the ticket
+-history page, if you want see them at the top set this to 0. This
+-option can be overridden by users in their preferences.
+-
+-=cut
+-
+-Set($OldestTransactionsFirst, 1);
+-
+-=item C<$DeferTransactionLoading>
+-
+-When set, defers loading ticket history until the user clicks a link.
+-This should end up serving pages to users quicker, since generating
+-all the HTML for transaction history can be slow for long tickets.
+-
+-=cut
+-
+-# Set($DeferTransactionLoading, 1);
+-
+-=item C<$ShowBccHeader>
+-
+-By default, RT hides from the web UI information about blind copies
+-user sent on reply or comment.
+-
+-=cut
+-
+-Set($ShowBccHeader, 0);
+-
+-=item C<$TrustHTMLAttachments>
+-
+-If C<TrustHTMLAttachments> is not defined, we will display them as
+-text. This prevents malicious HTML and JavaScript from being sent in a
+-request (although there is probably more to it than that)
+-
+-=cut
+-
+-Set($TrustHTMLAttachments, undef);
+-
+-=item C<$AlwaysDownloadAttachments>
+-
+-Always download attachments, regardless of content type. If set, this
+-overrides C<TrustHTMLAttachments>.
+-
+-=cut
+-
+-Set($AlwaysDownloadAttachments, undef);
+-
+-=item C<$AttachmentUnits>
+-
+-Controls the units (kilobytes or bytes) that attachment sizes use for
+-display. The default is to display kilobytes if the attachment is
+-larger than 1024 bytes, bytes otherwise. If you set
+-C<$AttachmentUnits> to C<'k'> then attachment sizes will always be
+-displayed in kilobytes. If set to C<'b'>, then sizes will be bytes.
+-
+-=cut
+-
+-Set($AttachmentUnits, undef);
+-
+-=item C<$PreferRichText>
+-
+-If C<$PreferRichText> is set to 1, RT will show HTML/Rich text messages
+-in preference to their plain-text alternatives. RT "scrubs" the HTML to
+-show only a minimal subset of HTML to avoid possible contamination by
+-cross-site-scripting attacks.
+-
+-=cut
+-
+-Set($PreferRichText, undef);
+-
+-=item C<$MaxInlineBody>
+-
+-C<$MaxInlineBody> is the maximum attachment size that we want to see
+-inline when viewing a transaction. RT will inline any text if the
+-value is undefined or 0. This option can be overridden by users in
+-their preferences.
+-
+-=cut
+-
+-Set($MaxInlineBody, 12000);
+-
+-=item C<$ShowTransactionImages>
+-
+-By default, RT shows images attached to incoming (and outgoing) ticket
+-updates inline. Set this variable to 0 if you'd like to disable that
+-behavior.
+-
+-=cut
+-
+-Set($ShowTransactionImages, 1);
+-
+-=item C<$PlainTextPre>
+-
+-Normally plaintext attachments are displayed as HTML with line breaks
+-preserved. This causes space- and tab-based formatting not to be
+-displayed correctly. By setting $PlainTextPre messages will be
+-displayed using <pre>.
+-
+-=cut
+-
+-Set($PlainTextPre, 0);
+-
+-
+-=item C<$PlainTextMono>
+-
+-Set C<$PlainTextMono> to 1 to use monospaced font and preserve
+-formatting; unlike C<$PlainTextPre>, the text will wrap to fit width
+-of the browser window; this option overrides C<$PlainTextPre>.
+-
+-=cut
+-
+-Set($PlainTextMono, 0);
+-
+-=item C<$SuppressInlineTextFiles>
+-
+-If C<$SuppressInlineTextFiles> is set to 1, then uploaded text files
+-(text-type attachments with file names) are prevented from being
+-displayed in-line when viewing a ticket's history.
+-
+-=cut
+-
+-Set($SuppressInlineTextFiles, undef);
+-
+-
+-=item C<@Active_MakeClicky>
+-
+-MakeClicky detects various formats of data in headers and email
+-messages, and extends them with supporting links. By default, RT
+-provides two formats:
+-
+-* 'httpurl': detects http:// and https:// URLs and adds '[Open URL]'
+- link after the URL.
+-
+-* 'httpurl_overwrite': also detects URLs as 'httpurl' format, but
+- replaces the URL with a link.
+-
+-See F<share/html/Elements/MakeClicky> for documentation on how to add
+-your own styles of link detection.
+-
+-=cut
+-
+-Set(@Active_MakeClicky, qw());
+-
+-=back
+-
+-
+-
+-=head1 Application logic
+-
+-=over 4
+-
+-=item C<$ParseNewMessageForTicketCcs>
+-
+-If C<$ParseNewMessageForTicketCcs> is set to 1, RT will attempt to
+-divine Ticket 'Cc' watchers from the To and Cc lines of incoming
+-messages that create new Tickets. This option does not apply to replies
+-or comments on existing Tickets. Be forewarned that if you have I<any>
+-addresses which forward mail to RT automatically and you enable this
+-option without modifying C<$RTAddressRegexp> below, you will get
+-yourself into a heap of trouble.
+-
+-=cut
+-
+-Set($ParseNewMessageForTicketCcs, undef);
+-
+-=item C<$UseTransactionBatch>
+-
+-Set C<$UseTransactionBatch> to 1 to execute transactions in batches,
+-such that a resolve and comment (for example) would happen
+-simultaneously, instead of as two transactions, unaware of each
+-others' existence.
+-
+-=cut
+-
+-Set($UseTransactionBatch, 1);
+-
+-=item C<$StrictLinkACL>
+-
+-When this feature is enabled a user needs I<ModifyTicket> rights on
+-both tickets to link them together; otherwise, I<ModifyTicket> rights
+-on either of them is sufficient.
+-
+-=cut
+-
+-Set($StrictLinkACL, 1);
+-
+-=item C<$RedistributeAutoGeneratedMessages>
+-
+-Should RT redistribute correspondence that it identifies as machine
+-generated? A 1 will do so; setting this to 0 will cause no
+-such messages to be redistributed. You can also use 'privileged' (the
+-default), which will redistribute only to privileged users. This helps
+-to protect against malformed bounces and loops caused by auto-created
+-requestors with bogus addresses.
+-
+-=cut
+-
+-Set($RedistributeAutoGeneratedMessages, "privileged");
+-
+-=item C<$ApprovalRejectionNotes>
+-
+-Should rejection notes from approvals be sent to the requestors?
+-
+-=cut
+-
+-Set($ApprovalRejectionNotes, 1);
+-
+-=item C<$ForceApprovalsView>
+-
+-Should approval tickets only be viewed and modified through the standard
+-approval interface? Changing this setting to 1 will redirect any attempt to
+-use the normal ticket display and modify page for approval tickets.
+-
+-For example, with this option set to 1 and an approval ticket #123:
+-
+- /Ticket/Display.html?id=123
+-
+-is redirected to
+-
+- /Approval/Display.html?id=123
+-
+-=back
+-
+-=cut
+-
+-Set($ForceApprovalsView, 0);
+-
+-=head1 Extra security
+-
+-This is a list of extra security measures to enable that help keep your RT
+-safe. If you don't know what these mean, you should almost certainly leave the
+-defaults alone.
+-
+-=over 4
+-
+-=item C<$DisallowExecuteCode>
+-
+-If set to a true value, the C<ExecuteCode> right will be removed from
+-all users, B<including> the superuser. This is intended for when RT is
+-installed into a shared environment where even the superuser should not
+-be allowed to run arbitrary Perl code on the server via scrips.
+-
+-=cut
+-
+-Set($DisallowExecuteCode, 0);
+-
+-=item C<$Framebusting>
+-
+-If set to a false value, framekiller javascript will be disabled and the
+-X-Frame-Options: DENY header will be suppressed from all responses.
+-This disables RT's clickjacking protection.
+-
+-=cut
+-
+-Set($Framebusting, 1);
+-
+-=item C<$RestrictReferrer>
+-
+-If set to a false value, the HTTP C<Referer> (sic) header will not be
+-checked to ensure that requests come from RT's own domain. As RT allows
+-for GET requests to alter state, disabling this opens RT up to
+-cross-site request forgery (CSRF) attacks.
+-
+-=cut
+-
+-Set($RestrictReferrer, 1);
+-
+-=item C<$RestrictLoginReferrer>
+-
+-If set to a false value, RT will allow the user to log in from any link
+-or request, merely by passing in C<user> and C<pass> parameters; setting
+-it to a true value forces all logins to come from the login box, so the
+-user is aware that they are being logged in. The default is off, for
+-backwards compatability.
+-
+-=cut
+-
+-Set($RestrictLoginReferrer, 0);
+-
+-=item C<@ReferrerWhitelist>
+-
+-This is a list of hostname:port combinations that RT will treat as being
+-part of RT's domain. This is particularly useful if you access RT as
+-multiple hostnames or have an external auth system that needs to
+-redirect back to RT once authentication is complete.
+-
+- Set(@ReferrerWhitelist, qw(www.example.com:443 www3.example.com:80));
+-
+-If the "RT has detected a possible cross-site request forgery" error is triggered
+-by a host:port sent by your browser that you believe should be valid, you can copy
+-the host:port from the error message into this list.
+-
+-Simple wildcards, similar to SSL certificates, are allowed. For example:
+-
+- *.example.com:80 # matches foo.example.com
+- # but not example.com
+- # or foo.bar.example.com
+-
+- www*.example.com:80 # matches www3.example.com
+- # and www-test.example.com
+- # and www.example.com
+-
+-=cut
+-
+-Set(@ReferrerWhitelist, qw());
+-
+-=back
+-
+-
+-
+-=head1 Authorization and user configuration
+-
+-=over 4
+-
+-=item C<$WebExternalAuth>
+-
+-If C<$WebExternalAuth> is defined, RT will defer to the environment's
+-REMOTE_USER variable.
+-
+-=cut
+-
+-Set($WebExternalAuth, undef);
+-
+-=item C<$WebExternalAuthContinuous>
+-
+-If C<$WebExternalAuthContinuous> is defined, RT will check for the
+-REMOTE_USER on each access. If you would prefer this to only happen
+-once (at initial login) set this to a false value. The default
+-setting will help ensure that if your external authentication system
+-deauthenticates a user, RT notices as soon as possible.
+-
+-=cut
+-
+-Set($WebExternalAuthContinuous, 1);
+-
+-=item C<$WebFallbackToInternalAuth>
+-
+-If C<$WebFallbackToInternalAuth> is defined, the user is allowed a
+-chance of fallback to the login screen, even if REMOTE_USER failed.
+-
+-=cut
+-
+-Set($WebFallbackToInternalAuth, undef);
+-
+-=item C<$WebExternalGecos>
+-
+-C<$WebExternalGecos> means to match 'gecos' field as the user
+-identity); useful with mod_auth_pwcheck and IIS Integrated Windows
+-logon.
+-
+-=cut
+-
+-Set($WebExternalGecos, undef);
+-
+-=item C<$WebExternalAuto>
+-
+-C<$WebExternalAuto> will create users under the same name as
+-REMOTE_USER upon login, if it's missing in the Users table.
+-
+-=cut
+-
+-Set($WebExternalAuto, undef);
+-
+-=item C<$AutoCreate>
+-
+-If C<$WebExternalAuto> is set to 1, C<$AutoCreate> will be passed to
+-User's Create method. Use it to set defaults, such as creating
+-Unprivileged users with C<{ Privileged => 0 }> This must be a hashref.
+-
+-=cut
+-
+-Set($AutoCreate, undef);
+-
+-=item C<$WebSessionClass>
+-
+-C<$WebSessionClass> is the class you wish to use for managing sessions.
+-It defaults to use your SQL database, except on Oracle, where it
+-defaults to files on disk.
+-
+-=cut
+-
+-# Set($WebSessionClass, "Apache::Session::File");
+-
+-=item C<$AutoLogoff>
+-
+-By default, RT's user sessions persist until a user closes his or her
+-browser. With the C<$AutoLogoff> option you can setup session lifetime
+-in minutes. A user will be logged out if he or she doesn't send any
+-requests to RT for the defined time.
+-
+-=cut
+-
+-Set($AutoLogoff, 0);
+-
+-=item C<$LogoutRefresh>
+-
+-The number of seconds to wait after logout before sending the user to
+-the login page. By default, 1 second, though you may want to increase
+-this if you display additional information on the logout page.
+-
+-=cut
+-
+-Set($LogoutRefresh, 1);
+-
+-=item C<$WebSecureCookies>
+-
+-By default, RT's session cookie isn't marked as "secure". Some web
+-browsers will treat secure cookies more carefully than non-secure
+-ones, being careful not to write them to disk, only sending them over
+-an SSL secured connection, and so on. To enable this behavior, set
+-C<$WebSecureCookies> to 1. NOTE: You probably don't want to turn this
+-on I<unless> users are only connecting via SSL encrypted HTTPS
+-connections.
+-
+-=cut
+-
+-Set($WebSecureCookies, 0);
+-
+-=item C<$WebHttpOnlyCookies>
+-
+-Default RT's session cookie to not being directly accessible to
+-javascript. The content is still sent during regular and AJAX requests,
+-and other cookies are unaffected, but the session-id is less
+-programmatically accessible to javascript. Turning this off should only
+-be necessary in situations with odd client-side authentication
+-requirements.
+-
+-=cut
+-
+-Set($WebHttpOnlyCookies, 1);
+-
+-=item C<$MinimumPasswordLength>
+-
+-C<$MinimumPasswordLength> defines the minimum length for user
+-passwords. Setting it to 0 disables this check.
+-
+-=cut
+-
+-Set($MinimumPasswordLength, 5);
+-
+-=back
+-
+-
+-=head1 Internationalization
+-
+-=over 4
+-
+-=item C<@LexiconLanguages>
+-
+-An array that contains languages supported by RT's
+-internationalization interface. Defaults to all *.po lexicons;
+-setting it to C<qw(en ja)> will make RT bilingual instead of
+-multilingual, but will save some memory.
+-
+-=cut
+-
+-Set(@LexiconLanguages, qw(*));
+-
+-=item C<@EmailInputEncodings>
+-
+-An array that contains default encodings used to guess which charset
+-an attachment uses, if it does not specify one explicitly. All
+-options must be recognized by L<Encode::Guess>. The first element may
+-also be '*', which enables encoding detection using
+-L<Encode::Detect::Detector>, if installed.
+-
+-=cut
+-
+-Set(@EmailInputEncodings, qw(utf-8 iso-8859-1 us-ascii));
+-
+-=item C<$EmailOutputEncoding>
+-
+-The charset for localized email. Must be recognized by Encode.
+-
+-=cut
+-
+-Set($EmailOutputEncoding, "utf-8");
+-
+-=back
+-
+-
+-
+-
+-
+-
+-
+-=head1 Date and time handling
+-
+-=over 4
+-
+-=item C<$DateTimeFormat>
+-
+-You can choose date and time format. See the "Output formatters"
+-section in perldoc F<lib/RT/Date.pm> for more options. This option
+-can be overridden by users in their preferences.
+-
+-Some examples:
+-
+-C<Set($DateTimeFormat, "LocalizedDateTime");>
+-C<Set($DateTimeFormat, { Format => "ISO", Seconds => 0 });>
+-C<Set($DateTimeFormat, "RFC2822");>
+-C<Set($DateTimeFormat, { Format => "RFC2822", Seconds => 0, DayOfWeek => 0 });>
+-
+-=cut
+-
+-Set($DateTimeFormat, "DefaultFormat");
+-
+-# Next two options are for Time::ParseDate
+-
+-=item C<$DateDayBeforeMonth>
+-
+-Set this to 1 if your local date convention looks like "dd/mm/yy"
+-instead of "mm/dd/yy". Used only for parsing, not for displaying
+-dates.
+-
+-=cut
+-
+-Set($DateDayBeforeMonth, 1);
+-
+-=item C<$AmbiguousDayInPast>, C<$AmbiguousDayInFuture>
+-
+-Should an unspecified day or year in a date refer to a future or a
+-past value? For example, should a date of "Tuesday" default to mean
+-the date for next Tuesday or last Tuesday? Should the date "March 1"
+-default to the date for next March or last March?
+-
+-Set C<$AmbiguousDayInPast> for the last date, or
+-C<$AmbiguousDayInFuture> for the next date; the default is usually
+-correct. If both are set, C<$AmbiguousDayInPast> takes precedence.
+-
+-=cut
+-
+-Set($AmbiguousDayInPast, 0);
+-Set($AmbiguousDayInFuture, 0);
+-
+-=item C<$DefaultTimeUnitsToHours>
+-
+-Use this to set the default units for time entry to hours instead of
+-minutes. Note that this only effects entry, not display.
+-
+-=cut
+-
+-Set($DefaultTimeUnitsToHours, 0);
+-
+-=back
+-
+-
+-
+-
+-=head1 GnuPG integration
+-
+-A full description of the (somewhat extensive) GnuPG integration can
+-be found by running the command `perldoc L<RT::Crypt::GnuPG>` (or
+-`perldoc lib/RT/Crypt/GnuPG.pm` from your RT install directory).
+-
+-=over 4
+-
+-=item C<%GnuPG>
+-
+-Set C<OutgoingMessagesFormat> to 'inline' to use inline encryption and
+-signatures instead of 'RFC' (GPG/MIME: RFC3156 and RFC1847) format.
+-
+-If you want to allow people to encrypt attachments inside the DB then
+-set C<AllowEncryptDataInDB> to 1.
+-
+-Set C<RejectOnMissingPrivateKey> to false if you don't want to reject
+-emails encrypted for key RT doesn't have and can not decrypt.
+-
+-Set C<RejectOnBadData> to false if you don't want to reject letters
+-with incorrect GnuPG data.
+-
+-=cut
+-
+-Set(%GnuPG,
+- Enable => 1,
+- OutgoingMessagesFormat => "RFC", # Inline
+- AllowEncryptDataInDB => 0,
+-
+- RejectOnMissingPrivateKey => 1,
+- RejectOnBadData => 1,
+-);
+-
+-=item C<%GnuPGOptions>
+-
+-Options to pass to the GnuPG program.
+-
+-If you override this in your RT_SiteConfig, you should be sure to
+-include a homedir setting.
+-
+-Note that options with '-' character MUST be quoted.
+-
+-=cut
+-
+-Set(%GnuPGOptions,
+- homedir => q{var/data/gpg},
+-
+-# URL of a keyserver
+-# keyserver => 'hkp://subkeys.pgp.net',
+-
+-# enables the automatic retrieving of keys when encrypting
+-# 'auto-key-locate' => 'keyserver',
+-
+-# enables the automatic retrieving of keys when verifying signatures
+-# 'keyserver-options' => 'auto-key-retrieve',
+-);
+-
+-=back
+-
+-
+-
+-=head1 Lifecycles
+-
+-=head2 Lifecycle definitions
+-
+-Each lifecycle is a list of possible statuses split into three logic
+-sets: B<initial>, B<active> and B<inactive>. Each status in a
+-lifecycle must be unique. (Statuses may not be repeated across sets.)
+-Each set may have any number of statuses.
+-
+-For example:
+-
+- default => {
+- initial => ['new'],
+- active => ['open', 'stalled'],
+- inactive => ['resolved', 'rejected', 'deleted'],
+- ...
+- },
+-
+-Status names can be from 1 to 64 ASCII characters. Statuses are
+-localized using RT's standard internationalization and localization
+-system.
+-
+-=over 4
+-
+-=item initial
+-
+-You can define multiple B<initial> statuses for tickets in a given
+-lifecycle.
+-
+-RT will automatically set its B<Started> date when you change a
+-ticket's status from an B<initial> state to an B<active> or
+-B<inactive> status.
+-
+-=item active
+-
+-B<Active> tickets are "currently in play" - they're things that are
+-being worked on and not yet complete.
+-
+-=item inactive
+-
+-B<Inactive> tickets are typically in their "final resting state".
+-
+-While you're free to implement a workflow that ignores that
+-description, typically once a ticket enters an inactive state, it will
+-never again enter an active state.
+-
+-RT will automatically set the B<Resolved> date when a ticket's status
+-is changed from an B<Initial> or B<Active> status to an B<Inactive>
+-status.
+-
+-B<deleted> is still a special status and protected by the
+-B<DeleteTicket> right, unless you re-defined rights (read below). If
+-you don't want to allow ticket deletion at any time simply don't
+-include it in your lifecycle.
+-
+-=back
+-
+-Statuses in each set are ordered and listed in the UI in the defined
+-order.
+-
+-Changes between statuses are constrained by transition rules, as
+-described below.
+-
+-=head2 Default values
+-
+-In some cases a default value is used to display in UI or in API when
+-value is not provided. You can configure defaults using the following
+-syntax:
+-
+- default => {
+- ...
+- defaults => {
+- on_create => 'new',
+- on_resolve => 'resolved',
+- ...
+- },
+- },
+-
+-The following defaults are used.
+-
+-=over 4
+-
+-=item on_create
+-
+-If you (or your code) doesn't specify a status when creating a ticket,
+-RT will use the this status. See also L</Statuses available during
+-ticket creation>.
+-
+-=item on_merge
+-
+-When tickets are merged, the status of the ticket that was merged
+-away is forced to this value. It should be one of inactive statuses;
+-'resolved' or its equivalent is most probably the best candidate.
+-
+-=item approved
+-
+-When an approval is accepted, the status of depending tickets will
+-be changed to this value.
+-
+-=item denied
+-
+-When an approval is denied, the status of depending tickets will
+-be changed to this value.
+-
+-=item reminder_on_open
+-
+-When a reminder is opened, the status will be changed to this value.
+-
+-=item reminder_on_resolve
+-
+-When a reminder is resolved, the status will be changed to this value.
+-
+-=back
+-
+-=head2 Transitions between statuses and UI actions
+-
+-A B<Transition> is a change of status from A to B. You should define
+-all possible transitions in each lifecycle using the following format:
+-
+- default => {
+- ...
+- 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)],
+- },
+- ...
+- },
+-
+-=head3 Statuses available during ticket creation
+-
+-By default users can create tickets with a status of new,
+-open, or resolved, but cannot create tickets with a status of
+-rejected, stalled, or deleted. If you want to change the statuses
+-available during creation, update the transition from '' (empty
+-string), like in the example above.
+-
+-=head3 Protecting status changes with rights
+-
+-A transition or group of transitions can be protected by a specific
+-right. Additionally, you can name new right names, which will be added
+-to the system to control that transition. For example, if you wished to
+-create a lesser right than ModifyTicket for rejecting tickets, you could
+-write:
+-
+- default => {
+- ...
+- rights => {
+- '* -> deleted' => 'DeleteTicket',
+- '* -> rejected' => 'RejectTicket',
+- '* -> *' => 'ModifyTicket',
+- },
+- ...
+- },
+-
+-This would create a new C<RejectTicket> right in the system which you
+-could assign to whatever groups you choose.
+-
+-On the left hand side you can have the following variants:
+-
+- '<from> -> <to>'
+- '* -> <to>'
+- '<from> -> *'
+- '* -> *'
+-
+-Valid transitions are listed in order of priority. If a user attempts
+-to change a ticket's status from B<new> to B<open> then the lifecycle
+-is checked for presence of an exact match, then for 'any to B<open>',
+-'B<new> to any' and finally 'any to any'.
+-
+-If you don't define any rights, or there is no match for a transition,
+-RT will use the B<DeleteTicket> or B<ModifyTicket> as appropriate.
+-
+-=head3 Labeling and defining actions
+-
+-For each transition you can define an action that will be shown in the
+-UI; each action annotated with a label and an update type.
+-
+-Each action may provide a default update type, which can be
+-B<Comment>, B<Respond>, or absent. For example, you may want your
+-staff to write a reply to the end user when they change status from
+-B<new> to B<open>, and thus set the update to B<Respond>. Neither
+-B<Comment> nor B<Respond> are mandatory, and user may leave the
+-message empty, regardless of the update type.
+-
+-This configuration can be used to accomplish what
+-$ResolveDefaultUpdateType was used for in RT 3.8.
+-
+-Use the following format to define labels and actions of transitions:
+-
+- default => {
+- ...
+- actions => [
+- 'new -> open' => { label => 'Open it', update => 'Respond' },
+- 'new -> resolved' => { label => 'Resolve', update => 'Comment' },
+- 'new -> rejected' => { label => 'Reject', update => 'Respond' },
+- 'new -> deleted' => { label => 'Delete' },
+-
+- 'open -> stalled' => { label => 'Stall', update => 'Comment' },
+- 'open -> resolved' => { label => 'Resolve', update => 'Comment' },
+- 'open -> rejected' => { label => 'Reject', update => 'Respond' },
+-
+- 'stalled -> open' => { label => 'Open it' },
+- 'resolved -> open' => { label => 'Re-open', update => 'Comment' },
+- 'rejected -> open' => { label => 'Re-open', update => 'Comment' },
+- 'deleted -> open' => { label => 'Undelete' },
+- ],
+- ...
+- },
+-
+-In addition, you may define multiple actions for the same transition.
+-Alternately, you may use '* -> x' to match more than one transition.
+-For example:
+-
+- default => {
+- ...
+- actions => [
+- ...
+- 'new -> rejected' => { label => 'Reject', update => 'Respond' },
+- 'new -> rejected' => { label => 'Quick Reject' },
+- ...
+- '* -> deleted' => { label => 'Delete' },
+- ...
+- ],
+- ...
+- },
+-
+-=head2 Moving tickets between queues with different lifecycles
+-
+-Unless there is an explicit mapping between statuses in two different
+-lifecycles, you can not move tickets between queues with these
+-lifecycles. This is true even if the different lifecycles use the exact
+-same set of statuses. Such a mapping is defined as follows:
+-
+- __maps__ => {
+- 'from lifecycle -> to lifecycle' => {
+- 'status in left lifecycle' => 'status in right lifecycle',
+- ...
+- },
+- ...
+- },
+-
+-=cut
+-
+-Set(%Lifecycles,
+- default => {
+- initial => [ 'new' ],
+- active => [ 'open', 'stalled' ],
+- inactive => [ 'resolved', 'rejected', 'deleted' ],
+-
+- defaults => {
+- on_create => 'new',
+- on_merge => 'resolved',
+- approved => 'open',
+- denied => 'rejected',
+- reminder_on_open => 'open',
+- reminder_on_resolve => 'resolved',
+- },
+-
+- transitions => {
+- '' => [qw(new open resolved)],
+-
+- # from => [ to list ],
+- new => [qw(open stalled resolved rejected deleted)],
+- open => [qw(new stalled resolved rejected deleted)],
+- stalled => [qw(new open rejected resolved deleted)],
+- resolved => [qw(new open stalled rejected deleted)],
+- rejected => [qw(new open stalled resolved deleted)],
+- deleted => [qw(new open stalled rejected resolved)],
+- },
+- rights => {
+- '* -> deleted' => 'DeleteTicket',
+- '* -> *' => 'ModifyTicket',
+- },
+- actions => [
+- 'new -> open' => {
+- label => 'Open It', # loc
+- update => 'Respond',
+- },
+- 'new -> resolved' => {
+- label => 'Resolve', # loc
+- update => 'Comment',
+- },
+- 'new -> rejected' => {
+- label => 'Reject', # loc
+- update => 'Respond',
+- },
+- 'new -> deleted' => {
+- label => 'Delete', # loc
+- },
+-
+- 'open -> stalled' => {
+- label => 'Stall', # loc
+- update => 'Comment',
+- },
+- 'open -> resolved' => {
+- label => 'Resolve', # loc
+- update => 'Comment',
+- },
+- 'open -> rejected' => {
+- label => 'Reject', # loc
+- update => 'Respond',
+- },
+-
+- 'stalled -> open' => {
+- label => 'Open It', # loc
+- },
+- 'resolved -> open' => {
+- label => 'Re-open', # loc
+- update => 'Comment',
+- },
+- 'rejected -> open' => {
+- label => 'Re-open', # loc
+- update => 'Comment',
+- },
+- 'deleted -> open' => {
+- label => 'Undelete', # loc
+- },
+- ],
+- },
+-# don't change lifecyle of the approvals, they are not capable to deal with
+-# custom statuses
+- approvals => {
+- initial => [ 'new' ],
+- active => [ 'open', 'stalled' ],
+- inactive => [ 'resolved', 'rejected', 'deleted' ],
+-
+- defaults => {
+- on_create => 'new',
+- on_merge => 'resolved',
+- reminder_on_open => 'open',
+- reminder_on_resolve => 'resolved',
+- },
+-
+- transitions => {
+- '' => [qw(new open resolved)],
+-
+- # from => [ to list ],
+- new => [qw(open stalled resolved rejected deleted)],
+- open => [qw(new stalled resolved rejected deleted)],
+- stalled => [qw(new open rejected resolved deleted)],
+- resolved => [qw(new open stalled rejected deleted)],
+- rejected => [qw(new open stalled resolved deleted)],
+- deleted => [qw(new open stalled rejected resolved)],
+- },
+- rights => {
+- '* -> deleted' => 'DeleteTicket',
+- '* -> rejected' => 'ModifyTicket',
+- '* -> *' => 'ModifyTicket',
+- },
+- actions => [
+- 'new -> open' => {
+- label => 'Open It', # loc
+- update => 'Respond',
+- },
+- 'new -> resolved' => {
+- label => 'Resolve', # loc
+- update => 'Comment',
+- },
+- 'new -> rejected' => {
+- label => 'Reject', # loc
+- update => 'Respond',
+- },
+- 'new -> deleted' => {
+- label => 'Delete', # loc
+- },
+-
+- 'open -> stalled' => {
+- label => 'Stall', # loc
+- update => 'Comment',
+- },
+- 'open -> resolved' => {
+- label => 'Resolve', # loc
+- update => 'Comment',
+- },
+- 'open -> rejected' => {
+- label => 'Reject', # loc
+- update => 'Respond',
+- },
+-
+- 'stalled -> open' => {
+- label => 'Open It', # loc
+- },
+- 'resolved -> open' => {
+- label => 'Re-open', # loc
+- update => 'Comment',
+- },
+- 'rejected -> open' => {
+- label => 'Re-open', # loc
+- update => 'Comment',
+- },
+- 'deleted -> open' => {
+- label => 'Undelete', # loc
+- },
+- ],
+- },
+-);
+-
+-
+-
+-
+-
+-=head1 Administrative interface
+-
+-=over 4
+-
+-=item C<$ShowRTPortal>
+-
+-RT can show administrators a feed of recent RT releases and other
+-related announcements and information from Best Practical on the top
+-level Configuration page. This feature helps you stay up to date on
+-RT security announcements and version updates.
+-
+-RT provides this feature using an "iframe" on C</Admin/index.html>
+-which asks the administrator's browser to show an inline page from
+-Best Practical's website.
+-
+-If you'd rather not make this feature available to your
+-administrators, set C<$ShowRTPortal> to a false value.
+-
+-=cut
+-
+-Set($ShowRTPortal, 1);
+-
+-=item C<%AdminSearchResultFormat>
+-
+-In the admin interface, format strings similar to tickets result
+-formats are used. Use C<%AdminSearchResultFormat> to define the format
+-strings used in the admin interface on a per-RT-class basis.
+-
+-=cut
+-
+-Set(%AdminSearchResultFormat,
+- Queues =>
+- q{'<a href="__WebPath__/Admin/Queues/Modify.html?id=__id__">__id__</a>/TITLE:#'}
+- .q{,'<a href="__WebPath__/Admin/Queues/Modify.html?id=__id__">__Name__</a>/TITLE:Name'}
+- .q{,__Description__,__Address__,__Priority__,__DefaultDueIn__,__Disabled__,__Lifecycle__},
+-
+- Groups =>
+- q{'<a href="__WebPath__/Admin/Groups/Modify.html?id=__id__">__id__</a>/TITLE:#'}
+- .q{,'<a href="__WebPath__/Admin/Groups/Modify.html?id=__id__">__Name__</a>/TITLE:Name'}
+- .q{,'__Description__'},
+-
+- Users =>
+- q{'<a href="__WebPath__/Admin/Users/Modify.html?id=__id__">__id__</a>/TITLE:#'}
+- .q{,'<a href="__WebPath__/Admin/Users/Modify.html?id=__id__">__Name__</a>/TITLE:Name'}
+- .q{,__RealName__, __EmailAddress__},
+-
+- CustomFields =>
+- q{'<a href="__WebPath__/Admin/CustomFields/Modify.html?id=__id__">__id__</a>/TITLE:#'}
+- .q{,'<a href="__WebPath__/Admin/CustomFields/Modify.html?id=__id__">__Name__</a>/TITLE:Name'}
+- .q{,__AppliedTo__, __FriendlyType__, __FriendlyPattern__},
+-
+- Scrips =>
+- q{'<a href="__WebPath__/Admin/Queues/Scrip.html?id=__id__&Queue=__QueueId__">__id__</a>/TITLE:#'}
+- .q{,'<a href="__WebPath__/Admin/Queues/Scrip.html?id=__id__&Queue=__QueueId__">__Description__</a>/TITLE:Description'}
+- .q{,__Stage__, __Condition__, __Action__, __Template__},
+-
+- GlobalScrips =>
+- q{'<a href="__WebPath__/Admin/Global/Scrip.html?id=__id__">__id__</a>/TITLE:#'}
+- .q{,'<a href="__WebPath__/Admin/Global/Scrip.html?id=__id__">__Description__</a>/TITLE:Description'}
+- .q{,__Stage__, __Condition__, __Action__, __Template__},
+-
+- Templates =>
+- q{'<a href="__WebPath__/__WebRequestPathDir__/Template.html?Queue=__QueueId__&Template=__id__">__id__</a>/TITLE:#'}
+- .q{,'<a href="__WebPath__/__WebRequestPathDir__/Template.html?Queue=__QueueId__&Template=__id__">__Name__</a>/TITLE:Name'}
+- .q{,'__Description__'},
+- Classes =>
+- q{ '<a href="__WebPath__/Admin/Articles/Classes/Modify.html?id=__id__">__id__</a>/TITLE:#'}
+- .q{,'<a href="__WebPath__/Admin/Articles/Classes/Modify.html?id=__id__">__Name__</a>/TITLE:Name'}
+- .q{,__Description__},
+-);
+-
+-=back
+-
+-
+-
+-
+-=head1 Development options
+-
+-=over 4
+-
+-=item C<$DevelMode>
+-
+-RT comes with a "Development mode" setting. This setting, as a
+-convenience for developers, turns on several of development options
+-that you most likely don't want in production:
+-
+-=over 4
+-
+-=item *
+-
+-Disables CSS and JS minification and concatenation. Both CSS and JS
+-will be instead be served as a number of individual smaller files,
+-unchanged from how they are stored on disk.
+-
+-=item *
+-
+-Uses L<Module::Refresh> to reload changed Perl modules on each
+-request.
+-
+-=item *
+-
+-Turns off Mason's C<static_source> directive; this causes Mason to
+-reload template files which have been modified on disk.
+-
+-=item *
+-
+-Turns on Mason's HTML C<error_format>; this renders compilation errors
+-to the browser, along with a full stack trace. It is possible for
+-stack traces to reveal sensitive information such as passwords or
+-ticket content.
+-
+-=item *
+-
+-Turns off caching of callbacks; this enables additional callbacks to
+-be added while the server is running.
+-
+-=back
+-
+-=cut
+-
+-Set($DevelMode, "0");
+-
+-
+-=item C<$RecordBaseClass>
+-
+-What abstract base class should RT use for its records. You should
+-probably never change this.
+-
+-Valid values are C<DBIx::SearchBuilder::Record> or
+-C<DBIx::SearchBuilder::Record::Cachable>
+-
+-=cut
+-
+-Set($RecordBaseClass, "DBIx::SearchBuilder::Record::Cachable");
+-
+-
+-=item C<@MasonParameters>
+-
+-C<@MasonParameters> is the list of parameters for the constructor of
+-HTML::Mason's Apache or CGI Handler. This is normally only useful for
+-debugging, e.g. profiling individual components with:
+-
+- use MasonX::Profiler; # available on CPAN
+- Set(@MasonParameters, (preamble => 'my $p = MasonX::Profiler->new($m, $r);'));
+-
+-=cut
+-
+-Set(@MasonParameters, ());
+-
+-=item C<$StatementLog>
+-
+-RT has rudimentary SQL statement logging support; simply set
+-C<$StatementLog> to be the level that you wish SQL statements to be
+-logged at.
+-
+-Enabling this option will also expose the SQL Queries page in the
+-Configuration -> Tools menu for SuperUsers.
+-
+-=cut
+-
+-Set($StatementLog, undef);
+-
+-=back
+-
+-
+-
+-
+-=head1 Deprecated options
+-
+-=over 4
+-
+-=item C<$LinkTransactionsRun1Scrip>
+-
+-RT-3.4 backward compatibility setting. Add/Delete Link used to record
+-one transaction and run one scrip. Set this value to 1 if you want
+-only one of the link transactions to have scrips run.
+-
+-=cut
+-
+-Set($LinkTransactionsRun1Scrip, 0);
+-
+-=item C<$ResolveDefaultUpdateType>
+-
+-This option has been deprecated. You can configure this site-wide
+-with L</Lifecycles> (see L</Labeling and defining actions>).
+-
+-=back
+-
+-=cut
+-
+-1;
+diff --git a/etc/upgrade/3.8-ical-extension b/etc/upgrade/3.8-ical-extension
+deleted file mode 100755
+index 7b160d7..0000000
+--- a/etc/upgrade/3.8-ical-extension
++++ /dev/null
+@@ -1,96 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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 lib "local/lib";
+-use lib "lib";
+-
+-
+-use RT;
+-RT::LoadConfig();
+-RT::Init();
+-
+-use RT::Attributes;
+-my $attrs = RT::Attributes->new( RT->SystemUser );
+-$attrs->Limit(FIELD => 'ObjectType', OPERATOR=> '=', VALUE => 'RT::User');
+-$attrs->Limit(FIELD => 'Name', OPERATOR=> '=', VALUE => 'ical-auth-token');
+-while ( my $attr = $attrs->Next ) {
+- my $uid = $attr->ObjectId;
+- print "Processing auth token of user #". $uid ."...\n";
+-
+- my $user = RT::User->new( RT->SystemUser );
+- $user->Load( $uid );
+- unless ( $user->id ) {
+- print STDERR "\tERROR. Couldn't load user record\n";
+- next;
+- }
+-
+- my ($status, $msg);
+-
+- ($status, $msg) = $user->DeleteAttribute('AuthToken')
+- if $user->FirstAttribute('AuthToken');
+- unless ( $status ) {
+- print STDERR "\tERROR. Couldn't delete duplicated attribute: $msg\n";
+- next;
+- } else {
+- print "\tdeleted duplicate attribute\n";
+- }
+-
+- ($status, $msg) = $attr->SetName('AuthToken');
+- unless ( $status ) {
+- print STDERR "\tERROR. Couldn't rename attribute: $msg\n";
+- next;
+- } else {
+- print "\trenamed attribute\n";
+- }
+- print "\tDONE\n";
+-}
+-
+-exit 0;
+diff --git a/etc/upgrade/4.0-customfield-checkbox-extension b/etc/upgrade/4.0-customfield-checkbox-extension
+deleted file mode 100755
+index a9f5dc5..0000000
+--- a/etc/upgrade/4.0-customfield-checkbox-extension
++++ /dev/null
+@@ -1,86 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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 lib "local/lib";
+-use lib "lib";
+-
+-use RT;
+-RT::LoadConfig();
+-RT::Init();
+-
+-use RT::CustomFields;
+-my $cfs = RT::CustomFields->new( RT->SystemUser );
+-$cfs->{find_disabled_rows} = 1;
+-$cfs->Limit(
+- FIELD => 'Type',
+- VALUE => 'SelectCheckbox',
+-);
+-
+-while ( my $cf = $cfs->Next ) {
+- print 'Processing custom field #' . $cf->id . "\n";
+- my ( $ret, $msg ) = $cf->SetType('Select');
+- unless ($ret) {
+- warn "Failed to set custom field #"
+- . $cf->id
+- . " Type to 'Select': $msg\n";
+- }
+-
+- ( $ret, $msg ) = $cf->SetRenderType('List');
+- unless ($ret) {
+- warn "Failed to set custom field #"
+- . $cf->id
+- . " RenderType to 'List': $msg\n";
+- }
+-}
+-
+-print "DONE\n";
+-
+-exit 0;
+diff --git a/etc/upgrade/generate-rtaddressregexp b/etc/upgrade/generate-rtaddressregexp
+deleted file mode 100755
+index c36805c..0000000
+--- a/etc/upgrade/generate-rtaddressregexp
++++ /dev/null
+@@ -1,109 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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 lib "local/lib";
+-use lib "lib";
+-
+-use RT;
+-RT::LoadConfig();
+-RT->Config->Set('LogToScreen' => 'debug');
+-RT::Init();
+-
+-$| = 1;
+-
+-if (my $re = RT->Config->Get('RTAddressRegexp')) {
+- print "No need to use this script, you already have RTAddressRegexp set to $re\n";
+- exit;
+-}
+-
+-use RT::Queues;
+-my $queues = RT::Queues->new( RT->SystemUser );
+-$queues->UnLimit;
+-
+-my %merged;
+-merge(\%merged, RT->Config->Get('CorrespondAddress'), RT->Config->Get('CommentAddress'));
+-while ( my $queue = $queues->Next ) {
+- merge(\%merged, $queue->CorrespondAddress, $queue->CommentAddress);
+-}
+-
+-my @domains;
+-for my $domain (sort keys %merged) {
+- my @addresses;
+- for my $base (sort keys %{$merged{$domain}}) {
+- my @subbits = keys(%{$merged{$domain}{$base}});
+- if (@subbits > 1) {
+- push @addresses, "\Q$base\E(?:".join("|", at subbits).")";
+- } else {
+- push @addresses, "\Q$base\E$subbits[0]";
+- }
+- }
+- if (@addresses > 1) {
+- push @domains, "(?:".join("|", @addresses).")\Q\@".$domain."\E";
+- } else {
+- push @domains, "$addresses[0]\Q\@$domain\E";
+- }
+-}
+-my $re = join "|", @domains;
+-
+-print <<ENDDESCRIPTION;
+-You can add the following to RT_SiteConfig.pm, but may want to collapse it into a more efficient regexp.
+-Keep in mind that this only contains the email addresses that RT knows about, you should also examine
+-your mail system for aliases that reach RT but which RT doesn't know about.
+-ENDDESCRIPTION
+-print "Set(\$RTAddressRegexp,qr{^(?:${re})\$}i);\n";
+-
+-sub merge {
+- my $merged = shift;
+- for my $address (grep {defined and length} @_) {
+- $address =~ /^\s*(.*?)(-comments?)?\@(.*?)\s*$/;
+- $merged->{lc $3}{$1}{$2||''}++;
+- }
+-}
+diff --git a/etc/upgrade/split-out-cf-categories b/etc/upgrade/split-out-cf-categories
+deleted file mode 100755
+index 7aa8dd9..0000000
+--- a/etc/upgrade/split-out-cf-categories
++++ /dev/null
+@@ -1,171 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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 lib "local/lib";
+-use lib "lib";
+-
+-use RT;
+-RT::LoadConfig();
+-RT->Config->Set('LogToScreen' => 'debug');
+-RT::Init();
+-
+-$| = 1;
+-
+-$RT::Handle->BeginTransaction();
+-
+-use RT::CustomFields;
+-my $CFs = RT::CustomFields->new( RT->SystemUser );
+-$CFs->UnLimit;
+-$CFs->Limit( FIELD => 'Type', VALUE => 'Select' );
+-
+-my $seen;
+-while (my $cf = $CFs->Next ) {
+- next if $cf->BasedOnObj->Id;
+- my @categories;
+- my %mapping;
+- my $values = $cf->Values;
+- while (my $value = $values->Next) {
+- next unless defined $value->Category and length $value->Category;
+- push @categories, $value->Category unless grep {$_ eq $value->Category} @categories;
+- $mapping{$value->Name} = $value->Category;
+- }
+- next unless @categories;
+-
+- $seen++;
+- print "Found CF '@{[$cf->Name]}' with categories:\n";
+- print " $_\n" for @categories;
+-
+- print "Split this CF's categories into a hierarchical custom field (Y/n)? ";
+- my $dothis = <>;
+- next if $dothis =~ /n/i;
+-
+- print "Enter name of CF to create as category ('@{[$cf->Name]} category'): ";
+- my $newname = <>;
+- chomp $newname;
+- $newname = $cf->Name . " category" unless length $newname;
+-
+- # bump the CF's sort oder up by one
+- $cf->SetSortOrder( ($cf->SortOrder || 0) + 1 );
+-
+- # ..and add a new CF before it
+- my $new = RT::CustomField->new( RT->SystemUser );
+- my ($id, $msg) = $new->Create(
+- Name => $newname,
+- Type => 'Select',
+- MaxValues => 1,
+- LookupType => $cf->LookupType,
+- SortOrder => $cf->SortOrder - 1,
+- );
+- die "Can't create custom field '$newname': $msg" unless $id;
+-
+- # Set the CF to be based on what we just made
+- $cf->SetBasedOn( $new->Id );
+-
+- # Apply it to all of the same things
+- {
+- my $ocfs = RT::ObjectCustomFields->new( RT->SystemUser );
+- $ocfs->LimitToCustomField( $cf->Id );
+- while (my $ocf = $ocfs->Next) {
+- my $newocf = RT::ObjectCustomField->new( RT->SystemUser );
+- ($id, $msg) = $newocf->Create(
+- SortOrder => $ocf->SortOrder,
+- CustomField => $new->Id,
+- ObjectId => $ocf->ObjectId,
+- );
+- die "Can't create ObjectCustomField: $msg" unless $id;
+- }
+- }
+-
+- # Copy over all of the rights
+- {
+- my $acl = RT::ACL->new( RT->SystemUser );
+- $acl->LimitToObject( $cf );
+- while (my $ace = $acl->Next) {
+- my $newace = RT::ACE->new( RT->SystemUser );
+- ($id, $msg) = $newace->Create(
+- PrincipalId => $ace->PrincipalId,
+- PrincipalType => $ace->PrincipalType,
+- RightName => $ace->RightName,
+- Object => $new,
+- );
+- die "Can't assign rights: $msg" unless $id;
+- }
+- }
+-
+- # Add values for all of the categories
+- for my $i (0..$#categories) {
+- ($id, $msg) = $new->AddValue(
+- Name => $categories[$i],
+- SortOrder => $i + 1,
+- );
+- die "Can't create custom field value: $msg" unless $id;
+- }
+-
+- # Grovel through all ObjectCustomFieldValues, and add the
+- # appropriate category
+- {
+- my $ocfvs = RT::ObjectCustomFieldValues->new( RT->SystemUser );
+- $ocfvs->LimitToCustomField( $cf->Id );
+- while (my $ocfv = $ocfvs->Next) {
+- next unless exists $mapping{$ocfv->Content};
+- my $newocfv = RT::ObjectCustomFieldValue->new( RT->SystemUser );
+- ($id, $msg) = $newocfv->Create(
+- CustomField => $new->Id,
+- ObjectType => $ocfv->ObjectType,
+- ObjectId => $ocfv->ObjectId,
+- Content => $mapping{$ocfv->Content},
+- );
+- }
+- }
+-}
+-
+-$RT::Handle->Commit;
+-print "No custom fields with categories found\n" unless $seen;
+diff --git a/etc/upgrade/upgrade-articles b/etc/upgrade/upgrade-articles
+deleted file mode 100755
+index fc480a6..0000000
+--- a/etc/upgrade/upgrade-articles
++++ /dev/null
+@@ -1,264 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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 lib "local/lib";
+-use lib "lib";
+-
+-use RT;
+-RT::LoadConfig();
+-RT->Config->Set('LogToScreen' => 'debug');
+-RT::Init();
+-
+-$| = 1;
+-
+-my $db_name = RT->Config->Get('DatabaseName');
+-my $db_type = RT->Config->Get('DatabaseType');
+-
+-my $dbh = $RT::Handle->dbh;
+-
+-my $found_fm_tables;
+-foreach my $name ( $RT::Handle->_TableNames ) {
+- next unless $name =~ /^fm_/i;
+- $found_fm_tables->{lc $name}++;
+-}
+-
+-unless ( $found_fm_tables->{fm_topics} && $found_fm_tables->{fm_objecttopics} ) {
+- warn "Couldn't find topics tables, it appears you have RTFM 2.0 or earlier.";
+- warn "This script cannot yet upgrade RTFM versions which are that old";
+- exit;
+-}
+-
+-{ # port over Articles
+- my @columns = qw(id Name Summary SortOrder Class Parent URI Creator Created LastUpdatedBy LastUpdated);
+- copy_tables('FM_Articles','Articles',\@columns);
+-
+-}
+-
+-
+-{ # port over Classes
+- my @columns = qw(id Name Description SortOrder Disabled Creator Created LastUpdatedBy LastUpdated);
+- if ( grep lc($_) eq 'hotlist', $RT::Handle->Fields('FM_Classes') ) {
+- push @columns, 'HotList';
+- }
+- copy_tables('FM_Classes','Classes',\@columns);
+-}
+-
+-{ # port over Topics
+- my @columns = qw(id Parent Name Description ObjectType ObjectId);
+- copy_tables('FM_Topics','Topics',\@columns);
+-}
+-
+-{ # port over ObjectTopics
+- my @columns = qw(id Topic ObjectType ObjectId);
+- copy_tables('FM_ObjectTopics','ObjectTopics',\@columns);
+-}
+-
+-sub copy_tables {
+- my ($source, $dest, $columns) = @_;
+- my $column_list = join(', ',@$columns);
+- my $sql;
+- # SQLite: http://www.sqlite.org/lang_insert.html
+- if ( $db_type eq 'mysql' || $db_type eq 'SQLite' ) {
+- $sql = "insert into $dest ($column_list) select $column_list from $source";
+- }
+- # Oracle: http://www.adp-gmbh.ch/ora/sql/insert/select_and_subquery.html
+- elsif ( $db_type eq 'Pg' || $db_type eq 'Oracle' ) {
+- $sql = "insert into $dest ($column_list) (select $column_list from $source)";
+- }
+- $RT::Logger->debug($sql);
+- $dbh->do($sql);
+-}
+-
+-{ # create ObjectClasses
+- # this logic will need updating when folks have an FM_ObjectClasses table
+- use RT::Classes;
+- use RT::ObjectClass;
+-
+- my $classes = RT::Classes->new(RT->SystemUser);
+- $classes->UnLimit;
+- while ( my $class = $classes->Next ) {
+- my $objectclass = RT::ObjectClass->new(RT->SystemUser);
+- my ($ret, $msg ) = $objectclass->Create( Class => $class->Id, ObjectType => 'RT::System', ObjectId => 0 );
+- if ($ret) {
+- warn("Applied Class '".$class->Name."' globally");
+- } else {
+- warn("Couldn't create linkage for Class ".$class->Name.": $msg");
+- }
+- }
+-}
+-
+-{ # update ACLs
+- use RT::ACL;
+- my $acl = RT::ACL->new(RT->SystemUser);
+- $acl->Limit( FIELD => 'ObjectType', VALUE => 'RT::FM::Class' );
+- $acl->Limit( FIELD => 'ObjectType', VALUE => 'RT::FM::System' );
+- while ( my $ace = $acl->Next ) {
+- if ( $ace->__Value('ObjectType') eq 'RT::FM::Class' ) {
+- my ($ret, $msg ) = $ace->__Set( Field => 'ObjectType', Value => 'RT::Class');
+- warn "Fixing ACL ".$ace->Id." to refer to RT::Class: $msg";
+- } elsif ( $ace->__Value('ObjectType') eq 'RT::FM::System' ) {
+- my ($ret, $msg) = $ace->__Set(Field => 'ObjectType', Value => 'RT::System');
+- warn "Fixing ACL ".$ace->Id." to refer to RT::System: $msg";
+- }
+- }
+-
+-
+-}
+-
+-{ # update CustomFields
+- use RT::CustomFields;
+- my $cfs = RT::CustomFields->new(RT->SystemUser);
+- $cfs->Limit( FIELD => 'LookupType', VALUE => 'RT::FM::Class-RT::FM::Article' );
+- $cfs->{'find_disabled_rows'} = 1;
+- while ( my $cf = $cfs->Next ) {
+- my ($ret, $msg) = $cf->__Set( Field => 'LookupType', Value => 'RT::Class-RT::Article' );
+- warn "Update Custom Field LookupType for CF.".$cf->Id." $msg";
+- }
+-}
+-
+-{ # update ObjectCustomFieldValues
+- use RT::ObjectCustomFieldValues;
+- my $ocfvs = RT::ObjectCustomFieldValues->new(RT->System);
+- $ocfvs->Limit( FIELD => 'ObjectType', VALUE => 'RT::FM::Article' );
+- $ocfvs->{'find_expired_rows'} = 1;
+- while ( my $ocfv = $ocfvs->Next ) {
+- my ($ret, $msg) = $ocfv->__Set( Field => 'ObjectType', Value => 'RT::Article' );
+- warn "Updated CF ".$ocfv->__Value('CustomField')." Value for Article ".$ocfv->__Value('ObjectId');
+- }
+-
+-}
+-
+-{ # update Topics
+- use RT::Topics;
+- my $topics = RT::Topics->new(RT->SystemUser);
+- $topics->Limit( FIELD => 'ObjectType', VALUE => 'RT::FM::Class' );
+- $topics->Limit( FIELD => 'ObjectType', VALUE => 'RT::FM::System' );
+- while ( my $topic = $topics->Next ) {
+- if ( $topic->__Value('ObjectType') eq 'RT::FM::Class' ) {
+- my ($ret, $msg ) = $topic->__Set( Field => 'ObjectType', Value => 'RT::Class');
+- warn "Fixing Topic ".$topic->Id." to refer to RT::Class: $msg";
+- } elsif ( $topic->__Value('ObjectType') eq 'RT::FM::System' ) {
+- my ($ret, $msg) = $topic->__Set(Field => 'ObjectType', Value => 'RT::System');
+- warn "Fixing Topic ".$topic->Id." to refer to RT::System: $msg";
+- }
+- }
+-}
+-
+-{ # update ObjectTopics
+- use RT::ObjectTopics;
+- my $otopics = RT::ObjectTopics->new(RT->SystemUser);
+- $otopics->UnLimit;
+- while ( my $otopic = $otopics->Next ) {
+- if ( $otopic->ObjectType eq 'RT::FM::Article' ) {
+- my ($ret, $msg) = $otopic->SetObjectType('RT::Article');
+- warn "Fixing Topic ".$otopic->Topic." to apply to article: $msg";
+- }
+- }
+-}
+-
+-{ # update Links
+- use RT::Links;
+- my $links = RT::Links->new(RT->SystemUser);
+- $links->Limit(FIELD => 'Base', VALUE => 'rtfm', OPERATOR => 'LIKE', SUBCLAUSE => 'stopanding', ENTRYAGGREGATOR => 'OR');
+- $links->Limit(FIELD => 'Target', VALUE => 'rtfm', OPERATOR => 'LIKE', SUBCLAUSE => 'stopanding', ENTRYAGGREGATOR => 'OR' );
+- while ( my $link = $links->Next ) {
+- my $base = $link->__Value('Base');
+- my $target = $link->__Value('Target');
+- if ( $base =~ s/rtfm/article/i ) {
+- my ($ret, $msg) = $link->__Set( Field => 'Base', Value => $base );
+- warn "Updating base to $base: $msg for link ".$link->id;
+- }
+- if ( $target =~ s/rtfm/article/i ) {
+- my ($ret, $msg) = $link->__Set( Field => 'Target', Value => $target );
+- warn "Updating target to $target: $msg for link ".$link->id;
+- }
+-
+- }
+-}
+-
+-{ # update Transactions
+- # we only keep article transactions at this point
+- no warnings 'once';
+- use RT::Transactions;
+- # Next calls Type to check readability and Type calls _Accessible
+- # which called CurrentUserCanSee which calls Object which tries to instantiate
+- # an RT::FM::Article. Rather than a shim RT::FM::Article class, I'm just avoiding
+- # the ACL check since we're running around as the superuser.
+- local *RT::Transaction::Type = sub { shift->__Value('Type') };
+- my $transactions = RT::Transactions->new(RT->SystemUser);
+- $transactions->Limit( FIELD => 'ObjectType', VALUE => 'RT::FM::Article' );
+- while ( my $t = $transactions->Next ) {
+- my ($ret, $msg) = $t->__Set( Field => 'ObjectType', Value => 'RT::Article' );
+- warn "Updated Transaction ".$t->Id." to point to RT::Article";
+- }
+-
+- # we also need to change links that point to articles
+- $transactions = RT::Transactions->new(RT->SystemUser);
+- $transactions->Limit( FIELD => 'Type', VALUE => 'AddLink' );
+- $transactions->Limit( FIELD => 'NewValue', VALUE => 'rtfm', OPERATOR => 'LIKE' );
+- while ( my $t = $transactions->Next ) {
+- my $value = $t->__Value('NewValue');
+- $value =~ s/rtfm/article/;
+- my ($ret, $msg) = $t->__Set( Field => 'NewValue', Value => $value );
+- warn "Updated Transaction ".$t->Id." to link to $value";
+- }
+-}
+-
+-{ # update Attributes
+- # these are all things we should make real columns someday
+- use RT::Attributes;
+- my $attributes = RT::Attributes->new(RT->SystemUser);
+- $attributes->Limit( FIELD => 'ObjectType', VALUE => 'RT::FM::Class' );
+- while ( my $a = $attributes->Next ) {
+- my ($ret,$msg) = $a->__Set( Field => 'ObjectType', Value => 'RT::Class' );
+- warn "Updating Attribute ".$a->Name." to point to RT::Class";
+- }
+-}
+diff --git a/etc/upgrade/vulnerable-passwords b/etc/upgrade/vulnerable-passwords
+deleted file mode 100755
+index 1785fa9..0000000
+--- a/etc/upgrade/vulnerable-passwords
++++ /dev/null
+@@ -1,142 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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 lib "local/lib";
+-use lib "lib";
+-
+-use RT;
+-RT::LoadConfig;
+-RT::Init;
+-
+-$| = 1;
+-
+-use Getopt::Long;
+-use Digest::SHA;
+-my $fix;
+-GetOptions("fix!" => \$fix);
+-
+-use RT::Users;
+-my $users = RT::Users->new( $RT::SystemUser );
+-$users->Limit(
+- FIELD => 'Password',
+- OPERATOR => 'IS NOT',
+- VALUE => 'NULL',
+- ENTRYAGGREGATOR => 'AND',
+-);
+-$users->Limit(
+- FIELD => 'Password',
+- OPERATOR => '!=',
+- VALUE => '*NO-PASSWORD*',
+- ENTRYAGGREGATOR => 'AND',
+-);
+-$users->Limit(
+- FIELD => 'Password',
+- OPERATOR => 'NOT STARTSWITH',
+- VALUE => '!',
+- ENTRYAGGREGATOR => 'AND',
+-);
+-push @{$users->{'restrictions'}{ "main.Password" }}, "AND", {
+- field => 'LENGTH(main.Password)',
+- op => '<',
+- value => '40',
+-};
+-
+-# we want to update passwords on disabled users
+-$users->{'find_disabled_rows'} = 1;
+-
+-my $count = $users->Count;
+-if ($count == 0) {
+- print "No users with unsalted or weak cryptography found.\n";
+- exit 0;
+-}
+-
+-if ($fix) {
+- print "Upgrading $count users...\n";
+- while (my $u = $users->Next) {
+- my $stored = $u->__Value("Password");
+- my $raw;
+- if (length $stored == 32) {
+- $raw = pack("H*",$stored);
+- } elsif (length $stored == 22) {
+- $raw = MIME::Base64::decode_base64($stored);
+- } elsif (length $stored == 13) {
+- printf "%20s => Old crypt() format, cannot upgrade\n", $u->Name;
+- } else {
+- printf "%20s => Unknown password format!\n", $u->Name;
+- }
+- next unless $raw;
+-
+- my $salt = pack("C4",map{int rand(256)} 1..4);
+- my $sha = Digest::SHA::sha256(
+- $salt . $raw
+- );
+- $u->_Set(
+- Field => "Password",
+- Value => MIME::Base64::encode_base64(
+- $salt . substr($sha,0,26), ""),
+- );
+- }
+- print "Done.\n";
+- exit 0;
+-} else {
+- if ($count < 20) {
+- print "$count users found with unsalted or weak-cryptography passwords:\n";
+- print " Id | Name\n", "-"x9, "+", "-"x9, "\n";
+- while (my $u = $users->Next) {
+- printf "%8d | %s\n", $u->Id, $u->Name;
+- }
+- } else {
+- print "$count users found with unsalted or weak-cryptography passwords\n";
+- }
+-
+- print "\n", "Run again with --fix to upgrade.\n";
+- exit 1;
+-}
+diff --git a/lib/RT/Generated.pm b/lib/RT/Generated.pm
+deleted file mode 100644
+index 9240880..0000000
+--- a/lib/RT/Generated.pm
++++ /dev/null
+@@ -1,81 +0,0 @@
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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 = '4.0.22';
+-
+-
+-
+-$BasePath = '/opt/rt4';
+-$EtcPath = 'etc';
+-$BinPath = 'bin';
+-$SbinPath = 'sbin';
+-$VarPath = 'var';
+-$LexiconPath = 'share/po';
+-$PluginPath = 'plugins';
+-$LocalPath = 'local';
+-$LocalEtcPath = 'local/etc';
+-$LocalLibPath = 'local/lib';
+-$LocalLexiconPath = 'local/po';
+-$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;
+diff --git a/sbin/rt-attributes-viewer b/sbin/rt-attributes-viewer
+deleted file mode 100755
+index 0b24928..0000000
+--- a/sbin/rt-attributes-viewer
++++ /dev/null
+@@ -1,122 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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;
+-
+-# fix lib paths, some may be relative
+-BEGIN {
+- require File::Spec;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- }
+- else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-}
+-
+-use Getopt::Long;
+-my %opt;
+-GetOptions( \%opt, 'help|h', );
+-
+-my $id = shift;
+-
+-if ( $opt{help} || !$id ) {
+- require Pod::Usage;
+- Pod::Usage::pod2usage({ verbose => 2 });
+- exit;
+-}
+-
+-require RT;
+-RT::LoadConfig();
+-RT::Init();
+-
+-require RT::Attribute;
+-my $attr = RT::Attribute->new( RT->SystemUser );
+-$attr->Load( $id );
+-unless ( $attr->id ) {
+- print STDERR "Couldn't load attribute #$id\n";
+- exit 1;
+-}
+-
+-my %res = ();
+-$res{$_} = $attr->$_() foreach qw(ObjectType ObjectId Name Description Content ContentType);
+-
+-use Data::Dumper;
+-print "Content of attribute #$id: ". Dumper( \%res );
+-
+-__END__
+-
+-=head1 NAME
+-
+-rt-attributes-viewer - show the content of an attribute
+-
+-=head1 SYNOPSIS
+-
+- # show the content of attribute 2
+- rt-attributes-viewer 2
+-
+-=head1 DESCRIPTION
+-
+-This script deserializes and print content of an attribute defined
+-by <attribute id>. May be useful for developers and for troubleshooting
+-problems.
+-
+diff --git a/sbin/rt-clean-sessions b/sbin/rt-clean-sessions
+deleted file mode 100755
+index 7b18b58..0000000
+--- a/sbin/rt-clean-sessions
++++ /dev/null
+@@ -1,190 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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;
+-
+-# fix lib paths, some may be relative
+-BEGIN {
+- require File::Spec;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- }
+- else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-}
+-
+-use Getopt::Long;
+-my %opt;
+-GetOptions( \%opt, "older=s", "debug", "help|h", "skip-user" );
+-
+-
+-if ( $opt{help} ) {
+- require Pod::Usage;
+- Pod::Usage::pod2usage({ verbose => 2 });
+- exit;
+-}
+-
+-
+-if( $opt{'older'} ) {
+- unless( $opt{'older'} =~ /^\s*([0-9]+)\s*(H|D|M|Y)?$/i ) {
+- print STDERR "wrong format of the 'older' argumnet\n";
+- exit(1);
+- }
+- my ($num,$unit) = ($1, uc($2 ||'D'));
+- my %factor = ( H => 60*60 );
+- $factor{'D'} = $factor{'H'}*24;
+- $factor{'M'} = $factor{'D'}*31;
+- $factor{'Y'} = $factor{'D'}*365;
+- $opt{'older'} = $num * $factor{ $unit };
+-}
+-
+-require RT;
+-RT::LoadConfig();
+-
+-if( $opt{'debug'} ) {
+- RT->Config->Set( LogToScreen => 'debug' );
+-} else {
+- RT->Config->Set( LogToScreen => undef );
+-}
+-
+-RT::ConnectToDatabase();
+-RT::InitLogging();
+-
+-require RT::Interface::Web::Session;
+-
+-my $alogoff = int RT->Config->Get('AutoLogoff');
+-if ( $opt{'older'} or $alogoff ) {
+- my $min;
+- foreach ($alogoff*60, $opt{'older'}) {
+- next unless $_;
+- $min = $_ unless $min;
+- $min = $_ if $_ < $min;
+- }
+-
+- RT::Interface::Web::Session->ClearOld( $min );
+-}
+-
+-RT::Interface::Web::Session->ClearByUser
+- unless $opt{'skip-user'};
+-
+-exit(0);
+-
+-__END__
+-
+-=head1 NAME
+-
+-rt-clean-sessions - clean old and duplicate RT sessions
+-
+-=head1 SYNOPSIS
+-
+- rt-clean-sessions [--debug] [--older <NUM>[H|D|M|Y]]
+-
+- rt-clean-sessions
+- rt-clean-sessions --debug
+- rt-clean-sessions --older 10D
+- rt-clean-sessions --debug --older 1M
+- rt-clean-sessions --older 10D --skip-user
+-
+-=head1 DESCRIPTION
+-
+-Script cleans RT sessions from DB or dir with sessions data.
+-Leaves in DB only one session per RT user and sessions that aren't older
+-than specified(see options).
+-
+-Script is safe because data in the sessions is temporary and can be deleted.
+-
+-=head1 OPTIONS
+-
+-=over 4
+-
+-=item older
+-
+-Date interval in the C<< <NUM>[<unit>] >> format. Default unit is D(ays),
+-H(our), M(onth) and Y(ear) are also supported.
+-
+-For example: C<rt-clean-sessions --older 1M> would delete all sessions that are
+-older than 1 month.
+-
+-=item skip-user
+-
+-By default only one session per user left in the DB, so users that have
+-sessions on multiple computers or in different browsers will be logged out.
+-Use this option to avoid this.
+-
+-=item debug
+-
+-Turn on debug output.
+-
+-=back
+-
+-=head1 NOTES
+-
+-Functionality similar to this is implemented in
+-html/Elements/SetupSessionCookie ; however, that does not guarantee
+-that a session will be removed from disk and database soon after the
+-timeout expires. This script, if run from a cron job, will ensure
+-that the timed out sessions are actually removed from disk; the Mason
+-component just ensures that the old sessions are not reusable before
+-the cron job gets to them.
+-
+-=cut
+diff --git a/sbin/rt-dump-metadata b/sbin/rt-dump-metadata
+deleted file mode 100755
+index 84f5531..0000000
+--- a/sbin/rt-dump-metadata
++++ /dev/null
+@@ -1,357 +0,0 @@
+-#!/usr/bin/perl -w
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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;
+-
+-# As we specify that XML is UTF-8 and we output it to STDOUT, we must be sure
+-# it is UTF-8 so further XMLin will not break
+-binmode( STDOUT, ":utf8" );
+-
+-# fix lib paths, some may be relative
+-BEGIN {
+- require File::Spec;
+- my @libs = ( "lib", "local/lib" );
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- } else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-
+-}
+-
+-use Getopt::Long;
+-my %opt;
+-GetOptions( \%opt, "help|h",
+- "limit-to-privileged|l",
+- "skip-disabled|s",
+- "all|a",
+-);
+-
+-if ( $opt{help} ) {
+- require Pod::Usage;
+- Pod::Usage::pod2usage( { verbose => 2 } );
+- exit;
+-}
+-
+-require RT;
+-require XML::Simple;
+-
+-RT::LoadConfig();
+-RT::Init();
+-
+-my %RV;
+-my %Ignore = (
+- All => [
+- qw(
+- id Created Creator LastUpdated LastUpdatedBy
+- )
+- ],
+- Templates => [
+- qw(
+- TranslationOf
+- )
+- ],
+-);
+-
+-my $SystemUserId = RT->SystemUser->Id;
+-my @classes = qw(
+- Users Groups Queues ScripActions ScripConditions
+- Templates Scrips ACL CustomFields
+- );
+-foreach my $class (@classes) {
+- require "RT/$class.pm";
+- my $objects = "RT::$class"->new( RT->SystemUser );
+- $objects->{find_disabled_rows} = 1 unless $opt{'skip-disabled'};
+- $objects->UnLimit;
+- $objects->LimitToPrivileged if $class eq 'Users'
+- && $opt{'limit-to-privileged'};
+- $objects->Limit(
+- FIELD => 'Domain',
+- OPERATOR => '=',
+- VALUE => 'UserDefined'
+- ) if $class eq 'Groups';
+-
+- if ( $class eq 'CustomFields' ) {
+- $objects->OrderByCols(
+- { FIELD => 'LookupType' },
+- { FIELD => 'SortOrder' },
+- { FIELD => 'Id' },
+- );
+- } else {
+- $objects->OrderBy( FIELD => 'Id' );
+- }
+-
+- unless ($opt{all}) {
+- next if $class eq 'ACL'; # XXX - would go into infinite loop - XXX
+- $objects->Limit(
+- FIELD => 'LastUpdatedBy',
+- OPERATOR => '!=',
+- VALUE => $SystemUserId
+- ) unless $class eq 'Groups';
+- $objects->Limit(
+- FIELD => 'Id',
+- OPERATOR => '!=',
+- VALUE => $SystemUserId
+- ) if $class eq 'Users';
+- }
+-
+- my %fields;
+-OBJECT:
+- while ( my $obj = $objects->Next ) {
+- next
+- if $obj->can('LastUpdatedBy')
+- and $obj->LastUpdatedBy == $SystemUserId;
+-
+- if ( !%fields ) {
+- %fields = map { $_ => 1 } keys %{ $obj->_ClassAccessible };
+- delete @fields{ @{ $Ignore{$class} ||= [] },
+- @{ $Ignore{All} ||= [] }, };
+- }
+-
+- my $rv;
+-
+- if ( $class ne 'ACL' ) {
+- # next if $obj-> # skip default names
+- foreach my $field ( sort keys %fields ) {
+- my $value = $obj->__Value($field);
+- $rv->{$field} = $value if ( defined($value) && length($value) );
+- }
+- delete $rv->{Disabled} unless $rv->{Disabled};
+-
+- foreach my $record ( map { /ACL/ ? 'ACE' : substr( $_, 0, -1 ) }
+- @classes )
+- {
+- foreach my $key ( map "$record$_", ( '', 'Id' ) ) {
+- next unless exists $rv->{$key};
+- my $id = $rv->{$key} or next;
+- my $obj = "RT::$record"->new( RT->SystemUser );
+- $obj->LoadByCols( Id => $id ) or next;
+- $rv->{$key} = $obj->__Value('Name') || 0;
+- }
+- }
+-
+- if ( $class eq 'Users' and defined $obj->Privileged ) {
+- $rv->{Privileged} = int( $obj->Privileged );
+- } elsif ( $class eq 'CustomFields' ) {
+- my $values = $obj->Values;
+- while ( my $value = $values->Next ) {
+- push @{ $rv->{Values} }, {
+- map { ( $_ => $value->__Value($_) ) }
+- qw(
+- Name Description SortOrder
+- ),
+- };
+- }
+- if ( $obj->LookupType eq 'RT::Queue-RT::Ticket' ) {
+- # XXX-TODO: unused CF's turn into global CF when importing
+- # as the sub InsertData in RT::Handle creates a global CF
+- # when no queue is specified.
+- $rv->{Queue} = [];
+- my $applies = $obj->AppliedTo;
+- while ( my $queue = $applies->Next ) {
+- push @{ $rv->{Queue} }, $queue->Name;
+- }
+- }
+- }
+- }
+- else {
+- # 1) pick the right
+- $rv->{Right} = $obj->RightName;
+-
+- # 2) Pick a level: Granted on Queue, CF, CF+Queue, or Globally?
+- for ( $obj->ObjectType ) {
+- if ( /^RT::Queue$/ ) {
+- next OBJECT if $opt{'skip-disabled'} && $obj->Object->Disabled;
+- $rv->{Queue} = $obj->Object->Name;
+- }
+- elsif ( /^RT::CustomField$/ ) {
+- next OBJECT if $opt{'skip-disabled'} && $obj->Object->Disabled;
+- $rv->{CF} = $obj->Object->Name;
+- }
+- elsif ( /^RT::Group$/ ) {
+- # No support for RT::Group ACLs in RT::Handle yet.
+- next OBJECT;
+- }
+- elsif ( /^RT::System$/ ) {
+- # skip setting anything on $rv;
+- # "Specifying none of the above will get you a global right."
+- }
+- }
+-
+- # 3) Pick a Principal; User or Group or Role
+- if ( $obj->PrincipalType eq 'Group' ) {
+- next OBJECT if $opt{'skip-disabled'} && $obj->PrincipalObj->Disabled;
+- my $group = $obj->PrincipalObj->Object;
+- for ( $group->Domain ) {
+- # An internal user group
+- if ( /^SystemInternal$/ ) {
+- $rv->{GroupDomain} = $group->Domain;
+- $rv->{GroupType} = $group->Type;
+- }
+- # An individual user
+- elsif ( /^ACLEquivalence$/ ) {
+- my $member = $group->MembersObj->Next->MemberObj;
+- next OBJECT if $opt{'skip-disabled'} && $member->Disabled;
+- $rv->{UserId} = $member->Object->Name;
+- }
+- # A group you created
+- elsif ( /^UserDefined$/ ) {
+- $rv->{GroupDomain} = 'UserDefined';
+- $rv->{GroupId} = $group->Name;
+- }
+- }
+- } else {
+- $rv->{GroupType} = $obj->PrincipalType;
+- # A system-level role
+- if ( $obj->ObjectType eq 'RT::System' ) {
+- $rv->{GroupDomain} = 'RT::System-Role';
+- }
+- # A queue-level role
+- elsif ( $obj->ObjectType eq 'RT::Queue' ) {
+- $rv->{GroupDomain} = 'RT::Queue-Role';
+- }
+- }
+- if ( $obj->LookupType eq 'RT::Queue-RT::Ticket' ) {
+- # XXX-TODO: unused CF's turn into global CF when importing
+- # as the sub InsertData in RT::Handle creates a global CF
+- # when no queue is specified.
+- $rv->{Queue} = [];
+- my $applies = $obj->AppliedTo;
+- while ( my $queue = $applies->Next ) {
+- push @{ $rv->{Queue} }, $queue->Name;
+- }
+- }
+- }
+-
+- if ( eval { require RT::Attributes; 1 } ) {
+- my $attributes = $obj->Attributes;
+- while ( my $attribute = $attributes->Next ) {
+- my $content = $attribute->Content;
+- if ( $class eq 'Users' and $attribute->Name eq 'Bookmarks' ) {
+- next;
+- }
+- $rv->{Attributes}{ $attribute->Name } = $content
+- if length($content);
+- }
+- }
+-
+- push @{ $RV{$class} }, $rv;
+- }
+-}
+-
+-print(<< ".");
+-no strict; use XML::Simple; *_ = XMLin(do { local \$/; readline(DATA) }, ForceArray => [qw(
+- @classes Values
+-)], NoAttr => 1, SuppressEmpty => ''); *\$_ = (\$_{\$_} || []) for keys \%_; 1; # vim: ft=xml
+-__DATA__
+-.
+-
+-print XML::Simple::XMLout(
+- { map { ( $_ => ( $RV{$_} || [] ) ) } @classes },
+- RootName => 'InitialData',
+- NoAttr => 1,
+- SuppressEmpty => '',
+- XMLDecl => '<?xml version="1.0" encoding="UTF-8"?>',
+-);
+-
+-__END__
+-
+-=head1 NAME
+-
+-rt-dump-metadata - dump configuration metadata from an RT database
+-
+-=head1 SYNOPSIS
+-
+- rt-dump-metdata [--all]
+-
+-=head1 DESCRIPTION
+-
+-C<rt-dump-metadata> is a tool that dumps configuration metadata from the
+-Request Tracker database into XML format, suitable for feeding into
+-C<rt-setup-database>. To dump and load a full RT database, you should generally
+-use the native database tools instead, as well as performing any necessary
+-steps from UPGRADING.
+-
+-This is NOT a tool for backing up an RT database. See also
+-L<docs/initialdata> for more straightforward means of importing data.
+-
+-=head1 OPTIONS
+-
+-=over
+-
+-=item C<--all> or C<-a>
+-
+-When run with C<--all>, the dump will include all configuration
+-metadata; otherwise, the metadata dump will only include 'local'
+-configuration changes, i.e. those done manually in the web interface.
+-
+-=item C<--limit-to-privileged> or C<-l>
+-
+-Causes the dumper to only dump privileged users.
+-
+-=item C<--skip-disabled> or C<-s>
+-
+-Ignores disabled rows in the database.
+-
+-=back
+-
+-=cut
+-
+diff --git a/sbin/rt-email-dashboards b/sbin/rt-email-dashboards
+deleted file mode 100755
+index b923e2d..0000000
+--- a/sbin/rt-email-dashboards
++++ /dev/null
+@@ -1,173 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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;
+-
+-# fix lib paths, some may be relative
+-BEGIN {
+- require File::Spec;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- }
+- else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-
+-}
+-
+-# Read in the options
+-my %opts;
+-use Getopt::Long;
+-GetOptions( \%opts,
+- "help|h", "dryrun", "time=i", "epoch=i", "all"
+-);
+-
+-if ($opts{'help'}) {
+- require Pod::Usage;
+- print Pod::Usage::pod2usage(-verbose => 2);
+- exit;
+-}
+-
+-require RT;
+-require RT::Interface::CLI;
+-RT::Interface::CLI->import(qw{ CleanEnv loc });
+-
+-# Clean out all the nasties from the environment
+-CleanEnv();
+-
+-# Load the config file
+-RT::LoadConfig();
+-
+-# Connect to the database and get RT::SystemUser and RT::Nobody loaded
+-RT::Init();
+-
+-require RT::Dashboard::Mailer;
+-RT::Dashboard::Mailer->MailDashboards(
+- All => $opts{all},
+- DryRun => $opts{dryrun},
+- Time => ($opts{time} || $opts{epoch} || time), # epoch is the old-style
+- Opts => \%opts,
+-);
+-
+-=head1 NAME
+-
+-rt-email-dashboards - Send email dashboards
+-
+-=head1 SYNOPSIS
+-
+- rt-email-dashboards [options]
+-
+-=head1 DESCRIPTION
+-
+-This tool will send users email based on how they have subscribed to
+-dashboards. A dashboard is a set of saved searches, the subscription controls
+-how often that dashboard is sent and how it's displayed.
+-
+-Each subscription has an hour, and possibly day of week or day of month. These
+-are taken to be in the user's timezone if available, UTC otherwise.
+-
+-=head1 SETUP
+-
+-You'll need to have cron run this script every hour. Here's an example crontab
+-entry to do this.
+-
+- 0 * * * * /usr/bin/perl /opt/rt4/local/sbin/rt-email-dashboards
+-
+-This will run the script every hour on the hour. This may need some further
+-tweaking to be run as the correct user.
+-
+-=head1 OPTIONS
+-
+-This tool supports a few options. Most are for debugging.
+-
+-=over 8
+-
+-=item -h
+-
+-=item --help
+-
+-Display this documentation
+-
+-=item --dryrun
+-
+-Figure out which dashboards would be sent, but don't actually generate or email
+-any of them
+-
+-=item --time SECONDS
+-
+-Instead of using the current time to figure out which dashboards should be
+-sent, use SECONDS (usually since midnight Jan 1st, 1970, so C<1192216018> would
+-be Oct 12 19:06:58 GMT 2007).
+-
+-=item --epoch SECONDS
+-
+-Back-compat for --time SECONDS.
+-
+-=item --all
+-
+-Ignore subscription frequency when considering each dashboard (should only be
+-used with --dryrun for testing and debugging)
+-
+-=back
+-
+-=cut
+-
+diff --git a/sbin/rt-email-digest b/sbin/rt-email-digest
+deleted file mode 100755
+index cb4d031..0000000
+--- a/sbin/rt-email-digest
++++ /dev/null
+@@ -1,380 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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;
+-
+-BEGIN {
+- require File::Spec;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- }
+- else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-
+-}
+-
+-use Date::Format qw( strftime );
+-use Getopt::Long;
+-use RT;
+-use RT::Interface::CLI qw( CleanEnv loc );
+-use RT::Interface::Email;
+-
+-CleanEnv();
+-RT::LoadConfig();
+-RT::Init();
+-
+-sub usage {
+- my ($error) = @_;
+- print loc("Usage:") . " $0 -m (daily|weekly) [--print] [--help]\n";
+- print loc(
+- "[_1] is a utility, meant to be run from cron, that dispatches all deferred RT notifications as a per-user digest.",
+- $0
+- ) . "\n";
+- print "\n\t-m, --mode\t"
+- . loc("Specify whether this is a daily or weekly run.") . "\n";
+- print "\t-p, --print\t"
+- . loc("Print the resulting digest messages to STDOUT; don't mail them. Do not mark them as sent")
+- . "\n";
+- print "\t-v, --verbose\t" . loc("Give output even on messages successfully sent") . "\n";
+- print "\t-h, --help\t" . loc("Print this message") . "\n";
+-
+- if ( $error eq 'help' ) {
+- exit 0;
+- } else {
+- print loc("Error") . ": " . loc($error) . "\n";
+- exit 1;
+- }
+-}
+-
+-my ( $frequency, $print, $verbose, $help ) = ( '', '', '', '' );
+-GetOptions(
+- 'mode=s' => \$frequency,
+- 'print' => \$print,
+- 'verbose' => \$verbose,
+- 'help' => \$help,
+-);
+-
+-usage('help') if $help;
+-usage("Mode argument must be 'daily' or 'weekly'")
+- unless $frequency =~ /^(daily|weekly)$/;
+-
+-run( $frequency, $print );
+-
+-sub run {
+- my $frequency = shift;
+- my $print = shift;
+-
+-## Find all the tickets that have been modified within the time frame
+-## described by $frequency.
+-
+- my ( $all_digest, $sent_transactions ) = find_transactions($frequency);
+-
+-## Iterate through our huge hash constructing the digest message
+-## for each user and sending it.
+-
+- foreach my $user ( keys %$all_digest ) {
+- my ( $contents_list, $contents_body ) = build_digest_for_user( $user, $all_digest->{$user} );
+- # Now we have a content head and a content body. We can send a message.
+- if ( send_digest( $user, $contents_list, $contents_body ) ) {
+- print "Sent message to $user\n" if $verbose;
+- mark_transactions_sent( $frequency, $user, values %{$sent_transactions->{$user}} ) unless ($print);
+- } else {
+- print "Failed to send message to $user\n";
+- }
+- }
+-}
+-exit 0;
+-
+-# Subroutines.
+-
+-sub send_digest {
+- my ( $to, $index, $messages ) = @_;
+-
+- # Combine the index and the messages.
+-
+- my $body = "============== Tickets with activity in the last "
+- . ( $frequency eq 'daily' ? "day" : "seven days" ) . "\n\n";
+-
+- $body .= $index;
+- $body .= "\n\n============== Messages recorded in the last "
+- . ( $frequency eq 'daily' ? "day" : "seven days" ) . "\n\n";
+- $body .= $messages;
+-
+- # Load our template. If we cannot load the template, abort
+- # immediately rather than failing through many loops.
+- my $digest_template = RT::Template->new( RT->SystemUser );
+- my ( $ret, $msg ) = $digest_template->Load('Email Digest');
+- unless ($ret) {
+- print loc("Failed to load template")
+- . " 'Email Digest': "
+- . $msg
+- . ". Cannot continue.\n";
+- exit 1;
+- }
+- ( $ret, $msg ) = $digest_template->Parse( Argument => $body );
+- unless ($ret) {
+- print loc("Failed to parse template")
+- . " 'Email Digest'. Cannot continue.\n";
+- exit 1;
+- }
+-
+- # Set our sender and recipient.
+- $digest_template->MIMEObj->head->replace(
+- 'From', Encode::encode( "UTF-8", RT::Config->Get('CorrespondAddress') ) );
+- $digest_template->MIMEObj->head->replace(
+- 'To', Encode::encode( "UTF-8", $to ) );
+-
+- if ($print) {
+- $digest_template->MIMEObj->print;
+- return 1;
+- } else {
+- return RT::Interface::Email::SendEmail( Entity => $digest_template->MIMEObj)
+- }
+-}
+-
+-# =item mark_transactions_sent( $frequency, $user, @txn_list );
+-#
+-# Takes a frequency string (either 'daily' or 'weekly'), a user and one or more
+-# transaction objects as its arguments. Marks the given deferred
+-# notifications as sent.
+-#
+-# =cut
+-
+-sub mark_transactions_sent {
+- my ( $freq, $user, @txns ) = @_;
+- return unless $freq =~ /(daily|weekly)/;
+- return unless @txns;
+- foreach my $txn (@txns) {
+-
+- # Grab the attribute, mark the "sent" as true, and store the new
+- # value.
+- if ( my $attr = $txn->FirstAttribute('DeferredRecipients') ) {
+- my $deferred = $attr->Content;
+- $deferred->{$freq}->{$user}->{'_sent'} = 1;
+- $txn->SetAttribute(
+- Name => 'DeferredRecipients',
+- Description => 'Deferred recipients for this message',
+- Content => $deferred,
+- );
+- }
+- }
+-}
+-
+-sub since_date {
+- my $frequency = shift;
+-
+- # Specify a short time for digest overlap, in case we aren't starting
+- # this process exactly on time.
+- my $OVERLAP_HEDGE = -30;
+-
+- my $since_date = RT::Date->new( RT->SystemUser );
+- $since_date->Set( Format => 'unix', Value => time() );
+- if ( $frequency eq 'daily' ) {
+- $since_date->AddDays(-1);
+- } else {
+- $since_date->AddDays(-7);
+- }
+-
+- $since_date->AddSeconds($OVERLAP_HEDGE);
+-
+- return $since_date;
+-}
+-
+-sub find_transactions {
+- my $frequency = shift;
+- my $since_date = since_date($frequency);
+-
+- my $txns = RT::Transactions->new( RT->SystemUser );
+-
+- # First limit to recent transactions.
+- $txns->Limit(
+- FIELD => 'Created',
+- OPERATOR => '>',
+- VALUE => $since_date->ISO
+- );
+-
+- # Next limit to ticket transactions.
+- $txns->Limit(
+- FIELD => 'ObjectType',
+- OPERATOR => '=',
+- VALUE => 'RT::Ticket',
+- ENTRYAGGREGATOR => 'AND'
+- );
+- my $all_digest = {};
+- my $sent_transactions = {};
+-
+- while ( my $txn = $txns->Next ) {
+- my $ticket = $txn->Ticket;
+- my $queue = $txn->TicketObj->QueueObj->Name;
+- # Xxx todo - may clobber if two queues have the same name
+- foreach my $user ( $txn->DeferredRecipients($frequency) ) {
+- $all_digest->{$user}->{$queue}->{$ticket}->{ $txn->id } = $txn->ContentObj;
+- $sent_transactions->{$user}->{ $txn->id } = $txn;
+- }
+- }
+-
+- return ( $all_digest, $sent_transactions );
+-}
+-
+-sub build_digest_for_user {
+- my $user = shift;
+- my $user_digest = shift;
+-
+- my $contents_list = ''; # Holds the digest index.
+- my $contents_body = ''; # Holds the digest body.
+-
+- # Has the user been disabled since a message was deferred on his/her
+- # behalf?
+- my $user_obj = RT::User->new( RT->SystemUser );
+- $user_obj->LoadByEmail($user);
+- if ( $user_obj->PrincipalObj->Disabled ) {
+- print STDERR loc("Skipping disabled user") . " $user\n";
+- next;
+- }
+-
+- print loc("Message for user") . " $user:\n\n" if $print;
+- foreach my $queue ( keys %$user_digest ) {
+- $contents_list .= "Queue $queue:\n";
+- $contents_body .= "Queue $queue:\n";
+- foreach my $ticket ( sort keys %{ $user_digest->{$queue} } ) {
+- my $tkt_txns = $user_digest->{$queue}->{$ticket};
+- my $ticket_obj = RT::Ticket->new( RT->SystemUser );
+- $ticket_obj->Load($ticket);
+-
+- # Spit out the index entry for this ticket.
+- my $ticket_title = sprintf(
+- "#%d %s [%s]\t%s\n",
+- $ticket, $ticket_obj->Status, $ticket_obj->OwnerObj->Name,
+- $ticket_obj->Subject
+- );
+- $contents_list .= $ticket_title;
+-
+- # Spit out the messages for the transactions on this ticket.
+- $contents_body .= "\n== $ticket_title\n";
+- foreach my $txn ( sort keys %$tkt_txns ) {
+- my $msg = $tkt_txns->{$txn};
+-
+- # $msg contains an RT::Attachment with our outgoing
+- # message. Print a few headers for clarity's sake.
+- $contents_body .= "From: " . $msg->GetHeader('From') . "\n";
+- my $date = $msg->GetHeader('Date ');
+- unless ($date) {
+- my $txn_obj = RT::Transaction->new( RT->SystemUser );
+- $txn_obj->Load($txn);
+- my $date_obj = RT::Date->new( RT->SystemUser );
+- $date_obj->Set(
+- Format => 'sql',
+- Value => $txn_obj->Created
+- );
+- $date = strftime( '%a, %d %b %Y %H:%M:%S %z',
+- @{ [ localtime( $date_obj->Unix ) ] } );
+- }
+- $contents_body .= "Date: $date\n\n";
+- $contents_body .= $msg->Content . "\n";
+- $contents_body .= "-------\n";
+- } # foreach transaction
+- } # foreach ticket
+- } # foreach queue
+-
+- return ( $contents_list, $contents_body );
+-
+-}
+-
+-__END__
+-
+-=head1 NAME
+-
+-rt-email-digest - dispatch deferred notifications as a per-user digest
+-
+-=head1 SYNOPSIS
+-
+- rt-email-digest -m (daily|weekly) [--print] [--help]
+-
+-=head1 DESCRIPTION
+-
+-This script is a tool to dispatch all deferred RT notifications as a per-user
+-object.
+-
+-=head1 OPTIONS
+-
+-=over
+-
+-=item mode
+-
+-Specify whether this is a daily or weekly run.
+-
+---mode is equal to -m
+-
+-=item print
+-
+-Print the resulting digest messages to STDOUT; don't mail them. Do not mark them as sent
+-
+---print is equal to -p
+-
+-=item help
+-
+-Print this message
+-
+---help is equal to -h
+-
+-=back
+diff --git a/sbin/rt-email-group-admin b/sbin/rt-email-group-admin
+deleted file mode 100755
+index 176b0b6..0000000
+--- a/sbin/rt-email-group-admin
++++ /dev/null
+@@ -1,527 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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-email-group-admin - Command line tool for administrating NotifyGroup actions
+-
+-=head1 SYNOPSIS
+-
+- rt-email-group-admin --list
+- rt-email-group-admin --create 'Notify foo team' --group Foo
+- rt-email-group-admin --create 'Notify foo team as comment' --comment --group Foo
+- rt-email-group-admin --create 'Notify group Foo and Bar' --group Foo --group Bar
+- rt-email-group-admin --create 'Notify user foo at bar.com' --user foo at bar.com
+- rt-email-group-admin --create 'Notify VIPs' --user vip1 at bar.com
+- rt-email-group-admin --add 'Notify VIPs' --user vip2 at bar.com --group vip1 --user vip3 at foo.com
+- rt-email-group-admin --rename 'Notify VIPs' --newname 'Inform VIPs'
+- rt-email-group-admin --switch 'Notify VIPs'
+- rt-email-group-admin --delete 'Notify user foo at bar.com'
+-
+-=head1 DESCRIPTION
+-
+-This script list, create, modify or delete scrip actions in the RT DB. Once
+-you've created an action you can use it in a scrip.
+-
+-For example you can create the following action using this script:
+-
+- rt-email-group-admin --create 'Notify developers' --group 'Development Team'
+-
+-Then you can add the followoing scrip to your Bugs queue:
+-
+- Condition: On Create
+- Action: Notify developers
+- Template: Transaction
+- Stage: TransactionCreate
+-
+-Your development team will be notified on every new ticket in the queue.
+-
+-=cut
+-
+-use warnings;
+-use strict;
+-
+-# fix lib paths, some may be relative
+-BEGIN {
+- require File::Spec;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- }
+- else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-
+-}
+-
+-use Getopt::Long qw(GetOptions);
+-Getopt::Long::Configure( "pass_through" );
+-
+-our $cmd = 'usage';
+-our $opts = {};
+-
+-sub parse_args {
+- my $tmp;
+- if ( GetOptions( 'list' => \$tmp ) && $tmp ) {
+- $cmd = 'list';
+- }
+- elsif ( GetOptions( 'create=s' => \$tmp ) && $tmp ) {
+- $cmd = 'create';
+- $opts->{'name'} = $tmp;
+- $opts->{'groups'} = [];
+- $opts->{'users'} = [];
+- GetOptions( 'comment' => \$opts->{'comment'} );
+- GetOptions( 'group:s@' => $opts->{'groups'} );
+- GetOptions( 'user:s@' => $opts->{'users'} );
+- unless ( @{ $opts->{'users'} } + @{ $opts->{'groups'} } ) {
+- usage();
+- exit(-1);
+- }
+- }
+- elsif ( GetOptions( 'add=s' => \$tmp ) && $tmp ) {
+- $cmd = 'add';
+- $opts->{'name'} = $tmp;
+- $opts->{'groups'} = [];
+- $opts->{'users'} = [];
+- GetOptions( 'group:s@' => $opts->{'groups'} );
+- GetOptions( 'user:s@' => $opts->{'users'} );
+- unless ( @{ $opts->{'users'} } + @{ $opts->{'groups'} } ) {
+- usage();
+- exit(-1);
+- }
+- }
+- elsif ( GetOptions( 'switch=s' => \$tmp ) && $tmp ) {
+- $cmd = 'switch';
+- $opts->{'name'} = $tmp;
+- }
+- elsif ( GetOptions( 'rename=s' => \$tmp ) && $tmp ) {
+- $cmd = 'rename';
+- $opts->{'name'} = $tmp;
+- GetOptions( 'newname=s' => \$opts->{'newname'} );
+- unless ( $opts->{'newname'} ) {
+- usage();
+- exit(-1);
+- }
+- }
+- elsif ( GetOptions( 'delete=s' => \$tmp ) && $tmp) {
+- $cmd = 'delete';
+- $opts->{'name'} = $tmp;
+- } else {
+- $cmd = 'usage';
+- }
+-
+- return;
+-}
+-
+-sub usage {
+- require Pod::Usage;
+- Pod::Usage::pod2usage({ verbose => 2 });
+-}
+-
+-my $help;
+-if ( GetOptions( 'help|h' => \$help ) && $help ) {
+- usage();
+- exit;
+-}
+-
+-parse_args();
+-
+-require RT;
+-RT->LoadConfig;
+-RT->Init;
+-
+-require RT::Principal;
+-require RT::User;
+-require RT::Group;
+-require RT::ScripActions;
+-
+-
+-{
+- eval "main::$cmd()";
+- if ( $@ ) {
+- print STDERR $@ ."\n";
+- }
+-}
+-
+-exit(0);
+-
+-=head1 USAGE
+-
+-rt-email-group-admin --COMMAND ARGS
+-
+-=head1 COMMANDS
+-
+-=head2 list
+-
+-Lists actions and its descriptions.
+-
+-=cut
+-
+-sub list {
+- my $actions = _get_our_actions();
+- while( my $a = $actions->Next ) {
+- _list( $a );
+- }
+- return;
+-}
+-
+-sub _list {
+- my $action = shift;
+-
+- print "Name: ". $action->Name() ."\n";
+- print "Module: ". $action->ExecModule() ."\n";
+-
+- my @princ = argument_to_list( $action );
+-
+- print "Members: \n";
+- foreach( @princ ) {
+- my $obj = RT::Principal->new( RT->SystemUser );
+- $obj->Load( $_ );
+- next unless $obj->id;
+-
+- print "\t". $obj->PrincipalType;
+- print "\t=> ". $obj->Object->Name;
+- print "(Disabled!!!)" if $obj->Disabled;
+- print "\n";
+- }
+- print "\n";
+- return;
+-}
+-
+-=head2 create NAME [--comment] [--group GNAME] [--user NAME-OR-EMAIL]
+-
+-Creates new action with NAME and adds users and/or groups to its
+-recipient list. Would be notify as comment if --comment specified. The
+-user, if specified, will be autocreated if necessary.
+-
+-=cut
+-
+-sub create {
+- my $actions = RT::ScripActions->new( RT->SystemUser );
+- $actions->Limit(
+- FIELD => 'Name',
+- VALUE => $opts->{'name'},
+- );
+- if ( $actions->Count ) {
+- print STDERR "ScripAction '". $opts->{'name'} ."' allready exists\n";
+- exit(-1);
+- }
+-
+- my @groups = _check_groups( @{ $opts->{'groups'} } );
+- my @users = _check_users( @{ $opts->{'users'} } );
+- unless ( @users + @groups ) {
+- print STDERR "List of groups and users is empty\n";
+- exit(-1);
+- }
+-
+- my $action = __create_empty( $opts->{'name'}, $opts->{'comment'} );
+-
+- __add( $action, $_ ) foreach( @users );
+- __add( $action, $_ ) foreach( @groups );
+-
+- return;
+-}
+-
+-sub __create_empty {
+- my $name = shift;
+- my $as_comment = shift || 0;
+- require RT::ScripAction;
+- my $action = RT::ScripAction->new( RT->SystemUser );
+- $action->Create(
+- Name => $name,
+- Description => "Created with rt-email-group-admin script",
+- ExecModule => $as_comment? 'NotifyGroupAsComment': 'NotifyGroup',
+- Argument => '',
+- );
+-
+- return $action;
+-}
+-
+-sub _check_groups
+-{
+- return map {$_->[1]}
+- grep { $_->[1] ? 1: do { print STDERR "Group '$_->[0]' skipped, doesn't exist\n"; 0; } }
+- map { [$_, __check_group($_)] } @_;
+-}
+-
+-sub __check_group
+-{
+- my $instance = shift;
+- require RT::Group;
+- my $obj = RT::Group->new( RT->SystemUser );
+- $obj->LoadUserDefinedGroup( $instance );
+- return $obj->id ? $obj : undef;
+-}
+-
+-sub _check_users
+-{
+- return map {$_->[1]}
+- grep { $_->[1] ? 1: do { print STDERR "User '$_->[0]' skipped, doesn't exist and couldn't autocreate\n"; 0; } }
+- map { [$_, __check_user($_)] } @_;
+-}
+-
+-sub __check_user
+-{
+- my $instance = shift;
+- require RT::User;
+- my $obj = RT::User->new( RT->SystemUser );
+- $obj->Load( $instance );
+- $obj->LoadByEmail( $instance )
+- if not $obj->id and $instance =~ /@/;
+-
+- unless ($obj->id) {
+- my ($ok, $msg) = $obj->Create(
+- Name => $instance,
+- EmailAddress => $instance,
+- Privileged => 0,
+- Comments => 'Autocreated when added to notify action via rt-email-group-admin',
+- );
+- print STDERR "Autocreate of user '$instance' failed: $msg\n"
+- unless $ok;
+- }
+-
+- return $obj->id ? $obj : undef;
+-}
+-
+-=head2 add NAME [--group GNAME] [--user NAME-OR-EMAIL]
+-
+-Adds groups and/or users to recipients of the action NAME. The user, if
+-specified, will be autocreated if necessary.
+-
+-=cut
+-
+-sub add {
+- my $action = _get_action_by_name( $opts->{'name'} );
+- unless ( $action ) {
+- print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n";
+- exit(-1);
+- }
+-
+- my @groups = _check_groups( @{ $opts->{'groups'} } );
+- my @users = _check_users( @{ $opts->{'users'} } );
+-
+- unless ( @users + @groups ) {
+- print STDERR "List of groups and users is empty\n";
+- exit(-1);
+- }
+-
+- __add( $action, $_ ) foreach @users;
+- __add( $action, $_ ) foreach @groups;
+-
+- return;
+-}
+-
+-sub __add
+-{
+- my $action = shift;
+- my $obj = shift;
+-
+- my @cur = argument_to_list( $action );
+-
+- my $id = $obj->id;
+- return if grep $_ == $id, @cur;
+-
+- push @cur, $id;
+-
+- return $action->__Set( Field => 'Argument', Value => join(',', @cur) );
+-}
+-
+-=head2 delete NAME
+-
+-Deletes action NAME if scrips doesn't use it.
+-
+-=cut
+-
+-sub delete {
+- my $action = _get_action_by_name( $opts->{'name'} );
+- unless ( $action ) {
+- print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n";
+- exit(-1);
+- }
+-
+- require RT::Scrips;
+- my $scrips = RT::Scrips->new( RT->SystemUser );
+- $scrips->Limit( FIELD => 'ScripAction', VALUE => $action->id );
+- if ( $scrips->Count ) {
+- my @sid;
+- while( my $s = $scrips->Next ) {
+- push @sid, $s->id;
+- }
+- print STDERR "ScripAction '". $opts->{'name'} ."'"
+- . " is in use by Scrip(s) ". join( ", ", map "#$_", @sid )
+- . "\n";
+- exit(-1);
+- }
+-
+- return __delete( $action );
+-}
+-
+-sub __delete {
+- require DBIx::SearchBuilder::Record;
+- return DBIx::SearchBuilder::Record::Delete( shift );
+-}
+-
+-sub _get_action_by_name {
+- my $name = shift;
+- my $actions = _get_our_actions();
+- $actions->Limit(
+- FIELD => 'Name',
+- VALUE => $name
+- );
+-
+- if ( $actions->Count > 1 ) {
+- print STDERR "More then one ScripAction with name '$name'\n";
+- }
+-
+- return $actions->First;
+-}
+-
+-=head2 switch NAME
+-
+-Switch action NAME from notify as correspondence to comment and back.
+-
+-=cut
+-
+-sub switch {
+- my $action = _get_action_by_name( $opts->{'name'} );
+- unless ( $action ) {
+- print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n";
+- exit(-1);
+- }
+-
+- my %h = (
+- NotifyGroup => 'NotifyGroupAsComment',
+- NotifyGroupAsComment => 'NotifyGroup'
+- );
+-
+- return $action->__Set(
+- Field => 'ExecModule',
+- Value => $h{ $action->ExecModule }
+- );
+-}
+-
+-=head2 rename NAME --newname NEWNAME
+-
+-Renames action NAME to NEWNAME.
+-
+-=cut
+-
+-sub rename {
+- my $action = _get_action_by_name( $opts->{'name'} );
+- unless ( $action ) {
+- print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n";
+- exit(-1);
+- }
+-
+- my $actions = RT::ScripActions->new( RT->SystemUser );
+- $actions->Limit( FIELD => 'Name', VALUE => $opts->{'newname'} );
+- if ( $actions->Count ) {
+- print STDERR "ScripAction '". $opts->{'newname'} ."' allready exists\n";
+- exit(-1);
+- }
+-
+- return $action->__Set(
+- Field => 'Name',
+- Value => $opts->{'newname'},
+- );
+-}
+-
+-=head2 NOTES
+-
+-If command has option --group or --user then you can use it more then once,
+-if other is not specified.
+-
+-=cut
+-
+-###############
+-#### Utils ####
+-###############
+-
+-sub argument_to_list {
+- my $action = shift;
+- require RT::Action::NotifyGroup;
+- return RT::Action::NotifyGroup->__SplitArg( $action->Argument );
+-}
+-
+-sub _get_our_actions {
+- my $actions = RT::ScripActions->new( RT->SystemUser );
+- $actions->Limit(
+- FIELD => 'ExecModule',
+- VALUE => 'NotifyGroup',
+- ENTRYAGGREGATOR => 'OR',
+- );
+- $actions->Limit(
+- FIELD => 'ExecModule',
+- VALUE => 'NotifyGroupAsComment',
+- ENTRYAGGREGATOR => 'OR',
+- );
+-
+- return $actions;
+-}
+-
+-=head1 AUTHOR
+-
+-Ruslan U. Zakirov E<lt>ruz at bestpractical.comE<gt>
+-
+-=head1 SEE ALSO
+-
+-L<RT::Action::NotifyGroup>, L<RT::Action::NotifyGroupAsComment>
+-
+-=cut
+diff --git a/sbin/rt-fulltext-indexer b/sbin/rt-fulltext-indexer
+deleted file mode 100755
+index f5b5dcf..0000000
+--- a/sbin/rt-fulltext-indexer
++++ /dev/null
+@@ -1,479 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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 'once';
+-
+-# fix lib paths, some may be relative
+-BEGIN {
+- require File::Spec;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- }
+- else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-}
+-
+-BEGIN {
+- use RT;
+- RT::LoadConfig();
+- RT::Init();
+-};
+-use RT::Interface::CLI ();
+-
+-my %OPT = (
+- help => 0,
+- debug => 0,
+- quiet => 0,
+-);
+-my @OPT_LIST = qw(help|h! debug! quiet);
+-
+-my $db_type = RT->Config->Get('DatabaseType');
+-if ( $db_type eq 'Pg' ) {
+- %OPT = (
+- %OPT,
+- limit => 0,
+- all => 0,
+- );
+- push @OPT_LIST, 'limit=i', 'all!';
+-}
+-elsif ( $db_type eq 'mysql' ) {
+- %OPT = (
+- %OPT,
+- limit => 0,
+- all => 0,
+- xmlpipe2 => 0,
+- );
+- push @OPT_LIST, 'limit=i', 'all!', 'xmlpipe2!';
+-}
+-elsif ( $db_type eq 'Oracle' ) {
+- %OPT = (
+- %OPT,
+- memory => '2M',
+- );
+- push @OPT_LIST, qw(memory=s);
+-}
+-
+-use Getopt::Long qw(GetOptions);
+-GetOptions( \%OPT, @OPT_LIST );
+-
+-if ( $OPT{'help'} ) {
+- RT::Interface::CLI->ShowHelp(
+- Sections => 'NAME|DESCRIPTION|'. uc($db_type),
+- );
+-}
+-
+-use Fcntl ':flock';
+-if ( !flock main::DATA, LOCK_EX | LOCK_NB ) {
+- if ( $OPT{quiet} ) {
+- RT::Logger->info("$0 is already running; aborting silently, as requested");
+- exit;
+- }
+- else {
+- print STDERR "$0 is already running\n";
+- exit 1;
+- }
+-}
+-
+-my $fts_config = RT->Config->Get('FullTextSearch') || {};
+-unless ( $fts_config->{'Enable'} ) {
+- print STDERR <<EOT;
+-
+-Full text search is disabled in your RT configuration. Run
+-/opt/rt4/sbin/rt-setup-fulltext-index to configure and enable it.
+-
+-EOT
+- exit 1;
+-}
+-unless ( $fts_config->{'Indexed'} ) {
+- print STDERR <<EOT;
+-
+-Full text search is enabled in your RT configuration, but not with any
+-full-text database indexing -- hence this tool is not required. Read
+-the documentation for %FullTextSearch in your RT_Config for more details.
+-
+-EOT
+- exit 1;
+-}
+-
+-if ( $db_type eq 'Oracle' ) {
+- my $index = $fts_config->{'IndexName'} || 'rt_fts_index';
+- $RT::Handle->dbh->do(
+- "begin ctx_ddl.sync_index(?, ?); end;", undef,
+- $index, $OPT{'memory'}
+- );
+- exit;
+-} elsif ( $db_type eq 'mysql' ) {
+- unless ($OPT{'xmlpipe2'}) {
+- print STDERR <<EOT;
+-
+-Updates to the external Sphinx index are done via running the sphinx
+-`indexer` tool:
+-
+- indexer rt
+-
+-EOT
+- exit 1;
+- }
+-}
+-
+-my @types = qw(text html);
+-foreach my $type ( @types ) {
+- REDO:
+- my $attachments = attachments($type);
+- $attachments->Limit(
+- FIELD => 'id',
+- OPERATOR => '>',
+- VALUE => last_indexed($type)
+- );
+- $attachments->OrderBy( FIELD => 'id', ORDER => 'asc' );
+- $attachments->RowsPerPage( $OPT{'limit'} || 100 );
+-
+- my $found = 0;
+- while ( my $a = $attachments->Next ) {
+- next if filter( $type, $a );
+- debug("Found attachment #". $a->id );
+- my $txt = extract($type, $a) or next;
+- $found++;
+- process( $type, $a, $txt );
+- debug("Processed attachment #". $a->id );
+- }
+- finalize( $type, $attachments ) if $found;
+- clean( $type );
+- goto REDO if $OPT{'all'} and $attachments->Count == ($OPT{'limit'} || 100)
+-}
+-
+-sub attachments {
+- my $type = shift;
+- my $res = RT::Attachments->new( RT->SystemUser );
+- my $txn_alias = $res->Join(
+- ALIAS1 => 'main',
+- FIELD1 => 'TransactionId',
+- TABLE2 => 'Transactions',
+- FIELD2 => 'id',
+- );
+- $res->Limit(
+- ALIAS => $txn_alias,
+- FIELD => 'ObjectType',
+- VALUE => 'RT::Ticket',
+- );
+- my $ticket_alias = $res->Join(
+- ALIAS1 => $txn_alias,
+- FIELD1 => 'ObjectId',
+- TABLE2 => 'Tickets',
+- FIELD2 => 'id',
+- );
+- $res->Limit(
+- ALIAS => $ticket_alias,
+- FIELD => 'Status',
+- OPERATOR => '!=',
+- VALUE => 'deleted'
+- );
+-
+- # On newer DBIx::SearchBuilder's, indicate that making the query DISTINCT
+- # is unnecessary because the joins won't produce duplicates. This
+- # drastically improves performance when fetching attachments.
+- $res->{joins_are_distinct} = 1;
+-
+- return goto_specific(
+- suffix => $type,
+- error => "Don't know how to find $type attachments",
+- arguments => [$res],
+- );
+-}
+-
+-sub last_indexed {
+- my ($type) = (@_);
+- return goto_specific(
+- suffix => $db_type,
+- error => "Don't know how to find last indexed $type attachment for $db_type DB",
+- arguments => \@_,
+- );
+-}
+-
+-sub filter {
+- my $type = shift;
+- return goto_specific(
+- suffix => $type,
+- arguments => \@_,
+- );
+-}
+-
+-sub extract {
+- my $type = shift;
+- return goto_specific(
+- suffix => $type,
+- error => "No way to convert $type attachment into text",
+- arguments => \@_,
+- );
+-}
+-
+-sub process {
+- return goto_specific(
+- suffix => $db_type,
+- error => "No processer for $db_type DB",
+- arguments => \@_,
+- );
+-}
+-
+-sub finalize {
+- return goto_specific(
+- suffix => $db_type,
+- arguments => \@_,
+- );
+-}
+-
+-sub clean {
+- return goto_specific(
+- suffix => $db_type,
+- arguments => \@_,
+- );
+-}
+-
+-{
+-sub last_indexed_mysql {
+- my $type = shift;
+- my $attr = $RT::System->FirstAttribute('LastIndexedAttachments');
+- return 0 unless $attr;
+- return 0 unless exists $attr->{ $type };
+- return $attr->{ $type } || 0;
+-}
+-
+-sub process_mysql {
+- my ($type, $attachment, $text) = (@_);
+-
+- my $doc = sphinx_template();
+-
+- my $element = $doc->createElement('sphinx:document');
+- $element->setAttribute( id => $attachment->id );
+- $element->appendTextChild( content => $$text );
+-
+- $doc->documentElement->appendChild( $element );
+-}
+-
+-my $doc = undef;
+-sub sphinx_template {
+- return $doc if $doc;
+-
+- require XML::LibXML;
+- $doc = XML::LibXML::Document->new('1.0', 'UTF-8');
+- my $root = $doc->createElement('sphinx:docset');
+- $doc->setDocumentElement( $root );
+-
+- my $schema = $doc->createElement('sphinx:schema');
+- $root->appendChild( $schema );
+- foreach ( qw(content) ) {
+- my $field = $doc->createElement('sphinx:field');
+- $field->setAttribute( name => $_ );
+- $schema->appendChild( $field );
+- }
+-
+- return $doc;
+-}
+-
+-sub finalize_mysql {
+- my ($type, $attachments) = @_;
+- sphinx_template()->toFH(*STDOUT, 1);
+-}
+-
+-sub clean_mysql {
+- $doc = undef;
+-}
+-
+-}
+-
+-sub last_indexed_pg {
+- my $type = shift;
+- my $attachments = attachments( $type );
+- my $alias = 'main';
+- if ( $fts_config->{'Table'} && $fts_config->{'Table'} ne 'Attachments' ) {
+- $alias = $attachments->Join(
+- TYPE => 'left',
+- FIELD1 => 'id',
+- TABLE2 => $fts_config->{'Table'},
+- FIELD2 => 'id',
+- );
+- }
+- $attachments->Limit(
+- ALIAS => $alias,
+- FIELD => $fts_config->{'Column'},
+- OPERATOR => 'IS NOT',
+- VALUE => 'NULL',
+- );
+- $attachments->OrderBy( FIELD => 'id', ORDER => 'desc' );
+- $attachments->RowsPerPage( 1 );
+- my $res = $attachments->First;
+- return 0 unless $res;
+- return $res->id;
+-}
+-
+-sub process_pg {
+- my ($type, $attachment, $text) = (@_);
+-
+- my $dbh = $RT::Handle->dbh;
+- my $table = $fts_config->{'Table'};
+- my $column = $fts_config->{'Column'};
+-
+- my $query;
+- if ( $table ) {
+- if ( my ($id) = $dbh->selectrow_array("SELECT id FROM $table WHERE id = ?", undef, $attachment->id) ) {
+- $query = "UPDATE $table SET $column = to_tsvector(?) WHERE id = ?";
+- } else {
+- $query = "INSERT INTO $table($column, id) VALUES(to_tsvector(?), ?)";
+- }
+- } else {
+- $query = "UPDATE Attachments SET $column = to_tsvector(?) WHERE id = ?";
+- }
+-
+- my $status = eval { $dbh->do( $query, undef, $$text, $attachment->id ) };
+- unless ( $status ) {
+- if ( $dbh->err == 7 && $dbh->state eq '54000' ) {
+- warn "Attachment @{[$attachment->id]} cannot be indexed. Most probably it contains too many unique words. Error: ". $dbh->errstr;
+- } elsif ( $dbh->err == 7 && $dbh->state eq '22021' ) {
+- warn "Attachment @{[$attachment->id]} cannot be indexed. Most probably it contains invalid UTF8 bytes. Error: ". $dbh->errstr;
+- } else {
+- die "error: ". $dbh->errstr;
+- }
+-
+- # Insert an empty tsvector, so we count this row as "indexed"
+- # for purposes of knowing where to pick up
+- eval { $dbh->do( $query, undef, "", $attachment->id ) }
+- or die "Failed to insert empty tsvector: " . $dbh->errstr;
+- }
+-}
+-
+-sub attachments_text {
+- my $res = shift;
+- $res->Limit( FIELD => 'ContentType', VALUE => 'text/plain' );
+- return $res;
+-}
+-
+-sub extract_text {
+- my $attachment = shift;
+- my $text = $attachment->Content;
+- return undef unless defined $text && length($text);
+- return \$text;
+-}
+-
+-sub attachments_html {
+- my $res = shift;
+- $res->Limit( FIELD => 'ContentType', VALUE => 'text/html' );
+- return $res;
+-}
+-
+-sub filter_html {
+- my $attachment = shift;
+- if ( my $parent = $attachment->ParentObj ) {
+-# skip html parts that are alternatives
+- return 1 if $parent->id
+- && $parent->ContentType eq 'mulitpart/alternative';
+- }
+- return 0;
+-}
+-
+-sub extract_html {
+- my $attachment = shift;
+- my $text = $attachment->Content;
+- return undef unless defined $text && length($text);
+-# TODO: html -> text
+- return \$text;
+-}
+-
+-sub goto_specific {
+- my %args = (@_);
+-
+- my $func = (caller(1))[3];
+- $func =~ s/.*:://;
+- my $call = $func ."_". lc $args{'suffix'};
+- unless ( defined &$call ) {
+- return undef unless $args{'error'};
+- require Carp; Carp::croak( $args{'error'} );
+- }
+- @_ = @{ $args{'arguments'} };
+- goto &$call;
+-}
+-
+-
+-# helper functions
+-sub debug { print @_, "\n" if $OPT{debug}; 1 }
+-sub error { $RT::Logger->error(_(@_)); 1 }
+-sub warning { $RT::Logger->warn(_(@_)); 1 }
+-
+-=head1 NAME
+-
+-rt-fulltext-indexer - Indexer for full text search
+-
+-=head1 DESCRIPTION
+-
+-This is a helper script to keep full text indexes in sync with data.
+-Read F<docs/full_text_indexing.pod> for complete details on how and when
+-to run it.
+-
+-=head1 AUTHOR
+-
+-Ruslan Zakirov E<lt>ruz at bestpractical.comE<gt>,
+-Alex Vandiver E<lt>alexmv at bestpractical.comE<gt>
+-
+-=cut
+-
+-__DATA__
+diff --git a/sbin/rt-preferences-viewer b/sbin/rt-preferences-viewer
+deleted file mode 100755
+index 601e638..0000000
+--- a/sbin/rt-preferences-viewer
++++ /dev/null
+@@ -1,149 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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;
+-
+-# fix lib paths, some may be relative
+-BEGIN {
+- require File::Spec;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- }
+- else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-}
+-
+-use Getopt::Long;
+-my %opt;
+-GetOptions( \%opt, 'help|h', 'user|u=s', 'option|o=s' );
+-
+-if ( $opt{help} ) {
+- require Pod::Usage;
+- Pod::Usage::pod2usage({ verbose => 2 });
+- exit;
+-}
+-
+-require RT;
+-RT::LoadConfig();
+-RT::Init();
+-
+-require RT::Attributes;
+-my $attrs = RT::Attributes->new( RT->SystemUser );
+-$attrs->Limit( FIELD => 'Name', VALUE => 'Pref-RT::System-1' );
+-$attrs->Limit( FIELD => 'ObjectType', VALUE => 'RT::User' );
+-
+-if ($opt{user}) {
+- my $user = RT::User->new( RT->SystemUser );
+- my ($val, $msg) = $user->Load($opt{user});
+- unless ($val) {
+- RT->Logger->error("Unable to load $opt{user}: $msg");
+- exit(1);
+- }
+- $attrs->Limit( FIELD => 'ObjectId', VALUE => $user->Id );
+-}
+-
+-use Data::Dumper;
+-$Data::Dumper::Terse = 1;
+-
+-while (my $attr = $attrs->Next ) {
+- my $user = RT::User->new( RT->SystemUser );
+- my ($val, $msg) = $user->Load($attr->ObjectId);
+- unless ($val) {
+- RT->Logger->warn("Unable to load User ".$attr->ObjectId." $msg");
+- next;
+- }
+- next if $user->Disabled;
+-
+- my $content = $attr->Content;
+- if ( my $config_name = $opt{option} ) {
+- if ( exists $content->{$config_name} ) {
+- my $setting = $content->{$config_name};
+- print $user->Name, "\t$config_name: $setting\n";
+- }
+- } else {
+- print $user->Name, " => ", Dumper($content);
+- }
+-
+-}
+-
+-__END__
+-
+-=head1 NAME
+-
+-rt-preferences-viewer - show user defined preferences
+-
+-=head1 SYNOPSIS
+-
+- rt-preferences-viewer
+-
+- rt-preferences-viewer --user=falcone
+- show only the falcone user's preferences
+-
+- rt-preferences-viewer --option=EmailFrequency
+- show users who have set the EmailFrequence config option
+-
+-=head1 DESCRIPTION
+-
+-This script shows user settings of preferences. If a user is using the system
+-default, it will not be listed. You can limit to a user name or id or to users
+-with a particular option set.
+diff --git a/sbin/rt-server b/sbin/rt-server
+deleted file mode 100755
+index add9d01..0000000
+--- a/sbin/rt-server
++++ /dev/null
+@@ -1,285 +0,0 @@
+-#!/usr/bin/perl -w
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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;
+-
+-# fix lib paths, some may be relative
+-BEGIN {
+- die <<EOT if ${^TAINT};
+-RT does not run under Perl's "taint mode". Remove -T from the command
+-line, or remove the PerlTaintCheck parameter from your mod_perl
+-configuration.
+-EOT
+-
+- require File::Spec;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- }
+- else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-
+-}
+-
+-use Getopt::Long;
+-no warnings 'once';
+-
+-if (grep { m/help/ } @ARGV) {
+- require Pod::Usage;
+- print Pod::Usage::pod2usage( { verbose => 2 } );
+- exit;
+-}
+-
+-require RT;
+-RT->LoadConfig();
+-RT->InitPluginPaths();
+-RT->InitLogging();
+-require Module::Refresh if RT->Config->Get('DevelMode');
+-
+-require RT::Handle;
+-my ($integrity, $state, $msg) = RT::Handle->CheckIntegrity;
+-
+-unless ( $integrity ) {
+- print STDERR <<EOF;
+-
+-RT couldn't connect to the database where tickets are stored.
+-If this is a new installation of RT, you should visit the URL below
+-to configure RT and initialize your database.
+-
+-If this is an existing RT installation, this may indicate a database
+-connectivity problem.
+-
+-The error RT got back when trying to connect to your database was:
+-
+-$msg
+-
+-EOF
+-
+- require RT::Installer;
+- # don't enter install mode if the file exists but is unwritable
+- if (-e RT::Installer->ConfigFile && !-w _) {
+- die 'Since your configuration exists ('
+- . RT::Installer->ConfigFile
+- . ") but is not writable, I'm refusing to do anything.\n";
+- }
+-
+- RT->Config->Set( 'LexiconLanguages' => '*' );
+- RT::I18N->Init;
+-
+- RT->InstallMode(1);
+-} else {
+- RT->Init( Heavy => 1 );
+-
+- my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'post');
+- unless ( $status ) {
+- print STDERR $msg, "\n\n";
+- exit -1;
+- }
+-}
+-
+-# we must disconnect DB before fork
+-if ($RT::Handle) {
+- $RT::Handle->dbh->disconnect if $RT::Handle->dbh;
+- $RT::Handle->dbh(undef);
+- undef $RT::Handle;
+-}
+-
+-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);
+-}
+-
+-# when used as a psgi file
+-if (caller) {
+- return $app;
+-}
+-
+-
+-# load appropriate server
+-
+-require Plack::Runner;
+-
+-my $is_fastcgi = $0 =~ m/fcgi$/;
+-my $r = Plack::Runner->new( $0 =~ /standalone/ ? ( server => 'Standalone' ) :
+- $is_fastcgi ? ( server => 'FCGI' )
+- : (),
+- env => 'deployment' );
+-
+-# figure out the port
+-my $port;
+-
+-# handle "rt-server 8888" for back-compat, but complain about it
+-if ($ARGV[0] && $ARGV[0] =~ m/^\d+$/) {
+- warn "Deprecated: please run $0 --port $ARGV[0] instead\n";
+- unshift @ARGV, '--port';
+-}
+-
+-my @args = @ARGV;
+-
+-use List::MoreUtils 'last_index';
+-my $last_index = last_index { $_ eq '--port' } @args;
+-
+-my $explicit_port;
+-
+-if ( $last_index != -1 && $args[$last_index+1] =~ /^\d+$/ ) {
+- $explicit_port = $args[$last_index+1];
+- $port = $explicit_port;
+-
+- # inform the rest of the system what port we manually chose
+- my $old_app = $app;
+- $app = sub {
+- my $env = shift;
+-
+- $env->{'rt.explicit_port'} = $port;
+-
+- $old_app->($env, @_);
+- };
+-}
+-else {
+- # default to the configured WebPort and inform Plack::Runner
+- $port = RT->Config->Get('WebPort') || '8080';
+- push @args, '--port', $port;
+-}
+-
+-push @args, '--server', 'Standalone' if RT->InstallMode;
+-push @args, '--server', 'Starlet' unless $r->{server} || grep { m/--server/ } @args;
+-
+-$r->parse_options(@args);
+-
+-delete $r->{options} if $is_fastcgi; ### mangle_host_port_socket ruins everything
+-
+-unless ($r->{env} eq 'development') {
+- push @{$r->{options}}, server_ready => sub {
+- my($args) = @_;
+- my $name = $args->{server_software} || ref($args); # $args is $server
+- my $host = $args->{host} || 0;
+- my $proto = $args->{proto} || 'http';
+- print STDERR "$name: Accepting connections at $proto://$host:$args->{port}/\n";
+- };
+-}
+-eval { $r->run($app) };
+-if (my $err = $@) {
+- handle_startup_error($err);
+-}
+-
+-exit 0;
+-
+-sub handle_startup_error {
+- my $err = shift;
+- if ( $err =~ /listen/ ) {
+- handle_bind_error();
+- } else {
+- die
+- "Something went wrong while trying to run RT's standalone web server:\n\t"
+- . $err;
+- }
+-}
+-
+-
+-sub handle_bind_error {
+-
+- print STDERR <<EOF;
+-WARNING: RT couldn't start up a web server on port @{[$port]}.
+-This is often the case if the port is already in use or you're running @{[$0]}
+-as someone other than your system's "root" user. You may also specify a
+-temporary port with: $0 --port <port>
+-EOF
+-
+- if ($explicit_port) {
+- print STDERR
+- "Please check your system configuration or choose another port\n\n";
+- }
+-}
+-
+-__END__
+-
+-=head1 NAME
+-
+-rt-server - RT standalone server
+-
+-=head1 SYNOPSIS
+-
+- # runs prefork server listening on port 8080, requires Starlet
+- rt-server --port 8080
+-
+- # runs server listening on port 8080
+- rt-server --server Standalone --port 8080
+- # or
+- standalone_httpd --port 8080
+-
+- # runs other PSGI server on port 8080
+- rt-server --server Starman --port 8080
+diff --git a/sbin/rt-server.fcgi b/sbin/rt-server.fcgi
+deleted file mode 100755
+index add9d01..0000000
+--- a/sbin/rt-server.fcgi
++++ /dev/null
+@@ -1,285 +0,0 @@
+-#!/usr/bin/perl -w
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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;
+-
+-# fix lib paths, some may be relative
+-BEGIN {
+- die <<EOT if ${^TAINT};
+-RT does not run under Perl's "taint mode". Remove -T from the command
+-line, or remove the PerlTaintCheck parameter from your mod_perl
+-configuration.
+-EOT
+-
+- require File::Spec;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- }
+- else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-
+-}
+-
+-use Getopt::Long;
+-no warnings 'once';
+-
+-if (grep { m/help/ } @ARGV) {
+- require Pod::Usage;
+- print Pod::Usage::pod2usage( { verbose => 2 } );
+- exit;
+-}
+-
+-require RT;
+-RT->LoadConfig();
+-RT->InitPluginPaths();
+-RT->InitLogging();
+-require Module::Refresh if RT->Config->Get('DevelMode');
+-
+-require RT::Handle;
+-my ($integrity, $state, $msg) = RT::Handle->CheckIntegrity;
+-
+-unless ( $integrity ) {
+- print STDERR <<EOF;
+-
+-RT couldn't connect to the database where tickets are stored.
+-If this is a new installation of RT, you should visit the URL below
+-to configure RT and initialize your database.
+-
+-If this is an existing RT installation, this may indicate a database
+-connectivity problem.
+-
+-The error RT got back when trying to connect to your database was:
+-
+-$msg
+-
+-EOF
+-
+- require RT::Installer;
+- # don't enter install mode if the file exists but is unwritable
+- if (-e RT::Installer->ConfigFile && !-w _) {
+- die 'Since your configuration exists ('
+- . RT::Installer->ConfigFile
+- . ") but is not writable, I'm refusing to do anything.\n";
+- }
+-
+- RT->Config->Set( 'LexiconLanguages' => '*' );
+- RT::I18N->Init;
+-
+- RT->InstallMode(1);
+-} else {
+- RT->Init( Heavy => 1 );
+-
+- my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'post');
+- unless ( $status ) {
+- print STDERR $msg, "\n\n";
+- exit -1;
+- }
+-}
+-
+-# we must disconnect DB before fork
+-if ($RT::Handle) {
+- $RT::Handle->dbh->disconnect if $RT::Handle->dbh;
+- $RT::Handle->dbh(undef);
+- undef $RT::Handle;
+-}
+-
+-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);
+-}
+-
+-# when used as a psgi file
+-if (caller) {
+- return $app;
+-}
+-
+-
+-# load appropriate server
+-
+-require Plack::Runner;
+-
+-my $is_fastcgi = $0 =~ m/fcgi$/;
+-my $r = Plack::Runner->new( $0 =~ /standalone/ ? ( server => 'Standalone' ) :
+- $is_fastcgi ? ( server => 'FCGI' )
+- : (),
+- env => 'deployment' );
+-
+-# figure out the port
+-my $port;
+-
+-# handle "rt-server 8888" for back-compat, but complain about it
+-if ($ARGV[0] && $ARGV[0] =~ m/^\d+$/) {
+- warn "Deprecated: please run $0 --port $ARGV[0] instead\n";
+- unshift @ARGV, '--port';
+-}
+-
+-my @args = @ARGV;
+-
+-use List::MoreUtils 'last_index';
+-my $last_index = last_index { $_ eq '--port' } @args;
+-
+-my $explicit_port;
+-
+-if ( $last_index != -1 && $args[$last_index+1] =~ /^\d+$/ ) {
+- $explicit_port = $args[$last_index+1];
+- $port = $explicit_port;
+-
+- # inform the rest of the system what port we manually chose
+- my $old_app = $app;
+- $app = sub {
+- my $env = shift;
+-
+- $env->{'rt.explicit_port'} = $port;
+-
+- $old_app->($env, @_);
+- };
+-}
+-else {
+- # default to the configured WebPort and inform Plack::Runner
+- $port = RT->Config->Get('WebPort') || '8080';
+- push @args, '--port', $port;
+-}
+-
+-push @args, '--server', 'Standalone' if RT->InstallMode;
+-push @args, '--server', 'Starlet' unless $r->{server} || grep { m/--server/ } @args;
+-
+-$r->parse_options(@args);
+-
+-delete $r->{options} if $is_fastcgi; ### mangle_host_port_socket ruins everything
+-
+-unless ($r->{env} eq 'development') {
+- push @{$r->{options}}, server_ready => sub {
+- my($args) = @_;
+- my $name = $args->{server_software} || ref($args); # $args is $server
+- my $host = $args->{host} || 0;
+- my $proto = $args->{proto} || 'http';
+- print STDERR "$name: Accepting connections at $proto://$host:$args->{port}/\n";
+- };
+-}
+-eval { $r->run($app) };
+-if (my $err = $@) {
+- handle_startup_error($err);
+-}
+-
+-exit 0;
+-
+-sub handle_startup_error {
+- my $err = shift;
+- if ( $err =~ /listen/ ) {
+- handle_bind_error();
+- } else {
+- die
+- "Something went wrong while trying to run RT's standalone web server:\n\t"
+- . $err;
+- }
+-}
+-
+-
+-sub handle_bind_error {
+-
+- print STDERR <<EOF;
+-WARNING: RT couldn't start up a web server on port @{[$port]}.
+-This is often the case if the port is already in use or you're running @{[$0]}
+-as someone other than your system's "root" user. You may also specify a
+-temporary port with: $0 --port <port>
+-EOF
+-
+- if ($explicit_port) {
+- print STDERR
+- "Please check your system configuration or choose another port\n\n";
+- }
+-}
+-
+-__END__
+-
+-=head1 NAME
+-
+-rt-server - RT standalone server
+-
+-=head1 SYNOPSIS
+-
+- # runs prefork server listening on port 8080, requires Starlet
+- rt-server --port 8080
+-
+- # runs server listening on port 8080
+- rt-server --server Standalone --port 8080
+- # or
+- standalone_httpd --port 8080
+-
+- # runs other PSGI server on port 8080
+- rt-server --server Starman --port 8080
+diff --git a/sbin/rt-session-viewer b/sbin/rt-session-viewer
+deleted file mode 100755
+index 555fc32..0000000
+--- a/sbin/rt-session-viewer
++++ /dev/null
+@@ -1,121 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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;
+-
+-# fix lib paths, some may be relative
+-BEGIN {
+- require File::Spec;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- }
+- else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-}
+-
+-use Getopt::Long;
+-my %opt;
+-GetOptions( \%opt, 'help|h', );
+-
+-my $session_id = shift;
+-
+-if ( $opt{help} || !$session_id ) {
+- require Pod::Usage;
+- Pod::Usage::pod2usage({ verbose => 2 });
+- exit;
+-}
+-
+-require RT;
+-RT::LoadConfig();
+-RT::Init();
+-
+-require RT::Interface::Web::Session;
+-my %session;
+-
+-tie %session, 'RT::Interface::Web::Session', $session_id;
+-unless ( $session{'_session_id'} eq $session_id ) {
+- print STDERR "Couldn't load session $session_id\n";
+- exit 1;
+-}
+-
+-use Data::Dumper;
+-print "Content of session $session_id: ". Dumper( \%session);
+-
+-__END__
+-
+-=head1 NAME
+-
+-rt-session-viewer - show the content of a user's session
+-
+-=head1 SYNOPSIS
+-
+- # show the content of a session
+- rt-session-viewer 2c21c8a2909c14eff12975dd2cc7b9a3
+-
+-=head1 DESCRIPTION
+-
+-This script deserializes and print content of a session identified
+-by <session id>. May be useful for developers and for troubleshooting
+-problems.
+-
+-=cut
+diff --git a/sbin/rt-setup-database b/sbin/rt-setup-database
+deleted file mode 100755
+index 0b8be39..0000000
+--- a/sbin/rt-setup-database
++++ /dev/null
+@@ -1,607 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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 vars qw($Nobody $SystemUser $item);
+-
+-# fix lib paths, some may be relative
+-BEGIN {
+- require File::Spec;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- }
+- else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-
+-}
+-
+-use Term::ReadKey;
+-use Getopt::Long;
+-
+-$| = 1; # unbuffer all output.
+-
+-my %args = (
+- dba => 'root',
+- package => 'RT',
+-);
+-GetOptions(
+- \%args,
+- 'action=s',
+- 'force', 'debug',
+- 'dba=s', 'dba-password=s', 'prompt-for-dba-password', 'package=s',
+- 'datafile=s', 'datadir=s', 'skip-create', 'root-password-file=s',
+- 'upgrade-from=s', 'upgrade-to=s',
+- 'help|h',
+-);
+-
+-no warnings 'once';
+-if ( $args{help} || ! $args{'action'} ) {
+- require Pod::Usage;
+- Pod::Usage::pod2usage({ verbose => 2 });
+- exit;
+-}
+-
+-require RT;
+-RT->LoadConfig();
+-RT->InitClasses();
+-
+-# Force warnings to be output to STDERR if we're not already logging
+-# them at a higher level
+-RT->Config->Set( LogToScreen => 'warning')
+- unless ( RT->Config->Get( 'LogToScreen' )
+- && RT->Config->Get( 'LogToScreen' ) =~ /^(debug|info|notice)$/ );
+-
+-# get customized root password
+-my $root_password;
+-if ( $args{'root-password-file'} ) {
+- open( my $fh, '<', $args{'root-password-file'} )
+- or die "Couldn't open 'args{'root-password-file'}' for reading: $!";
+- $root_password = <$fh>;
+- chomp $root_password;
+- my $min_length = RT->Config->Get('MinimumPasswordLength');
+- if ($min_length) {
+- die
+-"password needs to be at least $min_length long, please check file '$args{'root-password-file'}'"
+- if length $root_password < $min_length;
+- }
+- close $fh;
+-}
+-
+-
+-# check and setup @actions
+-my @actions = grep $_, split /,/, $args{'action'};
+-if ( @actions > 1 && $args{'datafile'} ) {
+- print STDERR "You can not use --datafile option with multiple actions.\n";
+- exit(-1);
+-}
+-foreach ( @actions ) {
+- unless ( /^(?:init|create|drop|schema|acl|coredata|insert|upgrade)$/ ) {
+- print STDERR "$0 called with an invalid --action parameter.\n";
+- exit(-1);
+- }
+- if ( /^(?:init|drop|upgrade)$/ && @actions > 1 ) {
+- print STDERR "You can not mix init, drop or upgrade action with any action.\n";
+- exit(-1);
+- }
+-}
+-
+-# convert init to multiple actions
+-my $init = 0;
+-if ( $actions[0] eq 'init' ) {
+- if ($args{'skip-create'}) {
+- @actions = qw(schema coredata insert);
+- } else {
+- @actions = qw(create schema acl coredata insert);
+- }
+- $init = 1;
+-}
+-
+-# set options from environment
+-foreach my $key(qw(Type Host Name User Password)) {
+- next unless exists $ENV{ 'RT_DB_'. uc $key };
+- print "Using Database$key from RT_DB_". uc($key) ." environment variable.\n";
+- RT->Config->Set( "Database$key", $ENV{ 'RT_DB_'. uc $key });
+-}
+-
+-my $db_type = RT->Config->Get('DatabaseType') || '';
+-my $db_host = RT->Config->Get('DatabaseHost') || '';
+-my $db_port = RT->Config->Get('DatabasePort') || '';
+-my $db_name = RT->Config->Get('DatabaseName') || '';
+-my $db_user = RT->Config->Get('DatabaseUser') || '';
+-my $db_pass = RT->Config->Get('DatabasePassword') || '';
+-
+-# load it here to get error immidiatly if DB type is not supported
+-require RT::Handle;
+-
+-if ( $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name) ) {
+- $db_name = File::Spec->catfile($RT::VarPath, $db_name);
+- RT->Config->Set( DatabaseName => $db_name );
+-}
+-
+-my $dba_user = $args{'dba'} || $ENV{'RT_DBA_USER'} || $db_user || '';
+-my $dba_pass = $args{'dba-password'} || $ENV{'RT_DBA_PASSWORD'};
+-
+-if ($args{'skip-create'}) {
+- $dba_user = $db_user;
+- $dba_pass = $db_pass;
+-} else {
+- if ( !$args{force} && ( !defined $dba_pass || $args{'prompt-for-dba-password'} ) ) {
+- $dba_pass = get_dba_password();
+- chomp $dba_pass if defined($dba_pass);
+- }
+-}
+-
+-my $version_word_regex = join '|', RT::Handle->version_words;
+-my $version_dir = qr/^\d+\.\d+\.\d+(?:$version_word_regex)?\d*$/;
+-
+-print "Working with:\n"
+- ."Type:\t$db_type\nHost:\t$db_host\nPort:\t$db_port\nName:\t$db_name\n"
+- ."User:\t$db_user\nDBA:\t$dba_user" . ($args{'skip-create'} ? ' (No DBA)' : '') . "\n";
+-
+-foreach my $action ( @actions ) {
+- no strict 'refs';
+- my ($status, $msg) = *{ 'action_'. $action }{'CODE'}->( %args );
+- error($action, $msg) unless $status;
+- print $msg .".\n" if $msg;
+- print "Done.\n";
+-}
+-
+-sub action_create {
+- my %args = @_;
+- my $dbh = get_system_dbh();
+- my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'create' );
+- return ($status, $msg) unless $status;
+-
+- print "Now creating a $db_type database $db_name for RT.\n";
+- return RT::Handle->CreateDatabase( $dbh );
+-}
+-
+-sub action_drop {
+- my %args = @_;
+-
+- print "Dropping $db_type database $db_name.\n";
+- unless ( $args{'force'} ) {
+- print <<END;
+-
+-About to drop $db_type database $db_name on $db_host (port '$db_port').
+-WARNING: This will erase all data in $db_name.
+-
+-END
+- exit(-2) unless _yesno();
+- }
+-
+- my $dbh = get_system_dbh();
+- return RT::Handle->DropDatabase( $dbh );
+-}
+-
+-sub action_schema {
+- my %args = @_;
+- my $dbh = get_admin_dbh();
+- my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'schema' );
+- return ($status, $msg) unless $status;
+-
+- print "Now populating database schema.\n";
+- return RT::Handle->InsertSchema( $dbh, $args{'datafile'} || $args{'datadir'} );
+-}
+-
+-sub action_acl {
+- my %args = @_;
+- my $dbh = get_admin_dbh();
+- my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'acl' );
+- return ($status, $msg) unless $status;
+-
+- print "Now inserting database ACLs.\n";
+- return RT::Handle->InsertACL( $dbh, $args{'datafile'} || $args{'datadir'} );
+-}
+-
+-sub action_coredata {
+- my %args = @_;
+- $RT::Handle = RT::Handle->new;
+- $RT::Handle->dbh( undef );
+- RT::ConnectToDatabase();
+- RT::InitLogging();
+- my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'coredata' );
+- return ($status, $msg) unless $status;
+-
+- print "Now inserting RT core system objects.\n";
+- return $RT::Handle->InsertInitialData;
+-}
+-
+-sub action_insert {
+- my %args = @_;
+- $RT::Handle = RT::Handle->new;
+- RT::Init();
+- my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'insert' );
+- return ($status, $msg) unless $status;
+-
+- print "Now inserting data.\n";
+- my $file = $args{'datafile'};
+- $file = $RT::EtcPath . "/initialdata" if $init && !$file;
+- $file ||= $args{'datadir'}."/content";
+-
+- # Slurp in backcompat
+- my %removed;
+- my @back = @{$args{backcompat} || []};
+- if (@back) {
+- my @lines = do {local @ARGV = @back; <>};
+- for (@lines) {
+- s/\#.*//;
+- next unless /\S/;
+- my ($class, @fields) = split;
+- $class->_BuildTableAttributes;
+- $RT::Logger->debug("Temporarily removing @fields from $class");
+- $removed{$class}{$_} = delete $RT::Record::_TABLE_ATTR->{$class}{$_}
+- for @fields;
+- }
+- }
+-
+- my @ret = $RT::Handle->InsertData( $file, $root_password );
+-
+- # Put back the fields we chopped off
+- for my $class (keys %removed) {
+- $RT::Record::_TABLE_ATTR->{$class}{$_} = $removed{$class}{$_}
+- for keys %{$removed{$class}};
+- }
+- return @ret;
+-}
+-
+-sub action_upgrade {
+- my %args = @_;
+- my $base_dir = $args{'datadir'} || "./etc/upgrade";
+- return (0, "Couldn't read dir '$base_dir' with upgrade data")
+- unless -d $base_dir || -r _;
+-
+- my $upgrading_from = undef;
+- do {
+- if ( defined $upgrading_from ) {
+- print "Doesn't match #.#.#: ";
+- } else {
+- print "Enter $args{package} version you're upgrading from: ";
+- }
+- $upgrading_from = $args{'upgrade-from'} || scalar <STDIN>;
+- chomp $upgrading_from;
+- $upgrading_from =~ s/\s+//g;
+- } while $upgrading_from !~ /$version_dir/;
+-
+- my $upgrading_to = $RT::VERSION;
+- return (0, "The current version $upgrading_to is lower than $upgrading_from")
+- if RT::Handle::cmp_version( $upgrading_from, $upgrading_to ) > 0;
+-
+- return (1, "The version $upgrading_to you're upgrading to is up to date")
+- if RT::Handle::cmp_version( $upgrading_from, $upgrading_to ) == 0;
+-
+- my @versions = get_versions_from_to($base_dir, $upgrading_from, undef);
+- return (1, "No DB changes since $upgrading_from")
+- unless @versions;
+-
+- if (RT::Handle::cmp_version($versions[-1], $upgrading_to) > 0) {
+- print "\n***** There are upgrades for $versions[-1], which is later than $upgrading_to,\n";
+- print "***** which you are nominally upgrading to. Upgrading to $versions[-1] instead.\n";
+- $upgrading_to = $versions[-1];
+- }
+-
+- print "\nGoing to apply following upgrades:\n";
+- print map "* $_\n", @versions;
+-
+- {
+- my $custom_upgrading_to = undef;
+- do {
+- if ( defined $custom_upgrading_to ) {
+- print "Doesn't match #.#.#: ";
+- } else {
+- print "\nEnter $args{package} version if you want to stop upgrade at some point,\n";
+- print " or leave it blank if you want apply above upgrades: ";
+- }
+- $custom_upgrading_to = $args{'upgrade-to'} || scalar <STDIN>;
+- chomp $custom_upgrading_to;
+- $custom_upgrading_to =~ s/\s+//g;
+- last unless $custom_upgrading_to;
+- } while $custom_upgrading_to !~ /$version_dir/;
+-
+- if ( $custom_upgrading_to ) {
+- return (
+- 0, "The version you entered ($custom_upgrading_to) is lower than\n"
+- ."version you're upgrading from ($upgrading_from)"
+- ) if RT::Handle::cmp_version( $upgrading_from, $custom_upgrading_to ) > 0;
+-
+- return (1, "The version you're upgrading to is up to date")
+- if RT::Handle::cmp_version( $upgrading_from, $custom_upgrading_to ) == 0;
+-
+- if ( RT::Handle::cmp_version( $RT::VERSION, $custom_upgrading_to ) < 0 ) {
+- print "Version you entered is greater than installed ($RT::VERSION).\n";
+- _yesno() or exit(-2);
+- }
+- # ok, checked everything no let's refresh list
+- $upgrading_to = $custom_upgrading_to;
+- @versions = get_versions_from_to($base_dir, $upgrading_from, $upgrading_to);
+-
+- return (1, "No DB changes between $upgrading_from and $upgrading_to")
+- unless @versions;
+-
+- print "\nGoing to apply following upgrades:\n";
+- print map "* $_\n", @versions;
+- }
+- }
+-
+- print "\nIT'S VERY IMPORTANT TO BACK UP BEFORE THIS STEP\n\n";
+- _yesno() or exit(-2) unless $args{'force'};
+-
+- my ( $ret, $msg );
+- foreach my $n ( 0..$#versions ) {
+- my $v = $versions[$n];
+- my @back = grep {-e $_} map {"$base_dir/$versions[$_]/backcompat"} $n+1..$#versions;
+- print "Processing $v\n";
+- my %tmp = (%args, datadir => "$base_dir/$v", datafile => undef, backcompat => \@back);
+- if ( -e "$base_dir/$v/schema.$db_type" ) {
+- ( $ret, $msg ) = action_schema( %tmp );
+- return ( $ret, $msg ) unless $ret;
+- }
+- if ( -e "$base_dir/$v/acl.$db_type" ) {
+- ( $ret, $msg ) = action_acl( %tmp );
+- return ( $ret, $msg ) unless $ret;
+- }
+- if ( -e "$base_dir/$v/content" ) {
+- ( $ret, $msg ) = action_insert( %tmp );
+- return ( $ret, $msg ) unless $ret;
+- }
+- }
+- return 1;
+-}
+-
+-sub get_versions_from_to {
+- my ($base_dir, $from, $to) = @_;
+-
+- opendir( my $dh, $base_dir ) or die "couldn't open dir: $!";
+- my @versions = grep -d "$base_dir/$_" && /$version_dir/, readdir $dh;
+- closedir $dh;
+-
+- die "\nERROR: No upgrade data found in '$base_dir'! Perhaps you specified the wrong --datadir?\n"
+- unless @versions;
+-
+- return
+- grep defined $to ? RT::Handle::cmp_version($_, $to) <= 0 : 1,
+- grep RT::Handle::cmp_version($_, $from) > 0,
+- sort RT::Handle::cmp_version @versions;
+-}
+-
+-sub error {
+- my ($action, $msg) = @_;
+- print STDERR "Couldn't finish '$action' step.\n\n";
+- print STDERR "ERROR: $msg\n\n";
+- exit(-1);
+-}
+-
+-sub get_dba_password {
+- print "In order to create or update your RT database,"
+- . " this script needs to connect to your "
+- . " $db_type instance on $db_host (port '$db_port') as $dba_user\n";
+- print "Please specify that user's database password below. If the user has no database\n";
+- print "password, just press return.\n\n";
+- print "Password: ";
+- ReadMode('noecho');
+- my $password = ReadLine(0);
+- ReadMode('normal');
+- print "\n";
+- return ($password);
+-}
+-
+-# get_system_dbh
+-# Returns L<DBI> database handle connected to B<system> with DBA credentials.
+-# See also L<RT::Handle/SystemDSN>.
+-
+-
+-sub get_system_dbh {
+- return _get_dbh( RT::Handle->SystemDSN, $dba_user, $dba_pass );
+-}
+-
+-sub get_admin_dbh {
+- return _get_dbh( RT::Handle->DSN, $dba_user, $dba_pass );
+-}
+-
+-# get_rt_dbh [USER, PASSWORD]
+-
+-# Returns L<DBI> database handle connected to RT database,
+-# you may specify credentials(USER and PASSWORD) to connect
+-# with. By default connects with credentials from RT config.
+-
+-sub get_rt_dbh {
+- return _get_dbh( RT::Handle->DSN, $db_user, $db_pass );
+-}
+-
+-sub _get_dbh {
+- my ($dsn, $user, $pass) = @_;
+- my $dbh = DBI->connect(
+- $dsn, $user, $pass,
+- { RaiseError => 0, PrintError => 0 },
+- );
+- unless ( $dbh ) {
+- my $msg = "Failed to connect to $dsn as user '$user': ". $DBI::errstr;
+- if ( $args{'debug'} ) {
+- require Carp; Carp::confess( $msg );
+- } else {
+- print STDERR $msg; exit -1;
+- }
+- }
+- return $dbh;
+-}
+-
+-sub _yesno {
+- print "Proceed [y/N]:";
+- my $x = scalar(<STDIN>);
+- $x =~ /^y/i;
+-}
+-
+-1;
+-
+-__END__
+-
+-=head1 NAME
+-
+-rt-setup-database - Set up RT's database
+-
+-=head1 SYNOPSIS
+-
+- rt-setup-database --action ...
+-
+-=head1 OPTIONS
+-
+-=over
+-
+-=item action
+-
+-Several actions can be combined using comma separated list.
+-
+-=over
+-
+-=item init
+-
+-Initialize the database. This is combination of multiple actions listed below.
+-Create DB, schema, setup acl, insert core data and initial data.
+-
+-=item upgrade
+-
+-Apply all needed schema/acl/content updates (will ask for version to upgrade
+-from)
+-
+-=item create
+-
+-Create the database.
+-
+-=item drop
+-
+-Drop the database. This will B<ERASE ALL YOUR DATA>.
+-
+-=item schema
+-
+-Initialize only the database schema
+-
+-To use a local or supplementary datafile, specify it using the '--datadir'
+-option below.
+-
+-=item acl
+-
+-Initialize only the database ACLs
+-
+-To use a local or supplementary datafile, specify it using the '--datadir'
+-option below.
+-
+-=item coredata
+-
+-Insert data into RT's database. This data is required for normal functioning of
+-any RT instance.
+-
+-=item insert
+-
+-Insert data into RT's database. By default, will use RT's installation data.
+-To use a local or supplementary datafile, specify it using the '--datafile'
+-option below.
+-
+-=back
+-
+-=item datafile
+-
+-file path of the data you want to action on
+-
+-e.g. C<--datafile /path/to/datafile>
+-
+-=item datadir
+-
+-Used to specify a path to find the local database schema and acls to be
+-installed.
+-
+-e.g. C<--datadir /path/to/>
+-
+-=item dba
+-
+-dba's username
+-
+-=item dba-password
+-
+-dba's password
+-
+-=item prompt-for-dba-password
+-
+-Ask for the database administrator's password interactively
+-
+-=item skip-create
+-
+-for 'init': skip creating the database and the user account, so we don't need
+-administrator privileges
+-
+-=item root-password-file
+-
+-for 'init' and 'insert': rather than using the default administrative password
+-for RT's "root" user, use the password in this file.
+-
+-=item upgrade-from
+-
+-for 'upgrade': specifies the version to upgrade from, and do not prompt
+-for it if it appears to be a valid version.
+-
+-=item upgrade-to
+-
+-for 'upgrade': specifies the version to upgrade to, and do not prompt
+-for it if it appears to be a valid version.
+-
+-=back
+diff --git a/sbin/rt-setup-fulltext-index b/sbin/rt-setup-fulltext-index
+deleted file mode 100755
+index 871d419..0000000
+--- a/sbin/rt-setup-fulltext-index
++++ /dev/null
+@@ -1,720 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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 'once';
+-
+-# fix lib paths, some may be relative
+-BEGIN {
+- require File::Spec;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- }
+- else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-}
+-
+-BEGIN {
+- use RT;
+- RT::LoadConfig();
+- RT::Init();
+-};
+-use RT::Interface::CLI ();
+-
+-my %DB = (
+- type => scalar RT->Config->Get('DatabaseType'),
+- user => scalar RT->Config->Get('DatabaseUser'),
+- admin => 'root',
+- admin_password => undef,
+-);
+-
+-my %OPT = (
+- help => 0,
+- ask => 1,
+- dryrun => 0,
+- attachments => 1,
+-);
+-
+-my %DEFAULT;
+-if ( $DB{'type'} eq 'Pg' ) {
+- %DEFAULT = (
+- table => 'Attachments',
+- column => 'ContentIndex',
+- );
+-}
+-elsif ( $DB{'type'} eq 'mysql' ) {
+- %DEFAULT = (
+- table => 'AttachmentsIndex',
+- );
+-}
+-elsif ( $DB{'type'} eq 'Oracle' ) {
+- %DEFAULT = (
+- prefix => 'rt_fts_',
+- );
+-}
+-
+-use Getopt::Long qw(GetOptions);
+-GetOptions(
+- 'h|help!' => \$OPT{'help'},
+- 'ask!' => \$OPT{'ask'},
+- 'dry-run!' => \$OPT{'dryrun'},
+- 'attachments!' => \$OPT{'attachments'},
+-
+- 'table=s' => \$OPT{'table'},
+- 'column=s' => \$OPT{'column'},
+- 'url=s' => \$OPT{'url'},
+- 'maxmatches=i' => \$OPT{'maxmatches'},
+- 'index-type=s' => \$OPT{'index-type'},
+-
+- 'dba=s' => \$DB{'admin'},
+- 'dba-password=s' => \$DB{'admin_password'},
+-) or show_help();
+-
+-if ( $OPT{'help'} || (!$DB{'admin'} && $DB{'type'} eq 'Oracle' ) ) {
+- show_help( !$OPT{'help'} );
+-}
+-
+-my $dbh = $RT::Handle->dbh;
+-$dbh->{'RaiseError'} = 1;
+-$dbh->{'PrintError'} = 1;
+-
+-if ( $DB{'type'} eq 'mysql' ) {
+- check_sphinx();
+- my $table = $OPT{'table'} || prompt(
+- message => "Enter name of a new MySQL table that will be used to connect to the\n"
+- . "Sphinx server:",
+- default => $DEFAULT{'table'},
+- silent => !$OPT{'ask'},
+- );
+-
+- my $url = 'sphinx://localhost:3312/rt';
+- my $version = ($dbh->selectrow_array("show variables like 'version'"))[1];
+- $url = 'sphinx://127.0.0.1:3312/rt'
+- if $version and $version =~ /^(\d+\.\d+)/ and $1 >= 5.5;
+-
+- $url = $OPT{'url'} || prompt(
+- message => "Enter URL of the sphinx search server; this should be of the form\n"
+- . "sphinx://<server>:<port>/<index name>",
+- default => $url,
+- silent => !$OPT{'ask'},
+- );
+- my $maxmatches = $OPT{'maxmatches'} || prompt(
+- message => "Maximum number of matches to return; this is the maximum number of\n"
+- . "attachment records returned by the search, not the maximum number\n"
+- . "of tickets. Both your RT_SiteConfig.pm and your sphinx.conf must\n"
+- . "agree on this value. Larger values cause your Sphinx server to\n"
+- . "consume more memory and CPU time per query.",
+- default => 10000,
+- silent => !$OPT{'ask'},
+- );
+-
+- my $schema = <<END;
+-CREATE TABLE $table (
+- id INTEGER UNSIGNED NOT NULL,
+- weight INTEGER NOT NULL,
+- query VARCHAR(3072) NOT NULL,
+- INDEX(query)
+-) ENGINE=SPHINX CONNECTION="$url" CHARACTER SET utf8
+-END
+-
+- do_error_is_ok( dba_handle() => "DROP TABLE $table" )
+- unless $OPT{'dryrun'};
+- insert_schema( $schema );
+-
+- print_rt_config( Table => $table, MaxMatches => $maxmatches );
+-
+- require URI;
+- my $urlo = URI->new( $url );
+- my ($host, $port) = split /:/, $urlo->authority;
+- my $index = $urlo->path;
+- $index =~ s{^/+}{};
+-
+- my $var_path = $RT::VarPath;
+-
+- my %sphinx_conf = ();
+- $sphinx_conf{'host'} = RT->Config->Get('DatabaseHost');
+- $sphinx_conf{'db'} = RT->Config->Get('DatabaseName');
+- $sphinx_conf{'user'} = RT->Config->Get('DatabaseUser');
+- $sphinx_conf{'pass'} = RT->Config->Get('DatabasePassword');
+-
+- print <<END
+-
+-Below is a simple Sphinx configuration which can be used to index all
+-text/plain attachments in your database. This configuration is not
+-ideal; you should read the Sphinx documentation to understand how to
+-configure it to better suit your needs.
+-
+-source rt {
+- type = mysql
+-
+- sql_host = $sphinx_conf{'host'}
+- sql_db = $sphinx_conf{'db'}
+- sql_user = $sphinx_conf{'user'}
+- sql_pass = $sphinx_conf{'pass'}
+-
+- sql_query_pre = SET NAMES utf8
+- sql_query = \\
+- SELECT a.id, a.content FROM Attachments a \\
+- JOIN Transactions txn ON a.TransactionId = txn.id AND txn.ObjectType = 'RT::Ticket' \\
+- JOIN Tickets t ON txn.ObjectId = t.id \\
+- WHERE a.ContentType = 'text/plain' AND t.Status != 'deleted'
+-
+- sql_query_info = SELECT * FROM Attachments WHERE id=\$id
+-}
+-
+-index $index {
+- source = rt
+- path = $var_path/sphinx/index
+- docinfo = extern
+- charset_type = utf-8
+-}
+-
+-indexer {
+- mem_limit = 32M
+-}
+-
+-searchd {
+- port = $port
+- log = $var_path/sphinx/searchd.log
+- query_log = $var_path/sphinx/query.log
+- read_timeout = 5
+- max_children = 30
+- pid_file = $var_path/sphinx/searchd.pid
+- max_matches = $maxmatches
+- seamless_rotate = 1
+- preopen_indexes = 0
+- unlink_old = 1
+-}
+-
+-END
+-
+-}
+-elsif ( $DB{'type'} eq 'Pg' ) {
+- check_tsvalue();
+- my $table = $OPT{'table'} || prompt(
+- message => "Enter the name of a DB table that will be used to store the Pg tsvector.\n"
+- . "You may either use the existing Attachments table, or create a new\n"
+- . "table.",
+- default => $DEFAULT{'table'},
+- silent => !$OPT{'ask'},
+- );
+- my $column = $OPT{'column'} || prompt(
+- message => 'Enter the name of a column that will be used to store the Pg tsvector:',
+- default => $DEFAULT{'column'},
+- silent => !$OPT{'ask'},
+- );
+-
+- my $schema;
+- my $drop;
+- if ( lc($table) eq 'attachments' ) {
+- $drop = "ALTER TABLE $table DROP COLUMN $column";
+- $schema = "ALTER TABLE $table ADD COLUMN $column tsvector";
+- } else {
+- $drop = "DROP TABLE $table";
+- $schema = "CREATE TABLE $table ( "
+- ."id INTEGER NOT NULL,"
+- ."$column tsvector )";
+- }
+-
+- my $index_type = lc($OPT{'index-type'} || '');
+- while ( $index_type ne 'gist' and $index_type ne 'gin' ) {
+- $index_type = lc prompt(
+- message => "You may choose between GiST or GIN indexes; the former is several times\n"
+- . "slower to search, but takes less space on disk and is faster to update.",
+- default => 'GiST',
+- silent => !$OPT{'ask'},
+- );
+- }
+-
+- do_error_is_ok( dba_handle() => $drop )
+- unless $OPT{'dryrun'};
+- insert_schema( $schema );
+- insert_schema("CREATE INDEX ${column}_idx ON $table USING $index_type($column)");
+-
+- print_rt_config( Table => $table, Column => $column );
+-}
+-elsif ( $DB{'type'} eq 'Oracle' ) {
+- {
+- my $dbah = dba_handle();
+- do_print_error( $dbah => 'GRANT CTXAPP TO '. $DB{'user'} );
+- do_print_error( $dbah => 'GRANT EXECUTE ON CTXSYS.CTX_DDL TO '. $DB{'user'} );
+- }
+-
+- my %PREFERENCES = (
+- datastore => {
+- type => 'DIRECT_DATASTORE',
+- },
+- filter => {
+- type => 'AUTO_FILTER',
+-# attributes => {
+-# timeout => 120, # seconds
+-# timeout_type => 'HEURISTIC', # or 'FIXED'
+-# },
+- },
+- lexer => {
+- type => 'WORLD_LEXER',
+- },
+- word_list => {
+- type => 'BASIC_WORDLIST',
+- attributes => {
+- stemmer => 'AUTO',
+- fuzzy_match => 'AUTO',
+-# fuzzy_score => undef,
+-# fuzzy_numresults => undef,
+-# substring_index => undef,
+-# prefix_index => undef,
+-# prefix_length_min => undef,
+-# prefix_length_max => undef,
+-# wlidcard_maxterms => undef,
+- },
+- },
+- 'section_group' => {
+- type => 'NULL_SECTION_GROUP',
+- },
+-
+- storage => {
+- type => 'BASIC_STORAGE',
+- attributes => {
+- R_TABLE_CLAUSE => 'lob (data) store as (cache)',
+- I_INDEX_CLAUSE => 'compress 2',
+- },
+- },
+- );
+-
+- my @params = ();
+- push @params, ora_create_datastore( %{ $PREFERENCES{'datastore'} } );
+- push @params, ora_create_filter( %{ $PREFERENCES{'filter'} } );
+- push @params, ora_create_lexer( %{ $PREFERENCES{'lexer'} } );
+- push @params, ora_create_word_list( %{ $PREFERENCES{'word_list'} } );
+- push @params, ora_create_stop_list();
+- push @params, ora_create_section_group( %{ $PREFERENCES{'section_group'} } );
+- push @params, ora_create_storage( %{ $PREFERENCES{'storage'} } );
+-
+- my $index_params = join "\n", @params;
+- my $index_name = $DEFAULT{prefix} .'index';
+- do_error_is_ok( $dbh => "DROP INDEX $index_name" )
+- unless $OPT{'dryrun'};
+- $dbh->do(
+- "CREATE INDEX $index_name ON Attachments(Content)
+- indextype is ctxsys.context parameters('
+- $index_params
+- ')",
+- ) unless $OPT{'dryrun'};
+-
+- print_rt_config( IndexName => $index_name );
+-}
+-else {
+- die "Full-text indexes on $DB{type} are not yet supported";
+-}
+-
+-sub check_tsvalue {
+- my $dbh = $RT::Handle->dbh;
+- my $fts = ($dbh->selectrow_array(<<EOQ))[0];
+-SELECT 1 FROM information_schema.routines WHERE routine_name = 'plainto_tsquery'
+-EOQ
+- unless ($fts) {
+- print STDERR <<EOT;
+-
+-Your PostgreSQL server does not include full-text support. You will
+-need to upgrade to PostgreSQL version 8.3 or higher to use full-text
+-indexing.
+-
+-EOT
+- exit 1;
+- }
+-}
+-
+-sub check_sphinx {
+- return if $RT::Handle->CheckSphinxSE;
+-
+- print STDERR <<EOT;
+-
+-Your MySQL server has not been compiled with the Sphinx storage engine
+-(sphinxse). You will need to recompile MySQL according to the
+-instructions in Sphinx's documentation at
+-http://sphinxsearch.com/docs/current.html#sphinxse-installing
+-
+-EOT
+- exit 1;
+-}
+-
+-sub ora_create_datastore {
+- return sprintf 'datastore %s', ora_create_preference(
+- @_,
+- name => 'datastore',
+- );
+-}
+-
+-sub ora_create_filter {
+- my $res = '';
+- $res .= sprintf "format column %s\n", ora_create_format_column();
+- $res .= sprintf 'filter %s', ora_create_preference(
+- @_,
+- name => 'filter',
+- );
+- return $res;
+-}
+-
+-sub ora_create_lexer {
+- return sprintf 'lexer %s', ora_create_preference(
+- @_,
+- name => 'lexer',
+- );
+-}
+-
+-sub ora_create_word_list {
+- return sprintf 'wordlist %s', ora_create_preference(
+- @_,
+- name => 'word_list',
+- );
+-}
+-
+-sub ora_create_stop_list {
+- my $file = shift || 'etc/stopwords/en.txt';
+- return '' unless -e $file;
+-
+- my $name = $DEFAULT{'prefix'} .'stop_list';
+- unless ($OPT{'dryrun'}) {
+- do_error_is_ok( $dbh => 'begin ctx_ddl.drop_stoplist(?); end;', $name );
+-
+- $dbh->do(
+- 'begin ctx_ddl.create_stoplist(?, ?); end;',
+- undef, $name, 'BASIC_STOPLIST'
+- );
+-
+- open( my $fh, '<:utf8', $file )
+- or die "couldn't open file '$file': $!";
+- while ( my $word = <$fh> ) {
+- chomp $word;
+- $dbh->do(
+- 'begin ctx_ddl.add_stopword(?, ?); end;',
+- undef, $name, $word
+- );
+- }
+- close $fh;
+- }
+- return sprintf 'stoplist %s', $name;
+-}
+-
+-sub ora_create_section_group {
+- my %args = @_;
+- my $name = $DEFAULT{'prefix'} .'section_group';
+- unless ($OPT{'dryrun'}) {
+- do_error_is_ok( $dbh => 'begin ctx_ddl.drop_section_group(?); end;', $name );
+- $dbh->do(
+- 'begin ctx_ddl.create_section_group(?, ?); end;',
+- undef, $name, $args{'type'}
+- );
+- }
+- return sprintf 'section group %s', $name;
+-}
+-
+-sub ora_create_storage {
+- return sprintf 'storage %s', ora_create_preference(
+- @_,
+- name => 'storage',
+- );
+-}
+-
+-sub ora_create_format_column {
+- my $column_name = 'ContentOracleFormat';
+- return $column_name if $OPT{'dryrun'};
+- unless (
+- $dbh->column_info(
+- undef, undef, uc('Attachments'), uc( $column_name )
+- )->fetchrow_array
+- ) {
+- $dbh->do(qq{
+- ALTER TABLE Attachments ADD $column_name VARCHAR2(10)
+- });
+- }
+-
+- my $detect_format = qq{
+- CREATE OR REPLACE FUNCTION $DEFAULT{prefix}detect_format_simple(
+- parent IN NUMBER,
+- type IN VARCHAR2,
+- encoding IN VARCHAR2,
+- fname IN VARCHAR2
+- )
+- RETURN VARCHAR2
+- AS
+- format VARCHAR2(10);
+- BEGIN
+- format := CASE
+- };
+- unless ( $OPT{'attachments'} ) {
+- $detect_format .= qq{
+- WHEN fname IS NOT NULL THEN 'ignore'
+- };
+- }
+- $detect_format .= qq{
+- WHEN type = 'text' THEN 'text'
+- WHEN type = 'text/rtf' THEN 'ignore'
+- WHEN type LIKE 'text/%' THEN 'text'
+- WHEN type LIKE 'message/%' THEN 'text'
+- ELSE 'ignore'
+- END;
+- RETURN format;
+- END;
+- };
+- ora_create_procedure( $detect_format );
+-
+- $dbh->do(qq{
+- UPDATE Attachments
+- SET $column_name = $DEFAULT{prefix}detect_format_simple(
+- Parent,
+- ContentType, ContentEncoding,
+- Filename
+- )
+- WHERE $column_name IS NULL
+- });
+- $dbh->do(qq{
+- CREATE OR REPLACE TRIGGER $DEFAULT{prefix}set_format
+- BEFORE INSERT
+- ON Attachments
+- FOR EACH ROW
+- BEGIN
+- :new.$column_name := $DEFAULT{prefix}detect_format_simple(
+- :new.Parent,
+- :new.ContentType, :new.ContentEncoding,
+- :new.Filename
+- );
+- END;
+- });
+- return $column_name;
+-}
+-
+-sub ora_create_preference {
+- my %info = @_;
+- my $name = $DEFAULT{'prefix'} . $info{'name'};
+- return $name if $OPT{'dryrun'};
+- do_error_is_ok( $dbh => 'begin ctx_ddl.drop_preference(?); end;', $name );
+- $dbh->do(
+- 'begin ctx_ddl.create_preference(?, ?); end;',
+- undef, $name, $info{'type'}
+- );
+- return $name unless $info{'attributes'};
+-
+- while ( my ($attr, $value) = each %{ $info{'attributes'} } ) {
+- $dbh->do(
+- 'begin ctx_ddl.set_attribute(?, ?, ?); end;',
+- undef, $name, $attr, $value
+- );
+- }
+-
+- return $name;
+-}
+-
+-sub ora_create_procedure {
+- my $text = shift;
+-
+- return if $OPT{'dryrun'};
+- my $status = $dbh->do($text, { RaiseError => 0 });
+-
+- # Statement succeeded
+- return if $status;
+-
+- if ( 6550 != $dbh->err ) {
+- # Utter failure
+- die $dbh->errstr;
+- }
+- else {
+- my $msg = $dbh->func( 'plsql_errstr' );
+- die $dbh->errstr if !defined $msg;
+- die $msg if $msg;
+- }
+-}
+-
+-sub dba_handle {
+- if ( $DB{'type'} eq 'Oracle' ) {
+- $ENV{'NLS_LANG'} = "AMERICAN_AMERICA.AL32UTF8";
+- $ENV{'NLS_NCHAR'} = "AL32UTF8";
+- }
+- my $dsn = do { my $h = new RT::Handle; $h->BuildDSN; $h->DSN };
+- my $dbh = DBI->connect(
+- $dsn, $DB{admin}, $DB{admin_password},
+- { RaiseError => 1, PrintError => 1 },
+- );
+- unless ( $dbh ) {
+- die "Failed to connect to $dsn as user '$DB{admin}': ". $DBI::errstr;
+- }
+- return $dbh;
+-}
+-
+-sub do_error_is_ok {
+- my $dbh = shift;
+- local $dbh->{'RaiseError'} = 0;
+- local $dbh->{'PrintError'} = 0;
+- return $dbh->do(shift, undef, @_);
+-}
+-
+-sub do_print_error {
+- my $dbh = shift;
+- local $dbh->{'RaiseError'} = 0;
+- local $dbh->{'PrintError'} = 1;
+- return $dbh->do(shift, undef, @_);
+-}
+-
+-sub prompt {
+- my %args = ( @_ );
+- return $args{'default'} if $args{'silent'};
+-
+- local $| = 1;
+- print $args{'message'};
+- if ( $args{'default'} ) {
+- print "\n[". $args{'default'} .']: ';
+- } else {
+- print ":\n";
+- }
+-
+- my $res = <STDIN>;
+- chomp $res;
+- print "\n";
+- return $args{'default'} if !$res && $args{'default'};
+- return $res;
+-}
+-
+-sub verbose { print @_, "\n" if $OPT{verbose} || $OPT{verbose}; 1 }
+-sub debug { print @_, "\n" if $OPT{debug}; 1 }
+-sub error { $RT::Logger->error( @_ ); verbose(@_); 1 }
+-sub warning { $RT::Logger->warning( @_ ); verbose(@_); 1 }
+-
+-sub show_help {
+- my $error = shift;
+- RT::Interface::CLI->ShowHelp(
+- ExitValue => $error,
+- Sections => 'NAME|DESCRIPTION',
+- );
+-}
+-
+-sub print_rt_config {
+- my %args = @_;
+- my $config = <<END;
+-
+-You can now configure RT to use the newly-created full-text index by
+-adding the following to your RT_SiteConfig.pm:
+-
+-Set( %FullTextSearch,
+- Enable => 1,
+- Indexed => 1,
+-END
+-
+- $config .= sprintf(" %-10s => '$args{$_}',\n",$_)
+- foreach grep defined $args{$_}, keys %args;
+- $config .= ");\n";
+-
+- print $config;
+-}
+-
+-sub insert_schema {
+- my $dbh = dba_handle();
+- my $message = "Going to run the following in the DB:";
+- my $schema = shift;
+- print "$message\n";
+- my $disp = $schema;
+- $disp =~ s/^/ /mg;
+- print "$disp\n\n";
+- return if $OPT{'dryrun'};
+-
+- my $res = $dbh->do( $schema );
+- unless ( $res ) {
+- die "Couldn't run DDL query: ". $dbh->errstr;
+- }
+-}
+-
+-=head1 NAME
+-
+-rt-setup-fulltext-index - Create indexes for full text search
+-
+-=head1 DESCRIPTION
+-
+-This script creates the appropriate tables, columns, functions, and / or
+-views necessary for full-text searching for your database type. It will
+-drop any existing indexes in the process.
+-
+-Please read F<docs/full_text_indexing.pod> for complete documentation on
+-full-text indexing for your database type.
+-
+-If you have a non-standard database administrator user or password, you
+-may use the C<--dba> and C<--dba-password> parameters to set them
+-explicitly:
+-
+- rt-setup-fulltext-index --dba sysdba --dba-password 'secret'
+-
+-To test what will happen without running any DDL, pass the C<--dryrun>
+-flag.
+-
+-The Oracle index determines which content-types it will index at
+-creation time. By default, textual message bodies and textual uploaded
+-attachments (attachments with filenames) are indexed; to ignore textual
+-attachments, pass the C<--no-attachments> flag when the index is
+-created.
+-
+-
+-=head1 AUTHOR
+-
+-Ruslan Zakirov E<lt>ruz at bestpractical.comE<gt>,
+-Alex Vandiver E<lt>alexmv at bestpractical.comE<gt>
+-
+-=cut
+-
+diff --git a/sbin/rt-shredder b/sbin/rt-shredder
+deleted file mode 100755
+index 0198ab0..0000000
+--- a/sbin/rt-shredder
++++ /dev/null
+@@ -1,325 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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-shredder - Script which wipe out tickets from RT DB
+-
+-=head1 SYNOPSIS
+-
+- rt-shredder --plugin list
+- rt-shredder --plugin help-Tickets
+- rt-shredder --plugin 'Tickets=query,Queue="general" and Status="deleted"'
+-
+- rt-shredder --sqldump unshred.sql --plugin ...
+- rt-shredder --force --plugin ...
+-
+-=head1 DESCRIPTION
+-
+-rt-shredder - is script that allow you to wipe out objects
+-from RT DB. This script uses API that L<RT::Shredder> module adds to RT.
+-Script can be used as example of usage of the shredder API.
+-
+-=head1 USAGE
+-
+-You can use several options to control which objects script
+-should wipeout.
+-
+-=head1 OPTIONS
+-
+-=head2 --sqldump <filename>
+-
+-Outputs INSERT queries into file. This dump can be used to restore data
+-after wiping out.
+-
+-By default creates files named F<< <ISO_date>-XXXX.sql >> in the current
+-directory.
+-
+-=head2 --object (DEPRECATED)
+-
+-Option has been deprecated, use plugin C<Objects> instead.
+-
+-=head2 --plugin '<plugin name>[=<arg>,<val>[;<arg>,<val>]...]'
+-
+-You can use plugins to select RT objects with various conditions.
+-See also --plugin list and --plugin help options.
+-
+-=head2 --plugin list
+-
+-Output list of the available plugins.
+-
+-=head2 --plugin help-<plugin name>
+-
+-Outputs help for specified plugin.
+-
+-=head2 --force
+-
+-Script doesn't ask any questions.
+-
+-=head1 SEE ALSO
+-
+-L<RT::Shredder>
+-
+-=cut
+-
+-use strict;
+-use warnings FATAL => 'all';
+-
+-# fix lib paths, some may be relative
+-BEGIN {
+- require File::Spec;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- }
+- else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-
+-}
+-
+-use RT -init;
+-
+-require RT::Shredder;
+-
+-use Getopt::Long qw(GetOptions);
+-use File::Spec ();
+-
+-use RT::Shredder::Plugin ();
+-# prefetch list of plugins
+-our %plugins = RT::Shredder::Plugin->List;
+-
+-our %opt;
+-parse_args();
+-
+-my $shredder = RT::Shredder->new;
+-
+-{
+- my $plugin = eval { $shredder->AddDumpPlugin( Arguments => {
+- file_name => $opt{'sqldump'},
+- from_storage => 0,
+- } ) };
+- if( $@ ) {
+- print STDERR "ERROR: Couldn't open SQL dump file: $@\n";
+- exit 1 if $opt{'sqldump'};
+-
+- print STDERR "WARNING: It's strongly recommended to use '--sqldump <filename>' option\n";
+- unless( $opt{'force'} ) {
+- exit 0 unless prompt_yN( "Do you want to proceed?" );
+- }
+- } else {
+- print "SQL dump file is '". $plugin->FileName ."'\n";
+- }
+-}
+-
+-my @objs = process_plugins( $shredder );
+-prompt_delete_objs( \@objs ) unless $opt{'force'};
+-
+-$shredder->PutObjects( Objects => $_ ) foreach @objs;
+-eval { $shredder->WipeoutAll };
+-if( $@ ) {
+- require RT::Shredder::Exceptions;
+- if( my $e = RT::Shredder::Exception::Info->caught ) {
+- print "\nERROR: $e\n\n";
+- exit 1;
+- }
+- die $@;
+-}
+-
+-sub prompt_delete_objs
+-{
+- my( $objs ) = @_;
+- unless( @$objs ) {
+- print "Objects list is empty, try refine search options\n";
+- exit 0;
+- }
+- my $list = "Next ". scalar( @$objs ) ." objects would be deleted:\n";
+- foreach my $o( @$objs ) {
+- $list .= "\t". $o->_AsString ." object\n";
+- }
+- print $list;
+- exit(0) unless prompt_yN( "Do you want to proceed?" );
+-}
+-
+-sub prompt_yN
+-{
+- my $text = shift;
+- print "$text [y/N] ";
+- unless( <STDIN> =~ /^(?:y|yes)$/i ) {
+- return 0;
+- }
+- return 1;
+-}
+-
+-sub usage
+-{
+- require RT::Shredder::POD;
+- RT::Shredder::POD::shredder_cli( $0, \*STDOUT );
+- exit 1;
+-}
+-
+-sub parse_args
+-{
+- my $tmp;
+- Getopt::Long::Configure( "pass_through" );
+- my @objs = ();
+- if( GetOptions( 'object=s' => \@objs ) && @objs ) {
+- print STDERR "Option --object had been deprecated, use plugin 'Objects' instead\n";
+- exit(1);
+- }
+-
+- my @plugins = ();
+- if( GetOptions( 'plugin=s' => \@plugins ) && @plugins ) {
+- $opt{'plugin'} = \@plugins;
+- foreach my $str( @plugins ) {
+- if( $str =~ /^\s*list\s*$/ ) {
+- show_plugin_list();
+- } elsif( $str =~ /^\s*help-(\w+)\s*$/ ) {
+- show_plugin_help( $1 );
+- } elsif( $str =~ /^(\w+)(=.*)?$/ && !$plugins{$1} ) {
+- print "Couldn't find plugin '$1'\n";
+- show_plugin_list();
+- }
+- }
+- }
+-
+- # other options make no sense without previouse
+- usage() unless keys %opt;
+-
+- if( GetOptions( 'force' => \$tmp ) && $tmp ) {
+- $opt{'force'}++;
+- }
+- $tmp = undef;
+- if( GetOptions( 'sqldump=s' => \$tmp ) && $tmp ) {
+- $opt{'sqldump'} = $tmp;
+- }
+- return;
+-}
+-
+-sub process_plugins
+-{
+- my $shredder = shift;
+-
+- my @res;
+- foreach my $str( @{ $opt{'plugin'} } ) {
+- my $plugin = RT::Shredder::Plugin->new;
+- my( $status, $msg ) = $plugin->LoadByString( $str );
+- unless( $status ) {
+- print STDERR "Couldn't load plugin\n";
+- print STDERR "Error: $msg\n";
+- exit(1);
+- }
+- if ( lc $plugin->Type eq 'search' ) {
+- push @res, _process_search_plugin( $shredder, $plugin );
+- }
+- elsif ( lc $plugin->Type eq 'dump' ) {
+- _process_dump_plugin( $shredder, $plugin );
+- }
+- }
+- return RT::Shredder->CastObjectsToRecords( Objects => \@res );
+-}
+-
+-sub _process_search_plugin {
+- my ($shredder, $plugin) = @_;
+- my ($status, @objs) = $plugin->Run;
+- unless( $status ) {
+- print STDERR "Couldn't run plugin\n";
+- print STDERR "Error: $objs[1]\n";
+- exit(1);
+- }
+-
+- my $msg;
+- ($status, $msg) = $plugin->SetResolvers( Shredder => $shredder );
+- unless( $status ) {
+- print STDERR "Couldn't set conflicts resolver\n";
+- print STDERR "Error: $msg\n";
+- exit(1);
+- }
+- return @objs;
+-}
+-
+-sub _process_dump_plugin {
+- my ($shredder, $plugin) = @_;
+- $shredder->AddDumpPlugin(
+- Object => $plugin,
+- );
+-}
+-
+-sub show_plugin_list
+-{
+- print "Plugins list:\n";
+- print "\t$_\n" foreach( grep !/^Base$/, keys %plugins );
+- exit(1);
+-}
+-
+-sub show_plugin_help
+-{
+- my( $name ) = @_;
+- require RT::Shredder::POD;
+- unless( $plugins{ $name } ) {
+- print "Couldn't find plugin '$name'\n";
+- show_plugin_list();
+- }
+- RT::Shredder::POD::plugin_cli( $plugins{'Base'}, \*STDOUT, 1 );
+- RT::Shredder::POD::plugin_cli( $plugins{ $name }, \*STDOUT );
+- exit(1);
+-}
+-
+-exit(0);
+diff --git a/sbin/rt-test-dependencies b/sbin/rt-test-dependencies
+deleted file mode 100755
+index 63a0956..0000000
+--- a/sbin/rt-test-dependencies
++++ /dev/null
+@@ -1,694 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license to use, copy, create derivative
+-# works based on those contributions, and sublicense and distribute
+-# those contributions and any derivatives thereof.
+-#
+-# END BPS TAGGED BLOCK }}}
+-#
+-# This is just a basic script that checks to make sure that all
+-# the modules needed by RT before you can install it.
+-#
+-
+-use strict;
+-use warnings;
+-no warnings qw(numeric redefine);
+-use Getopt::Long;
+-use Cwd qw(abs_path);
+-my %args;
+-my %deps;
+-my @orig_argv = @ARGV;
+-# Save our path because installers or tests can change cwd
+-my $script_path = abs_path($0);
+-
+-GetOptions(
+- \%args, 'v|verbose',
+- 'install!', 'with-MYSQL',
+- 'with-POSTGRESQL|with-pg|with-pgsql', 'with-SQLITE',
+- 'with-ORACLE', 'with-FASTCGI',
+- 'with-MODPERL1', 'with-MODPERL2',
+- 'with-STANDALONE',
+-
+- 'with-DEV',
+-
+- 'with-GPG',
+- 'with-ICAL',
+- 'with-SMTP',
+- 'with-GRAPHVIZ',
+- 'with-GD',
+- 'with-DASHBOARDS',
+- 'with-USERLOGO',
+- 'with-SSL-MAILGATE',
+- 'with-HTML-DOC',
+-
+- 'download=s',
+- 'repository=s',
+- 'list-deps',
+- 'help|h',
+-);
+-
+-if ( $args{help} ) {
+- require Pod::Usage;
+- Pod::Usage::pod2usage( { verbose => 2 } );
+- exit;
+-}
+-
+-# Set up defaults
+-my %default = (
+- 'with-MASON' => 1,
+- 'with-PSGI' => 1,
+- 'with-CORE' => 1,
+- 'with-CLI' => 1,
+- 'with-MAILGATE' => 1,
+- 'with-DEV' => 0,
+- 'with-GPG' => 1,
+- 'with-ICAL' => 1,
+- 'with-SMTP' => 1,
+- 'with-GRAPHVIZ' => 1,
+- 'with-GD' => 1,
+- 'with-DASHBOARDS' => 1,
+- 'with-USERLOGO' => 1,
+- 'with-SSL-MAILGATE' => 0,
+- 'with-HTML-DOC' => 0,
+-);
+-$args{$_} = $default{$_} foreach grep !exists $args{$_}, keys %default;
+-
+-{
+- my $section;
+- my %always_show_sections = (
+- perl => 1,
+- users => 1,
+- );
+-
+- sub section {
+- my $s = shift;
+- $section = $s;
+- print "$s:\n" unless $args{'list-deps'};
+- }
+-
+- sub print_found {
+- my $msg = shift;
+- my $test = shift;
+- my $extra = shift;
+-
+- unless ( $args{'list-deps'} ) {
+- if ( $args{'v'} or not $test or $always_show_sections{$section} ) {
+- print "\t$msg ...";
+- print $test ? "found" : "MISSING";
+- print "\n";
+- }
+-
+- print "\t\t$extra\n" if defined $extra;
+- }
+- }
+-}
+-
+-sub conclude {
+- my %missing_by_type = @_;
+-
+- unless ( $args{'list-deps'} ) {
+- unless ( keys %missing_by_type ) {
+- print "\nAll dependencies have been found.\n";
+- return;
+- }
+-
+- print "\nSOME DEPENDENCIES WERE MISSING.\n";
+-
+- for my $type ( keys %missing_by_type ) {
+- my $missing = $missing_by_type{$type};
+-
+- print "$type missing dependencies:\n";
+- for my $name ( keys %$missing ) {
+- my $module = $missing->{$name};
+- my $version = $module->{version};
+- my $error = $module->{error};
+- print_found( $name . ( $version && !$error ? " >= $version" : "" ),
+- 0, $module->{error} );
+- }
+- }
+- exit 1;
+- }
+-}
+-
+-sub text_to_hash {
+- my %hash;
+- for my $line ( split /\n/, $_[0] ) {
+- my($key, $value) = $line =~ /(\S+)\s*(\S*)/;
+- $value ||= '';
+- $hash{$key} = $value;
+- }
+-
+- return %hash;
+-}
+-sub set_dep {
+- my ($name, $module, $version) = @_;
+- my %list = @{$deps{$name}};
+- $list{$module} = ($version || '');
+- $deps{$name} = [ %list ];
+-}
+-
+-$deps{'CORE'} = [ text_to_hash( << '.') ];
+-Class::Accessor 0.34
+-DateTime 0.44
+-DateTime::Locale 0.40
+-Digest::base
+-Digest::MD5 2.27
+-Digest::SHA
+-DBI 1.37
+-Class::ReturnValue 0.40
+-DBIx::SearchBuilder 1.59
+-Text::Template 1.44
+-File::ShareDir
+-File::Spec 0.8
+-HTML::Quoted
+-HTML::Scrubber 0.08
+-HTML::TreeBuilder
+-HTML::FormatText
+-Log::Dispatch 2.23
+-Sys::Syslog 0.16
+-Locale::Maketext 1.06
+-Locale::Maketext::Lexicon 0.32
+-Locale::Maketext::Fuzzy
+-MIME::Entity 5.425
+-Mail::Mailer 1.57
+-Email::Address
+-Text::Wrapper
+-Time::ParseDate
+-Time::HiRes
+-File::Temp 0.19
+-Text::Quoted 2.02
+-Tree::Simple 1.04
+-UNIVERSAL::require
+-Regexp::Common
+-Scalar::Util
+-Module::Versions::Report 1.05
+-Cache::Simple::TimedExpiry
+-Encode 2.39
+-CSS::Squish 0.06
+-File::Glob
+-Devel::StackTrace 1.19
+-Text::Password::Pronounceable
+-Devel::GlobalDestruction
+-List::MoreUtils
+-Net::CIDR
+-Regexp::Common::net::CIDR
+-Regexp::IPv6
+-.
+-
+-$deps{'MASON'} = [ text_to_hash( << '.') ];
+-HTML::Mason 1.43
+-Errno
+-Digest::MD5 2.27
+-CGI::Cookie 1.20
+-Storable 2.08
+-Apache::Session 1.53
+-XML::RSS 1.05
+-Text::WikiFormat 0.76
+-CSS::Squish 0.06
+-Devel::StackTrace 1.19
+-JSON
+-IPC::Run3
+-.
+-
+-$deps{'PSGI'} = [ text_to_hash( << '.') ];
+-CGI 3.38
+-CGI::PSGI 0.12
+-HTML::Mason::PSGIHandler 0.52
+-Plack 0.9971
+-Plack::Handler::Starlet
+-CGI::Emulate::PSGI
+-.
+-set_dep( PSGI => CGI => 4.00 ) if $] > 5.019003;
+-
+-
+-$deps{'MAILGATE'} = [ text_to_hash( << '.') ];
+-Getopt::Long
+-LWP::UserAgent
+-Pod::Usage
+-.
+-
+-$deps{'SSL-MAILGATE'} = [ text_to_hash( << '.') ];
+-Crypt::SSLeay
+-Net::SSL
+-LWP::UserAgent 6.0
+-LWP::Protocol::https
+-Mozilla::CA
+-.
+-
+-$deps{'CLI'} = [ text_to_hash( << '.') ];
+-Getopt::Long 2.24
+-LWP
+-HTTP::Request::Common
+-Text::ParseWords
+-Term::ReadLine
+-Term::ReadKey
+-.
+-
+-$deps{'DEV'} = [ text_to_hash( << '.') ];
+-Email::Abstract
+-Test::Email
+-HTML::Form
+-HTML::TokeParser
+-WWW::Mechanize 1.52
+-Test::WWW::Mechanize 1.30
+-Module::Refresh 0.03
+-Test::Expect 0.31
+-XML::Simple
+-File::Find
+-Test::Deep 0 # needed for shredder tests
+-String::ShellQuote 0 # needed for gnupg-incoming.t
+-Log::Dispatch::Perl
+-Test::Warn
+-Test::Builder 0.90 # needed for is_passing
+-Test::MockTime
+-Log::Dispatch::Perl
+-Test::WWW::Mechanize::PSGI
+-Plack::Middleware::Test::StashWarnings 0.08
+-Test::LongString
+-Test::NoWarnings
+-Locale::PO
+-.
+-
+-$deps{'FASTCGI'} = [ text_to_hash( << '.') ];
+-FCGI 0.74
+-FCGI::ProcManager
+-.
+-
+-$deps{'MODPERL1'} = [ text_to_hash( << '.') ];
+-Apache::Request
+-Apache::DBI 0.92
+-.
+-
+-$deps{'MODPERL2'} = [ text_to_hash( << '.') ];
+-Apache::DBI
+-HTML::Mason 1.36
+-.
+-
+-$deps{'MYSQL'} = [ text_to_hash( << '.') ];
+-DBD::mysql 2.1018
+-.
+-
+-$deps{'ORACLE'} = [ text_to_hash( << '.') ];
+-DBD::Oracle
+-.
+-
+-$deps{'POSTGRESQL'} = [ text_to_hash( << '.') ];
+-DBIx::SearchBuilder 1.66
+-DBD::Pg 1.43
+-.
+-
+-$deps{'SQLITE'} = [ text_to_hash( << '.') ];
+-DBD::SQLite 1.00
+-.
+-
+-$deps{'GPG'} = [ text_to_hash( << '.') ];
+-GnuPG::Interface
+-PerlIO::eol
+-.
+-
+-$deps{'ICAL'} = [ text_to_hash( << '.') ];
+-Data::ICal
+-.
+-
+-$deps{'SMTP'} = [ text_to_hash( << '.') ];
+-Net::SMTP
+-.
+-
+-$deps{'DASHBOARDS'} = [ text_to_hash( << '.') ];
+-HTML::RewriteAttributes 0.05
+-MIME::Types
+-URI 1.59
+-.
+-
+-$deps{'GRAPHVIZ'} = [ text_to_hash( << '.') ];
+-GraphViz
+-IPC::Run 0.90
+-.
+-
+-$deps{'GD'} = [ text_to_hash( << '.') ];
+-GD
+-GD::Graph
+-GD::Text
+-.
+-
+-$deps{'USERLOGO'} = [ text_to_hash( << '.') ];
+-Convert::Color
+-.
+-
+-$deps{'HTML-DOC'} = [ text_to_hash( <<'.') ];
+-Pod::Simple 3.24
+-HTML::Entities
+-.
+-
+-my %AVOID = (
+- 'DBD::Oracle' => [qw(1.23)],
+- 'Email::Address' => [qw(1.893 1.894)],
+- 'Devel::StackTrace' => [qw(1.28 1.29)],
+-);
+-
+-if ($args{'download'}) {
+- download_mods();
+-}
+-
+-
+-check_perl_version();
+-
+-check_users();
+-
+-my %Missing_By_Type = ();
+-foreach my $type (sort grep $args{$_}, keys %args) {
+- next unless ($type =~ /^with-(.*?)$/) and $deps{$1};
+-
+- $type = $1;
+- section("$type dependencies");
+-
+- my @missing;
+- my @deps = @{ $deps{$type} };
+-
+- my %missing = test_deps(@deps);
+-
+- if ( $args{'install'} ) {
+- for my $module (keys %missing) {
+- resolve_dep($module, $missing{$module}{version});
+- my $m = $module . '.pm';
+- $m =~ s!::!/!g;
+- if ( delete $INC{$m} ) {
+- my $symtab = $module . '::';
+- no strict 'refs';
+- for my $symbol ( keys %{$symtab} ) {
+- next if substr( $symbol, -2, 2 ) eq '::';
+- delete $symtab->{$symbol};
+- }
+- }
+- delete $missing{$module}
+- if test_dep($module, $missing{$module}{version}, $AVOID{$module});
+- }
+- }
+-
+- $Missing_By_Type{$type} = \%missing if keys %missing;
+-}
+-
+-if ( $args{'install'} && keys %Missing_By_Type ) {
+- exec($script_path, @orig_argv, '--no-install');
+-}
+-else {
+- conclude(%Missing_By_Type);
+-}
+-
+-sub test_deps {
+- my @deps = @_;
+-
+- my %missing;
+- while(@deps) {
+- my $module = shift @deps;
+- my $version = shift @deps;
+- my($test, $error) = test_dep($module, $version, $AVOID{$module});
+- my $msg = $module . ($version && !$error ? " >= $version" : '');
+- print_found($msg, $test, $error);
+-
+- $missing{$module} = { version => $version, error => $error } unless $test;
+- }
+-
+- return %missing;
+-}
+-
+-sub test_dep {
+- my $module = shift;
+- my $version = shift;
+- my $avoid = shift;
+-
+- if ( $args{'list-deps'} ) {
+- print $module, ': ', $version || 0, "\n";
+- }
+- else {
+- no warnings 'deprecated';
+- eval "use $module $version ()";
+- if ( my $error = $@ ) {
+- return 0 unless wantarray;
+-
+- $error =~ s/\n(.*)$//s;
+- $error =~ s/at \(eval \d+\) line \d+\.$//;
+- undef $error if $error =~ /this is only/;
+-
+- return ( 0, $error );
+- }
+-
+- if ( $avoid ) {
+- my $version = $module->VERSION;
+- if ( grep $version eq $_, @$avoid ) {
+- return 0 unless wantarray;
+- return (0, "It's known that there are problems with RT and version '$version' of '$module' module. If it's the latest available version of the module then you have to downgrade manually.");
+- }
+- }
+-
+- return 1;
+- }
+-}
+-
+-sub resolve_dep {
+- my $module = shift;
+- my $version = shift;
+-
+- print "\nInstall module $module\n";
+-
+- my $ext = $ENV{'RT_FIX_DEPS_CMD'} || $ENV{'PERL_PREFER_CPAN_CLIENT'};
+- unless( $ext ) {
+- my $configured = 1;
+- {
+- local @INC = @INC;
+- if ( $ENV{'HOME'} ) {
+- unshift @INC, "$ENV{'HOME'}/.cpan";
+- }
+- $configured = eval { require CPAN::MyConfig } || eval { require CPAN::Config };
+- }
+- unless ( $configured ) {
+- print <<END;
+-You haven't configured the CPAN shell yet.
+-Please run `/usr/bin/perl -MCPAN -e shell` to configure it.
+-END
+- exit(1);
+- }
+- my $rv = eval { require CPAN; CPAN::Shell->install($module) };
+- return $rv unless $@;
+-
+- print <<END;
+-Failed to load module CPAN.
+-
+--------- Error ---------
+-$@
+-------------------------
+-
+-When we tried to start installing RT's perl dependencies,
+-we were unable to load the CPAN client. This module is usually distributed
+-with Perl. This usually indicates that your vendor has shipped an unconfigured
+-or incorrectly configured CPAN client.
+-The error above may (or may not) give you a hint about what went wrong
+-
+-You have several choices about how to install dependencies in
+-this situatation:
+-
+-1) use a different tool to install dependencies by running setting the following
+- shell environment variable and rerunning this tool:
+- RT_FIX_DEPS_CMD='/usr/bin/perl -MCPAN -e"install %s"'
+-2) Attempt to configure CPAN by running:
+- `/usr/bin/perl -MCPAN -e shell` program from shell.
+- If this fails, you may have to manually upgrade CPAN (see below)
+-3) Try to update the CPAN client. Download it from:
+- http://search.cpan.org/dist/CPAN and try again
+-4) Install each dependency manually by downloading them one by one from
+- http://search.cpan.org
+-
+-END
+- exit(1);
+- }
+-
+- if( $ext =~ /\%s/) {
+- $ext =~ s/\%s/$module/g; # sprintf( $ext, $module );
+- } else {
+- $ext .= " $module";
+- }
+- print "\t\tcommand: '$ext'\n";
+- return scalar `$ext 1>&2`;
+-}
+-
+-sub download_mods {
+- my %modules;
+- use CPAN;
+-
+- foreach my $key (keys %deps) {
+- my @deps = (@{$deps{$key}});
+- while (@deps) {
+- my $mod = shift @deps;
+- my $ver = shift @deps;
+- next if ($mod =~ /^(DBD-|Apache-Request)/);
+- $modules{$mod} = $ver;
+- }
+- }
+- my @mods = keys %modules;
+- CPAN::get();
+- my $moddir = $args{'download'};
+- foreach my $mod (@mods) {
+- $CPAN::Config->{'build_dir'} = $moddir;
+- CPAN::get($mod);
+- }
+-
+- opendir(DIR, $moddir);
+- while ( my $dir = readdir(DIR)) {
+- print "Dir is $dir\n";
+- next if ( $dir =~ /^\.\.?$/);
+-
+- # Skip things we've previously tagged
+- my $out = `svn ls $args{'repository'}/tags/$dir`;
+- next if ($out);
+-
+- if ($dir =~ /^(.*)-(.*?)$/) {
+- `svn_load_dirs -no_user_input -t tags/$dir -v $args{'repository'} dists/$1 $moddir/$dir`;
+- `rm -rf $moddir/$dir`;
+-
+- }
+-
+- }
+- closedir(DIR);
+- exit;
+-}
+-
+-sub check_perl_version {
+- section("perl");
+- eval {require 5.008003};
+- if ($@) {
+- print_found("5.8.3", 0,"RT is known to be non-functional on versions of perl older than 5.8.3. Please upgrade to 5.8.3 or newer.");
+- exit(1);
+- } else {
+- print_found( sprintf(">=5.8.3(%vd)", $^V), 1 );
+- }
+-}
+-
+-sub check_users {
+- section("users");
+- print_found("rt group (www-data)", defined getgrnam("www-data"));
+- print_found("bin owner (root)", defined getpwnam("root"));
+- print_found("libs owner (root)", defined getpwnam("root"));
+- print_found("libs group (bin)", defined getgrnam("bin"));
+- print_found("web owner (www-data)", defined getpwnam("www-data"));
+- print_found("web group (www-data)", defined getgrnam("www-data"));
+-}
+-
+-1;
+-
+-__END__
+-
+-=head1 NAME
+-
+-rt-test-dependencies - test rt's dependencies
+-
+-=head1 SYNOPSIS
+-
+- rt-test-dependencies
+- rt-test-dependencies --install
+- rt-test-dependencies --with-mysql --with-fastcgi
+-
+-=head1 DESCRIPTION
+-
+-by default, C<rt-test-dependencies> determines whether you have installed all
+-the perl modules RT needs to run.
+-
+-the "RT_FIX_DEPS_CMD" environment variable, if set, will be used instead of
+-the standard CPAN shell by --install to install any required modules. it will
+-be called with the module name, or, if "RT_FIX_DEPS_CMD" contains a "%s", will
+-replace the "%s" with the module name before calling the program.
+-
+-=head1 OPTIONS
+-
+-=over
+-
+-=item install
+-
+- install missing modules
+-
+-=item verbose
+-
+-list the status of all dependencies, rather than just the missing ones.
+-
+--v is equal to --verbose
+-
+-=item specify dependencies
+-
+-=over
+-
+-=item --with-mysql
+-
+- database interface for mysql
+-
+-=item --with-postgresql
+-
+- database interface for postgresql
+-
+-=item with-oracle
+-
+- database interface for oracle
+-
+-=item with-sqlite
+-
+- database interface and driver for sqlite (unsupported)
+-
+-=item with-fastcgi
+-
+- libraries needed to support the fastcgi handler
+-
+-=item with-modperl1
+-
+- libraries needed to support the modperl 1 handler
+-
+-=item with-modperl2
+-
+- libraries needed to support the modperl 2 handler
+-
+-=item with-dev
+-
+- tools needed for RT development
+-
+-=back
+-
+-=back
+-
+diff --git a/sbin/rt-validate-aliases b/sbin/rt-validate-aliases
+deleted file mode 100755
+index e4be013..0000000
+--- a/sbin/rt-validate-aliases
++++ /dev/null
+@@ -1,343 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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 Text::ParseWords qw//;
+-use Getopt::Long;
+-
+-BEGIN { # BEGIN RT CMD BOILERPLATE
+- require File::Spec;
+- require Cwd;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- $bin_path ||= ( File::Spec->splitpath(Cwd::abs_path(__FILE__)) )[1];
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-}
+-
+-require RT;
+-RT::LoadConfig();
+-RT::Init();
+-
+-my ($PREFIX, $URL, $HOST) = ("");
+-GetOptions(
+- "prefix|p=s" => \$PREFIX,
+- "url|u=s" => \$URL,
+- "host|h=s" => \$HOST,
+-);
+-
+-unless (@ARGV) {
+- @ARGV = grep {-f} ("/etc/aliases",
+- "/etc/mail/aliases",
+- "/etc/postfix/aliases");
+- die "Can't determine aliases file to parse!"
+- unless @ARGV;
+-}
+-
+-my %aliases = parse_lines();
+-unless (%aliases) {
+- warn "No mailgate aliases found in @ARGV";
+- exit;
+-}
+-
+-my %seen;
+-my $global_mailgate;
+-for my $address (sort keys %aliases) {
+- my ($mailgate, $opts, $extra) = @{$aliases{$address}};
+- my %opts = %{$opts};
+-
+- next if $opts{url} and $URL and $opts{url} !~ /\Q$URL\E/;
+-
+- if ($mailgate !~ /^\|/) {
+- warn "Missing the leading | on alias $address\n";
+- $mailgate = "|$mailgate";
+- }
+- if (($global_mailgate ||= $mailgate) ne $mailgate) {
+- warn "Unexpected mailgate for alias $address -- expected $global_mailgate, got $mailgate\n";
+- }
+-
+- if (not defined $opts{action}) {
+- warn "Missing --action parameter for alias $address\n";
+- } elsif ($opts{action} !~ /^(correspond|comment)$/) {
+- warn "Invalid --action parameter for alias $address: $opts{action}\n"
+- }
+-
+- my $queue = RT::Queue->new( RT->SystemUser );
+- if (not defined $opts{queue}) {
+- warn "Missing --queue parameter for alias $address\n";
+- } else {
+- $queue->Load( $opts{queue} );
+- if (not $queue->id) {
+- warn "Invalid --queue parameter for alias $address: $opts{queue}\n";
+- } elsif ($queue->Disabled) {
+- warn "Disabled --queue given for alias $address: $opts{queue}\n";
+- }
+- }
+-
+- if (not defined $opts{url}) {
+- warn "Missing --url parameter for alias $address\n";
+- } #XXX: Test connectivity and/or https certs?
+-
+- if ($queue->id and $opts{action} =~ /^(correspond|comment)$/) {
+- push @{$seen{lc $queue->Name}{$opts{action}}}, $address;
+- }
+-
+- warn "Unknown extra arguments for alias $address: @{$extra}\n"
+- if @{$extra};
+-}
+-
+-# Check the global settings
+-my %global;
+-for my $action (qw/correspond comment/) {
+- my $setting = ucfirst($action) . "Address";
+- my $value = RT->Config->Get($setting);
+- if (not defined $value) {
+- warn "$setting is not set!\n";
+- next;
+- }
+- my ($local,$host) = lc($value) =~ /(.*?)\@(.*)/;
+- next if $HOST and $host !~ /\Q$HOST\E/;
+- $local = "$PREFIX$local" unless exists $aliases{$local};
+-
+- $global{$setting} = $local;
+- if (not exists $aliases{$local}) {
+- warn "$setting $value does not exist in aliases!\n"
+- } elsif ($aliases{$local}[1]{action} ne $action) {
+- warn "$setting $value is a $aliases{$local}[1]{action} in aliases!"
+- }
+-}
+-warn "CorrespondAddress and CommentAddress are the same!\n"
+- if RT->Config->Get("CorrespondAddress") eq RT->Config->Get("CommentAddress");
+-
+-
+-# Go through the queues, one at a time
+-my $queues = RT::Queues->new( RT->SystemUser );
+-$queues->UnLimit;
+-while (my $q = $queues->Next) {
+- my $qname = $q->Name;
+- for my $action (qw/correspond comment/) {
+- my $setting = ucfirst($action) . "Address";
+- my $value = $q->$setting;
+-
+- if (not $value) {
+- my @other = grep {$_ ne $global{$setting}} @{$seen{lc $q->Name}{$action} || []};
+- warn "CorrespondAddress not set on $qname, but in aliases as "
+- .join(" and ", @other) . "\n" if @other;
+- next;
+- }
+-
+- if ($action eq "comment" and $q->CorrespondAddress
+- and $q->CorrespondAddress eq $q->CommentAddress) {
+- warn "CorrespondAddress and CommentAddress are set the same on $qname\n";
+- next;
+- }
+-
+- my ($local, $host) = lc($value) =~ /(.*?)\@(.*)/;
+- next if $HOST and $host !~ /\Q$HOST\E/;
+- $local = "$PREFIX$local" unless exists $aliases{$local};
+-
+- my @other = @{$seen{lc $q->Name}{$action} || []};
+- if (not exists $aliases{$local}) {
+- if (@other) {
+- warn "$setting $value on $qname does not exist in aliases -- typo'd as "
+- .join(" or ", @other) . "?\n";
+- } else {
+- warn "$setting $value on $qname does not exist in aliases!\n"
+- }
+- next;
+- }
+-
+- my %opt = %{$aliases{$local}[1]};
+- if ($opt{action} ne $action) {
+- warn "$setting address $value on $qname is a $opt{action} in aliases!\n"
+- }
+- if (lc $opt{queue} ne lc $q->Name and $action ne "comment") {
+- warn "$setting address $value on $qname points to queue $opt{queue} in aliases!\n";
+- }
+-
+- @other = grep {$_ ne $local} @other;
+- warn "Extra aliases for queue $qname: ".join(",", at other)."\n"
+- if @other;
+- }
+-}
+-
+-
+-sub parse_lines {
+- local @ARGV = @ARGV;
+-
+- my %aliases;
+- my $line = "";
+- for (<>) {
+- next unless /\S/;
+- next if /^#/;
+- chomp;
+- if (/^\s+/) {
+- $line .= $_;
+- } else {
+- add_line($line, \%aliases);
+- $line = $_;
+- }
+- }
+- add_line($line, \%aliases);
+-
+- expand(\%aliases);
+- filter_mailgate(\%aliases);
+-
+- return %aliases;
+-}
+-
+-sub expand {
+- my ($data) = @_;
+-
+- for (1..100) {
+- my $expanded = 0;
+- for my $address (sort keys %{$data}) {
+- my @new;
+- for my $part (@{$data->{$address}}) {
+- if (m!^[|/]! or not $data->{$part}) {
+- push @new, $part;
+- } else {
+- $expanded++;
+- push @new, @{$data->{$part}};
+- }
+- }
+- $data->{$address} = \@new;
+- }
+- return unless $expanded;
+- }
+- warn "Recursion limit exceeded -- cycle in aliases?\n";
+-}
+-
+-sub filter_mailgate {
+- my ($data) = @_;
+-
+- for my $address (sort keys %{$data}) {
+- my @parts = @{delete $data->{$address}};
+-
+- my @pipes = grep {m!^\|?.*?/rt-mailgate\b!} @parts;
+- next unless @pipes;
+-
+- my $pipe = shift @pipes;
+- warn "More than one rt-mailgate pipe for alias: $address\n"
+- if @pipes;
+-
+- my @args = Text::ParseWords::shellwords($pipe);
+-
+- # We allow "|/random-other-command /opt/rt4/bin/rt-mailgate ...",
+- # we just need to strip off enough
+- my $index = 0;
+- $index++ while $args[$index] !~ m!/rt-mailgate!;
+- my $mailgate = join(' ', splice(@args,0,$index+1));
+-
+- my %opts;
+- local @ARGV = @args;
+- Getopt::Long::Configure( "pass_through" ); # Allow unknown options
+- my $ret = eval {
+- GetOptions( \%opts, "queue=s", "action=s", "url=s",
+- "jar=s", "debug", "extension=s",
+- "timeout=i", "verify-ssl!", "ca-file=s",
+- );
+- 1;
+- };
+- warn "Failed to parse options for $address: $@" unless $ret;
+- next unless %opts;
+-
+- $data->{lc $address} = [$mailgate, \%opts, [@ARGV]];
+- }
+-}
+-
+-sub add_line {
+- my ($line, $data) = @_;
+- return unless $line =~ /\S/;
+-
+- my ($name, $parts) = parse_line($line);
+- return unless defined $name;
+-
+- if (defined $data->{$name}) {
+- warn "Duplicate definition for alias $name\n";
+- return;
+- }
+-
+- $data->{lc $name} = $parts;
+-}
+-
+-sub parse_line {
+- my $re_name = qr/\S+/;
+- # Intentionally accept pipe-like aliases with a missing | -- we deal with them later
+- my $re_quoted_pipe = qr/"\|?[^\\"]*(?:\\[\\"][^\\"]*)*"/;
+- my $re_nonquoted_pipe = qr/\|[^\s,]+/;
+- my $re_pipe = qr/(?:$re_quoted_pipe|$re_nonquoted_pipe)/;
+- my $re_path = qr!/[^,\s]+!;
+- my $re_address = qr![^|/,\s][^,\s]*!;
+- my $re_value = qr/(?:$re_pipe|$re_path|$re_address)/;
+- my $re_values = qr/(?:$re_value(?:\s*,\s*$re_value)*)/;
+-
+- my ($line) = @_;
+- if ($line =~ /^($re_name):\s*($re_values)/) {
+- my ($name, $all_parts) = ($1, $2);
+- my @parts;
+- while ($all_parts =~ s/^(?:\s*,\s*)?($re_value)//) {
+- my $part = $1;
+- if ($part =~ /^"/) {
+- $part =~ s/^"//; $part =~ s/"$//;
+- $part =~ s/\\(.)/$1/g;
+- }
+- push @parts, $part;
+- }
+- return $name, [@parts];
+- } else {
+- warn "Parse failure, line $. of $ARGV: $line\n";
+- return ();
+- }
+-}
+diff --git a/sbin/rt-validator b/sbin/rt-validator
+deleted file mode 100755
+index 84208a2..0000000
+--- a/sbin/rt-validator
++++ /dev/null
+@@ -1,1182 +0,0 @@
+-#!/usr/bin/perl
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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;
+-
+-# fix lib paths, some may be relative
+-BEGIN {
+- require File::Spec;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- }
+- else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-
+-}
+-
+-use Getopt::Long;
+-my %opt = ();
+-GetOptions(
+- \%opt,
+- 'check|c',
+- 'resolve',
+- 'force',
+- 'verbose|v',
+- 'help|h',
+-);
+-
+-if ( $opt{help} || !$opt{check} ) {
+- require Pod::Usage;
+- print Pod::Usage::pod2usage( { verbose => 2 } );
+- exit;
+-}
+-
+-usage_warning() if $opt{'resolve'} && !$opt{'force'};
+-
+-
+-sub usage_warning {
+- print <<END;
+-This utility can fix some issues with DB by creating or updating. In some
+-cases there is no enough data to resurect a missing record, but records which
+-refers to a missing can be deleted. It's up to you to decide what to do.
+-
+-In any case it's highly recommended to have a backup before resolving anything.
+-
+-Press enter to continue.
+-END
+-# Read a line of text, any line of text
+- <STDIN>;
+-}
+-
+-use RT;
+-RT::LoadConfig();
+-RT::Init();
+-
+-my $dbh = $RT::Handle->dbh;
+-my $db_type = RT->Config->Get('DatabaseType');
+-
+-my %TYPE = (
+- 'Transactions.Field' => 'text',
+- 'Transactions.OldValue' => 'text',
+- 'Transactions.NewValue' => 'text',
+-);
+-
+-my @models = qw(
+- ACE
+- Attachment
+- Attribute
+- CachedGroupMember
+- CustomField
+- CustomFieldValue
+- GroupMember
+- Group
+- Link
+- ObjectCustomField
+- ObjectCustomFieldValue
+- Principal
+- Queue
+- ScripAction
+- ScripCondition
+- Scrip
+- Template
+- Ticket
+- Transaction
+- User
+-);
+-
+-my %redo_on;
+-$redo_on{'Delete'} = {
+- ACL => [],
+-
+- Attributes => [],
+-
+- Links => [],
+-
+- CustomFields => [],
+- CustomFieldValues => [],
+- ObjectCustomFields => [],
+- ObjectCustomFieldValues => [],
+-
+- Queues => [],
+-
+- Scrips => [],
+- ScripActions => [],
+- ScripConditions => [],
+- Templates => [],
+-
+- Tickets => [ 'Tickets -> other', 'Tickets <-> Role Groups' ],
+- Transactions => [ 'Attachments -> other' ],
+-
+- Principals => ['User <-> ACL equivalence group', 'GMs -> Groups, Members' ],
+- Users => ['User <-> ACL equivalence group', 'GMs -> Groups, Members', 'Principals -> Users' ],
+- Groups => ['User <-> ACL equivalence group', 'GMs -> Groups, Members', 'CGM vs. GM', 'Principals -> Groups' ],
+-
+- GroupMembers => [ 'CGM vs. GM' ],
+- CachedGroupMembers => [ 'CGM vs. GM' ],
+-};
+-$redo_on{'Create'} = {
+- Principals => ['User <-> ACL equivalence group', 'GMs -> Groups, Members' ],
+- Groups => ['User <-> ACL equivalence group', 'GMs -> Groups, Members', 'CGM vs. GM' ],
+- GroupMembers => [ 'CGM vs. GM' ],
+- CachedGroupMembers => [ 'CGM vs. GM' ],
+-};
+-$redo_on{'Update'} = {
+- Groups => ['User Defined Group Name uniqueness'],
+-};
+-
+-my %describe_cb;
+-%describe_cb = (
+- Attachments => sub {
+- my $row = shift;
+- my $txn_id = $row->{transactionid};
+- my $res = 'Attachment #'. $row->{id} .' -> Txn #'. $txn_id;
+- return $res .', '. describe( 'Transactions', $txn_id );
+- },
+- Transactions => sub {
+- my $row = shift;
+- return 'Transaction #'. $row->{id} .' -> object '. $row->{objecttype} .' #'. $row->{objectid};
+- },
+-);
+-
+-{ my %cache = ();
+-sub m2t($) {
+- my $model = shift;
+- return $cache{$model} if $cache{$model};
+- my $class = "RT::$model";
+- my $object = $class->new( RT->SystemUser );
+- return $cache{$model} = $object->Table;
+-} }
+-
+-my (@do_check, %redo_check);
+-
+-my @CHECKS;
+-foreach my $table ( qw(Users Groups) ) {
+- push @CHECKS, "$table -> Principals" => sub {
+- my $msg = "A record in $table refers to a nonexistent record in Principals."
+- ." The script can either create the missing record in Principals"
+- ." or delete the record in $table.";
+- my ($type) = ($table =~ /^(.*)s$/);
+- check_integrity(
+- $table, 'id' => 'Principals', 'id',
+- join_condition => 't.PrincipalType = ?',
+- bind_values => [ $type ],
+- action => sub {
+- my $id = shift;
+- return unless my $a = prompt_action( ['Create', 'delete'], $msg );
+-
+- if ( $a eq 'd' ) {
+- delete_record( $table, $id );
+- }
+- elsif ( $a eq 'c' ) {
+- my $principal_id = create_record( 'Principals',
+- id => $id, PrincipalType => $type, ObjectId => $id, Disabled => 0
+- );
+- }
+- else {
+- die "Unknown action '$a'";
+- }
+- },
+- );
+- };
+-
+- push @CHECKS, "Principals -> $table" => sub {
+- my $msg = "A record in Principals refers to a nonexistent record in $table."
+- ." In some cases it's possible to manually resurrect such records,"
+- ." but this utility can only delete records.";
+-
+- check_integrity(
+- 'Principals', 'id' => $table, 'id',
+- condition => 's.PrincipalType = ?',
+- bind_values => [ $table =~ /^(.*)s$/ ],
+- action => sub {
+- my $id = shift;
+- return unless prompt( 'Delete', $msg );
+-
+- delete_record( 'Principals', $id );
+- },
+- );
+- };
+-}
+-
+-push @CHECKS, 'User <-> ACL equivalence group' => sub {
+- # from user to group
+- check_integrity(
+- 'Users', 'id' => 'Groups', 'Instance',
+- join_condition => 't.Domain = ? AND t.Type = ?',
+- bind_values => [ 'ACLEquivalence', 'UserEquiv' ],
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Create', "Found an user that has no ACL equivalence group."
+- );
+-
+- my $gid = create_record( 'Groups',
+- Domain => 'ACLEquivalence', Type => 'UserEquiv', Instance => $id,
+- );
+- },
+- );
+- # from group to user
+- check_integrity(
+- 'Groups', 'Instance' => 'Users', 'id',
+- condition => 's.Domain = ? AND s.Type = ?',
+- bind_values => [ 'ACLEquivalence', 'UserEquiv' ],
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete', "Found an user ACL equivalence group, but there is no user."
+- );
+-
+- delete_record( 'Groups', $id );
+- },
+- );
+- # one ACL equiv group for each user
+- check_uniqueness(
+- 'Groups',
+- columns => ['Instance'],
+- condition => '.Domain = ? AND .Type = ?',
+- bind_values => [ 'ACLEquivalence', 'UserEquiv' ],
+- );
+-};
+-
+-# check integrity of Queue role groups
+-push @CHECKS, 'Queues <-> Role Groups' => sub {
+- # XXX: we check only that there is at least one group for a queue
+- # from queue to group
+- check_integrity(
+- 'Queues', 'id' => 'Groups', 'Instance',
+- join_condition => 't.Domain = ?',
+- bind_values => [ 'RT::Queue-Role' ],
+- );
+- # from group to queue
+- check_integrity(
+- 'Groups', 'Instance' => 'Queues', 'id',
+- condition => 's.Domain = ?',
+- bind_values => [ 'RT::Queue-Role' ],
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete', "Found a role group of a nonexistent queue."
+- );
+-
+- delete_record( 'Groups', $id );
+- },
+- );
+-};
+-
+-# check integrity of Ticket role groups
+-push @CHECKS, 'Tickets <-> Role Groups' => sub {
+- # XXX: we check only that there is at least one group for a queue
+- # from queue to group
+- check_integrity(
+- 'Tickets', 'id' => 'Groups', 'Instance',
+- join_condition => 't.Domain = ?',
+- bind_values => [ 'RT::Ticket-Role' ],
+- );
+- # from group to ticket
+- check_integrity(
+- 'Groups', 'Instance' => 'Tickets', 'id',
+- condition => 's.Domain = ?',
+- bind_values => [ 'RT::Ticket-Role' ],
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete', "Found a role group of a nonexistent ticket."
+- );
+-
+- delete_record( 'Groups', $id );
+- },
+- );
+-};
+-
+-# additional CHECKS on groups
+-push @CHECKS, 'Role Groups (Instance, Type) uniqueness' => sub {
+- # Check that Domain, Instance and Type are unique
+- check_uniqueness(
+- 'Groups',
+- columns => ['Domain', 'Instance', 'Type'],
+- condition => '.Domain LIKE ?',
+- bind_values => [ '%-Role' ],
+- );
+-};
+-
+-push @CHECKS, 'System internal group uniqueness' => sub {
+- check_uniqueness(
+- 'Groups',
+- columns => ['Instance', 'Type'],
+- condition => '.Domain = ?',
+- bind_values => [ 'SystemInternal' ],
+- );
+-};
+-
+-# CHECK that user defined group names are unique
+-push @CHECKS, 'User Defined Group Name uniqueness' => sub {
+- check_uniqueness(
+- 'Groups',
+- columns => ['Name'],
+- condition => '.Domain = ?',
+- bind_values => [ 'UserDefined' ],
+- extra_tables => ['Principals sp', 'Principals tp'],
+- extra_condition => join(" and ", map { "$_.id = ${_}p.ObjectId and ${_}p.PrincipalType = ? and ${_}p.Disabled != 1" } qw(s t)),
+- extra_values => ['Group', 'Group'],
+- action => sub {
+- return unless prompt(
+- 'Rename', "Found a user defined group with a non-unique Name."
+- );
+-
+- my $id = shift;
+- my %cols = @_;
+- update_records('Groups', { id => $id }, { Name => join('-', $cols{'Name'}, $id) });
+- },
+- );
+-};
+-
+-push @CHECKS, 'GMs -> Groups, Members' => sub {
+- my $msg = "A record in GroupMembers references an object that doesn't exist."
+- ." Maybe you deleted a group or principal directly from the database?"
+- ." Usually it's OK to delete such records.";
+- check_integrity(
+- 'GroupMembers', 'GroupId' => 'Groups', 'id',
+- action => sub {
+- my $id = shift;
+- return unless prompt( 'Delete', $msg );
+-
+- delete_record( 'GroupMembers', $id );
+- },
+- );
+- check_integrity(
+- 'GroupMembers', 'MemberId' => 'Principals', 'id',
+- action => sub {
+- my $id = shift;
+- return unless prompt( 'Delete', $msg );
+-
+- delete_record( 'GroupMembers', $id );
+- },
+- );
+-};
+-
+-# CGM and GM
+-push @CHECKS, 'CGM vs. GM' => sub {
+- # all GM record should be duplicated in CGM
+- check_integrity(
+- GroupMembers => ['GroupId', 'MemberId'],
+- CachedGroupMembers => ['GroupId', 'MemberId'],
+- join_condition => 't.ImmediateParentId = t.GroupId AND t.Via = t.id',
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Create',
+- "Found a record in GroupMembers that has no direct duplicate in CachedGroupMembers table."
+- );
+-
+- my $gm = RT::GroupMember->new( RT->SystemUser );
+- $gm->Load( $id );
+- die "Couldn't load GM record #$id" unless $gm->id;
+- my $cgm = create_record( 'CachedGroupMembers',
+- GroupId => $gm->GroupId, MemberId => $gm->MemberId,
+- ImmediateParentId => $gm->GroupId, Via => undef,
+- Disabled => 0, # XXX: we should check integrity of Disabled field
+- );
+- update_records( "CachedGroupMembers", { id => $cgm }, { Via => $cgm } );
+- },
+- );
+- # all first level CGM records should have a GM record
+- check_integrity(
+- CachedGroupMembers => ['GroupId', 'MemberId'],
+- GroupMembers => ['GroupId', 'MemberId'],
+- condition => 's.ImmediateParentId = s.GroupId AND s.Via = s.id AND s.GroupId != s.MemberId',
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete',
+- "Found a record in CachedGroupMembers for a (Group, Member) pair"
+- ." that doesn't exist in the GroupMembers table."
+- );
+-
+- delete_record( 'CachedGroupMembers', $id );
+- },
+- );
+- # each group should have a CGM record where MemberId == GroupId
+- check_integrity(
+- Groups => ['id', 'id'],
+- CachedGroupMembers => ['GroupId', 'MemberId'],
+- join_condition => 't.ImmediateParentId = t.GroupId AND t.Via = t.id',
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Create',
+- "Found a record in Groups that has no direct"
+- ." duplicate in CachedGroupMembers table."
+- );
+-
+- my $g = RT::Group->new( RT->SystemUser );
+- $g->Load( $id );
+- die "Couldn't load group #$id" unless $g->id;
+- die "Loaded group by $id has id ". $g->id unless $g->id == $id;
+- my $cgm = create_record( 'CachedGroupMembers',
+- GroupId => $id, MemberId => $id,
+- ImmediateParentId => $id, Via => undef,
+- Disabled => $g->Disabled,
+- );
+- update_records( "CachedGroupMembers", { id => $cgm }, { Via => $cgm } );
+- },
+- );
+-
+- # and back, each record in CGM with MemberId == GroupId without exceptions
+- # should reference a group
+- check_integrity(
+- CachedGroupMembers => ['GroupId', 'MemberId'],
+- Groups => ['id', 'id'],
+- condition => "s.GroupId = s.MemberId",
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete',
+- "Found a record in CachedGroupMembers for a group that doesn't exist."
+- );
+-
+- delete_record( 'CachedGroupMembers', $id );
+- },
+- );
+- # Via
+- check_integrity(
+- CachedGroupMembers => 'Via',
+- CachedGroupMembers => 'id',
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete',
+- "Found a record in CachedGroupMembers with Via that references a nonexistent record."
+- );
+-
+- delete_record( 'CachedGroupMembers', $id );
+- },
+- );
+-
+- # for every CGM where ImmediateParentId != GroupId there should be
+- # matching parent record (first level)
+- check_integrity(
+- CachedGroupMembers => ['ImmediateParentId', 'MemberId'],
+- CachedGroupMembers => ['GroupId', 'MemberId'],
+- join_condition => 't.Via = t.id',
+- condition => 's.ImmediateParentId != s.GroupId',
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete',
+- "Found a record in CachedGroupMembers that references a nonexistent record in CachedGroupMembers table."
+- );
+-
+- delete_record( 'CachedGroupMembers', $id );
+- },
+- );
+-
+- # for every CGM where ImmediateParentId != GroupId there should be
+- # matching "grand" parent record
+- check_integrity(
+- CachedGroupMembers => ['GroupId', 'ImmediateParentId', 'Via'],
+- CachedGroupMembers => ['GroupId', 'MemberId', 'id'],
+- condition => 's.ImmediateParentId != s.GroupId',
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete',
+- "Found a record in CachedGroupMembers that references a nonexistent record in CachedGroupMembers table."
+- );
+-
+- delete_record( 'CachedGroupMembers', $id );
+- },
+- );
+-
+- # CHECK recursive records:
+- # if we have CGM1 (G1,M1,V1,IP1) then for every GM2(G2, M2), where G2 == M1,
+- # we should have CGM3 where G3 = G1, M3 = M2, V3 = ID1, IP3 = M1
+- {
+- my $query = <<END;
+-SELECT cgm1.GroupId, gm2.MemberId, cgm1.id AS Via,
+- cgm1.MemberId AS ImmediateParentId, cgm1.Disabled
+-FROM
+- CachedGroupMembers cgm1
+- CROSS JOIN GroupMembers gm2
+- LEFT JOIN CachedGroupMembers cgm3 ON (
+- cgm3.GroupId = cgm1.GroupId
+- AND cgm3.MemberId = gm2.MemberId
+- AND cgm3.Via = cgm1.id
+- AND cgm3.ImmediateParentId = cgm1.MemberId )
+-WHERE cgm1.GroupId != cgm1.MemberId
+-AND gm2.GroupId = cgm1.MemberId
+-AND cgm3.id IS NULL
+-END
+-
+- my $action = sub {
+- my %props = @_;
+- return unless prompt(
+- 'Create',
+- "Found records in CachedGroupMembers table without recursive duplicates."
+- );
+- my $cgm = create_record( 'CachedGroupMembers', %props );
+- };
+-
+- my $sth = execute_query( $query );
+- while ( my ($g, $m, $via, $ip, $dis) = $sth->fetchrow_array ) {
+- print STDERR "Principal #$m is member of #$ip when #$ip is member of #$g,";
+- print STDERR " but there is no cached GM record that $m is member of #$g.\n";
+- $action->(
+- GroupId => $g, MemberId => $m, Via => $via,
+- ImmediateParentId => $ip, Disabled => $dis,
+- );
+- }
+- }
+-};
+-
+-# Tickets
+-push @CHECKS, 'Tickets -> other' => sub {
+- check_integrity(
+- 'Tickets', 'EffectiveId' => 'Tickets', 'id',
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete',
+- "Found a ticket that's been merged into a ticket that no longer exists."
+- );
+-
+- delete_record( 'Tickets', $id );
+- },
+- );
+- check_integrity(
+- 'Tickets', 'Queue' => 'Queues', 'id',
+- );
+- check_integrity(
+- 'Tickets', 'Owner' => 'Users', 'id',
+- );
+- # XXX: check that owner is only member of owner role group
+-};
+-
+-
+-push @CHECKS, 'Transactions -> other' => sub {
+- foreach my $model ( @models ) {
+- check_integrity(
+- 'Transactions', 'ObjectId' => m2t($model), 'id',
+- condition => 's.ObjectType = ?',
+- bind_values => [ "RT::$model" ],
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete', "Found a transaction without object."
+- );
+-
+- delete_record( 'Transactions', $id );
+- },
+- );
+- }
+- # type = CustomField
+- check_integrity(
+- 'Transactions', 'Field' => 'CustomFields', 'id',
+- condition => 's.Type = ?',
+- bind_values => [ 'CustomField' ],
+- );
+- # type = Take, Untake, Force, Steal or Give
+- check_integrity(
+- 'Transactions', 'OldValue' => 'Users', 'id',
+- condition => 's.Type IN (?, ?, ?, ?, ?)',
+- bind_values => [ qw(Take Untake Force Steal Give) ],
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete', "Found a transaction regarding Owner changes,"
+- ." but the User with id stored in OldValue column doesn't exist anymore."
+- );
+-
+- delete_record( 'Transactions', $id );
+- },
+- );
+- check_integrity(
+- 'Transactions', 'NewValue' => 'Users', 'id',
+- condition => 's.Type IN (?, ?, ?, ?, ?)',
+- bind_values => [ qw(Take Untake Force Steal Give) ],
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete', "Found a transaction regarding Owner changes,"
+- ." but the User with id stored in NewValue column doesn't exist anymore."
+- );
+-
+- delete_record( 'Transactions', $id );
+- },
+- );
+- # type = DelWatcher
+- check_integrity(
+- 'Transactions', 'OldValue' => 'Principals', 'id',
+- condition => 's.Type = ?',
+- bind_values => [ 'DelWatcher' ],
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete', "Found a transaction describing watcher changes,"
+- ." but the User with id stored in OldValue column doesn't exist anymore."
+- );
+-
+- delete_record( 'Transactions', $id );
+- },
+- );
+- # type = AddWatcher
+- check_integrity(
+- 'Transactions', 'NewValue' => 'Principals', 'id',
+- condition => 's.Type = ?',
+- bind_values => [ 'AddWatcher' ],
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete', "Found a transaction describing watcher changes,"
+- ." but the User with id stored in NewValue column doesn't exist anymore."
+- );
+-
+- delete_record( 'Transactions', $id );
+- },
+- );
+-
+-# XXX: Links need more love, uri is stored instead of id
+-# # type = DeleteLink
+-# check_integrity(
+-# 'Transactions', 'OldValue' => 'Links', 'id',
+-# condition => 's.Type = ?',
+-# bind_values => [ 'DeleteLink' ],
+-# );
+-# # type = AddLink
+-# check_integrity(
+-# 'Transactions', 'NewValue' => 'Links', 'id',
+-# condition => 's.Type = ?',
+-# bind_values => [ 'AddLink' ],
+-# );
+-
+- # type = Set, Field = Queue
+- check_integrity(
+- 'Transactions', 'NewValue' => 'Queues', 'id',
+- condition => 's.Type = ? AND s.Field = ?',
+- bind_values => [ 'Set', 'Queue' ],
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete', "Found a transaction describing a queue change,"
+- ." but the Queue with id stored in the NewValue column doesn't exist anymore."
+- );
+-
+- delete_record( 'Transactions', $id );
+- },
+- );
+- check_integrity(
+- 'Transactions', 'OldValue' => 'Queues', 'id',
+- condition => 's.Type = ? AND s.Field = ?',
+- bind_values => [ 'Set', 'Queue' ],
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete', "Found a transaction describing a queue change,"
+- ." but the Queue with id stored in the OldValue column doesn't exist anymore."
+- );
+-
+- delete_record( 'Transactions', $id );
+- },
+- );
+- # Reminders
+- check_integrity(
+- 'Transactions', 'NewValue' => 'Tickets', 'id',
+- join_condition => 't.Type = ?',
+- condition => 's.Type IN (?, ?, ?)',
+- bind_values => [ 'reminder', 'AddReminder', 'OpenReminder', 'ResolveReminder' ],
+- );
+-};
+-
+-# Attachments
+-push @CHECKS, 'Attachments -> other' => sub {
+- check_integrity(
+- Attachments => 'TransactionId', Transactions => 'id',
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete', "Found an attachment without a transaction."
+- );
+- delete_record( 'Attachments', $id );
+- },
+- );
+- check_integrity(
+- Attachments => 'Parent', Attachments => 'id',
+- action => sub {
+- my $id = shift;
+- return unless prompt(
+- 'Delete', "Found an sub-attachment without its parent attachment."
+- );
+- delete_record( 'Attachments', $id );
+- },
+- );
+- check_integrity(
+- Attachments => 'Parent',
+- Attachments => 'id',
+- join_condition => 's.TransactionId = t.TransactionId',
+- );
+-};
+-
+-push @CHECKS, 'CustomFields and friends' => sub {
+- #XXX: ObjectCustomFields needs more love
+- check_integrity(
+- 'CustomFieldValues', 'CustomField' => 'CustomFields', 'id',
+- );
+- check_integrity(
+- 'ObjectCustomFieldValues', 'CustomField' => 'CustomFields', 'id',
+- );
+- foreach my $model ( @models ) {
+- check_integrity(
+- 'ObjectCustomFieldValues', 'ObjectId' => m2t($model), 'id',
+- condition => 's.ObjectType = ?',
+- bind_values => [ "RT::$model" ],
+- );
+- }
+-};
+-
+-push @CHECKS, Templates => sub {
+- check_integrity(
+- 'Templates', 'Queue' => 'Queues', 'id',
+- );
+-};
+-
+-push @CHECKS, Scrips => sub {
+- check_integrity(
+- 'Scrips', 'Queue' => 'Queues', 'id',
+- );
+- check_integrity(
+- 'Scrips', 'ScripCondition' => 'ScripConditions', 'id',
+- );
+- check_integrity(
+- 'Scrips', 'ScripAction' => 'ScripActions', 'id',
+- );
+- check_integrity(
+- 'Scrips', 'Template' => 'Templates', 'id',
+- );
+-};
+-
+-push @CHECKS, Attributes => sub {
+- foreach my $model ( @models ) {
+- check_integrity(
+- 'Attributes', 'ObjectId' => m2t($model), 'id',
+- condition => 's.ObjectType = ?',
+- bind_values => [ "RT::$model" ],
+- );
+- }
+-};
+-
+-# Fix situations when Creator or LastUpdatedBy references ACL equivalence
+-# group of a user instead of user
+-push @CHECKS, 'FIX: LastUpdatedBy and Creator' => sub {
+- my %fix = ();
+- foreach my $model ( @models ) {
+- my $class = "RT::$model";
+- my $object = $class->new( RT->SystemUser );
+- foreach my $column ( qw(LastUpdatedBy Creator) ) {
+- next unless $object->_Accessible( $column, 'auto' );
+-
+- my $table = m2t($model);
+- my $query = <<END;
+-SELECT m.id, g.id, g.Instance
+-FROM
+- Groups g JOIN $table m ON g.id = m.$column
+-WHERE
+- g.Domain = ?
+- AND g.Type = ?
+-END
+- my $action = sub {
+- my ($gid, $uid) = @_;
+- return unless prompt(
+- 'Update',
+- "Looks like there were a bug in old versions of RT back in 2006\n"
+- ."that has been fixed. If other checks are ok then it's ok to update\n"
+- ."these records to point them to users instead of groups"
+- );
+- $fix{ $table }{ $column }{ $gid } = $uid;
+- };
+-
+- my $sth = execute_query( $query, 'ACLEquivalence', 'UserEquiv' );
+- while ( my ($rid, $gid, $uid) = $sth->fetchrow_array ) {
+- print STDERR "Record #$rid in $table refers to ACL equivalence group #$gid of user #$uid";
+- print STDERR " when must reference user.\n";
+- $action->( $gid, $uid );
+- if ( keys( %fix ) > 1000 ) {
+- $sth->finish;
+- last;
+- }
+- }
+- }
+- }
+-
+- if ( keys %fix ) {
+- foreach my $table ( keys %fix ) {
+- foreach my $column ( keys %{ $fix{ $table } } ) {
+- my $query = "UPDATE $table SET $column = ? WHERE $column = ?";
+- while ( my ($gid, $uid) = each %{ $fix{ $table }{ $column } } ) {
+- update_records( $table, { $column => $gid }, { $column => $uid } );
+- }
+- }
+- }
+- $redo_check{'FIX: LastUpdatedBy and Creator'} = 1;
+- }
+-};
+-
+-push @CHECKS, 'LastUpdatedBy and Creator' => sub {
+- foreach my $model ( @models ) {
+- my $class = "RT::$model";
+- my $object = $class->new( RT->SystemUser );
+- my $table = $object->Table;
+- foreach my $column ( qw(LastUpdatedBy Creator) ) {
+- next unless $object->_Accessible( $column, 'auto' );
+- check_integrity(
+- $table, $column => 'Users', 'id',
+- action => sub {
+- my ($id, %prop) = @_;
+- return unless my $replace_with = prompt_integer(
+- 'Replace',
+- "Column $column should point to a user, but there is record #$id in table $table\n"
+- ."where it's not true. It's ok to replace these wrong references with id of any user.\n"
+- ."Note that id you enter is not checked. You can pick any user from your DB, but it's\n"
+- ."may be better to create a special user for this, for example 'user_that_has_been_deleted'\n"
+- ."or something like that.",
+- "$table.$column -> user #$prop{$column}"
+- );
+- update_records( $table, { id => $id, $column => $prop{$column} }, { $column => $replace_with } );
+- },
+- );
+- }
+- }
+-};
+-my %CHECKS = @CHECKS;
+-
+- at do_check = do { my $i = 1; grep $i++%2, @CHECKS };
+-
+-while ( my $check = shift @do_check ) {
+- $CHECKS{ $check }->();
+-
+- foreach my $redo ( keys %redo_check ) {
+- die "check $redo doesn't exist" unless $CHECKS{ $redo };
+- delete $redo_check{ $redo };
+- next if grep $_ eq $redo, @do_check; # don't do twice
+- push @do_check, $redo;
+- }
+-}
+-
+-sub check_integrity {
+- my ($stable, @scols) = (shift, shift);
+- my ($ttable, @tcols) = (shift, shift);
+- my %args = @_;
+-
+- @scols = @{ $scols[0] } if ref $scols[0];
+- @tcols = @{ $tcols[0] } if ref $tcols[0];
+-
+- print "Checking integrity of $stable.{". join(', ', @scols) ."} => $ttable.{". join(', ', @tcols) ."}\n"
+- if $opt{'verbose'};
+-
+- my $query = "SELECT s.id, ". join(', ', map "s.$_", @scols)
+- ." FROM $stable s LEFT JOIN $ttable t"
+- ." ON (". join(
+- ' AND ', map columns_eq_cond('s', $stable, $scols[$_] => 't', $ttable, $tcols[$_]), (0..(@scols-1))
+- ) .")"
+- . ($args{'join_condition'}? " AND ( $args{'join_condition'} )": "")
+- ." WHERE t.id IS NULL"
+- ." AND ". join(' AND ', map "s.$_ IS NOT NULL", @scols);
+-
+- $query .= " AND ( $args{'condition'} )" if $args{'condition'};
+-
+- my @binds = @{ $args{'bind_values'} || [] };
+- if ( $tcols[0] eq 'id' && @tcols == 1 ) {
+- my $type = $TYPE{"$stable.$scols[0]"} || 'number';
+- if ( $type eq 'number' ) {
+- $query .= " AND s.$scols[0] != ?"
+- }
+- elsif ( $type eq 'text' ) {
+- $query .= " AND s.$scols[0] NOT LIKE ?"
+- }
+- push @binds, 0;
+- }
+-
+- my $sth = execute_query( $query, @binds );
+- while ( my ($sid, @set) = $sth->fetchrow_array ) {
+- print STDERR "Record #$sid in $stable references a nonexistent record in $ttable\n";
+- for ( my $i = 0; $i < @scols; $i++ ) {
+- print STDERR "\t$scols[$i] => '$set[$i]' => $tcols[$i]\n";
+- }
+- print STDERR "\t". describe( $stable, $sid ) ."\n";
+- $args{'action'}->( $sid, map { $scols[$_] => $set[$_] } (0 .. (@scols-1)) ) if $args{'action'};
+- }
+-}
+-
+-sub describe {
+- my ($table, $id) = @_;
+- return '' unless my $cb = $describe_cb{ $table };
+-
+- my $row = load_record( $table, $id );
+- unless ( $row->{id} ) {
+- $table =~ s/s$//;
+- return "$table doesn't exist";
+- }
+- return $cb->( $row );
+-}
+-
+-sub columns_eq_cond {
+- my ($la, $lt, $lc, $ra, $rt, $rc) = @_;
+- my $ltype = $TYPE{"$lt.$lc"} || 'number';
+- my $rtype = $TYPE{"$rt.$rc"} || 'number';
+- return "$la.$lc = $ra.$rc" if $db_type ne 'Pg' || $ltype eq $rtype;
+-
+- if ( $rtype eq 'text' ) {
+- return "$ra.$rc LIKE CAST($la.$lc AS text)";
+- }
+- elsif ( $ltype eq 'text' ) {
+- return "$la.$lc LIKE CAST($ra.$rc AS text)";
+- }
+- else { die "don't know how to cast" }
+-}
+-
+-sub check_uniqueness {
+- my $on = shift;
+- my %args = @_;
+-
+- my @columns = @{ $args{'columns'} };
+-
+- print "Checking uniqueness of ( ", join(', ', map "'$_'", @columns )," ) in table '$on'\n"
+- if $opt{'verbose'};
+-
+- my ($scond, $tcond);
+- if ( $scond = $tcond = $args{'condition'} ) {
+- $scond =~ s/(\s|^)\./$1s./g;
+- $tcond =~ s/(\s|^)\./$1t./g;
+- }
+-
+- my $query = "SELECT s.id, t.id, ". join(', ', map "s.$_", @columns)
+- ." FROM $on s LEFT JOIN $on t "
+- ." ON s.id != t.id AND ". join(' AND ', map "s.$_ = t.$_", @columns)
+- . ($tcond? " AND ( $tcond )": "")
+- . ($args{'extra_tables'} ? join(", ", "", @{$args{'extra_tables'}}) : "")
+- ." WHERE t.id IS NOT NULL "
+- ." AND ". join(' AND ', map "s.$_ IS NOT NULL", @columns);
+- $query .= " AND ( $scond )" if $scond;
+- $query .= " AND ( $args{'extra_condition'} )" if $args{'extra_condition'};
+-
+- my $sth = execute_query(
+- $query,
+- $args{'bind_values'}? (@{ $args{'bind_values'} }, @{ $args{'bind_values'} }): (),
+- $args{'extra_values'}? (@{ $args{'extra_values'} }): ()
+- );
+- while ( my ($sid, $tid, @set) = $sth->fetchrow_array ) {
+- print STDERR "Record #$tid in $on has the same set of values as $sid\n";
+- for ( my $i = 0; $i < @columns; $i++ ) {
+- print STDERR "\t$columns[$i] => '$set[$i]'\n";
+- }
+- $args{'action'}->( $tid, map { $columns[$_] => $set[$_] } (0 .. (@columns-1)) ) if $args{'action'};
+- }
+-}
+-
+-sub load_record {
+- my ($table, $id) = @_;
+- my $sth = execute_query( "SELECT * FROM $table WHERE id = ?", $id );
+- return $sth->fetchrow_hashref('NAME_lc');
+-}
+-
+-sub delete_record {
+- my ($table, $id) = (@_);
+- print "Deleting record #$id in $table\n" if $opt{'verbose'};
+- my $query = "DELETE FROM $table WHERE id = ?";
+- $redo_check{ $_ } = 1 foreach @{ $redo_on{'Delete'}{ $table } || [] };
+- return execute_query( $query, $id );
+-}
+-
+-sub create_record {
+- print "Creating a record in $_[0]\n" if $opt{'verbose'};
+- $redo_check{ $_ } = 1 foreach @{ $redo_on{'Create'}{ $_[0] } || [] };
+- return $RT::Handle->Insert( @_ );
+-}
+-
+-sub update_records {
+- my $table = shift;
+- my $where = shift;
+- my $what = shift;
+-
+- my (@where_cols, @where_binds);
+- while ( my ($k, $v) = each %$where ) { push @where_cols, $k; push @where_binds, $v; }
+-
+- my (@what_cols, @what_binds);
+- while ( my ($k, $v) = each %$what ) { push @what_cols, $k; push @what_binds, $v; }
+-
+- print "Updating record(s) in $table\n" if $opt{'verbose'};
+- my $query = "UPDATE $table SET ". join(', ', map "$_ = ?", @what_cols)
+- ." WHERE ". join(' AND ', map "$_ = ?", @where_cols);
+- $redo_check{ $_ } = 1 foreach @{ $redo_on{'Update'}{ $table } || [] };
+- return execute_query( $query, @what_binds, @where_binds );
+-}
+-
+-sub execute_query {
+- my ($query, @binds) = @_;
+-
+- print "Executing query: $query\n\n" if $opt{'verbose'};
+-
+- my $sth = $dbh->prepare( $query ) or die "couldn't prepare $query\n\tError: ". $dbh->errstr;
+- $sth->execute( @binds ) or die "couldn't execute $query\n\tError: ". $sth->errstr;
+- return $sth;
+-}
+-
+-{ my %cached_answer;
+-sub prompt {
+- my $action = shift;
+- my $msg = shift;
+- my $token = shift || join ':', caller;
+-
+- return 0 unless $opt{'resolve'};
+- return 1 if $opt{'force'};
+-
+- return $cached_answer{ $token } if exists $cached_answer{ $token };
+-
+- print $msg, "\n";
+- print "$action ALL records with the same defect? [N]: ";
+- my $a = <STDIN>;
+- return $cached_answer{ $token } = 1 if $a =~ /^(y|yes)$/i;
+- return $cached_answer{ $token } = 0;
+-} }
+-
+-{ my %cached_answer;
+-sub prompt_action {
+- my $actions = shift;
+- my $msg = shift;
+- my $token = shift || join ':', caller;
+-
+- return '' unless $opt{'resolve'};
+- return lc substr $actions->[0], 0, 1 if $opt{'force'};
+- return $cached_answer{ $token } if exists $cached_answer{ $token };
+-
+- print $msg, "\n";
+- print join( ' or ', @$actions ) ." ALL records with the same defect? [do nothing]: ";
+- my $a = <STDIN>;
+- chomp $a;
+- return $cached_answer{ $token } = '' unless $a;
+- foreach ( grep rindex(lc $_, lc $a, 0) == 0, @$actions ) {
+- return $cached_answer{ $token } = lc substr $a, 0, 1;
+- }
+- return $cached_answer{ $token } = '';
+-} }
+-
+-{ my %cached_answer;
+-sub prompt_integer {
+- my $action = shift;
+- my $msg = shift;
+- my $token = shift || join ':', caller;
+-
+- return 0 unless $opt{'resolve'};
+- return 0 if $opt{'force'};
+-
+- return $cached_answer{ $token } if exists $cached_answer{ $token };
+-
+- print $msg, "\n";
+- print "$action ALL records with the same defect? [0]: ";
+- my $a = <STDIN>; chomp $a; $a = int($a);
+- return $cached_answer{ $token } = $a;
+-} }
+-
+-1;
+-
+-__END__
+-
+-=head1 NAME
+-
+-rt-validator - check and correct validity of records in RT's database
+-
+-=head1 SYNOPSIS
+-
+- rt-validator --check
+- rt-validator --check --verbose
+- rt-validator --check --verbose --resolve
+- rt-validator --check --verbose --resolve --force
+-
+-=head1 DESCRIPTION
+-
+-This script checks integrity of records in RT's DB. May delete some invalid
+-records or ressurect accidentally deleted.
+-
+-=head1 OPTIONS
+-
+-=over
+-
+-=item check
+-
+- mandatory.
+-
+- it's equal to -c
+-
+-=item verbose
+-
+- print additional info to STDOUT
+- it's equal to -v
+-
+-=item resolve
+-
+- enable resolver that can delete or create some records
+-
+-=item force
+-
+- resolve without asking questions
+-
+-=back
+-
+diff --git a/sbin/standalone_httpd b/sbin/standalone_httpd
+deleted file mode 100755
+index add9d01..0000000
+--- a/sbin/standalone_httpd
++++ /dev/null
+@@ -1,285 +0,0 @@
+-#!/usr/bin/perl -w
+-# BEGIN BPS TAGGED BLOCK {{{
+-#
+-# COPYRIGHT:
+-#
+-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+-# <sales at bestpractical.com>
+-#
+-# (Except where explicitly superseded by other copyright notices)
+-#
+-#
+-# LICENSE:
+-#
+-# This work is made available to you under the terms of Version 2 of
+-# the GNU General Public License. A copy of that license should have
+-# been provided with this software, but in any event can be snarfed
+-# from www.gnu.org.
+-#
+-# This work is distributed in the hope that it will be useful, but
+-# WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-# General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301 or visit their web page on the internet at
+-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-#
+-#
+-# CONTRIBUTION SUBMISSION POLICY:
+-#
+-# (The following paragraph is not intended to limit the rights granted
+-# to you to modify and distribute this software under the terms of
+-# the GNU General Public License and is only of importance to you if
+-# you choose to contribute your changes and enhancements to the
+-# community by submitting them to Best Practical Solutions, LLC.)
+-#
+-# By intentionally submitting any modifications, corrections or
+-# derivatives to this work, or any other work intended for use with
+-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-# you are the copyright holder for those contributions and you grant
+-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-# royalty-free, perpetual, license 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;
+-
+-# fix lib paths, some may be relative
+-BEGIN {
+- die <<EOT if ${^TAINT};
+-RT does not run under Perl's "taint mode". Remove -T from the command
+-line, or remove the PerlTaintCheck parameter from your mod_perl
+-configuration.
+-EOT
+-
+- require File::Spec;
+- my @libs = ("lib", "local/lib");
+- my $bin_path;
+-
+- for my $lib (@libs) {
+- unless ( File::Spec->file_name_is_absolute($lib) ) {
+- unless ($bin_path) {
+- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+- }
+- else {
+- require FindBin;
+- no warnings "once";
+- $bin_path = $FindBin::Bin;
+- }
+- }
+- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+- }
+- unshift @INC, $lib;
+- }
+-
+-}
+-
+-use Getopt::Long;
+-no warnings 'once';
+-
+-if (grep { m/help/ } @ARGV) {
+- require Pod::Usage;
+- print Pod::Usage::pod2usage( { verbose => 2 } );
+- exit;
+-}
+-
+-require RT;
+-RT->LoadConfig();
+-RT->InitPluginPaths();
+-RT->InitLogging();
+-require Module::Refresh if RT->Config->Get('DevelMode');
+-
+-require RT::Handle;
+-my ($integrity, $state, $msg) = RT::Handle->CheckIntegrity;
+-
+-unless ( $integrity ) {
+- print STDERR <<EOF;
+-
+-RT couldn't connect to the database where tickets are stored.
+-If this is a new installation of RT, you should visit the URL below
+-to configure RT and initialize your database.
+-
+-If this is an existing RT installation, this may indicate a database
+-connectivity problem.
+-
+-The error RT got back when trying to connect to your database was:
+-
+-$msg
+-
+-EOF
+-
+- require RT::Installer;
+- # don't enter install mode if the file exists but is unwritable
+- if (-e RT::Installer->ConfigFile && !-w _) {
+- die 'Since your configuration exists ('
+- . RT::Installer->ConfigFile
+- . ") but is not writable, I'm refusing to do anything.\n";
+- }
+-
+- RT->Config->Set( 'LexiconLanguages' => '*' );
+- RT::I18N->Init;
+-
+- RT->InstallMode(1);
+-} else {
+- RT->Init( Heavy => 1 );
+-
+- my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'post');
+- unless ( $status ) {
+- print STDERR $msg, "\n\n";
+- exit -1;
+- }
+-}
+-
+-# we must disconnect DB before fork
+-if ($RT::Handle) {
+- $RT::Handle->dbh->disconnect if $RT::Handle->dbh;
+- $RT::Handle->dbh(undef);
+- undef $RT::Handle;
+-}
+-
+-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);
+-}
+-
+-# when used as a psgi file
+-if (caller) {
+- return $app;
+-}
+-
+-
+-# load appropriate server
+-
+-require Plack::Runner;
+-
+-my $is_fastcgi = $0 =~ m/fcgi$/;
+-my $r = Plack::Runner->new( $0 =~ /standalone/ ? ( server => 'Standalone' ) :
+- $is_fastcgi ? ( server => 'FCGI' )
+- : (),
+- env => 'deployment' );
+-
+-# figure out the port
+-my $port;
+-
+-# handle "rt-server 8888" for back-compat, but complain about it
+-if ($ARGV[0] && $ARGV[0] =~ m/^\d+$/) {
+- warn "Deprecated: please run $0 --port $ARGV[0] instead\n";
+- unshift @ARGV, '--port';
+-}
+-
+-my @args = @ARGV;
+-
+-use List::MoreUtils 'last_index';
+-my $last_index = last_index { $_ eq '--port' } @args;
+-
+-my $explicit_port;
+-
+-if ( $last_index != -1 && $args[$last_index+1] =~ /^\d+$/ ) {
+- $explicit_port = $args[$last_index+1];
+- $port = $explicit_port;
+-
+- # inform the rest of the system what port we manually chose
+- my $old_app = $app;
+- $app = sub {
+- my $env = shift;
+-
+- $env->{'rt.explicit_port'} = $port;
+-
+- $old_app->($env, @_);
+- };
+-}
+-else {
+- # default to the configured WebPort and inform Plack::Runner
+- $port = RT->Config->Get('WebPort') || '8080';
+- push @args, '--port', $port;
+-}
+-
+-push @args, '--server', 'Standalone' if RT->InstallMode;
+-push @args, '--server', 'Starlet' unless $r->{server} || grep { m/--server/ } @args;
+-
+-$r->parse_options(@args);
+-
+-delete $r->{options} if $is_fastcgi; ### mangle_host_port_socket ruins everything
+-
+-unless ($r->{env} eq 'development') {
+- push @{$r->{options}}, server_ready => sub {
+- my($args) = @_;
+- my $name = $args->{server_software} || ref($args); # $args is $server
+- my $host = $args->{host} || 0;
+- my $proto = $args->{proto} || 'http';
+- print STDERR "$name: Accepting connections at $proto://$host:$args->{port}/\n";
+- };
+-}
+-eval { $r->run($app) };
+-if (my $err = $@) {
+- handle_startup_error($err);
+-}
+-
+-exit 0;
+-
+-sub handle_startup_error {
+- my $err = shift;
+- if ( $err =~ /listen/ ) {
+- handle_bind_error();
+- } else {
+- die
+- "Something went wrong while trying to run RT's standalone web server:\n\t"
+- . $err;
+- }
+-}
+-
+-
+-sub handle_bind_error {
+-
+- print STDERR <<EOF;
+-WARNING: RT couldn't start up a web server on port @{[$port]}.
+-This is often the case if the port is already in use or you're running @{[$0]}
+-as someone other than your system's "root" user. You may also specify a
+-temporary port with: $0 --port <port>
+-EOF
+-
+- if ($explicit_port) {
+- print STDERR
+- "Please check your system configuration or choose another port\n\n";
+- }
+-}
+-
+-__END__
+-
+-=head1 NAME
+-
+-rt-server - RT standalone server
+-
+-=head1 SYNOPSIS
+-
+- # runs prefork server listening on port 8080, requires Starlet
+- rt-server --port 8080
+-
+- # runs server listening on port 8080
+- rt-server --server Standalone --port 8080
+- # or
+- standalone_httpd --port 8080
+-
+- # runs other PSGI server on port 8080
+- rt-server --server Starman --port 8080
+diff --git a/t/data/configs/apache2.2+fastcgi.conf b/t/data/configs/apache2.2+fastcgi.conf
+deleted file mode 100644
+index cc1e4fe..0000000
+--- a/t/data/configs/apache2.2+fastcgi.conf
++++ /dev/null
+@@ -1,50 +0,0 @@
+-ServerRoot %%SERVER_ROOT%%
+-PidFile %%PID_FILE%%
+-LockFile %%LOCK_FILE%%
+-ServerAdmin root at localhost
+-
+-%%LOAD_MODULES%%
+-
+-<IfModule !mpm_netware_module>
+-<IfModule !mpm_winnt_module>
+-User www-data
+-Group www-data
+-</IfModule>
+-</IfModule>
+-
+-ServerName localhost
+-Listen %%LISTEN%%
+-
+-ErrorLog "%%LOG_FILE%%"
+-LogLevel debug
+-
+-<Directory />
+- Options FollowSymLinks
+- AllowOverride None
+- Order deny,allow
+- Deny from all
+-</Directory>
+-
+-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
+-
+-Alias /NoAuth/images/ %%DOCUMENT_ROOT%%/NoAuth/images/
+-ScriptAlias / %%RT_SBIN_PATH%%/rt-server.fcgi/
+-
+-DocumentRoot "%%DOCUMENT_ROOT%%"
+-<Location />
+- Order allow,deny
+- Allow from all
+-
+-%%BASIC_AUTH%%
+-
+- Options +ExecCGI
+- AddHandler fastcgi-script fcgi
+-</Location>
+-
+diff --git a/t/data/configs/apache2.2+mod_perl.conf b/t/data/configs/apache2.2+mod_perl.conf
+deleted file mode 100644
+index e0ce14c..0000000
+--- a/t/data/configs/apache2.2+mod_perl.conf
++++ /dev/null
+@@ -1,67 +0,0 @@
+-<IfModule mpm_prefork_module>
+- StartServers 1
+- MinSpareServers 1
+- MaxSpareServers 1
+- MaxClients 1
+- MaxRequestsPerChild 0
+-</IfModule>
+-
+-<IfModule mpm_worker_module>
+- StartServers 1
+- MinSpareThreads 1
+- MaxSpareThreads 1
+- ThreadLimit 1
+- ThreadsPerChild 1
+- MaxClients 1
+- MaxRequestsPerChild 0
+-</IfModule>
+-
+-ServerRoot %%SERVER_ROOT%%
+-PidFile %%PID_FILE%%
+-LockFile %%LOCK_FILE%%
+-ServerAdmin root at localhost
+-
+-%%LOAD_MODULES%%
+-
+-<IfModule !mpm_netware_module>
+-<IfModule !mpm_winnt_module>
+-User www-data
+-Group www-data
+-</IfModule>
+-</IfModule>
+-
+-ServerName localhost
+-Listen %%LISTEN%%
+-
+-ErrorLog "%%LOG_FILE%%"
+-LogLevel debug
+-
+-<Directory />
+- Options FollowSymLinks
+- AllowOverride None
+- Order deny,allow
+- Deny from all
+-</Directory>
+-
+-AddDefaultCharset UTF-8
+-PerlSetEnv RT_SITE_CONFIG %%RT_SITE_CONFIG%%
+-
+-DocumentRoot "%%DOCUMENT_ROOT%%"
+-<Location />
+- Order allow,deny
+- Allow from all
+-
+-%%BASIC_AUTH%%
+-
+- SetHandler modperl
+-
+- PerlResponseHandler Plack::Handler::Apache2
+- PerlSetVar psgi_app %%RT_SBIN_PATH%%/rt-server
+-</Location>
+-
+-<Perl>
+- $ENV{RT_TESTING}=1;
+- use Plack::Handler::Apache2;
+- Plack::Handler::Apache2->preload("%%RT_SBIN_PATH%%/rt-server");
+-</Perl>
+-
+--
+1.9.3
+
diff --git a/0007-Adjust-path-to-html-autohandler.patch b/0007-Adjust-path-to-html-autohandler.patch
new file mode 100644
index 0000000..7d332bd
--- /dev/null
+++ b/0007-Adjust-path-to-html-autohandler.patch
@@ -0,0 +1,24 @@
+From 775af7e4827e723ba8b4ccc210dc0eaa876ab797 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ralf=20Cors=C3=A9pius?= <corsepiu at fedoraproject.org>
+Date: Fri, 23 Jan 2015 04:38:35 +0100
+Subject: [PATCH 7/7] Adjust path to html/autohandler.
+
+---
+ t/web/query_log.t | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/t/web/query_log.t b/t/web/query_log.t
+index 89cca2d..9dd14e1 100644
+--- a/t/web/query_log.t
++++ b/t/web/query_log.t
+@@ -14,6 +14,6 @@ $root->LoadByEmail('root at 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{share/html/autohandler:\d+}, "stack trace includes mason components");
++$m->text_like(qr{share/rt/html/autohandler:\d+}, "stack trace includes mason components");
+ $m->text_contains("SELECT * FROM Principals WHERE id = '".$root->id."'", "we interpolate bind params");
+
+--
+2.1.0
+
diff --git a/README.fedora.in b/README.fedora.in
new file mode 100644
index 0000000..ae19e13
--- /dev/null
+++ b/README.fedora.in
@@ -0,0 +1,68 @@
+Some random notes on rt on Fedora:
+
+
+Finalizing the installation
+===========================
+
+The default configuration is set up for a mysql based rt system.
+It requires additional steps to be performed by the sysadmin to be
+fully functional.
+
+If you have never set up rt before, proceed as follows:
+1. Log-in to the server as root
+
+2. Install a mysql server:
+# yum install mysql-server
+
+3. Startup the mysql server:
+# systemctl start mysqld.service
+
+4. Set up the mysql server.
+If not already done, at minimum, you will want to set up a mysql root
+password:
+# mysqladmin -u root password <mysql-root-password>
+
+5. Edit /etc/rt/RT_SiteConfig.pm to meet your demands.
+You will want to compare RT_SiteConfig.pm against RT_Config.pm and
+add customized versions of those settings from RT_Config.pm to
+RT_SiteConfig.pm.
+
+In particular, you probably will want to add customized versions of
+those setting containing "example.com" to your RT_SiteConfig.pm.
+
+6. Initialize rt's mysql database:
+# /usr/sbin/rt-setup-database --action init \
+ --dba root \
+ --dba-password <mysql-root-password>
+
+7. Start/restart apache/httpd:
+# systemctl start httpd.service
+rsp.
+# systemctl restart httpd.service
+
+8. Try to log in:
+At this point, you should be able to login into rt on your rt-host via
+its web interface as rt-administrator (root), from an arbitrary host with
+http:-access to your rt-host:
+e.g.:
+<your-favorite-browser> http://<rt-host>/rt
+
+You should be greeted with a login window. Login with
+Username: root, Password: password.
+
+The next step should be to change your rt's "root" account's password,
+otherwise anybody with web access to your site will be able to abuse
+your site.
+
+Afterwards, you should be ready to create user accounts, configure
+the mail-interface etc.
+
+
+SELinux:
+Make sure /var/cache/rt has this SELinux context:
+# ls -lZ /var/cache/rt
+drwxr-xr-x. root root system_u:object_r:httpd_cache_t:s0 /var/cache/rt
+
+If not, perform the following to add the required file context:
+# semanage fcontext -a -t httpd_cache_t '/var/cache/rt(/.*)?'
+# restorecon -R -v /var/cache/rt
diff --git a/README.tests b/README.tests
new file mode 100644
index 0000000..2747098
--- /dev/null
+++ b/README.tests
@@ -0,0 +1,12 @@
+Run these tests with a command similar to this:
+
+RT_DBA_USER=root RT_DBA_PASSWORD=<mysql-passwd> /usr/bin/prove t/*.t t/*/*.t
+
+Notes:
+- Most (all?) tests require to be run as "root".
+- Some of these tests were not designed to work outside of rt's source
+ tree and do not work when being used as a separate test package.
+- Some of these tests are quite "chatty".
+- Failing tests may leave stray files on the filesystem, which will not be
+ automatically deinstalled upon package deinstallation.
+- Some tests fail for (yet) undetermined reasons.
diff --git a/rt.conf.in b/rt.conf.in
new file mode 100644
index 0000000..e746061
--- /dev/null
+++ b/rt.conf.in
@@ -0,0 +1,20 @@
+<Directory "@RT_WWWDIR@">
+ Options FollowSymLinks
+ <IfModule mod_authz_core.c>
+ # Apache 2.4
+ Require all granted
+ </IfModule>
+ <IfModule !mod_authz_core.c>
+ # Apache 2.2
+ Order allow,deny
+ Allow from all
+ </IfModule>
+</Directory>
+
+Alias /rt "@RT_WWWDIR@"
+
+<Location /rt>
+ SetHandler modperl
+ PerlResponseHandler Plack::Handler::Apache2
+ PerlSetVar psgi_app @RT_SBINDIR@/rt-server
+</Location>
diff --git a/rt.logrotate.in b/rt.logrotate.in
new file mode 100644
index 0000000..415aae9
--- /dev/null
+++ b/rt.logrotate.in
@@ -0,0 +1,5 @@
+ at RT_LOGDIR@/rt.log {
+ missingok
+ notifempty
+ size 100k
+}
diff --git a/rt.spec b/rt.spec
new file mode 100644
index 0000000..32666c6
--- /dev/null
+++ b/rt.spec
@@ -0,0 +1,624 @@
+#
+# Copyright (c) 2005-2015, Ralf Corsepius, Ulm, Germany.
+# This file and all modifications and additions to the pristine
+# package are under the same license as the package itself.
+#
+
+# Supported rpmbuild options:
+#
+# --with gd/--without gd
+# enable/disable gd support
+# Default: enabled (had been default in rt < 3.8.0)
+%bcond_without gd
+
+# --with graphviz/--without graphviz
+# enable/disable graphiz support
+# Default: disabled (missing deps)
+%bcond_with graphviz
+
+# --with devel_mode/--without devel_mode
+# enable/disable building/installing devel files
+# Default: enabled
+%bcond_without devel_mode
+
+# --with gpg/--without gpg
+# enable/disable building gpg support
+# Default: disabled
+%bcond_with gpg
+
+# --with runtests
+# run testsuite when building the rpm
+# Default: disabled (doesn't work in chroots.)
+%bcond_with runtests
+
+# --with mysql
+# configure for use with mysql
+%bcond_with mysql
+# --with pg
+# configure for use with postgress
+%bcond_with pg
+
+# default to mysql
+%if !%{with mysql} && !%{with pg}
+%global with_mysql 1
+%endif
+
+%global RT_BINDIR %{_bindir}
+%global RT_SBINDIR %{_sbindir}
+%global RT_FONTSDIR %{_datadir}/%{name}/fonts
+%global RT_LIBDIR %{perl_vendorlib}
+%global RT_WWWDIR %{_datadir}/%{name}/html
+%global RT_LEXDIR %{_datadir}/%{name}/po
+%global RT_LOGDIR %{_localstatedir}/log/%{name}
+%global RT_CACHEDIR %{_localstatedir}/cache/%{name}
+%global RT_LOCALSTATEDIR %{_localstatedir}/lib/%{name}
+
+Name: rt
+Version: 4.0.22
+Release: 3%{?dist}
+Summary: Request tracker
+
+Group: Applications/Internet
+License: GPLv2+
+URL: http://www.bestpractical.com/rt
+Source0: http://www.bestpractical.com/pub/rt/release/rt-%{version}.tar.gz
+# Notes on running the testsuite
+Source1: README.tests
+# rt's Apache configuration
+Source2: rt.conf.in
+# Fedora-specific installation notes
+Source3: README.fedora.in
+# rt's logrotate configuration
+Source4: rt.logrotate.in
+
+Patch1: 0001-Add-Fedora-configuration.patch
+Patch2: 0002-Broken-test-dependencies.patch
+Patch3: 0003-Use-usr-bin-perl-instead-of-usr-bin-env-perl.patch
+Patch4: 0004-Remove-fixperms-font-install.patch
+Patch5: 0005-Fix-permissions.patch
+Patch6: 0006-Remove-configure-time-generated-files.patch
+Patch7: 0007-Adjust-path-to-html-autohandler.patch
+
+BuildArch: noarch
+
+Obsoletes: rt3 < %{version}-%{release}
+Provides: rt3 = %{version}-%{release}
+
+# This list is alpha sorted
+BuildRequires: perl(Apache::DBI)
+BuildRequires: perl(Apache::Session) >= 1.53
+BuildRequires: perl(Cache::Simple::TimedExpiry)
+BuildRequires: perl(CGI::Cookie) >= 1.20
+BuildRequires: perl(CGI::PSGI)
+BuildRequires: perl(CGI::Emulate::PSGI)
+BuildRequires: perl(Class::ReturnValue) >= 0.40
+BuildRequires: perl(Convert::Color)
+BuildRequires: perl(CPAN)
+BuildRequires: perl(CSS::Squish) >= 0.06
+BuildRequires: perl(Data::ICal)
+BuildRequires: perl(Date::Format)
+BuildRequires: perl(DateTime) >= 0.44
+BuildRequires: perl(DateTime::Locale) >= 0.40
+%{?with_mysql:BuildRequires: perl(DBD::mysql) >= 2.1018}
+# This should be: BuildRequires: perl(DBD::Pg) != 3.3.0
+# cf. RHBZ#1138926
+%{?with_pg:BuildConflicts: perl(DBD::Pg) == 3.3.0}
+%{?with_pg:BuildRequires: perl(DBD::Pg)}
+BuildRequires: perl(DBI) >= 1.37
+BuildRequires: perl(DBIx::SearchBuilder) >= 1.59
+BuildRequires: perl(Devel::StackTrace) >= 1.19
+BuildRequires: perl(Devel::GlobalDestruction)
+BuildRequires: perl(Digest::base)
+BuildRequires: perl(Digest::MD5) >= 2.27
+BuildRequires: perl(Email::Address)
+BuildRequires: perl(Encode) >= 2.39
+BuildRequires: perl(Errno)
+%{?with_devel_mode:BuildRequires: perl(File::Find)}
+BuildRequires: perl(File::Glob)
+BuildRequires: perl(File::ShareDir)
+BuildRequires: perl(File::Spec) >= 0.8
+BuildRequires: perl(File::Temp) >= 0.19
+%{?with_gd:BuildRequires: perl(GD)}
+%{?with_gd:BuildRequires: perl(GD::Graph)}
+%{?with_gd:BuildRequires: perl(GD::Text)}
+%{?with_gpg:BuildRequires: perl(GnuPG::Interface)}
+%{?with_graphviz:BuildRequires: perl(GraphViz)}
+BuildRequires: perl(Getopt::Long) >= 2.24
+BuildRequires: perl(HTML::Entities)
+%{?with_devel_mode:BuildRequires: perl(HTML::Form)}
+BuildRequires: perl(HTML::FormatText)
+BuildRequires: perl(HTML::Mason) >= 1.43
+BuildRequires: perl(HTML::Mason::PSGIHandler)
+BuildRequires: perl(HTML::Quoted)
+BuildRequires: perl(HTML::RewriteAttributes) >= 0.02
+BuildRequires: perl(HTML::Scrubber) >= 0.08
+BuildRequires: perl(HTML::TreeBuilder)
+BuildRequires: perl(HTTP::Request::Common)
+BuildRequires: perl(HTTP::Server::Simple) >= 0.34
+BuildRequires: perl(HTTP::Server::Simple::Mason) >= 0.09
+BuildRequires: perl(Imager)
+BuildRequires: perl(Imager::File::GIF)
+BuildRequires: perl(Imager::File::PNG)
+BuildRequires: perl(Imager::File::JPEG)
+%{?with_graphviz:BuildRequires: perl(IPC::Run)}
+BuildRequires: perl(IPC::Run3)
+%{?with_graphviz:BuildRequires: perl(IPC::Run::SafeHandles)}
+BuildRequires: perl(JSON)
+BuildRequires: perl(JavaScript::Minifier)
+BuildRequires: perl(List::MoreUtils)
+BuildRequires: perl(Locale::Maketext) >= 1.06
+BuildRequires: perl(Locale::Maketext::Fuzzy)
+BuildRequires: perl(Locale::Maketext::Lexicon) >= 0.32
+BuildRequires: perl(Locale::PO)
+BuildRequires: perl(Log::Dispatch) >= 2.23
+%{?with_devel_mode:BuildRequires: perl(Log::Dispatch::Perl)}
+BuildRequires: perl(LWP)
+BuildRequires: perl(LWP::UserAgent)
+BuildRequires: perl(Mail::Mailer) >= 1.57
+BuildRequires: perl(MIME::Entity) >= 5.425
+BuildRequires: perl(MIME::Types)
+%{?with_devel_mode:BuildRequires: perl(Module::Refresh) >= 0.03}
+BuildRequires: perl(Module::Versions::Report) >= 1.05
+BuildRequires: perl(Net::CIDR)
+BuildRequires: perl(Net::Server)
+BuildRequires: perl(Net::Server::PreFork)
+BuildRequires: perl(Net::SMTP)
+%{?with_gpg:BuildRequires: perl(PerlIO::eol)}
+BuildRequires: perl(Pod::Usage)
+BuildRequires: perl(Plack)
+BuildRequires: perl(Plack::Handler::Starlet)
+%{?with_devel_mode:BuildRequires: perl(Plack::Middleware::Test::StashWarnings) >= 0.06}
+BuildRequires: perl(Regexp::Common)
+BuildRequires: perl(Regexp::Common::net::CIDR)
+BuildRequires: perl(Regexp::IPv6)
+BuildRequires: perl(Scalar::Util)
+BuildRequires: perl(Storable) >= 2.08
+%{?with_devel_mode:BuildRequires: perl(String::ShellQuote)}
+BuildRequires: perl(Term::ReadKey)
+BuildRequires: perl(Term::ReadLine)
+BuildRequires: perl(Term::EditorEdit)
+%{?with_devel_mode:BuildRequires: perl(Test::Builder) >= 0.77}
+%{?with_devel_mode:BuildRequires: perl(Test::Deep)}
+%{?with_devel_mode:BuildRequires: perl(Test::Email)}
+%{?with_devel_mode:BuildRequires: perl(Email::Abstract)}
+%{?with_devel_mode:BuildRequires: perl(Test::Expect) >= 0.31}
+%{?with_devel_mode:BuildRequires: perl(Test::HTTP::Server::Simple) >= 0.09}
+%{?with_devel_mode:BuildRequires: perl(Test::MockTime)}
+%{?with_devel_mode:BuildRequires: perl(Test::NoWarnings)}
+%{?with_devel_mode:BuildRequires: perl(Test::Warn)}
+%{?with_devel_mode:BuildRequires: perl(Test::WWW::Mechanize)}
+%{?with_devel_mode:BuildRequires: perl(Test::WWW::Mechanize::PSGI)}
+BuildRequires: perl(Text::ParseWords)
+BuildRequires: perl(Text::Password::Pronounceable)
+BuildRequires: perl(Text::Quoted) >= 2.02
+BuildRequires: perl(Text::Template)
+BuildRequires: perl(Text::WikiFormat) >= 0.76
+BuildRequires: perl(Text::Wrapper)
+BuildRequires: perl(Time::HiRes)
+BuildRequires: perl(Time::ParseDate)
+BuildRequires: perl(Tree::Simple) >= 1.04
+BuildRequires: perl(UNIVERSAL::require)
+%{?with_devel_mode:BuildRequires: perl(Web::Scraper)}
+%{?with_devel_mode:BuildRequires: perl(WWW::Mechanize)}
+BuildRequires: perl(XML::RSS) >= 1.05
+%{?with_devel_mode:BuildRequires: perl(XML::Simple)}
+
+%{?with_runtests:BuildRequires: perl(DBD::SQLite)}
+%{?with_runtests:BuildRequires: perl(Test::Warn)}
+%{?with_runtests:BuildRequires: perl(Test::MockTime)}
+%{?with_runtests:BuildRequires: perl(String::ShellQuote)}
+%{?with_runtests:BuildRequires: perl(Test::Pod) >= 1.14}
+%{?with_runtests:BuildRequires: perl(PerlIO::eol)}
+%{?with_runtests:BuildRequires: perl(Test::Expect)}
+BuildRequires: perl(Test::Pod) >= 1.14
+
+BuildRequires: /usr/bin/pod2man
+BuildRequires: /usr/sbin/apachectl
+
+# the original sources carry bundled versions of these ...
+Requires: /usr/share/fonts/google-droid/DroidSansFallback.ttf
+Requires: /usr/share/fonts/google-droid/DroidSans.ttf
+# ... we use symlinks to the system-wide versions ...
+BuildRequires: /usr/share/fonts/google-droid/DroidSansFallback.ttf
+BuildRequires: /usr/share/fonts/google-droid/DroidSans.ttf
+
+Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version))
+Requires: %{_sysconfdir}/httpd/conf.d
+
+Requires(postun): %{__rm}
+
+# rpm doesn't catch these:
+Requires: perl(Apache::Session)
+Requires: perl(Calendar::Simple)
+Requires: perl(CSS::Squish)
+Requires: perl(Data::ICal)
+Requires: perl(Data::ICal::Entry::Event)
+%{?with_mysql:Requires: perl(DBD::mysql) >= 2.1018}
+# This should be: Requires: perl(DBD::Pg) != 3.3.0
+# cf. RHBZ#1138926
+%{?with_pg:Requires: perl(DBD::Pg)}
+%{?with_pg:Conflicts: perl(DBD::Pg) == 3.3.0}
+Requires: perl(Log::Dispatch::Perl)
+%{?with_gd:Requires: perl(GD::Text)}
+%{?with_gd:Requires: perl(GD::Graph::bars)}
+%{?with_gd:Requires: perl(GD::Graph::pie)}
+Requires: perl(HTML::Quoted)
+Requires: perl(HTTP::Server::Simple::Mason)
+Requires: perl(HTML::Mason::Request)
+Requires: perl(I18N::LangTags::List)
+Requires: perl(IPC::Run3)
+Requires: perl(LWP::MediaTypes)
+Requires: perl(mod_perl2)
+Requires: perl(Module::Versions::Report)
+Requires: perl(Net::Server::PreFork)
+Requires: perl(Plack::Middleware::Test::StashWarnings) >= 0.06
+Requires: perl(Plack::Handler::Starlet)
+Requires: perl(Text::Quoted)
+Requires: perl(Text::WikiFormat)
+Requires: perl(URI::URL)
+Requires: perl(XML::RSS)
+
+# rpm fails to add these:
+Provides: perl(RT::Shredder::Exceptions)
+Provides: perl(RT::Shredder::Record)
+Provides: perl(RT::Shredder::Transaction)
+Provides: perl(RT::Tickets_SQL)
+
+# Split out. Technically, not actually necessary, but ... let's keep it for now.
+Requires: rt-mailgate
+
+%{?perl_default_filter}
+
+# Keep FCGI optional
+%global __requires_exclude %{?__requires_exclude:%__requires_exclude|}^perl\\(FCGI::ProcManager\\)
+# Work-around rpm's depgenerator defect:
+%global __requires_exclude %{?__requires_exclude:%__requires_exclude|}^perl\\(DBIx::SearchBuilder::Handle::\\)
+
+# Filter redundant provides
+%global __provides_exclude %{?__provides_exclude:%__provides_exclude|}^perl\\(RT\\)$
+# Filter bogus provides
+%global __provides_exclude %{?__provides_exclude:%__provides_exclude|}^perl\\(HTML::Mason
+%global __provides_exclude %{?__provides_exclude:%__provides_exclude|}^perl\\(IO::Handle::CRLF\\)$
+%global __provides_exclude %{?__provides_exclude:%__provides_exclude|}^perl\\(Log::Dispatch\\)$
+
+
+%description
+RT is an enterprise-grade ticketing system which enables a group of people
+to intelligently and efficiently manage tasks, issues, and requests submitted
+by a community of users.
+
+
+%package mailgate
+Summary: rt's mailgate utility
+Group: Applications/Internet
+# rpm doesn't catch these:
+Requires: perl(Pod::Usage)
+Requires: perl(HTML::TreeBuilder)
+Requires: perl(HTML::FormatText)
+Obsoletes: rt3-mailgate < %{version}-%{release}
+Provides: rt3-mailgate = %{version}-%{release}
+
+%description mailgate
+%{summary}
+
+
+%if %{with devel_mode}
+%package tests
+Summary: Test suite for package rt
+Group: Development/Debug
+Requires: %{name} = %{version}-%{release}
+Requires(postun): %{__rm}
+Requires: /usr/bin/prove
+Requires: perl(RT::Test)
+# rpm doesn't catch these:
+Requires: perl(DBD::SQLite)
+Requires: perl(GnuPG::Interface)
+# Bug: The testsuite unconditionally depends upon perl(GraphViz)
+Requires: perl(GraphViz)
+Requires: perl(PerlIO::eol)
+Requires: perl(Plack::Handler::Apache2)
+Requires: perl(String::ShellQuote)
+Requires: perl(Test::Deep)
+Requires: perl(Test::Expect)
+Requires: perl(Test::MockTime)
+Requires: perl(Test::Warn)
+
+Obsoletes: rt3-tests < %{version}-%{release}
+Provides: rt3-tests = %{version}-%{release}
+
+%description tests
+%{summary}
+
+# Running the tests leaves stray files
+# remove everything by brute force.
+%postun tests
+if [ $1 -eq 0 ]; then
+ %{__rm} -rf %{perl_testdir}/%{name}
+fi
+
+
+%package -n perl-RT-Test
+Summary: rt's test utility module
+Group: Applications/Internet
+Requires: rt = %{version}-%{release}
+Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version))
+
+# rpm doesn't catch these:
+Requires: perl(Test::WWW::Mechanize::PSGI)
+
+%description -n perl-RT-Test
+%{summary}
+
+%endif # devel_mode
+
+%prep
+%setup -q -n rt-%{version}
+
+sed -e 's, at RT_CACHEDIR@,%{RT_CACHEDIR},' %{SOURCE3} \
+ > README.fedora
+sed -e 's, at RT_LOGDIR@,%{RT_LOGDIR},' %{SOURCE4} \
+ > rt.logrotate
+
+%patch1 -p1
+%patch2 -p1
+%patch3 -p1
+%patch4 -p1
+%patch5 -p1
+%patch6 -p1
+%patch7 -p1
+
+# Propagate rpm's directories to config.layout
+cat << \EOF >> config.layout
+
+# Fedora directory layout.
+<Layout Fedora>
+ bindir: %{RT_BINDIR}
+ sbindir: %{RT_SBINDIR}
+ sysconfdir: %{_sysconfdir}/%{name}
+ libdir: %{RT_LIBDIR}
+ manualdir: %{_pkgdocdir}/docs
+ lexdir: %{RT_LEXDIR}
+ localstatedir: %{RT_LOCALSTATEDIR}
+ htmldir: %{RT_WWWDIR}
+ fontdir: %{RT_FONTSDIR}
+ logfiledir: %{RT_LOGDIR}
+ masonstatedir: %{RT_CACHEDIR}/mason_data
+ sessionstatedir: %{RT_CACHEDIR}/session_data
+ customdir: %{_prefix}/local/lib/%{name}
+ custometcdir: %{_prefix}/local/etc/%{name}
+ customhtmldir: ${customdir}/html
+ customlexdir: ${customdir}/po
+ customlibdir: ${customdir}/lib
+</Layout>
+EOF
+
+# Comment out the Makefile trying to change groups/owners
+# Fix DESTDIR support
+sed -i \
+ -e 's, chgrp, : chrgp,g' \
+ -e 's, chown, : chown,g' \
+ -e 's,$(DESTDIR)/,$(DESTDIR),g' \
+ -e 's,-o $(BIN_OWNER) -g $(RTGROUP),,g' \
+Makefile.in
+
+# Install upgrade/ into %%{_datadir}/%%{name}/upgrade
+sed -i -e 's,$(RT_ETC_PATH)/upgrade,%{_datadir}/%{name}/upgrade,g' Makefile.in
+
+%build
+%configure \
+--with-apachectl=/usr/sbin/apachectl \
+--with-web-user=apache --with-web-group=apache \
+--with-db-type=%{?with_mysql:mysql}%{?with_pg:Pg} \
+--enable-layout=Fedora \
+--with-web-handler=modperl2 \
+--libdir=%{RT_LIBDIR} \
+%{?with_graphviz:--enable-graphviz}%{!?with_graphviz:--disable-graphviz} \
+%{?with_gd:--enable-gd}%{!?with_gd:--disable-gd} \
+%{?with_gpg:--enable-gpg}%{!?with_gpg:--disable-gpg}
+
+make %{?_smp_mflags}
+
+# Explicitly check for devel-mode deps
+%{?with_devel_mode:%{__perl} ./sbin/rt-test-dependencies --verbose --with-%{?with_mysql:mysql}%{?with_pg:pg} --with-modperl2 --with-dev}
+
+# Generate man-pages
+for file in \
+bin/rt \
+bin/rt-crontool \
+bin/rt-mailgate \
+sbin/rt-attributes-viewer \
+sbin/rt-clean-sessions \
+sbin/rt-dump-metadata \
+sbin/rt-email-dashboards \
+sbin/rt-email-digest \
+sbin/rt-email-group-admin \
+sbin/rt-fulltext-indexer \
+sbin/rt-preferences-viewer \
+sbin/rt-server \
+sbin/rt-server.fcgi \
+sbin/rt-session-viewer \
+sbin/rt-setup-database \
+sbin/rt-setup-fulltext-index \
+sbin/rt-shredder \
+sbin/rt-validator \
+sbin/standalone_httpd \
+; do
+/usr/bin/pod2man $file > $file.1
+done
+
+
+%install
+make install DESTDIR=${RPM_BUILD_ROOT}
+
+# We don't want CPAN
+rm -f ${RPM_BUILD_ROOT}%{_sbindir}/rt-test-dependencies
+
+# Install apache configuration
+mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/httpd/conf.d
+sed -e 's, at RT_WWWDIR@,%{RT_WWWDIR},g' \
+ -e 's, at RT_SBINDIR@,%{RT_SBINDIR},g' \
+ -e 's, at RT_BINDIR@,%{RT_BINDIR},g' \
+ %{SOURCE2} > ${RPM_BUILD_ROOT}%{_sysconfdir}/httpd/conf.d/%{name}.conf
+
+mkdir -p ${RPM_BUILD_ROOT}%{_mandir}/man1
+for file in bin/*.1 sbin/*.1; do
+install -m 0644 $file ${RPM_BUILD_ROOT}%{_mandir}/man1
+done
+
+install -d -m755 ${RPM_BUILD_ROOT}%{RT_LOGDIR}
+
+# install log rotation stuff
+mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d
+install -m 644 rt.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}
+
+# Symlink, %%{_sysconfdir}/%%{name}/upgrade is hard-coded at various places
+ln -s %{_datadir}/%{name}/upgrade ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/upgrade
+
+install -d -m755 ${RPM_BUILD_ROOT}%{RT_FONTSDIR}
+ln -s /usr/share/fonts/google-droid/DroidSans.ttf ${RPM_BUILD_ROOT}%{RT_FONTSDIR}
+ln -s /usr/share/fonts/google-droid/DroidSansFallback.ttf ${RPM_BUILD_ROOT}%{RT_FONTSDIR}
+
+install -d -m755 ${RPM_BUILD_ROOT}%{perl_testdir}/%{name}
+cp -R t ${RPM_BUILD_ROOT}%{perl_testdir}/%{name}
+
+# Some parts of the testsuite want relative paths
+install -d -m755 ${RPM_BUILD_ROOT}%{perl_testdir}/%{name}/devel
+cp -R devel/tools ${RPM_BUILD_ROOT}%{perl_testdir}/%{name}/devel
+cp %{SOURCE1} ${RPM_BUILD_ROOT}%{perl_testdir}/%{name}
+install -d -m755 ${RPM_BUILD_ROOT}%{perl_testdir}/%{name}/share
+ln -s %{RT_WWWDIR} ${RPM_BUILD_ROOT}%{perl_testdir}/%{name}/share/html
+ln -s %{_bindir} ${RPM_BUILD_ROOT}%{perl_testdir}/%{name}/bin
+ln -s %{_sbindir} ${RPM_BUILD_ROOT}%{perl_testdir}/%{name}/sbin
+ln -s %{_sysconfdir}/%{name} ${RPM_BUILD_ROOT}%{perl_testdir}/%{name}/etc
+ln -s %{RT_LIBDIR} ${RPM_BUILD_ROOT}%{perl_testdir}/%{name}/lib
+
+
+# This file should not be installed
+rm ${RPM_BUILD_ROOT}%{RT_LEXDIR}/*.pot
+
+# Fix permissions
+find ${RPM_BUILD_ROOT}%{RT_WWWDIR} \
+ -type f -exec chmod a-x {} \;
+
+# Silence rpmlint
+chmod a+x \
+${RPM_BUILD_ROOT}%{_datadir}/%{name}/upgrade/3.8-ical-extension \
+${RPM_BUILD_ROOT}%{_datadir}/%{name}/upgrade/upgrade-articles \
+${RPM_BUILD_ROOT}%{_datadir}/%{name}/upgrade/generate-rtaddressregexp \
+${RPM_BUILD_ROOT}%{_datadir}/%{name}/upgrade/shrink_transactions_table.pl \
+${RPM_BUILD_ROOT}%{_datadir}/%{name}/upgrade/split-out-cf-categories \
+${RPM_BUILD_ROOT}%{_datadir}/%{name}/upgrade/4.0-customfield-checkbox-extension \
+${RPM_BUILD_ROOT}%{_datadir}/%{name}/upgrade/vulnerable-passwords \
+${RPM_BUILD_ROOT}%{_datadir}/%{name}/upgrade/upgrade-mysql-schema.pl \
+${RPM_BUILD_ROOT}%{_datadir}/%{name}/upgrade/shrink_cgm_table.pl
+
+%check
+# The tests don't work in buildroots, they
+# - require to be run as root
+# - require an operational rt system
+%{?with_runtests:make test}
+
+%{!?with_runtests:/usr/bin/prove -l t/pod.t}
+
+%postun
+if [ $1 -eq 0 ]; then
+ %{__rm} -rf %{RT_CACHEDIR}
+fi
+
+
+%files
+%doc COPYING README README.fedora
+%{_bindir}/*
+%{_sbindir}/*
+%exclude %{_bindir}/rt-mailgate
+%{_mandir}/man1/*
+%exclude %{_mandir}/man1/rt-mailgate*
+%{RT_LIBDIR}/*
+%exclude %{RT_LIBDIR}/RT/Test*
+%attr(0700,apache,apache) %{RT_LOGDIR}
+
+%dir %{_sysconfdir}/%{name}
+%attr(-,root,root)%{_datadir}/%{name}/upgrade
+%attr(-,root,root)%{_sysconfdir}/%{name}/upgrade
+%attr(-,root,root)%{_sysconfdir}/%{name}/acl*
+%attr(-,root,root)%{_sysconfdir}/%{name}/schema*
+%attr(-,root,root)%{_sysconfdir}/%{name}/init*
+%{?!with_pg:%exclude %{_sysconfdir}/%{name}/*.Pg}
+%{?!with_pg:%exclude %{_datadir}/%{name}/upgrade/*/*.Pg}
+%exclude %{_sysconfdir}/%{name}/*.Oracle
+%exclude %{_datadir}/%{name}/upgrade/*/*.Oracle
+%exclude %{_sysconfdir}/%{name}/*.SQLite
+%exclude %{_datadir}/%{name}/upgrade/*/*.SQLite
+%{?!with_mysql:%exclude %{_sysconfdir}/%{name}/*.mysql}
+%{?!with_mysql:%exclude %{_datadir}/%{name}/upgrade/*/*.mysql}
+%config(noreplace) %attr(0640,apache,apache) %{_sysconfdir}/%{name}/RT_*
+
+%config(noreplace) %{_sysconfdir}/logrotate.d/%{name}
+
+%dir %{_datadir}/%{name}
+%{RT_WWWDIR}
+%{RT_LEXDIR}
+%{RT_FONTSDIR}
+
+%config(noreplace) %{_sysconfdir}/httpd/conf.d/%{name}.conf
+
+%dir %{RT_CACHEDIR}
+%attr(0770,apache,apache) %{RT_CACHEDIR}/mason_data
+%attr(0770,apache,apache) %{RT_CACHEDIR}/session_data
+
+%files mailgate
+%doc COPYING
+%{_bindir}/rt-mailgate
+%{_mandir}/man1/rt-mailgate*
+
+%if %{with devel_mode}
+%files tests
+%dir %{perl_testdir}
+%{perl_testdir}/%{name}
+# Doesn't work outside of the source tree
+%exclude %{perl_testdir}/%{name}/t/pod.t
+# Required by t/shredder/*t
+%{_sysconfdir}/%{name}/*.SQLite
+
+%files -n perl-RT-Test
+%doc COPYING
+%dir %{RT_LIBDIR}/RT
+%{RT_LIBDIR}/RT/Test*
+%endif
+
+%changelog
+* Fri Jan 23 2015 Ralf Corsépius <corsepiu at fedoraproject.org> - 4.0.22-3
+- Fix testsuite failure (Add 0007-Adjust-path-to-html-autohandler.patch)
+ (From Jason Tibbitts).
+
+* Thu Jan 22 2015 Ralf Corsépius <corsepiu at fedoraproject.org> - 4.0.22-2
+- Misc. cosmetical changes.
+- Rework --with mysql/pg processing.
+- Filter out P: perl(Log::Dispatch).
+- Add R: %%{_sysconfdir}/httpd/conf.d.
+- Add note on SELinux to README.fedora.
+
+* Wed Oct 08 2014 Ralf Corsépius <corsepiu at fedoraproject.org> - 4.0.22-1
+- Upstream update.
+- Reflect rt-server being installed to %%{_sbindir}.
+- Add BuildConflicts/Conflicts: perl(DBD::Pg) == 3.3.0.
+- Install updates into %%{_datadir}.
+- Various misc. cosmetic fixes.
+
+* Sun Sep 07 2014 Ralf Corsépius <corsepiu at fedoraproject.org> - 4.0.21-4
+- Remove artefacts of installation outside of %%{_bindir}.
+- Add optional support for pg.
+- Introduce RT_FONTSDIR.
+- Require: perl(DBD::Pg) and perl(DBD::mysql).
+- Exclude unsupported upgrade files.
+
+* Wed Aug 20 2014 Ralf Corsépius <corsepiu at fedoraproject.org> - 4.0.21-3
+- Add rt-attributes-editor.
+
+* Tue Jul 22 2014 Ralf Corsépius <corsepiu at fedoraproject.org> - 4.0.21-2
+- Address misc. cosmetical issues.
+- Own %%{perl_testdir}.
+- Add COPYING to perl-RT-Tests.
+
+* Mon Jul 21 2014 Ralf Corsépius <corsepiu at fedoraproject.org> - 4.0.21-1
+- Fedora submission.
diff --git a/sources b/sources
index e69de29..7750565 100644
--- a/sources
+++ b/sources
@@ -0,0 +1 @@
+38bcc5d4eee480e70617175878f65c43 rt-4.0.22.tar.gz
More information about the scm-commits
mailing list