[Rt-commit] [svn] r1201 - in rt/branches/rt-3.1: . bin etc etc/upgrade/3.1.0 etc/upgrade/3.3.0 html html/Admin html/Admin/CustomFields html/Admin/Elements html/Admin/Global html/Admin/Groups html/Admin/Queues html/Admin/Users html/Download html/Download/CustomFieldValue html/Download/Tabular html/Elements html/NoAuth html/Search html/Search/Elements html/SelfService html/Ticket html/Ticket/Elements html/User lib lib/RT lib/RT/Action lib/RT/I18N lib/RT/Interface sbin spec

jesse at pallas.eruditorum.org jesse at pallas.eruditorum.org
Sat Jul 10 02:05:29 EDT 2004


Author: jesse
Date: Sat Jul 10 02:05:15 2004
New Revision: 1201

Added:
   rt/branches/rt-3.1/etc/upgrade/3.3.0/
   rt/branches/rt-3.1/etc/upgrade/3.3.0/acl.Informix
   rt/branches/rt-3.1/etc/upgrade/3.3.0/acl.Oracle
   rt/branches/rt-3.1/etc/upgrade/3.3.0/acl.Pg
   rt/branches/rt-3.1/etc/upgrade/3.3.0/acl.SQLite
   rt/branches/rt-3.1/etc/upgrade/3.3.0/acl.mysql
   rt/branches/rt-3.1/etc/upgrade/3.3.0/schema.Pg
   rt/branches/rt-3.1/etc/upgrade/3.3.0/schema.mysql
   rt/branches/rt-3.1/html/Admin/CustomFields/
   rt/branches/rt-3.1/html/Admin/CustomFields/GroupRights.html
   rt/branches/rt-3.1/html/Admin/CustomFields/Modify.html
   rt/branches/rt-3.1/html/Admin/CustomFields/Objects.html
   rt/branches/rt-3.1/html/Admin/CustomFields/UserRights.html
   rt/branches/rt-3.1/html/Admin/CustomFields/index.html
   rt/branches/rt-3.1/html/Admin/Elements/CustomFieldTabs
   rt/branches/rt-3.1/html/Admin/Elements/ObjectCustomFields
   rt/branches/rt-3.1/html/Admin/Elements/PickCustomFields
   rt/branches/rt-3.1/html/Admin/Elements/PickObjects
   rt/branches/rt-3.1/html/Admin/Elements/SelectCustomFieldLookupType
   rt/branches/rt-3.1/html/Admin/Groups/CustomFields.html
   rt/branches/rt-3.1/html/Admin/Groups/History.html
   rt/branches/rt-3.1/html/Admin/Users/CustomFields.html
   rt/branches/rt-3.1/html/Admin/Users/History.html
   rt/branches/rt-3.1/html/Admin/Users/Memberships.html
   rt/branches/rt-3.1/html/Download/
   rt/branches/rt-3.1/html/Download/CustomFieldValue/
   rt/branches/rt-3.1/html/Download/CustomFieldValue/dhandler
   rt/branches/rt-3.1/html/Download/Tabular/
   rt/branches/rt-3.1/html/Download/Tabular/dhandler
   rt/branches/rt-3.1/html/Elements/EditCustomField
   rt/branches/rt-3.1/html/Elements/EditCustomFieldBinary
   rt/branches/rt-3.1/html/Elements/EditCustomFieldFreeform
   rt/branches/rt-3.1/html/Elements/EditCustomFieldImage
   rt/branches/rt-3.1/html/Elements/EditCustomFieldSelect
   rt/branches/rt-3.1/html/Elements/EditCustomFieldText
   rt/branches/rt-3.1/html/Elements/ShowCustomFieldImage
   rt/branches/rt-3.1/html/Elements/ShowCustomFields
   rt/branches/rt-3.1/html/Elements/ShowMemberships
   rt/branches/rt-3.1/html/Search/Elements/SelectGroup
   rt/branches/rt-3.1/lib/RT/ObjectCustomField.pm
   rt/branches/rt-3.1/lib/RT/ObjectCustomFieldValue.pm
   rt/branches/rt-3.1/lib/RT/ObjectCustomFieldValue_Overlay.pm
   rt/branches/rt-3.1/lib/RT/ObjectCustomFieldValues.pm
   rt/branches/rt-3.1/lib/RT/ObjectCustomFieldValues_Overlay.pm
   rt/branches/rt-3.1/lib/RT/ObjectCustomField_Overlay.pm
   rt/branches/rt-3.1/lib/RT/ObjectCustomFields.pm
   rt/branches/rt-3.1/lib/RT/ObjectCustomFields_Overlay.pm
   rt/branches/rt-3.1/sbin/rt-dump-database.in
   rt/branches/rt-3.1/spec/
   rt/branches/rt-3.1/spec/schema.txt
Removed:
   rt/branches/rt-3.1/html/Admin/Global/CustomField.html
   rt/branches/rt-3.1/html/Admin/Global/CustomFields.html
Modified:
   rt/branches/rt-3.1/   (props changed)
   rt/branches/rt-3.1/README
   rt/branches/rt-3.1/bin/standalone_httpd.in
   rt/branches/rt-3.1/bin/webmux.pl.in
   rt/branches/rt-3.1/configure.ac
   rt/branches/rt-3.1/etc/RT_Config.pm.in
   rt/branches/rt-3.1/etc/acl.Pg
   rt/branches/rt-3.1/etc/acl.mysql
   rt/branches/rt-3.1/etc/schema.Informix
   rt/branches/rt-3.1/etc/schema.Oracle
   rt/branches/rt-3.1/etc/schema.Pg
   rt/branches/rt-3.1/etc/schema.SQLite
   rt/branches/rt-3.1/etc/schema.Sybase
   rt/branches/rt-3.1/etc/schema.mysql
   rt/branches/rt-3.1/etc/upgrade/3.1.0/acl.Oracle   (props changed)
   rt/branches/rt-3.1/etc/upgrade/3.1.0/acl.Pg   (props changed)
   rt/branches/rt-3.1/etc/upgrade/3.1.0/acl.SQLite   (props changed)
   rt/branches/rt-3.1/etc/upgrade/3.1.0/acl.mysql   (props changed)
   rt/branches/rt-3.1/etc/upgrade/3.1.0/schema.Pg   (props changed)
   rt/branches/rt-3.1/etc/upgrade/3.1.0/schema.mysql   (props changed)
   rt/branches/rt-3.1/html/Admin/Elements/AddCustomFieldValue
   rt/branches/rt-3.1/html/Admin/Elements/EditCustomFieldValues
   rt/branches/rt-3.1/html/Admin/Elements/EditCustomFields
   rt/branches/rt-3.1/html/Admin/Elements/EditScrip
   rt/branches/rt-3.1/html/Admin/Elements/GroupTabs
   rt/branches/rt-3.1/html/Admin/Elements/Header
   rt/branches/rt-3.1/html/Admin/Elements/QueueTabs
   rt/branches/rt-3.1/html/Admin/Elements/SelectCustomFieldType
   rt/branches/rt-3.1/html/Admin/Elements/SelectRights
   rt/branches/rt-3.1/html/Admin/Elements/SystemTabs
   rt/branches/rt-3.1/html/Admin/Elements/Tabs
   rt/branches/rt-3.1/html/Admin/Elements/UserTabs
   rt/branches/rt-3.1/html/Admin/Global/Templates.html
   rt/branches/rt-3.1/html/Admin/Global/index.html
   rt/branches/rt-3.1/html/Admin/Groups/Modify.html
   rt/branches/rt-3.1/html/Admin/Groups/index.html
   rt/branches/rt-3.1/html/Admin/Queues/CustomField.html
   rt/branches/rt-3.1/html/Admin/Queues/CustomFields.html
   rt/branches/rt-3.1/html/Admin/Queues/Modify.html
   rt/branches/rt-3.1/html/Admin/Queues/People.html
   rt/branches/rt-3.1/html/Admin/Users/Modify.html
   rt/branches/rt-3.1/html/Admin/Users/index.html
   rt/branches/rt-3.1/html/Admin/index.html
   rt/branches/rt-3.1/html/Elements/Header
   rt/branches/rt-3.1/html/Elements/SelectGroups
   rt/branches/rt-3.1/html/Elements/SelectUsers
   rt/branches/rt-3.1/html/Elements/SetupSessionCookie
   rt/branches/rt-3.1/html/Elements/Submit
   rt/branches/rt-3.1/html/NoAuth/webrt.css
   rt/branches/rt-3.1/html/Search/Bulk.html
   rt/branches/rt-3.1/html/Search/Elements/PickBasics
   rt/branches/rt-3.1/html/Search/Elements/PickCFs
   rt/branches/rt-3.1/html/Search/Elements/SelectPersonType
   rt/branches/rt-3.1/html/SelfService/Closed.html
   rt/branches/rt-3.1/html/SelfService/Display.html
   rt/branches/rt-3.1/html/Ticket/Create.html
   rt/branches/rt-3.1/html/Ticket/Elements/EditCustomFields
   rt/branches/rt-3.1/html/Ticket/Elements/EditPeople
   rt/branches/rt-3.1/html/Ticket/Elements/FindAttachments   (contents, props changed)
   rt/branches/rt-3.1/html/Ticket/Elements/LoadTextAttachments
   rt/branches/rt-3.1/html/Ticket/Elements/ShowCustomFields
   rt/branches/rt-3.1/html/Ticket/Elements/ShowRequestor
   rt/branches/rt-3.1/html/Ticket/Elements/ShowTransaction
   rt/branches/rt-3.1/html/Ticket/Modify.html
   rt/branches/rt-3.1/html/Ticket/ModifyAll.html
   rt/branches/rt-3.1/html/Ticket/Update.html
   rt/branches/rt-3.1/html/User/Prefs.html
   rt/branches/rt-3.1/html/autohandler
   rt/branches/rt-3.1/lib/RT.pm.in
   rt/branches/rt-3.1/lib/RT/Action/CreateTickets.pm
   rt/branches/rt-3.1/lib/RT/Attachment_Overlay.pm
   rt/branches/rt-3.1/lib/RT/Base.pm
   rt/branches/rt-3.1/lib/RT/CurrentUser.pm
   rt/branches/rt-3.1/lib/RT/CustomField.pm
   rt/branches/rt-3.1/lib/RT/CustomField_Overlay.pm
   rt/branches/rt-3.1/lib/RT/CustomFields_Overlay.pm
   rt/branches/rt-3.1/lib/RT/Group_Overlay.pm
   rt/branches/rt-3.1/lib/RT/Groups_Overlay.pm
   rt/branches/rt-3.1/lib/RT/I18N.pm
   rt/branches/rt-3.1/lib/RT/I18N/cs.po
   rt/branches/rt-3.1/lib/RT/I18N/de.po
   rt/branches/rt-3.1/lib/RT/I18N/en.po
   rt/branches/rt-3.1/lib/RT/I18N/es.po
   rt/branches/rt-3.1/lib/RT/I18N/fr.po
   rt/branches/rt-3.1/lib/RT/I18N/he.po
   rt/branches/rt-3.1/lib/RT/I18N/it.po
   rt/branches/rt-3.1/lib/RT/I18N/ja.po
   rt/branches/rt-3.1/lib/RT/I18N/nl.po
   rt/branches/rt-3.1/lib/RT/I18N/pt_br.po
   rt/branches/rt-3.1/lib/RT/I18N/ru.po
   rt/branches/rt-3.1/lib/RT/Interface/Web.pm
   rt/branches/rt-3.1/lib/RT/Link_Overlay.pm
   rt/branches/rt-3.1/lib/RT/Queue_Overlay.pm
   rt/branches/rt-3.1/lib/RT/Record.pm
   rt/branches/rt-3.1/lib/RT/SearchBuilder.pm
   rt/branches/rt-3.1/lib/RT/StyleGuide.pod
   rt/branches/rt-3.1/lib/RT/System.pm
   rt/branches/rt-3.1/lib/RT/Template_Overlay.pm
   rt/branches/rt-3.1/lib/RT/Ticket_Overlay.pm
   rt/branches/rt-3.1/lib/RT/Tickets_Overlay.pm
   rt/branches/rt-3.1/lib/RT/Tickets_Overlay_SQL.pm
   rt/branches/rt-3.1/lib/RT/Transaction.pm
   rt/branches/rt-3.1/lib/RT/Transaction_Overlay.pm
   rt/branches/rt-3.1/lib/RT/Transactions_Overlay.pm
   rt/branches/rt-3.1/lib/RT/User_Overlay.pm
   rt/branches/rt-3.1/releng.cnf
   rt/branches/rt-3.1/sbin/extract-message-catalog
Log:
 ----------------------------------------------------------------------
 r2112 at tinbook:  jesse | 2004-07-10T04:32:28.577645Z
 
  ----------------------------------------------------------------------
  r1996 at tinbook:  jesse | 2004-06-28T03:08:01.495343Z
  
   ----------------------------------------------------------------------
   r1769 at tinbook:  jesse | 2004-06-01T20:22:38.640731Z
   
   
   ----------------------------------------------------------------------
   r1902 at tinbook:  jesse | 2004-06-17T01:08:53.996154Z
   
   
   ----------------------------------------------------------------------
   r1991 at tinbook:  jesse | 2004-06-28T02:03:57.669851Z
   
   
   ----------------------------------------------------------------------
   r1992 at tinbook:  jesse | 2004-06-28T02:19:36.386707Z
   
   Pulling forward from RT 3.1
   
   
   ----------------------------------------------------------------------
   r1993 at tinbook:  jesse | 2004-06-28T03:06:56.930396Z
   
   Merging forward from 3.1
   ----------------------------------------------------------------------
  
  ----------------------------------------------------------------------
  r1998 at tinbook:  jesse | 2004-06-28T03:10:54.232175Z
  
   ----------------------------------------------------------------------
   r1997 at tinbook:  jesse | 2004-06-28T03:10:36.434329Z
   
   Bumping to 3.3.4
   ----------------------------------------------------------------------
  
  ----------------------------------------------------------------------
  r2058 at tinbook:  autrijus | 2004-07-05T16:56:22.344051Z
  
   ----------------------------------------------------------------------
   r5899 at not:  autrijus | 2004-07-05T15:13:55.850880Z
   
   * correct the documentation for CreateTickets; it was still referring
     to the archaic "Approvals" queue and "Approval" type, whereas now we're
     using "___Approvals" and "approval" now.
   ----------------------------------------------------------------------
  
  ----------------------------------------------------------------------
  r2059 at tinbook:  autrijus | 2004-07-05T19:43:17.347835Z
  
   ----------------------------------------------------------------------
   r5913 at not:  autrijus | 2004-07-05T19:41:37.720057Z
   
   * add rt-dump-database, a tool to dump config data to initialdata format.
   ----------------------------------------------------------------------
  
  ----------------------------------------------------------------------
 
 ----------------------------------------------------------------------
 r2113 at tinbook:  jesse | 2004-07-10T04:40:37.142269Z
 
  ----------------------------------------------------------------------
  r2012 at tinbook:  jesse | 2004-06-28T21:51:20.545520Z
  
  Fixed bugs in CreateTickets (Approvals) system, both preexisting and introduced in 3.2.0rc3
  ----------------------------------------------------------------------
  r2014 at tinbook:  jesse | 2004-06-29T02:46:58.362406Z
  
  
  ----------------------------------------------------------------------
  r2015 at tinbook:  jesse | 2004-06-29T02:47:32.464100Z
  
  3.2.0rc4
  ----------------------------------------------------------------------
  r2022 at tinbook:  jesse | 2004-06-29T17:50:48.925379Z
  
  
  ----------------------------------------------------------------------
  r2023 at tinbook:  jesse | 2004-06-29T17:55:34.408001Z
  
  RT-Ticket: 5787
  RT-Status: resolved
  RT-UpdateType: correspond
  
  Added ticket absolute urls for the various actions one can take on searches.
  
  ----------------------------------------------------------------------
  r2030 at tinbook:  jesse | 2004-06-30T04:36:01.943950Z
  
  Bumping to 3.2.0
  ----------------------------------------------------------------------
  r2067 at tinbook:  jesse | 2004-07-06T20:25:17.528316Z
  
  
  ----------------------------------------------------------------------
  r2068 at tinbook:  jesse | 2004-07-06T20:59:43.336929Z
  
  Explicit 'Bookmark this search' functionality has been added back to RT 3.2
  
  ----------------------------------------------------------------------
  r2070 at tinbook:  jesse | 2004-07-06T21:13:17.298878Z
  
  RT-Ticket: 5815
  RT-Status: resolved
  RT-Update-Type: correspond
  
  RT now advises users about how to set the DBA and DBA Password when upgrading
  
  ----------------------------------------------------------------------
  r2072 at tinbook:  jesse | 2004-07-06T22:04:15.878370Z
  
   * "Negative" Ticket searches (for all tickets which don't have a
   certain custom field value) now find tickets which have _no_ custom
   field value for that ticket.
  
   * Custom Field searches should now work better with non-ascii
   custom field names. (Needs user verification)
  ----------------------------------------------------------------------
  r2095 at tinbook:  jesse | 2004-07-09T17:06:56.744260Z
  
  
  ----------------------------------------------------------------------
  r2096 at tinbook:  jesse | 2004-07-09T17:55:13.811842Z
  
  RT-Ticket: 5800
  RT-Status: resolved
  
  RT now stores sent messages in the database as utf-8.
  
  ----------------------------------------------------------------------
  r2098 at tinbook:  jesse | 2004-07-09T19:19:39.944163Z
  
  Fixups to Attributes to work with lack of access, rather than just falling over.
  
  ----------------------------------------------------------------------
  r2099 at tinbook:  jesse | 2004-07-09T19:21:06.127719Z
  
  By default, use verbose mode for reporting missing modules.
  ----------------------------------------------------------------------
  r2100 at tinbook:  jesse | 2004-07-09T19:25:51.061318Z
  
  Added a note about the fact that you'll be happier if you blow away your old RT
  instance.
  ----------------------------------------------------------------------
  r2101 at tinbook:  jesse | 2004-07-09T19:27:27.455452Z
  
  Fixing tests to conform to linda"s fixed code
  ----------------------------------------------------------------------
  r2102 at tinbook:  jesse | 2004-07-09T19:29:51.316623Z
  
  (Merging 3.0 to 3.2)
  
   ----------------------------------------------------------------------
   r2081 at tinbook:  huberth | 2004-07-08T16:28:17.266000Z
   
   RT-Ticket: 5773
   RT-Status: resolved
   
    * DBIx::SearchBuilder dependency bumped from 0.97 to 1.01.
   ----------------------------------------------------------------------
   r2089 at tinbook:  alexmv | 2004-07-08T19:40:10.002177Z
   
    * @ARGV _is_ allowed to be empty; it is $search which must not be.
      Thanks to jdwitt at the domain of software.umn.edu for the catch.
   
   ----------------------------------------------------------------------
  
  ----------------------------------------------------------------------
  r2103 at tinbook:  jesse | 2004-07-09T19:39:27.276636Z
  
  Updated French translation from sebastien.person at easter-eggs.com 
  ----------------------------------------------------------------------
  r2105 at tinbook:  jesse | 2004-07-09T20:58:36.771281Z
  
  Made recording outgoing email optional.
  Stopped recording attachments multiple times.
  
  
  ----------------------------------------------------------------------
  r2106 at tinbook:  jesse | 2004-07-09T21:00:30.401132Z
  
  
  ----------------------------------------------------------------------
  r2107 at tinbook:  jesse | 2004-07-09T21:00:41.498447Z
  
  Removing debugging output
  ----------------------------------------------------------------------
  r2110 at tinbook:  jesse | 2004-07-10T04:20:46.424456Z
  
  Message catalogs updated
  ----------------------------------------------------------------------
 
 ----------------------------------------------------------------------
 r2114 at tinbook:  jesse | 2004-07-10T06:03:00.790969Z
 
 Custom field searching now works again on 3.3
 ----------------------------------------------------------------------


Modified: rt/branches/rt-3.1/README
==============================================================================
--- rt/branches/rt-3.1/README	(original)
+++ rt/branches/rt-3.1/README	Sat Jul 10 02:05:15 2004
@@ -197,12 +197,8 @@
     DocumentRoot /opt/rt3/share/html
     AddDefaultCharset UTF-8
 
-    # these four lines apply to Apache2+mod_perl2 only: {{{
-    PerlSetVar MasonArgsMethod CGI
-    PerlModule Apache2 Apache::compat
-    RewriteEngine On
-    RewriteRule ^(.*)/$ $1/index.html
-    # }}}
+    # This line applies to Apache2+mod_perl2 only:
+    RedirectMatch permanent (.*)/$ $1/index.html
 
     PerlModule Apache::DBI
     PerlRequire /opt/rt3/bin/webmux.pl

Modified: rt/branches/rt-3.1/bin/standalone_httpd.in
==============================================================================
--- rt/branches/rt-3.1/bin/standalone_httpd.in	(original)
+++ rt/branches/rt-3.1/bin/standalone_httpd.in	Sat Jul 10 02:05:15 2004
@@ -8,6 +8,9 @@
 
 use Socket;
 
+$SIG{CHLD} = "IGNORE"; # reap child processes
+
+
 RT::Init();
 
 my $port = shift || '8080';
@@ -32,6 +35,8 @@
 
         for ( ; accept( Remote, HTTPDaemon ); close Remote ) {
 
+	    fork and next;
+
             *STDIN  = *Remote;
             *STDOUT = *Remote;
 
@@ -85,7 +90,7 @@
                 $RT::Handle->ForceRollback;
                 $RT::Logger->crit( "Transaction not committed. Usually indicates a software fault. Data loss may have occurred");
             }
-
+	    exit(); # our forked process
         }
 
     }

Modified: rt/branches/rt-3.1/bin/webmux.pl.in
==============================================================================
--- rt/branches/rt-3.1/bin/webmux.pl.in	(original)
+++ rt/branches/rt-3.1/bin/webmux.pl.in	Sat Jul 10 02:05:15 2004
@@ -1,26 +1,26 @@
 #!@PERL@
 # BEGIN LICENSE BLOCK
-#
+# 
 # Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
-#
+# 
 # (Except where explictly superceded by other copyright notices)
-#
+# 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # been provided with this software, but in any event can be snarfed
 # from www.gnu.org.
-#
+# 
 # This work is distributed in the hope that it will be useful, but
 # WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
-#
+# 
 # Unless otherwise specified, all modifications, corrections or
 # extensions to this work which alter its source code become the
 # property of Best Practical Solutions, LLC when submitted for
 # inclusion in the work.
-#
-#
+# 
+# 
 # END LICENSE BLOCK
 
 use strict;
@@ -32,10 +32,9 @@
     $ENV{'ENV'}    = '' if defined $ENV{'ENV'};
     $ENV{'IFS'}    = '' if defined $ENV{'IFS'};
 
-    eval { require Apache2; require Apache::compat }
-      if $ENV{'MOD_PERL'}
-      and $ENV{'MOD_PERL'} =~ m{mod_perl/(?:2|1\.9)};
-
+    eval { require Apache2; require APR::Table; require MasonX::Apache2Handler; 1 } or
+    eval { require Apache2; require Apache::compat; 1 } or die $@
+        if $ENV{'MOD_PERL'} and $ENV{'MOD_PERL'} =~ m{mod_perl/(?:2|1\.9)};
 }
 
 use lib ( "@LOCAL_LIB_PATH@", "@RT_LIB_PATH@" );
@@ -47,23 +46,29 @@
                                    #set private_tempfiles
 
 BEGIN {
-    if ( $mod_perl::VERSION && ( $mod_perl::VERSION >= 1.9908 ) ) {
-        require Apache::RequestUtil;
-        no warnings 'redefine';
-        my $sub = *Apache::request{CODE};
-        *Apache::request = sub {
-            my $r;
-            eval { $r = $sub->('Apache'); };
-
-            # warn $@ if $@;
-            return $r;
-        };
+    if ($MasonX::Apache2Handler::VERSION) {
+        $RT::MasonHandlerClass = 'MasonX::Apache2Handler';
     }
-    if ($CGI::MOD_PERL) {
-        require HTML::Mason::ApacheHandler;
+    elsif ($mod_perl::VERSION && ($mod_perl::VERSION >= 1.9908)) {
+	require Apache::RequestUtil;
+	no warnings 'redefine';
+	my $sub = *Apache::request{CODE};
+	*Apache::request = sub {
+	    my $r;
+	    eval { $r = $sub->('Apache'); };
+	    # warn $@ if $@;
+	    return $r;
+	};
+	require HTML::Mason::ApacheHandler;
+        $RT::MasonHandlerClass = 'HTML::Mason::ApacheHandler';
+    }
+    elsif ($CGI::MOD_PERL) {
+	require HTML::Mason::ApacheHandler;
+        $RT::MasonHandlerClass = 'HTML::Mason::ApacheHandler';
     }
     else {
-        require HTML::Mason::CGIHandler;
+	require HTML::Mason::CGIHandler;
+        $RT::MasonHandlerClass = 'HTML::Mason::CGIHandler';
     }
 }
 
@@ -81,21 +86,6 @@
     package HTML::Mason::Commands;
     use vars qw(%session);
 
-    use RT::Tickets;
-    use RT::Transactions;
-    use RT::Users;
-    use RT::CurrentUser;
-    use RT::Templates;
-    use RT::Queues;
-    use RT::ScripActions;
-    use RT::ScripConditions;
-    use RT::Scrips;
-    use RT::Groups;
-    use RT::GroupMembers;
-    use RT::CustomFields;
-    use RT::CustomFieldValues;
-    use RT::TicketCustomFieldValues;
-
     use RT::Interface::Web;
     use MIME::Entity;
     use Text::Wrapper;

Modified: rt/branches/rt-3.1/configure.ac
==============================================================================
--- rt/branches/rt-3.1/configure.ac	(original)
+++ rt/branches/rt-3.1/configure.ac	Sat Jul 10 02:05:15 2004
@@ -7,7 +7,7 @@
 
 dnl Setup autoconf
 AC_PREREQ(2.53)
-AC_INIT(RT, [3.2.HEAD], [rt-bugs at fsck.com])
+AC_INIT(RT, [3.3.HEAD], [rt-3.1-bugs at fsck.com])
 AC_CONFIG_SRCDIR([lib/RT.pm.in])
 
 dnl Extract RT version number components

Modified: rt/branches/rt-3.1/etc/RT_Config.pm.in
==============================================================================
--- rt/branches/rt-3.1/etc/RT_Config.pm.in	(original)
+++ rt/branches/rt-3.1/etc/RT_Config.pm.in	Sat Jul 10 02:05:15 2004
@@ -362,8 +362,9 @@
 
 # @MasonParameters is the list of parameters for the constructor of
 # HTML::Mason's Apache or CGI Handler.  This is normally only useful
-# for debugging, eg. profiling individual components with
-#     (preamble => 'my $p = MasonX::Profiler->new($m, $r);');
+# for debugging, eg. profiling individual components with:
+#     use MasonX::Profiler; # available on CPAN
+#     @MasonParameters = (preamble => 'my $p = MasonX::Profiler->new($m, $r);');
 
 @MasonParameters = () unless (@MasonParameters);
 

Modified: rt/branches/rt-3.1/etc/acl.Pg
==============================================================================
--- rt/branches/rt-3.1/etc/acl.Pg	(original)
+++ rt/branches/rt-3.1/etc/acl.Pg	Sat Jul 10 02:05:15 2004
@@ -10,37 +10,39 @@
       Attributes
       attributes_id_seq
       queues_id_seq
-      Queues
+ Queues 
       links_id_seq
-      Links
+ Links 
       principals_id_seq
-      Principals
+ Principals 
       groups_id_seq
-      Groups
+ Groups 
       scripconditions_id_seq
-      ScripConditions
+ ScripConditions 
       transactions_id_seq
-      Transactions
+ Transactions 
       scrips_id_seq
-      Scrips
+ Scrips 
       acl_id_seq
-      ACL
+ ACL 
       groupmembers_id_seq
-      GroupMembers
+ GroupMembers 
       cachedgroupmembers_id_seq
-      CachedGroupMembers
+ CachedGroupMembers 
       users_id_seq
-      Users
+ Users 
       tickets_id_seq
-      Tickets
+ Tickets 
       scripactions_id_seq
-      ScripActions
+ ScripActions 
       templates_id_seq
-      Templates
-      ticketcustomfieldvalues_id_s
-      TicketCustomFieldValues
+ Templates 
+ objectcustomfieldvalues_id_s
+ ObjectCustomFieldValues 
       customfields_id_seq
-      CustomFields
+ CustomFields 
+ objectcustomfields_id_s
+ ObjectCustomFields 
       customfieldvalues_id_seq
       CustomFieldValues
       sessions

Modified: rt/branches/rt-3.1/etc/acl.mysql
==============================================================================
--- rt/branches/rt-3.1/etc/acl.mysql	(original)
+++ rt/branches/rt-3.1/etc/acl.mysql	Sat Jul 10 02:05:15 2004
@@ -1,4 +1,5 @@
 sub acl {
+return () if !$RT::DatabaseUser or $RT::DatabaseUser eq 'root';
 return  (
 "USE mysql;",
 "DELETE FROM user WHERE user = '${RT::DatabaseUser}';",

Modified: rt/branches/rt-3.1/etc/schema.Informix
==============================================================================
--- rt/branches/rt-3.1/etc/schema.Informix	(original)
+++ rt/branches/rt-3.1/etc/schema.Informix	Sat Jul 10 02:05:15 2004
@@ -103,20 +103,22 @@
 
 CREATE TABLE Transactions (
   	id 			SERIAL,
-  	EffectiveTicket 	INTEGER DEFAULT 0 NOT NULL,
-  	Ticket 			INTEGER DEFAULT 0 NOT NULL,
+  	ObjectType 		VARCHAR(255),
+  	ObjectId 		INTEGER DEFAULT 0 NOT NULL,
   	TimeTaken 		INTEGER DEFAULT 0 NOT NULL,
   	Type 			VARCHAR(20),
   	Field 			VARCHAR(40),
   	OldValue 		VARCHAR(255),
   	NewValue 		VARCHAR(255),
+  	ReferenceType 		VARCHAR(255),
+	OldReference		INTEGER DEFAULT 0,
+	NewReference		INTEGER DEFAULT 0,
   	Data 			VARCHAR(255),
   	Creator 		INTEGER DEFAULT 0 NOT NULL,
   	Created 		DATETIME YEAR TO SECOND,
         PRIMARY KEY (id)
 );
-CREATE INDEX Transactions1 ON Transactions (Ticket);
-CREATE INDEX Transactions2 ON Transactions (EffectiveTicket);
+CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId);
 
 
 CREATE TABLE Scrips (
@@ -305,7 +307,9 @@
 	id		SERIAL,
 	Name		VARCHAR(200),
 	Type		VARCHAR(200),
-	Queue		INTEGER DEFAULT 0 NOT NULL,
+	MaxValues	INTEGER DEFAULT 0 NOT NULL,
+	Pattern		VARCHAR(255),
+	LookupType	VARCHAR(255),
 	Description	VARCHAR(255),
 	SortOrder	INTEGER DEFAULT 0 NOT NULL,
 	Creator		INTEGER DEFAULT 0 NOT NULL,
@@ -315,7 +319,6 @@
 	Disabled	SMALLINT DEFAULT 0 NOT NULL,
         PRIMARY KEY (id)
 );
-CREATE INDEX CustomFields1 ON CustomFields (Disabled, Queue);
 
 
 CREATE TABLE CustomFieldValues (

Modified: rt/branches/rt-3.1/etc/schema.Oracle
==============================================================================
--- rt/branches/rt-3.1/etc/schema.Oracle	(original)
+++ rt/branches/rt-3.1/etc/schema.Oracle	Sat Jul 10 02:05:15 2004
@@ -105,19 +105,21 @@
 CREATE TABLE Transactions (
   	id 			NUMBER(11,0) 
 		CONSTRAINT Transactions_Key PRIMARY KEY,
-  	EffectiveTicket 	NUMBER(11,0) DEFAULT 0 NOT NULL,
-  	Ticket 			NUMBER(11,0) DEFAULT 0 NOT NULL,
+  	ObjectType 		VARCHAR2(255),
+  	ObjectId		NUMBER(11,0) DEFAULT 0 NOT NULL,
   	TimeTaken 		NUMBER(11,0) DEFAULT 0 NOT NULL,
   	Type 			VARCHAR2(20),
   	Field 			VARCHAR2(40),
   	OldValue 		VARCHAR2(255),
   	NewValue 		VARCHAR2(255),
+  	ReferenceType 		VARCHAR2(255),
+  	OldReference 		NUMBER(11,0),
+  	NewReference 		NUMBER(11,0),
   	Data 			VARCHAR2(255),
   	Creator 		NUMBER(11,0) DEFAULT 0 NOT NULL,
   	Created 		DATE
 );
-CREATE INDEX Transactions1 ON Transactions (Ticket);
-CREATE INDEX Transactions2 ON Transactions (EffectiveTicket);
+CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId);
 
 
 CREATE SEQUENCE SCRIPS_seq;
@@ -317,7 +319,9 @@
 		CONSTRAINT CustomFields_Key PRIMARY KEY,
 	Name		VARCHAR2(200),
 	Type		VARCHAR2(200),
-	Queue		NUMBER(11,0) DEFAULT 0 NOT NULL,
+	MaxValues	NUMBER(11,0) DEFAULT 0 NOT NULL,
+	Pattern		VARCHAR2(255),
+	LookupType	VARCHAR2(255),
 	Description	VARCHAR2(255),
 	SortOrder	NUMBER(11,0) DEFAULT 0 NOT NULL,
 	Creator		NUMBER(11,0) DEFAULT 0 NOT NULL,
@@ -326,7 +330,6 @@
 	LastUpdated	DATE,
 	Disabled	NUMBER(11,0) DEFAULT 0 NOT NULL
 );
-CREATE INDEX CustomFields1 ON CustomFields (Disabled, Queue);
 
 
 CREATE SEQUENCE CUSTOMFIELDVALUES_seq;

Modified: rt/branches/rt-3.1/etc/schema.Pg
==============================================================================
--- rt/branches/rt-3.1/etc/schema.Pg	(original)
+++ rt/branches/rt-3.1/etc/schema.Pg	Sat Jul 10 02:05:15 2004
@@ -57,7 +57,7 @@
   Created TIMESTAMP NULL  ,
   LastUpdatedBy integer NOT NULL DEFAULT 0  ,
   LastUpdated TIMESTAMP NULL  ,
-  Disabled int2 NOT NULL DEFAULT 0 ,
+  Disabled integer NOT NULL DEFAULT 0 ,
   PRIMARY KEY (id)
 
 );
@@ -108,7 +108,7 @@
         id INTEGER DEFAULT nextval('principals_id_seq') not null,
         PrincipalType VARCHAR(16) not null,
         ObjectId integer, 
-        Disabled int2 NOT NULL DEFAULT 0 ,
+        Disabled integer NOT NULL DEFAULT 0 ,
         PRIMARY KEY (id)
 
 );
@@ -183,13 +183,16 @@
 
 CREATE TABLE Transactions (
   id INTEGER DEFAULT nextval('transactions_id_seq'),
-  EffectiveTicket integer NOT NULL DEFAULT 0  ,
-  Ticket integer NOT NULL DEFAULT 0  ,
+  ObjectType varchar(255) NOT NULL  ,
+  ObjectId integer NOT NULL DEFAULT 0  ,
   TimeTaken integer NOT NULL DEFAULT 0  ,
   Type varchar(20) NULL  ,
   Field varchar(40) NULL  ,
   OldValue varchar(255) NULL  ,
   NewValue varchar(255) NULL  ,
+  ReferenceType varchar(255) NULL,
+  OldReference integer NULL  ,
+  NewReference integer NULL  ,
   Data varchar(255) NULL  ,
 
   Creator integer NOT NULL DEFAULT 0  ,
@@ -197,8 +200,7 @@
   PRIMARY KEY (id)
 
 );
-CREATE INDEX Transactions1 ON Transactions (Ticket);
-CREATE INDEX Transactions2 ON Transactions (EffectiveTicket);
+CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId);
 
 -- }}}
 
@@ -299,7 +301,7 @@
         MemberId int, 
         Via int, 
         ImmediateParentId int, 
-        Disabled int2 NOT NULL DEFAULT 0 , 
+        Disabled integer NOT NULL DEFAULT 0 , 
         PRIMARY KEY (id)
 
 );
@@ -406,7 +408,7 @@
   LastUpdated TIMESTAMP NULL  ,
   Creator integer NOT NULL DEFAULT 0  ,
   Created TIMESTAMP NULL  ,
-  Disabled int2 NOT NULL DEFAULT 0 ,
+  Disabled integer NOT NULL DEFAULT 0 ,
   PRIMARY KEY (id)
 
 );
@@ -474,7 +476,7 @@
 
 -- }}}
 
--- {{{ TicketCustomFieldValues 
+-- {{{ ObjectCustomFieldValues 
 
 
 
@@ -482,13 +484,18 @@
 -- Sequences for table TICKETCUSTOMFIELDVALUES
 --
 
-CREATE SEQUENCE ticketcustomfieldvalues_id_s;
+CREATE SEQUENCE objectcustomfieldvalues_id_s;
 
-CREATE TABLE TicketCustomFieldValues (
-  id INTEGER DEFAULT nextval('ticketcustomfieldvalues_id_s'),
-  Ticket int NOT NULL  ,
+CREATE TABLE ObjectCustomFieldValues (
+  id INTEGER DEFAULT nextval('objectcustomfieldvalues_id_s'),
   CustomField int NOT NULL  ,
+  ObjectType varchar(255) NULL  ,
+  ObjectId int NOT NULL  ,
+  Current int DEFAULT 1,
   Content varchar(255) NULL  ,
+  LargeContent text NULL,
+  ContentType varchar(80) NULL,
+  ContentEncoding varchar(80) NULL  ,
 
   Creator integer NOT NULL DEFAULT 0  ,
   Created TIMESTAMP NULL  ,
@@ -498,8 +505,8 @@
 
 );
 
-CREATE INDEX TicketCustomFieldValues1 ON TicketCustomFieldValues (CustomField,Ticket,Content); 
-CREATE INDEX TicketCustomFieldValues2 ON TicketCustomFieldValues (CustomField,Ticket); 
+CREATE INDEX ObjectCustomFieldValues1 ON ObjectCustomFieldValues (CustomField,ObjectType,ObjectId,Content); 
+CREATE INDEX ObjectCustomFieldValues2 ON ObjectCustomFieldValues (CustomField,ObjectType,ObjectId); 
 
 -- }}}
 
@@ -517,7 +524,10 @@
   id INTEGER DEFAULT nextval('customfields_id_seq'),
   Name varchar(200) NULL  ,
   Type varchar(200) NULL  ,
-  Queue integer NOT NULL DEFAULT 0 ,
+  MaxValues integer NOT NULL DEFAULT 0  ,
+  Repeated integer NOT NULL DEFAULT 0 , 
+  Pattern varchar(255) NULL  ,
+  LookupType varchar(255) NOT NULL  ,
   Description varchar(255) NULL  ,
   SortOrder integer NOT NULL DEFAULT 0  ,
 
@@ -525,7 +535,27 @@
   Created TIMESTAMP NULL  ,
   LastUpdatedBy integer NOT NULL DEFAULT 0  ,
   LastUpdated TIMESTAMP NULL  ,
-  Disabled int2 NOT NULL DEFAULT 0 ,
+  Disabled integer NOT NULL DEFAULT 0 ,
+  PRIMARY KEY (id)
+
+);
+
+-- }}}
+
+-- {{{ ObjectCustomFields 
+
+CREATE SEQUENCE objectcustomfields_id_s;
+
+CREATE TABLE ObjectCustomFields (
+  id INTEGER DEFAULT nextval('objectcustomfields_id_s'),
+  CustomField integer NOT NULL,
+  ObjectId integer NOT NULL,
+  SortOrder integer NOT NULL DEFAULT 0  ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created TIMESTAMP NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated TIMESTAMP NULL  ,
   PRIMARY KEY (id)
 
 );

Modified: rt/branches/rt-3.1/etc/schema.SQLite
==============================================================================
--- rt/branches/rt-3.1/etc/schema.SQLite	(original)
+++ rt/branches/rt-3.1/etc/schema.SQLite	Sat Jul 10 02:05:15 2004
@@ -112,21 +112,23 @@
 --- {{{ Transactions
 CREATE TABLE Transactions (
   id INTEGER PRIMARY KEY  ,
-  EffectiveTicket integer NULL  ,
-  Ticket integer NULL  ,
+  ObjectType varchar(255) NULL  ,
+  ObjectId integer NULL  ,
   TimeTaken integer NULL  ,
   Type varchar(20) NULL  ,
   Field varchar(40) NULL  ,
   OldValue varchar(255) NULL  ,
   NewValue varchar(255) NULL  ,
+  ReferenceType varchar(255) NULL  ,
+  OldReference integer NULL  ,
+  NewReference integer NULL  ,
   Data varchar(255) NULL  ,
 
   Creator integer NULL  ,
   Created DATETIME NULL  
   
 ) ;
-CREATE INDEX Transactions1 ON Transactions (Ticket);
-CREATE INDEX Transactions2 ON Transactions (EffectiveTicket);
+CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId);
 
 --- }}}
 
@@ -355,7 +357,9 @@
   id INTEGER PRIMARY KEY  ,
   Name varchar(200) NULL  ,
   Type varchar(200) NULL  ,
-  Queue int NULL ,
+  MaxValues int NULL ,
+  Pattern varchar(255) NULL  ,
+  LookupType varchar(255) NULL  ,
   Description varchar(255) NULL  ,
   SortOrder integer NULL  ,
 

Modified: rt/branches/rt-3.1/etc/schema.Sybase
==============================================================================
--- rt/branches/rt-3.1/etc/schema.Sybase	(original)
+++ rt/branches/rt-3.1/etc/schema.Sybase	Sat Jul 10 02:05:15 2004
@@ -1,3 +1,5 @@
+NEEDS UPDATE FOR 3.3
+
 # {{{ Attachments
 
 CREATE TABLE rt3.Attachments (

Modified: rt/branches/rt-3.1/etc/schema.mysql
==============================================================================
--- rt/branches/rt-3.1/etc/schema.mysql	(original)
+++ rt/branches/rt-3.1/etc/schema.mysql	Sat Jul 10 02:05:15 2004
@@ -118,21 +118,23 @@
 # {{{ Transactions
 CREATE TABLE Transactions (
   id INTEGER NOT NULL  AUTO_INCREMENT,
-  EffectiveTicket integer NOT NULL DEFAULT 0  ,
-  Ticket integer NOT NULL DEFAULT 0  ,
+  ObjectType varchar(64) NOT NULL,
+  ObjectId integer NOT NULL DEFAULT 0  ,
   TimeTaken integer NOT NULL DEFAULT 0  ,
   Type varchar(20) NULL  ,
   Field varchar(40) NULL  ,
   OldValue varchar(255) NULL  ,
   NewValue varchar(255) NULL  ,
+  ReferenceType varchar(255) NULL,
+  OldReference integer NULL  ,
+  NewReference integer NULL  ,
   Data varchar(255) NULL  ,
 
   Creator integer NOT NULL DEFAULT 0  ,
   Created DATETIME NULL  ,
   PRIMARY KEY (id)
 ) TYPE=InnoDB;
-CREATE INDEX Transactions1 ON Transactions (Ticket);
-CREATE INDEX Transactions2 ON Transactions (EffectiveTicket);
+CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId);
 
 # }}}
 
@@ -337,13 +339,19 @@
 
 # }}}
 
-# {{{ TicketCustomFieldValues 
+# {{{ ObjectCustomFieldValues 
 
-CREATE TABLE TicketCustomFieldValues (
+CREATE TABLE ObjectCustomFieldValues (
   id INTEGER NOT NULL  AUTO_INCREMENT,
-  Ticket int NOT NULL  ,
   CustomField int NOT NULL  ,
+  ObjectType varchar(255) NOT NULL,	    # Final target of the Object
+  ObjectId int NOT NULL  ,		    # New -- Replaces Ticket
+
+  Current BOOL DEFAULT 1,		    # New -- whether the value was current
   Content varchar(255) NULL  ,
+  LargeContent LONGTEXT NULL,		    # New -- to hold 255+ strings
+  ContentType varchar(80) NULL,		    # New -- only text/* gets searched
+  ContentEncoding varchar(80) NULL  ,	    # New -- for binary Content
 
   Creator integer NOT NULL DEFAULT 0  ,
   Created DATETIME NULL  ,
@@ -352,7 +360,8 @@
   PRIMARY KEY (id)
 ) TYPE=InnoDB;
 
-CREATE INDEX TicketCustomFieldValues1 ON TicketCustomFieldValues (CustomField,Ticket,Content); 
+CREATE INDEX ObjectCustomFieldValues1 ON ObjectCustomFieldValues (Content); 
+CREATE INDEX ObjectCustomFieldValues2 ON ObjectCustomFieldValues (CustomField,ObjectType,ObjectId); 
 
 # }}}
 
@@ -361,10 +370,13 @@
 CREATE TABLE CustomFields (
   id INTEGER NOT NULL  AUTO_INCREMENT,
   Name varchar(200) NULL  ,
-  Type varchar(200) NULL  ,
-  Queue integer NOT NULL DEFAULT 0 ,
+  Type varchar(200) NULL  ,	# Changed -- 'Single' and 'Multiple' is moved out
+  MaxValues integer,		# New -- was 'Single'(1) and 'Multiple'(0)
+  Pattern varchar(255) NULL  ,	# New -- Must validate against this
+  Repeated int2 NOT NULL DEFAULT 0 , # New -- repeated table entry
   Description varchar(255) NULL  ,
   SortOrder integer NOT NULL DEFAULT 0  ,
+  LookupType varchar(255) NOT NULL,
 
   Creator integer NOT NULL DEFAULT 0  ,
   Created DATETIME NULL  ,
@@ -374,8 +386,22 @@
   PRIMARY KEY (id)
 ) TYPE=InnoDB;
 
-CREATE INDEX CustomFields1 on CustomFields (Disabled, Queue);
+# }}}
+
+# {{{ ObjectCustomFields 
+
+CREATE TABLE ObjectCustomFields (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  CustomField int NOT NULL  ,
+  ObjectId integer NOT NULL,
+  SortOrder integer NOT NULL DEFAULT 0  ,
 
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
 
 # }}}
 

Added: rt/branches/rt-3.1/etc/upgrade/3.3.0/acl.Informix
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/etc/upgrade/3.3.0/acl.Informix	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,4 @@
+sub acl {
+    return ();
+}
+1;

Added: rt/branches/rt-3.1/etc/upgrade/3.3.0/acl.Oracle
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/etc/upgrade/3.3.0/acl.Oracle	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,4 @@
+sub acl {
+    return ();
+}
+1;

Added: rt/branches/rt-3.1/etc/upgrade/3.3.0/acl.Pg
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/etc/upgrade/3.3.0/acl.Pg	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,20 @@
+sub acl {
+    my $dbh = shift;
+
+    my @acls;
+
+    my @tables = qw (
+    objectcustomfieldvalues
+    objectcustomfields_id_s
+    objectcustomfields
+    );
+
+    foreach my $table (@tables) {
+        push @acls,
+          "GRANT SELECT, INSERT, UPDATE, DELETE ON $table to "
+          . $RT::DatabaseUser . ";";
+
+    }
+    return (@acls);
+}
+1;

Added: rt/branches/rt-3.1/etc/upgrade/3.3.0/acl.SQLite
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/etc/upgrade/3.3.0/acl.SQLite	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,4 @@
+sub acl {
+    return ();
+}
+1;

Added: rt/branches/rt-3.1/etc/upgrade/3.3.0/acl.mysql
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/etc/upgrade/3.3.0/acl.mysql	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,4 @@
+sub acl {
+    return ();
+}
+1;

Added: rt/branches/rt-3.1/etc/upgrade/3.3.0/schema.Pg
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/etc/upgrade/3.3.0/schema.Pg	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,78 @@
+alter Table Transactions ADD Column ObjectType varchar(64);
+update Transactions set ObjectType = 'RT::Ticket';
+ALTER TABLE Transactions ALTER COLUMN ObjectType SET NOT NULL;
+alter table Transactions drop column EffectiveTicket;
+alter table Transactions add column ReferenceType varchar(255) NULL;
+alter table Transactions add column OldReference integer NULL;      
+alter table Transactions add column NewReference integer NULL;
+drop index transactions1;            
+alter table Transactions rename column Ticket to ObjectId;
+
+
+CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId);
+
+alter table TicketCustomFieldValues rename to ObjectCustomFieldValues;
+
+alter table ObjectCustomFieldValues  rename column Ticket to ObjectId;
+
+alter table objectcustomfieldvalues add column ObjectType varchar(255);
+
+update objectcustomfieldvalues set ObjectType = 'RT::Ticket';
+
+ALTER TABLE objectcustomfieldvalues ALTER COLUMN ObjectType SET NOT NULL;
+
+alter table objectcustomfieldvalues add column Current int;
+
+alter table objectcustomfieldvalues alter column Current SET  default 1;  
+
+UPDATE objectcustomfieldvalues SET Current = 1;
+
+alter table objectcustomfieldvalues add column LargeContent TEXT NULL;
+
+alter table objectcustomfieldvalues add column ContentType varchar(80) NULL;
+
+alter table objectcustomfieldvalues add column ContentEncoding varchar(80) NULL;
+
+drop index ticketcustomfieldvalues1;
+
+drop index ticketcustomfieldvalues2;
+
+create index ObjectCustomFieldValues1 on objectcustomfieldvalues (CustomField,ObjectType,ObjectId,Content); 
+
+create index ObjectCustomFieldValues2  on objectcustomfieldvalues (CustomField,ObjectType,ObjectId); 
+
+
+CREATE SEQUENCE objectcustomfields_id_s;
+
+CREATE TABLE ObjectCustomFields (
+  id INTEGER DEFAULT nextval('objectcustomfields_id_s'),
+  CustomField integer NOT NULL,
+  ObjectId integer NOT NULL,
+  SortOrder integer NOT NULL DEFAULT 0  ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created TIMESTAMP NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated TIMESTAMP NULL  ,
+  PRIMARY KEY (id)
+
+);
+
+
+INSERT into ObjectCustomFields (CustomField, ObjectId, SortOrder, Creator, LastUpdatedBy) SELECT  id, Queue, SortOrder, Creator, LastUpdatedBy from CustomFields;
+
+alter table CustomFields add column LookupType varchar(255);
+alter table CustomFields add column Repeated int2;
+alter table CustomFields add column Pattern varchar(255) NULL;
+alter table CustomFields add column MaxValues integer;
+
+UPDATE CustomFields SET MaxValues = 0 WHERE Type LIKE '%Multiple';
+UPDATE CustomFields SET MaxValues = 1 WHERE Type LIKE '%Single';
+UPDATE CustomFields SET Type = 'Select' WHERE Type LIKE 'Select%';
+UPDATE CustomFields SET Type = 'Freeform' WHERE Type LIKE 'Freeform%';
+UPDATE CustomFields Set LookupType = 'RT::Queue-RT::Ticket';
+ALTER TABLE CustomFields ALTER COLUMN LookupType SET NOT NULL;
+UPDATE CustomFields Set Repeated = 0;
+ALTER TABLE CustomFields ALTER COLUMN Repeated SET DEFAULT 0;
+ALTER TABLE CustomFields ALTER COLUMN Repeated SET NOT NULL;
+alter table CustomFields drop column Queue; 

Added: rt/branches/rt-3.1/etc/upgrade/3.3.0/schema.mysql
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/etc/upgrade/3.3.0/schema.mysql	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,64 @@
+alter Table Transactions ADD Column (ObjectType varchar(64) not null);
+update Transactions set ObjectType = 'RT::Ticket';
+alter table Transactions drop column EffectiveTicket;
+alter table Transactions add column ReferenceType varchar(255) NULL;
+alter table Transactions add column OldReference integer NULL;      
+alter table Transactions add column NewReference integer NULL;
+alter table Transactions drop index transactions1;            
+alter table Transactions change Ticket ObjectId integer NOT NULL DEFAULT 0  ;
+
+CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId);
+
+alter table TicketCustomFieldValues rename ObjectCustomFieldValues;
+
+alter table ObjectCustomFieldValues  change Ticket ObjectId integer NOT NULL DEFAULT 0  ;
+
+alter table objectcustomfieldvalues add column ObjectType varchar(255) not null;
+
+update objectcustomfieldvalues set ObjectType = 'RT::Ticket';
+
+alter table objectcustomfieldvalues add column Current bool default 1;  
+
+alter table objectcustomfieldvalues add column LargeContent LONGTEXT NULL;
+
+alter table objectcustomfieldvalues add column ContentType varchar(80) NULL;
+
+alter table objectcustomfieldvalues add column ContentEncoding varchar(80) NULL;
+
+alter table objectcustomfieldvalues drop index ticketcustomfieldvalues1;
+
+alter table objectcustomfieldvalues drop index ticketcustomfieldvalues2;
+
+alter table objectcustomfieldvalues add index ObjectCustomFieldValues1 (Content); 
+
+alter table objectcustomfieldvalues add index ObjectCustomFieldValues2 (CustomField,ObjectType,ObjectId); 
+
+
+CREATE TABLE ObjectCustomFields (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  CustomField int NOT NULL  ,
+  ObjectId integer NOT NULL,
+  SortOrder integer NOT NULL DEFAULT 0  ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+
+INSERT into ObjectCustomFields (id, CustomField, ObjectId, SortOrder, Creator, LastUpdatedBy) SELECT  null, id, Queue, SortOrder, Creator, LastUpdatedBy from CustomFields;
+
+alter table CustomFields add column LookupType varchar(255) NOT NULL;
+alter table CustomFields add column Repeated int2 NOT NULL DEFAULT 0 ;
+alter table CustomFields add column Pattern varchar(255) NULL;
+alter table CustomFields add column MaxValues integer;
+alter table CustomFields drop index CustomFields1;
+
+UPDATE CustomFields SET MaxValues = 0 WHERE Type LIKE '%Multiple';
+UPDATE CustomFields SET MaxValues = 1 WHERE Type LIKE '%Single';
+UPDATE CustomFields SET Type = 'Select' WHERE Type LIKE 'Select%';
+UPDATE CustomFields SET Type = 'Freeform' WHERE Type LIKE 'Freeform%';
+UPDATE CustomFields Set LookupType = 'RT::Queue-RT::Ticket';
+alter table CustomFields drop column Queue; 

Added: rt/branches/rt-3.1/html/Admin/CustomFields/GroupRights.html
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Admin/CustomFields/GroupRights.html	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,140 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%#  Copyright (c) 2002-2003 Jesse Vincent <jesse at bestpractical.com>
+%#  
+%#  This program is free software; you can redistribute it and/or modify
+%#  it under the terms of version 2 of the GNU General Public License 
+%#  as published by the Free Software Foundation.
+%# 
+%#  A copy of that license should have arrived with this
+%#  software, but in any event can be snarfed from www.gnu.org.
+%# 
+%#  This program is distributed in the hope that it will be useful,
+%#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+%#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+%#  GNU General Public License for more details.
+%# 
+%# END LICENSE BLOCK
+
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/CustomFieldTabs, id => $id, current_subtab =>
+"Admin/CustomFields/UserRights.html?id=".$id, Title => $title &>
+<& /Elements/ListActions, actions => \@results &>
+
+  <FORM METHOD=POST ACTION="GroupRights.html">
+    <INPUT TYPE=HIDDEN NAME=id VALUE="<% $CustomFieldObj->id %>">
+      
+      
+<h1><&|/l&>System groups</&></h1>
+<TABLE>
+% $Groups = RT::Groups->new($session{'CurrentUser'});
+% $Groups->LimitToSystemInternalGroups();
+%	while (my $Group = $Groups->Next()) {
+  <TR ALIGN=RIGHT> 
+	<TD VALIGN=TOP>
+	    <% loc($Group->Type) %>
+		  </TD>
+	  <TD>
+	    <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
+        Object => $CustomFieldObj  &>
+	  </TD>
+	</TR>
+% }
+</TABLE>
+<h1><&|/l&>User defined groups</&></h1>
+<TABLE>
+% $Groups = RT::Groups->new($session{'CurrentUser'});
+% $Groups->LimitToUserDefinedGroups();    
+%	while (my $Group = $Groups->Next()) {
+  <TR ALIGN=RIGHT> 
+	<TD VALIGN=TOP>
+	    <% $Group->Name %>
+		  </TD>
+	  <TD>
+	    <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
+        Object => $CustomFieldObj  &>
+	  </TD>
+	</TR>
+% }
+</TABLE>
+            
+      <& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
+      
+  </FORM>
+  
+<%INIT>
+ 
+
+
+
+  
+
+if (!defined $id) {
+    $m->comp("/Elements/Error", Why => loc("No CustomField defined"));
+}
+
+my $CustomFieldObj = RT::CustomField->new($session{'CurrentUser'});
+$CustomFieldObj->Load($id) || $m->comp("/Elements/Error", Why => loc("Couldn't load CustomField [_1]",$id));
+
+my $Groups;
+  
+  my ( $ACL, @results );
+
+    foreach my $arg (keys %ARGS) {
+        if ($arg =~ /GrantRight-(\d+)-(.*?)-(\d+)$/) {
+            my $principal_id = $1;
+            my $object_type = $2;
+            my $object_id = $3;
+            my $rights = $ARGS{$arg};
+
+            my $principal = RT::Principal->new($session{'CurrentUser'});
+            $principal->Load($principal_id);
+            my $obj;
+
+            if ($object_type eq 'RT::CustomField') {
+                $obj = RT::CustomField->new($session{'CurrentUser'});
+                $obj->Load($object_id);
+            } else {
+                push (@results, loc("System Error").
+                                loc("Rights could not be granted for [_1]", $object_type));
+                next;
+            }
+
+            my @rights = ref($ARGS{$arg}) eq 'ARRAY' ? @{$ARGS{$arg}} : ($ARGS{$arg});
+            foreach my $right (@rights) {
+                next unless ($right);
+                my ($val, $msg) = $principal->GrantRight(Object => $obj, Right => $right);
+                push (@results, $msg);
+            }
+        }
+     elsif ($arg =~ /RevokeRight-(\d+)-(.*?)-(\d+)-(.*?)$/) {
+            my $principal_id = $1;
+            my $object_type = $2;
+            my $object_id = $3;
+            my $right = $4;
+
+            my $principal = RT::Principal->new($session{'CurrentUser'});
+            $principal->Load($principal_id);
+            next unless ($right);
+            my $obj;
+
+            if ($object_type eq 'RT::CustomField') {
+                $obj = RT::CustomField->new($session{'CurrentUser'});
+                $obj->Load($object_id);
+            } else {
+                push (@results, loc("System Error").
+                                loc("Rights could not be revoked for [_1]", $object_type));
+                next;
+            }
+            my ($val, $msg) = $principal->RevokeRight(Object => $obj, Right => $right);
+            push (@results, $msg);
+        }
+} 
+
+my $title = loc('Modify group rights for custom field [_1]', $CustomFieldObj->Name);
+    
+</%INIT>
+
+<%ARGS>
+$id => undef
+</%ARGS>

Added: rt/branches/rt-3.1/html/Admin/CustomFields/Modify.html
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Admin/CustomFields/Modify.html	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,166 @@
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/CustomFieldTabs, id => $CustomFieldObj->Id ,
+current_subtab => $current_subtab,
+Title => $title &>
+<& /Elements/ListActions, actions => \@results &>
+
+
+<FORM METHOD="POST" ACTION="Modify.html">
+<INPUT TYPE=HIDDEN NAME="id" VALUE="<%$id %>">
+<table>
+<tr>
+<td class="label"><&|/l&>Name</&></td>
+<td><input name="Name" VALUE="<%$CustomFieldObj->Name%>" SIZE=20></td></tr>
+<tr>
+<td class="label"><&|/l&>Description</&></td>
+<td><input name="Description" VALUE="<%$CustomFieldObj->Description%>" SIZE=80></td>
+</tr>
+
+<tr>
+<td class="label"><&|/l&>Type</&></td>
+<td><& /Admin/Elements/SelectCustomFieldType, 
+        Name => "TypeComposite", 
+        Default => $CustomFieldObj->TypeComposite, &>
+</td>
+</tr>
+<tr>
+<td class="label"><&|/l&>Applies to</&></td>
+<td><& /Admin/Elements/SelectCustomFieldLookupType, 
+        Name => "LookupType", 
+        Default => $CustomFieldObj->LookupType, &>
+</td>
+</tr>
+<tr>
+<td class="label">&nbsp;</td>
+<td>
+<INPUT TYPE=HIDDEN NAME="SetEnabled" VALUE="1">
+<INPUT TYPE=CHECKBOX NAME="Enabled" VALUE="1" <%$EnabledChecked%>> <&|/l&>Enabled (Unchecking this box disables this custom field)</&>
+</td>
+</tr>
+</table>
+<BR>
+% if ($CustomFieldObj->Id && $CustomFieldObj->Type =~ /^Select/i) {
+<H2><&|/l&>Values</&></H2>
+<div>
+<& /Admin/Elements/EditCustomFieldValues, CustomField => $CustomFieldObj &>
+<& /Admin/Elements/AddCustomFieldValue, CustomField => $CustomFieldObj &>
+</div>
+% }
+<&/Elements/Submit&>
+</FORM>
+
+
+
+<%INIT>
+
+
+
+my $CustomFieldObj = RT::CustomField->new( $session{'CurrentUser'} );
+my ( $title, @results, $EnabledChecked, $Disabled);
+$EnabledChecked = "CHECKED";
+
+if ( !$id ) {
+    $title = loc("Create a CustomField");
+    $id    = 'new';
+}
+else {
+
+    if ( $id eq 'new' ) {
+        my ( $val, $msg ) = $CustomFieldObj->Create(Name        => $Name,
+                                                    TypeComposite => $TypeComposite,
+                                                    LookupType => $LookupType,
+                                                    Description => $Description,);
+        $m->comp("/Elements/Error", Why =>  loc( "Could not create CustomField", $msg ) ) unless ($val);
+        push @results, $msg;
+        $title = loc( 'Created CustomField [_1]', $CustomFieldObj->Name() );
+    }
+    else {
+        $CustomFieldObj->Load($id) || $m->comp("/Elements/Error", Why =>  loc('No CustomField') );
+        $title = loc( 'Editing CustomField [_1]', $CustomFieldObj->Name() );
+
+        my @attribs = qw( Name TypeComposite LookupType Description);
+        my @aresults = UpdateRecordObject( AttributesRef => \@attribs,
+                                           Object        => $CustomFieldObj,
+                                           ARGSRef       => \%ARGS );
+
+        push @results, @aresults;
+
+	#we're asking about enabled on the web page but really care about disabled.
+	if ($Enabled == 1) {
+	    $Disabled = 0;
+	}	
+	else {
+	    $Disabled = 1;
+	}
+	if  ( ($SetEnabled) and ( $Disabled != $CustomFieldObj->Disabled) ) { 
+	    my  ($code, $msg) = $CustomFieldObj->SetDisabled($Disabled);
+	    push @results, loc('Enabled status: [_1]', loc_fuzzy($msg));
+	}
+	
+	if ($CustomFieldObj->Disabled()) {
+	    $EnabledChecked ="";
+	}
+    }
+
+    $id = $CustomFieldObj->id;
+}
+
+
+
+
+my $paramtag = "CustomField-".$CustomFieldObj->Id."-Value-";
+# Delete any fields that want to be deleted
+foreach my $key (keys %ARGS) {
+	
+	next unless ($key =~ /^Delete-$paramtag(\d+)$/);
+	my ($val, $msg) = $CustomFieldObj->DeleteValue($1);
+	push (@results, $msg);
+
+
+}
+# Update any existing values
+my $values = $CustomFieldObj->ValuesObj;
+while (my $value = $values->Next) {
+	foreach my $attr qw(Name Description SortOrder) {
+	my $param = $paramtag.$value->Id."-".$attr;
+
+	if ( $ARGS{$param} && ($value->$attr() ne $ARGS{$param}))  {
+		my $mutator = "Set$attr";
+		my ($id, $msg) = $value->$mutator($ARGS{$param});
+        push (@results, $msg);
+		}
+	}
+
+
+}
+
+
+
+# Add any new values
+if ($ARGS{$paramtag."new-Name"}) {
+	my ($id, $msg) = $CustomFieldObj->AddValue ( Name => $ARGS{$paramtag."new-Name"},
+						 Description => $ARGS{$paramtag."new-Description"},
+						 SortOrder => $ARGS{$paramtag."new-SortOrder"});
+	push (@results, $msg);
+}
+
+my $current_subtab;
+if ($ARGS{'Create'}){ 
+    $current_subtab = "Admin/CustomFields/Modify.html?Create=1";
+} else {
+    $current_subtab = "Admin/CustomFields/Modify.html?id=".$id;
+    }
+
+
+</%INIT>
+<%ARGS>
+$id => undef
+$TypeComposite => undef
+$LookupType => undef
+$MaxValues => undef
+$SortOrder => undef
+$Description => undef
+$Name => undef
+$SetEnabled => undef
+$Enabled => undef
+</%ARGS>

Added: rt/branches/rt-3.1/html/Admin/CustomFields/Objects.html
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Admin/CustomFields/Objects.html	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,111 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/CustomFieldTabs,
+    id => $id,
+    current_subtab => "Admin/CustomFields/Modify.html?id=".$id,
+    Title => $title
+    &>
+
+<& /Elements/ListActions, actions => \@results &>
+
+<FORM ACTION="Objects.html" METHOD=POST>
+<INPUT TYPE=HIDDEN NAME="id" VALUE="<% $id %>">
+<INPUT TYPE=HIDDEN NAME="UpdateObjs" VALUE="1">
+
+<h2><&|/l&>Selected objects</&></h2>
+<& /Admin/Elements/PickObjects, Objects => \@AssignedObjs, id => $id, Checked => 1 &>
+<h2><&|/l&>Unselected objects</&></h2>
+<& /Admin/Elements/PickObjects, Objects => \@UnassignedObjs, id => $id &>
+
+<& /Elements/Submit, CheckAll => 1, ClearAll => 1 &>
+</FORM>
+
+<%INIT>
+my $CF = RT::CustomField->new($session{'CurrentUser'});
+$CF->Load($id) or Abort(loc("Could not load CustomField [_1]"), $id);
+my $LookupType = $CF->LookupType;
+$LookupType =~ /^(RT::(\w+))/
+    or Abort(loc("Object of type [_1] cannot take custom fields", $LookupType));
+
+my $Type = $2;
+my $Class = $1;
+my $CollectionType = $2.'s';
+my $CollectionClass = $Class.'s';
+my $title = loc('Modify associated objects for [_1]', $CF->Name);
+
+my $Objects = $CollectionClass->new($session{'CurrentUser'});
+my (@results);
+my (@AssignedObjs, @UnassignedObjs);
+
+$Objects->UnLimit;
+$Objects->OrderBy( FIELD => 'Name' );
+
+
+my $ObjectCFs;
+$ObjectCFs = RT::ObjectCustomFields->new($session{'CurrentUser'});
+$ObjectCFs->UnLimit;
+$ObjectCFs->LimitToCustomField($id);
+
+my %seen;
+while (my $OCF = $ObjectCFs->Next) {
+    $seen{$OCF->ObjectId}++;
+}
+
+while (my $obj = $Objects->Next) { 
+    my $obj_id = $obj->Id;
+
+    if ($UpdateObjs) {
+	# Go through and delete all the custom field relationships that this object
+	# no longer has
+	my $key = "Object-$obj_id-CF-$id";
+	if ($ARGS{$key}) {
+	    if (!$seen{$obj_id}) {
+		my ($val, $msg) = $CF->AddToObject($obj);
+		push (@results, $msg);
+		push @UnassignedObjs, $obj if !$val;
+	    }
+	}
+	else {
+	    push @UnassignedObjs, $obj;
+	    if ($seen{$obj_id}) {
+		my ($val, $msg) = $CF->RemoveFromObject($obj);
+		push (@results, $msg);
+		pop @UnassignedObjs if !$val;
+	    }
+	}
+    }
+    elsif (!$seen{$obj_id}) {
+	push @UnassignedObjs, $obj;
+    }
+    next if @UnassignedObjs and $UnassignedObjs[-1] == $obj;
+    push @AssignedObjs, $obj;
+}
+
+</%INIT>
+<%ARGS>
+$id => undef
+$FindDisabledObjects => 0
+$UpdateObjs => 0
+</%ARGS>

Added: rt/branches/rt-3.1/html/Admin/CustomFields/UserRights.html
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Admin/CustomFields/UserRights.html	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,141 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%#  Copyright (c) 2002-2003 Jesse Vincent <jesse at bestpractical.com>
+%#  
+%#  This program is free software; you can redistribute it and/or modify
+%#  it under the terms of version 2 of the GNU General Public License 
+%#  as published by the Free Software Foundation.
+%# 
+%#  A copy of that license should have arrived with this
+%#  software, but in any event can be snarfed from www.gnu.org.
+%# 
+%#  This program is distributed in the hope that it will be useful,
+%#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+%#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+%#  GNU General Public License for more details.
+%# 
+%# END LICENSE BLOCK
+
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/CustomFieldTabs, id => $id, 
+current_subtab => "Admin/CustomFields/UserRights.html?id=".$id,
+Title => $title, &>
+<& /Elements/ListActions, actions => \@results &>
+
+  <FORM METHOD=POST ACTION="UserRights.html">
+    <INPUT TYPE=HIDDEN NAME=id VALUE="<% $CustomFieldObj->id %>">
+      
+      
+<TABLE>
+        
+%	while (my $Member = $Users->Next()) {
+% my $UserObj = $Member->MemberObj->Object();
+% my $group = RT::Group->new($session{'CurrentUser'});
+% $group->LoadACLEquivalenceGroup($Member->MemberObj);
+  <TR ALIGN=RIGHT> 
+	<TD VALIGN=TOP>
+	    <% $UserObj->Name %>
+		  </TD>
+	  <TD>
+	    <& /Admin/Elements/SelectRights, PrincipalId=> $group->PrincipalId,
+        Object => $CustomFieldObj  &>
+	  </TD>
+	</TR>
+% }
+      </TABLE>
+            
+      <& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
+      
+  </FORM>
+  
+<%INIT>
+ 
+  #Update the acls.
+  my @results;
+foreach my $arg (keys %ARGS) {
+        if ($arg =~ /GrantRight-(\d+)-(.*?)-(\d+)$/) {
+            my $principal_id = $1;
+            my $object_type = $2;
+            my $object_id = $3;
+            my $rights = $ARGS{$arg};
+
+            my $principal = RT::Principal->new($session{'CurrentUser'});
+            $principal->Load($principal_id);
+            my $obj;
+
+            if ($object_type eq 'RT::CustomField') {
+                $obj = RT::CustomField->new($session{'CurrentUser'});
+                $obj->Load($object_id);
+
+            } else {
+                push (@results, loc("System Error").
+                                loc("Rights could not be granted for [_1]",
+$object_type));
+                next;
+            }
+
+            my @rights = ref($ARGS{$arg}) eq 'ARRAY' ? @{$ARGS{$arg}} :
+($ARGS{$arg});
+            foreach my $right (@rights) {
+                next unless ($right);
+                my ($val, $msg) = $principal->GrantRight(Object => $obj, Right
+=> $right);
+                push (@results, $msg);
+            }
+        }
+     elsif ($arg =~ /RevokeRight-(\d+)-(.*?)-(\d+)-(.*?)$/) {
+            my $principal_id = $1;
+            my $object_type = $2;
+            my $object_id = $3;
+            my $right = $4;
+
+            my $principal = RT::Principal->new($session{'CurrentUser'});
+            $principal->Load($principal_id);
+            next unless ($right);
+            my $obj;
+
+            if ($object_type eq 'RT::Class') {
+                $obj = RT::Class->new($session{'CurrentUser'});
+                $obj->Load($object_id);
+            } else {
+                push (@results, loc("System Error").
+                                loc("Rights could not be revoked for [_1]",
+$object_type));
+                next;
+            }
+            my ($val, $msg) = $principal->RevokeRight(Object => $obj, Right =>
+$right);
+            push (@results, $msg);
+        }
+} 
+  
+
+# {{{ Deal with setting up the display of current rights.
+
+
+
+if (!defined $id) {
+    $m->comp("/Elements/Error", Why => loc("No Class defined"));
+}
+
+my $CustomFieldObj = RT::CustomField->new($session{'CurrentUser'});
+$CustomFieldObj->Load($id) || $m->comp("/Elements/Error", Why => loc("Couldn't load Class [_1]",$id));
+
+# Find out which users we want to display ACL selects for
+my $Privileged = RT::Group->new($session{'CurrentUser'});
+$Privileged->LoadSystemInternalGroup('Privileged');
+my $Users = $Privileged->MembersObj();
+
+    
+my $title = loc('Modify user rights for custom field [_1]', $CustomFieldObj->Name);
+  
+# }}}
+    
+</%INIT>
+
+<%ARGS>
+$id => undef
+$UserString => undef
+$UserOp => undef
+$UserField => undef
+</%ARGS>

Added: rt/branches/rt-3.1/html/Admin/CustomFields/index.html
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Admin/CustomFields/index.html	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,31 @@
+<& /Admin/Elements/Header, Title => loc('Select a Custom Field') &>
+<& /Admin/Elements/CustomFieldTabs, current_tab => 'Admin/CustomFields/', 
+    current_subtab => 'Admin/CustomFields/',
+    Title => loc('Select a Custom Field') &>
+
+% my $prev_lookup = '';
+% while (my $CustomFieldObj = $CustomFields->Next) { 
+%    $CustomFieldObj->CurrentUserHasRight('AdminCustomField') or next;
+%    my $lookup = $CustomFieldObj->FriendlyLookupType;
+%    if ($lookup ne $prev_lookup) {
+%        if ($prev_lookup) {
+</UL>
+%        }
+<H2><% loc("Custom Fields for [_1]", $lookup) %></H2>
+<UL>
+%        $prev_lookup = $lookup;
+%    }
+%    
+<LI>
+<A HREF="Modify.html?id=<%$CustomFieldObj->id()%>"><%$CustomFieldObj->Name%>: <%$CustomFieldObj->Description%></a>
+</LI>
+% }
+% if ($prev_lookup) {
+</UL>
+% }
+
+<%INIT>
+my $CustomFields = RT::CustomFields->new($session{'CurrentUser'});
+$CustomFields->UnLimit();
+$CustomFields->OrderByCols( { FIELD => 'LookupType' }, { FIELD => 'Name' } );
+</%INIT>

Modified: rt/branches/rt-3.1/html/Admin/Elements/AddCustomFieldValue
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Elements/AddCustomFieldValue	(original)
+++ rt/branches/rt-3.1/html/Admin/Elements/AddCustomFieldValue	Sat Jul 10 02:05:15 2004
@@ -25,15 +25,15 @@
 <TABLE BORDER="0">
 <TR><TD><small>
 <&|/l&>Sort</&>:<br>
-<input name="CustomField-<% $CustomField->Id %>-AddValue-SortOrder" size="5">
+<input size=3 name="CustomField-<%$CustomField->Id%>-Value-new-SortOrder" >
 </TD>
 <TD><small>
 <&|/l&>Name</&>:<br>
-<input size=20 name="CustomField-<% $CustomField->Id %>-AddValue-Name">
+<input type="text" size=30 name="CustomField-<%$CustomField->Id%>-Value-new-Name" >
 </TD>
 <TD><small>
 <&|/l&>Description</&>:<br>
-<input size="60" name="CustomField-<% $CustomField->Id %>-AddValue-Description">
+<input type="text" size=50 name="CustomField-<%$CustomField->Id%>-Value-new-Description">
 </TD></TR>
 </TABLE>
 

Added: rt/branches/rt-3.1/html/Admin/Elements/CustomFieldTabs
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Admin/Elements/CustomFieldTabs	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,69 @@
+<& /Admin/Elements/Tabs, 
+    subtabs => $tabs, 
+    current_tab => 'Admin/CustomFields/', 
+    current_subtab => $current_subtab, 
+    Title => $Title &>
+<%INIT>
+my $tabs;
+
+if ($id) {
+    my $cf = RT::CustomField->new( $session{'CurrentUser'} );
+    $cf->Load($id);
+    $tabs = {
+        C => {
+            title => $cf->Name,
+            path  => "Admin/CustomFields/Modify.html?id=" . $id,
+
+            subtabs => {
+
+                C => { title => loc('Basics'),
+                       path  => "Admin/CustomFields/Modify.html?id=" . $id,
+                },
+                D => { title => loc('Applies to'),
+                       path  => "Admin/CustomFields/Objects.html?id=" . $id,
+                },
+
+                F => { title => loc('Group Rights'),
+                       path  => "Admin/CustomFields/GroupRights.html?id="
+                         . $id, },
+                G => {
+                    title => loc('User Rights'),
+                    path => "Admin/CustomFields/UserRights.html?id=" . $id,
+                },
+
+            } }
+
+    };
+}
+
+if ($session{'CurrentUser'}->HasRight( Object => $RT::System, Right => 'AdminCustomField')) {
+  $tabs->{"A"} = { title => loc('Select custom field'),
+                        path => "Admin/CustomFields/",
+                           };
+  $tabs->{"B"} = { title => loc('New custom field'),
+                        path => "Admin/CustomFields/Modify.html?Create=1",
+                        separator => 1,
+                           };
+}
+
+  # Now let callbacks add their extra tabs
+  $m->comp('/Elements/Callback', tabs => $tabs, %ARGS);
+
+foreach my $tab ( sort keys %{$tabs} ) {                                        
+    if ( $tabs->{$tab}->{'path'} eq $current_subtab ) {                         
+        $tabs->{$tab}->{"current_subtab"} = $current_subtab;                    
+    }                                                                           
+  foreach my $subtab (sort keys %{$tabs->{'subtabs'}}) {
+    if ($tabs->{$tab}->{subtabs}->{$subtab}->{'path'} eq $current_tab) {
+      $tabs->{$tab}->{subtabs}->{$subtab}->{"current_subtab"} = $current_subtab;
+    }
+  }
+}                                                                               
+
+</%INIT>
+<%ARGS>
+$id => undef
+$current_subtab => undef
+$current_tab => undef
+$Title => undef
+</%ARGS>

Modified: rt/branches/rt-3.1/html/Admin/Elements/EditCustomFieldValues
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Elements/EditCustomFieldValues	(original)
+++ rt/branches/rt-3.1/html/Admin/Elements/EditCustomFieldValues	Sat Jul 10 02:05:15 2004
@@ -21,21 +21,41 @@
 %# 
 %# 
 %# END LICENSE BLOCK
-<i><&|/l&>(Check box to delete)</&></i>
-<ul>
-% while (my $v = $values->Next) {
-<li>
-<INPUT TYPE="text" SIZE="2" NAME="CustomField-<%$CustomField->Id%>-SortOrder<%$v->Id()%>" VALUE="<%$v->SortOrder()%>">
-<input type="checkbox" name="CustomField-<%$CustomField->Id%>-DeleteValue" value="<%$v->id%>">
-<%$v->Name%>
-% if ($v->Description) {
-<i>(<%$v->Description%>)</i>
+% if (!$values->Count) {
+<p><em><&|/l&>(no values)</&></em></p>
+%    return;
 % }
-</li>
+<i><&|/l&>(Check box to delete)</&></i>
+<table>
+<tr>
+<td>&nbsp;</td>
+<td><&|/l&>Sort</&></td>
+<td><&|/l&>Name</&></td>
+<td><&|/l&>Description</&></td>
+</tr>
+% while (my $value = $values->Next) {
+<tr>
+<td>
+<input type="checkbox" name="Delete-CustomField-<%$CustomField->Id%>-Value-<%$value->Id%>">
+</td>
+<td>
+<input size=3 name="CustomField-<%$CustomField->Id%>-Value-<%$value->Id%>-SortOrder" value="<%$value->SortOrder%>">
+</td>
+<td>
+<input type="text" size=30 name="CustomField-<%$CustomField->Id%>-Value-<%$value->Id%>-Name" value="<%$value->Name%>">
+</td>
+<td>
+<font size="-1">
+<input type="text" size=50 name="CustomField-<%$CustomField->Id%>-Value-<%$value->Id%>-Description" value="<%$value->Description%>">
+</font>
+</td>
+</tr>
 % }
-</ul>
+</table>
 <%init>
-my $values = $CustomField->Values();
+
+my $values = $CustomField->ValuesObj();
+
 </%init>
 <%args>
 $CustomField => undef

Modified: rt/branches/rt-3.1/html/Admin/Elements/EditCustomFields
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Elements/EditCustomFields	(original)
+++ rt/branches/rt-3.1/html/Admin/Elements/EditCustomFields	Sat Jul 10 02:05:15 2004
@@ -21,94 +21,63 @@
 %# 
 %# 
 %# END LICENSE BLOCK
-<& /Elements/ListActions, actions => \@actions &>
+<& /Elements/ListActions, actions => \@results &>
 
-<TABLE>
-<TR>
-<TD VALIGN=TOP>
-<h2><%$caption%></h2>
-</TD></TR></TABLE>
-% if ($CustomFields->Count == 0 ) {
-<P><i><&|/l&>(No custom fields)</&></i></P>
-% } else {
-<TABLE cellspacing=0 cellpadding=2>
-% my $count;
-% while (my $CustomFieldObj = $CustomFields->Next) { 
-<TR>
-  <TD valign="TOP">
-% if ($CustomFieldObj->Name) {
-    <A HREF="CustomField.html?Queue=<%$id%>&CustomField=<%$CustomFieldObj->id()%>"><b><%$CustomFieldObj->Name%></b></a><br>
-% } else {
-    <A HREF="CustomField.html?Queue=<%$id%>&CustomField=<%$CustomFieldObj->id()%>"><i>(<%loc("no name")%>)</i></a><br>
-% }
-    <%$CustomFieldObj->Description%>
-  </TD>
-  <TD valign="TOP">
-    <i><% $CustomFieldObj->FriendlyType %></i>
-  </TD>
-%  # show 'move up' unless it's the first item
-%  if ($count++) {
-  <TD valign="TOP">
-    <a href="CustomFields.html?id=<%$id%>&CustomField=<%$CustomFieldObj->id%>&Move=-1"><&|/l&>Move up</&></a>
-%  } else {
-  <TD valign="TOP" ALIGN=RIGHT>
-%  }
-
-%  # show 'move down' unless it's the last item
-%  if (!$CustomFields->IsLast) {
-%  $m->print(' | ') if $count > 1;
-    <a href="CustomFields.html?id=<%$id%>&CustomField=<%$CustomFieldObj->id%>&Move=1"><&|/l&>Move down</&></a>
-%  }
-</TD>
-</TR>
+<FORM ACTION="CustomFields.html" METHOD=POST>
+<INPUT TYPE=HIDDEN NAME="id" VALUE="<% $Object->Id %>">
+<INPUT TYPE=HIDDEN NAME="ObjectType" VALUE="<% $ObjectType %>">
+<INPUT TYPE=HIDDEN NAME="SubType" VALUE="<% $SubType %>">
+<INPUT TYPE=HIDDEN NAME="UpdateCFs" VALUE="1">
+
+% if ($Object->Id) {
+<h2><&|/l&>Global Custom Fields</&></h2>
+<& PickCustomFields, CustomFields => \@GlobalCFs, ReadOnly => 1, id => $id &>
 % }
+<h2><&|/l&>Selected Custom Fields</&></h2>
+<& PickCustomFields, CustomFields => [$ObjectCFs->CustomFields], id => $id, Checked => 1 &>
+<h2><&|/l&>Unselected Custom Fields</&></h2>
+<& PickCustomFields, CustomFields => \@UnassignedCFs, id => $id &>
 
-</TABLE>
-% }
-<FORM METHOD=GET ACTION="CustomFields.html">
-% if ($id) {
-<INPUT TYPE="Hidden" NAME="id" VALUE="<%$id%>">
-% }
-<input type="checkbox" name="FindDisabledCustomFields"> <&|/l&>Include disabled custom fields in listing.</&>
-<input type=submit value="<&|/l&>Go!</&>">
+<& /Elements/Submit, CheckAll => 1, ClearAll => 1 &>
 </FORM>
 
 
 <%INIT>
 my $CustomFields = RT::CustomFields->new($session{'CurrentUser'});
-my $QueueObj = RT::Queue->new($session{'CurrentUser'});
-my $caption;
+my @results;
+my (@GlobalCFs, @UnassignedCFs);
 
-if ($id)  {
-        $QueueObj->Load($id);                        
+my $id = $Object->Id;
+if ($id and !$Object->CurrentUserHasRight('AssignCustomFields')) {
+    $m->print('<P><i>', loc('(No custom fields)'), '</i></P>');
+    return;
 }
 
-if ($QueueObj->id) {
-	$CustomFields->LimitToQueue($id);
-}                                            
-else {                                       
-        $CustomFields->LimitToGlobal();
-}                                           
-
-if ($FindDisabledCustomFields) {
-    $caption = loc("All Custom Fields");
-    $CustomFields->{'find_disabled_rows'} = 1;
-} else {
-    $caption = loc("Enabled Custom Fields");
-}
+my $lookup = $ObjectType;
+$lookup .= "-$SubType" if $SubType;
+
+$CustomFields->LimitToLookupType($lookup);
+$CustomFields->OrderBy( FIELD => 'Name' );
+
+
+my ($GlobalCFs, $ObjectCFs);
+$ObjectCFs = RT::ObjectCustomFields->new($session{'CurrentUser'});
+$ObjectCFs->UnLimit;
+$ObjectCFs->LimitToObjectId($id);
+$ObjectCFs->LimitToLookupType($lookup);
 
 # {{{ deal with moving sortorder of custom fields
 if ($CustomField and $Move) {
-    my $SourceObj = RT::CustomField->new($session{'CurrentUser'});
-    $SourceObj->Load($CustomField) || Abort(loc('No CustomField'));
+    my $SourceObj = RT::ObjectCustomField->new($session{'CurrentUser'});
+    $SourceObj->LoadByCols( ObjectId => $id, CustomField => $CustomField );
 
     my $TargetObj;
     my $target_order = $SourceObj->SortOrder + $Move;
-    while (my $CustomFieldObj = $CustomFields->Next) { 
-	my $this_order = $CustomFieldObj->SortOrder;
+    while (my $ObjectCF = $ObjectCFs->Next) { 
+	my $this_order = $ObjectCF->SortOrder;
 
 	# if we have an exact match, finish the loop now
-	($TargetObj = $CustomFieldObj, last) if $this_order == $target_order;
+	($TargetObj = $ObjectCF, last) if $this_order == $target_order;
 
 	# otherwise, we need to apropos toward the general direction
 	# ... first, check the sign is correct
@@ -122,7 +91,7 @@
 	    next if $orig_delta < $this_delta;
 	}
 
-	$TargetObj = $CustomFieldObj;
+	$TargetObj = $ObjectCF;
     }
 
     if ($TargetObj) {
@@ -131,83 +100,66 @@
 	$TargetObj->SetSortOrder($s);
 	$SourceObj->SetSortOrder($t);
 	# because order changed, we must redo search for subsequent uses
-	$CustomFields->RedoSearch;
     }
 
-    $CustomFields->GotoFirstItem;
+    $ObjectCFs->GotoFirstItem;
 }
 # }}}
 
-# {{{ now process the 'copy queue' action
-my @actions;
-if ($Source and $Source ne $id) {
-    my $SourceQueue = RT::Queue->new($session{'CurrentUser'});
-    $SourceQueue->Load($Source) || Abort(loc("Couldn't load queue"));
-    my $SourceCustomFields = RT::CustomFields->new($session{'CurrentUser'});
-    $SourceCustomFields->LimitToQueue($SourceQueue->id);
-
-    # delete old fields
-    foreach my $CustomFieldObj ( @{$CustomFields->ItemsArrayRef} ) { 
-	$CustomFieldObj->Delete;
-    }
-
-    # add new fields
-    while (my $SourceCustomFieldObj = $SourceCustomFields->Next) {
-	my $CustomFieldObj = RT::CustomField->new($session{'CurrentUser'});
-	my ($val, $msg) =  $CustomFieldObj->Create(
-	    id => $SourceCustomFieldObj->id,
-	    Queue => $id,
-	    Name => $SourceCustomFieldObj->Name,
-	    Type => $SourceCustomFieldObj->Type,
-	    Description => $SourceCustomFieldObj->Description,
-	);
-	Abort(loc("Could not create CustomField") . ": $msg") unless ($val);
-	push @actions, $msg;
-
-	$CustomFieldObj->SetSortOrder($SourceCustomFieldObj->SortOrder);
-
-	# add new values
-	my $values = $SourceCustomFieldObj->Values();
-	while (my $v = $values->Next) {
-	    my ( $addval, $addmsg ) = $CustomFieldObj->AddValue(
-		Name => $v->Name,
-		Description => $v->Description,
-		SortOrder => $v->SortOrder
-	    );
-	}
-    }
-
-    # because content changed, we must redo search for subsequent uses
-    $CustomFields->RedoSearch;
-    $CustomFields->GotoFirstItem;
+if ($id) {
+    $GlobalCFs = RT::ObjectCustomFields->new($session{'CurrentUser'});
+    $GlobalCFs->LimitToObjectId(0);
+    $GlobalCFs->LimitToLookupType($lookup);
 }
-# }}}
 
-# {{{ deal with deleting existing custom fields
-foreach my $key (keys %ARGS) {
-  # {{{ if we're trying to delete the custom field
-  if ($key =~ /^DeleteCustomField-(\d+)/) {
-    my $id = $1;
-    my $CustomFieldObj = RT::CustomField->new($session{'CurrentUser'});
-    $CustomFieldObj->Load($id);
-    my ($retval, $msg) = $CustomFieldObj->Delete;
-    if ($retval) {
-      push @actions, loc("Custom field deleted");
+while (my $cf = $CustomFields->Next) { 
+    my $cf_id = $cf->Id;
+
+    if ($GlobalCFs and $GlobalCFs->HasEntryForCustomField($cf_id)) {
+	push @GlobalCFs, $cf;
+	next;
+    }
+
+    if ($UpdateCFs) {
+	# Go through and delete all the custom field relationships that this object
+	# no longer has
+	my $key = "Object-$id-CF-$cf_id";
+	if ($ARGS{$key}) {
+	    if (!$ObjectCFs->HasEntryForCustomField($cf_id)) {
+		my ($val, $msg) = $cf->AddToObject($Object);
+		push (@results, $msg);
+		push @UnassignedCFs, $cf if !$val;
+	    }
+	}
+	else {
+	    push @UnassignedCFs, $cf;
+	    if ($ObjectCFs->HasEntryForCustomField($cf_id)) {
+		my ($val, $msg) = $cf->RemoveFromObject($Object);
+		push (@results, $msg);
+		pop @UnassignedCFs if !$val;
+	    }
+	}
     }
-    else {
-      push @actions, $msg;
+    elsif (!$ObjectCFs->HasEntryForCustomField($cf_id)) {
+	push @UnassignedCFs, $cf;
     }
-  }
-  # }}}
 }
-# }}}
+
+# redo search...
+$ObjectCFs = RT::ObjectCustomFields->new($session{'CurrentUser'});
+$ObjectCFs->UnLimit;
+$ObjectCFs->LimitToObjectId($id);
+$ObjectCFs->LimitToLookupType($lookup);
 
 </%INIT>
 <%ARGS>
-$id => 0
 $title => undef
 $Move => undef
 $Source => undef
 $CustomField => undef
 $FindDisabledCustomFields => undef
+$UpdateCFs => 0
+$Object
+$ObjectType
+$SubType => ''
 </%ARGS>

Modified: rt/branches/rt-3.1/html/Admin/Elements/EditScrip
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Elements/EditScrip	(original)
+++ rt/branches/rt-3.1/html/Admin/Elements/EditScrip	Sat Jul 10 02:05:15 2004
@@ -109,6 +109,7 @@
 
 <& /Elements/Submit, Label => loc('Create'), Reset => 1 &>
 
+
 </FORM>
 <%init>
 my (@actions);

Modified: rt/branches/rt-3.1/html/Admin/Elements/GroupTabs
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Elements/GroupTabs	(original)
+++ rt/branches/rt-3.1/html/Admin/Elements/GroupTabs	Sat Jul 10 02:05:15 2004
@@ -45,14 +45,18 @@
                path  => "Admin/Groups/GroupRights.html?id=" . $GroupObj->id, },
         G => { title => loc('User Rights'),
                path  => "Admin/Groups/UserRights.html?id=" . $GroupObj->id, },
+        H => { title => loc('History'),
+               path  => "Admin/Groups/History.html?id=" . $GroupObj->id },
     }
 }
 }
 $tabs->{"A"} = { title => loc('Select group'),
                  path  => "Admin/Groups/", };
 $tabs->{"B"} = { title     => loc('New group'),
-                 path      => "Admin/Groups/Modify.html?Create=1",
-                 separator => 1, };
+                 path      => "Admin/Groups/Modify.html?Create=1", };
+$tabs->{"C"} = { title => loc('Custom Fields'),
+		 path => "Admin/Groups/CustomFields.html",
+		 separator => 1, };
 
 # Now let callbacks add their extra tabs
 $m->comp( '/Elements/Callback', tabs => $tabs, %ARGS );

Modified: rt/branches/rt-3.1/html/Admin/Elements/Header
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Elements/Header	(original)
+++ rt/branches/rt-3.1/html/Admin/Elements/Header	Sat Jul 10 02:05:15 2004
@@ -21,7 +21,7 @@
 %# 
 %# 
 %# END LICENSE BLOCK
-<& /Elements/Header, Title => $Title &>
+<& /Elements/Header, %ARGS &>
 
 <%ARGS>
 $Title => undef

Added: rt/branches/rt-3.1/html/Admin/Elements/ObjectCustomFields
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Admin/Elements/ObjectCustomFields	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,60 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title &>
+<& $ObjectTabs,
+$id ? (
+    id => $Object->id, 
+    current_tab => "Admin/$Types/CustomFields.html?$sub_type_url&id=".$id, 
+    current_subtab => "Admin/$Types/CustomFields.html?$sub_type_url&id=".$id, 
+    "${Type}Obj" => $Object,
+) : (
+    current_tab => "Admin/$Types/CustomFields.html?$sub_type_url",
+    current_subtab => "Admin/$Types/CustomFields.html?$sub_type_url",
+),
+    Title => $title
+    &>
+
+<& /Admin/Elements/EditCustomFields, %ARGS, title => $title, Object => $Object &>
+<%INIT>
+$ObjectType =~ /^RT::(Queue|User|Group)$/
+    or Abort(loc("Object of type [_1] cannot take custom fields", $ObjectType));
+
+my $Type = $1;
+my $Types = $Type.'s';
+my $ObjectTabs = "/Admin/Elements/${Type}Tabs";
+my $Object = $ObjectType->new($session{'CurrentUser'});
+$Object->Load($id) || Abort(loc("Couldn't load object [_1]", $id)) if $id;
+
+my $title = ($id ? loc('Edit Custom Fields for [_1]', $Object->Name)
+		 : loc("Modify Custom Fields which apply to all [_1]", loc($Types)));
+
+my $sub_type_url;
+$sub_type_url = "SubType=$SubType" if $SubType;
+
+</%INIT>
+<%ARGS>
+$id => undef
+$ObjectType
+$SubType => undef
+</%ARGS>

Added: rt/branches/rt-3.1/html/Admin/Elements/PickCustomFields
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Admin/Elements/PickCustomFields	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,74 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+% if (@CustomFields == 0) {
+<p><i><&|/l&>(None)</&></i></p>
+% } else {
+<TABLE cellspacing=0 cellpadding=2>
+% my $count;
+% foreach my $CustomFieldObj (@CustomFields) {
+<TR>
+%   if (!$ReadOnly) {
+  <TD valign="TOP">
+<input type="checkbox"
+name="Object-<%$id%>-CF-<%$CustomFieldObj->Id%>"
+value="1" 
+<% $Checked && 'CHECKED' %>
+>
+  </TD>
+%   }
+  <TD valign="TOP">
+% if ($CustomFieldObj->Name) {
+    <A HREF="/Admin/CustomFields/Modify.html?id=<%$CustomFieldObj->id()%>"><b><%$CustomFieldObj->Name%></b></a><br>
+% } else {
+    <A HREF="/Admin/CustomFields/Modify.html?id=<%$CustomFieldObj->id()%>"><i>(<%loc("no name")%>)</i></a><br>
+% }
+    <%$CustomFieldObj->Description%>
+  </TD>
+  <TD valign="TOP">
+    <i><% $CustomFieldObj->FriendlyTypeComposite %></i>
+  </TD>
+%  # show 'move up' unless it's the first item
+%  if ($count++ and $Checked) {
+  <TD valign="TOP">
+    [<a href="CustomFields.html?id=<%$id%>&CustomField=<%$CustomFieldObj->id%>&Move=-1"><&|/l&>Move up</&></a>]
+%  } else {
+  <TD valign="TOP" ALIGN=RIGHT>
+%  }
+
+%  # show 'move down' unless it's the last item
+%  if ($count != @CustomFields and $Checked) {
+%  $m->print(' | ') if $count > 1;
+    [<a href="CustomFields.html?id=<%$id%>&CustomField=<%$CustomFieldObj->id%>&Move=1"><&|/l&>Move down</&></a>]
+%  }
+  </TD>
+</TR>
+% }
+</TABLE>
+% }
+<%ARGS>
+ at CustomFields
+$id
+$ReadOnly => 0
+$Checked => 0
+</%ARGS>

Added: rt/branches/rt-3.1/html/Admin/Elements/PickObjects
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Admin/Elements/PickObjects	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,61 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+% if (@Objects == 0) {
+<P><i><&|/l&>(None)</&></i></P>
+% } else {
+<TABLE cellspacing=0 cellpadding=2>
+% my $count;
+% foreach my $Object (@Objects) {
+<TR>
+%   if (!$ReadOnly) {
+  <TD valign="TOP">
+<input type="checkbox"
+name="Object-<%$Object->id%>-CF-<%$id%>"
+value="1" 
+<% $Checked && 'CHECKED' %>
+>
+  </TD>
+%   }
+  <TD valign="TOP">
+% if ($Object->Name) {
+    <b><%$Object->Name%></b><br>
+% } else {
+    <i>(<%loc("no name")%>)</i><br>
+% }
+    <%$Object->can('Description') && $Object->Description%>
+  </TD>
+%#  <TD valign="TOP">
+%#    <i><% $CustomFieldObj->FriendlyTypeComposite %></i>
+%#  </TD>
+  </TD>
+</TR>
+% }
+</TABLE>
+% }
+<%ARGS>
+ at Objects
+$id
+$ReadOnly => 0
+$Checked => 0
+</%ARGS>

Modified: rt/branches/rt-3.1/html/Admin/Elements/QueueTabs
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Elements/QueueTabs	(original)
+++ rt/branches/rt-3.1/html/Admin/Elements/QueueTabs	Sat Jul 10 02:05:15 2004
@@ -49,8 +49,12 @@
 				path => "Admin/Queues/Templates.html?id=".$id,
 			      },
 
-                 G => { title => loc('Custom Fields'),
-                        path => 'Admin/Queues/CustomFields.html?id='.$id,
+                 G1 => { title => loc('Ticket Custom Fields'),
+                        path => 'Admin/Queues/CustomFields.html?SubType=RT::Ticket&id='.$id,
+                        },
+
+                 G2 => { title => loc('Transaction Custom Fields'),
+                        path => 'Admin/Queues/CustomFields.html?SubType=RT::Ticket-RT::Transaction&id='.$id,
                         },
 
 		 H => { title => loc('Group Rights'),
@@ -68,8 +72,13 @@
 			   };
   $tabs->{"B"} = { title => loc('New queue'),
   		     	path => "Admin/Queues/Modify.html?Create=1",
-			separator => 1,
 			   };
+  $tabs->{"G1"} = { title => loc('Ticket Custom Fields'),
+  		     	path => 'Admin/Queues/CustomFields.html?SubType=RT::Ticket',
+			   };
+  $tabs->{"G2"} = { title => loc('Transaction Custom Fields'),
+  		     	path => 'Admin/Queues/CustomFields.html?SubType=RT::Ticket-RT::Transaction',
+		 separator => 1, };
 }
 
   # Now let callbacks add their extra tabs

Added: rt/branches/rt-3.1/html/Admin/Elements/SelectCustomFieldLookupType
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Admin/Elements/SelectCustomFieldLookupType	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,36 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+<SELECT NAME ="<%$Name%>">
+%for my $option ($cf->LookupTypes) {
+<OPTION VALUE="<%$option%>" <%$option eq $Default && "SELECTED"%>><% $cf->FriendlyLookupType($option) %></OPTION>
+%}
+</SELECT>
+<%INIT>
+my $cf = RT::CustomField->new($session{'CurrentUser'});
+
+</%INIT>
+<%ARGS>
+$Default=>undef
+$Name => 'LookupType'
+</%ARGS>

Modified: rt/branches/rt-3.1/html/Admin/Elements/SelectCustomFieldType
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Elements/SelectCustomFieldType	(original)
+++ rt/branches/rt-3.1/html/Admin/Elements/SelectCustomFieldType	Sat Jul 10 02:05:15 2004
@@ -22,8 +22,8 @@
 %# 
 %# END LICENSE BLOCK
 <SELECT NAME ="<%$Name%>">
-%for my $option ($cf->Types) {
-<OPTION VALUE="<%$option%>" <%$option eq $Default && "SELECTED"%>><% $cf->FriendlyType($option) %></OPTION>
+%for my $option ($cf->TypeComposites) {
+<OPTION VALUE="<%$option%>" <%$option eq $Default && "SELECTED"%>><% $cf->FriendlyTypeComposite($option) %></OPTION>
 %}
 </SELECT>
 <%INIT>
@@ -32,5 +32,5 @@
 </%INIT>
 <%ARGS>
 $Default=>undef
-$Name => 'Type'
+$Name => 'TypeComposite'
 </%ARGS>

Modified: rt/branches/rt-3.1/html/Admin/Elements/SelectRights
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Elements/SelectRights	(original)
+++ rt/branches/rt-3.1/html/Admin/Elements/SelectRights	Sat Jul 10 02:05:15 2004
@@ -78,7 +78,7 @@
     } 
 
         else {
-                %Rights = { loc('System Error') => loc("No rights found")};
+                %Rights = ( loc('System Error') => loc("No rights found") );
         }
         
     $ACLDesc = "$PrincipalId-".ref($Object)."-".$Object->Id;

Modified: rt/branches/rt-3.1/html/Admin/Elements/SystemTabs
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Elements/SystemTabs	(original)
+++ rt/branches/rt-3.1/html/Admin/Elements/SystemTabs	Sat Jul 10 02:05:15 2004
@@ -36,9 +36,9 @@
                         path => 'Admin/Global/Templates.html',
                       },
               
-                F => { title => loc('Custom Fields'),
-                        path => 'Admin/Global/CustomFields.html',
-                        },
+#                F => { title => loc('Custom Fields'),
+#                        path => 'Admin/Global/CustomFields.html',
+#                        },
 
                 G => { title => loc('Group Rights'),
                                 path => 'Admin/Global/GroupRights.html',

Modified: rt/branches/rt-3.1/html/Admin/Elements/Tabs
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Elements/Tabs	(original)
+++ rt/branches/rt-3.1/html/Admin/Elements/Tabs	Sat Jul 10 02:05:15 2004
@@ -37,10 +37,13 @@
 	       C => { title => loc('Queues'),
 			   path => 'Admin/Queues/',
 			 },
-	       D => { 'title' => loc('Global'),
+	       D => { 'title' => loc('Custom Fields'),
+			   path => 'Admin/CustomFields/',
+			 },
+	       E => { 'title' => loc('Global'),
 			   path => 'Admin/Global/',
 			 },
-	       E => { 'title' => loc('Tools'),
+	       F => { 'title' => loc('Tools'),
 			   path => 'Admin/Tools/',
 			 },
 	     };

Modified: rt/branches/rt-3.1/html/Admin/Elements/UserTabs
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Elements/UserTabs	(original)
+++ rt/branches/rt-3.1/html/Admin/Elements/UserTabs	Sat Jul 10 02:05:15 2004
@@ -21,51 +21,63 @@
 %# 
 %# 
 %# END LICENSE BLOCK
-<& /Admin/Elements/Tabs,
+<& /Admin/Elements/Tabs, 
     subtabs => $tabs,
-    current_tab => 'Admin/Users/',
-    current_subtab => $current_tab,
+    current_tab => 'Admin/Users/', 
+    current_subtab => $current_tab, 
     Title => $Title &>
 <%INIT>
 my $tabs;
 if ($id) {
-	$tabs->{'this'} = {
-		title => eval { $UserObj->Name },
+$tabs->{'this'} = { title => eval { $UserObj->Name },
+
 		path => "Admin/Users/Modify.html?id=".$id,
-		current_subtab => $current_tab,
-		subtabs => {
-			A => { title => loc('Basics'),
+subtabs => {
+	       Basics => { title => loc('Basics'),
 				path => "Admin/Users/Modify.html?id=".$id
 			},
-		}
+	       Memberships => { title => loc('Memberships'),
+			   path => "Admin/Users/Memberships.html?id=".$id
+			 },
+	       History => { title => loc('History'),
+			   path => "Admin/Users/History.html?id=".$id
+			 },
+#	       Scrips => { title => loc('Rights'),
+#			   path => "Admin/Users/Rights.html?id=".$id
+#			 }
+	       
 	}
 }
-
-if ( $session{'CurrentUser'}->HasRight( Object => $RT::System, Right => 'AdminUsers') ) {
-	$tabs->{"A"} = {
-		title => loc('Select user'),
-		path => "Admin/Users/",
+}
+if ($session{'CurrentUser'}->HasRight( Object => $RT::System, Right => 'AdminUsers')) {
+  $tabs->{"A"} = { title => loc('Select user'),
+  		     	path => "Admin/Users/",
+			   };
+  $tabs->{"B"} = { title => loc('New user'),
+  		     	path => "Admin/Users/Modify.html?Create=1",
 	};
-	$tabs->{"B"} = {
-		title => loc('New user'),
-		path => "Admin/Users/Modify.html?Create=1",
+  $tabs->{"C"} = { title => loc('Custom Fields'),
+  		     	path => "Admin/Users/CustomFields.html",
 		separator => 1,
 	};
 }
 
-# Now let callbacks add their extra tabs
-$m->comp('/Elements/Callback', tabs => $tabs, %ARGS);
-
-foreach my $tab ( sort keys %{$tabs->{'this'}->{'subtabs'}} ) {
-	if ( $tabs->{'this'}->{'subtabs'}->{$tab}->{'path'} eq $current_tab ) {
-		$tabs->{'this'}->{"current_subtab"} = $current_tab; 
-		$tabs->{'this'}->{'subtabs'}->{$tab}->{"current_subtab"} = $current_subtab; 
-		$tabs->{'this'}->{'subtabs'}->{$tab}->{"subtabs"} = $subtabs; 
-	}
-}
-
+  # Now let callbacks add their extra tabs
+  $m->comp('/Elements/Callback', tabs => $tabs, %ARGS);
+                                                                                
+#foreach my $tab ( sort keys %{$tabs} ) {                                        
+#    if ( $tabs->{$tab}->{'path'} eq $current_subtab ) {                         
+#        $tabs->{$tab}->{"current_subtab"} = $current_subtab;                    
+#    }                                                                           
+#}                                                                               
+foreach my $tab ( sort keys %{$tabs->{'this'}->{'subtabs'}} ) {  
+    if ( $tabs->{'this'}->{'subtabs'}->{$tab}->{'path'} eq $current_tab ) {
+        $tabs->{'this'}->{'subtabs'}->{$tab}->{"subtabs"}        = $subtabs; 
+        $tabs->{'this'}->{'subtabs'}->{$tab}->{"current_subtab"} = $current_subtab; 
+    }                                                                           
+}   
+$tabs->{'this'}->{"current_subtab"} = $current_tab; 
 $current_tab = "Admin/Users/Modify.html?id=".$id if $id;
-
 </%INIT>
 <%ARGS>
 $UserObj => undef

Modified: rt/branches/rt-3.1/html/Admin/Global/Templates.html
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Global/Templates.html	(original)
+++ rt/branches/rt-3.1/html/Admin/Global/Templates.html	Sat Jul 10 02:05:15 2004
@@ -21,7 +21,7 @@
 %# 
 %# 
 %# END LICENSE BLOCK
-<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/Header, Title => $title, FeedURI => 'templates' &>
 <& /Admin/Elements/SystemTabs, 
     current_tab => 'Admin/Global/Templates.html', 
     current_subtab => 'Admin/Global/Templates.html', 

Modified: rt/branches/rt-3.1/html/Admin/Global/index.html
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Global/index.html	(original)
+++ rt/branches/rt-3.1/html/Admin/Global/index.html	Sat Jul 10 02:05:15 2004
@@ -46,10 +46,10 @@
                         path => 'Templates.html',
                       },
               
-                F => { title => loc('Custom Fields'),
-                        text => loc('Modify Custom Fields which apply to all queues'),
-                        path => 'CustomFields.html',
-                        },
+#                F => { title => loc('Custom Fields'),
+#                        text => loc('Modify Custom Fields which apply to all queues'),
+#                        path => 'CustomFields.html',
+#                        },
 
                 G => { title => loc('Group Rights'),
                                 text => loc('Modify global group rights'),

Added: rt/branches/rt-3.1/html/Admin/Groups/CustomFields.html
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Admin/Groups/CustomFields.html	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,24 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+<& /Admin/Elements/ObjectCustomFields, %ARGS, ObjectType => 'RT::Group' &>

Added: rt/branches/rt-3.1/html/Admin/Groups/History.html
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Admin/Groups/History.html	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,44 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title  &>
+<& /Admin/Elements/GroupTabs, 
+    id => $id, 
+    GroupObj => $GroupObj,
+    current_subtab => $current_tab, 
+    Title => $title &>
+
+<& /Ticket/Elements/ShowHistory,
+    Ticket => $GroupObj,
+    ShowHeaderModes => 0,
+&>
+
+<%INIT>
+my $current_tab = 'Admin/Groups/History.html?id='.$id;
+my $GroupObj = new RT::Group($session{'CurrentUser'});
+$GroupObj->Load($id) || Abort("Couldn't load group '$id'");
+my $title = loc("History of the group [_1]", $GroupObj->Name);
+</%INIT>
+<%ARGS>
+$id => undef
+</%ARGS>

Modified: rt/branches/rt-3.1/html/Admin/Groups/Modify.html
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Groups/Modify.html	(original)
+++ rt/branches/rt-3.1/html/Admin/Groups/Modify.html	Sat Jul 10 02:05:15 2004
@@ -31,7 +31,7 @@
 
 
 
-<FORM ACTION="<%$RT::WebPath%>/Admin/Groups/Modify.html" METHOD=POST>
+<FORM ACTION="<%$RT::WebPath%>/Admin/Groups/Modify.html" METHOD=POST ENCTYPE="multipart/form-data">
 
 %unless ($Group->Id) {
 <INPUT TYPE=HIDDEN NAME=id VALUE="new">
@@ -43,10 +43,20 @@
 <&|/l&>Name</&>:
 </TD>
 <TD><INPUT name="Name" value="<%$Group->Name%>"></TD>
-</TR><TR>
+</TR>
+<TR>
 <TD ALIGN=RIGHT>
 <&|/l&>Description</&>:</TD><TD COLSPAN=3><INPUT name="Description" value="<%$Group->Description%>" size=60></TD>
-</TR><TR>
+</TR>
+% my $CFs = $Group->CustomFields;
+% while (my $CF = $CFs->Next) {
+<TR VALIGN="TOP"><TD ALIGN="RIGHT">
+<% $CF->Name %>:
+</TD><TD>
+<& /Elements/EditCustomField, CustomField => $CF, Object => $Group &>
+</TD></TR>
+% }
+<TR>
 <TD COLSPAN=2>
 <INPUT TYPE=HIDDEN NAME="SetEnabled" VALUE="1">
 <INPUT TYPE=CHECKBOX NAME="Enabled" VALUE="1" <%$EnabledChecked%>> <&|/l&>Enabled (Unchecking this box disables this group)</&><BR>
@@ -102,6 +112,7 @@
 					    Object => $Group,
 					    ARGSRef => \%ARGS );
     push (@results, at fieldresults);
+    push @results, ProcessObjectCustomFieldUpdates( ARGSRef => \%ARGS, Object => $Group );
 }
 
 #we're asking about enabled on the web page but really care about disabled.

Modified: rt/branches/rt-3.1/html/Admin/Groups/index.html
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Groups/index.html	(original)
+++ rt/branches/rt-3.1/html/Admin/Groups/index.html	Sat Jul 10 02:05:15 2004
@@ -25,13 +25,19 @@
 <& /Admin/Elements/GroupTabs, current_tab => 'Admin/Groups/',
     current_subtab => 'Admin/Groups/', 
     Title => $title &>
-
-
-<UL>
+<%$caption%>:<BR> <UL> 
+%if ($Groups->Count == 0) {
+<LI> <i><&|/l&>No groups matching search criteria found.</&></i>
+% }
+%my @ids;
 %while ( my $Group = $Groups->Next) {
+%    push @ids, $Group->Id;
 <LI><A HREF="Modify.html?id=<%$Group->id%>"><%$Group->Name || loc('(empty)')%></a><BR>
 %}
 </UL>
+%if (my $ids = join(',', @ids)) {
+<em>(<a href="/Download/Tabular/Group/<% $ids %>/Groups.tsv"><&|/l&>Download as a tab-delimited file</&></a>)</em><br>
+%}
 <br><br>
 <FORM METHOD=POST ACTION="<% $RT::WebPath %>/Admin/Groups/index.html">
 <input type="checkbox" name="FindDisabledGroups"> <&|/l&>Include disabled groups in listing.</&>
@@ -39,17 +45,41 @@
 <div align=right><input type=submit value="<&|/l&>Go!</&>"></div> 
 </FORM>
 
+<br><br>
+<FORM METHOD=POST ACTION="<% $RT::WebPath %>/Admin/Groups/index.html">
+<&|/l&>Find groups whose</&> <& /Elements/SelectGroups &><BR>
+<div align=right><input type=submit value="<&|/l&>Go!</&>"></div> 
+</FORM>
 <%INIT>
 my $Groups = RT::Groups->new($session{'CurrentUser'});
-
-if ($FindDisabledGroups) {
-  $Groups->{'find_disabled_rows'} = 1;
-}
-
 $Groups->LimitToUserDefinedGroups();
 my $title = loc('Select a group');
+my $caption;
 
+if (length $GroupString) {
+    $caption = loc("Groups matching search criteria");
+    if ($GroupField =~ /^CustomField-(\d+)/) {
+	$Groups->LimitCustomField(
+	    CUSTOMFIELD => $1,
+	    OPERATOR => $GroupOp,
+	    VALUE => $GroupString,
+	); 
+    }
+    else {
+	$Groups->Limit(
+	    FIELD => $GroupField,
+	    OPERATOR => $GroupOp,
+	    VALUE => $GroupString,
+	); 
+    }
+}
+else {
+    $caption = loc("User-defined groups");
+}
 </%INIT>
 <%ARGS>
+$GroupString => undef
+$GroupOp => '='
+$GroupField => 'Name'
 $FindDisabledGroups => 0
 </%ARGS>

Modified: rt/branches/rt-3.1/html/Admin/Queues/CustomField.html
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Queues/CustomField.html	(original)
+++ rt/branches/rt-3.1/html/Admin/Queues/CustomField.html	Sat Jul 10 02:05:15 2004
@@ -56,5 +56,5 @@
 </%INIT>
 <%ARGS>
 $CustomField => undef
-$Queue => undef
+$Queue => 0
 </%ARGS>

Modified: rt/branches/rt-3.1/html/Admin/Queues/CustomFields.html
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Queues/CustomFields.html	(original)
+++ rt/branches/rt-3.1/html/Admin/Queues/CustomFields.html	Sat Jul 10 02:05:15 2004
@@ -21,29 +21,4 @@
 %# 
 %# 
 %# END LICENSE BLOCK
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/QueueTabs, id => $Queue->id, 
-    current_tab => 'Admin/Queues/CustomFields.html?id='.$id, 
-    QueueObj => $Queue,                                                      
-    subtabs => $subtabs,
-    Title => $title
-    &>
-
-<& /Admin/Elements/EditCustomFields, title => $title, %ARGS &>
-<%INIT>
-my $Queue = new RT::Queue($session{'CurrentUser'});
-$Queue->Load($id) || Abort(loc("Couldn't load queue", $id));
-
-my $CustomFields = RT::CustomFields->new($RT::SystemUser);
-$CustomFields->LimitToQueue($Queue->Id);
-my $subtabs = {
-	 A => { title => loc('New custom field'),
-	     	path => "Admin/Queues/CustomField.html?create=1&Queue=".$id,
-			   }
-	      };
-
-my $title=  loc('Edit Custom Fields for [_1]', $Queue->Name);
-</%INIT>
-<%ARGS>
-$id => undef
-</%ARGS>
+<& /Admin/Elements/ObjectCustomFields, %ARGS, ObjectType => 'RT::Queue' &>

Modified: rt/branches/rt-3.1/html/Admin/Queues/Modify.html
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Queues/Modify.html	(original)
+++ rt/branches/rt-3.1/html/Admin/Queues/Modify.html	Sat Jul 10 02:05:15 2004
@@ -137,7 +137,7 @@
   }
   if  ( ($SetEnabled) and ( $Disabled != $QueueObj->Disabled) ) { 
       my  ($code, $msg) = $QueueObj->SetDisabled($Disabled);
-      push @results, loc('Enabled status [_1]', loc_fuzzy($msg));
+      push @results, loc('Enabled status: [_1]', loc_fuzzy($msg));
   }
   
   if ($QueueObj->Disabled()) {

Modified: rt/branches/rt-3.1/html/Admin/Queues/People.html
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Queues/People.html	(original)
+++ rt/branches/rt-3.1/html/Admin/Queues/People.html	Sat Jul 10 02:05:15 2004
@@ -57,7 +57,7 @@
 <& /Elements/SelectUsers &>
 <input type=submit name="OnlySearchForPeople" value="<&|/l&>Go!</&>">
 <BR>
-<&|/l&>Find group whose</&><BR>
+<&|/l&>Find groups whose</&><BR>
 <& /Elements/SelectGroups &>
 <input type=submit name="OnlySearchForGroup" value="<&|/l&>Go!</&>">
 

Added: rt/branches/rt-3.1/html/Admin/Users/CustomFields.html
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Admin/Users/CustomFields.html	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,24 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+<& /Admin/Elements/ObjectCustomFields, %ARGS, ObjectType => 'RT::User' &>

Added: rt/branches/rt-3.1/html/Admin/Users/History.html
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Admin/Users/History.html	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,44 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title  &>
+<& /Admin/Elements/UserTabs, 
+    id => $id, 
+    UserObj => $UserObj,
+    current_tab => $current_tab, 
+    Title => $title &>
+
+<& /Ticket/Elements/ShowHistory,
+    Ticket => $UserObj,
+    ShowHeaderModes => 0,
+&>
+
+<%INIT>
+my $current_tab = 'Admin/Users/History.html?id='.$id;
+my $UserObj = new RT::User($session{'CurrentUser'});
+$UserObj->Load($id) || Abort("Couldn't load user '$id'");
+my $title = loc("History of the user [_1]", $UserObj->Name);
+</%INIT>
+<%ARGS>
+$id => undef
+</%ARGS>

Added: rt/branches/rt-3.1/html/Admin/Users/Memberships.html
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Admin/Users/Memberships.html	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,43 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title  &>
+<& /Admin/Elements/UserTabs, 
+    id => $id, 
+    UserObj => $UserObj,
+    current_tab => $current_tab, 
+    Title => $title &>
+
+<h2><&|/l&>Groups</&></h2>
+
+<& /Elements/ShowMemberships, UserObj => $UserObj &>
+
+<%INIT>
+my $UserObj = RT::User->new($session{'CurrentUser'});
+$UserObj->Load($id) || Abort("Couldn't load user '$id'");
+my $title = loc("Memberships of the user [_1]", $UserObj->Name);
+my $current_tab = 'Admin/Users/Memberships.html?id='.$id;
+</%INIT>
+<%ARGS>
+$id => undef
+</%ARGS>

Modified: rt/branches/rt-3.1/html/Admin/Users/Modify.html
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Users/Modify.html	(original)
+++ rt/branches/rt-3.1/html/Admin/Users/Modify.html	Sat Jul 10 02:05:15 2004
@@ -30,7 +30,7 @@
 
 <& /Elements/ListActions, actions => \@results &>
 
-<FORM ACTION="<%$RT::WebPath%>/Admin/Users/Modify.html" METHOD=POST>
+<FORM ACTION="<%$RT::WebPath%>/Admin/Users/Modify.html" METHOD=POST ENCTYPE="multipart/form-data">
 %if ($Create) {
 <INPUT TYPE=HIDDEN NAME=id VALUE="new">
 % } else {
@@ -69,6 +69,11 @@
 <input name="Gecos" value="<%$UserObj->Gecos%>">
 </TD></TR>
 <TR><TD ALIGN="RIGHT">
+<&|/l&>Language</&>: 
+</TD><TD>
+<& /Elements/SelectLang, Name => 'Lang', Default => $UserObj->Lang &>
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
 <&|/l&>Extra info</&>: 
 </TD><TD>
 <textarea name="FreeformContactInfo" cols=20 rows=5><%$UserObj->FreeformContactInfo%></TEXTAREA>
@@ -177,6 +182,21 @@
 <TR>
 </TR>
 </TABLE>
+<BR>
+<& /Elements/TitleBoxStart, title => loc('Custom Fields') &>
+<TABLE>
+% my $CFs = $UserObj->CustomFields;
+% while (my $CF = $CFs->Next) {
+<TR VALIGN="TOP"><TD ALIGN="RIGHT">
+<% $CF->Name %>:
+</TD><TD>
+<& /Elements/EditCustomField, %ARGS, Object => $UserObj, CustomField => $CF &>
+</TD></TR>
+% }
+<TR>
+</TR>
+</TABLE>
+<& /Elements/TitleBoxEnd &>
 <TR>
 <TD colspan="2">
 <& /Elements/TitleBoxStart, title => loc('Comments about this user') &>
@@ -288,6 +308,7 @@
 					    Object => $UserObj,
 					    ARGSRef => \%ARGS );
     push (@results, at fieldresults);
+    push @results, ProcessObjectCustomFieldUpdates( ARGSRef => \%ARGS, Object => $UserObj );
 
 
 # {{{ Deal with special fields: Privileged, Enabled and Password

Modified: rt/branches/rt-3.1/html/Admin/Users/index.html
==============================================================================
--- rt/branches/rt-3.1/html/Admin/Users/index.html	(original)
+++ rt/branches/rt-3.1/html/Admin/Users/index.html	Sat Jul 10 02:05:15 2004
@@ -34,11 +34,16 @@
 %if ($users->Count == 0) {
 <LI> <i><&|/l&>No users matching search criteria found.</&></i>
 % }
+%my @ids;
 %while ( $user = $users->Next) {
+%    push @ids, $user->Id;
 <LI><A HREF="Modify.html?id=<%$user->id%>"><%$user->Name || loc('(no name listed)')%></a></LI>
 %}
-
 </UL>
+%if (my $ids = join(',', @ids)) {
+<em>(<a href="/Download/Tabular/User/<% $ids %>/Users.tsv"><&|/l&>Download as a tab-delimited file</&></a>)</em><br>
+%}
+
 <br><br>
 <FORM METHOD=POST ACTION="<% $RT::WebPath %>/Admin/Users/index.html">
 
@@ -56,19 +61,26 @@
 	$users->{'find_disabled_rows'} = 1;
 }
 
-unless (defined $UserString) {
-    $users->LimitToPrivileged();
-    $caption = loc("Privileged users");
-}
-else {
+if (length $UserString) {
     $caption = loc("Users matching search criteria");
-
-  if ($UserString) {
-	$users->Limit( FIELD => $UserField,
-		        OPERATOR => $UserOp,
-		       VALUE => $UserString); 
-
+    if ($UserField =~ /^CustomField-(\d+)/) {
+	$users->LimitCustomField(
+	    CUSTOMFIELD => $1,
+	    OPERATOR => $UserOp,
+	    VALUE => $UserString,
+	); 
+    }
+    else {
+	$users->Limit(
+	    FIELD => $UserField,
+	    OPERATOR => $UserOp,
+	    VALUE => $UserString,
+	); 
+    }
 }
+else {
+    $caption = loc("Privileged users");
+    $users->LimitToPrivileged;
 }
 </%INIT>
 <%ARGS>

Modified: rt/branches/rt-3.1/html/Admin/index.html
==============================================================================
--- rt/branches/rt-3.1/html/Admin/index.html	(original)
+++ rt/branches/rt-3.1/html/Admin/index.html	Sat Jul 10 02:05:15 2004
@@ -34,6 +34,9 @@
 <li><font size="+2"><a href="Queues/"><&|/l&>Queues</&></a></font><br>
 <&|/l&>Manage queues and queue-specific properties</&>
 </li>
+<li><font size="+2"><a href="CustomFields/"><&|/l&>Custom Fields</&></a></font><br>
+<&|/l&>Manage custom fields and custom field values</&>
+</li>
 <li><font size="+2"><a href="Global/"><&|/l&>Global</&></a></font><br>
 <&|/l&>Manage properties and configuration which apply to all queues</&>
 </li>

Added: rt/branches/rt-3.1/html/Download/CustomFieldValue/dhandler
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Download/CustomFieldValue/dhandler	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,53 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+<%perl>
+my $id;
+my $arg = $m->dhandler_arg;                # get rest of path
+if ($arg =~ /^(\d+)\//) {
+    $id = $1;
+}
+else {
+    Abort("Corrupted customfieldvalue URL.");
+}
+my $OCFV = RT::ObjectCustomFieldValue->new($session{'CurrentUser'});
+$OCFV->Load($id) || Abort("OCFV '$id' could not be loaded");
+
+unless ($OCFV->id) {
+    Abort("Bad OCFV id. Couldn't find OCFV '$id'\n");
+}
+
+my $content_type = $OCFV->ContentType || 'text/plain';
+    
+unless ($RT::TrustHTMLAttachments) {
+    $content_type = 'text/plain' if ($content_type =~ /^text\/html/i);
+}
+
+$r->content_type( $content_type );
+$m->clear_buffer();
+$m->out($OCFV->LargeContent);
+$m->abort; 
+</%perl>
+<%attr>
+AutoFlush => 0
+</%attr>

Added: rt/branches/rt-3.1/html/Download/Tabular/dhandler
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Download/Tabular/dhandler	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,52 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+<%perl>
+my ($class, $filename, @ids);
+my $arg = $m->dhandler_arg;                # get rest of path
+if ($arg =~ /^(\w+)\/([,\d]+)(?:\/([^\/]+))?/) {
+    $class = "RT::$1";
+    $filename = $3 || "$1s.tsv";
+    @ids = sort split(/,+/, $2);
+}
+else {
+    Abort("Corrupted tabular URL.");
+}
+
+my @cols = $class->BasicColumns or return;
+
+#$r->content_type( 'application/octet-stream' );
+$r->content_type( 'text/plain' );
+$r->header_out( 'Content-Disposition' => "attachment; filename=$filename" );
+$m->clear_buffer();
+$m->out(join("\t", "Id", map $_->[1], @cols), "\n");
+foreach my $id (@ids) {
+    my $obj = $class->new;
+    $obj->Load($id) or next;
+    $m->out(join("\t", map $obj->$_, "Id", map $_->[0], @cols), "\n");
+}
+$m->abort; 
+</%perl>
+<%attr>
+AutoFlush => 0
+</%attr>

Added: rt/branches/rt-3.1/html/Elements/EditCustomField
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Elements/EditCustomField	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,97 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+% if ($CustomField->Type eq 'FreeformSingle') {
+      <input name="<%$NamePrefix%><%$CustomField->Id%>-Value"
+        size="<%$Cols%>"
+% if ($Object) {
+          value="<%$Values->Count ? $Values->First->Content : ''%>"
+% } elsif ($Default) {
+          value="<%$Default ? $Default : ''%>"
+% }
+>
+% } elsif ($CustomField->Type eq 'FreeformMultiple') {
+% my $content;
+% if ($Object) {
+%          while (my $value = $Values->Next ) {
+%                 $content .= $value->Content;
+%           }
+% } elsif ($Default) {
+          value="<%$Default ? $Default : ''%>"
+%  }
+<input type="hidden" name="<%$NamePrefix%><%$CustomField->Id%>-Values-Magic" value="1">
+<textarea cols=<%$Cols%> rows=<%$Rows%> name="<%$NamePrefix%><%$CustomField->Id%>-Values"><%$content%></textarea>
+% } elsif ($CustomField->Type =~ /^Select/) {
+      <input type="hidden" name="<%$NamePrefix%><%$CustomField->Id%>-Values-Magic" value="1">
+      <select name="<%$NamePrefix%><%$CustomField->Id%>-Values"
+        size="<%$Rows%>"
+        <%$CustomField->Type eq 'SelectMultiple' && 'MULTIPLE'%>>
+% my $CustomFieldValues = $CustomField->Values();
+% my $selected;
+% while (my $value = $CustomFieldValues->Next) {
+        <option value="<% $value->Name %>" 
+% if ($Object) {
+            <% $Values->HasEntry($value->Name) && ($selected = 1) && 'SELECTED' %>
+% } elsif ($Default) {
+            <% ($Default eq $value->Name) && ($selected = 1) && 'SELECTED' %>
+% }
+            ><% $value->Name%></option>
+% }
+        <option value="" <% !$selected && 'SELECTED' %>><&|/l&>(no value)</&></option>
+      </select>
+% }
+<%INIT>
+my $Values;
+if ($Object) {
+    $Values = $Object->CustomFieldValues($CustomField->id);
+    $Values->Columns( qw( id CustomField ObjectType ObjectId Current Content ContentType ContentEncoding ) );
+    $NamePrefix ||= join('-', 'Object', ref($Object), $Object->Id, 'CustomField', '');
+}
+my $Type = $CustomField->Type;
+my $MaxValues = $CustomField->MaxValues;
+if ($MaxValues == 1 and $Object and $Values) {
+    $Default = ($Values->First ? $Values->First->Content : '');
+    $Values->GotoFirstItem;
+}
+return $m->comp(
+    "EditCustomField$Type",
+    %ARGS,
+    Rows => $Rows,
+    Cols => $Cols,
+    Default => $Default,
+    Object => $Object,
+    Values => $Values,
+    MaxValues => $MaxValues,
+    Multiple => ($MaxValues != 1),
+    NamePrefix => $NamePrefix,
+    CustomField => $CustomField,
+);
+</%INIT>
+<%ARGS>
+$Object => undef
+$CustomField => undef
+$NamePrefix => undef
+$Rows => 5
+$Cols => 15
+$Default => undef
+</%ARGS>

Added: rt/branches/rt-3.1/html/Elements/EditCustomFieldBinary
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Elements/EditCustomFieldBinary	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,38 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+% while ($Values and my $value = $Values->Next ) {
+%# XXX - let user download the file(s) here?
+<input type="checkbox" name="<%$NamePrefix%><%$CustomField->Id%>-DeleteValueIds" value="<% $value->Id %>"><a href="<%$RT::WebPath%>/Download/CustomFieldValue/<% $value->Id %>/<% $value->Content %>"><% $value->Content %></a><br>
+% }
+% if (!$MaxValues or !$Values or $Values->Count < $MaxValues) {
+<input type="file" name="<%$NamePrefix%><%$CustomField->Id%>-Upload">
+% }
+<%ARGS>
+$Object => undef
+$CustomField => undef
+$NamePrefix => undef
+$Default => undef
+$Values => undef
+$MaxValues => undef
+</%ARGS>

Added: rt/branches/rt-3.1/html/Elements/EditCustomFieldFreeform
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Elements/EditCustomFieldFreeform	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,47 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+% if ($Multiple) {
+<input type="hidden" name="<%$NamePrefix%><%$CustomField->Id%>-Values-Magic" value="1">
+<textarea cols=<%$Cols%> rows=<%$Rows%> name="<%$NamePrefix%><%$CustomField->Id%>-Values"><%$Default%></textarea>
+% } else {
+<input name="<%$NamePrefix%><%$CustomField->Id%>-Value" size="<%$Cols%>" value="<%$Default ? $Default : ''%>">
+% }
+<%INIT>
+if ($Multiple and $Values) {
+    $Default = '';
+    while (my $value = $Values->Next ) {
+	$Default .= $value->Content;
+    }
+}
+</%INIT>
+<%ARGS>
+$Object => undef
+$CustomField => undef
+$NamePrefix => undef
+$Default => undef
+$Values => undef
+$Multiple => undef
+$Cols
+$Rows
+</%ARGS>

Added: rt/branches/rt-3.1/html/Elements/EditCustomFieldImage
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Elements/EditCustomFieldImage	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,38 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+% while ($Values and my $value = $Values->Next ) {
+<input type="checkbox" name="<%$NamePrefix%><%$CustomField->Id%>-DeleteValueIds" value="<% $value->Id %>"><& ShowCustomFieldImage, Object => $value &>
+<br>
+% }
+% if (!$MaxValues or !$Values or $Values->Count < $MaxValues) {
+<input type="file" name="<%$NamePrefix%><%$CustomField->Id%>-Upload">
+% }
+<%ARGS>
+$Object => undef
+$CustomField => undef
+$NamePrefix => undef
+$Default => undef
+$Values => undef
+$MaxValues => undef
+</%ARGS>

Added: rt/branches/rt-3.1/html/Elements/EditCustomFieldSelect
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Elements/EditCustomFieldSelect	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,50 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+      <input type="hidden" name="<%$NamePrefix%><%$CustomField->Id%>-Values-Magic" value="1">
+      <select name="<%$NamePrefix%><%$CustomField->Id%>-Values"
+        size="<%$Rows%>"
+        <% $Multiple && 'MULTIPLE' %>>
+% my $selected;
+% my $CFVs = $CustomField->Values;
+% while ($CFVs and my $value = $CFVs->Next ) {
+        <option value="<%$value->Name%>" 
+% if ($Values) {
+            <% $Values->HasEntry($value->Name) && ($selected = 1) && 'SELECTED' %>
+% } elsif ($Default) {
+            <% ($Default eq $value->Name) && ($selected = 1) && 'SELECTED' %>
+% }
+            ><% $value->Name%></option>
+% }
+        <option value="" <% !$selected && 'SELECTED' %>><&|/l&>(no value)</&></option>
+      </select>
+<%ARGS>
+$Object => undef
+$CustomField => undef
+$NamePrefix => undef
+$Default => undef
+$Values => undef
+$Multiple => 0
+$Cols
+$Rows
+</%ARGS>

Added: rt/branches/rt-3.1/html/Elements/EditCustomFieldText
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Elements/EditCustomFieldText	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,44 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+% while ($Values and my $value = $Values->Next ) {
+<textarea cols=<%$Cols%> rows=<%$Rows%> name="<%$NamePrefix%><%$CustomField->Id%>-Values"><% $value->Content %></textarea><br>
+% }
+% if (!$MaxValues or !$Values or $Values->Count < $MaxValues) {
+<input type="hidden" name="<%$NamePrefix%><%$CustomField->Id%>-Values-Magic" value="1">
+<textarea cols=<%$Cols%> rows=<%$Rows%> name="<%$NamePrefix%><%$CustomField->Id%>-Values"></textarea>
+% }
+<%INIT>
+# XXX - MultiValue textarea is for now outlawed.
+$MaxValues = 1;
+</%INIT>
+<%ARGS>
+$Object => undef
+$CustomField => undef
+$NamePrefix => undef
+$Default => undef
+$Values => undef
+$MaxValues => undef
+$Cols
+$Rows
+</%ARGS>

Modified: rt/branches/rt-3.1/html/Elements/Header
==============================================================================
--- rt/branches/rt-3.1/html/Elements/Header	(original)
+++ rt/branches/rt-3.1/html/Elements/Header	Sat Jul 10 02:05:15 2004
@@ -31,6 +31,11 @@
 
 <link rel="shortcut icon" href="<%$RT::WebImagesURL%>/favicon.png" type="image/png">
 <link rel="stylesheet" href="<%$RT::WebPath%>/NoAuth/webrt.css" type="text/css">
+<link rel="" href="<%$RT::WebImagesURL%>/favicon.png" type="image/png">
+% foreach my $service (qw( Feed Post Edit )) {
+%     my $uri = $ARGS{$service."URI"} or next;
+<link rel="service.<% lc($service) %>" type="application/x.atom+xml" href="<% $RT::WebPath %>/REST/2.0/<% $uri %>" title="Atom <% $service %>" />
+% }
 <script>
 function hideshow(num) {
     idstring = "element-" + num;
@@ -75,8 +80,8 @@
 </table>
 <%INIT>
 
-$r->header_out('Pragma' => 'no-cache');
-$r->header_out('Cache-control' => 'no-cache');
+$r->headers_out->{'Pragma'} = 'no-cache';
+$r->headers_out->{'Cache-control'} = 'no-cache';
 </%INIT>
 
 <%ARGS>

Modified: rt/branches/rt-3.1/html/Elements/SelectGroups
==============================================================================
--- rt/branches/rt-3.1/html/Elements/SelectGroups	(original)
+++ rt/branches/rt-3.1/html/Elements/SelectGroups	Sat Jul 10 02:05:15 2004
@@ -22,8 +22,17 @@
 %# 
 %# END LICENSE BLOCK
 <select name="GroupField">
-<option value="Name"><&|/l&>Name</&>
-<option value="Description"><&|/l&>Description</&>
+% foreach my $col (RT::Group->BasicColumns) {
+<option value="<% $col->[0] %>"><% loc($col->[1]) %>
+% }
+% while (my $CF = $CFs->Next) {
+<option value="CustomField-<% $CF->Id %>"><&|/l&>CustomField</&>: <% $CF->Name %>
+% }
 </select>
 <& /Elements/SelectMatch, Name=> 'GroupOp' &>
 <input size=8 name="GroupString">
+<%INIT>
+my $CFs = RT::CustomFields->new($session{'CurrentUser'});
+$CFs->LimitToChildType('RT::Group');
+$CFs->OrderBy( FIELD => 'Name' );
+</%INIT>

Modified: rt/branches/rt-3.1/html/Elements/SelectUsers
==============================================================================
--- rt/branches/rt-3.1/html/Elements/SelectUsers	(original)
+++ rt/branches/rt-3.1/html/Elements/SelectUsers	Sat Jul 10 02:05:15 2004
@@ -22,10 +22,17 @@
 %# 
 %# END LICENSE BLOCK
 <select name="UserField">
-<option value="Name"><&|/l&>User Id</&>
-<option value="EmailAddress"><&|/l&>Email</&>
-<option value="RealName"><&|/l&>Name</&>
-<option value="Organization"><&|/l&>Organization</&>
+% foreach my $col (RT::User->BasicColumns) {
+<option value="<% $col->[0] %>"><% loc($col->[1]) %>
+% }
+% while (my $CF = $CFs->Next) {
+<option value="CustomField-<% $CF->Id %>"><&|/l&>CustomField</&>: <% $CF->Name %>
+% }
 </select>
 <& /Elements/SelectMatch, Name=> 'UserOp' &>
 <input size=8 name="UserString">
+<%INIT>
+my $CFs = RT::CustomFields->new($session{'CurrentUser'});
+$CFs->LimitToChildType('RT::User');
+$CFs->OrderBy( FIELD => 'Name' );
+</%INIT>

Modified: rt/branches/rt-3.1/html/Elements/SetupSessionCookie
==============================================================================
--- rt/branches/rt-3.1/html/Elements/SetupSessionCookie	(original)
+++ rt/branches/rt-3.1/html/Elements/SetupSessionCookie	Sat Jul 10 02:05:15 2004
@@ -74,7 +74,7 @@
             -value => $session{_session_id},
             -path  => '/',
         );
-        $r->header_out('Set-Cookie', $cookie->as_string);
+        $r->headers_out->{'Set-Cookie'} = $cookie->as_string;
 
     } 
 

Added: rt/branches/rt-3.1/html/Elements/ShowCustomFieldImage
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Elements/ShowCustomFieldImage	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,6 @@
+%    my $url = $RT::WebPath . "/Download/CustomFieldValue/".$Object->Id.'/'.$Object->Content;
+<a href="<% $url %>"><% $Object->Content %></a>
+<img type="<% $Object->ContentType %>" height=64 src="<% $url %>" align="middle">
+<%ARGS>
+$Object
+</%ARGS>

Added: rt/branches/rt-3.1/html/Elements/ShowCustomFields
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Elements/ShowCustomFields	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,50 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+<table>
+% my @entry_fields;
+% while (my $CustomField = $CustomFields->Next()) {
+% my $Values = $Object->CustomFieldValues($CustomField->Id);
+  <tr>
+    <td class="label"><%$CustomField->Name%>:</td>
+    <td class="value">
+% while (my $Value = $Values->Next()) {
+%   if ($CustomField->Type eq 'Image') {
+<& ShowCustomFieldImage, Object => $Value &>
+%   } else {
+<%$Value->Content%><br>
+%   }
+% }
+% unless ($Values->Count()) {
+<i><&|/l&>(no value)</&></i>
+% }
+    </td>
+  </tr>
+% }
+</table>
+<%INIT>
+my $CustomFields = $Object->CustomFields;
+</%INIT>
+<%ARGS>
+$Object => undef
+</%ARGS>

Added: rt/branches/rt-3.1/html/Elements/ShowMemberships
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Elements/ShowMemberships	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,64 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+<ul>
+% while ( my $GroupMember = $GroupMembers->Next ) {
+%    my $Group = RT::Group->new($session{'CurrentUser'});
+%    $Group->Load($GroupMember->GroupId) or next;
+%    if ($Group->Domain eq 'UserDefined') {
+<li><a href="/Admin/Groups/Modify.html?id=<% $Group->Id %>"><% $Group->Name %></a></li>
+%    } elsif ($Group->Domain eq 'SystemInternal') {
+<li><em><% loc($Group->Type) %></em></li>
+%    }
+% }
+</ul>
+<%INIT>
+my $GroupMembers = RT::GroupMembers->new($session{'CurrentUser'});
+$GroupMembers->Limit( FIELD => 'MemberId', VALUE => $UserObj->Id );
+my $alias = $GroupMembers->Join(
+    TYPE       => 'left',
+    ALIAS1     => 'main',
+    FIELD1     => 'GroupId',
+    TABLE2     => 'Groups',
+    FIELD2     => 'id'
+);
+$GroupMembers->Limit(
+    ALIAS      => $alias,
+    FIELD      => 'Domain',
+    OPERATOR   => '=',
+    VALUE      => 'SystemInternal',
+);
+$GroupMembers->Limit(
+    ALIAS      => $alias,
+    FIELD      => 'Domain',
+    OPERATOR   => '=',
+    VALUE      => 'UserDefined',
+);
+$GroupMembers->OrderByCols(
+    { ALIAS => $alias, FIELD => 'Domain' },
+    { ALIAS => $alias, FIELD => 'Name' },
+);
+</%INIT>
+<%ARGS>
+$UserObj
+</%ARGS>

Modified: rt/branches/rt-3.1/html/Elements/Submit
==============================================================================
--- rt/branches/rt-3.1/html/Elements/Submit	(original)
+++ rt/branches/rt-3.1/html/Elements/Submit	Sat Jul 10 02:05:15 2004
@@ -21,15 +21,32 @@
 %# 
 %# 
 %# END LICENSE BLOCK
+% if ($CheckAll or $ClearAll) {
+<script><!--
+function set_checkbox (obj, val) {
+    var i;
+    var myfield = obj.form.getElementsByTagName('input');
+    for (i = 0; i < myfield.length; i++) {
+	if (myfield[i].type == 'checkbox') {
+	    myfield[i].checked = val;
+	}
+    }
+}
+--></script>
+% }
 <TABLE WIDTH=100% BGCOLOR="<%$color%>" CELLSPACING=0 BORDER=0 CELLPADDING=0 >
 <TR>
-% if ($Reset) {
 <TD>
-<FONT COLOR=#ffd800 >
+% if ($CheckAll) {
+<INPUT TYPE=BUTTON VALUE="<%$CheckAllLabel%>" ONCLICK="set_checkbox(this, true)">
+% }
+% if ($ClearAll) {
+<INPUT TYPE=BUTTON VALUE="<%$ClearAllLabel%>" ONCLICK="set_checkbox(this, false)">
+% }
+% if ($Reset) {
 <INPUT TYPE=RESET VALUE="<%$ResetLabel%>">
-</FONT>
-</TD>
 %}
+</TD>
 <TD>
 &nbsp;
 </TD>
@@ -57,6 +74,10 @@
 $AlternateLabel => undef
 $Label => loc('Submit')
 $Name => undef
+$CheckAll => undef
+$CheckAllLabel => loc('Check All')
+$ClearAll => undef
+$ClearAllLabel => loc('Clear All')
 $Reset => undef
 $ResetLabel => loc('Reset')
 </%ARGS>

Modified: rt/branches/rt-3.1/html/NoAuth/webrt.css
==============================================================================
--- rt/branches/rt-3.1/html/NoAuth/webrt.css	(original)
+++ rt/branches/rt-3.1/html/NoAuth/webrt.css	Sat Jul 10 02:05:15 2004
@@ -359,5 +359,5 @@
 </%flags>
 <%init>
 $r->content_type('text/css');
-$r->header_out('Expires' ,'+30m');
+$r->headers_out->{'Expires'} = '+30m';
 </%init>

Modified: rt/branches/rt-3.1/html/Search/Bulk.html
==============================================================================
--- rt/branches/rt-3.1/html/Search/Bulk.html	(original)
+++ rt/branches/rt-3.1/html/Search/Bulk.html	Sat Jul 10 02:05:15 2004
@@ -131,6 +131,12 @@
 </select> 
 </td></tr>
 <tr><td align=right><&|/l&>Subject</&>:</td><td> <input name="UpdateSubject" size=60 value=""></td></tr>
+% while (my $CF = $TxnCFs->Next()) {
+<TR>
+<TD ALIGN=RIGHT><% $CF->Name %>:</TD>
+<TD><& /Elements/EditCustomField, CustomField => $CF, NamePrefix => "TransactionCustomField-" &><em><% $CF->FriendlyType %></em></TD>
+</TD></TR>
+% } # end if while
  <tr><td align=right><&|/l&>Attach</&>:</td><td><input name="UpdateAttachment" type="file"></td></tr>
  <tr><td class=labeltop><&|/l&>Message</&>:</td><td>
  <& /Elements/MessageBox, Name=>"UpdateContent"&>
@@ -178,7 +184,10 @@
 my @linkresults;
 
 $Tickets->RedoSearch();
+
+my %queues;
 while (my $Ticket = $Tickets->Next) {
+    $queues{$Ticket->Queue}++;
     $RT::Logger->debug( "Checking Ticket ".$Ticket->Id ."\n");
     next unless ($ARGS{"UpdateTicket".$Ticket->Id});
     $RT::Logger->debug ("Matched\n");
@@ -218,4 +227,8 @@
     @results = (@results, @tempresults);
 }
 
+my $TxnCFs = RT::CustomFields->new($session{CurrentUser});
+$TxnCFs->LimitToLookupType("RT::Queue-RT::Ticket-RT::Transaction");
+$TxnCFs->LimitToGlobalOrObjectId(sort keys %queues);
+
 </%INIT>

Modified: rt/branches/rt-3.1/html/Search/Elements/PickBasics
==============================================================================
--- rt/branches/rt-3.1/html/Search/Elements/PickBasics	(original)
+++ rt/branches/rt-3.1/html/Search/Elements/PickBasics	Sat Jul 10 02:05:15 2004
@@ -128,15 +128,26 @@
 <& /Elements/SelectMatch, Name => "WatcherOp" &>
 </td><td>
 <Input Name="ValueOfWatcher" Size=20>
+% } elsif ($field eq 'WatcherGroup') {
+<& SelectPersonType, Name => 'WatcherGroupField', Default => 'RequestorGroup', Suffix => 'Group' &>
+</td><td>
+<& /Elements/SelectBoolean, Name => "WatcherGroupOp", 
+					  True=> 'belongs to', 
+					  False=> 'does not belong to', 
+					  TrueVal=> '=', 
+					  FalseVal => '!='
+&>
+</td><td>
+<& SelectGroup, Name => 'ValueOfWatcherGroup' &>
 % } else {
-<&|/l&><%$field%></&>
+<% loc($field) %>
 <& /Elements/SelectMatch, Name => "$field" . "Op" &>
 <INPUT Name="<%"ValueOf" . $field%>" value=""SIZE=20>
 % }
 </td></tr>
 % }
 % } else {
-<&|/l&><%$field%></&>
+<% loc($field) %>
 </td><td>
 <& /Elements/SelectMatch, Name => "$field" . "Op" &>
 </td><td>
@@ -160,5 +171,6 @@
 
 my @people = ('Actor',
 	      'Watcher',
+	      'WatcherGroup',
 	      );
 </%INIT>

Modified: rt/branches/rt-3.1/html/Search/Elements/PickCFs
==============================================================================
--- rt/branches/rt-3.1/html/Search/Elements/PickCFs	(original)
+++ rt/branches/rt-3.1/html/Search/Elements/PickCFs	Sat Jul 10 02:05:15 2004
@@ -24,14 +24,7 @@
 <table cellspacing=0 border=0>
 % while ( my $CustomField = $CustomFields->Next ) {
 <tr><td>
-
-% my $name;
-% if ($CustomField->QueueObj->id) {
-%   $name = "'CF." . $CustomField->QueueObj->Name . 
-%	".{" . $CustomField->Name . "}'";
-% } else {
-%   $name = "'CF." . $CustomField->Name . "'";
-% }
+% my $name = "'CF." . $CustomField->Name . "'";
 <% $CustomField->Name %> 
         <& /Elements/SelectCustomFieldOperator, Name => $name . "Op", 
                                     True => loc("is"), 
@@ -47,10 +40,9 @@
 
 <%INIT>
 my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'});
-foreach (keys %cfqueues) {
-    my $id = $_;
+foreach my $id (keys %cfqueues) {
     $id =~ s/^.'*(.*).'*$/$1/;
-    # Gotta load up the $queue object, since queues get stored by name now.
+    # Gotta load up the $queue object, since queues get stored by name now. my $id
     my $queue = RT::Queue->new($session{'CurrentUser'});
     $queue->Load($id);
     $CustomFields->LimitToQueue($queue->Id);

Added: rt/branches/rt-3.1/html/Search/Elements/SelectGroup
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/html/Search/Elements/SelectGroup	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,43 @@
+%# BEGIN LICENSE BLOCK
+%# 
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+%# 
+%# (Except where explictly superceded by other copyright notices)
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%# 
+%# 
+%# END LICENSE BLOCK
+<SELECT NAME="<%$Name%>">
+% if ($AllowNull) {
+<OPTION VALUE="">-</OPTION>
+% }
+%while (my $group = $groups->Next) {
+<OPTION VALUE="<%$group->id%>" <%$group->id eq $Default && "SELECTED"%>><%$group->Name%></OPTION>
+%}
+</SELECT>
+
+<%INIT>
+my $groups = new RT::Groups($session{'CurrentUser'});
+$groups->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => $Domain);
+
+</%INIT>
+<%ARGS>
+$AllowNull => 1
+$Default=>undef
+$Name => 'Group'
+$Domain => 'UserDefined';
+</%ARGS>

Modified: rt/branches/rt-3.1/html/Search/Elements/SelectPersonType
==============================================================================
--- rt/branches/rt-3.1/html/Search/Elements/SelectPersonType	(original)
+++ rt/branches/rt-3.1/html/Search/Elements/SelectPersonType	Sat Jul 10 02:05:15 2004
@@ -25,11 +25,15 @@
 % if ($AllowNull) {
 <OPTION VALUE="">-</OPTION>
 % }
-%for my $option (@types) {
-%foreach my $subtype (@subtypes) {
+% for my $option (@types) {
+%  if ($Suffix) {
+<OPTION VALUE="<% $option %><% $Suffix %>" <%$option eq $Default && "SELECTED"%> ><%loc($option)%></OPTION>
+%   next;
+%  }
+%  foreach my $subtype (@subtypes) {
 <OPTION VALUE="<%"$option.$subtype"%>" <%$option eq $Default && $subtype eq 'EmailAddress' && "SELECTED"%> ><%loc("[_1] [_2]",$option, $subtype)%></OPTION>
+%  }
 % }
-%}
 </SELECT>
 
 <%INIT>
@@ -37,6 +41,9 @@
 if ($Scope =~ 'queue') {
    @types = qw(Cc AdminCc);
 }
+elsif ($Suffix eq 'Group') {
+   @types = qw(Requestor Cc AdminCc Watcher);
+}
 else { 
    @types = qw(Requestor Cc AdminCc Watcher Owner);
 }
@@ -46,6 +53,7 @@
 </%INIT>
 <%ARGS>
 $AllowNull => 1
+$Suffix => ''
 $Default=>undef
 $Scope => 'ticket'
 $Name => 'WatcherType'

Modified: rt/branches/rt-3.1/html/SelfService/Closed.html
==============================================================================
--- rt/branches/rt-3.1/html/SelfService/Closed.html	(original)
+++ rt/branches/rt-3.1/html/SelfService/Closed.html	Sat Jul 10 02:05:15 2004
@@ -21,7 +21,7 @@
 %# 
 %# 
 %# END LICENSE BLOCK
-<& /SelfService/Elements/Header, Title => loc('Closed Tickets') &>
+<& /SelfService/Elements/Header, Title => loc('Closed tickets') &>
 
 <& /SelfService/Elements/MyRequests, status => ['rejected', 'resolved'], friendly_status =>
 loc('closed') &>

Modified: rt/branches/rt-3.1/html/SelfService/Display.html
==============================================================================
--- rt/branches/rt-3.1/html/SelfService/Display.html	(original)
+++ rt/branches/rt-3.1/html/SelfService/Display.html	Sat Jul 10 02:05:15 2004
@@ -174,9 +174,7 @@
 
 my $Transactions = $Ticket->Transactions;
 
-#!!pape: selfservice_find_attachments.patch {{
 my $attachments = $m->comp('/Ticket/Elements/FindAttachments', Ticket => $Ticket);
-#!!pape: selfservice_find_attachments.patch }}
 
 </%INIT>
 

Modified: rt/branches/rt-3.1/html/Ticket/Create.html
==============================================================================
--- rt/branches/rt-3.1/html/Ticket/Create.html	(original)
+++ rt/branches/rt-3.1/html/Ticket/Create.html	Sat Jul 10 02:05:15 2004
@@ -91,6 +91,14 @@
 <& /Ticket/Elements/EditCustomFields, QueueObj => $QueueObj &>
 </TD>
 </TR>
+% if ($TxnCFs->Count) {
+% while (my $CF = $TxnCFs->Next()) {
+<TR>
+<TD ALIGN=RIGHT><% $CF->Name %>:</TD>
+<TD><& /Elements/EditCustomField, CustomField => $CF, NamePrefix => "TransactionCustomField-" &><em><% $CF->FriendlyType %></em></TD>
+</TD></TR>
+% }
+% }
 <TR>
 % if (exists $session{'Attachments'}) {
 <TD class=label>
@@ -206,7 +214,8 @@
 
 my $QueueObj = new RT::Queue($session{'CurrentUser'});
 $QueueObj->Load($Queue) || Abort(loc("Queue could not be loaded."));
-my $CFs = $QueueObj->CustomFields();
+my $CFs = $QueueObj->TicketCustomFields();
+my $TxnCFs = $QueueObj->TicketTransactionCustomFields();
 
 # if no due date has been set explicitly, then use the
 # queue's default if it exists

Modified: rt/branches/rt-3.1/html/Ticket/Elements/EditCustomFields
==============================================================================
--- rt/branches/rt-3.1/html/Ticket/Elements/EditCustomFields	(original)
+++ rt/branches/rt-3.1/html/Ticket/Elements/EditCustomFields	Sat Jul 10 02:05:15 2004
@@ -44,7 +44,7 @@
       <b><%$CustomField->Name%></b><br>
       <i><%$CustomField->FriendlyType%></i>
     </td>
-    <td class="entry"><& EditCustomField, TicketObj => $TicketObj, CustomField => $CustomField, NamePrefix => $NamePrefix &></td>
+    <td class="entry"><& /Elements/EditCustomField, Object => $TicketObj, CustomField => $CustomField, NamePrefix => $NamePrefix &></td>
   </tr>
 % }
 </table>
@@ -57,12 +57,12 @@
 my $NamePrefix;
 
 if ($TicketObj) {
- $CustomFields = $TicketObj->QueueObj->CustomFields();
-  $NamePrefix = "Ticket-".$TicketObj->Id."-CustomField-";
+ $CustomFields = $TicketObj->CustomFields();
+  $NamePrefix = "Object-RT::Ticket-".$TicketObj->Id."-CustomField-";
 
 } else {
- $CustomFields = $QueueObj->CustomFields();
-  $NamePrefix = "CustomField-";
+ $CustomFields = $QueueObj->TicketCustomFields();
+  $NamePrefix = "Object-RT::Ticket--CustomField-";
 }
 
 

Modified: rt/branches/rt-3.1/html/Ticket/Elements/EditPeople
==============================================================================
--- rt/branches/rt-3.1/html/Ticket/Elements/EditPeople	(original)
+++ rt/branches/rt-3.1/html/Ticket/Elements/EditPeople	Sat Jul 10 02:05:15 2004
@@ -30,7 +30,7 @@
 <& /Elements/SelectUsers &>
 <input type=submit name="OnlySearchForPeople" value="<&|/l&>Go!</&>">
 <BR>
-<&|/l&>Find group whose</&><BR>
+<&|/l&>Find groups whose</&><BR>
 <& /Elements/SelectGroups &>
 <input type=submit name="OnlySearchForGroup" value="<&|/l&>Go!</&>">
 

Modified: rt/branches/rt-3.1/html/Ticket/Elements/FindAttachments
==============================================================================
--- rt/branches/rt-3.1/html/Ticket/Elements/FindAttachments	(original)
+++ rt/branches/rt-3.1/html/Ticket/Elements/FindAttachments	Sat Jul 10 02:05:15 2004
@@ -18,11 +18,14 @@
     
 my $tickets = $attachments->NewAlias('Tickets');
 
-$attachments->Join( ALIAS1 => $transactions,
-		    FIELD1 => 'Ticket',
-		    ALIAS2 => $tickets,
-		    FIELD2 => 'id' );
-
+  $attachments->Join( ALIAS1 => $transactions,                         
+                        FIELD1 => 'ObjectId',
+                        ALIAS2 => $tickets,
+                        FIELD2 => 'id' );
+    
+    $attachments->Limit( ALIAS => $transactions,
+                         FIELD => 'ObjectType',
+                        VALUE => 'RT::Ticket');
 if ($Tickets) {
     while ($Ticket = $Tickets->Next) {
 	$attachments->Limit( ALIAS => $tickets,

Modified: rt/branches/rt-3.1/html/Ticket/Elements/LoadTextAttachments
==============================================================================
--- rt/branches/rt-3.1/html/Ticket/Elements/LoadTextAttachments	(original)
+++ rt/branches/rt-3.1/html/Ticket/Elements/LoadTextAttachments	Sat Jul 10 02:05:15 2004
@@ -13,11 +13,17 @@
     
     my $tickets = $attachments->NewAlias('Tickets');
 
+
     $attachments->Join( ALIAS1 => $transactions,
-                        FIELD1 => 'Ticket',
+                        FIELD1 => 'ObjectId',
                         ALIAS2 => $tickets,
                         FIELD2 => 'id' );
 
+    $attachments->Limit( ALIAS => $transactions,
+                         FIELD => 'ObjectType',
+                        VALUE => 'RT::Ticket');
+
+
     $attachments->Limit( ALIAS => $tickets,
                          FIELD => 'EffectiveId',
                          VALUE => $Ticket->id() );

Modified: rt/branches/rt-3.1/html/Ticket/Elements/ShowCustomFields
==============================================================================
--- rt/branches/rt-3.1/html/Ticket/Elements/ShowCustomFields	(original)
+++ rt/branches/rt-3.1/html/Ticket/Elements/ShowCustomFields	Sat Jul 10 02:05:15 2004
@@ -21,26 +21,7 @@
 %# 
 %# 
 %# END LICENSE BLOCK
-<table>
-% my @entry_fields;
-% while (my $CustomField = $CustomFields->Next()) {
-% my $Values = $Ticket->CustomFieldValues($CustomField->Id);
-  <tr>
-    <td class="label"><%$CustomField->Name%>:</td>
-    <td class="value">
-% while (my $Value = $Values->Next()) {
-<%$Value->Content%><br>        
-% }
-% unless ($Values->Count()) {
-<i><&|/l&>(no value)</&></i>
-% }
-    </td>
-  </tr>
-% }
-</table>
-<%INIT>
-my $CustomFields = $Ticket->QueueObj->CustomFields();
-</%INIT>
+<& /Elements/ShowCustomFields, Object => $Ticket &>
 <%ARGS>
 $Ticket => undef
 </%ARGS>

Modified: rt/branches/rt-3.1/html/Ticket/Elements/ShowRequestor
==============================================================================
--- rt/branches/rt-3.1/html/Ticket/Elements/ShowRequestor	(original)
+++ rt/branches/rt-3.1/html/Ticket/Elements/ShowRequestor	Sat Jul 10 02:05:15 2004
@@ -51,6 +51,11 @@
 <LI><a href="<%$RT::WebPath%><%$DisplayPath%>?id=<%$w->id%>"><%$w->Id%>: <%$w->Subject%></a> (<%$w->Status%>)
 %}
 </UL>
+
+<&|/l&>Groups this user belongs to</&>:<BR>
+
+<& /Elements/ShowMemberships, UserObj => $requestor &>
+
 <& /Elements/TitleBoxEnd &>
 
 % }

Modified: rt/branches/rt-3.1/html/Ticket/Elements/ShowTransaction
==============================================================================
--- rt/branches/rt-3.1/html/Ticket/Elements/ShowTransaction	(original)
+++ rt/branches/rt-3.1/html/Ticket/Elements/ShowTransaction	Sat Jul 10 02:05:15 2004
@@ -34,6 +34,9 @@
 <TD ALIGN="RIGHT"><font size=-1><%$titlebar_commands|n%></font></TD>
 </TR>
 <TR class="<% $RowNum%2 ? 'oddline' : 'evenline'%>"><TD colspan=5>
+% if ($Transaction->CustomFieldValues->Count) {
+<& /Elements/ShowCustomFields, Object => $Transaction &>
+% }
 % $m->comp('ShowTransactionAttachments', %ARGS, Parent => 0) unless ($Collapsed ||!$ShowBody);
 </TD>
 </TR>

Modified: rt/branches/rt-3.1/html/Ticket/Modify.html
==============================================================================
--- rt/branches/rt-3.1/html/Ticket/Modify.html	(original)
+++ rt/branches/rt-3.1/html/Ticket/Modify.html	Sat Jul 10 02:05:15 2004
@@ -27,7 +27,7 @@
     Title => loc('Modify ticket #[_1]', $TicketObj->Id) &>
 
 <& /Elements/ListActions, actions => \@results &>
-<FORM METHOD=POST ACTION="Modify.html">
+<FORM METHOD=POST ACTION="Modify.html" ENCTYPE="multipart/form-data">
 <INPUT TYPE=HIDDEN NAME=id VALUE="<%$TicketObj->Id%>">
 
 <& /Elements/TitleBoxStart, title => loc('Modify ticket #[_1]',$TicketObj->Id),   color=> "#993333", width => "100%" &>
@@ -46,7 +46,7 @@
 $m->comp('/Elements/Callback', TicketObj => $TicketObj, CustomFields => $CustomFields, %ARGS);
 
 my @results = ProcessTicketBasics(TicketObj => $TicketObj, ARGSRef => \%ARGS);
-my @cf_results = ProcessTicketCustomFieldUpdates(TicketObj => $TicketObj, ARGSRef => \%ARGS);
+my @cf_results = ProcessObjectCustomFieldUpdates(Object => $TicketObj, ARGSRef => \%ARGS);
 push (@results, @cf_results);
 
 # TODO: display the results, even if we can't display the ticket

Modified: rt/branches/rt-3.1/html/Ticket/ModifyAll.html
==============================================================================
--- rt/branches/rt-3.1/html/Ticket/ModifyAll.html	(original)
+++ rt/branches/rt-3.1/html/Ticket/ModifyAll.html	Sat Jul 10 02:05:15 2004
@@ -78,6 +78,14 @@
     <td class="label"><&|/l&>Subject</&>:</td>
     <td class="entry"><input name="UpdateSubject" size=60 value="<%$Ticket->Subject%>"></td>
   </tr>
+% if (my $TxnCFs = $Ticket->TransactionCustomFields) {
+%    while (my $CF = $TxnCFs->Next()) {
+<TR>
+<TD class="label"><% $CF->Name %>:</TD>
+<TD class="entry"><& /Elements/EditCustomField, CustomField => $CF, NamePrefix => "TransactionCustomField-" &><em><% $CF->FriendlyType %></em></TD>
+</TD></TR>
+%    } # end if while
+% } # end of if
   <tr>
     <td class="label"><&|/l&>Attach</&>:</td>
     <td class="entry"><input name="UpdateAttachment" type=file></td>
@@ -115,7 +123,7 @@
 unless ($OnlySearchForPeople) {
     @wresults = ProcessTicketWatchers( TicketObj => $Ticket, ARGSRef => \%ARGS);
     @results = ProcessTicketBasics( TicketObj => $Ticket, ARGSRef => \%ARGS);
- @cf_results = ProcessTicketCustomFieldUpdates( TicketObj => $Ticket, ARGSRef => \%ARGS);
+ @cf_results = ProcessObjectCustomFieldUpdates( Object => $Ticket, ARGSRef => \%ARGS);
     @dresults = ProcessTicketDates( TicketObj => $Ticket, ARGSRef => \%ARGS);
     @lresults = ProcessTicketLinks( TicketObj => $Ticket, ARGSRef => \%ARGS);
 

Modified: rt/branches/rt-3.1/html/Ticket/Update.html
==============================================================================
--- rt/branches/rt-3.1/html/Ticket/Update.html	(original)
+++ rt/branches/rt-3.1/html/Ticket/Update.html	Sat Jul 10 02:05:15 2004
@@ -78,6 +78,16 @@
 </TR>
 <TR>
 % } # end of if
+
+% if (my $TxnCFs = $TicketObj->TransactionCustomFields) {
+%    while (my $CF = $TxnCFs->Next()) {
+<TR>
+<TD ALIGN=RIGHT><% $CF->Name %>:</TD>
+<TD><& /Elements/EditCustomField, CustomField => $CF, NamePrefix => "TransactionCustomField-" &><em><% $CF->FriendlyType %></em></TD>
+</TD></TR>
+%    } # end if while
+% } # end of if
+
 <tr><td align=right><&|/l&>Attach</&>:</td><td><input name="Attach" type="file"><INPUT TYPE=SUBMIT NAME="AddMoreAttach" VALUE="<&|/l&>Add More Files</&>"><input type="hidden" name="UpdateAttach" value="1">
 </td></tr>
 <tr><td align="right" valign="top"><&|/l&>Message</&>:</td><td>
@@ -178,6 +188,7 @@
     $m->comp('Display.html', TicketObj => $TicketObj, %ARGS);
     return;
 }
+
 </%INIT>
 
 <%ARGS>

Modified: rt/branches/rt-3.1/html/User/Prefs.html
==============================================================================
--- rt/branches/rt-3.1/html/User/Prefs.html	(original)
+++ rt/branches/rt-3.1/html/User/Prefs.html	Sat Jul 10 02:05:15 2004
@@ -188,9 +188,10 @@
 					    Object => $UserObj,
 					    ARGSRef => \%ARGS );
     if ($Lang) {
-        $session{'CurrentUser'}->LanguageHandle($Lang);
-        $session{'CurrentUser'}= $session{'CurrentUser'}; # Force writeback
+	$session{'CurrentUser'}->LanguageHandle($Lang);
+	$session{'CurrentUser'} = $session{'CurrentUser'}; # force writeback
     }
+
     push (@results, at fieldresults);
 
 

Modified: rt/branches/rt-3.1/html/autohandler
==============================================================================
--- rt/branches/rt-3.1/html/autohandler	(original)
+++ rt/branches/rt-3.1/html/autohandler	Sat Jul 10 02:05:15 2004
@@ -147,7 +147,7 @@
     elsif ($RT::WebFallbackToInternalAuth) {
   	unless (defined($session{'CurrentUser'})) {
 	        $m->comp('/Elements/Login', %ARGS,
-                         Error=> loc('XXX CHANGEME You are not an authorized user'));
+                         Error=> loc('You are not an authorized user'));
                 $m->abort();
 	}
     } else {

Modified: rt/branches/rt-3.1/lib/RT.pm.in
==============================================================================
--- rt/branches/rt-3.1/lib/RT.pm.in	(original)
+++ rt/branches/rt-3.1/lib/RT.pm.in	Sat Jul 10 02:05:15 2004
@@ -131,7 +131,8 @@
   
     $System = RT::System->new();
 
-   InitLogging(); 
+    InitClasses();
+    InitLogging(); 
 }
 
   
@@ -267,6 +268,29 @@
 
 }
 
+=head2 InitClasses
+
+Load all modules that define base classes
+
+=cut
+sub InitClasses {
+    require RT::Tickets;
+    require RT::Transactions;
+    require RT::Users;
+    require RT::CurrentUser;
+    require RT::Templates;
+    require RT::Queues;
+    require RT::ScripActions;
+    require RT::ScripConditions;
+    require RT::Scrips;
+    require RT::Groups;
+    require RT::GroupMembers;
+    require RT::CustomFields;
+    require RT::CustomFieldValues;
+    require RT::ObjectCustomFields;
+    require RT::ObjectCustomFieldValues;
+}
+
 # }}}
 
 

Modified: rt/branches/rt-3.1/lib/RT/Action/CreateTickets.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/Action/CreateTickets.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/Action/CreateTickets.pm	Sat Jul 10 02:05:15 2004
@@ -126,8 +126,8 @@
          push (@admins, $admin->EmailAddress); 
      }
  }
- Queue: Approvals
- Type: Approval
+ Queue: ___Approvals
+ Type: approval
  AdminCc: {join ("\nAdminCc: ", at admins) }
  Depended-On-By: TOP
  Refers-To: TOP
@@ -142,7 +142,7 @@
  Subject: Manager approval
  Depended-On-By: TOP
  Refers-On: {$Tickets{"approval"}->Id}
- Queue: Approvals
+ Queue: ___Approvals
  Content-Type: text/plain
  Content: 
  Your approval is requred for this ticket, too.
@@ -221,8 +221,8 @@
 
 my $approvals = 
 '===Create-Ticket: approval
-Queue: Approvals
-Type: Approval
+Queue: ___Approvals
+Type: approval
 AdminCc: {join ("\nAdminCc: ", at admins) }
 Depended-On-By: {$Tickets{"TOP"}->Id}
 Refers-To: TOP 
@@ -236,7 +236,7 @@
 ===Create-Ticket: two
 Subject: Manager approval.
 Depended-On-By: approval
-Queue: Approvals
+Queue: ___Approvals
 Content-Type: text/plain
 Content: 
 Your minion approved ticket {$Tickets{"TOP"}->Id}. you ok with that?

Modified: rt/branches/rt-3.1/lib/RT/Attachment_Overlay.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/Attachment_Overlay.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/Attachment_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -212,26 +212,7 @@
 
 sub Content {
   my $self = shift;
-  my $decode_utf8 = (($self->ContentType =~ qr{^text/plain}i) ? 1 : 0);
-
-  if ( $self->ContentEncoding eq 'none' || ! $self->ContentEncoding ) {
-      return $self->_Value(
-	  'Content',
-	  decode_utf8 => $decode_utf8,
-      );
-  } elsif ( $self->ContentEncoding eq 'base64' ) {
-      return ( $decode_utf8
-        ? Encode::decode_utf8(MIME::Base64::decode_base64($self->_Value('Content')))
-        : MIME::Base64::decode_base64($self->_Value('Content'))
-      );
-  } elsif ( $self->ContentEncoding eq 'quoted-printable' ) {
-      return ( $decode_utf8
-        ? Encode::decode_utf8(MIME::QuotedPrint::decode($self->_Value('Content')))
-        : MIME::QuotedPrint::decode($self->_Value('Content'))
-      );
-  } else {
-      return( $self->loc("Unknown ContentEncoding [_1]", $self->ContentEncoding));
-  }
+   $self->_DecodeLOB($self->ContentType, $self->ContentEncoding, $self->_Value('Content', decode_utf8 => 0));
 }
 
 

Modified: rt/branches/rt-3.1/lib/RT/Base.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/Base.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/Base.pm	Sat Jul 10 02:05:15 2004
@@ -103,6 +103,18 @@
     }
 }
 
+sub loc_fuzzy {
+    my $self = shift;
+    if (my $user = $self->OriginalUser) {
+        return $user->loc_fuzzy(@_);
+    }
+    else {
+        use Carp;
+        Carp::confess("No currentuser");
+        return ("Critical error:$self has no CurrentUser", $self);
+    }
+}
+
 eval "require RT::Base_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Base_Vendor.pm});
 eval "require RT::Base_Local";

Modified: rt/branches/rt-3.1/lib/RT/CurrentUser.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/CurrentUser.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/CurrentUser.pm	Sat Jul 10 02:05:15 2004
@@ -328,12 +328,12 @@
 =begin testing
 
 ok (my $cu = RT::CurrentUser->new('root'));
-ok (my $lh = $cu->LanguageHandle);
+ok (my $lh = $cu->LanguageHandle('en-us'));
 ok ($lh != undef);
 ok ($lh->isa('Locale::Maketext'));
-ok ($cu->loc('TEST_STRING') eq "Concrete Mixer", "Localized TEST_STRING into English");
+is ($cu->loc('TEST_STRING'), "Concrete Mixer", "Localized TEST_STRING into English");
 ok ($lh = $cu->LanguageHandle('fr'));
-ok ($cu->loc('Before') eq "Avant", "Localized TEST_STRING into Frenc");
+is ($cu->loc('Before'), "Avant", "Localized TEST_STRING into Frenc");
 
 =end testing
 
@@ -401,6 +401,48 @@
 
 }
 
+=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});

Modified: rt/branches/rt-3.1/lib/RT/CustomField.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/CustomField.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/CustomField.pm	Sat Jul 10 02:05:15 2004
@@ -46,7 +46,6 @@
 
 package RT::CustomField;
 use RT::Record; 
-use RT::Queue;
 
 
 use vars qw( @ISA );
@@ -69,9 +68,12 @@
 
   varchar(200) 'Name'.
   varchar(200) 'Type'.
-  int(11) 'Queue'.
+  int(11) 'MaxValues'.
+  varchar(255) 'Pattern'.
+  smallint(6) 'Repeated'.
   varchar(255) 'Description'.
   int(11) 'SortOrder'.
+  varchar(255) 'LookupType'.
   smallint(6) 'Disabled'.
 
 =cut
@@ -84,18 +86,24 @@
     my %args = ( 
                 Name => '',
                 Type => '',
-                Queue => '0',
+                MaxValues => '',
+                Pattern => '',
+                Repeated => '0',
                 Description => '',
                 SortOrder => '0',
+                LookupType => '',
                 Disabled => '0',
 
 		  @_);
     $self->SUPER::Create(
                          Name => $args{'Name'},
                          Type => $args{'Type'},
-                         Queue => $args{'Queue'},
+                         MaxValues => $args{'MaxValues'},
+                         Pattern => $args{'Pattern'},
+                         Repeated => $args{'Repeated'},
                          Description => $args{'Description'},
                          SortOrder => $args{'SortOrder'},
+                         LookupType => $args{'LookupType'},
                          Disabled => $args{'Disabled'},
 );
 
@@ -148,37 +156,59 @@
 =cut
 
 
-=head2 Queue
+=head2 MaxValues
 
-Returns the current value of Queue. 
-(In the database, Queue is stored as int(11).)
+Returns the current value of MaxValues. 
+(In the database, MaxValues is stored as int(11).)
 
 
 
-=head2 SetQueue VALUE
+=head2 SetMaxValues VALUE
 
 
-Set Queue to VALUE. 
+Set MaxValues to VALUE. 
 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, Queue will be stored as a int(11).)
+(In the database, MaxValues will be stored as a int(11).)
 
 
 =cut
 
 
-=head2 QueueObj
+=head2 Pattern
 
-Returns the Queue Object which has the id returned by Queue
+Returns the current value of Pattern. 
+(In the database, Pattern is stored as varchar(255).)
+
+
+
+=head2 SetPattern VALUE
+
+
+Set Pattern to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Pattern will be stored as a varchar(255).)
+
+
+=cut
+
+
+=head2 Repeated
+
+Returns the current value of Repeated. 
+(In the database, Repeated is stored as smallint(6).)
+
+
+
+=head2 SetRepeated VALUE
+
+
+Set Repeated to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Repeated will be stored as a smallint(6).)
 
 
 =cut
 
-sub QueueObj {
-	my $self = shift;
-	my $Queue =  RT::Queue->new($self->CurrentUser);
-	$Queue->Load($self->__Value('Queue'));
-	return($Queue);
-}
 
 =head2 Description
 
@@ -216,6 +246,24 @@
 =cut
 
 
+=head2 LookupType
+
+Returns the current value of LookupType. 
+(In the database, LookupType is stored as varchar(255).)
+
+
+
+=head2 SetLookupType VALUE
+
+
+Set LookupType to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, LookupType will be stored as a varchar(255).)
+
+
+=cut
+
+
 =head2 Creator
 
 Returns the current value of Creator. 
@@ -280,12 +328,18 @@
 		{read => 1, write => 1, type => 'varchar(200)', default => ''},
         Type => 
 		{read => 1, write => 1, type => 'varchar(200)', default => ''},
-        Queue => 
-		{read => 1, write => 1, type => 'int(11)', default => '0'},
+        MaxValues => 
+		{read => 1, write => 1, type => 'int(11)', default => ''},
+        Pattern => 
+		{read => 1, write => 1, type => 'varchar(255)', default => ''},
+        Repeated => 
+		{read => 1, write => 1, type => 'smallint(6)', default => '0'},
         Description => 
 		{read => 1, write => 1, type => 'varchar(255)', default => ''},
         SortOrder => 
 		{read => 1, write => 1, type => 'int(11)', default => '0'},
+        LookupType => 
+		{read => 1, write => 1, type => 'varchar(255)', default => ''},
         Creator => 
 		{read => 1, auto => 1, type => 'int(11)', default => '0'},
         Created => 

Modified: rt/branches/rt-3.1/lib/RT/CustomField_Overlay.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/CustomField_Overlay.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/CustomField_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -24,24 +24,47 @@
 use strict;
 no warnings qw(redefine);
 
-use vars qw(@TYPES %TYPES);
+use vars qw(@TYPES %TYPES $RIGHTS %FRIENDLY_OBJECT_TYPES);
 
 use RT::CustomFieldValues;
-use RT::TicketCustomFieldValues;
+use RT::ObjectCustomFieldValues;
 
 # Enumerate all valid types for this custom field
 @TYPES = (
-    'SelectSingle',	# loc
-    'SelectMultiple',	# loc
-    'FreeformSingle',	# loc
-    'FreeformMultiple', # loc
+    'Freeform',	# loc
+    'Select',	# loc
+    'Text',     # loc
+    'Image',    # loc
+    'Binary',   # loc
 );
 
 # Populate a hash of types of easier validation
 for (@TYPES) { $TYPES{$_} = 1};
 
 
+%FRIENDLY_OBJECT_TYPES =  (
+    'RT::Queue-RT::Ticket'                 => "Tickets",		# loc
+    'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions",	# loc
+    'RT::User'                             => "Users",			# loc
+    'RT::Group'                            => "Groups",			# loc
+);
+
+$RIGHTS = {
+    SeeCustomField            => 'See custom fields',       # loc_pair
+    AdminCustomField          => 'Create, delete and modify custom fields',        # loc_pair
+};
+
+# Tell RT::ACE that this sort of object can get acls granted
+$RT::ACE::OBJECT_TYPES{'RT::CustomField'} = 1;
+
+foreach my $right ( keys %{$RIGHTS} ) {
+    $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
+}
 
+sub AvailableRights {
+    my $self = shift;
+    return($RIGHTS);
+}
 
 =head1 NAME
 
@@ -61,7 +84,8 @@
 
   varchar(200) 'Name'.
   varchar(200) 'Type'.
-  int(11) 'Queue'.
+  int(11) 'MaxValues'.
+  varchar(255) 'Pattern'.
   varchar(255) 'Description'.
   int(11) 'SortOrder'.
   smallint(6) 'Disabled'.
@@ -76,19 +100,31 @@
     my %args = ( 
                 Name => '',
                 Type => '',
-                Queue => '0',
+		MaxValues => '0',
+		Pattern  => '',
                 Description => '',
-                SortOrder => '0',
                 Disabled => '0',
+		LookupType  => '',
+		Repeated  => '0',
 
 		  @_);
 
+    if ($args{TypeComposite}) {
+	@args{'Type', 'MaxValues'} = split(/-/, $args{TypeComposite}, 2);
+    }
+    elsif ($args{Type} =~ s/(?:(Single)|Multiple)$//) {
+	# old style Type string
+	$args{'MaxValues'} = $1 ? 1 : 0;
+    }
     
-
-    if (  ! $args{'Queue'} ) {
-        unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AdminCustomFields') ) {
+    if ( !exists $args{'Queue'}) {
+	# do nothing -- things below are strictly backward compat
+    }
+    elsif (  ! $args{'Queue'} ) {
+        unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AssignCustomFields') ) {
             return ( 0, $self->loc('Permission Denied') );
         }
+	$args{'LookupType'} = 'RT::Queue-RT::Ticket';
     }
     else {
         my $queue = RT::Queue->new($self->CurrentUser);
@@ -96,19 +132,32 @@
         unless ($queue->Id) {
             return (0, $self->loc("Queue not found"));
         }
-        unless ( $queue->CurrentUserHasRight('AdminCustomFields') ) {
+        unless ( $queue->CurrentUserHasRight('AssignCustomFields') ) {
             return ( 0, $self->loc('Permission Denied') );
         }
+	$args{'LookupType'} = 'RT::Queue-RT::Ticket';
     }
-    $self->SUPER::Create(
+    my $rv = $self->SUPER::Create(
                          Name => $args{'Name'},
                          Type => $args{'Type'},
-                         Queue => $args{'Queue'},
+                         MaxValues => $args{'MaxValues'},
+                         Pattern  => $args{'Pattern'},
                          Description => $args{'Description'},
-                         SortOrder => $args{'SortOrder'},
                          Disabled => $args{'Disabled'},
+			 LookupType => $args{'LookupType'},
+			 Repeated => $args{'Repeated'},
 );
 
+    return $rv unless exists $args{'Queue'};
+
+    # Compat code -- create a new ObjectCustomField mapping
+    my $OCF = RT::ObjectCustomField->new($self->CurrentUser);
+    $OCF->Create(
+	CustomField => $self->Id,
+	ObjectId => $args{'Queue'},
+    );
+
+    return $rv;
 }
 
 
@@ -138,7 +187,15 @@
 	$args{'Queue'} = $QueueObj->Id;
     }
 
-    return ( $self->LoadByCols( Name => $args{'Name'}, Queue => $args{'Queue'} ) );
+    # XXX - really naive implementation.  Slow.
+
+    my $CFs = RT::CustomFields->new($self->CurrentUser);
+    $CFs->Limit( FIELD => 'Name', VALUE => $args{'Name'} );
+    $CFs->LimitToQueue( $args{'Queue'} );
+    $CFs->RowsPerPage(1);
+
+    my $CF = $CFs->First or return;
+    return $self->Load($CF->Id);
 
 }
 
@@ -156,10 +213,12 @@
                                  Type=> 'SelectSingle'), 'Created a global CustomField');
 ok($id != 0, 'Global custom field correctly created');
 ok ($cf->SingleValue);
-ok($cf->Type eq 'SelectSingle');
+is($cf->Type, 'Select');
+is($cf->MaxValues, 1);
 
 ok($cf->SetType('SelectMultiple'));
-ok($cf->Type eq 'SelectMultiple');
+is($cf->Type, 'Select');
+is($cf->MaxValues, 0);
 ok(!$cf->SingleValue );
 ok(my ($bogus_val, $bogus_msg) = $cf->SetType('BogusType') , "Trying to set a custom field's type to a bogus type");
 ok($bogus_val == 0, "Unable to set a custom field's type to a bogus type");
@@ -203,7 +262,7 @@
 		     SortOrder => undef,
 		     @_ );
 
-    unless ($self->CurrentUserHasRight('AdminCustomFields')) {
+    unless ($self->CurrentUserHasRight('AdminCustomField')) {
         return (0, $self->loc('Permission Denied'));
     }
 
@@ -235,7 +294,7 @@
 sub DeleteValue {
 	my $self = shift;
     my $id = shift;
-    unless ($self->CurrentUserHasRight('AdminCustomFields')) {
+    unless ($self->CurrentUserHasRight('AdminCustomField')) {
         return (0, $self->loc('Permission Denied'));
     }
 
@@ -271,12 +330,17 @@
     my $self = shift;
 
     my $cf_values = RT::CustomFieldValues->new($self->CurrentUser);
-    if ( $self->__Value('Queue') == 0 || $self->CurrentUserHasRight( 'SeeQueue') ) {
+    if ( $self->CurrentUserHasRight( 'SeeCustomField') ) {
         $cf_values->LimitToCustomField($self->Id);
     }
     return ($cf_values);
 }
 
+sub ValuesObj {
+    my $self = shift;
+    return $self->Values(@_);
+}
+
 # }}}
 
 # }}}
@@ -287,7 +351,7 @@
 
 =head2 ValuesForTicket TICKET
 
-Returns a RT::TicketCustomFieldValues object of this Field's values for TICKET.
+Returns a RT::ObjectCustomFieldValues object of this Field's values for TICKET.
 TICKET is a ticket id.
 
 
@@ -297,7 +361,7 @@
 	my $self = shift;
     my $ticket_id = shift;
 
-	my $values = new RT::TicketCustomFieldValues($self->CurrentUser);
+	my $values = new RT::ObjectCustomFieldValues($self->CurrentUser);
 	$values->LimitToCustomField($self->Id);
     $values->LimitToTicket($ticket_id);
 
@@ -320,8 +384,9 @@
                  Content => undef,
 		     @_ );
 
-	my $newval = RT::TicketCustomFieldValue->new($self->CurrentUser);
-	my $val = $newval->Create(Ticket => $args{'Ticket'},
+	my $newval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
+	my $val = $newval->Create(ObjectType => 'RT::Ticket',
+	                    ObjectId => $args{'Ticket'},
                             Content => $args{'Content'},
                             CustomField => $self->Id);
 
@@ -346,7 +411,7 @@
                  Content => undef,
 		     @_ );
 
-	my $oldval = RT::TicketCustomFieldValue->new($self->CurrentUser);
+	my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
     $oldval->LoadByTicketContentAndCustomField (Ticket => $args{'Ticket'}, 
                                                 Content =>  $args{'Content'}, 
                                                 CustomField => $self->Id );
@@ -408,35 +473,61 @@
 # }}}
 
 
-=head2 FriendlyType [TYPE]
+=head2 FriendlyType [TYPE, MAX_VALUES]
 
 Returns a localized human-readable version of the custom field type.
 If a custom field type is specified as the parameter, the friendly type for that type will be returned
 
 =cut
 
+my %FriendlyTypes = (
+    Select => [
+        'Select multiple values',	# loc
+        'Select one value',		# loc
+        'Select up to [_1] values',	# loc
+    ],
+    Freeform => [
+        'Enter multiple values',	# loc
+        'Enter one value',		# loc
+        'Enter up to [_1] values',	# loc
+    ],
+    Text => [
+        'Fill in multiple text areas',	# loc
+        'Fill in one text area',	# loc
+        'Fill in up to [_1] text areas',# loc
+    ],
+    Image => [
+        'Upload multiple images',	# loc
+        'Upload one image',		# loc
+        'Upload up to [_1] images',	# loc
+    ],
+    Binary => [
+        'Upload multiple files',	# loc
+        'Upload one file',		# loc
+        'Upload up to [_1] files',	# loc
+    ],
+);
+
 sub FriendlyType {
     my $self = shift;
 
-    my $type = shift || $self->Type;
+    my $type = @_ ? shift : $self->Type;
+    my $max  = @_ ? shift : $self->MaxValues;
 
-    if ( $type eq 'SelectSingle' ) {
-        return ( $self->loc('Select one value') );
-    }
-    elsif ( $type eq 'SelectMultiple' ) {
-        return ( $self->loc('Select multiple values') );
-    }
-    elsif ( $type eq 'FreeformSingle' ) {
-        return ( $self->loc('Enter one value') );
-    }
-    elsif ( $type eq 'FreeformMultiple' ) {
-        return ( $self->loc('Enter multiple values') );
+    if (my $friendly_type = $FriendlyTypes{$type}[$max>2 ? 2 : $max]) {
+	return ( $self->loc( $friendly_type, $max ) );
     }
     else {
-        return ( $self->loc( $self->Type ) );
+        return ( $self->loc( $type ) );
     }
 }
 
+sub FriendlyTypeComposite {
+    my $self = shift;
+    my $composite = shift || $self->TypeComposite;
+    return $self->FriendlyType(split(/-/, $composite, 2));
+}
+
 
 =head2 ValidateType TYPE
 
@@ -458,6 +549,10 @@
     my $self = shift;
     my $type = shift;
 
+    if ($type =~ s/(?:Single|Multiple)$//) {
+	warn "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead";
+    }
+
     if( $TYPES{$type}) {
         return(1);
     }
@@ -466,6 +561,17 @@
     }
 }
 
+
+sub SetType {
+    my $self = shift;
+    my $type = shift;
+    if ($type =~ s/(?:(Single)|Multiple)$//) {
+	warn "'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead";
+	$self->SetMaxValues($1 ? 1 : 0);
+    }
+    $self->SUPER::SetType($type);
+}
+
 # {{{ SingleValue
 
 =head2 SingleValue
@@ -477,7 +583,17 @@
 
 sub SingleValue {
     my $self = shift;
-    if ($self->Type =~  /Single$/) {
+    if ($self->MaxValues == 1) {
+        return 1;
+    } 
+    else {
+        return undef;
+    }
+}
+
+sub UnlimitedValues {
+    my $self = shift;
+    if ($self->MaxValues == 0) {
         return 1;
     } 
     else {
@@ -496,14 +612,13 @@
 =cut
 
 sub CurrentUserHasRight {
-    my $self = shift;
+    my $self  = shift;
     my $right = shift;
-    # if there's no queue, we want to know about a global right
-    if ( ( !defined $self->__Value('Queue') ) || ( $self->__Value('Queue') == 0 ) ) {
-         return $self->CurrentUser->HasRight( Object => $RT::System, Right => $right); 
-    } else {
-        return ( $self->QueueObj->CurrentUserHasRight($right) );
-    }
+
+    return $self->CurrentUser->HasRight(
+	Object => $self,
+	Right  => $right,
+    );
 }
 
 # }}}
@@ -513,7 +628,7 @@
 sub _Set {
     my $self = shift;
 
-    unless ( $self->CurrentUserHasRight('AdminCustomFields') ) {
+    unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
         return ( 0, $self->loc('Permission Denied') );
     }
     return ( $self->SUPER::_Set(@_) );
@@ -536,16 +651,10 @@
     my $self  = shift;
     my $field = shift;
 
-    # We need to expose the queue so that we can do things like ACL checks
-    if ( $field eq 'Queue') {
-          return ( $self->SUPER::_Value($field) );
-     }
-
-
-    #Anybody can see global custom fields, otherwise we need to do the rights check
-        unless ( $self->__Value('Queue') == 0 || $self->CurrentUserHasRight( 'SeeQueue') ) {
-            return (undef);
-        }
+    # we need to do the rights check
+    unless ( $self->CurrentUserHasRight( 'SeeCustomField') ) {
+	return (undef);
+    }
     return ( $self->__Value($field) );
 
 }
@@ -563,4 +672,212 @@
 
 # }}}
 
+sub Queue {
+    return 0;
+}
+
+sub SetQueue {
+    return 0;
+}
+
+sub QueueObj {
+    return undef;
+}
+
+sub SetTypeComposite {
+    my $self = shift;
+    my $composite = shift;
+    my ($type, $max_values) = split(/-/, $composite, 2);
+    $self->SetType($type);
+    $self->SetMaxValues($max_values);
+}
+
+sub SetLookupType {
+    my $self = shift;
+    my $lookup = shift;
+    if ($lookup ne $self->LookupType) {
+	# Okay... We need to invalidate our existing relationships
+	my $ObjectCustomFields = RT::ObjectCustomFields->new($self->CurrentUser);
+	$ObjectCustomFields->LimitToCustomField($self->Id);
+	$_->Delete foreach @{$ObjectCustomFields->ItemsArrayRef};
+    }
+    $self->SUPER::SetLookupType($lookup);
+}
+
+sub TypeComposite {
+    my $self = shift;
+    join('-', $self->Type, $self->MaxValues);
+}
+
+sub TypeComposites {
+    my $self = shift;
+    return grep !/Text-0/, map { ("$_-1", "$_-0") } $self->Types;
+}
+
+sub LookupTypes {
+    my $self = shift;
+    qw(
+	RT::Queue-RT::Ticket
+	RT::Queue-RT::Ticket-RT::Transaction
+	RT::User
+	RT::Group
+    );
+}
+
+my @FriendlyObjectTypes = (
+    "[_1] objects",		    # loc
+    "[_1]'s [_2] objects",	    # loc
+    "[_1]'s [_2]'s [_3] objects",   # loc
+);
+
+sub FriendlyLookupType {
+    my $self = shift;
+    my $lookup = shift || $self->LookupType;
+   
+    return ($self->loc( $FRIENDLY_OBJECT_TYPES{$lookup} ))
+      	           if (defined  $FRIENDLY_OBJECT_TYPES{$lookup} );
+
+    my @types = map { s/^RT::// ? $self->loc($_) : $_ }
+      grep { defined and length }
+      split( /-/, $lookup )
+      or return;
+    return ( $self->loc( $FriendlyObjectTypes[$#types], @types ) );
+}
+
+sub AddToObject {
+    my $self  = shift;
+    my $object = shift;
+    my $id = $object->Id || 0;
+
+    unless (index($self->LookupType, ref($object)) == 0) {
+	return ( 0, $self->loc('Lookup type mismatch') );
+    }
+
+    unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
+        return ( 0, $self->loc('Permission Denied') );
+    }
+
+    my $ObjectCF = RT::ObjectCustomField->new( $self->CurrentUser );
+
+    $ObjectCF->LoadByCols( ObjectId => $id, CustomField => $self->Id );
+    if ( $ObjectCF->Id ) {
+        return ( 0, $self->loc("That is already the current value") );
+    }
+    my ( $id, $msg ) =
+      $ObjectCF->Create( ObjectId => $id, CustomField => $self->Id );
+
+    return ( $id, $msg );
+}
+
+sub RemoveFromObject {
+    my $self = shift;
+    my $object = shift;
+    my $id = $object->Id || 0;
+
+    unless (index($self->LookupType, ref($object)) == 0) {
+	return ( 0, $self->loc('Object type mismatch') );
+    }
+
+    unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
+        return ( 0, $self->loc('Permission Denied') );
+    }
+
+    my $ObjectCF = RT::ObjectCustomField->new( $self->CurrentUser );
+
+    $ObjectCF->LoadByCols( ObjectId => $id, CustomField => $self->Id );
+    unless ( $ObjectCF->Id ) {
+        return ( 0, $self->loc("This custom field does not apply to that object") );
+    }
+    my ( $id, $msg ) = $ObjectCF->Delete;
+
+    return ( $id, $msg );
+}
+
+# {{{ AddValueForObject
+
+=head2 AddValueForObject HASH
+
+Adds a custom field value for a ticket. Takes a param hash of Object and Content
+
+=cut
+
+sub AddValueForObject {
+	my $self = shift;
+	my %args = ( Object => undef,
+                 Content => undef,
+		 LargeContent => undef,
+		 ContentType => undef,
+		     @_ );
+	my $obj = $args{'Object'} or return;
+
+	my $newval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
+	my $val = $newval->Create(ObjectType => ref($obj),
+	                    ObjectId => $obj->Id,
+                            Content => $args{'Content'},
+                            LargeContent => $args{'LargeContent'},
+                            ContentType => $args{'ContentType'},
+                            CustomField => $self->Id);
+
+    return($val);
+
+}
+
+
+# }}}
+
+# {{{ DeleteValueForObject
+
+=head2 DeleteValueForObject HASH
+
+Adds a custom field value for a ticket. Takes a param hash of Object and Content
+
+=cut
+
+sub DeleteValueForObject {
+    my $self = shift;
+    my %args = ( Object => undef,
+                 Content => undef,
+                 Id => undef,
+		     @_ );
+
+    my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
+
+    if (my $id = $args{'Id'}) {
+	$oldval->Load($id);
+    }
+    else {
+	$oldval->LoadByObjectContentAndCustomField(
+	    Object => $args{'Object'}, 
+	    Content =>  $args{'Content'}, 
+	    CustomField => $self->Id
+	);
+    }
+
+    # check ot make sure we found it
+    unless ($oldval->Id) {
+        return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name));
+    }
+    # delete it
+
+    my $ret = $oldval->Delete();
+    unless ($ret) {
+        return(0, $self->loc("Custom field value could not be found"));
+    }
+    return($oldval->Id, $self->loc("Custom field value deleted"));
+}
+
+sub ValuesForObject {
+	my $self = shift;
+    my $object = shift;
+
+	my $values = new RT::ObjectCustomFieldValues($self->CurrentUser);
+	$values->LimitToCustomField($self->Id);
+    $values->LimitToObject($object);
+
+	return ($values);
+}
+
+
+# }}}
+
 1;

Modified: rt/branches/rt-3.1/lib/RT/CustomFields_Overlay.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/CustomFields_Overlay.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/CustomFields_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -46,6 +46,12 @@
 no warnings qw(redefine);
 
 
+sub _OCFAlias {
+    my $self = shift;
+    $self->{_sql_ocfalias} ||= $self->NewAlias('ObjectCustomFields');
+}
+
+
 # {{{ sub LimitToGlobalOrQueue 
 
 =item LimitToGlobalOrQueue QUEUEID
@@ -57,8 +63,8 @@
 sub LimitToGlobalOrQueue {
     my $self = shift;
     my $queue = shift;
-    $self->LimitToQueue($queue);
-    $self->LimitToGlobal();
+    $self->LimitToGlobalOrObjectId( $queue );
+    $self->LimitToLookupType( 'RT::Queue-RT::Ticket' );
 }
 
 # }}}
@@ -77,11 +83,12 @@
    my $self = shift;
   my $queue = shift;
  
-  $self->Limit (ENTRYAGGREGATOR => 'OR',
-		FIELD => 'Queue',
+  $self->Limit (ALIAS => $self->_OCFAlias,
+                ENTRYAGGREGATOR => 'OR',
+		FIELD => 'ObjectId',
 		VALUE => "$queue")
       if defined $queue;
-  
+  $self->LimitToLookupType( 'RT::Queue-RT::Ticket' );
 }
 # }}}
 
@@ -99,13 +106,24 @@
 sub LimitToGlobal  {
    my $self = shift;
  
-  $self->Limit (ENTRYAGGREGATOR => 'OR',
-		FIELD => 'Queue',
+  $self->Limit (ALIAS => $self->_OCFAlias,
+                ENTRYAGGREGATOR => 'OR',
+		FIELD => 'ObjectId',
 		VALUE => 0);
-  
+  $self->LimitToLookupType( 'RT::Queue-RT::Ticket' );
 }
 # }}}
 
+sub LimitToObjectType {
+    my $self = shift;
+    my $type = shift;
+
+    return if $self->{_sql_limit_objectype}{$type}++;
+    $self->Limit (ALIAS => $self->_OCFAlias,
+		    ENTRYAGGREGATOR => 'OR',
+		    FIELD => 'ObjectType',
+		    VALUE => $type);
+}
 
 # {{{ sub _DoSearch 
 
@@ -130,6 +148,95 @@
 }
 
 # }}}
+
+# {{{ sub Next 
+
+=head2 Next
+
+Returns the next custom field that this user can see.
+
+=cut
+  
+sub Next {
+    my $self = shift;
+    
+    
+    my $CF = $self->SUPER::Next();
+    if ((defined($CF)) and (ref($CF))) {
+
+	if ($CF->CurrentUserHasRight('SeeCustomField')) {
+	    return($CF);
+	}
+	
+	#If the user doesn't have the right to show this queue
+	else {	
+	    return($self->Next());
+	}
+    }
+    #if there never was any queue
+    else {
+	return(undef);
+    }	
+    
+}
+# }}}
+
+sub LimitToLookupType  {
+    my $self = shift;
+    my $lookup = shift;
+ 
+    $self->Limit( FIELD => 'LookupType', VALUE => "$lookup" );
+}
+
+sub LimitToChildType  {
+    my $self = shift;
+    my $lookup = shift;
+ 
+    $self->Limit( FIELD => 'LookupType', VALUE => "$lookup" );
+    $self->Limit( FIELD => 'LookupType', ENDSWITH => "$lookup" );
+}
+
+sub LimitToParentType  {
+    my $self = shift;
+    my $lookup = shift;
+ 
+    $self->Limit( FIELD => 'LookupType', VALUE => "$lookup" );
+    $self->Limit( FIELD => 'LookupType', STARTSWITH => "$lookup" );
+}
+
+sub LimitToGlobalOrObjectId {
+    my $self = shift;
+    my $global_only = 1;
+
+    $self->Join( ALIAS1 => 'main',
+                FIELD1 => 'id',
+                ALIAS2 => $self->_OCFAlias,
+                FIELD2 => 'CustomField' );
+
+    foreach my $id (@_) {
+	$self->Limit( ALIAS           => $self->_OCFAlias,
+		    FIELD           => 'ObjectId',
+		    OPERATOR        => '=',
+		    VALUE           => $id || 0,
+		    ENTRYAGGREGATOR => 'OR' );
+	$global_only = 0 if $id;
+    }
+
+    $self->Limit( ALIAS           => $self->_OCFAlias,
+                 FIELD           => 'ObjectId',
+                 OPERATOR        => '=',
+                 VALUE           => 0,
+                 ENTRYAGGREGATOR => 'OR' ) unless $global_only;
+
+    $self->OrderByCols(
+	{ ALIAS => $self->_OCFAlias, FIELD => 'ObjectId' },
+	{ ALIAS => $self->_OCFAlias, FIELD => 'SortOrder' },
+    );
+    
+    # This doesn't work on postgres. 
+    #$self->OrderBy( ALIAS => $class_cfs , FIELD => "SortOrder", ORDER => 'ASC');
+
+}
   
 1;
 

Modified: rt/branches/rt-3.1/lib/RT/Group_Overlay.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/Group_Overlay.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/Group_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -447,6 +447,7 @@
         Type        => undef,
         Instance    => '0',
         InsideTransaction => undef,
+        _RecordTransaction => 1,
         @_
     );
 
@@ -492,8 +493,12 @@
     $cgm->Create(Group =>$self->PrincipalObj, Member => $self->PrincipalObj, ImmediateParent => $self->PrincipalObj);
 
 
+    if ( $args{'_RecordTransaction'} ) {
+	$self->_NewTransaction( Type => "Create" );
+    }
 
     $RT::Handle->Commit() unless ($args{'InsideTransaction'});
+
     return ( $id, $self->loc("Group created") );
 }
 
@@ -1148,6 +1153,13 @@
 # {{{ sub _Set
 sub _Set {
     my $self = shift;
+    my %args = (
+        Field => undef,
+        Value => undef,
+	TransactionType   => 'Set',
+	RecordTransaction => 1,
+        @_
+    );
 
 	if ($self->Domain eq 'Personal') {
    		if ($self->CurrentUser->PrincipalId == $self->Instance) {
@@ -1165,7 +1177,30 @@
         	return ( 0, $self->loc('Permission Denied') );
     	}
 	}
-    return ( $self->SUPER::_Set(@_) );
+
+    my $Old = $self->SUPER::_Value("$args{'Field'}");
+    
+    my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
+					  Value => $args{'Value'} );
+    
+    #If we can't actually set the field to the value, don't record
+    # a transaction. instead, get out of here.
+    if ( $ret == 0 ) { return ( 0, $msg ); }
+
+    if ( $args{'RecordTransaction'} == 1 ) {
+
+        my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
+                                               Type => $args{'TransactionType'},
+                                               Field     => $args{'Field'},
+                                               NewValue  => $args{'Value'},
+                                               OldValue  => $Old,
+                                               TimeTaken => $args{'TimeTaken'},
+        );
+        return ( $Trans, scalar $TransObj->Description );
+    }
+    else {
+        return ( $ret, $msg );
+    }
 }
 
 # }}}
@@ -1253,5 +1288,13 @@
 }
 
 # }}}
+
+sub BasicColumns {
+    (
+	[ Name => 'Name' ],
+	[ Description => 'Description' ],
+    );
+}
+
 1;
 

Modified: rt/branches/rt-3.1/lib/RT/Groups_Overlay.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/Groups_Overlay.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/Groups_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -123,8 +123,7 @@
     $self->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'Personal');
     $self->Limit(   FIELD => 'Instance',   
                     OPERATOR => '=', 
-                    VALUE => $princ,
-                    ENTRY_AGGREGATOR => 'OR');
+                    VALUE => $princ);
 }
 
 

Modified: rt/branches/rt-3.1/lib/RT/I18N.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/I18N.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/I18N.pm	Sat Jul 10 02:05:15 2004
@@ -76,9 +76,11 @@
 =cut
 
 sub Init {
+    require File::Glob;
+
     # Load language-specific functions
-    foreach my $language ( glob(substr(__FILE__, 0, -3) . "/*.pm")) {
-        if ($language =~ /^([-\w.\/\\~:]+)$/) {
+    foreach my $language ( File::Glob::bsd_glob(substr(__FILE__, 0, -3) . "/*.pm")) {
+        if ($language =~ /^([-\w\s.\/\\~:]+)$/) {
             require $1;
         }
         else {
@@ -95,6 +97,7 @@
 	    $_	=> [
 		Gettext => (substr(__FILE__, 0, -3) . "/$_.po"),
 		Gettext => "$RT::LocalLexiconPath/*/$_.po",
+		Gettext => "$RT::LocalLexiconPath/$_.po",
 	    ],
 	} @lang
     });

Modified: rt/branches/rt-3.1/lib/RT/I18N/cs.po
==============================================================================
--- rt/branches/rt-3.1/lib/RT/I18N/cs.po	(original)
+++ rt/branches/rt-3.1/lib/RT/I18N/cs.po	Sat Jul 10 02:05:15 2004
@@ -16,7 +16,7 @@
 
 #: NOT FOUND IN SOURCE
 msgid "#%1"
-msgstr "#%1"
+msgstr "#%1"	
 
 #: html/Approvals/Elements/Approve:26 html/Approvals/Elements/ShowDependency:49 html/SelfService/Display.html:24 html/Ticket/Display.html:25 html/Ticket/Display.html:29
 #. ($TicketObj->Id, $TicketObj->Subject)

Modified: rt/branches/rt-3.1/lib/RT/I18N/de.po
==============================================================================
--- rt/branches/rt-3.1/lib/RT/I18N/de.po	(original)
+++ rt/branches/rt-3.1/lib/RT/I18N/de.po	Sat Jul 10 02:05:15 2004
@@ -344,7 +344,7 @@
 msgstr ""
 
 #: html/Ticket/Create.html:178
-msgid "(Enter ticket ids or URLs, separated with spaces)"
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
 msgstr "(Gib Anfragenummern oder URLs getrennt durch Leerzeichen ein)"
 
 #: NOT FOUND IN SOURCE

Modified: rt/branches/rt-3.1/lib/RT/I18N/en.po
==============================================================================
--- rt/branches/rt-3.1/lib/RT/I18N/en.po	(original)
+++ rt/branches/rt-3.1/lib/RT/I18N/en.po	Sat Jul 10 02:05:15 2004
@@ -58,7 +58,7 @@
 msgid "Open it"
 msgstr "Open"
 
-#: html/Admin/Users/Modify.html:156 html/User/Prefs.html:63
+#: html/Admin/Users/Modify.html:163 html/User/Prefs.html:63
 msgid "Residence"
 msgstr "Home"
 

Modified: rt/branches/rt-3.1/lib/RT/I18N/es.po
==============================================================================
--- rt/branches/rt-3.1/lib/RT/I18N/es.po	(original)
+++ rt/branches/rt-3.1/lib/RT/I18N/es.po	Sat Jul 10 02:05:15 2004
@@ -357,7 +357,7 @@
 msgstr ""
 
 #: html/Ticket/Create.html:178
-msgid "(Enter ticket ids or URLs, separated with spaces)"
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
 msgstr "(Introduzca los identificadores de ticket o URLs, separados por espacios)"
 
 #: NOT FOUND IN SOURCE

Modified: rt/branches/rt-3.1/lib/RT/I18N/fr.po
==============================================================================
--- rt/branches/rt-3.1/lib/RT/I18N/fr.po	(original)
+++ rt/branches/rt-3.1/lib/RT/I18N/fr.po	Sat Jul 10 02:05:15 2004
@@ -379,7 +379,7 @@
 msgstr "(Cocher les cases pour activer les notifications aux destinataires listés)"
 
 #: html/Ticket/Create.html:178
-msgid "(Enter ticket ids or URLs, separated with spaces)"
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
 msgstr "(Entrer les numéros de tickets ou les URLs, séparés par des espaces)"
 
 #: NOT FOUND IN SOURCE

Modified: rt/branches/rt-3.1/lib/RT/I18N/he.po
==============================================================================
--- rt/branches/rt-3.1/lib/RT/I18N/he.po	(original)
+++ rt/branches/rt-3.1/lib/RT/I18N/he.po	Sat Jul 10 02:05:15 2004
@@ -442,7 +442,7 @@
 msgstr ""
 
 #: html/Ticket/Create.html:178
-msgid "(Enter ticket ids or URLs, separated with spaces)"
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
 msgstr ""
 
 #: NOT FOUND IN SOURCE

Modified: rt/branches/rt-3.1/lib/RT/I18N/it.po
==============================================================================
--- rt/branches/rt-3.1/lib/RT/I18N/it.po	(original)
+++ rt/branches/rt-3.1/lib/RT/I18N/it.po	Sat Jul 10 02:05:15 2004
@@ -440,7 +440,7 @@
 msgstr ""
 
 #: html/Ticket/Create.html:178
-msgid "(Enter ticket ids or URLs, separated with spaces)"
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
 msgstr "(Inserire il numero di tickets o gli URL, separati da spazi)"
 
 #: NOT FOUND IN SOURCE

Modified: rt/branches/rt-3.1/lib/RT/I18N/ja.po
==============================================================================
--- rt/branches/rt-3.1/lib/RT/I18N/ja.po	(original)
+++ rt/branches/rt-3.1/lib/RT/I18N/ja.po	Sat Jul 10 02:05:15 2004
@@ -446,7 +446,7 @@
 msgstr ""
 
 #: html/Ticket/Create.html:178
-msgid "(Enter ticket ids or URLs, separated with spaces)"
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
 msgstr "(チケットIDまたはURLsを空欄で区切って入力してください)"
 
 #: NOT FOUND IN SOURCE

Modified: rt/branches/rt-3.1/lib/RT/I18N/nl.po
==============================================================================
--- rt/branches/rt-3.1/lib/RT/I18N/nl.po	(original)
+++ rt/branches/rt-3.1/lib/RT/I18N/nl.po	Sat Jul 10 02:05:15 2004
@@ -440,7 +440,7 @@
 msgstr ""
 
 #: html/Ticket/Create.html:178
-msgid "(Enter ticket ids or URLs, separated with spaces)"
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
 msgstr "(Vul ticket ids of URLs in, gescheiden door spaties)"
 
 #: NOT FOUND IN SOURCE

Modified: rt/branches/rt-3.1/lib/RT/I18N/pt_br.po
==============================================================================
--- rt/branches/rt-3.1/lib/RT/I18N/pt_br.po	(original)
+++ rt/branches/rt-3.1/lib/RT/I18N/pt_br.po	Sat Jul 10 02:05:15 2004
@@ -445,7 +445,7 @@
 msgstr ""
 
 #: html/Ticket/Create.html:178
-msgid "(Enter ticket ids or URLs, separated with spaces)"
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
 msgstr "(Entre com identificadores de tíquetes ou URLs, separados por espaços)"
 
 #: NOT FOUND IN SOURCE

Modified: rt/branches/rt-3.1/lib/RT/I18N/ru.po
==============================================================================
--- rt/branches/rt-3.1/lib/RT/I18N/ru.po	(original)
+++ rt/branches/rt-3.1/lib/RT/I18N/ru.po	Sat Jul 10 02:05:15 2004
@@ -442,7 +442,7 @@
 msgstr ""
 
 #: html/Ticket/Create.html:178
-msgid "(Enter ticket ids or URLs, separated with spaces)"
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
 msgstr "(Введите номера или ссылки на тикеты. Несколько тикетов разделяются пробелами.)"
 
 #: NOT FOUND IN SOURCE

Modified: rt/branches/rt-3.1/lib/RT/Interface/Web.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/Interface/Web.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/Interface/Web.pm	Sat Jul 10 02:05:15 2004
@@ -462,6 +462,7 @@
                 TimeTaken    => $args{ARGSRef}->{'UpdateTimeWorked'}
             );
             push ( @{ $args{Actions} }, $Description );
+	    $Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
         }
         elsif ( $args{ARGSRef}->{'UpdateType'} eq 'response' ) {
             my ( $Transaction, $Description, $Object ) = $args{TicketObj}->Correspond(
@@ -471,6 +472,7 @@
                 TimeTaken    => $args{ARGSRef}->{'UpdateTimeWorked'}
             );
             push ( @{ $args{Actions} }, $Description );
+	    $Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
         }
         else {
             push ( @{ $args{'Actions'} },
@@ -1070,112 +1072,145 @@
 
 # }}}
 
-# {{{ Sub ProcessTicketCustomFieldUpdates
-
 sub ProcessTicketCustomFieldUpdates {
-    my %args = (
-        ARGSRef => undef,
-        @_
-    );
+    my %args = @_;
+    $args{'Object'} = delete $args{'TicketObj'};
+    my $ARGSRef = { %{ $args{'ARGSRef'} } };
 
-    my @results;
+    # Build up a list of objects that we want to work with
+    my %custom_fields_to_mod;
+    foreach my $arg ( keys %$ARGSRef ) {
+        if ( $arg =~ /^Ticket-(\d+-.*)/) {
+	    $ARGSRef->{"Object-RT::Ticket-$1"} = delete $ARGSRef->{$arg};
+	}
+        elsif ( $arg =~ /^CustomField-(\d+-.*)/) {
+	    $ARGSRef->{"Object-RT::Ticket--$1"} = delete $ARGSRef->{$arg};
+	}
+    }
 
+    return ProcessObjectCustomFieldUpdates(%args, ARGSRef => $ARGSRef);
+}
+
+sub ProcessObjectCustomFieldUpdates {
+    my %args = @_;
     my $ARGSRef = $args{'ARGSRef'};
+    my @results;
 
-    # Build up a list of tickets that we want to work with
-    my %tickets_to_mod;
+    # Build up a list of objects that we want to work with
     my %custom_fields_to_mod;
-    foreach my $arg ( keys %{$ARGSRef} ) {
-        if ( $arg =~ /^Ticket-(\d+)-CustomField-(\d+)-/ ) {
-
-            # For each of those tickets, find out what custom fields we want to work with.
-            $custom_fields_to_mod{$1}{$2} = 1;
+    foreach my $arg ( keys %$ARGSRef ) {
+        if ( $arg =~ /^Object-([\w:]+)-(\d*)-CustomField-(\d+)-/ ) {
+            # For each of those objects, find out what custom fields we want to work with.
+            $custom_fields_to_mod{$1}{$2 || $args{'Object'}->Id}{$3} = 1;
         }
     }
 
-    # For each of those tickets
-    foreach my $tick ( keys %custom_fields_to_mod ) {
-        my $Ticket = $args{'TicketObj'};
-	if (!$Ticket or $Ticket->id != $tick) {
-	    $Ticket = RT::Ticket->new( $session{'CurrentUser'} );
-	    $Ticket->Load($tick);
+    # For each of those objects
+    foreach my $class ( keys %custom_fields_to_mod ) {
+	foreach my $id ( keys %{$custom_fields_to_mod{$class}} ) {
+	    my $Object = $args{'Object'};
+	    if (!$Object or ref($Object) ne $class or $Object->id != $id) {
+		$Object = $class->new( $session{'CurrentUser'} );
+		$Object->Load($id);
 	}
 
-        # For each custom field  
-        foreach my $cf ( keys %{ $custom_fields_to_mod{$tick} } ) {
-
+	    # For each custom field  
+	    foreach my $cf ( keys %{ $custom_fields_to_mod{$class}{$id} } ) {
 	    my $CustomFieldObj = RT::CustomField->new($session{'CurrentUser'});
 	    $CustomFieldObj->LoadById($cf);
 
-            foreach my $arg ( keys %{$ARGSRef} ) {
-                # since http won't pass in a form element with a null value, we need
-                # to fake it
-                if ($arg =~ /^(.*?)-Values-Magic$/ ) {
-                    # We don't care about the magic, if there's really a values element;
-                    next if (exists $ARGSRef->{$1.'-Values'}) ;
-
-                    $arg = $1."-Values";
-                    $ARGSRef->{$1."-Values"} = undef;
-                
-                }
-                next unless ( $arg =~ /^Ticket-$tick-CustomField-$cf-/ );
-                my @values =
-                  ( ref( $ARGSRef->{$arg} ) eq 'ARRAY' ) 
-                  ? @{ $ARGSRef->{$arg} }
-                  : split /\n/, $ARGSRef->{$arg} ;
-                if ( ( $arg =~ /-AddValue$/ ) || ( $arg =~ /-Value$/ ) ) {
-                    foreach my $value (@values) {
-                        next unless length($value);
-                        my ( $val, $msg ) = $Ticket->AddCustomFieldValue(
-                            Field => $cf,
-                            Value => $value
-                        );
-                        push ( @results, $msg );
-                    }
-                }
-                elsif ( $arg =~ /-DeleteValues$/ ) {
-                    foreach my $value (@values) {
-                        next unless length($value);
-                        my ( $val, $msg ) = $Ticket->DeleteCustomFieldValue(
-                            Field => $cf,
-                            Value => $value
-                        );
-                        push ( @results, $msg );
-                    }
-                }
-                elsif ( $arg =~ /-Values$/ and $CustomFieldObj->Type !~ /Entry/) {
-                    my $cf_values = $Ticket->CustomFieldValues($cf);
-
-                    my %values_hash;
-                    foreach my $value (@values) {
-                        next unless length($value);
-
-                        # build up a hash of values that the new set has
-                        $values_hash{$value} = 1;
-
-                        unless ( $cf_values->HasEntry($value) ) {
-                            my ( $val, $msg ) = $Ticket->AddCustomFieldValue(
-                                Field => $cf,
-                                Value => $value
-                            );
-                            push ( @results, $msg );
-                        }
-
-                    }
-                    while ( my $cf_value = $cf_values->Next ) {
-                        unless ( $values_hash{ $cf_value->Content } == 1 ) {
-                            my ( $val, $msg ) = $Ticket->DeleteCustomFieldValue(
-                                Field => $cf,
-                                Value => $cf_value->Content
-                            );
-                            push ( @results, $msg);
-
-                        }
-
-                    }
-                }
-                elsif ( $arg =~ /-Values$/ ) {
-                    my $cf_values = $Ticket->CustomFieldValues($cf);
+		foreach my $arg ( keys %{$ARGSRef} ) {
+		    # since http won't pass in a form element with a null value, we need
+		    # to fake it
+		    if ($arg =~ /^(.*?)-Values-Magic$/ ) {
+			# We don't care about the magic, if there's really a values element;
+			next if (exists $ARGSRef->{$1.'-Values'}) ;
+
+			$arg = $1."-Values";
+			$ARGSRef->{$1."-Values"} = undef;
+		    
+		    }
+		    next unless ( $arg =~ /^Object-$class-(?:$id)?-CustomField-$cf-/ );
+		    my @values =
+		    ( ref( $ARGSRef->{$arg} ) eq 'ARRAY' ) 
+		    ? @{ $ARGSRef->{$arg} }
+		    : split /\n/, $ARGSRef->{$arg} ;
+		    if ( ( $arg =~ /-AddValue$/ ) || ( $arg =~ /-Value$/ ) ) {
+			foreach my $value (@values) {
+			    next unless length($value);
+			    my ( $val, $msg ) = $Object->AddCustomFieldValue(
+				Field => $cf,
+				Value => $value
+			    );
+			    push ( @results, $msg );
+			}
+		    }
+		    elsif ( $arg =~ /-Upload$/ ) {
+			my $cgi_object = $m->cgi_object;
+			my $fh = $cgi_object->upload($arg) or next;
+			my $upload_info = $cgi_object->uploadInfo($fh);
+			my $filename = "$fh";
+			$filename =~ s#^.*[\\/]##;
+			my ( $val, $msg ) = $Object->AddCustomFieldValue(
+			    Field => $cf,
+			    Value => $filename,
+			    LargeContent => do { local $/; scalar <$fh> },
+			    ContentType => $upload_info->{'Content-Type'},
+			);
+			push ( @results, $msg );
+		    }
+		    elsif ( $arg =~ /-DeleteValues$/ ) {
+			foreach my $value (@values) {
+			    next unless length($value);
+			    my ( $val, $msg ) = $Object->DeleteCustomFieldValue(
+				Field => $cf,
+				Value => $value
+			    );
+			    push ( @results, $msg );
+			}
+		    }
+		    elsif ( $arg =~ /-DeleteValueIds$/ ) {
+			foreach my $value (@values) {
+			    next unless length($value);
+			    my ( $val, $msg ) = $Object->DeleteCustomFieldValue(
+				Field => $cf,
+				ValueId => $value,
+			    );
+			    push ( @results, $msg );
+			}
+		    }
+		    elsif ( $arg =~ /-Values$/ and !$CustomFieldObj->Repeated) {
+			my $cf_values = $Object->CustomFieldValues($cf);
+
+			my %values_hash;
+			foreach my $value (@values) {
+			    next unless length($value);
+
+			    # build up a hash of values that the new set has
+			    $values_hash{$value} = 1;
+
+			    unless ( $cf_values->HasEntry($value) ) {
+				my ( $val, $msg ) = $Object->AddCustomFieldValue(
+				    Field => $cf,
+				    Value => $value
+				);
+				push ( @results, $msg );
+			    }
+
+			}
+			while ( my $cf_value = $cf_values->Next ) {
+			    unless ( $values_hash{ $cf_value->Content } == 1 ) {
+				my ( $val, $msg ) = $Object->DeleteCustomFieldValue(
+				    Field => $cf,
+				    Value => $cf_value->Content
+				);
+				push ( @results, $msg);
+
+			    }
+			}
+		    }
+		    elsif ( $arg =~ /-Values$/ ) {
+			my $cf_values = $Object->CustomFieldValues($cf);
 
 		    # keep everything up to the point of difference, delete the rest
 		    my $delete_flag;
@@ -1191,24 +1226,23 @@
 
 		    # now add/replace extra things, if any
 		    foreach my $value (@values) {
-			my ( $val, $msg ) = $Ticket->AddCustomFieldValue(
+			    my ( $val, $msg ) = $Object->AddCustomFieldValue(
 			    Field => $cf,
 			    Value => $value
 			);
 			push ( @results, $msg );
 		    }
 		}
-                else {
-                    push ( @results, "User asked for an unknown update type for custom field " . $cf->Name . " for ticket " . $Ticket->id );
-                }
-            }
-        }
-        return (@results);
+		    else {
+			push ( @results, loc("User asked for an unknown update type for custom field [_1] for [_2] object #[_3]", $cf->Name, $class, $Object->id ) );
+		    }
+		}
+	    }
+	    return (@results);
+	}
     }
 }
 
-# }}}
-
 # {{{ sub ProcessTicketWatchers
 
 =head2 ProcessTicketWatchers ( TicketObj => $Ticket, ARGSRef => \%ARGS );

Modified: rt/branches/rt-3.1/lib/RT/Link_Overlay.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/Link_Overlay.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/Link_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -81,23 +81,31 @@
     my $base = RT::URI->new( $self->CurrentUser );
     $base->FromURI( $args{'Base'} );
 
-    unless ( $base->Resolver and $base->Scheme ) {
-        $RT::Logger->warning( "$self couldn't resolve base:'"
-                              . $args{'Base'} . " - "
-                              . "' into a URI\n" );
-
-        return (undef);
+    unless ( $base->Resolver && $base->Scheme ) {
+	my $msg = $self->loc("Couldn't resolve base '[_1]' into a URI.", 
+			     $args{'Base'});
+        $RT::Logger->warning( "$self $msg\n" );
+
+	if (wantarray) {
+	    return(undef, $msg);
+	} else {
+	    return (undef);
+	}
     }
 
     my $target = RT::URI->new( $self->CurrentUser );
     $target->FromURI( $args{'Target'} );
 
     unless ( $target->Resolver ) {
-        $RT::Logger->warning( "$self couldn't resolve target:'"
-                              . $args{'Target'} . " - "
-                              . "' into a URI\n" );
-
-        return (undef);
+	my $msg = $self->loc("Couldn't resolve target '[_1]' into a URI.", 
+			     $args{'Target'});
+        $RT::Logger->warning( "$self $msg\n" );
+
+	if (wantarray) {
+	    return(undef, $msg);
+	} else {
+	    return (undef);
+	}
     }
 
     my $base_id   = 0;

Added: rt/branches/rt-3.1/lib/RT/ObjectCustomField.pm
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/lib/RT/ObjectCustomField.pm	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,272 @@
+# BEGIN LICENSE BLOCK
+# 
+# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+# 
+# (Except where explictly superceded by other copyright notices)
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+# 
+# 
+# END LICENSE BLOCK
+
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse at bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
+# 
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::ObjectCustomField
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::ObjectCustomField;
+use RT::Record; 
+use RT::CustomField;
+
+
+use vars qw( @ISA );
+ at ISA= qw( RT::Record );
+
+sub _Init {
+  my $self = shift; 
+
+  $self->Table('ObjectCustomFields');
+  $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=head2 Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+  int(11) 'CustomField'.
+  int(11) 'ObjectId'.
+  int(11) 'SortOrder'.
+
+=cut
+
+
+
+
+sub Create {
+    my $self = shift;
+    my %args = ( 
+                CustomField => '0',
+                ObjectId => '0',
+                SortOrder => '0',
+
+		  @_);
+    $self->SUPER::Create(
+                         CustomField => $args{'CustomField'},
+                         ObjectId => $args{'ObjectId'},
+                         SortOrder => $args{'SortOrder'},
+);
+
+}
+
+
+
+=head2 id
+
+Returns the current value of id. 
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=head2 CustomField
+
+Returns the current value of CustomField. 
+(In the database, CustomField is stored as int(11).)
+
+
+
+=head2 SetCustomField VALUE
+
+
+Set CustomField to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, CustomField will be stored as a int(11).)
+
+
+=cut
+
+
+=head2 CustomFieldObj
+
+Returns the CustomField Object which has the id returned by CustomField
+
+
+=cut
+
+sub CustomFieldObj {
+	my $self = shift;
+	my $CustomField =  RT::CustomField->new($self->CurrentUser);
+	$CustomField->Load($self->__Value('CustomField'));
+	return($CustomField);
+}
+
+=head2 ObjectId
+
+Returns the current value of ObjectId. 
+(In the database, ObjectId is stored as int(11).)
+
+
+
+=head2 SetObjectId VALUE
+
+
+Set ObjectId to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ObjectId will be stored as a int(11).)
+
+
+=cut
+
+
+=head2 SortOrder
+
+Returns the current value of SortOrder. 
+(In the database, SortOrder is stored as int(11).)
+
+
+
+=head2 SetSortOrder VALUE
+
+
+Set SortOrder to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, SortOrder will be stored as a int(11).)
+
+
+=cut
+
+
+=head2 Creator
+
+Returns the current value of Creator. 
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=head2 Created
+
+Returns the current value of Created. 
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+=head2 LastUpdatedBy
+
+Returns the current value of LastUpdatedBy. 
+(In the database, LastUpdatedBy is stored as int(11).)
+
+
+=cut
+
+
+=head2 LastUpdated
+
+Returns the current value of LastUpdated. 
+(In the database, LastUpdated is stored as datetime.)
+
+
+=cut
+
+
+
+sub _CoreAccessible {
+    {
+     
+        id =>
+		{read => 1, type => 'int(11)', default => ''},
+        CustomField => 
+		{read => 1, write => 1, type => 'int(11)', default => '0'},
+        ObjectId => 
+		{read => 1, write => 1, type => 'int(11)', default => '0'},
+        SortOrder => 
+		{read => 1, write => 1, type => 'int(11)', default => '0'},
+        Creator => 
+		{read => 1, auto => 1, type => 'int(11)', default => '0'},
+        Created => 
+		{read => 1, auto => 1, type => 'datetime', default => ''},
+        LastUpdatedBy => 
+		{read => 1, auto => 1, type => 'int(11)', default => '0'},
+        LastUpdated => 
+		{read => 1, auto => 1, type => 'datetime', default => ''},
+
+ }
+};
+
+
+        eval "require RT::ObjectCustomField_Overlay";
+        if ($@ && $@ !~ qr{^Can't locate RT/ObjectCustomField_Overlay.pm}) {
+            die $@;
+        };
+
+        eval "require RT::ObjectCustomField_Vendor";
+        if ($@ && $@ !~ qr{^Can't locate RT/ObjectCustomField_Vendor.pm}) {
+            die $@;
+        };
+
+        eval "require RT::ObjectCustomField_Local";
+        if ($@ && $@ !~ qr{^Can't locate RT/ObjectCustomField_Local.pm}) {
+            die $@;
+        };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.  
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+
+   no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::ObjectCustomField_Overlay, RT::ObjectCustomField_Vendor, RT::ObjectCustomField_Local
+
+=cut
+
+
+1;

Added: rt/branches/rt-3.1/lib/RT/ObjectCustomFieldValue.pm
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/lib/RT/ObjectCustomFieldValue.pm	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,387 @@
+# BEGIN LICENSE BLOCK
+# 
+# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+# 
+# (Except where explictly superceded by other copyright notices)
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+# 
+# 
+# END LICENSE BLOCK
+
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse at bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
+# 
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::ObjectCustomFieldValue
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::ObjectCustomFieldValue;
+use RT::Record; 
+use RT::CustomField;
+
+
+use vars qw( @ISA );
+ at ISA= qw( RT::Record );
+
+sub _Init {
+  my $self = shift; 
+
+  $self->Table('ObjectCustomFieldValues');
+  $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=head2 Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+  int(11) 'CustomField'.
+  varchar(255) 'ObjectType'.
+  int(11) 'ObjectId'.
+  tinyint(1) 'Current' defaults to '1'.
+  varchar(255) 'Content'.
+  longtext 'LargeContent'.
+  varchar(80) 'ContentType'.
+  varchar(80) 'ContentEncoding'.
+
+=cut
+
+
+
+
+sub Create {
+    my $self = shift;
+    my %args = ( 
+                CustomField => '0',
+                ObjectType => '',
+                ObjectId => '0',
+                Current => '1',
+                Content => '',
+                LargeContent => '',
+                ContentType => '',
+                ContentEncoding => '',
+
+		  @_);
+    $self->SUPER::Create(
+                         CustomField => $args{'CustomField'},
+                         ObjectType => $args{'ObjectType'},
+                         ObjectId => $args{'ObjectId'},
+                         Current => $args{'Current'},
+                         Content => $args{'Content'},
+                         LargeContent => $args{'LargeContent'},
+                         ContentType => $args{'ContentType'},
+                         ContentEncoding => $args{'ContentEncoding'},
+);
+
+}
+
+
+
+=head2 id
+
+Returns the current value of id. 
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=head2 CustomField
+
+Returns the current value of CustomField. 
+(In the database, CustomField is stored as int(11).)
+
+
+
+=head2 SetCustomField VALUE
+
+
+Set CustomField to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, CustomField will be stored as a int(11).)
+
+
+=cut
+
+
+=head2 CustomFieldObj
+
+Returns the CustomField Object which has the id returned by CustomField
+
+
+=cut
+
+sub CustomFieldObj {
+	my $self = shift;
+	my $CustomField =  RT::CustomField->new($self->CurrentUser);
+	$CustomField->Load($self->__Value('CustomField'));
+	return($CustomField);
+}
+
+=head2 ObjectType
+
+Returns the current value of ObjectType. 
+(In the database, ObjectType is stored as varchar(255).)
+
+
+
+=head2 SetObjectType VALUE
+
+
+Set ObjectType to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ObjectType will be stored as a varchar(255).)
+
+
+=cut
+
+
+=head2 ObjectId
+
+Returns the current value of ObjectId. 
+(In the database, ObjectId is stored as int(11).)
+
+
+
+=head2 SetObjectId VALUE
+
+
+Set ObjectId to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ObjectId will be stored as a int(11).)
+
+
+=cut
+
+
+=head2 Current
+
+Returns the current value of Current. 
+(In the database, Current is stored as tinyint(1).)
+
+
+
+=head2 SetCurrent VALUE
+
+
+Set Current to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Current will be stored as a tinyint(1).)
+
+
+=cut
+
+
+=head2 Content
+
+Returns the current value of Content. 
+(In the database, Content is stored as varchar(255).)
+
+
+
+=head2 SetContent VALUE
+
+
+Set Content to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Content will be stored as a varchar(255).)
+
+
+=cut
+
+
+=head2 LargeContent
+
+Returns the current value of LargeContent. 
+(In the database, LargeContent is stored as longtext.)
+
+
+
+=head2 SetLargeContent VALUE
+
+
+Set LargeContent to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, LargeContent will be stored as a longtext.)
+
+
+=cut
+
+
+=head2 ContentType
+
+Returns the current value of ContentType. 
+(In the database, ContentType is stored as varchar(80).)
+
+
+
+=head2 SetContentType VALUE
+
+
+Set ContentType to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ContentType will be stored as a varchar(80).)
+
+
+=cut
+
+
+=head2 ContentEncoding
+
+Returns the current value of ContentEncoding. 
+(In the database, ContentEncoding is stored as varchar(80).)
+
+
+
+=head2 SetContentEncoding VALUE
+
+
+Set ContentEncoding to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ContentEncoding will be stored as a varchar(80).)
+
+
+=cut
+
+
+=head2 Creator
+
+Returns the current value of Creator. 
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=head2 Created
+
+Returns the current value of Created. 
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+=head2 LastUpdatedBy
+
+Returns the current value of LastUpdatedBy. 
+(In the database, LastUpdatedBy is stored as int(11).)
+
+
+=cut
+
+
+=head2 LastUpdated
+
+Returns the current value of LastUpdated. 
+(In the database, LastUpdated is stored as datetime.)
+
+
+=cut
+
+
+
+sub _CoreAccessible {
+    {
+     
+        id =>
+		{read => 1, type => 'int(11)', default => ''},
+        CustomField => 
+		{read => 1, write => 1, type => 'int(11)', default => '0'},
+        ObjectType => 
+		{read => 1, write => 1, type => 'varchar(255)', default => ''},
+        ObjectId => 
+		{read => 1, write => 1, type => 'int(11)', default => '0'},
+        Current => 
+		{read => 1, write => 1, type => 'tinyint(1)', default => '1'},
+        Content => 
+		{read => 1, write => 1, type => 'varchar(255)', default => ''},
+        LargeContent => 
+		{read => 1, write => 1, type => 'longtext', default => ''},
+        ContentType => 
+		{read => 1, write => 1, type => 'varchar(80)', default => ''},
+        ContentEncoding => 
+		{read => 1, write => 1, type => 'varchar(80)', default => ''},
+        Creator => 
+		{read => 1, auto => 1, type => 'int(11)', default => '0'},
+        Created => 
+		{read => 1, auto => 1, type => 'datetime', default => ''},
+        LastUpdatedBy => 
+		{read => 1, auto => 1, type => 'int(11)', default => '0'},
+        LastUpdated => 
+		{read => 1, auto => 1, type => 'datetime', default => ''},
+
+ }
+};
+
+
+        eval "require RT::ObjectCustomFieldValue_Overlay";
+        if ($@ && $@ !~ qr{^Can't locate RT/ObjectCustomFieldValue_Overlay.pm}) {
+            die $@;
+        };
+
+        eval "require RT::ObjectCustomFieldValue_Vendor";
+        if ($@ && $@ !~ qr{^Can't locate RT/ObjectCustomFieldValue_Vendor.pm}) {
+            die $@;
+        };
+
+        eval "require RT::ObjectCustomFieldValue_Local";
+        if ($@ && $@ !~ qr{^Can't locate RT/ObjectCustomFieldValue_Local.pm}) {
+            die $@;
+        };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.  
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+
+   no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::ObjectCustomFieldValue_Overlay, RT::ObjectCustomFieldValue_Vendor, RT::ObjectCustomFieldValue_Local
+
+=cut
+
+
+1;

Added: rt/branches/rt-3.1/lib/RT/ObjectCustomFieldValue_Overlay.pm
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/lib/RT/ObjectCustomFieldValue_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+# 
+# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+# 
+# (Except where explictly superceded by other copyright notices)
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+# 
+# 
+# END LICENSE BLOCK
+use strict;
+no warnings qw(redefine);
+
+
+sub Create {
+    my $self = shift;
+    my %args = (
+                CustomField => '0',
+                ObjectType => '',
+                ObjectId => '0',
+                Current => '1',
+                Content => '',
+                LargeContent => '',
+                ContentType => '',
+                ContentEncoding => '',
+
+          @_);
+    ($args{'ContentEncoding'}, $args{'LargeContent'}) = $self->_EncodeLOB($args{'LargeContent'}, $args{'ContentType'}) if ($args{'LargeContent'}); 
+    $self->SUPER::Create(
+                         CustomField => $args{'CustomField'},
+                         ObjectType => $args{'ObjectType'},
+                         ObjectId => $args{'ObjectId'},
+                         Current => $args{'Current'},
+                         Content => $args{'Content'},
+                         LargeContent => $args{'LargeContent'},
+                         ContentType => $args{'ContentType'},
+                         ContentEncoding => $args{'ContentEncoding'},
+);
+
+
+
+}
+
+
+sub LargeContent {
+    my $self = shift;
+    $self->_DecodeLOB( $self->ContentType, $self->ContentEncoding,
+        $self->_Value( 'LargeContent', decode_utf8 => 0 ) );
+
+}
+
+
+
+
+=head2 LoadByTicketContentAndCustomField { Ticket => TICKET, CustomField => CUSTOMFIELD, Content => CONTENT }
+
+Loads a custom field value by Ticket, Content and which CustomField it's tied to
+
+=cut
+
+
+sub LoadByTicketContentAndCustomField {
+    my $self = shift;
+    my %args = ( Ticket => undef,
+                CustomField => undef,
+                Content => undef,
+                @_
+                );
+
+
+    $self->LoadByCols( Content => $args{'Content'},
+                         CustomField => $args{'CustomField'},
+                         ObjectType => 'RT::Ticket',
+                         ObjectId => $args{'Ticket'},);
+
+    
+}
+
+sub LoadByObjectContentAndCustomField {
+    my $self = shift;
+    my %args = ( Object => undef,
+                CustomField => undef,
+                Content => undef,
+                @_
+                );
+
+    my $obj = $args{'Object'} or return;
+
+    $self->LoadByCols( Content => $args{'Content'},
+                         CustomField => $args{'CustomField'},
+                         ObjectType => ref($obj),
+                         ObjectId => $obj->Id,);
+
+    
+}
+
+sub Delete {
+    my $self = shift;
+    $self->SetCurrent(0);
+}
+
+1;

Added: rt/branches/rt-3.1/lib/RT/ObjectCustomFieldValues.pm
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/lib/RT/ObjectCustomFieldValues.pm	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,116 @@
+# BEGIN LICENSE BLOCK
+# 
+# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+# 
+# (Except where explictly superceded by other copyright notices)
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+# 
+# 
+# END LICENSE BLOCK
+
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse at bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
+# 
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+  RT::ObjectCustomFieldValues -- Class Description
+ 
+=head1 SYNOPSIS
+
+  use RT::ObjectCustomFieldValues
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::ObjectCustomFieldValues;
+
+use RT::SearchBuilder;
+use RT::ObjectCustomFieldValue;
+
+use vars qw( @ISA );
+ at ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+    my $self = shift;
+    $self->{'table'} = 'ObjectCustomFieldValues';
+    $self->{'primary_key'} = 'id';
+
+
+    return ( $self->SUPER::_Init(@_) );
+}
+
+
+=head2 NewItem
+
+Returns an empty new RT::ObjectCustomFieldValue item
+
+=cut
+
+sub NewItem {
+    my $self = shift;
+    return(RT::ObjectCustomFieldValue->new($self->CurrentUser));
+}
+
+        eval "require RT::ObjectCustomFieldValues_Overlay";
+        if ($@ && $@ !~ qr{^Can't locate RT/ObjectCustomFieldValues_Overlay.pm}) {
+            die $@;
+        };
+
+        eval "require RT::ObjectCustomFieldValues_Vendor";
+        if ($@ && $@ !~ qr{^Can't locate RT/ObjectCustomFieldValues_Vendor.pm}) {
+            die $@;
+        };
+
+        eval "require RT::ObjectCustomFieldValues_Local";
+        if ($@ && $@ !~ qr{^Can't locate RT/ObjectCustomFieldValues_Local.pm}) {
+            die $@;
+        };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.  
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+
+   no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::ObjectCustomFieldValues_Overlay, RT::ObjectCustomFieldValues_Vendor, RT::ObjectCustomFieldValues_Local
+
+=cut
+
+
+1;

Added: rt/branches/rt-3.1/lib/RT/ObjectCustomFieldValues_Overlay.pm
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/lib/RT/ObjectCustomFieldValues_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,133 @@
+# BEGIN LICENSE BLOCK
+# 
+# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+# 
+# (Except where explictly superceded by other copyright notices)
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+# 
+# 
+# END LICENSE BLOCK
+use strict;
+no warnings qw(redefine);
+
+# {{{ sub LimitToCustomField
+
+=head2 LimitToCustomField FIELD
+
+Limits the returned set to values for the custom field with Id FIELD
+
+=cut
+  
+sub LimitToCustomField {
+    my $self = shift;
+    my $cf = shift;
+    return ($self->Limit( FIELD => 'CustomField',
+			  VALUE => $cf,
+			  OPERATOR => '='));
+
+}
+
+# }}}
+
+# {{{ sub LimitToTicket
+
+=head2 LimitToTicket TICKETID
+
+Limits the returned set to values for the ticket with Id TICKETID
+
+=cut
+  
+sub LimitToTicket {
+    my $self = shift;
+    my $ticket = shift;
+    $self->Limit( FIELD => 'ObjectType',
+		  VALUE => 'RT::Ticket',
+		  OPERATOR => '=');
+    return ($self->Limit( FIELD => 'ObjectId',
+			  VALUE => $ticket,
+			  OPERATOR => '='));
+
+}
+
+# }}}
+
+
+sub LimitToObject {
+    my $self = shift;
+    my $object = shift;
+    $self->Limit( FIELD => 'ObjectType',
+		  VALUE => ref($object),
+		  OPERATOR => '=');
+    return ($self->Limit( FIELD => 'ObjectId',
+			  VALUE => $object->Id,
+			  OPERATOR => '='));
+
+}
+
+=sub HasEntry VALUE
+
+Returns true if this CustomFieldValues collection has an entry with content that eq VALUE
+
+=cut
+
+
+sub HasEntry {
+    my $self = shift;
+    my $value = shift;
+
+    #TODO: this could cache and optimize a fair bit.
+    foreach my $item (@{$self->ItemsArrayRef}) {
+        return(1) if ($item->Content eq $value);  
+    }
+    return undef;
+
+}
+
+sub _DoSearch {
+    my $self = shift;
+    
+    #unless we really want to find disabled rows, make sure we\'re only finding enabled ones.
+    unless($self->{'find_expired_rows'}) {
+        $self->LimitToCurrent();
+    }
+    
+    return($self->SUPER::_DoSearch(@_));
+    
+}
+
+sub _DoCount {
+    my $self = shift;
+    
+    #unless we really want to find disabled rows, make sure we\'re only finding enabled ones.
+    unless($self->{'find_expired_rows'}) {
+        $self->LimitToCurrent();
+    }
+    
+    return($self->SUPER::_DoCount(@_));
+    
+}
+
+sub LimitToCurrent {
+    my $self = shift;
+    
+    $self->Limit( FIELD => 'Current',
+		  VALUE => '1',
+		  OPERATOR => '=' );
+}
+
+1;
+

Added: rt/branches/rt-3.1/lib/RT/ObjectCustomField_Overlay.pm
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/lib/RT/ObjectCustomField_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,78 @@
+# BEGIN LICENSE BLOCK
+# 
+# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+# 
+# (Except where explictly superceded by other copyright notices)
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+# 
+# 
+# END LICENSE BLOCK
+
+use strict;
+no warnings qw(redefine);
+
+sub Create {
+    my $self = shift;
+    my %args = ( 
+                CustomField => '0',
+                ObjectId => '0',
+		SortOrder => undef,
+		  @_);
+
+    if (!defined $args{SortOrder}) {
+	my $CF = $self->CustomFieldObj($args{'CustomField'});
+	my $ObjectCFs = RT::ObjectCustomFields->new($self->CurrentUser);
+	$ObjectCFs->LimitToObjectId($args{'ObjectId'});
+	$ObjectCFs->LimitToLookupType($CF->LookupType);
+
+	$args{SortOrder} = $ObjectCFs->Count + 1;
+    }
+
+    $self->SUPER::Create(
+                         CustomField => $args{'CustomField'},
+                         ObjectId => $args{'ObjectId'},
+                         SortOrder => $args{'SortOrder'},
+		     );
+}
+
+sub Delete {
+    my $self = shift;
+
+    my $ObjectCFs = RT::ObjectCustomFields->new($self->CurrentUser);
+    $ObjectCFs->LimitToObjectId($self->ObjectId);
+    $ObjectCFs->LimitToLookupType($self->CustomFieldObj->LookupType);
+
+    # Move everything below us up
+    my $sort_order = $self->SortOrder;
+    while (my $OCF = $ObjectCFs->Next) {
+	my $this_order = $OCF->SortOrder;
+	next if $this_order <= $sort_order; 
+	$OCF->SetSortOrder($this_order - 1);
+    }
+
+    $self->SUPER::Delete;
+}
+
+sub CustomFieldObj {
+    my $self = shift;
+    my $id = shift || $self->CustomField;
+    my $CF = RT::CustomField->new($self->CurrentUser);
+    $CF->Load($id) or die "Cannot load CustomField $id";
+    return $CF;
+}
+
+1;

Added: rt/branches/rt-3.1/lib/RT/ObjectCustomFields.pm
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/lib/RT/ObjectCustomFields.pm	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,122 @@
+# BEGIN LICENSE BLOCK
+# 
+# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+# 
+# (Except where explictly superceded by other copyright notices)
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+# 
+# 
+# END LICENSE BLOCK
+
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse at bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
+# 
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+  RT::ObjectCustomFields -- Class Description
+ 
+=head1 SYNOPSIS
+
+  use RT::ObjectCustomFields
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::ObjectCustomFields;
+
+use RT::SearchBuilder;
+use RT::ObjectCustomField;
+
+use vars qw( @ISA );
+ at ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+    my $self = shift;
+    $self->{'table'} = 'ObjectCustomFields';
+    $self->{'primary_key'} = 'id';
+
+
+
+  # By default, order by name
+  $self->OrderBy( ALIAS => 'main',
+                  FIELD => 'SortOrder',
+                  ORDER => 'ASC');
+
+    return ( $self->SUPER::_Init(@_) );
+}
+
+
+=head2 NewItem
+
+Returns an empty new RT::ObjectCustomField item
+
+=cut
+
+sub NewItem {
+    my $self = shift;
+    return(RT::ObjectCustomField->new($self->CurrentUser));
+}
+
+        eval "require RT::ObjectCustomFields_Overlay";
+        if ($@ && $@ !~ qr{^Can't locate RT/ObjectCustomFields_Overlay.pm}) {
+            die $@;
+        };
+
+        eval "require RT::ObjectCustomFields_Vendor";
+        if ($@ && $@ !~ qr{^Can't locate RT/ObjectCustomFields_Vendor.pm}) {
+            die $@;
+        };
+
+        eval "require RT::ObjectCustomFields_Local";
+        if ($@ && $@ !~ qr{^Can't locate RT/ObjectCustomFields_Local.pm}) {
+            die $@;
+        };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.  
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line 
+
+   no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::ObjectCustomFields_Overlay, RT::ObjectCustomFields_Vendor, RT::ObjectCustomFields_Local
+
+=cut
+
+
+1;

Added: rt/branches/rt-3.1/lib/RT/ObjectCustomFields_Overlay.pm
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/lib/RT/ObjectCustomFields_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,77 @@
+# BEGIN LICENSE BLOCK
+# 
+# Copyright (c) 1996-2003 Jesse Vincent <jesse at bestpractical.com>
+# 
+# (Except where explictly superceded by other copyright notices)
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+# 
+# 
+# END LICENSE BLOCK
+
+use strict;
+no warnings qw(redefine);
+
+sub LimitToCustomField {
+    my $self = shift;
+    my $id = shift;
+    $self->Limit( FIELD => 'CustomField', VALUE => $id );
+}
+
+sub LimitToObjectId {
+    my $self = shift;
+    my $id = shift || 0;
+    $self->Limit( FIELD => 'ObjectId', VALUE => $id );
+}
+
+sub LimitToLookupType {
+    my $self = shift;
+    my $lookup = shift;
+    my $cfs = $self->NewAlias('CustomFields');
+    $self->Join( ALIAS1 => 'main',
+                FIELD1 => 'CustomField',
+                ALIAS2 => $cfs,
+                FIELD2 => 'id' );
+    $self->Limit( ALIAS           => $cfs,
+                 FIELD           => 'LookupType',
+                 OPERATOR        => '=',
+                 VALUE           => $lookup );
+}
+
+sub HasEntryForCustomField {
+    my $self = shift;
+    my $id = shift;
+
+    my @items = grep {$_->CustomField == $id } @{$self->ItemsArrayRef};
+
+    if ($#items > 1) {
+	die "$self HasEntry had a list with more than one of $id in it. this can never happen";
+    }
+    if ($#items == -1 ) {
+	return undef;
+    }
+    else {
+	return ($items[0]);
+    }  
+}
+
+sub CustomFields {
+    my $self = shift;
+    my %seen;
+    map { $_->CustomFieldObj } @{$self->ItemsArrayRef};
+}
+
+1;

Modified: rt/branches/rt-3.1/lib/RT/Queue_Overlay.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/Queue_Overlay.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/Queue_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -70,7 +70,7 @@
     ShowACL             => 'Display Access Control List',             # loc_pair
     ModifyACL           => 'Modify Access Control List',              # loc_pair
     ModifyQueueWatchers => 'Modify the queue watchers',               # loc_pair
-    AdminCustomFields   => 'Create, delete and modify custom fields', # loc_pair
+    AssignCustomFields  => 'Assign and remove custom fields',         # loc_pair
     ModifyTemplate      => 'Modify Scrip templates for this queue',   # loc_pair
     ShowTemplate        => 'Display Scrip templates for this queue',  # loc_pair
 
@@ -457,12 +457,32 @@
 
 =cut
 
+# XXX XXX - this should become TicketCustomFields
+
 sub CustomFields {
     my $self = shift;
+    warn "Queue->CustomFields is deprecated, use Queue->TicketCustomFields instead";
+    return $self->TicketCustomFields(@_);
+}
+
+sub TicketCustomFields {
+    my $self = shift;
+
+    my $cfs = RT::CustomFields->new( $self->CurrentUser );
+    if ( $self->CurrentUserHasRight('SeeQueue') ) {
+	$cfs->LimitToGlobalOrObjectId( $self->Id );
+	$cfs->LimitToLookupType( 'RT::Queue-RT::Ticket' );
+    }
+    return ($cfs);
+}
+
+sub TicketTransactionCustomFields {
+    my $self = shift;
 
     my $cfs = RT::CustomFields->new( $self->CurrentUser );
     if ( $self->CurrentUserHasRight('SeeQueue') ) {
-        $cfs->LimitToGlobalOrQueue( $self->Id );
+	$cfs->LimitToGlobalOrObjectId( $self->Id );
+	$cfs->LimitToLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' );
     }
     return ($cfs);
 }
@@ -1043,7 +1063,7 @@
     }
     return (
         $args{'Principal'}->HasRight(
-            Object => $self,
+            Object => $self->Id ? $self : $RT::System,
             Right    => $args{'Right'}
           )
     );

Modified: rt/branches/rt-3.1/lib/RT/Record.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/Record.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/Record.pm	Sat Jul 10 02:05:15 2004
@@ -676,6 +676,28 @@
 
 }
 
+sub _DecodeLOB {
+    my $self            = shift;
+    my $ContentType     = shift;
+    my $ContentEncoding = shift;
+    my $Content         = shift;
+
+    if ( $ContentEncoding eq 'base64' ) {
+        $Content = MIME::Base64::decode_base64($Content);
+    }
+    elsif ( $ContentEncoding eq 'quoted-printable' ) {
+        $Content = MIME::QuotedPrint::decode($Content);
+    }
+    elsif ( $ContentEncoding && $ContentEncoding ne 'none' ) {
+        return ( $self->loc( "Unknown ContentEncoding [_1]", $ContentEncoding ) );
+    }
+    if ( $ContentType eq 'text/plain' ) {
+        return Encode::decode_utf8($Content);
+    }
+    else {
+        return ($Content);
+    }
+}
 
 # {{{ LINKDIRMAP
 # A helper table for links mapping to make it easier
@@ -1176,6 +1198,431 @@
 
 # }}}
 
+# {{{ sub _NewTransaction
+
+=head2 _NewTransaction  PARAMHASH
+
+Private function to create a new RT::Transaction object for this ticket update
+
+=cut
+
+sub _NewTransaction {
+    my $self = shift;
+    my %args = (
+        TimeTaken => undef,
+        Type      => undef,
+        OldValue  => undef,
+        NewValue  => undef,
+        OldReference  => undef,
+        NewReference  => undef,
+        ReferenceType => undef,
+        Data      => undef,
+        Field     => undef,
+        MIMEObj   => undef,
+        ActivateScrips => 1,
+        CommitScrips => 1,
+        @_
+    );
+
+    my $old_ref = $args{'OldReference'};
+    my $new_ref = $args{'NewReference'};
+    my $ref_type = $args{'ReferenceType'};
+    if ($old_ref or $new_ref) {
+	$ref_type ||= ref($old_ref) || ref($new_ref);
+	if (!$ref_type) {
+	    $RT::Logger->error("Reference type not specified for transaction");
+	    return;
+	}
+	$old_ref = $old_ref->Id if ref($old_ref);
+	$new_ref = $new_ref->Id if ref($new_ref);
+    }
+
+    require RT::Transaction;
+    my $trans = new RT::Transaction( $self->CurrentUser );
+    my ( $transaction, $msg ) = $trans->Create(
+	ObjectId  => $self->Id,
+	ObjectType => ref($self),
+        TimeTaken => $args{'TimeTaken'},
+        Type      => $args{'Type'},
+        Data      => $args{'Data'},
+        Field     => $args{'Field'},
+        NewValue  => $args{'NewValue'},
+        OldValue  => $args{'OldValue'},
+        NewReference  => $new_ref,
+        OldReference  => $old_ref,
+        ReferenceType => $ref_type,
+        MIMEObj   => $args{'MIMEObj'},
+        ActivateScrips => $args{'ActivateScrips'},
+        CommitScrips => $args{'CommitScrips'},
+    );
+
+    # Rationalize the object since we may have done things to it during the caching.
+    $self->Load($self->Id);
+
+    $RT::Logger->warning($msg) unless $transaction;
+
+    $self->_SetLastUpdated;
+
+    if ( defined $args{'TimeTaken'} ) {
+        $self->_UpdateTimeTaken( $args{'TimeTaken'} );
+    }
+    if ( $RT::UseTransactionBatch and $transaction ) {
+	    push @{$self->{_TransactionBatch}}, $trans;
+    }
+    return ( $transaction, $msg, $trans );
+}
+
+# }}}
+
+# {{{ sub Transactions 
+
+=head2 Transactions
+
+  Returns an RT::Transactions object of all transactions on this ticket
+
+=cut
+
+sub Transactions {
+    my $self = shift;
+
+    use RT::Transactions;
+    my $transactions = RT::Transactions->new( $self->CurrentUser );
+
+    #If the user has no rights, return an empty object
+    $transactions->Limit(
+	FIELD => 'ObjectId',
+	VALUE => $self->id,
+    );
+    $transactions->Limit(
+	FIELD    => 'ObjectType',
+	VALUE    => ref($self),
+    );
+
+    return ($transactions);
+}
+
+# }}}
+
+# {{{ Routines dealing with custom fields
+
+sub CustomFields {
+    my $self = shift;
+    my $cfs = RT::CustomFields->new( $self->CurrentUser );
+    $cfs->UnLimit;
+
+    # XXX handle multiple types properly
+    foreach my $lookup ($self->_LookupTypes) {
+	$cfs->LimitToLookupType($lookup);
+	$cfs->LimitToGlobalOrObjectId($self->_LookupId($lookup));
+    }
+
+    return $cfs;
+}
+
+sub _LookupId {
+    my $self = shift;
+    my $lookup = shift;
+    my @classes = ($lookup =~ /RT::(\w+)-/g);
+
+    foreach my $class (reverse @classes) {
+	my $method = "${class}Obj";
+	$self = $self->$method;
+    }
+
+    return $self->Id;
+}
+
+sub _LookupTypes { ref($_[0]) }
+
+# {{{ AddCustomFieldValue
+
+=item AddCustomFieldValue { Field => FIELD, Value => VALUE }
+
+VALUE should be a string.
+FIELD can be a CustomField object OR a CustomField ID.
+
+
+Adds VALUE as a value of CustomField FIELD.  If this is a single-value custom field,
+deletes the old value. 
+If VALUE isn't a valid value for the custom field, returns 
+(0, 'Error message' ) otherwise, returns (1, 'Success Message')
+
+=cut
+
+sub AddCustomFieldValue {
+    my $self = shift;
+#    unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+#        return ( 0, $self->loc("Permission Denied") );
+#    }
+    $self->_AddCustomFieldValue(@_);
+}
+
+sub _AddCustomFieldValue {
+    my $self = shift;
+    my %args = (
+        Field => undef,
+        Value => undef,
+	RecordTransaction => 1,
+        @_
+    );
+
+    my $cf = RT::CustomField->new( $self->CurrentUser );
+    if ( UNIVERSAL::isa( $args{'Field'}, "RT::CustomField" ) ) {
+        $cf->Load( $args{'Field'}->id );
+    }
+    else {
+        $cf->Load( $args{'Field'} );
+    }
+
+    unless ( $cf->Id ) {
+        return ( 0, $self->loc("Custom field [_1] not found", $args{'Field'}) );
+    }
+
+    my $OCFs = $self->CustomFields;
+    $OCFs->Limit( FIELD => 'id', VALUE => $cf->Id );
+    unless ($OCFs->Count) {
+        return ( 0, $self->loc("Custom field [_1] does not apply to this object", $args{'Field'}) );
+    }
+
+    # Load up a ObjectCustomFieldValues object for this custom field and this ticket
+    my $values = $cf->ValuesForObject( $self );
+
+    unless ( $cf->ValidateValue( $args{'Value'} ) ) {
+        return ( 0, $self->loc("Invalid value for custom field") );
+    }
+
+    # If the custom field only accepts a single value, delete the existing
+    # value and record a "changed from foo to bar" transaction
+    if ( $cf->SingleValue ) {
+
+        # We need to whack any old values here.  In most cases, the custom field should
+        # only have one value to delete.  In the pathalogical case, this custom field
+        # used to be a multiple and we have many values to whack....
+        my $cf_values = $values->Count;
+
+        if ( $cf_values > 1 ) {
+            my $i = 0;   #We want to delete all but the last one, so we can then
+                 # execute the same code to "change" the value from old to new
+            while ( my $value = $values->Next ) {
+                $i++;
+                if ( $i < $cf_values ) {
+                    my ($val, $msg) = $cf->DeleteValueForObject(Object => $self, Content => $value->Content);
+                    unless ($val) {
+                        return (0,$msg);
+                    }
+                    my ( $TransactionId, $Msg, $TransactionObj ) =
+                      $self->_NewTransaction(
+                        Type     => 'CustomField',
+                        Field    => $cf->Id,
+                        OldReference => $value,
+                      );
+                }
+            }
+        }
+
+        my ($old_value, $old_content);
+        if ($old_value = $cf->ValuesForObject( $self )->First) {
+	    $old_content = $old_value->Content();
+	    return (1) if $old_content eq $args{'Value'};
+	}
+
+        my ( $new_value_id, $value_msg ) = $cf->AddValueForObject(
+            Object  => $self,
+            Content => $args{'Value'},
+            LargeContent => $args{'LargeContent'},
+            ContentType => $args{'ContentType'},
+        );
+
+        unless ($new_value_id) {
+            return ( 0,
+                $self->loc("Could not add new custom field value for ticket. [_1] ",
+                  ,$value_msg) );
+        }
+
+        my $new_value = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
+        $new_value->Load($new_value_id);
+
+        # now that adding the new value was successful, delete the old one
+	if ($old_value) {
+	    my ($val, $msg) = $cf->DeleteValueForObject(Object => $self, Content => $old_content);
+	    unless ($val) { 
+	    		return (0,$msg);
+	    }
+	}
+
+	if ($args{'RecordTransaction'}) {
+        my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction(
+            Type     => 'CustomField',
+            Field    => $cf->Id,
+            OldReference => $old_value,
+            NewReference => $new_value,
+        );
+	}
+
+        if ( $old_value eq '' ) {
+            return ( 1, $self->loc("[_1] [_2] added", $cf->Name, $new_value->Content) );
+        }
+        elsif ( $new_value->Content eq '' ) {
+            return ( 1, $self->loc("[_1] [_2] deleted", $cf->Name, $old_content) );
+        }
+        else {
+            return ( 1, $self->loc("[_1] [_2] changed to [_3]", $cf->Name, $old_content, $new_value->Content ) );
+        }
+
+    }
+
+    # otherwise, just add a new value and record "new value added"
+    else {
+        my ( $new_value_id ) = $cf->AddValueForObject(
+            Object  => $self,
+            Content => $args{'Value'},
+            LargeContent => $args{'LargeContent'},
+            ContentType => $args{'ContentType'},
+        );
+
+        unless ($new_value_id) {
+            return ( 0,
+                $self->loc("Could not add new custom field value for ticket. "));
+        }
+    if ( $args{'RecordTransaction'} ) {
+        my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction(
+            Type     => 'CustomField',
+            Field    => $cf->Id,
+            NewReference => $new_value_id,
+	    ReferenceType => 'RT::ObjectCustomFieldValue',
+        );
+        unless ($TransactionId) {
+            return ( 0,
+                $self->loc( "Couldn't create a transaction: [_1]", $Msg ) );
+        }
+    }
+        return ( 1, $self->loc("[_1] added as a value for [_2]",$args{'Value'}, $cf->Name));
+    }
+
+}
+
+# }}}
+
+# {{{ DeleteCustomFieldValue
+
+=item DeleteCustomFieldValue { Field => FIELD, Value => VALUE }
+
+Deletes VALUE as a value of CustomField FIELD. 
+
+VALUE can be a string, a CustomFieldValue or a ObjectCustomFieldValue.
+
+If VALUE isn't a valid value for the custom field, returns 
+(0, 'Error message' ) otherwise, returns (1, 'Success Message')
+
+=cut
+
+sub DeleteCustomFieldValue {
+    my $self = shift;
+    my %args = (
+        Field => undef,
+        Value => undef,
+	ValueId => undef,
+        @_);
+
+#    XXX - normalise CF related ACLs... ask obra!
+#    unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+#        return ( 0, $self->loc("Permission Denied") );
+#    }
+    my $cf = RT::CustomField->new( $self->CurrentUser );
+    if ( UNIVERSAL::isa( $args{'Field'}, "RT::CustomField" ) ) {
+        $cf->LoadById( $args{'Field'}->id );
+    }
+    else {
+        $cf->LoadById( $args{'Field'} );
+    }
+
+    unless ( $cf->Id ) {
+        return ( 0, $self->loc("Custom field not found") );
+    }
+
+
+     my ($val, $msg) = $cf->DeleteValueForObject(
+	 Object => $self,
+	 Id => $args{'ValueId'},
+	 Content => $args{'Value'},
+     );
+     unless ($val) { 
+            return (0,$msg);
+     }
+        my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction(
+            Type     => 'CustomField',
+            Field    => $cf->Id,
+            OldReference => $val,
+	    ReferenceType => 'RT::ObjectCustomFieldValue',
+        );
+        unless($TransactionId) {
+            return(0, $self->loc("Couldn't create a transaction: [_1]", $Msg));
+        } 
+
+        return($TransactionId, $self->loc("[_1] is no longer a value for custom field [_2]", $TransactionObj->OldValue, $cf->Name));
+}
+
+# }}}
+
+# {{{ FirstCustomFieldValue
+
+=item FirstCustomFieldValue FIELD
+
+Return the content of the first value of CustomField FIELD for this ticket
+Takes a field id or name
+
+=cut
+
+sub FirstCustomFieldValue {
+    my $self = shift;
+    my $field = shift;
+    my $values = $self->CustomFieldValues($field);
+    if ($values->First) {
+        return $values->First->Content;
+    } else {
+        return undef;
+    }
+
+}
+
+
+
+# {{{ CustomFieldValues
+
+=item CustomFieldValues FIELD
+
+Return a ObjectCustomFieldValues object of all values of CustomField FIELD for this ticket.  
+Takes a field id.
+
+
+=cut
+
+sub CustomFieldValues {
+    my $self  = shift;
+    my $field = shift;
+
+    my $cf_values = RT::ObjectCustomFieldValues->new( $self->CurrentUser );
+    $cf_values->LimitToObject($self);
+    $cf_values->OrderBy( FIELD => 'id' );
+
+    if (length $field) {
+	$field =~ /^\d+$/ or die "LoadByNameAndQueue impossible for Record.pm";
+	my $cf = RT::CustomField->new($self->CurrentUser);
+        $cf->LoadById($field);
+	$cf_values->LimitToCustomField($cf->id);
+    }
+
+    # @values is a CustomFieldValues object;
+    return ($cf_values);
+}
+
+# }}}
+
+# }}}
+
+sub BasicColumns {
+}
+
 eval "require RT::Record_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Record_Vendor.pm});
 eval "require RT::Record_Local";

Modified: rt/branches/rt-3.1/lib/RT/SearchBuilder.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/SearchBuilder.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/SearchBuilder.pm	Sat Jul 10 02:05:15 2004
@@ -109,14 +109,42 @@
 Takes NAME, OPERATOR and VALUE to find records that has the
 matching Attribute.
 
+If EMPTY is set, also select rows with an empty string as
+Attribute's Content.
+
+If NULL is set, also select rows without the named Attribute.
+
 =cut
 
+my %Negate = qw(
+    =		!=
+    !=		=
+    >		<=
+    <		>=
+    >=		<
+    <=		>
+    LIKE	NOT LIKE
+    NOT LIKE	LIKE
+    IS		IS NOT
+    IS NOT	IS
+);
+
 sub LimitAttribute {
     my ($self, %args) = @_;
+    my $clause = 'ALIAS';
+    my $operator = ($args{OPERATOR} || '=');
+    
+    if ($args{NULL} and exists $args{VALUE}) {
+	$clause = 'LEFTJOIN';
+	$operator = $Negate{$operator};
+    }
+    elsif ($args{NEGATE}) {
+	$operator = $Negate{$operator};
+    }
     
     my $alias = $self->Join(
 	TYPE   => 'left',
-	ALIAS1 => 'main',
+	ALIAS1 => $args{ALIAS} || 'main',
 	FIELD1 => 'id',
 	TABLE2 => 'Attributes',
 	FIELD2 => 'ObjectId'
@@ -126,13 +154,13 @@
     $type =~ s/(?:s|Collection)$//; # XXX - Hack!
 
     $self->Limit(
-	ALIAS	   => $alias,
+	$clause	   => $alias,
 	FIELD      => 'ObjectType',
 	OPERATOR   => '=',
 	VALUE      => $type,
     );
     $self->Limit(
-	ALIAS	   => $alias,
+	$clause	   => $alias,
 	FIELD      => 'Name',
 	OPERATOR   => '=',
 	VALUE      => $args{NAME},
@@ -141,27 +169,91 @@
     return unless exists $args{VALUE};
 
     $self->Limit(
-	ALIAS	   => $alias,
+	$clause	   => $alias,
 	FIELD      => 'Content',
-	OPERATOR   => ($args{OPERATOR} || '='),
+	OPERATOR   => $operator,
 	VALUE      => $args{VALUE},
-	ENTRYAGGREGATOR => 'OR',
     );
 
-    if ($args{EMPTY}) {
-	# Capture rows without the attribute defined by testing IS NULL.
-	$self->Limit(
-	    ALIAS      => $alias,
-	    FIELD      => $_,
-	    OPERATOR   => 'IS',
-	    VALUE      => 'NULL',
-	    ENTRYAGGREGATOR => 'OR',
-	) for qw( ObjectType Name Content );
-    }
+    # Capture rows with the attribute defined as an empty string.
+    $self->Limit(
+	$clause    => $alias,
+	FIELD      => 'Content',
+	OPERATOR   => '=',
+	VALUE      => '',
+	ENTRYAGGREGATOR => $args{NULL} ? 'AND' : 'OR',
+    ) if $args{EMPTY};
+
+    # Capture rows without the attribute defined
+    $self->Limit(
+	%args,
+	ALIAS      => $alias,
+	FIELD	   => 'id',
+	OPERATOR   => ($args{NEGATE} ? 'IS NOT' : 'IS'),
+	VALUE      => 'NULL',
+    ) if $args{NULL};
 }
 # }}}
 
-1;
+# {{{ sub LimitCustomField
+
+=head2 LimitCustomField
+
+Takes a paramhash of key/value pairs with the following keys:
+
+=over 4
+
+=item CUSTOMFIELD - CustomField name or id.  If a name is passed, an additional
+parameter QUEUE may also be passed to distinguish the custom field.
+
+=item OPERATOR - The usual Limit operators
+
+=item VALUE - The value to compare against
+
+=back
+
+=cut
+
+sub _SingularClass {
+    my $self = shift;
+    my $class = ref($self);
+    $class =~ s/s$// or die "Cannot deduce SingularClass for $class";
+    return $class;
+}
+
+sub LimitCustomField {
+    my $self = shift;
+    my %args = ( VALUE        => undef,
+                 CUSTOMFIELD  => undef,
+                 OPERATOR     => '=',
+                 @_ );
+
+    my $alias = $self->Join(
+	TYPE       => 'left',
+	ALIAS1     => 'main',
+	FIELD1     => 'id',
+	TABLE2     => 'ObjectCustomFieldValues',
+	FIELD2     => 'ObjectId'
+    );
+    $self->Limit(
+	ALIAS      => $alias,
+	FIELD      => 'CustomField',
+	OPERATOR   => '=',
+	VALUE      => $args{'CUSTOMFIELD'},
+    );
+    $self->Limit(
+	ALIAS      => $alias,
+	FIELD      => 'ObjectType',
+	OPERATOR   => '=',
+	VALUE      => $self->_SingularClass,
+    );
+    $self->Limit(
+	ALIAS      => $alias,
+	FIELD      => 'Content',
+	OPERATOR   => $args{'OPERATOR'},
+	VALUE      => $args{'VALUE'},
+    );
+}
 
 # {{{ sub FindAllRows
 

Modified: rt/branches/rt-3.1/lib/RT/StyleGuide.pod
==============================================================================
--- rt/branches/rt-3.1/lib/RT/StyleGuide.pod	(original)
+++ rt/branches/rt-3.1/lib/RT/StyleGuide.pod	Sat Jul 10 02:05:15 2004
@@ -857,7 +857,28 @@
 Send patches to rt-<major-version>-bugs at fsck.com, too.  Use C<diff
 -u> for patches.
 
+=head1 SCHEMA DESIGN
 
+RT uses a convention to denote the foreign key status in its tables.
+The rule of thumb is:
+
+=over 4
+
+=item When it references to another table, always use the table name
+
+For example, the C<Template> field in the C<Scrips> table refers to
+the C<Id> of the same-named C<Template> table.
+
+=item Otherwise, always use the C<Id> suffix
+
+For example, the C<ObjectId> field in the C<ACL> table can refer
+to any object, so it has the C<Id> suffix.
+
+=back
+
+There are some legacy fields that did not follow this rule, namely
+C<ACL.PrincipalId>, C<GroupMembers.GroupId> and C<Attachments.TransactionId>,
+but new tables are expected to be consistent.
 
 =head1 TO DO
 

Modified: rt/branches/rt-3.1/lib/RT/System.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/System.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/System.pm	Sat Jul 10 02:05:15 2004
@@ -96,12 +96,14 @@
 
     my $queue = RT::Queue->new($RT::SystemUser);
     my $group = RT::Group->new($RT::SystemUser);
+    my $cf    = RT::CustomField->new($RT::SystemUser);
 
     my $qr =$queue->AvailableRights();
     my $gr = $group->AvailableRights();
+    my $cr = $cf->AvailableRights();
 
     # Build a merged list of all system wide rights, queue rights and group rights.
-    my %rights = (%{$RIGHTS}, %{$gr}, %{$qr});
+    my %rights = (%{$RIGHTS}, %{$gr}, %{$qr}, %{$cr});
     return(\%rights);
 }
 

Modified: rt/branches/rt-3.1/lib/RT/Template_Overlay.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/Template_Overlay.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/Template_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -381,7 +381,15 @@
         SOURCE => $content
     );
 
-    my $retval = $template->fill_in( PACKAGE => 'T' );
+    my $is_broken = 0;
+    my $retval = $template->fill_in( PACKAGE => 'T', BROKEN => sub {
+        my (%args) = @_;
+        $RT::Logger->error("Template parsing error: $args{error}")
+	    unless $args{error} =~ /^Died at /; # ignore intentional die()
+        $is_broken++;
+	return undef;
+    } );
+    return undef if $is_broken;
 
     # MIME::Parser has problems dealing with high-bit utf8 data.
     Encode::_utf8_off($retval);

Modified: rt/branches/rt-3.1/lib/RT/Ticket_Overlay.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/Ticket_Overlay.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/Ticket_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -85,8 +85,8 @@
 
 ok(my $t2 = RT::Ticket->new($RT::SystemUser));
 ok($t2->Load($id));
-ok($t2->Subject eq 'Testing');
-ok($t2->QueueObj->Id eq $testqueue->id);
+is($t2->Subject, 'Testing');
+is($t2->QueueObj->Id, $testqueue->id);
 ok($t2->OwnerObj->Id == $u->Id);
 
 my $t3 = RT::Ticket->new($RT::SystemUser);
@@ -120,7 +120,6 @@
 use RT::Links;
 use RT::Date;
 use RT::CustomFields;
-use RT::TicketCustomFieldValues;
 use RT::Tickets;
 use RT::URI::fsck_com_rt;
 use RT::URI;
@@ -459,7 +458,7 @@
     }
 
     #If we've been handed something else, try to load the user.
-    elsif ( $args{'Owner'} ) {
+    elsif ( defined $args{'Owner'} ) {
         $Owner = RT::User->new( $self->CurrentUser );
         $Owner->Load( $args{'Owner'} );
 
@@ -2679,6 +2678,7 @@
 # {{{ sub MergeInto
 
 =head2 MergeInto
+
 MergeInto take the id of the ticket to merge this ticket into.
 
 =cut
@@ -2877,9 +2877,9 @@
 my $t = RT::Ticket->new($RT::SystemUser);
 $t->Load(1);
 $t->SetOwner('root');
-ok ($t->OwnerObj->Name eq 'root' , "Root owns the ticket");
+is ($t->OwnerObj->Name, 'root' , "Root owns the ticket");
 $t->Steal();
-ok ($t->OwnerObj->id eq $RT::SystemUser->id , "SystemUser owns the ticket");
+is ($t->OwnerObj->id, $RT::SystemUser->id , "SystemUser owns the ticket");
 my $txns = RT::Transactions->new($RT::SystemUser);
 $txns->OrderBy(FIELD => 'id', ORDER => 'DESC');
 $txns->Limit(FIELD => 'Ticket', VALUE => '1');
@@ -3109,14 +3109,14 @@
 my ($id, $tid, $msg)= $tt->Create(Queue => 'general',
             Subject => 'test');
 ok($id, $msg);
-ok($tt->Status eq 'new', "New ticket is created as new");
+is($tt->Status, 'new', "New ticket is created as new");
 
 ($id, $msg) = $tt->SetStatus('open');
 ok($id, $msg);
-ok ($msg =~ /open/i, "Status message is correct");
+like($msg, qr/open/i, "Status message is correct");
 ($id, $msg) = $tt->SetStatus('resolved');
 ok($id, $msg);
-ok ($msg =~ /resolved/i, "Status message is correct");
+like($msg, qr/resolved/i, "Status message is correct");
 ($id, $msg) = $tt->SetStatus('resolved');
 ok(!$id,$msg);
 
@@ -3268,282 +3268,6 @@
 
 # }}}
 
-# {{{ Routines dealing with custom fields
-
-
-# {{{ FirstCustomFieldValue
-
-=item FirstCustomFieldValue FIELD
-
-Return the content of the first value of CustomField FIELD for this ticket
-Takes a field id or name
-
-=cut
-
-sub FirstCustomFieldValue {
-    my $self = shift;
-    my $field = shift;
-    my $values = $self->CustomFieldValues($field);
-    if ($values->First) {
-        return $values->First->Content;
-    } else {
-        return undef;
-    }
-
-}
-
-
-
-# {{{ CustomFieldValues
-
-=item CustomFieldValues FIELD
-
-Return a TicketCustomFieldValues object of all values of CustomField FIELD for this ticket.  
-Takes a field id or name.
-
-
-=cut
-
-sub CustomFieldValues {
-    my $self  = shift;
-    my $field = shift;
-
-    my $cf = RT::CustomField->new($self->CurrentUser);
-
-    if ($field =~ /^\d+$/) {
-        $cf->LoadById($field);
-    } else {
-        $cf->LoadByNameAndQueue(Name => $field, Queue => $self->QueueObj->Id);
-        unless( $cf->id ) {
-            $cf->LoadByNameAndQueue(Name => $field, Queue => '0');
-        }
-    }
-    my $cf_values = RT::TicketCustomFieldValues->new( $self->CurrentUser );
-    $cf_values->LimitToCustomField($cf->id);
-    $cf_values->LimitToTicket($self->Id());
-    $cf_values->OrderBy( FIELD => 'id' );
-
-    # @values is a CustomFieldValues object;
-    return ($cf_values);
-}
-
-# }}}
-
-# {{{ AddCustomFieldValue
-
-=item AddCustomFieldValue { Field => FIELD, Value => VALUE }
-
-VALUE should be a string.
-FIELD can be a CustomField object OR a CustomField ID.
-
-
-Adds VALUE as a value of CustomField FIELD.  If this is a single-value custom field,
-deletes the old value. 
-If VALUE isn't a valid value for the custom field, returns 
-(0, 'Error message' ) otherwise, returns (1, 'Success Message')
-
-=cut
-
-sub AddCustomFieldValue {
-    my $self = shift;
-    unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
-        return ( 0, $self->loc("Permission Denied") );
-    }
-    $self->_AddCustomFieldValue(@_);
-}
-
-sub _AddCustomFieldValue {
-    my $self = shift;
-    my %args = (
-        Field => undef,
-        Value => undef,
-	RecordTransaction => 1,
-        @_
-    );
-
-    my $cf = RT::CustomField->new( $self->CurrentUser );
-    if ( UNIVERSAL::isa( $args{'Field'}, "RT::CustomField" ) ) {
-        $cf->Load( $args{'Field'}->id );
-    }
-    else {
-        $cf->Load( $args{'Field'} );
-    }
-
-    unless ( $cf->Id ) {
-        return ( 0, $self->loc("Custom field [_1] not found", $args{'Field'}) );
-    }
-
-    # Load up a TicketCustomFieldValues object for this custom field and this ticket
-    my $values = $cf->ValuesForTicket( $self->id );
-
-    unless ( $cf->ValidateValue( $args{'Value'} ) ) {
-        return ( 0, $self->loc("Invalid value for custom field") );
-    }
-
-    # If the custom field only accepts a single value, delete the existing
-    # value and record a "changed from foo to bar" transaction
-    if ( $cf->SingleValue ) {
-
-        # We need to whack any old values here.  In most cases, the custom field should
-        # only have one value to delete.  In the pathalogical case, this custom field
-        # used to be a multiple and we have many values to whack....
-        my $cf_values = $values->Count;
-
-        if ( $cf_values > 1 ) {
-            my $i = 0;   #We want to delete all but the last one, so we can then
-                 # execute the same code to "change" the value from old to new
-            while ( my $value = $values->Next ) {
-                $i++;
-                if ( $i < $cf_values ) {
-                    my $old_value = $value->Content;
-                    my ($val, $msg) = $cf->DeleteValueForTicket(Ticket => $self->Id, Content => $value->Content);
-                    unless ($val) {
-                        return (0,$msg);
-                    }
-                    my ( $TransactionId, $Msg, $TransactionObj ) =
-                      $self->_NewTransaction(
-                        Type     => 'CustomField',
-                        Field    => $cf->Id,
-                        OldValue => $old_value
-                      );
-                }
-            }
-        }
-
-        my $old_value;
-        if (my $value = $cf->ValuesForTicket( $self->Id )->First) {
-	    $old_value = $value->Content();
-	    return (1) if $old_value eq $args{'Value'};
-	}
-
-        my ( $new_value_id, $value_msg ) = $cf->AddValueForTicket(
-            Ticket  => $self->Id,
-            Content => $args{'Value'}
-        );
-
-        unless ($new_value_id) {
-            return ( 0,
-                $self->loc("Could not add new custom field value for ticket. [_1] ",
-                  ,$value_msg) );
-        }
-
-        my $new_value = RT::TicketCustomFieldValue->new( $self->CurrentUser );
-        $new_value->Load($new_value_id);
-
-        # now that adding the new value was successful, delete the old one
-	if ($old_value) {
-	    my ($val, $msg) = $cf->DeleteValueForTicket(Ticket => $self->Id, Content => $old_value);
-	    unless ($val) { 
-	    		return (0,$msg);
-	    }
-	}
-
-	if ($args{'RecordTransaction'}) {
-        my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction(
-            Type     => 'CustomField',
-            Field    => $cf->Id,
-            OldValue => $old_value,
-            NewValue => $new_value->Content
-        );
-	}
-
-        if ( $old_value eq '' ) {
-            return ( 1, $self->loc("[_1] [_2] added", $cf->Name, $new_value->Content) );
-        }
-        elsif ( $new_value->Content eq '' ) {
-            return ( 1, $self->loc("[_1] [_2] deleted", $cf->Name, $old_value) );
-        }
-        else {
-            return ( 1, $self->loc("[_1] [_2] changed to [_3]", $cf->Name, $old_value, $new_value->Content ) );
-        }
-
-    }
-
-    # otherwise, just add a new value and record "new value added"
-    else {
-        my ( $new_value_id ) = $cf->AddValueForTicket(
-            Ticket  => $self->Id,
-            Content => $args{'Value'}
-        );
-
-        unless ($new_value_id) {
-            return ( 0,
-                $self->loc("Could not add new custom field value for ticket. "));
-        }
-    if ( $args{'RecordTransaction'} ) {
-        my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction(
-            Type     => 'CustomField',
-            Field    => $cf->Id,
-            NewValue => $args{'Value'}
-        );
-        unless ($TransactionId) {
-            return ( 0,
-                $self->loc( "Couldn't create a transaction: [_1]", $Msg ) );
-        }
-    }
-        return ( 1, $self->loc("[_1] added as a value for [_2]",$args{'Value'}, $cf->Name));
-    }
-
-}
-
-# }}}
-
-# {{{ DeleteCustomFieldValue
-
-=item DeleteCustomFieldValue { Field => FIELD, Value => VALUE }
-
-Deletes VALUE as a value of CustomField FIELD. 
-
-VALUE can be a string, a CustomFieldValue or a TicketCustomFieldValue.
-
-If VALUE isn't a valid value for the custom field, returns 
-(0, 'Error message' ) otherwise, returns (1, 'Success Message')
-
-=cut
-
-sub DeleteCustomFieldValue {
-    my $self = shift;
-    my %args = (
-        Field => undef,
-        Value => undef,
-        @_);
-
-    unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
-        return ( 0, $self->loc("Permission Denied") );
-    }
-    my $cf = RT::CustomField->new( $self->CurrentUser );
-    if ( UNIVERSAL::isa( $args{'Field'}, "RT::CustomField" ) ) {
-        $cf->LoadById( $args{'Field'}->id );
-    }
-    else {
-        $cf->LoadById( $args{'Field'} );
-    }
-
-    unless ( $cf->Id ) {
-        return ( 0, $self->loc("Custom field not found") );
-    }
-
-
-     my ($val, $msg) = $cf->DeleteValueForTicket(Ticket => $self->Id, Content => $args{'Value'});
-     unless ($val) { 
-            return (0,$msg);
-     }
-        my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction(
-            Type     => 'CustomField',
-            Field    => $cf->Id,
-            OldValue => $args{'Value'}
-        );
-        unless($TransactionId) {
-            return(0, $self->loc("Couldn't create a transaction: [_1]", $Msg));
-        } 
-
-        return($TransactionId, $self->loc("[_1] is no longer a value for custom field [_2]", $args{'Value'}, $cf->Name));
-}
-
-# }}}
-
-# }}}
-
 # {{{ Actions + Routines dealing with transactions
 
 # {{{ sub SetTold and _SetTold
@@ -3598,113 +3322,6 @@
 
 # }}}
 
-# {{{ sub Transactions 
-
-=head2 Transactions
-
-  Returns an RT::Transactions object of all transactions on this ticket
-
-=cut
-
-sub Transactions {
-    my $self = shift;
-
-    use RT::Transactions;
-    my $transactions = RT::Transactions->new( $self->CurrentUser );
-
-    #If the user has no rights, return an empty object
-    if ( $self->CurrentUserHasRight('ShowTicket') ) {
-        my $tickets = $transactions->NewAlias('Tickets');
-        $transactions->Join(
-            ALIAS1 => 'main',
-            FIELD1 => 'Ticket',
-            ALIAS2 => $tickets,
-            FIELD2 => 'id'
-        );
-        $transactions->Limit(
-            ALIAS => $tickets,
-            FIELD => 'EffectiveId',
-            VALUE => $self->id()
-        );
-
-        # if the user may not see comments do not return them
-        unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
-            $transactions->Limit(
-                FIELD    => 'Type',
-                OPERATOR => '!=',
-                VALUE    => "Comment",
-                ENTRYAGGREGATOR => 'AND'
-            );
-            $transactions->Limit(
-                FIELD    => 'Type',
-                OPERATOR => '!=',
-                VALUE    => "CommentEmailRecord",
-                ENTRYAGGREGATOR => 'AND'
-            );
-        }
-    }
-
-    return ($transactions);
-}
-
-# }}}
-
-# {{{ sub _NewTransaction
-
-=head2 _NewTransaction  PARAMHASH
-
-Private function to create a new RT::Transaction object for this ticket update
-
-=cut
-
-sub _NewTransaction {
-    my $self = shift;
-    my %args = (
-        TimeTaken => 0,
-        Type      => undef,
-        OldValue  => undef,
-        NewValue  => undef,
-        Data      => undef,
-        Field     => undef,
-        MIMEObj   => undef,
-        ActivateScrips => 1,
-        CommitScrips => 1,
-        @_
-    );
-
-    require RT::Transaction;
-    my $trans = new RT::Transaction( $self->CurrentUser );
-    my ( $transaction, $msg ) = $trans->Create(
-        Ticket    => $self->Id,
-        TimeTaken => $args{'TimeTaken'},
-        Type      => $args{'Type'},
-        Data      => $args{'Data'},
-        Field     => $args{'Field'},
-        NewValue  => $args{'NewValue'},
-        OldValue  => $args{'OldValue'},
-        MIMEObj   => $args{'MIMEObj'},
-        ActivateScrips => $args{'ActivateScrips'},
-        CommitScrips => $args{'CommitScrips'},
-    );
-
-    # Rationalize the object since we may have done things to it during the caching.
-    $self->Load($self->Id);
-
-    $RT::Logger->warning($msg) unless $transaction;
-
-    $self->_SetLastUpdated;
-
-    if ( defined $args{'TimeTaken'} ) {
-        $self->_UpdateTimeTaken( $args{'TimeTaken'} );
-    }
-    if ( $RT::UseTransactionBatch and $transaction ) {
-	    push @{$self->{_TransactionBatch}}, $trans;
-    }
-    return ( $transaction, $msg, $trans );
-}
-
-# }}}
-
 =head2 TransactionBatch
 
   Returns an array reference of all transactions created on this ticket during
@@ -3957,6 +3574,86 @@
 
 # }}}
 
+# {{{ sub Transactions 
+
+=head2 Transactions
+
+  Returns an RT::Transactions object of all transactions on this ticket
+
+=cut
+
+sub Transactions {
+    my $self = shift;
+
+    use RT::Transactions;
+    my $transactions = RT::Transactions->new( $self->CurrentUser );
+
+    #If the user has no rights, return an empty object
+    if ( $self->CurrentUserHasRight('ShowTicket') ) {
+        my $tickets = $transactions->NewAlias('Tickets');
+        $transactions->Join(
+            ALIAS1 => 'main',
+            FIELD1 => 'ObjectId',
+            ALIAS2 => $tickets,
+            FIELD2 => 'id'
+        );
+        $transactions->Limit(
+            ALIAS => $tickets,
+            FIELD => 'EffectiveId',
+            VALUE => $self->id()
+        );
+	$transactions->Limit(
+	    FIELD    => 'ObjectType',
+	    VALUE    => ref($self),
+	);
+
+        # if the user may not see comments do not return them
+        unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
+            $transactions->Limit(
+                FIELD    => 'Type',
+                OPERATOR => '!=',
+                VALUE    => "Comment"
+            );
+            $transactions->Limit(
+                FIELD    => 'Type',
+                OPERATOR => '!=',
+                VALUE    => "CommentEmailRecord",
+                ENTRYAGGREGATOR => 'AND'
+            );
+
+        }
+    }
+
+    return ($transactions);
+}
+
+# }}}
+
+sub TransactionCustomFields {
+    my $self = shift;
+    return $self->QueueObj->TicketTransactionCustomFields;
+}
+
+# Do name => id mapping (if needed) before falling back to
+# RT::Record's CustomFieldValues
+sub CustomFieldValues {
+    my $self = shift;
+    my $field = shift;
+    unless ($field =~ /^\d+$/) {
+	my $cf = RT::CustomField->new($self->CurrentUser);
+	$cf->LoadByNameAndQueue(Name => $field, Queue => $self->QueueObj->Id);
+	unless( $cf->id ) {
+            $cf->LoadByNameAndQueue(Name => $field, Queue => '0');
+        }
+	$field = $cf->id;
+    }
+    return $self->SUPER::CustomFieldValues($field);
+}
+
+sub _LookupTypes {
+    "RT::Queue-RT::Ticket";
+}
+
 1;
 
 =head1 AUTHOR

Modified: rt/branches/rt-3.1/lib/RT/Tickets_Overlay.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/Tickets_Overlay.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/Tickets_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -108,6 +108,10 @@
     LinkedTo	    => ['LINKFIELD',],
     CustomFieldValue =>['CUSTOMFIELD',],
     CF              => ['CUSTOMFIELD',],
+    RequestorGroup  => ['MEMBERSHIPFIELD' => 'Requestor',],
+    CCGroup         => ['MEMBERSHIPFIELD' => 'Cc',],
+    AdminCCGroup    => ['MEMBERSHIPFIELD' => 'AdminCc',],
+    WatcherGroup    => ['MEMBERSHIPFIELD',],
   );
 
 # Mapping of Field Type to Function
@@ -120,8 +124,9 @@
     TRANSFIELD	    => \&_TransLimit,
     TRANSDATE	    => \&_TransDateLimit,
     WATCHERFIELD    => \&_WatcherLimit,
+    MEMBERSHIPFIELD => \&_WatcherMembershipLimit,
     LINKFIELD	    => \&_LinkFieldLimit,
-    CUSTOMFIELD    => \&_CustomFieldLimit,
+    CUSTOMFIELD     => \&_CustomFieldLimit,
   );
 my %can_bundle =
   ( WATCHERFIELD => "yeps",
@@ -242,7 +247,7 @@
     $o->Load( $value );
     $value = $o->Id;
   }
-  $sb->_SQLLimit( FIELD => $field,,
+  $sb->_SQLLimit( FIELD => $field,
 	      VALUE => $value,
 	      OPERATOR => $op,
 	      @rest,
@@ -538,6 +543,14 @@
 
   $sb->_OpenParen;
 
+  # Join Transactions To Attachments
+  $sb->_SQLJoin( ALIAS1 => $sb->{_sql_trattachalias}, FIELD1 => 'TransactionId',
+	     ALIAS2 => $sb->{_sql_transalias}, FIELD2 => 'id');
+
+  # Join Transactions to Tickets
+  $sb->_SQLJoin( ALIAS1 => 'main', FIELD1 => $sb->{'primary_key'}, # UGH!
+	     ALIAS2 => $sb->{_sql_transalias}, FIELD2 => 'Ticket');
+
   #Search for the right field
   $sb->_SQLLimit(ALIAS => $sb->{_sql_trattachalias},
 		 FIELD =>    $field,
@@ -547,14 +560,6 @@
 		 @rest
 		);
 
-  # Join Transactions To Attachments
-  $sb->_SQLJoin( ALIAS1 => $sb->{_sql_trattachalias}, FIELD1 => 'TransactionId',
-	     ALIAS2 => $sb->{_sql_transalias}, FIELD2 => 'id');
-
-  # Join Transactions to Tickets
-  $sb->_SQLJoin( ALIAS1 => 'main', FIELD1 => $sb->{'primary_key'}, # UGH!
-	     ALIAS2 => $sb->{_sql_transalias}, FIELD2 => 'Ticket');
-
   $sb->_CloseParen;
 
 }
@@ -642,6 +647,112 @@
 
 }
 
+=head2 _WatcherMembershipLimit
+
+Handle watcher membership limits, i.e. whether the watcher belongs to a
+specific group or not.
+
+Meta Data:
+  1: Field to query on
+
+SELECT DISTINCT main.*
+FROM
+    Tickets main,
+    Groups Groups_1,
+    CachedGroupMembers CachedGroupMembers_2,
+    Users Users_3
+WHERE (
+    (main.EffectiveId = main.id)
+) AND (
+    (main.Status != 'deleted')
+) AND (
+    (main.Type = 'ticket')
+) AND (
+    (
+	(Users_3.EmailAddress = '22')
+	    AND
+	(Groups_1.Domain = 'RT::Ticket-Role')
+	    AND
+	(Groups_1.Type = 'RequestorGroup')
+    )
+) AND
+    Groups_1.Instance = main.id
+AND
+    Groups_1.id = CachedGroupMembers_2.GroupId
+AND
+    CachedGroupMembers_2.MemberId = Users_3.id
+ORDER BY main.id ASC
+LIMIT 25
+=cut
+
+sub _WatcherMembershipLimit {
+  my ($self,$field,$op,$value, at rest) = @_;
+  my %rest = @rest;
+
+  $self->_OpenParen;
+
+  my $groups	    = $self->NewAlias('Groups');
+  my $groupmembers  = $self->NewAlias('CachedGroupMembers');
+  my $users	    = $self->NewAlias('Users');
+  my $memberships   = $self->NewAlias('CachedGroupMembers');
+
+  if (ref $field) { # gross hack
+    my @bundle = @$field;
+    $self->_OpenParen;
+    for my $chunk (@bundle) {
+      ($field,$op,$value, at rest) = @$chunk;
+      $self->_SQLLimit(ALIAS => $memberships,
+   		   FIELD => 'GroupId',
+   		   VALUE           => $value,
+   		   OPERATOR        => $op,
+   		   @rest,
+   		  );
+    }
+    $self->_CloseParen;
+  } else {
+     $self->_SQLLimit(ALIAS => $memberships,
+   		   FIELD => 'GroupId',
+   		   VALUE           => $value,
+   		   OPERATOR        => $op,
+   		   @rest,
+   		  );
+  }
+
+  # {{{ Tie to groups for tickets we care about
+  $self->_SQLLimit(ALIAS => $groups,
+		   FIELD => 'Domain',
+		   VALUE => 'RT::Ticket-Role',
+		   ENTRYAGGREGATOR => 'AND');
+
+  $self->Join(ALIAS1 => $groups, FIELD1 => 'Instance',
+	      ALIAS2 => 'main',   FIELD2 => 'id');
+  # }}}
+
+  # If we care about which sort of watcher
+  my $meta = $FIELDS{$field};
+  my $type = ( defined $meta->[1] ? $meta->[1] : undef );
+
+  if ( $type ) {
+    $self->_SQLLimit(ALIAS => $groups,
+		     FIELD => 'Type',
+		     VALUE => $type,
+		     ENTRYAGGREGATOR => 'AND');
+  }
+
+  $self->Join (ALIAS1 => $groups,  FIELD1 => 'id',
+	       ALIAS2 => $groupmembers, FIELD2 => 'GroupId');
+
+  $self->Join( ALIAS1 => $groupmembers, FIELD1 => 'MemberId',
+	       ALIAS2 => $users, FIELD2 => 'id');
+
+  $self->Join( ALIAS1 => $memberships, FIELD1 => 'MemberId',
+	       ALIAS2 => $users, FIELD2 => 'id');
+
+ $self->_CloseParen;
+
+}
+
+
 sub _LinkFieldLimit {
   my $restriction;
   my $self;
@@ -710,93 +821,102 @@
 =cut
 
 sub _CustomFieldLimit {
-  my ($self,$_field,$op,$value, at rest) = @_;
-
-  my %rest = @rest;
-  my $field = $rest{SUBKEY} || die "No field specified";
-
-  # For our sanity, we can only limit on one queue at a time
-  my $queue = 0;
+    my ( $self, $_field, $op, $value, @rest ) = @_;
 
+    my %rest  = @rest;
+    my $field = $rest{SUBKEY} || die "No field specified";
 
-  if ($field =~ /^(.+?)\.{(.+)}$/) {
-    $queue =  $1;
-    $field = $2;
-   }
-    $field = $1 if $field =~ /^{(.+)}$/; # trim { }
+    # For our sanity, we can only limit on one queue at a time
+    my $queue = 0;
 
-    my $q = RT::Queue->new($self->CurrentUser);
-    $q->Load($queue) if ($queue);
-
-    my $cf;
-    if ($q->id) {
-        $cf = $q->CustomField($field);
-    }
-    else { 
-        $cf = RT::CustomField->new($self->CurrentUser);
-        $cf->LoadByNameAndQueue(Queue => '0', Name => $field);
+    if ( $field =~ /^(.+?)\.{(.+)}$/ ) {
+        $queue = $1;
+        $field = $2;
     }
+    $field = $1 if $field =~ /^{(.+)}$/;    # trim { }
 
 
 
+# If we're trying to find custom fields that don't match something, we want tickets
+# where the custom field has no value at all
 
-
-  my $cfid = $cf->id;
-
-  die "No custom field named $field found\n" unless $cfid;
-
+    my $null_columns_ok;
+    if ( ( $op =~ /^IS$/i ) or ( $op =~ /^NOT LIKE$/i ) or ( $op eq '!=' ) ) {
+        $null_columns_ok = 1;
+    }
 
 
-  my $null_columns_ok;
 
-  my $TicketCFs;
-  # Perform one Join per CustomField
-  if ($self->{_sql_keywordalias}{$cfid}) {
-    $TicketCFs = $self->{_sql_keywordalias}{$cfid};
-  } else {
-    $TicketCFs = $self->{_sql_keywordalias}{$cfid} =
-      $self->_SQLJoin( TYPE   => 'left',
-		   ALIAS1 => 'main',
-		   FIELD1 => 'id',
-		   TABLE2 => 'TicketCustomFieldValues',
-		   FIELD2 => 'Ticket' );
-  }
+    my $q = RT::Queue->new( $self->CurrentUser );
+    $q->Load($queue) if ($queue);
 
-  $self->_OpenParen;
+    my $cf;
+    if ( $q->id ) {
+        $cf = $q->CustomField($field);
+    }
+    else {
+        $cf = RT::CustomField->new( $self->CurrentUser );
+        $cf->LoadByNameAndQueue( Queue => '0', Name => $field );
+    }
 
-  $self->_SQLLimit( ALIAS      => $TicketCFs,
-		    FIELD      => 'Content',
-		    OPERATOR   => $op,
-		    VALUE      => $value,
-		    QUOTEVALUE => 1,
-		    @rest );
+    my $cfid = $cf->id;
 
+    die "No custom field named $field found\n" unless $cfid;
 
-   # If we're trying to find custom fields that don't match something, we want tickets
-   # where the custom field has no value at all
 
-  if (   ($op =~ /^IS$/i) or ($op =~ /^NOT LIKE$/i) or ( $op eq '!=' ) ) {
-    $null_columns_ok = 1;
-  }
-    
+    my $TicketCFs;
 
-  if ( $null_columns_ok) {
-    $self->_SQLLimit( ALIAS           => $TicketCFs,
-		      FIELD           => 'Content',
-		      OPERATOR        => 'IS',
-		      VALUE           => 'NULL',
-		      QUOTEVALUE      => 0,
-		      ENTRYAGGREGATOR => 'OR', );
-  }
+    # Perform one Join per CustomField
+    if ( $self->{_sql_keywordalias}{$cfid} ) {
+        $TicketCFs = $self->{_sql_keywordalias}{$cfid};
+    }
+    else {
+        $TicketCFs = $self->{_sql_keywordalias}{$cfid} = $self->_SQLJoin(
+            TYPE   => 'left',
+            ALIAS1 => 'main',
+            FIELD1 => 'id',
+            TABLE2 => 'ObjectCustomFieldValues',
+            FIELD2 => 'ObjectId'
+        );
+
+    $self->_SQLLimit(
+        LEFTJOIN        => $TicketCFs,
+        FIELD => 'ObjectType',
+        VALUE => ref($self->NewItem), # we want a single item, not a collection
+        ENTRYAGGREGATOR => 'AND'
+    );
+
+    $self->_SQLLimit(
+        LEFTJOIN        => $TicketCFs,
+        FIELD           => 'CustomField',
+        VALUE           => $cfid,
+        ENTRYAGGREGATOR => 'AND'
+    );
 
-  $self->_SQLLimit( LEFTJOIN => $TicketCFs,
-		    FIELD    => 'CustomField',
-		    VALUE    => $cfid,
-		    ENTRYAGGREGATOR => 'OR' );
+    }
 
+    $self->_OpenParen;
 
+    $self->_SQLLimit(
+        ALIAS      => $TicketCFs,
+        FIELD      => 'Content',
+        OPERATOR   => $op,
+        VALUE      => $value,
+        QUOTEVALUE => 1,
+        @rest
+    );
+    if ($null_columns_ok) {
+        $self->_SQLLimit(
+            ALIAS           => $TicketCFs,
+            FIELD           => 'Content',
+            OPERATOR        => 'IS',
+            VALUE           => 'NULL',
+            QUOTEVALUE      => 0,
+            ENTRYAGGREGATOR => 'OR',
+        );
+    }
 
-  $self->_CloseParen;
+    $self->_CloseParen;
 
 }
 
@@ -870,8 +990,13 @@
 
 sub FreezeLimits {
 	my $self = shift;
-	require FreezeThaw;
-	return (FreezeThaw::freeze(@{$self}{$self->_FreezeThawKeys}));
+	require Storable;
+	require MIME::Base64;
+	MIME::Base64::base64_encode(
+	    Storable::freeze(
+		\@{$self}{$self->_FreezeThawKeys}
+	    )
+	);
 }
 
 # }}}
@@ -893,13 +1018,14 @@
 
     	$self->{'RecalcTicketLimits'} = 1;
 
-	require FreezeThaw;
-	
+	require Storable;
+	require MIME::Base64;
+
 	#We don't need to die if the thaw fails.
-	
-	eval {
-		@{$self}{$self->_FreezeThawKeys} = FreezeThaw::thaw($in);
+	@{$self}{$self->_FreezeThawKeys} = eval {
+	    @{Storable::thaw( MIME::Base64::base64_decode($in) )};
 	};
+	
 	$RT::Logger->error( $@ ) if $@;
 
 }
@@ -1972,9 +2098,9 @@
     }
 
     # Two special case
-    # CustomFields have a different real field
-    if ($field =~ /^CF\./) {
-      $realfield = "CF"
+    # Handle subkey fields with a different real field
+    if ($field =~ /^(\w+)\./) {
+      $realfield = $1;
     }
 
     die "I don't know about $field yet"

Modified: rt/branches/rt-3.1/lib/RT/Tickets_Overlay_SQL.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/Tickets_Overlay_SQL.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/Tickets_Overlay_SQL.pm	Sat Jul 10 02:05:15 2004
@@ -129,9 +129,14 @@
 use constant OP => 4;
 use constant PAREN => 8;
 use constant KEYWORD => 16;
-my @tokens = qw[VALUE AGGREG OP PAREN KEYWORD];
+use constant SELECT => 32;
+use constant WHERE => 64;
+use constant COLUMN => 128;
+my @tokens = qw[VALUE AGGREG OP PAREN KEYWORD SELECT WHERE COLUMN];
 
 my $re_aggreg = qr[(?i:AND|OR)];
+my $re_select = qr[(?i:SELECT)];
+my $re_where = qr[(?i:WHERE)];
 my $re_value  = qr[$RE{delimited}{-delim=>qq{\'\"}}|\d+];
 my $re_keyword = qr[$RE{delimited}{-delim=>qq{\'\"}}|(?:\{|\}|\w|\.)+];
 my $re_op     = qr[=|!=|>=|<=|>|<|(?i:IS NOT)|(?i:IS)|(?i:NOT LIKE)|(?i:LIKE)]; # long to short
@@ -171,7 +176,7 @@
 
 sub _parser {
   my ($self,$string) = @_;
-  my $want = KEYWORD | PAREN;
+  my $want = SELECT | KEYWORD | PAREN;
   my $last = undef;
 
   my $depth = 0;
@@ -188,7 +193,9 @@
 
 
   while ($string =~ /(
-                      $re_aggreg
+                      $re_select
+                      |$re_where
+                      |$re_aggreg
                       |$re_op
                       |$re_keyword
                       |$re_value
@@ -203,6 +210,9 @@
     $current = KEYWORD if _match($re_keyword,$val) && ($want & KEYWORD);
     $current = AGGREG  if _match($re_aggreg,$val);
     $current = PAREN   if _match($re_paren,$val);
+    $current = COLUMN if _match($re_keyword,$val) && ($want & COLUMN);
+    $current = WHERE if _match($re_where,$val) && ($want & WHERE);
+    $current = SELECT if _match($re_select,$val);
 
 
     unless ($current && $want & $current) {
@@ -230,7 +240,26 @@
 
       $want = KEYWORD | PAREN | AGGREG;
     }
+    elsif ($current & SELECT ) {
+        $want = COLUMN | WHERE;
+    }
+
+    elsif ($current & COLUMN ) {
+      if ($val =~ /$RE{delimited}{-delim=>qq{\'\"}}/) {
+        substr($val,0,1) = "";
+        substr($val,-1,1) = "";
+      }
+      # Unescape escaped characters
+      $val =~ s!\\(.)!$1!g;     
+        $self->_DisplayColumn($val);
+
+        $want = COLUMN | WHERE;
 
+    } 
+    elsif ($current & WHERE ) {
+        $want = KEYWORD | PAREN;
+
+    }
     elsif ( $current & AGGREG ) {
       $ea = $val;
       $want = KEYWORD | PAREN;
@@ -376,63 +405,60 @@
 
 use RT::Tickets;
 
-
+my $query = "SELECT id WHERE Status = 'open'";
 
 my $tix = RT::Tickets->new($RT::SystemUser);
 
-my $query = "Status = 'open'";
 my ($id, $msg)  = $tix->FromSQL($query);
 
 ok ($id, $msg);
 
+my @cols =  $tix->DisplayColumns;
+
+ok ($cols[0]->{'attribute'} == 'id', "We're  displaying the ticket id");
+ok ($cols[1] == undef, "We're  displaying the ticket id");
 
-my (@ids, @expectedids);
 
-my $t = RT::Ticket->new($RT::SystemUser);
+my $query = "SELECT id, Status WHERE Status = 'open'";
 
-my $string = 'subject/content SQL test';
-ok( $t->Create(Queue => 'General', Subject => $string), "Ticket Created");
+my $tix = RT::Tickets->new($RT::SystemUser);
 
-push @ids, $t->Id;
+my ($id, $msg)  = $tix->FromSQL($query);
 
-my $Message = MIME::Entity->build(
-			     Subject     => 'this is my subject',
-			     From        => 'jesse at example.com',
-			     Data        => [ $string ],
-        );
+ok ($id, $msg);
 
-ok( $t->Create(Queue => 'General', Subject => 'another ticket', MIMEObj => $Message, MemberOf => $ids[0]), "Ticket Created");
+my @cols =  $tix->DisplayColumns;
 
-push @ids, $t->Id;
+ok ($cols[0]->{'attribute'} == 'id', "We're only displaying the ticket id");
+ok ($cols[1]->{'attribute'} == 'Status', "We're only displaying the ticket id");
 
-$query = ("Subject LIKE '$string' OR Content LIKE '$string'");
+my $query = qq[SELECT id, Status, '<A href="/Ticket/Display.html?id=##id##">Subject, this: ##Subject##</a>' WHERE Status = 'open'];
 
-my ($id, $msg) = $tix->FromSQL($query);
+my $tix = RT::Tickets->new($RT::SystemUser);
+
+my ($id, $msg)  = $tix->FromSQL($query);
 
 ok ($id, $msg);
 
-is ($tix->Count, scalar @ids, "number of returned tickets same as entered");
+my @cols =  $tix->DisplayColumns;
 
-while (my $tick = $tix->Next) {
-    push @expectedids, $tick->Id;
-}
+ok ($cols[0]->{'attribute'} == 'id', "We're only displaying the ticket id");
+ok ($cols[1]->{'attribute'} == 'Status', "We're only displaying the ticket id");
 
-ok (eq_array(\@ids, \@expectedids), "returned expected tickets");
 
-$query = ("id = $ids[0] OR MemberOf = $ids[0]");
 
-my ($id, $msg) = $tix->FromSQL($query);
+$query = "Status = 'open'";
+my ($id, $msg)  = $tix->FromSQL($query);
 
 ok ($id, $msg);
 
-is ($tix->Count, scalar @ids, "number of returned tickets same as entered");
+my @cols =  $tix->DisplayColumns;
+
+ok ($cols[0] == undef, "We haven't explicitly asked to display anything");
+
+
 
- at expectedids = ();
-while (my $tick = $tix->Next) {
-    push @expectedids, $tick->Id;
-}
 
-ok (eq_array(\@ids, \@expectedids), "returned expected tickets");
 
 =end testing
 
@@ -499,6 +525,22 @@
 
 Returns the query that this object was initialized with
 
+=begin testing
+
+my $query = "SELECT id, Status WHERE Status = 'open'";
+
+my $tix = RT::Tickets->new($RT::SystemUser);
+
+my ($id, $msg)  = $tix->FromSQL($query);
+
+ok ($id, $msg);
+
+my $newq = $tix->Query();
+
+is ($query, $newq);
+
+=end testing
+
 =cut
 
 sub Query {
@@ -507,6 +549,71 @@
 }
 
 
+=head2 _DisplayColumn COL
+
+Add COL to this search's list of "Columns to display"
+
+COL can either be a
+
+LiteralColumnName
+"QuotedString" (Containing ##LiteralColumnName## to embed the colum name inside it)
+
+What else?
+
+
+
+=cut
+
+sub _DisplayColumn {
+    my $self = shift;
+    my $col  = shift;
+
+    my $colref;
+    if ( $col =~ s/\/STYLE:(.*?)$//io ) {
+        $colref->{'style'} = $1;
+    }
+    if ( $col =~ s/\/CLASS:(.*?)$//io ) {
+        $colref->{'class'} = $1;
+    }
+    if ( $col =~ s/\/TITLE:(.*?)$//io ) {
+        $colref->{'title'} = $1;
+    }
+    if ( $col =~ /__(.*?)__/gio ) {
+        my @subcols;
+        while ( $col =~ s/^(.*?)__(.*?)__//o ) {
+            push ( @subcols, $1 ) if ($1);
+            push ( @subcols, "__$2__" );
+            $colref->{'attribute'} = $2;
+        }
+        push ( @subcols, $col );
+        @{ $colref->{'output'} } = @subcols;
+    }
+    else {
+        @{ $colref->{'output'} } = ( "__" . $col . "__" );
+        $colref->{'attribute'} = $col;
+    }
+
+    if ( !$colref->{'title'} && grep { /^__(.*?)__$/io }
+        @{ $colref->{'output'} } )
+    {
+        $colref->{'title'}     = $1;
+        $colref->{'attribute'} = $1;
+    }
+    push @{ $self->{'_sql_columns_to_display'} }, $colref;
+
+}
+
+=head2 DisplayColumns 
+
+Returns an array of the columns to show in the printed results of this object
+
+=cut
+
+sub DisplayColumns {
+    my $self = shift;
+    return (@{$self->{'_sql_columns_to_display'}});
+}
+
 
 1;
 

Modified: rt/branches/rt-3.1/lib/RT/Transaction.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/Transaction.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/Transaction.pm	Sat Jul 10 02:05:15 2004
@@ -46,7 +46,6 @@
 
 package RT::Transaction;
 use RT::Record; 
-use RT::Ticket;
 
 
 use vars qw( @ISA );
@@ -67,13 +66,16 @@
 
 Create takes a hash of values and creates a row in the database:
 
-  int(11) 'EffectiveTicket'.
-  int(11) 'Ticket'.
+  varchar(64) 'ObjectType'.
+  int(11) 'ObjectId'.
   int(11) 'TimeTaken'.
   varchar(20) 'Type'.
   varchar(40) 'Field'.
   varchar(255) 'OldValue'.
   varchar(255) 'NewValue'.
+  varchar(255) 'ReferenceType'.
+  int(11) 'OldReference'.
+  int(11) 'NewReference'.
   varchar(255) 'Data'.
 
 =cut
@@ -84,24 +86,30 @@
 sub Create {
     my $self = shift;
     my %args = ( 
-                EffectiveTicket => '0',
-                Ticket => '0',
+                ObjectType => '',
+                ObjectId => '0',
                 TimeTaken => '0',
                 Type => '',
                 Field => '',
                 OldValue => '',
                 NewValue => '',
+                ReferenceType => '',
+                OldReference => '',
+                NewReference => '',
                 Data => '',
 
 		  @_);
     $self->SUPER::Create(
-                         EffectiveTicket => $args{'EffectiveTicket'},
-                         Ticket => $args{'Ticket'},
+                         ObjectType => $args{'ObjectType'},
+                         ObjectId => $args{'ObjectId'},
                          TimeTaken => $args{'TimeTaken'},
                          Type => $args{'Type'},
                          Field => $args{'Field'},
                          OldValue => $args{'OldValue'},
                          NewValue => $args{'NewValue'},
+                         ReferenceType => $args{'ReferenceType'},
+                         OldReference => $args{'OldReference'},
+                         NewReference => $args{'NewReference'},
                          Data => $args{'Data'},
 );
 
@@ -118,56 +126,42 @@
 =cut
 
 
-=head2 EffectiveTicket
+=head2 ObjectType
 
-Returns the current value of EffectiveTicket. 
-(In the database, EffectiveTicket is stored as int(11).)
+Returns the current value of ObjectType. 
+(In the database, ObjectType is stored as varchar(64).)
 
 
 
-=head2 SetEffectiveTicket VALUE
+=head2 SetObjectType VALUE
 
 
-Set EffectiveTicket to VALUE. 
+Set ObjectType to VALUE. 
 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, EffectiveTicket will be stored as a int(11).)
+(In the database, ObjectType will be stored as a varchar(64).)
 
 
 =cut
 
 
-=head2 Ticket
+=head2 ObjectId
 
-Returns the current value of Ticket. 
-(In the database, Ticket is stored as int(11).)
+Returns the current value of ObjectId. 
+(In the database, ObjectId is stored as int(11).)
 
 
 
-=head2 SetTicket VALUE
+=head2 SetObjectId VALUE
 
 
-Set Ticket to VALUE. 
+Set ObjectId to VALUE. 
 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, Ticket will be stored as a int(11).)
+(In the database, ObjectId will be stored as a int(11).)
 
 
 =cut
 
 
-=head2 TicketObj
-
-Returns the Ticket Object which has the id returned by Ticket
-
-
-=cut
-
-sub TicketObj {
-	my $self = shift;
-	my $Ticket =  RT::Ticket->new($self->CurrentUser);
-	$Ticket->Load($self->__Value('Ticket'));
-	return($Ticket);
-}
-
 =head2 TimeTaken
 
 Returns the current value of TimeTaken. 
@@ -258,6 +252,60 @@
 =cut
 
 
+=head2 ReferenceType
+
+Returns the current value of ReferenceType. 
+(In the database, ReferenceType is stored as varchar(255).)
+
+
+
+=head2 SetReferenceType VALUE
+
+
+Set ReferenceType to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ReferenceType will be stored as a varchar(255).)
+
+
+=cut
+
+
+=head2 OldReference
+
+Returns the current value of OldReference. 
+(In the database, OldReference is stored as int(11).)
+
+
+
+=head2 SetOldReference VALUE
+
+
+Set OldReference to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, OldReference will be stored as a int(11).)
+
+
+=cut
+
+
+=head2 NewReference
+
+Returns the current value of NewReference. 
+(In the database, NewReference is stored as int(11).)
+
+
+
+=head2 SetNewReference VALUE
+
+
+Set NewReference to VALUE. 
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, NewReference will be stored as a int(11).)
+
+
+=cut
+
+
 =head2 Data
 
 Returns the current value of Data. 
@@ -300,9 +348,9 @@
      
         id =>
 		{read => 1, type => 'int(11)', default => ''},
-        EffectiveTicket => 
-		{read => 1, write => 1, type => 'int(11)', default => '0'},
-        Ticket => 
+        ObjectType => 
+		{read => 1, write => 1, type => 'varchar(64)', default => ''},
+        ObjectId => 
 		{read => 1, write => 1, type => 'int(11)', default => '0'},
         TimeTaken => 
 		{read => 1, write => 1, type => 'int(11)', default => '0'},
@@ -314,6 +362,12 @@
 		{read => 1, write => 1, type => 'varchar(255)', default => ''},
         NewValue => 
 		{read => 1, write => 1, type => 'varchar(255)', default => ''},
+        ReferenceType => 
+		{read => 1, write => 1, type => 'varchar(255)', default => ''},
+        OldReference => 
+		{read => 1, write => 1, type => 'int(11)', default => ''},
+        NewReference => 
+		{read => 1, write => 1, type => 'int(11)', default => ''},
         Data => 
 		{read => 1, write => 1, type => 'varchar(255)', default => ''},
         Creator => 

Modified: rt/branches/rt-3.1/lib/RT/Transaction_Overlay.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/Transaction_Overlay.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/Transaction_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -75,7 +75,6 @@
     my %args = (
         id             => undef,
         TimeTaken      => 0,
-        Ticket         => 0,
         Type           => 'undefined',
         Data           => '',
         Field          => undef,
@@ -84,24 +83,36 @@
         MIMEObj        => undef,
         ActivateScrips => 1,
         CommitScrips => 1,
+	ObjectType => 'RT::Ticket',
+	ObjectId => 0,
+	ReferenceType => undef,
+        OldReference       => undef,
+        NewReference       => undef,
         @_
     );
 
+    $args{ObjectId} ||= $args{Ticket};
+
     #if we didn't specify a ticket, we need to bail
-    unless ( $args{'Ticket'} ) {
-        return ( 0, $self->loc( "Transaction->Create couldn't, as you didn't specify a ticket id"));
+    unless ( $args{'ObjectId'} && $args{'ObjectType'}) {
+        return ( 0, $self->loc( "Transaction->Create couldn't, as you didn't specify an object type and id"));
     }
 
 
 
     #lets create our transaction
-    my %params = (Ticket    => $args{'Ticket'},
+    my %params = (
         Type      => $args{'Type'},
         Data      => $args{'Data'},
         Field     => $args{'Field'},
         OldValue  => $args{'OldValue'},
         NewValue  => $args{'NewValue'},
-        Created   => $args{'Created'}
+        Created   => $args{'Created'},
+	ObjectType => $args{'ObjectType'},
+	ObjectId => $args{'ObjectId'},
+	ReferenceType => $args{'ReferenceType'},
+	OldReference => $args{'OldReference'},
+	NewReference => $args{'NewReference'},
     );
 
     # Parameters passed in during an import that we probably don't want to touch, otherwise
@@ -116,7 +127,7 @@
 
     #Provide a way to turn off scrips if we need to
         $RT::Logger->debug('About to think about scrips for transaction' .$self->Id);            
-    if ( $args{'ActivateScrips'} ) {
+    if ( $args{'ActivateScrips'} and $args{'ObjectType'} eq 'RT::Ticket' ) {
        $self->{'scrips'} = RT::Scrips->new($RT::SystemUser);
 
         $RT::Logger->debug('About to prepare scrips for transaction' .$self->Id);            
@@ -124,7 +135,7 @@
         $self->{'scrips'}->Prepare(
             Stage       => 'TransactionCreate',
             Type        => $args{'Type'},
-            Ticket      => $args{'Ticket'},
+            Ticket      => $args{'ObjectId'},
             Transaction => $self->id,
         );
         if ($args{'CommitScrips'} ) {
@@ -493,13 +504,15 @@
         return $self->loc("No transaction type specified");
     }
 
+    my $obj_type = $self->FriendlyObjectType;
+
     if ( $type eq 'Create' ) {
-        return ($self->loc("Ticket created"));
+        return ($self->loc("[_1] created", $obj_type));
     }
     elsif ( $type =~ /Status/ ) {
         if ( $self->Field eq 'Status' ) {
             if ( $self->NewValue eq 'deleted' ) {
-                return ($self->loc("Ticket deleted"));
+                return ($self->loc("[_1] deleted", $obj_type));
             }
             else {
                 return ( $self->loc("Status changed from [_1] to [_2]", $self->loc($self->OldValue), $self->loc($self->NewValue) ));
@@ -721,6 +734,7 @@
 
 sub IsInbound {
     my $self = shift;
+    $self->ObjectType eq 'RT::Ticket' or return undef;
     return ( $self->TicketObj->IsRequestor( $self->CreatorObj->PrincipalId ) );
 }
 
@@ -837,6 +851,77 @@
 
 # }}}
 
+sub Ticket {
+    my $self = shift;
+    return $self->ObjectId;
+}
+
+sub TicketObj {
+    my $self = shift;
+    return $self->Object;
+}
+
+sub OldValue {
+    my $self = shift;
+    if (my $type = $self->__Value('ReferenceType')) {
+	my $Object = $type->new($self->CurrentUser);
+	$Object->Load($self->__Value('OldReference'));
+	return $Object->Content;
+    }
+    else {
+	return $self->__Value('OldValue');
+    }
+}
+
+sub NewValue {
+    my $self = shift;
+    if (my $type = $self->__Value('ReferenceType')) {
+	my $Object = $type->new($self->CurrentUser);
+	$Object->Load($self->__Value('NewReference'));
+	return $Object->Content;
+    }
+    else {
+	return $self->__Value('NewValue');
+    }
+}
+
+sub Object {
+    my $self  = shift;
+    my $Object = $self->__Value('ObjectType')->new($self->CurrentUser);
+    $Object->Load($self->__Value('ObjectId'));
+    return($Object);
+}
+
+sub FriendlyObjectType {
+    my $self = shift;
+    my $type = $self->ObjectType or return undef;
+    $type =~ s/^RT:://;
+    return $self->loc($type);
+}
+
+sub UpdateCustomFields {
+    my ($self, %args) = @_;
+    my $args_ref = $args{ARGSRef} or return;
+
+    foreach my $arg ( keys %$args_ref ) {
+        $arg =~ /^(?:Transaction)?CustomField-(\d+).*?(?<!-Magic)$/ or next;
+	my $cfid = $1;
+	my $values = $args_ref->{$arg};
+	foreach my $value ( UNIVERSAL::isa($values, 'ARRAY') ? @$values : $values ) {
+	    next unless length($value);
+	    $self->_AddCustomFieldValue(
+		Field => $cfid,
+		Value => $value,
+		RecordTransaction => 0,
+	    );
+	}
+    }
+}
+
+sub _LookupTypes {
+    "RT::Queue-RT::Ticket-RT::Transaction";
+}
+
 # Transactions don't change. by adding this cache congif directiove, we don't lose pathalogically on long tickets.
 sub _CacheConfig {
   {

Modified: rt/branches/rt-3.1/lib/RT/Transactions_Overlay.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/Transactions_Overlay.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/Transactions_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -63,6 +63,30 @@
 }
 # }}}
 
+=head2 Limit
+
+A wrapper around SUPER::Limit to catch migration issues
+
+=cut
+
+sub Limit {
+	my $self = shift;
+	my %args = (@_);
+
+	if ($args{'FIELD'} eq 'Ticket') {
+		Carp::cluck("Historical code calling RT::Transactions::Limit with a 'Ticket'.  This deprecated API will be deleted in 3.6");
+		$self->SUPER::Limit(FIELD => 'ObjectType', OPERATOR => '=', VALUE =>'RT::Ticket');
+		$args{'FIELD'} = 'ObjectId';
+		$self->SUPER::Limit(%args);
+
+	} else {
+
+		$self->SUPER::Limit(%args);
+	}
+
+
+}
+
 # {{{ sub Next
 sub Next {
     my $self = shift;

Modified: rt/branches/rt-3.1/lib/RT/User_Overlay.pm
==============================================================================
--- rt/branches/rt-3.1/lib/RT/User_Overlay.pm	(original)
+++ rt/branches/rt-3.1/lib/RT/User_Overlay.pm	Sat Jul 10 02:05:15 2004
@@ -149,9 +149,13 @@
         Privileged => 0,
         Disabled => 0,
         EmailAddress => '',
+        _RecordTransaction => 1,
         @_    # get the real argumentlist
     );
 
+    # remove the value so it does not cripple SUPER::Create
+    my $record_transaction = delete $args{'_RecordTransaction'};
+
     #Check the ACL
     unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
         return ( 0, $self->loc('No permission to create users') );
@@ -292,7 +296,12 @@
     }
 
 
+    if ( $record_transaction ) {
+	$self->_NewTransaction( Type => "Create" );
+    }
+
     $RT::Handle->Commit;
+
     return ( $id, $self->loc('User created') );
 }
 
@@ -1002,7 +1011,7 @@
 
 =head2 _GeneratePassword PASSWORD
 
-returns an MD5 hash of the password passed in, in base64 encoding.
+returns an MD5 hash of the password passed in, in hexadecimal encoding.
 
 =cut
 
@@ -1012,6 +1021,23 @@
 
     my $md5 = Digest::MD5->new();
     $md5->add($password);
+    return ($md5->hexdigest);
+
+}
+
+=head2 _GeneratePasswordBase64 PASSWORD
+
+returns an MD5 hash of the password passed in, in base64 encoding
+(obsoleted now).
+
+=cut
+
+sub _GeneratePasswordBase64 {
+    my $self = shift;
+    my $password = shift;
+
+    my $md5 = Digest::MD5->new();
+    $md5->add($password);
     return ($md5->b64digest);
 
 }
@@ -1055,9 +1081,12 @@
     }
 
     #  if it's a historical password we say ok.
-
-    if ( $self->__Value('Password') eq crypt( $value, $self->__Value('Password') ) ) {
-        return (1);
+    if ($self->__Value('Password') eq crypt($value, $self->__Value('Password'))
+        or $self->_GeneratePasswordBase64($value) eq $self->__Value('Password'))
+    {
+        # ...but upgrade the legacy password inplace.
+        $self->SUPER::SetPassword( $self->_GeneratePassword($value) );
+        return(1);
     }
 
     # no password check has succeeded. get out
@@ -1109,7 +1138,7 @@
 ok(my $u = RT::User->new($RT::SystemUser));
 ok($u->Load(1), "Loaded the first user");
 ok($u->PrincipalObj->ObjectId == 1, "user 1 is the first principal");
-ok($u->PrincipalObj->PrincipalType eq 'User' , "Principal 1 is a user, not a group");
+is($u->PrincipalObj->PrincipalType, 'User' , "Principal 1 is a user, not a group");
 
 =end testing
 
@@ -1273,7 +1302,7 @@
 my $new_tick2 = RT::Ticket->new($RT::SystemUser);
 my ($tick2id, $tickmsg) = $new_tick2->Create(Subject=> 'ACL Test 2', Queue =>$q_as_system->Id);
 ok($tick2id, "Created ticket: $tick2id");
-ok($new_tick2->QueueObj->id eq $q_as_system->Id, "Created a new ticket in queue 1");
+is($new_tick2->QueueObj->id, $q_as_system->Id, "Created a new ticket in queue 1");
 
 
 # make sure that the user can't do this without subgroup membership
@@ -1523,6 +1552,8 @@
     my %args = (
         Field => undef,
         Value => undef,
+	TransactionType   => 'Set',
+	RecordTransaction => 1,
         @_
     );
 
@@ -1536,13 +1567,29 @@
         return ( 0, $self->loc("Permission Denied") );
     }
 
-    #Set the new value
-    my ( $ret, $msg ) = $self->SUPER::_Set(
-        Field => $args{'Field'},
-        Value => $args{'Value'}
-    );
-
-    return ( $ret, $msg );
+    my $Old = $self->SUPER::_Value("$args{'Field'}");
+    
+    my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
+					  Value => $args{'Value'} );
+    
+    #If we can't actually set the field to the value, don't record
+    # a transaction. instead, get out of here.
+    if ( $ret == 0 ) { return ( 0, $msg ); }
+
+    if ( $args{'RecordTransaction'} == 1 ) {
+
+        my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
+                                               Type => $args{'TransactionType'},
+                                               Field     => $args{'Field'},
+                                               NewValue  => $args{'Value'},
+                                               OldValue  => $Old,
+                                               TimeTaken => $args{'TimeTaken'},
+        );
+        return ( $Trans, scalar $TransObj->Description );
+    }
+    else {
+        return ( $ret, $msg );
+    }
 }
 
 # }}}
@@ -1592,6 +1639,14 @@
 
 # }}}
 
+sub BasicColumns {
+    (
+	[ Name => 'User Id' ],
+	[ EmailAddress => 'Email' ],
+	[ RealName => 'Name' ],
+	[ Organization => 'Organization' ],
+    );
+}
 
 1;
 

Modified: rt/branches/rt-3.1/releng.cnf
==============================================================================
--- rt/branches/rt-3.1/releng.cnf	(original)
+++ rt/branches/rt-3.1/releng.cnf	Sat Jul 10 02:05:15 2004
@@ -1,7 +1,7 @@
 PRODUCT			        = rt
-TAG			            = 3.2.0
+TAG			            = 3.3.4
 CANONICAL_REPO		    = svn+ssh://svn.bestpractical.com/svn/bps-public/rt/
 TAGS		            = tags/
-TRUNK	                = branches/rt-3.1/
+TRUNK	                = branches/rt-3.3/
 TMP_DIR			        = /tmp
 RELEASE_DIR		        = /home/ftp/pub/rt/devel

Modified: rt/branches/rt-3.1/sbin/extract-message-catalog
==============================================================================
--- rt/branches/rt-3.1/sbin/extract-message-catalog	(original)
+++ rt/branches/rt-3.1/sbin/extract-message-catalog	Sat Jul 10 02:05:15 2004
@@ -174,6 +174,7 @@
     while (@lines) {
         my $msghdr = "";
         $msghdr .= shift @lines while ( $lines[0] && $lines[0] !~ /^msgid/ );
+        
         my $msgid  = shift @lines;
         my $msgstr = "";
         $msgstr .= shift @lines while ( $lines[0] && $lines[0] =~ /^(msgstr|")/ );
@@ -182,8 +183,8 @@
 
         chomp $msgid;
         chomp $msgstr;
-        $msgid  =~ s/^msgid "(.*)"$/$1/    or warn $msgid;
-        $msgstr =~ s/^msgstr "(.*)"$/$1/ms or warn $msgstr;
+        $msgid  =~ s/^msgid "(.*)"\s*?$/$1/ms    or warn "$msgid in $file";
+        $msgstr =~ s/^msgstr "(.*)"\s*?$/$1/ms or warn "$msgstr  in $file";
 
         $Lexicon{$msgid} = $msgstr;
         $Header{$msgid}  = $msghdr;
@@ -208,6 +209,10 @@
 
         my %seen;
         $out .= $Header{$_} if exists $Header{$_};
+
+
+
+        next if (!$f && $_ && !$Lexicon{$_});
         if ( $f && $f !~ /^\s+$/ ) {
 
             $out .= "#: $f\n";

Added: rt/branches/rt-3.1/sbin/rt-dump-database.in
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/sbin/rt-dump-database.in	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,102 @@
+#!@PERL@ -w
+
+use strict;
+use lib "@RT_LIB_PATH@";
+use RT;
+
+RT::LoadConfig();
+RT::Init();
+
+my $LocalOnly = shift || 1;
+
+my %RV;
+my %Ignore = (
+    All => [qw(
+	id Created Creator LastUpdated LastUpdatedBy
+    )],
+    Templates => [qw(
+	TranslationOf
+    )],
+);
+
+my $SystemUserId = $RT::SystemUser->Id;
+my @classes = qw(
+    Users Groups Queues ScripActions ScripConditions
+    Templates Scrips ACL CustomFields
+);
+foreach my $class (@classes) {
+    require "RT/$class.pm";
+    my $objects = "RT::$class"->new($RT::SystemUser);
+    $objects->{find_disabled_rows} = 1;
+    $objects->UnLimit;
+    $objects->OrderBy( FIELD => 'Id' );
+
+    if ($LocalOnly) {
+	$objects->Limit( FIELD => 'LastUpdatedBy', OPERATOR => '!=', VALUE => $SystemUserId )
+	    unless $class eq 'Groups';
+	$objects->Limit( FIELD => 'Id', OPERATOR => '!=', VALUE => $SystemUserId )
+	    if $class eq 'Users';
+    }
+
+    my %fields;
+    while (my $obj = $objects->Next) {
+	next if $obj->can('LastUpdatedBy') and $obj->LastUpdatedBy == $SystemUserId;
+
+	if (!%fields) {
+	    %fields = map { $_ => 1 } keys %{$obj->_ClassAccessible};
+	    delete @fields{
+		@{$Ignore{$class}||=[]},
+		@{$Ignore{All}||=[]},
+	    };
+	}
+
+	my $rv;
+	# next if $obj-> # skip default names
+	foreach my $field (sort keys %fields) {
+	    my $value = $obj->__Value($field);
+	    $rv->{$field} = $value if length($value);
+	}
+	delete $rv->{Disabled} unless $rv->{Disabled};
+
+	foreach my $record (map { /ACL/ ? 'ACE' : substr($_, 0, -1) } @classes) {
+	    foreach my $key (map "$record$_", ('', 'Id')) {
+		next unless exists $rv->{$key};
+		my $id = $rv->{$key} or next;
+		my $obj = "RT::$record"->new($RT::SystemUser);
+		$obj->LoadByCols( Id => $id ) or next;
+		$rv->{$key} = $obj->__Value('Name') || 0;
+	    }
+	}
+
+	if ($class eq 'Users') {
+	    $rv->{Privileged} = int($obj->Privileged);
+	}
+	elsif ($class eq 'CustomFields') {
+	    my $values = $obj->Values;
+	    while (my $value = $values->Next) {
+		push @{$rv->{Values}}, {
+		    map { ($_ => $value->__Value($_)) } qw(
+			Name Description SortOrder
+		    ),
+		};
+	    }
+	}
+
+	if (eval { require RT::Attributes; 1 }) {
+	    my $attributes = $obj->Attributes;
+	    while (my $attribute = $attributes->Next) {
+		my $content = $attribute->Content;
+		$rv->{Attributes}{$attribute->Name} = $content if length($content);
+	    }
+	}
+
+	push @{$RV{$class}}, $rv;
+    }
+}
+
+use Data::Dumper;
+print Data::Dumper->Dump(
+    [map { $RV{$_} || [] } @classes],
+    [map { "*$_" } @classes],
+);
+

Added: rt/branches/rt-3.1/spec/schema.txt
==============================================================================
--- (empty file)
+++ rt/branches/rt-3.1/spec/schema.txt	Sat Jul 10 02:05:15 2004
@@ -0,0 +1,51 @@
+CF Schema redesign V5.
+======================
+
+TABLE CustomFields (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Name varchar(200) NULL  ,
+  Type varchar(200) NULL  ,	# Changed -- 'Single' and 'Multiple' is moved out
+  MaxValues integer,		# New -- was 'Single'(1) and 'Multiple'(0)
+  Pattern varchar(255) NULL  ,	# New -- Must validate against this
+  LookupType varchar(255) NOT NULL,	# Lookup paths like "RT::Queue-RT::Ticket"
+  Description varchar(255) NULL  ,
+  SortOrder integer NOT NULL DEFAULT 0  ,   # not used!
+)
+
+TABLE ObjectCustomFields (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  CustomField int NOT NULL  ,		    # CustomField ID
+  ObjectId integer NOT NULL,		    # Final id of toplevel parent, or the
+					    # object itself if ParentType is empty;
+					    # 0 means global as usual.
+  SortOrder integer NOT NULL DEFAULT 0  ,
+);
+
+TABLE ObjectCustomFieldValues (	# New -- Replaces TicketCustomFieldValues
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  CustomField int NOT NULL  ,
+  ObjectType varchar(255) NOT NULL,		    # Final target of the Object
+  ObjectId int NOT NULL  ,		    # New -- Replaces Ticket
+  SortOrder integer NOT NULL DEFAULT 0  ,
+
+  Current BOOL DEFAULT '1',		    # New -- whether the value was current
+  Content varchar(255) NULL  ,
+  LargeContent LONGTEXT NULL,		    # New -- to hold 255+ strings
+  ContentType varchar(80) NULL,		    # New -- only text/* gets searched
+  ContentEncoding varchar(80) NULL  ,	    # New -- for binary Content
+)
+
+TABLE Transactions (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  ObjectType varchar(255) NULL,		    # Final target of the Object
+  ObjectId integer NOT NULL DEFAULT 0  ,    # Changed -- was Ticket
+  TimeTaken integer NOT NULL DEFAULT 0  ,
+  Type varchar(20) NULL  ,
+  Field varchar(40) NULL  ,
+  OldValue varchar(255) NULL  ,
+  NewValue varchar(255) NULL  ,
+  ReferenceType varchar(255) NULL,	    # Currently "RT::OCFV" only
+  OldReference integer NULL  ,		    # New -- Id of ReferenceType
+  NewReference integer NULL  ,		    # New -- Id of ReferenceType
+  Data varchar(255) NULL  ,
+)


More information about the Rt-commit mailing list