From: cvs2git Date: Fri, 30 Jul 2010 22:26:41 +0000 (+0000) Subject: This commit was manufactured by cvs2svn to create tag 'freeside_2_1_0'. X-Git-Tag: freeside_2_1_0 X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=7b125e587a4d1ee0aca692e23ea7897f671855ae;hp=6662dd7c5260d74d92df0437d1eeebcb2baa6fe4 This commit was manufactured by cvs2svn to create tag 'freeside_2_1_0'. --- diff --git a/ChangeLog b/ChangeLog index 1dc321e8a..1a98b4460 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,29 @@ +2010-05-25 05:42 ivan + + * httemplate/elements/dashboard-install_welcome.html: adding, + though unused + +2010-05-25 05:41 ivan + + * httemplate/misc/: rate-import.html, process/rate-import.html: + unfinished rate import + +2010-05-25 05:40 ivan + + * bin/explain-bill-query: adding + +2010-05-25 05:35 ivan + + * ChangeLog, debian/changelog: Updated for 2.1.0 + +2010-05-25 05:33 ivan + + * Makefile: fix the rel target + +2010-05-25 05:30 ivan + + * ChangeLog, rpm/freeside.spec, debian/changelog: Updated for 2.1.0 + 2010-05-25 05:16 ivan * Makefile: its time diff --git a/bin/explain-bill-query b/bin/explain-bill-query new file mode 100644 index 000000000..e3f69781b --- /dev/null +++ b/bin/explain-bill-query @@ -0,0 +1,34 @@ +#!/usr/bin/perl -w + +use strict; +use Getopt::Std; +use FS::UID qw(adminsuidsetup dbh); +use FS::Cron::bill qw(bill_where); + +my $user = 'fs_daily'; + +#&untaint_argv; #what it sounds like (eww) +use vars qw(%opt); +getopts("p:a:d:vl:sy:nmrk", \%opt); + +adminsuidsetup $user; + + #we're at now now (and later). + $opt{'time'} = $opt{'d'} ? str2time($opt{'d'}) : $^T; + $opt{'time'} += $opt{'y'} * 86400 if $opt{'y'}; + + $opt{'invoice_time'} = $opt{'n'} ? $^T : $opt{'time'}; + + +my $sql = 'EXPLAIN SELECT custnum FROM cust_main WHERE '. bill_where(%opt); + +my $sth = dbh->prepare($sql) or die dbh->errstr; + +$sth->execute or die $sth->errstr; + +while ( my $row = $sth->fetchrow_arrayref ) { + + print join(' / ', @$row ). "\n"; + +} + diff --git a/debian/changelog b/debian/changelog index 9b23fc883..b7087407e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,10 @@ freeside (2.1.0-1) UNRELEASED; urgency=low * New upstream release + * New upstream release + * New upstream release - -- Ivan Kohler Tue, 25 May 2010 05:30:42 -0700 + -- Ivan Kohler Tue, 25 May 2010 05:43:49 -0700 freeside (1.9.1-1) unstable; urgency=low diff --git a/httemplate/elements/dashboard-install_welcome.html b/httemplate/elements/dashboard-install_welcome.html new file mode 100644 index 000000000..c8e2ded1f --- /dev/null +++ b/httemplate/elements/dashboard-install_welcome.html @@ -0,0 +1,10 @@ +% if ( $which ) { +<% include("dashboard-install_welcome-$which.html") %> +% } +<%init> + +my $conf = new FS::Conf; + +my $which = $conf->config('dashboard-install_welcome'); + + diff --git a/httemplate/misc/process/rate-import.html b/httemplate/misc/process/rate-import.html new file mode 100644 index 000000000..2c641642c --- /dev/null +++ b/httemplate/misc/process/rate-import.html @@ -0,0 +1,9 @@ +<% $server->process %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Import'); + +my $server = new FS::UI::Web::JSRPC 'FS::rate::process_batch_import', $cgi; + + diff --git a/httemplate/misc/rate-import.html b/httemplate/misc/rate-import.html new file mode 100644 index 000000000..ae8ee695b --- /dev/null +++ b/httemplate/misc/rate-import.html @@ -0,0 +1,76 @@ +<% include("/elements/header.html",'Import Rate Plan') %> + +<% include( '/elements/form-file_upload.html', + 'name' => 'RateImportForm', + 'action ' => 'process/rate-import.html', + 'num_files' => 1, + 'fields' => [ 'ratename' ], + 'message' => 'Rate plan import successful', +# 'url' => $p."browse/rate_detail.cgi?ratenum=$ratenum", #XXX how? + ) +%> + +<% &ntable("#cccccc", 2) %> + + + Rate plan + + + + + + <% include( '/elements/file-upload.html', + 'field' => 'file', + 'label' => 'Filename', + ) + %> + + + + + + + + + + + +
+ + +File format is CSV (comma-separated value), with the following field order: +
    +
  • Destination name +
  • Country code / Prefix. See below for formatting rules. +
  • Rate (per minute) + +
