This commit was manufactured by cvs2svn to create branch root_of_svc_elec_features
authorcvs2git <cvs2git>
Fri, 30 Jul 2010 22:26:40 +0000 (22:26 +0000)
committercvs2git <cvs2git>
Fri, 30 Jul 2010 22:26:40 +0000 (22:26 +0000)
'svc_elec_features'.

44 files changed:
rt/README
rt/etc/acl.Oracle
rt/etc/acl.Pg
rt/etc/acl.mysql
rt/lib/RT/ACE.pm
rt/lib/RT/ACL.pm
rt/lib/RT/Action/Autoreply.pm
rt/lib/RT/Action/Generic.pm
rt/lib/RT/Action/Notify.pm
rt/lib/RT/Action/NotifyAsComment.pm
rt/lib/RT/Action/ResolveMembers.pm
rt/lib/RT/Action/SendEmail.pm
rt/lib/RT/Attachment.pm
rt/lib/RT/Attachments.pm
rt/lib/RT/Condition/AnyTransaction.pm
rt/lib/RT/Condition/Generic.pm
rt/lib/RT/Condition/StatusChange.pm
rt/lib/RT/CurrentUser.pm
rt/lib/RT/Date.pm
rt/lib/RT/Group.pm
rt/lib/RT/GroupMember.pm
rt/lib/RT/GroupMembers.pm
rt/lib/RT/Groups.pm
rt/lib/RT/Handle.pm
rt/lib/RT/Interface/CLI.pm
rt/lib/RT/Interface/Email.pm
rt/lib/RT/Link.pm
rt/lib/RT/Links.pm
rt/lib/RT/Queue.pm
rt/lib/RT/Queues.pm
rt/lib/RT/Scrip.pm
rt/lib/RT/ScripAction.pm
rt/lib/RT/ScripActions.pm
rt/lib/RT/ScripCondition.pm
rt/lib/RT/ScripConditions.pm
rt/lib/RT/Scrips.pm
rt/lib/RT/Template.pm
rt/lib/RT/Templates.pm
rt/lib/RT/Ticket.pm
rt/lib/RT/Tickets.pm
rt/lib/RT/Transaction.pm
rt/lib/RT/Transactions.pm
rt/lib/RT/User.pm
rt/lib/RT/Users.pm

index 7c5e4d4..899a5a0 100755 (executable)
--- a/rt/README
+++ b/rt/README
-# BEGIN LICENSE BLOCK
-# 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-# 
-# (Except where explictly superceded by other copyright notices)
-# 
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-# 
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-# 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
-# 
-# 
-# END LICENSE BLOCK
-RT is an enterprise-grade issue tracking system. It allows
-organizations to keep track of their to-do lists, who is working
-on which tasks, what's already been done, and when tasks were
-completed. It is available under the terms of version 2 of the GNU
-General Public License (GPL), so it doesn't cost anything to set
-up and use.
+RT is an enterprise-grade issue tracking system. It allows organizations
+to keep track of what needs to get done, who is working on which tasks,
+what's already been done, and when tasks were (or weren't) completed.
 
+RT doesn't cost anything to use, no matter how much you use it; it
+is freely available under the terms of Version 2 of the GNU General
+Public License.
 
-        Jesse Vincent
-        Best Practical Solutions, LLC
-        March 2003
+RT is commercially-supported software. To purchase support, training,
+custom development, or professional services, please get in touch with
+us at sales@bestpractical.com.
 
-REQUIRED PACKAGES:
-------------------
+     Jesse Vincent
+     Best Practical Solutions, LLC
+     March, 2010
 
-o   Perl 5.8.0 or later (http://www.perl.com).
 
-       (If you intend to use the FastCGI or SpeedyCGI support, you 
-        need to make sure that perl has been built with support for 
-        setgid perl scripts.)`
+REQUIRED PACKAGES
+-----------------
 
-    Perl 5.6.1 is currently deprecated and will be officially desupported
-    in a future release
+o   Perl 5.8.3 or later (http://www.perl.org).
 
-o   A DB backend; MySQL is recommended ( http://www.mysql.com ) 
-        Currently supported:  Mysql 4.0.13 or later. 
+        Perl versions prior to 5.8.3 contain bugs that could result
+        in data corruption. RT won't start on older versions.
+
+o   A supported SQL database
+
+        Currently supported:  Mysql 4.0.13 or later with InnoDB support.
                               Postgres 7.2 or later.
+                              Oracle 9iR2 or later.
+                              SQLite 3.0. (Not recommended for production)
+
+o   Apache version 1.3.x or 2.x (http://httpd.apache.org)
+        with mod_perl -- (http://perl.apache.org )
+        or with FastCGI -- (www.fastcgi.com)
+        or other webserver with FastCGI support
+
+        RT's FastCGI handler needs to access RT's configuration file.
 
-                              Mysql 3.23.46 or newer with support for InnoDB 
-                             is currently deprecated and will be officially
-                             desupported in a future release.
+o   Various and sundry perl modules
+        A tool included with RT takes care of the installation of
+        most of these automatically during the install process.
 
-o   Apache version 1.3.x or 2.x (http://httpd.apache.org) 
-    with mod_perl -- (http://perl.apache.org ) 
-    or a webserver with FastCGI support (www.fastcgi.com)
+        The tool supplied with RT uses Perl's CPAN system
+        (http://www.cpan.org) to install modules. Some operating
+        systems package all or some of the modules required, and
+        you may be better off installing the modules that way.
 
-        mod_perl 2.0 isn't quite ready for prime_time just yet;
-        Best Practical Solutions strongly recommends that sites use 
-        Apache 1.3 or FastCGI.
 
-        Compiling mod_perl on Apache 1.3.x as a DSO has been known 
-         to have massive stability problems and is not recommended.
+GENERAL INSTALLATION
+--------------------
 
-        mod_perl 1.x must be build with EVERYTHING=1
+This is a rough guide to installing RT. For more detail, you'll
+want to read a more comprehensive installation guide at:
 
-        RT's FastCGI handler runs setgid to the 'rt' group to
-        protect RT's database password.  You may need to install
-        a special  "suidperl" package or reconfigure your perl
-        setup to support "setuid scripts" if you intend to use RT
-        with FastCGI.
+    http://wiki.bestpractical.com/index.cgi?InstallationGuides
 
-        Debian GNU/* 3.0+: the package which installs suidperl is
-         called perl-suid, and should work without any tweaking.
+1   Unpack this distribution other than where you want to install RT
 
-        FreeBSD 4.2+: the package is called sperl, and should
-         install a suidperl that just works 
+     To do this cleanly, run the following command:
 
-        Conectiva Linux 6.0+: suidperl is installed by default when 
-         perl is installed, but the program /bin/suidperl is not setuid. 
-         You must use chmod to make it setuid.
+       tar xzvf rt.tar.gz -C /tmp
 
+2   Run the "configure" script.
 
+       ./configure --help to see the list of options
+       ./configure (with the flags you want)
 
-o    Various and sundry perl modules
-       A tool included with RT takes care of the installation of
-       most of these automatically during the install process.
+    RT defaults to installing in /opt/rt3 with MySQL as its database. It
+    tries to guess which of www-data, www, apache or nobody your webserver
+    will run as, but you can override that behavior.  Note that the
+    default install directory in /opt/rt3 does not work under SELinux's
+    default configuration.
 
-       The tool supplied with RT uses Perl's CPAN system
-       (http://www.cpan.org) to install modules. Some operating
-       systems package all or some of the modules required and
-       you may be better off installing the modules that way.
+    If you're upgrading RT then it worth to read UPGRADING document at this
+    moment. Some extension you're using may have been integrated into
+    core. It's recommended to use new clean directory when you're
+    upgrading to new major release (for example from 3.6.x to 3.8.x).
 
+3   Make sure that RT has everything it needs to run.
 
-GENERAL INSTALLATION
---------------------
+    Check for missing dependencies by running:
+
+       make testdeps
+
+4   If the script reports any missing dependencies, install them by hand
+    or run the following command as a user who has permission to install perl
+    modules on your system:
+
+     make fixdeps
+
+    Some modules require user input or environment variables to install correctly,
+    so it may be necessary to install them manually.
+
+5   Check to make sure everything was installed properly.
+
+       make testdeps
+
+     It might sometimes be necessary to run "make fixdeps" several times
+     to install all necessary perl modules.
 
-This is a rough guide to installing RT. For more detail, you'll want 
-to read 'Chapter 2: Installing' in RT's manual, available at
-http://www.bestpractical.com/rt 
+6   If this is a new installation:
 
-1   Unpack this distribution SOMWHERE OTHER THAN where you want to install RT
+     As a user with permission to install RT in your chosen directory, type:
 
-        Granted, you've already got it open. To do this cleanly:
+       make install
 
-                tar xzvf rt.tar.gz -C /tmp
+     Set up etc/RT_SiteConfig.pm in your RT installation directory.
+     You'll need to add any values you need to change from the defaults
+     in etc/RT_Config.pm
 
-2   Run the "configure" script. 
+     As a user with permission to read RT's configuration file, type:
 
-        ./configure --help to see the list of options
-        ./configure (with the flags you want)
+       make initialize-database
 
-3   Satisfy RT's myriad dependencies. 
+     If the make fails, type:
 
-3.1   Check for compliance:
-        
-   perl sbin/rt-test-dependencies \ 
-                --with-<databasename> --with-<web-environment>
+       make dropdb
 
-        databasename is one of: mysql, postgres
-        web-environment is one of: fastcgi, modperl1, modperl2
+     and start over from step 6
 
-3.2   If there are unsatisfied dependencies, install them by hand or run:
+7   If you're upgrading from RT 3.0 or newer:
 
-        perl sbin/rt-test-dependencies \
-                --with-<databasename> --with-<web-environment> --install
-        
+     Read through the UPGRADING document included in this distribution. If
+     you're using MySQL, read through UPGRADING.mysql as well.
 
-3.3   Check to make sure everything was installed properly:
+     It includes special upgrade instructions that will help you get this
+     new version of RT up and running smoothly.
 
-        perl sbin/rt-test-dependencies \
-                --with-<databasename> --with-<web-environment>
+     As a user with permission to install RT in your chosen installation
+     directory, type:
 
-4   Create a group called 'rt'
+       make upgrade
 
-5a  FOR A NEW INSTALLATION: 
-        
-        As root, type:
-                 make install        (replace "make" with the local name for 
-                                 Make, if you need to)
+     This will install new binaries, config files and libraries without
+     overwriting your RT database.
 
-                       
-                 make initialize-database 
+     Update etc/RT_SiteConfig.pm in your RT installation directory.
+     You'll need to add any new values you need to change from the defaults
+     in etc/RT_Config.pm
 
+     You may also need to update RT's database.  You can do this with
+     the rt-setup-database tool.  Replace root with the name of the dba
+     user on your database (root is the default for MySQL).
 
-        If the make fails, type:
-                make dropdb 
-        and start over from step 5a
+     You will be prompted for your previous version of RT (such as 3.6.4)
+     so that we can calculate which database updates to apply
 
-5b  FOR UPGRADING: (Within the RT 3.0.x series)
+     You should back up your database before running this command.
 
-        As root, type: 
-                make upgrade     (replace "make" with the local name for 
-                                  Make, if you need to)
+       /opt/rt3/sbin/rt-setup-database --dba root --prompt-for-dba-password --action upgrade
 
-        This will build new binaries, config files and libraries without
-        overwriting your RT database. 
-        
-        It may then instruct you to update your RT system database objects 
+     Clear mason cache dir:
 
-6   Edit etc/RT_SiteConfig.pm in your RT installation directory, by specifying
-    any values you need to change from the defaults in etc/RT_Config.pm
+       rm -fr /opt/rt3/var/mason_data/obj
 
-7   Configure the email and web gateways, as described below. 
+     Stop and start web-server.
 
-8   Stop and start your webserver, so it picks up your configuration changes.
 
-    NOTE: root's password for the web interface is "password" 
-    (without the quotes.)  Not changing this is a SECURITY risk
-    
-9   Configure RT per the instructions in RT's manual.
+8  If you're upgrading from RT 2.0:
+
+    Use the RT::Extension::RT2toRT3 module to upgrade to the current RT
+    release.  You can download it from CPAN here:
+    http://search.cpan.org/dist/RT-Extension-RT2toRT3/
+
+9   Configure the email and web gateways, as described below.
+
+    NOTE: root's password for the web interface is "password"
+    (without the quotes).  Not changing this is a SECURITY risk!
+
+10  Set up automated recurring tasks (cronjobs):
+
+    To generate email digest messages, you must arrange for the provided
+    utility to be run once daily, and once weekly. You may also want to
+    arrange for the rt-email-dashboards utility to be run hourly.
+    For example, if your task scheduler is cron, you can configure it as
+    follows:
+
+        crontab -e    # as the RT administrator (probably root)
+        # insert the following lines:
+        0 0 * * * /opt/rt3/sbin/rt-email-digest -m daily
+        0 0 * * 0 /opt/rt3/sbin/rt-email-digest -m weekly
+        0 * * * * /opt/rt3/sbin/rt-email-dashboards
+
+
+11   Set up users, groups, queues, scrips and access control.
 
     Until you do this, RT will not be able to send or receive email,
     nor will it be more than marginally functional.  This is not an
     optional step.
 
 
-THE WEB INTERFACE
------------------
 
-RT's web interface is based around HTML::Mason, which works best with the mod_perl
-perl interpreter within Apache httpd.  Alternatively, support for the FastCGI
-(and plain CGI) interface is also provided as 'bin/mason_handler.fcgi'.
 
-Apache 
-        You'll need to add a few lines to your httpd.conf telling it about RT:
+SETTING UP THE WEB INTERFACE
+----------------------------
+
+RT's web interface is based around HTML::Mason, which works well with
+the mod_perl perl interpreter within Apache httpd and FastCGI.
+
+Once you've set up the web interface, consider setting up automatic
+logout for inactive sessions. For more information about how to do that,
+run
+    perldoc /path/to/rt/sbin/rt-clean-sessions
+
+
+mod_perl 1.xx
+-------------
+
+WARNING: mod_perl 1.99_xx is not supported.
+
+See below configuration instructions for mod_perl 2.x
+
+To install RT with mod_perl 1.x, you'll need to install the
+apache database connection cache. To make sure it's installed, run
+the following command:
+
+    perl -MCPAN -e'install "Apache::DBI"'
+
+Next, add a few lines to your Apache 1.3.xx configuration file, so that
+it knows where to find RT:
 
 <VirtualHost your.ip.address>
     ServerName your.rt.server.hostname
+
     DocumentRoot /opt/rt3/share/html
     AddDefaultCharset UTF-8
 
-    # this line applies to Apache2+mod_perl2 only
-    PerlModule Apache2 Apache::compat
+    # optional apache logs for RT
+    # ErrorLog /opt/rt3/var/log/apache.error
+    # TransferLog /opt/rt3/var/log/apache.access
 
     PerlModule Apache::DBI
     PerlRequire /opt/rt3/bin/webmux.pl
 
-    # this section applies to Apache 1 only
+    <Location /NoAuth/images>
+        SetHandler default
+    </Location>
     <Location />
         SetHandler perl-script
         PerlHandler RT::Mason
     </Location>
+</VirtualHost>
 
-    # this section applies to Apache2+mod_perl2 only
-    <FilesMatch "\.html$">
-        SetHandler perl-script
-        PerlHandler RT::Mason
-    </FilesMatch>
-    <LocationMatch "/Attachment/">
-        SetHandler perl-script
-        PerlHandler RT::Mason
-    </LocationMatch>
-    <LocationMatch "/REST/">
+mod_perl 2.xx
+-------------
+
+WARNING: mod_perl 1.99_xx is not supported.
+
+Add a few lines to your Apache 2.xx configuration file, so that
+it knows where to find RT:
+
+<VirtualHost your.ip.address>
+    ServerName your.rt.server.hostname
+
+    DocumentRoot /opt/rt3/share/html
+    AddDefaultCharset UTF-8
+
+    # optional apache logs for RT
+    # ErrorLog /opt/rt3/var/log/apache2.error
+    # TransferLog /opt/rt3/var/log/apache2.access
+
+    PerlRequire "/opt/rt3/bin/webmux.pl"
+
+    <Location /NoAuth/images>
+        SetHandler default
+    </Location>
+    <Location />
         SetHandler perl-script
-        PerlHandler RT::Mason
-    </LocationMatch>
+        PerlResponseHandler RT::Mason
+    </Location>
 </VirtualHost>
 
+FastCGI
+-------
+
+Installation with FastCGI is a little bit more complex and is documented
+in detail at http://wiki.bestpractical.com/index.cgi?FastCGIConfiguration
+
+In the most basic configuration, you can set up your webserver to run
+as a user who is a member of the "rt" unix group so that the FastCGI script
+can read RT's configuration file.  It's important to understand the security
+implications of this configuration, which are discussed in the document
+mentioned above.
+
+To install RT with FastCGI, you'll need to add a few lines to your
+Apache configuration file telling it about RT:
+
+
+# Tell FastCGI to put its temporary files somewhere sane.
+FastCgiIpcDir /tmp
+
+FastCgiServer /opt/rt3/bin/mason_handler.fcgi -idle-timeout 120
+
+<VirtualHost rt.example.com>
+   ServerName your.rt.server.hostname
 
+   # Pass through requests to display images
+   Alias /NoAuth/images/ /opt/rt3/share/html/NoAuth/images/
 
-SETTING UP THE MAIL GATEWAY 
+   AddHandler fastcgi-script fcgi
+   ScriptAlias / /opt/rt3/bin/mason_handler.fcgi/
+</VirtualHost>
+
+
+
+SETTING UP THE MAIL GATEWAY
 ---------------------------
 
-An alias for the initial queue will need to be made in either your
-global mail aliases file (if you are using NIS) or locally on your
-machine.
-Add the following lines to /etc/aliases (or your local equivalent) :
+To let email flow to your RT server, you need to add a few lines of
+configuration to your mail server's "aliases" file. These lines "pipe"
+incoming email messages from your mail server to RT.
 
-rt:         "|/opt/rt3/bin/rt-mailgate --queue general --action correspond --url http://localhost/"
-rt-comment: "|/opt/rt3/bin/rt-mailgate --queue general --action comment --url http://localhost/"
-                                            |                |             |
-                            <queue-name>----/                |             |
-                                                             |             |
-               <correspond or comment depending on whether   |             |
-               the mail should be resent to the requestor>---/             |
-                                                                           |
-                                            <URL for RT's web interface>---/
+Add the following lines to /etc/aliases (or your local equivalent) on your mail server:
 
+rt:         "|/opt/rt3/bin/rt-mailgate --queue general --action correspond --url http://rt.example.com/"
+rt-comment: "|/opt/rt3/bin/rt-mailgate --queue general --action comment --url http://rt.example.com/"
+
+You'll need to add similar lines for each queue you want to be able
+to send email to. To find out more about how to configure RT's email
+gateway, type:
+
+       perldoc /opt/rt3/bin/rt-mailgate
 
-BUGS
-----
 
-To report a bug, send email to rt-3.0-bugs@fsck.com.
 
 GETTING HELP
 ------------
 
 If RT is mission-critical for you or if you use it heavily, we recommend that
 you purchase a commercial support contract.  Details on support contracts
-are available at http://www.bestpractical.com.
+are available at http://www.bestpractical.com or by writing to
+<sales@bestpractical.com>.
 
 If you're interested in having RT extended or customized or would like more
-information about commercial support options, please send email to 
+information about commercial support options, please send email to
 <sales@bestpractical.com> to discuss rates and availability.
 
 
-RT-USERS MAILINGLIST
---------------------
+
+RT WEBSITE
+----------
+
+For current information about RT, check out the RT website at
+     http://www.bestpractical.com/
+
+You'll find screenshots, a pointer to the current version of RT, contributed
+patches, and lots of other great stuff.
+
+
+
+RT-USERS MAILING LIST
+---------------------
 
 To keep up to date on the latest RT tips, techniques and extensions,
 you probably want to join the rt-users mailing list.  Send a message to:
 
-         rt-users-request@lists.fsck.com 
+      rt-users-request@lists.bestpractical.com
 
-With the body of the message consisting of only the word:
+with the body of the message consisting of only the word:
 
-        subscribe
+     subscribe
 
 If you're interested in hacking on RT, you'll want to subscribe to
-rt-devel@lists.fsck.com.  Subscribe to it with instructions similar to
-those above.
+<rt-devel@lists.bestpractical.com>.  Subscribe to it with instructions
+similar to those above.
 
 Address questions about the stable release to the rt-users list, and
 questions about the development version to the rt-devel list.  If you feel
@@ -280,21 +371,63 @@ your questions are best not asked publicly, send them personally to
 <jesse@bestpractical.com>.
 
 
-RT WEBSITE
-----------
-
-For current information about RT, check out the RT website at 
-        http://www.bestpractical.com/  
-
-You'll find screenshots, a pointer to the current version of RT, contributed 
-patches, and lots of other great stuff.
 
+BUGS
+----
 
-TROUBLESHOOTING
----------------
+RT's a pretty complex application, and as you get up to speed, you might
+run into some trouble. Generally, it's best to ask about things you
+run into on the rt-users mailinglist (or pick up a commercial support
+contract from Best Practical). But, sometimes people do run into bugs. In
+the exceedingly unlikely event that you hit a bug in RT, please report
+it! We'd love to hear about problems you have with RT, so we can fix them.
+To report a bug, send email to rt-bugs@fsck.com.
 
-If the solution to the problem you're running into isn't obvious and you've 
-checked the FAQ, feel free to send mail to rt-users@fsck.com (for released 
-versions of RT) or rt-devel@fsck.com (for development versions).
 
-Thanks!
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
index c8667c0..9ca4122 100644 (file)
@@ -1,10 +1,4 @@
-sub acl {
-return (
-"CREATE USER ${RT::DatabaseUser} identified by ${RT::DatabasePassword}".
-"temporary tablespace TEMP" .
-"default tablespace USERS" .
-"quota unlimited on USERS;" ,
-"grant connect, resource to ${RT::DatabaseUser};",
-"exit;");
-}
+
+sub acl { return () }
+
 1;
index 16ea71b..8a0d4f2 100755 (executable)
@@ -1,63 +1,76 @@
+
 sub acl {
     my $dbh = shift;
 
     my @acls;
 
     my @tables = qw (
-
-      attachments_id_seq
-      Attachments
-      queues_id_seq
-      Queues
-      links_id_seq
-      Links
-      principals_id_seq
-      Principals
-      groups_id_seq
-      Groups
-      scripconditions_id_seq
-      ScripConditions
-      transactions_id_seq
-      Transactions
-      scrips_id_seq
-      Scrips
-      acl_id_seq
-      ACL
-      groupmembers_id_seq
-      GroupMembers
-      cachedgroupmembers_id_seq
-      CachedGroupMembers
-      users_id_seq
-      Users
-      tickets_id_seq
-      Tickets
-      scripactions_id_seq
-      ScripActions
-      templates_id_seq
-      Templates
-      ticketcustomfieldvalues_id_s
-      TicketCustomFieldValues
-      customfields_id_seq
-      CustomFields
-      customfieldvalues_id_seq
-      CustomFieldValues
-      sessions
+        attachments_id_seq
+        Attachments
+        Attributes
+        attributes_id_seq
+        queues_id_seq
+        Queues 
+        links_id_seq
+        Links 
+        principals_id_seq
+        Principals 
+        groups_id_seq
+        Groups 
+        scripconditions_id_seq
+        ScripConditions 
+        transactions_id_seq
+        Transactions 
+        scrips_id_seq
+        Scrips 
+        acl_id_seq
+        ACL 
+        groupmembers_id_seq
+        GroupMembers 
+        cachedgroupmembers_id_seq
+        CachedGroupMembers 
+        users_id_seq
+        Users 
+        tickets_id_seq
+        Tickets 
+        scripactions_id_seq
+        ScripActions 
+        templates_id_seq
+        Templates 
+        objectcustomfieldvalues_id_s
+        ObjectCustomFieldValues 
+        customfields_id_seq
+        CustomFields 
+        objectcustomfields_id_s
+        ObjectCustomFields 
+        customfieldvalues_id_seq
+        CustomFieldValues
+        sessions
     );
 
-    # if there's already an rt_user, drop it.
-    my @row =
-      $dbh->selectrow_array( "select usename from pg_user where usename = '" . $RT::DatabaseUser."'" );
-    if ( $row[0] ) {
-        push @acls, "drop user ${RT::DatabaseUser};",;
+    my $db_user = RT->Config->Get('DatabaseUser');
+    my $db_pass = RT->Config->Get('DatabasePassword');
+
+    # if there's already an rt_user, use it.
+    my @row = $dbh->selectrow_array( "SELECT usename FROM pg_user WHERE usename = '$db_user'" );
+    unless ( $row[0] ) {
+        push @acls, "CREATE USER \"$db_user\" WITH PASSWORD '$db_pass' NOCREATEDB NOCREATEUSER;";
     }
 
-    push @acls, "create user ${RT::DatabaseUser} with password '${RT::DatabasePassword}' NOCREATEDB NOCREATEUSER;";
+    my $sequence_right
+        = ( $dbh->{pg_server_version} >= 80200 )
+        ? "USAGE, SELECT, UPDATE"
+        : "SELECT, UPDATE";
     foreach my $table (@tables) {
-        push @acls,
-          "GRANT SELECT, INSERT, UPDATE, DELETE ON $table to "
-          . $RT::DatabaseUser . ";";
-
+        if ( $table =~ /^[a-z]/ && $table ne 'sessions' ) {
+# table like objectcustomfields_id_s
+            push @acls, "GRANT $sequence_right ON $table TO \"$db_user\";"
+        }
+        else {
+            push @acls, "GRANT SELECT, INSERT, UPDATE, DELETE ON $table TO \"$db_user\";"
+        }
     }
     return (@acls);
 }
+
 1;
index 0ecaa3b..0982ca2 100755 (executable)
@@ -1,8 +1,27 @@
+
 sub acl {
-return  (
-"USE mysql;",
-"DELETE FROM user WHERE user = '${RT::DatabaseUser}';",
-"DELETE FROM db where db = '${RT::DatabaseName}';",
-"GRANT SELECT,INSERT,CREATE,INDEX,UPDATE,DELETE ON ${RT::DatabaseName}.* TO ${RT::DatabaseUser}\@${RT::DatabaseRTHost} IDENTIFIED BY '${RT::DatabasePassword}';");
+    my $db_name = RT->Config->Get('DatabaseName');
+    my $db_rthost = RT->Config->Get('DatabaseRTHost');
+    my $db_user = RT->Config->Get('DatabaseUser');
+    my $db_pass = RT->Config->Get('DatabasePassword');
+    unless ( $db_user ) {
+        print STDERR "DatabaseUser option is not defined or empty. Skipping...\n";
+        return;
+    }
+    if ( $db_user eq 'root' ) {
+        print STDERR "DatabaseUser is root. Skipping...\n";
+        return;
+    }
+    print "Granting access to $db_user\@'$db_rthost' on $db_name.\n";
+    return (
+        "USE mysql;",
+        "DELETE FROM user WHERE user = '$db_user';",
+        "DELETE FROM db where db = '$db_name';",
+        "GRANT SELECT,INSERT,CREATE,INDEX,UPDATE,DELETE
+               ON $db_name.*
+               TO '$db_user'\@'$db_rthost'
+               IDENTIFIED BY '$db_pass';",
+    );
 }
+
 1;
index 1501a12..7f21ba0 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
 # 
-# END LICENSE BLOCK
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -44,11 +69,7 @@ RT::ACE
 =cut
 
 package RT::ACE;
-use RT::Record; 
-
-
-use vars qw( @ISA );
-@ISA= qw( RT::Record );
+use base 'RT::Record';
 
 sub _Init {
   my $self = shift; 
@@ -61,7 +82,7 @@ sub _Init {
 
 
 
-=item Create PARAMHASH
+=head2 Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -104,7 +125,7 @@ sub Create {
 
 
 
-=item id
+=head2 id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -113,14 +134,14 @@ Returns the current value of id.
 =cut
 
 
-=item PrincipalType
+=head2 PrincipalType
 
 Returns the current value of PrincipalType. 
 (In the database, PrincipalType is stored as varchar(25).)
 
 
 
-=item SetPrincipalType VALUE
+=head2 SetPrincipalType VALUE
 
 
 Set PrincipalType to VALUE. 
@@ -131,14 +152,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item PrincipalId
+=head2 PrincipalId
 
 Returns the current value of PrincipalId. 
 (In the database, PrincipalId is stored as int(11).)
 
 
 
-=item SetPrincipalId VALUE
+=head2 SetPrincipalId VALUE
 
 
 Set PrincipalId to VALUE. 
@@ -149,14 +170,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item RightName
+=head2 RightName
 
 Returns the current value of RightName. 
 (In the database, RightName is stored as varchar(25).)
 
 
 
-=item SetRightName VALUE
+=head2 SetRightName VALUE
 
 
 Set RightName to VALUE. 
@@ -167,14 +188,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item ObjectType
+=head2 ObjectType
 
 Returns the current value of ObjectType. 
 (In the database, ObjectType is stored as varchar(25).)
 
 
 
-=item SetObjectType VALUE
+=head2 SetObjectType VALUE
 
 
 Set ObjectType to VALUE. 
@@ -185,14 +206,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item ObjectId
+=head2 ObjectId
 
 Returns the current value of ObjectId. 
 (In the database, ObjectId is stored as int(11).)
 
 
 
-=item SetObjectId VALUE
+=head2 SetObjectId VALUE
 
 
 Set ObjectId to VALUE. 
@@ -203,14 +224,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item DelegatedBy
+=head2 DelegatedBy
 
 Returns the current value of DelegatedBy. 
 (In the database, DelegatedBy is stored as int(11).)
 
 
 
-=item SetDelegatedBy VALUE
+=head2 SetDelegatedBy VALUE
 
 
 Set DelegatedBy to VALUE. 
@@ -221,14 +242,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item DelegatedFrom
+=head2 DelegatedFrom
 
 Returns the current value of DelegatedFrom. 
 (In the database, DelegatedFrom is stored as int(11).)
 
 
 
-=item SetDelegatedFrom VALUE
+=head2 SetDelegatedFrom VALUE
 
 
 Set DelegatedFrom to VALUE. 
@@ -240,25 +261,25 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 
 
 
-sub _ClassAccessible {
+sub _CoreAccessible {
     {
      
         id =>
-               {read => 1, type => 'int(11)', default => ''},
+               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
         PrincipalType => 
-               {read => 1, write => 1, type => 'varchar(25)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 25,  is_blob => 0,  is_numeric => 0,  type => 'varchar(25)', default => ''},
         PrincipalId => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         RightName => 
-               {read => 1, write => 1, type => 'varchar(25)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 25,  is_blob => 0,  is_numeric => 0,  type => 'varchar(25)', default => ''},
         ObjectType => 
-               {read => 1, write => 1, type => 'varchar(25)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 25,  is_blob => 0,  is_numeric => 0,  type => 'varchar(25)', default => ''},
         ObjectId => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         DelegatedBy => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         DelegatedFrom => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
 
  }
 };
@@ -290,7 +311,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index 81f59c6..1dc66e8 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -47,12 +72,9 @@ use strict;
 
 package RT::ACL;
 
-use RT::SearchBuilder;
+use base 'RT::SearchBuilder';
 use RT::ACE;
 
-use vars qw( @ISA );
-@ISA= qw(RT::SearchBuilder);
-
 
 sub _Init {
     my $self = shift;
@@ -64,7 +86,7 @@ sub _Init {
 }
 
 
-=item NewItem
+=head2 NewItem
 
 Returns an empty new RT::ACE item
 
@@ -101,7 +123,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index 81f7bdd..3734d81 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 package RT::Action::Autoreply;
-require RT::Action::SendEmail;
 
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::SendEmail);
+use warnings;
+
+use base qw(RT::Action::SendEmail);
 
+=head2 Prepare
+
+Set up the relevant recipients, then call our parent.
+
+=cut
+
+
+sub Prepare {
+    my $self = shift;
+    $self->SetRecipients();
+    $self->SUPER::Prepare();
+}
 
 # {{{ sub SetRecipients
 
@@ -59,35 +96,37 @@ Set this message\'s return address to the apropriate queue address
 
 sub SetReturnAddress {
     my $self = shift;
-    my %args = ( is_comment => 0,
-                @_
-              );
     
-    my $replyto;
-    if ($args{'is_comment'}) { 
-       $replyto = $self->TicketObj->QueueObj->CommentAddress || 
-                    $RT::CommentAddress;
-    }
-    else {
-       $replyto = $self->TicketObj->QueueObj->CorrespondAddress ||
-                    $RT::CorrespondAddress;
-    }
-    
-    unless ($self->TemplateObj->MIMEObj->head->get('From')) {
-       my $friendly_name = $self->TicketObj->QueueObj->Description ||
-               $self->TicketObj->QueueObj->Name;
-       $friendly_name =~ s/"/\\"/g;
-       $self->SetHeader('From', "\"$friendly_name\" <$replyto>");
-    }
-    
-    unless ($self->TemplateObj->MIMEObj->head->get('Reply-To')) {
-       $self->SetHeader('Reply-To', "$replyto");
-    }
+    my $friendly_name;
+
+       if (RT->Config->Get('UseFriendlyFromLine')) {
+           $friendly_name = $self->TicketObj->QueueObj->Description ||
+                   $self->TicketObj->QueueObj->Name;
+       }
+
+    $self->SUPER::SetReturnAddress( @_, friendly_name => $friendly_name );
     
 }
   
 # }}}
 
+# {{{{ sub SetRTSpecialHeaders
+
+=head2 SetRTSpecialHeaders
+
+Set the C<Auto-Generated> header to C<auto-replied>, in accordance
+with RFC3834.
+
+=cut
+
+sub SetRTSpecialHeaders {
+    my $self = shift;
+    $self->SUPER::SetRTSpecialHeaders(@_);
+    $self->SetHeader( 'Auto-Submitted', 'auto-replied' );
+}
+
+# }}}
+
 eval "require RT::Action::Autoreply_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Autoreply_Vendor.pm});
 eval "require RT::Action::Autoreply_Local";
index 007d299..5e8ef32 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
 # 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
 # 
-# END LICENSE BLOCK
+# END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
-  RT::Action::Generic - a generic baseclass for RT Actions
+  RT::Action::Generic - deprecated, see RT::Action
 
 =head1 SYNOPSIS
 
 
 =head1 DESCRIPTION
 
-=head1 METHODS
-
-=begin testing
+This module is provided only for backwards compatibility.
 
-ok (require RT::Action::Generic);
+=head1 METHODS
 
-=end testing
 
 =cut
 
-package RT::Action::Generic;
-
 use strict;
-
-# {{{ sub new 
-sub new  {
-  my $proto = shift;
-  my $class = ref($proto) || $proto;
-  my $self  = {};
-  bless ($self, $class);
-  $self->_Init(@_);
-  return $self;
-}
-# }}}
-
-# {{{ sub new 
-sub loc {
-    my $self = shift;
-    return $self->{'ScripObj'}->loc(@_);
-}
-# }}}
-
-# {{{ sub _Init 
-sub _Init  {
-  my $self = shift;
-  my %args = ( TransactionObj => undef,
-              TicketObj => undef,
-              ScripObj => undef,
-              TemplateObj => undef,
-              Argument => undef,
-              Type => undef,
-              @_ );
-  
-  
-  $self->{'Argument'} = $args{'Argument'};
-  $self->{'ScripObj'} = $args{'ScripObj'};
-  $self->{'TicketObj'} = $args{'TicketObj'};
-  $self->{'TransactionObj'} = $args{'TransactionObj'};
-  $self->{'TemplateObj'} = $args{'TemplateObj'};
-  $self->{'Type'} = $args{'Type'};
-}
-# }}}
-
-# Access Scripwide data
-
-# {{{ sub Argument 
-sub Argument  {
-  my $self = shift;
-  return($self->{'Argument'});
-}
-# }}}
-
-# {{{ sub TicketObj
-sub TicketObj  {
-  my $self = shift;
-  return($self->{'TicketObj'});
-}
-# }}}
-
-# {{{ sub TransactionObj
-sub TransactionObj  {
-  my $self = shift;
-  return($self->{'TransactionObj'});
-}
-# }}}
-
-# {{{ sub TemplateObj
-sub TemplateObj  {
-  my $self = shift;
-  return($self->{'TemplateObj'});
-}
-# }}}
-
-# {{{ sub ScripObj
-sub ScripObj  {
-  my $self = shift;
-  return($self->{'ScripObj'});
-}
-# }}}
-
-# {{{ sub Type
-sub Type  {
-  my $self = shift;
-  return($self->{'Type'});
-}
-# }}}
-
-
-# Scrip methods
-
-#Do what we need to do and send it out.
-
-# {{{ sub Commit 
-sub Commit  {
-  my $self = shift;
-  return(0, $self->loc("Commit Stubbed"));
-}
-# }}}
-
-
-#What does this type of Action does
-
-# {{{ sub Describe 
-sub Describe  {
-  my $self = shift;
-  return $self->loc("No description for [_1]", ref $self);
-}
-# }}}
-
-
-#Parse the templates, get things ready to go.
-
-# {{{ sub Prepare 
-sub Prepare  {
-  my $self = shift;
-  return (0, $self->loc("Prepare Stubbed"));
-}
-# }}}
-
-
-#If this rule applies to this transaction, return true.
-
-# {{{ sub IsApplicable 
-sub IsApplicable  {
-  my $self = shift;
-  return(undef);
-}
-# }}}
-
-# {{{ sub DESTROY
-sub DESTROY {
-    my $self = shift;
-
-    # We need to clean up all the references that might maybe get
-    # oddly circular
-    $self->{'TemplateObj'} =undef
-    $self->{'TicketObj'} = undef;
-    $self->{'TransactionObj'} = undef;
-    $self->{'ScripObj'} = undef;
-
-
-     
-}
-
-# }}}
+use warnings;
+package RT::Action::Generic;
+use base 'RT::Action';
 
 eval "require RT::Action::Generic_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Generic_Vendor.pm});
+warn "RT::Action::Generic has become RT::Action. Please adjust your deprecated RT::Action::Generic_Vendor file at " . $INC{"RT/Action/Generic_Vendor.pm"} if !$@;
+
 eval "require RT::Action::Generic_Local";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Generic_Local.pm});
+warn "RT::Action::Generic has become RT::Action. Please adjust your deprecated RT::Action::Generic_Local file at " . $INC{"RT/Action/Generic_Local.pm"} if !$@;
 
 1;
+
index 1e4e4c0..3b782f3 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
 # 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
 # 
-# END LICENSE BLOCK
+# END BPS TAGGED BLOCK }}}
+
+#
 package RT::Action::Notify;
-require RT::Action::SendEmail;
 
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::SendEmail);
+use warnings;
+
+use base qw(RT::Action::SendEmail);
+
+use Email::Address;
+
+=head2 Prepare
+
+Set up the relevant recipients, then call our parent.
 
-# {{{ sub SetRecipients
+=cut
+
+
+sub Prepare {
+    my $self = shift;
+    $self->SetRecipients();
+    $self->SUPER::Prepare();
+}
 
 =head2 SetRecipients
 
@@ -40,89 +79,88 @@ Explicitly B<does not> notify the creator of the transaction by default
 sub SetRecipients {
     my $self = shift;
 
-    my $arg = $self->Argument;
+    my $ticket = $self->TicketObj;
 
+    my $arg = $self->Argument;
     $arg =~ s/\bAll\b/Owner,Requestor,AdminCc,Cc/;
 
     my ( @To, @PseudoTo, @Cc, @Bcc );
 
 
-    if ($arg =~ /\bOtherRecipients\b/) {
-        if ($self->TransactionObj->Attachments->First) {
-            push (@Cc, $self->TransactionObj->Attachments->First->GetHeader('RT-Send-Cc'));
-            push (@Bcc, $self->TransactionObj->Attachments->First->GetHeader('RT-Send-Bcc'));
+    if ( $arg =~ /\bOtherRecipients\b/ ) {
+        if ( my $attachment = $self->TransactionObj->Attachments->First ) {
+            push @Cc, map { $_->address } Email::Address->parse(
+                $attachment->GetHeader('RT-Send-Cc')
+            );
+            push @Bcc, map { $_->address } Email::Address->parse(
+                $attachment->GetHeader('RT-Send-Bcc')
+            );
         }
     }
 
     if ( $arg =~ /\bRequestor\b/ ) {
-        push ( @To, $self->TicketObj->Requestors->MemberEmailAddresses  );
+        push @To, $ticket->Requestors->MemberEmailAddresses;
     }
 
-    
-
     if ( $arg =~ /\bCc\b/ ) {
 
         #If we have a To, make the Ccs, Ccs, otherwise, promote them to To
         if (@To) {
-            push ( @Cc, $self->TicketObj->Cc->MemberEmailAddresses );
-            push ( @Cc, $self->TicketObj->QueueObj->Cc->MemberEmailAddresses  );
+            push ( @Cc, $ticket->Cc->MemberEmailAddresses );
+            push ( @Cc, $ticket->QueueObj->Cc->MemberEmailAddresses  );
         }
         else {
-            push ( @Cc, $self->TicketObj->Cc->MemberEmailAddresses  );
-            push ( @To, $self->TicketObj->QueueObj->Cc->MemberEmailAddresses  );
+            push ( @Cc, $ticket->Cc->MemberEmailAddresses  );
+            push ( @To, $ticket->QueueObj->Cc->MemberEmailAddresses  );
         }
     }
 
-    if ( ( $arg =~ /\bOwner\b/ )
-        && ( $self->TicketObj->OwnerObj->id != $RT::Nobody->id ) )
-    {
-
-        # If we're not sending to Ccs or requestors, 
+    if ( $arg =~ /\bOwner\b/ && $ticket->OwnerObj->id != $RT::Nobody->id ) {
+        # If we're not sending to Ccs or requestors,
         # then the Owner can be the To.
         if (@To) {
-            push ( @Bcc, $self->TicketObj->OwnerObj->EmailAddress );
+            push ( @Bcc, $ticket->OwnerObj->EmailAddress );
         }
         else {
-            push ( @To, $self->TicketObj->OwnerObj->EmailAddress );
+            push ( @To, $ticket->OwnerObj->EmailAddress );
         }
 
     }
 
     if ( $arg =~ /\bAdminCc\b/ ) {
-        push ( @Bcc, $self->TicketObj->AdminCc->MemberEmailAddresses  );
-        push ( @Bcc, $self->TicketObj->QueueObj->AdminCc->MemberEmailAddresses  );
+        push ( @Bcc, $ticket->AdminCc->MemberEmailAddresses  );
+        push ( @Bcc, $ticket->QueueObj->AdminCc->MemberEmailAddresses  );
     }
 
-    if ($RT::UseFriendlyToLine) {
+    if ( RT->Config->Get('UseFriendlyToLine') ) {
         unless (@To) {
-            push (
-               @PseudoTo,
-               sprintf($RT::FriendlyToLineFormat, $arg, $self->TicketObj->id),
-           );
+            push @PseudoTo,
+                sprintf RT->Config->Get('FriendlyToLineFormat'), $arg, $ticket->id;
         }
     }
 
-    my $creator = $self->TransactionObj->CreatorObj->EmailAddress();
+    my $creatorObj = $self->TransactionObj->CreatorObj;
+    my $creator = $creatorObj->EmailAddress();
 
     #Strip the sender out of the To, Cc and AdminCc and set the 
     # recipients fields used to build the message by the superclass.
     # unless a flag is set 
-    if ($RT::NotifyActor) {
+    my $TransactionCurrentUser = RT::CurrentUser->new;
+    $TransactionCurrentUser->LoadByName($creatorObj->Name);
+    if (RT->Config->Get('NotifyActor',$TransactionCurrentUser)) {
         @{ $self->{'To'} }  = @To;
         @{ $self->{'Cc'} }  = @Cc;
         @{ $self->{'Bcc'} } = @Bcc;
     }
     else {
-        @{ $self->{'To'} }  = grep ( !/^$creator$/, @To );
-        @{ $self->{'Cc'} }  = grep ( !/^$creator$/, @Cc );
-        @{ $self->{'Bcc'} } = grep ( !/^$creator$/, @Bcc );
+        @{ $self->{'To'} }  = grep ( lc $_ ne lc $creator, @To );
+        @{ $self->{'Cc'} }  = grep ( lc $_ ne lc $creator, @Cc );
+        @{ $self->{'Bcc'} } = grep ( lc $_ ne lc $creator, @Bcc );
     }
     @{ $self->{'PseudoTo'} } = @PseudoTo;
-    return (1);
 
-}
 
-# }}}
+}
 
 eval "require RT::Action::Notify_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Notify_Vendor.pm});
index 210e4ab..4380c86 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
 # 
-# END LICENSE BLOCK
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 package RT::Action::NotifyAsComment;
-require RT::Action::Notify;
 
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Notify);
+use warnings;
 
+use base qw(RT::Action::Notify);
 
 =head2 SetReturnAddress
 
-Tell SendEmail that this message should come out as a comment. 
+Tell SendEmail that this message should come out as a comment.
 Calls SUPER::SetReturnAddress.
 
 =cut
 
 sub SetReturnAddress {
-       my $self = shift;
-       
-       # Tell RT::Action::SendEmail that this should come 
-       # from the relevant comment email address.
-       $self->{'comment'} = 1;
-       
-       return($self->SUPER::SetReturnAddress(is_comment => 1));
+    my $self = shift;
+
+    # Tell RT::Action::SendEmail that this should come
+    # from the relevant comment email address.
+    $self->{'comment'} = 1;
+
+    return $self->SUPER::SetReturnAddress( @_, is_comment => 1 );
 }
 
 eval "require RT::Action::NotifyAsComment_Vendor";
@@ -52,4 +76,3 @@ eval "require RT::Action::NotifyAsComment_Local";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyAsComment_Local.pm});
 
 1;
-
index 02ff3a5..ff826cc 100644 (file)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license 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 Action will resolve all members of a resolved group ticket
 
 package RT::Action::ResolveMembers;
-require RT::Action::Generic;
+use base 'RT::Action';
 require RT::Links;
 
 use strict;
-use vars qw/@ISA/;
-@ISA=qw(RT::Action::Generic);
 
 #Do what we need to do and send it out.
 
index dac8fc8..8b682c1 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
 # 
-# END LICENSE BLOCK
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Portions Copyright 2000 Tobias Brox <tobix@cpan.org>
 
 package RT::Action::SendEmail;
-require RT::Action::Generic;
 
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Generic);
+use warnings;
 
-use MIME::Words qw(encode_mimeword);
+use base qw(RT::Action);
 
 use RT::EmailParser;
+use RT::Interface::Email;
+use Email::Address;
+our @EMAIL_RECIPIENT_HEADERS = qw(To Cc Bcc);
+
 
 =head1 NAME
 
@@ -42,273 +69,493 @@ RT::Action::AutoReply is a good example subclass.
 
 =head1 SYNOPSIS
 
-  require RT::Action::SendEmail;
-  @ISA  = qw(RT::Action::SendEmail);
-
+  use base 'RT::Action::SendEmail';
 
 =head1 DESCRIPTION
 
 Basically, you create another module RT::Action::YourAction which ISA
 RT::Action::SendEmail.
 
-If you want to set the recipients of the mail to something other than
-the addresses mentioned in the To, Cc, Bcc and headers in
-the template, you should subclass RT::Action::SendEmail and override
-either the SetRecipients method or the SetTo, SetCc, etc methods (see
-the comments for the SetRecipients sub).
+=head1 METHODS
 
+=head2 CleanSlate
 
-=begin testing
+Cleans class-wide options, like L</SquelchMailTo> or L</AttachTickets>.
 
-ok (require RT::Action::SendEmail);
+=cut
 
-=end testing
+sub CleanSlate {
+    my $self = shift;
+    $self->SquelchMailTo(undef);
+    $self->AttachTickets(undef);
+}
 
+=head2 Commit
 
-=head1 AUTHOR
+Sends the prepared message and writes outgoing record into DB if the feature is
+activated in the config.
 
-Jesse Vincent <jesse@bestpractical.com> and Tobias Brox <tobix@cpan.org>
+=cut
 
-=head1 SEE ALSO
+sub Commit {
+    my $self = shift;
 
-perl(1).
+    $self->DeferDigestRecipients() if RT->Config->Get('RecordOutgoingEmail');
+    my $message = $self->TemplateObj->MIMEObj;
+
+    my $orig_message;
+    if (   RT->Config->Get('RecordOutgoingEmail')
+        && RT->Config->Get('GnuPG')->{'Enable'} )
+    {
+
+        # it's hacky, but we should know if we're going to crypt things
+        my $attachment = $self->TransactionObj->Attachments->First;
+
+        my %crypt;
+        foreach my $argument (qw(Sign Encrypt)) {
+            if ( $attachment
+                && defined $attachment->GetHeader("X-RT-$argument") )
+            {
+                $crypt{$argument} = $attachment->GetHeader("X-RT-$argument");
+            } else {
+                $crypt{$argument} = $self->TicketObj->QueueObj->$argument();
+            }
+        }
+        if ( $crypt{'Sign'} || $crypt{'Encrypt'} ) {
+            $orig_message = $message->dup;
+        }
+    }
 
-=cut
+    my ($ret) = $self->SendMessage($message);
+    if ( $ret > 0 && RT->Config->Get('RecordOutgoingEmail') ) {
+        if ($orig_message) {
+            $message->attach(
+                Type        => 'application/x-rt-original-message',
+                Disposition => 'inline',
+                Data        => $orig_message->as_string,
+            );
+        }
+        $self->RecordOutgoingMailTransaction($message);
+        $self->RecordDeferredRecipients();
+    }
 
-# {{{ Scrip methods (_Init, Commit, Prepare, IsApplicable)
 
-# {{{ sub _Init
-# We use _Init from RT::Action
-# }}}
+    return ( abs $ret );
+}
 
-# {{{ sub Commit
-#Do what we need to do and send it out.
-sub Commit {
-    my $self = shift;
+=head2 Prepare
 
-    my $MIMEObj = $self->TemplateObj->MIMEObj;
-    my $msgid = $MIMEObj->head->get('Message-Id');
-    chomp $msgid;
-    $RT::Logger->info($msgid." #".$self->TicketObj->id."/".$self->TransactionObj->id." - Scrip ". $self->ScripObj->id ." ".$self->ScripObj->Description);
-    #send the email
+Builds an outgoing email we're going to send using scrip's template.
 
-        # Weed out any RT addresses. We really don't want to talk to ourselves!
-        @{$self->{'To'}} = RT::EmailParser::CullRTAddresses("", @{$self->{'To'}});
-        @{$self->{'Cc'}} = RT::EmailParser::CullRTAddresses("", @{$self->{'Cc'}});
-        @{$self->{'Bcc'}} = RT::EmailParser::CullRTAddresses("", @{$self->{'Bcc'}});
-    # If there are no recipients, don't try to send the message.
-    # If the transaction has content and has the header RT-Squelch-Replies-To
+=cut
 
-    if ( defined $self->TransactionObj->Attachments->First() ) {
+sub Prepare {
+    my $self = shift;
 
-        my $squelch = $self->TransactionObj->Attachments->First->GetHeader( 'RT-Squelch-Replies-To');
+    my ( $result, $message ) = $self->TemplateObj->Parse(
+        Argument       => $self->Argument,
+        TicketObj      => $self->TicketObj,
+        TransactionObj => $self->TransactionObj
+    );
+    if ( !$result ) {
+        return (undef);
+    }
 
-        if ($squelch) {
-            my @blacklist = split ( /,/, $squelch );
+    my $MIMEObj = $self->TemplateObj->MIMEObj;
 
-            # Cycle through the people we're sending to and pull out anyone on the
-            # system blacklist
+    # Header
+    $self->SetRTSpecialHeaders();
 
-            foreach my $person_to_yank (@blacklist) {
-                $person_to_yank =~ s/\s//g;
-                @{ $self->{'To'} } =
-                  grep ( !/^$person_to_yank$/, @{ $self->{'To'} } );
-                @{ $self->{'Cc'} } =
-                  grep ( !/^$person_to_yank$/, @{ $self->{'Cc'} } );
-                @{ $self->{'Bcc'} } =
-                  grep ( !/^$person_to_yank$/, @{ $self->{'Bcc'} } );
-            }
-        }
+    $self->RemoveInappropriateRecipients();
+
+    my %seen;
+    foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
+        @{ $self->{$type} }
+            = grep defined && length && !$seen{ lc $_ }++,
+            @{ $self->{$type} };
     }
 
     # Go add all the Tos, Ccs and Bccs that we need to to the message to
     # make it happy, but only if we actually have values in those arrays.
 
-    $self->SetHeader( 'To', join ( ',', @{ $self->{'To'} } ) )
-      if ( $self->{'To'} && @{ $self->{'To'} } );
-    $self->SetHeader( 'Cc', join ( ',', @{ $self->{'Cc'} } ) )
-      if ( $self->{'Cc'} && @{ $self->{'Cc'} } );
-    $self->SetHeader( 'Bcc', join ( ',', @{ $self->{'Bcc'} } ) )
-      if ( $self->{'Cc'} && @{ $self->{'Bcc'} } );
+# TODO: We should be pulling the recipients out of the template and shove them into To, Cc and Bcc
 
+    for my $header (@EMAIL_RECIPIENT_HEADERS) {
 
-    $self->SetHeader('MIME-Version', '1.0');
+    $self->SetHeader( $header, join( ', ', @{ $self->{$header} } ) )
+        if ( !$MIMEObj->head->get($header)
+        && $self->{$header}
+        && @{ $self->{$header} } );
+}
+    # PseudoTo (fake to headers) shouldn't get matched for message recipients.
+    # If we don't have any 'To' header (but do have other recipients), drop in
+    # the pseudo-to header.
+    $self->SetHeader( 'To', join( ', ', @{ $self->{'PseudoTo'} } ) )
+        if $self->{'PseudoTo'}
+            && @{ $self->{'PseudoTo'} }
+            && !$MIMEObj->head->get('To')
+            && ( $MIMEObj->head->get('Cc') or $MIMEObj->head->get('Bcc') );
+
+    # We should never have to set the MIME-Version header
+    $self->SetHeader( 'MIME-Version', '1.0' );
+
+    # fsck.com #5959: Since RT sends 8bit mail, we should say so.
+    $self->SetHeader( 'Content-Transfer-Encoding', '8bit' );
+
+    # For security reasons, we only send out textual mails.
+    foreach my $part ( grep !$_->is_multipart, $MIMEObj->parts_DFS ) {
+        my $type = $part->mime_type || 'text/plain';
+        $type = 'text/plain' unless RT::I18N::IsTextualContentType($type);
+        $part->head->mime_attr( "Content-Type"         => $type );
+        $part->head->mime_attr( "Content-Type.charset" => 'utf-8' );
+    }
 
-    # try to convert message body from utf-8 to $RT::EmailOutputEncoding
-    $self->SetHeader( 'Content-Type', 'text/plain; charset="utf-8"' );
+    RT::I18N::SetMIMEEntityToEncoding( $MIMEObj,
+        RT->Config->Get('EmailOutputEncoding'),
+        'mime_words_ok', );
 
-    RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, $RT::EmailOutputEncoding, 'mime_words_ok' );
-    $self->SetHeader( 'Content-Type', 'text/plain; charset="' . $RT::EmailOutputEncoding . '"' );
+    # Build up a MIME::Entity that looks like the original message.
+    $self->AddAttachments if ( $MIMEObj->head->get('RT-Attach-Message')
+                               && ( $MIMEObj->head->get('RT-Attach-Message') !~ /^(n|no|0|off|false)$/i ) );
+
+    $self->AddTickets;
+
+    my $attachment = $self->TransactionObj->Attachments->First;
+    if ($attachment
+        && !(
+               $attachment->GetHeader('X-RT-Encrypt')
+            || $self->TicketObj->QueueObj->Encrypt
+        )
+        )
+    {
+        $attachment->SetHeader( 'X-RT-Encrypt' => 1 )
+            if ( $attachment->GetHeader("X-RT-Incoming-Encryption") || '' ) eq
+            'Success';
+    }
 
+    return $result;
+}
 
-    # Build up a MIME::Entity that looks like the original message.
+=head2 To
 
-    my $do_attach = $self->TemplateObj->MIMEObj->head->get('RT-Attach-Message');
+Returns an array of L<Email::Address> objects containing all the To: recipients for this notification
 
-    if ($do_attach) {
-        $self->TemplateObj->MIMEObj->head->delete('RT-Attach-Message');
+=cut
 
-        my $attachments = RT::Attachments->new($RT::SystemUser);
-        $attachments->Limit( FIELD => 'TransactionId',
-                             VALUE => $self->TransactionObj->Id );
-        $attachments->OrderBy('id');
+sub To {
+    my $self = shift;
+    return ( $self->AddressesFromHeader('To') );
+}
 
-        my $transaction_content_obj = $self->TransactionObj->ContentObj;
+=head2 Cc
 
-        # attach any of this transaction's attachments
-        while ( my $attach = $attachments->Next ) {
+Returns an array of L<Email::Address> objects containing all the Cc: recipients for this notification
 
-            # Don't attach anything blank
-            next unless ( $attach->ContentLength );
+=cut
 
-            # We want to make sure that we don't include the attachment that's being sued as the "Content" of this message"
-            next
-              if (    $transaction_content_obj
-                   && $transaction_content_obj->Id == $attach->Id 
-                   && $transaction_content_obj->ContentType =~ qr{text/plain}i
-                );
-            $MIMEObj->make_multipart('mixed');
-            $MIMEObj->attach( Type => $attach->ContentType,
-                              Charset => $attach->OriginalEncoding,
-                              Data => $attach->OriginalContent,
-                              Filename => $self->MIMEEncodeString( $attach->Filename, $RT::EmailOutputEncoding ),
-                              Encoding    => '-SUGGEST');
-        }
+sub Cc {
+    my $self = shift;
+    return ( $self->AddressesFromHeader('Cc') );
+}
 
-    }
+=head2 Bcc
 
+Returns an array of L<Email::Address> objects containing all the Bcc: recipients for this notification
 
-    my $retval = $self->SendMessage($MIMEObj);
+=cut
 
+sub Bcc {
+    my $self = shift;
+    return ( $self->AddressesFromHeader('Bcc') );
 
-    return ($retval);
 }
 
-# }}}
+sub AddressesFromHeader {
+    my $self      = shift;
+    my $field     = shift;
+    my $header    = $self->TemplateObj->MIMEObj->head->get($field);
+    my @addresses = Email::Address->parse($header);
 
-# {{{ sub Prepare
+    return (@addresses);
+}
 
-sub Prepare {
-    my $self = shift;
+=head2 SendMessage MIMEObj
 
-    # This actually populates the MIME::Entity fields in the Template Object
+sends the message using RT's preferred API.
+TODO: Break this out to a separate module
 
-    unless ( $self->TemplateObj ) {
-        $RT::Logger->warning("No template object handed to $self\n");
-    }
+=cut
 
-    unless ( $self->TransactionObj ) {
-        $RT::Logger->warning("No transaction object handed to $self\n");
+sub SendMessage {
 
-    }
+    # DO NOT SHIFT @_ in this subroutine.  It breaks Hook::LexWrap's
+    # ability to pass @_ to a 'post' routine.
+    my ( $self, $MIMEObj ) = @_;
 
-    unless ( $self->TicketObj ) {
-        $RT::Logger->warning("No ticket object handed to $self\n");
+    my $msgid = $MIMEObj->head->get('Message-ID');
+    chomp $msgid;
+
+    $self->ScripActionObj->{_Message_ID}++;
+
+    $RT::Logger->info( $msgid . " #"
+            . $self->TicketObj->id . "/"
+            . $self->TransactionObj->id
+            . " - Scrip "
+            . ($self->ScripObj->id || '#rule'). " "
+            . ( $self->ScripObj->Description || '' ) );
+
+    my $status = RT::Interface::Email::SendEmail(
+        Entity      => $MIMEObj,
+        Ticket      => $self->TicketObj,
+        Transaction => $self->TransactionObj,
+    );
 
+     
+    return $status unless ($status > 0 || exists $self->{'Deferred'});
+
+    my $success = $msgid . " sent ";
+    foreach (@EMAIL_RECIPIENT_HEADERS) {
+        my $recipients = $MIMEObj->head->get($_);
+        $success .= " $_: " . $recipients if $recipients;
     }
 
-    my ( $result, $message ) = $self->TemplateObj->Parse(
-                                         Argument       => $self->Argument,
-                                         TicketObj      => $self->TicketObj,
-                                         TransactionObj => $self->TransactionObj
+    if( exists $self->{'Deferred'} ) {
+        for (qw(daily weekly susp)) {
+            $success .= "\nBatched email $_ for: ". join(", ", keys %{ $self->{'Deferred'}{ $_ } } )
+                if exists $self->{'Deferred'}{ $_ };
+        }
+    }
+
+    $success =~ s/\n//g;
+
+    $RT::Logger->info($success);
+
+    return (1);
+}
+
+=head2 AddAttachments
+
+Takes any attachments to this transaction and attaches them to the message
+we're building.
+
+=cut
+
+sub AddAttachments {
+    my $self = shift;
+
+    my $MIMEObj = $self->TemplateObj->MIMEObj;
+
+    $MIMEObj->head->delete('RT-Attach-Message');
+
+    my $attachments = RT::Attachments->new($RT::SystemUser);
+    $attachments->Limit(
+        FIELD => 'TransactionId',
+        VALUE => $self->TransactionObj->Id
     );
-    if ($result) {
-
-        # Header
-        $self->SetSubject();
-        $self->SetSubjectToken();
-        $self->SetRecipients();
-        $self->SetReturnAddress();
-        $self->SetRTSpecialHeaders();
-        if ($RT::EmailOutputEncoding) {
-
-            # l10n related header
-            $self->SetHeaderAsEncoding( 'Subject', $RT::EmailOutputEncoding );
+
+    # Don't attach anything blank
+    $attachments->LimitNotEmpty;
+    $attachments->OrderBy( FIELD => 'id' );
+
+    # We want to make sure that we don't include the attachment that's
+    # being used as the "Content" of this message" unless that attachment's
+    # content type is not like text/...
+    my $transaction_content_obj = $self->TransactionObj->ContentObj;
+
+    if (   $transaction_content_obj
+        && $transaction_content_obj->ContentType =~ m{text/}i )
+    {
+        # If this was part of a multipart/alternative, skip all of the kids
+        my $parent = $transaction_content_obj->ParentObj;
+        if ($parent and $parent->Id and $parent->ContentType eq "multipart/alternative") {
+            $attachments->Limit(
+                ENTRYAGGREGATOR => 'AND',
+                FIELD           => 'parent',
+                OPERATOR        => '!=',
+                VALUE           => $parent->Id,
+            );
+        } else {
+            $attachments->Limit(
+                ENTRYAGGREGATOR => 'AND',
+                FIELD           => 'id',
+                OPERATOR        => '!=',
+                VALUE           => $transaction_content_obj->Id,
+            );
         }
     }
 
-    return $result;
+    # attach any of this transaction's attachments
+    my $seen_attachment = 0;
+    while ( my $attach = $attachments->Next ) {
+        if ( !$seen_attachment ) {
+            $MIMEObj->make_multipart( 'mixed', Force => 1 );
+            $seen_attachment = 1;
+        }
+        $self->AddAttachment($attach);
+    }
+}
+
+=head2 AddAttachment $attachment
+
+Takes one attachment object of L<RT::Attachmment> class and attaches it to the message
+we're building.
+
+=cut
 
+sub AddAttachment {
+    my $self    = shift;
+    my $attach  = shift;
+    my $MIMEObj = shift || $self->TemplateObj->MIMEObj;
+
+    $MIMEObj->attach(
+        Type     => $attach->ContentType,
+        Charset  => $attach->OriginalEncoding,
+        Data     => $attach->OriginalContent,
+        Filename => $self->MIMEEncodeString( $attach->Filename ),
+        'RT-Attachment:' => $self->TicketObj->Id . "/"
+            . $self->TransactionObj->Id . "/"
+            . $attach->id,
+        Encoding => '-SUGGEST',
+    );
 }
 
-# }}}
+=head2 AttachTickets [@IDs]
 
-# }}}
+Returns or set list of ticket's IDs that should be attached to an outgoing message.
 
-# {{{ SendMessage
-=head2 SendMessage MIMEObj
+B<Note> this method works as a class method and setup things global, so you have to
+clean list by passing undef as argument.
 
-sends the message using RT's preferred API.
-TODO: Break this out to a seperate module
+=cut
+
+{
+    my $list = [];
+
+    sub AttachTickets {
+        my $self = shift;
+        $list = [ grep defined, @_ ] if @_;
+        return @$list;
+    }
+}
+
+=head2 AddTickets
+
+Attaches tickets to the current message, list of tickets' ids get from
+L</AttachTickets> method.
 
 =cut
 
-sub SendMessage {
+sub AddTickets {
     my $self = shift;
-    my $MIMEObj = shift;
+    $self->AddTicket($_) foreach $self->AttachTickets;
+    return;
+}
 
-    my $msgid = $MIMEObj->head->get('Message-Id');
+=head2 AddTicket $ID
 
+Attaches a ticket with ID to the message.
 
-    #If we don't have any recipients to send to, don't send a message;
-    unless (    $MIMEObj->head->get('To')
-             || $MIMEObj->head->get('Cc')
-             || $MIMEObj->head->get('Bcc') ) {
-        $RT::Logger->info($msgid.  " No recipients found. Not sending.\n");
-        return (1);
-    }
+Each ticket is attached as multipart entity and all its messages and attachments
+are attached as sub entities in order of creation, but only if transaction type
+is Create or Correspond.
 
-    # PseudoTo (fake to headers) shouldn't get matched for message recipients.
-    # If we don't have any 'To' header, drop in the pseudo-to header.
+=cut
 
-    $self->SetHeader( 'To', join ( ',', @{ $self->{'PseudoTo'} } ) )
-      if ( $self->{'PseudoTo'} && ( @{ $self->{'PseudoTo'} } )
-           and ( !$MIMEObj->head->get('To') ) );
-    if ( $RT::MailCommand eq 'sendmailpipe' ) {
-        eval {
-            open( MAIL, "|$RT::SendmailPath $RT::SendmailArguments" );
-            print MAIL $MIMEObj->as_string;
-            close(MAIL);
-          };
-          if ($@) {
-            $RT::Logger->crit($msgid.  "Could not send mail. -".$@ );
-        }
+sub AddTicket {
+    my $self = shift;
+    my $tid  = shift;
+
+    # XXX: we need a current user here, but who is current user?
+    my $attachs   = RT::Attachments->new($RT::SystemUser);
+    my $txn_alias = $attachs->TransactionAlias;
+    $attachs->Limit( ALIAS => $txn_alias, FIELD => 'Type', VALUE => 'Create' );
+    $attachs->Limit(
+        ALIAS => $txn_alias,
+        FIELD => 'Type',
+        VALUE => 'Correspond'
+    );
+    $attachs->LimitByTicket($tid);
+    $attachs->LimitNotEmpty;
+    $attachs->OrderBy( FIELD => 'Created' );
+
+    my $ticket_mime = MIME::Entity->build(
+        Type        => 'multipart/mixed',
+        Top         => 0,
+        Description => "ticket #$tid",
+    );
+    while ( my $attachment = $attachs->Next ) {
+        $self->AddAttachment( $attachment, $ticket_mime );
+    }
+    if ( $ticket_mime->parts ) {
+        my $email_mime = $self->TemplateObj->MIMEObj;
+        $email_mime->make_multipart;
+        $email_mime->add_part($ticket_mime);
     }
-    else {
-       my @mailer_args = ($RT::MailCommand);
-       local $ENV{MAILADDRESS};
+    return;
+}
 
-        if ( $RT::MailCommand eq 'sendmail' ) {
-           push @mailer_args, $RT::SendmailArguments;
-        }
-        elsif ( $RT::MailCommand eq 'smtp' ) {
-           $ENV{MAILADDRESS} = $RT::SMTPFrom || $MIMEObj->head->get('From');
-           push @mailer_args, (Server => $RT::SMTPServer);
-           push @mailer_args, (Debug => $RT::SMTPDebug);
-        }
-       else {
-           push @mailer_args, $RT::MailParams;
-       }
+=head2 RecordOutgoingMailTransaction MIMEObj
+
+Record a transaction in RT with this outgoing message for future record-keeping purposes
 
-        unless ( $MIMEObj->send( @mailer_args ) ) {
-            $RT::Logger->crit($msgid.  "Could not send mail." );
-            return (0);
+=cut
+
+sub RecordOutgoingMailTransaction {
+    my $self    = shift;
+    my $MIMEObj = shift;
+
+    my @parts = $MIMEObj->parts;
+    my @attachments;
+    my @keep;
+    foreach my $part (@parts) {
+        my $attach = $part->head->get('RT-Attachment');
+        if ($attach) {
+            $RT::Logger->debug(
+                "We found an attachment. we want to not record it.");
+            push @attachments, $attach;
+        } else {
+            $RT::Logger->debug("We found a part. we want to record it.");
+            push @keep, $part;
         }
     }
+    $MIMEObj->parts( \@keep );
+    foreach my $attachment (@attachments) {
+        $MIMEObj->head->add( 'RT-Attachment', $attachment );
+    }
 
+    RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, 'utf-8', 'mime_words_ok' );
 
-     my $success = ($msgid. " sent To: ".$MIMEObj->head->get('To') . " Cc: ".$MIMEObj->head->get('Cc') . " Bcc: ".$MIMEObj->head->get('Bcc'));
-    $success =~ s/\n//gi;
-    $RT::Logger->info($success);
+    my $transaction
+        = RT::Transaction->new( $self->TransactionObj->CurrentUser );
 
-    return (1);
-}
+# XXX: TODO -> Record attachments as references to things in the attachments table, maybe.
 
-# }}}
+    my $type;
+    if ( $self->TransactionObj->Type eq 'Comment' ) {
+        $type = 'CommentEmailRecord';
+    } else {
+        $type = 'EmailRecord';
+    }
 
-# {{{ Deal with message headers (Set* subs, designed for  easy overriding)
+    my $msgid = $MIMEObj->head->get('Message-ID');
+    chomp $msgid;
 
-# {{{ sub SetRTSpecialHeaders
+    my ( $id, $msg ) = $transaction->Create(
+        Ticket         => $self->TicketObj->Id,
+        Type           => $type,
+        Data           => $msgid,
+        MIMEObj        => $MIMEObj,
+        ActivateScrips => 0
+    );
+
+    if ($id) {
+        $self->{'OutgoingMailTransaction'} = $id;
+    } else {
+        $RT::Logger->warning(
+            "Could not record outgoing message transaction: $msg");
+    }
+    return $id;
+}
 
 =head2 SetRTSpecialHeaders 
 
@@ -320,85 +567,272 @@ that don't matter much to anybody else.
 sub SetRTSpecialHeaders {
     my $self = shift;
 
-    $self->SetReferences();
+    $self->SetSubject();
+    $self->SetSubjectToken();
+    $self->SetHeaderAsEncoding( 'Subject',
+        RT->Config->Get('EmailOutputEncoding') )
+        if ( RT->Config->Get('EmailOutputEncoding') );
+    $self->SetReturnAddress();
+    $self->SetReferencesHeaders();
+
+    unless ( $self->TemplateObj->MIMEObj->head->get('Message-ID') ) {
+
+        # Get Message-ID for this txn
+        my $msgid = "";
+        if ( my $msg = $self->TransactionObj->Message->First ) {
+            $msgid = $msg->GetHeader("RT-Message-ID")
+                || $msg->GetHeader("Message-ID");
+        }
 
-    $self->SetMessageID();
+        # If there is one, and we can parse it, then base our Message-ID on it
+        if (    $msgid
+            and $msgid
+            =~ s/<(rt-.*?-\d+-\d+)\.(\d+)-\d+-\d+\@\QRT->Config->Get('Organization')\E>$/
+                         "<$1." . $self->TicketObj->id
+                          . "-" . $self->ScripObj->id
+                          . "-" . $self->ScripActionObj->{_Message_ID}
+                          . "@" . RT->Config->Get('Organization') . ">"/eg
+            and $2 == $self->TicketObj->id
+            )
+        {
+            $self->SetHeader( "Message-ID" => $msgid );
+        } else {
+            $self->SetHeader(
+                'Message-ID' => RT::Interface::Email::GenMessageId(
+                    Ticket      => $self->TicketObj,
+                    Scrip       => $self->ScripObj,
+                    ScripAction => $self->ScripActionObj
+                ),
+            );
+        }
+    }
 
-    $self->SetPrecedence();
+    if (my $precedence = RT->Config->Get('DefaultMailPrecedence')
+        and !$self->TemplateObj->MIMEObj->head->get("Precedence")
+    ) {
+        $self->SetHeader( 'Precedence', $precedence );
+    }
 
-    $self->SetHeader( 'X-RT-Loop-Prevention', $RT::rtname );
+    $self->SetHeader( 'X-RT-Loop-Prevention', RT->Config->Get('rtname') );
     $self->SetHeader( 'RT-Ticket',
-                      $RT::rtname . " #" . $self->TicketObj->id() );
+        RT->Config->Get('rtname') . " #" . $self->TicketObj->id() );
     $self->SetHeader( 'Managed-by',
-                      "RT $RT::VERSION (http://www.bestpractical.com/rt/)" );
-
-    $self->SetHeader( 'RT-Originator',
-                      $self->TransactionObj->CreatorObj->EmailAddress );
-    return ();
+        "RT $RT::VERSION (http://www.bestpractical.com/rt/)" );
+
+# XXX, TODO: use /ShowUser/ShowUserEntry(or something like that) when it would be
+#            refactored into user's method.
+    if ( my $email = $self->TransactionObj->CreatorObj->EmailAddress
+         and RT->Config->Get('UseOriginatorHeader')
+    ) {
+        $self->SetHeader( 'RT-Originator', $email );
+    }
 
 }
 
-# {{{ sub SetReferences
 
-=head2 SetReferences 
-  
-  # This routine will set the References: and In-Reply-To headers,
-# autopopulating it with all the correspondence on this ticket so
-# far. This should make RT responses threadable.
+sub DeferDigestRecipients {
+    my $self = shift;
+    $RT::Logger->debug( "Calling SetRecipientDigests for transaction " . $self->TransactionObj . ", id " . $self->TransactionObj->id );
+
+    # The digest attribute will be an array of notifications that need to
+    # be sent for this transaction.  The array will have the following
+    # format for its objects.
+    # $digest_hash -> {daily|weekly|susp} -> address -> {To|Cc|Bcc}
+    #                                     -> sent -> {true|false}
+    # The "sent" flag will be used by the cron job to indicate that it has
+    # run on this transaction.
+    # In a perfect world we might move this hash construction to the
+    # extension module itself.
+    my $digest_hash = {};
+
+    foreach my $mailfield (@EMAIL_RECIPIENT_HEADERS) {
+        # If we have a "PseudoTo", the "To" contains it, so we don't need to access it
+        next if ( ( $self->{'PseudoTo'} && @{ $self->{'PseudoTo'} } ) && ( $mailfield eq 'To' ) );
+        $RT::Logger->debug( "Working on mailfield $mailfield; recipients are " . join( ',', @{ $self->{$mailfield} } ) );
+
+        # Store the 'daily digest' folk in an array.
+        my ( @send_now, @daily_digest, @weekly_digest, @suspended );
+
+        # Have to get the list of addresses directly from the MIME header
+        # at this point.
+        $RT::Logger->debug( $self->TemplateObj->MIMEObj->head->as_string );
+        foreach my $rcpt ( map { $_->address } $self->AddressesFromHeader($mailfield) ) {
+            next unless $rcpt;
+            my $user_obj = RT::User->new($RT::SystemUser);
+            $user_obj->LoadByEmail($rcpt);
+            if  ( ! $user_obj->id ) {
+                # If there's an email address in here without an associated
+                # RT user, pass it on through.
+                $RT::Logger->debug( "User $rcpt is not associated with an RT user object.  Send mail.");
+                push( @send_now, $rcpt );
+                next;
+            }
+
+            my $mailpref = RT->Config->Get( 'EmailFrequency', $user_obj ) || '';
+            $RT::Logger->debug( "Got user mail preference '$mailpref' for user $rcpt");
 
-=cut
+            if ( $mailpref =~ /daily/i ) { push( @daily_digest, $rcpt ) }
+            elsif ( $mailpref =~ /weekly/i ) { push( @weekly_digest, $rcpt ) }
+            elsif ( $mailpref =~ /suspend/i ) { push( @suspended, $rcpt ) }
+            else { push( @send_now, $rcpt ) }
+        }
 
-sub SetReferences {
+        # Reset the relevant mail field.
+        $RT::Logger->debug( "Removing deferred recipients from $mailfield: line");
+        if (@send_now) {
+            $self->SetHeader( $mailfield, join( ', ', @send_now ) );
+        } else {    # No recipients!  Remove the header.
+            $self->TemplateObj->MIMEObj->head->delete($mailfield);
+        }
+
+        # Push the deferred addresses into the appropriate field in
+        # our attribute hash, with the appropriate mail header.
+        $RT::Logger->debug(
+            "Setting deferred recipients for attribute creation");
+        $digest_hash->{'daily'}->{$_} = {'header' => $mailfield , _sent => 0}  for (@daily_digest);
+        $digest_hash->{'weekly'}->{$_} ={'header' =>  $mailfield, _sent => 0}  for (@weekly_digest);
+        $digest_hash->{'susp'}->{$_} = {'header' => $mailfield, _sent =>0 }  for (@suspended);
+    }
+
+    if ( scalar keys %$digest_hash ) {
+
+        # Save the hash so that we can add it as an attribute to the
+        # outgoing email transaction.
+        $self->{'Deferred'} = $digest_hash;
+    } else {
+        $RT::Logger->debug( "No recipients found for deferred delivery on "
+                . "transaction #"
+                . $self->TransactionObj->id );
+    }
+}
+
+
+    
+sub RecordDeferredRecipients {
     my $self = shift;
+    return unless exists $self->{'Deferred'};
 
-    # TODO: this one is broken.  What is this email really a reply to?
-    # If it's a reply to an incoming message, we'll need to use the
-    # actual message-id from the appropriate Attachment object.  For
-    # incoming mails, we would like to preserve the In-Reply-To and/or
-    # References.
+    my $txn_id = $self->{'OutgoingMailTransaction'};
+    return unless $txn_id;
 
-    $self->SetHeader( 'In-Reply-To',
-                   "<rt-" . $self->TicketObj->id() . "\@" . $RT::rtname . ">" );
+    my $txn_obj = RT::Transaction->new( $self->CurrentUser );
+    $txn_obj->Load( $txn_id );
+    my( $ret, $msg ) = $txn_obj->AddAttribute(
+        Name => 'DeferredRecipients',
+        Content => $self->{'Deferred'}
+    );
+    $RT::Logger->warning( "Unable to add deferred recipients to outgoing transaction: $msg" ) 
+        unless $ret;
 
-    # TODO We should always add References headers for all message-ids
-    # of previous messages related to this ticket.
+    return ($ret,$msg);
 }
 
-# }}}
+=head2 SquelchMailTo [@ADDRESSES]
+
+Mark ADDRESSES to be removed from list of the recipients. Returns list of the addresses.
+To empty list pass undefined argument.
+
+B<Note> that this method can be called as class method and works globaly. Don't forget to
+clean this list when blocking is not required anymore, pass undef to do this.
+
+=cut
 
-# {{{ sub SetMessageID
+{
+    my $squelch = [];
 
-=head2 SetMessageID 
+    sub SquelchMailTo {
+        my $self = shift;
+        if (@_) {
+            $squelch = [ grep defined, @_ ];
+        }
+        return @$squelch;
+    }
+}
 
-Without this one, threading won't work very nice in email agents.
-Anyway, I'm not really sure it's that healthy if we need to send
-several separate/different emails about the same transaction.
+=head2 RemoveInappropriateRecipients
+
+Remove addresses that are RT addresses or that are on this transaction's blacklist
 
 =cut
 
-sub SetMessageID {
+sub RemoveInappropriateRecipients {
     my $self = shift;
 
-    # TODO this one might be sort of broken.  If we have several scrips +++
-    # sending several emails to several different persons, we need to
-    # pull out different message-ids.  I'd suggest message ids like
-    # "rt-ticket#-transaction#-scrip#-receipient#"
-
-    $self->SetHeader( 'Message-ID',
-                      "<rt-"
-                        . $RT::VERSION ."-"
-                        . $self->TicketObj->id() . "-"
-                        . $self->TransactionObj->id() . "."
-                        . rand(20) . "\@"
-                        . $RT::Organization . ">" )
-      unless $self->TemplateObj->MIMEObj->head->get('Message-ID');
-}
+    my @blacklist = ();
+
+    # If there are no recipients, don't try to send the message.
+    # If the transaction has content and has the header RT-Squelch-Replies-To
+
+    my $msgid = $self->TemplateObj->MIMEObj->head->get('Message-Id');
+    if ( my $attachment = $self->TransactionObj->Attachments->First ) {
+
+        if ( $attachment->GetHeader('RT-DetectedAutoGenerated') ) {
+
+            # What do we want to do with this? It's probably (?) a bounce
+            # caused by one of the watcher addresses being broken.
+            # Default ("true") is to redistribute, for historical reasons.
+
+            if ( !RT->Config->Get('RedistributeAutoGeneratedMessages') ) {
+
+                # Don't send to any watchers.
+                @{ $self->{$_} } = () for (@EMAIL_RECIPIENT_HEADERS);
+                $RT::Logger->info( $msgid
+                        . " The incoming message was autogenerated. "
+                        . "Not redistributing this message based on site configuration."
+                );
+            } elsif ( RT->Config->Get('RedistributeAutoGeneratedMessages') eq
+                'privileged' )
+            {
+
+                # Only send to "privileged" watchers.
+                foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
+                    foreach my $addr ( @{ $self->{$type} } ) {
+                        my $user = RT::User->new($RT::SystemUser);
+                        $user->LoadByEmail($addr);
+                        push @blacklist, $addr if ( !$user->Privileged );
+                    }
+                }
+                $RT::Logger->info( $msgid
+                        . " The incoming message was autogenerated. "
+                        . "Not redistributing this message to unprivileged users based on site configuration."
+                );
+            }
+        }
+
+        if ( my $squelch = $attachment->GetHeader('RT-Squelch-Replies-To') ) {
+            push @blacklist, split( /,/, $squelch );
+        }
+    }
 
-# }}}
+# Let's grab the SquelchMailTo attribue and push those entries into the @blacklist
+    push @blacklist, map $_->Content, $self->TicketObj->SquelchMailTo;
+    push @blacklist, $self->SquelchMailTo;
 
-# }}}
+    # Cycle through the people we're sending to and pull out anyone on the
+    # system blacklist
 
-# {{{ sub SetReturnAddress
+    # Trim leading and trailing spaces. 
+    @blacklist = map { RT::User->CanonicalizeEmailAddress( $_->address ) } Email::Address->parse(join(', ', grep {defined} @blacklist));
+
+    foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
+        my @addrs;
+        foreach my $addr ( @{ $self->{$type} } ) {
+
+         # Weed out any RT addresses. We really don't want to talk to ourselves!
+         # If we get a reply back, that means it's not an RT address
+            if ( !RT::EmailParser->CullRTAddresses($addr) ) {
+                $RT::Logger->info( $msgid . "$addr appears to point to this RT instance. Skipping" );
+                next;
+            }
+            if ( grep /^\Q$addr\E$/, @blacklist ) {
+                $RT::Logger->info( $msgid . "$addr was blacklisted for outbound mail on this transaction. Skipping");
+                next;
+            }
+            push @addrs, $addr;
+        }
+        @{ $self->{$type} } = @addrs;
+    }
+}
 
 =head2 SetReturnAddress is_comment => BOOLEAN
 
@@ -409,8 +843,11 @@ Calculate and set From and Reply-To headers based on the is_comment flag.
 sub SetReturnAddress {
 
     my $self = shift;
-    my %args = ( is_comment => 0,
-                 @_ );
+    my %args = (
+        is_comment => 0,
+        friendly_name => undef,
+        @_
+    );
 
     # From and Reply-To
     # $args{is_comment} should be set if the comment address is to be used.
@@ -418,29 +855,37 @@ sub SetReturnAddress {
 
     if ( $args{'is_comment'} ) {
         $replyto = $self->TicketObj->QueueObj->CommentAddress
-          || $RT::CommentAddress;
-    }
-    else {
+            || RT->Config->Get('CommentAddress');
+    } else {
         $replyto = $self->TicketObj->QueueObj->CorrespondAddress
-          || $RT::CorrespondAddress;
+            || RT->Config->Get('CorrespondAddress');
     }
 
     unless ( $self->TemplateObj->MIMEObj->head->get('From') ) {
-       if ($RT::UseFriendlyFromLine) {
-           my $friendly_name = $self->TransactionObj->CreatorObj->RealName;
-           if ( $friendly_name =~ /^"(.*)"$/ ) {    # a quoted string
-               $friendly_name = $1;
-           }
-
-           $friendly_name =~ s/"/\\"/g;
-           $self->SetHeader( 'From',
-                       sprintf($RT::FriendlyFromLineFormat, 
-                $self->MIMEEncodeString( $friendly_name, $RT::EmailOutputEncoding ), $replyto),
-           );
-       }
-       else {
-           $self->SetHeader( 'From', $replyto );
-       }
+        if ( RT->Config->Get('UseFriendlyFromLine') ) {
+            my $friendly_name = $args{friendly_name};
+
+            unless ( $friendly_name ) {
+                $friendly_name = $self->TransactionObj->CreatorObj->FriendlyName;
+                if ( $friendly_name =~ /^"(.*)"$/ ) {    # a quoted string
+                    $friendly_name = $1;
+                }
+            }
+
+            $friendly_name =~ s/"/\\"/g;
+            $self->SetHeader(
+                'From',
+                sprintf(
+                    RT->Config->Get('FriendlyFromLineFormat'),
+                    $self->MIMEEncodeString(
+                        $friendly_name, RT->Config->Get('EmailOutputEncoding')
+                    ),
+                    $replyto
+                ),
+            );
+        } else {
+            $self->SetHeader( 'From', $replyto );
+        }
     }
 
     unless ( $self->TemplateObj->MIMEObj->head->get('Reply-To') ) {
@@ -449,10 +894,6 @@ sub SetReturnAddress {
 
 }
 
-# }}}
-
-# {{{ sub SetHeader
-
 =head2 SetHeader FIELD, VALUE
 
 Set the FIELD of the current MIME object into VALUE.
@@ -466,163 +907,151 @@ sub SetHeader {
 
     chomp $val;
     chomp $field;
-    $self->TemplateObj->MIMEObj->head->fold_length( $field, 10000 );
-    $self->TemplateObj->MIMEObj->head->replace( $field,     $val );
-    return $self->TemplateObj->MIMEObj->head->get($field);
+    my $head = $self->TemplateObj->MIMEObj->head;
+    $head->fold_length( $field, 10000 );
+    $head->replace( $field, $val );
+    return $head->get($field);
 }
 
-# }}}
-
-# {{{ sub SetRecipients
-
-=head2 SetRecipients
+=head2 SetSubject
 
-Dummy method to be overriden by subclasses which want to set the recipients.
+This routine sets the subject. it does not add the rt tag. That gets done elsewhere
+If subject is already defined via template, it uses that. otherwise, it tries to get
+the transaction's subject.
 
-=cut
+=cut 
 
-sub SetRecipients {
+sub SetSubject {
     my $self = shift;
-    return ();
-}
-
-# }}}
+    my $subject;
 
-# {{{ sub SetTo
+    if ( $self->TemplateObj->MIMEObj->head->get('Subject') ) {
+        return ();
+    }
 
-=head2 SetTo
+    my $message = $self->TransactionObj->Attachments;
+    $message->RowsPerPage(1);
+    if ( $self->{'Subject'} ) {
+        $subject = $self->{'Subject'};
+    } elsif ( my $first = $message->First ) {
+        my $tmp = $first->GetHeader('Subject');
+        $subject = defined $tmp ? $tmp : $self->TicketObj->Subject;
+    } else {
+        $subject = $self->TicketObj->Subject;
+    }
+    $subject = '' unless defined $subject;
+    chomp $subject;
 
-Takes a string that is the addresses you want to send mail to
+    $subject =~ s/(\r\n|\n|\s)/ /g;
 
-=cut
+    $self->SetHeader( 'Subject', $subject );
 
-sub SetTo {
-    my $self      = shift;
-    my $addresses = shift;
-    return $self->SetHeader( 'To', $addresses );
 }
 
-# }}}
-
-# {{{ sub SetCc
-
-=head2 SetCc
+=head2 SetSubjectToken
 
-Takes a string that is the addresses you want to Cc
+This routine fixes the RT tag in the subject. It's unlikely that you want to overwrite this.
 
 =cut
 
-sub SetCc {
-    my $self      = shift;
-    my $addresses = shift;
+sub SetSubjectToken {
+    my $self = shift;
 
-    return $self->SetHeader( 'Cc', $addresses );
+    my $head = $self->TemplateObj->MIMEObj->head;
+    $head->replace(
+        Subject => RT::Interface::Email::AddSubjectTag(
+            Encode::decode_utf8( $head->get('Subject') ),
+            $self->TicketObj,
+        ),
+    );
 }
 
-# }}}
-
-# {{{ sub SetBcc
+=head2 SetReferencesHeaders
 
-=head2 SetBcc
-
-Takes a string that is the addresses you want to Bcc
+Set References and In-Reply-To headers for this message.
 
 =cut
 
-sub SetBcc {
-    my $self      = shift;
-    my $addresses = shift;
-
-    return $self->SetHeader( 'Bcc', $addresses );
-}
-
-# }}}
-
-# {{{ sub SetPrecedence
-
-sub SetPrecedence {
+sub SetReferencesHeaders {
     my $self = shift;
-
-    unless ( $self->TemplateObj->MIMEObj->head->get("Precedence") ) {
-        $self->SetHeader( 'Precedence', "bulk" );
+    my ( @in_reply_to, @references, @msgid );
+
+    if ( my $top = $self->TransactionObj->Message->First ) {
+        @in_reply_to = split( /\s+/m, $top->GetHeader('In-Reply-To') || '' );
+        @references  = split( /\s+/m, $top->GetHeader('References')  || '' );
+        @msgid       = split( /\s+/m, $top->GetHeader('Message-ID')  || '' );
+    } else {
+        return (undef);
     }
-}
-
-# }}}
 
-# {{{ sub SetSubject
+    # There are two main cases -- this transaction was created with
+    # the RT Web UI, and hence we want to *not* append its Message-ID
+    # to the References and In-Reply-To.  OR it came from an outside
+    # source, and we should treat it as per the RFC
+    my $org = RT->Config->Get('Organization');
+    if ( "@msgid" =~ /<(rt-.*?-\d+-\d+)\.(\d+)-0-0\@\Q$org\E>/ ) {
+
+        # Make all references which are internal be to version which we
+        # have sent out
+
+        for ( @references, @in_reply_to ) {
+            s/<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@\Q$org\E>$/
+          "<$1." . $self->TicketObj->id .
+             "-" . $self->ScripObj->id .
+             "-" . $self->ScripActionObj->{_Message_ID} .
+             "@" . $org . ">"/eg
+        }
 
-=head2 SetSubject
+        # In reply to whatever the internal message was in reply to
+        $self->SetHeader( 'In-Reply-To', join( " ", (@in_reply_to) ) );
 
-This routine sets the subject. it does not add the rt tag. that gets done elsewhere
-If $self->{'Subject'} is already defined, it uses that. otherwise, it tries to get
-the transaction's subject.
+        # Default the references to whatever we're in reply to
+        @references = @in_reply_to unless @references;
 
-=cut 
+        # References are unchanged from internal
+    } else {
 
-sub SetSubject {
-    my $self = shift;
-    my $subject;
+        # In reply to that message
+        $self->SetHeader( 'In-Reply-To', join( " ", (@msgid) ) );
 
-    unless ( $self->TemplateObj->MIMEObj->head->get('Subject') ) {
-        my $message = $self->TransactionObj->Attachments;
-        my $ticket  = $self->TicketObj->Id;
+        # Default the references to whatever we're in reply to
+        @references = @in_reply_to unless @references;
 
-        if ( $self->{'Subject'} ) {
-            $subject = $self->{'Subject'};
-        }
-        elsif (    ( $message->First() )
-                && ( $message->First->Headers ) ) {
-            my $header = $message->First->Headers();
-            $header =~ s/\n\s+/ /g;
-            if ( $header =~ /^Subject: (.*?)$/m ) {
-                $subject = $1;
-            }
-            else {
-                $subject = $self->TicketObj->Subject();
-            }
+        # Push that message onto the end of the references
+        push @references, @msgid;
+    }
 
-        }
-        else {
-            $subject = $self->TicketObj->Subject();
-        }
+    # Push pseudo-ref to the front
+    my $pseudo_ref = $self->PseudoReference;
+    @references = ( $pseudo_ref, grep { $_ ne $pseudo_ref } @references );
 
-        $subject =~ s/(\r\n|\n|\s)/ /gi;
+    # If there are more than 10 references headers, remove all but the
+    # first four and the last six (Gotta keep this from growing
+    # forever)
+    splice( @references, 4, -6 ) if ( $#references >= 10 );
 
-        chomp $subject;
-        $self->SetHeader( 'Subject', $subject );
+    # Add on the references
+    $self->SetHeader( 'References', join( " ", @references ) );
+    $self->TemplateObj->MIMEObj->head->fold_length( 'References', 80 );
 
-    }
-    return ($subject);
 }
 
-# }}}
-
-# {{{ sub SetSubjectToken
+=head2 PseudoReference
 
-=head2 SetSubjectToken
-
-This routine fixes the RT tag in the subject. It's unlikely that you want to overwrite this.
+Returns a fake Message-ID: header for the ticket to allow a base level of threading
 
 =cut
 
-sub SetSubjectToken {
+sub PseudoReference {
+
     my $self = shift;
-    my $tag  = "[$RT::rtname #" . $self->TicketObj->id . "]";
-    my $sub  = $self->TemplateObj->MIMEObj->head->get('Subject');
-    unless ( $sub =~ /\Q$tag\E/ ) {
-        $sub =~ s/(\r\n|\n|\s)/ /gi;
-        chomp $sub;
-        $self->TemplateObj->MIMEObj->head->replace( 'Subject', "$tag $sub" );
-    }
+    my $pseudo_ref
+        = '<RT-Ticket-'
+        . $self->TicketObj->id . '@'
+        . RT->Config->Get('Organization') . '>';
+    return $pseudo_ref;
 }
 
-# }}}
-
-# }}}
-
-# {{{
-
 =head2 SetHeaderAsEncoding($field_name, $charset_encoding)
 
 This routine converts the field into specified charset encoding.
@@ -633,53 +1062,37 @@ sub SetHeaderAsEncoding {
     my $self = shift;
     my ( $field, $enc ) = ( shift, shift );
 
-    if ($field eq 'From' and $RT::SMTPFrom) {
-        $self->TemplateObj->MIMEObj->head->replace( $field, $RT::SMTPFrom );
-       return;
-    }
-
-    my $value = $self->TemplateObj->MIMEObj->head->get($field);
-
-    # don't bother if it's us-ascii
+    my $head = $self->TemplateObj->MIMEObj->head;
 
-    # See RT::I18N, 'NOTES:  Why Encode::_utf8_off before Encode::from_to'
-
-    $value =  $self->MIMEEncodeString($value, $enc);
-
-    $self->TemplateObj->MIMEObj->head->replace( $field, $value );
+    if ( lc($field) eq 'from' and RT->Config->Get('SMTPFrom') ) {
+        $head->replace( $field, RT->Config->Get('SMTPFrom') );
+        return;
+    }
 
+    my $value = $head->get( $field );
+    $value = $self->MIMEEncodeString( $value, $enc );
+    $head->replace( $field, $value );
 
-} 
-# }}}
+}
 
-# {{{ MIMENcodeString
+=head2 MIMEEncodeString
 
-=head2 MIMEEncodeString STRING ENCODING
+Takes a perl string and optional encoding pass it over
+L<RT::Interface::Email/EncodeToMIME>.
 
-Takes a string and a possible encoding and returns the string wrapped in MIME goo.
+Basicly encode a string using B encoding according to RFC2047.
 
 =cut
 
 sub MIMEEncodeString {
-    my  $self = shift;
-    my $value = shift;
-    my $enc = shift;
-
-    chomp $value;
-    return ($value) unless $value =~ /[^\x20-\x7e]/;
-
-    $value =~ s/\s*$//;
-    Encode::_utf8_off($value);
-    my $res = Encode::from_to( $value, "utf-8", $enc );
-    $value = encode_mimeword( $value,  'B', $enc );
+    my $self  = shift;
+    return RT::Interface::Email::EncodeToMIME( String => $_[0], Charset => $_[1] );
 }
 
-# }}}
-
 eval "require RT::Action::SendEmail_Vendor";
-die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Vendor.pm});
+die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Vendor.pm} );
 eval "require RT::Action::SendEmail_Local";
-die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Local.pm});
+die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Local.pm} );
 
 1;
 
index 2ed5201..4327238 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -61,7 +86,7 @@ sub _Init {
 
 
 
-=item Create PARAMHASH
+=head2 Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -110,7 +135,7 @@ sub Create {
 
 
 
-=item id
+=head2 id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -119,14 +144,14 @@ Returns the current value of id.
 =cut
 
 
-=item TransactionId
+=head2 TransactionId
 
 Returns the current value of TransactionId. 
 (In the database, TransactionId is stored as int(11).)
 
 
 
-=item SetTransactionId VALUE
+=head2 SetTransactionId VALUE
 
 
 Set TransactionId to VALUE. 
@@ -137,14 +162,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Parent
+=head2 Parent
 
 Returns the current value of Parent. 
 (In the database, Parent is stored as int(11).)
 
 
 
-=item SetParent VALUE
+=head2 SetParent VALUE
 
 
 Set Parent to VALUE. 
@@ -155,14 +180,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item MessageId
+=head2 MessageId
 
 Returns the current value of MessageId. 
 (In the database, MessageId is stored as varchar(160).)
 
 
 
-=item SetMessageId VALUE
+=head2 SetMessageId VALUE
 
 
 Set MessageId to VALUE. 
@@ -173,14 +198,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Subject
+=head2 Subject
 
 Returns the current value of Subject. 
 (In the database, Subject is stored as varchar(255).)
 
 
 
-=item SetSubject VALUE
+=head2 SetSubject VALUE
 
 
 Set Subject to VALUE. 
@@ -191,14 +216,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Filename
+=head2 Filename
 
 Returns the current value of Filename. 
 (In the database, Filename is stored as varchar(255).)
 
 
 
-=item SetFilename VALUE
+=head2 SetFilename VALUE
 
 
 Set Filename to VALUE. 
@@ -209,14 +234,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item ContentType
+=head2 ContentType
 
 Returns the current value of ContentType. 
 (In the database, ContentType is stored as varchar(80).)
 
 
 
-=item SetContentType VALUE
+=head2 SetContentType VALUE
 
 
 Set ContentType to VALUE. 
@@ -227,14 +252,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item ContentEncoding
+=head2 ContentEncoding
 
 Returns the current value of ContentEncoding. 
 (In the database, ContentEncoding is stored as varchar(80).)
 
 
 
-=item SetContentEncoding VALUE
+=head2 SetContentEncoding VALUE
 
 
 Set ContentEncoding to VALUE. 
@@ -245,14 +270,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Content
+=head2 Content
 
 Returns the current value of Content. 
 (In the database, Content is stored as longtext.)
 
 
 
-=item SetContent VALUE
+=head2 SetContent VALUE
 
 
 Set Content to VALUE. 
@@ -263,14 +288,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Headers
+=head2 Headers
 
 Returns the current value of Headers. 
 (In the database, Headers is stored as longtext.)
 
 
 
-=item SetHeaders VALUE
+=head2 SetHeaders VALUE
 
 
 Set Headers to VALUE. 
@@ -281,7 +306,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Creator
+=head2 Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -290,7 +315,7 @@ Returns the current value of Creator.
 =cut
 
 
-=item Created
+=head2 Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -300,33 +325,33 @@ Returns the current value of Created.
 
 
 
-sub _ClassAccessible {
+sub _CoreAccessible {
     {
      
         id =>
-               {read => 1, type => 'int(11)', default => ''},
+               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
         TransactionId => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Parent => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         MessageId => 
-               {read => 1, write => 1, type => 'varchar(160)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 160,  is_blob => 0,  is_numeric => 0,  type => 'varchar(160)', default => ''},
         Subject => 
-               {read => 1, write => 1, type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
         Filename => 
-               {read => 1, write => 1, type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
         ContentType => 
-               {read => 1, write => 1, type => 'varchar(80)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 80,  is_blob => 0,  is_numeric => 0,  type => 'varchar(80)', default => ''},
         ContentEncoding => 
-               {read => 1, write => 1, type => 'varchar(80)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 80,  is_blob => 0,  is_numeric => 0,  type => 'varchar(80)', default => ''},
         Content => 
-               {read => 1, write => 1, type => 'longtext', default => ''},
+               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'longtext', default => ''},
         Headers => 
-               {read => 1, write => 1, type => 'longtext', default => ''},
+               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'longtext', default => ''},
         Creator => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
 
  }
 };
@@ -358,7 +383,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index 177cdd0..416cde6 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -64,7 +89,7 @@ sub _Init {
 }
 
 
-=item NewItem
+=head2 NewItem
 
 Returns an empty new RT::Attachment item
 
@@ -101,7 +126,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index 4519fcf..1b90aa5 100644 (file)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
 # 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
 # 
-# END LICENSE BLOCK
+# END BPS TAGGED BLOCK }}}
 
 package RT::Condition::AnyTransaction;
-require RT::Condition::Generic;
+use base 'RT::Condition';
 
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Condition::Generic);
 
 
 =head2 IsApplicable
index bd26931..08baeda 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
-  RT::Condition::Generic - ;
+  RT::Condition::Generic - deprecated, see RT::Condition
 
 =head1 SYNOPSIS
 
-    use RT::Condition::Generic;
-    my $foo = new RT::Condition::IsApplicable( 
-               TransactionObj => $tr, 
-               TicketObj => $ti, 
-               ScripObj => $scr, 
-               Argument => $arg, 
-               Type => $type);
-
-    if ($foo->IsApplicable) {
-          # do something
-    }
-
+  use RT::Condition::Generic;
 
 =head1 DESCRIPTION
 
+This module is provided only for backwards compatibility.
 
 =head1 METHODS
 
 
-=begin testing
-
-ok (require RT::Condition::Generic);
-
-=end testing
-
-
 =cut
 
-package RT::Condition::Generic;
-
-use RT::Base;
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Base);
-
-# {{{ sub new 
-sub new  {
-  my $proto = shift;
-  my $class = ref($proto) || $proto;
-  my $self  = {};
-  bless ($self, $class);
-  $self->_Init(@_);
-  return $self;
-}
-# }}}
-
-# {{{ sub _Init 
-sub _Init  {
-  my $self = shift;
-  my %args = ( TransactionObj => undef,
-              TicketObj => undef,
-              ScripObj => undef,
-              TemplateObj => undef,
-              Argument => undef,
-              ApplicableTransTypes => undef,
-              @_ );
-  
-  $self->{'Argument'} = $args{'Argument'};
-  $self->{'ScripObj'} = $args{'ScripObj'};
-  $self->{'TicketObj'} = $args{'TicketObj'};
-  $self->{'TransactionObj'} = $args{'TransactionObj'};
-  $self->{'ApplicableTransTypes'} = $args{'ApplicableTransTypes'};
-}
-# }}}
-
-# Access Scripwide data
-
-# {{{ sub Argument 
-
-=head2 Argument
-
-Return the optional argument associated with this ScripCondition
-
-=cut
-
-sub Argument  {
-  my $self = shift;
-  return($self->{'Argument'});
-}
-# }}}
-
-# {{{ sub TicketObj
-
-=head2 TicketObj
-
-Return the ticket object we're talking about
-
-=cut
-
-sub TicketObj  {
-  my $self = shift;
-  return($self->{'TicketObj'});
-}
-# }}}
-
-# {{{ sub ScripObj
-
-=head2 ScripObj
-
-Return the Scrip object we're talking about
-
-=cut
-
-sub ScripObj  {
-  my $self = shift;
-  return($self->{'ScripObj'});
-}
-# }}}
-# {{{ sub TransactionObj
-
-=head2 TransactionObj
-
-Return the transaction object we're talking about
-
-=cut
-
-sub TransactionObj  {
-  my $self = shift;
-  return($self->{'TransactionObj'});
-}
-# }}}
-
-# {{{ sub Type
-
-=head2 Type 
-
-
-
-=cut
-
-sub ApplicableTransTypes  {
-  my $self = shift;
-  return($self->{'ApplicableTransTypes'});
-}
-# }}}
-
-
-# Scrip methods
-
-
-#What does this type of Action does
-
-# {{{ sub Describe 
-sub Describe  {
-  my $self = shift;
-  return ($self->loc("No description for [_1]", ref $self));
-}
-# }}}
-
-
-#Parse the templates, get things ready to go.
-
-#If this rule applies to this transaction, return true.
-
-# {{{ sub IsApplicable 
-sub IsApplicable  {
-  my $self = shift;
-  return(undef);
-}
-# }}}
-
-# {{{ sub DESTROY
-sub DESTROY {
-    my $self = shift;
-
-    # We need to clean up all the references that might maybe get
-    # oddly circular
-    $self->{'TemplateObj'} =undef
-    $self->{'TicketObj'} = undef;
-    $self->{'TransactionObj'} = undef;
-    $self->{'ScripObj'} = undef;
-     
-}
-
-# }}}
+use warnings;
+package RT::Condition::Generic;
+use base 'RT::Condition';
 
 eval "require RT::Condition::Generic_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/Generic_Vendor.pm});
+warn "RT::Condition::Generic has become RT::Condition. Please adjust your RT::Condition::Generic_Vendor file at " . $INC{"RT/Condition/Generic_Vendor.pm"} if !$@;
+
 eval "require RT::Condition::Generic_Local";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/Generic_Local.pm});
+warn "RT::Condition::Generic has become RT::Condition. Please adjust your RT::Condition::Generic_Local file at " . $INC{"RT/Condition/Generic_Local.pm"} if !$@;
 
 1;
+
index 8afabcd..285b71d 100644 (file)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
 # 
 # 
-# END LICENSE BLOCK
-
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
 
 package RT::Condition::StatusChange;
-require RT::Condition::Generic;
-
+use base 'RT::Condition';
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Condition::Generic);
 
 
 =head2 IsApplicable
index 4ca2f98..b674d4e 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
   RT::CurrentUser - an RT object representing the current user
 
 =head1 SYNOPSIS
 
-  use RT::CurrentUser
+    use RT::CurrentUser;
+
+    # laod
+    my $current_user = new RT::CurrentUser;
+    $current_user->Load(...);
+    # or
+    my $current_user = RT::CurrentUser->new( $user_obj );
+    # or
+    my $current_user = RT::CurrentUser->new( $address || $name || $id );
+
+    # manipulation
+    $current_user->UserObj->SetName('new_name');
 
 
 =head1 DESCRIPTION
 
+B<Read-only> subclass of L<RT::User> class. Used to define the current
+user. You should pass an instance of this class to constructors of
+many RT classes, then the instance used to check ACLs and localize
+strings.
 
 =head1 METHODS
 
+See also L<RT::User> for a list of methods this class has.
 
-=begin testing
-
-ok (require RT::CurrentUser);
+=head2 new
 
-=end testing
+Returns new CurrentUser object. Unlike all other classes of RT it takes
+either subclass of C<RT::User> class object or scalar value that is
+passed to Load method.
 
 =cut
 
 
 package RT::CurrentUser;
 
-use RT::Record;
 use RT::I18N;
 
 use strict;
-use vars qw/@ISA/;
-@ISA= qw(RT::Record);
+use warnings;
 
-# {{{ sub _Init 
+use base qw/RT::User/;
 
 #The basic idea here is that $self->CurrentUser is always supposed
 # to be a CurrentUser object. but that's hard to do when we're trying to load
 # the CurrentUser object
 
-sub _Init  {
-  my $self = shift;
-  my $Name = shift;
-
-  $self->{'table'} = "Users";
+sub _Init {
+    my $self = shift;
+    my $User = shift;
+
+    $self->{'table'} = "Users";
+
+    if ( defined $User ) {
+
+        if ( UNIVERSAL::isa( $User, 'RT::User' ) ) {
+            $self->LoadById( $User->id );
+        }
+        elsif ( ref $User ) {
+            $RT::Logger->crit(
+                "RT::CurrentUser->new() called with a bogus argument: $User");
+        }
+        else {
+            $self->Load( $User );
+        }
+    }
 
-  if (defined($Name)) {
-    $self->Load($Name);
-  }
-  
-  $self->CurrentUser($self);
+    $self->_BuildTableAttributes;
 
 }
-# }}}
 
-# {{{ sub Create
+=head2 Create, Delete and Set*
+
+As stated above it's a subclass of L<RT::User>, but this class is read-only
+and calls to these methods are illegal. Return 'permission denied' message
+and log an error.
+
+=cut
 
 sub Create {
     my $self = shift;
+    $RT::Logger->error('RT::CurrentUser is read-only, RT::User for manipulation');
     return (0, $self->loc('Permission Denied'));
 }
 
-# }}}
-
-# {{{ sub Delete
-
 sub Delete {
     my $self = shift;
+    $RT::Logger->error('RT::CurrentUser is read-only, RT::User for manipulation');
     return (0, $self->loc('Permission Denied'));
 }
 
-# }}}
-
-# {{{ sub UserObj
-
-=head2 UserObj
-
-  Returns the RT::User object associated with this CurrentUser object.
-
-=cut
-
-sub UserObj {
-    my $self = shift;
-    
-    unless ($self->{'UserObj'}) {
-       use RT::User;
-       $self->{'UserObj'} = RT::User->new($self);
-       unless ($self->{'UserObj'}->Load($self->Id)) {
-           $RT::Logger->err($self->loc("Couldn't load [_1] from the users database.\n", $self->Id));
-       }
-       
-    }
-    return ($self->{'UserObj'});
-}
-# }}}
-
-# {{{ sub PrincipalObj 
-
-=head2 PrincipalObj
-
-    Returns this user's principal object.  this is just a helper routine for
-    $self->UserObj->PrincipalObj
-
-=cut
-
-sub PrincipalObj {
+sub _Set {
     my $self = shift;
-    return($self->UserObj->PrincipalObj);
+    $RT::Logger->error('RT::CurrentUser is read-only, RT::User for manipulation');
+    return (0, $self->loc('Permission Denied'));
 }
 
+=head2 UserObj
 
-# }}}
-
-
-# {{{ sub PrincipalId 
-
-=head2 PrincipalId
-
-    Returns this user's principal Id.  this is just a helper routine for
-    $self->UserObj->PrincipalId
+Returns the L<RT::User> object associated with this CurrentUser object.
 
 =cut
 
-sub PrincipalId {
+sub UserObj {
     my $self = shift;
-    return($self->UserObj->PrincipalId);
-}
-
-
-# }}}
-
 
-# {{{ sub _Accessible 
-sub _Accessible  {
-  my $self = shift;
-  my %Cols = (
-             Name => 'read',
-             Gecos => 'read',
-             RealName => 'read',
-             Password => 'neither',
-             EmailAddress => 'read',
-             Privileged => 'read',
-             IsAdministrator => 'read'
-            );
-  return($self->SUPER::_Accessible(@_, %Cols));
+    my $user = RT::User->new( $self );
+    unless ( $user->LoadById( $self->Id ) ) {
+        $RT::Logger->error(
+            $self->loc("Couldn't load [_1] from the users database.\n", $self->Id)
+        );
+    }
+    return $user;
 }
-# }}}
-
-# {{{ sub LoadByEmail
-
-=head2 LoadByEmail
-
-Loads a User into this CurrentUser object.
-Takes the email address of the user to load.
-
-=cut
 
-sub LoadByEmail  {
-    my $self = shift;
-    my $identifier = shift;
-
-    $identifier = RT::User::CanonicalizeEmailAddress(undef, $identifier);
-        
-    $self->LoadByCol("EmailAddress",$identifier);
-    
+sub _CoreAccessible  {
+     {
+         Name           => { 'read' => 1 },
+           Gecos        => { 'read' => 1 },
+           RealName     => { 'read' => 1 },
+           Lang     => { 'read' => 1 },
+           Password     => { 'read' => 0, 'write' => 0 },
+          EmailAddress => { 'read' => 1, 'write' => 0 }
+     };
+  
 }
-# }}}
-
-# {{{ sub LoadByGecos
 
 =head2 LoadByGecos
 
@@ -199,155 +188,62 @@ Takes a unix username as its only argument.
 
 sub LoadByGecos  {
     my $self = shift;
-    my $identifier = shift;
-        
-    $self->LoadByCol("Gecos",$identifier);
-    
+    return $self->LoadByCol( "Gecos", shift );
 }
-# }}}
-
-# {{{ sub LoadByName
 
 =head2 LoadByName
 
 Loads a User into this CurrentUser object.
 Takes a Name.
-=cut
-
-sub LoadByName {
-    my $self = shift;
-    my $identifier = shift;
-    $self->LoadByCol("Name",$identifier);
-    
-}
-# }}}
-
-# {{{ sub Load 
-
-=head2 Load
-
-Loads a User into this CurrentUser object.
-Takes either an integer (users id column reference) or a Name
-The latter is deprecated. Instead, you should use LoadByName.
-Formerly, this routine also took email addresses. 
-
-=cut
-
-sub Load  {
-  my $self = shift;
-  my $identifier = shift;
-
-  #if it's an int, load by id. otherwise, load by name.
-  if ($identifier !~ /\D/) {
-    $self->SUPER::LoadById($identifier);
-  }
-  else {
-      # This is a bit dangerous, we might get false authen if somebody
-      # uses ambigous userids or real names:
-      $self->LoadByCol("Name",$identifier);
-  }
-}
-
-# }}}
-
-# {{{ sub IsPassword
-
-=head2 IsPassword
-
-Takes a password as a string.  Passes it off to IsPassword in this
-user's UserObj.  If it is the user's password and the user isn't
-disabled, returns 1.
-
-Otherwise, returns undef.
 
 =cut
 
-sub IsPassword { 
-  my $self = shift;
-  my $value = shift;
-  
-  return ($self->UserObj->IsPassword($value)); 
-}
-
-# }}}
-
-# {{{ sub Privileged
-
-=head2 Privileged
-
-Returns true if the current user can be granted rights and be
-a member of groups.
-
-=cut
-
-sub Privileged {
+sub LoadByName {
     my $self = shift;
-    return ($self->UserObj->Privileged());
-}
-
-# }}}
-
-
-# {{{ sub HasRight
-
-=head2 HasRight
-
-calls $self->UserObj->HasRight with the arguments passed in
-
-=cut
-
-sub HasRight {
-  my $self = shift;
-  return ($self->UserObj->HasRight(@_));
+    return $self->LoadByCol( "Name", shift );
 }
 
-# }}}
-
-# {{{ Localization
-
 =head2 LanguageHandle
 
 Returns this current user's langauge handle. Should take a language
 specification. but currently doesn't
 
-=begin testing
-
-ok (my $cu = RT::CurrentUser->new('root'));
-ok (my $lh = $cu->LanguageHandle);
-ok ($lh != undef);
-ok ($lh->isa('Locale::Maketext'));
-ok ($cu->loc('TEST_STRING') eq "Concrete Mixer", "Localized TEST_STRING into English");
-ok ($lh = $cu->LanguageHandle('fr'));
-ok ($cu->loc('Before') eq "Avant", "Localized TEST_STRING into Frenc");
-
-=end testing
-
 =cut 
 
 sub LanguageHandle {
     my $self = shift;
-    if  ((!defined $self->{'LangHandle'}) || 
-         (!UNIVERSAL::can($self->{'LangHandle'}, 'maketext')) || 
-         (@_))  {
+    if (   !defined $self->{'LangHandle'}
+        || !UNIVERSAL::can( $self->{'LangHandle'}, 'maketext' )
+        || @_ )
+    {
+        if ( my $lang = $self->Lang ) {
+            push @_, $lang;
+        }
+        elsif ( $self->id && ($self->id == ($RT::SystemUser->id||0) || $self->id == ($RT::Nobody->id||0)) ) {
+            # don't use ENV magic for system users
+            push @_, 'en';
+        }
+
         $self->{'LangHandle'} = RT::I18N->get_handle(@_);
     }
+
     # Fall back to english.
-    unless ($self->{'LangHandle'}) {
-        die "We couldn't get a dictionary. Nye mogu naidti slovar. No puedo encontrar dictionario.";
+    unless ( $self->{'LangHandle'} ) {
+        die "We couldn't get a dictionary. Ne mogu naidti slovar. No puedo encontrar dictionario.";
     }
-    return ($self->{'LangHandle'});
+    return $self->{'LangHandle'};
 }
 
 sub loc {
     my $self = shift;
-    return '' if $_[0] eq '';
+    return '' if !defined $_[0] || $_[0] eq '';
 
     my $handle = $self->LanguageHandle;
 
     if (@_ == 1) {
-       # pre-scan the lexicon hashes to return _AUTO keys verbatim,
-       # to keep locstrings containing '[' and '~' from tripping over Maketext
-       return $_[0] unless grep { exists $_->{$_[0]} } @{ $handle->_lex_refs };
+        # pre-scan the lexicon hashes to return _AUTO keys verbatim,
+        # to keep locstrings containing '[' and '~' from tripping over Maketext
+        return $_[0] unless grep exists $_->{$_[0]}, @{ $handle->_lex_refs };
     }
 
     return $handle->maketext(@_);
@@ -355,15 +251,65 @@ sub loc {
 
 sub loc_fuzzy {
     my $self = shift;
-    return '' if $_[0] eq '';
+    return '' if !defined $_[0] || $_[0] eq '';
 
     # XXX: work around perl's deficiency when matching utf8 data
     return $_[0] if Encode::is_utf8($_[0]);
-    my $result = $self->LanguageHandle->maketext_fuzzy(@_);
 
-    return($result);
+    return $self->LanguageHandle->maketext_fuzzy( @_ );
+}
+
+=head2 CurrentUser
+
+Return the current currentuser object
+
+=cut
+
+sub CurrentUser {
+    my $self = shift;
+    return($self);
+
+}
+
+=head2 Authenticate
+
+Takes $password, $created and $nonce, and returns a boolean value
+representing whether the authentication succeeded.
+
+If both $nonce and $created are specified, validate $password against:
+
+    encode_base64(sha1(
+        $nonce .
+        $created .
+        sha1_hex( "$username:$realm:$server_pass" )
+    ))
+
+where $server_pass is the md5_hex(password) digest stored in the
+database, $created is in ISO time format, and $nonce is a random
+string no longer than 32 bytes.
+
+=cut
+
+sub Authenticate { 
+    my ($self, $password, $created, $nonce, $realm) = @_;
+
+    require Digest::MD5;
+    require Digest::SHA1;
+    require MIME::Base64;
+
+    my $username = $self->UserObj->Name or return;
+    my $server_pass = $self->UserObj->__Value('Password') or return;
+    my $auth_digest = MIME::Base64::encode_base64(Digest::SHA1::sha1(
+        $nonce .
+        $created .
+        Digest::MD5::md5_hex("$username:$realm:$server_pass")
+    ));
+
+    chomp($password);
+    chomp($auth_digest);
+
+    return ($password eq $auth_digest);
 }
-# }}}
 
 eval "require RT::CurrentUser_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/CurrentUser_Vendor.pm});
@@ -371,4 +317,3 @@ eval "require RT::CurrentUser_Local";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/CurrentUser_Local.pm});
 
 1;
index 355370a..fc4c43c 100644 (file)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
   RT::Date - a simple Object Oriented date.
@@ -35,11 +60,6 @@ RT Date is a simple Date Object designed to be speedy and easy for RT to use
 
 The fact that it assumes that a time of 0 means "never" is probably a bug.
 
-=begin testing
-
-ok (require RT::Date);
-
-=end testing
 
 =head1 METHODS
 
@@ -49,12 +69,11 @@ ok (require RT::Date);
 package RT::Date;
 
 use Time::Local;
-
-use RT::Base;
+use POSIX qw(tzset);
 
 use strict;
-use vars qw/@ISA/;
-@ISA = qw/RT::Base/;
+use warnings;
+use base qw/RT::Base/;
 
 use vars qw($MINUTE $HOUR $DAY $WEEK $MONTH $YEAR);
 
@@ -62,30 +81,68 @@ $MINUTE = 60;
 $HOUR   = 60 * $MINUTE;
 $DAY    = 24 * $HOUR;
 $WEEK   = 7 * $DAY;
-$MONTH  = 4 * $WEEK;
-$YEAR   = 365 * $DAY;
-
-# {{{ sub new 
-
-sub new  {
-  my $proto = shift;
-  my $class = ref($proto) || $proto;
-  my $self  = {};
-  bless ($self, $class);
-  $self->CurrentUser(@_);
-  $self->Unix(0);
-  return $self;
+$MONTH  = 30.4375 * $DAY;
+$YEAR   = 365.25 * $DAY;
+
+our @MONTHS = (
+    'Jan', # loc
+    'Feb', # loc
+    'Mar', # loc
+    'Apr', # loc
+    'May', # loc
+    'Jun', # loc
+    'Jul', # loc
+    'Aug', # loc
+    'Sep', # loc
+    'Oct', # loc
+    'Nov', # loc
+    'Dec', # loc
+);
+
+our @DAYS_OF_WEEK = (
+    'Sun', # loc
+    'Mon', # loc
+    'Tue', # loc
+    'Wed', # loc
+    'Thu', # loc
+    'Fri', # loc
+    'Sat', # loc
+);
+
+our @FORMATTERS = (
+    'DefaultFormat', # loc
+    'ISO',           # loc
+    'W3CDTF',        # loc
+    'RFC2822',       # loc
+    'RFC2616',       # loc
+    'iCal',          # loc
+);
+if ( eval 'use DateTime qw(); 1;' && eval 'use DateTime::Locale qw(); 1;' && 
+     DateTime->can('format_cldr') && DateTime::Locale::root->can('date_format_full') ) {
+    push @FORMATTERS, 'LocalizedDateTime'; # loc
 }
 
-# }}}
+=head2 new
+
+Object constructor takes one argument C<RT::CurrentUser> object.
+
+=cut
 
-# {{{ sub Set
+sub new {
+    my $proto = shift;
+    my $class = ref($proto) || $proto;
+    my $self  = {};
+    bless ($self, $class);
+    $self->CurrentUser(@_);
+    $self->Unix(0);
+    return $self;
+}
 
-=head2 sub Set
+=head2 Set
 
-takes a param hash with the fields 'Format' and 'Value'
+Takes a param hash with the fields C<Format>, C<Value> and C<Timezone>.
 
-if $args->{'Format'} is 'unix', takes the number of seconds since the epoch 
+If $args->{'Format'} is 'unix', takes the number of seconds since the epoch.
 
 If $args->{'Format'} is ISO, tries to parse an ISO date.
 
@@ -94,243 +151,218 @@ things out. This is a heavyweight operation that should never be called from
 within RT's core. But it's really useful for something like the textbox date
 entry where we let the user do whatever they want.
 
-If $args->{'Value'}  is 0, assumes you mean never.
-
-=begin testing
-
-use_ok(RT::Date);
-my $date = RT::Date->new($RT::SystemUser);
-$date->Set(Format => 'unix', Value => '0');
-ok ($date->ISO eq '1970-01-01 00:00:00', "Set a date to midnight 1/1/1970 GMT");
-
-=end testing
+If $args->{'Value'} is 0, assumes you mean never.
 
 =cut
 
 sub Set {
     my $self = shift;
-    my %args = ( Format => 'unix',
-                 Value  => time,
-                 @_ );
-    if ( !$args{'Value'}
-         || ( ( $args{'Value'} =~ /^\d*$/ ) and ( $args{'Value'} == 0 ) ) ) {
-        $self->Unix(-1);
-        return ( $self->Unix() );
-    }
+    my %args = (
+        Format   => 'unix',
+        Value    => time,
+        Timezone => 'user',
+        @_
+    );
+
+    return $self->Unix(0) unless $args{'Value'};
 
     if ( $args{'Format'} =~ /^unix$/i ) {
-        $self->Unix( $args{'Value'} );
+        return $self->Unix( $args{'Value'} );
     }
-
     elsif ( $args{'Format'} =~ /^(sql|datemanip|iso)$/i ) {
-       $args{'Value'} =~ s!/!-!g;
-
-        if (( $args{'Value'} =~ /^(\d{4}?)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/ )
-            || ( $args{'Value'} =~
-                 /^(\d{4}?)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/ )
-            || ( $args{'Value'} =~
-                 /^(\d{4}?)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)\+00$/ )
-            || ($args{'Value'} =~ /^(\d{4}?)(\d\d)(\d\d)(\d\d):(\d\d):(\d\d)$/ )
+        $args{'Value'} =~ s!/!-!g;
+
+        if (   ( $args{'Value'} =~ /^(\d{4})?(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/ )
+            || ( $args{'Value'} =~ /^(\d{4})?(\d\d)(\d\d)(\d\d):(\d\d):(\d\d)$/ )
+            || ( $args{'Value'} =~ /^(?:(\d{4})-)?(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/ )
+            || ( $args{'Value'} =~ /^(?:(\d{4})-)?(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)\+00$/ )
           ) {
 
-            my $year  = $1;
-            my $mon   = $2;
-            my $mday  = $3;
-            my $hours = $4;
-            my $min   = $5;
-            my $sec   = $6;
+            my ($year, $mon, $mday, $hours, $min, $sec)  = ($1, $2, $3, $4, $5, $6);
+
+            # use current year if string has no value
+            $year ||= (localtime time)[5] + 1900;
 
             #timegm expects month as 0->11
             $mon--;
 
-            #now that we've parsed it, deal with the case where everything
-            #was 0
-            if ( $mon == -1 ) {
-                $self->Unix(-1);
-            }
-            else {
-
-                #Dateamnip strings aren't in GMT.
-                if ( $args{'Format'} =~ /^datemanip$/i ) {
-                    $self->Unix(
-                          timelocal( $sec, $min, $hours, $mday, $mon, $year ) );
-                }
-
-                #ISO and SQL dates are in GMT
-                else {
-                    $self->Unix(
-                             timegm( $sec, $min, $hours, $mday, $mon, $year ) );
-                }
-
-                $self->Unix(-1) unless $self->Unix;
-            }
+            #now that we've parsed it, deal with the case where everything was 0
+            return $self->Unix(0) if $mon < 0 || $mon > 11;
+
+            my $tz = lc $args{'Format'} eq 'datemanip'? 'user': 'utc';
+            $self->Unix( $self->Timelocal( $tz, $sec, $min, $hours, $mday, $mon, $year ) );
+
+            $self->Unix(0) unless $self->Unix > 0;
         }
         else {
-            use Carp;
-            Carp::cluck;
-            $RT::Logger->debug(
-                     "Couldn't parse date $args{'Value'} as a $args{'Format'}");
-
+            $RT::Logger->warning(
+                "Couldn't parse date '$args{'Value'}' as a $args{'Format'} format"
+            );
+            return $self->Unix(0);
         }
     }
     elsif ( $args{'Format'} =~ /^unknown$/i ) {
         require Time::ParseDate;
-
-        #Convert it to an ISO format string
-
-       my $date = Time::ParseDate::parsedate($args{'Value'},
-                       UK => $RT::DateDayBeforeMonth,
-                       PREFER_PAST => $RT::AmbiguousDayInPast,
-                       PREFER_FUTURE => !($RT::AmbiguousDayInPast));
-
-        #This date has now been set to a date in the _local_ timezone.
-        #since ISO dates are known to be in GMT (for RT's purposes);
-
-        $RT::Logger->debug( "RT::Date used date::parse to make "
-                            . $args{'Value'}
-                            . " $date\n" );
-
-        return ( $self->Set( Format => 'unix', Value => "$date" ) );
+        # the module supports only legacy timezones like PDT or EST...
+        # so we parse date as GMT and later apply offset, this only
+        # should be applied to absolute times, so compensate shift in NOW
+        my $now = time;
+        $now += ($self->Localtime( $args{Timezone}, $now ))[9];
+        my $date = Time::ParseDate::parsedate(
+            $args{'Value'},
+            GMT           => 1,
+            NOW           => $now,
+            UK            => RT->Config->Get('DateDayBeforeMonth'),
+            PREFER_PAST   => RT->Config->Get('AmbiguousDayInPast'),
+            PREFER_FUTURE => RT->Config->Get('AmbiguousDayInFuture'),
+        );
+        # apply timezone offset
+        $date -= ($self->Localtime( $args{Timezone}, $date ))[9];
+
+        $RT::Logger->debug(
+            "RT::Date used Time::ParseDate to make '$args{'Value'}' $date\n"
+        );
+
+        return $self->Set( Format => 'unix', Value => $date);
     }
     else {
-        die "Unknown Date format: " . $args{'Format'} . "\n";
+        $RT::Logger->error(
+            "Unknown Date format: $args{'Format'}\n"
+        );
+        return $self->Unix(0);
     }
 
-    return ( $self->Unix() );
+    return $self->Unix;
 }
 
-# }}}
+=head2 SetToNow
+
+Set the object's time to the current time. Takes no arguments
+and returns unix time.
 
-# {{{ sub SetToMidnight 
+=cut
+
+sub SetToNow {
+    return $_[0]->Unix(time);
+}
 
-=head2 SetToMidnight
+=head2 SetToMidnight [Timezone => 'utc']
 
-Sets the date to midnight (at the beginning of the day) GMT
+Sets the date to midnight (at the beginning of the day).
 Returns the unixtime at midnight.
 
-=cut
+Arguments:
 
-sub SetToMidnight {
-    my $self = shift;
-    
-    use Time::Local;
-    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($self->Unix);
-    $self->Unix(timegm (0,0,0,$mday,$mon,$year,$wday,$yday));
-    
-    return ($self->Unix);
-    
-    
-}
+=over 4
 
+=item Timezone
 
-# }}}
+Timezone context C<user>, C<server> or C<UTC>. See also L</Timezone>.
 
-# {{{ sub SetToNow
-sub SetToNow {
-       my $self = shift;
-       return($self->Set(Format => 'unix', Value => time))
-}
-# }}}
+=back
 
-# {{{ sub Diff
+=cut
+
+sub SetToMidnight {
+    my $self = shift;
+    my %args = ( Timezone => '', @_ );
+    my $new = $self->Timelocal(
+        $args{'Timezone'},
+        0,0,0,($self->Localtime( $args{'Timezone'} ))[3..9]
+    );
+    return $self->Unix( $new );
+}
 
 =head2 Diff
 
-Takes either an RT::Date object or the date in unixtime format as a string
+Takes either an C<RT::Date> object or the date in unixtime format as a string,
+if nothing is specified uses the current time.
 
-Returns the differnce between $self and that time as a number of seconds
+Returns the differnce between the time in the current object and that time
+as a number of seconds. Returns C<undef> if any of two compared values is
+incorrect or not set.
 
 =cut
 
 sub Diff {
     my $self = shift;
     my $other = shift;
-
-    if (ref($other) eq 'RT::Date') {
-       $other=$other->Unix;
+    $other = time unless defined $other;
+    if ( UNIVERSAL::isa( $other, 'RT::Date' ) ) {
+        $other = $other->Unix;
     }
-    return ($self->Unix - $other);
-}
-# }}}
+    return undef unless $other=~ /^\d+$/ && $other > 0;
 
-# {{{ sub DiffAsString
+    my $unix = $self->Unix;
+    return undef unless $unix > 0;
+
+    return $unix - $other;
+}
 
-=head2 sub DiffAsString
+=head2 DiffAsString
 
-Takes either an RT::Date object or the date in unixtime format as a string
+Takes either an C<RT::Date> object or the date in unixtime format as a string,
+if nothing is specified uses the current time.
 
-Returns the differnce between $self and that time as a number of seconds as
-as string fit for human consumption
+Returns the differnce between C<$self> and that time as a number of seconds as
+a localized string fit for human consumption. Returns empty string if any of
+two compared values is incorrect or not set.
 
 =cut
 
 sub DiffAsString {
     my $self = shift;
-    my $other = shift;
-
+    my $diff = $self->Diff( @_ );
+    return '' unless defined $diff;
 
-    if ($other < 1) {
-       return ("");
-    }
-    if ($self->Unix < 1) {
-       return("");
-    }
-    my $diff = $self->Diff($other);
-
-    return ($self->DurationAsString($diff));
+    return $self->DurationAsString( $diff );
 }
-# }}}
-
-# {{{ sub DurationAsString
-
 
 =head2 DurationAsString
 
-Takes a number of seconds. returns a string describing that duration
+Takes a number of seconds. Returns a localized string describing
+that duration.
 
 =cut
 
 sub DurationAsString {
-
     my $self     = shift;
-    my $duration = shift;
-
-    my ( $negative, $s );
+    my $duration = int shift;
 
-    $negative = 1 if ( $duration < 0 );
+    my ( $negative, $s, $time_unit );
+    $negative = 1 if $duration < 0;
+    $duration = abs $duration;
 
-    $duration = abs($duration);
-
-    my $time_unit;
     if ( $duration < $MINUTE ) {
         $s         = $duration;
         $time_unit = $self->loc("sec");
     }
     elsif ( $duration < ( 2 * $HOUR ) ) {
-        $s         = int( $duration / $MINUTE );
+        $s         = int( $duration / $MINUTE + 0.5 );
         $time_unit = $self->loc("min");
     }
     elsif ( $duration < ( 2 * $DAY ) ) {
-        $s         = int( $duration / $HOUR );
+        $s         = int( $duration / $HOUR + 0.5 );
         $time_unit = $self->loc("hours");
     }
     elsif ( $duration < ( 2 * $WEEK ) ) {
-        $s         = int( $duration / $DAY );
+        $s         = int( $duration / $DAY + 0.5 );
         $time_unit = $self->loc("days");
     }
     elsif ( $duration < ( 2 * $MONTH ) ) {
-        $s         = int( $duration / $WEEK );
+        $s         = int( $duration / $WEEK + 0.5 );
         $time_unit = $self->loc("weeks");
     }
     elsif ( $duration < $YEAR ) {
-        $s         = int( $duration / $MONTH );
+        $s         = int( $duration / $MONTH + 0.5 );
         $time_unit = $self->loc("months");
     }
     else {
-        $s         = int( $duration / $YEAR );
+        $s         = int( $duration / $YEAR + 0.5 );
         $time_unit = $self->loc("years");
     }
-    if (0) { # For now, never display the "AGO" # $negative) {
+
+    if ( $negative ) {
         return $self->loc( "[_1] [_2] ago", $s, $time_unit );
     }
     else {
@@ -338,46 +370,46 @@ sub DurationAsString {
     }
 }
 
-# }}}
+=head2 AgeAsString
 
-# {{{ sub AgeAsString
+Takes nothing. Returns a string that's the differnce between the
+time in the object and now.
 
-=head2 sub AgeAsString
-
-Takes nothing
+=cut
 
-Returns a string that's the differnce between the time in the object and now
+sub AgeAsString { return $_[0]->DiffAsString }
 
-=cut
 
-sub AgeAsString {
-    my $self = shift;
-    return ($self->DiffAsString(time));
-    }
-# }}}
 
-# {{{ sub AsString
+=head2 AsString
 
-=head2 sub AsString
+Returns the object's time as a localized string with curent user's prefered
+format and timezone.
 
-Returns the object\'s time as a string with the current timezone.
+If the current user didn't choose prefered format then system wide setting is
+used or L</DefaultFormat> if the latter is not specified. See config option
+C<DateTimeFormat>.
 
 =cut
 
 sub AsString {
     my $self = shift;
-    return ($self->loc("Not set")) if ($self->Unix <= 0);
+    my %args = (@_);
+
+    return $self->loc("Not set") unless $self->Unix > 0;
 
-    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($self->Unix);
+    my $format = RT->Config->Get( 'DateTimeFormat', $self->CurrentUser ) || 'DefaultFormat';
+    $format = { Format => $format } unless ref $format;
+    %args = (%$format, %args);
 
-    return $self->loc("[_1] [_2] [_3] [_4]:[_5]:[_6] [_7]", $self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900));
+    return $self->Get( Timezone => 'user', %args );
 }
-# }}}
 
-# {{{ GetWeekday
 =head2 GetWeekday DAY
 
-Takes an integer day of week and returns a localized string for that day of week
+Takes an integer day of week and returns a localized string for
+that day of week. Valid values are from range 0-6, Note that B<0
+is sunday>.
 
 =cut
 
@@ -385,169 +417,628 @@ sub GetWeekday {
     my $self = shift;
     my $dow = shift;
     
-    return $self->loc('Mon.') if ($dow == 1);
-    return $self->loc('Tue.') if ($dow == 2);
-    return $self->loc('Wed.') if ($dow == 3);
-    return $self->loc('Thu.') if ($dow == 4);
-    return $self->loc('Fri.') if ($dow == 5);
-    return $self->loc('Sat.') if ($dow == 6);
-    return $self->loc('Sun.') if ($dow == 0);
+    return $self->loc($DAYS_OF_WEEK[$dow])
+        if $DAYS_OF_WEEK[$dow];
+    return '';
 }
 
-# }}}
+=head2 GetMonth MONTH
 
-# {{{ GetMonth
-=head2 GetMonth DAY
-
-Takes an integer month and returns a localized string for that month 
+Takes an integer month and returns a localized string for that month.
+Valid values are from from range 0-11.
 
 =cut
 
 sub GetMonth {
     my $self = shift;
-   my $mon = shift;
-
-    # We do this rather than an array so that we don't call localize 12x what we need to
-    return $self->loc('Jan.') if ($mon == 0);
-    return $self->loc('Feb.') if ($mon == 1);
-    return $self->loc('Mar.') if ($mon == 2);
-    return $self->loc('Apr.') if ($mon == 3);
-    return $self->loc('May.') if ($mon == 4);
-    return $self->loc('Jun.') if ($mon == 5);
-    return $self->loc('Jul.') if ($mon == 6);
-    return $self->loc('Aug.') if ($mon == 7);
-    return $self->loc('Sep.') if ($mon == 8);
-    return $self->loc('Oct.') if ($mon == 9);
-    return $self->loc('Nov.') if ($mon == 10);
-    return $self->loc('Dec.') if ($mon == 11);
-}
+    my $mon = shift;
 
-# }}}
-
-# {{{ sub AddSeconds
+    return $self->loc($MONTHS[$mon])
+        if $MONTHS[$mon];
+    return '';
+}
 
-=head2 sub AddSeconds
+=head2 AddSeconds SECONDS
 
-Takes a number of seconds as a string
+Takes a number of seconds and returns the new unix time.
 
-Returns the new time
+Negative value can be used to substract seconds.
 
 =cut
 
 sub AddSeconds {
     my $self = shift;
-    my $delta = shift;
+    my $delta = shift or return $self->Unix;
     
     $self->Set(Format => 'unix', Value => ($self->Unix + $delta));
-    
     return ($self->Unix);
-    
+}
+
+=head2 AddDays [DAYS]
+
+Adds C<24 hours * DAYS> to the current time. Adds one day when
+no argument is specified. Negative value can be used to substract
+days.
+
+Returns new unix time.
 
+=cut
+
+sub AddDays {
+    my $self = shift;
+    my $days = shift || 1;
+    return $self->AddSeconds( $days * $DAY );
 }
 
-# }}}
+=head2 AddDay
+
+Adds 24 hours to the current time. Returns new unix time.
 
-# {{{ sub AddDays
+=cut
 
-=head2 AddDays $DAYS
+sub AddDay { return $_[0]->AddSeconds($DAY) }
 
-Adds 24 hours * $DAYS to the current time
+=head2 Unix [unixtime]
+
+Optionally takes a date in unix seconds since the epoch format.
+Returns the number of seconds since the epoch
 
 =cut
 
-sub AddDays {
+sub Unix {
+    my $self = shift; 
+    $self->{'time'} = int(shift || 0) if @_;
+    return $self->{'time'};
+}
+
+=head2 DateTime
+
+Alias for L</Get> method. Arguments C<Date> and <Time>
+are fixed to true values, other arguments could be used
+as described in L</Get>.
+
+=cut
+
+sub DateTime {
     my $self = shift;
-    my $days = shift;
-    $self->AddSeconds($days * $DAY);
-    
+    unless (defined $self) {
+        use Carp; Carp::confess("undefined $self");
+    }
+    return $self->Get( @_, Date => 1, Time => 1 );
 }
 
-# }}}
+=head2 Date
 
-# {{{ sub AddDay
+Takes Format argument which allows you choose date formatter.
+Pass throught other arguments to the formatter method.
 
-=head2 AddDay
+Returns the object's formatted date. Default formatter is ISO.
+
+=cut
+
+sub Date {
+    my $self = shift;
+    return $self->Get( @_, Date => 1, Time => 0 );
+}
+
+=head2 Time
 
-Adds 24 hours to the current time
 
 =cut
 
-sub AddDay {
+sub Time {
     my $self = shift;
-    $self->AddSeconds($DAY);
-    
+    return $self->Get( @_, Date => 0, Time => 1 );
 }
 
-# }}}
+=head2 Get
 
-# {{{ sub Unix
+Returnsa a formatted and localized string that represets time of
+the current object.
 
-=head2 sub Unix [unixtime]
 
-Optionally takes a date in unix seconds since the epoch format.
-Returns the number of seconds since the epoch
+=cut
+
+sub Get
+{
+    my $self = shift;
+    my %args = (Format => 'ISO', @_);
+    my $formatter = $args{'Format'};
+    $formatter = 'ISO' unless $self->can($formatter);
+    return $self->$formatter( %args );
+}
+
+=head2 Output formatters
+
+Fomatter is a method that returns date and time in different configurable
+format.
+
+Each method takes several arguments:
+
+=over 1
+
+=item Date
+
+=item Time
+
+=item Timezone - Timezone context C<server>, C<user> or C<UTC>
+
+=back
+
+Formatters may also add own arguments to the list, for example
+in RFC2822 format day of time in output is optional so it
+understand boolean argument C<DayOfTime>.
+
+=head3 Formatters
+
+Returns an array of available formatters.
 
 =cut
 
-sub Unix {
+sub Formatters
+{
     my $self = shift;
+
+    return @FORMATTERS;
+}
+
+=head3 DefaultFormat
+
+=cut
+
+sub DefaultFormat
+{
+    my $self = shift;
+    my %args = ( Date => 1,
+                 Time => 1,
+                 Timezone => '',
+                 Seconds => 1,
+                 @_,
+               );
     
-    $self->{'time'} = shift if (@_);
-    
-    return ($self->{'time'});
+       #  0    1    2     3     4    5     6     7      8      9
+    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$ydaym,$isdst,$offset) =
+                            $self->Localtime($args{'Timezone'});
+    $wday = $self->GetWeekday($wday);
+    $mon = $self->GetMonth($mon);
+    ($mday, $hour, $min, $sec) = map { sprintf "%02d", $_ } ($mday, $hour, $min, $sec);
+
+    if( $args{'Date'} && !$args{'Time'} ) {
+        return $self->loc('[_1] [_2] [_3] [_4]',
+                          $wday,$mon,$mday,$year);
+    } elsif( !$args{'Date'} && $args{'Time'} ) {
+        if( $args{'Seconds'} ) {
+            return $self->loc('[_1]:[_2]:[_3]',
+                              $hour,$min,$sec);
+        } else {
+            return $self->loc('[_1]:[_2]',
+                              $hour,$min);
+        }
+    } else {
+        if( $args{'Seconds'} ) {
+            return $self->loc('[_1] [_2] [_3] [_4]:[_5]:[_6] [_7]',
+                              $wday,$mon,$mday,$hour,$min,$sec,$year);
+        } else {
+            return $self->loc('[_1] [_2] [_3] [_4]:[_5] [_6]',
+                              $wday,$mon,$mday,$hour,$min,$year);
+        }
+    }
 }
-# }}}
 
-# {{{ sub ISO
+=head3 LocalizedDateTime
 
-=head2 ISO
+Returns date and time as string, with user localization.
 
-Takes nothing
+Supports arguments: C<DateFormat> and C<TimeFormat> which may contains date and
+time format as specified in DateTime::Locale (default to full_date_format and
+medium_time_format), C<AbbrDay> and C<AbbrMonth> which may be set to 0 if
+you want full Day/Month names instead of abbreviated ones.
 
-Returns the object's date in ISO format
+Require optionnal DateTime::Locale module.
 
 =cut
 
-sub ISO {
-    my $self=shift;
-    my    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst, $date) ;
+sub LocalizedDateTime
+{
+    my $self = shift;
+    my %args = ( Date => 1,
+                 Time => 1,
+                 Timezone => '',
+                 DateFormat => 'date_format_full',
+                 TimeFormat => 'time_format_medium',
+                 AbbrDay => 1,
+                 AbbrMonth => 1,
+                 @_,
+               );
+
+    return $self->loc("DateTime module missing") unless ( eval 'use DateTime qw(); 1;' );
+    return $self->loc("DateTime::Locale module missing") unless ( eval 'use DateTime::Locale qw(); 1;' );
+    return $self->loc("DateTime doesn't support format_cldr, you must upgrade to use this feature") 
+        unless can DateTime::('format_cldr');
+
+
+    my $date_format = $args{'DateFormat'};
+    my $time_format = $args{'TimeFormat'};
+
+    my $lang = $self->CurrentUser->UserObj->Lang;
+    unless ($lang) {
+        require I18N::LangTags::Detect;
+        $lang = ( I18N::LangTags::Detect::detect(), 'en' )[0];
+    }
     
-    return ('1970-01-01 00:00:00') if ($self->Unix == -1);
 
-    #  0    1    2     3     4    5     6     7     8
-    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($self->Unix);
-    #make the year YYYY
-    $year+=1900;
+    my $formatter = DateTime::Locale->load($lang);
+    return $self->loc("DateTime::Locale doesn't support date_format_full, you must upgrade to use this feature") 
+        unless $formatter->can('date_format_full');
+    $date_format = $formatter->$date_format;
+    $time_format = $formatter->$time_format;
+    $date_format =~ s/EEEE/EEE/g if ( $args{'AbbrDay'} );
+    $date_format =~ s/MMMM/MMM/g if ( $args{'AbbrMonth'} );
+
+    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$ydaym,$isdst,$offset) =
+                            $self->Localtime($args{'Timezone'});
+    $mon++;
+    my $tz = $self->Timezone($args{'Timezone'});
+
+    # FIXME : another way to call this module without conflict with local
+    # DateTime method?
+    my $dt = new DateTime::( locale => $lang,
+                            time_zone => $tz,
+                            year => $year,
+                            month => $mon,
+                            day => $mday,
+                            hour => $hour,
+                            minute => $min,
+                            second => $sec,
+                            nanosecond => 0,
+                          );
+
+    if ( $args{'Date'} && !$args{'Time'} ) {
+        return $dt->format_cldr($date_format);
+    } elsif ( !$args{'Date'} && $args{'Time'} ) {
+        return $dt->format_cldr($time_format);
+    } else {
+        return $dt->format_cldr($date_format) . " " . $dt->format_cldr($time_format);
+    }
+}
+
+=head3 ISO
+
+Returns the object's date in ISO format C<YYYY-MM-DD mm:hh:ss>.
+ISO format is locale independant, but adding timezone offset info
+is not implemented yet.
+
+Supports arguments: C<Timezone>, C<Date>, C<Time> and C<Seconds>.
+See </Output formatters> for description of arguments.
+
+=cut
+
+sub ISO {
+    my $self = shift;
+    my %args = ( Date => 1,
+                 Time => 1,
+                 Timezone => '',
+                 Seconds => 1,
+                 @_,
+               );
+       #  0    1    2     3     4    5     6     7      8      9
+    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$ydaym,$isdst,$offset) =
+                            $self->Localtime($args{'Timezone'});
+
+    #the month needs incrementing, as gmtime returns 0-11
+    $mon++;
+
+    my $res = '';
+    $res .= sprintf("%04d-%02d-%02d", $year, $mon, $mday) if $args{'Date'};
+    $res .= sprintf(' %02d:%02d', $hour, $min) if $args{'Time'};
+    $res .= sprintf(':%02d', $sec, $min) if $args{'Time'} && $args{'Seconds'};
+    $res =~ s/^\s+//;
+
+    return $res;
+}
+
+=head3 W3CDTF
+
+Returns the object's date and time in W3C date time format
+(L<http://www.w3.org/TR/NOTE-datetime>).
+
+Format is locale independand and is close enought to ISO, but
+note that date part is B<not optional> and output string
+has timezone offset mark in C<[+-]hh:mm> format.
+
+Supports arguments: C<Timezone>, C<Time> and C<Seconds>.
+See </Output formatters> for description of arguments.
+
+=cut
+
+sub W3CDTF {
+    my $self = shift;
+    my %args = (
+        Time => 1,
+        Timezone => '',
+        Seconds => 1,
+        @_,
+        Date => 1,
+    );
+       #  0    1    2     3     4    5     6     7      8      9
+    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$ydaym,$isdst,$offset) =
+                            $self->Localtime( $args{'Timezone'} );
+
+    #the month needs incrementing, as gmtime returns 0-11
+    $mon++;
+
+    my $res = '';
+    $res .= sprintf("%04d-%02d-%02d", $year, $mon, $mday);
+    if ( $args{'Time'} ) {
+        $res .= sprintf('T%02d:%02d', $hour, $min);
+        $res .= sprintf(':%02d', $sec, $min) if $args{'Seconds'};
+        if ( $offset ) {
+            $res .= sprintf "%s%02d:%02d", $self->_SplitOffset( $offset );
+        } else {
+            $res .= 'Z';
+        }
+    }
+
+    return $res;
+};
+
+
+=head3 RFC2822 (MIME)
+
+Returns the object's date and time in RFC2822 format,
+for example C<Sun, 06 Nov 1994 08:49:37 +0000>.
+Format is locale independand as required by RFC. Time
+part always has timezone offset in digits with sign prefix.
+
+Supports arguments: C<Timezone>, C<Date>, C<Time>, C<DayOfWeek>
+and C<Seconds>. See </Output formatters> for description of
+arguments.
+
+=cut
+
+sub RFC2822 {
+    my $self = shift;
+    my %args = ( Date => 1,
+                 Time => 1,
+                 Timezone => '',
+                 DayOfWeek => 1,
+                 Seconds => 1,
+                 @_,
+               );
+
+       #  0    1    2     3     4    5     6     7      8     9
+    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$ydaym,$isdst,$offset) =
+                            $self->Localtime($args{'Timezone'});
+
+    my ($date, $time) = ('','');
+    $date .= "$DAYS_OF_WEEK[$wday], " if $args{'DayOfWeek'} && $args{'Date'};
+    $date .= "$mday $MONTHS[$mon] $year" if $args{'Date'};
+
+    if ( $args{'Time'} ) {
+        $time .= sprintf("%02d:%02d", $hour, $min);
+        $time .= sprintf(":%02d", $sec) if $args{'Seconds'};
+        $time .= sprintf " %s%02d%02d", $self->_SplitOffset( $offset );
+    }
+
+    return join ' ', grep $_, ($date, $time);
+}
+
+=head3 RFC2616 (HTTP)
+
+Returns the object's date and time in RFC2616 (HTTP/1.1) format,
+for example C<Sun, 06 Nov 1994 08:49:37 GMT>. While the RFC describes
+version 1.1 of HTTP, but the same form date can be used in version 1.0.
+
+Format is fixed length, locale independand and always represented in GMT
+what makes it quite useless for users, but any date in HTTP transfers
+must be presented using this format.
+
+    HTTP-date = rfc1123 | ...
+    rfc1123   = wkday "," SP date SP time SP "GMT"
+    date      = 2DIGIT SP month SP 4DIGIT
+                ; day month year (e.g., 02 Jun 1982)
+    time      = 2DIGIT ":" 2DIGIT ":" 2DIGIT
+                ; 00:00:00 - 23:59:59
+    wkday     = "Mon" | "Tue" | "Wed" | "Thu" | "Fri" | "Sat" | "Sun"
+    month     = "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun"
+              | "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec"
+
+Supports arguments: C<Date> and C<Time>, but you should use them only for
+some personal reasons, RFC2616 doesn't define any optional parts.
+See </Output formatters> for description of arguments.
+
+=cut
+
+sub RFC2616 {
+    my $self = shift;
+    my %args = ( Date => 1, Time => 1,
+                 @_,
+                 Timezone => 'utc',
+                 Seconds => 1, DayOfWeek => 1,
+               );
+
+    my $res = $self->RFC2822( @_ );
+    $res =~ s/\s*[+-]\d\d\d\d$/ GMT/ if $args{'Time'};
+    return $res;
+}
+
+=head4 iCal
+
+Returns the object's date and time in iCalendar format,
+
+Supports arguments: C<Date> and C<Time>.
+See </Output formatters> for description of arguments.
+
+=cut
+
+sub iCal {
+    my $self = shift;
+    my %args = (
+        Date => 1, Time => 1,
+        @_,
+    );
+    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$ydaym,$isdst,$offset) =
+        $self->Localtime( 'utc' );
 
     #the month needs incrementing, as gmtime returns 0-11
     $mon++;
-        
-    $date = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year,$mon,$mday, $hour,$min,$sec);
+
+    my $res;
+    if ( $args{'Date'} && !$args{'Time'} ) {
+        $res = sprintf( '%04d%02d%02d', $year, $mon, $mday );
+    }
+    elsif ( !$args{'Date'} && $args{'Time'} ) {
+        $res = sprintf( 'T%02d%02d%02dZ', $hour, $min, $sec );
+    }
+    else {
+        $res = sprintf( '%04d%02d%02dT%02d%02d%02dZ', $year, $mon, $mday, $hour, $min, $sec );
+    }
+    return $res;
+}
+
+# it's been added by mistake in 3.8.0
+sub iCalDate { return (shift)->iCal( Time => 0, @_ ) }
+
+sub _SplitOffset {
+    my ($self, $offset) = @_;
+    my $sign = $offset < 0? '-': '+';
+    $offset = int( (abs $offset) / 60 + 0.001 );
+    my $mins = $offset % 60;
+    my $hours = int( $offset/60 + 0.001 );
+    return $sign, $hours, $mins; 
+}
+
+=head2 Timezones handling
+
+=head3 Localtime $context [$time]
+
+Takes one mandatory argument C<$context>, which determines whether
+we want "user local", "system" or "UTC" time. Also, takes optional
+argument unix C<$time>, default value is the current unix time.
+
+Returns object's date and time in the format provided by perl's
+builtin functions C<localtime> and C<gmtime> with two exceptions:
+
+1) "Year" is a four-digit year, rather than "years since 1900"
+
+2) The last element of the array returned is C<offset>, which
+represents timezone offset against C<UTC> in seconds.
+
+=cut
+
+sub Localtime
+{
+    my $self = shift;
+    my $tz = $self->Timezone(shift);
+
+    my $unix = shift || $self->Unix;
+    $unix = 0 unless $unix >= 0;
     
-    return ($date);
+    my @local;
+    if ($tz eq 'UTC') {
+        @local = gmtime($unix);
+    } else {
+        {
+            local $ENV{'TZ'} = $tz;
+            ## Using POSIX::tzset fixes a bug where the TZ environment variable
+            ## is cached.
+            POSIX::tzset();
+            @local = localtime($unix);
+        }
+        POSIX::tzset(); # return back previouse value
+    }
+    $local[5] += 1900; # change year to 4+ digits format
+    my $offset = Time::Local::timegm_nocheck(@local) - $unix;
+    return @local, $offset;
+}
+
+=head3 Timelocal $context @time
+
+Takes argument C<$context>, which determines whether we should
+treat C<@time> as "user local", "system" or "UTC" time.
+
+C<@time> is array returned by L<Localtime> functions. Only first
+six elements are mandatory - $sec, $min, $hour, $mday, $mon and $year.
+You may pass $wday, $yday and $isdst, these are ignored.
+
+If you pass C<$offset> as ninth argument, it's used instead of
+C<$context>. It's done such way as code 
+C<$self->Timelocal('utc', $self->Localtime('server'))> doesn't
+makes much sense and most probably would produce unexpected
+result, so the method ignore 'utc' context and uses offset
+returned by L<Localtime> method.
+
+=cut
+
+sub Timelocal {
+    my $self = shift;
+    my $tz = shift;
+    if ( defined $_[9] ) {
+        return timegm(@_[0..5]) - $_[9];
+    } else {
+        $tz = $self->Timezone( $tz );
+        if ( $tz eq 'UTC' ) {
+            return Time::Local::timegm(@_[0..5]);
+        } else {
+            my $rv;
+            {
+                local $ENV{'TZ'} = $tz;
+                ## Using POSIX::tzset fixes a bug where the TZ environment variable
+                ## is cached.
+                POSIX::tzset();
+                $rv = Time::Local::timelocal(@_[0..5]);
+            };
+            POSIX::tzset(); # switch back to previouse value
+            return $rv;
+        }
+    }
 }
 
-# }}}
 
+=head3 Timezone $context
+
+Returns the timezone name.
+
+Takes one argument, C<$context> argument which could be C<user>, C<server> or C<utc>.
 
-# {{{ sub LocalTimezone 
-=head2 LocalTimezone
+=over
 
-  Returns the current timezone. For now, draws off a system timezone, RT::Timezone. Eventually, this may
-pull from a 'Timezone' attribute of the CurrentUser
+=item user
+
+Default value is C<user> that mean it returns current user's Timezone value.
+
+=item server
+
+If context is C<server> it returns value of the C<Timezone> RT config option.
+
+=item  utc
+
+If both server's and user's timezone names are undefined returns 'UTC'.
+
+=back
 
 =cut
 
-sub LocalTimezone {
+sub Timezone {
     my $self = shift;
+    my $context = lc(shift);
 
-    return $self->CurrentUser->Timezone
-       if $self->CurrentUser and $self->CurrentUser->can('Timezone');
+    $context = 'utc' unless $context =~ /^(?:utc|server|user)$/i;
 
-    return ($RT::Timezone);
+    my $tz;
+    if( $context eq 'user' ) {
+        $tz = $self->CurrentUser->UserObj->Timezone;
+    } elsif( $context eq 'server') {
+        $tz = RT->Config->Get('Timezone');
+    } else {
+        $tz = 'UTC';
+    }
+    $tz ||= RT->Config->Get('Timezone') || 'UTC';
+    $tz = 'UTC' if lc $tz eq 'gmt';
+    return $tz;
 }
 
-# }}}
 
 eval "require RT::Date_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Date_Vendor.pm});
index 4dcef3f..17444b0 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -61,7 +86,7 @@ sub _Init {
 
 
 
-=item Create PARAMHASH
+=head2 Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -69,7 +94,7 @@ Create takes a hash of values and creates a row in the database:
   varchar(255) 'Description'.
   varchar(64) 'Domain'.
   varchar(64) 'Type'.
-  varchar(64) 'Instance'.
+  int(11) 'Instance'.
 
 =cut
 
@@ -98,7 +123,7 @@ sub Create {
 
 
 
-=item id
+=head2 id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -107,14 +132,14 @@ Returns the current value of id.
 =cut
 
 
-=item Name
+=head2 Name
 
 Returns the current value of Name. 
 (In the database, Name is stored as varchar(200).)
 
 
 
-=item SetName VALUE
+=head2 SetName VALUE
 
 
 Set Name to VALUE. 
@@ -125,14 +150,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Description
+=head2 Description
 
 Returns the current value of Description. 
 (In the database, Description is stored as varchar(255).)
 
 
 
-=item SetDescription VALUE
+=head2 SetDescription VALUE
 
 
 Set Description to VALUE. 
@@ -143,14 +168,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Domain
+=head2 Domain
 
 Returns the current value of Domain. 
 (In the database, Domain is stored as varchar(64).)
 
 
 
-=item SetDomain VALUE
+=head2 SetDomain VALUE
 
 
 Set Domain to VALUE. 
@@ -161,14 +186,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Type
+=head2 Type
 
 Returns the current value of Type. 
 (In the database, Type is stored as varchar(64).)
 
 
 
-=item SetType VALUE
+=head2 SetType VALUE
 
 
 Set Type to VALUE. 
@@ -179,40 +204,40 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Instance
+=head2 Instance
 
 Returns the current value of Instance. 
-(In the database, Instance is stored as varchar(64).)
+(In the database, Instance is stored as int(11).)
 
 
 
-=item SetInstance VALUE
+=head2 SetInstance VALUE
 
 
 Set Instance to VALUE. 
 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, Instance will be stored as a varchar(64).)
+(In the database, Instance will be stored as a int(11).)
 
 
 =cut
 
 
 
-sub _ClassAccessible {
+sub _CoreAccessible {
     {
      
         id =>
-               {read => 1, type => 'int(11)', default => ''},
+               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
         Name => 
-               {read => 1, write => 1, type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
         Description => 
-               {read => 1, write => 1, type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
         Domain => 
-               {read => 1, write => 1, type => 'varchar(64)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 64,  is_blob => 0,  is_numeric => 0,  type => 'varchar(64)', default => ''},
         Type => 
-               {read => 1, write => 1, type => 'varchar(64)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 64,  is_blob => 0,  is_numeric => 0,  type => 'varchar(64)', default => ''},
         Instance => 
-               {read => 1, write => 1, type => 'varchar(64)', default => ''},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
 
  }
 };
@@ -244,7 +269,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index 8de1a73..f7fa73e 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -61,7 +86,7 @@ sub _Init {
 
 
 
-=item Create PARAMHASH
+=head2 Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -89,7 +114,7 @@ sub Create {
 
 
 
-=item id
+=head2 id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -98,14 +123,14 @@ Returns the current value of id.
 =cut
 
 
-=item GroupId
+=head2 GroupId
 
 Returns the current value of GroupId. 
 (In the database, GroupId is stored as int(11).)
 
 
 
-=item SetGroupId VALUE
+=head2 SetGroupId VALUE
 
 
 Set GroupId to VALUE. 
@@ -116,14 +141,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item MemberId
+=head2 MemberId
 
 Returns the current value of MemberId. 
 (In the database, MemberId is stored as int(11).)
 
 
 
-=item SetMemberId VALUE
+=head2 SetMemberId VALUE
 
 
 Set MemberId to VALUE. 
@@ -135,15 +160,15 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 
 
 
-sub _ClassAccessible {
+sub _CoreAccessible {
     {
      
         id =>
-               {read => 1, type => 'int(11)', default => ''},
+               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
         GroupId => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         MemberId => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
 
  }
 };
@@ -175,7 +200,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index 31cb953..0e7514d 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -64,7 +89,7 @@ sub _Init {
 }
 
 
-=item NewItem
+=head2 NewItem
 
 Returns an empty new RT::GroupMember item
 
@@ -101,7 +126,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index 29f12a5..f8dac13 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
 # 
-# END LICENSE BLOCK
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -64,7 +89,7 @@ sub _Init {
 }
 
 
-=item NewItem
+=head2 NewItem
 
 Returns an empty new RT::Group item
 
@@ -91,8 +116,6 @@ sub NewItem {
         };
 
 
-
-
 =head1 SEE ALSO
 
 This class allows "overlay" methods to be placed
@@ -101,7 +124,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index 5cdb65e..46070ce 100644 (file)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
 # 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
 # 
-# END LICENSE BLOCK
+# END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
-  RT::Handle - RT's database handle
+RT::Handle - RT's database handle
 
 =head1 SYNOPSIS
 
-  use RT::Handle;
+    use RT;
+    BEGIN { RT::LoadConfig() };
+    use RT::Handle;
 
 =head1 DESCRIPTION
 
-=begin testing
-
-ok(require RT::Handle);
-
-=end testing
-
-=head1 METHODS
+C<RT::Handle> is RT specific wrapper over one of L<DBIx::SearchBuilder::Handle>
+classes. As RT works with different types of DBs we subclass repsective handler
+from L<DBIx::SerachBuilder>. Type of the DB is defined by C<DatabasseType> RT's
+config option. You B<must> load this module only when the configs have been
+loaded.
 
 =cut
 
 package RT::Handle;
 
 use strict;
+use warnings;
 use vars qw/@ISA/;
 
-eval "use DBIx::SearchBuilder::Handle::$RT::DatabaseType;
-\@ISA= qw(DBIx::SearchBuilder::Handle::$RT::DatabaseType);";
-#TODO check for errors here.
+=head1 METHODS
+
+=head2 FinalizeDatabaseType
+
+Sets RT::Handle's superclass to the correct subclass of
+L<DBIx::SearchBuilder::Handle>, using the C<DatabaseType> configuration.
+
+=cut
+
+sub FinalizeDatabaseType {
+    eval "use DBIx::SearchBuilder::Handle::". RT->Config->Get('DatabaseType') .";
+    \@ISA= qw(DBIx::SearchBuilder::Handle::". RT->Config->Get('DatabaseType') .");";
+
+    if ($@) {
+        die "Unable to load DBIx::SearchBuilder database handle for '". RT->Config->Get('DatabaseType') ."'.\n".
+            "Perhaps you've picked an invalid database type or spelled it incorrectly.\n".
+            $@;
+    }
+}
 
 =head2 Connect
 
-Connects to RT's database handle.
-Takes nothing. Calls SUPER::Connect with the needed args
+Connects to RT's database using credentials and options from the RT config.
+Takes nothing.
 
 =cut
 
 sub Connect {
-my $self=shift;
+    my $self = shift;
+
+    my $db_type = RT->Config->Get('DatabaseType');
+    if ( $db_type eq 'Oracle' ) {
+        $ENV{'NLS_LANG'} = "AMERICAN_AMERICA.AL32UTF8";
+        $ENV{'NLS_NCHAR'} = "AL32UTF8";
+    }
 
-# Unless the database port is a positive integer, we really don't want to pass it.
+    $self->SUPER::Connect(
+        User => RT->Config->Get('DatabaseUser'),
+        Password => RT->Config->Get('DatabasePassword'),
+    );
 
-$self->SUPER::Connect(
-                        User => $RT::DatabaseUser,
-                        Password => $RT::DatabasePassword,
-                       );
-   
+    if ( $db_type eq 'mysql' ) {
+        my $version = $self->DatabaseVersion;
+        ($version) = $version =~ /^(\d+\.\d+)/;
+        $self->dbh->do("SET NAMES 'utf8'") if $version >= 4.1;
+    }
+
+    $self->dbh->{'LongReadLen'} = RT->Config->Get('MaxAttachmentSize');
 }
 
-=item BuildDSN
+=head2 BuildDSN
 
-Build the DSN for the RT database. doesn't take any parameters, draws all that
-from the config file.
+Build the DSN for the RT database. Doesn't take any parameters, draws all that
+from the config.
 
 =cut
 
+require File::Spec;
 
 sub BuildDSN {
     my $self = shift;
-$RT::DatabasePort = undef unless (defined $RT::DatabasePort && $RT::DatabasePort =~ /^(\d+)$/);
-$RT::DatabaseHost = undef unless (defined $RT::DatabaseHost && $RT::DatabaseHost ne '');
+    # Unless the database port is a positive integer, we really don't want to pass it.
+    my $db_port = RT->Config->Get('DatabasePort');
+    $db_port = undef unless (defined $db_port && $db_port =~ /^(\d+)$/);
+    my $db_host = RT->Config->Get('DatabaseHost');
+    $db_host = undef unless $db_host;
+    my $db_name = RT->Config->Get('DatabaseName');
+    my $db_type = RT->Config->Get('DatabaseType');
+    $db_name = File::Spec->catfile($RT::VarPath, $db_name)
+        if $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name);
+
+    my %args = (
+        Host       => $db_host,
+        Database   => $db_name,
+        Port       => $db_port,
+        Driver     => $db_type,
+        RequireSSL => RT->Config->Get('DatabaseRequireSSL'),
+        DisconnectHandleOnDestroy => 1,
+    );
+    if ( $db_type eq 'Oracle' && $db_host ) {
+        $args{'SID'} = delete $args{'Database'};
+    }
+    $self->SUPER::BuildDSN( %args );
+}
+
+=head2 DSN
+
+Returns the DSN for this handle. In order to get correct value you must
+build DSN first, see L</BuildDSN>.
+
+This is method can be called as class method, in this case creates
+temporary handle object, L</BuildDSN builds DSN> and returns it.
+
+=cut
+
+sub DSN {
+    my $self = shift;
+    return $self->SUPER::DSN if ref $self;
+
+    my $handle = $self->new;
+    $handle->BuildDSN;
+    return $handle->DSN;
+}
+
+=head2 SystemDSN
+
+Returns a DSN suitable for database creates and drops
+and user creates and drops.
+
+Gets RT's DSN first (see L<DSN>) and then change it according
+to requirements of a database system RT's using.
+
+=cut
+
+sub SystemDSN {
+    my $self = shift;
+
+    my $db_name = RT->Config->Get('DatabaseName');
+    my $db_type = RT->Config->Get('DatabaseType');
+
+    my $dsn = $self->DSN;
+    if ( $db_type eq 'mysql' ) {
+        # with mysql, you want to connect sans database to funge things
+        $dsn =~ s/dbname=\Q$db_name//;
+    }
+    elsif ( $db_type eq 'Pg' ) {
+        # with postgres, you want to connect to template1 database
+        $dsn =~ s/dbname=\Q$db_name/dbname=template1/;
+    }
+    elsif ( $db_type eq 'Informix' ) {
+        # with Informix, you want to connect sans database:
+        $dsn =~ s/Informix:\Q$db_name/Informix:/;
+    }
+    return $dsn;
+}
+
+=head2 Database compatibility and integrity checks
+
+
+
+=cut
+
+sub CheckIntegrity {
+    my $self = shift;
+    
+    my $dsn = $self->DSN;
+    my $user = RT->Config->Get('DatabaseUser');
+    my $pass = RT->Config->Get('DatabasePassword');
+
+    my $dbh = DBI->connect(
+        $dsn, $user, $pass,
+        { RaiseError => 0, PrintError => 0 },
+    );
+    unless ( $dbh ) {
+        return (0, 'no connection', "Failed to connect to $dsn as user '$user': ". $DBI::errstr);
+    }
+
+    RT::ConnectToDatabase();
+    RT::InitLogging();
+
+    require RT::CurrentUser;
+    my $test_user = new RT::CurrentUser;
+    $test_user->Load('RT_System');
+    unless ( $test_user->id ) {
+        return (0, 'no system user', "Couldn't find RT_System user in the DB '$dsn'");
+    }
+
+    $test_user = new RT::CurrentUser;
+    $test_user->Load('Nobody');
+    unless ( $test_user->id ) {
+        return (0, 'no nobody user', "Couldn't find Nobody user in the DB '$dsn'");
+    }
+
+    return $dbh;
+}
+
+sub CheckCompatibility {
+    my $self = shift;
+    my $dbh = shift;
+    my $state = shift || 'post';
+
+    my $db_type = RT->Config->Get('DatabaseType');
+    if ( $db_type eq "mysql" ) {
+        # Check which version we're running
+        my $version = ($dbh->selectrow_array("show variables like 'version'"))[1];
+        return (0, "couldn't get version of the mysql server")
+            unless $version;
+
+        ($version) = $version =~ /^(\d+\.\d+)/;
+        return (0, "RT is unsupported on MySQL versions before 4.0.x, it's $version")
+            if $version < 4;
+
+        # MySQL must have InnoDB support
+        my $innodb = ($dbh->selectrow_array("show variables like 'have_innodb'"))[1];
+        if ( lc $innodb eq "no" ) {
+            return (0, "RT requires that MySQL be compiled with InnoDB table support.\n".
+                "See http://dev.mysql.com/doc/mysql/en/InnoDB.html");
+        } elsif ( lc $innodb eq "disabled" ) {
+            return (0, "RT requires that MySQL InnoDB table support be enabled.\n".
+                "Remove the 'skip-innodb' line from your my.cnf file, restart MySQL, and try again.\n");
+        }
+
+        if ( $state eq 'post' ) {
+            my $create_table = $dbh->selectrow_arrayref("SHOW CREATE TABLE Tickets")->[1];
+            unless ( $create_table =~ /(?:ENGINE|TYPE)\s*=\s*InnoDB/i ) {
+                return (0, "RT requires that all its tables be of InnoDB type. Upgrade RT tables.");
+            }
+        }
+        if ( $version >= 4.1 && $state eq 'post' ) {
+            my $create_table = $dbh->selectrow_arrayref("SHOW CREATE TABLE Attachments")->[1];
+            unless ( $create_table =~ /\bContent\b[^,]*BLOB/i ) {
+                return (0, "RT since version 3.8 has new schema for MySQL versions after 4.1.0\n"
+                    ."Follow instructions in the UPGRADING.mysql file.");
+            }
+        }
+    }
+    return (1)
+}
+
+=head2 Database maintanance
+
+=head3 CreateDatabase $DBH
+
+Creates a new database. This method can be used as class method.
+
+Takes DBI handle. Many database systems require special handle to
+allow you to create a new database, so you have to use L<SystemDSN>
+method during connection.
+
+Fetches type and name of the DB from the config.
+
+=cut
+
+sub CreateDatabase {
+    my $self = shift;
+    my $dbh  = shift or return (0, "No DBI handle provided");
+    my $db_type = RT->Config->Get('DatabaseType');
+    my $db_name = RT->Config->Get('DatabaseName');
+
+    my $status;
+    if ( $db_type eq 'SQLite' ) {
+        return (1, 'Skipped as SQLite doesn\'t need any action');
+    }
+    elsif ( $db_type eq 'Oracle' ) {
+        my $db_user = RT->Config->Get('DatabaseUser');
+        my $db_pass = RT->Config->Get('DatabasePassword');
+        $status = $dbh->do(
+            "CREATE USER $db_user IDENTIFIED BY $db_pass"
+            ." default tablespace USERS"
+            ." temporary tablespace TEMP"
+            ." quota unlimited on USERS"
+        );
+        unless ( $status ) {
+            return $status, "Couldn't create user $db_user identified by $db_pass."
+                ."\nError: ". $dbh->errstr;
+        }
+        $status = $dbh->do( "GRANT connect, resource TO $db_user" );
+        unless ( $status ) {
+            return $status, "Couldn't grant connect and resource to $db_user."
+                ."\nError: ". $dbh->errstr;
+        }
+        return (1, "Created user $db_user. All RT's objects should be in his schema.");
+    }
+    elsif ( $db_type eq 'Pg' ) {
+        # XXX: as we get external DBH we don't know if RaiseError or PrintError
+        # are enabled, so we have to setup it here and restore them back
+        $status = $dbh->do("CREATE DATABASE $db_name WITH ENCODING='UNICODE' TEMPLATE template0")
+            || $dbh->do("CREATE DATABASE $db_name TEMPLATE template0");
+    }
+    elsif ( $db_type eq 'Informix' ) {
+        local $ENV{'DB_LOCALE'} = 'en_us.utf8';
+        $status = $dbh->do("CREATE DATABASE $db_name WITH BUFFERED LOG");
+    }
+    else {
+        $status = $dbh->do("CREATE DATABASE $db_name");
+    }
+    return ($status, $DBI::errstr);
+}
+
+=head3 DropDatabase $DBH [Force => 0]
+
+Drops RT's database. This method can be used as class method.
+
+Takes DBI handle as first argument. Many database systems require
+special handle to allow you to create a new database, so you have
+to use L<SystemDSN> method during connection.
+
+Fetches type and name of the DB from the config.
+
+=cut
+
+sub DropDatabase {
+    my $self = shift;
+    my $dbh  = shift or return (0, "No DBI handle provided");
+
+    my $db_type = RT->Config->Get('DatabaseType');
+    my $db_name = RT->Config->Get('DatabaseName');
+
+    if ( $db_type eq 'Oracle' || $db_type eq 'Informix' ) {
+        my $db_user = RT->Config->Get('DatabaseUser');
+        my $status = $dbh->do( "DROP USER $db_user CASCADE" );
+        unless ( $status ) {
+            return 0, "Couldn't drop user $db_user."
+                ."\nError: ". $dbh->errstr;
+        }
+        return (1, "Successfully dropped user '$db_user' with his schema.");
+    }
+    elsif ( $db_type eq 'SQLite' ) {
+        my $path = $db_name;
+        $path = "$RT::VarPath/$path" unless substr($path, 0, 1) eq '/';
+        unlink $path or return (0, "Couldn't remove '$path': $!");
+        return (1);
+    } else {
+        $dbh->do("DROP DATABASE ". $db_name)
+            or return (0, $DBI::errstr);
+    }
+    return (1);
+}
+
+=head2 InsertACL
+
+=cut
+
+sub InsertACL {
+    my $self      = shift;
+    my $dbh       = shift;
+    my $base_path = shift || $RT::EtcPath;
+
+    my $db_type = RT->Config->Get('DatabaseType');
+    return (1) if $db_type eq 'SQLite';
+
+    $dbh = $self->dbh if !$dbh && ref $self;
+    return (0, "No DBI handle provided") unless $dbh;
+
+    return (0, "'$base_path' doesn't exist") unless -e $base_path;
+
+    my $path;
+    if ( -d $base_path ) {
+        $path = File::Spec->catfile( $base_path, "acl.$db_type");
+        $path = $self->GetVersionFile($dbh, $path);
+
+        $path = File::Spec->catfile( $base_path, "acl")
+            unless $path && -e $path;
+        return (0, "Couldn't find ACLs for $db_type")
+            unless -e $path;
+    } else {
+        $path = $base_path;
+    }
+
+    local *acl;
+    do $path || return (0, "Couldn't load ACLs: " . $@);
+    my @acl = acl($dbh);
+    foreach my $statement (@acl) {
+        my $sth = $dbh->prepare($statement)
+            or return (0, "Couldn't prepare SQL query:\n $statement\n\nERROR: ". $dbh->errstr);
+        unless ( $sth->execute ) {
+            return (0, "Couldn't run SQL query:\n $statement\n\nERROR: ". $sth->errstr);
+        }
+    }
+    return (1);
+}
+
+=head2 InsertSchema
+
+=cut
+
+sub InsertSchema {
+    my $self = shift;
+    my $dbh  = shift;
+    my $base_path = (shift || $RT::EtcPath);
+
+    $dbh = $self->dbh if !$dbh && ref $self;
+    return (0, "No DBI handle provided") unless $dbh;
+
+    my $db_type = RT->Config->Get('DatabaseType');
+
+    my $file;
+    if ( -d $base_path ) {
+        $file = $base_path . "/schema." . $db_type;
+    } else {
+        $file = $base_path;
+    }
+
+    $file = $self->GetVersionFile( $dbh, $file );
+    unless ( $file ) {
+        return (0, "Couldn't find schema file(s) '$file*'");
+    }
+    unless ( -f $file && -r $file ) {
+        return (0, "File '$file' doesn't exist or couldn't be read");
+    }
+
+    my (@schema);
+
+    open my $fh_schema, "<$file";
+
+    my $has_local = 0;
+    open my $fh_schema_local, "<" . $self->GetVersionFile( $dbh, $RT::LocalEtcPath . "/schema." . $db_type )
+        and $has_local = 1;
+
+    my $statement = "";
+    foreach my $line ( <$fh_schema>, ($_ = ';;'), $has_local? <$fh_schema_local>: () ) {
+        $line =~ s/\#.*//g;
+        $line =~ s/--.*//g;
+        $statement .= $line;
+        if ( $line =~ /;(\s*)$/ ) {
+            $statement =~ s/;(\s*)$//g;
+            push @schema, $statement;
+            $statement = "";
+        }
+    }
+    close $fh_schema; close $fh_schema_local;
+
+    if ( $db_type eq 'Oracle' ) {
+        my $db_user = RT->Config->Get('DatabaseUser');
+        my $status = $dbh->do( "ALTER SESSION SET CURRENT_SCHEMA=$db_user" );
+        unless ( $status ) {
+            return $status, "Couldn't set current schema to $db_user."
+                ."\nError: ". $dbh->errstr;
+        }
+    }
+
+    local $SIG{__WARN__} = sub {};
+    my $is_local = 0;
+    $dbh->begin_work or return (0, "Couldn't begin transaction: ". $dbh->errstr);
+    foreach my $statement (@schema) {
+        if ( $statement =~ /^\s*;$/ ) {
+            $is_local = 1; next;
+        }
+
+        my $sth = $dbh->prepare($statement)
+            or return (0, "Couldn't prepare SQL query:\n$statement\n\nERROR: ". $dbh->errstr);
+        unless ( $sth->execute or $is_local ) {
+            return (0, "Couldn't run SQL query:\n$statement\n\nERROR: ". $sth->errstr);
+        }
+    }
+    $dbh->commit or return (0, "Couldn't commit transaction: ". $dbh->errstr);
+    return (1);
+}
+
+=head1 GetVersionFile
+
+Takes base name of the file as argument, scans for <base name>-<version> named
+files and returns file name with closest version to the version of the RT DB.
+
+=cut
+
+sub GetVersionFile {
+    my $self = shift;
+    my $dbh = shift;
+    my $base_name = shift;
 
-    $self->SUPER::BuildDSN(Host => $RT::DatabaseHost, 
-                        Database => $RT::DatabaseName, 
-                        Port => $RT::DatabasePort,
-                        Driver => $RT::DatabaseType,
-                        RequireSSL => $RT::DatabaseRequireSSL,
-             DisconnectHandleOnDestroy => 1
-                       );
-   
+    my $db_version = ref $self
+        ? $self->DatabaseVersion
+        : do {
+            my $tmp = RT::Handle->new;
+            $tmp->dbh($dbh);
+            $tmp->DatabaseVersion;
+        };
 
+    require File::Glob;
+    my @files = File::Glob::bsd_glob("$base_name*");
+    return '' unless @files;
+
+    my %version = map { $_ =~ /\.\w+-([-\w\.]+)$/; ($1||0) => $_ } @files;
+    my $version;
+    foreach ( reverse sort cmp_version keys %version ) {
+        if ( cmp_version( $db_version, $_ ) >= 0 ) {
+            $version = $_;
+            last;
+        }
+    }
+
+    return defined $version? $version{ $version } : undef;
+}
+
+sub cmp_version($$) {
+    my ($a, $b) = (@_);
+    $b =~ s/HEAD$/9999/;
+    my @a = split /[^0-9]+/, $a;
+    my @b = split /[^0-9]+/, $b;
+    for ( my $i = 0; $i < @a; $i++ ) {
+        return 1 unless defined $b[$i];
+        return $a[$i] <=> $b[$i] if $a[$i] <=> $b[$i];
+    }
+    return 0 if @a == @b;
+    return -1;
 }
 
+
+=head2 InsertInitialData
+
+Inserts system objects into RT's DB, like system user or 'nobody',
+internal groups and other records required. However, this method
+doesn't insert any real users like 'root' and you have to use
+InsertData or another way to do that.
+
+Takes no arguments. Returns status and message tuple.
+
+It's safe to call this method even if those objects already exist.
+
+=cut
+
+sub InsertInitialData {
+    my $self    = shift;
+
+    my @warns;
+
+    # create RT_System user and grant him rights
+    {
+        require RT::CurrentUser;
+
+        my $test_user = RT::User->new( new RT::CurrentUser );
+        $test_user->Load('RT_System');
+        if ( $test_user->id ) {
+            push @warns, "Found system user in the DB.";
+        }
+        else {
+            my $user = RT::User->new( new RT::CurrentUser );
+            my ( $val, $msg ) = $user->_BootstrapCreate(
+                Name     => 'RT_System',
+                RealName => 'The RT System itself',
+                Comments => 'Do not delete or modify this user. '
+                    . 'It is integral to RT\'s internal database structures',
+                Creator  => '1',
+                LastUpdatedBy => '1',
+            );
+            return ($val, $msg) unless $val;
+        }
+        DBIx::SearchBuilder::Record::Cachable->FlushCache;
+    }
+
+    # init RT::SystemUser and RT::System objects
+    RT::InitSystemObjects();
+    unless ( $RT::SystemUser->id ) {
+        return (0, "Couldn't load system user");
+    }
+
+    # grant SuperUser right to system user
+    {
+        my $test_ace = RT::ACE->new( $RT::SystemUser );
+        $test_ace->LoadByCols(
+            PrincipalId   => ACLEquivGroupId( $RT::SystemUser->Id ),
+            PrincipalType => 'Group',
+            RightName     => 'SuperUser',
+            ObjectType    => 'RT::System',
+            ObjectId      => 1,
+        );
+        if ( $test_ace->id ) {
+            push @warns, "System user has global SuperUser right.";
+        } else {
+            my $ace = RT::ACE->new( $RT::SystemUser );
+            my ( $val, $msg ) = $ace->_BootstrapCreate(
+                PrincipalId   => ACLEquivGroupId( $RT::SystemUser->Id ),
+                PrincipalType => 'Group',
+                RightName     => 'SuperUser',
+                ObjectType    => 'RT::System',
+                ObjectId      => 1,
+            );
+            return ($val, $msg) unless $val;
+        }
+        DBIx::SearchBuilder::Record::Cachable->FlushCache;
+    }
+
+    # system groups
+    # $self->loc('Everyone'); # For the string extractor to get a string to localize
+    # $self->loc('Privileged'); # For the string extractor to get a string to localize
+    # $self->loc('Unprivileged'); # For the string extractor to get a string to localize
+    foreach my $name (qw(Everyone Privileged Unprivileged)) {
+        my $group = RT::Group->new( $RT::SystemUser );
+        $group->LoadSystemInternalGroup( $name );
+        if ( $group->id ) {
+            push @warns, "System group '$name' already exists.";
+            next;
+        }
+
+        $group = RT::Group->new( $RT::SystemUser );
+        my ( $val, $msg ) = $group->_Create(
+            Type        => $name,
+            Domain      => 'SystemInternal',
+            Description => 'Pseudogroup for internal use',  # loc
+            Name        => '',
+            Instance    => '',
+        );
+        return ($val, $msg) unless $val;
+    }
+
+    # nobody
+    {
+        my $user = RT::User->new( $RT::SystemUser );
+        $user->Load('Nobody');
+        if ( $user->id ) {
+            push @warns, "Found 'Nobody' user in the DB.";
+        }
+        else {
+            my ( $val, $msg ) = $user->Create(
+                Name     => 'Nobody',
+                RealName => 'Nobody in particular',
+                Comments => 'Do not delete or modify this user. It is integral '
+                    .'to RT\'s internal data structures',
+                Privileged => 0,
+            );
+            return ($val, $msg) unless $val;
+        }
+
+        if ( $user->HasRight( Right => 'OwnTicket', Object => $RT::System ) ) {
+            push @warns, "User 'Nobody' has global OwnTicket right.";
+        } else {
+            my ( $val, $msg ) = $user->PrincipalObj->GrantRight(
+                Right => 'OwnTicket',
+                Object => $RT::System,
+            );
+            return ($val, $msg) unless $val;
+        }
+    }
+
+    # rerun to get init Nobody as well
+    RT::InitSystemObjects();
+
+    # system role groups
+    foreach my $name (qw(Owner Requestor Cc AdminCc)) {
+        my $group = RT::Group->new( $RT::SystemUser );
+        $group->LoadSystemRoleGroup( $name );
+        if ( $group->id ) {
+            push @warns, "System role '$name' already exists.";
+            next;
+        }
+
+        $group = RT::Group->new( $RT::SystemUser );
+        my ( $val, $msg ) = $group->_Create(
+            Type        => $name,
+            Domain      => 'RT::System-Role',
+            Description => 'SystemRolegroup for internal use',  # loc
+            Name        => '',
+            Instance    => '',
+        );
+        return ($val, $msg) unless $val;
+    }
+
+    push @warns, "You appear to have a functional RT database."
+        if @warns;
+
+    return (1, join "\n", @warns);
+}
+
+=head2 InsertData
+
+Load some sort of data into the database, takes path to a file.
+
+=cut
+
+sub InsertData {
+    my $self     = shift;
+    my $datafile = shift;
+
+    # Slurp in stuff to insert from the datafile. Possible things to go in here:-
+    our (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
+           @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
+    local (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
+           @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
+
+    local $@;
+    $RT::Logger->debug("Going to load '$datafile' data file");
+    eval { require $datafile }
+      or return (0, "Couldn't load data from '$datafile' for import:\n\nERROR:". $@);
+
+    if ( @Initial ) {
+        $RT::Logger->debug("Running initial actions...");
+        foreach ( @Initial ) {
+            local $@;
+            eval { $_->(); 1 } or return (0, "One of initial functions failed: $@");
+        }
+        $RT::Logger->debug("Done.");
+    }
+    if ( @Groups ) {
+        $RT::Logger->debug("Creating groups...");
+        foreach my $item (@Groups) {
+            my $new_entry = RT::Group->new( $RT::SystemUser );
+            my $member_of = delete $item->{'MemberOf'};
+            my ( $return, $msg ) = $new_entry->_Create(%$item);
+            unless ( $return ) {
+                $RT::Logger->error( $msg );
+                next;
+            } else {
+                $RT::Logger->debug($return .".");
+            }
+            if ( $member_of ) {
+                $member_of = [ $member_of ] unless ref $member_of eq 'ARRAY';
+                foreach( @$member_of ) {
+                    my $parent = RT::Group->new($RT::SystemUser);
+                    if ( ref $_ eq 'HASH' ) {
+                        $parent->LoadByCols( %$_ );
+                    }
+                    elsif ( !ref $_ ) {
+                        $parent->LoadUserDefinedGroup( $_ );
+                    }
+                    else {
+                        $RT::Logger->error(
+                            "(Error: wrong format of MemberOf field."
+                            ." Should be name of user defined group or"
+                            ." hash reference with 'column => value' pairs."
+                            ." Use array reference to add to multiple groups)"
+                        );
+                        next;
+                    }
+                    unless ( $parent->Id ) {
+                        $RT::Logger->error("(Error: couldn't load group to add member)");
+                        next;
+                    }
+                    my ( $return, $msg ) = $parent->AddMember( $new_entry->Id );
+                    unless ( $return ) {
+                        $RT::Logger->error( $msg );
+                    } else {
+                        $RT::Logger->debug( $return ."." );
+                    }
+                }
+            }
+        }
+        $RT::Logger->debug("done.");
+    }
+    if ( @Users ) {
+        $RT::Logger->debug("Creating users...");
+        foreach my $item (@Users) {
+            my $new_entry = new RT::User( $RT::SystemUser );
+            my ( $return, $msg ) = $new_entry->Create(%$item);
+            unless ( $return ) {
+                $RT::Logger->error( $msg );
+            } else {
+                $RT::Logger->debug( $return ."." );
+            }
+        }
+        $RT::Logger->debug("done.");
+    }
+    if ( @Queues ) {
+        $RT::Logger->debug("Creating queues...");
+        for my $item (@Queues) {
+            my $new_entry = new RT::Queue($RT::SystemUser);
+            my ( $return, $msg ) = $new_entry->Create(%$item);
+            unless ( $return ) {
+                $RT::Logger->error( $msg );
+            } else {
+                $RT::Logger->debug( $return ."." );
+            }
+        }
+        $RT::Logger->debug("done.");
+    }
+    if ( @CustomFields ) {
+        $RT::Logger->debug("Creating custom fields...");
+        for my $item ( @CustomFields ) {
+            my $new_entry = new RT::CustomField( $RT::SystemUser );
+            my $values    = delete $item->{'Values'};
+
+            my @queues;
+            # if ref then it's list of queues, so we do things ourself
+            if ( exists $item->{'Queue'} && ref $item->{'Queue'} ) {
+                $item->{'LookupType'} = 'RT::Queue-RT::Ticket';
+                @queues = @{ delete $item->{'Queue'} };
+            }
+
+            my ( $return, $msg ) = $new_entry->Create(%$item);
+            unless( $return ) {
+                $RT::Logger->error( $msg );
+                next;
+            }
+
+            foreach my $value ( @{$values} ) {
+                my ( $return, $msg ) = $new_entry->AddValue(%$value);
+                $RT::Logger->error( $msg ) unless $return;
+            }
+
+            # apply by default
+            if ( !@queues && !exists $item->{'Queue'} && $item->{LookupType} ) {
+                my $ocf = RT::ObjectCustomField->new($RT::SystemUser);
+                $ocf->Create( CustomField => $new_entry->Id );
+            }
+
+            for my $q (@queues) {
+                my $q_obj = RT::Queue->new($RT::SystemUser);
+                $q_obj->Load($q);
+                unless ( $q_obj->Id ) {
+                    $RT::Logger->error("Could not find queue ". $q );
+                    next;
+                }
+                my $OCF = RT::ObjectCustomField->new($RT::SystemUser);
+                ( $return, $msg ) = $OCF->Create(
+                    CustomField => $new_entry->Id,
+                    ObjectId    => $q_obj->Id,
+                );
+                $RT::Logger->error( $msg ) unless $return and $OCF->Id;
+            }
+        }
+
+        $RT::Logger->debug("done.");
+    }
+    if ( @ACL ) {
+        $RT::Logger->debug("Creating ACL...");
+        for my $item (@ACL) {
+
+            my ($princ, $object);
+
+            # Global rights or Queue rights?
+            if ( $item->{'CF'} ) {
+                $object = RT::CustomField->new( $RT::SystemUser );
+                my @columns = ( Name => $item->{'CF'} );
+                push @columns, Queue => $item->{'Queue'} if $item->{'Queue'} and not ref $item->{'Queue'};
+                $object->LoadByName( @columns );
+            } elsif ( $item->{'Queue'} ) {
+                $object = RT::Queue->new($RT::SystemUser);
+                $object->Load( $item->{'Queue'} );
+            } else {
+                $object = $RT::System;
+            }
+
+            $RT::Logger->error("Couldn't load object") and next unless $object and $object->Id;
+
+            # Group rights or user rights?
+            if ( $item->{'GroupDomain'} ) {
+                $princ = RT::Group->new($RT::SystemUser);
+                if ( $item->{'GroupDomain'} eq 'UserDefined' ) {
+                  $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
+                } elsif ( $item->{'GroupDomain'} eq 'SystemInternal' ) {
+                  $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
+                } elsif ( $item->{'GroupDomain'} eq 'RT::System-Role' ) {
+                  $princ->LoadSystemRoleGroup( $item->{'GroupType'} );
+                } elsif ( $item->{'GroupDomain'} eq 'RT::Queue-Role' &&
+                          $item->{'Queue'} )
+                {
+                  $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
+                                              Queue => $object->id);
+                } else {
+                  $princ->Load( $item->{'GroupId'} );
+                }
+            } else {
+                $princ = RT::User->new($RT::SystemUser);
+                $princ->Load( $item->{'UserId'} );
+            }
+
+            # Grant it
+            my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
+                Right => $item->{'Right'},
+                Object => $object
+            );
+            unless ( $return ) {
+                $RT::Logger->error( $msg );
+            }
+            else {
+                $RT::Logger->debug( $return ."." );
+            }
+        }
+        $RT::Logger->debug("done.");
+    }
+
+    if ( @ScripActions ) {
+        $RT::Logger->debug("Creating ScripActions...");
+
+        for my $item (@ScripActions) {
+            my $new_entry = RT::ScripAction->new($RT::SystemUser);
+            my ( $return, $msg ) = $new_entry->Create(%$item);
+            unless ( $return ) {
+                $RT::Logger->error( $msg );
+            }
+            else {
+                $RT::Logger->debug( $return ."." );
+            }
+        }
+
+        $RT::Logger->debug("done.");
+    }
+
+    if ( @ScripConditions ) {
+        $RT::Logger->debug("Creating ScripConditions...");
+
+        for my $item (@ScripConditions) {
+            my $new_entry = RT::ScripCondition->new($RT::SystemUser);
+            my ( $return, $msg ) = $new_entry->Create(%$item);
+            unless ( $return ) {
+                $RT::Logger->error( $msg );
+            }
+            else {
+                $RT::Logger->debug( $return ."." );
+            }
+        }
+
+        $RT::Logger->debug("done.");
+    }
+
+    if ( @Templates ) {
+        $RT::Logger->debug("Creating templates...");
+
+        for my $item (@Templates) {
+            my $new_entry = new RT::Template($RT::SystemUser);
+            my ( $return, $msg ) = $new_entry->Create(%$item);
+            unless ( $return ) {
+                $RT::Logger->error( $msg );
+            }
+            else {
+                $RT::Logger->debug( $return ."." );
+            }
+        }
+        $RT::Logger->debug("done.");
+    }
+    if ( @Scrips ) {
+        $RT::Logger->debug("Creating scrips...");
+
+        for my $item (@Scrips) {
+            my $new_entry = new RT::Scrip($RT::SystemUser);
+
+            my @queues = ref $item->{'Queue'} eq 'ARRAY'? @{ $item->{'Queue'} }: $item->{'Queue'} || 0;
+            push @queues, 0 unless @queues; # add global queue at least
+
+            foreach my $q ( @queues ) {
+                my ( $return, $msg ) = $new_entry->Create( %$item, Queue => $q );
+                unless ( $return ) {
+                    $RT::Logger->error( $msg );
+                }
+                else {
+                    $RT::Logger->debug( $return ."." );
+                }
+            }
+        }
+        $RT::Logger->debug("done.");
+    }
+    if ( @Attributes ) {
+        $RT::Logger->debug("Creating predefined searches...");
+        my $sys = RT::System->new($RT::SystemUser);
+
+        for my $item (@Attributes) {
+            my $obj = delete $item->{Object}; # XXX: make this something loadable
+            $obj ||= $sys;
+            my ( $return, $msg ) = $obj->AddAttribute (%$item);
+            unless ( $return ) {
+                $RT::Logger->error( $msg );
+            }
+            else {
+                $RT::Logger->debug( $return ."." );
+            }
+        }
+        $RT::Logger->debug("done.");
+    }
+    if ( @Final ) {
+        $RT::Logger->debug("Running final actions...");
+        for ( @Final ) {
+            local $@;
+            eval { $_->(); };
+            $RT::Logger->error( "Failed to run one of final actions: $@" )
+                if $@;
+        }
+        $RT::Logger->debug("done.");
+    }
+
+    my $db_type = RT->Config->Get('DatabaseType');
+    $RT::Handle->Disconnect() unless $db_type eq 'SQLite';
+
+    $RT::Logger->debug("Done setting up database content.");
+
+# TODO is it ok to return 1 here? If so, the previous codes in this sub
+# should return (0, $msg) if error happens instead of just warning.
+# anyway, we need to return something here to tell if everything is ok
+    return( 1, 'Done inserting data' );
+}
+
+=head2 ACLEquivGroupId
+
+Given a userid, return that user's acl equivalence group
+
+=cut
+
+sub ACLEquivGroupId {
+    my $id = shift;
+
+    my $cu = $RT::SystemUser;
+    unless ( $cu ) {
+        require RT::CurrentUser;
+        $cu = new RT::CurrentUser;
+        $cu->LoadByName('RT_System');
+        warn "Couldn't load RT_System user" unless $cu->id;
+    }
+
+    my $equiv_group = RT::Group->new( $cu );
+    $equiv_group->LoadACLEquivalenceGroup( $id );
+    return $equiv_group->Id;
+}
+
+__PACKAGE__->FinalizeDatabaseType;
+
 eval "require RT::Handle_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Handle_Vendor.pm});
 eval "require RT::Handle_Local";
index ec0e877..a910fb4 100644 (file)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
 # 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
 # 
-# END LICENSE BLOCK
+# END BPS TAGGED BLOCK }}}
+
 use strict;
 
 use RT;
@@ -29,14 +54,12 @@ package RT::Interface::CLI;
 
 
 BEGIN {
-    use Exporter ();
-    use vars qw ($VERSION  @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+    use base 'Exporter';
+    use vars qw ($VERSION  @EXPORT @EXPORT_OK %EXPORT_TAGS);
     
     # set the version for version checking
-    $VERSION = do { my @r = (q$Revision: 1.2 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker
-    
-    @ISA         = qw(Exporter);
-    
+    $VERSION = do { my @r = (q$Revision: 1.1.1.8 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker
+
     # your exported package globals go here,
     # as well as any optionally exported functions
     @EXPORT_OK   = qw(&CleanEnv 
@@ -76,11 +99,6 @@ BEGIN {
 
 =head1 METHODS
 
-=begin testing
-
-ok(require RT::Interface::CLI);
-
-=end testing
 
 =cut
 
@@ -202,7 +220,7 @@ sub GetMessageContent {
     if ($edit) {       
 
        unless ($ENV{'EDITOR'}) {
-           $RT::Logger->crit('No $EDITOR variable defined'. "\n");
+           $RT::Logger->crit('No $EDITOR variable defined');
            return undef;
        }
        system ($ENV{'EDITOR'}, $filename);
@@ -225,7 +243,7 @@ sub debug {
     my $val = shift;
     my ($debug);
     if ($val) {
-       $RT::Logger->debug($val."\n");
+       $RT::Logger->debug($val);
        if ($debug) {
            print STDERR "$val\n";
        }
index 7eec050..e0815fb 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 package RT::Interface::Email;
 
 use strict;
-use Mail::Address;
+use warnings;
+
+use Email::Address;
 use MIME::Entity;
 use RT::EmailParser;
-
+use File::Temp;
+use UNIVERSAL::require;
+use Mail::Mailer ();
 
 BEGIN {
-    use Exporter ();
-    use vars qw ($VERSION  @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
-    
+    use base 'Exporter';
+    use vars qw ( @EXPORT_OK);
+
     # set the version for version checking
-    $VERSION = do { my @r = (q$Revision: 1.2 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker
-    
-    @ISA         = qw(Exporter);
-    
+    our $VERSION = 2.0;
+
     # your exported package globals go here,
     # as well as any optionally exported functions
-    @EXPORT_OK   = qw(
-              &CreateUser
-                     &GetMessageContent
-                     &CheckForLoops 
-                     &CheckForSuspiciousSender
-                     &CheckForAutoGenerated 
-                     &MailError 
-                     &ParseCcAddressesFromHead
-                     &ParseSenderAddressFromHead 
-                     &ParseErrorsToAddressFromHead
-                      &ParseAddressFromHeader
-              &Gateway);
+    @EXPORT_OK = qw(
+        &CreateUser
+        &GetMessageContent
+        &CheckForLoops
+        &CheckForSuspiciousSender
+        &CheckForAutoGenerated
+        &CheckForBounce
+        &MailError
+        &ParseCcAddressesFromHead
+        &ParseSenderAddressFromHead
+        &ParseErrorsToAddressFromHead
+        &ParseAddressFromHeader
+        &Gateway);
 
 }
 
 =head1 NAME
 
-  RT::Interface::CLI - helper functions for creating a commandline RT interface
+  RT::Interface::Email - helper functions for parsing email sent to RT
 
 =head1 SYNOPSIS
 
@@ -69,425 +97,1612 @@ BEGIN {
 =head1 DESCRIPTION
 
 
-=begin testing
-
-ok(require RT::Interface::Email);
-
-=end testing
 
 
 =head1 METHODS
 
-=cut
+=head2 CheckForLoops HEAD
 
+Takes a HEAD object of L<MIME::Head> class and returns true if the
+message's been sent by this RT instance. Uses "X-RT-Loop-Prevention"
+field of the head for test.
 
-# {{{ sub CheckForLoops 
+=cut
 
-sub CheckForLoops  {
+sub CheckForLoops {
     my $head = shift;
-    
-    #If this instance of RT sent it our, we don't want to take it in
+
+    # If this instance of RT sent it our, we don't want to take it in
     my $RTLoop = $head->get("X-RT-Loop-Prevention") || "";
-    chomp ($RTLoop); #remove that newline
-    if ($RTLoop eq "$RT::rtname") {
-       return (1);
+    chomp ($RTLoop); # remove that newline
+    if ( $RTLoop eq RT->Config->Get('rtname') ) {
+        return 1;
     }
-    
+
     # TODO: We might not trap the case where RT instance A sends a mail
     # to RT instance B which sends a mail to ...
-    return (undef);
+    return undef;
 }
 
-# }}}
+=head2 CheckForSuspiciousSender HEAD
+
+Takes a HEAD object of L<MIME::Head> class and returns true if sender
+is suspicious. Suspicious means mailer daemon.
 
-# {{{ sub CheckForSuspiciousSender
+See also L</ParseSenderAddressFromHead>.
+
+=cut
 
 sub CheckForSuspiciousSender {
     my $head = shift;
 
     #if it's from a postmaster or mailer daemon, it's likely a bounce.
-    
+
     #TODO: better algorithms needed here - there is no standards for
     #bounces, so it's very difficult to separate them from anything
     #else.  At the other hand, the Return-To address is only ment to be
     #used as an error channel, we might want to put up a separate
     #Return-To address which is treated differently.
-    
+
     #TODO: search through the whole email and find the right Ticket ID.
 
-    my ($From, $junk) = ParseSenderAddressFromHead($head);
-    
-    if (($From =~ /^mailer-daemon/i) or
-       ($From =~ /^postmaster/i)){
-       return (1);
-       
+    my ( $From, $junk ) = ParseSenderAddressFromHead($head);
+
+    if (   ( $From =~ /^mailer-daemon\@/i )
+        or ( $From =~ /^postmaster\@/i )
+        or ( $From eq "" ))
+    {
+        return (1);
+
     }
-    
-    return (undef);
 
+    return undef;
 }
 
-# }}}
+=head2 CheckForAutoGenerated HEAD
+
+Takes a HEAD object of L<MIME::Head> class and returns true if message
+is autogenerated. Checks 'Precedence' and 'X-FC-Machinegenerated'
+fields of the head in tests.
+
+=cut
 
-# {{{ sub CheckForAutoGenerated
 sub CheckForAutoGenerated {
     my $head = shift;
-    
-    my $Precedence = $head->get("Precedence") || "" ;
-    if ($Precedence =~ /^(bulk|junk)/i) {
-       return (1);
+
+    my $Precedence = $head->get("Precedence") || "";
+    if ( $Precedence =~ /^(bulk|junk)/i ) {
+        return (1);
     }
-    else {
-       return (0);
+
+    # Per RFC3834, any Auto-Submitted header which is not "no" means
+    # it is auto-generated.
+    my $AutoSubmitted = $head->get("Auto-Submitted") || "";
+    if ( length $AutoSubmitted and $AutoSubmitted ne "no" ) {
+        return (1);
+    }
+
+    # First Class mailer uses this as a clue.
+    my $FCJunk = $head->get("X-FC-Machinegenerated") || "";
+    if ( $FCJunk =~ /^true/i ) {
+        return (1);
     }
+
+    return (0);
 }
 
-# }}}
 
+sub CheckForBounce {
+    my $head = shift;
+
+    my $ReturnPath = $head->get("Return-path") || "";
+    return ( $ReturnPath =~ /<>/ );
+}
+
+
+=head2 MailError PARAM HASH
+
+Sends an error message. Takes a param hash:
+
+=over 4
+
+=item From - sender's address, by default is 'CorrespondAddress';
+
+=item To - recipient, by default is 'OwnerEmail';
+
+=item Bcc - optional Bcc recipients;
+
+=item Subject - subject of the message, default is 'There has been an error';
+
+=item Explanation - main content of the error, default value is 'Unexplained error';
+
+=item MIMEObj - optional MIME entity that's attached to the error mail, as well we
+add 'In-Reply-To' field to the error that points to this message.
+
+=item Attach - optional text that attached to the error as 'message/rfc822' part.
+
+=item LogLevel - log level under which we should write explanation message into the
+log, by default we log it as critical.
+
+=back
+
+=cut
 
-# {{{ sub MailError 
 sub MailError {
-    my %args = (To => $RT::OwnerEmail,
-               Bcc => undef,
-               From => $RT::CorrespondAddress,
-               Subject => 'There has been an error',
-               Explanation => 'Unexplained error',
-               MIMEObj => undef,
-               LogLevel => 'crit',
-               @_);
-
-
-    $RT::Logger->log(level => $args{'LogLevel'}, 
-                    message => $args{'Explanation'}
-                   );
-    my $entity = MIME::Entity->build( Type  =>"multipart/mixed",
-                                     From => $args{'From'},
-                                     Bcc => $args{'Bcc'},
-                                     To => $args{'To'},
-                                     Subject => $args{'Subject'},
-                                     'X-RT-Loop-Prevention' => $RT::rtname,
-                                   );
-
-    $entity->attach(  Data => $args{'Explanation'}."\n");
-    
-    my $mimeobj = $args{'MIMEObj'};
-    if ($mimeobj) {
-        $mimeobj->sync_headers();
-        $entity->add_part($mimeobj);
+    my %args = (
+        To          => RT->Config->Get('OwnerEmail'),
+        Bcc         => undef,
+        From        => RT->Config->Get('CorrespondAddress'),
+        Subject     => 'There has been an error',
+        Explanation => 'Unexplained error',
+        MIMEObj     => undef,
+        Attach      => undef,
+        LogLevel    => 'crit',
+        @_
+    );
+
+    $RT::Logger->log(
+        level   => $args{'LogLevel'},
+        message => $args{'Explanation'}
+    ) if $args{'LogLevel'};
+
+    # the colons are necessary to make ->build include non-standard headers
+    my %entity_args = (
+        Type                    => "multipart/mixed",
+        From                    => $args{'From'},
+        Bcc                     => $args{'Bcc'},
+        To                      => $args{'To'},
+        Subject                 => $args{'Subject'},
+        'X-RT-Loop-Prevention:' => RT->Config->Get('rtname'),
+    );
+
+    # only set precedence if the sysadmin wants us to
+    if (defined(RT->Config->Get('DefaultErrorMailPrecedence'))) {
+        $entity_args{'Precedence:'} = RT->Config->Get('DefaultErrorMailPrecedence');
+    }
+
+    my $entity = MIME::Entity->build(%entity_args);
+    SetInReplyTo( Message => $entity, InReplyTo => $args{'MIMEObj'} );
+
+    $entity->attach( Data => $args{'Explanation'} . "\n" );
+
+    if ( $args{'MIMEObj'} ) {
+        $args{'MIMEObj'}->sync_headers;
+        $entity->add_part( $args{'MIMEObj'} );
+    }
+
+    if ( $args{'Attach'} ) {
+        $entity->attach( Data => $args{'Attach'}, Type => 'message/rfc822' );
+
+    }
+
+    SendEmail( Entity => $entity, Bounce => 1 );
+}
+
+
+=head2 SendEmail Entity => undef, [ Bounce => 0, Ticket => undef, Transaction => undef ]
+
+Sends an email (passed as a L<MIME::Entity> object C<ENTITY>) using
+RT's outgoing mail configuration. If C<BOUNCE> is passed, and is a
+true value, the message will be marked as an autogenerated error, if
+possible. Sets Date field of the head to now if it's not set.
+
+If the C<X-RT-Squelch> header is set to any true value, the mail will
+not be sent. One use is to let extensions easily cancel outgoing mail.
+
+Ticket and Transaction arguments are optional. If Transaction is
+specified and Ticket is not then ticket of the transaction is
+used, but only if the transaction belongs to a ticket.
+
+Returns 1 on success, 0 on error or -1 if message has no recipients
+and hasn't been sent.
+
+=head3 Signing and Encrypting
+
+This function as well signs and/or encrypts the message according to
+headers of a transaction's attachment or properties of a ticket's queue.
+To get full access to the configuration Ticket and/or Transaction
+arguments must be provided, but you can force behaviour using Sign
+and/or Encrypt arguments.
+
+The following precedence of arguments are used to figure out if
+the message should be encrypted and/or signed:
+
+* if Sign or Encrypt argument is defined then its value is used
+
+* else if Transaction's first attachment has X-RT-Sign or X-RT-Encrypt
+header field then it's value is used
+
+* else properties of a queue of the Ticket are used.
+
+=cut
+
+sub SendEmail {
+    my (%args) = (
+        Entity => undef,
+        Bounce => 0,
+        Ticket => undef,
+        Transaction => undef,
+        @_,
+    );
+
+    my $TicketObj = $args{'Ticket'};
+    my $TransactionObj = $args{'Transaction'};
+
+    foreach my $arg( qw(Entity Bounce) ) {
+        next unless defined $args{ lc $arg };
+
+        $RT::Logger->warning("'". lc($arg) ."' argument is deprecated, use '$arg' instead");
+        $args{ $arg } = delete $args{ lc $arg };
+    }
+
+    unless ( $args{'Entity'} ) {
+        $RT::Logger->crit( "Could not send mail without 'Entity' object" );
+        return 0;
     }
+
+    my $msgid = $args{'Entity'}->head->get('Message-ID') || '';
+    chomp $msgid;
     
-    if ($RT::MailCommand eq 'sendmailpipe') {
-        open (MAIL, "|$RT::SendmailPath $RT::SendmailArguments") || return(0);
-        print MAIL $entity->as_string;
-        close(MAIL);
+    # If we don't have any recipients to send to, don't send a message;
+    unless ( $args{'Entity'}->head->get('To')
+        || $args{'Entity'}->head->get('Cc')
+        || $args{'Entity'}->head->get('Bcc') )
+    {
+        $RT::Logger->info( $msgid . " No recipients found. Not sending." );
+        return -1;
+    }
+
+    if ($args{'Entity'}->head->get('X-RT-Squelch')) {
+        $RT::Logger->info( $msgid . " Squelch header found. Not sending." );
+        return -1;
+    }
+
+    if ( $TransactionObj && !$TicketObj
+        && $TransactionObj->ObjectType eq 'RT::Ticket' )
+    {
+        $TicketObj = $TransactionObj->Object;
+    }
+
+    if ( RT->Config->Get('GnuPG')->{'Enable'} ) {
+        my %crypt;
+
+        my $attachment;
+        $attachment = $TransactionObj->Attachments->First
+            if $TransactionObj;
+
+        foreach my $argument ( qw(Sign Encrypt) ) {
+            next if defined $args{ $argument };
+
+            if ( $attachment && defined $attachment->GetHeader("X-RT-$argument") ) {
+                $crypt{$argument} = $attachment->GetHeader("X-RT-$argument");
+            } elsif ( $TicketObj ) {
+                $crypt{$argument} = $TicketObj->QueueObj->$argument();
+            }
+        }
+
+        my $res = SignEncrypt( %args, %crypt );
+        return $res unless $res > 0;
+    }
+
+    unless ( $args{'Entity'}->head->get('Date') ) {
+        require RT::Date;
+        my $date = RT::Date->new( $RT::SystemUser );
+        $date->SetToNow;
+        $args{'Entity'}->head->set( 'Date', $date->RFC2822( Timezone => 'server' ) );
+    }
+
+    my $mail_command = RT->Config->Get('MailCommand');
+
+    if ($mail_command eq 'testfile') {
+        $Mail::Mailer::testfile::config{outfile} = File::Temp->new;
+    }
+
+    # if it is a sub routine, we just return it;
+    return $mail_command->($args{'Entity'}) if UNIVERSAL::isa( $mail_command, 'CODE' );
+
+    if ( $mail_command eq 'sendmailpipe' ) {
+        my $path = RT->Config->Get('SendmailPath');
+        my $args = RT->Config->Get('SendmailArguments');
+
+        # SetOutgoingMailFrom
+        if ( RT->Config->Get('SetOutgoingMailFrom') ) {
+            my $OutgoingMailAddress;
+
+            if ($TicketObj) {
+                my $QueueName = $TicketObj->QueueObj->Name;
+                my $QueueAddressOverride = RT->Config->Get('OverrideOutgoingMailFrom')->{$QueueName};
+
+                if ($QueueAddressOverride) {
+                    $OutgoingMailAddress = $QueueAddressOverride;
+                } else {
+                    $OutgoingMailAddress = $TicketObj->QueueObj->CorrespondAddress;
+                }
+            }
+
+            $OutgoingMailAddress ||= RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'};
+
+            $args .= " -f $OutgoingMailAddress"
+                if $OutgoingMailAddress;
+        }
+
+        # Set Bounce Arguments
+        $args .= ' '. RT->Config->Get('SendmailBounceArguments') if $args{'Bounce'};
+
+        # VERP
+        if ( $TransactionObj and
+             my $prefix = RT->Config->Get('VERPPrefix') and
+             my $domain = RT->Config->Get('VERPDomain') )
+        {
+            my $from = $TransactionObj->CreatorObj->EmailAddress;
+            $from =~ s/@/=/g;
+            $from =~ s/\s//g;
+            $args .= " -f $prefix$from\@$domain";
+        }
+
+        eval {
+            # don't ignore CHLD signal to get proper exit code
+            local $SIG{'CHLD'} = 'DEFAULT';
+
+            open my $mail, "|$path $args" or die "couldn't execute program: $!";
+
+            # if something wrong with $mail->print we will get PIPE signal, handle it
+            local $SIG{'PIPE'} = sub { die "program unexpectedly closed pipe" };
+            $args{'Entity'}->print($mail);
+
+            unless ( close $mail ) {
+                die "close pipe failed: $!" if $!; # system error
+                # sendmail exit statuses mostly errors with data not software
+                # TODO: status parsing: core dump, exit on signal or EX_*
+                my $msg = "$msgid: `$path $args` exitted with code ". ($?>>8);
+                $msg = ", interrupted by signal ". ($?&127) if $?&127;
+                $RT::Logger->error( $msg );
+            }
+        };
+        if ( $@ ) {
+            $RT::Logger->crit( "$msgid: Could not send mail with command `$path $args`: " . $@ );
+            return 0;
+        }
+    }
+    elsif ( $mail_command eq 'smtp' ) {
+        require Net::SMTP;
+        my $smtp = do { local $@; eval { Net::SMTP->new(
+            Host  => RT->Config->Get('SMTPServer'),
+            Debug => RT->Config->Get('SMTPDebug'),
+        ) } };
+        unless ( $smtp ) {
+            $RT::Logger->crit( "Could not connect to SMTP server.");
+            return 0;
+        }
+
+        # duplicate head as we want drop Bcc field
+        my $head = $args{'Entity'}->head->dup;
+        my @recipients = map $_->address, map 
+            Email::Address->parse($head->get($_)), qw(To Cc Bcc);                       
+        $head->delete('Bcc');
+
+        my $sender = RT->Config->Get('SMTPFrom')
+            || $args{'Entity'}->head->get('From');
+        chomp $sender;
+
+        my $status = $smtp->mail( $sender )
+            && $smtp->recipient( @recipients );
+
+        if ( $status ) {
+            $smtp->data;
+            my $fh = $smtp->tied_fh;
+            $head->print( $fh );
+            print $fh "\n";
+            $args{'Entity'}->print_body( $fh );
+            $smtp->dataend;
+        }
+        $smtp->quit;
+
+        unless ( $status ) {
+            $RT::Logger->crit( "$msgid: Could not send mail via SMTP." );
+            return 0;
+        }
     }
     else {
-       $entity->send($RT::MailCommand, $RT::MailParams);
+        local ($ENV{'MAILADDRESS'}, $ENV{'PERL_MAILERS'});
+
+        my @mailer_args = ($mail_command);
+        if ( $mail_command eq 'sendmail' ) {
+            $ENV{'PERL_MAILERS'} = RT->Config->Get('SendmailPath');
+            push @mailer_args, split(/\s+/, RT->Config->Get('SendmailArguments'));
+        }
+        else {
+            push @mailer_args, RT->Config->Get('MailParams');
+        }
+
+        unless ( $args{'Entity'}->send( @mailer_args ) ) {
+            $RT::Logger->crit( "$msgid: Could not send mail." );
+            return 0;
+        }
+    }
+    return 1;
+}
+
+=head2 PrepareEmailUsingTemplate Template => '', Arguments => {}
+
+Loads a template. Parses it using arguments if it's not empty.
+Returns a tuple (L<RT::Template> object, error message).
+
+Note that even if a template object is returned MIMEObj method
+may return undef for empty templates.
+
+=cut
+
+sub PrepareEmailUsingTemplate {
+    my %args = (
+        Template => '',
+        Arguments => {},
+        @_
+    );
+
+    my $template = RT::Template->new( $RT::SystemUser );
+    $template->LoadGlobalTemplate( $args{'Template'} );
+    unless ( $template->id ) {
+        return (undef, "Couldn't load template '". $args{'Template'} ."'");
+    }
+    return $template if $template->IsEmpty;
+
+    my ($status, $msg) = $template->Parse( %{ $args{'Arguments'} } );
+    return (undef, $msg) unless $status;
+
+    return $template;
+}
+
+=head2 SendEmailUsingTemplate Template => '', Arguments => {}, From => CorrespondAddress, To => '', Cc => '', Bcc => ''
+
+Sends email using a template, takes name of template, arguments for it and recipients.
+
+=cut
+
+sub SendEmailUsingTemplate {
+    my %args = (
+        Template => '',
+        Arguments => {},
+        To => undef,
+        Cc => undef,
+        Bcc => undef,
+        From => RT->Config->Get('CorrespondAddress'),
+        InReplyTo => undef,
+        @_
+    );
+
+    my ($template, $msg) = PrepareEmailUsingTemplate( %args );
+    return (0, $msg) unless $template;
+
+    my $mail = $template->MIMEObj;
+    unless ( $mail ) {
+        $RT::Logger->info("Message is not sent as template #". $template->id ." is empty");
+        return -1;
+    }
+
+    $mail->head->set( $_ => Encode::encode_utf8( $args{ $_ } ) )
+        foreach grep defined $args{$_}, qw(To Cc Bcc From);
+
+    SetInReplyTo( Message => $mail, InReplyTo => $args{'InReplyTo'} );
+
+    return SendEmail( Entity => $mail );
+}
+
+=head2 ForwardTransaction TRANSACTION, To => '', Cc => '', Bcc => ''
+
+Forwards transaction with all attachments as 'message/rfc822'.
+
+=cut
+
+sub ForwardTransaction {
+    my $txn = shift;
+    my %args = ( To => '', Cc => '', Bcc => '', @_ );
+
+    my $entity = $txn->ContentAsMIME;
+
+    return SendForward( %args, Entity => $entity, Transaction => $txn );
+}
+
+=head2 ForwardTicket TICKET, To => '', Cc => '', Bcc => ''
+
+Forwards a ticket's Create and Correspond Transactions and their Attachments as 'message/rfc822'.
+
+=cut
+
+sub ForwardTicket {
+    my $ticket = shift;
+    my %args = ( To => '', Cc => '', Bcc => '', @_ );
+
+    my $txns = $ticket->Transactions;
+    $txns->Limit(
+        FIELD    => 'Type',
+        VALUE    => $_,
+    ) for qw(Create Correspond);
+
+    my $entity = MIME::Entity->build(
+        Type => 'multipart/mixed',
+    );
+    $entity->add_part( $_ ) foreach 
+        map $_->ContentAsMIME,
+        @{ $txns->ItemsArrayRef };
+
+    return SendForward( %args, Entity => $entity, Ticket => $ticket, Template => 'Forward Ticket' );
+}
+
+=head2 SendForward Entity => undef, Ticket => undef, Transaction => undef, Template => undef, To => '', Cc => '', Bcc => ''
+
+Forwards an Entity representing Ticket or Transaction as 'message/rfc822'. Entity is wrapped into Template.
+
+=cut
+
+sub SendForward {
+    my (%args) = (
+        Entity => undef,
+        Ticket => undef,
+        Transaction => undef,
+        Template => 'Forward',
+        To => '', Cc => '', Bcc => '',
+        @_
+    );
+
+    my $txn = $args{'Transaction'};
+    my $ticket = $args{'Ticket'};
+    $ticket ||= $txn->Object if $txn;
+
+    my $entity = $args{'Entity'};
+    unless ( $entity ) {
+        require Carp;
+        $RT::Logger->error(Carp::longmess("No entity provided"));
+        return (0, $ticket->loc("Couldn't send email"));
+    }
+
+    my ($template, $msg) = PrepareEmailUsingTemplate(
+        Template  => $args{'Template'},
+        Arguments => {
+            Ticket      => $ticket,
+            Transaction => $txn,
+        },
+    );
+
+    my $mail;
+    if ( $template ) {
+        $mail = $template->MIMEObj;
+    } else {
+        $RT::Logger->warning($msg);
+    }
+    unless ( $mail ) {
+        $RT::Logger->warning("Couldn't generate email using template '$args{Template}'");
+
+        my $description;
+        unless ( $args{'Transaction'} ) {
+            $description = 'This is forward of ticket #'. $ticket->id;
+        } else {
+            $description = 'This is forward of transaction #'
+                . $txn->id ." of a ticket #". $txn->ObjectId;
+        }
+        $mail = MIME::Entity->build(
+            Type => 'text/plain',
+            Data => $description,
+        );
+    }
+
+    $mail->head->set( $_ => EncodeToMIME( String => $args{$_} ) )
+        foreach grep defined $args{$_}, qw(To Cc Bcc);
+
+    $mail->attach(
+        Type => 'message/rfc822',
+        Disposition => 'attachment',
+        Description => 'forwarded message',
+        Data => $entity->as_string,
+    );
+
+    my $from;
+    my $subject = '';
+    $subject = $txn->Subject if $txn;
+    $subject ||= $ticket->Subject if $ticket;
+    if ( RT->Config->Get('ForwardFromUser') ) {
+        $from = ($txn || $ticket)->CurrentUser->UserObj->EmailAddress;
+    } else {
+        # XXX: what if want to forward txn of other object than ticket?
+        $subject = AddSubjectTag( $subject, $ticket );
+        $from = $ticket->QueueObj->CorrespondAddress
+            || RT->Config->Get('CorrespondAddress');
+    }
+    $mail->head->set( Subject => EncodeToMIME( String => "Fwd: $subject" ) );
+    $mail->head->set( From    => EncodeToMIME( String => $from ) );
+
+    my $status = RT->Config->Get('ForwardFromUser')
+        # never sign if we forward from User
+        ? SendEmail( %args, Entity => $mail, Sign => 0 )
+        : SendEmail( %args, Entity => $mail );
+    return (0, $ticket->loc("Couldn't send email")) unless $status;
+    return (1, $ticket->loc("Send email successfully"));
+}
+
+=head2 SignEncrypt Entity => undef, Sign => 0, Encrypt => 0
+
+Signs and encrypts message using L<RT::Crypt::GnuPG>, but as well
+handle errors with users' keys.
+
+If a recipient has no key or has other problems with it, then the
+unction sends a error to him using 'Error: public key' template.
+Also, notifies RT's owner using template 'Error to RT owner: public key'
+to inform that there are problems with users' keys. Then we filter
+all bad recipients and retry.
+
+Returns 1 on success, 0 on error and -1 if all recipients are bad and
+had been filtered out.
+
+=cut
+
+sub SignEncrypt {
+    my %args = (
+        Entity => undef,
+        Sign => 0,
+        Encrypt => 0,
+        @_
+    );
+    return 1 unless $args{'Sign'} || $args{'Encrypt'};
+
+    my $msgid = $args{'Entity'}->head->get('Message-ID') || '';
+    chomp $msgid;
+
+    $RT::Logger->debug("$msgid Signing message") if $args{'Sign'};
+    $RT::Logger->debug("$msgid Encrypting message") if $args{'Encrypt'};
+
+    require RT::Crypt::GnuPG;
+    my %res = RT::Crypt::GnuPG::SignEncrypt( %args );
+    return 1 unless $res{'exit_code'};
+
+    my @status = RT::Crypt::GnuPG::ParseStatus( $res{'status'} );
+
+    my @bad_recipients;
+    foreach my $line ( @status ) {
+        # if the passphrase fails, either you have a bad passphrase
+        # or gpg-agent has died.  That should get caught in Create and
+        # Update, but at least throw an error here
+        if (($line->{'Operation'}||'') eq 'PassphraseCheck'
+            && $line->{'Status'} =~ /^(?:BAD|MISSING)$/ ) {
+            $RT::Logger->error( "$line->{'Status'} PASSPHRASE: $line->{'Message'}" );
+            return 0;
+        }
+        next unless ($line->{'Operation'}||'') eq 'RecipientsCheck';
+        next if $line->{'Status'} eq 'DONE';
+        $RT::Logger->error( $line->{'Message'} );
+        push @bad_recipients, $line;
+    }
+    return 0 unless @bad_recipients;
+
+    $_->{'AddressObj'} = (Email::Address->parse( $_->{'Recipient'} ))[0]
+        foreach @bad_recipients;
+
+    foreach my $recipient ( @bad_recipients ) {
+        my $status = SendEmailUsingTemplate(
+            To        => $recipient->{'AddressObj'}->address,
+            Template  => 'Error: public key',
+            Arguments => {
+                %$recipient,
+                TicketObj      => $args{'Ticket'},
+                TransactionObj => $args{'Transaction'},
+            },
+        );
+        unless ( $status ) {
+            $RT::Logger->error("Couldn't send 'Error: public key'");
+        }
+    }
+
+    my $status = SendEmailUsingTemplate(
+        To        => RT->Config->Get('OwnerEmail'),
+        Template  => 'Error to RT owner: public key',
+        Arguments => {
+            BadRecipients  => \@bad_recipients,
+            TicketObj      => $args{'Ticket'},
+            TransactionObj => $args{'Transaction'},
+        },
+    );
+    unless ( $status ) {
+        $RT::Logger->error("Couldn't send 'Error to RT owner: public key'");
     }
+
+    DeleteRecipientsFromHead(
+        $args{'Entity'}->head,
+        map $_->{'AddressObj'}->address, @bad_recipients
+    );
+
+    unless ( $args{'Entity'}->head->get('To')
+          || $args{'Entity'}->head->get('Cc')
+          || $args{'Entity'}->head->get('Bcc') )
+    {
+        $RT::Logger->debug("$msgid No recipients that have public key, not sending");
+        return -1;
+    }
+
+    # redo without broken recipients
+    %res = RT::Crypt::GnuPG::SignEncrypt( %args );
+    return 0 if $res{'exit_code'};
+
+    return 1;
 }
 
-# }}}
+use MIME::Words ();
 
-# {{{ Create User
+=head2 EncodeToMIME
+
+Takes a hash with a String and a Charset. Returns the string encoded
+according to RFC2047, using B (base64 based) encoding.
+
+String must be a perl string, octets are returned.
+
+If Charset is not provided then $EmailOutputEncoding config option
+is used, or "latin-1" if that is not set.
+
+=cut
+
+sub EncodeToMIME {
+    my %args = (
+        String => undef,
+        Charset  => undef,
+        @_
+    );
+    my $value = $args{'String'};
+    return $value unless $value; # 0 is perfect ascii
+    my $charset  = $args{'Charset'} || RT->Config->Get('EmailOutputEncoding');
+    my $encoding = 'B';
+
+    # using RFC2047 notation, sec 2.
+    # encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
+
+    # An 'encoded-word' may not be more than 75 characters long
+    #
+    # MIME encoding increases 4/3*(number of bytes), and always in multiples
+    # of 4. Thus we have to find the best available value of bytes available
+    # for each chunk.
+    #
+    # First we get the integer max which max*4/3 would fit on space.
+    # Then we find the greater multiple of 3 lower or equal than $max.
+    my $max = int(
+        (   ( 75 - length( '=?' . $charset . '?' . $encoding . '?' . '?=' ) )
+            * 3
+        ) / 4
+    );
+    $max = int( $max / 3 ) * 3;
+
+    chomp $value;
+
+    if ( $max <= 0 ) {
+
+        # gives an error...
+        $RT::Logger->crit("Can't encode! Charset or encoding too big.");
+        return ($value);
+    }
+
+    return ($value) unless $value =~ /[^\x20-\x7e]/;
+
+    $value =~ s/\s+$//;
+
+    # we need perl string to split thing char by char
+    Encode::_utf8_on($value) unless Encode::is_utf8($value);
+
+    my ( $tmp, @chunks ) = ( '', () );
+    while ( length $value ) {
+        my $char = substr( $value, 0, 1, '' );
+        my $octets = Encode::encode( $charset, $char );
+        if ( length($tmp) + length($octets) > $max ) {
+            push @chunks, $tmp;
+            $tmp = '';
+        }
+        $tmp .= $octets;
+    }
+    push @chunks, $tmp if length $tmp;
+
+    # encode an join chuncks
+    $value = join "\n ",
+        map MIME::Words::encode_mimeword( $_, $encoding, $charset ),
+        @chunks;
+    return ($value);
+}
 
 sub CreateUser {
-    my ($Username, $Address, $Name, $ErrorsTo, $entity) = @_;
-    my $NewUser = RT::User->new($RT::SystemUser);
-
-    # This data is tainted by some Very Broken mailers.
-    # (Sometimes they send raw ISO 8859-1 data here. fear that.
-    require Encode;
-    $Username = Encode::encode(utf8 => $Username, Encode::FB_PERLQQ()) if defined $Username;
-    $Name = Encode::encode(utf8 => $Name, Encode::FB_PERLQQ()) if defined $Name;
-    
-    my ($Val, $Message) = 
-      $NewUser->Create(Name => ($Username || $Address),
-                       EmailAddress => $Address,
-                       RealName => $Name,
-                       Password => undef,
-                       Privileged => 0,
-                       Comments => 'Autocreated on ticket submission'
-                      );
-    
+    my ( $Username, $Address, $Name, $ErrorsTo, $entity ) = @_;
+
+    my $NewUser = RT::User->new( $RT::SystemUser );
+
+    my ( $Val, $Message ) = $NewUser->Create(
+        Name => ( $Username || $Address ),
+        EmailAddress => $Address,
+        RealName     => $Name,
+        Password     => undef,
+        Privileged   => 0,
+        Comments     => 'Autocreated on ticket submission',
+    );
+
     unless ($Val) {
-        
+
         # Deal with the race condition of two account creations at once
-        #
         if ($Username) {
             $NewUser->LoadByName($Username);
         }
-        
-        unless ($NewUser->Id) {
+
+        unless ( $NewUser->Id ) {
             $NewUser->LoadByEmail($Address);
         }
-        
-        unless ($NewUser->Id) {  
-            MailError( To => $ErrorsTo,
-                       Subject => "User could not be created",
-                       Explanation => "User creation failed in mailgateway: $Message",
-                       MIMEObj => $entity,
-                       LogLevel => 'crit'
-                     );
+
+        unless ( $NewUser->Id ) {
+            MailError(
+                To          => $ErrorsTo,
+                Subject     => "User could not be created",
+                Explanation =>
+                    "User creation failed in mailgateway: $Message",
+                MIMEObj  => $entity,
+                LogLevel => 'crit',
+            );
         }
     }
 
     #Load the new user object
-    my $CurrentUser = RT::CurrentUser->new();
-    $CurrentUser->LoadByEmail($Address);
+    my $CurrentUser = new RT::CurrentUser;
+    $CurrentUser->LoadByEmail( $Address );
 
-    unless ($CurrentUser->id) {
-            $RT::Logger->warning("Couldn't load user '$Address'.".  "giving up");
-                MailError( To => $ErrorsTo,
-                           Subject => "User could not be loaded",
-                           Explanation => "User  '$Address' could not be loaded in the mail gateway",
-                           MIMEObj => $entity,
-                           LogLevel => 'crit'
-                     );
+    unless ( $CurrentUser->id ) {
+        $RT::Logger->warning(
+            "Couldn't load user '$Address'." . "giving up" );
+        MailError(
+            To          => $ErrorsTo,
+            Subject     => "User could not be loaded",
+            Explanation =>
+                "User  '$Address' could not be loaded in the mail gateway",
+            MIMEObj  => $entity,
+            LogLevel => 'crit'
+        );
     }
 
     return $CurrentUser;
 }
-# }}}      
-# {{{ ParseCcAddressesFromHead 
 
-=head2 ParseCcAddressesFromHead HASHREF
 
-Takes a hashref object containing QueueObj, Head and CurrentUser objects.
-Returns a list of all email addresses in the To and Cc 
-headers b<except> the current Queue\'s email addresses, the CurrentUser\'s 
+
+=head2 ParseCcAddressesFromHead HASH
+
+Takes a hash containing QueueObj, Head and CurrentUser objects.
+Returns a list of all email addresses in the To and Cc
+headers b<except> the current Queue\'s email addresses, the CurrentUser\'s
 email address  and anything that the configuration sub RT::IsRTAddress matches.
 
 =cut
-  
+
 sub ParseCcAddressesFromHead {
-    my %args = ( Head => undef,
-                QueueObj => undef,
-                CurrentUser => undef,
-                @_ );
-    
-    my (@Addresses);
-        
-    my @ToObjs = Mail::Address->parse($args{'Head'}->get('To'));
-    my @CcObjs = Mail::Address->parse($args{'Head'}->get('Cc'));
-    
-    foreach my $AddrObj (@ToObjs, @CcObjs) {
-       my $Address = $AddrObj->address;
-       $Address = $args{'CurrentUser'}->UserObj->CanonicalizeEmailAddress($Address);
-       next if ($args{'CurrentUser'}->EmailAddress =~ /^$Address$/i);
-       next if ($args{'QueueObj'}->CorrespondAddress =~ /^$Address$/i);
-       next if ($args{'QueueObj'}->CommentAddress =~ /^$Address$/i);
-       next if (RT::EmailParser::IsRTAddress(undef, $Address));
-       
-       push (@Addresses, $Address);
-    }
-    return (@Addresses);
+    my %args = (
+        Head        => undef,
+        QueueObj    => undef,
+        CurrentUser => undef,
+        @_
+    );
+
+    my $current_address = lc $args{'CurrentUser'}->EmailAddress;
+    my $user = $args{'CurrentUser'}->UserObj;
+
+    return
+        grep $_ ne $current_address && !RT::EmailParser->IsRTAddress( $_ ),
+        map lc $user->CanonicalizeEmailAddress( $_->address ),
+        map Email::Address->parse( $args{'Head'}->get( $_ ) ),
+        qw(To Cc);
 }
 
 
-# }}}
 
-# {{{ ParseSenderAdddressFromHead
+=head2 ParseSenderAddressFromHead HEAD
 
-=head2 ParseSenderAddressFromHead
-
-Takes a MIME::Header object. Returns a tuple: (user@host, friendly name) 
+Takes a MIME::Header object. Returns a tuple: (user@host, friendly name)
 of the From (evaluated in order of Reply-To:, From:, Sender)
 
 =cut
 
 sub ParseSenderAddressFromHead {
     my $head = shift;
+
     #Figure out who's sending this message.
-    my $From = $head->get('Reply-To') || 
-      $head->get('From') || 
-       $head->get('Sender');
-    return (ParseAddressFromHeader($From));
-}
-# }}}
+    foreach my $header ('Reply-To', 'From', 'Sender') {
+        my $addr_line = $head->get($header) || next;
+        my ($addr, $name) = ParseAddressFromHeader( $addr_line );
+        # only return if the address is not empty
+        return ($addr, $name) if $addr;
+    }
 
-# {{{ ParseErrorsToAdddressFromHead
+    return (undef, undef);
+}
 
-=head2 ParseErrorsToAddressFromHead
+=head2 ParseErrorsToAddressFromHead HEAD
 
 Takes a MIME::Header object. Return a single value : user@host
-of the From (evaluated in order of Errors-To:,Reply-To:, From:, Sender)
+of the From (evaluated in order of Return-path:,Errors-To:,Reply-To:,
+From:, Sender)
 
 =cut
 
 sub ParseErrorsToAddressFromHead {
     my $head = shift;
+
     #Figure out who's sending this message.
 
-    foreach my $header ('Errors-To' , 'Reply-To', 'From', 'Sender' ) {
-       # If there's a header of that name
-       my $headerobj = $head->get($header);
-       if ($headerobj) {
-               my ($addr, $name ) = ParseAddressFromHeader($headerobj);
-               # If it's got actual useful content...
-               return ($addr) if ($addr);
-       }
+    foreach my $header ( 'Errors-To', 'Reply-To', 'From', 'Sender' ) {
+
+        # If there's a header of that name
+        my $headerobj = $head->get($header);
+        if ($headerobj) {
+            my ( $addr, $name ) = ParseAddressFromHeader($headerobj);
+
+            # If it's got actual useful content...
+            return ($addr) if ($addr);
+        }
     }
 }
-# }}}
 
-# {{{ ParseAddressFromHeader
+
 
 =head2 ParseAddressFromHeader ADDRESS
 
-Takes an address from $head->get('Line') and returns a tuple: user@host, friendly name
+Takes an address from C<$head->get('Line')> and returns a tuple: user@host, friendly name
 
 =cut
 
-
-sub ParseAddressFromHeader{
+sub ParseAddressFromHeader {
     my $Addr = shift;
-    
-    my @Addresses = Mail::Address->parse($Addr);
-    
-    my $AddrObj = $Addresses[0];
 
-    unless (ref($AddrObj)) {
-       return(undef,undef);
+    # Some broken mailers send:  ""Vincent, Jesse"" <jesse@fsck.com>. Hate
+    $Addr =~ s/\"\"(.*?)\"\"/\"$1\"/g;
+    my @Addresses = RT::EmailParser->ParseEmailAddress($Addr);
+
+    my ($AddrObj) = grep ref $_, @Addresses;
+    unless ( $AddrObj ) {
+        return ( undef, undef );
     }
-    my $Name =  ($AddrObj->phrase || $AddrObj->comment || $AddrObj->address);
-    
+
+    my $Name = ( $AddrObj->name || $AddrObj->phrase || $AddrObj->comment || $AddrObj->address );
+
     #Lets take the from and load a user object.
     my $Address = $AddrObj->address;
 
-    return ($Address, $Name);
+    return ( $Address, $Name );
+}
+
+=head2 DeleteRecipientsFromHead HEAD RECIPIENTS
+
+Gets a head object and list of addresses.
+Deletes addresses from To, Cc or Bcc fields.
+
+=cut
+
+sub DeleteRecipientsFromHead {
+    my $head = shift;
+    my %skip = map { lc $_ => 1 } @_;
+
+    foreach my $field ( qw(To Cc Bcc) ) {
+        $head->set( $field =>
+            join ', ', map $_->format, grep !$skip{ lc $_->address },
+                Email::Address->parse( $head->get( $field ) )
+        );
+    }
+}
+
+sub GenMessageId {
+    my %args = (
+        Ticket      => undef,
+        Scrip       => undef,
+        ScripAction => undef,
+        @_
+    );
+    my $org = RT->Config->Get('Organization');
+    my $ticket_id = ( ref $args{'Ticket'}? $args{'Ticket'}->id : $args{'Ticket'} ) || 0;
+    my $scrip_id = ( ref $args{'Scrip'}? $args{'Scrip'}->id : $args{'Scrip'} ) || 0;
+    my $sent = ( ref $args{'ScripAction'}? $args{'ScripAction'}->{'_Message_ID'} : 0 ) || 0;
+
+    return "<rt-". $RT::VERSION ."-". $$ ."-". CORE::time() ."-". int(rand(2000)) .'.'
+        . $ticket_id ."-". $scrip_id ."-". $sent ."@". $org .">" ;
 }
-# }}}
 
+sub SetInReplyTo {
+    my %args = (
+        Message   => undef,
+        InReplyTo => undef,
+        Ticket    => undef,
+        @_
+    );
+    return unless $args{'Message'} && $args{'InReplyTo'};
+
+    my $get_header = sub {
+        my @res;
+        if ( $args{'InReplyTo'}->isa('MIME::Entity') ) {
+            @res = $args{'InReplyTo'}->head->get( shift );
+        } else {
+            @res = $args{'InReplyTo'}->GetHeader( shift ) || '';
+        }
+        return grep length, map { split /\s+/m, $_ } grep defined, @res;
+    };
+
+    my @id = $get_header->('Message-ID');
+    #XXX: custom header should begin with X- otherwise is violation of the standard
+    my @rtid = $get_header->('RT-Message-ID');
+    my @references = $get_header->('References');
+    unless ( @references ) {
+        @references = $get_header->('In-Reply-To');
+    }
+    push @references, @id, @rtid;
+    if ( $args{'Ticket'} ) {
+        my $pseudo_ref =  '<RT-Ticket-'. $args{'Ticket'}->id .'@'. RT->Config->Get('Organization') .'>';
+        push @references, $pseudo_ref unless grep $_ eq $pseudo_ref, @references;
+    }
+    @references = splice @references, 4, -6
+        if @references > 10;
+
+    my $mail = $args{'Message'};
+    $mail->head->set( 'In-Reply-To' => join ' ', @rtid? (@rtid) : (@id) ) if @id || @rtid;
+    $mail->head->set( 'References' => join ' ', @references );
+}
 
+sub ParseTicketId {
+    my $Subject = shift;
+
+    my $rtname = RT->Config->Get('rtname');
+    my $test_name = RT->Config->Get('EmailSubjectTagRegex') || qr/\Q$rtname\E/i;
+
+    my $id;
+    if ( $Subject =~ s/\[$test_name\s+\#(\d+)\s*\]//i ) {
+        $id = $1;
+    } else {
+        foreach my $tag ( RT->System->SubjectTag ) {
+            next unless $Subject =~ s/\[\Q$tag\E\s+\#(\d+)\s*\]//i;
+            $id = $1;
+            last;
+        }
+    }
+    return undef unless $id;
+
+    $RT::Logger->debug("Found a ticket ID. It's $id");
+    return $id;
+}
+
+sub AddSubjectTag {
+    my $subject = shift;
+    my $ticket  = shift;
+    unless ( ref $ticket ) {
+        my $tmp = RT::Ticket->new( $RT::SystemUser );
+        $tmp->Load( $ticket );
+        $ticket = $tmp;
+    }
+    my $id = $ticket->id;
+    my $queue_tag = $ticket->QueueObj->SubjectTag;
+
+    my $tag_re = RT->Config->Get('EmailSubjectTagRegex');
+    unless ( $tag_re ) {
+        my $tag = $queue_tag || RT->Config->Get('rtname');
+        $tag_re = qr/\Q$tag\E/;
+    } elsif ( $queue_tag ) {
+        $tag_re = qr/$tag_re|\Q$queue_tag\E/;
+    }
+    return $subject if $subject =~ /\[$tag_re\s+#$id\]/;
+
+    $subject =~ s/(\r\n|\n|\s)/ /gi;
+    chomp $subject;
+    return "[". ($queue_tag || RT->Config->Get('rtname')) ." #$id] $subject";
+}
+
+
+=head2 Gateway ARGSREF
+
+
+Takes parameters:
+
+    action
+    queue
+    message
 
-=head2 Gateway
 
 This performs all the "guts" of the mail rt-mailgate program, and is
 designed to be called from the web interface with a message, user
 object, and so on.
 
+Can also take an optional 'ticket' parameter; this ticket id overrides
+any ticket id found in the subject.
+
+Returns:
+
+    An array of:
+
+    (status code, message, optional ticket object)
+
+    status code is a numeric value.
+
+      for temporary failures, the status code should be -75
+
+      for permanent failures which are handled by RT, the status code
+      should be 0
+
+      for succces, the status code should be 1
+
+
+
 =cut
 
+sub _LoadPlugins {
+    my @mail_plugins = @_;
+
+    my @res;
+    foreach my $plugin (@mail_plugins) {
+        if ( ref($plugin) eq "CODE" ) {
+            push @res, $plugin;
+        } elsif ( !ref $plugin ) {
+            my $Class = $plugin;
+            $Class = "RT::Interface::Email::" . $Class
+                unless $Class =~ /^RT::Interface::Email::/;
+            $Class->require or
+                do { $RT::Logger->error("Couldn't load $Class: $@"); next };
+
+            no strict 'refs';
+            unless ( defined *{ $Class . "::GetCurrentUser" }{CODE} ) {
+                $RT::Logger->crit( "No GetCurrentUser code found in $Class module");
+                next;
+            }
+            push @res, $Class;
+        } else {
+            $RT::Logger->crit( "$plugin - is not class name or code reference");
+        }
+    }
+    return @res;
+}
+
 sub Gateway {
-    my %args = ( message => undef,
-                 queue   => 1,
-                 action  => 'correspond',
-                 ticket  => undef,
-                 @_ );
+    my $argsref = shift;
+    my %args    = (
+        action  => 'correspond',
+        queue   => '1',
+        ticket  => undef,
+        message => undef,
+        %$argsref
+    );
 
-    # Validate the action
-    unless ( $args{'action'} =~ /^(comment|correspond|action)$/ ) {
+    my $SystemTicket;
+    my $Right;
 
-        # Can't safely loc this. What object do we loc around?
-        return ( 0, "Invalid 'action' parameter", undef );
+    # Validate the action
+    my ( $status, @actions ) = IsCorrectAction( $args{'action'} );
+    unless ($status) {
+        return (
+            -75,
+            "Invalid 'action' parameter "
+                . $actions[0]
+                . " for queue "
+                . $args{'queue'},
+            undef
+        );
     }
 
     my $parser = RT::EmailParser->new();
-    $parser->ParseMIMEEntityFromScalar( $args{'message'} );
+    $parser->SmartParseMIMEEntityFromScalar(
+        Message => $args{'message'},
+        Decode => 0,
+        Exact => 1,
+    );
 
     my $Message = $parser->Entity();
-    my $head = $Message->head;
+    unless ($Message) {
+        MailError(
+            Subject     => "RT Bounce: Unparseable message",
+            Explanation => "RT couldn't process the message below",
+            Attach      => $args{'message'}
+        );
+
+        return ( 0,
+            "Failed to parse this message. Something is likely badly wrong with the message"
+        );
+    }
+
+    my @mail_plugins = grep $_, RT->Config->Get('MailPlugins');
+    push @mail_plugins, "Auth::MailFrom" unless @mail_plugins;
+    @mail_plugins = _LoadPlugins( @mail_plugins );
+
+    my %skip_plugin;
+    foreach my $class( grep !ref, @mail_plugins ) {
+        # check if we should apply filter before decoding
+        my $check_cb = do {
+            no strict 'refs';
+            *{ $class . "::ApplyBeforeDecode" }{CODE};
+        };
+        next unless defined $check_cb;
+        next unless $check_cb->(
+            Message       => $Message,
+            RawMessageRef => \$args{'message'},
+        );
 
-    my ( $CurrentUser, $AuthStat, $status, $error );
+        $skip_plugin{ $class }++;
 
-    my $ErrorsTo = ParseErrorsToAddressFromHead($head);
+        my $Code = do {
+            no strict 'refs';
+            *{ $class . "::GetCurrentUser" }{CODE};
+        };
+        my ($status, $msg) = $Code->(
+            Message       => $Message,
+            RawMessageRef => \$args{'message'},
+        );
+        next if $status > 0;
+
+        if ( $status == -2 ) {
+            return (1, $msg, undef);
+        } elsif ( $status == -1 ) {
+            return (0, $msg, undef);
+        }
+    }
+    @mail_plugins = grep !$skip_plugin{"$_"}, @mail_plugins;
+    $parser->_DecodeBodies;
+    $parser->_PostProcessNewEntity;
 
-    my $MessageId = $head->get('Message-Id')
-      || "<no-message-id-" . time . rand(2000) . "\@.$RT::Organization>";
+    my $head = $Message->head;
+    my $ErrorsTo = ParseErrorsToAddressFromHead( $head );
+
+    my $MessageId = $head->get('Message-ID')
+        || "<no-message-id-". time . rand(2000) .'@'. RT->Config->Get('Organization') .'>';
 
     #Pull apart the subject line
     my $Subject = $head->get('Subject') || '';
     chomp $Subject;
+    
+    # {{{ Lets check for mail loops of various sorts.
+    my ($should_store_machine_generated_message, $IsALoop, $result);
+    ( $should_store_machine_generated_message, $ErrorsTo, $result, $IsALoop ) =
+      _HandleMachineGeneratedMail(
+        Message  => $Message,
+        ErrorsTo => $ErrorsTo,
+        Subject  => $Subject,
+        MessageId => $MessageId
+    );
+
+    # Do not pass loop messages to MailPlugins, to make sure the loop
+    # is broken, unless $RT::StoreLoops is set.
+    if ($IsALoop && !$should_store_machine_generated_message) {
+        return ( 0, $result, undef );
+    }
+    # }}}
 
+    $args{'ticket'} ||= ParseTicketId( $Subject );
 
-    $args{'ticket'} ||= $parser->ParseTicketId($Subject);
-
-    my $SystemTicket;
-    if ($args{'ticket'} ) {
-        $SystemTicket = RT::Ticket->new($RT::SystemUser);
-        $SystemTicket->Load($args{'ticket'});
+    $SystemTicket = RT::Ticket->new( $RT::SystemUser );
+    $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
+    if ( $SystemTicket->id ) {
+        $Right = 'ReplyToTicket';
+    } else {
+        $Right = 'CreateTicket';
     }
 
     #Set up a queue object
-    my $SystemQueueObj = RT::Queue->new($RT::SystemUser);
+    my $SystemQueueObj = RT::Queue->new( $RT::SystemUser );
     $SystemQueueObj->Load( $args{'queue'} );
 
-
     # We can safely have no queue of we have a known-good ticket
-    unless ( $args{'ticket'} || $SystemQueueObj->id ) {
+    unless ( $SystemTicket->id || $SystemQueueObj->id ) {
+        return ( -75, "RT couldn't find the queue: " . $args{'queue'}, undef );
+    }
+
+    my ($AuthStat, $CurrentUser, $error) = GetAuthenticationLevel(
+        MailPlugins   => \@mail_plugins,
+        Actions       => \@actions,
+        Message       => $Message,
+        RawMessageRef => \$args{message},
+        SystemTicket  => $SystemTicket,
+        SystemQueue   => $SystemQueueObj,
+    );
+
+    # {{{ If authentication fails and no new user was created, get out.
+    if ( !$CurrentUser || !$CurrentUser->id || $AuthStat == -1 ) {
+
+        # If the plugins refused to create one, they lose.
+        unless ( $AuthStat == -1 ) {
+            _NoAuthorizedUserFound(
+                Right     => $Right,
+                Message   => $Message,
+                Requestor => $ErrorsTo,
+                Queue     => $args{'queue'}
+            );
+
+        }
+        return ( 0, "Could not load a valid user", undef );
+    }
+
+    # If we got a user, but they don't have the right to say things
+    if ( $AuthStat == 0 ) {
         MailError(
-                 To          => $RT::OwnerEmail,
-                 Subject     => "RT Bounce: $Subject",
-                 Explanation => "RT couldn't find the queue: " . $args{'queue'},
-                 MIMEObj     => $Message );
-        return ( 0, "RT couldn't find the queue: " . $args{'queue'}, undef );
+            To          => $ErrorsTo,
+            Subject     => "Permission Denied",
+            Explanation =>
+                "You do not have permission to communicate with RT",
+            MIMEObj => $Message
+        );
+        return (
+            0,
+            "$ErrorsTo tried to submit a message to "
+                . $args{'Queue'}
+                . " without permission.",
+            undef
+        );
+    }
+
+
+    unless ($should_store_machine_generated_message) {
+        return ( 0, $result, undef );
+    }
+
+    # if plugin's updated SystemTicket then update arguments
+    $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
+
+    my $Ticket = RT::Ticket->new($CurrentUser);
+
+    if ( !$args{'ticket'} && grep /^(comment|correspond)$/, @actions )
+    {
+
+        my @Cc;
+        my @Requestors = ( $CurrentUser->id );
+
+        if (RT->Config->Get('ParseNewMessageForTicketCcs')) {
+            @Cc = ParseCcAddressesFromHead(
+                Head        => $head,
+                CurrentUser => $CurrentUser,
+                QueueObj    => $SystemQueueObj
+            );
+        }
+
+        my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
+            Queue     => $SystemQueueObj->Id,
+            Subject   => $Subject,
+            Requestor => \@Requestors,
+            Cc        => \@Cc,
+            MIMEObj   => $Message
+        );
+        if ( $id == 0 ) {
+            MailError(
+                To          => $ErrorsTo,
+                Subject     => "Ticket creation failed: $Subject",
+                Explanation => $ErrStr,
+                MIMEObj     => $Message
+            );
+            return ( 0, "Ticket creation failed: $ErrStr", $Ticket );
+        }
+
+        # strip comments&corresponds from the actions we don't need
+        # to record them if we've created the ticket just now
+        @actions = grep !/^(comment|correspond)$/, @actions;
+        $args{'ticket'} = $id;
+
+    } elsif ( $args{'ticket'} ) {
+
+        $Ticket->Load( $args{'ticket'} );
+        unless ( $Ticket->Id ) {
+            my $error = "Could not find a ticket with id " . $args{'ticket'};
+            MailError(
+                To          => $ErrorsTo,
+                Subject     => "Message not recorded: $Subject",
+                Explanation => $error,
+                MIMEObj     => $Message
+            );
+
+            return ( 0, $error );
+        }
+        $args{'ticket'} = $Ticket->id;
+    } else {
+        return ( 1, "Success", $Ticket );
     }
 
+    # }}}
+
+    my $unsafe_actions = RT->Config->Get('UnsafeEmailCommands');
+    foreach my $action (@actions) {
+
+        #   If the action is comment, add a comment.
+        if ( $action =~ /^(?:comment|correspond)$/i ) {
+            my $method = ucfirst lc $action;
+            my ( $status, $msg ) = $Ticket->$method( MIMEObj => $Message );
+            unless ($status) {
+
+                #Warn the sender that we couldn't actually submit the comment.
+                MailError(
+                    To          => $ErrorsTo,
+                    Subject     => "Message not recorded: $Subject",
+                    Explanation => $msg,
+                    MIMEObj     => $Message
+                );
+                return ( 0, "Message not recorded: $msg", $Ticket );
+            }
+        } elsif ($unsafe_actions) {
+            my ( $status, $msg ) = _RunUnsafeAction(
+                Action      => $action,
+                ErrorsTo    => $ErrorsTo,
+                Message     => $Message,
+                Ticket      => $Ticket,
+                CurrentUser => $CurrentUser,
+            );
+            return ($status, $msg, $Ticket) unless $status == 1;
+        }
+    }
+    return ( 1, "Success", $Ticket );
+}
+
+=head2 GetAuthenticationLevel
+
     # Authentication Level
-    # -1 - Get out.  this user has been explicitly declined 
+    # -1 - Get out.  this user has been explicitly declined
     # 0 - User may not do anything (Not used at the moment)
     # 1 - Normal user
     # 2 - User is allowed to specify status updates etc. a la enhanced-mailgate
 
-    push @RT::MailPlugins, "Auth::MailFrom"   unless @RT::MailPlugins;
-    # Since this needs loading, no matter what
+=cut
+
+sub GetAuthenticationLevel {
+    my %args = (
+        MailPlugins   => [],
+        Actions       => [],
+        Message       => undef,
+        RawMessageRef => undef,
+        SystemTicket  => undef,
+        SystemQueue   => undef,
+        @_,
+    );
 
-    for (@RT::MailPlugins) {
-        my $Code;
-        my $NewAuthStat;
+    my ( $CurrentUser, $AuthStat, $error );
+
+    # Initalize AuthStat so comparisons work correctly
+    $AuthStat = -9999999;
+
+    # if plugin returns AuthStat -2 we skip action
+    # NOTE: this is experimental API and it would be changed
+    my %skip_action = ();
+
+    # Since this needs loading, no matter what
+    foreach (@{ $args{MailPlugins} }) {
+        my ($Code, $NewAuthStat);
         if ( ref($_) eq "CODE" ) {
             $Code = $_;
-        }
-        else {
-            $_ = "RT::Interface::Email::$_" unless /^RT::Interface::Email::/;
-            eval "require $_;";
-            if ($@) {
-                die ("Couldn't load module $_: $@");
-                next;
-            }
+        } else {
             no strict 'refs';
-            if ( !defined( $Code = *{ $_ . "::GetCurrentUser" }{CODE} ) ) {
-                die ("No GetCurrentUser code found in $_ module");
-                next;
-            }
+            $Code = *{ $_ . "::GetCurrentUser" }{CODE};
+        }
+
+        foreach my $action (@{ $args{Actions} }) {
+            ( $CurrentUser, $NewAuthStat ) = $Code->(
+                Message       => $args{Message},
+                RawMessageRef => $args{RawMessageRef},
+                CurrentUser   => $CurrentUser,
+                AuthLevel     => $AuthStat,
+                Action        => $action,
+                Ticket        => $args{SystemTicket},
+                Queue         => $args{SystemQueue},
+            );
+
+# You get the highest level of authentication you were assigned, unless you get the magic -1
+# If a module returns a "-1" then we discard the ticket, so.
+            $AuthStat = $NewAuthStat
+                if ( $NewAuthStat > $AuthStat or $NewAuthStat == -1 or $NewAuthStat == -2 );
+
+            last if $AuthStat == -1;
+            $skip_action{$action}++ if $AuthStat == -2;
         }
 
-        ( $CurrentUser, $NewAuthStat ) = $Code->( Message     => $Message,
-                                                  CurrentUser => $CurrentUser,
-                                                  AuthLevel   => $AuthStat,
-                                                  Action => $args{'action'},
-                                                  Ticket => $SystemTicket,
-                                                  Queue  => $SystemQueueObj );
+        # strip actions we should skip
+        @{$args{Actions}} = grep !$skip_action{$_}, @{$args{Actions}}
+            if $AuthStat == -2;
+        last unless @{$args{Actions}};
 
-        # You get the highest level of authentication you were assigned.
         last if $AuthStat == -1;
-        $AuthStat = $NewAuthStat if $NewAuthStat > $AuthStat;
     }
 
-    # {{{ If authentication fails and no new user was created, get out.
-    if ( !$CurrentUser or !$CurrentUser->Id or $AuthStat == -1 ) {
+    return $AuthStat if !wantarray;
 
-        # If the plugins refused to create one, they lose.
-        MailError(
-            Subject     => "Could not load a valid user",
-            Explanation => <<EOT,
+    return ($AuthStat, $CurrentUser, $error);
+}
+
+sub _RunUnsafeAction {
+    my %args = (
+        Action      => undef,
+        ErrorsTo    => undef,
+        Message     => undef,
+        Ticket      => undef,
+        CurrentUser => undef,
+        @_
+    );
+
+    if ( $args{'Action'} =~ /^take$/i ) {
+        my ( $status, $msg ) = $args{'Ticket'}->SetOwner( $args{'CurrentUser'}->id );
+        unless ($status) {
+            MailError(
+                To          => $args{'ErrorsTo'},
+                Subject     => "Ticket not taken",
+                Explanation => $msg,
+                MIMEObj     => $args{'Message'}
+            );
+            return ( 0, "Ticket not taken" );
+        }
+    } elsif ( $args{'Action'} =~ /^resolve$/i ) {
+        my ( $status, $msg ) = $args{'Ticket'}->SetStatus('resolved');
+        unless ($status) {
+
+            #Warn the sender that we couldn't actually submit the comment.
+            MailError(
+                To          => $args{'ErrorsTo'},
+                Subject     => "Ticket not resolved",
+                Explanation => $msg,
+                MIMEObj     => $args{'Message'}
+            );
+            return ( 0, "Ticket not resolved" );
+        }
+    } else {
+        return ( 0, "Not supported unsafe action $args{'Action'}", $args{'Ticket'} );
+    }
+    return ( 1, "Success" );
+}
+
+=head2 _NoAuthorizedUserFound
+
+Emails the RT Owner and the requestor when the auth plugins return "No auth user found"
+
+=cut
+
+sub _NoAuthorizedUserFound {
+    my %args = (
+        Right     => undef,
+        Message   => undef,
+        Requestor => undef,
+        Queue     => undef,
+        @_
+    );
+
+    # Notify the RT Admin of the failure.
+    MailError(
+        To          => RT->Config->Get('OwnerEmail'),
+        Subject     => "Could not load a valid user",
+        Explanation => <<EOT,
 RT could not load a valid user, and RT's configuration does not allow
-for the creation of a new user for your email.
+for the creation of a new user for this email (@{[$args{Requestor}]}).
 
-Your RT administrator needs to grant 'Everyone' the right 'CreateTicket'
-for this queue.
+You might need to grant 'Everyone' the right '@{[$args{Right}]}' for the
+queue @{[$args{'Queue'}]}.
 
 EOT
-            MIMEObj  => $Message,
-            LogLevel => 'error' )
-          unless $AuthStat == -1;
-        return ( 0, "Could not load a valid user", undef );
+        MIMEObj  => $args{'Message'},
+        LogLevel => 'error'
+    );
+
+    # Also notify the requestor that his request has been dropped.
+    if ($args{'Requestor'} ne RT->Config->Get('OwnerEmail')) {
+    MailError(
+        To          => $args{'Requestor'},
+        Subject     => "Could not load a valid user",
+        Explanation => <<EOT,
+RT could not load a valid user, and RT's configuration does not allow
+for the creation of a new user for your email.
+
+EOT
+        MIMEObj  => $args{'Message'},
+        LogLevel => 'error'
+    );
     }
+}
 
-    # }}}
+=head2 _HandleMachineGeneratedMail
+
+Takes named params:
+    Message
+    ErrorsTo
+    Subject
+
+Checks the message to see if it's a bounce, if it looks like a loop, if it's autogenerated, etc.
+Returns a triple of ("Should we continue (boolean)", "New value for $ErrorsTo", "Status message",
+"This message appears to be a loop (boolean)" );
+
+=cut
+
+sub _HandleMachineGeneratedMail {
+    my %args = ( Message => undef, ErrorsTo => undef, Subject => undef, MessageId => undef, @_ );
+    my $head = $args{'Message'}->head;
+    my $ErrorsTo = $args{'ErrorsTo'};
+
+    my $IsBounce = CheckForBounce($head);
 
-    # {{{ Lets check for mail loops of various sorts.
     my $IsAutoGenerated = CheckForAutoGenerated($head);
 
     my $IsSuspiciousSender = CheckForSuspiciousSender($head);
@@ -496,153 +1711,77 @@ EOT
 
     my $SquelchReplies = 0;
 
+    my $owner_mail = RT->Config->Get('OwnerEmail');
+
     #If the message is autogenerated, we need to know, so we can not
     # send mail to the sender
-    if ( $IsSuspiciousSender || $IsAutoGenerated || $IsALoop ) {
+    if ( $IsBounce || $IsSuspiciousSender || $IsAutoGenerated || $IsALoop ) {
         $SquelchReplies = 1;
-        $ErrorsTo       = $RT::OwnerEmail;
+        $ErrorsTo       = $owner_mail;
     }
 
-    # }}}
-
-    # {{{ Drop it if it's disallowed
-    if ( $AuthStat == 0 ) {
-        MailError(
-             To          => $ErrorsTo,
-             Subject     => "Permission Denied",
-             Explanation => "You do not have permission to communicate with RT",
-             MIMEObj     => $Message );
-    }
-
-    # }}}
-    # {{{ Warn someone  if it's a loop
-
     # Warn someone if it's a loop, before we drop it on the ground
     if ($IsALoop) {
-        $RT::Logger->crit("RT Recieved mail ($MessageId) from itself.");
+        $RT::Logger->crit("RT Received mail (".$args{MessageId}.") from itself.");
 
         #Should we mail it to RTOwner?
-        if ($RT::LoopsToRTOwner) {
-            MailError( To          => $RT::OwnerEmail,
-                       Subject     => "RT Bounce: $Subject",
-                       Explanation => "RT thinks this message may be a bounce",
-                       MIMEObj     => $Message );
-
-            #Do we actually want to store it?
-            return ( 0, "Message Bounced", undef ) unless ($RT::StoreLoops);
+        if ( RT->Config->Get('LoopsToRTOwner') ) {
+            MailError(
+                To          => $owner_mail,
+                Subject     => "RT Bounce: ".$args{'Subject'},
+                Explanation => "RT thinks this message may be a bounce",
+                MIMEObj     => $args{Message}
+            );
         }
-    }
 
-    # }}}
+        #Do we actually want to store it?
+        return ( 0, $ErrorsTo, "Message Bounced", $IsALoop )
+            unless RT->Config->Get('StoreLoops');
+    }
 
-    # {{{ Squelch replies if necessary
+    # Squelch replies if necessary
     # Don't let the user stuff the RT-Squelch-Replies-To header.
     if ( $head->get('RT-Squelch-Replies-To') ) {
-        $head->add( 'RT-Relocated-Squelch-Replies-To',
-                    $head->get('RT-Squelch-Replies-To') );
+        $head->add(
+            'RT-Relocated-Squelch-Replies-To',
+            $head->get('RT-Squelch-Replies-To')
+        );
         $head->delete('RT-Squelch-Replies-To');
     }
 
     if ($SquelchReplies) {
-        ## TODO: This is a hack.  It should be some other way to
-        ## indicate that the transaction should be "silent".
 
+        # Squelch replies to the sender, and also leave a clue to
+        # allow us to squelch ALL outbound messages. This way we
+        # can punt the logic of "what to do when we get a bounce"
+        # to the scrip. We might want to notify nobody. Or just
+        # the RT Owner. Or maybe all Privileged watchers.
         my ( $Sender, $junk ) = ParseSenderAddressFromHead($head);
-        $head->add( 'RT-Squelch-Replies-To', $Sender );
-    }
-
-    # }}}
-
-    my $Ticket = RT::Ticket->new($CurrentUser);
-
-    # {{{ If we don't have a ticket Id, we're creating a new ticket
-    if ( !$args{'ticket'} ) {
-
-        # {{{ Create a new ticket
-
-        my @Cc;
-        my @Requestors = ( $CurrentUser->id );
-
-        if ($RT::ParseNewMessageForTicketCcs) {
-            @Cc = ParseCcAddressesFromHead( Head        => $head,
-                                            CurrentUser => $CurrentUser,
-                                            QueueObj    => $SystemQueueObj );
-        }
-
-        my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
-                                                      Queue     => $SystemQueueObj->Id,
-                                                      Subject   => $Subject,
-                                                      Requestor => \@Requestors,
-                                                      Cc        => \@Cc,
-                                                      MIMEObj   => $Message );
-        if ( $id == 0 ) {
-            MailError( To          => $ErrorsTo,
-                       Subject     => "Ticket creation failed",
-                       Explanation => $ErrStr,
-                       MIMEObj     => $Message );
-            $RT::Logger->error("Create failed: $id / $Transaction / $ErrStr ");
-            return ( 0, "Ticket creation failed", $Ticket );
-        }
-
-        # }}}
+        $head->add( 'RT-Squelch-Replies-To',    $Sender );
+        $head->add( 'RT-DetectedAutoGenerated', 'true' );
     }
+    return ( 1, $ErrorsTo, "Handled machine detection", $IsALoop );
+}
 
-    # }}}
-
-    #   If the action is comment, add a comment.
-    elsif ( $args{'action'} =~ /^(comment|correspond)$/i ) {
-        $Ticket->Load($args{'ticket'});
-        unless ( $Ticket->Id ) {
-            my $message = "Could not find a ticket with id ".$args{'ticket'};
-            MailError( To          => $ErrorsTo,
-                     Subject     => "Message not recorded",
-                     Explanation => $message,
-                     MIMEObj     => $Message );
-
-            return ( 0, $message);
-        }
-
-        my ( $status, $msg );
-        if ( $args{'action'} =~ /^correspond$/ ) {
-            ( $status, $msg ) = $Ticket->Correspond( MIMEObj => $Message );
-        }
-        else {
-            ( $status, $msg ) = $Ticket->Comment( MIMEObj => $Message );
-        }
-        unless ($status) {
-
-            #Warn the sender that we couldn't actually submit the comment.
-            MailError( To          => $ErrorsTo,
-                       Subject     => "Message not recorded",
-                       Explanation => $msg,
-                       MIMEObj     => $Message );
-            return ( 0, "Message not recorded", $Ticket );
-        }
-    }
+=head2 IsCorrectAction
 
-    else {
+Returns a list of valid actions we've found for this message
 
-        #Return mail to the sender with an error
-        MailError( To          => $ErrorsTo,
-                   Subject     => "RT Configuration error",
-                   Explanation => "'"
-                     . $args{'action'}
-                     . "' not a recognized action."
-                     . " Your RT administrator has misconfigured "
-                     . "the mail aliases which invoke RT",
-                   MIMEObj => $Message );
-        $RT::Logger->crit( $args{'action'} . " type unknown for $MessageId" );
-        return ( 0, "Configuration error: " . $args{'action'} . " not a recognized action", $Ticket );
+=cut
 
+sub IsCorrectAction {
+    my $action = shift;
+    my @actions = grep $_, split /-/, $action;
+    return ( 0, '(no value)' ) unless @actions;
+    foreach ( @actions ) {
+        return ( 0, $_ ) unless /^(?:comment|correspond|take|resolve)$/;
     }
-
-
-return ( 1, "Success", $Ticket );
+    return ( 1, @actions );
 }
 
 eval "require RT::Interface::Email_Vendor";
-die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email_Vendor.pm});
+die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Interface/Email_Vendor.pm} );
 eval "require RT::Interface::Email_Local";
-die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email_Local.pm});
+die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Interface/Email_Local.pm} );
 
 1;
index 962c378..0cab237 100644 (file)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -61,7 +86,7 @@ sub _Init {
 
 
 
-=item Create PARAMHASH
+=head2 Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -98,7 +123,7 @@ sub Create {
 
 
 
-=item id
+=head2 id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -107,14 +132,14 @@ Returns the current value of id.
 =cut
 
 
-=item Base
+=head2 Base
 
 Returns the current value of Base. 
 (In the database, Base is stored as varchar(240).)
 
 
 
-=item SetBase VALUE
+=head2 SetBase VALUE
 
 
 Set Base to VALUE. 
@@ -125,14 +150,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Target
+=head2 Target
 
 Returns the current value of Target. 
 (In the database, Target is stored as varchar(240).)
 
 
 
-=item SetTarget VALUE
+=head2 SetTarget VALUE
 
 
 Set Target to VALUE. 
@@ -143,14 +168,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Type
+=head2 Type
 
 Returns the current value of Type. 
 (In the database, Type is stored as varchar(20).)
 
 
 
-=item SetType VALUE
+=head2 SetType VALUE
 
 
 Set Type to VALUE. 
@@ -161,14 +186,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item LocalTarget
+=head2 LocalTarget
 
 Returns the current value of LocalTarget. 
 (In the database, LocalTarget is stored as int(11).)
 
 
 
-=item SetLocalTarget VALUE
+=head2 SetLocalTarget VALUE
 
 
 Set LocalTarget to VALUE. 
@@ -179,14 +204,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item LocalBase
+=head2 LocalBase
 
 Returns the current value of LocalBase. 
 (In the database, LocalBase is stored as int(11).)
 
 
 
-=item SetLocalBase VALUE
+=head2 SetLocalBase VALUE
 
 
 Set LocalBase to VALUE. 
@@ -197,7 +222,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item LastUpdatedBy
+=head2 LastUpdatedBy
 
 Returns the current value of LastUpdatedBy. 
 (In the database, LastUpdatedBy is stored as int(11).)
@@ -206,7 +231,7 @@ Returns the current value of LastUpdatedBy.
 =cut
 
 
-=item LastUpdated
+=head2 LastUpdated
 
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
@@ -215,7 +240,7 @@ Returns the current value of LastUpdated.
 =cut
 
 
-=item Creator
+=head2 Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -224,7 +249,7 @@ Returns the current value of Creator.
 =cut
 
 
-=item Created
+=head2 Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -234,29 +259,29 @@ Returns the current value of Created.
 
 
 
-sub _ClassAccessible {
+sub _CoreAccessible {
     {
      
         id =>
-               {read => 1, type => 'int(11)', default => ''},
+               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
         Base => 
-               {read => 1, write => 1, type => 'varchar(240)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 240,  is_blob => 0,  is_numeric => 0,  type => 'varchar(240)', default => ''},
         Target => 
-               {read => 1, write => 1, type => 'varchar(240)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 240,  is_blob => 0,  is_numeric => 0,  type => 'varchar(240)', default => ''},
         Type => 
-               {read => 1, write => 1, type => 'varchar(20)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 20,  is_blob => 0,  is_numeric => 0,  type => 'varchar(20)', default => ''},
         LocalTarget => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         LocalBase => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         LastUpdatedBy => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         LastUpdated => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
         Creator => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
 
  }
 };
@@ -288,7 +313,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index 7a1773a..ecf9643 100644 (file)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -64,7 +89,7 @@ sub _Init {
 }
 
 
-=item NewItem
+=head2 NewItem
 
 Returns an empty new RT::Link item
 
@@ -101,7 +126,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index b362c9f..183ab8f 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -61,7 +86,7 @@ sub _Init {
 
 
 
-=item Create PARAMHASH
+=head2 Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -107,7 +132,7 @@ sub Create {
 
 
 
-=item id
+=head2 id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -116,14 +141,14 @@ Returns the current value of id.
 =cut
 
 
-=item Name
+=head2 Name
 
 Returns the current value of Name. 
 (In the database, Name is stored as varchar(200).)
 
 
 
-=item SetName VALUE
+=head2 SetName VALUE
 
 
 Set Name to VALUE. 
@@ -134,14 +159,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Description
+=head2 Description
 
 Returns the current value of Description. 
 (In the database, Description is stored as varchar(255).)
 
 
 
-=item SetDescription VALUE
+=head2 SetDescription VALUE
 
 
 Set Description to VALUE. 
@@ -152,14 +177,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item CorrespondAddress
+=head2 CorrespondAddress
 
 Returns the current value of CorrespondAddress. 
 (In the database, CorrespondAddress is stored as varchar(120).)
 
 
 
-=item SetCorrespondAddress VALUE
+=head2 SetCorrespondAddress VALUE
 
 
 Set CorrespondAddress to VALUE. 
@@ -170,14 +195,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item CommentAddress
+=head2 CommentAddress
 
 Returns the current value of CommentAddress. 
 (In the database, CommentAddress is stored as varchar(120).)
 
 
 
-=item SetCommentAddress VALUE
+=head2 SetCommentAddress VALUE
 
 
 Set CommentAddress to VALUE. 
@@ -188,14 +213,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item InitialPriority
+=head2 InitialPriority
 
 Returns the current value of InitialPriority. 
 (In the database, InitialPriority is stored as int(11).)
 
 
 
-=item SetInitialPriority VALUE
+=head2 SetInitialPriority VALUE
 
 
 Set InitialPriority to VALUE. 
@@ -206,14 +231,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item FinalPriority
+=head2 FinalPriority
 
 Returns the current value of FinalPriority. 
 (In the database, FinalPriority is stored as int(11).)
 
 
 
-=item SetFinalPriority VALUE
+=head2 SetFinalPriority VALUE
 
 
 Set FinalPriority to VALUE. 
@@ -224,14 +249,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item DefaultDueIn
+=head2 DefaultDueIn
 
 Returns the current value of DefaultDueIn. 
 (In the database, DefaultDueIn is stored as int(11).)
 
 
 
-=item SetDefaultDueIn VALUE
+=head2 SetDefaultDueIn VALUE
 
 
 Set DefaultDueIn to VALUE. 
@@ -242,7 +267,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Creator
+=head2 Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -251,7 +276,7 @@ Returns the current value of Creator.
 =cut
 
 
-=item Created
+=head2 Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -260,7 +285,7 @@ Returns the current value of Created.
 =cut
 
 
-=item LastUpdatedBy
+=head2 LastUpdatedBy
 
 Returns the current value of LastUpdatedBy. 
 (In the database, LastUpdatedBy is stored as int(11).)
@@ -269,7 +294,7 @@ Returns the current value of LastUpdatedBy.
 =cut
 
 
-=item LastUpdated
+=head2 LastUpdated
 
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
@@ -278,14 +303,14 @@ Returns the current value of LastUpdated.
 =cut
 
 
-=item Disabled
+=head2 Disabled
 
 Returns the current value of Disabled. 
 (In the database, Disabled is stored as smallint(6).)
 
 
 
-=item SetDisabled VALUE
+=head2 SetDisabled VALUE
 
 
 Set Disabled to VALUE. 
@@ -297,35 +322,35 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 
 
 
-sub _ClassAccessible {
+sub _CoreAccessible {
     {
      
         id =>
-               {read => 1, type => 'int(11)', default => ''},
+               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
         Name => 
-               {read => 1, write => 1, type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
         Description => 
-               {read => 1, write => 1, type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
         CorrespondAddress => 
-               {read => 1, write => 1, type => 'varchar(120)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
         CommentAddress => 
-               {read => 1, write => 1, type => 'varchar(120)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
         InitialPriority => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         FinalPriority => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         DefaultDueIn => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Creator => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
         LastUpdatedBy => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         LastUpdated => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
         Disabled => 
-               {read => 1, write => 1, type => 'smallint(6)', default => '0'},
+               {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
 
  }
 };
@@ -357,7 +382,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index 60aec90..48f40f1 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -64,7 +89,7 @@ sub _Init {
 }
 
 
-=item NewItem
+=head2 NewItem
 
 Returns an empty new RT::Queue item
 
@@ -101,7 +126,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index a69dde0..ecc47e9 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -65,7 +90,7 @@ sub _Init {
 
 
 
-=item Create PARAMHASH
+=head2 Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -120,7 +145,7 @@ sub Create {
 
 
 
-=item id
+=head2 id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -129,14 +154,14 @@ Returns the current value of id.
 =cut
 
 
-=item Description
+=head2 Description
 
 Returns the current value of Description. 
 (In the database, Description is stored as varchar(255).)
 
 
 
-=item SetDescription VALUE
+=head2 SetDescription VALUE
 
 
 Set Description to VALUE. 
@@ -147,14 +172,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item ScripCondition
+=head2 ScripCondition
 
 Returns the current value of ScripCondition. 
 (In the database, ScripCondition is stored as int(11).)
 
 
 
-=item SetScripCondition VALUE
+=head2 SetScripCondition VALUE
 
 
 Set ScripCondition to VALUE. 
@@ -165,7 +190,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item ScripConditionObj
+=head2 ScripConditionObj
 
 Returns the ScripCondition Object which has the id returned by ScripCondition
 
@@ -179,14 +204,14 @@ sub ScripConditionObj {
        return($ScripCondition);
 }
 
-=item ScripAction
+=head2 ScripAction
 
 Returns the current value of ScripAction. 
 (In the database, ScripAction is stored as int(11).)
 
 
 
-=item SetScripAction VALUE
+=head2 SetScripAction VALUE
 
 
 Set ScripAction to VALUE. 
@@ -197,7 +222,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item ScripActionObj
+=head2 ScripActionObj
 
 Returns the ScripAction Object which has the id returned by ScripAction
 
@@ -211,14 +236,14 @@ sub ScripActionObj {
        return($ScripAction);
 }
 
-=item ConditionRules
+=head2 ConditionRules
 
 Returns the current value of ConditionRules. 
 (In the database, ConditionRules is stored as text.)
 
 
 
-=item SetConditionRules VALUE
+=head2 SetConditionRules VALUE
 
 
 Set ConditionRules to VALUE. 
@@ -229,14 +254,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item ActionRules
+=head2 ActionRules
 
 Returns the current value of ActionRules. 
 (In the database, ActionRules is stored as text.)
 
 
 
-=item SetActionRules VALUE
+=head2 SetActionRules VALUE
 
 
 Set ActionRules to VALUE. 
@@ -247,14 +272,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item CustomIsApplicableCode
+=head2 CustomIsApplicableCode
 
 Returns the current value of CustomIsApplicableCode. 
 (In the database, CustomIsApplicableCode is stored as text.)
 
 
 
-=item SetCustomIsApplicableCode VALUE
+=head2 SetCustomIsApplicableCode VALUE
 
 
 Set CustomIsApplicableCode to VALUE. 
@@ -265,14 +290,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item CustomPrepareCode
+=head2 CustomPrepareCode
 
 Returns the current value of CustomPrepareCode. 
 (In the database, CustomPrepareCode is stored as text.)
 
 
 
-=item SetCustomPrepareCode VALUE
+=head2 SetCustomPrepareCode VALUE
 
 
 Set CustomPrepareCode to VALUE. 
@@ -283,14 +308,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item CustomCommitCode
+=head2 CustomCommitCode
 
 Returns the current value of CustomCommitCode. 
 (In the database, CustomCommitCode is stored as text.)
 
 
 
-=item SetCustomCommitCode VALUE
+=head2 SetCustomCommitCode VALUE
 
 
 Set CustomCommitCode to VALUE. 
@@ -301,14 +326,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Stage
+=head2 Stage
 
 Returns the current value of Stage. 
 (In the database, Stage is stored as varchar(32).)
 
 
 
-=item SetStage VALUE
+=head2 SetStage VALUE
 
 
 Set Stage to VALUE. 
@@ -319,14 +344,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Queue
+=head2 Queue
 
 Returns the current value of Queue. 
 (In the database, Queue is stored as int(11).)
 
 
 
-=item SetQueue VALUE
+=head2 SetQueue VALUE
 
 
 Set Queue to VALUE. 
@@ -337,7 +362,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item QueueObj
+=head2 QueueObj
 
 Returns the Queue Object which has the id returned by Queue
 
@@ -351,14 +376,14 @@ sub QueueObj {
        return($Queue);
 }
 
-=item Template
+=head2 Template
 
 Returns the current value of Template. 
 (In the database, Template is stored as int(11).)
 
 
 
-=item SetTemplate VALUE
+=head2 SetTemplate VALUE
 
 
 Set Template to VALUE. 
@@ -369,7 +394,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item TemplateObj
+=head2 TemplateObj
 
 Returns the Template Object which has the id returned by Template
 
@@ -383,7 +408,7 @@ sub TemplateObj {
        return($Template);
 }
 
-=item Creator
+=head2 Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -392,7 +417,7 @@ Returns the current value of Creator.
 =cut
 
 
-=item Created
+=head2 Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -401,7 +426,7 @@ Returns the current value of Created.
 =cut
 
 
-=item LastUpdatedBy
+=head2 LastUpdatedBy
 
 Returns the current value of LastUpdatedBy. 
 (In the database, LastUpdatedBy is stored as int(11).)
@@ -410,7 +435,7 @@ Returns the current value of LastUpdatedBy.
 =cut
 
 
-=item LastUpdated
+=head2 LastUpdated
 
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
@@ -420,41 +445,41 @@ Returns the current value of LastUpdated.
 
 
 
-sub _ClassAccessible {
+sub _CoreAccessible {
     {
      
         id =>
-               {read => 1, type => 'int(11)', default => ''},
+               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
         Description => 
-               {read => 1, write => 1, type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
         ScripCondition => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         ScripAction => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         ConditionRules => 
-               {read => 1, write => 1, type => 'text', default => ''},
+               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
         ActionRules => 
-               {read => 1, write => 1, type => 'text', default => ''},
+               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
         CustomIsApplicableCode => 
-               {read => 1, write => 1, type => 'text', default => ''},
+               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
         CustomPrepareCode => 
-               {read => 1, write => 1, type => 'text', default => ''},
+               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
         CustomCommitCode => 
-               {read => 1, write => 1, type => 'text', default => ''},
+               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
         Stage => 
-               {read => 1, write => 1, type => 'varchar(32)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 32,  is_blob => 0,  is_numeric => 0,  type => 'varchar(32)', default => ''},
         Queue => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Template => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Creator => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
         LastUpdatedBy => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         LastUpdated => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
 
  }
 };
@@ -486,7 +511,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index 26824df..87a2613 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -61,7 +86,7 @@ sub _Init {
 
 
 
-=item Create PARAMHASH
+=head2 Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -95,7 +120,7 @@ sub Create {
 
 
 
-=item id
+=head2 id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -104,14 +129,14 @@ Returns the current value of id.
 =cut
 
 
-=item Name
+=head2 Name
 
 Returns the current value of Name. 
 (In the database, Name is stored as varchar(200).)
 
 
 
-=item SetName VALUE
+=head2 SetName VALUE
 
 
 Set Name to VALUE. 
@@ -122,14 +147,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Description
+=head2 Description
 
 Returns the current value of Description. 
 (In the database, Description is stored as varchar(255).)
 
 
 
-=item SetDescription VALUE
+=head2 SetDescription VALUE
 
 
 Set Description to VALUE. 
@@ -140,14 +165,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item ExecModule
+=head2 ExecModule
 
 Returns the current value of ExecModule. 
 (In the database, ExecModule is stored as varchar(60).)
 
 
 
-=item SetExecModule VALUE
+=head2 SetExecModule VALUE
 
 
 Set ExecModule to VALUE. 
@@ -158,14 +183,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Argument
+=head2 Argument
 
 Returns the current value of Argument. 
 (In the database, Argument is stored as varchar(255).)
 
 
 
-=item SetArgument VALUE
+=head2 SetArgument VALUE
 
 
 Set Argument to VALUE. 
@@ -176,7 +201,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Creator
+=head2 Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -185,7 +210,7 @@ Returns the current value of Creator.
 =cut
 
 
-=item Created
+=head2 Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -194,7 +219,7 @@ Returns the current value of Created.
 =cut
 
 
-=item LastUpdatedBy
+=head2 LastUpdatedBy
 
 Returns the current value of LastUpdatedBy. 
 (In the database, LastUpdatedBy is stored as int(11).)
@@ -203,7 +228,7 @@ Returns the current value of LastUpdatedBy.
 =cut
 
 
-=item LastUpdated
+=head2 LastUpdated
 
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
@@ -213,27 +238,27 @@ Returns the current value of LastUpdated.
 
 
 
-sub _ClassAccessible {
+sub _CoreAccessible {
     {
      
         id =>
-               {read => 1, type => 'int(11)', default => ''},
+               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
         Name => 
-               {read => 1, write => 1, type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
         Description => 
-               {read => 1, write => 1, type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
         ExecModule => 
-               {read => 1, write => 1, type => 'varchar(60)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 60,  is_blob => 0,  is_numeric => 0,  type => 'varchar(60)', default => ''},
         Argument => 
-               {read => 1, write => 1, type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
         Creator => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
         LastUpdatedBy => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         LastUpdated => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
 
  }
 };
@@ -265,7 +290,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index 614ff37..1f6fb98 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -64,7 +89,7 @@ sub _Init {
 }
 
 
-=item NewItem
+=head2 NewItem
 
 Returns an empty new RT::ScripAction item
 
@@ -101,7 +126,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index fe0aa2d..072a24a 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -61,7 +86,7 @@ sub _Init {
 
 
 
-=item Create PARAMHASH
+=head2 Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -98,7 +123,7 @@ sub Create {
 
 
 
-=item id
+=head2 id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -107,14 +132,14 @@ Returns the current value of id.
 =cut
 
 
-=item Name
+=head2 Name
 
 Returns the current value of Name. 
 (In the database, Name is stored as varchar(200).)
 
 
 
-=item SetName VALUE
+=head2 SetName VALUE
 
 
 Set Name to VALUE. 
@@ -125,14 +150,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Description
+=head2 Description
 
 Returns the current value of Description. 
 (In the database, Description is stored as varchar(255).)
 
 
 
-=item SetDescription VALUE
+=head2 SetDescription VALUE
 
 
 Set Description to VALUE. 
@@ -143,14 +168,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item ExecModule
+=head2 ExecModule
 
 Returns the current value of ExecModule. 
 (In the database, ExecModule is stored as varchar(60).)
 
 
 
-=item SetExecModule VALUE
+=head2 SetExecModule VALUE
 
 
 Set ExecModule to VALUE. 
@@ -161,14 +186,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Argument
+=head2 Argument
 
 Returns the current value of Argument. 
 (In the database, Argument is stored as varchar(255).)
 
 
 
-=item SetArgument VALUE
+=head2 SetArgument VALUE
 
 
 Set Argument to VALUE. 
@@ -179,14 +204,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item ApplicableTransTypes
+=head2 ApplicableTransTypes
 
 Returns the current value of ApplicableTransTypes. 
 (In the database, ApplicableTransTypes is stored as varchar(60).)
 
 
 
-=item SetApplicableTransTypes VALUE
+=head2 SetApplicableTransTypes VALUE
 
 
 Set ApplicableTransTypes to VALUE. 
@@ -197,7 +222,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Creator
+=head2 Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -206,7 +231,7 @@ Returns the current value of Creator.
 =cut
 
 
-=item Created
+=head2 Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -215,7 +240,7 @@ Returns the current value of Created.
 =cut
 
 
-=item LastUpdatedBy
+=head2 LastUpdatedBy
 
 Returns the current value of LastUpdatedBy. 
 (In the database, LastUpdatedBy is stored as int(11).)
@@ -224,7 +249,7 @@ Returns the current value of LastUpdatedBy.
 =cut
 
 
-=item LastUpdated
+=head2 LastUpdated
 
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
@@ -234,29 +259,29 @@ Returns the current value of LastUpdated.
 
 
 
-sub _ClassAccessible {
+sub _CoreAccessible {
     {
      
         id =>
-               {read => 1, type => 'int(11)', default => ''},
+               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
         Name => 
-               {read => 1, write => 1, type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
         Description => 
-               {read => 1, write => 1, type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
         ExecModule => 
-               {read => 1, write => 1, type => 'varchar(60)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 60,  is_blob => 0,  is_numeric => 0,  type => 'varchar(60)', default => ''},
         Argument => 
-               {read => 1, write => 1, type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
         ApplicableTransTypes => 
-               {read => 1, write => 1, type => 'varchar(60)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 60,  is_blob => 0,  is_numeric => 0,  type => 'varchar(60)', default => ''},
         Creator => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
         LastUpdatedBy => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         LastUpdated => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
 
  }
 };
@@ -288,7 +313,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index 34f788d..43ea86d 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -64,7 +89,7 @@ sub _Init {
 }
 
 
-=item NewItem
+=head2 NewItem
 
 Returns an empty new RT::ScripCondition item
 
@@ -101,7 +126,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index a394431..02d1019 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -64,7 +89,7 @@ sub _Init {
 }
 
 
-=item NewItem
+=head2 NewItem
 
 Returns an empty new RT::Scrip item
 
@@ -101,7 +126,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index f73ea3e..f27a0c2 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -62,7 +87,7 @@ sub _Init {
 
 
 
-=item Create PARAMHASH
+=head2 Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -105,7 +130,7 @@ sub Create {
 
 
 
-=item id
+=head2 id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -114,14 +139,14 @@ Returns the current value of id.
 =cut
 
 
-=item Queue
+=head2 Queue
 
 Returns the current value of Queue. 
 (In the database, Queue is stored as int(11).)
 
 
 
-=item SetQueue VALUE
+=head2 SetQueue VALUE
 
 
 Set Queue to VALUE. 
@@ -132,7 +157,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item QueueObj
+=head2 QueueObj
 
 Returns the Queue Object which has the id returned by Queue
 
@@ -146,14 +171,14 @@ sub QueueObj {
        return($Queue);
 }
 
-=item Name
+=head2 Name
 
 Returns the current value of Name. 
 (In the database, Name is stored as varchar(200).)
 
 
 
-=item SetName VALUE
+=head2 SetName VALUE
 
 
 Set Name to VALUE. 
@@ -164,14 +189,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Description
+=head2 Description
 
 Returns the current value of Description. 
 (In the database, Description is stored as varchar(255).)
 
 
 
-=item SetDescription VALUE
+=head2 SetDescription VALUE
 
 
 Set Description to VALUE. 
@@ -182,14 +207,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Type
+=head2 Type
 
 Returns the current value of Type. 
 (In the database, Type is stored as varchar(16).)
 
 
 
-=item SetType VALUE
+=head2 SetType VALUE
 
 
 Set Type to VALUE. 
@@ -200,14 +225,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Language
+=head2 Language
 
 Returns the current value of Language. 
 (In the database, Language is stored as varchar(16).)
 
 
 
-=item SetLanguage VALUE
+=head2 SetLanguage VALUE
 
 
 Set Language to VALUE. 
@@ -218,14 +243,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item TranslationOf
+=head2 TranslationOf
 
 Returns the current value of TranslationOf. 
 (In the database, TranslationOf is stored as int(11).)
 
 
 
-=item SetTranslationOf VALUE
+=head2 SetTranslationOf VALUE
 
 
 Set TranslationOf to VALUE. 
@@ -236,14 +261,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Content
+=head2 Content
 
 Returns the current value of Content. 
 (In the database, Content is stored as blob.)
 
 
 
-=item SetContent VALUE
+=head2 SetContent VALUE
 
 
 Set Content to VALUE. 
@@ -254,7 +279,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item LastUpdated
+=head2 LastUpdated
 
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
@@ -263,7 +288,7 @@ Returns the current value of LastUpdated.
 =cut
 
 
-=item LastUpdatedBy
+=head2 LastUpdatedBy
 
 Returns the current value of LastUpdatedBy. 
 (In the database, LastUpdatedBy is stored as int(11).)
@@ -272,7 +297,7 @@ Returns the current value of LastUpdatedBy.
 =cut
 
 
-=item Creator
+=head2 Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -281,7 +306,7 @@ Returns the current value of Creator.
 =cut
 
 
-=item Created
+=head2 Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -291,33 +316,33 @@ Returns the current value of Created.
 
 
 
-sub _ClassAccessible {
+sub _CoreAccessible {
     {
      
         id =>
-               {read => 1, type => 'int(11)', default => ''},
+               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
         Queue => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Name => 
-               {read => 1, write => 1, type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
         Description => 
-               {read => 1, write => 1, type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
         Type => 
-               {read => 1, write => 1, type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
         Language => 
-               {read => 1, write => 1, type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
         TranslationOf => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Content => 
-               {read => 1, write => 1, type => 'blob', default => ''},
+               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'blob', default => ''},
         LastUpdated => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
         LastUpdatedBy => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Creator => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
 
  }
 };
@@ -349,7 +374,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index 37db840..8988702 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -64,7 +89,7 @@ sub _Init {
 }
 
 
-=item NewItem
+=head2 NewItem
 
 Returns an empty new RT::Template item
 
@@ -101,7 +126,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index 2f075a2..4dcd189 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
 # 
-# END LICENSE BLOCK
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -62,7 +87,7 @@ sub _Init {
 
 
 
-=item Create PARAMHASH
+=head2 Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -116,7 +141,7 @@ sub Create {
                 Resolved => '',
                 Disabled => '0',
 
-                 @_);
+          @_);
     $self->SUPER::Create(
                          EffectiveId => $args{'EffectiveId'},
                          Queue => $args{'Queue'},
@@ -144,7 +169,7 @@ sub Create {
 
 
 
-=item id
+=head2 id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -153,14 +178,14 @@ Returns the current value of id.
 =cut
 
 
-=item EffectiveId
+=head2 EffectiveId
 
 Returns the current value of EffectiveId. 
 (In the database, EffectiveId is stored as int(11).)
 
 
 
-=item SetEffectiveId VALUE
+=head2 SetEffectiveId VALUE
 
 
 Set EffectiveId to VALUE. 
@@ -171,14 +196,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Queue
+=head2 Queue
 
 Returns the current value of Queue. 
 (In the database, Queue is stored as int(11).)
 
 
 
-=item SetQueue VALUE
+=head2 SetQueue VALUE
 
 
 Set Queue to VALUE. 
@@ -189,7 +214,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item QueueObj
+=head2 QueueObj
 
 Returns the Queue Object which has the id returned by Queue
 
@@ -197,20 +222,22 @@ Returns the Queue Object which has the id returned by Queue
 =cut
 
 sub QueueObj {
-       my $self = shift;
-       my $Queue =  RT::Queue->new($self->CurrentUser);
-       $Queue->Load($self->__Value('Queue'));
-       return($Queue);
+    my $self = shift;
+    my $Queue =  RT::Queue->new($self->CurrentUser);
+    $Queue->Load($self->__Value('Queue'));
+    return($Queue);
 }
 
-=item Type
+
+
+=head2 Type
 
 Returns the current value of Type. 
 (In the database, Type is stored as varchar(16).)
 
 
 
-=item SetType VALUE
+=head2 SetType VALUE
 
 
 Set Type to VALUE. 
@@ -221,14 +248,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item IssueStatement
+=head2 IssueStatement
 
 Returns the current value of IssueStatement. 
 (In the database, IssueStatement is stored as int(11).)
 
 
 
-=item SetIssueStatement VALUE
+=head2 SetIssueStatement VALUE
 
 
 Set IssueStatement to VALUE. 
@@ -239,14 +266,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Resolution
+=head2 Resolution
 
 Returns the current value of Resolution. 
 (In the database, Resolution is stored as int(11).)
 
 
 
-=item SetResolution VALUE
+=head2 SetResolution VALUE
 
 
 Set Resolution to VALUE. 
@@ -257,14 +284,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Owner
+=head2 Owner
 
 Returns the current value of Owner. 
 (In the database, Owner is stored as int(11).)
 
 
 
-=item SetOwner VALUE
+=head2 SetOwner VALUE
 
 
 Set Owner to VALUE. 
@@ -275,14 +302,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Subject
+=head2 Subject
 
 Returns the current value of Subject. 
 (In the database, Subject is stored as varchar(200).)
 
 
 
-=item SetSubject VALUE
+=head2 SetSubject VALUE
 
 
 Set Subject to VALUE. 
@@ -293,14 +320,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item InitialPriority
+=head2 InitialPriority
 
 Returns the current value of InitialPriority. 
 (In the database, InitialPriority is stored as int(11).)
 
 
 
-=item SetInitialPriority VALUE
+=head2 SetInitialPriority VALUE
 
 
 Set InitialPriority to VALUE. 
@@ -311,14 +338,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item FinalPriority
+=head2 FinalPriority
 
 Returns the current value of FinalPriority. 
 (In the database, FinalPriority is stored as int(11).)
 
 
 
-=item SetFinalPriority VALUE
+=head2 SetFinalPriority VALUE
 
 
 Set FinalPriority to VALUE. 
@@ -329,14 +356,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Priority
+=head2 Priority
 
 Returns the current value of Priority. 
 (In the database, Priority is stored as int(11).)
 
 
 
-=item SetPriority VALUE
+=head2 SetPriority VALUE
 
 
 Set Priority to VALUE. 
@@ -347,14 +374,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item TimeEstimated
+=head2 TimeEstimated
 
 Returns the current value of TimeEstimated. 
 (In the database, TimeEstimated is stored as int(11).)
 
 
 
-=item SetTimeEstimated VALUE
+=head2 SetTimeEstimated VALUE
 
 
 Set TimeEstimated to VALUE. 
@@ -365,14 +392,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item TimeWorked
+=head2 TimeWorked
 
 Returns the current value of TimeWorked. 
 (In the database, TimeWorked is stored as int(11).)
 
 
 
-=item SetTimeWorked VALUE
+=head2 SetTimeWorked VALUE
 
 
 Set TimeWorked to VALUE. 
@@ -383,14 +410,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Status
+=head2 Status
 
 Returns the current value of Status. 
 (In the database, Status is stored as varchar(10).)
 
 
 
-=item SetStatus VALUE
+=head2 SetStatus VALUE
 
 
 Set Status to VALUE. 
@@ -401,14 +428,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item TimeLeft
+=head2 TimeLeft
 
 Returns the current value of TimeLeft. 
 (In the database, TimeLeft is stored as int(11).)
 
 
 
-=item SetTimeLeft VALUE
+=head2 SetTimeLeft VALUE
 
 
 Set TimeLeft to VALUE. 
@@ -419,14 +446,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Told
+=head2 Told
 
 Returns the current value of Told. 
 (In the database, Told is stored as datetime.)
 
 
 
-=item SetTold VALUE
+=head2 SetTold VALUE
 
 
 Set Told to VALUE. 
@@ -437,14 +464,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Starts
+=head2 Starts
 
 Returns the current value of Starts. 
 (In the database, Starts is stored as datetime.)
 
 
 
-=item SetStarts VALUE
+=head2 SetStarts VALUE
 
 
 Set Starts to VALUE. 
@@ -455,14 +482,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Started
+=head2 Started
 
 Returns the current value of Started. 
 (In the database, Started is stored as datetime.)
 
 
 
-=item SetStarted VALUE
+=head2 SetStarted VALUE
 
 
 Set Started to VALUE. 
@@ -473,14 +500,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Due
+=head2 Due
 
 Returns the current value of Due. 
 (In the database, Due is stored as datetime.)
 
 
 
-=item SetDue VALUE
+=head2 SetDue VALUE
 
 
 Set Due to VALUE. 
@@ -491,14 +518,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Resolved
+=head2 Resolved
 
 Returns the current value of Resolved. 
 (In the database, Resolved is stored as datetime.)
 
 
 
-=item SetResolved VALUE
+=head2 SetResolved VALUE
 
 
 Set Resolved to VALUE. 
@@ -509,7 +536,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item LastUpdatedBy
+=head2 LastUpdatedBy
 
 Returns the current value of LastUpdatedBy. 
 (In the database, LastUpdatedBy is stored as int(11).)
@@ -518,7 +545,7 @@ Returns the current value of LastUpdatedBy.
 =cut
 
 
-=item LastUpdated
+=head2 LastUpdated
 
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
@@ -527,7 +554,7 @@ Returns the current value of LastUpdated.
 =cut
 
 
-=item Creator
+=head2 Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -536,7 +563,7 @@ Returns the current value of Creator.
 =cut
 
 
-=item Created
+=head2 Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -545,14 +572,14 @@ Returns the current value of Created.
 =cut
 
 
-=item Disabled
+=head2 Disabled
 
 Returns the current value of Disabled. 
 (In the database, Disabled is stored as smallint(6).)
 
 
 
-=item SetDisabled VALUE
+=head2 SetDisabled VALUE
 
 
 Set Disabled to VALUE. 
@@ -564,59 +591,59 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 
 
 
-sub _ClassAccessible {
+sub _CoreAccessible {
     {
      
         id =>
-               {read => 1, type => 'int(11)', default => ''},
+        {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
         EffectiveId => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+        {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Queue => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+        {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Type => 
-               {read => 1, write => 1, type => 'varchar(16)', default => ''},
+        {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
         IssueStatement => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+        {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Resolution => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+        {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Owner => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+        {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Subject => 
-               {read => 1, write => 1, type => 'varchar(200)', default => '[no subject]'},
+        {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => '[no subject]'},
         InitialPriority => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+        {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         FinalPriority => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+        {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Priority => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+        {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         TimeEstimated => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+        {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         TimeWorked => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+        {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Status => 
-               {read => 1, write => 1, type => 'varchar(10)', default => ''},
+        {read => 1, write => 1, sql_type => 12, length => 10,  is_blob => 0,  is_numeric => 0,  type => 'varchar(10)', default => ''},
         TimeLeft => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+        {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Told => 
-               {read => 1, write => 1, type => 'datetime', default => ''},
+        {read => 1, write => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
         Starts => 
-               {read => 1, write => 1, type => 'datetime', default => ''},
+        {read => 1, write => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
         Started => 
-               {read => 1, write => 1, type => 'datetime', default => ''},
+        {read => 1, write => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
         Due => 
-               {read => 1, write => 1, type => 'datetime', default => ''},
+        {read => 1, write => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
         Resolved => 
-               {read => 1, write => 1, type => 'datetime', default => ''},
+        {read => 1, write => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
         LastUpdatedBy => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+        {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         LastUpdated => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+        {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
         Creator => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+        {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+        {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
         Disabled => 
-               {read => 1, write => 1, type => 'smallint(6)', default => '0'},
+        {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
 
  }
 };
@@ -648,7 +675,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index b6b3491..d0699e5 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -64,7 +89,7 @@ sub _Init {
 }
 
 
-=item NewItem
+=head2 NewItem
 
 Returns an empty new RT::Ticket item
 
@@ -101,7 +126,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index ca491a6..351e79d 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -45,7 +70,6 @@ RT::Transaction
 
 package RT::Transaction;
 use RT::Record; 
-use RT::Ticket;
 
 
 use vars qw( @ISA );
@@ -62,18 +86,21 @@ sub _Init {
 
 
 
-=item Create PARAMHASH
+=head2 Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
-  int(11) 'EffectiveTicket'.
-  int(11) 'Ticket'.
+  varchar(64) 'ObjectType'.
+  int(11) 'ObjectId'.
   int(11) 'TimeTaken'.
   varchar(20) 'Type'.
   varchar(40) 'Field'.
   varchar(255) 'OldValue'.
   varchar(255) 'NewValue'.
-  varchar(100) 'Data'.
+  varchar(255) 'ReferenceType'.
+  int(11) 'OldReference'.
+  int(11) 'NewReference'.
+  varchar(255) 'Data'.
 
 =cut
 
@@ -83,24 +110,30 @@ Create takes a hash of values and creates a row in the database:
 sub Create {
     my $self = shift;
     my %args = ( 
-                EffectiveTicket => '0',
-                Ticket => '0',
+                ObjectType => '',
+                ObjectId => '0',
                 TimeTaken => '0',
                 Type => '',
                 Field => '',
                 OldValue => '',
                 NewValue => '',
+                ReferenceType => '',
+                OldReference => '',
+                NewReference => '',
                 Data => '',
 
                  @_);
     $self->SUPER::Create(
-                         EffectiveTicket => $args{'EffectiveTicket'},
-                         Ticket => $args{'Ticket'},
+                         ObjectType => $args{'ObjectType'},
+                         ObjectId => $args{'ObjectId'},
                          TimeTaken => $args{'TimeTaken'},
                          Type => $args{'Type'},
                          Field => $args{'Field'},
                          OldValue => $args{'OldValue'},
                          NewValue => $args{'NewValue'},
+                         ReferenceType => $args{'ReferenceType'},
+                         OldReference => $args{'OldReference'},
+                         NewReference => $args{'NewReference'},
                          Data => $args{'Data'},
 );
 
@@ -108,7 +141,7 @@ sub Create {
 
 
 
-=item id
+=head2 id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -117,64 +150,50 @@ Returns the current value of id.
 =cut
 
 
-=item EffectiveTicket
+=head2 ObjectType
 
-Returns the current value of EffectiveTicket
-(In the database, EffectiveTicket is stored as int(11).)
+Returns the current value of ObjectType
+(In the database, ObjectType is stored as varchar(64).)
 
 
 
-=item SetEffectiveTicket VALUE
+=head2 SetObjectType VALUE
 
 
-Set EffectiveTicket to VALUE. 
+Set ObjectType to VALUE. 
 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, EffectiveTicket will be stored as a int(11).)
+(In the database, ObjectType will be stored as a varchar(64).)
 
 
 =cut
 
 
-=item Ticket
+=head2 ObjectId
 
-Returns the current value of Ticket
-(In the database, Ticket is stored as int(11).)
+Returns the current value of ObjectId
+(In the database, ObjectId is stored as int(11).)
 
 
 
-=item SetTicket VALUE
+=head2 SetObjectId VALUE
 
 
-Set Ticket to VALUE. 
+Set ObjectId to VALUE. 
 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, Ticket will be stored as a int(11).)
+(In the database, ObjectId will be stored as a int(11).)
 
 
 =cut
 
 
-=item TicketObj
-
-Returns the Ticket Object which has the id returned by Ticket
-
-
-=cut
-
-sub TicketObj {
-       my $self = shift;
-       my $Ticket =  RT::Ticket->new($self->CurrentUser);
-       $Ticket->Load($self->__Value('Ticket'));
-       return($Ticket);
-}
-
-=item TimeTaken
+=head2 TimeTaken
 
 Returns the current value of TimeTaken. 
 (In the database, TimeTaken is stored as int(11).)
 
 
 
-=item SetTimeTaken VALUE
+=head2 SetTimeTaken VALUE
 
 
 Set TimeTaken to VALUE. 
@@ -185,14 +204,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Type
+=head2 Type
 
 Returns the current value of Type. 
 (In the database, Type is stored as varchar(20).)
 
 
 
-=item SetType VALUE
+=head2 SetType VALUE
 
 
 Set Type to VALUE. 
@@ -203,14 +222,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Field
+=head2 Field
 
 Returns the current value of Field. 
 (In the database, Field is stored as varchar(40).)
 
 
 
-=item SetField VALUE
+=head2 SetField VALUE
 
 
 Set Field to VALUE. 
@@ -221,14 +240,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item OldValue
+=head2 OldValue
 
 Returns the current value of OldValue. 
 (In the database, OldValue is stored as varchar(255).)
 
 
 
-=item SetOldValue VALUE
+=head2 SetOldValue VALUE
 
 
 Set OldValue to VALUE. 
@@ -239,14 +258,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item NewValue
+=head2 NewValue
 
 Returns the current value of NewValue. 
 (In the database, NewValue is stored as varchar(255).)
 
 
 
-=item SetNewValue VALUE
+=head2 SetNewValue VALUE
 
 
 Set NewValue to VALUE. 
@@ -257,25 +276,79 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Data
+=head2 ReferenceType
+
+Returns the current value of ReferenceType. 
+(In the database, ReferenceType is stored as varchar(255).)
+
+
+
+=head2 SetReferenceType VALUE
+
+
+Set ReferenceType to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ReferenceType will be stored as a varchar(255).)
+
+
+=cut
+
+
+=head2 OldReference
+
+Returns the current value of OldReference. 
+(In the database, OldReference is stored as int(11).)
+
+
+
+=head2 SetOldReference VALUE
+
+
+Set OldReference to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, OldReference will be stored as a int(11).)
+
+
+=cut
+
+
+=head2 NewReference
+
+Returns the current value of NewReference. 
+(In the database, NewReference is stored as int(11).)
+
+
+
+=head2 SetNewReference VALUE
+
+
+Set NewReference to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, NewReference will be stored as a int(11).)
+
+
+=cut
+
+
+=head2 Data
 
 Returns the current value of Data. 
-(In the database, Data is stored as varchar(100).)
+(In the database, Data is stored as varchar(255).)
 
 
 
-=item SetData VALUE
+=head2 SetData VALUE
 
 
 Set Data to VALUE. 
 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, Data will be stored as a varchar(100).)
+(In the database, Data will be stored as a varchar(255).)
 
 
 =cut
 
 
-=item Creator
+=head2 Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -284,7 +357,7 @@ Returns the current value of Creator.
 =cut
 
 
-=item Created
+=head2 Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -294,31 +367,37 @@ Returns the current value of Created.
 
 
 
-sub _ClassAccessible {
+sub _CoreAccessible {
     {
      
         id =>
-               {read => 1, type => 'int(11)', default => ''},
-        EffectiveTicket => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
-        Ticket => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+        ObjectType => 
+               {read => 1, write => 1, sql_type => 12, length => 64,  is_blob => 0,  is_numeric => 0,  type => 'varchar(64)', default => ''},
+        ObjectId => 
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         TimeTaken => 
-               {read => 1, write => 1, type => 'int(11)', default => '0'},
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Type => 
-               {read => 1, write => 1, type => 'varchar(20)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 20,  is_blob => 0,  is_numeric => 0,  type => 'varchar(20)', default => ''},
         Field => 
-               {read => 1, write => 1, type => 'varchar(40)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 40,  is_blob => 0,  is_numeric => 0,  type => 'varchar(40)', default => ''},
         OldValue => 
-               {read => 1, write => 1, type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
         NewValue => 
-               {read => 1, write => 1, type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
+        ReferenceType => 
+               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
+        OldReference => 
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+        NewReference => 
+               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
         Data => 
-               {read => 1, write => 1, type => 'varchar(100)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
         Creator => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
 
  }
 };
@@ -350,7 +429,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index 23a475a..deae6ae 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -64,7 +89,7 @@ sub _Init {
 }
 
 
-=item NewItem
+=head2 NewItem
 
 Returns an empty new RT::Transaction item
 
@@ -101,7 +126,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index cbc10f5..6919cba 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -61,7 +86,7 @@ sub _Init {
 
 
 
-=item Create PARAMHASH
+=head2 Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -170,7 +195,7 @@ sub Create {
 
 
 
-=item id
+=head2 id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -179,14 +204,14 @@ Returns the current value of id.
 =cut
 
 
-=item Name
+=head2 Name
 
 Returns the current value of Name. 
 (In the database, Name is stored as varchar(200).)
 
 
 
-=item SetName VALUE
+=head2 SetName VALUE
 
 
 Set Name to VALUE. 
@@ -197,14 +222,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Password
+=head2 Password
 
 Returns the current value of Password. 
 (In the database, Password is stored as varchar(40).)
 
 
 
-=item SetPassword VALUE
+=head2 SetPassword VALUE
 
 
 Set Password to VALUE. 
@@ -215,14 +240,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Comments
+=head2 Comments
 
 Returns the current value of Comments. 
 (In the database, Comments is stored as blob.)
 
 
 
-=item SetComments VALUE
+=head2 SetComments VALUE
 
 
 Set Comments to VALUE. 
@@ -233,14 +258,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Signature
+=head2 Signature
 
 Returns the current value of Signature. 
 (In the database, Signature is stored as blob.)
 
 
 
-=item SetSignature VALUE
+=head2 SetSignature VALUE
 
 
 Set Signature to VALUE. 
@@ -251,14 +276,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item EmailAddress
+=head2 EmailAddress
 
 Returns the current value of EmailAddress. 
 (In the database, EmailAddress is stored as varchar(120).)
 
 
 
-=item SetEmailAddress VALUE
+=head2 SetEmailAddress VALUE
 
 
 Set EmailAddress to VALUE. 
@@ -269,14 +294,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item FreeformContactInfo
+=head2 FreeformContactInfo
 
 Returns the current value of FreeformContactInfo. 
 (In the database, FreeformContactInfo is stored as blob.)
 
 
 
-=item SetFreeformContactInfo VALUE
+=head2 SetFreeformContactInfo VALUE
 
 
 Set FreeformContactInfo to VALUE. 
@@ -287,14 +312,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Organization
+=head2 Organization
 
 Returns the current value of Organization. 
 (In the database, Organization is stored as varchar(200).)
 
 
 
-=item SetOrganization VALUE
+=head2 SetOrganization VALUE
 
 
 Set Organization to VALUE. 
@@ -305,14 +330,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item RealName
+=head2 RealName
 
 Returns the current value of RealName. 
 (In the database, RealName is stored as varchar(120).)
 
 
 
-=item SetRealName VALUE
+=head2 SetRealName VALUE
 
 
 Set RealName to VALUE. 
@@ -323,14 +348,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item NickName
+=head2 NickName
 
 Returns the current value of NickName. 
 (In the database, NickName is stored as varchar(16).)
 
 
 
-=item SetNickName VALUE
+=head2 SetNickName VALUE
 
 
 Set NickName to VALUE. 
@@ -341,14 +366,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Lang
+=head2 Lang
 
 Returns the current value of Lang. 
 (In the database, Lang is stored as varchar(16).)
 
 
 
-=item SetLang VALUE
+=head2 SetLang VALUE
 
 
 Set Lang to VALUE. 
@@ -359,14 +384,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item EmailEncoding
+=head2 EmailEncoding
 
 Returns the current value of EmailEncoding. 
 (In the database, EmailEncoding is stored as varchar(16).)
 
 
 
-=item SetEmailEncoding VALUE
+=head2 SetEmailEncoding VALUE
 
 
 Set EmailEncoding to VALUE. 
@@ -377,14 +402,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item WebEncoding
+=head2 WebEncoding
 
 Returns the current value of WebEncoding. 
 (In the database, WebEncoding is stored as varchar(16).)
 
 
 
-=item SetWebEncoding VALUE
+=head2 SetWebEncoding VALUE
 
 
 Set WebEncoding to VALUE. 
@@ -395,14 +420,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item ExternalContactInfoId
+=head2 ExternalContactInfoId
 
 Returns the current value of ExternalContactInfoId. 
 (In the database, ExternalContactInfoId is stored as varchar(100).)
 
 
 
-=item SetExternalContactInfoId VALUE
+=head2 SetExternalContactInfoId VALUE
 
 
 Set ExternalContactInfoId to VALUE. 
@@ -413,14 +438,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item ContactInfoSystem
+=head2 ContactInfoSystem
 
 Returns the current value of ContactInfoSystem. 
 (In the database, ContactInfoSystem is stored as varchar(30).)
 
 
 
-=item SetContactInfoSystem VALUE
+=head2 SetContactInfoSystem VALUE
 
 
 Set ContactInfoSystem to VALUE. 
@@ -431,14 +456,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item ExternalAuthId
+=head2 ExternalAuthId
 
 Returns the current value of ExternalAuthId. 
 (In the database, ExternalAuthId is stored as varchar(100).)
 
 
 
-=item SetExternalAuthId VALUE
+=head2 SetExternalAuthId VALUE
 
 
 Set ExternalAuthId to VALUE. 
@@ -449,14 +474,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item AuthSystem
+=head2 AuthSystem
 
 Returns the current value of AuthSystem. 
 (In the database, AuthSystem is stored as varchar(30).)
 
 
 
-=item SetAuthSystem VALUE
+=head2 SetAuthSystem VALUE
 
 
 Set AuthSystem to VALUE. 
@@ -467,14 +492,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Gecos
+=head2 Gecos
 
 Returns the current value of Gecos. 
 (In the database, Gecos is stored as varchar(16).)
 
 
 
-=item SetGecos VALUE
+=head2 SetGecos VALUE
 
 
 Set Gecos to VALUE. 
@@ -485,14 +510,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item HomePhone
+=head2 HomePhone
 
 Returns the current value of HomePhone. 
 (In the database, HomePhone is stored as varchar(30).)
 
 
 
-=item SetHomePhone VALUE
+=head2 SetHomePhone VALUE
 
 
 Set HomePhone to VALUE. 
@@ -503,14 +528,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item WorkPhone
+=head2 WorkPhone
 
 Returns the current value of WorkPhone. 
 (In the database, WorkPhone is stored as varchar(30).)
 
 
 
-=item SetWorkPhone VALUE
+=head2 SetWorkPhone VALUE
 
 
 Set WorkPhone to VALUE. 
@@ -521,14 +546,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item MobilePhone
+=head2 MobilePhone
 
 Returns the current value of MobilePhone. 
 (In the database, MobilePhone is stored as varchar(30).)
 
 
 
-=item SetMobilePhone VALUE
+=head2 SetMobilePhone VALUE
 
 
 Set MobilePhone to VALUE. 
@@ -539,14 +564,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item PagerPhone
+=head2 PagerPhone
 
 Returns the current value of PagerPhone. 
 (In the database, PagerPhone is stored as varchar(30).)
 
 
 
-=item SetPagerPhone VALUE
+=head2 SetPagerPhone VALUE
 
 
 Set PagerPhone to VALUE. 
@@ -557,14 +582,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Address1
+=head2 Address1
 
 Returns the current value of Address1. 
 (In the database, Address1 is stored as varchar(200).)
 
 
 
-=item SetAddress1 VALUE
+=head2 SetAddress1 VALUE
 
 
 Set Address1 to VALUE. 
@@ -575,14 +600,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Address2
+=head2 Address2
 
 Returns the current value of Address2. 
 (In the database, Address2 is stored as varchar(200).)
 
 
 
-=item SetAddress2 VALUE
+=head2 SetAddress2 VALUE
 
 
 Set Address2 to VALUE. 
@@ -593,14 +618,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item City
+=head2 City
 
 Returns the current value of City. 
 (In the database, City is stored as varchar(100).)
 
 
 
-=item SetCity VALUE
+=head2 SetCity VALUE
 
 
 Set City to VALUE. 
@@ -611,14 +636,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item State
+=head2 State
 
 Returns the current value of State. 
 (In the database, State is stored as varchar(100).)
 
 
 
-=item SetState VALUE
+=head2 SetState VALUE
 
 
 Set State to VALUE. 
@@ -629,14 +654,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Zip
+=head2 Zip
 
 Returns the current value of Zip. 
 (In the database, Zip is stored as varchar(16).)
 
 
 
-=item SetZip VALUE
+=head2 SetZip VALUE
 
 
 Set Zip to VALUE. 
@@ -647,14 +672,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Country
+=head2 Country
 
 Returns the current value of Country. 
 (In the database, Country is stored as varchar(50).)
 
 
 
-=item SetCountry VALUE
+=head2 SetCountry VALUE
 
 
 Set Country to VALUE. 
@@ -665,14 +690,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Timezone
+=head2 Timezone
 
 Returns the current value of Timezone. 
 (In the database, Timezone is stored as varchar(50).)
 
 
 
-=item SetTimezone VALUE
+=head2 SetTimezone VALUE
 
 
 Set Timezone to VALUE. 
@@ -683,14 +708,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item PGPKey
+=head2 PGPKey
 
 Returns the current value of PGPKey. 
 (In the database, PGPKey is stored as text.)
 
 
 
-=item SetPGPKey VALUE
+=head2 SetPGPKey VALUE
 
 
 Set PGPKey to VALUE. 
@@ -701,7 +726,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=item Creator
+=head2 Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -710,7 +735,7 @@ Returns the current value of Creator.
 =cut
 
 
-=item Created
+=head2 Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -719,7 +744,7 @@ Returns the current value of Created.
 =cut
 
 
-=item LastUpdatedBy
+=head2 LastUpdatedBy
 
 Returns the current value of LastUpdatedBy. 
 (In the database, LastUpdatedBy is stored as int(11).)
@@ -728,7 +753,7 @@ Returns the current value of LastUpdatedBy.
 =cut
 
 
-=item LastUpdated
+=head2 LastUpdated
 
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
@@ -738,77 +763,77 @@ Returns the current value of LastUpdated.
 
 
 
-sub _ClassAccessible {
+sub _CoreAccessible {
     {
      
         id =>
-               {read => 1, type => 'int(11)', default => ''},
+               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
         Name => 
-               {read => 1, write => 1, type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
         Password => 
-               {read => 1, write => 1, type => 'varchar(40)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 40,  is_blob => 0,  is_numeric => 0,  type => 'varchar(40)', default => ''},
         Comments => 
-               {read => 1, write => 1, type => 'blob', default => ''},
+               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'blob', default => ''},
         Signature => 
-               {read => 1, write => 1, type => 'blob', default => ''},
+               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'blob', default => ''},
         EmailAddress => 
-               {read => 1, write => 1, type => 'varchar(120)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
         FreeformContactInfo => 
-               {read => 1, write => 1, type => 'blob', default => ''},
+               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'blob', default => ''},
         Organization => 
-               {read => 1, write => 1, type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
         RealName => 
-               {read => 1, write => 1, type => 'varchar(120)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
         NickName => 
-               {read => 1, write => 1, type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
         Lang => 
-               {read => 1, write => 1, type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
         EmailEncoding => 
-               {read => 1, write => 1, type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
         WebEncoding => 
-               {read => 1, write => 1, type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
         ExternalContactInfoId => 
-               {read => 1, write => 1, type => 'varchar(100)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 100,  is_blob => 0,  is_numeric => 0,  type => 'varchar(100)', default => ''},
         ContactInfoSystem => 
-               {read => 1, write => 1, type => 'varchar(30)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 30,  is_blob => 0,  is_numeric => 0,  type => 'varchar(30)', default => ''},
         ExternalAuthId => 
-               {read => 1, write => 1, type => 'varchar(100)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 100,  is_blob => 0,  is_numeric => 0,  type => 'varchar(100)', default => ''},
         AuthSystem => 
-               {read => 1, write => 1, type => 'varchar(30)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 30,  is_blob => 0,  is_numeric => 0,  type => 'varchar(30)', default => ''},
         Gecos => 
-               {read => 1, write => 1, type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
         HomePhone => 
-               {read => 1, write => 1, type => 'varchar(30)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 30,  is_blob => 0,  is_numeric => 0,  type => 'varchar(30)', default => ''},
         WorkPhone => 
-               {read => 1, write => 1, type => 'varchar(30)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 30,  is_blob => 0,  is_numeric => 0,  type => 'varchar(30)', default => ''},
         MobilePhone => 
-               {read => 1, write => 1, type => 'varchar(30)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 30,  is_blob => 0,  is_numeric => 0,  type => 'varchar(30)', default => ''},
         PagerPhone => 
-               {read => 1, write => 1, type => 'varchar(30)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 30,  is_blob => 0,  is_numeric => 0,  type => 'varchar(30)', default => ''},
         Address1 => 
-               {read => 1, write => 1, type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
         Address2 => 
-               {read => 1, write => 1, type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
         City => 
-               {read => 1, write => 1, type => 'varchar(100)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 100,  is_blob => 0,  is_numeric => 0,  type => 'varchar(100)', default => ''},
         State => 
-               {read => 1, write => 1, type => 'varchar(100)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 100,  is_blob => 0,  is_numeric => 0,  type => 'varchar(100)', default => ''},
         Zip => 
-               {read => 1, write => 1, type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
         Country => 
-               {read => 1, write => 1, type => 'varchar(50)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 50,  is_blob => 0,  is_numeric => 0,  type => 'varchar(50)', default => ''},
         Timezone => 
-               {read => 1, write => 1, type => 'varchar(50)', default => ''},
+               {read => 1, write => 1, sql_type => 12, length => 50,  is_blob => 0,  is_numeric => 0,  type => 'varchar(50)', default => ''},
         PGPKey => 
-               {read => 1, write => 1, type => 'text', default => ''},
+               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
         Creator => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
         LastUpdatedBy => 
-               {read => 1, auto => 1, type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
         LastUpdated => 
-               {read => 1, auto => 1, type => 'datetime', default => ''},
+               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
 
  }
 };
@@ -840,7 +865,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);
 
index d58f696..3a6eeff 100755 (executable)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
 # 
-# (Except where explictly superceded by other copyright notices)
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -64,7 +89,7 @@ sub _Init {
 }
 
 
-=item NewItem
+=head2 NewItem
 
 Returns an empty new RT::User item
 
@@ -101,7 +126,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+Each of these files should begin with the line 
 
    no warnings qw(redefine);