+ +Formatting rules for second field: +
    +
  • Simple entries contain just a countrycode or a countrycode and single prefix for example, "61" or "52 33". Whitespace, plus and dash are ignored. +
  • Additional prefixes may be appended after a comma (appropriately quoted), but country code should only be listed once at the beginning. For example, "61 38,39". +
  • +
+ +Have caution when importing prefix data that is mismatched to your current +prefixes. + +<% include('/elements/footer.html') %> + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Import'); + + diff --git a/httemplate/view/svc_acct/tr.html b/httemplate/view/svc_acct/tr.html deleted file mode 100644 index e2ec7d42f..000000000 --- a/httemplate/view/svc_acct/tr.html +++ /dev/null @@ -1,9 +0,0 @@ - - <% $opt{'label'} %> - <% $opt{'value'} %> - -<%init> - -my %opt = @_; - - diff --git a/rt/README b/rt/README index 7c5e4d47a..899a5a02f 100755 --- a/rt/README +++ b/rt/README @@ -1,278 +1,369 @@ -# BEGIN LICENSE BLOCK -# -# Copyright (c) 1996-2003 Jesse Vincent -# -# (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- --with- + 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- --with- --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- --with- + 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: 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 + + SetHandler default + SetHandler perl-script PerlHandler RT::Mason + - # this section applies to Apache2+mod_perl2 only - - SetHandler perl-script - PerlHandler RT::Mason - - - SetHandler perl-script - PerlHandler RT::Mason - - +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: + + + 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" + + + SetHandler default + + SetHandler perl-script - PerlHandler RT::Mason - + PerlResponseHandler RT::Mason + +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 + + + 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/ + + + + +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/" - | | | - ----/ | | - | | - ---/ | - | - ---/ +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 +. 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 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. +. 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 . -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 +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: +# +# This work is made available to you under the terms of Version 2 of +# the GNU General Public License. A copy of that license should have +# been provided with this software, but in any event can be snarfed +# from www.gnu.org. +# +# This work is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +# +# +# CONTRIBUTION SUBMISSION POLICY: +# +# (The following paragraph is not intended to limit the rights granted +# to you to modify and distribute this software under the terms of +# the GNU General Public License and is only of importance to you if +# you choose to contribute your changes and enhancements to the +# community by submitting them to Best Practical Solutions, LLC.) +# +# By intentionally submitting any modifications, corrections or +# derivatives to this work, or any other work intended for use with +# Request Tracker, to Best Practical Solutions, LLC, you confirm that +# you are the copyright holder for those contributions and you grant +# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +# royalty-free, perpetual, license to use, copy, create derivative +# works based on those contributions, and sublicense and distribute +# those contributions and any derivatives thereof. +# +# END BPS TAGGED BLOCK }}} diff --git a/rt/bin/standalone_httpd b/rt/bin/standalone_httpd deleted file mode 100755 index 7b447050b..000000000 --- a/rt/bin/standalone_httpd +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/perl -w -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC -# -# -# (Except where explicitly superseded by other copyright notices) -# -# -# LICENSE: -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# -# CONTRIBUTION SUBMISSION POLICY: -# -# (The following paragraph is not intended to limit the rights granted -# to you to modify and distribute this software under the terms of -# the GNU General Public License and is only of importance to you if -# you choose to contribute your changes and enhancements to the -# community by submitting them to Best Practical Solutions, LLC.) -# -# By intentionally submitting any modifications, corrections or -# derivatives to this work, or any other work intended for use with -# Request Tracker, to Best Practical Solutions, LLC, you confirm that -# you are the copyright holder for those contributions and you grant -# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -# royalty-free, perpetual, license to use, copy, create derivative -# works based on those contributions, and sublicense and distribute -# those contributions and any derivatives thereof. -# -# END BPS TAGGED BLOCK }}} -use warnings; -use strict; - -# fix lib paths, some may be relative -BEGIN { - require File::Spec; - my @libs = ("lib", "local/lib"); - my $bin_path; - - for my $lib (@libs) { - unless ( File::Spec->file_name_is_absolute($lib) ) { - unless ($bin_path) { - if ( File::Spec->file_name_is_absolute(__FILE__) ) { - $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; - } - else { - require FindBin; - no warnings "once"; - $bin_path = $FindBin::Bin; - } - } - $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); - } - unshift @INC, $lib; - } - -} - -use RT; -RT::LoadConfig(); -RT->InitLogging(); -if (RT->Config->Get('DevelMode')) { require Module::Refresh; } - -RT::CheckPerlRequirements(); -RT->InitPluginPaths(); - -my $explicit_port = shift @ARGV; -my $port = $explicit_port || RT->Config->Get('WebPort') || '8080'; - - -require RT::Handle; -my ($integrity, $state, $msg) = RT::Handle->CheckIntegrity; - -unless ( $integrity ) { - print STDERR <ConfigFile && !-w _) { - die 'Since your configuration exists (' - . RT::Installer->ConfigFile - . ") but is not writable, I'm refusing to do anything.\n"; - } - - RT->Config->Set( 'LexiconLanguages' => '*' ); - RT::I18N->Init; - - RT->InstallMode(1); -} else { - RT->ConnectToDatabase(); - RT->InitSystemObjects(); - RT->InitClasses( Heavy => 1 ); - RT->InitPlugins(); - RT->Config->PostLoadCheck(); - - my ($status, $msg) = RT::Handle->CheckCompatibility( - $RT::Handle->dbh, 'post' - ); - unless ( $status ) { - print STDERR $msg, "\n\n"; - exit -1; - } -} - -require RT::Interface::Web::Standalone; -my $server = RT::Interface::Web::Standalone->new; -run_server($port); -exit 0; - -sub run_server { - my $port = shift; - $server->port($port); - eval { $server->run() }; - - if ( my $err = $@ ) { - handle_startup_error($err); - } -} - -sub handle_startup_error { - my $err = shift; - if ( $err =~ /bind: Permission denied/ ) { - handle_bind_error(); - } else { - die - "Something went wrong while trying to run RT's standalone web server:\n\t" - . $err; - } -} - - -sub handle_bind_error { - - print STDERR <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; diff --git a/rt/etc/acl.mysql b/rt/etc/acl.mysql index 0ecaa3b15..0982ca228 100755 --- a/rt/etc/acl.mysql +++ b/rt/etc/acl.mysql @@ -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; diff --git a/rt/lib/RT/ACE.pm b/rt/lib/RT/ACE.pm index 1501a125e..7f21ba05e 100755 --- a/rt/lib/RT/ACE.pm +++ b/rt/lib/RT/ACE.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: # -# (Except where explictly superceded by other copyright notices) +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,13 +20,32 @@ # 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 ) # 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); diff --git a/rt/lib/RT/ACL.pm b/rt/lib/RT/ACL.pm index 81f59c6d0..1dc66e8b6 100755 --- a/rt/lib/RT/ACL.pm +++ b/rt/lib/RT/ACL.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: # -# (Except where explictly superceded by other copyright notices) +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,13 +20,32 @@ # 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 ) # 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); diff --git a/rt/lib/RT/Action/Autoreply.pm b/rt/lib/RT/Action/Autoreply.pm index 81f7bddfa..3734d819a 100755 --- a/rt/lib/RT/Action/Autoreply.pm +++ b/rt/lib/RT/Action/Autoreply.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: # -# (Except where explictly superceded by other copyright notices) +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,20 +20,51 @@ # 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 header to C, 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"; diff --git a/rt/lib/RT/Action/Generic.pm b/rt/lib/RT/Action/Generic.pm index 007d299c7..5e8ef32ce 100755 --- a/rt/lib/RT/Action/Generic.pm +++ b/rt/lib/RT/Action/Generic.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: # -# (Except where explictly superceded by other copyright notices) +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,16 +20,35 @@ # 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 @@ -31,165 +56,25 @@ =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; + diff --git a/rt/lib/RT/Action/Notify.pm b/rt/lib/RT/Action/Notify.pm index 1e4e4c073..3b782f34b 100755 --- a/rt/lib/RT/Action/Notify.pm +++ b/rt/lib/RT/Action/Notify.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: # -# (Except where explictly superceded by other copyright notices) +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,21 +20,54 @@ # 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 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}); diff --git a/rt/lib/RT/Action/NotifyAsComment.pm b/rt/lib/RT/Action/NotifyAsComment.pm index 210e4ab15..4380c86db 100755 --- a/rt/lib/RT/Action/NotifyAsComment.pm +++ b/rt/lib/RT/Action/NotifyAsComment.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: # -# (Except where explictly superceded by other copyright notices) +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,36 +20,54 @@ # 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; - diff --git a/rt/lib/RT/Action/ResolveMembers.pm b/rt/lib/RT/Action/ResolveMembers.pm index 02ff3a58c..ff826ccc1 100644 --- a/rt/lib/RT/Action/ResolveMembers.pm +++ b/rt/lib/RT/Action/ResolveMembers.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: # -# (Except where explictly superceded by other copyright notices) +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,22 +20,39 @@ # 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. diff --git a/rt/lib/RT/Action/SendEmail.pm b/rt/lib/RT/Action/SendEmail.pm index dac8fc8e7..8b682c118 100755 --- a/rt/lib/RT/Action/SendEmail.pm +++ b/rt/lib/RT/Action/SendEmail.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: # -# (Except where explictly superceded by other copyright notices) +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,25 +20,46 @@ # 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 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 or L. -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 and Tobias Brox +=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 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 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 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 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 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 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', - "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 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', - "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 + = '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. -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; diff --git a/rt/lib/RT/Attachment.pm b/rt/lib/RT/Attachment.pm index 2ed520162..4327238e6 100755 --- a/rt/lib/RT/Attachment.pm +++ b/rt/lib/RT/Attachment.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: # -# (Except where explictly superceded by other copyright notices) +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,13 +20,32 @@ # 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 ) # 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); diff --git a/rt/lib/RT/Attachments.pm b/rt/lib/RT/Attachments.pm index 177cdd094..416cde6ba 100755 --- a/rt/lib/RT/Attachments.pm +++ b/rt/lib/RT/Attachments.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: # -# (Except where explictly superceded by other copyright notices) +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,13 +20,32 @@ # 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 ) # 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); diff --git a/rt/lib/RT/Condition/AnyTransaction.pm b/rt/lib/RT/Condition/AnyTransaction.pm index 4519fcf5a..1b90aa53e 100644 --- a/rt/lib/RT/Condition/AnyTransaction.pm +++ b/rt/lib/RT/Condition/AnyTransaction.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: # -# (Except where explictly superceded by other copyright notices) +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,21 +20,36 @@ # 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 diff --git a/rt/lib/RT/Condition/Generic.pm b/rt/lib/RT/Condition/Generic.pm index bd269315e..08baeda25 100755 --- a/rt/lib/RT/Condition/Generic.pm +++ b/rt/lib/RT/Condition/Generic.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: # -# (Except where explictly superceded by other copyright notices) +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,198 +20,61 @@ # 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; + diff --git a/rt/lib/RT/Condition/StatusChange.pm b/rt/lib/RT/Condition/StatusChange.pm index 8afabcda0..285b71da6 100644 --- a/rt/lib/RT/Condition/StatusChange.pm +++ b/rt/lib/RT/Condition/StatusChange.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: # -# (Except where explictly superceded by other copyright notices) +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,22 +20,35 @@ # 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 diff --git a/rt/lib/RT/CurrentUser.pm b/rt/lib/RT/CurrentUser.pm index 4ca2f9891..b674d4e60 100755 --- a/rt/lib/RT/CurrentUser.pm +++ b/rt/lib/RT/CurrentUser.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: # -# (Except where explictly superceded by other copyright notices) +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,181 +20,164 @@ # 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 subclass of L 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 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 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, 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 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; - diff --git a/rt/lib/RT/Date.pm b/rt/lib/RT/Date.pm index 355370ada..fc4c43ce4 100644 --- a/rt/lib/RT/Date.pm +++ b/rt/lib/RT/Date.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: # -# (Except where explictly superceded by other copyright notices) +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,13 +20,32 @@ # 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 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, C and C. -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, C or C. See also L. -# {{{ 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 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 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 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 if the latter is not specified. See config option +C. =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 method. Arguments C and